Initial framework for equal rankings
This commit is contained in:
parent
27ead09960
commit
b0f869bf02
@ -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 \
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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]
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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)]
|
||||||
|
@ -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");
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user