SampleSet

SampleSet#

class SampleSet#

Multiple candidates evaluated against the same optimization problem.

Samples contains candidate assignments identified by sample IDs; a SampleSet adds their objective values, feasibility, evaluated variables and constraints, and modeling labels. Samples corresponds to State, while SampleSet corresponds to Solution. Candidates may come from a sampler or be supplied manually.

Use evaluate_samples() with Samples, a dictionary of sample IDs to assignments, or an iterable of assignments. Explicit IDs are preserved; iterables receive IDs from 0. See Samples for construction and repeated-assignment semantics.

Evaluate several candidates

Let’s consider a simple optimization problem:

maximize x_1 + 2 x_2 + 3 x_3 subject to x_1 + x_2 + x_3 = 1 x_1, x_2, x_3 in {0, 1}

>>> 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] + 2*x[1] + 3*x[2],
...     constraints={0: sum(x) == 1},
...     sense=Sense.Maximize,
... )

with three samples:

>>> samples = {
...     0: {0: 1, 1: 0, 2: 0},  # x1 = 1, x2 = x3 = 0
...     1: {0: 0, 1: 0, 2: 1},  # x3 = 1, x1 = x2 = 0
...     2: {0: 1, 1: 1, 2: 0},  # x1 = x2 = 1, x3 = 0 (infeasible)
... } # ^ sample ID

Evaluate all candidates using the same Instance:

>>> sample_set = instance.evaluate_samples(samples)
>>> sample_set.summary
           objective  feasible
sample_id
1                3.0      True
0                1.0      True
2                3.0     False

summary shows each sample’s objective and feasibility. Feasible candidates appear first, ordered by the objective according to sense. Feasibility checks variable kinds and bounds and all retained constraints, including removed constraints. Retrieve an individual sample as a Solution using its ID:

>>> solution = sample_set.get(sample_id=0)
>>> solution.objective
1.0

best_feasible selects the largest objective value among feasible samples for this maximization problem:

>>> solution = sample_set.best_feasible
>>> solution.objective
3.0

Minimization selects the smallest objective instead. This is the best among the supplied candidates, not a proof of global optimality. Selection raises ValueError when no candidate is feasible, including for an empty SampleSet.

Inspect a candidate’s values and diagnostics

The Solution returned by get() retains variable labels, evaluated constraints, named metrics, and the evaluation tolerance. Use its extraction methods or query extract_decision_variables(), extract_constraints(), and extract_named_functions() with a sample ID directly. NamedFunction records additional metrics without changing the objective or adding feasibility conditions.

constraints exposes per-constraint results across samples through SampledConstraintCollection. constraints_df() selects one family for a table; summary is the per-sample overview.

Evaluate transformed problems

Evaluate candidates with the transformed Instance supplied to the sampler. It retains the fixed values and dependencies needed to restore variables, removed constraints to inspect, and any preserved output objective. Evaluation reports that output objective and sense, which may differ from the active solver-facing objective.

>>> from ommx import 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)
>>> results = prepared.evaluate_samples({7: {y.id: 0}, 9: {y.id: 1}})
>>> assert results.sense == Sense.Maximize
>>> assert results.objectives == {7: 3.0, 9: 4.0}
>>> assert results.get(7).state.entries == {x.id: 1.0, y.id: 0.0}
>>> assert results.best_feasible_id == 9
>>> assert results.best_feasible.optimality == Optimality.Unspecified

Successful value reconstruction, feasibility, and optimality remain separate questions. feasible includes retained removed constraints; feasible_relaxed excludes them while still checking all variable domains. See Solution for output interpretation and populate_state() for reconstruction, defaults, and normalization. Choosing the best supplied candidate never establishes optimality of the original or transformed problem.

__copy__() → SampleSet#
__deepcopy__(_memo: Any) → SampleSet#
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 sampled 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).

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

DataFrame of sampled constraints, dispatched on kind=. See ommx.Instance.constraints_df() for column / kind= / include= semantics. Adds dynamic per-sample columns (value.{sample_id}, feasible.{sample_id}, etc.) on top of the kind-specific core columns.

SampleSet has no removed= parameter (no active/removed distinction at the sampled stage); reason data is gated by "removed_reason" in include=.

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

DataFrame of decision variables with per-sample value columns. Static columns: id, kind, lower, upper, name, subscripts, description. Dynamic columns: one per sample_id (int) with the variable’s value.

extract_all_decision_variables(sample_id: int) → dict#

Extract all decision variables grouped by name for a given sample ID.

Returns a mapping from variable name to a mapping from subscripts to values. This is useful for extracting all variables at once in a structured format. Variables without names are not included in the result. Inner keys are subscript tuples, with () for a scalar variable. Parameters and descriptions do not distinguish matching labels. Use state_from_named_values() on a receiving Instance to resolve its IDs from this dictionary before evaluating it there.

