Setting and Getting Component Variables#

You will both set and get the values in the dimensional and unscaled form via the Problem class. If you have promoted both inputs and outputs to the same name, then the output takes precedence and it determines the units you should work in.

Outputs and Independent Variables#

To set or get the output variable, you reference it by its promoted name. In the regular Sellar problem, all the variables have been promoted to the top of the model. So to get the value of the “y1” output defined in the SellarDis1WithDerivatives component, you would do the following:

import openmdao.api as om
from openmdao.test_suite.components.sellar_feature import SellarDerivatives

prob = om.Problem(model=SellarDerivatives())
prob.model.nonlinear_solver = om.NonlinearBlockGS()

prob.setup()

prob.set_val('x', 2.75)

prob.run_model()

print(prob.get_val('y1'))
NL: NLBGS Converged in 8 iterations
[27.30491784]

You use the same syntax when working with the independent variables of your problem. Independent variables can be used as design variables by a Driver or set directly by a user. OpenMDAO requires that every input variable must have a source. The ultimate source for any flow of data in an OpenMDAO model is a special component, IndepVarComp, that does not have any inputs. You can leave some of your inputs unconnected and OpenMDAO will automatically create an IndepVarComp called _auto_ivc with an output that connects to each input, or you can create your own IndepVarComp manually. For example, consider our paraboloid tutorial problem which has two independent variables: x and y.

These could be defined manually and set as follows:

from openmdao.test_suite.components.paraboloid import Paraboloid

prob = om.Problem()

model = prob.model
model.add_subsystem('p1', om.IndepVarComp('x', 0.0), promotes=['x'])
model.add_subsystem('p2', om.IndepVarComp('y', 0.0), promotes=['y'])
model.add_subsystem('comp', Paraboloid(), promotes=['x', 'y', 'f_xy'])

prob.setup()

prob['x'] = 2.
prob['y'] = 10.
prob.run_model()

print(prob.get_val('f_xy'))
[214.]

or, the inputs x and y could be left unconnected and OpenMDAO would connect them to outputs on _auto_ivc. The names of the output variables on _auto_ivc are sequentially named as they are created and are of the form v0, v1, etc., but you don’t really need to know those names. You can just interact with the inputs that those outputs are connected to and the framework will ensure that the proper values are set into the outputs (and into any other inputs connected to the same output). Here’s what the paraboloid tutorial problem looks like without declaring the IndepVarComp:

from openmdao.test_suite.components.paraboloid import Paraboloid

prob = om.Problem()

prob.model.add_subsystem('comp', Paraboloid(), promotes=['x', 'y', 'f_xy'])

prob.setup()

prob.set_val('x', 2.)
prob.set_val('y', 10.)
prob.run_model()

print(prob.get_val('f_xy'))
[214.]

As we said above, outputs are always referenced via their promoted name. So if you built the Sellar problem using connections (see Sellar), instead of promoting everything, then you would access the variables like this:

from openmdao.test_suite.components.sellar import SellarDerivativesConnected

prob = om.Problem(model=SellarDerivativesConnected())
prob.model.nonlinear_solver = om.NonlinearBlockGS()

prob.setup()

prob.set_val('x', 2.75)

prob.run_model()

print(prob.get_val('x'))
NL: NLBGS Converged in 8 iterations
[2.75]
print(prob.get_val('d1.y1'))
[27.30491784]

Working with Array Variables#

When you have an array variable, for convenience we allow you to set the value with any properly-sized array, list, or tuple. In other words, the shape of the list has to match the shape of the actual data.

from openmdao.test_suite.components.sellar_feature import SellarDerivatives

prob = om.Problem(model=SellarDerivatives())
prob.model.nonlinear_solver = om.NonlinearBlockGS()

prob.setup()

# default value from the class definition
print(prob.get_val('x'))
[1.]
prob.set_val('x', 2.75)
print(prob.get_val('x'))
2.75
# default value from the class definition
print(prob.get_val('z'))
[5. 2.]
prob.set_val('z', [1.5, 1.5])
print(prob.get_val('z'))
[1.5 1.5]
prob.run_model()
print(prob.get_val('y1'))
print(prob.get_val('y2'))
NL: NLBGS Converged in 9 iterations
[5.43379017]
[5.33104916]
import numpy as np

