Instance#
- class Instance#
Optimization problem instance.
Invariants#
Output-only variables are excluded from solver input and evaluated after the full state is populated.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], ... objective=3 * x, ... constraints={}, ... sense=Sense.Maximize, ... ) >>> assert instance.convert_active_objective(Sense.Minimize) >>> fixed = instance.partial_evaluate({0: 1}) >>> assert fixed.sense == Sense.Minimize >>> assert fixed.objective.evaluate({}) == -3.0 >>> assert fixed.required_ids() == set() >>> assert fixed.used_decision_variables == [] >>> assert fixed.populate_state({}).entries == {0: 1.0} >>> solution = fixed.evaluate({}) >>> assert (solution.sense, solution.objective) == (Sense.Maximize, 3.0)
- __copy__() Instance#
- __repr__() str#
- __str__() str#
- add_constraint(constraint: Constraint, name: Optional[str] = None, *, subscripts: Optional[Sequence[int]] = None, parameters: Optional[Mapping[str, str]] = None, description: Optional[str] = None) AttachedConstraint#
Add a regular constraint to this instance.
Picks an unused
ConstraintID, drains the wrapper's context snapshot into this instance's SoA store, and returns anAttachedConstraintbound to the new id. The inputConstraintis not mutated; subsequent writes that should land in the instance must go through the returned handle. When modeling-label fields are provided, they replace the corresponding fields stored on the inserted constraint without modifying the input snapshot. Omitted fields preserve the snapshot's existing values.Args:
constraint: Constraint to addname: Optional modeling name for the inserted constraintsubscripts: Optional integer indices for the inserted constraintparameters: Optional string-valued indices for the inserted constraintdescription: Optional description for the inserted constraint
Raises
ValueErrorif the constraint references an undefined decision variable or one currently used as a substitution-dependency key, matching the validation performed by other constraint-insertion paths.
- add_decision_variable(variable: DecisionVariable) AttachedDecisionVariable#
Add a decision variable to this instance.
Drains the wrapper's modeling-label snapshot into this instance's SoA store and returns an
AttachedDecisionVariablebound to the variable's id — a write-through handle for further label updates. The original wrapper is not modified.Raises
ValueErrorif the variable's id collides with an existing decision variable. Substituted variables retain their ids and therefore also count as existing decision variables.
- add_indicator_constraint(constraint: IndicatorConstraint) AttachedIndicatorConstraint#
Add an indicator constraint to this instance.
Picks an unused
IndicatorConstraintID, drains the wrapper's context snapshot into this instance's SoA store, and returns anAttachedIndicatorConstraintbound to the new id.Raises
ValueErrorif the constraint references an undefined decision variable or one currently used as a substitution-dependency key.
- add_integer_slack_to_inequality(constraint_id: int, slack_upper_bound: int, *, atol: Optional[float] = None) Optional[float]#
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, \text{slack\_upper\_bound}]\), and the coefficient \(b\) is 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. finer-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.
Returns: The coefficient \(b\) of the slack variable. If the constraint is trivially satisfied, this returns
None.atolcontrols zero-sensitive interval bounds and the inclusive inequality feasibility threshold used to select the slack coefficient; it must be less than0.5. If omitted, :attr:DEFAULT_ATOLis used.Examples#
Let's consider a simple inequality constraint x0 + 2*x1 <= 4.
>>> from ommx import DecisionVariable, Equality, Instance, Sense >>> 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={0: x[0] + 2*x[1] <= 4}, ... sense=Sense.Maximize, ... )
Introduce an integer slack variable s in [0, 2]
>>> b = instance.add_integer_slack_to_inequality( ... constraint_id=0, ... slack_upper_bound=2 ... ) >>> assert b == 2.0 >>> assert instance.constraints[0].function.terms == { ... (0,): 1.0, (1,): 2.0, (3,): 2.0, (): -4.0 ... } >>> assert instance.constraints[0].equality == Equality.LessThanOrEqualToZero
- add_one_hot_constraint(constraint: OneHotConstraint) AttachedOneHotConstraint#
Add a one-hot constraint to this instance.
- add_sos1_constraint(constraint: Sos1Constraint) AttachedSos1Constraint#
Add a SOS1 constraint to this instance.
- add_user_annotation(key: str, value: str, *, annotation_namespace: str = 'org.ommx.user.') None#
- add_user_annotations(annotations: Mapping[str, str], *, annotation_namespace: str = 'org.ommx.user.') None#
- as_hubo_format() tuple[dict, float]#
Return the active objective in HUBO format without preparing the instance.
Postconditions#
The returned coefficients represent the active objective rather than preserved output semantics.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=3 * x + 5, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.convert_active_objective(Sense.Minimize) >>> hubo, offset = instance.as_hubo_format() >>> assert (hubo, offset) == ({(0,): -3.0}, -5.0) >>> assert instance.objective.evaluate({0: 1}) == -8.0 >>> assert instance.evaluate({0: 1}).objective == 8.0
- as_maximization_problem() bool#
Convert the instance to a maximization problem.
If both the active objective and the output objective already use maximization, this does nothing.
Returns:
Trueif either objective is converted,Falseif both already use maximization.Postconditions#
Conversion changes both active and output objective semantics and is idempotent at the target sense. An existing output objective remains explicit even if both objectives become structurally equal.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=3 * x, constraints={}, sense=Sense.Minimize ... ) >>> assert instance.convert_active_objective(Sense.Maximize) >>> assert instance.evaluate({0: 1}).objective == 3.0 >>> assert instance.as_maximization_problem() >>> solution = instance.evaluate({0: 1}) >>> assert instance.objective.evaluate({0: 1}) == -3.0 >>> assert (solution.sense, solution.objective) == (Sense.Maximize, -3.0) >>> assert not instance.as_maximization_problem()
- as_minimization_problem() bool#
Convert the instance to a minimization problem.
If both the active objective and the output objective already use minimization, this does nothing.
Returns:
Trueif either objective is converted,Falseif both already use minimization.Postconditions#
Conversion changes both active and output objective semantics and is idempotent at the target sense. An existing output objective remains explicit even if both objectives become structurally equal.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=3 * x, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.convert_active_objective(Sense.Minimize) >>> assert instance.evaluate({0: 1}).objective == 3.0 >>> assert instance.as_minimization_problem() >>> solution = instance.evaluate({0: 1}) >>> assert instance.objective.evaluate({0: 1}) == -3.0 >>> assert (solution.sense, solution.objective) == (Sense.Minimize, -3.0) >>> assert not instance.as_minimization_problem()
- as_parametric_instance() ParametricInstance#
Convert this instance into a parameter-free parametric instance.
Postconditions#
Materializing the result without parameters preserves both active and output objective semantics.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=x, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.convert_active_objective(Sense.Minimize) >>> restored = instance.as_parametric_instance().with_parameters({}) >>> assert restored.sense == Sense.Minimize >>> assert restored.objective.evaluate({0: 1}) == -1.0 >>> solution = restored.evaluate({0: 1}) >>> assert (solution.sense, solution.objective) == (Sense.Maximize, 1.0)
- as_qubo_format() tuple[dict, float]#
Return the active objective in QUBO format without preparing the instance.
Postconditions#
The returned coefficients represent the active objective rather than preserved output semantics.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=3 * x + 5, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.convert_active_objective(Sense.Minimize) >>> qubo, offset = instance.as_qubo_format() >>> assert (qubo, offset) == ({(0, 0): -3.0}, -5.0) >>> assert instance.objective.evaluate({0: 1}) == -8.0 >>> assert instance.evaluate({0: 1}).objective == 8.0
- attached_decision_variable(variable_id: int) AttachedDecisionVariable#
Return an
AttachedDecisionVariablebound to the given id — a write-through handle whose label setters update this instance's SoA store. The handle also participates in arithmetic viaToFunction(only its id is consumed). Calldetach()to obtain an independentDecisionVariablesnapshot.Raises
KeyErrorif no variable withvariable_idexists.
- constraint_context_df(kind: Literal["regular", "indicator", "one_hot", "sos1"] = 'regular') DataFrame#
Constraint context DataFrame (id-indexed wide format).
One row per constraint id (active + removed) with columns
name,subscripts,description. Index column is{kind}_constraint_id.kindselects which constraint family to read:"regular","indicator","one_hot", or"sos1".
- constraint_parameters_df(kind: Literal["regular", "indicator", "one_hot", "sos1"] = 'regular') DataFrame#
Constraint parameters DataFrame (long format).
One row per (constraint_id, parameter_key) pair. Columns:
{kind}_constraint_id,key,value. Default RangeIndex.
- constraint_provenance_df(kind: Literal["regular", "indicator", "one_hot", "sos1"] = 'regular') DataFrame#
Constraint provenance DataFrame (long format).
One row per (constraint_id, step) pair. Columns:
{kind}_constraint_id,step,source_kind,source_id.
- constraint_removed_reasons_df(kind: Literal["regular", "indicator", "one_hot", "sos1"] = 'regular') DataFrame#
Removed-constraint reasons DataFrame (long format).
One row per (constraint_id, parameter_key) pair, plus one row with
key/valueset to NA when the reason has no parameters. Columns:{kind}_constraint_id,reason,key,value.
- constraints_df(kind: Literal["regular", "indicator", "one_hot", "sos1"] = 'regular', include: Optional[Sequence[str]] = None, removed: bool = False) DataFrame#
DataFrame of constraints, dispatched on
kind=.kindselects the constraint family —"regular","indicator","one_hot", or"sos1". The DataFrame is indexed by the kind- qualified id column ({kind}_constraint_id).includeselects which optional column families to fold in. It accepts a sequence of"label"/"parameters"/"removed_reason"; passingNone(the default) yields the v2-equivalent shape (label+parameters)."removed_reason"is a unit flag that gates both theremoved_reasoncolumn and theremoved_reason.{key}parameter columns together.removed=False(default) returns active constraints only.removed=Truereturns active + removed rows in the same DataFrame and auto-sets"removed_reason"so removed rows are distinguishable (active rows have NA in the reason columns).
- convert_active_objective(target: Sense) bool#
Convert only the active objective used by a solver-facing formulation.
This changes
senseandobjectivetotargetwhile preserving the objective semantics returned byevaluate()andevaluate_samples(). Useas_minimization_problem()oras_maximization_problem()when the output objective should be converted as part of the mathematical problem itself.Returns:
Trueif the active objective is converted,Falseif it already hastarget.Postconditions#
Conversion negates only the active objective and preserves evaluation semantics in either direction. Once captured, the output objective remains explicit even if a later conversion makes it structurally equal to the active objective.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> for source, target in ((Sense.Maximize, Sense.Minimize), (Sense.Minimize, Sense.Maximize)): ... instance = Instance.from_components( ... decision_variables=[x], objective=3 * x, constraints={}, sense=source ... ) ... before = instance.evaluate({0: 1}) ... assert instance.convert_active_objective(target) ... after = instance.evaluate({0: 1}) ... assert instance.sense == target ... assert instance.objective.evaluate({0: 1}) == -3.0 ... assert (after.sense, after.objective) == (before.sense, before.objective) ... assert not instance.convert_active_objective(target)
- convert_all_indicators_to_constraints(*, atol: Optional[float] = None) dict[int, list[int]]#
Convert every active indicator constraint to regular constraints using Big-M.
See
convert_indicator_to_constraint()for the conversion rule. Returns a dict mapping each original indicator ID to the list of regular constraint IDs it produced.Atomic: every active indicator is validated up front, and only if every one is convertible are the conversions applied. If any indicator fails validation (non-finite bound on a required side), no mutation happens and the instance is left untouched.
atolhas the same Function-body meaning as in the single-constraint conversion. If omitted, :attr:DEFAULT_ATOLis used.
- convert_all_one_hots_to_constraints() list[int]#
Convert every active one-hot constraint to a regular equality constraint.
See
convert_one_hot_to_constraint()for the conversion rule. Returns the IDs of the newly created regular constraints.Examples#
>>> from ommx import Instance, DecisionVariable, OneHotConstraint, Sense >>> x = [DecisionVariable.binary(i) for i in range(4)] >>> instance = Instance.from_components( ... decision_variables=x, ... objective=sum(x), ... constraints={}, ... one_hot_constraints={ ... 1: OneHotConstraint(variables=x[:2]), ... 2: OneHotConstraint(variables=x[2:]), ... }, ... sense=Sense.Minimize, ... ) >>> instance.convert_all_one_hots_to_constraints() [0, 1] >>> instance.one_hot_constraints {} >>> instance.constraints {0: Constraint(x0 + x1 - 1 == 0), 1: Constraint(x2 + x3 - 1 == 0)}
- convert_all_sos1_to_constraints() dict[int, list[int]]#
Convert every active SOS1 constraint to regular constraints using Big-M.
See
convert_sos1_to_constraints()for the conversion rule. Returns a dict mapping each original SOS1 ID to the list of regular constraint IDs it produced.Atomic: every active SOS1 is validated up front, and only if every one is convertible are the conversions applied. If any SOS1 fails validation (unsupported kind, non-finite bound, domain excludes 0, etc.), no mutation happens and the instance is left untouched.
Examples#
>>> from ommx import Instance, DecisionVariable, Sense, Sos1Constraint >>> x = [DecisionVariable.binary(i) for i in range(4)] >>> instance = Instance.from_components( ... decision_variables=x, ... objective=sum(x), ... constraints={}, ... sos1_constraints={ ... 1: Sos1Constraint(variables=x[:2]), ... 2: Sos1Constraint(variables=x[2:]), ... }, ... sense=Sense.Minimize, ... ) >>> instance.convert_all_sos1_to_constraints() {1: [0], 2: [1]} >>> instance.sos1_constraints {} >>> instance.constraints {0: Constraint(x0 + x1 - 1 <= 0), 1: Constraint(x2 + x3 - 1 <= 0)}
- convert_indicator_to_constraint(indicator_id: int, *, atol: Optional[float] = None) list[int]#
Convert an indicator constraint to regular constraints using the Big-M method.
An indicator constraint
y = 1 → f(x) <= 0(or= 0) is encoded with upper and lower Big-M sides computed from the interval bounds of \(f(x)\):\[ f(x) + u y - u \leq 0, \qquad -f(x) - l y + l \leq 0, \]where \(u \geq \sup f(x)\) and \(l \leq \inf f(x)\) are the upper and lower bounds of \(f\) over the decision variables' domains.
Side emission:
For
<=indicators, only the upper side is considered; it is emitted iff \(u > 0\). If \(u \leq 0\) the constraint is already implied by the variable bounds and no Big-M is emitted.For
=indicators, both sides are considered independently: upper emitted iff \(u > 0\), lower emitted iff \(l < 0\).
When an equality side is skipped, the remaining constraints still enforce the implication correctly because the skipped inequality is already implied by the variable bounds: e.g. \(u \leq 0\) together with the emitted lower side forces \(f(x) = 0\) at \(y = 1\) when \(u = 0\), or renders \(y = 1\) infeasible when \(u < 0\) (correctly reflecting that \(f(x) = 0\) has no solution under the given bounds). When both \(u = 0\) and \(l = 0\), the bound says \(f(x) \equiv 0\) so the equality is vacuously satisfied and nothing is emitted.
Returns the list of newly created regular constraint IDs in insertion order (upper first, then lower). The list is empty when both sides are redundant.
Raises if the bound needed for an emitted side is non-finite, or if \(f(x)\) references a semi-continuous / semi-integer variable (the split domain \(\{0\} \cup [l, u]\) is not uniformly implemented, so Big-M conversion could silently drop the upper side when \(0 \notin [l, u]\)). The instance is not mutated on error.
atolcontrols which Function-body values the bound evaluator treats as zero. If omitted, :attr:DEFAULT_ATOLis used. Big-M algebra assumes the indicator variable is exactly binary; this does not canonicalize an approximate solver value near 0 or 1.Examples#
Convert an inequality indicator where the upper side is active:
>>> from ommx import ( ... Instance, DecisionVariable, IndicatorConstraint, Equality, Sense, ... ) >>> x = DecisionVariable.continuous(0, lower=0.0, upper=5.0) >>> y = DecisionVariable.binary(1) >>> ic = IndicatorConstraint( ... indicator_variable=y, ... function=x - 2, ... equality=Equality.LessThanOrEqualToZero, ... ) >>> instance = Instance.from_components( ... decision_variables=[x, y], ... objective=x, ... constraints={}, ... indicator_constraints={1: ic}, ... sense=Sense.Minimize, ... ) >>> instance.convert_indicator_to_constraint(1) [0] >>> instance.indicator_constraints {} >>> instance.constraints {0: Constraint(x0 + 3*x1 - 5 <= 0)}
- convert_inequality_to_equality_with_integer_slack(constraint_id: int, max_integer_range: int, *, atol: Optional[float] = None) None#
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 every coefficient of \(a f(x)\) integer, \(a\) itself and the range of \(s\) becomes impractically large.
max_integer_rangelimits the maximal range of \(s\), and returns error if the range exceeds it.Since this method evaluates the bound of \(f(x)\), we may find that:
The bound \([l, u]\) is infeasible at the selected tolerance, i.e. \(l > \text{atol}\): this means the instance is infeasible because this constraint never be satisfied, and an error is raised.
The bound is feasible everywhere at the selected tolerance, i.e. \(u \leq \text{atol}\): this means this constraint is trivially satisfied, 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 x0 + 2*x1 <= 5.
>>> from ommx import DecisionVariable, Equality, Instance, Sense >>> 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={0: x[0] + 2*x[1] <= 5}, ... sense=Sense.Maximize, ... )
Introduce an integer slack variable
>>> instance.convert_inequality_to_equality_with_integer_slack( ... constraint_id=0, ... max_integer_range=32 ... ) >>> assert instance.constraints[0].function.terms == { ... (0,): 1.0, (1,): 2.0, (3,): 1.0, (): -5.0 ... } >>> assert instance.constraints[0].equality == Equality.EqualToZero
Raises
ExactIntegerSlackErrorwhen exact conversion is unavailable because the coefficients cannot be normalized or the slack range exceedsmax_integer_range. RaisesInfeasibleDetectedwhen the bounds prove the inequality infeasible.atolcontrols zero-sensitive interval evaluation and the inclusive inequality feasibility threshold and must be less than0.5. If omitted, :attr:DEFAULT_ATOLis used.
- convert_one_hot_to_constraint(one_hot_id: int) int#
Convert a one-hot constraint to a regular equality constraint.
A one-hot constraint over
{x_1, ..., x_n}is mathematically equivalent to the linear equalityx_1 + ... + x_n - 1 == 0. This method inserts that equality as a new regular constraint and moves the one-hot constraint intoremoved_one_hot_constraintswithreason="ommx.Instance.convert_one_hot_to_constraint"and aconstraint_idparameter pointing to the new regular constraint.Returns the ID of the newly created regular constraint.
Examples#
>>> from ommx import Instance, DecisionVariable, OneHotConstraint, Sense >>> x = [DecisionVariable.binary(i) for i in range(3)] >>> instance = Instance.from_components( ... decision_variables=x, ... objective=sum(x), ... constraints={}, ... one_hot_constraints={1: OneHotConstraint(variables=x)}, ... sense=Sense.Minimize, ... ) >>> new_id = instance.convert_one_hot_to_constraint(1) >>> instance.one_hot_constraints {} >>> instance.constraints {0: Constraint(x0 + x1 + x2 - 1 == 0)} >>> instance.removed_one_hot_constraints {1: RemovedOneHotConstraint(OneHotConstraint(exactly one of {x0, x1, x2} = 1), reason=ommx.Instance.convert_one_hot_to_constraint, constraint_id=0)}
- convert_sos1_to_constraints(sos1_id: int) list[int]#
Convert a SOS1 constraint to regular constraints using the Big-M method.
A SOS1 constraint over \(\{x_1, \ldots, x_n\}\) with each \(x_i \in [l_i, u_i]\) asserts that at most one \(x_i\) is non-zero. Per variable, a binary indicator \(y_i\) is introduced with the Big-M pair
\[ x_i - u_i y_i \leq 0, \qquad l_i y_i - x_i \leq 0 \](trivial sides \(u_i = 0\) or \(l_i = 0\) are skipped), together with the single cardinality constraint
\[ \sum_i y_i - 1 \leq 0. \]If \(x_i\) is already binary with bound \([0, 1]\), \(x_i\) itself is reused as its indicator (no new variable, no Big-M pair).
Returns the list of newly created regular constraint IDs in insertion order (Big-M upper/lower pairs per non-binary variable, followed by the cardinality sum).
Raises if any \(x_i\) has a non-binary bound that is not finite, if its domain excludes \(0\), or if its kind is semi-continuous / semi-integer (the split domain \(\{0\} \cup [l, u]\) is not uniformly implemented across the codebase yet, so Big-M conversion of these kinds is not supported). The instance is not mutated on error.
Examples#
All-binary SOS1 reduces to
sum(x_i) - 1 <= 0without extra variables:>>> from ommx import Instance, DecisionVariable, Sense, Sos1Constraint >>> x = [DecisionVariable.binary(i) for i in range(3)] >>> instance = Instance.from_components( ... decision_variables=x, ... objective=sum(x), ... constraints={}, ... sos1_constraints={1: Sos1Constraint(variables=x)}, ... sense=Sense.Minimize, ... ) >>> instance.convert_sos1_to_constraints(1) [0] >>> instance.sos1_constraints {} >>> instance.constraints {0: Constraint(x0 + x1 + x2 - 1 <= 0)} >>> instance.removed_sos1_constraints {1: RemovedSos1Constraint(Sos1Constraint(at most one of {x0, x1, x2} ≠ 0), reason=ommx.Instance.convert_sos1_to_constraints, constraint_ids=0)}
- decision_variable_role(id: int) Optional[DecisionVariableRole]#
Return the state role of a decision variable.
The role is one of
used,fixed,dependent, orirrelevant. Unknown IDs returnNone.
- decision_variable_roles() dict[int, DecisionVariableRole]#
Return the state role of every decision variable, keyed by ID.
- decision_variables_df(include: Optional[Sequence[str]] = None) DataFrame#
DataFrame of decision variables
- dependent_decision_variable_ids() set[int]#
Return IDs of decision variables defined by
decision_variable_dependency.
- display_function(function: ToFunction, max_terms: Optional[int] = 100, max_chars: Optional[int] = 20000) FunctionDisplay#
Build a bounded notebook display object for a function in this instance's context.
By default this returns a preview capped at 100 complete terms and 20,000 characters. Use
format_function()for an unbounded plain text string.
- empty() Instance#
Deprecated
Use Instance.minimize() instead.
Create trivial empty instance of minimization with zero objective, no constraints, and no decision variables.
Examples#
>>> from ommx import Instance, Sense >>> instance = Instance.minimize() >>> instance.sense == Sense.Minimize True
- evaluate(state: ToState, *, atol: Optional[float] = None) Solution#
Evaluate the given
Stateinto aSolution.Postconditions#
Evaluation first applies the canonicalization, population, and consistency assertion rules documented by
populate_state(), then applies preserved output objective semantics to the populated state.>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=3 * x, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.convert_active_objective(Sense.Minimize) >>> fixed = instance.partial_evaluate({0: 1}) >>> solution = fixed.evaluate({}) >>> assert (solution.sense, solution.objective) == (Sense.Maximize, 3.0)
Errors#
Evaluation raises
ValueErrorwhen an active required ID is missing.>>> required = Instance.from_components( ... decision_variables=[x], objective=x, constraints={}, sense=Sense.Minimize ... ) >>> try: ... required.evaluate({}) ... except ValueError as error: ... assert "missing required variable IDs" in str(error) ... else: ... raise AssertionError("evaluation accepted a missing active ID")
- evaluate_samples(samples: ToSamples, *, atol: Optional[float] = None) SampleSet#
Evaluate samples into a sample set.
Postconditions#
Every sample uses the canonicalization, population, and consistency assertion rules documented by
populate_state()before applying preserved output objective semantics. SampleID membership is preserved.>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=3 * x, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.convert_active_objective(Sense.Minimize) >>> fixed = instance.partial_evaluate({0: 1}) >>> sample_set = fixed.evaluate_samples({7: {}}) >>> assert sample_set.sense == Sense.Maximize >>> assert sample_set.objectives[7] == 3.0 >>> assert sample_set.get(7).state.entries == {0: 1.0}
- fixed_decision_variables() dict[int, float]#
Return fixed decision variables as
{id: fixed_value}.
- format_function(function: ToFunction, max_terms: Optional[int] = None, max_chars: Optional[int] = None) str#
Format a function using this instance's decision-variable labels.
The plain
repr()/str()representation ofFunctionremains context-free and renders rawx<id>symbols such asx1. Use this method when labels from this instance should be used instead.
- from_components(*, sense: Sense, objective: ToFunction, decision_variables: Sequence[DecisionVariable], constraints: Mapping[int, Constraint], indicator_constraints: Optional[Mapping[int, IndicatorConstraint]] = None, one_hot_constraints: Optional[Mapping[int, OneHotConstraint]] = None, sos1_constraints: Optional[Mapping[int, Sos1Constraint]] = None, named_functions: Optional[Sequence[NamedFunction]] = None, description: Optional[InstanceDescription] = None) Instance#
Create an instance from its components.
Args:
sense: Optimization sense (minimize or maximize)objective: Objective functiondecision_variables: List of decision variablesconstraints: List of constraintsnamed_functions: Optional list of named functionsdescription: Optional instance description
Returns: A new Instance
- from_v1_bytes(bytes: bytes) Instance#
Deserialize an instance from v1 protobuf bytes.
Raises
ValueErrorif the protobuf payload is malformed or semantically invalid.
- from_v2_bytes(bytes: bytes) Instance#
Deserialize an instance from v2 protobuf bytes.
Raises
ValueErrorif the protobuf payload is malformed or semantically invalid.
- get_constraint_by_id(constraint_id: int) Constraint#
Get a specific constraint by ID
- get_decision_variable_by_id(variable_id: int) DecisionVariable#
Get a specific decision variable by ID
- get_named_function_by_id(named_function_id: int) NamedFunction#
Get a specific named function by ID
- get_removed_constraint_by_id(constraint_id: int) RemovedConstraint#
Get a specific removed constraint by ID
- get_user_annotation(key: str, *, annotation_namespace: str = 'org.ommx.user.') str#
- get_user_annotations(*, annotation_namespace: str = 'org.ommx.user.') dict[str, str]#
- irrelevant_decision_variable_ids() set[int]#
Return IDs of decision variables not used, fixed, or dependent.
- load_mps(path: str) Instance#
- load_qplib(path: str) Instance#
- log_encode(decision_variable_ids: set[int] = set(), *, atol: Optional[float] = None) None#
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\).
Args:
decision_variable_ids: The IDs of the integer decision variables to log-encode. If not specified (or empty), all used integer variables are log-encoded.atol: Optional absolute tolerance used when normalizing integer bounds before encoding. If None, uses the default tolerance.
Raises
LogEncodingErrorwhen an exact representation is unavailable for a requested variable. Allocation and expression-rewrite failures retain their original exception types.Postconditions#
Encoding preserves existing output semantics, or captures the pre-encoding active objective when no output objective exists, while rewriting the active objective.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.integer(0, lower=0, upper=3) >>> instance = Instance.from_components( ... decision_variables=[x], objective=x, constraints={}, sense=Sense.Maximize ... ) >>> instance.log_encode({0}) >>> encoded_ids = instance.required_ids() >>> assert len(encoded_ids) == 2 >>> state = {variable_id: 1 for variable_id in encoded_ids} >>> assert instance.objective.evaluate(state) == 3.0 >>> solution = instance.evaluate(state) >>> assert (solution.sense, solution.objective) == (Sense.Maximize, 3.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.plandinferno. 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
Examples#
>>> from ommx import DecisionVariable, Instance, Sense >>> x = [DecisionVariable.binary(i) for i in range(3)] >>> instance = Instance.from_components( ... decision_variables=x, ... objective=x[0] + x[1], ... constraints={}, ... sense=Sense.Maximize, ... ) >>> profile = instance.logical_memory_profile() >>> isinstance(profile, str) True
- lower_special_constraints(kinds_to_lower: set[SpecialConstraintKind], *, atol: Optional[float] = None) set[SpecialConstraintKind]#
Lower selected active special constraint kinds into regular constraints.
For every kind in
kinds_to_lower, the corresponding bulk conversion is invoked (:meth:convert_all_indicators_to_constraints, :meth:convert_all_one_hots_to_constraints, or :meth:convert_all_sos1_to_constraints) when that kind is active. The instance is mutated in place. Kinds omitted fromkinds_to_lowerremain active, and an empty set is a no-op. This does not establish :class:InstanceClassmembership; check the resulting input separately.Returns the set of :class:
SpecialConstraintKindvalues that were requested and active, and therefore actually lowered. Empty when no requested kind was active.atolcontrols zero-sensitive interval bounds used while lowering Indicator constraints. If omitted, :attr:DEFAULT_ATOLis used. This aligns zero-sensitive Function body evaluation; lowering itself assumes exact discrete variable values.Kinds are processed in
Indicator,OneHot,Sos1order. Each individual family conversion is atomic, but the whole operation is not: an error in a later family does not roll back families already lowered.Raises if any underlying Big-M conversion fails (e.g. a SOS1 variable with a non-finite bound).
- map_active_optimality(active: Optimality) Optimality#
Map an optimality status for the active solver-facing formulation to the objective semantics returned by evaluation.
When the instance records that active-formulation optimality does not transport to its output objective, this returns
Unspecified.Postconditions#
Optimality is preserved for equivalent objective conversion and discarded after penalty preparation.
>>> from ommx import DecisionVariable, Instance, Optimality, Sense >>> x = DecisionVariable.binary(0) >>> equivalent = Instance.from_components( ... decision_variables=[x], objective=x, constraints={}, sense=Sense.Maximize ... ) >>> assert equivalent.convert_active_objective(Sense.Minimize) >>> statuses = (Optimality.Unspecified, Optimality.Optimal, Optimality.NotOptimal) >>> for status in statuses: ... assert equivalent.map_active_optimality(status) == status >>> penalized = Instance.from_components( ... decision_variables=[x], objective=x, constraints={7: x == 1}, sense=Sense.Minimize ... ) >>> _ = penalized.to_qubo(uniform_penalty_weight=1.0) >>> for status in statuses: ... assert penalized.map_active_optimality(status) == Optimality.Unspecified
- maximize() Instance#
Create an empty maximization instance with a zero objective.
Decision variables and constraints can be added incrementally with the
new_*methods andadd_constraint().
- minimize() Instance#
Create an empty minimization instance with a zero objective.
Decision variables and constraints can be added incrementally with the
new_*methods andadd_constraint().
- new_binary(name: Optional[str] = None, *, subscripts: Sequence[int] = [], parameters: Mapping[str, str] = {}, description: Optional[str] = None) AttachedDecisionVariable#
Create and add a binary decision variable with an automatically assigned ID.
Returns an
AttachedDecisionVariablethat can be used directly in expressions. The numeric ID remains available through itsidproperty.Args:
name: Optional human-readable modeling name. Names need not be unique.subscripts: Optional integer indices from the source model.parameters: Optional string-valued indices from the source model.description: Optional human-readable description.
Raises
ValueErrorif the maximum decision-variable ID is2**64 - 1and no larger automatic ID can be assigned. On failure, no variable or modeling label is added to the instance.
- new_continuous(name: Optional[str] = None, *, lower: float = float('-inf'), upper: float = float('inf'), subscripts: Sequence[int] = [], parameters: Mapping[str, str] = {}, description: Optional[str] = None) AttachedDecisionVariable#
Create and add a continuous decision variable with an automatically assigned ID.
The bounds default to
(-inf, inf). Returns anAttachedDecisionVariablethat can be used directly in expressions.Args:
name: Optional human-readable modeling name. Names need not be unique.lower: Lower bound of the variable.upper: Upper bound of the variable.subscripts: Optional integer indices from the source model.parameters: Optional string-valued indices from the source model.description: Optional human-readable description.
Raises
ValueErrorif the bounds are invalid or if no larger automatic ID can be assigned. On failure, no variable or modeling label is added to the instance.
- new_integer(name: Optional[str] = None, *, lower: float = float('-inf'), upper: float = float('inf'), subscripts: Sequence[int] = [], parameters: Mapping[str, str] = {}, description: Optional[str] = None, atol: Optional[float] = None) AttachedDecisionVariable#
Create and add an integer decision variable with an automatically assigned ID.
The bounds default to
(-inf, inf). Each finite side is normalized to the least or greatest integer value that satisfies the original bound underatol; an infinite side remains unbounded. Bound membership uses the same inequality-residual tolerance semantics as constraint feasibility. Returns anAttachedDecisionVariablethat can be used directly in expressions.Args:
name: Optional human-readable modeling name. Names need not be unique.lower: Lower bound of the variable.upper: Upper bound of the variable.subscripts: Optional integer indices from the source model.parameters: Optional string-valued indices from the source model.description: Optional human-readable description.atol: Absolute tolerance for integer-bound normalization. If omitted, the current default returned byget_default_atol()is used.
Raises
ValueErrorif the bounds are invalid or normalization yields no integer value, or if no larger automatic ID can be assigned. On failure, no variable or modeling label is added to the instance.
- new_semi_continuous(name: Optional[str] = None, *, lower: float = float('-inf'), upper: float = float('inf'), subscripts: Sequence[int] = [], parameters: Mapping[str, str] = {}, description: Optional[str] = None) AttachedDecisionVariable#
Create and add a semi-continuous decision variable with an automatically assigned ID.
The bounds default to
(-inf, inf). Returns anAttachedDecisionVariablethat can be used directly in expressions.Args:
name: Optional human-readable modeling name. Names need not be unique.lower: Lower bound of the variable.upper: Upper bound of the variable.subscripts: Optional integer indices from the source model.parameters: Optional string-valued indices from the source model.description: Optional human-readable description.
Raises
ValueErrorif the bounds are invalid or if no larger automatic ID can be assigned. On failure, no variable or modeling label is added to the instance.
- new_semi_integer(name: Optional[str] = None, *, lower: float = float('-inf'), upper: float = float('inf'), subscripts: Sequence[int] = [], parameters: Mapping[str, str] = {}, description: Optional[str] = None, atol: Optional[float] = None) AttachedDecisionVariable#
Create and add a semi-integer decision variable with an automatically assigned ID.
The bounds default to
(-inf, inf). Each finite side is normalized underatolusing the same residual-feasibility rule asnew_integer(), while an infinite side remains unbounded. Unlike an integer variable, if the bound contains no integer, its normalized bound becomes[0, 0], preserving the zero alternative in the semi-integer domain. Returns anAttachedDecisionVariablethat can be used directly in expressions.Args:
name: Optional human-readable modeling name. Names need not be unique.lower: Lower bound of the variable.upper: Upper bound of the variable.subscripts: Optional integer indices from the source model.parameters: Optional string-valued indices from the source model.description: Optional human-readable description.atol: Absolute tolerance for integer-bound normalization. If omitted, the current default returned byget_default_atol()is used.
Raises
ValueErrorif the bounds are invalid or if no larger automatic ID can be assigned. On failure, no variable or modeling label is added to the instance.
- partial_evaluate(state: ToState, *, atol: Optional[float] = None) Instance#
Creates a new instance by fixing decision variables from a supplied state.
This method validates supplied values, selects their Instance-owned representations under the rules below, and substitutes the specified decision variables. This creates a new problem instance where these variables are fixed and 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
Args:
state: Maps decision variable IDs to their fixed values. Can be aStateobject or a dictionary mapping variable IDs to values.atol: Absolute tolerance for floating point comparisons. If None, uses the default tolerance.
Returns: A new instance with the specified decision variables fixed to values selected by the canonicalization and ownership rules below.
Postconditions#
After the existing kind and bound validation, accepted supplied coordinates use the same Instance-owned canonicalization rules as
populate_state()before propagation and substitution. Non-fixed, non-dependent Continuous and SemiContinuous values are not rounded. Existing fixed values remain authoritative, dependent inputs remain consistency assertions, and values outside the existing partial-evaluation acceptance contract remain rejected. The new instance rewrites only active expressions while retaining canonical fixed values for output evaluation.>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=3 * x, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.convert_active_objective(Sense.Minimize) >>> fixed = instance.partial_evaluate({0: 1.0000005}, atol=1e-6) >>> assert instance.required_ids() == {0} >>> assert fixed.required_ids() == set() >>> assert fixed.objective.evaluate({}) == -3.0 >>> assert fixed.attached_decision_variable(0).substituted_value == 1.0 >>> solution = fixed.evaluate({}) >>> assert (solution.sense, solution.objective) == (Sense.Maximize, 3.0)
- penalty_method() ParametricInstance#
Convert to a parametric unconstrained instance by penalty method.
Roughly, this converts a constrained problem:
\[\min_x f(x) \quad \text{s.t.} \quad g_i(x) = 0 \; (\forall i), \quad h_j(x) \leq 0 \; (\forall j)\]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 constraints are stored in
removed_constraints.Note: 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().Postconditions#
Penalty conversion preserves existing output semantics, or captures the pre-penalty active objective when no output objective exists; materialization evaluates the penalty energy actively and invalidates optimality transport.
>>> from ommx import DecisionVariable, Instance, Optimality, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=x, constraints={7: x == 1}, sense=Sense.Minimize ... ) >>> parametric = instance.penalty_method() >>> parameters = {parameter.id: 2.0 for parameter in parametric.parameters} >>> prepared = parametric.with_parameters(parameters) >>> assert parametric.constraints == {} >>> assert 7 in parametric.removed_constraints >>> assert prepared.objective.evaluate({0: 0}) == 2.0 >>> solution = prepared.evaluate({0: 0}) >>> assert (solution.sense, solution.objective, solution.feasible) == (Sense.Minimize, 0.0, False) >>> assert prepared.map_active_optimality(Optimality.Optimal) == Optimality.Unspecified
- populate_state(state: ToState, *, atol: Optional[float] = None) State#
Canonicalize a solver state and populate fixed, irrelevant, and dependent decision variables.
The input state must contain all decision variables that are actually used by this instance's objective and active constraints. The returned
Statecontains every decision variable in the instance. For finite supplied coordinates that are neither fixed nor dependent, Binary values at mostatolaway from zero or one and Integer or SemiInteger values at mostatolaway from an integer are represented exactly. Derived dependent values use the same target-kind rule. Continuous and SemiContinuous values are not rounded. Other finite solver values remain available for feasibility checks. A caller-provided fixed or dependent value is a consistency assertion; after validation, the returned state uses the stored fixed value unchanged or the canonicalized derived dependent value. Dependencies consume canonicalized non-fixed, non-dependent inputs. A supplied dependent assertion is compared with the value derived from those inputs before target-kind canonicalization, so an assertion computed from the uncanonicalized solver vector can be rejected as inconsistent.Postconditions#
The returned state restores fixed variables needed only by preserved output semantics.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=3 * x, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.convert_active_objective(Sense.Minimize) >>> fixed = instance.partial_evaluate({0: 1}) >>> assert fixed.populate_state({}).entries == {0: 1.0} >>> assert fixed.evaluate({}).objective == 3.0
- prepare(input_class: InstanceClass, policy: PreparationPolicy) None#
Apply the caller's
policyto this instance in place to reachinput_classmembership.Selected phases are applied at most once in this order, stopping as soon as membership is reached:
special_constraints:lower_special_constraints()objective:convert_active_objective()integer_slack:convert_inequality_to_equality_with_integer_slack(), followed byadd_integer_slack_to_inequality()only when exact conversion is unavailable andslack_upper_boundis setfixed_penaltyinteger_encoding:log_encode()binary_power_reduction:reduce_binary_power()
Success guarantees
input_classmembership. Wheninput_classis an Adapter'sINPUT_CLASS, that membership is the complete applicability condition; converter-local or backend failures may still occur later. This operation is not transactional, so an error may leave the instance changed.PreparationTargetNotReachedErrorexposes the final membership report when the selections do not reachinput_class.Postconditions#
Selected owner operations establish their own output semantics, and successful composition reaches the target class.
>>> from ommx import DecisionVariable, Instance, InstanceClass, Optimality, PreparationPolicy, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=x, constraints={7: x == 1}, sense=Sense.Maximize ... ) >>> policy = PreparationPolicy.for_qubo(uniform_penalty_weight=2.0) >>> assert instance.prepare(InstanceClass.qubo(), policy) is None >>> assert InstanceClass.qubo().contains(instance) >>> assert instance.sense == Sense.Minimize >>> assert instance.objective.evaluate({0: 0}) == 2.0 >>> solution = instance.evaluate({0: 0}) >>> assert (solution.sense, solution.objective) == (Sense.Maximize, 0.0) >>> assert instance.map_active_optimality(Optimality.Optimal) == Optimality.Unspecified
- promote_sos1_big_m(request: Sos1BigMPromotionRequest, *, mode: Literal["best_effort", "strict"] = 'best_effort') Sos1BigMPromotion#
Validate and promote a batch of claimed SOS1 Big-M formulations.
The batch request is keyed by regular cardinality constraint ID. The Rust
Instancechecks every formulation against the same unchanged instance and reconciles conflicts before applying the plan. Bounds are inferred using only each formulation's claimed Big-M links. Successful entries apply their prepared bounds together with promotion; rejected entries contribute no bound changes, and strict rejection leaves the entire instance unchanged. Both modes return oneSos1BigMPromotionbatch report:mode="best_effort"(default) applies independent valid formulations. The report'spromotedmap associates each successful cardinality ID with its allocated SOS1 ID;rejectionsmaps rejected cardinality IDs to diagnostic strings.mode="strict"requires every formulation to be valid. Any rejection raisesSos1BigMPromotionBatchRejectedErrorbefore mutation. The exception'srequest_countis the batch size andrejectionscontains all rejected cardinality IDs and their diagnostics. A successful strict report has no rejections.
Overlapping formulation rows are rejected; independent formulations may share SOS1 members. An empty request returns an empty report. Planning and application do not clone the instance.
Promotion preserves the objective and mathematical feasible region on original members after projecting out fresh selectors. Positive link scaling is allowed; Big-M must cover the prepared member bounds exactly. Bounds are derived directly from exact link-coefficient ratios without tolerance expansion. Integer limits are rounded inward; conversion to floating-point bounds rounds outward. Exact clipping retains even sub-tolerance updates and does not read the default tolerance. Coverage validation is exact. Promotion does not promise identical violations or feasibility classification at finite tolerance. Unknown mode strings raise
ValueErrorbefore planning.
- random_samples(rng: Rng, *, num_different_samples: int = 5, num_samples: int = 10, max_sample_id: Optional[int] = 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.Args:
rng: Random number generatornum_different_samples: Number of different states to generatenum_samples: Total number of samples to generatemax_sample_id: Maximum sample ID (default:num_samples)
Returns: Samples object
Raises
ValueErrorif the requested state groups cannot partition the samples or the inclusive sample-ID range is too small.num_different_samples=0is valid only whennum_samples=0.Examples#
Generate samples for a simple instance:
>>> from ommx import DecisionVariable, Instance, Rng, Sense >>> x = [DecisionVariable.binary(i) for i in range(3)] >>> instance = Instance.from_components( ... decision_variables=x, ... objective=sum(x), ... constraints={0: sum(x) <= 2}, ... sense=Sense.Maximize, ... )
>>> rng = Rng() >>> samples = instance.random_samples(rng, num_different_samples=2, num_samples=5) >>> samples.num_samples() 5
- random_state(rng: 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 usage. Generated values respect the bounds of each variable type.
Args:
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.
Examples#
Generate random state only for used variables
>>> from ommx import DecisionVariable, Instance, Rng, Sense >>> x = [DecisionVariable.binary(i) for i in range(5)] >>> instance = Instance.from_components( ... decision_variables=x, ... objective=x[0] + x[1], ... constraints={}, ... sense=Sense.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
- 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 \geq 1\), so we can reduce higher powers to linear terms.
Returns:
Trueif any reduction was performed,Falseotherwise.Postconditions#
Reduction preserves existing output semantics, or captures the pre-reduction active objective when no output objective exists, while rewriting active expressions.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=x * x * x, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.reduce_binary_power() >>> assert instance.objective.evaluate({0: 1}) == 1.0 >>> solution = instance.evaluate({0: 1}) >>> assert (solution.sense, solution.objective) == (Sense.Maximize, 1.0) >>> assert not instance.reduce_binary_power()
- relax_constraint(constraint_id: int, reason: str, **parameters: str) None#
Remove a constraint from the instance.
The removed constraint is stored in
removed_constraints, and can be restored byrestore_constraint().Args:
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 import DecisionVariable, Instance, Sense >>> x = [DecisionVariable.binary(i) for i in range(3)] >>> instance = Instance.from_components( ... decision_variables=x, ... objective=sum(x), ... constraints={1: sum(x) == 3}, ... sense=Sense.Maximize, ... ) >>> assert set(instance.constraints) == {1}
>>> instance.relax_constraint(1, "manual relaxation") >>> assert not instance.constraints >>> assert set(instance.removed_constraints) == {1}
>>> instance.restore_constraint(1) >>> assert set(instance.constraints) == {1} >>> assert not instance.removed_constraints
- relax_indicator_constraint(constraint_id: int, reason: str, **parameters: str) None#
Relax an indicator constraint by moving it from active to removed.
- required_ids() set[int]#
Get the decision variable IDs required by the active formulation.
Postconditions#
IDs referenced only by preserved output semantics are not required solver inputs.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=x, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.convert_active_objective(Sense.Minimize) >>> fixed = instance.partial_evaluate({0: 1}) >>> assert fixed.required_ids() == set() >>> assert fixed.evaluate({}).objective == 1.0
- restore_constraint(constraint_id: int) None#
- restore_indicator_constraint(constraint_id: int) None#
Restore a removed indicator constraint back to active.
- save_mps(path: str, compress: bool = True) None#
- 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 } }Examples#
>>> from ommx import Instance >>> instance = Instance.minimize() >>> stats = instance.stats() >>> stats["decision_variables"]["total"] 0 >>> stats["constraints"]["total"] 0
- substitute(assignments: Mapping[int, ToFunction]) None#
Substitute decision variables with function expressions (in-place).
Replaces each given decision variable with the provided function in the objective and all active constraints. This is the general substitution mechanism behind
log_encode(), exposed so that users can implement their own integer encodings (e.g. unary, one-hot).Args:
assignments: A dict mapping decision variable IDs to the function expressions that should replace them.
Important: This method performs an algebraic rewrite. It does not automatically translate the substituted variable's bound or kind into constraints on the replacement expression. For example, substituting a binary variable
xwithy + zdoes not add0 <= y + z <= 1, and substituting an integer variable does not ensure that the replacement expression is integral. If the substitution must preserve the optimization problem, the caller must provide a domain-preserving encoding or add the required linking and bound constraints explicitly.Raises
ValueErroron cyclic or recursive assignments, or when substituting a variable that is a member of an indicator, one-hot, or SOS1 constraint.Postconditions#
Substitution rewrites the active objective while output evaluation restores the substituted variable value.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> b = DecisionVariable.binary(1) >>> instance = Instance.from_components( ... decision_variables=[x, b], objective=x, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.convert_active_objective(Sense.Minimize) >>> instance.substitute({0: b}) >>> assert instance.required_ids() == {1} >>> assert instance.objective.evaluate({1: 1}) == -1.0 >>> solution = instance.evaluate({1: 1}) >>> assert (solution.sense, solution.objective) == (Sense.Maximize, 1.0)
- tighten_bounds_simultaneously_once(*, max_terms: int = 32, atol: Optional[float] = None) dict[int, Bound]#
Apply one simultaneous bound-tightening pass using all active regular constraints.
Calls
tighten_bounds_simultaneously_once_using_constraints()with every active regular constraint ID and the samemax_termslimit. Non-affine rows and rows exceedingmax_termsvariable terms are skipped. All rows read the bounds at entry, and updates are applied together once. Returns a dictionary of variable IDs to their updatedBound. The same tolerance, supported-domain and atomicity rules apply as for the explicitly selected form.max_termsdefaults to 32 and excludes the constant term.
- tighten_bounds_simultaneously_once_using_constraints(constraint_ids: set[int], *, max_terms: int = 32, atol: Optional[float] = None) dict[int, Bound]#
Apply one simultaneous bound-tightening pass using selected regular constraints.
constraint_idsis a set of active regular constraint IDs. Unknown and removed IDs are errors; an empty set applies no updates. Selected non-affine rows are skipped. All eligible variables in the selected rows may have their bounds tightened. Only compact polynomial functions of degree at most one are used; composed expressions are skipped even when mathematically affine.max_termslimits each affine row to this many variable terms (default: 32). The constant term does not count. Rows exceeding the limit are skipped before domain lookup or candidate evaluation. Terms of fixed, semi and dependent variables still count. With a zero limit, only constant rows are processed, including contradiction detection.Returns a dictionary of variable IDs to their updated
Bound. Every row reads the bounds at entry; all updates are collected and applied together. Newly tightened bounds are not reused during this call. Call again to propagate changes through further rows. Candidate extrema are combined before comparing changes withatol. When continuous stored endpoints cross but their tolerated domains overlap, use the interval between the endpoints, intersected with the entry bounds. Both sides of equalities are used. Tolerance is accounted for algebraically: continuous domains expand to[lower - atol, upper + atol], and row residuals may be at mostatol. Fora*x + r <= 0witha > 0, the limit is(atol - min(r)) / a. Subtractatolto store a continuous upper bound, or round down for an integer/binary upper bound. Negative coefficients give the corresponding lower bound. Changes withinatolare ignored.Residual intervals use
evaluate_bound(); candidate arithmetic uses ordinary floating-point operations. No point-evaluation boundary is searched. Rounding, cancellation and evaluation order can change feasibility near numerical boundaries, even when subsequent evaluation uses the sameatol.Only the selected active regular constraints are used. Unbounded domains remain infinite. An upper/lower candidate with a non-finite residual or boundary calculation is skipped; other candidates in the same row are still processed. Semi-variable domains include zero when deriving other bounds, but semi, fixed and dependent variables are not changed. This is not a complete infeasibility detector.
Requires finite
atol < 1. On failure the instance is unchanged. If omitted,get_default_atol()supplies the tolerance.
- to_hubo(*, uniform_penalty_weight: Optional[float] = None, penalty_weights: Optional[Mapping[int, float]] = None, inequality_integer_slack_max_range: int = 31) tuple[dict, float]#
Convert the instance to a HUBO format.
Postconditions#
The driver is equivalent to HUBO Preparation followed by active-objective formatting and preserves the output semantics present on entry.
>>> import copy >>> from ommx import DecisionVariable, Instance, InstanceClass, PreparationPolicy, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=x, constraints={7: x == 1}, sense=Sense.Maximize ... ) >>> explicit = copy.copy(instance) >>> policy = PreparationPolicy.for_hubo(uniform_penalty_weight=2.0) >>> _ = explicit.prepare(InstanceClass.hubo(), policy) >>> expected = explicit.as_hubo_format() >>> actual = instance.to_hubo(uniform_penalty_weight=2.0) >>> assert actual == expected >>> assert InstanceClass.hubo().contains(instance) >>> assert instance.sense == Sense.Minimize >>> assert instance.objective.evaluate({0: 0}) == 2.0 >>> solution = instance.evaluate({0: 0}) >>> assert (solution.sense, solution.objective) == (Sense.Maximize, 0.0)
Errors#
Mutually exclusive penalty options raise
ValueErrorbefore mutating the instance.>>> unchanged = Instance.from_components( ... decision_variables=[x], objective=x, constraints={7: x == 1}, sense=Sense.Maximize ... ) >>> before = unchanged.to_v2_bytes() >>> try: ... unchanged.to_hubo(uniform_penalty_weight=1.0, penalty_weights={7: 2.0}) ... except ValueError: ... pass ... else: ... raise AssertionError("mutually exclusive penalty options were accepted") >>> assert unchanged.to_v2_bytes() == before
- to_qubo(*, uniform_penalty_weight: Optional[float] = None, penalty_weights: Optional[Mapping[int, float]] = None, inequality_integer_slack_max_range: int = 31) tuple[dict, float]#
Convert the instance to a QUBO format.
Postconditions#
The driver is equivalent to QUBO Preparation followed by active-objective formatting and preserves the output semantics present on entry.
>>> import copy >>> from ommx import DecisionVariable, Instance, InstanceClass, PreparationPolicy, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=x, constraints={7: x == 1}, sense=Sense.Maximize ... ) >>> explicit = copy.copy(instance) >>> policy = PreparationPolicy.for_qubo(uniform_penalty_weight=2.0) >>> _ = explicit.prepare(InstanceClass.qubo(), policy) >>> expected = explicit.as_qubo_format() >>> actual = instance.to_qubo(uniform_penalty_weight=2.0) >>> assert actual == expected >>> assert InstanceClass.qubo().contains(instance) >>> assert instance.sense == Sense.Minimize >>> assert instance.objective.evaluate({0: 0}) == 2.0 >>> solution = instance.evaluate({0: 0}) >>> assert (solution.sense, solution.objective) == (Sense.Maximize, 0.0)
Errors#
Mutually exclusive penalty options raise
ValueErrorbefore mutating the instance.>>> unchanged = Instance.from_components( ... decision_variables=[x], objective=x, constraints={7: x == 1}, sense=Sense.Maximize ... ) >>> before = unchanged.to_v2_bytes() >>> try: ... unchanged.to_qubo(uniform_penalty_weight=1.0, penalty_weights={7: 2.0}) ... except ValueError: ... pass ... else: ... raise AssertionError("mutually exclusive penalty options were accepted") >>> assert unchanged.to_v2_bytes() == before
- to_v1_bytes() bytes#
Serialize this instance in the OMMX v1 wire format.
Errors#
Serialization raises
RuntimeErrorwhenever an output objective is present because v1 cannot represent it.>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=x, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.convert_active_objective(Sense.Minimize) >>> assert instance.convert_active_objective(Sense.Maximize) >>> try: ... instance.to_v1_bytes() ... except RuntimeError: ... pass ... else: ... raise AssertionError("v1 serialization accepted an output objective")
- to_v2_bytes() bytes#
Serialize this instance in the OMMX v2 wire format.
Postconditions#
A v2 round-trip preserves both active and output objective semantics.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=3 * x, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.convert_active_objective(Sense.Minimize) >>> restored = Instance.from_v2_bytes(instance.to_v2_bytes()) >>> assert restored.sense == Sense.Minimize >>> assert restored.objective.evaluate({0: 1}) == -3.0 >>> solution = restored.evaluate({0: 1}) >>> assert (solution.sense, solution.objective) == (Sense.Maximize, 3.0)
- unary_encode(decision_variable_ids: set[int] = set(), *, max_range: int = 16, atol: Optional[float] = None) None#
Unary-encode the integer decision variables.
Unary encoding of an integer variable \(x \in [l, u]\) is to represent it by \(u - l\) bits \(b_j \in \{0, 1\}\):
\[x = l + \sum_j b_j\]Every bit configuration maps to a valid integer in the original range, so no encoding-validity penalty or linking constraint is added. This costs linearly many auxiliary variables, so use it for narrow integer ranges.
Args:
decision_variable_ids: The IDs of the integer decision variables to unary-encode. If not specified (or empty), all used integer variables are unary-encoded.max_range: Maximum allowedupper - lowerrange for each encoded variable. This also bounds the number of auxiliary binary variables introduced per integer variable.atol: Optional absolute tolerance used when normalizing integer bounds before encoding. If None, uses the default tolerance.
Postconditions#
Encoding preserves existing output semantics, or captures the pre-encoding active objective when no output objective exists, while rewriting the active objective.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.integer(0, lower=2, upper=5, name="x") >>> instance = Instance.from_components( ... decision_variables=[x], objective=x, constraints={}, sense=Sense.Maximize ... ) >>> instance.unary_encode({0}) >>> encoded_ids = instance.required_ids() >>> assert len(encoded_ids) == 3 >>> state = {variable_id: 1 for variable_id in encoded_ids} >>> assert instance.objective.evaluate(state) == 5.0 >>> solution = instance.evaluate(state) >>> assert (solution.sense, solution.objective) == (Sense.Maximize, 5.0)
- uniform_penalty_method() ParametricInstance#
Convert to a parametric unconstrained instance by penalty method with uniform weight.
Roughly, this converts a constrained problem:
\[\min_x f(x) \quad \text{s.t.} \quad g_i(x) = 0 \; (\forall i), \quad h_j(x) \leq 0 \; (\forall j)\]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 constraints are stored in
removed_constraints.Note: 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().Postconditions#
Uniform-penalty conversion preserves existing output semantics, or captures the pre-penalty active objective when no output objective exists; materialization evaluates the penalty energy actively and invalidates optimality transport.
>>> from ommx import DecisionVariable, Instance, Optimality, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=x, constraints={7: x == 1}, sense=Sense.Minimize ... ) >>> parametric = instance.uniform_penalty_method() >>> parameter_id = parametric.parameters[0].id >>> prepared = parametric.with_parameters({parameter_id: 2.0}) >>> assert parametric.constraints == {} >>> assert 7 in parametric.removed_constraints >>> assert prepared.objective.evaluate({0: 0}) == 2.0 >>> solution = prepared.evaluate({0: 0}) >>> assert (solution.sense, solution.objective, solution.feasible) == (Sense.Minimize, 0.0, False) >>> assert prepared.map_active_optimality(Optimality.Optimal) == Optimality.Unspecified
- variable_labels_df() DataFrame#
Decision-variable modeling-label DataFrame (id-indexed wide format).
Columns:
name,subscripts,description. Index column =variable_id.
- variable_parameters_df() DataFrame#
Decision-variable parameters DataFrame (long format).
One row per (variable_id, parameter_key) pair. Columns:
variable_id,key,value.
- Description: type[InstanceDescription]#
- MAXIMIZE: Sense#
Compatibility alias for
Sense.Maximize.
- MINIMIZE: Sense#
Compatibility alias for
Sense.Minimize.
- property active_special_constraint_kinds: set[SpecialConstraintKind]#
Read-only property.
The kinds of active special constraints this instance currently uses.
Returns the set of :class:
SpecialConstraintKindvalues corresponding to non-empty active (non-removed) special constraint collections. An empty set means the instance has no active special constraints.
- property annotations: MappingProxyType[str, str]#
Read-only property.
Returns a read-only mapping of flat annotations.
Use
add_user_annotation(), metadata properties, orreplace_annotations()to modify annotations.
- property authors: list[str]#
- property constraints: dict[int, AttachedConstraint]#
Read-only property.
Dict of all active constraints in the instance keyed by their IDs.
Each value is an
AttachedConstraint: a write-through handle whose getters read from this instance's SoA store and whose context setters write back through to it. Usedetach()to materialize aConstraintsnapshot if you need an independent copy.
- property decision_variable_names: set[str]#
Read-only property.
Get all unique decision variable names in this instance
- property decision_variables: list[AttachedDecisionVariable]#
Read-only property.
List of all decision variables in the instance sorted by their IDs.
Returns a list of
AttachedDecisionVariablewrite-through handles. Each handle reads its kind / bound / label live from this instance's SoA store and writes label updates back through to it. Handles also participate in arithmetic to build expressions (x + y,2 * xetc.) — only their id is consumed for that, no host borrow is taken. Calldetach()if you need an independentDecisionVariablesnapshot.
- property indicator_constraints: dict[int, AttachedIndicatorConstraint]#
Read-only property.
Dict of all active indicator constraints in the instance keyed by their IDs.
Each value is an
AttachedIndicatorConstraint: a write-through handle whose getters read from this instance's SoA store and whose context setters write back through to it.
- property named_function_names: set[str]#
Read-only property.
Get all unique named function names in this instance
- property named_functions: list[NamedFunction]#
Read-only property.
List of all named functions in the instance sorted by their IDs.
- property objective: Function#
Active objective used by the solver-facing formulation.
Postconditions#
Assignment replaces the active objective and rebases subsequent output evaluation onto it.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=x, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.convert_active_objective(Sense.Minimize) >>> assert instance.objective.evaluate({0: 1}) == -1.0 >>> instance.objective = 2 * x >>> solution = instance.evaluate({0: 1}) >>> assert instance.sense == Sense.Minimize >>> assert (solution.sense, solution.objective) == (Sense.Minimize, 2.0)
- property one_hot_constraints: dict[int, AttachedOneHotConstraint]#
Read-only property.
Dict of all active one-hot constraints in the instance keyed by their IDs.
Each value is an
AttachedOneHotConstraint: a write-through handle whose getters read from this instance's SoA store and whose context setters write back through to it.
- property output_objective: Optional[OutputObjective]#
Read-only property.
Read-only output objective used by
evaluate()andevaluate_samples(), if one has been captured.Postconditions#
Absence and an explicit output objective equal to the active pair remain distinct.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=3 * x, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.output_objective is None >>> assert instance.convert_active_objective(Sense.Minimize) >>> output = instance.output_objective >>> assert output is not None >>> assert output.sense == Sense.Maximize >>> assert output.function.evaluate({0: 1}) == 3.0 >>> assert output.preserves_optimality >>> assert instance.convert_active_objective(Sense.Maximize) >>> output = instance.output_objective >>> assert output is not None >>> assert output.sense == instance.sense >>> assert output.function.almost_equal(instance.objective)
- property removed_constraints: dict[int, RemovedConstraint]#
Read-only property.
Dict of all removed constraints in the instance keyed by their IDs.
- property removed_indicator_constraints: dict[int, RemovedIndicatorConstraint]#
Read-only property.
Dict of all removed indicator constraints in the instance keyed by their IDs.
- property removed_one_hot_constraints: dict[int, RemovedOneHotConstraint]#
Read-only property.
Dict of all removed one-hot constraints in the instance keyed by their IDs.
- property removed_sos1_constraints: dict[int, RemovedSos1Constraint]#
Read-only property.
Dict of all removed SOS1 constraints in the instance keyed by their IDs.
- property sense: Sense#
Read-only property.
Active optimization sense used by the solver-facing formulation.
Postconditions#
The property reports the active sense even when evaluation uses a distinct output sense.
>>> from ommx import DecisionVariable, Instance, Sense >>> x = DecisionVariable.binary(0) >>> instance = Instance.from_components( ... decision_variables=[x], objective=x, constraints={}, sense=Sense.Maximize ... ) >>> assert instance.convert_active_objective(Sense.Minimize) >>> assert instance.sense == Sense.Minimize >>> assert instance.evaluate({0: 1}).sense == Sense.Maximize
- property sos1_constraints: dict[int, AttachedSos1Constraint]#
Read-only property.
Dict of all active SOS1 constraints in the instance keyed by their IDs.
Each value is an
AttachedSos1Constraint: a write-through handle whose getters read from this instance's SoA store and whose context setters write back through to it.
- property used_decision_variables: list[DecisionVariable]#
Read-only property.