From eb98df4f933ea6d9e49710ac610ae50ff2a27dbd Mon Sep 17 00:00:00 2001 From: Simon-Rey Date: Tue, 24 Oct 2023 10:31:19 +0200 Subject: [PATCH] Fix in cohesiveness --- pabutools/analysis/cohesiveness.py | 6 +- pabutools/analysis/justifiedrepresentation.py | 150 +++++++++++++++--- 2 files changed, 135 insertions(+), 21 deletions(-) diff --git a/pabutools/analysis/cohesiveness.py b/pabutools/analysis/cohesiveness.py index 7f37fb6e..42b45318 100644 --- a/pabutools/analysis/cohesiveness.py +++ b/pabutools/analysis/cohesiveness.py @@ -16,7 +16,7 @@ def is_cohesive_approval( projects: Iterable[Project], ballots: Iterable[AbstractApprovalBallot] ) -> bool: - if not is_large_enough(profile.num_ballots(), sum(profile.multiplicity(b) for b in ballots), total_cost(projects), instance.budget_limit): + if not is_large_enough(sum(profile.multiplicity(b) for b in ballots), profile.num_ballots(), total_cost(projects), instance.budget_limit): return False if len(ballots) == 0 or len(projects) == 0: return False @@ -34,13 +34,13 @@ def is_cohesive_cardinal( ballots: Iterable[AbstractCardinalBallot], alpha: dict[Project, Number] ) -> bool: - if not is_large_enough(profile.num_ballots(), sum(profile.multiplicity(b) for b in ballots), total_cost(projects), instance.budget_limit): + if not is_large_enough(sum(profile.multiplicity(b) for b in ballots), profile.num_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 diff --git a/pabutools/analysis/justifiedrepresentation.py b/pabutools/analysis/justifiedrepresentation.py index 4b996079..de3c09fa 100644 --- a/pabutools/analysis/justifiedrepresentation.py +++ b/pabutools/analysis/justifiedrepresentation.py @@ -1,8 +1,31 @@ -from collections.abc import Iterable +from collections.abc import Iterable, Callable +from numbers import Number -from pabutools.analysis.cohesiveness import cohesive_groups +from pabutools.analysis.cohesiveness import cohesive_groups, is_large_enough from pabutools.election import Instance, AbstractApprovalProfile, Project, SatisfactionMeasure, Additive_Cardinal_Sat, \ - AbstractCardinalProfile + AbstractCardinalProfile, ApprovalBallot, total_cost +from pabutools.utils import powerset + + +def is_in_core_approval( + instance: Instance, + profile: AbstractApprovalProfile, + sat_class: type[SatisfactionMeasure], + budget_allocation: Iterable[Project] +) -> bool: + for group in powerset(profile): + if len(group) > 0: + for proj_set in powerset(instance): + if is_large_enough(len(group), profile.num_ballots(), total_cost(proj_set), instance.budget_limit): + all_better_alone = True + for ballot in group: + sat = sat_class(instance, profile, ballot) + if sat.sat(budget_allocation) >= sat.sat(proj_set): + all_better_alone = False + break + if all_better_alone: + return False + return True def is_strong_EJR_approval( @@ -13,7 +36,8 @@ def is_strong_EJR_approval( ) -> bool: for group, project_set in cohesive_groups(instance, profile): all_agents_sat = True - for sat in profile.as_sat_profile(sat_class): + for ballot in group: + sat = sat_class(instance, profile, ballot) if sat.sat(budget_allocation) < sat.sat(project_set): all_agents_sat = False break @@ -26,12 +50,17 @@ def is_EJR_approval( instance: Instance, profile: AbstractApprovalProfile, sat_class: type[SatisfactionMeasure], - budget_allocation: Iterable[Project] + budget_allocation: Iterable[Project], + up_to_func: Callable[[Iterable[Number]], Number] = None ) -> bool: for group, project_set in cohesive_groups(instance, profile): one_agent_sat = False - for sat in profile.as_sat_profile(sat_class): - if sat.sat(budget_allocation) >= sat.sat(project_set): + for ballot in group: + sat = sat_class(instance, profile, ballot) + surplus = 0 + if up_to_func is not None: + surplus = up_to_func(sat.sat_project(p) for p in project_set if p not in budget_allocation) + if sat.sat(budget_allocation) + surplus >= sat.sat(project_set): one_agent_sat = True break if not one_agent_sat: @@ -39,30 +68,73 @@ def is_EJR_approval( return True -def is_PJR_approval( +def is_EJR_any_approval( + instance: Instance, + profile: AbstractApprovalProfile, + sat_class: type[SatisfactionMeasure], + budget_allocation: Iterable[Project] +) -> bool: + return is_EJR_approval(instance, profile, sat_class, budget_allocation, up_to_func=lambda x: min(x, default=0)) + + +def is_EJR_one_approval( instance: Instance, profile: AbstractApprovalProfile, sat_class: type[SatisfactionMeasure], budget_allocation: Iterable[Project] +) -> bool: + return is_EJR_approval(instance, profile, sat_class, budget_allocation, up_to_func=lambda x: max(x, default=0)) + + +def is_PJR_approval( + instance: Instance, + profile: AbstractApprovalProfile, + sat_class: type[SatisfactionMeasure], + budget_allocation: Iterable[Project], + up_to_func: Callable[[Iterable[Number]], Number] = None ) -> bool: for group, project_set in cohesive_groups(instance, profile): - threshold = sat_class(instance, profile, project_set).sat(project_set) + sat = sat_class(instance, profile, ApprovalBallot(instance)) + threshold = sat.sat(project_set) group_approved = {p for p in budget_allocation if any(p in b for b in profile)} - group_sat = sat_class(instance, profile, group_approved).sat(group_approved) - if not group_sat < threshold: + surplus = 0 + if up_to_func is not None: + surplus = up_to_func(sat.sat_project(p) for p in project_set if p not in budget_allocation) + group_sat = sat.sat(group_approved) + surplus + if group_sat < threshold: return False return True +def is_PJR_any_approval( + instance: Instance, + profile: AbstractApprovalProfile, + sat_class: type[SatisfactionMeasure], + budget_allocation: Iterable[Project] +) -> bool: + return is_PJR_approval(instance, profile, sat_class, budget_allocation, up_to_func=lambda x: min(x, default=0)) + + +def is_PJR_one_approval( + instance: Instance, + profile: AbstractApprovalProfile, + sat_class: type[SatisfactionMeasure], + budget_allocation: Iterable[Project] +) -> bool: + return is_PJR_approval(instance, profile, sat_class, budget_allocation, up_to_func=lambda x: max(x, default=0)) + + def is_strong_EJR_cardinal( instance: Instance, profile: AbstractCardinalProfile, - budget_allocation: Iterable[Project] + budget_allocation: Iterable[Project], + sat_class: type[SatisfactionMeasure] = Additive_Cardinal_Sat, ) -> bool: for group, project_set in cohesive_groups(instance, profile): all_agents_sat = True threshold = sum(min(b[p] for b in profile) for p in project_set) - for sat in profile.as_sat_profile(Additive_Cardinal_Sat): + for ballot in group: + sat = sat_class(instance, profile, ballot) if sat.sat(budget_allocation) < threshold: all_agents_sat = False break @@ -74,13 +146,19 @@ def is_strong_EJR_cardinal( def is_EJR_cardinal( instance: Instance, profile: AbstractCardinalProfile, - budget_allocation: Iterable[Project] + budget_allocation: Iterable[Project], + sat_class: type[SatisfactionMeasure] = Additive_Cardinal_Sat, + up_to_func: Callable[[Iterable[Number]], Number] = None ) -> bool: for group, project_set in cohesive_groups(instance, profile): one_agent_sat = False threshold = sum(min(b[p] for b in profile) for p in project_set) - for sat in profile.as_sat_profile(Additive_Cardinal_Sat): - if sat.sat(budget_allocation) >= threshold: + for ballot in group: + sat = sat_class(instance, profile, ballot) + surplus = 0 + if up_to_func is not None: + surplus = up_to_func(sat.sat_project(p) for p in project_set if p not in budget_allocation) + if sat.sat(budget_allocation) + surplus >= threshold: one_agent_sat = True break if not one_agent_sat: @@ -88,14 +166,50 @@ def is_EJR_cardinal( return True +def is_EJR_one_cardinal( + instance: Instance, + profile: AbstractCardinalProfile, + budget_allocation: Iterable[Project], +) -> bool: + return is_EJR_cardinal(instance, profile, budget_allocation, up_to_func=lambda x: min(x, default=0)) + + +def is_EJR_any_cardinal( + instance: Instance, + profile: AbstractCardinalProfile, + budget_allocation: Iterable[Project], +) -> bool: + return is_EJR_cardinal(instance, profile, budget_allocation, up_to_func=lambda x: max(x, default=0)) + + def is_PJR_cardinal( instance: Instance, profile: AbstractCardinalProfile, - budget_allocation: Iterable[Project] + budget_allocation: Iterable[Project], + up_to_func: Callable[[Iterable[Number]], Number] = None ) -> bool: for group, project_set in cohesive_groups(instance, profile): threshold = sum(min(b[p] for b in profile) for p in project_set) group_sat = sum(max(b[p] for b in profile) for p in budget_allocation) - if group_sat < threshold: + surplus = 0 + if up_to_func is not None: + surplus = up_to_func(max(b[p] for b in profile) for p in project_set if p not in budget_allocation) + if group_sat + surplus < threshold: return False return True + + +def is_PJR_any_cardinal( + instance: Instance, + profile: AbstractCardinalProfile, + budget_allocation: Iterable[Project], +) -> bool: + return is_PJR_cardinal(instance, profile, budget_allocation, up_to_func=lambda x: min(x, default=0)) + + +def is_PJR_one_cardinal( + instance: Instance, + profile: AbstractCardinalProfile, + budget_allocation: Iterable[Project], +) -> bool: + return is_PJR_cardinal(instance, profile, budget_allocation, up_to_func=lambda x: max(x, default=0))