Autodetect when to normalise ballots, remove explicit --normalise-ballots

This commit is contained in:
RunasSudo 2022-04-20 19:54:58 +10:00
parent 03af86733e
commit f0e3b02051
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
9 changed files with 6 additions and 54 deletions

View File

@ -102,7 +102,7 @@ Random sample methods are also supported, but also not recommended:
* *Hare (exclusive sample)*: During surplus transfers, a subset of the ballot papers received in the last transfer, equal in size to the surplus, is examined.
* *Inclusive Hare (sample)*: During surplus transfers, a subset of the elected candidate's ballot papers, equal in size to the surplus, is examined.
The use of a random sample method requires *Normalise ballots* to be enabled, and will usually be used with a *Quota criterion* set to *>=*.
A random sample method will usually be used with a *Quota criterion* set to *>=*.
### Papers to examine in surplus transfer (--transferable-only/--subtract-nontransferable)
@ -219,14 +219,6 @@ This dropdown allows you to select how numbers (vote totals, etc.) are represent
* *Rational* (default): Numbers are represented exactly as fractions, resulting in the elimination of rounding error, but increasing computational complexity when the number of surplus transfers is very large.
* *Float (64-bit)*: Numbers are represented as native 64-bit floating-point numbers. This is fast, but not recommended as unexpectedly large rounding errors may be introduced in some circumstances.
### Normalise ballots (--normalise-ballots)
In the BLT file format, each set of preferences can have a specified weight – this is typically used to indicate multiple voters who had the same preferences.
When ballots are not normalised (default), a set of preferences with weight *n* > 1 is represented as a single ballot with value *n*. This is known as [list-packed ballots](http://www.votingmatters.org.uk/ISSUE21/I21P1.pdf).
When ballots are normalised, a set of preferences with weight *n* > 1 is instead converted to *n* ballots each with value 1.
## Count optimisations
### Early bulk election (--no-early-bulk-elect)

View File

@ -233,10 +233,6 @@
<input type="number" id="txtDP" value="5" min="0" style="width: 3em;">
</label>
</div>
<label class="col-12">
<input type="checkbox" id="chkNormaliseBallots">
Normalise ballots
</label>
<div class="col-12 subheading">
Count optimisations:
</div>

View File

@ -146,7 +146,6 @@ async function clickCount() {
document.getElementById('chkRoundQuota').checked ? parseInt(document.getElementById('txtRoundQuota').value) : null,
document.getElementById('selSumTransfers').value,
document.getElementById('txtMeekSurplusTolerance').value,
document.getElementById('chkNormaliseBallots').checked,
document.getElementById('selQuota').value,
document.getElementById('selQuotaCriterion').value,
document.getElementById('selQuotaMode').value,
@ -189,7 +188,6 @@ async function clickCount() {
'optsStr': optsStr,
'numbers': document.getElementById('selNumbers').value,
'decimals': document.getElementById('txtDP').value,
'normaliseBallots': document.getElementById('chkNormaliseBallots').checked,
'reportStyle': document.getElementById('selReport').value,
});
}

View File

