Skip to content

Commit

Permalink
Add model_to_solution and model_to_state. (#145)
Browse files Browse the repository at this point in the history
# 変更点
- 変更前の時点では、`model_to_solution`
関数が`ommx.v1.State`を返している状態だったため、関数名を`model_to_state`に変更しました
- `ommx.v1.Solution`を返す関数`model_to_solution`を新しく実装しました
- この変更に伴い、`solve`関数の内部処理を新規実装した`model_to_solution`に置き換えました

# 注意事項
`model_to_solution`関数の破壊的変更のため、最低でもマイナーバーションを上げる必要があります。
  • Loading branch information
NY57 authored Nov 19, 2024
1 parent 06fe10f commit 12fbe2e
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 38 deletions.
6 changes: 3 additions & 3 deletions python/ommx-python-mip-adapter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ sequenceDiagram
P->>P: Solve with CBC, Gurobi, or other solvers
P->>U: Optimized model
U->>A: Optimized model and ommx.v1.Instance
A->>U: ommx:SolutionList
A->>U: ommx.v1.Solution
```

Python-MIP as a user interface to create OMMX instance
Expand Down Expand Up @@ -60,9 +60,9 @@ ommx_instance = Instance.from_components(
model = adapter.instance_to_model(ommx_instance)
model.optimize()
# Create `ommx.v1.State` from Optimized `mip.Model`
ommx_solutions = adapter.model_to_solution(model, ommx_instance)
ommx_state = adapter.model_to_state(model, ommx_instance)

print(ommx_solutions)
print(ommx_state)
```

You can get `ommx.v1.Instance` from a Python-MIP model as the following:
Expand Down
2 changes: 1 addition & 1 deletion python/ommx-python-mip-adapter/docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
copyright = "2024, Jij Inc."
author = "Jij Inc."

version = "1.3.2"
version = "1.4.0"
release = version

# -- General configuration ---------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
from .python_mip_to_ommx import (
OMMXInstanceBuilder,
model_to_instance,
model_to_state,
model_to_solution,
)

__all__ = [
"instance_to_model",
"model_to_instance",
"model_to_state",
"model_to_solution",
"PythonMIPBuilder",
"OMMXInstanceBuilder",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ def instance_to_model(
>>> model.optimize()
<OptimizationStatus.OPTIMAL: 0>
>>> ommx_solutions = adapter.model_to_solution(model, ommx_instance)
>>> ommx_solutions.entries
>>> ommx_state = adapter.model_to_state(model, ommx_instance)
>>> ommx_state.entries
{1: 0.0}
"""
builder = PythonMIPBuilder(
Expand Down Expand Up @@ -303,26 +303,12 @@ def solve(
]:
return Result(error=f"Unknown status: {model.status}")

state = model_to_solution(model, instance)
solution = instance.evaluate(state)

assert solution.raw.feasible
solution = model_to_solution(model, instance)

if model.status == mip.OptimizationStatus.OPTIMAL:
solution.raw.optimality = Optimality.OPTIMALITY_OPTIMAL

if relax:
solution.raw.relaxation = Relaxation.RELAXATION_LP_RELAXED

dual_variables = {}
for constraint in model.constrs:
pi = constraint.pi
if pi is not None:
id = int(constraint.name)
dual_variables[id] = pi
for constraint in solution.raw.evaluated_constraints:
id = constraint.id
if id in dual_variables:
constraint.dual_variable = dual_variables[id]

return Result(solution=solution.raw)
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from ommx.v1.function_pb2 import Function
from ommx.v1.linear_pb2 import Linear
from ommx.v1.solution_pb2 import State
from ommx.v1 import Instance, DecisionVariable
from ommx.v1 import Instance, DecisionVariable, Solution

from .exception import OMMXPythonMIPAdapterError

Expand Down Expand Up @@ -152,7 +152,7 @@ def model_to_instance(model: mip.Model) -> Instance:
return builder.build()


def model_to_solution(
def model_to_state(
model: mip.Model,
instance: Instance,
) -> State:
Expand Down Expand Up @@ -180,8 +180,8 @@ def model_to_solution(
>>> model.optimize()
<OptimizationStatus.OPTIMAL: 0>
>>> ommx_solutions = adapter.model_to_solution(model, ommx_instance)
>>> ommx_solutions.entries
>>> ommx_state = adapter.model_to_state(model, ommx_instance)
>>> ommx_state.entries
{1: 0.0}
"""
if not (
Expand All @@ -198,3 +198,54 @@ def model_to_solution(
for var in instance.raw.decision_variables
}
)


def model_to_solution(
model: mip.Model,
instance: Instance,
) -> Solution:
"""
Convert optimized Python-MIP model and ommx.v1.Instance to ommx.v1.Solution.
Examples
=========
.. doctest::
>>> from ommx.v1 import Instance, DecisionVariable
>>> from ommx_python_mip_adapter import instance_to_model, model_to_solution
>>> p = [10, 13, 18, 31, 7, 15]
>>> w = [11, 15, 20, 35, 10, 33]
>>> x = [DecisionVariable.binary(i) for i in range(6)]
>>> instance = Instance.from_components(
... decision_variables=x,
... objective=sum(p[i] * x[i] for i in range(6)),
... constraints=[sum(w[i] * x[i] for i in range(6)) <= 47],
... sense=Instance.MAXIMIZE,
... )
>>> model = instance_to_model(instance)
>>> model.optimize()
<OptimizationStatus.OPTIMAL: 0>
>>> solution = model_to_solution(model, instance)
>>> solution.raw.objective
41.0
"""
state = model_to_state(model, instance)
solution = instance.evaluate(state)

dual_variables = {}
for constraint in model.constrs:
pi = constraint.pi
if pi is not None:
id = int(constraint.name)
dual_variables[id] = pi
for constraint in solution.raw.evaluated_constraints:
id = constraint.id
if id in dual_variables:
constraint.dual_variable = dual_variables[id]

return solution
4 changes: 2 additions & 2 deletions python/ommx-python-mip-adapter/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "ommx_python_mip_adapter"
version = "1.3.2"
version = "1.4.0"

description = "An adapter for the Python-MIP from/to OMMX."
authors = [
Expand All @@ -23,7 +23,7 @@ classifiers = [
"License :: OSI Approved :: MIT License",
]
dependencies = [
"ommx >= 1.3.2, < 2.0.0",
"ommx >= 1.4.0, < 2.0.0",

# FIXME: This project requires latest version of Python-MIP (will be 1.16.0?), which does not release yet.
# https://github.com/coin-or/python-mip/issues/384
Expand Down
16 changes: 7 additions & 9 deletions python/ommx-python-mip-adapter/tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@ def test_integration_lp(generater):

model = adapter.instance_to_model(ommx_instance_bytes)
model.optimize()
ommx_solution = adapter.model_to_solution(model, ommx_instance_bytes)
ommx_state = adapter.model_to_state(model, ommx_instance_bytes)
expected_solution = generater.get_v1_state()
assert ommx_solution.entries.keys() == expected_solution.entries.keys()
for key in ommx_solution.entries.keys():
assert ommx_solution.entries[key] == pytest.approx(
expected_solution.entries[key]
)
assert ommx_state.entries.keys() == expected_solution.entries.keys()
for key in ommx_state.entries.keys():
assert ommx_state.entries[key] == pytest.approx(expected_solution.entries[key])


def test_integration_milp():
Expand All @@ -54,7 +52,7 @@ def test_integration_milp():

model = adapter.instance_to_model(ommx_instance)
model.optimize()
ommx_solution = adapter.model_to_solution(model, ommx_instance)
ommx_state = adapter.model_to_state(model, ommx_instance)

assert ommx_solution.entries[1] == pytest.approx(3)
assert ommx_solution.entries[2] == pytest.approx(3)
assert ommx_state.entries[1] == pytest.approx(3)
assert ommx_state.entries[2] == pytest.approx(3)
2 changes: 1 addition & 1 deletion python/ommx/docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
copyright = "2024, Jij Inc."
author = "Jij Inc."

version = "1.3.2"
version = "1.4.0"
release = version

# -- General configuration ---------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion python/ommx/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "maturin"
[project]
name = "ommx"

version = "1.3.2"
version = "1.4.0"
description = "Open Mathematical prograMming eXchange (OMMX)"
authors = [{ name="Jij Inc.", email="info@j-ij.com" }]
readme = "README.md"
Expand Down

0 comments on commit 12fbe2e

Please sign in to comment.