Prepare for dynamic quota: independent flag for completion of surplus transfers/exclusions
This commit is contained in:
parent
0581571440
commit
ee1008b509
|
@ -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(),
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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(());
|
||||||
|
|
Loading…
Reference in New Issue