Fix bugs
Fix bug excluding-by-value/source candidates with no votes Fix bug electing too many candidates if more reach the quota than vacancies remain Add regression test
This commit is contained in:
parent
e4bfe45f49
commit
260dee1bb5
|
@ -496,7 +496,9 @@ where
|
||||||
}
|
}
|
||||||
ExclusionMethod::ByValue => {
|
ExclusionMethod::ByValue => {
|
||||||
// Exclude by value
|
// Exclude by value
|
||||||
let excluded_with_votes: Vec<&&Candidate> = excluded_candidates.iter().filter(|c| !state.candidates[*c].finalised).collect();
|
let excluded_with_votes: Vec<&&Candidate> = excluded_candidates.iter()
|
||||||
|
.filter(|c| { let cc = &state.candidates[*c]; !cc.finalised && !cc.parcels.is_empty() })
|
||||||
|
.collect();
|
||||||
|
|
||||||
if excluded_with_votes.is_empty() {
|
if excluded_with_votes.is_empty() {
|
||||||
votes_remain = false;
|
votes_remain = false;
|
||||||
|
@ -554,7 +556,9 @@ where
|
||||||
}
|
}
|
||||||
ExclusionMethod::BySource => {
|
ExclusionMethod::BySource => {
|
||||||
// Exclude by source candidate
|
// Exclude by source candidate
|
||||||
let excluded_with_votes: Vec<&&Candidate> = excluded_candidates.iter().filter(|c| !state.candidates[*c].finalised).collect();
|
let excluded_with_votes: Vec<&&Candidate> = excluded_candidates.iter()
|
||||||
|
.filter(|c| { let cc = &state.candidates[*c]; !cc.finalised && !cc.parcels.is_empty() })
|
||||||
|
.collect();
|
||||||
|
|
||||||
if excluded_with_votes.is_empty() {
|
if excluded_with_votes.is_empty() {
|
||||||
votes_remain = false;
|
votes_remain = false;
|
||||||
|
|
|
@ -939,11 +939,12 @@ fn meets_vre<N: Number>(state: &CountState<N>, count_card: &CountCard<N>, opts:
|
||||||
///
|
///
|
||||||
/// Returns `true` if any candidates were elected.
|
/// Returns `true` if any candidates were elected.
|
||||||
fn elect_sure_winners<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions) -> Result<bool, STVError> {
|
fn elect_sure_winners<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions) -> Result<bool, STVError> {
|
||||||
let num_vacancies = state.election.seats - state.num_elected;
|
if state.num_elected >= state.election.seats {
|
||||||
if num_vacancies == 0 {
|
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let num_vacancies = state.election.seats - state.num_elected;
|
||||||
|
|
||||||
let mut hopefuls: Vec<(&Candidate, &CountCard<N>)> = state.election.candidates.iter()
|
let mut hopefuls: Vec<(&Candidate, &CountCard<N>)> = state.election.candidates.iter()
|
||||||
.map(|c| (c, &state.candidates[c]))
|
.map(|c| (c, &state.candidates[c]))
|
||||||
.filter(|(_, cc)| cc.state == CandidateState::Hopeful || cc.state == CandidateState::Guarded)
|
.filter(|(_, cc)| cc.state == CandidateState::Hopeful || cc.state == CandidateState::Guarded)
|
||||||
|
@ -1033,7 +1034,7 @@ fn elect_hopefuls<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOption
|
||||||
|
|
||||||
let elected = !cands_meeting_quota.is_empty();
|
let elected = !cands_meeting_quota.is_empty();
|
||||||
|
|
||||||
while !cands_meeting_quota.is_empty() {
|
while !cands_meeting_quota.is_empty() && state.num_elected < state.election.seats {
|
||||||
// Declare elected in descending order of votes
|
// Declare elected in descending order of votes
|
||||||
let max_cands = ties::multiple_max_by(&cands_meeting_quota, |c| &state.candidates[c].votes);
|
let max_cands = ties::multiple_max_by(&cands_meeting_quota, |c| &state.candidates[c].votes);
|
||||||
let candidate = if max_cands.len() > 1 {
|
let candidate = if max_cands.len() > 1 {
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Comment: BY STV
|
||||||
|
# Comment: Number of truncated papers: 0
|
||||||
|
# Comment: AKA R038 in Wichmann's database
|
||||||
|
# Source: Nicolaus Tideman via Warren D Smith <https://rangevoting.org/TidemanData.html>
|
||||||
|
# Contributor: RunasSudo
|
||||||
|
18 3
|
||||||
|
1 15 9 5 6 8 0
|
||||||
|
1 18 7 16 1 12 14 0
|
||||||
|
1 14 9 17 13 15 5 2 12 16 8 0
|
||||||
|
1 6 5 3 4 15 12 9 7 1 14 13 16 18 17 8 2 10 11 0
|
||||||
|
1 12 1 17 9 6 5 2 0
|
||||||
|
1 15 8 5 3 0
|
||||||
|
1 8 15 2 3 5 4 1 9 0
|
||||||
|
1 3 5 1 4 11 2 8 0
|
||||||
|
1 17 4 9 14 16 6 1 13 8 15 3 18 5 12 11 10 2 7 0
|
||||||
|
0
|
||||||
|
"A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "f:a33"
|
|
@ -17,12 +17,13 @@
|
||||||
|
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use opentally::election::{CountState, Election};
|
use opentally::election::{CandidateState, CountState, Election};
|
||||||
use opentally::numbers::Rational;
|
use opentally::numbers::Rational;
|
||||||
use opentally::parser::blt;
|
use opentally::parser::blt;
|
||||||
use opentally::stv;
|
use opentally::stv;
|
||||||
use opentally::ties::TieStrategy;
|
use opentally::ties::TieStrategy;
|
||||||
|
|
||||||
|
/// Insufficient candidates to fill all vacancies
|
||||||
#[test]
|
#[test]
|
||||||
fn insufficient_candidates1() {
|
fn insufficient_candidates1() {
|
||||||
let stv_opts = stv::STVOptionsBuilder::default()
|
let stv_opts = stv::STVOptionsBuilder::default()
|
||||||
|
@ -48,6 +49,7 @@ fn insufficient_candidates1() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// After bulk exclusion of candidates with no votes, insufficient candidates remain to fill all vacancies
|
||||||
#[test]
|
#[test]
|
||||||
fn insufficient_candidates2() {
|
fn insufficient_candidates2() {
|
||||||
let stv_opts = stv::STVOptionsBuilder::default()
|
let stv_opts = stv::STVOptionsBuilder::default()
|
||||||
|
@ -72,3 +74,42 @@ fn insufficient_candidates2() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tideman A33 election: exclusion of candidate with no votes; more candidates reach the quota than vacancies
|
||||||
|
#[test]
|
||||||
|
fn tideman_a33_ers97_rational() {
|
||||||
|
let stv_opts = stv::STVOptionsBuilder::default()
|
||||||
|
.round_surplus_fractions(Some(2))
|
||||||
|
.round_values(Some(2))
|
||||||
|
.round_votes(Some(2))
|
||||||
|
.round_quota(Some(2))
|
||||||
|
.quota(stv::QuotaType::DroopExact)
|
||||||
|
.quota_criterion(stv::QuotaCriterion::GreaterOrEqual)
|
||||||
|
.quota_mode(stv::QuotaMode::ERS97)
|
||||||
|
.ties(vec![TieStrategy::Random(String::new("20210908"))])
|
||||||
|
.surplus(stv::SurplusMethod::EG)
|
||||||
|
.transferable_only(true)
|
||||||
|
.exclusion(stv::ExclusionMethod::ByValue)
|
||||||
|
.early_bulk_elect(false)
|
||||||
|
.bulk_exclude(true)
|
||||||
|
.defer_surpluses(true)
|
||||||
|
.build().unwrap();
|
||||||
|
|
||||||
|
let mut election: Election<Rational> = blt::parse_path("tests/data/A33.blt").expect("Syntax Error");
|
||||||
|
stv::preprocess_election(&mut election, &stv_opts);
|
||||||
|
|
||||||
|
let mut state = CountState::new(&election);
|
||||||
|
|
||||||
|
stv::count_init(&mut state, &stv_opts).unwrap();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let result = stv::count_one_stage::<Rational>(&mut state, &stv_opts);
|
||||||
|
match result {
|
||||||
|
Ok(done) => { if done { break; } }
|
||||||
|
Err(err) => { panic!("{}", err); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let num_winners = state.candidates.values().filter(|cc| cc.state == CandidateState::Elected).count();
|
||||||
|
assert_eq!(num_winners, 3);
|
||||||
|
}
|
Loading…
Reference in New Issue