示例#1
0
    def __call__(self, ratings, embeddings=None):
        ratings = Ratings(ratings)
        modified_ratings = np.zeros(ratings.shape)
        for i in range(ratings.n_voters):
            modified_ratings[i] = self.f(ratings.voter_ratings(i))
        self.ratings_ = ratings
        self._modified_ratings = modified_ratings

        if self.embeddings_as_history or embeddings is None:
            embedder = EmbeddingsFromRatingsCorrelation()
            if embeddings is None:
                self.embeddings_ = embedder(self.ratings_)
            else:
                self.embeddings_ = embedder(np.concatenate([embeddings, self.ratings_], axis=1))
        else:
            self.embeddings_ = Embeddings(embeddings)
            self.embeddings_.n_sing_val_ = embeddings.n_sing_val_

        self.n_v = self.embeddings_.n_sing_val_ #embedder.n_sing_val_
        self.delete_cache()

        return self
示例#2
0
class SingleVoterManipulation(DeleteCacheMixin):
    """
    This general class is used for the
    analysis of the manipulability of some :class:`ScoringRule`
    by a single voter.

    For instance, what proportion of voters can
    change the result of the rule (to their advantage)
    by giving false preferences ?

    Parameters
    ----------
    ratings: Ratings or np.ndarray
        The ratings of voters to candidates
    embeddings: Embeddings
        The embeddings of the voters
    rule : ScoringRule
        The aggregation rule we want to analysis.

    Attributes
    ----------
    ratings : Profile
        The ratings of voters on which we do the analysis.
    rule : ScoringRule
        The aggregation rule we want to analysis.
    winner_ : int
        The index of the winner of the election without manipulation.
    scores_ : float list
        The scores of the candidates without manipulation.
    welfare_ : float list
        The welfares of the candidates without manipulation.

    Examples
    --------
    >>> np.random.seed(42)
    >>> scores_matrix = [[1, .2, 0], [.5, .6, .9], [.1, .8, .3]]
    >>> embeddings = EmbeddingsGeneratorPolarized(10, 3)(.8)
    >>> ratings = RatingsFromEmbeddingsCorrelated(3, 3, scores_matrix)(embeddings, .8)
    >>> manipulation = SingleVoterManipulation(ratings, embeddings, SVDNash())
    >>> manipulation.winner_
    1
    >>> manipulation.welfare_
    [0.89..., 1.0, 0.0]
    """
    def __init__(self, ratings, embeddings, rule=None):
        self.ratings = Ratings(ratings)
        self.embeddings = Embeddings(embeddings)
        self.rule = rule
        if rule is not None:
            global_rule = self.rule(self.ratings, self.embeddings)
            self.winner_ = global_rule.winner_
            self.scores_ = global_rule.scores_
            self.welfare_ = global_rule.welfare_
        else:
            self.winner_ = None
            self.scores_ = None
            self.welfare_ = None

    def __call__(self, rule):
        self.rule = rule
        global_rule = self.rule(self.ratings, self.embeddings)
        self.winner_ = global_rule.winner_
        self.scores_ = global_rule.scores_
        self.welfare_ = global_rule.welfare_
        self.delete_cache()
        return self

    def set_profile(self, ratings, embeddings=None):
        """
        This function update the ratings of voters
        on which we do the analysis.

        Parameters
        ----------
        ratings : Ratings or np.ndarray
        embeddings : Embeddings

        Return
        ------
        SingleVoterManipulation
            The object itself.
        """
        if embeddings is not None:
            self.embeddings = Embeddings(embeddings)
        self.ratings = Ratings(ratings)
        global_rule = self.rule(self.ratings, self.embeddings)
        self.winner_ = global_rule.winner_
        self.scores_ = global_rule.scores_
        self.welfare_ = global_rule.welfare_
        self.delete_cache()
        return self

    def manipulation_voter(self, i):
        """
        This function return, for the `i^th` voter,
        its favorite candidate that he can turn to
        a winner by manipulating the election.

        Parameters
        ----------
        i : int
            The index of the voter.

        Return
        ------
        int
            The index of the best candidate
            that can be elected by manipulation.
        """
        score_i = self.ratings.voter_ratings(i).copy()
        preferences_order = np.argsort(score_i)[::-1]

        # If the favorite of the voter is the winner, he will not manipulate
        if preferences_order[0] == self.winner_:
            return self.winner_

        n_candidates = self.ratings.n_candidates
        self.ratings[i] = np.ones(n_candidates)
        scores_max = self.rule(self.ratings, self.embeddings).scores_
        self.ratings[i] = np.zeros(n_candidates)
        scores_min = self.rule(self.ratings, self.embeddings).scores_
        self.ratings[i] = score_i

        all_scores = [(s, i, 1) for i, s in enumerate(scores_max)]
        all_scores += [(s, i, 0) for i, s in enumerate(scores_min)]

        all_scores.sort()
        all_scores = all_scores[::-1]

        best_manipulation = np.where(preferences_order == self.winner_)[0][0]
        for (_, i, k) in all_scores:
            if k == 0:
                break

            index_candidate = np.where(preferences_order == i)[0][0]
            if index_candidate < best_manipulation:
                best_manipulation = index_candidate

        best_manipulation = preferences_order[best_manipulation]

        return best_manipulation

    @cached_property
    def manipulation_global_(self):
        """
        This function applies the function
        :meth:`manipulation_voter` to every voter.

        Return
        ------
        int list
            The list of the best candidates that can be
            turned into the winner for each voter.

        Examples
        --------
        >>> np.random.seed(42)
        >>> scores_matrix = [[1, .2, 0], [.5, .6, .9], [.1, .8, .3]]
        >>> embeddings = EmbeddingsGeneratorPolarized(10, 3)(.8)
        >>> ratings = RatingsFromEmbeddingsCorrelated(3, 3, scores_matrix)(embeddings, .8)
        >>> manipulation = SingleVoterManipulation(ratings, embeddings, SVDNash())
        >>> manipulation.manipulation_global_
        [1, 0, 0, 0, 1, 1, 1, 1, 1, 0]
        """
        return [
            self.manipulation_voter(i) for i in range(self.ratings.n_voters)
        ]

    @cached_property
    def prop_manipulator_(self):
        """
        This function computes the proportion
        of voters that can manipulate the election.

        Return
        ------
        float
            The proportion of voters
            that can manipulate the election.

        Examples
        --------
        >>> np.random.seed(42)
        >>> scores_matrix = [[1, .2, 0], [.5, .6, .9], [.1, .8, .3]]
        >>> embeddings = EmbeddingsGeneratorPolarized(10, 3)(.8)
        >>> ratings = RatingsFromEmbeddingsCorrelated(3, 3, scores_matrix)(embeddings, .8)
        >>> manipulation = SingleVoterManipulation(ratings, embeddings, SVDNash())
        >>> manipulation.prop_manipulator_
        0.4
        """
        return len([x for x in self.manipulation_global_ if x != self.winner_
                    ]) / self.ratings.n_voters

    @cached_property
    def avg_welfare_(self):
        """
        The function computes the average welfare
        of the winning candidate after a voter manipulation.

        Return
        ------
        float
            The average welfare.

        Examples
        --------
        >>> np.random.seed(42)
        >>> scores_matrix = [[1, .2, 0], [.5, .6, .9], [.1, .8, .3]]
        >>> embeddings = EmbeddingsGeneratorPolarized(10, 3)(.8)
        >>> ratings = RatingsFromEmbeddingsCorrelated(3, 3, scores_matrix)(embeddings, .8)
        >>> manipulation = SingleVoterManipulation(ratings, embeddings, SVDNash())
        >>> manipulation.avg_welfare_
        0.956...
        """
        return np.mean([self.welfare_[x] for x in self.manipulation_global_])

    @cached_property
    def worst_welfare_(self):
        """
        This function computes the worst possible welfare
        achievable by single voter manipulation.

        Return
        ------
        float
            The worst welfare.

        Examples
        --------
        >>> np.random.seed(42)
        >>> scores_matrix = [[1, .2, 0], [.5, .6, .9], [.1, .8, .3]]
        >>> embeddings = EmbeddingsGeneratorPolarized(10, 3)(.8)
        >>> ratings = RatingsFromEmbeddingsCorrelated(3, 3, scores_matrix)(embeddings, .8)
        >>> manipulation = SingleVoterManipulation(ratings, embeddings, SVDNash())
        >>> manipulation.worst_welfare_
        0.891...
        """
        return np.min([self.welfare_[x] for x in self.manipulation_global_])

    @cached_property
    def is_manipulable_(self):
        """
        This function quickly computes
        if the ratings is manipulable or not.

        Return
        ------
        bool
            If True, the ratings is
            manipulable by a single voter.

        Examples
        --------
        >>> np.random.seed(42)
        >>> scores_matrix = [[1, .2, 0], [.5, .6, .9], [.1, .8, .3]]
        >>> embeddings = EmbeddingsGeneratorPolarized(10, 3)(.8)
        >>> ratings = RatingsFromEmbeddingsCorrelated(3, 3, scores_matrix)(embeddings, .8)
        >>> manipulation = SingleVoterManipulation(ratings, embeddings, SVDNash())
        >>> manipulation.is_manipulable_
        True
        """
        for i in range(self.ratings.n_voters):
            if self.manipulation_voter(i) != self.winner_:
                return True
        return False

    def manipulation_map(self, map_size=20, scores_matrix=None, show=True):
        """
        A function to plot the manipulability
        of the ratings when the ``polarisation`` and the ``coherence``
        of the :class:`ParametricProfile` vary.
        The number of voters, dimensions, and candidates
        are those of the :attr:`profile_`.

        Parameters
        ----------
        map_size : int
            The number of different ``coherence``
            and ``polarisation`` parameters tested.
            The total number of test is `map_size` `^2`.
        scores_matrix : np.ndarray
            Matrix of shape :attr:`~embedded_voting.Profile.embeddings.n_dim`,
            :attr:`~embedded_voting.Profile.n_candidates` containing
            the scores given by each group.
            More precisely, `scores_matrix[i,j]` is the score given by the group
            represented by the dimension `i` to the candidate `j`.
            If None specified, a new matrix is generated for each test.
        show : bool
            If True, display the manipulation maps
            at the end of the function.

        Return
        ------
        dict
            The manipulation maps :
            ``manipulator`` for the proportion of manipulator,
            ``worst_welfare`` and ``avg_welfare``
            for the welfare maps.

        Examples
        --------
        >>> np.random.seed(42)
        >>> emb = EmbeddingsGeneratorPolarized(100, 3)(0)
        >>> rat = RatingsFromEmbeddingsCorrelated(5, 3)(emb)
        >>> manipulation = SingleVoterManipulation(rat, emb, rule=SVDNash())
        >>> maps = manipulation.manipulation_map(map_size=5, show=False)
        >>> maps['manipulator']
        array([[0.02, 0.49, 0.  , 0.  , 0.  ],
               [0.  , 0.  , 0.  , 0.  , 0.  ],
               [0.54, 0.  , 0.  , 0.4 , 0.  ],
               [0.01, 0.51, 0.  , 0.  , 0.  ],
               [0.  , 0.  , 0.  , 0.36, 0.  ]])
        """

        manipulator = np.zeros((map_size, map_size))
        worst_welfare = np.zeros((map_size, map_size))
        avg_welfare = np.zeros((map_size, map_size))

        n_voters, n_candidates = self.ratings.shape
        n_dim = self.embeddings.n_dim

        embeddings_generator = EmbeddingsGeneratorPolarized(n_voters, n_dim)
        ratings_generator = RatingsFromEmbeddingsCorrelated(
            n_candidates, n_dim)

        if scores_matrix is not None:
            ratings_generator.set_scores(scores_matrix)

        for i in range(map_size):
            for j in range(map_size):
                if scores_matrix is None:
                    ratings_generator.set_scores()
                embeddings = embeddings_generator(polarisation=i /
                                                  (map_size - 1))
                ratings = ratings_generator(embeddings,
                                            coherence=j / (map_size - 1))
                self.set_profile(ratings, embeddings)
                manipulator[i, j] = self.prop_manipulator_
                worst_welfare[i, j] = self.worst_welfare_
                avg_welfare[i, j] = self.avg_welfare_

        if show:
            fig = plt.figure(figsize=(15, 5))

            create_map_plot(fig, manipulator, [1, 3, 1],
                            "Proportion of manipulators")
            create_map_plot(fig, avg_welfare, [1, 3, 2], "Average welfare")
            create_map_plot(fig, worst_welfare, [1, 3, 3], "Worst welfare")

            plt.show()

        return {
            "manipulator": manipulator,
            "worst_welfare": worst_welfare,
            "avg_welfare": avg_welfare
        }