Validate constraints
This commit is contained in:
parent
15c6f3debe
commit
9f198c5e0f
@ -133,7 +133,7 @@ function handleException(ex) {
|
||||
if (py.isinstance(ex, py.pyRCV2.ties.RequireInput)) {
|
||||
// Signals we require input to break a tie
|
||||
postMessage({'type': 'require_input', 'message': ex.message});
|
||||
} else if (py.isinstance(ex, py.pyRCV2.method.base_stv.STVException)) {
|
||||
} else if (py.isinstance(ex, py.pyRCV2.exceptions.BaseRCVException)) {
|
||||
console.error(ex);
|
||||
postMessage({'type': 'stv_exception', 'message': ex.message});
|
||||
} else {
|
||||
|
@ -16,10 +16,11 @@
|
||||
|
||||
__pragma__ = lambda x: None
|
||||
|
||||
from pyRCV2.exceptions import BaseRCVException
|
||||
from pyRCV2.model import CandidateState
|
||||
from pyRCV2.safedict import SafeDict
|
||||
|
||||
class ConstraintException(Exception):
|
||||
class ConstraintException(BaseRCVException):
|
||||
pass
|
||||
|
||||
class ConstraintMatrix:
|
||||
|
20
pyRCV2/exceptions.py
Normal file
20
pyRCV2/exceptions.py
Normal file
@ -0,0 +1,20 @@
|
||||
# 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
class BaseRCVException(Exception):
|
||||
def __init__(self, message):
|
||||
Exception.__init__(self, message)
|
||||
self.message = message
|
@ -17,16 +17,15 @@
|
||||
__pragma__ = lambda x: None
|
||||
|
||||
from pyRCV2 import constraints
|
||||
from pyRCV2.exceptions import BaseRCVException
|
||||
from pyRCV2.model import BallotInCount, CandidateState, CountCard, CountCompleted, CountStepResult
|
||||
import pyRCV2.numbers
|
||||
from pyRCV2.numbers import Num
|
||||
from pyRCV2.safedict import SafeDict
|
||||
import pyRCV2.ties
|
||||
|
||||
class STVException(Exception):
|
||||
def __init__(self, message):
|
||||
Exception.__init__(self)
|
||||
self.message = message
|
||||
class STVException(BaseRCVException):
|
||||
pass
|
||||
|
||||
class BaseSTVCounter:
|
||||
"""
|
||||
@ -87,6 +86,7 @@ class BaseSTVCounter:
|
||||
self._exclusion = None # Optimisation to avoid re-collating/re-sorting ballots
|
||||
|
||||
if self.election.constraints:
|
||||
self.election.validate_constraints()
|
||||
constraints.init_matrix(self)
|
||||
constraints.stabilise_matrix(self)
|
||||
self.logs.extend(constraints.guard_or_doom(self))
|
||||
|
@ -127,6 +127,7 @@ class MeekSTVCounter(BaseSTVCounter):
|
||||
self._exclusion = None # Optimisation to avoid re-collating/re-sorting ballots
|
||||
|
||||
if self.election.constraints:
|
||||
self.election.validate_constraints()
|
||||
constraints.init_matrix(self)
|
||||
constraints.stabilise_matrix(self)
|
||||
self.logs.extend(constraints.guard_or_doom(self))
|
||||
|
@ -96,8 +96,18 @@ class Election:
|
||||
|
||||
def validate_constraints(self):
|
||||
"""Confirm that each constraint features each candidate once and only once"""
|
||||
# TODO
|
||||
return True
|
||||
from pyRCV2.constraints import ConstraintException
|
||||
|
||||
for category_name, category in self.constraints.items():
|
||||
candidates = [x for x in self.candidates]
|
||||
for group_name, group in category.items():
|
||||
for candidate in group.candidates:
|
||||
if candidate in candidates:
|
||||
candidates.remove(candidate)
|
||||
else:
|
||||
raise ConstraintException('Candidate "' + candidate.name + '" duplicated in category "' + category_name + '"')
|
||||
if len(candidates) > 0:
|
||||
raise ConstraintException('Candidate "' + candidates[0].name + '" not assigned to a group in category "' + category_name + '"')
|
||||
|
||||
class CountCard:
|
||||
"""
|
||||
|
@ -17,6 +17,7 @@
|
||||
import pyRCV2.blt
|
||||
import pyRCV2.con
|
||||
import pyRCV2.constraints
|
||||
import pyRCV2.exceptions
|
||||
import pyRCV2.model
|
||||
import pyRCV2.method, pyRCV2.method.base_stv, pyRCV2.method.gregory, pyRCV2.method.meek
|
||||
import pyRCV2.numbers
|
||||
|
1
tests/data/prsa1_invalid1.con
Normal file
1
tests/data/prsa1_invalid1.con
Normal file
@ -0,0 +1 @@
|
||||
"Gender" "Men" 0 2 2 3 4 6
|
2
tests/data/prsa1_invalid2.con
Normal file
2
tests/data/prsa1_invalid2.con
Normal file
@ -0,0 +1,2 @@
|
||||
"Gender" "Men" 0 2 2 3 4 2 6
|
||||
"Gender" "Women" 2 99 1 5 7
|
40
tests/test_constraints_validation.py
Normal file
40
tests/test_constraints_validation.py
Normal file
@ -0,0 +1,40 @@
|
||||
# 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
import pytest
|
||||
|
||||
import pyRCV2.blt, pyRCV2.con
|
||||
from pyRCV2.constraints import ConstraintException
|
||||
|
||||
def test_constraints_validation_invalid1():
|
||||
with open('tests/data/prsa1.blt', 'r') as f:
|
||||
election = pyRCV2.blt.readBLT(f.read())
|
||||
|
||||
with open('tests/data/prsa1_invalid1.con', 'r') as f:
|
||||
election.constraints = pyRCV2.con.readCON(f.read(), election)
|
||||
|
||||
with pytest.raises(ConstraintException, match='Candidate "Evans" not assigned to a group in category "Gender"'):
|
||||
election.validate_constraints()
|
||||
|
||||
def test_constraints_validation_invalid2():
|
||||
with open('tests/data/prsa1.blt', 'r') as f:
|
||||
election = pyRCV2.blt.readBLT(f.read())
|
||||
|
||||
with open('tests/data/prsa1_invalid2.con', 'r') as f:
|
||||
election.constraints = pyRCV2.con.readCON(f.read(), election)
|
||||
|
||||
with pytest.raises(ConstraintException, match='Candidate "Grey" duplicated in category "Gender"'):
|
||||
election.validate_constraints()
|
Reference in New Issue
Block a user