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