Implement --surplus-assume-total

This replaces and extends the former --subtract-nontransferable option

#4
This commit is contained in:
RunasSudo 2023-06-11 21:47:37 +10:00
parent c140ef0a90
commit 4cf9053681
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
9 changed files with 32 additions and 27 deletions

View File

@ -325,6 +325,8 @@
\subsection\label{surplus-wig-sf}\paragraph If \textit{--papers} is \textit{both}, divide the elected candidate's surplus by their progress total. \subsection\label{surplus-wig-sf}\paragraph If \textit{--papers} is \textit{both}, divide the elected candidate's surplus by their progress total.
% FIXME: Update this for new --transferable-only, --surplus-assume-total flags
\subsectionc\paragraph If \textit{--papers} is \textit{transferable}, for each parcel held by the elected candidate, multiply the number of transferable ballots in the parcel by the parcel's value, and sum the products. Divide the elected candidate's surplus by the sum. \subsectionc\paragraph If \textit{--papers} is \textit{transferable}, for each parcel held by the elected candidate, multiply the number of transferable ballots in the parcel by the parcel's value, and sum the products. Divide the elected candidate's surplus by the sum.
\subsectionc\paragraph If \textit{--papers} is \textit{subtract_nontransferable}, for each parcel held by the elected candidate, multiply the number of non-transferable ballots in the parcel by the parcel's value, and sum the products. Subtract the sum from the elected candidate's progress total. Divide the elected candidate's surplus by the difference. \subsectionc\paragraph If \textit{--papers} is \textit{subtract_nontransferable}, for each parcel held by the elected candidate, multiply the number of non-transferable ballots in the parcel by the parcel's value, and sum the products. Subtract the sum from the elected candidate's progress total. Divide the elected candidate's surplus by the difference.

View File

@ -109,11 +109,12 @@ Random sample methods are also supported, but also not recommended:
A random sample method will usually be used with a *Quota criterion* set to *>=*. A random sample method will usually be used with a *Quota criterion* set to *>=*.
### Ballots to examine in surplus transfer (--transferable-only/--subtract-nontransferable) ### Ballots to examine in surplus transfer (--transferable-only/--surplus-assume-total)
* *Include non-transferable ballots* (default): When this option is selected, all ballots of the transferring candidate are examined. Non-transferable ballots are always exhausted at the relevant surplus fractions. This is the method typically used with the weighted inclusive Gregory or Meek methods. * *Include non-transferable ballots* (default): When this option is selected, all ballots of the transferring candidate are examined. The denominator of the surplus fraction is the total value of the ballots. Non-transferable ballots are always exhausted at the relevant surplus fractions. This is the method typically used with the weighted inclusive Gregory or Meek methods.
* *Use transferable ballots only* (--transferable-only): When this option is selected, only transferable ballots of the transferring candidate are examined. Non-transferable ballots are exhausted only if the value of the transferable ballots is less than the surplus. This is the method typically used with other surplus distribution methods. * *Assume progress total* (--surplus-assume-total): Same as *Include non-transferable ballots*, but the denominator of the surplus fraction is the candidate's recorded progress total. This has effect only as far as concerns rounding, and only in the weighted inclusive Gregory method.
* *Subtract non-transferables* (--transferable-only --subtract-nontransferable): Same as *Use transferable ballots only*, but the value of the transferable ballots is calculated by subtracting the value of non-transferable ballots from the progress total. This has effect only as far as concerns rounding. * *Use transferable ballots only* (--transferable-only): When this option is selected, only transferable ballots of the transferring candidate are examined. The denominator of the surplus fraction is the total value of the transferable ballots. Non-transferable ballots are exhausted only if the value of the transferable ballots is less than the surplus. This is the method typically used with other surplus distribution methods.
* *Subtract non-transferables* (--transferable-only --surplus-assume-total): Same as *Use transferable ballots only*, but the value of the transferable ballots is calculated by subtracting the value of non-transferable ballots from the progress total. This has effect only as far as concerns rounding, and only in the weighted inclusive Gregory method.
### (Gregory) Exclusion method (--exclusion) ### (Gregory) Exclusion method (--exclusion)

View File

