Allow configuring --round-votes

Fix bug with display of negative Rational's
This commit is contained in:
RunasSudo 2021-05-29 17:51:45 +10:00
parent 32234ad13b
commit 77cf60c21f
No known key found for this signature in database
GPG Key ID: 7234E476BF21C61A
5 changed files with 207 additions and 146 deletions

View File

@ -21,9 +21,9 @@ mod numbers;
mod stv; mod stv;
use crate::election::{Candidate, CandidateState, CountCard, CountState, CountStateOrRef, Election, StageResult}; use crate::election::{Candidate, CandidateState, CountCard, CountState, CountStateOrRef, Election, StageResult};
use crate::numbers::{NativeFloat, Number, Rational}; use crate::numbers::{NativeFloat64, Number, Rational};
use clap::Clap; use clap::{AppSettings, Clap};
use git_version::git_version; use git_version::git_version;
use std::fs::File; use std::fs::File;
@ -47,12 +47,27 @@ enum Command {
/// Count a single transferable vote (STV) election /// Count a single transferable vote (STV) election
#[derive(Clap)] #[derive(Clap)]
#[clap(setting=AppSettings::DeriveDisplayOrder, setting=AppSettings::UnifiedHelpMessage)]
struct STV { struct STV {
// -- File input --
/// Path to the BLT file to be counted /// Path to the BLT file to be counted
filename: String, filename: String,
// -- Numbers settings --
/// Numbers mode /// Numbers mode
#[clap(short, long, possible_values(&["rational", "native"]), default_value="rational")] #[clap(short, long, possible_values(&["rational", "float64"]), default_value="rational")]
numbers: String, numbers: String,
// -- Rounding settings --
/// Round votes to specified decimal places
#[clap(long)]
round_votes: Option<usize>,
// -- Display settings --
/// Hide excluded candidates from results report /// Hide excluded candidates from results report
#[clap(long)] #[clap(long)]
hide_excluded: bool, hide_excluded: bool,
@ -77,8 +92,8 @@ fn main() {
if cmd_opts.numbers == "rational" { if cmd_opts.numbers == "rational" {
let election: Election<Rational> = Election::from_blt(lines); let election: Election<Rational> = Election::from_blt(lines);
count_election(election, cmd_opts); count_election(election, cmd_opts);
} else if cmd_opts.numbers == "native" { } else if cmd_opts.numbers == "float64" {
let election: Election<NativeFloat> = Election::from_blt(lines); let election: Election<NativeFloat64> = Election::from_blt(lines);
count_election(election, cmd_opts); count_election(election, cmd_opts);
} }
} }
@ -88,57 +103,28 @@ where
for<'r> &'r N: ops::Sub<&'r N, Output=N>, for<'r> &'r N: ops::Sub<&'r N, Output=N>,
for<'r> &'r N: ops::Neg<Output=N> for<'r> &'r N: ops::Neg<Output=N>
{ {
// Copy applicable options
let stv_opts = stv::STVOptions {
round_votes: cmd_opts.round_votes,
};
// Initialise count state // Initialise count state
let mut state = CountState::new(&election); let mut state = CountState::new(&election);
// Distribute first preferences // Distribute first preferences
stv::distribute_first_preferences(&mut state); stv::count_init(&mut state, &stv_opts);
stv::calculate_quota(&mut state);
stv::elect_meeting_quota(&mut state);
// Display // Display
let mut stage_num = 1; let mut stage_num = 1;
make_and_print_result(stage_num, &state, &cmd_opts); make_and_print_result(stage_num, &state, &cmd_opts);
loop { loop {
state.logger.entries.clear(); let is_done = stv::count_one_stage(&mut state, &stv_opts);
state.step_all(); if is_done {
stage_num += 1;
// Finish count
if stv::finished_before_stage(&state) {
break; break;
} }
stage_num += 1;
// Continue exclusions make_and_print_result(stage_num, &state, &cmd_opts);
if stv::continue_exclusion(&mut state) {
stv::elect_meeting_quota(&mut state);
make_and_print_result(stage_num, &state, &cmd_opts);
continue;
}
// Distribute surpluses
if stv::distribute_surpluses(&mut state) {
stv::elect_meeting_quota(&mut state);
make_and_print_result(stage_num, &state, &cmd_opts);
continue;
}
// Attempt bulk election
if stv::bulk_elect(&mut state) {
stv::elect_meeting_quota(&mut state);
make_and_print_result(stage_num, &state, &cmd_opts);
continue;
}
// Exclude lowest hopeful
if stv::exclude_hopefuls(&mut state) {
stv::elect_meeting_quota(&mut state);
make_and_print_result(stage_num, &state, &cmd_opts);
continue;
}
todo!();
} }
println!("Count complete. The winning candidates are, in order of election:"); println!("Count complete. The winning candidates are, in order of election:");

View File

@ -30,7 +30,7 @@ pub trait Number: NumRef + NumAssignRef + ops::Neg<Output=Self> + PartialOrd + A
fn new() -> Self; fn new() -> Self;
fn from(n: usize) -> Self; fn from(n: usize) -> Self;
fn floor_mut(&mut self); fn floor_mut(&mut self, dps: usize);
fn parse(s: &str) -> Self { fn parse(s: &str) -> Self {
if let Ok(value) = Self::from_str_radix(s, 10) { if let Ok(value) = Self::from_str_radix(s, 10) {
@ -41,5 +41,5 @@ pub trait Number: NumRef + NumAssignRef + ops::Neg<Output=Self> + PartialOrd + A
} }
} }
pub use self::native::NativeFloat; pub use self::native::NativeFloat64;
pub use self::rational::Rational; pub use self::rational::Rational;

View File

@ -25,218 +25,223 @@ use std::num::ParseIntError;
use std::fmt; use std::fmt;
use std::ops; use std::ops;
pub struct NativeFloat(f32); type ImplType = f64;
impl Number for NativeFloat { pub struct NativeFloat64(ImplType);
impl Number for NativeFloat64 {
fn new() -> Self { Self(0.0) } fn new() -> Self { Self(0.0) }
fn from(n: usize) -> Self { Self(n as f32) } fn from(n: usize) -> Self { Self(n as ImplType) }
fn floor_mut(&mut self) { self.0 = self.0.floor() } fn floor_mut(&mut self, dps: usize) {
let factor = 10.0_f64.powi(dps as i32);
self.0 = (self.0 * factor).floor() / factor;
}
} }
impl Num for NativeFloat { impl Num for NativeFloat64 {
type FromStrRadixErr = ParseIntError; type FromStrRadixErr = ParseIntError;
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> { fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
match i32::from_str_radix(str, radix) { match i64::from_str_radix(str, radix) {
Ok(value) => Ok(Self(value as f32)), Ok(value) => Ok(Self(value as ImplType)),
Err(err) => Err(err) Err(err) => Err(err)
} }
} }
} }
impl Assign for NativeFloat { impl Assign for NativeFloat64 {
fn assign(&mut self, src: Self) { self.0 = src.0 } fn assign(&mut self, src: Self) { self.0 = src.0 }
} }
impl Assign<&NativeFloat> for NativeFloat { impl Assign<&NativeFloat64> for NativeFloat64 {
fn assign(&mut self, src: &NativeFloat) { self.0 = src.0 } fn assign(&mut self, src: &NativeFloat64) { self.0 = src.0 }
} }
impl Clone for NativeFloat { impl Clone for NativeFloat64 {
fn clone(&self) -> Self { Self(self.0) } fn clone(&self) -> Self { Self(self.0) }
} }
impl fmt::Display for NativeFloat { impl fmt::Display for NativeFloat64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) }
} }
impl One for NativeFloat { impl One for NativeFloat64 {
fn one() -> Self { Self(1.0) } fn one() -> Self { Self(1.0) }
} }
impl Zero for NativeFloat { impl Zero for NativeFloat64 {
fn zero() -> Self { Self::new() } fn zero() -> Self { Self::new() }
fn is_zero(&self) -> bool { self.0.is_zero() } fn is_zero(&self) -> bool { self.0.is_zero() }
} }
impl PartialEq for NativeFloat { impl PartialEq for NativeFloat64 {
fn eq(&self, _other: &Self) -> bool { fn eq(&self, _other: &Self) -> bool {
todo!() todo!()
} }
} }
impl PartialOrd for NativeFloat { impl PartialOrd for NativeFloat64 {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { self.0.partial_cmp(&other.0) } fn partial_cmp(&self, other: &Self) -> Option<Ordering> { self.0.partial_cmp(&other.0) }
} }
impl ops::Neg for NativeFloat { impl ops::Neg for NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn neg(self) -> Self::Output { Self(-self.0) } fn neg(self) -> Self::Output { Self(-self.0) }
} }
impl ops::Add for NativeFloat { impl ops::Add for NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn add(self, _rhs: Self) -> Self::Output { fn add(self, _rhs: Self) -> Self::Output {
todo!() todo!()
} }
} }
impl ops::Sub for NativeFloat { impl ops::Sub for NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn sub(self, _rhs: Self) -> Self::Output { fn sub(self, _rhs: Self) -> Self::Output {
todo!() todo!()
} }
} }
impl ops::Mul for NativeFloat { impl ops::Mul for NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn mul(self, _rhs: Self) -> Self::Output { fn mul(self, _rhs: Self) -> Self::Output {
todo!() todo!()
} }
} }
impl ops::Div for NativeFloat { impl ops::Div for NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn div(self, _rhs: Self) -> Self::Output { fn div(self, _rhs: Self) -> Self::Output {
todo!() todo!()
} }
} }
impl ops::Rem for NativeFloat { impl ops::Rem for NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn rem(self, _rhs: Self) -> Self::Output { fn rem(self, _rhs: Self) -> Self::Output {
todo!() todo!()
} }
} }
impl ops::Add<&NativeFloat> for NativeFloat { impl ops::Add<&NativeFloat64> for NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn add(self, rhs: &NativeFloat) -> Self::Output { Self(self.0 + &rhs.0) } fn add(self, rhs: &NativeFloat64) -> Self::Output { Self(self.0 + &rhs.0) }
} }
impl ops::Sub<&NativeFloat> for NativeFloat { impl ops::Sub<&NativeFloat64> for NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn sub(self, _rhs: &NativeFloat) -> Self::Output { fn sub(self, _rhs: &NativeFloat64) -> Self::Output {
todo!() todo!()
} }
} }
impl ops::Mul<&NativeFloat> for NativeFloat { impl ops::Mul<&NativeFloat64> for NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn mul(self, rhs: &NativeFloat) -> Self::Output { NativeFloat(self.0 * &rhs.0) } fn mul(self, rhs: &NativeFloat64) -> Self::Output { NativeFloat64(self.0 * &rhs.0) }
} }
impl ops::Div<&NativeFloat> for NativeFloat { impl ops::Div<&NativeFloat64> for NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn div(self, rhs: &NativeFloat) -> Self::Output { NativeFloat(self.0 / &rhs.0) } fn div(self, rhs: &NativeFloat64) -> Self::Output { NativeFloat64(self.0 / &rhs.0) }
} }
impl ops::Rem<&NativeFloat> for NativeFloat { impl ops::Rem<&NativeFloat64> for NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn rem(self, _rhs: &NativeFloat) -> Self::Output { fn rem(self, _rhs: &NativeFloat64) -> Self::Output {
todo!() todo!()
} }
} }
impl ops::AddAssign for NativeFloat { impl ops::AddAssign for NativeFloat64 {
fn add_assign(&mut self, rhs: Self) { self.0 += rhs.0 } fn add_assign(&mut self, rhs: Self) { self.0 += rhs.0 }
} }
impl ops::SubAssign for NativeFloat { impl ops::SubAssign for NativeFloat64 {
fn sub_assign(&mut self, rhs: Self) { self.0 -= rhs.0 } fn sub_assign(&mut self, rhs: Self) { self.0 -= rhs.0 }
} }
impl ops::MulAssign for NativeFloat { impl ops::MulAssign for NativeFloat64 {
fn mul_assign(&mut self, _rhs: Self) { fn mul_assign(&mut self, _rhs: Self) {
todo!() todo!()
} }
} }
impl ops::DivAssign for NativeFloat { impl ops::DivAssign for NativeFloat64 {
fn div_assign(&mut self, rhs: Self) { fn div_assign(&mut self, rhs: Self) {
self.0 /= &rhs.0; self.0 /= &rhs.0;
} }
} }
impl ops::RemAssign for NativeFloat { impl ops::RemAssign for NativeFloat64 {
fn rem_assign(&mut self, _rhs: Self) { fn rem_assign(&mut self, _rhs: Self) {
todo!() todo!()
} }
} }
impl ops::AddAssign<&NativeFloat> for NativeFloat { impl ops::AddAssign<&NativeFloat64> for NativeFloat64 {
fn add_assign(&mut self, rhs: &NativeFloat) { self.0 += &rhs.0 } fn add_assign(&mut self, rhs: &NativeFloat64) { self.0 += &rhs.0 }
} }
impl ops::SubAssign<&NativeFloat> for NativeFloat { impl ops::SubAssign<&NativeFloat64> for NativeFloat64 {
fn sub_assign(&mut self, rhs: &NativeFloat) { self.0 -= &rhs.0 } fn sub_assign(&mut self, rhs: &NativeFloat64) { self.0 -= &rhs.0 }
} }
impl ops::MulAssign<&NativeFloat> for NativeFloat { impl ops::MulAssign<&NativeFloat64> for NativeFloat64 {
fn mul_assign(&mut self, _rhs: &NativeFloat) { fn mul_assign(&mut self, _rhs: &NativeFloat64) {
todo!() todo!()
} }
} }
impl ops::DivAssign<&NativeFloat> for NativeFloat { impl ops::DivAssign<&NativeFloat64> for NativeFloat64 {
fn div_assign(&mut self, _rhs: &NativeFloat) { fn div_assign(&mut self, _rhs: &NativeFloat64) {
todo!() todo!()
} }
} }
impl ops::RemAssign<&NativeFloat> for NativeFloat { impl ops::RemAssign<&NativeFloat64> for NativeFloat64 {
fn rem_assign(&mut self, _rhs: &NativeFloat) { fn rem_assign(&mut self, _rhs: &NativeFloat64) {
todo!() todo!()
} }
} }
impl ops::Neg for &NativeFloat { impl ops::Neg for &NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn neg(self) -> Self::Output { NativeFloat(-&self.0) } fn neg(self) -> Self::Output { NativeFloat64(-&self.0) }
} }
impl ops::Add<&NativeFloat> for &NativeFloat { impl ops::Add<&NativeFloat64> for &NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn add(self, _rhs: &NativeFloat) -> Self::Output { fn add(self, _rhs: &NativeFloat64) -> Self::Output {
todo!() todo!()
} }
} }
impl ops::Sub<&NativeFloat> for &NativeFloat { impl ops::Sub<&NativeFloat64> for &NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn sub(self, rhs: &NativeFloat) -> Self::Output { NativeFloat(&self.0 - &rhs.0) } fn sub(self, rhs: &NativeFloat64) -> Self::Output { NativeFloat64(&self.0 - &rhs.0) }
} }
impl ops::Mul<&NativeFloat> for &NativeFloat { impl ops::Mul<&NativeFloat64> for &NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn mul(self, _rhs: &NativeFloat) -> Self::Output { fn mul(self, _rhs: &NativeFloat64) -> Self::Output {
todo!() todo!()
} }
} }
impl ops::Div<&NativeFloat> for &NativeFloat { impl ops::Div<&NativeFloat64> for &NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn div(self, _rhs: &NativeFloat) -> Self::Output { fn div(self, _rhs: &NativeFloat64) -> Self::Output {
todo!() todo!()
} }
} }
impl ops::Rem<&NativeFloat> for &NativeFloat { impl ops::Rem<&NativeFloat64> for &NativeFloat64 {
type Output = NativeFloat; type Output = NativeFloat64;
fn rem(self, _rhs: &NativeFloat) -> Self::Output { fn rem(self, _rhs: &NativeFloat64) -> Self::Output {
todo!() todo!()
} }
} }

