Transforms
A transform converts a quantity between the user domain (the values you specify in the configuration and read back from results) and the optimizer domain (the values actually presented to the optimization algorithm).
Three independent transform types exist:
| Transform | Applied to |
|---|---|
VariableTransform |
Optimization variables (and their bounds). |
ObjectiveTransform |
Objective function values. |
NonlinearConstraintTransform |
Nonlinear constraint values. |
Why use them
- Scaling — bring variables of different physical magnitudes onto a common numerical scale so the optimizer treats them evenly.
- Reparametrization — optimize in a more convenient space (log-space, normalized \([0,1]\) space).
- Stability — apply monotone transforms to objectives that span many orders of magnitude.
How they plug in
Transform instances are stored in top-level tuples on the
EnOptContext:
variable_transformsobjective_transformsnonlinear_constraint_transforms
Each variable, objective, or constraint is assigned to a transform by its
index in the corresponding transforms array inside VariablesConfig,
ObjectiveFunctionsConfig, or NonlinearConstraintsConfig. An index of -1
(the default) means no transform applies.
"variable_transforms": [
{"method": "default/scaler", "options": {"scales": [1.0, 1e3, 1e-3]}},
],
"variables": {
"variable_count": 3,
"transforms": [0, 0, 0], # all three variables use transform 0
},
Multiple transforms may exists, each handling a different subset of variables or objectives/constraints.
At context initialization, each transform's init(mask) method is called
with a boolean mask combining:
- For variables: which variables are free (
variables.mask) and assigned to this transform (variables.transforms == idx). - For objectives/constraints: which objectives or constraints are assigned to this transform.
The transform must treat unmasked elements as identity (pass through unchanged).
See Configuration for the broadcasting and index-sharing rules.
Default transforms
The ropt.plugins.transforms.default package provides built-in linear scaling
transforms, exposed as the default/scaler method.
DefaultVariableTransform
DefaultVariableTransform
applies a per-variable linear scale and offset:
Configuration options:
scales— array of per-variable scaling factors (default: no scaling).offsets— array of per-variable offsets (default: no offset).
When both are provided they are broadcasted to the same length.
This transform also handles:
- Perturbation magnitudes — divided by
scaleso perturbations remain proportional in optimizer space. - Variable bound differences — multiplied by
scalewhen reporting constraint violations back in user units. - Linear constraints — the coefficient matrix \(\mathbf{A}\) and RHS bounds \(\mathbf{b}\) are adjusted to account for the scale and offset (see the API reference for the full derivation). The resulting equations are further normalized by dividing each row by its maximum absolute coefficient.
DefaultObjectiveTransform
DefaultObjectiveTransform
divides objective values by scales when going to the optimizer domain and
multiplies when returning:
Configuration options:
scales— array of per-objective scaling factors.
The update(scales) method allows changing scales mid-run (e.g., for adaptive
normalization when initial magnitudes are unknown).
DefaultNonlinearConstraintTransform
DefaultNonlinearConstraintTransform
divides constraint values and their RHS bounds by scales:
Configuration options:
scales— array of per-constraint scaling factors.
Like the objective transform, it supports update(scales) for mid-run
changes. Constraint-violation differences are multiplied by scales when
converting back to user domain.
Effects on bounds and constraints
During context initialization, ropt automatically applies variable transforms
to:
- Variable lower and upper bounds (via
to_optimizer). - Perturbation magnitudes (via
magnitudes_to_optimizer). - Linear constraint coefficients and RHS bounds (via
linear_constraints_to_optimizer).
This happens once at startup, so you always specify bounds, magnitudes, and
linear constraints in user-domain terms. See
LinearConstraintsConfig for the
underlying math.
Round-tripping results
During optimization, objective/constraint values are computed in the user domain by the evaluator, then transformed to the optimizer domain for the algorithm. Results objects therefore live in the optimizer domain by default.
To obtain user-domain results, call
transform_from_optimizer on
a FunctionResults or GradientResults object. This returns a new results
object with variables, objectives, and constraints mapped back to user-domain
values (including bound/constraint violation differences).
Higher-level helpers handle this automatically:
BasicOptimizerreturns user-domain results.- Event handlers may or may not perform the conversion automatically. For
example,
ResultsHandleraccepts adomainargument ("user"or"optimizer") to control which domain its stored result lives in.
Writing a custom transform
Custom transforms are plugins; see the base classes listed under Reference / Plugin Bases.
Lifecycle
All three transform types follow the same lifecycle:
__init__(transform_config)— store configuration (scales, offsets, etc.).init(mask)— called once at context initialization with a boolean mask. For variables the mask isfree AND assigned-to-this-transform; for objectives/constraints it isassigned-to-this-transform. Implementations must ensure that unmasked elements pass through unchanged (e.g., set scale=1, offset=0 for those positions).to_optimizer(values)/from_optimizer(values)— called repeatedly to map values between domains.update(*args, **kwargs)(optional) — update internal state mid-run when parameters are not known at initialization.
VariableTransform methods
VariableTransform is the most involved
because variables interact with bounds and constraints. Beyond to_optimizer
and from_optimizer, implementations must also provide:
| Method | Purpose |
|---|---|
magnitudes_to_optimizer |
Scale perturbation magnitudes consistently with the variable transform. |
bound_constraint_diffs_from_optimizer |
Map bound-violation differences back to user domain for reporting. |
linear_constraints_to_optimizer |
Transform the coefficient matrix and RHS bounds so linear constraints remain valid. |
linear_constraints_diffs_from_optimizer |
Map linear-constraint-violation differences back to user domain. |
All value arrays use the last axis for the element dimension; if the array layout differs, reorder axes before and after calling the transform.
ObjectiveTransform / NonlinearConstraintTransform methods
ObjectiveTransform requires only
to_optimizer and from_optimizer.
NonlinearConstraintTransform
additionally requires:
bounds_to_optimizer— transform constraint RHS bounds to optimizer domain.nonlinear_constraint_diffs_from_optimizer— map violation differences back to user domain.
Where to next
- Realization Filters — also configured via index-sharing.
- Configuration — broadcasting and index-sharing rules in context.