{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "associate-silicon",
   "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": "herbal-skating",
   "metadata": {},
   "source": [
    "# Changing Check Settings for FD or CS\n",
    "\n",
    "## Changing Settings for Inputs on a Specific Component\n",
    "\n",
    "You can change the settings for the approximation schemes that will be used to compare with your component’s derivatives by calling the `set_check_partial_options` method."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "beautiful-ethernet",
   "metadata": {},
   "source": [
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.core.component.Component.set_check_partial_options\n",
    "        :noindex:\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "received-nightmare",
   "metadata": {},
   "source": [
    "```{Note}\n",
    "If you want to use method=”cs”, then you must also pass `force_alloc_complex`=True to setup. See the example below.\n",
    "``` "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "piano-disease",
   "metadata": {},
   "source": [
    "This allows custom tailoring of the approximation settings on a variable basis."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "unavailable-anaheim",
   "metadata": {},
   "source": [
    "## Usage Examples"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "embedded-traveler",
   "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_src59\", get_code(\"openmdao.test_suite.components.paraboloid_mat_vec.ParaboloidMatVec\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "vocational-capitol",
   "metadata": {},
   "source": [
    ":::{Admonition} `ParaboloidMatVec` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src59`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "related-premium",
   "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_src60\", get_code(\"openmdao.core.tests.test_check_derivs.ParaboloidTricky\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "mighty-companion",
   "metadata": {},
   "source": [
    ":::{Admonition} `ParaboloidTricky` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src60`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "angry-drive",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.core.tests.test_check_derivs import ParaboloidTricky\n",
    "from openmdao.test_suite.components.paraboloid_mat_vec import ParaboloidMatVec\n",
    "\n",
    "prob = om.Problem()\n",
    "\n",
    "comp = prob.model.add_subsystem('comp', ParaboloidTricky())\n",
    "prob.model.add_subsystem('comp2', ParaboloidMatVec())\n",
    "\n",
    "prob.model.connect('comp.f_xy', 'comp2.x')\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "comp.set_check_partial_options(wrt='*', step=1e-2)\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()\n",
    "\n",
    "prob.check_partials(compact_print=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "owned-diana",
   "metadata": {},
   "source": [
    "Here, we show how to set the method. In this case, we use complex step on TrickyParaboloid because the finite difference is less accurate."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bright-despite",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "square-disclaimer",
   "metadata": {},
   "source": [
    "```{Note}\n",
    "You need to `set force_alloc_complex` to True during setup to utilize complex step during a check.\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "legal-drinking",
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.core.tests.test_check_derivs import ParaboloidTricky\n",
    "from openmdao.test_suite.components.paraboloid_mat_vec import ParaboloidMatVec\n",
    "\n",
    "prob = om.Problem()\n",
    "\n",
    "comp = prob.model.add_subsystem('comp', ParaboloidTricky())\n",
    "prob.model.add_subsystem('comp2', ParaboloidMatVec())\n",
    "\n",
    "prob.model.connect('comp.f_xy', 'comp2.x')\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "comp.set_check_partial_options(wrt='*', method='cs')\n",
    "\n",
    "prob.setup(force_alloc_complex=True)\n",
    "prob.run_model()\n",
    "\n",
    "prob.check_partials(compact_print=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "civilian-silicon",
   "metadata": {},
   "source": [
    "## Directional Derivatives\n",
    "You can also specify that an input or set of inputs be checked using a directional derivative. For vector inputs, this means that, instead of calculating the derivative with respect to each element of the array, we calculate the derivative with respect to a linear combination of all array indices. For finite difference and complex step, the step value is applied simultaneously to all elements of the vector. This is a much quicker check because it only requires a single execution of the component for the variable rather than one for each element of the array."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "superior-document",
   "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_src61\", get_code(\"openmdao.test_suite.components.array_comp.ArrayComp\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "educated-stevens",
   "metadata": {},
   "source": [
    ":::{Admonition} `ArrayComp` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src61`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "formed-campbell",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.array_comp import ArrayComp\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "mycomp = model.add_subsystem('mycomp', ArrayComp(), promotes=['*'])\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()\n",
    "\n",
    "data = prob.check_partials()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "facial-container",
   "metadata": {},
   "source": [
    "If your component is matrix-free and you request directional derivatives, then your reverse mode derivatives will be verified using the dot product test described [here](http://www.reproducibility.org/RSF/book/gee/ajt/paper_html/node20.html)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "rational-stock",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import openmdao.api as om\n",
    "\n",
    "class ArrayCompMatrixFree(om.ExplicitComponent):\n",
    "\n",
    "    def setup(self):\n",
    "\n",
    "        J1 = np.array([[1.0, 3.0, -2.0, 7.0],\n",
    "                        [6.0, 2.5, 2.0, 4.0],\n",
    "                        [-1.0, 0.0, 8.0, 1.0],\n",
    "                        [1.0, 4.0, -5.0, 6.0]])\n",
    "\n",
    "        self.J1 = J1\n",
    "        self.J2 = J1 * 3.3\n",
    "        self.Jb = J1.T\n",
    "\n",
    "        # Inputs\n",
    "        self.add_input('x1', np.zeros([4]))\n",
    "        self.add_input('x2', np.zeros([4]))\n",
    "        self.add_input('bb', np.zeros([4]))\n",
    "\n",
    "        # Outputs\n",
    "        self.add_output('y1', np.zeros([4]))\n",
    "\n",
    "        self.set_check_partial_options('*', directional=True)\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        \"\"\"\n",
    "        Execution.\n",
    "        \"\"\"\n",
    "        outputs['y1'] = self.J1.dot(inputs['x1']) + self.J2.dot(inputs['x2']) + self.Jb.dot(inputs['bb'])\n",
    "\n",
    "    def compute_jacvec_product(self, inputs, dinputs, doutputs, mode):\n",
    "        \"\"\"Returns the product of the incoming vector with the Jacobian.\"\"\"\n",
    "\n",
    "        if mode == 'fwd':\n",
    "            if 'x1' in dinputs:\n",
    "                doutputs['y1'] += self.J1.dot(dinputs['x1'])\n",
    "            if 'x2' in dinputs:\n",
    "                doutputs['y1'] += self.J2.dot(dinputs['x2'])\n",
    "            if 'bb' in dinputs:\n",
    "                doutputs['y1'] += self.Jb.dot(dinputs['bb'])\n",
    "\n",
    "        elif mode == 'rev':\n",
    "            if 'x1' in dinputs:\n",
    "                dinputs['x1'] += self.J1.T.dot(doutputs['y1'])\n",
    "            if 'x2' in dinputs:\n",
    "                dinputs['x2'] += self.J2.T.dot(doutputs['y1'])\n",
    "            if 'bb' in dinputs:\n",
    "                dinputs['bb'] += self.Jb.T.dot(doutputs['y1'])\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "model.add_subsystem('mycomp', ArrayCompMatrixFree(), promotes=['*'])\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()\n",
    "\n",
    "data = prob.check_partials()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "reasonable-honey",
   "metadata": {},
   "source": [
    "## Changing Global Settings For Whole Model"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "academic-charge",
   "metadata": {},
   "source": [
    "You can change the settings globally for all approximations used for all components. This is done by passing in a value for any of the following arguments:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "noble-triumph",
   "metadata": {},
   "source": [
    "| Name      | Description                                                                                    |\n",
    "|-----------|------------------------------------------------------------------------------------------------|\n",
    "| method    | Method for check: “fd” for finite difference, “cs” for complex step.                           |\n",
    "| form      | Finite difference form for check, can be “forward”, “central”, or backward.                    |\n",
    "| step      | Step size for finite difference check.                                                         |\n",
    "| step_calc | When \"abs\", the step is absolute. A relative step can also be chosen with one of the values in table below.                                |\n",
    "| minimum_step | Minimum allowable step when using one of the relative step_calc options. |\n",
    "\n",
    "If you need to scale the finite difference step by the variable's magnitude, the following additional choices for \"step_calc\" are available:\n",
    "\n",
    "| step_calc       | Step size is scaled by |\n",
    "| :---            |    :----:                                       |\n",
    "| \"rel_avg\"       | Average absolute value of the vector.           |\n",
    "| \"rel_element\"   | Absolute value of each vector element.          |\n",
    "| \"rel_legacy\"    | Norm of the vector.                             |\n",
    "| \"rel\"           | Same as \"rel_legacy\" currently, but will become \"rel_avg\" in the OpenMDAO 3.12.0.  |\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "interior-guest",
   "metadata": {},
   "source": [
    "```{Note}\n",
    "The global check options take precedence over the ones defined on a component.\n",
    "``` "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fancy-voltage",
   "metadata": {},
   "source": [
    "## Usage Examples"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "municipal-recruitment",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.core.tests.test_check_derivs import ParaboloidTricky\n",
    "from openmdao.test_suite.components.paraboloid_mat_vec import ParaboloidMatVec\n",
    "\n",
    "prob = om.Problem()\n",
    "\n",
    "prob.model.add_subsystem('comp', ParaboloidTricky())\n",
    "prob.model.add_subsystem('comp2', ParaboloidMatVec())\n",
    "\n",
    "prob.model.connect('comp.f_xy', 'comp2.x')\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()\n",
    "\n",
    "prob.check_partials(step=1e-2, compact_print=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "velvet-element",
   "metadata": {},
   "source": [
    "Here is an example where we check our partials using complex step.\n",
    "\n",
    "```{Note}\n",
    "You need to set `force_alloc_complex` to True during setup to utilize complex step during a check.\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "affected-seattle",
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.core.tests.test_check_derivs import ParaboloidTricky\n",
    "from openmdao.test_suite.components.paraboloid_mat_vec import ParaboloidMatVec\n",
    "\n",
    "prob = om.Problem()\n",
    "\n",
    "prob.model.add_subsystem('comp', ParaboloidTricky())\n",
    "prob.model.add_subsystem('comp2', ParaboloidMatVec())\n",
    "\n",
    "prob.model.connect('comp.f_xy', 'comp2.x')\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "prob.setup(force_alloc_complex=True)\n",
    "prob.run_model()\n",
    "\n",
    "prob.check_partials(method='cs', compact_print=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "otherwise-sheet",
   "metadata": {},
   "source": [
    "In this example, we check our partials with finite difference, but we use central differencing for more accuracy."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "closed-blood",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.core.tests.test_check_derivs import ParaboloidTricky\n",
    "from openmdao.test_suite.components.paraboloid_mat_vec import ParaboloidMatVec\n",
    "\n",
    "prob = om.Problem()\n",
    "\n",
    "prob.model.add_subsystem('comp', ParaboloidTricky())\n",
    "prob.model.add_subsystem('comp2', ParaboloidMatVec())\n",
    "\n",
    "prob.model.connect('comp.f_xy', 'comp2.x')\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()\n",
    "\n",
    "prob.check_partials(form='central', compact_print=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "reduced-richmond",
   "metadata": {},
   "source": [
    "In this example, we use a relative step-size. This is sometimes needed for casese where an input variable's value can be in a wide range of orders of magnitude, and we don't want the step size to become to small or large for it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "multiple-tiffany",
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.core.tests.test_check_derivs import ParaboloidTricky\n",
    "\n",
    "prob = om.Problem()\n",
    "\n",
    "prob.model.add_subsystem('comp', ParaboloidTricky())\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()\n",
    "\n",
    "prob.check_partials(step_calc='rel', compact_print=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "referenced-diabetes",
   "metadata": {},
   "source": [
    "Finally, in this example, we have a vector input whose elements differ by several orders of magnitude, and whose middle element is sometimes zero.  If you try to scale the step size by a variable that is zero, you end up with a step size equal to zero, which causes division by zero during computation.  To prevent this, OpenMDAO defines a minimum step size of 1e-12. You can change this value by setting the `minimum_step` argument to a new value whenever you set the `step_calc` argument. Here, we also use the 'rel_element' step_calc so that each element computes its own step size relative to the magnitude of that single element."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "nonprofit-dance",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import openmdao.api as om\n",
    "\n",
    "\n",
    "class FDComp(om.ExplicitComponent):\n",
    "\n",
    "    def initialize(self):\n",
    "        self.options.declare('vec_size', types=int, default=1)\n",
    "\n",
    "    def setup(self):\n",
    "        nn = self.options['vec_size']\n",
    "\n",
    "        self.add_input('x_element', np.ones((nn, )))\n",
    "        self.add_output('y', np.ones((nn, )))\n",
    "\n",
    "    def setup_partials(self):\n",
    "        self.declare_partials('*', 'x_element')\n",
    "\n",
    "        self.set_check_partial_options('x_element', method='fd', step_calc='rel_element', minimum_step=1e-15)\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        x = inputs['x_element']\n",
    "        outputs['y'] = 0.5 * x ** 2\n",
    "\n",
    "    def compute_partials(self, inputs, partials):\n",
    "        x = inputs['x_element']\n",
    "        partials['y', 'x_element'] = np.diag(x)\n",
    "\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('comp', FDComp(vec_size=3))\n",
    "\n",
    "prob.setup(force_alloc_complex=True)\n",
    "\n",
    "x = np.array([1e10, 0.0, 1e-10])\n",
    "prob.set_val('comp.x_element', x)\n",
    "\n",
    "prob.run_model()\n",
    "\n",
    "totals = prob.check_partials()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "still-outside",
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "assert_near_equal(totals['comp']['y', 'x_element']['J_fd'][0][0], x[0], 1e-5)\n",
    "assert_near_equal(totals['comp']['y', 'x_element']['J_fd'][1][1], x[1], 1e-5)\n",
    "assert_near_equal(totals['comp']['y', 'x_element']['J_fd'][2][2], x[2], 1e-5)"
   ]
  }
 ],
 "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.10"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
