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.
Files changed (92) hide show
  1. pref_voting/__init__.py +1 -0
  2. pref_voting/analysis.py +496 -0
  3. pref_voting/axiom.py +38 -0
  4. pref_voting/axiom_helpers.py +129 -0
  5. pref_voting/axioms.py +10 -0
  6. pref_voting/c1_methods.py +963 -0
  7. pref_voting/combined_methods.py +514 -0
  8. pref_voting/create_methods.py +128 -0
  9. pref_voting/data/examples/condorcet_winner/minimal_Anti-Plurality.soc +16 -0
  10. pref_voting/data/examples/condorcet_winner/minimal_Borda.soc +17 -0
  11. pref_voting/data/examples/condorcet_winner/minimal_Bracket_Voting.soc +20 -0
  12. pref_voting/data/examples/condorcet_winner/minimal_Bucklin.soc +19 -0
  13. pref_voting/data/examples/condorcet_winner/minimal_Coombs.soc +20 -0
  14. pref_voting/data/examples/condorcet_winner/minimal_Coombs_PUT.soc +20 -0
  15. pref_voting/data/examples/condorcet_winner/minimal_Coombs_TB.soc +20 -0
  16. pref_voting/data/examples/condorcet_winner/minimal_Dowdall.soc +19 -0
  17. pref_voting/data/examples/condorcet_winner/minimal_Instant_Runoff.soc +18 -0
  18. pref_voting/data/examples/condorcet_winner/minimal_Instant_Runoff_PUT.soc +18 -0
  19. pref_voting/data/examples/condorcet_winner/minimal_Instant_Runoff_TB.soc +18 -0
  20. pref_voting/data/examples/condorcet_winner/minimal_Iterated_Removal_Condorcet_Loser.soc +17 -0
  21. pref_voting/data/examples/condorcet_winner/minimal_Pareto.soc +17 -0
  22. pref_voting/data/examples/condorcet_winner/minimal_Plurality.soc +18 -0
  23. pref_voting/data/examples/condorcet_winner/minimal_PluralityWRunoff_PUT.soc +18 -0
  24. pref_voting/data/examples/condorcet_winner/minimal_Positive-Negative_Voting.soc +17 -0
  25. pref_voting/data/examples/condorcet_winner/minimal_Simplified_Bucklin.soc +18 -0
  26. pref_voting/data/examples/condorcet_winner/minimal_Superior_Voting.soc +19 -0
  27. pref_voting/data/examples/condorcet_winner/minimal_Weighted_Bucklin.soc +19 -0
  28. pref_voting/data/examples/condorcet_winner/minimal_resolute_Anti-Plurality.soc +17 -0
  29. pref_voting/data/examples/condorcet_winner/minimal_resolute_Borda.soc +17 -0
  30. pref_voting/data/examples/condorcet_winner/minimal_resolute_Bracket_Voting.soc +20 -0
  31. pref_voting/data/examples/condorcet_winner/minimal_resolute_Bucklin.soc +19 -0
  32. pref_voting/data/examples/condorcet_winner/minimal_resolute_Coombs.soc +21 -0
  33. pref_voting/data/examples/condorcet_winner/minimal_resolute_Coombs_PUT.soc +21 -0
  34. pref_voting/data/examples/condorcet_winner/minimal_resolute_Coombs_TB.soc +20 -0
  35. pref_voting/data/examples/condorcet_winner/minimal_resolute_Dowdall.soc +18 -0
  36. pref_voting/data/examples/condorcet_winner/minimal_resolute_Instant_Runoff.soc +18 -0
  37. pref_voting/data/examples/condorcet_winner/minimal_resolute_Instant_Runoff_PUT.soc +18 -0
  38. pref_voting/data/examples/condorcet_winner/minimal_resolute_Instant_Runoff_TB.soc +18 -0
  39. pref_voting/data/examples/condorcet_winner/minimal_resolute_Plurality.soc +18 -0
  40. pref_voting/data/examples/condorcet_winner/minimal_resolute_PluralityWRunoff_PUT.soc +18 -0
  41. pref_voting/data/examples/condorcet_winner/minimal_resolute_Positive-Negative_Voting.soc +17 -0
  42. pref_voting/data/examples/condorcet_winner/minimal_resolute_Simplified_Bucklin.soc +20 -0
  43. pref_voting/data/examples/condorcet_winner/minimal_resolute_Weighted_Bucklin.soc +19 -0
  44. pref_voting/data/voting_methods_properties.json +414 -0
  45. pref_voting/data/voting_methods_properties.json.lock +0 -0
  46. pref_voting/dominance_axioms.py +387 -0
  47. pref_voting/generate_profiles.py +801 -0
  48. pref_voting/generate_spatial_profiles.py +198 -0
  49. pref_voting/generate_utility_profiles.py +160 -0
  50. pref_voting/generate_weighted_majority_graphs.py +506 -0
  51. pref_voting/grade_methods.py +184 -0
  52. pref_voting/grade_profiles.py +357 -0
  53. pref_voting/helper.py +370 -0
  54. pref_voting/invariance_axioms.py +671 -0
  55. pref_voting/io/__init__.py +0 -0
  56. pref_voting/io/readers.py +432 -0
  57. pref_voting/io/writers.py +256 -0
  58. pref_voting/iterative_methods.py +2425 -0
  59. pref_voting/maj_graph_ex1.png +0 -0
  60. pref_voting/mappings.py +577 -0
  61. pref_voting/margin_based_methods.py +2345 -0
  62. pref_voting/monotonicity_axioms.py +872 -0
  63. pref_voting/num_evaluation_method.py +77 -0
  64. pref_voting/other_axioms.py +161 -0
  65. pref_voting/other_methods.py +939 -0
  66. pref_voting/pairwise_profiles.py +547 -0
  67. pref_voting/prob_voting_method.py +105 -0
  68. pref_voting/probabilistic_methods.py +287 -0
  69. pref_voting/profiles.py +856 -0
  70. pref_voting/profiles_with_ties.py +1069 -0
  71. pref_voting/rankings.py +466 -0
  72. pref_voting/scoring_methods.py +481 -0
  73. pref_voting/social_welfare_function.py +59 -0
  74. pref_voting/social_welfare_functions.py +7 -0
  75. pref_voting/spatial_profiles.py +448 -0
  76. pref_voting/stochastic_methods.py +99 -0
  77. pref_voting/strategic_axioms.py +1394 -0
  78. pref_voting/swf_axioms.py +173 -0
  79. pref_voting/utility_functions.py +102 -0
  80. pref_voting/utility_methods.py +178 -0
  81. pref_voting/utility_profiles.py +333 -0
  82. pref_voting/variable_candidate_axioms.py +640 -0
  83. pref_voting/variable_voter_axioms.py +3747 -0
  84. pref_voting/voting_method.py +355 -0
  85. pref_voting/voting_method_properties.py +92 -0
  86. pref_voting/voting_methods.py +8 -0
  87. pref_voting/voting_methods_registry.py +136 -0
  88. pref_voting/weighted_majority_graphs.py +1539 -0
  89. pref_voting-1.16.31.dist-info/METADATA +208 -0
  90. pref_voting-1.16.31.dist-info/RECORD +92 -0
  91. pref_voting-1.16.31.dist-info/WHEEL +4 -0
  92. pref_voting-1.16.31.dist-info/licenses/LICENSE.txt +21 -0
