ommx-python-mip-adapter 3.0.0b1__tar.gz → 3.0.0b3__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.0b1 → ommx_python_mip_adapter-3.0.0b3}/PKG-INFO +5 -5
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/README.md +3 -3
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/ommx_python_mip_adapter/adapter.py +56 -4
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/ommx_python_mip_adapter.egg-info/PKG-INFO +5 -5
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/ommx_python_mip_adapter.egg-info/requires.txt +1 -1
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/pyproject.toml +2 -2
- ommx_python_mip_adapter-3.0.0b3/tests/test_adapter.py +207 -0
- ommx_python_mip_adapter-3.0.0b1/tests/test_adapter.py +0 -41
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/ommx_python_mip_adapter/__init__.py +0 -0
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/ommx_python_mip_adapter/exception.py +0 -0
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/ommx_python_mip_adapter/python_mip_to_ommx.py +0 -0
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/ommx_python_mip_adapter.egg-info/SOURCES.txt +0 -0
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/ommx_python_mip_adapter.egg-info/dependency_links.txt +0 -0
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/ommx_python_mip_adapter.egg-info/top_level.txt +0 -0
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/setup.cfg +0 -0
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/tests/test_constant_constraint.py +0 -0
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/tests/test_integration.py +0 -0
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/tests/test_model_to_instance.py +0 -0
- {ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/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.0b3
|
|
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.0b3
|
|
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,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Optional
|
|
3
|
+
from typing import ClassVar, Optional
|
|
4
4
|
|
|
5
5
|
import mip
|
|
6
6
|
from opentelemetry import trace
|
|
@@ -12,14 +12,66 @@ from ommx.adapter import (
|
|
|
12
12
|
UnboundedDetected,
|
|
13
13
|
NoSolutionReturned,
|
|
14
14
|
)
|
|
15
|
-
from ommx import
|
|
15
|
+
from ommx import (
|
|
16
|
+
Constraint,
|
|
17
|
+
DecisionVariable,
|
|
18
|
+
DegreeBound,
|
|
19
|
+
Equality,
|
|
20
|
+
Function,
|
|
21
|
+
Instance,
|
|
22
|
+
InstanceClass,
|
|
23
|
+
InstanceClassClause,
|
|
24
|
+
Kind,
|
|
25
|
+
PreparationPolicy,
|
|
26
|
+
Sense,
|
|
27
|
+
Solution,
|
|
28
|
+
SpecialConstraintKind,
|
|
29
|
+
SpecialConstraintPreparation,
|
|
30
|
+
State,
|
|
31
|
+
)
|
|
16
32
|
|
|
17
33
|
from .exception import OMMXPythonMIPAdapterError
|
|
18
34
|
|
|
19
35
|
_tracer = trace.get_tracer("ommx.adapter.python_mip")
|
|
20
36
|
|
|
37
|
+
_LINEAR_CONSTRAINT_DEGREE_BOUNDS = {
|
|
38
|
+
Equality.EqualToZero: DegreeBound.at_most(1),
|
|
39
|
+
Equality.LessThanOrEqualToZero: DegreeBound.at_most(1),
|
|
40
|
+
}
|
|
41
|
+
|
|
21
42
|
|
|
22
43
|
class OMMXPythonMIPAdapter(SolverAdapter):
|
|
44
|
+
INPUT_CLASS: ClassVar[InstanceClass | None] = InstanceClass(
|
|
45
|
+
[
|
|
46
|
+
InstanceClassClause(
|
|
47
|
+
label="python-mip-linear-mip",
|
|
48
|
+
allowed_variable_kinds={Kind.Binary, Kind.Integer, Kind.Continuous},
|
|
49
|
+
objective_degree_bound=DegreeBound.at_most(1),
|
|
50
|
+
regular_constraint_degree_bounds=_LINEAR_CONSTRAINT_DEGREE_BOUNDS,
|
|
51
|
+
allowed_senses={Sense.Minimize, Sense.Maximize},
|
|
52
|
+
)
|
|
53
|
+
]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def recommended_preparation_policy(cls) -> PreparationPolicy:
|
|
58
|
+
"""Recommend lowering special constraints before using Python-MIP.
|
|
59
|
+
|
|
60
|
+
Python-MIP currently accepts only regular constraints through this
|
|
61
|
+
adapter. The recommendation therefore lowers the special-constraint
|
|
62
|
+
families currently understood by OMMX. The returned policy is fresh and
|
|
63
|
+
may be edited by the caller before :meth:`Instance.prepare` is invoked.
|
|
64
|
+
"""
|
|
65
|
+
return PreparationPolicy(
|
|
66
|
+
special_constraints=SpecialConstraintPreparation.lower_special_constraints(
|
|
67
|
+
kinds={
|
|
68
|
+
SpecialConstraintKind.Indicator,
|
|
69
|
+
SpecialConstraintKind.OneHot,
|
|
70
|
+
SpecialConstraintKind.Sos1,
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
23
75
|
def __init__(
|
|
24
76
|
self,
|
|
25
77
|
ommx_instance: Instance,
|
|
@@ -37,7 +89,7 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
37
89
|
:param verbose: If True, enable Python-MIP's verbose mode
|
|
38
90
|
"""
|
|
39
91
|
with _tracer.start_as_current_span("convert"):
|
|
40
|
-
|
|
92
|
+
self.require_applicable(ommx_instance)
|
|
41
93
|
if ommx_instance.sense == Instance.MAXIMIZE:
|
|
42
94
|
sense = mip.MAXIMIZE
|
|
43
95
|
elif ommx_instance.sense == Instance.MINIMIZE:
|
|
@@ -141,7 +193,7 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
141
193
|
>>> OMMXPythonMIPAdapter.solve(instance)
|
|
142
194
|
Traceback (most recent call last):
|
|
143
195
|
...
|
|
144
|
-
ommx.
|
|
196
|
+
ommx.InfeasibleDetected: Model was infeasible
|
|
145
197
|
|
|
146
198
|
Unbounded Problem
|
|
147
199
|
|
|
@@ -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.0b3
|
|
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.0b3
|
|
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.0b3"
|
|
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.0b3, < 4.0.0",
|
|
27
27
|
"mip >= 1.17.0, < 2.0.0",
|
|
28
28
|
"cffi >= 1.15.0, < 2.0.0",
|
|
29
29
|
]
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ommx import (
|
|
4
|
+
DecisionVariable,
|
|
5
|
+
DegreeBound,
|
|
6
|
+
Equality,
|
|
7
|
+
IndicatorConstraint,
|
|
8
|
+
Instance,
|
|
9
|
+
InstanceClassMismatch,
|
|
10
|
+
Kind,
|
|
11
|
+
OneHotConstraint,
|
|
12
|
+
Sense,
|
|
13
|
+
Sos1Constraint,
|
|
14
|
+
)
|
|
15
|
+
from ommx.adapter import AdapterNotApplicableError
|
|
16
|
+
from ommx_python_mip_adapter import OMMXPythonMIPAdapter
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_declares_linear_mip_input_class() -> None:
|
|
20
|
+
input_class = OMMXPythonMIPAdapter.INPUT_CLASS
|
|
21
|
+
assert input_class is not None
|
|
22
|
+
[clause] = input_class.clauses
|
|
23
|
+
|
|
24
|
+
assert clause.label == "python-mip-linear-mip"
|
|
25
|
+
assert clause.allowed_variable_kinds == {
|
|
26
|
+
Kind.Binary,
|
|
27
|
+
Kind.Integer,
|
|
28
|
+
Kind.Continuous,
|
|
29
|
+
}
|
|
30
|
+
assert clause.objective_degree_bound == DegreeBound.at_most(1)
|
|
31
|
+
assert clause.regular_constraint_degree_bounds == {
|
|
32
|
+
Equality.EqualToZero: DegreeBound.at_most(1),
|
|
33
|
+
Equality.LessThanOrEqualToZero: DegreeBound.at_most(1),
|
|
34
|
+
}
|
|
35
|
+
assert clause.indicator_constraint_degree_bounds == {}
|
|
36
|
+
assert not clause.allows_one_hot
|
|
37
|
+
assert not clause.allows_sos1
|
|
38
|
+
assert clause.allowed_senses == {Sense.Minimize, Sense.Maximize}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.parametrize("sense", [Sense.Minimize, Sense.Maximize])
|
|
42
|
+
def test_input_class_accepts_complete_linear_mip_boundary(sense: Sense) -> None:
|
|
43
|
+
x = DecisionVariable.binary(0)
|
|
44
|
+
y = DecisionVariable.integer(1)
|
|
45
|
+
z = DecisionVariable.continuous(2)
|
|
46
|
+
instance = Instance.from_components(
|
|
47
|
+
decision_variables=[x, y, z],
|
|
48
|
+
objective=x + y + z,
|
|
49
|
+
constraints={0: x + y == 1, 1: z <= 1},
|
|
50
|
+
sense=sense,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
report = OMMXPythonMIPAdapter.check_applicability(instance)
|
|
54
|
+
|
|
55
|
+
assert report.is_applicable
|
|
56
|
+
assert report.input_membership.matching_clauses == [(0, "python-mip-linear-mip")]
|
|
57
|
+
assert report.preconditions_checked
|
|
58
|
+
assert report.precondition_violations == ()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_error_nonlinear_objective():
|
|
62
|
+
# Objective function: 2.3 * x * x (variable ID should match)
|
|
63
|
+
x = DecisionVariable.continuous(0)
|
|
64
|
+
ommx_instance = Instance.from_components(
|
|
65
|
+
decision_variables=[x],
|
|
66
|
+
objective=2.3 * x * x,
|
|
67
|
+
constraints={},
|
|
68
|
+
sense=Instance.MINIMIZE,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
with pytest.raises(AdapterNotApplicableError) as e:
|
|
72
|
+
OMMXPythonMIPAdapter(ommx_instance)
|
|
73
|
+
assert isinstance(
|
|
74
|
+
e.value.report.input_membership.clause_reports[0].mismatches[0],
|
|
75
|
+
InstanceClassMismatch.ObjectiveDegreeExceedsBound,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_error_nonlinear_constraint():
|
|
80
|
+
# Objective function: 0
|
|
81
|
+
# Constraint: 2.3 * x * x = 0
|
|
82
|
+
x = DecisionVariable.continuous(1)
|
|
83
|
+
ommx_instance = Instance.from_components(
|
|
84
|
+
decision_variables=[x],
|
|
85
|
+
objective=0.0,
|
|
86
|
+
constraints={0: 2.3 * x * x == 0},
|
|
87
|
+
sense=Instance.MINIMIZE,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
with pytest.raises(AdapterNotApplicableError) as e:
|
|
91
|
+
OMMXPythonMIPAdapter(ommx_instance)
|
|
92
|
+
assert isinstance(
|
|
93
|
+
e.value.report.input_membership.clause_reports[0].mismatches[0],
|
|
94
|
+
InstanceClassMismatch.RegularConstraintDegreeExceedsBound,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.mark.parametrize(
|
|
99
|
+
("variable", "kind"),
|
|
100
|
+
[
|
|
101
|
+
(DecisionVariable.semi_integer(0, lower=1, upper=3), Kind.SemiInteger),
|
|
102
|
+
(
|
|
103
|
+
DecisionVariable.semi_continuous(0, lower=1, upper=3),
|
|
104
|
+
Kind.SemiContinuous,
|
|
105
|
+
),
|
|
106
|
+
],
|
|
107
|
+
)
|
|
108
|
+
def test_rejects_used_unsupported_variable_kinds(
|
|
109
|
+
variable: DecisionVariable, kind: Kind
|
|
110
|
+
) -> None:
|
|
111
|
+
instance = Instance.from_components(
|
|
112
|
+
decision_variables=[variable],
|
|
113
|
+
objective=variable,
|
|
114
|
+
constraints={},
|
|
115
|
+
sense=Sense.Minimize,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
with pytest.raises(AdapterNotApplicableError) as e:
|
|
119
|
+
OMMXPythonMIPAdapter(instance)
|
|
120
|
+
|
|
121
|
+
mismatch = e.value.report.input_membership.clause_reports[0].mismatches[0]
|
|
122
|
+
assert isinstance(mismatch, InstanceClassMismatch.VariableKindNotAllowed)
|
|
123
|
+
assert mismatch.kind == kind
|
|
124
|
+
assert mismatch.variable_ids == {0}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_ignores_unused_unsupported_variable_kind() -> None:
|
|
128
|
+
used = DecisionVariable.continuous(0)
|
|
129
|
+
unused = DecisionVariable.semi_integer(1, lower=1, upper=3)
|
|
130
|
+
instance = Instance.from_components(
|
|
131
|
+
decision_variables=[used, unused],
|
|
132
|
+
objective=used,
|
|
133
|
+
constraints={},
|
|
134
|
+
sense=Sense.Minimize,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
report = OMMXPythonMIPAdapter.check_applicability(instance)
|
|
138
|
+
adapter = OMMXPythonMIPAdapter(instance)
|
|
139
|
+
|
|
140
|
+
assert report.is_applicable
|
|
141
|
+
assert adapter.instance is instance
|
|
142
|
+
assert [variable.name for variable in adapter.solver_input.vars] == ["0"]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_rejects_special_constraints_without_mutating_input() -> None:
|
|
146
|
+
x = DecisionVariable.binary(0)
|
|
147
|
+
y = DecisionVariable.continuous(1)
|
|
148
|
+
instance = Instance.from_components(
|
|
149
|
+
decision_variables=[x, y],
|
|
150
|
+
objective=x + y,
|
|
151
|
+
constraints={},
|
|
152
|
+
sense=Sense.Minimize,
|
|
153
|
+
indicator_constraints={
|
|
154
|
+
10: IndicatorConstraint(
|
|
155
|
+
indicator_variable=x,
|
|
156
|
+
function=y - 1,
|
|
157
|
+
equality=Equality.LessThanOrEqualToZero,
|
|
158
|
+
)
|
|
159
|
+
},
|
|
160
|
+
one_hot_constraints={20: OneHotConstraint(variables=[x])},
|
|
161
|
+
sos1_constraints={30: Sos1Constraint(variables=[y])},
|
|
162
|
+
)
|
|
163
|
+
before = instance.to_v2_bytes()
|
|
164
|
+
|
|
165
|
+
with pytest.raises(AdapterNotApplicableError) as e:
|
|
166
|
+
OMMXPythonMIPAdapter(instance)
|
|
167
|
+
|
|
168
|
+
mismatches = e.value.report.input_membership.clause_reports[0].mismatches
|
|
169
|
+
mismatch_types = {type(mismatch) for mismatch in mismatches}
|
|
170
|
+
assert InstanceClassMismatch.IndicatorConstraintsNotAllowed in mismatch_types
|
|
171
|
+
assert InstanceClassMismatch.OneHotConstraintsNotAllowed in mismatch_types
|
|
172
|
+
assert InstanceClassMismatch.Sos1ConstraintsNotAllowed in mismatch_types
|
|
173
|
+
assert instance.to_v2_bytes() == before
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_recommended_preparation_reaches_the_python_mip_input_class() -> None:
|
|
177
|
+
x = DecisionVariable.binary(0)
|
|
178
|
+
y = DecisionVariable.continuous(1, lower=0, upper=2)
|
|
179
|
+
instance = Instance.from_components(
|
|
180
|
+
decision_variables=[x, y],
|
|
181
|
+
objective=x + y,
|
|
182
|
+
constraints={},
|
|
183
|
+
sense=Sense.Minimize,
|
|
184
|
+
indicator_constraints={
|
|
185
|
+
10: IndicatorConstraint(
|
|
186
|
+
indicator_variable=x,
|
|
187
|
+
function=y - 1,
|
|
188
|
+
equality=Equality.LessThanOrEqualToZero,
|
|
189
|
+
)
|
|
190
|
+
},
|
|
191
|
+
one_hot_constraints={20: OneHotConstraint(variables=[x])},
|
|
192
|
+
sos1_constraints={30: Sos1Constraint(variables=[y])},
|
|
193
|
+
)
|
|
194
|
+
input_class = OMMXPythonMIPAdapter.INPUT_CLASS
|
|
195
|
+
assert input_class is not None
|
|
196
|
+
assert not input_class.contains(instance)
|
|
197
|
+
|
|
198
|
+
policy = OMMXPythonMIPAdapter.recommended_preparation_policy()
|
|
199
|
+
assert policy.special_constraints is not None
|
|
200
|
+
assert policy.sense is None
|
|
201
|
+
assert policy.integer_slack is None
|
|
202
|
+
assert policy.integer_encoding is None
|
|
203
|
+
assert policy.fixed_penalty is None
|
|
204
|
+
|
|
205
|
+
assert instance.prepare(input_class, policy) is None
|
|
206
|
+
assert input_class.contains(instance)
|
|
207
|
+
assert OMMXPythonMIPAdapter.check_applicability(instance).is_applicable
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
from ommx import Instance, DecisionVariable
|
|
3
|
-
from ommx_python_mip_adapter import OMMXPythonMIPAdapter
|
|
4
|
-
from ommx_python_mip_adapter.exception import OMMXPythonMIPAdapterError
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def test_error_nonlinear_objective():
|
|
8
|
-
# Objective function: 2.3 * x * x (variable ID should match)
|
|
9
|
-
x = DecisionVariable.continuous(0)
|
|
10
|
-
ommx_instance = Instance.from_components(
|
|
11
|
-
decision_variables=[x],
|
|
12
|
-
objective=2.3 * x * x,
|
|
13
|
-
constraints={},
|
|
14
|
-
sense=Instance.MINIMIZE,
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
with pytest.raises(OMMXPythonMIPAdapterError) as e:
|
|
18
|
-
OMMXPythonMIPAdapter(ommx_instance)
|
|
19
|
-
assert (
|
|
20
|
-
"Function with degree 2 is not supported. Only linear (degree 1) and constant (degree 0) functions are supported."
|
|
21
|
-
in str(e.value)
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def test_error_nonlinear_constraint():
|
|
26
|
-
# Objective function: 0
|
|
27
|
-
# Constraint: 2.3 * x * x = 0
|
|
28
|
-
x = DecisionVariable.continuous(1)
|
|
29
|
-
ommx_instance = Instance.from_components(
|
|
30
|
-
decision_variables=[x],
|
|
31
|
-
objective=0.0,
|
|
32
|
-
constraints={0: 2.3 * x * x == 0},
|
|
33
|
-
sense=Instance.MINIMIZE,
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
with pytest.raises(OMMXPythonMIPAdapterError) as e:
|
|
37
|
-
OMMXPythonMIPAdapter(ommx_instance)
|
|
38
|
-
assert (
|
|
39
|
-
"Function with degree 2 is not supported. Only linear (degree 1) and constant (degree 0) functions are supported."
|
|
40
|
-
in str(e.value)
|
|
41
|
-
)
|
|
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.0b1 → ommx_python_mip_adapter-3.0.0b3}/tests/test_integration.py
RENAMED
|
File without changes
|
{ommx_python_mip_adapter-3.0.0b1 → ommx_python_mip_adapter-3.0.0b3}/tests/test_model_to_instance.py
RENAMED
|
File without changes
|
|
File without changes
|