ParametricInstance

ParametricInstance#

class ParametricInstance#

A family of optimization problems with fixed variables and constraints.

A ParametricInstance leaves some coefficients or constants symbolic as Parameter objects. The model’s caller supplies their values with with_parameters() to obtain an ordinary Instance for evaluation, preparation, or a solver adapter. Parameters are inputs chosen before solving; they are not decision variables for the solver to optimize.

Reuse a problem under different numerical conditions

Here the two items and their scores stay fixed, while the allowed number of selected items changes. The original family remains available after each call to with_parameters.

>>> from ommx import ConstraintID, DecisionVariable, Parameter, ParametricInstance, Sense
>>> xs = [DecisionVariable.binary(i, name="x", subscripts=[i]) for i in range(2)]
>>> capacity = Parameter(2, name="capacity", description="Maximum selected items")
>>> family = ParametricInstance.from_components(
...     decision_variables=xs, parameters=[capacity],
...     objective=3 * xs[0] + 2 * xs[1],
...     constraints={0: xs[0] + xs[1] <= capacity}, sense=Sense.Maximize,
... )
>>> one_item = family.with_parameters({capacity.id: 1})
>>> two_items = family.with_parameters({capacity.id: 2})
>>> candidate = {xs[0].id: 1, xs[1].id: 1}
>>> assert not one_item.evaluate(candidate).feasible
>>> assert two_items.evaluate(candidate).feasible
>>> assert two_items.evaluate(candidate).objective == 5.0
>>> assert {v.id for v in one_item.decision_variables} == {v.id for v in two_items.decision_variables}
>>> assert set(one_item.constraints) == set(two_items.constraints) == {ConstraintID(0)}
>>> assert family.parameter_ids == {capacity.id}

Materialization preserves decision-variable and constraint IDs. Those IDs let callers associate results with the same items and conditions across instances; they do not imply identical feasibility or optimal solutions. Parameters can also appear as objective and constraint coefficients, as in the weighted knapsack example in from_components().

Parameter identities and modeling labels

Parameters and decision variables use one ID space and must not overlap. Here the decision variables have IDs 0 and 1, and the parameter has ID 2. Both can appear in arithmetic expressions, but only decision variables receive candidate values after materialization. Parameter names, subscripts, string-valued metadata, and descriptions describe model inputs; the IDs identify the numerical values passed to with_parameters.

parameters and parameter_ids list declared parameters. parameters_df() collects their definitions and labels into a DataFrame, not a table of supplied numerical values.

>>> table = family.parameters_df()
>>> assert table.loc[capacity.id, "name"] == "capacity"
>>> assert table.loc[capacity.id, "description"] == "Maximum selected items"

decision_variables_df() and constraints_df() provide the corresponding model tables; the latter requires an explicit kind. For required IDs, extra keys, and numerical substitution rules, read with_parameters().

When to rebuild the model

Parameter values do not generate variable or constraint definitions. If an input changes the number of items or the model’s structure, build new components in Python or use a modeler that generates that structure. This differs from changing numerical coefficients within an existing family. No Experiment is needed to construct or materialize a ParametricInstance. Use Experiment separately when recording the conditions and results of multiple trials is useful.

__copy__() → ParametricInstance#
__deepcopy__(_memo: Any) → ParametricInstance#
__repr__() → str#
__str__() → str#
add_constraint(constraint: Constraint) → AttachedConstraint#

Add a regular constraint to this parametric instance.

Picks an unused ConstraintID, drains the wrapper’s context snapshot into this parametric instance’s SoA store, and returns an AttachedConstraint bound to the new id. The input Constraint is not mutated; subsequent writes that should land on this parametric instance must go through the returned handle.

Raises ValueError if the constraint references an id that is neither a defined decision variable nor a defined parameter, or if it references an id currently used as a substitution-dependency key.

add_decision_variable(variable: DecisionVariable) → AttachedDecisionVariable#

Add a decision variable to this parametric instance. Returns an AttachedDecisionVariable bound to the variable’s id — a write-through handle for further label updates.

Raises ValueError if the variable’s id collides with an existing decision variable or parameter. 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 parametric instance.

Picks an unused IndicatorConstraintID, drains the wrapper’s context snapshot into this parametric instance’s SoA store, and returns an AttachedIndicatorConstraint bound to the new id.

