Skip to content

Configuration Sections

Every section of a configuration dictionary, field by field, with its default value. For what that dictionary becomes, the rules that apply across all of it, and a fully expanded configuration showing every default at once, see Configuration; for the generated class documentation, see Configuration Classes.

variables

Validated into VariablesConfig.

Defines the decision variables for the optimization problem.

The variable_count field is required and determines the total number of variables, including both free and fixed variables.

The lower_bounds and upper_bounds fields define the bounds for each variable. They are broadcasted to match the number of variables and default to \(-\infty\) and \(+\infty\), respectively. Use an infinity to leave a variable unbounded on that side.

The optional types field allows assigning a VariableType to each variable (continuous or integer). If not provided, all variables default to continuous (VariableType.REAL). Integer variables are only honored by methods that support them; in the SciPy backend that is differential_evolution (see SciPyBackend).

The optional mask field is a boolean array that indicates which variables are free to change during optimization (default: all True, meaning all variables are free). True means the variable is free; False means it is fixed.

"variables": {
    "variable_count": 3,
    "lower_bounds": -1,
    "upper_bounds": [1, 2, 3],
    "types": "real",              # default; or "integer" for discrete variables
    "mask": [True, True, False],  # third variable is fixed
    "perturbation_magnitudes": 1e-5,
}

Variable perturbations

The variables section also stores information needed to generate perturbed variables for stochastic gradient estimation (see Stochastic Gradients).

Perturbations are generated by Sampler instances configured in the samplers section. The samplers field of variables assigns each variable to a sampler by key (default: "0", which is the first sampler when they are given as a list). Unless explicitly configured otherwise, the default sampler method is "scipy/default", which draws perturbations from a standard normal distribution \(N(0, 1)\).

The generated perturbation values are scaled by perturbation_magnitudes (default: 0.005) and can be modified based on perturbation_types (see PerturbationType):

  • ABSOLUTE (default): the perturbation magnitude is added directly to the variable value.
  • RELATIVE: the magnitude is scaled based on the variable's bounds.

Perturbed variables may violate the defined bounds. The boundary_types field specifies how to handle such violations (see BoundaryType). The default, MIRROR_BOTH, mirrors perturbations back into the valid range.

The seed value (default: 1) ensures consistent results across repeated runs. To obtain unique results for each optimization run, modify the seed. A common approach is to use a tuple with a unique ID as the first element, ensuring reproducibility across nested and parallel evaluations.

Named constants

The defaults above are defined as named constants in ropt.config.constants: DEFAULT_SEED, DEFAULT_PERTURBATION_MAGNITUDE, DEFAULT_PERTURBATION_TYPE, and DEFAULT_PERTURBATION_BOUNDARY_TYPE.

Scaling the variables

Variables reach the optimizer as \(y = (x - o)/s\), using the scales and offsets fields, and are reported back to you as \(x = s\,y + o\). Both directions come from the same two arrays, so they cannot disagree.

"variables": {
    "variable_count": 2,
    "lower_bounds": [0.0, 100.0],
    "upper_bounds": [1.0, 600.0],
    "scales": [1.0, 500.0],
    "offsets": [0.0, 100.0],
}

Scaling matters when variables differ by orders of magnitude. An optimizer takes a step of the same size in every direction, and judges convergence with one tolerance for all of them; both of those are only meaningful if the variables are comparable in size. The example above puts both variables in the range \([0, 1]\).

Deriving the scales and offsets from the bounds like that is the common case, and scales_and_offsets_from_bounds does it for you:

from ropt.utils import scales_and_offsets_from_bounds

scales, offsets = scales_and_offsets_from_bounds([0.0, 100.0], [1.0, 600.0])

Scales must be positive: a scale is a change of units, and nothing else. The default is a scale of 1 and an offset of 0, which is the identity. There is no auto_scale for variables, because there is nothing to estimate one from: the bounds are the only information available before the run starts, and using them is a choice, not a default.

Everything that describes the variables moves with them. The bounds are mapped, and so are the perturbation magnitudes of type ABSOLUTE; a magnitude is a distance rather than a position, so only the scale applies to it, not the offset. RELATIVE magnitudes are a fraction of the bound range and are already dimensionless, so they are left alone. Linear constraints follow the variables too, as described below.

The scales and offsets apply to every variable, including the ones fixed by mask. This keeps the scaling uniform, and a fixed variable maps back to exactly the value you gave it.

