Solution

Solution#

class Solution#

A candidate evaluated against a particular optimization problem.

A State maps variable IDs to values; it does not say whether those values satisfy a problem. A Solution adds the objective value, feasibility results, evaluated variables and constraints, and their modeling labels. Use evaluate() with a State or an ID-to-value dictionary to obtain one, whether the candidate came from a solver or was supplied manually.

Evaluate a candidate

This problem maximizes x + y while forbidding both binary variables from being 1. Evaluation checks one assignment; it does not search for a better assignment or prove optimality.

>>> from ommx import ConstraintKind, Instance, Optimality, State
>>> instance = Instance.maximize()
>>> x = instance.new_binary("x")
>>> y = instance.new_binary("y")
>>> instance.objective = x + y
>>> exclusive = instance.add_constraint(x * y == 0, "exclusive")
>>> state = State({x.id: 1, y.id: 0})
>>> solution = instance.evaluate(state)
>>> assert solution.state.entries == {x.id: 1.0, y.id: 0.0}
>>> assert solution.feasible
>>> assert solution.objective == 1.0
>>> assert solution.optimality == Optimality.Unspecified

Read feasibility, objective, and optimality separately

Property

Meaning

Does not establish

feasible

Variable domains and all retained constraints are satisfied

No better candidate exists

objective

The candidate’s reported objective value

Feasibility or optimality

optimality

Recorded optimality status, for example supplied by an adapter

A proof performed by evaluate

An infeasible candidate can still have an objective value when its expressions can be evaluated. It cannot be selected as a better feasible solution merely because that value looks preferable.

>>> infeasible = instance.evaluate({x.id: 1, y.id: 1})
>>> assert infeasible.objective == 2.0
>>> assert not infeasible.feasible

Feasibility includes variable kind and bounds, not only constraints. Floating-point checks use the tolerance supplied to evaluate(..., atol=...); feasibility_atol records it. The exact rules for discrete-value normalization, reconstruction, and omitted values belong to populate_state().

Inspect evaluated values

decision_variables_df() combines variable definitions and labels with evaluated values. constraints_df() selects one constraint family and includes its evaluation diagnostics. For a regular constraint f(x) <= 0, the signed value f(x) and the nonnegative violation max(0, f(x)) are different quantities.

For queries without pandas, constraints is a live EvaluatedConstraintCollection indexed by typed constraint IDs. It includes active constraints and constraints retained after removal. Each fetched row is an independent snapshot; editing it does not update the Solution. Use an owner method such as set_dual_variable() when changing result data. ConstraintFamilyView documents family-filtered queries, and ConstraintIDBase documents the extensible ID interface.

extract_decision_variables() and extract_constraints() use names and subscript tuples to retrieve variable values and constraint violations for application-level analysis. NamedFunction expressions registered on the Instance provide additional reported metrics through extract_named_functions() or named_functions_df(). These metrics do not add an objective or a feasibility condition.

Evaluate a transformed problem

Evaluate a solver’s returned State with the transformed Instance that was supplied to it. That Instance retains the fixed values, dependencies, removed constraints, and output objective needed to interpret the result. Passing the State directly to the original Instance may lose that context or leave required original variables unspecified.

>>> from ommx import ConstraintKind, Instance, Optimality, Sense
>>> original = Instance.maximize()
>>> x = original.new_binary("x")
>>> y = original.new_binary("y")
>>> original.objective = 3 * x + y
>>> prepared = original.partial_evaluate({x.id: 1})
>>> assert prepared.convert_active_objective(Sense.Minimize)
>>> restored = prepared.evaluate({y.id: 0})
>>> assert restored.state.entries == {x.id: 1.0, y.id: 0.0}
>>> assert prepared.objective.evaluate({y.id: 0}) == -3.0
>>> assert (restored.sense, restored.objective) == (Sense.Maximize, 3.0)
>>> assert restored.optimality == Optimality.Unspecified

Fixed and dependent values are reconstructed. Otherwise irrelevant variables omitted from the candidate receive defaults; a default is not a value found by the solver. Supply such a value when it matters to reported metrics. Distinguish successful reconstruction, feasibility including removed constraints, and transport of objective or optimality information. feasible_relaxed excludes removed constraints but still checks variable domains. Output objective and optimality transport follow output_objective and map_active_optimality(), respectively.

Use SampleSet to evaluate multiple candidates with the same rules. Being best among the candidates obtained does not establish global optimality. Solution annotations can also be persisted in protobuf payloads and mirrored to OMMX Artifact descriptors.

