Building a Model - Unconstrained Optimization

Your next task is to build a model that finds the minimum value for the Paraboloid component. This model contains the Paraboloid as well as a gradient optimizer called CONMIN, from the OpenMDAO standard library. Create a file called optimization_unconstrained.py and copy this block of code into it.

from openmdao.main.api import Assembly
from openmdao.lib.drivers.api import SLSQPdriver
from openmdao.examples.simple.paraboloid import Paraboloid

class OptimizationUnconstrained(Assembly):
    """Unconstrained optimization of the Paraboloid Component."""

    def configure(self):

        # Create Optimizer instance
        self.add('driver', SLSQPdriver())

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

        # Driver process definition
        self.driver.workflow.add('paraboloid')

        # SLSQP Flags
        self.driver.iprint = 0

        # Objective
        self.driver.add_objective('paraboloid.f_xy')

        # Design Variables
        self.driver.add_parameter('paraboloid.x', low=-50., high=50.)
        self.driver.add_parameter('paraboloid.y', low=-50., high=50.)

An Assembly is a container that can hold any number of components, drivers, and other assemblies. An Assembly also manages the connections between the components that it contains. In this problem the assembly includes a single Paraboloid component and a SLSQPdriver named driver. The name driver is special. When an assembly is executed, it looks for a Driver named driver and executes it. The OptimizationUnconstrained class is derived from Assembly.

class OptimizationUnconstrained(Assembly):
    """Unconstrained optimization of the Paraboloid with CONMIN."""

    def configure(self):
        """function that sets up the architecture"""

In the Paraboloid component, you created an execute function to tell it what to do when the component is run. Assemblies don’t need an execute function, since they don’t really do any calculations of their own. Instead the assembly has a configure function defined that manages the creation of all the components, drivers, and data connections.

When configuring you use the add function to put things into the assembly:

# Create Optimizer instance
self.add('driver', SLSQPdriver())

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

Here you will make an instance of the Paraboloid component that you created above and give it the name paraboloid. Similarly you will create an instance of SLSQPdriver and give it the name driver. These are now accessible in the OptimizationUnconstrained assembly via self.paraboloid and self.driver. Remember, assemblies always look for the Driver called driver to run the model.

Next, driver needs to be told what to run. Every driver has a Workflow that contains a list of the components that the driver tells to run. We can add the paraboloid component to the driver’s workflow by using its add function.

# Iteration Hierarchy
self.driver.workflow.add('paraboloid')

For this problem, you want to minimize f_xy. In optimization, this is called the objective function. In OpenMDAO, you define the objective function by calling the driver’s add_objective function.

# Objective
self.driver.add_objective('paraboloid.f_xy')

Every variable has a unique name in the OpenMDAO data hierarchy. This name combines the variable name with its parents’ names. You can think of it as something similar to the path name in a file system, but it uses a ”.” as a separator. This allows two components to have the same variable name while assuring that you can still refer to each of them uniquely. Here, the f_xy output of the Paraboloid component is selected as the objective for minimization by specifying its full name, paraboloid.f_xy.

To find the minimum value of the objective function, we want to optimizer to vary the x and y variables. The design variables are declared individually using the add_parameter method:

# Design Variables
self.driver.add_parameter('paraboloid.x', -50, 50)
self.driver.add_parameter('paraboloid.y', -50, 50)

Once again, you specify the parameters with the full name of each variable: paraboloid.x and paraboloid.y. The add_parameter method also allows you to add a range of validity for these variables so that the unconstrained optimization can be performed on a bounded region. For this problem, you are constraining x and y to lie between [-50, 50].

The problem is now essentially ready to execute. We’re just going to set the optimizer’s verbosity to a minimum. You can turn it up if you want more information about whats going on.

# SQLSQP Flags
self.driver.iprint = 0

Congratulations! You just built your first model in OpenMDAO. Now let’s run it.

Executing the Simple Optimization Problem

To run your model, you need to create an instance of OptimizationUnconstrained and tell it to run.

To do this, we’re going to add some code to the end of the optimization_unconstrained.py so that it can be executed in Python. Using the conditional if __name__ == "__main__": you can include some Python code at the bottom of optimization_unconstrained.py. This allows you to run the file but also lets you import your assembly into another model without running it. So the final lines in this file are:

if __name__ == "__main__":

    opt_problem = OptimizationUnconstrained()

    import time
    tt = time.time()

    opt_problem.run()

    print "\n"
    print "Minimum found at (%f, %f)" % (opt_problem.paraboloid.x, \
                                     opt_problem.paraboloid.y)
    print "Elapsed time: ", time.time()-tt, "seconds"

In this block of code you are doing four things:

  1. In the first statement, you create an instance of the class OptimizationUnconstrained with the name opt_problem.
  2. In the second statement, you set opt_problem as the top Assembly in the model hierarchy. (This will be explained in a later tutorial.)
  3. In the fifth statement, you tell opt_problem to run. (The model will execute until the optimizer’s termination criteria are reached.)
  4. In the remaining statements, you define the results to print, including the elapsed time.

Add the above code into your optimization_unconstrained.py file and save it. Then type the following at the command prompt:

python optimization_unconstrained.py

This should produce the output:

Minimum found at (6.666309, -7.333026)
Elapsed time:  0.0558300018311 seconds

Now you are ready to solve a more advanced optimization problem with constraints.

Debugging Via Log Messages

You may have noticed that a file with a name of the form openmdao_log_.txt was created during the above Python run. This file is written using the standard Python logging package. Many of the OpenMDAO modules will write to the log file to record errors, warnings, debugging information, etc. The format of the message is:

timestamp loglevel source: message

By default, only warnings (W) and errors (E) are written to the log file. If you want more information printed, you can use the standard logging level control on the root logger:

import logging
logging.getLogger().setLevel(logging.DEBUG)

This is a global enabling control of all messages. If you find that messages from a particular component need to be throttled somewhat, you can change the logging level for that component:

opt_problem.paraboloid.log_level = logging.INFO

If you would like to have the log messages printed on the console, you can include this in your main routine:

from openmdao.main.api import enable_console
enable_console()

Console log messages do not have timestamps.

Now the existing Paraboloid component does not do any logging beyond that of Component. You can either modify Paraboloid to add log messages or make a derived class:

class TracingParaboloid(Paraboloid):

    def execute(self):
        self._logger.info('execute x=%g, y=%g', self.x, self.y)
        super(TracingParaboloid, self).execute()
        self._logger.debug('    result=%g', self.f_xy)

Of course, if you make a derived class, you need to change your model to use that class rather than the original.

One final note about logging: if you have a distributed simulation, log messages from remote servers are automatically routed back to the client. In this case the message includes the source server in square brackets:

timestamp loglevel [server] source: message

You can remotely control the server’s logging level via the server’s set_log_level().

OpenMDAO Home

Table Of Contents

Previous topic

Overview

Next topic

Building a Model - Constrained Optimization

This Page