Solving Optimization Problems with OMMX Adapter#
OMMX provides OMMX Adapter software to enable interoperability with existing mathematical optimization tools. By using OMMX Adapter, you can convert optimization problems expressed in OMMX schemas into formats acceptable to other optimization tools, and convert the resulting data from those tools back into OMMX schemas.
Here, we introduce how to solve a 0-1 Knapsack Problem via OMMX PySCIPOpt Adapter.
Installing the Required Libraries#
First, install OMMX PySCIPOpt Adapter with:
pip install ommx-pyscipopt-adapter
Two Steps for Running the Optimization#

Fig. 4 Flow for solving 0-1 Knapsack Problem with OMMX PySCIPOpt Adapter.#
To solve the 0-1 Knapsack Problem through the OMMX PySCIPOpt Adapter, follow these two steps:
Prepare the 0-1 Knapsack problem instance.
Run the optimization via OMMX Adapter.
In Step 1, we create an ommx.v1.Instance
object defined in the OMMX Message Instance schema. There are several ways to generate this object, but in this guide, we’ll illustrate how to write it directly using the OMMX Python SDK.
Tip
There are four ways to prepare an ommx.v1.Instance
:
Write
ommx.v1.Instance
directly with the OMMX Python SDK.Convert an MPS file to
ommx.v1.Instance
using the OMMX Python SDK.Convert a problem instance from a different optimization tool into
ommx.v1.Instance
using an OMMX Adapter.Export
ommx.v1.Instance
from JijModeling.
In Step 2, we convert ommx.v1.Instance
into a PySCIPOpt Model
object and run optimization with SCIP. The result is obtained as an ommx.v1.Solution
object defined by the OMMX Message Solution schema.
Step 1: Preparing a 0-1 Knapsack Problem Instance#
The 0-1 Knapsack problem is formulated as:
We set the following data as parameters for this model.
# Data for 0-1 Knapsack Problem
v = [10, 13, 18, 31, 7, 15] # Values of each item
w = [11, 25, 20, 35, 10, 33] # Weights of each item
W = 47 # Capacity of the knapsack
N = len(v) # Total number of items
Below is an example code using the OMMX Python SDK to describe this problem instance.
from ommx.v1 import Instance, DecisionVariable
# Define decision variables
x = [
# Define binary variable x_i
DecisionVariable.binary(
# Specify the ID of the decision variable
id=i,
# Specify the name of the decision variable
name="x",
# Specify the subscript of the decision variable
subscripts=[i],
)
# Prepare binary variables for the number of items
for i in range(N)
]
# Define the objective function
objective = sum(v[i] * x[i] for i in range(N))
# Define the constraint
constraint = sum(w[i] * x[i] for i in range(N)) - W <= 0
# Specify the name of the constraint
constraint.add_name("Weight limit")
# Create an instance
instance = Instance.from_components(
# Register all decision variables included in the instance
decision_variables=x,
# Register the objective function
objective=objective,
# Register all constraints
constraints=[constraint],
# Specify that it is a maximization problem
sense=Instance.MAXIMIZE,
)
Step 2: Running Optimization with OMMX Adapter#
To optimize the instance prepared in Step 1, we convert it to a PySCIPOpt Model
and run SCIP optimization via the OMMX PySCIPOpt Adapter.
from ommx_pyscipopt_adapter import OMMXPySCIPOptAdapter
# Obtain an ommx.v1.Solution objection through a PySCIPOpt model.
solution = OMMXPySCIPOptAdapter.solve(instance)
The variable solution
is an ommx.v1.Solution
object that holds the results returned by SCIP.
Analyzing the Results#
From the solution
in Step 2, we can check:
The optimal solution (which items to pick to maximize total value)
The optimal value (maximum total value)
The status of constraints (how close we are to the knapsack weight limit)
We can do this with various properties in the ommx.v1.Solution
class.
Analyzing the Optimal Solution#
The decision_variables
property returns a pandas.DataFrame
containing information on each variable, such as ID, type, name, and value:
solution.decision_variables
kind | lower | upper | name | subscripts | description | substituted_value | value | |
---|---|---|---|---|---|---|---|---|
id | ||||||||
0 | binary | 0.0 | 1.0 | x | [0] | <NA> | <NA> | 1.0 |
1 | binary | 0.0 | 1.0 | x | [1] | <NA> | <NA> | 0.0 |
2 | binary | 0.0 | 1.0 | x | [2] | <NA> | <NA> | 0.0 |
3 | binary | 0.0 | 1.0 | x | [3] | <NA> | <NA> | 1.0 |
4 | binary | 0.0 | 1.0 | x | [4] | <NA> | <NA> | 0.0 |
5 | binary | 0.0 | 1.0 | x | [5] | <NA> | <NA> | 0.0 |
Using this pandas.DataFrame
, for example, you can easily create a table in pandas that shows which items are included in the knapsack.
import pandas as pd
df = solution.decision_variables
pd.DataFrame.from_dict(
{
"Item number": df.index,
"Include in knapsack?": df["value"].apply(lambda x: "Include" if x == 1.0 else "Exclude"),
}
)
Item number | Include in knapsack? | |
---|---|---|
id | ||
0 | 0 | Include |
1 | 1 | Exclude |
2 | 2 | Exclude |
3 | 3 | Include |
4 | 4 | Exclude |
5 | 5 | Exclude |
From this analysis, we see that choosing items 0 and 3 maximizes the total value while satisfying the knapsack’s weight constraint.
Analyzing the Optimal Value#
objective
stores the best value found. In this case, it should match the sum of items 0 and 3.
import numpy as np
# The expected value is the sum of the values of items 0 and 3
expected = v[0] + v[3]
assert np.isclose(solution.objective, expected)
Analyzing Constraints#
The constraints
property returns a pandas.DataFrame
that includes details about each constraint’s equality or inequality, its left-hand-side value ("value"
), name, and more.
solution.constraints
equality | value | used_ids | name | subscripts | description | dual_variable | removed_reason | |
---|---|---|---|---|---|---|---|---|
id | ||||||||
0 | <=0 | -1.0 | {0, 1, 2, 3, 4, 5} | Weight limit | [] | <NA> | <NA> | <NA> |
Specifically, The "value"
is helpful for understanding how much slack remains in each constraint. Here, item 0 weighs \(11\), item 3 weighs \(35\), and the knapsack’s capacity is \(47\). Therefore, for the weight constraint
the left-hand side “value” is \(-1\), indicating there is exactly 1 unit of slack under the capacity.