kopia lustrzana https://github.com/martinlackner/apportionment
562 wiersze
16 KiB
Python
562 wiersze
16 KiB
Python
"""
|
|
Unit tests
|
|
"""
|
|
|
|
import pytest
|
|
import apportionment.methods as app
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"method",
|
|
[
|
|
"quota",
|
|
"lrm",
|
|
"hamilton",
|
|
"largest_remainder",
|
|
"dhondt",
|
|
"jefferson",
|
|
"saintelague",
|
|
"webster",
|
|
"huntington",
|
|
"hill",
|
|
"adams",
|
|
"dean",
|
|
"smallestdivisor",
|
|
"harmonicmean",
|
|
"equalproportions",
|
|
"majorfractions",
|
|
"greatestdivisors",
|
|
"modified_saintelague",
|
|
],
|
|
)
|
|
@pytest.mark.parametrize("fractions", [True, False])
|
|
@pytest.mark.parametrize("verbose", [True, False])
|
|
def test_all_implemented(method, fractions, verbose):
|
|
votes = [1]
|
|
seats = 1
|
|
assert app.compute(method, votes, seats, fractions=fractions, verbose=verbose) == [1]
|
|
|
|
|
|
@pytest.mark.parametrize("method", app.METHODS)
|
|
@pytest.mark.parametrize("fractions", [True, False])
|
|
@pytest.mark.parametrize("verbose", [True, False])
|
|
def test_weak_proportionality(method, fractions, verbose):
|
|
votes = [14, 28, 7, 35]
|
|
seats = 12
|
|
result = app.compute(method, votes, seats, fractions=fractions, verbose=verbose)
|
|
assert result == [2, 4, 1, 5]
|
|
|
|
|
|
@pytest.mark.parametrize("method", app.METHODS)
|
|
@pytest.mark.parametrize("fractions", [True, False])
|
|
@pytest.mark.parametrize("verbose", [True, False])
|
|
def test_zero_parties(method, fractions, verbose):
|
|
votes = [0, 14, 28, 0, 0]
|
|
seats = 6
|
|
result = app.compute(method, votes, seats, fractions=fractions, verbose=verbose)
|
|
assert result == [0, 2, 4, 0, 0]
|
|
|
|
|
|
@pytest.mark.parametrize("method", app.METHODS)
|
|
@pytest.mark.parametrize("fractions", [True, False])
|
|
@pytest.mark.parametrize("verbose", [True, False])
|
|
def test_fewerseatsthanparties(method, fractions, verbose):
|
|
votes = [10, 9, 8, 8, 11, 12]
|
|
seats = 3
|
|
result = app.compute(method, votes, seats, fractions=fractions, verbose=verbose)
|
|
assert result == [1, 0, 0, 0, 1, 1]
|
|
|
|
|
|
# examples taken from
|
|
# Balinski, M. L., & Young, H. P. (1975).
|
|
# The quota method of apportionment.
|
|
# The American Mathematical Monthly, 82(7), 701-730.
|
|
@pytest.mark.parametrize(
|
|
"method, expected",
|
|
[
|
|
("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]),
|
|
],
|
|
)
|
|
@pytest.mark.parametrize("fractions", [True, False])
|
|
@pytest.mark.parametrize("verbose", [True, False])
|
|
def test_balinski_young_example1(method, expected, fractions, verbose):
|
|
votes = [5117, 4400, 162, 161, 160]
|
|
seats = 100
|
|
assert app.compute(method, votes, seats, fractions=fractions, verbose=verbose) == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"method, expected",
|
|
[
|
|
("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]),
|
|
],
|
|
)
|
|
@pytest.mark.parametrize("fractions", [True, False])
|
|
@pytest.mark.parametrize("verbose", [True, False])
|
|
def test_balinski_young_example2(method, expected, fractions, verbose):
|
|
votes = [9061, 7179, 5259, 3319, 1182]
|
|
seats = 26
|
|
assert app.compute(method, votes, seats, fractions=fractions, verbose=verbose) == expected
|
|
|
|
|
|
@pytest.mark.parametrize("method", app.METHODS)
|
|
@pytest.mark.parametrize("fractions", [True, False])
|
|
@pytest.mark.parametrize("verbose", [True, False])
|
|
def test_tiebreaking(method, fractions, verbose):
|
|
votes = [2, 1, 1, 2, 2]
|
|
seats = 2
|
|
assert app.compute(method, votes, seats, fractions=fractions, verbose=verbose) == [
|
|
1,
|
|
0,
|
|
0,
|
|
1,
|
|
0,
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("verbose", [True, False])
|
|
def test_within_quota(verbose):
|
|
votes = [5117, 4400, 162, 161, 160]
|
|
representatives = [51, 44, 2, 2, 1]
|
|
assert app.within_quota(votes, representatives, verbose=verbose)
|
|
representatives = [52, 45, 1, 1, 1]
|
|
assert not app.within_quota(votes, representatives, verbose=verbose)
|
|
representatives = [52, 43, 2, 1, 2]
|
|
assert not app.within_quota(votes, representatives, verbose=verbose)
|
|
|
|
|
|
@pytest.mark.parametrize("fractions", [True, False])
|
|
@pytest.mark.parametrize("verbose", [True, False])
|
|
def test_threshold(fractions, verbose):
|
|
votes = [41, 56, 3]
|
|
seats = 60
|
|
threshold = 0.03
|
|
filtered_votes = app.apply_threshold(votes, threshold)
|
|
assert filtered_votes == [41, 56, 3]
|
|
threshold = 0.031
|
|
filtered_votes = app.apply_threshold(votes, threshold)
|
|
assert filtered_votes == [41, 56, 0]
|
|
|
|
method = "dhondt"
|
|
threshold = 0
|
|
unfiltered_result = app.compute(
|
|
method, votes, seats, fractions=fractions, threshold=threshold, verbose=verbose
|
|
)
|
|
threshold = 0.04
|
|
filtered_result = app.compute(
|
|
method, votes, seats, fractions=fractions, threshold=threshold, verbose=verbose
|
|
)
|
|
assert unfiltered_result != filtered_result
|
|
|
|
|
|
@pytest.mark.parametrize("fractions", [True, False])
|
|
@pytest.mark.parametrize("verbose", [True, False])
|
|
def test_saintelague_difference(fractions, verbose):
|
|
votes = [6, 1]
|
|
seats = 4
|
|
r1 = app.compute("saintelague", votes, seats, fractions=fractions, verbose=verbose) # [3, 1]
|
|
r2 = app.compute(
|
|
"modified_saintelague", votes, seats, fractions=fractions, verbose=verbose
|
|
) # [4, 0]
|
|
assert r1 != r2
|
|
|
|
|
|
@pytest.mark.parametrize("method", app.METHODS)
|
|
@pytest.mark.parametrize("fractions", [True, False])
|
|
@pytest.mark.parametrize("verbose", [True, False])
|
|
def test_no_ties_allowed(method, fractions, verbose):
|
|
votes = [11, 11, 11]
|
|
seats = 4
|
|
if method == "quota":
|
|
return
|
|
with pytest.raises(app.TiesException):
|
|
app.compute(method, votes, seats, fractions=fractions, tiesallowed=False, verbose=verbose)
|
|
|
|
|
|
@pytest.mark.parametrize("method", app.METHODS)
|
|
@pytest.mark.parametrize("fractions", [True, False])
|
|
@pytest.mark.parametrize("verbose", [True, False])
|
|
def test_no_ties_allowed2(method, fractions, verbose):
|
|
votes = [12, 12, 11, 12]
|
|
seats = 3
|
|
if method == "quota":
|
|
return
|
|
assert app.compute(
|
|
method, votes, seats, fractions=fractions, tiesallowed=False, verbose=verbose
|
|
) == [1, 1, 0, 1]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"year, partynames, votes, officialresult",
|
|
[
|
|
(
|
|
2019,
|
|
[
|
|
"ÖVP",
|
|
"SPÖ",
|
|
"FPÖ",
|
|
"NEOS",
|
|
"JETZT",
|
|
"GRÜNE",
|
|
"KPÖ",
|
|
"WANDL",
|
|
"BZÖ",
|
|
"BIER",
|
|
"CPÖ",
|
|
"GILT",
|
|
"SLP",
|
|
],
|
|
[
|
|
1789417,
|
|
1011868,
|
|
772666,
|
|
387124,
|
|
89169,
|
|
664055,
|
|
32736,
|
|
22168,
|
|
760,
|
|
4946,
|
|
260,
|
|
1767,
|
|
310,
|
|
],
|
|
[71, 40, 31, 15, 0, 26, 0, 0, 0, 0, 0, 0, 0],
|
|
),
|
|
(
|
|
2017,
|
|
[
|
|
"SPÖ",
|
|
"ÖVP",
|
|
"FPÖ",
|
|
"GRÜNE",
|
|
"NEOS",
|
|
"PILZ",
|
|
"GILT",
|
|
"FLÖ",
|
|
"KPÖ",
|
|
"WEIßE",
|
|
"SLP",
|
|
"EUAUS",
|
|
"M",
|
|
"CPÖ",
|
|
"NBZ",
|
|
"ODP",
|
|
],
|
|
[
|
|
1361746,
|
|
1595526,
|
|
1316442,
|
|
192638,
|
|
268518,
|
|
223543,
|
|
48234,
|
|
8889,
|
|
39689,
|
|
9167,
|
|
713,
|
|
693,
|
|
221,
|
|
425,
|
|
2724,
|
|
761,
|
|
],
|
|
[52, 62, 51, 0, 10, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
),
|
|
(
|
|
2013,
|
|
[
|
|
"SPÖ",
|
|
"ÖVP",
|
|
"FPÖ",
|
|
"BZÖ",
|
|
"GRÜNE",
|
|
"FRANK",
|
|
"NEOS",
|
|
"KPÖ",
|
|
"PIRAT",
|
|
"CPÖ",
|
|
"WANDL",
|
|
"M",
|
|
"EUAUS",
|
|
"SLP",
|
|
],
|
|
[
|
|
1258605,
|
|
1125876,
|
|
962313,
|
|
165746,
|
|
582657,
|
|
268679,
|
|
232946,
|
|
48175,
|
|
36265,
|
|
6647,
|
|
3051,
|
|
490,
|
|
510,
|
|
947,
|
|
],
|
|
[52, 47, 40, 0, 24, 11, 9, 0, 0, 0, 0, 0, 0, 0],
|
|
),
|
|
(
|
|
2008,
|
|
[
|
|
"SPÖ",
|
|
"ÖVP",
|
|
"GRÜNE",
|
|
"FPÖ",
|
|
"BZÖ",
|
|
"FRITZ",
|
|
"DC",
|
|
"KPÖ",
|
|
"LIF",
|
|
"RETTÖ",
|
|
"LINKE",
|
|
"KLEMENTE",
|
|
"LINKE",
|
|
"STARK",
|
|
"TRP",
|
|
],
|
|
[
|
|
1430206,
|
|
1269656,
|
|
509936,
|
|
857029,
|
|
522933,
|
|
86194,
|
|
31080,
|
|
37362,
|
|
102249,
|
|
35718,
|
|
349,
|
|
347,
|
|
1789,
|
|
237,
|
|
2224,
|
|
],
|
|
[57, 51, 20, 34, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
),
|
|
(
|
|
2006,
|
|
[
|
|
"ÖVP",
|
|
"SPÖ",
|
|
"FPÖ",
|
|
"GRÜNE",
|
|
"BZÖ",
|
|
"KPÖ",
|
|
"MATIN",
|
|
"NFÖ",
|
|
"IVE",
|
|
"STARK",
|
|
"SAU",
|
|
"SLP",
|
|
],
|
|
[
|
|
1616493,
|
|
1663986,
|
|
519598,
|
|
520130,
|
|
193539,
|
|
47578,
|
|
131688,
|
|
10594,
|
|
592,
|
|
312,
|
|
1514,
|
|
2257,
|
|
],
|
|
[66, 68, 21, 21, 7, 0, 0, 0, 0, 0, 0, 0],
|
|
),
|
|
(
|
|
2002,
|
|
["SPÖ", "FPÖ", "ÖVP", "GRÜNE", "KPÖ", "LIF", "DD", "CWG", "SLP"],
|
|
[1792499, 491328, 2076833, 454980, 27568, 48083, 2439, 2009, 3906],
|
|
[69, 18, 79, 17, 0, 0, 0, 0, 0],
|
|
),
|
|
(
|
|
1999,
|
|
["SPÖ", "ÖVP", "FPÖ", "LIF", "GRÜNE", "KPÖ", "DU", "NEIN", "CWG"],
|
|
[1532448, 1243672, 1244087, 168612, 342260, 22016, 46943, 19286, 3030],
|
|
[65, 52, 52, 0, 14, 0, 0, 0, 0],
|
|
),
|
|
(
|
|
1995,
|
|
["SPÖ", "ÖVP", "FPÖ", "GRÜNE", "LIF", "NEIN", "KPÖ", "ÖNP", "DBP"],
|
|
[1843474, 1370510, 1060377, 233208, 267026, 53176, 13938, 1634, 830],
|
|
[71, 52, 41, 9, 10, 0, 0, 0, 0],
|
|
),
|
|
(
|
|
1994,
|
|
[
|
|
"SPÖ",
|
|
"ÖVP",
|
|
"FPÖ",
|
|
"GRÜNE",
|
|
"LIF",
|
|
"VGÖ",
|
|
"KPÖ",
|
|
"BGÖ",
|
|
"NEIN",
|
|
"CWG",
|
|
"ÖNP",
|
|
"FG",
|
|
"DBP",
|
|
],
|
|
[
|
|
1617804,
|
|
1281846,
|
|
1042332,
|
|
338538,
|
|
276580,
|
|
5776,
|
|
11919,
|
|
2504,
|
|
41492,
|
|
9051,
|
|
4209,
|
|
482,
|
|
581,
|
|
],
|
|
[65, 52, 42, 13, 11, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
),
|
|
],
|
|
)
|
|
@pytest.mark.parametrize("fractions", [True, False])
|
|
@pytest.mark.parametrize("verbose", [True, False])
|
|
def test_austrian_elections(year, partynames, votes, officialresult, fractions, verbose):
|
|
result = app.compute(
|
|
"dhondt",
|
|
votes,
|
|
183,
|
|
fractions=fractions,
|
|
parties=partynames,
|
|
threshold=0.04,
|
|
verbose=verbose,
|
|
)
|
|
assert str(tuple(result) == tuple(officialresult))
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"knesset_nr, partynames, votes, officialresult, threshold",
|
|
[
|
|
[
|
|
19,
|
|
[
|
|
"Likud Yisrael Beitenu+Habayit Hayehudi",
|
|
"Yesh Atid+Israel Labor Party",
|
|
"Shas+United Torah Judaism",
|
|
"Hatenua+Meretz",
|
|
"United Arab List",
|
|
"Hadash+National Democratic Assembly - Balad",
|
|
"Kadima",
|
|
],
|
|
[
|
|
885163 + 345985,
|
|
543458 + 432118,
|
|
331868 + 195892,
|
|
189167 + 172403,
|
|
138450,
|
|
113439 + 97030,
|
|
78974,
|
|
],
|
|
[31 + 12, 19 + 15, 11 + 7, 6 + 6, 4, 4 + 3, 2],
|
|
0.02,
|
|
],
|
|
[
|
|
20,
|
|
[
|
|
"Likud Chaired by Benjamin Netanyahu for Prime Minister+Habayit Hayehudi Chaired by Naftali Bennett",
|
|
"Zionist Camp Chaired by Isaac Herzog and Tzipi Livni+Israel's Left",
|
|
"Joint List (Hadash, National Democratic Assembly, Arab Movement for Renewal, United Arab List)",
|
|
"Yesh Atid Chaired by Yair Lapid",
|
|
"Kulanu Chaired by Moshe Kahlon+Yisrael Beitenu Chaired by Avigdor Liberman",
|
|
"Shas+United Torah Judaism",
|
|
],
|
|
[
|
|
985408 + 283910,
|
|
786313 + 165529,
|
|
446583,
|
|
371602,
|
|
315360 + 214906,
|
|
241613 + 210143,
|
|
],
|
|
[30 + 8, 24 + 5, 13, 11, 10 + 6, 7 + 6],
|
|
0.0325,
|
|
],
|
|
[
|
|
21,
|
|
[
|
|
"Likud Chaired by Benjamin Netanyahu for Prime Minister+United Right",
|
|
"Blue and White",
|
|
"Shas+United Torah Judaism",
|
|
"Hadash-Ta'al+Ra'am-Balad",
|
|
"Israeli Labor Party+Meretz",
|
|
"Yisrael Beitenu",
|
|
"Kulanu Chaired by Moshe Kahlon",
|
|
],
|
|
[
|
|
1140370 + 159468,
|
|
1125881,
|
|
258275 + 249049,
|
|
193442 + 143666,
|
|
190870 + 156473,
|
|
173004,
|
|
152756,
|
|
],
|
|
[35 + 5, 35, 8 + 8, 6 + 4, 6 + 4, 5, 4],
|
|
0.0325,
|
|
],
|
|
[
|
|
22,
|
|
[
|
|
"Blue and White+Yisrael Beitenu",
|
|
"Likud+Yemina",
|
|
"Joint List (Hadash, Ra'am, Ta'al, Balad)",
|
|
"Shas+United Torah Judaism",
|
|
"Labor-Gesher+Democratic Union",
|
|
],
|
|
[
|
|
1151214 + 310154,
|
|
1113617 + 260655,
|
|
470211,
|
|
330199 + 268775,
|
|
212782 + 192495,
|
|
],
|
|
[33 + 8, 32 + 7, 13, 9 + 7, 6 + 5],
|
|
0.0325,
|
|
],
|
|
],
|
|
)
|
|
@pytest.mark.parametrize("fractions", [True, False])
|
|
@pytest.mark.parametrize("verbose", [True, False])
|
|
def test_israeli_elections(
|
|
knesset_nr, partynames, votes, officialresult, threshold, fractions, verbose
|
|
):
|
|
print("Knesset #" + str(knesset_nr) + ":")
|
|
result = app.compute(
|
|
"dhondt",
|
|
votes,
|
|
sum(officialresult),
|
|
fractions=fractions,
|
|
parties=partynames,
|
|
threshold=threshold,
|
|
verbose=verbose,
|
|
)
|
|
assert str(tuple(result) == tuple(officialresult))
|