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]).
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: defaultNone(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.
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:
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.
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:
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_objectiveexists 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_boundsto \(-\infty\). - Greater-than inequalities: set
upper_boundsto \(+\infty\). - Equality constraints: set
lower_boundsequal toupper_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:
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:
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: defaultNone(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:
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 thanmax_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. WhenNone, no output directory is used. A relativestdoutorstderrpath is resolved against it. stdout(default:None): Capture the optimizer's standard output into the given file. WhenNone, the optimizer's output is not captured. Unlessstderris 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. Settingauto_scalethere 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): IfTrue, 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.Falseor0is silent,Trueselects the optimizer's own default level, and a positive integer selects a level explicitly, clamped to what the optimizer offers.Noneis silent. Backends map this onto whatever their library provides, so the levels are not directly comparable between them: as a guide,1is normal progress,2is detailed and3is everything.Where that output ends up is a separate question, decided by
stdoutandstderr. With neither set it goes to the terminal. A backend option that names the same setting, such as SciPy'sdisp, takes precedence oververbose. -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.
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): IfTrue, 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:
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.