Implement --exclusion first_prefs_then_by_value

This commit is contained in:
RunasSudo 2023-02-07 21:12:26 +11:00
parent 0ee5fd3285
commit eaf864062d
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
6 changed files with 95 additions and 47 deletions

View File

@ -120,6 +120,7 @@ When *Surplus method* is set to a Gregory method, this option controls how candi
* *Single stage* (default): When excluding candidate(s), transfer all their ballots in one stage. * *Single stage* (default): When excluding candidate(s), transfer all their ballots in one stage.
* *By value*: When excluding candidate(s), transfer their ballots in descending order of accumulated transfer value. Each transfer of all ballots of a certain transfer value forms a separate stage, i.e. if a transfer allows another candidate to meet the quota, no further ballots are transferred to that candidate. * *By value*: When excluding candidate(s), transfer their ballots in descending order of accumulated transfer value. Each transfer of all ballots of a certain transfer value forms a separate stage, i.e. if a transfer allows another candidate to meet the quota, no further ballots are transferred to that candidate.
* *FPV then by value*: When excluding candidate(s), transfer their first preference ballot papers in the first stage, then transfer ballot papers received on transfers as in *By value*.
* *By source*: When excluding candidate(s), transfer their ballots according to the candidate from which those ballots were received, in the order the transferring candidates were elected or excluded. Each transfer of all ballots received from a certain candidate forms a separate stage. * *By source*: When excluding candidate(s), transfer their ballots according to the candidate from which those ballots were received, in the order the transferring candidates were elected or excluded. Each transfer of all ballots received from a certain candidate forms a separate stage.
* *By parcel (by order)*: When excluding a candidate, transfer their ballot ballots one parcel at a time, in the order each was received. Each parcel forms a separate stage. This option cannot be combined with bulk exclusion. * *By parcel (by order)*: When excluding a candidate, transfer their ballot ballots one parcel at a time, in the order each was received. Each parcel forms a separate stage. This option cannot be combined with bulk exclusion.
* *Reset and re-iterate*: When excluding candidate(s), reset the count from the distribution of first preferences, disregarding the excluded candidates. * *Reset and re-iterate*: When excluding candidate(s), reset the count from the distribution of first preferences, disregarding the excluded candidates.

View File

@ -1,6 +1,6 @@
<!-- <!--
* OpenTally: Open-source election vote counting * OpenTally: Open-source election vote counting
* Copyright © 2021–2022 Lee Yingtong Li (RunasSudo) * Copyright © 2021–2023 Lee Yingtong Li (RunasSudo)
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU Affero General Public License as published by
@ -134,6 +134,7 @@
<select id="selExclusion"> <select id="selExclusion">
<option value="single_stage" selected>Single stage</option> <option value="single_stage" selected>Single stage</option>
<option value="by_value">By value</option> <option value="by_value">By value</option>
<option value="first_prefs_then_by_value">FPV then by value</option>
<option value="by_source">By source</option> <option value="by_source">By source</option>
<option value="parcels_by_order">By parcel (by order)</option> <option value="parcels_by_order">By parcel (by order)</option>
<option value="reset_and_reiterate">Reset and re-iterate</option> <option value="reset_and_reiterate">Reset and re-iterate</option>

View File

