ommx.v1

Submodules

Attributes

ConstraintHints

Equality

EvaluatedConstraint

EvaluatedDecisionVariable

EvaluatedNamedFunction

Kind

OneHot

Optimality

Relaxation

Rng

SampledConstraint

SampledDecisionVariable

SampledNamedFunction

Samples

Sense

Sos1

State

ToSamples

Type alias for convertible types to Samples.

ToState

Type alias for convertible types to State.

Classes

Bound

Variable bound representing the valid range for a decision variable.

Constraint

Constraints

DecisionVariable

Idiomatic wrapper of ommx.v1.DecisionVariable protobuf message.

Function

Helper class that provides a standard way to create an ABC using

Instance

Idiomatic wrapper of ommx.v1.Instance protobuf message.

Linear

Modeler API for linear function

NamedFunction

A named function attached to an optimization instance.

Parameter

Idiomatic wrapper of ommx.v1.Parameter protobuf message.

ParametricInstance

Idiomatic wrapper of ommx.v1.ParametricInstance protobuf message.

Polynomial

Helper class that provides a standard way to create an ABC using

Quadratic

Helper class that provides a standard way to create an ABC using

SampleSet

The output of sampling-based optimization algorithms, e.g. simulated annealing (SA).

Solution

Idiomatic wrapper of ommx.v1.Solution protobuf message.

Package Contents

class ommx.v1.Bound(lower: float, upper: float)

Variable bound representing the valid range for a decision variable.

This class provides a clean interface for working with variable bounds, including lower bounds, upper bounds, and various utility methods.

contains(value: float, atol: float = 1e-06) bool

Check if a value is within the bound with tolerance.

intersection(other: Bound) Bound | None

Get the intersection of two bounds, or None if no intersection.

is_finite() bool

Check if both bounds are finite (not infinite).

nearest_to_zero() float

Get the value within the bound that is nearest to zero.

classmethod negative() Bound

Create a negative bound (-inf, 0].

classmethod of_binary() Bound

Create a binary variable bound [0, 1].

classmethod positive() Bound

Create a positive bound [0, +inf).

classmethod unbounded() Bound

Create an unbounded range (-inf, +inf).

width() float

Get the width (upper - lower) of the bound.

property lower: float

Get the lower bound.

raw: ommx._ommx_rust.Bound
property upper: float

Get the upper bound.

class ommx.v1.Constraint(*, function: int | float | DecisionVariable | Linear | Quadratic | Polynomial | Function | ommx._ommx_rust.Function, equality: ommx._ommx_rust.Equality, id: int | None = None, name: str | None = None, description: str | None = None, subscripts: list[int] = [], parameters: dict[str, str] = {})

Constraints

Examples

>>> x = DecisionVariable.integer(1)
>>> y = DecisionVariable.integer(2)
>>> x + y == 1
Constraint(x1 + x2 - 1 == 0)

To set the name or other attributes, use methods like :py:meth:`add_name`.

>>> (x + y <= 5).add_name("constraint 1")
Constraint(x1 + x2 - 5 <= 0)
add_description(description: str) Constraint

Add or update the description of the constraint.

add_name(name: str) Constraint

Add or update the name of the constraint.

add_parameters(parameters: dict[str, str]) Constraint

Add or update parameters of the constraint.

add_subscripts(subscripts: list[int]) Constraint

Add subscripts to the constraint.

static from_bytes(data: bytes) Constraint
static from_protobuf(pb_constraint: constraint_pb2.Constraint) Constraint

Convert from protobuf Constraint to Rust Constraint via serialization

static from_raw(raw: ommx._ommx_rust.Constraint) Constraint
set_id(id: int) Constraint

Overwrite the constraint ID.

to_bytes() bytes
to_protobuf() constraint_pb2.Constraint

Convert to protobuf Constraint

EQUAL_TO_ZERO
LESS_THAN_OR_EQUAL_TO_ZERO
property description: str | None
property equality: ommx._ommx_rust.Equality
property function: Function
property id: int
property name: str | None
property parameters: dict[str, str]
raw: ommx._ommx_rust.Constraint
property subscripts: list[int]
class ommx.v1.DecisionVariable

Idiomatic wrapper of ommx.v1.DecisionVariable protobuf message.

Note that this object overloads == for creating a constraint, not for equality comparison for better integration to mathematical programming.

>>> x = DecisionVariable.integer(1)
>>> x == 1
Constraint(...)

To compare two objects, use equals_to() method.

>>> y = DecisionVariable.integer(2)
>>> x.equals_to(y)
False
static binary(id: int, *, name: str | None = None, subscripts: list[int] = [], parameters: dict[str, str] = {}, description: str | None = None) DecisionVariable
static continuous(id: int, *, lower: float = float('-inf'), upper: float = float('inf'), name: str | None = None, subscripts: list[int] = [], parameters: dict[str, str] = {}, description: str | None = None) DecisionVariable
equals_to(other: DecisionVariable) bool

Alternative to == operator to compare two decision variables.

static from_bytes(data: bytes) DecisionVariable
static from_protobuf(pb_dv: decision_variables_pb2.DecisionVariable) DecisionVariable

Convert from protobuf DecisionVariable to Rust DecisionVariable via serialization

static integer(id: int, *, lower: float = float('-inf'), upper: float = float('inf'), name: str | None = None, subscripts: list[int] = [], parameters: dict[str, str] = {}, description: str | None = None) DecisionVariable
static of_type(kind: Kind, id: int, *, lower: float, upper: float, name: str | None = None, subscripts: list[int] = [], parameters: dict[str, str] = {}, description: str | None = None) DecisionVariable
static semi_continuous(id: int, *, lower: float = float('-inf'), upper: float = float('inf'), name: str | None = None, subscripts: list[int] = [], parameters: dict[str, str] = {}, description: str | None = None) DecisionVariable
static semi_integer(id: int, *, lower: float = float('-inf'), upper: float = float('inf'), name: str | None = None, subscripts: list[int] = [], parameters: dict[str, str] = {}, description: str | None = None) DecisionVariable
to_bytes() bytes
to_protobuf() decision_variables_pb2.DecisionVariable

Convert to protobuf DecisionVariable via serialization

BINARY
CONTINUOUS
INTEGER
Kind
SEMI_CONTINUOUS
SEMI_INTEGER
property bound: Bound
property description: str
property id: int
property kind: Kind
property lower: float

Lower bound of the decision variable

property name: str
property parameters: dict[str, str]
raw: ommx._ommx_rust.DecisionVariable
property subscripts: list[int]
property substituted_value: float | None

The value of the decision variable fixed by :py:attr:`~Instance.partial_evaluate or presolvers.

property upper: float

Upper bound of the decision variable

class ommx.v1.Function(inner: int | float | DecisionVariable | Linear | Quadratic | Polynomial | ommx._ommx_rust.Function | function_pb2.Function)

Helper class that provides a standard way to create an ABC using inheritance.

almost_equal(other: Function, *, atol: float = 1e-10) bool

Compare two functions have almost equal coefficients as a polynomial

as_linear() Linear | None

Try to convert the function to a Linear object if it’s linear.

Returns:

A Linear object if the function is linear, None otherwise

as_quadratic() Quadratic | None

Try to convert the function to a Quadratic object if it’s quadratic.

Returns:

A Quadratic object if the function is quadratic, None otherwise

content_factor() float

For given polynomial \(f(x)\), get the minimal positive factor \(a\) which makes all coefficient of \(a f(x)\) integer. See also https://en.wikipedia.org/wiki/Primitive_part_and_content

Examples

\(\frac{1}{3} x_0 + \frac{3}{2} x_1\) can be multiplied by 6 to make all coefficients integer.

>>> x = [DecisionVariable.integer(i) for i in range(2)]
>>> f = Function((1.0/3.0)*x[0] + (3.0/2.0)*x[1])
>>> a = f.content_factor()
>>> (a, a*f)
(6.0, Function(2*x0 + 9*x1))

This works even for non-rational numbers like \(\pi\) because 64-bit float is actually rational.

>>> import math
>>> f = Function(math.pi*x[0] + 3*math.pi*x[1])
>>> a = f.content_factor()
>>> (a, a*f)
(0.3183098861837907, Function(x0 + 3*x1))

But this returns very large number if there is no multiplier:

>>> f = Function(math.pi*x[0] + math.e*x[1])
>>> a = f.content_factor()
>>> (a, a*f)
(3122347504612692.0, Function(9809143982445656*x0 + 8487420483923125*x1))

In practice, you must check if the multiplier is enough small.

degree() int

Get the degree of the polynomial function.

Returns:

The degree of the polynomial (0 for constants, 1 for linear, 2 for quadratic, etc.)

evaluate(state: ToState, *, atol: float | None = None) float

Evaluate the function with the given state.

Examples

Evaluate `2 x1 x2 + 3 x2 x3 + 1` with `x1 = 3, x2 = 4, x3 = 5`

>>> x1 = DecisionVariable.integer(1)
>>> x2 = DecisionVariable.integer(2)
>>> x3 = DecisionVariable.integer(3)
>>> f = Function(2*x1*x2 + 3*x2*x3 + 1)
>>> f
Function(2*x1*x2 + 3*x2*x3 + 1)

>>> f.evaluate({1: 3, 2: 4, 3: 5})
85.0

Missing ID raises an error
>>> f.evaluate({1: 3})
Traceback (most recent call last):
...
RuntimeError: Missing entry for id: 2
static from_bytes(data: bytes) Function
static from_raw(raw: ommx._ommx_rust.Function) Function
num_terms() int

Get the number of terms in the polynomial function.

Returns:

The number of terms in the polynomial

partial_evaluate(state: ToState, *, atol: float | None = None) Function

Partially evaluate the function with the given state.

Examples

Evaluate `2 x1 x2 + 3 x2 x3 + 1` with `x1 = 3`, yielding `3 x2 x3 + 6 x2 + 1`

>>> x1 = DecisionVariable.integer(1)
>>> x2 = DecisionVariable.integer(2)
>>> x3 = DecisionVariable.integer(3)
>>> f = Function(2*x1*x2 + 3*x2*x3 + 1)
>>> f
Function(2*x1*x2 + 3*x2*x3 + 1)

>>> f.partial_evaluate({1: 3})
Function(3*x2*x3 + 6*x2 + 1)
static random(rng: ommx._ommx_rust.Rng, num_terms: int = 5, max_degree: int = 3, max_id: int = 10) Function

Create a random function using the given random number generator.

Parameters:
  • rng – Random number generator

  • num_terms – Number of terms in the function

  • max_degree – Maximum degree of terms

  • max_id – Maximum variable ID to use

Returns:

Random Function

reduce_binary_power(binary_ids: set[int]) bool

Reduce binary powers in the function.

For binary variables, x^n = x for any n >= 1, so we can reduce higher powers to linear terms.

Parameters:

binary_ids – Set of binary variable IDs to reduce powers for

Returns:

True if any reduction was performed, False otherwise

Examples

Consider a function with binary variables and quadratic terms:

>>> from ommx.v1 import DecisionVariable, Function
>>> x0 = DecisionVariable.binary(0)
>>> x1 = DecisionVariable.binary(1)
>>> f = Function(x0 * x0 + x0 * x1)  # x0^2 + x0*x1
>>> f
Function(x0*x0 + x0*x1)

After reducing binary powers for variable 0, x0^2 becomes x0:

>>> changed = f.reduce_binary_power({0})
>>> changed
True
>>> f
Function(x0*x1 + x0)

Running it again should not change anything:

>>> changed = f.reduce_binary_power({0})
>>> changed
False
to_bytes() bytes
used_decision_variable_ids() set[int]

Get the IDs of decision variables used in the function.

property constant_term: float

Get the constant term of the function.

Returns:

The constant term. Returns 0.0 if function has no constant term. Works for all polynomial functions by filtering the degree-0 term.

Examples

>>> x1 = DecisionVariable.integer(1)
>>> f = Function(2*x1 + 5)
>>> f.constant_term
5.0
>>> f_no_const = Function(2*x1)
>>> f_no_const.constant_term
0.0
>>> f_const_only = Function(7)
>>> f_const_only.constant_term
7.0
>>> # Works for higher degree polynomials too
>>> f_cubic = Function(x1*x1*x1 + 2*x1 + 5)
>>> f_cubic.constant_term
5.0
property linear_terms: dict[int, float]

Get linear terms as a dictionary mapping variable id to coefficient.

Returns:

Dictionary mapping variable IDs to their linear coefficients. Returns empty dict if function has no linear terms. Works for all polynomial functions by filtering only degree-1 terms.

Examples

>>> x1 = DecisionVariable.integer(1)
>>> x2 = DecisionVariable.integer(2)
>>> f = Function(2*x1 + 3*x2 + 5)
>>> f.linear_terms
{1: 2.0, 2: 3.0}
>>> f_const = Function(5)
>>> f_const.linear_terms
{}
>>> # Works for higher degree polynomials too
>>> f_cubic = Function(x1*x1*x1 + 2*x1 + 5)
>>> f_cubic.linear_terms
{1: 2.0}
property quadratic_terms: dict[tuple[int, int], float]

Get quadratic terms as a dictionary mapping (row, col) to coefficient.

Returns:

Dictionary mapping variable ID pairs to their quadratic coefficients. Returns empty dict if function has no quadratic terms. Works for all polynomial functions by filtering only degree-2 terms.

Examples

>>> x1 = DecisionVariable.integer(1)
>>> x2 = DecisionVariable.integer(2)
>>> f = Function(2*x1*x2 + 3*x1 + 5)
>>> f.quadratic_terms
{(1, 2): 2.0}
>>> f_linear = Function(3*x1 + 5)
>>> f_linear.quadratic_terms
{}
>>> # Works for higher degree polynomials too
>>> f_cubic = Function(x1*x1*x1 + 2*x1*x2 + 5)
>>> f_cubic.quadratic_terms
{(1, 2): 2.0}
raw: ommx._ommx_rust.Function
property terms: dict[tuple[int, Ellipsis], float]

All terms as a dictionary mapping variable id tuples to coefficients

Returns dictionary with tuple keys (hashable) instead of list keys.

Examples

>>> from ommx.v1 import Function, Linear, DecisionVariable
>>> x = DecisionVariable.binary(1, name="x")
>>> linear = Linear(terms={1: 2.5}, constant=1.0)
>>> func = Function(linear)
>>> func.terms
{(1,): 2.5, (): 1.0}
class ommx.v1.Instance

Idiomatic wrapper of ommx.v1.Instance protobuf message.

Note that this class also contains annotations like title which are not contained in protobuf message but stored in OMMX artifact. These annotations are loaded from annotations while reading from OMMX artifact.

Examples

Create an instance for KnapSack Problem

>>> from ommx.v1 import Instance, DecisionVariable

Profit and weight of items

>>> p = [10, 13, 18, 31, 7, 15]
>>> w = [11, 15, 20, 35, 10, 33]

Decision variables

>>> x = [DecisionVariable.binary(i) for i in range(6)]

Objective and constraint

>>> objective = sum(p[i] * x[i] for i in range(6))
>>> constraint = sum(w[i] * x[i] for i in range(6)) <= 47

Compose as an instance

>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=objective,
...     constraints=[constraint],
...     sense=Instance.MAXIMIZE,
... )
add_integer_slack_to_inequality(constraint_id: int, slack_upper_bound: int) float | None

Convert inequality \(f(x) \leq 0\) to inequality \(f(x) + b s \leq 0\) with an integer slack variable s.

  • This should be used when convert_inequality_to_equality_with_integer_slack() is not applicable

  • The bound of \(s\) will be [0, slack_upper_bound], and the coefficients \(b\) are determined from the lower bound of \(f(x)\).

  • Since the slack variable is integer, the yielded inequality has residual error \(\min_s f(x) + b s\) at most \(b\). And thus \(b\) is returned to use scaling the penalty weight or other things.

    • Larger slack_upper_bound (i.e. fined-grained slack) yields smaller b, and thus smaller the residual error. But it needs more bits for the slack variable, and thus the problem size becomes larger.

  • Since this method evaluates the bound of \(f(x)\), we may find that:

    • The bound \([l, u]\) is strictly positive, i.e. \(l \gt 0\). This means the instance is infeasible because this constraint never be satisfied. In this case, an error is raised.

    • The bound \([l, u]\) is always negative, i.e. \(u \leq 0\). This means this constraint is trivially satisfied. In this case, the constraint is moved to removed_constraints, and this method returns without introducing slack variable or raising an error.

Returns:

The coefficient \(b\) of the slack variable. If the constraint is trivially satisfied, this returns None.

Examples

Let’s consider a simple inequality constraint \(x_0 + 2x_1 \leq 4\).

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [
...     DecisionVariable.integer(i, lower=0, upper=3, name="x", subscripts=[i])
...     for i in range(3)
... ]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(x),
...     constraints=[
...         (x[0] + 2*x[1] <= 4).set_id(0)   # Set ID manually to use after
...     ],
...     sense=Instance.MAXIMIZE,
... )
>>> instance.constraints[0]
Constraint(x0 + 2*x1 - 4 <= 0)

Introduce an integer slack variable \(s \in [0, 2]\)

>>> b = instance.add_integer_slack_to_inequality(
...     constraint_id=0,
...     slack_upper_bound=2
... )
>>> b, instance.constraints[0]
(2.0, Constraint(x0 + 2*x1 + 2*x3 - 4 <= 0))

The slack variable is added to the decision variables with name ommx.slack and the constraint ID is stored in subscripts.

>>> instance.decision_variables_df.dropna(axis=1, how="all")
       kind  lower  upper        name subscripts
id
0   Integer    0.0    3.0           x        [0]
1   Integer    0.0    3.0           x        [1]
2   Integer    0.0    3.0           x        [2]
3   Integer    0.0    2.0  ommx.slack        [0]

In this case, the slack variable only take \(s = \{ 0, 1, 2 \}\), and thus the residual error is not disappear for \(x_0 = x_1 = 1\) case \(f(x) + b \cdot x = 1 + 2 \cdot 1 + 2 \cdot s - 4 = 2s - 1\).

add_user_annotation(key: str, value: str, *, annotation_namespace: str = 'org.ommx.user.')

Add a user annotation to the instance.

Examples

>>> instance = Instance.empty()
>>> instance.add_user_annotation("author", "Alice")
>>> instance.get_user_annotations()
{'author': 'Alice'}
>>> instance.annotations
{'org.ommx.user.author': 'Alice'}
as_hubo_format() tuple[dict[tuple[int, Ellipsis], float], float]

Convert unconstrained quadratic instance to a dictionary-based HUBO format.

Note

This is a single-purpose method to only convert the format, not to execute any conversion of the instance. Use to_hubo() driver for the full HUBO conversion.

as_maximization_problem() bool

Convert the instance to a maximization problem.

If the instance is already a maximization problem, this does nothing.

Returns:

True if the instance is converted, False if already a maximization problem.

Examples

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [DecisionVariable.binary(i) for i in range(3)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(x),
...     constraints=[sum(x) == 1],
...     sense=Instance.MINIMIZE,
... )
>>> instance.sense == Instance.MINIMIZE
True
>>> instance.objective
Function(x0 + x1 + x2)

Convert to a maximization problem

>>> instance.as_maximization_problem()
True
>>> instance.sense == Instance.MAXIMIZE
True
>>> instance.objective
Function(-x0 - x1 - x2)

If the instance is already a maximization problem, this does nothing

>>> instance.as_maximization_problem()
False
>>> instance.sense == Instance.MAXIMIZE
True
>>> instance.objective
Function(-x0 - x1 - x2)
as_minimization_problem() bool

Convert the instance to a minimization problem.

If the instance is already a minimization problem, this does nothing.

Returns:

True if the instance is converted, False if already a minimization problem.

Examples

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [DecisionVariable.binary(i) for i in range(3)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(x),
...     constraints=[sum(x) == 1],
...     sense=Instance.MAXIMIZE,
... )
>>> instance.sense == Instance.MAXIMIZE
True
>>> instance.objective
Function(x0 + x1 + x2)

Convert to a minimization problem

>>> instance.as_minimization_problem()
True
>>> instance.sense == Instance.MINIMIZE
True
>>> instance.objective
Function(-x0 - x1 - x2)

If the instance is already a minimization problem, this does nothing

>>> instance.as_minimization_problem()
False
>>> instance.sense == Instance.MINIMIZE
True
>>> instance.objective
Function(-x0 - x1 - x2)
as_parametric_instance() ParametricInstance

Convert the instance to a ParametricInstance.

as_qubo_format() tuple[dict[tuple[int, int], float], float]

Convert unconstrained quadratic instance to PyQUBO-style format.

Note

This is a single-purpose method to only convert the format, not to execute any conversion of the instance. Use to_qubo() driver for the full QUBO conversion.

convert_inequality_to_equality_with_integer_slack(constraint_id: int, max_integer_range: int)

Convert an inequality constraint \(f(x) \leq 0\) to an equality constraint \(f(x) + s/a = 0\) with an integer slack variable s.

  • Since \(a\) is determined as the minimal multiplier to make the every coefficient of \(af(x)\) integer, \(a\) itself and the range of \(s\) becomes impractically large. max_integer_range limits the maximal range of \(s\), and returns error if the range exceeds it. See also content_factor().

  • Since this method evaluates the bound of \(f(x)\), we may find that:

    • The bound \([l, u]\) is strictly positive, i.e. \(l \gt 0\). This means the instance is infeasible because this constraint never be satisfied. In this case, an error is raised.

    • The bound \([l, u]\) is always negative, i.e. \(u \leq 0\). This means this constraint is trivially satisfied. In this case, the constraint is moved to removed_constraints, and this method returns without introducing slack variable or raising an error.

Examples

Let’s consider a simple inequality constraint \(x_0 + 2x_1 \leq 5\).

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [
...     DecisionVariable.integer(i, lower=0, upper=3, name="x", subscripts=[i])
...     for i in range(3)
... ]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(x),
...     constraints=[
...         (x[0] + 2*x[1] <= 5).set_id(0)   # Set ID manually to use after
...     ],
...     sense=Instance.MAXIMIZE,
... )
>>> instance.constraints[0]
Constraint(x0 + 2*x1 - 5 <= 0)

Introduce an integer slack variable

>>> instance.convert_inequality_to_equality_with_integer_slack(
...     constraint_id=0,
...     max_integer_range=32
... )
>>> instance.constraints[0]
Constraint(x0 + 2*x1 + x3 - 5 == 0)

The slack variable is added to the decision variables with name ommx.slack and the constraint ID is stored in subscripts.

>>> instance.decision_variables_df.dropna(axis=1, how="all")
       kind  lower  upper        name subscripts
id
0   Integer    0.0    3.0           x        [0]
1   Integer    0.0    3.0           x        [1]
2   Integer    0.0    3.0           x        [2]
3   Integer    0.0    5.0  ommx.slack        [0]
decision_variable_analysis() DecisionVariableAnalysis

Analyze decision variables in the optimization problem instance.

Returns a comprehensive analysis of all decision variables including: - Kind-based partitioning (binary, integer, continuous, etc.) - Usage-based partitioning (used in objective, constraints, fixed, etc.) - Variable bounds information

Returns:

Analysis object containing detailed information about decision variables

Return type:

DecisionVariableAnalysis

Examples

>>> x = [DecisionVariable.binary(i) for i in range(3)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=x[0] + x[1],
...     constraints=[(x[1] + x[2] == 1).set_id(0)],
...     sense=Instance.MAXIMIZE,
... )
>>> analysis = instance.decision_variable_analysis()
>>> analysis.used_decision_variable_ids()
{0, 1, 2}
>>> analysis.used_in_objective()
{0, 1}
>>> analysis.used_in_constraints()
{0: {1, 2}}
static empty() Instance

Create trivial empty instance of minimization with zero objective, no constraints, and no decision variables.

evaluate(state: ToState, *, atol: float | None = None) Solution

Evaluate the given State into a Solution.

This method evaluates the problem instance using the provided state (a map from decision variable IDs to their values), and returns a Solution object containing objective value, evaluated constraint values, and feasibility information.

Examples

Create a simple instance with three binary variables and evaluate a solution:

\[\begin{split}\begin{align*} \max & \space x_0 + x_1 + x_2 & \\ \text{ s.t. } & \space x_0 + x_1 \leq 1 & \\ & \space x_0, x_1, x_2 \in \{0, 1\} \end{align*}\end{split}\]
>>> from ommx.v1 import Instance, DecisionVariable
>>> 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)],
...     sense=Instance.MAXIMIZE,
... )

Evaluate it with a state \(x_0 = 1, x_1 = 0, x_2 = 0\), and show the objective and constraints:

>>> solution = instance.evaluate({0: 1, 1: 0, 2: 0})
>>> solution.objective
1.0
>>> solution.constraints_df.dropna(axis=1, how="all")
   equality  value used_ids subscripts
id
0       <=0    0.0   {0, 1}         []

The values of decision variables are also stored in the solution:

>>> solution.decision_variables_df.dropna(axis=1, how="all")
      kind  lower  upper subscripts  value
id
0   Binary    0.0    1.0         []    1.0
1   Binary    0.0    1.0         []    0.0
2   Binary    0.0    1.0         []    0.0

If the value is out of the range, the solution is infeasible:

>>> solution = instance.evaluate({0: 1, 1: 0, 2: 2})
>>> solution.feasible
False

If some of the decision variables are not set, this raises an error:

>>> instance.evaluate({0: 1, 1: 0})
Traceback (most recent call last):
    ...
RuntimeError: The state does not contain some required IDs: {VariableID(2)}

Irrelevant decision variables

Sometimes, the instance contains decision variables that are not used in the objective or constraints.

>>> x = [DecisionVariable.binary(i) for i in range(3)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=x[0],
...     constraints=[(x[0] + x[1] == 1).set_id(0)],
...     sense=Instance.MAXIMIZE,
... )

This instance does not contain the decision variable \(x_2\) in the objective or constraints. We call such variables “irrelevant”. This is mathematically meaningless, but sometimes useful in data science application. Since the irrelevant variables cannot be determined from the instance, solvers will ignore them, and do not return their values. This function works as well for such cases:

>>> solution = instance.evaluate({0: 1, 1: 0})
>>> solution.objective
1.0
>>> solution.constraints_df.dropna(axis=1, how="all")
   equality  value used_ids subscripts
id
0        =0    0.0   {0, 1}         []

The irrelevant decision variable \(x_2\) is also stored in the Solution with the value nearest to 0 within its bound. For example,

  • When the bound is \([-1, 1]\) or \((-\infty, \infty)\), the value is 0

  • When the bound is \([2, 5]\), the value is 2

  • When the bound is \([-3, -1]\), the value is -1

>>> solution.decision_variables_df.dropna(axis=1, how="all")
      kind  lower  upper subscripts  value
id
0   Binary    0.0    1.0         []    1.0
1   Binary    0.0    1.0         []    0.0
2   Binary    0.0    1.0         []    0.0
evaluate_samples(samples: ToSamples, *, atol: float | None = None) SampleSet

Evaluate the instance with multiple states.

static from_bytes(data: bytes) Instance
static from_components(*, objective: int | float | DecisionVariable | Linear | Quadratic | Polynomial | Function | function_pb2.Function | ommx._ommx_rust.Function, constraints: Iterable[Constraint | constraint_pb2.Constraint], sense: ommx._ommx_rust.Sense, decision_variables: Iterable[DecisionVariable | decision_variables_pb2.DecisionVariable], named_functions: Iterable[NamedFunction] | None = None, description: Instance | None = None, constraint_hints: ConstraintHints | None = None) Instance
get_constraint_by_id(constraint_id: int) Constraint

Get a constraint by ID.

get_decision_variable_by_id(variable_id: int) DecisionVariable

Get a decision variable by ID.

get_named_function_by_id(named_function_id: int) NamedFunction

Get a named function by ID.

get_removed_constraint_by_id(removed_constraint_id: int) RemovedConstraint

Get a removed constraint by ID.

get_user_annotation(key: str, *, annotation_namespace: str = 'org.ommx.user.')

Get a user annotation from the instance.

Examples

>>> instance = Instance.empty()
>>> instance.add_user_annotation("author", "Alice")
>>> instance.get_user_annotation("author")
'Alice'
get_user_annotations(*, annotation_namespace: str = 'org.ommx.user.') dict[str, str]

Get user annotations from the instance.

See also add_user_annotation().

static load_mps(path: str) Instance
static load_qplib(path: str) Instance
log_encode(decision_variable_ids: set[int] = set({}))

Log-encode the integer decision variables

Log encoding of an integer variable \(x \in [l, u]\) is to represent by \(m\) bits \(b_i \in \{0, 1\}\) by

\[x = \sum_{i=0}^{m-2} 2^i b_i + (u - l - 2^{m-1} + 1) b_{m-1} + l\]

where \(m = \lceil \log_2(u - l + 1) \rceil\).

Parameters:

decision_variable_ids – The IDs of the integer decision variables to log-encode. If not specified, all integer variables are log-encoded.

Examples

Let’s consider a simple integer programming problem with three integer variables \(x_0\), \(x_1\), and \(x_2\).

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [
...     DecisionVariable.integer(i, lower=0, upper=3, name="x", subscripts=[i])
...     for i in range(3)
... ]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(x),
...     constraints=[],
...     sense=Instance.MAXIMIZE,
... )
>>> instance.objective
Function(x0 + x1 + x2)

To log-encode the integer variables \(x_0\) and \(x_2\) (except \(x_1\)), call log_encode():

>>> instance.log_encode({0, 2})

Integer variable in range \([0, 3]\) can be represented by two binary variables:

\[x_0 = b_{0, 0} + 2 b_{0, 1}, x_2 = b_{2, 0} + 2 b_{2, 1}\]

And these are substituted into the objective and constraint functions.

>>> instance.objective
Function(x1 + x3 + 2*x4 + x5 + 2*x6)

Added binary variables are also appeared in decision_variables

>>> instance.decision_variables_df.dropna(axis=1, how="all")
       kind  lower  upper             name subscripts
id
0   Integer    0.0    3.0                x        [0]
1   Integer    0.0    3.0                x        [1]
2   Integer    0.0    3.0                x        [2]
3    Binary    0.0    1.0  ommx.log_encode     [0, 0]
4    Binary    0.0    1.0  ommx.log_encode     [0, 1]
5    Binary    0.0    1.0  ommx.log_encode     [2, 0]
6    Binary    0.0    1.0  ommx.log_encode     [2, 1]

The subscripts of the new binary variables must be two elements in form of \([i, j]\) where

  • \(i\) is the decision variable ID of the original integer variable

  • \(j\) is the index of the binary variable

After log-encoded, the problem does not contains original integer variables, and solver will returns only encoded variables.

>>> solution = instance.evaluate({
...   1: 2,          # x1 = 2
...   3: 0, 4: 1,    # x0 = x3 + 2*x4 = 0 + 2*1 = 2
...   5: 0, 6: 0     # x2 = x5 + 2*x6 = 0 + 2*0 = 0
... })               # x0 and x2 are not contained in the solver result

x0 and x2 are automatically evaluated:

>>> solution.extract_decision_variables("x")
{(0,): 2.0, (1,): 2.0, (2,): 0.0}

The name of the binary variables are automatically generated as ommx.log_encode.

>>> solution.extract_decision_variables("ommx.log_encode")
{(0, 0): 0.0, (0, 1): 1.0, (2, 0): 0.0, (2, 1): 0.0}
logical_memory_profile() str

Generate folded stack format for memory profiling of this instance.

This method generates a format compatible with flamegraph visualization tools like flamegraph.pl and inferno. Each line has the format: “frame1;frame2;…;frameN bytes”

The output shows the hierarchical memory structure of the instance, making it easy to identify which components are consuming the most memory.

To visualize with flamegraph:

  1. Save the output to a file: profile.txt

  2. Generate SVG: flamegraph.pl profile.txt > memory.svg

  3. Open memory.svg in a browser

Returns:

Folded stack format string that can be visualized with flamegraph tools

Return type:

str

Examples

>>> x = [DecisionVariable.binary(i) for i in range(3)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=x[0] + x[1],
...     constraints=[],
...     sense=Instance.MAXIMIZE,
... )
>>> print(instance.logical_memory_profile())
Instance.constraint_hints;ConstraintHints.one_hot_constraints;Vec[stack] 24
Instance.constraint_hints;ConstraintHints.sos1_constraints;Vec[stack] 24
Instance.constraints;BTreeMap[stack] 24
Instance.decision_variable_dependency;AcyclicAssignments.assignments;FnvHashMap[stack] 32
Instance.decision_variable_dependency;AcyclicAssignments.dependency 144
Instance.decision_variables;BTreeMap[key] 24
Instance.decision_variables;BTreeMap[stack] 24
Instance.decision_variables;DecisionVariable.bound 48
Instance.decision_variables;DecisionVariable.id 24
Instance.decision_variables;DecisionVariable.kind 3
Instance.decision_variables;DecisionVariable.metadata;DecisionVariableMetadata.description;Option[stack] 72
Instance.decision_variables;DecisionVariable.metadata;DecisionVariableMetadata.name;Option[stack] 72
Instance.decision_variables;DecisionVariable.metadata;DecisionVariableMetadata.parameters;FnvHashMap[stack] 96
Instance.decision_variables;DecisionVariable.metadata;DecisionVariableMetadata.subscripts;Vec[stack] 72
Instance.decision_variables;DecisionVariable.substituted_value;Option[stack] 48
Instance.description;Option[stack] 96
Instance.objective;Linear;PolynomialBase.terms 80
Instance.parameters;Option[stack] 48
Instance.removed_constraints;BTreeMap[stack] 24
Instance.sense 1
partial_evaluate(state: ToState, *, atol: float | None = None) Instance

Creates a new instance with specific decision variables fixed to given values.

This method substitutes the specified decision variables with their provided values, creating a new problem instance where these variables are fixed. This is useful for scenarios such as:

  • Creating simplified sub-problems with some variables fixed

  • Incrementally solving a problem by fixing some variables and optimizing the rest

  • Testing specific configurations of a problem

Parameters:
  • state (ToState) – Maps decision variable IDs to their fixed values. Can be a State object or a dictionary mapping variable IDs to values.

  • atol (float | None) – Absolute tolerance for floating point comparisons. If None, uses the default tolerance.

Returns:

A new instance with the specified decision variables fixed to their given values.

Return type:

Instance

Examples

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = DecisionVariable.binary(1)
>>> y = DecisionVariable.binary(2)
>>> instance = Instance.from_components(
...     decision_variables=[x, y],
...     objective=x + y,
...     constraints=[x + y <= 1],
...     sense=Instance.MINIMIZE
... )
>>> new_instance = instance.partial_evaluate({1: 1})
>>> new_instance.objective
Function(x2 + 1)

Substituted value is stored in the decision variable:

>>> x = new_instance.get_decision_variable_by_id(1)
>>> x.substituted_value
1.0

It appears in the decision variables DataFrame:

>>> new_instance.decision_variables_df.dropna(axis=1, how="all")
      kind  lower  upper subscripts substituted_value
id
1   Binary    0.0    1.0         []               1.0
2   Binary    0.0    1.0         []              <NA>
penalty_method() ParametricInstance

Convert to a parametric unconstrained instance by penalty method.

Roughly, this converts a constrained problem

\[\begin{split}\begin{align*} \min_x & \space f(x) & \\ \text{ s.t. } & \space g_i(x) = 0 & (\forall i) \\ & \space h_j(x) \leq 0 & (\forall j) \end{align*}\end{split}\]

to an unconstrained problem with parameters

\[\min_x f(x) + \sum_i \lambda_i g_i(x)^2 + \sum_j \rho_j h_j(x)^2\]

where \(\lambda_i\) and \(\rho_j\) are the penalty weight parameters for each constraint. If you want to use single weight parameter, use uniform_penalty_method() instead.

The removed constrains are stored in removed_constraints.

Note

Note that this method converts inequality constraints \(h(x) \leq 0\) to \(|h(x)|^2\) not to \(\max(0, h(x))^2\). This means the penalty is enforced even for \(h(x) < 0\) cases, and \(h(x) = 0\) is unfairly favored.

This feature is intended to use with add_integer_slack_to_inequality().

Examples

>>> from ommx.v1 import Instance, DecisionVariable, Constraint
>>> 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, x[1] + x[2] == 1],
...     sense=Instance.MAXIMIZE,
... )
>>> instance.objective
Function(x0 + x1 + x2)
>>> pi = instance.penalty_method()

The constraint is put in removed_constraints

>>> pi.constraints
[]
>>> len(pi.removed_constraints)
2
>>> pi.removed_constraints[0]
RemovedConstraint(x0 + x1 - 1 == 0, reason=penalty_method, parameter_id=3)
>>> pi.removed_constraints[1]
RemovedConstraint(x1 + x2 - 1 == 0, reason=penalty_method, parameter_id=4)

There are two parameters corresponding to the two constraints

>>> len(pi.parameters)
2
>>> p1 = pi.parameters[0]
>>> p1.id, p1.name
(3, 'penalty_weight')
>>> p2 = pi.parameters[1]
>>> p2.id, p2.name
(4, 'penalty_weight')

Substitute all parameters to zero to get the original objective

>>> instance0 = pi.with_parameters({p1.id: 0.0, p2.id: 0.0})
>>> instance0.objective
Function(x0 + x1 + x2)

Substitute all parameters to one

>>> instance1 = pi.with_parameters({p1.id: 1.0, p2.id: 1.0})
>>> instance1.objective
Function(x0*x0 + 2*x0*x1 + 2*x1*x1 + 2*x1*x2 + x2*x2 - x0 - 3*x1 - x2 + 2)
random_samples(rng: ommx._ommx_rust.Rng, *, num_different_samples: int = 5, num_samples: int = 10, max_sample_id: int | None = None) Samples

Generate random samples for this instance.

The generated samples will contain num_samples sample entries divided into num_different_samples groups, where each group shares the same state but has different sample IDs.

Parameters:
  • rng – Random number generator

  • num_different_samples – Number of different states to generate

  • num_samples – Total number of samples to generate

  • max_sample_id – Maximum sample ID (default: num_samples)

Returns:

Samples object

Examples

Generate samples for a simple instance:

>>> x = [DecisionVariable.binary(i) for i in range(3)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(x),
...     constraints=[(sum(x) <= 2).set_id(0)],
...     sense=Instance.MAXIMIZE,
... )
>>> rng = Rng()
>>> samples = instance.random_samples(rng, num_different_samples=2, num_samples=5)
>>> samples.num_samples()
5

Each generated state respects variable bounds:

>>> for sample_id in samples.sample_ids():
...     state = samples.get_state(sample_id)
...     for var_id, value in state.entries.items():
...         assert value in [0.0, 1.0], f"Binary variable {var_id} has invalid value {value}"
random_state(rng: ommx._ommx_rust.Rng) State

Generate a random state for this instance using the provided random number generator.

This method generates random values only for variables that are actually used in the objective function or constraints, as determined by decision variable analysis. Generated values respect the bounds of each variable type.

Parameters:

rng (_ommx_rust.Rng) – Random number generator to use for generating the state.

Returns:

A randomly generated state that satisfies the variable bounds of this instance. Only contains values for variables that are used in the problem.

Return type:

State

Examples

### Generate random state only for used variables

>>> from ommx.v1 import Instance, DecisionVariable, Rng
>>> x = [DecisionVariable.binary(i) for i in range(5)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=x[0] + x[1],  # Only x[0] and x[1] are used
...     constraints=[],
...     sense=Instance.MAXIMIZE,
... )
>>> rng = Rng()
>>> state = instance.random_state(rng)

Only used variables have values

>>> set(state.entries.keys())
{0, 1}

Values respect binary bounds

>>> all(state.entries[i] in [0.0, 1.0] for i in state.entries)
True

### Generate random state respecting variable bounds

>>> x_bin = DecisionVariable.binary(0)
>>> x_int = DecisionVariable.integer(1, lower=3, upper=7)
>>> x_cont = DecisionVariable.continuous(2, lower=-2.5, upper=5.0)
>>> instance = Instance.from_components(
...     decision_variables=[x_bin, x_int, x_cont],
...     objective=x_bin + x_int + x_cont,
...     constraints=[],
...     sense=Instance.MINIMIZE,
... )
>>> rng = Rng()
>>> state = instance.random_state(rng)

Values respect their respective bounds

>>> state.entries[0] in [0.0, 1.0]  # Binary
True
>>> 3.0 <= state.entries[1] <= 7.0  # Integer
True
>>> -2.5 <= state.entries[2] <= 5.0  # Continuous
True
reduce_binary_power() bool

Reduce binary powers in the instance.

This method replaces binary powers in the instance with their equivalent linear expressions. For binary variables, x^n = x for any n >= 1, so we can reduce higher powers to linear terms.

Returns:

True if any reduction was performed, False otherwise.

Examples

Consider an instance with binary variables and quadratic terms:

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [DecisionVariable.binary(i) for i in range(2)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=x[0] * x[0] + x[0] * x[1],  # x0^2 + x0*x1
...     constraints=[],
...     sense=Instance.MINIMIZE,
... )
>>> instance.objective
Function(x0*x0 + x0*x1)

After reducing binary powers, x0^2 becomes x0:

>>> changed = instance.reduce_binary_power()
>>> changed
True
>>> instance.objective
Function(x0*x1 + x0)

Running it again should not change anything:

>>> changed = instance.reduce_binary_power()
>>> changed
False
relax_constraint(constraint_id: int, reason: str, **parameters)

Remove a constraint from the instance. The removed constraint is stored in removed_constraints, and can be restored by restore_constraint().

Parameters:
  • constraint_id – The ID of the constraint to remove.

  • reason – The reason why the constraint is removed.

  • parameters – Additional parameters to describe the reason.

Examples

Relax constraint, and restore it.

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [DecisionVariable.binary(i) for i in range(3)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(x),
...     constraints=[(sum(x) == 3).set_id(1)],
...     sense=Instance.MAXIMIZE,
... )
>>> instance.constraints
[Constraint(x0 + x1 + x2 - 3 == 0)]

>>> instance.relax_constraint(1, "manual relaxation")
>>> instance.constraints
[]
>>> instance.removed_constraints
[RemovedConstraint(x0 + x1 + x2 - 3 == 0, reason=manual relaxation)]

>>> instance.restore_constraint(1)
>>> instance.constraints
[Constraint(x0 + x1 + x2 - 3 == 0)]
>>> instance.removed_constraints
[]

Evaluate relaxed instance, and show feasible_unrelaxed.

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [DecisionVariable.binary(i) for i in range(3)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(x),
...     constraints=[
...         (x[0] + x[1] == 2).set_id(0),
...         (x[1] + x[2] == 2).set_id(1),
...     ],
...     sense=Instance.MINIMIZE,
... )

For x0=0, x1=1, x2=1
- x0 + x1 == 2 is not feasible
- x1 + x2 == 2 is feasible

>>> solution = instance.evaluate({0: 0, 1: 1, 2: 1})
>>> solution.feasible_relaxed
False
>>> solution.feasible_unrelaxed
False

Relax the constraint: x0 + x1 == 2

>>> instance.relax_constraint(0, "testing")
>>> solution = instance.evaluate({0: 0, 1: 1, 2: 1})
>>> solution.feasible_relaxed
True
>>> solution.feasible_unrelaxed
False
restore_constraint(constraint_id: int)

Restore a removed constraint to the instance.

Parameters:

constraint_id – The ID of the constraint to restore.

Note that this drops the removed reason and associated parameters. See relax_constraint() for details.

save_mps(path: str, *, compress=True)

Outputs the instance as an MPS file.

  • The outputted file is optionally compressed by gzip, depending on the value of the compress parameter (default: True).

  • Only linear problems are supported.

  • Various forms of metadata, like problem description and variable/constraint names, are not preserved.

stats() dict

Get statistics about the instance.

Returns a dictionary containing counts of decision variables and constraints categorized by kind, usage, and status.

Returns:

A dictionary with the following structure:

{
    "decision_variables": {
        "total": int,
        "by_kind": {
            "binary": int,
            "integer": int,
            "continuous": int,
            "semi_integer": int,
            "semi_continuous": int
        },
        "by_usage": {
            "used_in_objective": int,
            "used_in_constraints": int,
            "used": int,
            "fixed": int,
            "dependent": int,
            "irrelevant": int
        }
    },
    "constraints": {
        "total": int,
        "active": int,
        "removed": int
    }
}

Return type:

dict

Examples

>>> instance = Instance.empty()
>>> stats = instance.stats()
>>> stats["decision_variables"]["total"]
0
>>> stats["constraints"]["total"]
0
to_bytes() bytes
to_hubo(*, uniform_penalty_weight: float | None = None, penalty_weights: dict[int, float] = {}, inequality_integer_slack_max_range: int = 31) tuple[dict[tuple[int, Ellipsis], float], float]

Convert the instance to a HUBO format

This is a Driver API for HUBO conversion calling single-purpose methods in order:

  1. Convert the instance to a minimization problem by as_minimization_problem().

  2. Check continuous variables and raise error if exists.

  3. Convert inequality constraints

  4. Convert to HUBO with (uniform) penalty method

    • If penalty_weights is given (in dict[constraint_id, weight] form), use penalty_method() with the given weights.

    • If uniform_penalty_weight is given, use uniform_penalty_method() with the given weight.

    • If both are None, defaults to uniform_penalty_weight = 1.0.

  5. Log-encode integer variables by log_encode().

  6. Finally convert to HUBO format by as_hubo_format().

Please see the documentation for to_qubo for more information, or the documentation for each individual method for additional details. The difference between this and to_qubo is that this method isn’t restricted to quadratic or linear problems. If you want to customize the conversion, use the individual methods above manually.

Important

The above process is not stable, and subject to change for better HUBO generation in the future versions. If you wish to keep the compatibility, please use the methods above manually.

to_qubo(*, uniform_penalty_weight: float | None = None, penalty_weights: dict[int, float] = {}, inequality_integer_slack_max_range: int = 31) tuple[dict[tuple[int, int], float], float]

Convert the instance to a QUBO format

This is a Driver API for QUBO conversion calling single-purpose methods in order:

  1. Convert the instance to a minimization problem by as_minimization_problem().

  2. Check continuous variables and raise error if exists.

  3. Convert inequality constraints

  4. Convert to QUBO with (uniform) penalty method

    • If penalty_weights is given (in dict[constraint_id, weight] form), use penalty_method() with the given weights.

    • If uniform_penalty_weight is given, use uniform_penalty_method() with the given weight.

    • If both are None, defaults to uniform_penalty_weight = 1.0.

  5. Log-encode integer variables by log_encode().

  6. Finally convert to QUBO format by as_qubo_format().

Please see the document of each method for details. If you want to customize the conversion, use the methods above manually.

Important

The above process is not stable, and subject to change for better QUBO generation in the future versions. If you wish to keep the compatibility, please use the methods above manually.

Examples

Let’s consider a maximization problem with two integer variables \(x_0, x_1 \in [0, 2]\) subject to an inequality:

\[\begin{split}\begin{align*} \max_{x_0, x_1} & \space x_0 + x_1 & \\ \text{ s.t. } & \space x_0 + 2x_1 \leq 3 \end{align*}\end{split}\]
>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [DecisionVariable.integer(i, lower=0, upper=2, name = "x", subscripts=[i]) for i in range(2)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(x),
...     constraints=[(x[0] + 2*x[1] <= 3).set_id(0)],
...     sense=Instance.MAXIMIZE,
... )

Convert into QUBO format

>>> qubo, offset = instance.to_qubo()
>>> qubo
{(3, 3): -6.0, (3, 4): 2.0, (3, 5): 4.0, (3, 6): 4.0, (3, 7): 2.0, (3, 8): 4.0, (4, 4): -6.0, (4, 5): 4.0, (4, 6): 4.0, (4, 7): 2.0, (4, 8): 4.0, (5, 5): -9.0, (5, 6): 8.0, (5, 7): 4.0, (5, 8): 8.0, (6, 6): -9.0, (6, 7): 4.0, (6, 8): 8.0, (7, 7): -5.0, (7, 8): 4.0, (8, 8): -8.0}
>>> offset
9.0

The instance object stores how converted:

  • For the maximization problem, the sense is converted to minimization for generating QUBO, and then converted back to maximization.

>>> instance.sense == Instance.MAXIMIZE
True
>>> instance.decision_variables_df.dropna(axis=1, how="all")
       kind  lower  upper             name subscripts
id
0   Integer    0.0    2.0                x        [0]
1   Integer    0.0    2.0                x        [1]
2   Integer    0.0    3.0       ommx.slack        [0]
3    Binary    0.0    1.0  ommx.log_encode     [0, 0]
4    Binary    0.0    1.0  ommx.log_encode     [0, 1]
5    Binary    0.0    1.0  ommx.log_encode     [1, 0]
6    Binary    0.0    1.0  ommx.log_encode     [1, 1]
7    Binary    0.0    1.0  ommx.log_encode     [2, 0]
8    Binary    0.0    1.0  ommx.log_encode     [2, 1]
>>> instance.objective
Function(-x3*x3 - 2*x3*x4 - 4*x3*x5 - 4*x3*x6 - 2*x3*x7 - 4*x3*x8 - x4*x4 - 4*x4*x5 - 4*x4*x6 - 2*x4*x7 - 4*x4*x8 - 4*x5*x5 - 8*x5*x6 - 4*x5*x7 - 8*x5*x8 - 4*x6*x6 - 4*x6*x7 - 8*x6*x8 - x7*x7 - 4*x7*x8 - 4*x8*x8 + 7*x3 + 7*x4 + 13*x5 + 13*x6 + 6*x7 + 12*x8 - 9)
>>> instance.get_removed_constraint_by_id(0)
RemovedConstraint(x0 + 2*x1 + x2 - 3 == 0, reason=uniform_penalty_method)

Solvers will return solutions which only contain log-encoded binary variables like:

>>> state = {
...     3: 1, 4: 1,  # x0 = 0 + (2-1)*1 = 2
...     5: 0, 6: 0,  # x1 = 0 + (2-1)*0 = 0
...     7: 1, 8: 0   # x3 = 1 + 2*0 = 1
... }

This can be evaluated by evaluate() method.

>>> solution = instance.evaluate(state)

The log-encoded integer variables are automatically evaluated from the binary variables.

>>> solution.decision_variables_df.dropna(axis=1, how="all")
       kind  lower  upper             name subscripts  value
id
0   Integer    0.0    2.0                x        [0]    2.0
1   Integer    0.0    2.0                x        [1]    0.0
2   Integer    0.0    3.0       ommx.slack        [0]    1.0
3    Binary    0.0    1.0  ommx.log_encode     [0, 0]    1.0
4    Binary    0.0    1.0  ommx.log_encode     [0, 1]    1.0
5    Binary    0.0    1.0  ommx.log_encode     [1, 0]    0.0
6    Binary    0.0    1.0  ommx.log_encode     [1, 1]    0.0
7    Binary    0.0    1.0  ommx.log_encode     [2, 0]    1.0
8    Binary    0.0    1.0  ommx.log_encode     [2, 1]    0.0
>>> solution.objective
2.0
>>> solution.constraints_df.dropna(axis=1, how="all")
   equality  value   used_ids subscripts          removed_reason
id
0        =0    0.0  {0, 1, 2}         []  uniform_penalty_method
uniform_penalty_method() ParametricInstance

Convert to a parametric unconstrained instance by penalty method with uniform weight.

Roughly, this converts a constrained problem

\[\begin{split}\begin{align*} \min_x & \space f(x) & \\ \text{ s.t. } & \space g_i(x) = 0 & (\forall i) \\ & \space h_j(x) \leq 0 & (\forall j) \end{align*}\end{split}\]

to an unconstrained problem with a parameter

\[\min_x f(x) + \lambda \left( \sum_i g_i(x)^2 + \sum_j h_j(x)^2 \right)\]

where \(\lambda\) is the uniform penalty weight parameter for all constraints.

The removed constrains are stored in removed_constraints.

Note

Note that this method converts inequality constraints \(h(x) \leq 0\) to \(|h(x)|^2\) not to \(\max(0, h(x))^2\). This means the penalty is enforced even for \(h(x) < 0\) cases, and \(h(x) = 0\) is unfairly favored.

This feature is intended to use with add_integer_slack_to_inequality().

Examples

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [DecisionVariable.binary(i) for i in range(3)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(x),
...     constraints=[sum(x) == 3],
...     sense=Instance.MAXIMIZE,
... )
>>> instance.objective
Function(x0 + x1 + x2)
>>> pi = instance.uniform_penalty_method()

The constraint is put in removed_constraints

>>> pi.constraints
[]
>>> len(pi.removed_constraints)
1
>>> pi.removed_constraints[0]
RemovedConstraint(x0 + x1 + x2 - 3 == 0, reason=uniform_penalty_method)

There is only one parameter in the instance

>>> len(pi.parameters)
1
>>> p = pi.parameters[0]
>>> p.id
3
>>> p.name
'uniform_penalty_weight'

Substitute p = 0 to get the original objective

>>> instance0 = pi.with_parameters({p.id: 0.0})
>>> instance0.objective
Function(x0 + x1 + x2)

Substitute p = 1

>>> instance1 = pi.with_parameters({p.id: 1.0})
>>> instance1.objective
Function(x0*x0 + 2*x0*x1 + 2*x0*x2 + x1*x1 + 2*x1*x2 + x2*x2 - 5*x0 - 5*x1 - 5*x2 + 9)
used_decision_variable_ids() set[int]

Get the set of decision variable IDs used in the objective and remaining constraints.

Examples

>>> x = [DecisionVariable.binary(i) for i in range(3)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(x),
...     constraints=[],
...     sense=Instance.MAXIMIZE,
... )
>>> instance.used_decision_variable_ids()
{0, 1, 2}
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=x[0],
...     constraints=[(x[1] == 1).set_id(0)],
...     sense=Instance.MAXIMIZE,
... )
>>> instance.used_decision_variable_ids()
{0, 1}
>>> instance.relax_constraint(0, "testing")
>>> instance.used_decision_variable_ids()
{0}
write_mps(path: str)
Description
MAXIMIZE
MINIMIZE
annotation_namespace = 'org.ommx.v1.instance'
annotations: dict[str, str]

Arbitrary annotations stored in OMMX artifact. Use title or other specific attributes if possible.

authors

Authors of this instance, stored as org.ommx.v1.instance.authors annotation in OMMX artifact.

property constraint_hints: ConstraintHints

Get constraint hints that provide additional information to solvers.

property constraints: list[Constraint]

Get constraints as a list of Constraint instances sorted by their IDs.

property constraints_df: pandas.DataFrame
created

The creation date of the instance, stored as org.ommx.v1.instance.created annotation in RFC3339 format in OMMX artifact.

dataset

Dataset name which this instance belongs to, stored as org.ommx.v1.instance.dataset annotation in OMMX artifact.

property decision_variable_names: set[str]

Get all unique decision variable names in this instance.

Returns a set of all unique variable names. Variables without names are not included.

Examples

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [DecisionVariable.binary(i, name="x", subscripts=[i]) for i in range(3)]
>>> y = [DecisionVariable.binary(i+3, name="y", subscripts=[i]) for i in range(2)]
>>> instance = Instance.from_components(
...     decision_variables=x + y,
...     objective=sum(x) + sum(y),
...     constraints=[],
...     sense=Instance.MAXIMIZE,
... )
>>> sorted(instance.decision_variable_names)
['x', 'y']
property decision_variables: list[DecisionVariable]

Get decision variables as a list of DecisionVariable instances sorted by their IDs.

property decision_variables_df: pandas.DataFrame
property description: Instance
license

License of this instance in the SPDX license identifier. This is stored as org.ommx.v1.instance.license annotation in OMMX artifact.

property named_function_names: set[str]

Get all unique named function names in this instance.

property named_functions: list[NamedFunction]

Get named functions as a list of NamedFunction instances sorted by their IDs.

property named_functions_df: pandas.DataFrame
num_constraints

Number of constraints in this instance, stored as org.ommx.v1.instance.constraints annotation in OMMX artifact.

num_variables

Number of variables in this instance, stored as org.ommx.v1.instance.variables annotation in OMMX artifact.

property objective: Function
raw: ommx._ommx_rust.Instance

The raw Rust instance.

property removed_constraints: list[RemovedConstraint]

Get removed constraints as a list of RemovedConstraint instances.

property removed_constraints_df: pandas.DataFrame
property sense: ommx._ommx_rust.Sense
title

The title of the instance, stored as org.ommx.v1.instance.title annotation in OMMX artifact.

property used_decision_variables: list[DecisionVariable]

Get a list of only the decision variables used in the objective and remaining constraints.

Returns a list of DecisionVariable instancess sorted by their IDs.

Decision variables defined in the instance but not actually present in the objective function and constraints are excluded from the list.

class ommx.v1.Linear(*, terms: dict[int, float | int], constant: float | int = 0)

Modeler API for linear function

This is a wrapper of linear_pb2.Linear protobuf message.

Examples

Create a linear function :math:`f(x_1, x_2) = 2 x_1 + 3 x_2 + 1`
>>> f = Linear(terms={1: 2, 2: 3}, constant=1)

Or create via DecisionVariable
>>> x1 = DecisionVariable.integer(1)
>>> x2 = DecisionVariable.integer(2)
>>> g = 2*x1 + 3*x2 + 1

Compare two linear functions are equal in terms of a polynomial with tolerance
>>> assert f.almost_equal(g, atol=1e-12)

Note that `f == g` becomes an equality `Constraint`
>>> assert isinstance(f == g, Constraint)
almost_equal(other: Linear, *, atol: float = 1e-10) bool

Compare two linear functions have almost equal coefficients and constant.

equals_to(other: Linear) bool

Alternative to == operator to compare two linear functions.

evaluate(state: ToState, *, atol: float | None = None) float

Evaluate the linear function with the given state.

Examples

Evaluate `2 x1 + 3 x2 + 1` with `x1 = 3, x2 = 4, x3 = 5`

>>> f = Linear(terms={1: 2, 2: 3}, constant=1)
>>> value = f.evaluate({1: 3, 2: 4, 3: 5}) # Unused ID `3` can be included

2*3 + 3*4 + 1 = 19
>>> value
19.0

Missing ID raises an error
>>> f.evaluate({1: 3})
Traceback (most recent call last):
...
RuntimeError: Missing entry for id: 2
static from_bytes(data: bytes) Linear
classmethod from_object(obj: float | int | DecisionVariable | ommx._ommx_rust.Linear | Linear) Linear
classmethod from_raw(obj: ommx._ommx_rust.Linear) Linear
partial_evaluate(state: ToState, *, atol: float | None = None) Linear

Partially evaluate the linear function with the given state.

Examples

Evaluate `2 x1 + 3 x2 + 1` with `x1 = 3`, yielding `3 x2 + 7`

>>> f = Linear(terms={1: 2, 2: 3}, constant=1)
>>> new_f = f.partial_evaluate({1: 3})
>>> new_f
Linear(3*x2 + 7)
>>> new_f.partial_evaluate({2: 4})
Linear(19)
static random(rng: ommx._ommx_rust.Rng, num_terms: int = 3, max_id: int = 10) Linear

Create a random linear function using the given random number generator.

Parameters:
  • rng – Random number generator

  • num_terms – Number of terms in the linear function

  • max_id – Maximum variable ID to use

Returns:

Random Linear function

to_bytes() bytes
property constant_term: float

Get the constant term of the linear function

property linear_terms: dict[int, float]

Get the terms of the linear function as a dictionary, except for the constant term.

raw: ommx._ommx_rust.Linear
property terms: dict[tuple[int, Ellipsis], float]

Linear terms and constant as a dictionary

class ommx.v1.NamedFunction(*, id: int, function: int | float | DecisionVariable | Linear | Quadratic | Polynomial | Function | ommx._ommx_rust.Function, name: str | None = None, subscripts: list[int] = [], description: str | None = None, parameters: dict[str, str] = {})

A named function attached to an optimization instance.

Named functions are auxiliary functions (not constraints or objectives) that are tracked alongside the optimization problem. They can be evaluated, partially evaluated, and used in arithmetic operations.

Examples

>>> from ommx.v1 import NamedFunction, DecisionVariable, Linear
>>> x = DecisionVariable.integer(1)
>>> nf = NamedFunction(id=0, function=2*x + 1, name="obj2", subscripts=[0])
>>> nf.name
'obj2'
>>> nf.id
0
evaluate(state: ToState, *, atol: float | None = None) EvaluatedNamedFunction

Evaluate this named function with the given state.

static from_bytes(data: bytes) NamedFunction
static from_raw(raw: ommx._ommx_rust.NamedFunction) NamedFunction
partial_evaluate(state: ToState, *, atol: float | None = None) NamedFunction

Partially evaluate this named function with the given state.

to_bytes() bytes
property description: str | None
property function: Function
property id: int
property name: str | None
property parameters: dict[str, str]
raw: ommx._ommx_rust.NamedFunction
property subscripts: list[int]
class ommx.v1.Parameter

Idiomatic wrapper of ommx.v1.Parameter protobuf message.

equals_to(other: Parameter) bool

Alternative to == operator to compare two decision variables.

static from_bytes(data: bytes) Parameter
static new(id: int, *, name: str | None = None, subscripts: Iterable[int] = [], description: str | None = None)
to_bytes() bytes
property description: str
property id: int
property name: str
property parameters: dict[str, str]
raw: parametric_instance_pb2.Parameter
property subscripts: list[int]
class ommx.v1.ParametricInstance

Idiomatic wrapper of ommx.v1.ParametricInstance protobuf message.

Examples

Create an instance for KnapSack Problem with parameters

>>> from ommx.v1 import ParametricInstance, DecisionVariable, Parameter

Decision variables

>>> x = [DecisionVariable.binary(i, name="x", subscripts=[i]) for i in range(6)]

Profit and weight of items as parameters

>>> p = [Parameter.new(id=i+6, name="Profit", subscripts=[i]) for i in range(6)]
>>> w = [Parameter.new(id=i+12, name="Weight", subscripts=[i]) for i in range(6)]
>>> W = Parameter.new(id=18, name="Capacity")

Objective and constraint

>>> objective = sum(p[i] * x[i] for i in range(6))
>>> constraint = sum(w[i] * x[i] for i in range(6)) <= W

Compose as an instance

>>> parametric_instance = ParametricInstance.from_components(
...     decision_variables=x,
...     parameters=p + w + [W],
...     objective=objective,
...     constraints=[constraint],
...     sense=Instance.MAXIMIZE,
... )

Substitute parameters to get an instance

>>> p_values = { x.id: value for x, value in zip(p, [10, 13, 18, 31, 7, 15]) }
>>> w_values = { x.id: value for x, value in zip(w, [11, 15, 20, 35, 10, 33]) }
>>> W_value = { W.id: 47 }
>>> instance = parametric_instance.with_parameters({**p_values, **w_values, **W_value})
static empty() ParametricInstance

Create trivial empty instance of minimization with zero objective, no constraints, and no decision variables and parameters.

static from_bytes(data: bytes) ParametricInstance
static from_components(*, objective: int | float | DecisionVariable | Linear | Quadratic | Polynomial | Function, constraints: Iterable[Constraint | constraint_pb2.Constraint], sense: instance_pb2.Instance.Sense.ValueType | Sense, decision_variables: Iterable[DecisionVariable | decision_variables_pb2.DecisionVariable], parameters: Iterable[Parameter | parametric_instance_pb2.Parameter], description: instance_pb2.Instance.Description | None = None) ParametricInstance
get_constraint_by_id(constraint_id: int) Constraint

Get a constraint by ID.

get_decision_variable_by_id(variable_id: int) DecisionVariable

Get a decision variable by ID.

get_parameter_by_id(parameter_id: int) Parameter

Get a parameter by ID.

get_removed_constraint_by_id(removed_constraint_id: int) RemovedConstraint

Get a removed constraint by ID.

to_bytes() bytes
with_parameters(parameters: instance_pb2.Parameters | Mapping[int, float]) Instance

Substitute parameters to yield an instance.

annotation_namespace = 'org.ommx.v1.parametric-instance'
annotations: dict[str, str]
authors

Authors of this instance, stored as org.ommx.v1.parametric-instance.authors annotation in OMMX artifact.

property constraints: list[Constraint]

`Constraint

