# How Voting Systems Work 10, Exercise 3: a weighted scorecard, and how the choice depends on the weights. # Assumed model: uniform electorates; 1000 elections for each measure. All weights are invented club preferences. # Needs votelab.py in the same folder. import random from math import dist as distance from votelab import make_electorate, make_ballots, condorcet, METHODS, regret def bury(b, voter, c): new = {"n": b["n"], "rank": list(b["rank"]), "approve": list(b["approve"]), "score": list(b["score"])} new["rank"][voter] = [x for x in b["rank"][voter] if x != c] + [c] new["approve"][voter] = b["approve"][voter] - {c} new["score"][voter] = [0 if x == c else s for x, s in enumerate(b["score"][voter])] return new rng = random.Random(3) runs = 1000 agree = {m: 0 for m in METHODS}; reg = {m: 0.0 for m in METHODS} worked = {m: 0 for m in METHODS}; tried = {m: 0 for m in METHODS} has_cw = 0 for _ in range(runs): voters, cands = make_electorate(rng, "uniform") b = make_ballots(voters, cands) cw = condorcet(b); has_cw += cw is not None for name, method in METHODS.items(): w = method(b) reg[name] += regret(voters, cands, w) agree[name] += (cw is not None and w == cw) firsts = [sum(1 for r in b["rank"] if r[0] == c) for c in range(b["n"])] rival = max((c for c in range(b["n"]) if c != w), key=lambda c: (firsts[c], -c)) bloc = [i for i, r in enumerate(b["rank"]) if r[0] == rival] if not bloc: continue tried[name] += 1 fake = b for i in bloc: fake = bury(fake, i, w) new = method(fake) mean = lambda c: sum(distance(voters[i], cands[c]) for i in bloc) / len(bloc) worked[name] += (new != w and mean(new) < mean(w)) worst = max(reg.values()) measures = {m: (agree[m] / has_cw, 1 - reg[m] / runs / (worst / runs), 1 - worked[m] / tried[m]) for m in METHODS} print("method condorcet low-regret burying-proof") for m, v in measures.items(): print(f"{m:>9} {v[0]:9.2f} {v[1]:11.2f} {v[2]:13.2f}") for label, wts in {"fairness first": (0.6, 0.2, 0.2), "closeness first": (0.2, 0.6, 0.2), "honesty first": (0.2, 0.2, 0.6)}.items(): ranked = sorted(measures, key=lambda m: -sum(w * x for w, x in zip(wts, measures[m]))) print(f"{label:>16}: " + " > ".join(ranked)) # Output from a real run: # method condorcet low-regret burying-proof # plurality 0.57 0.00 1.00 # irv 0.78 0.58 0.97 # borda 0.81 0.82 0.57 # copeland 1.00 0.89 0.71 # approval 0.75 0.78 0.53 # score 0.82 0.92 0.52 # fairness first: copeland > score > irv > borda > approval > plurality # closeness first: copeland > score > borda > approval > irv > plurality # honesty first: irv > copeland > plurality > borda > score > approval