Partial differential equations#

Partial differential equations expand ODEs to include multiple independant variables (coordinates).

  • Other spatial coordinates are typically extensions of the ODE case and are usually posed and treated as boundary value problems.

  • The time coordinate is almost always posed as an initial value problem and tends to introduces complexity in the form of numerical stability issues.

Classification of PDEs#

Introduction of time as a coordinate in PDEs raises the question of how information propogates, from the initial condition or other events that occur during solution.

Elliptic equations#

Equations of the form, $\( A\frac{\partial^2 u}{\partial x^2} + B\frac{\partial^2 u}{\partial x \partial y} + C\frac{\partial^2 u}{\partial y^2} \)$ are termed elliptic. The lack of a rate of change term implies that information is not being propogated, but rather is either instantaneously available everywere in space, or all propagations have finished and you are left with a steady state.

Note this does not imply the system isn’t changing with time (imagine the coefficients could be functions of time, \(A(t)\)), just that the impact of the change has reach an equilibrium.

Examples include steady-state, or quasistatic equations:

Laplace’s and and Poisson’s equation: \(\nabla^2 u = 0\), \(\nabla^2 u = f(x)\)

  • Electrostatics: Electric potential with a charge

  • Heat transport: Steady state heat distribution with a source

  • Fluid flow: Velocity potential of an incompressible, irrotational fluid with a sink

Helmholtz equation, \(\nabla^2 u +k^2 u = 0\)

  • Accoustics: vibrations of a membrane of a cavity

  • Quantum mechanics: wave functions in a potential well

Inclusion of Helmholtz’s equation may be surprising since it describes wave phenomena! Recall that wave propogation can be solved by the method of separation of variables to separate the time and space dependance, the latter of which is of the Helmholtz’s form (and defines the eigenfunctions!)

Solution approaches#

Elliptic equations generally amount to solving a system of (nonlinear) equations simultaneously, which we can do with root finders. Let’s recall the shortfalls of rootfinders in this context:

  • A good initial guess: This can be a substantial problem in common systems. A mitigation strategy might be to parametrically ramp-up a parameter from an easily solvable condition to your desired condition. E.g.: gradually ‘turn up the heat’ on a natural convection problem.

  • Convergence: You may need to discretize the problem with a large mesh which is a high dimensional problem. The root finder may wander easily in this space and fall into local minima or step completely out of the feasible region. Trust-region methods can help mitigate this but consume additional resources.

There is another subtle issue with parallelization on modern high performance platforms. A common approach is to perform domain decomposition which partitions the domain geometrically and passes pieces to different nodes. Elliptic systems imply each node instantaneously talks to all the others, which can be an overhead / node synchronization nightmare! For this reason multigrid methods / preconditioners are tremendously effective.

Parabolic equations#

Equations of the form, $\( A \frac{\partial u}{\partial t} - C \frac{\partial^2 u}{\partial x^2} \)$

propogate information in one direction (forward in time) and the process is diffusive.

Diffusive processes tend to smooth out discontinuities and any small oscillations, which is a favourable feature but doesn’t gaurantee stability as we will see.

This still implies an infinite rate of propogation of information, but now in the flux which instantaneously knows about a force everywhere at once.

##Solution schemes

Solving diffusion problems generally amounts to a time-marching scheme in which time is discretized with the spactial equation solved at each time step. We still have to be wary of stiffness!

It is common to want to solve the inverse problem, in which one uses a result to attempt to back-calculate a parameter of the model (e.g.: Use the rate of cooling to infer the thermal conductivity). It is here that the tendancy of diffusion to smooth the features (information only flows forward in time) works against you. Many paths can lead to the same result (consider trying to sharpen a blurry image!) this can be very problematic to arrive at anything more than a statistical answer.

Hyperbolic equations#

Equations of the form, $\( A \frac{\partial^2 u}{\partial t^2} - C \frac{\partial^2 u}{\partial x^2} \)\( propogate information forward *and* backward in time. This implies time-reversal symmetry, wherein you could replace \)t\( with \)-t\( and the physics would remain unchanged. This describes waves (second order equations) and advective terms (first order equations) e.g.: \)A \frac{\partial u}{\partial t} - C \frac{\partial u}{\partial x}$

Solution schemes#

Second time derivatives can generally be decomposed into a series of parabolic equations, which is a standard approach. Systems with periodic boundary conditions are treatable with fourier spectral analysis which can be much more efficient.

Time reversal symmetry implies the model may be reversible which is interesting for the inverse problem and reconstruction of earlier times before what is measured / simulated.

#Solving time dependent PDEs

The general approach to solving time dependent PDEs is to transform them into a BVP through a time-marching scheme:

  1. Discretize the time derivative as an IVP into current and previous solution(s) with the initial value known

  2. Discretize space as a BVP and add the term from the time descritization.

  3. Step forward in time, updating the BVP accordingly.

Consider the parabolic case, since we can usually use reduction of order to transform a hyperbolic equation into parabolic equations.

\[ \frac{\partial T}{\partial t} = \alpha \frac{\partial^2 T}{\partial x^2} \]

where \(\alpha\) is the thermal diffusivity, which is necessarily postive.

The finite difference scheme for the spatial component is: $\(\alpha \frac{\partial^2 T}{\partial x^2} = \alpha \frac{T_{i-1} - 2 T_i + T_{i+1}}{\Delta x^2}\)$

