Source code for pabutools.election.profile.ordinalprofile

from __future__ import annotations

from abc import ABC
from collections.abc import Iterable

from pabutools.election.ballot import (
    Ballot,
    OrdinalBallot,
    FrozenBallot,
    FrozenOrdinalBallot,
)
from pabutools.election.profile.profile import Profile, MultiProfile, AbstractProfile
from pabutools.election.instance import Instance


[docs] class AbstractOrdinalProfile(AbstractProfile, ABC): """ Abstract class for ordinal profiles. Stores the metadata and the methods specific to ordinal profiles. Parameters ---------- legal_min_length : int, optional The minimum length of an ordinal ballot per the rules of the election. Defaults to `None`. legal_max_length : int, optional The maximum length of an ordinal ballot per the rules of the election. Defaults to `None`. Attributes ---------- legal_min_length : int The minimum length of an ordinal ballot per the rules of the election. legal_max_length : int The maximum length of an ordinal ballot per the rules of the election. """ def __init__( self, legal_min_length: int | None = None, legal_max_length: int | None = None, ): AbstractProfile.__init__(self) ABC.__init__(self) self.legal_min_length = legal_min_length self.legal_max_length = legal_max_length
[docs] class OrdinalProfile(Profile, AbstractOrdinalProfile): """ A profile of ordinal ballots, that is, a list of ordinal ballots per voters. See the class :py:class:`~pabutools.election.ballot.ordinalballot.OrdinalBallot` for more details on ordinal ballots. This class inherits from the Python `list` class and can thus be used as one. Parameters ---------- init : Iterable[:py:class:`~pabutools.election.ballot.ordinalballot.OrdinalBallot`], optional An iterable of :py:class:`~pabutools.election.ballot.ordinalballot.OrdinalBallot` that is used an initializer for the list. If activated, the types of the ballots are validated. In case an :py:class:`~pabutools.election.profile.profile.AbstractProfile` object is passed, the additional attributes are also copied (except if the corresponding keyword arguments have been given). instance : :py:class:`~pabutools.election.instance.Instance`, optional The instance related to the profile. Defaults to `Instance()`. ballot_validation : bool, optional Boolean indicating whether ballots should be validated before being added to the profile. Defaults to `True`. ballot_type : type[:py:class:`~pabutools.election.ballot.ballot.AbstractBallot`], optional The type that the ballots are validated against. If `ballot_validation` is `True` and a ballot of a type that is not a subclass of `ballot_type` is added, an exception will be raised. Defaults to `OrdinalBallot`. legal_min_length : int, optional The minimum length of an ordinal ballot per the rules of the election. Defaults to `None`. legal_max_length : int, optional The maximum length of an ordinal ballot per the rules of the election. Defaults to `None`. Attributes ---------- instance : :py:class:`~pabutools.election.instance.Instance` The instance related to the profile. ballot_validation : bool Boolean indicating whether ballots should be validated before being added to the profile. ballot_type : type[:py:class:`~pabutools.election.ballot.ballot.AbstractBallot`] The type that the ballots are validated against. If `ballot_validation` is `True` and a ballot of a type that is not a subclass of `ballot_type` is added, an exception will be raised. legal_min_length : int The minimum length of an ordinal ballot per the rules of the election. legal_max_length : int The maximum length of an ordinal ballot per the rules of the election. """ def __init__( self, init: Iterable[OrdinalBallot] = (), instance: Instance | None = None, ballot_validation: bool | None = None, ballot_type: type[Ballot] | None = None, legal_min_length: int | None = None, legal_max_length: int | None = None, ) -> None: if legal_min_length is None and isinstance(init, AbstractOrdinalProfile): legal_min_length = init.legal_min_length if legal_max_length is None and isinstance(init, AbstractOrdinalProfile): legal_max_length = init.legal_max_length AbstractOrdinalProfile.__init__( self, legal_min_length=legal_min_length, legal_max_length=legal_max_length, ) if ballot_validation is None: if isinstance(init, AbstractProfile): ballot_validation = init.ballot_validation else: ballot_validation = True if ballot_type is None: if isinstance(init, AbstractProfile): ballot_type = init.ballot_type else: ballot_type = OrdinalBallot if instance is None and isinstance(init, AbstractOrdinalProfile): instance = init.instance Profile.__init__( self, init=init, instance=instance, ballot_validation=ballot_validation, ballot_type=ballot_type, )
[docs] def as_multiprofile(self): """ Converts the profile into a :py:class:`~pabutools.election.profile.ordinalprofile.OrdinalMultiProfile`. Returns ------- :py:class:`~pabutools.election.profile.ordinalprofile.OrdinalMultiProfile` The multiprofile corresponding to the profile. """ return OrdinalMultiProfile( instance=self.instance, profile=self, ballot_validation=self.ballot_validation, ballot_type=FrozenOrdinalBallot, legal_min_length=self.legal_min_length, legal_max_length=self.legal_max_length, )
@classmethod def _wrap_methods(cls, names): def wrap_method_closure(name): def inner(self, *args): result = getattr(super(cls, self), name)(*args) if isinstance(result, list) and not isinstance(result, cls): result = cls( result, instance=self.instance, ballot_validation=self.ballot_validation, ballot_type=self.ballot_type, legal_min_length=self.legal_min_length, legal_max_length=self.legal_max_length, ) return result inner.fn_name = name setattr(cls, name, inner) for n in names: wrap_method_closure(n)
OrdinalProfile._wrap_methods( [ "__add__", "__iadd__", "__imul__", "__mul__", "__reversed__", "__rmul__", "copy", "reverse", "__getitem__", ] )
[docs] class OrdinalMultiProfile(MultiProfile, AbstractOrdinalProfile): """ A multiprofile of ordinal ballots, that is, a multiset of ordinal ballots together with their multiplicity. Ballots needs to be hashable, so the class :py:class:`~pabutools.election.ballot.ordinalballot.FrozenOrdinalBallot` should be used by default here. This class inherits from the Python `Counter` class and can thus be used as one. Parameters ---------- init : Iterable[:py:class:`~pabutools.election.ballot.ordinalballot.FrozenOrdinalBallot`], optional An iterable of :py:class:`~pabutools.election.ballot.ordinalballot.FrozenOrdinalBallot` that is used an initializer for the list. If activated, the types of the ballots are validated. In case an :py:class:`~pabutools.election.profile.profile.AbstractProfile` object is passed, the additional attributes are also copied (except if the corresponding keyword arguments have been given). instance : :py:class:`~pabutools.election.instance.Instance`, optional The instance related to the profile. Defaults to `Instance()`. ballot_validation : bool, optional Boolean indicating whether ballots should be validated before being added to the profile. Defaults to `True`. ballot_type : type[:py:class:`~pabutools.election.ballot.ballot.AbstractBallot`], optional The type that the ballots are validated against. If `ballot_validation` is `True` and a ballot of a type that is not a subclass of `ballot_type` is added, an exception will be raised. Defaults to `ForzenOrdinalBallot`. profile: :py:class:`~pabutools.election.profile.ordinalprofile.OrdinalProfile`, optional A profile used to initialise the multiprofile. Some metadata are taken from the profile if they are not specified in the constructor. legal_min_length : int, optional The minimum length of an ordinal ballot per the rules of the election. Defaults to `None`. legal_max_length : int, optional The maximum length of an ordinal ballot per the rules of the election. Defaults to `None`. Attributes ---------- instance : :py:class:`~pabutools.election.instance.Instance` The instance related to the profile. ballot_validation : bool Boolean indicating whether ballots should be validated before being added to the profile. ballot_type : type[:py:class:`~pabutools.election.ballot.ballot.AbstractBallot`] The type that the ballots are validated against. If `ballot_validation` is `True` and a ballot of a type that is not a subclass of `ballot_type` is added, an exception will be raised. legal_min_length : int The minimum length of an ordinal ballot per the rules of the election. legal_max_length : int The maximum length of an ordinal ballot per the rules of the election. """ def __init__( self, init: Iterable[FrozenOrdinalBallot] = (), instance: Instance | None = None, ballot_validation: bool | None = None, ballot_type: type[FrozenBallot] | None = None, profile: OrdinalProfile | None = None, legal_min_length: int | None = None, legal_max_length: int | None = None, ) -> None: if legal_min_length is None: if isinstance(init, AbstractOrdinalProfile): legal_min_length = init.legal_min_length elif profile: legal_min_length = profile.legal_min_length if legal_max_length is None: if isinstance(init, AbstractOrdinalProfile): legal_max_length = init.legal_max_length elif profile: legal_max_length = profile.legal_max_length AbstractOrdinalProfile.__init__( self, legal_min_length=legal_min_length, legal_max_length=legal_max_length, ) if ballot_validation is None: if isinstance(init, AbstractProfile): ballot_validation = init.ballot_validation else: ballot_validation = True if ballot_type is None: if isinstance(init, AbstractProfile): ballot_type = init.ballot_type else: ballot_type = FrozenOrdinalBallot if instance is None: if isinstance(init, AbstractOrdinalProfile): instance = init.instance elif profile: instance = profile.instance MultiProfile.__init__( self, init=init, instance=instance, ballot_validation=ballot_validation, ballot_type=ballot_type, ) if profile is not None: self.extend(profile) @classmethod def _wrap_methods(cls, names): def wrap_method_closure(name): def inner(self, *args): result = getattr(super(cls, self), name)(*args) if isinstance(result, dict) and not isinstance(result, cls): result = cls( result, instance=self.instance, ballot_validation=self.ballot_validation, ballot_type=self.ballot_type, legal_min_length=self.legal_min_length, legal_max_length=self.legal_max_length, ) return result inner.fn_name = name setattr(cls, name, inner) for n in names: wrap_method_closure(n) def __reduce__(self): return self.__class__, ( dict(self), self.instance, self.ballot_validation, self.ballot_type, None, self.legal_min_length, self.legal_max_length, )
OrdinalMultiProfile._wrap_methods( [ "__add__", "__and__", "__iadd__", "__iand__", "__ior__", "__isub__", "__imul__", "__mul__", "__or__", "__ror__", "__sub__", "copy", ] )