ommx-python-mip-adapter 0.1.0__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.
Files changed (30) hide show
  1. {ommx_python_mip_adapter-0.1.0 → ommx_python_mip_adapter-0.3.4}/PKG-INFO +55 -41
  2. ommx_python_mip_adapter-0.3.4/README.md +87 -0
  3. {ommx_python_mip_adapter-0.1.0 → ommx_python_mip_adapter-0.3.4}/ommx_python_mip_adapter/__init__.py +3 -1
  4. ommx_python_mip_adapter-0.3.4/ommx_python_mip_adapter/adapter.py +366 -0
  5. {ommx_python_mip_adapter-0.1.0 → ommx_python_mip_adapter-0.3.4}/ommx_python_mip_adapter.egg-info/PKG-INFO +55 -41
  6. {ommx_python_mip_adapter-0.1.0 → ommx_python_mip_adapter-0.3.4}/ommx_python_mip_adapter.egg-info/SOURCES.txt +3 -13
  7. {ommx_python_mip_adapter-0.1.0 → ommx_python_mip_adapter-0.3.4}/ommx_python_mip_adapter.egg-info/requires.txt +4 -3
  8. {ommx_python_mip_adapter-0.1.0 → ommx_python_mip_adapter-0.3.4}/pyproject.toml +14 -18
  9. ommx_python_mip_adapter-0.3.4/tests/test_instance_to_model.py +90 -0
  10. ommx_python_mip_adapter-0.3.4/tests/test_integration.py +60 -0
  11. ommx_python_mip_adapter-0.3.4/tests/test_model_to_instance.py +202 -0
  12. ommx_python_mip_adapter-0.1.0/.github/workflows/docs.yml +0 -72
  13. ommx_python_mip_adapter-0.1.0/.github/workflows/publish.yml +0 -49
  14. ommx_python_mip_adapter-0.1.0/.github/workflows/test_and_lint.yml +0 -75
  15. ommx_python_mip_adapter-0.1.0/.gitignore +0 -162
  16. ommx_python_mip_adapter-0.1.0/LICENSE-APACHE +0 -201
  17. ommx_python_mip_adapter-0.1.0/LICENSE-MIT +0 -21
  18. ommx_python_mip_adapter-0.1.0/README.md +0 -72
  19. ommx_python_mip_adapter-0.1.0/docs/source/conf.py +0 -50
  20. ommx_python_mip_adapter-0.1.0/docs/source/index.rst +0 -20
  21. ommx_python_mip_adapter-0.1.0/ommx_python_mip_adapter/_version.py +0 -16
  22. ommx_python_mip_adapter-0.1.0/ommx_python_mip_adapter/adapter.py +0 -262
  23. ommx_python_mip_adapter-0.1.0/tests/conftest.py +0 -0
  24. ommx_python_mip_adapter-0.1.0/tests/test_adapter.py +0 -147
  25. ommx_python_mip_adapter-0.1.0/tests/test_import.py +0 -9
  26. ommx_python_mip_adapter-0.1.0/tests/test_integration.py +0 -142
  27. {ommx_python_mip_adapter-0.1.0 → ommx_python_mip_adapter-0.3.4}/ommx_python_mip_adapter/exception.py +0 -0
  28. {ommx_python_mip_adapter-0.1.0 → ommx_python_mip_adapter-0.3.4}/ommx_python_mip_adapter.egg-info/dependency_links.txt +0 -0
  29. {ommx_python_mip_adapter-0.1.0 → ommx_python_mip_adapter-0.3.4}/ommx_python_mip_adapter.egg-info/top_level.txt +0 -0
  30. {ommx_python_mip_adapter-0.1.0 → 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.1.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
- License-File: LICENSE-APACHE
19
- License-File: LICENSE-MIT
20
- Requires-Dist: ommx<0.2.0,>=0.1.1
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,34 +23,47 @@ 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 adaptor for Python-MIP
32
+ OMMX adapter for Python-MIP
34
33
  ============================
35
- ![PyPI - Version](https://img.shields.io/pypi/v/ommx-python-mip-adapter)
34
+ [![PyPI - Version](https://img.shields.io/pypi/v/ommx-python-mip-adapter)](https://pypi.org/project/ommx-python-mip-adapter/)
36
35
 
37
- This package provides an adaptor for the [Python-MIP](https://www.python-mip.com/) from/to [OMMX](https://github.com/Jij-Inc/ommx)
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
  -----------------------------------------
41
40
  ```mermaid
42
41
  sequenceDiagram
43
- participant O as Other OMMX toolchain
42
+ participant U as User
44
43
  participant A as Adapter
45
44
  participant P as Python-MIP
46
- O->>A: ommx::Instance and Parameters for Python-MIP;
47
- A->>P: Translate into Python-MIP input
45
+ U->>A: ommx.v1.Instance
46
+ A->>U: Python-MIP model
47
+ U->>P: Python-MIP model and Parameters for Python-MIP;
48
48
  P->>P: Solve with CBC, Gurobi, or other solvers
49
- P->>A: Solution
50
- A->>O: ommx:Solution
49
+ P->>U: Optimized model
50
+ U->>A: Optimized model and ommx.v1.Instance
51
+ A->>U: ommx:SolutionList
51
52
  ```
52
53
 
53
54
  Python-MIP as a user interface to create OMMX instance
54
55
  -------------------------------------------------------
55
- TBW
56
+ ```mermaid
57
+ sequenceDiagram
58
+ participant U as User
59
+ participant A as Adapter
60
+ participant O as Other OMMX toolchain
61
+ U->>A: Python-MIP model
62
+ A->>U: ommx.v1.Instance
63
+ U->>O: ommx.v1.Instance and Parameters for other solver
64
+ O->>O: Solve the instance with other solver using other adapter
65
+ O->>U: ommx.v1.Solution
66
+ ```
56
67
 
57
68
  Usage
58
69
  ======
@@ -66,37 +77,40 @@ Python-MIP can be used through `ommx-python-mip-adapter` by using the following:
66
77
 
67
78
  ```python markdown-code-runner
68
79
  import ommx_python_mip_adapter as adapter
69
- from ommx.v1.decision_variables_pb2 import DecisionVariable, Bound
70
- from ommx.v1.instance_pb2 import Instance
71
- from ommx.v1.function_pb2 import Function
72
- from ommx.v1.linear_pb2 import Linear
73
- from ommx.v1.solution_pb2 import SolutionList
74
-
75
- ommx_instance = Instance(
76
- decision_variables=[
77
- DecisionVariable(
78
- id=1,
79
- kind=DecisionVariable.KIND_INTEGER,
80
- bound=Bound(lower=0, upper=5),
81
- ),
82
- ],
83
- objective=Function(
84
- linear=Linear(
85
- terms=[Linear.Term(id=1, coefficient=1)]
86
- ),
87
- ),
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,
88
88
  )
89
- ommx_instance_bytes = ommx_instance.SerializeToString()
90
89
 
91
90
  # Convert from `ommx.v1.Instance` to `mip.Model`
92
- model = adapter.instance_to_model(ommx_instance_bytes)
91
+ model = adapter.instance_to_model(ommx_instance)
93
92
  model.optimize()
94
- # Create `ommx.v1.SolutionList` from Optimized `mip.Model`
95
- ommx_solutions_bytes = adapter.model_to_solution(
96
- model, ommx_instance_bytes
97
- )
93
+ # Create `ommx.v1.State` from Optimized `mip.Model`
94
+ ommx_solutions = adapter.model_to_solution(model, ommx_instance)
95
+
96
+ print(ommx_solutions)
97
+ ```
98
+
99
+ You can get `ommx.v1.Instance` from a Python-MIP model as the following:
100
+
101
+ ```python markdown-code-runner
102
+ import mip
103
+ import ommx_python_mip_adapter as adapter
104
+
105
+ model = mip.Model()
106
+ x1=model.add_var(name="1", var_type=mip.INTEGER, lb=0, ub=5)
107
+ x2=model.add_var(name="2", var_type=mip.CONTINUOUS, lb=0, ub=5)
108
+ model.objective = - x1 - 2 * x2
109
+ model.add_constr(x1 + x2 - 6 <= 0)
110
+
111
+ ommx_instance = adapter.model_to_instance(model)
98
112
 
99
- print(SolutionList.FromString(ommx_solutions_bytes))
113
+ print(ommx_instance)
100
114
  ```
101
115
 
102
116
  Reference
@@ -0,0 +1,87 @@
1
+ OMMX adapter for Python-MIP
2
+ ============================
3
+ [![PyPI - Version](https://img.shields.io/pypi/v/ommx-python-mip-adapter)](https://pypi.org/project/ommx-python-mip-adapter/)
4
+
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
+
7
+ Python-MIP as a solver in OMMX toolchain
8
+ -----------------------------------------
9
+ ```mermaid
10
+ sequenceDiagram
11
+ participant U as User
12
+ participant A as Adapter
13
+ participant P as Python-MIP
14
+ U->>A: ommx.v1.Instance
15
+ A->>U: Python-MIP model
16
+ U->>P: Python-MIP model and Parameters for Python-MIP;
17
+ P->>P: Solve with CBC, Gurobi, or other solvers
18
+ P->>U: Optimized model
19
+ U->>A: Optimized model and ommx.v1.Instance
20
+ A->>U: ommx:SolutionList
21
+ ```
22
+
23
+ Python-MIP as a user interface to create OMMX instance
24
+ -------------------------------------------------------
25
+ ```mermaid
26
+ sequenceDiagram
27
+ participant U as User
28
+ participant A as Adapter
29
+ participant O as Other OMMX toolchain
30
+ U->>A: Python-MIP model
31
+ A->>U: ommx.v1.Instance
32
+ U->>O: ommx.v1.Instance and Parameters for other solver
33
+ O->>O: Solve the instance with other solver using other adapter
34
+ O->>U: ommx.v1.Solution
35
+ ```
36
+
37
+ Usage
38
+ ======
39
+ `ommx-python-mip-adapter` can be installed from PyPI as follows:
40
+
41
+ ```bash
42
+ pip install ommx-python-mip-adapter
43
+ ```
44
+
45
+ Python-MIP can be used through `ommx-python-mip-adapter` by using the following:
46
+
47
+ ```python markdown-code-runner
48
+ import ommx_python_mip_adapter as adapter
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,
57
+ )
58
+
59
+ # Convert from `ommx.v1.Instance` to `mip.Model`
60
+ model = adapter.instance_to_model(ommx_instance)
61
+ model.optimize()
62
+ # Create `ommx.v1.State` from Optimized `mip.Model`
63
+ ommx_solutions = adapter.model_to_solution(model, ommx_instance)
64
+
65
+ print(ommx_solutions)
66
+ ```
67
+
68
+ You can get `ommx.v1.Instance` from a Python-MIP model as the following:
69
+
70
+ ```python markdown-code-runner
71
+ import mip
72
+ import ommx_python_mip_adapter as adapter
73
+
74
+ model = mip.Model()
75
+ x1=model.add_var(name="1", var_type=mip.INTEGER, lb=0, ub=5)
76
+ x2=model.add_var(name="2", var_type=mip.CONTINUOUS, lb=0, ub=5)
77
+ model.objective = - x1 - 2 * x2
78
+ model.add_constr(x1 + x2 - 6 <= 0)
79
+
80
+ ommx_instance = adapter.model_to_instance(model)
81
+
82
+ print(ommx_instance)
83
+ ```
84
+
85
+ Reference
86
+ ==============
87
+ - [OMMX-Python-MIP-Adapter API Reference](https://jij-inc.github.io/ommx-python-mip-adapter/index.html)
@@ -1,9 +1,11 @@
1
1
  from ommx_python_mip_adapter.adapter import (
2
2
  instance_to_model,
3
+ model_to_instance,
3
4
  model_to_solution,
4
5
  )
5
6
 
6
7
  __all__ = [
7
8
  "instance_to_model",
9
+ "model_to_instance",
8
10
  "model_to_solution",
9
- ]
11
+ ]
@@ -0,0 +1,366 @@
1
+ import typing as tp
2
+
3
+ import mip
4
+
5
+ from mip.exceptions import ParameterNotAvailable
6
+ from ommx.v1.constraint_pb2 import Constraint, Equality, ConstraintDescription
7
+ from ommx.v1.function_pb2 import Function
8
+ from ommx.v1.linear_pb2 import Linear
9
+ from ommx.v1.solution_pb2 import State
10
+ from ommx.v1 import Instance, DecisionVariable
11
+
12
+ from ommx_python_mip_adapter.exception import OMMXPythonMIPAdapterError
13
+
14
+
15
+ class PythonMIPBuilder:
16
+ def __init__(
17
+ self,
18
+ instance: Instance,
19
+ *,
20
+ sense: str = mip.MINIMIZE,
21
+ solver_name: str = mip.CBC,
22
+ solver: tp.Optional[mip.Solver] = None,
23
+ ):
24
+ self._ommx_instance = instance.raw
25
+ self._model = mip.Model(
26
+ sense=sense,
27
+ solver_name=solver_name,
28
+ solver=solver,
29
+ )
30
+
31
+ def _set_decision_variables(self):
32
+ for var in self._ommx_instance.decision_variables:
33
+ if var.kind == DecisionVariable.BINARY:
34
+ self._model.add_var(
35
+ name=str(var.id),
36
+ var_type=mip.BINARY,
37
+ )
38
+ elif var.kind == DecisionVariable.INTEGER:
39
+ self._model.add_var(
40
+ name=str(var.id),
41
+ var_type=mip.INTEGER,
42
+ lb=var.bound.lower, # type: ignore
43
+ ub=var.bound.upper, # type: ignore
44
+ )
45
+ elif var.kind == DecisionVariable.CONTINUOUS:
46
+ self._model.add_var(
47
+ name=str(var.id),
48
+ var_type=mip.CONTINUOUS,
49
+ lb=var.bound.lower, # type: ignore
50
+ ub=var.bound.upper, # type: ignore
51
+ )
52
+ else:
53
+ raise OMMXPythonMIPAdapterError(
54
+ f"Not supported decision variable kind: "
55
+ f"id: {var.id}, kind: {var.kind}"
56
+ )
57
+
58
+ def _make_linear_expr(
59
+ self,
60
+ ommx_function: Function,
61
+ ) -> mip.LinExpr:
62
+ ommx_linear = ommx_function.linear
63
+
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
71
+
72
+ def _set_objective_function(self):
73
+ ommx_objective = self._ommx_instance.objective
74
+
75
+ if ommx_objective.HasField("constant"):
76
+ self._model.objective = ommx_objective.constant # type: ignore
77
+ elif ommx_objective.HasField("linear"):
78
+ self._model.objective = self._make_linear_expr(ommx_objective)
79
+ else:
80
+ raise OMMXPythonMIPAdapterError(
81
+ "The objective function must be either `constant` or `linear`."
82
+ )
83
+
84
+ def _set_constraints(self):
85
+ ommx_constraints = self._ommx_instance.constraints
86
+
87
+ for constraint in ommx_constraints:
88
+ if not constraint.function.HasField("linear"):
89
+ raise OMMXPythonMIPAdapterError(
90
+ f"Only linear constraints are supported: "
91
+ f"id: {constraint.id}, "
92
+ f"type: {constraint.function.WhichOneof('function')}"
93
+ )
94
+
95
+ lin_expr = self._make_linear_expr(constraint.function)
96
+
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
101
+ else:
102
+ raise OMMXPythonMIPAdapterError(
103
+ f"Not supported constraint equality: "
104
+ f"id: {constraint.id}, equality: {constraint.equality}"
105
+ )
106
+
107
+ self._model.add_constr(constr_expr, name=str(constraint.id))
108
+
109
+ def build(self) -> mip.Model:
110
+ self._set_decision_variables()
111
+ self._set_objective_function()
112
+ self._set_constraints()
113
+
114
+ return self._model
115
+
116
+
117
+ class OMMXInstanceBuilder:
118
+ def __init__(
119
+ self,
120
+ model: mip.Model,
121
+ ):
122
+ self._model = model
123
+
124
+ def _decision_variables(self) -> tp.List[DecisionVariable]:
125
+ decision_variables = []
126
+
127
+ for var in self._model.vars:
128
+ if var.var_type == mip.BINARY:
129
+ kind = DecisionVariable.BINARY
130
+ elif var.var_type == mip.INTEGER:
131
+ kind = DecisionVariable.INTEGER
132
+ elif var.var_type == mip.CONTINUOUS:
133
+ kind = DecisionVariable.CONTINUOUS
134
+ else:
135
+ raise OMMXPythonMIPAdapterError(
136
+ f"Not supported variable type. "
137
+ f"idx: {var.idx} name: {var.name}, type: {var.var_type}"
138
+ )
139
+
140
+ decision_variables.append(
141
+ DecisionVariable.of_type(
142
+ kind,
143
+ var.idx,
144
+ lower=var.lb,
145
+ upper=var.ub,
146
+ description=DecisionVariable.Description(name=var.name),
147
+ )
148
+ )
149
+
150
+ return decision_variables
151
+
152
+ def _make_function_from_lin_expr(
153
+ self,
154
+ lin_expr: mip.LinExpr,
155
+ ) -> Function:
156
+ terms = [
157
+ Linear.Term(id=var.idx, coefficient=coeff) # type: ignore
158
+ for var, coeff in lin_expr.expr.items()
159
+ ]
160
+ constant: float = lin_expr.const # type: ignore
161
+
162
+ # If the terms are empty, the function is a constant.
163
+ if len(terms) == 0:
164
+ return Function(constant=constant)
165
+ else:
166
+ return Function(linear=Linear(terms=terms, constant=constant))
167
+
168
+ def _objective(self) -> Function:
169
+ # In Python-MIP, it is allowed not to set the objective function.
170
+ # If it isn't set, the model behaves as if the objective function is set to 0.
171
+ # However, an error occurs when accessing `.objective`.
172
+ # So if an error occurs, treat the objective function as 0.
173
+ try:
174
+ objective = self._model.objective
175
+ except ParameterNotAvailable:
176
+ return Function(constant=0)
177
+
178
+ return self._make_function_from_lin_expr(objective)
179
+
180
+ def _constraints(self) -> tp.List[Constraint]:
181
+ constraints = []
182
+
183
+ for constr in self._model.constrs:
184
+ id = constr.idx
185
+ lin_expr = constr.expr
186
+ name = constr.name
187
+
188
+ if lin_expr.sense == "=":
189
+ constraint = Constraint(
190
+ id=id,
191
+ equality=Equality.EQUALITY_EQUAL_TO_ZERO,
192
+ function=self._make_function_from_lin_expr(lin_expr),
193
+ description=ConstraintDescription(name=name),
194
+ )
195
+ elif lin_expr.sense == "<":
196
+ constraint = Constraint(
197
+ id=id,
198
+ equality=Equality.EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO,
199
+ function=self._make_function_from_lin_expr(lin_expr),
200
+ description=ConstraintDescription(name=name),
201
+ )
202
+ elif lin_expr.sense == ">":
203
+ # `ommx.v1.Constraint` does not support `GREATER_THAN_OR_EQUAL_TO_ZERO`.
204
+ # So multiply the linear expression by -1.
205
+ constraint = Constraint(
206
+ id=id,
207
+ equality=Equality.EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO,
208
+ function=self._make_function_from_lin_expr(-lin_expr),
209
+ description=ConstraintDescription(name=name),
210
+ )
211
+ else:
212
+ raise OMMXPythonMIPAdapterError(
213
+ f"Not supported constraint sense: "
214
+ f"name: {constr.name}, sense: {lin_expr.sense}"
215
+ )
216
+
217
+ constraints.append(constraint)
218
+
219
+ return constraints
220
+
221
+ def _sense(self):
222
+ if self._model.sense == mip.MAXIMIZE:
223
+ return Instance.MAXIMIZE
224
+ else:
225
+ return Instance.MINIMIZE
226
+
227
+ def build(self) -> Instance:
228
+ return Instance.from_components(
229
+ decision_variables=self._decision_variables(),
230
+ objective=self._objective(),
231
+ constraints=self._constraints(),
232
+ sense=self._sense(),
233
+ )
234
+
235
+
236
+ def instance_to_model(
237
+ instance: Instance,
238
+ *,
239
+ sense: str = mip.MINIMIZE,
240
+ solver_name: str = mip.CBC,
241
+ solver: tp.Optional[mip.Solver] = None,
242
+ ) -> mip.Model:
243
+ """
244
+ The function to convert ommx.v1.Instance to Python-MIP Model.
245
+
246
+ Args:
247
+ ommx_instance_bytes (bytes): Serialized ommx.v1.Instance.
248
+ sense (str): mip.MINIMIZE or mip.MAXIMIZE.
249
+ solver_name (str): mip.CBC or mip.GUROBI. Searches for which solver is available if not informed.
250
+ solver (mip.Solver): if this argument is provided, solver_name will be ignored.
251
+
252
+ Returns:
253
+ mip.Model: Python-MIP Model converted from ommx.v1.Instance.
254
+
255
+ Raises:
256
+ OMMXPythonMIPAdapterError: If converting is not possible.
257
+
258
+ Examples:
259
+ The following example of solving an unconstrained linear optimization problem with x1 as the objective function.
260
+
261
+ >>> import ommx_python_mip_adapter as adapter
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,
270
+ ... )
271
+ >>> model = adapter.instance_to_model(ommx_instance)
272
+ >>> model.optimize()
273
+ <OptimizationStatus.OPTIMAL: 0>
274
+
275
+ >>> ommx_solutions = adapter.model_to_solution(model, ommx_instance)
276
+ >>> ommx_solutions.entries
277
+ {1: 0.0}
278
+ """
279
+ builder = PythonMIPBuilder(
280
+ instance,
281
+ sense=sense,
282
+ solver_name=solver_name,
283
+ solver=solver,
284
+ )
285
+ return builder.build()
286
+
287
+
288
+ def model_to_instance(model: mip.Model) -> Instance:
289
+ """
290
+ The function to convert Python-MIP Model to ommx.v1.Instance.
291
+
292
+ Args:
293
+ model (mip.Model): Python-MIP Model.
294
+
295
+ Returns:
296
+ bytes: Serialized ommx.v1.Instance.
297
+
298
+ Raises:
299
+ OMMXPythonMIPAdapterError: If converting is not possible.
300
+
301
+ Examples:
302
+ >>> import mip
303
+ >>> import ommx_python_mip_adapter as adapter
304
+ >>> model = mip.Model()
305
+ >>> x1=model.add_var(name="1", var_type=mip.INTEGER, lb=0, ub=5)
306
+ >>> x2=model.add_var(name="2", var_type=mip.CONTINUOUS, lb=0, ub=5)
307
+ >>> model.objective = - x1 - 2 * x2
308
+ >>> constr = model.add_constr(x1 + x2 - 6 <= 0)
309
+ >>> ommx_instance = adapter.model_to_instance(model)
310
+ """
311
+ builder = OMMXInstanceBuilder(model)
312
+ return builder.build()
313
+
314
+
315
+ def model_to_solution(
316
+ model: mip.Model,
317
+ instance: Instance,
318
+ ) -> State:
319
+ """
320
+ The function to create ommx.v1.SolutionList from optimized Python-MIP Model.
321
+
322
+ Args:
323
+ model (mip.Model): Optimized Python-MIP Model.
324
+ ommx_instance_bytes (bytes): Serialized ommx.v1.Instance.
325
+
326
+ Returns:
327
+ bytes: Serialized ommx.v1.SolutionList
328
+
329
+ Raises:
330
+ OMMXPythonMIPAdapterError: When ommx.v1.SolutionList cannot be created.
331
+
332
+ Examples:
333
+ The following example of solving an unconstrained linear optimization problem with x1 as the objective function.
334
+
335
+ >>> import ommx_python_mip_adapter as adapter
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,
344
+ ... )
345
+ >>> model = adapter.instance_to_model(ommx_instance)
346
+ >>> model.optimize()
347
+ <OptimizationStatus.OPTIMAL: 0>
348
+
349
+ >>> ommx_solutions = adapter.model_to_solution(model, ommx_instance)
350
+ >>> ommx_solutions.entries
351
+ {1: 0.0}
352
+ """
353
+ if not (
354
+ model.status == mip.OptimizationStatus.OPTIMAL
355
+ or model.status == mip.OptimizationStatus.FEASIBLE
356
+ ):
357
+ raise OMMXPythonMIPAdapterError(
358
+ "`model.status` must be `OPTIMAL` or `FEASIBLE`."
359
+ )
360
+
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
+ )