ommx-python-mip-adapter 0.1.1__tar.gz → 0.4.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-0.1.1 → ommx_python_mip_adapter-0.4.0}/PKG-INFO +24 -38
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.4.0}/README.md +19 -32
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.4.0}/ommx_python_mip_adapter/__init__.py +1 -1
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.4.0}/ommx_python_mip_adapter/adapter.py +86 -142
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.4.0}/ommx_python_mip_adapter.egg-info/PKG-INFO +24 -38
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.4.0}/ommx_python_mip_adapter.egg-info/SOURCES.txt +1 -12
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.4.0}/ommx_python_mip_adapter.egg-info/requires.txt +4 -3
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.4.0}/pyproject.toml +14 -18
- ommx_python_mip_adapter-0.4.0/tests/test_instance_to_model.py +90 -0
- ommx_python_mip_adapter-0.4.0/tests/test_integration.py +60 -0
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.4.0}/tests/test_model_to_instance.py +34 -38
- ommx_python_mip_adapter-0.1.1/.github/workflows/docs.yml +0 -72
- ommx_python_mip_adapter-0.1.1/.github/workflows/publish.yml +0 -49
- ommx_python_mip_adapter-0.1.1/.github/workflows/test_and_lint.yml +0 -75
- ommx_python_mip_adapter-0.1.1/.gitignore +0 -162
- ommx_python_mip_adapter-0.1.1/LICENSE-APACHE +0 -201
- ommx_python_mip_adapter-0.1.1/LICENSE-MIT +0 -21
- ommx_python_mip_adapter-0.1.1/docs/source/conf.py +0 -50
- ommx_python_mip_adapter-0.1.1/docs/source/index.rst +0 -20
- ommx_python_mip_adapter-0.1.1/ommx_python_mip_adapter/_version.py +0 -16
- ommx_python_mip_adapter-0.1.1/tests/conftest.py +0 -0
- ommx_python_mip_adapter-0.1.1/tests/test_instance_to_model.py +0 -127
- ommx_python_mip_adapter-0.1.1/tests/test_integration.py +0 -142
- ommx_python_mip_adapter-0.1.1/tests/test_model_to_solution.py +0 -27
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.4.0}/ommx_python_mip_adapter/exception.py +0 -0
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.4.0}/ommx_python_mip_adapter.egg-info/dependency_links.txt +0 -0
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.4.0}/ommx_python_mip_adapter.egg-info/top_level.txt +0 -0
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.4.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ommx_python_mip_adapter
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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-python-mip-adapter
|
|
@@ -15,9 +15,7 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
16
|
Requires-Python: >=3.8
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
|
-
|
|
19
|
-
License-File: LICENSE-MIT
|
|
20
|
-
Requires-Dist: ommx<0.3.0,>=0.2.0
|
|
18
|
+
Requires-Dist: ommx<0.5.0,>=0.4.0
|
|
21
19
|
Requires-Dist: mip
|
|
22
20
|
Provides-Extra: dev
|
|
23
21
|
Requires-Dist: markdown-code-runner; extra == "dev"
|
|
@@ -25,16 +23,17 @@ Requires-Dist: mypy; extra == "dev"
|
|
|
25
23
|
Requires-Dist: numpy; extra == "dev"
|
|
26
24
|
Requires-Dist: pyright; extra == "dev"
|
|
27
25
|
Requires-Dist: pytest; extra == "dev"
|
|
26
|
+
Requires-Dist: ruff; extra == "dev"
|
|
28
27
|
Requires-Dist: sphinx; extra == "dev"
|
|
29
|
-
Requires-Dist: sphinx_rtd_theme; extra == "dev"
|
|
30
|
-
Requires-Dist: sphinx_fontawesome; extra == "dev"
|
|
31
28
|
Requires-Dist: sphinx-autoapi; extra == "dev"
|
|
29
|
+
Requires-Dist: sphinx_fontawesome; extra == "dev"
|
|
30
|
+
Requires-Dist: sphinx_rtd_theme; extra == "dev"
|
|
32
31
|
|
|
33
|
-
OMMX
|
|
32
|
+
OMMX adapter for Python-MIP
|
|
34
33
|
============================
|
|
35
|
-

