{
 "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\n",
    "except ImportError:\n",
    "    !python -m pip install openmdao[notebooks]"
   ]
  },
  {
   "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."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f26e1953",
   "metadata": {},
   "source": [
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.core.problem.Problem.check_partials\n",
    "        :noindex:\n",
    "```"
   ]
  },
  {
   "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 either `compute_jacvec_product` or `compute_multi_jacvec_product` methods.\n",
    "``` "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1876c591",
   "metadata": {},
   "source": [
    "## Basic Usage\n",
    "When the difference between the FD derivative and the provided derivative is larger (in either a relative or absolute sense) than 1e-6, that partial derivative will be marked with a '*'."
   ]
  },
  {
   "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()\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']['abs error']\n",
    "\n",
    "print(x1_error.forward)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c37d00da",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "x2_error = data['comp']['y', 'x2']['rel error']\n",
    "\n",
    "print(x2_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)\n",
    "assert_near_equal(x2_error.forward, 8.99999999860222)"
   ]
  },
  {
   "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)"
   ]
  },
  {
   "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)"
   ]
  }
 ],
 "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
}
