My favorite Condorcet method is “Ranked Pairs”, invented by Professor Nicolaus Tideman. It’s close to understandable
, although requires some elementary graph theory.
The idea: if candidate i beats candidate j on preferences, i should appear before j in a linear order. So the “beats” relation can be built up a pair at a time, and added to a topological sorter (our graphlib.TopologicalSorter is ideal for this).
The twists that make it work:
-
Pairs are added in decreasing order of winning margin. If. e.g., i is preferred to j on 40 ballots, and j preferred to i on 19 ballots, i’s winning margin is 40-19 = 21.
-
But if adding (i, j) to the topsort would create a cycle, ignore it! Move on to the next winning pair.
So there are no cycles to deal with after the fact, a (at least one) total topological order always exists, and the strongest preferences that can be accounted for without creating a cycle are favored.
In simulations it does very well, at least as well as Schulze and - yes - as STAR. It’s also designed to create a total order, not just a winner.
Leaving names out of it, here’s how it does on our ballots. Leaving aside that the election method in use affects how people vote (and especially so “strategic” voters), so there’s no way to know for sure how the election would have turned out had we used Ranked Pairs:
43 3 2 - added to topsort
42 3 5 - added to topsort
38 3 1 - added to topsort
33 3 0 - added to topsort
32 0 2 - added to topsort
24 4 1 - added to topsort
21 4 2 - added to topsort
20 0 5 - added to topsort
17 4 5 - added to topsort
17 3 4 XXX margin is the same as last - added to topsort
13 0 1 - added to topsort
8 5 2 - added to topsort
5 4 0 - added to topsort
3 1 5 - added to topsort
ready (3,)
ready (4,)
ready (0,)
ready (1,)
ready (5,)
ready (2,)
Suffice it to say that it found a total linear order (and a unique one - the sets of nodes ready to report were always singletons - no choices), and in the same order Bloc STAR found them.
It’s emphatically not true that the election method doesn’t matter. It matters a whole lot, at least as much as who can vote. The lesson you “should be” taking from all this is that the PSF people who have worked on this over the years (including me) have weeded out all but the best election methods known. And that’s a huge part of why we get such similar results from all. Although, ya, the power of the “heart poll” remains A Mystery
.
[EDIT: repaired error in calling reachable()]
Ranked Pairs code
print("Ranked pairs")
trips = []
for i, row in enumerate(pref):
for j, aij in enumerate(row):
diff = aij - pref[j][i]
if diff > 0:
trips.append((diff, i, j))
trips.sort(reverse=True)
def reachable(g, seen, start, target):
if start in seen:
return False
seen.add(start)
for succ in g[start]:
if succ == target or reachable(g, seen, succ, target):
return True
return False
from graphlib import TopologicalSorter
ts = TopologicalSorter()
for i in range(len(pref)):
ts.add(i)
g = defaultdict(set)
last = None
for t in trips:
print(*t, end=" ")
margin, i, j = t
if margin == last:
print(" XXX margin is the same as last", end=" ")
# TODO: it's conceivable that the order in which we
# add pairs with equal margins can affect when a
# cycle is detected, and so affect which relations
# are fed to the topsort. Think harder ;-)
last = margin
g[i].add(j)
if reachable(g, set(), i, i):
print("- forms a cycle! ignored")
g[i].remove(j)
else:
print("- added to topsort")
ts.add(j, i)
ts.prepare()
while ts:
r = ts.get_ready()
print("ready", r)
ts.done(*r)