Beispiel #1
0
    def d_ballot_share_weak_voters_sincere(self):
        """dict : Ballot shares due to the weak orders if they vote sincerely.

        Voters of type ``'a>b~c'`` (`lovers`):

        * In Approval or Plurality, they vote for `a`.
        * In Anti-plurality, half of them vote for `ab` (i.e. against `c`) and half of them vote for `ac` (i.e.
          against `b`).

        Voters of type ``'a~b>c'`` (`haters`):

        * In Approval or Anti-plurality, they vote for `ab` (i.e. against `c`).
        * In Plurality, half of them vote for `a` and half of them vote for `b`.
        """
        d = {ballot: 0 for ballot in BALLOTS_WITHOUT_INVERSIONS}
        for weak_order in self.support_in_weak_orders:
            share = self.d_weak_order_share[weak_order]
            if is_lover(weak_order):
                if self.voting_rule in {APPROVAL, PLURALITY}:
                    d[weak_order[0]] += share
                elif self.voting_rule == ANTI_PLURALITY:
                    d[sort_ballot(weak_order[0] + weak_order[2])] += my_division(share, 2)
                    d[sort_ballot(weak_order[0] + weak_order[4])] += my_division(share, 2)
                else:
                    raise NotImplementedError
            else:  # is_hater(weak_order)
                if self.voting_rule == PLURALITY:
                    d[weak_order[0]] += my_division(share, 2)
                    d[weak_order[2]] += my_division(share, 2)
                elif self.voting_rule in {APPROVAL, ANTI_PLURALITY}:
                    d[sort_ballot(weak_order[0] + weak_order[2])] += share
                else:
                    raise NotImplementedError
        return d
Beispiel #2
0
    def best_responses_to_strategy(self, tau, ratio_optimistic=Fraction(1, 2)):
        """Convert best responses to a :class:`StrategyThreshold`.

        Parameters
        ----------
        tau : TauVector
            Tau-vector.
        ratio_optimistic
            The value of `ratio_optimistic` to use. Default: 1/2.

        Returns
        -------
        StrategyThreshold
            The conversion of the best responses into a strategy. Only the rankings present in this profile are
            mentioned in the strategy.
        """
        # Deal with weak orders
        d_weak_order_ballot = {}
        if self.voting_rule == APPROVAL:
            pass
        elif self.voting_rule == PLURALITY:
            for weak_order in WEAK_ORDERS_HATE_WITHOUT_INVERSIONS:  # i~j>k
                if self.d_weak_order_share[weak_order] > 0:
                    i, j = weak_order[0], weak_order[2]
                    if tau.scores[i] > tau.scores[j]:
                        d_weak_order_ballot[weak_order] = i
                    elif tau.scores[i] < tau.scores[j]:
                        d_weak_order_ballot[weak_order] = j
                    else:
                        d_weak_order_ballot[weak_order] = SPLIT
        elif self.voting_rule == ANTI_PLURALITY:
            for weak_order in WEAK_ORDERS_LOVE_WITHOUT_INVERSIONS:  # i>j~k
                if self.d_weak_order_share[weak_order] > 0:
                    i, j, k = weak_order[0], weak_order[2], weak_order[4]
                    if tau.scores[j] > tau.scores[k]:  # Then vote against `j`
                        d_weak_order_ballot[weak_order] = sort_ballot(i + k)
                    elif tau.scores[j] < tau.scores[k]:  # Then vote against `k`
                        d_weak_order_ballot[weak_order] = sort_ballot(i + j)
                    else:
                        d_weak_order_ballot[weak_order] = SPLIT
        # Finish the job
        return StrategyThreshold(
            {
                ranking: best_response.utility_threshold
                for ranking, best_response in tau.d_ranking_best_response.items()
                if self.d_ranking_share[ranking] > 0
            },
            d_weak_order_ballot=d_weak_order_ballot, ratio_optimistic=ratio_optimistic,
            profile=self, voting_rule=self.voting_rule
        )
Beispiel #3
0
 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
