diff --git a/src/election.rs b/src/election.rs index 2876dfd..38fde55 100644 --- a/src/election.rs +++ b/src/election.rs @@ -1,5 +1,5 @@ /* OpenTally: Open-source election vote counting - * Copyright © 2021–2022 Lee Yingtong Li (RunasSudo) + * Copyright © 2021–2023 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 @@ -326,7 +326,11 @@ impl<'a, N: Number> CountState<'a, N> { total_vote += &self.loss_fraction.votes; result.push_str(&format!("Total votes: {:.dps$}\n", total_vote, dps=opts.pp_decimals)); - result.push_str(&format!("Quota: {:.dps$}\n", self.quota.as_ref().unwrap(), dps=opts.pp_decimals)); + if self.election.seats == 1 { + result.push_str(&format!("Majority: {:.dps$}\n", self.quota.as_ref().unwrap(), dps=opts.pp_decimals)); + } else { + result.push_str(&format!("Quota: {:.dps$}\n", self.quota.as_ref().unwrap(), dps=opts.pp_decimals)); + } if stv::should_show_vre(opts) { if let Some(vre) = &self.vote_required_election { result.push_str(&format!("Vote required for election: {:.dps$}\n", vre, dps=opts.pp_decimals)); diff --git a/src/stv/gregory/mod.rs b/src/stv/gregory/mod.rs index c4da3bd..a20fe6b 100644 --- a/src/stv/gregory/mod.rs +++ b/src/stv/gregory/mod.rs @@ -1,5 +1,5 @@ /* OpenTally: Open-source election vote counting - * Copyright © 2021–2022 Lee Yingtong Li (RunasSudo) + * Copyright © 2021–2023 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 @@ -645,13 +645,18 @@ where } if let ExclusionMethod::SingleStage = opts.exclusion { - if total_ballots == N::one() { + if state.election.seats == 1 { + // Ballots can never have nonzero value with a single winner + state.logger.log_literal(format!("Transferring {:.dps$} votes.", total_votes, dps=opts.pp_decimals)); + } else if total_ballots == N::one() { state.logger.log_literal(format!("Transferring 1 ballot, totalling {:.dps$} votes.", total_votes, dps=opts.pp_decimals)); } else { state.logger.log_literal(format!("Transferring {:.0} ballots, totalling {:.dps$} votes.", total_ballots, total_votes, dps=opts.pp_decimals)); } } else { - if total_ballots.is_zero() { + if state.election.seats == 1 { + state.logger.log_literal(format!("Transferring {:.dps$} votes, received at value {:.dps2$}.", total_votes, value.unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2))); + } else if total_ballots.is_zero() { state.logger.log_literal(format!("Transferring 0 ballots, totalling {:.dps$} votes.", 0, dps=opts.pp_decimals)); } else if total_ballots == N::one() { state.logger.log_literal(format!("Transferring 1 ballot, totalling {:.dps$} votes, received at value {:.dps2$}.", total_votes, value.unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2))); diff --git a/src/stv/html.rs b/src/stv/html.rs index 0c50f18..62f6fc6 100644 --- a/src/stv/html.rs +++ b/src/stv/html.rs @@ -1,5 +1,5 @@ /* OpenTally: Open-source election vote counting - * Copyright © 2021–2022 Lee Yingtong Li (RunasSudo) + * Copyright © 2021–2023 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 @@ -73,12 +73,20 @@ pub fn init_results_table(election: &Election, opts: &stv::STVOpti if report_style == "votes_transposed" { result.push(String::from(r#"Loss by fraction"#)); result.push(String::from(r#"Total"#)); - result.push(String::from(r#"Quota"#)); + if election.seats == 1 { + result.push(String::from(r#"Majority"#)); + } else { + result.push(String::from(r#"Quota"#)); + } } else { result.push(String::from(r#"Loss by fraction"#)); result.push(String::from(r#""#)); result.push(String::from(r#"Total"#)); - result.push(String::from(r#"Quota"#)); + if election.seats == 1 { + result.push(String::from(r#"Majority"#)); + } else { + result.push(String::from(r#"Quota"#)); + } } if stv::should_show_vre(opts) { diff --git a/src/stv/mod.rs b/src/stv/mod.rs index 13c4dc2..8ac6702 100644 --- a/src/stv/mod.rs +++ b/src/stv/mod.rs @@ -1,5 +1,5 @@ /* OpenTally: Open-source election vote counting - * Copyright © 2021–2022 Lee Yingtong Li (RunasSudo) + * Copyright © 2021–2023 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 @@ -378,7 +378,11 @@ fn calculate_quota(state: &mut CountState, opts: &STVOptions) { // Calculate the total vote let total_vote = state.total_vote(); - log.push_str(format!("{:.dps$} usable votes, so the quota is ", total_vote, dps=opts.pp_decimals).as_str()); + if state.election.seats == 1 { + log.push_str(format!("{:.dps$} usable votes, so a majority is ", total_vote, dps=opts.pp_decimals).as_str()); + } else { + log.push_str(format!("{:.dps$} usable votes, so the quota is ", total_vote, dps=opts.pp_decimals).as_str()); + } let quota = total_to_quota(total_vote, state.election.seats, opts); @@ -393,7 +397,11 @@ fn calculate_quota(state: &mut CountState, opts: &STVOptions) { // Calculate the active vote let active_vote = state.active_vote(); - log.push_str(format!("Active vote is {:.dps$}, so the quota is is ", active_vote, dps=opts.pp_decimals).as_str()); + if state.election.seats == 1 { + log.push_str(format!("Active vote is {:.dps$}, so a majority is ", active_vote, dps=opts.pp_decimals).as_str()); + } else { + log.push_str(format!("Active vote is {:.dps$}, so the quota is ", active_vote, dps=opts.pp_decimals).as_str()); + } // TODO: Calculate according to --quota ? let quota = active_vote / N::from(state.election.seats - state.num_elected + 1); @@ -414,7 +422,11 @@ fn calculate_quota(state: &mut CountState, opts: &STVOptions) { // Calculate the total vote let total_vote = state.total_vote(); - log.push_str(format!("{:.dps$} usable votes, so the quota is reduced to ", total_vote, dps=opts.pp_decimals).as_str()); + if state.election.seats == 1 { + log.push_str(format!("{:.dps$} usable votes, so a majority is ", total_vote, dps=opts.pp_decimals).as_str()); + } else { + log.push_str(format!("{:.dps$} usable votes, so the quota is reduced to ", total_vote, dps=opts.pp_decimals).as_str()); + } let quota = total_to_quota(total_vote, state.election.seats, opts); @@ -612,11 +624,19 @@ fn elect_hopefuls<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOption if cmp_quota_criterion(state.quota.as_ref().unwrap(), count_card, opts) { // Elected with a quota elected_on_quota = true; - state.logger.log_smart( - "{} meets the quota and is elected.", - "{} meet the quota and are elected.", - vec![candidate.name.as_str()] - ); + if state.election.seats == 1 { + state.logger.log_smart( + "{} reaches a majority and is elected.", + "{} reach a majority and are elected.", // This one is impossible + vec![candidate.name.as_str()] + ); + } else { + state.logger.log_smart( + "{} meets the quota and is elected.", + "{} meet the quota and are elected.", + vec![candidate.name.as_str()] + ); + } } else { // Elected with vote required elected_on_quota = false; @@ -966,11 +986,19 @@ where let names: Vec<&str> = excluded_candidates.iter().map(|c| c.name.as_str()).sorted().collect(); state.title = StageKind::ExclusionOf(excluded_candidates.clone()); - state.logger.log_smart( - "No surpluses to distribute, so {} is excluded.", - "No surpluses to distribute, so {} are excluded.", - names - ); + if state.election.seats == 1 { + state.logger.log_smart( + "No candidate has a majority, so {} is excluded.", + "No candidate has a majority, so {} are excluded.", + names + ); + } else { + state.logger.log_smart( + "No surpluses to distribute, so {} is excluded.", + "No surpluses to distribute, so {} are excluded.", + names + ); + } if opts.early_bulk_elect { // Determine if the proposed exclusion would enable a bulk election