Notation#

  • The spatial index will be noted as a subscript and indexed with \(i\), e.g.: \(T_i\).

  • The temporal index will be index with superscript \(t\).

  • The known timestep will always be \(T^t\) and we will be calculating \(T^{t+1}\).

Explicit Euler timestepping#

As in the IVP section, the explicit Euler scheme calculates the next solution based on the current one, $\( \frac{\partial T}{\partial t} = \frac{T^{t+1} -T^{t}}{\Delta t}\)$

and the full equation becomes,

\[\begin{split} \begin{align} \frac{\partial T}{\partial t} &= \alpha \frac{\partial^2 T}{{\partial x}^2} \\ \frac{T^{t+1}_i -T^{t}_i}{\Delta t} &= \alpha \frac{T^t_{i-1} - 2 T^t_i + T^t_{i+1}}{\Delta x^2} \\ T^{t+1}_i &= T_i^t + r [T^t_{i-1} - 2 T^t_i + T^t_{i+1}] \end{align}\end{split}\]

with $\( r = \frac{\alpha \Delta t} {\Delta x^2}\)$ for compactness.

As a stencil for forward Euler time stepping is:

![forward Euler stencil.svg](data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB3aWR0aD0iNDUuMjA2OTgybW0iCiAgIGhlaWdodD0iMTkuNDI3MTE2bW0iCiAgIHZpZXdCb3g9IjAgMCA0NS4yMDY5ODIgMTkuNDI3MTE2IgogICB2ZXJzaW9uPSIxLjEiCiAgIGlkPSJzdmcxIgogICBpbmtzY2FwZTpleHBvcnQtZmlsZW5hbWU9ImZvcndhcmQgRXVsZXIgc3RlbmNpbC5zdmciCiAgIGlua3NjYXBlOmV4cG9ydC14ZHBpPSI5NiIKICAgaW5rc2NhcGU6ZXhwb3J0LXlkcGk9Ijk2IgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0ibmFtZWR2aWV3MSIKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiMwMDAwMDAiCiAgICAgYm9yZGVyb3BhY2l0eT0iMC4yNSIKICAgICBpbmtzY2FwZTpzaG93cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIgogICAgIGlua3NjYXBlOnBhZ2VjaGVja2VyYm9hcmQ9IjAiCiAgICAgaW5rc2NhcGU6ZGVza2NvbG9yPSIjZDFkMWQxIgogICAgIGlua3NjYXBlOmRvY3VtZW50LXVuaXRzPSJtbSIgLz4KICA8ZGVmcwogICAgIGlkPSJkZWZzMSIgLz4KICA8ZwogICAgIGlua3NjYXBlOmxhYmVsPSJMYXllciAxIgogICAgIGlua3NjYXBlOmdyb3VwbW9kZT0ibGF5ZXIiCiAgICAgaWQ9ImxheWVyMSIKICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDIuMzc2MzgyLC02OC45Nzg3ODEpIj4KICAgIDxjaXJjbGUKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzIgogICAgICAgaWQ9InBhdGgxIgogICAgICAgY3g9IjQ0LjIwMjk1IgogICAgICAgY3k9Ijg2LjU3OTMzIgogICAgICAgcj0iMS44MjY1NjgyIgogICAgICAgaW5rc2NhcGU6ZXhwb3J0LWZpbGVuYW1lPSIuXHBhdGgxLnN2ZyIKICAgICAgIGlua3NjYXBlOmV4cG9ydC14ZHBpPSI5NiIKICAgICAgIGlua3NjYXBlOmV4cG9ydC15ZHBpPSI5NiIgLz4KICAgIDxjaXJjbGUKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzIgogICAgICAgaWQ9ImNpcmNsZTEiCiAgICAgICBjeD0iNTguMjI1ODgiCiAgICAgICBjeT0iODYuNTc5MzMiCiAgICAgICByPSIxLjgyNjU2ODIiIC8+CiAgICA8Y2lyY2xlCiAgICAgICBzdHlsZT0iZmlsbDojMDAwMDAwO3N0cm9rZS13aWR0aDowLjI2NDU4MyIKICAgICAgIGlkPSJjaXJjbGUyIgogICAgICAgY3g9IjcyLjI0ODgxIgogICAgICAgY3k9Ijg2LjU3OTMzIgogICAgICAgcj0iMS44MjY1NjgyIiAvPgogICAgPGNpcmNsZQogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yNjQ1ODMiCiAgICAgICBpZD0iY2lyY2xlMyIKICAgICAgIGN4PSI1OC4yMjU4OCIKICAgICAgIGN5PSI3MS4yMzM0ODIiCiAgICAgICByPSIxLjgyNjU2ODIiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yNjQ1ODMiCiAgICAgICBkPSJNIDU4LjA4NDg3LDcxLjYwMTQ3NSBWIDg2LjIxNDAyIgogICAgICAgaWQ9InBhdGgzIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzIgogICAgICAgZD0iTSA0NC4yMDI5NTMsODYuNTc5MzM2IEggNzIuNjk3NDE2IgogICAgICAgaWQ9InBhdGg0IiAvPgogICAgPGNpcmNsZQogICAgICAgaWQ9InBhdGg1IgogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MC4yNjQ1ODMiCiAgICAgICBjeD0iNzIuNjk3NDE4IgogICAgICAgY3k9Ijg2LjU3OTMzOCIKICAgICAgIHI9IjAuNzkzNzQ5OTkiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yNjQ1ODM7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Im0gNDQuMjAyOTUzLDg2LjU3OTMzNiBoIDI4LjEyOTE1IgogICAgICAgaWQ9InBhdGg2IiAvPgogICAgPGNpcmNsZQogICAgICAgaWQ9InBhdGg3IgogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MC4yNjQ1ODMiCiAgICAgICBjeD0iNzIuMzMyMTA4IgogICAgICAgY3k9Ijg2LjIxNDAyIgogICAgICAgcj0iMC4zOTY4NzQ5OSIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDojMDAwMDAwO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDowLjI2NDU4MztzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0iTSA1OC4yMjU4OCw4Ni40MDY2MjEgViA3MS4yOTUxNTEiCiAgICAgICBpZD0icGF0aDgiIC8+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6NC4yMzMzM3B4O2ZpbGw6IzAwMDAwMDtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yNjQ1ODM7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIHg9IjgwLjA3Nzg4OCIKICAgICAgIHk9Ijg3LjgyNzM2MiIKICAgICAgIGlkPSJ0ZXh0OCI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjgiCiAgICAgICAgIHN0eWxlPSJzdHJva2Utd2lkdGg6MC4yNjQ1ODMiCiAgICAgICAgIHg9IjgwLjA3Nzg4OCIKICAgICAgICAgeT0iODcuODI3MzYyIj50PC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6NC4yMzMzM3B4O2ZpbGw6IzAwMDAwMDtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yNjQ1ODM7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIHg9IjgwLjA3Nzg4OCIKICAgICAgIHk9IjcyLjE5OTI1NyIKICAgICAgIGlkPSJ0ZXh0OSI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjkiCiAgICAgICAgIHN0eWxlPSJzdHJva2Utd2lkdGg6MC4yNjQ1ODMiCiAgICAgICAgIHg9IjgwLjA3Nzg4OCIKICAgICAgICAgeT0iNzIuMTk5MjU3Ij50KzE8L3RzcGFuPjwvdGV4dD4KICA8L2c+Cjwvc3ZnPgo=)