Beispiel #4
0
    def random_tau_undominated(self):
        """Random tau based on undominated ballots.

        This is used, for example, in :meth:`~poisson_approval.ProfileCardinal.iterated_voting`.

        Returns
        -------
        TauVector
            A random tau-vector. Independently for each ranking, a proportion uniformly drawn in [0, 1] of voters
            use one undominated ballot, and the rest use the other undominated ballot. For example, in Approval voting,
            voters with ranking `abc` are randomly split between ballots `a` and `ab`.
        """
        d = {ballot: 0 for ballot in BALLOTS_WITHOUT_INVERSIONS}
        for ranking, share in self.d_ranking_share.items():
            r = random.random()
            d[ballot_low_u(ranking, self.voting_rule)] += r * share
            d[ballot_high_u(ranking, self.voting_rule)] += (1 - r) * share
        for weak_order, share in self.d_weak_order_share.items():
            if is_lover(weak_order):
                if self.voting_rule in {APPROVAL, PLURALITY}:
                    d[weak_order[0]] += share
                elif self.voting_rule == ANTI_PLURALITY:
                    r = random.random()
                    d[sort_ballot(weak_order[0] + weak_order[2])] += r * share
                    d[sort_ballot(weak_order[0] + weak_order[4])] += (1 - r) * share
                else:
                    raise NotImplementedError
            else:  # is_hater(weak_order)
                if self.voting_rule == PLURALITY:
                    r = random.random()
                    d[weak_order[0]] += r * share
                    d[weak_order[2]] += (1 - r) * share
                elif self.voting_rule in {APPROVAL, ANTI_PLURALITY}:
                    d[sort_ballot(weak_order[0] + weak_order[2])] += share
                else:
                    raise NotImplementedError
        return TauVector(d, voting_rule=self.voting_rule, symbolic=self.symbolic)
Beispiel #5
0
    def d_ballot_share_weak_voters_strategic(self, strategy):
        """dict : Ballot shares due to the weak orders if they vote strategically.

        For voters with a weak order, strategic voting is the same as sincere voting, except in two cases:

        * For voters of type ``'a~b>c'`` (`haters`)`in Plurality, who have two dominant strategies: vote for `a` or `b`.
        * For voters of type ``'a>b~c'`` (`lovers`) in Anti-Plurality, who have two dominant strategies: vote
          against `b` or `c` (i.e. respectively for `ac` or `ab`).
        """
        d = {ballot: 0 for ballot in BALLOTS_WITHOUT_INVERSIONS}
        for weak_order in self.support_in_weak_orders:
            share = self.d_weak_order_share[weak_order]
            if is_lover(weak_order):
                if self.voting_rule in {APPROVAL, PLURALITY}:
                    d[weak_order[0]] += share
                elif self.voting_rule == ANTI_PLURALITY:
                    ballot = strategy.d_weak_order_ballot[weak_order]
                    if ballot == SPLIT:
                        d[sort_ballot(weak_order[0] + weak_order[2])] += my_division(share, 2)
                        d[sort_ballot(weak_order[0] + weak_order[4])] += my_division(share, 2)
                    else:
                        d[ballot] += share
                else:
                    raise NotImplementedError
            else:  # is_hater(weak_order)
                if self.voting_rule == PLURALITY:
                    ballot = strategy.d_weak_order_ballot[weak_order]
                    if ballot == SPLIT:
                        d[weak_order[0]] += my_division(share, 2)
                        d[weak_order[2]] += my_division(share, 2)
                    else:
                        d[ballot] += share
                elif self.voting_rule in {APPROVAL, ANTI_PLURALITY}:
                    d[sort_ballot(weak_order[0] + weak_order[2])] += share
                else:
                    raise NotImplementedError
        return d
