Correctly compute vote required for election when using different quotas/quota criteria
This commit is contained in:
parent
b5ee76f159
commit
2ef7bf24f2
@ -165,7 +165,7 @@ pub struct CountState<'a, N: Number> {
|
||||
pub quota: Option<N>,
|
||||
/// Vote required for election
|
||||
///
|
||||
/// With a static quota, this is equal to the quota. With ERS97 rules, this may vary from the quota.
|
||||
/// Only used in ERS97/ERS76, or if early bulk election is enabled and there is 1 vacancy remaining.
|
||||
pub vote_required_election: Option<N>,
|
||||
|
||||
/// Number of candidates who have been declared elected
|
||||
|
@ -670,7 +670,8 @@ fn update_vre<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
|
||||
});
|
||||
log.push_str(format!("Total active vote is {:.dps$}, so the vote required for election is ", total_active_vote, dps=opts.pp_decimals).as_str());
|
||||
|
||||
let vote_req = total_to_quota(total_active_vote, state.election.seats - state.num_elected, opts); // FIXME: This is incorrect
|
||||
//let vote_req = total_to_quota(total_active_vote, state.election.seats - state.num_elected, opts);
|
||||
let vote_req = total_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
|
||||
@ -746,17 +747,14 @@ fn calculate_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
|
||||
// Early bulk election and one seat remains: VRE is majority of total active vote
|
||||
update_vre(state, opts);
|
||||
} else {
|
||||
// VRE is quota
|
||||
if state.vote_required_election.is_none() {
|
||||
state.vote_required_election = state.quota.clone();
|
||||
}
|
||||
// No use of VRE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if the given candidate meets the quota, according to [STVOptions::quota_criterion]
|
||||
fn meets_quota<N: Number>(quota: &N, count_card: &CountCard<N>, opts: &STVOptions) -> bool {
|
||||
/// 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 {
|
||||
match opts.quota_criterion {
|
||||
QuotaCriterion::GreaterOrEqual => {
|
||||
return count_card.votes >= *quota;
|
||||
@ -767,13 +765,26 @@ fn meets_quota<N: Number>(quota: &N, count_card: &CountCard<N>, opts: &STVOption
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
if let Some(vre) = &state.vote_required_election {
|
||||
if opts.quota_mode == QuotaMode::ERS97 || opts.quota_mode == QuotaMode::ERS76 {
|
||||
// VRE is set because ERS97/ERS76 rules
|
||||
return cmp_quota_criterion(vre, count_card, opts);
|
||||
} else {
|
||||
// VRE is set because early bulk election is enabled and 1 vacancy remains
|
||||
return count_card.votes > *vre;
|
||||
}
|
||||
} else {
|
||||
return cmp_quota_criterion(state.quota.as_ref().unwrap(), count_card, opts);
|
||||
}
|
||||
}
|
||||
|
||||
/// Declare elected all candidates meeting the quota
|
||||
fn elect_meeting_quota<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions) -> Result<bool, STVError> {
|
||||
let vote_req = state.vote_required_election.as_ref().unwrap().clone(); // Have to do this or else the borrow checker gets confused
|
||||
|
||||
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]))
|
||||
.filter(|(_, cc)| { (cc.state == CandidateState::Hopeful || cc.state == CandidateState::Guarded) && meets_quota(&vote_req, cc, opts) })
|
||||
.filter(|(_, cc)| { (cc.state == CandidateState::Hopeful || cc.state == CandidateState::Guarded) && meets_vre(state, cc, opts) })
|
||||
.collect();
|
||||
|
||||
// Sort by votes
|
||||
@ -795,7 +806,7 @@ fn elect_meeting_quota<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVO
|
||||
count_card.state = CandidateState::Elected;
|
||||
state.num_elected += 1;
|
||||
count_card.order_elected = state.num_elected as isize;
|
||||
if meets_quota(state.quota.as_ref().unwrap(), count_card, opts) {
|
||||
if cmp_quota_criterion(state.quota.as_ref().unwrap(), count_card, opts) {
|
||||
// Elected with a quota
|
||||
state.logger.log_smart(
|
||||
"{} meets the quota and is elected.",
|
||||
@ -815,7 +826,7 @@ fn elect_meeting_quota<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVO
|
||||
// Recheck as some candidates may have been doomed
|
||||
let mut cmq: Vec<(&Candidate, &CountCard<N>)> = state.election.candidates.iter() // Present in order in case of tie
|
||||
.map(|c| (c, &state.candidates[c]))
|
||||
.filter(|(_, cc)| { (cc.state == CandidateState::Hopeful || cc.state == CandidateState::Guarded) && meets_quota(&vote_req, cc, opts) })
|
||||
.filter(|(_, cc)| { (cc.state == CandidateState::Hopeful || cc.state == CandidateState::Guarded) && meets_vre(state, cc, opts) })
|
||||
.collect();
|
||||
cmq.sort_unstable_by(|a, b| a.1.votes.cmp(&b.1.votes));
|
||||
cands_meeting_quota = cmq.iter().map(|(c, _)| *c).collect();
|
||||
|
@ -287,7 +287,8 @@ fn describe_count<N: Number>(filename: String, election: &Election<N>, opts: &st
|
||||
|
||||
#[inline]
|
||||
fn should_show_vre(opts: &stv::STVOptions) -> bool {
|
||||
return opts.quota_mode == stv::QuotaMode::ERS97 || opts.quota_mode == stv::QuotaMode::ERS76 || opts.early_bulk_elect;
|
||||
//return opts.quota_mode == stv::QuotaMode::ERS97 || opts.quota_mode == stv::QuotaMode::ERS76 || opts.early_bulk_elect;
|
||||
return opts.quota_mode == stv::QuotaMode::ERS97 || opts.quota_mode == stv::QuotaMode::ERS76;
|
||||
}
|
||||
|
||||
/// Generate the first column of the HTML results table
|
||||
@ -360,7 +361,11 @@ fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>, opts
|
||||
|
||||
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(state.quota.as_ref().unwrap(), opts.pp_decimals)).into());
|
||||
if should_show_vre(opts) {
|
||||
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(state.vote_required_election.as_ref().unwrap(), opts.pp_decimals)).into());
|
||||
if let Some(vre) = &state.vote_required_election {
|
||||
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(vre, opts.pp_decimals)).into());
|
||||
} else {
|
||||
result.push(&format!(r#"<td class="{}count"></td>"#, tdclasses2).into());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -118,7 +118,8 @@ where
|
||||
&"lbf" => approx_eq(&state.loss_fraction.votes, &votes, cmp_dps, idx, "LBF"),
|
||||
&"nt" => approx_eq(&(&state.exhausted.votes + &state.loss_fraction.votes), &votes, cmp_dps, idx, "NTs"),
|
||||
&"quota" => approx_eq(state.quota.as_ref().unwrap(), &votes, cmp_dps, idx, "quota"),
|
||||
&"vre" => approx_eq(state.vote_required_election.as_ref().unwrap(), &votes, cmp_dps, idx, "VRE"),
|
||||
//&"vre" => approx_eq(state.vote_required_election.as_ref().unwrap(), &votes, cmp_dps, idx, "VRE"),
|
||||
&"vre" => approx_eq(state.vote_required_election.as_ref().unwrap(), &votes, Some(2), idx, "VRE"),
|
||||
_ => panic!("Unknown sum_rows"),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user