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
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
5 changed files with 207 additions and 146 deletions

View File

@ -21,9 +21,9 @@ mod numbers;
mod stv;
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 std::fs::File;
@ -47,12 +47,27 @@ enum Command {
/// Count a single transferable vote (STV) election
#[derive(Clap)]
#[clap(setting=AppSettings::DeriveDisplayOrder, setting=AppSettings::UnifiedHelpMessage)]
struct STV {
// -- File input --
/// Path to the BLT file to be counted
filename: String,
// -- Numbers settings --
/// Numbers mode
#[clap(short, long, possible_values(&["rational", "native"]), default_value="rational")]
#[clap(short, long, possible_values(&["rational", "float64"]), default_value="rational")]
numbers: String,
// -- Rounding settings --
/// Round votes to specified decimal places
#[clap(long)]
round_votes: Option<usize>,
// -- Display settings --
/// Hide excluded candidates from results report
#[clap(long)]
hide_excluded: bool,
@ -77,8 +92,8 @@ fn main() {
if cmd_opts.numbers == "rational" {
let election: Election<Rational> = Election::from_blt(lines);
count_election(election, cmd_opts);
} else if cmd_opts.numbers == "native" {
let election: Election<NativeFloat> = Election::from_blt(lines);
} else if cmd_opts.numbers == "float64" {
let election: Election<NativeFloat64> = Election::from_blt(lines);
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::Neg<Output=N>
{
// Copy applicable options
let stv_opts = stv::STVOptions {
round_votes: cmd_opts.round_votes,
};
// Initialise count state
let mut state = CountState::new(&election);
// Distribute first preferences
stv::distribute_first_preferences(&mut state);
stv::calculate_quota(&mut state);
stv::elect_meeting_quota(&mut state);
stv::count_init(&mut state, &stv_opts);
// Display
let mut stage_num = 1;
make_and_print_result(stage_num, &state, &cmd_opts);
loop {
state.logger.entries.clear();
state.step_all();
stage_num += 1;
// Finish count
if stv::finished_before_stage(&state) {
let is_done = stv::count_one_stage(&mut state, &stv_opts);
if is_done {
break;
}
// Continue exclusions
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!();
stage_num += 1;
make_and_print_result(stage_num, &state, &cmd_opts);
}
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 from(n: usize) -> Self;
fn floor_mut(&mut self);
fn floor_mut(&mut self, dps: usize);
fn parse(s: &str) -> Self {
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;

View File

@ -25,218 +25,223 @@ use std::num::ParseIntError;
use std::fmt;
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 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;
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
match i32::from_str_radix(str, radix) {
Ok(value) => Ok(Self(value as f32)),
match i64::from_str_radix(str, radix) {
Ok(value) => Ok(Self(value as ImplType)),
Err(err) => Err(err)
}
}
}
impl Assign for NativeFloat {
impl Assign for NativeFloat64 {
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 Assign<&NativeFloat64> for NativeFloat64 {
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) }
}
impl fmt::Display for NativeFloat {
impl fmt::Display for NativeFloat64 {
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) }
}
impl Zero for NativeFloat {
impl Zero for NativeFloat64 {
fn zero() -> Self { Self::new() }
fn is_zero(&self) -> bool { self.0.is_zero() }
}
impl PartialEq for NativeFloat {
impl PartialEq for NativeFloat64 {
fn eq(&self, _other: &Self) -> bool {
todo!()
}
}
impl PartialOrd for NativeFloat {
impl PartialOrd for NativeFloat64 {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { self.0.partial_cmp(&other.0) }
}
impl ops::Neg for NativeFloat {
type Output = NativeFloat;
impl ops::Neg for NativeFloat64 {
type Output = NativeFloat64;
fn neg(self) -> Self::Output { Self(-self.0) }
}
impl ops::Add for NativeFloat {
type Output = NativeFloat;
impl ops::Add for NativeFloat64 {
type Output = NativeFloat64;
fn add(self, _rhs: Self) -> Self::Output {
todo!()
}
}
impl ops::Sub for NativeFloat {
type Output = NativeFloat;
impl ops::Sub for NativeFloat64 {
type Output = NativeFloat64;
fn sub(self, _rhs: Self) -> Self::Output {
todo!()
}
}
impl ops::Mul for NativeFloat {
type Output = NativeFloat;
impl ops::Mul for NativeFloat64 {
type Output = NativeFloat64;
fn mul(self, _rhs: Self) -> Self::Output {
todo!()
}
}
impl ops::Div for NativeFloat {
type Output = NativeFloat;
impl ops::Div for NativeFloat64 {
type Output = NativeFloat64;
fn div(self, _rhs: Self) -> Self::Output {
todo!()
}
}
impl ops::Rem for NativeFloat {
type Output = NativeFloat;
impl ops::Rem for NativeFloat64 {
type Output = NativeFloat64;
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::Add<&NativeFloat64> for NativeFloat64 {
type Output = NativeFloat64;
fn add(self, rhs: &NativeFloat64) -> Self::Output { Self(self.0 + &rhs.0) }
}
impl ops::Sub<&NativeFloat> for NativeFloat {
type Output = NativeFloat;
fn sub(self, _rhs: &NativeFloat) -> Self::Output {
impl ops::Sub<&NativeFloat64> for NativeFloat64 {
type Output = NativeFloat64;
fn sub(self, _rhs: &NativeFloat64) -> 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::Mul<&NativeFloat64> for NativeFloat64 {
type Output = NativeFloat64;
fn mul(self, rhs: &NativeFloat64) -> Self::Output { NativeFloat64(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::Div<&NativeFloat64> for NativeFloat64 {
type Output = NativeFloat64;
fn div(self, rhs: &NativeFloat64) -> Self::Output { NativeFloat64(self.0 / &rhs.0) }
}
impl ops::Rem<&NativeFloat> for NativeFloat {
type Output = NativeFloat;
fn rem(self, _rhs: &NativeFloat) -> Self::Output {
impl ops::Rem<&NativeFloat64> for NativeFloat64 {
type Output = NativeFloat64;
fn rem(self, _rhs: &NativeFloat64) -> Self::Output {
todo!()
}
}
impl ops::AddAssign for NativeFloat {
impl ops::AddAssign for NativeFloat64 {
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 }
}
impl ops::MulAssign for NativeFloat {
impl ops::MulAssign for NativeFloat64 {
fn mul_assign(&mut self, _rhs: Self) {
todo!()
}
}
impl ops::DivAssign for NativeFloat {
impl ops::DivAssign for NativeFloat64 {
fn div_assign(&mut self, rhs: Self) {
self.0 /= &rhs.0;
}
}
impl ops::RemAssign for NativeFloat {
impl ops::RemAssign for NativeFloat64 {
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::AddAssign<&NativeFloat64> for NativeFloat64 {
fn add_assign(&mut self, rhs: &NativeFloat64) { self.0 += &rhs.0 }
}
impl ops::SubAssign<&NativeFloat> for NativeFloat {
fn sub_assign(&mut self, rhs: &NativeFloat) { self.0 -= &rhs.0 }
impl ops::SubAssign<&NativeFloat64> for NativeFloat64 {
fn sub_assign(&mut self, rhs: &NativeFloat64) { self.0 -= &rhs.0 }
}
impl ops::MulAssign<&NativeFloat> for NativeFloat {
fn mul_assign(&mut self, _rhs: &NativeFloat) {
impl ops::MulAssign<&NativeFloat64> for NativeFloat64 {
fn mul_assign(&mut self, _rhs: &NativeFloat64) {
todo!()
}
}
impl ops::DivAssign<&NativeFloat> for NativeFloat {
fn div_assign(&mut self, _rhs: &NativeFloat) {
impl ops::DivAssign<&NativeFloat64> for NativeFloat64 {
fn div_assign(&mut self, _rhs: &NativeFloat64) {
todo!()
}
}
impl ops::RemAssign<&NativeFloat> for NativeFloat {
fn rem_assign(&mut self, _rhs: &NativeFloat) {
impl ops::RemAssign<&NativeFloat64> for NativeFloat64 {
fn rem_assign(&mut self, _rhs: &NativeFloat64) {
todo!()
}
}
impl ops::Neg for &NativeFloat {
type Output = NativeFloat;
fn neg(self) -> Self::Output { NativeFloat(-&self.0) }
impl ops::Neg for &NativeFloat64 {
type Output = NativeFloat64;
fn neg(self) -> Self::Output { NativeFloat64(-&self.0) }
}
impl ops::Add<&NativeFloat> for &NativeFloat {
type Output = NativeFloat;
fn add(self, _rhs: &NativeFloat) -> Self::Output {
impl ops::Add<&NativeFloat64> for &NativeFloat64 {
type Output = NativeFloat64;
fn add(self, _rhs: &NativeFloat64) -> 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::Sub<&NativeFloat64> for &NativeFloat64 {
type Output = NativeFloat64;
fn sub(self, rhs: &NativeFloat64) -> Self::Output { NativeFloat64(&self.0 - &rhs.0) }
}
impl ops::Mul<&NativeFloat> for &NativeFloat {
type Output = NativeFloat;
fn mul(self, _rhs: &NativeFloat) -> Self::Output {
impl ops::Mul<&NativeFloat64> for &NativeFloat64 {
type Output = NativeFloat64;
fn mul(self, _rhs: &NativeFloat64) -> Self::Output {
todo!()
}
}
impl ops::Div<&NativeFloat> for &NativeFloat {
type Output = NativeFloat;
fn div(self, _rhs: &NativeFloat) -> Self::Output {
impl ops::Div<&NativeFloat64> for &NativeFloat64 {
type Output = NativeFloat64;
fn div(self, _rhs: &NativeFloat64) -> Self::Output {
todo!()
}
}
impl ops::Rem<&NativeFloat> for &NativeFloat {
type Output = NativeFloat;
fn rem(self, _rhs: &NativeFloat) -> Self::Output {
impl ops::Rem<&NativeFloat64> for &NativeFloat64 {
type Output = NativeFloat64;
fn rem(self, _rhs: &NativeFloat64) -> Self::Output {
todo!()
}
}

View File

@ -31,7 +31,16 @@ impl Number for Rational {
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 {
@ -65,7 +74,9 @@ impl fmt::Display for Rational {
return f.write_str(&result);
} else {
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
result = format!("{0:0>1$}", result, precision + 1);
@ -73,6 +84,11 @@ impl fmt::Display for Rational {
// Add the decimal point
result.insert(result.len() - precision, '.');
// Add the sign
if should_add_minus {
result.insert(0, '-');
}
return f.write_str(&result);
}
} else {

View File

@ -23,6 +23,56 @@ use crate::election::{Candidate, CandidateState, CountCard, CountState, Parcel,
use std::collections::HashMap;
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> {
candidates: HashMap<&'a Candidate, 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;
}
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 {
ballot: b,
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());
}
pub fn calculate_quota<N: Number>(state: &mut CountState<N>) {
fn calculate_quota<N: Number>(state: &mut CountState<N>) {
let mut log = String::new();
// Calculate the total vote
@ -127,7 +177,7 @@ pub fn calculate_quota<N: Number>(state: &mut CountState<N>) {
// TODO: Different rounding rules
state.quota += N::one();
state.quota.floor_mut();
state.quota.floor_mut(0);
log.push_str(format!("{:.2}.", state.quota).as_str());
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;
}
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 mut cands_meeting_quota: Vec<(&&Candidate, &mut CountCard<N>)> = state.candidates.iter_mut()
.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
for<'r> &'r N: Sub<&'r N, Output=N>,
for<'r> &'r N: Neg<Output=N>
@ -178,14 +228,14 @@ where
// Distribute top candidate's surplus
// TODO: Handle ties
let elected_candidate = has_surplus.first_mut().unwrap().0;
distribute_surplus(state, elected_candidate);
distribute_surplus(state, &opts, elected_candidate);
return true;
}
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
for<'r> &'r N: Sub<&'r N, 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;
// Round transfers
// TODO: Make configurable
candidate_transfers.floor_mut();
if let Some(dps) = opts.round_votes {
candidate_transfers.floor_mut(dps);
}
count_card.transfer(&candidate_transfers);
checksum += candidate_transfers;
}
@ -236,8 +287,9 @@ where
state.exhausted.parcels.push(parcel);
let mut exhausted_transfers = result.exhausted.num_ballots * &surplus / &result.total_ballots;
// TODO: Make configurable
exhausted_transfers.floor_mut();
if let Some(dps) = opts.round_votes {
exhausted_transfers.floor_mut(dps);
}
state.exhausted.transfer(&exhausted_transfers);
checksum += exhausted_transfers;
@ -251,7 +303,7 @@ where
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 {
state.kind = None;
state.title = "Bulk election".to_string();
@ -281,7 +333,7 @@ pub fn bulk_elect<N: Number>(state: &mut CountState<N>) -> bool {
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()
.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL)
.collect();
@ -301,12 +353,12 @@ pub fn exclude_hopefuls<N: Number>(state: &mut CountState<N>) -> bool {
vec![&excluded_candidate.name]
);
exclude_candidate(state, excluded_candidate);
exclude_candidate(state, opts, excluded_candidate);
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()
.filter(|(_, cc)| cc.state == CandidateState::EXCLUDED && !cc.votes.is_zero())
.collect();
@ -323,14 +375,14 @@ pub fn continue_exclusion<N: Number>(state: &mut CountState<N>) -> bool {
vec![&excluded_candidate.name]
);
exclude_candidate(state, excluded_candidate);
exclude_candidate(state, opts, excluded_candidate);
return true;
}
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();
count_card.state = CandidateState::EXCLUDED;
state.num_excluded += 1;
@ -352,9 +404,10 @@ fn exclude_candidate<N: Number>(state: &mut CountState<N>, excluded_candidate: &
count_card.parcels.push(parcel);
// Round transfers
// TODO: Make configurable
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);
checksum += candidate_transfers;
}
@ -364,8 +417,9 @@ fn exclude_candidate<N: Number>(state: &mut CountState<N>, excluded_candidate: &
state.exhausted.parcels.push(parcel);
let mut exhausted_transfers = result.exhausted.num_votes;
// TODO: Make configurable
exhausted_transfers.floor_mut();
if let Some(dps) = opts.round_votes {
exhausted_transfers.floor_mut(dps);
}
state.exhausted.transfer(&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);
}
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 {
return true;
}