From fb8d8981d72414deb8cd7efdac07d9d622371a94 Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Tue, 29 Dec 2020 22:19:15 +1100 Subject: [PATCH] Add test case based on PRSA ballot data --- requirements.txt | 10 +++ tests/prsa1.blt | 75 ++++++++++++++++ tests/test_prsa.py | 219 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 304 insertions(+) create mode 100644 tests/prsa1.blt create mode 100644 tests/test_prsa.py diff --git a/requirements.txt b/requirements.txt index e45ba5e..3f69467 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,18 @@ # Dependencies Transcrypt==3.7.16 +pytest==6.2.1 # Dependency tree +attrs==20.3.0 +importlib-metadata==3.3.0 +iniconfig==1.1.1 mypy==0.790 mypy-extensions==0.4.3 +packaging==20.8 +pluggy==0.13.1 +py==1.10.0 +pyparsing==2.4.7 +toml==0.10.2 typed-ast==1.4.1 typing-extensions==3.7.4.3 +zipp==3.4.0 diff --git a/tests/prsa1.blt b/tests/prsa1.blt new file mode 100644 index 0000000..eb36fc5 --- /dev/null +++ b/tests/prsa1.blt @@ -0,0 +1,75 @@ +7 4 +1000 1 3 0 +1000 2 4 7 0 +1000 2 6 1 7 0 +1000 2 6 5 3 0 +1000 3 0 +1000 4 0 +1000 3 0 +1000 4 0 +1000 4 0 +1000 3 0 +1000 5 7 0 +1000 4 1 0 +1000 6 0 +1000 4 0 +1000 2 3 0 +1000 3 0 +1000 7 0 +1000 4 0 +1000 2 4 7 0 +1000 2 4 7 0 +1000 4 0 +1000 2 3 0 +1000 4 0 +1000 7 0 +1000 2 6 0 +1000 2 1 0 +1000 7 0 +1000 2 4 1 3 0 +1000 4 0 +1000 6 0 +1000 6 0 +1000 2 4 3 7 0 +1000 6 0 +1000 2 7 0 +1000 2 7 0 +1000 2 6 1 7 0 +1000 3 0 +1000 2 4 7 0 +1000 2 6 0 +1000 2 4 3 7 0 +1000 2 6 0 +1000 2 4 7 0 +1000 4 0 +1000 2 6 7 0 +1000 2 6 1 0 +1000 2 4 7 0 +1000 6 0 +1000 2 4 7 0 +1000 2 3 0 +1000 6 0 +1000 2 6 5 3 0 +1000 2 4 1 7 0 +1000 6 0 +1000 6 0 +1000 2 1 0 +1000 2 4 7 0 +1000 2 6 5 1 7 0 +1000 2 7 0 +1000 2 4 3 7 0 +1000 6 0 +1000 2 3 0 +1000 2 5 7 0 +1000 2 4 5 1 3 0 +1000 6 0 +1000 6 0 +0 +"Evans" +"Grey" +"Thomson" +"Ames" +"Reid" +"Spears" +"White" +"1976 Committee of the Utopia Tennis Club" diff --git a/tests/test_prsa.py b/tests/test_prsa.py new file mode 100644 index 0000000..74a7e35 --- /dev/null +++ b/tests/test_prsa.py @@ -0,0 +1,219 @@ +# pyRCV2: Preferential vote counting +# Copyright © 2020 Lee Yingtong Li (RunasSudo) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import pyRCV2.blt +import pyRCV2.numbers +from pyRCV2.method.base_stv import EGSTVCounter +from pyRCV2.model import CandidateState, CountCompleted + +def isclose(x, y): + # There will be some discrepancy due to how the PRSA count rounds transfer values, so accept up to 0.1 votes difference + return abs(int(x.impl) - y) < 100 + +def test_prsa1(): + """Compare count of prsa1.blt with model result at http://www.prsa.org.au/example1.pdf""" + + pyRCV2.numbers.set_numclass(pyRCV2.numbers.Fixed) + pyRCV2.numbers.set_dps(5) + + with open('tests/prsa1.blt', 'r') as f: + election = pyRCV2.blt.readBLT(f.read()) + + assert len(election.candidates) == 7 + + c_evans = next(c for c in election.candidates if c.name == 'Evans') + c_grey = next(c for c in election.candidates if c.name == 'Grey') + c_thomson = next(c for c in election.candidates if c.name == 'Thomson') + c_ames = next(c for c in election.candidates if c.name == 'Ames') + c_reid = next(c for c in election.candidates if c.name == 'Reid') + c_spears = next(c for c in election.candidates if c.name == 'Spears') + c_white = next(c for c in election.candidates if c.name == 'White') + + counter = EGSTVCounter(election, { + 'papers': 'transferable', + 'exclusion': 'parcels_by_order' + }) + + # Stage 1 + result = counter.reset() + assert len(result.candidates.impl) == 7 + assert int(result.total.impl) == 65000 + assert int(result.quota.impl) == 13001 + assert result.comment == 'First preferences' + assert isclose(result.candidates[c_evans].votes, 1000) + assert isclose(result.candidates[c_grey].votes, 34000) + assert isclose(result.candidates[c_thomson].votes, 5000) + assert isclose(result.candidates[c_ames].votes, 10000) + assert isclose(result.candidates[c_reid].votes, 1000) + assert isclose(result.candidates[c_spears].votes, 11000) + assert isclose(result.candidates[c_white].votes, 3000) + assert isclose(result.exhausted.votes, 0) + + # Stage 2 + result = counter.step() + assert result.comment == 'Surplus of Grey' + assert isclose(result.candidates[c_evans].votes, 2234) + assert isclose(result.candidates[c_grey].votes, 13001) + assert isclose(result.candidates[c_thomson].votes, 7468) + assert isclose(result.candidates[c_ames].votes, 18638) + assert isclose(result.candidates[c_reid].votes, 1617) + assert isclose(result.candidates[c_spears].votes, 17170) + assert isclose(result.candidates[c_white].votes, 4851) + assert isclose(result.exhausted.votes, 0) + + # Stage 3 + result = counter.step() + assert result.comment == 'Surplus of Ames' + assert isclose(result.candidates[c_evans].votes, 3038) + assert isclose(result.candidates[c_grey].votes, 13001) + assert isclose(result.candidates[c_thomson].votes, 8674) + assert isclose(result.candidates[c_ames].votes, 13001) + assert isclose(result.candidates[c_reid].votes, 2019) + assert isclose(result.candidates[c_spears].votes, 17170) + assert isclose(result.candidates[c_white].votes, 8067) + assert isclose(result.exhausted.votes, 0) + + # Stage 4 + result = counter.step() + assert result.comment == 'Surplus of Spears' + assert isclose(result.candidates[c_evans].votes, 4823) + assert isclose(result.candidates[c_grey].votes, 13001) + assert isclose(result.candidates[c_thomson].votes, 8674) + assert isclose(result.candidates[c_ames].votes, 13001) + assert isclose(result.candidates[c_reid].votes, 3804) + assert isclose(result.candidates[c_spears].votes, 13001) + assert isclose(result.candidates[c_white].votes, 8662) + assert isclose(result.exhausted.votes, 0) + + # Stage 5 + result = counter.step() + assert result.comment == 'Exclusion of Reid' + assert isclose(result.candidates[c_reid].transfers, -1000) + assert isclose(result.candidates[c_white].transfers, 1000) + + # Stage 6 + result = counter.step() + assert result.comment == 'Exclusion of Reid' + assert isclose(result.candidates[c_reid].transfers, -617) + assert isclose(result.candidates[c_white].transfers, 617) + + # Stage 7 + result = counter.step() + assert result.comment == 'Exclusion of Reid' + assert isclose(result.candidates[c_reid].transfers, -402) + assert isclose(result.candidates[c_evans].transfers, 402) + + # Stage 8 + result = counter.step() + assert result.comment == 'Exclusion of Reid' + assert isclose(result.candidates[c_reid].transfers, -1785) + assert isclose(result.candidates[c_evans].transfers, 595) + assert isclose(result.candidates[c_thomson].transfers, 1190) + + assert isclose(result.candidates[c_evans].votes, 5820) + assert isclose(result.candidates[c_grey].votes, 13001) + assert isclose(result.candidates[c_thomson].votes, 9864) + assert isclose(result.candidates[c_ames].votes, 13001) + assert isclose(result.candidates[c_reid].votes, 0) + assert isclose(result.candidates[c_spears].votes, 13001) + assert isclose(result.candidates[c_white].votes, 10279) + assert isclose(result.exhausted.votes, 0) + + # Stage 9 + result = counter.step() + assert result.comment == 'Exclusion of Evans' + assert isclose(result.candidates[c_evans].transfers, -1000) + assert isclose(result.candidates[c_thomson].transfers, 1000) + + # Stage 10 + result = counter.step() + assert result.comment == 'Exclusion of Evans' + assert isclose(result.candidates[c_evans].transfers, -1234) + assert isclose(result.exhausted.transfers, 1234) + + # Stage 11 + result = counter.step() + assert result.comment == 'Exclusion of Evans' + assert isclose(result.candidates[c_evans].transfers, -804) + assert isclose(result.candidates[c_thomson].transfers, 402) + assert isclose(result.candidates[c_white].transfers, 402) + + # Stage 12 + result = counter.step() + assert result.comment == 'Exclusion of Evans' + assert isclose(result.candidates[c_evans].transfers, -1785) + assert isclose(result.candidates[c_white].transfers, 1190) + assert isclose(result.exhausted.transfers, 595) + + # Stage 13 + result = counter.step() + assert result.comment == 'Exclusion of Evans' + assert isclose(result.candidates[c_evans].transfers, -402) + assert isclose(result.candidates[c_thomson].transfers, 402) + + # Stage 14 + result = counter.step() + assert result.comment == 'Exclusion of Evans' + assert isclose(result.candidates[c_evans].transfers, -595) + assert isclose(result.candidates[c_white].transfers, 595) + + assert isclose(result.candidates[c_evans].votes, 0) + assert isclose(result.candidates[c_grey].votes, 13001) + assert isclose(result.candidates[c_thomson].votes, 11668) + assert isclose(result.candidates[c_ames].votes, 13001) + assert isclose(result.candidates[c_reid].votes, 0) + assert isclose(result.candidates[c_spears].votes, 13001) + assert isclose(result.candidates[c_white].votes, 12466) + assert isclose(result.exhausted.votes, 1829) + + # Stage 15 + result = counter.step() + assert result.comment == 'Exclusion of Thomson' + assert isclose(result.candidates[c_thomson].transfers, -5000) + assert isclose(result.exhausted.transfers, 5000) + + # Stage 16 + result = counter.step() + assert result.comment == 'Exclusion of Thomson' + assert isclose(result.candidates[c_thomson].transfers, -2468) + assert isclose(result.exhausted.transfers, 2468) + + # Stage 17 + result = counter.step() + assert result.comment == 'Exclusion of Thomson' + assert isclose(result.candidates[c_thomson].transfers, -1206) + assert isclose(result.candidates[c_white].transfers, 1206) + + assert isclose(result.candidates[c_evans].votes, 0) + assert isclose(result.candidates[c_grey].votes, 13001) + assert isclose(result.candidates[c_thomson].votes, 2994) + assert isclose(result.candidates[c_ames].votes, 13001) + assert isclose(result.candidates[c_reid].votes, 0) + assert isclose(result.candidates[c_spears].votes, 13001) + assert isclose(result.candidates[c_white].votes, 13672) + assert isclose(result.exhausted.votes, 9297) + + assert result.candidates[c_evans].state == CandidateState.EXCLUDED + assert result.candidates[c_grey].state == CandidateState.ELECTED + assert result.candidates[c_thomson].state == CandidateState.EXCLUDING + assert result.candidates[c_ames].state == CandidateState.ELECTED + assert result.candidates[c_reid].state == CandidateState.EXCLUDED + assert result.candidates[c_spears].state == CandidateState.ELECTED + assert result.candidates[c_white].state == CandidateState.PROVISIONALLY_ELECTED + + # Count complete + result = counter.step() + assert isinstance(result, CountCompleted)