Configuration
Every ropt optimization is described by a configuration dictionary. This page
covers what that dictionary holds, the rules that apply to it, and how the
pieces fit together. For the field-by-field description of each section, see
Configuration Sections.
The configuration object
The dictionary is validated when the optimization or evaluation starts, and each
top-level key becomes an instance of that section's configuration class:
variables becomes a
VariablesConfig, objectives an
ObjectiveFunctionsConfig, and so on.
Only variables is required; every other section has defaults.
Three keys are different in kind. samplers, realization_filters and
function_estimators do not hold settings but components — objects built by
the plugin system, which the sections that use them refer to by key.
Most of the rules below follow from this one step. Because the dictionary is converted once, validation and coercion happen before the optimization rather than during it; because the result is a set of objects rather than the dict you wrote, those objects are frozen; and because components are shared rather than copied, they are addressed by key.
Top-level layout
CONFIG = {
"variables": {...}, # required
"objectives": {...}, # optional
"linear_constraints": {...}, # optional
"nonlinear_constraints": {...}, # optional
"realizations": {...}, # optional
"optimizer": {...}, # optional
"backend": {...}, # optional
"gradient": {...}, # optional
"realization_filters": [...], # optional, tuple
"function_estimators": [...], # optional, tuple
"samplers": [...], # optional, tuple
"names": {...}, # optional, for labelled output
}
Only variables is required. Each value is either a plain dict (which Pydantic
validates against the corresponding config class) or a list/tuple of such
dicts for the plugin-bearing fields.
Rules that apply everywhere
Pydantic validation
All configuration dictionaries are validated using
Pydantic. Inputs are automatically coerced to the
expected types when possible. For example, you can pass a list wherever a
tuple is expected, or a plain list of numbers wherever a NumPy array is
required — Pydantic will handle the conversion during validation.
Some values are also adjusted during validation. For instance, the weights
fields in objectives and realizations are normalized to sum to 1:
Broadcasting
Many per-variable, per-objective, or per-constraint fields are NumPy arrays. A size-1 value is broadcast to match the relevant count, for example:
"variables": {
"variable_count": 5,
"lower_bounds": 0.0, # broadcast to [0, 0, 0, 0, 0]
"upper_bounds": [1, 2, 3, 4, 5],
}
Length-mismatched arrays raise a validation error.
Sharing optimizer components by key
Each tuple-typed field holds the optimizer components of the corresponding kind:
| Field | Component type |
|---|---|
realization_filters |
RealizationFilter |
function_estimators |
FunctionEstimator |
samplers |
Sampler |
You usually specify each component with a small config dict; see Providing optimizer components below.
Other config sections refer to these components by key. Give them as a
mapping to choose the keys yourself, or as a list, which is keyed by position —
so a list entry is reached by the integer that used to index it. For example,
VariablesConfig has a samplers field that
selects a sampler for each variable:
"samplers": [
{"method": "scipy/default"}, # index 0
{"method": "scipy/sobol"}, # index 1
],
"variables": {
"variable_count": 4,
"samplers": [0, 0, 1, 1], # variables 0,1 use sampler 0; 2,3 use sampler 1
},
The same thing written with names:
{
"samplers": {"coarse": {...}, "fine": {...}},
"variables": {
"samplers": ["coarse", "coarse", "fine", "fine"],
},
}
Use a single key to share one component across all elements; thanks to
broadcasting, a single value (the default "0") is sufficient.
For optional fields like realization_filters, None (the default) leaves the
corresponding element unfiltered. Any other key must exist, or building the
context fails with an error naming the unknown key.
Two runnable scripts select a component by position this way: examples/realization_filter.py and examples/function_estimator.py, each of which registers a custom component and points the objectives at it.
Providing optimizer components
Each entry ends up as an object that implements the component's base class (from the table above). You can build that object yourself, but usually you just provide a config dict and let the plugin system build it. Each tuple element accepts any of three equivalent forms, and a Pydantic validator converts it to the required object:
-
A plain
dict— the usual case. Give a small dictionary with amethodfield (and optionaloptions), androptbuilds the object for you through the plugin system:"samplers": [ {"method": "scipy/default"}, # index 0 -> SciPySampler {"method": "scipy/sobol"}, # index 1 -> SciPySampler ],The validator looks up a plugin in the
ropt.pluginssub-package by themethodfield ("plugin/method"form, or just"method"for implicit discovery) and builds the object, applying anyoptions. -
A typed config object, for example
SamplerConfig. This is the same as the dict form — the dict is validated into exactly this object — but lets you build it explicitly in Python. -
An already-constructed object — advanced. Pass an instance of a built-in class (for example a
SciPySamplerfromropt.sampler.scipy) or of your ownSamplersubclass, and it is used as-is. This is mainly for when you write the Python object yourself and want to provide it directly, without registering it as a plugin.
The same pattern applies to backend, function_estimators, and
realization_filters. You can mix these forms
freely — for example, a hand-built Sampler instance alongside a dict-configured
one in the same tuple. The dict and config-object forms resolve the method
through the plugin system, so the plugin must be registered; providing an object
directly does not.
Method strings
All method fields use the same naming convention:
"plugin/method"— explicit: use methodmethodfrom the plugin namedplugin. For example,"scipy/default"selects thedefaultmethod from thescipyplugin."method"— implicit: omit the plugin name and letroptsearch all registered plugins for one that supportsmethod. This is convenient when only one plugin provides the method, but ambiguous if multiple plugins expose the same name. The name"default"cannot be used this way, since more than one plugin may define one; write it as"plugin/default".
The plugin part corresponds to the name under which the plugin is registered
(via an entry point); the method part is any name the plugin declares in its
methods attribute. For example, the built-in SciPy backend is named scipy
and supports methods like "default", "SLSQP", and "L-BFGS-B".
Both the plugin name and the method name are case-insensitive, so
"SciPy/SLSQP", "scipy/slsqp", and "SCIPY/Slsqp" all resolve to the
same backend.
The backend field accepts one further form, "external/...", which runs the
named backend in a separate process; see Running the optimizer in a separate
process.
Immutability
The objects the dictionary is validated into are frozen, so a setting cannot be changed once an optimization has started. Validation also does work that a later change would not repeat: bounds, perturbation magnitudes and linear constraints are scaled at that point. To change a setting, modify the dictionary and start a new optimization.
Warning
Do not serialize and round-trip the validated configuration objects (for example, to and from JSON). Some parameters are scaled during validation in a way that cannot be undone, so validating those serialized values would scale them again, incorrectly. NumPy arrays and plugin instances may also not come back unchanged from a round-trip. Persist the raw input dicts instead.
Naming things for output
The names section attaches labels to
variables, objectives, realizations and other items in
exported results. For example,
examples/export.py
uses:
"names": {
"variable": ("x", "y", "z"),
"objective": ("val", "cost"),
"realization": ("r0", "r1"),
},
Each key specifies what to label: "variable" labels the variables,
"objective" labels the objectives, and "realization" labels the
realizations. The tuple must contain one label per item, in the same order,
so three variables need three labels. Items without labels are identified by
their integer positions instead. When you export results to a table, the labels
appear in its row index by default; you can also move a dimension into the
column names. See Working with Results
for examples.
You can also label the elements of an array returned as metadata. Use the metadata entry's name as the key and provide one label per array element, as shown in examples/metadata.py.
Labels do not change how the optimizer interprets values or matches reused evaluations: those operations depend on position, not labels. Keep the ordering consistent when reusing results between optimizations.
A worked example
CONFIG = {
"variables": {
"variable_count": 5,
"lower_bounds": -5.0,
"upper_bounds": 5.0,
"perturbation_magnitudes": 1e-5,
},
"objectives": {"weights": [1.0]},
"realizations": {"weights": [1.0] * 10},
"gradient": {"number_of_perturbations": 5},
"optimizer": {"max_batches": 50},
"backend": {
"method": "scipy/default",
"options": {"maxiter": 200},
},
}
This configures a 5-variable problem with bounded variables, an ensemble of 10 equally-weighted realizations, 5 perturbations per gradient estimate, SciPy's default optimizer, and a limit of 50 batches.
Full configuration schema
Expand the block below to see every field and its default value.
Fully expanded configuration (all defaults shown)
The example below shows every top-level section of the configuration with all fields set to their default values. In practice you only need to specify the fields you want to override — everything else is filled in automatically.
CONFIG = {
"variables": {
"variable_count": ..., # required, no default
"lower_bounds": -float("inf"), # default: -inf
"upper_bounds": float("inf"), # default: +inf
"types": "real", # default: continuous
"mask": True, # default: all free
"scales": 1.0, # default: no scaling
"offsets": 0.0, # default: no offset
"perturbation_magnitudes": 0.005,
"perturbation_types": "absolute",
"boundary_types": "mirror",
"samplers": "0", # default: use first sampler for all
"seed": 1,
},
"objectives": {
"weights": [1.0], # default: single objective, weight 1.0
"scales": 1.0, # default: no scaling
"offsets": 0.0, # default: no offset
"auto_scale": False, # default: do not estimate scales
"maximize": False, # default: minimize
"realization_filters": None, # default: no filter
"function_estimators": "0", # default: use first estimator for all
},
"linear_constraints": None, # No linear constraints
"nonlinear_constraints": None, # No non-linear constraints
"realizations": {
"weights": [1.0], # default: single realization, weight 1.0
"realization_min_success": None, # default: equal to number of realizations
},
"optimizer": {
"max_batches": None, # default: no limit
"max_functions": None, # default: no limit
"output_dir": None, # default: no output directory
"stdout": None, # default: not captured
"stderr": None, # default: not captured
},
"backend": {
"method": "scipy/default", # default: SciPy SLSQP
"max_iterations": None, # default: backend-specific
"convergence_tolerance": None, # default: backend-specific
"parallel": False, # default: Do not evaluate in parallel
"verbose": None, # default: silent
"options": None, # default: no extra options
},
"gradient": {
"number_of_perturbations": 5,
"perturbation_min_success": None, # default: equal to number_of_perturbations
"merge_realizations": False, # default: estimate and average gradients
"evaluation_policy": "auto", # default: evaluate functions and perturbations
}, # as needed
"samplers": [
{
"method": "scipy/default", # default: standard normal N(0,1)
"options": {},
"shared": False, # default: Each realizations has its own
# set of perturbations
},
],
"function_estimators": [
{
"method": "default/default", # default: weighted average
"options": {},
},
],
"realization_filters": [], # default: none configured
"names": {}, # default: none configured
}
types, perturbation_types and boundary_types are shown as strings.
These are the values of VariableType,
PerturbationType and
BoundaryType in ropt.enums,
whose members may be written in their place.
Some sections above are set to None or [] because they are optional
and problem-specific. When configured, their internal structure is as
follows:
# linear_constraints (all fields required, no defaults):
"linear_constraints": {
"coefficients": ..., # required: 2D array (constraints × variables)
"lower_bounds": ..., # required: 1D array (one per constraint)
"upper_bounds": ..., # required: 1D array (one per constraint)
"scales": 1.0, # default: no scaling
"auto_scale": False, # default: do not estimate scales
}
# nonlinear_constraints (bounds are required, the rest has defaults):
"nonlinear_constraints": {
"lower_bounds": ..., # required: 1D array (one per constraint)
"upper_bounds": ..., # required: 1D array (one per constraint)
"scales": 1.0, # default: no scaling
"auto_scale": False, # default: do not estimate scales
"realization_filters": None, # default: no filter
"function_estimators": "0", # default: use first estimator
}
# realization_filters entries (method is required):
"realization_filters": [
{
"method": ..., # required: str ("plugin/method")
"options": {},
},
]