2021-06-16 13:00:54 +10: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/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
use super::{STVError, STVOptions};
|
|
|
|
|
2021-09-06 02:43:33 +10:00
|
|
|
use crate::election::{Ballot, Candidate, CandidateState, CountCard, CountState, Election, StageKind};
|
2021-06-16 13:00:54 +10:00
|
|
|
use crate::numbers::Number;
|
|
|
|
|
|
|
|
use itertools::Itertools;
|
|
|
|
|
|
|
|
use std::cmp::max;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::ops;
|
|
|
|
|
|
|
|
/// Ballot in a [BallotTree]
|
|
|
|
#[derive(Clone)]
|
|
|
|
struct BallotInTree<'b, N: Number> {
|
|
|
|
ballot: &'b Ballot<N>,
|
|
|
|
/// Index of the next preference to examine
|
|
|
|
up_to_pref: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Tree-packed ballot representation
|
|
|
|
pub struct BallotTree<'t, N: Number> {
|
|
|
|
num_ballots: N,
|
|
|
|
ballots: Vec<BallotInTree<'t, N>>,
|
|
|
|
next_preferences: Option<Box<HashMap<&'t Candidate, BallotTree<'t, N>>>>,
|
|
|
|
next_exhausted: Option<Box<BallotTree<'t, N>>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'t, N: Number> BallotTree<'t, N> {
|
|
|
|
/// Construct a new empty [BallotTree]
|
|
|
|
fn new() -> Self {
|
|
|
|
BallotTree {
|
|
|
|
num_ballots: N::new(),
|
|
|
|
ballots: Vec::new(),
|
|
|
|
next_preferences: None,
|
|
|
|
next_exhausted: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Descend one level of the [BallotTree]
|
|
|
|
fn descend_tree(&mut self, candidates: &'t Vec<Candidate>) {
|
|
|
|
let mut next_preferences: HashMap<&Candidate, BallotTree<N>> = HashMap::new();
|
|
|
|
let mut next_exhausted = BallotTree::new();
|
|
|
|
|
|
|
|
for bit in self.ballots.iter() {
|
|
|
|
if bit.up_to_pref < bit.ballot.preferences.len() {
|
2021-09-03 23:53:15 +10:00
|
|
|
let preference = &bit.ballot.preferences[bit.up_to_pref];
|
|
|
|
|
|
|
|
if preference.len() != 1 {
|
|
|
|
todo!();
|
|
|
|
}
|
|
|
|
|
|
|
|
let candidate = &candidates[*preference.first().unwrap()];
|
2021-06-16 13:00:54 +10:00
|
|
|
|
|
|
|
if next_preferences.contains_key(candidate) {
|
|
|
|
let np_bt = next_preferences.get_mut(candidate).unwrap();
|
|
|
|
np_bt.num_ballots += &bit.ballot.orig_value;
|
|
|
|
np_bt.ballots.push(BallotInTree {
|
|
|
|
ballot: bit.ballot,
|
|
|
|
up_to_pref: bit.up_to_pref + 1,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
let mut np_bt = BallotTree::new();
|
|
|
|
np_bt.num_ballots += &bit.ballot.orig_value;
|
|
|
|
np_bt.ballots.push(BallotInTree {
|
|
|
|
ballot: bit.ballot,
|
|
|
|
up_to_pref: bit.up_to_pref + 1,
|
|
|
|
});
|
|
|
|
next_preferences.insert(candidate, np_bt);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Exhausted
|
|
|
|
next_exhausted.num_ballots += &bit.ballot.orig_value;
|
|
|
|
next_exhausted.ballots.push(bit.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.next_preferences = Some(Box::new(next_preferences));
|
|
|
|
self.next_exhausted = Some(Box::new(next_exhausted));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Initialise keep values, ballot tree and distribute preferences
|
2021-06-18 18:48:12 +10:00
|
|
|
pub fn distribute_first_preferences<N: Number>(state: &mut CountState<N>, opts: &STVOptions)
|
2021-06-16 13:00:54 +10:00
|
|
|
where
|
|
|
|
for<'r> &'r N: ops::Sub<&'r N, Output=N>,
|
|
|
|
for<'r> &'r N: ops::Mul<&'r N, Output=N>,
|
|
|
|
{
|
|
|
|
// Initialise keep values
|
|
|
|
for (_, count_card) in state.candidates.iter_mut() {
|
|
|
|
count_card.keep_value = Some(N::one());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialise ballot tree
|
|
|
|
let mut ballot_tree = BallotTree::new();
|
|
|
|
for ballot in state.election.ballots.iter() {
|
|
|
|
ballot_tree.ballots.push(BallotInTree {
|
|
|
|
ballot: ballot,
|
|
|
|
up_to_pref: 0,
|
|
|
|
});
|
|
|
|
ballot_tree.num_ballots += &ballot.orig_value;
|
|
|
|
}
|
|
|
|
state.ballot_tree = Some(ballot_tree);
|
|
|
|
|
|
|
|
// Distribute preferences
|
2021-06-18 18:48:12 +10:00
|
|
|
distribute_preferences(state, opts);
|
2021-06-16 13:00:54 +10:00
|
|
|
|
|
|
|
// Recalculate transfers
|
|
|
|
for (_, count_card) in state.candidates.iter_mut() {
|
|
|
|
count_card.transfers.assign(&count_card.votes);
|
|
|
|
}
|
|
|
|
state.exhausted.transfers.assign(&state.exhausted.votes);
|
|
|
|
|
2021-09-05 00:04:09 +10:00
|
|
|
// Calculate loss by fraction - if minivoters used
|
|
|
|
if let Some(orig_total) = &state.election.total_votes {
|
|
|
|
let mut total_votes = state.candidates.values().fold(N::new(), |acc, cc| acc + &cc.votes);
|
|
|
|
total_votes += &state.exhausted.votes;
|
|
|
|
let lbf = orig_total - &total_votes;
|
|
|
|
|
|
|
|
state.loss_fraction.votes = lbf.clone();
|
|
|
|
state.loss_fraction.transfers = lbf;
|
|
|
|
}
|
|
|
|
|
2021-09-06 02:43:33 +10:00
|
|
|
state.title = StageKind::FirstPreferences;
|
2021-06-16 13:00:54 +10:00
|
|
|
state.logger.log_literal("First preferences distributed.".to_string());
|
|
|
|
}
|
|
|
|
|
|
|
|
/// (Re)distribute preferences according to candidate keep values
|
2021-06-18 18:48:12 +10:00
|
|
|
pub fn distribute_preferences<N: Number>(state: &mut CountState<N>, opts: &STVOptions)
|
2021-06-16 13:00:54 +10:00
|
|
|
where
|
|
|
|
for<'r> &'r N: ops::Sub<&'r N, Output=N>,
|
|
|
|
for<'r> &'r N: ops::Mul<&'r N, Output=N>,
|
|
|
|
{
|
|
|
|
// Reset votes
|
|
|
|
for (_, count_card) in state.candidates.iter_mut() {
|
|
|
|
//count_card.orig_votes = N::new();
|
|
|
|
//count_card.transfers = N::new();
|
|
|
|
count_card.votes = N::new();
|
|
|
|
}
|
|
|
|
state.exhausted.votes = N::new();
|
|
|
|
|
2021-06-18 18:48:12 +10:00
|
|
|
distribute_recursively(&mut state.candidates, &mut state.exhausted, state.ballot_tree.as_mut().unwrap(), N::one(), &state.election, opts);
|
2021-06-16 13:00:54 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Distribute preferences recursively
|
|
|
|
///
|
|
|
|
/// Called by [distribute_preferences]
|
2021-06-18 18:48:12 +10:00
|
|
|
fn distribute_recursively<'t, N: Number>(candidates: &mut HashMap<&'t Candidate, CountCard<N>>, exhausted: &mut CountCard<N>, tree: &mut BallotTree<'t, N>, remaining_multiplier: N, election: &'t Election<N>, opts: &STVOptions)
|
2021-06-16 13:00:54 +10:00
|
|
|
where
|
|
|
|
for<'r> &'r N: ops::Mul<&'r N, Output=N>,
|
|
|
|
{
|
|
|
|
// Descend tree if required
|
|
|
|
if let None = tree.next_exhausted {
|
|
|
|
tree.descend_tree(&election.candidates);
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: Possibility of infinite loop if malformed inputs?
|
|
|
|
|
|
|
|
// Credit votes at this level
|
|
|
|
for (candidate, cand_tree) in tree.next_preferences.as_mut().unwrap().as_mut().iter_mut() {
|
|
|
|
let count_card = candidates.get_mut(candidate).unwrap();
|
|
|
|
match count_card.state {
|
|
|
|
CandidateState::Hopeful | CandidateState::Guarded | CandidateState::Doomed => {
|
|
|
|
// Hopeful candidate has keep value 1, so transfer entire remaining value
|
2021-06-18 18:48:12 +10:00
|
|
|
let mut to_transfer = &remaining_multiplier * &cand_tree.num_ballots;
|
|
|
|
if let Some(dps) = opts.round_votes {
|
|
|
|
// NZ Meek STV rounds *up*!
|
|
|
|
to_transfer.ceil_mut(dps);
|
|
|
|
}
|
|
|
|
count_card.votes += to_transfer;
|
2021-06-16 13:00:54 +10:00
|
|
|
}
|
|
|
|
CandidateState::Elected => {
|
|
|
|
// Transfer according to elected candidate's keep value
|
2021-06-18 18:48:12 +10:00
|
|
|
let mut to_transfer = &remaining_multiplier * &cand_tree.num_ballots * count_card.keep_value.as_ref().unwrap();
|
|
|
|
if let Some(dps) = opts.round_votes {
|
|
|
|
to_transfer.ceil_mut(dps);
|
|
|
|
}
|
|
|
|
count_card.votes += to_transfer;
|
|
|
|
|
|
|
|
let mut new_remaining_multiplier = &remaining_multiplier * &(N::one() - count_card.keep_value.as_ref().unwrap());
|
2021-08-03 16:46:21 +10:00
|
|
|
if let Some(dps) = opts.round_surplus_fractions {
|
2021-06-18 18:48:12 +10:00
|
|
|
new_remaining_multiplier.ceil_mut(dps);
|
|
|
|
}
|
|
|
|
|
2021-06-16 13:00:54 +10:00
|
|
|
// Recurse
|
2021-06-18 18:48:12 +10:00
|
|
|
distribute_recursively(candidates, exhausted, cand_tree, new_remaining_multiplier, election, opts);
|
2021-06-16 13:00:54 +10:00
|
|
|
}
|
|
|
|
CandidateState::Excluded | CandidateState::Withdrawn => {
|
|
|
|
// Excluded candidate has keep value 0, so skip over this candidate
|
|
|
|
// Recurse
|
2021-06-18 18:48:12 +10:00
|
|
|
distribute_recursively(candidates, exhausted, cand_tree, remaining_multiplier.clone(), election, opts);
|
2021-06-16 13:00:54 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Credit exhausted votes at this level
|
|
|
|
exhausted.votes += &remaining_multiplier * &tree.next_exhausted.as_ref().unwrap().as_ref().num_ballots;
|
|
|
|
}
|
|
|
|
|
2021-06-18 18:48:12 +10:00
|
|
|
fn recompute_keep_values<'s, N: Number>(state: &mut CountState<'s, N>, opts: &STVOptions, has_surplus: &Vec<&'s Candidate>) {
|
|
|
|
for candidate in has_surplus.into_iter() {
|
|
|
|
let count_card = state.candidates.get_mut(candidate).unwrap();
|
|
|
|
count_card.keep_value = Some(count_card.keep_value.take().unwrap() * state.quota.as_ref().unwrap() / &count_card.votes);
|
|
|
|
|
2021-08-03 16:46:21 +10:00
|
|
|
if let Some(dps) = opts.round_values {
|
2021-06-18 18:48:12 +10:00
|
|
|
// NZ Meek STV rounds *up*!
|
|
|
|
count_card.keep_value.as_mut().unwrap().ceil_mut(dps);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-27 17:44:30 +10:00
|
|
|
/// Determine if the specified surpluses should be distributed, according to [STVOptions::meek_surplus_tolerance]
|
2021-06-29 15:31:38 +10:00
|
|
|
fn should_distribute_surpluses<'a, N: Number>(state: &CountState<'a, N>, has_surplus: &Vec<&'a Candidate>, opts: &STVOptions) -> bool
|
2021-06-18 18:48:12 +10:00
|
|
|
where
|
|
|
|
for<'r> &'r N: ops::Sub<&'r N, Output=N>,
|
|
|
|
for<'r> &'r N: ops::Div<&'r N, Output=N>,
|
|
|
|
{
|
|
|
|
if opts.meek_surplus_tolerance.ends_with('%') {
|
|
|
|
// Distribute if any candidate has a surplus exceeding the tolerance
|
|
|
|
let quota_tolerance = N::parse(&opts.meek_surplus_tolerance[0..opts.meek_surplus_tolerance.len()-1]) / N::from(100) + N::one();
|
|
|
|
return has_surplus.iter().any(|c| {
|
2021-06-29 15:31:38 +10:00
|
|
|
let count_card = &state.candidates[c];
|
2021-06-18 18:48:12 +10:00
|
|
|
return &count_card.votes / state.quota.as_ref().unwrap() > quota_tolerance;
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// Distribute if the total surplus exceeds the tolerance
|
|
|
|
let quota_tolerance = N::parse(&opts.meek_surplus_tolerance);
|
|
|
|
let total_surpluses = has_surplus.iter()
|
2021-06-29 15:31:38 +10:00
|
|
|
.fold(N::new(), |acc, c| acc + &state.candidates[c].votes - state.quota.as_ref().unwrap());
|
2021-06-18 18:48:12 +10:00
|
|
|
return total_surpluses > quota_tolerance;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-16 13:00:54 +10:00
|
|
|
/// Recalculate all candidate keep factors to distribute all surpluses according to the Meek method
|
|
|
|
pub fn distribute_surpluses<N: Number>(state: &mut CountState<N>, opts: &STVOptions) -> Result<bool, STVError>
|
|
|
|
where
|
|
|
|
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>,
|
|
|
|
{
|
|
|
|
let quota = state.quota.as_ref().unwrap();
|
2021-06-20 01:28:54 +10:00
|
|
|
let mut has_surplus: Vec<&Candidate> = state.election.candidates.iter()
|
2021-06-16 13:00:54 +10:00
|
|
|
.filter(|c| {
|
2021-06-29 15:31:38 +10:00
|
|
|
let count_card = &state.candidates[c];
|
2021-06-18 18:48:12 +10:00
|
|
|
return count_card.state == CandidateState::Elected && &count_card.votes > quota;
|
2021-06-16 13:00:54 +10:00
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
2021-06-18 18:48:12 +10:00
|
|
|
let mut should_distribute = should_distribute_surpluses(state, &has_surplus, opts);
|
|
|
|
if should_distribute {
|
|
|
|
// Determine if surplues can be deferred
|
|
|
|
if opts.defer_surpluses {
|
|
|
|
let total_surpluses = has_surplus.iter()
|
2021-06-29 15:31:38 +10:00
|
|
|
.fold(N::new(), |acc, c| acc + &state.candidates[c].votes - quota);
|
2021-06-18 18:48:12 +10:00
|
|
|
if super::can_defer_surpluses(state, opts, &total_surpluses) {
|
|
|
|
state.logger.log_literal(format!("Distribution of surpluses totalling {:.dps$} votes will be deferred.", total_surpluses, dps=opts.pp_decimals));
|
|
|
|
return Ok(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut surpluses_deferred = None; // Option<total_surpluses>
|
|
|
|
let mut candidates_elected = None; // Option<LogEntry>
|
2021-06-16 13:00:54 +10:00
|
|
|
|
|
|
|
let orig_candidates = state.candidates.clone();
|
|
|
|
let orig_exhausted = state.exhausted.clone();
|
|
|
|
|
|
|
|
let mut num_iterations: u32 = 0;
|
|
|
|
|
2021-06-18 18:48:12 +10:00
|
|
|
while should_distribute {
|
2021-06-16 13:00:54 +10:00
|
|
|
num_iterations += 1;
|
|
|
|
|
|
|
|
// Recompute keep values
|
2021-06-18 18:48:12 +10:00
|
|
|
recompute_keep_values(state, opts, &has_surplus);
|
2021-06-16 13:00:54 +10:00
|
|
|
|
|
|
|
// Redistribute votes
|
2021-06-18 18:48:12 +10:00
|
|
|
distribute_preferences(state, opts);
|
2021-06-16 13:00:54 +10:00
|
|
|
|
|
|
|
// Recompute quota if more ballots have become exhausted
|
|
|
|
super::calculate_quota(state, opts);
|
|
|
|
|
2021-08-07 18:51:48 +10:00
|
|
|
if opts.immediate_elect {
|
2021-06-18 18:48:12 +10:00
|
|
|
// Try to elect candidates
|
2021-08-07 18:51:48 +10:00
|
|
|
if super::elect_hopefuls(state, opts, true)? {
|
2021-06-18 18:48:12 +10:00
|
|
|
candidates_elected = Some(state.logger.entries.pop().unwrap());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-06-16 13:00:54 +10:00
|
|
|
|
|
|
|
let quota = state.quota.as_ref().unwrap();
|
|
|
|
has_surplus = state.election.candidates.iter()
|
|
|
|
.filter(|c| {
|
2021-06-29 15:31:38 +10:00
|
|
|
let count_card = &state.candidates[c];
|
2021-06-18 18:48:12 +10:00
|
|
|
return count_card.state == CandidateState::Elected && &count_card.votes > quota;
|
2021-06-16 13:00:54 +10:00
|
|
|
})
|
|
|
|
.collect();
|
2021-06-18 18:48:12 +10:00
|
|
|
|
|
|
|
should_distribute = should_distribute_surpluses(state, &has_surplus, opts);
|
|
|
|
|
|
|
|
// Determine if surplues can be deferred
|
|
|
|
if should_distribute && opts.defer_surpluses {
|
|
|
|
let total_surpluses = has_surplus.iter()
|
2021-06-29 15:31:38 +10:00
|
|
|
.fold(N::new(), |acc, c| acc + &state.candidates[c].votes - quota);
|
2021-06-18 18:48:12 +10:00
|
|
|
if super::can_defer_surpluses(state, opts, &total_surpluses) {
|
|
|
|
surpluses_deferred = Some(total_surpluses);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-06-16 13:00:54 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
// Recalculate transfers
|
2021-06-16 18:24:30 +10:00
|
|
|
let mut checksum = N::new();
|
2021-06-16 13:00:54 +10:00
|
|
|
for (candidate, count_card) in state.candidates.iter_mut() {
|
2021-06-29 15:31:38 +10:00
|
|
|
count_card.transfers = &count_card.votes - &orig_candidates[candidate].votes;
|
2021-06-16 18:24:30 +10:00
|
|
|
checksum += &count_card.transfers;
|
2021-06-16 13:00:54 +10:00
|
|
|
}
|
|
|
|
state.exhausted.transfers = &state.exhausted.votes - &orig_exhausted.votes;
|
2021-06-16 18:24:30 +10:00
|
|
|
checksum += &state.exhausted.transfers;
|
|
|
|
state.loss_fraction.transfer(&-checksum);
|
2021-06-16 13:00:54 +10:00
|
|
|
|
|
|
|
// Remove intermediate logs on quota calculation
|
|
|
|
state.logger.entries.clear();
|
|
|
|
|
2021-09-06 02:43:33 +10:00
|
|
|
state.title = StageKind::SurplusesDistributed;
|
2021-06-16 13:00:54 +10:00
|
|
|
if num_iterations == 1 {
|
|
|
|
state.logger.log_literal("Surpluses distributed, requiring 1 iteration.".to_string());
|
|
|
|
} else {
|
|
|
|
state.logger.log_literal(format!("Surpluses distributed, requiring {} iterations.", num_iterations));
|
|
|
|
}
|
|
|
|
|
2021-06-18 18:48:12 +10:00
|
|
|
if let Some(total_surpluses) = surpluses_deferred {
|
|
|
|
state.logger.log_literal(format!("Distribution of surpluses totalling {:.dps$} votes will be deferred.", total_surpluses, dps=opts.pp_decimals));
|
|
|
|
}
|
|
|
|
|
|
|
|
// If candidates were elected, retain that log entry
|
|
|
|
if let Some(log_entry) = candidates_elected {
|
|
|
|
state.logger.log(log_entry);
|
|
|
|
}
|
|
|
|
|
2021-06-16 13:00:54 +10:00
|
|
|
let kv_str = state.election.candidates.iter()
|
2021-06-29 15:31:38 +10:00
|
|
|
.map(|c| (c, &state.candidates[c]))
|
2021-06-16 13:00:54 +10:00
|
|
|
.filter(|(_, cc)| cc.state == CandidateState::Elected)
|
|
|
|
.sorted_unstable_by(|a, b| a.1.order_elected.cmp(&b.1.order_elected))
|
|
|
|
.map(|(c, cc)| format!("{} ({:.dps2$})", c.name, cc.keep_value.as_ref().unwrap(), dps2=max(opts.pp_decimals, 2)))
|
|
|
|
.join(", ");
|
|
|
|
|
|
|
|
state.logger.log_literal(format!("Keep values of elected candidates are: {}.", kv_str));
|
|
|
|
|
|
|
|
return Ok(true);
|
|
|
|
}
|
|
|
|
return Ok(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Exclude the given candidates according to the Meek method
|
2021-06-18 18:48:12 +10:00
|
|
|
pub fn exclude_candidates<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, excluded_candidates: Vec<&'a Candidate>)
|
2021-06-16 13:00:54 +10:00
|
|
|
where
|
|
|
|
for<'r> &'r N: ops::Sub<&'r N, Output=N>,
|
|
|
|
for<'r> &'r N: ops::Mul<&'r N, Output=N>,
|
|
|
|
{
|
2021-06-20 01:28:54 +10:00
|
|
|
// NZ Meek STV: Iterate keep values one round before exclusion
|
|
|
|
if opts.meek_nz_exclusion {
|
|
|
|
let quota = state.quota.as_ref().unwrap();
|
|
|
|
let has_surplus: Vec<&Candidate> = state.election.candidates.iter()
|
|
|
|
.filter(|c| {
|
2021-06-29 15:31:38 +10:00
|
|
|
let count_card = &state.candidates[c];
|
2021-06-20 01:28:54 +10:00
|
|
|
return count_card.state == CandidateState::Elected && &count_card.votes > quota;
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
recompute_keep_values(state, opts, &has_surplus);
|
2021-06-20 01:51:34 +10:00
|
|
|
|
|
|
|
let kv_str = state.election.candidates.iter()
|
2021-06-29 15:31:38 +10:00
|
|
|
.map(|c| (c, &state.candidates[c]))
|
2021-06-20 01:51:34 +10:00
|
|
|
.filter(|(_, cc)| cc.state == CandidateState::Elected)
|
|
|
|
.sorted_unstable_by(|a, b| a.1.order_elected.cmp(&b.1.order_elected))
|
|
|
|
.map(|(c, cc)| format!("{} ({:.dps2$})", c.name, cc.keep_value.as_ref().unwrap(), dps2=max(opts.pp_decimals, 2)))
|
|
|
|
.join(", ");
|
|
|
|
|
|
|
|
state.logger.log_literal(format!("Keep values of elected candidates are: {}.", kv_str));
|
2021-06-20 01:28:54 +10:00
|
|
|
}
|
|
|
|
|
2021-06-16 13:00:54 +10:00
|
|
|
// Used to give bulk excluded candidate the same order_elected
|
|
|
|
let order_excluded = state.num_excluded + 1;
|
|
|
|
|
|
|
|
for excluded_candidate in excluded_candidates.into_iter() {
|
|
|
|
let count_card = state.candidates.get_mut(excluded_candidate).unwrap();
|
|
|
|
|
|
|
|
if count_card.state != CandidateState::Excluded {
|
|
|
|
count_card.state = CandidateState::Excluded;
|
|
|
|
state.num_excluded += 1;
|
|
|
|
count_card.order_elected = -(order_excluded as isize);
|
2021-08-08 19:11:15 +10:00
|
|
|
count_card.finalised = true;
|
2021-06-16 13:00:54 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let orig_candidates = state.candidates.clone();
|
|
|
|
let orig_exhausted = state.exhausted.clone();
|
|
|
|
|
2021-06-18 18:48:12 +10:00
|
|
|
distribute_preferences(state, opts);
|
2021-06-16 13:00:54 +10:00
|
|
|
|
|
|
|
// Recalculate transfers
|
2021-06-16 18:24:30 +10:00
|
|
|
let mut checksum = N::new();
|
2021-06-16 13:00:54 +10:00
|
|
|
for (candidate, count_card) in state.candidates.iter_mut() {
|
2021-06-29 15:31:38 +10:00
|
|
|
count_card.transfers = &count_card.votes - &orig_candidates[candidate].votes;
|
2021-06-16 18:24:30 +10:00
|
|
|
checksum += &count_card.transfers;
|
2021-06-16 13:00:54 +10:00
|
|
|
}
|
|
|
|
state.exhausted.transfers = &state.exhausted.votes - &orig_exhausted.votes;
|
2021-06-16 18:24:30 +10:00
|
|
|
checksum += &state.exhausted.transfers;
|
|
|
|
state.loss_fraction.transfer(&-checksum);
|
2021-06-16 13:00:54 +10:00
|
|
|
}
|