2021-05-28 11:58:40 +02:00
|
|
|
/* 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 <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2021-05-28 18:13:47 +02:00
|
|
|
use crate::logger::Logger;
|
2021-05-28 11:58:40 +02:00
|
|
|
use crate::numbers::Number;
|
2021-06-12 19:15:15 +02:00
|
|
|
use crate::sharandom::SHARandom;
|
2021-05-28 11:58:40 +02:00
|
|
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
pub struct Election<N> {
|
2021-06-03 13:35:25 +02:00
|
|
|
pub name: String,
|
2021-05-28 11:58:40 +02:00
|
|
|
pub seats: usize,
|
|
|
|
pub candidates: Vec<Candidate>,
|
2021-06-11 16:50:01 +02:00
|
|
|
pub withdrawn_candidates: Vec<usize>,
|
2021-05-28 11:58:40 +02:00
|
|
|
pub ballots: Vec<Ballot<N>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<N: Number> Election<N> {
|
2021-05-30 10:28:39 +02:00
|
|
|
pub fn from_blt<I: Iterator<Item=String>>(mut lines: I) -> Self {
|
2021-05-28 11:58:40 +02:00
|
|
|
// Read first line
|
2021-05-30 10:28:39 +02:00
|
|
|
let line = lines.next().expect("Unexpected EOF");
|
2021-05-28 11:58:40 +02:00
|
|
|
let mut bits = line.split(" ");
|
|
|
|
let num_candidates = bits.next().expect("Syntax Error").parse().expect("Syntax Error");
|
|
|
|
let seats: usize = bits.next().expect("Syntax Error").parse().expect("Syntax Error");
|
|
|
|
|
|
|
|
// Initialise the Election object
|
|
|
|
let mut election = Election {
|
2021-06-03 13:35:25 +02:00
|
|
|
name: String::new(),
|
2021-05-28 11:58:40 +02:00
|
|
|
seats: seats,
|
|
|
|
candidates: Vec::with_capacity(num_candidates),
|
2021-06-11 16:50:01 +02:00
|
|
|
withdrawn_candidates: Vec::new(),
|
2021-05-28 11:58:40 +02:00
|
|
|
ballots: Vec::new(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Read ballots
|
|
|
|
for line in &mut lines {
|
|
|
|
if line == "0" {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut bits = line.split(" ");
|
2021-06-11 16:50:01 +02:00
|
|
|
|
|
|
|
if line.starts_with("-") {
|
|
|
|
// Withdrawn candidates
|
|
|
|
for bit in bits.into_iter() {
|
|
|
|
let val = bit[1..bit.len()].parse::<usize>().expect("Syntax Error");
|
|
|
|
election.withdrawn_candidates.push(val - 1);
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-05-28 11:58:40 +02:00
|
|
|
let value = N::parse(bits.next().expect("Syntax Error"));
|
|
|
|
|
|
|
|
let mut ballot = Ballot {
|
|
|
|
orig_value: value,
|
|
|
|
preferences: Vec::new(),
|
|
|
|
};
|
|
|
|
|
|
|
|
for preference in bits {
|
|
|
|
if preference != "0" {
|
|
|
|
let preference = preference.parse::<usize>().expect("Syntax Error");
|
|
|
|
ballot.preferences.push(preference - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
election.ballots.push(ballot);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read candidates
|
2021-06-03 13:35:25 +02:00
|
|
|
for line in lines.by_ref().take(num_candidates) {
|
2021-05-30 10:28:39 +02:00
|
|
|
let mut line = &line[..];
|
2021-05-28 11:58:40 +02:00
|
|
|
if line.starts_with("\"") && line.ends_with("\"") {
|
|
|
|
line = &line[1..line.len()-1];
|
|
|
|
}
|
|
|
|
|
|
|
|
election.candidates.push(Candidate { name: line.to_string() });
|
|
|
|
}
|
|
|
|
|
2021-06-03 13:35:25 +02:00
|
|
|
// Read name
|
|
|
|
let line = lines.next().expect("Syntax Error");
|
|
|
|
let mut line = &line[..];
|
|
|
|
if line.starts_with("\"") && line.ends_with("\"") {
|
|
|
|
line = &line[1..line.len()-1];
|
|
|
|
}
|
|
|
|
election.name.push_str(line);
|
|
|
|
|
2021-05-28 11:58:40 +02:00
|
|
|
return election;
|
|
|
|
}
|
2021-06-11 13:23:08 +02:00
|
|
|
|
|
|
|
pub fn normalise_ballots(&mut self) {
|
|
|
|
let mut normalised_ballots = Vec::new();
|
|
|
|
for ballot in self.ballots.iter() {
|
|
|
|
let mut n = N::new();
|
|
|
|
let one = N::one();
|
|
|
|
while n < ballot.orig_value {
|
|
|
|
let new_ballot = Ballot {
|
|
|
|
orig_value: N::one(),
|
|
|
|
preferences: ballot.preferences.clone(),
|
|
|
|
};
|
|
|
|
normalised_ballots.push(new_ballot);
|
|
|
|
n += &one;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.ballots = normalised_ballots;
|
|
|
|
}
|
2021-05-28 11:58:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(PartialEq, Eq, Hash)]
|
|
|
|
pub struct Candidate {
|
|
|
|
pub name: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct CountState<'a, N> {
|
|
|
|
pub election: &'a Election<N>,
|
|
|
|
|
|
|
|
pub candidates: HashMap<&'a Candidate, CountCard<'a, N>>,
|
|
|
|
pub exhausted: CountCard<'a, N>,
|
2021-05-28 16:43:58 +02:00
|
|
|
pub loss_fraction: CountCard<'a, N>,
|
2021-05-28 11:58:40 +02:00
|
|
|
|
2021-06-12 16:15:14 +02:00
|
|
|
pub forwards_tiebreak: Option<HashMap<&'a Candidate, usize>>,
|
|
|
|
pub backwards_tiebreak: Option<HashMap<&'a Candidate, usize>>,
|
2021-06-12 19:15:15 +02:00
|
|
|
pub random: Option<SHARandom<'a>>,
|
2021-06-12 16:15:14 +02:00
|
|
|
|
2021-06-07 12:52:18 +02:00
|
|
|
pub quota: Option<N>,
|
|
|
|
pub vote_required_election: Option<N>,
|
2021-05-28 11:58:40 +02:00
|
|
|
|
|
|
|
pub num_elected: usize,
|
|
|
|
pub num_excluded: usize,
|
2021-05-28 17:22:46 +02:00
|
|
|
|
|
|
|
pub kind: Option<&'a str>,
|
|
|
|
pub title: String,
|
2021-05-28 18:13:47 +02:00
|
|
|
pub logger: Logger<'a>,
|
2021-05-28 11:58:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, N: Number> CountState<'a, N> {
|
|
|
|
pub fn new(election: &'a Election<N>) -> Self {
|
|
|
|
let mut state = CountState {
|
|
|
|
election: &election,
|
|
|
|
candidates: HashMap::new(),
|
|
|
|
exhausted: CountCard::new(),
|
2021-05-28 16:43:58 +02:00
|
|
|
loss_fraction: CountCard::new(),
|
2021-06-12 16:15:14 +02:00
|
|
|
forwards_tiebreak: None,
|
|
|
|
backwards_tiebreak: None,
|
2021-06-12 19:15:15 +02:00
|
|
|
random: None,
|
2021-06-07 12:52:18 +02:00
|
|
|
quota: None,
|
|
|
|
vote_required_election: None,
|
2021-05-28 11:58:40 +02:00
|
|
|
num_elected: 0,
|
|
|
|
num_excluded: 0,
|
2021-05-28 17:22:46 +02:00
|
|
|
kind: None,
|
|
|
|
title: String::new(),
|
2021-05-28 18:13:47 +02:00
|
|
|
logger: Logger { entries: Vec::new() },
|
2021-05-28 11:58:40 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
for candidate in election.candidates.iter() {
|
|
|
|
state.candidates.insert(candidate, CountCard::new());
|
|
|
|
}
|
|
|
|
|
2021-06-11 16:50:01 +02:00
|
|
|
for withdrawn_idx in election.withdrawn_candidates.iter() {
|
|
|
|
state.candidates.get_mut(&election.candidates[*withdrawn_idx]).unwrap().state = CandidateState::Withdrawn;
|
|
|
|
}
|
|
|
|
|
2021-05-28 11:58:40 +02:00
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn step_all(&mut self) {
|
|
|
|
for (_, count_card) in self.candidates.iter_mut() {
|
|
|
|
count_card.step();
|
|
|
|
}
|
|
|
|
self.exhausted.step();
|
2021-05-28 16:43:58 +02:00
|
|
|
self.loss_fraction.step();
|
2021-05-28 11:58:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-28 17:22:46 +02:00
|
|
|
#[allow(dead_code)]
|
2021-05-28 11:58:40 +02:00
|
|
|
pub enum CountStateOrRef<'a, N> {
|
2021-05-28 17:22:46 +02:00
|
|
|
State(CountState<'a, N>), // NYI: May be used e.g. for tie-breaking or rollback-based constraints
|
2021-05-28 11:58:40 +02:00
|
|
|
Ref(&'a CountState<'a, N>),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, N> CountStateOrRef<'a, N> {
|
2021-05-28 17:22:46 +02:00
|
|
|
pub fn from(state: &'a CountState<N>) -> Self {
|
|
|
|
return Self::Ref(state);
|
|
|
|
}
|
|
|
|
|
2021-05-28 11:58:40 +02:00
|
|
|
pub fn as_ref(&self) -> &CountState<N> {
|
|
|
|
match self {
|
|
|
|
CountStateOrRef::State(state) => &state,
|
|
|
|
CountStateOrRef::Ref(state) => state,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct StageResult<'a, N> {
|
2021-05-28 17:22:46 +02:00
|
|
|
pub kind: Option<&'a str>,
|
|
|
|
pub title: &'a String,
|
2021-05-28 18:13:47 +02:00
|
|
|
pub logs: Vec<String>,
|
2021-05-28 11:58:40 +02:00
|
|
|
pub state: CountStateOrRef<'a, N>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct CountCard<'a, N> {
|
|
|
|
pub state: CandidateState,
|
|
|
|
pub order_elected: isize,
|
|
|
|
|
|
|
|
pub orig_votes: N,
|
|
|
|
pub transfers: N,
|
|
|
|
pub votes: N,
|
|
|
|
|
|
|
|
pub parcels: Vec<Parcel<'a, N>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, N: Number> CountCard<'a, N> {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
return CountCard {
|
2021-06-11 16:50:01 +02:00
|
|
|
state: CandidateState::Hopeful,
|
2021-05-28 11:58:40 +02:00
|
|
|
order_elected: 0,
|
|
|
|
orig_votes: N::new(),
|
|
|
|
transfers: N::new(),
|
|
|
|
votes: N::new(),
|
|
|
|
parcels: Vec::new(),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
//pub fn votes(&'a self) -> N {
|
|
|
|
// return self.orig_votes.clone() + &self.transfers;
|
|
|
|
//}
|
|
|
|
|
|
|
|
pub fn transfer(&mut self, transfer: &'_ N) {
|
|
|
|
self.transfers += transfer;
|
|
|
|
self.votes += transfer;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn step(&mut self) {
|
|
|
|
self.orig_votes = self.votes.clone();
|
|
|
|
self.transfers = N::new();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub type Parcel<'a, N> = Vec<Vote<'a, N>>;
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Vote<'a, N> {
|
|
|
|
pub ballot: &'a Ballot<N>,
|
|
|
|
pub value: N,
|
|
|
|
pub up_to_pref: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Ballot<N> {
|
|
|
|
pub orig_value: N,
|
|
|
|
pub preferences: Vec<usize>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
#[derive(PartialEq)]
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub enum CandidateState {
|
2021-06-11 16:50:01 +02:00
|
|
|
Hopeful,
|
|
|
|
Guarded,
|
|
|
|
Elected,
|
|
|
|
Doomed,
|
|
|
|
Withdrawn,
|
|
|
|
Excluded,
|
2021-05-28 11:58:40 +02:00
|
|
|
}
|