Implement TransferTable for surpluses (WIP)
This commit is contained in:
parent
fbdc32ba30
commit
056242514d
@ -206,9 +206,7 @@ impl ops::Sub for Fixed {
|
|||||||
|
|
||||||
impl ops::Mul for Fixed {
|
impl ops::Mul for Fixed {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn mul(self, _rhs: Self) -> Self::Output {
|
fn mul(self, rhs: Self) -> Self::Output { Self(self.0 * rhs.0 / get_factor()) }
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ops::Div for Fixed {
|
impl ops::Div for Fixed {
|
||||||
@ -294,8 +292,9 @@ impl ops::MulAssign<&Self> for Fixed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ops::DivAssign<&Self> for Fixed {
|
impl ops::DivAssign<&Self> for Fixed {
|
||||||
fn div_assign(&mut self, _rhs: &Self) {
|
fn div_assign(&mut self, rhs: &Self) {
|
||||||
todo!()
|
self.0 *= get_factor();
|
||||||
|
self.0 /= &rhs.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,9 +238,7 @@ impl ops::Sub for GuardedFixed {
|
|||||||
|
|
||||||
impl ops::Mul for GuardedFixed {
|
impl ops::Mul for GuardedFixed {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn mul(self, _rhs: Self) -> Self::Output {
|
fn mul(self, rhs: Self) -> Self::Output { Self(self.0 * rhs.0 / get_factor())}
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ops::Div for GuardedFixed {
|
impl ops::Div for GuardedFixed {
|
||||||
@ -326,8 +324,9 @@ impl ops::MulAssign<&Self> for GuardedFixed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ops::DivAssign<&Self> for GuardedFixed {
|
impl ops::DivAssign<&Self> for GuardedFixed {
|
||||||
fn div_assign(&mut self, _rhs: &Self) {
|
fn div_assign(&mut self, rhs: &Self) {
|
||||||
todo!()
|
self.0 *= get_factor();
|
||||||
|
self.0 /= &rhs.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,9 +109,7 @@ impl ops::Sub for NativeFloat64 {
|
|||||||
|
|
||||||
impl ops::Mul for NativeFloat64 {
|
impl ops::Mul for NativeFloat64 {
|
||||||
type Output = NativeFloat64;
|
type Output = NativeFloat64;
|
||||||
fn mul(self, _rhs: Self) -> Self::Output {
|
fn mul(self, rhs: Self) -> Self::Output { Self(self.0 * rhs.0) }
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ops::Div for NativeFloat64 {
|
impl ops::Div for NativeFloat64 {
|
||||||
@ -188,9 +186,7 @@ impl ops::MulAssign<&NativeFloat64> for NativeFloat64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ops::DivAssign<&NativeFloat64> for NativeFloat64 {
|
impl ops::DivAssign<&NativeFloat64> for NativeFloat64 {
|
||||||
fn div_assign(&mut self, _rhs: &NativeFloat64) {
|
fn div_assign(&mut self, rhs: &NativeFloat64) { self.0 /= &rhs.0; }
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ops::RemAssign<&NativeFloat64> for NativeFloat64 {
|
impl ops::RemAssign<&NativeFloat64> for NativeFloat64 {
|
||||||
|
@ -186,9 +186,7 @@ impl ops::Sub for Rational {
|
|||||||
|
|
||||||
impl ops::Mul for Rational {
|
impl ops::Mul for Rational {
|
||||||
type Output = Rational;
|
type Output = Rational;
|
||||||
fn mul(self, _rhs: Self) -> Self::Output {
|
fn mul(self, rhs: Self) -> Self::Output { Self(self.0 * rhs.0) }
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ops::Div for Rational {
|
impl ops::Div for Rational {
|
||||||
@ -265,9 +263,7 @@ impl ops::MulAssign<&Rational> for Rational {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ops::DivAssign<&Rational> for Rational {
|
impl ops::DivAssign<&Rational> for Rational {
|
||||||
fn div_assign(&mut self, _rhs: &Rational) {
|
fn div_assign(&mut self, rhs: &Rational) { self.0 /= &rhs.0 }
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ops::RemAssign<&Rational> for Rational {
|
impl ops::RemAssign<&Rational> for Rational {
|
||||||
|
@ -185,9 +185,7 @@ impl ops::Sub for Rational {
|
|||||||
|
|
||||||
impl ops::Mul for Rational {
|
impl ops::Mul for Rational {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn mul(self, _rhs: Self) -> Self::Output {
|
fn mul(self, rhs: Self) -> Self::Output { Self(self.0 * rhs.0) }
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ops::Div for Rational {
|
impl ops::Div for Rational {
|
||||||
@ -264,9 +262,7 @@ impl ops::MulAssign<&Self> for Rational {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ops::DivAssign<&Self> for Rational {
|
impl ops::DivAssign<&Self> for Rational {
|
||||||
fn div_assign(&mut self, _rhs: &Self) {
|
fn div_assign(&mut self, rhs: &Self) { self.0 /= &rhs.0 }
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ops::RemAssign<&Self> for Rational {
|
impl ops::RemAssign<&Self> for Rational {
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* 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 super::{ExclusionMethod, NextPreferencesEntry, STVError, STVOptions, SumSurplusTransfersMode, SurplusMethod, SurplusOrder};
|
use super::{ExclusionMethod, STVError, STVOptions, SurplusMethod, SurplusOrder};
|
||||||
use super::sample;
|
use super::sample;
|
||||||
|
|
||||||
use crate::constraints;
|
use crate::constraints;
|
||||||
@ -25,7 +25,6 @@ use crate::stv::transfers::TransferTable;
|
|||||||
use crate::ties;
|
use crate::ties;
|
||||||
|
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::ops;
|
use std::ops;
|
||||||
|
|
||||||
/// Distribute first preference votes according to the Gregory method
|
/// Distribute first preference votes according to the Gregory method
|
||||||
@ -168,97 +167,23 @@ where
|
|||||||
/// Return the denominator of the surplus fraction
|
/// 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).
|
/// 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<N>
|
||||||
where
|
where
|
||||||
for<'r> &'r N: ops::Sub<&'r N, Output=N>
|
for<'r> &'r N: ops::Sub<&'r N, Output=N>
|
||||||
{
|
{
|
||||||
if transferable_only {
|
if opts.transferable_only {
|
||||||
let transferable_units = if weighted { transferable_votes } else { transferable_ballots };
|
let transferable_units = if opts.surplus.is_weighted() { transferable_votes } else { transferable_ballots };
|
||||||
|
|
||||||
if transferable_votes > surplus {
|
if transferable_votes > surplus {
|
||||||
return Some(transferable_units);
|
return Some(transferable_units.clone());
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if weighted {
|
if opts.surplus.is_weighted() {
|
||||||
return Some(total_votes);
|
return Some(total_votes.clone());
|
||||||
} else {
|
} else {
|
||||||
return Some(total_ballots);
|
return Some(total_ballots.clone());
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the reweighted value fraction of a parcel/vote after being transferred
|
|
||||||
fn reweight_value_fraction<N: Number>(
|
|
||||||
value_fraction: &N,
|
|
||||||
surplus: &N,
|
|
||||||
weighted: bool,
|
|
||||||
surplus_fraction: &Option<N>,
|
|
||||||
surplus_denom: &Option<&N>,
|
|
||||||
round_tvs: Option<usize>) -> 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<N: Number>(entry: &NextPreferencesEntry<N>, orig_value_fraction: &N, surplus: &N, is_weighted: bool, surplus_fraction: &Option<N>, surplus_denom: &Option<&N>, _state: &mut CountState<N>, 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -320,13 +245,7 @@ where
|
|||||||
parcels_next_prefs.push((parcel.value_fraction, result));
|
parcels_next_prefs.push((parcel.value_fraction, result));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate surplus fraction
|
// Calculate and print surplus fraction
|
||||||
|
|
||||||
let is_weighted = match opts.surplus {
|
|
||||||
SurplusMethod::WIG => { true }
|
|
||||||
SurplusMethod::UIG | SurplusMethod::EG => { false }
|
|
||||||
_ => unreachable!()
|
|
||||||
};
|
|
||||||
|
|
||||||
let total_ballots = &transferable_ballots + &exhausted_ballots;
|
let total_ballots = &transferable_ballots + &exhausted_ballots;
|
||||||
let total_votes = &transferable_votes + &exhausted_votes;
|
let total_votes = &transferable_votes + &exhausted_votes;
|
||||||
@ -334,15 +253,20 @@ where
|
|||||||
let count_card = state.candidates.get_mut(elected_candidate).unwrap();
|
let count_card = state.candidates.get_mut(elected_candidate).unwrap();
|
||||||
count_card.ballot_transfers = -&total_ballots;
|
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;
|
let mut surplus_fraction;
|
||||||
match surplus_denom {
|
match &surplus_denom {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
surplus_fraction = Some(surplus.clone() / v);
|
surplus_fraction = Some(surplus.clone() / v);
|
||||||
|
|
||||||
// Round down if requested
|
// Round down if requested
|
||||||
if let Some(dps) = opts.round_surplus_fractions {
|
if let Some(dps) = opts.round_surplus_fractions {
|
||||||
surplus_fraction.as_mut().unwrap().floor_mut(dps);
|
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 {
|
if opts.transferable_only {
|
||||||
@ -361,6 +285,8 @@ where
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
surplus_fraction = None;
|
surplus_fraction = None;
|
||||||
|
surplus_numer = None;
|
||||||
|
surplus_denom = None;
|
||||||
|
|
||||||
// This can only happen if --transferable-only
|
// This can only happen if --transferable-only
|
||||||
if transferable_ballots == N::one() {
|
if transferable_ballots == N::one() {
|
||||||
@ -373,41 +299,44 @@ where
|
|||||||
|
|
||||||
// Reweight and transfer parcels
|
// Reweight and transfer parcels
|
||||||
|
|
||||||
let mut candidate_transfers: HashMap<&Candidate, N> = HashMap::new();
|
let mut transfer_table = TransferTable::new();
|
||||||
for candidate in state.election.candidates.iter() {
|
|
||||||
candidate_transfers.insert(candidate, N::new());
|
|
||||||
}
|
|
||||||
let mut exhausted_transfers = N::new();
|
|
||||||
|
|
||||||
for (value_fraction, result) in parcels_next_prefs {
|
for (value_fraction, result) in parcels_next_prefs {
|
||||||
for (candidate, entry) in result.candidates.into_iter() {
|
for (candidate, entry) in result.candidates.into_iter() {
|
||||||
// Record transfers
|
// Record transfers
|
||||||
// TODO: Is there a better way of writing this?
|
transfer_table.add_transfers(&value_fraction, candidate, &entry.num_ballots);
|
||||||
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);
|
let mut new_value_fraction;
|
||||||
candidate_transfers.insert(candidate, transfers_orig + transfers_add);
|
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
|
// Transfer candidate votes
|
||||||
let parcel = Parcel {
|
let parcel = Parcel {
|
||||||
votes: entry.votes,
|
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,
|
source_order: state.num_elected + state.num_excluded,
|
||||||
};
|
};
|
||||||
let count_card = state.candidates.get_mut(candidate).unwrap();
|
let count_card = state.candidates.get_mut(candidate).unwrap();
|
||||||
count_card.ballot_transfers += parcel.num_ballots();
|
|
||||||
count_card.parcels.push(parcel);
|
count_card.parcels.push(parcel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record exhausted votes
|
// Record exhausted votes
|
||||||
if opts.transferable_only {
|
transfer_table.add_exhausted(&value_fraction, &result.exhausted.num_ballots);
|
||||||
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 exhausted votes
|
// Transfer exhausted votes
|
||||||
let parcel = Parcel {
|
let parcel = Parcel {
|
||||||
@ -415,29 +344,13 @@ where
|
|||||||
value_fraction: value_fraction, // TODO: Reweight exhausted votes
|
value_fraction: value_fraction, // TODO: Reweight exhausted votes
|
||||||
source_order: state.num_elected + state.num_excluded,
|
source_order: state.num_elected + state.num_excluded,
|
||||||
};
|
};
|
||||||
state.exhausted.ballot_transfers += parcel.num_ballots();
|
|
||||||
state.exhausted.parcels.push(parcel);
|
state.exhausted.parcels.push(parcel);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut checksum = N::new();
|
let mut checksum = N::new();
|
||||||
|
|
||||||
// Credit transferred votes
|
// Credit transferred votes
|
||||||
// ballot_transfers updated above
|
checksum += transfer_table.apply_to(state, opts, Some(&surplus), &surplus_numer, &surplus_denom);
|
||||||
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;
|
|
||||||
|
|
||||||
// Finalise candidate votes
|
// Finalise candidate votes
|
||||||
let count_card = state.candidates.get_mut(elected_candidate).unwrap();
|
let count_card = state.candidates.get_mut(elected_candidate).unwrap();
|
||||||
@ -690,7 +603,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Credit transferred votes
|
// Credit transferred votes
|
||||||
checksum += transfer_table.apply_to(state, opts);
|
checksum += transfer_table.apply_to(state, opts, None, &None, &None);
|
||||||
|
|
||||||
if !votes_remain {
|
if !votes_remain {
|
||||||
// Finalise candidate votes
|
// Finalise candidate votes
|
||||||
|
@ -403,6 +403,15 @@ impl SurplusMethod {
|
|||||||
SurplusMethod::Hare => "--surplus hare",
|
SurplusMethod::Hare => "--surplus hare",
|
||||||
}.to_string()
|
}.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<S: AsRef<str>> From<S> for SurplusMethod {
|
impl<S: AsRef<str>> From<S> for SurplusMethod {
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* 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 super::STVOptions;
|
use super::{STVOptions, SumSurplusTransfersMode};
|
||||||
|
|
||||||
use crate::election::{Candidate, CountState};
|
use crate::election::{Candidate, CountState};
|
||||||
use crate::numbers::Number;
|
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
|
/// 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.
|
/// Credit continuing candidates and exhausted pile with the appropriate number of ballot papers and votes.
|
||||||
pub fn apply_to(&self, state: &mut CountState<N>, opts: &STVOptions) -> N {
|
pub fn apply_to(&self, state: &mut CountState<N>, opts: &STVOptions, surplus: Option<&N>, surplus_numer: &Option<N>, surplus_denom: &Option<N>) -> N {
|
||||||
// TODO: SumSurplusTransfers
|
// Use weighted rules if exclusion or WIGM
|
||||||
|
let is_weighted = surplus.is_none() || opts.surplus.is_weighted();
|
||||||
|
|
||||||
let mut checksum = N::new();
|
let mut checksum = N::new();
|
||||||
|
|
||||||
@ -84,13 +85,73 @@ impl<'e, N: Number> TransferTable<'e, N> {
|
|||||||
let mut votes_transferred = N::new();
|
let mut votes_transferred = N::new();
|
||||||
let mut ballots_transferred = N::new();
|
let mut ballots_transferred = N::new();
|
||||||
|
|
||||||
for column in self.columns.iter() {
|
// If exclusion, or surplus at present value, or SumSurplusTransfersMode::ByValue
|
||||||
if let Some(cell) = column.cells.get(*candidate) {
|
if surplus_numer.is_none() || opts.sum_surplus_transfers == SumSurplusTransfersMode::ByValue {
|
||||||
votes_transferred += cell.ballots.clone() * &column.value_fraction;
|
// Calculate transfer across all votes in this parcel
|
||||||
ballots_transferred += &cell.ballots;
|
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 {
|
if let Some(dps) = opts.round_votes {
|
||||||
votes_transferred.floor_mut(dps);
|
votes_transferred.floor_mut(dps);
|
||||||
}
|
}
|
||||||
@ -102,23 +163,56 @@ impl<'e, N: Number> TransferTable<'e, N> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Credit exhausted votes
|
// Credit exhausted votes
|
||||||
let mut votes_transferred = N::new();
|
// If exclusion or not --transferable-only
|
||||||
let mut ballots_transferred = N::new();
|
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() {
|
for column in self.columns.iter() {
|
||||||
votes_transferred += column.exhausted.ballots.clone() * &column.value_fraction;
|
if is_weighted {
|
||||||
ballots_transferred += &column.exhausted.ballots;
|
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;
|
return checksum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user