Type:

Get constraints as a list of

Type:

class

property constraints_df: pandas.DataFrame
created

The creation date of the instance, stored as org.ommx.v1.parametric-instance.created annotation in RFC3339 format in OMMX artifact.

dataset

Dataset name which this instance belongs to, stored as org.ommx.v1.parametric-instance.dataset annotation in OMMX artifact.

property decision_variables: list[DecisionVariable]

Get decision variables as a list of DecisionVariable instances.

property decision_variables_df: pandas.DataFrame
license

License of this instance in the SPDX license identifier. This is stored as org.ommx.v1.parametric-instance.license annotation in OMMX artifact.

property named_functions: list[NamedFunction]

Get named functions as a list of NamedFunction instances.

property named_functions_df: pandas.DataFrame
num_constraints

Number of constraints in this instance, stored as org.ommx.v1.parametric-instance.constraints annotation in OMMX artifact.

num_variables

Number of variables in this instance, stored as org.ommx.v1.parametric-instance.variables annotation in OMMX artifact.

property parameters: list[Parameter]

Get parameters as a list of Parameter.

property parameters_df: pandas.DataFrame
raw: parametric_instance_pb2.ParametricInstance
property removed_constraints: list[RemovedConstraint]

Get removed constraints as a list of RemovedConstraint instances.