This implies that the value at a point at the next timestep depends only on the values at the previous time-step.

Example of Forward Euler time stepping#

# prompt: Give me an example of forward euler timestepping for a 1D heat balance equation with a slider for the time step size

import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, FloatSlider

def forward_euler_heat(T, alpha, dx, dt, num_steps):
  """
  Solves the 1D heat equation using forward Euler timestepping.

  Args:
    T: Initial temperature distribution (numpy array).
    alpha: Thermal diffusivity.
    dx: Spatial step size.
    dt: Time step size.
    num_steps: Number of time steps.

  Returns:
    A list of temperature distributions at each time step.
  """

  T_history = [T.copy()]
  for _ in range(num_steps):
    T_new = T.copy()
    for i in range(1, len(T) - 1):
      T_new[i] = T[i] + alpha * dt / (dx ** 2) * (T[i - 1] - 2 * T[i] + T[i + 1])
    T = T_new
    T_history.append(T.copy())
  return T_history


def plot_heat_equation(dt):
  """
  Plots the solution of the heat equation for a given time step size.

  Args:
    dt: Time step size.
  """

  # Parameters
  alpha = 0.1  # Thermal diffusivity
  dx = 0.1  # Spatial step size
  num_steps = 50
  x = np.arange(0, 1, 1/num_steps)
  T_initial = np.zeros_like(x)
  T_initial[int(len(x) / 2)] = 1  # Initial heat source in the middle

  # Solve the heat equation
  T_history = forward_euler_heat(T_initial, alpha, dx, dt, num_steps)

  # Plotting
  plt.figure(figsize=(8, 6))
  for i in range(0, len(T_history), 5):
    plt.plot(x, T_history[i], label=f"Time step {i}")
  plt.xlabel("Position (x)")
  plt.ylabel("Temperature (T)")
  plt.title("Forward Euler Solution of 1D Heat Equation")
  plt.legend()
  plt.grid(True)
  plt.show()

# Interactive widget
interact(plot_heat_equation, dt=FloatSlider(min=0.001, max=0.05, step=0.001, value=0.01));

Error analysis#

Analysing the error using the Explicit Euler timestepper is more sophisticated than for the ODE since we now need to consider the behaviouir of a (spatial) function at a given time.

We can do this through von Neumann stability analysis which checks the stability of a solution to oscillations. Mathematically, we express the soultion as a Fourier series and see which waves damp and which waves grow.

Consider Fourier modes of the solution: \(^kT_i^t = e^{j k i \Delta x}\) where \(k\) is the wave number and \(j\) is the imaginary number. For a particular wave with number \(k\), we check how the future solution, \(^kT_i^{t+1}\) depends on the current, \(^kT_i^n\), and define the growth factor \(G\), \(^kT_i^{t+1} = G ^kT_i^{t}.\) A method is deemed stable if the amplitude of the oscillations don’t grow over time; i.e.: \(|G| \le 1\).

Substitute this into the update equation:

