kopia lustrzana https://github.com/martinlackner/apportionment
Switched much functionality to use numpy
rodzic
0b5ea8669b
commit
7776f7738a
|
@ -4,7 +4,9 @@
|
|||
from __future__ import print_function, division
|
||||
import string
|
||||
import math
|
||||
import numpy as np
|
||||
from fractions import Fraction
|
||||
import time
|
||||
|
||||
|
||||
METHODS = [
|
||||
|
@ -30,7 +32,7 @@ def compute(
|
|||
parties=string.ascii_letters,
|
||||
threshold=None,
|
||||
tiesallowed=True,
|
||||
verbose=True,
|
||||
verbose=True
|
||||
):
|
||||
filtered_votes = apply_threshold(votes, threshold)
|
||||
if method == "quota":
|
||||
|
@ -120,31 +122,32 @@ def within_quota(votes, representatives, parties=string.ascii_letters, verbose=T
|
|||
def largest_remainder(
|
||||
votes, seats, parties=string.ascii_letters, tiesallowed=True, verbose=True
|
||||
):
|
||||
votes = np.array(votes)
|
||||
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]
|
||||
quotas = (votes * seats) / np.sum(votes)
|
||||
representatives = np.int_(np.trunc(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]
|
||||
if np.sum(representatives) < seats:
|
||||
remainders = quotas - representatives
|
||||
cutoff = remainders[np.argsort(remainders)[seats - np.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:
|
||||
reps_sum = np.sum(representatives)
|
||||
if reps_sum == seats and remainders[i] >= cutoff:
|
||||
if not ties:
|
||||
tiebreaking_message = tiebreaking_message[:-2]
|
||||
tiebreaking_message += "\n to the disadvantage of: "
|
||||
ties = True
|
||||
tiebreaking_message += parties[i] + ", "
|
||||
elif sum(representatives) < seats and remainders[i] > cutoff:
|
||||
elif reps_sum < seats and remainders[i] > cutoff:
|
||||
representatives[i] += 1
|
||||
elif sum(representatives) < seats and remainders[i] == cutoff:
|
||||
elif reps_sum < seats and remainders[i] == cutoff:
|
||||
tiebreaking_message += parties[i] + ", "
|
||||
representatives[i] += 1
|
||||
if ties and verbose:
|
||||
|
@ -156,26 +159,27 @@ def largest_remainder(
|
|||
if verbose:
|
||||
__print_results(representatives, parties)
|
||||
|
||||
return representatives
|
||||
return representatives.tolist()
|
||||
|
||||
|
||||
# Divisor methods
|
||||
def divisor(
|
||||
votes, seats, method, parties=string.ascii_letters, tiesallowed=True, verbose=True
|
||||
):
|
||||
representatives = [0] * len(votes)
|
||||
votes = np.array(votes)
|
||||
representatives = np.zeros(len(votes), dtype=int)
|
||||
if method in ["dhondt", "jefferson", "greatestdivisors"]:
|
||||
if verbose:
|
||||
print("\nD'Hondt (Jefferson) method")
|
||||
divisors = [i + 1 for i in range(seats)]
|
||||
divisors = np.arange(seats) + 1
|
||||
elif method in ["saintelague", "webster", "majorfractions"]:
|
||||
if verbose:
|
||||
print("\nSainte Lague (Webster) method")
|
||||
divisors = [2 * i + 1 for i in range(seats)]
|
||||
divisors = 2 * np.arange(seats) + 1
|
||||
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 = np.insert(2 * np.arange(1, seats) + 1, 0, 1.4)
|
||||
elif method in ["huntington", "hill", "equalproportions"]:
|
||||
if verbose:
|
||||
print("\nHuntington-Hill method")
|
||||
|
@ -184,8 +188,9 @@ def divisor(
|
|||
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)]
|
||||
representatives = np.array([1 if p > 0 else 0 for p in votes])
|
||||
divisors = np.arange(seats)
|
||||
divisors = np.sqrt((divisors + 1) * (divisors + 2))
|
||||
elif method in ["adams", "smallestdivisor"]:
|
||||
if verbose:
|
||||
print("\nAdams method")
|
||||
|
@ -195,7 +200,7 @@ def divisor(
|
|||
)
|
||||
else:
|
||||
representatives = [1 if p > 0 else 0 for p in votes]
|
||||
divisors = [i + 1 for i in range(seats)]
|
||||
divisors = np.arange(seats) + 1
|
||||
elif method in ["dean", "harmonicmean"]:
|
||||
if verbose:
|
||||
print("\nDean method")
|
||||
|
@ -204,37 +209,29 @@ def divisor(
|
|||
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)
|
||||
]
|
||||
representatives = np.array([1 if p > 0 else 0 for p in votes])
|
||||
divisors = np.arange(seats)
|
||||
divisors = (2 * (divisors + 1) * (divisors + 2)) / (2 * (divisors + 1) + 1)
|
||||
else:
|
||||
raise NotImplementedError("divisor method " + method + " not known")
|
||||
|
||||
# assigning representatives
|
||||
if seats > sum(representatives):
|
||||
weights = []
|
||||
for p in votes:
|
||||
if method in ["huntington", "hill", "modified_saintelague"]:
|
||||
weights.append([(p / div) for div in divisors])
|
||||
else:
|
||||
weights.append([Fraction(p, div) for div in divisors])
|
||||
flatweights = sorted([w for l in weights for w in l], reverse=True)
|
||||
minweight = flatweights[seats - sum(representatives) - 1]
|
||||
if seats > np.sum(representatives):
|
||||
weights = np.array([p / divisors for p in votes])
|
||||
flatweights = np.sort(weights, axis=None)
|
||||
minweight = flatweights[-seats + np.sum(representatives)]
|
||||
|
||||
for i in range(len(votes)):
|
||||
representatives[i] += len([w for w in weights[i] if w > minweight])
|
||||
representatives += np.count_nonzero(weights > minweight, axis=1)
|
||||
|
||||
ties = False
|
||||
# dealing with ties
|
||||
if seats > sum(representatives):
|
||||
if seats > np.sum(representatives):
|
||||
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 np.sum(representatives) == seats and minweight in weights[i]:
|
||||
if not ties:
|
||||
if not tiesallowed:
|
||||
raise TiesException("Tie occurred")
|
||||
|
@ -242,7 +239,7 @@ def divisor(
|
|||
tiebreaking_message += "\n to the disadvantage of: "
|
||||
ties = True
|
||||
tiebreaking_message += parties[i] + ", "
|
||||
if sum(representatives) < seats and minweight in weights[i]:
|
||||
if np.sum(representatives) < seats and minweight in weights[i]:
|
||||
tiebreaking_message += parties[i] + ", "
|
||||
representatives[i] += 1
|
||||
if ties and verbose:
|
||||
|
@ -254,12 +251,12 @@ def divisor(
|
|||
if verbose:
|
||||
__print_results(representatives, parties)
|
||||
|
||||
return representatives
|
||||
return representatives.tolist()
|
||||
|
||||
|
||||
# required for methods with 0 divisors (Adams, Huntington-Hill)
|
||||
def __divzero_fewerseatsthanparties(votes, seats, parties, tiesallowed, verbose):
|
||||
representatives = [0] * len(votes)
|
||||
representatives = np.zeros(len(votes), dtype=int)
|
||||
if verbose:
|
||||
print(
|
||||
" fewer seats than parties; "
|
||||
|
@ -268,13 +265,13 @@ def __divzero_fewerseatsthanparties(votes, seats, parties, tiesallowed, verbose)
|
|||
)
|
||||
tiebreaking_message = " ties broken in favor of: "
|
||||
ties = False
|
||||
mincount = sorted(votes, reverse=True)[seats - 1]
|
||||
mincount = np.sort(votes)[-seats]
|
||||
for i in range(len(votes)):
|
||||
if sum(representatives) < seats and votes[i] >= mincount:
|
||||
if np.sum(representatives) < seats and votes[i] >= mincount:
|
||||
if votes[i] == mincount:
|
||||
tiebreaking_message += parties[i] + ", "
|
||||
representatives[i] = 1
|
||||
elif sum(representatives) == seats and votes[i] >= mincount:
|
||||
elif np.sum(representatives) == seats and votes[i] >= mincount:
|
||||
if not ties:
|
||||
tiebreaking_message = tiebreaking_message[:-2]
|
||||
tiebreaking_message += "\n to the disadvantage of: "
|
||||
|
@ -301,18 +298,16 @@ def quota(votes, seats, parties=string.ascii_letters, tiesallowed=True, verbose=
|
|||
)
|
||||
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))]
|
||||
# check if upper quota is violated
|
||||
for i in range(len(votes)):
|
||||
upperquota = int(
|
||||
math.ceil(float(votes[i]) * (sum(representatives) + 1) / sum(votes))
|
||||
)
|
||||
if representatives[i] >= upperquota:
|
||||
quotas[i] = 0
|
||||
representatives = np.zeros(len(votes), dtype=int)
|
||||
votes = np.array(votes)
|
||||
|
||||
maxquotas = [i for i in range(len(votes)) if quotas[i] == max(quotas)]
|
||||
while np.sum(representatives) < seats:
|
||||
quotas = votes / (representatives + 1)
|
||||
# check if upper quota is violated
|
||||
upperquota = np.trunc(np.ceil(votes * (np.sum(representatives) + 1) / np.sum(votes)))
|
||||
# TODO: upperquota[0]?
|
||||
quotas = np.where(representatives >= upperquota, 0, quotas)
|
||||
maxquotas = np.nonzero(quotas == quotas.max())
|
||||
|
||||
nextrep = maxquotas[0]
|
||||
|
||||
|
@ -335,4 +330,4 @@ def quota(votes, seats, parties=string.ascii_letters, tiesallowed=True, verbose=
|
|||
if verbose:
|
||||
__print_results(representatives, parties)
|
||||
|
||||
return representatives
|
||||
return representatives.tolist()
|
||||
|
|
Ładowanie…
Reference in New Issue