Skip to content

Optimizer Backends

ropt.backend

Public API for optimizer backend implementations.

Backends define how ropt runs an optimization algorithm against the problem described by an EnOptContext object. A backend manages the optimizer lifecycle, requests function and gradient evaluations through the core callback interface, and advances the optimization from an initial variable vector toward a solution.

Core Interface

All backend implementations inherit from the Backend base class, which defines the backend lifecycle (__init__, init, start), validation hook (validate_options), and capability flags (allow_nan, is_parallel).

Integration with Optimization

Backends are accessed via an EnOptContext object through its backend field. A backend is instantiated either directly as an object or via a BackendConfig object, which is used by the plugin system to create an instance based on the configured backend method string.

During execution, a backend uses the OptimizerCallback interface to request objective, constraint, and gradient evaluations from the ropt core.

Built-in and Custom Backends

ropt includes two built-in backends:

  • SciPyBackend: Uses optimization methods provided by SciPy.
  • ExternalBackend: Delegates the optimization loop to an external executable or process.

Users can implement custom backends by subclassing Backend. Those subclasses can be instantiated directly and passed into an EnOptContext object through its backend field. Registering a custom backend with the plugin system is optional and only required when the backend should be selected and configured via BackendConfig objects instead of being instantiated explicitly by the user.

Backend

Bases: ABC

Abstract base class for optimizer backend implementations.

All concrete backend implementations must inherit from this class and implement the required lifecycle and validation methods. A backend is responsible for configuring a concrete optimization algorithm, interacting with the ropt evaluation pipeline through an OptimizerCallback, and executing the main optimization loop.

During optimization, the backend receives an EnOptContext object describing the problem setup and uses the callback interface to request objective, constraint, and gradient evaluations as needed by the underlying algorithm.

Lifecycle

  1. Instantiation via __init__: Called with a backend configuration object.
  2. Setup via init: Called once per optimization workflow with the EnOptContext and an OptimizerCallback.
  3. Validation via validate_options: Called to verify that the configured backend options are supported.
  4. Execution via start: Called with the initial variable vector to run the optimization algorithm.

Subclasses must implement:

  • __init__: Stores backend configuration and performs lightweight setup.
  • init: Receives the optimization context and callback interface.
  • start: Runs the optimization algorithm.
  • validate_options: Verifies that backend-specific options are valid.

Subclasses may optionally override:

  • allow_nan: Indicates whether the backend can continue when evaluations produce NaN values.
  • is_parallel: Indicates whether the backend may evaluate multiple candidate variable vectors concurrently.

__init__ abstractmethod

__init__(backend_config: BackendConfig) -> None

Create a new backend instance.

Called during instantiation. Subclasses should store the configuration and perform any lightweight initialization. Validation and context-dependent setup should usually be deferred to validate_options and init.

Parameters:

Name Type Description Default
backend_config BackendConfig

Configuration object specifying the backend method and any method-specific options.

required

init abstractmethod

init(
    context: EnOptContext,
    optimizer_callback: OptimizerCallback,
) -> None

Finalize initialization after the optimization context is known.

Called once at the start of each optimization workflow, after all configuration is finalized. Use this method to store the optimization context, retain the callback interface, and perform any setup that depends on the full problem definition.

Parameters:

Name Type Description Default
context EnOptContext

The full optimization context, containing all configuration and state for the current workflow.

required
optimizer_callback OptimizerCallback

Callback interface used to request objective, constraint, and gradient evaluations from the ropt core.

required

start abstractmethod

start(initial_values: NDArray[float64]) -> None

Run the optimization algorithm from the provided initial values.

Starts the backend's main optimization loop using the supplied initial variable vector. During execution, the implementation is expected to use the OptimizerCallback provided in init to request any required objective, constraint, or gradient evaluations from the ropt core.

Parameters:

Name Type Description Default
initial_values NDArray[float64]

A 1D array of shape (n_variables,) containing the starting point for the optimization.

required

allow_nan property

allow_nan: bool

Indicate whether the backend can handle NaN evaluation results.

Backends that can continue after receiving NaN objective or constraint values should override this property to return True.

This is particularly relevant in ensemble-based optimization where evaluations might fail for all realizations. When allow_nan is True, setting realization_min_success to zero allows the evaluation process to return NaN instead of raising an error, enabling the optimizer to potentially continue.

Returns:

Type Description
bool

True if the backend supports NaN evaluation results.

is_parallel property

is_parallel: bool

Indicate whether the backend may issue parallel evaluations.

Backends that evaluate multiple candidate variable vectors concurrently should override this property to return True.

This information can be used by ropt and related components to manage resources or coordinate parallel execution appropriately.

Returns:

Type Description
bool

True if the backend may perform parallel evaluations.

validate_options abstractmethod

validate_options() -> None

Validate backend-specific options for the configured method.

Checks that the options supplied through the BackendConfig object have the expected type, contain only supported keys, and satisfy any method-specific value constraints.

Concrete backends should implement validation logic for the methods they support, potentially using schema-validation tools such as Pydantic.

The raised exception must be a ValueError, or derive from a ValueError.

Note

Backend options may be represented as a dictionary or list, depending on the backend. This method should verify that the type matches what the backend expects and raise a ValueError with a clear message when it does not.

Method name with prefix

The method string may be prefixed in the form "backend/method". Implementations should account for this when parsing the method name.

Handling the default method

The method string may be set to "default", in which case it should be mapped to the backend's actual default method.

Raises:

Type Description
ValueError

If the provided options are invalid.

ropt.backend.scipy.SciPyBackend

Bases: Backend

Backend implementation using SciPy optimization algorithms.

Implements the Backend interface to expose optimization algorithms from scipy.optimize to ropt.

The algorithm is selected via the method field of the BackendConfig object. Algorithm-specific options are passed through the options dictionary. Click on the common options or the method name for the corresponding scipy.optimize documentation:

Common Options:

disp, maxiter, keep_feasible

The keep_feasible option is used to maintain feasibility with respect to bound, linear and non-linear constraints, by passing it to the constraint handling code of the underlying SciPy optimizer. Some algorithms may choose to ignore this option.

Hessian Options:

hess, exception_strategy, min_curvature, min_denominator, init_scale

These options are used to configure the Hessian approximation method for the optimizer. The hess option specifies the type of Hessian approximation to use ("BFGS" or "SR1"), while the other options provide additional parameters for the chosen method.

Method-specific Options:

Method Options
Nelder-Mead maxfev, xatol, fatol, adaptive
Powell maxfev, xtol, ftol
CG gtol, norm, eps, finite_diff_rel_step, c1, c2
BFGS gtol, norm, eps, finite_diff_rel_step, xrtol, c1, c2
Newton-CG xtol, eps, c1, c2
L-BFGS-B1 disp, maxcor, ftol, gtol, eps, maxfun, iprint, maxls, finite_diff_rel_step
TNC2 maxfun, eps, scale, offset, maxCGit, eta, stepmx, accuracy, minfev, ftol, xtol, gtol, rescale, finite_diff_rel_step, maxiter
COBYLA rhobeg, tol, catol
COBYQA maxfev, f_target, feasibility_tol, initial_tr_radius, final_tr_radius, scale
SLSQP ftol, eps, finite_diff_rel_step
trust-constr gtol, xtol, barrier_tol, sparse_jacobian, initial_tr_radius, initial_constr_penalty, initial_barrier_parameter, initial_barrier_tolerance, factorization_method, finite_diff_rel_step, verbose
differential_evolution strategy, popsize, tol, mutation, recombination, rng, polish, init, atol, updating

Notes:

  1. Options in italics override a common option with a different type or behavior.
  2. Options with strikethrough indicate a common option that is not supported.

ropt.backend.external.ExternalBackend

Bases: Backend

Backend implementation that runs an optimizer in a separate process.

Implements the Backend interface by spawning a child process to run a delegate backend. The child process performs the optimization independently and communicates back through queues to request function evaluations, report optimizer states, and propagate errors.

Method naming

Unlike other backends, the method field of BackendConfig must include both the plugin and method name in one of these forms:

  • external/plugin-name/method-name
  • external/method-name

The external/ prefix is stripped before the remainder is forwarded to the delegate plugin. Standard plugin-name/method-name resolution without the prefix is not supported by this backend.

Note

The cloudpickle package must be installed. If it is absent, instantiation raises NotImplementedError.

ropt.backend.utils

Utility functions for use by optimizer backend plugins.

This module provides helpers for constraint validation, linear constraint adjustment based on variable masks, unique output path construction, and normalization of non-linear constraints into a standard form.

validate_supported_constraints

validate_supported_constraints(
    context: EnOptContext,
    method: str,
    supported_constraints: dict[str, set[str]],
    required_constraints: dict[str, set[str]],
) -> None

Raise if the context's constraints are incompatible with the chosen method.

Checks bounds, linear, and non-linear constraints in context against the sets of method names that support or require each constraint type. Constraint types are identified by the keys "bounds", "linear:eq", "linear:ineq", "nonlinear:eq", and "nonlinear:ineq".

Raises NotImplementedError if a constraint present in context is not supported by the method, or if a constraint required by the method is absent from context.

Parameters:

Name Type Description Default
context EnOptContext

The optimization context to inspect.

required
method str

The name of the optimization method being used.

required
supported_constraints dict[str, set[str]]

Maps each constraint type to the supported methods.

required
required_constraints dict[str, set[str]]

Maps each constraint type to supported methods.

required

create_output_path

create_output_path(
    base_name: str,
    base_dir: Path | None = None,
    name: str | None = None,
    suffix: str | None = None,
) -> Path

Construct a unique output path, appending an index if necessary.

Builds a path from the provided components. If the resulting path already exists on disk, a three-digit counter suffix (e.g. -001, -002) is appended or incremented until a non-existing path is found.

The path is assembled as: <base_dir>/<base_name>[-<name>][-<index>][<suffix>]

base_dir is created (including parents) if it does not exist.

Parameters:

Name Type Description Default
base_name str

Base file or directory name.

required
base_dir Path | None

Parent directory. If None, the path is relative to the current working directory.

None
name str | None

Optional label appended to base_name with a - separator.

None
suffix str | None

Optional file extension including the leading dot

None

Returns:

Type Description
Path

A pathlib.Path that does not currently exist on disk.

NormalizedConstraints

Normalizes non-linear constraints into a standard scalar form.

Transforms raw constraints defined by lower and upper bound pairs into one of the forms C(x) = 0, C(x) > 0, or C(x) < 0 by subtracting the relevant bound and optionally flipping the sign.

Normalization rules (applied per constraint row):

  • If lower_bound ≈ upper_bound (within 1e-15): equality constraint, normalized as C(x) - lower_bound.
  • If only lower_bound is finite: inequality, normalized as C(x) - lower_bound.
  • If only upper_bound is finite: inequality, normalized as -(C(x) - upper_bound).
  • If both bounds are finite: the constraint is split into two rows, one for each bound.

By default inequality constraints are normalized to C(x) > 0. Setting flip=True produces C(x) < 0 instead.

Usage:

  1. Initialize with the lower and upper bounds.
  2. Before each new function/gradient evaluation with a new variable vector, reset the normalized constraints by calling the reset method.
  3. The constraint values are given by the constraints property. Before accessing it, call the set_constraints with the raw constraints. If necessary, this will calculate and cache the normalized values. Since values are cached, calling this method and accessing constraints multiple times is cheap.
  4. Use the same procedure for gradients, using the gradients property and set_gradients. Raw gradients must be provided as a matrix, where the rows are the gradients of each constraint.
  5. Use the is_eq property to retrieve a vector of boolean flags to check which constraints are equality constraints.

See the scipy optimization backend in the ropt source code for an example of usage.

Parallel evaluation.

The raw constraints may be a vector of constraints, or may be a matrix of constraints for multiple variables to support parallel evaluation. In the latter case, the constraints for different variables are given by the columns of the matrix. In this case, the constraints property will have the same structure. Note that this is only supported for the constraint values, not for the gradients. Hence, parallel evaluation of multiple gradients is not supported.

__init__

__init__(*, flip: bool = False) -> None

Create a new constraint normalizer.

Parameters:

Name Type Description Default
flip bool

Whether to normalize inequality constraints to C(x) < 0 instead of the default C(x) > 0.

False

set_bounds

set_bounds(
    lower_bounds: NDArray[float64],
    upper_bounds: NDArray[float64],
) -> None

