Scaling Variables#
As we saw in Declaring Continuous Variables, we can specify scaling parameters for outputs and residuals. Scaling can be important for the efficiency of some linear solvers and can have an impact on some gradient free nonlinear solvers such as Broyden. Knowing when and how to use scaling can be tricky, but in general, it is a good idea to scale outputs and residuals so that both have values that are O(1) and so that they have roughly the same range of variation in your design space.
For example, consider a variable that is expected to have a value around 2500. Then you might scale it by dividing by 1000. However, if the value was going to have an expected range between 2400 and 2500, then you might want to subtract out 2400 then divide by 100.
OpenMDAO supports this kind of linear scaling of both output and residual values through a set of user defined reference values specified when the variables are defined. These are described below.
Note
When you apply scaling to your variables, it does not affect the inputs and outputs you work with in your components. These are still worked with in physical, dimensional quantities. The scaling is only applied internally when values are given to solvers.
Basics#
For outputs, scaling can be specified using the ref
argument to add_output
. This argument is named as such because it represents the reference physical value that will be scaled to 1. The table below shows some example physical values and their scaled values for the a given ref
.
|
Physical value |
Normalized value |
---|---|---|
10000 |
0 |
0.0 |
– |
10000 |
1.0 |
– |
25000 |
2.5 |
0.0001 |
0.0000 |
0.0 |
– |
0.0001 |
1.0 |
– |
0.0003 |
3.0 |
For residuals, scaling works the same way, except that the argument to add_output
is res_ref
.
Scaling with an offset#
It can be desirable to scale with an offset when the variable values are very large but they only vary by a small amount. In these situations, we can specify a second argument, ref0
, to add_output
. This argument is named as such because ref0
represents the physical value when the scaled value is 0.
|
|
Physical value |
Normalized value |
---|---|---|---|
10001 |
10000 |
9999.5 |
-0.5 |
– |
– |
10000.0 |
0.0 |
– |
– |
10001.0 |
1.0 |
– |
– |
10003.2 |
3.2 |
Residual scaling works the same way with res_ref
, though there is no offset for residuals. In explicit components, res_ref
defaults to ref
.
Using scaling with units#
Now, we address the situation in which we use scaling in conjunction with units. Let us say we specify to add_output the units
argument along with ref
and ref0
. Then, the values pass in for ref
and ref0
are assumed to be in the units
given by the units argument. For instance, if ref=10001
. and units='Pa'
, then a scaled value of 1 represents 10001. Pa
.
|
|
|
Physical value |
Normalized value |
---|---|---|---|---|
kPa |
100 |
– |
0 kPa |
0.0 |
– |
– |
– |
100 kPa |
1.0 |
– |
– |
– |
250 kPa |
2.5 |
Pa |
100100 |
100000 |
99900 Pa |
-0.1 |
– |
– |
– |
100000 Pa |
0.0 |
– |
– |
– |
100100 Pa |
0.1 |
Note
residual scaling is separate and independent of output scaling in implicit components. In explicit components, the requested output scaling is applied to the residuals as well unless res_ref
is also specified.
Specifying a scaler on an output#
This example shows how to specify a scaler on outputs ‘y1’ and ‘y2’. The scaling used here assures that the outputs (which are states in this implicit component) are in the same order of magnitude when the solver interacts with them. Note that whenever a user function is called (like apply_nonlinear here), all outputs and residuals are reverted to unscaled dimensional form.
import openmdao.api as om
class ScalingExample1(om.ImplicitComponent):
def setup(self):
self.add_input('x1', val=100.0)
self.add_input('x2', val=5000.0)
self.add_output('y1', val=200., ref=1e2)
self.add_output('y2', val=6000., ref=1e3)
def apply_nonlinear(self, inputs, outputs, residuals):
x1 = inputs['x1']
x2 = inputs['x2']
y1 = outputs['y1']
y2 = outputs['y2']
residuals['y1'] = 1e5 * (x1 - y1)/y1
residuals['y2'] = 1e-5 * (x2 - y2)/y2
Specifying a scaler and offset on an output#
This example shows how to specify a scaler and an offset on outputs ‘y1’ and ‘y2’.
class ScalingExample2(om.ImplicitComponent):
def setup(self):
self.add_input('x1', val=100.0)
self.add_input('x2', val=5000.0)
self.add_output('y1', val=200., ref=300.0, ref0=100.0)
self.add_output('y2', val=6000., ref=11000.0, ref0=1000.0)
def apply_nonlinear(self, inputs, outputs, residuals):
x1 = inputs['x1']
x2 = inputs['x2']
y1 = outputs['y1']
y2 = outputs['y2']
residuals['y1'] = 1e5 * (x1 - y1)/y1
residuals['y2'] = 1e-5 * (x2 - y2)/y2
Specifying a scaler on a residual#
This example shows how to specify a scaler on the residuals for variables ‘y1’ and ‘y2’. This choice of scaler values assures that the residuals are of the same order of magnitude when the solver interacts with them.
class ScalingExample3(om.ImplicitComponent):
def setup(self):
self.add_input('x1', val=100.0)
self.add_input('x2', val=5000.0)
self.add_output('y1', val=200., ref=1e2, res_ref=1e5)
self.add_output('y2', val=6000., ref=1e3, res_ref=1e-5)
def apply_nonlinear(self, inputs, outputs, residuals):
x1 = inputs['x1']
x2 = inputs['x2']
y1 = outputs['y1']
y2 = outputs['y2']
residuals['y1'] = 1e5 * (x1 - y1)/y1
residuals['y2'] = 1e-5 * (x2 - y2)/y2
Specifying a vector of scalers#
When you have a vector output, you can also specify a vector scaling factor with individually selected elements. For this, the ref
, ref0
or res_ref
must have the same shape as the variable value.
class ScalingExampleVector(om.ImplicitComponent):
def setup(self):
self.add_input('x', val=np.array([100., 5000.]))
self.add_output('y', val=np.array([200., 6000.]),
ref=np.array([1e2, 1e3]),
res_ref=np.array([1e5, 1e-5]))
def apply_nonlinear(self, inputs, outputs, residuals):
x = inputs['x']
y = outputs['y']
residuals['y'][0] = 1e5 * (x[0] - y[0])/y[0]
residuals['y'][1] = 1e-5 * (x[1] - y[1])/y[1]