mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 04:41:03 +00:00
clean the interface of supports map (#9674)
* clean the interface of supports map, make it a bit cleaner and more efficients * Fix stiff * fix one test * Fix warnings
This commit is contained in:
@@ -24,7 +24,7 @@ use honggfuzz::fuzz;
|
||||
use rand::{self, SeedableRng};
|
||||
use sp_npos_elections::{
|
||||
assignment_ratio_to_staked_normalized, is_score_better, seq_phragmen, to_supports,
|
||||
to_without_backing, EvaluateSupport, VoteWeight,
|
||||
EvaluateSupport, VoteWeight,
|
||||
};
|
||||
use sp_runtime::Perbill;
|
||||
|
||||
@@ -58,8 +58,7 @@ fn main() {
|
||||
&stake_of,
|
||||
)
|
||||
.unwrap();
|
||||
let winners = to_without_backing(unbalanced.winners.clone());
|
||||
let score = to_supports(winners.as_ref(), staked.as_ref()).unwrap().evaluate();
|
||||
let score = to_supports(staked.as_ref()).evaluate();
|
||||
|
||||
if score[0] == 0 {
|
||||
// such cases cannot be improved by balancing.
|
||||
@@ -83,8 +82,7 @@ fn main() {
|
||||
&stake_of,
|
||||
)
|
||||
.unwrap();
|
||||
let winners = to_without_backing(balanced.winners);
|
||||
to_supports(winners.as_ref(), staked.as_ref()).unwrap().evaluate()
|
||||
to_supports(staked.as_ref()).evaluate()
|
||||
};
|
||||
|
||||
let enhance = is_score_better(balanced_score, unbalanced_score, Perbill::zero());
|
||||
|
||||
@@ -23,8 +23,8 @@ use common::*;
|
||||
use honggfuzz::fuzz;
|
||||
use rand::{self, SeedableRng};
|
||||
use sp_npos_elections::{
|
||||
assignment_ratio_to_staked_normalized, is_score_better, phragmms, to_supports,
|
||||
to_without_backing, EvaluateSupport, VoteWeight,
|
||||
assignment_ratio_to_staked_normalized, is_score_better, phragmms, to_supports, EvaluateSupport,
|
||||
VoteWeight,
|
||||
};
|
||||
use sp_runtime::Perbill;
|
||||
|
||||
@@ -58,8 +58,7 @@ fn main() {
|
||||
&stake_of,
|
||||
)
|
||||
.unwrap();
|
||||
let winners = to_without_backing(unbalanced.winners.clone());
|
||||
let score = to_supports(&winners, &staked).unwrap().evaluate();
|
||||
let score = to_supports(&staked).evaluate();
|
||||
|
||||
if score[0] == 0 {
|
||||
// such cases cannot be improved by balancing.
|
||||
@@ -80,8 +79,7 @@ fn main() {
|
||||
let staked =
|
||||
assignment_ratio_to_staked_normalized(balanced.assignments.clone(), &stake_of)
|
||||
.unwrap();
|
||||
let winners = to_without_backing(balanced.winners);
|
||||
to_supports(winners.as_ref(), staked.as_ref()).unwrap().evaluate()
|
||||
to_supports(staked.as_ref()).evaluate()
|
||||
};
|
||||
|
||||
let enhance = is_score_better(balanced_score, unbalanced_score, Perbill::zero());
|
||||
|
||||
@@ -104,13 +104,11 @@ fn generate_random_phragmen_assignment(
|
||||
}
|
||||
|
||||
fn assert_assignments_equal(
|
||||
winners: &Vec<AccountId>,
|
||||
ass1: &Vec<StakedAssignment<AccountId>>,
|
||||
ass2: &Vec<StakedAssignment<AccountId>>,
|
||||
) {
|
||||
let support_1 = to_support_map::<AccountId>(winners, ass1).unwrap();
|
||||
let support_2 = to_support_map::<AccountId>(winners, ass2).unwrap();
|
||||
|
||||
let support_1 = to_support_map::<AccountId>(ass1);
|
||||
let support_2 = to_support_map::<AccountId>(ass2);
|
||||
for (who, support) in support_1.iter() {
|
||||
assert_eq!(support.total, support_2.get(who).unwrap().total);
|
||||
}
|
||||
@@ -134,7 +132,7 @@ fn reduce_and_compare(assignment: &Vec<StakedAssignment<AccountId>>, winners: &V
|
||||
num_changed,
|
||||
);
|
||||
|
||||
assert_assignments_equal(winners, &assignment, &altered_assignment);
|
||||
assert_assignments_equal(&assignment, &altered_assignment);
|
||||
}
|
||||
|
||||
fn assignment_len(assignments: &[StakedAssignment<AccountId>]) -> u32 {
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
|
||||
//! Helper methods for npos-elections.
|
||||
|
||||
use crate::{
|
||||
Assignment, Error, IdentifierT, PerThing128, StakedAssignment, VoteWeight, WithApprovalOf,
|
||||
};
|
||||
use crate::{Assignment, Error, IdentifierT, PerThing128, StakedAssignment, VoteWeight};
|
||||
use sp_arithmetic::PerThing;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
@@ -81,11 +79,6 @@ pub fn assignment_staked_to_ratio_normalized<A: IdentifierT, P: PerThing128>(
|
||||
Ok(ratio)
|
||||
}
|
||||
|
||||
/// consumes a vector of winners with backing stake to just winners.
|
||||
pub fn to_without_backing<A: IdentifierT>(winners: Vec<WithApprovalOf<A>>) -> Vec<A> {
|
||||
winners.into_iter().map(|(who, _)| who).collect::<Vec<A>>()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -146,9 +146,6 @@ pub type ExtendedBalance = u128;
|
||||
/// [`EvaluateSupport::evaluate`].
|
||||
pub type ElectionScore = [ExtendedBalance; 3];
|
||||
|
||||
/// A winner, with their respective approval stake.
|
||||
pub type WithApprovalOf<A> = (A, ExtendedBalance);
|
||||
|
||||
/// A pointer to a candidate struct with interior mutability.
|
||||
pub type CandidatePtr<A> = Rc<RefCell<Candidate<A>>>;
|
||||
|
||||
@@ -327,7 +324,7 @@ impl<AccountId: IdentifierT> Voter<AccountId> {
|
||||
pub struct ElectionResult<AccountId, P: PerThing> {
|
||||
/// Just winners zipped with their approval stake. Note that the approval stake is merely the
|
||||
/// sub of their received stake and could be used for very basic sorting and approval voting.
|
||||
pub winners: Vec<WithApprovalOf<AccountId>>,
|
||||
pub winners: Vec<(AccountId, ExtendedBalance)>,
|
||||
/// Individual assignments. for each tuple, the first elements is a voter and the second is the
|
||||
/// list of candidates that it supports.
|
||||
pub assignments: Vec<Assignment<AccountId, P>>,
|
||||
@@ -361,107 +358,50 @@ pub type Supports<A> = Vec<(A, Support<A>)>;
|
||||
/// This is more helpful than a normal [`Supports`] as it allows faster error checking.
|
||||
pub type SupportMap<A> = BTreeMap<A, Support<A>>;
|
||||
|
||||
/// Helper trait to convert from a support map to a flat support vector.
|
||||
pub trait FlattenSupportMap<A> {
|
||||
/// Flatten the support.
|
||||
fn flatten(self) -> Supports<A>;
|
||||
}
|
||||
|
||||
impl<A> FlattenSupportMap<A> for SupportMap<A> {
|
||||
fn flatten(self) -> Supports<A> {
|
||||
self.into_iter().collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the support map from the winners and assignments.
|
||||
///
|
||||
/// The list of winners is basically a redundancy for error checking only; It ensures that all the
|
||||
/// targets pointed to by the [`Assignment`] are present in the `winners`.
|
||||
/// Build the support map from the assignments.
|
||||
pub fn to_support_map<AccountId: IdentifierT>(
|
||||
winners: &[AccountId],
|
||||
assignments: &[StakedAssignment<AccountId>],
|
||||
) -> Result<SupportMap<AccountId>, Error> {
|
||||
// Initialize the support of each candidate.
|
||||
let mut supports = <SupportMap<AccountId>>::new();
|
||||
winners.iter().for_each(|e| {
|
||||
supports.insert(e.clone(), Default::default());
|
||||
});
|
||||
) -> SupportMap<AccountId> {
|
||||
let mut supports = <BTreeMap<AccountId, Support<AccountId>>>::new();
|
||||
|
||||
// build support struct.
|
||||
for StakedAssignment { who, distribution } in assignments.iter() {
|
||||
for (c, weight_extended) in distribution.iter() {
|
||||
if let Some(support) = supports.get_mut(c) {
|
||||
support.total = support.total.saturating_add(*weight_extended);
|
||||
support.voters.push((who.clone(), *weight_extended));
|
||||
} else {
|
||||
return Err(Error::InvalidSupportEdge)
|
||||
}
|
||||
for StakedAssignment { who, distribution } in assignments.into_iter() {
|
||||
for (c, weight_extended) in distribution.into_iter() {
|
||||
let mut support = supports.entry(c.clone()).or_default();
|
||||
support.total = support.total.saturating_add(*weight_extended);
|
||||
support.voters.push((who.clone(), *weight_extended));
|
||||
}
|
||||
}
|
||||
Ok(supports)
|
||||
|
||||
supports
|
||||
}
|
||||
|
||||
/// Same as [`to_support_map`] except it calls `FlattenSupportMap` on top of the result to return a
|
||||
/// Same as [`to_support_map`] except it returns a
|
||||
/// flat vector.
|
||||
///
|
||||
/// Similar to [`to_support_map`], `winners` is used for error checking.
|
||||
pub fn to_supports<AccountId: IdentifierT>(
|
||||
winners: &[AccountId],
|
||||
assignments: &[StakedAssignment<AccountId>],
|
||||
) -> Result<Supports<AccountId>, Error> {
|
||||
to_support_map(winners, assignments).map(FlattenSupportMap::flatten)
|
||||
) -> Supports<AccountId> {
|
||||
to_support_map(assignments).into_iter().collect()
|
||||
}
|
||||
|
||||
/// Extension trait for evaluating a support map or vector.
|
||||
pub trait EvaluateSupport<K> {
|
||||
pub trait EvaluateSupport {
|
||||
/// Evaluate a support map. The returned tuple contains:
|
||||
///
|
||||
/// - Minimum support. This value must be **maximized**.
|
||||
/// - Sum of all supports. This value must be **maximized**.
|
||||
/// - Sum of all supports squared. This value must be **minimized**.
|
||||
fn evaluate(self) -> ElectionScore;
|
||||
fn evaluate(&self) -> ElectionScore;
|
||||
}
|
||||
|
||||
/// A common wrapper trait for both (&A, &B) and &(A, B).
|
||||
///
|
||||
/// This allows us to implemented something for both `Vec<_>` and `BTreeMap<_>`, such as
|
||||
/// [`EvaluateSupport`].
|
||||
pub trait TupleRef<K, V> {
|
||||
fn extract(&self) -> (&K, &V);
|
||||
}
|
||||
|
||||
impl<K, V> TupleRef<K, V> for &(K, V) {
|
||||
fn extract(&self) -> (&K, &V) {
|
||||
(&self.0, &self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> TupleRef<K, V> for (K, V) {
|
||||
fn extract(&self) -> (&K, &V) {
|
||||
(&self.0, &self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> TupleRef<K, V> for (&K, &V) {
|
||||
fn extract(&self) -> (&K, &V) {
|
||||
(self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, C, I> EvaluateSupport<A> for C
|
||||
where
|
||||
C: IntoIterator<Item = I>,
|
||||
I: TupleRef<A, Support<A>>,
|
||||
A: IdentifierT,
|
||||
{
|
||||
fn evaluate(self) -> ElectionScore {
|
||||
impl<AccountId: IdentifierT> EvaluateSupport for Supports<AccountId> {
|
||||
fn evaluate(&self) -> ElectionScore {
|
||||
let mut min_support = ExtendedBalance::max_value();
|
||||
let mut sum: ExtendedBalance = Zero::zero();
|
||||
// NOTE: The third element might saturate but fine for now since this will run on-chain and
|
||||
// need to be fast.
|
||||
let mut sum_squared: ExtendedBalance = Zero::zero();
|
||||
for item in self {
|
||||
let (_, support) = item.extract();
|
||||
for (_, support) in self {
|
||||
sum = sum.saturating_add(support.total);
|
||||
let squared = support.total.saturating_mul(support.total);
|
||||
sum_squared = sum_squared.saturating_add(squared);
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
use crate::{
|
||||
balancing, helpers::*, is_score_better, mock::*, seq_phragmen, seq_phragmen_core, setup_inputs,
|
||||
to_support_map, to_supports, Assignment, ElectionResult, EvaluateSupport, ExtendedBalance,
|
||||
IndexAssignment, NposSolution, StakedAssignment, Support, Voter,
|
||||
to_support_map, Assignment, ElectionResult, ExtendedBalance, IndexAssignment, NposSolution,
|
||||
StakedAssignment, Support, Voter,
|
||||
};
|
||||
use rand::{self, SeedableRng};
|
||||
use sp_arithmetic::{PerU16, Perbill, Percent, Permill};
|
||||
@@ -259,8 +259,7 @@ fn phragmen_poc_works() {
|
||||
);
|
||||
|
||||
let staked = assignment_ratio_to_staked(assignments, &stake_of);
|
||||
let winners = to_without_backing(winners);
|
||||
let support_map = to_support_map::<AccountId>(&winners, &staked).unwrap();
|
||||
let support_map = to_support_map::<AccountId>(&staked);
|
||||
|
||||
assert_eq_uvec!(
|
||||
staked,
|
||||
@@ -315,8 +314,7 @@ fn phragmen_poc_works_with_balancing() {
|
||||
);
|
||||
|
||||
let staked = assignment_ratio_to_staked(assignments, &stake_of);
|
||||
let winners = to_without_backing(winners);
|
||||
let support_map = to_support_map::<AccountId>(&winners, &staked).unwrap();
|
||||
let support_map = to_support_map::<AccountId>(&staked);
|
||||
|
||||
assert_eq_uvec!(
|
||||
staked,
|
||||
@@ -515,7 +513,7 @@ fn phragmen_large_scale_test() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq_uvec!(to_without_backing(winners.clone()), vec![24, 22]);
|
||||
assert_eq_uvec!(winners.iter().map(|(x, _)| *x).collect::<Vec<_>>(), vec![24, 22]);
|
||||
check_assignments_sum(&assignments);
|
||||
}
|
||||
|
||||
@@ -649,8 +647,7 @@ fn phragmen_self_votes_should_be_kept() {
|
||||
);
|
||||
|
||||
let staked_assignments = assignment_ratio_to_staked(result.assignments, &stake_of);
|
||||
let winners = to_without_backing(result.winners);
|
||||
let supports = to_support_map::<AccountId>(&winners, &staked_assignments).unwrap();
|
||||
let supports = to_support_map::<AccountId>(&staked_assignments);
|
||||
|
||||
assert_eq!(supports.get(&5u64), None);
|
||||
assert_eq!(
|
||||
@@ -670,9 +667,8 @@ fn duplicate_target_is_ignored() {
|
||||
|
||||
let ElectionResult { winners, assignments } =
|
||||
seq_phragmen::<_, Perbill>(2, candidates, voters, None).unwrap();
|
||||
let winners = to_without_backing(winners);
|
||||
|
||||
assert_eq!(winners, vec![(2), (3)]);
|
||||
assert_eq!(winners, vec![(2, 140), (3, 110)]);
|
||||
assert_eq!(
|
||||
assignments
|
||||
.into_iter()
|
||||
@@ -689,9 +685,8 @@ fn duplicate_target_is_ignored_when_winner() {
|
||||
|
||||
let ElectionResult { winners, assignments } =
|
||||
seq_phragmen::<_, Perbill>(2, candidates, voters, None).unwrap();
|
||||
let winners = to_without_backing(winners);
|
||||
|
||||
assert_eq!(winners, vec![1, 2]);
|
||||
assert_eq!(winners, vec![(1, 100), (2, 100)]);
|
||||
assert_eq!(
|
||||
assignments
|
||||
.into_iter()
|
||||
@@ -701,31 +696,6 @@ fn duplicate_target_is_ignored_when_winner() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn support_map_and_vec_can_be_evaluated() {
|
||||
let candidates = vec![1, 2, 3];
|
||||
let voters = vec![(10, vec![1, 2]), (20, vec![1, 3]), (30, vec![2, 3])];
|
||||
|
||||
let stake_of = create_stake_of(&[(10, 10), (20, 20), (30, 30)]);
|
||||
let ElectionResult { winners, assignments } = seq_phragmen::<_, Perbill>(
|
||||
2,
|
||||
candidates,
|
||||
voters
|
||||
.iter()
|
||||
.map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone()))
|
||||
.collect::<Vec<_>>(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let staked = assignment_ratio_to_staked(assignments, &stake_of);
|
||||
let winners = to_without_backing(winners);
|
||||
let support_map = to_support_map::<AccountId>(&winners, &staked).unwrap();
|
||||
let support_vec = to_supports(&winners, &staked).unwrap();
|
||||
|
||||
assert_eq!(support_map.evaluate(), support_vec.evaluate());
|
||||
}
|
||||
|
||||
mod assignment_convert_normalize {
|
||||
use super::*;
|
||||
#[test]
|
||||
|
||||
@@ -112,7 +112,6 @@ where
|
||||
/// Compute the score of this solution type.
|
||||
fn score<A, FS>(
|
||||
self,
|
||||
winners: &[A],
|
||||
stake_of: FS,
|
||||
voter_at: impl Fn(Self::VoterIndex) -> Option<A>,
|
||||
target_at: impl Fn(Self::TargetIndex) -> Option<A>,
|
||||
@@ -123,7 +122,7 @@ where
|
||||
{
|
||||
let ratio = self.into_assignment(voter_at, target_at)?;
|
||||
let staked = crate::helpers::assignment_ratio_to_staked_normalized(ratio, stake_of)?;
|
||||
let supports = crate::to_supports(winners, &staked)?;
|
||||
let supports = crate::to_supports(&staked);
|
||||
Ok(supports.evaluate())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user