# 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 . from pytest import approx from pytest_steps import test_steps import pyRCV2.blt import pyRCV2.numbers from pyRCV2.method.gregory import WIGSTVCounter from pyRCV2.model import CandidateState, CountCompleted def count_until_exclude(counter): while True: result = counter.step() if isinstance(result, CountCompleted): return result if result.comment.startswith('Exclusion of '): return result @test_steps(*['Round {}'.format(i) for i in range(1, 31)]) def test_csm15(): """Compare count of CSM15.blt with model result at https://www.eveonline.com/article/qbtyyo/meet-the-new-council""" pyRCV2.numbers.set_numclass(pyRCV2.numbers.Native) with open('tests/data/CSM15.blt', 'r') as f: election = pyRCV2.blt.readBLT(f.read()) assert len(election.candidates) == 40 cands = {c.name: c for c in election.candidates} counter = WIGSTVCounter(election, { 'exclusion': 'wright', 'round_quota': 0, }) # Round 1 result = counter.reset() assert result.candidates[cands['Merkelchen']].votes.impl == 5325 assert result.candidates[cands['Gobbins']].votes.impl == 3709 assert result.candidates[cands['Vily']].votes.impl == 2789 assert result.candidates[cands['Maria Taylor']].votes.impl == 2556 assert result.candidates[cands['Brisc Rubal']].votes.impl == 1794 assert result.candidates[cands['Ikarus Cesaille']].votes.impl == 1471 assert result.candidates[cands['Phantomite']].votes.impl == 1462 assert result.candidates[cands['Juvenius Drakonius']].votes.impl == 1375 assert result.candidates[cands['Torvald Uruz']].votes.impl == 1297 assert result.candidates[cands['Mike Azariah']].votes.impl == 1242 assert result.candidates[cands['BlazingBunny']].votes.impl == 1042 assert result.candidates[cands['ExookiZ']].votes.impl == 961 assert result.candidates[cands['Loroseco Kross']].votes.impl == 938 assert result.candidates[cands['Kenneth Feld']].votes.impl == 874 assert result.candidates[cands['Jurius Doctor']].votes.impl == 747 assert result.candidates[cands['Stitch Kaneland']].votes.impl == 698 assert result.candidates[cands['DutchGunner']].votes.impl == 690 assert result.candidates[cands['Insidious Sainthood']].votes.impl == 583 assert result.candidates[cands['Alexis Finch']].votes.impl == 526 assert result.candidates[cands['Steadyo']].votes.impl == 499 assert result.candidates[cands['Innominate']].votes.impl == 493 assert result.candidates[cands['Meredith en Thielles']].votes.impl == 477 assert result.candidates[cands['Ironwulf']].votes.impl == 472 assert result.candidates[cands['PJHustle']].votes.impl == 443 assert result.candidates[cands['Darius Caliente']].votes.impl == 442 assert result.candidates[cands['January Valentine']].votes.impl == 414 assert result.candidates[cands['The Oz']].votes.impl == 407 assert result.candidates[cands['Pandora Singularity']].votes.impl == 355 assert result.candidates[cands['UAxDEATH']].votes.impl == 323 assert result.candidates[cands['Komi Valentine']].votes.impl == 277 assert result.candidates[cands['Murray Rothbardo']].votes.impl == 184 assert result.candidates[cands['Xenuria']].votes.impl == 179 assert result.candidates[cands['Prospektor Schipplock']].votes.impl == 177 assert result.candidates[cands['Styxx']].votes.impl == 172 assert result.candidates[cands['boernl']].votes.impl == 171 assert result.candidates[cands['Dhuras']].votes.impl == 162 assert result.candidates[cands['Rheaha Preynar']].votes.impl == 125 assert result.candidates[cands['Kalen Tsero']].votes.impl == 113 assert result.candidates[cands['Storm Delay']].votes.impl == 96 assert result.candidates[cands['Leehams DaWildabeast']].votes.impl == 60 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Leehams DaWildabeast']].votes.impl == approx(63.208412) assert result.candidates[cands['Leehams DaWildabeast']].state == CandidateState.EXCLUDED yield 'Round 1' # Round 2 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Storm Delay']].votes.impl == approx(112.229279) assert result.candidates[cands['Storm Delay']].state == CandidateState.EXCLUDED yield 'Round 2' # Round 3 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Kalen Tsero']].votes.impl == approx(115.888433) assert result.candidates[cands['Kalen Tsero']].state == CandidateState.EXCLUDED yield 'Round 3' # Round 4 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Rheaha Preynar']].votes.impl == approx(133.122193) assert result.candidates[cands['Rheaha Preynar']].state == CandidateState.EXCLUDED yield 'Round 4' # Round 5 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Dhuras']].votes.impl == approx(170.123665) assert result.candidates[cands['Dhuras']].state == CandidateState.EXCLUDED yield 'Round 5' # Round 6 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['boernl']].votes.impl == approx(181.377165) assert result.candidates[cands['boernl']].state == CandidateState.EXCLUDED yield 'Round 6' # Round 7 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Styxx']].votes.impl == approx(184.463008) assert result.candidates[cands['Styxx']].state == CandidateState.EXCLUDED yield 'Round 7' # Round 8 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Murray Rothbardo']].votes.impl == approx(189.651433) assert result.candidates[cands['Murray Rothbardo']].state == CandidateState.EXCLUDED yield 'Round 8' # Round 9 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Xenuria']].votes.impl == approx(198.266912) assert result.candidates[cands['Xenuria']].state == CandidateState.EXCLUDED yield 'Round 9' # Round 10 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Prospektor Schipplock']].votes.impl == approx(213.386936) assert result.candidates[cands['Prospektor Schipplock']].state == CandidateState.EXCLUDED yield 'Round 10' # Round 11 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Komi Valentine']].votes.impl == approx(336.404306) assert result.candidates[cands['Komi Valentine']].state == CandidateState.EXCLUDED yield 'Round 11' # Round 12 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['UAxDEATH']].votes.impl == approx(357.732024) assert result.candidates[cands['UAxDEATH']].state == CandidateState.EXCLUDED yield 'Round 12' # Round 13 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Pandora Singularity']].votes.impl == approx(413.808353) assert result.candidates[cands['Pandora Singularity']].state == CandidateState.EXCLUDED yield 'Round 13' # Round 14 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['PJHustle']].votes.impl == approx(478.737771) assert result.candidates[cands['PJHustle']].state == CandidateState.EXCLUDED yield 'Round 14' # Round 15 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Ironwulf']].votes.impl == approx(528.845712) assert result.candidates[cands['Ironwulf']].state == CandidateState.EXCLUDED yield 'Round 15' # Round 16 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['January Valentine']].votes.impl == approx(557.154707) assert result.candidates[cands['January Valentine']].state == CandidateState.EXCLUDED yield 'Round 16' # Round 17 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Darius Caliente']].votes.impl == approx(620.307162) assert result.candidates[cands['Darius Caliente']].state == CandidateState.EXCLUDED yield 'Round 17' # Round 18 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Alexis Finch']].votes.impl == approx(630.517365) assert result.candidates[cands['Alexis Finch']].state == CandidateState.EXCLUDED yield 'Round 18' # Round 19 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['The Oz']].votes.impl == approx(673.241462) assert result.candidates[cands['The Oz']].state == CandidateState.EXCLUDED yield 'Round 19' # Round 20 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Steadyo']].votes.impl == approx(693.622387) assert result.candidates[cands['Steadyo']].state == CandidateState.EXCLUDED yield 'Round 20' # Round 21 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Insidious Sainthood']].votes.impl == approx(711.112702) assert result.candidates[cands['Insidious Sainthood']].state == CandidateState.EXCLUDED yield 'Round 21' # Round 22 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Meredith en Thielles']].votes.impl == approx(773.804223) assert result.candidates[cands['Meredith en Thielles']].state == CandidateState.EXCLUDED yield 'Round 22' # Round 23 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Stitch Kaneland']].votes.impl == approx(944.597498) assert result.candidates[cands['Stitch Kaneland']].state == CandidateState.EXCLUDED yield 'Round 23' # Round 24 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['DutchGunner']].votes.impl == approx(1056.847624) assert result.candidates[cands['DutchGunner']].state == CandidateState.EXCLUDED yield 'Round 24' # Round 25 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Jurius Doctor']].votes.impl == approx(1254.134642) assert result.candidates[cands['Jurius Doctor']].state == CandidateState.EXCLUDED yield 'Round 25' # Round 26 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['BlazingBunny']].votes.impl == approx(1448.527563) assert result.candidates[cands['BlazingBunny']].state == CandidateState.EXCLUDED yield 'Round 26' # Round 27 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['ExookiZ']].votes.impl == approx(1527.088745) assert result.candidates[cands['ExookiZ']].state == CandidateState.EXCLUDED yield 'Round 27' # Round 28 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Juvenius Drakonius']].votes.impl == approx(1614.455637) assert result.candidates[cands['Juvenius Drakonius']].state == CandidateState.EXCLUDED yield 'Round 28' # Round 29 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Ikarus Cesaille']].votes.impl == approx(1938.970656) assert result.candidates[cands['Ikarus Cesaille']].state == CandidateState.EXCLUDED yield 'Round 29' # Round 30 result = count_until_exclude(counter) assert counter.step_results[-2].candidates[cands['Loroseco Kross']].votes.impl == approx(2306.134123) assert result.candidates[cands['Loroseco Kross']].state == CandidateState.EXCLUDED result = counter.step() assert result.comment == 'Bulk election' assert result.candidates[cands['Vily']].state == CandidateState.PROVISIONALLY_ELECTED assert result.candidates[cands['Phantomite']].state == CandidateState.PROVISIONALLY_ELECTED assert result.candidates[cands['Kenneth Feld']].state == CandidateState.PROVISIONALLY_ELECTED assert result.candidates[cands['Merkelchen']].state == CandidateState.PROVISIONALLY_ELECTED assert result.candidates[cands['Maria Taylor']].state == CandidateState.PROVISIONALLY_ELECTED assert result.candidates[cands['Brisc Rubal']].state == CandidateState.PROVISIONALLY_ELECTED assert result.candidates[cands['Gobbins']].state == CandidateState.PROVISIONALLY_ELECTED assert result.candidates[cands['Torvald Uruz']].state == CandidateState.PROVISIONALLY_ELECTED assert result.candidates[cands['Mike Azariah']].state == CandidateState.PROVISIONALLY_ELECTED assert result.candidates[cands['Innominate']].state == CandidateState.PROVISIONALLY_ELECTED result = counter.step() assert isinstance(result, CountCompleted) yield 'Round 30'