Skip to content

Commit

Permalink
bgufix for ranked pairs elimination
Browse files Browse the repository at this point in the history
  • Loading branch information
milselarch committed May 5, 2024
1 parent b28be14 commit 43b164f
Showing 1 changed file with 92 additions and 10 deletions.
102 changes: 92 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ impl TrieNode {
pub struct RankedChoiceVoteTrie {
root: TrieNode,
dowdall_score_map: HashMap<u16, f32>,
elimination_strategy: EliminationStrategies
elimination_strategy: EliminationStrategies,
unique_candidates: HashSet<u16>
}

struct VoteTransfer<'a> {
Expand Down Expand Up @@ -137,6 +138,13 @@ fn is_graph_weakly_connected(graph: &DiGraph<u16, u64>) -> bool {
let start_node = nodes[0];
queue.push_back(start_node);

let get_undirected_neighbors = |node: NodeIndex| {
let mut neighbors = Vec::<NodeIndex>::new();
neighbors.extend(graph.neighbors_directed(node, Direction::Incoming));
neighbors.extend(graph.neighbors_directed(node, Direction::Outgoing));
return neighbors;
};

// do a DFS search to see if all nodes are reachable from start_node
loop {
let node = match queue.pop_back() {
Expand All @@ -147,8 +155,8 @@ fn is_graph_weakly_connected(graph: &DiGraph<u16, u64>) -> bool {
if explored_nodes.contains(&node) { continue }
explored_nodes.insert(node);

// collects nodes that share incoming or outgoing edges
let neighbors: Vec<NodeIndex> = graph.neighbors(node).collect();
let neighbors: Vec<NodeIndex> = get_undirected_neighbors(node);
// println!("DFS {:?}", (node, &neighbors));
for neighbor in neighbors {
queue.push_back(neighbor)
}
Expand All @@ -163,6 +171,7 @@ impl RankedChoiceVoteTrie {
root: TrieNode::new(),
dowdall_score_map: Default::default(),
elimination_strategy: EliminationStrategies::DowdallScoring,
unique_candidates: Default::default(),
}
}

Expand All @@ -179,12 +188,14 @@ impl RankedChoiceVoteTrie {
pub fn insert_vote<'a>(&mut self, vote: RankedVote) {
let mut current = &mut self.root;
let vote_items = vote.iter().enumerate();
current.num_votes += 1;

for (ranking, vote_value) in vote_items {
// println!("ITEM {}", ranking);
match vote_value {
VoteValues::SpecialVote(_) => {}
VoteValues::Candidate(candidate) => {
self.unique_candidates.insert(candidate);
let score = *self.dowdall_score_map
.entry(candidate).or_insert(0f32);
let new_score = score + 1.0 / (ranking + 1) as f32;
Expand Down Expand Up @@ -246,6 +257,9 @@ impl RankedChoiceVoteTrie {
&self, candidates: Vec<u16>,
ranked_pairs_map: &HashMap<(u16, u16), u64>
) -> Vec<u16> {
// println!("\n----------------");
// println!("PRE_RANK_FILTER {:?}", candidates);
// println!("PAIRS_MAP {:?}", ranked_pairs_map);
let mut graph = DiGraph::<u16, u64>::new();
let mut node_map = HashMap::<u16, NodeIndex>::new();

Expand All @@ -264,6 +278,13 @@ impl RankedChoiceVoteTrie {
ranked_pairs_map.get(&(candidate2, candidate1))
.unwrap_or(&0);

/*
println!("C_PAIR {:?}", (
(candidate1, candidate2),
(preferred_over_votes, preferred_against_votes)
));
*/

return if preferred_over_votes > preferred_against_votes {
let strength = preferred_over_votes - preferred_against_votes;
(PairPreferences::PreferredOver, strength)
Expand All @@ -280,7 +301,23 @@ impl RankedChoiceVoteTrie {
node_map: &mut HashMap<u16, NodeIndex>,
candidate: u16
) -> NodeIndex {
*node_map.get(&candidate).unwrap_or(&graph.add_node(candidate))
// println!("NODE_MAP_PRE {:?}", (candidate, &node_map, &graph));
let node = match node_map.get(&candidate) {
Some(node) => { *node }
None => {
let node = graph.add_node(candidate);
node_map.insert(candidate, node);
node
}
};

// println!("NODE_MAP_POST {:?}", (candidate, &node_map, &graph));
return node;
}

// initialize all the nodes in the graph
for candidate in &candidates {
get_or_create_node(&mut graph, &mut node_map, *candidate);
}

// construct preference strength graph between candidates
Expand All @@ -301,12 +338,20 @@ impl RankedChoiceVoteTrie {
let node2_idx =
get_or_create_node(&mut graph, &mut node_map, *candidate2);
if !graph.contains_edge(node1_idx, node2_idx) {
// println!("ADD_EDGE {:?}", (node1_idx, node2_idx));
graph.add_edge(node1_idx, node2_idx, strength);
}
}

// println!("GRAPH {:?}", graph);
// unable to establish pecking order among candidates
if !(is_graph_acyclic(&graph) && is_graph_weakly_connected(&graph)) {
/*
println!("POST_RANK_FILTER {:?}", (
&candidates, is_graph_acyclic(&graph),
is_graph_weakly_connected(&graph))
);
*/
return candidates.clone();
}

Expand All @@ -320,6 +365,8 @@ impl RankedChoiceVoteTrie {

let weakest_candidates = weakest_nodes
.iter().map(|&index| graph[index]).collect();
// println!("POST_NODES {:?}", weakest_nodes);
// println!("POST_RANK_FILTER {:?}", weakest_candidates);
return weakest_candidates;
}

Expand Down Expand Up @@ -352,41 +399,74 @@ impl RankedChoiceVoteTrie {
let mut rcv = RankedChoiceVoteTrie {
root: Default::default(),
dowdall_score_map: Default::default(),
elimination_strategy: self.elimination_strategy.clone()
elimination_strategy: self.elimination_strategy.clone(),
unique_candidates: Default::default()
};
rcv.insert_votes(votes);
return rcv.determine_winner();
}

fn build_ranked_pairs_map(
&self, node: &TrieNode, search_path: &mut Vec<u16>,
ranked_pairs_map: &mut HashMap<(u16, u16), u64>
ranked_pairs_map: &mut HashMap<(u16, u16), u64>,
unique_candidates: &HashSet<u16>
) {
let kv_pairs_vec: Vec<(Box<&VoteValues>, Box<&TrieNode>)> =
node.children.iter().map(|(vote_value, node)| {
(Box::new(vote_value), Box::new(node))
}).collect();

// number of votes that terminate at node
let mut terminating_votes: u64 = node.num_votes;
// println!("NODE_VOTES {:?}", node.num_votes);

for (boxed_vote_value, boxed_child) in kv_pairs_vec {
let vote_value = *boxed_vote_value;
let child = *boxed_child;

// println!("CHILD_VOTES {:?}", child.num_votes);
assert!(terminating_votes >= child.num_votes);
terminating_votes -= child.num_votes;

let candidate = match vote_value {
VoteValues::SpecialVote(_) => { continue }
VoteValues::Candidate(candidate) => { candidate }
};

for preferred_candidate in search_path.iter() {
let ranked_pair = (*preferred_candidate, *candidate);
for preferable_candidate in search_path.iter() {
let ranked_pair = (*preferable_candidate, *candidate);
let pairwise_votes =
ranked_pairs_map.entry(ranked_pair).or_insert(0);
*pairwise_votes += child.num_votes;
}

search_path.push(*candidate);
self.build_ranked_pairs_map(child, search_path, ranked_pairs_map);
self.build_ranked_pairs_map(
child, search_path, ranked_pairs_map, unique_candidates
);
search_path.pop();
};

if terminating_votes > 0 {
// println!("UNIQUE {:?}", unique_candidates);
// println!("TERMINATE {:?}", (&search_path, terminating_votes));
// candidates who weren't explicitly listed in current vote path
let search_path: &Vec<u16> = search_path.as_ref();
let mut unspecified_candidates = unique_candidates.clone();
for candidate in search_path {
unspecified_candidates.remove(candidate);
}

for preferable_candidate in search_path {
for candidate in &unspecified_candidates {
let ranked_pair = (*preferable_candidate, *candidate);
let pairwise_votes =
ranked_pairs_map.entry(ranked_pair).or_insert(0);
// println!("INSERT {:?}", (ranked_pair, terminating_votes));
*pairwise_votes += terminating_votes;
}
}
}
}

pub fn determine_winner<'a>(&self) -> Option<u16> {
Expand All @@ -403,6 +483,7 @@ impl RankedChoiceVoteTrie {

let kv_pairs_vec: Vec<(&VoteValues, &TrieNode)> =
self.root.children.iter().collect();

for (vote_value, node) in kv_pairs_vec {
match vote_value {
VoteValues::SpecialVote(SpecialVotes::ABSTAIN) => {}
Expand All @@ -421,7 +502,8 @@ impl RankedChoiceVoteTrie {
let mut ranked_pairs_map: HashMap<(u16, u16), u64> = HashMap::new();
if self.elimination_strategy == EliminationStrategies::RankedPairs {
self.build_ranked_pairs_map(
&self.root, &mut Vec::new(), &mut ranked_pairs_map
&self.root, &mut Vec::new(), &mut ranked_pairs_map,
&self.unique_candidates
);
}

Expand Down

0 comments on commit 43b164f

Please sign in to comment.