Skip to content

Commit 43183cd

Browse files
committed
Add support for anonymous enums and enum constructor functions
1 parent 16175e4 commit 43183cd

File tree

5 files changed

+86
-6
lines changed

5 files changed

+86
-6
lines changed

CHANGELOG.rst

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Added
1414

1515
- Add additional ``Driver.executable`` property to safely access the location
1616
of the executable used by the ``Driver`` object.
17+
- Support for input and output of complex MiniZinc enumerated types, such as
18+
anonymous enumerated types or enumerated types using constructor functions.
1719

1820
Removed
1921
^^^^^^^

src/minizinc/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .model import Method, Model
1313
from .result import Result, Status
1414
from .solver import Solver
15+
from .types import AnonEnum, ConstrEnum
1516

1617
logger = logging.getLogger("minizinc")
1718

@@ -36,6 +37,8 @@
3637
__all__ = [
3738
"__version__",
3839
"default_driver",
40+
"AnonEnum",
41+
"ConstrEnum",
3942
"Driver",
4043
"Instance",
4144
"Method",

src/minizinc/json.py

+22-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from json import JSONDecoder, JSONEncoder, loads
99

1010
from .error import MiniZincWarning, error_from_stream_obj
11+
from .types import AnonEnum, ConstrEnum
1112

1213
try:
1314
import numpy
@@ -19,6 +20,10 @@ class MZNJSONEncoder(JSONEncoder):
1920
def default(self, o):
2021
if isinstance(o, Enum):
2122
return {"e": o.name}
23+
if isinstance(o, AnonEnum):
24+
return {"e": o.enumName, "i": o.value}
25+
if isinstance(o, ConstrEnum):
26+
return {"c": o.constructor, "e": o.argument}
2227
if isinstance(o, set) or isinstance(o, range):
2328
return {"set": [{"e": i.name} if isinstance(i, Enum) else i for i in o]}
2429
if numpy is not None:
@@ -37,9 +42,20 @@ def __init__(self, enum_map=None, *args, **kwargs):
3742
self.enum_map = enum_map
3843
JSONDecoder.__init__(self, object_hook=self.mzn_object_hook, *args, **kwargs)
3944

45+
def transform_enum_object(self, obj):
46+
# TODO: This probably is an enum, but could still be a record
47+
if "e" in obj:
48+
if len(obj) == 1:
49+
return self.enum_map.get(obj["e"], obj["e"])
50+
elif len(obj) == 2 and "c" in obj:
51+
return ConstrEnum(obj["c"], obj["e"])
52+
elif len(obj) == 2 and "i" in obj:
53+
return AnonEnum(obj["e"], obj["i"])
54+
return obj
55+
4056
def mzn_object_hook(self, obj):
41-
if isinstance(obj, dict) and len(obj) == 1:
42-
if "set" in obj:
57+
if isinstance(obj, dict):
58+
if len(obj) == 1 and "set" in obj:
4359
if len(obj["set"]) == 1 and isinstance(obj["set"][0], list):
4460
assert len(obj["set"][0]) == 2
4561
return range(obj["set"][0][0], obj["set"][0][1] + 1)
@@ -49,13 +65,13 @@ def mzn_object_hook(self, obj):
4965
if isinstance(item, list):
5066
assert len(item) == 2
5167
li.extend([i for i in range(item[0], item[1] + 1)])
52-
elif isinstance(item, dict) and len(item) == 1 and "e" in item:
53-
li.append(self.enum_map.get(item["e"], item["e"]))
68+
elif isinstance(item, dict):
69+
li.append(self.transform_enum_object(item))
5470
else:
5571
li.append(item)
5672
return set(li)
57-
elif "e" in obj:
58-
return self.enum_map.get(obj["e"], obj["e"])
73+
else:
74+
return self.transform_enum_object(obj)
5975
return obj
6076

6177

src/minizinc/types.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
from dataclasses import dataclass
6+
from typing import Any
7+
8+
9+
@dataclass(frozen=True)
10+
class AnonEnum:
11+
"""Representation of anonymous enumeration values in MiniZinc"""
12+
13+
enumName: str
14+
value: int
15+
16+
def __str__(self):
17+
return f"to_enum({self.enumName},{self.value})"
18+
19+
20+
@dataclass(frozen=True)
21+
class ConstrEnum:
22+
"""Representation of constructor function enumerated values in MiniZinc"""
23+
24+
constructor: str
25+
argument: Any
26+
27+
def __str__(self):
28+
return f"{self.constructor}({self.argument})"

tests/test_types.py

+31
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from minizinc import Instance
1010
from minizinc.result import Status
11+
from minizinc.types import AnonEnum, ConstrEnum
1112

1213

1314
class TestEnum(InstanceTestCase):
@@ -104,6 +105,36 @@ def test_intenum_collections(self):
104105
# TODO: assert result["arr_t"] == [TT(3), TT(2), TT(1)]
105106
assert result["set_t"] == {TT(1), TT(2)}
106107

108+
def test_constructor_enum(self):
109+
self.instance = Instance(self.solver)
110+
self.instance.add_string(
111+
"""
112+
enum T = X(1..3);
113+
var T: x;
114+
constraint x > X(1) /\\ x < X(3); % TODO: Remove for MiniZinc 2.7+
115+
"""
116+
)
117+
# TODO: Remove for MiniZinc 2.7+
118+
# self.instance["x"] = ConstrEnum("X", 2)
119+
result = self.instance.solve()
120+
assert isinstance(result["x"], ConstrEnum)
121+
assert result["x"] == ConstrEnum("X", 2)
122+
assert str(result["x"]) == "X(2)"
123+
124+
def test_anon_enum(self):
125+
self.instance = Instance(self.solver)
126+
self.instance.add_string(
127+
"""
128+
enum T = _(1..5);
129+
var T: x;
130+
"""
131+
)
132+
self.instance["x"] = AnonEnum("T", 3)
133+
result = self.instance.solve()
134+
assert isinstance(result["x"], AnonEnum)
135+
assert result["x"].value == 3
136+
assert str(result["x"]) == "to_enum(T,3)"
137+
107138

108139
class TestSets(InstanceTestCase):
109140
def test_ranges(self):

0 commit comments

Comments
 (0)