Satisfaction Measures#
In participatory budgeting, various concepts and rules utilize proxies for voter satisfaction, which are deduced from the submitted ballots rather than using the ballots directly.
We offer a range of satisfaction functions and provide flexible ways to create custom ones.
A satisfaction function is represented by a class that inherits from
SatisfactionMeasure
.
Such a class is initialized with specific parameters: an instance, a profile, and a ballot.
The class implements a sat method used to compute the satisfaction value for that
particular ballot. Additionally, we introduce
SatisfactionProfile
,
a class that inherits from the Python class list and facilitates managing a collection
of satisfaction functions. We also have
SatisfactionMultiProfile
,
which represents satisfaction profiles as multisets (see our discussion above about multiprofiles).
The typical workflow involves gathering the ballots into a profile, converting them into a collection of satisfaction functions, and then using these functions as input to a rule.
from pabutools.election import SatisfactionProfile, SatisfactionMeasure
from pabutools.election import parse_pabulib
instance, profile = parse_pabulib("path_to_the_file")
sat_profile = SatisfactionProfile(instance=instance)
# We define a satisfaction function:
class MySatisfaction(SatisfactionMeasure):
def sat(self, projects):
return 100 if "p1" in projects else len(projects)
# We populate the satisfaction profile
for ballot in profile:
sat_profile.append(MySatisfaction(instance, profile, ballot))
# The satisfaction profile is ready for use
outcome = rule(sat_profile)
To simplify the process of defining the satisfaction profile, we offer convenient methods and provide several widely used satisfaction functions.
from pabutools.election import SatisfactionProfile, Cardinality_Sat
from pabutools.election import parse_pabulib
instance, profile = parse_pabulib("path_to_the_file")
# If a profile and a sat_class are given to the constructor, the satisfaction profile
# is directly initialized with one instance of the sat_class per ballot in the profile.
sat_profile = SatisfactionProfile(instance=instance, profile=profile, sat_class=Cardinality_Sat)
# The satisfaction profile is ready for use
outcome = rule(sat_profile)
Default Satisfaction Functions#
Several satisfaction functions are already defined in the package and can be imported from pabutools.election. We list them below.
CC_Sat
implements the Chamberlin-Courant satisfaction function.Cost_Sqrt_Sat
defines the satisfaction as the square root of the total cost of the selected and approved projects.Cost_Log_Sat
defines the satisfaction as the log of the total cost of the approved and selected projects.Cardinality_Sat
defines the satisfaction as the number of approved and selected projects.Relative_Cardinality_Sat
defines the satisfaction as the number of approved and selected projects divided by the size the largest feasible subset of the ballot.Cost_Sat
defines the satisfaction as the total cost of the approved and selected projects.Relative_Cost_Sat
defines the satisfaction as the total cost of the approved and selected projects divided by the total cost of the most expensive subset of the ballot.Relative_Cost_Approx_Normaliser_Sat
resembles the previous but uses the total cost of the ballot as the normalizer.Effort_Sat
defines the satisfaction as the total share of a voter, i.e., the sum over all approved and selected projects of the cost divided by the approval score.Additive_Cardinal_Sat
defines the satisfaction as the sum of the scores of the selected projects, where the scores are taken from the cardinal ballot of the voter.Additive_Borda_Sat
defines the satisfaction as the sum of the Borda scores of the selected projects.
Next, we present additional tools we provide to define satisfaction functions.
Functional Satisfaction Functions#
For more specific ways of defining satisfaction functions, we introduce the class
FunctionalSatisfaction
.
This class corresponds to a satisfaction function defined by a function that takes as arguments
an instance, a profile, a ballot, and a set of projects. To demonstrate its use, we illustrate
how to define the Chamberlin-Courant satisfaction function with approval (equal to 1 if
at least one approved project is selected and 0 otherwise).
from pabutools.election import FunctionalSatisfaction
def cc_sat_func(instance, profile, ballot, projects):
return int(any(p in ballot for p in projects))
class CC_Sat(FunctionalSatisfaction):
def __init__(self, instance, profile, ballot):
super(CC_Sat, self).__init__(instance, profile, ballot, cc_sat_func)
Additive Satisfaction Functions#
We also offer additive satisfaction functions, where the satisfaction for a set
of projects is equal to the sum of the satisfaction of each individual project. The class
AdditiveSatisfaction
implements such functions. Its constructor takes a function as a parameter that maps
instance, profile, ballot, project, and pre-computed values to a score. The pre-computed
argument is used to pass fixed parameters to the function that can be used
for expensive computations not to be done more than once. As an example, we demonstrate
how to define the cardinality satisfaction function.
from pabutools.election import AdditiveSatisfaction
def cardinality_sat_func(instance, profile, ballot, project, precomputed_values):
return int(project in ballot)
class Cardinality_Sat(AdditiveSatisfaction):
def __init__(self, instance, profile, ballot):
super(Cardinality_Sat, self).__init__(instance, profile, ballot, cardinality_sat_func)
Positional Satisfaction Functions#
For ordinal ballots, we have positional satisfaction functions, where a voter’s
satisfaction depends on the position of projects in their ballot. The class
PositionalSatisfaction
implements these functions. Its constructor takes two functions as parameters: one that maps
ballots and projects to a score and another that aggregates the individual scores for sets
of projects. As an example, we define the additive Borda satisfaction function.
from pabutools.election import PositionalSatisfaction
def borda_sat_func(ballot, project):
if project not in ballot:
return 0
return len(ballot) - ballot.index(project)
class Additive_Borda_Sat(PositionalSatisfaction):
def __init__(self, instance, profile, ballot):
super(Additive_Borda_Sat, self).__init__(instance, profile, ballot, borda_sat_func, sum)