What you see in the results follows from that:

  • Variables and perturbed variables are reported unscaled.
  • Distances to the variable bounds, in ConstraintInfo, are multiplied by the scale. An offset is a shift shared by a value and its bound, so it cancels out of the distance between them.

objectives

Validated into ObjectiveFunctionsConfig.

ropt supports multi-objective optimization. Multiple objectives are combined into a single value by summing them after weighting. The weights field determines the weight of each objective function, and its length defines the number of objectives (default: [1.0], meaning a single objective). The weights are automatically normalized to sum to 1 (for example, [1, 1] becomes [0.5, 0.5]).

"objectives": {"weights": [0.6, 0.4]}

Weights must not be negative, and must not all be zero. A zero weight is allowed, and disables its objective.

Objective functions can optionally be processed using realization filters and function estimators. Both fields select an object by its key in the corresponding mapping defined in EnOptContext.

  • realization_filters: default None (no filter applied).
  • function_estimators: default "0" (the first function estimator). Unless explicitly configured otherwise, the default function estimator method is "default/default", which computes a weighted average of the per-realization values.

Setting function_estimators to None leaves that objective unestimated, and its value is reported as NaN. Any other key must exist; see Sharing optimizer components by key.

Scaling objectives

Objectives are passed to the optimizer divided by the scales field, and reported back to you multiplied by it again. This changes the numbers the optimizer sees, which matters when objectives differ by orders of magnitude or when a method has absolute tolerances.

With a single objective that is all it changes: dividing by a positive constant leaves the optimum where it was. With several objectives it does more. The optimizer minimizes \(\sum_j w_j f_j / s_j\), so the effective weight of an objective is \(w_j / s_j\), and changing one scale changes the trade-off between the objectives and moves the optimum with it. Scales and weights multiply, so setting both means deciding what their product should be.

"objectives": {"weights": [0.6, 0.4], "scales": [1e6, 1.0]}

Scales must be positive: a scale is a change of units, and nothing else. Which direction an objective is optimized in is a separate setting, described below.

Set auto_scale to estimate a scale from the first batch of evaluations instead of stating one:

"objectives": {"auto_scale": True}

The estimate is the weighted average of the objectives over the realizations, combined into a single factor using the objective weights, so that the weighted sum of the objectives starts out at a magnitude of one. It is a single factor for all objectives, which preserves their relative magnitudes and therefore both the meaning of weights and, unlike a per-objective scale, the location of the optimum. Realizations that fail do not contribute.

The estimate is computed once, from the first batch, and then fixed for the rest of the run. It multiplies scales rather than replacing it, so a configured scale still applies on top of an estimated one. An offsets entry is subtracted before the estimate is taken, so the estimate measures what is left after it.

Because the estimate divides the objective the optimizer reports, switching auto_scale on lowers target_objective without changing the solution. Two runs that differ in this setting can only be compared through functions.objectives or their variables; see Scaling of results.

Offsetting objectives

An objective reaches the optimizer as \((f_j - o_j)/s_j\), using the offsets field, and is reported back to you as \(f_j = s_j\,y_j + o_j\). The default is zero, which is the identity.

"objectives": {"weights": [1.0], "scales": [1e5], "offsets": [1e9]}

An offset does not move the optimum and does not change the gradient: it shifts the weighted total by a constant. What it changes is the magnitude the optimizer works with, and that matters when an objective is large but varies little. An NPV around \(10^9\) that varies by \(10^5\) becomes, after dividing by \(10^9\), a quantity near one that varies by \(10^{-4}\) — below the convergence tolerance of many methods. Subtracting a baseline of \(10^9\) first and then dividing by \(10^5\) leaves the variation at order one.

Because it changes the value the optimizer tests against its tolerances, an offset can change where a run stops, even though it cannot change where the optimum is.

An aggregate that measures dispersion rather than central tendency has its offset subtracted too. A standard deviation is unchanged by shifting the values it summarizes, so the offset applies to the aggregate itself; the map is undone on the way out, so what you are reported is unaffected.

Choosing the direction of an objective

ropt minimizes. To maximize an objective, mark it in the maximize field, which is a boolean per objective and defaults to all-false:

"objectives": {"weights": [0.6, 0.4], "maximize": [False, True]}

The sign is flipped after the values of the individual realizations have been combined, and never on the values themselves. This matters when a function estimator produces a measure of dispersion rather than an average: negating the inputs of a standard deviation leaves it unchanged, so asking to maximize it would silently have minimized it instead. Negating the combined value is correct whatever produced it.