Raises KeyError if the sample ID does not exist, and ValueError if the same name and subscript combination is found multiple times.

Examples#

>>> from ommx import 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,
... )
>>> sample_set = instance.evaluate_samples({0: {i: 1 for i in range(5)}})
>>> all_vars = sample_set.extract_all_decision_variables(0)
>>> all_vars["x"]
{(0,): 1.0, (1,): 1.0, (2,): 1.0}
>>> all_vars["y"]
{(0,): 1.0, (1,): 1.0}
extract_all_named_functions(sample_id: int) → dict#

Extract all named function values grouped by name for a given sample ID.

The outer keys are names; inner keys are subscript tuples, with () for a scalar metric. Entries without names are excluded. Parameters and descriptions are ignored when grouping.

Raises KeyError if the sample ID does not exist, and ValueError if the same name and subscript combination is found multiple times.

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

Extract nonnegative violations of all constraint families by name for one sample.

Raises KeyError if the name or sample ID does not exist. 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.

extract_decision_variables(name: str, sample_id: int) → dict#

Extract decision variable values for a given name and sample ID.

Returns a dictionary keyed by subscript tuples. A scalar variable uses (). This is equivalent to extracting the name from get(sample_id); names and subscripts identify matches, not parameters or descriptions. See extract_decision_variables() for the relationship between named dictionaries, variable IDs, and DataFrames.

Raises KeyError if the name or sample ID does not exist, and ValueError if the same subscript is found more than once.

>>> from ommx import Instance
>>> instance = Instance.maximize()
>>> x = instance.new_binary("selected", subscripts=[0])
>>> instance.objective = x
>>> results = instance.evaluate_samples({7: {x.id: 1}})
>>> assert results.extract_decision_variables("selected", 7) == {(0,): 1.0}
>>> assert results.get(7).extract_decision_variables("selected") == {(0,): 1.0}
extract_named_functions(name: str, sample_id: int) → dict#

Extract named function values for a given name and sample ID.

Returns a dictionary keyed by subscript tuples, with () for a scalar metric. Named functions report candidate metrics without changing the objective or feasibility conditions. See extract_named_functions() for the corresponding single-candidate query, or NamedFunction for the complete registration and evaluation workflow. Parameters and descriptions do not distinguish matching labels.

Raises KeyError if the name or sample ID does not exist, and ValueError if the same subscript is found more than once.

>>> from ommx import 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],
... )
>>> results = instance.evaluate_samples({7: {x.id: 0}, 9: {x.id: 1}})
>>> assert results.extract_named_functions("cost", 9) == {(): 4.0}
>>> assert results.get(9).extract_named_functions("cost") == {(): 4.0}
feasible_ids() → set[int]#
feasible_relaxed_ids() → set[int]#
feasible_unrelaxed_ids() → set[int]#
from_v1_bytes(bytes: bytes) → SampleSet#
from_v2_bytes(bytes: bytes) → SampleSet#
get(sample_id: int) → Solution#

Return one sample as a Solution.

sample_id is the candidate’s ID, not its row position in summary. The independent result contains evaluated values, variable and constraint labels, retained removed constraints, named functions, reported objective and sense, and the stored tolerance. Changing the extracted Solution does not update the SampleSet. Raises KeyError if the sample ID does not exist.

>>> from ommx import Instance, Solution
>>> instance = Instance.maximize()
>>> x = instance.new_binary("selected", subscripts=[0])
>>> instance.objective = x
>>> results = instance.evaluate_samples({10: {x.id: 0}, 20: {x.id: 1}})
>>> assert list(results.summary.index) == [20, 10]
>>> solution = results.get(sample_id=10)
>>> assert isinstance(solution, Solution)
>>> assert solution.objective == 0.0
>>> assert solution.extract_decision_variables("selected") == {(0,): 0.0}
get_decision_variable_by_id(variable_id: int) → SampledDecisionVariable#

Get a specific sampled decision variable by ID

get_named_function_by_id(named_function_id: int) → SampledNamedFunction#

Get a specific sampled named function by ID

get_sample_by_id(sample_id: int) → Solution#

Get sample by ID (alias for get method).

Raises KeyError if the sample ID does not exist.

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 with per-sample value columns. Static columns: id, used_ids, name, subscripts, description, parameters.{key}. Dynamic columns: one per sample_id (int) with the function’s evaluated value.

num_samples() → int#

Number of candidate IDs, including repeated assignments with different IDs.

replace_annotations(annotations: Mapping[str, str]) → None#
sample_ids() → set[int]#

All candidate IDs as an independent set of integers.

to_v1_bytes() → bytes#
to_v2_bytes() → bytes#
variable_labels_df() → DataFrame#

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

variable_parameters_df() → DataFrame#