Raises ValueError if the constraint references an id that is neither a defined decision variable nor a defined parameter, or if it references an id currently used as a substitution-dependency key.

add_one_hot_constraint(constraint: OneHotConstraint) → AttachedOneHotConstraint#

Add a one-hot constraint to this parametric instance.

add_sos1_constraint(constraint: Sos1Constraint) → AttachedSos1Constraint#

Add a SOS1 constraint to this parametric 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#
attached_decision_variable(variable_id: int) → AttachedDecisionVariable#

Look up the AttachedDecisionVariable for the given id — a write-through handle.

constraint_context_df(*, kind: ConstraintKind[ConstraintIDBase]) → DataFrame#

Constraint context DataFrame (id-indexed). See ommx.Instance.constraint_context_df() for column / kind= semantics.

constraint_parameters_df(*, kind: ConstraintKind[ConstraintIDBase]) → DataFrame#

Constraint parameters DataFrame (long format).

constraint_provenance_df(*, kind: ConstraintKind[ConstraintIDBase]) → DataFrame#

Constraint provenance DataFrame (long format).

constraint_removed_reasons_df(*, kind: ConstraintKind[ConstraintIDBase]) → DataFrame#

Removed-constraint reasons DataFrame (long format).

constraints_df(*, kind: ConstraintKind[ConstraintIDBase], include: Optional[Sequence[str]] = None, removed: bool = False) → DataFrame#

DataFrame of constraints, dispatched on kind=. See ommx.Instance.constraints_df() for column / kind= / include= / removed= semantics.

decision_variables_df(include: Optional[Sequence[str]] = None) → DataFrame#

DataFrame of decision variables

display_function(function: builtins.int | builtins.float | numpy.integer | numpy.floating | DecisionVariable | AttachedDecisionVariable | Parameter | Linear | Quadratic | Polynomial | Function, max_terms: Optional[int] = 100, max_chars: Optional[int] = 20000) → FunctionDisplay#

Build a bounded notebook display object for a function in this parametric instance’s context.

By default this returns a preview capped at 100 complete terms and 20,000 characters. Pass None to disable either limit. Parameter labels are available before materialization, just as in format_function().

The returned FunctionDisplay has the same preview text and truncation metadata as display_function(). Use format_function without limits for a complete plain text string.

>>> from ommx import DecisionVariable, Parameter, ParametricInstance, Sense
>>> x = DecisionVariable.binary(0, name="x")
>>> p = Parameter(100, name="p")
>>> model = ParametricInstance.from_components(
...     sense=Sense.Minimize, objective=x + p, decision_variables=[x],
...     parameters=[p], constraints={},
... )
>>> preview = model.display_function(model.objective)
>>> assert preview.text == "x + p"
>>> assert not preview.truncated
empty() → ParametricInstance#

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

extract_constraints(name: str) → dict[tuple[int, ...], ConstraintView]#

Extract active constraints of every family by name, keyed by subscript tuples.

Values are attached handles, just as in constraints: edits update this model. The returned dictionary snapshots the matching keys; it does not track later label or membership changes. Removed constraints are excluded. Parameters and descriptions do not participate in matching.

Raises KeyError if no active constraint has this name, and ValueError if matching constraints share subscripts, including across different families. Use typed-ID lookup in constraints to distinguish ambiguous labels.

format_function(function: builtins.int | builtins.float | numpy.integer | numpy.floating | DecisionVariable | AttachedDecisionVariable | Parameter | Linear | Quadratic | Polynomial | Function, max_terms: Optional[int] = None, max_chars: Optional[int] = None) → str#

Format a function using this parametric instance’s decision-variable and parameter labels.

The plain repr() / str representation of Function remains context-free and renders raw x<id> symbols such as x1. This method resolves both variable and parameter labels before any parameter values have been supplied.

>>> from ommx import DecisionVariable, Parameter, ParametricInstance, Sense
>>> x = DecisionVariable.binary(0, name="x")
>>> p = Parameter(100, name="p", parameters={"scenario": "base"})
>>> model = ParametricInstance.from_components(
...     sense=Sense.Minimize, objective=x + p, decision_variables=[x],
...     parameters=[p], constraints={},
... )
>>> assert model.format_function(model.objective) == "x + p[scenario=base]"

