pyOptSparseDriver¶
pyOptSparseDriver wraps the optimizer package pyOptSparse, which provides a common interface for 11 optimizers, some of which are included in the package (e.g., SLSQP and NSGA2), and some of which are commercial products that must be obtained from their respective authors (e.g. SNOPT). The pyOptSparse package is based on pyOpt, but adds support for sparse specification of constraint Jacobians. Most of the sparsity features are only applicable when using the SNOPT optimizer.
Note
The pyOptSparse package does not come included with the OpenMDAO installation. It is a separate optional package that can be obtained from mdolab.
In this simple example, we use the SLSQP optimizer to minimize the objective of SellarDerivativesGrouped.
import numpy as np
import openmdao.api as om
from openmdao.test_suite.components.sellar import SellarDerivativesGrouped
prob = om.Problem()
model = prob.model = SellarDerivativesGrouped()
prob.driver = om.pyOptSparseDriver()
prob.driver.options['optimizer'] = "SLSQP"
model.add_design_var('z', lower=np.array([-10.0, 0.0]), upper=np.array([10.0, 10.0]))
model.add_design_var('x', lower=0.0, upper=10.0)
model.add_objective('obj')
model.add_constraint('con1', upper=0.0)
model.add_constraint('con2', upper=0.0)
prob.set_solver_print(level=0)
prob.setup(check=False, mode='rev')
prob.run_driver()
Optimization Problem -- Optimization using pyOpt_sparse ================================================================================ Objective Function: _objfunc Solution: -------------------------------------------------------------------------------- Total Time: 0.0442 User Objective Time : 0.0100 User Sensitivity Time : 0.0274 Interface Time : 0.0059 Opt Solver Time: 0.0010 Calls to Objective Function : 6 Calls to Sens Function : 6 Objectives Index Name Value Optimum 0 obj_cmp.obj 3.183394E+00 0.000000E+00 Variables (c - continuous, i - integer, d - discrete) Index Name Type Lower Bound Value Upper Bound Status 0 z_0 c -1.000000E+01 1.977639E+00 1.000000E+01 1 z_1 c 0.000000E+00 -2.176723E-15 1.000000E+01 l 2 x_0 c 0.000000E+00 1.643932E-15 1.000000E+01 l Constraints (i - inequality, e - equality) Index Name Type Lower Value Upper Status Lagrange Multiplier (N/A) 0 con_cmp1.con1 i -1.000000E+30 -8.637846E-11 0.000000E+00 u 9.00000E+100 1 con_cmp2.con2 i -1.000000E+30 -2.024472E+01 0.000000E+00 9.00000E+100 --------------------------------------------------------------------------------
print(prob.get_val('z', indices=0))
1.9776388834874465
pyOptSparseDriver Options¶
Option |
Default |
Acceptable Values |
Acceptable Types |
Description |
---|---|---|---|---|
debug_print |
[] |
N/A |
[‘list’] |
List of what type of Driver variables to print at each iteration. Valid items in list are ‘desvars’, ‘ln_cons’, ‘nl_cons’, ‘objs’, ‘totals’ |
gradient method |
openmdao |
[‘openmdao’, ‘pyopt_fd’, ‘snopt_fd’] |
N/A |
Finite difference implementation to use |
optimizer |
SLSQP |
[‘ALPSO’, ‘CONMIN’, ‘FSQP’, ‘IPOPT’, ‘NLPQLP’, ‘NSGA2’, ‘PSQP’, ‘SLSQP’, ‘SNOPT’, ‘NLPY_AUGLAG’, ‘NOMAD’, ‘ParOpt’] |
N/A |
Name of optimizers to use |
print_results |
True |
[True, False] |
[‘bool’] |
Print pyOpt results if True |
title |
Optimization using pyOpt_sparse |
N/A |
N/A |
Title of this optimization run |
user_teriminate_signal |
None |
N/A |
N/A |
OS signal that triggers a clean user-termination. Only SNOPTsupports this option. |
user_terminate_signal |
Signals.SIGUSR1 |
N/A |
N/A |
OS signal that triggers a clean user-termination. Only SNOPTsupports this option. |
pyOptSparseDriver Constructor¶
The call signature for the pyOptSparseDriver constructor is:
-
pyOptSparseDriver.
__init__
(**kwargs)[source] Initialize pyopt.
- Parameters
- **kwargsdict of keyword arguments
Keyword arguments that will be mapped into the Driver options.
Using pyOptSparseDriver¶
pyOptSparseDriver has a small number of unified options that can be specified as keyword arguments when it is instantiated or by using the “options” dictionary. We have already shown how to set the optimizer option. Next we see how the print_results option can be used to turn on or off the echoing of the results when the optimization finishes. The default is True, but here, we turn it off.
import numpy as np
import openmdao.api as om
from openmdao.test_suite.components.sellar import SellarDerivativesGrouped
prob = om.Problem()
model = prob.model = SellarDerivativesGrouped()
prob.driver = om.pyOptSparseDriver(optimizer='SLSQP')
prob.driver.options['print_results'] = False
model.add_design_var('z', lower=np.array([-10.0, 0.0]), upper=np.array([10.0, 10.0]))
model.add_design_var('x', lower=0.0, upper=10.0)
model.add_objective('obj')
model.add_constraint('con1', upper=0.0)
model.add_constraint('con2', upper=0.0)
prob.set_solver_print(level=0)
prob.setup(check=False, mode='rev')
prob.run_driver()
print(prob.get_val('z', indices=0))
1.9776388834874465
Every optimizer also has its own specialized settings that allow you to fine-tune the algorithm that it uses. You can access these within the opt_setting dictionary. These options are different for each optimizer, so to find out what they are, you need to read your optimizer’s documentation. We present a few common ones here.
SLSQP-Specific Settings¶
Here, we set a convergence tolerance for SLSQP:
import numpy as np
import openmdao.api as om
from openmdao.test_suite.components.sellar import SellarDerivativesGrouped
prob = om.Problem()
model = prob.model = SellarDerivativesGrouped()
prob.driver = om.pyOptSparseDriver()
prob.driver.options['optimizer'] = "SLSQP"
prob.driver.opt_settings['ACC'] = 1e-9
model.add_design_var('z', lower=np.array([-10.0, 0.0]), upper=np.array([10.0, 10.0]))
model.add_design_var('x', lower=0.0, upper=10.0)
model.add_objective('obj')
model.add_constraint('con1', upper=0.0)
model.add_constraint('con2', upper=0.0)
prob.set_solver_print(level=0)
prob.setup(check=False, mode='rev')
prob.run_driver()
Optimization Problem -- Optimization using pyOpt_sparse ================================================================================ Objective Function: _objfunc Solution: -------------------------------------------------------------------------------- Total Time: 0.0432 User Objective Time : 0.0097 User Sensitivity Time : 0.0273 Interface Time : 0.0055 Opt Solver Time: 0.0006 Calls to Objective Function : 6 Calls to Sens Function : 6 Objectives Index Name Value Optimum 0 obj_cmp.obj 3.183394E+00 0.000000E+00 Variables (c - continuous, i - integer, d - discrete) Index Name Type Lower Bound Value Upper Bound Status 0 z_0 c -1.000000E+01 1.977639E+00 1.000000E+01 1 z_1 c 0.000000E+00 -2.176723E-15 1.000000E+01 l 2 x_0 c 0.000000E+00 1.643932E-15 1.000000E+01 l Constraints (i - inequality, e - equality) Index Name Type Lower Value Upper Status Lagrange Multiplier (N/A) 0 con_cmp1.con1 i -1.000000E+30 -8.637846E-11 0.000000E+00 u 9.00000E+100 1 con_cmp2.con2 i -1.000000E+30 -2.024472E+01 0.000000E+00 9.00000E+100 --------------------------------------------------------------------------------
print(prob.get_val('z', indices=0))
1.9776388834874465
Similarly, we can set an iteration limit. Here, we set it to just a few iterations, and don’t quite reach the optimum.
import numpy as np
import openmdao.api as om
from openmdao.test_suite.components.sellar import SellarDerivativesGrouped
prob = om.Problem()
model = prob.model = SellarDerivativesGrouped()
prob.driver = om.pyOptSparseDriver()
prob.driver.options['optimizer'] = "SLSQP"
prob.driver.opt_settings['MAXIT'] = 3
model.add_design_var('z', lower=np.array([-10.0, 0.0]), upper=np.array([10.0, 10.0]))
model.add_design_var('x', lower=0.0, upper=10.0)
model.add_objective('obj')
model.add_constraint('con1', upper=0.0)
model.add_constraint('con2', upper=0.0)
prob.set_solver_print(level=0)
prob.setup(check=False, mode='rev')
prob.run_driver()
Optimization Problem -- Optimization using pyOpt_sparse ================================================================================ Objective Function: _objfunc Solution: -------------------------------------------------------------------------------- Total Time: 0.0308 User Objective Time : 0.0070 User Sensitivity Time : 0.0192 Interface Time : 0.0041 Opt Solver Time: 0.0005 Calls to Objective Function : 4 Calls to Sens Function : 4 Objectives Index Name Value Optimum 0 obj_cmp.obj 3.203561E+00 0.000000E+00 Variables (c - continuous, i - integer, d - discrete) Index Name Type Lower Bound Value Upper Bound Status 0 z_0 c -1.000000E+01 1.983377E+00 1.000000E+01 1 z_1 c 0.000000E+00 -2.037963E-12 1.000000E+01 l 2 x_0 c 0.000000E+00 -1.808298E-14 1.000000E+01 l Constraints (i - inequality, e - equality) Index Name Type Lower Value Upper Status Lagrange Multiplier (N/A) 0 con_cmp1.con1 i -1.000000E+30 -2.043382E-02 0.000000E+00 9.00000E+100 1 con_cmp2.con2 i -1.000000E+30 -2.023325E+01 0.000000E+00 9.00000E+100 --------------------------------------------------------------------------------
print(prob.get_val('z', indices=0))
1.9833770833078042
SNOPT-Specific Settings¶
SNOPT has many customizable settings. Here we show two common ones.
Setting the convergence tolerance:
import numpy as np
import openmdao.api as om
from openmdao.test_suite.components.sellar import SellarDerivativesGrouped
prob = om.Problem()
model = prob.model = SellarDerivativesGrouped()
prob.driver = om.pyOptSparseDriver()
prob.driver.options['optimizer'] = "SNOPT"
prob.driver.opt_settings['Major feasibility tolerance'] = 1e-9
model.add_design_var('z', lower=np.array([-10.0, 0.0]), upper=np.array([10.0, 10.0]))
model.add_design_var('x', lower=0.0, upper=10.0)
model.add_objective('obj')
model.add_constraint('con1', upper=0.0)
model.add_constraint('con2', upper=0.0)
prob.set_solver_print(level=0)
prob.setup(check=False, mode='rev')
prob.run_driver()
Optimization Problem -- Optimization using pyOpt_sparse ================================================================================ Objective Function: _objfunc Solution: -------------------------------------------------------------------------------- Total Time: 0.0554 User Objective Time : 0.0128 User Sensitivity Time : 0.0326 Interface Time : 0.0068 Opt Solver Time: 0.0031 Calls to Objective Function : 8 Calls to Sens Function : 7 Objectives Index Name Value Optimum 0 obj_cmp.obj 3.183394E+00 0.000000E+00 Variables (c - continuous, i - integer, d - discrete) Index Name Type Lower Bound Value Upper Bound Status 0 z_0 c -1.000000E+01 1.977639E+00 1.000000E+01 1 z_1 c 0.000000E+00 0.000000E+00 1.000000E+01 l 2 x_0 c 0.000000E+00 0.000000E+00 1.000000E+01 l Constraints (i - inequality, e - equality) Index Name Type Lower Value Upper Status Lagrange Multiplier 0 con_cmp1.con1 i -1.000000E+30 -5.966339E-12 0.000000E+00 u -9.86840E-01 1 con_cmp2.con2 i -1.000000E+30 -2.024472E+01 0.000000E+00 0.00000E+00 --------------------------------------------------------------------------------
print(prob.get_val('z', indices=0))
1.9776388834648047
Setting a limit on the number of major iterations. Here, we set it to just a few iterations, and don’t quite reach the optimum.
import numpy as np
import openmdao.api as om
from openmdao.test_suite.components.sellar import SellarDerivativesGrouped
prob = om.Problem()
model = prob.model = SellarDerivativesGrouped()
prob.driver = om.pyOptSparseDriver()
prob.driver.options['optimizer'] = "SNOPT"
# after upgrading to SNOPT 7.5-1.1, this test failed unless iter limit raised from 4 to 5
prob.driver.opt_settings['Major iterations limit'] = 5
model.add_design_var('z', lower=np.array([-10.0, 0.0]), upper=np.array([10.0, 10.0]))
model.add_design_var('x', lower=0.0, upper=10.0)
model.add_objective('obj')
model.add_constraint('con1', upper=0.0)
model.add_constraint('con2', upper=0.0)
prob.set_solver_print(level=0)
prob.setup(check=False, mode='rev')
prob.run_driver()
Optimization Problem -- Optimization using pyOpt_sparse ================================================================================ Objective Function: _objfunc Solution: -------------------------------------------------------------------------------- Total Time: 0.0517 User Objective Time : 0.0122 User Sensitivity Time : 0.0310 Interface Time : 0.0066 Opt Solver Time: 0.0018 Calls to Objective Function : 7 Calls to Sens Function : 6 Objectives Index Name Value Optimum 0 obj_cmp.obj 3.183402E+00 0.000000E+00 Variables (c - continuous, i - integer, d - discrete) Index Name Type Lower Bound Value Upper Bound Status 0 z_0 c -1.000000E+01 1.977641E+00 1.000000E+01 1 z_1 c 0.000000E+00 0.000000E+00 1.000000E+01 l 2 x_0 c 0.000000E+00 0.000000E+00 1.000000E+01 l Constraints (i - inequality, e - equality) Index Name Type Lower Value Upper Status Lagrange Multiplier 0 con_cmp1.con1 i -1.000000E+30 -8.621022E-06 0.000000E+00 -9.86840E-01 1 con_cmp2.con2 i -1.000000E+30 -2.024472E+01 0.000000E+00 0.00000E+00 --------------------------------------------------------------------------------
print(prob.get_val('z', indices=0))
1.9776413083133966
If you have pyoptsparse 1.1 or greater, then you can send the SIGUSR1 signal to a running SNOPT optimization to tell it to terminate cleanly. This is useful if an optimization has gotten close enough to an optimum. How to do this is dependent on your operating system in all cases, on your mpi implementation if you are running mpi, and on your queuing software if you are on a supercomputing cluster. Here is a simple example for unix and mpi.
ktmoore1$ ps -ef |grep sig
502 17955 951 0 4:05PM ttys000 0:00.02 mpirun -n 2 python sig_demo.py
502 17956 17955 0 4:05PM ttys000 0:00.03 python sig_demo.py
502 17957 17955 0 4:05PM ttys000 0:00.03 python sig_demo.py
502 17959 17312 0 4:05PM ttys001 0:00.00 grep sig
ktmoore1$ kill -SIGUSR1 17955
If SIGUSR1 is already used for something else, or its behavior is not supported on your operating system, mpi implementation, or queuing system, then you can choose a different signal by setting the “user_terminate_signal” option and giving it a different signal, or None to disable the feature. Here, we change the signal to SIGUSR2:
import openmdao.api as om
import signal
prob = om.Problem()
model = prob.model
prob.driver = om.pyOptSparseDriver()
prob.driver.options['optimizer'] = "SNOPT"
prob.driver.options['user_terminate_signal'] = signal.SIGUSR2
You can learn more about the available options in the SNOPT_Manual.