diff --git a/docs/options.md b/docs/options.md index dd09da8..3c81fad 100644 --- a/docs/options.md +++ b/docs/options.md @@ -43,7 +43,7 @@ This functionality is not available on the command line. The quota dropdowns allow you to define the quota used in the election, and the quota criterion used to elect candidates. The quota may be set to: -* *Droop* and *Droop (exact)*: *V*/(*S*+1) +* *Droop* (default) and *Droop (exact)*: *V*/(*S*+1) * *Hare* and *Hare (exact)*: *V*/*S* where *V* is the number of votes and *S* is the number of seats. @@ -54,15 +54,15 @@ When *Round quota to [n] d.p.* is not enabled, *Droop* (or *Droop (exact)*) is a ### Quota criterion (-c/--quota-criterion) -The quota criterion may be set to *>=* (candidates are elected if they meet or exceed the quota) or *>* (candidates are elected only if they strictly exceed the quota). +The quota criterion may be set to *>=* (candidates are elected if they meet or exceed the quota) or *>* (default; candidates are elected only if they strictly exceed the quota). -Note that the combination ‘*>= Droop (exact)*’ (with *Round quota to [n] d.p.* enabled) can result in more candidates meeting the quota than there are available vacancies, hence this particular combination is not recommended. +Note that the combination ‘*>= Droop (exact)*’ (or, when *Round quota to [n] d.p.* is disabled, ‘*>= Droop*’) can result in more candidates meeting the quota than there are available vacancies, hence this particular combination is not recommended. ### Quota mode (--quota-mode) This option allows you to specify whether the votes required for election can change during the count. The options are: -* *Static quota*: The quota is calculated once after all first-preference votes are allocated, and remains constant throughout the count. +* *Static quota* (default): The quota is calculated once after all first-preference votes are allocated, and remains constant throughout the count. * *Static with ERS97 rules*: The quota is static, but candidates may be elected if their vote exceeds (or equals, according to the *Quota criterion*) the active vote, divided by (*S* + 1). * *Dynamic by total vote*: The quota is recalculated at the end of each stage, according to the *Quota* option. * *Dynamic by active vote*: The quota is recalculated at the end of each stage, according to the *Quota* option, but where *V* is the active vote and *S* is the number of remaining vacancies. @@ -99,10 +99,10 @@ Random sample methods are also supported, but also not recommended: The use of a random sample method requires *Normalise ballots* to be enabled, and will usually be used with a *Quota criterion* set to *>=*. -### Papers to examine in surplus transfer (--t ransferable-only) +### Papers to examine in surplus transfer (--transferable-only) -* *Include non-transferable papers* (default): When this option is selected, all ballot papers of the transferring candidate are examined. Non-transferable papers are always exhausted at the relevant surplus fractions. -* *Use transferable papers only* (CLI: --transferable-only): When this option is selected, only transferable papers of the transferring candidate are examined. Non-transferable papers are exhausted only if the value of the transferable papers is less than the surplus. +* *Include non-transferable papers* (default): When this option is selected, all ballot papers of the transferring candidate are examined. Non-transferable papers are always exhausted at the relevant surplus fractions. This is the method typically used with the weighted inclusive Gregory or Meek methods. +* *Use transferable papers only*: When this option is selected, only transferable papers of the transferring candidate are examined. Non-transferable papers are exhausted only if the value of the transferable papers is less than the surplus. This is the method typically used with other surplus distribution methods. ### (Gregory) Exclusion method (--exclusion) @@ -125,7 +125,7 @@ When *Surplus method* is set to *Meek method*, this option controls how candidat When *Surplus method* is set to a random sample method, this option controls which subset of ballot papers is selected for transfer during surplus distributions: -* *Stratified (then by order)*: The candidate's ballot papers are first stratified into subparcels according to next available preference. From each subparcel, the subset transferred comprises the ballot papers most recently received by the candidate. +* *Stratified (then by order)* (default): The candidate's ballot papers are first stratified into subparcels according to next available preference. From each subparcel, the subset transferred comprises the ballot papers most recently received by the candidate. * *By order*: The subset transferred comprises the ballot papers most recently received by the candidate. * *Every n-th ballot*: The subset is selected using the deterministic method used in [Cambridge, Massachusetts](https://web.archive.org/web/20081118104049/http://www.fairvote.org/media/1993countmanual.pdf) (derived from Article IX of the former 1938 Cincinnati *Code of Ordinances*). @@ -173,7 +173,7 @@ This dropdown allows you to select how numbers (vote totals, etc.) are represent * *Fixed*: Numbers are represented as fixed-precision decimals, up to a certain number of decimal places (default: 5). * *Fixed (guarded)*: Numbers are represented as fixed-precision decimals with ‘guard digits’ – also known as [‘quasi-exact’ arithmetic](http://www.votingmatters.org.uk/ISSUE24/I24P2.pdf). If *n* decimal places are requested, numbers are represented up to 2*n* decimal places, and two values are considered equal if the absolute difference is less than (10−*n*)/2. -* *Rational*: Numbers are represented exactly as fractions, resulting in the elimination of rounding error, but increasing computational complexity when the number of surplus transfers is very large. +* *Rational* (default): Numbers are represented exactly as fractions, resulting in the elimination of rounding error, but increasing computational complexity when the number of surplus transfers is very large. * *Float (64-bit)*: Numbers are represented as native 64-bit floating-point numbers. This is fast, but not recommended as unexpectedly large rounding errors may be introduced in some circumstances. ### Display up to [n] d.p. (--pp-decimals) diff --git a/html/index.js b/html/index.js index 9a5c79e..fd277b8 100644 --- a/html/index.js +++ b/html/index.js @@ -365,7 +365,7 @@ async function printResult() { function changePreset() { if (document.getElementById('selPreset').value === 'wigm') { document.getElementById('selQuotaCriterion').value = 'gt'; - document.getElementById('selQuota').value = 'droop_exact'; + document.getElementById('selQuota').value = 'droop'; document.getElementById('selQuotaMode').value = 'static'; document.getElementById('chkBulkElection').checked = true; document.getElementById('chkBulkExclusion').checked = false; diff --git a/src/main.rs b/src/main.rs index bd71546..0eac96d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -97,7 +97,7 @@ struct STV { // -- Quota -- /// Quota type - #[clap(help_heading=Some("QUOTA"), short, long, possible_values=&["droop", "hare", "droop_exact", "hare_exact"], default_value="droop_exact")] + #[clap(help_heading=Some("QUOTA"), short, long, possible_values=&["droop", "hare", "droop_exact", "hare_exact"], default_value="droop")] quota: String, /// Whether to elect candidates on meeting (geq) or strictly exceeding (gt) the quota diff --git a/src/stv/mod.rs b/src/stv/mod.rs index 3252e8d..83298cc 100644 --- a/src/stv/mod.rs +++ b/src/stv/mod.rs @@ -75,7 +75,7 @@ pub struct STVOptions { pub normalise_ballots: bool, /// Quota type - #[builder(default="QuotaType::DroopExact")] + #[builder(default="QuotaType::Droop")] pub quota: QuotaType, /// Whether to elect candidates on meeting (geq) or strictly exceeding (gt) the quota @@ -173,7 +173,7 @@ impl STVOptions { if self.surplus != SurplusMethod::Meek && self.sum_surplus_transfers != SumSurplusTransfersMode::SingleStep { flags.push(self.sum_surplus_transfers.describe()); } if self.surplus == SurplusMethod::Meek && self.meek_surplus_tolerance != "0.001%" { flags.push(format!("--meek-surplus-tolerance {}", self.meek_surplus_tolerance)); } if self.normalise_ballots { flags.push("--normalise-ballots".to_string()); } - if self.quota != QuotaType::DroopExact { flags.push(self.quota.describe()); } + if self.quota != QuotaType::Droop { flags.push(self.quota.describe()); } if self.quota_criterion != QuotaCriterion::Greater { flags.push(self.quota_criterion.describe()); } if self.surplus != SurplusMethod::Meek && self.quota_mode != QuotaMode::Static { flags.push(self.quota_mode.describe()); } let ties_str = self.ties.iter().map(|t| t.describe()).join(" "); diff --git a/tests/act.rs b/tests/act.rs index e29747c..f2c2f8a 100644 --- a/tests/act.rs +++ b/tests/act.rs @@ -25,7 +25,6 @@ fn act_kurrajong20_rational() { let stv_opts = stv::STVOptionsBuilder::default() .round_votes(Some(6)) .round_quota(Some(0)) - .quota(stv::QuotaType::Droop) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .surplus(stv::SurplusMethod::EG) .surplus_order(stv::SurplusOrder::ByOrder) diff --git a/tests/aec.rs b/tests/aec.rs index 51a2179..d5a95ac 100644 --- a/tests/aec.rs +++ b/tests/aec.rs @@ -62,7 +62,6 @@ fn aec_tas19_rational() { let stv_opts = stv::STVOptionsBuilder::default() .round_votes(Some(0)) .round_quota(Some(0)) - .quota(stv::QuotaType::Droop) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .surplus(stv::SurplusMethod::UIG) .surplus_order(stv::SurplusOrder::ByOrder) diff --git a/tests/cambridge.rs b/tests/cambridge.rs index 3cbc606..46dd7ca 100644 --- a/tests/cambridge.rs +++ b/tests/cambridge.rs @@ -25,7 +25,6 @@ fn cambridge_cc03_rational() { let stv_opts = stv::STVOptionsBuilder::default() .round_quota(Some(0)) .normalise_ballots(true) - .quota(stv::QuotaType::Droop) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .surplus(stv::SurplusMethod::Cincinnati) .transferable_only(true) diff --git a/tests/cli.rs b/tests/cli.rs index 78f6961..bc178e6 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -19,9 +19,9 @@ use assert_cmd::Command; use predicates::prelude::*; #[test] -fn cli_ers97() { +fn cli_ers97old() { Command::cargo_bin("opentally").expect("Cargo Error") - .args(&["stv", "tests/data/ers97.blt", "--numbers", "fixed", "--decimals", "5", "--round-tvs", "2", "--round-weights", "2", "--round-votes", "2", "--round-quota", "2", "--quota", "droop_exact", "--quota-mode", "ers97", "--surplus", "eg", "--transferable-only", "--exclusion", "by_value"]) + .args(&["stv", "tests/data/ers97old.blt", "--numbers", "fixed", "--decimals", "5", "--round-tvs", "2", "--round-weights", "2", "--round-votes", "2", "--round-quota", "2", "--quota", "droop_exact", "--quota-mode", "ers97", "--surplus", "eg", "--transferable-only", "--exclusion", "by_value"]) .assert().success(); } diff --git a/tests/constraints.rs b/tests/constraints.rs index b800d05..3370e46 100644 --- a/tests/constraints.rs +++ b/tests/constraints.rs @@ -43,7 +43,6 @@ fn prsa1_constr1_rational() { .round_values(Some(3)) .round_votes(Some(3)) .round_quota(Some(3)) - .quota(stv::QuotaType::Droop) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .surplus(stv::SurplusMethod::EG) .surplus_order(stv::SurplusOrder::ByOrder) @@ -93,7 +92,6 @@ fn prsa1_constr2_rational() { .round_values(Some(3)) .round_votes(Some(3)) .round_quota(Some(3)) - .quota(stv::QuotaType::Droop) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .surplus(stv::SurplusMethod::EG) .surplus_order(stv::SurplusOrder::ByOrder) @@ -143,7 +141,6 @@ fn prsa1_constr3_rational() { .round_values(Some(3)) .round_votes(Some(3)) .round_quota(Some(3)) - .quota(stv::QuotaType::Droop) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .surplus(stv::SurplusMethod::EG) .surplus_order(stv::SurplusOrder::ByOrder) @@ -177,11 +174,11 @@ fn prsa1_constr3_rational() { /// Same election data as ers97_rational, but with a constraint that prevents the bulk exclusion of Glazier and Wright #[test] -fn ers97_cantbulkexclude_rational() { +fn ers97old_cantbulkexclude_rational() { // Read CSV file let reader = csv::ReaderBuilder::new() .has_headers(false) - .from_path("tests/data/ers97_cantbulkexclude.csv") + .from_path("tests/data/ers97old_cantbulkexclude.csv") .expect("IO Error"); let records: Vec = reader.into_records().map(|r| r.expect("Syntax Error")).collect(); @@ -192,10 +189,10 @@ fn ers97_cantbulkexclude_rational() { let stages: Vec = records.first().unwrap().iter().skip(1).step_by(2).map(|s| s.parse().unwrap()).collect(); // Read BLT - let mut election: Election = Election::from_file("tests/data/ers97.blt").expect("Syntax Error"); + let mut election: Election = Election::from_file("tests/data/ers97old.blt").expect("Syntax Error"); // Read CON - let file = File::open("tests/data/ers97_cantbulkexclude.con").expect("IO Error"); + let file = File::open("tests/data/ers97old_cantbulkexclude.con").expect("IO Error"); let file_reader = io::BufReader::new(file); let lines = file_reader.lines(); election.constraints = Some(Constraints::from_con(lines.map(|r| r.expect("IO Error").to_string()).into_iter())); @@ -205,6 +202,7 @@ fn ers97_cantbulkexclude_rational() { .round_values(Some(2)) .round_votes(Some(2)) .round_quota(Some(2)) + .quota(stv::QuotaType::DroopExact) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .quota_mode(stv::QuotaMode::ERS97) .surplus(stv::SurplusMethod::EG) @@ -213,7 +211,7 @@ fn ers97_cantbulkexclude_rational() { .early_bulk_elect(false) .bulk_exclude(true) .defer_surpluses(true) - .constraints_path(Some("tests/data/ers97_cantbulkexclude".to_string())) + .constraints_path(Some("tests/data/ers97old_cantbulkexclude".to_string())) .build().unwrap(); utils::validate_election::(stages, records, election, stv_opts, None, &["nt", "vre"]); diff --git a/tests/csm.rs b/tests/csm.rs index 4cca6d2..5575d4d 100644 --- a/tests/csm.rs +++ b/tests/csm.rs @@ -24,7 +24,6 @@ use opentally::stv; fn csm15_float64() { let stv_opts = stv::STVOptionsBuilder::default() .round_quota(Some(0)) - .quota(stv::QuotaType::Droop) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .exclusion(stv::ExclusionMethod::Wright) .early_bulk_elect(false) diff --git a/tests/data/ers97.blt b/tests/data/ers97old.blt similarity index 100% rename from tests/data/ers97.blt rename to tests/data/ers97old.blt diff --git a/tests/data/ers97.csv b/tests/data/ers97old.csv similarity index 100% rename from tests/data/ers97.csv rename to tests/data/ers97old.csv diff --git a/tests/data/ers97.ods b/tests/data/ers97old.ods similarity index 100% rename from tests/data/ers97.ods rename to tests/data/ers97old.ods diff --git a/tests/data/ers97_cantbulkexclude.con b/tests/data/ers97old_cantbulkexclude.con similarity index 100% rename from tests/data/ers97_cantbulkexclude.con rename to tests/data/ers97old_cantbulkexclude.con diff --git a/tests/data/ers97_cantbulkexclude.csv b/tests/data/ers97old_cantbulkexclude.csv similarity index 100% rename from tests/data/ers97_cantbulkexclude.csv rename to tests/data/ers97old_cantbulkexclude.csv diff --git a/tests/data/ers97_cantbulkexclude.ods b/tests/data/ers97old_cantbulkexclude.ods similarity index 100% rename from tests/data/ers97_cantbulkexclude.ods rename to tests/data/ers97old_cantbulkexclude.ods diff --git a/tests/data/ers97_meek.csv b/tests/data/ers97old_meek.csv similarity index 100% rename from tests/data/ers97_meek.csv rename to tests/data/ers97old_meek.csv diff --git a/tests/data/ers97_meek.ods b/tests/data/ers97old_meek.ods similarity index 100% rename from tests/data/ers97_meek.ods rename to tests/data/ers97old_meek.ods diff --git a/tests/ers97.rs b/tests/ers97.rs index 3132024..5a38704 100644 --- a/tests/ers97.rs +++ b/tests/ers97.rs @@ -21,12 +21,13 @@ use opentally::numbers::Rational; use opentally::stv; #[test] -fn ers97_rational() { +fn ers97old_rational() { let stv_opts = stv::STVOptionsBuilder::default() .round_surplus_fractions(Some(2)) .round_values(Some(2)) .round_votes(Some(2)) .round_quota(Some(2)) + .quota(stv::QuotaType::DroopExact) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .quota_mode(stv::QuotaMode::ERS97) .surplus(stv::SurplusMethod::EG) @@ -37,5 +38,5 @@ fn ers97_rational() { .defer_surpluses(true) .build().unwrap(); - utils::read_validate_election::("tests/data/ers97.csv", "tests/data/ers97.blt", stv_opts, None, &["nt", "vre"]); + utils::read_validate_election::("tests/data/ers97old.csv", "tests/data/ers97old.blt", stv_opts, None, &["nt", "vre"]); } diff --git a/tests/meek.rs b/tests/meek.rs index 07e717a..fda58bf 100644 --- a/tests/meek.rs +++ b/tests/meek.rs @@ -21,30 +21,30 @@ use opentally::election::{CandidateState, CountState, Election}; use opentally::numbers::{Fixed, NativeFloat64, Number}; use opentally::stv; -// Compare ers97.blt count with result produced by 1987 Hill–Wichmann–Woodall reference implementation +// Compare ers97old.blt count with result produced by 1987 Hill–Wichmann–Woodall reference implementation #[test] -fn meek87_ers97_float64() { +fn meek87_ers97old_float64() { let stv_opts = stv::STVOptionsBuilder::default() .meek_surplus_tolerance("0.001%".to_string()) + .quota(stv::QuotaType::DroopExact) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .quota_mode(stv::QuotaMode::DynamicByTotal) .surplus(stv::SurplusMethod::Meek) .immediate_elect(false) .build().unwrap(); - utils::read_validate_election::("tests/data/ers97_meek.csv", "tests/data/ers97.blt", stv_opts, Some(2), &["exhausted", "quota"]); + utils::read_validate_election::("tests/data/ers97old_meek.csv", "tests/data/ers97old.blt", stv_opts, Some(2), &["exhausted", "quota"]); } -// Compare ers97.blt count with result produced by OpenSTV 1.7 "Meek STV" +// Compare ers97old.blt count with result produced by OpenSTV 1.7 "Meek STV" #[test] -fn meek06_ers97_fixed12() { +fn meek06_ers97old_fixed12() { let stv_opts = stv::STVOptionsBuilder::default() .round_surplus_fractions(Some(9)) .round_values(Some(9)) .round_votes(Some(9)) .round_quota(Some(9)) .meek_surplus_tolerance("0.0001".to_string()) - .quota(stv::QuotaType::Droop) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .quota_mode(stv::QuotaMode::DynamicByTotal) .surplus(stv::SurplusMethod::Meek) @@ -54,7 +54,7 @@ fn meek06_ers97_fixed12() { Fixed::set_dps(12); // Read BLT - let election: Election = Election::from_file("tests/data/ers97.blt").expect("Syntax Error"); + let election: Election = Election::from_file("tests/data/ers97old.blt").expect("Syntax Error"); // Initialise count state let mut state = CountState::new(&election); @@ -95,16 +95,15 @@ fn meek06_ers97_fixed12() { } } -// Compare ers97.blt count with result produced by OpenSTV 1.7 "New Zealand Meek STV" (same result as Hill 2006 implementation) +// Compare ers97old.blt count with result produced by OpenSTV 1.7 "New Zealand Meek STV" (same result as Hill 2006 implementation) #[test] -fn meeknz_ers97_fixed12() { +fn meeknz_ers97old_fixed12() { let stv_opts = stv::STVOptionsBuilder::default() .round_surplus_fractions(Some(9)) .round_values(Some(9)) .round_votes(Some(9)) .round_quota(Some(9)) .meek_surplus_tolerance("0.0001".to_string()) - .quota(stv::QuotaType::Droop) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .quota_mode(stv::QuotaMode::DynamicByTotal) .surplus(stv::SurplusMethod::Meek) @@ -115,7 +114,7 @@ fn meeknz_ers97_fixed12() { Fixed::set_dps(12); // Read BLT - let election: Election = Election::from_file("tests/data/ers97.blt").expect("Syntax Error"); + let election: Election = Election::from_file("tests/data/ers97old.blt").expect("Syntax Error"); // Initialise count state let mut state = CountState::new(&election); diff --git a/tests/minneapolis.rs b/tests/minneapolis.rs index fd3807c..cdf0c61 100644 --- a/tests/minneapolis.rs +++ b/tests/minneapolis.rs @@ -25,7 +25,6 @@ fn minneapolis_boe09_rational() { let stv_opts = stv::STVOptionsBuilder::default() .round_surplus_fractions(Some(4)) .round_quota(Some(0)) - .quota(stv::QuotaType::Droop) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .early_bulk_elect(true) .bulk_exclude(true) @@ -41,7 +40,6 @@ fn minneapolis_pal13_rational() { let stv_opts = stv::STVOptionsBuilder::default() .round_surplus_fractions(Some(4)) .round_quota(Some(0)) - .quota(stv::QuotaType::Droop) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .early_bulk_elect(true) .bulk_exclude(true) diff --git a/tests/prsa.rs b/tests/prsa.rs index c164797..e003044 100644 --- a/tests/prsa.rs +++ b/tests/prsa.rs @@ -27,7 +27,6 @@ fn prsa1_rational() { .round_values(Some(3)) .round_votes(Some(3)) .round_quota(Some(3)) - .quota(stv::QuotaType::Droop) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .surplus(stv::SurplusMethod::EG) .surplus_order(stv::SurplusOrder::ByOrder) diff --git a/tests/scotland.rs b/tests/scotland.rs index 74a0228..862b3b5 100644 --- a/tests/scotland.rs +++ b/tests/scotland.rs @@ -33,7 +33,6 @@ fn scotland_linn07_fixed5() { .round_quota(Some(0)) .sum_surplus_transfers(stv::SumSurplusTransfersMode::PerBallot) .normalise_ballots(true) - .quota(stv::QuotaType::Droop) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .early_bulk_elect(false) .pp_decimals(5) @@ -52,7 +51,6 @@ fn scotland_linn07_gfixed5() { .round_quota(Some(0)) .sum_surplus_transfers(stv::SumSurplusTransfersMode::PerBallot) .normalise_ballots(true) - .quota(stv::QuotaType::Droop) .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) .early_bulk_elect(false) .pp_decimals(5)