{
 "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  # noqa: F401\n",
    "except ImportError:\n",
    "    !python -m pip install openmdao[notebooks]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Using Solver Options\n",
    "\n",
    "All solvers (both nonlinear and linear) have a number of options that you access via the `options` attribute that control its behavior:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input"
    ]
   },
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "om.show_options_table(\"openmdao.solvers.solver.Solver\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Iteration Limit and Convergence Tolerances\n",
    "\n",
    "Here is how you would change the iteration limit and convergence tolerances for [NonlinearBlockGS](../../building_blocks/solvers/nonlinear_block_gs)."
   ]
  },
  {
   "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_src46\", get_code(\"openmdao.test_suite.components.sellar.SellarDis1withDerivatives\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `SellarDis1withDerivatives` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src46`\n",
    ":::"
   ]
  },
  {
   "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_src47\", get_code(\"openmdao.test_suite.components.sellar.SellarDis2withDerivatives\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `SellarDis2withDerivatives` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src47`\n",
    ":::"
   ]
  },
  {
   "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_src48\", get_code(\"openmdao.test_suite.components.sellar_feature.SellarDerivatives\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `SellarDerivatives` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src48`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar_feature import SellarDerivatives\n",
    "\n",
    "prob = om.Problem(model=SellarDerivatives())\n",
    "\n",
    "nlbgs = prob.model.nonlinear_solver = om.NonlinearBlockGS()\n",
    "\n",
    "nlbgs.options['maxiter'] = 20\n",
    "nlbgs.options['atol'] = 1e-6\n",
    "nlbgs.options['rtol'] = 1e-6\n",
    "\n",
    "prob.setup()\n",
    "prob.set_val('x', 1.)\n",
    "prob.set_val('z', np.array([5.0, 2.0]))\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": [
    "## Displaying Solver Convergence Info\n",
    "\n",
    "Solvers can all print out some information about their convergence history. If you want to control that printing behavior you can use the `iprint` option in the solver.\n",
    "\n",
    "\n",
    "**iprint = -1**: Print nothing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar_feature import SellarDerivatives\n",
    "\n",
    "prob = om.Problem()\n",
    "prob.model = SellarDerivatives()\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "newton = prob.model.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "scipy = prob.model.linear_solver = om.ScipyKrylov()\n",
    "\n",
    "newton.options['maxiter'] = 2\n",
    "\n",
    "# use a real bad initial guess\n",
    "prob.set_val('y1', 10000)\n",
    "prob.set_val('y2', -26)\n",
    "\n",
    "newton.options['iprint'] = -1\n",
    "scipy.options['iprint'] = -1\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "**iprint = 0**: Print only errors or convergence failures."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar_feature import SellarDerivatives\n",
    "\n",
    "prob = om.Problem()\n",
    "prob.model = SellarDerivatives()\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "newton = prob.model.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "scipy = prob.model.linear_solver = om.ScipyKrylov()\n",
    "\n",
    "newton.options['maxiter'] = 1\n",
    "\n",
    "prob.set_val('y1', 10000)\n",
    "prob.set_val('y2', -26)\n",
    "\n",
    "newton.options['iprint'] = 0\n",
    "scipy.options['iprint'] = 0\n",
    "\n",
    "prob.run_model()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "**iprint = 1**: Print a convergence summary, as well as errors and convergence failures."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar_feature import SellarDerivatives\n",
    "\n",
    "prob = om.Problem(model=SellarDerivatives())\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "newton = prob.model.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "scipy = prob.model.linear_solver = om.ScipyKrylov()\n",
    "\n",
    "newton.options['maxiter'] = 20\n",
    "\n",
    "prob.set_val('y1', 10000)\n",
    "prob.set_val('y2', -26)\n",
    "\n",
    "newton.options['iprint'] = 1\n",
    "scipy.options['iprint'] = 0\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "**iprint = 2**: Print the residual for every solver iteration."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.sellar_feature import SellarDerivatives\n",
    "\n",
    "prob = om.Problem(model=SellarDerivatives())\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "newton = prob.model.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "scipy = prob.model.linear_solver = om.ScipyKrylov()\n",
    "\n",
    "newton.options['maxiter'] = 20\n",
    "\n",
    "prob.set_val('y1', 10000)\n",
    "prob.set_val('y2', -26)\n",
    "\n",
    "newton.options['iprint'] = 2\n",
    "scipy.options['iprint'] = 1\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(solver-options-set_solver-print)=\n",
    "## Controlling Solver Output in Large Models\n",
    "\n",
    "When you have a large model with multiple solvers, it is easier to use a shortcut method that\n",
    "recurses over the entire model. The `set_solver_print` method on `problem` can be used to\n",
    "set the iprint to one of four specific values for all solvers in the model while specifically\n",
    "controlling depth (how many systems deep) and the solver type (linear, nonlinear, or both.)\n",
    "\n",
    "To print everything, just call `set_solver_print` with a level of \"2\"."
   ]
  },
  {
   "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_src49\", get_code(\"openmdao.test_suite.components.double_sellar.SubSellar\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{Admonition} `SubSellar` class definition \n",
    ":class: dropdown\n",
    "\n",
    "{glue:}`code_src49`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "output_scroll"
    ]
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from openmdao.test_suite.components.double_sellar import SubSellar\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "sub1 = model.add_subsystem('sub1', om.Group(), promotes_inputs=['z'])\n",
    "sub2 = sub1.add_subsystem('sub2', om.Group(), promotes_inputs=['z'])\n",
    "g1 = sub2.add_subsystem('g1', SubSellar(), promotes_inputs=['z'])\n",
    "g2 = model.add_subsystem('g2', SubSellar())\n",
    "\n",
    "model.connect('sub1.sub2.g1.y2', 'g2.x')\n",
    "model.connect('g2.y2', 'sub1.sub2.g1.x')\n",
    "\n",
    "model.nonlinear_solver = om.NewtonSolver()\n",
    "model.linear_solver = om.ScipyKrylov()\n",
    "model.nonlinear_solver.options['solve_subsystems'] = True\n",
    "model.nonlinear_solver.options['max_sub_solves'] = 0\n",
    "\n",
    "g1.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "g1.linear_solver = om.LinearBlockGS()\n",
    "\n",
    "g2.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "g2.linear_solver = om.ScipyKrylov()\n",
    "g2.linear_solver.precon = om.LinearBlockGS()\n",
    "g2.linear_solver.precon.options['maxiter'] = 2\n",
    "\n",
    "prob.set_solver_print(level=2)\n",
    "\n",
    "prob.setup()\n",
    "prob.set_val('z', np.array([5.0, 2.0]))\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To print everything for nonlinear solvers, and nothing for the linear solvers, first turn everything\n",
    "on, as shown above, and then call `set_solver_print` again to set a level of \"-1\" on just the linear solvers (using the `type_` argument), so that we suppress everything, including the messages when the linear block Gauss-Seidel solver hits the maximum iteration limit. You can call the `set_solver_print` method multiple times to stack different solver print types in your model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from openmdao.test_suite.components.double_sellar import SubSellar\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "sub1 = model.add_subsystem('sub1', om.Group(), promotes_inputs=['z'])\n",
    "sub2 = sub1.add_subsystem('sub2', om.Group(), promotes_inputs=['z'])\n",
    "g1 = sub2.add_subsystem('g1', SubSellar(), promotes_inputs=['z'])\n",
    "g2 = model.add_subsystem('g2', SubSellar())\n",
    "\n",
    "model.connect('sub1.sub2.g1.y2', 'g2.x')\n",
    "model.connect('g2.y2', 'sub1.sub2.g1.x')\n",
    "\n",
    "model.nonlinear_solver = om.NewtonSolver()\n",
    "model.linear_solver = om.ScipyKrylov()\n",
    "model.nonlinear_solver.options['solve_subsystems'] = True\n",
    "model.nonlinear_solver.options['max_sub_solves'] = 0\n",
    "\n",
    "g1.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "g1.linear_solver = om.LinearBlockGS()\n",
    "\n",
    "g2.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "g2.linear_solver = om.ScipyKrylov()\n",
    "g2.linear_solver.precon = om.LinearBlockGS()\n",
    "g2.linear_solver.precon.options['maxiter'] = 2\n",
    "\n",
    "prob.set_solver_print(level=2)\n",
    "prob.set_solver_print(level=-1, type_='LN')\n",
    "\n",
    "prob.setup()\n",
    "prob.set_val('z', np.array([5.0, 2.0]))\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we just want to print solver output for the first level of this multi-level model, we first turn\n",
    "off all printing, and then set a print level of \"2\" with a `depth` argument of \"2\" so that we only print the\n",
    "top solver and the solver in 'g2', but not the solver in 'sub1.sub2.g1'."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "output_scroll"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.test_suite.components.double_sellar import SubSellar\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "sub1 = model.add_subsystem('sub1', om.Group(), promotes_inputs=['z'])\n",
    "sub2 = sub1.add_subsystem('sub2', om.Group(), promotes_inputs=['z'])\n",
    "g1 = sub2.add_subsystem('g1', SubSellar(), promotes_inputs=['z'])\n",
    "g2 = model.add_subsystem('g2', SubSellar())\n",
    "\n",
    "model.connect('sub1.sub2.g1.y2', 'g2.x')\n",
    "model.connect('g2.y2', 'sub1.sub2.g1.x')\n",
    "\n",
    "model.nonlinear_solver = om.NewtonSolver()\n",
    "model.linear_solver = om.ScipyKrylov()\n",
    "model.nonlinear_solver.options['solve_subsystems'] = True\n",
    "model.nonlinear_solver.options['max_sub_solves'] = 0\n",
    "\n",
    "g1.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "g1.linear_solver = om.LinearBlockGS()\n",
    "\n",
    "g2.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "g2.linear_solver = om.ScipyKrylov()\n",
    "g2.linear_solver.precon = om.LinearBlockGS()\n",
    "g2.linear_solver.precon.options['maxiter'] = 2\n",
    "\n",
    "prob.set_solver_print(level=0)\n",
    "prob.set_solver_print(level=2, depth=2)\n",
    "\n",
    "prob.setup()\n",
    "prob.set_val('z', np.array([5.0, 2.0]))\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `set_solver_print` method can also be called on Systems. For instance, if we want to print detailed output from group 'g2' down, we can first call `set_solver_print` on the problem or the top level model with a level of \"-1\", and then call it on group 'g2' with a level of \"2\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from openmdao.test_suite.components.double_sellar import SubSellar\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('pz', om.IndepVarComp('z', np.array([5.0, 2.0])))\n",
    "\n",
    "sub1 = model.add_subsystem('sub1', om.Group())\n",
    "sub2 = sub1.add_subsystem('sub2', om.Group())\n",
    "g1 = sub2.add_subsystem('g1', SubSellar())\n",
    "g2 = model.add_subsystem('g2', SubSellar())\n",
    "\n",
    "model.connect('sub1.sub2.g1.y2', 'g2.x')\n",
    "model.connect('g2.y2', 'sub1.sub2.g1.x')\n",
    "\n",
    "model.nonlinear_solver = om.NewtonSolver()\n",
    "model.linear_solver = om.ScipyKrylov()\n",
    "model.nonlinear_solver.options['solve_subsystems'] = True\n",
    "model.nonlinear_solver.options['max_sub_solves'] = 0\n",
    "\n",
    "g1.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "g1.linear_solver = om.LinearBlockGS()\n",
    "\n",
    "g2.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "g2.linear_solver = om.ScipyKrylov()\n",
    "g2.linear_solver.precon = om.LinearBlockGS()\n",
    "g2.linear_solver.precon.options['maxiter'] = 2\n",
    "\n",
    "prob.set_solver_print(level=-1, type_='all')\n",
    "g2.set_solver_print(level=2, type_='NL')\n",
    "\n",
    "prob.setup()\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Calling `set_solver_print` with the argument `debug_print=True` will turn on debug printing on all nonlinear solvers through which it recurses.\n",
    "To leave the print_level unchanged, once can pass `level=None`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from openmdao.test_suite.components.double_sellar import SubSellar\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('pz', om.IndepVarComp('z', np.array([5.0, 2.0])))\n",
    "\n",
    "sub1 = model.add_subsystem('sub1', om.Group())\n",
    "sub2 = sub1.add_subsystem('sub2', om.Group())\n",
    "g1 = sub2.add_subsystem('g1', SubSellar())\n",
    "g2 = model.add_subsystem('g2', SubSellar())\n",
    "\n",
    "model.connect('sub1.sub2.g1.y2', 'g2.x')\n",
    "model.connect('g2.y2', 'sub1.sub2.g1.x')\n",
    "\n",
    "model.nonlinear_solver = om.NewtonSolver()\n",
    "model.linear_solver = om.ScipyKrylov()\n",
    "model.nonlinear_solver.options['solve_subsystems'] = True\n",
    "model.nonlinear_solver.options['max_sub_solves'] = 0\n",
    "\n",
    "g1.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "g1.linear_solver = om.LinearBlockGS()\n",
    "\n",
    "g2.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "g2.linear_solver = om.ScipyKrylov()\n",
    "g2.linear_solver.precon = om.LinearBlockGS()\n",
    "g2.linear_solver.precon.options['maxiter'] = 2\n",
    "\n",
    "prob.set_solver_print(level=-1, type_='LN', debug_print=True)\n",
    "prob.set_solver_print(level=0, type_='NL', debug_print=True)\n",
    "\n",
    "prob.setup()\n",
    "# Provide an invalid input value to trigger a solver failure.\n",
    "prob.set_val('pz.z', np.array([np.inf, 2.0]))\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "py312",
   "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.12.10"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
