OpenTally/tests/meek.rs

209 lines
7.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* OpenTally: Open-source election vote counting
* Copyright © 2021 Lee Yingtong Li (RunasSudo)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
mod utils;
use opentally::election::{CandidateState, CountState, Election};
use opentally::numbers::{Fixed, NativeFloat64, Number};
use opentally::stv;
use std::io::{self, BufRead};
use std::fs::File;
// Compare ers97.blt count with result produced by 1987 HillWichmannWoodall reference implementation
#[test]
fn meek87_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,
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,
early_bulk_elect: true,
bulk_exclude: false,
defer_surpluses: false,
meek_immediate_elect: false,
constraints_path: None,
constraint_mode: stv::ConstraintMode::GuardDoom,
pp_decimals: 2,
};
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_tvs: Some(9),
round_weights: 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,
early_bulk_elect: true,
bulk_exclude: false,
defer_surpluses: true,
meek_immediate_elect: true,
constraints_path: None,
constraint_mode: stv::ConstraintMode::GuardDoom,
pp_decimals: 2,
};
Fixed::set_dps(12);
// 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<Fixed> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
// Initialise count state
let mut state = CountState::new(&election);
// Count to completion
stv::count_init(&mut state, &stv_opts).unwrap();
while !stv::count_one_stage::<Fixed>(&mut state, &stv_opts).unwrap() {}
// Check states and keep values
for (candidate, count_card) in state.candidates.iter() {
match candidate.name.as_str() {
"Smith" => {
assert!(count_card.state == CandidateState::Elected);
assert_eq!(count_card.keep_value, Some(Fixed::parse("0.452239778")));
}
"Carpenter" => {
assert!(count_card.state == CandidateState::Elected);
assert_eq!(count_card.keep_value, Some(Fixed::parse("0.614726576")));
}
"Duke" => {
assert!(count_card.state == CandidateState::Elected);
assert_eq!(count_card.keep_value, Some(Fixed::parse("0.610901635")));
}
"Prince" => {
assert!(count_card.state == CandidateState::Elected);
assert_eq!(count_card.keep_value, Some(Fixed::parse("0.741971929")));
}
"Freeman" => {
assert!(count_card.state == CandidateState::Elected);
assert_eq!(count_card.keep_value, Some(Fixed::parse("0.715546058")));
}
"Vicar" => {
assert!(count_card.state == CandidateState::Elected);
assert_eq!(count_card.keep_value, Some(Fixed::parse("1.0")));
}
_ => {}
}
}
}
// 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_tvs: Some(9),
round_weights: 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,
early_bulk_elect: true,
bulk_exclude: false,
defer_surpluses: true,
meek_immediate_elect: true,
constraints_path: None,
constraint_mode: stv::ConstraintMode::GuardDoom,
pp_decimals: 2,
};
Fixed::set_dps(12);
// 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<Fixed> = Election::from_blt(lines.map(|r| r.expect("IO Error").to_string()).into_iter());
// Initialise count state
let mut state = CountState::new(&election);
// Count to completion
stv::count_init(&mut state, &stv_opts).unwrap();
while !stv::count_one_stage::<Fixed>(&mut state, &stv_opts).unwrap() {}
// Check states and keep values
for (candidate, count_card) in state.candidates.iter() {
match candidate.name.as_str() {
"Smith" => {
assert!(count_card.state == CandidateState::Elected);
assert_eq!(count_card.keep_value, Some(Fixed::parse("0.452154292")));
}
"Carpenter" => {
assert!(count_card.state == CandidateState::Elected);
assert_eq!(count_card.keep_value, Some(Fixed::parse("0.614574172")));
}
"Duke" => {
assert!(count_card.state == CandidateState::Elected);
assert_eq!(count_card.keep_value, Some(Fixed::parse("0.610780881")));
}
"Prince" => {
assert!(count_card.state == CandidateState::Elected);
assert_eq!(count_card.keep_value, Some(Fixed::parse("0.741808717")));
}
"Freeman" => {
assert!(count_card.state == CandidateState::Elected);
assert_eq!(count_card.keep_value, Some(Fixed::parse("0.715384286")));
}
"Vicar" => {
assert!(count_card.state == CandidateState::Elected);
assert_eq!(count_card.keep_value, Some(Fixed::parse("1.0")));
}
_ => {}
}
}
}