diff --git a/src/numbers/fixed.rs b/src/numbers/fixed.rs
index 53e1f3f..d315177 100644
--- a/src/numbers/fixed.rs
+++ b/src/numbers/fixed.rs
@@ -206,9 +206,7 @@ impl ops::Sub for Fixed {
impl ops::Mul for Fixed {
type Output = Self;
- fn mul(self, _rhs: Self) -> Self::Output {
- todo!()
- }
+ fn mul(self, rhs: Self) -> Self::Output { Self(self.0 * rhs.0 / get_factor()) }
}
impl ops::Div for Fixed {
@@ -294,8 +292,9 @@ impl ops::MulAssign<&Self> for Fixed {
}
impl ops::DivAssign<&Self> for Fixed {
- fn div_assign(&mut self, _rhs: &Self) {
- todo!()
+ fn div_assign(&mut self, rhs: &Self) {
+ self.0 *= get_factor();
+ self.0 /= &rhs.0;
}
}
diff --git a/src/numbers/gfixed.rs b/src/numbers/gfixed.rs
index 9b816ec..a47d9ea 100644
--- a/src/numbers/gfixed.rs
+++ b/src/numbers/gfixed.rs
@@ -238,9 +238,7 @@ impl ops::Sub for GuardedFixed {
impl ops::Mul for GuardedFixed {
type Output = Self;
- fn mul(self, _rhs: Self) -> Self::Output {
- todo!()
- }
+ fn mul(self, rhs: Self) -> Self::Output { Self(self.0 * rhs.0 / get_factor())}
}
impl ops::Div for GuardedFixed {
@@ -326,8 +324,9 @@ impl ops::MulAssign<&Self> for GuardedFixed {
}
impl ops::DivAssign<&Self> for GuardedFixed {
- fn div_assign(&mut self, _rhs: &Self) {
- todo!()
+ fn div_assign(&mut self, rhs: &Self) {
+ self.0 *= get_factor();
+ self.0 /= &rhs.0;
}
}
diff --git a/src/numbers/native.rs b/src/numbers/native.rs
index 5ded8aa..f33c764 100644
--- a/src/numbers/native.rs
+++ b/src/numbers/native.rs
@@ -109,9 +109,7 @@ impl ops::Sub for NativeFloat64 {
impl ops::Mul for NativeFloat64 {
type Output = NativeFloat64;
- fn mul(self, _rhs: Self) -> Self::Output {
- todo!()
- }
+ fn mul(self, rhs: Self) -> Self::Output { Self(self.0 * rhs.0) }
}
impl ops::Div for NativeFloat64 {
@@ -188,9 +186,7 @@ impl ops::MulAssign<&NativeFloat64> for NativeFloat64 {
}
impl ops::DivAssign<&NativeFloat64> for NativeFloat64 {
- fn div_assign(&mut self, _rhs: &NativeFloat64) {
- todo!()
- }
+ fn div_assign(&mut self, rhs: &NativeFloat64) { self.0 /= &rhs.0; }
}
impl ops::RemAssign<&NativeFloat64> for NativeFloat64 {
diff --git a/src/numbers/rational_num.rs b/src/numbers/rational_num.rs
index 8c8bf4e..859b6b5 100644
--- a/src/numbers/rational_num.rs
+++ b/src/numbers/rational_num.rs
@@ -186,9 +186,7 @@ impl ops::Sub for Rational {
impl ops::Mul for Rational {
type Output = Rational;
- fn mul(self, _rhs: Self) -> Self::Output {
- todo!()
- }
+ fn mul(self, rhs: Self) -> Self::Output { Self(self.0 * rhs.0) }
}
impl ops::Div for Rational {
@@ -265,9 +263,7 @@ impl ops::MulAssign<&Rational> for Rational {
}
impl ops::DivAssign<&Rational> for Rational {
- fn div_assign(&mut self, _rhs: &Rational) {
- todo!()
- }
+ fn div_assign(&mut self, rhs: &Rational) { self.0 /= &rhs.0 }
}
impl ops::RemAssign<&Rational> for Rational {
diff --git a/src/numbers/rational_rug.rs b/src/numbers/rational_rug.rs
index 3974d96..bbbfd07 100644
--- a/src/numbers/rational_rug.rs
+++ b/src/numbers/rational_rug.rs
@@ -185,9 +185,7 @@ impl ops::Sub for Rational {
impl ops::Mul for Rational {
type Output = Self;
- fn mul(self, _rhs: Self) -> Self::Output {
- todo!()
- }
+ fn mul(self, rhs: Self) -> Self::Output { Self(self.0 * rhs.0) }
}
impl ops::Div for Rational {
@@ -264,9 +262,7 @@ impl ops::MulAssign<&Self> for Rational {
}
impl ops::DivAssign<&Self> for Rational {
- fn div_assign(&mut self, _rhs: &Self) {
- todo!()
- }
+ fn div_assign(&mut self, rhs: &Self) { self.0 /= &rhs.0 }
}
impl ops::RemAssign<&Self> for Rational {
diff --git a/src/stv/gregory.rs b/src/stv/gregory.rs
index 1233e43..ade495f 100644
--- a/src/stv/gregory.rs
+++ b/src/stv/gregory.rs
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-use super::{ExclusionMethod, NextPreferencesEntry, STVError, STVOptions, SumSurplusTransfersMode, SurplusMethod, SurplusOrder};
+use super::{ExclusionMethod, STVError, STVOptions, SurplusMethod, SurplusOrder};
use super::sample;
use crate::constraints;
@@ -25,7 +25,6 @@ use crate::stv::transfers::TransferTable;
use crate::ties;
use std::cmp::max;
-use std::collections::HashMap;
use std::ops;
/// Distribute first preference votes according to the Gregory method
@@ -168,97 +167,23 @@ where
/// Return the denominator of the surplus fraction
///
/// Returns `None` if the value of transferable votes <= surplus (i.e. all transferable votes are transferred at values received).
-fn calculate_surplus_denom<'n, N: Number>(surplus: &N, transferable_ballots: &'n N, transferable_votes: &'n N, total_ballots: &'n N, total_votes: &'n N, weighted: bool, transferable_only: bool) -> Option<&'n N>
+fn calculate_surplus_denom<'n, N: Number>(surplus: &N, transferable_ballots: &'n N, transferable_votes: &'n N, total_ballots: &'n N, total_votes: &'n N, opts: &STVOptions) -> Option
where
for<'r> &'r N: ops::Sub<&'r N, Output=N>
{
- if transferable_only {
- let transferable_units = if weighted { transferable_votes } else { transferable_ballots };
+ if opts.transferable_only {
+ let transferable_units = if opts.surplus.is_weighted() { transferable_votes } else { transferable_ballots };
if transferable_votes > surplus {
- return Some(transferable_units);
+ return Some(transferable_units.clone());
} else {
return None;
}
} else {
- if weighted {
- return Some(total_votes);
+ if opts.surplus.is_weighted() {
+ return Some(total_votes.clone());
} else {
- return Some(total_ballots);
- }
- }
-}
-
-/// Return the reweighted value fraction of a parcel/vote after being transferred
-fn reweight_value_fraction(
- value_fraction: &N,
- surplus: &N,
- weighted: bool,
- surplus_fraction: &Option,
- surplus_denom: &Option<&N>,
- round_tvs: Option) -> N
-{
- let result;
-
- match surplus_denom {
- Some(v) => {
- if let Some(_) = round_tvs {
- // Rounding requested: use the rounded transfer value
- if weighted {
- result = value_fraction.clone() * surplus_fraction.as_ref().unwrap();
- } else {
- result = surplus_fraction.as_ref().unwrap().clone();
- }
- } else {
- // Avoid unnecessary rounding error by first multiplying by the surplus
- if weighted {
- result = value_fraction.clone() * surplus / *v;
- } else {
- result = surplus.clone() / *v;
- }
- }
- }
- None => {
- result = value_fraction.clone();
- }
- }
-
- return result;
-}
-
-/// Compute the number of votes to credit to a continuing candidate during a surplus transfer, based on [STVOptions::sum_surplus_transfers]
-fn sum_surplus_transfers(entry: &NextPreferencesEntry, orig_value_fraction: &N, surplus: &N, is_weighted: bool, surplus_fraction: &Option, surplus_denom: &Option<&N>, _state: &mut CountState, opts: &STVOptions) -> N
-where
- for<'r> &'r N: ops::Mul<&'r N, Output=N>,
- for<'r> &'r N: ops::Div<&'r N, Output=N>,
-{
- match opts.sum_surplus_transfers {
- SumSurplusTransfersMode::ByValue => {
- // Calculate transfer across all votes in this parcel
- let mut result = N::new();
- for vote in entry.votes.iter() {
- result += &vote.ballot.orig_value;
- }
- result *= reweight_value_fraction(orig_value_fraction, surplus, is_weighted, surplus_fraction, surplus_denom, opts.round_surplus_fractions);
- return result;
- }
- SumSurplusTransfersMode::PerBallot => {
- // Sum transfer per each individual ballot
- // TODO: This could be moved to distribute_surplus to avoid looping over the votes and calculating transfer values twice
- let mut new_value_fraction = reweight_value_fraction(orig_value_fraction, surplus, is_weighted, surplus_fraction, surplus_denom, opts.round_surplus_fractions);
- if let Some(dps) = opts.round_votes {
- new_value_fraction.floor_mut(dps);
- }
-
- let mut result = N::new();
- for vote in entry.votes.iter() {
- let mut vote_value = &new_value_fraction * &vote.ballot.orig_value;
- if let Some(dps) = opts.round_votes {
- vote_value.floor_mut(dps);
- }
- result += vote_value;
- }
- return result;
+ return Some(total_ballots.clone());
}
}
}
@@ -320,13 +245,7 @@ where
parcels_next_prefs.push((parcel.value_fraction, result));
}
- // Calculate surplus fraction
-
- let is_weighted = match opts.surplus {
- SurplusMethod::WIG => { true }
- SurplusMethod::UIG | SurplusMethod::EG => { false }
- _ => unreachable!()
- };
+ // Calculate and print surplus fraction
let total_ballots = &transferable_ballots + &exhausted_ballots;
let total_votes = &transferable_votes + &exhausted_votes;
@@ -334,15 +253,20 @@ where
let count_card = state.candidates.get_mut(elected_candidate).unwrap();
count_card.ballot_transfers = -&total_ballots;
- let surplus_denom = calculate_surplus_denom(&surplus, &transferable_ballots, &transferable_votes, &total_ballots, &total_votes, is_weighted, opts.transferable_only);
+ let mut surplus_denom = calculate_surplus_denom(&surplus, &transferable_ballots, &transferable_votes, &total_ballots, &total_votes, opts);
+ let surplus_numer;
let mut surplus_fraction;
- match surplus_denom {
+ match &surplus_denom {
Some(v) => {
surplus_fraction = Some(surplus.clone() / v);
// Round down if requested
if let Some(dps) = opts.round_surplus_fractions {
surplus_fraction.as_mut().unwrap().floor_mut(dps);
+ surplus_numer = surplus_fraction.clone();
+ surplus_denom = None;
+ } else {
+ surplus_numer = Some(surplus.clone());
}
if opts.transferable_only {
@@ -361,6 +285,8 @@ where
}
None => {
surplus_fraction = None;
+ surplus_numer = None;
+ surplus_denom = None;
// This can only happen if --transferable-only
if transferable_ballots == N::one() {
@@ -373,41 +299,44 @@ where
// Reweight and transfer parcels
- let mut candidate_transfers: HashMap<&Candidate, N> = HashMap::new();
- for candidate in state.election.candidates.iter() {
- candidate_transfers.insert(candidate, N::new());
- }
- let mut exhausted_transfers = N::new();
+ let mut transfer_table = TransferTable::new();
for (value_fraction, result) in parcels_next_prefs {
for (candidate, entry) in result.candidates.into_iter() {
// Record transfers
- // TODO: Is there a better way of writing this?
- let transfers_orig = candidate_transfers.remove(candidate).unwrap();
- let transfers_add = sum_surplus_transfers(&entry, &value_fraction, &surplus, is_weighted, &surplus_fraction, &surplus_denom, state, opts);
- candidate_transfers.insert(candidate, transfers_orig + transfers_add);
+ transfer_table.add_transfers(&value_fraction, candidate, &entry.num_ballots);
+
+ let mut new_value_fraction;
+ if opts.surplus.is_weighted() {
+ new_value_fraction = value_fraction.clone();
+ new_value_fraction *= surplus_numer.as_ref().unwrap(); // Guaranteed to be Some in WIGM
+ if let Some(n) = &surplus_denom {
+ new_value_fraction /= n;
+ }
+ } else {
+ if let Some(sf) = &surplus_fraction {
+ new_value_fraction = sf.clone();
+ } else {
+ new_value_fraction = value_fraction.clone();
+ }
+ }
+
+ if let Some(dps) = opts.round_values {
+ new_value_fraction.floor_mut(dps);
+ }
// Transfer candidate votes
let parcel = Parcel {
votes: entry.votes,
- value_fraction: reweight_value_fraction(&value_fraction, &surplus, is_weighted, &surplus_fraction, &surplus_denom, opts.round_surplus_fractions),
+ value_fraction: new_value_fraction,
source_order: state.num_elected + state.num_excluded,
};
let count_card = state.candidates.get_mut(candidate).unwrap();
- count_card.ballot_transfers += parcel.num_ballots();
count_card.parcels.push(parcel);
}
// Record exhausted votes
- if opts.transferable_only {
- if transferable_votes > surplus {
- // No ballots exhaust
- } else {
- exhausted_transfers += &surplus - &transferable_votes;
- }
- } else {
- exhausted_transfers += sum_surplus_transfers(&result.exhausted, &value_fraction, &surplus, is_weighted, &surplus_fraction, &surplus_denom, state, opts);
- }
+ transfer_table.add_exhausted(&value_fraction, &result.exhausted.num_ballots);
// Transfer exhausted votes
let parcel = Parcel {
@@ -415,29 +344,13 @@ where
value_fraction: value_fraction, // TODO: Reweight exhausted votes
source_order: state.num_elected + state.num_excluded,
};
- state.exhausted.ballot_transfers += parcel.num_ballots();
state.exhausted.parcels.push(parcel);
}
let mut checksum = N::new();
// Credit transferred votes
- // ballot_transfers updated above
- for (candidate, mut votes) in candidate_transfers {
- if let Some(dps) = opts.round_votes {
- votes.floor_mut(dps);
- }
- let count_card = state.candidates.get_mut(candidate).unwrap();
- count_card.transfer(&votes);
- checksum += votes;
- }
-
- // Credit exhausted votes
- if let Some(dps) = opts.round_votes {
- exhausted_transfers.floor_mut(dps);
- }
- state.exhausted.transfer(&exhausted_transfers);
- checksum += exhausted_transfers;
+ checksum += transfer_table.apply_to(state, opts, Some(&surplus), &surplus_numer, &surplus_denom);
// Finalise candidate votes
let count_card = state.candidates.get_mut(elected_candidate).unwrap();
@@ -690,7 +603,7 @@ where
}
// Credit transferred votes
- checksum += transfer_table.apply_to(state, opts);
+ checksum += transfer_table.apply_to(state, opts, None, &None, &None);
if !votes_remain {
// Finalise candidate votes
diff --git a/src/stv/mod.rs b/src/stv/mod.rs
index 5e99aa1..bd075a2 100644
--- a/src/stv/mod.rs
+++ b/src/stv/mod.rs
@@ -403,6 +403,15 @@ impl SurplusMethod {
SurplusMethod::Hare => "--surplus hare",
}.to_string()
}
+
+ /// Returns `true` if this is a weighted method
+ pub fn is_weighted(&self) -> bool {
+ return match self {
+ SurplusMethod::WIG => { true }
+ SurplusMethod::UIG | SurplusMethod::EG => { false }
+ _ => unreachable!()
+ };
+ }
}
impl> From for SurplusMethod {
diff --git a/src/stv/transfers.rs b/src/stv/transfers.rs
index ad3b1ec..cfe8ba5 100644
--- a/src/stv/transfers.rs
+++ b/src/stv/transfers.rs
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-use super::STVOptions;
+use super::{STVOptions, SumSurplusTransfersMode};
use crate::election::{Candidate, CountState};
use crate::numbers::Number;
@@ -74,8 +74,9 @@ impl<'e, N: Number> TransferTable<'e, N> {
/// Apply the transfers described in the table to the count sheet
///
/// Credit continuing candidates and exhausted pile with the appropriate number of ballot papers and votes.
- pub fn apply_to(&self, state: &mut CountState, opts: &STVOptions) -> N {
- // TODO: SumSurplusTransfers
+ pub fn apply_to(&self, state: &mut CountState, opts: &STVOptions, surplus: Option<&N>, surplus_numer: &Option, surplus_denom: &Option) -> N {
+ // Use weighted rules if exclusion or WIGM
+ let is_weighted = surplus.is_none() || opts.surplus.is_weighted();
let mut checksum = N::new();
@@ -84,13 +85,73 @@ impl<'e, N: Number> TransferTable<'e, N> {
let mut votes_transferred = N::new();
let mut ballots_transferred = N::new();
- for column in self.columns.iter() {
- if let Some(cell) = column.cells.get(*candidate) {
- votes_transferred += cell.ballots.clone() * &column.value_fraction;
- ballots_transferred += &cell.ballots;
+ // If exclusion, or surplus at present value, or SumSurplusTransfersMode::ByValue
+ if surplus_numer.is_none() || opts.sum_surplus_transfers == SumSurplusTransfersMode::ByValue {
+ // Calculate transfer across all votes in this parcel
+ for column in self.columns.iter() {
+ if let Some(cell) = column.cells.get(*candidate) {
+ if is_weighted {
+ votes_transferred += cell.ballots.clone() * &column.value_fraction;
+ }
+ ballots_transferred += &cell.ballots;
+ }
}
+
+ if !is_weighted {
+ votes_transferred = ballots_transferred.clone();
+ }
+
+ // If surplus, multiply by surplus fraction
+ if let Some(n) = &surplus_numer {
+ votes_transferred *= n;
+ }
+ if let Some(n) = &surplus_denom {
+ votes_transferred /= n;
+ }
+ } else if opts.sum_surplus_transfers == SumSurplusTransfersMode::PerBallot {
+ // Sum transfer per each individual ballot
+ for column in self.columns.iter() {
+ if let Some(cell) = column.cells.get(*candidate) {
+ ballots_transferred += &cell.ballots;
+
+ let mut new_value_fraction;
+ if is_weighted {
+ new_value_fraction = column.value_fraction.clone();
+ // If surplus, multiply by surplus fraction
+ if let Some(n) = &surplus_numer {
+ new_value_fraction *= n;
+ }
+ if let Some(n) = &surplus_denom {
+ new_value_fraction /= n;
+ }
+ // Round if required
+ if let Some(dps) = opts.round_values {
+ new_value_fraction.floor_mut(dps);
+ }
+ } else {
+ if let Some(n) = &surplus_numer {
+ new_value_fraction = n.clone();
+ } else {
+ // Transferred at original value
+ new_value_fraction = column.value_fraction.clone();
+ }
+ if let Some(n) = &surplus_denom {
+ new_value_fraction /= n;
+ }
+ // Round if required
+ if let Some(dps) = opts.round_values {
+ new_value_fraction.floor_mut(dps);
+ }
+ }
+
+ votes_transferred += cell.ballots.clone() * new_value_fraction;
+ }
+ }
+ } else {
+ unreachable!();
}
+ // Round if required
if let Some(dps) = opts.round_votes {
votes_transferred.floor_mut(dps);
}
@@ -102,23 +163,56 @@ impl<'e, N: Number> TransferTable<'e, N> {
}
// Credit exhausted votes
- let mut votes_transferred = N::new();
- let mut ballots_transferred = N::new();
-
- for column in self.columns.iter() {
- votes_transferred += column.exhausted.ballots.clone() * &column.value_fraction;
- ballots_transferred += &column.exhausted.ballots;
+ // If exclusion or not --transferable-only
+ if surplus.is_none() || !opts.transferable_only {
+ // Standard rules
+ let mut votes_transferred = N::new();
+ let mut ballots_transferred = N::new();
+
+ for column in self.columns.iter() {
+ if is_weighted {
+ votes_transferred += column.exhausted.ballots.clone() * &column.value_fraction;
+ }
+ ballots_transferred += &column.exhausted.ballots;
+ }
+
+ if !is_weighted {
+ votes_transferred = ballots_transferred.clone();
+ }
+
+ // If surplus, multiply by surplus fraction
+ if let Some(n) = &surplus_numer {
+ votes_transferred *= n;
+ }
+ if let Some(n) = &surplus_denom {
+ votes_transferred /= n;
+ }
+
+ // Round if required
+ if let Some(dps) = opts.round_votes {
+ votes_transferred.floor_mut(dps);
+ }
+
+ state.exhausted.transfer(&votes_transferred);
+ state.exhausted.ballot_transfers += ballots_transferred;
+
+ checksum += votes_transferred;
+ } else {
+ // Credit only nontransferable difference
+ if surplus_numer.is_none() {
+ // TODO: Is there a purer way of calculating this?
+ let difference = surplus.unwrap().clone() - &checksum;
+ state.exhausted.transfer(&difference);
+ checksum += difference;
+
+ for column in self.columns.iter() {
+ state.exhausted.ballot_transfers += &column.exhausted.ballots;
+ }
+ } else {
+ // No ballots exhaust
+ }
}
- if let Some(dps) = opts.round_votes {
- votes_transferred.floor_mut(dps);
- }
-
- state.exhausted.transfer(&votes_transferred);
- state.exhausted.ballot_transfers += ballots_transferred;
-
- checksum += votes_transferred;
-
return checksum;
}
}