ommx-python-mip-adapter 3.0.0b2__tar.gz → 3.0.0b4__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-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/PKG-INFO +5 -5
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/README.md +3 -3
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/ommx_python_mip_adapter/adapter.py +67 -12
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/ommx_python_mip_adapter.egg-info/PKG-INFO +5 -5
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/ommx_python_mip_adapter.egg-info/requires.txt +1 -1
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/pyproject.toml +2 -2
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/tests/test_adapter.py +49 -11
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/tests/test_integration.py +35 -1
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/ommx_python_mip_adapter/__init__.py +0 -0
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/ommx_python_mip_adapter/exception.py +0 -0
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/ommx_python_mip_adapter/python_mip_to_ommx.py +0 -0
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/ommx_python_mip_adapter.egg-info/SOURCES.txt +0 -0
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/ommx_python_mip_adapter.egg-info/dependency_links.txt +0 -0
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/ommx_python_mip_adapter.egg-info/top_level.txt +0 -0
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/setup.cfg +0 -0
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/tests/test_constant_constraint.py +0 -0
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/tests/test_model_to_instance.py +0 -0
- {ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/tests/test_tracing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ommx_python_mip_adapter
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.0b4
|
|
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
|
|
@@ -15,7 +15,7 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
16
|
Requires-Python: >=3.10
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
|
-
Requires-Dist: ommx<4.0.0,>=3.0.
|
|
18
|
+
Requires-Dist: ommx<4.0.0,>=3.0.0b4
|
|
19
19
|
Requires-Dist: mip<2.0.0,>=1.17.0
|
|
20
20
|
Requires-Dist: cffi<2.0.0,>=1.15.0
|
|
21
21
|
|
|
@@ -90,9 +90,9 @@ import mip
|
|
|
90
90
|
import ommx_python_mip_adapter as adapter
|
|
91
91
|
|
|
92
92
|
model = mip.Model()
|
|
93
|
-
x1=model.add_var(name="1", var_type=mip.INTEGER, lb=0, ub=5)
|
|
94
|
-
x2=model.add_var(name="2", var_type=mip.CONTINUOUS, lb=0, ub=5)
|
|
95
|
-
model.objective = -
|
|
93
|
+
x1 = model.add_var(name="1", var_type=mip.INTEGER, lb=0, ub=5)
|
|
94
|
+
x2 = model.add_var(name="2", var_type=mip.CONTINUOUS, lb=0, ub=5)
|
|
95
|
+
model.objective = -x1 - 2 * x2
|
|
96
96
|
model.add_constr(x1 + x2 - 6 <= 0)
|
|
97
97
|
|
|
98
98
|
ommx_instance = adapter.model_to_instance(model)
|
|
@@ -69,9 +69,9 @@ import mip
|
|
|
69
69
|
import ommx_python_mip_adapter as adapter
|
|
70
70
|
|
|
71
71
|
model = mip.Model()
|
|
72
|
-
x1=model.add_var(name="1", var_type=mip.INTEGER, lb=0, ub=5)
|
|
73
|
-
x2=model.add_var(name="2", var_type=mip.CONTINUOUS, lb=0, ub=5)
|
|
74
|
-
model.objective = -
|
|
72
|
+
x1 = model.add_var(name="1", var_type=mip.INTEGER, lb=0, ub=5)
|
|
73
|
+
x2 = model.add_var(name="2", var_type=mip.CONTINUOUS, lb=0, ub=5)
|
|
74
|
+
model.objective = -x1 - 2 * x2
|
|
75
75
|
model.add_constr(x1 + x2 - 6 <= 0)
|
|
76
76
|
|
|
77
77
|
ommx_instance = adapter.model_to_instance(model)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import copy
|
|
3
4
|
from typing import ClassVar, Optional
|
|
4
5
|
|
|
5
6
|
import mip
|
|
@@ -22,8 +23,11 @@ from ommx import (
|
|
|
22
23
|
InstanceClass,
|
|
23
24
|
InstanceClassClause,
|
|
24
25
|
Kind,
|
|
26
|
+
PreparationPolicy,
|
|
25
27
|
Sense,
|
|
26
28
|
Solution,
|
|
29
|
+
SpecialConstraintKind,
|
|
30
|
+
SpecialConstraintPreparation,
|
|
27
31
|
State,
|
|
28
32
|
)
|
|
29
33
|
|
|
@@ -38,7 +42,7 @@ _LINEAR_CONSTRAINT_DEGREE_BOUNDS = {
|
|
|
38
42
|
|
|
39
43
|
|
|
40
44
|
class OMMXPythonMIPAdapter(SolverAdapter):
|
|
41
|
-
INPUT_CLASS: ClassVar[InstanceClass
|
|
45
|
+
INPUT_CLASS: ClassVar[InstanceClass] = InstanceClass(
|
|
42
46
|
[
|
|
43
47
|
InstanceClassClause(
|
|
44
48
|
label="python-mip-linear-mip",
|
|
@@ -50,6 +54,26 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
50
54
|
]
|
|
51
55
|
)
|
|
52
56
|
|
|
57
|
+
@classmethod
|
|
58
|
+
def recommended_preparation_policy(cls) -> PreparationPolicy:
|
|
59
|
+
"""Recommend lowering special constraints before using Python-MIP.
|
|
60
|
+
|
|
61
|
+
Python-MIP currently accepts only regular constraints through this
|
|
62
|
+
adapter. The recommendation therefore lowers the special-constraint
|
|
63
|
+
families currently understood by OMMX. The easy API applies a fresh
|
|
64
|
+
policy to an isolated copy; callers may instead edit and apply one
|
|
65
|
+
before invoking :meth:`solve_without_preparation`.
|
|
66
|
+
"""
|
|
67
|
+
return PreparationPolicy(
|
|
68
|
+
special_constraints=SpecialConstraintPreparation.lower_special_constraints(
|
|
69
|
+
kinds={
|
|
70
|
+
SpecialConstraintKind.Indicator,
|
|
71
|
+
SpecialConstraintKind.OneHot,
|
|
72
|
+
SpecialConstraintKind.Sos1,
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
53
77
|
def __init__(
|
|
54
78
|
self,
|
|
55
79
|
ommx_instance: Instance,
|
|
@@ -109,7 +133,10 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
109
133
|
"""
|
|
110
134
|
Solve the given ommx.Instance using Python-MIP, returning an ommx.Solution.
|
|
111
135
|
|
|
112
|
-
|
|
136
|
+
The input instance is not modified. An isolated copy is prepared with
|
|
137
|
+
the recommended Python-MIP policy before preparation-free execution.
|
|
138
|
+
|
|
139
|
+
:param ommx_instance: The ommx.Instance to prepare and solve.
|
|
113
140
|
:param relax: If True, relax all integer variables to continuous variables by using the `relax` parameter in Python-MIP's `Model.optimize() <https://docs.python-mip.com/en/latest/classes.html#mip.Model.optimize>`.
|
|
114
141
|
:param verbose: If True, enable Python-MIP's verbose mode
|
|
115
142
|
|
|
@@ -214,9 +241,28 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
214
241
|
1.0
|
|
215
242
|
|
|
216
243
|
"""
|
|
217
|
-
|
|
244
|
+
prepared = copy.copy(ommx_instance)
|
|
245
|
+
prepared.prepare(cls.INPUT_CLASS, cls.recommended_preparation_policy())
|
|
246
|
+
return cls.solve_without_preparation(
|
|
247
|
+
prepared,
|
|
248
|
+
relax=relax,
|
|
249
|
+
verbose=verbose,
|
|
250
|
+
diagnostics=diagnostics,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
@classmethod
|
|
254
|
+
def solve_without_preparation(
|
|
255
|
+
cls,
|
|
256
|
+
ommx_instance: Instance,
|
|
257
|
+
relax: bool = False,
|
|
258
|
+
verbose: bool = False,
|
|
259
|
+
*,
|
|
260
|
+
diagnostics: DiagnosticsSink | None = None,
|
|
261
|
+
) -> Solution:
|
|
262
|
+
"""Solve an exact Python-MIP Adapter input without preparing it."""
|
|
218
263
|
with _tracer.start_as_current_span("solve") as span:
|
|
219
264
|
span.set_attribute("adapter", f"{cls.__module__}.{cls.__qualname__}")
|
|
265
|
+
_ = diagnostics
|
|
220
266
|
adapter = cls(ommx_instance, relax=relax, verbose=verbose)
|
|
221
267
|
model = adapter.solver_input
|
|
222
268
|
with _tracer.start_as_current_span("call"):
|
|
@@ -248,6 +294,10 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
248
294
|
`solver_input`, you must set `solution.relaxation` yourself if you
|
|
249
295
|
care about this value.
|
|
250
296
|
|
|
297
|
+
Backend optimality is mapped through the instance's output-objective
|
|
298
|
+
semantics. It remains unspecified when active-formulation optimality
|
|
299
|
+
does not transport to that objective.
|
|
300
|
+
|
|
251
301
|
Examples
|
|
252
302
|
=========
|
|
253
303
|
|
|
@@ -281,17 +331,22 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
281
331
|
state = self.decode_to_state(data)
|
|
282
332
|
solution = self.instance.evaluate(state)
|
|
283
333
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
334
|
+
# Duals certify the active formulation and need an explicit mapping
|
|
335
|
+
# before they can be attached to projected output semantics.
|
|
336
|
+
if self.instance.output_objective is None:
|
|
337
|
+
dual_variables = {}
|
|
338
|
+
for constraint in data.constrs:
|
|
339
|
+
pi = constraint.pi
|
|
340
|
+
if pi is not None:
|
|
341
|
+
id = int(constraint.name)
|
|
342
|
+
dual_variables[id] = pi
|
|
343
|
+
for constraint_id, dual_value in dual_variables.items():
|
|
344
|
+
solution.set_dual_variable(constraint_id, dual_value)
|
|
292
345
|
|
|
293
346
|
if data.status == mip.OptimizationStatus.OPTIMAL:
|
|
294
|
-
solution.optimality =
|
|
347
|
+
solution.optimality = self.instance.map_active_optimality(
|
|
348
|
+
Solution.OPTIMAL
|
|
349
|
+
)
|
|
295
350
|
|
|
296
351
|
if self._relax:
|
|
297
352
|
solution.relaxation = Solution.LP_RELAXED
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ommx_python_mip_adapter
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.0b4
|
|
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
|
|
@@ -15,7 +15,7 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
16
|
Requires-Python: >=3.10
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
|
-
Requires-Dist: ommx<4.0.0,>=3.0.
|
|
18
|
+
Requires-Dist: ommx<4.0.0,>=3.0.0b4
|
|
19
19
|
Requires-Dist: mip<2.0.0,>=1.17.0
|
|
20
20
|
Requires-Dist: cffi<2.0.0,>=1.15.0
|
|
21
21
|
|
|
@@ -90,9 +90,9 @@ import mip
|
|
|
90
90
|
import ommx_python_mip_adapter as adapter
|
|
91
91
|
|
|
92
92
|
model = mip.Model()
|
|
93
|
-
x1=model.add_var(name="1", var_type=mip.INTEGER, lb=0, ub=5)
|
|
94
|
-
x2=model.add_var(name="2", var_type=mip.CONTINUOUS, lb=0, ub=5)
|
|
95
|
-
model.objective = -
|
|
93
|
+
x1 = model.add_var(name="1", var_type=mip.INTEGER, lb=0, ub=5)
|
|
94
|
+
x2 = model.add_var(name="2", var_type=mip.CONTINUOUS, lb=0, ub=5)
|
|
95
|
+
model.objective = -x1 - 2 * x2
|
|
96
96
|
model.add_constr(x1 + x2 - 6 <= 0)
|
|
97
97
|
|
|
98
98
|
ommx_instance = adapter.model_to_instance(model)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ommx_python_mip_adapter"
|
|
7
|
-
version = "3.0.
|
|
7
|
+
version = "3.0.0b4"
|
|
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" }]
|
|
@@ -23,7 +23,7 @@ classifiers = [
|
|
|
23
23
|
]
|
|
24
24
|
|
|
25
25
|
dependencies = [
|
|
26
|
-
"ommx >= 3.0.
|
|
26
|
+
"ommx >= 3.0.0b4, < 4.0.0",
|
|
27
27
|
"mip >= 1.17.0, < 2.0.0",
|
|
28
28
|
"cffi >= 1.15.0, < 2.0.0",
|
|
29
29
|
]
|
|
@@ -18,7 +18,6 @@ from ommx_python_mip_adapter import OMMXPythonMIPAdapter
|
|
|
18
18
|
|
|
19
19
|
def test_declares_linear_mip_input_class() -> None:
|
|
20
20
|
input_class = OMMXPythonMIPAdapter.INPUT_CLASS
|
|
21
|
-
assert input_class is not None
|
|
22
21
|
[clause] = input_class.clauses
|
|
23
22
|
|
|
24
23
|
assert clause.label == "python-mip-linear-mip"
|
|
@@ -52,10 +51,8 @@ def test_input_class_accepts_complete_linear_mip_boundary(sense: Sense) -> None:
|
|
|
52
51
|
|
|
53
52
|
report = OMMXPythonMIPAdapter.check_applicability(instance)
|
|
54
53
|
|
|
55
|
-
assert report.
|
|
56
|
-
assert report.
|
|
57
|
-
assert report.preconditions_checked
|
|
58
|
-
assert report.precondition_violations == ()
|
|
54
|
+
assert report.is_member
|
|
55
|
+
assert report.matching_clauses == [(0, "python-mip-linear-mip")]
|
|
59
56
|
|
|
60
57
|
|
|
61
58
|
def test_error_nonlinear_objective():
|
|
@@ -71,7 +68,7 @@ def test_error_nonlinear_objective():
|
|
|
71
68
|
with pytest.raises(AdapterNotApplicableError) as e:
|
|
72
69
|
OMMXPythonMIPAdapter(ommx_instance)
|
|
73
70
|
assert isinstance(
|
|
74
|
-
e.value.report.
|
|
71
|
+
e.value.report.clause_reports[0].mismatches[0],
|
|
75
72
|
InstanceClassMismatch.ObjectiveDegreeExceedsBound,
|
|
76
73
|
)
|
|
77
74
|
|
|
@@ -90,7 +87,7 @@ def test_error_nonlinear_constraint():
|
|
|
90
87
|
with pytest.raises(AdapterNotApplicableError) as e:
|
|
91
88
|
OMMXPythonMIPAdapter(ommx_instance)
|
|
92
89
|
assert isinstance(
|
|
93
|
-
e.value.report.
|
|
90
|
+
e.value.report.clause_reports[0].mismatches[0],
|
|
94
91
|
InstanceClassMismatch.RegularConstraintDegreeExceedsBound,
|
|
95
92
|
)
|
|
96
93
|
|
|
@@ -118,7 +115,7 @@ def test_rejects_used_unsupported_variable_kinds(
|
|
|
118
115
|
with pytest.raises(AdapterNotApplicableError) as e:
|
|
119
116
|
OMMXPythonMIPAdapter(instance)
|
|
120
117
|
|
|
121
|
-
mismatch = e.value.report.
|
|
118
|
+
mismatch = e.value.report.clause_reports[0].mismatches[0]
|
|
122
119
|
assert isinstance(mismatch, InstanceClassMismatch.VariableKindNotAllowed)
|
|
123
120
|
assert mismatch.kind == kind
|
|
124
121
|
assert mismatch.variable_ids == {0}
|
|
@@ -137,14 +134,14 @@ def test_ignores_unused_unsupported_variable_kind() -> None:
|
|
|
137
134
|
report = OMMXPythonMIPAdapter.check_applicability(instance)
|
|
138
135
|
adapter = OMMXPythonMIPAdapter(instance)
|
|
139
136
|
|
|
140
|
-
assert report.
|
|
137
|
+
assert report.is_member
|
|
141
138
|
assert adapter.instance is instance
|
|
142
139
|
assert [variable.name for variable in adapter.solver_input.vars] == ["0"]
|
|
143
140
|
|
|
144
141
|
|
|
145
142
|
def test_rejects_special_constraints_without_mutating_input() -> None:
|
|
146
143
|
x = DecisionVariable.binary(0)
|
|
147
|
-
y = DecisionVariable.continuous(1)
|
|
144
|
+
y = DecisionVariable.continuous(1, lower=0, upper=2)
|
|
148
145
|
instance = Instance.from_components(
|
|
149
146
|
decision_variables=[x, y],
|
|
150
147
|
objective=x + y,
|
|
@@ -165,9 +162,50 @@ def test_rejects_special_constraints_without_mutating_input() -> None:
|
|
|
165
162
|
with pytest.raises(AdapterNotApplicableError) as e:
|
|
166
163
|
OMMXPythonMIPAdapter(instance)
|
|
167
164
|
|
|
168
|
-
mismatches = e.value.report.
|
|
165
|
+
mismatches = e.value.report.clause_reports[0].mismatches
|
|
169
166
|
mismatch_types = {type(mismatch) for mismatch in mismatches}
|
|
170
167
|
assert InstanceClassMismatch.IndicatorConstraintsNotAllowed in mismatch_types
|
|
171
168
|
assert InstanceClassMismatch.OneHotConstraintsNotAllowed in mismatch_types
|
|
172
169
|
assert InstanceClassMismatch.Sos1ConstraintsNotAllowed in mismatch_types
|
|
173
170
|
assert instance.to_v2_bytes() == before
|
|
171
|
+
|
|
172
|
+
with pytest.raises(AdapterNotApplicableError):
|
|
173
|
+
OMMXPythonMIPAdapter.solve_without_preparation(instance)
|
|
174
|
+
assert instance.to_v2_bytes() == before
|
|
175
|
+
|
|
176
|
+
solution = OMMXPythonMIPAdapter.solve(instance)
|
|
177
|
+
assert solution.feasible
|
|
178
|
+
assert instance.to_v2_bytes() == before
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_recommended_preparation_reaches_the_python_mip_input_class() -> None:
|
|
182
|
+
x = DecisionVariable.binary(0)
|
|
183
|
+
y = DecisionVariable.continuous(1, lower=0, upper=2)
|
|
184
|
+
instance = Instance.from_components(
|
|
185
|
+
decision_variables=[x, y],
|
|
186
|
+
objective=x + y,
|
|
187
|
+
constraints={},
|
|
188
|
+
sense=Sense.Minimize,
|
|
189
|
+
indicator_constraints={
|
|
190
|
+
10: IndicatorConstraint(
|
|
191
|
+
indicator_variable=x,
|
|
192
|
+
function=y - 1,
|
|
193
|
+
equality=Equality.LessThanOrEqualToZero,
|
|
194
|
+
)
|
|
195
|
+
},
|
|
196
|
+
one_hot_constraints={20: OneHotConstraint(variables=[x])},
|
|
197
|
+
sos1_constraints={30: Sos1Constraint(variables=[y])},
|
|
198
|
+
)
|
|
199
|
+
input_class = OMMXPythonMIPAdapter.INPUT_CLASS
|
|
200
|
+
assert not input_class.contains(instance)
|
|
201
|
+
|
|
202
|
+
policy = OMMXPythonMIPAdapter.recommended_preparation_policy()
|
|
203
|
+
assert policy.special_constraints is not None
|
|
204
|
+
assert policy.objective is None
|
|
205
|
+
assert policy.integer_slack is None
|
|
206
|
+
assert policy.integer_encoding is None
|
|
207
|
+
assert policy.fixed_penalty is None
|
|
208
|
+
|
|
209
|
+
assert instance.prepare(input_class, policy) is None
|
|
210
|
+
assert input_class.contains(instance)
|
|
211
|
+
assert OMMXPythonMIPAdapter.check_applicability(instance).is_member
|
{ommx_python_mip_adapter-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/tests/test_integration.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
-
from ommx import Instance,
|
|
3
|
+
from ommx import DecisionVariable, Instance, Optimality, Sense, Solution
|
|
4
4
|
from ommx.adapter import InfeasibleDetected, UnboundedDetected, NoSolutionReturned
|
|
5
5
|
from ommx.testing import SingleFeasibleLPGenerator, DataType
|
|
6
6
|
|
|
@@ -75,6 +75,40 @@ def test_solution_optimality():
|
|
|
75
75
|
assert solution.optimality == Solution.OPTIMAL
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
def test_solution_optimality_is_not_transported_through_fixed_penalty():
|
|
79
|
+
x = DecisionVariable.binary(0)
|
|
80
|
+
instance = Instance.from_components(
|
|
81
|
+
decision_variables=[x],
|
|
82
|
+
objective=x,
|
|
83
|
+
constraints={7: x == 1},
|
|
84
|
+
sense=Instance.MINIMIZE,
|
|
85
|
+
)
|
|
86
|
+
instance.to_qubo(uniform_penalty_weight=0.0)
|
|
87
|
+
|
|
88
|
+
solution = OMMXPythonMIPAdapter.solve(instance)
|
|
89
|
+
|
|
90
|
+
assert not solution.feasible
|
|
91
|
+
assert solution.optimality == Optimality.Unspecified
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_output_objective_omits_active_formulation_dual_values():
|
|
95
|
+
x = DecisionVariable.continuous(0, lower=0)
|
|
96
|
+
instance = Instance.from_components(
|
|
97
|
+
decision_variables=[x],
|
|
98
|
+
objective=x,
|
|
99
|
+
constraints={0: x <= 1},
|
|
100
|
+
sense=Sense.Maximize,
|
|
101
|
+
)
|
|
102
|
+
assert instance.convert_active_objective(Sense.Minimize)
|
|
103
|
+
|
|
104
|
+
adapter = OMMXPythonMIPAdapter(instance)
|
|
105
|
+
model = adapter.solver_input
|
|
106
|
+
model.optimize()
|
|
107
|
+
solution = adapter.decode(model)
|
|
108
|
+
|
|
109
|
+
assert solution.get_dual_variable(0) is None
|
|
110
|
+
|
|
111
|
+
|
|
78
112
|
def test_partial_evaluate():
|
|
79
113
|
x = [DecisionVariable.binary(i, name="x", subscripts=[i]) for i in range(3)]
|
|
80
114
|
instance = Instance.from_components(
|
|
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-3.0.0b2 → ommx_python_mip_adapter-3.0.0b4}/tests/test_model_to_instance.py
RENAMED
|
File without changes
|
|
File without changes
|