Source code for poisson_approval.tau_vector.TauVector

import math
import warnings
from functools import partial
from poisson_approval.best_response.BestResponseAntiPlurality import BestResponseAntiPlurality
from poisson_approval.best_response.BestResponseApproval import BestResponseApproval
from poisson_approval.best_response.BestResponsePlurality import BestResponsePlurality
from poisson_approval.constants.basic_constants import *
from poisson_approval.constants.Focus import Focus
from poisson_approval.containers.Scores import Scores
from poisson_approval.events.EventDuo import EventDuo
from poisson_approval.events.EventPivotStrict import EventPivotStrict
from poisson_approval.events.EventPivotTij import EventPivotTij
from poisson_approval.events.EventPivotTjk import EventPivotTjk
from poisson_approval.events.EventTrio import EventTrio
from poisson_approval.events.EventTrio1t import EventTrio1t
from poisson_approval.events.EventTrio2t import EventTrio2t
from poisson_approval.events.EventPivotWeak import EventPivotWeak
from poisson_approval.utils.computation_engine import computation_engine
from poisson_approval.utils.DictPrintingInOrder import DictPrintingInOrder
from poisson_approval.utils.DictPrintingInOrderIgnoringZeros import DictPrintingInOrderIgnoringZeros
from poisson_approval.utils.Util import my_division
from poisson_approval.utils.UtilBallots import sort_ballot
from poisson_approval.utils.UtilCache import cached_property


