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>,
|
pub quota: Option<N>,
|
||||||
/// Vote required for election
|
/// 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>,
|
pub vote_required_election: Option<N>,
|
||||||
|
|
||||||
/// Number of candidates who have been declared elected
|
/// 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());
|
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() {
|
if &vote_req < state.quota.as_ref().unwrap() {
|
||||||
// VRE is less than the quota
|
// 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
|
// Early bulk election and one seat remains: VRE is majority of total active vote
|
||||||
update_vre(state, opts);
|
update_vre(state, opts);
|
||||||
} else {
|
} else {
|
||||||
// VRE is quota
|
// No use of VRE
|
||||||
if state.vote_required_election.is_none() {
|
|
||||||
state.vote_required_election = state.quota.clone();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine if the given candidate meets the quota, according to [STVOptions::quota_criterion]
|
/// Compare the candidate's votes with the specified target according to [STVOptions::quota_criterion]
|
||||||
fn meets_quota<N: Number>(quota: &N, count_card: &CountCard<N>, opts: &STVOptions) -> bool {
|
fn cmp_quota_criterion<N: Number>(quota: &N, count_card: &CountCard<N>, opts: &STVOptions) -> bool {
|
||||||
match opts.quota_criterion {
|
match opts.quota_criterion {
|
||||||
QuotaCriterion::GreaterOrEqual => {
|
QuotaCriterion::GreaterOrEqual => {
|
||||||
return count_card.votes >= *quota;
|
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
|
/// Declare elected all candidates meeting the quota
|
||||||
fn elect_meeting_quota<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions) -> Result<bool, STVError> {
|
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
|
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]))
|
.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();
|
.collect();
|
||||||
|
|
||||||
// Sort by votes
|
// 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;
|
count_card.state = CandidateState::Elected;
|
||||||
state.num_elected += 1;
|
state.num_elected += 1;
|
||||||
count_card.order_elected = state.num_elected as isize;
|
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
|
// Elected with a quota
|
||||||
state.logger.log_smart(
|
state.logger.log_smart(
|
||||||
"{} meets the quota and is elected.",
|
"{} 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
|
// 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
|
let mut cmq: Vec<(&Candidate, &CountCard<N>)> = state.election.candidates.iter() // Present in order in case of tie
|
||||||
.map(|c| (c, &state.candidates[c]))
|
.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();
|
.collect();
|
||||||
cmq.sort_unstable_by(|a, b| a.1.votes.cmp(&b.1.votes));
|
cmq.sort_unstable_by(|a, b| a.1.votes.cmp(&b.1.votes));
|
||||||
cands_meeting_quota = cmq.iter().map(|(c, _)| *c).collect();
|
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]
|
#[inline]
|
||||||
fn should_show_vre(opts: &stv::STVOptions) -> bool {
|
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
|
/// 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());
|
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(state.quota.as_ref().unwrap(), opts.pp_decimals)).into());
|
||||||
if should_show_vre(opts) {
|
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;
|
return result;
|
||||||
|
|
|
@ -118,7 +118,8 @@ where
|
||||||
&"lbf" => approx_eq(&state.loss_fraction.votes, &votes, cmp_dps, idx, "LBF"),
|
&"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"),
|
&"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"),
|
&"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"),
|
_ => panic!("Unknown sum_rows"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue