diff --git a/src/election.rs b/src/election.rs index 1d384cf..830835d 100644 --- a/src/election.rs +++ b/src/election.rs @@ -294,10 +294,11 @@ pub struct CountCard<'a, N> { pub state: CandidateState, /// Order of election or exclusion /// - /// Positive integers represent order of election; negative integers represent order of exclusion + /// Positive integers represent order of election; negative integers represent order of exclusion. pub order_elected: isize, + /// Whether distribution of this candidate's surpluses/transfer of excluded candidate's votes is complete + pub finalised: bool, - //pub orig_votes: N, /// Net votes transferred to this candidate in this stage pub transfers: N, /// Votes of the candidate at the end of this stage @@ -316,7 +317,7 @@ impl<'a, N: Number> CountCard<'a, N> { return CountCard { state: CandidateState::Hopeful, order_elected: 0, - //orig_votes: N::new(), + finalised: false, transfers: N::new(), votes: N::new(), parcels: Vec::new(), diff --git a/src/stv/gregory.rs b/src/stv/gregory.rs index 13eb98c..7f664d2 100644 --- a/src/stv/gregory.rs +++ b/src/stv/gregory.rs @@ -75,7 +75,7 @@ where let has_surplus: Vec<&Candidate> = state.election.candidates.iter() // Present in order in case of tie .filter(|c| { let cc = &state.candidates[c]; - &cc.votes > quota && cc.parcels.iter().any(|p| !p.votes.is_empty()) + &cc.votes > quota && !cc.finalised }) .collect(); @@ -388,7 +388,7 @@ where count_card.votes.assign(state.quota.as_ref().unwrap()); checksum -= surplus; - count_card.parcels.clear(); // Mark surpluses as done + count_card.finalised = true; // Mark surpluses as done // Update loss by fraction state.loss_fraction.transfer(&-checksum); @@ -426,7 +426,7 @@ where 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(); + count_card.finalised = true; // Update votes let votes_transferred = votes.iter().fold(N::new(), |acc, v| acc + &v.value); @@ -437,7 +437,7 @@ where } ExclusionMethod::ByValue => { // Exclude by value - let excluded_with_votes: Vec<&&Candidate> = excluded_candidates.iter().filter(|c| !state.candidates[*c].parcels.is_empty()).collect(); + let excluded_with_votes: Vec<&&Candidate> = excluded_candidates.iter().filter(|c| !state.candidates[*c].finalised).collect(); if excluded_with_votes.is_empty() { votes_remain = false; @@ -487,7 +487,7 @@ where } ExclusionMethod::BySource => { // Exclude by source candidate - let excluded_with_votes: Vec<&&Candidate> = excluded_candidates.iter().filter(|c| !state.candidates[*c].parcels.is_empty()).collect(); + let excluded_with_votes: Vec<&&Candidate> = excluded_candidates.iter().filter(|c| !state.candidates[*c].finalised).collect(); if excluded_with_votes.is_empty() { votes_remain = false; @@ -617,6 +617,7 @@ where checksum -= &count_card.votes; count_card.transfers -= &count_card.votes; count_card.votes = N::new(); + count_card.finalised = true; } if let ExclusionMethod::SingleStage = opts.exclusion { @@ -665,6 +666,12 @@ where CandidateState::Excluded => CandidateState::Excluded, _ => CandidateState::Hopeful, }; + + if count_card.state == CandidateState::Excluded { + count_card.finalised = true; + } else { + count_card.finalised = false; + } } state.exhausted.votes = N::new(); diff --git a/src/stv/meek.rs b/src/stv/meek.rs index 68f36af..1391ae3 100644 --- a/src/stv/meek.rs +++ b/src/stv/meek.rs @@ -389,6 +389,7 @@ where count_card.state = CandidateState::Excluded; state.num_excluded += 1; count_card.order_elected = -(order_excluded as isize); + count_card.finalised = true; } } diff --git a/src/stv/mod.rs b/src/stv/mod.rs index 27a2ef7..b19fbbd 100644 --- a/src/stv/mod.rs +++ b/src/stv/mod.rs @@ -783,7 +783,7 @@ fn update_vre(state: &mut CountState, opts: &STVOptions) { // Calculate total active vote let total_active_vote = state.candidates.values().fold(N::zero(), |acc, cc| { match cc.state { - CandidateState::Elected => { if &cc.votes > state.quota.as_ref().unwrap() { acc + &cc.votes - state.quota.as_ref().unwrap() } else { acc } } + CandidateState::Elected => { if !cc.finalised && &cc.votes > state.quota.as_ref().unwrap() { acc + &cc.votes - state.quota.as_ref().unwrap() } else { acc } } _ => { acc + &cc.votes } } }); @@ -923,7 +923,7 @@ fn elect_sure_winners<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOp // Add finally any votes awaiting transfer total_trailing += state.candidates.values().fold(N::zero(), |acc, cc| { match cc.state { - CandidateState::Elected => { if &cc.votes > state.quota.as_ref().unwrap() { acc + &cc.votes - state.quota.as_ref().unwrap() } else { acc } } + CandidateState::Elected => { if !cc.finalised && &cc.votes > state.quota.as_ref().unwrap() { acc + &cc.votes - state.quota.as_ref().unwrap() } else { acc } } CandidateState::Hopeful | CandidateState::Guarded | CandidateState::Withdrawn => { acc } CandidateState::Excluded | CandidateState::Doomed => { acc + &cc.votes } } @@ -1275,7 +1275,7 @@ fn hopefuls_to_bulk_exclude<'a, N: Number>(state: &CountState<'a, N>, _opts: &ST hopefuls.sort_unstable_by(|a, b| a.1.votes.cmp(&b.1.votes)); let total_surpluses = state.candidates.iter() - .filter(|(_, cc)| &cc.votes > state.quota.as_ref().unwrap()) + .filter(|(_, cc)| !cc.finalised && &cc.votes > state.quota.as_ref().unwrap()) .fold(N::new(), |agg, (_, cc)| agg + &cc.votes - state.quota.as_ref().unwrap()); // Attempt to exclude as many candidates as possible @@ -1387,7 +1387,7 @@ where { // Cannot filter by raw vote count, as candidates may have 0.00 votes but still have recorded ballot papers let mut excluded_with_votes: Vec<(&&Candidate, &CountCard)> = state.candidates.iter() - .filter(|(_, cc)| cc.state == CandidateState::Excluded && cc.parcels.iter().any(|p| !p.votes.is_empty())) + .filter(|(_, cc)| cc.state == CandidateState::Excluded && !cc.finalised) .collect(); if !excluded_with_votes.is_empty() { diff --git a/src/stv/sample.rs b/src/stv/sample.rs index 38d360c..c831639 100644 --- a/src/stv/sample.rs +++ b/src/stv/sample.rs @@ -181,7 +181,7 @@ where count_card.votes.assign(state.quota.as_ref().unwrap()); checksum -= surplus; - count_card.parcels.clear(); // Mark surpluses as done + count_card.finalised = true; // Mark surpluses as done // Update loss by fraction state.loss_fraction.transfer(&-checksum); @@ -386,7 +386,6 @@ where 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(); for vote in votes { transfer_ballot(state, opts, excluded_candidate, vote, false)?; @@ -394,6 +393,9 @@ where return Ok(()); } } + + let count_card = state.candidates.get_mut(excluded_candidate).unwrap(); + count_card.finalised = true; } return Ok(());