Skip to content

Commit 7d5ae8d

Browse files
committed
Finishing the doc
1 parent 0e20bde commit 7d5ae8d

30 files changed

Lines changed: 2844 additions & 517 deletions

docs-source/source/reference/rules/index.rst

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Meta Rule
1616
:members:
1717
:show-inheritance:
1818

19-
.. autoclass:: trivoting.rules.ilp_schemes.PuLPSolvers
19+
.. autoclass:: trivoting.rules.ilp_schemes.ILPSolver
2020
:members:
2121
:show-inheritance:
2222

@@ -74,3 +74,27 @@ Tax-Based Rules
7474
.. autofunction:: trivoting.rules.tax_rules.tax_method_of_equal_shares
7575

7676
.. autofunction:: trivoting.rules.tax_rules.tax_sequential_phragmen
77+
78+
79+
Chamberlin-Courant Rule
80+
-----------------------
81+
82+
.. automodule:: trivoting.rules.chamberlin_courant
83+
84+
.. autofunction:: trivoting.rules.chamberlin_courant.chamberlin_courant
85+
86+
.. autoclass:: trivoting.rules.chamberlin_courant.ChamberlinCourantILPBuilder
87+
88+
.. autofunction:: trivoting.rules.chamberlin_courant.chamberlin_courant_brute_force
89+
90+
91+
Max Net Support Rule
92+
--------------------
93+
94+
.. automodule:: trivoting.rules.max_net_support
95+
96+
.. autofunction:: trivoting.rules.max_net_support.max_net_support
97+
98+
.. autofunction:: trivoting.rules.max_net_support.max_net_support_ilp
99+
100+
.. autoclass:: trivoting.rules.max_net_support.MaxNetSupportILPBuilder

docs-source/source/usage.rst

Lines changed: 155 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -364,190 +364,248 @@ You can add or extend the selected and rejected sets dynamically:
364364
Rules
365365
-----
366366

367-
Rules are used to determine selections based on a profile. They all take a `max_size_selection` argument that indicates
368-
the maximum number of alternatives that can be selected. Since ballots can indicate disapproval, rules need not select
369-
exactly the desired number of alternatives.
367+
Rules determine which alternatives are selected based on the preferences of voters expressed in a trichotomous profile
368+
(approvals, neutral, disapprovals). All rules take a ``max_size_selection`` argument, indicating the maximum number of
369+
alternatives that can be selected. Since ballots can include disapproval, rules are not required to select exactly that
370+
number.
370371

371-
We try to always offer the possibility to ask for resolute or irresolute outcomes, via the `resoluteness` argument.
372-
The tie breaking rule to use can typically be specified via the `tie_breaking`
373-
argument (not all the rules support that). See the :py:mod:`~trivoting.tiebreaking` module for more information.
374-
Initial selection can usually be passed to the rules via the `initial` argument. The rule then completes the initial
375-
selection.
372+
Most rules support the following common options:
376373

377-
Thiele Methods
378-
^^^^^^^^^^^^^^
374+
- ``resoluteness``: whether to return a single selection (``True``, default) or all tied optimal selections (``False``).
375+
- ``tie_breaking``: tie-breaking rule, typically a function from the :py:mod:`~trivoting.tiebreaking` module.
376+
- ``initial_selection``: a partially fixed selection, allowing some alternatives to be pre-selected or rejected.
379377

380-
The :py:func:`~trivoting.rules.thiele.thiele_method` function implements a general scheme to compute the outcome of a Thiele method
381-
using Integer Linear Programming (ILP).
378+
Thiele Rules
379+
^^^^^^^^^^^^
382380

383-
As usual, you need to pass a trichotomous profile (implementing the :py:class:`~trivoting.election.trichotomous_profile.AbstractTrichotomousProfile` interface)
384-
and specify the maximum number of alternatives to select. You also need an additional `ild_builder_class` argument
385-
that takes a subclass of the abstract class :py:class:`~trivoting.rules.thiele.ThieleILPBuilder`.
381+
We provide two implementations for Thiele rules: exact via ILP sovlers and sequential approximations.
386382

