Update documentation
This commit is contained in:
parent
4ebb6474fd
commit
8829fa5a7b
|
@ -15,6 +15,7 @@ OpenTally accepts data in the [BLT file format](https://yingtongli.me/git/OpenTa
|
|||
* weighted inclusive Gregory STV (e.g. [Scottish STV](https://www.legislation.gov.uk/ssi/2011/399/schedule/1/made))
|
||||
* unweighted inclusive Gregory STV (e.g. [Australian Senate STV](https://www.legislation.gov.au/Details/C2020C00400/Html/Text#_Toc59107700))
|
||||
* exclusive Gregory STV (e.g. [PRSA 1977](https://www.prsa.org.au/rule1977.htm) and [ERS97](https://www.electoral-reform.org.uk/latest-news-and-research/publications/how-to-conduct-an-election-by-the-single-transferable-vote-3rd-edition/))
|
||||
* [Meek STV](http://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf) – with [tree-packed ballots](http://www.votingmatters.org.uk/ISSUE21/I21P1.pdf) for efficient computation
|
||||
|
||||
OpenTally is highly customisable, including options for:
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ The preset dropdown allows you to choose from a hardcoded list of preloaded STV
|
|||
|
||||
* *Recommended WIGM*: A recommended set of simple STV rules designed for computer counting, using the weighted inclusive Gregory method and rational arithmetic.
|
||||
* *Scottish STV*: Rules from the [*Scottish Local Government Elections Order 2011*](https://www.legislation.gov.uk/ssi/2011/399/schedule/1/made), using the weighted inclusive Gregory method. Validated against the [2007 Scottish local government election result for Linn ward](https://web.archive.org/web/20121004213938/http://www.glasgow.gov.uk/en/YourCouncil/Elections_Voting/Election_Results/ElectionScotland2007/LGWardResults.htm?ward=1&wardname=1%20-%20Linn).
|
||||
* [*Meek STV*](http://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf): Advanced STV rules designed for computer counting, recognised by the Proportional Representation Society of Australia (Victoria–Tasmania) as the superior STV system. Validated against the [Hill–Wichmann–Woodall implementation](https://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf) for the ERS97 model election (see below).
|
||||
* *Australian Senate STV*: Rules from the [*Commonwealth Electoral Act 1918*](https://www.legislation.gov.au/Details/C2020C00400/Html/Text#_Toc59107700), using the unweighted inclusive Gregory method. Validated against the [2019 Australian Senate election result for Tasmania](https://results.aec.gov.au/24310/Website/SenateDownloadsMenu-24310-Csv.htm).
|
||||
* [*PRSA 1977*](https://www.prsa.org.au/rule1977.htm): Simple rules designed for hand counting, using the exclusive Gregory method, with counting automatically performed in thousandths of a vote. Validated against [example 1](https://www.prsa.org.au/example1.pdf) of the PRSA's [*Proportional Representation Manual*](https://www.prsa.org.au/publicat.htm#p2).
|
||||
* [*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. Validated against the ERS97 [model election](https://www.electoral-reform.org.uk/latest-news-and-research/publications/how-to-conduct-an-election-by-the-single-transferable-vote-3rd-edition/#sub-section-24).
|
||||
|
@ -40,6 +41,8 @@ This option allows you to specify whether the votes required for election can ch
|
|||
* *Static quota*: The quota is calculated once after all first-preference votes are allocated, and remains constant throughout the count.
|
||||
* *Static with ERS97 rules*: The quota is static, but candidates may be elected if their vote exceeds (or equals, according to the *Quota criterion*) the total active vote, divided by (*S* + 1) (or *S*, according to the *Quota* option).
|
||||
|
||||
When *Surplus method* is set to *Meek method*, this setting is ignored, and the progressively reducing quota of the Meek method is instead applied.
|
||||
|
||||
## STV variants
|
||||
|
||||
### Surplus order (--surplus-order)
|
||||
|
@ -56,7 +59,7 @@ Some STV counting rules provide, for example, that ‘no surplus shall be transf
|
|||
This dropdown allows you to select how ballots are transferred during surplus transfers. The recommended methods are:
|
||||
|
||||
* *Weighted inclusive Gregory* (default): During surplus transfers, all applicable ballot papers of the transferring candidate are examined. Transfers are weighted according to the weights of the ballot papers.
|
||||
* *Meek STV*: Transfers are computed as described at <http://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf>.
|
||||
* *Meek method*: Transfers are computed as described at <http://www.dia.govt.nz/diawebsite.NSF/Files/meekm/%24file/meekm.pdf>.
|
||||
|
||||
Other methods are supported, but not recommended:
|
||||
|
||||
|
@ -76,6 +79,8 @@ Other surplus transfer methods, such as non-fractional transfers (e.g. random sa
|
|||
* *Exclude by parcel (by order)*: When excluding a candidate, transfer their ballot papers one parcel at a time, in the order each was received. Each parcel forms a separate stage, i.e. if a transfer allows another candidate to meet the quota criterion, no further papers are transferred to that candidate. This option cannot be combined with bulk exclusion.
|
||||
* *Exclude by value*: When excluding candidate(s), transfer their ballot papers in descending order of accumulated transfer value. Each transfer of all ballots of a certain transfer value forms a separate stage.
|
||||
|
||||
When *Surplus method* is set to *Meek method*, this setting is ignored, and the Meek method is instead applied.
|
||||
|
||||
### Ties (-t/--ties)
|
||||
|
||||
This dropdown allows you to select how ties (in surplus transfer or exclusion) are broken. The options are:
|
||||
|
|
|
@ -23,10 +23,15 @@ use std::collections::HashMap;
|
|||
|
||||
/// An election to be counted
|
||||
pub struct Election<N> {
|
||||
/// Name of the election
|
||||
pub name: String,
|
||||
/// Number of candidates to be elected
|
||||
pub seats: usize,
|
||||
/// [Vec] of [Candidate]s in the election
|
||||
pub candidates: Vec<Candidate>,
|
||||
/// Indexes of withdrawn candidates
|
||||
pub withdrawn_candidates: Vec<usize>,
|
||||
/// [Vec] of [Ballot]s cast in the election
|
||||
pub ballots: Vec<Ballot<N>>,
|
||||
}
|
||||
|
||||
|
@ -125,32 +130,52 @@ impl<N: Number> Election<N> {
|
|||
/// A candidate in an [Election]
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct Candidate {
|
||||
/// Name of the candidate
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// The current state of counting an [Election]
|
||||
//#[derive(Clone)]
|
||||
pub struct CountState<'a, N: Number> {
|
||||
/// Pointer to the [Election] being counted
|
||||
pub election: &'a Election<N>,
|
||||
|
||||
/// [HashMap] of [CountCard]s for each [Candidate] in the election
|
||||
pub candidates: HashMap<&'a Candidate, CountCard<'a, N>>,
|
||||
/// [CountCard] representing the exhausted pile
|
||||
pub exhausted: CountCard<'a, N>,
|
||||
/// [CountCard] representing loss by fraction
|
||||
pub loss_fraction: CountCard<'a, N>,
|
||||
|
||||
/// [crate::stv::meek::BallotTree] for Meek STV
|
||||
pub ballot_tree: Option<crate::stv::meek::BallotTree<'a, N>>,
|
||||
|
||||
/// Values used to break ties, based on forwards tie-breaking
|
||||
pub forwards_tiebreak: Option<HashMap<&'a Candidate, usize>>,
|
||||
/// Values used to break ties, based on backwards tie-breaking
|
||||
pub backwards_tiebreak: Option<HashMap<&'a Candidate, usize>>,
|
||||
/// [SHARandom] for random tie-breaking
|
||||
pub random: Option<SHARandom<'a>>,
|
||||
|
||||
/// Quota for election
|
||||
pub quota: Option<N>,
|
||||
/// Vote required for election
|
||||
///
|
||||
/// With a static quota, this is equal to the quota. With ERS97 rules, this may vary from the quota.
|
||||
pub vote_required_election: Option<N>,
|
||||
|
||||
/// Number of candidates who have been declared elected
|
||||
pub num_elected: usize,
|
||||
/// Number of candidates who have been declared excluded
|
||||
pub num_excluded: usize,
|
||||
|
||||
/// The type of stage being counted
|
||||
///
|
||||
/// For example, "Surplus of", "Exclusion of"
|
||||
pub kind: Option<&'a str>,
|
||||
/// The description of the stage being counted, excluding [CountState::kind]
|
||||
pub title: String,
|
||||
/// [Logger] for this stage of the count
|
||||
pub logger: Logger<'a>,
|
||||
}
|
||||
|
||||
|
@ -199,7 +224,11 @@ impl<'a, N: Number> CountState<'a, N> {
|
|||
/// Represents either a reference to a [CountState] or a clone
|
||||
#[allow(dead_code)]
|
||||
pub enum CountStateOrRef<'a, N: Number> {
|
||||
State(CountState<'a, N>), // NYI: May be used e.g. for tie-breaking or rollback-based constraints
|
||||
/// Cloned [CountState]
|
||||
///
|
||||
/// Currently unused/unimplemented, but may be used in future for rollback-based constraints
|
||||
State(CountState<'a, N>),
|
||||
/// Reference to a [CountState]
|
||||
Ref(&'a CountState<'a, N>),
|
||||
}
|
||||
|
||||
|
@ -220,22 +249,33 @@ impl<'a, N: Number> CountStateOrRef<'a, N> {
|
|||
|
||||
/// Result of a stage of counting
|
||||
pub struct StageResult<'a, N: Number> {
|
||||
/// See [CountState::kind]
|
||||
pub kind: Option<&'a str>,
|
||||
/// See [CountState::title]
|
||||
pub title: &'a String,
|
||||
/// Detailed logs of this stage, rendered from [CountState::logger]
|
||||
pub logs: Vec<String>,
|
||||
/// Reference to the [CountState] or cloned [CountState] of this stage
|
||||
pub state: CountStateOrRef<'a, N>,
|
||||
}
|
||||
|
||||
/// Current state of a [Candidate] during an election count
|
||||
#[derive(Clone)]
|
||||
pub struct CountCard<'a, N> {
|
||||
/// State of the candidate
|
||||
pub state: CandidateState,
|
||||
/// Order of election or exclusion
|
||||
///
|
||||
/// Positive integers represent order of election; negative integers represent order of exclusion
|
||||
pub order_elected: isize,
|
||||
|
||||
//pub orig_votes: N,
|
||||
/// Net votes transferred to this candidate in this stage
|
||||
pub transfers: N,
|
||||
/// Votes of the candidate at the end of this stage
|
||||
pub votes: N,
|
||||
|
||||
/// Parcels of ballots assigned to this candidate
|
||||
pub parcels: Vec<Parcel<'a, N>>,
|
||||
|
||||
/// Candidate's keep value (Meek STV)
|
||||
|
@ -275,7 +315,9 @@ pub type Parcel<'a, N> = Vec<Vote<'a, N>>;
|
|||
/// Represents a [Ballot] with an associated value
|
||||
#[derive(Clone)]
|
||||
pub struct Vote<'a, N> {
|
||||
/// Ballot from which the vote is derived
|
||||
pub ballot: &'a Ballot<N>,
|
||||
/// Current value of the ballot
|
||||
pub value: N,
|
||||
/// Index of the next preference to examine
|
||||
pub up_to_pref: usize,
|
||||
|
@ -283,7 +325,9 @@ pub struct Vote<'a, N> {
|
|||
|
||||
/// A record of a voter's preferences
|
||||
pub struct Ballot<N> {
|
||||
/// Original value/weight of the ballot
|
||||
pub orig_value: N,
|
||||
/// Indexes of candidates preferenced on the ballot
|
||||
pub preferences: Vec<usize>,
|
||||
}
|
||||
|
||||
|
@ -292,10 +336,16 @@ pub struct Ballot<N> {
|
|||
#[derive(PartialEq)]
|
||||
#[derive(Clone)]
|
||||
pub enum CandidateState {
|
||||
/// Hopeful (continuing candidate)
|
||||
Hopeful,
|
||||
/// Required by constraints to be guarded from exclusion
|
||||
Guarded,
|
||||
/// Declared elected
|
||||
Elected,
|
||||
/// Required by constraints to be doomed to be excluded
|
||||
Doomed,
|
||||
/// Withdrawn candidate
|
||||
Withdrawn,
|
||||
/// Declared excluded
|
||||
Excluded,
|
||||
}
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! Open source counting software for various preferential voting election systems
|
||||
|
||||
/// Data types for representing abstract elections
|
||||
pub mod election;
|
||||
/// Smart logging framework
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
/// Smart logger used in election counts
|
||||
#[derive(Clone)]
|
||||
pub struct Logger<'a> {
|
||||
/// [Vec] of log entries for the current stage
|
||||
pub entries: Vec<LogEntry<'a>>,
|
||||
}
|
||||
|
||||
|
@ -73,7 +74,9 @@ impl<'a> Logger<'a> {
|
|||
/// Represents either a literal or smart log entry
|
||||
#[derive(Clone)]
|
||||
pub enum LogEntry<'a> {
|
||||
/// Smart log entry - see [SmartLogEntry]
|
||||
Smart(SmartLogEntry<'a>),
|
||||
/// Literal log entry
|
||||
Literal(String)
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ fn get_factor() -> &'static IBig {
|
|||
pub struct Fixed(IBig);
|
||||
|
||||
impl Fixed {
|
||||
/// Set the number of decimal places to compute results to
|
||||
pub fn set_dps(dps: usize) {
|
||||
unsafe {
|
||||
DPS = Some(dps);
|
||||
|
@ -108,6 +109,7 @@ impl From<f64> for Fixed {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Fix rounding
|
||||
impl fmt::Display for Fixed {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let dps = match f.precision() {
|
||||
|
|
|
@ -48,6 +48,7 @@ fn get_factor_cmp() -> &'static IBig {
|
|||
pub struct GuardedFixed(IBig);
|
||||
|
||||
impl GuardedFixed {
|
||||
/// Set the number of decimal places to compute results to
|
||||
pub fn set_dps(dps: usize) {
|
||||
unsafe {
|
||||
DPS = Some(dps);
|
||||
|
|
|
@ -22,6 +22,7 @@ pub mod gregory;
|
|||
/// Meek method of surplus distributions, etc.
|
||||
pub mod meek;
|
||||
|
||||
/// WebAssembly wrappers
|
||||
//#[cfg(target_arch = "wasm32")]
|
||||
pub mod wasm;
|
||||
|
||||
|
@ -38,22 +39,39 @@ use std::ops;
|
|||
|
||||
/// Options for conducting an STV count
|
||||
pub struct STVOptions {
|
||||
/// Round transfer values to specified decimal places
|
||||
pub round_tvs: Option<usize>,
|
||||
/// Round ballot weights to specified decimal places
|
||||
pub round_weights: Option<usize>,
|
||||
/// Round votes to specified decimal places
|
||||
pub round_votes: Option<usize>,
|
||||
/// Round quota to specified decimal places
|
||||
pub round_quota: Option<usize>,
|
||||
/// How to calculate votes to credit to candidates in surplus transfers
|
||||
pub sum_surplus_transfers: SumSurplusTransfersMode,
|
||||
/// Convert ballots with value >1 to multiple ballots of value 1
|
||||
pub normalise_ballots: bool,
|
||||
/// Quota type
|
||||
pub quota: QuotaType,
|
||||
/// Whether to elect candidates on meeting (geq) or strictly exceeding (gt) the quota
|
||||
pub quota_criterion: QuotaCriterion,
|
||||
/// Whether to apply a form of progressive quota
|
||||
pub quota_mode: QuotaMode,
|
||||
/// Tie-breaking method
|
||||
pub ties: Vec<TieStrategy>,
|
||||
/// Method of surplus distributions
|
||||
pub surplus: SurplusMethod,
|
||||
/// Order to distribute surpluses
|
||||
pub surplus_order: SurplusOrder,
|
||||
/// Examine only transferable papers during surplus distributions
|
||||
pub transferable_only: bool,
|
||||
/// Method of exclusions
|
||||
pub exclusion: ExclusionMethod,
|
||||
/// Use bulk exclusion
|
||||
pub bulk_exclude: bool,
|
||||
/// Defer surplus distributions if possible
|
||||
pub defer_surpluses: bool,
|
||||
/// Print votes to specified decimal places in results report
|
||||
pub pp_decimals: usize,
|
||||
}
|
||||
|
||||
|
@ -172,8 +190,11 @@ impl STVOptions {
|
|||
#[derive(Clone, Copy)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum SumSurplusTransfersMode {
|
||||
/// Sum and round all surplus transfers for a candidate in a single step
|
||||
SingleStep,
|
||||
/// Sum and round a candidate's surplus transfers separately for ballot papers received at each particular value
|
||||
ByValue,
|
||||
/// Sum and round a candidate's surplus transfers individually for each ballot paper
|
||||
PerBallot,
|
||||
}
|
||||
|
||||
|
@ -193,9 +214,13 @@ impl SumSurplusTransfersMode {
|
|||
#[derive(Clone, Copy)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum QuotaType {
|
||||
/// Droop quota
|
||||
Droop,
|
||||
/// Hare quota
|
||||
Hare,
|
||||
/// Exact Droop quota (Newland–Britton/Hagenbach-Bischoff quota)
|
||||
DroopExact,
|
||||
/// Exact Hare quota
|
||||
HareExact,
|
||||
}
|
||||
|
||||
|
@ -216,7 +241,9 @@ impl QuotaType {
|
|||
#[derive(Clone, Copy)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum QuotaCriterion {
|
||||
/// Elect candidates on equalling or exceeding the quota
|
||||
GreaterOrEqual,
|
||||
/// Elect candidates on strictly exceeding the quota
|
||||
Greater,
|
||||
}
|
||||
|
||||
|
@ -235,7 +262,9 @@ impl QuotaCriterion {
|
|||
#[derive(Clone, Copy)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum QuotaMode {
|
||||
/// Static quota
|
||||
Static,
|
||||
/// Static quota with ERS97 rules
|
||||
ERS97,
|
||||
}
|
||||
|
||||
|
@ -254,9 +283,13 @@ impl QuotaMode {
|
|||
#[derive(Clone, Copy)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum SurplusMethod {
|
||||
/// Weighted inclusive Gregory method
|
||||
WIG,
|
||||
/// Unweighted inclusive Gregory method
|
||||
UIG,
|
||||
/// Exclusive Gregory method (last bundle)
|
||||
EG,
|
||||
/// Meek method
|
||||
Meek,
|
||||
}
|
||||
|
||||
|
@ -277,7 +310,9 @@ impl SurplusMethod {
|
|||
#[derive(Clone, Copy)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum SurplusOrder {
|
||||
/// Transfer the largest surplus first, even if it arose at a later stage of the count
|
||||
BySize,
|
||||
/// Transfer the surplus of the candidate elected first, even if it is smaller than another
|
||||
ByOrder,
|
||||
}
|
||||
|
||||
|
@ -296,8 +331,11 @@ impl SurplusOrder {
|
|||
#[derive(Clone, Copy)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum ExclusionMethod {
|
||||
/// Transfer all ballot papers of an excluded candidate in one stage
|
||||
SingleStage,
|
||||
/// Transfer the ballot papers of an excluded candidate in descending order of accumulated transfer value
|
||||
ByValue,
|
||||
/// Transfer the ballot papers of an excluded candidate parcel by parcel in the order received
|
||||
ParcelsByOrder,
|
||||
}
|
||||
|
||||
|
@ -316,7 +354,9 @@ impl ExclusionMethod {
|
|||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub enum STVError {
|
||||
/// User input is required
|
||||
RequireInput,
|
||||
/// Tie could not be resolved
|
||||
UnresolvedTie,
|
||||
}
|
||||
|
||||
|
|
|
@ -129,27 +129,38 @@ macro_rules! impl_type {
|
|||
}
|
||||
|
||||
// Wrapper structs
|
||||
// Required as we cannot specify &'static in wasm-bindgen: issue #1187
|
||||
|
||||
/// Wrapper for [CountState]
|
||||
///
|
||||
/// This is required as `&'static` cannot be specified in wasm-bindgen: see [issue 1187](https://github.com/rustwasm/wasm-bindgen/issues/1187).
|
||||
///
|
||||
#[wasm_bindgen]
|
||||
pub struct [<CountState$type>](CountState<'static, $type>);
|
||||
#[wasm_bindgen]
|
||||
impl [<CountState$type>] {
|
||||
/// Create a new [CountState] wrapper
|
||||
pub fn new(election: &[<Election$type>]) -> Self {
|
||||
return [<CountState$type>](CountState::new(election.as_static()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for [Election]
|
||||
///
|
||||
/// This is required as `&'static` cannot be specified in wasm-bindgen: see [issue 1187](https://github.com/rustwasm/wasm-bindgen/issues/1187).
|
||||
///
|
||||
#[wasm_bindgen]
|
||||
pub struct [<Election$type>](Election<$type>);
|
||||
#[wasm_bindgen]
|
||||
impl [<Election$type>] {
|
||||
/// Return [Election::seats]
|
||||
pub fn seats(&self) -> usize { self.0.seats }
|
||||
|
||||
/// Return the underlying [Election] as a `&'static Election`
|
||||
///
|
||||
/// # Safety
|
||||
/// This assumes that the underlying [Election] is valid for the `'static` lifetime, as it would be if the [Election] were created from Javascript.
|
||||
///
|
||||
fn as_static(&self) -> &'static Election<$type> {
|
||||
// Need to have this as we cannot specify &'static in wasm-bindgen: issue #1187
|
||||
unsafe {
|
||||
let ptr = &self.0 as *const Election<$type>;
|
||||
&*ptr
|
||||
|
@ -218,7 +229,7 @@ impl STVOptions {
|
|||
/// Return the underlying [stv::STVOptions] as a `&'static stv::STVOptions`
|
||||
///
|
||||
/// # Safety
|
||||
/// Assumes that the underlying [stv::STVOptions] is valid for the `'static` lifetime, as it would be if the [stv::STVOptions] were created from Javascript
|
||||
/// This assumes that the underlying [stv::STVOptions] is valid for the `'static` lifetime, as it would be if the [stv::STVOptions] were created from Javascript.
|
||||
///
|
||||
fn as_static(&self) -> &'static stv::STVOptions {
|
||||
unsafe {
|
||||
|
|
|
@ -29,9 +29,13 @@ use std::io::{stdin, stdout, Write};
|
|||
/// Strategy for breaking ties
|
||||
#[derive(PartialEq)]
|
||||
pub enum TieStrategy {
|
||||
/// Break ties according to the candidate who first had more/fewer votes
|
||||
Forwards,
|
||||
/// Break ties according to the candidate who most recently had more/fewer votes
|
||||
Backwards,
|
||||
/// Break ties randomly (see [crate::sharandom])
|
||||
Random(String),
|
||||
/// Prompt the user to break ties
|
||||
Prompt,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue