ommx-python-mip-adapter 0.0.2__tar.gz → 0.1.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.0/.github/workflows/docs.yml +72 -0
- {ommx_python_mip_adapter-0.0.2 → ommx_python_mip_adapter-0.1.0}/.github/workflows/test_and_lint.yml +23 -2
- ommx_python_mip_adapter-0.1.0/PKG-INFO +104 -0
- ommx_python_mip_adapter-0.1.0/README.md +72 -0
- ommx_python_mip_adapter-0.1.0/docs/source/conf.py +50 -0
- ommx_python_mip_adapter-0.1.0/docs/source/index.rst +20 -0
- ommx_python_mip_adapter-0.1.0/ommx_python_mip_adapter/__init__.py +9 -0
- {ommx_python_mip_adapter-0.0.2 → ommx_python_mip_adapter-0.1.0}/ommx_python_mip_adapter/_version.py +2 -2
- ommx_python_mip_adapter-0.1.0/ommx_python_mip_adapter/adapter.py +262 -0
- ommx_python_mip_adapter-0.1.0/ommx_python_mip_adapter/exception.py +2 -0
- ommx_python_mip_adapter-0.1.0/ommx_python_mip_adapter.egg-info/PKG-INFO +104 -0
- {ommx_python_mip_adapter-0.0.2 → ommx_python_mip_adapter-0.1.0}/ommx_python_mip_adapter.egg-info/SOURCES.txt +8 -2
- ommx_python_mip_adapter-0.1.0/ommx_python_mip_adapter.egg-info/requires.txt +13 -0
- {ommx_python_mip_adapter-0.0.2 → ommx_python_mip_adapter-0.1.0}/pyproject.toml +9 -2
- ommx_python_mip_adapter-0.1.0/tests/test_adapter.py +147 -0
- ommx_python_mip_adapter-0.1.0/tests/test_import.py +9 -0
- ommx_python_mip_adapter-0.1.0/tests/test_integration.py +142 -0
- ommx_python_mip_adapter-0.0.2/PKG-INFO +0 -47
- ommx_python_mip_adapter-0.0.2/README.md +0 -22
- ommx_python_mip_adapter-0.0.2/ommx_python_mip_adapter/__init__.py +0 -0
- ommx_python_mip_adapter-0.0.2/ommx_python_mip_adapter/ommx_to_python_mip.py +0 -2
- ommx_python_mip_adapter-0.0.2/ommx_python_mip_adapter.egg-info/PKG-INFO +0 -47
- ommx_python_mip_adapter-0.0.2/ommx_python_mip_adapter.egg-info/requires.txt +0 -6
- ommx_python_mip_adapter-0.0.2/tests/test_omm_to_python_mip.py +0 -2
- {ommx_python_mip_adapter-0.0.2 → ommx_python_mip_adapter-0.1.0}/.github/workflows/publish.yml +0 -0
- {ommx_python_mip_adapter-0.0.2 → ommx_python_mip_adapter-0.1.0}/.gitignore +0 -0
- {ommx_python_mip_adapter-0.0.2 → ommx_python_mip_adapter-0.1.0}/LICENSE-APACHE +0 -0
- {ommx_python_mip_adapter-0.0.2 → ommx_python_mip_adapter-0.1.0}/LICENSE-MIT +0 -0
- {ommx_python_mip_adapter-0.0.2 → ommx_python_mip_adapter-0.1.0}/ommx_python_mip_adapter.egg-info/dependency_links.txt +0 -0
- {ommx_python_mip_adapter-0.0.2 → ommx_python_mip_adapter-0.1.0}/ommx_python_mip_adapter.egg-info/top_level.txt +0 -0
- {ommx_python_mip_adapter-0.0.2 → ommx_python_mip_adapter-0.1.0}/setup.cfg +0 -0
- {ommx_python_mip_adapter-0.0.2 → ommx_python_mip_adapter-0.1.0}/tests/conftest.py +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
name: Documents
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
pages: write
|
|
14
|
+
id-token: write
|
|
15
|
+
|
|
16
|
+
# Allow one concurrent deployment
|
|
17
|
+
concurrency:
|
|
18
|
+
group: "pages"
|
|
19
|
+
cancel-in-progress: true
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
build:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- name: Checkout
|
|
26
|
+
uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- name: Setup Python
|
|
29
|
+
uses: actions/setup-python@v5
|
|
30
|
+
with:
|
|
31
|
+
python-version: "3.8"
|
|
32
|
+
|
|
33
|
+
- name: Install dependencies
|
|
34
|
+
run: |
|
|
35
|
+
python -m pip install ".[dev]"
|
|
36
|
+
|
|
37
|
+
- name: Build Python documentation
|
|
38
|
+
run: |
|
|
39
|
+
sphinx-build -b html ./docs/source ./docs/build
|
|
40
|
+
|
|
41
|
+
- name: Upload HTML
|
|
42
|
+
uses: actions/upload-artifact@v4
|
|
43
|
+
with:
|
|
44
|
+
name: docs
|
|
45
|
+
path: ./docs/build
|
|
46
|
+
retention-days: 30
|
|
47
|
+
|
|
48
|
+
deploy:
|
|
49
|
+
if: github.ref == 'refs/heads/main'
|
|
50
|
+
needs: build
|
|
51
|
+
environment:
|
|
52
|
+
name: github-pages
|
|
53
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- name: Download HTML
|
|
57
|
+
uses: actions/download-artifact@v4
|
|
58
|
+
with:
|
|
59
|
+
name: docs
|
|
60
|
+
path: .
|
|
61
|
+
|
|
62
|
+
- name: Configure GitHub Pages
|
|
63
|
+
uses: actions/configure-pages@v4
|
|
64
|
+
|
|
65
|
+
- name: Upload artifact
|
|
66
|
+
uses: actions/upload-pages-artifact@v3
|
|
67
|
+
with:
|
|
68
|
+
path: "."
|
|
69
|
+
|
|
70
|
+
- name: Deploy to GitHub Pages
|
|
71
|
+
id: deployment
|
|
72
|
+
uses: actions/deploy-pages@v4
|
{ommx_python_mip_adapter-0.0.2 → ommx_python_mip_adapter-0.1.0}/.github/workflows/test_and_lint.yml
RENAMED
|
@@ -28,7 +28,7 @@ jobs:
|
|
|
28
28
|
|
|
29
29
|
- name: Run Tests
|
|
30
30
|
run: |
|
|
31
|
-
python -m pytest
|
|
31
|
+
python -m pytest --doctest-modules -vv
|
|
32
32
|
|
|
33
33
|
lint:
|
|
34
34
|
runs-on: ubuntu-latest
|
|
@@ -50,5 +50,26 @@ jobs:
|
|
|
50
50
|
|
|
51
51
|
- name: Lint
|
|
52
52
|
run: |
|
|
53
|
-
python -m mypy ./ommx_python_mip_adapter
|
|
54
53
|
python -m pyright ./ommx_python_mip_adapter
|
|
54
|
+
|
|
55
|
+
markdown-code-runner:
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
strategy:
|
|
58
|
+
fail-fast: false
|
|
59
|
+
matrix:
|
|
60
|
+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@v4
|
|
63
|
+
|
|
64
|
+
- name: Setup Python ${{ matrix.python-version }}
|
|
65
|
+
uses: actions/setup-python@v5
|
|
66
|
+
with:
|
|
67
|
+
python-version: ${{ matrix.python-version }}
|
|
68
|
+
|
|
69
|
+
- name: Install Dependencies
|
|
70
|
+
run: |
|
|
71
|
+
python -m pip install ".[dev]"
|
|
72
|
+
|
|
73
|
+
- name: Lint
|
|
74
|
+
run: |
|
|
75
|
+
markdown-code-runner --verbose README.md
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ommx_python_mip_adapter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An adapter for the Python-MIP from/to OMMX.
|
|
5
|
+
Author-email: "Jij Inc." <info@j-ij.com>
|
|
6
|
+
Project-URL: Repository, https://github.com/Jij-Inc/ommx-python-mip-adapter
|
|
7
|
+
Project-URL: Issues, https://github.com/Jij-Inc/ommx-python-mip-adapter/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Requires-Python: >=3.8
|
|
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
|
|
21
|
+
Requires-Dist: mip
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: markdown-code-runner; extra == "dev"
|
|
24
|
+
Requires-Dist: mypy; extra == "dev"
|
|
25
|
+
Requires-Dist: numpy; extra == "dev"
|
|
26
|
+
Requires-Dist: pyright; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest; extra == "dev"
|
|
28
|
+
Requires-Dist: sphinx; extra == "dev"
|
|
29
|
+
Requires-Dist: sphinx_rtd_theme; extra == "dev"
|
|
30
|
+
Requires-Dist: sphinx_fontawesome; extra == "dev"
|
|
31
|
+
Requires-Dist: sphinx-autoapi; extra == "dev"
|
|
32
|
+
|
|
33
|
+
OMMX adaptor for Python-MIP
|
|
34
|
+
============================
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
This package provides an adaptor for the [Python-MIP](https://www.python-mip.com/) from/to [OMMX](https://github.com/Jij-Inc/ommx)
|
|
38
|
+
|
|
39
|
+
Python-MIP as a solver in OMMX toolchain
|
|
40
|
+
-----------------------------------------
|
|
41
|
+
```mermaid
|
|
42
|
+
sequenceDiagram
|
|
43
|
+
participant O as Other OMMX toolchain
|
|
44
|
+
participant A as Adapter
|
|
45
|
+
participant P as Python-MIP
|
|
46
|
+
O->>A: ommx::Instance and Parameters for Python-MIP;
|
|
47
|
+
A->>P: Translate into Python-MIP input
|
|
48
|
+
P->>P: Solve with CBC, Gurobi, or other solvers
|
|
49
|
+
P->>A: Solution
|
|
50
|
+
A->>O: ommx:Solution
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Python-MIP as a user interface to create OMMX instance
|
|
54
|
+
-------------------------------------------------------
|
|
55
|
+
TBW
|
|
56
|
+
|
|
57
|
+
Usage
|
|
58
|
+
======
|
|
59
|
+
`ommx-python-mip-adapter` can be installed from PyPI as follows:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install ommx-python-mip-adapter
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Python-MIP can be used through `ommx-python-mip-adapter` by using the following:
|
|
66
|
+
|
|
67
|
+
```python markdown-code-runner
|
|
68
|
+
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
|
+
),
|
|
88
|
+
)
|
|
89
|
+
ommx_instance_bytes = ommx_instance.SerializeToString()
|
|
90
|
+
|
|
91
|
+
# Convert from `ommx.v1.Instance` to `mip.Model`
|
|
92
|
+
model = adapter.instance_to_model(ommx_instance_bytes)
|
|
93
|
+
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
|
+
)
|
|
98
|
+
|
|
99
|
+
print(SolutionList.FromString(ommx_solutions_bytes))
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Reference
|
|
103
|
+
==============
|
|
104
|
+
- [OMMX-Python-MIP-Adapter API Reference](https://jij-inc.github.io/ommx-python-mip-adapter/index.html)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
OMMX adaptor for Python-MIP
|
|
2
|
+
============================
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
This package provides an adaptor 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 O as Other OMMX toolchain
|
|
12
|
+
participant A as Adapter
|
|
13
|
+
participant P as Python-MIP
|
|
14
|
+
O->>A: ommx::Instance and Parameters for Python-MIP;
|
|
15
|
+
A->>P: Translate into Python-MIP input
|
|
16
|
+
P->>P: Solve with CBC, Gurobi, or other solvers
|
|
17
|
+
P->>A: Solution
|
|
18
|
+
A->>O: ommx:Solution
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Python-MIP as a user interface to create OMMX instance
|
|
22
|
+
-------------------------------------------------------
|
|
23
|
+
TBW
|
|
24
|
+
|
|
25
|
+
Usage
|
|
26
|
+
======
|
|
27
|
+
`ommx-python-mip-adapter` can be installed from PyPI as follows:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install ommx-python-mip-adapter
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Python-MIP can be used through `ommx-python-mip-adapter` by using the following:
|
|
34
|
+
|
|
35
|
+
```python markdown-code-runner
|
|
36
|
+
import ommx_python_mip_adapter as adapter
|
|
37
|
+
from ommx.v1.decision_variables_pb2 import DecisionVariable, Bound
|
|
38
|
+
from ommx.v1.instance_pb2 import Instance
|
|
39
|
+
from ommx.v1.function_pb2 import Function
|
|
40
|
+
from ommx.v1.linear_pb2 import Linear
|
|
41
|
+
from ommx.v1.solution_pb2 import SolutionList
|
|
42
|
+
|
|
43
|
+
ommx_instance = Instance(
|
|
44
|
+
decision_variables=[
|
|
45
|
+
DecisionVariable(
|
|
46
|
+
id=1,
|
|
47
|
+
kind=DecisionVariable.KIND_INTEGER,
|
|
48
|
+
bound=Bound(lower=0, upper=5),
|
|
49
|
+
),
|
|
50
|
+
],
|
|
51
|
+
objective=Function(
|
|
52
|
+
linear=Linear(
|
|
53
|
+
terms=[Linear.Term(id=1, coefficient=1)]
|
|
54
|
+
),
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
ommx_instance_bytes = ommx_instance.SerializeToString()
|
|
58
|
+
|
|
59
|
+
# Convert from `ommx.v1.Instance` to `mip.Model`
|
|
60
|
+
model = adapter.instance_to_model(ommx_instance_bytes)
|
|
61
|
+
model.optimize()
|
|
62
|
+
# Create `ommx.v1.SolutionList` from Optimized `mip.Model`
|
|
63
|
+
ommx_solutions_bytes = adapter.model_to_solution(
|
|
64
|
+
model, ommx_instance_bytes
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
print(SolutionList.FromString(ommx_solutions_bytes))
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Reference
|
|
71
|
+
==============
|
|
72
|
+
- [OMMX-Python-MIP-Adapter API Reference](https://jij-inc.github.io/ommx-python-mip-adapter/index.html)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Configuration file for the Sphinx documentation builder.
|
|
2
|
+
#
|
|
3
|
+
# For the full list of built-in configuration values, see the documentation:
|
|
4
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
|
5
|
+
|
|
6
|
+
import sphinx_rtd_theme
|
|
7
|
+
import sphinx_fontawesome
|
|
8
|
+
|
|
9
|
+
# -- Project information -----------------------------------------------------
|
|
10
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
|
11
|
+
|
|
12
|
+
project = 'ommx-python-mip-adapter'
|
|
13
|
+
copyright = '2024, Jij Inc.'
|
|
14
|
+
author = 'Jij Inc.'
|
|
15
|
+
|
|
16
|
+
version = '0.1.0'
|
|
17
|
+
release = '0.1.0'
|
|
18
|
+
|
|
19
|
+
# -- General configuration ---------------------------------------------------
|
|
20
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
|
21
|
+
|
|
22
|
+
extensions = [
|
|
23
|
+
'sphinx.ext.autodoc',
|
|
24
|
+
'sphinx.ext.napoleon',
|
|
25
|
+
'sphinx_rtd_theme',
|
|
26
|
+
'sphinx_fontawesome',
|
|
27
|
+
'autoapi.extension',
|
|
28
|
+
]
|
|
29
|
+
source_suffix = {
|
|
30
|
+
'.rst': 'restructuredtext',
|
|
31
|
+
'.md': 'markdown',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
templates_path = ['_templates']
|
|
35
|
+
language = 'en'
|
|
36
|
+
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
|
37
|
+
|
|
38
|
+
# -- Options for HTML output -------------------------------------------------
|
|
39
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
|
40
|
+
|
|
41
|
+
html_theme = "sphinx_rtd_theme"
|
|
42
|
+
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
|
43
|
+
html_show_sourcelink = False
|
|
44
|
+
html_static_path = ['_static']
|
|
45
|
+
|
|
46
|
+
# -- AutoAPI settings --------------------------------------------------------
|
|
47
|
+
# https://sphinx-autoapi.readthedocs.io/en/latest/reference/config.html#event-autoapi-skip-member
|
|
48
|
+
|
|
49
|
+
autoapi_dirs = ['../../ommx_python_mip_adapter']
|
|
50
|
+
autoapi_member_order = 'groupwise'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.. ommx-python-mip-adapter documentation master file, created by
|
|
2
|
+
sphinx-quickstart on Mon Apr 22 14:29:19 2024.
|
|
3
|
+
You can adapt this file completely to your liking, but it should at least
|
|
4
|
+
contain the root `toctree` directive.
|
|
5
|
+
|
|
6
|
+
Welcome to ommx-python-mip-adapter's documentation!
|
|
7
|
+
===================================================
|
|
8
|
+
|
|
9
|
+
.. toctree::
|
|
10
|
+
:maxdepth: 2
|
|
11
|
+
:caption: Contents:
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
Indices and tables
|
|
16
|
+
==================
|
|
17
|
+
|
|
18
|
+
* :ref:`genindex`
|
|
19
|
+
* :ref:`modindex`
|
|
20
|
+
* :ref:`search`
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import typing as tp
|
|
2
|
+
|
|
3
|
+
import mip
|
|
4
|
+
|
|
5
|
+
from ommx.v1.constraint_pb2 import Constraint
|
|
6
|
+
from ommx.v1.decision_variables_pb2 import DecisionVariable
|
|
7
|
+
from ommx.v1.function_pb2 import Function
|
|
8
|
+
from ommx.v1.instance_pb2 import Instance
|
|
9
|
+
from ommx.v1.solution_pb2 import Solution, SolutionList
|
|
10
|
+
|
|
11
|
+
from ommx_python_mip_adapter.exception import OMMXPythonMIPAdapterError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PythonMIPBuilder:
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
ommx_instance_bytes: bytes,
|
|
18
|
+
*,
|
|
19
|
+
sense: str = mip.MINIMIZE,
|
|
20
|
+
solver_name: str = mip.CBC,
|
|
21
|
+
solver: tp.Optional[mip.Solver] = None,
|
|
22
|
+
):
|
|
23
|
+
try:
|
|
24
|
+
self._ommx_instance = Instance.FromString(ommx_instance_bytes)
|
|
25
|
+
except Exception as e:
|
|
26
|
+
raise OMMXPythonMIPAdapterError(
|
|
27
|
+
"Invalid `ommx_instance_bytes` as ommx.v1.Instance."
|
|
28
|
+
) from e
|
|
29
|
+
|
|
30
|
+
self._model = mip.Model(
|
|
31
|
+
sense=sense,
|
|
32
|
+
solver_name=solver_name,
|
|
33
|
+
solver=solver,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _set_decision_variables(self):
|
|
38
|
+
for var in self._ommx_instance.decision_variables:
|
|
39
|
+
if var.kind == DecisionVariable.KIND_BINARY:
|
|
40
|
+
self._model.add_var(
|
|
41
|
+
name=str(var.id),
|
|
42
|
+
var_type=mip.BINARY,
|
|
43
|
+
)
|
|
44
|
+
elif var.kind == DecisionVariable.KIND_INTEGER:
|
|
45
|
+
self._model.add_var(
|
|
46
|
+
name=str(var.id),
|
|
47
|
+
var_type=mip.INTEGER,
|
|
48
|
+
lb=var.bound.lower, # type: ignore
|
|
49
|
+
ub=var.bound.upper, # type: ignore
|
|
50
|
+
)
|
|
51
|
+
elif var.kind == DecisionVariable.KIND_CONTINUOUS:
|
|
52
|
+
self._model.add_var(
|
|
53
|
+
name=str(var.id),
|
|
54
|
+
var_type=mip.CONTINUOUS,
|
|
55
|
+
lb=var.bound.lower, # type: ignore
|
|
56
|
+
ub=var.bound.upper, # type: ignore
|
|
57
|
+
)
|
|
58
|
+
else:
|
|
59
|
+
raise OMMXPythonMIPAdapterError(
|
|
60
|
+
f"Not supported decision variable kind: "
|
|
61
|
+
f"id: {var.id}, kind: {var.kind}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _make_linear_expr(
|
|
66
|
+
self,
|
|
67
|
+
ommx_function: Function,
|
|
68
|
+
) -> mip.LinExpr:
|
|
69
|
+
ommx_linear = ommx_function.linear
|
|
70
|
+
|
|
71
|
+
return mip.xsum(
|
|
72
|
+
term.coefficient * self._model.vars[str(term.id)] # type: ignore
|
|
73
|
+
for term in ommx_linear.terms
|
|
74
|
+
) + ommx_linear.constant # type: ignore
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _set_objective_function(self):
|
|
78
|
+
ommx_objective = self._ommx_instance.objective
|
|
79
|
+
|
|
80
|
+
if ommx_objective.HasField("constant"):
|
|
81
|
+
self._model.objective = ommx_objective.constant # type: ignore
|
|
82
|
+
elif ommx_objective.HasField("linear"):
|
|
83
|
+
self._model.objective = self._make_linear_expr(ommx_objective)
|
|
84
|
+
else:
|
|
85
|
+
raise OMMXPythonMIPAdapterError(
|
|
86
|
+
"The objective function must be either `constant` or `linear`."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _set_constraints(self):
|
|
91
|
+
ommx_constraints = self._ommx_instance.constraints
|
|
92
|
+
|
|
93
|
+
for constraint in ommx_constraints:
|
|
94
|
+
if not constraint.function.HasField("linear"):
|
|
95
|
+
raise OMMXPythonMIPAdapterError(
|
|
96
|
+
f"Only linear constraints are supported: "
|
|
97
|
+
f"id: {constraint.id}, "
|
|
98
|
+
f"type: {constraint.function.WhichOneof('function')}"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
lin_expr = self._make_linear_expr(constraint.function)
|
|
102
|
+
|
|
103
|
+
if constraint.equality == Constraint.EQUALITY_EQUAL_TO_ZERO:
|
|
104
|
+
constr_expr = (lin_expr == 0)
|
|
105
|
+
elif (constraint.equality == Constraint.EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO):
|
|
106
|
+
constr_expr = (lin_expr <= 0) # type: ignore
|
|
107
|
+
else:
|
|
108
|
+
raise OMMXPythonMIPAdapterError(
|
|
109
|
+
f"Not supported constraint equality: "
|
|
110
|
+
f"id: {constraint.id}, equality: {constraint.equality}"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
self._model.add_constr(constr_expr, name=str(constraint.id))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def build(self) -> mip.Model:
|
|
117
|
+
self._set_decision_variables()
|
|
118
|
+
self._set_objective_function()
|
|
119
|
+
self._set_constraints()
|
|
120
|
+
|
|
121
|
+
return self._model
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def instance_to_model(
|
|
125
|
+
ommx_instance_bytes: bytes,
|
|
126
|
+
*,
|
|
127
|
+
sense: str = mip.MINIMIZE,
|
|
128
|
+
solver_name: str = mip.CBC,
|
|
129
|
+
solver: tp.Optional[mip.Solver] = None,
|
|
130
|
+
) -> mip.Model:
|
|
131
|
+
"""
|
|
132
|
+
The function to convert ommx.v1.Instance to Python-MIP Model.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
ommx_instance_bytes (bytes): Serialized ommx.v1.Instance.
|
|
136
|
+
sense (str): mip.MINIMIZE or mip.MAXIMIZE.
|
|
137
|
+
solver_name (str): mip.CBC or mip.GUROBI. Searches for which solver is available if not informed.
|
|
138
|
+
solver (mip.Solver): if this argument is provided, solver_name will be ignored.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
mip.Model: Python-MIP Model converted from ommx.v1.Instance.
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
OMMXPythonMIPAdapterError: If converting is not possible.
|
|
145
|
+
|
|
146
|
+
Examples:
|
|
147
|
+
The following example of solving an unconstrained linear optimization problem with x1 as the objective function.
|
|
148
|
+
|
|
149
|
+
>>> import ommx_python_mip_adapter as adapter
|
|
150
|
+
>>> from ommx.v1.decision_variables_pb2 import DecisionVariable, Bound
|
|
151
|
+
>>> from ommx.v1.instance_pb2 import Instance
|
|
152
|
+
>>> from ommx.v1.function_pb2 import Function
|
|
153
|
+
>>> from ommx.v1.linear_pb2 import Linear
|
|
154
|
+
>>> from ommx.v1.solution_pb2 import SolutionList
|
|
155
|
+
>>> ommx_instance = Instance(
|
|
156
|
+
... decision_variables=[
|
|
157
|
+
... DecisionVariable(
|
|
158
|
+
... id=1,
|
|
159
|
+
... kind=DecisionVariable.KIND_INTEGER,
|
|
160
|
+
... bound=Bound(lower=0, upper=5),
|
|
161
|
+
... ),
|
|
162
|
+
... ],
|
|
163
|
+
... objective=Function(
|
|
164
|
+
... linear=Linear(
|
|
165
|
+
... terms=[Linear.Term(id=1, coefficient=1)]
|
|
166
|
+
... ),
|
|
167
|
+
... ),
|
|
168
|
+
... )
|
|
169
|
+
>>> ommx_instance_bytes = ommx_instance.SerializeToString()
|
|
170
|
+
>>> model = adapter.instance_to_model(ommx_instance_bytes)
|
|
171
|
+
>>> model.optimize()
|
|
172
|
+
<OptimizationStatus.OPTIMAL: 0>
|
|
173
|
+
>>> ommx_solutions_bytes = adapter.model_to_solution(
|
|
174
|
+
... model, ommx_instance_bytes
|
|
175
|
+
... )
|
|
176
|
+
>>> SolutionList.FromString(ommx_solutions_bytes).solutions[0].entries
|
|
177
|
+
{1: 0.0}
|
|
178
|
+
"""
|
|
179
|
+
builder = PythonMIPBuilder(
|
|
180
|
+
ommx_instance_bytes,
|
|
181
|
+
sense=sense,
|
|
182
|
+
solver_name=solver_name,
|
|
183
|
+
solver=solver,
|
|
184
|
+
)
|
|
185
|
+
return builder.build()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def model_to_solution(
|
|
189
|
+
model: mip.Model,
|
|
190
|
+
ommx_instance_bytes: bytes,
|
|
191
|
+
) -> bytes:
|
|
192
|
+
"""
|
|
193
|
+
The function to create ommx.v1.SolutionList from optimized Python-MIP Model.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
model (mip.Model): Optimized Python-MIP Model.
|
|
197
|
+
ommx_instance_bytes (bytes): Serialized ommx.v1.Instance.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
bytes: Serialized ommx.v1.SolutionList
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
OMMXPythonMIPAdapterError: When ommx.v1.SolutionList cannot be created.
|
|
204
|
+
|
|
205
|
+
Examples:
|
|
206
|
+
The following example of solving an unconstrained linear optimization problem with x1 as the objective function.
|
|
207
|
+
|
|
208
|
+
>>> import ommx_python_mip_adapter as adapter
|
|
209
|
+
>>> from ommx.v1.decision_variables_pb2 import DecisionVariable, Bound
|
|
210
|
+
>>> from ommx.v1.instance_pb2 import Instance
|
|
211
|
+
>>> from ommx.v1.function_pb2 import Function
|
|
212
|
+
>>> from ommx.v1.linear_pb2 import Linear
|
|
213
|
+
>>> from ommx.v1.solution_pb2 import SolutionList
|
|
214
|
+
>>> ommx_instance = Instance(
|
|
215
|
+
... decision_variables=[
|
|
216
|
+
... DecisionVariable(
|
|
217
|
+
... id=1,
|
|
218
|
+
... kind=DecisionVariable.KIND_INTEGER,
|
|
219
|
+
... bound=Bound(lower=0, upper=5),
|
|
220
|
+
... ),
|
|
221
|
+
... ],
|
|
222
|
+
... objective=Function(
|
|
223
|
+
... linear=Linear(
|
|
224
|
+
... terms=[Linear.Term(id=1, coefficient=1)]
|
|
225
|
+
... ),
|
|
226
|
+
... ),
|
|
227
|
+
... )
|
|
228
|
+
>>> ommx_instance_bytes = ommx_instance.SerializeToString()
|
|
229
|
+
>>> model = adapter.instance_to_model(ommx_instance_bytes)
|
|
230
|
+
>>> model.optimize()
|
|
231
|
+
<OptimizationStatus.OPTIMAL: 0>
|
|
232
|
+
>>> ommx_solutions_bytes = adapter.model_to_solution(
|
|
233
|
+
... model, ommx_instance_bytes
|
|
234
|
+
... )
|
|
235
|
+
>>> SolutionList.FromString(ommx_solutions_bytes).solutions[0].entries
|
|
236
|
+
{1: 0.0}
|
|
237
|
+
"""
|
|
238
|
+
if not (
|
|
239
|
+
model.status == mip.OptimizationStatus.OPTIMAL
|
|
240
|
+
or model.status == mip.OptimizationStatus.FEASIBLE
|
|
241
|
+
):
|
|
242
|
+
raise OMMXPythonMIPAdapterError(
|
|
243
|
+
"`model.status` must be `OPTIMAL` or `FEASIBLE`."
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
ommx_instance = Instance.FromString(ommx_instance_bytes)
|
|
248
|
+
except Exception as e:
|
|
249
|
+
raise OMMXPythonMIPAdapterError(
|
|
250
|
+
"Invalid `ommx_instance_bytes` as ommx.v1.Instance."
|
|
251
|
+
) from e
|
|
252
|
+
|
|
253
|
+
return SolutionList(
|
|
254
|
+
solutions=[
|
|
255
|
+
Solution(
|
|
256
|
+
entries={
|
|
257
|
+
var.id: model.var_by_name(str(var.id)).x # type: ignore
|
|
258
|
+
for var in ommx_instance.decision_variables
|
|
259
|
+
}
|
|
260
|
+
)
|
|
261
|
+
]
|
|
262
|
+
).SerializeToString()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ommx_python_mip_adapter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An adapter for the Python-MIP from/to OMMX.
|
|
5
|
+
Author-email: "Jij Inc." <info@j-ij.com>
|
|
6
|
+
Project-URL: Repository, https://github.com/Jij-Inc/ommx-python-mip-adapter
|
|
7
|
+
Project-URL: Issues, https://github.com/Jij-Inc/ommx-python-mip-adapter/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Requires-Python: >=3.8
|
|
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
|
|
21
|
+
Requires-Dist: mip
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: markdown-code-runner; extra == "dev"
|
|
24
|
+
Requires-Dist: mypy; extra == "dev"
|
|
25
|
+
Requires-Dist: numpy; extra == "dev"
|
|
26
|
+
Requires-Dist: pyright; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest; extra == "dev"
|
|
28
|
+
Requires-Dist: sphinx; extra == "dev"
|
|
29
|
+
Requires-Dist: sphinx_rtd_theme; extra == "dev"
|
|
30
|
+
Requires-Dist: sphinx_fontawesome; extra == "dev"
|
|
31
|
+
Requires-Dist: sphinx-autoapi; extra == "dev"
|
|
32
|
+
|
|
33
|
+
OMMX adaptor for Python-MIP
|
|
34
|
+
============================
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
This package provides an adaptor for the [Python-MIP](https://www.python-mip.com/) from/to [OMMX](https://github.com/Jij-Inc/ommx)
|
|
38
|
+
|
|
39
|
+
Python-MIP as a solver in OMMX toolchain
|
|
40
|
+
-----------------------------------------
|
|
41
|
+
```mermaid
|
|
42
|
+
sequenceDiagram
|
|
43
|
+
participant O as Other OMMX toolchain
|
|
44
|
+
participant A as Adapter
|
|
45
|
+
participant P as Python-MIP
|
|
46
|
+
O->>A: ommx::Instance and Parameters for Python-MIP;
|
|
47
|
+
A->>P: Translate into Python-MIP input
|
|
48
|
+
P->>P: Solve with CBC, Gurobi, or other solvers
|
|
49
|
+
P->>A: Solution
|
|
50
|
+
A->>O: ommx:Solution
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Python-MIP as a user interface to create OMMX instance
|
|
54
|
+
-------------------------------------------------------
|
|
55
|
+
TBW
|
|
56
|
+
|
|
57
|
+
Usage
|
|
58
|
+
======
|
|
59
|
+
`ommx-python-mip-adapter` can be installed from PyPI as follows:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install ommx-python-mip-adapter
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Python-MIP can be used through `ommx-python-mip-adapter` by using the following:
|
|
66
|
+
|
|
67
|
+
```python markdown-code-runner
|
|
68
|
+
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
|
+
),
|
|
88
|
+
)
|
|
89
|
+
ommx_instance_bytes = ommx_instance.SerializeToString()
|
|
90
|
+
|
|
91
|
+
# Convert from `ommx.v1.Instance` to `mip.Model`
|
|
92
|
+
model = adapter.instance_to_model(ommx_instance_bytes)
|
|
93
|
+
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
|
+
)
|
|
98
|
+
|
|
99
|
+
print(SolutionList.FromString(ommx_solutions_bytes))
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Reference
|
|
103
|
+
==============
|
|
104
|
+
- [OMMX-Python-MIP-Adapter API Reference](https://jij-inc.github.io/ommx-python-mip-adapter/index.html)
|
|
@@ -3,15 +3,21 @@ LICENSE-APACHE
|
|
|
3
3
|
LICENSE-MIT
|
|
4
4
|
README.md
|
|
5
5
|
pyproject.toml
|
|
6
|
+
.github/workflows/docs.yml
|
|
6
7
|
.github/workflows/publish.yml
|
|
7
8
|
.github/workflows/test_and_lint.yml
|
|
9
|
+
docs/source/conf.py
|
|
10
|
+
docs/source/index.rst
|
|
8
11
|
ommx_python_mip_adapter/__init__.py
|
|
9
12
|
ommx_python_mip_adapter/_version.py
|
|
10
|
-
ommx_python_mip_adapter/
|
|
13
|
+
ommx_python_mip_adapter/adapter.py
|
|
14
|
+
ommx_python_mip_adapter/exception.py
|
|
11
15
|
ommx_python_mip_adapter.egg-info/PKG-INFO
|
|
12
16
|
ommx_python_mip_adapter.egg-info/SOURCES.txt
|
|
13
17
|
ommx_python_mip_adapter.egg-info/dependency_links.txt
|
|
14
18
|
ommx_python_mip_adapter.egg-info/requires.txt
|
|
15
19
|
ommx_python_mip_adapter.egg-info/top_level.txt
|
|
16
20
|
tests/conftest.py
|
|
17
|
-
tests/
|
|
21
|
+
tests/test_adapter.py
|
|
22
|
+
tests/test_import.py
|
|
23
|
+
tests/test_integration.py
|
|
@@ -18,15 +18,22 @@ classifiers = [
|
|
|
18
18
|
"License :: OSI Approved :: MIT License",
|
|
19
19
|
]
|
|
20
20
|
dependencies = [
|
|
21
|
-
"ommx >= 0.1.0",
|
|
21
|
+
"ommx >= 0.1.1, < 0.2.0",
|
|
22
|
+
"mip",
|
|
22
23
|
]
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
[project.optional-dependencies]
|
|
26
27
|
dev = [
|
|
27
|
-
"
|
|
28
|
+
"markdown-code-runner",
|
|
28
29
|
"mypy",
|
|
30
|
+
"numpy",
|
|
29
31
|
"pyright",
|
|
32
|
+
"pytest",
|
|
33
|
+
"sphinx",
|
|
34
|
+
"sphinx_rtd_theme",
|
|
35
|
+
"sphinx_fontawesome",
|
|
36
|
+
"sphinx-autoapi",
|
|
30
37
|
]
|
|
31
38
|
|
|
32
39
|
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import mip
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from ommx.v1.constraint_pb2 import Constraint
|
|
5
|
+
from ommx.v1.decision_variables_pb2 import DecisionVariable
|
|
6
|
+
from ommx.v1.function_pb2 import Function
|
|
7
|
+
from ommx.v1.instance_pb2 import Instance
|
|
8
|
+
from ommx.v1.linear_pb2 import Linear
|
|
9
|
+
from ommx.v1.quadratic_pb2 import Quadratic
|
|
10
|
+
from ommx.testing import SingleFeasibleLPGenerator, DataType
|
|
11
|
+
|
|
12
|
+
import ommx_python_mip_adapter as adapter
|
|
13
|
+
|
|
14
|
+
from ommx_python_mip_adapter.exception import OMMXPythonMIPAdapterError
|
|
15
|
+
|
|
16
|
+
def test_error_invalid_instance():
|
|
17
|
+
with pytest.raises(OMMXPythonMIPAdapterError) as e:
|
|
18
|
+
adapter.instance_to_model(b"invalid")
|
|
19
|
+
assert "Invalid `ommx_instance_bytes`" in str(e.value)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_error_not_suppoerted_decision_variable():
|
|
23
|
+
ommx_instance = Instance(
|
|
24
|
+
decision_variables=[
|
|
25
|
+
DecisionVariable(
|
|
26
|
+
id=1,
|
|
27
|
+
kind=DecisionVariable.KIND_UNSPECIFIED,
|
|
28
|
+
)
|
|
29
|
+
],
|
|
30
|
+
)
|
|
31
|
+
ommx_instance_bytes = ommx_instance.SerializeToString()
|
|
32
|
+
|
|
33
|
+
with pytest.raises(OMMXPythonMIPAdapterError) as e:
|
|
34
|
+
adapter.instance_to_model(ommx_instance_bytes)
|
|
35
|
+
assert "Not supported decision variable" in str(e.value)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_error_nonlinear_objective():
|
|
39
|
+
# Objective function: 2.3 * x * x
|
|
40
|
+
ommx_instance = Instance(
|
|
41
|
+
decision_variables=[
|
|
42
|
+
DecisionVariable(
|
|
43
|
+
id=0,
|
|
44
|
+
kind=DecisionVariable.KIND_CONTINUOUS,
|
|
45
|
+
),
|
|
46
|
+
],
|
|
47
|
+
objective=Function(
|
|
48
|
+
quadratic=Quadratic(
|
|
49
|
+
rows=[1],
|
|
50
|
+
columns=[1],
|
|
51
|
+
values=[2.3],
|
|
52
|
+
),
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
ommx_instance_bytes = ommx_instance.SerializeToString()
|
|
56
|
+
|
|
57
|
+
with pytest.raises(OMMXPythonMIPAdapterError) as e:
|
|
58
|
+
adapter.instance_to_model(ommx_instance_bytes)
|
|
59
|
+
assert "The objective function must be" in str(e.value)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_error_nonlinear_constraint():
|
|
63
|
+
# Objective function: 0
|
|
64
|
+
# Constraint: 2.3 * x * x = 0
|
|
65
|
+
ommx_instance = Instance(
|
|
66
|
+
decision_variables=[
|
|
67
|
+
DecisionVariable(
|
|
68
|
+
id=1,
|
|
69
|
+
kind=DecisionVariable.KIND_CONTINUOUS,
|
|
70
|
+
),
|
|
71
|
+
],
|
|
72
|
+
objective=Function(
|
|
73
|
+
constant=0,
|
|
74
|
+
),
|
|
75
|
+
constraints=[
|
|
76
|
+
Constraint(
|
|
77
|
+
function=Function(
|
|
78
|
+
quadratic=Quadratic(
|
|
79
|
+
rows=[1],
|
|
80
|
+
columns=[1],
|
|
81
|
+
values=[2.3],
|
|
82
|
+
),
|
|
83
|
+
),
|
|
84
|
+
equality=Constraint.EQUALITY_EQUAL_TO_ZERO,
|
|
85
|
+
),
|
|
86
|
+
],
|
|
87
|
+
)
|
|
88
|
+
ommx_instance_bytes = ommx_instance.SerializeToString()
|
|
89
|
+
|
|
90
|
+
with pytest.raises(OMMXPythonMIPAdapterError) as e:
|
|
91
|
+
adapter.instance_to_model(ommx_instance_bytes)
|
|
92
|
+
assert "Only linear constraints are supported" in str(e.value)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_error_not_supported_constraint_equality():
|
|
96
|
+
# Objective function: 0
|
|
97
|
+
# Constraint: 2x ?? 0 (equality: unspecified)
|
|
98
|
+
ommx_instance = Instance(
|
|
99
|
+
decision_variables=[
|
|
100
|
+
DecisionVariable(
|
|
101
|
+
id=1,
|
|
102
|
+
kind=DecisionVariable.KIND_CONTINUOUS,
|
|
103
|
+
),
|
|
104
|
+
],
|
|
105
|
+
objective=Function(
|
|
106
|
+
constant=0,
|
|
107
|
+
),
|
|
108
|
+
constraints=[
|
|
109
|
+
Constraint(
|
|
110
|
+
function=Function(
|
|
111
|
+
linear=Linear(
|
|
112
|
+
terms=[
|
|
113
|
+
Linear.Term(
|
|
114
|
+
id=1,
|
|
115
|
+
coefficient=2,
|
|
116
|
+
),
|
|
117
|
+
],
|
|
118
|
+
),
|
|
119
|
+
),
|
|
120
|
+
equality=Constraint.EQUALITY_UNSPECIFIED,
|
|
121
|
+
),
|
|
122
|
+
],
|
|
123
|
+
)
|
|
124
|
+
ommx_instance_bytes = ommx_instance.SerializeToString()
|
|
125
|
+
|
|
126
|
+
with pytest.raises(OMMXPythonMIPAdapterError) as e:
|
|
127
|
+
adapter.instance_to_model(ommx_instance_bytes)
|
|
128
|
+
assert "Not supported constraint equality" in str(e.value)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_error_not_optimized_model():
|
|
132
|
+
model = mip.Model()
|
|
133
|
+
|
|
134
|
+
with pytest.raises(OMMXPythonMIPAdapterError) as e:
|
|
135
|
+
adapter.model_to_solution(model, b"")
|
|
136
|
+
assert "`model.status` must be " in str(e.value)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_error_invalid_ommx_instance_bytes():
|
|
140
|
+
generator = SingleFeasibleLPGenerator(10, DataType.INT)
|
|
141
|
+
ommx_instance_bytes = generator.get_v1_instance()
|
|
142
|
+
model = adapter.instance_to_model(ommx_instance_bytes)
|
|
143
|
+
model.optimize()
|
|
144
|
+
|
|
145
|
+
with pytest.raises(OMMXPythonMIPAdapterError) as e:
|
|
146
|
+
adapter.model_to_solution(model, b"invalid")
|
|
147
|
+
assert "Invalid `ommx_instance_bytes`" in str(e.value)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
def test_import():
|
|
2
|
+
import ommx_python_mip_adapter as adapter
|
|
3
|
+
from ommx_python_mip_adapter import instance_to_model
|
|
4
|
+
from ommx_python_mip_adapter import model_to_solution
|
|
5
|
+
|
|
6
|
+
assert adapter.instance_to_model
|
|
7
|
+
assert adapter.model_to_solution
|
|
8
|
+
assert instance_to_model
|
|
9
|
+
assert model_to_solution
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ommx.v1.constraint_pb2 import Constraint
|
|
4
|
+
from ommx.v1.decision_variables_pb2 import DecisionVariable, Bound
|
|
5
|
+
from ommx.v1.function_pb2 import Function
|
|
6
|
+
from ommx.v1.instance_pb2 import Instance
|
|
7
|
+
from ommx.v1.linear_pb2 import Linear
|
|
8
|
+
from ommx.v1.solution_pb2 import SolutionList
|
|
9
|
+
from ommx.testing import SingleFeasibleLPGenerator, DataType
|
|
10
|
+
|
|
11
|
+
import ommx_python_mip_adapter as adapter
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.parametrize(
|
|
15
|
+
"generater",
|
|
16
|
+
[
|
|
17
|
+
SingleFeasibleLPGenerator(10, DataType.INT),
|
|
18
|
+
SingleFeasibleLPGenerator(10, DataType.FLOAT),
|
|
19
|
+
]
|
|
20
|
+
)
|
|
21
|
+
def test_integration_lp(generater):
|
|
22
|
+
# Objective function: 0
|
|
23
|
+
# Constraints:
|
|
24
|
+
# A @ x = b (A: regular matrix, b: constant vector)
|
|
25
|
+
ommx_instance_bytes = generater.get_v1_instance()
|
|
26
|
+
|
|
27
|
+
model = adapter.instance_to_model(ommx_instance_bytes)
|
|
28
|
+
model.optimize()
|
|
29
|
+
ommx_solution = adapter.model_to_solution(model, ommx_instance_bytes)
|
|
30
|
+
|
|
31
|
+
ommx_solution_obj = SolutionList.FromString(ommx_solution)
|
|
32
|
+
expected_solution_obj = SolutionList.FromString(
|
|
33
|
+
generater.get_v1_solution()
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Check the number of solutions
|
|
37
|
+
assert len(ommx_solution_obj.solutions) == 1
|
|
38
|
+
|
|
39
|
+
actual_entries = ommx_solution_obj.solutions[0].entries
|
|
40
|
+
expected_entries = expected_solution_obj.solutions[0].entries
|
|
41
|
+
|
|
42
|
+
# Check the solution of each decision variable
|
|
43
|
+
for key, actual_value in actual_entries.items():
|
|
44
|
+
expected_value = expected_entries[key]
|
|
45
|
+
assert actual_value == pytest.approx(expected_value)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_integration_milp():
|
|
49
|
+
# Objective function: - x1 - x2
|
|
50
|
+
# Constraints:
|
|
51
|
+
# 3x1 - x2 - 6 <= 0
|
|
52
|
+
# -x1 + 3x2 - 6 <= 0
|
|
53
|
+
# 0 <= x1 <= 10 (x: integer)
|
|
54
|
+
# 0 <= x2 <= 10 (x: continuous)
|
|
55
|
+
# Optimal solution: x1 = 3, x2 = 3
|
|
56
|
+
LOWER_BOUND = 0
|
|
57
|
+
UPPER_BOUND = 10
|
|
58
|
+
ommx_instance = Instance(
|
|
59
|
+
decision_variables=[
|
|
60
|
+
DecisionVariable(
|
|
61
|
+
id=1,
|
|
62
|
+
kind=DecisionVariable.KIND_INTEGER,
|
|
63
|
+
bound=Bound(
|
|
64
|
+
lower=LOWER_BOUND,
|
|
65
|
+
upper=UPPER_BOUND,
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
DecisionVariable(
|
|
69
|
+
id=2,
|
|
70
|
+
kind=DecisionVariable.KIND_CONTINUOUS,
|
|
71
|
+
bound=Bound(
|
|
72
|
+
lower=LOWER_BOUND,
|
|
73
|
+
upper=UPPER_BOUND,
|
|
74
|
+
),
|
|
75
|
+
),
|
|
76
|
+
],
|
|
77
|
+
objective=Function(
|
|
78
|
+
linear=Linear(
|
|
79
|
+
terms=[
|
|
80
|
+
Linear.Term(
|
|
81
|
+
id=1,
|
|
82
|
+
coefficient=-1,
|
|
83
|
+
),
|
|
84
|
+
Linear.Term(
|
|
85
|
+
id=2,
|
|
86
|
+
coefficient=-1,
|
|
87
|
+
),
|
|
88
|
+
],
|
|
89
|
+
)
|
|
90
|
+
),
|
|
91
|
+
constraints=[
|
|
92
|
+
Constraint(
|
|
93
|
+
function=Function(
|
|
94
|
+
linear=Linear(
|
|
95
|
+
terms=[
|
|
96
|
+
Linear.Term(
|
|
97
|
+
id=1,
|
|
98
|
+
coefficient=3,
|
|
99
|
+
),
|
|
100
|
+
Linear.Term(
|
|
101
|
+
id=2,
|
|
102
|
+
coefficient=-1,
|
|
103
|
+
),
|
|
104
|
+
],
|
|
105
|
+
constant=-6
|
|
106
|
+
),
|
|
107
|
+
),
|
|
108
|
+
equality=Constraint.EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO,
|
|
109
|
+
),
|
|
110
|
+
Constraint(
|
|
111
|
+
function=Function(
|
|
112
|
+
linear=Linear(
|
|
113
|
+
terms=[
|
|
114
|
+
Linear.Term(
|
|
115
|
+
id=1,
|
|
116
|
+
coefficient=-1,
|
|
117
|
+
),
|
|
118
|
+
Linear.Term(
|
|
119
|
+
id=2,
|
|
120
|
+
coefficient=3,
|
|
121
|
+
),
|
|
122
|
+
],
|
|
123
|
+
constant=-6
|
|
124
|
+
),
|
|
125
|
+
),
|
|
126
|
+
equality=Constraint.EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO,
|
|
127
|
+
),
|
|
128
|
+
],
|
|
129
|
+
)
|
|
130
|
+
ommx_instance_bytes = ommx_instance.SerializeToString()
|
|
131
|
+
|
|
132
|
+
model = adapter.instance_to_model(ommx_instance_bytes)
|
|
133
|
+
model.optimize()
|
|
134
|
+
ommx_solution = adapter.model_to_solution(model, ommx_instance_bytes)
|
|
135
|
+
|
|
136
|
+
ommx_solution_obj = SolutionList.FromString(ommx_solution)
|
|
137
|
+
|
|
138
|
+
assert len(ommx_solution_obj.solutions) == 1
|
|
139
|
+
|
|
140
|
+
actual_entries = ommx_solution_obj.solutions[0].entries
|
|
141
|
+
assert actual_entries[1] == pytest.approx(3)
|
|
142
|
+
assert actual_entries[2] == pytest.approx(3)
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: ommx_python_mip_adapter
|
|
3
|
-
Version: 0.0.2
|
|
4
|
-
Summary: An adapter for the Python-MIP from/to OMMX.
|
|
5
|
-
Author-email: "Jij Inc." <info@j-ij.com>
|
|
6
|
-
Project-URL: Repository, https://github.com/Jij-Inc/ommx-python-mip-adapter
|
|
7
|
-
Project-URL: Issues, https://github.com/Jij-Inc/ommx-python-mip-adapter/issues
|
|
8
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
-
Requires-Python: >=3.8
|
|
17
|
-
Description-Content-Type: text/markdown
|
|
18
|
-
License-File: LICENSE-APACHE
|
|
19
|
-
License-File: LICENSE-MIT
|
|
20
|
-
Requires-Dist: ommx>=0.1.0
|
|
21
|
-
Provides-Extra: dev
|
|
22
|
-
Requires-Dist: pytest; extra == "dev"
|
|
23
|
-
Requires-Dist: mypy; extra == "dev"
|
|
24
|
-
Requires-Dist: pyright; extra == "dev"
|
|
25
|
-
|
|
26
|
-
OMMX adaptor for Python-MIP
|
|
27
|
-
============================
|
|
28
|
-
|
|
29
|
-
This package provides an adaptor for the [Python-MIP](https://www.python-mip.com/) from/to [OMMX](https://github.com/Jij-Inc/ommx)
|
|
30
|
-
|
|
31
|
-
Python-MIP as a solver in OMMX toolchain
|
|
32
|
-
-----------------------------------------
|
|
33
|
-
```mermaid
|
|
34
|
-
sequenceDiagram
|
|
35
|
-
participant O as Other OMMX toolchain
|
|
36
|
-
participant A as Adapter
|
|
37
|
-
participant P as Python-MIP
|
|
38
|
-
O->>A: ommx::Instance and Parameters for Python-MIP;
|
|
39
|
-
A->>P: Translate into Python-MIP input
|
|
40
|
-
P->>P: Solve with CBC, Gurobi, or other solvers
|
|
41
|
-
P->>A: Solution
|
|
42
|
-
A->>O: ommx:Solution
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Python-MIP as a user interface to create OMMX instance
|
|
46
|
-
-------------------------------------------------------
|
|
47
|
-
TBW
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
OMMX adaptor for Python-MIP
|
|
2
|
-
============================
|
|
3
|
-
|
|
4
|
-
This package provides an adaptor for the [Python-MIP](https://www.python-mip.com/) from/to [OMMX](https://github.com/Jij-Inc/ommx)
|
|
5
|
-
|
|
6
|
-
Python-MIP as a solver in OMMX toolchain
|
|
7
|
-
-----------------------------------------
|
|
8
|
-
```mermaid
|
|
9
|
-
sequenceDiagram
|
|
10
|
-
participant O as Other OMMX toolchain
|
|
11
|
-
participant A as Adapter
|
|
12
|
-
participant P as Python-MIP
|
|
13
|
-
O->>A: ommx::Instance and Parameters for Python-MIP;
|
|
14
|
-
A->>P: Translate into Python-MIP input
|
|
15
|
-
P->>P: Solve with CBC, Gurobi, or other solvers
|
|
16
|
-
P->>A: Solution
|
|
17
|
-
A->>O: ommx:Solution
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Python-MIP as a user interface to create OMMX instance
|
|
21
|
-
-------------------------------------------------------
|
|
22
|
-
TBW
|
|
File without changes
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: ommx_python_mip_adapter
|
|
3
|
-
Version: 0.0.2
|
|
4
|
-
Summary: An adapter for the Python-MIP from/to OMMX.
|
|
5
|
-
Author-email: "Jij Inc." <info@j-ij.com>
|
|
6
|
-
Project-URL: Repository, https://github.com/Jij-Inc/ommx-python-mip-adapter
|
|
7
|
-
Project-URL: Issues, https://github.com/Jij-Inc/ommx-python-mip-adapter/issues
|
|
8
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
-
Requires-Python: >=3.8
|
|
17
|
-
Description-Content-Type: text/markdown
|
|
18
|
-
License-File: LICENSE-APACHE
|
|
19
|
-
License-File: LICENSE-MIT
|
|
20
|
-
Requires-Dist: ommx>=0.1.0
|
|
21
|
-
Provides-Extra: dev
|
|
22
|
-
Requires-Dist: pytest; extra == "dev"
|
|
23
|
-
Requires-Dist: mypy; extra == "dev"
|
|
24
|
-
Requires-Dist: pyright; extra == "dev"
|
|
25
|
-
|
|
26
|
-
OMMX adaptor for Python-MIP
|
|
27
|
-
============================
|
|
28
|
-
|
|
29
|
-
This package provides an adaptor for the [Python-MIP](https://www.python-mip.com/) from/to [OMMX](https://github.com/Jij-Inc/ommx)
|
|
30
|
-
|
|
31
|
-
Python-MIP as a solver in OMMX toolchain
|
|
32
|
-
-----------------------------------------
|
|
33
|
-
```mermaid
|
|
34
|
-
sequenceDiagram
|
|
35
|
-
participant O as Other OMMX toolchain
|
|
36
|
-
participant A as Adapter
|
|
37
|
-
participant P as Python-MIP
|
|
38
|
-
O->>A: ommx::Instance and Parameters for Python-MIP;
|
|
39
|
-
A->>P: Translate into Python-MIP input
|
|
40
|
-
P->>P: Solve with CBC, Gurobi, or other solvers
|
|
41
|
-
P->>A: Solution
|
|
42
|
-
A->>O: ommx:Solution
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Python-MIP as a user interface to create OMMX instance
|
|
46
|
-
-------------------------------------------------------
|
|
47
|
-
TBW
|
{ommx_python_mip_adapter-0.0.2 → ommx_python_mip_adapter-0.1.0}/.github/workflows/publish.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|