Every referenced ID must belong to exactly one of this instance’s decision variables or parameters. The lookup uses IDs, not names from another model, and validates them before applying output limits. max_terms and max_chars both default to None for no limit; their meaning is the same as in format_function(). For a preview with truncation metadata, use display_function().

from_components(*, sense: Sense, objective: builtins.int | builtins.float | numpy.integer | numpy.floating | DecisionVariable | AttachedDecisionVariable | Parameter | Linear | Quadratic | Polynomial | Function, decision_variables: Sequence[DecisionVariable], constraints: Mapping[ConstraintID, Constraint] | Mapping[int, Constraint] | ConstraintID | int | Constraint, parameters: Sequence[Parameter], indicator_constraints: Mapping[IndicatorConstraintID, IndicatorConstraint] | Mapping[int, IndicatorConstraint] | IndicatorConstraintID | int | IndicatorConstraint = None, one_hot_constraints: Mapping[OneHotConstraintID, OneHotConstraint] | Mapping[int, OneHotConstraint] | OneHotConstraintID | int | OneHotConstraint = None, sos1_constraints: Mapping[Sos1ConstraintID, Sos1Constraint] | Mapping[int, Sos1Constraint] | Sos1ConstraintID | int | Sos1Constraint = None, named_functions: Optional[Sequence[NamedFunction]] = None, description: Optional[InstanceDescription] = None) → ParametricInstance#

Build a parametric instance from its model components.

Constraint mappings accept integer keys or IDs of the matching family. Integers are promoted to that family’s ID. IDs of another family raise TypeError; keys that become duplicates raise ValueError. Returned collections always use typed IDs.

Raises ValueError if a decision variable and parameter use the same ID.

Parameterize objective coefficients, weights, and a capacity

This two-item knapsack family fixes the item count and uses parameters for both coefficients and the right-hand-side constant. Parameter IDs are separate from the decision-variable IDs in the shared ID space.

>>> from ommx import DecisionVariable, Parameter, ParametricInstance, Sense
>>> n = 2
>>> xs = [DecisionVariable.binary(i, name="x", subscripts=[i]) for i in range(n)]
>>> profits = [Parameter(n + i, name="profit", subscripts=[i]) for i in range(n)]
>>> weights = [Parameter(2 * n + i, name="weight", subscripts=[i]) for i in range(n)]
>>> capacity = Parameter(3 * n, name="capacity")
>>> family = ParametricInstance.from_components(
...     decision_variables=xs, parameters=profits + weights + [capacity],
...     objective=sum(p * x for p, x in zip(profits, xs)),
...     constraints={0: sum(w * x for w, x in zip(weights, xs)) <= capacity},
...     sense=Sense.Maximize,
... )
>>> values = {p.id: value for p, value in zip(profits, [3, 2])}
>>> values.update({w.id: value for w, value in zip(weights, [2, 1])})
>>> values[capacity.id] = 2
>>> instance = family.with_parameters(values)
>>> solution = instance.evaluate({xs[0].id: 1, xs[1].id: 0})
>>> assert solution.feasible and solution.objective == 3.0
>>> assert not instance.evaluate({xs[0].id: 1, xs[1].id: 1}).feasible
from_v1_bytes(bytes: bytes) → ParametricInstance#

Deserialize a parametric instance from v1 protobuf bytes.

Raises ValueError if the protobuf payload is malformed or semantically invalid.

from_v2_bytes(bytes: bytes) → ParametricInstance#

Deserialize a parametric instance from v2 protobuf bytes.

Raises ValueError if the protobuf payload is malformed or semantically invalid.

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_parameter_by_id(parameter_id: int) → Parameter#

Get a specific parameter 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]#
named_functions_df(include: Optional[Sequence[str]] = None) → DataFrame#

DataFrame of named functions

parameters_df(include: Optional[Sequence[str]] = None) → DataFrame#

Collect declared parameter definitions and modeling labels into a DataFrame.

The index is the parameter ID, named id. By default, the table includes name, subscripts, description, and flattened parameters.<key> metadata columns. include=[] omits optional columns; include=["label"] selects the modeling labels and include=["parameters"] selects the string-valued metadata.

These labels are not the numerical assignments supplied to with_parameters(). Editing the DataFrame does not change this ParametricInstance. See the class example for selecting a parameter’s name and description by ID.

