Skip to content

Commit b0be0c8

Browse files
committed
Fix fair share
1 parent 3e19e59 commit b0be0c8

1 file changed

Lines changed: 51 additions & 6 deletions

File tree

pabutools/analysis/fairshare.py

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1+
from pulp import LpProblem, LpMaximize, LpBinary, LpVariable, lpSum, PULP_CBC_CMD, value
2+
13
from pabutools.election import Instance, AbstractApprovalProfile
24
from pabutools.fractions import frac
35
from pabutools.rules import BudgetAllocation
46
from pabutools.utils import Numeric
57

68

7-
def average_normalised_distance_to_fair_share(instance: Instance, profile: AbstractApprovalProfile, budget_allocation: BudgetAllocation) -> Numeric:
9+
def average_distance_to_fair_share(instance: Instance, profile: AbstractApprovalProfile, budget_allocation: BudgetAllocation) -> Numeric:
810
"""
9-
Returns the average normalised distance to fair share of the given budget allocation. The distance to fair
11+
Returns the average distance to fair share of the given budget allocation. The distance to fair
1012
share for a given ballot is defined as the absolute value of `fair share of the ballot - share of the ballot`.
11-
This is normalised by dividing by the fair share of the ballot. This value is averaged up for all ballots in the
12-
profile and 1 minus the resulting value is returned. This is a measure in which 1 is the best.
13+
This is a measure in which 0 is the best and the lower, the better.
1314
1415
Parameters
1516
----------
@@ -32,9 +33,53 @@ def average_normalised_distance_to_fair_share(instance: Instance, profile: Abstr
3233
for ballot in profile:
3334
ballot_share = sum(project_share[p] for p in ballot if p in budget_allocation)
3435
ballot_fairshare = min(sum(project_share[p] for p in ballot), frac(instance.budget_limit, profile.num_ballots()))
35-
d += frac(abs(ballot_share - ballot_fairshare), ballot_fairshare) * profile.multiplicity(ballot)
36+
d += abs(ballot_share - ballot_fairshare) * profile.multiplicity(ballot)
37+
38+
return frac(d, profile.num_ballots())
39+
40+
41+
def min_distance_to_fair_share(instance: Instance, profile: AbstractApprovalProfile) -> Numeric:
42+
"""
43+
Returns the minimum achievable distance to fair share for the given instance and profile. The distance to fair
44+
share for a given ballot is defined as the absolute value of `fair share of the ballot - share of the ballot`.
45+
This is a measure in which 0 is the best and the lower the better.
46+
47+
Parameters
48+
----------
49+
instance : :py:class:`~pabutools.election.instance.Instance`
50+
The instance.
51+
profile : :py:class:`~pabutools.election.profile.profile.AbstractProfile`
52+
The profile.
53+
54+
Returns
55+
-------
56+
Numeric
57+
The average normalised distance to fair share
58+
"""
59+
mip_model = LpProblem("MaxBudgetAllocationScore", LpMaximize)
60+
61+
p_vars = {p: LpVariable(f"p_{p}", cat=LpBinary) for p in instance}
62+
share_vars = {i: LpVariable(f"bs_{i}") for i, b in enumerate(profile)}
63+
share_abs_vars = {i: LpVariable(f"bsabs_{i}") for i, b in enumerate(profile)}
64+
65+
mip_model += lpSum(share_abs_vars[i] * profile.multiplicity(b) for i, b in enumerate(profile))
66+
67+
mip_model += lpSum(p_vars[p] * float(p.cost) for p in instance) <= instance.budget_limit
68+
69+
approval_scores = profile.approval_scores()
70+
project_share = {p: frac(p.cost, approval_scores[p]) for p in instance}
71+
72+
for i, ballot in enumerate(profile):
73+
ballot_fairshare = min(sum(project_share[p] for p in ballot), frac(instance.budget_limit, profile.num_ballots()))
74+
75+
mip_model += share_vars[i] == lpSum(p_vars[p] * float(project_share[p]) for p in ballot)
76+
mip_model += share_abs_vars[i] >= share_vars[i] - float(ballot_fairshare)
77+
mip_model += share_abs_vars[i] >= float(ballot_fairshare) - share_vars[i]
78+
79+
mip_model.solve(PULP_CBC_CMD(msg=False))
80+
81+
return value(mip_model.objective)
3682

37-
return 1 - frac(d, profile.num_ballots())
3883

3984
def average_capped_fair_share_ratio(instance: Instance, profile: AbstractApprovalProfile, budget_allocation: BudgetAllocation) -> Numeric:
4085
"""

0 commit comments

Comments
 (0)