Source code for trivoting.election.preflib
from __future__ import annotations
from preflibtools.instances import CategoricalInstance, get_parsed_instance
from trivoting.election.alternative import Alternative
from trivoting.election.trichotomous_ballot import TrichotomousBallot
from trivoting.election.trichotomous_profile import TrichotomousProfile
[docs]
def cat_preferences_to_trichotomous_ballot(
pref: tuple[tuple[int]],
alt_map: dict[int, Alternative]
) -> TrichotomousBallot:
"""
Converts a categorical preference from PrefLib into a trichotomous ballot.
The first category in the preference is treated as the set of approved alternatives.
If a second category is present, it is treated as neutral and ignored in the ballot.
If a third category is present, it is treated as the set of disapproved alternatives.
Parameters
----------
pref : tuple[tuple[int]]
The categorical preferences, typically a tuple of up to 3 ranked groups of alternative IDs.
alt_map : dict[int, Alternative]
A mapping from PrefLib integer IDs to Alternative objects.
Returns
-------
TrichotomousBallot
The corresponding trichotomous ballot.
Raises
------
ValueError
If the number of categories is not between 1 and 3.
"""
if len(pref) == 0 or len(pref) > 3:
raise ValueError("Only categorical preferences between 1 and 3 categories can be converted to"
f"a trichotomous ballot. Pref {pref} has {len(pref)} categories.")
ballot = TrichotomousBallot(approved=[alt_map[j] for j in pref[0]])
if len(pref) < 2:
return ballot
ballot.disapproved = [alt_map[j] for j in pref[-1]]
return ballot
[docs]
def cat_instance_to_trichotomous_profile(
cat_instance: CategoricalInstance
) -> TrichotomousProfile:
"""
Converts a PrefLib CategoricalInstance into a trichotomous profile. The PrefLib instance should have 1, 2 or 3
categories. If there is a single categories, it is assumed to represent the approved alternatives. If there are
2 categories, it is assumed that they represent the approved and neutral alternatives. In case of 3 categories,
the categories are assumed to represent approved, neutral and disapproved alternatives, in that order.
Each ballot in the categorical instance is converted using
`cat_preferences_to_trichotomous_ballot`.
Parameters
----------
cat_instance : CategoricalInstance
A parsed categorical instance from PrefLib.
Returns
-------
TrichotomousProfile
A profile composed of trichotomous ballots.
"""
if cat_instance.num_categories == 0 or cat_instance.num_categories > 3:
raise ValueError("Only categorical preferences between 1 and 3 categories can be converted to"
f"a trichotomous profile. Categorical instance {cat_instance} has "
f"{cat_instance.num_categories} categories.")
alt_map = {j: Alternative(j) for j in cat_instance.alternatives_name}
profile = TrichotomousProfile(alternatives=alt_map.values())
for p, m in cat_instance.multiplicity.items():
for _ in range(m):
profile.append(cat_preferences_to_trichotomous_ballot(p, alt_map))
return profile
[docs]
def parse_preflib(file_path: str) -> TrichotomousProfile:
"""
Parses a PrefLib file and returns the corresponding trichotomous profile.
The file is parsed using `preflibtools.get_parsed_instance`, and only
categorical instances with 1–3 categories are supported.
Parameters
----------
file_path : str
The file path to a PrefLib categorical instance.
Returns
-------
TrichotomousProfile
A trichotomous profile built from the given file.
"""
instance = get_parsed_instance(file_path, autocorrect=True)
if isinstance(instance, CategoricalInstance):
return cat_instance_to_trichotomous_profile(instance)
raise ValueError(f"PrefLib instances of type {type(instance)} cannot be converted to trichotomous profiles.")