Allow configuring --round-votes
Fix bug with display of negative Rational's
This commit is contained in:
parent
32234ad13b
commit
77cf60c21f
72
src/main.rs
72
src/main.rs
|
@ -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
|
|
||||||
if stv::continue_exclusion(&mut state) {
|
|
||||||
stv::elect_meeting_quota(&mut state);
|
|
||||||
make_and_print_result(stage_num, &state, &cmd_opts);
|
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:");
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue