Extension to Nonlinear Systems of ODEs#

Last time we introduced forward and backward Euler and tried them on the test ODE. Now for the practical details of using them on nonlinear problems and systems, plus some examples that motivate more advanced methods and a theoretical framework for analyzing them.

Solving Nonlinear ODEs with Backward Euler#

Consider a generic nonlinear (still scalar) ODE

\[\dv{u}{t} = f(t, u) \qquad u(0) = c \qquad f : [0, T] \times \mathbb{R} \to \mathbb{R}\]

Forward Euler doesn’t care: we only need to evaluate \( f \) at the known point \( (t_n, u_n) \) .

\begin{algorithm}
\caption{Forward Euler}
\begin{algorithmic}
\State Given $c$, $f$, $\Delta t$
\State Set $u_0 = c$
\For{$n = 0, 1, 2, \dots, T/\Delta t - 1$}
    \State Set $u_{n+1} = u_n + \Delta t \, f(t_n, u_n)$
\EndFor
\end{algorithmic}
\end{algorithm}

It’s a bit more complicated to write out backward Euler,

\[u_{n+1} = u_n + \Delta t \, f(t_{n+1}, u_{n+1})\]

because \( f(t_{n+1}, u_{n+1}) \) is a general nonlinear function of the unknown, so we have to solve a nonlinear equation every step. Any nonlinear solver works, but our primary approach is Newton’s method.

Newton’s Method#

Consider

\[g(v) = 0\]

where \( g : \mathbb{R} \to \mathbb{R} \) is continuously differentiable. Our goal is to find \( v \). We build a sequence of guesses \( \{v_k\}_{k \geq 0} \) that should end up arbitrarily close to \( v \). At each guess, we pretend \( g \) is linear and solve for the root of its linear Taylor expansion

\[g(v_{k+1}) \approx g(v_k) + g'(v_k)(v_{k+1} - v_k) = 0 \quad \to \quad v_{k+1} = v_k - \frac{g(v_k)}{g'(v_k)}\]
  • We need to pick an initial guess \( v_0 \)
  • We need to be able to evaluate \( g \) and \( g’ \)
  • We assume \( g’ \) stays bounded away from zero near the solution

Convergence of Newton’s method is nontrivial and depends on the initialization and the smoothness of \( g \) (see e.g. Nocedal & Wright, Thm. 3.5).

So how does this help us with backward Euler? Let \( v = u_{n+1} \)

\[\begin{aligned} u_{n+1} &= u_n + \Delta t \, f(t_{n+1}, u_{n+1}) \\ v &= u_n + \Delta t \, f(t_{n+1}, v) \\ g(v) \equiv v - u_n - \Delta t \, f(t_{n+1}, v) &= 0 \end{aligned}\]

Notice that