\[\begin{split} \begin{align} ^k T^{t+1}_i &= e^{j k i \Delta x} + \big[e^{j k [i-1] \Delta x} - 2 e^{j k i \Delta x} + e^{j k [i+1] \Delta x}\big] r \\ &= e^{j k i \Delta x} \big[1+ r [e^{j k \Delta x} +e^{-j k \Delta x} -2] \big] \\ ^k T^{t+1}_i &= \ ^kT^{t}_i \big[1+ r [e^{j k \Delta x} +e^{-j k \Delta x} -2] \big] \\ G &= 1+2 r [\cos(\Delta x)-1] \end{align}\end{split}\]

Looking at the cosign, we can see \(G\) is in the range \(\bigg[1-4\frac{\alpha \Delta t}{\Delta x^2}, 1\bigg]\). The maximum already satisfies the stability, but the minimum must satisfy, $\( \frac{\alpha \Delta t}{\Delta x^2} \le \frac{1}{2}\)$

or more simply $\( \Delta t \le \frac{\Delta x^2}{2 \alpha}\)$

For higher dimensions, additional waves add to the instabilities. For a square grid with step size \(h\),

2D: $\( \Delta t \le \frac{h}{4 \alpha}\)\( 3D: \)\( \Delta t \le \frac{h}{6 \alpha}\)$

Once again we have come up against a stiffness issue wherein our timestep is limited by numerical stability rather than accuracy. Unsurprisingly and unfortunatley, this is true of all our explicit time stepping schemes.

Implicit Euler timestepping#

As with the ODE case, we can attempt an Implicit Euler timestepping scheme: $\( \frac{\partial T}{\partial t} = \frac{T^{t+1} -T^{t}}{\Delta t}\)$

and now discretize space at the new timestep:

\[ T^{t+1}_i = T_i^{t} + [T^{t+1}_{i-1} - 2 T^{t+1}_i + T^{t+1}_{i+1}] r\]

The stencil is:

![backward Euler stencil.svg](data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB3aWR0aD0iNDUuMjA2OTgybW0iCiAgIGhlaWdodD0iMTkuNDI3MTQxbW0iCiAgIHZpZXdCb3g9IjAgMCA0NS4yMDY5ODIgMTkuNDI3MTQxIgogICB2ZXJzaW9uPSIxLjEiCiAgIGlkPSJzdmcxIgogICBpbmtzY2FwZTpleHBvcnQtZmlsZW5hbWU9ImZvcndhcmQgRXVsZXIgc3RlbmNpbC5zdmciCiAgIGlua3NjYXBlOmV4cG9ydC14ZHBpPSI5NiIKICAgaW5rc2NhcGU6ZXhwb3J0LXlkcGk9Ijk2IgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0ibmFtZWR2aWV3MSIKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiMwMDAwMDAiCiAgICAgYm9yZGVyb3BhY2l0eT0iMC4yNSIKICAgICBpbmtzY2FwZTpzaG93cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIgogICAgIGlua3NjYXBlOnBhZ2VjaGVja2VyYm9hcmQ9IjAiCiAgICAgaW5rc2NhcGU6ZGVza2NvbG9yPSIjZDFkMWQxIgogICAgIGlua3NjYXBlOmRvY3VtZW50LXVuaXRzPSJtbSIgLz4KICA8ZGVmcwogICAgIGlkPSJkZWZzMSIgLz4KICA8ZwogICAgIGlua3NjYXBlOmxhYmVsPSJMYXllciAxIgogICAgIGlua3NjYXBlOmdyb3VwbW9kZT0ibGF5ZXIiCiAgICAgaWQ9ImxheWVyMSIKICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDIuMzc2MzgyLC0xMDEuNzg3MTEpIj4KICAgIDxjaXJjbGUKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzIgogICAgICAgaWQ9ImNpcmNsZTkiCiAgICAgICBjeD0iNDQuMjAyOTUiCiAgICAgICBjeT0iLTEwNC4wNDE4NSIKICAgICAgIHI9IjEuODI2NTY4MiIKICAgICAgIGlua3NjYXBlOmV4cG9ydC1maWxlbmFtZT0iYmFja3dhcmQgRXVsZXIgc3RlbmNpbC5zdmciCiAgICAgICBpbmtzY2FwZTpleHBvcnQteGRwaT0iOTYiCiAgICAgICBpbmtzY2FwZTpleHBvcnQteWRwaT0iOTYiCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDEsLTEpIiAvPgogICAgPGNpcmNsZQogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yNjQ1ODMiCiAgICAgICBpZD0iY2lyY2xlMTAiCiAgICAgICBjeD0iNTguMjI1ODgiCiAgICAgICBjeT0iLTEwNC4wNDE4NSIKICAgICAgIHI9IjEuODI2NTY4MiIKICAgICAgIHRyYW5zZm9ybT0ic2NhbGUoMSwtMSkiIC8+CiAgICA8Y2lyY2xlCiAgICAgICBzdHlsZT0iZmlsbDojMDAwMDAwO3N0cm9rZS13aWR0aDowLjI2NDU4MyIKICAgICAgIGlkPSJjaXJjbGUxMSIKICAgICAgIGN4PSI3Mi4yNDg4MSIKICAgICAgIGN5PSItMTA0LjA0MTg1IgogICAgICAgcj0iMS44MjY1NjgyIgogICAgICAgdHJhbnNmb3JtPSJzY2FsZSgxLC0xKSIgLz4KICAgIDxjaXJjbGUKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzIgogICAgICAgaWQ9ImNpcmNsZTEyIgogICAgICAgY3g9IjU4LjIyNTg4IgogICAgICAgY3k9Ii0xMTkuMzg3NjgiCiAgICAgICByPSIxLjgyNjU2ODIiCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDEsLTEpIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzIgogICAgICAgZD0iTSA1OC4wODQ4NywxMTkuMDE5NjggViAxMDQuNDA3MDYiCiAgICAgICBpZD0icGF0aDEyIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzIgogICAgICAgZD0iTSA0NC4yMDI5NTMsMTA0LjA0MTc0IEggNzIuNjk3NDE2IgogICAgICAgaWQ9InBhdGgxMyIgLz4KICAgIDxjaXJjbGUKICAgICAgIGlkPSJjaXJjbGUxMyIKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzIgogICAgICAgY3g9IjcyLjY5NzQxOCIKICAgICAgIGN5PSItMTA0LjA0MTg1IgogICAgICAgcj0iMC43OTM3NDk5OSIKICAgICAgIHRyYW5zZm9ybT0ic2NhbGUoMSwtMSkiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yNjQ1ODM7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Im0gNDQuMjAyOTUzLDEwNC4wNDE3NCBoIDI4LjEyOTE1IgogICAgICAgaWQ9InBhdGgxNCIgLz4KICAgIDxjaXJjbGUKICAgICAgIGlkPSJjaXJjbGUxNCIKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzIgogICAgICAgY3g9IjcyLjMzMjEwOCIKICAgICAgIGN5PSItMTA0LjQwNzE1IgogICAgICAgcj0iMC4zOTY4NzQ5OSIKICAgICAgIHRyYW5zZm9ybT0ic2NhbGUoMSwtMSkiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yNjQ1ODM7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Im0gNTguMjI1ODgsMTA0LjIxNDQ2IHYgMTUuMTExNTUiCiAgICAgICBpZD0icGF0aDE1IiAvPgogICAgPHRleHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjQuMjMzMzNweDtmaWxsOiMwMDAwMDA7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICB4PSI4MC4wNzc4ODgiCiAgICAgICB5PSIxMjAuNjM1NyIKICAgICAgIGlkPSJ0ZXh0MTUiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW4xNSIKICAgICAgICAgc3R5bGU9InN0cm9rZS13aWR0aDowLjI2NDU4MyIKICAgICAgICAgeD0iODAuMDc3ODg4IgogICAgICAgICB5PSIxMjAuNjM1NyI+dDwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjQuMjMzMzNweDtmaWxsOiMwMDAwMDA7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICB4PSI4MC4wNzc4ODgiCiAgICAgICB5PSIxMDUuMDA3NTgiCiAgICAgICBpZD0idGV4dDE2Ij48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuMTYiCiAgICAgICAgIHN0eWxlPSJzdHJva2Utd2lkdGg6MC4yNjQ1ODMiCiAgICAgICAgIHg9IjgwLjA3Nzg4OCIKICAgICAgICAgeT0iMTA1LjAwNzU4Ij50KzE8L3RzcGFuPjwvdGV4dD4KICA8L2c+Cjwvc3ZnPgo=)

