Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • poquet/schulze
1 result
Select Git revision
Show changes
Commits on Source (2)
#!/usr/bin/env python3
import argparse
import json
import networkx as nx
import numpy as np
# N[a,b] is the number of voters who prefer candidate a to candidate b
......@@ -39,13 +40,24 @@ def weak_condorcet_winners(n, candidates):
return weak_winners
# As defined in section 4 https://citizensassembly.arts.ubc.ca/resources/submissions/csharman-10_0409201706-143.pdf
def schulze_winners(n, candidates):
# + 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)
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]
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):
......@@ -67,6 +79,101 @@ def schulze_winners(n, candidates):
winners.append(candidates[i])
return winners
# from https://en.wikipedia.org/wiki/Schwartz_set (2023-07-14)
# In voting systems, the Schwartz set is the union of all Schwartz set components.
# A Schwartz set component is any non-empty set S of candidates such that
# Every candidate inside the set S is pairwise unbeaten by every candidate outside S; and
# No non-empty proper subset of S fulfills the first property.
def schwartz_set(graph, candidates):
nb_nodes = 0
candidate_to_beat_index = dict()
beat_index_to_candidate = dict()
for node in graph.nodes():
candidate_to_beat_index[node] = nb_nodes
beat_index_to_candidate[nb_nodes] = node
nb_nodes += 1
beats = np.zeros((nb_nodes, nb_nodes), dtype=bool)
for (i, neighbors) in graph.adjacency():
for j in neighbors:
beats[candidate_to_beat_index[i]][candidate_to_beat_index[j]] = True
for k in range(nb_nodes):
for i in range(nb_nodes):
for j in range(nb_nodes):
beats[i][j] = beats[i][j] or (beats[i][k] and beats[k][j])
schwartz_components = []
for i in range(nb_nodes):
schwartz_component = {i}
is_schwartz = True
for j in range(nb_nodes):
if beats[j][i]:
if beats[i][j]:
schwartz_component.add(j)
else:
is_schwartz = False
if is_schwartz:
schwartz_components.append(schwartz_component)
schwartz_set_index = set.union(*schwartz_components)
return [beat_index_to_candidate[x] for x in schwartz_set_index]
# As defined in Appendix 4 of https://citizensassembly.arts.ubc.ca/resources/submissions/csharman-10_0409201706-143.pdf
def schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin'):
nb_candidates = len(candidates)
graph = nx.DiGraph()
graph.add_nodes_from(candidates)
for i in range(nb_candidates):
for j in range(i, nb_candidates):
margin = n[i][j] - n[j][i]
if margin != 0:
(x, y) = (i, j) if margin > 0 else (j, i)
if link_strength_method == 'margin':
graph.add_edge(candidates[x], candidates[y], weight=abs(margin))
elif link_strength_method == 'ratio':
graph.add_edge(candidates[x], candidates[y], weight=np.Inf if n[y][x] == 0 else n[x][y]/n[y][x])
elif link_strength_method == 'winning_votes':
graph.add_edge(candidates[x], candidates[y], weight=n[x][y])
elif link_strength_method == 'losing_votes':
graph.add_edge(candidates[x], candidates[y], weight=-n[y][x])
else:
raise ValueError(f"link_strength_method='{link_strength_method}' is not implemented")
while len(graph.nodes()) > 1:
s_set = schwartz_set(graph, candidates)
#print(f'Schwartz set: {s_set}')
# eliminate all candidates that are not in the Schwartz set
nodes_to_remove = []
for candidate in graph.nodes():
if candidate not in s_set:
nodes_to_remove.append(candidate)
if len(nodes_to_remove) > 0:
#print(f'Removing nodes: {nodes_to_remove}')
graph.remove_nodes_from(nodes_to_remove)
if len(graph.nodes()) > 1 and len(graph.edges()) > 1:
# remove all the edges that have the current minimal weight
minimum_weight = np.Inf
edges_per_weight = dict()
for (i, neighbors) in graph.adjacency():
for (neighbor, data) in neighbors.items():
weight = data['weight']
minimum_weight = min(minimum_weight, weight)
if weight in edges_per_weight:
edges_per_weight[weight].append((i, neighbor))
else:
edges_per_weight[weight] = [(i, neighbor)]
#print(f'Removing edges of weight={minimum_weight}: {edges_per_weight[minimum_weight]}')
for (i, j) in edges_per_weight[minimum_weight]:
graph.remove_edge(i, j)
continue
break
return [x for x in graph.nodes()]
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Computes winners of the election')
......@@ -96,6 +203,13 @@ if __name__ == '__main__':
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}')
for method in ['margin', 'ratio']:
s_winners = schulze_winners(n, candidates, link_strength_method=method)
assert(len(s_winners) > 0)
print(f'Schulze winners (beatpath, {method}): {s_winners}')
# Schulze winners, computed via the Schwartz sequential dropping algorithm
for method in ['margin', 'ratio', 'winning_votes', 'losing_votes']:
s_winners = schwartz_sequential_dropping_winners(n, candidates, link_strength_method=method)
assert(len(s_winners) > 0)
print(f'Schulze winners (ssd, {method}): {s_winners}')
......@@ -27,7 +27,12 @@ def test_example1():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['d']
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['d']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['d']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['d']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['d']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['d']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['d']
def test_example2():
......@@ -50,7 +55,12 @@ def test_example2():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['c']
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['c']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['c']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['c']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['c']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['c']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['c']
def test_example3():
......@@ -77,7 +87,12 @@ def test_example3():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['e']
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['e']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['e']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['e']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['e']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['e']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['e']
def test_example4():
......@@ -99,7 +114,12 @@ def test_example4():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['b', 'd']
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['b', 'd']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['b', 'd']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['b', 'd']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['b', 'd']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['b', 'd']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['b', 'd']
def test_example5():
......@@ -122,7 +142,12 @@ def test_example5():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['d']
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['d']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['d']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['d']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['d']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['d']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['d']
def test_example6():
......@@ -147,7 +172,12 @@ def test_example6():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['a', 'c']
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['a', 'c']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['a', 'c']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['a', 'c']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['a', 'c']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['a', 'c']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['a', 'c']
def test_example7():
# situation 1
......@@ -174,7 +204,12 @@ def test_example7():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['a']
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['a']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['a']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['a']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['a']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['a']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['a']
# situation 2
candidates = ['a', 'b', 'c', 'd', 'e', 'f']
......@@ -194,7 +229,12 @@ def test_example7():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['d']
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['d']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['d']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['d']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['d']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['d']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['d']
def test_example8():
......@@ -222,7 +262,12 @@ def test_example8():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['a']
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['a']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['a']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['a']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['a']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['a']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['a']
# situation 2
candidates = ['a', 'b', 'c', 'd', 'e']
......@@ -249,7 +294,12 @@ def test_example8():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['b']
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['b']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['b']
def test_example9():
......@@ -272,7 +322,12 @@ def test_example9():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['a']
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['a']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['a']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['a']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['a']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['a']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['a']
# situation 2
candidates = ['a', 'b', 'c', 'd', 'e']
......@@ -294,7 +349,12 @@ def test_example9():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['b']
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['b']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['b']
def test_example10():
candidates = ['a', 'b', 'c', 'd']
......@@ -323,10 +383,12 @@ def test_example10():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['a']
#assert False, "TODO: implement other methods to compute strength of link"
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['a']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['a']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['d']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['c']
def test_example11():
candidates = ['a', 'b', 'c', 'd', 'e']
......@@ -352,7 +414,12 @@ def test_example11():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['b']
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['b']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['b']
def test_example12():
......@@ -379,7 +446,12 @@ def test_example12():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['e']
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['e']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['e']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['e']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['e']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['e']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['e']
def test_example13():
......@@ -399,4 +471,9 @@ def test_example13():
assert elect.condorcet_winner(n, candidates) is None
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates) == ['a', 'b']
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['a', 'b']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['a', 'b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='margin') == ['a', 'b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='ratio') == ['a', 'b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='winning_votes') == ['a', 'b']
assert elect.schwartz_sequential_dropping_winners(n, candidates, link_strength_method='losing_votes') == ['a', 'b']