What you see in the results follows from that:

  • Per-realization values (evaluations.objectives) are scaled, never flipped.
  • Combined values (functions.objectives) and their gradients are reported with the flip undone, so that they agree in sign with the values they summarize.
  • target_objective exists only as the optimizer sees it, because it mixes objectives of different scales and directions and there is no single factor to undo. It is always a value being minimized, which is what lets results be compared by "lowest is best".

Note

Objective and realization weights must not be negative. A negative weight used to act as a way to maximize; mark the objective in maximize instead.

linear_constraints

Validated into LinearConstraintsConfig.

Linear constraints are defined by a set of linear equations involving the optimization variables. The coefficients field is a 2D array where each row represents a constraint and each column corresponds to a variable. The number of rows determines the number of constraints.

The lower_bounds and upper_bounds fields specify the bounds on the right-hand side of each constraint equation. They are broadcasted to match the number of constraints.

  • Less-than inequalities: set lower_bounds to \(-\infty\).
  • Greater-than inequalities: set upper_bounds to \(+\infty\).
  • Equality constraints: set lower_bounds equal to upper_bounds.

All three fields (coefficients, lower_bounds, upper_bounds) are required; there are no defaults.

Coefficients refer to all variables, including those fixed by the mask field of variables. Before the constraints are passed to the optimizer, the contribution of the fixed variables, evaluated at their initial values, is subtracted from the bounds. Constraints that only involve fixed variables reduce to a constant and are dropped. Dropping applies only to the optimizer: results are checked against the full set of constraints, so a constant that violates its bounds is reported in every result through ConstraintInfo.

Scaling the equations

Two separate things happen to the linear constraints, in this order.

First, the variable scales and offsets are substituted into the equations. Writing \(x = s\,y + o\) turns \(A\) into \(A\,\mathrm{diag}(s)\) and the bounds \(b\) into \(b - A\,o\). This is a change of variables rather than a rescaling: it shifts every equation and every bound by the same amount, so the distance between them, and therefore the feasible set, is unchanged. It happens whenever the variables are scaled, and needs no configuration.

Second, each equation is divided by its entry in the scales field, together with its bounds:

"linear_constraints": {..., "scales": [1e3, 1.0]}

Dividing an equation by a positive number leaves its solutions alone; what it changes is the size of the numbers the optimizer works with, in the same way that scaling the variables does. Set auto_scale to estimate those divisors instead of stating them:

"linear_constraints": {..., "auto_scale": True}

The estimate for an equation is the largest absolute value among its coefficients, so that the largest coefficient the optimizer sees is one. Only the columns of variables that are free count towards it, since the fixed ones are eliminated before the optimizer sees the problem. An equation with nothing left to measure is divided by one rather than by zero. The estimate multiplies scales, so a configured scale still applies on top of an estimated one.

Unlike auto_scale for nonlinear constraints, this is a single boolean rather than one per equation: the rows form one matrix equation and are scaled together or not at all.

Distances to the constraint bounds are reported unscaled, with both steps undone.

nonlinear_constraints

Validated into NonlinearConstraintsConfig.

Nonlinear constraints are defined by comparing a constraint function to right-hand-side bounds. The lower_bounds and upper_bounds fields specify these bounds, and their length determines the number of constraint functions. Both fields are required; there are no defaults.

The same bound conventions apply as for linear constraints: use \(-\infty\) or \(+\infty\) for one-sided inequalities, and equal bounds for equality constraints.

The constraint function values are returned by the evaluator in the same array as the objectives (appended after them).

Only some optimization methods accept non-linear constraints; for the SciPy backend, see the table in SciPyBackend.

Like objectives, nonlinear constraints can optionally be processed using realization filters and function estimators, selected by key:

  • realization_filters: default None (no filter applied).
  • function_estimators: default "0" (the first function estimator, which by default computes a weighted average of per-realization values).

Scaling constraints

Constraints support scales and auto_scale just as objectives do, with two differences.

The bounds are scaled together with the constraint values, so the constraint you configured is the constraint that is solved. Scales are positive, so the bounds keep their order.

Constraints have no maximize field: a constraint is a bound to satisfy, not a quantity to optimize.

Auto-scaling estimates a separate factor for each constraint, rather than the single shared factor used for objectives, because constraints are independent conditions rather than terms of one weighted sum. For the same reason auto_scale is a boolean per constraint, so that constraints can be scaled independently:

"nonlinear_constraints": {..., "auto_scale": [True, False]}

There is no offsets entry for constraints. An objective needs an offset because it has no natural reference point, but a constraint already has one: the optimizer only ever sees the difference between a constraint and its bound, so the value is centred on the bound already and an offset would cancel out.

