ommx-python-mip-adapter 2.3.0__tar.gz → 2.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/PKG-INFO +1 -1
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter/adapter.py +22 -10
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter.egg-info/PKG-INFO +1 -1
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/pyproject.toml +1 -1
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/tests/test_integration.py +82 -0
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/README.md +0 -0
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter/__init__.py +0 -0
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter/exception.py +0 -0
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter/python_mip_to_ommx.py +0 -0
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter.egg-info/SOURCES.txt +0 -0
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter.egg-info/dependency_links.txt +0 -0
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter.egg-info/requires.txt +0 -0
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter.egg-info/top_level.txt +0 -0
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/setup.cfg +0 -0
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/tests/test_adapter.py +0 -0
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/tests/test_constant_constraint.py +0 -0
- {ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/tests/test_model_to_instance.py +0 -0
{ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter/adapter.py
RENAMED
|
@@ -4,7 +4,12 @@ from typing import Optional
|
|
|
4
4
|
|
|
5
5
|
import mip
|
|
6
6
|
|
|
7
|
-
from ommx.adapter import
|
|
7
|
+
from ommx.adapter import (
|
|
8
|
+
SolverAdapter,
|
|
9
|
+
InfeasibleDetected,
|
|
10
|
+
UnboundedDetected,
|
|
11
|
+
NoSolutionReturned,
|
|
12
|
+
)
|
|
8
13
|
from ommx.v1 import Instance, Constraint, DecisionVariable, Solution, State, Function
|
|
9
14
|
|
|
10
15
|
from .exception import OMMXPythonMIPAdapterError
|
|
@@ -228,14 +233,6 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
228
233
|
42.0
|
|
229
234
|
|
|
230
235
|
"""
|
|
231
|
-
# TODO check if `optimize()` has been called
|
|
232
|
-
|
|
233
|
-
if data.status == mip.OptimizationStatus.INFEASIBLE:
|
|
234
|
-
raise InfeasibleDetected("Model was infeasible")
|
|
235
|
-
|
|
236
|
-
if data.status == mip.OptimizationStatus.UNBOUNDED:
|
|
237
|
-
raise UnboundedDetected("Model was unbounded")
|
|
238
|
-
|
|
239
236
|
state = self.decode_to_state(data)
|
|
240
237
|
solution = self.instance.evaluate(state)
|
|
241
238
|
|
|
@@ -285,12 +282,27 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
285
282
|
>>> ommx_state.entries
|
|
286
283
|
{1: 0.0}
|
|
287
284
|
"""
|
|
285
|
+
if data.status == mip.OptimizationStatus.LOADED:
|
|
286
|
+
raise OMMXPythonMIPAdapterError(
|
|
287
|
+
"The model may not be optimized. [status: LOADED]"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if data.status == mip.OptimizationStatus.INFEASIBLE:
|
|
291
|
+
raise InfeasibleDetected("Model was infeasible")
|
|
292
|
+
|
|
293
|
+
if data.status == mip.OptimizationStatus.UNBOUNDED:
|
|
294
|
+
raise UnboundedDetected("Model was unbounded")
|
|
295
|
+
|
|
296
|
+
if data.status == mip.OptimizationStatus.NO_SOLUTION_FOUND:
|
|
297
|
+
raise NoSolutionReturned("No solution was returned during the search")
|
|
298
|
+
|
|
299
|
+
# Catch all other statuses (CUTOFF, ERROR, INT_INFEASIBLE, etc.)
|
|
288
300
|
if not (
|
|
289
301
|
data.status == mip.OptimizationStatus.OPTIMAL
|
|
290
302
|
or data.status == mip.OptimizationStatus.FEASIBLE
|
|
291
303
|
):
|
|
292
304
|
raise OMMXPythonMIPAdapterError(
|
|
293
|
-
"
|
|
305
|
+
f"Optimization ended with status: {data.status.name}"
|
|
294
306
|
)
|
|
295
307
|
|
|
296
308
|
return State(
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
3
|
from ommx.v1 import Instance, DecisionVariable, Solution
|
|
4
|
+
from ommx.adapter import InfeasibleDetected, UnboundedDetected, NoSolutionReturned
|
|
4
5
|
from ommx.testing import SingleFeasibleLPGenerator, DataType
|
|
5
6
|
|
|
6
7
|
from ommx_python_mip_adapter import OMMXPythonMIPAdapter
|
|
8
|
+
from ommx_python_mip_adapter.exception import OMMXPythonMIPAdapterError
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
@pytest.mark.parametrize(
|
|
@@ -119,3 +121,83 @@ def test_relax_constraint():
|
|
|
119
121
|
solution = OMMXPythonMIPAdapter.solve(instance)
|
|
120
122
|
# x[2] is still present as part of the evaluate/decoding process but has a value of 0
|
|
121
123
|
assert [var.value for var in solution.decision_variables] == [0, 0, 0]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_integration_timelimit():
|
|
127
|
+
# KnapSack Problem
|
|
128
|
+
p = [10, 13, 18, 32, 7, 15, 12, 6, 22, 20, 19, 13, 11, 39, 10]
|
|
129
|
+
w = [11, 15, 20, 35, 10, 33, 28, 23, 11, 10, 12, 16, 17, 26, 29]
|
|
130
|
+
n = len(p)
|
|
131
|
+
x = [DecisionVariable.binary(i) for i in range(n)]
|
|
132
|
+
constraint = sum(w[i] * x[i] for i in range(n)) <= sum(w) // 2
|
|
133
|
+
assert not isinstance(constraint, bool)
|
|
134
|
+
instance = Instance.from_components(
|
|
135
|
+
decision_variables=x,
|
|
136
|
+
objective=sum(p[i] * x[i] for i in range(n)),
|
|
137
|
+
constraints=[constraint],
|
|
138
|
+
sense=Instance.MAXIMIZE,
|
|
139
|
+
)
|
|
140
|
+
adapter = OMMXPythonMIPAdapter(instance)
|
|
141
|
+
model = adapter.solver_input
|
|
142
|
+
# Set a very small time limit to force the solver to stop before finding any solution
|
|
143
|
+
model.max_seconds = 0.0001
|
|
144
|
+
model.optimize()
|
|
145
|
+
|
|
146
|
+
with pytest.raises(
|
|
147
|
+
NoSolutionReturned,
|
|
148
|
+
match="No solution was returned during the search",
|
|
149
|
+
):
|
|
150
|
+
adapter.decode(model)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_infeasible_problem():
|
|
154
|
+
# x must be >= 4, but upper bound is 3 -> infeasible
|
|
155
|
+
x = DecisionVariable.integer(0, lower=0, upper=3)
|
|
156
|
+
instance = Instance.from_components(
|
|
157
|
+
decision_variables=[x],
|
|
158
|
+
objective=x,
|
|
159
|
+
constraints=[x >= 4],
|
|
160
|
+
sense=Instance.MAXIMIZE,
|
|
161
|
+
)
|
|
162
|
+
adapter = OMMXPythonMIPAdapter(instance)
|
|
163
|
+
model = adapter.solver_input
|
|
164
|
+
model.optimize()
|
|
165
|
+
|
|
166
|
+
with pytest.raises(InfeasibleDetected):
|
|
167
|
+
adapter.decode_to_state(model)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_unbounded_problem():
|
|
171
|
+
# x has no upper bound, maximize x -> unbounded
|
|
172
|
+
x = DecisionVariable.integer(0, lower=0)
|
|
173
|
+
instance = Instance.from_components(
|
|
174
|
+
decision_variables=[x],
|
|
175
|
+
objective=x,
|
|
176
|
+
constraints=[],
|
|
177
|
+
sense=Instance.MAXIMIZE,
|
|
178
|
+
)
|
|
179
|
+
adapter = OMMXPythonMIPAdapter(instance)
|
|
180
|
+
model = adapter.solver_input
|
|
181
|
+
model.optimize()
|
|
182
|
+
|
|
183
|
+
with pytest.raises(UnboundedDetected):
|
|
184
|
+
adapter.decode_to_state(model)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_decode_before_optimize():
|
|
188
|
+
x = DecisionVariable.integer(0, lower=0, upper=5)
|
|
189
|
+
instance = Instance.from_components(
|
|
190
|
+
decision_variables=[x],
|
|
191
|
+
objective=x,
|
|
192
|
+
constraints=[],
|
|
193
|
+
sense=Instance.MINIMIZE,
|
|
194
|
+
)
|
|
195
|
+
adapter = OMMXPythonMIPAdapter(instance)
|
|
196
|
+
model = adapter.solver_input
|
|
197
|
+
# Do not call model.optimize()
|
|
198
|
+
|
|
199
|
+
with pytest.raises(
|
|
200
|
+
OMMXPythonMIPAdapterError,
|
|
201
|
+
match=r"The model may not be optimized. \[status: LOADED\]",
|
|
202
|
+
):
|
|
203
|
+
adapter.decode_to_state(model)
|
|
File without changes
|
{ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter/__init__.py
RENAMED
|
File without changes
|
{ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter/exception.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/tests/test_constant_constraint.py
RENAMED
|
File without changes
|
{ommx_python_mip_adapter-2.3.0 → ommx_python_mip_adapter-2.3.1}/tests/test_model_to_instance.py
RENAMED
|
File without changes
|