Simple Optimization

The Paraboloid

This example finds the minimum point in a paraboloid defined by:

(2)\[\begin{align} f(x,y) &= (x-3)^2 + x y + (y+4)^2 - 3 \end{align}\]
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-50, 50, 100)
y = np.linspace(-50, 50, 100)
XX, YY = np.meshgrid(x, y)
ZZ = (XX - 3)**2 + XX * YY + (YY + 4)**2 - 3

plt.figure(figsize=(8, 6))
plt.contour(XX, YY, ZZ, levels=np.linspace(np.min(ZZ), np.max(ZZ), 50))
plt.xlabel('x')
plt.ylabel('y')
plt.colorbar(label='f')
plt.suptitle('Contour of the paraboloid', fontsize=18);
../_images/paraboloid_3_0.png

Finding the minimum with OpenMDAO

The following script uses OpenMDAO to find the minimum of the paraboloid.

import openmdao.api as om

# build the model
prob = om.Problem()

prob.model.add_subsystem('paraboloid', om.ExecComp('f = (x-3)**2 + x*y + (y+4)**2 - 3'))

# setup the optimization
prob.driver = om.ScipyOptimizeDriver()
prob.driver.options['optimizer'] = 'SLSQP'

prob.model.add_design_var('paraboloid.x', lower=-50, upper=50)
prob.model.add_design_var('paraboloid.y', lower=-50, upper=50)
prob.model.add_objective('paraboloid.f')

prob.setup()

# Set initial values.
prob.set_val('paraboloid.x', 3.0)
prob.set_val('paraboloid.y', -4.0)

# run the optimization
prob.run_driver();
Optimization terminated successfully.    (Exit mode 0)
            Current function value: -27.33333333333333
            Iterations: 5
            Function evaluations: 6
            Gradient evaluations: 5
Optimization Complete
-----------------------------------

Step-by-step explanation

First, we import the OpenMDAO API into the namespace om

import openmdao.api as om

Instantiating a Problem

An OpenMDAO Problem couples a computational model with some sort of driver that executes that model.

The default model is an empty Group, to which we can add Components or Groups of Components.

The default driver simply executes the model a single time. More commonly, you’ll use drivers that perform optimization (such as ScipyOptimizeDriver or pyOptSparseDriver) or execute the model to perform a design-of-experiments (DOEDriver).

prob = om.Problem()

Building our Model

In this case, our model consists of a single component; an ExecComp that executes a user-defined function. We’ve given it the mathematical equation for our paraboloid.

prob.model.add_subsystem('paraboloid', om.ExecComp('f = (x-3)**2 + x*y + (y+4)**2 - 3'));

Specifying the Driver

In this case we wish to find the minimum value on the contour of the paraboloid. To do this, we use an optimizaation driver. Optimization drivers vary the values of some design variables to minimize the value of some objective function. Many optimiation techniques can also respect some set of constraints imposed upon the optimization, but this particular problem is unconstrained. We’re going to use OpenMDAO’s built in ScipyOptimizeDriver, which uses the optimizers from scipy.optimize.minimize to perform the optimization. In this case, the design variables are x and y, and the objective is f.

# setup the optimization
prob.driver = om.ScipyOptimizeDriver()
prob.driver.options['optimizer'] = 'SLSQP'

prob.model.add_design_var('paraboloid.x', lower=-50, upper=50)
prob.model.add_design_var('paraboloid.y', lower=-50, upper=50)
prob.model.add_objective('paraboloid.f')

Setup the Problem

The next step is setting up the problem. This is a bit analogous to compiling source code when programming. OpenMDAO allocates the necessary data structures that enable pass data between different parts of our model and to compute the derivatives of our model for the optimizer.

prob.setup()

Specifying Initial Values

During the course of optimization, OpenMDAO is going to vary the value of the design variables (x and y) in an attempt to find the minimum value of f. The optimization algorithms involved generally need the user to specify a starting point. This is accomplished using the set_val method of the Problem class.

# Set initial values.
prob.set_val('paraboloid.x', 3.0)
prob.set_val('paraboloid.y', -4.0)

Running the Optimization Driver

Finally, we run the optimization driver to actually find the minimum value. The ScipyOptimizeDriver will output the results of the optimization to standard output. In addition, run_driver will return a fail flag. If True, this means the optimization driver believes it failed to successfully find an optimal solution.

# run the optimization
prob.run_driver()
Optimization terminated successfully.    (Exit mode 0)
            Current function value: -27.33333333333333
            Iterations: 5
            Function evaluations: 6
            Gradient evaluations: 5
Optimization Complete
-----------------------------------
False

Getting the results

We can access the values of variables in our model by using the get_val method of Problem.

print('Optimal x value: ', prob.get_val('paraboloid.x'))
print('Optimal y value: ', prob.get_val('paraboloid.y'))
print('Objective value: ', prob.get_val('paraboloid.f'))
Optimal x value:  [6.66666667]
Optimal y value:  [-7.33333333]
Objective value:  [-27.33333333]