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.

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.

ref

ref0

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.

units

ref

ref0

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]