ommx-python-mip-adapter 1.9.4__tar.gz → 2.0.0__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-1.9.4 → ommx_python_mip_adapter-2.0.0}/PKG-INFO +2 -2
- {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter/adapter.py +33 -37
- {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter/python_mip_to_ommx.py +24 -21
- {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter.egg-info/PKG-INFO +2 -2
- ommx_python_mip_adapter-2.0.0/ommx_python_mip_adapter.egg-info/requires.txt +2 -0
- {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/pyproject.toml +2 -2
- ommx_python_mip_adapter-2.0.0/tests/test_adapter.py +41 -0
- {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/tests/test_constant_constraint.py +1 -1
- {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/tests/test_integration.py +2 -3
- {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/tests/test_model_to_instance.py +56 -77
- ommx_python_mip_adapter-1.9.4/ommx_python_mip_adapter.egg-info/requires.txt +0 -2
- ommx_python_mip_adapter-1.9.4/tests/test_adapter.py +0 -90
- {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/README.md +0 -0
- {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter/__init__.py +0 -0
- {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter/exception.py +0 -0
- {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter.egg-info/SOURCES.txt +0 -0
- {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter.egg-info/dependency_links.txt +0 -0
- {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter.egg-info/top_level.txt +0 -0
- {ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ommx_python_mip_adapter
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
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.9
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
|
-
Requires-Dist: ommx<
|
|
18
|
+
Requires-Dist: ommx<3.0.0,>=2.0.0b3
|
|
19
19
|
Requires-Dist: mip<2.0.0,>=1.15.0
|
|
20
20
|
|
|
21
21
|
OMMX adapter for Python-MIP
|
{ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter/adapter.py
RENAMED
|
@@ -5,9 +5,7 @@ from typing import Optional
|
|
|
5
5
|
import mip
|
|
6
6
|
|
|
7
7
|
from ommx.adapter import SolverAdapter, InfeasibleDetected, UnboundedDetected
|
|
8
|
-
from ommx.v1 import Instance, Constraint, DecisionVariable, Solution, State,
|
|
9
|
-
from ommx.v1.function_pb2 import Function
|
|
10
|
-
from ommx.v1.solution_pb2 import Relaxation
|
|
8
|
+
from ommx.v1 import Instance, Constraint, DecisionVariable, Solution, State, Function
|
|
11
9
|
|
|
12
10
|
from .exception import OMMXPythonMIPAdapterError
|
|
13
11
|
|
|
@@ -29,14 +27,12 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
29
27
|
:param solver: Passes a specific solver to the Python-MIP model.
|
|
30
28
|
:param verbose: If True, enable Python-MIP's verbose mode
|
|
31
29
|
"""
|
|
32
|
-
if ommx_instance.
|
|
30
|
+
if ommx_instance.sense == Instance.MAXIMIZE:
|
|
33
31
|
sense = mip.MAXIMIZE
|
|
34
|
-
elif ommx_instance.
|
|
32
|
+
elif ommx_instance.sense == Instance.MINIMIZE:
|
|
35
33
|
sense = mip.MINIMIZE
|
|
36
34
|
else:
|
|
37
|
-
raise OMMXPythonMIPAdapterError(
|
|
38
|
-
f"Unsupported sense: {ommx_instance.raw.sense}"
|
|
39
|
-
)
|
|
35
|
+
raise OMMXPythonMIPAdapterError(f"Unsupported sense: {ommx_instance.sense}")
|
|
40
36
|
self.instance = ommx_instance
|
|
41
37
|
self.model = mip.Model(
|
|
42
38
|
sense=sense,
|
|
@@ -80,7 +76,6 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
80
76
|
.. doctest::
|
|
81
77
|
|
|
82
78
|
>>> from ommx.v1 import Instance, DecisionVariable
|
|
83
|
-
>>> from ommx.v1.solution_pb2 import Optimality
|
|
84
79
|
>>> from ommx_python_mip_adapter import OMMXPythonMIPAdapter
|
|
85
80
|
|
|
86
81
|
>>> p = [10, 13, 18, 32, 7, 15]
|
|
@@ -89,7 +84,7 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
89
84
|
>>> instance = Instance.from_components(
|
|
90
85
|
... decision_variables=x,
|
|
91
86
|
... objective=sum(p[i] * x[i] for i in range(6)),
|
|
92
|
-
... constraints=[sum(w[i] * x[i] for i in range(6)) <= 47],
|
|
87
|
+
... constraints=[(sum(w[i] * x[i] for i in range(6)) <= 47).set_id(0)],
|
|
93
88
|
... sense=Instance.MAXIMIZE,
|
|
94
89
|
... )
|
|
95
90
|
|
|
@@ -103,14 +98,14 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
103
98
|
[(0, 1.0), (1, 0.0), (2, 0.0), (3, 1.0), (4, 0.0), (5, 0.0)]
|
|
104
99
|
>>> solution.feasible
|
|
105
100
|
True
|
|
106
|
-
>>> assert solution.optimality ==
|
|
101
|
+
>>> assert solution.optimality == Solution.OPTIMAL
|
|
107
102
|
|
|
108
103
|
p[0] + p[3] = 42
|
|
109
104
|
w[0] + w[3] = 46 <= 47
|
|
110
105
|
|
|
111
106
|
>>> solution.objective
|
|
112
107
|
42.0
|
|
113
|
-
>>> solution.
|
|
108
|
+
>>> solution.get_constraint_value(0)
|
|
114
109
|
-1.0
|
|
115
110
|
|
|
116
111
|
Infeasible Problem
|
|
@@ -165,12 +160,12 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
165
160
|
>>> instance = Instance.from_components(
|
|
166
161
|
... decision_variables=[x, y],
|
|
167
162
|
... objective=x + y,
|
|
168
|
-
... constraints=[x + y <= 1],
|
|
163
|
+
... constraints=[(x + y <= 1).set_id(0)],
|
|
169
164
|
... sense=Instance.MAXIMIZE,
|
|
170
165
|
... )
|
|
171
166
|
|
|
172
167
|
>>> solution = OMMXPythonMIPAdapter.solve(instance)
|
|
173
|
-
>>> solution.
|
|
168
|
+
>>> solution.get_dual_variable(0)
|
|
174
169
|
1.0
|
|
175
170
|
|
|
176
171
|
"""
|
|
@@ -250,16 +245,14 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
250
245
|
if pi is not None:
|
|
251
246
|
id = int(constraint.name)
|
|
252
247
|
dual_variables[id] = pi
|
|
253
|
-
for
|
|
254
|
-
|
|
255
|
-
if id in dual_variables:
|
|
256
|
-
constraint.dual_variable = dual_variables[id]
|
|
248
|
+
for constraint_id, dual_value in dual_variables.items():
|
|
249
|
+
solution.set_dual_variable(constraint_id, dual_value)
|
|
257
250
|
|
|
258
251
|
if data.status == mip.OptimizationStatus.OPTIMAL:
|
|
259
|
-
solution.
|
|
252
|
+
solution.optimality = Solution.OPTIMAL
|
|
260
253
|
|
|
261
254
|
if self._relax:
|
|
262
|
-
solution.
|
|
255
|
+
solution.relaxation = Solution.LP_RELAXED
|
|
263
256
|
return solution
|
|
264
257
|
|
|
265
258
|
def decode_to_state(self, data: mip.Model) -> State:
|
|
@@ -303,12 +296,12 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
303
296
|
return State(
|
|
304
297
|
entries={
|
|
305
298
|
var.id: data.var_by_name(str(var.id)).x # type: ignore
|
|
306
|
-
for var in self.instance.
|
|
299
|
+
for var in self.instance.decision_variables
|
|
307
300
|
}
|
|
308
301
|
)
|
|
309
302
|
|
|
310
303
|
def _set_decision_variables(self):
|
|
311
|
-
for var in self.instance.
|
|
304
|
+
for var in self.instance.decision_variables:
|
|
312
305
|
if var.kind == DecisionVariable.BINARY:
|
|
313
306
|
self.model.add_var(
|
|
314
307
|
name=str(var.id),
|
|
@@ -341,26 +334,29 @@ class OMMXPythonMIPAdapter(SolverAdapter):
|
|
|
341
334
|
"""
|
|
342
335
|
Translate ommx.v1.Function to `mip.LinExpr` or `float`.
|
|
343
336
|
"""
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
337
|
+
degree = f.degree()
|
|
338
|
+
constant = f.constant_term
|
|
339
|
+
if degree > 1:
|
|
340
|
+
raise OMMXPythonMIPAdapterError(
|
|
341
|
+
f"Function with degree {degree} is not supported. "
|
|
342
|
+
"Only linear (degree 1) and constant (degree 0) functions are supported."
|
|
343
|
+
)
|
|
344
|
+
if degree == 0:
|
|
345
|
+
return mip.LinExpr(const=constant) # type: ignore
|
|
346
|
+
assert degree == 1
|
|
347
|
+
return (
|
|
348
|
+
mip.xsum(
|
|
349
|
+
coeff * self.model.vars[str(var_id)] # type: ignore
|
|
350
|
+
for var_id, coeff in f.linear_terms.items()
|
|
351
|
+
)
|
|
352
|
+
+ constant
|
|
357
353
|
)
|
|
358
354
|
|
|
359
355
|
def _set_objective(self):
|
|
360
|
-
self.model.objective = self._as_lin_expr(self.instance.
|
|
356
|
+
self.model.objective = self._as_lin_expr(self.instance.objective)
|
|
361
357
|
|
|
362
358
|
def _set_constraints(self):
|
|
363
|
-
for constraint in self.instance.
|
|
359
|
+
for constraint in self.instance.constraints:
|
|
364
360
|
lin_expr = self._as_lin_expr(constraint.function)
|
|
365
361
|
if constraint.equality == Constraint.EQUAL_TO_ZERO:
|
|
366
362
|
constr_expr = lin_expr == 0
|
|
@@ -4,10 +4,7 @@ from typing import final
|
|
|
4
4
|
import mip
|
|
5
5
|
|
|
6
6
|
from mip.exceptions import ParameterNotAvailable
|
|
7
|
-
from ommx.v1
|
|
8
|
-
from ommx.v1.function_pb2 import Function
|
|
9
|
-
from ommx.v1.linear_pb2 import Linear
|
|
10
|
-
from ommx.v1 import Instance, DecisionVariable
|
|
7
|
+
from ommx.v1 import Instance, DecisionVariable, Constraint, Function, Linear
|
|
11
8
|
|
|
12
9
|
from .exception import OMMXPythonMIPAdapterError
|
|
13
10
|
|
|
@@ -27,35 +24,41 @@ class OMMXInstanceBuilder:
|
|
|
27
24
|
decision_variables = []
|
|
28
25
|
for var in self.model.vars:
|
|
29
26
|
if var.var_type == mip.BINARY:
|
|
30
|
-
|
|
27
|
+
decision_variables.append(
|
|
28
|
+
DecisionVariable.binary(var.idx, name=var.name)
|
|
29
|
+
)
|
|
31
30
|
elif var.var_type == mip.INTEGER:
|
|
32
|
-
|
|
31
|
+
decision_variables.append(
|
|
32
|
+
DecisionVariable.integer(
|
|
33
|
+
var.idx, lower=var.lb, upper=var.ub, name=var.name
|
|
34
|
+
)
|
|
35
|
+
)
|
|
33
36
|
elif var.var_type == mip.CONTINUOUS:
|
|
34
|
-
|
|
37
|
+
decision_variables.append(
|
|
38
|
+
DecisionVariable.continuous(
|
|
39
|
+
var.idx, lower=var.lb, upper=var.ub, name=var.name
|
|
40
|
+
)
|
|
41
|
+
)
|
|
35
42
|
else:
|
|
36
43
|
raise OMMXPythonMIPAdapterError(
|
|
37
44
|
f"Not supported variable type. "
|
|
38
45
|
f"idx: {var.idx} name: {var.name}, type: {var.var_type}"
|
|
39
46
|
)
|
|
40
|
-
decision_variables.append(
|
|
41
|
-
DecisionVariable.of_type(
|
|
42
|
-
kind, var.idx, lower=var.lb, upper=var.ub, name=var.name
|
|
43
|
-
)
|
|
44
|
-
)
|
|
45
47
|
return decision_variables
|
|
46
48
|
|
|
47
49
|
def as_ommx_function(self, lin_expr: mip.LinExpr) -> Function:
|
|
48
|
-
terms =
|
|
49
|
-
|
|
50
|
+
terms = {
|
|
51
|
+
var.idx: coefficient # type: ignore
|
|
50
52
|
for var, coefficient in lin_expr.expr.items()
|
|
51
|
-
|
|
53
|
+
}
|
|
52
54
|
constant: float = lin_expr.const # type: ignore
|
|
53
55
|
|
|
54
56
|
# If the terms are empty, the function is a constant.
|
|
55
57
|
if len(terms) == 0:
|
|
56
|
-
return Function(constant
|
|
58
|
+
return Function(constant)
|
|
57
59
|
else:
|
|
58
|
-
|
|
60
|
+
linear = Linear(terms=terms, constant=constant) # type: ignore
|
|
61
|
+
return Function(linear)
|
|
59
62
|
|
|
60
63
|
def objective(self) -> Function:
|
|
61
64
|
# In Python-MIP, it is allowed not to set the objective function.
|
|
@@ -65,7 +68,7 @@ class OMMXInstanceBuilder:
|
|
|
65
68
|
try:
|
|
66
69
|
objective = self.model.objective
|
|
67
70
|
except ParameterNotAvailable:
|
|
68
|
-
return Function(
|
|
71
|
+
return Function(0.0)
|
|
69
72
|
|
|
70
73
|
return self.as_ommx_function(objective)
|
|
71
74
|
|
|
@@ -80,15 +83,15 @@ class OMMXInstanceBuilder:
|
|
|
80
83
|
if lin_expr.sense == "=":
|
|
81
84
|
constraint = Constraint(
|
|
82
85
|
id=id,
|
|
83
|
-
equality=Equality.EQUALITY_EQUAL_TO_ZERO,
|
|
84
86
|
function=self.as_ommx_function(lin_expr),
|
|
87
|
+
equality=Constraint.EQUAL_TO_ZERO,
|
|
85
88
|
name=name,
|
|
86
89
|
)
|
|
87
90
|
elif lin_expr.sense == "<":
|
|
88
91
|
constraint = Constraint(
|
|
89
92
|
id=id,
|
|
90
|
-
equality=Equality.EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO,
|
|
91
93
|
function=self.as_ommx_function(lin_expr),
|
|
94
|
+
equality=Constraint.LESS_THAN_OR_EQUAL_TO_ZERO,
|
|
92
95
|
name=name,
|
|
93
96
|
)
|
|
94
97
|
elif lin_expr.sense == ">":
|
|
@@ -96,8 +99,8 @@ class OMMXInstanceBuilder:
|
|
|
96
99
|
# So multiply the linear expression by -1.
|
|
97
100
|
constraint = Constraint(
|
|
98
101
|
id=id,
|
|
99
|
-
equality=Equality.EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO,
|
|
100
102
|
function=self.as_ommx_function(-lin_expr),
|
|
103
|
+
equality=Constraint.LESS_THAN_OR_EQUAL_TO_ZERO,
|
|
101
104
|
name=name,
|
|
102
105
|
)
|
|
103
106
|
else:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ommx_python_mip_adapter
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
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.9
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
|
-
Requires-Dist: ommx<
|
|
18
|
+
Requires-Dist: ommx<3.0.0,>=2.0.0b3
|
|
19
19
|
Requires-Dist: mip<2.0.0,>=1.15.0
|
|
20
20
|
|
|
21
21
|
OMMX adapter for Python-MIP
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ommx_python_mip_adapter"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0.0"
|
|
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" }]
|
|
@@ -27,7 +27,7 @@ classifiers = [
|
|
|
27
27
|
# To support Python 3.12 and Apple silicon systems, this requires latest version of Python-MIP (will be 1.16.0?),
|
|
28
28
|
# which does not release yet. https://github.com/coin-or/python-mip/issues/384
|
|
29
29
|
# This project uses dev-dependency to use the latest version of Python-MIP not to include pre-release version in the release.
|
|
30
|
-
dependencies = ["ommx >=
|
|
30
|
+
dependencies = ["ommx >= 2.0.0b3, < 3.0.0", "mip >= 1.15.0, < 2.0.0"]
|
|
31
31
|
|
|
32
32
|
[project.urls]
|
|
33
33
|
Repository = "https://github.com/Jij-Inc/ommx"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from ommx.v1 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=[(2.3 * x * x == 0).set_id(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
|
+
)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
-
from ommx.v1 import Instance, DecisionVariable
|
|
4
|
-
from ommx.v1.solution_pb2 import Optimality
|
|
3
|
+
from ommx.v1 import Instance, DecisionVariable, Solution
|
|
5
4
|
from ommx.testing import SingleFeasibleLPGenerator, DataType
|
|
6
5
|
|
|
7
6
|
from ommx_python_mip_adapter import OMMXPythonMIPAdapter
|
|
@@ -74,4 +73,4 @@ def test_solution_optimality():
|
|
|
74
73
|
)
|
|
75
74
|
|
|
76
75
|
solution = OMMXPythonMIPAdapter.solve(ommx_instance)
|
|
77
|
-
assert solution.optimality ==
|
|
76
|
+
assert solution.optimality == Solution.OPTIMAL
|
{ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/tests/test_model_to_instance.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import mip
|
|
2
2
|
|
|
3
|
-
from ommx.v1 import
|
|
3
|
+
from ommx.v1 import DecisionVariable, Instance, Constraint
|
|
4
4
|
|
|
5
5
|
from ommx_python_mip_adapter import model_to_instance
|
|
6
6
|
|
|
@@ -43,25 +43,25 @@ def test_milp():
|
|
|
43
43
|
model.add_constr(-7 * x1 + 8 * x3 - 9 <= 0) # type: ignore
|
|
44
44
|
model.add_constr(10 * x2 - 11 * x3 + 12 >= 0) # type: ignore
|
|
45
45
|
|
|
46
|
-
ommx_instance = model_to_instance(model)
|
|
46
|
+
ommx_instance = model_to_instance(model)
|
|
47
47
|
|
|
48
48
|
assert ommx_instance.sense == Instance.MINIMIZE
|
|
49
49
|
|
|
50
50
|
# Check the decision variables
|
|
51
51
|
assert len(ommx_instance.decision_variables) == 3
|
|
52
|
-
decision_variables_x1 = ommx_instance.
|
|
52
|
+
decision_variables_x1 = ommx_instance.get_decision_variable_by_id(0)
|
|
53
53
|
assert decision_variables_x1.id == 0
|
|
54
54
|
assert decision_variables_x1.kind == DecisionVariable.CONTINUOUS
|
|
55
55
|
assert decision_variables_x1.bound.lower == CONTINUOUS_LOWER_BOUND
|
|
56
56
|
assert decision_variables_x1.bound.upper == CONTINUOUS_UPPER_BOUND
|
|
57
57
|
assert decision_variables_x1.name == "1"
|
|
58
|
-
decision_variables_x2 = ommx_instance.
|
|
58
|
+
decision_variables_x2 = ommx_instance.get_decision_variable_by_id(1)
|
|
59
59
|
assert decision_variables_x2.id == 1
|
|
60
60
|
assert decision_variables_x2.kind == DecisionVariable.INTEGER
|
|
61
61
|
assert decision_variables_x2.bound.lower == INTEGER_LOWER_BOUND
|
|
62
62
|
assert decision_variables_x2.bound.upper == INTEGER_UPPER_BOUND
|
|
63
63
|
assert decision_variables_x2.name == "2"
|
|
64
|
-
decision_variables_x3 = ommx_instance.
|
|
64
|
+
decision_variables_x3 = ommx_instance.get_decision_variable_by_id(2)
|
|
65
65
|
assert decision_variables_x3.id == 2
|
|
66
66
|
assert decision_variables_x3.kind == DecisionVariable.BINARY
|
|
67
67
|
assert decision_variables_x3.bound.lower == 0
|
|
@@ -69,58 +69,43 @@ def test_milp():
|
|
|
69
69
|
assert decision_variables_x3.name == "3"
|
|
70
70
|
|
|
71
71
|
# Check the objective function
|
|
72
|
-
assert ommx_instance.objective.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
assert len(
|
|
76
|
-
|
|
77
|
-
assert
|
|
78
|
-
assert
|
|
79
|
-
objective_term_x2 = objective.terms[1]
|
|
80
|
-
assert objective_term_x2.id == 1
|
|
81
|
-
assert objective_term_x2.coefficient == -2
|
|
82
|
-
objective_term_x3 = objective.terms[2]
|
|
83
|
-
assert objective_term_x3.id == 2
|
|
84
|
-
assert objective_term_x3.coefficient == -3
|
|
72
|
+
assert ommx_instance.objective.degree() == 1
|
|
73
|
+
assert ommx_instance.objective.constant_term == 0
|
|
74
|
+
linear_terms = ommx_instance.objective.linear_terms
|
|
75
|
+
assert len(linear_terms) == 3
|
|
76
|
+
assert linear_terms[0] == -1
|
|
77
|
+
assert linear_terms[1] == -2
|
|
78
|
+
assert linear_terms[2] == -3
|
|
85
79
|
|
|
86
80
|
# Check the constraints
|
|
87
81
|
assert len(ommx_instance.constraints) == 3
|
|
88
82
|
|
|
89
|
-
constraint1 = ommx_instance.
|
|
83
|
+
constraint1 = ommx_instance.get_constraint_by_id(0)
|
|
90
84
|
assert constraint1.equality == Constraint.EQUAL_TO_ZERO
|
|
91
|
-
assert constraint1.function.
|
|
92
|
-
assert constraint1.function.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
assert
|
|
96
|
-
assert
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
assert constraint1_term_x2.coefficient == -5
|
|
100
|
-
|
|
101
|
-
constraint2 = ommx_instance.constraints[1]
|
|
85
|
+
assert constraint1.function.degree() == 1
|
|
86
|
+
assert constraint1.function.constant_term == 6
|
|
87
|
+
constraint1_terms = constraint1.function.linear_terms
|
|
88
|
+
assert len(constraint1_terms) == 2
|
|
89
|
+
assert constraint1_terms[0] == 4
|
|
90
|
+
assert constraint1_terms[1] == -5
|
|
91
|
+
|
|
92
|
+
constraint2 = ommx_instance.get_constraint_by_id(1)
|
|
102
93
|
assert constraint2.equality == Constraint.LESS_THAN_OR_EQUAL_TO_ZERO
|
|
103
|
-
assert constraint2.function.
|
|
104
|
-
assert constraint2.function.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
assert
|
|
108
|
-
assert
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
assert constraint2_term_x3.coefficient == 8
|
|
112
|
-
|
|
113
|
-
constraint3 = ommx_instance.constraints[2]
|
|
94
|
+
assert constraint2.function.degree() == 1
|
|
95
|
+
assert constraint2.function.constant_term == -9
|
|
96
|
+
constraint2_terms = constraint2.function.linear_terms
|
|
97
|
+
assert len(constraint2_terms) == 2
|
|
98
|
+
assert constraint2_terms[0] == -7
|
|
99
|
+
assert constraint2_terms[2] == 8
|
|
100
|
+
|
|
101
|
+
constraint3 = ommx_instance.get_constraint_by_id(2)
|
|
114
102
|
assert constraint3.equality == Constraint.LESS_THAN_OR_EQUAL_TO_ZERO
|
|
115
|
-
assert constraint3.function.
|
|
116
|
-
assert constraint3.function.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
assert
|
|
120
|
-
assert
|
|
121
|
-
constraint3_term_x3 = constraint3.function.linear.terms[1]
|
|
122
|
-
assert constraint3_term_x3.id == 2
|
|
123
|
-
assert constraint3_term_x3.coefficient == 11
|
|
103
|
+
assert constraint3.function.degree() == 1
|
|
104
|
+
assert constraint3.function.constant_term == -12
|
|
105
|
+
constraint3_terms = constraint3.function.linear_terms
|
|
106
|
+
assert len(constraint3_terms) == 2
|
|
107
|
+
assert constraint3_terms[1] == -10
|
|
108
|
+
assert constraint3_terms[2] == 11
|
|
124
109
|
|
|
125
110
|
|
|
126
111
|
def test_no_objective_model():
|
|
@@ -151,52 +136,46 @@ def test_no_objective_model():
|
|
|
151
136
|
model.add_constr(1 * x1 + 2 * x2 - 5 == 0) # type: ignore
|
|
152
137
|
model.add_constr(4 * x1 + 3 * x2 - 10 == 0) # type: ignore
|
|
153
138
|
|
|
154
|
-
ommx_instance = model_to_instance(model)
|
|
139
|
+
ommx_instance = model_to_instance(model)
|
|
155
140
|
|
|
156
141
|
assert ommx_instance.sense == Instance.MAXIMIZE
|
|
157
142
|
|
|
158
143
|
# Check the decision variables
|
|
159
144
|
assert len(ommx_instance.decision_variables) == 2
|
|
160
|
-
decision_variables_x1 = ommx_instance.
|
|
145
|
+
decision_variables_x1 = ommx_instance.get_decision_variable_by_id(0)
|
|
161
146
|
assert decision_variables_x1.id == 0
|
|
162
147
|
assert decision_variables_x1.kind == DecisionVariable.CONTINUOUS
|
|
163
148
|
assert decision_variables_x1.bound.lower == LOWER_BOUND
|
|
164
149
|
assert decision_variables_x1.bound.upper == UPPER_BOUND
|
|
165
150
|
assert decision_variables_x1.name == "1"
|
|
166
|
-
decision_variables_x2 = ommx_instance.
|
|
151
|
+
decision_variables_x2 = ommx_instance.get_decision_variable_by_id(1)
|
|
167
152
|
assert decision_variables_x2.id == 1
|
|
168
153
|
assert decision_variables_x2.kind == DecisionVariable.CONTINUOUS
|
|
169
154
|
assert decision_variables_x2.bound.lower == LOWER_BOUND
|
|
170
155
|
assert decision_variables_x2.bound.upper == UPPER_BOUND
|
|
171
156
|
assert decision_variables_x2.name == "2"
|
|
172
157
|
|
|
173
|
-
# check the objective function
|
|
174
|
-
assert ommx_instance.objective.
|
|
175
|
-
assert ommx_instance.objective.
|
|
158
|
+
# check the objective function - should be a zero constant
|
|
159
|
+
assert ommx_instance.objective.degree() == 0
|
|
160
|
+
assert ommx_instance.objective.num_terms() == 0 # Zero function has 0 terms
|
|
176
161
|
|
|
177
162
|
# Check the constraints
|
|
178
163
|
assert len(ommx_instance.constraints) == 2
|
|
179
164
|
|
|
180
|
-
constraint1 = ommx_instance.
|
|
165
|
+
constraint1 = ommx_instance.get_constraint_by_id(0)
|
|
181
166
|
assert constraint1.equality == Constraint.EQUAL_TO_ZERO
|
|
182
|
-
assert constraint1.function.
|
|
183
|
-
assert constraint1.function.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
assert
|
|
187
|
-
assert
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
assert constraint1_term_x2.coefficient == 2
|
|
191
|
-
|
|
192
|
-
constraint2 = ommx_instance.constraints[1]
|
|
167
|
+
assert constraint1.function.degree() == 1
|
|
168
|
+
assert constraint1.function.constant_term == -5
|
|
169
|
+
constraint1_terms = constraint1.function.linear_terms
|
|
170
|
+
assert len(constraint1_terms) == 2
|
|
171
|
+
assert constraint1_terms[0] == 1
|
|
172
|
+
assert constraint1_terms[1] == 2
|
|
173
|
+
|
|
174
|
+
constraint2 = ommx_instance.get_constraint_by_id(1)
|
|
193
175
|
assert constraint2.equality == Constraint.EQUAL_TO_ZERO
|
|
194
|
-
assert constraint2.function.
|
|
195
|
-
assert constraint2.function.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
assert
|
|
199
|
-
assert
|
|
200
|
-
constraint2_term_x3 = constraint2.function.linear.terms[1]
|
|
201
|
-
assert constraint2_term_x3.id == 1
|
|
202
|
-
assert constraint2_term_x3.coefficient == 3
|
|
176
|
+
assert constraint2.function.degree() == 1
|
|
177
|
+
assert constraint2.function.constant_term == -10
|
|
178
|
+
constraint2_terms = constraint2.function.linear_terms
|
|
179
|
+
assert len(constraint2_terms) == 2
|
|
180
|
+
assert constraint2_terms[0] == 4
|
|
181
|
+
assert constraint2_terms[1] == 3
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
|
-
from ommx.v1.constraint_pb2 import Constraint, Equality
|
|
4
|
-
from ommx.v1.decision_variables_pb2 import DecisionVariable as _DecisionVariable
|
|
5
|
-
from ommx.v1.function_pb2 import Function
|
|
6
|
-
from ommx.v1.linear_pb2 import Linear
|
|
7
|
-
from ommx.v1.quadratic_pb2 import Quadratic
|
|
8
|
-
from ommx.v1 import Instance, DecisionVariable
|
|
9
|
-
|
|
10
|
-
from ommx_python_mip_adapter import OMMXPythonMIPAdapter
|
|
11
|
-
|
|
12
|
-
from ommx_python_mip_adapter.exception import OMMXPythonMIPAdapterError
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def test_error_not_suppoerted_decision_variable():
|
|
16
|
-
ommx_instance = Instance.from_components(
|
|
17
|
-
decision_variables=[
|
|
18
|
-
DecisionVariable(
|
|
19
|
-
_DecisionVariable(id=1, kind=_DecisionVariable.KIND_UNSPECIFIED)
|
|
20
|
-
)
|
|
21
|
-
],
|
|
22
|
-
objective=Function(constant=0),
|
|
23
|
-
constraints=[],
|
|
24
|
-
sense=Instance.MINIMIZE,
|
|
25
|
-
)
|
|
26
|
-
with pytest.raises(OMMXPythonMIPAdapterError) as e:
|
|
27
|
-
OMMXPythonMIPAdapter(ommx_instance)
|
|
28
|
-
assert "Not supported decision variable" in str(e.value)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def test_error_nonlinear_objective():
|
|
32
|
-
# Objective function: 2.3 * x * x
|
|
33
|
-
ommx_instance = Instance.from_components(
|
|
34
|
-
decision_variables=[DecisionVariable.continuous(0)],
|
|
35
|
-
objective=Function(
|
|
36
|
-
quadratic=Quadratic(rows=[1], columns=[1], values=[2.3]),
|
|
37
|
-
),
|
|
38
|
-
constraints=[],
|
|
39
|
-
sense=Instance.MINIMIZE,
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
with pytest.raises(OMMXPythonMIPAdapterError) as e:
|
|
43
|
-
OMMXPythonMIPAdapter(ommx_instance)
|
|
44
|
-
assert "The function must be either `constant` or `linear`." in str(e.value)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def test_error_nonlinear_constraint():
|
|
48
|
-
# Objective function: 0
|
|
49
|
-
# Constraint: 2.3 * x * x = 0
|
|
50
|
-
ommx_instance = Instance.from_components(
|
|
51
|
-
decision_variables=[DecisionVariable.continuous(1)],
|
|
52
|
-
objective=Function(
|
|
53
|
-
constant=0,
|
|
54
|
-
),
|
|
55
|
-
constraints=[
|
|
56
|
-
Constraint(
|
|
57
|
-
function=Function(
|
|
58
|
-
quadratic=Quadratic(rows=[1], columns=[1], values=[2.3]),
|
|
59
|
-
),
|
|
60
|
-
equality=Equality.EQUALITY_EQUAL_TO_ZERO,
|
|
61
|
-
),
|
|
62
|
-
],
|
|
63
|
-
sense=Instance.MINIMIZE,
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
with pytest.raises(OMMXPythonMIPAdapterError) as e:
|
|
67
|
-
OMMXPythonMIPAdapter(ommx_instance)
|
|
68
|
-
assert "The function must be either `constant` or `linear`." in str(e.value)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def test_error_not_supported_constraint_equality():
|
|
72
|
-
# Objective function: 0
|
|
73
|
-
# Constraint: 2x ?? 0 (equality: unspecified)
|
|
74
|
-
ommx_instance = Instance.from_components(
|
|
75
|
-
decision_variables=[DecisionVariable.continuous(1)],
|
|
76
|
-
objective=Function(constant=0),
|
|
77
|
-
constraints=[
|
|
78
|
-
Constraint(
|
|
79
|
-
function=Function(
|
|
80
|
-
linear=Linear(terms=[Linear.Term(id=1, coefficient=2)])
|
|
81
|
-
),
|
|
82
|
-
equality=Equality.EQUALITY_UNSPECIFIED,
|
|
83
|
-
),
|
|
84
|
-
],
|
|
85
|
-
sense=Instance.MINIMIZE,
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
with pytest.raises(OMMXPythonMIPAdapterError) as e:
|
|
89
|
-
OMMXPythonMIPAdapter(ommx_instance)
|
|
90
|
-
assert "Not supported constraint equality" in str(e.value)
|
|
File without changes
|
{ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter/__init__.py
RENAMED
|
File without changes
|
{ommx_python_mip_adapter-1.9.4 → ommx_python_mip_adapter-2.0.0}/ommx_python_mip_adapter/exception.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|