property removed_constraints_df: pandas.DataFrame
title

The title of the instance, stored as org.ommx.v1.parametric-instance.title annotation in OMMX artifact.

class ommx.v1.Polynomial(*, terms: dict[Sequence[int], float | int] = {})

Helper class that provides a standard way to create an ABC using inheritance.

almost_equal(other: Polynomial, *, atol: float = 1e-10) bool

Compare two polynomial have almost equal coefficients

evaluate(state: ToState, *, atol: float | None = None) float

Evaluate the polynomial with the given state.

Examples

Evaluate `2 x1 x2 x3 + 3 x2 x3 + 1` with `x1 = 3, x2 = 4, x3 = 5`

>>> x1 = DecisionVariable.integer(1)
>>> x2 = DecisionVariable.integer(2)
>>> x3 = DecisionVariable.integer(3)
>>> f = 2*x1*x2*x3 + 3*x2*x3 + 1
>>> f
Polynomial(2*x1*x2*x3 + 3*x2*x3 + 1)

>>> f.evaluate({1: 3, 2: 4, 3: 5})
181.0

Missing ID raises an error
>>> f.evaluate({1: 3})
Traceback (most recent call last):
...
RuntimeError: Missing entry for id: 2
static from_bytes(data: bytes) Polynomial
static from_raw(raw: ommx._ommx_rust.Polynomial) Polynomial
partial_evaluate(state: ToState, *, atol: float | None = None) Polynomial

Partially evaluate the polynomial with the given state.

Examples

Evaluate `2 x1 x2 x3 + 3 x2 x3 + 1` with `x1 = 3`, yielding `9 x2 x3 + 1`

>>> x1 = DecisionVariable.integer(1)
>>> x2 = DecisionVariable.integer(2)
>>> x3 = DecisionVariable.integer(3)
>>> f = 2*x1*x2*x3 + 3*x2*x3 + 1
>>> f
Polynomial(2*x1*x2*x3 + 3*x2*x3 + 1)

>>> f.partial_evaluate({1: 3})
Polynomial(9*x2*x3 + 1)
static random(rng: ommx._ommx_rust.Rng, num_terms: int = 5, max_degree: int = 3, max_id: int = 10) Polynomial

Create a random polynomial function using the given random number generator.

Parameters:
  • rng – Random number generator

  • num_terms – Number of terms in the polynomial function

  • max_degree – Maximum degree of terms

  • max_id – Maximum variable ID to use

Returns:

Random Polynomial function

to_bytes() bytes
raw: ommx._ommx_rust.Polynomial
property terms: dict[tuple[int, Ellipsis], float]
class ommx.v1.Quadratic(*, columns: Iterable[int], rows: Iterable[int], values: Iterable[float | int], linear: Linear | None = None)

