{
 "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": [
    "# Defining Partial Derivatives on Implicit Components\n",
    "\n",
    "For [ImplicitComponent](../../features/core_features/working_with_components/implicit_component.ipynb) instances, you will provide partial derivatives of **residuals with respect to inputs and outputs**. Note that this is slightly different than what you do for [ExplicitComponent instances](partial_derivs_explicit.ipynb), but the general procedure is similar:\n",
    "\n",
    "1. Declare the partial derivatives via `declare_partials`.\n",
    "2. Specify their values via `linearize`.\n",
    "\n",
    "Residual values are computed in the `apply_nonlinear` method, so those equations are the ones you will differentiate. For the sake of complete clarity, if your `ImplicitComponent` does happen to define a `solve_nonlinear` method, then you will still provide derivatives of the `apply_nonlinear` method to OpenMDAO.\n",
    "\n",
    "Here is a simple example to consider:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "\n",
    "class QuadraticComp(om.ImplicitComponent):\n",
    "    \"\"\"\n",
    "    A Simple Implicit Component representing a Quadratic Equation.\n",
    "\n",
    "    R(a, b, c, x) = ax^2 + bx + c\n",
    "\n",
    "    Solution via Quadratic Formula:\n",
    "    x = (-b + sqrt(b^2 - 4ac)) / 2a\n",
    "    \"\"\"\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('a', val=1.)\n",
    "        self.add_input('b', val=1.)\n",
    "        self.add_input('c', val=1.)\n",
    "        self.add_output('x', val=0.)\n",
    "\n",
    "    def setup_partials(self):\n",
    "        self.declare_partials(of='x', wrt='*')\n",
    "\n",
    "    def apply_nonlinear(self, inputs, outputs, residuals):\n",
    "        a = inputs['a']\n",
    "        b = inputs['b']\n",
    "        c = inputs['c']\n",
    "        x = outputs['x']\n",
    "        residuals['x'] = a * x ** 2 + b * x + c\n",
    "\n",
    "    def solve_nonlinear(self, inputs, outputs):\n",
    "        a = inputs['a']\n",
    "        b = inputs['b']\n",
    "        c = inputs['c']\n",
    "        outputs['x'] = (-b + (b ** 2 - 4 * a * c) ** 0.5) / (2 * a)\n",
    "\n",
    "    def linearize(self, inputs, outputs, partials):\n",
    "        a = inputs['a']\n",
    "        b = inputs['b']\n",
    "        c = inputs['c']\n",
    "        x = outputs['x']\n",
    "\n",
    "        partials['x', 'a'] = x ** 2\n",
    "        partials['x', 'b'] = x\n",
    "        partials['x', 'c'] = 1.0\n",
    "        partials['x', 'x'] = 2 * a * x + b\n",
    "\n",
    "        self.inv_jac = 1.0 / (2 * a * x + b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this component, `x` is an output, and you take derivatives with respect to it. This might seem a bit strange to you if you're used to thinking about things from an [ExplicitComponent](partial_derivs_explicit.ipynb) perspective. But for implicit components it is necessary, because the values of those outputs are determined by a solver, like [NewtonSolver](../../features/building_blocks/solvers/newton.ipynb), which will need to know those derivatives. They are also necessary for the total derivative computations across the whole model. So if your residual is a function of one or more of the component outputs, make sure you provide those partials to OpenMDAO.\n",
    "\n",
    "## Check That Your Derivatives Are Correct!\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from openmdao.test_suite.components.quad_implicit import QuadraticComp\n",
    "\n",
    "p = om.Problem()\n",
    "\n",
    "p.model.add_subsystem('quad', QuadraticComp())\n",
    "\n",
    "p.setup()\n",
    "\n",
    "p.check_partials(compact_print=True);"
   ]
  }
 ],
 "metadata": {
  "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"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
