Skip to content

Commit

Permalink
Docs and refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon-Rey committed Feb 26, 2024
1 parent f14ae7d commit c96d930
Show file tree
Hide file tree
Showing 40 changed files with 332 additions and 177 deletions.
3 changes: 3 additions & 0 deletions docs-source/source/reference/analysis/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ Analysis module

.. automodule:: pabutools.analysis.category
:members:

.. automodule:: pabutools.analysis.projectloss
:members:
27 changes: 27 additions & 0 deletions docs-source/source/reference/rules/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,47 @@ Rules module

.. automodule:: pabutools.rules

Budget Allocation
-----------------

.. autoclass:: pabutools.rules.budgetallocation.BudgetAllocation

.. autoclass:: pabutools.rules.budgetallocation.AllocationDetails

Greedy Utilitarian Rule
-----------------------

.. autofunction:: pabutools.rules.greedywelfare.greedy_utilitarian_welfare

Additive Utilitarian Welfare Maximiser
--------------------------------------

.. autofunction:: pabutools.rules.maxwelfare.max_additive_utilitarian_welfare

Sequential Phragmén's Rule
--------------------------

.. autofunction:: pabutools.rules.phragmen.sequential_phragmen

Method of Equal Shares (MES)
----------------------------

.. autofunction:: pabutools.rules.mes.method_of_equal_shares

.. autoclass:: pabutools.rules.mes.MESAllocationDetails

.. autoclass:: pabutools.rules.mes.MESIteration

Exhaustion Methods
------------------

.. autofunction:: pabutools.rules.exhaustion.completion_by_rule_combination

.. autofunction:: pabutools.rules.exhaustion.exhaustion_by_budget_increase

Rule Composition
----------------

.. autofunction:: pabutools.rules.composition.popularity_comparison

.. autofunction:: pabutools.rules.composition.social_welfare_comparison
38 changes: 37 additions & 1 deletion docs-source/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,8 @@ outcome (for visualisation/explanation purposes).
Additive Utilitarian Welfare Maximiser
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

:py:func:`~pabutools.rules.maxwelfare.max_additive_utilitarian_welfare`

The first rule provided is the Additive Utilitarian Welfare Maximiser. It aims to return
budget allocations that maximize the utilitarian social welfare when the satisfaction
measure is additive.
Expand Down Expand Up @@ -584,6 +586,8 @@ for non-additive satisfaction measures.
Greedy Approximation of the Welfare Maximiser
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

:py:func:`~pabutools.rules.greedywelfare.greedy_utilitarian_welfare`

The library also implements standard greedy rules. The primary rule used in this
context is the Greedy Utilitarian Welfare. It behaves similarly to the
Utilitarian Welfare Maximiser but offers additional functionalities: it is not limited
Expand Down Expand Up @@ -653,6 +657,8 @@ to additive satisfaction measures (and runs faster).
Sequential Phragmén's Rule
^^^^^^^^^^^^^^^^^^^^^^^^^^

:py:func:`~pabutools.rules.phragmen.sequential_phragmen`

Another rule provided is the Sequential Phragmén's Rule, which is different from the
previous two as it does not rely on a satisfaction measure.

Expand Down Expand Up @@ -700,6 +706,8 @@ previous two as it does not rely on a satisfaction measure.
Method of Equal Shares (MES)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

:py:func:`~pabutools.rules.mes.method_of_equal_shares`

The Method of Equal Shares is another rule that returns budget allocations based on the satisfaction
measure provided. For more details, see the `equalshares.net <https://equalshares.net/>`_ website.

Expand Down Expand Up @@ -773,6 +781,10 @@ performances, one should use the following:
Exhaustion Methods
^^^^^^^^^^^^^^^^^^

:py:func:`~pabutools.rules.exhaustion.completion_by_rule_combination`

:py:func:`~pabutools.rules.exhaustion.exhaustion_by_budget_increase`

Since not all rules return exhaustive budget allocations, the library offers standard
methods to render their outcome exhaustive.

Expand Down Expand Up @@ -856,6 +868,10 @@ parameter directly to obtain the iterated version.
Rule Composition
^^^^^^^^^^^^^^^^

:py:func:`~pabutools.rules.composition.popularity_comparison`

:py:func:`~pabutools.rules.composition.social_welfare_comparison`

The library also provides ways to compose rules, such as selecting the outcome that is
preferred by the largest number of voters for a given satisfaction measure.

Expand Down Expand Up @@ -929,13 +945,33 @@ the following:
We also provide a similar comparison using utilitarian social welfare through the function
:py:func:`~pabutools.rules.composition.social_welfare_comparison`.

Details for the Budget Allocation Rule
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Some rules, for instance :py:func:`~pabutools.rules.mes.method_of_equal_shares`, accept a
:code:`analytics` boolean argument to activate the storage of additional information
regarding the budget allocations output by the rule. When :code:`analytics = True`,
the rule populate the :code:`details` member of the
:py:class:`~pabutools.rules.budgetallocation.BudgetAllocation` object it returns.
The stored information can then be used for analytical purposes.

.. list-table::
:widths: 50 50
:header-rows: 1

* - Rule
- Details class
* - :py:func:`~pabutools.rules.mes.method_of_equal_shares`
- :py:class:`~pabutools.rules.mes.MESAllocationDetails`


Tie-Breaking
------------

For reference, see the module :py:mod:`~pabutools.tiebreaking`.

We provide several ways to break ties between several projects. All tie-breaking rules are
instantiations of the :py:class:`pabutools.tiebreaking.TieBreakingRule` class.
instantiations of the :py:class:`~pabutools.tiebreaking.TieBreakingRule` class.
This class defines two functions `untie` and `order` that respectively return a single project
from a set of several or order a list of projects.

Expand Down
2 changes: 1 addition & 1 deletion docs/_sources/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -935,7 +935,7 @@ Tie-Breaking
For reference, see the module :py:mod:`~pabutools.tiebreaking`.

We provide several ways to break ties between several projects. All tie-breaking rules are
instantiations of the :py:class:`pabutools.tiebreaking.TieBreakingRule` class.
instantiations of the :py:class:`~pabutools.tiebreaking.TieBreakingRule` class.
This class defines two functions `untie` and `order` that respectively return a single project
from a set of several or order a list of projects.

Expand Down
8 changes: 6 additions & 2 deletions pabutools/analysis/justifiedrepresentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
Additive_Cardinal_Sat,
AbstractCardinalProfile,
ApprovalBallot,
total_cost, AbstractProfile,
total_cost,
AbstractProfile,
)
from pabutools.utils import powerset

Expand All @@ -39,7 +40,10 @@ def is_in_core(
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
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):
all_better_alone = False
Expand Down
61 changes: 39 additions & 22 deletions pabutools/analysis/projectloss.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,23 @@
class ProjectLoss(Project):
"""
Class used to represent the projects and how much budget they lost due to other projects being picked.
This extends the :py:class:`~pabutools.election.instance.Project` and thus represents the project itself.
Parameters
----------
project: Project
project: :py:class:`~pabutools.election.instance.Project`
Project for which analytics is calculated.
supporters_budget: Numeric
supporters_budget: :py:class:`~pabutools.utils.Numeric`
The collective budget of the project supporters when project was considered by a rule.
budget_lost: dict[Project, Numeric]
budget_lost: dict[:py:class:`~pabutools.election.instance.Project`, :py:class:`~pabutools.utils.Numeric`]
Describes the amount of budget project supporters spent on other projects prior to this
projects' consideration.
Attributes
----------
project: Project
Project for which analytics is calculated.
supporters_budget: Numeric
supporters_budget: :py:class:`~pabutools.utils.Numeric`
The collective budget of the project supporters when project was considered by rule.
budget_lost: dict[Project, Numeric]
budget_lost: dict[:py:class:`~pabutools.election.instance.Project`, :py:class:`~pabutools.utils.Numeric`]
Describes the amount of budget project supporters spent on other projects prior to this
projects' consideration.
"""
Expand All @@ -50,7 +49,7 @@ def total_budget_lost(self) -> Numeric:
Returns
-------
Numeric
:py:class:`~pabutools.utils.Numeric`
The total budget spent.
"""
return sum(self.budget_lost.values())
Expand All @@ -65,12 +64,28 @@ def __repr__(self):
def calculate_project_loss(
allocation_details: AllocationDetails, verbose: bool = False
) -> list[ProjectLoss]:
if (
allocation_details.iterations is None
or allocation_details.initial_budget_per_voter is None
"""Returns a list of :py:class:`~pabutools.analysis.projectloss.ProjectLoss` objects for the projects.
Parameters
----------
allocation_details: :py:class:`~pabutools.rules.budgetallocation.AllocationDetails`
The details of the budget allocation considered.
verbose: bool, optional
(De)Activate the display of additional information.
Defaults to `False`.
Returns
-------
list[:py:class:`~pabutools.analysis.projectloss.ProjectLoss`]
List of :py:class:`~pabutools.analysis.projectloss.ProjectLoss` objects.
"""
if not hasattr(allocation_details, "iterations") or not hasattr(
allocation_details, "initial_budget_per_voter"
):
raise ValueError(
"Provided allocation details do not support calculating project loss"
"Provided budget allocation details do not support calculating project loss. The allocation_details "
"should have an 'iterations' and an 'initial_budget_per_voter' attributes."
)
if len(allocation_details.iterations) == 0:
if verbose:
Expand All @@ -87,31 +102,33 @@ def calculate_project_loss(
allocation_details.initial_budget_per_voter for _ in range(voter_count)
]

for iter in allocation_details.iterations:
for iteration in allocation_details.iterations:
if verbose:
print(f"Considering: {iter.project.name}, status: {iter.was_picked}")
print(
f"Considering: {iteration.project.name}, status: {iteration.was_picked}"
)
budget_lost = {}
for spending in [voter_spendings[i] for i in iter.supporter_indices]:
for spending in [voter_spendings[i] for i in iteration.supporter_indices]:
for project, spent in spending:
if project not in budget_lost.keys():
budget_lost[project] = 0
budget_lost[project] = budget_lost[project] + spent
project_losses.append(
ProjectLoss(
iter.project,
sum(current_voters_budget[i] for i in iter.supporter_indices),
iteration.project,
sum(current_voters_budget[i] for i in iteration.supporter_indices),
budget_lost,
)
)
if iter.was_picked:
for supporter_idx in iter.supporter_indices:
if iteration.was_picked:
for supporter_idx in iteration.supporter_indices:
voter_spendings[supporter_idx].append(
(
iter.project,
iteration.project,
current_voters_budget[supporter_idx]
- iter.voters_budget[supporter_idx],
- iteration.voters_budget[supporter_idx],
)
)
current_voters_budget = iter.voters_budget
current_voters_budget = iteration.voters_budget

return project_losses
1 change: 1 addition & 0 deletions pabutools/election/ballot/approvalballot.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Approval ballots, i.e., ballots in which the voters indicate which projects they approve of.
"""