@ -27,7 +27,6 @@ function changePreset() {
document.getElementById('txtMinThreshold').value = '0';
document.getElementById('selNumbers').value = 'rational';
document.getElementById('txtPPDP').value = '2';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = false;
document.getElementById('chkRoundVotes').checked = false;
document.getElementById('chkRoundSFs').checked = false;
@ -50,7 +49,6 @@ function changePreset() {
document.getElementById('selNumbers').value = 'fixed';
document.getElementById('txtDP').value = '5';
document.getElementById('txtPPDP').value = '5';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '0';
document.getElementById('chkRoundVotes').checked = false;
@ -76,7 +74,6 @@ function changePreset() {
document.getElementById('selNumbers').value = 'fixed';
document.getElementById('txtDP').value = '5';
document.getElementById('txtPPDP').value = '2';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = false;
document.getElementById('chkRoundVotes').checked = false;
document.getElementById('chkRoundSFs').checked = false;
@ -101,7 +98,6 @@ function changePreset() {
document.getElementById('selNumbers').value = 'fixed';
document.getElementById('txtDP').value = '12';
document.getElementById('txtPPDP').value = '2';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '9';
document.getElementById('chkRoundVotes').checked = true;
@ -130,7 +126,6 @@ function changePreset() {
document.getElementById('selNumbers').value = 'fixed';
document.getElementById('txtDP').value = '12';
document.getElementById('txtPPDP').value = '2';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '9';
document.getElementById('chkRoundVotes').checked = true;
@ -157,7 +152,6 @@ function changePreset() {
document.getElementById('txtMinThreshold').value = '0';
document.getElementById('selNumbers').value = 'rational';
document.getElementById('txtPPDP').value = '0';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '0';
document.getElementById('chkRoundVotes').checked = true;
@ -181,7 +175,6 @@ function changePreset() {
document.getElementById('txtMinThreshold').value = '0';
document.getElementById('selNumbers').value = 'rational';
document.getElementById('txtPPDP').value = '0';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '0';
document.getElementById('chkRoundVotes').checked = true;
@ -205,7 +198,6 @@ function changePreset() {
document.getElementById('txtMinThreshold').value = '0';
document.getElementById('selNumbers').value = 'rational';
document.getElementById('txtPPDP').value = '2';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '0';
document.getElementById('chkRoundVotes').checked = true;
@ -229,7 +221,6 @@ function changePreset() {
document.getElementById('txtMinThreshold').value = '0';
document.getElementById('selNumbers').value = 'rational';
document.getElementById('txtPPDP').value = '0';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '0';
document.getElementById('chkRoundVotes').checked = true;
@ -254,7 +245,6 @@ function changePreset() {
document.getElementById('selNumbers').value = 'fixed';
document.getElementById('txtDP').value = '4';
document.getElementById('txtPPDP').value = '4';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '0';
document.getElementById('chkRoundVotes').checked = false;
@ -280,7 +270,6 @@ function changePreset() {
document.getElementById('txtMinThreshold').value = '49';
document.getElementById('selNumbers').value = 'rational';
document.getElementById('txtPPDP').value = '0';
document.getElementById('chkNormaliseBallots').checked = true;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '0';
document.getElementById('selSumTransfers').value = 'single_step';
@ -301,7 +290,6 @@ function changePreset() {
document.getElementById('txtMinThreshold').value = '0';
document.getElementById('selNumbers').value = 'rational';
document.getElementById('txtPPDP').value = '0';
document.getElementById('chkNormaliseBallots').checked = true;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '0';
document.getElementById('selSumTransfers').value = 'single_step';
@ -322,7 +310,6 @@ function changePreset() {
document.getElementById('selNumbers').value = 'fixed';
document.getElementById('txtDP').value = '5';
document.getElementById('txtPPDP').value = '2';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '0';
document.getElementById('chkRoundVotes').checked = false;
@ -346,7 +333,6 @@ function changePreset() {
document.getElementById('selNumbers').value = 'fixed';
document.getElementById('txtDP').value = '6';
document.getElementById('txtPPDP').value = '3';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '3';
document.getElementById('chkRoundVotes').checked = true;
@ -373,7 +359,6 @@ function changePreset() {
document.getElementById('selNumbers').value = 'fixed';
document.getElementById('txtDP').value = '5';
document.getElementById('txtPPDP').value = '2';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '2';
document.getElementById('chkRoundVotes').checked = true;
@ -400,7 +385,6 @@ function changePreset() {
document.getElementById('selNumbers').value = 'fixed';
document.getElementById('txtDP').value = '5';
document.getElementById('txtPPDP').value = '2';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '2';
document.getElementById('chkRoundVotes').checked = true;
@ -427,7 +411,6 @@ function changePreset() {
document.getElementById('selNumbers').value = 'fixed';
document.getElementById('txtDP').value = '5';
document.getElementById('txtPPDP').value = '2';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '2';
document.getElementById('chkRoundVotes').checked = true;
@ -454,7 +437,6 @@ function changePreset() {
document.getElementById('selNumbers').value = 'fixed';
document.getElementById('txtDP').value = '5';
document.getElementById('txtPPDP').value = '2';
document.getElementById('chkNormaliseBallots').checked = false;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '2';
document.getElementById('chkRoundVotes').checked = true;

View File

@ -56,10 +56,6 @@ pub struct SubcmdOptions {
#[clap(help_heading=Some("NUMBERS"), long, default_value="5", value_name="dps")]
decimals: usize,
/// Convert ballots with value >1 to multiple ballots of value 1
#[clap(help_heading=Some("NUMBERS"), long)]
normalise_ballots: bool,
// -----------------------
// -- Rounding settings --
@ -294,7 +290,6 @@ where
cmd_opts.round_quota,
cmd_opts.round_subtransfers.into(),
cmd_opts.meek_surplus_tolerance,
cmd_opts.normalise_ballots,
cmd_opts.quota.into(),
cmd_opts.quota_criterion.into(),
cmd_opts.quota_mode.into(),

View File

@ -71,10 +71,6 @@ pub struct STVOptions {
#[builder(default=r#"String::from("0.001%")"#)]
pub meek_surplus_tolerance: String,
/// Convert ballots with value >1 to multiple ballots of value 1 (used only for [STVOptions::describe])
#[builder(default="false")]
pub normalise_ballots: bool,
/// Quota type
#[builder(default="QuotaType::Droop")]
pub quota: QuotaType,
@ -181,7 +177,6 @@ impl STVOptions {
if let Some(dps) = self.round_quota { flags.push(format!("--round-quota {}", dps)); }
if self.surplus != SurplusMethod::Meek && self.round_subtransfers != RoundSubtransfersMode::SingleStep { flags.push(self.round_subtransfers.describe()); }
if self.surplus == SurplusMethod::Meek && self.meek_surplus_tolerance != "0.001%" { flags.push(format!("--meek-surplus-tolerance {}", self.meek_surplus_tolerance)); }
if self.normalise_ballots { flags.push("--normalise-ballots".to_string()); }
if self.quota != QuotaType::Droop { flags.push(self.quota.describe()); }
if self.quota_criterion != QuotaCriterion::Greater { flags.push(self.quota_criterion.describe()); }
if self.quota_mode != QuotaMode::Static { flags.push(self.quota_mode.describe()); }
@ -224,7 +219,6 @@ impl STVOptions {
}
if self.surplus == SurplusMethod::IHare || self.surplus == SurplusMethod::Hare {
if self.round_quota != Some(0) { return Err(STVError::InvalidOptions("--surplus ihare and --surplus hare require --round-quota 0")); }
if !self.normalise_ballots { return Err(STVError::InvalidOptions("--surplus ihare and --surplus hare require --normalise-ballots")); }
if self.sample == SampleMethod::StratifyLR && self.sample_per_ballot { return Err(STVError::InvalidOptions("--sample stratify is incompatible with --sample-per-ballot")); }
//if self.sample == SampleMethod::StratifyFloor && self.sample_per_ballot { return Err(STVError::InvalidOptions("--sample stratify_floor is incompatible with --sample-per-ballot")); }
if self.sample_per_ballot && !self.immediate_elect { return Err(STVError::InvalidOptions("--sample-per-ballot is incompatible with --no-immediate-elect")); }
@ -623,8 +617,8 @@ impl fmt::Display for STVError {
/// Preprocess the given election
pub fn preprocess_election<N: Number>(election: &mut Election<N>, opts: &STVOptions) {
// Normalise ballots if requested
if opts.normalise_ballots {
// Normalise ballots if required
if opts.surplus == SurplusMethod::IHare || opts.surplus == SurplusMethod::Hare {
election.normalise_ballots();
}

View File

@ -246,7 +246,6 @@ impl STVOptions {
round_quota: Option<usize>,
round_subtransfers: &str,
meek_surplus_tolerance: String,
normalise_ballots: bool,
quota: &str,
quota_criterion: &str,
quota_mode: &str,
@ -275,7 +274,6 @@ impl STVOptions {
round_quota,
round_subtransfers.into(),
meek_surplus_tolerance,
normalise_ballots,
quota.into(),
quota_criterion.into(),
quota_mode.into(),

View File

@ -24,7 +24,6 @@ use opentally::stv;
fn cambridge_cc03_rational() {
let stv_opts = stv::STVOptionsBuilder::default()
.round_quota(Some(0))
.normalise_ballots(true)
.quota_criterion(stv::QuotaCriterion::GreaterOrEqual)
.surplus(stv::SurplusMethod::Hare)
.transferable_only(true)
@ -34,7 +33,7 @@ fn cambridge_cc03_rational() {
.min_threshold("49".to_string())
.build().unwrap();
assert_eq!(stv_opts.describe::<Rational>(), "--round-quota 0 --normalise-ballots --quota-criterion geq --surplus hare --transferable-only --sample cincinnati --sample-per-ballot --no-early-bulk-elect --min-threshold 49");
assert_eq!(stv_opts.describe::<Rational>(), "--round-quota 0 --quota-criterion geq --surplus hare --transferable-only --sample cincinnati --sample-per-ballot --no-early-bulk-elect --min-threshold 49");
utils::read_validate_election::<Rational>("tests/data/CambCC2003.csv", "tests/data/CambCC2003.blt", stv_opts, None, &["exhausted"]);
}

View File

@ -25,7 +25,6 @@ use opentally::ties::TieStrategy;
fn dublin_north_2002_rational() {
let stv_opts = stv::STVOptionsBuilder::default()
.round_quota(Some(0))
.normalise_ballots(true)
.quota_criterion(stv::QuotaCriterion::GreaterOrEqual)
.surplus(stv::SurplusMethod::Hare)
.surplus_order(stv::SurplusOrder::ByOrder)
@ -33,7 +32,7 @@ fn dublin_north_2002_rational() {
.defer_surpluses(true)
.build().unwrap();
assert_eq!(stv_opts.describe::<Rational>(), "--round-quota 0 --normalise-ballots --quota-criterion geq --surplus hare --surplus-order by_order --transferable-only --defer-surpluses");
assert_eq!(stv_opts.describe::<Rational>(), "--round-quota 0 --quota-criterion geq --surplus hare --surplus-order by_order --transferable-only --defer-surpluses");
utils::read_validate_election::<Rational>("tests/data/DublinNorthSorted.csv", "tests/data/DublinNorthSorted.blt", stv_opts, None, &["exhausted"]);
}
@ -42,7 +41,6 @@ fn dublin_north_2002_rational() {
fn grey_fitzgerald_rational() {
let stv_opts = stv::STVOptionsBuilder::default()
.round_quota(Some(0))
.normalise_ballots(true)
.quota_criterion(stv::QuotaCriterion::GreaterOrEqual)
.ties(vec![TieStrategy::Forwards])
.surplus(stv::SurplusMethod::Hare)
@ -52,7 +50,7 @@ fn grey_fitzgerald_rational() {
.defer_surpluses(true)
.build().unwrap();
assert_eq!(stv_opts.describe::<Rational>(), "--round-quota 0 --normalise-ballots --quota-criterion geq --ties forwards --surplus hare --transferable-only --bulk-exclude --defer-surpluses");
assert_eq!(stv_opts.describe::<Rational>(), "--round-quota 0 --quota-criterion geq --ties forwards --surplus hare --transferable-only --bulk-exclude --defer-surpluses");
utils::read_validate_election::<Rational>("tests/data/grey_fitzgerald.csv", "tests/data/grey_fitzgerald.blt", stv_opts, None, &["exhausted"]);
}