Skip to content

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:

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 (or None if 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 ConstraintInfo reference 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 (or None if 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 for FunctionResults (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 by ropt — useful for reporting and analysis.
  • names: a mapping from AxisName values 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:

if result.functions is not None:
    print(result.functions.target_objective)

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:

CONFIG = {
    ...
    "names": {
        "variable": ("x0", "x1", "x2"),
        "objective": ("NPV", "cost"),
    },
}

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