2021-05-28 19:58:40 +10:00
/* OpenTally: Open-source election vote counting
2022-03-23 00:34:43 +11:00
* Copyright © 2021 – 2022 Lee Yingtong Li ( RunasSudo )
2021-05-28 19:58:40 +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-08-03 18:38:45 +10:00
/// Gregory methods of surplus distributions
2021-06-14 20:43:36 +10:00
pub mod gregory ;
2021-06-16 13:00:54 +10:00
/// Meek method of surplus distributions, etc.
pub mod meek ;
2021-08-05 18:41:39 +10:00
/// Random sample methods of surplus distributions
2021-08-04 13:46:32 +10:00
pub mod sample ;
2021-06-14 20:43:36 +10:00
2021-06-16 17:20:29 +10:00
/// WebAssembly wrappers
2021-05-30 18:28:39 +10:00
//#[cfg(target_arch = "wasm32")]
pub mod wasm ;
2022-08-22 11:35:20 +10:00
mod options ;
pub use options ::* ;
2022-08-21 03:37:12 +10:00
use crate ::candmap ::CandidateMap ;
2021-06-28 00:56:28 +10:00
use crate ::constraints ;
2022-04-16 02:27:59 +10:00
use crate ::election ::{ Candidate , CandidateState , CountCard , CountState , Election , RollbackState , StageKind , Vote } ;
2022-08-22 11:35:20 +10:00
use crate ::numbers ::Number ;
2021-06-13 03:15:15 +10:00
use crate ::sharandom ::SHARandom ;
2021-06-29 15:31:38 +10:00
use crate ::ties ::{ self , TieStrategy } ;
2021-05-28 19:58:40 +10:00
2021-06-11 21:22:28 +10:00
use itertools ::Itertools ;
2021-05-31 22:25:53 +10:00
2021-07-31 15:24:23 +10:00
use std ::fmt ;
2021-05-30 02:28:52 +10:00
use std ::ops ;
2021-05-28 19:58:40 +10:00
2021-06-14 20:43:36 +10:00
/// An error during the STV count
2021-09-05 22:31:34 +10:00
#[ derive(Debug, Eq, PartialEq) ]
2021-06-12 02:09:26 +10:00
pub enum STVError {
2021-07-31 15:24:23 +10:00
/// Options for the count are invalid
InvalidOptions ( & 'static str ) ,
2021-06-16 17:20:29 +10:00
/// Tie could not be resolved
2021-06-13 00:15:14 +10:00
UnresolvedTie ,
2021-09-05 22:31:34 +10:00
/// Unrecoverable error during the count
CannotCompleteCount ( & 'static str ) ,
2021-06-12 02:09:26 +10:00
}
2021-06-28 00:56:28 +10:00
impl STVError {
2021-07-31 15:24:23 +10:00
/// Describe the error
pub fn describe ( & self ) -> & 'static str {
2021-06-28 00:56:28 +10:00
match self {
2021-07-31 15:24:23 +10:00
STVError ::InvalidOptions ( s ) = > s ,
STVError ::UnresolvedTie = > " Unable to resolve tie " ,
2021-09-05 22:31:34 +10:00
STVError ::CannotCompleteCount ( s ) = > s ,
2021-06-28 00:56:28 +10:00
}
}
}
2021-07-31 15:24:23 +10:00
impl fmt ::Display for STVError {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
f . write_str ( self . describe ( ) ) ? ;
return Ok ( ( ) ) ;
}
}
2021-09-04 22:46:29 +10:00
/// Preprocess the given election
pub fn preprocess_election < N : Number > ( election : & mut Election < N > , opts : & STVOptions ) {
2022-04-20 19:54:58 +10:00
// Normalise ballots if required
if opts . surplus = = SurplusMethod ::IHare | | opts . surplus = = SurplusMethod ::Hare {
2021-09-04 22:46:29 +10:00
election . normalise_ballots ( ) ;
}
// Process equal rankings
election . realise_equal_rankings ( ) ;
}
2021-06-14 20:43:36 +10:00
/// Distribute first preferences, and initialise other states such as the random number generator and tie-breaking rules
2021-08-05 20:18:10 +10:00
pub fn count_init < ' a , N : Number > ( state : & mut CountState < ' a , N > , opts : & ' a STVOptions ) -> Result < ( ) , STVError >
2021-06-16 13:00:54 +10:00
where
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N > ,
for < ' r > & ' r N : ops ::Mul < & ' r N , Output = N > ,
{
2021-06-13 03:15:15 +10:00
// Initialise RNG
for t in opts . ties . iter ( ) {
if let TieStrategy ::Random ( seed ) = t {
state . random = Some ( SHARandom ::new ( seed ) ) ;
}
}
2021-06-28 00:56:28 +10:00
constraints ::update_constraints ( state , opts ) ;
distribute_first_preferences ( state , opts ) ;
calculate_quota ( state , opts ) ;
2021-08-07 18:51:48 +10:00
elect_hopefuls ( state , opts , true ) ? ;
2021-06-28 00:56:28 +10:00
init_tiebreaks ( state , opts ) ;
2021-08-05 20:18:10 +10:00
return Ok ( ( ) ) ;
2021-05-29 17:51:45 +10:00
}
2021-06-14 20:43:36 +10:00
/// Perform a single stage of the STV count
2021-08-05 20:18:10 +10:00
///
/// Returns `true` if the count is complete, otherwise `false`.
2021-06-28 00:56:28 +10:00
pub fn count_one_stage < ' a , N : Number > ( state : & mut CountState < ' a , N > , opts : & STVOptions ) -> Result < bool , STVError >
2021-05-29 17:51:45 +10:00
where
2021-08-16 00:46:05 +10:00
for < ' r > & ' r N : ops ::Add < & ' r N , Output = N > ,
2021-05-30 02:28:52 +10:00
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N > ,
2021-06-16 13:00:54 +10:00
for < ' r > & ' r N : ops ::Mul < & ' r N , Output = N > ,
2021-05-30 02:28:52 +10:00
for < ' r > & ' r N : ops ::Div < & ' r N , Output = N > ,
for < ' r > & ' r N : ops ::Neg < Output = N > ,
2021-05-29 17:51:45 +10:00
{
2021-09-11 18:42:15 +10:00
state . transfer_table = None ;
2021-05-29 17:51:45 +10:00
state . logger . entries . clear ( ) ;
state . step_all ( ) ;
// Finish count
2021-10-27 19:52:51 +11:00
if finished_before_stage ( state ) {
2021-06-12 02:09:26 +10:00
return Ok ( true ) ;
2021-05-29 17:51:45 +10:00
}
2022-04-16 02:27:59 +10:00
if let RollbackState ::Normal = state . rollback_state {
2022-04-21 21:56:21 +10:00
} else {
constraints ::rollback_one_stage ( state , opts ) ? ;
2022-04-16 02:27:59 +10:00
elect_hopefuls ( state , opts , true ) ? ;
update_tiebreaks ( state , opts ) ;
return Ok ( false ) ;
}
2021-06-23 00:52:25 +10:00
// Attempt early bulk election
if opts . early_bulk_elect {
2021-10-27 19:52:51 +11:00
if bulk_elect ( state , opts ) ? {
2021-06-23 00:52:25 +10:00
return Ok ( false ) ;
}
}
2021-05-29 17:51:45 +10:00
// Continue exclusions
2021-10-27 19:52:51 +11:00
if continue_exclusion ( state , opts ) ? {
2021-06-28 00:56:28 +10:00
calculate_quota ( state , opts ) ;
2021-08-07 18:51:48 +10:00
elect_hopefuls ( state , opts , true ) ? ;
2021-06-28 00:56:28 +10:00
update_tiebreaks ( state , opts ) ;
2021-06-12 02:09:26 +10:00
return Ok ( false ) ;
2021-05-29 17:51:45 +10:00
}
2021-06-27 21:57:24 +10:00
// Exclude doomed candidates
2021-10-27 19:52:51 +11:00
if exclude_doomed ( state , opts ) ? {
2021-06-28 00:56:28 +10:00
calculate_quota ( state , opts ) ;
2021-08-07 18:51:48 +10:00
elect_hopefuls ( state , opts , true ) ? ;
2021-06-28 00:56:28 +10:00
update_tiebreaks ( state , opts ) ;
2021-06-27 21:57:24 +10:00
return Ok ( false ) ;
}
2021-05-29 17:51:45 +10:00
// Distribute surpluses
2021-10-27 19:52:51 +11:00
if distribute_surpluses ( state , opts ) ? {
2021-06-28 00:56:28 +10:00
calculate_quota ( state , opts ) ;
2021-08-07 18:51:48 +10:00
elect_hopefuls ( state , opts , true ) ? ;
2021-06-28 00:56:28 +10:00
update_tiebreaks ( state , opts ) ;
2021-06-12 02:09:26 +10:00
return Ok ( false ) ;
2021-05-29 17:51:45 +10:00
}
2021-06-23 00:52:25 +10:00
// Attempt late bulk election
2021-10-27 19:52:51 +11:00
if bulk_elect ( state , opts ) ? {
2021-06-12 02:09:26 +10:00
return Ok ( false ) ;
2021-05-29 17:51:45 +10:00
}
2021-09-05 22:31:34 +10:00
// Sanity check
let num_hopefuls = state . candidates . values ( )
. filter ( | cc | cc . state = = CandidateState ::Hopeful )
. count ( ) ;
if num_hopefuls = = 0 {
return Err ( STVError ::CannotCompleteCount ( " Insufficient continuing candidates to complete count " ) ) ;
}
2021-05-29 17:51:45 +10:00
// Exclude lowest hopeful
2021-10-27 19:52:51 +11:00
exclude_hopefuls ( state , opts ) ? ; // Cannot fail
2021-08-03 18:38:45 +10:00
calculate_quota ( state , opts ) ;
2021-08-07 18:51:48 +10:00
elect_hopefuls ( state , opts , true ) ? ;
2021-08-03 18:38:45 +10:00
update_tiebreaks ( state , opts ) ;
return Ok ( false ) ;
2021-05-29 17:51:45 +10:00
}
2021-06-14 20:43:36 +10:00
/// See [next_preferences]
2022-04-16 02:27:59 +10:00
pub struct NextPreferencesResult < ' a , N > {
/// [NextPreferencesEntry] for each [Candidate]
2022-08-21 03:37:12 +10:00
pub candidates : CandidateMap < ' a , NextPreferencesEntry < ' a , N > > ,
2022-04-16 02:27:59 +10:00
/// [NextPreferencesEntry] for exhausted ballots
pub exhausted : NextPreferencesEntry < ' a , N > ,
/// Total weight of ballots examined
pub total_ballots : N ,
2021-05-28 19:58:40 +10:00
}
2021-06-14 20:43:36 +10:00
/// See [next_preferences]
2022-04-16 02:27:59 +10:00
pub struct NextPreferencesEntry < ' a , N > {
/// Votes recording a next preference for the candidate
pub votes : Vec < Vote < ' a , N > > ,
/// Weight of such ballots
pub num_ballots : N ,
2021-05-28 19:58:40 +10:00
}
2021-06-14 20:43:36 +10:00
/// Count the given votes, grouping according to next available preference
2022-04-16 02:27:59 +10:00
pub fn next_preferences < ' a , N : Number > ( state : & CountState < ' a , N > , votes : Vec < Vote < ' a , N > > ) -> NextPreferencesResult < ' a , N > {
2021-05-28 19:58:40 +10:00
let mut result = NextPreferencesResult {
2022-08-21 03:37:12 +10:00
candidates : CandidateMap ::with_capacity ( state . election . candidates . len ( ) ) ,
2021-05-28 19:58:40 +10:00
exhausted : NextPreferencesEntry {
votes : Vec ::new ( ) ,
num_ballots : N ::new ( ) ,
} ,
total_ballots : N ::new ( ) ,
} ;
for mut vote in votes . into_iter ( ) {
result . total_ballots + = & vote . ballot . orig_value ;
let mut next_candidate = None ;
2021-10-27 19:52:51 +11:00
while let Some ( preference ) = vote . next_preference ( ) {
let candidate = & state . election . candidates [ preference ] ;
let count_card = & state . candidates [ candidate ] ;
if let CandidateState ::Hopeful | CandidateState ::Guarded = count_card . state {
next_candidate = Some ( candidate ) ;
break ;
2021-05-28 19:58:40 +10:00
}
}
// Have to structure like this to satisfy Rust's borrow checker
if let Some ( candidate ) = next_candidate {
2022-08-20 22:43:57 +10:00
match result . candidates . get_mut ( candidate ) {
Some ( entry ) = > {
entry . num_ballots + = & vote . ballot . orig_value ;
entry . votes . push ( vote ) ;
}
None = > {
let entry = NextPreferencesEntry {
num_ballots : vote . ballot . orig_value . clone ( ) ,
votes : vec ! [ vote ] ,
} ;
result . candidates . insert ( candidate , entry ) ;
}
2021-05-28 19:58:40 +10:00
}
} else {
result . exhausted . num_ballots + = & vote . ballot . orig_value ;
result . exhausted . votes . push ( vote ) ;
}
}
return result ;
}
2021-06-14 20:43:36 +10:00
/// Distribute first preference votes
2021-06-16 13:00:54 +10:00
fn distribute_first_preferences < N : Number > ( state : & mut CountState < N > , opts : & STVOptions )
where
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N > ,
for < ' r > & ' r N : ops ::Mul < & ' r N , Output = N > ,
{
match opts . surplus {
2021-09-27 19:02:30 +10:00
SurplusMethod ::WIG | SurplusMethod ::UIG | SurplusMethod ::EG | SurplusMethod ::IHare | SurplusMethod ::Hare = > {
2021-09-04 23:54:28 +10:00
gregory ::distribute_first_preferences ( state , opts ) ;
2021-06-16 13:00:54 +10:00
}
SurplusMethod ::Meek = > {
2021-06-18 18:48:12 +10:00
meek ::distribute_first_preferences ( state , opts ) ;
2021-06-16 13:00:54 +10:00
}
2021-05-28 19:58:40 +10:00
}
}
2021-06-14 20:43:36 +10:00
/// Calculate the quota, given the total vote, according to [STVOptions::quota]
2021-06-07 20:52:18 +10:00
fn total_to_quota < N : Number > ( mut total : N , seats : usize , opts : & STVOptions ) -> N {
2021-06-02 18:07:05 +10:00
match opts . quota {
QuotaType ::Droop | QuotaType ::DroopExact = > {
2021-06-07 20:52:18 +10:00
total / = N ::from ( seats + 1 ) ;
2021-06-02 18:07:05 +10:00
}
QuotaType ::Hare | QuotaType ::HareExact = > {
2021-06-07 20:52:18 +10:00
total / = N ::from ( seats ) ;
2021-06-02 18:07:05 +10:00
}
}
2021-05-28 19:58:40 +10:00
2021-06-01 21:20:38 +10:00
if let Some ( dps ) = opts . round_quota {
2021-06-02 18:07:05 +10:00
match opts . quota {
QuotaType ::Droop | QuotaType ::Hare = > {
// Increment to next available increment
let mut factor = N ::from ( 10 ) ;
factor . pow_assign ( dps as i32 ) ;
2021-06-07 20:52:18 +10:00
total * = & factor ;
total . floor_mut ( 0 ) ;
total + = N ::one ( ) ;
total / = factor ;
2021-06-02 18:07:05 +10:00
}
QuotaType ::DroopExact | QuotaType ::HareExact = > {
// Round up to next available increment if necessary
2021-06-07 20:52:18 +10:00
total . ceil_mut ( dps ) ;
2021-06-02 18:07:05 +10:00
}
}
2021-06-01 21:20:38 +10:00
}
2021-06-07 20:52:18 +10:00
return total ;
}
2021-08-09 19:50:07 +10:00
/// Update vote required for election according to ERS97/ERS76 rules
2021-09-10 01:29:13 +10:00
fn update_vre_ers < N : Number > ( state : & mut CountState < N > , opts : & STVOptions ) {
2021-08-09 19:50:07 +10:00
if opts . quota_mode = = QuotaMode ::ERS76 & & state . num_excluded = = 0 & & ( state . num_elected = = 0 | | state . candidates . values ( ) . all ( | cc | ! cc . finalised ) ) {
// ERS76 rules: Do not update VRE until a surplus is distributed or candidate is excluded
state . vote_required_election = state . quota . clone ( ) ;
return ;
}
2021-07-21 10:59:06 +10:00
let mut log = String ::new ( ) ;
2021-08-08 19:34:02 +10:00
// Calculate active vote
2022-08-21 07:20:21 +10:00
let active_vote = state . active_vote ( ) ;
2021-08-08 19:34:02 +10:00
log . push_str ( format! ( " Active vote is {:.dps$} , so the vote required for election is " , active_vote , dps = opts . pp_decimals ) . as_str ( ) ) ;
2021-07-21 10:59:06 +10:00
2021-08-08 19:34:02 +10:00
let vote_req = active_vote / N ::from ( state . election . seats - state . num_elected + 1 ) ;
2021-07-21 10:59:06 +10:00
if & vote_req < state . quota . as_ref ( ) . unwrap ( ) {
// VRE is less than the quota
if let Some ( v ) = & state . vote_required_election {
if & vote_req ! = v {
log . push_str ( format! ( " {:.dps$} . " , vote_req , dps = opts . pp_decimals ) . as_str ( ) ) ;
state . vote_required_election = Some ( vote_req ) ;
state . logger . log_literal ( log ) ;
}
} else {
log . push_str ( format! ( " {:.dps$} . " , vote_req , dps = opts . pp_decimals ) . as_str ( ) ) ;
state . vote_required_election = Some ( vote_req ) ;
state . logger . log_literal ( log ) ;
}
} else {
// VRE is not less than the quota, so use the quota
state . vote_required_election = state . quota . clone ( ) ;
}
}
2021-09-10 01:42:42 +10:00
/// Update vote required for election if only one candidate remains, used in early bulk election
///
/// Assumes early bulk election is enabled.
fn update_vre_bulk < N : Number > ( state : & mut CountState < N > , _opts : & STVOptions ) {
// If --early-bulk-elect and one candidate remains, VRE is half of the active vote
// For display purposes only
if state . election . seats - state . num_elected = = 1 {
//let mut log = String::new();
// Calculate active vote
2022-08-21 07:20:21 +10:00
let active_vote = state . active_vote ( ) ;
2021-09-10 01:42:42 +10:00
//log.push_str(format!("Active vote is {:.dps$}, so the vote required for election is ", active_vote, dps=opts.pp_decimals).as_str());
let vote_req = active_vote / N ::from ( state . election . seats - state . num_elected + 1 ) ;
if & vote_req < state . quota . as_ref ( ) . unwrap ( ) {
// VRE is less than the quota
//if let Some(v) = &state.vote_required_election {
// if &vote_req != v {
//log.push_str(format!("{:.dps$}.", vote_req, dps=opts.pp_decimals).as_str());
state . vote_required_election = Some ( vote_req ) ;
//state.logger.log_literal(log);
// }
//} else {
//log.push_str(format!("{:.dps$}.", vote_req, dps=opts.pp_decimals).as_str());
// state.vote_required_election = Some(vote_req);
//state.logger.log_literal(log);
//}
}
}
}
2021-06-14 20:43:36 +10:00
/// Calculate the quota according to [STVOptions::quota]
2021-06-07 20:52:18 +10:00
fn calculate_quota < N : Number > ( state : & mut CountState < N > , opts : & STVOptions ) {
2021-08-08 19:34:02 +10:00
if state . quota . is_none ( ) | | opts . quota_mode = = QuotaMode ::DynamicByTotal {
// Calculate quota by total vote
2021-06-07 20:52:18 +10:00
let mut log = String ::new ( ) ;
// Calculate the total vote
2022-08-21 07:20:21 +10:00
let total_vote = state . total_vote ( ) ;
2021-06-07 20:52:18 +10:00
log . push_str ( format! ( " {:.dps$} usable votes, so the quota is " , total_vote , dps = opts . pp_decimals ) . as_str ( ) ) ;
let quota = total_to_quota ( total_vote , state . election . seats , opts ) ;
2021-08-08 19:34:02 +10:00
log . push_str ( format! ( " {:.dps$} . " , quota , dps = opts . pp_decimals ) . as_str ( ) ) ;
state . quota = Some ( quota ) ;
state . logger . log_literal ( log ) ;
} else if opts . quota_mode = = QuotaMode ::DynamicByActive {
// Calculate quota by active vote
let mut log = String ::new ( ) ;
// Calculate the active vote
2022-08-21 07:20:21 +10:00
let active_vote = state . active_vote ( ) ;
2021-08-08 19:34:02 +10:00
log . push_str ( format! ( " Active vote is {:.dps$} , so the quota is is " , active_vote , dps = opts . pp_decimals ) . as_str ( ) ) ;
// TODO: Calculate according to --quota ?
let quota = active_vote / N ::from ( state . election . seats - state . num_elected + 1 ) ;
2021-06-07 20:52:18 +10:00
log . push_str ( format! ( " {:.dps$} . " , quota , dps = opts . pp_decimals ) . as_str ( ) ) ;
state . quota = Some ( quota ) ;
state . logger . log_literal ( log ) ;
}
2021-05-29 01:22:46 +10:00
2021-07-18 20:01:35 +10:00
if opts . quota_mode = = QuotaMode ::ERS97 | | opts . quota_mode = = QuotaMode ::ERS76 {
// ERS97/ERS76 rules
2021-06-07 20:52:18 +10:00
// -------------------------
2021-07-21 10:59:06 +10:00
// (ERS97) Reduce quota if allowable
2021-06-07 20:52:18 +10:00
2021-07-21 10:59:06 +10:00
if opts . quota_mode = = QuotaMode ::ERS97 & & state . num_elected = = 0 {
2021-06-07 20:52:18 +10:00
let mut log = String ::new ( ) ;
// Calculate the total vote
2022-08-21 07:20:21 +10:00
let total_vote = state . total_vote ( ) ;
2021-06-07 20:52:18 +10:00
log . push_str ( format! ( " {:.dps$} usable votes, so the quota is reduced to " , total_vote , dps = opts . pp_decimals ) . as_str ( ) ) ;
let quota = total_to_quota ( total_vote , state . election . seats , opts ) ;
if & quota < state . quota . as_ref ( ) . unwrap ( ) {
log . push_str ( format! ( " {:.dps$} . " , quota , dps = opts . pp_decimals ) . as_str ( ) ) ;
state . quota = Some ( quota ) ;
state . logger . log_literal ( log ) ;
}
}
// ------------------------------------
// Calculate vote required for election
if state . num_elected < state . election . seats {
2021-09-10 01:29:13 +10:00
update_vre_ers ( state , opts ) ;
2021-06-07 20:52:18 +10:00
}
} else {
2021-09-10 01:29:13 +10:00
// No ERS97/ERS76 rules
2021-09-10 01:42:42 +10:00
if opts . early_bulk_elect {
update_vre_bulk ( state , opts ) ;
2021-09-10 01:29:13 +10:00
}
2021-06-07 20:52:18 +10:00
}
2021-05-28 19:58:40 +10:00
}
2021-07-21 13:43:16 +10:00
/// Compare the candidate's votes with the specified target according to [STVOptions::quota_criterion]
fn cmp_quota_criterion < N : Number > ( quota : & N , count_card : & CountCard < N > , opts : & STVOptions ) -> bool {
2021-06-02 18:07:05 +10:00
match opts . quota_criterion {
QuotaCriterion ::GreaterOrEqual = > {
return count_card . votes > = * quota ;
}
QuotaCriterion ::Greater = > {
return count_card . votes > * quota ;
}
}
2021-05-28 19:58:40 +10:00
}
2021-07-21 13:43:16 +10:00
/// Determine if the given candidate meets the vote required to be elected, according to [STVOptions::quota_criterion] and [STVOptions::quota_mode]
fn meets_vre < N : Number > ( state : & CountState < N > , count_card : & CountCard < N > , opts : & STVOptions ) -> bool {
2021-09-10 01:29:13 +10:00
if opts . quota_mode = = QuotaMode ::ERS97 | | opts . quota_mode = = QuotaMode ::ERS76 {
2021-08-08 19:34:02 +10:00
// VRE is set because ERS97/ERS76 rules
2021-09-10 01:29:13 +10:00
return cmp_quota_criterion ( state . vote_required_election . as_ref ( ) . unwrap ( ) , count_card , opts ) ;
2021-07-21 13:43:16 +10:00
} else {
2021-09-10 01:29:13 +10:00
// VRE is set (if at all) for display purposes only so ignore it here
2021-07-21 13:43:16 +10:00
return cmp_quota_criterion ( state . quota . as_ref ( ) . unwrap ( ) , count_card , opts ) ;
}
}
2021-07-23 01:21:29 +10:00
/// Declare elected the continuing candidates leading for remaining vacancies if they cannot be overtaken
2021-08-05 20:18:10 +10:00
///
/// Returns `true` if any candidates were elected.
2021-07-23 01:21:29 +10:00
fn elect_sure_winners < ' a , N : Number > ( state : & mut CountState < ' a , N > , opts : & STVOptions ) -> Result < bool , STVError > {
2022-04-16 02:27:59 +10:00
// Do not interrupt rolling back!!
if let RollbackState ::Normal = state . rollback_state {
} else {
return Ok ( false ) ;
}
2021-09-09 01:19:31 +10:00
if state . num_elected > = state . election . seats {
2021-07-23 01:21:29 +10:00
return Ok ( false ) ;
}
2021-09-09 01:19:31 +10:00
let num_vacancies = state . election . seats - state . num_elected ;
2021-07-23 01:21:29 +10:00
let mut hopefuls : Vec < ( & Candidate , & CountCard < N > ) > = state . election . candidates . iter ( )
. map ( | c | ( c , & state . candidates [ c ] ) )
. filter ( | ( _ , cc ) | cc . state = = CandidateState ::Hopeful | | cc . state = = CandidateState ::Guarded )
. collect ( ) ;
hopefuls . sort_unstable_by ( | a , b | b . 1. votes . cmp ( & a . 1. votes ) ) ;
let mut total_trailing = N ::new ( ) ;
// For leading candidates, count only untransferred surpluses
2022-08-21 07:20:21 +10:00
total_trailing + = state . total_surplus_of ( hopefuls . iter ( ) . take ( num_vacancies ) . map ( | ( _ , cc ) | * cc ) ) ;
2021-07-23 01:21:29 +10:00
// For trailing candidates, count all votes
2022-08-21 07:20:21 +10:00
total_trailing + = state . total_votes_of ( hopefuls . iter ( ) . skip ( num_vacancies ) . map ( | ( _ , cc ) | * cc ) ) ;
2021-07-23 01:21:29 +10:00
// Add finally any votes awaiting transfer
2022-08-20 22:33:48 +10:00
total_trailing + = state . candidates . values ( ) . fold ( N ::new ( ) , | mut acc , cc | {
2021-07-23 01:21:29 +10:00
match cc . state {
2022-08-20 22:33:48 +10:00
CandidateState ::Elected = > {
if ! cc . finalised & & & cc . votes > state . quota . as_ref ( ) . unwrap ( ) {
acc + = & cc . votes ;
acc - = state . quota . as_ref ( ) . unwrap ( ) ;
}
}
CandidateState ::Hopeful | CandidateState ::Guarded | CandidateState ::Withdrawn = > { }
CandidateState ::Excluded | CandidateState ::Doomed = > {
acc + = & cc . votes ;
}
2021-07-23 01:21:29 +10:00
}
2022-08-20 22:33:48 +10:00
acc
2021-07-23 01:21:29 +10:00
} ) ;
2021-09-05 22:31:34 +10:00
if num_vacancies - 1 < hopefuls . len ( ) {
let last_winner = hopefuls [ num_vacancies - 1 ] . 1 ;
if last_winner . votes < = total_trailing {
return Ok ( false ) ;
}
2021-07-23 01:21:29 +10:00
}
2021-08-01 23:50:15 +10:00
let mut leading_hopefuls : Vec < & Candidate > = hopefuls . iter ( ) . take ( num_vacancies ) . map ( | ( c , _ ) | * c ) . collect ( ) ;
2021-07-23 01:21:29 +10:00
2022-04-16 02:27:59 +10:00
match constraints ::test_constraints_any_time ( state , & leading_hopefuls , CandidateState ::Elected ) {
2021-08-01 23:50:15 +10:00
Ok ( _ ) = > { }
Err ( _ ) = > { return Ok ( false ) ; } // Bulk election conflicts with constraints
}
2021-09-10 01:29:13 +10:00
// Bulk election is possible!
// Elect all leading candidates
if num_vacancies > 1 {
// Update VRE
// (If num_vacancies == 1, this has already been done in calculate_quota)
state . vote_required_election = Some ( total_trailing ) ;
}
2021-09-05 22:31:34 +10:00
while ! leading_hopefuls . is_empty ( ) & & state . num_elected < state . election . seats {
2021-08-01 23:50:15 +10:00
let max_cands = ties ::multiple_max_by ( & leading_hopefuls , | c | & state . candidates [ c ] . votes ) ;
2021-07-23 01:21:29 +10:00
let candidate = if max_cands . len ( ) > 1 {
2021-09-26 02:27:37 +10:00
choose_highest ( state , opts , & max_cands , " Which candidate to elect? " ) ?
2021-07-23 01:21:29 +10:00
} else {
max_cands [ 0 ]
} ;
let count_card = state . candidates . get_mut ( candidate ) . unwrap ( ) ;
count_card . state = CandidateState ::Elected ;
state . num_elected + = 1 ;
count_card . order_elected = state . num_elected as isize ;
state . logger . log_smart (
" As they cannot now be overtaken, {} is elected to fill the remaining vacancy. " ,
" As they cannot now be overtaken, {} are elected to fill the remaining vacancies. " ,
2022-04-16 02:27:59 +10:00
vec! [ candidate . name . as_str ( ) ] // rust-analyzer doesn't understand &String -> &str
2021-07-23 01:21:29 +10:00
) ;
2021-08-01 23:50:15 +10:00
leading_hopefuls . remove ( leading_hopefuls . iter ( ) . position ( | c | * c = = candidate ) . unwrap ( ) ) ;
2021-07-23 01:21:29 +10:00
}
2021-08-01 23:50:15 +10:00
constraints ::update_constraints ( state , opts ) ;
2021-07-23 01:21:29 +10:00
return Ok ( true ) ;
}
/// Declare elected all candidates meeting the quota, and (if enabled) any candidates who can be early bulk elected because they have sufficiently many votes
2021-08-05 20:18:10 +10:00
///
/// Returns `true` if any candidates were elected.
2021-08-07 18:51:48 +10:00
fn elect_hopefuls < ' a , N : Number > ( state : & mut CountState < ' a , N > , opts : & STVOptions , if_immediate : bool ) -> Result < bool , STVError > {
if opts . immediate_elect ! = if_immediate & & opts . surplus ! = SurplusMethod ::Meek {
// For --no-immediate-elect
return Ok ( false ) ;
}
2021-06-29 15:31:38 +10:00
let mut cands_meeting_quota : Vec < ( & Candidate , & CountCard < N > ) > = state . election . candidates . iter ( ) // Present in order in case of tie
. map ( | c | ( c , & state . candidates [ c ] ) )
2021-07-21 13:43:16 +10:00
. filter ( | ( _ , cc ) | { ( cc . state = = CandidateState ::Hopeful | | cc . state = = CandidateState ::Guarded ) & & meets_vre ( state , cc , opts ) } )
2021-05-28 22:37:18 +10:00
. collect ( ) ;
2021-05-28 19:58:40 +10:00
2021-06-27 21:57:24 +10:00
// Sort by votes
2021-07-23 01:21:29 +10:00
cands_meeting_quota . sort_unstable_by ( | a , b | b . 1. votes . cmp ( & a . 1. votes ) ) ;
2021-06-29 15:31:38 +10:00
let mut cands_meeting_quota : Vec < & Candidate > = cands_meeting_quota . iter ( ) . map ( | ( c , _ ) | * c ) . collect ( ) ;
2021-06-27 21:57:24 +10:00
let elected = ! cands_meeting_quota . is_empty ( ) ;
2021-09-09 01:19:31 +10:00
while ! cands_meeting_quota . is_empty ( ) & & state . num_elected < state . election . seats {
2021-05-28 19:58:40 +10:00
// Declare elected in descending order of votes
2021-06-29 15:31:38 +10:00
let max_cands = ties ::multiple_max_by ( & cands_meeting_quota , | c | & state . candidates [ c ] . votes ) ;
let candidate = if max_cands . len ( ) > 1 {
2021-09-26 02:27:37 +10:00
choose_highest ( state , opts , & max_cands , " Which candidate to elect? " ) ?
2021-06-28 00:56:28 +10:00
} else {
2021-06-29 15:31:38 +10:00
max_cands [ 0 ]
} ;
2021-06-27 21:57:24 +10:00
2022-04-16 02:27:59 +10:00
if opts . constraint_mode = = ConstraintMode ::RepeatCount & & state . election . constraints . is_some ( ) {
if let Err ( ( constraint , group ) ) = constraints ::test_constraints_immediate ( state , & [ candidate ] , CandidateState ::Elected ) {
// This election would violate a constraint, so stop here
state . logger . log_smart (
" The election of {} now would violate constraints. " ,
" The election of {} now would violate constraints. " ,
vec! [ candidate . name . as_str ( ) ]
) ;
// Trigger rollback
constraints ::init_repeat_count_rollback ( state , constraint , group ) ;
return Ok ( elected ) ;
}
}
2021-06-27 21:57:24 +10:00
let count_card = state . candidates . get_mut ( candidate ) . unwrap ( ) ;
count_card . state = CandidateState ::Elected ;
state . num_elected + = 1 ;
count_card . order_elected = state . num_elected as isize ;
2021-08-09 19:50:07 +10:00
let elected_on_quota ;
2021-07-21 13:43:16 +10:00
if cmp_quota_criterion ( state . quota . as_ref ( ) . unwrap ( ) , count_card , opts ) {
2021-07-21 10:59:06 +10:00
// Elected with a quota
2021-08-09 19:50:07 +10:00
elected_on_quota = true ;
2021-07-21 10:59:06 +10:00
state . logger . log_smart (
" {} meets the quota and is elected. " ,
" {} meet the quota and are elected. " ,
2022-04-16 02:27:59 +10:00
vec! [ candidate . name . as_str ( ) ]
2021-07-21 10:59:06 +10:00
) ;
} else {
// Elected with vote required
2021-08-09 19:50:07 +10:00
elected_on_quota = false ;
2021-07-21 10:59:06 +10:00
state . logger . log_smart (
" {} meets the vote required and is elected. " ,
" {} meet the vote required and are elected. " ,
2022-04-16 02:27:59 +10:00
vec! [ candidate . name . as_str ( ) ]
2021-07-21 10:59:06 +10:00
) ;
}
2021-06-27 21:57:24 +10:00
2021-06-28 00:56:28 +10:00
if constraints ::update_constraints ( state , opts ) {
2021-06-27 21:57:24 +10:00
// Recheck as some candidates may have been doomed
2021-06-29 15:31:38 +10:00
let mut cmq : Vec < ( & Candidate , & CountCard < N > ) > = state . election . candidates . iter ( ) // Present in order in case of tie
. map ( | c | ( c , & state . candidates [ c ] ) )
2021-07-21 13:43:16 +10:00
. filter ( | ( _ , cc ) | { ( cc . state = = CandidateState ::Hopeful | | cc . state = = CandidateState ::Guarded ) & & meets_vre ( state , cc , opts ) } )
2021-06-27 21:57:24 +10:00
. collect ( ) ;
2021-07-23 01:21:29 +10:00
cmq . sort_unstable_by ( | a , b | b . 1. votes . cmp ( & a . 1. votes ) ) ;
2021-06-29 15:31:38 +10:00
cands_meeting_quota = cmq . iter ( ) . map ( | ( c , _ ) | * c ) . collect ( ) ;
2021-06-28 00:56:28 +10:00
} else {
cands_meeting_quota . remove ( cands_meeting_quota . iter ( ) . position ( | c | * c = = candidate ) . unwrap ( ) ) ;
2021-06-27 21:57:24 +10:00
}
2021-09-10 01:42:42 +10:00
if opts . quota_mode = = QuotaMode ::ERS97 | | opts . quota_mode = = QuotaMode ::ERS76 | | opts . quota_mode = = QuotaMode ::DynamicByActive {
2021-06-27 21:57:24 +10:00
// Vote required for election may have changed
2021-08-09 19:50:07 +10:00
// ERS97: Check this after every elected candidate (cf. model election)
// ERS76: Check this after every candidate elected on a quota, but all at once for candidates elected on VRE (cf. model election)
2021-09-10 01:42:42 +10:00
if opts . quota_mode = = QuotaMode ::ERS97 | | ( opts . quota_mode = = QuotaMode ::ERS76 & & elected_on_quota ) | | opts . quota_mode = = QuotaMode ::DynamicByActive {
calculate_quota ( state , opts ) ;
// Repeat in case vote required for election has changed
match elect_hopefuls ( state , opts , true ) {
Ok ( _ ) = > { break ; }
Err ( e ) = > { return Err ( e ) ; }
}
2021-06-27 21:57:24 +10:00
}
2021-09-10 01:42:42 +10:00
} else if opts . early_bulk_elect {
// Vote required for election may have changed for display purposes
update_vre_bulk ( state , opts ) ;
2021-06-27 21:57:24 +10:00
}
}
2021-07-28 00:12:57 +10:00
// Determine if early bulk election can be effected
if opts . early_bulk_elect {
if elect_sure_winners ( state , opts ) ? {
return Ok ( true ) ;
}
}
2021-06-28 00:56:28 +10:00
return Ok ( elected ) ;
2021-05-28 19:58:40 +10:00
}
2021-06-14 20:43:36 +10:00
/// Determine whether the transfer of all surpluses can be deferred
///
2021-08-05 20:18:10 +10:00
/// The value of [STVOptions::defer_surpluses] is not taken into account and must be handled by the caller.
2021-06-09 12:42:47 +10:00
fn can_defer_surpluses < N : Number > ( state : & CountState < N > , opts : & STVOptions , total_surpluses : & N ) -> bool
2021-06-09 12:16:25 +10:00
where
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N >
{
// Do not defer if this could change the last 2 candidates
2022-08-21 05:24:54 +10:00
let mut hopefuls : Vec < ( & Candidate , & CountCard < N > ) > = state . candidates . iter ( )
2021-06-12 00:50:01 +10:00
. filter ( | ( _ , cc ) | cc . state = = CandidateState ::Hopeful | | cc . state = = CandidateState ::Guarded )
2021-06-09 12:16:25 +10:00
. collect ( ) ;
2021-09-09 13:36:27 +10:00
if hopefuls . len ( ) < 2 {
return true ;
}
2021-06-09 12:16:25 +10:00
hopefuls . sort_unstable_by ( | ( _ , cc1 ) , ( _ , cc2 ) | cc1 . votes . cmp ( & cc2 . votes ) ) ;
2022-06-18 01:59:29 +10:00
if total_surpluses > = & ( & hopefuls [ 1 ] . 1. votes - & hopefuls [ 0 ] . 1. votes ) {
2021-06-09 12:16:25 +10:00
return false ;
}
// Do not defer if this could affect a bulk exclusion
if opts . bulk_exclude {
let to_exclude = hopefuls_to_bulk_exclude ( state , opts ) ;
let num_to_exclude = to_exclude . len ( ) ;
if num_to_exclude > 0 {
2022-08-21 07:20:21 +10:00
let total_excluded = state . total_votes_of ( to_exclude . into_iter ( ) . map ( | c | & state . candidates [ c ] ) ) ;
2022-06-18 01:59:29 +10:00
if total_surpluses > = & ( & hopefuls [ num_to_exclude ] . 1. votes - & total_excluded ) {
2021-06-09 12:16:25 +10:00
return false ;
}
}
}
return true ;
}
2021-06-14 20:43:36 +10:00
/// Distribute surpluses according to [STVOptions::surplus]
2021-08-05 20:18:10 +10:00
///
/// Returns `true` if any surpluses were distributed.
2021-06-12 02:09:26 +10:00
fn distribute_surpluses < N : Number > ( state : & mut CountState < N > , opts : & STVOptions ) -> Result < bool , STVError >
2021-05-29 00:43:58 +10:00
where
2021-08-16 00:46:05 +10:00
for < ' r > & ' r N : ops ::Add < & ' r N , Output = N > ,
2021-05-30 02:28:52 +10:00
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N > ,
2021-06-16 13:00:54 +10:00
for < ' r > & ' r N : ops ::Mul < & ' r N , Output = N > ,
2021-06-11 21:22:28 +10:00
for < ' r > & ' r N : ops ::Div < & ' r N , Output = N > ,
2021-06-16 13:00:54 +10:00
for < ' r > & ' r N : ops ::Neg < Output = N > ,
2021-05-29 00:43:58 +10:00
{
2021-05-31 22:25:53 +10:00
match opts . surplus {
2021-09-27 19:02:30 +10:00
SurplusMethod ::WIG | SurplusMethod ::UIG | SurplusMethod ::EG | SurplusMethod ::IHare | SurplusMethod ::Hare = > {
2021-06-14 20:43:36 +10:00
return gregory ::distribute_surpluses ( state , opts ) ;
2021-05-31 22:25:53 +10:00
}
SurplusMethod ::Meek = > {
2021-06-16 13:00:54 +10:00
return meek ::distribute_surpluses ( state , opts ) ;
2021-05-31 22:25:53 +10:00
}
}
2021-05-28 19:58:40 +10:00
}
2021-07-21 00:45:10 +10:00
/// Determine if, with the proposed exclusion of num_to_exclude candidates (if any), a bulk election can be made
fn can_bulk_elect < N : Number > ( state : & CountState < N > , num_to_exclude : usize ) -> bool {
let num_hopefuls = state . election . candidates . iter ( )
. filter ( | c | {
let cc = & state . candidates [ c ] ;
2021-08-02 00:24:41 +10:00
// Include doomed candidates here as these are included in num_to_exclude and so will later be subtracted
return cc . state = = CandidateState ::Hopeful | | cc . state = = CandidateState ::Guarded | | cc . state = = CandidateState ::Doomed ;
2021-07-21 00:45:10 +10:00
} )
. count ( ) ;
2021-09-05 23:13:37 +10:00
if num_hopefuls - num_to_exclude > 0 & & state . num_elected + num_hopefuls - num_to_exclude < = state . election . seats {
2021-07-21 00:45:10 +10:00
return true ;
}
return false ;
}
/// Declare all continuing candidates to be elected
2021-08-03 18:38:45 +10:00
fn do_bulk_elect < N : Number > ( state : & mut CountState < N > , opts : & STVOptions , template1 : & 'static str , template2 : & 'static str ) -> Result < ( ) , STVError > {
2021-06-27 23:20:35 +10:00
let mut hopefuls : Vec < & Candidate > = state . election . candidates . iter ( )
. filter ( | c | {
2021-06-29 15:31:38 +10:00
let cc = & state . candidates [ c ] ;
2021-06-27 23:20:35 +10:00
return cc . state = = CandidateState ::Hopeful | | cc . state = = CandidateState ::Guarded ;
} )
. collect ( ) ;
2021-07-21 00:45:10 +10:00
// Bulk elect all remaining candidates
while ! hopefuls . is_empty ( ) {
let max_cands = ties ::multiple_max_by ( & hopefuls , | c | & state . candidates [ c ] . votes ) ;
let candidate = if max_cands . len ( ) > 1 {
2021-09-26 02:27:37 +10:00
choose_highest ( state , opts , & max_cands , " Which candidate to elect? " ) ?
2021-07-21 00:45:10 +10:00
} else {
max_cands [ 0 ]
} ;
2021-05-29 01:22:46 +10:00
2021-07-21 00:45:10 +10:00
let count_card = state . candidates . get_mut ( candidate ) . unwrap ( ) ;
count_card . state = CandidateState ::Elected ;
state . num_elected + = 1 ;
count_card . order_elected = state . num_elected as isize ;
state . logger . log_smart (
template1 ,
template2 ,
2022-04-16 02:27:59 +10:00
vec! [ candidate . name . as_str ( ) ]
2021-07-21 00:45:10 +10:00
) ;
if constraints ::update_constraints ( state , opts ) {
// Recheck as some candidates may have been doomed
hopefuls = state . election . candidates . iter ( )
. filter ( | c | {
let cc = & state . candidates [ c ] ;
return cc . state = = CandidateState ::Hopeful | | cc . state = = CandidateState ::Guarded ;
} )
. collect ( ) ;
} else {
hopefuls . remove ( hopefuls . iter ( ) . position ( | c | * c = = candidate ) . unwrap ( ) ) ;
2021-05-28 19:58:40 +10:00
}
2021-07-21 00:45:10 +10:00
}
2021-08-03 18:38:45 +10:00
return Ok ( ( ) ) ;
2021-07-21 00:45:10 +10:00
}
/// Declare all continuing candidates elected, if the number equals the number of remaining vacancies
2021-08-05 20:18:10 +10:00
///
/// Returns `true` if any candidates were elected.
2021-07-21 00:45:10 +10:00
fn bulk_elect < N : Number > ( state : & mut CountState < N > , opts : & STVOptions ) -> Result < bool , STVError > {
if can_bulk_elect ( state , 0 ) {
2021-09-06 02:43:33 +10:00
state . title = StageKind ::BulkElection ;
2021-08-03 18:38:45 +10:00
do_bulk_elect ( state , opts , " {} is elected to fill the remaining vacancy. " , " {} are elected to fill the remaining vacancies. " ) ? ;
return Ok ( true ) ;
2021-05-28 19:58:40 +10:00
}
2021-06-13 00:15:14 +10:00
return Ok ( false ) ;
2021-05-28 19:58:40 +10:00
}
2021-08-05 20:18:10 +10:00
/// Declare all doomed candidates excluded
///
/// Returns `true` if any candidates were excluded.
2021-06-27 21:57:24 +10:00
fn exclude_doomed < ' a , N : Number > ( state : & mut CountState < ' a , N > , opts : & STVOptions ) -> Result < bool , STVError >
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 > ,
{
2021-06-29 15:31:38 +10:00
let doomed : Vec < & Candidate > = state . election . candidates . iter ( ) // Present in order in case of tie
. filter ( | c | state . candidates [ c ] . state = = CandidateState ::Doomed )
2021-06-27 21:57:24 +10:00
. collect ( ) ;
if ! doomed . is_empty ( ) {
let excluded_candidates ;
if opts . bulk_exclude {
2021-06-29 15:31:38 +10:00
excluded_candidates = doomed ;
2021-06-27 21:57:24 +10:00
} else {
// Exclude only the lowest-ranked doomed candidate
2021-06-29 15:31:38 +10:00
let min_cands = ties ::multiple_min_by ( & doomed , | c | & state . candidates [ c ] . votes ) ;
excluded_candidates = if min_cands . len ( ) > 1 {
2021-09-26 02:27:37 +10:00
vec! [ choose_lowest ( state , opts , & min_cands , " Which candidate to exclude? " ) ? ]
2021-06-27 21:57:24 +10:00
} else {
2021-06-29 15:31:38 +10:00
vec! [ min_cands [ 0 ] ]
} ;
2021-06-27 21:57:24 +10:00
}
2021-06-28 00:56:28 +10:00
let names : Vec < & str > = excluded_candidates . iter ( ) . map ( | c | c . name . as_str ( ) ) . sorted ( ) . collect ( ) ;
2021-09-06 02:43:33 +10:00
state . title = StageKind ::ExclusionOf ( excluded_candidates . clone ( ) ) ;
2021-06-27 21:57:24 +10:00
state . logger . log_smart (
" Doomed candidate, {}, is excluded. " ,
" Doomed candidates, {}, are excluded. " ,
names
) ;
2021-07-21 00:45:10 +10:00
if opts . early_bulk_elect {
// Determine if the proposed exclusion would enable a bulk election
2021-07-22 00:40:01 +10:00
// See comment in exclude_hopefuls as to constraints
2021-07-21 00:45:10 +10:00
if can_bulk_elect ( state , excluded_candidates . len ( ) ) {
// Exclude candidates without further transfers
let order_excluded = state . num_excluded + 1 ;
for candidate in excluded_candidates {
let count_card = state . candidates . get_mut ( candidate ) . unwrap ( ) ;
count_card . state = CandidateState ::Excluded ;
state . num_excluded + = 1 ;
count_card . order_elected = - ( order_excluded as isize ) ;
}
2021-08-03 18:38:45 +10:00
do_bulk_elect ( state , opts , " As a result of the proposed exclusion, {} is elected to fill the remaining vacancy. " , " As a result of the proposed exclusion, {} are elected to fill the remaining vacancies. " ) ? ;
return Ok ( true ) ;
2021-07-21 00:45:10 +10:00
}
}
2022-04-20 20:12:50 +10:00
exclude_candidates ( state , opts , excluded_candidates , " Exclusion " ) ? ;
2021-08-03 18:38:45 +10:00
return Ok ( true ) ;
2021-06-27 21:57:24 +10:00
}
return Ok ( false ) ;
}
2021-08-03 23:22:52 +10:00
/// Determine which continuing candidates have votes equal to or below the minimum threshold
fn hopefuls_below_threshold < ' a , N : Number > ( state : & CountState < ' a , N > , opts : & STVOptions ) -> Vec < & ' a Candidate > {
let min_threshold = N ::parse ( & opts . min_threshold ) ;
let excluded_candidates : Vec < & Candidate > = state . candidates . iter ( )
. filter_map ( | ( c , cc ) |
if cc . state = = CandidateState ::Hopeful & & cc . votes < = min_threshold {
2022-08-21 05:24:54 +10:00
Some ( c )
2021-08-03 23:22:52 +10:00
} else {
None
} )
. collect ( ) ;
// Do not exclude if this violates constraints
2022-04-16 02:27:59 +10:00
match constraints ::test_constraints_any_time ( state , & excluded_candidates , CandidateState ::Excluded ) {
2021-08-03 23:22:52 +10:00
Ok ( _ ) = > { return excluded_candidates ; }
Err ( _ ) = > { return Vec ::new ( ) ; } // Bulk exclusion conflicts with constraints
}
}
2021-06-14 20:43:36 +10:00
/// Determine which continuing candidates could be excluded in a bulk exclusion
///
2021-08-05 20:18:10 +10:00
/// The value of [STVOptions::bulk_exclude] is not taken into account and must be handled by the caller.
2021-06-09 12:16:25 +10:00
fn hopefuls_to_bulk_exclude < ' a , N : Number > ( state : & CountState < ' a , N > , _opts : & STVOptions ) -> Vec < & ' a Candidate > {
let mut excluded_candidates = Vec ::new ( ) ;
2022-08-21 05:24:54 +10:00
let mut hopefuls : Vec < ( & Candidate , & CountCard < N > ) > = state . candidates . iter ( )
2021-06-12 00:50:01 +10:00
. filter ( | ( _ , cc ) | cc . state = = CandidateState ::Hopeful )
2021-05-28 22:37:18 +10:00
. collect ( ) ;
2021-05-28 19:58:40 +10:00
// Sort by votes
2021-06-13 00:15:14 +10:00
// NB: Unnecessary to handle ties, as ties will be rejected at "Do not exclude if this could change the order of exclusion"
hopefuls . sort_unstable_by ( | a , b | a . 1. votes . cmp ( & b . 1. votes ) ) ;
2021-05-28 19:58:40 +10:00
2022-08-21 07:20:21 +10:00
let total_surpluses = state . total_surplus ( ) ;
2021-06-09 12:16:25 +10:00
// Attempt to exclude as many candidates as possible
for i in 0 .. hopefuls . len ( ) {
let try_exclude = & hopefuls [ 0 .. hopefuls . len ( ) - i ] ;
// Do not exclude if this leaves insufficient candidates
if state . num_elected + hopefuls . len ( ) - try_exclude . len ( ) < state . election . seats {
continue ;
}
// Do not exclude if this could change the order of exclusion
2022-08-21 07:20:21 +10:00
let total_votes = state . total_votes_of ( try_exclude . iter ( ) . map ( | ( _ , cc ) | * cc ) ) ;
2021-06-13 00:15:14 +10:00
if i ! = 0 & & total_votes + & total_surpluses > = hopefuls [ hopefuls . len ( ) - i ] . 1. votes {
2021-06-09 12:16:25 +10:00
continue ;
}
2022-08-21 05:24:54 +10:00
let try_exclude : Vec < & Candidate > = try_exclude . iter ( ) . map ( | ( c , _ ) | * c ) . collect ( ) ;
2021-08-01 23:50:15 +10:00
// Do not exclude if this violates constraints
2022-04-16 02:27:59 +10:00
match constraints ::test_constraints_any_time ( state , & try_exclude , CandidateState ::Excluded ) {
2021-08-01 23:50:15 +10:00
Ok ( _ ) = > { }
Err ( _ ) = > { break ; } // Bulk exclusion conflicts with constraints
2021-06-09 12:16:25 +10:00
}
2021-08-01 23:50:15 +10:00
excluded_candidates . extend ( try_exclude ) ;
2021-06-09 12:16:25 +10:00
break ;
}
return excluded_candidates ;
}
2021-06-14 20:43:36 +10:00
/// Exclude the lowest-ranked hopeful candidate(s)
2021-08-03 18:38:45 +10:00
fn exclude_hopefuls < ' a , N : Number > ( state : & mut CountState < ' a , N > , opts : & STVOptions ) -> Result < ( ) , STVError >
2021-06-09 12:16:25 +10:00
where
2021-06-16 13:00:54 +10:00
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N > ,
for < ' r > & ' r N : ops ::Mul < & ' r N , Output = N > ,
2021-06-09 12:16:25 +10:00
for < ' r > & ' r N : ops ::Div < & ' r N , Output = N > ,
{
2021-06-08 22:22:43 +10:00
let mut excluded_candidates : Vec < & Candidate > = Vec ::new ( ) ;
2021-08-03 23:22:52 +10:00
if state . num_excluded = = 0 {
2021-08-05 18:41:39 +10:00
if opts . bulk_exclude & & opts . min_threshold = = " 0 " {
// Proceed directly to bulk exclusion, as candidates with 0 votes will necessarily be included
} else {
// Exclude candidates below min threshold
excluded_candidates = hopefuls_below_threshold ( state , opts ) ;
}
2021-08-03 23:22:52 +10:00
}
2021-06-08 22:22:43 +10:00
// Attempt a bulk exclusion
2021-08-03 23:22:52 +10:00
if excluded_candidates . is_empty ( ) & & opts . bulk_exclude {
2021-06-09 12:16:25 +10:00
excluded_candidates = hopefuls_to_bulk_exclude ( state , opts ) ;
2021-06-08 22:22:43 +10:00
}
2021-05-28 19:58:40 +10:00
// Exclude lowest ranked candidate
2021-06-13 00:15:14 +10:00
if excluded_candidates . is_empty ( ) {
2021-06-29 15:31:38 +10:00
let hopefuls : Vec < & Candidate > = state . election . candidates . iter ( ) // Present in order in case of tie
. filter ( | c | state . candidates [ c ] . state = = CandidateState ::Hopeful )
2021-06-12 00:50:01 +10:00
. collect ( ) ;
2021-06-09 12:16:25 +10:00
2021-06-29 15:31:38 +10:00
let min_cands = ties ::multiple_min_by ( & hopefuls , | c | & state . candidates [ c ] . votes ) ;
excluded_candidates = if min_cands . len ( ) > 1 {
2021-09-26 02:27:37 +10:00
vec! [ choose_lowest ( state , opts , & min_cands , " Which candidate to exclude? " ) ? ]
2021-06-12 02:09:26 +10:00
} else {
2021-06-29 15:31:38 +10:00
vec! [ min_cands [ 0 ] ]
} ;
2021-06-08 22:22:43 +10:00
}
2021-05-29 01:22:46 +10:00
2021-06-28 00:56:28 +10:00
let names : Vec < & str > = excluded_candidates . iter ( ) . map ( | c | c . name . as_str ( ) ) . sorted ( ) . collect ( ) ;
2021-09-06 02:43:33 +10:00
state . title = StageKind ::ExclusionOf ( excluded_candidates . clone ( ) ) ;
2021-05-29 02:13:47 +10:00
state . logger . log_smart (
" No surpluses to distribute, so {} is excluded. " ,
" No surpluses to distribute, so {} are excluded. " ,
2021-06-08 22:22:43 +10:00
names
2021-05-29 02:13:47 +10:00
) ;
2021-05-29 01:22:46 +10:00
2021-07-21 00:45:10 +10:00
if opts . early_bulk_elect {
// Determine if the proposed exclusion would enable a bulk election
2021-07-22 00:40:01 +10:00
// This should be OK for constraints, as if the election of the remaining candidates would be invalid, the excluded candidate must necessarily have be guarded already
2021-07-21 00:45:10 +10:00
if can_bulk_elect ( state , excluded_candidates . len ( ) ) {
// Exclude candidates without further transfers
let order_excluded = state . num_excluded + 1 ;
for candidate in excluded_candidates {
let count_card = state . candidates . get_mut ( candidate ) . unwrap ( ) ;
count_card . state = CandidateState ::Excluded ;
state . num_excluded + = 1 ;
count_card . order_elected = - ( order_excluded as isize ) ;
}
2021-08-03 18:38:45 +10:00
do_bulk_elect ( state , opts , " As a result of the proposed exclusion, {} is elected to fill the remaining vacancy. " , " As a result of the proposed exclusion, {} are elected to fill the remaining vacancies. " ) ? ;
return Ok ( ( ) ) ;
2021-07-21 00:45:10 +10:00
}
}
2021-05-28 19:58:40 +10:00
2022-04-20 20:12:50 +10:00
exclude_candidates ( state , opts , excluded_candidates , " Exclusion " ) ? ;
2021-08-03 18:38:45 +10:00
return Ok ( ( ) ) ;
2021-05-28 19:58:40 +10:00
}
2021-06-14 20:43:36 +10:00
/// Continue the exclusion of a candidate who is being excluded
2021-08-05 20:18:10 +10:00
///
/// Returns `true` if an exclusion was continued.
2021-07-21 00:45:10 +10:00
fn continue_exclusion < ' a , N : Number > ( state : & mut CountState < ' a , N > , opts : & STVOptions ) -> Result < bool , STVError >
2021-05-30 02:28:52 +10:00
where
2021-06-16 13:00:54 +10:00
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N > ,
for < ' r > & ' r N : ops ::Mul < & ' r N , Output = N > ,
2021-05-30 02:28:52 +10:00
for < ' r > & ' r N : ops ::Div < & ' r N , Output = N > ,
{
// Cannot filter by raw vote count, as candidates may have 0.00 votes but still have recorded ballot papers
2022-08-21 05:24:54 +10:00
let mut excluded_with_votes : Vec < ( & Candidate , & CountCard < N > ) > = state . candidates . iter ( )
2021-08-08 19:11:15 +10:00
. filter ( | ( _ , cc ) | cc . state = = CandidateState ::Excluded & & ! cc . finalised )
2021-05-28 22:37:18 +10:00
. collect ( ) ;
2021-05-28 19:58:40 +10:00
2021-06-13 00:15:14 +10:00
if ! excluded_with_votes . is_empty ( ) {
excluded_with_votes . sort_unstable_by ( | a , b | a . 1. order_elected . cmp ( & b . 1. order_elected ) ) ;
2021-05-29 01:22:46 +10:00
2021-06-13 00:15:14 +10:00
let order_excluded = excluded_with_votes [ 0 ] . 1. order_elected ;
2021-06-08 22:22:43 +10:00
let excluded_candidates : Vec < & Candidate > = excluded_with_votes . into_iter ( )
. filter ( | ( _ , cc ) | cc . order_elected = = order_excluded )
2022-08-21 05:24:54 +10:00
. map ( | ( c , _ ) | c )
2021-06-08 22:22:43 +10:00
. collect ( ) ;
2021-06-28 00:56:28 +10:00
let names : Vec < & str > = excluded_candidates . iter ( ) . map ( | c | c . name . as_str ( ) ) . sorted ( ) . collect ( ) ;
2021-09-06 02:43:33 +10:00
state . title = StageKind ::ExclusionOf ( excluded_candidates . clone ( ) ) ;
2021-05-29 02:13:47 +10:00
state . logger . log_smart (
" Continuing exclusion of {}. " ,
" Continuing exclusion of {}. " ,
2021-06-08 22:22:43 +10:00
names
2021-05-29 02:13:47 +10:00
) ;
2021-05-29 01:22:46 +10:00
2022-04-20 20:12:50 +10:00
exclude_candidates ( state , opts , excluded_candidates , " Exclusion " ) ? ;
2021-08-03 18:38:45 +10:00
return Ok ( true ) ;
2021-05-28 19:58:40 +10:00
}
2021-07-21 00:45:10 +10:00
return Ok ( false ) ;
2021-05-28 19:58:40 +10:00
}
2021-06-14 20:43:36 +10:00
/// Perform one stage of a candidate exclusion, according to [STVOptions::exclusion]
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 ) -> Result < ( ) , STVError >
2021-05-30 02:28:52 +10:00
where
2021-06-16 13:00:54 +10:00
for < ' r > & ' r N : ops ::Sub < & ' r N , Output = N > ,
for < ' r > & ' r N : ops ::Mul < & ' r N , Output = N > ,
2021-05-30 02:28:52 +10:00
for < ' r > & ' r N : ops ::Div < & ' r N , Output = N > ,
{
2021-06-22 14:34:26 +10:00
match opts . exclusion {
ExclusionMethod ::SingleStage = > {
match opts . surplus {
SurplusMethod ::WIG | SurplusMethod ::UIG | SurplusMethod ::EG = > {
2022-04-20 20:12:50 +10:00
gregory ::exclude_candidates ( state , opts , excluded_candidates , complete_type ) ;
2021-06-22 14:34:26 +10:00
}
SurplusMethod ::Meek = > {
meek ::exclude_candidates ( state , opts , excluded_candidates ) ;
}
2021-09-27 19:02:30 +10:00
SurplusMethod ::IHare | SurplusMethod ::Hare = > {
2021-08-04 13:46:32 +10:00
sample ::exclude_candidates ( state , opts , excluded_candidates ) ? ;
2021-08-03 18:38:45 +10:00
}
2021-06-22 14:34:26 +10:00
}
}
2021-07-19 23:15:17 +10:00
ExclusionMethod ::ByValue | ExclusionMethod ::BySource | ExclusionMethod ::ParcelsByOrder = > {
2021-06-22 14:34:26 +10:00
// Exclusion in parts compatible only with Gregory method
2022-04-20 20:12:50 +10:00
gregory ::exclude_candidates ( state , opts , excluded_candidates , complete_type ) ;
2021-06-08 22:22:43 +10:00
}
2022-08-23 18:37:42 +10:00
ExclusionMethod ::ResetAndReiterate = > {
gregory ::exclude_candidates_and_reset ( state , opts , excluded_candidates ) ;
2021-05-30 02:28:52 +10:00
}
}
2021-07-21 00:45:10 +10:00
2021-08-03 18:38:45 +10:00
return Ok ( ( ) ) ;
2021-05-28 19:58:40 +10:00
}
2021-06-14 20:43:36 +10:00
/// Determine if the count is complete because the number of elected candidates equals the number of vacancies
2021-05-29 17:51:45 +10:00
fn finished_before_stage < N : Number > ( state : & CountState < N > ) -> bool {
2021-05-28 19:58:40 +10:00
if state . num_elected > = state . election . seats {
return true ;
}
return false ;
}
2021-06-13 00:15:14 +10:00
2021-06-14 20:43:36 +10:00
/// Break a tie between the given candidates according to [STVOptions::ties], selecting the highest candidate
///
2021-08-05 20:18:10 +10:00
/// The given candidates are assumed to be tied in this round.
2021-10-27 19:52:51 +11:00
pub fn choose_highest < ' c , N : Number > ( state : & mut CountState < N > , opts : & STVOptions , candidates : & [ & ' c Candidate ] , prompt_text : & str ) -> Result < & ' c Candidate , STVError > {
2021-06-13 00:15:14 +10:00
for strategy in opts . ties . iter ( ) {
2021-10-27 19:52:51 +11:00
match strategy . choose_highest ( state , opts , candidates , prompt_text ) {
2021-06-13 00:15:14 +10:00
Ok ( c ) = > {
return Ok ( c ) ;
}
Err ( e ) = > {
if let STVError ::UnresolvedTie = e {
continue ;
} else {
return Err ( e ) ;
}
}
}
}
2021-07-31 15:24:23 +10:00
return Err ( STVError ::UnresolvedTie ) ;
2021-06-13 00:15:14 +10:00
}
2021-06-14 20:43:36 +10:00
/// Break a tie between the given candidates according to [STVOptions::ties], selecting the lowest candidate
///
2021-08-05 20:18:10 +10:00
/// The given candidates are assumed to be tied in this round.
2021-10-27 19:52:51 +11:00
pub fn choose_lowest < ' c , N : Number > ( state : & mut CountState < N > , opts : & STVOptions , candidates : & [ & ' c Candidate ] , prompt_text : & str ) -> Result < & ' c Candidate , STVError > {
2021-06-13 00:15:14 +10:00
for strategy in opts . ties . iter ( ) {
2021-10-27 19:52:51 +11:00
match strategy . choose_lowest ( state , opts , candidates , prompt_text ) {
2021-06-13 00:15:14 +10:00
Ok ( c ) = > {
return Ok ( c ) ;
}
Err ( e ) = > {
if let STVError ::UnresolvedTie = e {
continue ;
} else {
return Err ( e ) ;
}
}
}
}
2021-07-31 15:24:23 +10:00
return Err ( STVError ::UnresolvedTie ) ;
2021-06-13 00:15:14 +10:00
}
2021-06-14 20:43:36 +10:00
/// If required, initialise the state of the forwards or backwards tie-breaking strategies, according to [STVOptions::ties]
2021-06-13 00:15:14 +10:00
fn init_tiebreaks < N : Number > ( state : & mut CountState < N > , opts : & STVOptions ) {
if ! opts . ties . iter ( ) . any ( | t | t = = & TieStrategy ::Forwards ) & & ! opts . ties . iter ( ) . any ( | t | t = = & TieStrategy ::Backwards ) {
return ;
}
// Sort candidates in this stage by votes, grouping by ties
2022-08-21 05:24:54 +10:00
let mut sorted_candidates : Vec < ( & Candidate , & CountCard < N > ) > = state . candidates . iter ( ) . collect ( ) ;
2021-06-13 00:15:14 +10:00
sorted_candidates . sort_unstable_by ( | a , b | a . 1. votes . cmp ( & b . 1. votes ) ) ;
2022-08-21 05:24:54 +10:00
let sorted_candidates : Vec < Vec < ( & Candidate , & CountCard < N > ) > > = sorted_candidates . into_iter ( )
2021-06-13 00:15:14 +10:00
. group_by ( | ( _ , cc ) | & cc . votes )
. into_iter ( )
. map ( | ( _ , candidates ) | candidates . collect ( ) )
. collect ( ) ;
// Update forwards tie-breaking order
if opts . ties . iter ( ) . any ( | t | t = = & TieStrategy ::Forwards ) {
2022-08-21 03:37:12 +10:00
let mut hm = CandidateMap ::with_capacity ( state . candidates . len ( ) ) ;
2021-06-13 00:15:14 +10:00
for ( i , group ) in sorted_candidates . iter ( ) . enumerate ( ) {
for ( candidate , _ ) in group . iter ( ) {
hm . insert ( candidate , i ) ;
}
}
state . forwards_tiebreak = Some ( hm ) ;
}
// Update backwards tie-breaking order
if opts . ties . iter ( ) . any ( | t | t = = & TieStrategy ::Backwards ) {
2022-08-21 03:37:12 +10:00
let mut hm = CandidateMap ::with_capacity ( state . candidates . len ( ) ) ;
2021-06-13 00:15:14 +10:00
for ( i , group ) in sorted_candidates . iter ( ) . enumerate ( ) {
for ( candidate , _ ) in group . iter ( ) {
hm . insert ( candidate , i ) ;
}
}
state . backwards_tiebreak = Some ( hm ) ;
}
}
2021-06-14 20:43:36 +10:00
/// If required, update the state of the forwards or backwards tie-breaking strategies, according to [STVOptions::ties]
2021-06-13 00:15:14 +10:00
fn update_tiebreaks < N : Number > ( state : & mut CountState < N > , _opts : & STVOptions ) {
2021-10-27 19:52:51 +11:00
if state . forwards_tiebreak . is_none ( ) & & state . backwards_tiebreak . is_none ( ) {
return ;
2021-06-13 00:15:14 +10:00
}
// Sort candidates in this stage by votes, grouping by ties
2022-08-21 05:24:54 +10:00
let mut sorted_candidates : Vec < ( & Candidate , & CountCard < N > ) > = state . candidates . iter ( ) . collect ( ) ;
2021-06-13 00:15:14 +10:00
sorted_candidates . sort_unstable_by ( | a , b | a . 1. votes . cmp ( & b . 1. votes ) ) ;
let sorted_candidates : Vec < Vec < & Candidate > > = sorted_candidates . into_iter ( )
. group_by ( | ( _ , cc ) | & cc . votes )
. into_iter ( )
2022-08-21 05:24:54 +10:00
. map ( | ( _ , candidates ) | candidates . map ( | ( c , _ ) | c ) . collect ( ) )
2021-06-13 00:15:14 +10:00
. collect ( ) ;
// Update forwards tie-breaking order
if let Some ( hm ) = state . forwards_tiebreak . as_mut ( ) {
// TODO: Check if already completely sorted
2022-08-21 05:24:54 +10:00
let mut sorted_last_round : Vec < ( & Candidate , & usize ) > = hm . iter ( ) . collect ( ) ;
2021-06-13 00:15:14 +10:00
sorted_last_round . sort_unstable_by ( | a , b | a . 1. cmp ( b . 1 ) ) ;
let sorted_last_round : Vec < Vec < & Candidate > > = sorted_last_round . into_iter ( )
. group_by ( | ( _ , v ) | * * v )
. into_iter ( )
2022-08-21 05:24:54 +10:00
. map ( | ( _ , group ) | group . map ( | ( c , _ ) | c ) . collect ( ) )
2021-06-13 00:15:14 +10:00
. collect ( ) ;
let mut i : usize = 0 ;
for mut group in sorted_last_round . into_iter ( ) {
if group . len ( ) = = 1 {
hm . insert ( group [ 0 ] , i ) ;
i + = 1 ;
continue ;
} else {
// Tied in last round - refer to this round
group . sort_unstable_by ( | a , b |
sorted_candidates . iter ( ) . position ( | x | x . contains ( a ) ) . unwrap ( )
. cmp ( & sorted_candidates . iter ( ) . position ( | x | x . contains ( b ) ) . unwrap ( ) )
) ;
let tied_last_round = group . into_iter ( )
. group_by ( | c | sorted_candidates . iter ( ) . position ( | x | x . contains ( c ) ) . unwrap ( ) ) ;
for ( _ , group2 ) in tied_last_round . into_iter ( ) {
for candidate in group2 {
hm . insert ( candidate , i ) ;
}
i + = 1 ;
}
}
}
}
// Update backwards tie-breaking order
if let Some ( hm ) = state . backwards_tiebreak . as_mut ( ) {
let hm_orig = hm . clone ( ) ;
let mut i : usize = 0 ;
for group in sorted_candidates . iter ( ) {
if group . len ( ) = = 1 {
hm . insert ( group [ 0 ] , i ) ;
i + = 1 ;
continue ;
} else {
// Tied in this round - refer to last round
2021-10-27 19:52:51 +11:00
let mut tied_this_round : Vec < & Candidate > = group . iter ( ) . copied ( ) . collect ( ) ;
2021-06-29 15:31:38 +10:00
tied_this_round . sort_unstable_by ( | a , b | hm_orig [ a ] . cmp ( & hm_orig [ b ] ) ) ;
2021-06-13 00:15:14 +10:00
let tied_this_round = tied_this_round . into_iter ( )
2021-06-29 15:31:38 +10:00
. group_by ( | c | hm_orig [ c ] ) ;
2021-06-13 00:15:14 +10:00
for ( _ , group2 ) in tied_this_round . into_iter ( ) {
for candidate in group2 {
hm . insert ( candidate , i ) ;
}
i + = 1 ;
}
}
}
}
}
2021-09-10 02:41:40 +10:00
/// Returns `true` if the votes required for election should be displayed, based on the given [STVOptions]
pub fn should_show_vre ( opts : & STVOptions ) -> bool {
if opts . quota_mode = = QuotaMode ::ERS97 | | opts . quota_mode = = QuotaMode ::ERS76 {
return true ;
}
2022-06-18 23:07:00 +10:00
if opts . surplus = = SurplusMethod ::Meek & & opts . quota_mode = = QuotaMode ::DynamicByTotal {
// Meek method ensures that, if the quota is recalculated, every candidate will be elected with a quota
2021-09-10 02:41:40 +10:00
return false ;
}
if opts . early_bulk_elect {
return true ;
}
return false ;
}