From 9f1476da637c74fa65633a1d0c2eb6b1be06fcab Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Mon, 9 Aug 2021 19:50:07 +1000 Subject: [PATCH] Complete ERS76 implementation and add test case --- docs/options.md | 4 +- docs/validation.md | 1 + src/stv/mod.rs | 17 +++++++-- tests/data/.~lock.ers76.ods# | 1 + tests/data/ers76.blt | 71 +++++++++++++++++++++++++++++++++++ tests/data/ers76.csv | 15 ++++++++ tests/data/ers76.ods | Bin 0 -> 14925 bytes tests/{ers97.rs => ers.rs} | 21 +++++++++++ 8 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 tests/data/.~lock.ers76.ods# create mode 100644 tests/data/ers76.blt create mode 100644 tests/data/ers76.csv create mode 100644 tests/data/ers76.ods rename tests/{ers97.rs => ers.rs} (76%) diff --git a/docs/options.md b/docs/options.md index 3c81fad..fb0bce8 100644 --- a/docs/options.md +++ b/docs/options.md @@ -20,7 +20,7 @@ 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. | | ✓ | | [PRSA 1977](https://www.prsa.org.au/rule1977.htm) | Simple rules designed for hand counting, using the exclusive Gregory method, with counting performed in thousandths of a vote. | | ✓ | | [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. | | ✓ | -| • ERS76 | Former rules from the 1976 2nd edition. | [E6] | | +| • ERS76 | Former rules from the 1976 2nd edition. | [E6] | ✓ | | • ERS73 | Former rules from the 1973 1st edition. | [E6] | | | Church of England | Rules from the Church of England [*Single Transferable Vote Rules 2020*](https://www.churchofengland.org/sites/default/files/2020-02/STV%20Rules%202020%20-%20final.pdf), similar to ERS73. | | @@ -31,7 +31,7 @@ Exceptions: * [E3] A tie between 2 candidates for the final vacancy will be broken backwards then at random, rather than the method described in the legislation. * [E4] Bulk exclusion is not performed. See the section on *Bulk exclusion* for further discussion. * [E5] The ‘mathematically eliminated by the sum of all ranked-choice votes comparison’ is not implemented. -* [E6] The quota is always calculated to 2 decimal places. For full ERS76 (ERS73) compliance, set *Round quota to 0 d.p.* when the quota is more than 100 (100 or more). +* [E6] By default, the quota is always calculated to 2 decimal places. For full ERS76 (ERS73) compliance, set *Round quota to 0 d.p.* when the quota is more than 100 (100 or more). For details of validation, see [validation.md](validation.md). diff --git a/docs/validation.md b/docs/validation.md index 94e1baf..10c3832 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -18,6 +18,7 @@ STV-counting software is frequently validated empirically by comparing the resul | PRSA 1977 | [*Proportional Representation Manual*](https://www.prsa.org.au/publicat.htm#p2) [example 1](https://www.prsa.org.au/utopiatc.pdf) | [Model result](https://www.prsa.org.au/example1.pdf) (official) | ✓ | | ERS97 | [Reverse engineered ballot papers for the ERS97 model election](https://yingtongli.me/blog/2021/01/04/ers97.html) | [Model result](https://www.electoral-reform.org.uk/latest-news-and-research/publications/how-to-conduct-an-election-by-the-single-transferable-vote-3rd-edition/#sub-section-24) (official) | ✓ | | ERS97 | [Joe Otten/eSTV ballot papers for the ERS97 model election](https://web.archive.org/web/20020606014623/http://estv.otten.co.uk/) | [Model result](https://www.electoral-reform.org.uk/latest-news-and-research/publications/how-to-conduct-an-election-by-the-single-transferable-vote-3rd-edition/#sub-section-24) (official) | ✓ | +| ERS76 | Ballot papers adapted from Joe Otten/eSTV ERS97 papers | Model result (official) | ✓ | # References diff --git a/src/stv/mod.rs b/src/stv/mod.rs index 83298cc..60ddf91 100644 --- a/src/stv/mod.rs +++ b/src/stv/mod.rs @@ -785,8 +785,14 @@ fn total_to_quota(mut total: N, seats: usize, opts: &STVOptions) -> N return total; } -/// Update vote required for election according to ERS97 rules +/// Update vote required for election according to ERS97/ERS76 rules fn update_vre(state: &mut CountState, opts: &STVOptions) { + if opts.quota_mode == QuotaMode::ERS76 && state.num_excluded == 0 && (state.num_elected == 0 || state.candidates.values().all(|cc| !cc.finalised)) { + // ERS76 rules: Do not update VRE until a surplus is distributed or candidate is excluded + state.vote_required_election = state.quota.clone(); + return; + } + let mut log = String::new(); // Calculate active vote @@ -1023,8 +1029,11 @@ fn elect_hopefuls<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOption count_card.state = CandidateState::Elected; state.num_elected += 1; count_card.order_elected = state.num_elected as isize; + + let elected_on_quota; if cmp_quota_criterion(state.quota.as_ref().unwrap(), count_card, opts) { // Elected with a quota + elected_on_quota = true; state.logger.log_smart( "{} meets the quota and is elected.", "{} meet the quota and are elected.", @@ -1032,6 +1041,7 @@ fn elect_hopefuls<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOption ); } else { // Elected with vote required + elected_on_quota = false; state.logger.log_smart( "{} meets the vote required and is elected.", "{} meet the vote required and are elected.", @@ -1051,9 +1061,10 @@ fn elect_hopefuls<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOption cands_meeting_quota.remove(cands_meeting_quota.iter().position(|c| *c == candidate).unwrap()); } - if opts.quota_mode == QuotaMode::ERS97 { + if opts.quota_mode == QuotaMode::ERS97 || (opts.quota_mode == QuotaMode::ERS76 && elected_on_quota) { // Vote required for election may have changed - // This is not performed in ERS76 - TODO: Check this + // ERS97: Check this after every elected candidate (cf. model election) + // ERS76: Check this after every candidate elected on a quota, but all at once for candidates elected on VRE (cf. model election) calculate_quota(state, opts); // Repeat in case vote required for election has changed diff --git a/tests/data/.~lock.ers76.ods# b/tests/data/.~lock.ers76.ods# new file mode 100644 index 0000000..b30e851 --- /dev/null +++ b/tests/data/.~lock.ers76.ods# @@ -0,0 +1 @@ +Yingtong Li,runassudo,NEXUS.localdomain,09.08.2021 19:43,file:///home/runassudo/.config/libreoffice/4; \ No newline at end of file diff --git a/tests/data/ers76.blt b/tests/data/ers76.blt new file mode 100644 index 0000000..12d97c3 --- /dev/null +++ b/tests/data/ers76.blt @@ -0,0 +1,71 @@ +# Comment: Ballot papers for the ERS76 model election - all votes - ERS76 +# Source: Adapted by RunasSudo from ers97.blt (Joe Otten) +# Contributor: RunasSudo +11 6 +8 1 2 0 +3 1 3 0 +14 1 4 0 +34 1 5 0 +1 1 2 0 # Changed from "1 6" +4 1 7 0 +1 1 8 0 +3 1 9 3 0 # One of these changed to "1 9 6" +3 1 9 4 0 +3 1 9 6 0 +3 1 9 7 0 +4 1 9 8 0 +9 1 9 0 +4 1 10 3 0 +5 1 10 4 0 +3 1 10 6 0 +3 1 10 7 0 +6 1 10 8 0 +10 1 10 0 +1 1 11 5 0 +1 1 11 0 +11 1 0 +105 2 0 +91 3 0 +90 4 0 +81 5 0 +64 6 0 +11 7 6 0 +36 7 8 0 +12 7 0 +55 8 0 +3 9 3 0 +2 9 4 0 +2 9 5 3 0 +1 9 5 4 0 +15 9 5 0 +2 9 6 0 +2 9 8 0 +2 10 3 0 +2 10 4 0 +2 10 5 6 0 +1 10 5 7 6 0 +2 10 5 8 0 +11 10 5 0 +1 10 6 0 +1 10 7 4 0 +1 10 8 0 +1 10 0 +2 11 2 0 +4 11 3 0 +1 11 4 0 +5 11 7 8 0 +10 11 8 0 +1 11 0 +0 +"Smith" +"Duke" +"Prince" +"Freeman" +"Carpenter" +"Baron" +"Abbot" +"Vicar" +"Wright" +"Glazier" +"Monk" +"ERS Model - 1997 Edition" diff --git a/tests/data/ers76.csv b/tests/data/ers76.csv new file mode 100644 index 0000000..099c286 --- /dev/null +++ b/tests/data/ers76.csv @@ -0,0 +1,15 @@ +Stage:,1,,2,,4,,6,,7,,8, +Comment:,First preferences,,Surplus of Smith,,Exclusion of Monk,,"Exclusion of Glazier, Wright",,Surplus of Carpenter,,Exclusion of Abbot, +Smith,134,EL,108,EL,108,EL,108,EL,108,EL,108,EL +Duke,105,H,106.89,H,108.89,EL,108.89,EL,108.89,EL,108.89,EL +Prince,91,H,91.63,H,95.63,H,102.1,H,104.1,EL,104.1,EL +Freeman,90,H,92.94,H,93.94,H,99.62,H,100.62,H,101.62,EL +Carpenter,81,H,88.14,H,88.35,H,122.35,EL,108,EL,108,EL +Baron,64,H,64,H,64,H,68.26,H,70.26,H,82.26,H +Abbot,59,H,59.84,H,64.84,H,67.1,H,68.1,H,2.1,EX +Vicar,55,H,55.21,H,65.21,H,70.31,H,72.31,H,113.31,EL +Wright,27,H,32.25,H,32.25,H,0,EX,0,EX,0,EX +Glazier,24,H,30.51,H,30.51,H,0,EX,0,EX,0,EX +Monk,23,H,23.42,H,0,EX,0,EX,0,EX,0,EX +Non-transferable,0,,0.17,,1.38,,6.37,,12.72,,24.72, +Votes required,108,,,,,,,,104.07,,96.09, diff --git a/tests/data/ers76.ods b/tests/data/ers76.ods new file mode 100644 index 0000000000000000000000000000000000000000..c6cecec1a7f73a2f2268dcd19782b653249876be GIT binary patch literal 14925 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 literal 0 HcmV?d00001 diff --git a/tests/ers97.rs b/tests/ers.rs similarity index 76% rename from tests/ers97.rs rename to tests/ers.rs index 57f48dc..5f5a8fd 100644 --- a/tests/ers97.rs +++ b/tests/ers.rs @@ -61,3 +61,24 @@ fn ers97_rational() { utils::read_validate_election::("tests/data/ers97.csv", "tests/data/ers97.blt", stv_opts, None, &["nt", "vre"]); } + +#[test] +fn ers76_rational() { + let stv_opts = stv::STVOptionsBuilder::default() + .round_surplus_fractions(Some(2)) + .round_values(Some(2)) + .round_votes(Some(2)) + .round_quota(Some(0)) + .quota(stv::QuotaType::DroopExact) + .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) + .quota_mode(stv::QuotaMode::ERS76) + .surplus(stv::SurplusMethod::EG) + .transferable_only(true) + .exclusion(stv::ExclusionMethod::ByValue) + .early_bulk_elect(false) + .bulk_exclude(true) + .defer_surpluses(true) + .build().unwrap(); + + utils::read_validate_election::("tests/data/ers76.csv", "tests/data/ers76.blt", stv_opts, None, &["nt", "vre"]); +}