realizations

Validated into RealizationsConfig.

To optimize an ensemble of functions, a set of realizations is defined. When the optimizer requests a function value or a gradient, these are calculated for each realization and then combined into a single value. Typically, this combination is a weighted sum, but other methods are possible (see function estimators).

The weights field determines the weight of each realization, and its length defines the ensemble size (default: [1.0], meaning a single realization). The weights are automatically normalized to sum to 1 (for example, [1, 1] becomes [0.5, 0.5]). As with the objective weights, they must not be negative and must not all be zero.

If function evaluations for some realizations fail (for example, due to a simulation error), the total function and gradient values can still be calculated by excluding the missing values. The realization_min_success field specifies the minimum number of successful realizations required (default: equal to the number of realizations, meaning no failures are allowed).

Note

Setting realization_min_success to zero allows the optimization to proceed even if all realizations fail. While some optimizers can handle this, most will treat it as if the value were one, requiring at least one successful realization.

optimizer

Validated into OptimizerConfig.

Workflow-level settings that control how the optimization run is managed. All fields are optional and default to None (no limit) or None (no redirection):

  • max_batches: Limits the total number of calls made to the evaluation function. An optimizer might request a batch containing multiple function and/or gradient evaluations within a single call. Where batches are evaluated in parallel (for example, on an HPC cluster), this bounds the number of sequential submission steps. The number of batches does not necessarily correspond directly to the number of optimizer iterations.

  • max_functions: Sets a hard limit on the total number of individual objective function evaluations performed across all batches. Since a single batch can involve multiple function evaluations, this bounds the total computational effort more closely than max_batches. Exceeding this limit might cause the optimization to terminate mid-batch.

  • output_dir (default: None): An optional output directory where the optimizer can store files. When None, no output directory is used. A relative stdout or stderr path is resolved against it.

  • stdout (default: None): Capture the optimizer's standard output into the given file. When None, the optimizer's output is not captured. Unless stderr is also set, the optimizer's error output goes to this file too.
  • stderr (default: None): Capture the optimizer's standard error into the given file. May be set on its own, in which case standard output is left alone.

These settings determine where the optimizer's output goes, not whether there is any: that is verbose. The evaluation callback is excluded either way — anything your objective function or your result handlers print goes to the terminal as usual.

Capture is for one run at a time

Capturing rewires process-global state, so only one optimization at a time can do it. A second run that tries while another holds it — a concurrent run of optimize_many, or an optimization started from inside an evaluation callback — raises WorkflowError. Leave stdout and stderr unset on runs that overlap.

Whether an optimizer reports at all is decided by verbose, independently of capture. To silence one, set verbose=False; to discard output you cannot switch off, capture to os.devnull — but this discards anything else the process writes during the run, which a throwaway file would not.

What else lands in the file

Capture is scoped to a period of time, not to a source, so anything the process writes through sys.stdout or sys.stderr while the optimizer is working is captured with it. In practice that means Python warnings raised by the optimizer, which belong there, and output from your own threads, which does not. Log records are normally left alone, because a handler writes to the stream it was given when it was created; see Logging.

backend

Validated into BackendConfig.

Selects the optimizer algorithm and provides a standardized set of common settings that are forwarded to the backend:

  • method (default: "scipy/default"): Selects the algorithm using a "plugin/method" string. The default uses SciPy's SLSQP optimizer.
  • max_iterations (default: None): Maximum number of iterations. The exact definition depends on the optimizer backend, and not all backends support this setting.
  • convergence_tolerance (default: None): Convergence tolerance used as a stopping criterion. The exact definition depends on the optimizer, and not all backends support this setting. It is compared against the quantities the optimizer works with, which are scaled; see Scaling objectives. Setting auto_scale there brings the weighted objective sum to a magnitude of one at the start of the run, which is what makes a fixed tolerance mean the same thing across problems.
  • parallel (default: False): If True, allows the optimizer to use parallelized function evaluations. Typically applies to gradient-free methods; not all backends support this setting.
  • verbose (default: None): How much the optimizer reports about its own progress. False or 0 is silent, True selects the optimizer's own default level, and a positive integer selects a level explicitly, clamped to what the optimizer offers. None is silent. Backends map this onto whatever their library provides, so the levels are not directly comparable between them: as a guide, 1 is normal progress, 2 is detailed and 3 is everything.

    Where that output ends up is a separate question, decided by stdout and stderr. With neither set it goes to the terminal. A backend option that names the same setting, such as SciPy's disp, takes precedence over verbose. - options (default: None): A dictionary or list of strings for generic optimizer options. The format and interpretation depend on the specific optimization method. These are passed straight to the backend.

