Implement --meek-nz-exclusion for NZ Meek STV

This commit is contained in:
RunasSudo 2021-06-20 01:28:54 +10:00
parent a2e4ab557c
commit 1b39b8b138
No known key found for this signature in database
GPG Key ID: 7234E476BF21C61A
7 changed files with 79 additions and 12 deletions

View File

@ -7,7 +7,7 @@ The preset dropdown allows you to choose from a hardcoded list of preloaded STV
* *Recommended WIGM*: A recommended set of simple STV rules designed for computer counting, using the weighted inclusive Gregory method and rational arithmetic. * *Recommended WIGM*: A recommended set of simple STV rules designed for computer counting, using the weighted inclusive Gregory method and rational arithmetic.
* *Scottish STV*: Rules from the [*Scottish Local Government Elections Order 2011*](https://www.legislation.gov.uk/ssi/2011/399/schedule/1/made), using the weighted inclusive Gregory method. Validated against the [2007 Scottish local government election result for Linn ward](https://web.archive.org/web/20121004213938/http://www.glasgow.gov.uk/en/YourCouncil/Elections_Voting/Election_Results/ElectionScotland2007/LGWardResults.htm?ward=1&wardname=1%20-%20Linn). * *Scottish STV*: Rules from the [*Scottish Local Government Elections Order 2011*](https://www.legislation.gov.uk/ssi/2011/399/schedule/1/made), using the weighted inclusive Gregory method. Validated against the [2007 Scottish local government election result for Linn ward](https://web.archive.org/web/20121004213938/http://www.glasgow.gov.uk/en/YourCouncil/Elections_Voting/Election_Results/ElectionScotland2007/LGWardResults.htm?ward=1&wardname=1%20-%20Linn).
* [*Meek STV*](http://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf): Advanced STV rules designed for computer counting, recognised by the Proportional Representation Society of Australia (VictoriaTasmania) as the superior STV system. * [*Meek STV*](http://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf): Advanced STV rules designed for computer counting, recognised by the Proportional Representation Society of Australia (VictoriaTasmania) as the superior STV system.
* *Meek STV (1986)* operates according to the original [HillWichmannWoodall specification](https://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf) of Meek STV, with the modifications, relevant only in exceptional cases, that (a) fixed-point arithmetic with 5 decimal places is used, and (b) candidates are elected on strictly exceeding the quota. Validated against the HillWichmannWoodall implementation for the [ERS97 model election](https://www.electoral-reform.org.uk/latest-news-and-research/publications/how-to-conduct-an-election-by-the-single-transferable-vote-3rd-edition/#sub-section-24). * *Meek STV (1987)* operates according to the original [HillWichmannWoodall specification](https://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf) of Meek STV, with the modifications, relevant only in exceptional cases, that (a) fixed-point arithmetic with 5 decimal places is used, and (b) candidates are elected on strictly exceeding the quota. Validated against the HillWichmannWoodall implementation for the [ERS97 model election](https://www.electoral-reform.org.uk/latest-news-and-research/publications/how-to-conduct-an-election-by-the-single-transferable-vote-3rd-edition/#sub-section-24).
* *Meek STV (2006)* operates according to [Hill's 2006 revisions](http://www.votingmatters.org.uk/ISSUE22/I22P2.pdf). This is the algorithm referred to in OpenSTV/OpaVote as Meek STV, and forms the basis of New Zealand's Meek STV rules. Validated against OpenSTV for the ERS97 model election. * *Meek STV (2006)* operates according to [Hill's 2006 revisions](http://www.votingmatters.org.uk/ISSUE22/I22P2.pdf). This is the algorithm referred to in OpenSTV/OpaVote as Meek STV, and forms the basis of New Zealand's Meek STV rules. Validated against OpenSTV for the ERS97 model election.
* *Australian Senate STV*: Rules from the [*Commonwealth Electoral Act 1918*](https://www.legislation.gov.au/Details/C2020C00400/Html/Text#_Toc59107700), using the unweighted inclusive Gregory method. Validated against the [2019 Australian Senate election result for Tasmania](https://results.aec.gov.au/24310/Website/SenateDownloadsMenu-24310-Csv.htm). * *Australian Senate STV*: Rules from the [*Commonwealth Electoral Act 1918*](https://www.legislation.gov.au/Details/C2020C00400/Html/Text#_Toc59107700), using the unweighted inclusive Gregory method. Validated against the [2019 Australian Senate election result for Tasmania](https://results.aec.gov.au/24310/Website/SenateDownloadsMenu-24310-Csv.htm).
* [*PRSA 1977*](https://www.prsa.org.au/rule1977.htm): Simple rules designed for hand counting, using the exclusive Gregory method, with counting automatically performed in thousandths of a vote. Validated against [example 1](https://www.prsa.org.au/example1.pdf) of the PRSA's [*Proportional Representation Manual*](https://www.prsa.org.au/publicat.htm#p2). * [*PRSA 1977*](https://www.prsa.org.au/rule1977.htm): Simple rules designed for hand counting, using the exclusive Gregory method, with counting automatically performed in thousandths of a vote. Validated against [example 1](https://www.prsa.org.au/example1.pdf) of the PRSA's [*Proportional Representation Manual*](https://www.prsa.org.au/publicat.htm#p2).
@ -83,6 +83,13 @@ Other surplus transfer methods, such as non-fractional transfers (e.g. random sa
When *Surplus method* is set to *Meek method*, this setting is ignored, and the Meek method is instead applied. When *Surplus method* is set to *Meek method*, this setting is ignored, and the Meek method is instead applied.
### (Meek) NZ-style exclusion (--meek-nz-exclusion)
When *Surplus method* is set to *Meek method*, this option controls how candidate keep values are updated when candidates are excluded:
* When NZ-style exclusion is disabled (default), the excluded candidate's keep value is immediately reduced to 0. This is the method specified in the 1987 and 2006 Meek rules.
* When NZ-style exclusion is enabled, all elected candidates' keep values are first updated by one further iteration; only then is the excluded candidate's keep value reduced to 0. This is the method specified in the New Zealand *Local Electoral Regulations 2001*.
### Ties (-t/--ties) ### Ties (-t/--ties)
This dropdown allows you to select how ties (in surplus transfer or exclusion) are broken. The options are: This dropdown allows you to select how ties (in surplus transfer or exclusion) are broken. The options are:
@ -141,9 +148,9 @@ When deferred surpluses is enabled, the transfer of all surpluses is deferred if
### (Meek) Immediate election (--meek-immediate-elect) ### (Meek) Immediate election (--meek-immediate-elect)
This option controls when candidates are elected when *Surplus method* is set to *Meek method*: When *Surplus method* is set to *Meek method*, this option controls when candidates are elected:
* When immediate election is disabled (default), all current surpluses are distributed and keep values finalised, before any candidates exceeding the quota are then declared elected. This is the method specified in the 1986 Meek rules. * When immediate election is disabled (default), all current surpluses are distributed and keep values finalised, before any candidates exceeding the quota are then declared elected. This is the method specified in the 1987 Meek rules.
* When immediate election is enabled, a candidate meeting the quota interrupts a surplus distribution. The candidate is immediately declared elected, before the distribution of all surpluses of all now-elected candidates continues. This is the method specified in the 2006 Meek rules. * When immediate election is enabled, a candidate meeting the quota interrupts a surplus distribution. The candidate is immediately declared elected, before the distribution of all surpluses of all now-elected candidates continues. This is the method specified in the 2006 Meek rules.
## Rounding ## Rounding
@ -181,5 +188,5 @@ This option affects the result only insofar as rounding (due to use of fixed-pre
When *Surplus method* is set to *Meek method*, this option allows you to specify when the distribution of surpluses will be considered complete. The tolerance may be specified either as a percentage (ends with a `%`) or absolute number of votes (no `%`): When *Surplus method* is set to *Meek method*, this option allows you to specify when the distribution of surpluses will be considered complete. The tolerance may be specified either as a percentage (ends with a `%`) or absolute number of votes (no `%`):
* Percentage: Surplus distributions will be considered complete when every elected candidate's surplus exceeds the quota by no more than the specified percentage. This is the method specified in the 1986 Meek rules. * Percentage: Surplus distributions will be considered complete when every elected candidate's surplus exceeds the quota by no more than the specified percentage. This is the method specified in the 1987 Meek rules.
* Absolute number of votes: Surplus distributions will be considered complete when the total surpluses of all elected candidates is no greater than the specified number of votes. This is the simpler method specified in the 2006 Meek rules. * Absolute number of votes: Surplus distributions will be considered complete when the total surpluses of all elected candidates is no greater than the specified number of votes. This is the simpler method specified in the 2006 Meek rules.

View File

@ -39,6 +39,7 @@
<option value="scottish">Scottish STV</option> <option value="scottish">Scottish STV</option>
<option value="meek87">Meek STV (1987)</option> <option value="meek87">Meek STV (1987)</option>
<option value="meek06">Meek STV (2006)</option> <option value="meek06">Meek STV (2006)</option>
<option value="meeknz">Meek STV (New Zealand)</option>
<option value="senate">Australian Senate STV</option> <option value="senate">Australian Senate STV</option>
<!--<option value="wright">Wright STV</option>--> <!--<option value="wright">Wright STV</option>-->
<option value="prsa77">PRSA 1977</option> <option value="prsa77">PRSA 1977</option>
@ -114,6 +115,10 @@
<!--<option value="wright">Wright method (re-iterate)</option>--> <!--<option value="wright">Wright method (re-iterate)</option>-->
</select> </select>
</label> </label>
<label style="margin-left:1em;">
<input type="checkbox" id="chkMeekNZExclusion">
(Meek) NZ-style exclusion
</label>
</div> </div>
<div class="subheading"> <div class="subheading">
Tie-breaking: Tie-breaking:

View File

@ -114,6 +114,7 @@ async function clickCount() {
document.getElementById('selSurplus').value, document.getElementById('selSurplus').value,
document.getElementById('selPapers').value == 'transferable', document.getElementById('selPapers').value == 'transferable',
document.getElementById('selExclusion').value, document.getElementById('selExclusion').value,
document.getElementById('chkMeekNZExclusion').checked,
document.getElementById('chkBulkExclusion').checked, document.getElementById('chkBulkExclusion').checked,
document.getElementById('chkDeferSurpluses').checked, document.getElementById('chkDeferSurpluses').checked,
document.getElementById('chkMeekImmediateElect').checked, document.getElementById('chkMeekImmediateElect').checked,
@ -364,6 +365,7 @@ function changePreset() {
document.getElementById('chkBulkExclusion').checked = false; document.getElementById('chkBulkExclusion').checked = false;
document.getElementById('chkDeferSurpluses').checked = false; document.getElementById('chkDeferSurpluses').checked = false;
document.getElementById('chkMeekImmediateElect').checked = false; document.getElementById('chkMeekImmediateElect').checked = false;
document.getElementById('chkMeekNZExclusion').checked = false;
document.getElementById('selNumbers').value = 'fixed'; document.getElementById('selNumbers').value = 'fixed';
document.getElementById('txtDP').value = '5'; document.getElementById('txtDP').value = '5';
document.getElementById('txtPPDP').value = '2'; document.getElementById('txtPPDP').value = '2';
@ -387,6 +389,35 @@ function changePreset() {
document.getElementById('chkBulkExclusion').checked = false; document.getElementById('chkBulkExclusion').checked = false;
document.getElementById('chkDeferSurpluses').checked = true; document.getElementById('chkDeferSurpluses').checked = true;
document.getElementById('chkMeekImmediateElect').checked = true; document.getElementById('chkMeekImmediateElect').checked = true;
document.getElementById('chkMeekNZExclusion').checked = false;
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;
document.getElementById('txtRoundVotes').value = '9';
document.getElementById('chkRoundTVs').checked = true;
document.getElementById('txtRoundTVs').value = '9';
document.getElementById('chkRoundWeights').checked = true;
document.getElementById('txtRoundWeights').value = '9';
//document.getElementById('selSumTransfers').value = 'single_step';
document.getElementById('txtMeekSurplusTolerance').value = '0.0001';
//document.getElementById('selSurplus').value = 'by_size';
document.getElementById('selTransfers').value = 'meek';
//document.getElementById('selPapers').value = 'both';
//document.getElementById('selExclusion').value = 'single_stage';
document.getElementById('selTies').value = 'backwards,random';
} else if (document.getElementById('selPreset').value === 'meeknz') {
document.getElementById('selQuotaCriterion').value = 'geq';
document.getElementById('selQuota').value = 'droop';
document.getElementById('selQuotaMode').value = 'static';
document.getElementById('chkBulkElection').checked = true;
document.getElementById('chkBulkExclusion').checked = false;
document.getElementById('chkDeferSurpluses').checked = true;
document.getElementById('chkMeekImmediateElect').checked = true;
document.getElementById('chkMeekNZExclusion').checked = true;
document.getElementById('selNumbers').value = 'fixed'; document.getElementById('selNumbers').value = 'fixed';
document.getElementById('txtDP').value = '12'; document.getElementById('txtDP').value = '12';
document.getElementById('txtPPDP').value = '2'; document.getElementById('txtPPDP').value = '2';

View File

@ -133,6 +133,10 @@ struct STV {
#[clap(help_heading=Some("STV VARIANTS"), long, possible_values=&["single_stage", "by_value", "parcels_by_order"], default_value="single_stage", value_name="method")] #[clap(help_heading=Some("STV VARIANTS"), long, possible_values=&["single_stage", "by_value", "parcels_by_order"], default_value="single_stage", value_name="method")]
exclusion: String, exclusion: String,
/// (Meek STV) NZ Meek STV behaviour: Iterate keep values one round before candidate exclusion
#[clap(help_heading=Some("STV VARIANTS"), long)]
meek_nz_exclusion: bool,
// ------------------------- // -------------------------
// -- Count optimisations -- // -- Count optimisations --
@ -219,6 +223,7 @@ where
cmd_opts.bulk_exclude, cmd_opts.bulk_exclude,
cmd_opts.defer_surpluses, cmd_opts.defer_surpluses,
cmd_opts.meek_immediate_elect, cmd_opts.meek_immediate_elect,
cmd_opts.meek_nz_exclusion,
cmd_opts.pp_decimals, cmd_opts.pp_decimals,
); );

View File

@ -240,7 +240,7 @@ where
for<'r> &'r N: ops::Div<&'r N, Output=N>, for<'r> &'r N: ops::Div<&'r N, Output=N>,
{ {
let quota = state.quota.as_ref().unwrap(); let quota = state.quota.as_ref().unwrap();
let mut has_surplus: Vec<&Candidate> = state.election.candidates.iter() // Present in order in case of tie let mut has_surplus: Vec<&Candidate> = state.election.candidates.iter()
.filter(|c| { .filter(|c| {
let count_card = state.candidates.get(c).unwrap(); let count_card = state.candidates.get(c).unwrap();
return count_card.state == CandidateState::Elected && &count_card.votes > quota; return count_card.state == CandidateState::Elected && &count_card.votes > quota;
@ -358,6 +358,18 @@ where
for<'r> &'r N: ops::Sub<&'r N, Output=N>, 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::Mul<&'r N, Output=N>,
{ {
// 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| {
let count_card = state.candidates.get(c).unwrap();
return count_card.state == CandidateState::Elected && &count_card.votes > quota;
})
.collect();
recompute_keep_values(state, opts, &has_surplus);
}
// Used to give bulk excluded candidate the same order_elected // Used to give bulk excluded candidate the same order_elected
let order_excluded = state.num_excluded + 1; let order_excluded = state.num_excluded + 1;

View File

@ -69,6 +69,8 @@ pub struct STVOptions {
pub transferable_only: bool, pub transferable_only: bool,
/// Method of exclusions /// Method of exclusions
pub exclusion: ExclusionMethod, pub exclusion: ExclusionMethod,
/// (Meek STV) NZ Meek STV behaviour: Iterate keep values one round before candidate exclusion
pub meek_nz_exclusion: bool,
/// Use bulk exclusion /// Use bulk exclusion
pub bulk_exclude: bool, pub bulk_exclude: bool,
/// Defer surplus distributions if possible /// Defer surplus distributions if possible
@ -98,6 +100,7 @@ impl STVOptions {
surplus_order: &str, surplus_order: &str,
transferable_only: bool, transferable_only: bool,
exclusion: &str, exclusion: &str,
meek_nz_exclusion: bool,
bulk_exclude: bool, bulk_exclude: bool,
defer_surpluses: bool, defer_surpluses: bool,
meek_immediate_elect: bool, meek_immediate_elect: bool,
@ -159,6 +162,7 @@ impl STVOptions {
"parcels_by_order" => ExclusionMethod::ParcelsByOrder, "parcels_by_order" => ExclusionMethod::ParcelsByOrder,
_ => panic!("Invalid --exclusion"), _ => panic!("Invalid --exclusion"),
}, },
meek_nz_exclusion,
bulk_exclude, bulk_exclude,
defer_surpluses, defer_surpluses,
meek_immediate_elect, meek_immediate_elect,
@ -174,19 +178,20 @@ impl STVOptions {
if let Some(dps) = self.round_weights { flags.push(format!("--round-weights {}", dps)); } if let Some(dps) = self.round_weights { flags.push(format!("--round-weights {}", dps)); }
if let Some(dps) = self.round_votes { flags.push(format!("--round-votes {}", dps)); } if let Some(dps) = self.round_votes { flags.push(format!("--round-votes {}", dps)); }
if let Some(dps) = self.round_quota { flags.push(format!("--round-quota {}", dps)); } if let Some(dps) = self.round_quota { flags.push(format!("--round-quota {}", dps)); }
if self.sum_surplus_transfers != SumSurplusTransfersMode::SingleStep { flags.push(self.sum_surplus_transfers.describe()); } if self.surplus != SurplusMethod::Meek && self.sum_surplus_transfers != SumSurplusTransfersMode::SingleStep { flags.push(self.sum_surplus_transfers.describe()); }
if self.surplus == SurplusMethod::Meek && self.meek_surplus_tolerance != "0.001%" { flags.push(format!("--meek-surplus-tolerance {}", self.meek_surplus_tolerance)); } 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.normalise_ballots { flags.push("--normalise-ballots".to_string()); }
if self.quota != QuotaType::DroopExact { flags.push(self.quota.describe()); }
if self.quota_criterion != QuotaCriterion::Greater { flags.push(self.quota_criterion.describe()); }
if self.surplus != SurplusMethod::Meek && self.quota_mode != QuotaMode::Static { flags.push(self.quota_mode.describe()); }
let ties_str = self.ties.iter().map(|t| t.describe()).join(" "); let ties_str = self.ties.iter().map(|t| t.describe()).join(" ");
if ties_str != "prompt" { flags.push(format!("--ties {}", ties_str)); } if ties_str != "prompt" { flags.push(format!("--ties {}", ties_str)); }
for t in self.ties.iter() { if let TieStrategy::Random(seed) = t { flags.push(format!("--random-seed {}", seed)); } } for t in self.ties.iter() { if let TieStrategy::Random(seed) = t { flags.push(format!("--random-seed {}", seed)); } }
if self.quota != QuotaType::DroopExact { 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()); }
if self.surplus != SurplusMethod::WIG { flags.push(self.surplus.describe()); } if self.surplus != SurplusMethod::WIG { flags.push(self.surplus.describe()); }
if self.surplus_order != SurplusOrder::BySize { flags.push(self.surplus_order.describe()); } if self.surplus != SurplusMethod::Meek && self.surplus_order != SurplusOrder::BySize { flags.push(self.surplus_order.describe()); }
if self.transferable_only { flags.push("--transferable-only".to_string()); } if self.surplus != SurplusMethod::Meek && self.transferable_only { flags.push("--transferable-only".to_string()); }
if self.exclusion != ExclusionMethod::SingleStage { flags.push(self.exclusion.describe()); } if self.surplus != SurplusMethod::Meek && 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.bulk_exclude { flags.push("--bulk-exclude".to_string()); } if self.bulk_exclude { flags.push("--bulk-exclude".to_string()); }
if self.defer_surpluses { flags.push("--defer-surpluses".to_string()); } if self.defer_surpluses { flags.push("--defer-surpluses".to_string()); }
if self.surplus == SurplusMethod::Meek && self.meek_immediate_elect { flags.push("--meek-immediate-elect".to_string()); } if self.surplus == SurplusMethod::Meek && self.meek_immediate_elect { flags.push("--meek-immediate-elect".to_string()); }

View File

@ -199,6 +199,7 @@ impl STVOptions {
surplus_order: &str, surplus_order: &str,
transferable_only: bool, transferable_only: bool,
exclusion: &str, exclusion: &str,
meek_nz_exclusion: bool,
bulk_exclude: bool, bulk_exclude: bool,
defer_surpluses: bool, defer_surpluses: bool,
meek_immediate_elect: bool, meek_immediate_elect: bool,
@ -221,6 +222,7 @@ impl STVOptions {
surplus_order, surplus_order,
transferable_only, transferable_only,
exclusion, exclusion,
meek_nz_exclusion,
bulk_exclude, bulk_exclude,
defer_surpluses, defer_surpluses,
meek_immediate_elect, meek_immediate_elect,