tp_npos_elections/lib.rs
1// This file is part of Tetcore.
2
3// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. SPDX-License-Identifier: Apache-2.0
4
5// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
6// in compliance with the License. You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software distributed under the License
11// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12// or implied. See the License for the specific language governing permissions and limitations under
13// the License.
14
15//! A set of election algorithms to be used with a tetcore runtime, typically within the staking
16//! sub-system. Notable implementation include:
17//!
18//! - [`seq_phragmen`]: Implements the Phragmén Sequential Method. An un-ranked, relatively fast
19//! election method that ensures PJR, but does not provide a constant factor approximation of the
20//! maximin problem.
21//! - [`phragmms()`]: Implements a hybrid approach inspired by Phragmén which is executed faster but
22//! it can achieve a constant factor approximation of the maximin problem, similar to that of the
23//! MMS algorithm.
24//! - [`balance`]: Implements the star balancing algorithm. This iterative process can push a
25//! solution toward being more `balances`, which in turn can increase its score.
26//!
27//! ### Terminology
28//!
29//! This crate uses context-independent words, not to be confused with staking. This is because the
30//! election algorithms of this crate, while designed for staking, can be used in other contexts as
31//! well.
32//!
33//! `Voter`: The entity casting some votes to a number of `Targets`. This is the same as `Nominator`
34//! in the context of staking. `Target`: The entities eligible to be voted upon. This is the same as
35//! `Validator` in the context of staking. `Edge`: A mapping from a `Voter` to a `Target`.
36//!
37//! The goal of an election algorithm is to provide an `ElectionResult`. A data composed of:
38//! - `winners`: A flat list of identifiers belonging to those who have won the election, usually
39//! ordered in some meaningful way. They are zipped with their total backing stake.
40//! - `assignment`: A mapping from each voter to their winner-only targets, zipped with a ration
41//! denoting the amount of support given to that particular target.
42//!
43//! ```rust
44//! # use tp_npos_elections::*;
45//! # use tp_runtime::Perbill;
46//! // the winners.
47//! let winners = vec![(1, 100), (2, 50)];
48//! let assignments = vec![
49//! // A voter, giving equal backing to both 1 and 2.
50//! Assignment {
51//! who: 10,
52//! distribution: vec![(1, Perbill::from_percent(50)), (2, Perbill::from_percent(50))],
53//! },
54//! // A voter, Only backing 1.
55//! Assignment { who: 20, distribution: vec![(1, Perbill::from_percent(100))] },
56//! ];
57//!
58//! // the combination of the two makes the election result.
59//! let election_result = ElectionResult { winners, assignments };
60//! ```
61//!
62//! The `Assignment` field of the election result is voter-major, i.e. it is from the perspective of
63//! the voter. The struct that represents the opposite is called a `Support`. This struct is usually
64//! accessed in a map-like manner, i.e. keyed by voters, therefor it is stored as a mapping called
65//! `SupportMap`.
66//!
67//! Moreover, the support is built from absolute backing values, not ratios like the example above.
68//! A struct similar to `Assignment` that has stake value instead of ratios is called an
69//! `StakedAssignment`.
70//!
71//!
72//! More information can be found at: <https://arxiv.org/abs/2004.12990>
73
74#![cfg_attr(not(feature = "std"), no_std)]
75
76use arithmetic::{
77 traits::{Bounded, UniqueSaturatedInto, Zero},
78 Normalizable, PerThing, Rational128, ThresholdOrd,
79};
80use tetcore_std::{
81 cell::RefCell,
82 cmp::Ordering,
83 collections::btree_map::BTreeMap,
84 convert::{TryFrom, TryInto},
85 fmt::Debug,
86 ops::Mul,
87 prelude::*,
88 rc::Rc,
89};
90use tet_core::RuntimeDebug;
91
92use codec::{Decode, Encode};
93#[cfg(feature = "std")]
94use serde::{Deserialize, Serialize};
95
96#[cfg(test)]
97mod mock;
98#[cfg(test)]
99mod tests;
100
101mod phragmen;
102mod balancing;
103mod phragmms;
104mod node;
105mod reduce;
106mod helpers;
107
108pub use reduce::reduce;
109pub use helpers::*;
110pub use phragmen::*;
111pub use phragmms::*;
112pub use balancing::*;
113
114// re-export the compact macro, with the dependencies of the macro.
115#[doc(hidden)]
116pub use codec;
117#[doc(hidden)]
118pub use arithmetic;
119
120/// Simple Extension trait to easily convert `None` from index closures to `Err`.
121///
122/// This is only generated and re-exported for the compact solution code to use.
123#[doc(hidden)]
124pub trait __OrInvalidIndex<T> {
125 fn or_invalid_index(self) -> Result<T, Error>;
126}
127
128impl<T> __OrInvalidIndex<T> for Option<T> {
129 fn or_invalid_index(self) -> Result<T, Error> {
130 self.ok_or(Error::CompactInvalidIndex)
131 }
132}
133
134/// A common interface for all compact solutions.
135///
136/// See [`tp-npos-elections-compact`] for more info.
137pub trait CompactSolution: Sized {
138 /// The maximum number of votes that are allowed.
139 const LIMIT: usize;
140
141 /// The voter type. Needs to be an index (convert to usize).
142 type Voter: UniqueSaturatedInto<usize> + TryInto<usize> + TryFrom<usize> + Debug + Copy + Clone;
143
144 /// The target type. Needs to be an index (convert to usize).
145 type Target: UniqueSaturatedInto<usize> + TryInto<usize> + TryFrom<usize> + Debug + Copy + Clone;
146
147 /// The weight/accuracy type of each vote.
148 type Accuracy: PerThing128;
149
150 /// Build self from a `assignments: Vec<Assignment<A, Self::Accuracy>>`.
151 fn from_assignment<FV, FT, A>(
152 assignments: Vec<Assignment<A, Self::Accuracy>>,
153 voter_index: FV,
154 target_index: FT,
155 ) -> Result<Self, Error>
156 where
157 A: IdentifierT,
158 for<'r> FV: Fn(&'r A) -> Option<Self::Voter>,
159 for<'r> FT: Fn(&'r A) -> Option<Self::Target>;
160
161 /// Convert self into a `Vec<Assignment<A, Self::Accuracy>>`
162 fn into_assignment<A: IdentifierT>(
163 self,
164 voter_at: impl Fn(Self::Voter) -> Option<A>,
165 target_at: impl Fn(Self::Target) -> Option<A>,
166 ) -> Result<Vec<Assignment<A, Self::Accuracy>>, Error>;
167
168 /// Get the length of all the voters that this type is encoding.
169 ///
170 /// This is basically the same as the number of assignments, or number of active voters.
171 fn voter_count(&self) -> usize;
172
173 /// Get the total count of edges.
174 ///
175 /// This is effectively in the range of {[`Self::voter_count`], [`Self::voter_count`] *
176 /// [`Self::LIMIT`]}.
177 fn edge_count(&self) -> usize;
178
179 /// Get the number of unique targets in the whole struct.
180 ///
181 /// Once presented with a list of winners, this set and the set of winners must be
182 /// equal.
183 fn unique_targets(&self) -> Vec<Self::Target>;
184
185 /// Get the average edge count.
186 fn average_edge_count(&self) -> usize {
187 self.edge_count()
188 .checked_div(self.voter_count())
189 .unwrap_or(0)
190 }
191
192 /// Remove a certain voter.
193 ///
194 /// This will only search until the first instance of `to_remove`, and return true. If
195 /// no instance is found (no-op), then it returns false.
196 ///
197 /// In other words, if this return true, exactly **one** element must have been removed from
198 /// `self.len()`.
199 fn remove_voter(&mut self, to_remove: Self::Voter) -> bool;
200
201 /// Compute the score of this compact solution type.
202 fn score<A, FS>(
203 self,
204 winners: &[A],
205 stake_of: FS,
206 voter_at: impl Fn(Self::Voter) -> Option<A>,
207 target_at: impl Fn(Self::Target) -> Option<A>,
208 ) -> Result<ElectionScore, Error>
209 where
210 for<'r> FS: Fn(&'r A) -> VoteWeight,
211 A: IdentifierT,
212 {
213 let ratio = self.into_assignment(voter_at, target_at)?;
214 let staked = helpers::assignment_ratio_to_staked_normalized(ratio, stake_of)?;
215 let supports = to_supports(winners, &staked)?;
216 Ok(supports.evaluate())
217 }
218}
219
220// re-export the compact solution type.
221pub use tp_npos_elections_compact::generate_solution_type;
222
223/// an aggregator trait for a generic type of a voter/target identifier. This usually maps to
224/// tetcore's account id.
225pub trait IdentifierT: Clone + Eq + Default + Ord + Debug + codec::Codec {}
226impl<T: Clone + Eq + Default + Ord + Debug + codec::Codec> IdentifierT for T {}
227
228/// Aggregator trait for a PerThing that can be multiplied by u128 (ExtendedBalance).
229pub trait PerThing128: PerThing + Mul<ExtendedBalance, Output = ExtendedBalance> {}
230impl<T: PerThing + Mul<ExtendedBalance, Output = ExtendedBalance>> PerThing128 for T {}
231
232/// The errors that might occur in the this crate and compact.
233#[derive(Eq, PartialEq, RuntimeDebug)]
234pub enum Error {
235 /// While going from compact to staked, the stake of all the edges has gone above the total and
236 /// the last stake cannot be assigned.
237 CompactStakeOverflow,
238 /// The compact type has a voter who's number of targets is out of bound.
239 CompactTargetOverflow,
240 /// One of the index functions returned none.
241 CompactInvalidIndex,
242 /// An error occurred in some arithmetic operation.
243 ArithmeticError(&'static str),
244 /// The data provided to create support map was invalid.
245 InvalidSupportEdge,
246}
247
248/// A type which is used in the API of this crate as a numeric weight of a vote, most often the
249/// stake of the voter. It is always converted to [`ExtendedBalance`] for computation.
250pub type VoteWeight = u64;
251
252/// A type in which performing operations on vote weights are safe.
253pub type ExtendedBalance = u128;
254
255/// The score of an assignment. This can be computed from the support map via
256/// [`EvaluateSupport::evaluate`].
257pub type ElectionScore = [ExtendedBalance; 3];
258
259/// A winner, with their respective approval stake.
260pub type WithApprovalOf<A> = (A, ExtendedBalance);
261
262/// A pointer to a candidate struct with interior mutability.
263pub type CandidatePtr<A> = Rc<RefCell<Candidate<A>>>;
264
265/// A candidate entity for the election.
266#[derive(RuntimeDebug, Clone, Default)]
267pub struct Candidate<AccountId> {
268 /// Identifier.
269 who: AccountId,
270 /// Score of the candidate.
271 ///
272 /// Used differently in seq-phragmen and max-score.
273 score: Rational128,
274 /// Approval stake of the candidate. Merely the sum of all the voter's stake who approve this
275 /// candidate.
276 approval_stake: ExtendedBalance,
277 /// The final stake of this candidate. Will be equal to a subset of approval stake.
278 backed_stake: ExtendedBalance,
279 /// True if this candidate is already elected in the current election.
280 elected: bool,
281 /// The round index at which this candidate was elected.
282 round: usize,
283}
284
285/// A vote being casted by a [`Voter`] to a [`Candidate`] is an `Edge`.
286#[derive(Clone, Default)]
287pub struct Edge<AccountId> {
288 /// Identifier of the target.
289 ///
290 /// This is equivalent of `self.candidate.borrow().who`, yet it helps to avoid double borrow
291 /// errors of the candidate pointer.
292 who: AccountId,
293 /// Load of this edge.
294 load: Rational128,
295 /// Pointer to the candidate.
296 candidate: CandidatePtr<AccountId>,
297 /// The weight (i.e. stake given to `who`) of this edge.
298 weight: ExtendedBalance,
299}
300
301#[cfg(feature = "std")]
302impl<A: IdentifierT> tetcore_std::fmt::Debug for Edge<A> {
303 fn fmt(&self, f: &mut tetcore_std::fmt::Formatter<'_>) -> tetcore_std::fmt::Result {
304 write!(f, "Edge({:?}, weight = {:?})", self.who, self.weight)
305 }
306}
307
308/// A voter entity.
309#[derive(Clone, Default)]
310pub struct Voter<AccountId> {
311 /// Identifier.
312 who: AccountId,
313 /// List of candidates approved by this voter.
314 edges: Vec<Edge<AccountId>>,
315 /// The stake of this voter.
316 budget: ExtendedBalance,
317 /// Load of the voter.
318 load: Rational128,
319}
320
321#[cfg(feature = "std")]
322impl<A: IdentifierT> std::fmt::Debug for Voter<A> {
323 fn fmt(&self, f: &mut tetcore_std::fmt::Formatter<'_>) -> tetcore_std::fmt::Result {
324 write!(f, "Voter({:?}, budget = {}, edges = {:?})", self.who, self.budget, self.edges)
325 }
326}
327
328impl<AccountId: IdentifierT> Voter<AccountId> {
329 /// Returns none if this voter does not have any non-zero distributions.
330 ///
331 /// Note that this might create _un-normalized_ assignments, due to accuracy loss of `P`. Call
332 /// site might compensate by calling `normalize()` on the returned `Assignment` as a
333 /// post-precessing.
334 pub fn into_assignment<P: PerThing>(self) -> Option<Assignment<AccountId, P>> {
335 let who = self.who;
336 let budget = self.budget;
337 let distribution = self
338 .edges
339 .into_iter()
340 .filter_map(|e| {
341 let per_thing = P::from_rational_approximation(e.weight, budget);
342 // trim zero edges.
343 if per_thing.is_zero() { None } else { Some((e.who, per_thing)) }
344 }).collect::<Vec<_>>();
345
346 if distribution.len() > 0 {
347 Some(Assignment { who, distribution })
348 } else {
349 None
350 }
351 }
352
353 /// Try and normalize the votes of self.
354 ///
355 /// If the normalization is successful then `Ok(())` is returned.
356 ///
357 /// Note that this will not distinguish between elected and unelected edges. Thus, it should
358 /// only be called on a voter who has already been reduced to only elected edges.
359 ///
360 /// ### Errors
361 ///
362 /// This will return only if the internal `normalize` fails. This can happen if the sum of the
363 /// weights exceeds `ExtendedBalance::max_value()`.
364 pub fn try_normalize(&mut self) -> Result<(), &'static str> {
365 let edge_weights = self.edges.iter().map(|e| e.weight).collect::<Vec<_>>();
366 edge_weights.normalize(self.budget).map(|normalized| {
367 // here we count on the fact that normalize does not change the order.
368 for (edge, corrected) in self.edges.iter_mut().zip(normalized.into_iter()) {
369 let mut candidate = edge.candidate.borrow_mut();
370 // first, subtract the incorrect weight
371 candidate.backed_stake = candidate.backed_stake.saturating_sub(edge.weight);
372 edge.weight = corrected;
373 // Then add the correct one again.
374 candidate.backed_stake = candidate.backed_stake.saturating_add(edge.weight);
375 }
376 })
377 }
378
379 /// Same as [`Self::try_normalize`] but the normalization is only limited between elected edges.
380 pub fn try_normalize_elected(&mut self) -> Result<(), &'static str> {
381 let elected_edge_weights = self
382 .edges
383 .iter()
384 .filter_map(|e| if e.candidate.borrow().elected { Some(e.weight) } else { None })
385 .collect::<Vec<_>>();
386 elected_edge_weights.normalize(self.budget).map(|normalized| {
387 // here we count on the fact that normalize does not change the order, and that vector
388 // iteration is deterministic.
389 for (edge, corrected) in self
390 .edges
391 .iter_mut()
392 .filter(|e| e.candidate.borrow().elected)
393 .zip(normalized.into_iter())
394 {
395 let mut candidate = edge.candidate.borrow_mut();
396 // first, subtract the incorrect weight
397 candidate.backed_stake = candidate.backed_stake.saturating_sub(edge.weight);
398 edge.weight = corrected;
399 // Then add the correct one again.
400 candidate.backed_stake = candidate.backed_stake.saturating_add(edge.weight);
401 }
402 })
403 }
404}
405
406/// Final result of the election.
407#[derive(RuntimeDebug)]
408pub struct ElectionResult<AccountId, P: PerThing> {
409 /// Just winners zipped with their approval stake. Note that the approval stake is merely the
410 /// sub of their received stake and could be used for very basic sorting and approval voting.
411 pub winners: Vec<WithApprovalOf<AccountId>>,
412 /// Individual assignments. for each tuple, the first elements is a voter and the second is the
413 /// list of candidates that it supports.
414 pub assignments: Vec<Assignment<AccountId, P>>,
415}
416
417/// A voter's stake assignment among a set of targets, represented as ratios.
418#[derive(RuntimeDebug, Clone, Default)]
419#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))]
420pub struct Assignment<AccountId, P: PerThing> {
421 /// Voter's identifier.
422 pub who: AccountId,
423 /// The distribution of the voter's stake.
424 pub distribution: Vec<(AccountId, P)>,
425}
426
427impl<AccountId: IdentifierT, P: PerThing128> Assignment<AccountId, P> {
428 /// Convert from a ratio assignment into one with absolute values aka. [`StakedAssignment`].
429 ///
430 /// It needs `stake` which is the total budget of the voter.
431 ///
432 /// Note that this might create _un-normalized_ assignments, due to accuracy loss of `P`. Call
433 /// site might compensate by calling `try_normalize()` on the returned `StakedAssignment` as a
434 /// post-precessing.
435 ///
436 /// If an edge ratio is [`Bounded::min_value()`], it is dropped. This edge can never mean
437 /// anything useful.
438 pub fn into_staked(self, stake: ExtendedBalance) -> StakedAssignment<AccountId> {
439 let distribution = self
440 .distribution
441 .into_iter()
442 .filter_map(|(target, p)| {
443 // if this ratio is zero, then skip it.
444 if p.is_zero() {
445 None
446 } else {
447 // NOTE: this mul impl will always round to the nearest number, so we might both
448 // overflow and underflow.
449 let distribution_stake = p * stake;
450 Some((target, distribution_stake))
451 }
452 })
453 .collect::<Vec<(AccountId, ExtendedBalance)>>();
454
455 StakedAssignment {
456 who: self.who,
457 distribution,
458 }
459 }
460
461 /// Try and normalize this assignment.
462 ///
463 /// If `Ok(())` is returned, then the assignment MUST have been successfully normalized to 100%.
464 ///
465 /// ### Errors
466 ///
467 /// This will return only if the internal `normalize` fails. This can happen if sum of
468 /// `self.distribution.map(|p| p.deconstruct())` fails to fit inside `UpperOf<P>`. A user of
469 /// this crate may statically assert that this can never happen and safely `expect` this to
470 /// return `Ok`.
471 pub fn try_normalize(&mut self) -> Result<(), &'static str> {
472 self.distribution
473 .iter()
474 .map(|(_, p)| *p)
475 .collect::<Vec<_>>()
476 .normalize(P::one())
477 .map(|normalized_ratios|
478 self.distribution
479 .iter_mut()
480 .zip(normalized_ratios)
481 .for_each(|((_, old), corrected)| { *old = corrected; })
482 )
483 }
484}
485
486/// A voter's stake assignment among a set of targets, represented as absolute values in the scale
487/// of [`ExtendedBalance`].
488#[derive(RuntimeDebug, Clone, Default)]
489#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))]
490pub struct StakedAssignment<AccountId> {
491 /// Voter's identifier
492 pub who: AccountId,
493 /// The distribution of the voter's stake.
494 pub distribution: Vec<(AccountId, ExtendedBalance)>,
495}
496
497impl<AccountId> StakedAssignment<AccountId> {
498 /// Converts self into the normal [`Assignment`] type.
499 ///
500 /// NOTE: This will always round down, and thus the results might be less than a full 100% `P`.
501 /// Use a normalization post-processing to fix this. The data type returned here will
502 /// potentially get used to create a compact type; a compact type requires sum of ratios to be
503 /// less than 100% upon un-compacting.
504 ///
505 /// If an edge stake is so small that it cannot be represented in `T`, it is ignored. This edge
506 /// can never be re-created and does not mean anything useful anymore.
507 pub fn into_assignment<P: PerThing>(self) -> Assignment<AccountId, P>
508 where
509 AccountId: IdentifierT,
510 {
511 let stake = self.total();
512 let distribution = self.distribution
513 .into_iter()
514 .filter_map(|(target, w)| {
515 let per_thing = P::from_rational_approximation(w, stake);
516 if per_thing == Bounded::min_value() {
517 None
518 } else {
519 Some((target, per_thing))
520 }
521 })
522 .collect::<Vec<(AccountId, P)>>();
523
524 Assignment {
525 who: self.who,
526 distribution,
527 }
528 }
529
530 /// Try and normalize this assignment.
531 ///
532 /// If `Ok(())` is returned, then the assignment MUST have been successfully normalized to
533 /// `stake`.
534 ///
535 /// NOTE: current implementation of `.normalize` is almost safe to `expect()` upon. The only
536 /// error case is when the input cannot fit in `T`, or the sum of input cannot fit in `T`.
537 /// Sadly, both of these are dependent upon the implementation of `VoteLimit`, i.e. the limit of
538 /// edges per voter which is enforced from upstream. Hence, at this crate, we prefer returning a
539 /// result and a use the name prefix `try_`.
540 pub fn try_normalize(&mut self, stake: ExtendedBalance) -> Result<(), &'static str> {
541 self.distribution
542 .iter()
543 .map(|(_, ref weight)| *weight)
544 .collect::<Vec<_>>()
545 .normalize(stake)
546 .map(|normalized_weights|
547 self.distribution
548 .iter_mut()
549 .zip(normalized_weights.into_iter())
550 .for_each(|((_, weight), corrected)| { *weight = corrected; })
551 )
552 }
553
554 /// Get the total stake of this assignment (aka voter budget).
555 pub fn total(&self) -> ExtendedBalance {
556 self.distribution.iter().fold(Zero::zero(), |a, b| a.saturating_add(b.1))
557 }
558}
559
560/// A structure to demonstrate the election result from the perspective of the candidate, i.e. how
561/// much support each candidate is receiving.
562///
563/// This complements the [`ElectionResult`] and is needed to run the balancing post-processing.
564///
565/// This, at the current version, resembles the `Exposure` defined in the Staking pallet, yet they
566/// do not necessarily have to be the same.
567#[derive(Default, RuntimeDebug, Encode, Decode, Clone, Eq, PartialEq)]
568#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
569pub struct Support<AccountId> {
570 /// Total support.
571 pub total: ExtendedBalance,
572 /// Support from voters.
573 pub voters: Vec<(AccountId, ExtendedBalance)>,
574}
575
576/// A target-major representation of the the election outcome.
577///
578/// Essentially a flat variant of [`SupportMap`].
579///
580/// The main advantage of this is that it is encodable.
581pub type Supports<A> = Vec<(A, Support<A>)>;
582
583/// Linkage from a winner to their [`Support`].
584///
585/// This is more helpful than a normal [`Supports`] as it allows faster error checking.
586pub type SupportMap<A> = BTreeMap<A, Support<A>>;
587
588/// Helper trait to convert from a support map to a flat support vector.
589pub trait FlattenSupportMap<A> {
590 /// Flatten the support.
591 fn flatten(self) -> Supports<A>;
592}
593
594impl<A> FlattenSupportMap<A> for SupportMap<A> {
595 fn flatten(self) -> Supports<A> {
596 self.into_iter().collect::<Vec<_>>()
597 }
598}
599
600/// Build the support map from the winners and assignments.
601///
602/// The list of winners is basically a redundancy for error checking only; It ensures that all the
603/// targets pointed to by the [`Assignment`] are present in the `winners`.
604pub fn to_support_map<A: IdentifierT>(
605 winners: &[A],
606 assignments: &[StakedAssignment<A>],
607) -> Result<SupportMap<A>, Error> {
608 // Initialize the support of each candidate.
609 let mut supports = <SupportMap<A>>::new();
610 winners.iter().for_each(|e| {
611 supports.insert(e.clone(), Default::default());
612 });
613
614 // build support struct.
615 for StakedAssignment { who, distribution } in assignments.iter() {
616 for (c, weight_extended) in distribution.iter() {
617 if let Some(support) = supports.get_mut(c) {
618 support.total = support.total.saturating_add(*weight_extended);
619 support.voters.push((who.clone(), *weight_extended));
620 } else {
621 return Err(Error::InvalidSupportEdge)
622 }
623 }
624 }
625 Ok(supports)
626}
627
628/// Same as [`to_support_map`] except it calls `FlattenSupportMap` on top of the result to return a
629/// flat vector.
630///
631/// Similar to [`to_support_map`], `winners` is used for error checking.
632pub fn to_supports<A: IdentifierT>(
633 winners: &[A],
634 assignments: &[StakedAssignment<A>],
635) -> Result<Supports<A>, Error> {
636 to_support_map(winners, assignments).map(FlattenSupportMap::flatten)
637}
638
639/// Extension trait for evaluating a support map or vector.
640pub trait EvaluateSupport<K> {
641 /// Evaluate a support map. The returned tuple contains:
642 ///
643 /// - Minimum support. This value must be **maximized**.
644 /// - Sum of all supports. This value must be **maximized**.
645 /// - Sum of all supports squared. This value must be **minimized**.
646 fn evaluate(self) -> ElectionScore;
647}
648
649/// A common wrapper trait for both (&A, &B) and &(A, B).
650///
651/// This allows us to implemented something for both `Vec<_>` and `BTreeMap<_>`, such as
652/// [`EvaluateSupport`].
653pub trait TupleRef<K, V> {
654 fn extract(&self) -> (&K, &V);
655}
656
657impl<K, V> TupleRef<K, V> for &(K, V) {
658 fn extract(&self) -> (&K, &V) {
659 (&self.0, &self.1)
660 }
661}
662
663impl<K, V> TupleRef<K, V> for (K, V) {
664 fn extract(&self) -> (&K, &V) {
665 (&self.0, &self.1)
666 }
667}
668
669impl<K, V> TupleRef<K, V> for (&K, &V) {
670 fn extract(&self) -> (&K, &V) {
671 (self.0, self.1)
672 }
673}
674
675impl<A, C, I> EvaluateSupport<A> for C
676where
677 C: IntoIterator<Item = I>,
678 I: TupleRef<A, Support<A>>,
679 A: IdentifierT,
680{
681 fn evaluate(self) -> ElectionScore {
682 let mut min_support = ExtendedBalance::max_value();
683 let mut sum: ExtendedBalance = Zero::zero();
684 // NOTE: The third element might saturate but fine for now since this will run on-chain and
685 // need to be fast.
686 let mut sum_squared: ExtendedBalance = Zero::zero();
687 for item in self {
688 let (_, support) = item.extract();
689 sum = sum.saturating_add(support.total);
690 let squared = support.total.saturating_mul(support.total);
691 sum_squared = sum_squared.saturating_add(squared);
692 if support.total < min_support {
693 min_support = support.total;
694 }
695 }
696 [min_support, sum, sum_squared]
697 }
698}
699
700/// Compares two sets of election scores based on desirability and returns true if `this` is better
701/// than `that`.
702///
703/// Evaluation is done in a lexicographic manner, and if each element of `this` is `that * epsilon`
704/// greater or less than `that`.
705///
706/// Note that the third component should be minimized.
707pub fn is_score_better<P: PerThing>(this: ElectionScore, that: ElectionScore, epsilon: P) -> bool {
708 match this
709 .iter()
710 .zip(that.iter())
711 .map(|(thi, tha)| (
712 thi.ge(&tha),
713 thi.tcmp(&tha, epsilon.mul_ceil(*tha)),
714 ))
715 .collect::<Vec<(bool, Ordering)>>()
716 .as_slice()
717 {
718 // epsilon better in the score[0], accept.
719 [(_, Ordering::Greater), _, _] => true,
720
721 // less than epsilon better in score[0], but more than epsilon better in the second.
722 [(true, Ordering::Equal), (_, Ordering::Greater), _] => true,
723
724 // less than epsilon better in score[0, 1], but more than epsilon better in the third
725 [(true, Ordering::Equal), (true, Ordering::Equal), (_, Ordering::Less)] => true,
726
727 // anything else is not a good score.
728 _ => false,
729 }
730}
731
732/// Converts raw inputs to types used in this crate.
733///
734/// This will perform some cleanup that are most often important:
735/// - It drops any votes that are pointing to non-candidates.
736/// - It drops duplicate targets within a voter.
737pub(crate) fn setup_inputs<AccountId: IdentifierT>(
738 initial_candidates: Vec<AccountId>,
739 initial_voters: Vec<(AccountId, VoteWeight, Vec<AccountId>)>,
740) -> (Vec<CandidatePtr<AccountId>>, Vec<Voter<AccountId>>) {
741 // used to cache and access candidates index.
742 let mut c_idx_cache = BTreeMap::<AccountId, usize>::new();
743
744 let candidates = initial_candidates
745 .into_iter()
746 .enumerate()
747 .map(|(idx, who)| {
748 c_idx_cache.insert(who.clone(), idx);
749 Rc::new(RefCell::new(Candidate { who, ..Default::default() }))
750 })
751 .collect::<Vec<CandidatePtr<AccountId>>>();
752
753 let voters = initial_voters.into_iter().filter_map(|(who, voter_stake, votes)| {
754 let mut edges: Vec<Edge<AccountId>> = Vec::with_capacity(votes.len());
755 for v in votes {
756 if edges.iter().any(|e| e.who == v) {
757 // duplicate edge.
758 continue;
759 }
760 if let Some(idx) = c_idx_cache.get(&v) {
761 // This candidate is valid + already cached.
762 let mut candidate = candidates[*idx].borrow_mut();
763 candidate.approval_stake =
764 candidate.approval_stake.saturating_add(voter_stake.into());
765 edges.push(
766 Edge {
767 who: v.clone(),
768 candidate: Rc::clone(&candidates[*idx]),
769 ..Default::default()
770 }
771 );
772 } // else {} would be wrong votes. We don't really care about it.
773 }
774 if edges.is_empty() {
775 None
776 }
777 else {
778 Some(Voter {
779 who,
780 edges: edges,
781 budget: voter_stake.into(),
782 load: Rational128::zero(),
783 })
784 }
785
786 }).collect::<Vec<_>>();
787
788 (candidates, voters,)
789}