Calculate loss by fraction introduced by minivoters with equal rankings
This commit is contained in:
parent
90971e976a
commit
2475b42056
|
@ -21,11 +21,13 @@ use crate::numbers::Number;
|
||||||
use crate::sharandom::SHARandom;
|
use crate::sharandom::SHARandom;
|
||||||
use crate::stv::{QuotaMode, STVOptions};
|
use crate::stv::{QuotaMode, STVOptions};
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
use rkyv::{Archive, Archived, Deserialize, Fallible, Resolver, Serialize, with::{ArchiveWith, DeserializeWith, SerializeWith}};
|
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use rkyv::{Archive, Deserialize, Serialize};
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use crate::numbers::{SerializedNumber, SerializedOptionNumber};
|
||||||
|
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
@ -43,6 +45,11 @@ pub struct Election<N> {
|
||||||
pub withdrawn_candidates: Vec<usize>,
|
pub withdrawn_candidates: Vec<usize>,
|
||||||
/// [Vec] of [Ballot]s cast in the election
|
/// [Vec] of [Ballot]s cast in the election
|
||||||
pub ballots: Vec<Ballot<N>>,
|
pub ballots: Vec<Ballot<N>>,
|
||||||
|
/// Total value of [Ballot]s cast in the election
|
||||||
|
///
|
||||||
|
/// Used for [Election::realise_equal_rankings].
|
||||||
|
#[cfg_attr(not(target_arch = "wasm32"), with(SerializedOptionNumber))]
|
||||||
|
pub total_votes: Option<N>,
|
||||||
/// Constraints on candidates
|
/// Constraints on candidates
|
||||||
pub constraints: Option<Constraints>,
|
pub constraints: Option<Constraints>,
|
||||||
}
|
}
|
||||||
|
@ -70,6 +77,9 @@ impl<N: Number> Election<N> {
|
||||||
|
|
||||||
/// Convert ballots with equal rankings to strict-preference "minivoters"
|
/// Convert ballots with equal rankings to strict-preference "minivoters"
|
||||||
pub fn realise_equal_rankings(&mut self) {
|
pub fn realise_equal_rankings(&mut self) {
|
||||||
|
// Record total_votes so loss by fraction can be calculated
|
||||||
|
self.total_votes = Some(self.ballots.iter().fold(N::new(), |acc, b| acc + &b.orig_value));
|
||||||
|
|
||||||
let mut realised_ballots = Vec::new();
|
let mut realised_ballots = Vec::new();
|
||||||
for ballot in self.ballots.iter() {
|
for ballot in self.ballots.iter() {
|
||||||
let mut b = ballot.realise_equal_rankings();
|
let mut b = ballot.realise_equal_rankings();
|
||||||
|
@ -405,7 +415,7 @@ impl<'a, N> Vote<'a, N> {
|
||||||
#[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))]
|
#[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))]
|
||||||
pub struct Ballot<N> {
|
pub struct Ballot<N> {
|
||||||
/// Original value/weight of the ballot
|
/// Original value/weight of the ballot
|
||||||
#[cfg_attr(not(target_arch = "wasm32"), with(SerializedNum))]
|
#[cfg_attr(not(target_arch = "wasm32"), with(SerializedNumber))]
|
||||||
pub orig_value: N,
|
pub orig_value: N,
|
||||||
/// Indexes of candidates preferenced at each level on the ballot
|
/// Indexes of candidates preferenced at each level on the ballot
|
||||||
pub preferences: Vec<Vec<usize>>,
|
pub preferences: Vec<Vec<usize>>,
|
||||||
|
@ -451,34 +461,6 @@ impl<N: Number> Ballot<N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// rkyv-serialized representation of [Number]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
pub struct SerializedNum;
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
impl<N: Number> ArchiveWith<N> for SerializedNum {
|
|
||||||
type Archived = Archived<String>;
|
|
||||||
type Resolver = Resolver<String>;
|
|
||||||
|
|
||||||
unsafe fn resolve_with(field: &N, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) {
|
|
||||||
field.to_string().resolve(pos, resolver, out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
impl<N: Number, S: Fallible + ?Sized> SerializeWith<N, S> for SerializedNum where String: Serialize<S> {
|
|
||||||
fn serialize_with(field: &N, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
|
|
||||||
return field.to_string().serialize(serializer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
impl<N: Number, D: Fallible + ?Sized> DeserializeWith<Archived<String>, N, D> for SerializedNum where Archived<String>: Deserialize<String, D> {
|
|
||||||
fn deserialize_with(field: &Archived<String>, deserializer: &mut D) -> Result<N, D::Error> {
|
|
||||||
return Ok(N::parse(&field.deserialize(deserializer)?));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// State of a [Candidate] during a count
|
/// State of a [Candidate] during a count
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
|
|
|
@ -32,6 +32,9 @@ mod rational_num;
|
||||||
|
|
||||||
use num_traits::{NumAssignRef, NumRef};
|
use num_traits::{NumAssignRef, NumRef};
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use rkyv::{Archive, Archived, Deserialize, Fallible, Resolver, Serialize, with::{ArchiveWith, DeserializeWith, SerializeWith}};
|
||||||
|
|
||||||
use std::cmp::Ord;
|
use std::cmp::Ord;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops;
|
use std::ops;
|
||||||
|
@ -76,6 +79,81 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// rkyv-serialized representation of [Number]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub struct SerializedNumber;
|
||||||
|
|
||||||
|
/// rkyv-serialized representation of [Option<Number>]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub struct SerializedOptionNumber;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
impl<N: Number> ArchiveWith<N> for SerializedNumber {
|
||||||
|
type Archived = Archived<String>;
|
||||||
|
type Resolver = Resolver<String>;
|
||||||
|
|
||||||
|
unsafe fn resolve_with(field: &N, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) {
|
||||||
|
field.to_string().resolve(pos, resolver, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
impl<N: Number> ArchiveWith<Option<N>> for SerializedOptionNumber {
|
||||||
|
type Archived = Archived<String>;
|
||||||
|
type Resolver = Resolver<String>;
|
||||||
|
|
||||||
|
unsafe fn resolve_with(field: &Option<N>, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) {
|
||||||
|
match field {
|
||||||
|
Some(n) => {
|
||||||
|
n.to_string().resolve(pos, resolver, out);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
String::new().resolve(pos, resolver, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
impl<N: Number, S: Fallible + ?Sized> SerializeWith<N, S> for SerializedNumber where String: Serialize<S> {
|
||||||
|
fn serialize_with(field: &N, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
|
||||||
|
return field.to_string().serialize(serializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
impl<N: Number, S: Fallible + ?Sized> SerializeWith<Option<N>, S> for SerializedOptionNumber where String: Serialize<S> {
|
||||||
|
fn serialize_with(field: &Option<N>, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
|
||||||
|
match field {
|
||||||
|
Some(n) => {
|
||||||
|
return n.to_string().serialize(serializer);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return String::new().serialize(serializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
impl<N: Number, D: Fallible + ?Sized> DeserializeWith<Archived<String>, N, D> for SerializedNumber where Archived<String>: Deserialize<String, D> {
|
||||||
|
fn deserialize_with(field: &Archived<String>, deserializer: &mut D) -> Result<N, D::Error> {
|
||||||
|
return Ok(N::parse(&field.deserialize(deserializer)?));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
impl<N: Number, D: Fallible + ?Sized> DeserializeWith<Archived<String>, Option<N>, D> for SerializedOptionNumber where Archived<String>: Deserialize<String, D> {
|
||||||
|
fn deserialize_with(field: &Archived<String>, deserializer: &mut D) -> Result<Option<N>, D::Error> {
|
||||||
|
let s = field.deserialize(deserializer)?;
|
||||||
|
if s.len() == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
} else {
|
||||||
|
return Ok(Some(N::parse(&s)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub use self::fixed::Fixed;
|
pub use self::fixed::Fixed;
|
||||||
pub use self::gfixed::GuardedFixed;
|
pub use self::gfixed::GuardedFixed;
|
||||||
pub use self::native::NativeFloat64;
|
pub use self::native::NativeFloat64;
|
||||||
|
|
|
@ -372,6 +372,7 @@ impl<N: Number, I: Iterator<Item=char>> BLTParser<N, I> {
|
||||||
candidates: Vec::new(),
|
candidates: Vec::new(),
|
||||||
withdrawn_candidates: Vec::new(),
|
withdrawn_candidates: Vec::new(),
|
||||||
ballots: Vec::new(),
|
ballots: Vec::new(),
|
||||||
|
total_votes: None,
|
||||||
constraints: None,
|
constraints: None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,7 @@ pub fn parse_reader<R: Read, N: Number>(reader: R) -> Election<N> {
|
||||||
candidates: candidates,
|
candidates: candidates,
|
||||||
withdrawn_candidates: Vec::new(),
|
withdrawn_candidates: Vec::new(),
|
||||||
ballots: ballots,
|
ballots: ballots,
|
||||||
|
total_votes: None,
|
||||||
constraints: None,
|
constraints: None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ use std::ops;
|
||||||
|
|
||||||
/// Distribute first preference votes according to the Gregory method
|
/// Distribute first preference votes according to the Gregory method
|
||||||
pub fn distribute_first_preferences<N: Number>(state: &mut CountState<N>, opts: &STVOptions)
|
pub fn distribute_first_preferences<N: Number>(state: &mut CountState<N>, opts: &STVOptions)
|
||||||
|
where
|
||||||
|
for<'r> &'r N: ops::Sub<&'r N, Output=N>
|
||||||
{
|
{
|
||||||
let votes = state.election.ballots.iter().map(|b| Vote {
|
let votes = state.election.ballots.iter().map(|b| Vote {
|
||||||
ballot: b,
|
ballot: b,
|
||||||
|
@ -66,6 +68,16 @@ pub fn distribute_first_preferences<N: Number>(state: &mut CountState<N>, opts:
|
||||||
state.exhausted.transfer(&result.exhausted.num_ballots);
|
state.exhausted.transfer(&result.exhausted.num_ballots);
|
||||||
state.exhausted.ballot_transfers += result.exhausted.num_ballots;
|
state.exhausted.ballot_transfers += result.exhausted.num_ballots;
|
||||||
|
|
||||||
|
// Calculate loss by fraction - if minivoters used
|
||||||
|
if let Some(orig_total) = &state.election.total_votes {
|
||||||
|
let mut total_votes = state.candidates.values().fold(N::new(), |acc, cc| acc + &cc.votes);
|
||||||
|
total_votes += &state.exhausted.votes;
|
||||||
|
let lbf = orig_total - &total_votes;
|
||||||
|
|
||||||
|
state.loss_fraction.votes = lbf.clone();
|
||||||
|
state.loss_fraction.transfers = lbf;
|
||||||
|
}
|
||||||
|
|
||||||
state.kind = None;
|
state.kind = None;
|
||||||
state.title = "First preferences".to_string();
|
state.title = "First preferences".to_string();
|
||||||
state.logger.log_literal("First preferences distributed.".to_string());
|
state.logger.log_literal("First preferences distributed.".to_string());
|
||||||
|
|
|
@ -127,6 +127,16 @@ where
|
||||||
}
|
}
|
||||||
state.exhausted.transfers.assign(&state.exhausted.votes);
|
state.exhausted.transfers.assign(&state.exhausted.votes);
|
||||||
|
|
||||||
|
// Calculate loss by fraction - if minivoters used
|
||||||
|
if let Some(orig_total) = &state.election.total_votes {
|
||||||
|
let mut total_votes = state.candidates.values().fold(N::new(), |acc, cc| acc + &cc.votes);
|
||||||
|
total_votes += &state.exhausted.votes;
|
||||||
|
let lbf = orig_total - &total_votes;
|
||||||
|
|
||||||
|
state.loss_fraction.votes = lbf.clone();
|
||||||
|
state.loss_fraction.transfers = lbf;
|
||||||
|
}
|
||||||
|
|
||||||
state.kind = None;
|
state.kind = None;
|
||||||
state.title = "First preferences".to_string();
|
state.title = "First preferences".to_string();
|
||||||
state.logger.log_literal("First preferences distributed.".to_string());
|
state.logger.log_literal("First preferences distributed.".to_string());
|
||||||
|
|
Loading…
Reference in New Issue