1+ from pulp import LpProblem , LpMaximize , LpBinary , LpVariable , lpSum , PULP_CBC_CMD , value
2+
13from pabutools .election import Instance , AbstractApprovalProfile
24from pabutools .fractions import frac
35from pabutools .rules import BudgetAllocation
46from 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
3984def average_capped_fair_share_ratio (instance : Instance , profile : AbstractApprovalProfile , budget_allocation : BudgetAllocation ) -> Numeric :
4085 """
0 commit comments