From 6968df5c9bfa4811ef46c4edaabc9fbc5b29b4f7 Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Fri, 11 Mar 2022 13:33:28 +1100 Subject: [PATCH] Fix incorrect crediting of votes when surplus votes transferred at values received Many thanks to J Groves for pointing this out --- src/stv/gregory/transfers.rs | 5 +++-- tests/data/surplus_values_received.blt | 13 +++++++++++ tests/data/surplus_values_received.csv | 7 ++++++ tests/data/surplus_values_received.ods | Bin 0 -> 10604 bytes tests/tests_impl/special_cases.rs | 29 ++++++++++++++++++++++++- 5 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 tests/data/surplus_values_received.blt create mode 100644 tests/data/surplus_values_received.csv create mode 100644 tests/data/surplus_values_received.ods diff --git a/src/stv/gregory/transfers.rs b/src/stv/gregory/transfers.rs index 6eb5d52..ac1e986 100644 --- a/src/stv/gregory/transfers.rs +++ b/src/stv/gregory/transfers.rs @@ -202,7 +202,8 @@ impl<'e, N: Number> TransferTable<'e, N> { if self.surplus.is_none() || opts.sum_surplus_transfers == SumSurplusTransfersMode::ByValue { for (_candidate, cell) in self.total.cells.iter_mut() { let mut votes_out; - if is_weighted { + if is_weighted || self.surpfrac.is_none() { + // NB: If surplus.is_none, then votes transferred at values received votes_out = cell.votes_in.clone(); } else { votes_out = cell.ballots.clone(); @@ -221,7 +222,7 @@ impl<'e, N: Number> TransferTable<'e, N> { if self.surplus.is_none() || !opts.transferable_only { let mut votes_out; - if is_weighted { + if is_weighted || self.surpfrac.is_none() { votes_out = self.total.exhausted.votes_in.clone(); } else { votes_out = self.total.exhausted.ballots.clone(); diff --git a/tests/data/surplus_values_received.blt b/tests/data/surplus_values_received.blt new file mode 100644 index 0000000..a2c3ea6 --- /dev/null +++ b/tests/data/surplus_values_received.blt @@ -0,0 +1,13 @@ +# Comment: Constructed example - surplus votes need to be transferred at values received +4 3 +12 1 2 0 +3 1 2 3 0 # These votes need to be transferred to C at values received by B +9 2 0 +8 3 0 +7 4 0 +0 +"A" +"B" +"C" +"D" +"Surplus transferred at values received" diff --git a/tests/data/surplus_values_received.csv b/tests/data/surplus_values_received.csv new file mode 100644 index 0000000..f1842c0 --- /dev/null +++ b/tests/data/surplus_values_received.csv @@ -0,0 +1,7 @@ +Stage:,1,,2,,3, +Comment:,First preferences,,Surplus of A,,Surplus of B, +A,15,EL,9.75,EL,9.75,EL +B,9,H,14.25,EL,9.75,EL +C,8,H,8,H,9.05,EL +D,7,H,7,H,7,H +Exhausted,0,,0,,3.45, diff --git a/tests/data/surplus_values_received.ods b/tests/data/surplus_values_received.ods new file mode 100644 index 0000000000000000000000000000000000000000..266209dd678fb586f8b802b426032b75e840f76f GIT binary patch literal 10604 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 K&K~vM+5Z5e&my`2 literal 0 HcmV?d00001 diff --git a/tests/tests_impl/special_cases.rs b/tests/tests_impl/special_cases.rs index 172f9b2..dadc9a1 100644 --- a/tests/tests_impl/special_cases.rs +++ b/tests/tests_impl/special_cases.rs @@ -15,8 +15,10 @@ * along with this program. If not, see . */ +use crate::utils; + use opentally::election::{CandidateState, CountState, Election}; -use opentally::numbers::Rational; +use opentally::numbers::{Fixed, Rational}; use opentally::parser::blt; use opentally::stv; use opentally::ties::TieStrategy; @@ -146,3 +148,28 @@ fn tideman_a34_prsa1977_rational() { // Assert count completes } + +/// Surplus votes need to be transferred at values received +#[test] +fn surplus_values_received() { + let stv_opts = stv::STVOptionsBuilder::default() + .round_surplus_fractions(Some(2)) + .round_values(Some(2)) + .round_votes(Some(2)) + .round_quota(Some(2)) + .quota(stv::QuotaType::DroopExact) + .quota_criterion(stv::QuotaCriterion::GreaterOrEqual) + .quota_mode(stv::QuotaMode::ERS97) + .surplus(stv::SurplusMethod::EG) + .transferable_only(true) + .exclusion(stv::ExclusionMethod::ByValue) + .bulk_exclude(true) + .defer_surpluses(true) + .build().unwrap(); + + Fixed::set_dps(5); + + assert_eq!(stv_opts.describe::(), "--numbers fixed --decimals 5 --round-surplus-fractions 2 --round-values 2 --round-votes 2 --round-quota 2 --quota droop_exact --quota-criterion geq --quota-mode ers97 --surplus eg --transferable-only --exclusion by_value --bulk-exclude --defer-surpluses"); + + utils::read_validate_election::("tests/data/surplus_values_received.csv", "tests/data/surplus_values_received.blt", stv_opts, None, &["exhausted"]); +}