This package contains the OpenMDAO Standard Library. It’s intended to contain all of the plugins that are officially supported and released as part of openmdao.
Bases: enthought.traits.has_traits.HasTraits
switch to decide between EI or PI for infill criterion
number of initial training points to use
EI or PI to use for stopping condition of optimization
number of adaptively sampled points to use
A central place to access all of the OpenMDAO case recorders and case iterators in the standard library.
Bases: object
A CaseRecorder/CaseIterator containing Cases having the same set of input/output strings but different data. Cases are not necessarily unique.
Bases: openmdao.lib.casehandlers.caseset.CaseArray
A CaseRecorder/CaseIterator containing Cases having the same set of input/output strings but different data. All Cases in the set are unique.
Return a new CaseSet with Cases that are common to this and all others.
Return True if this CaseSet has no Cases in common with the given CaseSet.
Retrieve the values of specified variables from cases in a CaseIterator.
Returns a CaseSet containing cases with the specified varnames.
Cases in the case iterator that do not have all of the specified varnames are ignored.
Bases: object
Pulls Cases from a relational DB (sqlite). It doesn’t support general sql queries, but it does allow for a series of boolean selectors, e.g., ‘x<=y’, that are ANDed together.
Bases: object
Records Cases to a relational DB (sqlite). Values other than floats, ints or strings are pickled and are opaque to SQL queries.
Retrieve the values of specified variables from a sqlite DB containing Case data.
Returns a dict containing a list of values for each entry, keyed on variable name.
Only data from cases containing ALL of the specified variables will be returned so that all data values with the same index will correspond to the same case.
Based on command line options, display an XY plot using data from a sqlite Case DB.
Display an XY plot using Case data from a sqlite DB.
Pseudo package providing a central place to access all of the OpenMDAO components in the standard library.
Bases: openmdao.main.component.Component
Takes inputs and passes them directly to outputs to be broadcast out to other components
names of the variables you want to broadcast from this component
name/type pairs describing the variable types of each broadcast variable.’default’ name is used if no other type is set explicitly
Expected Improvement calculation for one or more objectives.
Bases: openmdao.main.component.Component
CaseSet which contains a single case, representing the criteria value.
Name of the variable to maximize the expected improvement around. Must be a NormalDistrubtion type.
the Normal Distribution of the predicted value for some function at some point where you wish to calculate the EI.
The expected improvement of the predicted_value
The probability of improvement of the predicted_value
Expected Improvement calculation for one or more objectives.
Bases: openmdao.main.component.Component
CaseIterator which contains only Pareto optimal cases according to criteria
Names of responses to maximize expected improvement around. Must be NormalDistribution type.
CaseIterator which contains NormalDistributions for each response at a location where you wish to calculate EI.
The expected improvement of the next_case
The probability of improvement of the next_case
Base class for an external application that needs to be executed.
Bases: openmdao.main.component_with_derivatives.ComponentWithDerivatives
Run an external code as a component.
Environment variables required by the command.
Delay between polling for command completion. A value of zero will use an internally computed default.
Resources required to run this component.
Maximum time to wait for command completion. A value of zero implies an infinite wait.
Return code from the command.
True if the command timed-out.
The command to be executed.
Copy inputs from inputs_dir that match patterns.
This can be useful for resetting problem state.
Copy files from results_dir that match patterns.
This can be useful for workflow debugging when the external code takes a long time to execute.
Runs the specified command.
First removes existing output (but not in/out) files. Then if resources have been specified, an appropriate server is allocated and the command is run on that server. Otherwise the command is run locally.
MetaModel is a class which supports generalized meta modeling capabilities. It has two sockets, one for surrogate model generators and a second for the model that is being approximated. The first socket, named surrogate, must always be filled before anything else is done. This socket gets filled with a dictionary that specifies which surrogate model generator should be used for which outputs. The keys of the dictionary are the variable names, and the values are the particular surrogate model generators which adhere to the ISurrogate interface. A special key, 'default', can be used to indicate a surrogate model generator to be used if no specific one is given for a particular variable. Any specified variables will override the default. OpenMDAO provides some surrogate modelers in openmdao.lib.surrogatemodels.
from openmdao.main.api import Assembly
from openmdao.lib.components.api import MetaModel
from openmdao.lib.surrogatemodels.api import KrigingSurrogate,LogisticRegression
class Simulation(Assembly):
def __init__(self):
super(Simulation,self).__init__(self)
self.add('meta_model',MetaModel())
#using KriginSurrogate for all outputs
self.meta_model.surrogate = {'default':KrigingSurrogate()}
#alternately, overiding the default for a specific variable
self.meta_model.surrogate = {'default':LogisticRegression(),
'f_xy':KrigingSurrogate()}
Once the surrogate dictionary has been specified, the model socket, called model, can be filled with a component. As soon as a component is put in the socket, MetaModel will automatically mirror the inputs and outputs of that component. In other words, MetaModel will have the same inputs and outputs as whatever component is put into the model socket.
from openmdao.main.api import Assembly
from openmdao.lib.components.api import MetaModel
from openmdao.lib.surrogatemodels.api import KrigingSurrogate
from openmdao.examples.expected_improvement.branin_component import BraninComponent
class Simulation(Assembly):
def __init__(self):
super(Simulation,self).__init__(self)
self.add('meta_model',MetaModel())
self.meta_model.surrogate = {'default':KrigingSurrogate()}
#component has two inputs: x,y
self.meta_model.model = BraninComponent()
#meta_model now has two inputs: x,y
self.meta_model.x = 9
self.meta_model.y = 9
Depending on the component being approximated, you may not want to generate approximations for all the outputs. Alternatively, you may want to exclude some of the inputs from consideration when the surrogate models are generated if the inputs are going to be held constant for a given study. MetaModel provides two I/O-Traits to handle this situation: includes and excludes. Only one of these traits can be used at a time, and both inputs and outputs are specified at the same time.
If you are specifying the includes, then only the I/O-Traits in that list will be used. If you are specifying the excludes, then everything but the I/O-Traits in the list will be mirrored by MetaModel.
from openmdao.main.api import Assembly
from openmdao.lib.components.api import MetaModel
from openmdao.lib.surrogatemodels.api import KrigingSurrogate
from openmdao.examples.expected_improvement.branin_component import BraninComponent
class Simulation(Assembly):
def __init__(self):
super(Simulation,self).__init__(self)
self.add('meta_model',MetaModel())
self.meta_model.surrogate = {'default':KrigingSurrogate()}
#component has two inputs: x,y
self.meta_model.model = BraninComponent()
#exclude the x input
self.meta_model.excludes=['x']
or
from openmdao.main.api import Assembly
from openmdao.lib.components.api import MetaModel
from openmdao.lib.surrogatemodels.api import KrigingSurrogate
from openmdao.examples.expected_improvement.branin_component import BraninComponent
class Simulation(Assembly):
def __init__(self):
super(Simulation,self).__init__(self)
self.add('meta_model',MetaModel())
self.meta_model.surrogate = {'default': KrigingSurrogate()}
#component has two inputs: x,y
self.meta_model.model = BraninComponent()
#include only the y input
self.meta_model.includes=['y']
MetaModel treats inputs and outputs a little differently. All the inputs, regardless of which ones are being included/excluded, will be mirrored by a MetaModel. But if inputs are excluded, then MetaModel won’t pass down their values to the surrogate models as inputs to training cases.
When outputs are excluded, they no longer get mirrored by MetaModel. They won’t get surrogate models fit to them, and consequently, they won’t be available to the simulation from MetaModel.
Now you have set up your MetaModel with a specific surrogate model, and you have put a model into the model socket. The input and output inclusions/exclusions have been specified. The next step is to actually start training and executing the MetaModel in simulations.
MetaModel has two operating modes: training and prediction. When run in training mode, MetaModel passes its given inputs down to the model in the model socket and runs it. Then it stores the outputs from the model to use for generating a surrogate model later. When run in predict mode, MetaModel will check for any new training data and, if present, will generate a surrogate model for each model output with the data. Then it will make a prediction of the model outputs for the given inputs. A MetaModel instance must always be run in training mode before executing it in predict mode.
To put an instance of MetaModel into the training mode, you must set the train_next event trait before executing the component. This event trait automatically resets itself after the execution, so it must be set again before each training case. An event trait is just a trigger mechanism, and it will trigger its behavior regardless of the value you set it to.
from openmdao.main.api import Assembly
from openmdao.lib.components.api import MetaModel
from openmdao.lib.surrogatemodels.api import KrigingSurrogate
from openmdao.examples.expected_improvement.branin_component import BraninComponent
class Simulation(Assembly):
def __init__(self):
super(Simulation,self).__init__()
self.add('meta_model',MetaModel())
self.meta_model.surrogate = {'default':KrigingSurrogate()}
#component has two inputs: x,y
self.meta_model.model = BraninComponent()
self.meta_model.train_next = True
self.meta_model.x = 2
self.meta_model.y = 3
self.meta_model.execute()
In a typical iteration hierarchy, a Driver is responsible for setting the train_next event when appropriate. This is accomplished via the IHasEvents Driver sub-interface. The train_next event is added to a Driver, which will then automatically set train_next prior to each iteration of the model. A simple code snippet is presented below, while a more detailed example can be found in the single_objective_ei example under the openmdao.examples.expected_improvement package.
from openmdao.main.api import Assembly
from openmdao.lib.drivers.api import DOEdriver
from openmdao.lib.components.api import MetaModel
from openmdao.examples.expected_improvement.branin_component import BraninComponent
class Analysis(Assembly):
def __init__(self,doc=None):
super(Analysis,self).__init__()
self.add('branin_meta_model',MetaModel())
self.branin_meta_model.surrogate = KrigingSurrogate()
self.branin_meta_model.model = BraninComponent()
self.add('driver',DOEdriver())
self.driver.workflow.add('branin_meta_model')
self.driver.add_event('branin_meta_model.train_next')
When the train_next event is not set, MetaModel automatically runs in predict mode. When in predict mode, the outputs provided are the result of predicted outputs from the surrogate model inside of MetaModel.
Before being able to predict the surrogate model response for any of the outputs of MetaModel, the surrogate model must be trained with the recorded training data. This will happen automatically whenever MetaModel is run in predict mode and new training data is available. This makes MetaModel more efficient, because it is not trying to retrain the model constantly when running large sets of training cases. Instead, the actual surrogate model training is only done when a prediction is needed and new training data is available.
Metamodel provides basic Meta Modeling capability.
Bases: openmdao.main.component.Component
Slot for the Component or Assembly being encapsulated.
Records training cases
A list of names of variables to be excluded from the public interface.
A list of names of variables to be included in the public interface.
CaseIterator containing cases to use as initial training data. When this is set, all previous training data is cleared, and replaced with data from this CaseIterator
An dictionary provides a mapping between variables and surrogate models for each output. The “default” key must be given. It is the default surrogate model for all outputs. Any specific surrogate models can be specifed by a key with the desired variable name.
Return the list of names of public inputs that correspond to model inputs.
Return the list of names of public outputs that correspond to model outputs.
Bases: openmdao.main.component.Component
Takes one List input and splits it into n indvidual outputs. This is a logical demultiplexer.
number of items in the array to be demultiplexed
Bases: openmdao.main.component.Component
Takes in n inputs and exports a length n List with the data. It is a logical multiplexer.
number of inputs to be multiplexed
The NastranComponent documentation refers to MSC (MacNeal-Schwendler Corporation) Nastran. This component is a wrapper for MSC Nastran but does not include the MSC Nastran executable. MSC Nastran must be installed with a valid license before this wrapper will work.
Overview
If you are creating a component that is supposed to call Nastran to calculate your component’s outputs, you must do four things:
Once you do these things, NastranComponent will worry about setting up Nastran’s input file (for the correct input variables), running Nastran, and parsing the output values out of Nastran’s output. The MSC Nastran Component has been tested exclusively with MSC Nastran 2005, although as long as the input and output don’t change, it should work for any version.
Subclassing NastranComponent
All of NastranComponent’s logic is in the execute function. The execute function reads the traits that are connected to it (both input and output variables). It uses NastranReplacer and then NastranMaker to update the Nastran file for the current input variables. It runs the Nastran command by calling its superclass, ExternalCode. Finally, it parses the output two ways: first, by calling the output variable’s nastran_func function in order to parse out the value from the FileParser and the NastranOutput object, and second, by calling NastranParser.
What all these classes do will be explained when we discuss how to tell NastranComponent how to process the input and output variables.
Controlling Nastran’s Input
To control what Nastran solves, you have to change certain variables in the Nastran input file. NastranComponent can only insert the correct variables in the right places if you tell it where to insert the variables. You can specify the input variables in two ways: via NastranReplacer (the Crude Way) or NastranMaker.
Parsing Nastran’s Output
The goal is to set output variables to certain values in Nastran’s output. As with Nastran’s input, there are two ways of going about it: one involves instructing the parser to pick out a certain location denoted by its distance from a certain anchor; the other way attempts to intelligently parse the grid structure that most pages of output have. The second way will not work for every case, but it’s a much cleaner solution if it works.
NastranOutput (the Crude Way)
Although this method is generally not recommended, sometimes it is necessary to use it. When specifying the design variable, you also specify a nastran_func attribute. You will specify a function that takes one variable: a FileParser (from openmdao.util.filewrap). The idea is that the function you specify will be able to parse out the value you want from the FileParser. The FileParser is a convenient way of looking for something in the text. You can specify an anchor in the text (such as D I S P L A C E M E N T V E C T O R) and then take the value that is x lines down and y fields across the line. You can also access the output text itself in filewrap.data.
This method is not recommended because it is not very sturdy. If the data in the output file changes significantly, and you specify the values you want by the number of fields they are away from the beginning of the line, you may unknowingly get bad data. The other problem is that if you define two functions in your class (perhaps a helper function and another one that returns the results), when you pass the function that returns the results in through nastran_func, it will not know where the helper function is and will break. (For more information on Nastran output, see NastranParser.)
nastran.py defines NastranComponent.
Bases: openmdao.lib.components.external_code.ExternalCode
All Nastran-capable components should be subclasses of NastranComponent.
By subclassing NastranComponent, any component should have easy access to NastranMaker, NastranReplacer, and NastranParser. Your subclass must specify how to handle the input and output variables to NastranComponent by specifying nastran-specific attributes on the traits. All of these attributes are described further in the NastranComponent docs.
Note: This component does nothing with external_files. If you want to deal with that, then do so in your subclass.
Should I delete the temporary files?
If I am deleting temporary files, should I keep the first one?
If I am deleting temporary files, should I keep the last one?
Location of nastran executable.
Arguments to the nastran command.
Input filename with placeholder variables.
Directory in which to put the output temp dir.
Output filename.
Runs the NastranComponent.
We are overiding ExternalCode’s execute function. First, we setup the input file (by running NastranReplacer and then NastranMaker). Next, we run Nastran by calling our parent’s execute function. Finally, we parse the data and set the output variables given to us.
While there are no explicit parameters or return values for this function, it gets all the input it needs from the design variables that are connected to the subclass of NastranComponent. This should be described pretty well in the documentation.
A subclass can override this function to dynamically add variables to NastranMaker.
The return will be ignored. Right after this function exits, the Nastran input file will be written out to a file.
NastranMaker does not rely on placeholder variables; instead, you must provide the keyword, the id, and the fieldnum to change a card. NastranMaker will find the right card to modify and will convert the entire card to long form. This way, you get 16 characters to express numbers. It also allows you to keep the Nastran input unmodified, instead of littering it with placeholder variables. Below is an example:
>>> t1 = Float(10., desc="Thickness of pshell #1",
iotype="in",
nastran_card="PSHELL",
nastran_id="1",
nastran_fieldnum=3)
Note that the Nastran_card (the keyword) and the id must be strings, while the fieldnum must be an integer. To make sense of which fields to change, an understanding of Nastran is required. Each field specifies a different attribute that can be modified. To find out which fields modify which attributes, consult the Nastran documentation. (See the MSC.Nastran 2004 Quick Reference Guide.)
In general, a sample input line will look something like this:
PSHELL 8 4 3
Here, PSHELL is the keyword, or the type of thing that you’re modifying. The first number is usually the id, so in this case, it is 8. In this example, there are two attributes, with values 4 and 3, that control something about this PSHELL. As an example, for a PSHELL, the second argument (4) dictates which material card you’re referencing, and the third argument (3) specifies the thickness.
Note
To use NastranMaker without actually defining the traits in your subclass, you can implement the function nastran_maker_hook in your subclass. This function will be called with one argument, the NastranMaker object. It is called after it has processed all the input variables that are visible on traits. The function’s return is ignored. Right after it finishes, NastranMaker writes out the Nastran file that will be run.
Defines NastranMaker, an intelligent bulk data replacer for Nastran files.
Bases: object
A object that performs specified replacements conforming to the Nastran format.
The goal of NastranMaker is to output a Nastran file with a set of variables replaced. It takes an existing Nastran file and variables. In order to retain as much data as possible, it replaces the variables in long format, allowing for 16 characters, instead of 8.
Records what should be replaced where.
Instead of doing the replacing as we are given the variables to replace, we’d like to do all the replacing at one time. Therefore, this function just records what should be replaced.
name: str
cid: int or str Specifies the id of the card.
After specifying the substitutions that should be made, write out the finished product.
This changes self.text and then prints self.text to a file. So, calling write_to_file more than once is unnecessary, although it shouldn’t actually change anything. Also note that the unique_int should be unique within the entire file.
NastranParser tries to parse the grid out of each page of output. It identifies 1) a header for the page, then 2) the grid’s headers, and finally 3) its values. If it parses a page correctly, the query for information is much like querying a database, but much simpler. See the following example.
>>> a = Float(0.0, iotype="out",
nastran_header="displacement vector",
nastran_subcase=1, # this must be an integer
nastran_constraints={"column name" : "value"},
nastran_columns=["column name"])
Once these values are specified, NastranParser will try to find the header in the output, then apply the constraints to the grid, and yield a smaller grid with the viable rows and the acceptable columns (specified by nastran_columns). Note that a is a two-dimensional Python array. Each row will be a row in a grid and will contain only the columns listed in nastran_columns.
NastranParser accepts the name of the header as a string of all lower case letters with sane spacing as well as the header presented in the output file (stripped of spaces at the beginning and end).
Note
As of this writing, if it cannot find the header, it will break. If it cannot find the column names you specify, it will break. Right now, even though you specify a smaller grid of values than you want returned, the value of the variable will be only result[0][0]. This will change in future versions.
One of the main reasons to support retrieving multiple columns is that you can access the parser outside of design variable declaration. NastranComponent has an attribute parser, which is the NastranParser after it’s run Nastran. After you call super(...).execute(), you could retrieve values by calling the parser’s get function, in an identical fashion to the design variable declaration:
>>> displacement_vector = self.parser.get("displacement vector",
1,
{"POINT ID." : "443"},
["T2"])
Do note that displacement_vector is a two-dimensional array. In this example, it has one value ([[value]]), but if more columns or more rows were allowed, you would get a bit bigger two-dimensional array.
self.parser.get has an optional argument that is useful in parsing grids that have more than one value per column. A good example can be found in test/practice-grid.row-width.txt. As you can see, if you wanted to select the data for element id 1, you’d actually want those 15 rows of data. So, you invoke get with the optional argument row_width. By using row_width, once you find a row that satisfies your constraints, it’ll include the remaining (row_width-1) rows in the output.
It is important to understand how NastranParser works. It is a heuristic-based parser. This means that the developers have built something that correctly identifies most grids that they have thrown at it. Since there is no official Nastran output specification, it might not work on your grid. This is a known problem without a known solution.
Another, perhaps more pressing, problem is that NastranParser uses the data in the grid to help the parsing task. This means that if the data changes significantly, you could get different parses. While this is not very likely, it is a possibility. Currently, if this happens, the hope is that the get function will break because you’ll try to access a column that NastranParser doesn’t recognize. While this is a real problem, it is not showstopping because most of the time NastranParser will parse the grid correctly regardless and because, under most runs, the data doesn’t undergo drastic changes. One example of a drastic change would be omitting an entire column of values during one execution and then having values in the next iteration. Another example would be going from a floating point number to 0.0. The problem is that the floating point numbers are long and usually block unnecessary columns from forming. But if there is a column of 0.0, the parsing problem might think there’s an extra column. If you are worried about inconsistencies in parsing, you could isolate the particular grid you are parsing and change.
Defines NastranParser, an object that provides a way of parsing and accessing the data in the grids in Nastran’s output.
Bases: object
Provides access to the grids of Nastran output.
Get some data from the grid that we parsed previously.
You specify the grid you want with the header and a subcase. If you don’t care about the subcase, set it to None. You can also give it a dictionary of constraints and also specify which columns you’d liked returned. The row_width optional attribute is useful if you have something that looks like:
- ELEMENT ID PRICES
- 1 4.00
- 5.00
- 2 38.30
- 60.00
As you can see, each element has two prices. In this case, if we had a constraint that selected only the second element, we would want both prices returned. Therefore, we would set row_width to 2.
Parse the information!
Try to parse grids and headers from the outfile that was given to us beforehand. The basic idea is that we split the entire file by pages. Nastran conveniently labels sections of the output by pages. Each of these pages is expected to have one grid. We parse it by trying to find location of the columns. This is done completely heuristically because of the inconsistency in Nastran’s output specification.
NastranReplacer looks at the Nastran input file and replaces all instances of %varname with the current value of the design variable. The length of varname is limited to seven characters since, along with the percent sign, it must fit in an eight-character block. You can use the same placeholder in multiple places, but it will give you a warning.
The main shortcoming, and the reason why it is the crude way, is that the input variable is placed in the same block as the placeholder variable, which limits its precision. When using an optimizer with a very small step size, it’s possible that eight characters aren’t enough to distinguish between iterations.
There is a secondary mode of operation. If you specify a variable that starts with an asterisk (e.g., %*myvar), NsatranReplacer will overwrite the variable and keep on overwriting for the length of the value. This is useful when you want to insert a value that doesn’t correspond to an eight-character wide block. The best example is if you wanted to replace the number in the line METHOD 103. If you tried replacing it with a normal variable (if you insert XXXXXXXX), you would get either METHOD 1XXXXXXXX or XXXXXXXX03. Using overwrite variables you can insert 104 in the expression METHOD %*n, and it will yield METHOD 104.
The asterisk variables are very useful when replacing variables that aren’t in the bulk data section. When you want to replace a bulk value (in a card), NastranMaker is much more appropriate since it understands the bulk data format. Replacing bulk data with NastranReplacer is highly discouraged.
Defines NastranReplacer, an object which provides a crude and simple search and replace function.
Bases: object
A kind of dummy object that just replaces variables in a text with their corresponding values.
Replace the input_variables in the text with the corresponding values.
Changes self.text. Running this twice should probably error out since it wouldn’t find the variables it was meant to replace.
Defines helpful functions that are used in conjunction with NastranComponent.
Find a string and replace it with another in one big string.
In big_string (probably a line), find old_string and replace it with new_string. Because a lot of Nastran is based on 8-character blocks, this will find the 8-character block currently occupied by old_string and replace that entire block with new_string.
big_string, old_string, new_string: str
Convert thing to a string of a certain length.
This function tries to make the best use of space. For integers and floaters, it will try to get the most signicant digits while staying within length. For everything else, we just try to stick in a string.
Pareto Filter – finds non-dominated cases.
Bases: openmdao.main.component.Component
Takes a set of cases and filters out the subset of cases which are pareto optimal. Assumes that smaller values for model responses are better, so all problems must be posed as minimization problems.
CaseSet with the cases to be filtered to Find the pareto optimal subset.
List of outputs from the case to consider for filtering. Note that only case outputs are allowed as criteria.
Resulting collection of dominated cases.
Resulting collection of pareto optimal cases.
A differentiator is a special object that can be used by a driver to calculate the first or second derivatives of a workflow. The derivatives are calculated from the parameter inputs to the objective and constraint outputs. Any driver that has been decorated with the add_delegate decorator containing the UsesGradients or UsesHessians delegates contains a socket (i.e., Instance trait) called Differentiator. This socket can take a Differentiator object.
The FiniteDifference differentiator provides the gradient vector and Hessian matrix of the workflow using the finite difference method. For first derivatives, you have a choice of forward, backward, or central differencing. Second derivatives are calculated using the standard three-point difference for both on-diagonal and off-diagonal terms.
The FiniteDifference differentiator can be used with the CONMIN or NEWSUMT optimizer by plugging it into the differentiator socket.
from openmdao.main.api import Assembly
from openmdao.lib.drivers.api import NEWSUMTdriver
from openmdao.lib.differentiators.finite_difference import FiniteDifference
from openmdao.examples.simple.paraboloid import Paraboloid
class OptimizationConstrained(Assembly):
"""Constrained optimization of the Paraboloid."""
def __init__(self):
""" Creates a new Assembly containing a Paraboloid and an optimizer"""
super(OptimizationConstrained, self).__init__()
# Create Paraboloid component instances
self.add('paraboloid', Paraboloid_Derivative())
# Create Optimizer instance
self.add('driver', NEWSUMTdriver())
# Driver process definition
self.driver.workflow.add('paraboloid')
# Differentiator
self.driver.differentiator = FiniteDifference(self.driver)
self.driver.differentiator.form = 'central'
self.driver.differentiator.default_stepsize = .0001
# Objective
self.driver.add_objective('paraboloid.f_xy')
# Design Variables
self.driver.add_parameter('paraboloid.x', low=-50., high=50., fd_step=.01)
self.driver.add_parameter('paraboloid.y', low=-50., high=50.)
# Constraints
self.driver.add_constraint('paraboloid.x-paraboloid.y >= 15.0')
The only argument that FiniteDifference takes is the driver you are plugging into.
FiniteDifference has two additional control variables. The form parameter is used to declare which difference the first derivative will use. (The default is 'central'.) The default_stepsize parameter is used to set a default finite difference step size. Note that you can declare a separate finite difference step size for each parameter in the call to add_parameter. Here, the finite difference step size for the input 'x' to paraboloid is set to .01. If you don’t specify fd_step for a parameter, then the default step size is used.
Fake Finite Difference is fully supported by the finite difference generator.
Differentiates a driver’s workflow using the finite difference method. A variety of difference types are available for both first and second order.
Bases: enthought.traits.has_traits.HasTraits
Differentiates a driver’s workflow using the finite difference method. A variety of difference types are available for both first and second order.
Default finite difference step size.
Finite difference form (central, forward, backward)
Returns the Hessian matrix for this Driver’s workflow.
Evaluates a first order central difference.
Evaluates a first order forward or backward difference.
Pseudo package providing a central place to access all of the OpenMDAO doegenerators in the standard library.
DOEgenerator that performs a central composite Design of Experiments. Plugs into the DOEgenerator socket on a DOEdriver.
Bases: enthought.traits.has_traits.HasTraits
DOEgenerator that performs a central composite Design of Experiments. Plugs into the DOEgenerator socket on a DOEdriver.
Number of independent parameters in the DOE.
Type of central composite design
The FullFactorial DOEgenerator implements a full factorial Design of Experiments; that is, it generates a set of design points that fully span the range of the parameters at the requested resolution. It plugs into the DOEgenerator socket on a DOEdriver.
Bases: enthought.traits.has_traits.HasTraits
DOEgenerator that performs a full-factorial Design of Experiments. Plugs into the DOEgenerator socket on a DOEdriver.
Number of levels of values for each parameter.
Number of independent parameters in the DOE.
The OptLatinHypercube component implements an algorithm that produces an optimal Latin hypercube based on an evolutionary optimization of its Morris-Mitchell sampling criterion.
Bases: object
Bases: enthought.traits.has_traits.HasTraits
IDOEgenerator which provides a Latin hypercube DOE sample set. The Morris-Mitchell sampling criterion of the DOE is optimzied using an evolutionary algorithm.
Number of generations the optimization will evolve over.
Vector norm calculation method. ‘1-norm’ is faster, but less accurate.
Number of parameters, or dimensions, for the DOE.
Number of sample points in the DOE sample set.
Size of the population used in the evolutionary optimization.
DOEgenerator that performs a uniform space-filling Design of Experiments. Plugs into the DOEgenerator socket on a DOEdriver.
Bases: enthought.traits.has_traits.HasTraits
DOEgenerator that performs a space-filling Design of Experiments with uniform distributions on all design variables. Plugs into the DOEgenerator socket on a DOEdriver.
number of independent parameters in the DOE
number of total samples in the DOE
Pseudo package providing a central place to access all of the OpenMDAO drivers in the standard library.
The BroydenSolver can be used to solve for the set of inputs (independents) that are needed to to make a model satisfy an equation (the dependent equation) that is a function of model outputs. BroydenSolver is based on the quasi-Newton-Raphson algorithms found in SciPy’s nonlinear solver library. As the name implies, a Broyden update is used to approximate the Jacobian matrix. In fact, no Jacobian is evaluated with these algorithms, so they are quicker than a full Newton solver, but they may not be suitable for all problems.
To see how to use the BroydenSolver, consider a problem where we’d like to solve for the intersection of a line and a parabola. We can implement this as a single component.
from openmdao.lib.datatypes.api import Float
from openmdao.main.api import Component
class MIMOSystem(Component):
""" Two equations, two unknowns """
x = Float(10.0, iotype="in", doc="Input 1")
y = Float(10.0, iotype="in", doc="Input 2")
f_xy = Float(0.0, iotype="out", doc="Output 1")
g_xy = Float(0.0, iotype="out", doc="Output 2")
def execute(self):
""" Evaluate:
f_xy = 2.0*x**2 - y + 2.0
g_xy = 2.0*x - y - 4.0
"""
x = self.x
y = self.y
self.f_xy = 2.0*x**2 - y + 2.0
self.g_xy = 2.0*x - y + 4.0
Notice that this is a two-input problem – the variables are x and y. There are also two equations that need to be satisfied: the equation for the line and the equation for the parabola. There are actually two solutions to this set of equations. The solver will return the first one that it finds. You can usually find other solutions by starting the solution from different initial points. We start at (10, 10), as designated by the default values for the variables x and y.
Next, we build a model that uses the BroydenSolver to find a root for the equations defined in MIMOSystem.
from openmdao.lib.drivers.api import BroydenSolver
from openmdao.main.api import Assembly
class SolutionAssembly(Assembly):
""" Solves for the root of MIMOSystem. """
def __init__(self):
""" Creates a new Assembly with this problem
root at (0,1)
"""
super(SolutionAssembly, self).__init__()
self.add('driver', BroydenSolver())
self.add('problem', MIMOSystem())
self.driver.workflow.add('problem')
self.driver.add_parameter('problem.x', low=-1.0e99, high=1.0e99)
self.driver.add_parameter('problem.y', low=-1.0e99, high=1.0e99)
self.driver.add_constraint('problem.f_xy = 0.0')
self.driver.add_constraint('problem.g_xy = 0.0')
self.driver.itmax = 20
self.driver.alpha = .4
self.driver.tol = .000000001
The parameters are the independent variables that the solver is allowed to vary. The method add_parameter is used to define these. Broyden does not utilize the low and high arguments, so they are set to some large arbitrary negative and positive values.
The equations that we want to satisfy are added as equality constraints using the add_constraint method. We want to find x and y that satisfy f_xy=0 and g_xy=0, so these two equations are added to the solver.
Both the add_parameter and add_constraint methods are presented in more detail in Tutorial: MDAO Architectures.
The resulting solution should yield:
>>> top = SolutionAssembly()
>>> top.run()
>>> print top.problem.x, top.problem.y
1.61... 7.23...
Five parameters control the solution process in the BroydenSolver.
broyden2: Broyden’s second method – the same as broyden1 but updates the inverse Jacobian directly
broyden3: Broyden’s third method – the same as broyden2, but instead of directly computing the inverse Jacobian, it remembers how to construct it using vectors. When computing inv(J)*F, it uses those vectors to compute this product, thus avoiding the expensive NxN matrix multiplication.
excitingmixing: The excitingmixing algorithm. J=-1/alpha
The default value for algorithm is "broyden2".
self.driver.algorithm = "broyden2"
This parameter specifies the maximum number of iterations before BroydenSolver terminates. The default value is 10.
self.driver.itmax = 10
This parameter specifies the mixing coefficient for the algorithm. The mixing coefficient is a linear scale factor applied to the update of the parameters, so increasing it can lead to quicker convergence but can also lead to instability. The default value is 0.4. If you use the excitingmixing algorithm, you should try a lower value, such as 0.1.
self.driver.alpha = 0.1
Convergence tolerance for the solution. Iteration ends when the constraint equation is satisfied within this tolerance. The default value is 0.00001.
self.driver.tol = 0.00001
This parameter is only used for the excitingmixing algorithm where the mixing coefficient is adaptively adjusted. It specifies the maximum allowable mixing coefficient for adaptation. The default value is 1.0.
self.driver.alphamax = 1.0
broyednsolver.py – Solver based on the nonlinear solvers found in Scipy.Optimize.
Bases: openmdao.main.driver.Driver
MIMO Newton-Raphson Solver with Broyden approximation to the Jacobian. Algorithms are based on those found in scipy.optimize.
Nonlinear solvers
These solvers find x for which F(x)=0. Both x and F are multidimensional.
All solvers have the parameter iter (the number of iterations to compute); some of them have other parameters of the solver. See the particular solver for details.
A collection of general-purpose nonlinear multidimensional solvers.
The broyden2 is the best. For large systems, use broyden3; excitingmixing is also very effective. The remaining nonlinear solvers from SciPy are, in their own words, of “mediocre quality,” so they were not implemented.
Algorithm to use. Choose from broyden2, broyden3, and excitingmixing.
Mixing Coefficient.
Maximum Mixing Coefficient (only used with excitingmixing.)
Maximum number of iterations before termination.
Convergence tolerance. If the norm of the independent vector is lower than this, then terminate successfully.
Adds a constraint in the form of a boolean expression string to the driver.
Parameters:
Adds a parameter or group of parameters to the driver.
If neither “low” nor “high” is specified, the min and max will default to the values in the metadata of the variable being referenced. If they are not specified in the metadata and not provided as arguments, a ValueError is raised.
Returns True if types is [‘eq’].
Returns True if this Driver supports parameters of the give types.
Removes all constraints.
Removes all parameters.
Returns a list of tuples of the form (lhs, rhs, comparator, is_violated).
From SciPy, Broyden’s second method.
Updates inverse Jacobian by an optimal formula. There is NxN matrix multiplication in every iteration.
The best norm(F(x))=0.003 achieved in ~20 iterations.
from scipy, Broyden’s second (sic) method.
Updates inverse Jacobian by an optimal formula. The NxN matrix multiplication is avoided.
The best norm(F(x))=0.003 achieved in ~20 iterations.
from scipy, The excitingmixing method.
J=-1/alpha
The best norm(F(x))=0.005 achieved in ~140 iterations.
Note: SciPy uses 0.1 as the default value for alpha for this algorithm. Ours is set at 0.4, which is appropriate for Broyden2 and Broyden3, so adjust it accordingly if there are problems.
Returns an ordered dict of constraint objects.
Returns an ordered dict of parameter objects.
Return a list of strings containing constraint expressions.
Returns a list of parameter targets. Note that this list may contain more entries than the list of Parameter and ParameterGroup objects since ParameterGroups have multiple targets.
Removes the constraint with the given string.
Removes the parameter with the given name.
Pushes the values in the iterator ‘values’ into the corresponding variables in the model. If the ‘case’ arg is supplied, the values will be set into the case and not into the model.
This should be called during the Driver’s __init__ function to specify what types of parameters that the Driver supports.
Bases: openmdao.main.driver.Driver
A base class for Drivers that run sets of cases in a manner similar to the ROSE framework. Concurrent evaluation is supported, with the various evaluations executed across servers obtained from the ResourceAllocationManager.
If ABORT, any error stops the evaluation of the whole set of cases.
Maximum number of times to retry a failed case.
If True, reload the model between executions.
If True, evaluate cases sequentially.
Runs all cases and records results in recorder. Uses setup() and resume() with default arguments.
Resume execution.
Bases: openmdao.lib.drivers.caseiterdriver.CaseIterDriverBase
Run a set of cases provided by an ICaseIterator. Concurrent evaluation is supported, with the various evaluations executed across servers obtained from the ResourceAllocationManager.
Iterator supplying Cases to evaluate.
CONMIN is a Fortran program written as a subroutine to solve linear or nonlinear constrained optimization problems. The basic optimization algorithm is the Method of Feasible Directions. If analytic gradients of the objective or constraint functions are not available (i.e., if a Differentiator is not plugged into the differentiator socket), then the gradients are calculated by CONMIN’s internal finite difference code. While the program is intended primarily for efficient solution of constrained problems, unconstrained function minimization problems may also be solved. The conjugate direction method of Fletcher and Reeves is used for this purpose.
More information on CONMIN can be found in the CONMIN User’s Manual. (In the simple tutorial, CONMIN is used for an unconstrained and a constrained optimization.)
CONMIN has been included in the OpenMDAO standard library to provide users with a basic gradient-based optimization algorithm.
Basic Interface
The CONMIN code contains a number of different parameters and switches that are useful for controlling the optimization process. These can be subdivided into those parameters that will be used in a typical optimization problem and those that are more likely to be used by an expert user.
For the simplest possible unconstrained optimization problem, CONMIN just needs an objective function and one or more decision variables (parameters.) The basic interface conforms to OpenMDAO’s driver API, which is discussed in The Driver API, and covers how to assign design variables, constraints, and objectives.
The OpenMDAO CONMIN driver can be imported from openmdao.lib.drivers.api.
from openmdao.lib.drivers.api import CONMINdriver
Typically, CONMIN will be used as a driver in the top level assembly, though it also can be used in a subassembly as part of a nested driver scheme. Using the OpenMDAO script interface, a simple optimization problem can be set up as follows:
from openmdao.main.api import Assembly
from openmdao.examples.enginedesign.vehicle import Vehicle
from openmdao.lib.drivers.api import CONMINdriver
class EngineOptimization(Assembly):
""" Top level assembly for optimizing a vehicle. """
def __init__(self):
""" Creates a new Assembly for vehicle performance optimization."""
super(EngineOptimization, self).__init__()
# Create CONMIN Optimizer instance
self.add('driver', CONMINdriver())
# Create Vehicle instance
self.add('vehicle', Vehicle())
# add Vehicle to optimizer workflow
self.driver.workflow.add('vehicle')
# CONMIN Flags
self.driver.iprint = 0
self.driver.itmax = 30
# CONMIN Objective
self.driver.add_objective('vehicle.fuel_burn')
# CONMIN Design Variables
self.driver.add_parameter('vehicle.spark_angle', low=-50. , high=10.)
self.driver.add_parameter('vehicle.bore', low=65. , high=100.)
This first section of code defines an assembly called EngineOptimization. This assembly contains a DrivingSim component and a CONMINdriver, both of which are created and added inside the __init__ function with add. The DrivingSim component is also added to the driver’s workflow. The objective function, design variables, constraints, and any CONMIN parameters are also assigned in the __init__ function. The specific syntax for all of these is discussed in The Driver API.
Controlling the Optimization
It is often necessary to control the convergence criteria for an optimization. The CONMIN driver allows control over both the number of iterations before termination as well as the convergence tolerance (both absolute and relative).
The maximum number of iterations is specified by setting the itmax parameter. The default value is 10.
self.driver.itmax = 30
The convergence tolerance is controlled with dabfun and delfun. Dabfun is the absolute change in the objective function to indicate convergence (i.e., if the objective function changes by less than dabfun, then the problem is converged). Similarly, delfun is the relative change of the objective function with respect to the value at the previous step. Note that delfun has a hard-wired minimum of 1e-10 in the Fortran code, and dabfun has a minimum of 0.0001.
self.driver.dabfun = .001
self.driver.delfun = .1
All of these convergence checks are always active during optimization. The tests are performed in the following sequence:
The number of successive iterations that the convergence tolerance should be checked before terminating the loop can also be specified with the itrm parameter, whose default value is 3.
self.driver.itrm = 3
CONMIN can calculate the gradient of both the objective functions and of the constraints using a finite difference approximation. This is the default behavior if no Differentiator is plugged into the differentiator socket. Two parameters control the step size used for numerically estimating the local gradient: fdch and fdchm. The fdchm parameter is the minimum absolute step size that the finite difference will use, and fdch is the step size relative to the design variable.
self.driver.fdch = .0001
self.driver.fdchm = .0001
Note
The default values of fdch and fdchm are set to 0.01. This may be too large for some problems and will manifest itself by converging to a value that is not the minimum. It is important to evaluate the scale of the objective function around the optimum so that these can be chosen well.
You can also replace CONMIN’s finite difference with OpenMDAO’s built-in capability by inserting a differentiator into the Differentiator slot in the driver, as shown in Calculating Derivatives with Finite Difference.
For certain problems, it is desirable to scale the inputs. Several scaling options are available, as summarized here:
Value | Result |
---|---|
nscal < 0 | User-defined scaling with the vector in scal |
nscal = 0 | No scaling of the design variables |
nscal > 0 | Scale the design variables every NSCAL iteration. Please see the CONMIN User’s Manual for additional notes about using this option. |
The default setting is nscal=0 for no scaling of the design variables. The nscal parameter can be set to a negative number to turn on user-defined scaling. When this is enabled, the array of values in the vector scal is used to scale the design variables.
self.driver.scal = [10.0, 10.0, 10.0, 10.0]
self.driver.nscal = -1
There need to be as many scale values as there are design variables.
If your problem uses linear constraints, you can improve the efficiency of the optimization process by designating those that are linear functions of the design variables as follows:
map(self.driver.add_constraint, ['vehicle.stroke < vehicle.bore',
'vehicle.stroke * vehicle.bore > 1.0'])
self.driver.cons_is_linear = [1, 0]
Here, the first constraint is linear, and the second constraint is nonlinear. If cons_is_linear is not specified, then all the constraints are assumed to be nonlinear. Note that the original CONMIN parameter for this is ISC. If your constraint includes some framework output in the equation, then it is probably not a linear function of the design variables.
Finally, the iprint parameter can be used to display diagnostic messages inside of CONMIN. These messages are currently sent to the standard output.
self.driver.iprint = 0
Higher positive values of iprint turn on the display of more levels of output, as summarized below.
Value | Result |
---|---|
iprint = 0 | All output is suppressed |
iprint = 1 | Print initial and final function information |
iprint = 2 | Debug level 1: All of the above plus control parameters |
iprint = 3 | Debug level 2: All of the above plus all constraint values, number of active/violated constraints, direction vectors, move parameters, and miscellaneous information |
iprint = 4 | Complete debug: All of the above plus objective function gradients, active and violated constraint gradients, and miscellaneous information |
iprint = 5 | All of above plus each proposed design vector, objective and constraints during the one-dimensional search |
iprint = 101 | All of above plus a dump of the arguments passed to subroutine CONMIN |
Advanced Options
The following options exercise some of the more advanced capabilities of CONMIN. The details given here briefly summarize the effects of these parameters; more information is available in the CONMIN User’s Manual.
conmindriver.py - Driver for the CONMIN optimizer.
The CONMIN driver can be a good example of how to wrap another Driver for OpenMDAO. However, there are some things to keep in mind.
1. This implementation of the CONMIN Fortran driver is interruptable, in that control is returned every time an objective or constraint evaluation is needed. Most external optimizers just expect a function to be passed for these, so they require a slightly different driver implementation than this.
2. The CONMIN driver is a direct wrap, and all inputs are passed using Numpy arrays. This means there are a lot of locally stored variables that you might not need for a pure Python optimizer.
Ultimately, if you are wrapping a new optimizer for OpenMDAO, you should endeavour to understand the purpose for each statement so that your implementation doesn’t do any unneccessary or redundant calculation.
Bases: openmdao.main.driver.Driver
Driver wrapper of Fortran version of CONMIN.
0: Initial and final state
1: Initial evaluation of objective and constraint values
2: Evalute gradients of objective and constraints (internal only)
3: Evalute gradients of objective and constraints
4: One-dimensional search on unconstrained function
5: Solve 1D search problem for unconstrained function
Array designating whether each constraint is linear.
Constraint thickness parameter.
Constraint thickness parameter for linear and side constraints.
Minimum absolute value of ctl used in optimization.
Minimum absolute value of ct used in optimization.
Absolute convergence tolerance.
Relative convergence tolerance.
Relative change in parameters when calculating finite difference gradients. (only when CONMIN calculates gradient)
Minimum absolute step in finite difference gradient calculations. (only when CONMIN calculates gradient)
Conjugate gradient restart parameter.
Print information during CONMIN solution. Higher values are more verbose.
Maximum number of iterations before termination.
Number of consecutive iterations to indicate convergence (relative or absolute).
Linear objective function flag. Set to True if objective is linear
Scaling control parameter – controls scaling of decision variables.
Participation coefficient - penalty parameter that pushes designs towards the feasible region.
List of extra variables to output in the recorder.
Array of scaling factors for the parameters.
Mean value of the push-off factor in the method of feasible directions.
Adds a constraint in the form of a boolean expression string to the driver.
Adds an objective to the driver.
Takes an iterator of objective strings and creates objectives for them in the driver.
Adds a parameter or group of parameters to the driver.
If neither “low” nor “high” is specified, the min and max will default to the values in the metadata of the variable being referenced. If they are not specified in the metadata and not provided as arguments, a ValueError is raised.
Returns True if types is [‘ineq’].
Returns True if this Driver supports parameters of the give types.
Run check_derivatives on our workflow.
Removes all constraints.
Removes all objectives.
Removes all parameters.
Returns a list of constraint values
Returns a list of values of the evaluated objectives.
Returns a list of values of the evaluated objectives.
Returns an ordered dict of inequality constraint objects.
Returns an OrderedDict of objective expressions.
Returns an ordered dict of parameter objects.
Return a list of strings containing constraint expressions.
Returns a list of parameter targets. Note that this list may contain more entries than the list of Parameter and ParameterGroup objects since ParameterGroups have multiple targets.
Removes the constraint with the given string.
Removes the specified objective expression. Spaces within the expression are ignored.
Removes the parameter with the given name.
Pushes the values in the iterator ‘values’ into the corresponding variables in the model. If the ‘case’ arg is supplied, the values will be set into the case and not into the model.
This should be called during the Driver’s __init__ function to specify what types of parameters that the Driver supports.
The DOEdriver provides the capability to execute a DOE on a workflow. This Driver supports the IHasParameters interface. At execution time, the driver will use the list of parameters added to it by the user to create a specific DOE and then iteratively execute the DOE cases on the workflow.
Users can pick from any of the DOEgenerators provided in the standard library or provide their own custom instance of a DOEgenerator. A DOEgenerator must be plugged into the DOEgenerator socket on the DOEdriver in order to operate.
from openmdao.main.api import Assembly from openmdao.lib.drivers.api import DOEdriver from openmdao.lib.doegenerators.api import FullFactorial from openmdao.examples.expected_improvement.branin_component import BraninComponent class Analysis(Assembly): def __init__(self,doc=None): super(Analysis,self).__init__() self.add('branin', BraninComponent()) self.add('driver', DOEdriver()) self.driver.workflow.add('branin') self.driver.add_parameter('branin.x') self.driver.add_parameter('branin.y') #use a full factorial DOE with 2 variables, and 3 levels # for each variable self.driver.DOEgenerator = FullFactorial(num_levels=3)
The min and max metadata of the parameters are used to denote the range for each variable over which the DOE will span.
doedriver.py – Driver that executes a Design of Experiments.
Bases: openmdao.lib.drivers.caseiterdriver.CaseIterDriverBase
Driver for Design of Experiments
Iterator supplying normalized DOE values.
A list of outputs to be saved with each case.
Adds a parameter or group of parameters to the driver.
If neither “low” nor “high” is specified, the min and max will default to the values in the metadata of the variable being referenced. If they are not specified in the metadata and not provided as arguments, a ValueError is raised.
Returns True if this Driver supports parameters of the give types.
Removes all parameters.
Returns an ordered dict of parameter objects.
Returns a list of parameter targets. Note that this list may contain more entries than the list of Parameter and ParameterGroup objects since ParameterGroups have multiple targets.
Removes the parameter with the given name.
Pushes the values in the iterator ‘values’ into the corresponding variables in the model. If the ‘case’ arg is supplied, the values will be set into the case and not into the model.
This should be called during the Driver’s __init__ function to specify what types of parameters that the Driver supports.
Genetic is a driver which performs optimization using a genetic algorithm based on Pyevolve. Genetic is a global optimizer and is ideal for optimizing problems with integer or discrete design variables because it is a non-derivative based optimization method.
Genetic can be used in any simulation by importing it from openmdao.lib.drivers.api:
from openmdao.lib.drivers.api import Genetic
Design Variables
IOtraits are added to Genetic and become optimization parameters. Genetic will vary the set of parameters to search for an optimum. Genetic supports three variable types: Float, Int, and Enum. These types can be used as parameters in any optimization.
You add design variables to Genetic using the add_parameter method.
from openmdao.main.api import Assembly,Component, set_as_top
from openmdao.lib.drivers.api import Genetic
from openmdao.lib.datatypes.api import Float,Int,Enum
class SomeComp(Component):
"""Arbitrary component with a few variables, but which does not really do
any calculations
"""
w = Float(0.0, low=-10, high=10, iotype="in")
x = Float(0.0, low=0.0, high=100.0, iotype="in")
y = Int(10, low=10, high=100, iotype="in")
z = Enum([-10, -5, 0, 7], iotype="in")
class Simulation(Assembly):
"""Top Level Assembly used for simulation"""
def __init__(self):
"""Adds the Genetic driver to the assembly"""
super(Simulation,self).__init__()
self.add('driver', Genetic())
self.add('comp', SomeComp())
# Driver process definition
self.driver.workflow.add('comp')
self.driver.add_parameter('comp.x')
self.driver.add_parameter('comp.y')
self.driver.add_parameter('comp.z')
top = Simulation()
set_as_top(top)
In the above example, three parameters were added to the optimizer. The optimizer figures out for itself what type of variable it is and behaves appropriately. In all three cases, since no low or high arguments were provided, the optimizer will use the values from the metadata provided in the variable deceleration.
For comp.x the optimizer will try floats between 0.0 and 100.0. For comp.y the optimizer will try integers between 10 and 100. For comp.z the optimizer will pick from the list of allowed values: [-10,-5,0,7].
You can override the low and high values from the metadata if you want the optimizer to use a different range instead of the default.
top.driver.add_parameter('comp.w', low=5.0, high=7.0)
Now, for comp.x the optimizer will only try values between 5.0 and 7.0. Note that low and high are applicable only to Float and Int variables. For Enum variables, low and high are not applicable.
Configuration
When setting the objective you can specify a single variable name or a more complex function, such as
top.driver.add_objective("comp.x")
or
top.driver.clear_objectives()
top.driver.add_objective("2*comp.x + comp.y + 3*comp.z")
In the second example above, a more complex objective function was created where the overall objective was a weighted combination of comp.x, comp.y, and comp.z.
To set the optimizer to either minimize or maximize your objective, you set the opt_type variable of Genetic to "minimize" or "maximize.
top.driver.opt_type = "minimize"
You can control the size of the population in each generation and the maximum number of generations in your optimization with the population_size and generations variables.
top.driver.population_size = 80
top.driver.generations = 100
As you increase the population size, you are effectively adding diversity in to the gene pool of your optimization. A large population means that a larger number of individuals from a given generation will be chosen to provide genetic material for the next generation. So there is a better chance that weaker individuals will pass on their genes. This diversity helps to ensure that your optimization will find a true global optimum within the allowed design space. However, it also serves to slow down the optimization because of the increased number of function evaluations necessary for each generation.
Picking an appropriate value for the maximum number of generations will depend highly on the specifics of your problem. Setting this number too low will likely prevent the optimization from converging on a true optimum. Setting it too high will help you find the true optimum, but you may end up wasting the computation time on later generations where the optimum has been found.
You can further control the behavior of the genetic algorithm by setting the crossover_rate, mutation_rate, selection_method, and elitism variables. These settings will allow you to fine-tune the convergence of your optimization to achieve the desired result; however, for many optimizations the default values will work well and won’t need to be changed.
The crossover_rate controls the rate at which the crossover operator gets applied to the genome of a set of individuals who are reproducing. The allowed values are between 0.0 and 1.0. A higher rate will mean that more of the genes are swapped between parents. The result will be a more uniform population and better searching of the design space. If the rate is set too high, then it is likely that stronger individuals could be lost to churn.
top.driver.crossover_rate = 0.9
The mutation_rate controls how likely any particular gene is to experience a mutation. A low, but non-zero, mutation rate will help prevent stagnation in the gene pool by randomly moving the values of genes. If this rate is set too high, the algorithm basically degrades into a random search through the design space. The allowed values are between 0.0 and 1.0.
top.driver.mutation_rate = .02
In a pure genetic algorithm, it is possible that your best performing individual will not survive from one generation to the next due to competition, mutation, and crossover. If you want to ensure that the best individual survives intact from one generation to the next, then turn on the elitism flag for your optimization. This will ensure that the best individual is always copied to the next generation no matter what.
top.driver.elitism = True
A number of different commonly used selection algorithms are available. The default algorithm is the Roulette Wheel Algorithm, but Tournament Selection, Rank Selection, and Uniform Selection are also available. The selection_method variable allows you to select the algorithm; allowed values are: "roulette_wheel," "tournament," "rank," and "uniform".
top.driver.selection_method="rank"
A simple Pyevolve-based driver for OpenMDAO.
Bases: openmdao.main.driver.Driver
Genetic algorithm for the OpenMDAO framework, based on the Pyevolve Genetic algorithm module.
The crossover rate used when two parent genomes reproduce to form a child genome.
Controls the use of elitism in the creation of new generations.
The maximum number of generations the algorithm will evolve to before stopping.
The mutation rate applied to population members.
Sets the optimization to either minimize or maximize the objective function.
The size of the population in each generation.
Random seed for the optimizer. Set to a specific value for repeatable results; otherwise leave as None for truly random seeding.
The selection method used to pick population members who will survive for breeding into the next generation.
The genome with the best score from the optimization.
Adds an objective to the driver.
Takes an iterator of objective strings and creates objectives for them in the driver.
Adds a parameter or group of parameters to the driver.
If neither “low” nor “high” is specified, the min and max will default to the values in the metadata of the variable being referenced. If they are not specified in the metadata and not provided as arguments, a ValueError is raised.
Returns True if this Driver supports parameters of the give types.
Removes all objectives.
Removes all parameters.
Returns a list of values of the evaluated objectives.
Returns a list of values of the evaluated objectives.
Returns an OrderedDict of objective expressions.
Returns an ordered dict of parameter objects.
Returns a list of parameter targets. Note that this list may contain more entries than the list of Parameter and ParameterGroup objects since ParameterGroups have multiple targets.
Removes the specified objective expression. Spaces within the expression are ignored.
Removes the parameter with the given name.
Pushes the values in the iterator ‘values’ into the corresponding variables in the model. If the ‘case’ arg is supplied, the values will be set into the case and not into the model.
This should be called during the Driver’s __init__ function to specify what types of parameters that the Driver supports.
The FixedPointIterator is a simple solver that can solve a single-input single-output problem using fixed point iteration. It provides a way to iterate on a single input to match an output. In other words, fixed point iteration can be used to solve the equation x = f(x). By extension, FixedPointIterator can be used to close a loop in the data flow. The algorithm is useful for some problems, so it is included here. However, it may require more functional evaluations than the BroydenSolver.
As an example, let’s implement a component that can be run iteratively to produce the square root of a number.
from openmdao.lib.datatypes.api import Float
from openmdao.main.api import Component
class Babylonian(Component):
""" The Babylonians had a neat way of calculating square
roots using Fixed Point Iteration"""
x = Float(1.0, iotype="in", doc="Input is x")
y = Float(iotype="out", doc="Output is y")
def execute(self):
""" Iterate to find the square root of 2, the Babylonian way:
"""
x = self.x
self.y = 0.5*(2.0/x + x)
An assembly with this component and the FixedPointIterator would look like this.
from openmdao.lib.drivers.api import FixedPointIterator
from openmdao.main.api import Assembly
class SolutionAssembly(Assembly):
""" Solves for the root of MIMOSystem. """
def __init__(self):
""" Creates a new Assembly with this problem
the answer should be 1.4142.....
"""
super(SolutionAssembly, self).__init__()
self.add('driver', FixedPointIterator())
self.add('problem', Babylonian())
self.driver.workflow.add('problem')
# Set our independent and dependent
self.driver.add_parameter('problem.x', low=-9.e99, high=9.e99)
self.driver.add_constraint('problem.y = problem.x')
The x input and the F(x) output equation are specified as string expressions using the add_parameter and add_constraint methods. The constraint contains the equation x = f(x), which we are trying to solve. Note that this is a single-input single-output method, so it is only valid to specify one constraint and one parameter.
>>> top = SolutionAssembly()
>>> top.run()
>>> print top.problem.x
1.4142...
Two additional parameters control the FixedPointIterator. The parameter tolerance sets the convergence tolerance for the comparison between value of x_out at the current iteration and the previous iteration. The default value for tolerance is 0.00001. The parameter max_iteration specifies the number of iterations to run. The default value for max_iterations is 25.
A more useful example in which the FixedPointIterator is used to converge two coupled components is shown in Tutorial: MDAO Architectures. Source Documentation for iterate.py ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A simple iteration driver. Basically runs a workflow, passing the output to the input for the next iteration. Relative change and number of iterations are used as termination criteria.
Bases: openmdao.main.driver.Driver
A simple fixed point iteration driver, which runs a workflow and passes the value from the output to the input for the next iteration. Relative change and number of iterations are used as termination criterea.
Maximum number of iterations before termination.
Absolute convergence tolerance between iterations.
Adds a constraint in the form of a boolean expression string to the driver.
Parameters:
Adds a parameter or group of parameters to the driver.
If neither “low” nor “high” is specified, the min and max will default to the values in the metadata of the variable being referenced. If they are not specified in the metadata and not provided as arguments, a ValueError is raised.
Returns True if types is [‘eq’].
Returns True if this Driver supports parameters of the give types.
Removes all constraints.
Removes all parameters.
Returns a list of tuples of the form (lhs, rhs, comparator, is_violated).
Returns an ordered dict of constraint objects.
Returns an ordered dict of parameter objects.
Return a list of strings containing constraint expressions.
Returns a list of parameter targets. Note that this list may contain more entries than the list of Parameter and ParameterGroup objects since ParameterGroups have multiple targets.
Removes the constraint with the given string.
Removes the parameter with the given name.
Pushes the values in the iterator ‘values’ into the corresponding variables in the model. If the ‘case’ arg is supplied, the values will be set into the case and not into the model.
This should be called during the Driver’s __init__ function to specify what types of parameters that the Driver supports.
Bases: openmdao.main.driver.Driver
A simple driver to run a workflow until some stop condition is met
maximun number of iterations
If True, driver will ignore stop conditions for the first iteration, and run at least one iteration
current iteration counter
Removes all stop conditions.
Returns a list of evaluated stop conditions.
Returns a list of stop condition strings.
Removes the stop condition matching the given string.
Return True if any of the stopping conditions evaluate to True.
NEWSUMT is a Fortran subroutine for solving linear and nonlinear constrained or unconstrained function minimization problems. It has been included in the OpenMDAO standard library to provide users with a basic gradient-based optimization algorithm.
The minimization algorithm used in NEWSUMT is a sequence of unconstrained minimizations technique (SUMT) where the modified Newton’s method is used for unconstrained function minimizations.
If analytic gradients of the objective or constraint functions are not available, this information is calculated by finite difference.
NEWSUMT treats inequality constraints in a way that is especially well suited to engineering design applications.
More information on NEWSUMT can be found in the NEWSUMT Users Guide.
Basic Interface
The NEWSUMT code contains a number of different parameters and switches that are useful for controlling the optimization process. These can be subdivided into those parameters that will be used in a typical optimization problem and those that are more likely to be used by an expert user.
For the simplest possible unconstrained optimization problem, NEWSUMT just needs an objective function and one or more decision variables (parameters.) The basic interface conforms to OpenMDAO’s driver API, which is discussed in The Driver API. This document covers how to assign design variables, constraints, and objectives.
The OpenMDAO NEWSUMT driver can be imported from openmdao.lib.drivers.api.
from openmdao.lib.drivers.api import NEWSUMTdriver
Typically, NEWSUMT will be used as a driver in the top level assembly, though it can be also used in a subassembly as part of a nested driver scheme. Using the OpenMDAO script interface, a simple optimization problem can be set up as follows:
from openmdao.main.api import Assembly
from openmdao.examples.enginedesign.vehicle import Vehicle
from openmdao.lib.drivers.api import CONMINdriver
class EngineOptimization(Assembly):
""" Top level assembly for optimizing a vehicle. """
def __init__(self):
""" Creates a new Assembly for vehicle performance optimization."""
super(EngineOptimization, self).__init__()
# Create CONMIN Optimizer instance
self.add('driver', NEWSUMTdriver())
# Create Vehicle instance
self.add('vehicle', Vehicle())
# add Vehicle to optimizer workflow
self.driver.workflow.add('vehicle')
# CONMIN Flags
self.driver.iprint = 0
self.driver.itmax = 30
# CONMIN Objective
self.driver.add_objective('vehicle.fuel_burn')
# CONMIN Design Variables
self.driver.add_parameter('vehicle.spark_angle', low=-50. , high=10.)
self.driver.add_parameter('vehicle.bore', low=65. , high=100.)
This first section of code defines an assembly called EngineOptimization. This assembly contains a DrivingSim component and a NEWSUMTdriver, both of which are created and added inside the __init__ function with add. The DrivingSim component is also added to the driver’s workflow. The objective function, design variables, constraints, and any NEWSUMT parameters are also assigned in the __init__ function. The specific syntax for all of these is discussed in The Driver API.
Basic Parameters
This section contains the basic parameters for NEWSUMT.
The default behavior for NEWSUMT is to calculate its own gradients and Hessians of the objective and constraints using a first-order forward finite difference. The second derivatives are approximated from the first order differences. You can replace NEWSUMT’s finite difference with OpenMDAO’s built-in capability by inserting a differentiator into the Differentiator slot in the driver, as shown in Calculating Derivatives with Finite Difference.
If you want to use NEWSUMT for the finite difference calculation and want the same finite difference step size in all your variables, you can set the default_fd_stepsize parameter.
self.driver.default_fd_stepsize = .0025
The default step size will be used for all parameters for which you have not set the fd_step attribute.
When using NEWSUMT, if you have any linear constraints, it may be advantageous to specify them as such so that NEWSUMT can treat them differently. Use the integer array ilin to designate whether a constraint is linear. A value of 0 indicates that that constraint is non-linear, while a value of 1 indicates that that the constraint is linear. This parameter is optional, and when it is omitted, all constraints are assumed to be nonlinear.
map(self.driver.add_constraint, ['vehicle.stroke < vehicle.bore',
'vehicle.stroke * vehicle.bore > 1.0'])
self.driver.ilin_linear = [1, 0]
Similarly, NEWSUMT has a flag parameter to indicate whether the objective function is linear or nonlinear. Setting lobj to 1 indicates a linear objective function. Setting it to 0, which is the default value, indicates a nonlinear objective function.
self.driver.lobj = 0
The jprint parameter can be used to display diagnostic messages. These messages are currently sent to the standard output.
self.driver.jprint = 0
Higher positive values of jprint turn on the display of more levels of output, as summarized below.
Value | Result |
---|---|
jprint = -1 | All output is suppressed, including warnings |
jprint = 0 | Print initial and final designs only |
jprint = 1 | Print brief results of analysis for initial and final designs together with minimal intermediate information |
jprint = 2 | Detailed printing |
jprint = 3 | Debugging printing |
Controlling the Optimization
NEWSUMT provides a variety of parameters to control the convergence criteria for an optimization.
The maximum number of iterations is specified by setting the itmax parameter. The default value is 10.
self.driver.itmax = 30
The convergence tolerance is controlled with six parameters. The following table summarizes these parameters.
Parameter | Description | Default |
---|---|---|
epsgsn | Convergence criteria of the golden section algorithm used for the one-dimensional minimization | 0.001 |
epsodm | Convergence criteria of the unconstrained minimization | 0.001 |
epsrsf | Convergence criteria for the overall process | 0.001 |
maxgsn | Maximum allowable number of golden section iterations used for 1D minimization | 20 |
maxodm | Maximum allowable number of one-dimensional minimizations | 6 |
maxrsf | Maximum allowable number of unconstrained minimizations | 15 |
self.driver.epsgsn = .000001
self.driver.maxgsn = 40
Advanced Options
There are additional options for advanced users. More information on these parameters can be found in the NEWSUMT Users Guide. (This doc is slow to load.)
Parameter | Description | Default |
---|---|---|
mflag | Flag for penalty multiplier. If 0, initial value computed by NEWSUMT. If 1, initial value set by ra | 0 |
ra | Penalty multiplier. Required if mflag=1 | 1.0 |
racut | Penalty multiplier decrease ratio. Required if mflag=1 | 0.1 |
ramin | Lower bound of penalty multiplier. Required if mflag=1 | 1.0e-13 |
g0 | Initial value of the transition parameter | 0.1 |
newsumtdriver.py - Driver for the NEWSUMT optimizer.
Bases: openmdao.main.driver.Driver
Driver wrapper of Fortran version of NEWSUMT.
Todo
Check to see if this itmax variable is needed. NEWSUMT might handle it for us.
Default finite difference stepsize. Parameters with specified values override this.
Convergence criteria of the golden section algorithm used for the one dimensional minimization.
Convergence criteria of the unconstrained minimization.
Convergence criteria for the overall process.
Initial value of the transition parameter.
Array designating whether each constraint is linear.
Maximum number of iterations before termination.
Print information during NEWSUMT solution. Higher values are more verbose. If 0, print initial and final designs only.
Set to 1 if linear objective function.
Maximum allowable number of golden section iterations used for 1D minimization.
Maximum allowable number of one dimensional minimizations.
Maximum allowable number of unconstrained minimizations.
Flag for penalty multiplier. If 0, initial value computed by NEWSUMT. If 1, initial value set by ra.
Penalty multiplier. Required if mflag=1
Penalty multiplier decrease ratio. Required if mflag=1.
Lower bound of penalty multiplier. Required if mflag=1.
Maximum bound imposed on the initial step size of the one-dimensional minimization.
Adds a constraint in the form of a boolean expression string to the driver.
Adds an objective to the driver.
Takes an iterator of objective strings and creates objectives for them in the driver.
Adds a parameter or group of parameters to the driver.
If neither “low” nor “high” is specified, the min and max will default to the values in the metadata of the variable being referenced. If they are not specified in the metadata and not provided as arguments, a ValueError is raised.
Returns True if types is [‘ineq’].
Returns True if this Driver supports parameters of the give types.
Run check_derivatives on our workflow.
Run check_derivatives on our workflow.
Removes all constraints.
Removes all objectives.
Removes all parameters.
Returns a list of constraint values
Returns a list of values of the evaluated objectives.
Returns a list of values of the evaluated objectives.
Returns an ordered dict of inequality constraint objects.
Returns an OrderedDict of objective expressions.
Returns an ordered dict of parameter objects.
Return a list of strings containing constraint expressions.
Returns a list of parameter targets. Note that this list may contain more entries than the list of Parameter and ParameterGroup objects since ParameterGroups have multiple targets.
Removes the constraint with the given string.
Removes the specified objective expression. Spaces within the expression are ignored.
Removes the parameter with the given name.
Pushes the values in the iterator ‘values’ into the corresponding variables in the model. If the ‘case’ arg is supplied, the values will be set into the case and not into the model.
This should be called during the Driver’s __init__ function to specify what types of parameters that the Driver supports.
A simple driver that runs cases from a CaseIterator and records them with a CaseRecorder.
Bases: openmdao.main.driver.Driver
A Driver that sequentially runs a set of cases provided by an ICaseIterator and optionally records the results in a CaseRecorder. This is intended for test cases or very simple models only. For a more full-featured Driver with similar functionality, see CaseIteratorDriver.
The iterator socket provides the cases to be evaluated.
the Driver class.
For each case coming from the iterator, the workflow will be executed once.
Source of Cases.
Pseudo package providing a central place to access all of the OpenMDAO surrogatemodels in the standard library.
Surrogate model based on Kriging.
Bases: object
Returns a NormalDistribution centered around the value, with a standard deviation of 0.
Surrogate Model based on a logistic regression model, with regularization to adjust for overfitting. Based on work from Python logistic regression.
Bases: enthought.traits.has_traits.HasTraits
L2 regularization strength
Returns the value iself. Logistic regressions don’t have uncertainty
Pseudo package providing a central place to access all of the OpenMDAO datatypes in the standard library.
on_trait_change
Trait for numpy array variables, with optional units.
Bases: enthought.traits.trait_numeric.Array
A variable wrapper for a numpy array with optional units. The unit applies to the entire array.
Bases: object
A DomainObj represents a (possibly multi-zoned) mesh and data related to that mesh.
Add zones from other to self, retaining names where possible.
Add a Zone. Returns the added zone.
Test if self and other are equivalent.
Convert to Cartesian coordinate system.
Convert to cylindrical coordinate system.
Remove a zone. Returns the removed zone.
Rename a zone.
Translate coordinates.
Trait for enumerations, with optional alias list.
Bases: openmdao.main.variable.Variable
A variable wrapper for an enumeration, which is a variable that can assume one value from a set of specified values.
Support for files, either as File or external files.
Bases: openmdao.main.variable.Variable
A trait wrapper for a FileRef object. For input files legal_types may be set to a list of expected ‘content_type’ strings. Then upon assignment the actual ‘content_type’ must match one of the legal_types strings. Also for input files, if local_path is set, then upon assignent the associated file will be copied to that path.
Trait for floating point variables, with optional min, max, and units.
Bases: openmdao.main.variable.Variable
A Variable wrapper for floating point number valid within a specified range of values.
Bases: object
Contains solution variables for a Zone. All variables have the same shape and grid location.
Add a numpy.ndarray of scalar data and bind to name. Returns the added array.
Add a Vector and bind to name. Returns the added vector.
Test if self and other are equivalent.
Convert to Cartesian coordinate system.
Convert to cylindrical coordinate system.
Number of ghost cells for each index direction.
Position at which data is located.
Bases: openmdao.lib.datatypes.domain.vector.Vector
Coordinate data for a Zone.
Test if self and other are equivalent.
Convert to Cartesian coordinate system.
Convert to cylindrical coordinate system.
Translate coordinates.
Number of ghost cells for each index direction.
Trait for integer variables, with optional high and low.
Bases: openmdao.main.variable.Variable
A variable wrapper for an integer valid within a specified range of values.
Functions to read and write a DomainObj in Plot3D format. Many function arguments are common:
Default argument values are set for a typical 3D multiblock single-precision Fortran unformatted file. When writing, zones are assumed in Cartesian coordinates with data located at the vertices.
Returns a DomainObj initialized from Plot3D grid_file and f_file. Variables are assigned to names of the form f_N.
Returns a DomainObj initialized from Plot3D grid_file.
Returns a DomainObj initialized from Plot3D grid_file and q_file. Q variables are assigned to ‘density’, ‘momentum’, and ‘energy_stagnation_density’. Scalars are assigned to ‘mach’, ‘alpha’, ‘reynolds’, and ‘time’.
Returns a list of zone dimensions from Plot3D grid_file.
Writes domain to grid_file and f_file in Plot3D format. If varnames is None, then all arrays and then all vectors are written.
Writes domain to grid_file in Plot3D format.
Writes domain to grid_file and q_file in Plot3D format. Requires ‘density’, ‘momentum’, and ‘energy_stagnation_density’ variables as well as ‘mach’, ‘alpha’, ‘reynolds’, and ‘time’ scalars.
Register a surface probe function.
Calculate metrics on mesh surfaces. Currently only supports 3D structured grids with cell-centered data.
Returns a list of metric values in the order of the variables list.
Bases: object
Vector data for a FlowSolution. In Cartesian coordinates, array indexing order is x,y,z; so an ‘i-face’ is a y,z surface. In cylindrical coordinates, array indexing order is z,r,t; so an ‘i-face’ is an r,t surface.
Test if self and other are equivalent.
Convert to Cartesian coordinate system.
Bases: object
One zone in a possibly multi-zone DomainObj.
Test if self and other are equivalent.
Convert to Cartesian coordinate system.
Convert to cylindrical coordinate system.
Translate coordinates.
Coordinate system in use.