use crate::{
balance, setup_inputs, BalancingConfig, CandidatePtr, ElectionResult, ExtendedBalance,
IdentifierT, PerThing128, VoteWeight, Voter,
};
use alloc::{rc::Rc, vec, vec::Vec};
use sp_arithmetic::{traits::Bounded, PerThing, Rational128};
pub fn phragmms<AccountId: IdentifierT, P: PerThing128>(
to_elect: usize,
candidates: Vec<AccountId>,
voters: Vec<(AccountId, VoteWeight, impl IntoIterator<Item = AccountId>)>,
balancing: Option<BalancingConfig>,
) -> Result<ElectionResult<AccountId, P>, crate::Error> {
let (candidates, mut voters) = setup_inputs(candidates, voters);
let mut winners = vec![];
for round in 0..to_elect {
if let Some(round_winner) = calculate_max_score::<AccountId, P>(&candidates, &voters) {
apply_elected::<AccountId>(&mut voters, Rc::clone(&round_winner));
round_winner.borrow_mut().round = round;
round_winner.borrow_mut().elected = true;
winners.push(round_winner);
if let Some(ref config) = balancing {
balance(&mut voters, config);
}
} else {
break
}
}
let mut assignments =
voters.into_iter().filter_map(|v| v.into_assignment()).collect::<Vec<_>>();
let _ = assignments
.iter_mut()
.try_for_each(|a| a.try_normalize())
.map_err(crate::Error::ArithmeticError)?;
let winners = winners
.into_iter()
.map(|w_ptr| (w_ptr.borrow().who.clone(), w_ptr.borrow().backed_stake))
.collect();
Ok(ElectionResult { winners, assignments })
}
pub(crate) fn calculate_max_score<AccountId: IdentifierT, P: PerThing>(
candidates: &[CandidatePtr<AccountId>],
voters: &[Voter<AccountId>],
) -> Option<CandidatePtr<AccountId>> {
for c_ptr in candidates.iter() {
let mut candidate = c_ptr.borrow_mut();
if !candidate.elected {
candidate.score = Rational128::from(1, P::ACCURACY.into());
}
}
for voter in voters.iter() {
let mut denominator_contribution: ExtendedBalance = 0;
for edge in voter.edges.iter() {
let edge_candidate = edge.candidate.borrow();
if edge_candidate.elected {
let edge_contribution: ExtendedBalance =
P::from_rational(edge.weight, edge_candidate.backed_stake).deconstruct().into();
denominator_contribution += edge_contribution;
}
}
for edge in voter.edges.iter() {
let mut edge_candidate = edge.candidate.borrow_mut();
if !edge_candidate.elected {
let prev_d = edge_candidate.score.d();
edge_candidate.score = Rational128::from(1, denominator_contribution + prev_d);
}
}
}
let mut best_score = Rational128::zero();
let mut best_candidate = None;
for c_ptr in candidates.iter() {
let mut candidate = c_ptr.borrow_mut();
if candidate.approval_stake > 0 {
let score_d = candidate.score.d();
let one: ExtendedBalance = P::ACCURACY.into();
let score_n =
candidate.approval_stake.checked_mul(one).unwrap_or_else(Bounded::max_value);
candidate.score = Rational128::from(score_n, score_d);
if !candidate.elected && candidate.score > best_score {
best_score = candidate.score;
best_candidate = Some(Rc::clone(c_ptr));
}
} else {
candidate.score = Rational128::zero();
}
}
best_candidate
}
pub(crate) fn apply_elected<AccountId: IdentifierT>(
voters: &mut Vec<Voter<AccountId>>,
elected_ptr: CandidatePtr<AccountId>,
) {
let elected_who = elected_ptr.borrow().who.clone();
let cutoff = elected_ptr
.borrow()
.score
.to_den(1)
.expect("(n / d) < u128::MAX and (n' / 1) == (n / d), thus n' < u128::MAX'; qed.")
.n();
let mut elected_backed_stake = elected_ptr.borrow().backed_stake;
for voter in voters {
if let Some(new_edge_index) = voter.edges.iter().position(|e| e.who == elected_who) {
let used_budget: ExtendedBalance = voter.edges.iter().map(|e| e.weight).sum();
let mut new_edge_weight = voter.budget.saturating_sub(used_budget);
elected_backed_stake = elected_backed_stake.saturating_add(new_edge_weight);
for (_, edge) in
voter.edges.iter_mut().enumerate().filter(|(edge_index, edge_inner)| {
*edge_index != new_edge_index && edge_inner.weight > 0
}) {
let mut edge_candidate = edge.candidate.borrow_mut();
if edge_candidate.backed_stake > cutoff {
let stake_to_take =
edge.weight.saturating_mul(cutoff) / edge_candidate.backed_stake.max(1);
edge.weight = edge.weight.saturating_sub(stake_to_take);
edge_candidate.backed_stake =
edge_candidate.backed_stake.saturating_sub(stake_to_take);
elected_backed_stake = elected_backed_stake.saturating_add(stake_to_take);
new_edge_weight = new_edge_weight.saturating_add(stake_to_take);
}
}
voter.edges[new_edge_index].weight = new_edge_weight;
}
}
elected_ptr.borrow_mut().backed_stake = elected_backed_stake;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Assignment, ElectionResult};
use alloc::rc::Rc;
use sp_runtime::{Perbill, Percent};
#[test]
fn basic_election_manual_works() {
let candidates = vec![1, 2, 3];
let voters = vec![(10, 10, vec![1, 2]), (20, 20, vec![1, 3]), (30, 30, vec![2, 3])];
let (candidates, mut voters) = setup_inputs(candidates, voters);
let winner =
calculate_max_score::<u32, Percent>(candidates.as_ref(), voters.as_ref()).unwrap();
assert_eq!(winner.borrow().who, 3);
assert_eq!(winner.borrow().score, 50u32.into());
apply_elected(&mut voters, Rc::clone(&winner));
assert_eq!(
voters
.iter()
.find(|x| x.who == 30)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(30, vec![(2, 0), (3, 30)]),
);
assert_eq!(
voters
.iter()
.find(|x| x.who == 20)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(20, vec![(1, 0), (3, 20)]),
);
winner.borrow_mut().elected = true;
winner.borrow_mut().round = 0;
drop(winner);
let config = BalancingConfig { iterations: 10, tolerance: 0 };
balance(&mut voters, &config);
let winner =
calculate_max_score::<u32, Percent>(candidates.as_ref(), voters.as_ref()).unwrap();
assert_eq!(winner.borrow().who, 2);
assert_eq!(winner.borrow().score, 25u32.into());
apply_elected(&mut voters, Rc::clone(&winner));
assert_eq!(
voters
.iter()
.find(|x| x.who == 30)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(30, vec![(2, 15), (3, 15)]),
);
assert_eq!(
voters
.iter()
.find(|x| x.who == 20)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(20, vec![(1, 0), (3, 20)]),
);
assert_eq!(
voters
.iter()
.find(|x| x.who == 10)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(10, vec![(1, 0), (2, 10)]),
);
winner.borrow_mut().elected = true;
winner.borrow_mut().round = 0;
drop(winner);
balance(&mut voters, &config);
assert_eq!(
voters
.iter()
.find(|x| x.who == 30)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(30, vec![(2, 20), (3, 10)]),
);
assert_eq!(
voters
.iter()
.find(|x| x.who == 20)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(20, vec![(1, 0), (3, 20)]),
);
assert_eq!(
voters
.iter()
.find(|x| x.who == 10)
.map(|v| (v.who, v.edges.iter().map(|e| (e.who, e.weight)).collect::<Vec<_>>()))
.unwrap(),
(10, vec![(1, 0), (2, 10)]),
);
}
#[test]
fn basic_election_works() {
let candidates = vec![1, 2, 3];
let voters = vec![(10, 10, vec![1, 2]), (20, 20, vec![1, 3]), (30, 30, vec![2, 3])];
let config = BalancingConfig { iterations: 2, tolerance: 0 };
let ElectionResult::<_, Perbill> { winners, assignments } =
phragmms(2, candidates, voters, Some(config)).unwrap();
assert_eq!(winners, vec![(3, 30), (2, 30)]);
assert_eq!(
assignments,
vec![
Assignment { who: 10u64, distribution: vec![(2, Perbill::one())] },
Assignment { who: 20, distribution: vec![(3, Perbill::one())] },
Assignment {
who: 30,
distribution: vec![
(2, Perbill::from_parts(666666666)),
(3, Perbill::from_parts(333333334)),
],
},
]
)
}
#[test]
fn linear_voting_example_works() {
let candidates = vec![11, 21, 31, 41, 51, 61, 71];
let voters = vec![
(2, 2000, vec![11]),
(4, 1000, vec![11, 21]),
(6, 1000, vec![21, 31]),
(8, 1000, vec![31, 41]),
(110, 1000, vec![41, 51]),
(120, 1000, vec![51, 61]),
(130, 1000, vec![61, 71]),
];
let config = BalancingConfig { iterations: 2, tolerance: 0 };
let ElectionResult::<_, Perbill> { winners, assignments: _ } =
phragmms(4, candidates, voters, Some(config)).unwrap();
assert_eq!(winners, vec![(11, 3000), (31, 2000), (51, 1500), (61, 1500),]);
}
#[test]
fn large_balance_wont_overflow() {
let candidates = vec![1u32, 2, 3];
let mut voters = (0..1000).map(|i| (10 + i, u64::MAX, vec![1, 2, 3])).collect::<Vec<_>>();
voters.push((2, u64::MAX, vec![1, 3]));
let config = BalancingConfig { iterations: 2, tolerance: 0 };
let ElectionResult::<_, Perbill> { winners, assignments: _ } =
phragmms(2, candidates, voters, Some(config)).unwrap();
assert_eq!(winners.into_iter().map(|(w, _)| w).collect::<Vec<_>>(), vec![1u32, 3]);
}
}