Further aggressive early bulk election
This commit is contained in:
parent
ed4a86e699
commit
b5ee76f159
@ -154,6 +154,7 @@ When early bulk election is enabled (default), the count terminates as soon as t
|
|||||||
|
|
||||||
* At the beginning of each stage, if the number of not-excluded candidates exactly equals the number of vacancies to fill, all remaining candidates are declared elected in a single stage. This is typical of most STV rules.
|
* At the beginning of each stage, if the number of not-excluded candidates exactly equals the number of vacancies to fill, all remaining candidates are declared elected in a single stage. This is typical of most STV rules.
|
||||||
* If a proposed exclusion would cause the number of not-excluded candidates to exactly equal the number of vacancies, all remaining candidates are declared elected without transfers arising from the proposed exclusion being performed.
|
* If a proposed exclusion would cause the number of not-excluded candidates to exactly equal the number of vacancies, all remaining candidates are declared elected without transfers arising from the proposed exclusion being performed.
|
||||||
|
* At the end of any stage, if only 1 vacancy remains and one continuing candidate has more votes than all other continuing candidates (plus votes awaiting transfer), that candidate is immediately declared elected.
|
||||||
|
|
||||||
If an early bulk election is performed, further surplus distributions are not performed, and outstanding exclusions, if any, are not completed, even if they could change the order of election.
|
If an early bulk election is performed, further surplus distributions are not performed, and outstanding exclusions, if any, are not completed, even if they could change the order of election.
|
||||||
|
|
||||||
|
@ -655,6 +655,42 @@ fn total_to_quota<N: Number>(mut total: N, seats: usize, opts: &STVOptions) -> N
|
|||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update vote required for election according to ERS97 rules
|
||||||
|
///
|
||||||
|
/// This is also used to compute the vote required for election when early bulk election is enabled and 1 vacancy remains.
|
||||||
|
fn update_vre<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
|
||||||
|
let mut log = String::new();
|
||||||
|
|
||||||
|
// 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 } }
|
||||||
|
_ => { acc + &cc.votes }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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
|
||||||
|
|
||||||
|
if &vote_req < state.quota.as_ref().unwrap() {
|
||||||
|
// VRE is less than the quota
|
||||||
|
if let Some(v) = &state.vote_required_election {
|
||||||
|
if &vote_req != v {
|
||||||
|
log.push_str(format!("{:.dps$}.", vote_req, dps=opts.pp_decimals).as_str());
|
||||||
|
state.vote_required_election = Some(vote_req);
|
||||||
|
state.logger.log_literal(log);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.push_str(format!("{:.dps$}.", vote_req, dps=opts.pp_decimals).as_str());
|
||||||
|
state.vote_required_election = Some(vote_req);
|
||||||
|
state.logger.log_literal(log);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// VRE is not less than the quota, so use the quota
|
||||||
|
state.vote_required_election = state.quota.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Calculate the quota according to [STVOptions::quota]
|
/// Calculate the quota according to [STVOptions::quota]
|
||||||
fn calculate_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
|
fn calculate_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
|
||||||
// Calculate quota
|
// Calculate quota
|
||||||
@ -676,9 +712,9 @@ fn calculate_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
|
|||||||
// ERS97/ERS76 rules
|
// ERS97/ERS76 rules
|
||||||
|
|
||||||
// -------------------------
|
// -------------------------
|
||||||
// Reduce quota if allowable
|
// (ERS97) Reduce quota if allowable
|
||||||
|
|
||||||
if state.num_elected == 0 {
|
if opts.quota_mode == QuotaMode::ERS97 && state.num_elected == 0 {
|
||||||
let mut log = String::new();
|
let mut log = String::new();
|
||||||
|
|
||||||
// Calculate the total vote
|
// Calculate the total vote
|
||||||
@ -698,41 +734,23 @@ fn calculate_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
|
|||||||
// Calculate vote required for election
|
// Calculate vote required for election
|
||||||
|
|
||||||
if state.num_elected < state.election.seats {
|
if state.num_elected < state.election.seats {
|
||||||
let mut log = String::new();
|
update_vre(state, opts);
|
||||||
|
|
||||||
// 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 } }
|
|
||||||
_ => { acc + &cc.votes }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
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);
|
|
||||||
|
|
||||||
if &vote_req < state.quota.as_ref().unwrap() {
|
|
||||||
// VRE is less than the quota
|
|
||||||
if let Some(v) = &state.vote_required_election {
|
|
||||||
if &vote_req != v {
|
|
||||||
log.push_str(format!("{:.dps$}.", vote_req, dps=opts.pp_decimals).as_str());
|
|
||||||
state.vote_required_election = Some(vote_req);
|
|
||||||
state.logger.log_literal(log);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.push_str(format!("{:.dps$}.", vote_req, dps=opts.pp_decimals).as_str());
|
|
||||||
state.vote_required_election = Some(vote_req);
|
|
||||||
state.logger.log_literal(log);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// VRE is not less than the quota, so use the quota
|
|
||||||
state.vote_required_election = state.quota.clone();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No ERS97/ERS76 rules
|
// No ERS97/ERS76 rules
|
||||||
if state.vote_required_election.is_none() || opts.surplus == SurplusMethod::Meek {
|
if opts.surplus == SurplusMethod::Meek {
|
||||||
|
// Update quota and so VRE every stage
|
||||||
state.vote_required_election = state.quota.clone();
|
state.vote_required_election = state.quota.clone();
|
||||||
|
} else {
|
||||||
|
if opts.early_bulk_elect && state.num_elected + 1 == state.election.seats {
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -777,11 +795,21 @@ 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) {
|
||||||
|
// Elected with a quota
|
||||||
state.logger.log_smart(
|
state.logger.log_smart(
|
||||||
"{} meets the quota and is elected.",
|
"{} meets the quota and is elected.",
|
||||||
"{} meet the quota and are elected.",
|
"{} meet the quota and are elected.",
|
||||||
vec![&candidate.name]
|
vec![&candidate.name]
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
// Elected with vote required
|
||||||
|
state.logger.log_smart(
|
||||||
|
"{} meets the vote required and is elected.",
|
||||||
|
"{} meet the vote required and are elected.",
|
||||||
|
vec![&candidate.name]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if constraints::update_constraints(state, opts) {
|
if constraints::update_constraints(state, opts) {
|
||||||
// Recheck as some candidates may have been doomed
|
// Recheck as some candidates may have been doomed
|
||||||
|
@ -285,6 +285,11 @@ fn describe_count<N: Number>(filename: String, election: &Election<N>, opts: &st
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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;
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate the first column of the HTML results table
|
/// Generate the first column of the HTML results table
|
||||||
fn init_results_table<N: Number>(election: &Election<N>, opts: &stv::STVOptions) -> String {
|
fn init_results_table<N: Number>(election: &Election<N>, opts: &stv::STVOptions) -> String {
|
||||||
let mut result = String::from(r#"<tr class="stage-no"><td rowspan="3"></td></tr><tr class="stage-kind"></tr><tr class="stage-comment"></tr>"#);
|
let mut result = String::from(r#"<tr class="stage-no"><td rowspan="3"></td></tr><tr class="stage-kind"></tr><tr class="stage-comment"></tr>"#);
|
||||||
@ -292,7 +297,7 @@ fn init_results_table<N: Number>(election: &Election<N>, opts: &stv::STVOptions)
|
|||||||
result.push_str(&format!(r#"<tr class="candidate transfers"><td rowspan="2">{}</td></tr><tr class="candidate votes"></tr>"#, candidate.name));
|
result.push_str(&format!(r#"<tr class="candidate transfers"><td rowspan="2">{}</td></tr><tr class="candidate votes"></tr>"#, candidate.name));
|
||||||
}
|
}
|
||||||
result.push_str(r#"<tr class="info transfers"><td rowspan="2">Exhausted</td></tr><tr class="info votes"></tr><tr class="info transfers"><td rowspan="2">Loss by fraction</td></tr><tr class="info votes"></tr><tr class="info transfers"><td>Total</td></tr><tr class="info transfers"><td>Quota</td></tr>"#);
|
result.push_str(r#"<tr class="info transfers"><td rowspan="2">Exhausted</td></tr><tr class="info votes"></tr><tr class="info transfers"><td rowspan="2">Loss by fraction</td></tr><tr class="info votes"></tr><tr class="info transfers"><td>Total</td></tr><tr class="info transfers"><td>Quota</td></tr>"#);
|
||||||
if opts.quota_mode == stv::QuotaMode::ERS97 || opts.quota_mode == stv::QuotaMode::ERS76 {
|
if should_show_vre(opts) {
|
||||||
result.push_str(r#"<tr class="info transfers"><td>Vote required for election</td></tr>"#);
|
result.push_str(r#"<tr class="info transfers"><td>Vote required for election</td></tr>"#);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -354,7 +359,7 @@ fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>, opts
|
|||||||
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&total_vote, opts.pp_decimals)).into());
|
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&total_vote, opts.pp_decimals)).into());
|
||||||
|
|
||||||
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 opts.quota_mode == stv::QuotaMode::ERS97 || opts.quota_mode == stv::QuotaMode::ERS76 {
|
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());
|
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(state.vote_required_election.as_ref().unwrap(), opts.pp_decimals)).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user