A quick glance at the error analysis will see that it is now \(\frac{1}{G}\) which is in the range \(\bigg[1-4\frac{\alpha \Delta t}{\Delta x^2}, 1\bigg]\) and which always satisfies the stability condition.

Once again the Forward Euler method, and implicit method more generally, are unconditional stability but at the cost of solving a system of equations.

Example of Backward Euler time stepping#

# prompt: Repeat solving hte heat transport equation but thistime with a backward Euler method

import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, FloatSlider

def backward_euler_heat(T, alpha, dx, dt, num_steps):
  """
  Solves the 1D heat equation using backward Euler timestepping.

  Args:
    T: Initial temperature distribution (numpy array).
    alpha: Thermal diffusivity.
    dx: Spatial step size.
    dt: Time step size.
    num_steps: Number of time steps.

  Returns:
    A list of temperature distributions at each time step.
  """

  T_history = [T.copy()]

  for _ in range(num_steps):
    A = np.diag(-alpha * dt / dx**2 * np.ones(len(T) - 1), -1) + \
        np.diag(1 + 2 * alpha * dt / dx**2 * np.ones(len(T)), 0) + \
        np.diag(-alpha * dt / dx**2 * np.ones(len(T) - 1), 1)

    # Apply boundary conditions (Dirichlet in this example, T=0 at edges)
    b = T.copy()
    b[1:-1] = T[1:-1]

    # plt.spy(A, markersize=1)  # Creates a sparsity plot
    # plt.title('Sparsity Pattern of Matrix A')
    # plt.show()

    # Solve the linear system for T at the next time step
    T_new = np.linalg.solve(A, b)

    # Update T and store the solution
    T = T_new.copy()
    T_history.append(T.copy())

  return T_history