prob.set_val('z', np.array([2.5, 2.5])) # for convenience we convert the list to an array.
print(prob.get_val('z'))
[2.5 2.5]
prob.run_model()
print(prob.get_val('y1'))
print(prob.get_val('y2'))
NL: NLBGS Converged in 9 iterations
[9.8716174]
[8.14191302]
prob = om.Problem()
model = prob.model
model.add_subsystem(name='indeps',
                    subsys=om.IndepVarComp(name='X_c', shape=(3, 1)))
prob.setup()

new_val = -5*np.ones((3, 1))
prob['indeps.X_c'] = new_val
prob.final_setup()

print(prob['indeps.X_c'])
[[-5.]
 [-5.]
 [-5.]]
new_val = 2.5*np.ones(3)
prob['indeps.X_c'][:, 0] = new_val
prob.final_setup()

print(prob['indeps.X_c'])
print(prob['indeps.X_c'][:, 0])
[[2.5]
 [2.5]
 [2.5]]
[2.5 2.5 2.5]

Residuals#

If you want to look at the residual values associated with any particular output variable, you will reference them using the same naming conventions the outputs. Also like outputs, you will be given the residuals in the unscaled dimensional form.

from openmdao.test_suite.components.sellar_feature import SellarDerivatives

model=SellarDerivatives()
model.nonlinear_solver = om.NonlinearBlockGS()
model.linear_solver = om.ScipyKrylov()

prob = om.Problem(model)
prob.setup()

prob.set_val('z', [1.5, 1.5])
prob.run_model()

inputs, outputs, residuals = prob.model.get_nonlinear_vectors()

print(residuals['y1'])
print(residuals['y2'])
NL: NLBGS Converged in 9 iterations
[7.66033903e-10]
[1.97471373e-10]

Inputs#

You can get or set the value of an input variable using either its promoted name or its absolute name. If you reference it by its promoted name, however, and that input is connected to an output because the input and output are promoted to the same name, then the promoted name will be interpreted as that of the output, and the units will be assumed to be those of the output as well. If the input has not been connected to an output then the framework will connect it automatically to an output of _auto_ivc. In this case, setting or getting using the input name will cause the framework to assume the units are those of the input, assuming there is no abiguity in units for example.

Connected Inputs Without a Source#

If multiple inputs have been promoted to the same name but not connected manually to an output or promoted to the same name as an output, then again the framework will connect all of those inputs to an _auto_ivc output. If, however, there is any difference between the units or values of any of those inputs, then you must tell the framework what units and/or values to use when creating the corresponding _auto_ivc output. You do this by calling the set_input_defaults function using the promoted input name on a Group that contains all of the promoted inputs.

Below is an example of what you’ll see if you do not call set_input_defaults to disambiguate your units and/or values:

prob = om.Problem(name='no_set_input_defaults')
prob.model.add_subsystem('C1', om.ExecComp('y=x*2.',
                                             x={'val': 1.0, 'units': 'ft'},
                                             y={'val': 0.0, 'units': 'ft'}),
                         promotes=['x'])
prob.model.add_subsystem('C2', om.ExecComp('y=x*3.',
                                             x={'val': 1.0, 'units': 'inch'},
                                             y={'val': 0.0, 'units': 'inch'}),
                         promotes=['x'])

try:
    prob.setup()
except RuntimeError as err:
    print(str(err))
Collected errors for problem 'no_set_input_defaults':
   <model> <class Group>: The following inputs, ['C1.x', 'C2.x'], promoted to 'x', are connected but their metadata entries ['units', 'val'] differ. Call <group>.set_input_defaults('x', units=?, val=?), where <group> is the model to remove the ambiguity.

The next example shows a successful run after calling set_input_defaults:

prob = om.Problem()
G1 = prob.model.add_subsystem('G1', om.Group())
G1.add_subsystem('C1', om.ExecComp('y=x*2.',
                                    x={'val': 1.0, 'units': 'cm'},
                                    y={'val': 0.0, 'units': 'cm'}),
                 promotes=['x'])
G1.add_subsystem('C2', om.ExecComp('y=x*3.',
                                    x={'val': 1.0, 'units': 'mm'},
                                    y={'val': 0.0, 'units': 'mm'}),
                 promotes=['x'])

# units and value to use for the _auto_ivc output are ambiguous.  This fixes that.
G1.set_input_defaults('x', units='m', val=1.0)

prob.setup()

# set G1.x to 2.0 m, based on the units we gave in the set_input_defaults call
prob.set_val('G1.x', 2.)

prob.run_model()

