pref_voting 1.16.31__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pref_voting/__init__.py +1 -0
- pref_voting/analysis.py +496 -0
- pref_voting/axiom.py +38 -0
- pref_voting/axiom_helpers.py +129 -0
- pref_voting/axioms.py +10 -0
- pref_voting/c1_methods.py +963 -0
- pref_voting/combined_methods.py +514 -0
- pref_voting/create_methods.py +128 -0
- pref_voting/data/examples/condorcet_winner/minimal_Anti-Plurality.soc +16 -0
- pref_voting/data/examples/condorcet_winner/minimal_Borda.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_Bracket_Voting.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_Bucklin.soc +19 -0
- pref_voting/data/examples/condorcet_winner/minimal_Coombs.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_Coombs_PUT.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_Coombs_TB.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_Dowdall.soc +19 -0
- pref_voting/data/examples/condorcet_winner/minimal_Instant_Runoff.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_Instant_Runoff_PUT.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_Instant_Runoff_TB.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_Iterated_Removal_Condorcet_Loser.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_Pareto.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_Plurality.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_PluralityWRunoff_PUT.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_Positive-Negative_Voting.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_Simplified_Bucklin.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_Superior_Voting.soc +19 -0
- pref_voting/data/examples/condorcet_winner/minimal_Weighted_Bucklin.soc +19 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Anti-Plurality.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Borda.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Bracket_Voting.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Bucklin.soc +19 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Coombs.soc +21 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Coombs_PUT.soc +21 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Coombs_TB.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Dowdall.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Instant_Runoff.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Instant_Runoff_PUT.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Instant_Runoff_TB.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Plurality.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_PluralityWRunoff_PUT.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Positive-Negative_Voting.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Simplified_Bucklin.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Weighted_Bucklin.soc +19 -0
- pref_voting/data/voting_methods_properties.json +414 -0
- pref_voting/data/voting_methods_properties.json.lock +0 -0
- pref_voting/dominance_axioms.py +387 -0
- pref_voting/generate_profiles.py +801 -0
- pref_voting/generate_spatial_profiles.py +198 -0
- pref_voting/generate_utility_profiles.py +160 -0
- pref_voting/generate_weighted_majority_graphs.py +506 -0
- pref_voting/grade_methods.py +184 -0
- pref_voting/grade_profiles.py +357 -0
- pref_voting/helper.py +370 -0
- pref_voting/invariance_axioms.py +671 -0
- pref_voting/io/__init__.py +0 -0
- pref_voting/io/readers.py +432 -0
- pref_voting/io/writers.py +256 -0
- pref_voting/iterative_methods.py +2425 -0
- pref_voting/maj_graph_ex1.png +0 -0
- pref_voting/mappings.py +577 -0
- pref_voting/margin_based_methods.py +2345 -0
- pref_voting/monotonicity_axioms.py +872 -0
- pref_voting/num_evaluation_method.py +77 -0
- pref_voting/other_axioms.py +161 -0
- pref_voting/other_methods.py +939 -0
- pref_voting/pairwise_profiles.py +547 -0
- pref_voting/prob_voting_method.py +105 -0
- pref_voting/probabilistic_methods.py +287 -0
- pref_voting/profiles.py +856 -0
- pref_voting/profiles_with_ties.py +1069 -0
- pref_voting/rankings.py +466 -0
- pref_voting/scoring_methods.py +481 -0
- pref_voting/social_welfare_function.py +59 -0
- pref_voting/social_welfare_functions.py +7 -0
- pref_voting/spatial_profiles.py +448 -0
- pref_voting/stochastic_methods.py +99 -0
- pref_voting/strategic_axioms.py +1394 -0
- pref_voting/swf_axioms.py +173 -0
- pref_voting/utility_functions.py +102 -0
- pref_voting/utility_methods.py +178 -0
- pref_voting/utility_profiles.py +333 -0
- pref_voting/variable_candidate_axioms.py +640 -0
- pref_voting/variable_voter_axioms.py +3747 -0
- pref_voting/voting_method.py +355 -0
- pref_voting/voting_method_properties.py +92 -0
- pref_voting/voting_methods.py +8 -0
- pref_voting/voting_methods_registry.py +136 -0
- pref_voting/weighted_majority_graphs.py +1539 -0
- pref_voting-1.16.31.dist-info/METADATA +208 -0
- pref_voting-1.16.31.dist-info/RECORD +92 -0
- pref_voting-1.16.31.dist-info/WHEEL +4 -0
- pref_voting-1.16.31.dist-info/licenses/LICENSE.txt +21 -0
@@ -0,0 +1,963 @@
|
|
1
|
+
'''
|
2
|
+
File: c1_methods.py
|
3
|
+
Author: Wes Holliday (wesholliday@berkeley.edu) and Eric Pacuit (epacuit@umd.edu)
|
4
|
+
Date: January 10, 2022
|
5
|
+
Update: July 31, 2022
|
6
|
+
|
7
|
+
Implementations of voting methods that work on both profiles and majority graphs.
|
8
|
+
'''
|
9
|
+
|
10
|
+
from pref_voting.voting_method import *
|
11
|
+
from pref_voting.helper import get_mg, get_weak_mg
|
12
|
+
from pref_voting.margin_based_methods import distance_to_margin_graph
|
13
|
+
from pref_voting.probabilistic_methods import c1_maximal_lottery
|
14
|
+
from pref_voting.rankings import Ranking, break_ties_alphabetically
|
15
|
+
from pref_voting.social_welfare_function import swf
|
16
|
+
import copy
|
17
|
+
import math
|
18
|
+
from itertools import product, permutations, combinations, chain
|
19
|
+
import networkx as nx
|
20
|
+
import matplotlib.pyplot as plt
|
21
|
+
from pref_voting.voting_method_properties import ElectionTypes
|
22
|
+
|
23
|
+
@vm(name = "Condorcet",
|
24
|
+
input_types = [ElectionTypes.PROFILE, ElectionTypes.PROFILE_WITH_TIES, ElectionTypes.MAJORITY_GRAPH, ElectionTypes.MARGIN_GRAPH])
|
25
|
+
def condorcet(edata, curr_cands = None):
|
26
|
+
"""
|
27
|
+
Return the Condorcet winner if one exists, otherwise return all the candidates. A Condorcet winner is a candidate :math:`c` that is majority preferred to every other candidate.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
edata (Profile, ProfileWithTies, MajorityGraph, MarginGraph): Any election data that has a `condorcet_winner` method.
|
31
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
A sorted list of candidates
|
35
|
+
|
36
|
+
.. seealso::
|
37
|
+
|
38
|
+
:meth:`pref_voting.profiles.Profile.condorcet_winner`, :meth:`pref_voting.profiles_with_ties.ProfileWithTies.condorcet_winner`, :meth:`pref_voting.weighted_majority_graphs.MajorityGraph.condorcet_winner`
|
39
|
+
|
40
|
+
:Example:
|
41
|
+
|
42
|
+
.. exec_code::
|
43
|
+
|
44
|
+
from pref_voting.profiles import Profile
|
45
|
+
from pref_voting.c1_methods import condorcet
|
46
|
+
|
47
|
+
prof = Profile([[0, 1, 2], [1, 2, 0], [2, 0, 1]], [1, 1, 1])
|
48
|
+
|
49
|
+
prof.display()
|
50
|
+
print(prof.condorcet_winner())
|
51
|
+
condorcet.display(prof)
|
52
|
+
condorcet.display(prof.majority_graph())
|
53
|
+
condorcet.display(prof.margin_graph())
|
54
|
+
|
55
|
+
prof2 = Profile([[0, 1, 2], [2, 1, 0], [1, 0, 2]], [3, 1, 1])
|
56
|
+
|
57
|
+
prof2.display()
|
58
|
+
print(prof2.condorcet_winner())
|
59
|
+
condorcet.display(prof2)
|
60
|
+
condorcet.display(prof2.majority_graph())
|
61
|
+
condorcet.display(prof2.margin_graph())
|
62
|
+
|
63
|
+
"""
|
64
|
+
|
65
|
+
candidates = edata.candidates if curr_cands is None else curr_cands
|
66
|
+
cond_winner = edata.condorcet_winner(curr_cands = curr_cands)
|
67
|
+
|
68
|
+
return [cond_winner] if cond_winner is not None else sorted(candidates)
|
69
|
+
|
70
|
+
@vm(name = "Weak Condorcet",
|
71
|
+
input_types = [ElectionTypes.PROFILE, ElectionTypes.PROFILE_WITH_TIES, ElectionTypes.MAJORITY_GRAPH, ElectionTypes.MARGIN_GRAPH])
|
72
|
+
def weak_condorcet(edata, curr_cands = None):
|
73
|
+
|
74
|
+
"""
|
75
|
+
Return all weak Condorcet winner if one exists, otherwise return all the candidates. A weak Condorcet winner is a candidate :math:`c` such that no other candidate is majority preferred to :math:`c`.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
edata (Profile, ProfileWithTies, MajorityGraph, MarginGraph): Any election data that has a `weak_condorcet_winner` method.
|
79
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
A sorted list of candidates
|
83
|
+
|
84
|
+
.. seealso::
|
85
|
+
|
86
|
+
:meth:`pref_voting.profiles.Profile.weak_condorcet_winner`,
|
87
|
+
:meth:`pref_voting.profiles_with_ties.ProfileWithTies.weak_condorcet_winner`,
|
88
|
+
:meth:`pref_voting.weighted_majority_graphs.MajorityGraph.weak_condorcet_winner`
|
89
|
+
|
90
|
+
"""
|
91
|
+
|
92
|
+
candidates = edata.candidates if curr_cands is None else curr_cands
|
93
|
+
weak_cond_winners = edata.weak_condorcet_winner(curr_cands = curr_cands)
|
94
|
+
|
95
|
+
return weak_cond_winners if weak_cond_winners is not None else sorted(candidates)
|
96
|
+
|
97
|
+
@vm(name = "Copeland",
|
98
|
+
input_types = [ElectionTypes.PROFILE, ElectionTypes.PROFILE_WITH_TIES, ElectionTypes.MAJORITY_GRAPH, ElectionTypes.MARGIN_GRAPH])
|
99
|
+
def copeland(edata, curr_cands = None):
|
100
|
+
"""
|
101
|
+
The Copeland score for c is the number of candidates that c is majority preferred to minus the number of candidates majority preferred to c. The Copeland winners are the candidates with the maximum Copeland score in the profile restricted to ``curr_cands``.
|
102
|
+
|
103
|
+
Equivalently, give each candidate 1 point for each head-to-head win, 1/2 point for each head-to-head tie, and 0 points for each head-to-head loss. Then the Copeland winners are the candidates with the maximum number of points.
|
104
|
+
|
105
|
+
Args:
|
106
|
+
edata (Profile, ProfileWithTies, MajorityGraph, MarginGraph): Any election data that has a `copeland_scores` method.
|
107
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
108
|
+
|
109
|
+
Returns:
|
110
|
+
A sorted list of candidates
|
111
|
+
|
112
|
+
.. seealso::
|
113
|
+
|
114
|
+
:meth:`pref_voting.profiles.Profile.copeland_scores`, :meth:`pref_voting.profiles_with_ties.ProfileWithTies.copeland_scores`, :meth:`pref_voting.weighted_majority_graphs.MajorityGraph.copeland_scores`
|
115
|
+
|
116
|
+
|
117
|
+
:Example:
|
118
|
+
|
119
|
+
.. plot:: margin_graphs_examples/mg_ex_copeland_llull.py
|
120
|
+
:context: reset
|
121
|
+
:include-source: True
|
122
|
+
|
123
|
+
|
124
|
+
.. code-block::
|
125
|
+
|
126
|
+
from pref_voting.c1_methods import copeland
|
127
|
+
copeland.display(prof)
|
128
|
+
|
129
|
+
|
130
|
+
.. exec_code::
|
131
|
+
:hide_code:
|
132
|
+
|
133
|
+
from pref_voting.profiles import Profile
|
134
|
+
from pref_voting.c1_methods import copeland
|
135
|
+
|
136
|
+
prof = Profile([[1, 3, 0, 4, 2], [0, 1, 4, 2, 3], [2, 4, 0, 1, 3], [3, 0, 2, 4, 1], [4, 3, 1, 0, 2], [2, 3, 0, 1, 4]], [1, 1, 1, 1, 1, 1])
|
137
|
+
|
138
|
+
copeland.display(prof)
|
139
|
+
print(prof.copeland_scores())
|
140
|
+
|
141
|
+
|
142
|
+
"""
|
143
|
+
c_scores = edata.copeland_scores(curr_cands = curr_cands)
|
144
|
+
max_score = max(c_scores.values())
|
145
|
+
return sorted([c for c in c_scores.keys() if c_scores[c] == max_score])
|
146
|
+
|
147
|
+
@swf(name = "Copeland ranking")
|
148
|
+
def copeland_ranking(edata, curr_cands=None, local=True, tie_breaking=None):
|
149
|
+
"""The SWF that ranks candidates by their Copeland scores. If local is True, then the Copeland scores are computed with respect to the profile restricted to curr_cands. Otherwise, the Copeland scores are computed with respect to the entire profile.
|
150
|
+
|
151
|
+
Args:
|
152
|
+
profile (Profile): An anonymous profile of linear orders on a set of candidates
|
153
|
+
curr_cands (List[int], optional): The candidates to rank. If None, then all candidates in profile are ranked
|
154
|
+
local (bool, optional): If True, then the Copeland scores are computed with respect to the profile restricted to curr_cands. Otherwise, the Copeland scores are computed with respect to the entire profile.
|
155
|
+
tie_breaking (str, optional): The tie-breaking method to use. If None, then no tie-breaking is used. If "alphabetic", then the tie-breaking is done alphabetically.
|
156
|
+
|
157
|
+
Returns:
|
158
|
+
A Ranking object
|
159
|
+
"""
|
160
|
+
|
161
|
+
cands = edata.candidates if curr_cands is None else curr_cands
|
162
|
+
|
163
|
+
if local:
|
164
|
+
copeland_scores_dict = edata.copeland_scores(curr_cands=cands)
|
165
|
+
|
166
|
+
else:
|
167
|
+
c_scores = edata.copeland_scores(curr_cands=edata.candidates)
|
168
|
+
copeland_scores_dict = {c: c_scores[c] for c in cands}
|
169
|
+
|
170
|
+
for cand in cands:
|
171
|
+
copeland_scores_dict[cand] = -copeland_scores_dict[cand]
|
172
|
+
|
173
|
+
copeland_ranking = Ranking(copeland_scores_dict)
|
174
|
+
copeland_ranking.normalize_ranks()
|
175
|
+
|
176
|
+
if tie_breaking == "alphabetic":
|
177
|
+
copeland_ranking = break_ties_alphabetically(copeland_ranking)
|
178
|
+
|
179
|
+
return copeland_ranking
|
180
|
+
|
181
|
+
|
182
|
+
@vm(name = "Llull",
|
183
|
+
input_types = [ElectionTypes.PROFILE, ElectionTypes.PROFILE_WITH_TIES, ElectionTypes.MAJORITY_GRAPH, ElectionTypes.MARGIN_GRAPH])
|
184
|
+
def llull(edata, curr_cands = None):
|
185
|
+
"""The Llull score for a candidate :math:`c` is the number of candidates that :math:`c` is weakly majority preferred to. This is equivalent to calculating the Copeland scores for a candidate :math:`c` with 1 point for each candidate that :math:`c` is majority preferred to, 1 point for each candidate that :math:`c` is tied with (instead of 1/2 a point as for Copeland), and 0 points for each candidate that is majority preferred to :math:`c`. The Llull winners are the candidates with the maximum Llull score in the profile restricted to ``curr_cands``.
|
186
|
+
|
187
|
+
Args:
|
188
|
+
edata (Profile, ProfileWithTies, MajorityGraph, MarginGraph): Any election data that has a `copeland_scores` method.
|
189
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
190
|
+
|
191
|
+
Returns:
|
192
|
+
A sorted list of candidates
|
193
|
+
|
194
|
+
.. seealso::
|
195
|
+
|
196
|
+
:meth:`pref_voting.profiles.Profile.copeland_scores`, :meth:`pref_voting.profiles_with_ties.ProfileWithTies.copeland_scores`, :meth:`pref_voting.weighted_majority_graphs.MajorityGraph.copeland_scores`
|
197
|
+
|
198
|
+
:Example:
|
199
|
+
|
200
|
+
.. plot:: margin_graphs_examples/mg_ex_copeland_llull.py
|
201
|
+
:context: reset
|
202
|
+
:include-source: True
|
203
|
+
|
204
|
+
|
205
|
+
.. code-block::
|
206
|
+
|
207
|
+
from pref_voting.c1_methods import llull
|
208
|
+
llull.display(prof)
|
209
|
+
|
210
|
+
|
211
|
+
.. exec_code::
|
212
|
+
:hide_code:
|
213
|
+
|
214
|
+
from pref_voting.profiles import Profile
|
215
|
+
from pref_voting.c1_methods import llull
|
216
|
+
|
217
|
+
prof = Profile([[1, 3, 0, 4, 2], [0, 1, 4, 2, 3], [2, 4, 0, 1, 3], [3, 0, 2, 4, 1], [4, 3, 1, 0, 2], [2, 3, 0, 1, 4]], [1, 1, 1, 1, 1, 1])
|
218
|
+
|
219
|
+
llull.display(prof)
|
220
|
+
print(prof.copeland_scores(scores=(1, 0.5, 0)))
|
221
|
+
|
222
|
+
"""
|
223
|
+
|
224
|
+
l_scores = edata.copeland_scores(curr_cands = curr_cands, scores = (1,1,0))
|
225
|
+
max_score = max(l_scores.values())
|
226
|
+
return sorted([c for c in l_scores.keys() if l_scores[c] == max_score])
|
227
|
+
|
228
|
+
def left_covers(dom, c1, c2):
|
229
|
+
# left covers: c1 left covers c2 when all the candidates that are majority preferred to c1 are also majority preferred to c2.
|
230
|
+
|
231
|
+
# weakly left covers: c1 weakly left covers c2 when all the candidates that are majority preferred to or tied with c1
|
232
|
+
# are also majority preferred to or tied with c2.
|
233
|
+
|
234
|
+
return dom[c1].issubset(dom[c2])
|
235
|
+
|
236
|
+
def right_covers(dom, c1, c2):
|
237
|
+
# right covers: c1 right covers c2 when all the candidates that c2 majority preferrs are majority
|
238
|
+
# preferred by c1
|
239
|
+
|
240
|
+
return dom[c2].issubset(dom[c1])
|
241
|
+
|
242
|
+
@vm(name = "Uncovered Set",
|
243
|
+
input_types = [ElectionTypes.PROFILE, ElectionTypes.PROFILE_WITH_TIES, ElectionTypes.MAJORITY_GRAPH, ElectionTypes.MARGIN_GRAPH])
|
244
|
+
def uc_gill(edata, curr_cands = None):
|
245
|
+
"""Uncovered Set (Gillies version): Given candidates :math:`a` and :math:`b`, say that :math:`a` defeats :math:`b` in the election if :math:`a` is majority preferred to :math:`b` and :math:`a` left covers :math:`b`: i.e., for all :math:`c`, if :math:`c` is majority preferred to :math:`a`, then :math:`c` majority preferred to :math:`b`. The winners are the set of candidates who are undefeated in the election restricted to ``curr_cands``.
|
246
|
+
|
247
|
+
Args:
|
248
|
+
edata (Profile, ProfileWithTies, MajorityGraph, MarginGraph): Any election data that has a `dominators` method.
|
249
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
250
|
+
|
251
|
+
Returns:
|
252
|
+
A sorted list of candidates
|
253
|
+
|
254
|
+
.. seealso::
|
255
|
+
|
256
|
+
:func:`pref_voting.c1_methods.uc_fish`, :func:`pref_voting.c1_methods.uc_bordes`, :func:`pref_voting.c1_methods.uc_mckelvey`
|
257
|
+
|
258
|
+
:Example:
|
259
|
+
|
260
|
+
.. plot:: margin_graphs_examples/mg_ex_uncovered_sets.py
|
261
|
+
:context: reset
|
262
|
+
:include-source: True
|
263
|
+
|
264
|
+
.. code-block::
|
265
|
+
|
266
|
+
from pref_voting.c1_methods import uc_gill
|
267
|
+
uc_gill.display(prof)
|
268
|
+
|
269
|
+
|
270
|
+
.. exec_code::
|
271
|
+
:hide_code:
|
272
|
+
|
273
|
+
from pref_voting.profiles import Profile
|
274
|
+
from pref_voting.c1_methods import uc_gill
|
275
|
+
|
276
|
+
prof = Profile([[2, 3, 0, 1], [0, 2, 1, 3], [3, 0, 1, 2], [1, 2, 0, 3], [1, 2, 3, 0]], [1, 1, 1, 2, 1])
|
277
|
+
|
278
|
+
uc_gill.display(prof)
|
279
|
+
|
280
|
+
"""
|
281
|
+
|
282
|
+
candidates = edata.candidates if curr_cands is None else curr_cands
|
283
|
+
dom = {c: set(edata.dominators(c, curr_cands = curr_cands)) for c in candidates}
|
284
|
+
uc_set = list()
|
285
|
+
for c1 in candidates:
|
286
|
+
is_in_ucs = True
|
287
|
+
for c2 in edata.dominators(c1, curr_cands = curr_cands): # consider only c2 predecessors
|
288
|
+
if c1 != c2:
|
289
|
+
# check if c2 left covers c1
|
290
|
+
if left_covers(dom, c2, c1):
|
291
|
+
is_in_ucs = False
|
292
|
+
if is_in_ucs:
|
293
|
+
uc_set.append(c1)
|
294
|
+
return list(sorted(uc_set))
|
295
|
+
|
296
|
+
def uc_gill_defeat(edata, curr_cands = None):
|
297
|
+
"""Returns the defeat relation used to find the Uncovered Set (Gillies version) winners.
|
298
|
+
|
299
|
+
Args:
|
300
|
+
edata (Profile, ProfileWithTies, MajorityGraph, MarginGraph): Any election data that has a `dominators` method.
|
301
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
302
|
+
|
303
|
+
Returns:
|
304
|
+
A networkx object in which there is an edge from :math:`a` to :math:`b` when :math:`a` to :math:`b` according to Top Cycle.
|
305
|
+
|
306
|
+
.. seealso::
|
307
|
+
|
308
|
+
:func:`pref_voting.c1_methods.uc_gill`
|
309
|
+
|
310
|
+
:Example:
|
311
|
+
|
312
|
+
|
313
|
+
.. plot:: margin_graphs_examples/uc_gill_defeat_example.py
|
314
|
+
:include-source: True
|
315
|
+
|
316
|
+
|
317
|
+
"""
|
318
|
+
|
319
|
+
defeat = nx.DiGraph()
|
320
|
+
|
321
|
+
candidates = edata.candidates if curr_cands is None else curr_cands
|
322
|
+
|
323
|
+
defeat.add_nodes_from(candidates)
|
324
|
+
|
325
|
+
dom = {c: set(edata.dominators(c, curr_cands = curr_cands)) for c in candidates}
|
326
|
+
for c1 in candidates:
|
327
|
+
for c2 in edata.dominators(c1, curr_cands = curr_cands): # consider only c2 predecessors
|
328
|
+
if c1 != c2:
|
329
|
+
# check if c2 left covers c1
|
330
|
+
if left_covers(dom, c2, c1):
|
331
|
+
defeat.add_edge(c2, c1)
|
332
|
+
return defeat
|
333
|
+
|
334
|
+
@vm(name = "Uncovered Set - Fishburn",
|
335
|
+
input_types = [ElectionTypes.PROFILE, ElectionTypes.PROFILE_WITH_TIES, ElectionTypes.MAJORITY_GRAPH, ElectionTypes.MARGIN_GRAPH])
|
336
|
+
def uc_fish(edata, curr_cands = None):
|
337
|
+
"""Uncovered Set (Fishburn version): Given candidates :math:`a` and :math:`b`, say that :math:`a` defeats :math:`b` in the election :math:`a` left covers :math:`b`: i.e., for all :math:`c`, if :math:`c` is majority preferred to :math:`a`, then :math:`c` majority preferred to :math:`b`. The winners are the set of candidates who are undefeated in the election restricted to ``curr_cands``.
|
338
|
+
|
339
|
+
Args:
|
340
|
+
edata (Profile, ProfileWithTies, MajorityGraph, MarginGraph): Any election data that has a `dominators` method.
|
341
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
342
|
+
|
343
|
+
Returns:
|
344
|
+
A sorted list of candidates
|
345
|
+
|
346
|
+
.. seealso::
|
347
|
+
|
348
|
+
:func:`pref_voting.c1_methods.uc_gill`, :func:`pref_voting.c1_methods.uc_bordes`, :func:`pref_voting.c1_methods.uc_mckelvey`
|
349
|
+
|
350
|
+
:Example:
|
351
|
+
|
352
|
+
|
353
|
+
.. plot:: margin_graphs_examples/mg_ex_uncovered_sets.py
|
354
|
+
:include-source: True
|
355
|
+
|
356
|
+
|
357
|
+
.. code-block::
|
358
|
+
|
359
|
+
from pref_voting.c1_methods import uc_fish
|
360
|
+
uc_fish.display(prof)
|
361
|
+
|
362
|
+
|
363
|
+
.. exec_code::
|
364
|
+
:hide_code:
|
365
|
+
|
366
|
+
from pref_voting.profiles import Profile
|
367
|
+
from pref_voting.c1_methods import uc_fish
|
368
|
+
|
369
|
+
prof = Profile([[2, 3, 0, 1], [0, 2, 1, 3], [3, 0, 1, 2], [1, 2, 0, 3], [1, 2, 3, 0]], [1, 1, 1, 2, 1])
|
370
|
+
|
371
|
+
uc_fish.display(prof)
|
372
|
+
|
373
|
+
"""
|
374
|
+
candidates = edata.candidates if curr_cands is None else curr_cands
|
375
|
+
dom = {c: set(edata.dominators(c, curr_cands = curr_cands)) for c in candidates}
|
376
|
+
uc_set = list()
|
377
|
+
for c1 in candidates:
|
378
|
+
is_in_ucs = True
|
379
|
+
for c2 in candidates:
|
380
|
+
if c1 != c2:
|
381
|
+
# check if c2 left covers c1 but c1 does not left cover c2
|
382
|
+
if left_covers(dom, c2, c1) and not left_covers(dom, c1, c2):
|
383
|
+
is_in_ucs = False
|
384
|
+
if is_in_ucs:
|
385
|
+
uc_set.append(c1)
|
386
|
+
return list(sorted(uc_set))
|
387
|
+
|
388
|
+
def uc_fish_defeat(edata, curr_cands = None):
|
389
|
+
"""Returns the defeat relation used to find the Uncovered Set (Fishburn version) winners.
|
390
|
+
|
391
|
+
Args:
|
392
|
+
edata (Profile, ProfileWithTies, MajorityGraph, MarginGraph): Any election data that has a `dominators` method.
|
393
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
394
|
+
|
395
|
+
Returns:
|
396
|
+
A networkx object in which there is an edge from :math:`a` to :math:`b` when :math:`a` to :math:`b` according to Top Cycle.
|
397
|
+
|
398
|
+
.. seealso::
|
399
|
+
|
400
|
+
:func:`pref_voting.c1_methods.uc_fish`
|
401
|
+
|
402
|
+
|
403
|
+
:Example:
|
404
|
+
|
405
|
+
|
406
|
+
.. plot:: margin_graphs_examples/uc_fish_defeat_example.py
|
407
|
+
:include-source: True
|
408
|
+
|
409
|
+
"""
|
410
|
+
defeat = nx.DiGraph()
|
411
|
+
|
412
|
+
candidates = edata.candidates if curr_cands is None else curr_cands
|
413
|
+
|
414
|
+
defeat.add_nodes_from(candidates)
|
415
|
+
dom = {c: set(edata.dominators(c, curr_cands = curr_cands)) for c in candidates}
|
416
|
+
for c1 in candidates:
|
417
|
+
is_in_ucs = True
|
418
|
+
for c2 in candidates:
|
419
|
+
if c1 != c2:
|
420
|
+
# check if c2 left covers c1 but c1 does not left cover c2
|
421
|
+
if left_covers(dom, c2, c1) and not left_covers(dom, c1, c2):
|
422
|
+
defeat.add_edge(c2, c1)
|
423
|
+
return defeat
|
424
|
+
|
425
|
+
@vm(name = "Uncovered Set - Bordes",
|
426
|
+
input_types = [ElectionTypes.PROFILE, ElectionTypes.PROFILE_WITH_TIES, ElectionTypes.MAJORITY_GRAPH, ElectionTypes.MARGIN_GRAPH])
|
427
|
+
def uc_bordes(edata, curr_cands = None):
|
428
|
+
"""Uncovered Set (Bordes version): Given candidates :math:`a` and :math:`b`, say that :math:`a` Bordes covers :math:`b` if :math:`a` is majority preferred to :math:`b` and for all :math:`c`, if :math:`c` is majority preferred or tied with :math:`a`, then :math:`c` is majority preferred to or tied with :math:`b`. The winners are the set of candidates who are not Bordes covered in the election restricted to ``curr_cands``.
|
429
|
+
|
430
|
+
Args:
|
431
|
+
edata (Profile, ProfileWithTies, MajorityGraph, MarginGraph): Any election data that has `dominators` and `majority_prefers` methods.
|
432
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
433
|
+
|
434
|
+
Returns:
|
435
|
+
A sorted list of candidates
|
436
|
+
|
437
|
+
.. seealso::
|
438
|
+
|
439
|
+
:func:`pref_voting.c1_methods.uc_gill`, :func:`pref_voting.c1_methods.uc_fish`, :func:`pref_voting.c1_methods.uc_mckelvey`
|
440
|
+
|
441
|
+
:Example:
|
442
|
+
|
443
|
+
|
444
|
+
.. plot:: margin_graphs_examples/mg_ex_uncovered_sets.py
|
445
|
+
:context: reset
|
446
|
+
:include-source: True
|
447
|
+
|
448
|
+
|
449
|
+
.. code-block::
|
450
|
+
|
451
|
+
from pref_voting.c1_methods import uc_bordes
|
452
|
+
uc_bordes.display(prof)
|
453
|
+
|
454
|
+
|
455
|
+
.. exec_code::
|
456
|
+
:hide_code:
|
457
|
+
|
458
|
+
from pref_voting.profiles import Profile
|
459
|
+
from pref_voting.c1_methods import uc_bordes
|
460
|
+
|
461
|
+
prof = Profile([[2, 3, 0, 1], [0, 2, 1, 3], [3, 0, 1, 2], [1, 2, 0, 3], [1, 2, 3, 0]], [1, 1, 1, 2, 1])
|
462
|
+
|
463
|
+
uc_bordes.display(prof)
|
464
|
+
|
465
|
+
"""
|
466
|
+
|
467
|
+
candidates = edata.candidates if curr_cands is None else curr_cands
|
468
|
+
|
469
|
+
dom = {c: set(edata.dominators(c, curr_cands = curr_cands)).union([_c for _c in candidates if (not edata.majority_prefers(c, _c) and not edata.majority_prefers(_c, c))]) for c in candidates}
|
470
|
+
|
471
|
+
uc_set = list()
|
472
|
+
for c1 in candidates:
|
473
|
+
is_in_ucs = True
|
474
|
+
for c2 in edata.dominators(c1, curr_cands = curr_cands): # consider only c2 predecessors
|
475
|
+
if c1 != c2:
|
476
|
+
# check if c2 left covers c1
|
477
|
+
if left_covers(dom, c2, c1):
|
478
|
+
is_in_ucs = False
|
479
|
+
if is_in_ucs:
|
480
|
+
uc_set.append(c1)
|
481
|
+
return list(sorted(uc_set))
|
482
|
+
|
483
|
+
@vm(name = "Uncovered Set - McKelvey",
|
484
|
+
input_types = [ElectionTypes.PROFILE, ElectionTypes.PROFILE_WITH_TIES, ElectionTypes.MAJORITY_GRAPH, ElectionTypes.MARGIN_GRAPH])
|
485
|
+
def uc_mckelvey(edata, curr_cands = None):
|
486
|
+
"""Uncovered Set (McKelvey version): Given candidates :math:`a` and :math:`b`, say that :math:`a` McKelvey covers :math:`b` if a Gillies covers :math:`b` and :math:`a` Bordes covers :math:`b`. The winners are the set of candidates who are not McKelvey covered in the election restricted to ``curr_cands``.
|
487
|
+
|
488
|
+
Args:
|
489
|
+
edata (Profile, ProfileWithTies, MajorityGraph, MarginGraph): Any election data that has `dominators` and `majority_prefers` methods.
|
490
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
491
|
+
|
492
|
+
Returns:
|
493
|
+
A sorted list of candidates
|
494
|
+
|
495
|
+
.. seealso::
|
496
|
+
|
497
|
+
:func:`pref_voting.c1_methods.uc_gill`, :func:`pref_voting.c1_methods.uc_fish`, :func:`pref_voting.c1_methods.uc_bordes`
|
498
|
+
|
499
|
+
:Example:
|
500
|
+
|
501
|
+
.. plot:: margin_graphs_examples/mg_ex_uncovered_sets.py
|
502
|
+
:context: reset
|
503
|
+
:include-source: True
|
504
|
+
|
505
|
+
|
506
|
+
.. code-block::
|
507
|
+
|
508
|
+
from pref_voting.c1_methods import uc_mckelvey
|
509
|
+
uc_bordes.display(prof)
|
510
|
+
|
511
|
+
|
512
|
+
.. exec_code::
|
513
|
+
:hide_code:
|
514
|
+
|
515
|
+
from pref_voting.profiles import Profile
|
516
|
+
from pref_voting.c1_methods import uc_mckelvey
|
517
|
+
|
518
|
+
prof = Profile([[2, 3, 0, 1], [0, 2, 1, 3], [3, 0, 1, 2], [1, 2, 0, 3], [1, 2, 3, 0]], [1, 1, 1, 2, 1])
|
519
|
+
|
520
|
+
uc_mckelvey.display(prof)
|
521
|
+
|
522
|
+
"""
|
523
|
+
candidates = edata.candidates if curr_cands is None else curr_cands
|
524
|
+
|
525
|
+
strict_dom = {c: set(edata.dominators(c, curr_cands = curr_cands)) for c in candidates}
|
526
|
+
weak_dom = {c: strict_dom[c].union([_c for _c in candidates if (not edata.majority_prefers(c, _c) and not edata.majority_prefers(_c, c))]) for c in candidates}
|
527
|
+
uc_set = list()
|
528
|
+
for c1 in candidates:
|
529
|
+
is_in_ucs = True
|
530
|
+
for c2 in edata.dominators(c1, curr_cands = curr_cands): # consider only c2 predecessors
|
531
|
+
if c1 != c2:
|
532
|
+
# check if c2 left covers c1
|
533
|
+
if left_covers(strict_dom, c2, c1) and left_covers(weak_dom, c2, c1):
|
534
|
+
is_in_ucs = False
|
535
|
+
if is_in_ucs:
|
536
|
+
uc_set.append(c1)
|
537
|
+
return list(sorted(uc_set))
|
538
|
+
|
539
|
+
@vm(name = "Top Cycle",
|
540
|
+
input_types = [ElectionTypes.PROFILE, ElectionTypes.PROFILE_WITH_TIES, ElectionTypes.MAJORITY_GRAPH, ElectionTypes.MARGIN_GRAPH])
|
541
|
+
def top_cycle(edata, curr_cands = None):
|
542
|
+
"""The smallest set of candidates such that every candidate inside the set is majority preferred to every candidate outside the set.
|
543
|
+
|
544
|
+
Args:
|
545
|
+
edata (Profile, ProfileWithTies, MajorityGraph, MarginGraph): Any election data that has a `majority_prefers` method.
|
546
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
547
|
+
|
548
|
+
Returns:
|
549
|
+
A sorted list of candidates
|
550
|
+
|
551
|
+
.. seealso::
|
552
|
+
|
553
|
+
Also known as ``getcha`` and ``smith_set``.
|
554
|
+
|
555
|
+
Related function includes :func:`pref_voting.c1_methods.gocha`
|
556
|
+
|
557
|
+
:Example:
|
558
|
+
|
559
|
+
|
560
|
+
.. plot:: margin_graphs_examples/mg_ex_top_cycle_gocha.py
|
561
|
+
:context: reset
|
562
|
+
:include-source: True
|
563
|
+
|
564
|
+
|
565
|
+
.. code-block::
|
566
|
+
|
567
|
+
from pref_voting.c1_methods import top_cycle, getcha, smith_set
|
568
|
+
top_cycle.display(prof)
|
569
|
+
getcha.display(prof)
|
570
|
+
smith_set.display(prof)
|
571
|
+
|
572
|
+
|
573
|
+
.. exec_code::
|
574
|
+
:hide_code:
|
575
|
+
|
576
|
+
from pref_voting.profiles import Profile
|
577
|
+
from pref_voting.c1_methods import top_cycle, getcha, smith_set
|
578
|
+
|
579
|
+
prof = Profile([[1, 2, 0, 3], [1, 3, 0, 2], [3, 1, 0, 2], [0, 3, 1, 2]], [1, 1, 1, 1])
|
580
|
+
|
581
|
+
top_cycle.display(prof)
|
582
|
+
getcha.display(prof)
|
583
|
+
smith_set.display(prof)
|
584
|
+
|
585
|
+
|
586
|
+
"""
|
587
|
+
wmg = get_weak_mg(edata, curr_cands = curr_cands)
|
588
|
+
scc = list(nx.strongly_connected_components(wmg))
|
589
|
+
min_indegree = min([max([wmg.in_degree(n) for n in comp]) for comp in scc])
|
590
|
+
smith = [comp for comp in scc if max([wmg.in_degree(n) for n in comp]) == min_indegree][0]
|
591
|
+
return sorted(list(smith))
|
592
|
+
|
593
|
+
# Create some aliases for Top Cycle
|
594
|
+
top_cycle.set_name("GETCHA")
|
595
|
+
getcha = copy.deepcopy(top_cycle)
|
596
|
+
getcha.skip_registration = True
|
597
|
+
|
598
|
+
top_cycle.set_name("Smith Set")
|
599
|
+
smith_set = copy.deepcopy(top_cycle)
|
600
|
+
smith_set.skip_registration = True
|
601
|
+
|
602
|
+
# reset the name Top Cycle
|
603
|
+
top_cycle.set_name("Top Cycle")
|
604
|
+
|
605
|
+
def top_cycle_defeat(edata, curr_cands = None):
|
606
|
+
"""Return the defeat relation associated with the Top Cycle voting method.
|
607
|
+
|
608
|
+
Args:
|
609
|
+
edata (Profile, ProfileWithTies, MajorityGraph, MarginGraph): Any election data that has a `majority_prefers` method.
|
610
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
611
|
+
|
612
|
+
Returns:
|
613
|
+
A networkx object in which there is an edge from :math:`a` to :math:`b` when :math:`a` to :math:`b` according to Top Cycle.
|
614
|
+
|
615
|
+
.. seealso::
|
616
|
+
|
617
|
+
:func:`pref_voting.c1_methods.top_cycle`
|
618
|
+
|
619
|
+
:Example:
|
620
|
+
|
621
|
+
.. plot:: margin_graphs_examples/top_cycle_defeat.py
|
622
|
+
:context: reset
|
623
|
+
:include-source: True
|
624
|
+
|
625
|
+
"""
|
626
|
+
|
627
|
+
defeat = nx.DiGraph()
|
628
|
+
candidates = edata.candidates if curr_cands is None else curr_cands
|
629
|
+
smith_set = top_cycle(edata, curr_cands = candidates)
|
630
|
+
|
631
|
+
defeat.add_nodes_from(candidates)
|
632
|
+
defeat.add_edges_from([(a, b) for a in candidates for b in candidates if a != b and a in smith_set and b not in smith_set])
|
633
|
+
return defeat
|
634
|
+
|
635
|
+
@vm(name = "GOCHA",
|
636
|
+
input_types = [ElectionTypes.PROFILE, ElectionTypes.PROFILE_WITH_TIES, ElectionTypes.MAJORITY_GRAPH, ElectionTypes.MARGIN_GRAPH])
|
637
|
+
def gocha(edata, curr_cands = None):
|
638
|
+
"""The GOCHA set (also known as the Schwartz set) is the set of all candidates x such that if y can reach x in the transitive closer of the majority relation, then x can reach y in the transitive closer of the majority relation.
|
639
|
+
|
640
|
+
Args:
|
641
|
+
edata (Profile, ProfileWithTies, MajorityGraph, MarginGraph): Any election data that has a `majority_prefers` method.
|
642
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
643
|
+
|
644
|
+
Returns:
|
645
|
+
A sorted list of candidates
|
646
|
+
|
647
|
+
.. seealso::
|
648
|
+
|
649
|
+
Also known as ``schwartz_set``.
|
650
|
+
|
651
|
+
Related function includes :func:`pref_voting.c1_methods.top_cycle`
|
652
|
+
|
653
|
+
:Example:
|
654
|
+
|
655
|
+
.. plot:: margin_graphs_examples/mg_ex_top_cycle_gocha.py
|
656
|
+
:context: reset
|
657
|
+
:include-source: True
|
658
|
+
|
659
|
+
.. code-block::
|
660
|
+
|
661
|
+
from pref_voting.c1_methods import top_cycle, gocha, schwartz_set
|
662
|
+
|
663
|
+
gocha.display(prof)
|
664
|
+
schwartz_set.display(prof)
|
665
|
+
|
666
|
+
.. exec_code::
|
667
|
+
:hide_code:
|
668
|
+
|
669
|
+
from pref_voting.profiles import Profile
|
670
|
+
from pref_voting.c1_methods import gocha, schwartz_set
|
671
|
+
|
672
|
+
prof = Profile([[1, 2, 0, 3], [1, 3, 0, 2], [3, 1, 0, 2], [0, 3, 1, 2]], [1, 1, 1, 1])
|
673
|
+
|
674
|
+
gocha.display(prof)
|
675
|
+
schwartz_set.display(prof)
|
676
|
+
|
677
|
+
"""
|
678
|
+
|
679
|
+
mg = get_mg(edata, curr_cands = curr_cands)
|
680
|
+
transitive_closure = nx.algorithms.dag.transitive_closure(mg)
|
681
|
+
schwartz = set()
|
682
|
+
for ssc in nx.strongly_connected_components(transitive_closure):
|
683
|
+
if not any([transitive_closure.has_edge(c2,c1)
|
684
|
+
for c1 in ssc for c2 in transitive_closure.nodes if c2 not in ssc]):
|
685
|
+
schwartz = schwartz.union(ssc)
|
686
|
+
return sorted(list(schwartz))
|
687
|
+
|
688
|
+
# Create some aliases for GOCHA
|
689
|
+
gocha.set_name("Schwartz Set")
|
690
|
+
schwartz_set = copy.deepcopy(gocha)
|
691
|
+
schwartz_set.skip_registration = True
|
692
|
+
|
693
|
+
# reset the name GETCHA
|
694
|
+
gocha.set_name("GOCHA")
|
695
|
+
|
696
|
+
|
697
|
+
## Banks
|
698
|
+
#
|
699
|
+
|
700
|
+
def seqs(iterable):
|
701
|
+
s = list(iterable)
|
702
|
+
return chain.from_iterable(permutations(s, r) for r in range(len(s)+1))
|
703
|
+
|
704
|
+
def is_transitive(G, p):
|
705
|
+
for c1_idx, c1 in enumerate(p[:-1]):
|
706
|
+
for c2 in p[c1_idx+1::]:
|
707
|
+
if not G.has_edge(c1,c2):
|
708
|
+
return False
|
709
|
+
return True
|
710
|
+
|
711
|
+
def is_subsequence(x, y):
|
712
|
+
it = iter(y)
|
713
|
+
return all(any(c == ch for c in it) for ch in x)
|
714
|
+
|
715
|
+
@vm(name = "Banks",
|
716
|
+
input_types = [ElectionTypes.PROFILE, ElectionTypes.PROFILE_WITH_TIES, ElectionTypes.MAJORITY_GRAPH, ElectionTypes.MARGIN_GRAPH])
|
717
|
+
def banks(edata, curr_cands = None):
|
718
|
+
""" Say that a *chain* in majority graph is a subset of candidates that is linearly ordered by the majority relation. Then a candidate :math:`a` if :math:`a` is the maximum element with respect to the majority relation of some maximal chain in the majority graph.
|
719
|
+
|
720
|
+
Args:
|
721
|
+
edata (Profile, ProfileWithTies, MarginGraph): Any election data that has a `margin` method.
|
722
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
723
|
+
|
724
|
+
Returns:
|
725
|
+
A sorted list of candidates
|
726
|
+
|
727
|
+
|
728
|
+
:Example:
|
729
|
+
|
730
|
+
.. plot:: margin_graphs_examples/mg_ex_banks.py
|
731
|
+
:context: reset
|
732
|
+
:include-source: True
|
733
|
+
|
734
|
+
|
735
|
+
.. code-block::
|
736
|
+
|
737
|
+
from pref_voting.c1_methods import banks
|
738
|
+
|
739
|
+
banks.display(prof)
|
740
|
+
|
741
|
+
|
742
|
+
.. exec_code::
|
743
|
+
:hide_code:
|
744
|
+
|
745
|
+
from pref_voting.weighted_majority_graphs import MarginGraph
|
746
|
+
from pref_voting.c1_methods import banks
|
747
|
+
|
748
|
+
mg = MarginGraph([0, 1, 2, 3], [(0, 2, 2), (0, 3, 6), (1, 0, 8), (2, 3, 4), (2, 1, 10), (3, 1, 12)])
|
749
|
+
|
750
|
+
banks.display(mg)
|
751
|
+
|
752
|
+
"""
|
753
|
+
|
754
|
+
mg = get_mg(edata, curr_cands = curr_cands)
|
755
|
+
trans_paths = list()
|
756
|
+
for s in seqs(mg.nodes):
|
757
|
+
if nx.algorithms.simple_paths.is_simple_path(mg, s):
|
758
|
+
if is_transitive(mg, s):
|
759
|
+
trans_paths.append(s)
|
760
|
+
|
761
|
+
maximal_paths = list()
|
762
|
+
#print("max paths")
|
763
|
+
for s in trans_paths:
|
764
|
+
is_max = True
|
765
|
+
for other_s in trans_paths:
|
766
|
+
if s != other_s:
|
767
|
+
if is_subsequence(s, other_s):
|
768
|
+
is_max = False
|
769
|
+
break
|
770
|
+
if is_max:
|
771
|
+
maximal_paths.append(s)
|
772
|
+
|
773
|
+
return sorted(list(set([p[0] for p in maximal_paths])))
|
774
|
+
|
775
|
+
def banks_with_explanation(edata, curr_cands = None):
|
776
|
+
"""Return the Banks winners and the list of maximal chains in the majority graph.
|
777
|
+
|
778
|
+
Args:
|
779
|
+
edata (Profile, ProfileWithTies, MarginGraph): Any election data that has a `margin` method.
|
780
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
781
|
+
|
782
|
+
Returns:
|
783
|
+
A sorted list of candidates
|
784
|
+
|
785
|
+
A list of lists of candidates each representing a maximal chain in the majority graph
|
786
|
+
|
787
|
+
:Example:
|
788
|
+
|
789
|
+
.. plot:: margin_graphs_examples/mg_ex_banks.py
|
790
|
+
:context: reset
|
791
|
+
:include-source: True
|
792
|
+
|
793
|
+
|
794
|
+
.. code-block::
|
795
|
+
|
796
|
+
from pref_voting.c1_methods import banks_with_explanation
|
797
|
+
|
798
|
+
bws, maximal_chains = banks_with_explanation(mg)
|
799
|
+
|
800
|
+
print(f"Winning set: {bws}")
|
801
|
+
for c in maximal_chains:
|
802
|
+
print(f"Maximal chain: {c}")
|
803
|
+
|
804
|
+
|
805
|
+
.. exec_code::
|
806
|
+
:hide_code:
|
807
|
+
|
808
|
+
from pref_voting.weighted_majority_graphs import MarginGraph
|
809
|
+
from pref_voting.c1_methods import banks_with_explanation
|
810
|
+
|
811
|
+
mg = MarginGraph([0, 1, 2, 3], [(0, 2, 2), (0, 3, 6), (1, 0, 8), (2, 3, 4), (2, 1, 10), (3, 1, 12)])
|
812
|
+
|
813
|
+
bws, maximal_chains = banks_with_explanation(mg)
|
814
|
+
|
815
|
+
print(f"Winning set: {bws}")
|
816
|
+
for c in maximal_chains:
|
817
|
+
print(f"Maximal chain: {c}")
|
818
|
+
|
819
|
+
"""
|
820
|
+
|
821
|
+
mg = get_mg(edata, curr_cands = curr_cands)
|
822
|
+
trans_paths = list()
|
823
|
+
for s in seqs(mg.nodes):
|
824
|
+
if nx.algorithms.simple_paths.is_simple_path(mg, s):
|
825
|
+
if is_transitive(mg, s):
|
826
|
+
trans_paths.append(s)
|
827
|
+
|
828
|
+
maximal_paths = list()
|
829
|
+
#print("max paths")
|
830
|
+
for s in trans_paths:
|
831
|
+
is_max = True
|
832
|
+
for other_s in trans_paths:
|
833
|
+
if s != other_s:
|
834
|
+
if is_subsequence(s, other_s):
|
835
|
+
is_max = False
|
836
|
+
break
|
837
|
+
if is_max:
|
838
|
+
maximal_paths.append(s)
|
839
|
+
|
840
|
+
return sorted(list(set([p[0] for p in maximal_paths]))), maximal_paths
|
841
|
+
|
842
|
+
|
843
|
+
def lin_order_to_rel(lin_order):
|
844
|
+
"""Convert a linear order (a list of items) into a set of ordered pairs"""
|
845
|
+
els = sorted(lin_order)
|
846
|
+
rel = []
|
847
|
+
for a,b in combinations(els, 2):
|
848
|
+
if lin_order.index(a) < lin_order.index(b):
|
849
|
+
rel.append((a,b))
|
850
|
+
elif lin_order.index(b) < lin_order.index(a):
|
851
|
+
rel.append((b,a))
|
852
|
+
return rel
|
853
|
+
|
854
|
+
|
855
|
+
def slater_rankings(edata, curr_cands = None):
|
856
|
+
"""
|
857
|
+
A Slater ranking is a linear order :math:`R` of the candidates that minimizes the number of edges in the majority graph we have to turn around before we obtain :math:`R`.
|
858
|
+
|
859
|
+
Args:
|
860
|
+
edata (Profile, ProfileWithTies, MarginGraph): Any election data that has a `margin` method.
|
861
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
862
|
+
|
863
|
+
Returns:
|
864
|
+
rankings: A list of Slater rankings.
|
865
|
+
|
866
|
+
dist: The minimum distance of the Slater rankings.
|
867
|
+
|
868
|
+
:Example:
|
869
|
+
|
870
|
+
.. exec_code::
|
871
|
+
|
872
|
+
from pref_voting.weighted_majority_graphs import MarginGraph
|
873
|
+
from pref_voting.c1_methods import slater_rankings
|
874
|
+
|
875
|
+
mg = MarginGraph([0, 1, 2, 3], [(0, 2, 2), (0, 3, 6), (1, 0, 8), (2, 3, 4), (2, 1, 10), (3, 1, 12)])
|
876
|
+
|
877
|
+
srs, d = slater_rankings(mg)
|
878
|
+
print(f"minimum distance: {d}")
|
879
|
+
for sr in srs:
|
880
|
+
print(f"ranking: {sr}")
|
881
|
+
"""
|
882
|
+
candidates = edata.candidates if curr_cands is None else curr_cands
|
883
|
+
min_dist = np.inf
|
884
|
+
|
885
|
+
rankings = list()
|
886
|
+
for lin_order in permutations(candidates):
|
887
|
+
lo_rel = lin_order_to_rel(lin_order)
|
888
|
+
|
889
|
+
dist = distance_to_margin_graph(edata, lo_rel, exp = 0, curr_cands = curr_cands)
|
890
|
+
if dist < min_dist:
|
891
|
+
min_dist = dist
|
892
|
+
rankings = [lin_order]
|
893
|
+
elif dist == min_dist:
|
894
|
+
rankings.append(lin_order)
|
895
|
+
return rankings, min_dist
|
896
|
+
|
897
|
+
@vm(name = "Slater",
|
898
|
+
input_types = [ElectionTypes.PROFILE, ElectionTypes.PROFILE_WITH_TIES, ElectionTypes.MAJORITY_GRAPH, ElectionTypes.MARGIN_GRAPH])
|
899
|
+
def slater(edata, curr_cands = None):
|
900
|
+
"""A Slater ranking is a linear order :math:`R` of the candidates that minimizes the number of edges in the majority graph we have to turn around before we obtain :math:`R`. A candidate is a Slater winner if the candidate is the top element of some Slater ranking.
|
901
|
+
|
902
|
+
Args:
|
903
|
+
edata (Profile, ProfileWithTies, MarginGraph): Any election data that has a `margin` method.
|
904
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
905
|
+
|
906
|
+
Returns:
|
907
|
+
A sorted list of candidates
|
908
|
+
|
909
|
+
|
910
|
+
:Example:
|
911
|
+
|
912
|
+
.. plot:: margin_graphs_examples/mg_ex_slater.py
|
913
|
+
:context: reset
|
914
|
+
:include-source: True
|
915
|
+
|
916
|
+
|
917
|
+
.. code-block::
|
918
|
+
|
919
|
+
from pref_voting.c1_methods import slater
|
920
|
+
|
921
|
+
slater.display(prof)
|
922
|
+
|
923
|
+
|
924
|
+
.. exec_code::
|
925
|
+
:hide_code:
|
926
|
+
|
927
|
+
from pref_voting.weighted_majority_graphs import MarginGraph
|
928
|
+
from pref_voting.c1_methods import slater
|
929
|
+
|
930
|
+
mg = MarginGraph([0, 1, 2, 3], [(0, 2, 2), (0, 3, 6), (1, 0, 8), (2, 3, 4), (2, 1, 10), (3, 1, 12)])
|
931
|
+
|
932
|
+
slater.display(mg)
|
933
|
+
|
934
|
+
"""
|
935
|
+
rankings, dist = slater_rankings(edata, curr_cands = curr_cands)
|
936
|
+
|
937
|
+
return sorted(list(set([r[0] for r in rankings])))
|
938
|
+
|
939
|
+
@vm(name = "Bipartisan Set",
|
940
|
+
input_types = [ElectionTypes.PROFILE, ElectionTypes.PROFILE_WITH_TIES, ElectionTypes.MAJORITY_GRAPH, ElectionTypes.MARGIN_GRAPH])
|
941
|
+
def bipartisan(edata, curr_cands = None, threshold = 0.0000001):
|
942
|
+
"""The Bipartisan Set is the support of the (chosen) C1 maximal lottery.
|
943
|
+
|
944
|
+
Args:
|
945
|
+
edata (Profile, ProfileWithTies, MajorityGraph, MarginGraph): Any election data that has a `margin_matrix` attribute.
|
946
|
+
curr_cands (List[int], optional): If set, then find the winners for the profile restricted to the candidates in ``curr_cands``
|
947
|
+
|
948
|
+
Returns:
|
949
|
+
A sorted list of candidates.
|
950
|
+
"""
|
951
|
+
|
952
|
+
ml = c1_maximal_lottery(edata, curr_cands=curr_cands)
|
953
|
+
return sorted([c for c in ml.keys() if ml[c] > threshold])
|
954
|
+
|
955
|
+
c1_swf = [
|
956
|
+
copeland_ranking
|
957
|
+
]
|
958
|
+
|
959
|
+
defeat_methods = [
|
960
|
+
top_cycle_defeat,
|
961
|
+
uc_gill_defeat,
|
962
|
+
uc_fish_defeat
|
963
|
+
]
|