Prepare for dynamic quota: independent flag for completion of surplus transfers/exclusions

This commit is contained in:
RunasSudo 2021-08-08 19:11:15 +10:00
parent 0581571440
commit ee1008b509
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
5 changed files with 25 additions and 14 deletions

View File

@ -294,10 +294,11 @@ pub struct CountCard<'a, N> {
pub state: CandidateState, pub state: CandidateState,
/// Order of election or exclusion /// 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, 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 /// Net votes transferred to this candidate in this stage
pub transfers: N, pub transfers: N,
/// Votes of the candidate at the end of this stage /// Votes of the candidate at the end of this stage
@ -316,7 +317,7 @@ impl<'a, N: Number> CountCard<'a, N> {
return CountCard { return CountCard {
state: CandidateState::Hopeful, state: CandidateState::Hopeful,
order_elected: 0, order_elected: 0,
//orig_votes: N::new(), finalised: false,
transfers: N::new(), transfers: N::new(),
votes: N::new(), votes: N::new(),
parcels: Vec::new(), parcels: Vec::new(),

View File

@ -75,7 +75,7 @@ where
let has_surplus: Vec<&Candidate> = state.election.candidates.iter() // Present in order in case of tie let has_surplus: Vec<&Candidate> = state.election.candidates.iter() // Present in order in case of tie
.filter(|c| { .filter(|c| {
let cc = &state.candidates[c]; let cc = &state.candidates[c];
&cc.votes > quota && cc.parcels.iter().any(|p| !p.votes.is_empty()) &cc.votes > quota && !cc.finalised
}) })
.collect(); .collect();
@ -388,7 +388,7 @@ where
count_card.votes.assign(state.quota.as_ref().unwrap()); count_card.votes.assign(state.quota.as_ref().unwrap());
checksum -= surplus; checksum -= surplus;
count_card.parcels.clear(); // Mark surpluses as done count_card.finalised = true; // Mark surpluses as done
// Update loss by fraction // Update loss by fraction
state.loss_fraction.transfer(&-checksum); state.loss_fraction.transfer(&-checksum);
@ -426,7 +426,7 @@ where
for excluded_candidate in excluded_candidates.iter() { for excluded_candidate in excluded_candidates.iter() {
let count_card = state.candidates.get_mut(excluded_candidate).unwrap(); let count_card = state.candidates.get_mut(excluded_candidate).unwrap();
votes.append(&mut count_card.concat_parcels()); votes.append(&mut count_card.concat_parcels());
count_card.parcels.clear(); count_card.finalised = true;
// Update votes // Update votes
let votes_transferred = votes.iter().fold(N::new(), |acc, v| acc + &v.value); let votes_transferred = votes.iter().fold(N::new(), |acc, v| acc + &v.value);
@ -437,7 +437,7 @@ where
} }
ExclusionMethod::ByValue => { ExclusionMethod::ByValue => {
// Exclude by value // 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() { if excluded_with_votes.is_empty() {
votes_remain = false; votes_remain = false;
@ -487,7 +487,7 @@ where
} }
ExclusionMethod::BySource => { ExclusionMethod::BySource => {
// Exclude by source candidate // 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() { if excluded_with_votes.is_empty() {
votes_remain = false; votes_remain = false;
@ -617,6 +617,7 @@ where
checksum -= &count_card.votes; checksum -= &count_card.votes;
count_card.transfers -= &count_card.votes; count_card.transfers -= &count_card.votes;
count_card.votes = N::new(); count_card.votes = N::new();
count_card.finalised = true;
} }
if let ExclusionMethod::SingleStage = opts.exclusion { if let ExclusionMethod::SingleStage = opts.exclusion {
@ -665,6 +666,12 @@ where
CandidateState::Excluded => CandidateState::Excluded, CandidateState::Excluded => CandidateState::Excluded,
_ => CandidateState::Hopeful, _ => CandidateState::Hopeful,
}; };
if count_card.state == CandidateState::Excluded {
count_card.finalised = true;
} else {
count_card.finalised = false;
}
} }
state.exhausted.votes = N::new(); state.exhausted.votes = N::new();

View File

@ -389,6 +389,7 @@ where
count_card.state = CandidateState::Excluded; count_card.state = CandidateState::Excluded;
state.num_excluded += 1; state.num_excluded += 1;
count_card.order_elected = -(order_excluded as isize); count_card.order_elected = -(order_excluded as isize);
count_card.finalised = true;
} }
} }

View File

@ -783,7 +783,7 @@ fn update_vre<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
// Calculate total active vote // Calculate total active vote
let total_active_vote = state.candidates.values().fold(N::zero(), |acc, cc| { let total_active_vote = state.candidates.values().fold(N::zero(), |acc, cc| {
match cc.state { 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 } _ => { 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 // Add finally any votes awaiting transfer
total_trailing += state.candidates.values().fold(N::zero(), |acc, cc| { total_trailing += state.candidates.values().fold(N::zero(), |acc, cc| {
match cc.state { 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::Hopeful | CandidateState::Guarded | CandidateState::Withdrawn => { acc }
CandidateState::Excluded | CandidateState::Doomed => { acc + &cc.votes } 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)); hopefuls.sort_unstable_by(|a, b| a.1.votes.cmp(&b.1.votes));
let total_surpluses = state.candidates.iter() 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()); .fold(N::new(), |agg, (_, cc)| agg + &cc.votes - state.quota.as_ref().unwrap());
// Attempt to exclude as many candidates as possible // 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 // 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<N>)> = state.candidates.iter() let mut excluded_with_votes: Vec<(&&Candidate, &CountCard<N>)> = 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(); .collect();
if !excluded_with_votes.is_empty() { if !excluded_with_votes.is_empty() {

View File

@ -181,7 +181,7 @@ where
count_card.votes.assign(state.quota.as_ref().unwrap()); count_card.votes.assign(state.quota.as_ref().unwrap());
checksum -= surplus; checksum -= surplus;
count_card.parcels.clear(); // Mark surpluses as done count_card.finalised = true; // Mark surpluses as done
// Update loss by fraction // Update loss by fraction
state.loss_fraction.transfer(&-checksum); state.loss_fraction.transfer(&-checksum);
@ -386,7 +386,6 @@ where
for excluded_candidate in excluded_candidates.iter() { for excluded_candidate in excluded_candidates.iter() {
let count_card = state.candidates.get_mut(excluded_candidate).unwrap(); let count_card = state.candidates.get_mut(excluded_candidate).unwrap();
let votes = count_card.concat_parcels(); let votes = count_card.concat_parcels();
count_card.parcels.clear();
for vote in votes { for vote in votes {
transfer_ballot(state, opts, excluded_candidate, vote, false)?; transfer_ballot(state, opts, excluded_candidate, vote, false)?;
@ -394,6 +393,9 @@ where
return Ok(()); return Ok(());
} }
} }
let count_card = state.candidates.get_mut(excluded_candidate).unwrap();
count_card.finalised = true;
} }
return Ok(()); return Ok(());