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,333 @@
1
+ '''
2
+ File: utility_profiles.py
3
+ Author: Wes Holliday (wesholliday@berkeley.edu) and Eric Pacuit (epacuit@umd.edu)
4
+ Date: May 26, 2023
5
+
6
+ Functions to reason about profiles of utilities.
7
+ '''
8
+
9
+
10
+ from math import ceil
11
+ import numpy as np
12
+ import json
13
+ from scipy import stats
14
+ import networkx as nx
15
+ from tabulate import tabulate
16
+ from tabulate import SEPARATING_LINE
17
+ from pref_voting.profiles_with_ties import ProfileWithTies
18
+ from pref_voting.rankings import Ranking
19
+ from pref_voting.mappings import Utility
20
+ from pref_voting.grade_profiles import GradeProfile
21
+
22
+ # turn off future warnings.
23
+ # getting the following warning when calling tabulate to display a profile:
24
+ # /Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/tabulate.py:1027: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
25
+ # if headers == "keys" and not rows:
26
+ # see https://stackoverflow.com/questions/40659212/futurewarning-elementwise-comparison-failed-returning-scalar-but-in-the-futur
27
+ #
28
+ import warnings
29
+ warnings.simplefilter(action='ignore', category=FutureWarning)
30
+
31
+ class UtilityProfile(object):
32
+ """An anonymous profile of (truncated) utilities.
33
+
34
+ :param utilities: List of utilities in the profile, where a utility is either a :class:`Utility` object or a dictionary.
35
+ :type utilities: list[dict[int or str: float]] or list[Utility]
36
+ :param ucounts: List of the number of voters associated with each utility. Should be the same length as utilities. If not provided, it is assumed that 1 voters submitted each element of ``utilities``.
37
+ :type ucounts: list[int], optional
38
+ :param domain: List of alternatives in the profile. If not provided, it is the alternatives that are assigned a utility by least on voter.
39
+ :type domain: list[int] or list[str], optional
40
+ :param cmap: Dictionary mapping alternatives to alternative names (strings). If not provided, each alternative name is mapped to itself.
41
+ :type cmap: dict[int or str: str], optional
42
+
43
+ :Example:
44
+
45
+ The following code creates a profile in which
46
+ 2 voters submitted the ranking 0 ranked first, 1 ranked second, and 2 ranked third; 3 voters submitted the ranking 1 and 2 are tied for first place and 0 is ranked second; and 1 voter submitted the ranking in which 2 is ranked first and 0 is ranked second:
47
+
48
+ .. code-block:: python
49
+
50
+ uprof = UtilityProfile([{"x":1, "y":3, "z":1}, {"x":0, "y":-1, "z":4}, {"x":0.5, "y":-1}, {"x":0, "y":1, "z":2}], ucounts=[2, 3, 1, 1], domain=["x", "y", "z"])
51
+
52
+ """
53
+
54
+ def __init__(self, utilities, ucounts=None, domain=None, cmap=None):
55
+ """Constructor method"""
56
+
57
+ assert ucounts is None or len(utilities) == len(
58
+ ucounts
59
+ ), "The number of utilities much be the same as the number of ucounts"
60
+
61
+ _domain = domain if domain is not None else []
62
+ for u in utilities:
63
+ if isinstance(u, dict):
64
+ _domain += [x for x in u.keys() if x not in _domain]
65
+ elif isinstance(u, Utility):
66
+ _domain += [x for x in u.domain if x not in _domain]
67
+
68
+ self.domain = sorted(list(set(_domain)))
69
+ """The domain of the profile. """
70
+
71
+ self.cmap = cmap if cmap is not None else {c: str(c) for c in self.domain}
72
+ """The candidate map is a dictionary associating an alternative with the name used when displaying a alternative."""
73
+
74
+ self._utilities = [
75
+ Utility(u, domain = self.domain, cmap=self.cmap)
76
+ if type(u) == dict
77
+ else Utility(u.as_dict(), domain=self.domain, cmap=self.cmap)
78
+ for u in utilities
79
+ ]
80
+ """The list of utilities in the Profile (each utility is a :class:`Utility` object).
81
+ """
82
+
83
+ self.ucounts = [1] * len(utilities) if ucounts is None else list(ucounts)
84
+
85
+ self.num_voters = np.sum(self.ucounts)
86
+ """The number of voters in the profile. """
87
+
88
+ @property
89
+ def candidates(self):
90
+ """Return the candidates in the profile."""
91
+ return self.domain
92
+
93
+ @property
94
+ def num_cands(self):
95
+ """Return the number of candidates in the profile."""
96
+ return len(self.candidates)
97
+
98
+ @property
99
+ def utilities_counts(self):
100
+ """Returns the utilities and the counts of each utility."""
101
+
102
+ return self._utilities, self.ucounts
103
+
104
+ @property
105
+ def utilities(self):
106
+ """Return all of the utilities in the profile."""
107
+
108
+ us = list()
109
+ for u,c in zip(self._utilities, self.ucounts):
110
+ us += [u] * c
111
+ return us
112
+
113
+
114
+ def normalize_by_range(self):
115
+ """Return a profile in which each utility is normalized by range."""
116
+
117
+ return UtilityProfile([
118
+ u.normalize_by_range() for u in self._utilities
119
+ ], ucounts = self.ucounts, domain = self.domain, cmap=self.cmap)
120
+
121
+ def normalize_by_standard_score(self):
122
+ """Return a profile in which each utility is normalized by standard scores.
123
+ """
124
+ return UtilityProfile([
125
+ u.normalize_by_standard_score() for u in self._utilities
126
+ ], ucounts = self.ucounts, domain = self.domain, cmap=self.cmap)
127
+
128
+ def has_utility(self, x):
129
+ """Return True if ``x`` is assigned a utility by at least one voter."""
130
+
131
+ return any([u.has_utility(x) for u in self._utilities])
132
+
133
+ def util_sum(self, x):
134
+ """Return the sum of the utilities of ``x``. If ``x`` is not assigned a utility by any voter, return None."""
135
+
136
+ return np.sum([u(x) * c for u,c in zip(*self.utilities_counts) if u.has_utility(x)]) if self.has_utility(x) else None
137
+
138
+ def util_avg(self, x):
139
+ """Return the sum of the utilities of ``x``. If ``x`` is not assigned a utility by any voter, return None."""
140
+
141
+ return np.average([u(x) * c for u,c in zip(*self.utilities_counts) if u.has_utility(x)]) if self.has_utility(x) else None
142
+
143
+ def util_max(self, x):
144
+ """Return the maximum of the utilities of ``x``. If ``x`` is not assigned a utility by any voter, return None."""
145
+
146
+ return max([u(x) for u in self._utilities if u.has_utility(x)]) if self.has_utility(x) else None
147
+
148
+ def util_min(self, x):
149
+ """Return the minimum of the utilities of ``x``. If ``x`` is not assigned a utility by any voter, return None."""
150
+
151
+ return min([u(x) for u in self._utilities if u.has_utility(x)]) if self.has_utility(x) else None
152
+
153
+ def sum_utility_function(self):
154
+ """Return the sum utility function of the profile."""
155
+
156
+ return Utility(
157
+ {
158
+ x: self.util_sum(x)
159
+ for x in self.domain
160
+ },
161
+ domain=self.domain,
162
+ )
163
+ def avg_utility_function(self):
164
+ """Return the average utility function of the profile."""
165
+
166
+ return Utility(
167
+ {
168
+ x: np.average([u(x) for u in self.utilities])
169
+ for x in self.domain
170
+ },
171
+ domain=self.domain,
172
+ )
173
+
174
+ def to_ranking_profile(self):
175
+ """Return a ranking profile (a :class:ProfileWithTies) corresponding to the profile."""
176
+
177
+ return ProfileWithTies(
178
+ [u.ranking() for u in self._utilities],
179
+ rcounts = self.ucounts,
180
+ candidates = self.domain,
181
+ cmap = self.cmap
182
+ )
183
+
184
+ def to_approval_profile(self, prob_to_cont_approving=1.0, decay_rate=0.0):
185
+ """
186
+ Return a GradeProfile with each utility transformed to an approval ballot.
187
+
188
+ See :meth:`pref_voting.Utility.to_approval_ballot` for more details.
189
+ """
190
+
191
+ return GradeProfile(
192
+ [u.to_approval_ballot(
193
+ prob_to_cont_approving=prob_to_cont_approving,
194
+ decay_rate=decay_rate)
195
+ for u in self._utilities],
196
+ [0, 1],
197
+ gcounts = self.ucounts,
198
+ candidates = self.domain,
199
+ cmap = self.cmap
200
+ )
201
+
202
+ def to_k_approval_profile(self, k, prob_to_cont_approving=1.0, decay_rate=0.0):
203
+ """
204
+ Return a GradeProfile with each utility transformed to a k-approval ballot.
205
+
206
+ See :meth:`pref_voting.Utility.to_approval_ballot` for more details.
207
+ """
208
+
209
+ return GradeProfile(
210
+ [u.to_k_approval_ballot(
211
+ k,
212
+ prob_to_cont_approving=prob_to_cont_approving,
213
+ decay_rate=decay_rate)
214
+ for u in self._utilities],
215
+ [0, 1],
216
+ gcounts = self.ucounts,
217
+ candidates = self.domain,
218
+ cmap = self.cmap
219
+ )
220
+
221
+
222
+ def write(self):
223
+ """Write the profile to a string."""
224
+
225
+ uprof_str = f"{len(self.domain)};{self.num_voters}"
226
+ for u in self.utilities:
227
+ u_str = ''
228
+ for c in u.domain:
229
+ if u.has_utility(c):
230
+ u_str += f"{c}:{u(c)},"
231
+ uprof_str += f";{u_str[0:-1]}"
232
+ return str(uprof_str)
233
+
234
+ def as_dict(self):
235
+ """Return a the profile as a dictionary."""
236
+
237
+ return {
238
+ "domain": self.domain,
239
+ "utilities": [u.as_dict() for u in self._utilities],
240
+ "ucounts": self.ucounts,
241
+ "cmap": self.cmap
242
+ }
243
+
244
+ @classmethod
245
+ def from_json(cls, uprof_json):
246
+ """
247
+ Returns a profile of utilities described by ``uprof_json``.
248
+
249
+ ``uprof_json`` must be in the format produced by the :meth:`pref_voting.UtilityProfile.as_dict` function.
250
+ """
251
+ domain = uprof_json["domain"]
252
+ util_maps = uprof_json["utilities"]
253
+ ucounts = uprof_json["ucounts"]
254
+ cmap = uprof_json["cmap"]
255
+
256
+ # since json converts all keys to strings, we need to convert them back to integers if the domain is integers.
257
+ integer_domain = all([type(x) == int for x in domain])
258
+ if integer_domain:
259
+ util_maps = [{int(c):v for c,v in u.items()} for u in util_maps]
260
+ cmap = {int(c):v for c,v in cmap.items()}
261
+
262
+ return cls(util_maps, domain=domain, ucounts=ucounts, cmap=cmap)
263
+
264
+ @classmethod
265
+ def from_string(cls, uprof_str):
266
+ """
267
+ Returns a profile of utilities described by ``uprof_str``.
268
+
269
+ ``uprof_str`` must be in the format produced by the :meth:`pref_voting.UtilityProfile.write` function.
270
+ """
271
+ uprof_data = uprof_str.split(";")
272
+
273
+ num_alternatives,num_voters,utilities = int(uprof_data[0]),int(uprof_data[1]),uprof_data[2:]
274
+
275
+ util_maps = [{int(cu.split(":")[0]): float(cu.split(":")[1]) for cu in utils.split(",")} if utils != '' else {} for utils in utilities]
276
+
277
+ if len(util_maps) != num_voters:
278
+ raise Exception("Number of voters does not match the number of utilities.")
279
+
280
+ return cls(util_maps, domain=range(num_alternatives))
281
+
282
+ def display(self, vmap = None, show_totals=False):
283
+ """Display a utility profile as an ascii table (using tabulate). If ``show_totals`` is true then the sum, min, and max of the utilities are displayed.
284
+
285
+ """
286
+
287
+ utilities = self.utilities
288
+
289
+ vmap = vmap if vmap is not None else {vidx: str(vidx + 1) for vidx in range(len(utilities))}
290
+ voters = range(len(utilities))
291
+
292
+ if show_totals:
293
+ tbl ={"Voter" : [vmap[v] for v in voters] + [SEPARATING_LINE] + ["Sum", "Min", "Max"]}
294
+ tbl.update({self.cmap(x): [utilities[v](x) for v in voters] + [SEPARATING_LINE] + [self.util_sum(x), self.util_min(x), self.util_max(x)] for x in self.domain})
295
+ else:
296
+ tbl ={"Voter" : [vmap[v] for v in voters]}
297
+ tbl.update({str(x): [utilities[v](x) for v in voters] for x in self.domain})
298
+ print( tabulate(tbl, headers="keys"))
299
+
300
+ def __getstate__(self):
301
+ # Serialize only the essential data
302
+ state = {
303
+ 'utilities': [u.as_dict() for u in self._utilities],
304
+ 'ucounts': self.ucounts,
305
+ 'domain': self.domain,
306
+ 'cmap': self.cmap
307
+ }
308
+ return state
309
+
310
+ def __setstate__(self, state):
311
+ # Restore essential data
312
+ self.domain = state['domain']
313
+ self.cmap = state['cmap']
314
+ self.ucounts = state['ucounts']
315
+
316
+ self._utilities = [Utility(u_dict, domain=self.domain, cmap=self.cmap)
317
+ for u_dict in state['utilities']]
318
+
319
+ self.num_voters = sum(self.ucounts)
320
+
321
+ def write_utility_profiles_to_json(uprofs, filename):
322
+ """Write a list of utility profiles to a json file."""
323
+
324
+ uprofs_json = [uprof.as_dict() for uprof in uprofs]
325
+ with open(filename, "w") as f:
326
+ json.dump(uprofs_json, f)
327
+
328
+ def read_utility_profiles_from_json(filename):
329
+ """Read a list of utility profiles to a json file."""
330
+
331
+ with open(filename, "r") as f:
332
+ uprofs_json = json.load(f)
333
+ return [UtilityProfile.from_json(uprof_json) for uprof_json in uprofs_json]