ommx-python-mip-adapter 2.2.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.
Files changed (17) hide show
  1. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/PKG-INFO +1 -1
  2. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter/adapter.py +22 -10
  3. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter.egg-info/PKG-INFO +1 -1
  4. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/pyproject.toml +1 -1
  5. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/tests/test_integration.py +82 -0
  6. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/README.md +0 -0
  7. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter/__init__.py +0 -0
  8. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter/exception.py +0 -0
  9. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter/python_mip_to_ommx.py +0 -0
  10. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter.egg-info/SOURCES.txt +0 -0
  11. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter.egg-info/dependency_links.txt +0 -0
  12. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter.egg-info/requires.txt +0 -0
  13. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/ommx_python_mip_adapter.egg-info/top_level.txt +0 -0
  14. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/setup.cfg +0 -0
  15. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/tests/test_adapter.py +0 -0
  16. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/tests/test_constant_constraint.py +0 -0
  17. {ommx_python_mip_adapter-2.2.0 → ommx_python_mip_adapter-2.3.1}/tests/test_model_to_instance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ommx_python_mip_adapter
3
- Version: 2.2.0
3
+ Version: 2.3.1
4
4
  Summary: An adapter for the Python-MIP from/to OMMX.
5
5
  Author-email: "Jij Inc." <info@j-ij.com>
6
6
  Project-URL: Repository, https://github.com/Jij-Inc/ommx
@@ -4,7 +4,12 @@ from typing import Optional
4
4
 
5
5
  import mip
6
6
 
7
- from ommx.adapter import SolverAdapter, InfeasibleDetected, UnboundedDetected
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
- " The model's `status` must be `OPTIMAL` or `FEASIBLE`."
305
+ f"Optimization ended with status: {data.status.name}"
294
306
  )
295
307
 
296
308
  return State(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ommx_python_mip_adapter
3
- Version: 2.2.0
3
+ Version: 2.3.1
4
4
  Summary: An adapter for the Python-MIP from/to OMMX.
5
5
  Author-email: "Jij Inc." <info@j-ij.com>
6
6
  Project-URL: Repository, https://github.com/Jij-Inc/ommx
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ommx_python_mip_adapter"
7
- version = "2.2.0"
7
+ version = "2.3.1"
8
8
 
9
9
  description = "An adapter for the Python-MIP from/to OMMX."
10
10
  authors = [{ name = "Jij Inc.", email = "info@j-ij.com" }]
@@ -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)