ommx-python-mip-adapter 3.0.0b3__tar.gz → 3.0.0b5__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.0b3 → ommx_python_mip_adapter-3.0.0b5}/PKG-INFO +2 -2
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/ommx_python_mip_adapter/adapter.py +60 -20
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/ommx_python_mip_adapter.egg-info/PKG-INFO +2 -2
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/ommx_python_mip_adapter.egg-info/requires.txt +1 -1
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/pyproject.toml +2 -2
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/tests/test_adapter.py +41 -20
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/tests/test_integration.py +35 -1
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/README.md +0 -0
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/ommx_python_mip_adapter/__init__.py +0 -0
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/ommx_python_mip_adapter/exception.py +0 -0
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/ommx_python_mip_adapter/python_mip_to_ommx.py +0 -0
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/ommx_python_mip_adapter.egg-info/SOURCES.txt +0 -0
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/ommx_python_mip_adapter.egg-info/dependency_links.txt +0 -0
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/ommx_python_mip_adapter.egg-info/top_level.txt +0 -0
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/setup.cfg +0 -0
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/tests/test_constant_constraint.py +0 -0
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/tests/test_model_to_instance.py +0 -0
- {ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/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.0b5
|
|
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.0b5
|
|
19
19
|
Requires-Dist: mip<2.0.0,>=1.17.0
|
|
20
20
|
Requires-Dist: cffi<2.0.0,>=1.15.0
|
|
21
21
|
|
|
@@ -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
|
|
@@ -15,13 +16,13 @@ from ommx.adapter import (
|
|
|
15
16
|
from ommx import (
|
|
16
17
|
Constraint,
|
|
17
18
|
DecisionVariable,
|
|
18
|
-
DegreeBound,
|
|
19
19
|
Equality,
|
|
20
20
|
Function,
|
|
21
21
|
Instance,
|
|
22
22
|
InstanceClass,
|
|
23
23
|
InstanceClassClause,
|
|
24
24
|
Kind,
|
|
25
|
+
PolynomialRequirement,
|
|
25
26
|
PreparationPolicy,
|
|
26
27
|
Sense,
|
|
27
28
|
Solution,
|
|
@@ -34,20 +35,22 @@ from .exception import OMMXPythonMIPAdapterError
|
|
|
34
35
|
|
|
35
36
|
_tracer = trace.get_tracer("ommx.adapter.python_mip")
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
Equality.EqualToZero:
|
|
39
|
-
Equality.LessThanOrEqualToZero:
|
|
38
|
+
_LINEAR_CONSTRAINT_POLYNOMIAL_REQUIREMENTS = {
|
|
39
|
+
Equality.EqualToZero: PolynomialRequirement.at_most(1),
|
|
40
|
+
Equality.LessThanOrEqualToZero: PolynomialRequirement.at_most(1),
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
class OMMXPythonMIPAdapter(SolverAdapter):
|
|
44
|
-
INPUT_CLASS: ClassVar[InstanceClass
|
|
45
|
+
INPUT_CLASS: ClassVar[InstanceClass] = InstanceClass(
|
|
45
46
|
[
|
|
46
47
|
InstanceClassClause(
|
|
47
48
|
label="python-mip-linear-mip",
|
|
48
49
|
allowed_variable_kinds={Kind.Binary, Kind.Integer, Kind.Continuous},
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
objective_polynomial_requirement=PolynomialRequirement.at_most(1),
|
|
51
|
+
regular_constraint_polynomial_requirements=(
|
|
52
|
+
_LINEAR_CONSTRAINT_POLYNOMIAL_REQUIREMENTS
|
|
53
|
+
),
|
|
51
54
|
allowed_senses={Sense.Minimize, Sense.Maximize},
|
|
52
55
|
)
|
|
53
56
|
]
|
|
@@ -59,8 +62,9 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
59
62
|
|
|
60
63
|
Python-MIP currently accepts only regular constraints through this
|
|
61
64
|
adapter. The recommendation therefore lowers the special-constraint
|
|
62
|
-
families currently understood by OMMX. The
|
|
63
|
-
|
|
65
|
+
families currently understood by OMMX. The easy API applies a fresh
|
|
66
|
+
policy to an isolated copy; callers may instead edit and apply one
|
|
67
|
+
before invoking :meth:`solve_without_preparation`.
|
|
64
68
|
"""
|
|
65
69
|
return PreparationPolicy(
|
|
66
70
|
special_constraints=SpecialConstraintPreparation.lower_special_constraints(
|
|
@@ -131,7 +135,10 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
131
135
|
"""
|
|
132
136
|
Solve the given ommx.Instance using Python-MIP, returning an ommx.Solution.
|
|
133
137
|
|
|
134
|
-
|
|
138
|
+
The input instance is not modified. An isolated copy is prepared with
|
|
139
|
+
the recommended Python-MIP policy before preparation-free execution.
|
|
140
|
+
|
|
141
|
+
:param ommx_instance: The ommx.Instance to prepare and solve.
|
|
135
142
|
: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>`.
|
|
136
143
|
:param verbose: If True, enable Python-MIP's verbose mode
|
|
137
144
|
|
|
@@ -236,9 +243,28 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
236
243
|
1.0
|
|
237
244
|
|
|
238
245
|
"""
|
|
239
|
-
|
|
246
|
+
prepared = copy.copy(ommx_instance)
|
|
247
|
+
prepared.prepare(cls.INPUT_CLASS, cls.recommended_preparation_policy())
|
|
248
|
+
return cls.solve_without_preparation(
|
|
249
|
+
prepared,
|
|
250
|
+
relax=relax,
|
|
251
|
+
verbose=verbose,
|
|
252
|
+
diagnostics=diagnostics,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
@classmethod
|
|
256
|
+
def solve_without_preparation(
|
|
257
|
+
cls,
|
|
258
|
+
ommx_instance: Instance,
|
|
259
|
+
relax: bool = False,
|
|
260
|
+
verbose: bool = False,
|
|
261
|
+
*,
|
|
262
|
+
diagnostics: DiagnosticsSink | None = None,
|
|
263
|
+
) -> Solution:
|
|
264
|
+
"""Solve an exact Python-MIP Adapter input without preparing it."""
|
|
240
265
|
with _tracer.start_as_current_span("solve") as span:
|
|
241
266
|
span.set_attribute("adapter", f"{cls.__module__}.{cls.__qualname__}")
|
|
267
|
+
_ = diagnostics
|
|
242
268
|
adapter = cls(ommx_instance, relax=relax, verbose=verbose)
|
|
243
269
|
model = adapter.solver_input
|
|
244
270
|
with _tracer.start_as_current_span("call"):
|
|
@@ -270,6 +296,10 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
270
296
|
`solver_input`, you must set `solution.relaxation` yourself if you
|
|
271
297
|
care about this value.
|
|
272
298
|
|
|
299
|
+
Backend optimality is mapped through the instance's output-objective
|
|
300
|
+
semantics. It remains unspecified when active-formulation optimality
|
|
301
|
+
does not transport to that objective.
|
|
302
|
+
|
|
273
303
|
Examples
|
|
274
304
|
=========
|
|
275
305
|
|
|
@@ -303,17 +333,22 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
303
333
|
state = self.decode_to_state(data)
|
|
304
334
|
solution = self.instance.evaluate(state)
|
|
305
335
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
336
|
+
# Duals certify the active formulation and need an explicit mapping
|
|
337
|
+
# before they can be attached to projected output semantics.
|
|
338
|
+
if self.instance.output_objective is None:
|
|
339
|
+
dual_variables = {}
|
|
340
|
+
for constraint in data.constrs:
|
|
341
|
+
pi = constraint.pi
|
|
342
|
+
if pi is not None:
|
|
343
|
+
id = int(constraint.name)
|
|
344
|
+
dual_variables[id] = pi
|
|
345
|
+
for constraint_id, dual_value in dual_variables.items():
|
|
346
|
+
solution.set_dual_variable(constraint_id, dual_value)
|
|
314
347
|
|
|
315
348
|
if data.status == mip.OptimizationStatus.OPTIMAL:
|
|
316
|
-
solution.optimality =
|
|
349
|
+
solution.optimality = self.instance.map_active_optimality(
|
|
350
|
+
Solution.OPTIMAL
|
|
351
|
+
)
|
|
317
352
|
|
|
318
353
|
if self._relax:
|
|
319
354
|
solution.relaxation = Solution.LP_RELAXED
|
|
@@ -414,6 +449,11 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
414
449
|
Translate ommx.Function to `mip.LinExpr` or `float`.
|
|
415
450
|
"""
|
|
416
451
|
degree = f.degree()
|
|
452
|
+
if degree is None:
|
|
453
|
+
raise OMMXPythonMIPAdapterError(
|
|
454
|
+
"Non-polynomial functions are not supported. "
|
|
455
|
+
"Only linear (degree 1) and constant (degree 0) functions are supported."
|
|
456
|
+
)
|
|
417
457
|
constant = f.constant_term
|
|
418
458
|
if degree > 1:
|
|
419
459
|
raise OMMXPythonMIPAdapterError(
|
|
@@ -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.0b5
|
|
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.0b5
|
|
19
19
|
Requires-Dist: mip<2.0.0,>=1.17.0
|
|
20
20
|
Requires-Dist: cffi<2.0.0,>=1.15.0
|
|
21
21
|
|
|
@@ -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.0b5"
|
|
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.0b5, < 4.0.0",
|
|
27
27
|
"mip >= 1.17.0, < 2.0.0",
|
|
28
28
|
"cffi >= 1.15.0, < 2.0.0",
|
|
29
29
|
]
|
|
@@ -2,13 +2,14 @@ import pytest
|
|
|
2
2
|
|
|
3
3
|
from ommx import (
|
|
4
4
|
DecisionVariable,
|
|
5
|
-
DegreeBound,
|
|
6
5
|
Equality,
|
|
6
|
+
Function,
|
|
7
7
|
IndicatorConstraint,
|
|
8
8
|
Instance,
|
|
9
9
|
InstanceClassMismatch,
|
|
10
10
|
Kind,
|
|
11
11
|
OneHotConstraint,
|
|
12
|
+
PolynomialRequirement,
|
|
12
13
|
Sense,
|
|
13
14
|
Sos1Constraint,
|
|
14
15
|
)
|
|
@@ -18,7 +19,6 @@ from ommx_python_mip_adapter import OMMXPythonMIPAdapter
|
|
|
18
19
|
|
|
19
20
|
def test_declares_linear_mip_input_class() -> None:
|
|
20
21
|
input_class = OMMXPythonMIPAdapter.INPUT_CLASS
|
|
21
|
-
assert input_class is not None
|
|
22
22
|
[clause] = input_class.clauses
|
|
23
23
|
|
|
24
24
|
assert clause.label == "python-mip-linear-mip"
|
|
@@ -27,12 +27,12 @@ def test_declares_linear_mip_input_class() -> None:
|
|
|
27
27
|
Kind.Integer,
|
|
28
28
|
Kind.Continuous,
|
|
29
29
|
}
|
|
30
|
-
assert clause.
|
|
31
|
-
assert clause.
|
|
32
|
-
Equality.EqualToZero:
|
|
33
|
-
Equality.LessThanOrEqualToZero:
|
|
30
|
+
assert clause.objective_polynomial_requirement == PolynomialRequirement.at_most(1)
|
|
31
|
+
assert clause.regular_constraint_polynomial_requirements == {
|
|
32
|
+
Equality.EqualToZero: PolynomialRequirement.at_most(1),
|
|
33
|
+
Equality.LessThanOrEqualToZero: PolynomialRequirement.at_most(1),
|
|
34
34
|
}
|
|
35
|
-
assert clause.
|
|
35
|
+
assert clause.indicator_body_polynomial_requirements == {}
|
|
36
36
|
assert not clause.allows_one_hot
|
|
37
37
|
assert not clause.allows_sos1
|
|
38
38
|
assert clause.allowed_senses == {Sense.Minimize, Sense.Maximize}
|
|
@@ -52,10 +52,8 @@ def test_input_class_accepts_complete_linear_mip_boundary(sense: Sense) -> None:
|
|
|
52
52
|
|
|
53
53
|
report = OMMXPythonMIPAdapter.check_applicability(instance)
|
|
54
54
|
|
|
55
|
-
assert report.
|
|
56
|
-
assert report.
|
|
57
|
-
assert report.preconditions_checked
|
|
58
|
-
assert report.precondition_violations == ()
|
|
55
|
+
assert report.is_member
|
|
56
|
+
assert report.matching_clauses == [(0, "python-mip-linear-mip")]
|
|
59
57
|
|
|
60
58
|
|
|
61
59
|
def test_error_nonlinear_objective():
|
|
@@ -71,11 +69,27 @@ def test_error_nonlinear_objective():
|
|
|
71
69
|
with pytest.raises(AdapterNotApplicableError) as e:
|
|
72
70
|
OMMXPythonMIPAdapter(ommx_instance)
|
|
73
71
|
assert isinstance(
|
|
74
|
-
e.value.report.
|
|
72
|
+
e.value.report.clause_reports[0].mismatches[0],
|
|
75
73
|
InstanceClassMismatch.ObjectiveDegreeExceedsBound,
|
|
76
74
|
)
|
|
77
75
|
|
|
78
76
|
|
|
77
|
+
def test_error_non_polynomial_objective() -> None:
|
|
78
|
+
x = DecisionVariable.continuous(0)
|
|
79
|
+
instance = Instance.from_components(
|
|
80
|
+
decision_variables=[x],
|
|
81
|
+
objective=abs(Function(x)),
|
|
82
|
+
constraints={},
|
|
83
|
+
sense=Sense.Minimize,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
with pytest.raises(AdapterNotApplicableError) as error:
|
|
87
|
+
OMMXPythonMIPAdapter(instance)
|
|
88
|
+
|
|
89
|
+
[mismatch] = error.value.report.clause_reports[0].mismatches
|
|
90
|
+
assert isinstance(mismatch, InstanceClassMismatch.ObjectiveFunctionNotPolynomial)
|
|
91
|
+
|
|
92
|
+
|
|
79
93
|
def test_error_nonlinear_constraint():
|
|
80
94
|
# Objective function: 0
|
|
81
95
|
# Constraint: 2.3 * x * x = 0
|
|
@@ -90,7 +104,7 @@ def test_error_nonlinear_constraint():
|
|
|
90
104
|
with pytest.raises(AdapterNotApplicableError) as e:
|
|
91
105
|
OMMXPythonMIPAdapter(ommx_instance)
|
|
92
106
|
assert isinstance(
|
|
93
|
-
e.value.report.
|
|
107
|
+
e.value.report.clause_reports[0].mismatches[0],
|
|
94
108
|
InstanceClassMismatch.RegularConstraintDegreeExceedsBound,
|
|
95
109
|
)
|
|
96
110
|
|
|
@@ -118,7 +132,7 @@ def test_rejects_used_unsupported_variable_kinds(
|
|
|
118
132
|
with pytest.raises(AdapterNotApplicableError) as e:
|
|
119
133
|
OMMXPythonMIPAdapter(instance)
|
|
120
134
|
|
|
121
|
-
mismatch = e.value.report.
|
|
135
|
+
mismatch = e.value.report.clause_reports[0].mismatches[0]
|
|
122
136
|
assert isinstance(mismatch, InstanceClassMismatch.VariableKindNotAllowed)
|
|
123
137
|
assert mismatch.kind == kind
|
|
124
138
|
assert mismatch.variable_ids == {0}
|
|
@@ -137,14 +151,14 @@ def test_ignores_unused_unsupported_variable_kind() -> None:
|
|
|
137
151
|
report = OMMXPythonMIPAdapter.check_applicability(instance)
|
|
138
152
|
adapter = OMMXPythonMIPAdapter(instance)
|
|
139
153
|
|
|
140
|
-
assert report.
|
|
154
|
+
assert report.is_member
|
|
141
155
|
assert adapter.instance is instance
|
|
142
156
|
assert [variable.name for variable in adapter.solver_input.vars] == ["0"]
|
|
143
157
|
|
|
144
158
|
|
|
145
159
|
def test_rejects_special_constraints_without_mutating_input() -> None:
|
|
146
160
|
x = DecisionVariable.binary(0)
|
|
147
|
-
y = DecisionVariable.continuous(1)
|
|
161
|
+
y = DecisionVariable.continuous(1, lower=0, upper=2)
|
|
148
162
|
instance = Instance.from_components(
|
|
149
163
|
decision_variables=[x, y],
|
|
150
164
|
objective=x + y,
|
|
@@ -165,13 +179,21 @@ def test_rejects_special_constraints_without_mutating_input() -> None:
|
|
|
165
179
|
with pytest.raises(AdapterNotApplicableError) as e:
|
|
166
180
|
OMMXPythonMIPAdapter(instance)
|
|
167
181
|
|
|
168
|
-
mismatches = e.value.report.
|
|
182
|
+
mismatches = e.value.report.clause_reports[0].mismatches
|
|
169
183
|
mismatch_types = {type(mismatch) for mismatch in mismatches}
|
|
170
184
|
assert InstanceClassMismatch.IndicatorConstraintsNotAllowed in mismatch_types
|
|
171
185
|
assert InstanceClassMismatch.OneHotConstraintsNotAllowed in mismatch_types
|
|
172
186
|
assert InstanceClassMismatch.Sos1ConstraintsNotAllowed in mismatch_types
|
|
173
187
|
assert instance.to_v2_bytes() == before
|
|
174
188
|
|
|
189
|
+
with pytest.raises(AdapterNotApplicableError):
|
|
190
|
+
OMMXPythonMIPAdapter.solve_without_preparation(instance)
|
|
191
|
+
assert instance.to_v2_bytes() == before
|
|
192
|
+
|
|
193
|
+
solution = OMMXPythonMIPAdapter.solve(instance)
|
|
194
|
+
assert solution.feasible
|
|
195
|
+
assert instance.to_v2_bytes() == before
|
|
196
|
+
|
|
175
197
|
|
|
176
198
|
def test_recommended_preparation_reaches_the_python_mip_input_class() -> None:
|
|
177
199
|
x = DecisionVariable.binary(0)
|
|
@@ -192,16 +214,15 @@ def test_recommended_preparation_reaches_the_python_mip_input_class() -> None:
|
|
|
192
214
|
sos1_constraints={30: Sos1Constraint(variables=[y])},
|
|
193
215
|
)
|
|
194
216
|
input_class = OMMXPythonMIPAdapter.INPUT_CLASS
|
|
195
|
-
assert input_class is not None
|
|
196
217
|
assert not input_class.contains(instance)
|
|
197
218
|
|
|
198
219
|
policy = OMMXPythonMIPAdapter.recommended_preparation_policy()
|
|
199
220
|
assert policy.special_constraints is not None
|
|
200
|
-
assert policy.
|
|
221
|
+
assert policy.objective is None
|
|
201
222
|
assert policy.integer_slack is None
|
|
202
223
|
assert policy.integer_encoding is None
|
|
203
224
|
assert policy.fixed_penalty is None
|
|
204
225
|
|
|
205
226
|
assert instance.prepare(input_class, policy) is None
|
|
206
227
|
assert input_class.contains(instance)
|
|
207
|
-
assert OMMXPythonMIPAdapter.check_applicability(instance).
|
|
228
|
+
assert OMMXPythonMIPAdapter.check_applicability(instance).is_member
|
{ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/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
|
|
File without changes
|
{ommx_python_mip_adapter-3.0.0b3 → ommx_python_mip_adapter-3.0.0b5}/tests/test_model_to_instance.py
RENAMED
|
File without changes
|
|
File without changes
|