Implement --no-early-bulk-elect

This commit is contained in:
RunasSudo 2021-06-23 00:52:25 +10:00
parent bd6b4b01c5
commit ce8b252453
No known key found for this signature in database
GPG Key ID: 7234E476BF21C61A
12 changed files with 48 additions and 11 deletions

View File

@ -137,6 +137,14 @@ When ballots are normalised, a set of preferences with weight *n* > 1 is instead
## Count optimisations ## Count optimisations
## Early bulk election (--no-early-bulk-elect)
When early bulk election is enabled (default), all remaining candidates are declared elected in a single stage as soon as the number of not-excluded candidates exactly equals the number of vacancies to fill. Further surplus distributions are not performed, and outstanding exclusions, if any, are not completed. This is typical of most STV rules.
When early bulk election is disabled, surpluses continue to be distributed, and outstanding exclusions continue to be completed, even once the number of not-excluded candidates exactly equals the number of vacancies to fill. Bulk election is performed only once there are no more surpluses to distribute, and no exclusions to complete.
In either case, candidates are declared elected in descending order of votes. This ensures that only one candidate is ever elected at a time and the order of election is well-defined, which is required e.g. for some affirmative action rules.
### Bulk exclusion (--bulk-exclude) ### Bulk exclusion (--bulk-exclude)
When bulk exclusion is disabled (default), only one candidate is ever excluded per stage. When bulk exclusion is disabled (default), only one candidate is ever excluded per stage.

View File

@ -179,7 +179,7 @@
Count optimisations: Count optimisations:
</div> </div>
<label class="col-6"> <label class="col-6">
<input type="checkbox" id="chkBulkElection" disabled> <input type="checkbox" id="chkBulkElection" checked>
Early bulk election Early bulk election
</label> </label>
<label class="col-6"> <label class="col-6">

View File

