2021-06-14 20:43:36 +10:00
/* OpenTally: Open-source election vote counting
* Copyright © 2021 Lee Yingtong Li ( RunasSudo )
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https ://www.gnu.org/licenses/>.
* /
2021-06-16 13:00:54 +10:00
use super ::{ ExclusionMethod , NextPreferencesEntry , NextPreferencesResult , STVError , STVOptions , SumSurplusTransfersMode , SurplusMethod , SurplusOrder } ;
2021-08-04 13:46:32 +10:00
use super ::sample ;
2021-06-14 20:43:36 +10:00
2021-06-28 00:56:28 +10:00
use crate ::constraints ;
2021-06-29 15:31:38 +10:00
use crate ::election ::{ Candidate , CandidateState , CountState , Parcel , Vote } ;
2021-06-14 20:43:36 +10:00
use crate ::numbers ::Number ;
2021-06-29 15:31:38 +10:00
use crate ::ties ;
2021-06-14 20:43:36 +10:00
use itertools ::Itertools ;
use std ::cmp ::max ;
use std ::ops ;
2021-06-16 13:00:54 +10:00
/// Distribute first preference votes according to the Gregory method
pub fn distribute_first_preferences < N : Number > ( state : & mut CountState < N > ) {
let votes = state . election . ballots . iter ( ) . map ( | b | Vote {
ballot : b ,
value : b . orig_value . clone ( ) ,
up_to_pref : 0 ,
} ) . collect ( ) ;
let result = super ::next_preferences ( state , votes ) ;
// Transfer candidate votes
for ( candidate , entry ) in result . candidates . into_iter ( ) {
2021-07-19 23:15:17 +10:00
let parcel = Parcel {
votes : entry . votes ,
source_order : 0 ,
} ;
2021-06-16 13:00:54 +10:00
let count_card = state . candidates . get_mut ( candidate ) . unwrap ( ) ;
count_card . parcels . push ( parcel ) ;
count_card . transfer ( & entry . num_votes ) ;
}
// Transfer exhausted votes
2021-07-19 23:15:17 +10:00
let parcel = Parcel {
votes : result . exhausted . votes ,
source_order : 0 ,
} ;
2021-06-16 13:00:54 +10:00
state . exhausted . parcels . push ( parcel ) ;
state . exhausted . transfer ( & result . exhausted . num_votes ) ;
state . kind = None ;
state . title = " First preferences " . to_string ( ) ;
state . logger . log_literal ( " First preferences distributed. " . to_string ( ) ) ;
}
2021-08-03 18:38:45 +10:00
/// Distribute the largest surplus according to the Gregory or random subset method, based on [STVOptions::surplus]
2021-08-05 20:18:10 +10:00
///
/// Returns `true` if any surpluses were distributed.
2021-06-14 20:43:36 +10:00
pub fn distribute_surpluses < N : Number > ( state : & mut CountState < N > , opts : & STVOptions ) -> Result < bool , STVError >
where
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N > ,
for < ' r > & ' r N : ops ::Div < & ' r N , Output = N > ,
for < ' r > & ' r N : ops ::Neg < Output = N >
{
let quota = state . quota . as_ref ( ) . unwrap ( ) ;
2021-06-29 15:31:38 +10:00
let has_surplus : Vec < & Candidate > = state . election . candidates . iter ( ) // Present in order in case of tie
. filter ( | c | {
let cc = & state . candidates [ c ] ;
2021-07-19 23:15:17 +10:00
& cc . votes > quota & & cc . parcels . iter ( ) . any ( | p | ! p . votes . is_empty ( ) )
2021-06-29 15:31:38 +10:00
} )
2021-06-14 20:43:36 +10:00
. collect ( ) ;
if ! has_surplus . is_empty ( ) {
2021-06-16 13:00:54 +10:00
let total_surpluses = has_surplus . iter ( )
2021-06-29 15:31:38 +10:00
. fold ( N ::new ( ) , | acc , c | acc + & state . candidates [ c ] . votes - quota ) ;
2021-06-16 13:00:54 +10:00
2021-06-14 20:43:36 +10:00
// Determine if surplues can be deferred
if opts . defer_surpluses {
if super ::can_defer_surpluses ( state , opts , & total_surpluses ) {
state . logger . log_literal ( format! ( " Distribution of surpluses totalling {:.dps$} votes will be deferred. " , total_surpluses , dps = opts . pp_decimals ) ) ;
return Ok ( false ) ;
}
}
2021-06-29 15:31:38 +10:00
// Distribute top candidate's surplus
let max_cands = match opts . surplus_order {
2021-06-14 20:43:36 +10:00
SurplusOrder ::BySize = > {
2021-06-29 15:31:38 +10:00
ties ::multiple_max_by ( & has_surplus , | c | & state . candidates [ c ] . votes )
2021-06-14 20:43:36 +10:00
}
SurplusOrder ::ByOrder = > {
2021-06-29 15:31:38 +10:00
ties ::multiple_min_by ( & has_surplus , | c | state . candidates [ c ] . order_elected )
2021-06-14 20:43:36 +10:00
}
2021-06-29 15:31:38 +10:00
} ;
let elected_candidate = if max_cands . len ( ) > 1 {
2021-07-31 17:51:09 +10:00
super ::choose_highest ( state , opts , max_cands , " Which candidate's surplus to distribute? " ) ?
2021-06-14 20:43:36 +10:00
} else {
2021-06-29 15:31:38 +10:00
max_cands [ 0 ]
} ;
2021-06-14 20:43:36 +10:00
2021-08-03 18:38:45 +10:00
match opts . surplus {
SurplusMethod ::WIG | SurplusMethod ::UIG | SurplusMethod ::EG = > { distribute_surplus ( state , & opts , elected_candidate ) ; }
2021-08-04 13:46:32 +10:00
SurplusMethod ::Cincinnati | SurplusMethod ::Hare = > { sample ::distribute_surplus ( state , & opts , elected_candidate ) ? ; }
2021-08-03 18:38:45 +10:00
_ = > unreachable! ( )
}
2021-06-14 20:43:36 +10:00
return Ok ( true ) ;
}
return Ok ( false ) ;
}
2021-08-05 18:41:39 +10:00
/// Return the denominator of the surplus fraction
///
2021-08-05 20:18:10 +10:00
/// Returns `None` if the value of transferable votes <= surplus (i.e. all transferable votes are transferred at values received).
2021-06-14 20:43:36 +10:00
fn calculate_surplus_denom < N : Number > ( surplus : & N , result : & NextPreferencesResult < N > , transferable_votes : & N , weighted : bool , transferable_only : bool ) -> Option < N >
where
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N >
{
if transferable_only {
let total_units = if weighted { & result . total_votes } else { & result . total_ballots } ;
let exhausted_units = if weighted { & result . exhausted . num_votes } else { & result . exhausted . num_ballots } ;
let transferable_units = total_units - exhausted_units ;
if transferable_votes > surplus {
return Some ( transferable_units ) ;
} else {
return None ;
}
} else {
if weighted {
return Some ( result . total_votes . clone ( ) ) ;
} else {
return Some ( result . total_ballots . clone ( ) ) ;
}
}
}
/// Return the reweighted value of the vote after being transferred
fn reweight_vote < N : Number > (
num_votes : & N ,
num_ballots : & N ,
surplus : & N ,
weighted : bool ,
surplus_fraction : & Option < N > ,
surplus_denom : & Option < N > ,
round_tvs : Option < usize > ,
rounding : Option < usize > ) -> N
{
let mut result ;
match surplus_denom {
Some ( v ) = > {
if let Some ( _ ) = round_tvs {
// Rounding requested: use the rounded transfer value
if weighted {
result = num_votes . clone ( ) * surplus_fraction . as_ref ( ) . unwrap ( ) ;
} else {
result = num_ballots . clone ( ) * surplus_fraction . as_ref ( ) . unwrap ( ) ;
}
} else {
// Avoid unnecessary rounding error by first multiplying by the surplus
if weighted {
result = num_votes . clone ( ) * surplus / v ;
} else {
result = num_ballots . clone ( ) * surplus / v ;
}
}
}
None = > {
result = num_votes . clone ( ) ;
}
}
// Round down if requested
if let Some ( dps ) = rounding {
result . floor_mut ( dps ) ;
}
return result ;
}
/// Compute the number of votes to credit to a continuing candidate during a surplus transfer, based on [STVOptions::sum_surplus_transfers]
fn sum_surplus_transfers < N : Number > ( entry : & NextPreferencesEntry < N > , surplus : & N , is_weighted : bool , surplus_fraction : & Option < N > , surplus_denom : & Option < N > , _state : & mut CountState < N > , opts : & STVOptions ) -> N
where
for < ' r > & ' r N : ops ::Div < & ' r N , Output = N > ,
{
match opts . sum_surplus_transfers {
SumSurplusTransfersMode ::SingleStep = > {
// Calculate transfer across all votes
//state.logger.log_literal(format!("Transferring {:.0} ballot papers, totalling {:.dps$} votes.", entry.num_ballots, entry.num_votes, dps=opts.pp_decimals));
2021-08-03 16:46:21 +10:00
return reweight_vote ( & entry . num_votes , & entry . num_ballots , surplus , is_weighted , surplus_fraction , surplus_denom , opts . round_surplus_fractions , opts . round_votes ) ;
2021-06-14 20:43:36 +10:00
}
SumSurplusTransfersMode ::ByValue = > {
// Sum transfers by value
let mut result = N ::new ( ) ;
// Sort into parcels by value
let mut votes : Vec < & Vote < N > > = entry . votes . iter ( ) . collect ( ) ;
votes . sort_unstable_by ( | a , b | ( & a . value / & a . ballot . orig_value ) . cmp ( & ( & b . value / & b . ballot . orig_value ) ) ) ;
for ( _value , parcel ) in & votes . into_iter ( ) . group_by ( | v | & v . value / & v . ballot . orig_value ) {
let mut num_votes = N ::new ( ) ;
let mut num_ballots = N ::new ( ) ;
for vote in parcel {
num_votes + = & vote . value ;
num_ballots + = & vote . ballot . orig_value ;
}
//state.logger.log_literal(format!("Transferring {:.0} ballot papers, totalling {:.dps$} votes, received at value {:.dps2$}.", num_ballots, num_votes, value, dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
2021-08-03 16:46:21 +10:00
result + = reweight_vote ( & num_votes , & num_ballots , surplus , is_weighted , surplus_fraction , surplus_denom , opts . round_surplus_fractions , opts . round_votes ) ;
2021-06-14 20:43:36 +10:00
}
return result ;
}
SumSurplusTransfersMode ::PerBallot = > {
// Sum transfer per each individual ballot
// TODO: This could be moved to distribute_surplus to avoid looping over the votes and calculating transfer values twice
let mut result = N ::new ( ) ;
for vote in entry . votes . iter ( ) {
2021-08-03 16:46:21 +10:00
result + = reweight_vote ( & vote . value , & vote . ballot . orig_value , surplus , is_weighted , surplus_fraction , surplus_denom , opts . round_surplus_fractions , opts . round_votes ) ;
2021-06-14 20:43:36 +10:00
}
//state.logger.log_literal(format!("Transferring {:.0} ballot papers, totalling {:.dps$} votes.", entry.num_ballots, entry.num_votes, dps=opts.pp_decimals));
return result ;
}
}
}
/// Distribute the surplus of a given candidate according to the Gregory method, based on [STVOptions::surplus]
fn distribute_surplus < N : Number > ( state : & mut CountState < N > , opts : & STVOptions , elected_candidate : & Candidate )
where
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N > ,
for < ' r > & ' r N : ops ::Div < & ' r N , Output = N > ,
for < ' r > & ' r N : ops ::Neg < Output = N >
{
2021-08-03 18:38:45 +10:00
state . kind = Some ( " Surplus of " ) ;
state . title = String ::from ( & elected_candidate . name ) ;
2021-06-14 20:43:36 +10:00
state . logger . log_literal ( format! ( " Surplus of {} distributed. " , elected_candidate . name ) ) ;
2021-06-29 15:31:38 +10:00
let count_card = & state . candidates [ elected_candidate ] ;
2021-06-14 20:43:36 +10:00
let surplus = & count_card . votes - state . quota . as_ref ( ) . unwrap ( ) ;
let votes ;
match opts . surplus {
SurplusMethod ::WIG | SurplusMethod ::UIG = > {
// Inclusive Gregory
2021-07-19 23:15:17 +10:00
votes = state . candidates . get_mut ( elected_candidate ) . unwrap ( ) . concat_parcels ( ) ;
2021-06-14 20:43:36 +10:00
}
SurplusMethod ::EG = > {
// Exclusive Gregory
// Should be safe to unwrap() - or else how did we get a quota!
2021-07-19 23:15:17 +10:00
votes = state . candidates . get_mut ( elected_candidate ) . unwrap ( ) . parcels . pop ( ) . unwrap ( ) . votes ;
2021-06-14 20:43:36 +10:00
}
2021-08-03 18:38:45 +10:00
_ = > unreachable! ( )
2021-06-14 20:43:36 +10:00
}
// Count next preferences
let result = super ::next_preferences ( state , votes ) ;
// Transfer candidate votes
// TODO: Refactor??
let is_weighted = match opts . surplus {
SurplusMethod ::WIG = > { true }
SurplusMethod ::UIG | SurplusMethod ::EG = > { false }
2021-08-03 18:38:45 +10:00
_ = > unreachable! ( )
2021-06-14 20:43:36 +10:00
} ;
let transferable_votes = & result . total_votes - & result . exhausted . num_votes ;
let surplus_denom = calculate_surplus_denom ( & surplus , & result , & transferable_votes , is_weighted , opts . transferable_only ) ;
let mut surplus_fraction ;
match surplus_denom {
Some ( ref v ) = > {
surplus_fraction = Some ( surplus . clone ( ) / v ) ;
// Round down if requested
2021-08-03 16:46:21 +10:00
if let Some ( dps ) = opts . round_surplus_fractions {
2021-06-14 20:43:36 +10:00
surplus_fraction . as_mut ( ) . unwrap ( ) . floor_mut ( dps ) ;
}
if opts . transferable_only {
2021-06-29 15:31:38 +10:00
if & result . total_ballots - & result . exhausted . num_ballots = = N ::one ( ) {
state . logger . log_literal ( format! ( " Transferring 1 transferable ballot, totalling {:.dps$} transferable votes, with surplus fraction {:.dps2$} . " , transferable_votes , surplus_fraction . as_ref ( ) . unwrap ( ) , dps = opts . pp_decimals , dps2 = max ( opts . pp_decimals , 2 ) ) ) ;
} else {
state . logger . log_literal ( format! ( " Transferring {:.0} transferable ballots, totalling {:.dps$} transferable votes, with surplus fraction {:.dps2$} . " , & result . total_ballots - & result . exhausted . num_ballots , transferable_votes , surplus_fraction . as_ref ( ) . unwrap ( ) , dps = opts . pp_decimals , dps2 = max ( opts . pp_decimals , 2 ) ) ) ;
}
2021-06-14 20:43:36 +10:00
} else {
2021-06-29 15:31:38 +10:00
if result . total_ballots = = N ::one ( ) {
state . logger . log_literal ( format! ( " Transferring 1 ballot, totalling {:.dps$} votes, with surplus fraction {:.dps2$} . " , result . total_votes , surplus_fraction . as_ref ( ) . unwrap ( ) , dps = opts . pp_decimals , dps2 = max ( opts . pp_decimals , 2 ) ) ) ;
} else {
state . logger . log_literal ( format! ( " Transferring {:.0} ballots, totalling {:.dps$} votes, with surplus fraction {:.dps2$} . " , result . total_ballots , result . total_votes , surplus_fraction . as_ref ( ) . unwrap ( ) , dps = opts . pp_decimals , dps2 = max ( opts . pp_decimals , 2 ) ) ) ;
}
2021-06-14 20:43:36 +10:00
}
}
None = > {
surplus_fraction = None ;
2021-08-05 18:41:39 +10:00
// This can only happen if --transferable-only
if & result . total_ballots - & result . exhausted . num_ballots = = N ::one ( ) {
state . logger . log_literal ( format! ( " Transferring 1 transferable ballot, totalling {:.dps$} transferable votes, at values received. " , transferable_votes , dps = opts . pp_decimals ) ) ;
2021-06-14 20:43:36 +10:00
} else {
2021-08-05 18:41:39 +10:00
state . logger . log_literal ( format! ( " Transferring {:.0} transferable ballots, totalling {:.dps$} transferable votes, at values received. " , & result . total_ballots - & result . exhausted . num_ballots , transferable_votes , dps = opts . pp_decimals ) ) ;
2021-06-14 20:43:36 +10:00
}
}
}
let mut checksum = N ::new ( ) ;
for ( candidate , entry ) in result . candidates . into_iter ( ) {
// Credit transferred votes
let candidate_transfers = sum_surplus_transfers ( & entry , & surplus , is_weighted , & surplus_fraction , & surplus_denom , state , opts ) ;
let count_card = state . candidates . get_mut ( candidate ) . unwrap ( ) ;
count_card . transfer ( & candidate_transfers ) ;
checksum + = candidate_transfers ;
2021-07-19 23:15:17 +10:00
let mut parcel = Parcel {
votes : entry . votes ,
source_order : state . num_elected + state . num_excluded ,
} ;
2021-06-14 20:43:36 +10:00
// Reweight votes
2021-07-19 23:15:17 +10:00
for vote in parcel . votes . iter_mut ( ) {
2021-08-03 16:46:21 +10:00
vote . value = reweight_vote ( & vote . value , & vote . ballot . orig_value , & surplus , is_weighted , & surplus_fraction , & surplus_denom , opts . round_surplus_fractions , opts . round_values ) ;
2021-06-14 20:43:36 +10:00
}
count_card . parcels . push ( parcel ) ;
}
// Credit exhausted votes
let mut exhausted_transfers ;
if opts . transferable_only {
if transferable_votes > surplus {
// No ballots exhaust
exhausted_transfers = N ::new ( ) ;
} else {
exhausted_transfers = & surplus - & transferable_votes ;
if let Some ( dps ) = opts . round_votes {
exhausted_transfers . floor_mut ( dps ) ;
}
}
} else {
exhausted_transfers = sum_surplus_transfers ( & result . exhausted , & surplus , is_weighted , & surplus_fraction , & surplus_denom , state , opts ) ;
}
state . exhausted . transfer ( & exhausted_transfers ) ;
checksum + = exhausted_transfers ;
// Transfer exhausted votes
2021-07-19 23:15:17 +10:00
let parcel = Parcel {
votes : result . exhausted . votes ,
source_order : state . num_elected + state . num_excluded ,
} ;
2021-06-14 20:43:36 +10:00
state . exhausted . parcels . push ( parcel ) ;
// Finalise candidate votes
let count_card = state . candidates . get_mut ( elected_candidate ) . unwrap ( ) ;
count_card . transfers = - & surplus ;
count_card . votes . assign ( state . quota . as_ref ( ) . unwrap ( ) ) ;
checksum - = surplus ;
2021-06-22 15:23:46 +10:00
count_card . parcels . clear ( ) ; // Mark surpluses as done
2021-06-14 20:43:36 +10:00
// Update loss by fraction
state . loss_fraction . transfer ( & - checksum ) ;
}
2021-06-16 13:00:54 +10:00
/// Perform one stage of a candidate exclusion according to the Gregory method, based on [STVOptions::exclusion]
pub fn exclude_candidates < ' a , N : Number > ( state : & mut CountState < ' a , N > , opts : & STVOptions , excluded_candidates : Vec < & ' a Candidate > )
where
for < ' r > & ' r N : ops ::Div < & ' r N , Output = N > ,
{
// Used to give bulk excluded candidate the same order_elected
let order_excluded = state . num_excluded + 1 ;
for excluded_candidate in excluded_candidates . iter ( ) {
let count_card = state . candidates . get_mut ( excluded_candidate ) . unwrap ( ) ;
// Rust borrow checker is unhappy if we try to put this in exclude_hopefuls ??!
if count_card . state ! = CandidateState ::Excluded {
count_card . state = CandidateState ::Excluded ;
state . num_excluded + = 1 ;
count_card . order_elected = - ( order_excluded as isize ) ;
2021-06-27 21:57:24 +10:00
2021-06-28 00:56:28 +10:00
constraints ::update_constraints ( state , opts ) ;
2021-06-16 13:00:54 +10:00
}
}
// Determine votes to transfer in this stage
let mut votes = Vec ::new ( ) ;
let mut votes_remain ;
let mut checksum = N ::new ( ) ;
match opts . exclusion {
ExclusionMethod ::SingleStage = > {
// Exclude in one round
for excluded_candidate in excluded_candidates . iter ( ) {
let count_card = state . candidates . get_mut ( excluded_candidate ) . unwrap ( ) ;
2021-07-19 23:15:17 +10:00
votes . append ( & mut count_card . concat_parcels ( ) ) ;
2021-06-16 13:00:54 +10:00
count_card . parcels . clear ( ) ;
// Update votes
let votes_transferred = votes . iter ( ) . fold ( N ::new ( ) , | acc , v | acc + & v . value ) ;
checksum - = & votes_transferred ;
count_card . transfer ( & - votes_transferred ) ;
}
votes_remain = false ;
}
ExclusionMethod ::ByValue = > {
// Exclude by value
2021-07-19 18:35:23 +10:00
let excluded_with_votes : Vec < & & Candidate > = excluded_candidates . iter ( ) . filter ( | c | ! state . candidates [ * c ] . parcels . is_empty ( ) ) . collect ( ) ;
2021-06-16 13:00:54 +10:00
2021-07-19 18:35:23 +10:00
if excluded_with_votes . is_empty ( ) {
votes_remain = false ;
} else {
// If candidates to exclude still having votes, select only those with the greatest value
let max_value = excluded_with_votes . iter ( )
. map ( | c | state . candidates [ * c ] . parcels . iter ( )
2021-07-19 23:15:17 +10:00
. map ( | p | p . votes . iter ( ) . map ( | v | & v . value / & v . ballot . orig_value ) . max ( ) . unwrap ( ) )
2021-07-19 18:35:23 +10:00
. max ( ) . unwrap ( ) )
. max ( ) . unwrap ( ) ;
2021-06-16 13:00:54 +10:00
2021-07-19 18:35:23 +10:00
votes_remain = false ;
2021-06-16 13:00:54 +10:00
2021-07-19 18:35:23 +10:00
for excluded_candidate in excluded_with_votes . iter ( ) {
let count_card = state . candidates . get_mut ( * excluded_candidate ) . unwrap ( ) ;
// Filter out just those votes with max_value
let mut remaining_votes = Vec ::new ( ) ;
2021-07-19 23:15:17 +10:00
let cand_votes = count_card . concat_parcels ( ) ;
2021-07-19 18:35:23 +10:00
let mut votes_transferred = N ::new ( ) ;
for vote in cand_votes . into_iter ( ) {
if & vote . value / & vote . ballot . orig_value = = max_value {
votes_transferred + = & vote . value ;
votes . push ( vote ) ;
} else {
remaining_votes . push ( vote ) ;
}
2021-06-16 13:00:54 +10:00
}
2021-07-19 18:35:23 +10:00
if ! remaining_votes . is_empty ( ) {
votes_remain = true ;
}
// Leave remaining votes with candidate (as one parcel)
2021-07-19 23:15:17 +10:00
count_card . parcels = vec! [ Parcel {
votes : remaining_votes ,
source_order : 0 , // Unused in this mode
} ] ;
// Update votes
checksum - = & votes_transferred ;
count_card . transfer ( & - votes_transferred ) ;
}
}
}
ExclusionMethod ::BySource = > {
// Exclude by source candidate
let excluded_with_votes : Vec < & & Candidate > = excluded_candidates . iter ( ) . filter ( | c | ! state . candidates [ * c ] . parcels . is_empty ( ) ) . collect ( ) ;
if excluded_with_votes . is_empty ( ) {
votes_remain = false ;
} else {
// If candidates to exclude still having votes, select only those from the earliest elected/excluded source candidate
let min_order = excluded_with_votes . iter ( )
. map ( | c | state . candidates [ * c ] . parcels . iter ( )
. map ( | p | p . source_order )
. min ( ) . unwrap ( ) )
. min ( ) . unwrap ( ) ;
votes_remain = false ;
for excluded_candidate in excluded_with_votes . iter ( ) {
let count_card = state . candidates . get_mut ( * excluded_candidate ) . unwrap ( ) ;
// Filter out just those votes with min_order
let mut remaining_parcels = Vec ::new ( ) ;
let mut votes_transferred = N ::new ( ) ;
while ! count_card . parcels . is_empty ( ) {
let parcel = count_card . parcels . pop ( ) . unwrap ( ) ;
if parcel . source_order = = min_order {
for vote in parcel . votes {
votes_transferred + = & vote . value ;
votes . push ( vote ) ;
}
} else {
remaining_parcels . push ( parcel ) ;
}
}
if ! remaining_parcels . is_empty ( ) {
votes_remain = true ;
}
// Leave remaining parcels with candidate
count_card . parcels = remaining_parcels ;
2021-07-19 18:35:23 +10:00
// Update votes
checksum - = & votes_transferred ;
count_card . transfer ( & - votes_transferred ) ;
2021-06-16 13:00:54 +10:00
}
}
}
ExclusionMethod ::ParcelsByOrder = > {
// Exclude by parcel by order
if excluded_candidates . len ( ) > 1 {
2021-07-19 23:15:17 +10:00
// TODO: We can probably support this actually
2021-06-16 13:00:54 +10:00
panic! ( " --exclusion parcels_by_order is incompatible with --bulk-exclude " ) ;
}
let count_card = state . candidates . get_mut ( excluded_candidates [ 0 ] ) . unwrap ( ) ;
2021-07-19 18:35:23 +10:00
if count_card . parcels . is_empty ( ) {
votes_remain = false ;
} else {
2021-07-19 23:15:17 +10:00
votes = count_card . parcels . remove ( 0 ) . votes ;
2021-07-19 18:35:23 +10:00
votes_remain = ! count_card . parcels . is_empty ( ) ;
// Update votes
let votes_transferred = votes . iter ( ) . fold ( N ::new ( ) , | acc , v | acc + & v . value ) ;
checksum - = & votes_transferred ;
count_card . transfer ( & - votes_transferred ) ;
}
2021-06-16 13:00:54 +10:00
}
2021-06-22 15:23:46 +10:00
_ = > panic! ( )
2021-06-16 13:00:54 +10:00
}
if ! votes . is_empty ( ) {
2021-08-03 18:38:45 +10:00
// Count next preferences
2021-06-16 13:00:54 +10:00
let value = & votes [ 0 ] . value / & votes [ 0 ] . ballot . orig_value ;
let result = super ::next_preferences ( state , votes ) ;
if let ExclusionMethod ::SingleStage = opts . exclusion {
2021-06-29 15:31:38 +10:00
if result . total_ballots = = N ::one ( ) {
state . logger . log_literal ( format! ( " Transferring 1 ballot, totalling {:.dps$} votes. " , result . total_votes , dps = opts . pp_decimals ) ) ;
} else {
state . logger . log_literal ( format! ( " Transferring {:.0} ballots, totalling {:.dps$} votes. " , result . total_ballots , result . total_votes , dps = opts . pp_decimals ) ) ;
}
2021-06-16 13:00:54 +10:00
} else {
2021-06-29 15:31:38 +10:00
if result . total_ballots = = N ::one ( ) {
state . logger . log_literal ( format! ( " Transferring 1 ballot, totalling {:.dps$} votes, received at value {:.dps2$} . " , result . total_votes , value , dps = opts . pp_decimals , dps2 = max ( opts . pp_decimals , 2 ) ) ) ;
} else {
state . logger . log_literal ( format! ( " Transferring {:.0} ballots, totalling {:.dps$} votes, received at value {:.dps2$} . " , result . total_ballots , result . total_votes , value , dps = opts . pp_decimals , dps2 = max ( opts . pp_decimals , 2 ) ) ) ;
}
2021-06-16 13:00:54 +10:00
}
// Transfer candidate votes
for ( candidate , entry ) in result . candidates . into_iter ( ) {
2021-07-19 23:15:17 +10:00
let parcel = Parcel {
votes : entry . votes ,
source_order : state . num_elected + state . num_excluded ,
} ;
2021-06-16 13:00:54 +10:00
let count_card = state . candidates . get_mut ( candidate ) . unwrap ( ) ;
count_card . parcels . push ( parcel ) ;
// Round transfers
let mut candidate_transfers = entry . num_votes ;
if let Some ( dps ) = opts . round_votes {
candidate_transfers . floor_mut ( dps ) ;
}
count_card . transfer ( & candidate_transfers ) ;
checksum + = candidate_transfers ;
}
// Transfer exhausted votes
2021-07-19 23:15:17 +10:00
let parcel = Parcel {
votes : result . exhausted . votes ,
source_order : state . num_elected + state . num_excluded ,
} ;
2021-06-16 13:00:54 +10:00
state . exhausted . parcels . push ( parcel ) ;
let mut exhausted_transfers = result . exhausted . num_votes ;
if let Some ( dps ) = opts . round_votes {
exhausted_transfers . floor_mut ( dps ) ;
}
state . exhausted . transfer ( & exhausted_transfers ) ;
checksum + = exhausted_transfers ;
}
if ! votes_remain {
// Finalise candidate votes
for excluded_candidate in excluded_candidates . into_iter ( ) {
let count_card = state . candidates . get_mut ( excluded_candidate ) . unwrap ( ) ;
checksum - = & count_card . votes ;
count_card . transfers - = & count_card . votes ;
count_card . votes = N ::new ( ) ;
}
if let ExclusionMethod ::SingleStage = opts . exclusion {
} else {
state . logger . log_literal ( " Exclusion complete. " . to_string ( ) ) ;
}
}
// Update loss by fraction
state . loss_fraction . transfer ( & - checksum ) ;
}
2021-06-22 15:23:46 +10:00
/// Perform one stage of a candidate exclusion according to the Wright method
pub fn wright_exclude_candidates < ' a , N : Number > ( state : & mut CountState < ' a , N > , opts : & STVOptions , excluded_candidates : Vec < & ' a Candidate > )
where
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N > ,
for < ' r > & ' r N : ops ::Mul < & ' r N , Output = N > ,
for < ' r > & ' r N : ops ::Div < & ' r N , Output = N > ,
{
// Used to give bulk excluded candidate the same order_elected
let order_excluded = state . num_excluded + 1 ;
for excluded_candidate in excluded_candidates . iter ( ) {
let count_card = state . candidates . get_mut ( excluded_candidate ) . unwrap ( ) ;
// Rust borrow checker is unhappy if we try to put this in exclude_hopefuls ??!
if count_card . state ! = CandidateState ::Excluded {
count_card . state = CandidateState ::Excluded ;
state . num_excluded + = 1 ;
count_card . order_elected = - ( order_excluded as isize ) ;
}
2021-06-27 21:57:24 +10:00
2021-06-28 00:56:28 +10:00
constraints ::update_constraints ( state , opts ) ;
2021-06-22 15:23:46 +10:00
}
// Reset count
for ( _ , count_card ) in state . candidates . iter_mut ( ) {
if count_card . order_elected > 0 {
count_card . order_elected = 0 ;
}
count_card . parcels . clear ( ) ;
count_card . votes = N ::new ( ) ;
count_card . transfers = N ::new ( ) ;
count_card . state = match count_card . state {
CandidateState ::Withdrawn = > CandidateState ::Withdrawn ,
CandidateState ::Excluded = > CandidateState ::Excluded ,
_ = > CandidateState ::Hopeful ,
} ;
}
state . exhausted . votes = N ::new ( ) ;
state . exhausted . transfers = N ::new ( ) ;
state . loss_fraction . votes = N ::new ( ) ;
state . loss_fraction . transfers = N ::new ( ) ;
state . num_elected = 0 ;
let orig_title = state . title . clone ( ) ;
// Redistribute first preferences
super ::distribute_first_preferences ( state , opts ) ;
state . kind = Some ( " Exclusion of " ) ;
state . title = orig_title ;
// Trigger recalculation of quota within stv::count_one_stage
state . quota = None ;
state . vote_required_election = None ;
}