Beispiel #6
0
 def __init__(self,
              d_ranking_ballot,
              d_weak_order_ballot=None,
              profile=None,
              voting_rule=None):
     """
         >>> strategy = StrategyTwelve({'non_existing_ranking': 'utility-dependent'})
         Traceback (most recent call last):
         ValueError: Unknown key: non_existing_ranking
         >>> strategy = StrategyTwelve({'abc': 'non_existing_ballot'})
         Traceback (most recent call last):
         ValueError: Unknown strategy: non_existing_ballot
     """
     voting_rule = self._get_voting_rule_(profile, voting_rule)
     # Populate the dictionary and check for typos in the input
     self.d_ranking_ballot = DictPrintingInOrderIgnoringZeros(
         {ranking: ''
          for ranking in RANKINGS})
     for ranking, ballot in d_ranking_ballot.items():
         # Sanity checks
         if ranking not in RANKINGS:
             raise ValueError('Unknown key: ' + ranking)
         if voting_rule == APPROVAL:
             authorized_ballots = {
                 ballot_one(ranking),
                 ballot_one_two(ranking),
                 ballot_one_two(ranking)[::-1], '', UTILITY_DEPENDENT
             }
         elif voting_rule == PLURALITY:
             authorized_ballots = {
                 ballot_one(ranking),
                 ballot_two(ranking), '', UTILITY_DEPENDENT
             }
         elif voting_rule == ANTI_PLURALITY:
             authorized_ballots = {
                 ballot_one_two(ranking),
                 ballot_one_two(ranking)[::-1],
                 ballot_one_three(ranking),
                 ballot_one_three(ranking)[::-1], '', UTILITY_DEPENDENT
             }
         else:
             raise NotImplementedError
         if ballot not in authorized_ballots:
             raise ValueError('Unknown strategy: ' + ballot)
         # Record the ballot
         self.d_ranking_ballot[
             ranking] = ballot if ballot == UTILITY_DEPENDENT else sort_ballot(
                 ballot)
     # Weak orders
     self.d_weak_order_ballot = DictPrintingInOrderIgnoringZeros(
         {weak_order: ''
          for weak_order in WEAK_ORDERS_WITHOUT_INVERSIONS})
     if d_weak_order_ballot:
         if voting_rule == APPROVAL:
             for weak_order, ballot in d_weak_order_ballot.items():
                 if ballot != '':
                     raise ValueError(
                         'In Approval, you should not specify ballots for weak orders.'
                     )
         elif voting_rule == PLURALITY:
             for weak_order, ballot in d_weak_order_ballot.items():
                 if not is_weak_order(weak_order):
                     raise ValueError('Unknown key: ' + weak_order)
                 elif is_hater(weak_order):
                     possible_ballots = {
                         weak_order[0], weak_order[2], SPLIT
                     }
                     if ballot not in possible_ballots:
                         raise ValueError('Unknown strategy: ' + ballot)
                     self.d_weak_order_ballot[sort_weak_order(
                         weak_order)] = ballot
                 else:  # is_lover(weak_order)
                     if ballot != '':
                         raise ValueError(
                             'In Plurality, you should not specify ballots for "lovers" (e.g. a>b~c).'
                         )
         elif voting_rule == ANTI_PLURALITY:
             for weak_order, ballot in d_weak_order_ballot.items():
                 if not is_weak_order(weak_order):
                     raise ValueError('Unknown key: ' + weak_order)
                 elif is_lover(weak_order):
                     possible_ballots = {
                         sort_ballot(weak_order[0] + weak_order[2]),
                         sort_ballot(weak_order[0] + weak_order[4]), SPLIT
                     }
                     if ballot != SPLIT:
                         ballot = sort_ballot(ballot)
                     if ballot not in possible_ballots:
                         raise ValueError('Unknown strategy: ' + ballot +
                                          ' for ' + weak_order)
                     self.d_weak_order_ballot[sort_weak_order(
                         weak_order)] = ballot
                 else:  # is_hater(weak_order)
                     if ballot != '':
                         raise ValueError(
                             'In Anti-Plurality, you should not specify ballots '
                             'for "haters" (e.g. a~b>c).')
         else:
             raise NotImplementedError
     # Call parent class
     super().__init__(profile=profile, voting_rule=voting_rule)
