From ba8d9bf79cfaa64216874509dabb6d59463e601b Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Thu, 3 Jun 2021 21:35:25 +1000 Subject: [PATCH] Describe election count/options in web UI --- html/index.js | 5 +++ html/main.css | 2 +- src/election.rs | 12 +++++- src/lib.rs | 4 ++ src/main.rs | 5 +-- src/numbers/mod.rs | 2 + src/numbers/native.rs | 2 + src/numbers/rational_num.rs | 2 + src/numbers/rational_rug.rs | 2 + src/stv/mod.rs | 75 +++++++++++++++++++++++++++++++++++++ src/stv/wasm.rs | 31 ++++++++------- 11 files changed, 120 insertions(+), 22 deletions(-) diff --git a/html/index.js b/html/index.js index 44c58f2..4e8a9dc 100644 --- a/html/index.js +++ b/html/index.js @@ -80,6 +80,11 @@ async function clickCount() { parseInt(document.getElementById('txtPPDP').value), ); + // Describe count + let filePath = document.getElementById('bltFile').value; + filePath = filePath.substring(Math.max(filePath.lastIndexOf('\\'), filePath.lastIndexOf('/')) + 1); + document.getElementById('resultLogs1').innerHTML = wasm.describe_count_Rational(filePath, election, opts); + // Step election let state = wasm.CountStateRational.new(election); wasm.count_init_Rational(state, opts); diff --git a/html/main.css b/html/main.css index f62cbe4..c3d03f9 100644 --- a/html/main.css +++ b/html/main.css @@ -71,7 +71,7 @@ table { } .result td { padding: 0px 8px; - min-height: 1em; + height: 1em; } td.count { text-align: right; diff --git a/src/election.rs b/src/election.rs index 80220be..602513c 100644 --- a/src/election.rs +++ b/src/election.rs @@ -21,6 +21,7 @@ use crate::numbers::Number; use std::collections::HashMap; pub struct Election { + pub name: String, pub seats: usize, pub candidates: Vec, pub ballots: Vec>, @@ -36,6 +37,7 @@ impl Election { // Initialise the Election object let mut election = Election { + name: String::new(), seats: seats, candidates: Vec::with_capacity(num_candidates), ballots: Vec::new(), @@ -66,7 +68,7 @@ impl Election { } // Read candidates - for line in lines.take(num_candidates) { + for line in lines.by_ref().take(num_candidates) { let mut line = &line[..]; if line.starts_with("\"") && line.ends_with("\"") { line = &line[1..line.len()-1]; @@ -75,6 +77,14 @@ impl Election { election.candidates.push(Candidate { name: line.to_string() }); } + // Read name + let line = lines.next().expect("Syntax Error"); + let mut line = &line[..]; + if line.starts_with("\"") && line.ends_with("\"") { + line = &line[1..line.len()-1]; + } + election.name.push_str(line); + return election; } } diff --git a/src/lib.rs b/src/lib.rs index 1ae4c63..3712bcf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,3 +19,7 @@ pub mod election; pub mod logger; pub mod numbers; pub mod stv; + +use git_version::git_version; + +pub const VERSION: &str = git_version!(args=["--always", "--dirty=-dev"], fallback="unknown"); diff --git a/src/main.rs b/src/main.rs index 54c1587..2606fdf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,17 +20,14 @@ use opentally::election::{Candidate, CandidateState, CountCard, CountState, Coun use opentally::numbers::{NativeFloat64, Number, Rational}; use clap::{AppSettings, Clap}; -use git_version::git_version; use std::fs::File; use std::io::{self, BufRead}; use std::ops; -const VERSION: &str = git_version!(args=["--always", "--dirty=-dev"], fallback="unknown"); - /// Open-source election vote counting #[derive(Clap)] -#[clap(name="OpenTally", version=VERSION)] +#[clap(name="OpenTally", version=opentally::VERSION)] struct Opts { #[clap(subcommand)] command: Command, diff --git a/src/numbers/mod.rs b/src/numbers/mod.rs index b2238b0..c1643c4 100644 --- a/src/numbers/mod.rs +++ b/src/numbers/mod.rs @@ -45,6 +45,8 @@ where { fn new() -> Self; + fn describe() -> String; + fn pow_assign(&mut self, exponent: i32); fn floor_mut(&mut self, dps: usize); fn ceil_mut(&mut self, dps: usize); diff --git a/src/numbers/native.rs b/src/numbers/native.rs index fe5d05a..3d7deae 100644 --- a/src/numbers/native.rs +++ b/src/numbers/native.rs @@ -31,6 +31,8 @@ pub struct NativeFloat64(ImplType); impl Number for NativeFloat64 { fn new() -> Self { Self(0.0) } + fn describe() -> String { "--numbers float64".to_string() } + fn pow_assign(&mut self, exponent: i32) { self.0 = self.0.powi(exponent); } diff --git a/src/numbers/rational_num.rs b/src/numbers/rational_num.rs index 5937109..4a59b07 100644 --- a/src/numbers/rational_num.rs +++ b/src/numbers/rational_num.rs @@ -30,6 +30,8 @@ pub struct Rational(BigRational); impl Number for Rational { fn new() -> Self { Self(BigRational::zero()) } + fn describe() -> String { "--numbers rational".to_string() } + fn pow_assign(&mut self, exponent: i32) { self.0 = self.0.pow(exponent); } diff --git a/src/numbers/rational_rug.rs b/src/numbers/rational_rug.rs index 6097a99..8e23237 100644 --- a/src/numbers/rational_rug.rs +++ b/src/numbers/rational_rug.rs @@ -30,6 +30,8 @@ pub struct Rational(rug::Rational); impl Number for Rational { fn new() -> Self { Self(rug::Rational::new()) } + fn describe() -> String { "--numbers rational".to_string() } + fn pow_assign(&mut self, exponent: i32) { self.0.pow_assign(exponent); } diff --git a/src/stv/mod.rs b/src/stv/mod.rs index fa76ade..c41ba46 100644 --- a/src/stv/mod.rs +++ b/src/stv/mod.rs @@ -99,8 +99,29 @@ impl STVOptions { } } +impl STVOptions { + // Not exported to WebAssembly + pub fn describe(&self) -> String { + let mut flags = Vec::new(); + flags.push(N::describe()); + if let Some(dps) = self.round_tvs { flags.push(format!("--round-tvs {}", dps)); } + if let Some(dps) = self.round_weights { flags.push(format!("--round-weights {}", dps)); } + if let Some(dps) = self.round_votes { flags.push(format!("--round-votes {}", dps)); } + if let Some(dps) = self.round_quota { flags.push(format!("--round-quota {}", dps)); } + if self.quota != QuotaType::Droop { flags.push(self.quota.describe()); } + if self.quota_criterion != QuotaCriterion::GreaterOrEqual { flags.push(self.quota_criterion.describe()); } + if self.surplus != SurplusMethod::WIG { flags.push(self.surplus.describe()); } + if self.surplus_order != SurplusOrder::BySize { flags.push(self.surplus_order.describe()); } + if self.transferable_only { flags.push("--transferable-only".to_string()); } + if self.exclusion != ExclusionMethod::SingleStage { flags.push(self.exclusion.describe()); } + if self.pp_decimals != 2 { flags.push(format!("--pp-decimals {}", self.pp_decimals)); } + return flags.join(" "); + } +} + #[wasm_bindgen] #[derive(Clone, Copy)] +#[derive(PartialEq)] pub enum QuotaType { Droop, Hare, @@ -108,15 +129,37 @@ pub enum QuotaType { HareExact, } +impl QuotaType { + fn describe(self) -> String { + match self { + QuotaType::Droop => "--quota droop", + QuotaType::Hare => "--quota hare", + QuotaType::DroopExact => "--quota droop_exact", + QuotaType::HareExact => "--quota hare_exact", + }.to_string() + } +} + #[wasm_bindgen] #[derive(Clone, Copy)] +#[derive(PartialEq)] pub enum QuotaCriterion { GreaterOrEqual, Greater, } +impl QuotaCriterion { + fn describe(self) -> String { + match self { + QuotaCriterion::GreaterOrEqual => "--quota-criterion geq", + QuotaCriterion::Greater => "--quota-criterion gt", + }.to_string() + } +} + #[wasm_bindgen] #[derive(Clone, Copy)] +#[derive(PartialEq)] pub enum SurplusMethod { WIG, UIG, @@ -124,21 +167,53 @@ pub enum SurplusMethod { Meek, } +impl SurplusMethod { + fn describe(self) -> String { + match self { + SurplusMethod::WIG => "--surplus wig", + SurplusMethod::UIG => "--surplus uig", + SurplusMethod::EG => "--surplus eg", + SurplusMethod::Meek => "--surplus meek", + }.to_string() + } +} + #[wasm_bindgen] #[derive(Clone, Copy)] +#[derive(PartialEq)] pub enum SurplusOrder { BySize, ByOrder, } +impl SurplusOrder { + fn describe(self) -> String { + match self { + SurplusOrder::BySize => "--surplus-order by_size", + SurplusOrder::ByOrder => "--surplus-order by_order", + }.to_string() + } +} + #[wasm_bindgen] #[derive(Clone, Copy)] +#[derive(PartialEq)] pub enum ExclusionMethod { SingleStage, ByValue, ParcelsByOrder, } +impl ExclusionMethod { + fn describe(self) -> String { + match self { + ExclusionMethod::SingleStage => "--exclusion single_stage", + ExclusionMethod::ByValue => "--exclusion by_value", + ExclusionMethod::ParcelsByOrder => "--exclusion parcels_by_value", + }.to_string() + } +} + pub fn count_init(mut state: &mut CountState<'_, N>, opts: &STVOptions) { distribute_first_preferences(&mut state); calculate_quota(&mut state, opts); diff --git a/src/stv/wasm.rs b/src/stv/wasm.rs index 3768b6a..fbcb370 100644 --- a/src/stv/wasm.rs +++ b/src/stv/wasm.rs @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -use crate::election::{Candidate, CandidateState, CountCard, CountState, CountStateOrRef, Election, StageResult}; +use crate::election::{CandidateState, CountState, Election}; use crate::numbers::{NativeFloat64, Number, Rational}; use crate::stv; @@ -24,21 +24,6 @@ extern crate console_error_panic_hook; use js_sys::Array; use wasm_bindgen::prelude::wasm_bindgen; -// Logging - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_namespace = console)] - fn log(s: &str); -} -/// println! to console -macro_rules! cprintln { - ($($t:tt)*) => ( - #[allow(unused_unsafe)] - unsafe { log(&format_args!($($t)*).to_string()) } - ) -} - // Helper macros for making functions macro_rules! impl_type { @@ -75,6 +60,12 @@ macro_rules! impl_type { return init_results_table(&election.0); } + #[wasm_bindgen] + #[allow(non_snake_case)] + pub fn [](filename: String, election: &[], opts: &stv::STVOptions) -> String { + return describe_count(filename, &election.0, opts); + } + #[wasm_bindgen] #[allow(non_snake_case)] pub fn [](stage_num: usize, state: &[], opts: &stv::STVOptions) -> Array { @@ -142,6 +133,14 @@ fn init_results_table(election: &Election) -> String { return result; } +fn describe_count(filename: String, election: &Election, opts: &stv::STVOptions) -> String { + let mut result = String::from("

Count computed by OpenTally (revision "); + result.push_str(crate::VERSION); + let total_ballots = election.ballots.iter().fold(N::zero(), |acc, b| { acc + &b.orig_value }); + result.push_str(&format!(r#"). Read {} ballots from ‘{}’ for election ‘{}’. There are {} candidates for {} vacancies. Counting using options {}.

"#, total_ballots, filename, election.name, election.candidates.len(), election.seats, opts.describe::())); + return result; +} + fn update_results_table(stage_num: usize, state: &CountState, opts: &stv::STVOptions) -> Array { let result = Array::new(); result.push(&format!(r#"{}"#, stage_num).into());