From cad1e324d922051b1232deb20592d0ff880f1f8c Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Thu, 14 Jan 2021 00:47:44 +1100 Subject: [PATCH] Add Meek STV test case --- tests/data/meekm.blt | 14 +++++++ tests/test_meekm.py | 98 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 tests/data/meekm.blt create mode 100644 tests/test_meekm.py diff --git a/tests/data/meekm.blt b/tests/data/meekm.blt new file mode 100644 index 0000000..fd5e688 --- /dev/null +++ b/tests/data/meekm.blt @@ -0,0 +1,14 @@ +4 2 +-2 +3 1 3 4 0 +4 1 3 2 0 +2 4 1 3 0 +1 2 0 +2 2 4 3 1 0 +1 3 4 2 0 +0 +"Adam" +"Basil" +"Charlotte" +"Donald" +"Title" diff --git a/tests/test_meekm.py b/tests/test_meekm.py new file mode 100644 index 0000000..f4fad4e --- /dev/null +++ b/tests/test_meekm.py @@ -0,0 +1,98 @@ +# 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 pytest + +import pyRCV2.blt +import pyRCV2.numbers +from pyRCV2.method.base_stv import STVException +from pyRCV2.method.meek import MeekSTVCounter +from pyRCV2.model import CandidateState, CountCompleted +from pyRCV2.ties import TiesBackwards + +def test_meekm(): + """Compare count of meekm.blt with model result produced by Hill et al. reference implementation""" + + pyRCV2.numbers.set_numclass(pyRCV2.numbers.Fixed) + pyRCV2.numbers.set_dps(9) + + with open('tests/data/meekm.blt', 'r') as f: + election = pyRCV2.blt.readBLT(f.read()) + + assert len(election.candidates) == 4 + + cand_a = next(c for c in election.candidates if c.name == 'Adam') + cand_b = next(c for c in election.candidates if c.name == 'Basil') + cand_c = next(c for c in election.candidates if c.name == 'Charlotte') + cand_d = next(c for c in election.candidates if c.name == 'Donald') + + assert election.withdrawn == [cand_b] + + counter = MeekSTVCounter(election, { + 'quota': 'droop_exact', + 'quota_criterion': 'gt', + 'quota_mode': 'progressive', + 'ties': [] + }) + + # Stage 1 + result = counter.reset() + assert result.comment == 'First preferences' + assert result.total == pyRCV2.numbers.Num('13') + assert result.quota == pyRCV2.numbers.Num('4') + + assert len(result.candidates.impl) == 4 + assert result.candidates[cand_a].state == CandidateState.ELECTED + assert result.candidates[cand_b].state == CandidateState.WITHDRAWN + assert result.candidates[cand_c].state == CandidateState.HOPEFUL + assert result.candidates[cand_d].state == CandidateState.HOPEFUL + + assert result.candidates[cand_a].votes == pyRCV2.numbers.Num('7') + assert result.candidates[cand_c].votes == pyRCV2.numbers.Num('1') + assert result.candidates[cand_d].votes == pyRCV2.numbers.Num('4') + assert result.exhausted.votes == pyRCV2.numbers.Num('1') + + # Stage 2 + result = counter.step() + assert result.total == pyRCV2.numbers.Num('13') + assert result.quota == pyRCV2.numbers.Num('4') + + assert result.candidates[cand_a].state == CandidateState.ELECTED + assert result.candidates[cand_b].state == CandidateState.WITHDRAWN + assert result.candidates[cand_c].state == CandidateState.HOPEFUL + assert result.candidates[cand_d].state == CandidateState.HOPEFUL + + assert result.candidates[cand_a].votes == pyRCV2.numbers.Num('4') + assert result.candidates[cand_c].votes == pyRCV2.numbers.Num('4') + assert result.candidates[cand_d].votes == pyRCV2.numbers.Num('4') + assert result.exhausted.votes == pyRCV2.numbers.Num('1') + + # Stage 3 + # FIXME: This fails in Javascript (rounding errors?) + with pytest.raises(STVException) as exc_info: + result = counter.step() + assert exc_info.value.message == 'Unable to resolve tie' + + # Retry further stages + counter.options['ties'] = [TiesBackwards(counter)] + result = counter.step() + while not isinstance(result, CountCompleted): + result = counter.step() + + assert result.candidates[cand_a].state == CandidateState.ELECTED + assert result.candidates[cand_b].state == CandidateState.WITHDRAWN + assert result.candidates[cand_c].state == CandidateState.EXCLUDED # Charlotte loses the tie if broken backwards + assert result.candidates[cand_d].state == CandidateState.ELECTED