@ -123,6 +123,7 @@
<label> <label>
<select id="selPapers"> <select id="selPapers">
<option value="both" selected>Include non-transferable ballots</option> <option value="both" selected>Include non-transferable ballots</option>
<option value="assume_progress_total">Assume progress total</option>
<option value="transferable">Use transferable ballots only</option> <option value="transferable">Use transferable ballots only</option>
<option value="subtract_nontransferable">Subtract non-transferables</option> <option value="subtract_nontransferable">Subtract non-transferables</option>
</select> </select>

View File

@ -207,7 +207,7 @@ function changePreset() {
document.getElementById('selSumTransfers').value = 'by_parcel'; document.getElementById('selSumTransfers').value = 'by_parcel';
document.getElementById('selSurplus').value = 'by_order'; document.getElementById('selSurplus').value = 'by_order';
document.getElementById('selMethod').value = 'wig'; document.getElementById('selMethod').value = 'wig';
document.getElementById('selPapers').value = 'both'; document.getElementById('selPapers').value = 'assume_progress_total';
document.getElementById('selExclusion').value = 'parcels_by_order'; document.getElementById('selExclusion').value = 'parcels_by_order';
document.getElementById('selTies').value = 'backwards,random'; document.getElementById('selTies').value = 'backwards,random';
} else if (document.getElementById('selPreset').value === 'act') { } else if (document.getElementById('selPreset').value === 'act') {

View File

@ -121,9 +121,9 @@ pub struct SubcmdOptions {
#[clap(help_heading=Some("STV VARIANTS"), long)] #[clap(help_heading=Some("STV VARIANTS"), long)]
transferable_only: bool, transferable_only: bool,
/// (Gregory STV) If --transferable-only, calculate value of transferable papers by subtracting value of non-transferable papers /// (Gregory STV) When calculating surplus fractions, assume the progress total is the total value of all the candidate's papers
#[clap(help_heading=Some("STV VARIANTS"), long)] #[clap(help_heading=Some("STV VARIANTS"), long)]
subtract_nontransferable: bool, surplus_assume_total: 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", "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)] #[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)]
@ -302,7 +302,7 @@ where
cmd_opts.surplus.into(), cmd_opts.surplus.into(),
cmd_opts.surplus_order.into(), cmd_opts.surplus_order.into(),
cmd_opts.transferable_only, cmd_opts.transferable_only,
cmd_opts.subtract_nontransferable, cmd_opts.surplus_assume_total,
cmd_opts.exclusion.into(), cmd_opts.exclusion.into(),
cmd_opts.meek_nz_exclusion, cmd_opts.meek_nz_exclusion,
cmd_opts.sample.into(), cmd_opts.sample.into(),

View File

@ -266,15 +266,20 @@ where
// Calculate and print surplus fraction // Calculate and print surplus fraction
let total_ballots = &transferable_ballots + &exhausted_ballots; let total_ballots = &transferable_ballots + &exhausted_ballots;
let total_votes = &transferable_votes + &exhausted_votes; let mut total_votes = &transferable_votes + &exhausted_votes;
let count_card = state.candidates.get_mut(elected_candidate).unwrap(); let count_card = state.candidates.get_mut(elected_candidate).unwrap();
count_card.ballot_transfers = -&total_ballots; count_card.ballot_transfers = -&total_ballots;
if opts.transferable_only && opts.subtract_nontransferable { if opts.surplus_assume_total {
// Override total_votes
total_votes = count_card.votes.clone();
if opts.transferable_only {
// Override transferable_votes // Override transferable_votes
transferable_votes = count_card.votes.clone() - exhausted_votes; transferable_votes = count_card.votes.clone() - exhausted_votes;
} }
}
let mut surplus_denom = calculate_surplus_denom(&surplus, &transferable_ballots, &transferable_votes, &total_ballots, &total_votes, opts); let mut surplus_denom = calculate_surplus_denom(&surplus, &transferable_ballots, &transferable_votes, &total_ballots, &total_votes, opts);
let surplus_numer; let surplus_numer;

View File

