Complete implementation of Cambridge STV
Implement --min-threshold Add test
This commit is contained in:
parent
f182ca02bd
commit
0efc1e6eab
@ -15,6 +15,7 @@ The preset dropdown allows you to choose from a hardcoded list of preloaded STV
|
||||
| Australian Senate STV | Rules from the [*Commonwealth Electoral Act 1918*](https://www.legislation.gov.au/Details/C2020C00400/Html/Text#_Toc59107700), using the unweighted inclusive Gregory method. | [E2] [E3] [E4] | ✓ |
|
||||
| Western Australia STV | Rules from the [*Electoral Act 1907* (WA)](https://www.legislation.wa.gov.au/legislation/prod/filestore.nsf/FileURL/mrdoc_29498.pdf/$FILE/Electoral%20Act%201907%20-%20[17-a0-06].pdf), using the weighted inclusive Gregory method. | [E2] [E3] | |
|
||||
| Australian Capital Territory STV | Rules from the [*Electoral Act 1992* (ACT)](https://www.legislation.act.gov.au/View/a/1992-71/current/PDF/1992-71.PDF), using the exclusive Gregory method. | | ✓ |
|
||||
| Cambridge STV | Rules from the former [chapter 54A of the *Massachusetts General Laws*](https://www.cambridgema.gov/-/media/Files/electioncommission/massachusettsgenerallawschapter54a.pdf), as modified and in effect in Cambridge, Massachusetts. See also [here](https://web.archive.org/web/20081118104049/http://www.fairvote.org/media/1993countmanual.pdf). | | ✓ |
|
||||
| [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. | | ✓ |
|
||||
@ -207,6 +208,10 @@ When *Surplus method* is set to *Meek method*, this option controls when candida
|
||||
* When immediate election is disabled (default), all current surpluses are distributed and keep values finalised, before any candidates exceeding the quota are then declared elected. This is the method specified in the 1987 Meek rules.
|
||||
* When immediate election is enabled, a candidate meeting the quota interrupts a surplus distribution. The candidate is immediately declared elected, before the distribution of all surpluses of all now-elected candidates continues. This is the method specified in the 2006 Meek rules.
|
||||
|
||||
### Minimum threshold (--min-threshold)
|
||||
|
||||
In the first stage when candidates are excluded, all candidates with votes less than or equal to this threshold are excluded at once. The default value is 0, i.e. all candidates with no votes are excluded at once.
|
||||
|
||||
## Rounding
|
||||
|
||||
### Round quota/votes/surplus fractions/ballot values to [n] d.p. (--round-quota, --round-votes, --round-surplus-fractions, --round-values)
|
||||
|
@ -4,16 +4,17 @@ STV-counting software is frequently validated empirically by comparing the resul
|
||||
|
||||
| Method | Election | Comparator | Included test case |
|
||||
|-|-|-|-|
|
||||
| Scottish STV | [2007 Glasgow council Linn ward election](https://web.archive.org/web/20121004213938/http://www.glasgow.gov.uk/en/YourCouncil/Elections_Voting/Election_Results/ElectionScotland2007/LGWardResults.htm?ward=1&wardname=1%20-%20Linn) | eSTV 2.0.16 | ✓ |
|
||||
| Scottish STV | [2007 Glasgow council Linn ward election](https://web.archive.org/web/20121004213938/http://www.glasgow.gov.uk/en/YourCouncil/Elections_Voting/Election_Results/ElectionScotland2007/LGWardResults.htm?ward=1&wardname=1%20-%20Linn) | eSTV 2.0.16 (official) | ✓ |
|
||||
| OpenTally Meek | [Ballot papers derived from the ERS97 model election](https://yingtongli.me/blog/2021/01/04/ers97.html) | [Algorithm 123](https://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf) | ✓ |
|
||||
| Meek STV (2006) | Ballot papers derived from the ERS97 model election | [OpenSTV 1.7](https://github.com/Conservatory/openstv) | ✓ |
|
||||
| Meek STV (New Zealand) | Ballot papers derived from the ERS97 model election | OpenSTV 1.7, [Hill's nzmeek 6.7.7](https://yingtongli.me/blog/2021/07/08/nzmeek.html) | ✓ |
|
||||
| Australian Senate STV | [2019 Tasmanian Senate election](https://results.aec.gov.au/24310/Website/SenateDownloadsMenu-24310-Csv.htm) | EasyCount | ✓ |
|
||||
| Australian Senate STV | [2019 NSW Senate election](https://results.aec.gov.au/24310/Website/SenateDownloadsMenu-24310-Csv.htm) | EasyCount | |
|
||||
| Australian Capital Territory STV | [2020 Kurrajong Legislative Assembly election](https://www.elections.act.gov.au/elections_and_voting/2020_legislative_assembly_election/ballot-paper-preference-data-2020-election) | [eVACS 2020](https://www.elections.act.gov.au/elections_and_voting/electronic_voting_and_counting) | ✓ |
|
||||
| Wright STV | [EVE Online CSM 15 election](https://www.eveonline.com/news/view/meet-the-new-council) | [ccp-wright-stv](https://github.com/ccpgames/ccp-wright-stv) | ✓ |
|
||||
| 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) | ✓ |
|
||||
| ERS97 | Ballot papers derived from the ERS97 model election | [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) | ✓ |
|
||||
| Australian Senate STV | [2019 Tasmanian Senate election](https://results.aec.gov.au/24310/Website/SenateDownloadsMenu-24310-Csv.htm) | EasyCount (official) | ✓ |
|
||||
| Australian Senate STV | [2019 NSW Senate election](https://results.aec.gov.au/24310/Website/SenateDownloadsMenu-24310-Csv.htm) | EasyCount (official) | |
|
||||
| Australian Capital Territory STV | [2020 Kurrajong Legislative Assembly election](https://www.elections.act.gov.au/elections_and_voting/2020_legislative_assembly_election/ballot-paper-preference-data-2020-election) | [eVACS 2020](https://www.elections.act.gov.au/elections_and_voting/electronic_voting_and_counting) (official) | ✓ |
|
||||
| Cambridge STV | [2003 Cambridge City Council election](https://web.archive.org/web/20070204083508/http://stv.sourceforge.net/) | OpenSTV 1.7, [ChoicePlus Pro 2.1](https://www.votingsolutions.com/cpdetail.htm) (official) | ✓ |
|
||||
| Wright STV | [EVE Online CSM 15 election](https://www.eveonline.com/news/view/meet-the-new-council) | [ccp-wright-stv](https://github.com/ccpgames/ccp-wright-stv) (official) | ✓ |
|
||||
| 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 | Ballot papers derived from the ERS97 model election | [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) | ✓ |
|
||||
|
||||
# References
|
||||
|
||||
|
@ -213,6 +213,10 @@
|
||||
<span class="pill-grey" title="This option has effect only if “Method” is set to “Meek method”">Meek</span>
|
||||
Immediate election
|
||||
</label>
|
||||
<label class="col-12">
|
||||
Minimum threshold:
|
||||
<input type="number" id="txtMinThreshold" value="0" min="0" style="width: 3em;">
|
||||
</label>
|
||||
<div class="col-12 subheading">
|
||||
Rounding:
|
||||
</div>
|
||||
|
@ -153,6 +153,7 @@ async function clickCount() {
|
||||
document.getElementById('chkBulkExclusion').checked,
|
||||
document.getElementById('chkDeferSurpluses').checked,
|
||||
document.getElementById('chkMeekImmediateElect').checked,
|
||||
document.getElementById('txtMinThreshold').value,
|
||||
conPath,
|
||||
"guard_doom",
|
||||
parseInt(document.getElementById('txtPPDP').value),
|
||||
@ -365,6 +366,7 @@ function changePreset() {
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = false;
|
||||
document.getElementById('chkDeferSurpluses').checked = false;
|
||||
document.getElementById('txtMinThreshold').value = '0';
|
||||
document.getElementById('selNumbers').value = 'rational';
|
||||
document.getElementById('txtPPDP').value = '2';
|
||||
document.getElementById('chkNormaliseBallots').checked = false;
|
||||
@ -385,6 +387,7 @@ function changePreset() {
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = false;
|
||||
document.getElementById('chkDeferSurpluses').checked = false;
|
||||
document.getElementById('txtMinThreshold').value = '0';
|
||||
document.getElementById('selNumbers').value = 'fixed';
|
||||
document.getElementById('txtDP').value = '5';
|
||||
document.getElementById('txtPPDP').value = '5';
|
||||
@ -410,6 +413,7 @@ function changePreset() {
|
||||
document.getElementById('chkDeferSurpluses').checked = false;
|
||||
document.getElementById('chkMeekImmediateElect').checked = false;
|
||||
document.getElementById('chkMeekNZExclusion').checked = false;
|
||||
document.getElementById('txtMinThreshold').value = '0';
|
||||
document.getElementById('selNumbers').value = 'fixed';
|
||||
document.getElementById('txtDP').value = '5';
|
||||
document.getElementById('txtPPDP').value = '2';
|
||||
@ -434,6 +438,7 @@ function changePreset() {
|
||||
document.getElementById('chkDeferSurpluses').checked = true;
|
||||
document.getElementById('chkMeekImmediateElect').checked = true;
|
||||
document.getElementById('chkMeekNZExclusion').checked = false;
|
||||
document.getElementById('txtMinThreshold').value = '0';
|
||||
document.getElementById('selNumbers').value = 'fixed';
|
||||
document.getElementById('txtDP').value = '12';
|
||||
document.getElementById('txtPPDP').value = '2';
|
||||
@ -462,6 +467,7 @@ function changePreset() {
|
||||
document.getElementById('chkDeferSurpluses').checked = true;
|
||||
document.getElementById('chkMeekImmediateElect').checked = true;
|
||||
document.getElementById('chkMeekNZExclusion').checked = true;
|
||||
document.getElementById('txtMinThreshold').value = '0';
|
||||
document.getElementById('selNumbers').value = 'fixed';
|
||||
document.getElementById('txtDP').value = '12';
|
||||
document.getElementById('txtPPDP').value = '2';
|
||||
@ -488,6 +494,7 @@ function changePreset() {
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = false; // Senate "bulk exclusion" does not permit quota to be exceeded
|
||||
document.getElementById('chkDeferSurpluses').checked = false;
|
||||
document.getElementById('txtMinThreshold').value = '0';
|
||||
document.getElementById('selNumbers').value = 'rational';
|
||||
document.getElementById('txtPPDP').value = '0';
|
||||
document.getElementById('chkNormaliseBallots').checked = false;
|
||||
@ -510,6 +517,7 @@ function changePreset() {
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = false;
|
||||
document.getElementById('chkDeferSurpluses').checked = false;
|
||||
document.getElementById('txtMinThreshold').value = '0';
|
||||
document.getElementById('selNumbers').value = 'rational';
|
||||
document.getElementById('txtPPDP').value = '0';
|
||||
document.getElementById('chkNormaliseBallots').checked = false;
|
||||
@ -532,6 +540,7 @@ function changePreset() {
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = false;
|
||||
document.getElementById('chkDeferSurpluses').checked = false;
|
||||
document.getElementById('txtMinThreshold').value = '0';
|
||||
document.getElementById('selNumbers').value = 'rational';
|
||||
document.getElementById('txtPPDP').value = '2';
|
||||
document.getElementById('chkNormaliseBallots').checked = false;
|
||||
@ -554,6 +563,7 @@ function changePreset() {
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = false; // TODO: Cambridge-style bulk exclusion
|
||||
document.getElementById('chkDeferSurpluses').checked = false;
|
||||
document.getElementById('txtMinThreshold').value = '49';
|
||||
document.getElementById('selNumbers').value = 'rational';
|
||||
document.getElementById('txtPPDP').value = '0';
|
||||
document.getElementById('chkNormaliseBallots').checked = true;
|
||||
@ -571,6 +581,7 @@ function changePreset() {
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = true;
|
||||
document.getElementById('chkDeferSurpluses').checked = false;
|
||||
document.getElementById('txtMinThreshold').value = '0';
|
||||
document.getElementById('selNumbers').value = 'fixed';
|
||||
document.getElementById('txtDP').value = '5';
|
||||
document.getElementById('txtPPDP').value = '2';
|
||||
@ -593,6 +604,7 @@ function changePreset() {
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = false;
|
||||
document.getElementById('chkDeferSurpluses').checked = true;
|
||||
document.getElementById('txtMinThreshold').value = '0';
|
||||
document.getElementById('selNumbers').value = 'fixed';
|
||||
document.getElementById('txtDP').value = '6';
|
||||
document.getElementById('txtPPDP').value = '3';
|
||||
@ -618,6 +630,7 @@ function changePreset() {
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = true;
|
||||
document.getElementById('chkDeferSurpluses').checked = true;
|
||||
document.getElementById('txtMinThreshold').value = '0';
|
||||
document.getElementById('selNumbers').value = 'fixed';
|
||||
document.getElementById('txtDP').value = '5';
|
||||
document.getElementById('txtPPDP').value = '2';
|
||||
@ -643,6 +656,7 @@ function changePreset() {
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = true;
|
||||
document.getElementById('chkDeferSurpluses').checked = true;
|
||||
document.getElementById('txtMinThreshold').value = '0';
|
||||
document.getElementById('selNumbers').value = 'fixed';
|
||||
document.getElementById('txtDP').value = '5';
|
||||
document.getElementById('txtPPDP').value = '2';
|
||||
@ -668,6 +682,7 @@ function changePreset() {
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = true;
|
||||
document.getElementById('chkDeferSurpluses').checked = true;
|
||||
document.getElementById('txtMinThreshold').value = '0';
|
||||
document.getElementById('selNumbers').value = 'fixed';
|
||||
document.getElementById('txtDP').value = '5';
|
||||
document.getElementById('txtPPDP').value = '2';
|
||||
@ -693,6 +708,7 @@ function changePreset() {
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = false;
|
||||
document.getElementById('chkDeferSurpluses').checked = true;
|
||||
document.getElementById('txtMinThreshold').value = '0';
|
||||
document.getElementById('selNumbers').value = 'fixed';
|
||||
document.getElementById('txtDP').value = '5';
|
||||
document.getElementById('txtPPDP').value = '2';
|
||||
|
@ -69,11 +69,11 @@ struct STV {
|
||||
// -- Rounding settings --
|
||||
|
||||
/// Round surplus fractions to specified decimal places
|
||||
#[clap(help_heading=Some("ROUNDING"), long, alias="round_tvs", value_name="dps")]
|
||||
#[clap(help_heading=Some("ROUNDING"), long, alias="round-tvs", value_name="dps")]
|
||||
round_surplus_fractions: Option<usize>,
|
||||
|
||||
/// Round ballot values to specified decimal places
|
||||
#[clap(help_heading=Some("ROUNDING"), long, alias="round_weights", value_name="dps")]
|
||||
#[clap(help_heading=Some("ROUNDING"), long, alias="round-weights", value_name="dps")]
|
||||
round_values: Option<usize>,
|
||||
|
||||
/// Round votes to specified decimal places
|
||||
@ -157,6 +157,10 @@ struct STV {
|
||||
#[clap(help_heading=Some("COUNT OPTIMISATIONS"), long)]
|
||||
meek_immediate_elect: bool,
|
||||
|
||||
/// On exclusion, exclude any candidate with fewer than this many votes
|
||||
#[clap(help_heading=Some("COUNT OPTIMISATIONS"), long, default_value="0", value_name="votes")]
|
||||
min_threshold: String,
|
||||
|
||||
// -----------------
|
||||
// -- Constraints --
|
||||
|
||||
@ -274,6 +278,7 @@ where
|
||||
cmd_opts.bulk_exclude,
|
||||
cmd_opts.defer_surpluses,
|
||||
cmd_opts.meek_immediate_elect,
|
||||
cmd_opts.min_threshold,
|
||||
cmd_opts.constraints.as_deref(),
|
||||
&cmd_opts.constraint_mode,
|
||||
cmd_opts.hide_excluded,
|
||||
|
@ -83,6 +83,8 @@ pub struct STVOptions {
|
||||
pub defer_surpluses: bool,
|
||||
/// (Meek STV) Immediately elect candidates even if keep values have not converged
|
||||
pub meek_immediate_elect: bool,
|
||||
/// On exclusion, exclude any candidate with this many votes or fewer
|
||||
pub min_threshold: String,
|
||||
/// Path to constraints file (used only for [STVOptions::describe])
|
||||
pub constraints_path: Option<String>,
|
||||
/// Mode of handling constraints
|
||||
@ -119,6 +121,7 @@ impl STVOptions {
|
||||
bulk_exclude: bool,
|
||||
defer_surpluses: bool,
|
||||
meek_immediate_elect: bool,
|
||||
min_threshold: String,
|
||||
constraints_path: Option<&str>,
|
||||
constraint_mode: &str,
|
||||
hide_excluded: bool,
|
||||
@ -191,6 +194,7 @@ impl STVOptions {
|
||||
bulk_exclude,
|
||||
defer_surpluses,
|
||||
meek_immediate_elect,
|
||||
min_threshold,
|
||||
constraints_path: match constraints_path {
|
||||
Some(p) => Some(p.to_string()),
|
||||
None => None,
|
||||
@ -234,6 +238,7 @@ impl STVOptions {
|
||||
if self.bulk_exclude { flags.push("--bulk-exclude".to_string()); }
|
||||
if self.defer_surpluses { flags.push("--defer-surpluses".to_string()); }
|
||||
if self.surplus == SurplusMethod::Meek && self.meek_immediate_elect { flags.push("--meek-immediate-elect".to_string()); }
|
||||
if self.min_threshold != "1" { flags.push(format!("--min-threshold {}", self.min_threshold)); }
|
||||
if let Some(path) = &self.constraints_path {
|
||||
flags.push(format!("--constraints {}", path));
|
||||
if self.constraint_mode != ConstraintMode::GuardDoom { flags.push(self.constraint_mode.describe()); }
|
||||
@ -255,6 +260,7 @@ impl STVOptions {
|
||||
if self.quota_criterion != QuotaCriterion::GreaterOrEqual { return Err(STVError::InvalidOptions("--surplus cincinnati and --surplus hare require --quota-criterion geq")); }
|
||||
if !self.normalise_ballots { return Err(STVError::InvalidOptions("--surplus cincinnati and --surplus hare require --normalise-ballots")); }
|
||||
}
|
||||
if self.min_threshold != "0" && self.defer_surpluses { return Err(STVError::InvalidOptions("--min-threshold is incompatible with --defer-surpluses")); } // TODO: Permit this
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@ -1135,6 +1141,26 @@ where
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
/// Determine which continuing candidates have votes equal to or below the minimum threshold
|
||||
fn hopefuls_below_threshold<'a, N: Number>(state: &CountState<'a, N>, opts: &STVOptions) -> Vec<&'a Candidate> {
|
||||
let min_threshold = N::parse(&opts.min_threshold);
|
||||
|
||||
let excluded_candidates: Vec<&Candidate> = state.candidates.iter()
|
||||
.filter_map(|(c, cc)|
|
||||
if cc.state == CandidateState::Hopeful && cc.votes <= min_threshold {
|
||||
Some(*c)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Do not exclude if this violates constraints
|
||||
match constraints::try_constraints(state, &excluded_candidates, CandidateState::Excluded) {
|
||||
Ok(_) => { return excluded_candidates; }
|
||||
Err(_) => { return Vec::new(); } // Bulk exclusion conflicts with constraints
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine which continuing candidates could be excluded in a bulk exclusion
|
||||
///
|
||||
/// The value of [STVOptions::bulk_exclude] is not taken into account and must be handled by the caller
|
||||
@ -1192,8 +1218,13 @@ where
|
||||
{
|
||||
let mut excluded_candidates: Vec<&Candidate> = Vec::new();
|
||||
|
||||
// Exclude candidates below min threshold
|
||||
if state.num_excluded == 0 {
|
||||
excluded_candidates = hopefuls_below_threshold(state, opts);
|
||||
}
|
||||
|
||||
// Attempt a bulk exclusion
|
||||
if opts.bulk_exclude {
|
||||
if excluded_candidates.is_empty() && opts.bulk_exclude {
|
||||
excluded_candidates = hopefuls_to_bulk_exclude(state, opts);
|
||||
}
|
||||
|
||||
|
@ -232,6 +232,7 @@ impl STVOptions {
|
||||
bulk_exclude: bool,
|
||||
defer_surpluses: bool,
|
||||
meek_immediate_elect: bool,
|
||||
min_threshold: String,
|
||||
constraints_path: Option<String>,
|
||||
constraint_mode: &str,
|
||||
pp_decimals: usize,
|
||||
@ -258,6 +259,7 @@ impl STVOptions {
|
||||
bulk_exclude,
|
||||
defer_surpluses,
|
||||
meek_immediate_elect,
|
||||
min_threshold,
|
||||
constraints_path.as_deref(),
|
||||
constraint_mode,
|
||||
false,
|
||||
|
@ -43,6 +43,7 @@ fn act_kurrajong20_rational() {
|
||||
bulk_exclude: false,
|
||||
defer_surpluses: false,
|
||||
meek_immediate_elect: false,
|
||||
min_threshold: "0".to_string(),
|
||||
constraints_path: None,
|
||||
constraint_mode: stv::ConstraintMode::GuardDoom,
|
||||
hide_excluded: false,
|
||||
|
@ -80,6 +80,7 @@ fn aec_tas19_rational() {
|
||||
bulk_exclude: true,
|
||||
defer_surpluses: false,
|
||||
meek_immediate_elect: false,
|
||||
min_threshold: "0".to_string(),
|
||||
constraints_path: None,
|
||||
constraint_mode: stv::ConstraintMode::GuardDoom,
|
||||
hide_excluded: false,
|
||||
|
54
tests/cambridge.rs
Normal file
54
tests/cambridge.rs
Normal file
@ -0,0 +1,54 @@
|
||||
/* OpenTally: Open-source election vote counting
|
||||
* Copyright © 2021 Lee Yingtong Li (RunasSudo)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
mod utils;
|
||||
|
||||
use opentally::numbers::Rational;
|
||||
use opentally::stv;
|
||||
|
||||
#[test]
|
||||
fn cambridge_cc03_rational() {
|
||||
let stv_opts = stv::STVOptions {
|
||||
round_surplus_fractions: None,
|
||||
round_values: None,
|
||||
round_votes: None,
|
||||
round_quota: Some(0),
|
||||
sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep,
|
||||
meek_surplus_tolerance: String::new(),
|
||||
normalise_ballots: true,
|
||||
quota: stv::QuotaType::Droop,
|
||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||
quota_mode: stv::QuotaMode::Static,
|
||||
ties: vec![],
|
||||
surplus: stv::SurplusMethod::Cincinnati,
|
||||
surplus_order: stv::SurplusOrder::ByOrder,
|
||||
transferable_only: true,
|
||||
exclusion: stv::ExclusionMethod::SingleStage,
|
||||
meek_nz_exclusion: false,
|
||||
early_bulk_elect: false,
|
||||
bulk_exclude: false,
|
||||
defer_surpluses: false,
|
||||
meek_immediate_elect: false,
|
||||
min_threshold: "49".to_string(),
|
||||
constraints_path: None,
|
||||
constraint_mode: stv::ConstraintMode::GuardDoom,
|
||||
hide_excluded: false,
|
||||
sort_votes: false,
|
||||
pp_decimals: 2,
|
||||
};
|
||||
utils::read_validate_election::<Rational>("tests/data/CambCC2003.csv", "tests/data/CambCC2003.blt", stv_opts, None, &["exhausted"]);
|
||||
}
|
@ -59,6 +59,7 @@ fn prsa1_constr1_rational() {
|
||||
bulk_exclude: false,
|
||||
defer_surpluses: false,
|
||||
meek_immediate_elect: false,
|
||||
min_threshold: "0".to_string(),
|
||||
constraints_path: Some("tests/data/prsa1_constr1.con".to_string()),
|
||||
constraint_mode: stv::ConstraintMode::GuardDoom,
|
||||
hide_excluded: false,
|
||||
@ -122,6 +123,7 @@ fn prsa1_constr2_rational() {
|
||||
bulk_exclude: false,
|
||||
defer_surpluses: false,
|
||||
meek_immediate_elect: false,
|
||||
min_threshold: "0".to_string(),
|
||||
constraints_path: Some("tests/data/prsa1_constr2.con".to_string()),
|
||||
constraint_mode: stv::ConstraintMode::GuardDoom,
|
||||
hide_excluded: false,
|
||||
@ -185,6 +187,7 @@ fn prsa1_constr3_rational() {
|
||||
bulk_exclude: false,
|
||||
defer_surpluses: false,
|
||||
meek_immediate_elect: false,
|
||||
min_threshold: "0".to_string(),
|
||||
constraints_path: Some("tests/data/prsa1_constr2.con".to_string()),
|
||||
constraint_mode: stv::ConstraintMode::GuardDoom,
|
||||
hide_excluded: false,
|
||||
@ -260,6 +263,7 @@ fn ers97_cantbulkexclude_rational() {
|
||||
bulk_exclude: true,
|
||||
defer_surpluses: true,
|
||||
meek_immediate_elect: false,
|
||||
min_threshold: "0".to_string(),
|
||||
constraints_path: Some("tests/data/ers97_cantbulkexclude".to_string()),
|
||||
constraint_mode: stv::ConstraintMode::GuardDoom,
|
||||
hide_excluded: false,
|
||||
|
@ -43,6 +43,7 @@ fn csm15_float64() {
|
||||
bulk_exclude: true,
|
||||
defer_surpluses: false,
|
||||
meek_immediate_elect: false,
|
||||
min_threshold: "0".to_string(),
|
||||
constraints_path: None,
|
||||
constraint_mode: stv::ConstraintMode::GuardDoom,
|
||||
hide_excluded: false,
|
||||
|
20116
tests/data/CambCC2003.blt
Normal file
20116
tests/data/CambCC2003.blt
Normal file
File diff suppressed because it is too large
Load Diff
32
tests/data/CambCC2003.csv
Normal file
32
tests/data/CambCC2003.csv
Normal file
@ -0,0 +1,32 @@
|
||||
Stage:,1,,2,,3,,4,,5,,6,,7,,8,,9,,10,,11,,12,,13,
|
||||
Comment:,First preferences,,"Surplus of Galluccio, Anthony D.",,,,"Exclusion of Dixon, Vincent Lawrence",,"Exclusion of Hall, Robert L., Sr.",,"Exclusion of LaTremouille, Robert J.",,"Exclusion of Taymorberry, Laurie",,"Exclusion of King, Ethridge A., Jr.",,"Exclusion of Smith, Aimee Louise",,"Exclusion of Bellew, Carole K.",,"Exclusion of Kelley, Craig A.",,"Exclusion of Pitkin, John",,"Exclusion of DeBergalis, Matt S.",
|
||||
"Bellew, Carole K.",735,H,759,H,761,H,762,H,767,H,771,H,788,H,811,H,861,H,0,EX,0,EX,0,EX,0,EX
|
||||
"Davis, Henrietta",1846,H,1896,H,1901,H,1904,H,1909,H,1920,H,1950,H,1977,H,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL
|
||||
"DeBergalis, Matt S.",1206,H,1216,H,1220,H,1227,H,1229,H,1233,H,1242,H,1283,H,1342,H,1441,H,1494,H,1640,H,0,EX
|
||||
"Decker, Marjorie C.",1378,H,1425,H,1428,H,1432,H,1437,H,1442,H,1463,H,1486,H,1528,H,1641,H,1787,H,2009,EL,2009,EL
|
||||
"Dixon, Vincent Lawrence",64,H,64,H,65,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
"Galluccio, Anthony D.",2994,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL,2009,EL
|
||||
"Greenwood, Dan J.",39,H,41,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
"Hall, Robert L., Sr.",96,H,101,H,114,H,123,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
"Kelley, Craig A.",992,H,1013,H,1016,H,1021,H,1051,H,1062,H,1070,H,1094,H,1118,H,1181,H,0,EX,0,EX,0,EX
|
||||
"King, Ethridge A., Jr.",361,H,372,H,377,H,381,H,387,H,399,H,406,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
"LaTremouille, Robert J.",126,H,131,H,133,H,136,H,142,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
"Maher, David P.",1190,H,1430,H,1433,H,1435,H,1444,H,1448,H,1453,H,1499,H,1505,H,1573,H,1698,H,1839,H,2009,EL
|
||||
"Murphy, Brian",1362,H,1396,H,1398,H,1401,H,1403,H,1410,H,1423,H,1446,H,1479,H,1595,H,1742,H,2009,EL,2009,EL
|
||||
"Pitkin, John",1010,H,1032,H,1034,H,1037,H,1044,H,1057,H,1066,H,1091,H,1130,H,1223,H,1493,H,0,EX,0,EX
|
||||
"Reeves, Kenneth E.",1525,H,1602,H,1607,H,1611,H,1618,H,1630,H,1637,H,1667,H,1695,H,1735,H,1807,H,1898,H,2009,EL
|
||||
"Simmons, Denise",1181,H,1232,H,1233,H,1239,H,1250,H,1259,H,1274,H,1299,H,1408,H,1479,H,1569,H,1777,H,2009,EL
|
||||
"Smith, Aimee Louise",480,H,484,H,489,H,490,H,491,H,499,H,517,H,527,H,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
"Sullivan, Michael A.",1656,H,1893,H,1895,H,1900,H,1905,H,1910,H,1918,H,1953,H,1976,H,2009,EL,2009,EL,2009,EL,2009,EL
|
||||
"Taymorberry, Laurie",188,H,193,H,193,H,194,H,197,H,208,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
"Toomey, Timothy J., Jr.",1613,H,1753,H,1753,H,1755,H,1757,H,1764,H,1776,H,1790,H,1805,H,1859,H,1921,H,2009,EL,2009,EL
|
||||
Write-In 1,36,H,36,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
Write-In 2,2,H,2,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
Write-In 3,0,H,0,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
Write-In 4,0,H,0,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
Write-In 5,0,H,0,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
Write-In 6,0,H,0,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
Write-In 7,0,H,0,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
Write-In 8,0,H,0,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
Write-In 9,0,H,0,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
|
||||
Exhausted,0,,0,,21,,23,,40,,59,,88,,148,,215,,326,,542,,872,,1999,
|
|
BIN
tests/data/CambCC2003.ods
Normal file
BIN
tests/data/CambCC2003.ods
Normal file
Binary file not shown.
@ -43,6 +43,7 @@ fn ers97_rational() {
|
||||
bulk_exclude: true,
|
||||
defer_surpluses: true,
|
||||
meek_immediate_elect: false,
|
||||
min_threshold: "0".to_string(),
|
||||
constraints_path: None,
|
||||
constraint_mode: stv::ConstraintMode::GuardDoom,
|
||||
hide_excluded: false,
|
||||
|
@ -45,6 +45,7 @@ fn meek87_ers97_float64() {
|
||||
bulk_exclude: false,
|
||||
defer_surpluses: false,
|
||||
meek_immediate_elect: false,
|
||||
min_threshold: "0".to_string(),
|
||||
constraints_path: None,
|
||||
constraint_mode: stv::ConstraintMode::GuardDoom,
|
||||
hide_excluded: false,
|
||||
@ -78,6 +79,7 @@ fn meek06_ers97_fixed12() {
|
||||
bulk_exclude: false,
|
||||
defer_surpluses: true,
|
||||
meek_immediate_elect: true,
|
||||
min_threshold: "0".to_string(),
|
||||
constraints_path: None,
|
||||
constraint_mode: stv::ConstraintMode::GuardDoom,
|
||||
hide_excluded: false,
|
||||
@ -152,6 +154,7 @@ fn meeknz_ers97_fixed12() {
|
||||
bulk_exclude: false,
|
||||
defer_surpluses: true,
|
||||
meek_immediate_elect: true,
|
||||
min_threshold: "0".to_string(),
|
||||
constraints_path: None,
|
||||
constraint_mode: stv::ConstraintMode::GuardDoom,
|
||||
hide_excluded: false,
|
||||
|
@ -43,6 +43,7 @@ fn prsa1_rational() {
|
||||
bulk_exclude: false,
|
||||
defer_surpluses: false,
|
||||
meek_immediate_elect: false,
|
||||
min_threshold: "0".to_string(),
|
||||
constraints_path: None,
|
||||
constraint_mode: stv::ConstraintMode::GuardDoom,
|
||||
hide_excluded: false,
|
||||
|
@ -47,6 +47,7 @@ fn scotland_linn07_fixed5() {
|
||||
bulk_exclude: false,
|
||||
defer_surpluses: false,
|
||||
meek_immediate_elect: false,
|
||||
min_threshold: "0".to_string(),
|
||||
constraints_path: None,
|
||||
constraint_mode: stv::ConstraintMode::GuardDoom,
|
||||
hide_excluded: false,
|
||||
@ -80,6 +81,7 @@ fn scotland_linn07_gfixed5() {
|
||||
bulk_exclude: false,
|
||||
defer_surpluses: false,
|
||||
meek_immediate_elect: false,
|
||||
min_threshold: "0".to_string(),
|
||||
constraints_path: None,
|
||||
constraint_mode: stv::ConstraintMode::GuardDoom,
|
||||
hide_excluded: false,
|
||||
|
Loading…
Reference in New Issue
Block a user