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
pref_voting/helper.py ADDED
@@ -0,0 +1,370 @@
1
+ from pref_voting.profiles import Profile
2
+ from pref_voting.profiles_with_ties import ProfileWithTies
3
+ from pref_voting.weighted_majority_graphs import MajorityGraph
4
+ from pref_voting.rankings import Ranking
5
+ from pref_voting.social_welfare_function import *
6
+ from pref_voting.voting_method import *
7
+ from itertools import combinations, chain
8
+ import random
9
+
10
+ import networkx as nx
11
+
12
+ def get_mg(edata, curr_cands = None):
13
+
14
+ if curr_cands == None:
15
+ if type(edata) == Profile or type(edata) == ProfileWithTies:
16
+ mg = MajorityGraph.from_profile(edata).mg
17
+ else:
18
+ mg = edata.mg
19
+ else:
20
+ if type(edata) == Profile or type(edata) == ProfileWithTies:
21
+ mg = nx.DiGraph()
22
+ mg.add_nodes_from(curr_cands)
23
+ mg.add_edges_from([(c1,c2) for c1 in curr_cands for c2 in curr_cands if edata.majority_prefers(c1, c2)])
24
+ else:
25
+ mg = edata.mg.copy()
26
+ mg.remove_nodes_from([c for c in edata.candidates if c not in curr_cands])
27
+ return mg
28
+
29
+
30
+ def get_weak_mg(edata, curr_cands = None):
31
+
32
+ if curr_cands == None:
33
+ if type(edata) == Profile or type(edata) == ProfileWithTies:
34
+ wmg = MajorityGraph.from_profile(edata).mg
35
+ else:
36
+ wmg = edata.mg
37
+ wmg.add_edges_from([(c1, c2) for c1 in edata.candidates for c2 in edata.candidates if c1 != c2 and edata.is_tied(c1, c2)])
38
+ else:
39
+ if type(edata) == Profile or type(edata) == ProfileWithTies:
40
+ wmg = nx.DiGraph()
41
+ wmg.add_nodes_from(curr_cands)
42
+ wmg.add_edges_from([(c1,c2) for c1 in curr_cands for c2 in curr_cands if c1 != c2 and (edata.majority_prefers(c1, c2) or edata.is_tied(c1, c2))])
43
+ else:
44
+ wmg = edata.mg.copy()
45
+ wmg.remove_nodes_from([c for c in edata.candidates if c not in curr_cands])
46
+ wmg.add_edges_from([(c1, c2) for c1 in curr_cands for c2 in curr_cands if c1 != c2 and edata.is_tied(c1, c2)])
47
+ return wmg
48
+
49
+
50
+ def swf_from_vm(vm, tie_breaker = None):
51
+ """
52
+ Given a voting method, returns a social welfare function that uses the voting method to rank the candidates (winners are ranked first; then they are excluded from curr_cands and the new winners are ranked second; etc.).
53
+
54
+ Args:
55
+ vm (function): A voting method.
56
+ tie_breaker (str): The tie-breaking method to use. Options are "alphabetic", "random", and None. Default is None.
57
+
58
+ Returns:
59
+ function: A social welfare function that uses the voting method to rank the candidates.
60
+ """
61
+
62
+ def f(prof, curr_cands = None):
63
+
64
+ cands = prof.candidates if curr_cands == None else curr_cands
65
+
66
+ ranked_cands = list()
67
+ ranking_dict = dict()
68
+
69
+ n=0
70
+
71
+ while n < len(cands):
72
+
73
+ if len(ranked_cands) == len(cands):
74
+ break
75
+
76
+ ws = vm(prof, curr_cands = [c for c in cands if c not in ranked_cands])
77
+ ranked_cands = ranked_cands + ws
78
+
79
+ if tie_breaker is None:
80
+ for c in ws:
81
+ ranking_dict[c] = n
82
+ n += 1
83
+
84
+ if tie_breaker == "alphabetic":
85
+ sorted_ws = sorted(ws)
86
+ for c in sorted_ws:
87
+ ranking_dict[c] = n
88
+ n += 1
89
+
90
+ if tie_breaker == "random":
91
+ random.shuffle(ws)
92
+ for c in ws:
93
+ ranking_dict[c] = n
94
+ n += 1
95
+
96
+ return Ranking(ranking_dict)
97
+
98
+ return SocialWelfareFunction(f, name = f"SWF from {vm.name}")
99
+
100
+
101
+ def vm_from_swf(swf):
102
+ """
103
+ Given a social welfare function, returns a voting method that selects all the candidates ranked first according to the swf.
104
+
105
+ Args:
106
+ swf (function): A social welfare function.
107
+
108
+ Returns:
109
+ function: A voting method that uses the swf to find the winning set.
110
+ """
111
+
112
+ def f(edata, curr_cands = None):
113
+ return sorted(swf(edata, curr_cands = curr_cands).first())
114
+
115
+ return VotingMethod(f, name = f"VM from {swf.name}")
116
+
117
+
118
+ def create_election(ranking_list,
119
+ rcounts = None,
120
+ using_extended_strict_preference=None,
121
+ candidates=None):
122
+ """Creates an election from a list of rankings.
123
+
124
+ Args:
125
+ ranking_list (list): A list of rankings, which may be a list of tuples of candidates, a list of dictionaries or a list of Ranking objects.
126
+ using_extended_strict_preference (bool, optional): Whether to use extended strict preference after creating a ProfileWithTies. Defaults to None.
127
+ candidates (list, optional): A list of candidates. Only used for creating a ProfileWithTies. Defaults to None (by default the candidates are all the candidates that are ranked by at least on voter).
128
+
129
+ Returns:
130
+ Profile or ProfileWithTies: The election profile.
131
+ """
132
+
133
+ if len(ranking_list) > 0 and (type(ranking_list[0]) == tuple or type(ranking_list[0]) == list):
134
+ return Profile(ranking_list, rcounts=rcounts)
135
+ elif len(ranking_list) > 0 and (type(ranking_list[0]) == dict or type(ranking_list[0]) == Ranking):
136
+ if candidates is not None:
137
+ prof = ProfileWithTies(ranking_list, candidates=candidates, rcounts=rcounts)
138
+ else:
139
+ prof = ProfileWithTies(ranking_list, rcounts=rcounts)
140
+ if using_extended_strict_preference:
141
+ prof.use_extended_strict_preference()
142
+ return prof
143
+ else: # ranking_list is empty
144
+ print("Warning: list of rankings is empty.")
145
+ return Profile(ranking_list)
146
+
147
+
148
+ class SPO(object):
149
+ """A strict partial order class due to Jobst Heitzig.
150
+
151
+ The strict partial order P as a binary relation is encoded as a 2d numpy array. The predecessors and successors of each object are precomputed. The add method adds a new pair to the relation and computes the transitive closure.
152
+
153
+ Args:
154
+ n (int): The number of objects.
155
+
156
+ """
157
+
158
+ n = None
159
+ """The number of objects"""
160
+ objects = None
161
+ """The list of objects"""
162
+ P = None
163
+ """The strict partial ordering P as a binary relation encoded as a 2d numpy array"""
164
+ preds = None
165
+ """The list of predecessors of each object"""
166
+ succs = None
167
+ """The list of successors of each object"""
168
+
169
+ def __init__(self, n):
170
+ self.n = n
171
+ self.objects = list(range(n))
172
+ self.P = np.zeros((n, n), dtype=bool)
173
+ self.preds = [[] for _ in range(n)]
174
+ self.succs = [[] for _ in range(n)]
175
+
176
+ def add(self, a, b):
177
+ """add a P b and all transitive consequences"""
178
+ if not self.P[a][b]:
179
+ self.P[a][b] = True
180
+ self.preds[b].append(a)
181
+ self.succs[a].append(b)
182
+ for c in self.preds[a]:
183
+ self._register(c, b)
184
+ for d in self.succs[b]:
185
+ self._register(c, d)
186
+ for d in self.succs[b]:
187
+ self._register(a, d)
188
+
189
+ def initial_elements(self):
190
+ """return the initial elements of P (those without predecessors))"""
191
+ return [i for i in self.objects if len(self.preds[i]) == 0]
192
+
193
+ def _register(self, a, b):
194
+ """register that a P b, without forming the transitive closure"""
195
+ if not self.P[a][b]:
196
+ self.P[a][b] = True
197
+ self.preds[b].append(a)
198
+ self.succs[a].append(b)
199
+
200
+ def to_numpy(self):
201
+ """Return the partial order matrix P as a numpy array."""
202
+ return self.P
203
+
204
+ def to_networkx(self, cmap=None):
205
+ """Convert the SPO to a networkx DiGraph.
206
+
207
+ Args:
208
+ cmap (dict): A dictionary mapping each number to a candidate name. If None, the identity map is used.
209
+
210
+ Returns:
211
+ nx.DiGraph: The resulting directed graph with nodes labeled according to cmap if provided.
212
+ """
213
+ G = nx.DiGraph()
214
+
215
+ # Determine node labels based on cmap
216
+ if cmap is not None:
217
+ node_labels = {i: cmap[i] for i in self.objects}
218
+ else:
219
+ node_labels = {i: i for i in self.objects}
220
+
221
+ # Add nodes with labels
222
+ G.add_nodes_from(node_labels.values())
223
+
224
+ # Add edges based on the partial order matrix
225
+ for a in range(self.n):
226
+ for b in range(self.n):
227
+ if self.P[a][b]:
228
+ G.add_edge(node_labels[a], node_labels[b])
229
+
230
+ return G
231
+
232
+ def to_list(self, cmap=None):
233
+ """If the SPO is a linear order, return a list representing the order.
234
+
235
+ The list will contain candidate names based on the cmap if provided; otherwise, it will
236
+ contain the numbers.
237
+
238
+ Returns None if the SPO is not a linear order.
239
+
240
+ Args:
241
+ cmap (dict): A dictionary mapping each number to a candidate name. If None, the identity map is used.
242
+ """
243
+ # Check if the SPO is a strict linear order
244
+ for i in range(self.n):
245
+ for j in range(i + 1, self.n):
246
+ if not (self.P[i][j] or self.P[j][i]):
247
+ return None # i and j are not comparable
248
+
249
+ # Create the linear order list by topologically sorting the nodes
250
+ linear_order = []
251
+ visited = [False] * self.n
252
+
253
+ def visit(node):
254
+ if not visited[node]:
255
+ visited[node] = True
256
+ for successor in self.succs[node]:
257
+ visit(successor)
258
+ linear_order.append(node)
259
+
260
+ for node in self.objects:
261
+ visit(node)
262
+
263
+ # Reverse to get the correct order
264
+ linear_order = linear_order[::-1]
265
+
266
+ # If cmap is provided, map the numbers to candidate names
267
+ if cmap is not None:
268
+ linear_order = [cmap[node] for node in linear_order]
269
+
270
+ return linear_order
271
+
272
+ def weak_orders(A):
273
+ """A generator for all weak orders on A"""
274
+ if not A:
275
+ yield {}
276
+ return
277
+ for k in range(1, len(A) + 1):
278
+ for B in combinations(A, k):
279
+ for order in weak_orders(set(A) - set(B)):
280
+ new_order = {cand: rank + 1 for cand, rank in order.items()}
281
+ yield {**new_order, **{cand: 0 for cand in B}}
282
+
283
+
284
+ def weak_compositions(n, k):
285
+ """A generator for all weak compositions of n into k parts"""
286
+
287
+ if k == 1:
288
+ yield [n]
289
+ else:
290
+ for i in range(n + 1):
291
+ for comp in weak_compositions(n - i, k - 1):
292
+ yield [i] + comp
293
+
294
+ def compositions(n):
295
+ """Generates all compositions of the integer n. Adapted from https://stackoverflow.com/questions/10244180/python-generating-integer-partitions."""
296
+
297
+ a = [0 for i in range(n + 1)]
298
+ k = 1
299
+ a[0] = 0
300
+ a[1] = n
301
+ while k != 0:
302
+ x = a[k - 1] + 1
303
+ y = a[k] - 1
304
+ k -= 1
305
+ while 1 <= y:
306
+ a[k] = x
307
+ x = 1
308
+ y -= x
309
+ k += 1
310
+ a[k] = x + y
311
+ yield a[:k + 1]
312
+
313
+ def enumerate_compositions(int_list):
314
+ """Given a list of integers, enumerate all the compositions of the integers."""
315
+
316
+ first_int = int_list[0]
317
+
318
+ if len(int_list) == 1:
319
+ for composition in compositions(first_int):
320
+ yield [composition]
321
+
322
+ else:
323
+ for composition in compositions(first_int):
324
+ for comps in enumerate_compositions(int_list[1:]):
325
+ yield [composition] + comps
326
+
327
+ def sublists(lst, length, x = None, partial_sublist = None):
328
+ """Generate all sublists of lst of a specified length."""
329
+
330
+ x = length if x is None else x
331
+
332
+ partial_sublist = list() if partial_sublist is None else partial_sublist
333
+
334
+ if len(partial_sublist) == length:
335
+ yield partial_sublist
336
+
337
+ for i,el in enumerate(lst):
338
+
339
+ if i < x:
340
+
341
+ extended_partial_sublist = partial_sublist + [el]
342
+ x += 1
343
+ yield from sublists(lst[i+1::], length, x, extended_partial_sublist)
344
+
345
+ def convex_lexicographic_sublists(l):
346
+ """Given a list l, return all convex sublists S such that S is already sorted lexicographically."""
347
+
348
+ cl_sublists = []
349
+ current_list = []
350
+
351
+ for idx, p in enumerate(l):
352
+ if current_list + [p] == sorted(current_list + [p]):
353
+ current_list = current_list + [p]
354
+
355
+ if idx == len(l)-1:
356
+ cl_sublists.append(current_list)
357
+
358
+ else:
359
+ cl_sublists.append(current_list)
360
+ current_list = [p]
361
+
362
+ if idx == len(l) - 1:
363
+ cl_sublists.append(current_list)
364
+
365
+ return cl_sublists
366
+
367
+ def powerset(iterable):
368
+ """powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"""
369
+ s = list(iterable)
370
+ return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))