__copy__() → Solution#
__deepcopy__(_memo: Any) → Solution#
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#
constraint_context_df(*, kind: ConstraintKind[ConstraintIDBase]) → DataFrame#

Constraint context DataFrame (id-indexed). See ommx.Instance.constraint_context_df() for column / kind= semantics. Reads from the evaluated collection’s context store.

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).

constraint_violation(constraint_id: ConstraintIDBase) → float#

Get one constraint’s nonnegative scalar violation.

Uses the definitions in total_violation(), including for removed constraints. The ID type selects its family. Raises KeyError when the ID is absent from that constraint family.

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

DataFrame of evaluated constraints, including feasible and violation for every kind.

kind is a required ConstraintKind selector. The index contains typed IDs of the selected family. Rows retain their labels and evaluation diagnostics; editing this materialized table does not update the Solution.

For regular constraints, value is the signed left-hand-side value: equality requires zero and inequality requires a nonpositive value, within feasibility_atol. The nonnegative violation column and per-row feasible are therefore distinct from value. Per-constraint feasibility does not check variable domains. The violation column uses constraint_violation(). See ommx.Instance.constraints_df() for column / kind= / include= semantics.

Solution has no removed= parameter (no active/removed distinction at the evaluated stage); reason data is gated by "removed_reason" in include=. When the flag is on, rows for constraints removed before evaluation get removed_reason / removed_reason.{key} columns populated; other rows have NA. The related metadata queries constraint_context_df(), constraint_parameters_df(), constraint_provenance_df(), and constraint_removed_reasons_df() also require kind.

>>> from ommx import ConstraintKind, Instance
>>> instance = Instance.minimize()
>>> x = instance.new_binary("x")
>>> capacity = instance.add_constraint(x <= 2, "capacity")
>>> solution = instance.evaluate({x.id: 0})
>>> table = solution.constraints_df(kind=ConstraintKind.Regular)
>>> assert table.loc[capacity.constraint_id, "value"] == -2.0
>>> assert table.loc[capacity.constraint_id, "violation"] == 0.0
>>> assert table.loc[capacity.constraint_id, "feasible"]
decision_variables_df(include: Optional[Sequence[str]] = None) → DataFrame#

A materialized DataFrame of variable definitions, labels, and evaluated values.

The integer id index identifies variables. Columns include kind, lower, upper, and value, plus modeling labels such as name, subscripts, description, and parameters.{key}. Evaluated rows have no substituted_value field, so that compatibility column is NA; read the populated value from value. include selects optional label/sidecar columns as on decision_variables_df(). The table includes variables without names. subscripts holds lists such as [0]; extract_decision_variables() uses tuples as dictionary keys instead. Editing this table does not update the Solution.

>>> from ommx import ConstraintKind, Instance
>>> instance = Instance.maximize()
>>> x = instance.new_binary("selected", subscripts=[0])
>>> instance.objective = x
>>> solution = instance.evaluate({x.id: 1})
>>> table = solution.decision_variables_df()
>>> assert table.loc[x.id, "name"] == "selected"
>>> assert table.loc[x.id, "value"] == 1.0
>>> table.loc[x.id, "value"] = 0.0
>>> assert solution.state.entries[x.id] == 1.0
extract_all_decision_variables() → dict#

Extract all decision variables grouped by name.

Returns a mapping from variable name to a mapping from subscripts to values. This is useful for extracting all variables at once in a structured format. Variables without names are not included in the result. Scalar variables use the empty tuple () as their inner key. Names and subscripts select matches; parameters and descriptions are ignored.

Raises ValueError if the same name and subscript combination is found multiple times. Distinct IDs do not disambiguate duplicate labels in this format. Use state or decision_variables_df() for values keyed by ID. See extract_decision_variables() for the complete single-name, grouped, and table workflow, and state_from_named_values() to map these values into another Instance.

Examples#

>>> from ommx import ConstraintKind, DecisionVariable, Instance, Sense
>>> x = [DecisionVariable.binary(i, name="x", subscripts=[i]) for i in range(3)]
>>> y = [DecisionVariable.binary(i+3, name="y", subscripts=[i]) for i in range(2)]
>>> instance = Instance.from_components(
...     decision_variables=x + y,
...     objective=sum(x) + sum(y),
...     constraints={},
...     sense=Sense.Maximize,
... )
>>> solution = instance.evaluate({i: 1 for i in range(5)})
>>> all_vars = solution.extract_all_decision_variables()
>>> all_vars["x"]
{(0,): 1.0, (1,): 1.0, (2,): 1.0}
>>> all_vars["y"]
{(0,): 1.0, (1,): 1.0}
extract_all_named_functions() → dict#