Beispiel #7
0
    def __init__(self, candidate_x, candidate_y, candidate_z, tau):
        # -------------
        # Preliminaries
        # -------------
        self.symbolic = tau.symbolic
        self.ce = computation_engine(self.symbolic)
        # Create labels
        self._label_x, self._label_y, self._label_z = candidate_x, candidate_y, candidate_z
        self._label_xy = ''.join(sorted([self._label_x, self._label_y]))
        self._label_xz = ''.join(sorted([self._label_x, self._label_z]))
        self._label_yz = ''.join(sorted([self._label_y, self._label_z]))
        self._label_xyd = self._label_xy[1] + self._label_xy[0]
        self._label_xzd = self._label_xz[1] + self._label_xz[0]
        self._label_yzd = self._label_yz[1] + self._label_yz[0]
        self._labels_std_one = {self._label_x: 'x', self._label_y: 'y', self._label_z: 'z'}
        self._labels_std_two = {self._label_xy: 'xy', self._label_xz: 'xz', self._label_yz: 'yz'}
        self._labels_std_two_down = {self._label_xyd: 'xy', self._label_xzd: 'xz', self._label_yzd: 'yz'}
        self._labels_std = self._labels_std_one.copy()
        self._labels_std.update(self._labels_std_two)
        self._labels_std.update(self._labels_std_two_down)
        # Declare variables to tranquilize PyCharm's syntax checker
        self.tau = tau
        self._tau_x, self._tau_y, self._tau_z = 0, 0, 0
        self._tau_xy, self._tau_xz, self._tau_yz = 0, 0, 0
        # Initialize variables such as self.tau_a and self._tau_x
        for label, label_std in self._labels_std.items():
            # Ex: label = 'ab', label_std = 'xy'
            # The share
            share = tau.d_ballot_share[sort_ballot(label)]
            # Define variable such as self._tau_xy
            setattr(self, '_tau_' + label_std, share)
            # Define variable such as tau_ab
            setattr(self, 'tau_' + label, share)
        # Declare the computed variables
        self._phi_x, self._phi_y, self._phi_z = None, None, None
        self._phi_xy, self._phi_xz, self._phi_yz = None, None, None
        self.asymptotic = None
        # ------------------------------------------------
        # Magnitudes, offsets and (if possible) asymptotic
        # ------------------------------------------------
        self._compute(tau_x=self._tau_x, tau_y=self._tau_y, tau_z=self._tau_z,
                      tau_xy=self._tau_xy, tau_xz=self._tau_xz, tau_yz=self._tau_yz)
        self.mu = self.asymptotic.mu
        self.nu = self.asymptotic.nu
        self.xi = self.asymptotic.xi
        # ---------------------------------------------------------------
        # Create the variables like self.phi_ab, self.phi['ab'] (offsets)
        # ---------------------------------------------------------------
        self.phi = dict()
        for label, label_std in self._labels_std.items():
            # Ex: label = 'ab', label_std = 'xy'
            setattr(self, 'phi_' + label, getattr(self, '_phi_' + label_std))
            self.phi[label] = getattr(self, '_phi_' + label_std)
        # ----------------------------------------------------------------------
        # Create the variables like self.psi_ab, self.psi['ab'] (pseudo-offsets)
        # ----------------------------------------------------------------------

        def pseudo_offset(phi, phi_left, phi_right):
            if isnan(phi):
                return phi_left * phi_right
            else:
                return phi
        self.psi = dict()
        self.psi[self._label_x] = pseudo_offset(self.phi[self._label_x],
                                                self.phi[self._label_xy], self.phi[self._label_xz])
        self.psi[self._label_y] = pseudo_offset(self.phi[self._label_y],
                                                self.phi[self._label_xy], self.phi[self._label_yz])
        self.psi[self._label_z] = pseudo_offset(self.phi[self._label_z],
                                                self.phi[self._label_xz], self.phi[self._label_yz])
        self.psi[self._label_xy] = pseudo_offset(self.phi[self._label_xy],
                                                 self.phi[self._label_x], self.phi[self._label_y])
        self.psi[self._label_xz] = pseudo_offset(self.phi[self._label_xz],
                                                 self.phi[self._label_x], self.phi[self._label_z])
        self.psi[self._label_yz] = pseudo_offset(self.phi[self._label_yz],
                                                 self.phi[self._label_y], self.phi[self._label_z])
        self.psi[self._label_xyd] = self.psi[self._label_xy]
        self.psi[self._label_xzd] = self.psi[self._label_xz]
        self.psi[self._label_yzd] = self.psi[self._label_yz]
        for label in self._labels_std.keys():
            setattr(self, 'psi_' + label, self.psi[label])
Beispiel #8
0
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)]
Beispiel #9
0
    @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)