Implement Wright STV
This commit is contained in:
parent
a1c21cf2b4
commit
d46eb69f26
@ -41,7 +41,7 @@
|
|||||||
<option value="meek06">Meek STV (2006)</option>
|
<option value="meek06">Meek STV (2006)</option>
|
||||||
<option value="meeknz">Meek STV (New Zealand)</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>
|
||||||
<option value="ers97">ERS97</option>
|
<option value="ers97">ERS97</option>
|
||||||
</select>
|
</select>
|
||||||
@ -112,7 +112,7 @@
|
|||||||
<option value="single_stage" selected>Single stage</option>
|
<option value="single_stage" selected>Single stage</option>
|
||||||
<option value="by_value">By value</option>
|
<option value="by_value">By value</option>
|
||||||
<option value="parcels_by_order">By parcel (by order)</option>
|
<option value="parcels_by_order">By parcel (by order)</option>
|
||||||
<!--<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;">
|
<label style="margin-left:1em;">
|
||||||
|
@ -460,6 +460,28 @@ function changePreset() {
|
|||||||
document.getElementById('selPapers').value = 'both';
|
document.getElementById('selPapers').value = 'both';
|
||||||
document.getElementById('selExclusion').value = 'by_value';
|
document.getElementById('selExclusion').value = 'by_value';
|
||||||
document.getElementById('selTies').value = 'backwards,random';
|
document.getElementById('selTies').value = 'backwards,random';
|
||||||
|
} else if (document.getElementById('selPreset').value === 'wright') {
|
||||||
|
document.getElementById('selQuotaCriterion').value = 'geq';
|
||||||
|
document.getElementById('selQuota').value = 'droop';
|
||||||
|
document.getElementById('selQuotaMode').value = 'static';
|
||||||
|
//document.getElementById('chkBulkElection').checked = true;
|
||||||
|
document.getElementById('chkBulkExclusion').checked = true;
|
||||||
|
document.getElementById('chkDeferSurpluses').checked = false;
|
||||||
|
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;
|
||||||
|
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 = 'wright';
|
||||||
|
document.getElementById('selTies').value = 'random';
|
||||||
} else if (document.getElementById('selPreset').value === 'prsa77') {
|
} else if (document.getElementById('selPreset').value === 'prsa77') {
|
||||||
document.getElementById('selQuotaCriterion').value = 'geq';
|
document.getElementById('selQuotaCriterion').value = 'geq';
|
||||||
document.getElementById('selQuota').value = 'droop';
|
document.getElementById('selQuota').value = 'droop';
|
||||||
|
@ -106,6 +106,10 @@ tr.stage-no td:not(:empty), tr.transfers td {
|
|||||||
tr.info:last-child td, .bb {
|
tr.info:last-child td, .bb {
|
||||||
border-bottom: 1px solid #76858c;
|
border-bottom: 1px solid #76858c;
|
||||||
}
|
}
|
||||||
|
.blw {
|
||||||
|
/* Used to separate counts in Wright STV */
|
||||||
|
border-left: 2px solid #76858c;
|
||||||
|
}
|
||||||
|
|
||||||
/* Table stripes */
|
/* Table stripes */
|
||||||
|
|
||||||
|
@ -63,7 +63,8 @@ where
|
|||||||
let quota = state.quota.as_ref().unwrap();
|
let quota = state.quota.as_ref().unwrap();
|
||||||
let mut has_surplus: Vec<(&Candidate, &CountCard<N>)> = state.election.candidates.iter() // Present in order in case of tie
|
let mut has_surplus: Vec<(&Candidate, &CountCard<N>)> = state.election.candidates.iter() // Present in order in case of tie
|
||||||
.map(|c| (c, state.candidates.get(c).unwrap()))
|
.map(|c| (c, state.candidates.get(c).unwrap()))
|
||||||
.filter(|(_, cc)| &cc.votes > quota)
|
//.filter(|(_, cc)| &cc.votes > quota)
|
||||||
|
.filter(|(_, cc)| &cc.votes > quota && cc.parcels.iter().any(|p| !p.is_empty()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !has_surplus.is_empty() {
|
if !has_surplus.is_empty() {
|
||||||
@ -337,6 +338,8 @@ where
|
|||||||
count_card.votes.assign(state.quota.as_ref().unwrap());
|
count_card.votes.assign(state.quota.as_ref().unwrap());
|
||||||
checksum -= surplus;
|
checksum -= surplus;
|
||||||
|
|
||||||
|
count_card.parcels.clear(); // Mark surpluses as done
|
||||||
|
|
||||||
// Update loss by fraction
|
// Update loss by fraction
|
||||||
state.loss_fraction.transfer(&-checksum);
|
state.loss_fraction.transfer(&-checksum);
|
||||||
}
|
}
|
||||||
@ -435,6 +438,7 @@ where
|
|||||||
checksum -= &votes_transferred;
|
checksum -= &votes_transferred;
|
||||||
count_card.transfer(&-votes_transferred);
|
count_card.transfer(&-votes_transferred);
|
||||||
}
|
}
|
||||||
|
_ => panic!()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !votes.is_empty() {
|
if !votes.is_empty() {
|
||||||
@ -494,3 +498,59 @@ where
|
|||||||
// Update loss by fraction
|
// Update loss by fraction
|
||||||
state.loss_fraction.transfer(&-checksum);
|
state.loss_fraction.transfer(&-checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Perform one stage of a candidate exclusion according to the Wright method
|
||||||
|
pub fn wright_exclude_candidates<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, excluded_candidates: Vec<&'a Candidate>)
|
||||||
|
where
|
||||||
|
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::Div<&'r N, Output=N>,
|
||||||
|
{
|
||||||
|
// Used to give bulk excluded candidate the same order_elected
|
||||||
|
let order_excluded = state.num_excluded + 1;
|
||||||
|
|
||||||
|
for excluded_candidate in excluded_candidates.iter() {
|
||||||
|
let count_card = state.candidates.get_mut(excluded_candidate).unwrap();
|
||||||
|
|
||||||
|
// Rust borrow checker is unhappy if we try to put this in exclude_hopefuls ??!
|
||||||
|
if count_card.state != CandidateState::Excluded {
|
||||||
|
count_card.state = CandidateState::Excluded;
|
||||||
|
state.num_excluded += 1;
|
||||||
|
count_card.order_elected = -(order_excluded as isize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset count
|
||||||
|
for (_, count_card) in state.candidates.iter_mut() {
|
||||||
|
if count_card.order_elected > 0 {
|
||||||
|
count_card.order_elected = 0;
|
||||||
|
}
|
||||||
|
count_card.parcels.clear();
|
||||||
|
count_card.votes = N::new();
|
||||||
|
count_card.transfers = N::new();
|
||||||
|
count_card.state = match count_card.state {
|
||||||
|
CandidateState::Withdrawn => CandidateState::Withdrawn,
|
||||||
|
CandidateState::Excluded => CandidateState::Excluded,
|
||||||
|
_ => CandidateState::Hopeful,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
state.exhausted.votes = N::new();
|
||||||
|
state.exhausted.transfers = N::new();
|
||||||
|
state.loss_fraction.votes = N::new();
|
||||||
|
state.loss_fraction.transfers = N::new();
|
||||||
|
|
||||||
|
state.num_elected = 0;
|
||||||
|
|
||||||
|
let orig_title = state.title.clone();
|
||||||
|
|
||||||
|
// Redistribute first preferences
|
||||||
|
super::distribute_first_preferences(state, opts);
|
||||||
|
|
||||||
|
state.kind = Some("Exclusion of");
|
||||||
|
state.title = orig_title;
|
||||||
|
|
||||||
|
// Trigger recalculation of quota within stv::count_one_stage
|
||||||
|
state.quota = None;
|
||||||
|
state.vote_required_election = None;
|
||||||
|
}
|
||||||
|
@ -160,6 +160,7 @@ impl STVOptions {
|
|||||||
"single_stage" => ExclusionMethod::SingleStage,
|
"single_stage" => ExclusionMethod::SingleStage,
|
||||||
"by_value" => ExclusionMethod::ByValue,
|
"by_value" => ExclusionMethod::ByValue,
|
||||||
"parcels_by_order" => ExclusionMethod::ParcelsByOrder,
|
"parcels_by_order" => ExclusionMethod::ParcelsByOrder,
|
||||||
|
"wright" => ExclusionMethod::Wright,
|
||||||
_ => panic!("Invalid --exclusion"),
|
_ => panic!("Invalid --exclusion"),
|
||||||
},
|
},
|
||||||
meek_nz_exclusion,
|
meek_nz_exclusion,
|
||||||
@ -360,6 +361,8 @@ pub enum ExclusionMethod {
|
|||||||
ByValue,
|
ByValue,
|
||||||
/// Transfer the ballot papers of an excluded candidate parcel by parcel in the order received
|
/// Transfer the ballot papers of an excluded candidate parcel by parcel in the order received
|
||||||
ParcelsByOrder,
|
ParcelsByOrder,
|
||||||
|
/// Wright method (re-iterate)
|
||||||
|
Wright,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExclusionMethod {
|
impl ExclusionMethod {
|
||||||
@ -369,6 +372,7 @@ impl ExclusionMethod {
|
|||||||
ExclusionMethod::SingleStage => "--exclusion single_stage",
|
ExclusionMethod::SingleStage => "--exclusion single_stage",
|
||||||
ExclusionMethod::ByValue => "--exclusion by_value",
|
ExclusionMethod::ByValue => "--exclusion by_value",
|
||||||
ExclusionMethod::ParcelsByOrder => "--exclusion parcels_by_order",
|
ExclusionMethod::ParcelsByOrder => "--exclusion parcels_by_order",
|
||||||
|
ExclusionMethod::Wright => "--exclusion wright",
|
||||||
}.to_string()
|
}.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -952,6 +956,8 @@ where
|
|||||||
// Exclusion in parts compatible only with Gregory method
|
// Exclusion in parts compatible only with Gregory method
|
||||||
gregory::exclude_candidates(state, opts, excluded_candidates);
|
gregory::exclude_candidates(state, opts, excluded_candidates);
|
||||||
}
|
}
|
||||||
|
ExclusionMethod::Wright => {
|
||||||
|
gregory::wright_exclude_candidates(state, opts, excluded_candidates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -285,43 +285,52 @@ fn init_results_table<N: Number>(election: &Election<N>, opts: &stv::STVOptions)
|
|||||||
/// Generate subsequent columns of the HTML results table
|
/// Generate subsequent columns of the HTML results table
|
||||||
fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>, opts: &stv::STVOptions) -> Array {
|
fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>, opts: &stv::STVOptions) -> Array {
|
||||||
let result = Array::new();
|
let result = Array::new();
|
||||||
result.push(&format!(r#"<td>{}</td>"#, stage_num).into());
|
|
||||||
result.push(&format!(r#"<td>{}</td>"#, state.kind.unwrap_or("")).into());
|
// Insert borders to left of new exclusions in Wright STV
|
||||||
result.push(&format!(r#"<td>{}</td>"#, state.title).into());
|
let mut tdclasses1 = "";
|
||||||
|
let mut tdclasses2 = "";
|
||||||
|
if opts.exclusion == stv::ExclusionMethod::Wright && state.kind == Some("Exclusion of") {
|
||||||
|
tdclasses1 = r#" class="blw""#;
|
||||||
|
tdclasses2 = r#"blw "#;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(&format!(r#"<td{}>{}</td>"#, tdclasses1, stage_num).into());
|
||||||
|
result.push(&format!(r#"<td{}>{}</td>"#, tdclasses1, state.kind.unwrap_or("")).into());
|
||||||
|
result.push(&format!(r#"<td{}>{}</td>"#, tdclasses1, state.title).into());
|
||||||
for candidate in state.election.candidates.iter() {
|
for candidate in state.election.candidates.iter() {
|
||||||
let count_card = state.candidates.get(candidate).unwrap();
|
let count_card = state.candidates.get(candidate).unwrap();
|
||||||
if count_card.state == stv::CandidateState::Elected {
|
if count_card.state == stv::CandidateState::Elected {
|
||||||
result.push(&format!(r#"<td class="count elected">{}</td>"#, pp(&count_card.transfers, opts.pp_decimals)).into());
|
result.push(&format!(r#"<td class="{}count elected">{}</td>"#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
|
||||||
result.push(&format!(r#"<td class="count elected">{}</td>"#, pp(&count_card.votes, opts.pp_decimals)).into());
|
result.push(&format!(r#"<td class="{}count elected">{}</td>"#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
|
||||||
} else if count_card.state == stv::CandidateState::Excluded {
|
} else if count_card.state == stv::CandidateState::Excluded {
|
||||||
result.push(&format!(r#"<td class="count excluded">{}</td>"#, pp(&count_card.transfers, opts.pp_decimals)).into());
|
result.push(&format!(r#"<td class="{}count excluded">{}</td>"#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
|
||||||
if count_card.votes.is_zero() {
|
if count_card.votes.is_zero() {
|
||||||
result.push(&r#"<td class="count excluded">Ex</td>"#.into());
|
result.push(&format!(r#"<td class="{}count excluded">Ex</td>"#, tdclasses2).into());
|
||||||
} else {
|
} else {
|
||||||
result.push(&format!(r#"<td class="count excluded">{}</td>"#, pp(&count_card.votes, opts.pp_decimals)).into());
|
result.push(&format!(r#"<td class="{}count excluded">{}</td>"#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
|
||||||
}
|
}
|
||||||
} else if count_card.state == stv::CandidateState::Withdrawn {
|
} else if count_card.state == stv::CandidateState::Withdrawn {
|
||||||
result.push(&r#"<td class="count excluded"></td>"#.into());
|
result.push(&format!(r#"<td class="{}count excluded"></td>"#, tdclasses2).into());
|
||||||
result.push(&r#"<td class="count excluded">WD</td>"#.into());
|
result.push(&format!(r#"<td class="{}count excluded">WD</td>"#, tdclasses2).into());
|
||||||
} else {
|
} else {
|
||||||
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&count_card.transfers, opts.pp_decimals)).into());
|
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
|
||||||
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&count_card.votes, opts.pp_decimals)).into());
|
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&count_card.votes, opts.pp_decimals)).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&state.exhausted.transfers, opts.pp_decimals)).into());
|
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&state.exhausted.transfers, opts.pp_decimals)).into());
|
||||||
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&state.exhausted.votes, opts.pp_decimals)).into());
|
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&state.exhausted.votes, opts.pp_decimals)).into());
|
||||||
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&state.loss_fraction.transfers, opts.pp_decimals)).into());
|
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&state.loss_fraction.transfers, opts.pp_decimals)).into());
|
||||||
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&state.loss_fraction.votes, opts.pp_decimals)).into());
|
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&state.loss_fraction.votes, opts.pp_decimals)).into());
|
||||||
|
|
||||||
// Calculate total votes
|
// Calculate total votes
|
||||||
let mut total_vote = state.candidates.values().fold(N::zero(), |acc, cc| { acc + &cc.votes });
|
let mut total_vote = state.candidates.values().fold(N::zero(), |acc, cc| { acc + &cc.votes });
|
||||||
total_vote += &state.exhausted.votes;
|
total_vote += &state.exhausted.votes;
|
||||||
total_vote += &state.loss_fraction.votes;
|
total_vote += &state.loss_fraction.votes;
|
||||||
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&total_vote, opts.pp_decimals)).into());
|
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&total_vote, opts.pp_decimals)).into());
|
||||||
|
|
||||||
result.push(&format!(r#"<td class="count">{}</td>"#, pp(state.quota.as_ref().unwrap(), opts.pp_decimals)).into());
|
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(state.quota.as_ref().unwrap(), opts.pp_decimals)).into());
|
||||||
if opts.quota_mode == stv::QuotaMode::ERS97 {
|
if opts.quota_mode == stv::QuotaMode::ERS97 {
|
||||||
result.push(&format!(r#"<td class="count">{}</td>"#, pp(state.vote_required_election.as_ref().unwrap(), opts.pp_decimals)).into());
|
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(state.vote_required_election.as_ref().unwrap(), opts.pp_decimals)).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
Loading…
Reference in New Issue
Block a user