Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
# Conflicts:
#	pabutools/analysis/cohesiveness.py
#	pabutools/analysis/justifiedrepresentation.py
  • Loading branch information
Simon-Rey committed Oct 24, 2023
2 parents eb98df4 + 9f8b85c commit 49ee6dd
Show file tree
Hide file tree
Showing 4 changed files with 352 additions and 305 deletions.
124 changes: 75 additions & 49 deletions analysis/mes_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import pabutools.fractions

from pabutools.fractions import frac


def equal_shares_fast(instance, profile, sat_class):
projects = set(instance)
Expand Down Expand Up @@ -52,13 +54,13 @@ def equal_shares_iterated_fast(instance, profile, sat_class):
return equal_shares(voters, projects, costs, utilities, instance.budget_limit)


def equal_shares_iterated_fast_approval(instance, profile):
def equal_shares_iterated_fast_approval(instance, profile, budget_multiplier=1):
projects = set(instance)
costs = {p: p.cost for p in projects}
voters = range(len(profile))
approvers = {p: [i for i in voters if p in profile[i]] for p in instance}
return equal_shares_approval(
voters, projects, costs, approvers, instance.budget_limit
voters, projects, costs, approvers, instance.budget_limit, budget_multiplier
)


Expand All @@ -72,28 +74,32 @@ def equal_shares_fast_approval(instance, profile):
)


def equal_shares_fixed_budget(N, C, cost, util, total_utility, approvers, B):
def break_ties(N, C, cost, total_utility, choices):
remaining = choices.copy()
best_cost = min(cost[c] for c in remaining)
remaining = [c for c in remaining if cost[c] == best_cost]
best_count = max(total_utility[c] for c in remaining)
remaining = [c for c in remaining if total_utility[c] == best_count]
return remaining
def equal_shares_fixed_budget(N, C, cost, util, total_utility, approvers, B, verbose=False):
def break_ties(N, C, cost, approvers, choices):
min_proj = None
for p in choices:
if min_proj is None or p.name < min_proj:
min_proj = p
return [min_proj]

budget = {i: B / len(N) for i in N}
budget = {i: frac(B, len(N)) for i in N}
remaining = {} # remaining candidate -> previous effective vote count
for c in C:
if cost[c] > 0 and len(approvers[c]) > 0:
remaining[c] = total_utility[c]
if verbose:
tmp = sorted([(p, a) for p, a in remaining.items()], key=lambda x: x[1])
for p, a in tmp:
print(f"{p} -- {float(a)}")
winners = []
while True:
best = []
best_eff_vote_count = 0
# go through remaining candidates in order of decreasing previous effective vote count
remaining_sorted = sorted(remaining, key=lambda c: remaining[c], reverse=True)
if verbose: print("========================")
for c in remaining_sorted:
# print(f"Considering: {c}")
if verbose: print(f"\tConsidering: {c}")
previous_eff_vote_count = remaining[c]
if previous_eff_vote_count < best_eff_vote_count:
# c cannot be better than the best so far
Expand All @@ -104,21 +110,22 @@ def break_ties(N, C, cost, total_utility, choices):
del remaining[c]
continue
# calculate the effective vote count of c
approvers[c].sort(key=lambda i: budget[i] / util[i][c])
approvers[c].sort(key=lambda i: frac(budget[i], util[i][c]))
paid_so_far = 0
denominator = total_utility[c]
for i in approvers[c]:
# compute payment if remaining approvers pay proportional to their utility
payment_factor = (cost[c] - paid_so_far) / denominator
eff_vote_count = cost[c] / payment_factor
payment_factor = frac(cost[c] - paid_so_far, denominator)
eff_vote_count = frac(cost[c], payment_factor)
if payment_factor * util[i][c] > budget[i]:
# i cannot afford the payment, so pays entire remaining budget
paid_so_far += budget[i]
denominator -= util[i][c]
else:
# i (and all later approvers) can afford the payment; stop here
# print(f"Factor: {payment_factor}")
# print(f"Eff: {eff_vote_count}")
if verbose:
print(f"\t\tFactor: {float(payment_factor)} = ({cost[c]} - {paid_so_far})/{denominator}")
print(f"\t\tEff: {float(eff_vote_count)}")
remaining[c] = eff_vote_count
if eff_vote_count > best_eff_vote_count:
best_eff_vote_count = eff_vote_count
Expand All @@ -140,7 +147,7 @@ def break_ties(N, C, cost, total_utility, choices):
winners.append(best)
del remaining[best]
# charge the approvers of best
payment_factor = cost[best] / best_eff_vote_count
payment_factor = frac(cost[best], best_eff_vote_count)
for i in approvers[best]:
payment = payment_factor * util[i][best]
if budget[i] > payment:
Expand All @@ -156,7 +163,7 @@ def equal_shares(N, C, cost, u, B):
mes = equal_shares_fixed_budget(N, C, cost, u, total_utility, approvers, B)
# add1 completion
# start with integral per-voter budget
budget = int(B / len(N)) * len(N)
budget = frac(B, len(N)) * len(N)
current_cost = sum(cost[c] for c in mes)
while True:
# is current outcome exhaustive?
Expand All @@ -170,9 +177,8 @@ def equal_shares(N, C, cost, u, B):
break
# would the next highest budget work?
next_budget = budget + len(N)
# print(f"budget: {next_budget}")
next_mes = equal_shares_fixed_budget(
N, C, cost, u, total_utility, approvers, next_budget
N, C, cost, u, total_utility, approvers, next_budget, verbose=frac(next_budget, len(N)) > 301
)
# print(f"Next: {next_mes}")
current_cost = sum(cost[c] for c in next_mes)
Expand All @@ -186,11 +192,11 @@ def equal_shares(N, C, cost, u, B):
return mes