replace_annotations(annotations: Mapping[str, str]) → None#
substitute(assignments: Mapping[int, builtins.int | builtins.float | numpy.integer | numpy.floating | DecisionVariable | AttachedDecisionVariable | Parameter | Linear | Quadratic | Polynomial | Function]) → 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. Replacement functions may still reference parameters; those parameter references remain in this parametric instance and are evaluated later by with_parameters(). Assignment targets must be existing decision variable IDs. Replacement expressions may only reference existing decision variable or parameter IDs; parameter IDs cannot be assignment targets.

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. 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 ValueError on cyclic or recursive assignments, undefined IDs, when a parameter ID is used as an assignment target, or when substituting a variable that is a member of an indicator, one-hot, or SOS1 constraint.

Postconditions#

Substitution rewrites active expressions while materialized output evaluation restores the substituted variable.

>>> from ommx import DecisionVariable, Instance, Sense
>>> x = DecisionVariable.binary(0)
>>> b = DecisionVariable.binary(1)
>>> source = Instance.from_components(
...     decision_variables=[x, b], objective=x, constraints={}, sense=Sense.Maximize
... )
>>> assert source.convert_active_objective(Sense.Minimize)
>>> parametric = source.as_parametric_instance()
>>> parametric.substitute({0: b})
>>> materialized = parametric.with_parameters({})
>>> assert materialized.required_ids() == {1}
>>> assert materialized.objective.evaluate({1: 1}) == -1.0
>>> solution = materialized.evaluate({1: 1})
>>> assert (solution.sense, solution.objective) == (Sense.Maximize, 1.0)
to_v1_bytes() → bytes#

Serialize this parametric instance in the OMMX v1 wire format.

Errors#

Serialization raises RuntimeError whenever 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)
>>> parametric = instance.as_parametric_instance()
>>> try:
...     parametric.to_v1_bytes()
... except RuntimeError:
...     pass
... else:
...     raise AssertionError("v1 serialization accepted an output objective")
to_v2_bytes() → bytes#

Serialize this parametric instance in the OMMX v2 wire format.

Postconditions#

A v2 round-trip preserves both active and output objective semantics through materialization.

>>> from ommx import DecisionVariable, Instance, ParametricInstance, Sense
>>> x = DecisionVariable.binary(0)
>>> source = Instance.from_components(
...     decision_variables=[x], objective=x, constraints={7: x == 1}, sense=Sense.Minimize
... )
>>> parametric = source.uniform_penalty_method()
>>> parameter_id = parametric.parameters[0].id
>>> restored = ParametricInstance.from_v2_bytes(parametric.to_v2_bytes())
>>> materialized = restored.with_parameters({parameter_id: 2.0})
>>> assert materialized.sense == Sense.Minimize
>>> assert materialized.objective.evaluate({0: 0}) == 2.0
>>> solution = materialized.evaluate({0: 0})
>>> assert (solution.sense, solution.objective) == (Sense.Minimize, 0.0)
variable_labels_df() → DataFrame#

Decision-variable modeling-label DataFrame (id-indexed).

variable_parameters_df() → DataFrame#

Decision-variable parameters DataFrame (long format).

with_parameters(parameters: Mapping[int, float]) → Instance#

Substitute parameters to yield an instance.

Provide a dict mapping every declared parameter ID to its numerical value, including parameters not referenced by an expression. The original ParametricInstance is unchanged and can be materialized again. Missing declared IDs raise RuntimeError.

The method checks that declared IDs are included, not that the key sets are equal. Additional keys are accepted and participate in substitution wherever referenced. In particular, an extra decision-variable ID can also substitute that coordinate in expressions. For parameter-only materialization, supply exactly the IDs listed by parameters.

Substitution uses the SDK’s default absolute tolerance and the expression partial-evaluation rules; see partial_evaluate(). This is not a feasibility check or decision-variable kind/bound validation. There is no blanket finiteness check of all supplied values: a referenced non-finite value can fail during substitution, while an unreferenced value need not be examined. Successful materialization does not prove that all supplied values are finite or that the resulting problem is feasible.