@ -81,9 +81,9 @@ pub struct STVOptions {
#[builder(default="false")] #[builder(default="false")]
pub transferable_only: bool, pub transferable_only: bool,
/// (Gregory STV) If --transferable-only, calculate value of transferable papers by subtracting value of non-transferable papers /// (Gregory STV) When calculating surplus fractions, assume the progress total is the total value of all the candidate's papers
#[builder(default="false")] #[builder(default="false")]
pub subtract_nontransferable: bool, pub surplus_assume_total: bool,
/// (Gregory STV) Method of exclusions /// (Gregory STV) Method of exclusions
#[builder(default="ExclusionMethod::SingleStage")] #[builder(default="ExclusionMethod::SingleStage")]
@ -169,7 +169,7 @@ impl STVOptions {
if self.surplus != SurplusMethod::Meek { if self.surplus != SurplusMethod::Meek {
if self.surplus_order != SurplusOrder::BySize { flags.push(self.surplus_order.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.transferable_only { flags.push("--transferable-only".to_string()); }
if self.subtract_nontransferable { flags.push("--subtract-nontransferable".to_string()); } if self.surplus_assume_total { flags.push("--surplus-assume-total".to_string()); }
if self.exclusion != ExclusionMethod::SingleStage { flags.push(self.exclusion.describe()); } if self.exclusion != ExclusionMethod::SingleStage { flags.push(self.exclusion.describe()); }
} }
if self.surplus == SurplusMethod::Meek && self.meek_nz_exclusion { flags.push("--meek-nz-exclusion".to_string()); } if self.surplus == SurplusMethod::Meek && self.meek_nz_exclusion { flags.push("--meek-nz-exclusion".to_string()); }
@ -238,14 +238,10 @@ impl STVOptions {
return Err(STVError::InvalidOptions("--constraint-mode repeat_count requires a Gregory method for --surplus")); return Err(STVError::InvalidOptions("--constraint-mode repeat_count requires a Gregory method for --surplus"));
} }
} }
if self.subtract_nontransferable { if self.surplus_assume_total {
if self.surplus != SurplusMethod::WIG { if self.surplus != SurplusMethod::WIG {
// Invalid because other methods do not distinguish between ballots of different value during surplus transfer // Invalid because other methods do not distinguish between ballots of different value during surplus transfer
return Err(STVError::InvalidOptions("--subtract-nontransferable requires --surplus wig")); return Err(STVError::InvalidOptions("--surplus-assume-total requires --surplus wig"));
}
if !self.transferable_only {
// Invalid because nontransferables are only subtracted with --transferable-only
return Err(STVError::InvalidOptions("--subtract-nontransferable requires --transferable-only"));
} }
} }
if !self.immediate_elect { if !self.immediate_elect {

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
@ -280,7 +280,7 @@ impl STVOptions {
surplus.into(), surplus.into(),
surplus_order.into(), surplus_order.into(),
if papers == "transferable" || papers == "subtract_nontransferable" { true } else { false }, if papers == "transferable" || papers == "subtract_nontransferable" { true } else { false },
if papers == "subtract_nontransferable" { true } else { false }, if papers == "assume_progress_total" || papers == "subtract_nontransferable" { true } else { false },
exclusion.into(), exclusion.into(),
meek_nz_exclusion, meek_nz_exclusion,
sample.into(), sample.into(),

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
@ -33,10 +33,10 @@ fn nswlg_albury21_rational() {
.ties(vec![TieStrategy::Backwards, TieStrategy::Random(String::from("20220322"))]) .ties(vec![TieStrategy::Backwards, TieStrategy::Random(String::from("20220322"))])
.surplus_order(stv::SurplusOrder::ByOrder) .surplus_order(stv::SurplusOrder::ByOrder)
.transferable_only(true) .transferable_only(true)
.subtract_nontransferable(true) .surplus_assume_total(true)
.build().unwrap(); .build().unwrap();
assert_eq!(stv_opts.describe::<Rational>(), "--round-votes 0 --round-quota 0 --round-subtransfers by_parcel --quota-criterion geq --ties backwards random --random-seed 20220322 --surplus-order by_order --transferable-only --subtract-nontransferable"); assert_eq!(stv_opts.describe::<Rational>(), "--round-votes 0 --round-quota 0 --round-subtransfers by_parcel --quota-criterion geq --ties backwards random --random-seed 20220322 --surplus-order by_order --transferable-only --surplus-assume-total");
utils::read_validate_election::<Rational>("tests/data/City_of_Albury-finalpreferencedatafile.csv", "tests/data/City_of_Albury-finalpreferencedatafile.blt", stv_opts, None, &[]); utils::read_validate_election::<Rational>("tests/data/City_of_Albury-finalpreferencedatafile.csv", "tests/data/City_of_Albury-finalpreferencedatafile.blt", stv_opts, None, &[]);
} }