Implement --ties random
This commit is contained in:
parent
266d8e2495
commit
4845ebe52f
63
Cargo.lock
generated
63
Cargo.lock
generated
@ -38,6 +38,15 @@ version = "1.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "bstr"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
@ -122,6 +131,15 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@ -171,6 +189,15 @@ version = "2.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "doc-comment"
|
name = "doc-comment"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@ -195,6 +222,16 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "git-version"
|
name = "git-version"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@ -368,6 +405,12 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opentally"
|
name = "opentally"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -387,6 +430,7 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
"paste",
|
"paste",
|
||||||
"rug",
|
"rug",
|
||||||
|
"sha2",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"xmltree",
|
"xmltree",
|
||||||
]
|
]
|
||||||
@ -524,6 +568,19 @@ version = "1.0.126"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
|
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
"opaque-debug",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "static_assertions"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -553,6 +610,12 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
|
checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.7.1"
|
version = "1.7.1"
|
||||||
|
@ -13,6 +13,7 @@ git-version = "0.3.4"
|
|||||||
ibig = "0.3.2"
|
ibig = "0.3.2"
|
||||||
itertools = "0.10.1"
|
itertools = "0.10.1"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
|
sha2 = "0.9.5"
|
||||||
wasm-bindgen = "0.2.74"
|
wasm-bindgen = "0.2.74"
|
||||||
|
|
||||||
# Only for WebAssembly - include here for syntax highlighting
|
# Only for WebAssembly - include here for syntax highlighting
|
||||||
|
11
docs/rng.md
Normal file
11
docs/rng.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Deterministic random number generator specification
|
||||||
|
|
||||||
|
The deterministic random number generator used in OpenTally is based on an algorithm by [Ronald L Rivest](https://people.csail.mit.edu/rivest/sampler.py).
|
||||||
|
|
||||||
|
The algorithm takes a *seed* value, which is an arbitrary character string. The algorithm has, in its internal state, a *counter*, whose value is initially 0.
|
||||||
|
|
||||||
|
In order to generate a value between 0 (inclusive) and *n* (exclusive), to the state is appended a comma (",") followed by the value of the counter, and a SHA-256 hash *H* is computed of the resulting string encoded using UTF-8. The hash *H* is represented as an unsigned hexadecimal integer, *k*. The counter is incremented by 1.
|
||||||
|
|
||||||
|
In order to avoid modulo bias, if *k* ≥ ⌊*M*/*n*⌋ × *n* (where *M* = 2^256), *k* is discarded and the algorithm is repeated.
|
||||||
|
|
||||||
|
Otherwise, the result is *k* modulo *n*.
|
@ -127,10 +127,10 @@
|
|||||||
<option value="prompt">Prompt</option>
|
<option value="prompt">Prompt</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<!--<label>
|
<label>
|
||||||
Random seed:
|
Random seed:
|
||||||
<input type="text" id="txtSeed" value="">
|
<input type="text" id="txtSeed" value="">
|
||||||
</label>-->
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<!--<div class="subheading">
|
<!--<div class="subheading">
|
||||||
Constraints:
|
Constraints:
|
||||||
|
@ -108,6 +108,7 @@ async function clickCount() {
|
|||||||
document.getElementById('selQuotaCriterion').value,
|
document.getElementById('selQuotaCriterion').value,
|
||||||
document.getElementById('selQuotaMode').value,
|
document.getElementById('selQuotaMode').value,
|
||||||
document.getElementById('selTies').value.split(','),
|
document.getElementById('selTies').value.split(','),
|
||||||
|
document.getElementById('txtSeed').value,
|
||||||
document.getElementById('selTransfers').value,
|
document.getElementById('selTransfers').value,
|
||||||
document.getElementById('selSurplus').value,
|
document.getElementById('selSurplus').value,
|
||||||
document.getElementById('selPapers').value == 'transferable',
|
document.getElementById('selPapers').value == 'transferable',
|
||||||
@ -129,6 +130,13 @@ async function clickCount() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Provide a default seed
|
||||||
|
if (document.getElementById('txtSeed').value === '') {
|
||||||
|
function pad(x) { if (x < 10) { return '0' + x; } return '' + x; }
|
||||||
|
let d = new Date();
|
||||||
|
document.getElementById('txtSeed').value = d.getFullYear() + pad(d.getMonth() + 1) + pad(d.getDate());
|
||||||
|
}
|
||||||
|
|
||||||
// Print logic
|
// Print logic
|
||||||
|
|
||||||
async function printResult() {
|
async function printResult() {
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
use crate::logger::Logger;
|
use crate::logger::Logger;
|
||||||
use crate::numbers::Number;
|
use crate::numbers::Number;
|
||||||
|
use crate::sharandom::SHARandom;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@ -133,6 +134,7 @@ pub struct CountState<'a, N> {
|
|||||||
|
|
||||||
pub forwards_tiebreak: Option<HashMap<&'a Candidate, usize>>,
|
pub forwards_tiebreak: Option<HashMap<&'a Candidate, usize>>,
|
||||||
pub backwards_tiebreak: Option<HashMap<&'a Candidate, usize>>,
|
pub backwards_tiebreak: Option<HashMap<&'a Candidate, usize>>,
|
||||||
|
pub random: Option<SHARandom<'a>>,
|
||||||
|
|
||||||
pub quota: Option<N>,
|
pub quota: Option<N>,
|
||||||
pub vote_required_election: Option<N>,
|
pub vote_required_election: Option<N>,
|
||||||
@ -154,6 +156,7 @@ impl<'a, N: Number> CountState<'a, N> {
|
|||||||
loss_fraction: CountCard::new(),
|
loss_fraction: CountCard::new(),
|
||||||
forwards_tiebreak: None,
|
forwards_tiebreak: None,
|
||||||
backwards_tiebreak: None,
|
backwards_tiebreak: None,
|
||||||
|
random: None,
|
||||||
quota: None,
|
quota: None,
|
||||||
vote_required_election: None,
|
vote_required_election: None,
|
||||||
num_elected: 0,
|
num_elected: 0,
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
pub mod election;
|
pub mod election;
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
pub mod numbers;
|
pub mod numbers;
|
||||||
|
pub mod sharandom;
|
||||||
pub mod stv;
|
pub mod stv;
|
||||||
pub mod ties;
|
pub mod ties;
|
||||||
|
|
||||||
|
@ -108,6 +108,10 @@ struct STV {
|
|||||||
#[clap(help_heading=Some("STV VARIANTS"), short='t', long, possible_values=&["forwards", "backwards", "random", "prompt"], default_value="prompt", value_name="methods")]
|
#[clap(help_heading=Some("STV VARIANTS"), short='t', long, possible_values=&["forwards", "backwards", "random", "prompt"], default_value="prompt", value_name="methods")]
|
||||||
ties: Vec<String>,
|
ties: Vec<String>,
|
||||||
|
|
||||||
|
/// Random seed to use with --ties random
|
||||||
|
#[clap(help_heading=Some("STV VARIANTS"), long, value_name="seed")]
|
||||||
|
random_seed: Option<String>,
|
||||||
|
|
||||||
/// Method of surplus distributions
|
/// Method of surplus distributions
|
||||||
#[clap(help_heading=Some("STV VARIANTS"), short='s', long, possible_values=&["wig", "uig", "eg", "meek"], default_value="wig", value_name="method")]
|
#[clap(help_heading=Some("STV VARIANTS"), short='s', long, possible_values=&["wig", "uig", "eg", "meek"], default_value="wig", value_name="method")]
|
||||||
surplus: String,
|
surplus: String,
|
||||||
@ -192,6 +196,7 @@ where
|
|||||||
&cmd_opts.quota_criterion,
|
&cmd_opts.quota_criterion,
|
||||||
&cmd_opts.quota_mode,
|
&cmd_opts.quota_mode,
|
||||||
&cmd_opts.ties,
|
&cmd_opts.ties,
|
||||||
|
&cmd_opts.random_seed,
|
||||||
&cmd_opts.surplus,
|
&cmd_opts.surplus,
|
||||||
&cmd_opts.surplus_order,
|
&cmd_opts.surplus_order,
|
||||||
cmd_opts.transferable_only,
|
cmd_opts.transferable_only,
|
||||||
|
63
src/sharandom.rs
Normal file
63
src/sharandom.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/* 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 ibig::UBig;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SHARandom<'r> {
|
||||||
|
seed: &'r str,
|
||||||
|
counter: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> SHARandom<'r> {
|
||||||
|
pub fn new(seed: &'r str) -> Self {
|
||||||
|
Self {
|
||||||
|
seed: seed,
|
||||||
|
counter: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, max: usize) -> usize {
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(format!("{},{}", self.seed, self.counter).as_bytes());
|
||||||
|
|
||||||
|
self.counter += 1;
|
||||||
|
|
||||||
|
let hash = hasher.finalize();
|
||||||
|
let hash = UBig::from_be_bytes(&hash);
|
||||||
|
|
||||||
|
if hash >= UBig::from(2_u32).pow(256) / UBig::from(max) * UBig::from(max) {
|
||||||
|
return self.next(max);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (hash % UBig::from(max)).to_string().parse().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sharandom1() {
|
||||||
|
let mut random = SHARandom::new(&"foobar");
|
||||||
|
assert_eq!(random.next(10), 0);
|
||||||
|
assert_eq!(random.next(42), 30);
|
||||||
|
assert_eq!(random.next(64), 13);
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@ pub mod wasm;
|
|||||||
|
|
||||||
use crate::numbers::Number;
|
use crate::numbers::Number;
|
||||||
use crate::election::{Candidate, CandidateState, CountCard, CountState, Parcel, Vote};
|
use crate::election::{Candidate, CandidateState, CountCard, CountState, Parcel, Vote};
|
||||||
|
use crate::sharandom::SHARandom;
|
||||||
use crate::ties::TieStrategy;
|
use crate::ties::TieStrategy;
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@ -31,7 +32,7 @@ use std::cmp::max;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops;
|
use std::ops;
|
||||||
|
|
||||||
pub struct STVOptions<'o> {
|
pub struct STVOptions {
|
||||||
pub round_tvs: Option<usize>,
|
pub round_tvs: Option<usize>,
|
||||||
pub round_weights: Option<usize>,
|
pub round_weights: Option<usize>,
|
||||||
pub round_votes: Option<usize>,
|
pub round_votes: Option<usize>,
|
||||||
@ -41,7 +42,7 @@ pub struct STVOptions<'o> {
|
|||||||
pub quota: QuotaType,
|
pub quota: QuotaType,
|
||||||
pub quota_criterion: QuotaCriterion,
|
pub quota_criterion: QuotaCriterion,
|
||||||
pub quota_mode: QuotaMode,
|
pub quota_mode: QuotaMode,
|
||||||
pub ties: Vec<TieStrategy<'o>>,
|
pub ties: Vec<TieStrategy>,
|
||||||
pub surplus: SurplusMethod,
|
pub surplus: SurplusMethod,
|
||||||
pub surplus_order: SurplusOrder,
|
pub surplus_order: SurplusOrder,
|
||||||
pub transferable_only: bool,
|
pub transferable_only: bool,
|
||||||
@ -51,7 +52,7 @@ pub struct STVOptions<'o> {
|
|||||||
pub pp_decimals: usize,
|
pub pp_decimals: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'o> STVOptions<'o> {
|
impl STVOptions {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
round_tvs: Option<usize>,
|
round_tvs: Option<usize>,
|
||||||
round_weights: Option<usize>,
|
round_weights: Option<usize>,
|
||||||
@ -63,6 +64,7 @@ impl<'o> STVOptions<'o> {
|
|||||||
quota_criterion: &str,
|
quota_criterion: &str,
|
||||||
quota_mode: &str,
|
quota_mode: &str,
|
||||||
ties: &Vec<String>,
|
ties: &Vec<String>,
|
||||||
|
random_seed: &Option<String>,
|
||||||
surplus: &str,
|
surplus: &str,
|
||||||
surplus_order: &str,
|
surplus_order: &str,
|
||||||
transferable_only: bool,
|
transferable_only: bool,
|
||||||
@ -103,7 +105,7 @@ impl<'o> STVOptions<'o> {
|
|||||||
ties: ties.into_iter().map(|t| match t.as_str() {
|
ties: ties.into_iter().map(|t| match t.as_str() {
|
||||||
"forwards" => TieStrategy::Forwards,
|
"forwards" => TieStrategy::Forwards,
|
||||||
"backwards" => TieStrategy::Backwards,
|
"backwards" => TieStrategy::Backwards,
|
||||||
"random" => TieStrategy::Random(&"TODO"),
|
"random" => TieStrategy::Random(random_seed.as_ref().expect("Must provide a --random-seed if using --ties random").clone()),
|
||||||
"prompt" => TieStrategy::Prompt,
|
"prompt" => TieStrategy::Prompt,
|
||||||
_ => panic!("Invalid --ties"),
|
_ => panic!("Invalid --ties"),
|
||||||
}).collect(),
|
}).collect(),
|
||||||
@ -143,6 +145,7 @@ impl<'o> STVOptions<'o> {
|
|||||||
if self.normalise_ballots { flags.push("--normalise-ballots".to_string()); }
|
if self.normalise_ballots { flags.push("--normalise-ballots".to_string()); }
|
||||||
let ties_str = self.ties.iter().map(|t| t.describe()).join(" ");
|
let ties_str = self.ties.iter().map(|t| t.describe()).join(" ");
|
||||||
if ties_str != "prompt" { flags.push(format!("--ties {}", ties_str)); }
|
if ties_str != "prompt" { flags.push(format!("--ties {}", ties_str)); }
|
||||||
|
for t in self.ties.iter() { if let TieStrategy::Random(seed) = t { flags.push(format!("--random-seed {}", seed)); } }
|
||||||
if self.quota != QuotaType::DroopExact { flags.push(self.quota.describe()); }
|
if self.quota != QuotaType::DroopExact { flags.push(self.quota.describe()); }
|
||||||
if self.quota_criterion != QuotaCriterion::Greater { flags.push(self.quota_criterion.describe()); }
|
if self.quota_criterion != QuotaCriterion::Greater { flags.push(self.quota_criterion.describe()); }
|
||||||
if self.quota_mode != QuotaMode::Static { flags.push(self.quota_mode.describe()); }
|
if self.quota_mode != QuotaMode::Static { flags.push(self.quota_mode.describe()); }
|
||||||
@ -295,7 +298,14 @@ pub enum STVError {
|
|||||||
UnresolvedTie,
|
UnresolvedTie,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count_init<N: Number>(mut state: &mut CountState<'_, N>, opts: &STVOptions) {
|
pub fn count_init<'a, N: Number>(mut state: &mut CountState<'a, N>, opts: &'a STVOptions) {
|
||||||
|
// Initialise RNG
|
||||||
|
for t in opts.ties.iter() {
|
||||||
|
if let TieStrategy::Random(seed) = t {
|
||||||
|
state.random = Some(SHARandom::new(seed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
distribute_first_preferences(&mut state);
|
distribute_first_preferences(&mut state);
|
||||||
calculate_quota(&mut state, opts);
|
calculate_quota(&mut state, opts);
|
||||||
elect_meeting_quota(&mut state, opts);
|
elect_meeting_quota(&mut state, opts);
|
||||||
|
@ -56,7 +56,7 @@ macro_rules! impl_type {
|
|||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn [<count_init_$type>](state: &mut [<CountState$type>], opts: &STVOptions) {
|
pub fn [<count_init_$type>](state: &mut [<CountState$type>], opts: &STVOptions) {
|
||||||
stv::count_init(&mut state.0, &opts.0);
|
stv::count_init(&mut state.0, opts.as_static());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@ -141,7 +141,7 @@ impl_type!(NativeFloat64);
|
|||||||
impl_type!(Rational);
|
impl_type!(Rational);
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct STVOptions(stv::STVOptions<'static>);
|
pub struct STVOptions(stv::STVOptions);
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl STVOptions {
|
impl STVOptions {
|
||||||
@ -156,6 +156,7 @@ impl STVOptions {
|
|||||||
quota_criterion: &str,
|
quota_criterion: &str,
|
||||||
quota_mode: &str,
|
quota_mode: &str,
|
||||||
ties: Array,
|
ties: Array,
|
||||||
|
random_seed: String,
|
||||||
surplus: &str,
|
surplus: &str,
|
||||||
surplus_order: &str,
|
surplus_order: &str,
|
||||||
transferable_only: bool,
|
transferable_only: bool,
|
||||||
@ -175,6 +176,7 @@ impl STVOptions {
|
|||||||
quota_criterion,
|
quota_criterion,
|
||||||
quota_mode,
|
quota_mode,
|
||||||
&ties.iter().map(|v| v.as_string().unwrap()).collect(),
|
&ties.iter().map(|v| v.as_string().unwrap()).collect(),
|
||||||
|
&Some(random_seed),
|
||||||
surplus,
|
surplus,
|
||||||
surplus_order,
|
surplus_order,
|
||||||
transferable_only,
|
transferable_only,
|
||||||
@ -186,6 +188,15 @@ impl STVOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl STVOptions {
|
||||||
|
fn as_static(&self) -> &'static stv::STVOptions {
|
||||||
|
unsafe {
|
||||||
|
let ptr = &self.0 as *const stv::STVOptions;
|
||||||
|
&*ptr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reporting
|
// Reporting
|
||||||
|
|
||||||
fn init_results_table<N: Number>(election: &Election<N>, opts: &stv::STVOptions) -> String {
|
fn init_results_table<N: Number>(election: &Election<N>, opts: &stv::STVOptions) -> String {
|
||||||
|
11
src/ties.rs
11
src/ties.rs
@ -27,14 +27,14 @@ use wasm_bindgen::prelude::wasm_bindgen;
|
|||||||
use std::io::{stdin, stdout, Write};
|
use std::io::{stdin, stdout, Write};
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub enum TieStrategy<'s> {
|
pub enum TieStrategy {
|
||||||
Forwards,
|
Forwards,
|
||||||
Backwards,
|
Backwards,
|
||||||
Random(&'s str),
|
Random(String),
|
||||||
Prompt,
|
Prompt,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> TieStrategy<'s> {
|
impl TieStrategy {
|
||||||
pub fn describe(&self) -> String {
|
pub fn describe(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::Forwards => "forwards",
|
Self::Forwards => "forwards",
|
||||||
@ -73,7 +73,10 @@ impl<'s> TieStrategy<'s> {
|
|||||||
return Ok(candidates[0]);
|
return Ok(candidates[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Random(_seed) => { todo!() }
|
Self::Random(_) => {
|
||||||
|
state.logger.log_literal(format!("Tie between {} broken at random.", smart_join(&candidates.iter().map(|c| c.name.as_str()).collect())));
|
||||||
|
return Ok(candidates[state.random.as_mut().unwrap().next(candidates.len())]);
|
||||||
|
}
|
||||||
Self::Prompt => {
|
Self::Prompt => {
|
||||||
match prompt(candidates) {
|
match prompt(candidates) {
|
||||||
Ok(c) => {
|
Ok(c) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user