Implement --sum-surplus-transfers
This commit is contained in:
parent
9d4cac2e89
commit
96a3eaec84
|
@ -151,6 +151,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.20"
|
version = "1.0.20"
|
||||||
|
@ -233,6 +239,15 @@ dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.4.7"
|
version = "0.4.7"
|
||||||
|
@ -338,6 +353,7 @@ dependencies = [
|
||||||
"flate2",
|
"flate2",
|
||||||
"git-version",
|
"git-version",
|
||||||
"ibig",
|
"ibig",
|
||||||
|
"itertools",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"num-rational",
|
"num-rational",
|
||||||
|
|
|
@ -11,6 +11,7 @@ crate-type = ["lib", "cdylib"]
|
||||||
derive_more = "0.99.14"
|
derive_more = "0.99.14"
|
||||||
git-version = "0.3.4"
|
git-version = "0.3.4"
|
||||||
ibig = "0.3.2"
|
ibig = "0.3.2"
|
||||||
|
itertools = "0.10.1"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
wasm-bindgen = "0.2.74"
|
wasm-bindgen = "0.2.74"
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,8 @@
|
||||||
<label>
|
<label>
|
||||||
Preset:
|
Preset:
|
||||||
<select id="selPreset" onchange="changePreset()">
|
<select id="selPreset" onchange="changePreset()">
|
||||||
<option value="scottish" selected>Scottish STV</option>
|
<option value="wigm" selected>Recommended WIGM</option>
|
||||||
|
<option value="scottish">Scottish STV</option>
|
||||||
<option value="senate">Australian Senate STV</option>
|
<option value="senate">Australian Senate STV</option>
|
||||||
<!--<option value="meek">Meek STV</option>
|
<!--<option value="meek">Meek STV</option>
|
||||||
<option value="wright">Wright STV</option>-->
|
<option value="wright">Wright STV</option>-->
|
||||||
|
@ -58,14 +59,14 @@
|
||||||
<label>
|
<label>
|
||||||
Quota:
|
Quota:
|
||||||
<select id="selQuotaCriterion">
|
<select id="selQuotaCriterion">
|
||||||
<option value="geq" selected>>=</option>
|
<option value="geq">≥</option>
|
||||||
<option value="gt">></option>
|
<option value="gt" selected>></option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<select id="selQuota">
|
<select id="selQuota">
|
||||||
<option value="droop" selected>Droop</option>
|
<option value="droop">Droop</option>
|
||||||
<option value="droop_exact">Droop (exact)</option>
|
<option value="droop_exact" selected>Droop (exact)</option>
|
||||||
<option value="hare">Hare</option>
|
<option value="hare">Hare</option>
|
||||||
<option value="hare_exact">Hare (exact)</option>
|
<option value="hare_exact">Hare (exact)</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -146,8 +147,8 @@
|
||||||
<label>
|
<label>
|
||||||
Numbers:
|
Numbers:
|
||||||
<select id="selNumbers">
|
<select id="selNumbers">
|
||||||
<option value="rational">Rational</option>
|
<option value="rational" selected>Rational</option>
|
||||||
<option value="fixed" selected>Fixed</option>
|
<option value="fixed">Fixed</option>
|
||||||
<!--<option value="gfixed">Fixed (guarded)</option>-->
|
<!--<option value="gfixed">Fixed (guarded)</option>-->
|
||||||
<option value="float64">Float (64-bit)</option>
|
<option value="float64">Float (64-bit)</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -184,7 +185,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="chkRoundQuota" checked>
|
<input type="checkbox" id="chkRoundQuota">
|
||||||
Quota:
|
Quota:
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
|
@ -205,7 +206,7 @@
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="chkRoundTVs">
|
<input type="checkbox" id="chkRoundTVs">
|
||||||
Transfer values:
|
Surplus fractions:
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input type="number" id="txtRoundTVs" value="0" min="0" style="width: 3em;">
|
<input type="number" id="txtRoundTVs" value="0" min="0" style="width: 3em;">
|
||||||
|
@ -222,6 +223,14 @@
|
||||||
d.p.
|
d.p.
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<label class="col-12">
|
||||||
|
Sum surplus transfers:
|
||||||
|
<select id="selSumTransfers">
|
||||||
|
<option value="single_step" selected>Single step</option>
|
||||||
|
<option value="by_value">By value</option>
|
||||||
|
<option value="per_ballot">Per ballot</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,7 @@ async function clickCount() {
|
||||||
document.getElementById('chkRoundWeights').checked ? parseInt(document.getElementById('txtRoundWeights').value) : null,
|
document.getElementById('chkRoundWeights').checked ? parseInt(document.getElementById('txtRoundWeights').value) : null,
|
||||||
document.getElementById('chkRoundVotes').checked ? parseInt(document.getElementById('txtRoundVotes').value) : null,
|
document.getElementById('chkRoundVotes').checked ? parseInt(document.getElementById('txtRoundVotes').value) : null,
|
||||||
document.getElementById('chkRoundQuota').checked ? parseInt(document.getElementById('txtRoundQuota').value) : null,
|
document.getElementById('chkRoundQuota').checked ? parseInt(document.getElementById('txtRoundQuota').value) : null,
|
||||||
|
document.getElementById('selSumTransfers').value,
|
||||||
document.getElementById('selQuota').value,
|
document.getElementById('selQuota').value,
|
||||||
document.getElementById('selQuotaCriterion').value,
|
document.getElementById('selQuotaCriterion').value,
|
||||||
document.getElementById('selQuotaMode').value,
|
document.getElementById('selQuotaMode').value,
|
||||||
|
@ -291,7 +292,26 @@ async function printResult() {
|
||||||
// Presets
|
// Presets
|
||||||
|
|
||||||
function changePreset() {
|
function changePreset() {
|
||||||
if (document.getElementById('selPreset').value === 'scottish') {
|
if (document.getElementById('selPreset').value === 'wigm') {
|
||||||
|
document.getElementById('selQuotaCriterion').value = 'gt';
|
||||||
|
document.getElementById('selQuota').value = 'droop_exact';
|
||||||
|
document.getElementById('selQuotaMode').value = 'static';
|
||||||
|
document.getElementById('chkBulkElection').checked = true;
|
||||||
|
document.getElementById('chkBulkExclusion').checked = false;
|
||||||
|
document.getElementById('chkDeferSurpluses').checked = false;
|
||||||
|
document.getElementById('selNumbers').value = 'rational';
|
||||||
|
document.getElementById('txtPPDP').value = '2';
|
||||||
|
document.getElementById('chkRoundQuota').checked = false;
|
||||||
|
document.getElementById('chkRoundVotes').checked = false;
|
||||||
|
document.getElementById('chkRoundTVs').checked = false;
|
||||||
|
document.getElementById('chkRoundWeights').checked = false;
|
||||||
|
document.getElementById('selSumTransfers').value = 'single_step';
|
||||||
|
document.getElementById('selSurplus').value = 'by_size';
|
||||||
|
document.getElementById('selTransfers').value = 'wig';
|
||||||
|
document.getElementById('selPapers').value = 'both';
|
||||||
|
document.getElementById('selExclusion').value = 'single_stage';
|
||||||
|
//document.getElementById('selTies').value = 'backwards_random';
|
||||||
|
} else if (document.getElementById('selPreset').value === 'scottish') {
|
||||||
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';
|
||||||
|
@ -300,12 +320,14 @@ function changePreset() {
|
||||||
document.getElementById('chkDeferSurpluses').checked = false;
|
document.getElementById('chkDeferSurpluses').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 = '5';
|
||||||
document.getElementById('chkRoundQuota').checked = true;
|
document.getElementById('chkRoundQuota').checked = true;
|
||||||
document.getElementById('txtRoundQuota').value = '0';
|
document.getElementById('txtRoundQuota').value = '0';
|
||||||
document.getElementById('chkRoundVotes').checked = false;
|
document.getElementById('chkRoundVotes').checked = false;
|
||||||
document.getElementById('chkRoundTVs').checked = false;
|
document.getElementById('chkRoundTVs').checked = true;
|
||||||
|
document.getElementById('txtRoundTVs').value = '5';
|
||||||
document.getElementById('chkRoundWeights').checked = false;
|
document.getElementById('chkRoundWeights').checked = false;
|
||||||
|
document.getElementById('selSumTransfers').value = 'per_ballot';
|
||||||
document.getElementById('selSurplus').value = 'by_size';
|
document.getElementById('selSurplus').value = 'by_size';
|
||||||
document.getElementById('selTransfers').value = 'wig';
|
document.getElementById('selTransfers').value = 'wig';
|
||||||
document.getElementById('selPapers').value = 'both';
|
document.getElementById('selPapers').value = 'both';
|
||||||
|
@ -327,6 +349,7 @@ function changePreset() {
|
||||||
document.getElementById('txtRoundVotes').value = '0';
|
document.getElementById('txtRoundVotes').value = '0';
|
||||||
document.getElementById('chkRoundTVs').checked = false;
|
document.getElementById('chkRoundTVs').checked = false;
|
||||||
document.getElementById('chkRoundWeights').checked = false;
|
document.getElementById('chkRoundWeights').checked = false;
|
||||||
|
document.getElementById('selSumTransfers').value = 'single_step';
|
||||||
document.getElementById('selSurplus').value = 'by_order';
|
document.getElementById('selSurplus').value = 'by_order';
|
||||||
document.getElementById('selTransfers').value = 'uig';
|
document.getElementById('selTransfers').value = 'uig';
|
||||||
document.getElementById('selPapers').value = 'both';
|
document.getElementById('selPapers').value = 'both';
|
||||||
|
@ -350,6 +373,7 @@ function changePreset() {
|
||||||
document.getElementById('txtRoundTVs').value = '3';
|
document.getElementById('txtRoundTVs').value = '3';
|
||||||
document.getElementById('chkRoundWeights').checked = true;
|
document.getElementById('chkRoundWeights').checked = true;
|
||||||
document.getElementById('txtRoundWeights').value = '3';
|
document.getElementById('txtRoundWeights').value = '3';
|
||||||
|
document.getElementById('selSumTransfers').value = 'single_step';
|
||||||
document.getElementById('selSurplus').value = 'by_order';
|
document.getElementById('selSurplus').value = 'by_order';
|
||||||
document.getElementById('selTransfers').value = 'eg';
|
document.getElementById('selTransfers').value = 'eg';
|
||||||
document.getElementById('selPapers').value = 'transferable';
|
document.getElementById('selPapers').value = 'transferable';
|
||||||
|
@ -373,6 +397,7 @@ function changePreset() {
|
||||||
document.getElementById('txtRoundTVs').value = '2';
|
document.getElementById('txtRoundTVs').value = '2';
|
||||||
document.getElementById('chkRoundWeights').checked = true;
|
document.getElementById('chkRoundWeights').checked = true;
|
||||||
document.getElementById('txtRoundWeights').value = '2';
|
document.getElementById('txtRoundWeights').value = '2';
|
||||||
|
document.getElementById('selSumTransfers').value = 'single_step';
|
||||||
document.getElementById('selSurplus').value = 'by_size';
|
document.getElementById('selSurplus').value = 'by_size';
|
||||||
document.getElementById('selTransfers').value = 'eg';
|
document.getElementById('selTransfers').value = 'eg';
|
||||||
document.getElementById('selPapers').value = 'transferable';
|
document.getElementById('selPapers').value = 'transferable';
|
||||||
|
|
|
@ -78,6 +78,9 @@ struct STV {
|
||||||
#[clap(help_heading=Some("ROUNDING"), long, value_name="dps")]
|
#[clap(help_heading=Some("ROUNDING"), long, value_name="dps")]
|
||||||
round_quota: Option<usize>,
|
round_quota: Option<usize>,
|
||||||
|
|
||||||
|
#[clap(help_heading=Some("ROUNDING"), long, possible_values=&["single_step", "by_value", "per_ballot"], default_value="single_step", value_name="mode")]
|
||||||
|
sum_surplus_transfers: String,
|
||||||
|
|
||||||
// -----------
|
// -----------
|
||||||
// -- Quota --
|
// -- Quota --
|
||||||
|
|
||||||
|
@ -173,6 +176,7 @@ where
|
||||||
cmd_opts.round_weights,
|
cmd_opts.round_weights,
|
||||||
cmd_opts.round_votes,
|
cmd_opts.round_votes,
|
||||||
cmd_opts.round_quota,
|
cmd_opts.round_quota,
|
||||||
|
&cmd_opts.sum_surplus_transfers,
|
||||||
&cmd_opts.quota,
|
&cmd_opts.quota,
|
||||||
&cmd_opts.quota_criterion,
|
&cmd_opts.quota_criterion,
|
||||||
&cmd_opts.quota_mode,
|
&cmd_opts.quota_mode,
|
||||||
|
|
133
src/stv/mod.rs
133
src/stv/mod.rs
|
@ -23,6 +23,7 @@ pub mod wasm;
|
||||||
use crate::numbers::Number;
|
use crate::numbers::Number;
|
||||||
use crate::election::{Candidate, CandidateState, CountCard, CountState, Parcel, Vote};
|
use crate::election::{Candidate, CandidateState, CountCard, CountState, Parcel, Vote};
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
|
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
|
@ -35,6 +36,7 @@ pub struct STVOptions {
|
||||||
pub round_weights: Option<usize>,
|
pub round_weights: Option<usize>,
|
||||||
pub round_votes: Option<usize>,
|
pub round_votes: Option<usize>,
|
||||||
pub round_quota: Option<usize>,
|
pub round_quota: Option<usize>,
|
||||||
|
pub sum_surplus_transfers: SumSurplusTransfersMode,
|
||||||
pub quota: QuotaType,
|
pub quota: QuotaType,
|
||||||
pub quota_criterion: QuotaCriterion,
|
pub quota_criterion: QuotaCriterion,
|
||||||
pub quota_mode: QuotaMode,
|
pub quota_mode: QuotaMode,
|
||||||
|
@ -54,6 +56,7 @@ impl STVOptions {
|
||||||
round_weights: Option<usize>,
|
round_weights: Option<usize>,
|
||||||
round_votes: Option<usize>,
|
round_votes: Option<usize>,
|
||||||
round_quota: Option<usize>,
|
round_quota: Option<usize>,
|
||||||
|
sum_transfers: &str,
|
||||||
quota: &str,
|
quota: &str,
|
||||||
quota_criterion: &str,
|
quota_criterion: &str,
|
||||||
quota_mode: &str,
|
quota_mode: &str,
|
||||||
|
@ -70,6 +73,12 @@ impl STVOptions {
|
||||||
round_weights,
|
round_weights,
|
||||||
round_votes,
|
round_votes,
|
||||||
round_quota,
|
round_quota,
|
||||||
|
sum_surplus_transfers: match sum_transfers {
|
||||||
|
"single_step" => SumSurplusTransfersMode::SingleStep,
|
||||||
|
"by_value" => SumSurplusTransfersMode::ByValue,
|
||||||
|
"per_ballot" => SumSurplusTransfersMode::PerBallot,
|
||||||
|
_ => panic!("Invalid --sum-transfers"),
|
||||||
|
},
|
||||||
quota: match quota {
|
quota: match quota {
|
||||||
"droop" => QuotaType::Droop,
|
"droop" => QuotaType::Droop,
|
||||||
"hare" => QuotaType::Hare,
|
"hare" => QuotaType::Hare,
|
||||||
|
@ -134,6 +143,15 @@ impl STVOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub enum SumSurplusTransfersMode {
|
||||||
|
SingleStep,
|
||||||
|
ByValue,
|
||||||
|
PerBallot,
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
|
@ -581,6 +599,7 @@ where
|
||||||
fn distribute_surpluses<N: Number>(state: &mut CountState<N>, opts: &STVOptions) -> bool
|
fn distribute_surpluses<N: Number>(state: &mut CountState<N>, opts: &STVOptions) -> bool
|
||||||
where
|
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::Div<&'r N, Output=N>,
|
||||||
for<'r> &'r N: ops::Neg<Output=N>
|
for<'r> &'r N: ops::Neg<Output=N>
|
||||||
{
|
{
|
||||||
let quota = state.quota.as_ref().unwrap();
|
let quota = state.quota.as_ref().unwrap();
|
||||||
|
@ -620,7 +639,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the denominator of the transfer value
|
/// Return the denominator of the transfer value
|
||||||
fn calculate_transfer_denom<N: Number>(surplus: &N, result: &NextPreferencesResult<N>, transferable_votes: &N, weighted: bool, transferable_only: bool) -> Option<N>
|
fn calculate_surplus_denom<N: Number>(surplus: &N, result: &NextPreferencesResult<N>, transferable_votes: &N, weighted: bool, transferable_only: bool) -> Option<N>
|
||||||
where
|
where
|
||||||
for<'r> &'r N: ops::Sub<&'r N, Output=N>
|
for<'r> &'r N: ops::Sub<&'r N, Output=N>
|
||||||
{
|
{
|
||||||
|
@ -648,21 +667,21 @@ fn reweight_vote<N: Number>(
|
||||||
num_ballots: &N,
|
num_ballots: &N,
|
||||||
surplus: &N,
|
surplus: &N,
|
||||||
weighted: bool,
|
weighted: bool,
|
||||||
transfer_value: &Option<N>,
|
surplus_fraction: &Option<N>,
|
||||||
transfer_denom: &Option<N>,
|
surplus_denom: &Option<N>,
|
||||||
round_tvs: Option<usize>,
|
round_tvs: Option<usize>,
|
||||||
rounding: Option<usize>) -> N
|
rounding: Option<usize>) -> N
|
||||||
{
|
{
|
||||||
let mut result;
|
let mut result;
|
||||||
|
|
||||||
match transfer_denom {
|
match surplus_denom {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
if let Some(_) = round_tvs {
|
if let Some(_) = round_tvs {
|
||||||
// Rounding requested: use the rounded transfer value
|
// Rounding requested: use the rounded transfer value
|
||||||
if weighted {
|
if weighted {
|
||||||
result = num_votes.clone() * transfer_value.as_ref().unwrap();
|
result = num_votes.clone() * surplus_fraction.as_ref().unwrap();
|
||||||
} else {
|
} else {
|
||||||
result = num_ballots.clone() * transfer_value.as_ref().unwrap();
|
result = num_ballots.clone() * surplus_fraction.as_ref().unwrap();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Avoid unnecessary rounding error by first multiplying by the surplus
|
// Avoid unnecessary rounding error by first multiplying by the surplus
|
||||||
|
@ -686,11 +705,57 @@ fn reweight_vote<N: Number>(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sum_surplus_transfers<N: Number>(entry: &NextPreferencesEntry<N>, surplus: &N, is_weighted: bool, surplus_fraction: &Option<N>, surplus_denom: &Option<N>, _state: &mut CountState<N>, opts: &STVOptions) -> N
|
||||||
|
where
|
||||||
|
for<'r> &'r N: ops::Div<&'r N, Output=N>,
|
||||||
|
{
|
||||||
|
match opts.sum_surplus_transfers {
|
||||||
|
SumSurplusTransfersMode::SingleStep => {
|
||||||
|
// Calculate transfer across all votes
|
||||||
|
//state.logger.log_literal(format!("Transferring {:.0} ballot papers, totalling {:.dps$} votes.", entry.num_ballots, entry.num_votes, dps=opts.pp_decimals));
|
||||||
|
return reweight_vote(&entry.num_votes, &entry.num_ballots, surplus, is_weighted, surplus_fraction, surplus_denom, opts.round_tvs, opts.round_votes);
|
||||||
|
}
|
||||||
|
SumSurplusTransfersMode::ByValue => {
|
||||||
|
// Sum transfers by value
|
||||||
|
let mut result = N::new();
|
||||||
|
|
||||||
|
// Sort into parcels by value
|
||||||
|
let mut votes: Vec<&Vote<N>> = entry.votes.iter().collect();
|
||||||
|
votes.sort_unstable_by(|a, b| (&a.value / &a.ballot.orig_value).cmp(&(&b.value / &b.ballot.orig_value)));
|
||||||
|
for (_value, parcel) in &votes.into_iter().group_by(|v| &v.value / &v.ballot.orig_value) {
|
||||||
|
let mut num_votes = N::new();
|
||||||
|
let mut num_ballots = N::new();
|
||||||
|
for vote in parcel {
|
||||||
|
num_votes += &vote.value;
|
||||||
|
num_ballots += &vote.ballot.orig_value;
|
||||||
|
}
|
||||||
|
//state.logger.log_literal(format!("Transferring {:.0} ballot papers, totalling {:.dps$} votes, received at value {:.dps2$}.", num_ballots, num_votes, value, dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
|
||||||
|
result += reweight_vote(&num_votes, &num_ballots, surplus, is_weighted, surplus_fraction, surplus_denom, opts.round_tvs, opts.round_votes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
SumSurplusTransfersMode::PerBallot => {
|
||||||
|
// Sum transfer per each individual ballot
|
||||||
|
// TODO: This could be moved to distribute_surplus to avoid looping over the votes and calculating transfer values twice
|
||||||
|
let mut result = N::new();
|
||||||
|
for vote in entry.votes.iter() {
|
||||||
|
result += reweight_vote(&vote.value, &vote.ballot.orig_value, surplus, is_weighted, surplus_fraction, surplus_denom, opts.round_tvs, opts.round_votes);
|
||||||
|
}
|
||||||
|
//state.logger.log_literal(format!("Transferring {:.0} ballot papers, totalling {:.dps$} votes.", entry.num_ballots, entry.num_votes, dps=opts.pp_decimals));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn distribute_surplus<N: Number>(state: &mut CountState<N>, opts: &STVOptions, elected_candidate: &Candidate)
|
fn distribute_surplus<N: Number>(state: &mut CountState<N>, opts: &STVOptions, elected_candidate: &Candidate)
|
||||||
where
|
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::Div<&'r N, Output=N>,
|
||||||
for<'r> &'r N: ops::Neg<Output=N>
|
for<'r> &'r N: ops::Neg<Output=N>
|
||||||
{
|
{
|
||||||
|
state.logger.log_literal(format!("Surplus of {} distributed.", elected_candidate.name));
|
||||||
|
|
||||||
let count_card = state.candidates.get(elected_candidate).unwrap();
|
let count_card = state.candidates.get(elected_candidate).unwrap();
|
||||||
let surplus = &count_card.votes - state.quota.as_ref().unwrap();
|
let surplus = &count_card.votes - state.quota.as_ref().unwrap();
|
||||||
|
|
||||||
|
@ -725,47 +790,54 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
let transferable_votes = &result.total_votes - &result.exhausted.num_votes;
|
let transferable_votes = &result.total_votes - &result.exhausted.num_votes;
|
||||||
let transfer_denom = calculate_transfer_denom(&surplus, &result, &transferable_votes, is_weighted, opts.transferable_only);
|
let surplus_denom = calculate_surplus_denom(&surplus, &result, &transferable_votes, is_weighted, opts.transferable_only);
|
||||||
let mut transfer_value;
|
let mut surplus_fraction;
|
||||||
match transfer_denom {
|
match surplus_denom {
|
||||||
Some(ref v) => {
|
Some(ref v) => {
|
||||||
transfer_value = Some(surplus.clone() / v);
|
surplus_fraction = Some(surplus.clone() / v);
|
||||||
|
|
||||||
// Round down if requested
|
// Round down if requested
|
||||||
if let Some(dps) = opts.round_tvs {
|
if let Some(dps) = opts.round_tvs {
|
||||||
transfer_value.as_mut().unwrap().floor_mut(dps);
|
surplus_fraction.as_mut().unwrap().floor_mut(dps);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.logger.log_literal(format!("Surplus of {} distributed at value {:.dps2$}.", elected_candidate.name, transfer_value.as_ref().unwrap(), dps2=max(opts.pp_decimals, 2)));
|
if opts.transferable_only {
|
||||||
|
state.logger.log_literal(format!("Transferring {:.0} transferable ballots, totalling {:.dps$} transferable votes, with surplus fraction {:.dps2$}.", &result.total_ballots - &result.exhausted.num_ballots, transferable_votes, surplus_fraction.as_ref().unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
|
||||||
|
} else {
|
||||||
|
state.logger.log_literal(format!("Transferring {:.0} ballots, totalling {:.dps$} votes, with surplus fraction {:.dps2$}.", result.total_ballots, result.total_votes, surplus_fraction.as_ref().unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
transfer_value = None;
|
surplus_fraction = None;
|
||||||
state.logger.log_literal(format!("Surplus of {} distributed at values received.", elected_candidate.name));
|
|
||||||
|
if opts.transferable_only {
|
||||||
|
state.logger.log_literal(format!("Transferring {:.0} transferable ballots, totalling {:.dps$} transferable votes, at values received.", &result.total_ballots - &result.exhausted.num_ballots, transferable_votes, dps=opts.pp_decimals));
|
||||||
|
} else {
|
||||||
|
state.logger.log_literal(format!("Transferring {:.0} ballots, totalling {:.dps$} votes, at values received.", result.total_ballots, result.total_votes, dps=opts.pp_decimals));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut checksum = N::new();
|
let mut checksum = N::new();
|
||||||
|
|
||||||
for (candidate, entry) in result.candidates.into_iter() {
|
for (candidate, entry) in result.candidates.into_iter() {
|
||||||
|
// Credit transferred votes
|
||||||
|
let candidate_transfers = sum_surplus_transfers(&entry, &surplus, is_weighted, &surplus_fraction, &surplus_denom, state, opts);
|
||||||
|
let count_card = state.candidates.get_mut(candidate).unwrap();
|
||||||
|
count_card.transfer(&candidate_transfers);
|
||||||
|
checksum += candidate_transfers;
|
||||||
|
|
||||||
let mut parcel = entry.votes as Parcel<N>;
|
let mut parcel = entry.votes as Parcel<N>;
|
||||||
|
|
||||||
// Reweight votes
|
// Reweight votes
|
||||||
for vote in parcel.iter_mut() {
|
for vote in parcel.iter_mut() {
|
||||||
vote.value = reweight_vote(&vote.value, &vote.ballot.orig_value, &surplus, is_weighted, &transfer_value, &transfer_denom, opts.round_tvs, opts.round_weights);
|
vote.value = reweight_vote(&vote.value, &vote.ballot.orig_value, &surplus, is_weighted, &surplus_fraction, &surplus_denom, opts.round_tvs, opts.round_weights);
|
||||||
}
|
}
|
||||||
|
|
||||||
let count_card = state.candidates.get_mut(candidate).unwrap();
|
|
||||||
count_card.parcels.push(parcel);
|
count_card.parcels.push(parcel);
|
||||||
|
|
||||||
let candidate_transfers = reweight_vote(&entry.num_votes, &entry.num_ballots, &surplus, is_weighted, &transfer_value, &transfer_denom, opts.round_tvs, opts.round_votes);
|
|
||||||
count_card.transfer(&candidate_transfers);
|
|
||||||
checksum += candidate_transfers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer exhausted votes
|
// Credit exhausted votes
|
||||||
let parcel = result.exhausted.votes as Parcel<N>;
|
|
||||||
state.exhausted.parcels.push(parcel);
|
|
||||||
|
|
||||||
let mut exhausted_transfers;
|
let mut exhausted_transfers;
|
||||||
if opts.transferable_only {
|
if opts.transferable_only {
|
||||||
if transferable_votes > surplus {
|
if transferable_votes > surplus {
|
||||||
|
@ -773,17 +845,22 @@ where
|
||||||
exhausted_transfers = N::new();
|
exhausted_transfers = N::new();
|
||||||
} else {
|
} else {
|
||||||
exhausted_transfers = &surplus - &transferable_votes;
|
exhausted_transfers = &surplus - &transferable_votes;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
exhausted_transfers = reweight_vote(&result.exhausted.num_votes, &result.exhausted.num_ballots, &surplus, is_weighted, &transfer_value, &transfer_denom, opts.round_tvs, opts.round_votes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(dps) = opts.round_votes {
|
if let Some(dps) = opts.round_votes {
|
||||||
exhausted_transfers.floor_mut(dps);
|
exhausted_transfers.floor_mut(dps);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exhausted_transfers = sum_surplus_transfers(&result.exhausted, &surplus, is_weighted, &surplus_fraction, &surplus_denom, state, opts);
|
||||||
|
}
|
||||||
|
|
||||||
state.exhausted.transfer(&exhausted_transfers);
|
state.exhausted.transfer(&exhausted_transfers);
|
||||||
checksum += exhausted_transfers;
|
checksum += exhausted_transfers;
|
||||||
|
|
||||||
|
// Transfer exhausted votes
|
||||||
|
let parcel = result.exhausted.votes as Parcel<N>;
|
||||||
|
state.exhausted.parcels.push(parcel);
|
||||||
|
|
||||||
// Finalise candidate votes
|
// Finalise candidate votes
|
||||||
let count_card = state.candidates.get_mut(elected_candidate).unwrap();
|
let count_card = state.candidates.get_mut(elected_candidate).unwrap();
|
||||||
count_card.transfers = -&surplus;
|
count_card.transfers = -&surplus;
|
||||||
|
|
|
@ -59,6 +59,7 @@ fn aec_tas19_rational() {
|
||||||
round_weights: None,
|
round_weights: None,
|
||||||
round_votes: Some(0),
|
round_votes: Some(0),
|
||||||
round_quota: Some(0),
|
round_quota: Some(0),
|
||||||
|
sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep,
|
||||||
quota: stv::QuotaType::Droop,
|
quota: stv::QuotaType::Droop,
|
||||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||||
quota_mode: stv::QuotaMode::Static,
|
quota_mode: stv::QuotaMode::Static,
|
||||||
|
|
|
@ -33,6 +33,7 @@ fn ers97_rational() {
|
||||||
round_weights: Some(2),
|
round_weights: Some(2),
|
||||||
round_votes: Some(2),
|
round_votes: Some(2),
|
||||||
round_quota: Some(2),
|
round_quota: Some(2),
|
||||||
|
sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep,
|
||||||
quota: stv::QuotaType::DroopExact,
|
quota: stv::QuotaType::DroopExact,
|
||||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||||
quota_mode: stv::QuotaMode::ERS97,
|
quota_mode: stv::QuotaMode::ERS97,
|
||||||
|
|
|
@ -27,6 +27,7 @@ fn prsa1_rational() {
|
||||||
round_weights: Some(3),
|
round_weights: Some(3),
|
||||||
round_votes: Some(3),
|
round_votes: Some(3),
|
||||||
round_quota: Some(3),
|
round_quota: Some(3),
|
||||||
|
sum_surplus_transfers: stv::SumSurplusTransfersMode::SingleStep,
|
||||||
quota: stv::QuotaType::Droop,
|
quota: stv::QuotaType::Droop,
|
||||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||||
quota_mode: stv::QuotaMode::Static,
|
quota_mode: stv::QuotaMode::Static,
|
||||||
|
|
|
@ -34,6 +34,7 @@ fn scotland_linn07_fixed5() {
|
||||||
round_weights: None,
|
round_weights: None,
|
||||||
round_votes: None,
|
round_votes: None,
|
||||||
round_quota: Some(0),
|
round_quota: Some(0),
|
||||||
|
sum_surplus_transfers: stv::SumSurplusTransfersMode::PerBallot,
|
||||||
quota: stv::QuotaType::Droop,
|
quota: stv::QuotaType::Droop,
|
||||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||||
quota_mode: stv::QuotaMode::Static,
|
quota_mode: stv::QuotaMode::Static,
|
||||||
|
|
Loading…
Reference in New Issue