Implement --exclusion first_prefs_then_by_value
This commit is contained in:
parent
0ee5fd3285
commit
eaf864062d
|
@ -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.
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* 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
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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() })
|
||||||
|
@ -456,55 +456,97 @@ where
|
||||||
if excluded_with_votes.is_empty() {
|
if excluded_with_votes.is_empty() {
|
||||||
votes_remain = false;
|
votes_remain = false;
|
||||||
} else {
|
} else {
|
||||||
// If candidates to exclude still having votes, select only those with the greatest value
|
|
||||||
let max_value = excluded_with_votes.iter()
|
|
||||||
.map(|c| state.candidates[*c].parcels.iter()
|
|
||||||
.map(|p| &p.value_fraction)
|
|
||||||
.max().unwrap())
|
|
||||||
.max().unwrap()
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
votes_remain = false;
|
votes_remain = false;
|
||||||
|
|
||||||
let mut votes = Vec::new();
|
let mut votes = Vec::new();
|
||||||
|
|
||||||
for excluded_candidate in excluded_with_votes.iter() {
|
if opts.exclusion == ExclusionMethod::FirstPreferencesThenByValue
|
||||||
let count_card = state.candidates.get_mut(*excluded_candidate).unwrap();
|
&& excluded_with_votes.iter().any(|c| state.candidates[*c].parcels.iter().any(|p| p.source_order == 0))
|
||||||
let mut cc_parcels = Vec::new();
|
{
|
||||||
cc_parcels.append(&mut count_card.parcels);
|
// If candidates to exclude still having votes, select only those with first preferences
|
||||||
|
for excluded_candidate in excluded_with_votes.iter() {
|
||||||
// Filter out just those votes with max_value
|
let count_card = state.candidates.get_mut(*excluded_candidate).unwrap();
|
||||||
let mut remaining_parcels = Vec::new();
|
let mut cc_parcels = Vec::new();
|
||||||
|
cc_parcels.append(&mut count_card.parcels);
|
||||||
for mut parcel in cc_parcels {
|
|
||||||
if parcel.value_fraction == max_value {
|
// Filter out just those first preferences
|
||||||
count_card.ballot_transfers -= parcel.num_ballots();
|
let mut remaining_parcels = Vec::new();
|
||||||
|
|
||||||
let votes_transferred = parcel.num_votes();
|
for mut parcel in cc_parcels {
|
||||||
votes.append(&mut parcel.votes);
|
if parcel.source_order == 0 {
|
||||||
|
count_card.ballot_transfers -= parcel.num_ballots();
|
||||||
// Update votes
|
|
||||||
checksum -= &votes_transferred;
|
let votes_transferred = parcel.num_votes();
|
||||||
count_card.transfer(&-votes_transferred);
|
votes.append(&mut parcel.votes);
|
||||||
} else {
|
|
||||||
remaining_parcels.push(parcel);
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !remaining_parcels.is_empty() {
|
// Group all votes of one value in single parcel
|
||||||
votes_remain = true;
|
parcels.push(Parcel {
|
||||||
|
votes,
|
||||||
|
value_fraction: N::one(), // By definition, first preferences have value of 1
|
||||||
|
source_order: 0, // Set this later
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If candidates to exclude still having votes, select only those with the greatest value
|
||||||
|
let max_value = excluded_with_votes.iter()
|
||||||
|
.map(|c| state.candidates[*c].parcels.iter()
|
||||||
|
.map(|p| &p.value_fraction)
|
||||||
|
.max().unwrap())
|
||||||
|
.max().unwrap()
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
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 votes with max_value
|
||||||
|
let mut remaining_parcels = Vec::new();
|
||||||
|
|
||||||
|
for mut parcel in cc_parcels {
|
||||||
|
if parcel.value_fraction == max_value {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Leave remaining votes with candidate
|
// Group all votes of one value in single parcel
|
||||||
count_card.parcels = remaining_parcels;
|
parcels.push(Parcel {
|
||||||
|
votes,
|
||||||
|
value_fraction: max_value,
|
||||||
|
source_order: 0, // Set this later
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group all votes of one value in single parcel
|
|
||||||
parcels.push(Parcel {
|
|
||||||
votes,
|
|
||||||
value_fraction: max_value,
|
|
||||||
source_order: 0, // source_order is unused in this mode
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExclusionMethod::BySource => {
|
ExclusionMethod::BySource => {
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* 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
|
||||||
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue