Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions elsim/_docstrings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Shared :mod:`docrep` snippets for recurring NumPy-style parameter docs."""

from docrep import DocstringProcessor

docstrings = DocstringProcessor()


@docstrings.get_sections(base='utilities_2d', sections=['Parameters'])
@docstrings.dedent
def _utilities_2d_param_doc():
"""
Shared documentation for the ``utilities`` parameter.

Parameters
----------
utilities : array_like
A 2D collection of utilities.

Rows represent voters, and columns represent candidate IDs.
Higher utility numbers mean greater approval of that candidate by that
voter.
"""
pass


docstrings.keep_params('utilities_2d.parameters', 'utilities')
64 changes: 33 additions & 31 deletions elsim/elections/elections.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import numpy as np
from scipy.spatial.distance import cdist as _cdist

from elsim._docstrings import docstrings
from elsim.strategies import honest_rankings as _honest_rankings

elections_rng = np.random.default_rng()
Expand Down Expand Up @@ -30,16 +31,11 @@ def _check_random_state(seed):
'numpy.random.Generator instance')


def random_utilities(n_voters, n_cands, random_state=None):
@docstrings.get_sections(base='election_common', sections=['Parameters'])
@docstrings.dedent
def _election_common_param_doc():
"""
Generate utilities using the impartial culture / random society model.

The random society [1]_ or random uniform utilities [2]_ model selects
independent candidate utilities for each voter from a uniform distribution
in the interval [0, 1).

This model is unrealistic, but is commonly used because it has some
worst-case properties and is comparable between researchers. [3]_
Shared parameter documentation for election generators.

Parameters
----------
Expand All @@ -54,6 +50,29 @@ def random_utilities(n_voters, n_cands, random_state=None):
If None (default), an existing Generator is used.
If `random_state` is already a Generator instance, then
that object is used.
"""
pass


docstrings.keep_params('election_common.parameters', 'n_voters', 'n_cands')
docstrings.keep_params('election_common.parameters', 'random_state')


@docstrings.dedent
def random_utilities(n_voters, n_cands, random_state=None):
"""
Generate utilities using the impartial culture / random society model.

The random society [1]_ or random uniform utilities [2]_ model selects
independent candidate utilities for each voter from a uniform distribution
in the interval [0, 1).

This model is unrealistic, but is commonly used because it has some
worst-case properties and is comparable between researchers. [3]_

Parameters
----------
%(election_common.parameters)s

Returns
-------
Expand Down Expand Up @@ -92,6 +111,7 @@ def random_utilities(n_voters, n_cands, random_state=None):
return rng.random((n_voters, n_cands))


@docstrings.dedent
def impartial_culture(n_voters, n_cands, random_state=None):
"""
Generate ranked ballots using the impartial culture / random society model.
Expand All @@ -104,17 +124,7 @@ def impartial_culture(n_voters, n_cands, random_state=None):

Parameters
----------
n_voters : int
Number of voters
n_cands : int
Number of candidates
random_state : {None, int, np.random.Generator}, optional
Initializes the random number generator. If `random_state` is int, a
new Generator instance is used, seeded with its value. (If the same
int is given twice, the function will return the same values.)
If None (default), an existing Generator is used.
If `random_state` is already a Generator instance, then
that object is used.
%(election_common.parameters)s

Returns
-------
Expand Down Expand Up @@ -164,17 +174,15 @@ def impartial_culture(n_voters, n_cands, random_state=None):
return rankings


@docstrings.dedent
def normal_electorate(n_voters, n_cands, dims=2, corr=0.0, disp=1.0,
random_state=None):
"""
Generate normally distributed voters and candidates in issue space.

Parameters
----------
n_voters : int
Number of voters
n_cands : int
Number of candidates
%(election_common.parameters.n_voters|n_cands)s
dims : int
Number of dimensions
corr : float
Expand All @@ -184,13 +192,7 @@ def normal_electorate(n_voters, n_cands, dims=2, corr=0.0, disp=1.0,
standard deviations. For example, 1.0 means they are distributed by
the same amount, while 0.5 means that candidates are more tightly
concentrated than voters.
random_state : {None, int, np.random.Generator}, optional
Initializes the random number generator. If `random_state` is int, a
new Generator instance is used, seeded with its value. (If the same
int is given twice, the function will return the same values.)
If None (default), an existing Generator is used.
If `random_state` is already a Generator instance, then
that object is used.
%(election_common.parameters.random_state)s

Returns
-------
Expand Down
12 changes: 4 additions & 8 deletions elsim/methods/utility_winner.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import numpy as np

from elsim.methods._common import (_all_indices, _get_tiebreak, _no_tiebreak,
_order_tiebreak_keep, _random_tiebreak)
from elsim._docstrings import docstrings
from elsim.methods._common import _all_indices, _get_tiebreak, _no_tiebreak, _order_tiebreak_keep, _random_tiebreak

_tiebreak_map = {'order': _order_tiebreak_keep,
'random': _random_tiebreak,
None: _no_tiebreak}


@docstrings.dedent
def utility_winner(utilities, tiebreaker=None):
"""
Find the utilitarian winner of an election. (Dummy "election method").
Expand All @@ -17,12 +18,7 @@ def utility_winner(utilities, tiebreaker=None):

Parameters
----------
utilities : array_like
A 2D collection of utilities.

Rows represent voters, and columns represent candidate IDs.
Higher utility numbers mean greater approval of that candidate by that
voter.
%(utilities_2d.parameters.utilities)s

tiebreaker : {'random', 'order', None}, optional
If there is a tie, and `tiebreaker` is ``'random'``, a random finalist
Expand Down
34 changes: 10 additions & 24 deletions elsim/strategies/strategies.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import numpy as np

from elsim._docstrings import docstrings


@docstrings.dedent
def honest_rankings(utilities):
"""
Convert utilities into rankings using honest strategy.

Parameters
----------
utilities : array_like
A 2D collection of utilities.

Rows represent voters, and columns represent candidate IDs.
Higher utility numbers mean greater approval of that candidate by that
voter.
%(utilities_2d.parameters.utilities)s

Returns
-------
Expand Down Expand Up @@ -57,6 +55,7 @@ def honest_rankings(utilities):
return np.argsort(utilities)[:, ::-1].astype(np.uint8)


@docstrings.dedent
def honest_normed_scores(utilities, max_score=5):
"""
Convert utilities into scores using honest (but normalized) strategy.
Expand All @@ -67,12 +66,7 @@ def honest_normed_scores(utilities, max_score=5):

Parameters
----------
utilities : array_like
A 2D collection of utilities.

Rows represent voters, and columns represent candidate IDs.
Higher utility numbers mean greater approval of that candidate by that
voter.
%(utilities_2d.parameters.utilities)s

max_score : int, optional
The highest score on the ballot. If `max_score` = 3, the possible
Expand Down Expand Up @@ -125,6 +119,7 @@ def honest_normed_scores(utilities, max_score=5):
return scores


@docstrings.dedent
def approval_optimal(utilities):
"""
Convert utilities to optimal approval voting ballots.
Expand All @@ -135,12 +130,7 @@ def approval_optimal(utilities):

Parameters
----------
utilities : array_like
A 2D collection of utilities.

Rows represent voters, and columns represent candidate IDs.
Higher utility numbers mean greater approval of that candidate by that
voter.
%(utilities_2d.parameters.utilities)s

Returns
-------
Expand Down Expand Up @@ -185,6 +175,7 @@ def approval_optimal(utilities):
return approvals


@docstrings.dedent
def vote_for_k(utilities, k):
"""
Convert utilities to approval voting ballots, approving top k candidates.
Expand All @@ -194,12 +185,7 @@ def vote_for_k(utilities, k):

Parameters
----------
utilities : array_like
A 2D collection of utilities.

Rows represent voters, and columns represent candidate IDs.
Higher utility numbers mean greater approval of that candidate by that
voter.
%(utilities_2d.parameters.utilities)s
k : int or 'half'
The number of candidates approved of by each voter, or 'half' to make
the number dependent on the number of candidates. If a negative int,
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ classifiers = [
"Operating System :: OS Independent",
]
dependencies = [
"docrep>=0.3.2",
"numpy>=1.17",
"scipy",
]
Expand Down
21 changes: 21 additions & 0 deletions tests/test_docstrings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Tests for :mod:`docrep`-backed shared docstring anchors (see ``elsim._docstrings``)."""


def test_utilities_2d_doc_anchor_executes():
from elsim._docstrings import _utilities_2d_param_doc

assert _utilities_2d_param_doc() is None


def test_election_common_doc_anchor_executes():
from elsim.elections.elections import _election_common_param_doc

assert _election_common_param_doc() is None


def test_docrep_substitutions_present():
from elsim.elections import random_utilities
from elsim.strategies import honest_rankings

assert 'n_voters : int' in (random_utilities.__doc__ or '')
assert 'utilities : array_like' in (honest_rankings.__doc__ or '')
Loading