{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fb7cb606",
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output",
     "active-ipynb"
    ]
   },
   "outputs": [],
   "source": [
    "try:\n",
    "    from openmdao.utils.notebook_utils import notebook_mode  # noqa: F401\n",
    "except ImportError:\n",
    "    !python -m pip install openmdao[notebooks]"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "e25d82ec",
   "metadata": {},
   "source": [
    "# Checking Partial Derivatives with Finite Difference\n",
    "In addition to using approximations to estimate partial derivatives, you can also use approximations to check your implementations of the partial derivatives for a component.\n",
    "\n",
    "`Problem` has a method, `check_partials`, that checks partial derivatives\n",
    "comprehensively for all Components in your model. To do this check, the framework compares the analytic result against a finite difference result. This means that the check_partials function can be quite computationally expensive. So use it to check your work, but don’t leave the call in your production run scripts."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "f26e1953",
   "metadata": {},
   "source": [
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.core.problem.Problem.check_partials\n",
    "        :noindex:\n",
    "```"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "9f22725a",
   "metadata": {},
   "source": [
    "```{Note}\n",
    "For components that provide their partials directly (from the `compute_partials` or `linearize` methods, only information about the forward derivatives are shown. For components that are matrix-free, both forward and reverse derivative information is shown.\n",
    "\n",
    "Implicit components are matrix-free if they define a `apply_linear` method. Explicit components are matrix-free if they define a `compute_jacvec_product` method.\n",
    "``` "
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "1876c591",
   "metadata": {},
   "source": [
    "## Basic Usage\n",
    "When the difference between the FD derivative and the provided derivative violates the tolerance check, that partial derivative will be marked with a '*'. The tolerance check is like the one used in numpy's `allclose` function, where a tolerance violation occurs if:\n",
    "\n",
    " abs(check_value - desired_value) > abs_tol + rtol * abs(desired_value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fb1d8499",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import openmdao.api as om\n",
    "\n",
    "class MyComp(om.ExplicitComponent):\n",
    "    def setup(self):\n",
    "        self.add_input('x1', 3.0)\n",
    "        self.add_input('x2', 5.0)\n",
    "\n",
    "        self.add_output('y', 5.5)\n",
    "\n",
    "        self.declare_partials(of='*', wrt='*')\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        outputs['y'] = 3.0*inputs['x1'] + 4.0*inputs['x2']\n",
    "\n",
    "    def compute_partials(self, inputs, partials):\n",
    "        \"\"\"Intentionally incorrect derivative.\"\"\"\n",
    "        J = partials\n",
    "        J['y', 'x1'] = np.array([4.0])\n",
    "        J['y', 'x2'] = np.array([40])\n",
    "\n",
    "prob = om.Problem()\n",
    "\n",
    "prob.model.add_subsystem('comp', MyComp())\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "prob.setup(mode='rev')\n",
    "prob.run_model()\n",
    "\n",
    "data = prob.check_partials()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2352a2cc",
   "metadata": {},
   "outputs": [],
   "source": [
    "x1_error = data['comp']['y', 'x1']['tol violation']\n",
    "\n",
    "print(x1_error.forward)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6e1d9c07",
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "\n",
    "assert_near_equal(x1_error.forward, 1.0000000004688445, tolerance=3e-6)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "7a1a41db",
   "metadata": {},
   "source": [
    "Turn off standard output and just view the derivatives in the return:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bc3becfb",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import openmdao.api as om\n",
    "\n",
    "class MyComp(om.ExplicitComponent):\n",
    "    def setup(self):\n",
    "        self.add_input('x1', 3.0)\n",
    "        self.add_input('x2', 5.0)\n",
    "\n",
    "        self.add_output('y', 5.5)\n",
    "\n",
    "        self.declare_partials(of='*', wrt='*')\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        outputs['y'] = 3.0*inputs['x1'] + 4.0*inputs['x2']\n",
    "\n",
    "    def compute_partials(self, inputs, partials):\n",
    "        \"\"\"Intentionally incorrect derivative.\"\"\"\n",
    "        J = partials\n",
    "        J['y', 'x1'] = np.array([4.0])\n",
    "        J['y', 'x2'] = np.array([40])\n",
    "\n",
    "prob = om.Problem()\n",
    "\n",
    "prob.model.add_subsystem('comp', MyComp())\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "97cd474d",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "data = prob.check_partials(out_stream=None, compact_print=True)\n",
    "print(data)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "0929a5f6",
   "metadata": {},
   "source": [
    "# Show Only Incorrect Printing Option\n",
    "If you are only concerned with seeing the partials calculations that are incorrect, set `show_only_incorrect` to `True`. This applies to both `compact_print` equal to `True` and `False`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "909248ff",
   "metadata": {
    "tags": [
     "remove-ouput"
    ]
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import openmdao.api as om\n",
    "\n",
    "class MyCompGoodPartials(om.ExplicitComponent):\n",
    "    def setup(self):\n",
    "        self.add_input('x1', 3.0)\n",
    "        self.add_input('x2', 5.0)\n",
    "        self.add_output('y', 5.5)\n",
    "        self.declare_partials(of='*', wrt='*')\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        outputs['y'] = 3.0 * inputs['x1'] + 4.0 * inputs['x2']\n",
    "\n",
    "    def compute_partials(self, inputs, partials):\n",
    "        \"\"\"Correct derivative.\"\"\"\n",
    "        J = partials\n",
    "        J['y', 'x1'] = np.array([3.0])\n",
    "        J['y', 'x2'] = np.array([4.0])\n",
    "\n",
    "class MyCompBadPartials(om.ExplicitComponent):\n",
    "    def setup(self):\n",
    "        self.add_input('y1', 3.0)\n",
    "        self.add_input('y2', 5.0)\n",
    "        self.add_output('z', 5.5)\n",
    "        self.declare_partials(of='*', wrt='*')\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        outputs['z'] = 3.0 * inputs['y1'] + 4.0 * inputs['y2']\n",
    "\n",
    "    def compute_partials(self, inputs, partials):\n",
    "        \"\"\"Intentionally incorrect derivative.\"\"\"\n",
    "        J = partials\n",
    "        J['z', 'y1'] = np.array([33.0])\n",
    "        J['z', 'y2'] = np.array([40.0])\n",
    "\n",
    "prob = om.Problem()\n",
    "prob.model.add_subsystem('good', MyCompGoodPartials())\n",
    "prob.model.add_subsystem('bad', MyCompBadPartials())\n",
    "prob.model.connect('good.y', 'bad.y1')\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "prob.setup()\n",
    "prob.run_model()\n",
    "\n",
    "prob.check_partials(compact_print=True, show_only_incorrect=True)\n",
    "prob.check_partials(compact_print=False, show_only_incorrect=True)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "0b342f49",
   "metadata": {},
   "source": [
    "# Running With Multiple FD Step Sizes\n",
    "If the `step` argument is provided as a list of values instead of a single value, the FD partial derivatives will be evaluated and displayed for each given step size.  This can be useful in certain cases where complex step checks are not possible and the component(s) being checked are expensive to execute.  Supplying multiple FD step sizes in that case will only compute the analytic derivatives once and compare those to each computed FD derivative, which is less expensive than making a separate call to `check_partials` for each FD step size."
   ]
  }
 ],
 "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.5"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
