def set_profile(self, ratings, embeddings=None): 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 __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, ratings, train=None): """ This function run an election using the :attr:`embedder` and the scores. Parameters ---------- ratings: np.ndarray or list The matrix of scores given by the voters. ``ratings[i,j]`` corresponds to the score given by the voter i to candidate j. train: bool If True, we retrain the :attr:`embedder` before doing the election (using the data of the election). """ ratings = Ratings(ratings) if self.ratings_history is None: self.ratings_history = ratings else: self.ratings_history = np.concatenate( [self.ratings_history, ratings], axis=1) if self.embeddings is None or (train is None and self.default_train) or train: self.train() self.rule.delete_cache() return self.rule(ratings, self.embeddings)
def test_features(): ratings = Ratings(np.array([[.5, .6, .3], [.7, 0, .2], [.2, 1, .8]])) embeddings = Embeddings(np.array([[1, 1, 0], [1, 0, 1], [0, 1, 0]])) election = FeaturesRule()(ratings, embeddings) election.plot_features("3D", show=False) election.plot_features("ternary", show=False) with pytest.raises(ValueError): election.plot_features("3D", dim=[0, 1], show=False)
def test_multi(): ratings = Ratings(np.array([[.5, .6, .3], [.7, 0, .2], [.2, 1, .8]])) embeddings = Embeddings(np.array([[1, 1, 0], [1, 0, 1], [0, 1, 0]])) election = IterSVD(k=2)(ratings, embeddings) election.plot_weights("3D", show=False) election.plot_weights("ternary", show=False) with pytest.raises(ValueError): election.plot_weights("3D", dim=[0, 1], show=False) election.plot_winners("3D", show=False)
def __call__(self, n_candidates=1, *args): self.ground_truth_ = self.generate_true_values(n_candidates=n_candidates) ratings = np.zeros((self.n_voters, n_candidates)) for i in range(n_candidates): v_dependent_noise = np.random.multivariate_normal( mean=np.zeros(self.n_voters), cov=self.covariance_matrix) v_independent_noise = np.random.normal( loc=0, scale=self.independent_noise, size=self.n_voters) ratings[:, i] = self.ground_truth_[i] + v_dependent_noise + v_independent_noise return Ratings(ratings)
def __call__(self, ratings, embeddings=None, k=None): self.ratings = Ratings(ratings) if embeddings is None: self.embeddings = EmbeddingsFromRatingsIdentity()(self.ratings) else: self.embeddings = Embeddings(embeddings) if k is not None: self.k_ = k self.delete_cache() return self
def __call__(self, n_candidates=1, *args): self.ground_truth_ = self.generate_true_values(n_candidates=n_candidates) ratings = np.zeros((self.n_voters, n_candidates)) for i in range(n_candidates): sigma_groups = np.abs(np.random.normal(loc=0, scale=self.group_noise, size=self.n_groups)) sigma_voters = self.m_voter_group @ sigma_groups v_noise = np.random.multivariate_normal( mean=np.zeros(self.n_voters), cov=np.diag(sigma_voters)) ratings[:, i] = self.ground_truth_[i] + v_noise return Ratings(ratings)
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
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 __call__(self, ratings): ratings = Ratings(ratings) positions = (ratings.T / np.sqrt((ratings**2).sum(axis=1))).T n_voters, n_candidates = ratings.shape self.n_dim = n_candidates u, s, v = np.linalg.svd(positions) n_voters, n_candidates = positions.shape s = np.sqrt(s) s /= s.sum() n_v = 0 for s_e in s: if s_e >= max(1 / n_voters, 1 / n_candidates): n_v += 1 embeddings = Embeddings(np.dot(positions, positions.T)) embeddings.n_sing_val_ = n_v return embeddings
def __call__(self, ratings, embeddings=None): """ Parameters ---------- ratings : Ratings or list or np.ndarray The ratings of voters on which we run the election. embeddings : Embeddings or list or np.ndarray The embeddings of the voters on which we run the election. Return ------ ScoringRule The object itself """ self.delete_cache() self.ratings_ = Ratings(ratings) if embeddings is None: self.embeddings_ = self.embedder(self.ratings_) elif embeddings is not None: self.embeddings_ = Embeddings(embeddings) return self
def __call__(self, rule, ratings=None): """ This function is used to update the rule used, and also the ratings Parameters ---------- rule: ScoringRule The rule we will use ratings: np.ndarray or Ratings The ratings of the candidates Return ------ MovingVoter The object """ self.rule = rule if ratings is None: ratings = np.array([[.8, .8, .8, .5], [.1, .1, 1, .5], [.1, 1, .1, .5], [1, .1, .1, .5]]) self.ratings_ = Ratings(ratings) return self
def test_embeddings(): emb = Embeddings(np.array([[.2, .5, .3], [.3, .2, .2], [.6, .2, .3]])) emb.dilate(approx=False) emb.recenter(approx=False) emb = Embeddings(np.array([[1, 1, 1], [1, 1, 1]])) emb.dilate() emb = Embeddings(np.array([[1, 1]])) with pytest.raises(ValueError): emb.recenter() with pytest.raises(ValueError): emb.dilate() emb = Embeddings(np.array([[0, 1, 0], [1, 0, 0], [0, 0, 1]])) emb.recenter() emb.plot("3D", show=False) emb.plot("ternary", dim=[1, 2, 0], show=False) with pytest.raises(ValueError): emb.plot("test", show=False) with pytest.raises(ValueError): emb.plot("3D", dim=[1, 2], show=False) ratings = np.array([[1, .8, .5], [.6, .5, .2], [.6, .9, .5]]) emb.plot_candidate(ratings, 0, "3D", show=False) emb.plot_candidate(ratings, 0, "ternary", dim=[1, 2, 0], show=False) with pytest.raises(ValueError): emb.plot_candidate(ratings, 0, "test", show=False) emb.plot_candidates(Ratings(ratings), "3D", show=False) emb.plot_candidates(ratings, "ternary", show=False, list_titles=["c_1", "c_2", "c_3"]) with pytest.raises(ValueError): emb.plot_candidates(ratings, "test", show=False) Embeddings(np.array([[0, -1], [-1, 0]])).recenter()
def __call__(self, ratings): ratings = Ratings(ratings) n_dim = ratings.shape[0] return Embeddings(np.eye(n_dim))
def __call__(self, ratings): ratings = Ratings(ratings) n_voters = ratings.shape[0] embs = np.abs(np.random.randn(n_voters, self.n_dim)) return Embeddings(embs, norm=True)
def __init__(self, ratings, embeddings, rule=None): ratings = Ratings(ratings) super().__init__(ratings, embeddings, InstantRunoffExtension(ratings.n_candidates), rule)
def __call__(self, n_candidates, **kwargs): return Ratings(np.random.rand(self.n_voters, n_candidates))
def __init__(self, ratings, embeddings, rule=None): ratings = Ratings(ratings) super().__init__(ratings, embeddings, BordaExtension(ratings.n_candidates, rule), rule)
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 }
def __init__(self, ratings, embeddings, k=2, rule=None): ratings = Ratings(ratings) super().__init__(ratings, embeddings, KApprovalExtension(ratings.n_candidates, k=k), rule)
def __call__(self, n_candidates=1, *args): self.ground_truth_ = self.generate_true_values( n_candidates=n_candidates) m_noises_candidates = np.random.randn(self.n_noises, n_candidates) return Ratings(self.ground_truth_[np.newaxis, :] + self.m_voters_noises @ m_noises_candidates)
def __call__(self, ratings, embeddings=None): ratings = Ratings(ratings) self.aggregation_rule = lambda x: np.sum(np.log(1+x*ratings.n_voters)) super().__call__(ratings, embeddings) return self
class ManipulationCoalition(DeleteCacheMixin): """ This general class is used for the analysis of the manipulability of the rule by a coalition of voter. It only look if there is a trivial manipulation by a coalition of voter. That means, for some candidate `c` different than the current winner `w`, gather every voter who prefers `c` to `w`, and ask them to put `c` first and `w` last. If `c` is the new winner, then the ratings can be manipulated. 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 voter 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 welfare 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 = ManipulationCoalition(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): 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 trivial_manipulation(self, candidate, verbose=False): """ This function computes if a trivial manipulation is possible for the candidate passed as parameter. Parameters ---------- candidate : int The index of the candidate for which we manipulate. verbose : bool Verbose mode. By default, is set to False. Return ------ bool If True, the ratings is manipulable for this candidate. 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 = ManipulationCoalition(ratings, embeddings, SVDNash()) >>> manipulation.trivial_manipulation(0, verbose=True) 2 voters interested to elect 0 instead of 1 Winner is 0 True """ voters_interested = [] for i in range(self.ratings.shape[1]): score_i = self.ratings[i] if score_i[self.winner_] < score_i[candidate]: voters_interested.append(i) if verbose: print("%i voters interested to elect %i instead of %i" % (len(voters_interested), candidate, self.winner_)) profile = self.ratings.copy() for i in voters_interested: profile[i] = np.zeros(self.ratings.shape[1]) profile[i][candidate] = 1 new_winner = self.rule(profile, self.embeddings).winner_ if verbose: print("Winner is %i" % new_winner) return new_winner == candidate @cached_property def is_manipulable_(self): """ A function that quickly computes if the ratings is manipulable. Return ------ bool If True, the ratings is manipulable for some candidate. 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 = ManipulationCoalition(ratings, embeddings, SVDNash()) >>> manipulation.is_manipulable_ True """ for i in range(self.ratings.n_candidates): if i == self.winner_: continue if self.trivial_manipulation(i): return True return False @cached_property def worst_welfare_(self): """ A function that compute the worst welfare attainable by coalition 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 = ManipulationCoalition(ratings, embeddings, SVDNash()) >>> manipulation.worst_welfare_ 0.0 """ worst_welfare = self.welfare_[self.winner_] for i in range(self.ratings.n_candidates): if i == self.winner_: continue if self.trivial_manipulation(i): worst_welfare = min(worst_welfare, self.welfare_[i]) return worst_welfare 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`` vary. 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.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 not specified, a new matrix is generated for each test. show : bool If True, displays 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 = ManipulationCoalition(rat, emb, SVDNash()) >>> maps = manipulation.manipulation_map(map_size=5, show=False) >>> maps['worst_welfare'] array([[1. , 0.98024051, 1. , 1. , 1. ], [1. , 1. , 1. , 1. , 1. ], [0.87833419, 1. , 1. , 0.97819913, 1. ], [0.80173984, 0.74016286, 1. , 1. , 1. ], [0.81466796, 1. , 1. , 0.92431457, 1. ]]) """ manipulator_map = np.zeros((map_size, map_size)) worst_welfare_map = 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) worst_welfare = self.welfare_[self.winner_] is_manipulable = 0 for candidate in range(self.ratings.n_candidates): if candidate == self.winner_: continue if self.trivial_manipulation(candidate): is_manipulable = 1 worst_welfare = min(worst_welfare, self.welfare_[candidate]) manipulator_map[i, j] = is_manipulable worst_welfare_map[i, j] = worst_welfare if show: fig = plt.figure(figsize=(10, 5)) create_map_plot(fig, manipulator_map, [1, 2, 1], "Proportion of manipulators") create_map_plot(fig, worst_welfare_map, [1, 2, 2], "Worst welfare") plt.show() return { "manipulator": manipulator_map, "worst_welfare": worst_welfare_map }