-
Millian Poquet authoredMillian Poquet authored
elect.py 4.50 KiB
#!/usr/bin/env python3
import argparse
import json
import numpy as np
# N[a,b] is the number of voters who prefer candidate a to candidate b
def load_pairwise_preferences_matrix(file):
list_of_lists = json.load(file)
n = np.stack([np.asarray(l, dtype=np.int8) for l in list_of_lists])
return n
# As defined in section 4.12.1 https://arxiv.org/pdf/1804.02973.pdf,
# A Condorcet winner is an alternative a ∈ A that wins every head-to-head
# contest with some other alternative b ∈ A \ {a}.
def condorcet_winner(n, candidates):
nb_candidates = len(candidates)
for potential_winner in range(nb_candidates):
won_all_contests = True
for opponent in range(nb_candidates):
if n[opponent][potential_winner] > n[potential_winner][opponent]:
won_all_contests = False
if won_all_contests:
return candidates[potential_winner]
return None
# As defined in section 4.12.1 https://arxiv.org/pdf/1804.02973.pdf,
# A weak Condorcet winner is an alternative a ∈ A that doesn’t lose any
# head-to-head contest with some other alternative b ∈ A \ {a}
def weak_condorcet_winners(n, candidates):
weak_winners = []
nb_candidates = len(candidates)
for potential_winner in range(nb_candidates):
lost_a_contest = False
for opponent in range(nb_candidates):
if n[opponent][potential_winner] > n[potential_winner][opponent]:
lost_a_contest = True
if not lost_a_contest:
weak_winners.append(candidates[potential_winner])
return weak_winners
# As defined in section 4 https://citizensassembly.arts.ubc.ca/resources/submissions/csharman-10_0409201706-143.pdf
# + other link strength methods defined in https://arxiv.org/pdf/1804.02973.pdf
def schulze_winners(n, candidates, link_strength_method='margin'):
nb_candidates = len(candidates)
if link_strength_method == 'margin':
p = np.zeros((nb_candidates, nb_candidates), dtype=np.int8)
for i in range(nb_candidates):
for j in range(nb_candidates):
if i != j:
p[i][j] = n[i][j] - n[j][i]
elif link_strength_method == 'ratio':
p = np.ones((nb_candidates, nb_candidates), dtype=np.int8) * np.Inf
for i in range(nb_candidates):
for j in range(nb_candidates):
if i != j and n[j][i] > 0:
p[i][j] = n[i][j] / n[j][i]
else:
raise ValueError(f"link_strength_method '{link_strength_method}' is not implemented")
for i in range(nb_candidates):
for j in range(nb_candidates):
if i != j:
for k in range(nb_candidates):
if i != k and j != k:
s = min(p[j][i], p[i][k])
if p[j][k] < s:
p[j][k] = s
winners = []
for i in range(nb_candidates):
has_lost_directly_or_indirectly = False
for j in range(nb_candidates):
if i != j:
if p[j][i] > p[i][j]:
has_lost_directly_or_indirectly = True
if not has_lost_directly_or_indirectly:
winners.append(candidates[i])
return winners
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Computes winners of the election')
parser.add_argument('-c', '--candidates-file', help='candidates JSON file', type=argparse.FileType('r', encoding='utf-8'), required=True)
parser.add_argument('matrix_file', help="pairwise preference matric JSON file", type=argparse.FileType('r', encoding='utf-8'))
args = parser.parse_args()
n = load_pairwise_preferences_matrix(args.matrix_file)
assert(n.shape[0] > 0)
assert(n.shape[0] == n.shape[1])
candidates = json.load(args.candidates_file)
assert(len(candidates) == n.shape[0])
# Condorcet winner (win every head-to-head contest)
c_winner = condorcet_winner(n, candidates)
if c_winner is None:
print('There is no Condorcet winner')
else:
print(f'Condorcet winner: {c_winner}')
# Condorcet weak winner (do not lose any head-to-head contest)
weak_c_winners = weak_condorcet_winners(n, candidates)
if len(weak_c_winners) == 0:
print('There are no weak Condorcet winners')
else:
print(f'Weak Condorcet winners: {weak_c_winners}')
# Schulze winners (win every head-to-head contest, directly or indirectly)
schulze_winners = schulze_winners(n, candidates)
assert(len(schulze_winners) > 0)
print(f'Schulze winners: {schulze_winners}')