{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "active-ipynb",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "try:\n",
    "    from openmdao.utils.notebook_utils import notebook_mode\n",
    "except ImportError:\n",
    "    !python -m pip install openmdao[notebooks]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# pyOptSparseDriver\n",
    "\n",
    "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.\n",
    "\n",
    "```{note}\n",
    "The pyOptSparse package does not come included with the OpenMDAO installation. It is a separate optional package that can be obtained from [mdolab](https://github.com/mdolab/pyoptsparse).\n",
    "```\n",
    "\n",
    "In this simple example, we use the SLSQP optimizer to minimize the objective of SellarDerivativesGrouped."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.notebook_utils import get_code\n",
    "from myst_nb import glue\n",
    "glue(\"code_src18\", get_code(\"openmdao.test_suite.components.sellar.SellarDerivativesGrouped\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `SellarDerivativesGrouped` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src18`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarDerivativesGrouped\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model = SellarDerivativesGrouped()\n",
    "\n",
    "prob.driver = om.pyOptSparseDriver()\n",
    "prob.driver.options['optimizer'] = \"SLSQP\"\n",
    "\n",
    "model.add_design_var('z', lower=np.array([-10.0, 0.0]), upper=np.array([10.0, 10.0]))\n",
    "model.add_design_var('x', lower=0.0, upper=10.0)\n",
    "model.add_objective('obj')\n",
    "model.add_constraint('con1', upper=0.0)\n",
    "model.add_constraint('con2', upper=0.0)\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "prob.setup(check=False, mode='rev')\n",
    "prob.run_driver()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('z', indices=0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "assert_near_equal(prob.get_val('z', indices=0), 1.9776, 1e-3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## pyOptSparseDriver Options"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input"
    ]
   },
   "outputs": [],
   "source": [
    "om.show_options_table(\"openmdao.drivers.pyoptsparse_driver.pyOptSparseDriver\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## pyOptSparseDriver Constructor\n",
    "\n",
    "The call signature for the *pyOptSparseDriver* constructor is:\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.drivers.pyoptsparse_driver.pyOptSparseDriver.__init__\n",
    "       :noindex:\n",
    "```    \n",
    "\n",
    "## Using pyOptSparseDriver\n",
    "\n",
    "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."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarDerivativesGrouped\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model = SellarDerivativesGrouped()\n",
    "\n",
    "prob.driver = om.pyOptSparseDriver(optimizer='SLSQP')\n",
    "\n",
    "prob.driver.options['print_results'] = False\n",
    "\n",
    "model.add_design_var('z', lower=np.array([-10.0, 0.0]), upper=np.array([10.0, 10.0]))\n",
    "model.add_design_var('x', lower=0.0, upper=10.0)\n",
    "model.add_objective('obj')\n",
    "model.add_constraint('con1', upper=0.0)\n",
    "model.add_constraint('con2', upper=0.0)\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "prob.setup(check=False, mode='rev')\n",
    "prob.run_driver()\n",
    "\n",
    "print(prob.get_val('z', indices=0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert_near_equal(prob.get_val('z', indices=0), 1.9776, 1e-3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "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.\n",
    "\n",
    "\n",
    "## SLSQP-Specific Settings\n",
    "\n",
    "Here, we set a convergence tolerance for SLSQP:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarDerivativesGrouped\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model = SellarDerivativesGrouped()\n",
    "\n",
    "prob.driver = om.pyOptSparseDriver()\n",
    "prob.driver.options['optimizer'] = \"SLSQP\"\n",
    "\n",
    "prob.driver.opt_settings['ACC'] = 1e-9\n",
    "\n",
    "model.add_design_var('z', lower=np.array([-10.0, 0.0]), upper=np.array([10.0, 10.0]))\n",
    "model.add_design_var('x', lower=0.0, upper=10.0)\n",
    "model.add_objective('obj')\n",
    "model.add_constraint('con1', upper=0.0)\n",
    "model.add_constraint('con2', upper=0.0)\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "prob.setup(check=False, mode='rev')\n",
    "prob.run_driver()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('z', indices=0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert_near_equal(prob.get_val('z', indices=0), 1.9776, 1e-3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Similarly, we can set an iteration limit. Here, we set it to just a few iterations, and don't quite reach the optimum.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarDerivativesGrouped\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model = SellarDerivativesGrouped()\n",
    "\n",
    "prob.driver = om.pyOptSparseDriver()\n",
    "prob.driver.options['optimizer'] = \"SLSQP\"\n",
    "\n",
    "prob.driver.opt_settings['MAXIT'] = 3\n",
    "\n",
    "model.add_design_var('z', lower=np.array([-10.0, 0.0]), upper=np.array([10.0, 10.0]))\n",
    "model.add_design_var('x', lower=0.0, upper=10.0)\n",
    "model.add_objective('obj')\n",
    "model.add_constraint('con1', upper=0.0)\n",
    "model.add_constraint('con2', upper=0.0)\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "prob.setup(check=False, mode='rev')\n",
    "prob.run_driver()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('z', indices=0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert_near_equal(prob.get_val('z', indices=0), 1.98337708, 1e-3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## SNOPT-Specific Settings\n",
    "\n",
    "SNOPT has many customizable settings. Here we show two common ones.\n",
    "\n",
    "Setting the convergence tolerance:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarDerivativesGrouped\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model = SellarDerivativesGrouped()\n",
    "\n",
    "prob.driver = om.pyOptSparseDriver()\n",
    "prob.driver.options['optimizer'] = \"SNOPT\"\n",
    "\n",
    "prob.driver.opt_settings['Major feasibility tolerance'] = 1e-9\n",
    "\n",
    "model.add_design_var('z', lower=np.array([-10.0, 0.0]), upper=np.array([10.0, 10.0]))\n",
    "model.add_design_var('x', lower=0.0, upper=10.0)\n",
    "model.add_objective('obj')\n",
    "model.add_constraint('con1', upper=0.0)\n",
    "model.add_constraint('con2', upper=0.0)\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "prob.setup(check=False, mode='rev')\n",
    "prob.run_driver()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('z', indices=0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert_near_equal(prob.get_val('z', indices=0), 1.9776, 1e-3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "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."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarDerivativesGrouped\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model = SellarDerivativesGrouped()\n",
    "\n",
    "prob.driver = om.pyOptSparseDriver()\n",
    "prob.driver.options['optimizer'] = \"SNOPT\"\n",
    "\n",
    "# after upgrading to SNOPT 7.5-1.1, this test failed unless iter limit raised from 4 to 5\n",
    "prob.driver.opt_settings['Major iterations limit'] = 5\n",
    "\n",
    "model.add_design_var('z', lower=np.array([-10.0, 0.0]), upper=np.array([10.0, 10.0]))\n",
    "model.add_design_var('x', lower=0.0, upper=10.0)\n",
    "model.add_objective('obj')\n",
    "model.add_constraint('con1', upper=0.0)\n",
    "model.add_constraint('con2', upper=0.0)\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "prob.setup(check=False, mode='rev')\n",
    "\n",
    "prob.run_driver()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('z', indices=0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert_near_equal(prob.get_val('z', indices=0), 1.9780247, 2e-3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you have pyoptsparse 1.1 or greater, then you can send a signal such as SIGUSR1 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.\n",
    "\n",
    "``` bash\n",
    "    ktmoore1$ ps -ef |grep sig\n",
    "      502 17955   951   0  4:05PM ttys000    0:00.02 mpirun -n 2 python sig_demo.py\n",
    "      502 17956 17955   0  4:05PM ttys000    0:00.03 python sig_demo.py\n",
    "      502 17957 17955   0  4:05PM ttys000    0:00.03 python sig_demo.py\n",
    "      502 17959 17312   0  4:05PM ttys001    0:00.00 grep sig\n",
    "\n",
    "    ktmoore1$ kill -SIGUSR1 17955\n",
    "```\n",
    "\n",
    "You can enable this feature by setting the \"user_terminate_signal\" option and giving it a signal (imported from the\n",
    "signal library in Python).  By default, user_terminate_signal is None, which disables the feature.\n",
    "Here, we set the signal to SIGUSR1:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "import signal\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "prob.driver = om.pyOptSparseDriver()\n",
    "prob.driver.options['optimizer'] = \"SNOPT\"\n",
    "prob.driver.options['user_terminate_signal'] = signal.SIGUSR1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can learn more about the available options in the [SNOPT_Manual](http://www.sbsi-sol-optimize.com/manuals/SNOPT%20Manual.pdf)."
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.1"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
