Skip to content
Snippets Groups Projects
Commit c0ec9adb authored by Millian Poquet's avatar Millian Poquet
Browse files

code: Schwartz sequential dropping + tests

parent 684efe67
Branches
No related tags found
No related merge requests found
#!/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
......@@ -78,6 +79,101 @@ def schulze_winners(n, candidates, link_strength_method='margin'):
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')
......@@ -107,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}')
......@@ -29,6 +29,10 @@ def test_example1():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
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():
......@@ -53,6 +57,10 @@ def test_example2():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
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():
......@@ -81,6 +89,10 @@ def test_example3():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
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():
......@@ -104,6 +116,10 @@ def test_example4():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
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():
......@@ -128,6 +144,10 @@ def test_example5():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
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():
......@@ -154,6 +174,10 @@ def test_example6():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
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
......@@ -182,6 +206,10 @@ def test_example7():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
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']
......@@ -203,6 +231,10 @@ def test_example7():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
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():
......@@ -232,6 +264,10 @@ def test_example8():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
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']
......@@ -260,6 +296,10 @@ def test_example8():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
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():
......@@ -284,6 +324,10 @@ def test_example9():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
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']
......@@ -307,6 +351,10 @@ def test_example9():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
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']
......@@ -337,9 +385,10 @@ def test_example10():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
assert elect.schulze_winners(n, candidates, link_strength_method='margin') == ['a']
assert elect.schulze_winners(n, candidates, link_strength_method='ratio') == ['b']
#assert False, "TODO: implement other methods to compute strength of link"
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']
......@@ -367,6 +416,10 @@ def test_example11():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
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():
......@@ -395,6 +448,10 @@ def test_example12():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
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():
......@@ -416,3 +473,7 @@ def test_example13():
assert len(elect.weak_condorcet_winners(n, candidates)) == 0
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']
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment