kopia lustrzana https://github.com/martinlackner/apportionment
fixed handling of ties in quota method: ties are now reported whenever they occur.
It is currently not possible to find out whether there is more than one Quota apportionment (also, ran black formatter)pull/7/head
rodzic
f447b5c9db
commit
0b5ea8669b
|
|
@ -13,7 +13,7 @@ seats = 20
|
||||||
methods = ["quota", "largest_remainder", "dhondt", "saintelague", "adams"]
|
methods = ["quota", "largest_remainder", "dhondt", "saintelague", "adams"]
|
||||||
|
|
||||||
|
|
||||||
iterator = combinations(range(1, maxvoters+1), parties)
|
iterator = combinations(range(1, maxvoters + 1), parties)
|
||||||
|
|
||||||
for iterations, votes in enumerate(iterator):
|
for iterations, votes in enumerate(iterator):
|
||||||
apportionments = set()
|
apportionments = set()
|
||||||
|
|
@ -21,8 +21,10 @@ for iterations, votes in enumerate(iterator):
|
||||||
for method in methods:
|
for method in methods:
|
||||||
try: # in case of ties an exception occurs because tiesallowed=False
|
try: # in case of ties an exception occurs because tiesallowed=False
|
||||||
apportionments.add(
|
apportionments.add(
|
||||||
tuple(app.compute(method, votes, seats,
|
tuple(
|
||||||
tiesallowed=False, verbose=False)))
|
app.compute(method, votes, seats, tiesallowed=False, verbose=False)
|
||||||
|
)
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -36,6 +38,6 @@ print("votes = {}".format(votes))
|
||||||
print("found in {} iterations\n\n".format(iterations))
|
print("found in {} iterations\n\n".format(iterations))
|
||||||
|
|
||||||
for method in methods:
|
for method in methods:
|
||||||
print("{:>20s}: {}".format(method,
|
print(
|
||||||
app.compute(method, votes, seats,
|
"{:>20s}: {}".format(method, app.compute(method, votes, seats, verbose=False))
|
||||||
verbose=False)))
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ with open("./nr_wahlen.txt", "r") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
year, partynames, votes, officialresult = eval(line)
|
year, partynames, votes, officialresult = eval(line)
|
||||||
print(year)
|
print(year)
|
||||||
result = app.compute("dhondt", votes,
|
result = app.compute(
|
||||||
183,
|
"dhondt", votes, 183, parties=partynames, threshold=0.04, verbose=True
|
||||||
parties=partynames,
|
)
|
||||||
threshold=.04,
|
|
||||||
verbose=True)
|
|
||||||
# actual results
|
# actual results
|
||||||
print("Identical with official result: "
|
print(
|
||||||
+ (str(tuple(result) == tuple(officialresult)))
|
"Identical with official result: "
|
||||||
+ "\n\n")
|
+ (str(tuple(result) == tuple(officialresult)))
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,19 @@ print("See https://www.knesset.gov.il/lexicon/eng/seats_eng.htm\n")
|
||||||
with open("knesset.txt", "r") as f:
|
with open("knesset.txt", "r") as f:
|
||||||
|
|
||||||
for line in f:
|
for line in f:
|
||||||
knesset_nr, partynames, votes, officialresult, threshold = \
|
knesset_nr, partynames, votes, officialresult, threshold = eval(line)
|
||||||
eval(line)
|
|
||||||
print("Knesset #" + str(knesset_nr) + ":")
|
print("Knesset #" + str(knesset_nr) + ":")
|
||||||
result = app.compute("dhondt", votes,
|
result = app.compute(
|
||||||
sum(officialresult),
|
"dhondt",
|
||||||
parties=partynames,
|
votes,
|
||||||
threshold=threshold,
|
sum(officialresult),
|
||||||
verbose=True)
|
parties=partynames,
|
||||||
|
threshold=threshold,
|
||||||
|
verbose=True,
|
||||||
|
)
|
||||||
# actual results
|
# actual results
|
||||||
print("Identical with official result: "
|
print(
|
||||||
+ (str(tuple(result) == tuple(officialresult)))
|
"Identical with official result: "
|
||||||
+ "\n\n")
|
+ (str(tuple(result) == tuple(officialresult)))
|
||||||
|
+ "\n\n"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,19 @@ import apportionment.methods as app
|
||||||
votes = [1, 3, 6, 7, 78]
|
votes = [1, 3, 6, 7, 78]
|
||||||
seats = 20
|
seats = 20
|
||||||
|
|
||||||
print("votes", "."*(25 - len("votes")), votes, "\n")
|
print("votes", "." * (25 - len("votes")), votes, "\n")
|
||||||
|
|
||||||
print(seats, "seats", "\n")
|
print(seats, "seats", "\n")
|
||||||
|
|
||||||
print("apportionment results:")
|
print("apportionment results:")
|
||||||
for method in ["quota", "largest_remainder", "dhondt",
|
for method in [
|
||||||
"saintelague", "huntington", "adams", "dean"]:
|
"quota",
|
||||||
|
"largest_remainder",
|
||||||
|
"dhondt",
|
||||||
|
"saintelague",
|
||||||
|
"huntington",
|
||||||
|
"adams",
|
||||||
|
"dean",
|
||||||
|
]:
|
||||||
result = app.compute(method, votes, seats, verbose=False)
|
result = app.compute(method, votes, seats, verbose=False)
|
||||||
print(method, "."*(25 - len(method)), result)
|
print(method, "." * (25 - len(method)), result)
|
||||||
|
|
|
||||||
|
|
@ -7,32 +7,55 @@ import math
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
|
|
||||||
|
|
||||||
METHODS = ["quota", "largest_remainder", "dhondt", "saintelague",
|
METHODS = [
|
||||||
"modified_saintelague", "huntington", "adams", "dean"]
|
"quota",
|
||||||
|
"largest_remainder",
|
||||||
|
"dhondt",
|
||||||
|
"saintelague",
|
||||||
|
"modified_saintelague",
|
||||||
|
"huntington",
|
||||||
|
"adams",
|
||||||
|
"dean",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class TiesException(Exception):
|
class TiesException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def compute(method, votes, seats, parties=string.ascii_letters,
|
def compute(
|
||||||
threshold=None, tiesallowed=True, verbose=True):
|
method,
|
||||||
|
votes,
|
||||||
|
seats,
|
||||||
|
parties=string.ascii_letters,
|
||||||
|
threshold=None,
|
||||||
|
tiesallowed=True,
|
||||||
|
verbose=True,
|
||||||
|
):
|
||||||
filtered_votes = apply_threshold(votes, threshold)
|
filtered_votes = apply_threshold(votes, threshold)
|
||||||
if method == "quota":
|
if method == "quota":
|
||||||
return quota(filtered_votes, seats, parties, tiesallowed, verbose)
|
return quota(filtered_votes, seats, parties, tiesallowed, verbose)
|
||||||
elif method in ["lrm", "hamilton", "largest_remainder"]:
|
elif method in ["lrm", "hamilton", "largest_remainder"]:
|
||||||
return largest_remainder(filtered_votes, seats, parties,
|
return largest_remainder(filtered_votes, seats, parties, tiesallowed, verbose)
|
||||||
tiesallowed, verbose)
|
elif method in [
|
||||||
elif method in ["dhondt", "jefferson", "saintelague", "webster",
|
"dhondt",
|
||||||
"modified_saintelague",
|
"jefferson",
|
||||||
"huntington", "hill", "adams", "dean",
|
"saintelague",
|
||||||
"smallestdivisor", "harmonicmean", "equalproportions",
|
"webster",
|
||||||
"majorfractions", "greatestdivisors"]:
|
"modified_saintelague",
|
||||||
return divisor(filtered_votes, seats, method, parties,
|
"huntington",
|
||||||
tiesallowed, verbose)
|
"hill",
|
||||||
|
"adams",
|
||||||
|
"dean",
|
||||||
|
"smallestdivisor",
|
||||||
|
"harmonicmean",
|
||||||
|
"equalproportions",
|
||||||
|
"majorfractions",
|
||||||
|
"greatestdivisors",
|
||||||
|
]:
|
||||||
|
return divisor(filtered_votes, seats, method, parties, tiesallowed, verbose)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("apportionment method " + method +
|
raise NotImplementedError("apportionment method " + method + " not known")
|
||||||
" not known")
|
|
||||||
|
|
||||||
|
|
||||||
def apply_threshold(votes, threshold):
|
def apply_threshold(votes, threshold):
|
||||||
|
|
@ -52,14 +75,14 @@ def apply_threshold(votes, threshold):
|
||||||
|
|
||||||
|
|
||||||
def __print_results(representatives, parties):
|
def __print_results(representatives, parties):
|
||||||
|
print("apportionment:")
|
||||||
for i in range(len(representatives)):
|
for i in range(len(representatives)):
|
||||||
print(" "+str(parties[i])+": "+str(representatives[i]))
|
print(" " + str(parties[i]) + ": " + str(representatives[i]))
|
||||||
|
|
||||||
|
|
||||||
# verifies whether a given assignment of representatives
|
# verifies whether a given assignment of representatives
|
||||||
# is within quota
|
# is within quota
|
||||||
def within_quota(votes, representatives, parties=string.ascii_letters,
|
def within_quota(votes, representatives, parties=string.ascii_letters, verbose=True):
|
||||||
verbose=True):
|
|
||||||
n = sum(votes)
|
n = sum(votes)
|
||||||
seats = sum(representatives)
|
seats = sum(representatives)
|
||||||
within = True
|
within = True
|
||||||
|
|
@ -67,36 +90,51 @@ def within_quota(votes, representatives, parties=string.ascii_letters,
|
||||||
upperquota = int(math.ceil(float(votes[i]) * seats / n))
|
upperquota = int(math.ceil(float(votes[i]) * seats / n))
|
||||||
if representatives[i] > upperquota:
|
if representatives[i] > upperquota:
|
||||||
if verbose:
|
if verbose:
|
||||||
print("upper quota of party", parties[i],
|
print(
|
||||||
"violated: quota is", float(votes[i]) * seats / n,
|
"upper quota of party",
|
||||||
"but has", representatives[i], "representatives")
|
parties[i],
|
||||||
|
"violated: quota is",
|
||||||
|
float(votes[i]) * seats / n,
|
||||||
|
"but has",
|
||||||
|
representatives[i],
|
||||||
|
"representatives",
|
||||||
|
)
|
||||||
within = False
|
within = False
|
||||||
lowerquota = int(math.floor(float(votes[i]) * seats / n))
|
lowerquota = int(math.floor(float(votes[i]) * seats / n))
|
||||||
if representatives[i] < lowerquota:
|
if representatives[i] < lowerquota:
|
||||||
if verbose:
|
if verbose:
|
||||||
print("lower quota of party", parties[i],
|
print(
|
||||||
"violated: quota is", float(votes[i]) * seats / n,
|
"lower quota of party",
|
||||||
"but has only", representatives[i], "representatives")
|
parties[i],
|
||||||
|
"violated: quota is",
|
||||||
|
float(votes[i]) * seats / n,
|
||||||
|
"but has only",
|
||||||
|
representatives[i],
|
||||||
|
"representatives",
|
||||||
|
)
|
||||||
within = False
|
within = False
|
||||||
return within
|
return within
|
||||||
|
|
||||||
|
|
||||||
# Largest remainder method (Hamilton method)
|
# Largest remainder method (Hamilton method)
|
||||||
def largest_remainder(votes, seats, parties=string.ascii_letters,
|
def largest_remainder(
|
||||||
tiesallowed=True, verbose=True):
|
votes, seats, parties=string.ascii_letters, tiesallowed=True, verbose=True
|
||||||
|
):
|
||||||
if verbose:
|
if verbose:
|
||||||
print("\nLargest remainder method with Hare quota (Hamilton)")
|
print("\nLargest remainder method with Hare quota (Hamilton)")
|
||||||
q = Fraction(sum(votes), seats)
|
q = Fraction(sum(votes), seats)
|
||||||
quotas = [Fraction(p, q) for p in votes]
|
quotas = [Fraction(p, q) for p in votes]
|
||||||
representatives = [int(qu.numerator)//int(qu.denominator) for qu in quotas]
|
representatives = [int(qu.numerator) // int(qu.denominator) for qu in quotas]
|
||||||
|
|
||||||
ties = False
|
ties = False
|
||||||
if sum(representatives) < seats:
|
if sum(representatives) < seats:
|
||||||
remainders = [a-b for a, b in zip(quotas, representatives)]
|
remainders = [a - b for a, b in zip(quotas, representatives)]
|
||||||
cutoff = sorted(remainders, reverse=True)[seats-sum(representatives)-1]
|
cutoff = sorted(remainders, reverse=True)[seats - sum(representatives) - 1]
|
||||||
tiebreaking_message = (" tiebreaking in order of: " +
|
tiebreaking_message = (
|
||||||
str(parties[:len(votes)]) +
|
" tiebreaking in order of: "
|
||||||
"\n ties broken in favor of: ")
|
+ str(parties[: len(votes)])
|
||||||
|
+ "\n ties broken in favor of: "
|
||||||
|
)
|
||||||
for i in range(len(votes)):
|
for i in range(len(votes)):
|
||||||
if sum(representatives) == seats and remainders[i] >= cutoff:
|
if sum(representatives) == seats and remainders[i] >= cutoff:
|
||||||
if not ties:
|
if not ties:
|
||||||
|
|
@ -122,49 +160,54 @@ def largest_remainder(votes, seats, parties=string.ascii_letters,
|
||||||
|
|
||||||
|
|
||||||
# Divisor methods
|
# Divisor methods
|
||||||
def divisor(votes, seats, method, parties=string.ascii_letters,
|
def divisor(
|
||||||
tiesallowed=True, verbose=True):
|
votes, seats, method, parties=string.ascii_letters, tiesallowed=True, verbose=True
|
||||||
|
):
|
||||||
representatives = [0] * len(votes)
|
representatives = [0] * len(votes)
|
||||||
if method in ["dhondt", "jefferson", "greatestdivisors"]:
|
if method in ["dhondt", "jefferson", "greatestdivisors"]:
|
||||||
if verbose:
|
if verbose:
|
||||||
print("\nD'Hondt (Jefferson) method")
|
print("\nD'Hondt (Jefferson) method")
|
||||||
divisors = [i+1 for i in range(seats)]
|
divisors = [i + 1 for i in range(seats)]
|
||||||
elif method in ["saintelague", "webster", "majorfractions"]:
|
elif method in ["saintelague", "webster", "majorfractions"]:
|
||||||
if verbose:
|
if verbose:
|
||||||
print("\nSainte Lague (Webster) method")
|
print("\nSainte Lague (Webster) method")
|
||||||
divisors = [2*i+1 for i in range(seats)]
|
divisors = [2 * i + 1 for i in range(seats)]
|
||||||
elif method in ["modified_saintelague"]:
|
elif method in ["modified_saintelague"]:
|
||||||
if verbose:
|
if verbose:
|
||||||
print("\nModified Sainte Lague (Webster) method")
|
print("\nModified Sainte Lague (Webster) method")
|
||||||
divisors = [1.4] + [2*i+1 for i in range(1, seats)]
|
divisors = [1.4] + [2 * i + 1 for i in range(1, seats)]
|
||||||
elif method in ["huntington", "hill", "equalproportions"]:
|
elif method in ["huntington", "hill", "equalproportions"]:
|
||||||
if verbose:
|
if verbose:
|
||||||
print("\nHuntington-Hill method")
|
print("\nHuntington-Hill method")
|
||||||
if seats < len(votes):
|
if seats < len(votes):
|
||||||
representatives = __divzero_fewerseatsthanparties(
|
representatives = __divzero_fewerseatsthanparties(
|
||||||
votes, seats, parties, tiesallowed, verbose)
|
votes, seats, parties, tiesallowed, verbose
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
representatives = [1 if p > 0 else 0 for p in votes]
|
representatives = [1 if p > 0 else 0 for p in votes]
|
||||||
divisors = [math.sqrt((i+1)*(i+2)) for i in range(seats)]
|
divisors = [math.sqrt((i + 1) * (i + 2)) for i in range(seats)]
|
||||||
elif method in ["adams", "smallestdivisor"]:
|
elif method in ["adams", "smallestdivisor"]:
|
||||||
if verbose:
|
if verbose:
|
||||||
print("\nAdams method")
|
print("\nAdams method")
|
||||||
if seats < len(votes):
|
if seats < len(votes):
|
||||||
representatives = __divzero_fewerseatsthanparties(
|
representatives = __divzero_fewerseatsthanparties(
|
||||||
votes, seats, parties, tiesallowed, verbose)
|
votes, seats, parties, tiesallowed, verbose
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
representatives = [1 if p > 0 else 0 for p in votes]
|
representatives = [1 if p > 0 else 0 for p in votes]
|
||||||
divisors = [i+1 for i in range(seats)]
|
divisors = [i + 1 for i in range(seats)]
|
||||||
elif method in ["dean", "harmonicmean"]:
|
elif method in ["dean", "harmonicmean"]:
|
||||||
if verbose:
|
if verbose:
|
||||||
print("\nDean method")
|
print("\nDean method")
|
||||||
if seats < len(votes):
|
if seats < len(votes):
|
||||||
representatives = __divzero_fewerseatsthanparties(
|
representatives = __divzero_fewerseatsthanparties(
|
||||||
votes, seats, parties, tiesallowed, verbose)
|
votes, seats, parties, tiesallowed, verbose
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
representatives = [1 if p > 0 else 0 for p in votes]
|
representatives = [1 if p > 0 else 0 for p in votes]
|
||||||
divisors = [Fraction(2 * (i+1) * (i+2), 2 * (i+1) + 1)
|
divisors = [
|
||||||
for i in range(seats)]
|
Fraction(2 * (i + 1) * (i + 2), 2 * (i + 1) + 1) for i in range(seats)
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("divisor method " + method + " not known")
|
raise NotImplementedError("divisor method " + method + " not known")
|
||||||
|
|
||||||
|
|
@ -185,9 +228,11 @@ def divisor(votes, seats, method, parties=string.ascii_letters,
|
||||||
ties = False
|
ties = False
|
||||||
# dealing with ties
|
# dealing with ties
|
||||||
if seats > sum(representatives):
|
if seats > sum(representatives):
|
||||||
tiebreaking_message = (" tiebreaking in order of: " +
|
tiebreaking_message = (
|
||||||
str(parties[:len(votes)]) +
|
" tiebreaking in order of: "
|
||||||
"\n ties broken in favor of: ")
|
+ str(parties[: len(votes)])
|
||||||
|
+ "\n ties broken in favor of: "
|
||||||
|
)
|
||||||
for i in range(len(votes)):
|
for i in range(len(votes)):
|
||||||
if sum(representatives) == seats and minweight in weights[i]:
|
if sum(representatives) == seats and minweight in weights[i]:
|
||||||
if not ties:
|
if not ties:
|
||||||
|
|
@ -213,15 +258,17 @@ def divisor(votes, seats, method, parties=string.ascii_letters,
|
||||||
|
|
||||||
|
|
||||||
# required for methods with 0 divisors (Adams, Huntington-Hill)
|
# required for methods with 0 divisors (Adams, Huntington-Hill)
|
||||||
def __divzero_fewerseatsthanparties(votes, seats, parties,
|
def __divzero_fewerseatsthanparties(votes, seats, parties, tiesallowed, verbose):
|
||||||
tiesallowed, verbose):
|
|
||||||
representatives = [0] * len(votes)
|
representatives = [0] * len(votes)
|
||||||
if verbose:
|
if verbose:
|
||||||
print(" fewer seats than parties; " + str(seats) +
|
print(
|
||||||
" strongest parties receive one seat")
|
" fewer seats than parties; "
|
||||||
|
+ str(seats)
|
||||||
|
+ " strongest parties receive one seat"
|
||||||
|
)
|
||||||
tiebreaking_message = " ties broken in favor of: "
|
tiebreaking_message = " ties broken in favor of: "
|
||||||
ties = False
|
ties = False
|
||||||
mincount = sorted(votes, reverse=True)[seats-1]
|
mincount = sorted(votes, reverse=True)[seats - 1]
|
||||||
for i in range(len(votes)):
|
for i in range(len(votes)):
|
||||||
if sum(representatives) < seats and votes[i] >= mincount:
|
if sum(representatives) < seats and votes[i] >= mincount:
|
||||||
if votes[i] == mincount:
|
if votes[i] == mincount:
|
||||||
|
|
@ -240,54 +287,52 @@ def __divzero_fewerseatsthanparties(votes, seats, parties,
|
||||||
return representatives
|
return representatives
|
||||||
|
|
||||||
|
|
||||||
# The quota method
|
def quota(votes, seats, parties=string.ascii_letters, tiesallowed=True, verbose=True):
|
||||||
# ( see Balinski, M. L., & Young, H. P. (1975).
|
"""The quota method
|
||||||
# The quota method of apportionment.
|
see Balinski, M. L., & Young, H. P. (1975).
|
||||||
# The American Mathematical Monthly, 82(7), 701-730.)
|
The quota method of apportionment.
|
||||||
def quota(votes, seats, parties=string.ascii_letters,
|
The American Mathematical Monthly, 82(7), 701-730.)
|
||||||
tiesallowed=True, verbose=True):
|
|
||||||
|
Warning: tiesallowed is not supported here (difficult to implement)
|
||||||
|
"""
|
||||||
|
if not tiesallowed:
|
||||||
|
raise NotImplementedError(
|
||||||
|
"parameter tiesallowed not supported for Quota method"
|
||||||
|
)
|
||||||
if verbose:
|
if verbose:
|
||||||
print("\nQuota method")
|
print("\nQuota method")
|
||||||
representatives = [0] * len(votes)
|
representatives = [0] * len(votes)
|
||||||
while sum(representatives) < seats:
|
while sum(representatives) < seats:
|
||||||
quotas = [Fraction(votes[i], representatives[i]+1)
|
quotas = [Fraction(votes[i], representatives[i] + 1) for i in range(len(votes))]
|
||||||
for i in range(len(votes))]
|
|
||||||
# check if upper quota is violated
|
# check if upper quota is violated
|
||||||
for i in range(len(votes)):
|
for i in range(len(votes)):
|
||||||
upperquota = int(math.ceil(float(votes[i]) *
|
upperquota = int(
|
||||||
(sum(representatives)+1)
|
math.ceil(float(votes[i]) * (sum(representatives) + 1) / sum(votes))
|
||||||
/ sum(votes)))
|
)
|
||||||
if representatives[i] >= upperquota:
|
if representatives[i] >= upperquota:
|
||||||
quotas[i] = 0
|
quotas[i] = 0
|
||||||
|
|
||||||
maxquotas = [i for i in range(len(votes))
|
maxquotas = [i for i in range(len(votes)) if quotas[i] == max(quotas)]
|
||||||
if quotas[i] == max(quotas)]
|
|
||||||
|
|
||||||
nextrep = maxquotas[0]
|
nextrep = maxquotas[0]
|
||||||
|
|
||||||
|
# print tiebreaking information
|
||||||
|
if verbose and len(maxquotas) > 1:
|
||||||
|
print(
|
||||||
|
"tiebreaking necessary in round "
|
||||||
|
+ str(sum(representatives) + 1)
|
||||||
|
+ ":"
|
||||||
|
+ " tiebreaking in order of: "
|
||||||
|
+ str(parties[: len(votes)])
|
||||||
|
+ "\n ties broken in favor of: "
|
||||||
|
+ str(parties[nextrep])
|
||||||
|
+ "\n to the disadvantage of: "
|
||||||
|
+ ", ".join(parties[i] for i in maxquotas[1:])
|
||||||
|
)
|
||||||
|
|
||||||
representatives[nextrep] += 1
|
representatives[nextrep] += 1
|
||||||
|
|
||||||
if len(maxquotas) > 1 and not tiesallowed:
|
|
||||||
raise TiesException("Tie occurred")
|
|
||||||
|
|
||||||
# print tiebreaking information
|
|
||||||
if verbose and len(maxquotas) > 1:
|
|
||||||
quotas_now = [Fraction(votes[i], representatives[i]+1)
|
|
||||||
for i in range(len(votes))]
|
|
||||||
tiebreaking_message = (" tiebreaking in order of: " +
|
|
||||||
str(parties[:len(votes)]) +
|
|
||||||
"\n ties broken in favor of: ")
|
|
||||||
ties_favor = [i for i in range(len(votes))
|
|
||||||
if quotas_now[i] == quotas_now[nextrep]]
|
|
||||||
for i in ties_favor:
|
|
||||||
tiebreaking_message += str(parties[i]) + ", "
|
|
||||||
tiebreaking_message = (tiebreaking_message[:-2] +
|
|
||||||
"\n to the disadvantage of: ")
|
|
||||||
for i in maxquotas[1:]:
|
|
||||||
tiebreaking_message += str(parties[i]) + ", "
|
|
||||||
print(tiebreaking_message[:-2])
|
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
__print_results(representatives, parties)
|
__print_results(representatives, parties)
|
||||||
|
|
||||||
return representatives
|
return representatives
|
||||||
|
|
||||||
|
|
@ -6,22 +6,33 @@ import apportionment.methods as app
|
||||||
|
|
||||||
|
|
||||||
class TestApprovalMultiwinner(unittest.TestCase):
|
class TestApprovalMultiwinner(unittest.TestCase):
|
||||||
|
|
||||||
def test_all_implemented(self):
|
def test_all_implemented(self):
|
||||||
ALLMETHODSSTRINGS = ["quota", "lrm", "hamilton", "largest_remainder",
|
ALLMETHODSSTRINGS = [
|
||||||
"dhondt", "jefferson", "saintelague", "webster",
|
"quota",
|
||||||
"huntington", "hill", "adams", "dean",
|
"lrm",
|
||||||
"smallestdivisor", "harmonicmean",
|
"hamilton",
|
||||||
"equalproportions", "majorfractions",
|
"largest_remainder",
|
||||||
"greatestdivisors", "modified_saintelague"]
|
"dhondt",
|
||||||
|
"jefferson",
|
||||||
|
"saintelague",
|
||||||
|
"webster",
|
||||||
|
"huntington",
|
||||||
|
"hill",
|
||||||
|
"adams",
|
||||||
|
"dean",
|
||||||
|
"smallestdivisor",
|
||||||
|
"harmonicmean",
|
||||||
|
"equalproportions",
|
||||||
|
"majorfractions",
|
||||||
|
"greatestdivisors",
|
||||||
|
"modified_saintelague",
|
||||||
|
]
|
||||||
|
|
||||||
votes = [1]
|
votes = [1]
|
||||||
seats = 1
|
seats = 1
|
||||||
for method in ALLMETHODSSTRINGS:
|
for method in ALLMETHODSSTRINGS:
|
||||||
result = app.compute(method, votes,
|
result = app.compute(method, votes, seats, verbose=False)
|
||||||
seats, verbose=False)
|
self.assertEqual(result, [1], msg=method + " does not exist")
|
||||||
self.assertEqual(result, [1],
|
|
||||||
msg=method + " does not exist")
|
|
||||||
|
|
||||||
def test_weak_proportionality(self):
|
def test_weak_proportionality(self):
|
||||||
self.longMessage = True
|
self.longMessage = True
|
||||||
|
|
@ -29,10 +40,8 @@ class TestApprovalMultiwinner(unittest.TestCase):
|
||||||
votes = [14, 28, 7, 35]
|
votes = [14, 28, 7, 35]
|
||||||
seats = 12
|
seats = 12
|
||||||
for method in app.METHODS:
|
for method in app.METHODS:
|
||||||
result = app.compute(method, votes,
|
result = app.compute(method, votes, seats, verbose=False)
|
||||||
seats, verbose=False)
|
self.assertEqual(result, [2, 4, 1, 5], msg=method + " failed")
|
||||||
self.assertEqual(result, [2, 4, 1, 5],
|
|
||||||
msg=method + " failed")
|
|
||||||
|
|
||||||
def test_zero_parties(self):
|
def test_zero_parties(self):
|
||||||
self.longMessage = True
|
self.longMessage = True
|
||||||
|
|
@ -40,10 +49,8 @@ class TestApprovalMultiwinner(unittest.TestCase):
|
||||||
votes = [0, 14, 28, 0, 0]
|
votes = [0, 14, 28, 0, 0]
|
||||||
seats = 6
|
seats = 6
|
||||||
for method in app.METHODS:
|
for method in app.METHODS:
|
||||||
result = app.compute(method, votes,
|
result = app.compute(method, votes, seats, verbose=False)
|
||||||
seats, verbose=False)
|
self.assertEqual(result, [0, 2, 4, 0, 0], msg=method + " failed")
|
||||||
self.assertEqual(result, [0, 2, 4, 0, 0],
|
|
||||||
msg=method + " failed")
|
|
||||||
|
|
||||||
def test_fewerseatsthanparties(self):
|
def test_fewerseatsthanparties(self):
|
||||||
self.longMessage = True
|
self.longMessage = True
|
||||||
|
|
@ -51,10 +58,8 @@ class TestApprovalMultiwinner(unittest.TestCase):
|
||||||
votes = [10, 9, 8, 8, 11, 12]
|
votes = [10, 9, 8, 8, 11, 12]
|
||||||
seats = 3
|
seats = 3
|
||||||
for method in app.METHODS:
|
for method in app.METHODS:
|
||||||
result = app.compute(method, votes,
|
result = app.compute(method, votes, seats, verbose=False)
|
||||||
seats, verbose=False)
|
self.assertEqual(result, [1, 0, 0, 0, 1, 1], msg=method + " failed")
|
||||||
self.assertEqual(result, [1, 0, 0, 0, 1, 1],
|
|
||||||
msg=method + " failed")
|
|
||||||
|
|
||||||
# examples taken from
|
# examples taken from
|
||||||
# Balinski, M. L., & Young, H. P. (1975).
|
# Balinski, M. L., & Young, H. P. (1975).
|
||||||
|
|
@ -63,44 +68,42 @@ class TestApprovalMultiwinner(unittest.TestCase):
|
||||||
def test_balinski_young_example1(self):
|
def test_balinski_young_example1(self):
|
||||||
self.longMessage = True
|
self.longMessage = True
|
||||||
|
|
||||||
RESULTS = {"quota": [52, 44, 2, 1, 1],
|
RESULTS = {
|
||||||
"largest_remainder": [51, 44, 2, 2, 1],
|
"quota": [52, 44, 2, 1, 1],
|
||||||
"dhondt": [52, 45, 1, 1, 1],
|
"largest_remainder": [51, 44, 2, 2, 1],
|
||||||
"saintelague": [51, 43, 2, 2, 2],
|
"dhondt": [52, 45, 1, 1, 1],
|
||||||
"modified_saintelague": [51, 43, 2, 2, 2],
|
"saintelague": [51, 43, 2, 2, 2],
|
||||||
"huntington": [51, 43, 2, 2, 2],
|
"modified_saintelague": [51, 43, 2, 2, 2],
|
||||||
"adams": [51, 43, 2, 2, 2],
|
"huntington": [51, 43, 2, 2, 2],
|
||||||
"dean": [51, 43, 2, 2, 2]
|
"adams": [51, 43, 2, 2, 2],
|
||||||
}
|
"dean": [51, 43, 2, 2, 2],
|
||||||
|
}
|
||||||
|
|
||||||
votes = [5117, 4400, 162, 161, 160]
|
votes = [5117, 4400, 162, 161, 160]
|
||||||
seats = 100
|
seats = 100
|
||||||
for method in RESULTS.keys():
|
for method in RESULTS.keys():
|
||||||
result = app.compute(method, votes,
|
result = app.compute(method, votes, seats, verbose=False)
|
||||||
seats, verbose=False)
|
self.assertEqual(result, RESULTS[method], msg=method + " failed")
|
||||||
self.assertEqual(result, RESULTS[method],
|
|
||||||
msg=method + " failed")
|
|
||||||
|
|
||||||
def test_balinski_young_example2(self):
|
def test_balinski_young_example2(self):
|
||||||
self.longMessage = True
|
self.longMessage = True
|
||||||
|
|
||||||
RESULTS = {"quota": [10, 7, 5, 3, 1],
|
RESULTS = {
|
||||||
"largest_remainder": [9, 7, 5, 4, 1],
|
"quota": [10, 7, 5, 3, 1],
|
||||||
"dhondt": [10, 7, 5, 3, 1],
|
"largest_remainder": [9, 7, 5, 4, 1],
|
||||||
"saintelague": [9, 8, 5, 3, 1],
|
"dhondt": [10, 7, 5, 3, 1],
|
||||||
"modified_saintelague": [9, 8, 5, 3, 1],
|
"saintelague": [9, 8, 5, 3, 1],
|
||||||
"huntington": [9, 7, 6, 3, 1],
|
"modified_saintelague": [9, 8, 5, 3, 1],
|
||||||
"adams": [9, 7, 5, 3, 2],
|
"huntington": [9, 7, 6, 3, 1],
|
||||||
"dean": [9, 7, 5, 4, 1]
|
"adams": [9, 7, 5, 3, 2],
|
||||||
}
|
"dean": [9, 7, 5, 4, 1],
|
||||||
|
}
|
||||||
|
|
||||||
votes = [9061, 7179, 5259, 3319, 1182]
|
votes = [9061, 7179, 5259, 3319, 1182]
|
||||||
seats = 26
|
seats = 26
|
||||||
for method in RESULTS.keys():
|
for method in RESULTS.keys():
|
||||||
result = app.compute(method, votes,
|
result = app.compute(method, votes, seats, verbose=False)
|
||||||
seats, verbose=False)
|
self.assertEqual(result, RESULTS[method], msg=method + " failed")
|
||||||
self.assertEqual(result, RESULTS[method],
|
|
||||||
msg=method + " failed")
|
|
||||||
|
|
||||||
def test_tiebreaking(self):
|
def test_tiebreaking(self):
|
||||||
self.longMessage = True
|
self.longMessage = True
|
||||||
|
|
@ -108,78 +111,80 @@ class TestApprovalMultiwinner(unittest.TestCase):
|
||||||
votes = [2, 1, 1, 2, 2]
|
votes = [2, 1, 1, 2, 2]
|
||||||
seats = 2
|
seats = 2
|
||||||
for method in app.METHODS:
|
for method in app.METHODS:
|
||||||
result = app.compute(method, votes,
|
result = app.compute(method, votes, seats, verbose=False)
|
||||||
seats, verbose=False)
|
self.assertEqual(result, [1, 0, 0, 1, 0], msg=method + " failed")
|
||||||
self.assertEqual(result, [1, 0, 0, 1, 0],
|
|
||||||
msg=method + " failed")
|
|
||||||
|
|
||||||
def test_within_quota(self):
|
def test_within_quota(self):
|
||||||
votes = [5117, 4400, 162, 161, 160]
|
votes = [5117, 4400, 162, 161, 160]
|
||||||
representatives = [51, 44, 2, 2, 1]
|
representatives = [51, 44, 2, 2, 1]
|
||||||
self.assertTrue(app.within_quota(votes, representatives,
|
self.assertTrue(app.within_quota(votes, representatives, verbose=False))
|
||||||
verbose=False))
|
|
||||||
representatives = [52, 45, 1, 1, 1]
|
representatives = [52, 45, 1, 1, 1]
|
||||||
self.assertFalse(app.within_quota(votes, representatives,
|
self.assertFalse(app.within_quota(votes, representatives, verbose=False))
|
||||||
verbose=False))
|
|
||||||
representatives = [52, 43, 2, 1, 2]
|
representatives = [52, 43, 2, 1, 2]
|
||||||
self.assertFalse(app.within_quota(votes, representatives,
|
self.assertFalse(app.within_quota(votes, representatives, verbose=False))
|
||||||
verbose=False))
|
|
||||||
|
|
||||||
def test_threshold(self):
|
def test_threshold(self):
|
||||||
votes = [41, 56, 3]
|
votes = [41, 56, 3]
|
||||||
seats = 60
|
seats = 60
|
||||||
threshold = 0.03
|
threshold = 0.03
|
||||||
filtered_votes = app.apply_threshold(votes, threshold)
|
filtered_votes = app.apply_threshold(votes, threshold)
|
||||||
self.assertEqual(filtered_votes, [41, 56, 3],
|
self.assertEqual(filtered_votes, [41, 56, 3], "Threshold cut too much.")
|
||||||
"Threshold cut too much.")
|
|
||||||
threshold = 0.031
|
threshold = 0.031
|
||||||
filtered_votes = app.apply_threshold(votes, threshold)
|
filtered_votes = app.apply_threshold(votes, threshold)
|
||||||
self.assertEqual(filtered_votes, [41, 56, 0],
|
self.assertEqual(
|
||||||
"Threshold was not applied correctly.")
|
filtered_votes, [41, 56, 0], "Threshold was not applied correctly."
|
||||||
|
)
|
||||||
|
|
||||||
method = "dhondt"
|
method = "dhondt"
|
||||||
threshold = 0
|
threshold = 0
|
||||||
unfiltered_result = app.compute(method, votes, seats,
|
unfiltered_result = app.compute(
|
||||||
threshold=threshold,
|
method, votes, seats, threshold=threshold, verbose=False
|
||||||
verbose=False)
|
)
|
||||||
threshold = 0.04
|
threshold = 0.04
|
||||||
filtered_result = app.compute(method, votes, seats,
|
filtered_result = app.compute(
|
||||||
threshold=threshold,
|
method, votes, seats, threshold=threshold, verbose=False
|
||||||
verbose=False)
|
)
|
||||||
self.assertNotEqual(unfiltered_result, filtered_result,
|
self.assertNotEqual(
|
||||||
"Result did not change despite threshold")
|
unfiltered_result,
|
||||||
|
filtered_result,
|
||||||
|
"Result did not change despite threshold",
|
||||||
|
)
|
||||||
|
|
||||||
def test_saintelague_difference(self):
|
def test_saintelague_difference(self):
|
||||||
votes = [6, 1]
|
votes = [6, 1]
|
||||||
seats = 4
|
seats = 4
|
||||||
r1 = app.compute("saintelague", votes,
|
r1 = app.compute("saintelague", votes, seats, verbose=False) # [3, 1]
|
||||||
seats, verbose=False) # [3, 1]
|
r2 = app.compute("modified_saintelague", votes, seats, verbose=False) # [4, 0]
|
||||||
r2 = app.compute("modified_saintelague", votes,
|
self.assertNotEqual(
|
||||||
seats, verbose=False) # [4, 0]
|
r1,
|
||||||
self.assertNotEqual(r1, r2,
|
r2,
|
||||||
"Sainte Lague and its modified variant"
|
"Sainte Lague and its modified variant"
|
||||||
+ "should produce different results.")
|
+ "should produce different results.",
|
||||||
|
)
|
||||||
|
|
||||||
def test_no_ties_allowed(self):
|
def test_no_ties_allowed(self):
|
||||||
self.longMessage = True
|
self.longMessage = True
|
||||||
votes = [11, 11, 11]
|
votes = [11, 11, 11]
|
||||||
seats = 4
|
seats = 4
|
||||||
for method in app.METHODS:
|
for method in app.METHODS:
|
||||||
with self.assertRaises(app.TiesException,
|
if method == "quota":
|
||||||
msg=method + " failed"):
|
continue
|
||||||
app.compute(method, votes, seats,
|
with self.assertRaises(app.TiesException, msg=method + " failed"):
|
||||||
tiesallowed=False, verbose=False)
|
app.compute(method, votes, seats, tiesallowed=False, verbose=False)
|
||||||
|
|
||||||
def test_no_ties_allowed2(self):
|
def test_no_ties_allowed2(self):
|
||||||
self.longMessage = True
|
self.longMessage = True
|
||||||
votes = [12, 12, 11, 12]
|
votes = [12, 12, 11, 12]
|
||||||
seats = 3
|
seats = 3
|
||||||
for method in app.METHODS:
|
for method in app.METHODS:
|
||||||
|
if method == "quota":
|
||||||
|
continue
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
app.compute(method, votes, seats,
|
app.compute(method, votes, seats, tiesallowed=False, verbose=False),
|
||||||
tiesallowed=False, verbose=False),
|
[1, 1, 0, 1],
|
||||||
[1, 1, 0, 1], msg=method + " failed")
|
msg=method + " failed",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
Ładowanie…
Reference in New Issue