Helper class that provides a standard way to create an ABC using inheritance.

almost_equal(other: Quadratic, *, atol: float = 1e-10) bool

Compare two quadratic functions have almost equal coefficients

evaluate(state: ToState, *, atol: float | None = None) float

Evaluate the quadratic function with the given state.

Examples

Evaluate `2 x1 x2 + 3 x2 x3 + 1` with `x1 = 3, x2 = 4, x3 = 5`

>>> x1 = DecisionVariable.integer(1)
>>> x2 = DecisionVariable.integer(2)
>>> x3 = DecisionVariable.integer(3)
>>> f = 2*x1*x2 + 3*x2*x3 + 1
>>> f
Quadratic(2*x1*x2 + 3*x2*x3 + 1)

>>> f.evaluate({1: 3, 2: 4, 3: 5})
85.0

Missing ID raises an error
>>> f.evaluate({1: 3})
Traceback (most recent call last):
...
RuntimeError: Missing entry for id: 2
static from_bytes(data: bytes) Quadratic
static from_raw(raw: ommx._ommx_rust.Quadratic) Quadratic
partial_evaluate(state: ToState, *, atol: float | None = None) Quadratic

Partially evaluate the quadratic function with the given state.

Examples

Evaluate `2 x1 x2 + 3 x2 x3 + 1` with `x1 = 3`, yielding `3 x2 x3 + 6 x2 + 1`