Extract all named functions grouped by name (returns a Python dict).

The outer keys are names; each inner dictionary maps subscript tuples to evaluated values, with () for a scalar metric. Entries without names are excluded. Parameters and descriptions are ignored when grouping. Use named_functions_df() to include unnamed entries and retain their IDs.

Raises ValueError if the same name and subscript combination is found multiple times.

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

Extract nonnegative violations of all constraint families by name, keyed by subscript tuples.

Discover available names with constraint_names. Matching uses the name and subscripts only; a scalar constraint has the key (). The returned dictionary captures values at extraction time.

Raises KeyError if no constraint has the requested name. Raises ValueError if matching constraints share subscripts, including across families. Parameters and descriptions are ignored. Retained removed constraints are included. Regular constraints return violations, not signed function values. Indicator violations account for activation, while OneHot and SOS1 use their respective metrics; see total_violation(). Use get_constraint_value() or the regular row’s evaluated_value when the signed value is needed.

A transformation may copy a source constraint’s labels onto generated constraints. If both survive in the result with the same name and subscripts, distinguish them through typed-ID lookup in constraints instead of name extraction.

Related methods on extract_constraints() and extract_constraints() return attached handles for active constraints; extract_constraints() returns violations for a selected sample, including removed constraints.

Examples#

>>> from ommx import ConstraintKind, DecisionVariable, Instance, Sense
>>> x = [DecisionVariable.binary(i) for i in range(3)]
>>> c0 = (x[0] + x[1] == 1).set_name("c").add_subscripts([0])
>>> c1 = (x[1] + x[2] == 1).set_name("c").add_subscripts([1])
>>> instance = Instance.from_components(
...     decision_variables=x,
...     objective=sum(x),
...     constraints={0: c0, 1: c1},
...     sense=Sense.Maximize,
... )
>>> solution = instance.evaluate({0: 1, 1: 0, 2: 1})
>>> solution.extract_constraints("c")
{(0,): 0.0, (1,): 0.0}

A feasible inequality can have a negative signed value and zero violation:

>>> from ommx import ConstraintKind, Instance
>>> named = Instance.minimize()
>>> x = named.new_binary("x")
>>> capacity = named.add_constraint(x <= 2, "capacity", subscripts=[0])
>>> assert named.constraint_names == {"capacity"}
>>> assert named.extract_constraints("capacity")[(0,)] == capacity
>>> assert named.as_parametric_instance().extract_constraints("capacity")[(0,)].name == "capacity"
>>> solution = named.evaluate({x.id: 0})
>>> assert solution.extract_constraints("capacity") == {(0,): 0.0}
>>> assert solution.get_constraint_value(capacity.constraint_id) == -2.0
>>> samples = named.evaluate_samples({7: {x.id: 0}})
>>> assert samples.extract_constraints("capacity", 7) == {(0,): 0.0}
extract_decision_variables(name: str) → dict#

Extract evaluated variable values using their model name and subscript tuples.

Use labels such as product, facility, or time indices to return results to application code. The result is a dictionary: (i,) identifies a one-dimensional subscript, (i, j) a pair, and () a scalar variable without subscripts. Names and subscripts are labels, not variable IDs; parameters and description do not distinguish extraction keys.

Raises KeyError if no decision variable has the requested name, and ValueError if the same subscript is found more than once.

>>> from ommx import ConstraintKind, Instance
>>> instance = Instance.maximize()
>>> x = [instance.new_binary("x", subscripts=[i]) for i in range(2)]
>>> y = instance.new_integer("y", lower=0, upper=3)
>>> instance.objective = 3 * x[0] + 5 * x[1] + y
>>> _ = instance.add_constraint(x[0] + x[1] <= 1)
>>> solution = instance.evaluate({x[0].id: 1, x[1].id: 0, y.id: 2})
>>> assert solution.feasible
>>> assert solution.extract_decision_variables("x") == {(0,): 1.0, (1,): 0.0}
>>> assert solution.extract_decision_variables("y") == {(): 2.0}
>>> named_values = solution.extract_all_decision_variables()
>>> assert named_values == {"x": {(0,): 1.0, (1,): 0.0}, "y": {(): 2.0}}
>>> table = solution.decision_variables_df()
>>> x_rows = table.loc[table["name"] == "x", ["subscripts", "value"]]
>>> assert x_rows["value"].to_dict() == {x[0].id: 1.0, x[1].id: 0.0}

