ProfileNoisyDiscrete: More Options
[1]:
from fractions import Fraction
import poisson_approval as pa
Weak orders
The profile can contains weak orders. Voters of the form 'a>b~c'
(lovers) always vote for their top candidate, and voters of the form 'a~b>c'
(haters) always vote for their two top candidates.
[2]:
profile = pa.ProfileNoisyDiscrete({
('abc', 0.9, 0.01): Fraction(3, 10),
('bac', 0.9, 0.01): Fraction(3, 10),
'c>a~b': Fraction(4, 10)
})
profile
[2]:
<abc 0.9 ± 0.01: 3/10, bac 0.9 ± 0.01: 3/10, c>a~b: 2/5> (Condorcet winner: a, b)
Which rankings are in the profile?
[3]:
profile.support_in_rankings
[3]:
{abc, bac}
Which weak orders are in the profile?
[4]:
profile.support_in_weak_orders
[4]:
{c>a~b}
Does the profile contain rankings?
[5]:
profile.contains_rankings
[5]:
True
Does the profile contain weak orders?
[6]:
profile.contains_weak_orders
[6]:
True
Expressive voters
Two kinds of expressive voters are defined:
Sincere voters vote for their top candidate anyway, and vote for their second candidate if and only if their utility for her is strictly greater than 0.5.
Fanatic voters always vote for their top candidate only.
Define a profile with some sincere and some fanatic voters:
[7]:
profile = pa.ProfileNoisyDiscrete({
('abc', 0.4, 0.01): Fraction(1, 10),
('bac', 0.2, 0.01): Fraction(6, 10),
('cab', 0.7, 0.01): Fraction(3, 10)
}, ratio_sincere=Fraction(1, 100), ratio_fanatic=Fraction(2, 100))
profile
[7]:
<abc 0.4 ± 0.01: 1/10, bac 0.2 ± 0.01: 3/5, cab 0.7 ± 0.01: 3/10> (Condorcet winner: b) (ratio_sincere: 1/100) (ratio_fanatic: 1/50)
In the example above, in all groups, a fraction 1/100 of the voters vote sincerely, and a fraction 2/100 vote fanatically. For example, let us define a strategy:
[8]:
strategy = pa.StrategyOrdinal({'abc': 'a', 'bac': 'ab', 'cab': 'c'})
strategy
[8]:
<abc: a, bac: ab, cab: c>
If we apply this strategy to the profile, what happens?
[9]:
profile.tau(strategy)
[9]:
<a: 1/10, ab: 291/500, ac: 3/1000, b: 9/500, c: 297/1000> ==> a
Out of the 3/10 of voters of the group \((cab, 0.7, 0.01)\):
1/100 of them, i.e. 3/1000, are sincere and vote for \(ac\),
2/100 of them, i.e. 6/1000, are fanatic and vote for \(c\),
The rest of them, i.e. 291/1000, apply the given strategy and vote for \(c\).
In total, 3/1000 vote for \(ac\) and 297/1000 vote for \(c\).
The analyzed strategies take this behavior into account:
[10]:
profile.analyzed_strategies_ordinal
[10]:
Equilibrium:
<abc: a, bac: b, cab: ac> ==> b (FF)
Non-equilibria:
<abc: a, bac: b, cab: c> ==> b (FF)
<abc: a, bac: ab, cab: c> ==> a (D)
<abc: a, bac: ab, cab: ac> ==> a (D)
<abc: ab, bac: b, cab: c> ==> b (FF)
<abc: ab, bac: b, cab: ac> ==> b (FF)
<abc: ab, bac: ab, cab: c> ==> b (D)
<abc: ab, bac: ab, cab: ac> ==> a (D)
Note that without expressive voters, the equilibria are different:
[11]:
profile = pa.ProfileNoisyDiscrete({
('abc', 0.4, 0.01): Fraction(1, 10),
('bac', 0.2, 0.01): Fraction(6, 10),
('cab', 0.7, 0.01): Fraction(3, 10)
})
profile.analyzed_strategies_ordinal
[11]:
Equilibria:
<abc: a, bac: b, cab: ac> ==> b (FF)
<abc: a, bac: ab, cab: c> ==> a (D)
Non-equilibria:
<abc: a, bac: b, cab: c> ==> b (FF)
<abc: a, bac: ab, cab: ac> ==> a (D)
<abc: ab, bac: b, cab: c> ==> b (FF)
<abc: ab, bac: b, cab: ac> ==> b (FF)
<abc: ab, bac: ab, cab: c> ==> a, b (FF)
<abc: ab, bac: ab, cab: ac> ==> a (D)
Other Voting Rules: Plurality and Anti-Plurality
In addition to Approval, the package also implements Plurality and Anti-plurality.
For Plurality, you just need to specify voting_rule=pa.PLURALITY
when you define a Profile or a Strategy:
[12]:
profile = pa.ProfileNoisyDiscrete({
('abc', 0.4, 0.01): Fraction(1, 10),
('bac', 0.2, 0.01): Fraction(6, 10),
('cab', 0.7, 0.01): Fraction(3, 10)
}, voting_rule=pa.PLURALITY)
profile
[12]:
<abc 0.4 ± 0.01: 1/10, bac 0.2 ± 0.01: 3/5, cab 0.7 ± 0.01: 3/10> (Condorcet winner: b) (Plurality)
[13]:
strategy = pa.StrategyOrdinal({'abc': 'a', 'bac': 'b', 'cab': 'a'}, voting_rule=pa.PLURALITY)
strategy
[13]:
<abc: a, bac: b, cab: a> (Plurality)
Note that if you define a strategy with an attached profile, you do not need to specify the voting rule again because it is deduced from the one of the profile:
[14]:
strategy = pa.StrategyOrdinal({'abc': 'a', 'bac': 'b', 'cab': 'a'}, profile=profile)
strategy
[14]:
<abc: a, bac: b, cab: a> ==> b (Plurality)
All the other features work as usual. For example:
[15]:
profile.analyzed_strategies_ordinal
[15]:
Equilibria:
<abc: a, bac: b, cab: a> ==> b (Plurality) (FF)
<abc: a, bac: a, cab: c> ==> a (Plurality) (FF)
<abc: b, bac: b, cab: c> ==> b (Plurality) (FF)
Non-equilibria:
<abc: a, bac: b, cab: c> ==> b (Plurality) (FF)
<abc: a, bac: a, cab: a> ==> a (Plurality) (UF)
<abc: b, bac: b, cab: a> ==> b (Plurality) (FF)
<abc: b, bac: a, cab: c> ==> a (Plurality) (FF)
<abc: b, bac: a, cab: a> ==> a (Plurality) (FF)
Similarly, there exists an option voting_rule=pa.ANTI_PLURALITY
. Since this package is focused on Approval, the anti-plurality ballots are represented by their approval counterpart: for example, a ballot against candidate \(c\) is represented by \(ab\).
[16]:
profile = pa.ProfileNoisyDiscrete({
('abc', 0.4, 0.01): Fraction(1, 10),
('bac', 0.2, 0.01): Fraction(6, 10),
('cab', 0.7, 0.01): Fraction(3, 10)
}, voting_rule=pa.ANTI_PLURALITY)
profile
[16]:
<abc 0.4 ± 0.01: 1/10, bac 0.2 ± 0.01: 3/5, cab 0.7 ± 0.01: 3/10> (Condorcet winner: b) (Anti-plurality)
[17]:
strategy = pa.StrategyOrdinal({'abc': 'ab', 'bac': 'ab', 'cab': 'ac'}, profile=profile)
strategy
[17]:
<abc: ab, bac: ab, cab: ac> ==> a (Anti-plurality)
[18]:
strategy.is_equilibrium
[18]:
EquilibriumStatus.NOT_EQUILIBRIUM