Decision-variable parameters DataFrame (long format).

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 best_feasible: Solution#

Read-only property.

The best feasible candidate as an independent Solution.

Selects only candidates satisfying feasible, including variable domains and retained removed constraints. Minimization selects the smallest reported objective; maximization selects the largest. Selection does not prove global optimality or change the extracted Solution’s optimality status.

Raises ValueError if there is no feasible candidate, including when the SampleSet is empty. There is no universal ordering of infeasible candidates: choose your own criteria from the constraint and variable diagnostics. The objective ordering in summary is not such a feasibility criterion.

>>> from ommx import Instance
>>> instance = Instance.maximize()
>>> x = instance.new_binary("x")
>>> instance.objective = x
>>> _ = instance.add_constraint(x == 1, "required")
>>> results = instance.evaluate_samples({10: {x.id: 0}, 20: {x.id: 1}})
>>> assert results.best_feasible_id == 20
>>> assert results.best_feasible.state.entries == {x.id: 1.0}
>>> infeasible = instance.evaluate_samples([{x.id: 0}])
>>> infeasible.best_feasible
Traceback (most recent call last):
...
ValueError: No feasible solution found
property best_feasible_id: int#

Read-only property.

ID of the best feasible candidate, using the same selection as best_feasible.

Raises ValueError if there is no feasible sample, including when empty.

property best_feasible_relaxed: Solution#

Read-only property.

The best candidate satisfying relaxed feasibility as an independent Solution.

Uses feasible_relaxed: removed constraints are excluded, while all variable kinds and bounds still apply. The selected candidate can therefore fail feasible. Comparison still uses the reported objective and sense; it does not switch to an active solver-facing objective. Raises ValueError if there is no qualifying candidate, including when empty.

property best_feasible_relaxed_id: int#

Read-only property.

ID of the best candidate satisfying feasible_relaxed.

Uses the reported objective and sense, like best_feasible_relaxed. Raises ValueError if there is no such candidate, including when empty.

property best_feasible_unrelaxed: Solution#

Read-only property.

Alias for best_feasible, including removed constraints. Raises ValueError if there is no feasible sample.

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: SampledConstraintCollection#

Read-only property.

Live mapping of all constraint families, including removed results. Typed IDs select the family. Rows are fetched only when indexed.

property decision_variable_names: set[str]#

Read-only property.

Get all unique decision variable names in this sample set.

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

Examples#

>>> from ommx import 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,
... )
>>> sample_set = instance.evaluate_samples({0: {i: 1 for i in range(5)}})
>>> sorted(sample_set.decision_variable_names)
['x', 'y']
property decision_variables: list[SampledDecisionVariable]#

Read-only property.

Get decision variables for compatibility with existing Python code

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: dict[int, bool]#

Read-only property.

Get feasibility status for all samples, including variable bounds and kinds.

Includes all retained regular and special constraints, even removed ones. Each value matches feasible on the corresponding extracted Solution and uses feasibility_atol.

property feasible_relaxed: dict[int, bool]#

Read-only property.

Get relaxed feasibility status for all samples.

Removed constraints are excluded; variable bounds and kinds still apply.

property feasible_unrelaxed: dict[int, bool]#

Read-only property.

Alias for feasible, keyed by sample ID.

property instance: Optional[str]#
property instance_digest: Optional[str]#
property named_function_names: set[str]#

Read-only property.

Get all unique named function names in this sample set

property named_functions: list[SampledNamedFunction]#

Read-only property.

Get named functions for compatibility with existing Python code

property objectives: dict[int, float]#

Read-only property.

Reported objective values keyed by sample ID.

Evaluation uses the Instance’s preserved output objective when present, otherwise its active objective. An infeasible candidate can still have an objective value; read this together with feasible.

property parameters: Optional[Any]#
property parameters_annotation: Optional[Any]#
property sample_ids_list: list[int]#

Read-only property.

Get sample IDs as a list (property version)

property sense: Sense#

Read-only property.

The sense associated with the reported objective values.

This is the preserved output sense when recorded by the evaluated Instance, otherwise its active sense. summary and the best_feasible selectors use it even if a transformation changed the solver-facing sense.

property solver: Optional[Any]#
property solver_annotation: Optional[Any]#
property start: Optional[datetime]#
property summary: DataFrame#

Read-only property.

A materialized overview with one row per sample ID.

The index is sample_id and the columns are objective and feasible. Feasibility matches feasible, including variable kinds and bounds and all retained constraints, even removed ones. Feasible candidates appear first; within each feasibility group, objective values are ascending for minimization and descending for maximization, according to sense.

The index keeps the candidate’s ID rather than its displayed row position; use that ID with get(). Identical assignments under different IDs remain separate rows. Editing the DataFrame does not update the SampleSet. See the SampleSet example for the table layout.