>>> x1 = DecisionVariable.integer(1)
>>> x2 = DecisionVariable.integer(2)
>>> x3 = DecisionVariable.integer(3)
>>> f = 2*x1*x2 + 3*x2*x3 + 1
>>> f
Quadratic(2*x1*x2 + 3*x2*x3 + 1)

>>> f.partial_evaluate({1: 3})
Quadratic(3*x2*x3 + 6*x2 + 1)
static random(rng: ommx._ommx_rust.Rng, num_terms: int = 5, max_id: int = 10) Quadratic

Create a random quadratic function using the given random number generator.

Parameters:
  • rng – Random number generator

  • num_terms – Number of terms in the quadratic function

  • max_id – Maximum variable ID to use

Returns:

Random Quadratic function

to_bytes() bytes
property constant_term: float

Constant term of the quadratic function

property linear: Linear | None
property linear_terms: dict[int, float]

Linear terms as a dictionary mapping variable id to coefficient

property quadratic_terms: dict[tuple[int, int], float]

Quadratic terms as a dictionary mapping (row, col) to coefficient

raw: ommx._ommx_rust.Quadratic
property terms: dict[tuple[int, Ellipsis], float]

All terms as a dictionary mapping variable id tuples to coefficients

Returns dictionary with tuple keys (hashable) instead of list keys.

Examples

>>> from ommx.v1 import DecisionVariable
>>> x = DecisionVariable.binary(1, name="x")
>>> y = DecisionVariable.binary(2, name="y")
>>> quad = x * y + 2 * x + 3
>>> quad.terms
{(1,): 2.0, (1, 2): 1.0, (): 3.0}
class ommx.v1.SampleSet

The output of sampling-based optimization algorithms, e.g. simulated annealing (SA).

  • Similar to Solution rather than solution_pb2.State. This class contains the sampled values of decision variables with the objective value, constraint violations, feasibility, and metadata of constraints and decision variables.

  • This class is usually created via Instance.evaluate_samples().

Examples

Let’s consider a simple optimization problem:

\[\begin{split}\begin{align*} \max &\quad x_1 + 2 x_2 + 3 x_3 \\ \text{s.t.} &\quad x_1 + x_2 + x_3 = 1 \\ &\quad x_1, x_2, x_3 \in \{0, 1\} \end{align*}\end{split}\]
>>> x = [DecisionVariable.binary(i) for i in range(3)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=x[0] + 2*x[1] + 3*x[2],
...     constraints=[sum(x) == 1],
...     sense=Instance.MAXIMIZE,
... )

with three samples:

>>> samples = {
...     0: {0: 1, 1: 0, 2: 0},  # x1 = 1, x2 = x3 = 0
...     1: {0: 0, 1: 0, 2: 1},  # x3 = 1, x1 = x2 = 0
...     2: {0: 1, 1: 1, 2: 0},  # x1 = x2 = 1, x3 = 0 (infeasible)
... } # ^ sample ID

Note that this will be done by sampling-based solvers, but we do it manually here. We can evaluate the samples with via Instance.evaluate_samples():

>>> sample_set = instance.evaluate_samples(samples)
>>> sample_set.summary
           objective  feasible
sample_id
1                3.0      True
0                1.0      True
2                3.0     False

The summary attribute shows the objective value, feasibility of each sample. Note that this feasible column represents the feasibility of the original constraints, not the relaxed constraints. You can get each samples by get() as a Solution format:

>>> solution = sample_set.get(sample_id=0)
>>> solution.objective
1.0
>>> solution.decision_variables_df
      kind  lower  upper  name subscripts description substituted_value  value
id
0   Binary    0.0    1.0  <NA>         []        <NA>              <NA>    1.0
1   Binary    0.0    1.0  <NA>         []        <NA>              <NA>    0.0
2   Binary    0.0    1.0  <NA>         []        <NA>              <NA>    0.0

best_feasible() returns the best feasible sample, i.e. the largest objective value among feasible samples:

>>> solution = sample_set.best_feasible
>>> solution.objective
3.0
>>> solution.decision_variables_df
      kind  lower  upper  name subscripts description substituted_value  value
id
0   Binary    0.0    1.0  <NA>         []        <NA>              <NA>    0.0
1   Binary    0.0    1.0  <NA>         []        <NA>              <NA>    0.0
2   Binary    0.0    1.0  <NA>         []        <NA>              <NA>    1.0

Of course, the sample of smallest objective value is returned for minimization problems.

extract_all_decision_variables(sample_id: int) dict[str, dict[tuple[int, Ellipsis], float]]

Extract all decision variables grouped by name for a given sample ID.

Returns a mapping from variable name to a mapping from subscripts to values. This is useful for extracting all variables at once in a structured format. Variables without names are not included in the result.

Raises:

ValueError – If a decision variable with parameters is found, or if the same name and subscript combination is found multiple times, or if the sample ID is invalid.

Examples

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [DecisionVariable.binary(i, name="x", subscripts=[i]) for i in range(3)]
>>> y = [DecisionVariable.binary(i+3, name="y", subscripts=[i]) for i in range(2)]
>>> instance = Instance.from_components(
...     decision_variables=x + y,
...     objective=sum(x) + sum(y),
...     constraints=[],
...     sense=Instance.MAXIMIZE,
... )
>>> sample_set = instance.evaluate_samples({0: {i: 1 for i in range(5)}})
>>> all_vars = sample_set.extract_all_decision_variables(0)
>>> all_vars["x"]
{(0,): 1.0, (1,): 1.0, (2,): 1.0}
>>> all_vars["y"]
{(0,): 1.0, (1,): 1.0}
extract_all_named_functions(sample_id: int) dict[str, dict[tuple[int, Ellipsis], float]]

Extract all named function values grouped by name for a given sample ID.

extract_constraints(name: str, sample_id: int) dict[tuple[int, Ellipsis], float]

Extract evaluated constraint violations for a given constraint name and sample ID.

extract_decision_variables(name: str, sample_id: int) dict[tuple[int, Ellipsis], float]

Extract sampled decision variable values for a given name and sample ID.

extract_named_functions(name: str, sample_id: int) dict[tuple[int, Ellipsis], float]

Extract named function values for a given name and sample ID.

static from_bytes(data: bytes) SampleSet
get(sample_id: int) Solution

Get a sample for a given ID as a solution format

get_constraint_by_id(constraint_id: int) SampledConstraint

Get a specific sampled constraint by ID.

get_decision_variable_by_id(variable_id: int) SampledDecisionVariable

Get a specific sampled decision variable by ID.

get_named_function_by_id(named_function_id: int) SampledNamedFunction

Get a specific sampled named function by ID.

get_sample_by_id(sample_id: int) Solution

Get sample by ID (alias for get method)

to_bytes() bytes
annotation_namespace = 'org.ommx.v1.sample-set'
annotations: dict[str, str]

Arbitrary annotations stored in OMMX artifact. Use parameters or other specific attributes if possible.

property best_feasible: Solution

Get the best feasible solution.

Raises:

RuntimeError – If no feasible solution exists.

property best_feasible_id: int

Get the sample ID of the best feasible solution.

Raises:

RuntimeError – If no feasible solution exists.

property best_feasible_relaxed: Solution

Get the best feasible solution without relaxation.

Raises:

RuntimeError – If no feasible solution exists.

property best_feasible_relaxed_id: int

Get the sample ID of the best feasible solution without relaxation.

Raises:

RuntimeError – If no feasible solution exists.

property best_feasible_unrelaxed: Solution

Get the best feasible solution without relaxation.

Raises:

