ommx_python_mip_adapter#

Submodules#

Exceptions#

OMMXPythonMIPAdapterError

Common base class for all non-exit exceptions.

Classes#

OMMXPythonMIPAdapter

An abstract interface for OMMX Solver Adapters, defining how solvers should be used with OMMX.

Functions#

model_to_instance(→ ommx.Instance)

The function to convert Python-MIP Model to ommx.Instance.

Package Contents#

exception OMMXPythonMIPAdapterError#

Common base class for all non-exit exceptions.

class OMMXPythonMIPAdapter(ommx_instance: Instance, *, relax: bool = False, solver_name: str = mip.CBC, solver: mip.Solver | None = None, verbose: bool = False)#

An abstract interface for OMMX Solver Adapters, defining how solvers should be used with OMMX.

See the implementation guide for more details.

Subclasses declare INPUT_CLASS as the OMMX-defined structural class used by the first applicability condition. check_applicability does not mutate the input and combines class membership with the adapter's _check_preconditions hook.

INPUT_CLASS describes only which exact inputs an adapter accepts; it does not prescribe how the subclass processes them. The base class never lowers or otherwise mutates the input instance.

classmethod check_applicability(ommx_instance: Instance) AdapterApplicabilityReport#

Inspect applicability without mutating or preparing ommx_instance.

Adapter-specific preconditions run only after at least one complete input-class clause contains the instance. The hook receives an isolated copy so it cannot mutate the caller's instance. Any explicitly transformed value is a different input and must be checked separately.

decode(data: mip.Model) Solution#

Convert optimized Python-MIP model and ommx.Instance to ommx.Solution.

This method is intended to be used if the model has been acquired with solver_input for futher adjustment of the solver parameters, and separately optimizing the model.

Note that alterations to the model may make the decoding process incompatible -- decoding will only work if the model still describes effectively the same problem as the OMMX instance used to create the adapter.

When creating the solution, this method reflects the relax flag used in this adapter's constructor. The solution's relaxation metadata will be set _only_ if relax=True was passed to the constructor. There is no way for this adapter to get relaxation information from Python-MIP directly. If relaxing the model separately after obtaining it with solver_input, you must set solution.relaxation yourself if you care about this value.

Examples#

>>> from ommx import Instance, DecisionVariable
>>> from ommx_python_mip_adapter import OMMXPythonMIPAdapter

>>> p = [10, 13, 18, 32, 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={0: sum(w[i] * x[i] for i in range(6)) <= 47},
...     sense=Instance.MAXIMIZE,
... )

>>> adapter = OMMXPythonMIPAdapter(instance)
>>> model = adapter.solver_input
>>> # ... some modification of model's parameters
>>> model.optimize()
<OptimizationStatus.OPTIMAL: 0>

>>> solution = adapter.decode(model)
>>> solution.objective
42.0
decode_to_state(data: mip.Model) State#

Create an ommx.State from an optimized Python-MIP Model.

Examples#

The following example of solving an unconstrained linear optimization problem with x1 as the objective function.

>>> from ommx_python_mip_adapter import OMMXPythonMIPAdapter
>>> from ommx import Instance, DecisionVariable

>>> x1 = DecisionVariable.integer(1, lower=0, upper=5)
>>> ommx_instance = Instance.from_components(
...     decision_variables=[x1],
...     objective=x1,
...     constraints={},
...     sense=Instance.MINIMIZE,
... )
>>> adapter = OMMXPythonMIPAdapter(ommx_instance)
>>> model = adapter.solver_input
>>> model.optimize()
<OptimizationStatus.OPTIMAL: 0>

>>> ommx_state = adapter.decode_to_state(model)
>>> ommx_state.entries
{1: 0.0}
classmethod require_applicable(ommx_instance: Instance) AdapterApplicabilityReport#

Return the report or raise AdapterNotApplicableError.

classmethod solve(ommx_instance: Instance, relax: bool = False, verbose: bool = False, *, diagnostics: DiagnosticsSink | None = None) Solution#

Solve the given ommx.Instance using Python-MIP, returning an ommx.Solution.

パラメータ:
  • ommx_instance -- The ommx.Instance to solve.

  • relax -- If True, relax all integer variables to continuous variables by using the relax parameter in Python-MIP's Model.optimize() <https://docs.python-mip.com/en/latest/classes.html#mip.Model.optimize>.

  • verbose -- If True, enable Python-MIP's verbose mode

Examples#

KnapSack Problem

>>> from ommx import Instance, DecisionVariable
>>> from ommx_python_mip_adapter import OMMXPythonMIPAdapter

>>> p = [10, 13, 18, 32, 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={0: sum(w[i] * x[i] for i in range(6)) <= 47},
...     sense=Instance.MAXIMIZE,
... )

Solve it

>>> solution = OMMXPythonMIPAdapter.solve(instance)

Check output

>>> sorted([(id, value) for id, value in solution.state.entries.items()])
[(0, 1.0), (1, 0.0), (2, 0.0), (3, 1.0), (4, 0.0), (5, 0.0)]
>>> solution.feasible
True
>>> assert solution.optimality == Solution.OPTIMAL

p[0] + p[3] = 42
w[0] + w[3] = 46 <= 47

>>> solution.objective
42.0
>>> solution.get_constraint_value(0)
-1.0

Infeasible Problem

>>> from ommx import Instance, DecisionVariable
>>> from ommx_python_mip_adapter import OMMXPythonMIPAdapter

>>> x = DecisionVariable.integer(0, upper=3, lower=0)
>>> instance = Instance.from_components(
...     decision_variables=[x],
...     objective=x,
...     constraints={0: x >= 4},
...     sense=Instance.MAXIMIZE,
... )

>>> OMMXPythonMIPAdapter.solve(instance)
Traceback (most recent call last):
    ...
ommx.InfeasibleDetected: Model was infeasible

Unbounded Problem

>>> from ommx import Instance, DecisionVariable
>>> from ommx_python_mip_adapter import OMMXPythonMIPAdapter

>>> x = DecisionVariable.integer(0, lower=0)
>>> instance = Instance.from_components(
...     decision_variables=[x],
...     objective=x,
...     constraints={},
...     sense=Instance.MAXIMIZE,
... )

>>> OMMXPythonMIPAdapter.solve(instance)
Traceback (most recent call last):
    ...
ommx.adapter.UnboundedDetected: Model was unbounded

Dual variable

>>> from ommx import Instance, DecisionVariable
>>> from ommx_python_mip_adapter import OMMXPythonMIPAdapter

>>> x = DecisionVariable.continuous(0, lower=0, upper=1)
>>> y = DecisionVariable.continuous(1, lower=0, upper=1)
>>> instance = Instance.from_components(
...     decision_variables=[x, y],
...     objective=x + y,
...     constraints={0: x + y <= 1},
...     sense=Instance.MAXIMIZE,
... )

>>> solution = OMMXPythonMIPAdapter.solve(instance)
>>> solution.get_dual_variable(0)
1.0
INPUT_CLASS: ClassVar[InstanceClass | None]#
property solver_input: mip.Model#

The Python-MIP model generated from this OMMX instance

model_to_instance(model: mip.Model) Instance#

The function to convert Python-MIP Model to ommx.Instance.

Examples#

>>> model = mip.Model()
>>> x1=model.add_var(name="1", var_type=mip.INTEGER, lb=0, ub=5)
>>> x2=model.add_var(name="2", var_type=mip.CONTINUOUS, lb=0, ub=5)

>>> model.objective = - x1 - 2 * x2
>>> constr = model.add_constr(x1 + x2 - 6 <= 0)

>>> ommx_instance = adapter.model_to_instance(model)