Calculate loss by fraction introduced by minivoters with equal rankings

This commit is contained in:
RunasSudo 2021-09-05 00:04:09 +10:00
parent 90971e976a
commit 2475b42056
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
6 changed files with 116 additions and 32 deletions

View File

@ -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)]

View File

@ -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;

View File

@ -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,
},
}

View File

@ -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,
};
}

View File

@ -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());

View File

@ -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());