387-
.. code-block:: python
383+
A Thiele rule is described via subclasses of the :py:class:`~trivoting.rules.thiele.ThieleScore` class.
384+
The following scoring functions are currently available:
385+
386+
- :py:class:`~trivoting.rules.thiele.PAVScoreKraiczy2025`: PAV rule combining approvals and implicit support from disapprovals of unselected options.
387+
- :py:class:`~trivoting.rules.thiele.PAVScoreTalmonPaige2021`: PAV with negative contributions for disapproved but selected alternatives.
388+
- :py:class:`~trivoting.rules.thiele.PAVScoreHervouin2025`: PAV variant combining approval satisfaction and mitigation of disapproval.
389+
- :py:class:`~trivoting.rules.thiele.ApprovalThieleScore`: Simplified Thiele rule maximizing total approvals.
390+
- :py:class:`~trivoting.rules.thiele.NetSupportThieleScore`: Thiele rule maximizing net support (approvals minus disapprovals).
388391

389-
from trivoting.rules import thiele_method, PAVILPKraiczy2025
392+
Optimal Thiele Rules
393+
~~~~~~~~~~~~~~~~~~~~
390394

391-
selection = thiele_method(profile, max_size_selection=3, ilp_builder_class=PAVILPKraiczy2025)
395+
The :py:func:`~trivoting.rules.thiele.thiele_method` function implements a general Thiele rule scheme using an Integer
396+
Linear Programming (ILP) solver.
392397

393-
print(result)
398+
You must provide:
394399

395-
By default, the function returns a single optimal selection (resolute). To retrieve all optimal selections (if multiple exist), set `resoluteness=False`.
400+
- a trichotomous profile (implementing :py:class:`~trivoting.election.trichotomous_profile.AbstractTrichotomousProfile`),
401+
- a ``max_size_selection``, and
402+
- a subclass of :py:class:`~trivoting.rules.thiele.ThieleScore` via the argument ``thiele_score_class``.
396403

397404
.. code-block:: python
398405
399-
results = thiele_method(profile, max_size_selection=3, ilp_builder_class=PAVILPKraiczy2025, resoluteness=False)
406+
from trivoting.rules import thiele_method, PAVScoreKraiczy2025
400407
401-
for selection in results:
402-
print(selection)
408+
selection = thiele_method(profile, max_size_selection=3, thiele_score_class=PAVScoreKraiczy2025)
403409
404-
You can fix certain alternatives to be selected or rejected by providing an initial :py:class:`~trivoting.election.selection.Selection` object.
410+
You can use ``resoluteness=False`` to obtain an irresolute outcome:
405411

406412
.. code-block:: python
407413
408-
from trivoting.election import Selection
409-
410-
initial = Selection(selected=[a1], implicit_reject=True)
414+
results = thiele_method(
415+
profile,
416+
max_size_selection=3,
417+
thiele_score_class=PAVScoreKraiczy2025,
418+
resoluteness=False
419+
)
411420
412-
result = thiele_method(profile, max_size_selection=3, ilp_builder_class=PAVILPKraiczy2025, initial_selection=initial)
421+
To fix certain alternatives as already selected or rejected, provide an initial
422+
:py:class:`~trivoting.election.selection.Selection`:
413423

414-
Additional options for the ILP solver can be passed:
424+
.. code-block:: python
415425
416-
- ``verbose=True`` enables output from the ILP solver.
417-
- ``max_seconds`` can be used to limit the computation time (default: 600 seconds).
426+
from trivoting.election import Selection
418427
419-
.. code-block:: python
428+
initial = Selection(selected=[a1], implicit_reject=True)
420429
421430
result = thiele_method(
422431
profile,
423-
max_size_selection=4,
424-
ilp_builder_class=PAVILPKraiczy2025
425-
resoluteness=True,
426-
verbose=True,
427-
max_seconds=300
432+
max_size_selection=3,
433+
thiele_score_class=PAVScoreKraiczy2025,
434+
initial_selection=initial
428435
)
429436
430-
Sequential Phragmén
431-
^^^^^^^^^^^^^^^^^^^
437+
Additional options for the ILP solver:
432438

