Fixes to edge cases in stratify (LR) sample method
This commit is contained in:
parent
2f7abf9f0a
commit
cf75943829
|
@ -135,7 +135,7 @@ where
|
|||
}
|
||||
};
|
||||
let elected_candidate = if max_cands.len() > 1 {
|
||||
super::choose_highest(state, opts, max_cands, "Which candidate's surplus to distribute?")?
|
||||
super::choose_highest(state, opts, &max_cands, "Which candidate's surplus to distribute?")?
|
||||
} else {
|
||||
max_cands[0]
|
||||
};
|
||||
|
|
|
@ -1061,7 +1061,7 @@ fn elect_sure_winners<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOp
|
|||
while !leading_hopefuls.is_empty() && state.num_elected < state.election.seats {
|
||||
let max_cands = ties::multiple_max_by(&leading_hopefuls, |c| &state.candidates[c].votes);
|
||||
let candidate = if max_cands.len() > 1 {
|
||||
choose_highest(state, opts, max_cands, "Which candidate to elect?")?
|
||||
choose_highest(state, opts, &max_cands, "Which candidate to elect?")?
|
||||
} else {
|
||||
max_cands[0]
|
||||
};
|
||||
|
@ -1109,7 +1109,7 @@ fn elect_hopefuls<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOption
|
|||
// Declare elected in descending order of votes
|
||||
let max_cands = ties::multiple_max_by(&cands_meeting_quota, |c| &state.candidates[c].votes);
|
||||
let candidate = if max_cands.len() > 1 {
|
||||
choose_highest(state, opts, max_cands, "Which candidate to elect?")?
|
||||
choose_highest(state, opts, &max_cands, "Which candidate to elect?")?
|
||||
} else {
|
||||
max_cands[0]
|
||||
};
|
||||
|
@ -1207,7 +1207,7 @@ where
|
|||
if num_to_exclude > 0 {
|
||||
let total_excluded = to_exclude.into_iter()
|
||||
.fold(N::new(), |acc, c| acc + &state.candidates[c].votes);
|
||||
if total_surpluses > &(&hopefuls[num_to_exclude + 1].1.votes - &total_excluded) {
|
||||
if total_surpluses > &(&hopefuls[num_to_exclude].1.votes - &total_excluded) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1265,7 +1265,7 @@ fn do_bulk_elect<N: Number>(state: &mut CountState<N>, opts: &STVOptions, templa
|
|||
while !hopefuls.is_empty() {
|
||||
let max_cands = ties::multiple_max_by(&hopefuls, |c| &state.candidates[c].votes);
|
||||
let candidate = if max_cands.len() > 1 {
|
||||
choose_highest(state, opts, max_cands, "Which candidate to elect?")?
|
||||
choose_highest(state, opts, &max_cands, "Which candidate to elect?")?
|
||||
} else {
|
||||
max_cands[0]
|
||||
};
|
||||
|
@ -1330,7 +1330,7 @@ where
|
|||
// Exclude only the lowest-ranked doomed candidate
|
||||
let min_cands = ties::multiple_min_by(&doomed, |c| &state.candidates[c].votes);
|
||||
excluded_candidates = if min_cands.len() > 1 {
|
||||
vec![choose_lowest(state, opts, min_cands, "Which candidate to exclude?")?]
|
||||
vec![choose_lowest(state, opts, &min_cands, "Which candidate to exclude?")?]
|
||||
} else {
|
||||
vec![min_cands[0]]
|
||||
};
|
||||
|
@ -1468,7 +1468,7 @@ where
|
|||
|
||||
let min_cands = ties::multiple_min_by(&hopefuls, |c| &state.candidates[c].votes);
|
||||
excluded_candidates = if min_cands.len() > 1 {
|
||||
vec![choose_lowest(state, opts, min_cands, "Which candidate to exclude?")?]
|
||||
vec![choose_lowest(state, opts, &min_cands, "Which candidate to exclude?")?]
|
||||
} else {
|
||||
vec![min_cands[0]]
|
||||
};
|
||||
|
@ -1586,7 +1586,7 @@ fn finished_before_stage<N: Number>(state: &CountState<N>) -> bool {
|
|||
/// Break a tie between the given candidates according to [STVOptions::ties], selecting the highest candidate
|
||||
///
|
||||
/// The given candidates are assumed to be tied in this round.
|
||||
fn choose_highest<'c, N: Number>(state: &mut CountState<N>, opts: &STVOptions, candidates: Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> {
|
||||
pub fn choose_highest<'c, N: Number>(state: &mut CountState<N>, opts: &STVOptions, candidates: &Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> {
|
||||
for strategy in opts.ties.iter() {
|
||||
match strategy.choose_highest(state, opts, &candidates, prompt_text) {
|
||||
Ok(c) => {
|
||||
|
@ -1607,7 +1607,7 @@ fn choose_highest<'c, N: Number>(state: &mut CountState<N>, opts: &STVOptions, c
|
|||
/// Break a tie between the given candidates according to [STVOptions::ties], selecting the lowest candidate
|
||||
///
|
||||
/// The given candidates are assumed to be tied in this round.
|
||||
fn choose_lowest<'c, N: Number>(state: &mut CountState<N>, opts: &STVOptions, candidates: Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> {
|
||||
pub fn choose_lowest<'c, N: Number>(state: &mut CountState<N>, opts: &STVOptions, candidates: &Vec<&'c Candidate>, prompt_text: &str) -> Result<&'c Candidate, STVError> {
|
||||
for strategy in opts.ties.iter() {
|
||||
match strategy.choose_lowest(state, opts, &candidates, prompt_text) {
|
||||
Ok(c) => {
|
||||
|
|
|
@ -188,19 +188,38 @@ where
|
|||
// Round remainders to remove loss by fraction
|
||||
let transferred = candidate_transfers_remainders.values().fold(N::new(), |acc, (t, _)| acc + t);
|
||||
let loss_fraction = &surplus - &transferred;
|
||||
if !loss_fraction.is_zero() {
|
||||
if !loss_fraction.is_zero() && surplus_fraction.is_some() {
|
||||
let n_to_round: usize = format!("{:.0}", loss_fraction).parse().expect("Loss by fraction overflows usize");
|
||||
|
||||
let mut cands_by_remainder: Vec<Option<&Candidate>> = candidate_transfers_remainders.keys().cloned().collect();
|
||||
let mut cands_by_remainder = candidate_transfers_remainders.keys().cloned().collect::<Vec<_>>();
|
||||
|
||||
// Sort by whole parts
|
||||
// Compare b to a to sort high-low
|
||||
cands_by_remainder.sort_unstable_by(|a, b| candidate_transfers_remainders[b].1.cmp(&candidate_transfers_remainders[a].1));
|
||||
cands_by_remainder.sort_unstable_by(|a, b| candidate_transfers_remainders[b].0.cmp(&candidate_transfers_remainders[a].0));
|
||||
|
||||
// Then sort by remainders
|
||||
cands_by_remainder.sort_by(|a, b| candidate_transfers_remainders[b].1.cmp(&candidate_transfers_remainders[a].1));
|
||||
|
||||
// Select top remainders
|
||||
let top_remainders: Vec<&Option<&Candidate>> = cands_by_remainder.iter().take(n_to_round).collect();
|
||||
let mut top_remainders = cands_by_remainder.iter().take(n_to_round).collect::<Vec<_>>();
|
||||
|
||||
// Check for tied remainders
|
||||
if candidate_transfers_remainders[top_remainders.last().unwrap()].1 == candidate_transfers_remainders[cands_by_remainder.iter().nth(n_to_round + 1).unwrap()].1 {
|
||||
todo!("Tie for largest remainders");
|
||||
if candidate_transfers_remainders[top_remainders.last().unwrap()] == candidate_transfers_remainders[cands_by_remainder.iter().nth(n_to_round).unwrap()] {
|
||||
// Get the top entry
|
||||
let top_entry = &candidate_transfers_remainders[top_remainders.last().unwrap()];
|
||||
|
||||
// Separate out tied entries
|
||||
top_remainders = top_remainders.into_iter().filter(|c| &candidate_transfers_remainders[c] != top_entry).collect();
|
||||
let mut tied_top = cands_by_remainder.iter()
|
||||
.filter_map(|c| if let Some(c2) = c { if &candidate_transfers_remainders[c] == top_entry { Some(*c2) } else { None } } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Get top entries by tie-breaking method
|
||||
for _ in 0..(n_to_round-top_remainders.len()) {
|
||||
let cand = super::choose_highest(state, opts, &tied_top, "Which fraction to round up?")?;
|
||||
tied_top.remove(tied_top.iter().position(|c| *c == cand).unwrap());
|
||||
top_remainders.push(cands_by_remainder.iter().find(|c| **c == Some(cand)).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
// Round up top remainders
|
||||
|
|
|
@ -309,7 +309,7 @@ impl STVOptions {
|
|||
// Reporting
|
||||
|
||||
/// Generate the lead-in description of the count in HTML
|
||||
fn describe_count<N: Number>(filename: String, election: &Election<N>, opts: &stv::STVOptions) -> String {
|
||||
pub fn describe_count<N: Number>(filename: String, election: &Election<N>, opts: &stv::STVOptions) -> String {
|
||||
let mut result = String::from("<p>Count computed by OpenTally (revision ");
|
||||
result.push_str(crate::VERSION);
|
||||
let total_ballots = election.ballots.iter().fold(N::zero(), |acc, b| { acc + &b.orig_value });
|
||||
|
@ -326,7 +326,7 @@ fn describe_count<N: Number>(filename: String, election: &Election<N>, opts: &st
|
|||
}
|
||||
|
||||
/// Generate the first column of the HTML results table
|
||||
fn init_results_table<N: Number>(election: &Election<N>, opts: &stv::STVOptions, report_style: &str) -> String {
|
||||
pub fn init_results_table<N: Number>(election: &Election<N>, opts: &stv::STVOptions, report_style: &str) -> String {
|
||||
let mut result = String::from(r#"<tr class="stage-no"><td rowspan="3"></td></tr><tr class="stage-kind"></tr><tr class="stage-comment"></tr>"#);
|
||||
|
||||
if report_style == "ballots_votes" {
|
||||
|
@ -361,7 +361,7 @@ fn init_results_table<N: Number>(election: &Election<N>, opts: &stv::STVOptions,
|
|||
}
|
||||
|
||||
/// Generate subsequent columns of the HTML results table
|
||||
fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>, opts: &stv::STVOptions, report_style: &str) -> Array {
|
||||
pub fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>, opts: &stv::STVOptions, report_style: &str) -> Array {
|
||||
let result = Array::new();
|
||||
|
||||
// Insert borders to left of new exclusions in Wright STV
|
||||
|
@ -621,7 +621,7 @@ fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>, opts
|
|||
}
|
||||
|
||||
/// Get the comment for the current stage
|
||||
fn update_stage_comments<N: Number>(state: &CountState<N>, stage_num: usize) -> String {
|
||||
pub fn update_stage_comments<N: Number>(state: &CountState<N>, stage_num: usize) -> String {
|
||||
let mut comments = state.logger.render().join(" ");
|
||||
if let Some(_) = state.transfer_table {
|
||||
comments.push_str(&format!(r##" <a href="#" class="detailedTransfersLink" onclick="viewDetailedTransfers({});return false;">[View detailed transfers]</a>"##, stage_num));
|
||||
|
@ -630,7 +630,7 @@ fn update_stage_comments<N: Number>(state: &CountState<N>, stage_num: usize) ->
|
|||
}
|
||||
|
||||
/// Generate the final column of the HTML results table
|
||||
fn finalise_results_table<N: Number>(state: &CountState<N>, report_style: &str) -> Array {
|
||||
pub fn finalise_results_table<N: Number>(state: &CountState<N>, report_style: &str) -> Array {
|
||||
let result = Array::new();
|
||||
|
||||
// Header rows
|
||||
|
@ -673,7 +673,7 @@ fn finalise_results_table<N: Number>(state: &CountState<N>, report_style: &str)
|
|||
}
|
||||
|
||||
/// Generate the final lead-out text summarising the result of the election
|
||||
fn final_result_summary<N: Number>(state: &CountState<N>, opts: &stv::STVOptions) -> String {
|
||||
pub fn final_result_summary<N: Number>(state: &CountState<N>, opts: &stv::STVOptions) -> String {
|
||||
let mut result = String::from("<p>Count complete. The winning candidates are, in order of election:</p><ol>");
|
||||
|
||||
let mut winners = Vec::new();
|
||||
|
|
Loading…
Reference in New Issue