@@ -0,0 +1,640 @@
1
+ """
2
+ File: variable_candidate_axioms.py
3
+ Author: Wes Holliday (wesholliday@berkeley.edu) and Eric Pacuit (epacuit@umd.edu)
4
+ Date: July 7, 2024
5
+ Updated: August 20, 2025
6
+
7
+ Variable candidate axioms
8
+ """
9
+
10
+ from pref_voting.axiom import Axiom
11
+ from pref_voting.axiom_helpers import *
12
+ from pref_voting.c1_methods import top_cycle
13
+ import numpy as np
14
+ from itertools import combinations
15
+
16
+ def has_stability_for_winners_violation(edata, vm, verbose=False, strong_stability=False):
17
+ """
18
+ Returns True if there is some candidate A who wins without another candidate B in the election, A is majority preferred to B, but A loses when B is included in the election.
19
+
20
+ If strong_stability is True, then A can be weakly majority preferred to B.
21
+
22
+ .. note::
23
+ This axiom is studied in https://doi.org/10.1007/s11127-023-01042-3 and is closely related to an axiom mentioned in https://doi.org/10.1016/0022-0531(83)90024-8.
24
+
25
+ Args:
26
+ edata (Profile, ProfileWithTies, MarginGraph): Any election data that has a `margin` method.
27
+ vm (VotingMethod): A voting method to test.
28
+ verbose (bool, default=False): If a violation is found, display the violation.
29
+
30
+ Returns:
31
+ bool: True if there is a violation, False otherwise.
32
+ """
33
+ winners = vm(edata)
34
+ losers = [c for c in edata.candidates if c not in winners]
35
+
36
+ for a in losers:
37
+ for b in edata.candidates:
38
+ if edata.margin(a,b) > 0:
39
+ winners_in_reduced_prof = vm(edata, curr_cands = [x for x in edata.candidates if x != b])
40
+ if a in winners_in_reduced_prof:
41
+ if verbose:
42
+ print(f"Stability for Winners violation for {vm.name}.")
43
+ print(f"{a} wins without {b} in the election and is majority preferred to {b} but loses when {b} is included:")
44
+ edata.display()
45
+ print(edata.description())
46
+ if isinstance(edata, Profile):
47
+ edata.display_margin_graph()
48
+ print(f"Winners in full election: {winners}")
49
+ print(f"Winners in election without {b}: {winners_in_reduced_prof}")
50
+ return True
51
+
52
+ if strong_stability and edata.margin(a,b) == 0:
53
+ winners_in_reduced_prof = vm(edata, curr_cands = [x for x in edata.candidates if x != b])
54
+ if a in winners_in_reduced_prof:
55
+ if verbose:
56
+ print(f"Strong Stability for Winners violation for {vm.name}.")
57
+ print(f"{a} wins without {b} in the election and is weakly majority preferred to {b} but loses when {b} is included:")
58
+ edata.display()
59
+ print(edata.description())
60
+ if isinstance(edata, Profile):
61
+ edata.display_margin_graph()
62
+ print(f"Winners in full election: {winners}")
63
+ print(f"Winners in election without {b}: {winners_in_reduced_prof}")
64
+ return True
65
+ return False
66
+
67
+ def find_all_stability_for_winners_violations(edata, vm, verbose=False, strong_stability = False):
68
+ """
69
+ Returns all violations of Stability for Winners (some candidate A wins without another candidate B in the election, A is majority preferred to B, but A loses when B is included in the election) for the given election data and voting method.
70
+
71
+ If strong_stability is True, then A can be weakly majority preferred to B.
72
+
73
+ .. note:: This axiom is studied in https://doi.org/10.1007/s11127-023-01042-3 and is closely related to an axiom mentioned in https://doi.org/10.1016/0022-0531(83)90024-8.
74
+
75
+ Args:
76
+ edata (Profile, ProfileWithTies, MarginGraph): Any election data that has a `margin` method.
77
+ vm (VotingMethod): A voting method to test.
78
+ verbose (bool, default=False): If a violation is found, display the violation.
79
+
80
+ Returns:
81
+ List of pairs (cand1,cand2) where cand1 wins without cand2 in the election, cand1 is majority preferred to cand2, but cand1 loses when cand2 is included in the election.
82
+
83
+ """
84
+ winners = vm(edata)
85
+ losers = [c for c in edata.candidates if c not in winners]
86
+
87
+ violations = list()
88
+
89
+ for a in losers:
90
+ for b in edata.candidates:
91
+ if edata.margin(a,b) > 0:
92
+ winners_in_reduced_prof = vm(edata, curr_cands = [x for x in edata.candidates if x != b])
93
+ if a in winners_in_reduced_prof:
94
+ violations.append((a,b))
95
+ if verbose:
96
+ print(f"Stability for Winners violation for {vm.name}.")
97
+ print(f"{a} wins without {b} in the election and is majority preferred to {b} but loses when {b} is included:")
98
+ edata.display()
99
+ print(edata.description())
100
+ if isinstance(edata, Profile):
101
+ edata.display_margin_graph()
102
+ print(f"Winners in full election: {winners}")
103
+ print(f"Winners in election without {b}: {winners_in_reduced_prof}")
104
+
105
+ if strong_stability and edata.margin(a,b) == 0:
106
+ winners_in_reduced_prof = vm(edata, curr_cands = [x for x in edata.candidates if x != b])
107
+ if a in winners_in_reduced_prof:
108
+ violations.append((a,b))
109
+ if verbose:
110
+ print(f"Strong Stability for Winners violation for {vm.name}.")
111
+ print(f"{a} wins without {b} in the election and is weakly majority preferred to {b} but loses when {b} is included:")
112
+ edata.display()
113
+ print(edata.description())
114
+ if isinstance(edata, Profile):
115
+ edata.display_margin_graph()
116
+ print(f"Winners in full election: {winners}")
117
+ print(f"Winners in election without {b}: {winners_in_reduced_prof}")
118
+
119
+ return violations
120
+
121
+ stability_for_winners = Axiom(
122
+ "Stability for Winners",
123
+ has_violation = has_stability_for_winners_violation,
124
+ find_all_violations = find_all_stability_for_winners_violations,
125
+ )
126
+
127
+ def has_immunity_to_spoilers_violation(edata, vm, verbose=False, strong_immunity = False):
128
+ """
129
+ Returns True if there is some candidate A who wins without another candidate B in the election, A is majority preferred to B, but both A and B lose when B is included in the election.
130
+
131
+ If strong_immunity is True, then A can be weakly majority preferred to B.
132
+
133
+ .. note::
134
+ This axiom was introduced in https://doi.org/10.1007/s11127-023-01042-3.
135
+
136
+ Args:
137
+ edata (Profile, ProfileWithTies, MarginGraph): Any election data that has a `margin` method.
138
+ vm (VotingMethod): A voting method to test.
139
+ verbose (bool, default=False): If a violation is found, display the violation.
140
+
141
+ Returns:
142
+ bool: True if there is a violation, False otherwise.
143
+ """
144
+ winners = vm(edata)
145
+ losers = [c for c in edata.candidates if c not in winners]
146
+
147
+ for a in losers:
148
+ for b in losers:
149
+ if edata.margin(a,b) > 0:
150
+ winners_in_reduced_prof = vm(edata, curr_cands = [x for x in edata.candidates if x != b])
151
+ if a in winners_in_reduced_prof:
152
+ if verbose:
153
+ print(f"Immunity to Spoilers violation for {vm.name}.")
154
+ print(f"{a} wins without {b} in the election and is majority preferred to {b} but both lose when {b} is included:")
155
+ edata.display()
156
+ print(edata.description())
157
+ if isinstance(edata, Profile):
158
+ edata.display_margin_graph()
159
+ print(f"Winners in full election: {winners}")
160
+ print(f"Winners in election without {b}: {winners_in_reduced_prof}")
161
+ return True
162
+
163
+ if strong_immunity and edata.margin(a,b) == 0:
164
+ winners_in_reduced_prof = vm(edata, curr_cands = [x for x in edata.candidates if x != b])
165
+ if a in winners_in_reduced_prof:
166
+ if verbose:
167
+ print(f"Strong Immunity to Spoilers violation for {vm.name}.")
168
+ print(f"{a} wins without {b} in the election and is weakly majority preferred to {b} but both lose when {b} is included:")
169
+ edata.display()
170
+ print(edata.description())
171
+ if isinstance(edata, Profile):
172
+ edata.display_margin_graph()
173
+ print(f"Winners in full election: {winners}")
174
+ print(f"Winners in election without {b}: {winners_in_reduced_prof}")
175
+ return True
176
+ return False
177
+
178
+ def find_all_immunity_to_spoilers_violations(edata, vm, verbose=False, strong_immunity = False):
179
+ """
180
+ Returns all violations of Immunity to Spoilers (some candidate A wins without another candidate B in the election, A is majority preferred to B, but both A and B lose when B is included in the election) for the given election data and voting method.
181
+
182
+ If strong_immunity is True, then A can be weakly majority preferred to B.
183
+
184
+ .. note::
185
+ This axiom was introduced in https://doi.org/10.1007/s11127-023-01042-3.
186
+
187
+ Args:
188
+ edata (Profile, ProfileWithTies, MarginGraph): Any election data that has a `margin` method.
189
+ vm (VotingMethod): A voting method to test.
190
+ verbose (bool, default=False): If a violation is found, display the violation.
191
+
192
+ Returns:
193
+ List of pairs (cand1,cand2) where cand1 wins without cand2 in the election, cand1 is majority preferred to cand2, but both cand1 and cand2 lose when cand2 is included in the election.
194
+
195
+ """
196
+
197
+ winners = vm(edata)
198
+ losers = [c for c in edata.candidates if c not in winners]
199
+
200
+ violations = list()
201
+
202
+ for a in losers:
203
+ for b in losers:
204
+ if edata.margin(a,b) > 0:
205
+ winners_in_reduced_prof = vm(edata, curr_cands = [x for x in edata.candidates if x != b])
206
+ if a in winners_in_reduced_prof:
207
+ violations.append((a,b))
208
+ if verbose:
209
+ print(f"Immunity to Spoilers violation for {vm.name}.")
210
+ print(f"{a} wins without {b} in the election and is majority preferred to {b} but both lose when {b} is included:")
211
+ edata.display()
212
+ print(edata.description())
213
+ if isinstance(edata, Profile):
214
+ edata.display_margin_graph()
215
+ print(f"Winners in full election: {winners}")
216
+ print(f"Winners in election without {b}: {winners_in_reduced_prof}")
217
+
218
+ if strong_immunity and edata.margin(a,b) == 0:
219
+ winners_in_reduced_prof = vm(edata, curr_cands = [x for x in edata.candidates if x != b])
220
+ if a in winners_in_reduced_prof:
221
+ violations.append((a,b))
222
+ if verbose:
223
+ print(f"Strong Immunity to Spoilers violation for {vm.name}.")
224
+ print(f"{a} wins without {b} in the election and is weakly majority preferred to {b} but both lose when {b} is included:")
225
+ edata.display()
226
+ print(edata.description())
227
+ if isinstance(edata, Profile):
228
+ edata.display_margin_graph()
229
+ print(f"Winners in full election: {winners}")
230
+ print(f"Winners in election without {b}: {winners_in_reduced_prof}")
231
+
232
+ return violations
233
+
234
+ immunity_to_spoilers = Axiom(
235
+ "Immunity to Spoilers",
236
+ has_violation = has_immunity_to_spoilers_violation,
237
+ find_all_violations = find_all_immunity_to_spoilers_violations,
238
+ )
239
+
240
+ def has_ISDA_violation(edata, vm, verbose=False):
241
+ """
242
+ Independence of Smith-Dominated Alternatives: returns True if there is a candidate A outside of the Smith set such that removing A changes the set of winners according to the voting method vm.
243
+
244
+ Args:
245
+ edata (Profile, ProfileWithTies, MarginGraph): Any election data that has a `margin` method.
246
+ vm (VotingMethod): A voting method to test.
247
+ verbose (bool, default=False): If a violation is found, display the violation.
248
+
249
+ Returns:
250
+ bool: True if there is a violation, False otherwise.
251
+ """
252
+
253
+ winners = vm(edata)
254
+ smith_set = top_cycle(edata)
255
+ non_smith_set = [c for c in edata.candidates if c not in smith_set]
256
+
257
+ for a in non_smith_set:
258
+ winners_in_reduced_prof = vm(edata, curr_cands = [x for x in edata.candidates if x != a])
259
+ if winners != winners_in_reduced_prof:
260
+ if verbose:
261
+ print(f"ISDA violation for {vm.name}.")
262
+ print(f"{a} is outside of the Smith set and removing {a} changes the set of winners:")
263
+ edata.display()
264
+ print(edata.description())
265
+ if isinstance(edata, Profile):
266
+ edata.display_margin_graph()
267
+ print(f"Winners in full election: {winners}")
268
+ print(f"Winners in election without {a}: {winners_in_reduced_prof}")
269
+ return True
270
+ return False
271
+
272
+ def find_all_ISDA_violations(edata, vm, verbose=False):
273
+ """
274
+ Returns all violations of ISDA (some candidate A outside of the Smith set such that removing A changes the set of winners according to the voting method vm) for the given election data and voting method.
275
+
276
+ Args:
277
+ edata (Profile, ProfileWithTies, MarginGraph): Any election data that has a `margin` method.
278
+ vm (VotingMethod): A voting method to test.
279
+ verbose (bool, default=False): If a violation is found, display the violation.
280
+
281
+ Returns:
282
+ List of candidates A outside of the Smith set such that removing A changes the set of winners according to the voting method vm.
283
+ """
284
+
285
+ winners = vm(edata)
286
+ smith_set = top_cycle(edata)
287
+ non_smith_set = [c for c in edata.candidates if c not in smith_set]
288
+
289
+ violations = list()
290
+
291
+ for a in non_smith_set:
292
+ winners_in_reduced_prof = vm(edata, curr_cands = [x for x in edata.candidates if x != a])
293
+ if winners != winners_in_reduced_prof:
294
+ violations.append(a)
295
+ if verbose:
296
+ print(f"ISDA violation for {vm.name}.")
297
+ print(f"{a} is outside of the Smith set and removing {a} changes the set of winners:")
298
+ edata.display()
299
+ print(edata.description())
300
+ if isinstance(edata, Profile):
301
+ edata.display_margin_graph()
302
+ print(f"Winners in full election: {winners}")
303
+ print(f"Winners in election without {a}: {winners_in_reduced_prof}")
304
+ return violations
305
+
306
+ ISDA = Axiom(
307
+ "Independence of Smith-Dominated Alternatives",
308
+ has_violation = has_ISDA_violation,
309
+ find_all_violations = find_all_ISDA_violations,
310
+ )
311
+
312
+ def has_IPDA_violation(prof, vm, verbose=False, strong_Pareto = False):
313
+ """
314
+ Independence of Pareto-Dominated Alternatives: returns True if there is a candidate A who is Pareto-dominated by another candidate B such that removing A changes the set of winners according to the voting method vm.
315
+
316
+ If strong_Pareto is True, then a candidate A is dominated if there is a candidate B such that some voter prefers B to A and no voter prefers A to B.
317
+
318
+ Args:
319
+ prof (Profile, ProfileWithTies): the election data.
320
+ vm (VotingMethod): A voting method to test.
321
+ verbose (bool, default=False): If a violation is found, display the violation.
322
+
323
+ Returns:
324
+ bool: True if there is a violation, False otherwise.
325
+ """
326
+
327
+ winners = vm(prof)
328
+ pareto_dominated = list()
329
+
330
+ for a in prof.candidates:
331
+ for b in prof.candidates:
332
+ if (strong_Pareto == False and prof.support(b,a) == prof.num_voters) or (strong_Pareto == True and prof.support(b,a) > 0 and prof.support(a,b) == 0):
333
+ pareto_dominated.append(a)
334
+ break
335
+
336
+ for a in pareto_dominated:
337
+ winners_in_reduced_prof = vm(prof, curr_cands = [x for x in prof.candidates if x != a])
338
+ if winners != winners_in_reduced_prof:
339
+ if verbose:
340
+ print(f"IPDA violation for {vm.name}.")
341
+ print(f"{a} is Pareto-dominated by another candidate and removing {a} changes the set of winners:")
342
+ prof.display()
343
+ print(prof.description())
344
+ prof.display_margin_graph()
345
+ print(f"Winners in full election: {winners}")
346
+ print(f"Winners in election without {a}: {winners_in_reduced_prof}")
347
+ return True
348
+ return False
349
+
350
+ def find_all_IPDA_violations(prof, vm, verbose=False, strong_Pareto = False):
351
+ """
352
+ Returns all violations of IPDA (some candidate A who is Pareto-dominated by another candidate B such that removing A changes the set of winners according to the voting method vm) for the given election data and voting method.
353
+
354
+ If strong_Pareto is True, then a candidate A is dominated if there is a candidate B such that some voter prefers B to A and no voter prefers A to B.
355
+
356
+ Args:
357
+ prof (Profile, ProfileWithTies): the election data.
358
+ vm (VotingMethod): A voting method to test.
359
+ verbose (bool, default=False): If a violation is found, display the violation.
360
+
361
+ Returns:
362
+ List of candidates A who are Pareto-dominated by another candidate such that removing A changes the set of winners according to the voting method vm.
363
+ """
364
+
365
+ winners = vm(prof)
366
+ pareto_dominated = list()
367
+
368
+ for a in prof.candidates:
369
+ for b in prof.candidates:
370
+ if (strong_Pareto == False and prof.support(b,a) == prof.num_voters) or (strong_Pareto == True and prof.support(b,a) > 0 and prof.support(a,b) == 0):
371
+ pareto_dominated.append(a)
372
+ break
373
+
374
+ violations = list()
375
+
376
+ for a in pareto_dominated:
377
+ winners_in_reduced_prof = vm(prof, curr_cands = [x for x in prof.candidates if x != a])
378
+ if winners != winners_in_reduced_prof:
379
+ violations.append(a)
380
+ if verbose:
381
+ print(f"IPDA violation for {vm.name}.")
382
+ print(f"{a} is Pareto-dominated by another candidate and removing {a} changes the set of winners:")
383
+ prof.display()
384
+ print(prof.description())
385
+ prof.display_margin_graph()
386
+ print(f"Winners in full election: {winners}")
387
+ print(f"Winners in election without {a}: {winners_in_reduced_prof}")
388
+ return violations
389
+
390
+ IPDA = Axiom(
391
+ "Independence of Pareto-Dominated Alternatives",
392
+ has_violation = has_IPDA_violation,
393
+ find_all_violations = find_all_IPDA_violations,
394
+ )
395
+
396
+ def convex_sublists(list):
397
+ return [list[i:j] for i in range(len(list)) for j in range(i+1,len(list)+1)]
398
+
399
+ def convex_proper_sublists(list):
400
+ return [list[i:j] for i in range(len(list)) for j in range(i+1,len(list))]
401
+
402
+ def convex_nonsingleton_proper_sublists(list):
403
+ return [list[i:j] for i in range(len(list)) for j in range(i+2,len(list))]
404
+
405
+ def is_convex_set(S,L):
406
+ if len(S) == 1:
407
+ return True
408
+ for sublist in convex_sublists(L):
409
+ if set(S) == set(sublist):
410
+ return True
411
+ return False
412
+
413
+ def tideman_clone_sets(prof):
414
+ """Given a profile, returns all sets of clones according to Tideman's definition. A set C of candidates is a set of clones if no candidate outside of C appears in between two candidates in C in any ranking."""
415
+
416
+ rankings = prof.rankings
417
+ first_ranking = rankings[0]
418
+
419
+ _clone_sets = convex_nonsingleton_proper_sublists(first_ranking)
420
+ clone_sets = list(_clone_sets)
421
+
422
+ for clone_set in _clone_sets:
423
+ for r in rankings[1:]:
424
+ if not is_convex_set(set(clone_set),r):
425
+ clone_sets.remove(clone_set)
426
+ break
427
+
428
+ return clone_sets
429
+
430
+ def marginal_clone_sets(edata, epsilon=0):
431
+ """Given a profile or margin graph, returns all sets of "marginal clones": a set C of candidates is a set of marginal clones if for any c,d in C and x not in C, |margin(c,x) - margin(d,x)| <= epsilon."""
432
+
433
+ mc_sets = []
434
+
435
+ cands = edata.candidates
436
+
437
+ for subset in powerset(cands):
438
+
439
+ if len(subset) <=1 or len(subset) == len(cands):
440
+ continue
441
+
442
+ is_marginal_clone_set = True
443
+
444
+ for c, d in combinations(subset, 2):
445
+ are_clones = True
446
+
447
+ for x in cands:
448
+ if not x in subset:
449
+ if abs(edata.margin(c,x) - edata.margin(d,x)) > epsilon:
450
+ are_clones = False
451
+ break
452
+
453
+ if not are_clones:
454
+ is_marginal_clone_set = False
455
+ break
456
+
457
+ if is_marginal_clone_set:
458
+ mc_sets.append(subset)
459
+
460
+ return mc_sets
461
+
462
+ def has_independence_of_clones_violation(prof, vm, clone_def = "Tideman", epsilon = 0, conditions_to_check = "all", verbose = False):
463
+ """Independence of Clones: returns True if there is a set C of clones and a clone c in C such that removing c either (i) changes which non-clones (candidates not in C) win or (ii) changes whether any clone in C wins. We call (i) a violation of "non-clone choice is independent of clones" (NCIC) and call (ii) a violation of "clone choice is independent of clones" (CIC).
464
+
465
+ Args:
466
+ prof (Profile): the election data.
467
+ vm (VotingMethod): A voting method to test.
468
+ clone_def (str, default="Tideman"): The definition of clones. Options are "Tideman" and "Marginal".
469
+ epsilon (float, default=0): If clone_def is "Marginal", then for C to be a marginal clone set, it must but that for any c,c' in C and x not in C, |margin(c,x) - margin(c',x)| <= epsilon.
470
+ conditions_to_check (str, default="all"): The conditions to check. If "all", then both NCIC and CIC are checked. If "NCIC", then only NCIC is checked. If "CIC", then only CIC is checked.
471
+ verbose (bool, default=False): If a violation is found, display the violation.
472
+
473
+ """
474
+
475
+ if clone_def == "Tideman":
476
+ clone_sets = tideman_clone_sets(prof)
477
+
478
+ if clone_def == "Marginal":
479
+ clone_sets = marginal_clone_sets(prof, epsilon)
480
+
481
+ for clone_set in clone_sets:
482
+
483
+ non_clones = [n for n in prof.candidates if n not in clone_set]
484
+
485
+ for c in clone_set:
486
+
487
+ cands_without_c = [d for d in prof.candidates if d != c]
488
+
489
+ for n in non_clones:
490
+
491
+ if conditions_to_check == "all" or conditions_to_check == "NCIC":
492
+
493
+ if n in vm(prof) and not n in vm(prof,curr_cands = cands_without_c):
494
+ if verbose:
495
+ print("Non-clone choice is not independent of clones:")
496
+ prof.display()
497
+ print(prof.description())
498
+ print("Clone set:",clone_set)
499
+ print(f"{vm.name} winners in full profile: {vm(prof)}")
500
+ print(f"{vm.name} winners without clone {c}: {vm(prof,curr_cands = cands_without_c)}")
501
+ print(f"The non-clone {n} wins with the clone {c} included but loses when the clone is removed.")
502
+ return True
503
+
504
+ if n not in vm(prof) and n in vm(prof,curr_cands = cands_without_c):
505
+ if verbose:
506
+ print("Non-clone choice is not independent of clones:")
507
+ prof.display()
508
+ print(prof.description())
509
+ print("Clone set:",clone_set)
510
+ print(f"{vm.name} winners in full profile: {vm(prof)}")
511
+ print(f"{vm.name} winners without clone {c}: {vm(prof,curr_cands = cands_without_c)}")
512
+ print(f"The non-clone {n} loses with the clone {c} included but wins when the clone is removed.")
513
+ return True
514
+
515
+ if conditions_to_check == "all" or conditions_to_check == "CIC":
516
+
517
+ if len([c for c in vm(prof) if c in clone_set]) > 0 and len([c for c in vm(prof, cands_without_c) if c in clone_set]) == 0:
518
+ if verbose:
519
+ print("Clone choice is not independent of clones:")
520
+ prof.display()
521
+ print(prof.description())
522
+ print("Clone set:",clone_set)
523
+ print(f"{vm.name} winners in full profile: {vm(prof)}")
524
+ print(f"{vm.name} winners without clone {c}: {vm(prof,curr_cands = cands_without_c)}")
525
+ print(f"A clone wins with the clone {c} included but no clone wins when {c} is removed.")
526
+ return True
527
+
528
+ if len([c for c in vm(prof) if c in clone_set]) == 0 and len([c for c in vm(prof, cands_without_c) if c in clone_set]) > 0:
529
+ if verbose:
530
+ print("Clone choice is not independent of clones:")
531
+ prof.display()
532
+ print(prof.description())
533
+ print("Clone set:",clone_set)
534
+ print(f"{vm.name} winners in full profile: {vm(prof)}")
535
+ print(f"{vm.name} winners without clone {c}: {vm(prof,curr_cands = cands_without_c)}")
536
+ print(f"No clone wins with the clone {c} included but a clone wins when {c} is removed.")
537
+ return True
538
+
539
+ return False
540
+
541
+ def find_all_independence_of_clones_violations(prof, vm, clone_def = "Tideman", epsilon = 0, conditions_to_check = "all", verbose = False):
542
+ """Returns all violations of Independence of Clones for the given election data and voting method.
543
+
544
+ Args:
545
+ prof (Profile): the election data.
546
+ vm (VotingMethod): A voting method to test.
547
+ clone_def (str, default="Tideman"): The definition of clones. Options are "Tideman" and "Marginal".
548
+ epsilon (float, default=0): If clone_def is "Marginal", then for C to be a marginal clone set, it must but that for any c,c' in C and x not in C, |margin(c,x) - margin(c',x)| <= epsilon.
549
+ conditions_to_check (str, default="all"): The conditions to check. If "all", then both NCIC and CIC are checked. If "NCIC", then only NCIC is checked. If "CIC", then only CIC is checked.
550
+ verbose (bool, default=False): If a violation is found, display the violation.
551
+
552
+ Returns:
553
+ List whose elements are either triples of the form (clone_set,clone,non-clone) or pairs of the form (clone_set,clone). If the triple (clone_set,clone,non-clone) is returned, then non-clone choice is not independent ot clones. If the pair (clone_set,clone) is returned, then clone choice is not independent of clones.
554
+ """
555
+
556
+ if clone_def == "Tideman":
557
+ clone_sets = tideman_clone_sets(prof)
558
+
559
+ if clone_def == "Marginal":
560
+ clone_sets = marginal_clone_sets(prof, epsilon)
561
+
562
+ violations = list()
563
+
564
+ for clone_set in clone_sets:
565
+
566
+ non_clones = [n for n in prof.candidates if n not in clone_set]
567
+
568
+ for c in clone_set:
569
+
570
+ cands_without_c = [d for d in prof.candidates if d != c]
571
+
572
+ for n in non_clones:
573
+
574
+ if conditions_to_check == "all" or conditions_to_check == "NCIC":
575
+
576
+ if n in vm(prof) and not n in vm(prof,curr_cands = cands_without_c):
577
+ violations.append((clone_set,c,n))
578
+ if verbose:
579
+ print("Non-clone choice is not independent of clones:")
580
+ prof.display()
581
+ print(prof.description())
582
+ print("Clone set:",clone_set)
583
+ print(f"{vm.name} winners in full profile: {vm(prof)}")
584
+ print(f"{vm.name} winners without clone {c}: {vm(prof,curr_cands = cands_without_c)}")
585
+ print(f"The non-clone {n} wins with the clone {c} included but loses when the clone is removed.")
586
+ print("")
587
+
588
+ if n not in vm(prof) and n in vm(prof,curr_cands = cands_without_c):
589
+ violations.append((clone_set,c,n))
590
+ if verbose:
591
+ print("Non-clone choice is not independent of clones:")
592
+ prof.display()
593
+ print(prof.description())
594
+ print("Clone set:",clone_set)
595
+ print(f"{vm.name} winners in full profile: {vm(prof)}")
596
+ print(f"{vm.name} winners without clone {c}: {vm(prof,curr_cands = cands_without_c)}")
597
+ print(f"The non-clone {n} loses with the clone {c} included but wins when the clone is removed.")
598
+ print("")
599
+
600
+ if conditions_to_check == "all" or conditions_to_check == "CIC":
601
+
602
+ if len([c for c in vm(prof) if c in clone_set]) > 0 and len([c for c in vm(prof, cands_without_c) if c in clone_set]) == 0:
603
+ violations.append((clone_set,c))
604
+ if verbose:
605
+ print("Clone choice is not independent of clones:")
606
+ prof.display()
607
+ print(prof.description())
608
+ print("Clone set:",clone_set)
609
+ print(f"{vm.name} winners in full profile: {vm(prof)}")
610
+ print(f"{vm.name} winners without clone {c}: {vm(prof,curr_cands = cands_without_c)}")
611
+ print(f"A clone wins with the clone {c} included but no clone wins when {c} is removed.")
612
+ print("")
613
+
614
+ if len([c for c in vm(prof) if c in clone_set]) == 0 and len([c for c in vm(prof, cands_without_c) if c in clone_set]) > 0:
615
+ violations.append((clone_set,c))
616
+ if verbose:
617
+ print("Clone choice is not independent of clones:")
618
+ prof.display()
619
+ print(prof.description())
620
+ print("Clone set:",clone_set)
621
+ print(f"{vm.name} winners in full profile: {vm(prof)}")
622
+ print(f"{vm.name} winners without clone {c}: {vm(prof,curr_cands = cands_without_c)}")
623
+ print(f"No clone wins with the clone {c} included but a clone wins when {c} is removed.")
624
+ print("")
625
+
626
+ return violations
627
+
628
+ independence_of_clones = Axiom(
629
+ "Independence of Clones",
630
+ has_violation = has_independence_of_clones_violation,
631
+ find_all_violations = find_all_independence_of_clones_violations,
632
+ )
633
+
634
+ variable_candidate_axioms = [
635
+ stability_for_winners,
636
+ immunity_to_spoilers,
637
+ ISDA,
638
+ IPDA,
639
+ independence_of_clones
640
+ ]