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::stv::{QuotaMode, STVOptions};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use rkyv::{Archive, Archived, Deserialize, Fallible, Resolver, Serialize, with::{ArchiveWith, DeserializeWith, SerializeWith}};
|
||||
|
||||
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::collections::HashMap;
|
||||
|
||||
|
@ -43,6 +45,11 @@ pub struct Election<N> {
|
|||
pub withdrawn_candidates: Vec<usize>,
|
||||
/// [Vec] of [Ballot]s cast in the election
|
||||
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
|
||||
pub constraints: Option<Constraints>,
|
||||
}
|
||||
|
@ -70,6 +77,9 @@ impl<N: Number> Election<N> {
|
|||
|
||||
/// Convert ballots with equal rankings to strict-preference "minivoters"
|
||||
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();
|
||||
for ballot in self.ballots.iter() {
|
||||
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))]
|
||||
pub struct Ballot<N> {
|
||||
/// 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,
|
||||
/// Indexes of candidates preferenced at each level on the ballot
|
||||
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
|
||||
#[allow(dead_code)]
|
||||
#[derive(PartialEq)]
|
||||
|
|
|
@ -32,6 +32,9 @@ mod rational_num;
|
|||
|
||||
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::fmt;
|
||||
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::gfixed::GuardedFixed;
|
||||
pub use self::native::NativeFloat64;
|
||||
|
|
|
@ -372,6 +372,7 @@ impl<N: Number, I: Iterator<Item=char>> BLTParser<N, I> {
|
|||
candidates: Vec::new(),
|
||||
withdrawn_candidates: Vec::new(),
|
||||
ballots: Vec::new(),
|
||||
total_votes: None,
|
||||
constraints: None,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -107,6 +107,7 @@ pub fn parse_reader<R: Read, N: Number>(reader: R) -> Election<N> {
|
|||
candidates: candidates,
|
||||
withdrawn_candidates: Vec::new(),
|
||||
ballots: ballots,
|
||||
total_votes: None,
|
||||
constraints: None,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ use std::ops;
|
|||
|
||||
/// Distribute first preference votes according to the Gregory method
|
||||
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 {
|
||||
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.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.title = "First preferences".to_string();
|
||||
state.logger.log_literal("First preferences distributed.".to_string());
|
||||
|
|
|
@ -127,6 +127,16 @@ where
|
|||
}
|
||||
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.title = "First preferences".to_string();
|
||||
state.logger.log_literal("First preferences distributed.".to_string());
|
||||
|
|
Loading…
Reference in New Issue