diff --git a/Cargo.lock b/Cargo.lock index 443bc79..9ef8534 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 0c703e7..c49bbec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/docs/options.md b/docs/options.md index 5f9be3c..b06def5 100644 --- a/docs/options.md +++ b/docs/options.md @@ -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. diff --git a/html/index.js b/html/index.js index 3abab4b..dffecd5 100644 --- a/html/index.js +++ b/html/index.js @@ -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'; diff --git a/src/main.rs b/src/main.rs index 9075a96..6eff379 100644 --- a/src/main.rs +++ b/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, diff --git a/src/stv/mod.rs b/src/stv/mod.rs index b5ca21f..61bbff4 100644 --- a/src/stv/mod.rs +++ b/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, + /// Round ballot values to specified decimal places + #[builder(default="None")] pub round_values: Option, + /// Round votes to specified decimal places + #[builder(default="None")] pub round_votes: Option, + /// Round quota to specified decimal places + #[builder(default="None")] pub round_quota: Option, + /// 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, + /// 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, + /// 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, - round_values: Option, - round_votes: Option, - round_quota: Option, - sum_surplus_transfers: &str, - meek_surplus_tolerance: &str, - normalise_ballots: bool, - quota: &str, - quota_criterion: &str, - quota_mode: &str, - ties: &Vec, - random_seed: &Option, - 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(&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> From 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> From 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> From 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> From 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> From 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> From 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> From 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> From 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 { diff --git a/src/stv/wasm.rs b/src/stv/wasm.rs index 063f58d..16fe446 100644 --- a/src/stv/wasm.rs +++ b/src/stv/wasm.rs @@ -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, round_quota: Option, 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, diff --git a/src/ties.rs b/src/ties.rs index 2bfe0c4..ab3a775 100644 --- a/src/ties.rs +++ b/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>(strs: Vec, mut random_seed: Option) -> Vec { + 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 { diff --git a/tests/act.rs b/tests/act.rs index 4090e45..e29747c 100644 --- a/tests/act.rs +++ b/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::("tests/data/ACT2020Kurrajong.csv", "tests/data/ACT2020Kurrajong.blt", stv_opts, Some(6), &["exhausted", "lbf"]); } diff --git a/tests/aec.rs b/tests/aec.rs index 550dad0..51a2179 100644 --- a/tests/aec.rs +++ b/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::(stages, records, election, stv_opts, None, &["exhausted", "lbf"]); } diff --git a/tests/cambridge.rs b/tests/cambridge.rs index 222a0d3..5702437 100644 --- a/tests/cambridge.rs +++ b/tests/cambridge.rs @@ -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::("tests/data/CambCC2003.csv", "tests/data/CambCC2003.blt", stv_opts, None, &["exhausted"]); } diff --git a/tests/constraints.rs b/tests/constraints.rs index 868a1c1..b800d05 100644 --- a/tests/constraints.rs +++ b/tests/constraints.rs @@ -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::(stages, records, election, stv_opts, None, &["nt", "vre"]); } diff --git a/tests/csm.rs b/tests/csm.rs index 20da147..4cca6d2 100644 --- a/tests/csm.rs +++ b/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::("tests/data/CSM15.csv", "tests/data/CSM15.blt", stv_opts, Some(6), &["quota"]); } diff --git a/tests/ers97.rs b/tests/ers97.rs index a371895..3132024 100644 --- a/tests/ers97.rs +++ b/tests/ers97.rs @@ -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::("tests/data/ers97.csv", "tests/data/ers97.blt", stv_opts, None, &["nt", "vre"]); } diff --git a/tests/meek.rs b/tests/meek.rs index 39548c0..bead2e8 100644 --- a/tests/meek.rs +++ b/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::("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 diff --git a/tests/prsa.rs b/tests/prsa.rs index 23644c1..c164797 100644 --- a/tests/prsa.rs +++ b/tests/prsa.rs @@ -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::("tests/data/prsa1.csv", "tests/data/prsa1.blt", stv_opts, None, &["exhausted", "lbf"]); } diff --git a/tests/scotland.rs b/tests/scotland.rs index 3a535ea..74a0228 100644 --- a/tests/scotland.rs +++ b/tests/scotland.rs @@ -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::(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::(stv_opts); }