|
|
34
|
+
[](https://pypi.org/project/ommx-python-mip-adapter/)
|
|
36
35
|
|
|
37
|
-
This package provides an
|
|
36
|
+
This package provides an adapter for the [Python-MIP](https://www.python-mip.com/) from/to [OMMX](https://github.com/Jij-Inc/ommx)
|
|
38
37
|
|
|
39
38
|
Python-MIP as a solver in OMMX toolchain
|
|
40
39
|
-----------------------------------------
|
|
@@ -78,43 +77,30 @@ Python-MIP can be used through `ommx-python-mip-adapter` by using the following:
|
|
|
78
77
|
|
|
79
78
|
```python markdown-code-runner
|
|
80
79
|
import ommx_python_mip_adapter as adapter
|
|
81
|
-
from ommx.v1
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
DecisionVariable(
|
|
90
|
-
id=1,
|
|
91
|
-
kind=DecisionVariable.KIND_INTEGER,
|
|
92
|
-
bound=Bound(lower=0, upper=5),
|
|
93
|
-
),
|
|
94
|
-
],
|
|
95
|
-
objective=Function(
|
|
96
|
-
linear=Linear(
|
|
97
|
-
terms=[Linear.Term(id=1, coefficient=1)]
|
|
98
|
-
),
|
|
99
|
-
),
|
|
80
|
+
from ommx.v1 import Instance, DecisionVariable
|
|
81
|
+
|
|
82
|
+
x1 = DecisionVariable.integer(1, lower=0, upper=5)
|
|
83
|
+
ommx_instance = Instance.from_components(
|
|
84
|
+
decision_variables=[x1],
|
|
85
|
+
objective=x1,
|
|
86
|
+
constraints=[],
|
|
87
|
+
sense=Instance.MINIMIZE,
|
|
100
88
|
)
|
|
101
|
-
ommx_instance_bytes = ommx_instance.SerializeToString()
|
|
102
89
|
|
|
103
90
|
# Convert from `ommx.v1.Instance` to `mip.Model`
|
|
104
|
-
model = adapter.instance_to_model(
|
|
91
|
+
model = adapter.instance_to_model(ommx_instance)
|
|
105
92
|
model.optimize()
|
|
106
|
-
# Create `ommx.v1.
|
|
107
|
-
|
|
108
|
-
model, ommx_instance_bytes
|
|
109
|
-
)
|
|
93
|
+
# Create `ommx.v1.State` from Optimized `mip.Model`
|
|
94
|
+
ommx_solutions = adapter.model_to_solution(model, ommx_instance)
|
|
110
95
|
|
|
111
|
-
print(
|
|
96
|
+
print(ommx_solutions)
|
|
112
97
|
```
|
|
98
|
+
|
|
113
99
|
You can get `ommx.v1.Instance` from a Python-MIP model as the following:
|
|
100
|
+
|
|
114
101
|
```python markdown-code-runner
|
|
115
102
|
import mip
|
|
116
103
|
import ommx_python_mip_adapter as adapter
|
|
117
|
-
from ommx.v1.instance_pb2 import Instance
|
|
118
104
|
|
|
119
105
|
model = mip.Model()
|
|
120
106
|
x1=model.add_var(name="1", var_type=mip.INTEGER, lb=0, ub=5)
|
|
@@ -122,9 +108,9 @@ x2=model.add_var(name="2", var_type=mip.CONTINUOUS, lb=0, ub=5)
|
|
|
122
108
|
model.objective = - x1 - 2 * x2
|
|
123
109
|
model.add_constr(x1 + x2 - 6 <= 0)
|
|
124
110
|
|
|
125
|
-
|
|
111
|
+
ommx_instance = adapter.model_to_instance(model)
|
|
126
112
|
|
|
127
|
-
print(
|
|
113
|
+
print(ommx_instance)
|
|
128
114
|
```
|
|
129
115
|
|
|
130
116
|
Reference
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
OMMX
|
|
1
|
+
OMMX adapter for Python-MIP
|
|
2
2
|
============================
|
|
3
|
-

|
|
3
|
+
[](https://pypi.org/project/ommx-python-mip-adapter/)
|
|
4
4
|
|
|
5
|
-
This package provides an
|
|
5
|
+
This package provides an adapter for the [Python-MIP](https://www.python-mip.com/) from/to [OMMX](https://github.com/Jij-Inc/ommx)
|
|
6
6
|
|
|
7
7
|
Python-MIP as a solver in OMMX toolchain
|
|
8
8
|
-----------------------------------------
|
|
@@ -46,43 +46,30 @@ Python-MIP can be used through `ommx-python-mip-adapter` by using the following:
|
|
|
46
46
|
|
|
47
47
|
```python markdown-code-runner
|
|
48
48
|
import ommx_python_mip_adapter as adapter
|
|
49
|
-
from ommx.v1
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
DecisionVariable(
|
|
58
|
-
id=1,
|
|
59
|
-
kind=DecisionVariable.KIND_INTEGER,
|
|
60
|
-
bound=Bound(lower=0, upper=5),
|
|
61
|
-
),
|
|
62
|
-
],
|
|
63
|
-
objective=Function(
|
|
64
|
-
linear=Linear(
|
|
65
|
-
terms=[Linear.Term(id=1, coefficient=1)]
|
|
66
|
-
),
|
|
67
|
-
),
|
|
49
|
+
from ommx.v1 import Instance, DecisionVariable
|
|
50
|
+
|
|
51
|
+
x1 = DecisionVariable.integer(1, lower=0, upper=5)
|
|
52
|
+
ommx_instance = Instance.from_components(
|
|
53
|
+
decision_variables=[x1],
|
|
54
|
+
objective=x1,
|
|
55
|
+
constraints=[],
|
|
56
|
+
sense=Instance.MINIMIZE,
|
|
68
57
|
)
|
|
69
|
-
ommx_instance_bytes = ommx_instance.SerializeToString()
|
|
70
58
|
|
|
71
59
|
# Convert from `ommx.v1.Instance` to `mip.Model`
|
|
72
|
-
model = adapter.instance_to_model(
|
|
60
|
+
model = adapter.instance_to_model(ommx_instance)
|
|
73
61
|
model.optimize()
|
|
74
|
-
# Create `ommx.v1.
|
|
75
|
-
|
|
76
|
-
model, ommx_instance_bytes
|
|
77
|
-
)
|
|
62
|
+
# Create `ommx.v1.State` from Optimized `mip.Model`
|
|
63
|
+
ommx_solutions = adapter.model_to_solution(model, ommx_instance)
|
|
78
64
|
|
|
79
|
-
print(
|
|
65
|
+
print(ommx_solutions)
|
|
80
66
|
```
|
|
67
|
+
|
|
81
68
|
You can get `ommx.v1.Instance` from a Python-MIP model as the following:
|
|
69
|
+
|
|
82
70
|
```python markdown-code-runner
|
|
83
71
|
import mip
|
|
84
72
|
import ommx_python_mip_adapter as adapter
|
|
85
|
-
from ommx.v1.instance_pb2 import Instance
|
|
86
73
|
|
|
87
74
|
model = mip.Model()
|
|
88
75
|
x1=model.add_var(name="1", var_type=mip.INTEGER, lb=0, ub=5)
|
|
@@ -90,9 +77,9 @@ x2=model.add_var(name="2", var_type=mip.CONTINUOUS, lb=0, ub=5)
|
|
|
90
77
|
model.objective = - x1 - 2 * x2
|
|
91
78
|
model.add_constr(x1 + x2 - 6 <= 0)
|
|
92
79
|
|
|
93
|
-
|
|
80
|
+
ommx_instance = adapter.model_to_instance(model)
|
|
94
81
|
|
|
95
|
-
print(
|
|
82
|
+
print(ommx_instance)
|
|
96
83
|
```
|
|
97
84
|
|
|
98
85
|
Reference
|
{ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.4.0}/ommx_python_mip_adapter/adapter.py
RENAMED
|
@@ -3,12 +3,11 @@ import typing as tp
|
|
|
3
3
|
import mip
|
|
4
4
|
|
|
5
5
|
from mip.exceptions import ParameterNotAvailable
|
|
6
|
-
from ommx.v1.constraint_pb2 import Constraint
|
|
7
|
-
from ommx.v1.decision_variables_pb2 import DecisionVariable, Bound
|
|
6
|
+
from ommx.v1.constraint_pb2 import Constraint, Equality
|
|
8
7
|
from ommx.v1.function_pb2 import Function
|
|
9
|
-
from ommx.v1.instance_pb2 import Instance
|
|
10
8
|
from ommx.v1.linear_pb2 import Linear
|
|
11
|
-
from ommx.v1.solution_pb2 import
|
|
9
|
+
from ommx.v1.solution_pb2 import State
|
|
10
|
+
from ommx.v1 import Instance, DecisionVariable
|
|
12
11
|
|
|
13
12
|
from ommx_python_mip_adapter.exception import OMMXPythonMIPAdapterError
|
|
14
13
|
|
|
@@ -16,71 +15,65 @@ from ommx_python_mip_adapter.exception import OMMXPythonMIPAdapterError
|
|
|
16
15
|
class PythonMIPBuilder:
|
|
17
16
|
def __init__(
|
|
18
17
|
self,
|
|
19
|
-
|
|
18
|
+
instance: Instance,
|
|
20
19
|
*,
|
|
21
20
|
sense: str = mip.MINIMIZE,
|
|
22
21
|
solver_name: str = mip.CBC,
|
|
23
22
|
solver: tp.Optional[mip.Solver] = None,
|
|
24
23
|
):
|
|
25
|
-
|
|
26
|
-
self._ommx_instance = Instance.FromString(ommx_instance_bytes)
|
|
27
|
-
except Exception as e:
|
|
28
|
-
raise OMMXPythonMIPAdapterError(
|
|
29
|
-
"Invalid `ommx_instance_bytes` as ommx.v1.Instance."
|
|
30
|
-
) from e
|
|
31
|
-
|
|
24
|
+
self._ommx_instance = instance.raw
|
|
32
25
|
self._model = mip.Model(
|
|
33
26
|
sense=sense,
|
|
34
27
|
solver_name=solver_name,
|
|
35
28
|
solver=solver,
|
|
36
29
|
)
|
|
37
30
|
|
|
38
|
-
|
|
39
31
|
def _set_decision_variables(self):
|
|
40
32
|
for var in self._ommx_instance.decision_variables:
|
|
41
|
-
if var.kind == DecisionVariable.
|
|
33
|
+
if var.kind == DecisionVariable.BINARY:
|
|
42
34
|
self._model.add_var(
|
|
43
35
|
name=str(var.id),
|
|
44
36
|
var_type=mip.BINARY,
|
|
45
37
|
)
|
|
46
|
-
elif var.kind == DecisionVariable.
|
|
38
|
+
elif var.kind == DecisionVariable.INTEGER:
|
|
47
39
|
self._model.add_var(
|
|
48
40
|
name=str(var.id),
|
|
49
41
|
var_type=mip.INTEGER,
|
|
50
|
-
lb=var.bound.lower,
|
|
51
|
-
ub=var.bound.upper,
|
|
42
|
+
lb=var.bound.lower, # type: ignore
|
|
43
|
+
ub=var.bound.upper, # type: ignore
|
|
52
44
|
)
|
|
53
|
-
elif var.kind == DecisionVariable.
|
|
45
|
+
elif var.kind == DecisionVariable.CONTINUOUS:
|
|
54
46
|
self._model.add_var(
|
|
55
47
|
name=str(var.id),
|
|
56
48
|
var_type=mip.CONTINUOUS,
|
|
57
|
-
lb=var.bound.lower,
|
|
58
|
-
ub=var.bound.upper,
|
|
49
|
+
lb=var.bound.lower, # type: ignore
|
|
50
|
+
ub=var.bound.upper, # type: ignore
|
|
59
51
|
)
|
|
60
52
|
else:
|
|
61
53
|
raise OMMXPythonMIPAdapterError(
|
|
62
54
|
f"Not supported decision variable kind: "
|
|
63
55
|
f"id: {var.id}, kind: {var.kind}"
|
|
64
56
|
)
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
|
|
67
58
|
def _make_linear_expr(
|
|
68
59
|
self,
|
|
69
60
|
ommx_function: Function,
|
|
70
|
-
) -> mip.LinExpr:
|
|
61
|
+
) -> mip.LinExpr:
|
|
71
62
|
ommx_linear = ommx_function.linear
|
|
72
63
|
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
64
|
+
return (
|
|
65
|
+
mip.xsum(
|
|
66
|
+
term.coefficient * self._model.vars[str(term.id)] # type: ignore
|
|
67
|
+
for term in ommx_linear.terms
|
|
68
|
+
)
|
|
69
|
+
+ ommx_linear.constant
|
|
70
|
+
) # type: ignore
|
|
77
71
|
|
|
78
|
-
|
|
79
72
|
def _set_objective_function(self):
|
|
80
73
|
ommx_objective = self._ommx_instance.objective
|
|
81
74
|
|
|
82
75
|
if ommx_objective.HasField("constant"):
|
|
83
|
-
self._model.objective = ommx_objective.constant
|
|
76
|
+
self._model.objective = ommx_objective.constant # type: ignore
|
|
84
77
|
elif ommx_objective.HasField("linear"):
|
|
85
78
|
self._model.objective = self._make_linear_expr(ommx_objective)
|
|
86
79
|
else:
|
|
@@ -88,7 +81,6 @@ class PythonMIPBuilder:
|
|
|
88
81
|
"The objective function must be either `constant` or `linear`."
|
|
89
82
|
)
|
|
90
83
|
|
|
91
|
-
|
|
92
84
|
def _set_constraints(self):
|
|
93
85
|
ommx_constraints = self._ommx_instance.constraints
|
|
94
86
|
|
|
@@ -102,18 +94,17 @@ class PythonMIPBuilder:
|
|
|
102
94
|
|
|
103
95
|
lin_expr = self._make_linear_expr(constraint.function)
|
|
104
96
|
|
|
105
|
-
if constraint.equality ==
|
|
106
|
-
constr_expr =
|
|
107
|
-
elif
|
|
108
|
-
constr_expr =
|
|
97
|
+
if constraint.equality == Equality.EQUALITY_EQUAL_TO_ZERO:
|
|
98
|
+
constr_expr = lin_expr == 0
|
|
99
|
+
elif constraint.equality == Equality.EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO:
|
|
100
|
+
constr_expr = lin_expr <= 0 # type: ignore
|
|
109
101
|
else:
|
|
110
102
|
raise OMMXPythonMIPAdapterError(
|
|
111
103
|
f"Not supported constraint equality: "
|
|
112
104
|
f"id: {constraint.id}, equality: {constraint.equality}"
|
|
113
105
|
)
|
|
114
|
-
|
|
115
|
-
self._model.add_constr(constr_expr, name=str(constraint.id))
|
|
116
106
|
|
|
107
|
+
self._model.add_constr(constr_expr, name=str(constraint.id))
|
|
117
108
|
|
|
118
109
|
def build(self) -> mip.Model:
|
|
119
110
|
self._set_decision_variables()
|
|
@@ -135,11 +126,11 @@ class OMMXInstanceBuilder:
|
|
|
135
126
|
|
|
136
127
|
for var in self._model.vars:
|
|
137
128
|
if var.var_type == mip.BINARY:
|
|
138
|
-
kind = DecisionVariable.
|
|
129
|
+
kind = DecisionVariable.BINARY
|
|
139
130
|
elif var.var_type == mip.INTEGER:
|
|
140
|
-
kind = DecisionVariable.
|
|
131
|
+
kind = DecisionVariable.INTEGER
|
|
141
132
|
elif var.var_type == mip.CONTINUOUS:
|
|
142
|
-
kind = DecisionVariable.
|
|
133
|
+
kind = DecisionVariable.CONTINUOUS
|
|
143
134
|
else:
|
|
144
135
|
raise OMMXPythonMIPAdapterError(
|
|
145
136
|
f"Not supported variable type. "
|
|
@@ -147,35 +138,28 @@ class OMMXInstanceBuilder:
|
|
|
147
138
|
)
|
|
148
139
|
|
|
149
140
|
decision_variables.append(
|
|
150
|
-
DecisionVariable(
|
|
151
|
-
|
|
152
|
-
kind=kind,
|
|
153
|
-
bound=Bound(lower=var.lb, upper=var.ub),
|
|
154
|
-
description=DecisionVariable.Description(name=var.name)
|
|
141
|
+
DecisionVariable.of_type(
|
|
142
|
+
kind, var.idx, lower=var.lb, upper=var.ub, name=var.name
|
|
155
143
|
)
|
|
156
144
|
)
|
|
157
145
|
|
|
158
146
|
return decision_variables
|
|
159
|
-
|
|
160
147
|
|
|
161
148
|
def _make_function_from_lin_expr(
|
|
162
149
|
self,
|
|
163
150
|
lin_expr: mip.LinExpr,
|
|
164
151
|
) -> Function:
|
|
165
152
|
terms = [
|
|
166
|
-
Linear.Term(id=var.idx, coefficient=coeff)
|
|
153
|
+
Linear.Term(id=var.idx, coefficient=coeff) # type: ignore
|
|
167
154
|
for var, coeff in lin_expr.expr.items()
|
|
168
155
|
]
|
|
169
|
-
constant: float = lin_expr.const
|
|
156
|
+
constant: float = lin_expr.const # type: ignore
|
|
170
157
|
|
|
171
158
|
# If the terms are empty, the function is a constant.
|
|
172
159
|
if len(terms) == 0:
|
|
173
160
|
return Function(constant=constant)
|
|
174
161
|
else:
|
|
175
|
-
return Function(
|
|
176
|
-
linear=Linear(terms=terms, constant=constant)
|
|
177
|
-
)
|
|
178
|
-
|
|
162
|
+
return Function(linear=Linear(terms=terms, constant=constant))
|
|
179
163
|
|
|
180
164
|
def _objective(self) -> Function:
|
|
181
165
|
# In Python-MIP, it is allowed not to set the objective function.
|
|
@@ -186,9 +170,8 @@ class OMMXInstanceBuilder:
|
|
|
186
170
|
objective = self._model.objective
|
|
187
171
|
except ParameterNotAvailable:
|
|
188
172
|
return Function(constant=0)
|
|
189
|
-
|
|
190
|
-
return self._make_function_from_lin_expr(objective)
|
|
191
173
|
|
|
174
|
+
return self._make_function_from_lin_expr(objective)
|
|
192
175
|
|
|
193
176
|
def _constraints(self) -> tp.List[Constraint]:
|
|
194
177
|
constraints = []
|
|
@@ -201,55 +184,53 @@ class OMMXInstanceBuilder:
|
|
|
201
184
|
if lin_expr.sense == "=":
|
|
202
185
|
constraint = Constraint(
|
|
203
186
|
id=id,
|
|
204
|
-
equality=
|
|
187
|
+
equality=Equality.EQUALITY_EQUAL_TO_ZERO,
|
|
205
188
|
function=self._make_function_from_lin_expr(lin_expr),
|
|
206
|
-
|
|
189
|
+
name=name,
|
|
207
190
|
)
|
|
208
191
|
elif lin_expr.sense == "<":
|
|
209
192
|
constraint = Constraint(
|
|
210
193
|
id=id,
|
|
211
|
-
equality=
|
|
194
|
+
equality=Equality.EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO,
|
|
212
195
|
function=self._make_function_from_lin_expr(lin_expr),
|
|
213
|
-
|
|
196
|
+
name=name,
|
|
214
197
|
)
|
|
215
198
|
elif lin_expr.sense == ">":
|
|
216
199
|
# `ommx.v1.Constraint` does not support `GREATER_THAN_OR_EQUAL_TO_ZERO`.
|
|
217
200
|
# So multiply the linear expression by -1.
|
|
218
201
|
constraint = Constraint(
|
|
219
202
|
id=id,
|
|
220
|
-
equality=
|
|
203
|
+
equality=Equality.EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO,
|
|
221
204
|
function=self._make_function_from_lin_expr(-lin_expr),
|
|
222
|
-
|
|
205
|
+
name=name,
|
|
223
206
|
)
|
|
224
207
|
else:
|
|
225
208
|
raise OMMXPythonMIPAdapterError(
|
|
226
209
|
f"Not supported constraint sense: "
|
|
227
210
|
f"name: {constr.name}, sense: {lin_expr.sense}"
|
|
228
211
|
)
|
|
229
|
-
|
|
212
|
+
|
|
230
213
|
constraints.append(constraint)
|
|
231
214
|
|
|
232
215
|
return constraints
|
|
233
216
|
|
|
234
|
-
|
|
235
217
|
def _sense(self):
|
|
236
218
|
if self._model.sense == mip.MAXIMIZE:
|
|
237
|
-
return Instance.
|
|
219
|
+
return Instance.MAXIMIZE
|
|
238
220
|
else:
|
|
239
|
-
return Instance.
|
|
221
|
+
return Instance.MINIMIZE
|
|
240
222
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
return Instance(
|
|
223
|
+
def build(self) -> Instance:
|
|
224
|
+
return Instance.from_components(
|
|
244
225
|
decision_variables=self._decision_variables(),
|
|
245
226
|
objective=self._objective(),
|
|
246
227
|
constraints=self._constraints(),
|
|
247
228
|
sense=self._sense(),
|
|
248
|
-
)
|
|
229
|
+
)
|
|
249
230
|
|
|
250
231
|
|
|
251
232
|
def instance_to_model(
|
|
252
|
-
|
|
233
|
+
instance: Instance,
|
|
253
234
|
*,
|
|
254
235
|
sense: str = mip.MINIMIZE,
|
|
255
236
|
solver_name: str = mip.CBC,
|
|
@@ -260,7 +241,7 @@ def instance_to_model(
|
|
|
260
241
|
|
|
261
242
|
Args:
|
|
262
243
|
ommx_instance_bytes (bytes): Serialized ommx.v1.Instance.
|
|
263
|
-
sense (str): mip.MINIMIZE or mip.MAXIMIZE.
|
|
244
|
+
sense (str): mip.MINIMIZE or mip.MAXIMIZE.
|
|
264
245
|
solver_name (str): mip.CBC or mip.GUROBI. Searches for which solver is available if not informed.
|
|
265
246
|
solver (mip.Solver): if this argument is provided, solver_name will be ignored.
|
|
266
247
|
|
|
@@ -274,37 +255,25 @@ def instance_to_model(
|
|
|
274
255
|
The following example of solving an unconstrained linear optimization problem with x1 as the objective function.
|
|
275
256
|
|
|
276
257
|
>>> import ommx_python_mip_adapter as adapter
|
|
277
|
-
>>> from ommx.v1
|
|
278
|
-
|
|
279
|
-
>>>
|
|
280
|
-
>>>
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
...
|
|
284
|
-
...
|
|
285
|
-
... id=1,
|
|
286
|
-
... kind=DecisionVariable.KIND_INTEGER,
|
|
287
|
-
... bound=Bound(lower=0, upper=5),
|
|
288
|
-
... ),
|
|
289
|
-
... ],
|
|
290
|
-
... objective=Function(
|
|
291
|
-
... linear=Linear(
|
|
292
|
-
... terms=[Linear.Term(id=1, coefficient=1)]
|
|
293
|
-
... ),
|
|
294
|
-
... ),
|
|
258
|
+
>>> from ommx.v1 import Instance, DecisionVariable
|
|
259
|
+
|
|
260
|
+
>>> x1 = DecisionVariable.integer(1, lower=0, upper=5)
|
|
261
|
+
>>> ommx_instance = Instance.from_components(
|
|
262
|
+
... decision_variables=[x1],
|
|
263
|
+
... objective=x1,
|
|
264
|
+
... constraints=[],
|
|
265
|
+
... sense=Instance.MINIMIZE,
|
|
295
266
|
... )
|
|
296
|
-
>>>
|
|
297
|
-
>>> model = adapter.instance_to_model(ommx_instance_bytes)
|
|
267
|
+
>>> model = adapter.instance_to_model(ommx_instance)
|
|
298
268
|
>>> model.optimize()
|
|
299
269
|
<OptimizationStatus.OPTIMAL: 0>
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
>>> SolutionList.FromString(ommx_solutions_bytes).solutions[0].entries
|
|
270
|
+
|
|
271
|
+
>>> ommx_solutions = adapter.model_to_solution(model, ommx_instance)
|
|
272
|
+
>>> ommx_solutions.entries
|
|
304
273
|
{1: 0.0}
|
|
305
274
|
"""
|
|
306
275
|
builder = PythonMIPBuilder(
|
|
307
|
-
|
|
276
|
+
instance,
|
|
308
277
|
sense=sense,
|
|
309
278
|
solver_name=solver_name,
|
|
310
279
|
solver=solver,
|
|
@@ -312,13 +281,13 @@ def instance_to_model(
|
|
|
312
281
|
return builder.build()
|
|
313
282
|
|
|
314
283
|
|
|
315
|
-
def model_to_instance(model: mip.Model) ->
|
|
284
|
+
def model_to_instance(model: mip.Model) -> Instance:
|
|
316
285
|
"""
|
|
317
286
|
The function to convert Python-MIP Model to ommx.v1.Instance.
|
|
318
287
|
|
|
319
288
|
Args:
|
|
320
289
|
model (mip.Model): Python-MIP Model.
|
|
321
|
-
|
|
290
|
+
|
|
322
291
|
Returns:
|
|
323
292
|
bytes: Serialized ommx.v1.Instance.
|
|
324
293
|
|
|
@@ -328,14 +297,12 @@ def model_to_instance(model: mip.Model) -> bytes:
|
|
|
328
297
|
Examples:
|
|
329
298
|
>>> import mip
|
|
330
299
|
>>> import ommx_python_mip_adapter as adapter
|
|
331
|
-
>>> from ommx.v1.instance_pb2 import Instance
|
|
332
300
|
>>> model = mip.Model()
|
|
333
301
|
>>> x1=model.add_var(name="1", var_type=mip.INTEGER, lb=0, ub=5)
|
|
334
302
|
>>> x2=model.add_var(name="2", var_type=mip.CONTINUOUS, lb=0, ub=5)
|
|
335
303
|
>>> model.objective = - x1 - 2 * x2
|
|
336
304
|
>>> constr = model.add_constr(x1 + x2 - 6 <= 0)
|
|
337
|
-
>>>
|
|
338
|
-
>>> ommx_instance = Instance.FromString(ommx_instance_bytes)
|
|
305
|
+
>>> ommx_instance = adapter.model_to_instance(model)
|
|
339
306
|
"""
|
|
340
307
|
builder = OMMXInstanceBuilder(model)
|
|
341
308
|
return builder.build()
|
|
@@ -343,8 +310,8 @@ def model_to_instance(model: mip.Model) -> bytes:
|
|
|
343
310
|
|
|
344
311
|
def model_to_solution(
|
|
345
312
|
model: mip.Model,
|
|
346
|
-
|
|
347
|
-
) ->
|
|
313
|
+
instance: Instance,
|
|
314
|
+
) -> State:
|
|
348
315
|
"""
|
|
349
316
|
The function to create ommx.v1.SolutionList from optimized Python-MIP Model.
|
|
350
317
|
|
|
@@ -362,33 +329,21 @@ def model_to_solution(
|
|
|
362
329
|
The following example of solving an unconstrained linear optimization problem with x1 as the objective function.
|
|
363
330
|
|
|
364
331
|
>>> import ommx_python_mip_adapter as adapter
|
|
365
|
-
>>> from ommx.v1
|
|
366
|
-
|
|
367
|
-
>>>
|
|
368
|
-
>>>
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
...
|
|
372
|
-
...
|
|
373
|
-
... id=1,
|
|
374
|
-
... kind=DecisionVariable.KIND_INTEGER,
|
|
375
|
-
... bound=Bound(lower=0, upper=5),
|
|
376
|
-
... ),
|
|
377
|
-
... ],
|
|
378
|
-
... objective=Function(
|
|
379
|
-
... linear=Linear(
|
|
380
|
-
... terms=[Linear.Term(id=1, coefficient=1)]
|
|
381
|
-
... ),
|
|
382
|
-
... ),
|
|
332
|
+
>>> from ommx.v1 import Instance, DecisionVariable
|
|
333
|
+
|
|
334
|
+
>>> x1 = DecisionVariable.integer(1, lower=0, upper=5)
|
|
335
|
+
>>> ommx_instance = Instance.from_components(
|
|
336
|
+
... decision_variables=[x1],
|
|
337
|
+
... objective=x1,
|
|
338
|
+
... constraints=[],
|
|
339
|
+
... sense=Instance.MINIMIZE,
|
|
383
340
|
... )
|
|
384
|
-
>>>
|
|
385
|
-
>>> model = adapter.instance_to_model(ommx_instance_bytes)
|
|
341
|
+
>>> model = adapter.instance_to_model(ommx_instance)
|
|
386
342
|
>>> model.optimize()
|
|
387
343
|
<OptimizationStatus.OPTIMAL: 0>
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
>>> SolutionList.FromString(ommx_solutions_bytes).solutions[0].entries
|
|
344
|
+
|
|
345
|
+
>>> ommx_solutions = adapter.model_to_solution(model, ommx_instance)
|
|
346
|
+
>>> ommx_solutions.entries
|
|
392
347
|
{1: 0.0}
|
|
393
348
|
"""
|
|
394
349
|
if not (
|
|
@@ -399,20 +354,9 @@ def model_to_solution(
|
|
|
399
354
|
"`model.status` must be `OPTIMAL` or `FEASIBLE`."
|
|
400
355
|
)
|
|
401
356
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
return SolutionList(
|
|
410
|
-
solutions=[
|
|
411
|
-
Solution(
|
|
412
|
-
entries={
|
|
413
|
-
var.id: model.var_by_name(str(var.id)).x # type: ignore
|
|
414
|
-
for var in ommx_instance.decision_variables
|
|
415
|
-
}
|
|
416
|
-
)
|
|
417
|
-
]
|
|
418
|
-
).SerializeToString()
|
|
357
|
+
return State(
|
|
358
|
+
entries={
|
|
359
|
+
var.id: model.var_by_name(str(var.id)).x # type: ignore
|
|
360
|
+
for var in instance.raw.decision_variables
|
|
361
|
+
}
|
|
362
|
+
)
|