{
 "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": [
    "# BrentSolver\n",
    "\n",
    "BrentSolver is an implementation of Scipy's Brentq, which is based on the Wijngaarden-Dekker-Brent method. It can solve for a single state bracketed between a lower and upper bound using the bisection method, where the value at the two bounding points must have opposite signs. Convergence is guaranteed for generally well-behaved problems on the interval. Derivatives are not required for this solver, though it is limited to solving a single state. Brent can be nested with other solvers, including other Brent solvers.\n",
    "\n",
    "\n",
    "## BrentSolver Options"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input"
    ]
   },
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "om.show_options_table(\"openmdao.solvers.nonlinear.brent.BrentSolver\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "## BrentSolver Constructor\n",
    "\n",
    "The call signature for the `BrentSolver` constructor is:\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.solvers.nonlinear.brent.BrentSolver.__init__\n",
    "        :noindex:\n",
    "```\n",
    "\n",
    "## BrentSolver Example\n",
    "\n",
    "The following simple example shows the use of the Brent solver to find the output of an `ImplicitComponent` that models the equation `x = a*x**n + b*x - c`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "class CompTest(om.ImplicitComponent):\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=10.)\n",
    "        self.add_input('n', val=77.0/27.0)\n",
    "\n",
    "        self.add_output('x', val=2., lower=0, upper=100)\n",
    "\n",
    "    def apply_nonlinear(self, inputs, outputs, residuals, discrete_inputs=None, discrete_outputs=None):\n",
    "        a = inputs['a']\n",
    "        b = inputs['b']\n",
    "        c = inputs['c']\n",
    "        n = inputs['n']\n",
    "        x = outputs['x']\n",
    "\n",
    "        # Can't take fractional power of negative number\n",
    "        if x >= 0.0:\n",
    "            fact = x ** n\n",
    "        else:\n",
    "            fact = - (-x) ** n\n",
    "\n",
    "        residuals['x'] = a * fact + b * x - c\n",
    "\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "model.add_subsystem('comp', CompTest(), promotes=['*'])\n",
    "model.nonlinear_solver = om.BrentSolver(\n",
    "    state_target='x',\n",
    "    lower_bound=0.0,\n",
    "    upper_bound=80.0,\n",
    "    maxiter=100,\n",
    "    atol=1e-8,\n",
    "    rtol=1e-8,\n",
    ")\n",
    "\n",
    "prob.setup()\n",
    "prob.set_solver_print(2)\n",
    "\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('x'))"
   ]
  },
  {
   "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('x')[0], 2.06720359226, 1e-6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The convergence history clearly shows the bisection and bracketing process, where the model is evaluated at closer points to the eventual solution.\n",
    "\n",
    "## BrentSolver Option Example: Upper and Lower Bounds from the Model\n",
    "\n",
    "The BrentSolver also allows the lower and upper bounds to be sourced from the model output, which is useful in cases where the interval of interest can be determined ahead of time by an upstream calculation. Note that the bounds are only queried at the start of the Brent iteration."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('lower', om.ExecComp('low = 2*a'), promotes=['*'])\n",
    "model.add_subsystem('upper', om.ExecComp('high = 2*b'), promotes=['*'])\n",
    "\n",
    "model.add_subsystem('comp', CompTest(), promotes=['*'])\n",
    "model.nonlinear_solver = om.BrentSolver(\n",
    "    state_target='x',\n",
    "    maxiter=100,\n",
    "    atol=1e-8,\n",
    "    rtol=1e-8,\n",
    "    lower_bound_target='low',\n",
    "    upper_bound_target='high',\n",
    ")\n",
    "\n",
    "prob.setup()\n",
    "prob.set_solver_print(2)\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "prob.set_val('a', -5.0)\n",
    "prob.set_val('b', 55.0)\n",
    "\n",
    "prob.run_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(prob.get_val('x'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "assert_near_equal(prob.get_val('x')[0], -3.7451537261581453, 1e-6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.3"
  },
  "orphan": true
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
