Implement Dáil Éireann STV

This commit is contained in:
RunasSudo 2021-09-14 22:47:35 +10:00
parent a641b97d1f
commit 3a4e53e1f0
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
13 changed files with 19529 additions and 52 deletions

View File

@ -17,11 +17,12 @@ The preset dropdown allows you to choose from a hardcoded list of preloaded STV
| Australian Capital Territory STV | Rules from the [*Electoral Act 1992* (ACT)](https://www.legislation.act.gov.au/View/a/1992-71/current/PDF/1992-71.PDF), using the exclusive Gregory method. | | ✓ |
| Minneapolis STV | Rules from chapter 167 of the [*Minneapolis Code of Ordinances*](https://library.municode.com/mn/minneapolis/codes/code_of_ordinances?nodeId=COOR_TIT8.5EL_CH167MUELRUCO), using the weighted inclusive Gregory method. | [E5] | ✓ |
| Cambridge STV | Rules in force in Cambridge, Massachusetts, using random sample transfers. These rules are derived from the [former chapter 54A of the Massachusetts General Laws](https://www.cambridgema.gov/-/media/Files/electioncommission/massachusettsgenerallawschapter54a.pdf), but have by regulation been modified to incorporate the procedures set out in Article IX of the former [1938 Charter of the City of Cincinnati](https://catalog.hathitrust.org/Record/001754258). See also [here](https://web.archive.org/web/20081118104049/http://www.fairvote.org/media/1993countmanual.pdf). | | ✓ |
| Dáil Éireann STV | Rules from the [*Electoral Act 1992* (Ireland)](http://www.irishstatutebook.ie/eli/1992/act/23/enacted/en/print), using stratified random sample transfers. | [E4] [E6] | ✓ |
| [Wright STV](https://www.aph.gov.au/Parliamentary_Business/Committees/House_of_Representatives_Committees?url=em/elect07/subs/sub051.1.pdf) | Rules proposed by Anthony van der Craats designed for computer counting, involving reset and re-iteration of the count after each candidate exclusion. | | ✓ |
| [PRSA 1977](https://www.prsa.org.au/rule1977.htm) | Simple rules designed for hand counting, using the exclusive Gregory method, with counting performed in thousandths of a vote. | | ✓ |
| [ERS97](https://www.electoral-reform.org.uk/latest-news-and-research/publications/how-to-conduct-an-election-by-the-single-transferable-vote-3rd-edition/) | More complex rules designed for hand counting, using the exclusive Gregory method. | [E6] | ✓ |
| • ERS76 | Former rules from the 1976 2nd edition. | [E6] [E7] | ✓ |
| • ERS73 | Former rules from the 1973 1st edition. | [E6] [E7] | |
| [ERS97](https://www.electoral-reform.org.uk/latest-news-and-research/publications/how-to-conduct-an-election-by-the-single-transferable-vote-3rd-edition/) | More complex rules designed for hand counting, using the exclusive Gregory method. | [E7] | ✓ |
| • ERS76 | Former rules from the 1976 2nd edition. | [E7] [E8] | ✓ |
| • ERS73 | Former rules from the 1973 1st edition. | [E7] [E8] | |
| Church of England | Rules from the Church of England [*Single Transferable Vote Rules 2020*](https://www.churchofengland.org/sites/default/files/2020-02/STV%20Rules%202020%20-%20final.pdf), similar to ERS73. | | ✓ |
Exceptions:
@ -29,10 +30,11 @@ Exceptions:
* [E1] When generating random numbers, OpenTally uses a [deterministic random number generator based on SHA-256](rng.md), rather than the WichmannHill(-based) algorithm.
* [E2] When breaking ties backwards, OpenTally selects the candidate who had more/fewer votes at the last stage when *any* tied candidate had more/fewer votes, rather than the method described in the legislation (when each all had unequal votes). The OpenTally developers regard the method described in the legislation as a defect. For an independent discussion, see <a href="https://dl.acm.org/doi/10.1145/3014812.3014837">Conway et al.</a>
* [E3] A tie between 2 candidates for the final vacancy will be broken backwards then at random, rather than the method described in the legislation.
* [E4] Bulk exclusion is not performed. See the section on *Bulk exclusion* for further discussion.
* [E4] Bulk exclusion is not performed, as the prescribed rules are more conservative than OpenTally's. See also the section on *Bulk exclusion* for further discussion.
* [E5] The mathematically eliminated by the sum of all ranked-choice votes comparison is not implemented.
* [E6] No distinction is made between stages and substages (during exclusion). This affects only the numbering of stages and not the result.
* [E7] By default, the quota is always calculated to 2 decimal places. For full ERS76 (ERS73) compliance, set *Round quota to 0 d.p.* when the quota is more than 100 (100 or more).
* [E6] The quarter of a quota provision is disregarded when determining whether to defer surplus distributions.
* [E7] No distinction is made between stages and substages (during exclusion). This affects only the numbering of stages and not the result.
* [E8] By default, the quota is always calculated to 2 decimal places. For full ERS76 (ERS73) compliance, set *Round quota to 0 d.p.* when the quota is more than 100 (100 or more).
For details of validation, see [validation.md](validation.md).
@ -126,7 +128,8 @@ When *Surplus method* is set to *Meek method*, this option controls how candidat
When *Surplus method* is set to a random sample method, this option controls which subset of ballot papers is selected for transfer during surplus distributions:
* *Stratified (then by order)* (default): The candidate's ballot papers are first stratified into subparcels according to next available preference. From each subparcel, the subset transferred comprises the ballot papers most recently received by the candidate.
* *Stratify (LR)* (default): The candidate's ballot papers are first stratified into subparcels according to next available preference, and an equal proportion of each subparcel is transferred, with the subset transferred comprising the ballot papers in each subparcel most recently received by the candidate. In the calculation of proportions, the largest remainders are rounded up so there is no loss by fraction. This is the method specified by the [*Electoral Act 1992* (Ireland)](http://www.irishstatutebook.ie/eli/1992/act/23/section/121/enacted/en/html#sec121).
* *Stratify (floor)*: The same as *Stratify (LR)*, except in the calculation of proportions, all remainders are disregarded, and the difference is lost by fraction.
* *By order*: The subset transferred comprises the ballot papers most recently received by the candidate.
* *Every n-th ballot*: The subset is selected using the deterministic method used in [Cambridge, Massachusetts](https://web.archive.org/web/20081118104049/http://www.fairvote.org/media/1993countmanual.pdf) (derived from Article IX of the former 1938 Cincinnati *Code of Ordinances*).

View File

@ -14,6 +14,7 @@ STV-counting software is frequently validated empirically by comparing the resul
| Minneapolis STV | [2009 Minneapolis Board of Estimate and Taxation election](https://vote.minneapolismn.gov/results-data/election-results/2009/bet/) | Results sheet (official) | ✓ |
| Minneapolis STV | [2013 Minneapolis Parks & Recreation Commissioner At Large election](https://vote.minneapolismn.gov/results-data/election-results/2013/park-board-at-large/) | Results sheet (official) | ✓ |
| Cambridge STV | [2003 Cambridge City Council election](https://web.archive.org/web/20070204083508/http://stv.sourceforge.net/) | OpenSTV 1.7, [ChoicePlus Pro 2.1](https://www.votingsolutions.com/cpdetail.htm) (official) | ✓ |
| Dáil Éireann STV | [2002 Dublin North election](https://electionsireland.org/counts.cfm?election=2002&cons=96) | Results sheet (official) | ✓ |
| Wright STV | [EVE Online CSM 15 election](https://www.eveonline.com/news/view/meet-the-new-council) | [ccp-wright-stv](https://github.com/ccpgames/ccp-wright-stv) (official) | ✓ |
| PRSA 1977 | [*Proportional Representation Manual*](https://www.prsa.org.au/publicat.htm#p2) [example 1](https://www.prsa.org.au/utopiatc.pdf) | [Model result](https://www.prsa.org.au/example1.pdf) (official) | ✓ |
| PRSA 1977 | 40 elections from [stvdb](https://gitlab.com/RunasSudo/stvdb) | [count.nl (RunasSudo version)](https://gitlab.com/RunasSudo/prsa_count) | ✓ |

View File

@ -47,6 +47,7 @@
<option value="meeknz">Meek STV (New Zealand)</option>
<option value="minneapolis">Minneapolis STV</option>
<option value="cambridge">Cambridge STV</option>
<option value="dail">Dáil Éireann STV</option>
</optgroup>
<optgroup label="Hand-count">
<option value="prsa77">PRSA 1977</option>
@ -148,7 +149,8 @@
<span class="pill-grey" title="This option has effect only if “Method” is set to a random sample method">Sample</span>
Sample method:
<select id="selSample">
<option value="stratified" selected>Stratified (then by order)</option>
<option value="stratify_lr" selected>Stratify (LR)</option>
<option value="stratify_floor" selected>Stratify (floor)</option>
<option value="by_order">By order</option>
<option value="nth_ballot">Every n-th ballot</option>
</select>

View File

@ -678,6 +678,28 @@ function changePreset() {
document.getElementById('selPapers').value = 'transferable';
document.getElementById('selExclusion').value = 'single_stage';
document.getElementById('selTies').value = 'backwards,random';
} else if (document.getElementById('selPreset').value === 'dail') {
document.getElementById('selQuotaCriterion').value = 'geq';
document.getElementById('selQuota').value = 'droop';
document.getElementById('selQuotaMode').value = 'static';
document.getElementById('chkBulkElection').checked = true;
document.getElementById('chkBulkExclusion').checked = false;
document.getElementById('chkDeferSurpluses').checked = true;
document.getElementById('chkImmediateElect').checked = true;
document.getElementById('selSample').value = 'stratify_lr';
document.getElementById('chkSamplePerBallot').checked = false;
document.getElementById('txtMinThreshold').value = '0';
document.getElementById('selNumbers').value = 'rational';
document.getElementById('txtPPDP').value = '0';
document.getElementById('chkNormaliseBallots').checked = true;
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '0';
document.getElementById('selSumTransfers').value = 'by_value';
document.getElementById('selSurplus').value = 'by_order';
document.getElementById('selMethod').value = 'hare';
document.getElementById('selPapers').value = 'transferable';
document.getElementById('selExclusion').value = 'single_stage';
document.getElementById('selTies').value = 'forwards,random';
} else if (document.getElementById('selPreset').value === 'wright') {
document.getElementById('selQuotaCriterion').value = 'geq';
document.getElementById('selQuota').value = 'droop';

View File

@ -134,7 +134,7 @@ pub struct SubcmdOptions {
meek_nz_exclusion: bool,
/// (Cincinnati/Hare) Method of drawing a sample
#[clap(help_heading=Some("STV VARIANTS"), long, possible_values=&["stratified", "by_order", "nth_ballot"], default_value="stratified", value_name="method")]
#[clap(help_heading=Some("STV VARIANTS"), long, possible_values=&["stratify_lr", "stratify_floor", "by_order", "nth_ballot"], default_value="stratify_lr", value_name="method")]
sample: String,
/// (Cincinnati/Hare) Sample-based methods: Check for candidate election after each individual ballot paper transfer

View File

@ -71,8 +71,8 @@ impl Number for Rational {
factor.pow_assign(-(dps as i32));
factor /= Self::from(2);
*self = self.clone() - factor;
self.ceil_mut(dps);
*self = self.clone() + factor;
self.floor_mut(dps);
}
}
@ -179,9 +179,7 @@ impl ops::Add for Rational {
impl ops::Sub for Rational {
type Output = Rational;
fn sub(self, _rhs: Self) -> Self::Output {
todo!()
}
fn sub(self, rhs: Self) -> Self::Output { Self(self.0 - rhs.0) }
}
impl ops::Mul for Rational {
@ -196,9 +194,7 @@ impl ops::Div for Rational {
impl ops::Rem for Rational {
type Output = Rational;
fn rem(self, _rhs: Self) -> Self::Output {
todo!()
}
fn rem(self, rhs: Self) -> Self::Output { Self(self.0 % rhs.0) }
}
impl ops::Add<&Rational> for Rational {
@ -223,9 +219,7 @@ impl ops::Div<&Rational> for Rational {
impl ops::Rem<&Rational> for Rational {
type Output = Rational;
fn rem(self, _rhs: &Rational) -> Self::Output {
todo!()
}
fn rem(self, rhs: &Rational) -> Self::Output { Rational(self.0 % &rhs.0) }
}
impl ops::AddAssign for Rational {
@ -245,9 +239,7 @@ impl ops::DivAssign for Rational {
}
impl ops::RemAssign for Rational {
fn rem_assign(&mut self, _rhs: Self) {
todo!()
}
fn rem_assign(&mut self, rhs: Self) { self.0 %= &rhs.0; }
}
impl ops::AddAssign<&Rational> for Rational {
@ -263,13 +255,11 @@ impl ops::MulAssign<&Rational> for Rational {
}
impl ops::DivAssign<&Rational> for Rational {
fn div_assign(&mut self, rhs: &Rational) { self.0 /= &rhs.0 }
fn div_assign(&mut self, rhs: &Rational) { self.0 /= &rhs.0; }
}
impl ops::RemAssign<&Rational> for Rational {
fn rem_assign(&mut self, _rhs: &Rational) {
todo!()
}
fn rem_assign(&mut self, rhs: &Rational) { self.0 %= &rhs.0; }
}
impl ops::Neg for &Rational {
@ -299,9 +289,7 @@ impl ops::Div<Self> for &Rational {
impl ops::Rem<Self> for &Rational {
type Output = Rational;
fn rem(self, _rhs: &Rational) -> Self::Output {
todo!()
}
fn rem(self, rhs: &Rational) -> Self::Output { Rational(&self.0 % &rhs.0) }
}
/*

View File

@ -112,7 +112,7 @@ pub struct STVOptions {
pub meek_nz_exclusion: bool,
/// (Cincinnati/Hare) Method of drawing a sample
#[builder(default="SampleMethod::Stratified")]
#[builder(default="SampleMethod::StratifyLR")]
pub sample: SampleMethod,
/// (Cincinnati/Hare) Sample-based methods: Check for candidate election after each individual ballot paper transfer
@ -191,7 +191,7 @@ impl STVOptions {
if self.exclusion != ExclusionMethod::SingleStage { flags.push(self.exclusion.describe()); }
}
if self.surplus == SurplusMethod::Meek && self.meek_nz_exclusion { flags.push("--meek-nz-exclusion".to_string()); }
if (self.surplus == SurplusMethod::Cincinnati || self.surplus == SurplusMethod::Hare) && self.sample != SampleMethod::Stratified { flags.push(self.sample.describe()); }
if (self.surplus == SurplusMethod::Cincinnati || self.surplus == SurplusMethod::Hare) && self.sample != SampleMethod::StratifyLR { flags.push(self.sample.describe()); }
if (self.surplus == SurplusMethod::Cincinnati || self.surplus == SurplusMethod::Hare) && self.sample_per_ballot { flags.push("--sample-per-ballot".to_string()); }
if !self.early_bulk_elect { flags.push("--no-early-bulk-elect".to_string()); }
if self.bulk_exclude { flags.push("--bulk-exclude".to_string()); }
@ -219,7 +219,9 @@ impl STVOptions {
if self.surplus == SurplusMethod::Cincinnati || self.surplus == SurplusMethod::Hare {
if self.round_quota != Some(0) { return Err(STVError::InvalidOptions("--surplus cincinnati and --surplus hare require --round-quota 0")); }
if !self.normalise_ballots { return Err(STVError::InvalidOptions("--surplus cincinnati and --surplus hare require --normalise-ballots")); }
if self.sample == SampleMethod::Stratified && self.sample_per_ballot { return Err(STVError::InvalidOptions("--sample stratified is incompatible with --sample-per-ballot")); }
if self.sample == SampleMethod::StratifyLR && self.sample_per_ballot { return Err(STVError::InvalidOptions("--sample stratify_lr is incompatible with --sample-per-ballot")); }
if self.sample == SampleMethod::StratifyFloor && self.sample_per_ballot { return Err(STVError::InvalidOptions("--sample stratify_floor is incompatible with --sample-per-ballot")); }
if self.sample == SampleMethod::StratifyLR && self.round_surplus_fractions.is_some() { return Err(STVError::InvalidOptions("--sample stratify_lr is incompatible with --round-surplus-fractions")); }
if self.sample_per_ballot && !self.immediate_elect { return Err(STVError::InvalidOptions("--sample-per-ballot is incompatible with --no-immediate-elect")); }
}
if self.min_threshold != "0" && self.defer_surpluses { return Err(STVError::InvalidOptions("--min-threshold is incompatible with --defer-surpluses")); } // TODO: Permit this
@ -506,8 +508,10 @@ impl<S: AsRef<str>> From<S> for ExclusionMethod {
#[derive(Clone, Copy)]
#[derive(PartialEq)]
pub enum SampleMethod {
/// Stratify the ballots into parcels according to next available preference and transfer the last ballots from each parcel
Stratified,
/// Stratify the ballots into parcels according to next available preference and transfer the last ballots from each parcel; round fractions according to largest remainders
StratifyLR,
/// Stratify the ballots into parcels according to next available preference and transfer the last ballots from each parcel; disregard fractions
StratifyFloor,
/// Transfer the last ballots
ByOrder,
/// Transfer every n-th ballot, Cincinnati style
@ -518,7 +522,8 @@ impl SampleMethod {
/// Convert to CLI argument representation
fn describe(self) -> String {
match self {
SampleMethod::Stratified => "--sample stratified",
SampleMethod::StratifyLR => "--sample stratify_lr",
SampleMethod::StratifyFloor => "--sample stratify_floor",
SampleMethod::ByOrder => "--sample by_order",
SampleMethod::NthBallot => "--sample nth_ballot",
}.to_string()
@ -528,7 +533,8 @@ impl SampleMethod {
impl<S: AsRef<str>> From<S> for SampleMethod {
fn from(s: S) -> Self {
match s.as_ref() {
"stratified" => SampleMethod::Stratified,
"stratify_lr" => SampleMethod::StratifyLR,
"stratify_floor" => SampleMethod::StratifyFloor,
"by_order" => SampleMethod::ByOrder,
"nth_ballot" => SampleMethod::NthBallot,
_ => panic!("Invalid --sample-method"),

View File

@ -48,6 +48,7 @@ where
pub fn distribute_surplus<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, elected_candidate: &'a Candidate) -> Result<(), STVError>
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>,
for<'r> &'r N: ops::Neg<Output=N>
{
@ -72,16 +73,14 @@ where
}
match opts.sample {
SampleMethod::Stratified => {
// Stratified by next available preference
// FIXME: This is untested
SampleMethod::StratifyLR | SampleMethod::StratifyFloor => {
// Stratified by next available preference (round fractions according to largest remainders)
let result = super::next_preferences(state, votes);
let transferable_ballots = &result.total_ballots - &result.exhausted.num_ballots;
let surplus_denom = calculate_surplus_denom(&surplus, &result, &transferable_ballots, opts.transferable_only);
let mut surplus_fraction;
match surplus_denom {
match &surplus_denom {
Some(v) => {
surplus_fraction = Some(surplus.clone() / v);
@ -116,22 +115,107 @@ where
}
}
let mut checksum = N::new();
let mut candidate_transfers_remainders: HashMap<Option<&Candidate>, (N, N)> = HashMap::new(); // None -> exhausted pile
for (candidate, entry) in result.candidates.into_iter() {
// Credit transferred votes
for (candidate, entry) in result.candidates.iter() {
// Calculate votes to transfer
let mut candidate_transfers;
let remainder;
match surplus_fraction {
Some(ref f) => {
candidate_transfers = entry.num_ballots * f;
candidate_transfers.floor_mut(0);
match opts.sample {
SampleMethod::StratifyLR => {
// Incompatible with --round-surplus-fractions
candidate_transfers = &entry.num_ballots * &surplus / surplus_denom.as_ref().unwrap();
candidate_transfers.floor_mut(0);
remainder = (&entry.num_ballots * &surplus) % surplus_denom.as_ref().unwrap();
}
SampleMethod::StratifyFloor => {
match opts.round_surplus_fractions {
Some(_) => {
candidate_transfers = &entry.num_ballots * f;
}
None => {
candidate_transfers = &entry.num_ballots * &surplus / surplus_denom.as_ref().unwrap();
}
}
candidate_transfers.floor_mut(0);
remainder = N::new();
}
_ => unreachable!()
}
}
None => {
// All ballots transferred
candidate_transfers = entry.num_ballots.clone();
remainder = N::new();
}
}
let candidate_transfers_usize: usize = format!("{:.0}", candidate_transfers).parse().expect("Transfers overflow usize");
candidate_transfers_remainders.insert(Some(candidate), (candidate_transfers, remainder));
}
// Calculate exhausted votes to transfer
if !opts.transferable_only {
let mut exhausted_transfers;
let remainder;
match opts.sample {
SampleMethod::StratifyLR => {
// Incompatible with --round-surplus-fractions
exhausted_transfers = &result.exhausted.num_ballots * &surplus / surplus_denom.as_ref().unwrap();
exhausted_transfers.floor_mut(0);
remainder = (&result.exhausted.num_ballots * &surplus) % surplus_denom.as_ref().unwrap();
}
SampleMethod::StratifyFloor => {
match opts.round_surplus_fractions {
Some(_) => {
exhausted_transfers = &result.exhausted.num_ballots * surplus_fraction.as_ref().unwrap();
}
None => {
exhausted_transfers = &result.exhausted.num_ballots * &surplus / surplus_denom.as_ref().unwrap();
}
}
exhausted_transfers.floor_mut(0);
remainder = N::new();
}
_ => unreachable!()
}
candidate_transfers_remainders.insert(None, (exhausted_transfers, remainder));
}
if opts.sample == SampleMethod::StratifyLR {
// 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() {
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();
// 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));
// Select top remainders
let top_remainders: Vec<&Option<&Candidate>> = cands_by_remainder.iter().take(n_to_round).collect();
// 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");
}
// Round up top remainders
for candidate in top_remainders {
candidate_transfers_remainders.get_mut(candidate).unwrap().0 += N::one();
}
}
}
let mut checksum = N::new();
for (candidate, entry) in result.candidates.into_iter() {
// Credit transferred votes
let candidate_transfers = &candidate_transfers_remainders[&Some(candidate)].0;
let candidate_transfers_usize: usize = format!("{:.0}", candidate_transfers).parse().expect("Transfer overflows usize");
let count_card = state.candidates.get_mut(candidate).unwrap();
count_card.transfer(&candidate_transfers);
@ -154,16 +238,14 @@ where
exhausted_transfers = N::new();
} else {
exhausted_transfers = &surplus - &transferable_ballots;
if let Some(dps) = opts.round_votes {
exhausted_transfers.floor_mut(dps);
}
}
} else {
exhausted_transfers = result.exhausted.num_ballots * surplus_fraction.as_ref().unwrap();
exhausted_transfers.floor_mut(0);
exhausted_transfers = candidate_transfers_remainders.remove(&None).unwrap().0;
}
let exhausted_transfers_usize: usize = format!("{:.0}", exhausted_transfers).parse().expect("Transfers overflow usize");
let exhausted_transfers_usize: usize = format!("{:.0}", exhausted_transfers).parse().expect("Transfer overflows usize");
state.exhausted.transfer(&exhausted_transfers);
checksum += exhausted_transfers;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
Stage:,1,,2,,3,,4,,5,,6,,7,,8,,9,
Comment:,First preferences,,,,,,Exclusion of Ciaran Goulding,,Exclusion of Cathal Boland,,Exclusion of Mick Davis,,Exclusion of Nora Owen,,Surplus of Trevor Sargent,,Exclusion of Michael Kennedy,
Clare Daly,5502,H,,,5552,H,5731,H,5797,H,6245,H,6591,H,6773,H,,
Mick Davis,1350,H,,,1382,H,1424,H,1440,H,0,EX,0,EX,0,EX,0,EX
Jim Glennon,5892,H,,,5945,H,6028,H,6152,H,6294,H,6511,H,6596,H,,
Ciaran Goulding,914,H,,,1009,H,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
Michael Kennedy,5253,H,,,5309,H,5368,H,5422,H,5532,H,5732,H,5801,H,0,EX
Nora Owen,4012,H,,,4030,H,4132,H,4720,H,4763,H,0,EX,0,EX,0,EX
Eamon Quinn,285,H,,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
Seán Ryan,6359,H,,,6407,H,6535,H,6665,H,6847,H,8578,H,9128,EL,9128,EL
Trevor Sargent,7294,H,,,7380,H,7678,H,7818,H,8118,H,9785,EL,8789,EL,8789,EL
David Walshe,247,H,,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX
G V Wright,5658,H,,,5707,H,5739,H,5777,H,5868,H,6139,H,6249,H,,
Cathal Boland,1177,H,,,1189,H,1216,H,0,EX,0,EX,0,EX,0,EX,0,EX
Non-transferable,0,,,,33,,92,,152,,276,,607,,607,,,
1 Stage: 1 2 3 4 5 6 7 8 9
2 Comment: First preferences Exclusion of Ciaran Goulding Exclusion of Cathal Boland Exclusion of Mick Davis Exclusion of Nora Owen Surplus of Trevor Sargent Exclusion of Michael Kennedy
3 Clare Daly 5502 H 5552 H 5731 H 5797 H 6245 H 6591 H 6773 H
4 Mick Davis 1350 H 1382 H 1424 H 1440 H 0 EX 0 EX 0 EX 0 EX
5 Jim Glennon 5892 H 5945 H 6028 H 6152 H 6294 H 6511 H 6596 H
6 Ciaran Goulding 914 H 1009 H 0 EX 0 EX 0 EX 0 EX 0 EX 0 EX
7 Michael Kennedy 5253 H 5309 H 5368 H 5422 H 5532 H 5732 H 5801 H 0 EX
8 Nora Owen 4012 H 4030 H 4132 H 4720 H 4763 H 0 EX 0 EX 0 EX
9 Eamon Quinn 285 H 0 EX 0 EX 0 EX 0 EX 0 EX 0 EX 0 EX
10 Seán Ryan 6359 H 6407 H 6535 H 6665 H 6847 H 8578 H 9128 EL 9128 EL
11 Trevor Sargent 7294 H 7380 H 7678 H 7818 H 8118 H 9785 EL 8789 EL 8789 EL
12 David Walshe 247 H 0 EX 0 EX 0 EX 0 EX 0 EX 0 EX 0 EX
13 G V Wright 5658 H 5707 H 5739 H 5777 H 5868 H 6139 H 6249 H
14 Cathal Boland 1177 H 1189 H 1216 H 0 EX 0 EX 0 EX 0 EX 0 EX
15 Non-transferable 0 33 92 152 276 607 607

Binary file not shown.

38
tests/tests_impl/dail.rs Normal file
View File

@ -0,0 +1,38 @@
/* OpenTally: Open-source election vote counting
* Copyright © 2021 Lee Yingtong Li (RunasSudo)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use crate::utils;
use opentally::numbers::Rational;
use opentally::stv;
#[test]
fn dublin_north_2002_rational() {
let stv_opts = stv::STVOptionsBuilder::default()
.round_quota(Some(0))
.normalise_ballots(true)
.quota_criterion(stv::QuotaCriterion::GreaterOrEqual)
.surplus(stv::SurplusMethod::Hare)
.surplus_order(stv::SurplusOrder::ByOrder)
.transferable_only(true)
.defer_surpluses(true)
.build().unwrap();
assert_eq!(stv_opts.describe::<Rational>(), "--round-quota 0 --normalise-ballots --quota-criterion geq --surplus hare --surplus-order by_order --transferable-only --defer-surpluses");
utils::read_validate_election::<Rational>("tests/data/DublinNorthSorted.csv", "tests/data/DublinNorthSorted.blt", stv_opts, None, &["exhausted"]);
}

View File

@ -23,6 +23,7 @@ mod coe;
mod constraints;
mod convert;
mod csm;
mod dail;
mod equal_ranks;
mod ers;
mod meek;