In the previous tutorial, we discussed the three basic kinds of Components in the OpenMDAO framework. This tutorial focuses on using one of those, ExplicitComponent, to build a simple analysis of a paraboloid function. We’ll explain the basic structure of a run file, show you how to set inputs, run the model, and check the output files.

Paraboloid - A Single-Discipline Model

Consider a paraboloid, defined by the explicit function

\[ f(x,y) = (x-3.0)^2 + x \times y + (y+4.0)^2 - 3.0 , \]

where x and y are the inputs to the function. The minimum of this function is located at

\[ x = \frac{20}{3} \quad , \quad y = -\frac{22}{3} . \]

Here is a complete script that defines this equation as a component and then executes it with different input values, printing the results to the console when it’s done. Take a look at the full run script first, then we’ll break it down part by part to explain what each one does.

class Paraboloid(om.ExplicitComponent):
    """
    Evaluates the equation f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3.
    """

    def setup(self):
        self.add_input('x', val=0.0)
        self.add_input('y', val=0.0)

        self.add_output('f_xy', val=0.0)

    def setup_partials(self):
        # Finite difference all partials.
        self.declare_partials('*', '*', method='fd')

    def compute(self, inputs, outputs):
        """
        f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3

        Minimum at: x = 6.6667; y = -7.3333
        """
        x = inputs['x']
        y = inputs['y']

        outputs['f_xy'] = (x - 3.0)**2 + x * y + (y + 4.0)**2 - 3.0


if __name__ == "__main__":

    model = om.Group()
    model.add_subsystem('parab_comp', Paraboloid())

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

    prob.set_val('parab_comp.x', 3.0)
    prob.set_val('parab_comp.y', -4.0)

    prob.run_model()
    print(prob['parab_comp.f_xy'])

    prob.set_val('parab_comp.x', 5.0)
    prob.set_val('parab_comp.y', -2.0)

    prob.run_model()
    print(prob.get_val('parab_comp.f_xy'))
[-15.]
[-5.]

Next, let’s break this script down and understand each section:

Preamble

import openmdao.api as om

At the top of any script you’ll see these lines (or lines very similar to these) which import needed classes and functions. On the first import line, the print_function is used so that the code in the script will work in either Python 2 or 3. If you want to know whats going on with the division operator, check out this detailed explanation.

The second import line make availabe the most common OpenMDAO classes that are needed to build and run a model. The openmdao.api module contains the class ExplicitComponent that we need to define the Paraboloid model. This can be accessed via “om.ExplicitComponent” when we need it. As you progress to more complex models, you will learn about more classes available in openmdao.api,but for now we only need this one to define our paraboloid component.

Defining a Component

The component is the basic building block of a model. You will always define components as a subclass of either ExplicitComponent or ImplicitComponent. Since our simple paraboloid function is explicit, we’ll use the ExplicitComponent. You see three methods defined:

  • setup: define all your inputs and outputs here

  • setup_partials: declare partial derivatives

  • compute: calculation of all output values for the given inputs

In the setup method you define the inputs and outputs of the component. In the setup_partials method in this case you also ask OpenMDAO to approximate all the partial derivatives (derivatives of outputs with respect to inputs) using the finite difference method <https://en.wikipedia.org/wiki/Finite_difference_method>_.

Note

One of OpenMDAO's most unique features is its support for analytic derivatives.
Providing analytic partial derivatives from your components can result in much more efficient optimizations.
We'll get to using analytic derivatives in later tutorials.
class Paraboloid(om.ExplicitComponent):
    """
    Evaluates the equation f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3.
    """

    def setup(self):
        self.add_input('x', val=0.0)
        self.add_input('y', val=0.0)

        self.add_output('f_xy', val=0.0)

    def setup_partials(self):
        # Finite difference all partials.
        self.declare_partials('*', '*', method='fd')

    def compute(self, inputs, outputs):
        """
        f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3

        Minimum at: x = 6.6667; y = -7.3333
        """
        x = inputs['x']
        y = inputs['y']

        outputs['f_xy'] = (x - 3.0)**2 + x * y + (y + 4.0)**2 - 3.0

The Run Script

In this example we’ve set up the run script at the bottom of the file. The start of the run script is denoted by the following statement:

if __name__ == '__main__':

All OpenMDAO models are built up from a hierarchy of Group instances that organize the components. In this example, the hierarchy is very simple, consisting of a single root group that holds a single component that is an instance of the Paraboloid class that we just defined.

Once the model hierarchy is defined, we pass it to the constructor of the Problem class. Then we call the setup() method on that problem, which tells the framework to do some initial work to get the data structures in place for execution. In this case, we call run_model() to actually perform the computation. Later, we’ll see how to explicitly set drivers and will be calling run_driver() instead.

Here we called run_model twice. The first time x and y have the initial values of 3.0 and -4.0 respectively. The second time we changed those values to 5.0 and -2.0 and then re-ran. There are a few details to note here. First, notice the way we printed the outputs via prob.get_val('parab_comp.f_xy') and similarly how we set the new values for x and y. You can get and set values using the “get_val” and “set_val” methods on problem. By default, these methods will set and get values defined in the dimensional units of the specified input or output. In this case, there are no units on the source (i.e. des_vars.x).

Note

Detailed information on units and scaling can be found in the feature documentation.

    if __name__ == "__main__":

        model = om.Group()
        model.add_subsystem('parab_comp', Paraboloid())

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

        prob.set_val('parab_comp.x', 3.0)
        prob.set_val('parab_comp.y', -4.0)

        prob.run_model()
        print(prob['parab_comp.f_xy'])

        prob.set_val('parab_comp.x', 5.0)
        prob.set_val('parab_comp.y', -2.0)

        prob.run_model()
        print(prob['parab_comp.f_xy'])