433-
The :py:func:`~trivoting.rules.phragmen.sequential_phragmen` function implements the sequential Phragmén rule
434-
adapted to trichotomous preferences. This method distributes load among voters to achieve proportional representation.
439+
- ``verbose=True`` enables solver output.
440+
- ``max_seconds`` sets a time limit (default is 600 seconds).
435441

436-
You need to pass a trichotomous profile (implementing the :py:class:`~trivoting.election.trichotomous_profile.AbstractTrichotomousProfile` interface)
437-
and specify the maximum number of alternatives to select.
442+
Sequential Thiele Rules
443+
~~~~~~~~~~~~~~~~~~~~~~~
444+
445+
For heuristic computation, :py:func:`~trivoting.rules.thiele.sequential_thiele` implements a sequential version
446+
of Thiele rules. The same :py:class:`~trivoting.rules.thiele.ThieleScore` subclasses can be
447+
used here.
438448

439449
.. code-block:: python
440450
441-
from trivoting.rules import sequential_phragmen
451+
from trivoting.rules import sequential_thiele, PAVScoreTalmonPaige2021
442452
443-
selection = sequential_phragmen(profile, max_size_selection=3)
453+
selection = sequential_thiele(
454+
profile,
455+
max_size_selection=4,
456+
thiele_score_class=PAVScoreTalmonPaige2021
457+
)
458+
459+
These rules also support tie breaking mechanisms and resoluteness.
444460

461+
Max Net Support Rule
462+
^^^^^^^^^^^^^^^^^^^^
445463

446-
By default, the function returns a single optimal selection (resolute). To retrieve all optimal selections (if multiple exist), set `resoluteness=False`.
464+
The :py:func:`~trivoting.rules.max_net_support.max_net_support` rule returns selectionsm aximising the total net support,
465+
that is, the sum over all voters of the number of approved and selected alternatives minus the number of disapproved and selected ones.
447466

448467
.. code-block:: python
449468
450-
results = sequential_phragmen(profile, max_size_selection=3, resoluteness=False)
469+
from trivoting.rules import max_net_support
451470
452-
for selection in results:
453-
print(selection)
454-
# Example output:
455-
# {[a1, a2, a5]} // {implicit}
456-
# {[a1, a3, a5]} // {implicit}
471+
selection = max_net_support(profile, max_size_selection=3)
457472
458-
You can fix certain alternatives to be selected or rejected by providing an initial :py:class:`~trivoting.election.selection.Selection` object.
473+
There is also an ILP version used mostly for debugging and comparison purposes.
474+
Note that it can also be computed as the outcome of a Thiele rule.
459475

460476
.. code-block:: python
461477
462-
from trivoting.election import Selection
478+
from trivoting.rules.max_net_support import max_net_support_ilp
479+
from trivoting.rules.thiele import NetSupportThieleScore
480+
from trivoting.rules import thiele
463481
464-
initial = Selection(selected=[a1], implicit_reject=True)
482+
selection = max_net_support_ilp(profile, max_size_selection=3)
483+
selection_same = thiele(profile, max_size_selection=3, thiele_score_class=NetSupportThieleScore)
465484
466-
result = sequential_phragmen(profile, max_size_selection=3, initial_selection=initial)
467485
468-
Additional options include:
486+
Sequential Phragmén
487+
^^^^^^^^^^^^^^^^^^^
469488

470-
- ``initial_loads``: a list of numeric values to set initial loads for the voters (default is 0 for all).
471-
- ``tie_breaking``: a custom tie-breaking rule. Defaults to lexicographic tie-breaking.
489+
The :py:func:`~trivoting.rules.phragmen.sequential_phragmen` function implements the Sequential Phragmén rule adapted
490+
to trichotomous preferences. This rule distributes “load” among voters to achieve proportional representation.
472491