>>> from ommx import DecisionVariable, Parameter, ParametricInstance, Sense
>>> x = DecisionVariable.binary(0)
>>> p, unused = Parameter(1, name="p"), Parameter(2, name="unused")
>>> family = ParametricInstance.from_components(
...     decision_variables=[x], parameters=[p, unused], objective=x + p,
...     constraints={}, sense=Sense.Minimize
... )
>>> try:
...     family.with_parameters({p.id: 3})
... except RuntimeError as error:
...     assert "Missing parameters" in str(error)
... else:
...     raise AssertionError("an omitted declared parameter was accepted")
>>> concrete = family.with_parameters({p.id: 3, unused.id: 0})
>>> assert concrete.objective.evaluate({x.id: 1}) == 4.0

Postconditions#

Penalty conversion preserves existing output semantics, or captures the pre-penalty active objective when no output objective exists. Materialization substitutes parameters in the active energy, and an existing output objective remains explicit even if specialization makes it structurally equal to the active objective.

>>> from ommx import DecisionVariable, Instance, Sense
>>> x = DecisionVariable.binary(0)
>>> source = Instance.from_components(
...     decision_variables=[x], objective=x, constraints={7: x == 1}, sense=Sense.Minimize
... )
>>> parametric = source.uniform_penalty_method()
>>> parameter_id = parametric.parameters[0].id
>>> materialized = parametric.with_parameters({parameter_id: 2.0})
>>> assert materialized.objective.evaluate({0: 0}) == 2.0
>>> solution = materialized.evaluate({0: 0})
>>> assert (solution.sense, solution.objective, solution.feasible) == (Sense.Minimize, 0.0, False)
property annotations: MappingProxyType[str, str]#

Read-only property.

Returns a read-only mapping of flat annotations.

Use add_user_annotation(), metadata properties, or replace_annotations() to modify annotations.

property authors: list[str]#
property constraint_names: set[str]#

Read-only property.

Unique names across all constraint families, as a set of strings.

Only active constraints are included. The scope matches extract_constraints. Unnamed constraints are excluded; an explicitly empty name is included. Duplicate names appear once, even when extraction would reject duplicate subscripts. Each access returns a fresh set; editing it does not change this owner.

property constraints: ConstraintCollection#

Read-only property.

Live mapping of all active constraint families, keyed by typed IDs. The view retains this owner and fetches each constraint on demand.

property created: Optional[datetime]#
property dataset: Optional[str]#
property decision_variable_ids: set[int]#

Read-only property.

property decision_variable_names: set[str]#

Read-only property.

Unique decision-variable names as a set of strings, excluding parameters. Unnamed variables are excluded; explicitly empty names are included. Each access returns a fresh set; editing it does not change this owner.

property decision_variables: list[AttachedDecisionVariable]#

Read-only property.

List of all decision variables in the parametric instance sorted by their IDs.

Returns a list of AttachedDecisionVariable write-through handles. See decision_variables for the same shape on Instance.

property description: Optional[InstanceDescription]#

Read-only property.

property license: Optional[str]#
property named_functions: list[NamedFunction]#

Read-only property.

property num_constraints: Optional[int]#

Read-only property.

property num_variables: Optional[int]#

Read-only property.

property objective: Function#

Read-only property.

property output_objective: Optional[OutputObjective]#

Read-only property.

Read-only output objective retained through parameter materialization, if one has been captured.

Postconditions#

Conversion preserves both absence and an explicit output objective equal to the active pair.

>>> from ommx import DecisionVariable, Instance, Sense
>>> x = DecisionVariable.binary(0)
>>> source = Instance.from_components(
...     decision_variables=[x], objective=x, constraints={}, sense=Sense.Maximize
... )
>>> assert source.as_parametric_instance().output_objective is None
>>> assert source.convert_active_objective(Sense.Minimize)
>>> assert source.convert_active_objective(Sense.Maximize)
>>> parametric = source.as_parametric_instance()
>>> output = parametric.output_objective
>>> assert output is not None
>>> assert output.sense == parametric.sense
>>> assert output.function.almost_equal(parametric.objective)
property parameter_ids: set[int]#

Read-only property.

IDs of all declared parameters, including those unused in expressions.

with_parameters() requires a value for every ID in this set. The set is independent of the owner.

property parameters: list[Parameter]#

Read-only property.

Declared parameters and their modeling labels, sorted by ID.

These are independent definitions, not assigned numerical values. Supply values through with_parameters().

property removed_constraints: RemovedConstraintCollection#

Read-only property.

Live mapping of all removed constraint families, keyed by typed IDs. The view retains this owner and fetches each constraint on demand.

property sense: Sense#

Read-only property.

property title: Optional[str]#