extract_all_decision_variables() groups all named variables. decision_variables_df() also includes unnamed variables and keeps variable IDs as its index; its subscripts column contains lists, while the dictionaries above use tuple keys. extract_decision_variables() provides the same query for a selected sample ID.

To reuse values in a rebuilt model, use state_from_named_values() on the receiving Instance. It resolves that model’s IDs by labels; it does not establish feasibility.

extract_named_functions(name: str) → dict#

Extract named functions by name with subscripts as key (returns a Python dict).

Named functions report additional metrics for the evaluated candidate; registering one does not change the objective or add a constraint. Discover names with named_function_names, or use named_functions_df() for a table including labels. A scalar metric uses the empty tuple () as its key. Names and subscripts select matches; parameters and descriptions are ignored. The NamedFunction class example covers registration, single-candidate and multi-candidate evaluation, and reporting-only variables.

Raises KeyError if no named function has the requested name, and ValueError if the same subscript is found more than once.

>>> from ommx import ConstraintKind, DecisionVariable, Instance, NamedFunction, Sense
>>> x = DecisionVariable.binary(0, name="selected")
>>> cost = NamedFunction(id=10, function=4 * x, name="cost")
>>> instance = Instance.from_components(
...     decision_variables=[x], objective=x, constraints={},
...     sense=Sense.Maximize, named_functions=[cost],
... )
>>> solution = instance.evaluate({x.id: 1})
>>> assert solution.objective == 1.0
>>> assert solution.extract_named_functions("cost") == {(): 4.0}
>>> assert solution.named_functions_df().loc[cost.id, "value"] == 4.0
from_v1_bytes(bytes: bytes) → Solution#
from_v2_bytes(bytes: bytes) → Solution#
get_constraint_value(constraint_id: ConstraintID | int) → float#

Get the evaluated value of a specific constraint by ID

get_decision_variable_by_id(variable_id: int) → EvaluatedDecisionVariable#

Get a specific evaluated decision variable by ID

get_dual_variable(constraint_id: ConstraintID | int) → Optional[float]#

Get the dual variable value for a specific constraint by ID

get_named_function_by_id(named_function_id: int) → EvaluatedNamedFunction#

Get a specific evaluated named function 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 evaluated named functions, including entries without names.

Columns: id (index), value, used_ids, name, subscripts, description, parameters.{key}

The index is the named-function ID, and value is its evaluated metric. See NamedFunction for a complete registration-to-table example.

replace_annotations(annotations: Mapping[str, str]) → None#
set_dual_variable(constraint_id: ConstraintID | int, value: Optional[float]) → None#

Set the dual variable value for a specific constraint by ID.

Raises KeyError if the constraint ID does not exist.

to_v1_bytes() → bytes#
to_v2_bytes() → bytes#
total_violation() → float#

Sum the nonnegative scalar violation of every constraint, including removed constraints.

  • Equality: abs(f(x)); inequality: max(0, f(x)).

  • Indicator: the inner violation when active, otherwise zero.

  • OneHot: min_i (abs(x_i - 1) + sum_{j != i} abs(x_j)).

  • SOS1: min_i sum_{j != i} abs(x_j).

Each constraint is feasible exactly when its violation is at most the evaluation tolerance. This threshold applies to each constraint separately, not to the total. Zero therefore implies that all constraints are feasible. Variable bound and kind violations are not added. Values use the evaluated state after discrete-value canonicalization. Lowering need not preserve the metric: a retained original and its generated constraints each contribute.

Use constraint_violation() for individual values, also available in the violation column of constraints_df() for every constraint kind.

variable_labels_df() → DataFrame#

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

variable_parameters_df() → DataFrame#

Decision-variable parameters DataFrame (long format).

LP_RELAXED: Relaxation#

Class constant for LP-relaxed solutions

NOT_OPTIMAL: Optimality#

Class constant for non-optimal solutions

OPTIMAL: Optimality#

Class constant for optimal solutions

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 constraint_names: set[str]#

Read-only property.

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

Retained removed constraints are included; no sample ID is needed. 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: EvaluatedConstraintCollection#

Read-only property.

Live mapping of all constraint families, including removed results.

