Update documentation

This commit is contained in:
RunasSudo 2021-06-16 17:20:29 +10:00
parent 4ebb6474fd
commit 8829fa5a7b
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
10 changed files with 126 additions and 5 deletions

View File

@ -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:

View File

@ -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:

View File

@ -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,
}

View File

@ -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

View File

@ -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)
}

View File

@ -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() {

View File

@ -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);

View File

@ -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,
}

View File

@ -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 {

View File

@ -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,
}