@ -1,5 +1,5 @@
/* OpenTally: Open-source election vote counting /* OpenTally: Open-source election vote counting
* Copyright © 20212022 Lee Yingtong Li (RunasSudo) * Copyright © 20212023 Lee Yingtong Li (RunasSudo)
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU Affero General Public License as published by
@ -126,7 +126,7 @@ pub struct SubcmdOptions {
subtract_nontransferable: bool, subtract_nontransferable: bool,
/// (Gregory STV) Method of exclusions [default: single_stage] [possible values: single_stage, by_value, by_source, parcels_by_order, reset_and_reiterate] /// (Gregory STV) Method of exclusions [default: single_stage] [possible values: single_stage, by_value, by_source, parcels_by_order, reset_and_reiterate]
#[clap(help_heading=Some("STV VARIANTS"), long, possible_values=&["single_stage", "by_value", "by_source", "parcels_by_order", "wright", "reset_and_reiterate"], default_value="single_stage", value_name="method", hide_possible_values=true, hide_default_value=true)] #[clap(help_heading=Some("STV VARIANTS"), long, possible_values=&["single_stage", "by_value", "first_prefs_then_by_value", "by_source", "parcels_by_order", "wright", "reset_and_reiterate"], default_value="single_stage", value_name="method", hide_possible_values=true, hide_default_value=true)]
exclusion: String, exclusion: String,
/// (Meek STV) NZ Meek STV behaviour: Iterate keep values one round before candidate exclusion /// (Meek STV) NZ Meek STV behaviour: Iterate keep values one round before candidate exclusion

View File

@ -447,7 +447,7 @@ where
} }
votes_remain = false; votes_remain = false;
} }
ExclusionMethod::ByValue => { ExclusionMethod::ByValue | ExclusionMethod::FirstPreferencesThenByValue => {
// Exclude by value // Exclude by value
let excluded_with_votes: Vec<&&Candidate> = excluded_candidates.iter() let excluded_with_votes: Vec<&&Candidate> = excluded_candidates.iter()
.filter(|c| { let cc = &state.candidates[*c]; !cc.finalised && !cc.parcels.is_empty() }) .filter(|c| { let cc = &state.candidates[*c]; !cc.finalised && !cc.parcels.is_empty() })
@ -455,6 +455,51 @@ where
if excluded_with_votes.is_empty() { if excluded_with_votes.is_empty() {
votes_remain = false; votes_remain = false;
} else {
votes_remain = false;
let mut votes = Vec::new();
if opts.exclusion == ExclusionMethod::FirstPreferencesThenByValue
&& excluded_with_votes.iter().any(|c| state.candidates[*c].parcels.iter().any(|p| p.source_order == 0))
{
// If candidates to exclude still having votes, select only those with first preferences
for excluded_candidate in excluded_with_votes.iter() {
let count_card = state.candidates.get_mut(*excluded_candidate).unwrap();
let mut cc_parcels = Vec::new();
cc_parcels.append(&mut count_card.parcels);
// Filter out just those first preferences
let mut remaining_parcels = Vec::new();
for mut parcel in cc_parcels {
if parcel.source_order == 0 {
count_card.ballot_transfers -= parcel.num_ballots();
let votes_transferred = parcel.num_votes();
votes.append(&mut parcel.votes);
// Update votes
checksum -= &votes_transferred;
count_card.transfer(&-votes_transferred);
} else {
remaining_parcels.push(parcel);
}
}
if !remaining_parcels.is_empty() {
votes_remain = true;
}
// Leave remaining votes with candidate
count_card.parcels = remaining_parcels;
}
// Group all votes of one value in single parcel
parcels.push(Parcel {
votes,
value_fraction: N::one(), // By definition, first preferences have value of 1
source_order: 0, // Set this later
});
} else { } else {
// If candidates to exclude still having votes, select only those with the greatest value // If candidates to exclude still having votes, select only those with the greatest value
let max_value = excluded_with_votes.iter() let max_value = excluded_with_votes.iter()
@ -464,10 +509,6 @@ where
.max().unwrap() .max().unwrap()
.clone(); .clone();
votes_remain = false;
let mut votes = Vec::new();
for excluded_candidate in excluded_with_votes.iter() { for excluded_candidate in excluded_with_votes.iter() {
let count_card = state.candidates.get_mut(*excluded_candidate).unwrap(); let count_card = state.candidates.get_mut(*excluded_candidate).unwrap();
let mut cc_parcels = Vec::new(); let mut cc_parcels = Vec::new();
@ -503,10 +544,11 @@ where
parcels.push(Parcel { parcels.push(Parcel {
votes, votes,
value_fraction: max_value, value_fraction: max_value,
source_order: 0, // source_order is unused in this mode source_order: 0, // Set this later
}); });
} }
} }
}
ExclusionMethod::BySource => { ExclusionMethod::BySource => {
// Exclude by source candidate // Exclude by source candidate
let excluded_with_votes: Vec<&&Candidate> = excluded_candidates.iter() let excluded_with_votes: Vec<&&Candidate> = excluded_candidates.iter()

View File

@ -1081,8 +1081,8 @@ where
} }
} }
} }
ExclusionMethod::ByValue | ExclusionMethod::BySource | ExclusionMethod::ParcelsByOrder => { ExclusionMethod::ByValue | ExclusionMethod::FirstPreferencesThenByValue | ExclusionMethod::BySource | ExclusionMethod::ParcelsByOrder => {
// Exclusion in parts compatible only with Gregory method // Segmented exclusion compatible only with Gregory method
gregory::exclude_candidates(state, opts, excluded_candidates, complete_type); gregory::exclude_candidates(state, opts, excluded_candidates, complete_type);
} }
ExclusionMethod::ResetAndReiterate => { ExclusionMethod::ResetAndReiterate => {

View File

@ -1,5 +1,5 @@
/* OpenTally: Open-source election vote counting /* OpenTally: Open-source election vote counting
* Copyright © 20212022 Lee Yingtong Li (RunasSudo) * Copyright © 20212023 Lee Yingtong Li (RunasSudo)
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU Affero General Public License as published by
@ -526,6 +526,8 @@ pub enum ExclusionMethod {
SingleStage, SingleStage,
/// Transfer the ballot papers of an excluded candidate in descending order of accumulated transfer value /// Transfer the ballot papers of an excluded candidate in descending order of accumulated transfer value
ByValue, ByValue,
/// Transfer the first preferences for an excluded candidate, then ballot papers received on transfers in descending order of accumulated transfer value
FirstPreferencesThenByValue,
/// Transfer the ballot papers of an excluded candidate according to the candidate who transferred the papers to the excluded candidate, in the order the transferring candidates were elected or excluded /// Transfer the ballot papers of an excluded candidate according to the candidate who transferred the papers to the excluded candidate, in the order the transferring candidates were elected or excluded
BySource, BySource,
/// Transfer the ballot papers of an excluded candidate parcel by parcel in the order received /// Transfer the ballot papers of an excluded candidate parcel by parcel in the order received
@ -540,6 +542,7 @@ impl ExclusionMethod {
match self { match self {
ExclusionMethod::SingleStage => "--exclusion single_stage", ExclusionMethod::SingleStage => "--exclusion single_stage",
ExclusionMethod::ByValue => "--exclusion by_value", ExclusionMethod::ByValue => "--exclusion by_value",
ExclusionMethod::FirstPreferencesThenByValue => "--exclusion first_prefs_then_by_value",
ExclusionMethod::BySource => "--exclusion by_source", ExclusionMethod::BySource => "--exclusion by_source",
ExclusionMethod::ParcelsByOrder => "--exclusion parcels_by_order", ExclusionMethod::ParcelsByOrder => "--exclusion parcels_by_order",
ExclusionMethod::ResetAndReiterate => "--exclusion reset_and_reiterate", ExclusionMethod::ResetAndReiterate => "--exclusion reset_and_reiterate",
@ -552,6 +555,7 @@ impl<S: AsRef<str>> From<S> for ExclusionMethod {
match s.as_ref() { match s.as_ref() {
"single_stage" => ExclusionMethod::SingleStage, "single_stage" => ExclusionMethod::SingleStage,
"by_value" => ExclusionMethod::ByValue, "by_value" => ExclusionMethod::ByValue,
"first_prefs_then_by_value" => ExclusionMethod::FirstPreferencesThenByValue,
"by_source" => ExclusionMethod::BySource, "by_source" => ExclusionMethod::BySource,
"parcels_by_order" => ExclusionMethod::ParcelsByOrder, "parcels_by_order" => ExclusionMethod::ParcelsByOrder,
"reset_and_reiterate" => ExclusionMethod::ResetAndReiterate, "reset_and_reiterate" => ExclusionMethod::ResetAndReiterate,