def plot_heat_equation(dt):
  """
  Plots the solution of the heat equation for a given time step size.

  Args:
    dt: Time step size.
  """

  # Parameters
  alpha = 0.1  # Thermal diffusivity
  dx = 0.1  # Spatial step size
  num_steps = 50
  x = np.arange(0, 1, 1/num_steps)
  T_initial = np.zeros_like(x)
  T_initial[int(len(x) / 2)] = 1  # Initial heat source in the middle

  # Solve the heat equation
  T_history = backward_euler_heat(T_initial, alpha, dx, dt, num_steps)

  # Plotting
  plt.figure(figsize=(8, 6))
  for i in range(0, len(T_history), 5):
    plt.plot(x, T_history[i], label=f"Time step {i}")
  plt.xlabel("Position (x)")
  plt.ylabel("Temperature (T)")
  plt.title("Backward Euler Solution of 1D Heat Equation")
  plt.legend()
  plt.grid(True)
  plt.show()


# Interactive widget
interact(plot_heat_equation, dt=FloatSlider(min=0.01, max=0.1, step=0.01, value=0.05));

Crank-Nicholson method#

One shortcoming of the backward Euler method is that it is only 1st order accurate in time whereas the central difference spatial discretization is second order. This means that one would need smaller time steps compared to spatial steps to ensure accuracy is optimized.

The Crank-Nicholson method is an alternative implicit method that is second order in both time and space. As with the central difference derivation (and many others!) the key is to average the explicit and implicit methods!

Take, $\( \frac{\partial T}{\partial t} = \frac{T^{t+1} -T^{t}}{\Delta t}\)$

and now average the spatial distretizations at \(t+1\) and \(t\):

\[ \frac{\partial^2 T}{\partial x^2} = \frac{1}{2} [T^{t+1}_{i-1} - 2 T^{t+1}_i + T^{t+1}_{i+1} + T^{t}_{i-1} - 2 T^{t}_i + T^{t}_{i+1}] {\Delta x^2}\]

Such that the increment scheme is: $\( (1 + 2r) T_i^{t+1} - r T_{i+1}^{t+1} - r T_{i-1}^{t+1} = (1 - 2r) T_i^{t} + r T_{i+1}^{t} + r T_{i-1}^{t}\)$

which seems ugly at first but is actually the exact same matrix sparsity as backward Euler, so achieves better accuracy for comparable computational cost.

The stencil is,

