Initial framework for equal rankings

This commit is contained in:
RunasSudo 2021-09-03 23:53:15 +10:00
parent 27ead09960
commit b0f869bf02
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
12 changed files with 107 additions and 24 deletions

View File

@ -14,6 +14,7 @@ llvm-profdata merge -sparse target/coverage/prof/*.profraw -o target/coverage/op
eval llvm-cov show target/coverage/debug/opentally -instr-profile=target/coverage/opentally.profdata -Xdemangler="$HOME/.cargo/bin/rustfilt" \ eval llvm-cov show target/coverage/debug/opentally -instr-profile=target/coverage/opentally.profdata -Xdemangler="$HOME/.cargo/bin/rustfilt" \
$(for file in $(cargo test --no-run --message-format=json 2>/dev/null | jq -r "select(.profile.test == true) | .filenames[]"); do echo -n --object '"'$file'" '; done) \ $(for file in $(cargo test --no-run --message-format=json 2>/dev/null | jq -r "select(.profile.test == true) | .filenames[]"); do echo -n --object '"'$file'" '; done) \
-ignore-filename-regex="$HOME/." \ -ignore-filename-regex="$HOME/." \
-ignore-filename-regex=rustc \
-ignore-filename-regex=numbers/rational_num.rs \ -ignore-filename-regex=numbers/rational_num.rs \
-ignore-filename-regex=stv/wasm.rs \ -ignore-filename-regex=stv/wasm.rs \
-ignore-filename-regex=tests \ -ignore-filename-regex=tests \

View File

@ -44,10 +44,14 @@ onmessage = function(evt) {
// Init election // Init election
election = wasm['election_from_blt_' + numbers](evt.data.bltData); election = wasm['election_from_blt_' + numbers](evt.data.bltData);
// Normalise ballots if requested
if (evt.data.normaliseBallots) { if (evt.data.normaliseBallots) {
wasm['election_normalise_ballots_' + numbers](election); wasm['election_normalise_ballots_' + numbers](election);
} }
// Process equal rankings
wasm['election_realise_equal_rankings_' + numbers](election);
// Init constraints if applicable // Init constraints if applicable
if (evt.data.conData) { if (evt.data.conData) {
wasm['election_load_constraints_' + numbers](election, evt.data.conData); wasm['election_load_constraints_' + numbers](election, evt.data.conData);

View File

@ -309,6 +309,9 @@ where
election.normalise_ballots(); election.normalise_ballots();
} }
// Process equal rankings
election.realise_equal_rankings();
// Initialise count state // Initialise count state
let mut state = CountState::new(&election); let mut state = CountState::new(&election);

View File

@ -47,6 +47,8 @@ pub struct Election<N> {
impl<N: Number> Election<N> { impl<N: Number> Election<N> {
/// Convert ballots with weight >1 to multiple ballots of weight 1 /// Convert ballots with weight >1 to multiple ballots of weight 1
///
/// Assumes ballots have integer weight.
pub fn normalise_ballots(&mut self) { pub fn normalise_ballots(&mut self) {
let mut normalised_ballots = Vec::new(); let mut normalised_ballots = Vec::new();
for ballot in self.ballots.iter() { for ballot in self.ballots.iter() {
@ -63,6 +65,16 @@ impl<N: Number> Election<N> {
} }
self.ballots = normalised_ballots; self.ballots = normalised_ballots;
} }
/// Convert ballots with equal rankings to strict-preference "minivoters"
pub fn realise_equal_rankings(&mut self) {
let mut realised_ballots = Vec::new();
for ballot in self.ballots.iter() {
let mut b = ballot.realise_equal_rankings();
realised_ballots.append(&mut b);
}
self.ballots = realised_ballots;
}
} }
/// A candidate in an [Election] /// A candidate in an [Election]
@ -74,7 +86,6 @@ pub struct Candidate {
} }
/// The current state of counting an [Election] /// The current state of counting an [Election]
//#[derive(Clone)]
pub struct CountState<'a, N: Number> { pub struct CountState<'a, N: Number> {
/// Pointer to the [Election] being counted /// Pointer to the [Election] being counted
pub election: &'a Election<N>, pub election: &'a Election<N>,
@ -368,6 +379,22 @@ pub struct Vote<'a, N> {
pub up_to_pref: usize, pub up_to_pref: usize,
} }
impl<'a, N> Vote<'a, N> {
/// Get the next preference and increment `up_to_pref`
///
/// Assumes that each preference level contains only one preference.
pub fn next_preference(&mut self) -> Option<usize> {
if self.up_to_pref >= self.ballot.preferences.len() {
return None;
}
let preference = &self.ballot.preferences[self.up_to_pref];
self.up_to_pref += 1;
return Some(*preference.first().unwrap());
}
}
/// A record of a voter's preferences /// A record of a voter's preferences
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))] #[cfg_attr(not(target_arch = "wasm32"), derive(Archive, Deserialize, Serialize))]
@ -375,8 +402,32 @@ 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(SerializedNum))]
pub orig_value: N, pub orig_value: N,
/// Indexes of candidates preferenced on the ballot /// Indexes of candidates preferenced at each level on the ballot
pub preferences: Vec<usize>, pub preferences: Vec<Vec<usize>>,
}
impl<N: Number> Ballot<N> {
/// Convert ballot with equal rankings to strict-preference "minivoters"
pub fn realise_equal_rankings(&self) -> Vec<Ballot<N>> {
// Preferences for each minivoter
let mut minivoters = vec![Vec::new()];
for preference in self.preferences.iter() {
if preference.len() == 1 {
for minivoter in minivoters.iter_mut() {
minivoter.push(preference.clone());
}
} else {
todo!();
}
}
let weight_each = self.orig_value.clone() / N::from(minivoters.len());
let ballots = minivoters.into_iter()
.map(|p| Ballot { orig_value: weight_each.clone(), preferences: p })
.collect();
return ballots;
}
} }
/// rkyv-serialized representation of [Number] /// rkyv-serialized representation of [Number]

View File

@ -151,7 +151,7 @@ impl<N: Number, I: Iterator<Item=char>> BLTParser<N, I> {
self.accept(); self.accept();
break; break;
} else { } else {
preferences.push(self.usize()? - 1); preferences.push(vec![self.usize()? - 1]);
self.delimiter_not_nl(); self.delimiter_not_nl();
} }
} }

View File

@ -87,7 +87,7 @@ pub fn parse_reader<R: Read, N: Number>(reader: R) -> Election<N> {
ballots.push(Ballot { ballots.push(Ballot {
orig_value: value, orig_value: value,
preferences: preferences.into_iter().map(|(_, i)| *i).collect(), preferences: preferences.into_iter().map(|(_, i)| vec![*i]).collect(),
}); });
} }

View File

@ -60,7 +60,13 @@ impl<'t, N: Number> BallotTree<'t, N> {
for bit in self.ballots.iter() { for bit in self.ballots.iter() {
if bit.up_to_pref < bit.ballot.preferences.len() { if bit.up_to_pref < bit.ballot.preferences.len() {
let candidate = &candidates[bit.ballot.preferences[bit.up_to_pref]]; let preference = &bit.ballot.preferences[bit.up_to_pref];
if preference.len() != 1 {
todo!();
}
let candidate = &candidates[*preference.first().unwrap()];
if next_preferences.contains_key(candidate) { if next_preferences.contains_key(candidate) {
let np_bt = next_preferences.get_mut(candidate).unwrap(); let np_bt = next_preferences.get_mut(candidate).unwrap();

View File

@ -692,16 +692,20 @@ fn next_preferences<'a, N: Number>(state: &CountState<'a, N>, votes: Vec<Vote<'a
let mut next_candidate = None; let mut next_candidate = None;
for (i, preference) in vote.ballot.preferences.iter().enumerate().skip(vote.up_to_pref) { loop {
let candidate = &state.election.candidates[*preference]; match vote.next_preference() {
Some(preference) => {
let candidate = &state.election.candidates[preference];
let count_card = &state.candidates[candidate]; let count_card = &state.candidates[candidate];
if let CandidateState::Hopeful | CandidateState::Guarded = count_card.state { if let CandidateState::Hopeful | CandidateState::Guarded = count_card.state {
next_candidate = Some(candidate); next_candidate = Some(candidate);
vote.up_to_pref = i + 1;
break; break;
} }
} }
None => { break; }
}
}
// Have to structure like this to satisfy Rust's borrow checker // Have to structure like this to satisfy Rust's borrow checker
if let Some(candidate) = next_candidate { if let Some(candidate) = next_candidate {

View File

@ -275,16 +275,21 @@ where
fn transfer_ballot<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, source_candidate: &Candidate, mut vote: Vote<'a, N>, ignore_nontransferable: bool) -> Result<(), STVError> { fn transfer_ballot<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions, source_candidate: &Candidate, mut vote: Vote<'a, N>, ignore_nontransferable: bool) -> Result<(), STVError> {
// Get next preference // Get next preference
let mut next_candidate = None; let mut next_candidate = None;
for (i, preference) in vote.ballot.preferences.iter().enumerate().skip(vote.up_to_pref) {
let candidate = &state.election.candidates[*preference]; loop {
match vote.next_preference() {
Some(preference) => {
let candidate = &state.election.candidates[preference];
let count_card = &state.candidates[candidate]; let count_card = &state.candidates[candidate];
if let CandidateState::Hopeful | CandidateState::Guarded = count_card.state { if let CandidateState::Hopeful | CandidateState::Guarded = count_card.state {
next_candidate = Some(candidate); next_candidate = Some(candidate);
vote.up_to_pref = i + 1;
break; break;
} }
} }
None => { break; }
}
}
// Have to structure like this to satisfy Rust's borrow checker // Have to structure like this to satisfy Rust's borrow checker
if let Some(candidate) = next_candidate { if let Some(candidate) = next_candidate {

View File

@ -87,6 +87,13 @@ macro_rules! impl_type {
election.0.normalise_ballots(); election.0.normalise_ballots();
} }
/// Wrapper for [Election::realise_equal_rankings]
#[wasm_bindgen]
#[allow(non_snake_case)]
pub fn [<election_realise_equal_rankings_$type>](election: &mut [<Election$type>]) {
election.0.realise_equal_rankings();
}
/// Call [Constraints::from_con] and set [Election::constraints] /// Call [Constraints::from_con] and set [Election::constraints]
#[wasm_bindgen] #[wasm_bindgen]
#[allow(non_snake_case)] #[allow(non_snake_case)]

View File

@ -40,7 +40,7 @@ pub fn write<W: Write, N: Number>(election: Election<N>, output: W) {
output.write_fmt(format_args!("{}", ballot.orig_value)).expect("IO Error"); output.write_fmt(format_args!("{}", ballot.orig_value)).expect("IO Error");
for preference in ballot.preferences { for preference in ballot.preferences {
output.write_fmt(format_args!(" {}", preference + 1)).expect("IO Error"); output.write_fmt(format_args!(" {}", preference.into_iter().map(|p| p + 1).join("="))).expect("IO Error");
} }
output.write(b" 0\n").expect("IO Error"); output.write(b" 0\n").expect("IO Error");

View File

@ -40,7 +40,9 @@ pub fn write<W: Write, N: Number>(election: Election<N>, output: W) {
// Code preferences to rankings // Code preferences to rankings
let mut rankings = vec![0_usize; election.candidates.len()]; let mut rankings = vec![0_usize; election.candidates.len()];
for (i, preference) in ballot.preferences.into_iter().enumerate() { for (i, preference) in ballot.preferences.into_iter().enumerate() {
rankings[preference] = i + 1; for p in preference {
rankings[p] = i + 1;
}
} }
// Write rankings // Write rankings