RuntimeError – If no feasible solution exists.

property best_feasible_unrelaxed_id: int

Get the sample ID of the best feasible solution without relaxation.

Raises:

RuntimeError – If no feasible solution exists.

property constraints: list[SampledConstraint]

Get sampled constraints as a list sorted by ID.

property constraints_df: pandas.DataFrame
property decision_variable_names: set[str]

Get all unique decision variable names in this sample set.

Returns a set of all unique variable names. Variables without names are not included.

Examples

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [DecisionVariable.binary(i, name="x", subscripts=[i]) for i in range(3)]
>>> y = [DecisionVariable.binary(i+3, name="y", subscripts=[i]) for i in range(2)]
>>> instance = Instance.from_components(
...     decision_variables=x + y,
...     objective=sum(x) + sum(y),
...     constraints=[],
...     sense=Instance.MAXIMIZE,
... )
>>> sample_set = instance.evaluate_samples({0: {i: 1 for i in range(5)}})
>>> sorted(sample_set.decision_variable_names)
['x', 'y']
property decision_variables: list[SampledDecisionVariable]

Get sampled decision variables as a list sorted by ID.

property decision_variables_df: pandas.DataFrame
end

When the optimization ended, stored as org.ommx.v1.sample-set.end annotation in RFC3339 format in OMMX artifact.

property feasible: dict[int, bool]

Feasibility in terms of the original constraints, an alias to feasible_unrelaxed.

Compatibility

The meaning of this property has changed from Python SDK 1.7.0. Previously, this property represents the feasibility of the remaining constraints only, i.e. excluding relaxed constraints. From Python SDK 1.7.0, this property represents the feasibility of all constraints, including relaxed constraints.

property feasible_relaxed: dict[int, bool]

Feasibility in terms of the remaining (non-removed) constraints.

For each sample_id, this property shows whether the sample is feasible for the all Instance.constraints

property feasible_unrelaxed: dict[int, bool]

Feasibility in terms of the original constraints without relaxation.

For each sample_id, this property shows whether the sample is feasible both for the all Instance.constraints and all Instance.removed_constraints.

instance

The digest of the instance layer, stored as org.ommx.v1.sample-set.instance annotation in OMMX artifact.

property named_function_names: set[str]

Get all unique named function names in this sample set.

property named_functions: list[SampledNamedFunction]

Get sampled named functions as a list sorted by ID.

property named_functions_df: pandas.DataFrame
property objectives: dict[int, float]
parameters

The parameters used in the optimization, stored as org.ommx.v1.sample-set.parameters annotation as a JSON in OMMX artifact.

raw: ommx._ommx_rust.SampleSet
property sample_ids: list[int]
property sense: ommx._ommx_rust.Sense
solver

The solver which generated this sample set, stored as org.ommx.v1.sample-set.solver annotation as a JSON in OMMX artifact.

start

When the optimization started, stored as org.ommx.v1.sample-set.start annotation in RFC3339 format in OMMX artifact.

property summary: pandas.DataFrame
property summary_with_constraints: pandas.DataFrame
class ommx.v1.Solution

Idiomatic wrapper of ommx.v1.Solution protobuf message.

This also contains annotations not contained in protobuf message, and will be stored in OMMX artifact.

extract_all_decision_variables() dict[str, dict[tuple[int, Ellipsis], float]]

Extract all decision variables grouped by name.

Returns a mapping from variable name to a mapping from subscripts to values. This is useful for extracting all variables at once in a structured format. Variables without names are not included in the result.

Raises:

ValueError – If a decision variable with parameters is found, or if the same name and subscript combination is found multiple times.

Examples

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [DecisionVariable.binary(i, name="x", subscripts=[i]) for i in range(3)]
>>> y = [DecisionVariable.binary(i+3, name="y", subscripts=[i]) for i in range(2)]
>>> instance = Instance.from_components(
...     decision_variables=x + y,
...     objective=sum(x) + sum(y),
...     constraints=[],
...     sense=Instance.MAXIMIZE,
... )
>>> solution = instance.evaluate({i: 1 for i in range(5)})
>>> all_vars = solution.extract_all_decision_variables()
>>> all_vars["x"]
{(0,): 1.0, (1,): 1.0, (2,): 1.0}
>>> all_vars["y"]
{(0,): 1.0, (1,): 1.0}
extract_all_named_functions() dict[str, dict[tuple[int, Ellipsis], float]]

Extract all named functions grouped by name.

extract_constraints(name: str) dict[tuple[int, Ellipsis], float]

Extract the values of constraints based on the name with subscripts key.

Raises:

ValueError – If the constraint with parameters is found, or if the same subscript is found.

Examples

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [DecisionVariable.binary(i) for i in range(3)]
>>> c0 = (x[0] + x[1] == 1).add_name("c").add_subscripts([0])
>>> c1 = (x[1] + x[2] == 1).add_name("c").add_subscripts([1])
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(x),
...     constraints=[c0, c1],
...     sense=Instance.MAXIMIZE,
... )
>>> solution = instance.evaluate({0: 1, 1: 0, 2: 1})
>>> solution.extract_constraints("c")
{(0,): 0.0, (1,): 0.0}
extract_decision_variables(name: str) dict[tuple[int, Ellipsis], float]

Extract the values of decision variables based on the name with subscripts key.

Raises:

ValueError – If the decision variable with parameters is found, or if the same subscript is found.

Examples

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [DecisionVariable.binary(i, name="x", subscripts=[i]) for i in range(3)]
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(x),
...     constraints=[sum(x) == 1],
...     sense=Instance.MAXIMIZE,
... )
>>> solution = instance.evaluate({i: 1 for i in range(3)})
>>> solution.extract_decision_variables("x")
{(0,): 1.0, (1,): 1.0, (2,): 1.0}
extract_named_functions(name: str) dict[tuple[int, Ellipsis], float]

Extract named function values by name with subscripts as key.

static from_bytes(data: bytes) Solution
get_constraint_by_id(constraint_id: int) EvaluatedConstraint

Get a specific evaluated constraint by ID.

get_constraint_value(constraint_id: int) float

Get the evaluated value of a specific constraint.

get_decision_variable_by_id(variable_id: int) EvaluatedDecisionVariable

Get a specific evaluated decision variable by ID.

get_dual_variable(constraint_id: int) float | None

Get the dual variable value for a specific constraint.

get_named_function_by_id(named_function_id: int) EvaluatedNamedFunction

Get a specific evaluated named function by ID.

set_dual_variable(constraint_id: int, value: float | None) None

Set the dual variable value for a specific constraint.

to_bytes() bytes
total_violation_l1() float

Calculate total constraint violation using L1 norm (sum of absolute violations).

Returns the sum of violations across all constraints (including removed constraints):

  • For equality constraints: Σ|f(x)|

  • For inequality constraints: Σmax(0, f(x))

Returns:

The total L1 norm violation value.

Return type:

float

total_violation_l2() float

Calculate total constraint violation using L2 norm squared (sum of squared violations).

Returns the sum of squared violations across all constraints (including removed constraints):

  • For equality constraints: Σ(f(x))²

  • For inequality constraints: Σ(max(0, f(x)))²

Returns:

The total L2 norm squared violation value.

Return type:

float

LP_RELAXED
NOT_OPTIMAL
OPTIMAL
annotation_namespace = 'org.ommx.v1.solution'
annotations: dict[str, str]

Arbitrary annotations stored in OMMX artifact. Use parameters or other specific attributes if possible.

property constraint_ids: set[int]

Get the IDs of constraints in this solution.

property constraints: list[EvaluatedConstraint]

Get evaluated constraints as a list sorted by ID.

property constraints_df: pandas.DataFrame
property decision_variable_ids: set[int]

Get the IDs of decision variables in this solution.

property decision_variable_names: set[str]

Get all unique decision variable names in this solution.

Returns a set of all unique variable names. Variables without names are not included.

Examples

>>> from ommx.v1 import Instance, DecisionVariable
>>> x = [DecisionVariable.binary(i, name="x", subscripts=[i]) for i in range(3)]
>>> y = [DecisionVariable.binary(i+3, name="y", subscripts=[i]) for i in range(2)]
>>> instance = Instance.from_components(
...     decision_variables=x + y,
...     objective=sum(x) + sum(y),
...     constraints=[],
...     sense=Instance.MAXIMIZE,
... )
>>> solution = instance.evaluate({i: 1 for i in range(5)})
>>> sorted(solution.decision_variable_names)
['x', 'y']
property decision_variables: list[EvaluatedDecisionVariable]

Get evaluated decision variables as a list sorted by ID.

property decision_variables_df: pandas.DataFrame
end

When the optimization ended, stored as org.ommx.v1.solution.end annotation in RFC3339 format in OMMX artifact.

property feasible: bool

Feasibility of the solution in terms of all constraints, including removed_constraints.

This is an alias for feasible_unrelaxed.

Compatibility

The meaning of this property has changed from Python SDK 1.7.0. Previously, this property represents the feasibility of the remaining constraints only, i.e. excluding relaxed constraints. From Python SDK 1.7.0, this property represents the feasibility of all constraints, including relaxed constraints.

property feasible_relaxed: bool

Feasibility of the solution in terms of remaining constraints, not including relaxed (removed) constraints.

property feasible_unrelaxed: bool

Feasibility of the solution in terms of all constraints, including relaxed (removed) constraints.

instance

The digest of the instance layer, stored as org.ommx.v1.solution.instance annotation in OMMX artifact.

This Solution is the solution of the mathematical programming problem described by the instance.

property named_function_ids: set[int]

Get all named function IDs in this solution.

property named_function_names: set[str]

Get all unique named function names in this solution.

property named_functions: list[EvaluatedNamedFunction]

Get evaluated named functions as a list.

property named_functions_df: pandas.DataFrame
property objective: float
property optimality: ommx._ommx_rust.Optimality
parameters

The parameters used in the optimization, stored as org.ommx.v1.solution.parameters annotation as a JSON in OMMX artifact.

raw: ommx._ommx_rust.Solution

The raw _ommx_rust.Solution object.

property relaxation: ommx._ommx_rust.Relaxation
property sense: ommx._ommx_rust.Sense
solver

The solver which generated this solution, stored as org.ommx.v1.solution.solver annotation as a JSON in OMMX artifact.

start

When the optimization started, stored as org.ommx.v1.solution.start annotation in RFC3339 format in OMMX artifact.

property state: State
ommx.v1.ConstraintHints
ommx.v1.Equality
ommx.v1.EvaluatedConstraint
ommx.v1.EvaluatedDecisionVariable
ommx.v1.EvaluatedNamedFunction
ommx.v1.Kind
ommx.v1.OneHot
ommx.v1.Optimality
ommx.v1.Relaxation
ommx.v1.Rng
ommx.v1.SampledConstraint
ommx.v1.SampledDecisionVariable
ommx.v1.SampledNamedFunction
ommx.v1.Samples
ommx.v1.Sense
ommx.v1.Sos1
ommx.v1.State
type ommx.v1.ToSamples = Samples | Mapping[int, ToState] | Sequence[ToState]

Type alias for convertible types to Samples.

type ommx.v1.ToState = State | Mapping[int, float]

Type alias for convertible types to State.