Implement ERS76 rules

This commit is contained in:
RunasSudo 2021-07-18 20:01:35 +10:00
parent bc8ed9a7e0
commit d144ab0cb4
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
6 changed files with 41 additions and 10 deletions

View File

@ -14,7 +14,8 @@ The preset dropdown allows you to choose from a hardcoded list of preloaded STV
* [*Wright STV*](https://www.aph.gov.au/Parliamentary_Business/Committees/House_of_Representatives_Committees?url=em/elect07/subs/sub051.1.pdf): Rules proposed by Anthony van der Craats designed for computer counting, involving reset and re-iteration of the count after each candidate exclusion. Validated against the [EVE Online reference implementation](https://github.com/ccpgames/ccp-wright-stv) for the [CSM 15 election](https://www.eveonline.com/news/view/meet-the-new-council).
* [*PRSA 1977*](https://www.prsa.org.au/rule1977.htm): Simple rules designed for hand counting, using the exclusive Gregory method, with counting automatically performed in thousandths of a vote. Validated against [example 1](https://www.prsa.org.au/example1.pdf) of the PRSA's [*Proportional Representation Manual*](https://www.prsa.org.au/publicat.htm#p2).
* [*ERS97*](https://www.electoral-reform.org.uk/latest-news-and-research/publications/how-to-conduct-an-election-by-the-single-transferable-vote-3rd-edition/): More complex rules designed for hand counting, using the exclusive Gregory method. Validated against the ERS97 model election.
* *ERS73*: Former rules from the 1973 1st edition. The quota is calculated always to 2 decimal places – for full ERS73 compliance, set *Round quota to 0 d.p.* when the quota is 100 or more.
* *ERS76*: Former rules from the 1976 2nd edition. The quota is always calculated to 2 decimal places – for full ERS76 compliance, set *Round quota to 0 d.p.* when the quota is more than 100.
* *ERS73*: Former rules from the 1973 1st edition. The quota is always calculated to 2 decimal places – for full ERS73 compliance, set *Round quota to 0 d.p.* when the quota is 100 or more.
This functionality is not available on the command line.

View File

@ -44,6 +44,7 @@
<option value="wright">Wright STV</option>
<option value="prsa77">PRSA 1977</option>
<option value="ers97">ERS97</option>
<option value="ers76">ERS76</option>
<option value="ers73">ERS73</option>
</select>
</label>
@ -79,6 +80,7 @@
<option value="static" selected>Static quota</option>
<!--<option value="progressive">Progressive quota</option>-->
<option value="ers97">Static with ERS97 rules</option>
<option value="ers76">Static with ERS76 rules</option>
</select>
</label>
</div>

View File

@ -548,6 +548,31 @@ function changePreset() {
document.getElementById('selPapers').value = 'transferable';
document.getElementById('selExclusion').value = 'by_value';
document.getElementById('selTies').value = 'forwards,random';
} else if (document.getElementById('selPreset').value === 'ers76') {
document.getElementById('selQuotaCriterion').value = 'geq';
document.getElementById('selQuota').value = 'droop_exact';
document.getElementById('selQuotaMode').value = 'ers76';
document.getElementById('chkBulkElection').checked = true;
document.getElementById('chkBulkExclusion').checked = true;
document.getElementById('chkDeferSurpluses').checked = true;
document.getElementById('selNumbers').value = 'fixed';
document.getElementById('txtDP').value = '5';
document.getElementById('txtPPDP').value = '2';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '2';
document.getElementById('chkRoundVotes').checked = true;
document.getElementById('txtRoundVotes').value = '2';
document.getElementById('chkRoundTVs').checked = true;
document.getElementById('txtRoundTVs').value = '2';
document.getElementById('chkRoundWeights').checked = true;
document.getElementById('txtRoundWeights').value = '2';
document.getElementById('selSumTransfers').value = 'single_step';
document.getElementById('selSurplus').value = 'by_size';
document.getElementById('selTransfers').value = 'eg';
document.getElementById('selPapers').value = 'transferable';
document.getElementById('selExclusion').value = 'by_value';
document.getElementById('selTies').value = 'forwards,random';
} else if (document.getElementById('selPreset').value === 'ers73') {
document.getElementById('selQuotaCriterion').value = 'geq';
document.getElementById('selQuota').value = 'droop_exact';

View File

@ -104,7 +104,7 @@ struct STV {
quota_criterion: String,
/// Whether to apply a form of progressive quota
#[clap(help_heading=Some("QUOTA"), long, possible_values=&["static", "ers97"], default_value="static", value_name="mode")]
#[clap(help_heading=Some("QUOTA"), long, possible_values=&["static", "ers97", "ers76"], default_value="static", value_name="mode")]
quota_mode: String,
// ------------------

View File

@ -144,6 +144,7 @@ impl STVOptions {
quota_mode: match quota_mode {
"static" => QuotaMode::Static,
"ers97" => QuotaMode::ERS97,
"ers76" => QuotaMode::ERS76,
_ => panic!("Invalid --quota-mode"),
},
ties: ties.into_iter().map(|t| match t.as_str() {
@ -315,6 +316,8 @@ pub enum QuotaMode {
Static,
/// Static quota with ERS97 rules
ERS97,
/// Static quota with ERS76 rules
ERS76,
}
impl QuotaMode {
@ -323,6 +326,7 @@ impl QuotaMode {
match self {
QuotaMode::Static => "--quota-mode static",
QuotaMode::ERS97 => "--quota-mode ers97",
QuotaMode::ERS76 => "--quota-mode ers76",
}.to_string()
}
}
@ -664,8 +668,8 @@ fn calculate_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
state.logger.log_literal(log);
}
if let QuotaMode::ERS97 = opts.quota_mode {
// ERS97 rules
if opts.quota_mode == QuotaMode::ERS97 || opts.quota_mode == QuotaMode::ERS76 {
// ERS97/ERS76 rules
// -------------------------
// Reduce quota if allowable
@ -722,7 +726,7 @@ fn calculate_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
}
}
} else {
// No ERS97 rules
// No ERS97/ERS76 rules
if state.vote_required_election.is_none() || opts.surplus == SurplusMethod::Meek {
state.vote_required_election = state.quota.clone();
}
@ -789,10 +793,9 @@ fn elect_meeting_quota<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVO
if opts.quota_mode == QuotaMode::ERS97 {
// Vote required for election may have changed
// This is not performed in ERS76 - TODO: Check this
calculate_quota(state, opts);
}
if opts.quota_mode == QuotaMode::ERS97 {
// Repeat in case vote required for election has changed
match elect_meeting_quota(state, opts) {
Ok(_) => {}

View File

@ -292,7 +292,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(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 {
if opts.quota_mode == stv::QuotaMode::ERS97 || opts.quota_mode == stv::QuotaMode::ERS76 {
result.push_str(r#"<tr class="info transfers"><td>Vote required for election</td></tr>"#);
}
return result;
@ -354,7 +354,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(state.quota.as_ref().unwrap(), opts.pp_decimals)).into());
if opts.quota_mode == stv::QuotaMode::ERS97 {
if opts.quota_mode == stv::QuotaMode::ERS97 || opts.quota_mode == stv::QuotaMode::ERS76 {
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(state.vote_required_election.as_ref().unwrap(), opts.pp_decimals)).into());
}