diff --git a/src/main.rs b/src/main.rs index d9e97bb..07fd613 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,13 +21,14 @@ mod numbers; mod stv; use crate::election::{Candidate, CandidateState, CountCard, CountState, CountStateOrRef, Election, StageResult}; -use crate::numbers::{Number, NumType}; +use crate::numbers::{NativeFloat, Number, Rational}; use clap::Clap; use git_version::git_version; use std::fs::File; use std::io::{self, BufRead}; +use std::ops; const VERSION: &str = git_version!(args=["--always", "--dirty=-dev"], fallback="unknown"); @@ -49,6 +50,9 @@ enum Command { struct STV { /// Path to the BLT file to be counted filename: String, + /// Numbers mode + #[clap(short, long, possible_values(&["rational", "native"]), default_value="rational")] + numbers: String, /// Hide excluded candidates from results report #[clap(long)] hide_excluded: bool, @@ -60,66 +64,6 @@ struct STV { pp_decimals: usize, } -fn print_candidates<'a, N: 'a + Number, I: Iterator)>>(candidates: I, cmd_opts: &STV) { - for (candidate, count_card) in candidates { - if count_card.state == CandidateState::ELECTED { - println!("- {}: {:.dps$} ({:.dps$}) - ELECTED {}", candidate.name, count_card.votes, count_card.transfers, count_card.order_elected, dps=cmd_opts.pp_decimals); - } else if count_card.state == CandidateState::EXCLUDED { - // If --hide-excluded, hide unless nonzero votes or nonzero transfers - if !cmd_opts.hide_excluded || !count_card.votes.is_zero() || !count_card.transfers.is_zero() { - println!("- {}: {:.dps$} ({:.dps$}) - Excluded {}", candidate.name, count_card.votes, count_card.transfers, -count_card.order_elected, dps=cmd_opts.pp_decimals); - } - } else { - println!("- {}: {:.dps$} ({:.dps$})", candidate.name, count_card.votes, count_card.transfers, dps=cmd_opts.pp_decimals); - } - } -} - -fn print_stage(stage_num: usize, result: &StageResult, cmd_opts: &STV) { - // Print stage details - match result.kind { - None => { println!("{}. {}", stage_num, result.title); } - Some(kind) => { println!("{}. {} {}", stage_num, kind, result.title); } - }; - println!("{}", result.logs.join(" ")); - - let state = result.state.as_ref(); - - // Print candidates - if cmd_opts.sort_votes { - // Sort by votes if requested - let mut candidates: Vec<(&Candidate, &CountCard)> = state.candidates.iter() - .map(|(c, cc)| (*c, cc)).collect(); - // First sort by order of election (as a tie-breaker, if votes are equal) - candidates.sort_unstable_by(|a, b| b.1.order_elected.partial_cmp(&a.1.order_elected).unwrap()); - // Then sort by votes - candidates.sort_by(|a, b| a.1.votes.partial_cmp(&b.1.votes).unwrap()); - print_candidates(candidates.into_iter().rev(), cmd_opts); - } else { - let candidates = state.election.candidates.iter() - .map(|c| (c, state.candidates.get(c).unwrap())); - print_candidates(candidates, cmd_opts); - } - - // Print summary rows - println!("Exhausted: {:.dps$} ({:.dps$})", state.exhausted.votes, state.exhausted.transfers, dps=cmd_opts.pp_decimals); - println!("Loss by fraction: {:.dps$} ({:.dps$})", state.loss_fraction.votes, state.loss_fraction.transfers, dps=cmd_opts.pp_decimals); - - println!("Quota: {:.dps$}", state.quota, dps=cmd_opts.pp_decimals); - - println!(""); -} - -fn make_and_print_result(stage_num: usize, state: &CountState, cmd_opts: &STV) { - let result = StageResult { - kind: state.kind, - title: &state.title, - logs: state.logger.render(), - state: CountStateOrRef::from(&state), - }; - print_stage(stage_num, &result, &cmd_opts); -} - fn main() { // Read arguments let opts: Opts = Opts::parse(); @@ -128,8 +72,22 @@ fn main() { // Read BLT file let file = File::open(&cmd_opts.filename).expect("IO Error"); let lines = io::BufReader::new(file).lines(); - let election: Election = Election::from_blt(lines); + // Create and count election according to --numbers + if cmd_opts.numbers == "rational" { + let election: Election = Election::from_blt(lines); + count_election(election, cmd_opts); + } else if cmd_opts.numbers == "native" { + let election: Election = Election::from_blt(lines); + count_election(election, cmd_opts); + } +} + +fn count_election(election: Election, cmd_opts: STV) +where + for<'r> &'r N: ops::Sub<&'r N, Output=N>, + for<'r> &'r N: ops::Neg +{ // Initialise count state let mut state = CountState::new(&election); @@ -197,3 +155,63 @@ fn main() { println!("{}. {}", i + 1, winner.name); } } + +fn print_candidates<'a, N: 'a + Number, I: Iterator)>>(candidates: I, cmd_opts: &STV) { + for (candidate, count_card) in candidates { + if count_card.state == CandidateState::ELECTED { + println!("- {}: {:.dps$} ({:.dps$}) - ELECTED {}", candidate.name, count_card.votes, count_card.transfers, count_card.order_elected, dps=cmd_opts.pp_decimals); + } else if count_card.state == CandidateState::EXCLUDED { + // If --hide-excluded, hide unless nonzero votes or nonzero transfers + if !cmd_opts.hide_excluded || !count_card.votes.is_zero() || !count_card.transfers.is_zero() { + println!("- {}: {:.dps$} ({:.dps$}) - Excluded {}", candidate.name, count_card.votes, count_card.transfers, -count_card.order_elected, dps=cmd_opts.pp_decimals); + } + } else { + println!("- {}: {:.dps$} ({:.dps$})", candidate.name, count_card.votes, count_card.transfers, dps=cmd_opts.pp_decimals); + } + } +} + +fn print_stage(stage_num: usize, result: &StageResult, cmd_opts: &STV) { + // Print stage details + match result.kind { + None => { println!("{}. {}", stage_num, result.title); } + Some(kind) => { println!("{}. {} {}", stage_num, kind, result.title); } + }; + println!("{}", result.logs.join(" ")); + + let state = result.state.as_ref(); + + // Print candidates + if cmd_opts.sort_votes { + // Sort by votes if requested + let mut candidates: Vec<(&Candidate, &CountCard)> = state.candidates.iter() + .map(|(c, cc)| (*c, cc)).collect(); + // First sort by order of election (as a tie-breaker, if votes are equal) + candidates.sort_unstable_by(|a, b| b.1.order_elected.partial_cmp(&a.1.order_elected).unwrap()); + // Then sort by votes + candidates.sort_by(|a, b| a.1.votes.partial_cmp(&b.1.votes).unwrap()); + print_candidates(candidates.into_iter().rev(), cmd_opts); + } else { + let candidates = state.election.candidates.iter() + .map(|c| (c, state.candidates.get(c).unwrap())); + print_candidates(candidates, cmd_opts); + } + + // Print summary rows + println!("Exhausted: {:.dps$} ({:.dps$})", state.exhausted.votes, state.exhausted.transfers, dps=cmd_opts.pp_decimals); + println!("Loss by fraction: {:.dps$} ({:.dps$})", state.loss_fraction.votes, state.loss_fraction.transfers, dps=cmd_opts.pp_decimals); + + println!("Quota: {:.dps$}", state.quota, dps=cmd_opts.pp_decimals); + + println!(""); +} + +fn make_and_print_result(stage_num: usize, state: &CountState, cmd_opts: &STV) { + let result = StageResult { + kind: state.kind, + title: &state.title, + logs: state.logger.render(), + state: CountStateOrRef::from(&state), + }; + print_stage(stage_num, &result, &cmd_opts); +} diff --git a/src/numbers/mod.rs b/src/numbers/mod.rs new file mode 100644 index 0000000..8d110f0 --- /dev/null +++ b/src/numbers/mod.rs @@ -0,0 +1,45 @@ +/* OpenTally: Open-source election vote counting + * Copyright © 2021 Lee Yingtong Li (RunasSudo) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +mod native; +mod rational; + +use num_traits::{NumAssignRef, NumRef}; +use rug::{self, Assign}; + +use std::cmp::{PartialOrd}; +use std::fmt; +use std::ops; + +//pub trait Number: NumRef + NumAssignRef + PartialOrd + Assign + Clone + fmt::Display where for<'a> &'a Self: RefNum<&'a Self> { +pub trait Number: NumRef + NumAssignRef + ops::Neg + PartialOrd + Assign + Clone + fmt::Display where for<'a> Self: Assign<&'a Self>{ + fn new() -> Self; + fn from(n: usize) -> Self; + + fn floor_mut(&mut self); + + fn parse(s: &str) -> Self { + if let Ok(value) = Self::from_str_radix(s, 10) { + return value; + } else { + panic!("Syntax Error"); + } + } +} + +pub use self::native::NativeFloat; +pub use self::rational::Rational; diff --git a/src/numbers/native.rs b/src/numbers/native.rs new file mode 100644 index 0000000..a89c947 --- /dev/null +++ b/src/numbers/native.rs @@ -0,0 +1,264 @@ +/* OpenTally: Open-source election vote counting + * Copyright © 2021 Lee Yingtong Li (RunasSudo) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use super::Number; + +use num_traits::{Num, One, Zero}; +use rug::Assign; + +use std::cmp::{Ordering, PartialEq, PartialOrd}; +use std::num::ParseIntError; +use std::fmt; +use std::ops; + +pub struct NativeFloat(f32); + +impl Number for NativeFloat { + fn new() -> Self { Self(0.0) } + + fn from(n: usize) -> Self { Self(n as f32) } + + fn floor_mut(&mut self) { self.0 = self.0.floor() } +} + +impl Num for NativeFloat { + type FromStrRadixErr = ParseIntError; + + fn from_str_radix(str: &str, radix: u32) -> Result { + match i32::from_str_radix(str, radix) { + Ok(value) => Ok(Self(value as f32)), + Err(err) => Err(err) + } + } +} + +impl Assign for NativeFloat { + fn assign(&mut self, src: Self) { self.0 = src.0 } +} + +impl Assign<&NativeFloat> for NativeFloat { + fn assign(&mut self, src: &NativeFloat) { self.0 = src.0 } +} + +impl Clone for NativeFloat { + fn clone(&self) -> Self { Self(self.0) } +} + +impl fmt::Display for NativeFloat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } +} + +impl One for NativeFloat { + fn one() -> Self { Self(1.0) } +} + +impl Zero for NativeFloat { + fn zero() -> Self { Self::new() } + fn is_zero(&self) -> bool { self.0.is_zero() } +} + +impl PartialEq for NativeFloat { + fn eq(&self, _other: &Self) -> bool { + todo!() + } +} + +impl PartialOrd for NativeFloat { + fn partial_cmp(&self, other: &Self) -> Option { self.0.partial_cmp(&other.0) } +} + +impl ops::Neg for NativeFloat { + type Output = NativeFloat; + fn neg(self) -> Self::Output { Self(-self.0) } +} + +impl ops::Add for NativeFloat { + type Output = NativeFloat; + fn add(self, _rhs: Self) -> Self::Output { + todo!() + } +} + +impl ops::Sub for NativeFloat { + type Output = NativeFloat; + fn sub(self, _rhs: Self) -> Self::Output { + todo!() + } +} + +impl ops::Mul for NativeFloat { + type Output = NativeFloat; + fn mul(self, _rhs: Self) -> Self::Output { + todo!() + } +} + +impl ops::Div for NativeFloat { + type Output = NativeFloat; + fn div(self, _rhs: Self) -> Self::Output { + todo!() + } +} + +impl ops::Rem for NativeFloat { + type Output = NativeFloat; + + fn rem(self, _rhs: Self) -> Self::Output { + todo!() + } +} + +impl ops::Add<&NativeFloat> for NativeFloat { + type Output = NativeFloat; + fn add(self, rhs: &NativeFloat) -> Self::Output { Self(self.0 + &rhs.0) } +} + +impl ops::Sub<&NativeFloat> for NativeFloat { + type Output = NativeFloat; + fn sub(self, _rhs: &NativeFloat) -> Self::Output { + todo!() + } +} + +impl ops::Mul<&NativeFloat> for NativeFloat { + type Output = NativeFloat; + fn mul(self, rhs: &NativeFloat) -> Self::Output { NativeFloat(self.0 * &rhs.0) } +} + +impl ops::Div<&NativeFloat> for NativeFloat { + type Output = NativeFloat; + fn div(self, rhs: &NativeFloat) -> Self::Output { NativeFloat(self.0 / &rhs.0) } +} + +impl ops::Rem<&NativeFloat> for NativeFloat { + type Output = NativeFloat; + fn rem(self, _rhs: &NativeFloat) -> Self::Output { + todo!() + } +} + +impl ops::AddAssign for NativeFloat { + fn add_assign(&mut self, rhs: Self) { self.0 += rhs.0 } +} + +impl ops::SubAssign for NativeFloat { + fn sub_assign(&mut self, rhs: Self) { self.0 -= rhs.0 } +} + +impl ops::MulAssign for NativeFloat { + fn mul_assign(&mut self, _rhs: Self) { + todo!() + } +} + +impl ops::DivAssign for NativeFloat { + fn div_assign(&mut self, rhs: Self) { + self.0 /= &rhs.0; + } +} + +impl ops::RemAssign for NativeFloat { + fn rem_assign(&mut self, _rhs: Self) { + todo!() + } +} + +impl ops::AddAssign<&NativeFloat> for NativeFloat { + fn add_assign(&mut self, rhs: &NativeFloat) { self.0 += &rhs.0 } +} + +impl ops::SubAssign<&NativeFloat> for NativeFloat { + fn sub_assign(&mut self, rhs: &NativeFloat) { self.0 -= &rhs.0 } +} + +impl ops::MulAssign<&NativeFloat> for NativeFloat { + fn mul_assign(&mut self, _rhs: &NativeFloat) { + todo!() + } +} + +impl ops::DivAssign<&NativeFloat> for NativeFloat { + fn div_assign(&mut self, _rhs: &NativeFloat) { + todo!() + } +} + +impl ops::RemAssign<&NativeFloat> for NativeFloat { + fn rem_assign(&mut self, _rhs: &NativeFloat) { + todo!() + } +} + +impl ops::Neg for &NativeFloat { + type Output = NativeFloat; + fn neg(self) -> Self::Output { NativeFloat(-&self.0) } +} + +impl ops::Add<&NativeFloat> for &NativeFloat { + type Output = NativeFloat; + fn add(self, _rhs: &NativeFloat) -> Self::Output { + todo!() + } +} + +impl ops::Sub<&NativeFloat> for &NativeFloat { + type Output = NativeFloat; + fn sub(self, rhs: &NativeFloat) -> Self::Output { NativeFloat(&self.0 - &rhs.0) } +} + +impl ops::Mul<&NativeFloat> for &NativeFloat { + type Output = NativeFloat; + fn mul(self, _rhs: &NativeFloat) -> Self::Output { + todo!() + } +} + +impl ops::Div<&NativeFloat> for &NativeFloat { + type Output = NativeFloat; + fn div(self, _rhs: &NativeFloat) -> Self::Output { + todo!() + } +} + +impl ops::Rem<&NativeFloat> for &NativeFloat { + type Output = NativeFloat; + fn rem(self, _rhs: &NativeFloat) -> Self::Output { + todo!() + } +} + +/* +impl ops::Add<&&NativeFloat> for &NativeFloat { + +} + +impl ops::Sub<&&NativeFloat> for &NativeFloat { + +} + +impl ops::Mul<&&NativeFloat> for &NativeFloat { + +} + +impl ops::Div<&&NativeFloat> for &NativeFloat { + +} + +impl ops::Rem<&&NativeFloat> for &NativeFloat { + +} +*/ diff --git a/src/numbers.rs b/src/numbers/rational.rs similarity index 83% rename from src/numbers.rs rename to src/numbers/rational.rs index 0675c28..50a1762 100644 --- a/src/numbers.rs +++ b/src/numbers/rational.rs @@ -15,30 +15,15 @@ * along with this program. If not, see . */ -use num_traits::{Num, NumAssignRef, NumRef, One, Zero}; +use super::Number; + +use num_traits::{Num, One, Zero}; use rug::{self, Assign, ops::Pow, rational::ParseRationalError}; use std::cmp::{Ordering, PartialEq, PartialOrd}; -use std::convert::TryInto; use std::fmt; use std::ops; -//pub trait Number: NumRef + NumAssignRef + PartialOrd + Assign + Clone + fmt::Display where for<'a> &'a Self: RefNum<&'a Self> { -pub trait Number: NumRef + NumAssignRef + ops::Neg + PartialOrd + Assign + Clone + fmt::Display where for<'a> Self: Assign<&'a Self>{ - fn new() -> Self; - fn from(n: usize) -> Self; - - fn floor_mut(&mut self); - - fn parse(s: &str) -> Self { - if let Ok(value) = Self::from_str_radix(s, 10) { - return value; - } else { - panic!("Syntax Error"); - } - } -} - pub struct Rational(rug::Rational); impl Number for Rational { @@ -53,7 +38,7 @@ impl Num for Rational { type FromStrRadixErr = ParseRationalError; fn from_str_radix(str: &str, radix: u32) -> Result { - match rug::Rational::parse_radix(str, radix.try_into().unwrap()) { + match rug::Rational::parse_radix(str, radix as i32) { Ok(value) => Ok(Self(rug::Rational::from(value))), Err(err) => Err(err) } @@ -97,7 +82,7 @@ impl fmt::Display for Rational { } impl One for Rational { - fn one() -> Self { Rational(rug::Rational::from(1)) } + fn one() -> Self { Self(rug::Rational::from(1)) } } impl Zero for Rational { @@ -117,7 +102,7 @@ impl PartialOrd for Rational { impl ops::Neg for Rational { type Output = Rational; - fn neg(self) -> Self::Output { Rational(-self.0) } + fn neg(self) -> Self::Output { Self(-self.0) } } impl ops::Add for Rational { @@ -158,7 +143,7 @@ impl ops::Rem for Rational { impl ops::Add<&Rational> for Rational { type Output = Rational; - fn add(self, rhs: &Rational) -> Self::Output { Rational(self.0 + &rhs.0) } + fn add(self, rhs: &Rational) -> Self::Output { Self(self.0 + &rhs.0) } } impl ops::Sub<&Rational> for Rational { @@ -170,12 +155,12 @@ impl ops::Sub<&Rational> for Rational { impl ops::Mul<&Rational> for Rational { type Output = Rational; - fn mul(self, rhs: &Rational) -> Self::Output { Rational(self.0 * &rhs.0) } + fn mul(self, rhs: &Rational) -> Self::Output { Self(self.0 * &rhs.0) } } impl ops::Div<&Rational> for Rational { type Output = Rational; - fn div(self, rhs: &Rational) -> Self::Output { Rational(self.0 / &rhs.0) } + fn div(self, rhs: &Rational) -> Self::Output { Self(self.0 / &rhs.0) } } impl ops::Rem<&Rational> for Rational { @@ -251,9 +236,7 @@ impl ops::Add<&Rational> for &Rational { impl ops::Sub<&Rational> for &Rational { type Output = Rational; - fn sub(self, rhs: &Rational) -> Self::Output { - return Rational(rug::Rational::from(&self.0 - &rhs.0)); - } + fn sub(self, rhs: &Rational) -> Self::Output { Rational(rug::Rational::from(&self.0 - &rhs.0)) } } impl ops::Mul<&Rational> for &Rational { @@ -298,5 +281,3 @@ impl ops::Rem<&&Rational> for &Rational { } */ - -pub type NumType = Rational;