Implement --transferable-only
This commit is contained in:
parent
c114d3a4ee
commit
76d69913c7
12
src/main.rs
12
src/main.rs
@ -45,41 +45,52 @@ enum Command {
|
||||
#[derive(Clap)]
|
||||
#[clap(setting=AppSettings::DeriveDisplayOrder)]
|
||||
struct STV {
|
||||
// ----------------
|
||||
// -- File input --
|
||||
|
||||
/// Path to the BLT file to be counted
|
||||
filename: String,
|
||||
|
||||
// ----------------------
|
||||
// -- Numbers settings --
|
||||
|
||||
/// Numbers mode
|
||||
#[clap(help_heading=Some("NUMBERS"), short, long, possible_values=&["rational", "float64"], default_value="rational", value_name="mode")]
|
||||
numbers: String,
|
||||
|
||||
// -----------------------
|
||||
// -- Rounding settings --
|
||||
|
||||
/// Round votes to specified decimal places
|
||||
#[clap(help_heading=Some("ROUNDING"), long, value_name="dps")]
|
||||
round_votes: Option<usize>,
|
||||
|
||||
// ------------------
|
||||
// -- STV variants --
|
||||
|
||||
/// Method of surplus transfers
|
||||
#[clap(help_heading=Some("STV VARIANTS"), long, possible_values=&["wig", "uig", "eg", "meek"], default_value="wig", value_name="method")]
|
||||
surplus: String,
|
||||
|
||||
/// Examine only transferable papers during surplus distributions
|
||||
#[clap(help_heading=Some("STV VARIANTS"), long)]
|
||||
transferable_only: bool,
|
||||
|
||||
/// Method of exclusions
|
||||
#[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,
|
||||
|
||||
// ----------------------
|
||||
// -- Display settings --
|
||||
|
||||
/// Hide excluded candidates from results report
|
||||
#[clap(help_heading=Some("DISPLAY"), long)]
|
||||
hide_excluded: bool,
|
||||
|
||||
/// Sort candidates by votes in results report
|
||||
#[clap(help_heading=Some("DISPLAY"), long)]
|
||||
sort_votes: bool,
|
||||
|
||||
/// Print votes to specified decimal places in results report
|
||||
#[clap(help_heading=Some("DISPLAY"), long, default_value="2", value_name="dps")]
|
||||
pp_decimals: usize,
|
||||
@ -120,6 +131,7 @@ where
|
||||
"meek" => stv::SurplusMethod::Meek,
|
||||
_ => panic!("Invalid --surplus"),
|
||||
},
|
||||
transferable_only: cmd_opts.transferable_only,
|
||||
exclusion: match cmd_opts.exclusion.as_str() {
|
||||
"single_stage" => stv::ExclusionMethod::SingleStage,
|
||||
"by_value" => stv::ExclusionMethod::ByValue,
|
||||
|
@ -32,6 +32,7 @@ use std::ops;
|
||||
pub struct STVOptions {
|
||||
pub round_votes: Option<usize>,
|
||||
pub surplus: SurplusMethod,
|
||||
pub transferable_only: bool,
|
||||
pub exclusion: ExclusionMethod,
|
||||
pub pp_decimals: usize,
|
||||
}
|
||||
@ -265,6 +266,45 @@ where
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 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>
|
||||
where
|
||||
for<'r> &'r N: ops::Sub<&'r N, Output=N>
|
||||
{
|
||||
if transferable_only {
|
||||
let total_units = if weighted { &result.total_votes } else { &result.total_ballots };
|
||||
let exhausted_units = if weighted { &result.exhausted.num_votes } else { &result.exhausted.num_ballots };
|
||||
let transferable_units = total_units - exhausted_units;
|
||||
|
||||
if transferable_votes > surplus {
|
||||
return Some(transferable_units);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
if weighted {
|
||||
return Some(result.total_votes.clone());
|
||||
} else {
|
||||
return Some(result.total_ballots.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reweight_vote<N: Number>(num_votes: &N, num_ballots: &N, surplus: &N, weighted: bool, transfer_denom: &Option<N>) -> N {
|
||||
match transfer_denom {
|
||||
Some(v) => {
|
||||
if weighted {
|
||||
return num_votes.clone() * surplus / v;
|
||||
} else {
|
||||
return num_ballots.clone() * surplus / v;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return num_votes.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn distribute_surplus<N: Number>(state: &mut CountState<N>, opts: &STVOptions, elected_candidate: &Candidate)
|
||||
where
|
||||
for<'r> &'r N: ops::Sub<&'r N, Output=N>,
|
||||
@ -292,23 +332,29 @@ where
|
||||
// Count next preferences
|
||||
let result = next_preferences(state, votes);
|
||||
|
||||
// Transfer candidate votes
|
||||
let transfer_value;
|
||||
match opts.surplus {
|
||||
SurplusMethod::WIG => {
|
||||
// Weighted inclusive Gregory
|
||||
transfer_value = surplus.clone() / &result.total_votes;
|
||||
}
|
||||
SurplusMethod::UIG | SurplusMethod::EG => {
|
||||
// Unweighted inclusive Gregory
|
||||
transfer_value = surplus.clone() / &result.total_ballots;
|
||||
}
|
||||
SurplusMethod::Meek => { todo!(); }
|
||||
}
|
||||
|
||||
state.kind = Some("Surplus of");
|
||||
state.title = String::from(&elected_candidate.name);
|
||||
|
||||
// Transfer candidate votes
|
||||
// TODO: Refactor??
|
||||
let is_weighted = match opts.surplus {
|
||||
SurplusMethod::WIG => { true }
|
||||
SurplusMethod::UIG | SurplusMethod::EG => { false }
|
||||
SurplusMethod::Meek => { todo!() }
|
||||
};
|
||||
|
||||
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 transfer_value;
|
||||
match transfer_denom {
|
||||
Some(ref v) => {
|
||||
transfer_value = surplus.clone() / v;
|
||||
state.logger.log_literal(format!("Surplus of {} distributed at value {:.dps$}.", elected_candidate.name, transfer_value, dps=opts.pp_decimals));
|
||||
}
|
||||
None => {
|
||||
state.logger.log_literal(format!("Surplus of {} distributed at values received.", elected_candidate.name));
|
||||
}
|
||||
}
|
||||
|
||||
let mut checksum = N::new();
|
||||
|
||||
@ -317,14 +363,13 @@ where
|
||||
|
||||
// Reweight votes
|
||||
for vote in parcel.iter_mut() {
|
||||
//vote.value = vote.ballot.orig_value.clone() * &transfer_value;
|
||||
vote.value = vote.ballot.orig_value.clone() * &surplus / &result.total_ballots;
|
||||
vote.value = reweight_vote(&vote.value, &vote.ballot.orig_value, &surplus, is_weighted, &transfer_denom);
|
||||
}
|
||||
|
||||
let count_card = state.candidates.get_mut(candidate).unwrap();
|
||||
count_card.parcels.push(parcel);
|
||||
|
||||
let mut candidate_transfers = entry.num_ballots * &surplus / &result.total_ballots;
|
||||
let mut candidate_transfers = reweight_vote(&entry.num_votes, &entry.num_ballots, &surplus, is_weighted, &transfer_denom);
|
||||
// Round transfers
|
||||
if let Some(dps) = opts.round_votes {
|
||||
candidate_transfers.floor_mut(dps);
|
||||
@ -337,7 +382,18 @@ where
|
||||
let parcel = result.exhausted.votes as Parcel<N>;
|
||||
state.exhausted.parcels.push(parcel);
|
||||
|
||||
let mut exhausted_transfers = result.exhausted.num_ballots * &surplus / &result.total_ballots;
|
||||
let mut exhausted_transfers;
|
||||
if opts.transferable_only {
|
||||
if transferable_votes > surplus {
|
||||
// No ballots exhaust
|
||||
exhausted_transfers = N::new();
|
||||
} else {
|
||||
exhausted_transfers = &surplus - &transferable_votes;
|
||||
}
|
||||
} else {
|
||||
exhausted_transfers = reweight_vote(&result.exhausted.num_votes, &result.exhausted.num_ballots, &surplus, is_weighted, &transfer_denom);
|
||||
}
|
||||
|
||||
if let Some(dps) = opts.round_votes {
|
||||
exhausted_transfers.floor_mut(dps);
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ fn aec_tas19_rational() {
|
||||
let stv_opts = stv::STVOptions {
|
||||
round_votes: Some(0),
|
||||
surplus: stv::SurplusMethod::UIG,
|
||||
transferable_only: false,
|
||||
exclusion: stv::ExclusionMethod::ByValue,
|
||||
pp_decimals: 2,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user