{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output",
     "active-ipynb"
    ]
   },
   "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": [
    "# Checking Total Derivatives\n",
    "\n",
    "If you want to check the analytic derivatives of your model (or just part of it) against finite difference or complex-step approximations, you can use `check_totals`(). You should always converge your model before calling this method. By default, this method checks the derivatives of all of the driver responses (objectives, constraints) with respect to the des_vars, though you can also specify the variables you want to check. Derivatives are computed and compared in an unscaled form by default, but you can optionally request for them to be computed in scaled form using the `ref` and `ref0` that were declared when adding the constraints, objectives, and des_vars."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{Note}\n",
    "You should probably **not** use this method until you’ve used `check_partials`() to verify the partials for each component in your model. `check_totals`() is a blunt instrument, since it can only tell you that there is a problem, but will not give you much insight into which component or group is causing the problem.\n",
    "\n",
    "``` "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.core.problem.Problem.check_totals\n",
    "        :noindex:\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Please check out the [Complex Step Guidelines](complex-step-guidelines) for more information on how to use it on a model."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Examples\n",
    "\n",
    "You can check specific combinations of variables by specifying them manually:"
   ]
  },
  {
   "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_src63\", get_code(\"openmdao.test_suite.components.sellar.SellarDerivatives\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `SellarDerivatives` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src63`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarDerivatives\n",
    "\n",
    "prob = om.Problem()\n",
    "prob.model = SellarDerivatives()\n",
    "prob.model.nonlinear_solver = om.NonlinearBlockGS()\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# manually specify which derivatives to check\n",
    "prob.check_totals(of=['obj', 'con1'], wrt=['x', 'z'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "Check all the derivatives that the driver will need:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarDerivatives\n",
    "\n",
    "prob = om.Problem()\n",
    "prob.model = SellarDerivatives()\n",
    "prob.model.nonlinear_solver = om.NonlinearBlockGS()\n",
    "\n",
    "prob.model.add_design_var('x', lower=-100, upper=100)\n",
    "prob.model.add_design_var('z', lower=-100, upper=100)\n",
    "prob.model.add_objective('obj')\n",
    "prob.model.add_constraint('con1', upper=0.0)\n",
    "prob.model.add_constraint('con2', upper=0.0)\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "# We don't call run_driver() here because we don't\n",
    "# actually want the optimizer to run\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# check derivatives of all obj+constraints w.r.t all design variables\n",
    "prob.check_totals()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "Use the driver scaled values during the check:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarDerivatives\n",
    "\n",
    "prob = om.Problem()\n",
    "prob.model = SellarDerivatives()\n",
    "prob.model.nonlinear_solver = om.NonlinearBlockGS()\n",
    "\n",
    "prob.model.add_design_var('x', lower=-100, upper=100, ref=100.0, ref0=-100.0)\n",
    "prob.model.add_design_var('z', lower=-100, upper=100)\n",
    "prob.model.add_objective('obj')\n",
    "prob.model.add_constraint('con1', upper=0.0, ref=3.0)\n",
    "prob.model.add_constraint('con2', upper=0.0, ref=20.0)\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "# We don't call run_driver() here because we don't\n",
    "# actually want the optimizer to run\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# check derivatives of all driver vars using the declared scaling\n",
    "prob.check_totals(driver_scaling=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "---\n",
    "Display the results in a compact format:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarDerivatives\n",
    "\n",
    "prob = om.Problem()\n",
    "prob.model = SellarDerivatives()\n",
    "prob.model.nonlinear_solver = om.NonlinearBlockGS()\n",
    "\n",
    "prob.model.add_design_var('x', lower=-100, upper=100)\n",
    "prob.model.add_design_var('z', lower=-100, upper=100)\n",
    "prob.model.add_objective('obj')\n",
    "prob.model.add_constraint('con1', upper=0.0)\n",
    "prob.model.add_constraint('con2', upper=0.0)\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "# We don't call run_driver() here because we don't\n",
    "# actually want the optimizer to run\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# check derivatives of all obj+constraints w.r.t all design variables\n",
    "prob.check_totals(compact_print=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "Use complex step instead of finite difference for a more accurate check. We also tighten the tolerance on the nonlinear Gauss-Seidel solver so that we get more accurate converged values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarDerivatives\n",
    "\n",
    "prob = om.Problem()\n",
    "prob.model = SellarDerivatives()\n",
    "prob.model.nonlinear_solver = om.NonlinearBlockGS()\n",
    "\n",
    "prob.model.add_design_var('x', lower=-100, upper=100)\n",
    "prob.model.add_design_var('z', lower=-100, upper=100)\n",
    "prob.model.add_objective('obj')\n",
    "prob.model.add_constraint('con1', upper=0.0)\n",
    "prob.model.add_constraint('con2', upper=0.0)\n",
    "\n",
    "prob.setup(force_alloc_complex=True)\n",
    "\n",
    "# We don't call run_driver() here because we don't\n",
    "# actually want the optimizer to run\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prob.model.nonlinear_solver.options['atol'] = 1e-15\n",
    "prob.model.nonlinear_solver.options['rtol'] = 1e-15\n",
    "\n",
    "# check derivatives with complex step\n",
    "prob.check_totals(method='cs')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that you need to set the argument `force_alloc_complex` to True when you call `setup`. This makes sure that the vectors allocate space to pass complex numbers. All systems also have an attribute `force_alloc_complex` that can be queried during `setup` in case you need to set up your `Component` or `Group` differently to accomodate complex step."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "class SimpleComp(om.ExplicitComponent):\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('x', val=1.0)\n",
    "        self.add_output('y', val=1.0)\n",
    "\n",
    "        self.declare_partials(of='y', wrt='x')\n",
    "\n",
    "        if self._force_alloc_complex:\n",
    "            print(\"Vectors allocated for complex step.\")\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        outputs['y'] = 3.0*inputs['x']\n",
    "\n",
    "    def compute_partials(self, inputs, partials):\n",
    "        partials['y', 'x'] = 3.\n",
    "\n",
    "prob = om.Problem()\n",
    "prob.model.add_subsystem('comp', SimpleComp())\n",
    "\n",
    "prob.model.add_design_var('comp.x', lower=-100, upper=100)\n",
    "prob.model.add_objective('comp.y')\n",
    "\n",
    "prob.setup(force_alloc_complex=True)\n",
    "\n",
    "prob.run_model()\n",
    "\n",
    "prob.check_totals(method='cs')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "---\n",
    "Turn off standard output and just view the derivatives in the return:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarDerivatives\n",
    "\n",
    "prob = om.Problem()\n",
    "prob.model = SellarDerivatives()\n",
    "prob.model.nonlinear_solver = om.NonlinearBlockGS()\n",
    "\n",
    "prob.model.add_design_var('x', lower=-100, upper=100)\n",
    "prob.model.add_design_var('z', lower=-100, upper=100)\n",
    "prob.model.add_objective('obj')\n",
    "prob.model.add_constraint('con1', upper=0.0)\n",
    "prob.model.add_constraint('con2', upper=0.0)\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "# We don't call run_driver() here because we don't\n",
    "# actually want the optimizer to run\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# check derivatives of all obj+constraints w.r.t all design variables\n",
    "totals = prob.check_totals(out_stream=None)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(totals)"
   ]
  }
 ],
 "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": 5
}
