{
 "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  # noqa: F401\n",
    "except ImportError:\n",
    "    !python -m pip install openmdao[notebooks]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# ScipyOptimizeDriver\n",
    "\n",
    "ScipyOptimizeDriver wraps the optimizers in *scipy.optimize.minimize*. In this example, we use the SLSQP optimizer to find the minimum of the Paraboloid problem."
   ]
  },
  {
   "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_src019\", get_code(\"openmdao.test_suite.components.paraboloid.Paraboloid\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `Paraboloid` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src019`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.paraboloid import Paraboloid\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('comp', Paraboloid(), promotes=['*'])\n",
    "\n",
    "prob.driver = om.ScipyOptimizeDriver()\n",
    "prob.driver.options['optimizer'] = 'SLSQP'\n",
    "prob.driver.options['tol'] = 1e-9\n",
    "prob.driver.options['disp'] = True\n",
    "\n",
    "model.add_design_var('x', lower=-50.0, upper=50.0)\n",
    "model.add_design_var('y', lower=-50.0, upper=50.0)\n",
    "model.add_objective('f_xy')\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "prob.set_val('x', 50.0)\n",
    "prob.set_val('y', 50.0)\n",
    "\n",
    "prob.run_driver()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('x'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('y'))"
   ]
  },
  {
   "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('x'), 6.66666667, 1e-6)\n",
    "assert_near_equal(prob.get_val('y'), -7.3333333, 1e-6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ScipyOptimizeDriver Options"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input"
    ]
   },
   "outputs": [],
   "source": [
    "om.show_options_table(\"openmdao.drivers.scipy_optimizer.ScipyOptimizeDriver\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ScipyOptimizeDriver Constructor\n",
    "\n",
    "The call signature for the *ScipyOptimizeDriver* constructor is:\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.drivers.scipy_optimizer.ScipyOptimizeDriver.__init__\n",
    "        :noindex:\n",
    "```\n",
    "\n",
    "## ScipyOptimizeDriver Option Examples\n",
    "\n",
    "**optimizer**\n",
    "\n",
    "    The “optimizer” option lets you choose which optimizer to use. ScipyOptimizeDriver supports all of the optimizers in scipy.optimize except for ‘dogleg’ and ‘trust-ncg’. Generally, the optimizers that you are most likely to use are “COBYLA” and “SLSQP”, as these are the only ones that support constraints. Only SLSQP supports equality constraints. SLSQP also uses gradients provided by OpenMDAO, while COBYLA is gradient-free. Also, SLSQP supports both equality and inequality constraints, but COBYLA only supports inequality constraints.\n",
    "\n",
    "    Here we pass the optimizer option as a keyword argument."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.paraboloid import Paraboloid\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('comp', Paraboloid(), promotes=['*'])\n",
    "\n",
    "prob.driver = om.ScipyOptimizeDriver(optimizer='COBYLA')\n",
    "\n",
    "model.add_design_var('x', lower=-50.0, upper=50.0)\n",
    "model.add_design_var('y', lower=-50.0, upper=50.0)\n",
    "model.add_objective('f_xy')\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "prob.set_val('x', 50.0)\n",
    "prob.set_val('y', 50.0)\n",
    "\n",
    "prob.run_driver()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('x'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('y'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert_near_equal(prob.get_val('x'), 6.66666667, 1e-6)\n",
    "assert_near_equal(prob.get_val('y'), -7.3333333, 1e-6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**maxiter**\n",
    "\n",
    "    The “maxiter” option is used to specify the maximum number of major iterations before termination. It is generally a valid option across all of the available options."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "\n",
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.paraboloid import Paraboloid\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('comp', Paraboloid(), promotes=['*'])\n",
    "\n",
    "prob.driver = om.ScipyOptimizeDriver()\n",
    "prob.driver.options['maxiter'] = 20\n",
    "\n",
    "model.add_design_var('x', lower=-50.0, upper=50.0)\n",
    "model.add_design_var('y', lower=-50.0, upper=50.0)\n",
    "model.add_objective('f_xy')\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "prob.set_val('x', 50.0)\n",
    "prob.set_val('y', 50.0)\n",
    "\n",
    "prob.run_driver()\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('x'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('y'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert_near_equal(prob.get_val('x'), 6.66666667, 1e-6)\n",
    "assert_near_equal(prob.get_val('y'), -7.3333333, 1e-6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**tol**\n",
    "\n",
    "    The “tol” option allows you to specify the tolerance for termination."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.paraboloid import Paraboloid\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('comp', Paraboloid(), promotes=['*'])\n",
    "\n",
    "prob.driver = om.ScipyOptimizeDriver()\n",
    "prob.driver.options['tol'] = 1.0e-9\n",
    "\n",
    "model.add_design_var('x', lower=-50.0, upper=50.0)\n",
    "model.add_design_var('y', lower=-50.0, upper=50.0)\n",
    "model.add_objective('f_xy')\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "prob.set_val('x', 50.0)\n",
    "prob.set_val('y', 50.0)\n",
    "\n",
    "prob.run_driver()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('x'))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('y'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert_near_equal(prob.get_val('x'), 6.66666667, 1e-6)\n",
    "assert_near_equal(prob.get_val('y'), -7.3333333, 1e-6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ScipyOptimizeDriver Driver Specific Options\n",
    "\n",
    "Optimizers in *scipy.optimize.minimize* have optimizer specific options. To let the user specify values for these options, OpenMDAO provides an option in the form of a dictionary named *opt_settings*. See the *scipy.optimize.minimize* documentation for more information about the driver specific options that are available.\n",
    "\n",
    "As an example, here is code using some *opt_settings* for the shgo optimizer:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Source of example: https://stefan-endres.github.io/shgo/\n",
    "\n",
    "import numpy as np\n",
    "import openmdao.api as om\n",
    "\n",
    "size = 3  # size of the design variable\n",
    "\n",
    "def rastrigin(x):\n",
    "    a = 10  # constant\n",
    "    return np.sum(np.square(x) - a * np.cos(2 * np.pi * x)) + a * np.size(x)\n",
    "\n",
    "class Rastrigin(om.ExplicitComponent):\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('x', np.ones(size))\n",
    "        self.add_output('f', 0.0)\n",
    "        \n",
    "        self.declare_partials(of='f', wrt='x', method='cs')\n",
    "\n",
    "    def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None):\n",
    "        x = inputs['x']\n",
    "        outputs['f'] = rastrigin(x)\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('rastrigin', Rastrigin(), promotes=['*'])\n",
    "\n",
    "prob.driver = driver = om.ScipyOptimizeDriver()\n",
    "driver.options['optimizer'] = 'shgo'\n",
    "driver.options['disp'] = False\n",
    "driver.opt_settings['maxtime'] = 10  # seconds\n",
    "driver.opt_settings['iters'] = 3\n",
    "driver.opt_settings['maxiter'] = None\n",
    "\n",
    "model.add_design_var('x', lower=-5.12*np.ones(size), upper=5.12*np.ones(size))\n",
    "model.add_objective('f')\n",
    "prob.setup()\n",
    "\n",
    "prob.set_val('x', 2*np.ones(size))\n",
    "prob.run_driver()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('x'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('f'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert_near_equal(prob.get_val('x'), np.zeros(size), 1e-6)\n",
    "assert_near_equal(prob.get_val('f'), 0.0, 1e-6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice that when using the shgo optimizer, setting the *opt_settings[‘maxiter’]* to None overrides *ScipyOptimizeDriver*’s *options[‘maxiter’]* value. It is not possible to set *options[‘maxiter’]* to anything other than an integer so the *opt_settings[‘maxiter’]* option provides a way to set the maxiter value for the shgo optimizer to None."
   ]
  }
 ],
 "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
}
