# -*- coding: utf-8 -*-
"""
Created on Mon Sep 22 15:24:52 2014
Copyright François Durand 2014-2018
fradurand@gmail.com
This file is part of SVVAMP.
SVVAMP is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
SVVAMP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SVVAMP. If not, see <http://www.gnu.org/licenses/>.
"""
import itertools
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# noinspection PyUnresolvedReferences
from mpl_toolkits.mplot3d import Axes3D
from svvamp.utils.printing import printm, print_title
from svvamp.utils import my_log
from svvamp.utils.util_cache import cached_property
from svvamp.utils.misc import initialize_random_seeds
from svvamp.utils.misc import preferences_ut_to_preferences_rk, preferences_rk_to_preferences_borda_rk, \
preferences_ut_to_preferences_borda_ut, preferences_ut_to_matrix_duels_ut, is_resistant_condorcet, \
matrix_victories_to_smith_set
from svvamp.utils.pseudo_bool import equal_true
from svvamp.preferences.plurality_elimination_engine_profile import PluralityEliminationEngineProfile
[docs]
class Profile(my_log.MyLog):
"""Create a profile of voters with preferences over some candidates.
Parameters
----------
preferences_ut : list of list (or 2d ndarray)
2d array of floats. ``preferences_ut[v, c]`` is the utility of candidate ``c`` as seen by voter ``v``.
preferences_rk : list of list (or 2d ndarray)
2d array of integers. ``preferences_rk[v, k]`` is the candidate at rank ``k`` for voter ``v``.
preferences_borda_rk : list of list (or 2d ndarray)
2d array of integers. ``preferences_borda_rk[v, c]`` gains 1 point for each candidate ``d`` such that
voter ``v`` ranks ``c`` before ``d``. So, these Borda scores are between ``0`` and ``C - 1``.
log_creation : object
Any type (string, list...). Some comments.
labels_candidates : list of str
Names of the candidates.
sort_voters : bool
If True, then when the profile is created, voters are immediately sorted, first by their
strict order of preference (their row in :attr:`~svvamp.Population.preferences_rk`), then by their weak
order of preference (their row in :attr:`~svvamp.Population.preferences_borda_ut`). Note that two voters
having the same strict order may have different weak orders, and vice versa. The objective of this
sorting is to accelerate some algorithms (typically Individual Manipulation). It can also be convenient
to visualize the profile.
Notes
-----
You may enter ``preferences_ut``, ``preferences_rk`` or both to define the preferences of the population.
In all cases, instead of ``preferences_rk``, you may enter ``preferences_borda_rk``.
If you provide ``preferences_rk`` only, then ``preferences_ut`` is set to the corresponding Borda scores
(:attr:`~svvamp.Population.preferences_borda_rk`).
If you provide ``preferences_ut`` only, then ``preferences_rk`` is naturally derived from utilities. If voter
``v`` has a greater utility for candidate ``c`` than for candidate ``d``, then she ranks ``c`` before ``d``.
If voter ``v`` attributes the same utility to several candidates, then the first time the attribute
``preferences_rk`` is called, a random ranking will be decided for these tied candidates (once and for all).
If you provide both, then SVVAMP DOES NOT CHECK that they are consistent. You should ensure that they are,
in the sense that if ``v`` ranks ``c`` before ``d``, then she her utility for ``c`` must be at least equal to
her utility for ``d``. Similarly, you can provide both ``preferences_rk`` and ``preferences_borda_rk``, but
SVVAMP DOES NOT CHECK that they are consistent.
``preferences_rk`` will be used for sincere voting when a voting system accepts only strict orders.
In contrast, for manipulation purposes, ``preferences_ut`` is always used , which means that indifference is
taken into account as such to determine the interest in manipulation. If voter ``v`` attributes the same
utility to candidates ``w`` and ``c``, and if ``w`` is the sincere winner in an election, then ``v`` is not
interested in a manipulation for ``c``.
If all voters have a strict order of preference (in the sense of ut), then for functions below having
variants with suffix ``_ut`` or ``_rk``, the two variants are equivalent.
In some voting systems and in some of the attributes below, we use a process referred as **candidate
tie-breaking** or **CTB** in SVVAMP. It means that lowest-index candidates are favored. When using CTB,
if candidates ``c`` and ``d`` are tied (for example, in an election using Plurality), then ``c`` is favored
over ``d`` iff ``c < d``.
Implications between majority favorite and Condorcet criteria (cf. corresponding attributes below).
::
majority_favorite_ut ==> majority_favorite_ut_ctb
|| || || ||
V || || ||
Resistant Cond. V V ||
|| majority_favorite_rk ==> majority_favorite_rk_ctb ||
|| || || ||
V || || V
Condorcet_ut_abs ==> Condorcet_ut_abs_ctb
|| || || || || ||
|| V V V V ||
V Condorcet_rk ==> Condorcet_rk_ctb V
Condorcet_ut_rel ==> Condorcet_ut_rel_ctb
||
V
Weak Condorcet
||
V
Condorcet-admissible
If all voters have strict orders of preference (in the sense of ut) and if there is an odd number of voters,
then:
* ``majority_favorite_ut``, ``majority_favorite_rk``, ``majority_favorite_ut_ctb`` and
``majority_favorite_rk_ctb`` are equivalent,
* ``Condorcet_ut_abs``, ``Condorcet_ut_abs_ctb``, ``Condorcet_rk``, ``Condorcet_rk_ctb``,
``Condorcet_ut_rel``, ``Condorcet_ut_rel_ctb``, ``Weak Condorcet`` and ``Condorcet-admissible`` are
equivalent.
Examples
--------
>>> profile = Profile(preferences_borda_rk=[[2, 1, 0], [1, 0, 2]])
>>> profile.preferences_rk
array([[0, 1, 2],
[2, 0, 1]])
>>> profile = Profile(preferences_ut=[[1, .3, 0], [.7, 0, 1]])
>>> profile.preferences_rk
array([[0, 1, 2],
[2, 0, 1]])
>>> profile = Profile()
>>> profile.preferences_rk
Traceback (most recent call last):
ValueError: Please provide at least preferences_rk, preferences_borda_rk or preferences_ut.
"""
def __init__(self, preferences_ut=None, preferences_rk=None, preferences_borda_rk=None,
log_creation=None, labels_candidates=None, sort_voters=False):
super().__init__(log_identity="PROFILE")
if preferences_ut is None:
self._preferences_ut_input = None
else:
self._preferences_ut_input = np.array(preferences_ut)
if preferences_rk is None:
self._preferences_rk_input = None
else:
self._preferences_rk_input = np.array(preferences_rk)
if preferences_borda_rk is None:
self._preferences_borda_rk_input = None
else:
self._preferences_borda_rk_input = np.array(preferences_borda_rk)
self.log_creation = log_creation
self._labels_candidates = labels_candidates
self._sort_voters = sort_voters
# %% Basic variables
@cached_property
def n_v(self):
"""int : Number of voters."""
if self._preferences_rk_input is not None:
n_v = self._preferences_rk_input.shape[0]
else:
n_v = self._preferences_ut_input.shape[0]
# if n_v < 2:
# raise ValueError("A profile must have at least 2 voters.")
return n_v
@cached_property
def n_c(self):
"""int : Number of candidates."""
if self._preferences_rk_input is not None:
n_c = self._preferences_rk_input.shape[1]
else:
n_c = self._preferences_ut_input.shape[1]
if n_c < 2:
raise ValueError("A profile must have at least 2 candidates.")
return n_c
@cached_property
def _preferences_rk_unsorted(self):
"""preferences_rk, before sorting by voter."""
if self._preferences_rk_input is not None:
return self._preferences_rk_input
if self._preferences_borda_rk_input is not None:
return preferences_ut_to_preferences_rk(self._preferences_borda_rk_input)
if self._preferences_ut_input is not None:
return preferences_ut_to_preferences_rk(self._preferences_ut_input)
raise ValueError('Please provide at least preferences_rk, preferences_borda_rk or preferences_ut.')
@cached_property
def _preferences_borda_rk_unsorted(self):
"""preferences_borda_rk, before sorting by voter."""
if self._preferences_borda_rk_input is not None:
return self._preferences_borda_rk_input
else:
return preferences_rk_to_preferences_borda_rk(self._preferences_rk_unsorted)
@cached_property
def _preferences_ut_unsorted(self):
"""preferences_ut, before sorting by voter."""
if self._preferences_ut_input is not None:
return self._preferences_ut_input
else:
return self._preferences_borda_rk_unsorted
@cached_property
def _preferences_borda_ut_unsorted(self):
"""preferences_borda_ut, before sorting by voter."""
return preferences_ut_to_preferences_borda_ut(self._preferences_ut_unsorted)
@cached_property
def _index_sort_voters(self):
"""Index used to sort by voter."""
# Sort voters by strict ranking, then by weak order (deducted from utility)
sort_criterion = np.concatenate(
(self._preferences_rk_unsorted, self._preferences_borda_ut_unsorted),
axis=1
).tolist()
# noinspection PyUnresolvedReferences
indexes = sorted(range(self.n_v), key=sort_criterion.__getitem__)
return indexes
@cached_property
def preferences_rk(self):
"""2d array of integers. ``preferences_rk[v, k]`` is the candidate at rank ``k`` for voter ``v``. For
example, ``preferences_rk[v, 0]`` is ``v``'s preferred candidate.
"""
if self._sort_voters:
return self._preferences_rk_unsorted[self._index_sort_voters, ::]
else:
return self._preferences_rk_unsorted
@cached_property
def preferences_ut(self):
"""2d array of floats. ``preferences_ut[v, c]`` is the utility of candidate ``c`` as seen by voter ``v``."""
if self._sort_voters:
return self._preferences_ut_unsorted[self._index_sort_voters, ::]
else:
return self._preferences_ut_unsorted
@cached_property
def preferences_borda_rk(self):
"""2d array of integers. ``preferences_borda_rk[v, c]`` gains 1 point for each candidate ``d`` such that
voter ``v`` ranks ``c`` before ``d``. So, these Borda scores are between ``0`` and ``C - 1``.
"""
if self._sort_voters:
return self._preferences_borda_rk_unsorted[self._index_sort_voters, ::]
else:
return self._preferences_borda_rk_unsorted
@cached_property
def preferences_borda_ut(self):
"""2d array of floats. ``preferences_borda_ut[v, c]`` gains 1 point for each ``d`` such that ``v`` strictly
prefers ``c`` to ``d`` (in the sense of utilities), and 0.5 point for each ``d`` such that ``v`` is
indifferent between ``c`` and ``d``. So, these Borda scores are between ``0`` and ``C - 1``.
"""
if self._sort_voters:
return self._preferences_borda_ut_unsorted[self._index_sort_voters, ::]
else:
return self._preferences_borda_ut_unsorted
@property
def labels_candidates(self):
"""List of :attr:`~svvamp.Population.n_c` strings (names of the candidates).
"""
if self._labels_candidates is None:
self._labels_candidates = [str(x) for x in range(self.n_c)]
return self._labels_candidates
@labels_candidates.setter
def labels_candidates(self, value):
self._labels_candidates = value
# %% Identify voters with the same ordinal preferences.
@cached_property
def v_has_same_ordinal_preferences_as_previous_voter(self):
"""1d array of booleans. ``v_has_same_ordinal_preferences_as_previous_voter[v]`` is ``True`` iff voter ``v``
has the same preference strict order (row in :attr:`~svvamp.Population.preferences_rk`) and the same
preference weak order (row in :attr:`~svvamp.Population.preferences_borda_ut`) as voter ``v-1``. By convention,
it is ``False`` for voter ``0``.
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.v_has_same_ordinal_preferences_as_previous_voter
array([False, True])
"""
self.mylog("Compute v_has_same_ordinal_preferences_as_previous_voter", 1)
return np.concatenate((
[False],
np.logical_and(
np.all(self.preferences_rk[range(self.n_v - 1), :]
== self.preferences_rk[range(1, self.n_v), :], 1),
np.all(self.preferences_borda_ut[range(self.n_v - 1), :]
== self.preferences_borda_ut[range(1, self.n_v), :], 1)
)
))
# %% Plurality scores
@cached_property
def plurality_scores_rk(self):
"""1d array of int. ``plurality_scores_rk[c]`` is the number of voters for whom ``c`` is the top-ranked
candidate (with voter tie-breaking).
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.plurality_scores_rk
array([2, 0, 0])
"""
self.mylog("Compute Plurality scores (rk)", 1)
return np.array(np.bincount(self.preferences_rk[:, 0], minlength=self.n_c), dtype=int)
@cached_property
def plurality_scores_ut(self):
"""1d array of int. ``plurality_scores_ut[c]`` is the number of voters who strictly prefer ``c`` to all
other candidates. If a voter has several candidates with maximal utility, then none of them receives any point.
>>> from svvamp import Profile
>>> profile = Profile(preferences_ut=[[2, 1, 0], [0, 1, 1]])
>>> profile.plurality_scores_ut
array([1, 0, 0])
"""
self.mylog("Compute Plurality scores (ut)", 1)
ut_max = np.max(self.preferences_ut, axis=1)
nb_favorites = np.sum(self.preferences_ut == ut_max[:, np.newaxis], axis=1)
favorite = np.argmax(self.preferences_ut, axis=1)
return np.array(np.bincount(favorite[nb_favorites == 1], minlength=self.n_c), dtype=int)
# %% Matrix of duels
@cached_property
def matrix_duels_ut(self):
"""2d array of integers. ``matrix_duels_ut[c, d]`` is the number of voters who have a strictly greater
utility for ``c`` than for ``d``. By convention, diagonal coefficients are set to ``0``.
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.matrix_duels_ut
array([[0, 2, 2],
[0, 0, 2],
[0, 0, 0]])
"""
self.mylog("Compute matrix of duels", 1)
return preferences_ut_to_matrix_duels_ut(self.preferences_borda_ut)
@cached_property
def matrix_duels_rk(self):
"""2d array of integers. ``matrix_duels_rk[c, d]`` is the number of voters who rank candidate ``c`` before
``d`` (in the sense of :attr:`~svvamp.Population.preferences_rk`). By convention, diagonal coefficients are
set to 0.
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.matrix_duels_rk
array([[0, 2, 2],
[0, 0, 2],
[0, 0, 0]])
"""
self.mylog("Compute matrix of duels (with strict orders)", 1)
return preferences_ut_to_matrix_duels_ut(self.preferences_borda_rk)
@cached_property
def matrix_victories_ut_abs(self):
"""2d array of values in {0, 0.5, 1}. Matrix of absolute victories based on
:attr:`~svvamp.Population.matrix_duels_ut`.
``matrix_victories_ut_abs[c, d]`` is:
* 1 iff ``matrix_duels_ut[c, d] > V / 2``.
* 0.5 iff ``matrix_duels_ut[c, d] = V / 2``.
* 0 iff ``matrix_duels_ut[c, d] < V / 2``.
By convention, diagonal coefficients are set to 0.
Examples
--------
A victory:
>>> profile = Profile(preferences_ut=[[1, 0], [1, 0]])
>>> profile.matrix_victories_ut_abs
array([[0., 1.],
[0., 0.]])
A half-victory, but the other candidate has lost:
>>> profile = Profile(preferences_ut=[[1, 0], [42, 42]])
>>> profile.matrix_victories_ut_abs
array([[0. , 0.5],
[0. , 0. ]])
A half-victory for each candidate:
>>> profile = Profile(preferences_ut=[[1, 0], [0, 1]])
>>> profile.matrix_victories_ut_abs
array([[0. , 0.5],
[0.5, 0. ]])
A defeat for each candidate:
>>> profile = Profile(preferences_ut=[[0, 0], [0, 0]])
>>> profile.matrix_victories_ut_abs
array([[0., 0.],
[0., 0.]])
"""
self.mylog("Compute matrix_victories_ut_abs", 1)
return (np.multiply(0.5, self.matrix_duels_ut >= self.n_v / 2)
+ np.multiply(0.5, self.matrix_duels_ut > self.n_v / 2))
@cached_property
def matrix_victories_ut_abs_ctb(self):
"""2d array of values in {0, 1}. Matrix of absolute victories based on
:attr:`~svvamp.Population.matrix_duels_ut`, with tie-breaks on candidates.
``matrix_victories_ut_abs_ctb[c, d]`` is:
* 1 iff ``matrix_duels_ut[c, d] > V / 2``, or ``matrix_duels_ut[c, d] = V / 2`` and ``c < d``.
* 0 iff ``matrix_duels_ut[c, d] < V / 2``, or ``matrix_duels_ut[c, d] = V / 2`` and ``d < c``.
By convention, diagonal coefficients are set to 0.
Examples
--------
A victory (without tie-breaking):
>>> profile = Profile(preferences_ut=[[1, 0], [1, 0]])
>>> profile.matrix_victories_ut_abs_ctb
array([[0., 1.],
[0., 0.]])
A victory with tie-breaking:
>>> profile = Profile(preferences_ut=[[1, 0], [0, 1]])
>>> profile.matrix_victories_ut_abs_ctb
array([[0., 1.],
[0., 0.]])
Another case of victory with tie-breaking:
>>> profile = Profile(preferences_ut=[[1, 0], [42, 42]])
>>> profile.matrix_victories_ut_abs_ctb
array([[0., 1.],
[0., 0.]])
A defeat for each candidate:
>>> profile = Profile(preferences_ut=[[0, 0], [0, 0]])
>>> profile.matrix_victories_ut_abs_ctb
array([[0., 0.],
[0., 0.]])
"""
self.mylog("Compute matrix_victories_ut_abs_ctb", 1)
result = np.zeros((self.n_c, self.n_c))
for c in range(self.n_c):
for d in range(self.n_c):
if c == d:
continue
if self.matrix_duels_ut[c, d] > self.n_v / 2 or (self.matrix_duels_ut[c, d] == self.n_v / 2 and c < d):
result[c, d] = 1
return result
@cached_property
def matrix_victories_ut_rel(self):
"""2d array of values in {0, 0.5, 1}. Matrix of relative victories based on
:attr:`~svvamp.Population.matrix_duels_ut`.
``matrix_victories_ut_rel[c, d]`` is:
* 1 iff ``matrix_duels_ut[c, d] > matrix_duels_ut[d, c]``.
* 0.5 iff ``matrix_duels_ut[c, d] = matrix_duels_ut[d, c]``.
* 0 iff ``matrix_duels_ut[c, d] < matrix_duels_ut[d, c]``.
By convention, diagonal coefficients are set to 0.
Examples
--------
A relative victory (which is not an absolute victory):
>>> profile = Profile(preferences_ut=[[1, 0], [0, 0], [0, 0]])
>>> profile.matrix_victories_ut_rel
array([[0., 1.],
[0., 0.]])
A tie:
>>> profile = Profile(preferences_ut=[[1, 0], [0, 1], [0, 0]])
>>> profile.matrix_victories_ut_rel
array([[0. , 0.5],
[0.5, 0. ]])
"""
self.mylog("Compute matrix_victories_ut_rel", 1)
return (
np.multiply(0.5, self.matrix_duels_ut >= self.matrix_duels_ut.T)
+ np.multiply(0.5, self.matrix_duels_ut > self.matrix_duels_ut.T)
- np.multiply(0.5, np.eye(self.n_c))
)
@cached_property
def matrix_victories_ut_rel_ctb(self):
"""2d array of values in {0, 1}. Matrix of relative victories based on
:attr:`~svvamp.Population.matrix_duels_ut`, with tie-breaks on candidates.
``matrix_victories_ut_rel_ctb[c, d]`` is:
* 1 iff ``matrix_duels_ut[c, d] > matrix_duels_ut[d, c]``, or
``matrix_duels_ut[c, d] = matrix_duels_ut[d, c]`` and ``c < d``.
* 0 iff ``matrix_duels_ut[c, d] < matrix_duels_ut[d, c]``, or
``matrix_duels_ut[c, d] = matrix_duels_ut[d, c]`` and ``d < c``.
By convention, diagonal coefficients are set to 0.
Examples
--------
A relative victory (without tie-breaking):
>>> profile = Profile(preferences_ut=[[1, 0], [0, 0], [0, 0]])
>>> profile.matrix_victories_ut_rel_ctb
array([[0., 1.],
[0., 0.]])
A relative victory with tie-breaking:
>>> profile = Profile(preferences_ut=[[1, 0], [0, 1], [0, 0]])
>>> profile.matrix_victories_ut_rel_ctb
array([[0., 1.],
[0., 0.]])
"""
self.mylog("Compute matrix_victories_ut_rel_ctb", 1)
result = np.zeros((self.n_c, self.n_c))
for c in range(self.n_c):
for d in range(c + 1, self.n_c):
if self.matrix_duels_ut[c, d] >= self.matrix_duels_ut[d, c]:
result[c, d] = 1
result[d, c] = 0
else:
result[c, d] = 0
result[d, c] = 1
return result
@cached_property
def matrix_victories_rk(self):
"""2d array of values in {0, 0.5, 1}. Matrix of victories based on :attr:`~svvamp.Population.matrix_duels_rk`.
``matrix_victories_rk[c, d]`` is:
* 1 iff ``matrix_duels_rk[c, d] > matrix_duels_rk[d, c]``, i.e. iff ``matrix_duels_rk[c, d] > V / 2``.
* 0.5 iff ``matrix_duels_rk[c, d] = matrix_duels_rk[d, c]``, i.e. iff ``matrix_duels_rk[c, d] = V / 2``.
* 0 iff ``matrix_duels_rk[c, d] < matrix_duels_rk[d, c]``, i.e. iff ``matrix_duels_rk[c, d] < V / 2``.
By convention, diagonal coefficients are set to 0.
Examples
--------
A victory:
>>> profile = Profile(preferences_rk=[[0, 1], [0, 1]])
>>> profile.matrix_victories_rk
array([[0., 1.],
[0., 0.]])
A tie:
>>> profile = Profile(preferences_rk=[[0, 1], [1, 0]])
>>> profile.matrix_victories_rk
array([[0. , 0.5],
[0.5, 0. ]])
"""
self.mylog("Compute matrix_victories_rk", 1)
return (np.multiply(0.5, self.matrix_duels_rk >= self.n_v / 2)
+ np.multiply(0.5, self.matrix_duels_rk > self.n_v / 2))
@cached_property
def matrix_victories_rk_ctb(self):
"""2d array of values in {0, 1}. Matrix of victories based on :attr:`~svvamp.Population.matrix_duels_rk`,
with tie-breaks on candidates.
``matrix_victories_rk_ctb[c, d]`` is:
* 1 iff ``matrix_duels_rk[c, d] > matrix_duels_rk[d, c]``, or
``matrix_duels_rk[c, d] = matrix_duels_rk[d, c]`` and ``c < d``.
* 0 iff ``matrix_duels_rk[c, d] < matrix_duels_rk[d, c]``, or
``matrix_duels_rk[c, d] = matrix_duels_rk[d, c]`` and ``d < c``.
By convention, diagonal coefficients are set to 0.
Examples
--------
A victory (without tie-breaking):
>>> profile = Profile(preferences_rk=[[0, 1], [0, 1]])
>>> profile.matrix_victories_rk_ctb
array([[0., 1.],
[0., 0.]])
A victory with tie-breaking:
>>> profile = Profile(preferences_rk=[[0, 1], [1, 0]])
>>> profile.matrix_victories_rk_ctb
array([[0., 1.],
[0., 0.]])
"""
self.mylog("Compute matrix_victories_rk_ctb", 1)
result = np.zeros((self.n_c, self.n_c))
for c in range(self.n_c):
for d in range(c + 1, self.n_c):
if self.matrix_duels_rk[c, d] >= self.n_v / 2:
result[c, d] = 1
result[d, c] = 0
else:
result[c, d] = 0
result[d, c] = 1
return result
# %% Existence of Condorcet order
@cached_property
def exists_condorcet_order_rk_ctb(self):
"""Boolean. True iff in matrix :attr:`~svvamp.Population.matrix_victories_rk_ctb`, there is a candidate with
``n_c - 1`` victories, a candidate with ``n_c - 2`` victories, etc.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_condorcet_order_rk_ctb
True
See Also
--------
:attr:`~svvamp.Population.not_exists_condorcet_order_rk_ctb`.
"""
self.mylog("Compute exists_condorcet_order_rk_ctb", 1)
temp = np.copy(self.matrix_victories_rk_ctb.sum(1))
temp.sort()
return np.array_equal(range(self.n_c), temp)
@cached_property
def not_exists_condorcet_order_rk_ctb(self):
"""Boolean. Cf. :attr:`~svvamp.Population.exists_condorcet_order_rk_ctb`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_condorcet_order_rk_ctb
False
"""
return not self.exists_condorcet_order_rk_ctb
@cached_property
def exists_condorcet_order_rk(self):
"""Boolean. True iff in matrix :attr:`~svvamp.Population.matrix_victories_rk`, there is a candidate with
``n_c - 1`` victories, a candidate with ``n_c - 2`` victories, etc.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_condorcet_order_rk
True
See Also
--------
:attr:`~svvamp.Population.not_exists_condorcet_order_rk`.
"""
self.mylog("Compute exists_condorcet_order_rk", 1)
temp = np.copy(self.matrix_victories_rk.sum(1))
temp.sort()
return np.array_equal(range(self.n_c), temp)
@cached_property
def not_exists_condorcet_order_rk(self):
"""Boolean. Cf. :attr:`~svvamp.Population.exists_condorcet_order_rk`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_condorcet_order_rk
False
"""
return not self.exists_condorcet_order_rk
@cached_property
def exists_condorcet_order_ut_abs(self):
"""Boolean. True iff in matrix :attr:`~svvamp.Population.matrix_victories_ut_abs`, there is a candidate with
``n_c - 1`` victories, a candidate with ``n_c - 2`` victories, etc.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_condorcet_order_ut_abs
True
See Also
--------
:attr:`~svvamp.Population.not_exists_condorcet_order_ut_abs`.
"""
self.mylog("Compute exists_condorcet_order_ut_abs", 1)
temp = np.copy(self.matrix_victories_ut_abs.sum(1))
temp.sort()
return np.array_equal(range(self.n_c), temp)
@cached_property
def not_exists_condorcet_order_ut_abs(self):
"""Boolean. Cf. :attr:`~svvamp.Population.exists_condorcet_order_ut_abs`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_condorcet_order_ut_abs
False
"""
return not self.exists_condorcet_order_ut_abs
@cached_property
def exists_condorcet_order_ut_abs_ctb(self):
"""Boolean. True iff in matrix :attr:`~svvamp.Population.matrix_victories_ut_abs_ctb`, there is a candidate
with ``n_c - 1`` victories, a candidate with ``n_c - 2`` victories, etc.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_condorcet_order_ut_abs_ctb
True
See Also
--------
:attr:`~svvamp.Population.not_exists_condorcet_order_ut_abs_ctb`.
"""
self.mylog("Compute exists_condorcet_order_ut_abs_ctb", 1)
temp = np.copy(self.matrix_victories_ut_abs_ctb.sum(1))
temp.sort()
return np.array_equal(range(self.n_c), temp)
@cached_property
def not_exists_condorcet_order_ut_abs_ctb(self):
"""Boolean. Cf. :attr:`~svvamp.Population.exists_condorcet_order_ut_abs_ctb`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_condorcet_order_ut_abs_ctb
False
"""
return not self.exists_condorcet_order_ut_abs_ctb
@cached_property
def exists_condorcet_order_ut_rel(self):
"""Boolean. True iff in matrix :attr:`~svvamp.Population.matrix_victories_ut_rel`, there is a candidate with
``n_c - 1`` victories, a candidate with ``n_c - 2`` victories, etc.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_condorcet_order_ut_rel
True
See Also
--------
:attr:`~svvamp.Population.not_exists_condorcet_order_ut_rel`.
"""
self.mylog("Compute exists_condorcet_order_ut_rel", 1)
temp = np.copy(self.matrix_victories_ut_rel.sum(1))
temp.sort()
return np.array_equal(range(self.n_c), temp)
@cached_property
def not_exists_condorcet_order_ut_rel(self):
"""Boolean. Cf. :attr:`~svvamp.Population.exists_condorcet_order_ut_rel`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_condorcet_order_ut_rel
False
"""
return not self.exists_condorcet_order_ut_rel
@cached_property
def exists_condorcet_order_ut_rel_ctb(self):
"""Boolean. True iff in matrix :attr:`~svvamp.Population.matrix_victories_ut_rel_ctb`, there is a candidate
with ``n_c - 1`` victories, a candidate with ``n_c - 2`` victories, etc.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_condorcet_order_ut_rel_ctb
True
See Also
--------
:attr:`~svvamp.Population.not_exists_condorcet_order_ut_rel_ctb`.
"""
self.mylog("Compute exists_condorcet_order_ut_rel_ctb", 1)
temp = np.copy(self.matrix_victories_ut_rel_ctb.sum(1))
temp.sort()
return np.array_equal(range(self.n_c), temp)
@cached_property
def not_exists_condorcet_order_ut_rel_ctb(self):
"""Boolean. Cf. :attr:`~svvamp.Population.exists_condorcet_order_ut_rel_ctb`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_condorcet_order_ut_rel_ctb
False
"""
return not self.exists_condorcet_order_ut_rel_ctb
# %% Condorcet winner and variants
@cached_property
def condorcet_admissible_candidates(self):
"""1d array of booleans. ``condorcet_admissible_candidates[c]`` is ``True`` iff candidate ``c`` is
Condorcet-admissible, i.e. iff no candidate ``d`` has an absolute victory against ``c`` (in the sense of
:attr:`~svvamp.Population.matrix_victories_ut_abs`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.condorcet_admissible_candidates
array([ True, False, False])
See Also
--------
:attr:`~svvamp.Population.nb_condorcet_admissible`,
:attr:`~svvamp.Population.exists_condorcet_admissible`,
:attr:`~svvamp.Population.not_exists_condorcet_admissible`.
"""
self.mylog("Compute Condorcet-admissible candidates", 1)
return np.all(self.matrix_victories_ut_abs <= 0.5, 0)
@cached_property
def nb_condorcet_admissible(self):
"""Integer (number of Condorcet-admissible candidates,
:attr:`~svvamp.Population.condorcet_admissible_candidates`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.nb_condorcet_admissible
1
"""
self.mylog("Compute number of Condorcet-admissible candidates", 1)
return int(np.sum(self.condorcet_admissible_candidates))
@cached_property
def exists_condorcet_admissible(self):
"""Boolean (``True`` iff there is at least one Condorcet-admissible candidate,
:attr:`~svvamp.Population.condorcet_admissible_candidates`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_condorcet_admissible
True
"""
return bool(np.any(self.condorcet_admissible_candidates))
@cached_property
def not_exists_condorcet_admissible(self):
"""Boolean (``True`` iff there is no Condorcet-admissible candidate,
:attr:`~svvamp.Population.condorcet_admissible_candidates`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_condorcet_admissible
False
"""
return not self.exists_condorcet_admissible
@cached_property
def weak_condorcet_winners(self):
"""1d array of booleans. ``weak_condorcet_winners[c]`` is ``True`` iff candidate ``c`` is a weak Condorcet
winner, i.e. iff no candidate ``d`` has a relative victory against ``c`` (in the sense of
:attr:`~svvamp.Population.matrix_victories_ut_rel`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.weak_condorcet_winners
array([ True, False, False])
See Also
--------
:attr:`~svvamp.Population.nb_weak_condorcet_winners`,
:attr:`~svvamp.Population.exists_weak_condorcet_winner`,
:attr:`~svvamp.Population.not_exists_weak_condorcet_winner`.
"""
self.mylog("Compute weak Condorcet winners", 1)
return np.all(self.matrix_victories_ut_rel <= 0.5, 0)
@cached_property
def nb_weak_condorcet_winners(self):
"""Integer (number of weak Condorcet winners, :attr:`~svvamp.Population.weak_condorcet_winners`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.nb_weak_condorcet_winners
1
"""
self.mylog("Compute number of weak Condorcet winners", 1)
return int(np.sum(self.weak_condorcet_winners))
@cached_property
def exists_weak_condorcet_winner(self):
"""Boolean (``True`` iff there is at least one weak Condorcet winner,
:attr:`~svvamp.Population.weak_condorcet_winners`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_weak_condorcet_winner
True
"""
return bool(np.any(self.weak_condorcet_winners))
@cached_property
def not_exists_weak_condorcet_winner(self):
"""Boolean (``True`` iff there is no weak Condorcet winner, :attr:`~svvamp.Population.weak_condorcet_winners`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_weak_condorcet_winner
False
"""
return not self.exists_weak_condorcet_winner
@cached_property
def condorcet_winner_rk_ctb(self):
"""Integer or ``NaN``. Candidate who has only victories in
:attr:`~svvamp.Population.matrix_victories_rk_ctb`. If there is no such candidate, then ``NaN``.
Examples
--------
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.condorcet_winner_rk_ctb
0
No Condorcet winner:
>>> profile = Profile(preferences_rk=[[0, 1, 2], [1, 2, 0], [2, 0, 1]])
>>> profile.condorcet_winner_rk_ctb
nan
See Also
--------
:attr:`~svvamp.Population.exists_condorcet_winner_rk_ctb`,
:attr:`~svvamp.Population.not_exists_condorcet_winner_rk_ctb`.
"""
self.mylog("Compute condorcet_winner_rk_ctb", 1)
for c in range(self.n_c):
# The whole COLUMN must be 0.
if np.array_equiv(self.matrix_victories_rk_ctb[:, c], 0):
return c
return np.nan
@cached_property
def exists_condorcet_winner_rk_ctb(self):
"""Boolean (``True`` iff there is a :attr:`~svvamp.Population.condorcet_winner_rk_ctb`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_condorcet_winner_rk_ctb
True
"""
return not np.isnan(self.condorcet_winner_rk_ctb)
@cached_property
def not_exists_condorcet_winner_rk_ctb(self):
"""Boolean (``True`` iff there is no :attr:`~svvamp.Population.condorcet_winner_rk_ctb`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_condorcet_winner_rk_ctb
False
"""
return bool(np.isnan(self.condorcet_winner_rk_ctb))
@cached_property
def condorcet_winner_rk(self):
"""Integer or ``NaN``. Candidate who has only victories in :attr:`~svvamp.Population.matrix_victories_rk`. If
there is no such candidate, then ``NaN``.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.condorcet_winner_rk
0
No Condorcet winner:
>>> profile = Profile(preferences_rk=[[0, 1, 2], [1, 2, 0], [2, 0, 1]])
>>> profile.condorcet_winner_rk
nan
See Also
--------
:attr:`~svvamp.Population.exists_condorcet_winner_rk`,
:attr:`~svvamp.Population.not_exists_condorcet_winner_rk`.
"""
self.mylog("Compute condorcet_winner_rk", 1)
for c in range(self.n_c):
# The whole COLUMN must be 0.
if np.array_equiv(self.matrix_victories_rk[:, c], 0):
return c
return np.nan
@cached_property
def exists_condorcet_winner_rk(self):
"""Boolean (``True`` iff there is a :attr:`~svvamp.Population.condorcet_winner_rk`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_condorcet_winner_rk
True
"""
return not np.isnan(self.condorcet_winner_rk)
@cached_property
def not_exists_condorcet_winner_rk(self):
"""Boolean (``True`` iff there is no :attr:`~svvamp.Population.condorcet_winner_rk`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_condorcet_winner_rk
False
"""
return bool(np.isnan(self.condorcet_winner_rk))
@cached_property
def condorcet_winner_ut_rel_ctb(self):
"""Integer or ``NaN``. Candidate who has only victories in
:attr:`~svvamp.Population.matrix_victories_ut_rel_ctb`. If there is no such candidate, then ``NaN``.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.condorcet_winner_ut_rel_ctb
0
No Condorcet winner:
>>> profile = Profile(preferences_rk=[[0, 1, 2], [1, 2, 0], [2, 0, 1]])
>>> profile.condorcet_winner_ut_rel_ctb
nan
See Also
--------
:attr:`~svvamp.Population.exists_condorcet_winner_ut_rel_ctb`,
:attr:`~svvamp.Population.not_exists_condorcet_winner_ut_rel_ctb`.
"""
self.mylog("Compute condorcet_winner_ut_rel_ctb", 1)
for c in range(self.n_c):
# The whole COLUMN must be 0.
if np.array_equiv(self.matrix_victories_ut_rel_ctb[:, c], 0):
return c
return np.nan
@cached_property
def exists_condorcet_winner_ut_rel_ctb(self):
"""Boolean (``True`` iff there is a :attr:`~svvamp.Population.condorcet_winner_ut_rel_ctb`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_condorcet_winner_ut_rel_ctb
True
"""
return not np.isnan(self.condorcet_winner_ut_rel_ctb)
@cached_property
def not_exists_condorcet_winner_ut_rel_ctb(self):
"""Boolean (``True`` iff there is no :attr:`~svvamp.Population.condorcet_winner_ut_rel_ctb`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_condorcet_winner_ut_rel_ctb
False
"""
return bool(np.isnan(self.condorcet_winner_ut_rel_ctb))
@cached_property
def condorcet_winner_ut_rel(self):
"""Integer or ``NaN``. Candidate who has only victories in
:attr:`~svvamp.Population.matrix_victories_ut_rel`. If there is no such candidate, then ``NaN``.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.condorcet_winner_ut_rel
0
No Condorcet winner:
>>> profile = Profile(preferences_rk=[[0, 1, 2], [1, 2, 0], [2, 0, 1]])
>>> profile.condorcet_winner_ut_rel
nan
See Also
--------
:attr:`~svvamp.Population.exists_condorcet_winner_ut_rel`,
:attr:`~svvamp.Population.not_exists_condorcet_winner_ut_rel`.
"""
self.mylog("Compute condorcet_winner_ut_rel", 1)
for c in range(self.n_c):
# The whole COLUMN must be 0.
if np.array_equiv(self.matrix_victories_ut_rel[:, c], 0):
return c
return np.nan
@cached_property
def exists_condorcet_winner_ut_rel(self):
"""Boolean (``True`` iff there is a :attr:`~svvamp.Population.condorcet_winner_ut_rel`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_condorcet_winner_ut_rel
True
"""
return not np.isnan(self.condorcet_winner_ut_rel)
@cached_property
def not_exists_condorcet_winner_ut_rel(self):
"""Boolean (``True`` iff there is no :attr:`~svvamp.Population.condorcet_winner_ut_rel`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_condorcet_winner_ut_rel
False
"""
return bool(np.isnan(self.condorcet_winner_ut_rel))
@cached_property
def condorcet_winner_ut_abs_ctb(self):
"""Integer or ``NaN``. Candidate who has only victories in
:attr:`~svvamp.Population.matrix_victories_ut_abs_ctb`. If there is no such candidate, then ``NaN``.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.condorcet_winner_ut_abs_ctb
0
No Condorcet winner:
>>> profile = Profile(preferences_rk=[[0, 1, 2], [1, 2, 0], [2, 0, 1]])
>>> profile.condorcet_winner_ut_abs_ctb
nan
See Also
--------
:attr:`~svvamp.Population.exists_condorcet_winner_ut_abs_ctb`,
:attr:`~svvamp.Population.not_exists_condorcet_winner_ut_abs_ctb`
"""
self.mylog("Compute condorcet_winner_ut_abs_ctb", 1)
for c in range(self.n_c):
if np.array_equiv(self.matrix_victories_ut_abs_ctb[c, np.array(range(self.n_c)) != c], 1):
return c
return np.nan
@cached_property
def exists_condorcet_winner_ut_abs_ctb(self):
"""Boolean (``True`` iff there is a :attr:`~svvamp.Population.condorcet_winner_ut_abs_ctb`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_condorcet_winner_ut_abs_ctb
True
"""
return not np.isnan(self.condorcet_winner_ut_abs_ctb)
@cached_property
def not_exists_condorcet_winner_ut_abs_ctb(self):
"""Boolean (``True`` iff there is no :attr:`~svvamp.Population.condorcet_winner_ut_abs_ctb`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_condorcet_winner_ut_abs_ctb
False
"""
return bool(np.isnan(self.condorcet_winner_ut_abs_ctb))
@cached_property
def condorcet_winner_ut_abs(self):
"""Integer or ``NaN``. Candidate who has only victories in
:attr:`~svvamp.Population.matrix_victories_ut_abs`. If there is no such candidate, then ``NaN``.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.condorcet_winner_ut_abs
0
No Condorcet winner:
>>> profile = Profile(preferences_rk=[[0, 1, 2], [1, 2, 0], [2, 0, 1]])
>>> profile.condorcet_winner_ut_abs
nan
See Also
--------
:attr:`~svvamp.Population.exists_condorcet_winner_ut_abs`,
:attr:`~svvamp.Population.not_exists_condorcet_winner_ut_abs`.
"""
self.mylog("Compute Condorcet winner", 1)
for c in range(self.n_c):
if np.array_equiv(self.matrix_victories_ut_abs[c, np.array(range(self.n_c)) != c], 1):
return c
return np.nan
@cached_property
def exists_condorcet_winner_ut_abs(self):
"""Boolean (``True`` iff there is a :attr:`~svvamp.Population.condorcet_winner_ut_abs`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_condorcet_winner_ut_abs
True
"""
return not np.isnan(self.condorcet_winner_ut_abs)
@cached_property
def not_exists_condorcet_winner_ut_abs(self):
"""Boolean (``True`` iff there is no :attr:`~svvamp.Population.condorcet_winner_ut_abs`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_condorcet_winner_ut_abs
False
"""
return bool(np.isnan(self.condorcet_winner_ut_abs))
# %% Resistant Condorcet winner
@cached_property
def resistant_condorcet_winner(self):
"""Integer or ``NaN``. Resistant Condorcet Winner. If there is no such candidate, then ``NaN``.
A Condorcet winner ``w`` (in the sense of :attr:`~svvamp.Population.condorcet_winner_ut_abs`) is resistant
iff in any Condorcet voting system (in the same sense), the profile is not manipulable (cf. Durand et al.).
This is equivalent to say that for any pair ``(c, d)`` of other distinct candidates, there is a strict
majority of voters who simultaneously:
1) Do not prefer ``c`` to ``w``,
2) And prefer ``w`` to ``d``.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.resistant_condorcet_winner
0
No resistant Condorcet winner:
>>> profile = Profile(preferences_rk=[[0, 1, 2], [1, 2, 0], [2, 0, 1]])
>>> profile.resistant_condorcet_winner
nan
See Also
--------
:attr:`~svvamp.Population.exists_resistant_condorcet_winner`,
:attr:`~svvamp.Population.not_exists_resistant_condorcet_winner`.
"""
self.mylog("Compute Resistant Condorcet winner", 1)
if is_resistant_condorcet(self.condorcet_winner_ut_abs, self.preferences_ut):
return self.condorcet_winner_ut_abs
return np.nan
@cached_property
def exists_resistant_condorcet_winner(self):
"""Boolean (``True`` iff there is a :attr:`~svvamp.Population.resistant_condorcet_winner`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_resistant_condorcet_winner
True
"""
return not np.isnan(self.resistant_condorcet_winner)
@cached_property
def not_exists_resistant_condorcet_winner(self):
"""Boolean (``True`` iff there is no :attr:`~svvamp.Population.resistant_condorcet_winner`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_resistant_condorcet_winner
False
"""
return bool(np.isnan(self.resistant_condorcet_winner))
@cached_property
def threshold_c_prevents_w_condorcet_ut_abs(self):
"""2d array of integers. Threshold for ``c``-manipulators to prevent ``w`` from being a Condorcet winner (in
the sense of :attr:`~svvamp.Population.condorcet_winner_ut_abs`).
Intuitively, the question is the following: in an election where ``w`` is the winner, how many
``c``-manipulators are needed to prevent ``w`` from being a Condorcet winner?
We start with the sub-population of :math:`n_s` 'sincere' voters, i.e. not preferring ``c`` to ``w``. The
precise question is: how many ``c``-manipulators :math:`n_m` must we add in order to create a non-victory for
``w`` against some candidate ``d`` :math:`\\neq` ``w`` (possibly ``c`` herself)?
In the following, :math:`| c > d |` denotes the number of voters who strictly prefer candidate ``c`` to
``d``. We need:
.. math::
| \\text{sincere voters for whom } w > d |
\\leq \\frac{n_s + n_m}{2}.
I.e.:
.. math::
| \\text{non } c > w \\text{ and } w > d | \\leq
\\frac{| \\text{non } c > w | + n_m}{2}.
I.e.:
.. math::
n_m \\geq 2 \\cdot | \\text{non } c > w \\text{ and } w > d |
- | \\text{non } c > w |.
One candidate ``d`` is enough, so:
``threshold_c_prevents_w_Condorcet_ut_abs[c, w]`` :math:`= 2 \\cdot \\min_{d \\neq w} |w \\geq c \\text{ and }
w > d| - |w \\geq c|`.
If this result is negative, it means that even without ``c``-manipulators, ``w`` is not a Condorcet winner.
In that case, threshold is set to 0 instead.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.threshold_c_prevents_w_condorcet_ut_abs
array([[0, 0, 0],
[2, 0, 0],
[2, 0, 0]])
"""
self.mylog("Compute threshold_c_prevents_w_Condorcet_ut_abs", 1)
result = np.full((self.n_c, self.n_c), 42)
for w in range(self.n_c):
for c in range(self.n_c):
if c == w:
result[c, w] = 0
continue
v_does_not_prefer_c_to_w = (self.preferences_ut[:, w] >= self.preferences_ut[:, c])
for d in range(self.n_c):
if d == w:
continue
# But d == c is allowed (useful if self.C == 2)
v_prefers_w_to_d = (self.preferences_ut[:, w] > self.preferences_ut[:, d])
threshold_c_makes_w_not_win_against_d = (
np.multiply(2, np.sum(np.logical_and(v_does_not_prefer_c_to_w, v_prefers_w_to_d)))
- np.sum(v_does_not_prefer_c_to_w)
)
result[c, w] = np.minimum(result[c, w], threshold_c_makes_w_not_win_against_d)
result = np.maximum(result, 0)
return result
# %% Smith Set
@cached_property
def smith_set_rk_ctb(self):
"""List. Smith set computed from :attr:`~svvamp.Population.matrix_victories_rk_ctb`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2, 3], [1, 2, 0, 3], [2, 0, 1, 3]])
>>> profile.smith_set_rk_ctb
[0, 1, 2]
"""
return matrix_victories_to_smith_set(self.matrix_victories_rk_ctb)
@cached_property
def smith_set_rk(self):
"""List. Smith set computed from :attr:`~svvamp.Population.matrix_victories_rk`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2, 3], [1, 2, 0, 3], [2, 0, 1, 3]])
>>> profile.smith_set_rk
[0, 1, 2]
"""
return matrix_victories_to_smith_set(self.matrix_victories_rk)
@cached_property
def smith_set_ut_rel_ctb(self):
"""List. Smith set computed from :attr:`~svvamp.Population.matrix_victories_ut_rel_ctb`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2, 3], [1, 2, 0, 3], [2, 0, 1, 3]])
>>> profile.smith_set_ut_rel_ctb
[0, 1, 2]
"""
return matrix_victories_to_smith_set(self.matrix_victories_ut_rel_ctb)
@cached_property
def smith_set_ut_rel(self):
"""List. Smith set computed from :attr:`~svvamp.Population.matrix_victories_ut_rel`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2, 3], [1, 2, 0, 3], [2, 0, 1, 3]])
>>> profile.smith_set_ut_rel
[0, 1, 2]
"""
return matrix_victories_to_smith_set(self.matrix_victories_ut_rel)
@cached_property
def smith_set_ut_abs_ctb(self):
"""List. Smith set computed from :attr:`~svvamp.Population.matrix_victories_ut_abs_ctb`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2, 3], [1, 2, 0, 3], [2, 0, 1, 3]])
>>> profile.smith_set_ut_abs_ctb
[0, 1, 2]
"""
return matrix_victories_to_smith_set(self.matrix_victories_ut_abs_ctb)
@cached_property
def smith_set_ut_abs(self):
"""List. Smith set computed from :attr:`~svvamp.Population.matrix_victories_ut_abs`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2, 3], [1, 2, 0, 3], [2, 0, 1, 3]])
>>> profile.smith_set_ut_abs
[0, 1, 2]
"""
return matrix_victories_to_smith_set(self.matrix_victories_ut_abs)
# %% Majority favorite
@cached_property
def majority_favorite_rk_ctb(self):
"""Integer or ``NaN``. Candidate who has strictly more than n_v/2 in
:attr:`~svvamp.Population.plurality_scores_rk`. Can also be candidate 0 with exactly n_v/2. If there is no such
candidate, then ``NaN``.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.majority_favorite_rk_ctb
0
No majority favorite:
>>> profile = Profile(preferences_rk=[[0, 1, 2], [1, 2, 0], [2, 0, 1]])
>>> profile.majority_favorite_rk_ctb
nan
See Also
--------
:attr:`~svvamp.Population.exists_majority_favorite_rk_ctb`,
:attr:`~svvamp.Population.not_exists_majority_favorite_rk_ctb`.
"""
self.mylog("Compute majority favorite (rk_ctb)", 1)
c = int(np.argmax(self.plurality_scores_rk))
score_c = self.plurality_scores_rk[c]
if score_c > self.n_v / 2 or (c == 0 and score_c == self.n_v / 2):
return c
return np.nan
@cached_property
def exists_majority_favorite_rk_ctb(self):
"""Boolean (``True`` iff there is a :attr:`~svvamp.Population.majority_favorite_rk_ctb`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_majority_favorite_rk_ctb
True
"""
return not np.isnan(self.majority_favorite_rk_ctb)
@cached_property
def not_exists_majority_favorite_rk_ctb(self):
"""Boolean (``True`` iff there is no :attr:`~svvamp.Population.majority_favorite_rk_ctb`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_majority_favorite_rk_ctb
False
"""
return bool(np.isnan(self.majority_favorite_rk_ctb))
@cached_property
def majority_favorite_ut_ctb(self):
"""Integer or ``NaN``. Candidate who has strictly more than n_v/2 in
:attr:`~svvamp.Population.plurality_scores_ut`. Can also be candidate 0 with exactly n_v/2. If there is no
such candidate, then ``NaN``.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.majority_favorite_ut_ctb
0
No majority favorite:
>>> profile = Profile(preferences_rk=[[0, 1, 2], [1, 2, 0], [2, 0, 1]])
>>> profile.majority_favorite_ut_ctb
nan
See Also
--------
:attr:`~svvamp.Population.exists_majority_favorite_ut_ctb`,
:attr:`~svvamp.Population.not_exists_majority_favorite_ut_ctb`.
"""
self.mylog("Compute majority favorite (ut_ctb)", 1)
c = int(np.argmax(self.plurality_scores_ut))
score_c = self.plurality_scores_ut[c]
if score_c > self.n_v / 2 or (c == 0 and score_c == self.n_v / 2):
return c
return np.nan
@cached_property
def exists_majority_favorite_ut_ctb(self):
"""Boolean (``True`` iff there is a :attr:`~svvamp.Population.majority_favorite_ut_ctb`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_majority_favorite_ut_ctb
True
"""
return not np.isnan(self.majority_favorite_ut_ctb)
@cached_property
def not_exists_majority_favorite_ut_ctb(self):
"""Boolean (``True`` iff there is no :attr:`~svvamp.Population.majority_favorite_ut_ctb`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_majority_favorite_ut_ctb
False
"""
return bool(np.isnan(self.majority_favorite_ut_ctb))
@cached_property
def majority_favorite_rk(self):
"""Integer or ``NaN``. Candidate who has strictly more than n_v/2 in
:attr:`~svvamp.Population.plurality_scores_rk`. If there is no such candidate, then ``NaN``.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.majority_favorite_rk
0
No majority favorite:
>>> profile = Profile(preferences_rk=[[0, 1, 2], [1, 2, 0], [2, 0, 1]])
>>> profile.majority_favorite_rk
nan
See Also
--------
:attr:`~svvamp.Population.exists_majority_favorite_rk`,
:attr:`~svvamp.Population.not_exists_majority_favorite_rk`.
"""
self.mylog("Compute majority favorite (rk)", 1)
c = int(np.argmax(self.plurality_scores_rk))
score_c = self.plurality_scores_rk[c]
if score_c > self.n_v / 2:
return c
return np.nan
@cached_property
def exists_majority_favorite_rk(self):
"""Boolean (``True`` iff there is a :attr:`~svvamp.Population.majority_favorite_rk`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_majority_favorite_rk
True
"""
return not np.isnan(self.majority_favorite_rk)
@cached_property
def not_exists_majority_favorite_rk(self):
"""Boolean (``True`` iff there is no :attr:`~svvamp.Population.majority_favorite_rk`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_majority_favorite_rk
False
"""
return bool(np.isnan(self.majority_favorite_rk))
@cached_property
def majority_favorite_ut(self):
"""Integer or ``NaN``. Candidate who has strictly more than n_v/2 in
:attr:`~svvamp.Population.plurality_scores_ut`. If there is no such candidate, then ``NaN``.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.majority_favorite_ut
0
No majority favorite:
>>> profile = Profile(preferences_rk=[[0, 1, 2], [1, 2, 0], [2, 0, 1]])
>>> profile.majority_favorite_ut
nan
See Also
--------
:attr:`~svvamp.Population.exists_majority_favorite_ut`,
:attr:`~svvamp.Population.not_exists_majority_favorite_ut`.
"""
self.mylog("Compute majority favorite (ut)", 1)
c = int(np.argmax(self.plurality_scores_ut))
score_c = self.plurality_scores_ut[c]
if score_c > self.n_v / 2:
return c
return np.nan
@cached_property
def exists_majority_favorite_ut(self):
"""Boolean (``True`` iff there is a :attr:`~svvamp.Population.majority_favorite_ut`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.exists_majority_favorite_ut
True
"""
return not np.isnan(self.majority_favorite_ut)
@cached_property
def not_exists_majority_favorite_ut(self):
"""Boolean (``True`` iff there is no :attr:`~svvamp.Population.majority_favorite_ut`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.not_exists_majority_favorite_ut
False
"""
return bool(np.isnan(self.majority_favorite_ut))
# %% Total utilities
@cached_property
def total_utility_c(self):
"""1d array of numbers. ``total_utility_c[c]`` is the total utility for candidate ``c`` (i.e. the sum of
``c``'s column in matrix :attr:`~svvamp.Population.preferences_ut`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_ut=[[5, 1, 2], [4, 10, 1]])
>>> profile.total_utility_c
array([ 9, 11, 3])
"""
self.mylog("Compute total utility of candidates", 1)
return np.sum(self.preferences_ut, 0)
@cached_property
def total_utility_min(self):
"""Float. ``total_utility_min`` is the minimum of :attr:`~svvamp.Population.total_utility_c`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_ut=[[5, 1, 2], [4, 10, 1]])
>>> profile.total_utility_min
3.0
"""
self.mylog("Compute total_utility_min", 1)
return float(np.min(self.total_utility_c))
@cached_property
def total_utility_max(self):
"""Float. ``total_utility_max`` is the maximum of :attr:`~svvamp.Population.total_utility_c`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_ut=[[5, 1, 2], [4, 10, 1]])
>>> profile.total_utility_max
11.0
"""
self.mylog("Compute total_utility_max", 1)
return float(np.max(self.total_utility_c))
@cached_property
def total_utility_mean(self):
"""Float. ``total_utility_mean`` is the mean of :attr:`~svvamp.Population.total_utility_c`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_ut=[[5, 1, 2], [4, 10, 1]])
>>> profile.total_utility_mean == (9 + 11 + 3) / 3
True
"""
self.mylog("Compute total_utility_mean", 1)
return float(np.mean(self.total_utility_c))
@cached_property
def total_utility_std(self):
"""Float. ``total_utility_std`` is the standard deviation of :attr:`~svvamp.Population.total_utility_c`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_ut=[[5, 1, 2], [4, 10, 1]])
>>> profile.total_utility_std == float(np.std([9, 11, 3]))
True
"""
self.mylog("Compute total_utility_std", 1)
return float(np.std(self.total_utility_c, ddof=0))
# %% Relative social welfare
@cached_property
def relative_social_welfare_c(self):
"""1d array of numbers. ``relative_social_welfare_c[c]`` is equal to ``total_utility_c[c]``, but renormalized
so that the minimum is 0 and the maximum is 1.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_ut=[[5, 1, 2], [4, 10, 1]])
>>> profile.relative_social_welfare_c
array([0.75, 1. , 0. ])
By convention, if all candidates have the same social welfare, then their relative social welfare is 1:
>>> from svvamp import Profile
>>> profile = Profile(preferences_ut=[[2, 1, 1], [1, 2, 2]])
>>> profile.relative_social_welfare_c
array([1, 1, 1])
"""
self.mylog("Compute relative social welfare of candidates", 1)
if self.total_utility_min == self.total_utility_max:
return np.ones(self.total_utility_c.shape, dtype=self.total_utility_c.dtype)
return (self.total_utility_c - self.total_utility_min) / (self.total_utility_max - self.total_utility_min)
# %% Mean utilities
@cached_property
def mean_utility_c(self):
"""1d array of floats. ``mean_utility_c[c]`` is the mean utility for candidate ``c`` (i.e. the mean of
``c``'s column in matrix :attr:`~svvamp.Population.preferences_ut`).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_ut=[[5, 1, 2], [4, 10, 1]])
>>> profile.mean_utility_c
array([4.5, 5.5, 1.5])
"""
self.mylog("Compute mean utility of candidates", 1)
return np.mean(self.preferences_ut, 0)
@cached_property
def mean_utility_min(self):
"""Float. ``mean_utility_min`` is the minimum of :attr:`~svvamp.Population.mean_utility_c`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_ut=[[5, 1, 2], [4, 10, 1]])
>>> profile.mean_utility_min
1.5
"""
self.mylog("Compute mean_utility_min", 1)
return float(np.min(self.mean_utility_c))
@cached_property
def mean_utility_max(self):
"""Float. ``mean_utility_max`` is the maximum of :attr:`~svvamp.Population.mean_utility_c`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_ut=[[5, 1, 2], [4, 10, 1]])
>>> profile.mean_utility_max
5.5
"""
self.mylog("Compute mean_utility_max", 1)
return float(np.max(self.mean_utility_c))
@cached_property
def mean_utility_mean(self):
"""Float. ``mean_utility_mean`` is the mean of :attr:`~svvamp.Population.mean_utility_c`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_ut=[[5, 1, 2], [4, 10, 1]])
>>> profile.mean_utility_mean == (4.5 + 5.5 + 1.5) / 3
True
"""
self.mylog("Compute mean_utility_mean", 1)
return float(np.mean(self.mean_utility_c))
@cached_property
def mean_utility_std(self):
"""Float. ``mean_utility_std`` is the standard deviation of :attr:`~svvamp.Population.mean_utility_c`.
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_ut=[[5, 1, 2], [4, 10, 1]])
>>> profile.mean_utility_std == float(np.std([4.5, 5.5, 1.5]))
True
"""
self.mylog("Compute mean_utility_std", 1)
return float(np.std(self.mean_utility_c, ddof=0))
# %% Borda scores
@cached_property
def borda_score_c_rk(self):
"""1d array of integers. ``borda_score_c_rk[c]`` is the total Borda score of candidate ``c`` (using
:attr:`~svvamp.Population.preferences_borda_rk`, i.e. strict preferences).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.borda_score_c_rk
array([4, 2, 0])
"""
self.mylog("Compute Borda scores of the candidates (rankings)", 1)
return np.sum(self.matrix_duels_rk, 1)
@cached_property
def borda_score_c_ut(self):
"""1d array of floats. ``borda_score_c_ut[c]`` is the total Borda score of candidate ``c`` (using
:attr:`~svvamp.Population.preferences_borda_ut`, i.e. weak preferences).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.borda_score_c_ut
array([4., 2., 0.])
"""
self.mylog("Compute Borda scores of the candidates (weak orders)", 1)
return np.sum(self.preferences_borda_ut, 0)
@cached_property
def _tuple_decreasing_borda_score_rk(self):
candidates_by_decreasing_borda_score_rk = np.array(np.argsort(- self.borda_score_c_rk, kind='mergesort'),
dtype=int)
decreasing_borda_scores_rk = self.borda_score_c_rk[candidates_by_decreasing_borda_score_rk]
return candidates_by_decreasing_borda_score_rk, decreasing_borda_scores_rk
@cached_property
def candidates_by_decreasing_borda_score_rk(self):
"""1d array of integers. ``candidates_by_decreasing_borda_score_rk[k]`` is the candidate ranked ``k``\
:sup:`th` by decreasing Borda score (using :attr:`~svvamp.Population.borda_score_c_rk`, i.e. strict
preferences).
For example, ``candidates_by_decreasing_borda_score_rk[0]`` is the candidate with highest Borda score (rk).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.candidates_by_decreasing_borda_score_rk
array([0, 1, 2])
"""
self.mylog("Compute candidates_by_decreasing_borda_score_rk", 1)
return self._tuple_decreasing_borda_score_rk[0]
@cached_property
def decreasing_borda_scores_rk(self):
"""1d array of integers. ``decreasing_borda_scores_rk[k]`` is the ``k``-th Borda score (using
:attr:`~svvamp.Population.borda_score_c_rk`, i.e. strict preferences) by decreasing order.
For example, ``decreasing_borda_scores_rk[0]`` is the highest Borda score for a candidate (rk).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.decreasing_borda_scores_rk
array([4, 2, 0])
"""
self.mylog("Compute decreasing_borda_scores_rk", 1)
return self._tuple_decreasing_borda_score_rk[1]
@cached_property
def _tuple_decreasing_borda_score_ut(self):
candidates_by_decreasing_borda_score_ut = np.array(np.argsort(- self.borda_score_c_ut, kind='mergesort'),
dtype=int)
decreasing_borda_scores_ut = self.borda_score_c_ut[candidates_by_decreasing_borda_score_ut]
return candidates_by_decreasing_borda_score_ut, decreasing_borda_scores_ut
@cached_property
def candidates_by_decreasing_borda_score_ut(self):
"""1d array of integers. ``candidates_by_decreasing_borda_score_ut[k]`` is the candidate ranked ``k``\
:sup:`th` by decreasing Borda score (using :attr:`~svvamp.Population.borda_score_c_ut`, i.e. weak preferences).
For example, ``candidates_by_decreasing_borda_score_ut[0]`` is the candidate with highest Borda score (ut).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.candidates_by_decreasing_borda_score_ut
array([0, 1, 2])
"""
self.mylog("Compute candidates_by_decreasing_borda_score_ut", 1)
return self._tuple_decreasing_borda_score_ut[0]
@cached_property
def decreasing_borda_scores_ut(self):
"""1d array of integers. ``decreasing_borda_scores_ut[k]`` is the ``k``-th Borda score (using
:attr:`~svvamp.Population.borda_score_c_ut`, i.e. weak preferences) by decreasing order.
For example, ``decreasing_borda_scores_ut[0]`` is the highest Borda score for a candidate (rk).
Examples
--------
>>> from svvamp import Profile
>>> profile = Profile(preferences_rk=[[0, 1, 2], [0, 1, 2]])
>>> profile.decreasing_borda_scores_ut
array([4., 2., 0.])
"""
self.mylog("Compute decreasing_borda_scores_ut", 1)
return self._tuple_decreasing_borda_score_ut[1]
# %% Plot a population
[docs]
def plot3(self, indexes=None, normalize=True, use_labels=True):
"""Plot utilities for 3 candidates (with approval limit).
Parameters
----------
indexes : list
List of 3 candidates. If None, defaults to [0, 1, 2].
normalize : bool
Cf. below.
use_labels : bool
If ``True``, then :attr:`~svvamp.Population.labels_candidates` is used to label
the plot. Otherwise, candidates are simply represented by their index.
Notes
-----
Each red point of the plot represents a voter ``v``. Its position is
:attr:`~svvamp.Population.preferences_ut`\\ ``[v, indexes]``. If ``normalize`` is ``True``, then each position
is normalized before plotting so that its Euclidean norm is equal to 1.
The equator (in blue) is the set of points :math:`\\mathbf{u}` such that :math:`\\sum {u_i}^2 = 1` and
:math:`\\sum u_i = 0`, i.e. the unit circle of the plan that is orthogonal to the main diagonal [1, 1, 1].
Other blue circles are the frontiers between the 6 different strict total orders on the candidates.
Examples
--------
Typical usage:
>>> initialize_random_seeds()
>>> preferences_ut_test = np.random.randint(-5, 5, (10, 5))
>>> profile = Profile(preferences_ut=preferences_ut_test,
... labels_candidates=['Alice', 'Bob', 'Catherine', 'Dave', 'Ellen'])
>>> profile.plot3(indexes=[0, 1, 2])
If `indexes` is not given, it defaults to [0, 1, 2]:
>>> profile.plot3()
You can ignore the labels of the candidates and simply use their numbers:
>>> profile.plot3(use_labels=False)
References
----------
Durand et al., 'Geometry on the Utility Space'.
"""
if indexes is None:
_indexes = [0, 1, 2]
else:
_indexes = indexes
# Define figure
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# North-South axis (not plotted anymore)
# ax.plot(xs=[-1, 1], ys=[-1, 1], zs=[-1, 1], c='k')
# Equator
theta = np.linspace(0, 2 * np.pi, 200)
xs = np.cos(theta) / np.sqrt(2) - np.sin(theta) / np.sqrt(6)
ys = - np.cos(theta) / np.sqrt(2) - np.sin(theta) / np.sqrt(6)
zs = 2 * np.sin(theta) / np.sqrt(6)
ax.scatter(xs, ys, zs, s=7, c='b')
# Frontiers between strict orders
for i in range(3):
pole = np.full(3, 1 / np.sqrt(3))
ortho = np.ones(3)
ortho[i] = -2
ortho = ortho / np.sqrt(6)
xs = np.cos(theta) * pole[0] + np.sin(theta) * ortho[0]
ys = np.cos(theta) * pole[1] + np.sin(theta) * ortho[1]
zs = np.cos(theta) * pole[2] + np.sin(theta) * ortho[2]
ax.scatter(xs, ys, zs, s=7, c='b')
# Voters
mat_temp = np.copy(self.preferences_ut[:, _indexes]).astype(float)
self.mylogm('mat_temp =', mat_temp, 3)
if normalize:
for v in range(mat_temp.shape[0]):
norm = np.sqrt(np.sum(mat_temp[v, :]**2))
if norm > 0:
mat_temp[v, :] /= norm
self.mylogm('mat_temp =', mat_temp, 3)
ax.scatter(mat_temp[:, 0], mat_temp[:, 1], mat_temp[:, 2], s=40, c='r', marker='o')
# Axes and labels
if use_labels:
ax.set_xlabel(self.labels_candidates[_indexes[0]] + ' (' + str(_indexes[0]) + ')')
ax.set_ylabel(self.labels_candidates[_indexes[1]] + ' (' + str(_indexes[1]) + ')')
ax.set_zlabel(self.labels_candidates[_indexes[2]] + ' (' + str(_indexes[2]) + ')')
else:
ax.set_xlabel('Candidate ' + str(_indexes[0]))
ax.set_ylabel('Candidate ' + str(_indexes[1]))
ax.set_zlabel('Candidate ' + str(_indexes[2]))
temp = 1 / np.sqrt(2)
for best in range(3):
for worst in range(3):
if best == worst:
continue
other = 3 - best - worst
position = np.zeros(3)
position[best] = temp
position[worst] = - temp
ax.text(position[0], position[1], position[2],
' ' + str(_indexes[best]) + ' > ' + str(_indexes[other]) + ' > ' + str(_indexes[worst]))
# plt.show()
[docs]
def plot4(self, indexes=None, normalize=True, use_labels=True):
"""Plot utilities for 4 candidates (without approval limit).
Parameters
----------
indexes : list
List of 4 candidates. If None, defaults to [0, 1, 2, 3].
normalize : bool
Cf. below.
use_labels : bool
If ``True``, then :attr:`~svvamp.Population.labels_candidates` is used to label
the plot. Otherwise, candidates are simply represented by their index.
Notes
-----
Each red point of the plot represents a voter ``v``.
* :attr:`~svvamp.Population.preferences_ut`\\ ``[v, indexes]`` is sent to the hyperplane that is
orthogonal to [1, 1, 1, 1] (by orthogonal projection), which discards information related to approval
limit and keeps only the relative preferences between candidates.
* The plot is done in this 3d hyperplane. In practice, we use a mirror symmetry that exchanges [1, 1, 1,
1] and [0, 0, 0, 1]. This way, the image vector is orthogonal to [0, 0, 0, 1] and can be plotted in the
first 3 dimensions.
* If ``normalize`` is True, then the image vector is normalized before plotting so that its Euclidean
norm is equal to 1.
Blue lines are the frontiers between the 24 different strict total orders on the candidates ('permutohedron').
Examples
--------
Typical usage:
>>> initialize_random_seeds()
>>> preferences_ut_test = np.random.randint(-5, 5, (10, 5))
>>> profile = Profile(preferences_ut=preferences_ut_test,
... labels_candidates=['Alice', 'Bob', 'Catherine', 'Dave', 'Ellen'])
>>> profile.plot4(indexes=[0, 1, 2, 4])
If `indexes` is not given, it defaults to [0, 1, 2, 3]:
>>> profile.plot4()
You can ignore the labels of the candidates and simply use their numbers:
>>> profile.plot4(use_labels=False)
It is possible to avoid normalization:
>>> profile.plot4(normalize=False)
References
----------
Durand et al., 'Geometry on the Utility Space'.
"""
if indexes is None:
_indexes = [0, 1, 2, 3]
else:
_indexes = indexes
# Define figure
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Voters
# We transform the population matrix to send it to R^3
# 0. Keep only the specified candidates
mat_temp = np.copy(self.preferences_ut[:, _indexes]).astype(float)
# 1. Send each voter to the hyperplane orthogonal to (1, 1, 1, 1)
mat_temp -= np.mean(mat_temp, 1)[:, np.newaxis]
# 2. Use symmetry by a hyperplane to exchange (1, 1, 1, 1) and (0, 0, 0, 1)
# unitary_parallel = [1, 1, 1, 1] / sqrt(4) - [0, 0, 0, 1] then normalize.
unitary_parallel = np.array([0.5, 0.5, 0.5, -0.5])
# unitary_parallel = unitary_parallel / np.sqrt(np.sum(unitary_parallel**2))
temp_parallel = np.outer(np.sum(mat_temp * unitary_parallel[np.newaxis, :], 1), unitary_parallel)
mat_temp -= 2 * temp_parallel
# 3. Normalize if asked
if normalize:
for v in range(mat_temp.shape[0]):
norm = np.sqrt(np.sum(mat_temp[v, :]**2))
if norm > 0:
mat_temp[v, :] /= norm
# 4. Now just do not care about last coordinate, it is 0.
ax.scatter(mat_temp[:, 0], mat_temp[:, 1], mat_temp[:, 2], s=40, c='r', marker='o')
# Permutohedron
xs = []
ys = []
zs = []
def add_line(v1, v2):
"""v1 and v2 are vertices."""
if normalize:
theta_tot = np.arccos(np.sum(v1 * v2))
ortho = v2 - np.sum(v2 * v1) * v1
ortho = ortho / np.sqrt(np.sum(ortho**2))
theta = np.linspace(0, theta_tot, 100)
xs.extend(np.cos(theta) * v1[0] + np.sin(theta) * ortho[0])
ys.extend(np.cos(theta) * v1[1] + np.sin(theta) * ortho[1])
zs.extend(np.cos(theta) * v1[2] + np.sin(theta) * ortho[2])
else:
xs.extend(np.linspace(v1[0], v2[0], 50))
ys.extend(np.linspace(v1[1], v2[1], 50))
zs.extend(np.linspace(v1[2], v2[2], 50))
def image(vertex):
result = np.multiply(vertex, 2) - 1
result = result - np.mean(result)
the_temp_parallel = np.sum(result*unitary_parallel) * unitary_parallel
result -= 2 * the_temp_parallel
if normalize:
result = result / np.sqrt(np.sum(result**2))
return result
for i in range(4):
for j in range(4):
if j == i:
continue
# Vertex 1 of type [1, 0, 0, 0]
vertex1 = image(np.array(range(4)) == i)
if use_labels:
pass
ax.text(vertex1[0], vertex1[1], vertex1[2], ' Favorite = ' + self.labels_candidates[_indexes[i]])
else:
ax.text(vertex1[0], vertex1[1], vertex1[2], ' Favorite = ' + str(_indexes[i]))
# Vertex 2 of type [1, 1, 1, 0]
vertex2 = np.ones(4)
vertex2[j] = 0
vertex2 = image(vertex2)
add_line(vertex1, vertex2)
# Vertex 2 of type [1, 1, 0, 0]
vertex2 = np.zeros(4)
vertex2[i] = 1
vertex2[j] = 1
vertex2 = image(vertex2)
add_line(vertex1, vertex2)
# Vertex 1 of type [0, 1, 1, 1]
vertex1 = image(np.array(range(4)) != i)
# Vertex 2 of type [1, 1, 0, 0]
vertex2 = np.ones(4)
vertex2[i] = 0
vertex2[j] = 0
vertex2 = image(vertex2)
add_line(vertex1, vertex2)
ax.scatter(xs, ys, zs, s=7, c='b')
# Conclude
plt.axis('off')
# plt.show()
# %% Demo
[docs]
def demo(self, log_depth=1):
"""Demonstrate the methods of :class:`~svvamp.Population` class.
Parameters
----------
log_depth : int
Integer from 0 (basic info) to 3 (verbose).
Examples
--------
>>> initialize_random_seeds()
>>> preferences_ut_test = np.random.randint(-5, 5, (10, 5))
>>> profile = Profile(preferences_ut=preferences_ut_test,
... labels_candidates=['Alice', 'Bob', 'Catherine', 'Dave', 'Ellen'],
... sort_voters=True)
>>> profile.demo(log_depth=0) # doctest: +NORMALIZE_WHITESPACE
<BLANKLINE>
************************
* Basic properties *
************************
n_v = 10
n_c = 5
labels_candidates = ['Alice', 'Bob', 'Catherine', 'Dave', 'Ellen']
preferences_ut =
[[ 4 -2 0 -3 -1]
[ 4 4 -5 -1 2]
[ 0 4 3 4 -1]
[-2 -2 2 -5 -4]
[-2 -3 2 -3 -5]
[-3 -2 3 -4 -2]
[ 2 1 3 3 -4]
[-2 -5 -2 0 -5]
[ 1 2 2 3 -4]
[ 0 -5 -2 -2 2]]
preferences_borda_ut =
[[4. 1. 3. 0. 2. ]
[3.5 3.5 0. 1. 2. ]
[1. 3.5 2. 3.5 0. ]
[2.5 2.5 4. 0. 1. ]
[3. 1.5 4. 1.5 0. ]
[1. 2.5 4. 0. 2.5]
[2. 1. 3.5 3.5 0. ]
[2.5 0.5 2.5 4. 0.5]
[1. 2.5 2.5 4. 0. ]
[3. 0. 1.5 1.5 4. ]]
preferences_borda_rk =
[[4 1 3 0 2]
[3 4 0 1 2]
[1 4 2 3 0]
[3 2 4 0 1]
[3 1 4 2 0]
[1 3 4 0 2]
[2 1 3 4 0]
[2 0 3 4 1]
[1 2 3 4 0]
[3 0 2 1 4]]
preferences_rk =
[[0 2 4 1 3]
[1 0 4 3 2]
[1 3 2 0 4]
[2 0 1 4 3]
[2 0 3 1 4]
[2 1 4 0 3]
[3 2 0 1 4]
[3 2 0 4 1]
[3 2 1 0 4]
[4 0 2 3 1]]
v_has_same_ordinal_preferences_as_previous_voter =
[False False False False False False False False False False]
<BLANKLINE>
************************
* Plurality scores *
************************
preferences_rk (reminder) =
[[0 2 4 1 3]
[1 0 4 3 2]
[1 3 2 0 4]
[2 0 1 4 3]
[2 0 3 1 4]
[2 1 4 0 3]
[3 2 0 1 4]
[3 2 0 4 1]
[3 2 1 0 4]
[4 0 2 3 1]]
plurality_scores_rk = [1 2 3 3 1]
majority_favorite_rk = nan
majority_favorite_rk_ctb = nan
<BLANKLINE>
preferences_borda_ut (reminder) =
[[4. 1. 3. 0. 2. ]
[3.5 3.5 0. 1. 2. ]
[1. 3.5 2. 3.5 0. ]
[2.5 2.5 4. 0. 1. ]
[3. 1.5 4. 1.5 0. ]
[1. 2.5 4. 0. 2.5]
[2. 1. 3.5 3.5 0. ]
[2.5 0.5 2.5 4. 0.5]
[1. 2.5 2.5 4. 0. ]
[3. 0. 1.5 1.5 4. ]]
plurality_scores_ut = [1 0 3 2 1]
majority_favorite_ut = nan
majority_favorite_ut_ctb = nan
<BLANKLINE>
********************
* Borda scores *
********************
preferences_borda_rk (reminder) =
[[4 1 3 0 2]
[3 4 0 1 2]
[1 4 2 3 0]
[3 2 4 0 1]
[3 1 4 2 0]
[1 3 4 0 2]
[2 1 3 4 0]
[2 0 3 4 1]
[1 2 3 4 0]
[3 0 2 1 4]]
borda_score_c_rk =
[23 18 28 19 12]
Remark: Borda scores above are computed with the matrix of duels.
Check: np.sum(self.preferences_borda_rk, 0) =
[23 18 28 19 12]
decreasing_borda_scores_rk =
[28 23 19 18 12]
candidates_by_decreasing_borda_score_rk =
[2 0 3 1 4]
<BLANKLINE>
preferences_borda_ut (reminder) =
[[4. 1. 3. 0. 2. ]
[3.5 3.5 0. 1. 2. ]
[1. 3.5 2. 3.5 0. ]
[2.5 2.5 4. 0. 1. ]
[3. 1.5 4. 1.5 0. ]
[1. 2.5 4. 0. 2.5]
[2. 1. 3.5 3.5 0. ]
[2.5 0.5 2.5 4. 0.5]
[1. 2.5 2.5 4. 0. ]
[3. 0. 1.5 1.5 4. ]]
borda_score_c_ut =
[23.5 18.5 27. 19. 12. ]
decreasing_borda_scores_ut =
[27. 23.5 19. 18.5 12. ]
candidates_by_decreasing_borda_score_ut =
[2 0 3 1 4]
<BLANKLINE>
*****************
* Utilities *
*****************
preferences_ut (reminder) =
[[ 4 -2 0 -3 -1]
[ 4 4 -5 -1 2]
[ 0 4 3 4 -1]
[-2 -2 2 -5 -4]
[-2 -3 2 -3 -5]
[-3 -2 3 -4 -2]
[ 2 1 3 3 -4]
[-2 -5 -2 0 -5]
[ 1 2 2 3 -4]
[ 0 -5 -2 -2 2]]
total_utility_c =
[ 2 -8 6 -8 -22]
total_utility_min = -22.0
total_utility_max = 6.0
total_utility_mean = -6.0
total_utility_std = 9.715966241192895
<BLANKLINE>
*******************************************
* Condorcet notions based on rankings *
*******************************************
preferences_rk (reminder) =
[[0 2 4 1 3]
[1 0 4 3 2]
[1 3 2 0 4]
[2 0 1 4 3]
[2 0 3 1 4]
[2 1 4 0 3]
[3 2 0 1 4]
[3 2 0 4 1]
[3 2 1 0 4]
[4 0 2 3 1]]
matrix_duels_rk =
[[0 6 3 6 8]
[4 0 2 5 7]
[7 8 0 5 8]
[4 5 5 0 5]
[2 3 2 5 0]]
matrix_victories_rk =
[[0. 1. 0. 1. 1. ]
[0. 0. 0. 0.5 1. ]
[1. 1. 0. 0.5 1. ]
[0. 0.5 0.5 0. 0.5]
[0. 0. 0. 0.5 0. ]]
condorcet_winner_rk = nan
exists_condorcet_order_rk = False
matrix_victories_rk_ctb =
[[0. 1. 0. 1. 1.]
[0. 0. 0. 1. 1.]
[1. 1. 0. 1. 1.]
[0. 0. 0. 0. 1.]
[0. 0. 0. 0. 0.]]
condorcet_winner_rk_ctb = 2
exists_condorcet_order_rk_ctb = True
<BLANKLINE>
***************************************
* Relative Condorcet notions (ut) *
***************************************
preferences_borda_ut (reminder) =
[[4. 1. 3. 0. 2. ]
[3.5 3.5 0. 1. 2. ]
[1. 3.5 2. 3.5 0. ]
[2.5 2.5 4. 0. 1. ]
[3. 1.5 4. 1.5 0. ]
[1. 2.5 4. 0. 2.5]
[2. 1. 3.5 3.5 0. ]
[2.5 0.5 2.5 4. 0.5]
[1. 2.5 2.5 4. 0. ]
[3. 0. 1.5 1.5 4. ]]
matrix_duels_ut =
[[0 5 3 6 8]
[3 0 2 4 6]
[6 7 0 4 8]
[4 4 4 0 5]
[2 2 2 5 0]]
matrix_victories_ut_rel =
[[0. 1. 0. 1. 1. ]
[0. 0. 0. 0.5 1. ]
[1. 1. 0. 0.5 1. ]
[0. 0.5 0.5 0. 0.5]
[0. 0. 0. 0.5 0. ]]
condorcet_winner_ut_rel = nan
exists_condorcet_order_ut_rel = False
matrix_victories_ut_rel_ctb =
[[0. 1. 0. 1. 1.]
[0. 0. 0. 1. 1.]
[1. 1. 0. 1. 1.]
[0. 0. 0. 0. 1.]
[0. 0. 0. 0. 0.]]
condorcet_winner_ut_rel_ctb = 2
exists_condorcet_order_ut_rel_ctb = True
<BLANKLINE>
***************************************
* Absolute Condorcet notions (ut) *
***************************************
matrix_duels_ut (reminder) =
[[0 5 3 6 8]
[3 0 2 4 6]
[6 7 0 4 8]
[4 4 4 0 5]
[2 2 2 5 0]]
matrix_victories_ut_abs =
[[0. 0.5 0. 1. 1. ]
[0. 0. 0. 0. 1. ]
[1. 1. 0. 0. 1. ]
[0. 0. 0. 0. 0.5]
[0. 0. 0. 0.5 0. ]]
condorcet_admissible_candidates =
[False False True False False]
nb_condorcet_admissible = 1
weak_condorcet_winners =
[False False True False False]
nb_weak_condorcet_winners = 1
condorcet_winner_ut_abs = nan
exists_condorcet_order_ut_abs = False
resistant_condorcet_winner = nan
threshold_c_prevents_w_Condorcet_ut_abs =
[[0 0 0 2 0]
[0 0 0 0 0]
[2 0 0 2 0]
[0 0 2 0 0]
[0 0 0 1 0]]
matrix_victories_ut_abs_ctb =
[[0. 1. 0. 1. 1.]
[0. 0. 0. 0. 1.]
[1. 1. 0. 0. 1.]
[0. 0. 0. 0. 1.]
[0. 0. 0. 0. 0.]]
condorcet_winner_ut_abs_ctb = nan
exists_condorcet_order_ut_abs_ctb = False
<BLANKLINE>
**********************************************
* Implications between Condorcet notions *
**********************************************
maj_fav_ut (False) ==> maj_fav_ut_ctb (False)
|| || || ||
|| V V ||
|| maj_fav_rk (False) ==> maj_fav_rk_ctb (False) ||
V || || ||
Resistant Condorcet (False) ||
|| || || ||
V || || V
Condorcet_ut_abs (False) ==> Condorcet_ut_abs_ctb (False)
|| || || || || ||
|| V V V V ||
|| Condorcet_rk (False) ==> Condorcet_rk_ctb (True) ||
V V
Condorcet_ut_rel (False) ==> Condorcet_ut_rel_ctb (True)
||
V
Weak Condorcet (True)
||
V
Condorcet-admissible (True)
"""
old_log_depth = self.log_depth
self.log_depth = log_depth
print_title("Basic properties")
print("n_v =", self.n_v)
print("n_c =", self.n_c)
print("labels_candidates =", self.labels_candidates)
printm("preferences_ut =", self.preferences_ut)
printm("preferences_borda_ut =", self.preferences_borda_ut)
printm("preferences_borda_rk =", self.preferences_borda_rk)
printm("preferences_rk =", self.preferences_rk)
printm("v_has_same_ordinal_preferences_as_previous_voter =",
self.v_has_same_ordinal_preferences_as_previous_voter)
print_title("Plurality scores")
printm("preferences_rk (reminder) =", self.preferences_rk)
print("plurality_scores_rk =", self.plurality_scores_rk)
print("majority_favorite_rk =", self.majority_favorite_rk)
print("majority_favorite_rk_ctb =", self.majority_favorite_rk_ctb)
print("")
printm("preferences_borda_ut (reminder) =", self.preferences_borda_ut)
print("plurality_scores_ut =", self.plurality_scores_ut)
print("majority_favorite_ut =", self.majority_favorite_ut)
print("majority_favorite_ut_ctb =", self.majority_favorite_ut_ctb)
print_title("Borda scores")
printm("preferences_borda_rk (reminder) =", self.preferences_borda_rk)
printm("borda_score_c_rk =", self.borda_score_c_rk)
print("Remark: Borda scores above are computed with the matrix of duels.")
printm("Check: np.sum(self.preferences_borda_rk, 0) =", np.sum(self.preferences_borda_rk, 0))
printm("decreasing_borda_scores_rk =", self.decreasing_borda_scores_rk)
printm("candidates_by_decreasing_borda_score_rk =", self.candidates_by_decreasing_borda_score_rk)
print("")
printm("preferences_borda_ut (reminder) =", self.preferences_borda_ut)
printm("borda_score_c_ut =", self.borda_score_c_ut)
printm("decreasing_borda_scores_ut =", self.decreasing_borda_scores_ut)
printm("candidates_by_decreasing_borda_score_ut =", self.candidates_by_decreasing_borda_score_ut)
print_title("Utilities")
printm("preferences_ut (reminder) =", self.preferences_ut)
printm("total_utility_c = ", self.total_utility_c)
print("total_utility_min =", self.total_utility_min)
print("total_utility_max =", self.total_utility_max)
print("total_utility_mean =", self.total_utility_mean)
print("total_utility_std =", self.total_utility_std)
print_title("Condorcet notions based on rankings")
printm("preferences_rk (reminder) =", self.preferences_rk)
printm("matrix_duels_rk =", self.matrix_duels_rk)
printm("matrix_victories_rk =", self.matrix_victories_rk)
print("condorcet_winner_rk =", self.condorcet_winner_rk)
print("exists_condorcet_order_rk =", self.exists_condorcet_order_rk)
printm("matrix_victories_rk_ctb =", self.matrix_victories_rk_ctb)
print("condorcet_winner_rk_ctb =", self.condorcet_winner_rk_ctb)
print("exists_condorcet_order_rk_ctb =", self.exists_condorcet_order_rk_ctb)
print_title("Relative Condorcet notions (ut)")
printm("preferences_borda_ut (reminder) =", self.preferences_borda_ut)
printm("matrix_duels_ut =", self.matrix_duels_ut)
printm("matrix_victories_ut_rel =", self.matrix_victories_ut_rel)
print("condorcet_winner_ut_rel =", self.condorcet_winner_ut_rel)
print("exists_condorcet_order_ut_rel =", self.exists_condorcet_order_ut_rel)
printm("matrix_victories_ut_rel_ctb =", self.matrix_victories_ut_rel_ctb)
print("condorcet_winner_ut_rel_ctb =", self.condorcet_winner_ut_rel_ctb)
print("exists_condorcet_order_ut_rel_ctb =", self.exists_condorcet_order_ut_rel_ctb)
print_title("Absolute Condorcet notions (ut)")
printm("matrix_duels_ut (reminder) =", self.matrix_duels_ut)
printm("matrix_victories_ut_abs =", self.matrix_victories_ut_abs)
printm("condorcet_admissible_candidates = ", self.condorcet_admissible_candidates)
print("nb_condorcet_admissible =", self.nb_condorcet_admissible)
printm("weak_condorcet_winners =", self.weak_condorcet_winners)
print("nb_weak_condorcet_winners =", self.nb_weak_condorcet_winners)
print("condorcet_winner_ut_abs =", self.condorcet_winner_ut_abs)
print("exists_condorcet_order_ut_abs =", self.exists_condorcet_order_ut_abs)
print("resistant_condorcet_winner =", self.resistant_condorcet_winner)
printm("threshold_c_prevents_w_Condorcet_ut_abs =", self.threshold_c_prevents_w_condorcet_ut_abs)
printm("matrix_victories_ut_abs_ctb =", self.matrix_victories_ut_abs_ctb)
print("condorcet_winner_ut_abs_ctb =", self.condorcet_winner_ut_abs_ctb)
print("exists_condorcet_order_ut_abs_ctb =", self.exists_condorcet_order_ut_abs_ctb)
print_title("Implications between Condorcet notions")
# maj_fav_ut (False) ==> maj_fav_ut_ctb (False)
# || || || ||
# || V V ||
# || maj_fav_rk (False) ==> maj_fav_rk_ctb (False) ||
# V || || ||
# Resistant Condorcet (False) ||
# || || || ||
# V || || V
# Condorcet_ut_abs (False) ==> Condorcet_ut_abs_ctb (False)
# || || || || || ||
# || V V V V ||
# || Condorcet_rk (False) ==> Condorcet_rk_ctb (False) ||
# V V
# Condorcet_ut_rel (False) ==> Condorcet_ut_rel_ctb (False)
# ||
# V
# Weak Condorcet (False)
# ||
# V
# Condorcet-admissible (False)
def display_bool(value):
return '(True) ' if equal_true(value) else '(False)'
print('maj_fav_ut ' + display_bool(self.majority_favorite_ut) + ' ==> '
+ 'maj_fav_ut_ctb ' + display_bool(self.majority_favorite_ut_ctb))
print(' || || || ||')
print(' || V V ||')
print(' || maj_fav_rk ' + display_bool(self.majority_favorite_rk) + ' ==> '
+ 'maj_fav_rk_ctb ' + display_bool(self.majority_favorite_rk_ctb) + ' ||')
print(' V || || ||')
print('Resistant Condorcet ' + display_bool(self.resistant_condorcet_winner)
+ ' ||')
print(' || || || ||')
print(' V || || V')
print('Condorcet_ut_abs ' + display_bool(self.condorcet_winner_ut_abs) + ' ==> '
'Condorcet_ut_abs_ctb ' + display_bool(self.condorcet_winner_ut_abs_ctb) + '')
print(' || || || || || ||')
print(' || V V V V ||')
print(' || Condorcet_rk ' + display_bool(self.exists_condorcet_winner_rk) +
' ==> Condorcet_rk_ctb ' + display_bool(self.exists_condorcet_winner_rk_ctb) + ' ||')
print(' V V')
print('Condorcet_ut_rel ' + display_bool(self.exists_condorcet_winner_ut_rel) +
' ==> Condorcet_ut_rel_ctb ' + display_bool(self.exists_condorcet_winner_ut_rel_ctb))
print(' ||')
print(' V')
print('Weak Condorcet ' + display_bool(self.exists_weak_condorcet_winner))
print(' ||')
print(' V')
print('Condorcet-admissible ' + display_bool(self.exists_condorcet_admissible))
self.log_depth = old_log_depth
# %% For developers
[docs]
def to_doctest_string(self, ut=True, rk=True):
"""Convert to string, in the doctest format.
Parameters
----------
ut : bool
Whether to print `preferences_ut`.
rk : bool
Whether to print `preferences_rk`.
Returns
-------
str
A string that can be copied-pasted to make a doctest.
Examples
--------
>>> profile = Profile(preferences_rk=[[0, 1], [0, 1]])
>>> print('.' + profile.to_doctest_string()) # doctest: +NORMALIZE_WHITESPACE
. >>> profile = Profile(preferences_ut=[
... [1, 0],
... [1, 0],
... ], preferences_rk=[
... [0, 1],
... [0, 1],
... ])
"""
s = ' >>> profile = Profile('
arguments = []
if ut:
argument = ''
argument += 'preferences_ut=[\n'
argument += (
repr(self.preferences_ut)
.replace('array([', ' ... ')
.replace('\n ', '\n ... ')
.replace('])', ',')
)
argument += '\n ... ]'
arguments.append(argument)
if rk:
argument = ''
argument += 'preferences_rk=[\n'
argument += (
repr(self.preferences_rk)
.replace('array([', ' ... ')
.replace('\n ', '\n ... ')
.replace('])', ',')
)
argument += '\n ... ]'
arguments.append(argument)
s += ', '.join(arguments)
s += ')'
return s
def to_csv(self, file_name):
df = pd.DataFrame(self.preferences_ut, columns=self.labels_candidates)
df.to_csv(file_name, sep=';')
def plurality_elimination_engine(self):
return PluralityEliminationEngineProfile(self)
@cached_property
def c_might_be_there_when_cw_is_eliminated_irv_style(self):
"""Whether a candidate c might be present in a round where the Condorcet winner is eliminated, IRV-style.
Returns
-------
ndarray or None
If there is no :attr:`condorcet_winner_rk_ctb`, then return None.
If there is a :attr:`condorcet_winner_rk_ctb`, denote her `w`.
`c_might_be_there_when_cw_rk_is_eliminated_irv_style[c]` is True iff there exists a subset of candidates
including `c` and `w` such that, in a Plurality election over this subset of candidates, `w` has at most
`n_v / card(subset)` votes. What is interesting is the negation: if it is False, then it is impossible
for `c` to manipulate in several IRV-related rules, because `c` cannot be there when `w` is eliminated.
Examples
--------
When there is no Condorcet winner (rk ctb), then the output is None:
>>> profile = Profile(preferences_rk=[[0, 1, 2], [1, 2, 0], [2, 0, 1]])
>>> profile.condorcet_winner_rk_ctb
nan
>>> print(profile.c_might_be_there_when_cw_is_eliminated_irv_style)
None
"""
if not self.exists_condorcet_winner_rk_ctb:
return None
w = self.condorcet_winner_rk_ctb
c_might_be_there_when_w_is_eliminated_irv_style = np.zeros(self.n_c, dtype=bool)
c_might_be_there_when_w_is_eliminated_irv_style[w] = True # To allow for np.all(), but remove at the end
other_candidates = [c for c in range(self.n_c) if c != w]
for n_opponents in range(2, self.n_c):
for subset in itertools.combinations(other_candidates, n_opponents):
candidates_r = np.array([w] + list(subset))
preferences_borda_rk_r = self.preferences_borda_rk[:, candidates_r]
score_w_r = np.sum(np.argmax(preferences_borda_rk_r, axis=1) == 0)
if score_w_r <= self.n_v / (n_opponents + 1):
c_might_be_there_when_w_is_eliminated_irv_style[np.array(subset)] = True
if np.all(c_might_be_there_when_w_is_eliminated_irv_style):
c_might_be_there_when_w_is_eliminated_irv_style[w] = False
return c_might_be_there_when_w_is_eliminated_irv_style
c_might_be_there_when_w_is_eliminated_irv_style[w] = False
return c_might_be_there_when_w_is_eliminated_irv_style
@cached_property
def exists_irv_immune_candidate(self):
"""Whether there exists a 'IRV-immune' candidate.
A candidate `w` is IRV-immune iff she is Condorcet winner (rk ctb) and for any other candidate `c`,
we have that `c_might_be_there_when_cw_is_eliminated_irv_style[c] ` is False. In other words, there is no
subset of candidates containing `w` such that `w` has at most `n_v / card(subset)` plurality votes.
Examples
--------
>>> profile = Profile(preferences_rk=[[0, 2, 1], [0, 2, 1], [1, 2, 0], [1, 2, 0], [2, 0, 1]])
>>> profile.condorcet_winner_rk_ctb
2
>>> profile.exists_irv_immune_candidate
False
"""
return self.exists_condorcet_order_rk_ctb and not(np.any(self.c_might_be_there_when_cw_is_eliminated_irv_style))
@cached_property
def necessary_coalition_size_to_break_irv_immunity(self):
"""Necessary coalition size to break IRV immunity.
Returns
-------
ndarray or None
If there is no :attr:`condorcet_winner_rk_ctb`, then return None.
If there is a :attr:`condorcet_winner_rk_ctb`, denote her `w`. For a given `c`, consider the voters
who would be sincere in case of manipulation for `c`, i.e. those who do not prefer `c` to `w`. Now,
consider a subset of candidates that includes `c` and `w`. The sincere voters ensure that `w` has a certain
plurality score `s`. To eliminate `w` in this configuration, it is necessary that the total number of voters
(sincere + manipulators) is greater than `s` multiplied by the number of candidates in the subset.
Implicitly, this implies a necessary number of manipulators. Taking the minimal value of this necessary
number over all possible subsets, this gives `necessary_coalition_size_to_break_irv_immunity[c]`. In an
IRV-style election, it is necessary to have at least this number of manipulators to manipulate in favor
of `c`.
Examples
--------
When there is no Condorcet winner (rk ctb), then the output is None:
>>> profile = Profile(preferences_rk=[[0, 1, 2], [1, 2, 0], [2, 0, 1]])
>>> profile.condorcet_winner_rk_ctb
nan
>>> print(profile.necessary_coalition_size_to_break_irv_immunity)
None
"""
if not self.exists_condorcet_winner_rk_ctb:
return None
w = self.condorcet_winner_rk_ctb
result = np.zeros(self.n_c, dtype=int)
for c in range(self.n_c):
if c == w:
continue
other_candidates = [d for d in range(self.n_c) if d != c and d != w]
n_s = self.n_v - self.matrix_duels_ut[c, w]
voters_s = (self.preferences_ut[:, w] >= self.preferences_ut[:, c])
n_m = None
for n_opponents in range(0, self.n_c - 1):
nb_candidates_r = n_opponents + 2
for subset in itertools.combinations(other_candidates, n_opponents):
candidates_r = np.array([w] + [c] + list(subset))
preferences_borda_rk_s_r = self.preferences_borda_rk[voters_s, :][:, candidates_r]
score_w_s_r = np.sum(np.argmax(preferences_borda_rk_s_r, axis=1) == 0)
n_m_new = score_w_s_r * nb_candidates_r - n_s
if n_m is None:
n_m = n_m_new
else:
n_m = min(n_m, n_m_new)
result[c] = n_m
return result