ommx-python-mip-adapter 0.1.1__tar.gz → 0.3.4__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.3.4}/PKG-INFO +24 -38
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.3.4}/README.md +19 -32
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.3.4}/ommx_python_mip_adapter/__init__.py +1 -1
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.3.4}/ommx_python_mip_adapter/adapter.py +90 -142
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.3.4}/ommx_python_mip_adapter.egg-info/PKG-INFO +24 -38
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.3.4}/ommx_python_mip_adapter.egg-info/SOURCES.txt +1 -12
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.3.4}/ommx_python_mip_adapter.egg-info/requires.txt +4 -3
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.3.4}/pyproject.toml +14 -18
- ommx_python_mip_adapter-0.3.4/tests/test_instance_to_model.py +90 -0
- ommx_python_mip_adapter-0.3.4/tests/test_integration.py +60 -0
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.3.4}/tests/test_model_to_instance.py +29 -33
- 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.3.4}/ommx_python_mip_adapter/exception.py +0 -0
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.3.4}/ommx_python_mip_adapter.egg-info/dependency_links.txt +0 -0
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.3.4}/ommx_python_mip_adapter.egg-info/top_level.txt +0 -0
- {ommx_python_mip_adapter-0.1.1 → ommx_python_mip_adapter-0.3.4}/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.3.4
|
|
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.4.0,>=0.3.4
|
|
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.3.4}/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, ConstraintDescription
|
|
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,32 @@ class OMMXInstanceBuilder:
|
|
|
147
138
|
)
|
|
148
139
|
|
|
149
140
|
decision_variables.append(
|
|
150
|
-
DecisionVariable(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
141
|
+
DecisionVariable.of_type(
|
|
142
|
+
kind,
|
|
143
|
+
var.idx,
|
|
144
|
+
lower=var.lb,
|
|
145
|
+
upper=var.ub,
|
|
146
|
+
description=DecisionVariable.Description(name=var.name),
|
|
155
147
|
)
|
|
156
148
|
)
|
|
157
149
|
|
|
158
150
|
return decision_variables
|
|
159
|
-
|
|
160
151
|
|
|
161
152
|
def _make_function_from_lin_expr(
|
|
162
153
|
self,
|
|
163
154
|
lin_expr: mip.LinExpr,
|
|
164
155
|
) -> Function:
|
|
165
156
|
terms = [
|
|
166
|
-
Linear.Term(id=var.idx, coefficient=coeff)
|
|
157
|
+
Linear.Term(id=var.idx, coefficient=coeff) # type: ignore
|
|
167
158
|
for var, coeff in lin_expr.expr.items()
|
|
168
159
|
]
|
|
169
|
-
constant: float = lin_expr.const
|
|
160
|
+
constant: float = lin_expr.const # type: ignore
|
|
170
161
|
|
|
171
162
|
# If the terms are empty, the function is a constant.
|
|
172
163
|
if len(terms) == 0:
|
|
173
164
|
return Function(constant=constant)
|
|
174
165
|
else:
|
|
175
|
-
return Function(
|
|
176
|
-
linear=Linear(terms=terms, constant=constant)
|
|
177
|
-
)
|
|
178
|
-
|
|
166
|
+
return Function(linear=Linear(terms=terms, constant=constant))
|
|
179
167
|
|
|
180
168
|
def _objective(self) -> Function:
|
|
181
169
|
# In Python-MIP, it is allowed not to set the objective function.
|
|
@@ -186,9 +174,8 @@ class OMMXInstanceBuilder:
|
|
|
186
174
|
objective = self._model.objective
|
|
187
175
|
except ParameterNotAvailable:
|
|
188
176
|
return Function(constant=0)
|
|
189
|
-
|
|
190
|
-
return self._make_function_from_lin_expr(objective)
|
|
191
177
|
|
|
178
|
+
return self._make_function_from_lin_expr(objective)
|
|
192
179
|
|
|
193
180
|
def _constraints(self) -> tp.List[Constraint]:
|
|
194
181
|
constraints = []
|
|
@@ -201,55 +188,53 @@ class OMMXInstanceBuilder:
|
|
|
201
188
|
if lin_expr.sense == "=":
|
|
202
189
|
constraint = Constraint(
|
|
203
190
|
id=id,
|
|
204
|
-
equality=
|
|
191
|
+
equality=Equality.EQUALITY_EQUAL_TO_ZERO,
|
|
205
192
|
function=self._make_function_from_lin_expr(lin_expr),
|
|
206
|
-
description=
|
|
193
|
+
description=ConstraintDescription(name=name),
|
|
207
194
|
)
|
|
208
195
|
elif lin_expr.sense == "<":
|
|
209
196
|
constraint = Constraint(
|
|
210
197
|
id=id,
|
|
211
|
-
equality=
|
|
198
|
+
equality=Equality.EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO,
|
|
212
199
|
function=self._make_function_from_lin_expr(lin_expr),
|
|
213
|
-
description=
|
|
200
|
+
description=ConstraintDescription(name=name),
|
|
214
201
|
)
|
|
215
202
|
elif lin_expr.sense == ">":
|
|
216
203
|
# `ommx.v1.Constraint` does not support `GREATER_THAN_OR_EQUAL_TO_ZERO`.
|
|
217
204
|
# So multiply the linear expression by -1.
|
|
218
205
|
constraint = Constraint(
|
|
219
206
|
id=id,
|
|
220
|
-
equality=
|
|
207
|
+
equality=Equality.EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO,
|
|
221
208
|
function=self._make_function_from_lin_expr(-lin_expr),
|
|
222
|
-
description=
|
|
209
|
+
description=ConstraintDescription(name=name),
|
|
223
210
|
)
|
|
224
211
|
else:
|
|
225
212
|
raise OMMXPythonMIPAdapterError(
|
|
226
213
|
f"Not supported constraint sense: "
|
|
227
214
|
f"name: {constr.name}, sense: {lin_expr.sense}"
|
|
228
215
|
)
|
|
229
|
-
|
|
216
|
+
|
|
230
217
|
constraints.append(constraint)
|
|
231
218
|
|
|
232
219
|
return constraints
|
|
233
220
|
|
|
234
|
-
|
|
235
221
|
def _sense(self):
|
|
236
222
|
if self._model.sense == mip.MAXIMIZE:
|
|
237
|
-
return Instance.
|
|
223
|
+
return Instance.MAXIMIZE
|
|
238
224
|
else:
|
|
239
|
-
return Instance.
|
|
225
|
+
return Instance.MINIMIZE
|
|
240
226
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
return Instance(
|
|
227
|
+
def build(self) -> Instance:
|
|
228
|
+
return Instance.from_components(
|
|
244
229
|
decision_variables=self._decision_variables(),
|
|
245
230
|
objective=self._objective(),
|
|
246
231
|
constraints=self._constraints(),
|
|
247
232
|
sense=self._sense(),
|
|
248
|
-
)
|
|
233
|
+
)
|
|
249
234
|
|
|
250
235
|
|
|
251
236
|
def instance_to_model(
|
|
252
|
-
|
|
237
|
+
instance: Instance,
|
|
253
238
|
*,
|
|
254
239
|
sense: str = mip.MINIMIZE,
|
|
255
240
|
solver_name: str = mip.CBC,
|
|
@@ -260,7 +245,7 @@ def instance_to_model(
|
|
|
260
245
|
|
|
261
246
|
Args:
|
|
262
247
|
ommx_instance_bytes (bytes): Serialized ommx.v1.Instance.
|
|
263
|
-
sense (str): mip.MINIMIZE or mip.MAXIMIZE.
|
|
248
|
+
sense (str): mip.MINIMIZE or mip.MAXIMIZE.
|
|
264
249
|
solver_name (str): mip.CBC or mip.GUROBI. Searches for which solver is available if not informed.
|
|
265
250
|
solver (mip.Solver): if this argument is provided, solver_name will be ignored.
|
|
266
251
|
|
|
@@ -274,37 +259,25 @@ def instance_to_model(
|
|
|
274
259
|
The following example of solving an unconstrained linear optimization problem with x1 as the objective function.
|
|
275
260
|
|
|
276
261
|
>>> 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
|
-
... ),
|
|
262
|
+
>>> from ommx.v1 import Instance, DecisionVariable
|
|
263
|
+
|
|
264
|
+
>>> x1 = DecisionVariable.integer(1, lower=0, upper=5)
|
|
265
|
+
>>> ommx_instance = Instance.from_components(
|
|
266
|
+
... decision_variables=[x1],
|
|
267
|
+
... objective=x1,
|
|
268
|
+
... constraints=[],
|
|
269
|
+
... sense=Instance.MINIMIZE,
|
|
295
270
|
... )
|
|
296
|
-
>>>
|
|
297
|
-
>>> model = adapter.instance_to_model(ommx_instance_bytes)
|
|
271
|
+
>>> model = adapter.instance_to_model(ommx_instance)
|
|
298
272
|
>>> model.optimize()
|
|
299
273
|
<OptimizationStatus.OPTIMAL: 0>
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
>>> SolutionList.FromString(ommx_solutions_bytes).solutions[0].entries
|
|
274
|
+
|
|
275
|
+
>>> ommx_solutions = adapter.model_to_solution(model, ommx_instance)
|
|
276
|
+
>>> ommx_solutions.entries
|
|
304
277
|
{1: 0.0}
|
|
305
278
|
"""
|
|
306
279
|
builder = PythonMIPBuilder(
|
|
307
|
-
|
|
280
|
+
instance,
|
|
308
281
|
sense=sense,
|
|
309
282
|
solver_name=solver_name,
|
|
310
283
|
solver=solver,
|
|
@@ -312,13 +285,13 @@ def instance_to_model(
|
|
|
312
285
|
return builder.build()
|
|
313
286
|
|
|
314
287
|
|
|
315
|
-
def model_to_instance(model: mip.Model) ->
|
|
288
|
+
def model_to_instance(model: mip.Model) -> Instance:
|
|
316
289
|
"""
|
|
317
290
|
The function to convert Python-MIP Model to ommx.v1.Instance.
|
|
318
291
|
|
|
319
292
|
Args:
|
|
320
293
|
model (mip.Model): Python-MIP Model.
|
|
321
|
-
|
|
294
|
+
|
|
322
295
|
Returns:
|
|
323
296
|
bytes: Serialized ommx.v1.Instance.
|
|
324
297
|
|
|
@@ -328,14 +301,12 @@ def model_to_instance(model: mip.Model) -> bytes:
|
|
|
328
301
|
Examples:
|
|
329
302
|
>>> import mip
|
|
330
303
|
>>> import ommx_python_mip_adapter as adapter
|
|
331
|
-
>>> from ommx.v1.instance_pb2 import Instance
|
|
332
304
|
>>> model = mip.Model()
|
|
333
305
|
>>> x1=model.add_var(name="1", var_type=mip.INTEGER, lb=0, ub=5)
|
|
334
306
|
>>> x2=model.add_var(name="2", var_type=mip.CONTINUOUS, lb=0, ub=5)
|
|
335
307
|
>>> model.objective = - x1 - 2 * x2
|
|
336
308
|
>>> constr = model.add_constr(x1 + x2 - 6 <= 0)
|
|
337
|
-
>>>
|
|
338
|
-
>>> ommx_instance = Instance.FromString(ommx_instance_bytes)
|
|
309
|
+
>>> ommx_instance = adapter.model_to_instance(model)
|
|
339
310
|
"""
|
|
340
311
|
builder = OMMXInstanceBuilder(model)
|
|
341
312
|
return builder.build()
|
|
@@ -343,8 +314,8 @@ def model_to_instance(model: mip.Model) -> bytes:
|
|
|
343
314
|
|
|
344
315
|
def model_to_solution(
|
|
345
316
|
model: mip.Model,
|
|
346
|
-
|
|
347
|
-
) ->
|
|
317
|
+
instance: Instance,
|
|
318
|
+
) -> State:
|
|
348
319
|
"""
|
|
349
320
|
The function to create ommx.v1.SolutionList from optimized Python-MIP Model.
|
|
350
321
|
|
|
@@ -362,33 +333,21 @@ def model_to_solution(
|
|
|
362
333
|
The following example of solving an unconstrained linear optimization problem with x1 as the objective function.
|
|
363
334
|
|
|
364
335
|
>>> 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
|
-
... ),
|
|
336
|
+
>>> from ommx.v1 import Instance, DecisionVariable
|
|
337
|
+
|
|
338
|
+
>>> x1 = DecisionVariable.integer(1, lower=0, upper=5)
|
|
339
|
+
>>> ommx_instance = Instance.from_components(
|
|
340
|
+
... decision_variables=[x1],
|
|
341
|
+
... objective=x1,
|
|
342
|
+
... constraints=[],
|
|
343
|
+
... sense=Instance.MINIMIZE,
|
|
383
344
|
... )
|
|
384
|
-
>>>
|
|
385
|
-
>>> model = adapter.instance_to_model(ommx_instance_bytes)
|
|
345
|
+
>>> model = adapter.instance_to_model(ommx_instance)
|
|
386
346
|
>>> model.optimize()
|
|
387
347
|
<OptimizationStatus.OPTIMAL: 0>
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
>>> SolutionList.FromString(ommx_solutions_bytes).solutions[0].entries
|
|
348
|
+
|
|
349
|
+
>>> ommx_solutions = adapter.model_to_solution(model, ommx_instance)
|
|
350
|
+
>>> ommx_solutions.entries
|
|
392
351
|
{1: 0.0}
|
|
393
352
|
"""
|
|
394
353
|
if not (
|
|
@@ -399,20 +358,9 @@ def model_to_solution(
|
|
|
399
358
|
"`model.status` must be `OPTIMAL` or `FEASIBLE`."
|
|
400
359
|
)
|
|
401
360
|
|
|
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()
|
|
361
|
+
return State(
|
|
362
|
+
entries={
|
|
363
|
+
var.id: model.var_by_name(str(var.id)).x # type: ignore
|
|
364
|
+
for var in instance.raw.decision_variables
|
|
365
|
+
}
|
|
366
|
+
)
|