View File

@ -31,7 +31,16 @@ impl Number for Rational {
fn from(n: usize) -> Self { Self(rug::Rational::from(n)) } fn from(n: usize) -> Self { Self(rug::Rational::from(n)) }
fn floor_mut(&mut self) { self.0.floor_mut() } fn floor_mut(&mut self, dps: usize) {
if dps == 0 {
self.0.floor_mut();
} else {
let factor = rug::Rational::from(10).pow(dps as u32);
self.0 *= &factor;
self.0.floor_mut();
self.0 /= factor;
}
}
} }
impl Num for Rational { impl Num for Rational {
@ -65,7 +74,9 @@ impl fmt::Display for Rational {
return f.write_str(&result); return f.write_str(&result);
} else { } else {
let base = rug::Rational::from(10).pow(precision as u32); let base = rug::Rational::from(10).pow(precision as u32);
let mut result = rug::Integer::from((&self.0 * base).round_ref()).to_string(); let mut result = rug::Integer::from((&self.0 * base).abs().round_ref()).to_string();
let should_add_minus = (self.0 < 0) && result != "0";
// Add leading 0s // Add leading 0s
result = format!("{0:0>1$}", result, precision + 1); result = format!("{0:0>1$}", result, precision + 1);
@ -73,6 +84,11 @@ impl fmt::Display for Rational {
// Add the decimal point // Add the decimal point
result.insert(result.len() - precision, '.'); result.insert(result.len() - precision, '.');
// Add the sign
if should_add_minus {
result.insert(0, '-');
}
return f.write_str(&result); return f.write_str(&result);
} }
} else { } else {

View File

@ -23,6 +23,56 @@ use crate::election::{Candidate, CandidateState, CountCard, CountState, Parcel,
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::{Neg, Sub}; use std::ops::{Neg, Sub};
pub struct STVOptions {
pub round_votes: Option<usize>,
}
pub fn count_init<N: Number>(mut state: &mut CountState<'_, N>, _opts: &STVOptions) {
distribute_first_preferences(&mut state);
calculate_quota(&mut state);
elect_meeting_quota(&mut state);
}
pub fn count_one_stage<N: Number>(mut state: &mut CountState<'_, N>, opts: &STVOptions) -> bool
where
for<'r> &'r N: Sub<&'r N, Output=N>,
for<'r> &'r N: Neg<Output=N>
{
state.logger.entries.clear();
state.step_all();
// Finish count
if finished_before_stage(&state) {
return true;
}
// Continue exclusions
if continue_exclusion(&mut state, &opts) {
elect_meeting_quota(&mut state);
return false;
}
// Distribute surpluses
if distribute_surpluses(&mut state, &opts) {
elect_meeting_quota(&mut state);
return false;
}
// Attempt bulk election
if bulk_elect(&mut state) {
elect_meeting_quota(&mut state);
return false;
}
// Exclude lowest hopeful
if exclude_hopefuls(&mut state, &opts) {
elect_meeting_quota(&mut state);
return false;
}
todo!();
}
struct NextPreferencesResult<'a, N> { struct NextPreferencesResult<'a, N> {
candidates: HashMap<&'a Candidate, NextPreferencesEntry<'a, N>>, candidates: HashMap<&'a Candidate, NextPreferencesEntry<'a, N>>,
exhausted: NextPreferencesEntry<'a, N>, exhausted: NextPreferencesEntry<'a, N>,
@ -88,7 +138,7 @@ fn next_preferences<'a, N: Number>(state: &CountState<'a, N>, votes: Vec<Vote<'a
return result; return result;
} }
pub fn distribute_first_preferences<N: Number>(state: &mut CountState<N>) { fn distribute_first_preferences<N: Number>(state: &mut CountState<N>) {
let votes = state.election.ballots.iter().map(|b| Vote { let votes = state.election.ballots.iter().map(|b| Vote {
ballot: b, ballot: b,
value: b.orig_value.clone(), value: b.orig_value.clone(),
@ -115,7 +165,7 @@ pub fn distribute_first_preferences<N: Number>(state: &mut CountState<N>) {
state.logger.log_literal("First preferences distributed.".to_string()); state.logger.log_literal("First preferences distributed.".to_string());
} }
pub fn calculate_quota<N: Number>(state: &mut CountState<N>) { fn calculate_quota<N: Number>(state: &mut CountState<N>) {
let mut log = String::new(); let mut log = String::new();
// Calculate the total vote // Calculate the total vote
@ -127,7 +177,7 @@ pub fn calculate_quota<N: Number>(state: &mut CountState<N>) {
// TODO: Different rounding rules // TODO: Different rounding rules
state.quota += N::one(); state.quota += N::one();
state.quota.floor_mut(); state.quota.floor_mut(0);
log.push_str(format!("{:.2}.", state.quota).as_str()); log.push_str(format!("{:.2}.", state.quota).as_str());
state.logger.log_literal(log); state.logger.log_literal(log);
@ -138,7 +188,7 @@ fn meets_quota<N: Number>(quota: &N, count_card: &CountCard<N>) -> bool {
return count_card.votes >= *quota; return count_card.votes >= *quota;
} }
pub fn elect_meeting_quota<N: Number>(state: &mut CountState<N>) { fn elect_meeting_quota<N: Number>(state: &mut CountState<N>) {
let quota = &state.quota; // Have to do this or else the borrow checker gets confused let quota = &state.quota; // Have to do this or else the borrow checker gets confused
let mut cands_meeting_quota: Vec<(&&Candidate, &mut CountCard<N>)> = state.candidates.iter_mut() let mut cands_meeting_quota: Vec<(&&Candidate, &mut CountCard<N>)> = state.candidates.iter_mut()
.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL && meets_quota(quota, cc)) .filter(|(_, cc)| cc.state == CandidateState::HOPEFUL && meets_quota(quota, cc))
@ -162,7 +212,7 @@ pub fn elect_meeting_quota<N: Number>(state: &mut CountState<N>) {
} }
} }
pub fn distribute_surpluses<N: Number>(state: &mut CountState<N>) -> bool fn distribute_surpluses<N: Number>(state: &mut CountState<N>, opts: &STVOptions) -> bool
where where
for<'r> &'r N: Sub<&'r N, Output=N>, for<'r> &'r N: Sub<&'r N, Output=N>,
for<'r> &'r N: Neg<Output=N> for<'r> &'r N: Neg<Output=N>
@ -178,14 +228,14 @@ where
// Distribute top candidate's surplus // Distribute top candidate's surplus
// TODO: Handle ties // TODO: Handle ties
let elected_candidate = has_surplus.first_mut().unwrap().0; let elected_candidate = has_surplus.first_mut().unwrap().0;
distribute_surplus(state, elected_candidate); distribute_surplus(state, &opts, elected_candidate);
return true; return true;
} }
return false; return false;
} }
fn distribute_surplus<N: Number>(state: &mut CountState<N>, elected_candidate: &Candidate) fn distribute_surplus<N: Number>(state: &mut CountState<N>, opts: &STVOptions, elected_candidate: &Candidate)
where where
for<'r> &'r N: Sub<&'r N, Output=N>, for<'r> &'r N: Sub<&'r N, Output=N>,
for<'r> &'r N: Neg<Output=N> for<'r> &'r N: Neg<Output=N>
@ -225,8 +275,9 @@ where
let mut candidate_transfers = entry.num_ballots * &surplus / &result.total_ballots; let mut candidate_transfers = entry.num_ballots * &surplus / &result.total_ballots;
// Round transfers // Round transfers
// TODO: Make configurable if let Some(dps) = opts.round_votes {
candidate_transfers.floor_mut(); candidate_transfers.floor_mut(dps);
}
count_card.transfer(&candidate_transfers); count_card.transfer(&candidate_transfers);
checksum += candidate_transfers; checksum += candidate_transfers;
} }
@ -236,8 +287,9 @@ where
state.exhausted.parcels.push(parcel); state.exhausted.parcels.push(parcel);
let mut exhausted_transfers = result.exhausted.num_ballots * &surplus / &result.total_ballots; let mut exhausted_transfers = result.exhausted.num_ballots * &surplus / &result.total_ballots;
// TODO: Make configurable if let Some(dps) = opts.round_votes {
exhausted_transfers.floor_mut(); exhausted_transfers.floor_mut(dps);
}
state.exhausted.transfer(&exhausted_transfers); state.exhausted.transfer(&exhausted_transfers);
checksum += exhausted_transfers; checksum += exhausted_transfers;
@ -251,7 +303,7 @@ where
state.loss_fraction.transfer(&-checksum); state.loss_fraction.transfer(&-checksum);
} }
pub fn bulk_elect<N: Number>(state: &mut CountState<N>) -> bool { fn bulk_elect<N: Number>(state: &mut CountState<N>) -> bool {
if state.election.candidates.len() - state.num_excluded <= state.election.seats { if state.election.candidates.len() - state.num_excluded <= state.election.seats {
state.kind = None; state.kind = None;
state.title = "Bulk election".to_string(); state.title = "Bulk election".to_string();
@ -281,7 +333,7 @@ pub fn bulk_elect<N: Number>(state: &mut CountState<N>) -> bool {
return false; return false;
} }
pub fn exclude_hopefuls<N: Number>(state: &mut CountState<N>) -> bool { fn exclude_hopefuls<N: Number>(state: &mut CountState<N>, opts: &STVOptions) -> bool {
let mut hopefuls: Vec<(&&Candidate, &CountCard<N>)> = state.candidates.iter() let mut hopefuls: Vec<(&&Candidate, &CountCard<N>)> = state.candidates.iter()
.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL) .filter(|(_, cc)| cc.state == CandidateState::HOPEFUL)
.collect(); .collect();
@ -301,12 +353,12 @@ pub fn exclude_hopefuls<N: Number>(state: &mut CountState<N>) -> bool {
vec![&excluded_candidate.name] vec![&excluded_candidate.name]
); );
exclude_candidate(state, excluded_candidate); exclude_candidate(state, opts, excluded_candidate);
return true; return true;
} }
pub fn continue_exclusion<N: Number>(state: &mut CountState<N>) -> bool { fn continue_exclusion<N: Number>(state: &mut CountState<N>, opts: &STVOptions) -> bool {
let mut excluded_with_votes: Vec<(&&Candidate, &CountCard<N>)> = state.candidates.iter() let mut excluded_with_votes: Vec<(&&Candidate, &CountCard<N>)> = state.candidates.iter()
.filter(|(_, cc)| cc.state == CandidateState::EXCLUDED && !cc.votes.is_zero()) .filter(|(_, cc)| cc.state == CandidateState::EXCLUDED && !cc.votes.is_zero())
.collect(); .collect();
@ -323,14 +375,14 @@ pub fn continue_exclusion<N: Number>(state: &mut CountState<N>) -> bool {
vec![&excluded_candidate.name] vec![&excluded_candidate.name]
); );
exclude_candidate(state, excluded_candidate); exclude_candidate(state, opts, excluded_candidate);
return true; return true;
} }
return false; return false;
} }
fn exclude_candidate<N: Number>(state: &mut CountState<N>, excluded_candidate: &Candidate) { fn exclude_candidate<N: Number>(state: &mut CountState<N>, opts: &STVOptions, excluded_candidate: &Candidate) {
let count_card = state.candidates.get_mut(excluded_candidate).unwrap(); let count_card = state.candidates.get_mut(excluded_candidate).unwrap();
count_card.state = CandidateState::EXCLUDED; count_card.state = CandidateState::EXCLUDED;
state.num_excluded += 1; state.num_excluded += 1;
@ -352,9 +404,10 @@ fn exclude_candidate<N: Number>(state: &mut CountState<N>, excluded_candidate: &
count_card.parcels.push(parcel); count_card.parcels.push(parcel);
// Round transfers // Round transfers
// TODO: Make configurable
let mut candidate_transfers = entry.num_votes; let mut candidate_transfers = entry.num_votes;
candidate_transfers.floor_mut(); if let Some(dps) = opts.round_votes {
candidate_transfers.floor_mut(dps);
}
count_card.transfer(&candidate_transfers); count_card.transfer(&candidate_transfers);
checksum += candidate_transfers; checksum += candidate_transfers;
} }
@ -364,8 +417,9 @@ fn exclude_candidate<N: Number>(state: &mut CountState<N>, excluded_candidate: &
state.exhausted.parcels.push(parcel); state.exhausted.parcels.push(parcel);
let mut exhausted_transfers = result.exhausted.num_votes; let mut exhausted_transfers = result.exhausted.num_votes;
// TODO: Make configurable if let Some(dps) = opts.round_votes {
exhausted_transfers.floor_mut(); exhausted_transfers.floor_mut(dps);
}
state.exhausted.transfer(&exhausted_transfers); state.exhausted.transfer(&exhausted_transfers);
checksum += exhausted_transfers; checksum += exhausted_transfers;
@ -379,7 +433,7 @@ fn exclude_candidate<N: Number>(state: &mut CountState<N>, excluded_candidate: &
state.loss_fraction.transfer(&-checksum); state.loss_fraction.transfer(&-checksum);
} }
pub fn finished_before_stage<N: Number>(state: &CountState<N>) -> bool { fn finished_before_stage<N: Number>(state: &CountState<N>) -> bool {
if state.num_elected >= state.election.seats { if state.num_elected >= state.election.seats {
return true; return true;
} }