Setting up a Basic Model With Paraboloid

Working with the Paraboloid component that we built in the previous section, you now have an analysis tool that you can run. So how do we set up some kind of analysis with this model? Before you get there, we’re going to discuss a few more basic concepts in the OpenMDAO framework: Driver, Workflow, and Assembly. Then we’ll move on to building a simple assembly that lets you run a basic analysis.

Driver

Once you have a bunch of components, you’re going to want to do something with them, like run an optimization or a Design of Experiments. Any kind of iterative execution of components is controlled by a Driver. There are a number of different kinds of drivers in the standard library, but you could also write your own if need be.

Workflow

When a Driver is running, it needs to know which components to execute and in what order to execute them. This behavior is controlled by the Workflow class. Each driver has a workflow associated with it. You can think of the workflow as being responsible for controlling the process used to run a given analysis.

Although in many cases a workflow contains just basic components, it can also contain other drivers. This allows nested iterative processes to be created. Nested iterations provide the flexibility needed to build complex optimization processes defined by MDAO architectures. Also, Components are allowed to show up multiple times in a single workflow or in multiple parts of a nested workflow. This can be used, for example, if you need to train a meta model in one part of a workflow and then optimize it in another. We often refer to collection of drivers/workflows in a given model as an iteration hierarchy.

The following figure shows an example of an iteration hierarchy involving four different Drivers. Note that in this example the same component, component2, appears in two different parts of the iteration hierarchy.

Figure shows workflows for each of 4 drivers; the workflows contain a total of 5 components

View of an Iteration Hierarchy

Each component can report its location in the iteration hierarchy by its iteration coordinates. The coordinates are of the form <workflow execution count>-<component index in workflow> for each level in the hierarchy. For example, when component2 above is executing in the workflow for driver3 for the first time its iteration coordinates would be 1-3.1-1. Which denotes the first execution of the top workflow, third component (driver3), first execution of that driver’s workflow, first component in that workflow (component2). To have all components report their iteration coordinates to stderr (the default):

from openmdao.util.log import enable_trace
enable_trace()

An Assembly is a container for your analysis models. When an Assembly executes, it will always look for a Driver named driver and start there, then work its way down the iteration hierarchy.

Besides being a container for all the other objects, an Assembly has two other main functions. It is responsible for managing all of the data connections between components in the framework. Whenever data needs to move from one component to another, this action is specified via the connect method of the assembly.

Assembly

An Assembly is a container for all of your components, drivers, and workflows. When an Assembly executes, it will always look for a Driver named driver and start there, then work its way down the iteration hierarchy.

Besides being a container for all the other objects, an Assembly has two other main functions. It is responsible for managing all of the data connections between components in the framework. Whenever data needs to move from one component to another, this action is specified via the connect method of the assembly.

Refer to adjacent text

View of an Assembly Showing Data Flow

An Assembly is also a special type of Component. Assemblies, like regular components, can have their own inputs and outputs. You can take advantage of this behavior to construct nested models that can help simplify a complex analysis a bit. You could produce a model of a jet engine from a number of analysis tools, then wrap that up into an assembly that is used as part of an aircraft simulation.

For example, we could replace component3 from the figure above with an assembly containing two other components, resulting in the following:

Refer to caption

View of an Assembly within an Assembly

So assemblies allow us to organize our model into a hierarchy of submodels, and within each submodel, drivers and workflows give us a flexible way to define an iteration scheme.

Building a Basic Model

So a model is built from an assembly which contains components, drivers, and workflows. Each assembly has its own iteration hierarchy, with driver at the root, that determines which components are run and in what order.

from openmdao.main.api import Assembly
from openmdao.examples.simple.paraboloid import Paraboloid

class BasicModel(Assembly):
    """A basic OpenMDAO Model"""

    def configure(self):
        """ Creates a new Assembly containing a Paraboloid component"""

        # Create Paraboloid component instances
        self.add('paraboloid', Paraboloid())

        # Add to driver's workflow
        self.driver.workflow.add('paraboloid')

We can see here that you use the configure method to add things into an assembly. Within the configure method, you use the add method which takes a valid OpenMDAO name and a corresponding component instance as its arguments. This adds the instance to the OpenMDAO model using the given name. In this case then, the Paraboloid is accessed via self.paraboloid.