Typed IDs select the family. Rows are fetched only when indexed, as independent snapshots with labels, provenance, removal reasons, and evaluation diagnostics. Changing a snapshot does not update this Solution; subsequent lookups read the owner’s current result. Use set_dual_variable() to update a stored regular constraint’s dual value.

>>> from ommx import ConstraintKind, Instance
>>> instance = Instance.minimize()
>>> x = instance.new_binary("x")
>>> capacity = instance.add_constraint(x <= 2, "capacity")
>>> solution = instance.evaluate({x.id: 0})
>>> rows = solution.constraints
>>> row = rows[capacity.constraint_id]
>>> assert row.evaluated_value == -2.0
>>> assert row.violation() == 0.0
>>> assert row.is_feasible(atol=solution.feasibility_atol)
>>> row.dual_variable = 9.0
>>> assert rows[capacity.constraint_id].dual_variable is None
>>> solution.set_dual_variable(capacity.constraint_id, 2.0)
>>> assert rows[capacity.constraint_id].dual_variable == 2.0
>>> assert row.dual_variable == 9.0

EvaluatedConstraintCollection specifies membership, lookup, iteration, and equality. Its of_kind method returns a ConstraintFamilyView for one family without collecting rows. Use constraints_df() for a materialized table.

property decision_variable_ids: set[int]#

Read-only property.

property decision_variable_names: set[str]#

Read-only property.

Get all unique decision variable names in this solution.

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

Examples#

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

Read-only property.

Get evaluated decision variables as a list sorted by ID

property end: Optional[datetime]#
property feasibility_atol: float#

Read-only property.

Absolute tolerance associated with the stored evaluation and feasibility results.

Pass this to an extracted constraint’s explicit feasibility query to use the enclosing result’s threshold.

property feasible: bool#

Read-only property.

Whether all retained constraints and variable domains are satisfied.

Checks all regular and special constraints, including removed constraints, together with the kind and bounds of every retained variable, using feasibility_atol. This is an alias for feasible_unrelaxed; it does not establish optimality.

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

property feasible_relaxed: bool#

Read-only property.

Whether active constraints and all retained variable domains are satisfied.

Excludes constraints retained after removal, across all constraint families. Variable kind and bound checks are still applied with feasibility_atol. A result can satisfy this property while failing feasible because a removed constraint is violated.

>>> from ommx import ConstraintKind, Instance
>>> instance = Instance.minimize()
>>> x = instance.new_binary("x")
>>> instance.objective = x
>>> required = instance.add_constraint(x == 1, "required")
>>> instance.relax_constraint(required.constraint_id, "manual relaxation")
>>> solution = instance.evaluate({x.id: 0})
>>> assert solution.feasible_relaxed
>>> assert not solution.feasible
>>> assert solution.constraints[required.constraint_id].violation() == 1.0
property feasible_unrelaxed: bool#

Read-only property.

Alias for feasible, including removed constraints and every retained variable’s kind and bounds.

property instance: Optional[str]#
property instance_digest: Optional[str]#
property named_function_ids: set[int]#

Read-only property.

property named_function_names: set[str]#

Read-only property.

Get all unique named function names in this solution

property named_functions: list[EvaluatedNamedFunction]#

Read-only property.

Get evaluated named functions as a list sorted by ID

property objective: float#

Read-only property.

The reported objective value of this candidate.

Evaluation uses the Instance’s preserved output objective when one is present; otherwise it uses the active objective. Read the value together with sense and feasible. An infeasible candidate can have an objective value, and the value alone establishes neither feasibility nor optimality.

property optimality: Optimality#

The recorded optimality status, not a result of candidate evaluation.

evaluate() produces Optimality.Unspecified. An adapter may attach a solver’s status, or a caller may set this property; setting it does not prove the claim. For a transformed formulation, use map_active_optimality() to determine which status transports to the reported output problem.

property parameters: Optional[Any]#
property parameters_annotation: Optional[Any]#
property relaxation: Relaxation#

Get the relaxation status

property sense: Sense#

Read-only property.

The optimization sense associated with the reported objective value.

This is the preserved output sense when an output objective is recorded on the evaluated Instance, and otherwise its active sense. It can differ from the solver-facing sense after a transformation. Raises RuntimeError for older serialized results that do not record a sense.

property solver: Optional[Any]#
property solver_annotation: Optional[Any]#
property start: Optional[datetime]#
property state: State#

Read-only property.

An independent State containing the evaluated variable values.

Includes retained fixed and dependent values restored by evaluation and defaults supplied for omitted irrelevant variables. See populate_state() for the reconstruction rules.