From 2ea8b4b757ffe2bca86916ecc4fba078fd5a7f04 Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Wed, 16 Jun 2021 20:28:03 +1000 Subject: [PATCH] Add tests for Meek STV --- src/numbers/fixed.rs | 2 +- src/numbers/gfixed.rs | 2 +- src/numbers/mod.rs | 2 +- src/numbers/native.rs | 2 +- src/numbers/rational_num.rs | 2 +- src/numbers/rational_rug.rs | 2 +- tests/aec.rs | 3 +- tests/data/ers97_meek.csv | 15 ++++++ tests/data/ers97_meek.ods | Bin 0 -> 14692 bytes tests/ers97.rs | 95 +----------------------------------- tests/meek.rs | 45 +++++++++++++++++ tests/prsa.rs | 2 +- tests/utils/mod.rs | 73 +++++++++++++++++++-------- 13 files changed, 123 insertions(+), 122 deletions(-) create mode 100644 tests/data/ers97_meek.csv create mode 100644 tests/data/ers97_meek.ods create mode 100644 tests/meek.rs diff --git a/src/numbers/fixed.rs b/src/numbers/fixed.rs index 01bf8c9..013f951 100644 --- a/src/numbers/fixed.rs +++ b/src/numbers/fixed.rs @@ -38,7 +38,7 @@ fn get_factor() -> &'static IBig { } /// Fixed-point number -#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct Fixed(IBig); impl Fixed { diff --git a/src/numbers/gfixed.rs b/src/numbers/gfixed.rs index 22d0e54..45ab786 100644 --- a/src/numbers/gfixed.rs +++ b/src/numbers/gfixed.rs @@ -44,7 +44,7 @@ fn get_factor_cmp() -> &'static IBig { } /// Guarded fixed-point number -#[derive(Clone, Eq)] +#[derive(Clone, Debug, Eq)] pub struct GuardedFixed(IBig); impl GuardedFixed { diff --git a/src/numbers/mod.rs b/src/numbers/mod.rs index c1e115f..37d317e 100644 --- a/src/numbers/mod.rs +++ b/src/numbers/mod.rs @@ -45,7 +45,7 @@ pub trait Assign { /// Trait for OpenTally numeric representations //pub trait Number: NumRef + NumAssignRef + PartialOrd + Assign + Clone + fmt::Display where for<'a> &'a Self: RefNum<&'a Self> { pub trait Number: - NumRef + NumAssignRef + ops::Neg + Ord + Assign + From + From + Clone + fmt::Display + NumRef + NumAssignRef + ops::Neg + Ord + Assign + From + From + Clone + fmt::Debug + fmt::Display where for<'a> Self: Assign<&'a Self> { diff --git a/src/numbers/native.rs b/src/numbers/native.rs index e036db5..e6de00f 100644 --- a/src/numbers/native.rs +++ b/src/numbers/native.rs @@ -27,7 +27,7 @@ use std::ops; type ImplType = f64; /// Native 64-bit floating-point number -#[derive(Clone, Display, PartialEq, PartialOrd)] +#[derive(Clone, Debug, Display, PartialEq, PartialOrd)] pub struct NativeFloat64(ImplType); impl Number for NativeFloat64 { diff --git a/src/numbers/rational_num.rs b/src/numbers/rational_num.rs index 23e6c8d..69438a2 100644 --- a/src/numbers/rational_num.rs +++ b/src/numbers/rational_num.rs @@ -26,7 +26,7 @@ use std::ops; type RatioBase = num_bigint::BigInt; type RatioType = num_rational::BigRational; -#[derive(Clone, PartialEq, PartialOrd)] +#[derive(Clone, Debug, PartialEq, PartialOrd)] pub struct Rational(RatioType); impl Number for Rational { diff --git a/src/numbers/rational_rug.rs b/src/numbers/rational_rug.rs index 9252137..513a996 100644 --- a/src/numbers/rational_rug.rs +++ b/src/numbers/rational_rug.rs @@ -26,7 +26,7 @@ use std::fmt; use std::ops; /// Rational number -#[derive(Clone, PartialEq, PartialOrd)] +#[derive(Clone, Debug, PartialEq, PartialOrd)] pub struct Rational(rug::Rational); impl Number for Rational { diff --git a/tests/aec.rs b/tests/aec.rs index f5e4080..271dfe6 100644 --- a/tests/aec.rs +++ b/tests/aec.rs @@ -73,6 +73,5 @@ fn aec_tas19_rational() { defer_surpluses: false, pp_decimals: 2, }; - - utils::validate_election(stages, records, election, stv_opts); + utils::validate_election(stages, records, election, stv_opts, None, &["exhausted", "lbf"]); } diff --git a/tests/data/ers97_meek.csv b/tests/data/ers97_meek.csv new file mode 100644 index 0000000..b25c65c --- /dev/null +++ b/tests/data/ers97_meek.csv @@ -0,0 +1,15 @@ +Stage:,1,,2,,4,,6,,9,,11, +Comment:,First preferences,,Surpluses distributed,,Surpluses distributed,,Surpluses distributed,,Surpluses distributed,,Surpluses distributed, +Smith,134,EL,107.26,EL,106.96,EL,104.42,EL,101.58,EL,73,EL +Carpenter,81,,87.98,,88.47,,97.71,,101.58,EL,73,EL +Wright,27,,31.99,,32.34,,34.97,,0,EX,0,EX +Glazier,24,,30.19,,30.62,,0,EX,0,EX,0,EX +Duke,105,,106.6,,106.96,EL,104.42,EL,101.58,EL,73,EL +Prince,91,,91,,91,,92.45,,97.07,,73,EL +Baron,64,,64,,64,,64.03,,67.24,,0,EX +Abbot,59,,59.8,,64.85,,66,,68.92,,70.83, +Vicar,55,,55,,69.21,,70.99,,73.27,,75.16,EL +Monk,23,,23.4,,0,EX,0,EX,0,EX,0,EX +Freeman,90,,93.59,,94.27,,95.97,,99.81,,73,EL +Exhausted,0,,2.2,,4.31,,22.05,,41.95,,242.01, +Quota,107.57,,107.26,,106.96,,104.42,,101.58,,73, diff --git a/tests/data/ers97_meek.ods b/tests/data/ers97_meek.ods new file mode 100644 index 0000000000000000000000000000000000000000..31940a059788f7b659a8e71106067918a5d29b19 GIT binary patch literal 14692 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 literal 0 HcmV?d00001 diff --git a/tests/ers97.rs b/tests/ers97.rs index 9a93396..e94620c 100644 --- a/tests/ers97.rs +++ b/tests/ers97.rs @@ -17,15 +17,9 @@ mod utils; -use opentally::election::{CandidateState, CountState, Election}; use opentally::numbers::Rational; use opentally::stv; -use csv::StringRecord; - -use std::fs::File; -use std::io::{self, BufRead}; - #[test] fn ers97_rational() { let stv_opts = stv::STVOptions { @@ -47,92 +41,5 @@ fn ers97_rational() { defer_surpluses: true, pp_decimals: 2, }; - - // --------------------------------------------- - // Custom implementation due to nontransferables - // and vote required for election - - // Read CSV file - let reader = csv::ReaderBuilder::new() - .has_headers(false) - .from_path("tests/data/ers97.csv") - .expect("IO Error"); - let records: Vec = reader.into_records().map(|r| r.expect("Syntax Error")).collect(); - - let mut candidates: Vec<&str> = records.iter().skip(2).map(|r| &r[0]).collect(); - // Remove NT/VRE rows - candidates.truncate(candidates.len() - 2); - - // TODO: Validate candidate names - - let stages: Vec = records.first().unwrap().iter().skip(1).step_by(2).map(|s| s.parse().unwrap()).collect(); - - // Read BLT - let file = File::open("tests/data/ers97.blt").expect("IO Error"); - let file_reader = io::BufReader::new(file); - let lines = file_reader.lines(); - - let election: Election = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter()); - - // Initialise count state - let mut state = CountState::new(&election); - - // Distribute first preferences - stv::count_init(&mut state, &stv_opts); - let mut stage_num = 1; - - for (idx, stage) in stages.into_iter().enumerate() { - while stage_num < stage { - // Step through stages - // Assert count not yet done - assert_eq!(stv::count_one_stage(&mut state, &stv_opts).unwrap(), false); - stage_num += 1; - } - - println!("Col at idx {}", idx); - - let mut candidate_votes: Vec> = records.iter().skip(2) - .map(|r| - if r[idx*2 + 1].len() > 0 { - Some(Rational::from(r[idx*2 + 1].parse::().expect("Syntax Error"))) - } else { - None - }) - .collect(); - - // Validate NT/VRE - let vre_votes = candidate_votes.pop().unwrap(); - let nt_votes = candidate_votes.pop().unwrap(); - - assert!(&state.exhausted.votes + &state.loss_fraction.votes == nt_votes.unwrap()); - if let Some(v) = vre_votes { - assert!(state.vote_required_election.as_ref().unwrap() == &v); - } - - // Remove NT/VRE rows - candidate_votes.truncate(candidate_votes.len() - 2); - - // Validate candidate votes - for (candidate, votes) in state.election.candidates.iter().zip(candidate_votes) { - let count_card = state.candidates.get(candidate).unwrap(); - assert!(&count_card.votes == votes.as_ref().unwrap(), "Failed to validate votes for candidate {}. Expected {:}, got {:}", candidate.name, votes.unwrap(), count_card.votes); - } - - // Validate candidate states - let mut candidate_states: Vec<&str> = records.iter().skip(2).map(|r| &r[idx*2 + 2]).collect(); - candidate_states.truncate(candidate_states.len() - 2); - - for (candidate, candidate_state) in state.election.candidates.iter().zip(candidate_states) { - let count_card = state.candidates.get(candidate).unwrap(); - if candidate_state == "" { - assert!(count_card.state == CandidateState::Hopeful); - } else if candidate_state == "EL" || candidate_state == "PEL" { - assert!(count_card.state == CandidateState::Elected); - } else if candidate_state == "EX" || candidate_state == "EXCLUDING" { - assert!(count_card.state == CandidateState::Excluded); - } else { - panic!("Unknown state descriptor {}", candidate_state); - } - } - } + 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 new file mode 100644 index 0000000..7e84da2 --- /dev/null +++ b/tests/meek.rs @@ -0,0 +1,45 @@ +/* OpenTally: Open-source election vote counting + * Copyright © 2021 Lee Yingtong Li (RunasSudo) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +mod utils; + +use opentally::numbers::NativeFloat64; +use opentally::stv; + +#[test] +fn meek_ers97_float64() { + let stv_opts = stv::STVOptions { + round_tvs: None, + round_weights: None, + round_votes: None, + round_quota: None, + sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep, + 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, + bulk_exclude: false, + defer_surpluses: false, + pp_decimals: 2, + }; + utils::read_validate_election::("tests/data/ers97_meek.csv", "tests/data/ers97.blt", stv_opts, Some(2), &["exhausted", "quota"]); +} diff --git a/tests/prsa.rs b/tests/prsa.rs index 7da42ba..2316417 100644 --- a/tests/prsa.rs +++ b/tests/prsa.rs @@ -41,5 +41,5 @@ fn prsa1_rational() { defer_surpluses: false, pp_decimals: 2, }; - utils::read_validate_election::("tests/data/prsa1.csv", "tests/data/prsa1.blt", stv_opts); + utils::read_validate_election::("tests/data/prsa1.csv", "tests/data/prsa1.blt", stv_opts, None, &["exhausted", "lbf"]); } diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 5f86777..9da9522 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -16,7 +16,7 @@ */ use opentally::election::{CandidateState, CountState, Election}; -use opentally::numbers::{Number, Rational}; +use opentally::numbers::Number; use opentally::stv; use csv::StringRecord; @@ -26,7 +26,14 @@ use std::io::{self, BufRead}; use std::ops; #[allow(dead_code)] // Suppress false positive -pub fn read_validate_election(csv_file: &str, blt_file: &str, stv_opts: stv::STVOptions) { +pub fn read_validate_election(csv_file: &str, blt_file: &str, stv_opts: stv::STVOptions, cmp_dps: Option, sum_rows: &[&str]) +where + for<'r> &'r N: ops::Add<&'r N, Output=N>, + for<'r> &'r N: ops::Sub<&'r N, Output=N>, + for<'r> &'r N: ops::Mul<&'r N, Output=N>, + for<'r> &'r N: ops::Div<&'r N, Output=N>, + for<'r> &'r N: ops::Neg, +{ // Read CSV file let reader = csv::ReaderBuilder::new() .has_headers(false) @@ -47,18 +54,23 @@ pub fn read_validate_election(csv_file: &str, blt_file: &str, stv_opt let file_reader = io::BufReader::new(file); let lines = file_reader.lines(); - let election: Election = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter()); + let election: Election = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter()); - validate_election(stages, records, election, stv_opts); + validate_election(stages, records, election, stv_opts, cmp_dps, sum_rows); } -pub fn validate_election(stages: Vec, records: Vec, election: Election, stv_opts: stv::STVOptions) +pub fn validate_election(stages: Vec, records: Vec, mut election: Election, stv_opts: stv::STVOptions, cmp_dps: Option, sum_rows: &[&str]) where + for<'r> &'r N: ops::Add<&'r N, Output=N>, for<'r> &'r N: ops::Sub<&'r N, Output=N>, for<'r> &'r N: ops::Mul<&'r N, Output=N>, for<'r> &'r N: ops::Div<&'r N, Output=N>, for<'r> &'r N: ops::Neg, { + if stv_opts.normalise_ballots { + election.normalise_ballots(); + } + // Initialise count state let mut state = CountState::new(&election); @@ -73,29 +85,38 @@ where assert_eq!(stv::count_one_stage(&mut state, &stv_opts).unwrap(), false); stage_num += 1; } - validate_stage(idx, &state, &records); + validate_stage(idx, &state, &records, cmp_dps, sum_rows); } } -fn validate_stage(idx: usize, state: &CountState, records: &Vec) { - println!("Col at idx {}", idx); - - let mut candidate_votes: Vec = records.iter().skip(2).map(|r| N::from(r[idx*2 + 1].parse::().expect("Syntax Error"))).collect(); +fn validate_stage(idx: usize, state: &CountState, records: &Vec, cmp_dps: Option, sum_rows: &[&str]) +where + for<'r> &'r N: ops::Add<&'r N, Output=N>, +{ + let mut candidate_votes: Vec> = records.iter().skip(2) + .map(|r| if r[idx*2 + 1].len() > 0 { Some(N::from(r[idx*2 + 1].parse::().expect("Syntax Error"))) } else { None }) + .collect(); // Validate exhausted/LBF - let lbf_votes = candidate_votes.pop().unwrap(); - let exhausted_votes = candidate_votes.pop().unwrap(); - - assert!(state.exhausted.votes == exhausted_votes); - assert!(state.loss_fraction.votes == lbf_votes); - - // Remove exhausted/LBF rows - candidate_votes.truncate(candidate_votes.len() - 2); + for kind in sum_rows.iter().rev() { + let votes = candidate_votes.pop().unwrap_or(None); + if let Some(votes) = votes { + match kind { + &"exhausted" => approx_eq(&state.exhausted.votes, &votes, cmp_dps, idx, "exhausted votes"), + &"lbf" => approx_eq(&state.loss_fraction.votes, &votes, cmp_dps, idx, "LBF"), + &"nt" => approx_eq(&(&state.exhausted.votes + &state.loss_fraction.votes), &votes, cmp_dps, idx, "NTs"), + &"quota" => approx_eq(state.quota.as_ref().unwrap(), &votes, cmp_dps, idx, "quota"), + &"vre" => approx_eq(state.vote_required_election.as_ref().unwrap(), &votes, cmp_dps, idx, "VRE"), + _ => panic!("Unknown sum_rows"), + } + } + } // Validate candidate votes for (candidate, votes) in state.election.candidates.iter().zip(candidate_votes) { let count_card = state.candidates.get(candidate).unwrap(); - assert!(count_card.votes == votes, "Failed to validate votes for candidate {}. Expected {:}, got {:}", candidate.name, votes, count_card.votes); + let votes = votes.unwrap(); + approx_eq(&count_card.votes, &votes, cmp_dps, idx, &format!("votes for candidate {}", candidate.name)); } // Validate candidate states @@ -115,3 +136,17 @@ fn validate_stage(idx: usize, state: &CountState, records: &Vec(n1: &N, n2: &N, cmp_dps: Option, idx: usize, description: &str) { + match cmp_dps { + Some(dps) => { + let s1 = format!("{:.dps$}", n1, dps=dps); + let s2 = format!("{:.dps$}", n2, dps=dps); + assert!(s1 == s2, "Failed to verify {} for idx {}. Expected {}, got {}", description, idx, s2, s1); + } + None => { + assert!(n1 == n2, "Failed to verify {} for idx {}. Expected {}, got {}", description, idx, n2, n1); + } + } + +}