Objectives
An optimizer needs a single number to minimize. When a problem has more than one
quantity to optimize — a cost and a risk, a yield and an emission — ropt
forms that number for you, as a weighted sum of the objectives you declare.
This page covers how many objectives there are, how they are weighted, which
direction each is optimized in, and how scaling interacts with the weights. For
the field-by-field reference, see
objectives.
Declaring more than one objective
The weights field declares how many objectives there are: one entry each.
Nothing else in the configuration changes.
CONFIG = {
"variables": {"variable_count": 5, "perturbation_magnitudes": 1e-6},
"objectives": {"weights": [0.75, 0.25]},
}
An evaluation function with several objectives returns a sequence instead of a single number, in the order the weights declare them:
The order is positional: optional labels do not select objectives. If the problem also has nonlinear constraints, their values follow the objectives in the same sequence. See Constraints and Running Optimizations.
Two properties of weights matter here:
- Weights are normalized to sum to one.
[1, 1]and[0.5, 0.5]describe the same problem, so only the ratio matters. - A zero weight disables an objective. It is still evaluated and still reported, but it does not influence the search. Weights may not be negative.
examples/export.py runs a two-objective problem, and gives the objectives names so that the exported table is readable.
Maximizing instead of minimizing
ropt minimizes. To maximize an objective, mark it in maximize, which takes
one boolean per objective:
Do not try to maximize by making a weight negative; weights must be
non-negative, and maximize is the supported way to express a direction. See
Choosing the direction of an
objective.
The sign is flipped after the realizations have been combined, never on the per-realization values. That ordering matters as soon as a function estimator produces something other than an average. Negating the inputs of a standard deviation leaves it unchanged, so flipping first would silently minimize the dispersion that was declared for maximization; flipping the combined value is correct whatever produced it.
Weights and scales determine the trade-off together
Objectives reach the optimizer divided by their scales, so what is actually
minimized is
The effective weight of an objective is therefore \(w_j / s_j\), not \(w_j\). With a single objective this does not affect the solution: dividing by a positive constant leaves the optimum where it is. With several it does, because changing one scale changes the balance between them and moves the optimum with it.
The consequence is that two objectives differing by orders of magnitude are not balanced by their weights alone — the larger one dominates whatever the weights say, because it contributes far more to the sum:
Setting auto_scale instead estimates a single factor from the first batch,
which brings the weighted total near one while preserving the objectives'
relative magnitudes — so it does not rebalance them, and does not move the
optimum.
A rescaled optimization reports a different objective
auto_scale divides the objective the optimizer reports, so switching it on
lowers target_objective without improving the solution. Two optimizations
that differ in this setting can only be compared through
functions.objectives or their variables. The runnable demonstration is
examples/scaling.py.
An offsets entry is subtracted before the division. It cannot move the optimum
or change the gradient, but it changes the magnitude the optimizer tests against
its tolerances, which matters for an objective that is large and
varies little. See Scaling
objectives and
Offsetting objectives for the
full field descriptions. To choose scales and offsets from your own preliminary
evaluation, see Choose scales from a preliminary
evaluation.
See also
- What an optimization reports in each domain, and which fields are comparable between optimizations: Working with Results.