\[g'(v) = 1 - \Delta t \pdv{f}{u}(t_{n+1}, v)\]

so our Newton sub-iterations take the form

\[v_{k+1} = v_k - \frac{v_k - u_n - \Delta t \, f(t_{n+1}, v_k)}{1 - \Delta t \pdv{f}{u}(t_{n+1}, v_k)}\]

Typically we use \( v_0 = u_n \) as the initial guess, since \( u_{n+1} \to u_n \) as \( \Delta t \to 0 \). Newton converges (asymptotically) quadratically near the solution, so we only need a few iterations, but we never converge exactly. We stop when \( |v_{k+1} - v_k| < \text{tol} \) for some prescribed tolerance, e.g. \( 10^{-8} \).

\begin{algorithm}
\caption{Backward Euler with Newton Sub-iterations}
\begin{algorithmic}
\State Given $c$, $f$, $\Delta t$, and tolerance $\text{tol} > 0$
\State Set $u_0 = c$
\For{$n = 0, 1, 2, \dots, T/\Delta t - 1$}
    \State Initialize $v_0 = u_n$
    \State Set $k = 0$
    \Repeat
        \State Set $v_{k+1} = v_k - \dfrac{v_k - u_n - \Delta t \, f(t_{n+1}, v_k)}{1 - \Delta t \, \frac{\partial f}{\partial u}(t_{n+1}, v_k)}$
        \State Increment $k \gets k+1$
    \Until{$|v_{k+1} - v_k| < \text{tol}$}
    \State Set $u_{n+1} = v_{k+1}$
\EndFor
\end{algorithmic}
\end{algorithm}

Not too bad! Now we have nested loops, so the cost per step is waaaay higher, but it is at least stable.

Example: Growing Ice Thickness#

Stefan’s ice-growth law models the thickness \( u \) of ice on top of a frozen lake

\[\dv{u}{t} = \frac{D}{u} \qquad u(0) = c > 0\]

where \( D > 0 \) is an effective thermal diffusivity of the ice. This has an analytic solution: the ODE is \( \dv{}{t} u^2 = 2D \), so

\[u(t) = \sqrt{2Dt + c^2}\]

The ice grows like \( \sqrt{t} \): thickening slows down over time.

img/amath581/stefan_ice_growth_dt0.01.png img/amath581/stefan_newton_iters_dt0.01.png

Stefan's ice-growth law, \( \Delta t = 0.01 \). Left: numerical solutions against the truth. Middle: errors. Right: Newton sub-iterations per step.

Systems of ODEs with Euler#

\[\dv{\vec{u}}{t} = \vec{f}(t, \vec{u}) \qquad \vec{u}(0) = \vec{c} \qquad \vec{f} : [0, T] \times \mathbb{R}^d \to \mathbb{R}^d\]

Forward Euler is exactly the same, just with vector-valued \( \vec{u} \)

\[\vec{u}_{n+1} = \vec{u}_n + \Delta t \, \vec{f}(t_n, \vec{u}_n)\]

But backward Euler

\[\vec{u}_{n+1} = \vec{u}_n + \Delta t \, \vec{f}(t_{n+1}, \vec{u}_{n+1})\]

now requires solving a nonlinear system of equations at each step, so we extend Newton’s method to systems.

Newton’s Method for Systems#

Consider a generic problem

\[\vec{g}(\vec{v}) = \vec{0} \qquad \vec{g} : \mathbb{R}^d \to \mathbb{R}^d\]

The Jacobian of \( \vec{g} \) is the matrix of partial derivatives, with the gradients of each component as its rows

\[D\vec{g}(\vec{v}) = \begin{bmatrix} - & \grad g_1^T & - \\ - & \grad g_2^T & - \\ & \vdots & \\ - & \grad g_d^T & - \end{bmatrix} \qquad \left[ D\vec{g} \right]_{ij} = \pdv{g_i}{v_j}\]

Again this comes from a Taylor expansion of \( \vec{g} \)

\[\vec{g}(\vec{v}_{k+1}) = \vec{g}(\vec{v}_k) + D\vec{g}(\vec{v}_k)(\vec{v}_{k+1} - \vec{v}_k) + \mathcal{O}\left(\| \vec{v}_{k+1} - \vec{v}_k \|^2\right)\]

Set the linear part to zero and solve for \( \vec{v}_{k+1} \)

\[\vec{v}_{k+1} = \vec{v}_k - \left( D\vec{g}(\vec{v}_k) \right)^{-1} \vec{g}(\vec{v}_k)\]

Of course we don’t want to compute a whole inverse, so in practice we solve the linear system

\[D\vec{g}(\vec{v}_k) \, \delta\vec{v} = -\vec{g}(\vec{v}_k) \qquad \vec{v}_{k+1} = \vec{v}_k + \delta\vec{v}\]

For backward Euler, \( \vec{g}(\vec{v}) = \vec{v} - \vec{u}_n - \Delta t \, \vec{f}(t_{n+1}, \vec{v}) \) , so \( D\vec{g} = I - \Delta t \, D_{\vec{u}}\vec{f} \) and the sub-iterations are

\[\vec{v}_{k+1} = \vec{v}_k - \left( I - \Delta t \, D_{\vec{u}}\vec{f}(t_{n+1}, \vec{v}_k) \right)^{-1} \left[ \vec{v}_k - \vec{u}_n - \Delta t \, \vec{f}(t_{n+1}, \vec{v}_k) \right]\]

We now need access to \( D\vec{f} \) and a linear solve every Newton iteration. \( D\vec{f} \) can be approximated many ways, though nowadays automatic differentiation is often used to compute it efficiently.

\begin{algorithm}
\caption{Backward Euler with Newton Sub-iterations for Systems}
\begin{algorithmic}
\State Given $\mathbf{c}$, $\mathbf{f}$, $D_{\mathbf{u}}\mathbf{f}$, $\Delta t$, and tolerance $\text{tol} > 0$
\State Set $\mathbf{u}_0 = \mathbf{c}$
\For{$n = 0, 1, 2, \dots, T/\Delta t - 1$}
    \State Initialize $\mathbf{v}_0 = \mathbf{u}_n$
    \State Set $k = 0$
    \Repeat
        \State Set $\mathbf{v}_{k+1} = \mathbf{v}_k - \left( I - \Delta t \, D_{\mathbf{u}} \mathbf{f}(t_{n+1}, \mathbf{v}_k) \right)^{-1} \left[ \mathbf{v}_k - \mathbf{u}_n - \Delta t \, \mathbf{f}(t_{n+1}, \mathbf{v}_k) \right]$
        \State Increment $k \gets k+1$
    \Until{$\| \mathbf{v}_{k+1} - \mathbf{v}_k \| < \text{tol}$}
    \State Set $\mathbf{u}_{n+1} = \mathbf{v}_{k+1}$
\EndFor
\end{algorithmic}
\end{algorithm}

Example: Predator-Prey Model (Lotka-Volterra)#

\[\begin{aligned} \dv{u_1}{t} &= \alpha u_1 - \beta u_1 u_2 \\ \dv{u_2}{t} &= \delta u_1 u_2 - \gamma u_2 \end{aligned}\]

where \( u_1 \) is the prey population, \( u_2 \) is the predator population, and \( \alpha, \beta, \gamma, \delta > 0 \). The RHS and its Jacobian are

\[\vec{f}(t, \vec{u}) = \begin{bmatrix} \alpha u_1 - \beta u_1 u_2 \\ \delta u_1 u_2 - \gamma u_2 \end{bmatrix} \qquad D_{\vec{u}}\vec{f}(t, \vec{u}) = \begin{bmatrix} \alpha - \beta u_2 & -\beta u_1 \\ \delta u_2 & \delta u_1 - \gamma \end{bmatrix}\]

This gives us everything we need to plug and chug.

img/amath581/lotka_volterra.png

Lotka-Volterra with \( (\alpha, \beta, \gamma, \delta) = (1, 1, 1.5, 0.075) \), computed with backward Euler + Newton sub-iterations.