# noinspection PyUnresolvedReferences
[docs]class TauVector: """A vector `tau` (ballot distribution). Parameters ---------- d_ballot_share : dict Ballot distribution, e.g. ``{'a': 0.1, 'ab': 0.6, 'c':0.3}``. voting_rule : str The voting rule. Possible values are ``APPROVAL``, ``PLURALITY`` and ``ANTI_PLURALITY``. symbolic : bool Whether the computations are symbolic or numeric. normalization_warning : bool Whether a warning should be issued if the input distribution is not normalized. Notes ----- If the input distribution `d_ballot_share` is not normalized, the tau vector will be normalized anyway and a warning will be issued (unless `normalization_warning` is False). Examples -------- >>> from fractions import Fraction >>> tau = TauVector({'a': Fraction(1, 10), 'ab': Fraction(3, 5), 'c': Fraction(3, 10)}) >>> tau TauVector({'a': Fraction(1, 10), 'ab': Fraction(3, 5), 'c': Fraction(3, 10)}) >>> print(tau) <a: 1/10, ab: 3/5, c: 3/10> ==> a >>> tau.a Fraction(1, 10) >>> tau.b 0 >>> tau.c Fraction(3, 10) >>> tau.ab Fraction(3, 5) >>> tau.ba # Alternate notation for tau.ab Fraction(3, 5) >>> tau.ac 0 >>> tau.ca # Alternate notation for tau.ac, etc. 0 >>> tau.bc 0 >>> tau.cb 0 >>> tau.duo_ab <asymptotic = exp(- 0.1 n + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> >>> tau.duo_ba <asymptotic = exp(- 0.1 n + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> >>> tau.duo_ac <asymptotic = exp(- 0.0834849 n - 0.5 log n - 0.87535 + o(1)), phi_a = 0.654654, phi_c = 1.52753, \ phi_ab = 0.654654> >>> tau.duo_ca <asymptotic = exp(- 0.0834849 n - 0.5 log n - 0.87535 + o(1)), phi_a = 0.654654, phi_c = 1.52753, \ phi_ab = 0.654654> >>> tau.duo_bc <asymptotic = exp(- 0.0514719 n - 0.5 log n - 0.836813 + o(1)), phi_a = 1, phi_c = 1.41421, phi_ab = 0.707107> >>> tau.duo_cb <asymptotic = exp(- 0.0514719 n - 0.5 log n - 0.836813 + o(1)), phi_a = 1, phi_c = 1.41421, phi_ab = 0.707107> >>> tau.pivot_weak_ab <asymptotic = exp(- 0.1 n + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> >>> tau.pivot_weak_ba <asymptotic = exp(- 0.1 n + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> >>> tau.pivot_weak_ac <asymptotic = exp(- 0.0834849 n - 0.5 log n - 0.87535 + o(1)), phi_a = 0.654654, phi_c = 1.52753, \ phi_ab = 0.654654> >>> tau.pivot_weak_ca <asymptotic = exp(- 0.0834849 n - 0.5 log n - 0.87535 + o(1)), phi_a = 0.654654, phi_c = 1.52753, \ phi_ab = 0.654654> >>> tau.pivot_weak_bc <asymptotic = exp(- 0.151472 n - 0.5 log n - 0.836813 + o(1)), phi_a = 0, phi_c = 1.41421, \ phi_ab = 0.707107> >>> tau.pivot_weak_cb <asymptotic = exp(- 0.151472 n - 0.5 log n - 0.836813 + o(1)), phi_a = 0, phi_c = 1.41421, phi_ab = \ 0.707107> >>> tau.pivot_strict_ab <asymptotic = exp(- 0.1 n + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> >>> tau.pivot_strict_ba <asymptotic = exp(- 0.1 n + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> >>> tau.pivot_strict_ac <asymptotic = exp(- 0.0834849 n - 0.5 log n - 0.87535 + o(1)), phi_a = 0.654654, phi_c = 1.52753, \ phi_ab = 0.654654> >>> tau.pivot_strict_ca <asymptotic = exp(- 0.0834849 n - 0.5 log n - 0.87535 + o(1)), phi_a = 0.654654, phi_c = 1.52753, \ phi_ab = 0.654654> >>> tau.pivot_strict_bc <asymptotic = exp(- inf)> >>> tau.pivot_strict_cb <asymptotic = exp(- inf)> >>> tau.pivot_tij_abc <asymptotic = exp(- 0.1 n + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> >>> tau.pivot_tij_acb <asymptotic = exp(- 0.0834849 n - 0.5 log n - 0.371758 + o(1)), phi_a = 0.654654, phi_c = 1.52753, \ phi_ab = 0.654654> >>> tau.pivot_tij_bac <asymptotic = exp(- 0.1 n + log n - 2.30259 + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> >>> tau.pivot_tij_bca <asymptotic = exp(- 0.151472 n - 0.5 log n - 0.302013 + o(1)), phi_a = 0, phi_c = 1.41421, \ phi_ab = 0.707107> >>> tau.pivot_tij_cab <asymptotic = exp(- 0.0834849 n - 0.5 log n + 0.0518905 + o(1)), phi_a = 0.654654, phi_c = 1.52753, \ phi_ab = 0.654654> >>> tau.pivot_tij_cba <asymptotic = exp(- 0.151472 n - 0.5 log n - 0.836813 + o(1)), phi_a = 0, phi_c = 1.41421, \ phi_ab = 0.707107> >>> tau.pivot_tjk_abc <asymptotic = exp(- inf)> >>> tau.pivot_tjk_acb <asymptotic = exp(- inf)> >>> tau.pivot_tjk_bac <asymptotic = exp(- 0.0834849 n - 0.5 log n - 0.371758 + o(1)), phi_a = 0.654654, phi_c = 1.52753, \ phi_ab = 0.654654> >>> tau.pivot_tjk_bca <asymptotic = exp(- 0.0834849 n - 0.5 log n + 0.0518905 + o(1)), phi_a = 0.654654, phi_c = 1.52753, \ phi_ab = 0.654654> >>> tau.pivot_tjk_cab <asymptotic = exp(- 0.1 n + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> >>> tau.pivot_tjk_cba <asymptotic = exp(- 0.1 n + log n - 2.30259 + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> >>> tau.trio <asymptotic = exp(- 0.151472 n - 0.5 log n - 0.836813 + o(1)), phi_a = 0, phi_c = 1.41421, \ phi_ab = 0.707107> >>> tau.trio_1t_a <asymptotic = exp(- inf)> >>> tau.trio_1t_b <asymptotic = exp(- 0.151472 n + 0.5 log n - 3.48597 + o(1)), phi_a = 0, phi_c = 1.41421, phi_ab = 0.707107> >>> tau.trio_1t_c <asymptotic = exp(- 0.151472 n - 0.5 log n - 0.490239 + o(1)), phi_a = 0, phi_c = 1.41421, \ phi_ab = 0.707107> >>> tau.trio_2t_ab <asymptotic = exp(- 0.151472 n - 0.5 log n - 1.18339 + o(1)), phi_a = 0, phi_c = 1.41421, phi_ab = 0.707107> >>> tau.trio_2t_ac <asymptotic = exp(- inf)> >>> tau.trio_2t_bc <asymptotic = exp(- 0.151472 n + 0.5 log n - 3.1394 + o(1)), phi_a = 0, phi_c = 1.41421, phi_ab = 0.707107> >>> tau.trio_2t_ba <asymptotic = exp(- 0.151472 n - 0.5 log n - 1.18339 + o(1)), phi_a = 0, phi_c = 1.41421, phi_ab = 0.707107> >>> tau.trio_2t_ca <asymptotic = exp(- inf)> >>> tau.trio_2t_cb <asymptotic = exp(- 0.151472 n + 0.5 log n - 3.1394 + o(1)), phi_a = 0, phi_c = 1.41421, phi_ab = 0.707107> """ def __init__(self, d_ballot_share: dict, voting_rule=APPROVAL, symbolic=False, normalization_warning: bool = True): self.symbolic = symbolic self.ce = computation_engine(symbolic) # Populate the dictionary and check for typos in the input self.d_ballot_share = DictPrintingInOrderIgnoringZeros({ ballot: 0 for ballot in BALLOTS_WITHOUT_INVERSIONS}) for ballot, share in d_ballot_share.items(): self.d_ballot_share[sort_ballot(ballot)] += share # Normalize if necessary total = sum(self.d_ballot_share.values()) if not self.ce.look_equal(total, 1): if normalization_warning and not self.ce.look_equal(total, 1, rel_tol=1e-5): warnings.warn(NORMALIZATION_WARNING) for ballot in self.d_ballot_share.keys(): self.d_ballot_share[ballot] = my_division(self.d_ballot_share[ballot], total) # Voting rule self.voting_rule = voting_rule if self.voting_rule == PLURALITY: assert self.ab == self.ac == self.bc == 0 elif self.voting_rule == ANTI_PLURALITY: assert self.a == self.b == self.c == 0 def __repr__(self): arguments = repr(self.d_ballot_share) if self.voting_rule != APPROVAL: arguments += ', voting_rule=%r' % self.voting_rule return 'TauVector(%s)' % arguments def __str__(self): s = '<%s>' % str(self.d_ballot_share)[1:-1] + ' ==> ' + str(self.winners) if self.voting_rule != APPROVAL: s += ' (%s)' % self.voting_rule return s def _repr_pretty_(self, p, cycle): # pragma: no cover - Only for notebooks # https://stackoverflow.com/questions/41453624/tell-ipython-to-use-an-objects-str-instead-of-repr-for-output p.text(str(self) if not cycle else '...') @cached_property def scores(self): """Scores : The scores. Examples -------- >>> from fractions import Fraction >>> tau = TauVector({'a': Fraction(1, 10), 'ab': Fraction(3, 5), 'c': Fraction(3, 10)}) >>> tau.scores {'a': Fraction(7, 10), 'b': Fraction(3, 5), 'c': Fraction(3, 10)} """ return Scores(dict(a=self.a + self.ab + self.ac, b=self.b + self.ab + self.bc, c=self.c + self.ac + self.bc)) @property def winners(self): """Winners : The winners. Examples -------- >>> from fractions import Fraction >>> tau = TauVector({'a': Fraction(1, 10), 'ab': Fraction(3, 5), 'c': Fraction(3, 10)}) >>> tau.winners Winners({'a'}) """ return self.scores.winners def __eq__(self, other): """Equality test. Parameters ---------- other : object Returns ------- bool True iff it is the same tau-vector. Examples -------- >>> from fractions import Fraction >>> tau = TauVector({'a': Fraction(1, 10), 'ab': Fraction(3, 5), 'c': Fraction(3, 10)}) >>> tau == TauVector({'a': Fraction(10, 100), 'ab': Fraction(60, 100), 'c': Fraction(30, 100)}) True """ return (isinstance(other, TauVector) and self.d_ballot_share == other.d_ballot_share and self.voting_rule == other.voting_rule) @cached_property def has_two_consecutive_zeros(self): """bool Whether the tau-vector has two consecutive holes in the "compass" representation. True iff ``self.a == 0 and self.ab == 0``, or ``self.ab == 0 and self.b == 0``, etc. """ return ((self.a == 0 and (self.ab == 0 or self.ac == 0)) or (self.b == 0 and (self.ab == 0 or self.bc == 0)) or (self.c == 0 and (self.ac == 0 or self.bc == 0)))
[docs] def isclose(self, other, *args, **kwargs): """Test near-equality. Parameters ---------- other : object *args Cf. ``math.isclose``. **kwargs Cf. ``math.isclose``. Returns ------- isclose : bool True if this tau-vector is approximately equal to `other`. Cf. :func:`isclose`. Examples -------- >>> tau = TauVector({'ab': 0.4, 'b': 0.6}) >>> tau.isclose(TauVector({'ab': 0.4, 'b': 0.59999999999999999999999999})) True """ return isinstance(other, TauVector) and all([ math.isclose(share, other.d_ballot_share[ballot], *args, **kwargs) for ballot, share in self.d_ballot_share.items() ])
@cached_property def standardized_version(self): """Standardized version of the profile (makes it unique, up to permutations). Notes ----- It returns the same profile, up to a permutation of the candidates. How the permutation is chosen in practice does not really matter: the important point is that the `standardized version` is the same for all the profile that are identical up to a permutation of the candidates. Examples -------- >>> from fractions import Fraction >>> tau = TauVector({'a': Fraction(1, 10), 'ab': Fraction(3, 5), 'c': Fraction(3, 10)}) >>> tau.standardized_version TauVector({'a': Fraction(3, 10), 'b': Fraction(1, 10), 'bc': Fraction(3, 5)}) """ def translate(s, permute): return ''.join(sorted(s.replace('a', permute[0]).replace('b', permute[1]).replace('c', permute[2]))) best_d = {} best_signature = [] for perm in XYZ_PERMUTATIONS: d_test = {translate(ballot, perm): share for ballot, share in self.d_ballot_share.items()} signature_test = [d_test[ballot] for ballot in XYZ_BALLOTS_WITHOUT_INVERSION] if signature_test > best_signature: best_signature = signature_test best_d = d_test return TauVector({ballot: best_d[xyz_ballot] for ballot, xyz_ballot in zip(BALLOTS_WITHOUT_INVERSIONS, XYZ_BALLOTS_WITHOUT_INVERSION)}, voting_rule=self.voting_rule) @cached_property def is_standardized(self): """Whether the profile is standardized or not. Cf. :meth:`standardized_version`. Examples -------- >>> from fractions import Fraction >>> tau = TauVector({'a': Fraction(1, 10), 'ab': Fraction(3, 5), 'c': Fraction(3, 10)}) >>> tau.is_standardized False """ return self == self.standardized_version @cached_property def share_single_votes(self): """Number: share of single votes, i.e. votes for one candidate only. Examples -------- >>> from fractions import Fraction >>> tau = TauVector({'a': Fraction(1, 10), 'ab': Fraction(3, 5), 'c': Fraction(3, 10)}) >>> tau.share_single_votes Fraction(2, 5) """ return sum([share for ballot, share in self.d_ballot_share.items() if len(ballot) == 1]) @cached_property def share_double_votes(self): """Number: share of double votes, i.e. votes for two candidates. Examples -------- >>> from fractions import Fraction >>> tau = TauVector({'a': Fraction(1, 10), 'ab': Fraction(3, 5), 'c': Fraction(3, 10)}) >>> tau.share_double_votes Fraction(3, 5) """ return sum([share for ballot, share in self.d_ballot_share.items() if len(ballot) == 2]) # Pivots @cached_property def trio(self): """Event: Trio.""" return EventTrio(candidate_x='a', candidate_y='b', candidate_z='c', tau=self) @property def focus(self): """Focus : Focus of this tau-vector. This is based on the weak pivots. Examples -------- >>> from fractions import Fraction >>> tau = TauVector({'a': Fraction(1, 10), 'ab': Fraction(3, 5), 'c': Fraction(3, 10)}) >>> tau.focus Focus.DIRECT """ magnitudes = sorted([self.pivot_weak_ab.mu, self.pivot_weak_ac.mu, self.pivot_weak_bc.mu]) if self.ce.look_equal(magnitudes[0], magnitudes[2]): return Focus.UNFOCUSED elif self.ce.look_equal(magnitudes[0], magnitudes[1]): return Focus.FORWARD_FOCUSED elif self.ce.look_equal(magnitudes[1], magnitudes[2]): return Focus.BACKWARD_FOCUSED else: return Focus.DIRECT
[docs] def print_magnitudes_order(self): """Print the order of the magnitudes of the weak pivots. Examples -------- >>> from fractions import Fraction >>> tau = TauVector({'a': Fraction(1, 10), 'ab': Fraction(3, 5), 'c': Fraction(3, 10)}) >>> tau.print_magnitudes_order() mu_ac > mu_ab > mu_bc >>> from fractions import Fraction >>> tau = TauVector({'a': Fraction(1, 3), 'b': Fraction(1, 3), 'c': Fraction(1, 3)}) >>> tau.print_magnitudes_order() mu_ab = mu_ac = mu_bc """ d_notation_value = {'mu_%s' % pair: getattr(self, 'pivot_weak_%s' % pair).mu for pair in ['ab', 'ac', 'bc']} notation_sorted = sorted(d_notation_value.keys(), key=d_notation_value.get, reverse=True) values_sorted = sorted(d_notation_value.values(), reverse=True) s = notation_sorted[0] s += ' = ' if values_sorted[0] == values_sorted[1] else ' > ' s += notation_sorted[1] s += ' = ' if values_sorted[1] == values_sorted[2] else ' > ' s += notation_sorted[2] print(s)
[docs] def print_weak_pivots(self): """Print the weak pivots (including the 3-way tie). Examples -------- >>> from fractions import Fraction >>> tau = TauVector({'a': Fraction(1, 10), 'ab': Fraction(3, 5), 'c': Fraction(3, 10)}) >>> tau.print_weak_pivots() pivot_weak_ab: <asymptotic = exp(- 0.1 n + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> pivot_weak_ac: <asymptotic = exp(- 0.0834849 n - 0.5 log n - 0.87535 + o(1)), phi_a = 0.654654, \ phi_c = 1.52753, phi_ab = 0.654654> pivot_weak_bc: <asymptotic = exp(- 0.151472 n - 0.5 log n - 0.836813 + o(1)), phi_a = 0, phi_c = 1.41421, \ phi_ab = 0.707107> trio: <asymptotic = exp(- 0.151472 n - 0.5 log n - 0.836813 + o(1)), phi_a = 0, phi_c = 1.41421, \ phi_ab = 0.707107> """ for pair in PAIRS_WITHOUT_INVERSIONS: print('pivot_weak_%s: ' % pair, getattr(self, 'pivot_weak_%s' % pair)) print('trio: ', self.trio)
[docs] def print_all_pivots(self): """Print all the pivots. Examples -------- >>> from fractions import Fraction >>> tau = TauVector({'a': Fraction(1, 10), 'ab': Fraction(3, 5), 'c': Fraction(3, 10)}) >>> tau.print_all_pivots() pivot_weak_ab: <asymptotic = exp(- 0.1 n + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> pivot_weak_ac: <asymptotic = exp(- 0.0834849 n - 0.5 log n - 0.87535 + o(1)), phi_a = 0.654654, \ phi_c = 1.52753, phi_ab = 0.654654> pivot_weak_bc: <asymptotic = exp(- 0.151472 n - 0.5 log n - 0.836813 + o(1)), phi_a = 0, phi_c = 1.41421, \ phi_ab = 0.707107> pivot_strict_ab: <asymptotic = exp(- 0.1 n + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> pivot_strict_ac: <asymptotic = exp(- 0.0834849 n - 0.5 log n - 0.87535 + o(1)), phi_a = 0.654654, \ phi_c = 1.52753, phi_ab = 0.654654> pivot_strict_bc: <asymptotic = exp(- inf)> pivot_tij_abc: <asymptotic = exp(- 0.1 n + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> pivot_tij_acb: <asymptotic = exp(- 0.0834849 n - 0.5 log n - 0.371758 + o(1)), phi_a = 0.654654, \ phi_c = 1.52753, phi_ab = 0.654654> pivot_tij_bac: <asymptotic = exp(- 0.1 n + log n - 2.30259 + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> pivot_tij_bca: <asymptotic = exp(- 0.151472 n - 0.5 log n - 0.302013 + o(1)), phi_a = 0, phi_c = 1.41421, \ phi_ab = 0.707107> pivot_tij_cab: <asymptotic = exp(- 0.0834849 n - 0.5 log n + 0.0518905 + o(1)), phi_a = 0.654654, \ phi_c = 1.52753, phi_ab = 0.654654> pivot_tij_cba: <asymptotic = exp(- 0.151472 n - 0.5 log n - 0.836813 + o(1)), phi_a = 0, phi_c = 1.41421, \ phi_ab = 0.707107> pivot_tjk_abc: <asymptotic = exp(- inf)> pivot_tjk_acb: <asymptotic = exp(- inf)> pivot_tjk_bac: <asymptotic = exp(- 0.0834849 n - 0.5 log n - 0.371758 + o(1)), phi_a = 0.654654, \ phi_c = 1.52753, phi_ab = 0.654654> pivot_tjk_bca: <asymptotic = exp(- 0.0834849 n - 0.5 log n + 0.0518905 + o(1)), phi_a = 0.654654, \ phi_c = 1.52753, phi_ab = 0.654654> pivot_tjk_cab: <asymptotic = exp(- 0.1 n + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> pivot_tjk_cba: <asymptotic = exp(- 0.1 n + log n - 2.30259 + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> trio: <asymptotic = exp(- 0.151472 n - 0.5 log n - 0.836813 + o(1)), phi_a = 0, phi_c = 1.41421, \ phi_ab = 0.707107> trio_1t_a: <asymptotic = exp(- inf)> trio_1t_b: <asymptotic = exp(- 0.151472 n + 0.5 log n - 3.48597 + o(1)), phi_a = 0, phi_c = 1.41421, \ phi_ab = 0.707107> trio_1t_c: <asymptotic = exp(- 0.151472 n - 0.5 log n - 0.490239 + o(1)), phi_a = 0, phi_c = 1.41421, \ phi_ab = 0.707107> trio_2t_ab: <asymptotic = exp(- 0.151472 n - 0.5 log n - 1.18339 + o(1)), phi_a = 0, phi_c = 1.41421, \ phi_ab = 0.707107> trio_2t_ac: <asymptotic = exp(- inf)> trio_2t_bc: <asymptotic = exp(- 0.151472 n + 0.5 log n - 3.1394 + o(1)), phi_a = 0, phi_c = 1.41421, \ phi_ab = 0.707107> duo_ab: <asymptotic = exp(- 0.1 n + o(1)), phi_a = 0, phi_c = 1, phi_ab = 1> duo_ac: <asymptotic = exp(- 0.0834849 n - 0.5 log n - 0.87535 + o(1)), phi_a = 0.654654, phi_c = 1.52753, \ phi_ab = 0.654654> duo_bc: <asymptotic = exp(- 0.0514719 n - 0.5 log n - 0.836813 + o(1)), phi_a = 1, phi_c = 1.41421, \ phi_ab = 0.707107> """ for pair in PAIRS_WITHOUT_INVERSIONS: print('pivot_weak_%s: ' % pair, getattr(self, 'pivot_weak_%s' % pair)) for pair in PAIRS_WITHOUT_INVERSIONS: print('pivot_strict_%s: ' % pair, getattr(self, 'pivot_strict_%s' % pair)) for ranking in RANKINGS: print('pivot_tij_%s: ' % ranking, getattr(self, 'pivot_tij_%s' % ranking)) for ranking in RANKINGS: print('pivot_tjk_%s: ' % ranking, getattr(self, 'pivot_tjk_%s' % ranking)) print('trio: ', self.trio) for candidate in CANDIDATES: print('trio_1t_%s: ' % candidate, getattr(self, 'trio_1t_%s' % candidate)) for pair in PAIRS_WITHOUT_INVERSIONS: print('trio_2t_%s: ' % pair, getattr(self, 'trio_2t_%s' % pair)) for pair in PAIRS_WITHOUT_INVERSIONS: print('duo_%s: ' % pair, getattr(self, 'duo_%s' % pair))
@cached_property def d_ranking_best_response(self): """dict : Best response profile. * Key: a ranking (e.g. ``'abc'``). * Value: a :class:`BestResponse` (whose subclass depends on `voting_rule`). Examples -------- >>> from fractions import Fraction >>> tau = TauVector({'a': Fraction(1, 10), 'ab': Fraction(3, 5), 'c': Fraction(3, 10)}) >>> tau.d_ranking_best_response['abc'] <ballot = a, utility_threshold = 1, justification = Asymptotic method> """ if self.voting_rule == APPROVAL: return DictPrintingInOrder({ ranking: BestResponseApproval(tau=self, ranking=ranking) for ranking in RANKINGS}) if self.voting_rule == PLURALITY: return DictPrintingInOrder({ ranking: BestResponsePlurality(tau=self, ranking=ranking) for ranking in RANKINGS}) if self.voting_rule == ANTI_PLURALITY: return DictPrintingInOrder({ ranking: BestResponseAntiPlurality(tau=self, ranking=ranking) for ranking in RANKINGS}) raise NotImplementedError @cached_property def is_best_response_ordinal(self): """bool : Whether the best responses of all rankings are ordinal (not utility-dependent). Examples -------- >>> from fractions import Fraction >>> tau = TauVector({'a': Fraction(1, 10), 'ab': Fraction(3, 5), 'c': Fraction(3, 10)}) >>> tau.is_best_response_ordinal True """ return all([ best_response.is_ordinal for best_response in self.d_ranking_best_response.values() ]) @cached_property def score_ab_in_duo_ab(self): """Number : Common score of `a` and `b` in duo `ab`.""" return (self.ce.multiply_with_absorbing_zero(self.a, self.duo_ab.phi_a) + self.ce.multiply_with_absorbing_zero(self.ab, self.duo_ab.phi_ab) + self.ce.multiply_with_absorbing_zero(self.ac, self.duo_ab.phi_ac)) @cached_property def score_ac_in_duo_ac(self): """Number : Common score of `a` and `c` in duo `ac`.""" return (self.ce.multiply_with_absorbing_zero(self.a, self.duo_ac.phi_a) + self.ce.multiply_with_absorbing_zero(self.ab, self.duo_ac.phi_ab) + self.ce.multiply_with_absorbing_zero(self.ac, self.duo_ac.phi_ac)) @cached_property def score_bc_in_duo_bc(self): """Number : Common score of `b` and `c` in duo `bc`.""" return (self.ce.multiply_with_absorbing_zero(self.b, self.duo_bc.phi_b) + self.ce.multiply_with_absorbing_zero(self.ab, self.duo_bc.phi_ab) + self.ce.multiply_with_absorbing_zero(self.bc, self.duo_bc.phi_bc)) @cached_property def score_ba_in_duo_ba(self): """Number : Alternate notation for :attr:`score_ab_in_duo_ab`.""" return self.score_ab_in_duo_ab @cached_property def score_ca_in_duo_ca(self): """Number : Alternate notation for :attr:`score_ac_in_duo_ac`.""" return self.score_ac_in_duo_ac @cached_property def score_cb_in_duo_cb(self): """Number : Alternate notation for :attr:`score_bc_in_duo_bc`.""" return self.score_bc_in_duo_bc @cached_property def score_c_in_duo_ab(self): """Number : Score of `c` in duo `ab`.""" return (self.ce.multiply_with_absorbing_zero(self.c, self.duo_ab.phi_c) + self.ce.multiply_with_absorbing_zero(self.ac, self.duo_ab.phi_ac) + self.ce.multiply_with_absorbing_zero(self.bc, self.duo_ab.phi_bc)) @cached_property def score_b_in_duo_ac(self): """Number : Score of `b` in duo `ac`.""" return (self.ce.multiply_with_absorbing_zero(self.b, self.duo_ac.phi_b) + self.ce.multiply_with_absorbing_zero(self.ab, self.duo_ac.phi_ab) + self.ce.multiply_with_absorbing_zero(self.bc, self.duo_ac.phi_bc)) @cached_property def score_a_in_duo_bc(self): """Number : Score of `a` in duo `bc`.""" return (self.ce.multiply_with_absorbing_zero(self.a, self.duo_bc.phi_a) + self.ce.multiply_with_absorbing_zero(self.ab, self.duo_bc.phi_ab) + self.ce.multiply_with_absorbing_zero(self.ac, self.duo_bc.phi_ac)) @cached_property def score_c_in_duo_ba(self): """Number : Alternate notation for :attr:`score_c_in_duo_ab`.""" return self.score_c_in_duo_ab @cached_property def score_b_in_duo_ca(self): """Number : Alternate notation for :attr:`score_b_in_duo_ac`.""" return self.score_b_in_duo_ac @cached_property def score_a_in_duo_cb(self): """Number : Alternate notation for :attr:`score_a_in_duo_bc`.""" return self.score_a_in_duo_bc @cached_property def pivot_ab_easy_or_tight(self): """bool : True if the pivot `ab` is easy or tight, False if it is difficult.""" pivot_easy = self.score_ab_in_duo_ab > self.score_c_in_duo_ab pivot_tight = self.ce.look_equal(self.score_ab_in_duo_ab, self.score_c_in_duo_ab) return pivot_easy or pivot_tight @cached_property def pivot_ac_easy_or_tight(self): """bool : True if the pivot `ac` is easy or tight, False if it is difficult.""" pivot_easy = self.score_ac_in_duo_ac > self.score_b_in_duo_ac pivot_tight = self.ce.look_equal(self.score_ac_in_duo_ac, self.score_b_in_duo_ac) return pivot_easy or pivot_tight @cached_property def pivot_bc_easy_or_tight(self): """bool : True if the pivot `bc` is easy or tight, False if it is difficult.""" pivot_easy = self.score_bc_in_duo_bc > self.score_a_in_duo_bc pivot_tight = self.ce.look_equal(self.score_bc_in_duo_bc, self.score_a_in_duo_bc) return pivot_easy or pivot_tight @cached_property def pivot_ba_easy_or_tight(self): """bool : Alternate notation for :attr:`pivot_ab_easy_or_tight`""" return self.pivot_ab_easy_or_tight @cached_property def pivot_ca_easy_or_tight(self): """bool : Alternate notation for :attr:`pivot_ac_easy_or_tight`""" return self.pivot_ac_easy_or_tight @cached_property def pivot_cb_easy_or_tight(self): """bool : Alternate notation for :attr:`pivot_bc_easy_or_tight`""" return self.pivot_bc_easy_or_tight
def _f_ballot_share(self, ballot): """Share of this ballot""" # This function is used to define an attribute for each ballot. return self.d_ballot_share[sort_ballot(ballot)] for my_ballot in BALLOTS_WITH_INVERSIONS: setattr(TauVector, my_ballot, property(partial(_f_ballot_share, ballot=my_ballot))) if sort_ballot(my_ballot) == my_ballot: getattr(TauVector, my_ballot).__doc__ = "Number: Share of the ballot ``'%s'``." % my_ballot else: getattr(TauVector, my_ballot).__doc__ = \ "Number: Share of the ballot ``'%s'`` (alternate notation)." % sort_ballot(my_ballot) # Events based on a duo: create cached properties like duo_ab, etc. def _f_duo(self, candidate_x, candidate_y, candidate_z, cls, stub): if candidate_x < candidate_y: return cls(candidate_x=candidate_x, candidate_y=candidate_y, candidate_z=candidate_z, tau=self) else: return getattr(self, stub + '_%s%s' % (candidate_y, candidate_x)) for event_class, event_stub, event_doc in [ (EventDuo, 'duo', 'EventDuo : Event where these two candidates have the same score.'), (EventPivotWeak, 'pivot_weak', 'EventPivotWeak : Event where these two candidates have the same score, at least as high as the remaining ' 'candidate.'), (EventPivotStrict, 'pivot_strict', 'EventPivotStrict : Event where these two candidates have the same score, strictly higher than the ' 'remaining candidate.'), (EventTrio2t, 'trio_2t', 'EventTrio1t : Event where these candidates have one vote less than the remaining candidate.')]: for x, y, z in RANKINGS: name = event_stub + '_%s%s' % (x, y) setattr(TauVector, name, partial(_f_duo, candidate_x=x, candidate_y=y, candidate_z=z, cls=event_class, stub=event_stub)) getattr(TauVector, name).__name__ = name setattr(TauVector, name, cached_property(getattr(TauVector, name))) getattr(TauVector, name).__doc__ = event_doc # Events based on a permutation: create cached properties like pivot_tij_abc, etc. def _f_ranking(self, candidate_x, candidate_y, candidate_z, cls): return cls(candidate_x=candidate_x, candidate_y=candidate_y, candidate_z=candidate_z, tau=self) for event_class, event_stub, event_doc in [ (EventPivotTij, 'pivot_tij', 'EventPivotTij: Personalized pivot of type Tij (between the two most-liked candidates).'), (EventPivotTjk, 'pivot_tjk', 'EventPivotTjk: Personalized pivot of type Tjk (between the two least-liked candidates).')]: for x, y, z in RANKINGS: name = event_stub + '_%s%s%s' % (x, y, z) if event_stub == 'pivot_tjk': setattr(TauVector, name, partial(_f_ranking, candidate_x=z, candidate_y=y, candidate_z=x, cls=event_class)) else: setattr(TauVector, name, partial(_f_ranking, candidate_x=x, candidate_y=y, candidate_z=z, cls=event_class)) getattr(TauVector, name).__name__ = name setattr(TauVector, name, cached_property(getattr(TauVector, name))) getattr(TauVector, name).__doc__ = event_doc # Events based on one candidate: create cached properties like trio_1t_a, etc. for event_class, event_stub, event_doc in [ (EventTrio1t, 'trio_1t', 'EventTrio1t : Event where this candidate has one vote less than the two others.')]: for x, y, z in RANKINGS: if y > z: continue name = event_stub + '_%s' % x setattr(TauVector, name, partial(_f_ranking, candidate_x=x, candidate_y=y, candidate_z=z, cls=event_class)) getattr(TauVector, name).__name__ = name setattr(TauVector, name, cached_property(getattr(TauVector, name))) getattr(TauVector, name).__doc__ = event_doc