Skip to content

Commit

Permalink
Fix problem with empty ballots when reading pb files
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon-Rey committed Jul 2, 2024
1 parent 21eec05 commit b1143eb
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 17 deletions.
2 changes: 1 addition & 1 deletion pabutools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__author__ = "Simon Rey, Grzegorz Pierczyński, Markus Utke and Piotr Skowron"
__email__ = "[email protected]"
__version__ = "1.1.7"
__version__ = "1.1.8"
3 changes: 2 additions & 1 deletion pabutools/analysis/priceability.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,10 +390,11 @@ def priceable(
else:
status = mip_model.optimize(max_seconds=max_seconds)

if status == OptimizationStatus.INF_OR_UNBD:
if hasattr(OptimizationStatus, "INF_OR_UNBD") and status == OptimizationStatus.INF_OR_UNBD:
# https://support.gurobi.com/hc/en-us/articles/4402704428177-How-do-I-resolve-the-error-Model-is-infeasible-or-unbounded
# https://github.com/coin-or/python-mip/blob/1.15.0/mip/gurobi.py#L777
# https://github.com/coin-or/python-mip/blob/1.16-pre/mip/gurobi.py#L778
# This is not part of old python-mip version, hence the first test
#
mip_model.solver.set_int_param("DualReductions", 0)
mip_model.reset()
Expand Down
21 changes: 12 additions & 9 deletions pabutools/election/pabulib.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,24 +96,27 @@ def parse_pabulib_from_string(file_content: str) -> tuple[Instance, Profile]:
if vote_type == "approval":
ballot = ApprovalBallot()
for project_name in ballot_meta["vote"].split(","):
ballot.add(instance.get_project(project_name))
if project_name:
ballot.add(instance.get_project(project_name))
ballot_meta.pop("vote")
elif vote_type in ["scoring", "cumulative"]:
if vote_type == "scoring":
ballot = CardinalBallot()
else:
ballot = CumulativeBallot()
points = ballot_meta["points"].split(",")
for index, project_name in enumerate(ballot_meta["vote"].split(",")):
ballot[instance.get_project(project_name)] = str_as_frac(
points[index].strip()
)
ballot_meta.pop("vote")
ballot_meta.pop("points")
if "points" in ballot_meta: # if not, the ballot should be empty
points = ballot_meta["points"].split(",")
for index, project_name in enumerate(ballot_meta["vote"].split(",")):
ballot[instance.get_project(project_name)] = str_as_frac(
points[index].strip()
)
ballot_meta.pop("vote")
ballot_meta.pop("points")
elif vote_type == "ordinal":
ballot = OrdinalBallot()
for project_name in ballot_meta["vote"].split(","):
ballot.append(instance.get_project(project_name))
if project_name:
ballot.append(instance.get_project(project_name))
ballot_meta.pop("vote")
else:
raise NotImplementedError(
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"

[project]
name = "pabutools"
version = "1.1.7"
version = "1.1.8"
description = "Implementation of all the tools necessary to explore and analyse participatory budgeting elections"
authors = [
{ name = "Simon Rey", email = "[email protected]" },
Expand Down
79 changes: 74 additions & 5 deletions tests/test_pabulib.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from unittest import TestCase

from pabutools.election import OrdinalBallot
from pabutools.election import OrdinalBallot, ApprovalBallot
from pabutools.election.pabulib import (
parse_pabulib,
parse_pabulib_from_string,
Expand Down Expand Up @@ -93,6 +93,71 @@ def test_approval(self):

os.remove("test.pb")

def test_empty_voters(self):
contents = """META
key;value
description;Auto-filled description
country;Auto-filled country
unit;Auto-filled unit
instance;Auto-filled instance
num_projects;20
num_votes;1000
budget;600000
vote_type;approval
rule;Auto-filled rule
PROJECTS
project_id;cost
p0;124200
p1;128400
p2;135600
p3;128400
p4;316929
p5;51599
p6;66000
p7;55200
p8;55800
p9;54000
p10;66600
p11;61199
p12;51000
p13;59400
p14;59400
p15;58800
p16;63000
p17;64800
p18;66000
p19;57600
VOTES
voter_id;vote
0;p12,p19,p1,p14
1;p11,p1,p13
2;p16
3;p12,p4,p9
4;p0
5;p12,p18,p16,p2
6;
7;p6,p3,p13,p17,p19
8;p5,p10
9;
10;p0,p2
11;p11,p6,p2
12;p16,p19
13;
14;p10,p2"""

with open("test.pb", "w", encoding="utf-8") as f:
f.write(contents)

for instance, profile in [
parse_pabulib("test.pb"),
parse_pabulib_from_string(contents),
]:
assert len(instance) == 20
assert len(profile) == 15
assert profile[6] == ApprovalBallot()

os.remove("test.pb")

def test_cumulative(self):
contents = """META
key;value
Expand Down Expand Up @@ -156,7 +221,9 @@ def test_cumulative(self):
8;14,8,2,4;3,2,1,1
9;1,6,7,9,10,20,30;1,1,1,1,1,1,1
10;6,7;3,3
11;2,14,8;3,3,1"""
11;2,14,8;3,3,1
12;
13;6,7;3,3"""

with open("test.pb", "w", encoding="utf-8") as f:
f.write(contents)
Expand All @@ -167,7 +234,7 @@ def test_cumulative(self):
]:
assert len(instance) == 30
assert instance.budget_limit == 1000000
assert len(profile) == 12
assert len(profile) == 14
assert len(profile[0]) == 4
assert len(profile[4]) == 5

Expand Down Expand Up @@ -247,7 +314,8 @@ def test_scoring(self):
8;14,8,2,4;3,2,1,1
9;1,6,7,9,10,20,30;1,1,1,1,1,1,1
10;6,7;3,3
11;2,14,8;3,3,1"""
11;2,14,8;3,3,1
12;"""

with open("test.pb", "w", encoding="utf-8") as f:
f.write(contents)
Expand Down Expand Up @@ -452,6 +520,7 @@ def test_ordinal(self):
42;95,75,111;paper;PODGÓRZE DUCHACKIE
43;84,101,83;internet;PODGÓRZE
44;4,36,41;internet;KROWODRZA
45;;internet;KROWODRZA
"""

with open("test.pb", "w", encoding="utf-8") as f:
Expand All @@ -463,7 +532,7 @@ def test_ordinal(self):
]:
assert len(instance) == 123
assert instance.budget_limit == 8000100
assert len(profile) == 45
assert len(profile) == 46
assert len(profile[44]) == 3
assert len(profile[4]) == 3

Expand Down

0 comments on commit b1143eb

Please sign in to comment.