OMMX Python SDK 1.7.0#
Please refer to the GitHub Release for individual changes.
Summary#
QPLIB format parser
Several APIs have been added to
ommx.v1.SampleSet
andommx.v1.ParametricInstance
, and integration with OMMX Artifact has been added.For
ommx.v1.SampleSet
, please refer to the new explanation pageFor support of OMMX Artifact, please refer to the API reference ommx.artifact.Artifact and ommx.artifact.ArtifactBuilder.
Change in behavior of
{Solution, SampleSet}.feasible
QPLIB format parser#
Following the MPS format, support for the QPLIB format parser has been added.
import tempfile
# Example problem from QPLIB
#
# Furini, Fabio, et al. "QPLIB: a library of quadratic programming instances." Mathematical Programming Computation 11 (2019): 237-265 pages 42 & 43
# https://link.springer.com/article/10.1007/s12532-018-0147-4
contents = """
! ---------------
! example problem
! ---------------
MIPBAND # problem name
QML # problem is a mixed-integer quadratic program
Minimize # minimize the objective function
3 # variables
2 # general linear constraints
5 # nonzeros in lower triangle of Q^0
1 1 2.0 5 lines row & column index & value of nonzero in lower triangle Q^0
2 1 -1.0 |
2 2 2.0 |
3 2 -1.0 |
3 3 2.0 |
-0.2 default value for entries in b_0
1 # non default entries in b_0
2 -0.4 1 line of index & value of non-default values in b_0
0.0 value of q^0
4 # nonzeros in vectors b^i (i=1,...,m)
1 1 1.0 4 lines constraint, index & value of nonzero in b^i (i=1,...,m)
1 2 1.0 |
2 1 1.0 |
2 3 1.0 |
1.0E+20 infinity
1.0 default value for entries in c_l
0 # non default entries in c_l
1.0E+20 default value for entries in c_u
0 # non default entries in c_u
0.0 default value for entries in l
0 # non default entries in l
1.0 default value for entries in u
1 # non default entries in u
2 2.0 1 line of non-default indices and values in u
0 default variable type is continuous
1 # non default variable types
3 2 variable 3 is binary
1.0 default value for initial values for x
0 # non default entries in x
0.0 default value for initial values for y
0 # non default entries in y
0.0 default value for initial values for z
0 # non default entries in z
0 # non default names for variables
0 # non default names for constraints"#;
"""
# Create a named temporary file
with tempfile.NamedTemporaryFile(delete=False, suffix='.qplib') as temp_file:
temp_file.write(contents.encode())
qplib_sample_path = temp_file.name
print(f"QPLIB sample file created at: {qplib_sample_path}")
QPLIB sample file created at: /tmp/tmpgj09u52g.qplib
from ommx import qplib
# Load a QPLIB file
instance = qplib.load_file(qplib_sample_path)
# Display decision variables and constraints
display(instance.decision_variables)
display(instance.constraints)
kind | lower | upper | name | subscripts | description | substituted_value | |
---|---|---|---|---|---|---|---|
id | |||||||
0 | continuous | 0.0 | 1.0 | <NA> | [] | <NA> | <NA> |
1 | continuous | 0.0 | 2.0 | <NA> | [] | <NA> | <NA> |
2 | binary | 0.0 | 1.0 | <NA> | [] | <NA> | <NA> |
equality | type | used_ids | name | subscripts | description | |
---|---|---|---|---|---|---|
id | ||||||
2 | <=0 | linear | {0, 1} | Qplib_constr_0 [c_l] | [] | <NA> |
3 | <=0 | linear | {0, 2} | Qplib_constr_1 [c_l] | [] | <NA> |
Change in behavior of {Solution, SampleSet}.feasible
#
The behavior of
feasible
inommx.v1.Solution
andommx.v1.SampleSet
has been changed.The handling of
removed_constraints
introduced in Python SDK 1.6.0 has been changed. In 1.6.0,feasible
ignoredremoved_constraints
, but in 1.7.0,feasible
now considersremoved_constraints
.Additionally,
feasible_relaxed
which explicitly ignoresremoved_constraints
andfeasible_unrelaxed
which considersremoved_constraints
have been introduced.feasible
is an alias forfeasible_unrelaxed
.
To understand the behavior, let’s consider the following simple optimization problem:
from ommx.v1 import DecisionVariable, Instance
x = [DecisionVariable.binary(i) for i in range(3)]
instance = Instance.from_components(
decision_variables=x,
objective=sum(x),
constraints=[
(x[0] + x[1] <= 1).set_id(0),
(x[1] + x[2] <= 1).set_id(1),
],
sense=Instance.MAXIMIZE,
)
instance.constraints
equality | type | used_ids | name | subscripts | description | |
---|---|---|---|---|---|---|
id | ||||||
0 | <=0 | linear | {0, 1} | <NA> | [] | <NA> |
1 | <=0 | linear | {1, 2} | <NA> | [] | <NA> |
Next, we relax one of the constraints \(x_0 + x_1 \leq 1\).
instance.relax_constraint(constraint_id=0, reason="Manual relaxation")
display(instance.constraints)
display(instance.removed_constraints)
equality | type | used_ids | name | subscripts | description | |
---|---|---|---|---|---|---|
id | ||||||
1 | <=0 | linear | {1, 2} | <NA> | [] | <NA> |
equality | type | used_ids | name | subscripts | description | removed_reason | |
---|---|---|---|---|---|---|---|
id | |||||||
0 | <=0 | linear | {0, 1} | <NA> | [] | <NA> | Manual relaxation |
Now, \(x_0 = 1, x_1 = 1, x_2 = 0\) is not a solution to the original problem, but it is a solution to the relaxed problem. Therefore, feasible_relaxed
will be True
, but feasible_unrelaxed
will be False
. Since feasible
is an alias for feasible_unrelaxed
, it will be False
.
solution = instance.evaluate({0: 1, 1: 1, 2: 0})
print(f"{solution.feasible=}")
print(f"{solution.feasible_relaxed=}")
print(f"{solution.feasible_unrelaxed=}")
solution.feasible=False
solution.feasible_relaxed=True
solution.feasible_unrelaxed=False