Source code for svvamp.meta.voting_rule_tasks

# -*- coding: utf-8 -*-
"""
Created on jul. 21, 2021, 21:16
Copyright François Durand 2014-2021
fradurand@gmail.com

This file is part of SVVAMP.

    SVVAMP is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    SVVAMP is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with SVVAMP.  If not, see <http://www.gnu.org/licenses/>.
"""

import numpy as np
import inspect
import copy
from svvamp.meta.study_rule_criteria import StudyRuleCriteria
from svvamp.rules.all_rule_classes import ALL_RULE_CLASSES
from svvamp.rules.rule_approval import RuleApproval
from svvamp.rules.rule_plurality import RulePlurality
from svvamp.rules.rule_ranked_pairs import RuleRankedPairs
from svvamp.rules.rule_schulze import RuleSchulze
from svvamp.rules.rule_exhaustive_ballot import RuleExhaustiveBallot
from svvamp.rules.rule_irv import RuleIRV
from svvamp.rules.rule_condorcet_vtb_irv import RuleCondorcetVtbIRV
from svvamp.rules.rule_icrv import RuleICRV
from svvamp.utils.misc import indent


[docs] class VotingRuleTasks(list): """A set of tasks for the simulator, i.e. which voting rules with which options and which criteria about them. A "task" consists of: a voting rule, a dictionary of options, and a :class:`StudyRuleCriteria` object. A `VotingRuleTasks` is essentially a list of tasks. In the following, what we call a "voting system" consists of a voting rule and a dictionary of options (i.e. like a task, but with no :class:`StudyRuleCriteria` object specified). Parameters ---------- voting_systems: List, optional A list of pairs (rule_class, options), where `rule_class` is a subclass of :class:`Rule` and `options` is a dictionary of options. Instead of (rule_class, options), one may provide a singleton `rule_class` (without parentheses), which is equivalent to (rule_class, {}). study_rule_criteria: StudyRuleCriteria, optional A :class:`StudyRuleCriteria` object. detailed_tasks: object, optional Either a list of triples (rule_class, options, study_rule_criteria), or a VotingRuleTasks object. Examples -------- Basically, there are two non-exclusive ways to define a VotingRuleTasks object: * Exhaustively with detailed_tasks. * As a Cartesian product by voting_systems and study_rule_criteria. >>> voting_rule_tasks1 = VotingRuleTasks( ... voting_systems=[ ... RulePlurality, ... RuleExhaustiveBallot, ... (RuleIRV, {'cm_option': 'fast'}), ... (RuleApproval, {'approval_threshold': 10}), ... ], ... study_rule_criteria=StudyRuleCriteria( ... manipulation_criteria=['is_cm_', 'is_tm_', 'is_um_'], ... ), ... detailed_tasks=[ ... ( ... RuleIRV, ... {'cm_option': 'exact'}, ... StudyRuleCriteria( ... manipulation_criteria=['is_cm_'], ... manipulation_only=True ... ) ... ), ... ( ... RuleApproval, ... {'approval_threshold': 5}, ... StudyRuleCriteria( ... manipulation_criteria=['is_cm_', 'is_tm_', 'is_um_'] ... ) ... ), ... ] ... ) >>> voting_rule_tasks2 = VotingRuleTasks( ... voting_systems=[RuleIRV, RuleExhaustiveBallot], ... study_rule_criteria=StudyRuleCriteria(), ... detailed_tasks=VotingRuleTasks( ... voting_systems=[ ... (RuleIRV, {'cm_option': 'exact'}), ... (RuleExhaustiveBallot, {'cm_option': 'exact'}) ... ], ... study_rule_criteria=StudyRuleCriteria( ... manipulation_criteria=['is_cm_'], ... manipulation_only=True ... ) ... ) ... ) In examples 1 and 2 above, all elements of `voting_systems` will be assigned the same set of criteria, the argument `study_rule_criteria`. The argument `detailed_tasks` allows to give different criteria for voting systems needing a specific treatment. * When voting_systems is None (default), it is normally set to []. But if detailed_tasks is empty, or study_rule_criteria is not empty, then voting_systems is set to a quite extensive list of voting systems. * When study_rule_criteria is None (default), it is normally set to []. But if detailed_tasks is empty, or voting_systems is not empty, then study_rule_criteria is set to StudyRuleCriteria(), which leads to study an quite extensive list of criteria. Study :class:`RuleSchulze` and :class:`Ranked Pairs` with a quite extensive list of criteria: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleSchulze, RuleRankedPairs] ... ) Study a quite extensive list of voting systems, with CM being the only manipulation criterion: >>> voting_rule_tasks = VotingRuleTasks( ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_']) ... ) Study a quite extensive list of voting systems with a quite extensive list of criteria: >>> voting_rule_tasks = VotingRuleTasks() """ def __init__(self, voting_systems=None, study_rule_criteria=None, detailed_tasks=None): # Dealing with default arguments... if detailed_tasks is None: detailed_tasks = [] if voting_systems is None: if not detailed_tasks or study_rule_criteria is not None: voting_systems = ALL_RULE_CLASSES else: voting_systems = [] if study_rule_criteria is None: study_rule_criteria = StudyRuleCriteria() # Here we go super().__init__([]) for voting_system in voting_systems: if inspect.isclass(voting_system): self.append( (voting_system, {}, copy.deepcopy(study_rule_criteria)) ) else: self.append( (voting_system[0], voting_system[1], copy.deepcopy(study_rule_criteria)) ) self.extend(detailed_tasks) def __str__(self): """ >>> voting_rule_tasks = VotingRuleTasks(detailed_tasks=[ ... ( ... RuleIRV, ... {'cm_option': 'exact', 'um_option': 'exact'}, ... StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ), ... ( ... RuleCondorcetVtbIRV, ... {'cm_option': 'very_slow', 'um_option': 'exact'}, ... StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) ... ]) >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleIRV options: {'cm_option': 'exact', 'um_option': 'exact'} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleCondorcetVtbIRV options: {'cm_option': 'very_slow', 'um_option': 'exact'} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None """ lines = [] for (rule_class, options, study_rule_criteria) in self: lines.append(f'voting_system: {rule_class.__name__}') lines.append(f'options: {options}') lines.append(str(study_rule_criteria)) return 'VotingRuleTasks with:\n' + indent('\n'.join(lines))
[docs] def remove_rule(self, rule_class): """Remove a rule Removes all occurrences of `rule_class` from this `VotingRuleTasks` object. Parameters ---------- rule_class : class Subclass of :class:`Rule`. Examples -------- >>> voting_rule_tasks = VotingRuleTasks(detailed_tasks=[ ... ( ... RuleIRV, ... {'cm_option': 'fast', 'um_option': 'fast'}, ... StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ), ... ( ... RuleIRV, ... {'cm_option': 'exact', 'um_option': 'exact'}, ... StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ), ... ( ... RuleCondorcetVtbIRV, ... {'cm_option': 'very_slow', 'um_option': 'exact'}, ... StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) ... ]) >>> voting_rule_tasks.remove_rule(RuleIRV) >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleCondorcetVtbIRV options: {'cm_option': 'very_slow', 'um_option': 'exact'} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None """ self[:] = [element for element in self if element[0] != rule_class]
[docs] def append_task(self, rule_class, options=None, study_rule_criteria=None): """Append a task to the list. Parameters ---------- rule_class : class Subclass of :class:`Rule`. options : dict, optional A dictionary of options. If None (default), then options will be an empty dictionary, which amounts to using the default options for this voting rule. study_rule_criteria : StudyRuleCriteria, optional If None (default), then use the default StudyRuleCriteria() (with a quite extensive list of criteria). Examples -------- >>> voting_rule_tasks = VotingRuleTasks(detailed_tasks=[ ... ( ... RuleIRV, ... {'cm_option': 'fast'}, ... StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ), ... ]) >>> voting_rule_tasks.append_task( ... RuleIRV, ... {'cm_option': 'exact'}, ... StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleIRV options: {'cm_option': 'fast'} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {'cm_option': 'exact'} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None If `options` is not specified, then it is considered empty: >>> voting_rule_tasks = VotingRuleTasks(voting_systems=[], detailed_tasks=[]) >>> voting_rule_tasks.append_task( ... rule_class=RuleIRV, ... study_rule_criteria=StudyRuleCriteria( ... manipulation_criteria=['is_cm_'], manipulation_only=True, numerical_criteria=[] ... ) ... ) >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None If `study_rule_criteria` is not specified, then it is the default instance `StudyRuleCriteria()`: >>> voting_rule_tasks = VotingRuleTasks(voting_systems=[], detailed_tasks=[]) >>> voting_rule_tasks.append_task(rule_class=RuleIRV, options={'cm_option': 'exact'}) >>> print(voting_rule_tasks) # doctest: +ELLIPSIS VotingRuleTasks with: voting_system: RuleIRV options: {'cm_option': 'exact'} StudyRuleCriteria with: manipulation_criteria: is_tm_ is_um_ ... """ if options is None: options = {} if study_rule_criteria is None: study_rule_criteria = StudyRuleCriteria() self.append((rule_class, options, copy.deepcopy(study_rule_criteria)))
[docs] def extend(self, iterable): """Extend a `VotingRuleTasks` object. Note that the usual command `extend` can be used to concatenate two `VotingRuleTasks` objects. Examples -------- >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleExhaustiveBallot, RuleIRV], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks_2 = VotingRuleTasks( ... voting_systems=[RuleICRV, RuleCondorcetVtbIRV], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_um_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks.extend(voting_rule_tasks_2) >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleICRV options: {} StudyRuleCriteria with: manipulation_criteria: is_um_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleCondorcetVtbIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_um_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None """ super().extend(iterable)
[docs] def remove_criterion(self, criterion, rule_class=None): """Remove a criterion for one or all voting rules. Parameters ---------- criterion : str rule_class : class Subclass of :class:`Rule`. Examples -------- Removes all occurrences of `criterion` for all occurrences of `rule_class`: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleExhaustiveBallot, RuleIRV, (RuleIRV, {'cm_option': 'exact'})], ... study_rule_criteria=StudyRuleCriteria( ... manipulation_criteria=['is_cm_', 'is_um_'], ... manipulation_only=True, ... numerical_criteria=[] ... ) ... ) >>> voting_rule_tasks.remove_criterion(criterion='is_um_', rule_class=RuleIRV) >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ is_um_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {'cm_option': 'exact'} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None If `rule_class` is None (default), then `criterion` is removed for all voting rules: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleExhaustiveBallot, RuleIRV], ... study_rule_criteria=StudyRuleCriteria( ... manipulation_criteria=['is_cm_', 'is_um_'], ... manipulation_only=True, ... numerical_criteria=[] ... ) ... ) >>> voting_rule_tasks.remove_criterion('is_um_') >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None """ for (rc, options, study_rule_criteria) in self: if rule_class is None or rule_class == rc: study_rule_criteria.remove(criterion)
[docs] def append_manipulation_criterion(self, criterion, rule_class=None): """Add a manipulation criterion for one or all voting rules. Parameters ---------- criterion : str rule_class : class Subclass of :class:`Rule`. Examples -------- Add `criterion` for all occurrences of `rule_class`: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleExhaustiveBallot, RuleIRV, (RuleIRV, {'cm_option': 'exact'})], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks.append_manipulation_criterion(criterion='is_um_', rule_class=RuleIRV) >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ is_um_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {'cm_option': 'exact'} StudyRuleCriteria with: manipulation_criteria: is_cm_ is_um_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None If `rule_class` is None (default), then add `criterion` for all voting rules: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleExhaustiveBallot, RuleIRV], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks.append_manipulation_criterion('is_um_') >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ is_um_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ is_um_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None """ for (rc, options, study_rule_criteria) in self: if rule_class is None or rule_class == rc: study_rule_criteria.append_manipulation_criterion(criterion)
[docs] def append_manipulation_criterion_c(self, criterion, rule_class=None): """Add a manipulation criterion (candidates) for one or all voting rules. Parameters ---------- criterion : str rule_class : class Subclass of :class:`Rule`. Examples -------- Add `criterion` for all occurrences of `rule_class`: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleExhaustiveBallot, RuleIRV, (RuleIRV, {'cm_option': 'exact'})], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks.append_manipulation_criterion_c(criterion='candidates_cm_', rule_class=RuleIRV) >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: candidates_cm_ result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {'cm_option': 'exact'} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: candidates_cm_ result_criteria: None utility_criteria: None numerical_criteria: None If `rule_class` is None (default), then add `criterion` for all voting rules: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleExhaustiveBallot, RuleIRV], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks.append_manipulation_criterion_c('candidates_cm_') >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: candidates_cm_ result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: candidates_cm_ result_criteria: None utility_criteria: None numerical_criteria: None """ for (rc, options, study_rule_criteria) in self: if rule_class is None or rule_class == rc: study_rule_criteria.append_manipulation_criterion_c(criterion)
[docs] def append_result_criterion(self, criterion, rule_class=None): """Add a result criterion for one or all voting rules Parameters ---------- criterion : str rule_class : class Subclass of :class:`Rule`. Examples -------- Add `criterion` for all occurrences of `rule_class`: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleExhaustiveBallot, RuleIRV, (RuleIRV, {'cm_option': 'exact'})], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks.append_result_criterion(criterion='w_is_condorcet_admissible_', rule_class=RuleIRV) >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: w_is_condorcet_admissible_ utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {'cm_option': 'exact'} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: w_is_condorcet_admissible_ utility_criteria: None numerical_criteria: None If `rule_class` is None (default), then add `criterion` for all voting rules: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleExhaustiveBallot, RuleIRV], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks.append_result_criterion(criterion='w_is_condorcet_admissible_') >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: w_is_condorcet_admissible_ utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: w_is_condorcet_admissible_ utility_criteria: None numerical_criteria: None """ for (rc, options, study_rule_criteria) in self: if rule_class is None or rule_class == rc: study_rule_criteria.append_result_criterion(criterion)
[docs] def append_utility_criterion(self, criterion, func, name, rule_class=None): """Add a utility criterion for one or all voting rules Parameters ---------- criterion : str The numerical data to be processed. func : callable An aggregating function. name : str The name chosen for this pair criterion / aggregation function. Cf. :meth:`StudyRuleCriteria.append_utility_criterion`. rule_class : class Subclass of :class:`Rule`. Examples -------- Add `criterion` for all occurrences of `rule_class`: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleExhaustiveBallot, RuleIRV, (RuleIRV, {'cm_option': 'exact'})], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks.append_utility_criterion( ... criterion='total_utility_w_', func=np.sum, name='total_u_sum', rule_class=RuleIRV) >>> print(voting_rule_tasks) # doctest: +ELLIPSIS VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: ('total_utility_w_', <function sum at ...>, 'total_u_sum') numerical_criteria: None voting_system: RuleIRV options: {'cm_option': 'exact'} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: ('total_utility_w_', <function sum at ...>, 'total_u_sum') numerical_criteria: None If `rule_class` is None (default), then add `criterion` for all voting rules: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleExhaustiveBallot, RuleIRV], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks.append_utility_criterion( ... criterion='total_utility_w_', func=np.sum, name='total_u_sum') >>> print(voting_rule_tasks) # doctest: +ELLIPSIS VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: ('total_utility_w_', <function sum at ...>, 'total_u_sum') numerical_criteria: None voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: ('total_utility_w_', <function sum at ...>, 'total_u_sum') numerical_criteria: None """ for (rc, options, study_rule_criteria) in self: if rule_class is None or rule_class == rc: study_rule_criteria.append_utility_criterion(criterion, func, name)
[docs] def append_numerical_criterion(self, criterion, rule_class=None): """Add a numerical criterion for one or all voting rules Parameters ---------- criterion : str rule_class : class Subclass of :class:`Rule`. Examples -------- Add `criterion` for all occurrences of `rule_class`: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleExhaustiveBallot, RuleIRV, (RuleIRV, {'cm_option': 'exact'})], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks.append_numerical_criterion(criterion='nb_candidates_cm_', rule_class=RuleIRV) >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: nb_candidates_cm_ voting_system: RuleIRV options: {'cm_option': 'exact'} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: nb_candidates_cm_ If `rule_class` is None (default), then add `criterion` for all voting rules: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleExhaustiveBallot, RuleIRV], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks.append_numerical_criterion(criterion='nb_candidates_cm_') >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: nb_candidates_cm_ voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: nb_candidates_cm_ """ for (rc, options, study_rule_criteria) in self: if rule_class is None or rule_class == rc: study_rule_criteria.append_numerical_criterion(criterion)
[docs] def remove_option(self, option, rule_class=None): """Remove an option and its value for one or all voting rules Parameters ---------- option : str rule_class : class Subclass of :class:`Rule`. Examples -------- Remove `option` for all occurrences of `rule_class`: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[ ... (RuleExhaustiveBallot, {'cm_option': 'exact'}), ... (RuleIRV, {'cm_option': 'exact'}) ... ], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks.remove_option(option='cm_option', rule_class=RuleIRV) >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {'cm_option': 'exact'} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None This means that from now on, `RuleIRV` will use its default value for `cm_option`. If `rule_class` is None (default), then remove the option for all voting rules: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[ ... (RuleExhaustiveBallot, {'cm_option': 'exact'}), ... (RuleIRV, {'cm_option': 'exact'}) ... ], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks.remove_option(option='cm_option') >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None """ for (rc, options, study_rule_criteria) in self: if rule_class is None or rule_class == rc: if option in options: del(options[option])
[docs] def set_option(self, option, value, rule_class=None): """Set an option for one or all voting rules Parameters ---------- option : str value : object New value for the option. rule_class : class Subclass of :class:`Rule`. Examples -------- Set `option` to `value` for all occurrences of `rule_class`: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleExhaustiveBallot, RuleIRV], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks.set_option('cm_option', 'exact', rule_class=RuleIRV) >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {'cm_option': 'exact'} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None If `rule_class` is None (default), do this for all voting rules: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleExhaustiveBallot, RuleIRV], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['is_cm_'], manipulation_only=True, ... numerical_criteria=[]) ... ) >>> voting_rule_tasks.set_option('cm_option', 'exact') >>> print(voting_rule_tasks) VotingRuleTasks with: voting_system: RuleExhaustiveBallot options: {'cm_option': 'exact'} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None voting_system: RuleIRV options: {'cm_option': 'exact'} StudyRuleCriteria with: manipulation_criteria: is_cm_ manipulation_criteria_c: None result_criteria: None utility_criteria: None numerical_criteria: None """ for (rc, options, study_rule_criteria) in self: if rule_class is None or rule_class == rc: # print(rule_class) # print('Change option to '+ str(value)) options[option] = value
[docs] def check_sanity(self): """Check sanity of the object Preform some basic checks. It is recommended to use this method before launching big simulations. Examples -------- Confirm that a configuration is sound: >>> voting_rule_tasks = VotingRuleTasks() >>> voting_rule_tasks.check_sanity() VotingRuleTasks: Sanity check was successful. Detect an illegal option: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[(RuleIRV, {'cm_option': 'unexpected_option'})], ... ) >>> voting_rule_tasks.check_sanity() Traceback (most recent call last): ValueError: 'cm_option' = 'unexpected_option' is not allowed in RuleIRV. Detect an illegal :class:`StudyRuleCriteria`: >>> voting_rule_tasks = VotingRuleTasks( ... voting_systems=[RuleIRV], ... study_rule_criteria=StudyRuleCriteria(manipulation_criteria=['unexpected_criterion']) ... ) >>> voting_rule_tasks.check_sanity() Traceback (most recent call last): ValueError: Criterion 'unexpected_criterion' is unknown for RuleIRV. """ for (rule_class, options, study_rule_criteria) in self: # print(rule_class) for option, value in options.items(): rule_class.check_option_allowed(option, value) study_rule_criteria.check_sanity(rule_class) print('VotingRuleTasks: Sanity check was successful.')