{
 "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": [
    "# Solving for Derivatives of Multiple Separable Constraints Using a Single Linear Solve\n",
    "\n",
    "A set of constraints are separable when there are subsets of the design variables that don't affect\n",
    "any of the responses. In other words, there is some subset of columns of the\n",
    "**total-derivative Jacobian** where none of those columns have nonzero values in any of the same rows.\n",
    "This kind of sparsity structure in the total-derivative Jacobian allows OpenMDAO to solve for multiple\n",
    "total derivatives simultaneously, which can dramatically reduce the cost of computing total derivatives.\n",
    "Remember that OpenMDAO solves the [unified derivative equations](../total_derivs_theory.ipynb) to\n",
    "compute total derivatives.\n",
    "\n",
    "$$\n",
    "    \\begin{align*}\n",
    "    \\left[\\frac{\\partial \\mathcal{R}}{\\partial U}\\right] \\left[\\frac{du}{dr}\\right] = \\left[ I \\right] = \\left[\\frac{\\partial \\mathcal{R}}{\\partial U}\\right]^T \\left[\\frac{du}{dr}\\right]^T .\n",
    "    \\end{align*}\n",
    "$$\n",
    "\n",
    "When separable constraints are present, multiple right hand sides from $\\left[ I \\right]$ can\n",
    "be combined into a single right-hand-side vector, and then total derivatives for multiple variables\n",
    "can be computed with a single linear solve. Normally, summing multiple right-hand-side vectors would\n",
    "result in the solution vector holding linear combinations of multiple derivatives:\n",
    "\n",
    "$$\n",
    "  \\begin{gather}\n",
    "  \\frac{du_0}{dr_0} + \\frac{du_0}{dr_2} + \\cdots + \\frac{du_0}{dr_n}\\\\\n",
    "  \\vdots \\\\\n",
    "  \\frac{du_m}{dr_0} + \\frac{du_m}{dr_2} + \\cdots + \\frac{du_m}{dr_n}\\\\\n",
    "  \\end{gather}\n",
    "$$\n",
    "\n",
    "However, because the problem is separable, we know that $\\frac{dy_i}{dx_j}=0$ for all $i \\ne j$ for seperable variables.\n",
    "Therefore, it is safe to do all the linear solves at the same time, and we can get a significant\n",
    "computational savings with no additional complexity of memory costs.\n",
    "\n",
    "## A Simple Example\n",
    "\n",
    "Consider a notional optimization problem with 5 design variables ($a, b, c, d, e$), one objective\n",
    "($f$), and three constraints ($g_c, g_d, g_e$). Normally with 5 design variables and 4\n",
    "responses --- 3 constraints + 1 objective --- you would choose to use reverse mode since that would\n",
    "yield fewer linear solves. However, if the problem had the following partial derivative Jacobian\n",
    "structure, then it would be separable in forward mode, and we'll show that because of that, forward\n",
    "mode is the preferred method.\n",
    "\n",
    "![Jacobian structure for a notional separable problem](../images/matrix_figs/simultaneous_jac.png)\n",
    "\n",
    "The two dense columns corresponding to $a, b$ mean that all of the outputs depend on these\n",
    "variables and they must each get their own linear solves in forward mode.\n",
    "\n",
    "![Two linear solves needed for the two non-separable variables](../images/matrix_figs/simultaneous_dense.png)\n",
    "\n",
    "Normally, each of the remaining variables, ($c, d, e$), would also need their own linear solves, as shown below.\n",
    "In the solution and right-hand-side vectors, the zero values are denoted by the lighter-colored blocks.\n",
    "The nonzero values are denoted by the darker-colored blocks.\n",
    "Notice how the three solution vectors have no overlapping nonzero values.\n",
    "\n",
    "![Three separate linear solves for the separable variables](../images/matrix_figs/simultaneous_sparse_separate.png)\n",
    "\n",
    "Those three solution vectors are non-overlapping because the three associated variables are separable.\n",
    "The forward-separable structure shows up clearly in the partial-derivative Jacobian, because it has\n",
    "been ordered to expose a block-diagonal structure.\n",
    "This allows us to collapse all three linear solves into a single simultaneous one:\n",
    "\n",
    "![Three separate linear solves for the separable variables](../images/matrix_figs/simultaneous_sparse_combined.png)\n",
    "\n",
    "Using forward simultaneous derivatives reduces the required number of solves from 5 to 3\n",
    "(2 for $a, b$ and 1 for $c, d, e$).\n",
    "Hence, it would be faster to solve for total derivatives using forward mode with simultaneous\n",
    "derivatives than reverse mode.\n",
    "\n",
    "Determining if Your Problem is Separable\n",
    "\n",
    "The simple example above was contrived to make it relatively obvious that the problem was separable.\n",
    "For realistic problems, even if you know that the problem should be separable, computing the actual\n",
    "input/output sets can be challenging. You can think of the total derivative Jacobian as a graph with\n",
    "nodes representing each variable and non-zero entries representing edges connecting the nodes.\n",
    "Then the task of finding the separable variables can be performed using a graph-coloring algorithm.\n",
    "In that case, a set of separable variables are said to have the same color.\n",
    "The simple example problem would then have three colors; one each for $a$ and $b$ and\n",
    "one more for $c,d,e$.\n",
    "\n",
    "For any arbitrary problem, once you know the total-derivative Jacobian, then, in theory, you could color it.\n",
    "Since OpenMDAO can compute the total-derivative Jacobian, it would seem to be simply a matter of\n",
    "applying a coloring algorithm to it. However, there is a potential pitfall that needs to be accounted for.\n",
    "For any arbitrary point in the design space, some total derivatives could turn out to be zero, despite\n",
    "the fact that they are nonzero at other locations. An incidental zero would mean a missing edge in\n",
    "the graph, and could potentially deliver an incorrect coloring. The challenge is to figure out the\n",
    "non-zero entries in the total derivative Jacobian in a more robust way.\n",
    "\n",
    "OpenMDAO knows the partial-derivative sparsity of a model because the\n",
    "[nonzero partials are specified](../../features/core_features/working_with_derivatives/sparse_partials.ipynb) by each component in its setup method. So we need to compute the sparsity pattern of the total Jacobian, given the sparsity pattern of the partial Jacobian, in a way that reduces the chance of getting incidental zero values.\n",
    "\n",
    "We can minimize the chance of having incidental zeros in the inverse by setting random numbers into the nonzero entries of the partial-derivative matrix,\n",
    "then computing the resulting total-derivative Jacobian using the randomized values. The derivatives computed in this way will not be physically meaningful,\n",
    "but the chance of having any incidental zero values is now very small. The likelihood of incidental zeros can be further reduced by\n",
    "computing the total-derivative Jacobian multiple times with different, random left-hand sides, and summing the absolute values of the\n",
    "resulting total-derivative Jacobians together.\n",
    "\n",
    "Hence the cost of the coloring algorithm increases by the cost of $n$ computations of the full total-derivative Jacobian.\n",
    "The larger you choose to make $n$, the more reliable your coloring will be.\n",
    "If the model is intended to be used in an optimization context, then it is fair to assume that the total-derivative Jacobian is inexpensive enough to compute many times,\n",
    "and using a few additional computations to compute a coloring will not significantly impact the overall compute cost.\n",
    "\n",
    "Choosing Forward or Reverse Mode for Separable Problems\n",
    "If a problem has a section of design variables and constraints that are separable,\n",
    "then it is possible to leverage that quality in either forward or reverse mode.\n",
    "Which mode you choose depends on which direction gives you fewer total linear solves.\n",
    "In the example above, we show how separability changes the faster method from reverse to forward, but in general it does not have to cause that effect.\n",
    "\n",
    "Normally you would count the number of design variables and responses and choose the mode corresponding to whichever one is smaller.\n",
    "For separable problems, you count the number of colors you have in each direction and choose which ever one is smaller.\n",
    "Sometimes the answer is different than you would get by counting design variables and constraints, but sometimes its not.\n",
    "The result is problem-dependent.\n",
    "\n",
    "Relevance to Finite Difference and Complex Step\n",
    "It is worth noting that, in addition to speeding up linear solutions for the unified derivative equations, forward separability also offers benefits when finite difference or complex step are being used to compute derivatives numerically.\n",
    "For the same reasons that multiple linear solves can be combined, you can also take steps in multiple variables to compute derivatives with respect to multiple variables at the same time.\n",
    "\n",
    "\n",
    "How to actually use it!\n",
    "OpenMDAO provides a mechanism for you to specify a coloring to take advantage of separability, via the\n",
    "[use_fixed_coloring](../../features/core_features/working_with_derivatives/simul_derivs.ipynb) method.\n",
    "OpenMDAO also provides a [coloring tool](static-coloring) to determine the minimum number of colors your problem can be reduced to.\n",
    "\n",
    "You can also see an example of setting up an optimization with simultaneous derivatives in\n",
    "the [Simple Optimization using Simultaneous Derivatives](../../examples/simul_deriv_example.ipynb) example."
   ]
  }
 ],
 "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"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
