Working with Results
ropt exposes the full intermediate and final state of an optimization through
Results objects. This page describes the result
classes and how to inspect them; see Basic Optimization and
Optimization Workflows for how results are produced and
delivered to your code.
The result hierarchy
During optimization, function and gradient evaluations generate data that is
reported via EnOptEvent objects passed to callbacks.
Each Results object represents the outcome of the
calculation for a single variable vector — that is, the objective and
gradient values computed at one point in variable space. However, the optimizer
may request evaluations at multiple variable vectors in a single batch (e.g.,
multiple perturbations or multiple candidates in a gradient-free method). In
that case, the event payload contains a sequence of Results objects, one per
variable vector evaluated in that batch.
Two concrete subclasses exist:
FunctionResults— objective and constraint values for a batch.GradientResults— gradient estimates for the objective and constraints.
Each carries nested ResultField objects:
| Result | Fields |
|---|---|
FunctionResults |
evaluations (FunctionEvaluations), functions (Functions), realizations (Realizations), constraint_info (ConstraintInfo). |
GradientResults |
evaluations (GradientEvaluations), gradients (Gradients). |
What each field holds
FunctionResults fields
evaluations(FunctionEvaluations) — the raw per-realization evaluation data:variables: the unperturbed variable vector, shape \((n_v,)\).objectives: objective values per realization, shape \((n_r, n_o)\).constraints: constraint values per realization, shape \((n_r, n_c)\) (only present when nonlinear constraints are configured).metadata: optional dict of per-realization metadata arrays, each of shape \((n_r,)\).
functions(Functions) — aggregated values derived from the per-realization evaluations (orNoneif all realizations failed):target_objective: the single weighted scalar the optimizer minimizes (0-D array).objectives: individual objective values, shape \((n_o,)\).constraints: individual constraint values, shape \((n_c,)\) (if configured).
realizations(Realizations) — ensemble metadata:evaluated_realizations: boolean array indicating which realizations were evaluated, shape \((n_r,)\).objective_weights: per-realization objective weights, shape \((n_o, n_r)\). May change during optimization (e.g., when realization filters are active).constraint_weights: per-realization constraint weights, shape \((n_c, n_r)\) (if constraints are configured).
-
constraint_info(ConstraintInfo) — constraint bound information. Present when bounds or constraints are defined. Contains two kinds of data for each constraint type (bound, linear, and nonlinear):- Differences: the signed distance between the current value and each bound. For lower bounds, a negative difference means the value is below the bound (violated). For upper bounds, a positive difference means the value is above the bound (violated).
- Violations: the absolute magnitude of any bound exceedance, or zero when the constraint is satisfied. For example, if a constraint requires \(g(\mathbf{x}) \leq 0\) and the actual value is \(0.5\), the violation is \(0.5\).
See the
ConstraintInforeference for the full list of fields.
GradientResults fields
evaluations(GradientEvaluations) — evaluation data for perturbed variables:variables: the unperturbed variable vector, shape \((n_v,)\).perturbed_variables: perturbed variable values, shape \((n_r, n_p, n_v)\).perturbed_objectives: objective values for each perturbation, shape \((n_r, n_p, n_o)\).perturbed_constraints: constraint values for each perturbation, shape \((n_r, n_p, n_c)\) (if configured).metadata: optional dict of per-realization/perturbation metadata arrays, each of shape \((n_r, n_p)\).
gradients(Gradients) — aggregated gradient values (orNoneif estimation failed):target_objective: gradient of the weighted objective w.r.t. each variable, shape \((n_v,)\).objectives: per-objective gradients, shape \((n_o, n_v)\).constraints: per-constraint gradients, shape \((n_c, n_v)\) (if configured).
realizations(Realizations) — same structure as forFunctionResults(see above).
In the shapes above: \(n_v\) = number of variables, \(n_o\) = number of objectives, \(n_c\) = number of nonlinear constraints, \(n_r\) = number of realizations, \(n_p\) = number of perturbations. All values are NumPy arrays.
Common attributes on all results
Every Results object carries:
batch_id: an integer identifying the evaluation batch (potentially generated by the evaluator).metadata: a dictionary of additional information generated during optimization. Not interpreted byropt— useful for reporting and analysis.names: a mapping fromAxisNamevalues to label tuples. Used to produce labelled multi-index DataFrames when exporting (see Exporting to pandas).
Accessing result data
Common access patterns:
result.evaluations.variables # variable vector(s) evaluated
result.functions.target_objective # weighted scalar objective
result.functions.objectives # per-objective values (after weighting)
result.functions.constraints # per-constraint values
If functions is None, the result represents a request that produced no
valid values (e.g. all realizations failed). Always guard accesses:
Axes and dimensionality
Much of the data within result objects is multi-dimensional. For example, the
objectives field within
FunctionEvaluations is a 2-D array where
each row is a realization and each column is an objective.
To simplify exporting and reporting, the identity of each dimension is stored as
axis metadata on each field. The ResultField base
class provides a get_axes class method
for retrieving this metadata:
from ropt.results import FunctionEvaluations
FunctionEvaluations.get_axes("objectives")
# (<AxisName.REALIZATION: 'realization'>, <AxisName.OBJECTIVE: 'objective'>)
The AxisName enumeration defines:
| Axis name | Meaning |
|---|---|
VARIABLE |
Index corresponds to the variable number as defined in VariablesConfig. |
OBJECTIVE |
Index corresponds to the objective number (position in the weights array of ObjectiveFunctionsConfig). |
NONLINEAR_CONSTRAINT |
Index corresponds to the nonlinear constraint number as defined in NonlinearConstraintsConfig. |
LINEAR_CONSTRAINT |
Index corresponds to the linear constraint number as defined in LinearConstraintsConfig. |
REALIZATION |
Index corresponds to the realization number in the ensemble. Present whenever results involve multiple realizations. |
PERTURBATION |
Index corresponds to a perturbation used for gradient estimation. Present in GradientEvaluations where objectives and constraints are reported for each perturbed variable set. |
The dimensionality and order of axes for each field are fixed — they are listed in the "Result descriptions" section of each class in the reference.
Note
Dimensionality is fixed: even with a single objective, result arrays still
include an OBJECTIVE axis of length one.
Domain transforms on results
When transforms are configured, optimization internally operates in the optimizer domain — variables, objectives, and constraints may be scaled or shifted for numerical stability. Results attached to events are in this optimizer domain.
The transform_from_optimizer
method reverses these transforms, mapping results back to the user domain.
When using BasicOptimizer, results passed to
observer callbacks are always transformed to user domain automatically.
In workflows, event handlers determine how results are returned,
for instance by offering a domain argument that controls whether results are
handled in user or optimizer domain. See Optimization Workflows
for details on how individual event handlers handle this.
Exporting to pandas
ropt can export results to pandas DataFrames for analysis and reporting.
This requires the pandas optional extra (see Installation).
Exporting a single result field
The to_dataframe method on an individual
result exports one field (or a subset of its sub-fields):
from ropt.enums import AxisName
df = result.to_dataframe(
"evaluations",
select=["variables", "objectives"],
)
By default, all axes of the exported sub-fields are represented as levels in a
multi-index. For example, exporting objectives from
FunctionEvaluations (which has axes
REALIZATION and OBJECTIVE) produces a DataFrame with a two-level index: one
level for the realization, one for the objective.
You can unstack selected axes into separate columns using the unstack
argument:
df = result.to_dataframe(
"evaluations",
select=["variables", "objectives"],
unstack=[AxisName.VARIABLE, AxisName.OBJECTIVE],
)
This pivots the specified axes out of the index and into columns. Column names
become tuples combining the sub-field name and the axis label(s). For instance,
unstacking VARIABLE on a variables sub-field with labels ("x0", "x1",
"x2") produces columns named ("variables", "x0"), ("variables", "x1"),
("variables", "x2").
Aggregating multiple results
results_to_dataframe builds on
to_dataframe to convert a sequence of results into a single DataFrame. It
handles field selection and automatically unstacks the most common axes into
columns (e.g., VARIABLE, OBJECTIVE, NONLINEAR_CONSTRAINT):
from ropt.results import results_to_dataframe
df = results_to_dataframe(
all_results,
fields={"evaluations.variables", "functions.objectives"},
result_type="functions",
)
Field names use dot notation for nested sub-fields (e.g.,
evaluations.variables, functions.target_objective). For metadata
dictionaries, use a second dot: evaluations.metadata.my_key.
The result_type argument selects which result objects to process:
"functions" includes only
FunctionResults, "gradients" includes only
GradientResults.
Multi-index labelling
The DataFrame index is constructed from the axis metadata of each exported
field. Each axis becomes an index level, named after the
AxisName value (e.g., "variable", "realization",
"objective").
Labels for each level come from the names attribute on the result (which is
populated from the names field in
EnOptContext). This is a dict mapping axis name
strings to tuples of labels:
When labels are provided for an axis, they appear in the DataFrame index or column headers. When no labels are provided, 0-based integer indices are used instead.
The batch_id on the result is always prepended to the index, allowing results
from multiple batches to be distinguished in an aggregated DataFrame.
Where to next
- Run an optimization and receive results via callbacks: Basic Optimization.
- Use event handlers to collect or react to results in a workflow: Optimization Workflows.