Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Step 1 — 1D Linear Convection

Authors
Affiliations
University of Valencia

13 Steps to Navier-Stokes with somax (inspired by Lorena Barba’s CFD Python)

The linear convection equation is the simplest PDE in computational fluid dynamics. It describes how a disturbance propagates through a medium at a constant speed without changing shape (in the continuous limit). Mastering this equation gives you the foundation for every subsequent step.

What you’ll learn:

  1. The physics of linear wave propagation and the CFL condition

  2. How to set up a 1D PDE model in somax

  3. How to compare numerical output against the analytical solution

  4. How to differentiate through the simulation with eqx.filter_grad

The PDE

∂u∂t+c∂u∂x=0\frac{\partial u}{\partial t} + c \frac{\partial u}{\partial x} = 0

where cc is the constant wave speed. The exact solution for an initial condition u(x,0)=u0(x)u(x, 0) = u_0(x) is simply:

u(x,t)=u0(x−ct)u(x, t) = u_0(x - ct)

The initial profile translates to the right at speed cc without distortion. This is pure advection — no diffusion, no steepening.

The CFL condition

The Courant-Friedrichs-Lewy (CFL) number νCFL=c Δt/Δx\nu_{\mathrm{CFL}} = c \, \Delta t / \Delta x must satisfy νCFL≤1\nu_{\mathrm{CFL}} \le 1 for explicit upwind schemes. If the numerical domain of dependence does not contain the physical domain of dependence, the scheme is unstable.

Grid layout and boundary conditions

somax uses the Arakawa C-grid from finitevolX. In 1D, scalar fields (like uu) live at T-points (cell centres) and fluxes are computed at U-points (cell edges):

 ghost                    interior                     ghost
 ┌─────┬──────┬──────┬──────┬──────┬──────┬──────┬─────┐
 │  G  │  T₁  │  T₂  │  T₃  │ ···  │ Tₙ₋₁│  Tₙ  │  G  │
 └──┬──┴──┬───┴──┬───┴──┬───┴──┬───┴──┬───┴──┬───┴──┬──┘
    U₀    U₁     U₂     U₃           Uₙ₋₁    Uₙ    Uₙ₊₁
         ←── dx ──→
  • T-points (indices 1:-1): where u is stored and updated

  • U-points: where Difference1D computes derivatives

  • Ghost cells (indices 0 and -1): filled by BCs before each RHS evaluation

Periodic BCs copy the last interior value into the opposite ghost cell:

 u[0] = u[-2]      (left ghost ← rightmost interior)
 u[-1] = u[1]      (right ghost ← leftmost interior)

This makes the domain wrap around so waves that exit one side re-enter from the other.

1. Create the model

LinearConvection1D.create() builds a model on a 1D Arakawa C-grid with nx interior cells spanning a domain of length Lx. The wave speed c is a differentiable parameter visible to jax.grad.

Grid: Nx=202 (includes 2 ghost cells), dx=0.0200
Wave speed c = 1.0

2. Initial condition

We use a Gaussian bump centered at x=1.0x = 1.0 with standard deviation σ=0.2\sigma = 0.2. The state array has shape (Nx,) which includes the two ghost cells that enforce periodic boundary conditions.

<Figure size 800x300 with 1 Axes>

3. Forward simulation

We integrate from t=0t = 0 to t=0.5t = 0.5 using model.integrate(). The method wraps diffrax.diffeqsolve and automatically applies periodic boundary conditions at every RHS evaluation.

Trajectory shape: u=(6, 202)
CFL number: c * dt / dx = 0.100

4. Visualize the wave propagation

The Gaussian translates to the right at speed c=1c = 1. After t=0.5t = 0.5 it should have moved by Δx=0.5\Delta x = 0.5.

<Figure size 1000x400 with 1 Axes>

5. Comparison with the analytical solution

The exact solution is u(x,t)=u0(x−ct)u(x, t) = u_0(x - ct). With periodic BCs the shifted coordinate wraps around the domain.

<Figure size 1200x400 with 2 Axes>
Max error: 0.005749
L2 error:  0.003391

6. Diagnostics — energy conservation

For the linear convection equation with periodic BCs, the total energy E=12∫u2 dxE = \frac{1}{2} \int u^2 \, dx is conserved analytically. Numerical dissipation causes a slow decay.

<Figure size 800x300 with 1 Axes>
Energy: initial = 0.177245, final = 0.177245
Relative change: 0.0000%

7. Differentiability demo

One of the key advantages of somax is that every simulation is end-to-end differentiable. We define a loss function and compute ∂L/∂c\partial \mathcal{L} / \partial c — the gradient of the loss with respect to the wave speed parameter.

--- Gradient w.r.t. model parameters ---
  dL/d(c) = -0.001377

The gradient tells us how to adjust the wave speed to move
the final profile closer to the target location x = 1.5.

Joint gradient -- parameters and initial state

We can simultaneously compute gradients with respect to both the wave speed cc and the initial condition u0u_0. This is the foundation for bi-level optimisation (e.g. weak-constraint 4D-Var).

--- Joint gradient ---
  dL/d(u0) max = 2.0000
  dL/d(c)      = -0.0000

Comparing adjoint methods

diffrax provides several adjoint methods for differentiating through ODE solves. RecursiveCheckpointAdjoint (the default) gives exact gradients with O(N)O(\sqrt{N}) memory. DirectAdjoint stores the full trajectory (O(N)O(N) memory) but can be faster for short integrations.

--- Adjoint method comparison ---
  RecursiveCheckpoint                  dL/d(c)=-0.0000  (4.1335s)
  Direct                               dL/d(c)=-0.0000  (3.0787s)

Ensemble simulation with jax.vmap

We can run an ensemble of simulations with different initial conditions to explore sensitivity efficiently.

<Figure size 1000x400 with 1 Axes>

Summary

Conceptsomax API
Create modelLinearConvection1D.create(nx=200, Lx=4.0, c=1.0)
Initial stateLinearConvection1DState(u=...)
Forward simmodel.integrate(state0, t0, t1, dt, saveat=...)
Diagnosticsmodel.diagnose(state)
Grad w.r.t. paramseqx.filter_grad(loss)(model)

Key takeaway: Linear convection translates a signal without distortion. The CFL condition cΔt/Δx≤1c \Delta t / \Delta x \le 1 governs stability.

Next: Step 2 — Nonlinear Convection introduces amplitude-dependent wave speed and shock formation.