This tutorial shows how to create a model to solve a simple problem consisting of two coupled disciplines using several MDAO strategies, including:
The tutorial will introduce you to some new topics that include using the iteration hierarchy to set up models with nested optimization loops, using a solver to “close the loop” in a coupled multidisciplinary simulation, and using a broadcaster to set the values of design variables in multiple places at one time.
This tutorial covers some of the more advanced capabilities of OpenMDAO. You should read and understand A Simple Tutorial Problem before starting this one. An understanding of the material presented in A More Complex Tutorial Problem is also recommended.
All of these tutorials use the Sellar problem which consists of two disciplines as follows:
Variables z1, z2, and x1 are the design variables over which we’d like to minimize the objective. Both disciplines are functions of z1 and z2, so they are called the global design variables, while only the first discipline is a function of x1, so it is called the local design variable. The two disciplines are coupled by the coupling variables y1 and y2. Discipline 1 takes y2 as an input, and computes y1 as an output, while Discipline 2 takes y1 as an input and computes y2 as an output. As such, the two disciplines depend on each other’s output, so iteration is required to find a set of coupling variables that satisfies both equations.
Disciplines 1 and 2 were implemented in OpenMDAO as components.
from openmdao.main.api import Component
from openmdao.lib.datatypes.api import Float
class SellarDiscipline1(Component):
"""Component containing Discipline 1"""
# pylint: disable-msg=E1101
z1 = Float(0.0, iotype='in', desc='Global Design Variable')
z2 = Float(0.0, iotype='in', desc='Global Design Variable')
x1 = Float(0.0, iotype='in', desc='Local Design Variable')
y2 = Float(0.0, iotype='in', desc='Disciplinary Coupling')
y1 = Float(iotype='out', desc='Output of this Discipline')
def execute(self):
"""Evaluates the equation
y1 = z1**2 + z2 + x1 - 0.2*y2"""
z1 = self.z1
z2 = self.z2
x1 = self.x1
y2 = self.y2
self.y1 = z1**2 + z2 + x1 - 0.2*y2
class SellarDiscipline2(Component):
"""Component containing Discipline 2"""
# pylint: disable-msg=E1101
z1 = Float(0.0, iotype='in', desc='Global Design Variable')
z2 = Float(0.0, iotype='in', desc='Global Design Variable')
y1 = Float(0.0, iotype='in', desc='Disciplinary Coupling')
y2 = Float(iotype='out', desc='Output of this Discipline')
def execute(self):
"""Evaluates the equation
y1 = y1**(.5) + z1 + z2"""
z1 = self.z1
z2 = self.z2
# Note: this may cause some issues. However, y1 is constrained to be
# above 3.16, so lets just let it converge, and the optimizer will
# throw it out
y1 = abs(self.y1)
self.y2 = y1**(.5) + z1 + z2
SellarDiscipline2 contains a square root of variable y1 in its calculation. For negative values of y1, the result would be imaginary, so the absolute value is taken before the square root is applied. This component is clearly not valid for y1 < 0, and our first thought was to add a low attribute to the variable definition for y1. However, the solver that was used to converge the two disciplines occasionally forced y1 to go slightly negative. The inclusion of the absolute value solved the problem without impacting the eventual convergence of the solver.
These two components are contained in the file disciplines.py.
Reference:
Sellar, R. S., Batill, S. M., and Renaud, J. E., “Response Surface Based, Concurrent Subspace Optimization for Multidisciplinary System Design,” Proceedings References 79 of the 34th AIAA Aerospace Sciences Meeting and Exhibit, Reno, NV, January 1996.