{
 "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": [
    "# Optimizing the Sellar Problem\n",
    "In the previous tutorials we showed you how to define the Sellar model and run it directly. Now let’s see how we can optimize the Sellar problem to minimize the objective function. Here is the mathematical problem formulation for the Sellar optimization problem:\n",
    "\n",
    "\\begin{align}\n",
    "\\text{min}: & \\ \\ \\ & x_1^2 + z_2 + y_1 + e^{-y_2} \\\\\n",
    "\\text{w.r.t.}: & \\ \\ \\ &  x_1, z_1, z_2 \\\\\n",
    "\\text{subject to}: & \\ \\ \\ & \\\\\n",
    "& \\ \\ \\ & 3.16 - y_1 <=0 \\\\\n",
    "& \\ \\ \\ & y_2 - 24.0 <=0\n",
    "\\end{align}\n",
    "\n",
    "Remember that we built our Sellar model as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "\n",
    "class SellarMDA(om.Group):\n",
    "    \"\"\"\n",
    "    Group containing the Sellar MDA.\n",
    "    \"\"\"\n",
    "\n",
    "    def setup(self):\n",
    "        cycle = self.add_subsystem('cycle', om.Group(), promotes=['*'])\n",
    "        cycle.add_subsystem('d1', SellarDis1(), promotes_inputs=['x', 'z', 'y2'],\n",
    "                            promotes_outputs=['y1'])\n",
    "        cycle.add_subsystem('d2', SellarDis2(), promotes_inputs=['z', 'y1'],\n",
    "                            promotes_outputs=['y2'])\n",
    "\n",
    "        cycle.set_input_defaults('x', 1.0)\n",
    "        cycle.set_input_defaults('z', np.array([5.0, 2.0]))\n",
    "\n",
    "        # Nonlinear Block Gauss Seidel is a gradient free solver\n",
    "        cycle.nonlinear_solver = om.NonlinearBlockGS()\n",
    "\n",
    "        self.add_subsystem('obj_cmp', om.ExecComp('obj = x**2 + z[1] + y1 + exp(-y2)',\n",
    "                                                  z=np.array([0.0, 0.0]), x=0.0),\n",
    "                           promotes=['x', 'z', 'y1', 'y2', 'obj'])\n",
    "\n",
    "        self.add_subsystem('con_cmp1', om.ExecComp('con1 = 3.16 - y1'), promotes=['con1', 'y1'])\n",
    "        self.add_subsystem('con_cmp2', om.ExecComp('con2 = y2 - 24.0'), promotes=['con2', 'y2'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All the variables we need to set up the optimization are there. So now we just need the run script to execute the optimization."
   ]
  },
  {
   "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_src7\", get_code(\"openmdao.test_suite.components.sellar_feature.SellarMDA\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `SellarMDA` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src7`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar_feature import SellarMDA\n",
    "\n",
    "prob = om.Problem()\n",
    "prob.model = SellarMDA()\n",
    "\n",
    "prob.driver = om.ScipyOptimizeDriver()\n",
    "prob.driver.options['optimizer'] = 'SLSQP'\n",
    "# prob.driver.options['maxiter'] = 100\n",
    "prob.driver.options['tol'] = 1e-8\n",
    "\n",
    "prob.model.add_design_var('x', lower=0, upper=10)\n",
    "prob.model.add_design_var('z', lower=0, upper=10)\n",
    "prob.model.add_objective('obj')\n",
    "prob.model.add_constraint('con1', upper=0)\n",
    "prob.model.add_constraint('con2', upper=0)\n",
    "\n",
    "# Ask OpenMDAO to finite-difference across the model to compute the gradients for the optimizer\n",
    "prob.model.approx_totals()\n",
    "\n",
    "prob.setup()\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "prob.run_driver()\n",
    "\n",
    "print('minimum found at')\n",
    "print(prob.get_val('x')[0])\n",
    "print(prob.get_val('z'))\n",
    "\n",
    "print('minumum objective')\n",
    "print(prob.get_val('obj')[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",
    "\n",
    "assert_near_equal(prob.get_val('x')[0], 0., 1e-5)\n",
    "assert_near_equal(prob.get_val('z'), [1.977639, 0.], 1e-5)\n",
    "assert_near_equal(prob.get_val('obj')[0], 3.18339395045, 1e-5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Controlling the Solver Print Output\n",
    "Notice the call to `prob.set_solver_print()`,\n",
    "which sets the solver output to level 0.\n",
    "This is the semi-quiet setting where you will only be notified if the solver failed to converge.\n",
    "There are lots of ways to [configure the solver print](../../features/core_features/controlling_solver_behavior/solver_options) output in your model to suit your needs.\n",
    "\n",
    "\n",
    "## Approximate the total derivatives with finite difference\n",
    "\n",
    "In this case we're using the `SLSQP` algorithm, which is a gradient-based optimization approach.\n",
    "Up to this point, none of our components have provided any analytic derivatives,\n",
    "so we'll just finite difference across the whole model to approximate the derivatives.\n",
    "This is accomplished by this line of code:\n",
    "```\n",
    "prob.model.approx_totals()\n",
    "```\n",
    "\n",
    "```{Note}\n",
    "We're using finite difference here for simplicity, but for larger models, finite differencing results in a high computational cost, and can have limited accuracy.\n",
    "It's much better to use analytic derivatives with your models. You can learn more about that in the [Advanced User Guide](../../advanced_user_guide/advanced_user_guide).\n",
    "```"
   ]
  }
 ],
 "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.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
