ommx.v1
Submodules
- ommx.v1.annotation
- ommx.v1.constraint_hints_pb2
- ommx.v1.constraint_pb2
- ommx.v1.decision_variables_pb2
- ommx.v1.function_pb2
- ommx.v1.instance_pb2
- ommx.v1.linear_pb2
- ommx.v1.named_function_pb2
- ommx.v1.one_hot_pb2
- ommx.v1.parametric_instance_pb2
- ommx.v1.polynomial_pb2
- ommx.v1.quadratic_pb2
- ommx.v1.sample_set_pb2
- ommx.v1.solution_pb2
- ommx.v1.sos1_pb2
Attributes
Type alias for convertible types to |
|
Type alias for convertible types to |
Classes
Variable bound representing the valid range for a decision variable. |
|
Constraints |
|
Idiomatic wrapper of |
|
Helper class that provides a standard way to create an ABC using |
|
Idiomatic wrapper of |
|
Modeler API for linear function |
|
A named function attached to an optimization instance. |
|
Idiomatic wrapper of |
|
Idiomatic wrapper of |
|
Helper class that provides a standard way to create an ABC using |
|
Helper class that provides a standard way to create an ABC using |
|
The output of sampling-based optimization algorithms, e.g. simulated annealing (SA). |
|
Idiomatic wrapper of |
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.
- 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 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.DecisionVariableprotobuf 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 description: str
- property id: int
- 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
- 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.Instanceprotobuf message.Note that this class also contains annotations like
titlewhich 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 applicableThe 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:
Trueif the instance is converted,Falseif 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:
Trueif the instance is converted,Falseif 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
Stateinto aSolution.This method evaluates the problem instance using the provided state (a map from decision variable IDs to their values), and returns a
Solutionobject 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
Solutionwith the value nearest to0within its bound. For example,When the bound is \([-1, 1]\) or \((-\infty, \infty)\), the value is
0When the bound is \([2, 5]\), the value is
2When 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_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().
- 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:
Save the output to a file:
profile.txtGenerate SVG:
flamegraph.pl profile.txt > memory.svgOpen 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:
- Returns:
A new instance with the specified decision variables fixed to their given values.
- Return type:
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_samplessample entries divided intonum_different_samplesgroups, 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:
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:
Trueif any reduction was performed,Falseotherwise.
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 byrestore_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:
Convert the instance to a minimization problem by
as_minimization_problem().Check continuous variables and raise error if exists.
Convert inequality constraints
Try
convert_inequality_to_equality_with_integer_slack()first with giveninequality_integer_slack_max_range.If failed,
add_integer_slack_to_inequality()
Convert to HUBO with (uniform) penalty method
If
penalty_weightsis given (indict[constraint_id, weight]form), usepenalty_method()with the given weights.If
uniform_penalty_weightis given, useuniform_penalty_method()with the given weight.If both are None, defaults to
uniform_penalty_weight = 1.0.
Log-encode integer variables by
log_encode().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:
Convert the instance to a minimization problem by
as_minimization_problem().Check continuous variables and raise error if exists.
Convert inequality constraints
Try
convert_inequality_to_equality_with_integer_slack()first with giveninequality_integer_slack_max_range.If failed,
add_integer_slack_to_inequality()
Convert to QUBO with (uniform) penalty method
If
penalty_weightsis given (indict[constraint_id, weight]form), usepenalty_method()with the given weights.If
uniform_penalty_weightis given, useuniform_penalty_method()with the given weight.If both are None, defaults to
uniform_penalty_weight = 1.0.
Log-encode integer variables by
log_encode().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
instanceobject 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
Two types of decision variables are added
ommx.slackinteger slack variable \(x_2\) byconvert_inequality_to_equality_with_integer_slack()ommx.log_encodebinary variables \(x_3, \ldots, x_8\) introduced bylog_encode().
>>> 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]
The yielded
objectiveonly has these binary variables.The
removed_constraintskeep the original variables until restored.
>>> 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
titleor other specific attributes if possible.
- authors
Authors of this instance, stored as
org.ommx.v1.instance.authorsannotation 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
Constraintinstances sorted by their IDs.
- property constraints_df: pandas.DataFrame
- created
The creation date of the instance, stored as
org.ommx.v1.instance.createdannotation in RFC3339 format in OMMX artifact.
- dataset
Dataset name which this instance belongs to, stored as
org.ommx.v1.instance.datasetannotation 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
DecisionVariableinstances sorted by their IDs.
- property decision_variables_df: pandas.DataFrame
- license
License of this instance in the SPDX license identifier. This is stored as
org.ommx.v1.instance.licenseannotation 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
NamedFunctioninstances sorted by their IDs.
- property named_functions_df: pandas.DataFrame
- num_constraints
Number of constraints in this instance, stored as
org.ommx.v1.instance.constraintsannotation in OMMX artifact.
- num_variables
Number of variables in this instance, stored as
org.ommx.v1.instance.variablesannotation in OMMX artifact.
- raw: ommx._ommx_rust.Instance
The raw Rust instance.
- property removed_constraints: list[RemovedConstraint]
Get removed constraints as a list of
RemovedConstraintinstances.
- property removed_constraints_df: pandas.DataFrame
- property sense: ommx._ommx_rust.Sense
- title
The title of the instance, stored as
org.ommx.v1.instance.titleannotation 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
DecisionVariableinstancess 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.Linearprotobuf 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.
- 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
- classmethod from_object(obj: float | int | DecisionVariable | ommx._ommx_rust.Linear | 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 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.Parameterprotobuf message.- 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]
- property subscripts: list[int]
- class ommx.v1.ParametricInstance
Idiomatic wrapper of
ommx.v1.ParametricInstanceprotobuf 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_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.authorsannotation 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.createdannotation in RFC3339 format in OMMX artifact.
- dataset
Dataset name which this instance belongs to, stored as
org.ommx.v1.parametric-instance.datasetannotation in OMMX artifact.
- property decision_variables: list[DecisionVariable]
Get decision variables as a list of
DecisionVariableinstances.
- 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.licenseannotation in OMMX artifact.
- property named_functions: list[NamedFunction]
Get named functions as a list of
NamedFunctioninstances.
- property named_functions_df: pandas.DataFrame
- num_constraints
Number of constraints in this instance, stored as
org.ommx.v1.parametric-instance.constraintsannotation in OMMX artifact.
- num_variables
Number of variables in this instance, stored as
org.ommx.v1.parametric-instance.variablesannotation in OMMX artifact.
- property parameters_df: pandas.DataFrame
- property removed_constraints: list[RemovedConstraint]
Get removed constraints as a list of
RemovedConstraintinstances.
- property removed_constraints_df: pandas.DataFrame
- title
The title of the instance, stored as
org.ommx.v1.parametric-instance.titleannotation 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
- 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_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
Solutionrather thansolution_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
summaryattribute 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 byget()as aSolutionformat:>>> 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.
- 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.
- to_bytes() bytes
- annotation_namespace = 'org.ommx.v1.sample-set'
- annotations: dict[str, str]
Arbitrary annotations stored in OMMX artifact. Use
parametersor 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.endannotation 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.constraintsand allInstance.removed_constraints.
- instance
The digest of the instance layer, stored as
org.ommx.v1.sample-set.instanceannotation 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.parametersannotation 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.solverannotation as a JSON in OMMX artifact.
- start
When the optimization started, stored as
org.ommx.v1.sample-set.startannotation 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.Solutionprotobuf 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.
- 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
parametersor 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.endannotation 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.instanceannotation in OMMX artifact.This
Solutionis 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.parametersannotation 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.solverannotation as a JSON in OMMX artifact.
- start
When the optimization started, stored as
org.ommx.v1.solution.startannotation in RFC3339 format in OMMX artifact.
- 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