def equal_shares_approval(N, C, cost, approvers, B):
def equal_shares_approval(N, C, cost, approvers, B, budget_multiplier = 1):
mes = equal_shares_fixed_budget_approval(N, C, cost, approvers, B)
# add1 completion
# start with integral per-voter budget
budget = int(B / len(N)) * len(N)
budget = frac(B, len(N)) * len(N)
current_cost = sum(cost[c] for c in mes)
while True:
# is current outcome exhaustive?
Expand All @@ -203,9 +209,9 @@ def equal_shares_approval(N, C, cost, approvers, B):
if is_exhaustive:
break
# would the next highest budget work?
next_budget = budget + len(N)
next_budget = budget + len(N) * budget_multiplier
next_mes = equal_shares_fixed_budget_approval(
N, C, cost, approvers, next_budget
N, C, cost, approvers, next_budget, verbose=False
)
current_cost = sum(cost[c] for c in next_mes)
if current_cost <= B:
Expand All @@ -218,27 +224,38 @@ def equal_shares_approval(N, C, cost, approvers, B):
return mes


def equal_shares_fixed_budget_approval(N, C, cost, approvers, B):
def equal_shares_fixed_budget_approval(N, C, cost, approvers, B, verbose=False):
def break_ties(N, C, cost, approvers, choices):
remaining = choices.copy()
best_cost = min(cost[c] for c in remaining)
remaining = [c for c in remaining if cost[c] == best_cost]
best_count = max(len(approvers[c]) for c in remaining)
remaining = [c for c in remaining if len(approvers[c]) == best_count]
return remaining
min_proj = None
for p in choices:
if min_proj is None or p.name < min_proj:
min_proj = p
# if len(choices) > 1:
# print(f"Breaking!! {choices} -- {min_proj}")
return [min_proj]