from __future__ import annotations

import random
Expand Down
1 change: 1 addition & 0 deletions pabutools/election/ballot/ballot.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Ballots, that is, the information submitted by the voters during the election.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
Expand Down
1 change: 1 addition & 0 deletions pabutools/election/ballot/cardinalballot.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Cardinal ballots, i.e., ballots in which the voters map projects to scores.
"""

from __future__ import annotations

from abc import ABC
Expand Down
1 change: 1 addition & 0 deletions pabutools/election/ballot/cumulativeballot.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Cumulative ballots, i.e., ballots in which the voters distribute a given amount of points to the projects.
"""

from __future__ import annotations

from abc import ABC
Expand Down
1 change: 1 addition & 0 deletions pabutools/election/ballot/ordinalballot.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Ordinal ballots, i.e., ballots in which the voters order the projects given their preferences.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
Expand Down
1 change: 1 addition & 0 deletions pabutools/election/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
The :py:class:`~pabutools.election.instance.Project` and the
:py:class:`~pabutools.election.instance.Instance` classes are defined here.
"""

from __future__ import annotations

from collections.abc import Collection, Generator
Expand Down
10 changes: 8 additions & 2 deletions pabutools/election/pabulib.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Tools to work with PaBuLib.
"""

from natsort import natsorted
from copy import deepcopy

Expand Down Expand Up @@ -410,10 +411,15 @@ def update_meta_value(meta_dict, inst_meta, field, mandatory=False):
file_str += f"{key};{value}\n"
file_str += "PROJECTS\n" + ";".join(project_keys) + "\n"
for project_dict in project_dicts:
file_str += ";".join([str(project_dict.get(key, "None")) for key in project_keys]) + "\n"
file_str += (
";".join([str(project_dict.get(key, "None")) for key in project_keys])
+ "\n"
)
file_str += "VOTES\n" + ";".join(vote_keys) + "\n"
for vote_dict in vote_dicts:
file_str += ";".join([str(vote_dict.get(key, "None")) for key in vote_keys]) + "\n"
file_str += (
";".join([str(vote_dict.get(key, "None")) for key in vote_keys]) + "\n"
)
return file_str


Expand Down
1 change: 1 addition & 0 deletions pabutools/election/preflib.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Tools to work with PrefLib.
"""

from __future__ import annotations

import preflibtools.instances as preflib
Expand Down
1 change: 0 additions & 1 deletion pabutools/election/profile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
"""


from pabutools.election.profile.profile import AbstractProfile, Profile, MultiProfile
from pabutools.election.profile.approvalprofile import (
AbstractApprovalProfile,
Expand Down
1 change: 1 addition & 0 deletions pabutools/election/profile/approvalprofile.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Approval profiles, i.e., collections of approval ballots.
"""

from __future__ import annotations

from abc import ABC
Expand Down
Loading

0 comments on commit c96d930

Please sign in to comment.