{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "ee6769f6",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:42:19.477258Z",
     "iopub.status.busy": "2026-10-02T14:42:19.476906Z",
     "iopub.status.idle": "2026-10-02T14:42:19.481774Z",
     "shell.execute_reply": "2026-10-02T14:42:19.481113Z"
    },
    "papermill": {
     "duration": 0.008964,
     "end_time": "2026-10-02T14:42:19.482448+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:19.473484+00:00",
     "status": "completed"
    },
    "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",
   "id": "568835aa",
   "metadata": {
    "papermill": {
     "duration": 0.04314,
     "end_time": "2026-10-02T14:42:19.527134+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:19.483994+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "# BoundsEnforceLS\n",
    "\n",
    "The BoundsEnforceLS only backtracks until variables violate their upper and lower bounds.\n",
    "\n",
    "Here is a simple example where BoundsEnforceLS is used to backtrack during the Newton solver's iteration on\n",
    "a system that contains an implicit component with 3 states that are confined to a small range of values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "a13c2cbd",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:42:19.530524Z",
     "iopub.status.busy": "2026-10-02T14:42:19.530325Z",
     "iopub.status.idle": "2026-10-02T14:42:22.165588Z",
     "shell.execute_reply": "2026-10-02T14:42:22.164927Z"
    },
    "papermill": {
     "duration": 2.637783,
     "end_time": "2026-10-02T14:42:22.166167+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:19.528384+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [
    {
     "data": {
      "application/papermill.record/text/html": "<style>pre { line-height: 125%; }\ntd.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\nspan.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\ntd.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\nspan.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\n.output_html .hll { background-color: #ffffcc }\n.output_html { background: #f8f8f8; }\n.output_html .c { color: #3D7B7B; font-style: italic } /* Comment */\n.output_html .err { border: 1px solid #F00 } /* Error */\n.output_html .k { color: #008000; font-weight: bold } /* Keyword */\n.output_html .o { color: #666 } /* Operator */\n.output_html .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */\n.output_html .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */\n.output_html .cp { color: #9C6500 } /* Comment.Preproc */\n.output_html .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */\n.output_html .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */\n.output_html .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */\n.output_html .gd { color: #A00000 } /* Generic.Deleted */\n.output_html .ge { font-style: italic } /* Generic.Emph */\n.output_html .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */\n.output_html .gr { color: #E40000 } /* Generic.Error */\n.output_html .gh { color: #000080; font-weight: bold } /* Generic.Heading */\n.output_html .gi { color: #008400 } /* Generic.Inserted */\n.output_html .go { color: #717171 } /* Generic.Output */\n.output_html .gp { color: #000080; font-weight: bold } /* Generic.Prompt */\n.output_html .gs { font-weight: bold } /* Generic.Strong */\n.output_html .gu { color: #800080; font-weight: bold } /* Generic.Subheading */\n.output_html .gt { color: #04D } /* Generic.Traceback */\n.output_html .kc { color: #008000; font-weight: bold } /* Keyword.Constant */\n.output_html .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */\n.output_html .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */\n.output_html .kp { color: #008000 } /* Keyword.Pseudo */\n.output_html .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */\n.output_html .kt { color: #B00040 } /* Keyword.Type */\n.output_html .m { color: #666 } /* Literal.Number */\n.output_html .s { color: #BA2121 } /* Literal.String */\n.output_html .na { color: #687822 } /* Name.Attribute */\n.output_html .nb { color: #008000 } /* Name.Builtin */\n.output_html .nc { color: #00F; font-weight: bold } /* Name.Class */\n.output_html .no { color: #800 } /* Name.Constant */\n.output_html .nd { color: #A2F } /* Name.Decorator */\n.output_html .ni { color: #717171; font-weight: bold } /* Name.Entity */\n.output_html .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */\n.output_html .nf { color: #00F } /* Name.Function */\n.output_html .nl { color: #767600 } /* Name.Label */\n.output_html .nn { color: #00F; font-weight: bold } /* Name.Namespace */\n.output_html .nt { color: #008000; font-weight: bold } /* Name.Tag */\n.output_html .nv { color: #19177C } /* Name.Variable */\n.output_html .ow { color: #A2F; font-weight: bold } /* Operator.Word */\n.output_html .w { color: #BBB } /* Text.Whitespace */\n.output_html .mb { color: #666 } /* Literal.Number.Bin */\n.output_html .mf { color: #666 } /* Literal.Number.Float */\n.output_html .mh { color: #666 } /* Literal.Number.Hex */\n.output_html .mi { color: #666 } /* Literal.Number.Integer */\n.output_html .mo { color: #666 } /* Literal.Number.Oct */\n.output_html .sa { color: #BA2121 } /* Literal.String.Affix */\n.output_html .sb { color: #BA2121 } /* Literal.String.Backtick */\n.output_html .sc { color: #BA2121 } /* Literal.String.Char */\n.output_html .dl { color: #BA2121 } /* Literal.String.Delimiter */\n.output_html .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */\n.output_html .s2 { color: #BA2121 } /* Literal.String.Double */\n.output_html .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */\n.output_html .sh { color: #BA2121 } /* Literal.String.Heredoc */\n.output_html .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */\n.output_html .sx { color: #008000 } /* Literal.String.Other */\n.output_html .sr { color: #A45A77 } /* Literal.String.Regex */\n.output_html .s1 { color: #BA2121 } /* Literal.String.Single */\n.output_html .ss { color: #19177C } /* Literal.String.Symbol */\n.output_html .bp { color: #008000 } /* Name.Builtin.Pseudo */\n.output_html .fm { color: #00F } /* Name.Function.Magic */\n.output_html .vc { color: #19177C } /* Name.Variable.Class */\n.output_html .vg { color: #19177C } /* Name.Variable.Global */\n.output_html .vi { color: #19177C } /* Name.Variable.Instance */\n.output_html .vm { color: #19177C } /* Name.Variable.Magic */\n.output_html .il { color: #666 } /* Literal.Number.Integer.Long */</style><div class=\"highlight\"><pre><span></span><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">ImplCompTwoStatesArrays</span><span class=\"p\">(</span><span class=\"n\">om</span><span class=\"o\">.</span><span class=\"n\">ImplicitComponent</span><span class=\"p\">):</span>\n<span class=\"w\">    </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">    A Simple Implicit Component with an additional output equation.</span>\n\n<span class=\"sd\">    f(x,z) = xz + z - 4</span>\n<span class=\"sd\">    y = x + 2z</span>\n\n<span class=\"sd\">    Sol : when x = 0.5, z = 2.666</span>\n<span class=\"sd\">    Sol : when x = 2.0, z = 1.333</span>\n\n<span class=\"sd\">    Coupled derivs:</span>\n\n<span class=\"sd\">    y = x + 8/(x+1)</span>\n<span class=\"sd\">    dy_dx = 1 - 8/(x+1)**2 = -2.5555555555555554</span>\n\n<span class=\"sd\">    z = 4/(x+1)</span>\n<span class=\"sd\">    dz_dx = -4/(x+1)**2 = -1.7777777777777777</span>\n<span class=\"sd\">    &quot;&quot;&quot;</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">setup</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">add_input</span><span class=\"p\">(</span><span class=\"s1\">&#39;x&#39;</span><span class=\"p\">,</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)))</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">add_output</span><span class=\"p\">(</span><span class=\"s1\">&#39;y&#39;</span><span class=\"p\">,</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)))</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">add_output</span><span class=\"p\">(</span><span class=\"s1\">&#39;z&#39;</span><span class=\"p\">,</span> <span class=\"mf\">2.0</span><span class=\"o\">*</span><span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">ones</span><span class=\"p\">((</span><span class=\"mi\">3</span><span class=\"p\">,</span> <span class=\"mi\">1</span><span class=\"p\">)),</span> <span class=\"n\">lower</span><span class=\"o\">=</span><span class=\"mf\">1.5</span><span class=\"p\">,</span>\n            <span class=\"n\">upper</span><span class=\"o\">=</span><span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">([</span><span class=\"mf\">2.6</span><span class=\"p\">,</span> <span class=\"mf\">2.5</span><span class=\"p\">,</span> <span class=\"mf\">2.65</span><span class=\"p\">])</span><span class=\"o\">.</span><span class=\"n\">reshape</span><span class=\"p\">((</span><span class=\"mi\">3</span><span class=\"p\">,</span><span class=\"mi\">1</span><span class=\"p\">)))</span>\n\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">maxiter</span> <span class=\"o\">=</span> <span class=\"mi\">10</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">atol</span> <span class=\"o\">=</span> <span class=\"mf\">1.0e-12</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">setup_partials</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">declare_partials</span><span class=\"p\">(</span><span class=\"n\">of</span><span class=\"o\">=</span><span class=\"s1\">&#39;*&#39;</span><span class=\"p\">,</span> <span class=\"n\">wrt</span><span class=\"o\">=</span><span class=\"s1\">&#39;*&#39;</span><span class=\"p\">)</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">apply_nonlinear</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">residuals</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">        Don&#39;t solve; just calculate the residual.</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n\n        <span class=\"n\">x</span> <span class=\"o\">=</span> <span class=\"n\">inputs</span><span class=\"p\">[</span><span class=\"s1\">&#39;x&#39;</span><span class=\"p\">]</span>\n        <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"s1\">&#39;y&#39;</span><span class=\"p\">]</span>\n        <span class=\"n\">z</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"s1\">&#39;z&#39;</span><span class=\"p\">]</span>\n\n        <span class=\"n\">residuals</span><span class=\"p\">[</span><span class=\"s1\">&#39;y&#39;</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">y</span> <span class=\"o\">-</span> <span class=\"n\">x</span> <span class=\"o\">-</span> <span class=\"mf\">2.0</span><span class=\"o\">*</span><span class=\"n\">z</span>\n        <span class=\"n\">residuals</span><span class=\"p\">[</span><span class=\"s1\">&#39;z&#39;</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">x</span><span class=\"o\">*</span><span class=\"n\">z</span> <span class=\"o\">+</span> <span class=\"n\">z</span> <span class=\"o\">-</span> <span class=\"mf\">4.0</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">linearize</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">inputs</span><span class=\"p\">,</span> <span class=\"n\">outputs</span><span class=\"p\">,</span> <span class=\"n\">jac</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">        Analytical derivatives.</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n\n        <span class=\"c1\"># Output equation</span>\n        <span class=\"n\">jac</span><span class=\"p\">[(</span><span class=\"s1\">&#39;y&#39;</span><span class=\"p\">,</span> <span class=\"s1\">&#39;x&#39;</span><span class=\"p\">)]</span> <span class=\"o\">=</span> <span class=\"o\">-</span><span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">diag</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">([</span><span class=\"mf\">1.0</span><span class=\"p\">,</span> <span class=\"mf\">1.0</span><span class=\"p\">,</span> <span class=\"mf\">1.0</span><span class=\"p\">]))</span>\n        <span class=\"n\">jac</span><span class=\"p\">[(</span><span class=\"s1\">&#39;y&#39;</span><span class=\"p\">,</span> <span class=\"s1\">&#39;y&#39;</span><span class=\"p\">)]</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">diag</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">([</span><span class=\"mf\">1.0</span><span class=\"p\">,</span> <span class=\"mf\">1.0</span><span class=\"p\">,</span> <span class=\"mf\">1.0</span><span class=\"p\">]))</span>\n        <span class=\"n\">jac</span><span class=\"p\">[(</span><span class=\"s1\">&#39;y&#39;</span><span class=\"p\">,</span> <span class=\"s1\">&#39;z&#39;</span><span class=\"p\">)]</span> <span class=\"o\">=</span> <span class=\"o\">-</span><span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">diag</span><span class=\"p\">(</span><span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">([</span><span class=\"mf\">2.0</span><span class=\"p\">,</span> <span class=\"mf\">2.0</span><span class=\"p\">,</span> <span class=\"mf\">2.0</span><span class=\"p\">]))</span>\n\n        <span class=\"c1\"># State equation</span>\n        <span class=\"n\">jac</span><span class=\"p\">[(</span><span class=\"s1\">&#39;z&#39;</span><span class=\"p\">,</span> <span class=\"s1\">&#39;z&#39;</span><span class=\"p\">)]</span> <span class=\"o\">=</span> <span class=\"p\">(</span><span class=\"n\">inputs</span><span class=\"p\">[</span><span class=\"s1\">&#39;x&#39;</span><span class=\"p\">]</span> <span class=\"o\">+</span> <span class=\"mf\">1.0</span><span class=\"p\">)</span> <span class=\"o\">*</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">eye</span><span class=\"p\">(</span><span class=\"mi\">3</span><span class=\"p\">)</span>\n        <span class=\"n\">jac</span><span class=\"p\">[(</span><span class=\"s1\">&#39;z&#39;</span><span class=\"p\">,</span> <span class=\"s1\">&#39;x&#39;</span><span class=\"p\">)]</span> <span class=\"o\">=</span> <span class=\"n\">outputs</span><span class=\"p\">[</span><span class=\"s1\">&#39;z&#39;</span><span class=\"p\">]</span> <span class=\"o\">*</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">eye</span><span class=\"p\">(</span><span class=\"mi\">3</span><span class=\"p\">)</span>\n</pre></div>\n",
      "application/papermill.record/text/latex": "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n\\PY{k}{class}\\PY{+w}{ }\\PY{n+nc}{ImplCompTwoStatesArrays}\\PY{p}{(}\\PY{n}{om}\\PY{o}{.}\\PY{n}{ImplicitComponent}\\PY{p}{)}\\PY{p}{:}\n\\PY{+w}{    }\\PY{l+s+sd}{\\PYZdq{}\\PYZdq{}\\PYZdq{}}\n\\PY{l+s+sd}{    A Simple Implicit Component with an additional output equation.}\n\n\\PY{l+s+sd}{    f(x,z) = xz + z \\PYZhy{} 4}\n\\PY{l+s+sd}{    y = x + 2z}\n\n\\PY{l+s+sd}{    Sol : when x = 0.5, z = 2.666}\n\\PY{l+s+sd}{    Sol : when x = 2.0, z = 1.333}\n\n\\PY{l+s+sd}{    Coupled derivs:}\n\n\\PY{l+s+sd}{    y = x + 8/(x+1)}\n\\PY{l+s+sd}{    dy\\PYZus{}dx = 1 \\PYZhy{} 8/(x+1)**2 = \\PYZhy{}2.5555555555555554}\n\n\\PY{l+s+sd}{    z = 4/(x+1)}\n\\PY{l+s+sd}{    dz\\PYZus{}dx = \\PYZhy{}4/(x+1)**2 = \\PYZhy{}1.7777777777777777}\n\\PY{l+s+sd}{    \\PYZdq{}\\PYZdq{}\\PYZdq{}}\n\n    \\PY{k}{def}\\PY{+w}{ }\\PY{n+nf}{setup}\\PY{p}{(}\\PY{n+nb+bp}{self}\\PY{p}{)}\\PY{p}{:}\n        \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{add\\PYZus{}input}\\PY{p}{(}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{x}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{zeros}\\PY{p}{(}\\PY{p}{(}\\PY{l+m+mi}{3}\\PY{p}{,} \\PY{l+m+mi}{1}\\PY{p}{)}\\PY{p}{)}\\PY{p}{)}\n        \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{add\\PYZus{}output}\\PY{p}{(}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{y}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{,} \\PY{n}{np}\\PY{o}{.}\\PY{n}{zeros}\\PY{p}{(}\\PY{p}{(}\\PY{l+m+mi}{3}\\PY{p}{,} \\PY{l+m+mi}{1}\\PY{p}{)}\\PY{p}{)}\\PY{p}{)}\n        \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{add\\PYZus{}output}\\PY{p}{(}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{z}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{,} \\PY{l+m+mf}{2.0}\\PY{o}{*}\\PY{n}{np}\\PY{o}{.}\\PY{n}{ones}\\PY{p}{(}\\PY{p}{(}\\PY{l+m+mi}{3}\\PY{p}{,} \\PY{l+m+mi}{1}\\PY{p}{)}\\PY{p}{)}\\PY{p}{,} \\PY{n}{lower}\\PY{o}{=}\\PY{l+m+mf}{1.5}\\PY{p}{,}\n            \\PY{n}{upper}\\PY{o}{=}\\PY{n}{np}\\PY{o}{.}\\PY{n}{array}\\PY{p}{(}\\PY{p}{[}\\PY{l+m+mf}{2.6}\\PY{p}{,} \\PY{l+m+mf}{2.5}\\PY{p}{,} \\PY{l+m+mf}{2.65}\\PY{p}{]}\\PY{p}{)}\\PY{o}{.}\\PY{n}{reshape}\\PY{p}{(}\\PY{p}{(}\\PY{l+m+mi}{3}\\PY{p}{,}\\PY{l+m+mi}{1}\\PY{p}{)}\\PY{p}{)}\\PY{p}{)}\n\n        \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{maxiter} \\PY{o}{=} \\PY{l+m+mi}{10}\n        \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{atol} \\PY{o}{=} \\PY{l+m+mf}{1.0e\\PYZhy{}12}\n\n    \\PY{k}{def}\\PY{+w}{ }\\PY{n+nf}{setup\\PYZus{}partials}\\PY{p}{(}\\PY{n+nb+bp}{self}\\PY{p}{)}\\PY{p}{:}\n        \\PY{n+nb+bp}{self}\\PY{o}{.}\\PY{n}{declare\\PYZus{}partials}\\PY{p}{(}\\PY{n}{of}\\PY{o}{=}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{*}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{,} \\PY{n}{wrt}\\PY{o}{=}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{*}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{)}\n\n    \\PY{k}{def}\\PY{+w}{ }\\PY{n+nf}{apply\\PYZus{}nonlinear}\\PY{p}{(}\\PY{n+nb+bp}{self}\\PY{p}{,} \\PY{n}{inputs}\\PY{p}{,} \\PY{n}{outputs}\\PY{p}{,} \\PY{n}{residuals}\\PY{p}{)}\\PY{p}{:}\n\\PY{+w}{        }\\PY{l+s+sd}{\\PYZdq{}\\PYZdq{}\\PYZdq{}}\n\\PY{l+s+sd}{        Don\\PYZsq{}t solve; just calculate the residual.}\n\\PY{l+s+sd}{        \\PYZdq{}\\PYZdq{}\\PYZdq{}}\n\n        \\PY{n}{x} \\PY{o}{=} \\PY{n}{inputs}\\PY{p}{[}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{x}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{]}\n        \\PY{n}{y} \\PY{o}{=} \\PY{n}{outputs}\\PY{p}{[}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{y}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{]}\n        \\PY{n}{z} \\PY{o}{=} \\PY{n}{outputs}\\PY{p}{[}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{z}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{]}\n\n        \\PY{n}{residuals}\\PY{p}{[}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{y}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{]} \\PY{o}{=} \\PY{n}{y} \\PY{o}{\\PYZhy{}} \\PY{n}{x} \\PY{o}{\\PYZhy{}} \\PY{l+m+mf}{2.0}\\PY{o}{*}\\PY{n}{z}\n        \\PY{n}{residuals}\\PY{p}{[}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{z}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{]} \\PY{o}{=} \\PY{n}{x}\\PY{o}{*}\\PY{n}{z} \\PY{o}{+} \\PY{n}{z} \\PY{o}{\\PYZhy{}} \\PY{l+m+mf}{4.0}\n\n    \\PY{k}{def}\\PY{+w}{ }\\PY{n+nf}{linearize}\\PY{p}{(}\\PY{n+nb+bp}{self}\\PY{p}{,} \\PY{n}{inputs}\\PY{p}{,} \\PY{n}{outputs}\\PY{p}{,} \\PY{n}{jac}\\PY{p}{)}\\PY{p}{:}\n\\PY{+w}{        }\\PY{l+s+sd}{\\PYZdq{}\\PYZdq{}\\PYZdq{}}\n\\PY{l+s+sd}{        Analytical derivatives.}\n\\PY{l+s+sd}{        \\PYZdq{}\\PYZdq{}\\PYZdq{}}\n\n        \\PY{c+c1}{\\PYZsh{} Output equation}\n        \\PY{n}{jac}\\PY{p}{[}\\PY{p}{(}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{y}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{,} \\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{x}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{)}\\PY{p}{]} \\PY{o}{=} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{diag}\\PY{p}{(}\\PY{n}{np}\\PY{o}{.}\\PY{n}{array}\\PY{p}{(}\\PY{p}{[}\\PY{l+m+mf}{1.0}\\PY{p}{,} \\PY{l+m+mf}{1.0}\\PY{p}{,} \\PY{l+m+mf}{1.0}\\PY{p}{]}\\PY{p}{)}\\PY{p}{)}\n        \\PY{n}{jac}\\PY{p}{[}\\PY{p}{(}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{y}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{,} \\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{y}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{)}\\PY{p}{]} \\PY{o}{=} \\PY{n}{np}\\PY{o}{.}\\PY{n}{diag}\\PY{p}{(}\\PY{n}{np}\\PY{o}{.}\\PY{n}{array}\\PY{p}{(}\\PY{p}{[}\\PY{l+m+mf}{1.0}\\PY{p}{,} \\PY{l+m+mf}{1.0}\\PY{p}{,} \\PY{l+m+mf}{1.0}\\PY{p}{]}\\PY{p}{)}\\PY{p}{)}\n        \\PY{n}{jac}\\PY{p}{[}\\PY{p}{(}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{y}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{,} \\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{z}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{)}\\PY{p}{]} \\PY{o}{=} \\PY{o}{\\PYZhy{}}\\PY{n}{np}\\PY{o}{.}\\PY{n}{diag}\\PY{p}{(}\\PY{n}{np}\\PY{o}{.}\\PY{n}{array}\\PY{p}{(}\\PY{p}{[}\\PY{l+m+mf}{2.0}\\PY{p}{,} \\PY{l+m+mf}{2.0}\\PY{p}{,} \\PY{l+m+mf}{2.0}\\PY{p}{]}\\PY{p}{)}\\PY{p}{)}\n\n        \\PY{c+c1}{\\PYZsh{} State equation}\n        \\PY{n}{jac}\\PY{p}{[}\\PY{p}{(}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{z}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{,} \\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{z}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{)}\\PY{p}{]} \\PY{o}{=} \\PY{p}{(}\\PY{n}{inputs}\\PY{p}{[}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{x}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{]} \\PY{o}{+} \\PY{l+m+mf}{1.0}\\PY{p}{)} \\PY{o}{*} \\PY{n}{np}\\PY{o}{.}\\PY{n}{eye}\\PY{p}{(}\\PY{l+m+mi}{3}\\PY{p}{)}\n        \\PY{n}{jac}\\PY{p}{[}\\PY{p}{(}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{z}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{,} \\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{x}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{)}\\PY{p}{]} \\PY{o}{=} \\PY{n}{outputs}\\PY{p}{[}\\PY{l+s+s1}{\\PYZsq{}}\\PY{l+s+s1}{z}\\PY{l+s+s1}{\\PYZsq{}}\\PY{p}{]} \\PY{o}{*} \\PY{n}{np}\\PY{o}{.}\\PY{n}{eye}\\PY{p}{(}\\PY{l+m+mi}{3}\\PY{p}{)}\n\\end{Verbatim}\n",
      "application/papermill.record/text/plain": "class ImplCompTwoStatesArrays(om.ImplicitComponent):\n    \"\"\"\n    A Simple Implicit Component with an additional output equation.\n\n    f(x,z) = xz + z - 4\n    y = x + 2z\n\n    Sol : when x = 0.5, z = 2.666\n    Sol : when x = 2.0, z = 1.333\n\n    Coupled derivs:\n\n    y = x + 8/(x+1)\n    dy_dx = 1 - 8/(x+1)**2 = -2.5555555555555554\n\n    z = 4/(x+1)\n    dz_dx = -4/(x+1)**2 = -1.7777777777777777\n    \"\"\"\n\n    def setup(self):\n        self.add_input('x', np.zeros((3, 1)))\n        self.add_output('y', np.zeros((3, 1)))\n        self.add_output('z', 2.0*np.ones((3, 1)), lower=1.5,\n            upper=np.array([2.6, 2.5, 2.65]).reshape((3,1)))\n\n        self.maxiter = 10\n        self.atol = 1.0e-12\n\n    def setup_partials(self):\n        self.declare_partials(of='*', wrt='*')\n\n    def apply_nonlinear(self, inputs, outputs, residuals):\n        \"\"\"\n        Don't solve; just calculate the residual.\n        \"\"\"\n\n        x = inputs['x']\n        y = outputs['y']\n        z = outputs['z']\n\n        residuals['y'] = y - x - 2.0*z\n        residuals['z'] = x*z + z - 4.0\n\n    def linearize(self, inputs, outputs, jac):\n        \"\"\"\n        Analytical derivatives.\n        \"\"\"\n\n        # Output equation\n        jac[('y', 'x')] = -np.diag(np.array([1.0, 1.0, 1.0]))\n        jac[('y', 'y')] = np.diag(np.array([1.0, 1.0, 1.0]))\n        jac[('y', 'z')] = -np.diag(np.array([2.0, 2.0, 2.0]))\n\n        # State equation\n        jac[('z', 'z')] = (inputs['x'] + 1.0) * np.eye(3)\n        jac[('z', 'x')] = outputs['z'] * np.eye(3)"
     },
     "metadata": {
      "scrapbook": {
       "mime_prefix": "application/papermill.record/",
       "name": "code_src20"
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "from openmdao.utils.notebook_utils import get_code\n",
    "from myst_nb import glue\n",
    "glue(\"code_src20\", get_code(\"openmdao.test_suite.components.implicit_newton_linesearch.ImplCompTwoStatesArrays\"), display=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "05453939",
   "metadata": {
    "papermill": {
     "duration": 0.001105,
     "end_time": "2026-10-02T14:42:22.168650+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:22.167545+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    ":::{dropdown} `ImplCompTwoStatesArrays` class definition \n",
    "\n",
    "{glue:}`code_src20`\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "aad91883",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:42:22.171864Z",
     "iopub.status.busy": "2026-10-02T14:42:22.171661Z",
     "iopub.status.idle": "2026-10-02T14:42:23.390934Z",
     "shell.execute_reply": "2026-10-02T14:42:23.390044Z"
    },
    "papermill": {
     "duration": 1.222264,
     "end_time": "2026-10-02T14:42:23.392062+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:22.169798+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1790952143.374906] [runnervm8df0l:7077 :0]        ib_iface.c:1269 UCX  ERROR mana_0: iface 0x55862d26b600 failed to create UD QP TX wr:256 sge:6 inl:64 resp:0 RX wr:4096 sge:1 resp:0 failed: Operation not supported\n",
      "[1790952143.375183] [runnervm8df0l:7077 :0]      ucp_worker.c:1412 UCX  ERROR uct_iface_open(ud_verbs/mana_0:1) failed: Input/output error\n",
      "NL: NewtonSolver 'NL: Newton' on system '' failed to converge in 10 iterations.\n",
      "[1.5]\n",
      "[1.5]\n",
      "[1.5]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[runnervm8df0l:07077] pml_ucx.c:313  Error: Failed to create UCP worker\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "import openmdao.api as om\n",
    "from openmdao.test_suite.components.implicit_newton_linesearch import ImplCompTwoStatesArrays\n",
    "\n",
    "top = om.Problem()\n",
    "top.model.add_subsystem('comp', ImplCompTwoStatesArrays(), promotes_inputs=['x'])\n",
    "\n",
    "top.model.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "top.model.nonlinear_solver.options['maxiter'] = 10\n",
    "top.model.linear_solver = om.ScipyKrylov()\n",
    "\n",
    "top.model.nonlinear_solver.linesearch = om.BoundsEnforceLS()\n",
    "\n",
    "top.setup()\n",
    "top.set_val('x', np.array([2., 2, 2]).reshape(3, 1))\n",
    "\n",
    "# Test lower bounds: should go to the lower bound and stall\n",
    "top.set_val('comp.y', 0.)\n",
    "top.set_val('comp.z', 1.6)\n",
    "top.run_model()\n",
    "\n",
    "for ind in range(3):\n",
    "    print(top.get_val('comp.z', indices=ind))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "51065d3c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:42:23.397341Z",
     "iopub.status.busy": "2026-10-02T14:42:23.397120Z",
     "iopub.status.idle": "2026-10-02T14:42:23.400888Z",
     "shell.execute_reply": "2026-10-02T14:42:23.400344Z"
    },
    "papermill": {
     "duration": 0.007162,
     "end_time": "2026-10-02T14:42:23.401652+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:23.394490+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "\n",
    "for ind in range(3):\n",
    "    assert_near_equal(top.get_val('comp.z', indices=ind), [1.5], 1e-8)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "804140de",
   "metadata": {
    "papermill": {
     "duration": 0.001548,
     "end_time": "2026-10-02T14:42:23.404809+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:23.403261+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "## BoundsEnforceLS Options"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "3df92ffe",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:42:23.412038Z",
     "iopub.status.busy": "2026-10-02T14:42:23.411822Z",
     "iopub.status.idle": "2026-10-02T14:42:23.416346Z",
     "shell.execute_reply": "2026-10-02T14:42:23.415727Z"
    },
    "papermill": {
     "duration": 0.010368,
     "end_time": "2026-10-02T14:42:23.416787+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:23.406419+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input"
    ]
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<!DOCTYPE html>\n",
       "<html lang=\"en\">\n",
       "<head>\n",
       "    <style>\n",
       "        h2 {\n",
       "            text-align: center;\n",
       "        }\n",
       "    </style>\n",
       "</head>\n",
       "<body>\n",
       "    <h2></h2>\n",
       "        <table style=\"border: 1px solid #999; border-collapse: collapse;\">\n",
       "        <tr><th style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; background-color: #E9E9E9; text-align: left;\">Option</th><th style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; background-color: #E9E9E9; text-align: left;\">Default</th><th style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; background-color: #E9E9E9; text-align: left;\">Acceptable Values</th><th style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; background-color: #E9E9E9; text-align: left;\">Acceptable Types</th><th style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; background-color: #E9E9E9; text-align: left;\">Description</th></tr>\n",
       "       <tr style=\"background-color: ghostwhite;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">bound_enforcement</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">scalar</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[&#x27;vector&#x27;, &#x27;scalar&#x27;, &#x27;wall&#x27;]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">If this is set to &#x27;vector&#x27;, the entire vector is backtracked together when a bound is violated. If this is set to &#x27;scalar&#x27;, only the violating entries are set to the bound and then the backtracking occurs on the vector as a whole. If this is set to &#x27;wall&#x27;, only the violating entries are set to the bound, and then the backtracking follows the wall - i.e., the violating entries do not change during the line search.</td></tr>\n",
       "       <tr style=\"background-color: #F3F3F3;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">debug_print</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">False</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[True, False]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[&#x27;bool&#x27;]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">If True, the values of input and output variables at the start of iteration are printed and written to a file after a failure to converge or when encountering aninvalid value in the residual.</td></tr>\n",
       "       <tr style=\"background-color: ghostwhite;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">iprint</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">1</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[&#x27;int&#x27;]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">whether to print output</td></tr>\n",
       "       <tr style=\"background-color: #F3F3F3;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">print_bound_enforce</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">False</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">Set to True to print out names and values of variables that are pulled back to their bounds.</td></tr>\n",
       "       <tr style=\"background-color: ghostwhite;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">stall_limit</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">0</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">Number of iterations after which, if the residual norms are identical within the stall_tol, then terminate as if max iterations were reached. Default is 0, which disables this feature.</td></tr>\n",
       "       <tr style=\"background-color: #F3F3F3;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">stall_tol</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">1e-12</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">When stall checking is enabled, the threshold below which the residual norm is considered unchanged.</td></tr>\n",
       "       <tr style=\"background-color: ghostwhite;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">stall_tol_type</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">rel</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[&#x27;abs&#x27;, &#x27;rel&#x27;]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">Specifies whether the absolute or relative norm of the residual is used for stall detection.</td></tr>\n",
       "    </table>\n",
       "</body>\n",
       "</html>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "om.show_options_table(\"openmdao.solvers.linesearch.backtracking.BoundsEnforceLS\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fe6625a7",
   "metadata": {
    "papermill": {
     "duration": 0.001757,
     "end_time": "2026-10-02T14:42:23.420270+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:23.418513+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "## BoundsEnforceLS Constructor\n",
    "\n",
    "The call signature for the `BoundsEnforceLS` constructor is:\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.solvers.linesearch.backtracking.BoundsEnforceLS.__init__\n",
    "        :noindex:\n",
    "```\n",
    "\n",
    "## BoundsEnforceLS Option Examples\n",
    "\n",
    "**bound_enforcement**\n",
    "\n",
    "BoundsEnforceLS includes the `bound_enforcement` option in its options dictionary. This option has a dual role:\n",
    "\n",
    "1. Behavior of the non-bounded variables when the bounded ones are capped.\n",
    "2. Direction of the further backtracking.\n",
    "\n",
    "There are three difference bounds enforcement schemes available in this option.\n",
    "\n",
    "With \"scalar\" bounds enforcement, only the variables that violate their bounds are pulled back to feasible values; the\n",
    "remaining values are kept at the Newton-stepped point. This changes the direction of the backtracking vector so that\n",
    "it still moves in the direction of the initial point. This is the default bounds enforcement for `BoundsEnforceLS`.\n",
    "\n",
    "![BT2](images/BT2.jpg)\n",
    "\n",
    "With \"vector\" bounds enforcement, the solution in the output vector is pulled back in unison to a point where none of the\n",
    "variables violate any upper or lower bounds. Further backtracking continues along the Newton gradient direction vector back towards the\n",
    "initial point.\n",
    "\n",
    "![BT1](images/BT1.jpg)\n",
    "\n",
    "With \"wall\" bounds enforcement, only the variables that violate their bounds are pulled back to feasible values; the remaining values are kept at the Newton-stepped point. Further backtracking only occurs in the direction of the non-violating variables, so that it will move along the wall.\n",
    "\n",
    "```{Note}\n",
    "When using BoundsEnforceLS linesearch, the `scalar` and `wall` methods are exactly the same because no further\n",
    "backtracking is performed.\n",
    "```\n",
    "\n",
    "![BT3](images/BT3.jpg)\n",
    "\n",
    "Here are a few examples of this option:\n",
    "\n",
    "- bound_enforcement: vector\n",
    "\n",
    "  The `bound_enforcement` option in the options dictionary is used to specify how the output bounds\n",
    "  are enforced. When this is set to \"vector\", the output vector is rolled back along the computed gradient until\n",
    "  it reaches a point where the earliest bound violation occurred. The backtracking continues along the original\n",
    "  computed gradient."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "b814ff07",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:42:23.424873Z",
     "iopub.status.busy": "2026-10-02T14:42:23.424686Z",
     "iopub.status.idle": "2026-10-02T14:42:23.438303Z",
     "shell.execute_reply": "2026-10-02T14:42:23.437618Z"
    },
    "papermill": {
     "duration": 0.016846,
     "end_time": "2026-10-02T14:42:23.438976+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:23.422130+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NL: NewtonSolver 'NL: Newton' on system '' failed to converge in 10 iterations.\n",
      "[1.5]\n",
      "[1.5]\n",
      "[1.5]\n"
     ]
    }
   ],
   "source": [
    "from openmdao.test_suite.components.implicit_newton_linesearch import ImplCompTwoStatesArrays\n",
    "\n",
    "top = om.Problem()\n",
    "top.model.add_subsystem('comp', ImplCompTwoStatesArrays(), promotes_inputs=['x'])\n",
    "\n",
    "top.model.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "top.model.nonlinear_solver.options['maxiter'] = 10\n",
    "top.model.linear_solver = om.ScipyKrylov()\n",
    "\n",
    "top.model.nonlinear_solver.linesearch = om.BoundsEnforceLS(bound_enforcement='vector')\n",
    "\n",
    "top.setup()\n",
    "top.set_val('x', np.array([2., 2, 2]).reshape(3, 1))\n",
    "\n",
    "# Test lower bounds: should go to the lower bound and stall\n",
    "top.set_val('comp.y', 0.)\n",
    "top.set_val('comp.z', 1.6)\n",
    "top.run_model()\n",
    "\n",
    "for ind in range(3):\n",
    "    print(top.get_val('comp.z', indices=ind))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "7d70f5db",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:42:23.443258Z",
     "iopub.status.busy": "2026-10-02T14:42:23.443091Z",
     "iopub.status.idle": "2026-10-02T14:42:23.446283Z",
     "shell.execute_reply": "2026-10-02T14:42:23.445664Z"
    },
    "papermill": {
     "duration": 0.00634,
     "end_time": "2026-10-02T14:42:23.447016+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:23.440676+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "for ind in range(3):\n",
    "    assert_near_equal(top.get_val('comp.z', indices=ind), [1.5], 1e-8)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc58b908",
   "metadata": {
    "papermill": {
     "duration": 0.001796,
     "end_time": "2026-10-02T14:42:23.450323+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:23.448527+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "- bound_enforcement: scalar\n",
    "\n",
    "  The `bound_enforcement` option in the options dictionary is used to specify how the output bounds\n",
    "  are enforced. When this is set to \"scaler\", then the only indices in the output vector that are rolled back\n",
    "  are the ones that violate their upper or lower bounds. The backtracking continues along the modified gradient."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "f393be12",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:42:23.454608Z",
     "iopub.status.busy": "2026-10-02T14:42:23.454449Z",
     "iopub.status.idle": "2026-10-02T14:42:23.465473Z",
     "shell.execute_reply": "2026-10-02T14:42:23.464881Z"
    },
    "papermill": {
     "duration": 0.014121,
     "end_time": "2026-10-02T14:42:23.466187+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:23.452066+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NL: NewtonSolver 'NL: Newton' on system '' failed to converge in 10 iterations.\n"
     ]
    }
   ],
   "source": [
    "from openmdao.test_suite.components.implicit_newton_linesearch import ImplCompTwoStatesArrays\n",
    "\n",
    "top = om.Problem()\n",
    "top.model.add_subsystem('comp', ImplCompTwoStatesArrays(), promotes_inputs=['x'])\n",
    "\n",
    "top.model.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "top.model.nonlinear_solver.options['maxiter'] = 10\n",
    "top.model.linear_solver = om.ScipyKrylov()\n",
    "\n",
    "top.model.nonlinear_solver.linesearch = om.BoundsEnforceLS(bound_enforcement='scalar')\n",
    "\n",
    "top.setup()\n",
    "top.set_val('x', np.array([2., 2, 2]).reshape(3, 1))\n",
    "top.run_model()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "4623cfa8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:42:23.470429Z",
     "iopub.status.busy": "2026-10-02T14:42:23.470308Z",
     "iopub.status.idle": "2026-10-02T14:42:23.479792Z",
     "shell.execute_reply": "2026-10-02T14:42:23.478973Z"
    },
    "papermill": {
     "duration": 0.012268,
     "end_time": "2026-10-02T14:42:23.480320+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:23.468052+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NL: NewtonSolver 'NL: Newton' on system '' failed to converge in 10 iterations.\n"
     ]
    }
   ],
   "source": [
    "# Test lower bounds: should stop just short of the lower bound\n",
    "top.set_val('comp.y', 0.)\n",
    "top.set_val('comp.z', 1.6)\n",
    "top.run_model()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bdd55e67",
   "metadata": {
    "papermill": {
     "duration": 0.001772,
     "end_time": "2026-10-02T14:42:23.483650+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:23.481878+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "- bound_enforcement: wall\n",
    "\n",
    "  The `bound_enforcement` option in the options dictionary is used to specify how the output bounds\n",
    "  are enforced. When this is set to \"wall\", then the only indices in the output vector that are rolled back\n",
    "  are the ones that violate their upper or lower bounds. The backtracking continues along a modified gradient\n",
    "  direction that follows the boundary of the violated output bounds."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "01789615",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:42:23.488006Z",
     "iopub.status.busy": "2026-10-02T14:42:23.487888Z",
     "iopub.status.idle": "2026-10-02T14:42:23.501466Z",
     "shell.execute_reply": "2026-10-02T14:42:23.500741Z"
    },
    "papermill": {
     "duration": 0.016685,
     "end_time": "2026-10-02T14:42:23.502110+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:23.485425+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NL: NewtonSolver 'NL: Newton' on system '' failed to converge in 10 iterations.\n",
      "[2.6]\n",
      "[2.5]\n",
      "[2.65]\n"
     ]
    }
   ],
   "source": [
    "from openmdao.test_suite.components.implicit_newton_linesearch import ImplCompTwoStatesArrays\n",
    "\n",
    "top = om.Problem()\n",
    "top.model.add_subsystem('comp', ImplCompTwoStatesArrays(), promotes_inputs=['x'])\n",
    "\n",
    "top.model.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)\n",
    "top.model.nonlinear_solver.options['maxiter'] = 10\n",
    "top.model.linear_solver = om.ScipyKrylov()\n",
    "\n",
    "top.model.nonlinear_solver.linesearch = om.BoundsEnforceLS(bound_enforcement='wall')\n",
    "\n",
    "top.setup()\n",
    "top.set_val('x', np.array([0.5, 0.5, 0.5]).reshape(3, 1))\n",
    "\n",
    "# Test upper bounds: should go to the upper bound and stall\n",
    "top.set_val('comp.y', 0.)\n",
    "top.set_val('comp.z', 2.4)\n",
    "top.run_model()\n",
    "\n",
    "print(top.get_val('comp.z', indices=0))\n",
    "print(top.get_val('comp.z', indices=1))\n",
    "print(top.get_val('comp.z', indices=2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "2d17a00b",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:42:23.506670Z",
     "iopub.status.busy": "2026-10-02T14:42:23.506476Z",
     "iopub.status.idle": "2026-10-02T14:42:23.511368Z",
     "shell.execute_reply": "2026-10-02T14:42:23.510867Z"
    },
    "papermill": {
     "duration": 0.007852,
     "end_time": "2026-10-02T14:42:23.511829+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:42:23.503977+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "np.float64(0.0)"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "assert_near_equal(top.get_val('comp.z', indices=0), [2.6], 1e-8)\n",
    "assert_near_equal(top.get_val('comp.z', indices=1), [2.5], 1e-8)\n",
    "assert_near_equal(top.get_val('comp.z', indices=2), [2.65], 1e-8)"
   ]
  }
 ],
 "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.13.14"
  },
  "orphan": true,
  "papermill": {
   "default_parameters": {},
   "duration": 8.20267,
   "end_time": "2026-10-02T14:42:26.894454+00:00",
   "environment_variables": {},
   "exception": null,
   "input_path": "/home/runner/work/OpenMDAO/OpenMDAO/openmdao/docs/openmdao_book/features/building_blocks/solvers/bounds_enforce.ipynb",
   "output_path": "/home/runner/work/OpenMDAO/OpenMDAO/openmdao/docs/_executed_book/features/building_blocks/solvers/bounds_enforce.ipynb",
   "parameters": {},
   "start_time": "2026-10-02T14:42:18.691784+00:00",
   "version": "2.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}