"backend": {
    "method": "scipy/default",
    "max_iterations": 200,
    "options": {"maxiter": 200},
}

Which methods a backend supports, which kinds of constraint and variable each of them accepts, and which options they take, is documented by the backend itself. For the built-in SciPy backend, see SciPyBackend. A method configured with a constraint it cannot handle is only rejected when the run starts, with UnsupportedError, after the rest of the configuration has already been written.

A backend can also be run in a process of its own, by prefixing the method with external/; see Running the optimizer in a separate process.

gradient

Validated into GradientConfig.

Controls how stochastic gradients are estimated (see also Stochastic Gradients for a deeper discussion).

Gradients are estimated using function values calculated from perturbed and unperturbed variables. The number_of_perturbations field determines how many perturbed variable sets are used (default: DEFAULT_NUMBER_OF_PERTURBATIONS = 5, must be at least 1).

If function evaluations for some perturbations fail, the gradient can still be estimated as long as a minimum number succeed. The perturbation_min_success field specifies this minimum (default: equal to number_of_perturbations).

Gradients are calculated for each realization individually and then combined. If number_of_perturbations is low (or just 1), individual gradient calculations may be unreliable. Setting merge_realizations to True (default: False) directs the optimizer to combine the results of all realizations directly into a single gradient estimate.

The evaluation_policy option (default: "auto") controls how and when objective functions and gradients are calculated:

  • "auto": Evaluate functions and/or gradients strictly according to the optimizer's requests.
  • "speculative": Evaluate the gradient whenever the objective function is requested, even if the optimizer hasn't explicitly asked for it. This can improve load balancing on HPC clusters by initiating gradient work earlier.
  • "separate": Always launch function and gradient evaluations as distinct operations, even if the optimizer requests both simultaneously. This applies with realization filters that might disable certain realizations, as it can reduce the number of gradient evaluations needed based on information obtained from the function evaluations.

function_estimators

Validated into FunctionEstimatorConfig.

A list of component configurations, each selecting a plugin with a method field and an optional options dict, referenced by key from the sections that use it (see Sharing optimizer components by key).

Function estimators control how objective and constraint function values (and their gradients) are combined across realizations. By default, a weighted average over realizations is used; function estimators allow replacing that with a different combination method (for example, standard deviation).

Fields:

  • method (default: "default/default"): Selects the estimator plugin.
  • options (default: {}): Plugin-specific options.

realization_filters

Validated into RealizationFilterConfig.

A list of component configurations, each selecting a plugin with a method field and an optional options dict, referenced by key from the sections that use it (see Sharing optimizer components by key).

Realization filters modify the weights of individual realizations. For example, they can select a subset of realizations by setting the weights of the others to zero, which is how a risk-aware objective is constructed.

Fields:

  • method (required, no default): Selects the filter plugin.
  • options (default: {}): Plugin-specific options.

samplers

Validated into SamplerConfig.

A list of component configurations, each selecting a plugin with a method field and an optional options dict, referenced by key from variables.samplers (see Sharing optimizer components by key).

Samplers generate perturbations added to variables for gradient calculations. These perturbations can be deterministic or stochastic.

Fields:

  • method (default: "scipy/default"): Selects the sampler plugin. The default draws perturbations from a standard normal distribution \(N(0, 1)\).
  • options (default: {}): Plugin-specific options.
  • shared (default: False): If True, the same set of perturbed values is used for all realizations.

names

Optional mapping from axis names to tuples of labels. These labels are used to produce human-readable multi-index DataFrames when results are exported (see Working with Results).

Each key is an AxisName value that identifies a dimension of the optimization problem:

AxisName value Labels apply to
"variable" The optimization variables
"objective" The objective functions
"nonlinear_constraint" The nonlinear constraint functions
"linear_constraint" The linear constraints
"realization" The realizations in the ensemble
"perturbation" The perturbations used for gradient estimation

A key may also be the name of a metadata key that carries array values, which labels the user axis that metadata spans.

The corresponding value is a tuple of strings (or integers) whose length must match the count of that axis. For example, with 3 variables and 2 objectives:

"names": {
    "variable": ("x", "y", "z"),
    "objective": ("f0", "f1"),
}

You only need to provide labels for axes you want named — unlabelled axes default to integer indices. See Working with Results for how these labels appear in exported DataFrames.