Solution#
- class Solution#
A candidate evaluated against a particular optimization problem.
A
Statemaps 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. Useevaluate()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 + ywhile 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
Variable domains and all retained constraints are satisfied
No better candidate exists
The candidate’s reported objective value
Feasibility or optimality
Recorded optimality status, for example supplied by an adapter
A proof performed by
evaluateAn 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
kindand bounds, not only constraints. Floating-point checks use the tolerance supplied toevaluate(..., atol=...);feasibility_atolrecords it. The exact rules for discrete-value normalization, reconstruction, and omitted values belong topopulate_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 constraintf(x) <= 0, the signed valuef(x)and the nonnegative violationmax(0, f(x))are different quantities.For queries without pandas,
constraintsis a liveEvaluatedConstraintCollectionindexed 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 asset_dual_variable()when changing result data.ConstraintFamilyViewdocuments family-filtered queries, andConstraintIDBasedocuments the extensible ID interface.extract_decision_variables()andextract_constraints()use names and subscript tuples to retrieve variable values and constraint violations for application-level analysis.NamedFunctionexpressions registered on the Instance provide additional reported metrics throughextract_named_functions()ornamed_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_relaxedexcludes removed constraints but still checks variable domains. Output objective and optimality transport followoutput_objectiveandmap_active_optimality(), respectively.Use
SampleSetto 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#
- 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. RaisesKeyErrorwhen the ID is absent from that constraint family.
- constraints_df(*, kind: ConstraintKind[ConstraintIDBase], include: Optional[Sequence[str]] = None) DataFrame#
DataFrame of evaluated constraints, including
feasibleandviolationfor every kind.kindis a requiredConstraintKindselector. 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,
valueis the signed left-hand-side value: equality requires zero and inequality requires a nonpositive value, withinfeasibility_atol. The nonnegativeviolationcolumn and per-rowfeasibleare therefore distinct fromvalue. Per-constraint feasibility does not check variable domains. Theviolationcolumn usesconstraint_violation(). Seeommx.Instance.constraints_df()for column /kind=/include=semantics.Solutionhas noremoved=parameter (no active/removed distinction at the evaluated stage); reason data is gated by"removed_reason"ininclude=. When the flag is on, rows for constraints removed before evaluation getremoved_reason/removed_reason.{key}columns populated; other rows have NA. The related metadata queriesconstraint_context_df(),constraint_parameters_df(),constraint_provenance_df(), andconstraint_removed_reasons_df()also requirekind.>>> 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
idindex identifies variables. Columns includekind,lower,upper, andvalue, plus modeling labels such asname,subscripts,description, andparameters.{key}. Evaluated rows have nosubstituted_valuefield, so that compatibility column is NA; read the populated value fromvalue.includeselects optional label/sidecar columns as ondecision_variables_df(). The table includes variables without names.subscriptsholds 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
stateordecision_variables_df()for values keyed by ID. Seeextract_decision_variables()for the complete single-name, grouped, and table workflow, andstate_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. Usenamed_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(). Useget_constraint_value()or the regular row’sevaluated_valuewhen 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
constraintsinstead of name extraction.Related methods on
extract_constraints()andextract_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;parametersanddescriptiondo 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; itssubscriptscolumn 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 usenamed_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. TheNamedFunctionclass 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
valueis its evaluated metric. SeeNamedFunctionfor a complete registration-to-table example.
- 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 theviolationcolumn ofconstraints_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, orreplace_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
EvaluatedConstraintCollectionspecifies membership, lookup, iteration, and equality. Itsof_kindmethod returns aConstraintFamilyViewfor one family without collecting rows. Useconstraints_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 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 forfeasible_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 failingfeasiblebecause 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 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
senseandfeasible. 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()producesOptimality.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, usemap_active_optimality()to determine which status transports to the reported output problem.
- 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 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.