budget = {i: B / len(N) for i in N}
budget = {i: frac(B, len(N)) for i in N}
remaining = {} # remaining candidate -> previous effective vote count
for c in C:
if cost[c] > 0 and len(approvers[c]) > 0:
remaining[c] = len(approvers[c])
if verbose:
tmp = sorted([(p, a) for p, a in remaining.items()], key=lambda x: -x[1])
for p, a in tmp:
print(f"{p} -- {float(a)}")
winners = []
while True:
best = []
best_eff_vote_count = 0
# go through remaining candidates in order of decreasing previous effective vote count
remaining_sorted = sorted(remaining, key=lambda c: remaining[c], reverse=True)
if verbose:
print("========================")
tmp = sorted([(p, a) for p, a in remaining.items()], key=lambda x: -x[1])
for p, a in tmp[:5]:
print(f"{p} -- {float(a)}")
for c in remaining_sorted:
if verbose: print(f"\tConsidering: {c}")
previous_eff_vote_count = remaining[c]
if previous_eff_vote_count < best_eff_vote_count:
# c cannot be better than the best so far
Expand All @@ -254,14 +271,17 @@ def break_ties(N, C, cost, approvers, choices):
denominator = len(approvers[c])
for i in approvers[c]:
# compute payment if remaining approvers pay equally
max_payment = (cost[c] - paid_so_far) / denominator
eff_vote_count = cost[c] / max_payment
max_payment = frac(cost[c] - paid_so_far, denominator)
eff_vote_count = frac(cost[c], max_payment)
if max_payment > budget[i]:
# i cannot afford the payment, so pays entire remaining budget
paid_so_far += budget[i]
denominator -= 1
else:
# i (and all later approvers) can afford the payment; stop here
if verbose:
print(f"\t\tFactor: {float(max_payment)} = ({cost[c]} - {paid_so_far})/{denominator}")
print(f"\t\tEff: {float(eff_vote_count)}")
remaining[c] = eff_vote_count
if eff_vote_count > best_eff_vote_count:
best_eff_vote_count = eff_vote_count
Expand All @@ -272,6 +292,7 @@ def break_ties(N, C, cost, approvers, choices):
if not best:
# no remaining candidates are affordable
break
if verbose: print(best)
best = break_ties(N, C, cost, approvers, best)
if len(best) > 1:
raise Exception(
Expand All @@ -283,7 +304,7 @@ def break_ties(N, C, cost, approvers, choices):
winners.append(best)
del remaining[best]
# charge the approvers of best
best_max_payment = cost[best] / best_eff_vote_count
best_max_payment = frac(cost[best], best_eff_vote_count)
for i in approvers[best]:
if budget[i] > best_max_payment:
budget[i] -= best_max_payment
Expand All @@ -295,20 +316,22 @@ def break_ties(N, C, cost, approvers, choices):
if __name__ == "__main__":
# pabutools.fractions.FRACTION = "float"
instance, profile = parse_pabulib("poland_wieliczka_2023.pb")
# instance, profile = parse_pabulib("poland_warszawa_2019_ursynow.pb")
# profile = profile.as_multiprofile()

# instance = Instance([Project("1", 2), Project("2", 3), Project("3", 3)], budget_limit=4)
# profile = ApprovalProfile([ApprovalBallot([instance.get_project("1")]), ApprovalBallot([instance.get_project("2")]), ApprovalBallot([instance.get_project("3")]), ApprovalBallot([instance.get_project("3")])])

# winners_fast = equal_shares_fast(instance, profile, sat_class=Cost_Sat)
winners_fast = equal_shares_iterated_fast(instance, profile, sat_class=Cardinality_Sat)
print(len(winners_fast))
print(winners_fast)
# winners_fast = equal_shares_fast(instance, profile, sat_class=Cardinality_Sat)
# winners_fast = equal_shares_iterated_fast(
# instance, profile, sat_class=Cardinality_Sat
# )
# print(len(winners_fast))
# print(winners_fast)
# winners_fast = equal_shares_fast_approval(instance, profile)
winners_fast = equal_shares_iterated_fast_approval(instance, profile)
winners_fast = equal_shares_iterated_fast_approval(instance, profile, budget_multiplier=1)
print(len(winners_fast))
print(winners_fast)

# winners_slow = method_of_equal_shares(
# instance,
# profile,
Expand All @@ -318,16 +341,19 @@ def break_ties(N, C, cost, approvers, choices):
instance,
profile.as_multiprofile(),
sat_class=Cost_Sat,
budget_step=len(profile),
budget_step=1,
)
# winners_slow = exhaustion_by_budget_increase(
# instance,
# profile,
# rule=method_of_equal_shares,
# rule_params={'sat_class': Cost_Sat},
# budget_step=len(profile))
print(len(winners_slow))
print(winners_slow)
# winners_slow = method_of_equal_shares(
# instance,
# profile.as_multiprofile(),
# sat_class=Cost_Sat,
# budget_step=len(profile),
# binary_sat=False,
# )
# print(len(winners_slow))
# print(winners_slow)

# print(profile.approval_score(instance.get_project("24")))
# print(profile.approval_score(instance.get_project("41")))
6 changes: 3 additions & 3 deletions pabutools/analysis/cohesiveness.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def is_cohesive_approval(
projects: Iterable[Project],
ballots: Iterable[AbstractApprovalBallot]
) -> bool:
if not is_large_enough(sum(profile.multiplicity(b) for b in ballots), profile.num_ballots(), total_cost(projects), instance.budget_limit):
if not is_large_enough(profile.num_ballots(), sum(profile.multiplicity(b) for b in ballots), total_cost(projects), instance.budget_limit):
return False
if len(ballots) == 0 or len(projects) == 0:
return False
Expand All @@ -34,13 +34,13 @@ def is_cohesive_cardinal(
ballots: Iterable[AbstractCardinalBallot],
alpha: dict[Project, Number]
) -> bool:
if not is_large_enough(sum(profile.multiplicity(b) for b in ballots), profile.num_ballots(), total_cost(projects), instance.budget_limit):
if not is_large_enough(profile.num_ballots(), sum(profile.multiplicity(b) for b in ballots), total_cost(projects), instance.budget_limit):
return False
if len(ballots) == 0 or len(projects) == 0:
return False
for ballot in ballots:
for p in projects:
if ballot[p] < alpha[p]:
if ballot[p] > alpha[p]:
return False
return True

Expand Down
Loading

0 comments on commit 49ee6dd

Please sign in to comment.