/* 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 .
*/
#![allow(mutable_borrow_reservation_conflict)]
//#[cfg(target_arch = "wasm32")]
pub mod wasm;
use crate::numbers::Number;
use crate::election::{Candidate, CandidateState, CountCard, CountState, Parcel, Vote};
use itertools::Itertools;
use wasm_bindgen::prelude::wasm_bindgen;
use std::cmp::max;
use std::collections::HashMap;
use std::ops;
#[wasm_bindgen]
pub struct STVOptions {
pub round_tvs: Option,
pub round_weights: Option,
pub round_votes: Option,
pub round_quota: Option,
pub sum_surplus_transfers: SumSurplusTransfersMode,
pub quota: QuotaType,
pub quota_criterion: QuotaCriterion,
pub quota_mode: QuotaMode,
pub surplus: SurplusMethod,
pub surplus_order: SurplusOrder,
pub transferable_only: bool,
pub exclusion: ExclusionMethod,
pub bulk_exclude: bool,
pub defer_surpluses: bool,
pub pp_decimals: usize,
}
#[wasm_bindgen]
impl STVOptions {
pub fn new(
round_tvs: Option,
round_weights: Option,
round_votes: Option,
round_quota: Option,
sum_transfers: &str,
quota: &str,
quota_criterion: &str,
quota_mode: &str,
surplus: &str,
surplus_order: &str,
transferable_only: bool,
exclusion: &str,
bulk_exclude: bool,
defer_surpluses: bool,
pp_decimals: usize,
) -> Self {
return STVOptions {
round_tvs,
round_weights,
round_votes,
round_quota,
sum_surplus_transfers: match sum_transfers {
"single_step" => SumSurplusTransfersMode::SingleStep,
"by_value" => SumSurplusTransfersMode::ByValue,
"per_ballot" => SumSurplusTransfersMode::PerBallot,
_ => panic!("Invalid --sum-transfers"),
},
quota: match quota {
"droop" => QuotaType::Droop,
"hare" => QuotaType::Hare,
"droop_exact" => QuotaType::DroopExact,
"hare_exact" => QuotaType::HareExact,
_ => panic!("Invalid --quota"),
},
quota_criterion: match quota_criterion {
"geq" => QuotaCriterion::GreaterOrEqual,
"gt" => QuotaCriterion::Greater,
_ => panic!("Invalid --quota-criterion"),
},
quota_mode: match quota_mode {
"static" => QuotaMode::Static,
"ers97" => QuotaMode::ERS97,
_ => panic!("Invalid --quota-mode"),
},
surplus: match surplus {
"wig" => SurplusMethod::WIG,
"uig" => SurplusMethod::UIG,
"eg" => SurplusMethod::EG,
"meek" => SurplusMethod::Meek,
_ => panic!("Invalid --surplus"),
},
surplus_order: match surplus_order {
"by_size" => SurplusOrder::BySize,
"by_order" => SurplusOrder::ByOrder,
_ => panic!("Invalid --surplus-order"),
},
transferable_only,
exclusion: match exclusion {
"single_stage" => ExclusionMethod::SingleStage,
"by_value" => ExclusionMethod::ByValue,
"parcels_by_order" => ExclusionMethod::ParcelsByOrder,
_ => panic!("Invalid --exclusion"),
},
bulk_exclude,
defer_surpluses,
pp_decimals,
};
}
}
impl STVOptions {
// Not exported to WebAssembly
pub fn describe(&self) -> String {
let mut flags = Vec::new();
flags.push(N::describe());
if let Some(dps) = self.round_tvs { flags.push(format!("--round-tvs {}", dps)); }
if let Some(dps) = self.round_weights { flags.push(format!("--round-weights {}", dps)); }
if let Some(dps) = self.round_votes { flags.push(format!("--round-votes {}", dps)); }
if let Some(dps) = self.round_quota { flags.push(format!("--round-quota {}", dps)); }
if self.quota != QuotaType::Droop { flags.push(self.quota.describe()); }
if self.quota_criterion != QuotaCriterion::GreaterOrEqual { flags.push(self.quota_criterion.describe()); }
if self.quota_mode != QuotaMode::Static { flags.push(self.quota_mode.describe()); }
if self.surplus != SurplusMethod::WIG { flags.push(self.surplus.describe()); }
if self.surplus_order != SurplusOrder::BySize { flags.push(self.surplus_order.describe()); }
if self.transferable_only { flags.push("--transferable-only".to_string()); }
if self.exclusion != ExclusionMethod::SingleStage { flags.push(self.exclusion.describe()); }
if self.pp_decimals != 2 { flags.push(format!("--pp-decimals {}", self.pp_decimals)); }
return flags.join(" ");
}
}
#[wasm_bindgen]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum SumSurplusTransfersMode {
SingleStep,
ByValue,
PerBallot,
}
#[wasm_bindgen]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum QuotaType {
Droop,
Hare,
DroopExact,
HareExact,
}
impl QuotaType {
fn describe(self) -> String {
match self {
QuotaType::Droop => "--quota droop",
QuotaType::Hare => "--quota hare",
QuotaType::DroopExact => "--quota droop_exact",
QuotaType::HareExact => "--quota hare_exact",
}.to_string()
}
}
#[wasm_bindgen]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum QuotaCriterion {
GreaterOrEqual,
Greater,
}
impl QuotaCriterion {
fn describe(self) -> String {
match self {
QuotaCriterion::GreaterOrEqual => "--quota-criterion geq",
QuotaCriterion::Greater => "--quota-criterion gt",
}.to_string()
}
}
#[wasm_bindgen]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum QuotaMode {
Static,
ERS97,
}
impl QuotaMode {
fn describe(self) -> String {
match self {
QuotaMode::Static => "--quota-mode static",
QuotaMode::ERS97 => "--quota-mode ers97",
}.to_string()
}
}
#[wasm_bindgen]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum SurplusMethod {
WIG,
UIG,
EG,
Meek,
}
impl SurplusMethod {
fn describe(self) -> String {
match self {
SurplusMethod::WIG => "--surplus wig",
SurplusMethod::UIG => "--surplus uig",
SurplusMethod::EG => "--surplus eg",
SurplusMethod::Meek => "--surplus meek",
}.to_string()
}
}
#[wasm_bindgen]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum SurplusOrder {
BySize,
ByOrder,
}
impl SurplusOrder {
fn describe(self) -> String {
match self {
SurplusOrder::BySize => "--surplus-order by_size",
SurplusOrder::ByOrder => "--surplus-order by_order",
}.to_string()
}
}
#[wasm_bindgen]
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum ExclusionMethod {
SingleStage,
ByValue,
ParcelsByOrder,
}
impl ExclusionMethod {
fn describe(self) -> String {
match self {
ExclusionMethod::SingleStage => "--exclusion single_stage",
ExclusionMethod::ByValue => "--exclusion by_value",
ExclusionMethod::ParcelsByOrder => "--exclusion parcels_by_order",
}.to_string()
}
}
pub fn count_init(mut state: &mut CountState<'_, N>, opts: &STVOptions) {
distribute_first_preferences(&mut state);
calculate_quota(&mut state, opts);
elect_meeting_quota(&mut state, opts);
}
pub fn count_one_stage<'a, N: Number>(mut state: &mut CountState<'a, N>, opts: &STVOptions) -> bool
where
for<'r> &'r N: ops::Sub<&'r N, Output=N>,
for<'r> &'r N: ops::Div<&'r N, Output=N>,
for<'r> &'r N: ops::Neg