@ -115,6 +115,7 @@ async function clickCount() {
document.getElementById('selPapers').value == 'transferable', document.getElementById('selPapers').value == 'transferable',
document.getElementById('selExclusion').value, document.getElementById('selExclusion').value,
document.getElementById('chkMeekNZExclusion').checked, document.getElementById('chkMeekNZExclusion').checked,
document.getElementById('chkBulkElection').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,
@ -318,7 +319,7 @@ function changePreset() {
document.getElementById('selQuotaCriterion').value = 'gt'; document.getElementById('selQuotaCriterion').value = 'gt';
document.getElementById('selQuota').value = 'droop_exact'; document.getElementById('selQuota').value = 'droop_exact';
document.getElementById('selQuotaMode').value = 'static'; document.getElementById('selQuotaMode').value = 'static';
//document.getElementById('chkBulkElection').checked = true; document.getElementById('chkBulkElection').checked = true;
document.getElementById('chkBulkExclusion').checked = false; document.getElementById('chkBulkExclusion').checked = false;
document.getElementById('chkDeferSurpluses').checked = false; document.getElementById('chkDeferSurpluses').checked = false;
document.getElementById('selNumbers').value = 'rational'; document.getElementById('selNumbers').value = 'rational';
@ -338,7 +339,7 @@ function changePreset() {
document.getElementById('selQuotaCriterion').value = 'geq'; document.getElementById('selQuotaCriterion').value = 'geq';
document.getElementById('selQuota').value = 'droop'; document.getElementById('selQuota').value = 'droop';
document.getElementById('selQuotaMode').value = 'static'; document.getElementById('selQuotaMode').value = 'static';
//document.getElementById('chkBulkElection').checked = true; document.getElementById('chkBulkElection').checked = true;
document.getElementById('chkBulkExclusion').checked = false; document.getElementById('chkBulkExclusion').checked = false;
document.getElementById('chkDeferSurpluses').checked = false; document.getElementById('chkDeferSurpluses').checked = false;
document.getElementById('selNumbers').value = 'fixed'; document.getElementById('selNumbers').value = 'fixed';
@ -361,7 +362,7 @@ function changePreset() {
document.getElementById('selQuotaCriterion').value = 'gt'; document.getElementById('selQuotaCriterion').value = 'gt';
document.getElementById('selQuota').value = 'droop_exact'; document.getElementById('selQuota').value = 'droop_exact';
document.getElementById('selQuotaMode').value = 'static'; document.getElementById('selQuotaMode').value = 'static';
//document.getElementById('chkBulkElection').checked = true; document.getElementById('chkBulkElection').checked = true;
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;
@ -385,7 +386,7 @@ function changePreset() {
document.getElementById('selQuotaCriterion').value = 'geq'; document.getElementById('selQuotaCriterion').value = 'geq';
document.getElementById('selQuota').value = 'droop'; document.getElementById('selQuota').value = 'droop';
document.getElementById('selQuotaMode').value = 'static'; document.getElementById('selQuotaMode').value = 'static';
//document.getElementById('chkBulkElection').checked = true; document.getElementById('chkBulkElection').checked = true;
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;
@ -413,7 +414,7 @@ function changePreset() {
document.getElementById('selQuotaCriterion').value = 'geq'; document.getElementById('selQuotaCriterion').value = 'geq';
document.getElementById('selQuota').value = 'droop'; document.getElementById('selQuota').value = 'droop';
document.getElementById('selQuotaMode').value = 'static'; document.getElementById('selQuotaMode').value = 'static';
//document.getElementById('chkBulkElection').checked = true; document.getElementById('chkBulkElection').checked = true;
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;
@ -441,7 +442,7 @@ function changePreset() {
document.getElementById('selQuotaCriterion').value = 'geq'; document.getElementById('selQuotaCriterion').value = 'geq';
document.getElementById('selQuota').value = 'droop'; document.getElementById('selQuota').value = 'droop';
document.getElementById('selQuotaMode').value = 'static'; document.getElementById('selQuotaMode').value = 'static';
//document.getElementById('chkBulkElection').checked = true; document.getElementById('chkBulkElection').checked = true;
document.getElementById('chkBulkExclusion').checked = true; document.getElementById('chkBulkExclusion').checked = true;
document.getElementById('chkDeferSurpluses').checked = false; document.getElementById('chkDeferSurpluses').checked = false;
document.getElementById('selNumbers').value = 'fixed'; document.getElementById('selNumbers').value = 'fixed';
@ -464,7 +465,7 @@ function changePreset() {
document.getElementById('selQuotaCriterion').value = 'geq'; document.getElementById('selQuotaCriterion').value = 'geq';
document.getElementById('selQuota').value = 'droop'; document.getElementById('selQuota').value = 'droop';
document.getElementById('selQuotaMode').value = 'static'; document.getElementById('selQuotaMode').value = 'static';
//document.getElementById('chkBulkElection').checked = true; document.getElementById('chkBulkElection').checked = true;
document.getElementById('chkBulkExclusion').checked = true; document.getElementById('chkBulkExclusion').checked = true;
document.getElementById('chkDeferSurpluses').checked = false; document.getElementById('chkDeferSurpluses').checked = false;
document.getElementById('selNumbers').value = 'fixed'; document.getElementById('selNumbers').value = 'fixed';
@ -486,7 +487,7 @@ function changePreset() {
document.getElementById('selQuotaCriterion').value = 'geq'; document.getElementById('selQuotaCriterion').value = 'geq';
document.getElementById('selQuota').value = 'droop'; document.getElementById('selQuota').value = 'droop';
document.getElementById('selQuotaMode').value = 'static'; document.getElementById('selQuotaMode').value = 'static';
//document.getElementById('chkBulkElection').checked = true; document.getElementById('chkBulkElection').checked = true;
document.getElementById('chkBulkExclusion').checked = false; document.getElementById('chkBulkExclusion').checked = false;
document.getElementById('chkDeferSurpluses').checked = true; document.getElementById('chkDeferSurpluses').checked = true;
document.getElementById('selNumbers').value = 'fixed'; document.getElementById('selNumbers').value = 'fixed';
@ -511,7 +512,7 @@ function changePreset() {
document.getElementById('selQuotaCriterion').value = 'geq'; document.getElementById('selQuotaCriterion').value = 'geq';
document.getElementById('selQuota').value = 'droop_exact'; document.getElementById('selQuota').value = 'droop_exact';
document.getElementById('selQuotaMode').value = 'ers97'; document.getElementById('selQuotaMode').value = 'ers97';
//document.getElementById('chkBulkElection').checked = true; document.getElementById('chkBulkElection').checked = true;
document.getElementById('chkBulkExclusion').checked = true; document.getElementById('chkBulkExclusion').checked = true;
document.getElementById('chkDeferSurpluses').checked = true; document.getElementById('chkDeferSurpluses').checked = true;
document.getElementById('selNumbers').value = 'fixed'; document.getElementById('selNumbers').value = 'fixed';

View File

@ -140,6 +140,10 @@ struct STV {
// ------------------------- // -------------------------
// -- Count optimisations -- // -- Count optimisations --
/// Continue count even if continuing candidates fill all remaining vacancies
#[clap(help_heading=Some("COUNT OPTIMISATIONS"), long)]
no_early_bulk_elect: bool,
/// Use bulk exclusion /// Use bulk exclusion
#[clap(help_heading=Some("COUNT OPTIMISATIONS"), long)] #[clap(help_heading=Some("COUNT OPTIMISATIONS"), long)]
bulk_exclude: bool, bulk_exclude: bool,
@ -221,6 +225,7 @@ where
cmd_opts.transferable_only, cmd_opts.transferable_only,
&cmd_opts.exclusion, &cmd_opts.exclusion,
cmd_opts.meek_nz_exclusion, cmd_opts.meek_nz_exclusion,
!cmd_opts.no_early_bulk_elect,
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,

View File

@ -71,6 +71,8 @@ pub struct STVOptions {
pub exclusion: ExclusionMethod, pub exclusion: ExclusionMethod,
/// (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
pub meek_nz_exclusion: bool, pub meek_nz_exclusion: bool,
/// Bulk elect as soon as continuing candidates fill all remaining vacancies
pub early_bulk_elect: 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
@ -101,6 +103,7 @@ impl STVOptions {
transferable_only: bool, transferable_only: bool,
exclusion: &str, exclusion: &str,
meek_nz_exclusion: bool, meek_nz_exclusion: bool,
early_bulk_elect: bool,
bulk_exclude: bool, bulk_exclude: bool,
defer_surpluses: bool, defer_surpluses: bool,
meek_immediate_elect: bool, meek_immediate_elect: bool,
@ -164,6 +167,7 @@ impl STVOptions {
_ => panic!("Invalid --exclusion"), _ => panic!("Invalid --exclusion"),
}, },
meek_nz_exclusion, meek_nz_exclusion,
early_bulk_elect,
bulk_exclude, bulk_exclude,
defer_surpluses, defer_surpluses,
meek_immediate_elect, meek_immediate_elect,
@ -193,6 +197,7 @@ impl STVOptions {
if self.surplus != SurplusMethod::Meek && 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.surplus != SurplusMethod::Meek && 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.surplus == SurplusMethod::Meek && self.meek_nz_exclusion { flags.push("--meek-nz-exclusion".to_string()); }
if !self.early_bulk_elect { flags.push("--no-early-bulk-elect".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()); }
@ -422,6 +427,13 @@ where
return Ok(true); return Ok(true);
} }
// Attempt early bulk election
if opts.early_bulk_elect {
if bulk_elect(&mut state, &opts)? {
return Ok(false);
}
}
// Continue exclusions // Continue exclusions
if continue_exclusion(&mut state, &opts) { if continue_exclusion(&mut state, &opts) {
calculate_quota(&mut state, opts); calculate_quota(&mut state, opts);
@ -438,7 +450,7 @@ where
return Ok(false); return Ok(false);
} }
// Attempt bulk election // Attempt late bulk election
if bulk_elect(&mut state, &opts)? { if bulk_elect(&mut state, &opts)? {
return Ok(false); return Ok(false);
} }

View File

@ -200,6 +200,7 @@ impl STVOptions {
transferable_only: bool, transferable_only: bool,
exclusion: &str, exclusion: &str,
meek_nz_exclusion: bool, meek_nz_exclusion: bool,
early_bulk_elect: bool,
bulk_exclude: bool, bulk_exclude: bool,
defer_surpluses: bool, defer_surpluses: bool,
meek_immediate_elect: bool, meek_immediate_elect: bool,
@ -223,6 +224,7 @@ impl STVOptions {
transferable_only, transferable_only,
exclusion, exclusion,
meek_nz_exclusion, meek_nz_exclusion,
early_bulk_elect,
bulk_exclude, bulk_exclude,
defer_surpluses, defer_surpluses,
meek_immediate_elect, meek_immediate_elect,

View File

@ -71,6 +71,7 @@ fn aec_tas19_rational() {
transferable_only: false, transferable_only: false,
exclusion: stv::ExclusionMethod::ByValue, exclusion: stv::ExclusionMethod::ByValue,
meek_nz_exclusion: false, meek_nz_exclusion: false,
early_bulk_elect: true,
bulk_exclude: true, bulk_exclude: true,
defer_surpluses: false, defer_surpluses: false,
meek_immediate_elect: false, meek_immediate_elect: false,

View File

@ -39,6 +39,7 @@ fn csm15_float64() {
transferable_only: false, transferable_only: false,
exclusion: stv::ExclusionMethod::Wright, exclusion: stv::ExclusionMethod::Wright,
meek_nz_exclusion: false, meek_nz_exclusion: false,
early_bulk_elect: true,
bulk_exclude: true, bulk_exclude: true,
defer_surpluses: false, defer_surpluses: false,
meek_immediate_elect: false, meek_immediate_elect: false,

View File

@ -39,6 +39,7 @@ fn ers97_rational() {
transferable_only: true, transferable_only: true,
exclusion: stv::ExclusionMethod::ByValue, exclusion: stv::ExclusionMethod::ByValue,
meek_nz_exclusion: false, meek_nz_exclusion: false,
early_bulk_elect: true,
bulk_exclude: true, bulk_exclude: true,
defer_surpluses: true, defer_surpluses: true,
meek_immediate_elect: false, meek_immediate_elect: false,

View File

@ -44,6 +44,7 @@ fn meek87_ers97_float64() {
transferable_only: false, transferable_only: false,
exclusion: stv::ExclusionMethod::SingleStage, exclusion: stv::ExclusionMethod::SingleStage,
meek_nz_exclusion: false, meek_nz_exclusion: false,
early_bulk_elect: true,
bulk_exclude: false, bulk_exclude: false,
defer_surpluses: false, defer_surpluses: false,
meek_immediate_elect: false, meek_immediate_elect: false,
@ -72,6 +73,7 @@ fn meek06_ers97_fixed12() {
transferable_only: false, transferable_only: false,
exclusion: stv::ExclusionMethod::SingleStage, exclusion: stv::ExclusionMethod::SingleStage,
meek_nz_exclusion: false, meek_nz_exclusion: false,
early_bulk_elect: true,
bulk_exclude: false, bulk_exclude: false,
defer_surpluses: true, defer_surpluses: true,
meek_immediate_elect: true, meek_immediate_elect: true,
@ -145,6 +147,7 @@ fn meeknz_ers97_fixed12() {
transferable_only: false, transferable_only: false,
exclusion: stv::ExclusionMethod::SingleStage, exclusion: stv::ExclusionMethod::SingleStage,
meek_nz_exclusion: true, meek_nz_exclusion: true,
early_bulk_elect: true,
bulk_exclude: false, bulk_exclude: false,
defer_surpluses: true, defer_surpluses: true,
meek_immediate_elect: true, meek_immediate_elect: true,

View File

@ -39,6 +39,7 @@ fn prsa1_rational() {
transferable_only: true, transferable_only: true,
exclusion: stv::ExclusionMethod::ParcelsByOrder, exclusion: stv::ExclusionMethod::ParcelsByOrder,
meek_nz_exclusion: false, meek_nz_exclusion: false,
early_bulk_elect: false,
bulk_exclude: false, bulk_exclude: false,
defer_surpluses: false, defer_surpluses: false,
meek_immediate_elect: false, meek_immediate_elect: false,

View File

@ -46,6 +46,7 @@ fn scotland_linn07_fixed5() {
transferable_only: false, transferable_only: false,
exclusion: stv::ExclusionMethod::SingleStage, exclusion: stv::ExclusionMethod::SingleStage,
meek_nz_exclusion: false, meek_nz_exclusion: false,
early_bulk_elect: true,
bulk_exclude: false, bulk_exclude: false,
defer_surpluses: false, defer_surpluses: false,
meek_immediate_elect: false, meek_immediate_elect: false,
@ -74,6 +75,7 @@ fn scotland_linn07_gfixed5() {
transferable_only: false, transferable_only: false,
exclusion: stv::ExclusionMethod::SingleStage, exclusion: stv::ExclusionMethod::SingleStage,
meek_nz_exclusion: false, meek_nz_exclusion: false,
early_bulk_elect: true,
bulk_exclude: false, bulk_exclude: false,
defer_surpluses: false, defer_surpluses: false,
meek_immediate_elect: false, meek_immediate_elect: false,