diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..bee8a64b79a99590d5303307144172cfe824fbf7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/elect.py b/elect.py new file mode 100755 index 0000000000000000000000000000000000000000..7ebcd12a14c92b64eb70de01dc937c51278363c2 --- /dev/null +++ b/elect.py @@ -0,0 +1,101 @@ +#!/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 +def schulze_winners(n, candidates): + 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] + + 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}') diff --git a/preference_counter.py b/preference_counter.py new file mode 100755 index 0000000000000000000000000000000000000000..3f0c48969525dce61f755cb91d140e476d159a02 --- /dev/null +++ b/preference_counter.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +import argparse +import json +import numpy as np +import pygraphviz as pgv +from itertools import groupby + +def all_equal(iterable): + g = groupby(iterable) + return next(g, True) and not next(g, False) + +def is_blank(ballot, unranked_value): + for x in ballot: + if x != 0 and x != unranked_value: + return False + return True + +# ballot values should be consistent (unranked value should be the greatest) +# N[a,b] is the number of voters who prefer candidate a to candidate b +def pairwise_preferences(ballot, nb_candidates): + n = np.zeros((nb_candidates, nb_candidates), dtype=np.int8) + + for a in range(nb_candidates): + for b in range(a, nb_candidates): + if ballot[a] < ballot[b]: + n[a][b] = 1 + elif ballot[a] > ballot[b]: + n[b][a] = 1 + return n + +def pairwise_preferences_matrix(belenios_raw_data): + # traverse data to compute some stuff and check some assumptions + max_observed_value = -1 + nb_blank = 0 + min_nb_candidates = 1000 + max_nb_candidates = -1 + for ballot in belenios_raw_data: + max_observed_value = max(max_observed_value, max(ballot)) + nb_candidates = len(ballot) + min_nb_candidates = min(min_nb_candidates, nb_candidates) + max_nb_candidates = max(max_nb_candidates, nb_candidates) + assert(max_observed_value > -1) + assert(min_nb_candidates == max_nb_candidates) + nb_candidates = min_nb_candidates + unranked_value = max_observed_value + 1 + + # put a consistent value for unranked candidates + fix_unranked_value = lambda x: unranked_value if x == 0 else x + data = [ list(map(fix_unranked_value, ballot)) for ballot in belenios_raw_data ] + + # compute some stats + nb_voters = len(data) + nb_blank = sum([int(is_blank(ballot, unranked_value)) for ballot in data]) + + # compute N, the matrix of pairwise preferences + # N[a,b] is the number of voters who prefer candidate a to candidate b + n = np.zeros((nb_candidates, nb_candidates), dtype=np.int8) + for ballot in data: + n += pairwise_preferences(ballot, nb_candidates) + + return (n, nb_candidates, nb_voters, nb_blank) + +# compute graph of pairwise preferences +# When N[i,j] > N[j,i], then there is a link from candidate i to candidate j of strength N[i,j] – N[j,i] +def pairwise_preferences_graph(n, candidates): + graph = pgv.AGraph(strict=True, directed=True) + + graph.add_nodes_from(candidates) + + assert n.shape[0] == n.shape[1] + assert n.shape[0] == len(candidates) + nb_candidates = len(candidates) + for a in range(nb_candidates): + for b in range(a, nb_candidates): + margin = n[a][b] - n[b][a] + if margin != 0: + (x, y) = (a, b) if margin > 0 else (b, a) + margin = abs(margin) + cx = candidates[x] + cy = candidates[y] + text_fr = f"{cx} domine {cy} par {margin} votants\n{n[x][y]} votants préfèrent {cx} à {cy}\n{n[y][x]} votants préfèrent {cy} à {cx}" + text_en = f"{cx} dominates {cy} by a margin of {margin} voters\n{n[x][y]} voters prefer {cx} over {cy}\n{n[y][x]} voters prefer {cy} over {cx}" + graph.add_edge(cx, cy, label=f"{margin}", tooltip=f"{text_fr}\n\n{text_en}") + + return graph + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Computes pairwise preferences matrix and graph from belenios ballot files') + parser.add_argument('-om', '--output-matrix-file', required=True) + parser.add_argument('-og', '--output-graph-file', required=True) + parser.add_argument('-c', '--candidates-file', help='candidates JSON file', type=argparse.FileType('r', encoding='utf-8'), required=True) + parser.add_argument('ballot_file', nargs='+', help="belenios ballot JSON file", type=argparse.FileType('r', encoding='utf-8')) + args = parser.parse_args() + + # compute the pairwise preference matrix from all belenios ballot files + ballot_data = [] + for file in args.ballot_file: + raw_data = json.load(file) + (n, nb_candidates, nb_voters, nb_blank) = pairwise_preferences_matrix(raw_data) + #print(n) + ballot_data.append((n, nb_candidates, nb_voters, nb_blank)) + + # make sure that the ballot fires are consistent with each other + nb_candidates = [b[1] for b in ballot_data] + assert(all_equal(nb_candidates)) + + # aggregate values + nb_voters = sum([b[2] for b in ballot_data]) + nb_blank = sum([b[3] for b in ballot_data]) + n = sum([b[0] for b in ballot_data]) + print(n) + + with open(args.output_matrix_file, 'w') as matrix_file: + json.dump(n.tolist(), matrix_file) + + candidates = json.load(args.candidates_file) + + g = pairwise_preferences_graph(n, candidates) + with open(args.output_graph_file, 'w') as graph_file: + graph_file.write(g.to_string()) + + #print(args) + #raw_data = [[0,0,0,0,0,0],[1,2,3,4,5,5],[1,2,3,0,0,0],[0,0,0,0,0,0],[1,2,3,4,5,5]] + + #(n, nb_candidates, nb_voters, nb_blank) = pairwise_preferences_matrix(raw_data) diff --git a/test_schulze_examples.py b/test_schulze_examples.py new file mode 100644 index 0000000000000000000000000000000000000000..1272428ce63e528471beacc15e2619d049193d79 --- /dev/null +++ b/test_schulze_examples.py @@ -0,0 +1,402 @@ +#!/usr/bin/env python3 +import numpy as np + +import preference_counter +import elect + +# all examples are from Section 3 of Markus Schulze's "The Schulze Method of Voting", +# version 13 (latest as I write these lines: 2023-07-14). https://arxiv.org/pdf/1804.02973.pdf + +def test_example1(): + candidates = ['a', 'b', 'c', 'd'] + ballots = [[ 1, 4, 2, 3]] * 8 + \ + [[ 2, 1, 4, 3]] * 2 + \ + [[ 4, 3, 1, 2]] * 4 + \ + [[ 3, 2, 4, 1]] * 4 + \ + [[ 4, 3, 2, 1]] * 3 + + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(ballots) + expected_n = np.array([[ 0, 8, 14, 10], + [13, 0, 6, 2], + [ 7, 15, 0, 12], + [11, 19, 9, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 8+2+4+4+3 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + assert elect.condorcet_winner(n, candidates) is None + assert len(elect.weak_condorcet_winners(n, candidates)) == 0 + assert elect.schulze_winners(n, candidates) == ['d'] + + +def test_example2(): + candidates = ['a', 'b', 'c', 'd'] + ballots = [[ 1, 4, 2, 3]] * 3 + \ + [[ 2, 1, 3, 4]] * 9 + \ + [[ 3, 4, 1, 2]] * 8 + \ + [[ 2, 3, 4, 1]] * 5 + \ + [[ 4, 2, 3, 1]] * 5 + + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(ballots) + expected_n = np.array([[ 0, 16, 17, 12], + [14, 0, 19, 9], + [13, 11, 0, 20], + [18, 21, 10, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 3+9+8+5+5 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + assert elect.condorcet_winner(n, candidates) is None + assert len(elect.weak_condorcet_winners(n, candidates)) == 0 + assert elect.schulze_winners(n, candidates) == ['c'] + + +def test_example3(): + candidates = ['a', 'b', 'c', 'd', 'e'] + ballots = [[ 1, 3, 2, 5, 4]] * 5 + \ + [[ 1, 5, 4, 2, 3]] * 5 + \ + [[ 4, 1, 5, 3, 2]] * 8 + \ + [[ 2, 3, 1, 5, 4]] * 3 + \ + [[ 2, 4, 1, 5, 3]] * 7 + \ + [[ 3, 2, 1, 4, 5]] * 2 + \ + [[ 5, 4, 2, 1, 3]] * 7 + \ + [[ 3, 2, 5, 4, 1]] * 8 + + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(ballots) + expected_n = np.array([[ 0, 20, 26, 30, 22], + [25, 0, 16, 33, 18], + [19, 29, 0, 17, 24], + [15, 12, 28, 0, 14], + [23, 27, 21, 31, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 5+5+8+3+7+2+7+8 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + assert elect.condorcet_winner(n, candidates) is None + assert len(elect.weak_condorcet_winners(n, candidates)) == 0 + assert elect.schulze_winners(n, candidates) == ['e'] + + +def test_example4(): + candidates = ['a', 'b', 'c', 'd'] + ballots = [[ 1, 2, 3, 4]] * 3 + \ + [[ 4, 2, 1, 3]] * 2 + \ + [[ 2, 3, 4, 1]] * 2 + \ + [[ 4, 2, 3, 1]] * 2 + + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(ballots) + expected_n = np.array([[ 0, 5, 5, 3], + [ 4, 0, 7, 5], + [ 4, 2, 0, 5], + [ 6, 4, 4, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 3+2+2+2 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + 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'] + + +def test_example5(): + candidates = ['a', 'b', 'c', 'd'] + ballots = [[ 1, 2, 3, 4]] * 12 + \ + [[ 1, 3, 4, 2]] * 6 + \ + [[ 4, 1, 2, 3]] * 9 + \ + [[ 3, 4, 1, 2]] * 15 + \ + [[ 3, 2, 4, 1]] * 21 + + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(ballots) + expected_n = np.array([[ 0, 33, 39, 18], + [30, 0, 48, 21], + [24, 15, 0, 36], + [45, 42, 27, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 12+6+9+15+21 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + assert elect.condorcet_winner(n, candidates) is None + assert len(elect.weak_condorcet_winners(n, candidates)) == 0 + assert elect.schulze_winners(n, candidates) == ['d'] + + +def test_example6(): + candidates = ['a', 'b', 'c', 'd'] + ballots = [[ 1, 4, 2, 3]] * 8 + \ + [[ 4, 1, 2, 3]] * 2 + \ + [[ 3, 1, 4, 2]] * 3 + \ + [[ 4, 2, 1, 3]] * 5 + \ + [[ 4, 3, 1, 2]] * 1 + \ + [[ 2, 3, 4, 1]] * 3 + \ + [[ 3, 2, 4, 1]] * 1 + + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(ballots) + expected_n = np.array([[ 0, 11, 15, 8], + [12, 0, 9, 10], + [ 8, 14, 0, 16], + [15, 13, 7, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 8+2+3+5+1+3+1 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + 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'] + +def test_example7(): + # situation 1 + candidates = ['a', 'b', 'c', 'd', 'e', 'f'] + ballots = [[ 1, 4, 5, 2, 3, 6]] * 3 + \ + [[ 6, 1, 4, 5, 3, 2]] * 3 + \ + [[ 2, 3, 1, 5, 6, 4]] * 4 + \ + [[ 6, 2, 3, 1, 4, 5]] * 1 + \ + [[ 4, 5, 6, 1, 2, 3]] * 4 + \ + [[ 6, 3, 2, 4, 1, 5]] * 2 + \ + [[ 2, 5, 3, 4, 6, 1]] * 2 + + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(ballots) + expected_n = np.array([[ 0, 13, 9, 9, 9, 7], + [ 6, 0, 11, 9, 10, 13], + [10, 8, 0, 11, 7, 10], + [10, 10, 8, 0, 14, 10], + [10, 9, 12, 5, 0, 10], + [12, 6, 9, 9, 9, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 3+3+4+1+4+2+2 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + assert elect.condorcet_winner(n, candidates) is None + assert len(elect.weak_condorcet_winners(n, candidates)) == 0 + assert elect.schulze_winners(n, candidates) == ['a'] + + # situation 2 + candidates = ['a', 'b', 'c', 'd', 'e', 'f'] + ballots2 = [[ 1, 5, 4, 6, 2, 3]] * 2 + new_ballots = ballots + ballots2 + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(new_ballots) + expected_n = np.array([[ 0, 15, 11, 11, 11, 9], + [ 6, 0, 11, 11, 10, 13], + [10, 10, 0, 13, 7, 10], + [10, 10, 8, 0, 14, 10], + [10, 11, 14, 7, 0, 12], + [12, 8, 11, 11, 9, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 3+3+4+1+4+2+2+2 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + assert elect.condorcet_winner(n, candidates) is None + assert len(elect.weak_condorcet_winners(n, candidates)) == 0 + assert elect.schulze_winners(n, candidates) == ['d'] + + +def test_example8(): + # situation 1 + candidates = ['a', 'b', 'c', 'd'] + ballots = [[ 1, 2, 4, 3]] * 3 + \ + [[ 1, 3, 4, 2]] * 5 + \ + [[ 1, 4, 3, 2]] * 1 + \ + [[ 2, 1, 4, 3]] * 2 + \ + [[ 4, 1, 3, 2]] * 2 + \ + [[ 2, 3, 1, 4]] * 4 + \ + [[ 3, 2, 1, 4]] * 6 + \ + [[ 4, 2, 3, 1]] * 2 + \ + [[ 3, 4, 2, 1]] * 5 + + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(ballots) + expected_n = np.array([[ 0, 18, 11, 21], + [12, 0, 14, 17], + [19, 16, 0, 10], + [ 9, 13, 20, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 3+5+1+2+2+4+6+2+5 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + assert elect.condorcet_winner(n, candidates) is None + assert len(elect.weak_condorcet_winners(n, candidates)) == 0 + assert elect.schulze_winners(n, candidates) == ['a'] + + # situation 2 + candidates = ['a', 'b', 'c', 'd', 'e'] + ballots = [[ 1, 2, 5, 3, 4]] * 3 + \ + [[ 1, 4, 5, 2, 3]] * 5 + \ + [[ 1, 5, 4, 2, 3]] * 1 + \ + [[ 2, 1, 5, 3, 4]] * 2 + \ + [[ 5, 1, 4, 2, 3]] * 2 + \ + [[ 2, 3, 1, 4, 5]] * 4 + \ + [[ 3, 2, 1, 4, 5]] * 6 + \ + [[ 5, 2, 4, 1, 3]] * 2 + \ + [[ 4, 5, 3, 1, 2]] * 5 + + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(ballots) + expected_n = np.array([[ 0, 18, 11, 21, 21], + [12, 0, 14, 17, 19], + [19, 16, 0, 10, 10], + [ 9, 13, 20, 0, 30], + [ 9, 11, 20, 0, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 3+5+1+2+2+4+6+2+5 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + assert elect.condorcet_winner(n, candidates) is None + assert len(elect.weak_condorcet_winners(n, candidates)) == 0 + assert elect.schulze_winners(n, candidates) == ['b'] + + +def test_example9(): + # situation 1 + candidates = ['a', 'b', 'c', 'd'] + ballots = [[ 1, 4, 2, 3]] * 5 + \ + [[ 4, 1, 2, 3]] * 2 + \ + [[ 3, 1, 4, 2]] * 4 + \ + [[ 3, 4, 1, 2]] * 2 + + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(ballots) + expected_n = np.array([[ 0, 7, 9, 5], + [ 6, 0, 6, 6], + [ 4, 7, 0, 9], + [ 8, 7, 4, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 5+2+4+2 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + assert elect.condorcet_winner(n, candidates) is None + assert len(elect.weak_condorcet_winners(n, candidates)) == 0 + assert elect.schulze_winners(n, candidates) == ['a'] + + # situation 2 + candidates = ['a', 'b', 'c', 'd', 'e'] + ballots = [[ 1, 5, 3, 4, 2]] * 5 + \ + [[ 4, 1, 2, 3, 5]] * 2 + \ + [[ 3, 1, 5, 2, 4]] * 4 + \ + [[ 3, 4, 1, 2, 5]] * 2 + + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(ballots) + expected_n = np.array([[ 0, 7, 9, 5, 13], + [ 6, 0, 6, 6, 8], + [ 4, 7, 0, 9, 4], + [ 8, 7, 4, 0, 8], + [ 0, 5, 9, 5, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 5+2+4+2 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + assert elect.condorcet_winner(n, candidates) is None + assert len(elect.weak_condorcet_winners(n, candidates)) == 0 + assert elect.schulze_winners(n, candidates) == ['b'] + +def test_example10(): + candidates = ['a', 'b', 'c', 'd'] + ballots = [[ 1, 2, 3, 4]] * 6 + \ + [[ 1, 1, 2, 2]] * 8 + \ + [[ 1, 2, 1, 2]] * 8 + \ + [[ 1, 3, 1, 2]] * 18 + \ + [[ 1, 2, 1, 1]] * 8 + \ + [[ 2, 1, 2, 2]] * 40 + \ + [[ 4, 2, 1, 3]] * 4 + \ + [[ 3, 4, 1, 2]] * 9 + \ + [[ 2, 2, 1, 1]] * 8 + \ + [[ 2, 3, 4, 1]] * 14 + \ + [[ 4, 2, 3, 1]] * 11 + \ + [[ 3, 4, 2, 1]] * 4 + + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(ballots) + expected_n = np.array([[ 0, 67, 28, 40], + [55, 0, 79, 58], + [36, 59, 0, 45], + [50, 72, 29, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 6+8+8+18+8+40+4+9+8+14+11+4 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + 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" + + +def test_example11(): + candidates = ['a', 'b', 'c', 'd', 'e'] + ballots = [[ 1, 3, 5, 2, 4]] * 9 + \ + [[ 3, 1, 2, 4, 5]] * 6 + \ + [[ 5, 1, 2, 3, 4]] * 5 + \ + [[ 5, 3, 1, 2, 4]] * 2 + \ + [[ 5, 4, 3, 1, 2]] * 6 + \ + [[ 2, 4, 3, 5, 1]] * 14 + \ + [[ 3, 4, 2, 5, 1]] * 2 + \ + [[ 3, 5, 4, 2, 1]] * 1 + + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(ballots) + expected_n = np.array([[ 0, 26, 24, 31, 15], + [19, 0, 20, 27, 22], + [21, 25, 0, 29, 13], + [14, 18, 16, 0, 28], + [30, 23, 32, 17, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 9+6+5+2+6+14+2+1 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + assert elect.condorcet_winner(n, candidates) is None + assert len(elect.weak_condorcet_winners(n, candidates)) == 0 + assert elect.schulze_winners(n, candidates) == ['b'] + + +def test_example12(): + candidates = ['a', 'b', 'c', 'd', 'e'] + ballots = [[ 1, 3, 5, 2, 4]] * 9 + \ + [[ 2, 1, 3, 5, 4]] * 1 + \ + [[ 3, 2, 1, 4, 5]] * 6 + \ + [[ 5, 3, 1, 2, 4]] * 2 + \ + [[ 4, 5, 1, 2, 3]] * 5 + \ + [[ 4, 5, 3, 1, 2]] * 6 + \ + [[ 3, 2, 4, 5, 1]] * 14 + \ + [[ 4, 2, 3, 5, 1]] * 2 + + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(ballots) + expected_n = np.array([[ 0, 20, 24, 32, 16], + [25, 0, 26, 23, 18], + [21, 19, 0, 30, 14], + [13, 22, 15, 0, 28], + [29, 27, 31, 17, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 9+1+6+2+5+6+14+2 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + assert elect.condorcet_winner(n, candidates) is None + assert len(elect.weak_condorcet_winners(n, candidates)) == 0 + assert elect.schulze_winners(n, candidates) == ['e'] + + +def test_example13(): + candidates = ['a', 'b', 'c'] + ballots = [[ 1, 2, 3]] * 2 + \ + [[ 3, 1, 2]] * 2 + \ + [[ 2, 3, 1]] * 1 + + (n, nb_candidates, nb_voters, nb_blank) = preference_counter.pairwise_preferences_matrix(ballots) + expected_n = np.array([[0, 3, 2], + [2, 0, 4], + [3, 1, 0]], dtype=np.int8) + assert nb_candidates == len(candidates) + assert nb_voters == 2+2+1 + assert nb_blank == 0 + assert np.array_equal(n, expected_n) + + 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']