# pyRCV2: Preferential vote counting # Copyright © 2020–2021 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.gregory import EGSTVCounter from pyRCV2.model import CandidateState, CountCompleted def isclose(x, y): # Misnomer: Require exact match return x == pyRCV2.numbers.Num(y) / pyRCV2.numbers.Num(1000) 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/data/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, { 'surplus_order': 'order', 'papers': 'transferable', 'exclusion': 'parcels_by_order', 'round_quota': 3, 'round_votes': 3, 'round_tvs': 3, 'round_weights': 3, }) # Stage 1 result = counter.reset() assert len(result.candidates.impl) == 7 assert result.total == pyRCV2.numbers.Num('65') assert result.quota == pyRCV2.numbers.Num('13.001') 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)