pref_voting 1.16.31__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pref_voting/__init__.py +1 -0
- pref_voting/analysis.py +496 -0
- pref_voting/axiom.py +38 -0
- pref_voting/axiom_helpers.py +129 -0
- pref_voting/axioms.py +10 -0
- pref_voting/c1_methods.py +963 -0
- pref_voting/combined_methods.py +514 -0
- pref_voting/create_methods.py +128 -0
- pref_voting/data/examples/condorcet_winner/minimal_Anti-Plurality.soc +16 -0
- pref_voting/data/examples/condorcet_winner/minimal_Borda.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_Bracket_Voting.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_Bucklin.soc +19 -0
- pref_voting/data/examples/condorcet_winner/minimal_Coombs.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_Coombs_PUT.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_Coombs_TB.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_Dowdall.soc +19 -0
- pref_voting/data/examples/condorcet_winner/minimal_Instant_Runoff.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_Instant_Runoff_PUT.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_Instant_Runoff_TB.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_Iterated_Removal_Condorcet_Loser.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_Pareto.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_Plurality.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_PluralityWRunoff_PUT.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_Positive-Negative_Voting.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_Simplified_Bucklin.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_Superior_Voting.soc +19 -0
- pref_voting/data/examples/condorcet_winner/minimal_Weighted_Bucklin.soc +19 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Anti-Plurality.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Borda.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Bracket_Voting.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Bucklin.soc +19 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Coombs.soc +21 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Coombs_PUT.soc +21 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Coombs_TB.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Dowdall.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Instant_Runoff.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Instant_Runoff_PUT.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Instant_Runoff_TB.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Plurality.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_PluralityWRunoff_PUT.soc +18 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Positive-Negative_Voting.soc +17 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Simplified_Bucklin.soc +20 -0
- pref_voting/data/examples/condorcet_winner/minimal_resolute_Weighted_Bucklin.soc +19 -0
- pref_voting/data/voting_methods_properties.json +414 -0
- pref_voting/data/voting_methods_properties.json.lock +0 -0
- pref_voting/dominance_axioms.py +387 -0
- pref_voting/generate_profiles.py +801 -0
- pref_voting/generate_spatial_profiles.py +198 -0
- pref_voting/generate_utility_profiles.py +160 -0
- pref_voting/generate_weighted_majority_graphs.py +506 -0
- pref_voting/grade_methods.py +184 -0
- pref_voting/grade_profiles.py +357 -0
- pref_voting/helper.py +370 -0
- pref_voting/invariance_axioms.py +671 -0
- pref_voting/io/__init__.py +0 -0
- pref_voting/io/readers.py +432 -0
- pref_voting/io/writers.py +256 -0
- pref_voting/iterative_methods.py +2425 -0
- pref_voting/maj_graph_ex1.png +0 -0
- pref_voting/mappings.py +577 -0
- pref_voting/margin_based_methods.py +2345 -0
- pref_voting/monotonicity_axioms.py +872 -0
- pref_voting/num_evaluation_method.py +77 -0
- pref_voting/other_axioms.py +161 -0
- pref_voting/other_methods.py +939 -0
- pref_voting/pairwise_profiles.py +547 -0
- pref_voting/prob_voting_method.py +105 -0
- pref_voting/probabilistic_methods.py +287 -0
- pref_voting/profiles.py +856 -0
- pref_voting/profiles_with_ties.py +1069 -0
- pref_voting/rankings.py +466 -0
- pref_voting/scoring_methods.py +481 -0
- pref_voting/social_welfare_function.py +59 -0
- pref_voting/social_welfare_functions.py +7 -0
- pref_voting/spatial_profiles.py +448 -0
- pref_voting/stochastic_methods.py +99 -0
- pref_voting/strategic_axioms.py +1394 -0
- pref_voting/swf_axioms.py +173 -0
- pref_voting/utility_functions.py +102 -0
- pref_voting/utility_methods.py +178 -0
- pref_voting/utility_profiles.py +333 -0
- pref_voting/variable_candidate_axioms.py +640 -0
- pref_voting/variable_voter_axioms.py +3747 -0
- pref_voting/voting_method.py +355 -0
- pref_voting/voting_method_properties.py +92 -0
- pref_voting/voting_methods.py +8 -0
- pref_voting/voting_methods_registry.py +136 -0
- pref_voting/weighted_majority_graphs.py +1539 -0
- pref_voting-1.16.31.dist-info/METADATA +208 -0
- pref_voting-1.16.31.dist-info/RECORD +92 -0
- pref_voting-1.16.31.dist-info/WHEEL +4 -0
- pref_voting-1.16.31.dist-info/licenses/LICENSE.txt +21 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
'''
|
2
|
+
File: num_evaluation_method.py
|
3
|
+
Author: Wes Holliday (wesholliday@berkeley.edu) and Eric Pacuit (epacuit@umd.edu)
|
4
|
+
Date: June 2, 2024
|
5
|
+
|
6
|
+
The NumEvaluationMethod class and helper functions for numerical evaluation methods.
|
7
|
+
'''
|
8
|
+
|
9
|
+
import functools
|
10
|
+
import numpy as np
|
11
|
+
from pref_voting.mappings import Utility
|
12
|
+
from pref_voting.rankings import Ranking
|
13
|
+
|
14
|
+
class NumEvaluationMethod(object):
|
15
|
+
"""
|
16
|
+
A class to add functionality to numerical evaluation methods
|
17
|
+
|
18
|
+
Args:
|
19
|
+
nem (function): An implementation of a numerical evaluation method. The function should accept any type of profile, and a keyword parameter ``curr_cands`` to find the numerical evaluation after restricting to ``curr_cands``.
|
20
|
+
name (string): The human-readable name of the numerical evaluation function.
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
A utility function (Utility) that represents the numerical evaluation of each candidate.
|
24
|
+
|
25
|
+
"""
|
26
|
+
def __init__(self, nem, name = None):
|
27
|
+
|
28
|
+
self.nem = nem
|
29
|
+
self.name = name
|
30
|
+
functools.update_wrapper(self, nem)
|
31
|
+
|
32
|
+
def __call__(self, edata, curr_cands = None, **kwargs):
|
33
|
+
|
34
|
+
if (curr_cands is not None and len(curr_cands) == 0) or len(edata.candidates) == 0:
|
35
|
+
return Utility({})
|
36
|
+
return self.nem(edata, curr_cands = curr_cands, **kwargs)
|
37
|
+
|
38
|
+
def ranking(self, edata, curr_cands = None, **kwargs):
|
39
|
+
"""
|
40
|
+
Return the ranking generated by the numerical evaluation.
|
41
|
+
"""
|
42
|
+
|
43
|
+
if (curr_cands is not None and len(curr_cands) == 0) or len(edata.candidates) == 0:
|
44
|
+
return Ranking({})
|
45
|
+
ev = self.nem(edata, curr_cands = curr_cands, **kwargs)
|
46
|
+
return ev.ranking()
|
47
|
+
|
48
|
+
def display(self, edata, curr_cands = None, cmap = None, **kwargs):
|
49
|
+
"""
|
50
|
+
Display the winning set of candidates.
|
51
|
+
"""
|
52
|
+
|
53
|
+
cmap = cmap if cmap is not None else edata.cmap
|
54
|
+
|
55
|
+
ev = self.__call__(edata, curr_cands = curr_cands, **kwargs)
|
56
|
+
|
57
|
+
if ev is None: # some voting methods may return None if, for instance, it is taking long to compute the winner.
|
58
|
+
print(f"{self.name} numerical evaluation is not available")
|
59
|
+
else:
|
60
|
+
w_str = f"{self.name} evaluation is "
|
61
|
+
print(w_str + str(ev))
|
62
|
+
|
63
|
+
def set_name(self, new_name):
|
64
|
+
"""Set the name of the social welfare function."""
|
65
|
+
|
66
|
+
self.name = new_name
|
67
|
+
|
68
|
+
def __str__(self):
|
69
|
+
return f"{self.name}"
|
70
|
+
|
71
|
+
def nem(name = None):
|
72
|
+
"""
|
73
|
+
A decorator used when creating a numerical evaluation method.
|
74
|
+
"""
|
75
|
+
def wrapper(f):
|
76
|
+
return NumEvaluationMethod(f, name=name)
|
77
|
+
return wrapper
|
@@ -0,0 +1,161 @@
|
|
1
|
+
"""
|
2
|
+
File: other_axioms.py
|
3
|
+
Date: April 29 2025
|
4
|
+
Authors: Wes Holliday (wesholliday@berkeley.edu) and Eric Pacuit (epacuit@umd.edu)
|
5
|
+
|
6
|
+
Other axioms
|
7
|
+
---------------------
|
8
|
+
"""
|
9
|
+
|
10
|
+
from itertools import repeat
|
11
|
+
import numpy as np
|
12
|
+
|
13
|
+
from pref_voting.axiom import Axiom
|
14
|
+
from pref_voting.profiles import Profile
|
15
|
+
from pref_voting.profiles_with_ties import ProfileWithTies
|
16
|
+
from pref_voting.rankings import Ranking
|
17
|
+
|
18
|
+
|
19
|
+
def _reverse_ranking(ballot, all_cands):
|
20
|
+
"""
|
21
|
+
Reverse a single ballot, treating *unranked* candidates as
|
22
|
+
tied for last before the reversal.
|
23
|
+
|
24
|
+
Parameters
|
25
|
+
----------
|
26
|
+
ballot : tuple | Ranking
|
27
|
+
• tuple – a strict linear order
|
28
|
+
• Ranking – weak order, possibly incomplete
|
29
|
+
all_cands : list | set
|
30
|
+
The full candidate set of the profile.
|
31
|
+
"""
|
32
|
+
if isinstance(ballot, tuple):
|
33
|
+
return tuple(reversed(ballot))
|
34
|
+
|
35
|
+
if isinstance(ballot, Ranking):
|
36
|
+
full_rmap = ballot.rmap.copy()
|
37
|
+
if full_rmap:
|
38
|
+
last_rank = max(full_rmap.values()) + 1
|
39
|
+
else:
|
40
|
+
last_rank = 1
|
41
|
+
for c in all_cands:
|
42
|
+
if c not in full_rmap:
|
43
|
+
full_rmap[c] = last_rank
|
44
|
+
|
45
|
+
max_rank = max(full_rmap.values())
|
46
|
+
rev_rmap = {c: max_rank + 1 - k for c, k in full_rmap.items()}
|
47
|
+
return Ranking(rev_rmap)
|
48
|
+
|
49
|
+
def _reverse_profile(P):
|
50
|
+
"""
|
51
|
+
Build the reversed profile ``Pᵣ``.
|
52
|
+
"""
|
53
|
+
all_cands = P.candidates
|
54
|
+
rev_ballots = [_reverse_ranking(b, all_cands) for b in P.rankings]
|
55
|
+
|
56
|
+
if isinstance(P, Profile):
|
57
|
+
return Profile(rev_ballots)
|
58
|
+
|
59
|
+
if isinstance(P, ProfileWithTies):
|
60
|
+
P_r = ProfileWithTies(rev_ballots, candidates=all_cands)
|
61
|
+
if P.using_extended_strict_preference:
|
62
|
+
P_r.use_extended_strict_preference()
|
63
|
+
return P_r
|
64
|
+
|
65
|
+
def _reverse_margin_graph(mg):
|
66
|
+
"""
|
67
|
+
Return the *edge-reversed* margin graph.
|
68
|
+
|
69
|
+
All positive-margin edges (u, v, w) become (v, u, w); weights are preserved.
|
70
|
+
"""
|
71
|
+
rev_edges = [(v, u, w) for (u, v, w) in mg.edges]
|
72
|
+
return MarginGraph(mg.candidates[:], rev_edges, cmap=mg.cmap)
|
73
|
+
|
74
|
+
def has_reversal_symmetry_violation(edata, vm, verbose=False):
|
75
|
+
"""
|
76
|
+
Returns True iff ``vm`` violates reversal symmetry on *edata*.
|
77
|
+
|
78
|
+
Reversal Symmetry states that if x is a **unique** winner in ``edata``,
|
79
|
+
then x should not be among the winners in the reversal of ``edata``.
|
80
|
+
"""
|
81
|
+
|
82
|
+
if len(edata.candidates) <= 1:
|
83
|
+
return False
|
84
|
+
|
85
|
+
if isinstance(edata, MarginGraph):
|
86
|
+
mg = edata
|
87
|
+
winners = vm(mg)
|
88
|
+
if len(winners) != 1:
|
89
|
+
return False
|
90
|
+
|
91
|
+
x = winners[0]
|
92
|
+
mg_r = _reverse_margin_graph(mg)
|
93
|
+
rev_winners = vm(mg_r)
|
94
|
+
|
95
|
+
if x in rev_winners:
|
96
|
+
if verbose:
|
97
|
+
print(f"Reversal-symmetry violation for {vm.name} on a MarginGraph")
|
98
|
+
print(f"Unique winner {x} also wins after edge reversal.")
|
99
|
+
print("\nOriginal margin graph:")
|
100
|
+
mg.display()
|
101
|
+
print(mg.description())
|
102
|
+
vm.display(mg)
|
103
|
+
print('\nReversed margin graph:')
|
104
|
+
mg_r.display()
|
105
|
+
print(mg_r.description())
|
106
|
+
vm.display(mg_r)
|
107
|
+
|
108
|
+
return True
|
109
|
+
return False
|
110
|
+
|
111
|
+
winners = vm(edata)
|
112
|
+
if isinstance(winners, np.ndarray):
|
113
|
+
winners = winners.tolist()
|
114
|
+
if len(winners) != 1:
|
115
|
+
return False
|
116
|
+
|
117
|
+
x = winners[0]
|
118
|
+
P_r = _reverse_profile(edata)
|
119
|
+
rev_winners = vm(P_r)
|
120
|
+
if isinstance(rev_winners, np.ndarray):
|
121
|
+
rev_winners = rev_winners.tolist()
|
122
|
+
|
123
|
+
if x in rev_winners:
|
124
|
+
if verbose:
|
125
|
+
print(f"Reversal-symmetry violation for {vm.name}")
|
126
|
+
print(f"Unique winner {x} also wins after reversal.")
|
127
|
+
print("\nOriginal profile:")
|
128
|
+
edata.display()
|
129
|
+
print(edata.description())
|
130
|
+
vm.display(edata)
|
131
|
+
print("\nReversed profile:")
|
132
|
+
P_r.display()
|
133
|
+
print(P_r.description())
|
134
|
+
vm.display(P_r)
|
135
|
+
return True
|
136
|
+
return False
|
137
|
+
|
138
|
+
|
139
|
+
def find_all_reversal_symmetry_violations(edata, vm, verbose=False):
|
140
|
+
"""
|
141
|
+
Returns a one-item list [(unique_winner, winners_after_reversal)] describing the violation on *edata*, or [].
|
142
|
+
"""
|
143
|
+
if not has_reversal_symmetry_violation(edata, vm, verbose):
|
144
|
+
return []
|
145
|
+
|
146
|
+
winners = vm(edata)
|
147
|
+
winners = winners.tolist() if isinstance(winners, np.ndarray) else winners
|
148
|
+
if isinstance(edata, MarginGraph):
|
149
|
+
rev_winners = vm(_reverse_margin_graph(edata))
|
150
|
+
else:
|
151
|
+
rev_winners = vm(_reverse_profile(edata))
|
152
|
+
|
153
|
+
rev_winners = rev_winners.tolist() if isinstance(rev_winners, np.ndarray) else rev_winners
|
154
|
+
return [(winners, rev_winners)]
|
155
|
+
|
156
|
+
|
157
|
+
reversal_symmetry = Axiom(
|
158
|
+
"Reversal Symmetry",
|
159
|
+
has_violation=has_reversal_symmetry_violation,
|
160
|
+
find_all_violations=find_all_reversal_symmetry_violations,
|
161
|
+
)
|