2021-06-14 20:43:36 +10:00
/* OpenTally: Open-source election vote counting
2022-03-23 01:51:38 +11:00
* Copyright © 2021 – 2022 Lee Yingtong Li ( RunasSudo )
2021-06-14 20:43:36 +10:00
*
* 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-09-11 21:08:36 +10:00
// --------------
// Child packages
/// Transfer tables
2021-09-11 18:42:15 +10:00
mod transfers ;
pub use transfers ::{ TransferTable , TransferTableCell , TransferTableColumn } ;
2021-09-11 21:08:36 +10:00
/// prettytable-compatible API for HTML table output in WebAssembly
pub mod prettytable_html ;
// --------
// STV code
2022-03-23 01:51:38 +11:00
use super ::{ ExclusionMethod , RoundSubtransfersMode , STVError , STVOptions , 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-09-06 02:43:33 +10:00
use crate ::election ::{ Candidate , CandidateState , CountState , Parcel , StageKind , 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 ;
use std ::ops ;
2021-06-16 13:00:54 +10:00
/// Distribute first preference votes according to the Gregory method
2021-09-04 23:54:28 +10:00
pub fn distribute_first_preferences < N : Number > ( state : & mut CountState < N > , opts : & STVOptions )
2021-09-05 00:04:09 +10:00
where
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N >
2021-09-04 23:54:28 +10:00
{
2021-06-16 13:00:54 +10:00
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-09-04 23:54:28 +10:00
let mut vote_transfers = entry . num_ballots . clone ( ) ;
if let Some ( dps ) = opts . round_votes {
vote_transfers . floor_mut ( dps ) ;
}
count_card . transfer ( & vote_transfers ) ;
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
2021-09-05 00:04:09 +10:00
// Calculate loss by fraction - if minivoters used
if let Some ( orig_total ) = & state . election . total_votes {
2022-08-18 23:55:39 +10:00
let mut total_votes = state . candidates . values ( ) . fold ( N ::new ( ) , | mut acc , cc | { acc + = & cc . votes ; acc } ) ;
2021-09-05 00:04:09 +10:00
total_votes + = & state . exhausted . votes ;
let lbf = orig_total - & total_votes ;
state . loss_fraction . votes = lbf . clone ( ) ;
state . loss_fraction . transfers = lbf ;
}
2021-09-06 02:43:33 +10:00
state . title = StageKind ::FirstPreferences ;
2021-06-16 13:00:54 +10:00
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 ( )
2022-08-18 23:55:39 +10:00
. fold ( N ::new ( ) , | mut acc , c | { acc + = & state . candidates [ c ] . votes ; acc - = quota ; acc } ) ;
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-09-26 02:27:37 +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. " ,
2022-04-20 20:12:50 +10:00
vec! [ elected_candidate . name . as_str ( ) ]
2021-08-07 18:51:48 +10:00
) ;
constraints ::update_constraints ( state , opts ) ;
}
2021-08-03 18:38:45 +10:00
match opts . surplus {
2021-10-27 19:52:51 +11:00
SurplusMethod ::WIG | SurplusMethod ::UIG | SurplusMethod ::EG = > { distribute_surplus ( state , opts , elected_candidate ) ; }
SurplusMethod ::IHare | 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-09-11 02:43:11 +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 , opts : & STVOptions ) -> Option < N >
2021-06-14 20:43:36 +10:00
where
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N >
{
2021-09-11 02:43:11 +10:00
if opts . transferable_only {
let transferable_units = if opts . surplus . is_weighted ( ) { transferable_votes } else { transferable_ballots } ;
2021-06-14 20:43:36 +10:00
if transferable_votes > surplus {
2021-09-11 02:43:11 +10:00
return Some ( transferable_units . clone ( ) ) ;
2021-06-14 20:43:36 +10:00
} else {
return None ;
}
} else {
2021-09-11 02:43:11 +10:00
if opts . surplus . is_weighted ( ) {
return Some ( total_votes . clone ( ) ) ;
2021-06-14 20:43:36 +10:00
} else {
2021-09-11 02:43:11 +10:00
return Some ( total_ballots . clone ( ) ) ;
2021-06-14 20:43:36 +10:00
}
}
}
/// Distribute the surplus of a given candidate according to the Gregory method, based on [STVOptions::surplus]
2022-04-16 02:27:59 +10:00
pub fn distribute_surplus < ' a , N : Number > ( state : & mut CountState < ' a , N > , opts : & STVOptions , elected_candidate : & ' a Candidate )
2021-06-14 20:43:36 +10:00
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-10-27 19:52:51 +11:00
state . title = StageKind ::SurplusOf ( elected_candidate ) ;
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 ;
2022-03-23 01:51:38 +11:00
parcels_next_prefs . push ( ( parcel . value_fraction , parcel . source_order , result ) ) ;
2021-08-16 00:46:05 +10:00
}
2021-09-11 02:43:11 +10:00
// Calculate and print surplus fraction
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 ;
2022-03-25 02:46:30 +11:00
if opts . transferable_only & & opts . subtract_nontransferable {
// Override transferable_votes
transferable_votes = count_card . votes . clone ( ) - exhausted_votes ;
}
2021-09-11 02:43:11 +10:00
let mut surplus_denom = calculate_surplus_denom ( & surplus , & transferable_ballots , & transferable_votes , & total_ballots , & total_votes , opts ) ;
let surplus_numer ;
2021-06-14 20:43:36 +10:00
let mut surplus_fraction ;
2021-09-11 02:43:11 +10:00
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 ) ;
2021-09-11 02:43:11 +10:00
surplus_numer = surplus_fraction . clone ( ) ;
surplus_denom = None ;
} else {
surplus_numer = Some ( surplus . clone ( ) ) ;
2021-06-14 20:43:36 +10:00
}
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-09-11 02:43:11 +10:00
surplus_numer = None ;
surplus_denom = None ;
2021-06-14 20:43:36 +10:00
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-09-12 00:20:49 +10:00
let mut transfer_table = TransferTable ::new_surplus (
state . election . candidates . iter ( ) . filter ( | c | state . candidates [ c ] . state = = CandidateState ::Hopeful | | state . candidates [ c ] . state = = CandidateState ::Guarded ) . collect ( ) ,
surplus . clone ( ) , surplus_fraction . clone ( ) , surplus_numer . clone ( ) , surplus_denom . clone ( )
) ;
2021-08-16 00:46:05 +10:00
2022-03-23 01:51:38 +11:00
for ( value_fraction , source_order , result ) in parcels_next_prefs {
2021-08-16 00:46:05 +10:00
for ( candidate , entry ) in result . candidates . into_iter ( ) {
// Record transfers
2022-03-23 01:51:38 +11:00
transfer_table . add_transfers (
& value_fraction ,
2022-03-25 03:04:00 +11:00
match opts . round_subtransfers {
RoundSubtransfersMode ::ByValueAndSource = > Some ( source_order ) ,
RoundSubtransfersMode ::ByParcel = > None , // Force new column per parcel
_ = > Some ( 0 )
} ,
2022-03-23 01:51:38 +11:00
candidate ,
& entry . num_ballots
) ;
2021-09-11 02:43:11 +10:00
let mut new_value_fraction ;
if opts . surplus . is_weighted ( ) {
new_value_fraction = value_fraction . clone ( ) ;
new_value_fraction * = surplus_numer . as_ref ( ) . unwrap ( ) ; // Guaranteed to be Some in WIGM
if let Some ( n ) = & surplus_denom {
new_value_fraction / = n ;
}
} else {
if let Some ( sf ) = & surplus_fraction {
new_value_fraction = sf . clone ( ) ;
} else {
new_value_fraction = value_fraction . clone ( ) ;
}
}
if let Some ( dps ) = opts . round_values {
new_value_fraction . floor_mut ( dps ) ;
}
2021-08-16 00:46:05 +10:00
// 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 ,
2021-09-11 02:43:11 +10:00
value_fraction : new_value_fraction ,
2021-08-16 00:46:05 +10:00
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 . 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
2022-03-23 01:51:38 +11:00
transfer_table . add_exhausted (
& value_fraction ,
2022-03-25 03:04:00 +11:00
match opts . round_subtransfers {
RoundSubtransfersMode ::ByValueAndSource = > Some ( source_order ) ,
RoundSubtransfersMode ::ByParcel = > None , // Force new column per parcel
_ = > Some ( 0 )
} ,
2022-03-23 01:51:38 +11:00
& result . exhausted . num_ballots
) ;
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 ,
2021-10-27 19:52:51 +11:00
value_fraction , // TODO: Reweight exhausted votes
2021-08-16 00:46:05 +10:00
source_order : state . num_elected + state . num_excluded ,
} ;
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-09-11 18:42:15 +10:00
transfer_table . calculate ( opts ) ;
checksum + = transfer_table . apply_to ( state , opts ) ;
state . transfer_table = Some ( transfer_table ) ;
2021-06-14 20:43:36 +10:00
// 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]
2021-10-27 19:52:51 +11:00
#[ allow(clippy::branches_sharing_code) ]
2022-04-20 20:12:50 +10:00
pub fn exclude_candidates < ' a , N : Number > ( state : & mut CountState < ' a , N > , opts : & STVOptions , excluded_candidates : Vec < & ' a Candidate > , complete_type : & 'static str )
2021-06-16 13:00:54 +10:00
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-09-09 01:19:31 +10:00
let excluded_with_votes : Vec < & & Candidate > = excluded_candidates . iter ( )
. filter ( | c | { let cc = & state . candidates [ * c ] ; ! cc . finalised & & ! cc . 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-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 {
2021-10-27 19:52:51 +11:00
votes ,
2021-08-16 00:46:05 +10:00
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-09-09 01:19:31 +10:00
let excluded_with_votes : Vec < & & Candidate > = excluded_candidates . iter ( )
. filter ( | c | { let cc = & state . candidates [ * c ] ; ! cc . finalised & & ! cc . parcels . is_empty ( ) } )
. 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 ( ) ;
2021-10-27 19:52:51 +11:00
let value = parcels . first ( ) . map ( | p | p . value_fraction . clone ( ) ) ;
2021-08-16 00:46:05 +10:00
2021-09-12 00:20:49 +10:00
let mut transfer_table = TransferTable ::new_exclusion (
state . election . candidates . iter ( ) . filter ( | c | state . candidates [ c ] . state = = CandidateState ::Hopeful | | state . candidates [ c ] . state = = CandidateState ::Guarded ) . collect ( ) ,
) ;
2021-08-16 00:46:05 +10:00
2022-03-23 01:51:38 +11:00
for src_parcel in parcels {
2021-08-03 18:38:45 +10:00
// Count next preferences
2022-03-23 01:51:38 +11:00
let result = super ::next_preferences ( state , src_parcel . votes ) ;
2021-06-16 13:00:54 +10:00
2021-08-16 00:46:05 +10:00
total_ballots + = & result . total_ballots ;
2022-03-23 01:51:38 +11:00
total_votes + = & result . total_ballots * & src_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 ,
2022-03-23 01:51:38 +11:00
value_fraction : src_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
2022-03-23 01:51:38 +11:00
transfer_table . add_transfers (
& parcel . value_fraction ,
2022-03-25 03:04:00 +11:00
match opts . round_subtransfers {
RoundSubtransfersMode ::ByValueAndSource = > Some ( src_parcel . source_order ) ,
RoundSubtransfersMode ::ByParcel = > None , // Force new column per parcel
_ = > Some ( 0 )
} ,
2022-03-23 01:51:38 +11:00
candidate ,
& entry . num_ballots
) ;
2021-08-16 00:46:05 +10:00
2021-06-16 13:00:54 +10:00
let count_card = state . candidates . get_mut ( candidate ) . unwrap ( ) ;
count_card . parcels . push ( parcel ) ;
}
// Transfer exhausted votes
2021-07-19 23:15:17 +10:00
let parcel = Parcel {
votes : result . exhausted . votes ,
2022-03-23 01:51:38 +11:00
value_fraction : src_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
2022-03-23 01:51:38 +11:00
transfer_table . add_exhausted (
& parcel . value_fraction ,
2022-03-25 03:04:00 +11:00
match opts . round_subtransfers {
RoundSubtransfersMode ::ByValueAndSource = > Some ( src_parcel . source_order ) ,
RoundSubtransfersMode ::ByParcel = > None , // Force new column per parcel
_ = > Some ( 0 )
} ,
2022-03-23 01:51:38 +11:00
& result . exhausted . num_ballots
) ;
2021-06-16 13:00:54 +10:00
2021-09-11 01:19:38 +10:00
state . exhausted . parcels . push ( parcel ) ;
2021-08-16 00:46:05 +10:00
}
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-09-11 18:42:15 +10:00
transfer_table . calculate ( opts ) ;
checksum + = transfer_table . apply_to ( state , opts ) ;
state . transfer_table = Some ( transfer_table ) ;
2021-08-16 00:46:05 +10:00
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 {
2022-04-20 20:12:50 +10:00
state . logger . log_literal ( format! ( " {} complete. " , complete_type ) ) ;
2021-06-16 13:00:54 +10:00
}
}
// 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 . title = orig_title ;
// Trigger recalculation of quota within stv::count_one_stage
state . quota = None ;
state . vote_required_election = None ;
}