473492
.. code-block:: python
474493
475-
from trivoting.tiebreaking import lexico_tie_breaking
494+
from trivoting.rules import sequential_phragmen
476495
477-
result = sequential_phragmen(
478-
profile,
479-
max_size_selection=4,
480-
initial_loads=[0, 0, 0],
481-
initial_selection=initial,
482-
tie_breaking=lexico_tie_breaking,
483-
resoluteness=True
484-
)
496+
selection = sequential_phragmen(profile, max_size_selection=3)
497+
498+
Additional options:
485499

500+
- ``resoluteness``: return one or all tied selections.
501+
- ``tie_breaking``: custom tie-breaking rule (default: :py:func:`~trivoting.tiebreaking.lexico_tie_breaking`).
502+
- ``initial_selection``: partially fixed selection.
503+
- ``initial_loads``: optional initial load vector (defaults to 0 for all voters).
486504

487-
Tax PB Rule Scheme
488-
^^^^^^^^^^^^^^^^^^
505+
Tax-Based Rules
506+
^^^^^^^^^^^^^^^
489507

490-
The :py:func:`~trivoting.rules.tax_rules.tax_pb_rule_scheme` function implements a generic scheme for applying
491-
participatory budgeting (PB) rules to trichotomous profiles converted into PB instance via an opposition tax.
508+
The :py:func:`~trivoting.rules.tax_rules.tax_pb_rule_scheme` function adapts participatory budgeting (PB) rules to
509+
trichotomous preferences by introducing a cost for each project in the from of a tax.
492510

493-
This method first translates the trichotomous profile into a PB instance by assigning higher costs to alternatives with
494-
lower net support (approvals minus disapprovals). It then applies a PB rule from the `pabutools` package and converts
495-
the resulting budget allocations back into selections of alternatives.
511+
This method converts the profile into a PB instance (using the ``pabutools`` package), applies a PB rule, and converts
512+
the result back to a trichotomous selection.
513+
514+
The tax function, defining the cost of the projects on the PB side, is defined as a subclass of the
515+
:py:class:`~trivoting.rules.tax_rules.TaxFunction`.
496516

497-
You need to pass a trichotomous profile (implementing the :py:class:`~trivoting.election.trichotomous_profile.AbstractTrichotomousProfile` interface),
498-
specify the maximum number of alternatives to select and the PB rule to use (that may require kwargs).
499517

500518
.. code-block:: python
501519
502-
from trivoting.rules import tax_pb_rule_scheme
520+
from trivoting.rules import tax_pb_rule_scheme, TaxKraiczy2025
503521
from pabutools.rules import method_of_equal_shares
504522
505523
selection = tax_pb_rule_scheme(
506524
profile,
507525
max_size_selection=5,
526+
tax_function=TaxKraiczy2025,
508527
pb_rule=method_of_equal_shares,
509528
pb_rule_kwargs={"sat_class": pb_election.Cardinality_Sat}
510529
)
511530
512-
Additionally, resoluteness, initial selection and tie-breaking can be specified:
531+
Two specialized wrappers are available for convenience:
532+
533+
- :py:func:`~trivoting.rules.tax_rules.tax_method_of_equal_shares`
534+
- :py:func:`~trivoting.rules.tax_rules.tax_sequential_phragmen`
513535

