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,173 @@
1
+ """
2
+ File: swf_axioms.py
3
+ Author: Wesley H. Holliday (wesholliday@berkeley.edu) and Eric Pacuit (epacuit@umd.edu)
4
+ Date: April 29, 2024
5
+
6
+ SWF axioms
7
+ """
8
+
9
+ from pref_voting.axiom import Axiom
10
+ from pref_voting.axiom_helpers import *
11
+ from itertools import permutations
12
+ from pref_voting.helper import weak_orders
13
+
14
+ def has_pareto_ranking_violation(prof,swf,verbose=False, strong_Pareto=False):
15
+
16
+ """Returns True if all voters rank x above y in prof, but the SWF does not rank x above y.
17
+
18
+ If verbose is True, prints the profile and the violation.
19
+
20
+ If strong_Pareto is True, then a violation occurs when all voters weakly prefer x to y, some voter strictly prefers x to y, but the SWF does not rank x above y.
21
+
22
+ Args:
23
+ prof: a Profile or ProfileWithTies
24
+ swf (SocialWelfareFunction): An SWF to test.
25
+ verbose (bool, default=False): If a violation is found, display the violation.
26
+
27
+ Returns:
28
+ Result of the test (bool): Returns True if there is a violation and False otherwise.
29
+
30
+ """
31
+ social_ranking = swf(prof)
32
+
33
+ for x in prof.candidates:
34
+ for y in prof.candidates:
35
+ if not social_ranking.extended_strict_pref(x,y):
36
+ if (strong_Pareto == False and prof.support(x,y)==prof.num_voters) or (strong_Pareto == True and prof.support(x,y)> 0 and prof.support(y,x)==0):
37
+ if verbose:
38
+ print(f"Pareto ranking violation by {swf}:")
39
+ prof.display()
40
+ print(prof.description())
41
+ swf.display(prof)
42
+ print(f"Candidate {x} Pareto dominates {y} but is not ranked above {y} by {swf}.")
43
+ print()
44
+ return True
45
+ return False
46
+
47
+ def find_all_pareto_ranking_violations(prof,swf,verbose=False, strong_Pareto=False):
48
+
49
+ """Returns a list of all pairs of candidates for which the SWF violates the Pareto ranking axiom.
50
+
51
+ If verbose is True, prints the profile and the violation.
52
+
53
+ If strong_Pareto is True, then a violation occurs when all voters weakly prefer x to y, some voter strictly prefers x to y, but the SWF does not rank x above y.
54
+
55
+ Args:
56
+ prof: a Profile or ProfileWithTies
57
+ swf (SocialWelfareFunction): An SWF to test.
58
+ verbose (bool, default=False): If a violation is found, display the violation.
59
+
60
+ Returns:
61
+ List of violations (list): List of all pairs of candidates for which there is a violation.
62
+
63
+ """
64
+ social_ranking = swf(prof)
65
+
66
+ violations = []
67
+
68
+ for x in prof.candidates:
69
+ for y in prof.candidates:
70
+ if not social_ranking.extended_strict_pref(x,y):
71
+ if (strong_Pareto == False and prof.support(x,y)==prof.num_voters) or (strong_Pareto == True and prof.support(x,y)> 0 and prof.support(y,x)==0):
72
+ if verbose:
73
+ print(f"Pareto ranking violation by {swf}:")
74
+ prof.display()
75
+ print(prof.description())
76
+ swf.display(prof)
77
+ print(f"Candidate {x} Pareto dominates {y} but is not ranked above {y} by {swf}.")
78
+ print()
79
+ violations.append((x,y))
80
+
81
+ return violations
82
+
83
+ pareto_ranking = Axiom(
84
+ "Pareto Ranking",
85
+ has_violation = has_pareto_ranking_violation,
86
+ find_all_violations = find_all_pareto_ranking_violations,
87
+ )
88
+
89
+ def has_core_support_violation(prof,swf,verbose=False):
90
+ """Returns True if the swf violates the "core support criterion" of https://arxiv.org/abs/2308.08430 for the given profile, False otherwise. If verbose is True, prints the profile and the core support violation.
91
+
92
+ Args:
93
+ prof: a Profile.
94
+ swf (SocialWelfareFunction): An SWF to test.
95
+ verbose (bool, default=False): If a violation is found, display the violation.
96
+
97
+ Returns:
98
+ Result of the test (bool): Returns True if there is a violation and False otherwise.
99
+ """
100
+
101
+ social_ranking = swf(prof)
102
+
103
+ for x in prof.candidates:
104
+ for y in prof.candidates:
105
+ if social_ranking.extended_strict_pref(x,y):
106
+
107
+ maj_cand_for_y = [c for c in prof.candidates if social_ranking.extended_weak_pref(c,y)]
108
+
109
+ if isinstance(prof,Profile):
110
+ core_support_for_x_vs_y = [r for r in prof.rankings if r.index(x) == 0 or r.index(x) < min([r.index(c) for c in maj_cand_for_y if c != x])]
111
+ core_support_for_y_vs_y = [r for r in prof.rankings if r.index(y) == 0 or r.index(y) < min([r.index(c) for c in maj_cand_for_y if c != y])]
112
+ core_support = core_support_for_x_vs_y + core_support_for_y_vs_y
113
+ restricted_prof = Profile(core_support)
114
+
115
+ if not restricted_prof.majority_prefers(x,y):
116
+ if verbose:
117
+ print(f"Core support violation by {swf} for {x} relative to {y}:")
118
+ print(prof.anonymize())
119
+ prof.display_margin_graph()
120
+ print("Social ranking:",social_ranking)
121
+ print(f"Major candidates relative to {y}:",maj_cand_for_y)
122
+ print(f"Profile restricted to voters in core support for {x} relative to {y} and for {y} relative to {y}:")
123
+ print(restricted_prof.anonymize())
124
+ restricted_prof.display_margin_graph()
125
+ return True
126
+ return False
127
+
128
+ def find_all_core_support_violations(prof,swf,verbose=False):
129
+ """Returns a list of all pairs of candidates for which the swf violates the "core support criterion" of https://arxiv.org/abs/2308.08430 for the given profile. If verbose is True, prints the profile and the core support violation.
130
+
131
+ Args:
132
+ prof: a Profile.
133
+ swf (SocialWelfareFunction): An SWF to test.
134
+ verbose (bool, default=False): If a violation is found, display the violation.
135
+
136
+ Returns:
137
+ List of violations (list): List of all pairs of candidates for which there is a violation.
138
+ """
139
+
140
+ social_ranking = swf(prof)
141
+
142
+ violations = []
143
+
144
+ for x in prof.candidates:
145
+ for y in prof.candidates:
146
+ if social_ranking.extended_strict_pref(x,y):
147
+
148
+ maj_cand_for_y = [c for c in prof.candidates if social_ranking.extended_weak_pref(c,y)]
149
+
150
+ if isinstance(prof,Profile):
151
+ core_support_for_x_vs_y = [r for r in prof.rankings if r.index(x) == 0 or r.index(x) < min([r.index(c) for c in maj_cand_for_y if c != x])]
152
+ core_support_for_y_vs_y = [r for r in prof.rankings if r.index(y) == 0 or r.index(y) < min([r.index(c) for c in maj_cand_for_y if c != y])]
153
+ core_support = core_support_for_x_vs_y + core_support_for_y_vs_y
154
+ restricted_prof = Profile(core_support)
155
+
156
+ if not restricted_prof.majority_prefers(x,y):
157
+ if verbose:
158
+ print(f"Core support violation by {swf} for {x} relative to {y}:")
159
+ print(prof.anonymize())
160
+ prof.display_margin_graph()
161
+ print("Social ranking:",social_ranking)
162
+ print(f"Major candidates relative to {y}:",maj_cand_for_y)
163
+ print(f"Profile restricted to voters in core support for {x} relative to {y} and for {y} relative to {y}:")
164
+ print(restricted_prof.anonymize())
165
+ restricted_prof.display_margin_graph()
166
+ violations.append((x,y))
167
+ return violations
168
+
169
+ core_support = Axiom(
170
+ "Core Support",
171
+ has_violation = has_core_support_violation,
172
+ find_all_violations = find_all_core_support_violations,
173
+ )
@@ -0,0 +1,102 @@
1
+
2
+ import numpy as np
3
+ from scipy.spatial import distance
4
+ from numba import jit, float32
5
+
6
+ @jit(nopython=True, fastmath=True)
7
+ def mixed_rm_utility(v_pos: float32[:], c_pos: float32[:], beta = 0.5):
8
+ """Based on the Rabinowitz and Macdonald (1989) mixed model described on pages 43-44 of "A Unified Theory of Voting" by S. Merrill III and B. Grofman.
9
+
10
+ beta = 1 is the proximity quadratic utility function
11
+ beta = 0 is the RM directional utility function
12
+
13
+ Args:
14
+ v_pos (numpy array): The position(s) of the voter.
15
+ c_pos (numpy array): The position(s) of the candidate.
16
+ beta (float): The beta parameter of the mixed model.
17
+
18
+ Returns:
19
+ float: The utility of the candidate to the voter.
20
+ """
21
+ return 2 * (1-beta) * np.dot(v_pos, c_pos) - beta * np.linalg.norm(v_pos - c_pos) ** 2
22
+
23
+ def rm_utility(v_pos: float32[:], c_pos: float32[:]):
24
+ """Based on the Rabinowitz and Macdonald (1989) pure directional model. See "A Unified Theory of Voting" by S. Merrill III and B. Grofman, pg. 31.
25
+
26
+ Args:
27
+ v_pos (numpy array): The position(s) of the voter.
28
+ c_pos (numpy array): The position(s) of the candidate.
29
+
30
+ Returns:
31
+ float: The utility of the candidate to the voter.
32
+ """
33
+
34
+ return np.dot(v_pos, c_pos)
35
+
36
+ def linear_utility(v_pos: float32[:], c_pos: float32[:]):
37
+ """
38
+ The utility of the candidate for the voter is negative of the Euclidean distance between the positions.
39
+
40
+ Args:
41
+ v_pos (numpy array): The position(s) of the voter.
42
+ c_pos (numpy array): The position(s) of the candidate.
43
+ Returns:
44
+ float: The utility of the candidate to the voter.
45
+ """
46
+ return -np.linalg.norm(v_pos - c_pos)
47
+
48
+ def quadratic_utility(v_pos: float32[:], c_pos: float32[:]):
49
+ """
50
+ The utility of the candidate for the voter is negative of the squared Euclidean distance between the positions.
51
+
52
+ Args:
53
+ v_pos (numpy array): The position(s) of the voter.
54
+ c_pos (numpy array): The position(s) of the candidate.
55
+ Returns:
56
+ float: The utility of the candidate to the voter.
57
+ """
58
+ return -np.linalg.norm(v_pos - c_pos)**2
59
+
60
+
61
+ def city_block_utility(v_pos: float32[:], c_pos: float32[:]):
62
+ """
63
+ The utility of the candidate for the voter is the negative of the city-block distance between the positions (also known as the Manhattan distance).
64
+
65
+ Args:
66
+ v_pos (numpy array): The position(s) of the voter.
67
+ c_pos (numpy array): The position(s) of the candidate.
68
+ Returns:
69
+ float: The utility of the candidate to the voter.
70
+ """
71
+ return -distance.cityblock(v_pos, c_pos)
72
+
73
+ @jit(nopython=True, fastmath=True)
74
+ def shepsle_utility(v_pos: float32[:], c_pos: float32[:], kappa: float32 = 1):
75
+ """
76
+ The Shepsle utility function from "The Strategy of Ambiguity: Uncertainty and Electoral Competition" by Kenneth A. Shepsle, American Political Science Review, 1972, vol. 66, issue 2, pp. 555-568. For a justification of this utility function, see Appendix B from *Making Multicandidate Elections More Democratic* (https://doi.org/10.1515/9781400859504.114) by S. Merrill III.
77
+
78
+ Args:
79
+ v_pos (numpy array): The position(s) of the voter.
80
+ c_pos (numpy array): The position(s) of the candidate.
81
+ kappa (float): A parameter that determines the steepness of the utility function.
82
+ Returns:
83
+ float: The utility of the candidate to the voter.
84
+ """
85
+ d = np.linalg.norm(v_pos - c_pos)
86
+ return np.exp((kappa**2 * -d**2) / 2)
87
+
88
+
89
+ @jit(nopython=True, fastmath=True)
90
+ def matthews_utility(v_pos: float32[:], c_pos: float32[:]):
91
+ """
92
+ Based on the Matthews directional model. See "A Unified Theory of Voting" by S. Merrill III and B. Grofman, pg. 26.
93
+
94
+ Args:
95
+ v_pos (numpy array): The position(s) of the voter.
96
+ c_pos (numpy array): The position(s) of the candidate.
97
+ Returns:
98
+ float: The utility of the candidate to the voter.
99
+
100
+ """
101
+ return np.dot(v_pos, c_pos) / (np.linalg.norm(v_pos) * np.linalg.norm(c_pos))
102
+
@@ -0,0 +1,178 @@
1
+ '''
2
+ File: utility_methods.py
3
+ Author: Wes Holliday (wesholliday@berkeley.edu) and Eric Pacuit (epacuit@umd.edu)
4
+ Date: May 26, 2023
5
+
6
+ Implementations of utility methods.
7
+ '''
8
+ from pref_voting.voting_method import vm
9
+ from pref_voting.social_welfare_function import swf
10
+ from pref_voting.rankings import Ranking
11
+ import numpy as np
12
+
13
+ @swf(name="Sum Utilitarian")
14
+ def sum_utilitarian_ranking(uprof, curr_cands = None):
15
+ """Rank the alternatives according to the sum of the utilities.
16
+ Args:
17
+ uprof (Profile): A Profile object.
18
+ curr_cands (list): A list of candidates to restrict the ranking to. If ``None``, then the ranking is over the entire domain of the profile.
19
+ Returns:
20
+ Ranking: A ranking of the candidates in ``curr_cands`` according to sum of the utilities.
21
+ """
22
+
23
+ curr_cands = curr_cands if curr_cands is not None else uprof.domain
24
+
25
+ sums = {c:uprof.util_sum(c) for c in curr_cands}
26
+ sorted_sums = sorted(list(set(sums.values())), reverse=True)
27
+ return Ranking({x: uidx+1 for uidx, u in enumerate(sorted_sums)
28
+ for x in curr_cands if sums[x] == u})
29
+
30
+ @vm(name="Sum Utilitarian")
31
+ def sum_utilitarian(uprof, curr_cands=None):
32
+ """
33
+ Return the winning set of candidates according to sum of the utilities.
34
+ """
35
+ return sorted(sum_utilitarian_ranking(uprof, curr_cands=curr_cands).first())
36
+
37
+
38
+ @swf(name="Relative Utilitarian")
39
+ def relative_utilitarian_ranking(uprof, curr_cands=None):
40
+ """
41
+ Rank the alternatives according to sum of the normalized utilities.
42
+
43
+ Args:
44
+ uprof (Profile): A Profile object.
45
+ curr_cands (list): A list of candidates to restrict the ranking to. If ``None``, then the ranking is over the entire domain of the profile.
46
+ Returns:
47
+ Ranking: A ranking of the candidates in ``curr_cands`` according to sum of the normalized utilities.
48
+
49
+ .. note::
50
+ Before restricting to curr_cands, we normalize with respect to *all* alternatives in the domain.
51
+ """
52
+
53
+ curr_cands = curr_cands if curr_cands is not None else uprof.domain
54
+
55
+ rel_utils = [u.normalize_by_range() for u in uprof.utilities]
56
+ sums = {c:np.sum([u(c) for u in rel_utils if u(c) is not None]) for c in curr_cands}
57
+ sorted_sums = sorted(list(set(sums.values())), reverse=True)
58
+ return Ranking({x: uidx+1 for uidx, u in enumerate(sorted_sums)
59
+ for x in curr_cands if sums[x] == u})
60
+
61
+ @vm(name="Relative Utilitarian")
62
+ def relative_utilitarian(uprof, curr_cands=None):
63
+ """
64
+ Return the winning set of candidates according to sum of the normalized utilities.
65
+ """
66
+ return sorted(relative_utilitarian_ranking(uprof, curr_cands=curr_cands).first())
67
+
68
+
69
+ @swf(name="Maximin")
70
+ def maximin_ranking(uprof, curr_cands=None):
71
+ """
72
+ Rank the alternatives according to the minimum utility.
73
+
74
+ Args:
75
+ uprof (Profile): A Profile object.
76
+ curr_cands (list): A list of candidates to restrict the ranking to. If ``None``, then the ranking is over the entire domain of the profile.
77
+ Returns:
78
+ Ranking: A ranking of the candidates in ``curr_cands`` according to minimum utility.
79
+ """
80
+
81
+ curr_cands = curr_cands if curr_cands is not None else uprof.domain
82
+
83
+ min_utils = {x:uprof.util_min(x) for x in curr_cands}
84
+ sorted_min_utils = sorted(list(set(min_utils.values())), reverse=True)
85
+ return Ranking({x: midx+1
86
+ for midx, m in enumerate(sorted_min_utils)
87
+ for x in min_utils.keys() if min_utils[x] == m})
88
+
89
+ @vm(name="Maximin")
90
+ def maximin(uprof, curr_cands=None):
91
+ """
92
+ Return the winning set of candidates according to minimum utility.
93
+ """
94
+ return sorted(maximin_ranking(uprof, curr_cands=curr_cands).first())
95
+
96
+
97
+ @swf(name="Lexicographic Maximin")
98
+ def lexicographic_maximin_ranking(uprof, curr_cands=None):
99
+ """
100
+ Rank the alternatives according to the lexicographic maximin ranking. The lexicographic maximin ranking is the ranking that ranks alternatives according to the minimum utility, and then breaks ties by ranking alternatives according to the second minimum utility, and so on.
101
+
102
+ Args:
103
+ uprof (Profile): A Profile object.
104
+ curr_cands (list): A list of candidates to restrict the ranking to. If ``None``, then the ranking is over the entire domain of the profile.
105
+ Returns:
106
+ Ranking: A ranking of the candidates in ``curr_cands`` according to lexicographic maximin ranking.
107
+ """
108
+
109
+ curr_cands = curr_cands if curr_cands is not None else uprof.domain
110
+
111
+ utils = {x: tuple(sorted([u(x) for u in uprof.utilities if u(x) is not None])) for x in curr_cands}
112
+ assert len(list(set([len(us) for us in utils.values()]))) == 1, "Not all the items have the same number of utilities."
113
+ sorted_utils = sorted(list(set(utils.values())), reverse=True)
114
+ return Ranking({x: idx+1
115
+ for idx, us in enumerate(sorted_utils)
116
+ for x in utils.keys() if utils[x] == us})
117
+
118
+ @vm(name="Lexicographic Maximin")
119
+ def lexicographic_maximin(uprof, curr_cands=None):
120
+ """
121
+ Return the winning set of candidates according to lexicographic maximin ranking.
122
+ """
123
+ return sorted(lexicographic_maximin_ranking(uprof, curr_cands=curr_cands).first())
124
+
125
+
126
+ @swf(name="Nash")
127
+ def nash_ranking(uprof, sq=None, curr_cands=None):
128
+ """
129
+ Rank the alternatives according to the Nash product ranking. Given the status quo ``sq``, the Nash product ranking ranks alternatives according to the product of the utilities of the alternatives minus the utility of the status quo.
130
+
131
+ Args:
132
+ uprof (Profile): A Profile object.
133
+ sq (Candidate): The status quo. If ``None``, then the status quo is the first candidate in the domain of the profile.
134
+ curr_cands (list): A list of candidates to restrict the ranking to. If ``None``, then the ranking is over the entire domain of the profile.
135
+ Returns:
136
+ Ranking: A ranking of the candidates in ``curr_cands`` according to Nash product ranking.
137
+
138
+ """
139
+
140
+ assert sq is None or sq in uprof.domain, f"The status quo {sq} must be in the domain of the profile."
141
+
142
+ curr_cands = curr_cands if curr_cands is not None else uprof.domain
143
+
144
+ sq = curr_cands[0] if sq is None else sq
145
+
146
+ items_to_rank = list(set([x for x in curr_cands if all([u(x) > u(sq)
147
+ for u in uprof.utilities])] + [sq]))
148
+
149
+ nash_utils = {x: np.prod([u(x) - u(sq) for u in uprof.utilities])
150
+ for x in items_to_rank}
151
+ sorted_nash_utils = sorted(list(set(nash_utils.values())), reverse=True)
152
+
153
+ return Ranking({x: nidx+1
154
+ for nidx, n in enumerate(sorted_nash_utils)
155
+ for x in nash_utils.keys() if nash_utils[x] == n})
156
+
157
+ @vm(name="Nash")
158
+ def nash(uprof, sq=None, curr_cands=None):
159
+ """
160
+ Return the winning set of candidates according to Nash product ranking.
161
+ """
162
+ return sorted(nash_ranking(uprof, sq=sq, curr_cands=curr_cands).first())
163
+
164
+ utilitarian_vms = [
165
+ sum_utilitarian,
166
+ relative_utilitarian,
167
+ maximin,
168
+ lexicographic_maximin,
169
+ nash
170
+ ]
171
+
172
+ utilitarian_swfs = [
173
+ sum_utilitarian_ranking,
174
+ relative_utilitarian_ranking,
175
+ maximin_ranking,
176
+ lexicographic_maximin_ranking,
177
+ # nash_ranking
178
+ ]