/* OpenTally: Open-source election vote counting
* Copyright © 2021–2022 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 .
*/
/// Gregory methods of surplus distributions
pub mod gregory;
/// Meek method of surplus distributions, etc.
pub mod meek;
/// Random sample methods of surplus distributions
pub mod sample;
/// WebAssembly wrappers
//#[cfg(target_arch = "wasm32")]
pub mod wasm;
mod options;
pub use options::*;
use crate::candmap::CandidateMap;
use crate::constraints;
use crate::election::{Candidate, CandidateState, CountCard, CountState, Election, RollbackState, StageKind, Vote};
use crate::numbers::Number;
use crate::sharandom::SHARandom;
use crate::ties::{self, TieStrategy};
use itertools::Itertools;
use std::fmt;
use std::ops;
/// An error during the STV count
#[derive(Debug, Eq, PartialEq)]
pub enum STVError {
/// Options for the count are invalid
InvalidOptions(&'static str),
/// Tie could not be resolved
UnresolvedTie,
/// Unrecoverable error during the count
CannotCompleteCount(&'static str),
}
impl STVError {
/// Describe the error
pub fn describe(&self) -> &'static str {
match self {
STVError::InvalidOptions(s) => s,
STVError::UnresolvedTie => "Unable to resolve tie",
STVError::CannotCompleteCount(s) => s,
}
}
}
impl fmt::Display for STVError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.describe())?;
return Ok(());
}
}
/// Preprocess the given election
pub fn preprocess_election(election: &mut Election, opts: &STVOptions) {
// Normalise ballots if required
if opts.surplus == SurplusMethod::IHare || opts.surplus == SurplusMethod::Hare {
election.normalise_ballots();
}
// Process equal rankings
election.realise_equal_rankings();
}
/// Distribute first preferences, and initialise other states such as the random number generator and tie-breaking rules
pub fn count_init<'a, N: Number>(state: &mut CountState<'a, N>, opts: &'a STVOptions) -> Result<(), STVError>
where
for<'r> &'r N: ops::Sub<&'r N, Output=N>,
for<'r> &'r N: ops::Mul<&'r N, Output=N>,
{
// Initialise RNG
for t in opts.ties.iter() {
if let TieStrategy::Random(seed) = t {
state.random = Some(SHARandom::new(seed));
}
}
constraints::update_constraints(state, opts);
distribute_first_preferences(state, opts);
calculate_quota(state, opts);
elect_hopefuls(state, opts, true)?;
init_tiebreaks(state, opts);
return Ok(());
}
/// Perform a single stage of the STV count
///
/// Returns `true` if the count is complete, otherwise `false`.
pub fn count_one_stage<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions) -> Result
where
for<'r> &'r N: ops::Add<&'r N, Output=N>,
for<'r> &'r N: ops::Sub<&'r N, Output=N>,
for<'r> &'r N: ops::Mul<&'r N, Output=N>,
for<'r> &'r N: ops::Div<&'r N, Output=N>,
for<'r> &'r N: ops::Neg