![Crank Nicholson stencil.svg](data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB3aWR0aD0iNDUuMjA2OTgybW0iCiAgIGhlaWdodD0iMTkuMDI2MTczbW0iCiAgIHZpZXdCb3g9IjAgMCA0NS4yMDY5ODIgMTkuMDI2MTczIgogICB2ZXJzaW9uPSIxLjEiCiAgIGlkPSJzdmcxIgogICBpbmtzY2FwZTpleHBvcnQtZmlsZW5hbWU9ImZvcndhcmQgRXVsZXIgc3RlbmNpbC5zdmciCiAgIGlua3NjYXBlOmV4cG9ydC14ZHBpPSI5NiIKICAgaW5rc2NhcGU6ZXhwb3J0LXlkcGk9Ijk2IgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0ibmFtZWR2aWV3MSIKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiMwMDAwMDAiCiAgICAgYm9yZGVyb3BhY2l0eT0iMC4yNSIKICAgICBpbmtzY2FwZTpzaG93cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIgogICAgIGlua3NjYXBlOnBhZ2VjaGVja2VyYm9hcmQ9IjAiCiAgICAgaW5rc2NhcGU6ZGVza2NvbG9yPSIjZDFkMWQxIgogICAgIGlua3NjYXBlOmRvY3VtZW50LXVuaXRzPSJtbSIgLz4KICA8ZGVmcwogICAgIGlkPSJkZWZzMSIgLz4KICA8ZwogICAgIGlua3NjYXBlOmxhYmVsPSJMYXllciAxIgogICAgIGlua3NjYXBlOmdyb3VwbW9kZT0ibGF5ZXIiCiAgICAgaWQ9ImxheWVyMSIKICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDIuMzc2MzgyLC0xMjYuMTI4NzUpIj4KICAgIDxjaXJjbGUKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzIgogICAgICAgaWQ9ImNpcmNsZTE2IgogICAgICAgY3g9IjQ0LjIwMjk1IgogICAgICAgY3k9Ii0xMjguMzgzNTEiCiAgICAgICByPSIxLjgyNjU2ODIiCiAgICAgICBpbmtzY2FwZTpleHBvcnQtZmlsZW5hbWU9ImJhY2t3YXJkIEV1bGVyIHN0ZW5jaWwuc3ZnIgogICAgICAgaW5rc2NhcGU6ZXhwb3J0LXhkcGk9Ijk2IgogICAgICAgaW5rc2NhcGU6ZXhwb3J0LXlkcGk9Ijk2IgogICAgICAgdHJhbnNmb3JtPSJzY2FsZSgxLC0xKSIgLz4KICAgIDxjaXJjbGUKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzIgogICAgICAgaWQ9ImNpcmNsZTE3IgogICAgICAgY3g9IjU4LjIyNTg4IgogICAgICAgY3k9Ii0xMjguMzgzNTEiCiAgICAgICByPSIxLjgyNjU2ODIiCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDEsLTEpIiAvPgogICAgPGNpcmNsZQogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yNjQ1ODMiCiAgICAgICBpZD0iY2lyY2xlMTgiCiAgICAgICBjeD0iNzIuMjQ4ODEiCiAgICAgICBjeT0iLTEyOC4zODM1MSIKICAgICAgIHI9IjEuODI2NTY4MiIKICAgICAgIHRyYW5zZm9ybT0ic2NhbGUoMSwtMSkiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yNjQ1ODMiCiAgICAgICBkPSJNIDU4LjA4NDg3LDE0My4zNjE1IFYgMTI4Ljc0ODg4IgogICAgICAgaWQ9InBhdGgxOSIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDojMDAwMDAwO3N0cm9rZS13aWR0aDowLjI2NDU4MyIKICAgICAgIGQ9Ik0gNDQuMjAyOTUzLDEyOC4zODM1NiBIIDcyLjY5NzQxNiIKICAgICAgIGlkPSJwYXRoMjAiIC8+CiAgICA8Y2lyY2xlCiAgICAgICBpZD0iY2lyY2xlMjAiCiAgICAgICBzdHlsZT0iZmlsbDojMDAwMDAwO3N0cm9rZTpub25lO3N0cm9rZS13aWR0aDowLjI2NDU4MyIKICAgICAgIGN4PSI3Mi42OTc0MTgiCiAgICAgICBjeT0iLTEyOC4zODM1MSIKICAgICAgIHI9IjAuNzkzNzQ5OTkiCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDEsLTEpIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJtIDQ0LjIwMjk1MywxMjguMzgzNTYgaCAyOC4xMjkxNSIKICAgICAgIGlkPSJwYXRoMjEiIC8+CiAgICA8Y2lyY2xlCiAgICAgICBpZD0iY2lyY2xlMjEiCiAgICAgICBzdHlsZT0iZmlsbDojMDAwMDAwO3N0cm9rZTpub25lO3N0cm9rZS13aWR0aDowLjI2NDU4MyIKICAgICAgIGN4PSI3Mi4zMzIxMDgiCiAgICAgICBjeT0iLTEyOC43NDg3OSIKICAgICAgIHI9IjAuMzk2ODc0OTkiCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDEsLTEpIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJtIDU4LjIyNTg4LDEyOC41NTYyOCB2IDE1LjExMTU1IgogICAgICAgaWQ9InBhdGgyMiIgLz4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo0LjIzMzMzcHg7ZmlsbDojMDAwMDAwO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDowLjI2NDU4MztzdHJva2Utb3BhY2l0eToxIgogICAgICAgeD0iODAuMDc3ODg4IgogICAgICAgeT0iMTQ0Ljk3NzE0IgogICAgICAgaWQ9InRleHQyMiI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjIyIgogICAgICAgICBzdHlsZT0ic3Ryb2tlLXdpZHRoOjAuMjY0NTgzIgogICAgICAgICB4PSI4MC4wNzc4ODgiCiAgICAgICAgIHk9IjE0NC45NzcxNCI+dDwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjQuMjMzMzNweDtmaWxsOiMwMDAwMDA7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICB4PSI4MC4wNzc4ODgiCiAgICAgICB5PSIxMjkuMzQ5MjEiCiAgICAgICBpZD0idGV4dDIzIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuMjMiCiAgICAgICAgIHN0eWxlPSJzdHJva2Utd2lkdGg6MC4yNjQ1ODMiCiAgICAgICAgIHg9IjgwLjA3Nzg4OCIKICAgICAgICAgeT0iMTI5LjM0OTIxIj50KzE8L3RzcGFuPjwvdGV4dD4KICAgIDxjaXJjbGUKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzIgogICAgICAgaWQ9ImNpcmNsZTIzIgogICAgICAgY3g9IjQ0LjIwMjk1IgogICAgICAgY3k9Ii0xNDMuMTk5OTgiCiAgICAgICByPSIxLjgyNjU2ODIiCiAgICAgICBpbmtzY2FwZTpleHBvcnQtZmlsZW5hbWU9ImJhY2t3YXJkIEV1bGVyIHN0ZW5jaWwuc3ZnIgogICAgICAgaW5rc2NhcGU6ZXhwb3J0LXhkcGk9Ijk2IgogICAgICAgaW5rc2NhcGU6ZXhwb3J0LXlkcGk9Ijk2IgogICAgICAgdHJhbnNmb3JtPSJzY2FsZSgxLC0xKSIgLz4KICAgIDxjaXJjbGUKICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuMjY0NTgzIgogICAgICAgaWQ9ImNpcmNsZTI0IgogICAgICAgY3g9IjU4LjIyNTg4IgogICAgICAgY3k9Ii0xNDMuMTk5OTgiCiAgICAgICByPSIxLjgyNjU2ODIiCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDEsLTEpIiAvPgogICAgPGNpcmNsZQogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yNjQ1ODMiCiAgICAgICBpZD0iY2lyY2xlMjUiCiAgICAgICBjeD0iNzIuMjQ4ODEiCiAgICAgICBjeT0iLTE0My4xOTk5OCIKICAgICAgIHI9IjEuODI2NTY4MiIKICAgICAgIHRyYW5zZm9ybT0ic2NhbGUoMSwtMSkiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yNjQ1ODMiCiAgICAgICBkPSJNIDQ0LjIwMjk1MywxNDMuMjAwMzIgSCA3Mi42OTc0MTYiCiAgICAgICBpZD0icGF0aDI1IiAvPgogICAgPGNpcmNsZQogICAgICAgaWQ9ImNpcmNsZTI2IgogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MC4yNjQ1ODMiCiAgICAgICBjeD0iNzIuNjk3NDE4IgogICAgICAgY3k9Ii0xNDMuMTk5OTgiCiAgICAgICByPSIwLjc5Mzc0OTk5IgogICAgICAgdHJhbnNmb3JtPSJzY2FsZSgxLC0xKSIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDojMDAwMDAwO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDowLjI2NDU4MztzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0ibSA0NC4yMDI5NTMsMTQzLjIwMDMyIGggMjguMTI5MTUiCiAgICAgICBpZD0icGF0aDI2IiAvPgogICAgPGNpcmNsZQogICAgICAgaWQ9ImNpcmNsZTI3IgogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MC4yNjQ1ODMiCiAgICAgICBjeD0iNzIuMzMyMTA4IgogICAgICAgY3k9Ii0xNDMuNTY1MjYiCiAgICAgICByPSIwLjM5Njg3NDk5IgogICAgICAgdHJhbnNmb3JtPSJzY2FsZSgxLC0xKSIgLz4KICA8L2c+Cjwvc3ZnPgo=)