# we gave 'G1.x' units of 'm' in the set_input_defaults call
print(prob.get_val('G1.x'))
[2.]
# using absolute value will give us the value of the input C1.x, in its units of 'cm'
print(prob.get_val('G1.C1.x'))
[200.]
# using absolute value will give us the value of the input C2.x, in its units of 'mm'
print(prob.get_val('G1.C2.x'))
[2000.]

Another possible scenario is to have multiple inputs promoted to the same name when those inputs have different units, but then connecting them manually to an output using the connect function. In this case, the framework will not raise an exception during setup if set_input_defaults was not called as it does in the case of multiple promoted inputs that connected to _auto_ivc. However, if the user attempts to set or get the input using the promoted name, the framework will raise an exception if set_input_defaults has not been called to disambiguate the units of the promoted input. The reason for this difference is that in the unconnected case, the framework won’t know what value and units to assign to the _auto_ivc output if they’re ambiguous. In the manually connected case, the value and units of the output have already been supplied by the user, and the only time that there’s an ambiguity is if the user tries to access the inputs using their promoted name.

Specifying Units

You can also set an input or request the value of any variable in a different unit than its declared unit, and OpenMDAO will perform the conversion for you. This is done with the Problem methods get_val and set_val.

prob = om.Problem()
prob.model.add_subsystem('comp', om.ExecComp('y=x+1.',
                                             x={'val': 100.0, 'units': 'cm'},
                                             y={'units': 'm'}))

prob.setup()
prob.run_model()

print(prob.get_val('comp.x'))
[100.]
print(prob.get_val('comp.x', 'm'))
[1.]
prob.set_val('comp.x', 10.0, 'mm')
print(prob.get_val('comp.x'))
[1.]
print(prob.get_val('comp.x', 'm'))
[0.01]

When dealing with arrays, you can set or get specific indices or index ranges by adding the “indices” argument to the calls:

prob = om.Problem()
prob.model.add_subsystem('comp', om.ExecComp('y=x+1.',
                                             x={'val': np.array([100.0, 33.3]), 'units': 'cm'},
                                             y={'shape': (2, ), 'units': 'm'}))

prob.setup()
prob.run_model()

print(prob.get_val('comp.x'))
print(prob.get_val('comp.x', 'm'))
print(prob.get_val('comp.x', 'km', indices=[0]))
[100.   33.3]
[1.    0.333]
[0.001]
prob.set_val('comp.x', 10.0, 'mm')
print(prob.get_val('comp.x'))
print(prob.get_val('comp.x', 'm', indices=0))
[1. 1.]
0.01
prob.set_val('comp.x', 50.0, 'mm', indices=[1])
print(prob.get_val('comp.x'))
print(prob.get_val('comp.x', 'm', indices=1))
[1. 5.]
0.05

An alternate method of specifying the indices is by making use of the slicer object. This object serves as a helper function allowing the user to specify the indices value using the same syntax as you would when accessing a numpy array. This example shows that usage.

prob = om.Problem()
prob.model.add_subsystem('comp', om.ExecComp('y=x+1.',
                                             x={'val': np.array([[1., 2.], [3., 4.]]), },
                                             y={'shape': (2, 2), }))

prob.setup()
prob.run_model()

print(prob.get_val('comp.x', indices=om.slicer[:, 0]))
[1. 3.]
print(prob.get_val('comp.x', indices=om.slicer[0, 1]))
2.0
print(prob.get_val('comp.x', indices=om.slicer[1, -1]))
4.0
prob.set_val('comp.x', [5., 6.], indices=om.slicer[:,0])
print(prob.get_val('comp.x', indices=om.slicer[:, 0]))
[5. 6.]
prob.run_model()
print(prob.get_val('comp.y', indices=om.slicer[:, 0]))
[6. 7.]

Retrieving Remote Variables#

If you’re running under MPI, the Problem.get_val method also has a get_remote arg that allows you to get the value of a variable even if it’s not local to the current MPI process. For example, the code below will retrieve the value of foo.bar.x in all processes, whether the variable is local or not.

    val = prob.get_val('foo.bar.x', get_remote=True)

Warning

If get_remote is True, get_val makes a collective MPI call, so make sure to call it in all ranks of the Problem’s MPI communicator. Otherwise, collective calls made in different ranks will get out of sync and result in cryptic MPI errors.

Testing if a Variable or System is Local#

If you want to know if a given variable or system is local to the current process, the Problem.is_local method will tell you. For example:

    if prob.is_local('foo.bar.x'):
        print("foo.bar.x is local!")