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-08-16 00:46:05 +10:00
use super ::{ ExclusionMethod , NextPreferencesEntry , 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 std ::cmp ::max ;
2021-08-16 00:46:05 +10:00
use std ::collections ::HashMap ;
2021-06-14 20:43:36 +10:00
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 ,
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 ,
2021-08-16 00:46:05 +10:00
value_fraction : N ::one ( ) ,
2021-07-19 23:15:17 +10:00
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 ) ;
2021-08-16 00:46:05 +10:00
count_card . transfer ( & entry . num_ballots ) ;
2021-08-16 18:48:49 +10:00
count_card . ballot_transfers + = entry . num_ballots ;
2021-06-16 13:00:54 +10:00
}
// Transfer exhausted votes
2021-07-19 23:15:17 +10:00
let parcel = Parcel {
votes : result . exhausted . votes ,
2021-08-16 00:46:05 +10:00
value_fraction : N ::one ( ) ,
2021-07-19 23:15:17 +10:00
source_order : 0 ,
} ;
2021-06-16 13:00:54 +10:00
state . exhausted . parcels . push ( parcel ) ;
2021-08-16 00:46:05 +10:00
state . exhausted . transfer ( & result . exhausted . num_ballots ) ;
2021-08-16 18:48:49 +10:00
state . exhausted . ballot_transfers + = result . exhausted . num_ballots ;
2021-06-16 13:00:54 +10:00
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
2021-08-16 00:46:05 +10:00
for < ' r > & ' r N : ops ::Add < & ' r N , Output = N > ,
2021-06-14 20:43:36 +10:00
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N > ,
2021-08-16 00:46:05 +10:00
for < ' r > & ' r N : ops ::Mul < & ' r N , Output = N > ,
2021-06-14 20:43:36 +10:00
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-08-08 19:11:15 +10:00
& cc . votes > quota & & ! cc . finalised
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-07 18:51:48 +10:00
// If --no-immediate-elect, declare elected the candidate with the highest surplus
if ! opts . immediate_elect {
let count_card = state . candidates . get_mut ( elected_candidate ) . unwrap ( ) ;
count_card . state = CandidateState ::Elected ;
state . num_elected + = 1 ;
count_card . order_elected = state . num_elected as isize ;
state . logger . log_smart (
" {} meets the quota and is elected. " ,
" {} meet the quota and are elected. " ,
vec! [ & elected_candidate . name ]
) ;
constraints ::update_constraints ( state , opts ) ;
}
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 ) ;
}
2021-08-07 18:51:48 +10:00
// If --no-immediate-elect, check for candidates with exactly a quota to elect
// However, if --defer-surpluses, zero surplus is necessarily deferred so skip
if ! opts . immediate_elect & & ! opts . defer_surpluses {
if super ::elect_hopefuls ( state , opts , false ) ? {
return Ok ( true ) ;
}
}
2021-06-14 20:43:36 +10:00
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-08-16 00:46:05 +10:00
fn calculate_surplus_denom < ' n , N : Number > ( surplus : & N , transferable_ballots : & ' n N , transferable_votes : & ' n N , total_ballots : & ' n N , total_votes : & ' n N , weighted : bool , transferable_only : bool ) -> Option < & ' n N >
2021-06-14 20:43:36 +10:00
where
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N >
{
if transferable_only {
2021-08-16 00:46:05 +10:00
let transferable_units = if weighted { transferable_votes } else { transferable_ballots } ;
2021-06-14 20:43:36 +10:00
if transferable_votes > surplus {
return Some ( transferable_units ) ;
} else {
return None ;
}
} else {
if weighted {
2021-08-16 00:46:05 +10:00
return Some ( total_votes ) ;
2021-06-14 20:43:36 +10:00
} else {
2021-08-16 00:46:05 +10:00
return Some ( total_ballots ) ;
2021-06-14 20:43:36 +10:00
}
}
}
2021-08-16 00:46:05 +10:00
/// Return the reweighted value fraction of a parcel/vote after being transferred
fn reweight_value_fraction < N : Number > (
value_fraction : & N ,
2021-06-14 20:43:36 +10:00
surplus : & N ,
weighted : bool ,
surplus_fraction : & Option < N > ,
2021-08-16 00:46:05 +10:00
surplus_denom : & Option < & N > ,
round_tvs : Option < usize > ) -> N
2021-06-14 20:43:36 +10:00
{
2021-08-16 00:46:05 +10:00
let result ;
2021-06-14 20:43:36 +10:00
match surplus_denom {
Some ( v ) = > {
if let Some ( _ ) = round_tvs {
// Rounding requested: use the rounded transfer value
if weighted {
2021-08-16 00:46:05 +10:00
result = value_fraction . clone ( ) * surplus_fraction . as_ref ( ) . unwrap ( ) ;
2021-06-14 20:43:36 +10:00
} else {
2021-08-16 00:46:05 +10:00
result = surplus_fraction . as_ref ( ) . unwrap ( ) . clone ( ) ;
2021-06-14 20:43:36 +10:00
}
} else {
// Avoid unnecessary rounding error by first multiplying by the surplus
if weighted {
2021-08-16 00:46:05 +10:00
result = value_fraction . clone ( ) * surplus / * v ;
2021-06-14 20:43:36 +10:00
} else {
2021-08-16 00:46:05 +10:00
result = surplus . clone ( ) / * v ;
2021-06-14 20:43:36 +10:00
}
}
}
None = > {
2021-08-16 00:46:05 +10:00
result = value_fraction . clone ( ) ;
2021-06-14 20:43:36 +10:00
}
}
return result ;
}
/// Compute the number of votes to credit to a continuing candidate during a surplus transfer, based on [STVOptions::sum_surplus_transfers]
2021-08-16 00:46:05 +10:00
fn sum_surplus_transfers < N : Number > ( entry : & NextPreferencesEntry < N > , orig_value_fraction : & N , surplus : & N , is_weighted : bool , surplus_fraction : & Option < N > , surplus_denom : & Option < & N > , _state : & mut CountState < N > , opts : & STVOptions ) -> N
2021-06-14 20:43:36 +10:00
where
2021-08-16 00:46:05 +10:00
for < ' r > & ' r N : ops ::Mul < & ' r N , Output = N > ,
2021-06-14 20:43:36 +10:00
for < ' r > & ' r N : ops ::Div < & ' r N , Output = N > ,
{
match opts . sum_surplus_transfers {
SumSurplusTransfersMode ::ByValue = > {
2021-08-16 00:46:05 +10:00
// Calculate transfer across all votes in this parcel
2021-06-14 20:43:36 +10:00
let mut result = N ::new ( ) ;
2021-08-16 00:46:05 +10:00
for vote in entry . votes . iter ( ) {
result + = & vote . ballot . orig_value ;
2021-06-14 20:43:36 +10:00
}
2021-08-16 00:46:05 +10:00
result * = reweight_value_fraction ( orig_value_fraction , surplus , is_weighted , surplus_fraction , surplus_denom , opts . round_surplus_fractions ) ;
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
2021-08-19 18:08:24 +10:00
let mut new_value_fraction = reweight_value_fraction ( orig_value_fraction , surplus , is_weighted , surplus_fraction , surplus_denom , opts . round_surplus_fractions ) ;
if let Some ( dps ) = opts . round_votes {
new_value_fraction . floor_mut ( dps ) ;
}
2021-06-14 20:43:36 +10:00
let mut result = N ::new ( ) ;
for vote in entry . votes . iter ( ) {
2021-08-19 18:08:24 +10:00
let mut vote_value = & new_value_fraction * & vote . ballot . orig_value ;
2021-08-16 00:46:05 +10:00
if let Some ( dps ) = opts . round_votes {
vote_value . floor_mut ( dps ) ;
}
result + = vote_value ;
2021-06-14 20:43:36 +10:00
}
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
2021-08-16 00:46:05 +10:00
for < ' r > & ' r N : ops ::Add < & ' r N , Output = N > ,
2021-06-14 20:43:36 +10:00
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N > ,
2021-08-16 00:46:05 +10:00
for < ' r > & ' r N : ops ::Mul < & ' r N , Output = N > ,
2021-06-14 20:43:36 +10:00
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-08-17 01:56:43 +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 ( ) ;
2021-08-16 00:46:05 +10:00
// Determine which votes to examine
let mut parcels ;
2021-06-14 20:43:36 +10:00
match opts . surplus {
SurplusMethod ::WIG | SurplusMethod ::UIG = > {
// Inclusive Gregory
2021-08-16 00:46:05 +10:00
parcels = Vec ::new ( ) ;
parcels . append ( & mut state . candidates . get_mut ( elected_candidate ) . unwrap ( ) . 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-08-16 00:46:05 +10:00
parcels = vec! [ state . candidates . get_mut ( elected_candidate ) . unwrap ( ) . parcels . pop ( ) . unwrap ( ) ] ;
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
}
2021-08-16 00:46:05 +10:00
// Count votes
let mut parcels_next_prefs = Vec ::new ( ) ;
let mut transferable_ballots = N ::new ( ) ;
let mut transferable_votes = N ::new ( ) ;
let mut exhausted_ballots = N ::new ( ) ;
let mut exhausted_votes = N ::new ( ) ;
for parcel in parcels {
// Count next preferences
let result = super ::next_preferences ( state , parcel . votes ) ;
for ( _ , entry ) in result . candidates . iter ( ) {
transferable_ballots + = & entry . num_ballots ;
transferable_votes + = & entry . num_ballots * & parcel . value_fraction ;
}
exhausted_ballots + = & result . exhausted . num_ballots ;
exhausted_votes + = & result . exhausted . num_ballots * & parcel . value_fraction ;
parcels_next_prefs . push ( ( parcel . value_fraction , result ) ) ;
}
// Calculate surplus fraction
2021-06-14 20:43:36 +10:00
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
} ;
2021-08-16 00:46:05 +10:00
let total_ballots = & transferable_ballots + & exhausted_ballots ;
let total_votes = & transferable_votes + & exhausted_votes ;
2021-08-17 01:56:43 +10:00
let count_card = state . candidates . get_mut ( elected_candidate ) . unwrap ( ) ;
count_card . ballot_transfers = - & total_ballots ;
2021-08-16 00:46:05 +10:00
let surplus_denom = calculate_surplus_denom ( & surplus , & transferable_ballots , & transferable_votes , & total_ballots , & total_votes , is_weighted , opts . transferable_only ) ;
2021-06-14 20:43:36 +10:00
let mut surplus_fraction ;
match surplus_denom {
2021-08-16 00:46:05 +10:00
Some ( v ) = > {
2021-06-14 20:43:36 +10:00
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-08-16 00:46:05 +10:00
if transferable_ballots = = N ::one ( ) {
2021-06-29 15:31:38 +10:00
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 {
2021-08-16 00:46:05 +10:00
state . logger . log_literal ( format! ( " Transferring {:.0} transferable ballots, totalling {:.dps$} transferable votes, with surplus fraction {:.dps2$} . " , transferable_ballots , transferable_votes , surplus_fraction . as_ref ( ) . unwrap ( ) , dps = opts . pp_decimals , dps2 = max ( opts . pp_decimals , 2 ) ) ) ;
2021-06-29 15:31:38 +10:00
}
2021-06-14 20:43:36 +10:00
} else {
2021-08-16 00:46:05 +10:00
if total_ballots = = N ::one ( ) {
state . logger . log_literal ( format! ( " Transferring 1 ballot, totalling {:.dps$} votes, with surplus fraction {:.dps2$} . " , total_votes , surplus_fraction . as_ref ( ) . unwrap ( ) , dps = opts . pp_decimals , dps2 = max ( opts . pp_decimals , 2 ) ) ) ;
2021-06-29 15:31:38 +10:00
} else {
2021-08-16 00:46:05 +10:00
state . logger . log_literal ( format! ( " Transferring {:.0} ballots, totalling {:.dps$} votes, with surplus fraction {:.dps2$} . " , total_ballots , total_votes , surplus_fraction . as_ref ( ) . unwrap ( ) , dps = opts . pp_decimals , dps2 = max ( opts . pp_decimals , 2 ) ) ) ;
2021-06-29 15:31:38 +10:00
}
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
2021-08-16 00:46:05 +10:00
if transferable_ballots = = N ::one ( ) {
2021-08-05 18:41:39 +10:00
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-16 00:46:05 +10:00
state . logger . log_literal ( format! ( " Transferring {:.0} transferable ballots, totalling {:.dps$} transferable votes, at values received. " , transferable_ballots , transferable_votes , dps = opts . pp_decimals ) ) ;
2021-06-14 20:43:36 +10:00
}
}
}
2021-08-16 00:46:05 +10:00
// Reweight and transfer parcels
2021-06-14 20:43:36 +10:00
2021-08-16 00:46:05 +10:00
let mut candidate_transfers : HashMap < & Candidate , N > = HashMap ::new ( ) ;
for candidate in state . election . candidates . iter ( ) {
candidate_transfers . insert ( candidate , N ::new ( ) ) ;
}
let mut exhausted_transfers = N ::new ( ) ;
for ( value_fraction , result ) in parcels_next_prefs {
for ( candidate , entry ) in result . candidates . into_iter ( ) {
// Record transfers
// TODO: Is there a better way of writing this?
let transfers_orig = candidate_transfers . remove ( candidate ) . unwrap ( ) ;
let transfers_add = sum_surplus_transfers ( & entry , & value_fraction , & surplus , is_weighted , & surplus_fraction , & surplus_denom , state , opts ) ;
candidate_transfers . insert ( candidate , transfers_orig + transfers_add ) ;
// Transfer candidate votes
2021-08-16 18:48:49 +10:00
let parcel = Parcel {
2021-08-16 00:46:05 +10:00
votes : entry . votes ,
value_fraction : reweight_value_fraction ( & value_fraction , & surplus , is_weighted , & surplus_fraction , & surplus_denom , opts . round_surplus_fractions ) ,
source_order : state . num_elected + state . num_excluded ,
} ;
let count_card = state . candidates . get_mut ( candidate ) . unwrap ( ) ;
2021-08-16 18:48:49 +10:00
count_card . ballot_transfers + = parcel . num_ballots ( ) ;
count_card . parcels . push ( parcel ) ;
2021-08-16 00:46:05 +10:00
}
2021-06-14 20:43:36 +10:00
2021-08-16 00:46:05 +10:00
// Record exhausted votes
if opts . transferable_only {
if transferable_votes > surplus {
// No ballots exhaust
} else {
exhausted_transfers + = & surplus - & transferable_votes ;
}
} else {
exhausted_transfers + = sum_surplus_transfers ( & result . exhausted , & value_fraction , & surplus , is_weighted , & surplus_fraction , & surplus_denom , state , opts ) ;
2021-06-14 20:43:36 +10:00
}
2021-08-16 00:46:05 +10:00
// Transfer exhausted votes
let parcel = Parcel {
votes : result . exhausted . votes ,
value_fraction : value_fraction , // TODO: Reweight exhausted votes
source_order : state . num_elected + state . num_excluded ,
} ;
2021-08-16 18:48:49 +10:00
state . exhausted . ballot_transfers + = parcel . num_ballots ( ) ;
2021-08-16 00:46:05 +10:00
state . exhausted . parcels . push ( parcel ) ;
2021-06-14 20:43:36 +10:00
}
2021-08-16 00:46:05 +10:00
let mut checksum = N ::new ( ) ;
// Credit transferred votes
2021-08-16 18:48:49 +10:00
// ballot_transfers updated above
2021-08-16 00:46:05 +10:00
for ( candidate , mut votes ) in candidate_transfers {
if let Some ( dps ) = opts . round_votes {
votes . floor_mut ( dps ) ;
2021-06-14 20:43:36 +10:00
}
2021-08-16 00:46:05 +10:00
let count_card = state . candidates . get_mut ( candidate ) . unwrap ( ) ;
count_card . transfer ( & votes ) ;
checksum + = votes ;
2021-06-14 20:43:36 +10:00
}
2021-08-16 00:46:05 +10:00
// Credit exhausted votes
if let Some ( dps ) = opts . round_votes {
exhausted_transfers . floor_mut ( dps ) ;
}
2021-06-14 20:43:36 +10:00
state . exhausted . transfer ( & exhausted_transfers ) ;
checksum + = exhausted_transfers ;
// 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-08-08 19:11:15 +10:00
count_card . finalised = true ; // Mark surpluses as done
2021-06-22 15:23:46 +10:00
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
2021-08-16 00:46:05 +10:00
for < ' r > & ' r N : ops ::Mul < & ' r N , Output = N > ,
2021-06-16 13:00:54 +10:00
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
2021-08-16 00:46:05 +10:00
let mut parcels = Vec ::new ( ) ;
2021-06-16 13:00:54 +10:00
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-08-16 18:48:49 +10:00
count_card . ballot_transfers = - count_card . num_ballots ( ) ;
2021-08-08 19:11:15 +10:00
count_card . finalised = true ;
2021-06-16 13:00:54 +10:00
2021-08-16 00:46:05 +10:00
parcels . append ( & mut count_card . parcels ) ;
2021-06-16 13:00:54 +10:00
// Update votes
2021-08-16 00:46:05 +10:00
checksum - = & count_card . votes ;
count_card . transfers = - count_card . votes . clone ( ) ;
count_card . votes = N ::new ( ) ;
2021-06-16 13:00:54 +10:00
}
votes_remain = false ;
}
ExclusionMethod ::ByValue = > {
// Exclude by value
2021-08-08 19:11:15 +10:00
let excluded_with_votes : Vec < & & Candidate > = excluded_candidates . iter ( ) . filter ( | c | ! state . candidates [ * c ] . finalised ) . 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-08-16 00:46:05 +10:00
. map ( | p | & p . value_fraction )
2021-07-19 18:35:23 +10:00
. max ( ) . unwrap ( ) )
2021-08-16 00:46:05 +10:00
. max ( ) . unwrap ( )
. clone ( ) ;
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-08-16 00:46:05 +10:00
let mut votes = Vec ::new ( ) ;
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 ( ) ;
2021-08-16 00:46:05 +10:00
let mut cc_parcels = Vec ::new ( ) ;
cc_parcels . append ( & mut count_card . parcels ) ;
2021-07-19 18:35:23 +10:00
// Filter out just those votes with max_value
2021-08-16 00:46:05 +10:00
let mut remaining_parcels = Vec ::new ( ) ;
2021-07-19 18:35:23 +10:00
2021-08-16 00:46:05 +10:00
for mut parcel in cc_parcels {
if parcel . value_fraction = = max_value {
2021-08-16 18:48:49 +10:00
count_card . ballot_transfers - = parcel . num_ballots ( ) ;
2021-08-16 00:46:05 +10:00
let votes_transferred = parcel . num_votes ( ) ;
votes . append ( & mut parcel . votes ) ;
// Update votes
checksum - = & votes_transferred ;
count_card . transfer ( & - votes_transferred ) ;
2021-07-19 18:35:23 +10:00
} else {
2021-08-16 00:46:05 +10:00
remaining_parcels . push ( parcel ) ;
2021-07-19 18:35:23 +10:00
}
2021-06-16 13:00:54 +10:00
}
2021-07-19 18:35:23 +10:00
2021-08-16 00:46:05 +10:00
if ! remaining_parcels . is_empty ( ) {
2021-07-19 18:35:23 +10:00
votes_remain = true ;
}
2021-08-16 00:46:05 +10:00
// Leave remaining votes with candidate
count_card . parcels = remaining_parcels ;
2021-07-19 23:15:17 +10:00
}
2021-08-16 00:46:05 +10:00
// Group all votes of one value in single parcel
parcels . push ( Parcel {
votes : votes ,
value_fraction : max_value ,
source_order : 0 , // source_order is unused in this mode
} ) ;
2021-07-19 23:15:17 +10:00
}
}
ExclusionMethod ::BySource = > {
// Exclude by source candidate
2021-08-08 19:11:15 +10:00
let excluded_with_votes : Vec < & & Candidate > = excluded_candidates . iter ( ) . filter ( | c | ! state . candidates [ * c ] . finalised ) . collect ( ) ;
2021-07-19 23:15:17 +10:00
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 ( ) ;
2021-08-16 00:46:05 +10:00
let mut cc_parcels = Vec ::new ( ) ;
cc_parcels . append ( & mut count_card . parcels ) ;
2021-07-19 23:15:17 +10:00
// Filter out just those votes with min_order
let mut remaining_parcels = Vec ::new ( ) ;
2021-08-16 00:46:05 +10:00
for parcel in cc_parcels {
2021-07-19 23:15:17 +10:00
if parcel . source_order = = min_order {
2021-08-16 18:48:49 +10:00
count_card . ballot_transfers - = parcel . num_ballots ( ) ;
2021-08-16 00:46:05 +10:00
let votes_transferred = parcel . num_votes ( ) ;
parcels . push ( parcel ) ;
// Update votes
checksum - = & votes_transferred ;
count_card . transfer ( & - votes_transferred ) ;
2021-07-19 23:15:17 +10:00
} else {
remaining_parcels . push ( parcel ) ;
}
}
if ! remaining_parcels . is_empty ( ) {
votes_remain = true ;
}
2021-08-16 00:46:05 +10:00
// Leave remaining votes with candidate
2021-07-19 23:15:17 +10:00
count_card . parcels = remaining_parcels ;
2021-06-16 13:00:54 +10:00
}
}
}
ExclusionMethod ::ParcelsByOrder = > {
// Exclude by parcel by order
2021-08-06 01:33:31 +10:00
if excluded_candidates . len ( ) > 1 & & excluded_candidates . iter ( ) . any ( | c | ! state . candidates [ c ] . parcels . is_empty ( ) ) {
2021-07-19 23:15:17 +10:00
// TODO: We can probably support this actually
2021-08-06 01:33:31 +10:00
panic! ( " --exclusion parcels_by_order is incompatible with multiple exclusions " ) ;
2021-06-16 13:00:54 +10:00
}
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-08-16 00:46:05 +10:00
parcels . push ( count_card . parcels . remove ( 0 ) ) ;
2021-07-19 18:35:23 +10:00
votes_remain = ! count_card . parcels . is_empty ( ) ;
2021-08-16 18:48:49 +10:00
count_card . ballot_transfers - = parcels . first ( ) . unwrap ( ) . num_ballots ( ) ;
2021-07-19 18:35:23 +10:00
// Update votes
2021-08-16 00:46:05 +10:00
let votes_transferred = parcels . first ( ) . unwrap ( ) . num_votes ( ) ;
2021-07-19 18:35:23 +10:00
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
}
2021-08-16 00:46:05 +10:00
let mut total_ballots = N ::new ( ) ;
let mut total_votes = N ::new ( ) ;
let value = match parcels . first ( ) { Some ( p ) = > Some ( p . value_fraction . clone ( ) ) , _ = > None } ;
let mut candidate_transfers : HashMap < & Candidate , N > = HashMap ::new ( ) ;
for candidate in state . election . candidates . iter ( ) {
candidate_transfers . insert ( candidate , N ::new ( ) ) ;
}
let mut exhausted_transfers = N ::new ( ) ;
for parcel in parcels {
2021-08-03 18:38:45 +10:00
// Count next preferences
2021-08-16 00:46:05 +10:00
let result = super ::next_preferences ( state , parcel . votes ) ;
2021-06-16 13:00:54 +10:00
2021-08-16 00:46:05 +10:00
total_ballots + = & result . total_ballots ;
total_votes + = & result . total_ballots * & parcel . value_fraction ;
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 ,
2021-08-16 00:46:05 +10:00
value_fraction : parcel . value_fraction . clone ( ) ,
2021-07-19 23:15:17 +10:00
source_order : state . num_elected + state . num_excluded ,
} ;
2021-08-16 00:46:05 +10:00
// Record transfers
let transfers_orig = candidate_transfers . remove ( candidate ) . unwrap ( ) ;
candidate_transfers . insert ( candidate , transfers_orig + & entry . num_ballots * & parcel . value_fraction ) ;
2021-06-16 13:00:54 +10:00
let count_card = state . candidates . get_mut ( candidate ) . unwrap ( ) ;
2021-08-16 18:48:49 +10:00
count_card . ballot_transfers + = parcel . num_ballots ( ) ;
2021-06-16 13:00:54 +10:00
count_card . parcels . push ( parcel ) ;
}
// Transfer exhausted votes
2021-07-19 23:15:17 +10:00
let parcel = Parcel {
votes : result . exhausted . votes ,
2021-08-16 00:46:05 +10:00
value_fraction : parcel . value_fraction ,
2021-07-19 23:15:17 +10:00
source_order : state . num_elected + state . num_excluded ,
} ;
2021-08-16 00:46:05 +10:00
// Record transfers
2021-08-16 18:48:49 +10:00
state . exhausted . ballot_transfers + = parcel . num_ballots ( ) ;
2021-08-16 00:46:05 +10:00
exhausted_transfers + = & result . exhausted . num_ballots * & parcel . value_fraction ;
2021-06-16 13:00:54 +10:00
state . exhausted . parcels . push ( parcel ) ;
2021-08-16 00:46:05 +10:00
// TODO: Detailed transfers logs
}
if let ExclusionMethod ::SingleStage = opts . exclusion {
if total_ballots = = N ::one ( ) {
state . logger . log_literal ( format! ( " Transferring 1 ballot, totalling {:.dps$} votes. " , total_votes , dps = opts . pp_decimals ) ) ;
} else {
state . logger . log_literal ( format! ( " Transferring {:.0} ballots, totalling {:.dps$} votes. " , total_ballots , total_votes , dps = opts . pp_decimals ) ) ;
}
} else {
if total_ballots . is_zero ( ) {
state . logger . log_literal ( format! ( " Transferring 0 ballots, totalling {:.dps$} votes. " , 0 , dps = opts . pp_decimals ) ) ;
} else if total_ballots = = N ::one ( ) {
state . logger . log_literal ( format! ( " Transferring 1 ballot, totalling {:.dps$} votes, received at value {:.dps2$} . " , total_votes , value . unwrap ( ) , 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$} . " , total_ballots , total_votes , value . unwrap ( ) , dps = opts . pp_decimals , dps2 = max ( opts . pp_decimals , 2 ) ) ) ;
}
}
// Credit transferred votes
2021-08-16 18:48:49 +10:00
// ballot_transfers updated above
2021-08-16 00:46:05 +10:00
for ( candidate , mut votes ) in candidate_transfers {
2021-06-16 13:00:54 +10:00
if let Some ( dps ) = opts . round_votes {
2021-08-16 00:46:05 +10:00
votes . floor_mut ( dps ) ;
2021-06-16 13:00:54 +10:00
}
2021-08-16 00:46:05 +10:00
let count_card = state . candidates . get_mut ( candidate ) . unwrap ( ) ;
count_card . transfer ( & votes ) ;
checksum + = votes ;
2021-06-16 13:00:54 +10:00
}
2021-08-16 00:46:05 +10:00
// Credit exhausted votes
if let Some ( dps ) = opts . round_votes {
exhausted_transfers . floor_mut ( dps ) ;
}
state . exhausted . transfer ( & exhausted_transfers ) ;
checksum + = exhausted_transfers ;
2021-06-16 13:00:54 +10:00
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 ( ) ;
2021-08-08 19:11:15 +10:00
count_card . finalised = true ;
2021-06-16 13:00:54 +10:00
}
2021-08-16 00:46:05 +10:00
if opts . exclusion ! = ExclusionMethod ::SingleStage {
2021-06-16 13:00:54 +10:00
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 ,
} ;
2021-08-08 19:11:15 +10:00
if count_card . state = = CandidateState ::Excluded {
count_card . finalised = true ;
} else {
count_card . finalised = false ;
}
2021-06-22 15:23:46 +10:00
}
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 ;
}