{
 "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": [
    "# Setting Nonlinear and Linear Solvers\n",
    "\n",
    "A nonlinear solver, like [NonlinearBlockGS](../../../_srcdocs/packages/solvers.nonlinear/nonlinear_block_gs) or [Newton](../../../_srcdocs/packages/solvers.nonlinear/newton),\n",
    "is used to converge the nonlinear analysis. A nonlinear solver is needed whenever there is a cyclic dependency between components in your model.\n",
    "It might also be needed if you have an [ImplicitComponent](../../../_srcdocs/packages/core/implicitcomponent) in your model that expects the framework to handle its convergence.\n",
    "\n",
    "Whenever you use a nonlinear solver on a [Group](../../../_srcdocs/packages/core/group) or [Component](../../../_srcdocs/packages/core/component), if you're going to be working with analytic derivatives,\n",
    "you will also need a linear solver.\n",
    "A linear solver, like [LinearBlockGS](../../../_srcdocs/packages/solvers.linear/linear_block_gs) or [DirectSolver](../../../_srcdocs/packages/solvers.linear/direct),\n",
    "is used to solve the linear system that provides total derivatives across the model.\n",
    "\n",
    "You can add nonlinear and linear solvers at any level of the model hierarchy,\n",
    "letting you build a hierarchical solver setup to efficiently converge your model and solve for total derivatives across it.\n",
    "\n",
    "\n",
    "## Solvers for the Sellar Problem\n",
    "\n",
    "The Sellar Problem has two components with a cyclic dependency, so the appropriate nonlinear solver is necessary.\n",
    "We'll use the [Newton](../../../_srcdocs/packages/solvers.nonlinear/newton) nonlinear solver,\n",
    "which requires derivatives, so we'll also use the [DirectSolver](../../../_srcdocs/packages/solvers.linear/direct) linear solver."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "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_src44\", get_code(\"openmdao.test_suite.components.sellar.SellarDerivatives\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `SellarDerivatives` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src44`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar import SellarDerivatives\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model = SellarDerivatives()\n",
    "\n",
    "model.nonlinear_solver = newton = om.NewtonSolver(solve_subsystems=False)\n",
    "\n",
    "# using a different linear solver for Newton with a looser tolerance\n",
    "newton.linear_solver = om.ScipyKrylov(atol=1e-4)\n",
    "\n",
    "# used for analytic derivatives\n",
    "model.linear_solver = om.DirectSolver()\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()\n",
    "\n",
    "print(prob.get_val('y1'))\n",
    "print(prob.get_val('y2'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "\n",
    "assert_near_equal(prob.get_val('y1'), 25.58830273, .00001)\n",
    "assert_near_equal(prob.get_val('y2'), 12.05848819, .00001)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Some models have more complex coupling. There could be top-level cycles between groups as well as\n",
    "lower-level groups that have cycles of their own. [openmdao.test_suite.components.double_sellar.DoubleSellar](https://github.com/OpenMDAO/OpenMDAO/blob/4e329d76c687336e2efd3a1f484ff735dbd219d6/openmdao/test_suite/components/double_sellar.py) is a simple example of this kind of model structure. In these problems, you might want to specify a more complex hierarchical solver structure for both nonlinear and linear solvers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "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_src45\", get_code(\"openmdao.test_suite.components.double_sellar.DoubleSellar\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `DoubleSellar` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src45`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true,
    "tags": [
     "output_scroll"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.test_suite.components.double_sellar import DoubleSellar\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model = DoubleSellar()\n",
    "\n",
    "# each SubSellar group converges itself\n",
    "g1 = model.g1\n",
    "g1.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "g1.linear_solver = om.DirectSolver()  # used for derivatives\n",
    "\n",
    "g2 = model.g2\n",
    "g2.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "g2.linear_solver = om.DirectSolver()\n",
    "\n",
    "# Converge the outer loop with Gauss Seidel, with a looser tolerance.\n",
    "model.nonlinear_solver = om.NonlinearBlockGS(rtol=1.0e-5)\n",
    "model.linear_solver = om.ScipyKrylov()\n",
    "model.linear_solver.precon = om.LinearBlockGS()\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()\n",
    "\n",
    "print(prob.get_val('g1.y1'))\n",
    "print(prob.get_val('g1.y2'))\n",
    "print(prob.get_val('g2.y1'))\n",
    "print(prob.get_val('g2.y2'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert_near_equal(prob.get_val('g1.y1'), 0.64, .00001)\n",
    "assert_near_equal(prob.get_val('g1.y2'), 0.80, .00001)\n",
    "assert_near_equal(prob.get_val('g2.y1'), 0.64, .00001)\n",
    "assert_near_equal(prob.get_val('g2.y2'), 0.80, .00001)"
   ]
  }
 ],
 "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.7.3"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
