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