Refactor tests specification using builder pattern
This commit is contained in:
parent
0800701960
commit
f3e4071886
134
Cargo.lock
generated
134
Cargo.lock
generated
@ -187,14 +187,81 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.14"
|
||||
name = "darling"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc7b9cef1e351660e5443924e4f43ab25fbbed3e9a5f052df3677deb4d6b320"
|
||||
checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d13202debe11181040ae9063d739fa32cfcaaebe2275fe387703460ae2365b30"
|
||||
dependencies = [
|
||||
"derive_builder_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66e616858f6187ed828df7c64a6d71720d83767a7f19740b2d1b6fe6327b36e5"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn",
|
||||
]
|
||||
|
||||
@ -246,6 +313,12 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.4"
|
||||
@ -316,6 +389,12 @@ dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.6.2"
|
||||
@ -480,6 +559,7 @@ dependencies = [
|
||||
"clap",
|
||||
"console_error_panic_hook",
|
||||
"csv",
|
||||
"derive_builder",
|
||||
"derive_more",
|
||||
"flate2",
|
||||
"git-version",
|
||||
@ -511,6 +591,15 @@ version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||
dependencies = [
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "1.0.8"
|
||||
@ -646,12 +735,39 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
|
||||
dependencies = [
|
||||
"pest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.126"
|
||||
@ -677,6 +793,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.72"
|
||||
@ -706,6 +828,12 @@ version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.7.1"
|
||||
|
@ -8,6 +8,7 @@ edition = "2018"
|
||||
crate-type = ["lib", "cdylib"]
|
||||
|
||||
[dependencies]
|
||||
derive_builder = "0.10.2"
|
||||
derive_more = "0.99.14"
|
||||
git-version = "0.3.4"
|
||||
ibig = "0.3.2"
|
||||
|
@ -93,7 +93,7 @@ Random sample methods are also supported, but also not recommended:
|
||||
* *Cincinnati (inclusive sample)*: During surplus transfers, a subset of the elected candidate's ballot papers, equal in size to the surplus, is examined.
|
||||
* *Hare (exclusive sample)*: During surplus transfers, a subset of the ballot papers received in the last transfer, equal in size to the surplus, is examined.
|
||||
|
||||
The use of a random sample method requires *Normalise ballots* to be enabled, and requires the *Quota criterion* to be set to *>=*.
|
||||
The use of a random sample method requires *Normalise ballots* to be enabled, and will usually be used with a *Quota criterion* set to *>=*.
|
||||
|
||||
In both random sample methods, 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*). This depends on the order of ballot papers in the BLT file, and is independent of the *Random seed* option.
|
||||
|
||||
|
@ -564,7 +564,7 @@ function changePreset() {
|
||||
document.getElementById('selQuota').value = 'droop';
|
||||
document.getElementById('selQuotaMode').value = 'static';
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = false; // TODO: Cambridge-style bulk exclusion
|
||||
document.getElementById('chkBulkExclusion').checked = false;
|
||||
document.getElementById('chkDeferSurpluses').checked = false;
|
||||
document.getElementById('chkSamplePerBallot').checked = true;
|
||||
document.getElementById('txtMinThreshold').value = '49';
|
||||
|
24
src/main.rs
24
src/main.rs
@ -19,6 +19,7 @@ use opentally::constraints::Constraints;
|
||||
use opentally::election::{CandidateState, CountState, Election};
|
||||
use opentally::numbers::{Fixed, GuardedFixed, NativeFloat64, Number, Rational};
|
||||
use opentally::stv::{self, STVOptions};
|
||||
use opentally::ties;
|
||||
|
||||
use clap::{AppSettings, Clap};
|
||||
|
||||
@ -265,18 +266,17 @@ where
|
||||
cmd_opts.round_values,
|
||||
cmd_opts.round_votes,
|
||||
cmd_opts.round_quota,
|
||||
&cmd_opts.sum_surplus_transfers,
|
||||
&cmd_opts.meek_surplus_tolerance,
|
||||
cmd_opts.sum_surplus_transfers.into(),
|
||||
cmd_opts.meek_surplus_tolerance.into(),
|
||||
cmd_opts.normalise_ballots,
|
||||
&cmd_opts.quota,
|
||||
&cmd_opts.quota_criterion,
|
||||
&cmd_opts.quota_mode,
|
||||
&cmd_opts.ties,
|
||||
&cmd_opts.random_seed,
|
||||
&cmd_opts.surplus,
|
||||
&cmd_opts.surplus_order,
|
||||
cmd_opts.quota.into(),
|
||||
cmd_opts.quota_criterion.into(),
|
||||
cmd_opts.quota_mode.into(),
|
||||
ties::from_strs(cmd_opts.ties, cmd_opts.random_seed),
|
||||
cmd_opts.surplus.into(),
|
||||
cmd_opts.surplus_order.into(),
|
||||
cmd_opts.transferable_only,
|
||||
&cmd_opts.exclusion,
|
||||
cmd_opts.exclusion.into(),
|
||||
cmd_opts.meek_nz_exclusion,
|
||||
cmd_opts.sample_per_ballot,
|
||||
!cmd_opts.no_early_bulk_elect,
|
||||
@ -284,8 +284,8 @@ where
|
||||
cmd_opts.defer_surpluses,
|
||||
cmd_opts.meek_immediate_elect,
|
||||
cmd_opts.min_threshold,
|
||||
cmd_opts.constraints.as_deref(),
|
||||
&cmd_opts.constraint_mode,
|
||||
cmd_opts.constraints,
|
||||
cmd_opts.constraint_mode.into(),
|
||||
cmd_opts.hide_excluded,
|
||||
cmd_opts.sort_votes,
|
||||
cmd_opts.pp_decimals,
|
||||
|
263
src/stv/mod.rs
263
src/stv/mod.rs
@ -34,6 +34,8 @@ use crate::election::{Candidate, CandidateState, CountCard, CountState, Vote};
|
||||
use crate::sharandom::SHARandom;
|
||||
use crate::ties::{self, TieStrategy};
|
||||
|
||||
use derive_builder::Builder;
|
||||
use derive_more::Constructor;
|
||||
use itertools::Itertools;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
@ -42,178 +44,118 @@ use std::fmt;
|
||||
use std::ops;
|
||||
|
||||
/// Options for conducting an STV count
|
||||
#[derive(Builder, Constructor)]
|
||||
pub struct STVOptions {
|
||||
/// Round surplus fractions to specified decimal places
|
||||
#[builder(default="None")]
|
||||
pub round_surplus_fractions: Option<usize>,
|
||||
|
||||
/// Round ballot values to specified decimal places
|
||||
#[builder(default="None")]
|
||||
pub round_values: Option<usize>,
|
||||
|
||||
/// Round votes to specified decimal places
|
||||
#[builder(default="None")]
|
||||
pub round_votes: Option<usize>,
|
||||
|
||||
/// Round quota to specified decimal places
|
||||
#[builder(default="None")]
|
||||
pub round_quota: Option<usize>,
|
||||
|
||||
/// How to calculate votes to credit to candidates in surplus transfers
|
||||
#[builder(default="SumSurplusTransfersMode::SingleStep")]
|
||||
pub sum_surplus_transfers: SumSurplusTransfersMode,
|
||||
|
||||
/// (Meek STV) Limit for stopping iteration of surplus distribution
|
||||
#[builder(default)]
|
||||
pub meek_surplus_tolerance: String,
|
||||
|
||||
/// Convert ballots with value >1 to multiple ballots of value 1 (used only for [STVOptions::describe])
|
||||
#[builder(default="false")]
|
||||
pub normalise_ballots: bool,
|
||||
|
||||
/// Quota type
|
||||
#[builder(default="QuotaType::DroopExact")]
|
||||
pub quota: QuotaType,
|
||||
|
||||
/// Whether to elect candidates on meeting (geq) or strictly exceeding (gt) the quota
|
||||
#[builder(default="QuotaCriterion::Greater")]
|
||||
pub quota_criterion: QuotaCriterion,
|
||||
|
||||
/// Whether to apply a form of progressive quota
|
||||
#[builder(default="QuotaMode::Static")]
|
||||
pub quota_mode: QuotaMode,
|
||||
|
||||
/// Tie-breaking method
|
||||
#[builder(default)]
|
||||
pub ties: Vec<TieStrategy>,
|
||||
|
||||
/// Method of surplus distributions
|
||||
#[builder(default="SurplusMethod::WIG")]
|
||||
pub surplus: SurplusMethod,
|
||||
|
||||
/// (Gregory STV) Order to distribute surpluses
|
||||
#[builder(default="SurplusOrder::BySize")]
|
||||
pub surplus_order: SurplusOrder,
|
||||
|
||||
/// (Gregory STV) Examine only transferable papers during surplus distributions
|
||||
#[builder(default="false")]
|
||||
pub transferable_only: bool,
|
||||
|
||||
/// (Gregory STV) Method of exclusions
|
||||
#[builder(default="ExclusionMethod::SingleStage")]
|
||||
pub exclusion: ExclusionMethod,
|
||||
|
||||
/// (Meek STV) NZ Meek STV behaviour: Iterate keep values one round before candidate exclusion
|
||||
#[builder(default="false")]
|
||||
pub meek_nz_exclusion: bool,
|
||||
|
||||
/// (Cincinnati/Hare) Sample-based methods: Check for candidate election after each individual ballot paper transfer
|
||||
#[builder(default="false")]
|
||||
pub sample_per_ballot: bool,
|
||||
|
||||
/// Bulk elect as soon as continuing candidates fill all remaining vacancies
|
||||
#[builder(default="true")]
|
||||
pub early_bulk_elect: bool,
|
||||
|
||||
/// Use bulk exclusion
|
||||
#[builder(default="false")]
|
||||
pub bulk_exclude: bool,
|
||||
|
||||
/// Defer surplus distributions if possible
|
||||
#[builder(default="false")]
|
||||
pub defer_surpluses: bool,
|
||||
|
||||
/// (Meek STV) Immediately elect candidates even if keep values have not converged
|
||||
#[builder(default="false")]
|
||||
pub meek_immediate_elect: bool,
|
||||
|
||||
/// On exclusion, exclude any candidate with this many votes or fewer
|
||||
#[builder(default="\"0\".to_string()")]
|
||||
pub min_threshold: String,
|
||||
|
||||
/// Path to constraints file (used only for [STVOptions::describe])
|
||||
#[builder(default="None")]
|
||||
pub constraints_path: Option<String>,
|
||||
|
||||
/// Mode of handling constraints
|
||||
#[builder(default="ConstraintMode::GuardDoom")]
|
||||
pub constraint_mode: ConstraintMode,
|
||||
|
||||
/// Hide excluded candidates from results report
|
||||
#[builder(default="false")]
|
||||
pub hide_excluded: bool,
|
||||
|
||||
/// Sort candidates by votes in results report
|
||||
#[builder(default="false")]
|
||||
pub sort_votes: bool,
|
||||
|
||||
/// Print votes to specified decimal places in results report
|
||||
#[builder(default="2")]
|
||||
pub pp_decimals: usize,
|
||||
}
|
||||
|
||||
impl STVOptions {
|
||||
/// Returns a new [STVOptions] based on arguments given as strings
|
||||
pub fn new(
|
||||
round_surplus_fractions: Option<usize>,
|
||||
round_values: Option<usize>,
|
||||
round_votes: Option<usize>,
|
||||
round_quota: Option<usize>,
|
||||
sum_surplus_transfers: &str,
|
||||
meek_surplus_tolerance: &str,
|
||||
normalise_ballots: bool,
|
||||
quota: &str,
|
||||
quota_criterion: &str,
|
||||
quota_mode: &str,
|
||||
ties: &Vec<String>,
|
||||
random_seed: &Option<String>,
|
||||
surplus: &str,
|
||||
surplus_order: &str,
|
||||
transferable_only: bool,
|
||||
exclusion: &str,
|
||||
meek_nz_exclusion: bool,
|
||||
sample_per_ballot: bool,
|
||||
early_bulk_elect: bool,
|
||||
bulk_exclude: bool,
|
||||
defer_surpluses: bool,
|
||||
meek_immediate_elect: bool,
|
||||
min_threshold: String,
|
||||
constraints_path: Option<&str>,
|
||||
constraint_mode: &str,
|
||||
hide_excluded: bool,
|
||||
sort_votes: bool,
|
||||
pp_decimals: usize,
|
||||
) -> Self {
|
||||
return STVOptions {
|
||||
round_surplus_fractions,
|
||||
round_values,
|
||||
round_votes,
|
||||
round_quota,
|
||||
sum_surplus_transfers: match sum_surplus_transfers {
|
||||
"single_step" => SumSurplusTransfersMode::SingleStep,
|
||||
"by_value" => SumSurplusTransfersMode::ByValue,
|
||||
"per_ballot" => SumSurplusTransfersMode::PerBallot,
|
||||
_ => panic!("Invalid --sum-transfers"),
|
||||
},
|
||||
meek_surplus_tolerance: meek_surplus_tolerance.to_string(),
|
||||
normalise_ballots,
|
||||
quota: match quota {
|
||||
"droop" => QuotaType::Droop,
|
||||
"hare" => QuotaType::Hare,
|
||||
"droop_exact" => QuotaType::DroopExact,
|
||||
"hare_exact" => QuotaType::HareExact,
|
||||
_ => panic!("Invalid --quota"),
|
||||
},
|
||||
quota_criterion: match quota_criterion {
|
||||
"geq" => QuotaCriterion::GreaterOrEqual,
|
||||
"gt" => QuotaCriterion::Greater,
|
||||
_ => panic!("Invalid --quota-criterion"),
|
||||
},
|
||||
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() {
|
||||
"forwards" => TieStrategy::Forwards,
|
||||
"backwards" => TieStrategy::Backwards,
|
||||
"random" => TieStrategy::Random(random_seed.as_ref().expect("Must provide a --random-seed if using --ties random").clone()),
|
||||
"prompt" => TieStrategy::Prompt,
|
||||
_ => panic!("Invalid --ties"),
|
||||
}).collect(),
|
||||
surplus: match surplus {
|
||||
"wig" => SurplusMethod::WIG,
|
||||
"uig" => SurplusMethod::UIG,
|
||||
"eg" => SurplusMethod::EG,
|
||||
"meek" => SurplusMethod::Meek,
|
||||
"cincinnati" => SurplusMethod::Cincinnati,
|
||||
"hare" => SurplusMethod::Hare,
|
||||
_ => panic!("Invalid --surplus"),
|
||||
},
|
||||
surplus_order: match surplus_order {
|
||||
"by_size" => SurplusOrder::BySize,
|
||||
"by_order" => SurplusOrder::ByOrder,
|
||||
_ => panic!("Invalid --surplus-order"),
|
||||
},
|
||||
transferable_only,
|
||||
exclusion: match exclusion {
|
||||
"single_stage" => ExclusionMethod::SingleStage,
|
||||
"by_value" => ExclusionMethod::ByValue,
|
||||
"by_source" => ExclusionMethod::BySource,
|
||||
"parcels_by_order" => ExclusionMethod::ParcelsByOrder,
|
||||
"wright" => ExclusionMethod::Wright,
|
||||
_ => panic!("Invalid --exclusion"),
|
||||
},
|
||||
meek_nz_exclusion,
|
||||
sample_per_ballot,
|
||||
early_bulk_elect,
|
||||
bulk_exclude,
|
||||
defer_surpluses,
|
||||
meek_immediate_elect,
|
||||
min_threshold,
|
||||
constraints_path: match constraints_path {
|
||||
Some(p) => Some(p.to_string()),
|
||||
None => None,
|
||||
},
|
||||
constraint_mode: match constraint_mode {
|
||||
"guard_doom" => ConstraintMode::GuardDoom,
|
||||
"rollback" => ConstraintMode::Rollback,
|
||||
_ => panic!("Invalid --constraint-mode"),
|
||||
},
|
||||
hide_excluded,
|
||||
sort_votes,
|
||||
pp_decimals,
|
||||
};
|
||||
}
|
||||
|
||||
/// Converts the [STVOptions] into CLI argument representation
|
||||
pub fn describe<N: Number>(&self) -> String {
|
||||
let mut flags = Vec::new();
|
||||
@ -263,7 +205,7 @@ impl STVOptions {
|
||||
if self.exclusion != ExclusionMethod::SingleStage { return Err(STVError::InvalidOptions("--surplus meek requires --exclusion single_stage")); }
|
||||
}
|
||||
if self.surplus == SurplusMethod::Cincinnati || self.surplus == SurplusMethod::Hare {
|
||||
if self.quota_criterion != QuotaCriterion::GreaterOrEqual { return Err(STVError::InvalidOptions("--surplus cincinnati and --surplus hare require --quota-criterion geq")); }
|
||||
if self.round_quota != Some(0) { return Err(STVError::InvalidOptions("--surplus cincinnati and --surplus hare require --round-quota 0")); }
|
||||
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
|
||||
@ -295,6 +237,17 @@ impl SumSurplusTransfersMode {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>> From<S> for SumSurplusTransfersMode {
|
||||
fn from(s: S) -> Self {
|
||||
match s.as_ref() {
|
||||
"single_step" => SumSurplusTransfersMode::SingleStep,
|
||||
"by_value" => SumSurplusTransfersMode::ByValue,
|
||||
"per_ballot" => SumSurplusTransfersMode::PerBallot,
|
||||
_ => panic!("Invalid --sum-transfers"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum of options for [STVOptions::quota]
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy)]
|
||||
@ -322,6 +275,18 @@ impl QuotaType {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>> From<S> for QuotaType {
|
||||
fn from(s: S) -> Self {
|
||||
match s.as_ref() {
|
||||
"droop" => QuotaType::Droop,
|
||||
"hare" => QuotaType::Hare,
|
||||
"droop_exact" => QuotaType::DroopExact,
|
||||
"hare_exact" => QuotaType::HareExact,
|
||||
_ => panic!("Invalid --quota"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum of options for [STVOptions::quota_criterion]
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy)]
|
||||
@ -343,6 +308,16 @@ impl QuotaCriterion {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>> From<S> for QuotaCriterion {
|
||||
fn from(s: S) -> Self {
|
||||
match s.as_ref() {
|
||||
"geq" => QuotaCriterion::GreaterOrEqual,
|
||||
"gt" => QuotaCriterion::Greater,
|
||||
_ => panic!("Invalid --quota-criterion"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum of options for [STVOptions::quota_mode]
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy)]
|
||||
@ -367,6 +342,17 @@ impl QuotaMode {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>> From<S> for QuotaMode {
|
||||
fn from(s: S) -> Self {
|
||||
match s.as_ref() {
|
||||
"static" => QuotaMode::Static,
|
||||
"ers97" => QuotaMode::ERS97,
|
||||
"ers76" => QuotaMode::ERS76,
|
||||
_ => panic!("Invalid --quota-mode"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum of options for [STVOptions::surplus]
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy)]
|
||||
@ -400,6 +386,20 @@ impl SurplusMethod {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>> From<S> for SurplusMethod {
|
||||
fn from(s: S) -> Self {
|
||||
match s.as_ref() {
|
||||
"wig" => SurplusMethod::WIG,
|
||||
"uig" => SurplusMethod::UIG,
|
||||
"eg" => SurplusMethod::EG,
|
||||
"meek" => SurplusMethod::Meek,
|
||||
"cincinnati" => SurplusMethod::Cincinnati,
|
||||
"hare" => SurplusMethod::Hare,
|
||||
_ => panic!("Invalid --surplus"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum of options for [STVOptions::surplus_order]
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy)]
|
||||
@ -421,6 +421,16 @@ impl SurplusOrder {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>> From<S> for SurplusOrder {
|
||||
fn from(s: S) -> Self {
|
||||
match s.as_ref() {
|
||||
"by_size" => SurplusOrder::BySize,
|
||||
"by_order" => SurplusOrder::ByOrder,
|
||||
_ => panic!("Invalid --surplus-order"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum of options for [STVOptions::exclusion]
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy)]
|
||||
@ -451,6 +461,19 @@ impl ExclusionMethod {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>> From<S> for ExclusionMethod {
|
||||
fn from(s: S) -> Self {
|
||||
match s.as_ref() {
|
||||
"single_stage" => ExclusionMethod::SingleStage,
|
||||
"by_value" => ExclusionMethod::ByValue,
|
||||
"by_source" => ExclusionMethod::BySource,
|
||||
"parcels_by_order" => ExclusionMethod::ParcelsByOrder,
|
||||
"wright" => ExclusionMethod::Wright,
|
||||
_ => panic!("Invalid --exclusion"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum of options for [STVOptions::constraint_mode]
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(PartialEq)]
|
||||
@ -471,6 +494,16 @@ impl ConstraintMode {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsRef<str>> From<S> for ConstraintMode {
|
||||
fn from(s: S) -> Self {
|
||||
match s.as_ref() {
|
||||
"guard_doom" => ConstraintMode::GuardDoom,
|
||||
"rollback" => ConstraintMode::Rollback,
|
||||
_ => panic!("Invalid --constraint-mode"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error during the STV count
|
||||
#[derive(Debug)]
|
||||
pub enum STVError {
|
||||
|
@ -22,6 +22,7 @@ use crate::constraints::Constraints;
|
||||
use crate::election::{CandidateState, CountState, Election};
|
||||
use crate::numbers::{Fixed, GuardedFixed, NativeFloat64, Number, Rational};
|
||||
use crate::stv;
|
||||
use crate::ties;
|
||||
|
||||
extern crate console_error_panic_hook;
|
||||
|
||||
@ -216,7 +217,7 @@ impl STVOptions {
|
||||
round_votes: Option<usize>,
|
||||
round_quota: Option<usize>,
|
||||
sum_surplus_transfers: &str,
|
||||
meek_surplus_tolerance: &str,
|
||||
meek_surplus_tolerance: String,
|
||||
normalise_ballots: bool,
|
||||
quota: &str,
|
||||
quota_criterion: &str,
|
||||
@ -243,18 +244,17 @@ impl STVOptions {
|
||||
round_values,
|
||||
round_votes,
|
||||
round_quota,
|
||||
sum_surplus_transfers,
|
||||
sum_surplus_transfers.into(),
|
||||
meek_surplus_tolerance,
|
||||
normalise_ballots,
|
||||
quota,
|
||||
quota_criterion,
|
||||
quota_mode,
|
||||
&ties.iter().map(|v| v.as_string().unwrap()).collect(),
|
||||
&Some(random_seed),
|
||||
surplus,
|
||||
surplus_order,
|
||||
quota.into(),
|
||||
quota_criterion.into(),
|
||||
quota_mode.into(),
|
||||
ties::from_strs(ties.iter().map(|v| v.as_string().unwrap()).collect(), Some(random_seed)),
|
||||
surplus.into(),
|
||||
surplus_order.into(),
|
||||
transferable_only,
|
||||
exclusion,
|
||||
exclusion.into(),
|
||||
meek_nz_exclusion,
|
||||
sample_per_ballot,
|
||||
early_bulk_elect,
|
||||
@ -262,8 +262,8 @@ impl STVOptions {
|
||||
defer_surpluses,
|
||||
meek_immediate_elect,
|
||||
min_threshold,
|
||||
constraints_path.as_deref(),
|
||||
constraint_mode,
|
||||
constraints_path,
|
||||
constraint_mode.into(),
|
||||
false,
|
||||
false,
|
||||
pp_decimals,
|
||||
|
13
src/ties.rs
13
src/ties.rs
@ -27,7 +27,7 @@ use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use std::io::{stdin, stdout, Write};
|
||||
|
||||
/// Strategy for breaking ties
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum TieStrategy {
|
||||
/// Break ties according to the candidate who first had more/fewer votes
|
||||
Forwards,
|
||||
@ -39,6 +39,17 @@ pub enum TieStrategy {
|
||||
Prompt,
|
||||
}
|
||||
|
||||
/// Get a [Vec] of [TieStrategy] based on string representations
|
||||
pub fn from_strs<S: AsRef<str>>(strs: Vec<S>, mut random_seed: Option<String>) -> Vec<TieStrategy> {
|
||||
strs.into_iter().map(|t| match t.as_ref() {
|
||||
"forwards" => TieStrategy::Forwards,
|
||||
"backwards" => TieStrategy::Backwards,
|
||||
"random" => TieStrategy::Random(random_seed.take().expect("Must provide a --random-seed if using --ties random")),
|
||||
"prompt" => TieStrategy::Prompt,
|
||||
_ => panic!("Invalid --ties"),
|
||||
}).collect()
|
||||
}
|
||||
|
||||
impl TieStrategy {
|
||||
/// Convert to CLI argument representation
|
||||
pub fn describe(&self) -> String {
|
||||
|
41
tests/act.rs
41
tests/act.rs
@ -22,34 +22,17 @@ use opentally::stv;
|
||||
|
||||
#[test]
|
||||
fn act_kurrajong20_rational() {
|
||||
let stv_opts = stv::STVOptions {
|
||||
round_surplus_fractions: None,
|
||||
round_values: None,
|
||||
round_votes: Some(6),
|
||||
round_quota: Some(0),
|
||||
sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep,
|
||||
meek_surplus_tolerance: String::new(),
|
||||
normalise_ballots: false,
|
||||
quota: stv::QuotaType::Droop,
|
||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||
quota_mode: stv::QuotaMode::Static,
|
||||
ties: vec![],
|
||||
surplus: stv::SurplusMethod::EG,
|
||||
surplus_order: stv::SurplusOrder::ByOrder,
|
||||
transferable_only: true,
|
||||
exclusion: stv::ExclusionMethod::ByValue,
|
||||
meek_nz_exclusion: false,
|
||||
sample_per_ballot: false,
|
||||
early_bulk_elect: false,
|
||||
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,
|
||||
sort_votes: false,
|
||||
pp_decimals: 2,
|
||||
};
|
||||
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)
|
||||
.transferable_only(true)
|
||||
.exclusion(stv::ExclusionMethod::ByValue)
|
||||
.early_bulk_elect(false)
|
||||
.build().unwrap();
|
||||
|
||||
utils::read_validate_election::<Rational>("tests/data/ACT2020Kurrajong.csv", "tests/data/ACT2020Kurrajong.blt", stv_opts, Some(6), &["exhausted", "lbf"]);
|
||||
}
|
||||
|
40
tests/aec.rs
40
tests/aec.rs
@ -59,34 +59,16 @@ fn aec_tas19_rational() {
|
||||
assert_eq!(election.candidates[i].name, *candidate);
|
||||
}
|
||||
|
||||
let stv_opts = stv::STVOptions {
|
||||
round_surplus_fractions: None,
|
||||
round_values: None,
|
||||
round_votes: Some(0),
|
||||
round_quota: Some(0),
|
||||
sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep,
|
||||
meek_surplus_tolerance: String::new(),
|
||||
normalise_ballots: false,
|
||||
quota: stv::QuotaType::Droop,
|
||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||
quota_mode: stv::QuotaMode::Static,
|
||||
ties: vec![],
|
||||
surplus: stv::SurplusMethod::UIG,
|
||||
surplus_order: stv::SurplusOrder::ByOrder,
|
||||
transferable_only: false,
|
||||
exclusion: stv::ExclusionMethod::ByValue,
|
||||
meek_nz_exclusion: false,
|
||||
sample_per_ballot: false,
|
||||
early_bulk_elect: true,
|
||||
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,
|
||||
sort_votes: false,
|
||||
pp_decimals: 2,
|
||||
};
|
||||
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)
|
||||
.exclusion(stv::ExclusionMethod::ByValue)
|
||||
.bulk_exclude(true)
|
||||
.build().unwrap();
|
||||
|
||||
utils::validate_election::<Rational>(stages, records, election, stv_opts, None, &["exhausted", "lbf"]);
|
||||
}
|
||||
|
@ -22,34 +22,17 @@ 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,
|
||||
sample_per_ballot: true,
|
||||
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,
|
||||
};
|
||||
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)
|
||||
.sample_per_ballot(true)
|
||||
.early_bulk_elect(false)
|
||||
.min_threshold("49".to_string())
|
||||
.build().unwrap();
|
||||
|
||||
utils::read_validate_election::<Rational>("tests/data/CambCC2003.csv", "tests/data/CambCC2003.blt", stv_opts, None, &["exhausted"]);
|
||||
}
|
||||
|
@ -38,35 +38,20 @@ fn prsa1_constr1_rational() {
|
||||
let lines = file_reader.lines();
|
||||
election.constraints = Some(Constraints::from_con(lines.map(|r| r.expect("IO Error").to_string()).into_iter()));
|
||||
|
||||
let stv_opts = stv::STVOptions {
|
||||
round_surplus_fractions: Some(3),
|
||||
round_values: Some(3),
|
||||
round_votes: Some(3),
|
||||
round_quota: Some(3),
|
||||
sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep,
|
||||
meek_surplus_tolerance: String::new(),
|
||||
normalise_ballots: false,
|
||||
quota: stv::QuotaType::Droop,
|
||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||
quota_mode: stv::QuotaMode::Static,
|
||||
ties: vec![],
|
||||
surplus: stv::SurplusMethod::EG,
|
||||
surplus_order: stv::SurplusOrder::ByOrder,
|
||||
transferable_only: true,
|
||||
exclusion: stv::ExclusionMethod::ParcelsByOrder,
|
||||
meek_nz_exclusion: false,
|
||||
sample_per_ballot: false,
|
||||
early_bulk_elect: false,
|
||||
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,
|
||||
sort_votes: false,
|
||||
pp_decimals: 2,
|
||||
};
|
||||
let stv_opts = stv::STVOptionsBuilder::default()
|
||||
.round_surplus_fractions(Some(3))
|
||||
.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)
|
||||
.transferable_only(true)
|
||||
.exclusion(stv::ExclusionMethod::ParcelsByOrder)
|
||||
.early_bulk_elect(false)
|
||||
.constraints_path(Some("tests/data/prsa1_constr1.con".to_string()))
|
||||
.build().unwrap();
|
||||
|
||||
// Initialise count state
|
||||
let mut state = CountState::new(&election);
|
||||
@ -103,35 +88,20 @@ fn prsa1_constr2_rational() {
|
||||
let lines = file_reader.lines();
|
||||
election.constraints = Some(Constraints::from_con(lines.map(|r| r.expect("IO Error").to_string()).into_iter()));
|
||||
|
||||
let stv_opts = stv::STVOptions {
|
||||
round_surplus_fractions: Some(3),
|
||||
round_values: Some(3),
|
||||
round_votes: Some(3),
|
||||
round_quota: Some(3),
|
||||
sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep,
|
||||
meek_surplus_tolerance: String::new(),
|
||||
normalise_ballots: false,
|
||||
quota: stv::QuotaType::Droop,
|
||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||
quota_mode: stv::QuotaMode::Static,
|
||||
ties: vec![],
|
||||
surplus: stv::SurplusMethod::EG,
|
||||
surplus_order: stv::SurplusOrder::ByOrder,
|
||||
transferable_only: true,
|
||||
exclusion: stv::ExclusionMethod::ParcelsByOrder,
|
||||
meek_nz_exclusion: false,
|
||||
sample_per_ballot: false,
|
||||
early_bulk_elect: false,
|
||||
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,
|
||||
sort_votes: false,
|
||||
pp_decimals: 2,
|
||||
};
|
||||
let stv_opts = stv::STVOptionsBuilder::default()
|
||||
.round_surplus_fractions(Some(3))
|
||||
.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)
|
||||
.transferable_only(true)
|
||||
.exclusion(stv::ExclusionMethod::ParcelsByOrder)
|
||||
.early_bulk_elect(false)
|
||||
.constraints_path(Some("tests/data/prsa1_constr1.con".to_string()))
|
||||
.build().unwrap();
|
||||
|
||||
// Initialise count state
|
||||
let mut state = CountState::new(&election);
|
||||
@ -168,35 +138,20 @@ fn prsa1_constr3_rational() {
|
||||
let lines = file_reader.lines();
|
||||
election.constraints = Some(Constraints::from_con(lines.map(|r| r.expect("IO Error").to_string()).into_iter()));
|
||||
|
||||
let stv_opts = stv::STVOptions {
|
||||
round_surplus_fractions: Some(3),
|
||||
round_values: Some(3),
|
||||
round_votes: Some(3),
|
||||
round_quota: Some(3),
|
||||
sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep,
|
||||
meek_surplus_tolerance: String::new(),
|
||||
normalise_ballots: false,
|
||||
quota: stv::QuotaType::Droop,
|
||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||
quota_mode: stv::QuotaMode::Static,
|
||||
ties: vec![],
|
||||
surplus: stv::SurplusMethod::EG,
|
||||
surplus_order: stv::SurplusOrder::ByOrder,
|
||||
transferable_only: true,
|
||||
exclusion: stv::ExclusionMethod::ParcelsByOrder,
|
||||
meek_nz_exclusion: false,
|
||||
sample_per_ballot: false,
|
||||
early_bulk_elect: false,
|
||||
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,
|
||||
sort_votes: false,
|
||||
pp_decimals: 2,
|
||||
};
|
||||
let stv_opts = stv::STVOptionsBuilder::default()
|
||||
.round_surplus_fractions(Some(3))
|
||||
.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)
|
||||
.transferable_only(true)
|
||||
.exclusion(stv::ExclusionMethod::ParcelsByOrder)
|
||||
.early_bulk_elect(false)
|
||||
.constraints_path(Some("tests/data/prsa1_constr1.con".to_string()))
|
||||
.build().unwrap();
|
||||
|
||||
// Initialise count state
|
||||
let mut state = CountState::new(&election);
|
||||
@ -245,34 +200,21 @@ fn ers97_cantbulkexclude_rational() {
|
||||
let lines = file_reader.lines();
|
||||
election.constraints = Some(Constraints::from_con(lines.map(|r| r.expect("IO Error").to_string()).into_iter()));
|
||||
|
||||
let stv_opts = stv::STVOptions {
|
||||
round_surplus_fractions: Some(2),
|
||||
round_values: Some(2),
|
||||
round_votes: Some(2),
|
||||
round_quota: Some(2),
|
||||
sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep,
|
||||
meek_surplus_tolerance: String::new(),
|
||||
normalise_ballots: false,
|
||||
quota: stv::QuotaType::DroopExact,
|
||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||
quota_mode: stv::QuotaMode::ERS97,
|
||||
ties: vec![],
|
||||
surplus: stv::SurplusMethod::EG,
|
||||
surplus_order: stv::SurplusOrder::BySize,
|
||||
transferable_only: true,
|
||||
exclusion: stv::ExclusionMethod::ByValue,
|
||||
meek_nz_exclusion: false,
|
||||
sample_per_ballot: false,
|
||||
early_bulk_elect: false,
|
||||
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,
|
||||
sort_votes: false,
|
||||
pp_decimals: 2,
|
||||
};
|
||||
let stv_opts = stv::STVOptionsBuilder::default()
|
||||
.round_surplus_fractions(Some(2))
|
||||
.round_values(Some(2))
|
||||
.round_votes(Some(2))
|
||||
.round_quota(Some(2))
|
||||
.quota_criterion(stv::QuotaCriterion::GreaterOrEqual)
|
||||
.quota_mode(stv::QuotaMode::ERS97)
|
||||
.surplus(stv::SurplusMethod::EG)
|
||||
.transferable_only(true)
|
||||
.exclusion(stv::ExclusionMethod::ByValue)
|
||||
.early_bulk_elect(false)
|
||||
.bulk_exclude(true)
|
||||
.defer_surpluses(true)
|
||||
.constraints_path(Some("tests/data/ers97_cantbulkexclude".to_string()))
|
||||
.build().unwrap();
|
||||
|
||||
utils::validate_election::<Rational>(stages, records, election, stv_opts, None, &["nt", "vre"]);
|
||||
}
|
||||
|
38
tests/csm.rs
38
tests/csm.rs
@ -22,34 +22,14 @@ use opentally::stv;
|
||||
|
||||
#[test]
|
||||
fn csm15_float64() {
|
||||
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: false,
|
||||
quota: stv::QuotaType::Droop,
|
||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||
quota_mode: stv::QuotaMode::Static,
|
||||
ties: vec![],
|
||||
surplus: stv::SurplusMethod::WIG,
|
||||
surplus_order: stv::SurplusOrder::BySize,
|
||||
transferable_only: false,
|
||||
exclusion: stv::ExclusionMethod::Wright,
|
||||
meek_nz_exclusion: false,
|
||||
sample_per_ballot: false,
|
||||
early_bulk_elect: false, // Required for validation
|
||||
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,
|
||||
sort_votes: false,
|
||||
pp_decimals: 2,
|
||||
};
|
||||
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)
|
||||
.bulk_exclude(true)
|
||||
.build().unwrap();
|
||||
|
||||
utils::read_validate_election::<NativeFloat64>("tests/data/CSM15.csv", "tests/data/CSM15.blt", stv_opts, Some(6), &["quota"]);
|
||||
}
|
||||
|
@ -22,34 +22,20 @@ use opentally::stv;
|
||||
|
||||
#[test]
|
||||
fn ers97_rational() {
|
||||
let stv_opts = stv::STVOptions {
|
||||
round_surplus_fractions: Some(2),
|
||||
round_values: Some(2),
|
||||
round_votes: Some(2),
|
||||
round_quota: Some(2),
|
||||
sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep,
|
||||
meek_surplus_tolerance: String::new(),
|
||||
normalise_ballots: false,
|
||||
quota: stv::QuotaType::DroopExact,
|
||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||
quota_mode: stv::QuotaMode::ERS97,
|
||||
ties: vec![],
|
||||
surplus: stv::SurplusMethod::EG,
|
||||
surplus_order: stv::SurplusOrder::BySize,
|
||||
transferable_only: true,
|
||||
exclusion: stv::ExclusionMethod::ByValue,
|
||||
meek_nz_exclusion: false,
|
||||
sample_per_ballot: false,
|
||||
early_bulk_elect: false,
|
||||
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,
|
||||
sort_votes: false,
|
||||
pp_decimals: 2,
|
||||
};
|
||||
let stv_opts = stv::STVOptionsBuilder::default()
|
||||
.round_surplus_fractions(Some(2))
|
||||
.round_values(Some(2))
|
||||
.round_votes(Some(2))
|
||||
.round_quota(Some(2))
|
||||
.quota_criterion(stv::QuotaCriterion::GreaterOrEqual)
|
||||
.quota_mode(stv::QuotaMode::ERS97)
|
||||
.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::<Rational>("tests/data/ers97.csv", "tests/data/ers97.blt", stv_opts, None, &["nt", "vre"]);
|
||||
}
|
||||
|
120
tests/meek.rs
120
tests/meek.rs
@ -24,70 +24,31 @@ use opentally::stv;
|
||||
// Compare ers97.blt count with result produced by 1987 Hill–Wichmann–Woodall reference implementation
|
||||
#[test]
|
||||
fn meek87_ers97_float64() {
|
||||
let stv_opts = stv::STVOptions {
|
||||
round_surplus_fractions: None,
|
||||
round_values: None,
|
||||
round_votes: None,
|
||||
round_quota: None,
|
||||
sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep,
|
||||
meek_surplus_tolerance: String::from("0.001%"),
|
||||
normalise_ballots: false,
|
||||
quota: stv::QuotaType::DroopExact,
|
||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||
quota_mode: stv::QuotaMode::Static,
|
||||
ties: vec![],
|
||||
surplus: stv::SurplusMethod::Meek,
|
||||
surplus_order: stv::SurplusOrder::BySize,
|
||||
transferable_only: false,
|
||||
exclusion: stv::ExclusionMethod::SingleStage,
|
||||
meek_nz_exclusion: false,
|
||||
sample_per_ballot: false,
|
||||
early_bulk_elect: true,
|
||||
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,
|
||||
sort_votes: false,
|
||||
pp_decimals: 2,
|
||||
};
|
||||
let stv_opts = stv::STVOptionsBuilder::default()
|
||||
.meek_surplus_tolerance("0.001%".to_string())
|
||||
.quota_criterion(stv::QuotaCriterion::GreaterOrEqual)
|
||||
.surplus(stv::SurplusMethod::Meek)
|
||||
.build().unwrap();
|
||||
|
||||
utils::read_validate_election::<NativeFloat64>("tests/data/ers97_meek.csv", "tests/data/ers97.blt", stv_opts, Some(2), &["exhausted", "quota"]);
|
||||
}
|
||||
|
||||
// Compare ers97.blt count with result produced by OpenSTV 1.7 "Meek STV"
|
||||
#[test]
|
||||
fn meek06_ers97_fixed12() {
|
||||
let stv_opts = stv::STVOptions {
|
||||
round_surplus_fractions: Some(9),
|
||||
round_values: Some(9),
|
||||
round_votes: Some(9),
|
||||
round_quota: Some(9),
|
||||
sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep,
|
||||
meek_surplus_tolerance: String::from("0.0001"),
|
||||
normalise_ballots: false,
|
||||
quota: stv::QuotaType::Droop,
|
||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||
quota_mode: stv::QuotaMode::Static,
|
||||
ties: vec![],
|
||||
surplus: stv::SurplusMethod::Meek,
|
||||
surplus_order: stv::SurplusOrder::BySize,
|
||||
transferable_only: false,
|
||||
exclusion: stv::ExclusionMethod::SingleStage,
|
||||
meek_nz_exclusion: false,
|
||||
sample_per_ballot: false,
|
||||
early_bulk_elect: true,
|
||||
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,
|
||||
sort_votes: false,
|
||||
pp_decimals: 2,
|
||||
};
|
||||
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)
|
||||
.surplus(stv::SurplusMethod::Meek)
|
||||
.defer_surpluses(true)
|
||||
.meek_immediate_elect(true)
|
||||
.build().unwrap();
|
||||
|
||||
Fixed::set_dps(12);
|
||||
|
||||
// Read BLT
|
||||
@ -135,35 +96,20 @@ 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)
|
||||
#[test]
|
||||
fn meeknz_ers97_fixed12() {
|
||||
let stv_opts = stv::STVOptions {
|
||||
round_surplus_fractions: Some(9),
|
||||
round_values: Some(9),
|
||||
round_votes: Some(9),
|
||||
round_quota: Some(9),
|
||||
sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep,
|
||||
meek_surplus_tolerance: String::from("0.0001"),
|
||||
normalise_ballots: false,
|
||||
quota: stv::QuotaType::Droop,
|
||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||
quota_mode: stv::QuotaMode::Static,
|
||||
ties: vec![],
|
||||
surplus: stv::SurplusMethod::Meek,
|
||||
surplus_order: stv::SurplusOrder::BySize,
|
||||
transferable_only: false,
|
||||
exclusion: stv::ExclusionMethod::SingleStage,
|
||||
meek_nz_exclusion: true,
|
||||
sample_per_ballot: false,
|
||||
early_bulk_elect: true,
|
||||
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,
|
||||
sort_votes: false,
|
||||
pp_decimals: 2,
|
||||
};
|
||||
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)
|
||||
.surplus(stv::SurplusMethod::Meek)
|
||||
.meek_nz_exclusion(true)
|
||||
.defer_surpluses(true)
|
||||
.meek_immediate_elect(true)
|
||||
.build().unwrap();
|
||||
|
||||
Fixed::set_dps(12);
|
||||
|
||||
// Read BLT
|
||||
|
@ -22,34 +22,19 @@ use opentally::stv;
|
||||
|
||||
#[test]
|
||||
fn prsa1_rational() {
|
||||
let stv_opts = stv::STVOptions {
|
||||
round_surplus_fractions: Some(3),
|
||||
round_values: Some(3),
|
||||
round_votes: Some(3),
|
||||
round_quota: Some(3),
|
||||
sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep,
|
||||
meek_surplus_tolerance: String::new(),
|
||||
normalise_ballots: false,
|
||||
quota: stv::QuotaType::Droop,
|
||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||
quota_mode: stv::QuotaMode::Static,
|
||||
ties: vec![],
|
||||
surplus: stv::SurplusMethod::EG,
|
||||
surplus_order: stv::SurplusOrder::ByOrder,
|
||||
transferable_only: true,
|
||||
exclusion: stv::ExclusionMethod::ParcelsByOrder,
|
||||
meek_nz_exclusion: false,
|
||||
sample_per_ballot: false,
|
||||
early_bulk_elect: false, // Required for validation
|
||||
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,
|
||||
sort_votes: false,
|
||||
pp_decimals: 2,
|
||||
};
|
||||
let stv_opts = stv::STVOptionsBuilder::default()
|
||||
.round_surplus_fractions(Some(3))
|
||||
.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)
|
||||
.transferable_only(true)
|
||||
.exclusion(stv::ExclusionMethod::ParcelsByOrder)
|
||||
.early_bulk_elect(false)
|
||||
.build().unwrap();
|
||||
|
||||
utils::read_validate_election::<Rational>("tests/data/prsa1.csv", "tests/data/prsa1.blt", stv_opts, None, &["exhausted", "lbf"]);
|
||||
}
|
||||
|
@ -26,70 +26,38 @@ use std::ops;
|
||||
|
||||
#[test]
|
||||
fn scotland_linn07_fixed5() {
|
||||
let stv_opts = stv::STVOptions {
|
||||
round_surplus_fractions: Some(5),
|
||||
round_values: Some(5),
|
||||
round_votes: Some(5),
|
||||
round_quota: Some(0),
|
||||
sum_surplus_transfers: stv::SumSurplusTransfersMode::PerBallot,
|
||||
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::WIG,
|
||||
surplus_order: stv::SurplusOrder::BySize,
|
||||
transferable_only: false,
|
||||
exclusion: stv::ExclusionMethod::SingleStage,
|
||||
meek_nz_exclusion: false,
|
||||
sample_per_ballot: false,
|
||||
early_bulk_elect: false,
|
||||
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,
|
||||
sort_votes: false,
|
||||
pp_decimals: 5,
|
||||
};
|
||||
let stv_opts = stv::STVOptionsBuilder::default()
|
||||
.round_surplus_fractions(Some(5))
|
||||
.round_values(Some(5))
|
||||
.round_votes(Some(5))
|
||||
.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)
|
||||
.build().unwrap();
|
||||
|
||||
Fixed::set_dps(5);
|
||||
scotland_linn07::<Fixed>(stv_opts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scotland_linn07_gfixed5() {
|
||||
let stv_opts = stv::STVOptions {
|
||||
round_surplus_fractions: Some(5),
|
||||
round_values: Some(5),
|
||||
round_votes: Some(5),
|
||||
round_quota: Some(0),
|
||||
sum_surplus_transfers: stv::SumSurplusTransfersMode::PerBallot,
|
||||
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::WIG,
|
||||
surplus_order: stv::SurplusOrder::BySize,
|
||||
transferable_only: false,
|
||||
exclusion: stv::ExclusionMethod::SingleStage,
|
||||
meek_nz_exclusion: false,
|
||||
sample_per_ballot: false,
|
||||
early_bulk_elect: false,
|
||||
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,
|
||||
sort_votes: false,
|
||||
pp_decimals: 5,
|
||||
};
|
||||
let stv_opts = stv::STVOptionsBuilder::default()
|
||||
.round_surplus_fractions(Some(5))
|
||||
.round_values(Some(5))
|
||||
.round_votes(Some(5))
|
||||
.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)
|
||||
.build().unwrap();
|
||||
|
||||
GuardedFixed::set_dps(5);
|
||||
scotland_linn07::<GuardedFixed>(stv_opts);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user