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,355 @@
1
+ '''
2
+ File: voting_methods.py
3
+ Author: Wes Holliday (wesholliday@berkeley.edu) and Eric Pacuit (epacuit@umd.edu)
4
+ Date: November 6, 2021
5
+ Update: January 15, 2023
6
+
7
+ The VotingMethod class and helper functions for voting methods
8
+ '''
9
+
10
+ import functools
11
+ import inspect
12
+ import numpy as np
13
+ from numba import jit # Remove until numba supports python 3.11
14
+ import random
15
+ import json
16
+ from pref_voting.voting_method_properties import VotingMethodProperties
17
+ from filelock import FileLock, Timeout
18
+ import importlib.resources
19
+
20
+ import glob
21
+ import os
22
+
23
+ class VotingMethod(object):
24
+ """
25
+ A class to add functionality to voting methods.
26
+
27
+ Args:
28
+ vm (function): An implementation of a voting method. The function should accept a Profile, ProfileWithTies, MajorityGraph, and/or MarginGraph, and a keyword parameter ``curr_cands`` to find the winner after restricting to ``curr_cands``.
29
+ name (string): The Human-readable name of the voting method.
30
+ properties (VotingMethodProperties): The properties of the voting method.
31
+ input_types (list): The types of input that the voting method can accept.
32
+
33
+ """
34
+ def __init__(self,
35
+ vm,
36
+ name=None,
37
+ input_types=None,
38
+ skip_registration=False, properties_file=None):
39
+
40
+ self.vm = vm
41
+ self.name = name
42
+
43
+ # Determine the path to the properties file
44
+ if properties_file is None:
45
+
46
+ properties_file = importlib.resources.files('pref_voting') / 'data' / 'voting_methods_properties.json'
47
+
48
+
49
+ # Get the properties of the voting method
50
+ try:
51
+ with open(properties_file, "r") as file:
52
+ vm_props = json.load(file)
53
+ except FileNotFoundError:
54
+ vm_props = {}
55
+ except Exception as e:
56
+ print(f"An error occurred while opening the properties file: {e}")
57
+ vm_props = {}
58
+
59
+ if name in vm_props:
60
+ properties = VotingMethodProperties(**vm_props[name])
61
+ else:
62
+ properties = VotingMethodProperties()
63
+
64
+ self.properties = properties
65
+ self.input_types = input_types
66
+ self.skip_registration = skip_registration
67
+ self.algorithm = None
68
+
69
+ functools.update_wrapper(self, vm)
70
+
71
+ def __call__(self, edata, curr_cands = None, **kwargs):
72
+
73
+ if (curr_cands is not None and len(curr_cands) == 0) or len(edata.candidates) == 0:
74
+ return []
75
+
76
+ # Set the algorithm from self.algorithm if it's not already provided in kwargs
77
+ if 'algorithm' not in kwargs and self.algorithm is not None:
78
+ params = inspect.signature(self.vm).parameters
79
+ if 'algorithm' in params and params['algorithm'].kind in [inspect.Parameter.KEYWORD_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD]:
80
+ kwargs['algorithm'] = self.algorithm
81
+
82
+ return self.vm(edata, curr_cands=curr_cands, **kwargs)
83
+
84
+ def set_algorithm(self, algorithm):
85
+ """
86
+ Set the algorithm for the voting method if 'algorithm' is an accepted keyword parameter.
87
+
88
+ Args:
89
+ algorithm: The algorithm to set for the voting method.
90
+ """
91
+ params = inspect.signature(self.vm).parameters
92
+ if 'algorithm' in params and params['algorithm'].kind in [inspect.Parameter.KEYWORD_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD]:
93
+ self.algorithm = algorithm
94
+ else:
95
+ raise ValueError(f"The method {self.name} does not accept 'algorithm' as a parameter.")
96
+
97
+ def choose(self, edata, curr_cands = None):
98
+ """
99
+ Return a randomly chosen element from the winning set.
100
+ """
101
+
102
+ ws = self.__call__(edata, curr_cands = curr_cands)
103
+ return random.choice(ws)
104
+
105
+ def prob(self, edata, curr_cands = None):
106
+ """
107
+ Return a dictionary representing the even-chance tiebreaking for the voting method.
108
+ """
109
+
110
+ ws = self.__call__(edata, curr_cands = curr_cands)
111
+ return {c: 1.0 / len(ws) if c in ws else 0.0 for c in edata.candidates}
112
+
113
+ def display(self, edata, curr_cands = None, cmap = None, **kwargs):
114
+ """
115
+ Display the winning set of candidates.
116
+ """
117
+
118
+ cmap = cmap if cmap is not None else edata.cmap
119
+
120
+ ws = self.__call__(edata, curr_cands = curr_cands, **kwargs)
121
+
122
+ if ws is None: # some voting methods, such as ``ranked_pairs_with_test``, may return None if it is taking long to compute the winner.
123
+ print(f"{self.name} winning set is not available")
124
+ else:
125
+ w_str = f"{self.name} winner is " if len(ws) == 1 else f"{self.name} winners are "
126
+ print(w_str + "{" + ", ".join([str(cmap[c]) for c in ws]) + "}")
127
+
128
+ def set_name(self, new_name):
129
+ """Set the name of the voting method."""
130
+
131
+ self.name = new_name
132
+
133
+ def add_property(self, prop, value):
134
+ """Add a property to the voting method."""
135
+
136
+ setattr(self.properties, prop, value)
137
+
138
+ def remove_property(self, prop):
139
+ """Remove a property from the voting method."""
140
+
141
+ delattr(self.properties, prop)
142
+
143
+ def load_properties(self, filename=None):
144
+ """Load the properties of the voting method from a JSON file."""
145
+
146
+ # Determine the path to the properties file
147
+ if filename is None:
148
+ filename = importlib.resources.files('pref_voting') / 'data' / 'voting_methods_properties.json'
149
+ lock = FileLock(f"{filename}.lock")
150
+ with lock:
151
+ try:
152
+ with open(filename, 'r') as file:
153
+ vm_props = json.load(file)
154
+ except FileNotFoundError:
155
+ vm_props = {}
156
+
157
+ if self.name in vm_props:
158
+ self.properties = VotingMethodProperties(**vm_props[self.name])
159
+ else:
160
+ self.properties = VotingMethodProperties()
161
+
162
+ def has_property(self, prop):
163
+ """Check if the voting method has a property."""
164
+
165
+ return self.properties[prop]
166
+
167
+ def get_properties(self):
168
+ """Return the properties of the voting method."""
169
+
170
+ return {
171
+ "satisfied": [prop
172
+ for prop, val in self.properties.items()
173
+ if val is True],
174
+ "violated": [prop
175
+ for prop, val in self.properties.items()
176
+ if val is False],
177
+ "na": [prop
178
+ for prop, val in self.properties.items()
179
+ if val is None]
180
+ }
181
+
182
+ def save_properties(self, filename=None, timeout=10):
183
+ """Save the properties of the voting method to a JSON file."""
184
+
185
+ # Determine the path to the properties file
186
+ if filename is None:
187
+ filename = importlib.resources.files('pref_voting') / 'data' / 'voting_methods_properties.json'
188
+
189
+
190
+ lock = FileLock(f"{filename}.lock", timeout=timeout)
191
+ try:
192
+ with lock:
193
+ try:
194
+ with open(filename, 'r') as file:
195
+ vm_props = json.load(file)
196
+ except FileNotFoundError:
197
+ vm_props = {}
198
+
199
+ vm_props[self.name] = self.properties.__dict__
200
+
201
+ with open(filename, 'w') as file:
202
+ json.dump(vm_props, file, indent=4, sort_keys=True)
203
+ except Timeout:
204
+ print(f"Could not acquire the lock within {timeout} seconds.")
205
+
206
+ def get_violation_witness(self, prop, minimal_resolute=False, minimal=False):
207
+ """Return the election that witnesses a violation of prop."""
208
+
209
+ from pref_voting.profiles import Profile
210
+
211
+ elections = {
212
+ "minimal resolute": None,
213
+ "minimal": None,
214
+ "any": None
215
+ }
216
+ if self.properties[prop]:
217
+ print(f"{self.name} satisfies {prop}, no election returned.")
218
+ return elections
219
+ elif self.properties[prop] is None:
220
+ print(f"{self.name} does not have a value for {prop}, no election returned.")
221
+ return elections
222
+ else:
223
+ dir = importlib.resources.files('pref_voting') / 'data' / 'examples' / prop
224
+
225
+ for f in glob.glob(f"{dir}*"):
226
+ fname = os.path.basename(f)
227
+ is_min = fname.startswith("minimal_")
228
+ is_min_resolute = fname.startswith("minimal_resolute")
229
+ found_it = False
230
+ if is_min_resolute and fname.startswith(f"minimal_resolute_{self.name.replace(' ', '_')}"):
231
+ print(f"Minimal resolute election for a violation of {prop} found.")
232
+ elections["minimal resolute"] = Profile.from_preflib(f)
233
+ if is_min and not is_min_resolute and fname.startswith(f"minimal_{self.name.replace(' ', '_')}"):
234
+ print(f"Minimal election for a violation of {prop} found.")
235
+ elections["minimal"] = Profile.from_preflib(f)
236
+
237
+ elif not is_min and not is_min_resolute and fname.startswith(f"{self.name.replace(' ', '_')}"):
238
+ elections["any"] = Profile.from_preflib(f)
239
+ if all([v is None for v in elections.values()]):
240
+ print(f"No election found illustrating the violation of {prop}.")
241
+ return elections
242
+
243
+ def check_property(self, prop, include_counterexample=True):
244
+ """Check if the voting method satisfies a property."""
245
+ from pref_voting.axioms import axioms_dict
246
+
247
+ if not self.properties[prop]:
248
+ print(f"{self.name} does not satisfy {prop}")
249
+ if include_counterexample:
250
+ if prop in axioms_dict:
251
+ #prof = prof
252
+ axioms_dict[prop].counterexample(self)
253
+
254
+ elif self.properties[prop] is None:
255
+ print(f"{self.name} does not have a value for {prop}")
256
+
257
+ else:
258
+ print(f"{self.name} satisfies {prop}")
259
+
260
+ def __str__(self):
261
+ return f"{self.name}"
262
+
263
+ def vm(name=None,
264
+ input_types=None,
265
+ skip_registration=False):
266
+ """
267
+ A decorator used when creating a voting method.
268
+ """
269
+ def wrapper(f):
270
+ return VotingMethod(f,
271
+ name=name,
272
+ input_types=input_types,
273
+ skip_registration=skip_registration)
274
+ return wrapper
275
+
276
+ @jit(nopython=True, fastmath=True)
277
+ def isin(arr, val):
278
+ """compiled function testing if the value val is in the array arr
279
+ """
280
+
281
+ for i in range(arr.shape[0]):
282
+ if (arr[i]==val):
283
+ return True
284
+ return False
285
+
286
+ @jit(nopython=True, fastmath=True)
287
+ def _num_rank_first(rankings, rcounts, cands_to_ignore, cand):
288
+ """The number of voters that rank candidate cand first after ignoring the candidates in
289
+ cands_to_ignore
290
+
291
+ Parameters
292
+ ----------
293
+ rankings: 2d numpy array
294
+ list of linear orderings of the candidates
295
+ rcounts: 1d numpy array
296
+ list of numbers of voters with the rankings
297
+ cands_to_ignore: 1d numpy array
298
+ list of the candidates to ignore
299
+ cand: int
300
+ a candidate
301
+
302
+ Key assumptions:
303
+ * the candidates are named 0...num_cands - 1, and c1 and c2 are
304
+ numbers between 0 and num_cands - 1
305
+ * voters submit linear orders over the candidate
306
+ """
307
+
308
+ num_voters = len(rankings)
309
+
310
+ top_cands_indices = np.zeros(num_voters, dtype=np.int32)
311
+
312
+ for vidx in range(num_voters):
313
+ for level in range(0, len(rankings[vidx])):
314
+ if not isin(cands_to_ignore, rankings[vidx][level]):
315
+ top_cands_indices[vidx] = level
316
+ break
317
+ top_cands = np.array([rankings[vidx][top_cands_indices[vidx]] for vidx in range(num_voters)])
318
+ is_cand = top_cands == cand # set to 0 each candidate not equal to cand
319
+ return np.sum(is_cand * rcounts)
320
+
321
+
322
+ @jit(nopython=True, fastmath=True)
323
+ def _num_rank_last(rankings, rcounts, cands_to_ignore, cand):
324
+ """The number of voters that rank candidate cand last after ignoring the candidates in
325
+ cands_to_ignore
326
+
327
+ Parameters
328
+ ----------
329
+ rankings: 2d numpy array
330
+ list of linear orderings of the candidates
331
+ rcounts: 1d numpy array
332
+ list of numbers of voters with the rankings
333
+ cands_to_ignore: 1d numpy array
334
+ list of the candidates to ignore
335
+ cand: int
336
+ a candidate
337
+
338
+ Key assumptions:
339
+ * the candidates are named 0...num_cands - 1, and c1 and c2 are
340
+ numbers between 0 and num_cands - 1
341
+ * voters submit linear orders over the candidate
342
+ """
343
+
344
+ num_voters = len(rankings)
345
+
346
+ last_cands_indices = np.zeros(num_voters, dtype=np.int32)
347
+
348
+ for vidx in range(num_voters):
349
+ for level in range(len(rankings[vidx]) - 1,-1,-1):
350
+ if not isin(cands_to_ignore, rankings[vidx][level]):
351
+ last_cands_indices[vidx] = level
352
+ break
353
+ bottom_cands = np.array([rankings[vidx][last_cands_indices[vidx]] for vidx in range(num_voters)])
354
+ is_cand = bottom_cands == cand
355
+ return np.sum(is_cand * rcounts)
@@ -0,0 +1,92 @@
1
+ '''
2
+ File: voting_methods.py
3
+ Author: Wes Holliday (wesholliday@berkeley.edu) and Eric Pacuit (epacuit@umd.edu)
4
+ Date: November 6, 2021
5
+ Update: January 15, 2023
6
+
7
+ The VotingMethodProperties class to encapsulate properties of voting methods.
8
+ '''
9
+ from enum import Enum
10
+
11
+ class ElectionTypes(Enum):
12
+ PROFILE = "Profile"
13
+ PROFILE_WITH_TIES = "ProfileWithTies"
14
+ TRUNCATED_LINEAR_PROFILE = "TruncatedLinearProfile"
15
+ MAJORITY_GRAPH = "MajorityGraph"
16
+ MARGIN_GRAPH = "MarginGraph"
17
+
18
+ class VotingMethodProperties:
19
+ """
20
+ Class to represent the properties of a voting method.
21
+ """
22
+ def __init__(self, **kwargs):
23
+ """
24
+ Initialize the properties of the voting method.
25
+
26
+ Args:
27
+ **kwargs: Arbitrary keyword arguments representing properties and their boolean values.
28
+ """
29
+ for key, value in kwargs.items():
30
+ setattr(self, key, value)
31
+
32
+ def items(self):
33
+ """
34
+ Return all properties as a list of items.
35
+
36
+ Returns:
37
+ List of all properties and their boolean values.
38
+ """
39
+ return self.__dict__.items()
40
+
41
+ def satisfied(self):
42
+ """
43
+ List all properties that are satisfied (i.e., True).
44
+
45
+ Returns:
46
+ List of property names that are satisfied.
47
+ """
48
+ return [key for key, value in self.items() if value is True]
49
+
50
+ def violated(self):
51
+ """
52
+ List all properties that are violated (i.e., False).
53
+
54
+ Returns:
55
+ List of property names that are violated.
56
+ """
57
+ return [key for key, value in self.items() if value is False]
58
+
59
+ def not_available(self):
60
+ """
61
+ List all properties that are not available (i.e., None).
62
+
63
+ Returns:
64
+ List of property names that are None (currently not available).
65
+ """
66
+ return [key for key, value in self.items() if value is None]
67
+
68
+
69
+ def __getitem__(self, prop):
70
+ """
71
+ Get the value of a property.
72
+
73
+ Args:
74
+ prop: The name of the property.
75
+
76
+ Returns:
77
+ The value of the property, or None if the property does not exist.
78
+ """
79
+ return self.__dict__.get(prop, None)
80
+
81
+ def __str__(self) -> str:
82
+ """
83
+ Return a string representation of all properties.
84
+
85
+ Returns:
86
+ A string representation of all properties with their status (Satisfied, Violated, or N/A).
87
+ """
88
+ properties = [
89
+ f"{key}: {'Satisfied' if value is True else 'Violated' if value is False else 'N/A'}"
90
+ for key, value in self.__dict__.items()
91
+ ]
92
+ return "\n".join(properties)
@@ -0,0 +1,8 @@
1
+ from pref_voting.scoring_methods import *
2
+ from pref_voting.iterative_methods import *
3
+ from pref_voting.c1_methods import *
4
+ from pref_voting.margin_based_methods import *
5
+ from pref_voting.combined_methods import *
6
+ from pref_voting.other_methods import *
7
+ from pref_voting.stochastic_methods import *
8
+
@@ -0,0 +1,136 @@
1
+ '''
2
+ File: voting_methods_registry.py
3
+ Author: Wes Holliday (wesholliday@berkeley.edu) and Eric Pacuit (epacuit@umd.edu)
4
+ Date: April 29, 2024
5
+
6
+ '''
7
+
8
+ from pref_voting.voting_method import VotingMethod
9
+ import inspect
10
+
11
+ class VotingMethodRegistry:
12
+ def __init__(self):
13
+ self.methods = {}
14
+
15
+ def register(self,
16
+ method,
17
+ name,
18
+ method_type,
19
+ file_location,
20
+ election_types,
21
+ properties):
22
+ self.methods[name] = {
23
+ "method": method,
24
+ "method_type": method_type,
25
+ "file_location": file_location,
26
+ "election_types": election_types,
27
+ "properties": properties
28
+ }
29
+
30
+ def discover_methods(self, module):
31
+ """Discovers and registers all VotingMethod instances in the given module."""
32
+
33
+ fname_to_method_type = {
34
+ "scoring_methods.py": "Scoring Rule",
35
+ "iterative_methods.py": "Iterative Method",
36
+ "c1_methods.py": "C1 Method",
37
+ "margin_based_methods.py": "Margin Based Method",
38
+ "other_methods.py": "Other Method",
39
+ "combined_methods.py": "Combined Method",
40
+ }
41
+ for name, obj in inspect.getmembers(module, lambda member: isinstance(member, VotingMethod)):
42
+ if hasattr(obj, 'name') and hasattr(obj, 'properties'):
43
+
44
+ if getattr(obj, 'skip_registration', False):
45
+ continue
46
+
47
+ self.register(obj,
48
+ obj.name,
49
+ fname_to_method_type[module.__file__.split("/")[-1]],
50
+ module.__file__.split("/")[-1],
51
+ getattr(obj, 'input_types', []),
52
+ getattr(obj, 'properties', {}))
53
+
54
+ def list_methods(self):
55
+ return list(self.methods.keys())
56
+
57
+ def display_methods(self):
58
+ for name, details in self.methods.items():
59
+ print(f"{name} ({details['method_type']})")
60
+ print(f"Satisfied properties: {details['properties'].satisfied() if details['properties'] is not None else 'N/A'}")
61
+ print(f"Violated properties: {details['properties'].violated() if details['properties'] is not None else 'N/A'}")
62
+ print()
63
+
64
+ def get_methods(self, method_type):
65
+ return [self.methods[vm]['method'] for vm in self.methods if self.methods[vm]['method_type'] == method_type]
66
+
67
+ def filter(self,
68
+ satisfies=None,
69
+ violates=None,
70
+ unknown=None,
71
+ election_types=None):
72
+
73
+ if satisfies is None:
74
+ satisfies = []
75
+ if violates is None:
76
+ violates = []
77
+ if unknown is None:
78
+ unknown = []
79
+ if election_types is None:
80
+ election_types = []
81
+
82
+ found_methods = []
83
+ for name, method_info in self.methods.items():
84
+ properties = method_info['properties']
85
+ method_election_types = method_info['election_types']
86
+
87
+ # Check if the method satisfies the required properties
88
+ if not all(getattr(properties, prop, None) is True for prop in satisfies):
89
+ continue
90
+
91
+ # Check if the method violates the required properties
92
+ if not all(getattr(properties, prop, None) is False for prop in violates):
93
+ continue
94
+
95
+ # Check for unknown properties
96
+ if not all(hasattr(properties, prop) and getattr(properties, prop, None) is None for prop in unknown):
97
+ continue
98
+
99
+ # Check if the method supports all required election types
100
+ if not all(et in method_election_types for et in election_types):
101
+ continue
102
+
103
+ # If all conditions are met, add to results
104
+ found_methods.append(method_info['method'])
105
+
106
+ return found_methods
107
+
108
+ def method_type(self, method_name):
109
+ return self.methods[method_name]['method_type']
110
+ def file_location(self, method_name):
111
+ return self.methods[method_name]['file_location']
112
+
113
+ def __len__(self):
114
+ return len(self.methods)
115
+
116
+ def __iter__(self):
117
+ self._iter = iter(method_details['method'] for method_details in self.methods.values())
118
+ return self
119
+
120
+ def __next__(self):
121
+ return next(self._iter)
122
+
123
+ voting_methods = VotingMethodRegistry()
124
+
125
+ import pref_voting.scoring_methods
126
+ voting_methods.discover_methods(pref_voting.scoring_methods)
127
+ import pref_voting.iterative_methods
128
+ voting_methods.discover_methods(pref_voting.iterative_methods)
129
+ import pref_voting.c1_methods
130
+ voting_methods.discover_methods(pref_voting.c1_methods)
131
+ import pref_voting.margin_based_methods
132
+ voting_methods.discover_methods(pref_voting.margin_based_methods)
133
+ import pref_voting.combined_methods
134
+ voting_methods.discover_methods(pref_voting.combined_methods)
135
+ import pref_voting.other_methods
136
+ voting_methods.discover_methods(pref_voting.other_methods)