From 429191dc81dd47616d31fc1f232d03dae51a5ea0 Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Thu, 5 Aug 2021 20:18:10 +1000 Subject: [PATCH] With --sample-per-ballot, terminate immediately on electing the required number --- src/stv/gregory.rs | 4 +- src/stv/mod.rs | 27 ++++++-- src/stv/sample.rs | 125 ++++++++++---------------------------- src/stv/wasm.rs | 6 +- tests/data/CambCC2003.csv | 4 +- tests/data/CambCC2003.ods | Bin 20851 -> 20854 bytes 6 files changed, 61 insertions(+), 105 deletions(-) diff --git a/src/stv/gregory.rs b/src/stv/gregory.rs index d67f7db..fb0b699 100644 --- a/src/stv/gregory.rs +++ b/src/stv/gregory.rs @@ -63,6 +63,8 @@ pub fn distribute_first_preferences(state: &mut CountState) { } /// Distribute the largest surplus according to the Gregory or random subset method, based on [STVOptions::surplus] +/// +/// Returns `true` if any surpluses were distributed. pub fn distribute_surpluses(state: &mut CountState, opts: &STVOptions) -> Result where for<'r> &'r N: ops::Sub<&'r N, Output=N>, @@ -117,7 +119,7 @@ where /// Return the denominator of the surplus fraction /// -/// Returns `None` if the value of transferable votes <= surplus (i.e. all transferable votes are transferred at values received) +/// Returns `None` if the value of transferable votes <= surplus (i.e. all transferable votes are transferred at values received). fn calculate_surplus_denom(surplus: &N, result: &NextPreferencesResult, transferable_votes: &N, weighted: bool, transferable_only: bool) -> Option where for<'r> &'r N: ops::Sub<&'r N, Output=N> diff --git a/src/stv/mod.rs b/src/stv/mod.rs index 8193f21..312fd8a 100644 --- a/src/stv/mod.rs +++ b/src/stv/mod.rs @@ -572,7 +572,7 @@ impl fmt::Display for STVError { } /// Distribute first preferences, and initialise other states such as the random number generator and tie-breaking rules -pub fn count_init<'a, N: Number>(state: &mut CountState<'a, N>, opts: &'a STVOptions) -> Result +pub fn count_init<'a, N: Number>(state: &mut CountState<'a, N>, opts: &'a STVOptions) -> Result<(), STVError> where for<'r> &'r N: ops::Sub<&'r N, Output=N>, for<'r> &'r N: ops::Mul<&'r N, Output=N>, @@ -591,10 +591,12 @@ where elect_hopefuls(state, opts)?; init_tiebreaks(state, opts); - return Ok(true); + return Ok(()); } /// Perform a single stage of the STV count +/// +/// Returns `true` if the count is complete, otherwise `false`. pub fn count_one_stage<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions) -> Result where for<'r> &'r N: ops::Sub<&'r N, Output=N>, @@ -891,6 +893,8 @@ fn meets_vre(state: &CountState, count_card: &CountCard, opts: } /// Declare elected the continuing candidates leading for remaining vacancies if they cannot be overtaken +/// +/// Returns `true` if any candidates were elected. fn elect_sure_winners<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions) -> Result { let num_vacancies = state.election.seats - state.num_elected; if num_vacancies == 0 { @@ -965,6 +969,8 @@ fn elect_sure_winners<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOp } /// Declare elected all candidates meeting the quota, and (if enabled) any candidates who can be early bulk elected because they have sufficiently many votes +/// +/// Returns `true` if any candidates were elected. fn elect_hopefuls<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions) -> Result { let mut cands_meeting_quota: Vec<(&Candidate, &CountCard)> = state.election.candidates.iter() // Present in order in case of tie .map(|c| (c, &state.candidates[c])) @@ -1043,7 +1049,7 @@ fn elect_hopefuls<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOption /// Determine whether the transfer of all surpluses can be deferred /// -/// The value of [STVOptions::defer_surpluses] is not taken into account and must be handled by the caller +/// The value of [STVOptions::defer_surpluses] is not taken into account and must be handled by the caller. fn can_defer_surpluses(state: &CountState, opts: &STVOptions, total_surpluses: &N) -> bool where for<'r> &'r N: ops::Sub<&'r N, Output=N> @@ -1073,6 +1079,8 @@ where } /// Distribute surpluses according to [STVOptions::surplus] +/// +/// Returns `true` if any surpluses were distributed. fn distribute_surpluses(state: &mut CountState, opts: &STVOptions) -> Result where for<'r> &'r N: ops::Sub<&'r N, Output=N>, @@ -1152,6 +1160,8 @@ fn do_bulk_elect(state: &mut CountState, opts: &STVOptions, templa } /// Declare all continuing candidates elected, if the number equals the number of remaining vacancies +/// +/// Returns `true` if any candidates were elected. fn bulk_elect(state: &mut CountState, opts: &STVOptions) -> Result { if can_bulk_elect(state, 0) { state.kind = None; @@ -1163,6 +1173,9 @@ fn bulk_elect(state: &mut CountState, opts: &STVOptions) -> Result return Ok(false); } +/// Declare all doomed candidates excluded +/// +/// Returns `true` if any candidates were excluded. fn exclude_doomed<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions) -> Result where for<'r> &'r N: ops::Sub<&'r N, Output=N>, @@ -1243,7 +1256,7 @@ fn hopefuls_below_threshold<'a, N: Number>(state: &CountState<'a, N>, opts: &STV /// Determine which continuing candidates could be excluded in a bulk exclusion /// -/// The value of [STVOptions::bulk_exclude] is not taken into account and must be handled by the caller +/// The value of [STVOptions::bulk_exclude] is not taken into account and must be handled by the caller. fn hopefuls_to_bulk_exclude<'a, N: Number>(state: &CountState<'a, N>, _opts: &STVOptions) -> Vec<&'a Candidate> { let mut excluded_candidates = Vec::new(); @@ -1358,6 +1371,8 @@ where } /// Continue the exclusion of a candidate who is being excluded +/// +/// Returns `true` if an exclusion was continued. fn continue_exclusion<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions) -> Result where for<'r> &'r N: ops::Sub<&'r N, Output=N>, @@ -1437,7 +1452,7 @@ fn finished_before_stage(state: &CountState) -> bool { /// Break a tie between the given candidates according to [STVOptions::ties], selecting the highest candidate /// -/// The given candidates are assumed to be tied in this round +/// The given candidates are assumed to be tied in this round. fn choose_highest<'c, N: Number>(state: &mut CountState, opts: &STVOptions, candidates: Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> { for strategy in opts.ties.iter() { match strategy.choose_highest(state, opts, &candidates, prompt_text) { @@ -1458,7 +1473,7 @@ fn choose_highest<'c, N: Number>(state: &mut CountState, opts: &STVOptions, c /// Break a tie between the given candidates according to [STVOptions::ties], selecting the lowest candidate /// -/// The given candidates are assumed to be tied in this round +/// The given candidates are assumed to be tied in this round. fn choose_lowest<'c, N: Number>(state: &mut CountState, opts: &STVOptions, candidates: Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> { for strategy in opts.ties.iter() { match strategy.choose_lowest(state, opts, &candidates, prompt_text) { diff --git a/src/stv/sample.rs b/src/stv/sample.rs index 430ddb6..48b9af4 100644 --- a/src/stv/sample.rs +++ b/src/stv/sample.rs @@ -28,7 +28,7 @@ use super::{NextPreferencesResult, STVError}; /// Return the denominator of the surplus fraction /// -/// Returns `None` if transferable ballots <= surplus (i.e. all transferable ballots are transferred at full value) +/// Returns `None` if transferable ballots <= surplus (i.e. all transferable ballots are transferred at full value). fn calculate_surplus_denom(surplus: &N, result: &NextPreferencesResult, transferable_ballots: &N, transferable_only: bool) -> Option where for<'r> &'r N: ops::Sub<&'r N, Output=N> @@ -197,7 +197,10 @@ where match votes.pop() { Some(vote) => { // Transfer to next preference - transfer_ballot(state, opts, elected_candidate, vote)?; + transfer_ballot(state, opts, elected_candidate, vote, opts.transferable_only)?; + if state.num_elected == state.election.seats { + return Ok(()); + } } None => { // We have run out of ballot papers @@ -234,7 +237,10 @@ where while &state.candidates[elected_candidate].votes > state.quota.as_ref().unwrap() { // Transfer one vote to next available preference let vote = numbered_votes.remove(&index).unwrap(); - transfer_ballot(state, opts, elected_candidate, vote)?; + transfer_ballot(state, opts, elected_candidate, vote, opts.transferable_only)?; + if state.num_elected == state.election.seats { + return Ok(()); + } index += skip_value; if index >= total_ballots { @@ -260,8 +266,8 @@ where /// Transfer the given ballot paper to its next available preference, and check for candidates meeting the quota if --sample-per-ballot /// -/// Does nothing if --transferable-only and the ballot is nontransferable. -fn transfer_ballot<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, elected_candidate: &Candidate, mut vote: Vote<'a, N>) -> Result<(), STVError> { +/// If `ignore_nontransferable`, does nothing if --transferable-only and the ballot is nontransferable. +fn transfer_ballot<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, source_candidate: &Candidate, mut vote: Vote<'a, N>, ignore_nontransferable: bool) -> Result<(), STVError> { // Get next preference let mut next_candidate = None; for (i, preference) in vote.ballot.preferences.iter().enumerate().skip(vote.up_to_pref) { @@ -278,7 +284,7 @@ fn transfer_ballot<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptio // Have to structure like this to satisfy Rust's borrow checker if let Some(candidate) = next_candidate { // Available preference - state.candidates.get_mut(elected_candidate).unwrap().transfer(&-vote.value.clone()); + state.candidates.get_mut(source_candidate).unwrap().transfer(&-vote.value.clone()); let count_card = state.candidates.get_mut(candidate).unwrap(); count_card.transfer(&vote.value); @@ -309,10 +315,10 @@ fn transfer_ballot<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptio } } else { // Exhausted - if opts.transferable_only { + if opts.transferable_only && ignore_nontransferable { // Another ballot paper required } else { - state.candidates.get_mut(elected_candidate).unwrap().transfer(&-vote.value.clone()); + state.candidates.get_mut(source_candidate).unwrap().transfer(&-vote.value.clone()); state.exhausted.transfer(&vote.value); match state.exhausted.parcels.last_mut() { @@ -362,97 +368,30 @@ where } } - // Determine votes to transfer - let mut votes = Vec::new(); - + // Count votes + let mut total_ballots: usize = 0; for excluded_candidate in excluded_candidates.iter() { let count_card = state.candidates.get_mut(excluded_candidate).unwrap(); - votes.append(&mut count_card.concat_parcels()); - count_card.parcels.clear(); + total_ballots = count_card.parcels.iter() + .fold(total_ballots, |acc, p| acc + p.votes.len()); } - let total_ballots = votes.len(); + if total_ballots == 1 { + state.logger.log_literal("Transferring 1 ballot.".to_string()); + } else { + state.logger.log_literal(format!("Transferring {:.0} ballots.", total_ballots)); + } - if !votes.is_empty() { - if total_ballots == 1 { - state.logger.log_literal("Transferring 1 ballot.".to_string()); - } else { - state.logger.log_literal(format!("Transferring {:.0} ballots.", total_ballots)); - } + // Transfer votes + for excluded_candidate in excluded_candidates.iter() { + let count_card = state.candidates.get_mut(excluded_candidate).unwrap(); + let votes = count_card.concat_parcels(); + count_card.parcels.clear(); - // Transfer vote by vote - for mut vote in votes { - // Subtract votes from excluded candidate - let count_card = state.candidates.get_mut(&state.election.candidates[vote.ballot.preferences[vote.up_to_pref - 1]]).unwrap(); - count_card.transfer(&-vote.value.clone()); - - // Transfer to next preference - let mut next_candidate = None; - for (i, preference) in vote.ballot.preferences.iter().enumerate().skip(vote.up_to_pref) { - 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); - vote.up_to_pref = i + 1; - break; - } - } - - // Have to structure like this to satisfy Rust's borrow checker - if let Some(candidate) = next_candidate { - // Available preference - let count_card = state.candidates.get_mut(candidate).unwrap(); - count_card.transfer(&vote.value); - - match count_card.parcels.last_mut() { - Some(parcel) => { - if parcel.source_order == state.num_elected + state.num_excluded { - parcel.votes.push(vote); - } else { - let parcel = Parcel { - votes: vec![vote], - source_order: state.num_elected + state.num_excluded, - }; - count_card.parcels.push(parcel); - } - } - None => { - let parcel = Parcel { - votes: vec![vote], - source_order: state.num_elected + state.num_excluded, - }; - count_card.parcels.push(parcel); - } - } - - if opts.sample_per_ballot { - super::elect_hopefuls(state, opts)?; - } - } else { - // Exhausted - state.exhausted.transfer(&vote.value); - - match state.exhausted.parcels.last_mut() { - Some(parcel) => { - if parcel.source_order == state.num_elected + state.num_excluded { - parcel.votes.push(vote); - } else { - let parcel = Parcel { - votes: vec![vote], - source_order: state.num_elected + state.num_excluded, - }; - state.exhausted.parcels.push(parcel); - } - } - None => { - let parcel = Parcel { - votes: vec![vote], - source_order: state.num_elected + state.num_excluded, - }; - state.exhausted.parcels.push(parcel); - } - } + for vote in votes { + transfer_ballot(state, opts, excluded_candidate, vote, false)?; + if state.num_elected == state.election.seats { + return Ok(()); } } } diff --git a/src/stv/wasm.rs b/src/stv/wasm.rs index 6a6b1fe..ca3eb81 100644 --- a/src/stv/wasm.rs +++ b/src/stv/wasm.rs @@ -96,9 +96,9 @@ macro_rules! impl_type { /// Wrapper for [stv::count_init] #[wasm_bindgen] #[allow(non_snake_case)] - pub fn [](state: &mut [], opts: &STVOptions) -> bool { + pub fn [](state: &mut [], opts: &STVOptions) { match stv::count_init(&mut state.0, opts.as_static()) { - Ok(v) => v, + Ok(_) => (), Err(err) => wasm_error!("Error", err), } } @@ -369,7 +369,7 @@ fn update_results_table(stage_num: usize, state: &CountState, opts } CandidateState::Excluded => { result.push(&format!(r#"{}"#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into()); - if count_card.parcels.iter().all(|p| p.votes.is_empty()) { + if count_card.votes.is_zero() && count_card.parcels.iter().all(|p| p.votes.is_empty()) { result.push(&format!(r#"Ex"#, tdclasses2).into()); } else { result.push(&format!(r#"{}"#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into()); diff --git a/tests/data/CambCC2003.csv b/tests/data/CambCC2003.csv index daa1406..08d89ff 100644 --- a/tests/data/CambCC2003.csv +++ b/tests/data/CambCC2003.csv @@ -2,7 +2,7 @@ Stage:,1,,2,,3,,4,,5,,6,,7,,8,,9,,10,,11,,12,,13, Comment:,First preferences,,"Surplus of Galluccio, Anthony D.",,,,"Exclusion of Dixon, Vincent Lawrence",,"Exclusion of Hall, Robert L., Sr.",,"Exclusion of LaTremouille, Robert J.",,"Exclusion of Taymorberry, Laurie",,"Exclusion of King, Ethridge A., Jr.",,"Exclusion of Smith, Aimee Louise",,"Exclusion of Bellew, Carole K.",,"Exclusion of Kelley, Craig A.",,"Exclusion of Pitkin, John",,"Exclusion of DeBergalis, Matt S.", "Bellew, Carole K.",735,H,759,H,761,H,762,H,767,H,771,H,788,H,811,H,861,H,0,EX,0,EX,0,EX,0,EX "Davis, Henrietta",1846,H,1896,H,1901,H,1904,H,1909,H,1920,H,1950,H,1977,H,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL -"DeBergalis, Matt S.",1206,H,1216,H,1220,H,1227,H,1229,H,1233,H,1242,H,1283,H,1342,H,1441,H,1494,H,1640,H,0,EX +"DeBergalis, Matt S.",1206,H,1216,H,1220,H,1227,H,1229,H,1233,H,1242,H,1283,H,1342,H,1441,H,1494,H,1640,H,,EX "Decker, Marjorie C.",1378,H,1425,H,1428,H,1432,H,1437,H,1442,H,1463,H,1486,H,1528,H,1641,H,1787,H,2009,EL,2009,EL "Dixon, Vincent Lawrence",64,H,64,H,65,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX "Galluccio, Anthony D.",2994,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL @@ -29,4 +29,4 @@ Write-In 6,0,H,0,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX Write-In 7,0,H,0,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX Write-In 8,0,H,0,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX Write-In 9,0,H,0,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX -Exhausted,0,,0,,21,,23,,40,,59,,88,,148,,215,,326,,542,,872,,1999, +Exhausted,0,,0,,21,,23,,40,,59,,88,,148,,215,,326,,542,,872,,, diff --git a/tests/data/CambCC2003.ods b/tests/data/CambCC2003.ods index 5d614905f5e3615d2253a7161c34eec8e3c26f70..5703c3ee3b23cf3ab33fba896b5c943787df0bb4 100644 GIT binary patch delta 6132 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