Fix incorrect crediting of votes when surplus votes transferred at values received

Many thanks to J Groves for pointing this out
This commit is contained in:
RunasSudo 2022-03-11 13:33:28 +11:00
parent f5114bccda
commit 6968df5c9b
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
5 changed files with 51 additions and 3 deletions

View File

@ -202,7 +202,8 @@ impl<'e, N: Number> TransferTable<'e, N> {
if self.surplus.is_none() || opts.sum_surplus_transfers == SumSurplusTransfersMode::ByValue { if self.surplus.is_none() || opts.sum_surplus_transfers == SumSurplusTransfersMode::ByValue {
for (_candidate, cell) in self.total.cells.iter_mut() { for (_candidate, cell) in self.total.cells.iter_mut() {
let mut votes_out; 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(); votes_out = cell.votes_in.clone();
} else { } else {
votes_out = cell.ballots.clone(); votes_out = cell.ballots.clone();
@ -221,7 +222,7 @@ impl<'e, N: Number> TransferTable<'e, N> {
if self.surplus.is_none() || !opts.transferable_only { if self.surplus.is_none() || !opts.transferable_only {
let mut votes_out; let mut votes_out;
if is_weighted { if is_weighted || self.surpfrac.is_none() {
votes_out = self.total.exhausted.votes_in.clone(); votes_out = self.total.exhausted.votes_in.clone();
} else { } else {
votes_out = self.total.exhausted.ballots.clone(); votes_out = self.total.exhausted.ballots.clone();

View File

@ -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"

View File

@ -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,
1 Stage: 1 2 3
2 Comment: First preferences Surplus of A Surplus of B
3 A 15 EL 9.75 EL 9.75 EL
4 B 9 H 14.25 EL 9.75 EL
5 C 8 H 8 H 9.05 EL
6 D 7 H 7 H 7 H
7 Exhausted 0 0 3.45

Binary file not shown.

View File

@ -15,8 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use crate::utils;
use opentally::election::{CandidateState, CountState, Election}; use opentally::election::{CandidateState, CountState, Election};
use opentally::numbers::Rational; use opentally::numbers::{Fixed, Rational};
use opentally::parser::blt; use opentally::parser::blt;
use opentally::stv; use opentally::stv;
use opentally::ties::TieStrategy; use opentally::ties::TieStrategy;
@ -146,3 +148,28 @@ fn tideman_a34_prsa1977_rational() {
// Assert count completes // 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::<Fixed>(), "--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::<Fixed>("tests/data/surplus_values_received.csv", "tests/data/surplus_values_received.blt", stv_opts, None, &["exhausted"]);
}