514536
.. code-block:: python
515537
538+
from trivoting.rules import tax_method_of_equal_shares
516539
from trivoting.tiebreaking import lexico_tie_breaking
517540
518-
selection = tax_pb_rule_scheme(
541+
selection = tax_method_of_equal_shares(
519542
profile,
520543
max_size_selection=5,
521-
pb_rule=method_of_equal_shares,
522-
pb_rule_kwargs={"sat_class": pb_election.Cardinality_Sat},
523544
resoluteness=False,
524-
tie_breaking=lexico_tie_breaking,
525-
initial_selection=selection
545+
tie_breaking=lexico_tie_breaking
526546
)
527547
528-
Two tax-based rules are defined by default: :py:func:`~trivoting.rules.tax_rules.tax_method_of_equal_shares` and
529-
:py:func:`~trivoting.rules.tax_rules.tax_sequential_phragmen`.
548+
549+
Chamberlin–Courant Rule
550+
^^^^^^^^^^^^^^^^^^^^^^^
551+
552+
The :py:func:~`trivoting.rules.chamberlin_courant.chamberlin_courant` function implements the Chamberlin–Courant rule for trichotomous preferences.
553+
It computes selections that maximizes the number of covered voters, that is, voters who have strictly more approved and selected alternatives than disapproved and selected ones.
554+
555+
The outcome of the rule is computed through an ILP solver.
556+
557+
.. code-block:: python
558+
559+
from trivoting.rules import chamberlin_courant
560+
561+
selection = chamberlin_courant(profile, max_size_selection=3)
562+
563+
Irresolute outcomes can be obtained by using `resoluteness=False`:
564+
565+
.. code-block:: python
566+
567+
results = chamberlin_courant(profile, max_size_selection=3, resoluteness=False)
568+
569+
You can fix certain alternatives to be selected or rejected by providing an initial
570+
:py:class:`~trivoting.election.selection.Selection` object.
530571

531572
.. code-block:: python
532573
533-
from trivoting.rules import tax_pb_rule_scheme
574+
from trivoting.election import Selection
534575
535-
selection_mes = tax_method_of_equal_shares(
576+
initial = Selection(selected=[a1], implicit_reject=True)
577+
578+
result = chamberlin_courant(
536579
profile,
537-
max_size_selection=5,
538-
resoluteness=False,
539-
tie_breaking=lexico_tie_breaking,
540-
initial_selection=selection
580+
max_size_selection=3,
581+
initial_selection=initial,
582+
resoluteness=True
541583
)
542584
543-
selection_phrag = tax_sequential_phragmen(
585+
Additional options for the ILP solver can be passed:
586+
587+
- `verbose=True` enables solver output;
588+
- `max_seconds` limits the solver runtime (default is 600 seconds).
589+
590+
.. code-block:: python
591+
592+
result = chamberlin_courant(
544593
profile,
545-
max_size_selection=5,
546-
resoluteness=False,
547-
tie_breaking=lexico_tie_breaking,
548-
initial_selection=selection
594+
max_size_selection=4,
595+
initial_selection=initial,
596+
resoluteness=True,
597+
verbose=True,
598+
max_seconds=300
549599
)
550600
601+
A brute-force variant is also available for testing purposes:
602+
603+
.. code-block:: python
604+
605+
from trivoting.rules.chamberlin_courant import chamberlin_courant_brute_force
606+
607+
selection = chamberlin_courant_brute_force(profile, max_size_selection=3)
608+
551609
Tie-Breaking
552610
------------
553611

docs/.doctrees/environment.pickle

39.2 KB
Binary file not shown.
174 KB
Binary file not shown.

docs/.doctrees/usage.doctree

12.2 KB
Binary file not shown.

docs/_modules/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,9 @@ <h1>All modules for which code is available</h1>
290290
<li><a href="trivoting/election/trichotomous_ballot.html">trivoting.election.trichotomous_ballot</a></li>
291291
<li><a href="trivoting/election/trichotomous_profile.html">trivoting.election.trichotomous_profile</a></li>
292292
<li><a href="trivoting/fractions.html">trivoting.fractions</a></li>
293+
<li><a href="trivoting/rules/chamberlin_courant.html">trivoting.rules.chamberlin_courant</a></li>
293294
<li><a href="trivoting/rules/ilp_schemes.html">trivoting.rules.ilp_schemes</a></li>
295+
<li><a href="trivoting/rules/max_net_support.html">trivoting.rules.max_net_support</a></li>
294296
<li><a href="trivoting/rules/phragmen.html">trivoting.rules.phragmen</a></li>
295297
<li><a href="trivoting/rules/tax_rules.html">trivoting.rules.tax_rules</a></li>
296298
<li><a href="trivoting/rules/thiele.html">trivoting.rules.thiele</a></li>

0 commit comments

Comments
 (0)