diff --git a/src/election.rs b/src/election.rs index 86ed6af..4f0795d 100644 --- a/src/election.rs +++ b/src/election.rs @@ -20,7 +20,9 @@ use crate::logger::Logger; use crate::numbers::Number; use crate::parser::blt::{BLTParser, ParseError}; use crate::sharandom::SHARandom; +use crate::stv::{QuotaMode, STVOptions}; +use std::cmp::max; use std::collections::HashMap; use std::iter::Peekable; @@ -208,6 +210,81 @@ impl<'a, N: Number> CountState<'a, N> { self.exhausted.step(); self.loss_fraction.step(); } + + /// List the candidates, and their current state, votes and transfers + pub fn describe_candidates(&self, opts: &STVOptions) -> String { + let mut candidates: Vec<(&Candidate, &CountCard)>; + + if opts.sort_votes { + // Sort by votes if requested + candidates = self.candidates.iter() + .map(|(c, cc)| (*c, cc)).collect(); + // First sort by order of election (as a tie-breaker, if votes are equal) + candidates.sort_unstable_by(|a, b| b.1.order_elected.cmp(&a.1.order_elected)); + // Then sort by votes + candidates.sort_by(|a, b| a.1.votes.cmp(&b.1.votes)); + candidates.reverse(); + } else { + candidates = self.election.candidates.iter() + .map(|c| (c, &self.candidates[c])) + .collect(); + } + + let mut result = String::new(); + + for (candidate, count_card) in candidates { + match count_card.state { + CandidateState::Hopeful => { + result.push_str(&format!("- {}: {:.dps$} ({:.dps$})\n", candidate.name, count_card.votes, count_card.transfers, dps=opts.pp_decimals)); + } + CandidateState::Guarded => { + result.push_str(&format!("- {}: {:.dps$} ({:.dps$}) - Guarded\n", candidate.name, count_card.votes, count_card.transfers, dps=opts.pp_decimals)); + } + CandidateState::Elected => { + if let Some(kv) = &count_card.keep_value { + result.push_str(&format!("- {}: {:.dps$} ({:.dps$}) - ELECTED {} (kv = {:.dps2$})\n", candidate.name, count_card.votes, count_card.transfers, count_card.order_elected, kv, dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2))); + } else { + result.push_str(&format!("- {}: {:.dps$} ({:.dps$}) - ELECTED {}\n", candidate.name, count_card.votes, count_card.transfers, count_card.order_elected, dps=opts.pp_decimals)); + } + } + CandidateState::Doomed => { + result.push_str(&format!("- {}: {:.dps$} ({:.dps$}) - Doomed\n", candidate.name, count_card.votes, count_card.transfers, dps=opts.pp_decimals)); + } + CandidateState::Withdrawn => { + if !opts.hide_excluded || !count_card.votes.is_zero() || !count_card.transfers.is_zero() { + result.push_str(&format!("- {}: {:.dps$} ({:.dps$}) - Withdrawn\n", candidate.name, count_card.votes, count_card.transfers, dps=opts.pp_decimals)); + } + } + CandidateState::Excluded => { + // If --hide-excluded, hide unless nonzero votes or nonzero transfers + if !opts.hide_excluded || !count_card.votes.is_zero() || !count_card.transfers.is_zero() { + result.push_str(&format!("- {}: {:.dps$} ({:.dps$}) - Excluded {}\n", candidate.name, count_card.votes, count_card.transfers, -count_card.order_elected, dps=opts.pp_decimals)); + } + } + } + } + return result; + } + + /// Produce summary rows for the current stage + pub fn describe_summary(&self, opts: &STVOptions) -> String { + let mut result = String::new(); + + result.push_str(&format!("Exhausted: {:.dps$} ({:.dps$})\n", self.exhausted.votes, self.exhausted.transfers, dps=opts.pp_decimals)); + result.push_str(&format!("Loss by fraction: {:.dps$} ({:.dps$})\n", self.loss_fraction.votes, self.loss_fraction.transfers, dps=opts.pp_decimals)); + + let mut total_vote = self.candidates.values().fold(N::zero(), |acc, cc| { acc + &cc.votes }); + total_vote += &self.exhausted.votes; + total_vote += &self.loss_fraction.votes; + result.push_str(&format!("Total votes: {:.dps$}\n", total_vote, dps=opts.pp_decimals)); + + result.push_str(&format!("Quota: {:.dps$}\n", self.quota.as_ref().unwrap(), dps=opts.pp_decimals)); + if opts.quota_mode == QuotaMode::ERS97 || opts.quota_mode == QuotaMode::ERS76 { + result.push_str(&format!("Vote required for election: {:.dps$}\n", self.vote_required_election.as_ref().unwrap(), dps=opts.pp_decimals)); + } + + return result; + } } /// Current state of a [Candidate] during an election count diff --git a/src/main.rs b/src/main.rs index 843243f..8e2ebdf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,9 +16,9 @@ */ use opentally::constraints::Constraints; -use opentally::election::{Candidate, CandidateState, CountCard, CountState, Election}; +use opentally::election::{CandidateState, CountState, Election}; use opentally::numbers::{Fixed, GuardedFixed, NativeFloat64, Number, Rational}; -use opentally::stv; +use opentally::stv::{self, STVOptions}; use clap::{AppSettings, Clap}; @@ -252,7 +252,7 @@ where for<'r> &'r N: ops::Neg { // Copy applicable options - let stv_opts = stv::STVOptions::new( + let opts = STVOptions::new( cmd_opts.round_tvs, cmd_opts.round_weights, cmd_opts.round_votes, @@ -276,11 +276,13 @@ where cmd_opts.meek_immediate_elect, cmd_opts.constraints.as_deref(), &cmd_opts.constraint_mode, + cmd_opts.hide_excluded, + cmd_opts.sort_votes, cmd_opts.pp_decimals, ); // Validate options - match stv_opts.validate() { + match opts.validate() { Ok(_) => {} Err(err) => { println!("Error: {}", err.describe()); @@ -291,7 +293,7 @@ where // Describe count let total_ballots = election.ballots.iter().fold(N::zero(), |acc, b| { acc + &b.orig_value }); print!("Count computed by OpenTally (revision {}). Read {:.0} ballots from \"{}\" for election \"{}\". There are {} candidates for {} vacancies. ", opentally::VERSION, total_ballots, cmd_opts.filename, election.name, election.candidates.len(), election.seats); - let opts_str = stv_opts.describe::(); + let opts_str = opts.describe::(); if opts_str.len() > 0 { println!("Counting using options \"{}\".", opts_str); } else { @@ -308,7 +310,7 @@ where let mut state = CountState::new(&election); // Distribute first preferences - match stv::count_init(&mut state, &stv_opts) { + match stv::count_init(&mut state, &opts) { Ok(_) => {} Err(err) => { println!("Error: {}", err.describe()); @@ -317,10 +319,10 @@ where } let mut stage_num = 1; - print_stage(stage_num, &state, &cmd_opts); + print_stage(stage_num, &state, &opts); loop { - match stv::count_one_stage(&mut state, &stv_opts) { + match stv::count_one_stage(&mut state, &opts) { Ok(is_done) => { if is_done { break; @@ -333,7 +335,7 @@ where } stage_num += 1; - print_stage(stage_num, &state, &cmd_opts); + print_stage(stage_num, &state, &opts); } println!("Count complete. The winning candidates are, in order of election:"); @@ -348,7 +350,7 @@ where for (i, (winner, count_card)) in winners.into_iter().enumerate() { if let Some(kv) = &count_card.keep_value { - println!("{}. {} (kv = {:.dps2$})", i + 1, winner.name, kv, dps2=max(stv_opts.pp_decimals, 2)); + println!("{}. {} (kv = {:.dps2$})", i + 1, winner.name, kv, dps2=max(opts.pp_decimals, 2)); } else { println!("{}. {}", i + 1, winner.name); } @@ -357,79 +359,19 @@ where return Ok(()); } -fn print_candidates<'a, N: 'a + Number, I: Iterator)>>(candidates: I, cmd_opts: &STV) { - for (candidate, count_card) in candidates { - match count_card.state { - CandidateState::Hopeful => { - println!("- {}: {:.dps$} ({:.dps$})", candidate.name, count_card.votes, count_card.transfers, dps=cmd_opts.pp_decimals); - } - CandidateState::Guarded => { - println!("- {}: {:.dps$} ({:.dps$}) - Guarded", candidate.name, count_card.votes, count_card.transfers, dps=cmd_opts.pp_decimals); - } - CandidateState::Elected => { - if let Some(kv) = &count_card.keep_value { - println!("- {}: {:.dps$} ({:.dps$}) - ELECTED {} (kv = {:.dps2$})", candidate.name, count_card.votes, count_card.transfers, count_card.order_elected, kv, dps=cmd_opts.pp_decimals, dps2=max(cmd_opts.pp_decimals, 2)); - } else { - println!("- {}: {:.dps$} ({:.dps$}) - ELECTED {}", candidate.name, count_card.votes, count_card.transfers, count_card.order_elected, dps=cmd_opts.pp_decimals); - } - } - CandidateState::Doomed => { - println!("- {}: {:.dps$} ({:.dps$}) - Doomed", candidate.name, count_card.votes, count_card.transfers, dps=cmd_opts.pp_decimals); - } - CandidateState::Withdrawn => { - if !cmd_opts.hide_excluded || !count_card.votes.is_zero() || !count_card.transfers.is_zero() { - println!("- {}: {:.dps$} ({:.dps$}) - Withdrawn", candidate.name, count_card.votes, count_card.transfers, dps=cmd_opts.pp_decimals); - } - } - CandidateState::Excluded => { - // If --hide-excluded, hide unless nonzero votes or nonzero transfers - if !cmd_opts.hide_excluded || !count_card.votes.is_zero() || !count_card.transfers.is_zero() { - println!("- {}: {:.dps$} ({:.dps$}) - Excluded {}", candidate.name, count_card.votes, count_card.transfers, -count_card.order_elected, dps=cmd_opts.pp_decimals); - } - } - } - } -} - -fn print_stage(stage_num: usize, state: &CountState, cmd_opts: &STV) { - let logs = state.logger.render(); - +fn print_stage(stage_num: usize, state: &CountState, opts: &STVOptions) { // Print stage details match state.kind { None => { println!("{}. {}", stage_num, state.title); } Some(kind) => { println!("{}. {} {}", stage_num, kind, state.title); } }; - println!("{}", logs.join(" ")); + println!("{}", state.logger.render().join(" ")); // Print candidates - if cmd_opts.sort_votes { - // Sort by votes if requested - let mut candidates: Vec<(&Candidate, &CountCard)> = state.candidates.iter() - .map(|(c, cc)| (*c, cc)).collect(); - // First sort by order of election (as a tie-breaker, if votes are equal) - candidates.sort_unstable_by(|a, b| b.1.order_elected.cmp(&a.1.order_elected)); - // Then sort by votes - candidates.sort_by(|a, b| a.1.votes.cmp(&b.1.votes)); - print_candidates(candidates.into_iter().rev(), cmd_opts); - } else { - let candidates = state.election.candidates.iter() - .map(|c| (c, &state.candidates[c])); - print_candidates(candidates, cmd_opts); - } + print!("{}", state.describe_candidates(opts)); // Print summary rows - println!("Exhausted: {:.dps$} ({:.dps$})", state.exhausted.votes, state.exhausted.transfers, dps=cmd_opts.pp_decimals); - println!("Loss by fraction: {:.dps$} ({:.dps$})", state.loss_fraction.votes, state.loss_fraction.transfers, dps=cmd_opts.pp_decimals); - - let mut total_vote = state.candidates.values().fold(N::zero(), |acc, cc| { acc + &cc.votes }); - total_vote += &state.exhausted.votes; - total_vote += &state.loss_fraction.votes; - println!("Total votes: {:.dps$}", total_vote, dps=cmd_opts.pp_decimals); - - println!("Quota: {:.dps$}", state.quota.as_ref().unwrap(), dps=cmd_opts.pp_decimals); - if cmd_opts.quota_mode == "ers97" { - println!("Vote required for election: {:.dps$}", state.vote_required_election.as_ref().unwrap(), dps=cmd_opts.pp_decimals); - } + print!("{}", state.describe_summary(opts)); println!(""); } diff --git a/src/stv/mod.rs b/src/stv/mod.rs index 8f60850..794384c 100644 --- a/src/stv/mod.rs +++ b/src/stv/mod.rs @@ -85,6 +85,10 @@ pub struct STVOptions { pub constraints_path: Option, /// Mode of handling constraints pub constraint_mode: ConstraintMode, + /// Hide excluded candidates from results report + pub hide_excluded: bool, + /// Sort candidates by votes in results report + pub sort_votes: bool, /// Print votes to specified decimal places in results report pub pp_decimals: usize, } @@ -115,6 +119,8 @@ impl STVOptions { meek_immediate_elect: bool, constraints_path: Option<&str>, constraint_mode: &str, + hide_excluded: bool, + sort_votes: bool, pp_decimals: usize, ) -> Self { return STVOptions { @@ -190,6 +196,8 @@ impl STVOptions { "rollback" => ConstraintMode::Rollback, _ => panic!("Invalid --constraint-mode"), }, + hide_excluded, + sort_votes, pp_decimals, }; } @@ -224,6 +232,8 @@ impl STVOptions { flags.push(format!("--constraints {}", path)); if self.constraint_mode != ConstraintMode::GuardDoom { flags.push(self.constraint_mode.describe()); } } + if self.hide_excluded { flags.push(format!("--hide-excluded")); } + if self.sort_votes { flags.push(format!("--sort-votes")); } if self.pp_decimals != 2 { flags.push(format!("--pp-decimals {}", self.pp_decimals)); } return flags.join(" "); } @@ -1283,7 +1293,7 @@ fn finished_before_stage(state: &CountState) -> bool { /// The given candidates are assumed to be tied in this round fn choose_highest<'c, N: Number>(state: &mut CountState, opts: &STVOptions, candidates: Vec<&'c Candidate>) -> Result<&'c Candidate, STVError> { for strategy in opts.ties.iter() { - match strategy.choose_highest(state, &candidates) { + match strategy.choose_highest(state, opts, &candidates) { Ok(c) => { return Ok(c); } @@ -1304,7 +1314,7 @@ fn choose_highest<'c, N: Number>(state: &mut CountState, opts: &STVOptions, c /// The given candidates are assumed to be tied in this round fn choose_lowest<'c, N: Number>(state: &mut CountState, opts: &STVOptions, candidates: Vec<&'c Candidate>) -> Result<&'c Candidate, STVError> { for strategy in opts.ties.iter() { - match strategy.choose_lowest(state, &candidates) { + match strategy.choose_lowest(state, opts, &candidates) { Ok(c) => { return Ok(c); } diff --git a/src/stv/wasm.rs b/src/stv/wasm.rs index e7de324..76fb4e9 100644 --- a/src/stv/wasm.rs +++ b/src/stv/wasm.rs @@ -260,6 +260,8 @@ impl STVOptions { meek_immediate_elect, constraints_path.as_deref(), constraint_mode, + false, + false, pp_decimals, )) } diff --git a/src/ties.rs b/src/ties.rs index fce34d0..8453802 100644 --- a/src/ties.rs +++ b/src/ties.rs @@ -18,7 +18,7 @@ use crate::election::{Candidate, CountState}; use crate::logger::smart_join; use crate::numbers::Number; -use crate::stv::STVError; +use crate::stv::{STVError, STVOptions}; #[allow(unused_imports)] use wasm_bindgen::prelude::wasm_bindgen; @@ -53,7 +53,7 @@ impl TieStrategy { /// Break a tie between the given candidates, selecting the highest candidate /// /// The given candidates are assumed to be tied in this round - pub fn choose_highest<'c, N: Number>(&self, state: &mut CountState, candidates: &Vec<&'c Candidate>) -> Result<&'c Candidate, STVError> { + pub fn choose_highest<'c, N: Number>(&self, state: &mut CountState, opts: &STVOptions, candidates: &Vec<&'c Candidate>) -> Result<&'c Candidate, STVError> { match self { Self::Forwards => { let mut candidates = candidates.clone(); @@ -87,7 +87,7 @@ impl TieStrategy { return Ok(candidates[state.random.as_mut().unwrap().next(candidates.len())]); } Self::Prompt => { - match prompt(candidates) { + match prompt(state, opts, candidates) { Ok(c) => { state.logger.log_literal(format!("Tie between {} broken by manual intervention.", smart_join(&candidates.iter().map(|c| c.name.as_str()).collect()))); return Ok(c); @@ -101,7 +101,7 @@ impl TieStrategy { /// Break a tie between the given candidates, selecting the lowest candidate /// /// The given candidates are assumed to be tied in this round - pub fn choose_lowest<'c, N: Number>(&self, state: &mut CountState, candidates: &Vec<&'c Candidate>) -> Result<&'c Candidate, STVError> { + pub fn choose_lowest<'c, N: Number>(&self, state: &mut CountState, opts: &STVOptions, candidates: &Vec<&'c Candidate>) -> Result<&'c Candidate, STVError> { match self { Self::Forwards => { let mut candidates = candidates.clone(); @@ -130,10 +130,10 @@ impl TieStrategy { } } Self::Random(_seed) => { - return self.choose_highest(state, candidates); + return self.choose_highest(state, opts, candidates); } Self::Prompt => { - return self.choose_highest(state, candidates); + return self.choose_highest(state, opts, candidates); } } } @@ -199,7 +199,25 @@ where /// Prompt the candidate for input, depending on CLI or WebAssembly target #[cfg(not(target_arch = "wasm32"))] -fn prompt<'c>(candidates: &Vec<&'c Candidate>) -> Result<&'c Candidate, STVError> { +fn prompt<'c, N: Number>(state: &CountState, opts: &STVOptions, candidates: &Vec<&'c Candidate>) -> Result<&'c Candidate, STVError> { + // Show intrastage progress if required + if !state.logger.entries.is_empty() { + // Print stage details + match state.kind { + None => { println!("Tie during: {}", state.title); } + Some(kind) => { println!("Tie during: {} {}", kind, state.title); } + }; + println!("{}", state.logger.render().join(" ")); + + // Print candidates + print!("{}", state.describe_candidates(opts)); + + // Print summary rows + print!("{}", state.describe_summary(opts)); + + println!(""); + } + println!("Multiple tied candidates:"); for (i, candidate) in candidates.iter().enumerate() { println!("{}. {}", i + 1, candidate.name); @@ -234,8 +252,29 @@ extern "C" { } #[cfg(target_arch = "wasm32")] -fn prompt<'c>(candidates: &Vec<&'c Candidate>) -> Result<&'c Candidate, STVError> { - let mut message = String::from("Multiple tied candidates:\n"); +fn prompt<'c, N: Number>(state: &CountState, opts: &STVOptions, candidates: &Vec<&'c Candidate>) -> Result<&'c Candidate, STVError> { + let mut message = String::new(); + + // Show intrastage progress if required + if !state.logger.entries.is_empty() { + // Print stage details + match state.kind { + None => { message.push_str(&format!("Tie during: {}\n", state.title)); } + Some(kind) => { message.push_str(&format!("Tie during: {} {}\n", kind, state.title)); } + }; + message.push_str(&state.logger.render().join(" ")); + message.push('\n'); + + // Print candidates + message.push_str(&state.describe_candidates(opts)); + message.push('\n'); + + // Print summary rows + message.push_str(&state.describe_summary(opts)); + message.push('\n'); + } + + message.push_str(&"Multiple tied candidates:\n"); for (i, candidate) in candidates.iter().enumerate() { message.push_str(&format!("{}. {}\n", i + 1, candidate.name)); } diff --git a/tests/act.rs b/tests/act.rs index de201d8..9faefda 100644 --- a/tests/act.rs +++ b/tests/act.rs @@ -45,6 +45,8 @@ fn act_kurrajong20_rational() { meek_immediate_elect: false, constraints_path: None, constraint_mode: stv::ConstraintMode::GuardDoom, + hide_excluded: false, + sort_votes: false, pp_decimals: 2, }; 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 28b15a9..928cdb9 100644 --- a/tests/aec.rs +++ b/tests/aec.rs @@ -82,6 +82,8 @@ fn aec_tas19_rational() { meek_immediate_elect: false, constraints_path: None, constraint_mode: stv::ConstraintMode::GuardDoom, + hide_excluded: false, + sort_votes: false, pp_decimals: 2, }; utils::validate_election::(stages, records, election, stv_opts, None, &["exhausted", "lbf"]); diff --git a/tests/constraints.rs b/tests/constraints.rs index 7d5d649..bdb3f38 100644 --- a/tests/constraints.rs +++ b/tests/constraints.rs @@ -61,6 +61,8 @@ fn prsa1_constr1_rational() { meek_immediate_elect: false, constraints_path: Some("tests/data/prsa1_constr1.con".to_string()), constraint_mode: stv::ConstraintMode::GuardDoom, + hide_excluded: false, + sort_votes: false, pp_decimals: 2, }; @@ -122,6 +124,8 @@ fn prsa1_constr2_rational() { meek_immediate_elect: false, constraints_path: Some("tests/data/prsa1_constr2.con".to_string()), constraint_mode: stv::ConstraintMode::GuardDoom, + hide_excluded: false, + sort_votes: false, pp_decimals: 2, }; @@ -183,6 +187,8 @@ fn prsa1_constr3_rational() { meek_immediate_elect: false, constraints_path: Some("tests/data/prsa1_constr2.con".to_string()), constraint_mode: stv::ConstraintMode::GuardDoom, + hide_excluded: false, + sort_votes: false, pp_decimals: 2, }; diff --git a/tests/csm.rs b/tests/csm.rs index 8ef3033..2b97ee8 100644 --- a/tests/csm.rs +++ b/tests/csm.rs @@ -45,6 +45,8 @@ fn csm15_float64() { meek_immediate_elect: false, constraints_path: None, constraint_mode: stv::ConstraintMode::GuardDoom, + hide_excluded: false, + sort_votes: false, pp_decimals: 2, }; 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 74687b1..55266db 100644 --- a/tests/ers97.rs +++ b/tests/ers97.rs @@ -45,6 +45,8 @@ fn ers97_rational() { meek_immediate_elect: false, constraints_path: None, constraint_mode: stv::ConstraintMode::GuardDoom, + hide_excluded: false, + sort_votes: false, pp_decimals: 2, }; 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 0971015..45dd6c3 100644 --- a/tests/meek.rs +++ b/tests/meek.rs @@ -20,10 +20,6 @@ mod utils; use opentally::election::{CandidateState, CountState, Election}; use opentally::numbers::{Fixed, NativeFloat64, Number}; use opentally::stv; -use utf8_chars::BufReadCharsExt; - -use std::fs::File; -use std::io::BufReader; // Compare ers97.blt count with result produced by 1987 Hill–Wichmann–Woodall reference implementation #[test] @@ -51,6 +47,8 @@ fn meek87_ers97_float64() { meek_immediate_elect: false, constraints_path: None, constraint_mode: stv::ConstraintMode::GuardDoom, + hide_excluded: false, + sort_votes: false, pp_decimals: 2, }; utils::read_validate_election::("tests/data/ers97_meek.csv", "tests/data/ers97.blt", stv_opts, Some(2), &["exhausted", "quota"]); @@ -82,6 +80,8 @@ fn meek06_ers97_fixed12() { meek_immediate_elect: true, constraints_path: None, constraint_mode: stv::ConstraintMode::GuardDoom, + hide_excluded: false, + sort_votes: false, pp_decimals: 2, }; Fixed::set_dps(12); @@ -154,6 +154,8 @@ fn meeknz_ers97_fixed12() { meek_immediate_elect: true, constraints_path: None, constraint_mode: stv::ConstraintMode::GuardDoom, + hide_excluded: false, + sort_votes: false, pp_decimals: 2, }; Fixed::set_dps(12); diff --git a/tests/prsa.rs b/tests/prsa.rs index 6b6c3ec..ee4aadf 100644 --- a/tests/prsa.rs +++ b/tests/prsa.rs @@ -45,6 +45,8 @@ fn prsa1_rational() { meek_immediate_elect: false, constraints_path: None, constraint_mode: stv::ConstraintMode::GuardDoom, + hide_excluded: false, + sort_votes: false, pp_decimals: 2, }; 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 54388b1..f05bbca 100644 --- a/tests/scotland.rs +++ b/tests/scotland.rs @@ -19,11 +19,9 @@ use opentally::election::{CandidateState, CountState, Election}; use opentally::numbers::{Fixed, GuardedFixed, Number}; use opentally::stv; -use utf8_chars::BufReadCharsExt; use xmltree::Element; use std::fs::File; -use std::io::BufReader; use std::ops; #[test] @@ -51,6 +49,8 @@ fn scotland_linn07_fixed5() { meek_immediate_elect: false, constraints_path: None, constraint_mode: stv::ConstraintMode::GuardDoom, + hide_excluded: false, + sort_votes: false, pp_decimals: 5, }; Fixed::set_dps(5); @@ -82,6 +82,8 @@ fn scotland_linn07_gfixed5() { meek_immediate_elect: false, constraints_path: None, constraint_mode: stv::ConstraintMode::GuardDoom, + hide_excluded: false, + sort_votes: false, pp_decimals: 5, }; GuardedFixed::set_dps(5);