Set or update the constraint bounds.

Computes the internal normalization mapping from the supplied bound arrays. If the bounds change between calls, the mapping is rebuilt.

Parameters:

Name Type Description Default
lower_bounds NDArray[float64]

Lower bound for each raw constraint.

required
upper_bounds NDArray[float64]

Upper bound for each raw constraint.

required

Raises:

Type Description
ValueError

If the new bounds change which constraints are equalities vs. inequalities, since this would invalidate cached optimizer state.

is_eq property

is_eq: list[bool]

Return flags indicating which normalized rows are equalities.

The returned list corresponds to the normalized constraint rows after any splitting of two-sided bounds into separate lower and upper rows.

Returns:

Type Description
list[bool]

A list of booleans where True marks a normalized equality constraint row.

reset

reset() -> None

Discard cached normalized values and gradients.

Call this before normalizing results for a new variable vector. After reset, the next calls to set_constraints and set_gradients will rebuild the cached normalized data.

After calling this method, the constraints and gradients properties return None until new values are cached.

constraints property

constraints: NDArray[float64] | None

Return cached normalized constraint values, if available.

These values are produced by set_constraints after subtracting the relevant bound and applying any required sign flip.

Returns None if set_constraints has not been called since the last reset.

Returns:

Type Description
NDArray[float64] | None

A 2D NumPy array of shape (n_normalized_constraints, n_points), or None when no normalized values are cached.

gradients property

gradients: NDArray[float64] | None

Return cached normalized constraint gradients, if available.

These gradients are produced by set_gradients after applying any required sign flip.

Returns None if set_gradients has not been called since the last reset.

Returns:

Type Description
NDArray[float64] | None

A 2D NumPy array of shape (n_normalized_constraints, n_variables), or None when no normalized gradients are cached.

set_constraints

set_constraints(values: NDArray[float64]) -> None

Normalize and cache raw constraint values.

Applies the configured bound subtraction and sign convention to raw constraint values, then stores the result in the constraints cache.

Parallel evaluation is supported: if values is 2D, columns represent different evaluation points and rows represent raw constraint indices. A 1D input is treated as a single evaluation point.

If normalized values are already cached, this method returns without modifying them; call reset first to recompute.

Parameters:

Name Type Description Default
values NDArray[float64]

Raw constraint values with shape (n_constraints,) or (n_constraints, n_points).

required

set_gradients

set_gradients(values: NDArray[float64]) -> None

Normalize and cache raw constraint gradients.

Applies the configured sign convention to raw constraint gradients and stores the result in the gradients cache.

If normalized gradients are already cached, this method returns without modifying them; call reset first to recompute.

Note

Unlike set_constraints, this method does not support parallel evaluation; it expects gradients for a single variable vector.

Parameters:

Name Type Description Default
values NDArray[float64]

Raw constraint gradients with shape (n_constraints, n_variables).

required

get_masked_linear_constraints

get_masked_linear_constraints(
    context: EnOptContext, initial_values: NDArray[float64]
) -> tuple[
    NDArray[np.float64],
    NDArray[np.float64],
    NDArray[np.float64],
]

Adjust linear constraints based on a variable mask.

When an optimization problem uses a variable mask (context.variables.mask) to optimize only a subset of variables, the linear constraints need to be adapted. This function performs that adaptation.

It removes columns from the constraint coefficient matrix (context.linear_constraints.coefficients) that correspond to the masked (fixed) variables. The contribution of these fixed variables (using their initial_values) is then calculated and subtracted from the original lower and upper bounds (context.linear_constraints.lower_bounds, context.linear_constraints.upper_bounds) to produce adjusted bounds for the optimization involving only the active variables.

Additionally, any constraint rows that originally involved only masked variables (i.e., all coefficients for active variables in that row are zero) are removed entirely, as they become trivial constants.

Parameters:

Name Type Description Default
context EnOptContext

The EnOptContext object containing the variable mask and linear constraints.

required
initial_values NDArray[float64]

The initial values to use.

required

Returns:

Type Description
tuple[NDArray[float64], NDArray[float64], NDArray[float64]]

A tuple of (coefficients, lower_bounds, upper_bounds) for the active variables only, with the contributions of fixed variables already subtracted from the bounds.