Notice that we never added any kind of driver, but we still referenced it to add paraboloid to the workflow. Assemblies always have a default driver, which simply runs once through its workflow. In later tutorials, we’ll show you how to replace the default driver with something else like an optimizer. For now though, our models just run once through their workflows.

Connecting Components

Of course, most of your models will have more than one component in them, and you’re going to want to pass some information between them. In OpenMDAO we use connections for that. Lets take a look at how connections work.

from openmdao.main.api import Assembly
from openmdao.examples.simple.paraboloid import Paraboloid

class ConnectingComponents(Assembly):
    """ Top level assembly """

    def configure(self):
        """ Creates a new Assembly containing a chain of Paraboloid components"""

        self.add("par1",Paraboloid())
        self.add("par2",Paraboloid())
        self.add("par3",Paraboloid())

        self.driver.workflow.add(['par1','par2','par3'])

        self.connect("par1.f_xy","par2.x")
        self.connect("par2.f_xy","par3.y")

The connect method takes two arguments, the first of which must be a component output, and the second of which must be a component input or a sequence of component inputs. One thing to note is that only one output can be connected to any given input. On the other hand, it is fine to connect an output to multiple inputs. When you connect one output to multiple inputs, we call that broadcasting the output.

In the above code, we created a chain of three paraboloid components. However, we could have configured them slightly differently so that the output of the first paraboloid gets broadcast to the inputs for the next to.

from openmdao.main.api import Assembly
from openmdao.examples.simple.paraboloid import Paraboloid

class ConnectingComponents(Assembly):
    """ Top level assembly """

    def configure(self):
        """ Creates a new Assembly containing a chain of Paraboloid components"""

        self.add("par1",Paraboloid())
        self.add("par2",Paraboloid())
        self.add("par3",Paraboloid())

        self.driver.workflow.add(['par1','par2','par3'])

        self.connect("par1.f_xy","par2.x")
        self.connect("par1.f_xy","par3.y")

        #shortcut syntax
        #self.connect("par1.f_xy",["par2.x","par3.y"])

You can broadcast the output two ways. The above code shows them both. The first way is just to issue two separate connections. Notice that both connection calls have par1.f_xt as their source. The second way provides a shortcut, where you make one connect call, but specify a list of inputs to connect two. The two methods result in the exact same result, so use whichever one you prefer.

One last note: A variable is not required to be connected to anything. Typically components will have numerous inputs, and many of these will contain values that are set by the user or are perfectly fine at their defaults. That’s fine; you only need to issue connections when you want to link up multiple codes.

Variables and Assemblies

Variables can be added directly to an assembly and used to promote internal variables, making them visible to components outside of the assembly. A convenience function called create_passthrough creates a variable in the assembly and connects it to an internal component variable in one step.

Consider a similar assembly as shown above, except that we want to promote the remaining unconnected variables to the assembly boundary so that they can be linked at that level.

from openmdao.main.api import Assembly, set_as_top
from openmdao.examples.simple.paraboloid import Paraboloid

class ConnectingComponents(Assembly):
    """ Top level assembly """

    def configure(self):
        """ Creates a new Assembly containing a Paraboloid and an optimizer"""

        self.add("par1",Paraboloid())
        self.add("par2",Paraboloid())

        self.connect("par1.f_xy","par2.x")

        self.driver.workflow.add(['par1','par2'])

        self.create_passthrough('par1.x')
        self.create_passthrough('par1.y')
        self.create_passthrough('par2.y')
        self.create_passthrough('par2.f_xy')

The create_passthrough function creates a variable in the assembly. This new variable has the same name, iotype, default value, units, description, and range characteristics as the original variable on the component that you’re passing through. If you would like to present a different interface external to the assembly (perhaps you would like different units), then a passthrough cannot be used. Instead, the desired variables must be manually created and connected. You can find a more detailed example of this in the complex tutorial. Most of the time passthroughs are sufficient.

Next we’ll move on to our tutorial for setting up a basic optimization, still using the same Paraboloid component that we built for this one.

OpenMDAO Home

Table Of Contents

Previous topic

Working with Variables

Next topic

Simple Optimization

This Page