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)) {
|
if (py.isinstance(ex, py.pyRCV2.ties.RequireInput)) {
|
||||||
// Signals we require input to break a tie
|
// Signals we require input to break a tie
|
||||||
postMessage({'type': 'require_input', 'message': ex.message});
|
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);
|
console.error(ex);
|
||||||
postMessage({'type': 'stv_exception', 'message': ex.message});
|
postMessage({'type': 'stv_exception', 'message': ex.message});
|
||||||
} else {
|
} else {
|
||||||
|
@ -16,10 +16,11 @@
|
|||||||
|
|
||||||
__pragma__ = lambda x: None
|
__pragma__ = lambda x: None
|
||||||
|
|
||||||
|
from pyRCV2.exceptions import BaseRCVException
|
||||||
from pyRCV2.model import CandidateState
|
from pyRCV2.model import CandidateState
|
||||||
from pyRCV2.safedict import SafeDict
|
from pyRCV2.safedict import SafeDict
|
||||||
|
|
||||||
class ConstraintException(Exception):
|
class ConstraintException(BaseRCVException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class ConstraintMatrix:
|
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
|
__pragma__ = lambda x: None
|
||||||
|
|
||||||
from pyRCV2 import constraints
|
from pyRCV2 import constraints
|
||||||
|
from pyRCV2.exceptions import BaseRCVException
|
||||||
from pyRCV2.model import BallotInCount, CandidateState, CountCard, CountCompleted, CountStepResult
|
from pyRCV2.model import BallotInCount, CandidateState, CountCard, CountCompleted, CountStepResult
|
||||||
import pyRCV2.numbers
|
import pyRCV2.numbers
|
||||||
from pyRCV2.numbers import Num
|
from pyRCV2.numbers import Num
|
||||||
from pyRCV2.safedict import SafeDict
|
from pyRCV2.safedict import SafeDict
|
||||||
import pyRCV2.ties
|
import pyRCV2.ties
|
||||||
|
|
||||||
class STVException(Exception):
|
class STVException(BaseRCVException):
|
||||||
def __init__(self, message):
|
pass
|
||||||
Exception.__init__(self)
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
class BaseSTVCounter:
|
class BaseSTVCounter:
|
||||||
"""
|
"""
|
||||||
@ -87,6 +86,7 @@ class BaseSTVCounter:
|
|||||||
self._exclusion = None # Optimisation to avoid re-collating/re-sorting ballots
|
self._exclusion = None # Optimisation to avoid re-collating/re-sorting ballots
|
||||||
|
|
||||||
if self.election.constraints:
|
if self.election.constraints:
|
||||||
|
self.election.validate_constraints()
|
||||||
constraints.init_matrix(self)
|
constraints.init_matrix(self)
|
||||||
constraints.stabilise_matrix(self)
|
constraints.stabilise_matrix(self)
|
||||||
self.logs.extend(constraints.guard_or_doom(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
|
self._exclusion = None # Optimisation to avoid re-collating/re-sorting ballots
|
||||||
|
|
||||||
if self.election.constraints:
|
if self.election.constraints:
|
||||||
|
self.election.validate_constraints()
|
||||||
constraints.init_matrix(self)
|
constraints.init_matrix(self)
|
||||||
constraints.stabilise_matrix(self)
|
constraints.stabilise_matrix(self)
|
||||||
self.logs.extend(constraints.guard_or_doom(self))
|
self.logs.extend(constraints.guard_or_doom(self))
|
||||||
|
@ -96,8 +96,18 @@ class Election:
|
|||||||
|
|
||||||
def validate_constraints(self):
|
def validate_constraints(self):
|
||||||
"""Confirm that each constraint features each candidate once and only once"""
|
"""Confirm that each constraint features each candidate once and only once"""
|
||||||
# TODO
|
from pyRCV2.constraints import ConstraintException
|
||||||
return True
|
|
||||||
|
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:
|
class CountCard:
|
||||||
"""
|
"""
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import pyRCV2.blt
|
import pyRCV2.blt
|
||||||
import pyRCV2.con
|
import pyRCV2.con
|
||||||
import pyRCV2.constraints
|
import pyRCV2.constraints
|
||||||
|
import pyRCV2.exceptions
|
||||||
import pyRCV2.model
|
import pyRCV2.model
|
||||||
import pyRCV2.method, pyRCV2.method.base_stv, pyRCV2.method.gregory, pyRCV2.method.meek
|
import pyRCV2.method, pyRCV2.method.base_stv, pyRCV2.method.gregory, pyRCV2.method.meek
|
||||||
import pyRCV2.numbers
|
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