Example of Crank Nicholson accuracy vs. Euler#

# prompt: compare the backward euler and crank-nicholson method for 5 time steps using the  previous example. Have the slider no change the time index instead of hte step.

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from ipywidgets import interact, IntSlider

# ... (Your existing code for forward_euler_heat, backward_euler_heat, etc.) ...


def crank_nicholson_heat(T, alpha, dx, dt, num_steps):
  """
  Solves the 1D heat equation using the Crank-Nicholson method.

  Args:
    T: Initial temperature distribution (numpy array).
    alpha: Thermal diffusivity.
    dx: Spatial step size.
    dt: Time step size.
    num_steps: Number of time steps.

  Returns:
    A list of temperature distributions at each time step.
  """

  T_history = [T.copy()]
  r = alpha * dt / (2 * dx**2)

  for _ in range(num_steps):
    A = np.diag(1 + 2 * r * np.ones(len(T)), 0) + \
        np.diag(-r * np.ones(len(T) - 1), -1) + \
        np.diag(-r * np.ones(len(T) - 1), 1)

    b = np.diag(1 - 2 * r * np.ones(len(T)), 0) @ T + \
        np.diag(r * np.ones(len(T) - 1), -1) @ T + \
        np.diag(r * np.ones(len(T) - 1), 1) @ T

    plt.spy(A, markersize=1)  # Creates a sparsity plot
    plt.title('Sparsity Pattern of Matrix A')
    plt.show()

    # Apply boundary conditions (Dirichlet in this example, T=0 at edges)
    b[0] = 0
    b[-1] = 0

    T_new = np.linalg.solve(A, b)
    T = T_new.copy()
    T_history.append(T.copy())

  return T_history


def plot_comparison(time_index):
  """
  Plots the solution of the heat equation using different methods for 5 timesteps.

  Args:
    time_index: Index of the time step to plot.
  """

  # Parameters
  alpha = 0.1
  dx = 0.1
  dt = 0.01
  num_steps = 5
  x = np.arange(0, 1, 1/10)
  T_initial = np.zeros_like(x)
  T_initial[int(len(x) / 2)] = 1

  # Solve the heat equation using different methods
  forward_euler_solution = forward_euler_heat(T_initial, alpha, dx, dt, num_steps)
  backward_euler_solution = backward_euler_heat(T_initial, alpha, dx, dt, num_steps)
  crank_nicholson_solution = crank_nicholson_heat(T_initial, alpha, dx, dt, num_steps)

  # Plotting
  plt.figure(figsize=(8, 6))
  plt.plot(x, forward_euler_solution[time_index], label="Forward Euler")
  plt.plot(x, backward_euler_solution[time_index], label="Backward Euler")
  plt.plot(x, crank_nicholson_solution[time_index], label="Crank-Nicholson")
  plt.xlabel("Position (x)")
  plt.ylabel("Temperature (T)")
  plt.ylim(-.1, 1.1)
  plt.title(f"Comparison of Methods at Time Step {time_index}")
  plt.legend()
  plt.grid(True)
  plt.show()


# Interactive widget
interact(plot_comparison, time_index=IntSlider(min=0, max=4, step=1, value=0));

#Summary

Once again, we have seen the value of implicit timesteppers for solving stiff problems. However, as discussed in the Initial Value Problem section, they require a root finder to converge on a system, which generally relies on linear solvers. For large systems, direct solution (LU) is intractable, and so iterative methods are required.

Explicit timesteppers are conditionally stable which limits the timestep, but they don’t require simultaneous solutions of equations which avoids the pitfalls of a root finder:

  • no question of initial guesses

  • always converges

  • parallelizes well on HPCs

  • much smaller memory usage

  • Does not need an iterative linear solver routine

At what point does the expense of interations in an implicit time stepper outweigh the moderate step size? -> It depends on the problem!

General multiphysics solvers rely on implicit timestepping since modern systems can use LU to root find. Specialized software (build for large systems) may prefer explicit methods which just work:

  • LS-Dyna - impact tests, crashes, drop tests, etc.

  • Several CFD packages (Flow 3D)