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