title: "Chapter 4 — Kinematic-Wave Routing" chapter: 4
Chapter 4 — Kinematic-Wave Routing
A flood wave arrives at the downstream gauge minutes before the water molecules that triggered it. Grasping why — and how to compute it — is the foundation of every 1-D flood routing model, including the OPM engine this course is built around.
4.1 — The Saint-Venant Equations
The complete physics of 1-D open-channel flow is captured by the Saint-Venant equations — one continuity law and one momentum law:
The kinematic wave drops terms ①②③ — local acceleration, convective acceleration, and pressure gradient — leaving only friction balanced by gravity: . Combined with Manning's equation this yields the power law , which is the only equation OPM's kinematic router needs per cell.
Click each term to drop it and watch the simplification cascade. Press "Drop Inertia (①②③)" to jump straight to kinematic.
The Saint-Venant Equations
Click any term to understand it — then decide what to keep or drop
① Continuity Equation — Water Conservation
Click any term to understand it (continuity always holds — nothing to drop here):
② Momentum Equation — Newton's 2nd Law for Water
Apply Newton's 2nd law (F = ma) to a water element of length Δx. Forces acting: hydrostatic pressure difference between faces, gravity down the slope, friction opposing flow. After expanding, dividing by ρ·A·Δx, and rearranging:
← Click any term to understand its physical meaning.
Kinematic Wave Derivation (from S_f = S₀)
Drop terms ①②③ to see the kinematic wave emerge.
After dropping ①②③ — remaining balance:
gA·S_f = gA·S₀ → S_f = S₀
The flow is ALWAYS in local equilibrium: the channel instantaneously adjusts to uniform flow. This is the quasi-steady approximation.
Step 1
S_f = S₀
The flow is ALWAYS in local equilibrium: the channel instantaneously adjusts to uniform flow. This is the quasi-steady approximation.
Step 2
Q = (1/n)·A·R^(2/3)·S₀^(1/2)
Manning's equation applies at every cell, at every instant.
Step 3
R ≈ h = A/B
Wide rectangular channel: width >> depth, so hydraulic radius ≈ depth.
Step 4 — Result
Q = α · A^β
β = 5/3 > 1 always → wave celerity c = dQ/dA = (5/3)·u > u → wave outruns water.
α (n=0.04, S₀=0.001, B=10m)
0.1703
β (universal)
1.667
Simplification Hierarchy
| Drop | Keep | Name | Notes |
|---|---|---|---|
| — | ①②③④⑤ | Dynamic wave | Full SVE; tides, dam-break |
| ①② | ③④⑤ | Diffusive wave | Backwater; flat rivers |
| ①②③ | ④⑤ | Kinematic wave | S_f = S₀; used in OPM |
The table on the right shows that dropping ① and ② gives the diffusive wave (keeps backwater); dropping all three gives the kinematic wave (bed-slope-only friction). Every term you drop is an assumption — revisit them in §4.7.
4.2 — Manning's Rating Curve & Wave Celerity
For a wide rectangular channel (, so hydraulic radius ), Manning's equation integrates into the power law:
The wave celerity — the speed at which a small disturbance propagates downstream — is the slope of the rating curve:
Wave speed is always 5/3 times the flow velocity. The water moves at ; the wave front races ahead at .
Adjust the sliders below to explore any channel geometry. The animation at the bottom shows the water particles (blue) lagging behind the wave crest (amber) by exactly that 5/3 ratio. The full step-by-step derivation — plus the physical and geometric intuition for why the wave always outruns the water — is built into the widget below the sliders.
Manning's Rating Curve & Wave Celerity
c = dQ/dA = (5/3)u — the wave always outruns the water
| h | A | Q | u | c | Cr | Status |
|---|---|---|---|---|---|---|
| 0.50 | 5.0 | 2.49 | 0.498 | 0.830 | 0.498 | ✓ Stable |
| 0.80 | 8.0 | 5.45 | 0.681 | 1.135 | 0.681 | ✓ Stable |
| 1.00 | 10.0 | 7.91 | 0.791 | 1.318 | 0.791 | ✓ Stable |
| 1.15 | 11.5 | 9.98 | 0.868 | 1.446 | 0.868 | ✓ Stable |
| 1.50 | 15.0 | 15.54 | 1.036 | 1.727 | 1.036 | ✗ Unstable |
| 2.00 | 20.0 | 25.10 | 1.255 | 2.092 | 1.255 | ✗ Unstable |
| 2.50 | 25.0 | 36.41 | 1.456 | 2.427 | 1.456 | ✗ Unstable |
h (m) | A (m²) | Q (m³/s) | u (m/s) | c=5u/3 | Cr=cΔt/Δx
Rating Curve Q(h) — tangent slope = c·B = dQ/dh
Because the rating curve bends upward (it's convex — Q'' > 0 since β > 1), the tangent at any point is always steeper than the secant from the origin. That's a purely geometric proof that c > u for any convex rating curve, not just this one — the result generalizes to any power law Q = α·A^β with β > 1, not only wide rectangular channels.
Wave crest (amber) vs water particles (blue) — c = (5/3) u
Why Is the Wave Faster Than the Water? — Deriving c = (5/3)u
When the channel is much wider than it is deep, the two short side walls barely add to the wetted perimeter — so the hydraulic radius collapses to just the depth.
A rise in depth does two things at once: (1) there's simply more cross-sectional area to carry water — that's the factor of A; and (2) the water that's already there speeds up, because R^(2/3) grows too — a deeper channel has proportionally less wetted perimeter dragging on the flow, so friction's grip loosens and velocity rises. The two effects compound multiplicatively: A¹ from the extra area, A^(2/3) from the extra speed, giving Q ∝ A^(5/3). Because Q grows faster than A, the marginal rate dQ/dA exceeds the average rate Q/A — and a disturbance (the wave) travels at the marginal rate.
It's the same reason a wider highway lane doesn't just fit more cars — it also lets them drive faster. The marginal car entering downstream moves traffic more than the average flow rate would suggest.
At the default settings (n=0.04, S₀=0.001, B=10 m, h=1.15 m): , m/s, m/s. The Courant column in the table turns red when — that is the stability boundary for explicit schemes.
4.3 — Explicit Finite Differences & the Courant Condition
The kinematic wave equation is a partial differential equation — it links how changes in space and time simultaneously. For a real river, wave speed depends on depth, which changes as the flood evolves. No closed-form solution exists. We must solve it numerically: replace the continuous derivatives with finite ratios on a discrete grid, then march forward one step at a time.
The widget below builds this from absolute scratch — from "what is a finite difference?" to a 3-reach calculation you can verify with a calculator.
From Calculus to Computation
Build the explicit scheme from first principles — then walk through it by hand
Why Can't We Solve This Analytically?
If wave speed c is CONSTANT and q_lat = 0: ∂A/∂t + c·∂A/∂x = 0 Exact solution: A(x, t) = A₀(x − c·t) The initial wave shape just SLIDES downstream at speed c. No numerical methods needed.
For real rivers: c = β·α·A^(β−1) — it DEPENDS ON A. As the flood rises, A grows → c grows → wave accelerates. Different parts of the wave travel at different speeds. No closed-form solution exists for this nonlinear PDE. We must compute it on a discrete grid.
The strategy: divide the river into short cells (Δx = 500 m) and time into short steps (Δt = 300 s), then march forward one step at a time using a simple algebraic update formula.
Replace the River with a Grid
Chop the channel into N = 8 cells of length Δx = 500 m each. Label them i = 0, 1, 2, …, 7. Then divide time into steps of Δt = 300 s. At every grid point (i, t) we store one number: A[i, t], the cross-sectional area at that location and instant.
Goal: fill in the entire table. Given what we know in the green and amber cells, can we systematically compute every white cell? Yes — that's what the explicit scheme does.
What Is a Derivative on a Grid?
Temperature at 8am: 15°C. At 9am: 18°C. Rate of change = (18−15)/1 hr = 3°C/hr. Same idea.
We look UPSTREAM (i−1), not downstream (i+1).
Water flows from i−1 → i → i+1. Information travels WITH the flow — downstream. If we looked at Q[i+1,t], we'd be reading data from a cell the flood hasn't reached yet. Using Q[i−1,t] means we look “into the wind” — toward where the flow is coming from. This is the upwind approximation, and it's both physically correct and numerically stable.
Derive the Update Formula Step by Step
Every ∂ (calculus) is replaced by Δ (arithmetic). No limits, no calculus — just subtraction.
Compute It Yourself — 3 Reaches, 5 Time Steps
| t | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| Q_in (m³/s) | 2 | 10 | 12 | 8 | 2 |
| t \ i | 0 | 1 | 2 |
|---|---|---|---|
| 0 | 4.38 | 4.38 | 4.38 |
| 1 | 11.52 | ? | ? |
| 2 | 12.85 | ? | ? |
| 3 | 10.07 | ? | ? |
| 4 | 4.38 | ? | ? |
| t \ i | 0 | 1 | 2 |
|---|---|---|---|
| 0 | 2.00 | 2.00 | 2.00 |
| 1 | 10.00 | ? | ? |
| 2 | 12.00 | ? | ? |
| 3 | 8.00 | ? | ? |
| 4 | 2.00 | ? | ? |
Rows t=0 and column i=0 are already known. Press "▶ Compute next cell" to start.
The Courant Condition — When C > 1, It Breaks
The explicit scheme advances information exactly one cell-width Δx per time step. But the flood wave travels c·Δt meters per step. If c·Δt > Δx — if the wave moves MORE than one cell per step — the scheme loses track of it.
Think of it like throwing a ball and trying to catch it: if it flies past you in less time than it takes your arm to reach, you can't catch it. The scheme “misses” the wave and produces oscillating, unphysical values.
C = c · Δt / Δx ≤ 1 c = (5/3)·Q/A = wave celerity [m/s] Δt = time step size [s] Δx = cell width [m] At defaults (Q=10, A=11.52): c = 1.446 m/s Max stable Δt = Δx / c = 500 / 1.446 = 345 s Default Δt = 300 s → C = 0.868 ✓
Same upstream hydrograph (2, 8, 12, 8, 4, 2, 2, 2 m³/s), same 3 reaches, same formula — only Δt differs. Press Play and watch reach i=2 (the bottom cell in each strip, furthest downstream) as the flood arrives.
| i | A (m²) | Q (m³/s) |
|---|---|---|
| 0 | 4.38 | 2.00 |
| 1 | 4.38 | 2.00 |
| 2 | 4.38 | 2.00 |
| i | A (m²) | Q (m³/s) |
|---|---|---|
| 0 | 4.38 | 2.00 |
| 1 | 4.38 | 2.00 |
| 2 | 4.38 | 2.00 |
| i | A (m²) | Q (m³/s) |
|---|---|---|
| 0 | 4.38 | 2.00 |
| 1 | 4.38 | 2.00 |
| 2 | 4.38 | 2.00 |
At C > 1, the scheme subtracts more water from a cell than it contains — producing negative (unphysical) areas. The simulation diverges within a few steps. OPM fixes this with a flux limiter (see §4.4).
- Finite differences replace ∂ with Δ
- Upwind scheme uses upstream Q
- Explicit means one unknown per step
- Courant condition C ≤ 1 ensures stability
The interactive simulation below runs this same algorithm across 8 reaches and 12 time steps — every cell visible, every number computed.
The explicit update formula (derived in the widget above):
then evaluate . The scheme is first-order accurate and conditionally stable: wave speed and time step must satisfy
Use the Step Through tab below to watch 12 time steps unfold across 8 reaches. Drag past 346 s to trigger instability. Toggle "Enable Flux Limiter" to see OPM's fix.
Explicit Finite Differences — Step by Step
Watch each reach update, track the Courant number, trigger instability
| i | A (m²) | Q (m³/s) | u (m/s) | c (m/s) | C | Q_lim | Active? |
|---|---|---|---|---|---|---|---|
| 0 | 4.384 | 2.000 | 0.456 | 0.760 | 0.456 | 2.000 | — |
| 1 | 4.384 | 2.000 | 0.456 | 0.760 | 0.456 | 2.000 | — |
| 2 | 4.384 | 2.000 | 0.456 | 0.760 | 0.456 | 2.000 | — |
| 3 | 4.384 | 2.000 | 0.456 | 0.760 | 0.456 | 2.000 | — |
| 4 | 4.384 | 2.000 | 0.456 | 0.760 | 0.456 | 2.000 | — |
| 5 | 4.384 | 2.000 | 0.456 | 0.760 | 0.456 | 2.000 | — |
| 6 | 4.384 | 2.000 | 0.456 | 0.760 | 0.456 | 2.000 | — |
| 7 | 4.384 | 2.000 | 0.456 | 0.760 | 0.456 | 2.000 | — |
= 0.456 × 300 / 500
= 0.274
Seeing the blow-up is not the same as understanding it — and instability is only half the story. The next section derives where the limit actually comes from, and the quieter tax the grid charges even when you obey it.
4.4 — The Computer's White Lie: Numerical Diffusion & the Courant Number
§4.3 showed that the explicit scheme blows up past . This section shows why — and the subtler price you pay even when you stay safely below it. The headline: a pure kinematic wave should translate a flood without changing its shape, yet every grid quietly shrinks and smears the peak.
Where the wave equation comes from
Continuity closed with the rating curve (so with ) collapses to a single advection equation:
The exact solution is just the initial shape slid downstream at speed — sharp stays sharp, the peak is preserved forever. So any flattening your model produces is not physics; it is numerical, and the Courant number controls exactly how much.
The flood peak the grid throws away
The true kinematic wave just slides downstream, shape intact. The upwind grid smears it. The Courant number C decides how much.
CFL_TARGET = 0.85 instead of a tiny step: a sharp, physical peak.The update is a weighted average
Discretize with forward-time, backward-space (upwind — because the water arrives from upstream) and rearrange:
For both weights are positive and sum to one, so the new value is a weighted average of upstream and here — it can never escape the range it started in (bounded stable). At the weight turns negative: no longer an average but an extrapolation, and the errors amplify every step.
Average, or extrapolation? The weight (1−C) decides
The new value is a blend of upstream and here. Two non-negative weights = a safe average. A negative weight = a runaway.
Telephone-game intuition. Pass a message cell to cell. Copy it exactly () and it survives. Blend in a bit of your own value () and it blurs a little each time — that blur is the diffusion. Exaggerate it () and it explodes into nonsense.
What the computer is really solving
Taylor-expand that update around , keep the leading error, then use the equation itself to trade the time error for a space error (). The scheme turns out not to solve pure advection — it solves advection plus a diffusion term nobody wrote down:
That is the heat / diffusion operator — it spreads and flattens gradients. Because , the sign of this artificial diffusion is the sign of :
| Courant | what happens | |
|---|---|---|
| real diffusion → smears / damps the peak (stable, but dissipative) | ||
| pure advection recovered → exact, no smear | ||
| anti-diffusion → sharpens & amplifies → blows up |
The equation the computer actually solves
Six lines of Taylor algebra reveal a hidden diffusion term — and the Courant number sets its sign.
Start from the upwind scheme
Forward in time, backward (upwind) in space: (Qᵢⁿ⁺¹ − Qᵢⁿ)/Δt + c·(Qᵢⁿ − Qᵢ₋₁ⁿ)/Δx = 0
Dashed grey = the original pulse (C=1, untouched). Solid = after the numerical diffusion α acts: spreads when C<1, blows up when C>1.
The counterintuitive trap. Since , on a fixed grid shrinking pushes and drives up toward its maximum . A smaller, "safer" time step makes the smearing worse, not better — the only cures are a finer or pushing . This is exactly why OPM targets (CFL_TARGET) instead of a tiny step, and why config.py warns that a static s "over-diffuses (damps/smears the peak)."
Grid speed vs. water speed
The upwind stencil reaches exactly one cell upstream per step, so information cannot travel faster than the grid celerity . The Courant number is the ratio of the two wave speeds — how many cells the flood crosses in one step:
Trace the characteristic back from the node you are solving: the water there came from upstream. Land on a node () and the answer is exact; land between nodes () and the scheme linearly interpolates a curved profile — cutting the corner, shaving the peak, which is the numerical diffusion you just derived; land outside the stencil () and the formula reaches for data it does not have instability.
Can the wave outrun the grid?
C = how many cells the wave crosses per step. Trace it back to where the water came from — and see why missing the node smears the peak.
C = c ÷ (Δx/Δt) = wave speed ÷ grid speed. Keep the wave from out-running the grid: C ≤ 1.
Why erupts into oscillations
You watched a peak both collapse and spike when — let's see why it flickers like that instead of just growing. The grid carries information one cell per step (the stencil reaches one cell upstream). When the wave travels more than one cell per step, leaping past node into a cell the update cannot see; forced to rebuild that value from the only two nodes it has, the scheme stops interpolating and starts extrapolating, overshooting to the wrong side.
To see the runaway in its purest form, feed the update the jaggediest pattern a grid can hold — a sawtooth, where every cell is the mirror image of its neighbour, . The whole update collapses to a single multiply:
So each step the entire zigzag is just scaled by one number, :
| each step the zigzag… | meaning | ||
|---|---|---|---|
| vanishes instantly | diffusion crushes the wiggles | ||
| flips sign, same size | neutral — the exact knife-edge | ||
| flips sign and grows ×2.2 | oscillation + blow-up |
A negative means every cell swaps sign each step — high low high — which is the oscillation; means each swing is bigger than the last. That is why, once , a peak doesn't merely grow: it flickers up and down while exploding. Press ▶ below and compare the creeping, smooth spread at small with the growing sawtooth past .
When the wave outruns the grid
One tick, one cell — that's all the grid can follow. Watch what happens when the wave tries to cross more.
4.5 — Adaptive Δt: Global vs. Local Time Stepping (and How to Keep It Physical)
§4.4 left us on a knife-edge: explodes, over-smears, and only is both stable and sharp. But the wave speed is not a constant — it changes from cell to cell (steep headwater vs. flat valley) and from moment to moment (rising vs. falling limb). No single fixed can hold everywhere, for all time. The fix is to let the flow choose the step: adaptive time stepping.
How adaptive Δt works (what OPM does)
Each step, find the fastest wave anywhere on the wet grid and size so its Courant number lands on the target:
\Delta t^{n} = C_\text{target}\cdot\frac{\Delta x}{c_\max^{n}}, \qquad c_\max^{n} = \max_i c_i^{n}OPM uses , with a floor/ceiling and a growth limiter (Δt may shrink instantly but grow by at most 1.5× per step, so a sudden drop in c_\max can't dump a volume pulse). In the real source it is one line — a max over the whole domain:
inv_dt = c_1d / dx # per-cell 1/Δt requirement
inv_dt_max = float(inv_dt.max()) # ← the single fastest cell wins
dt_new = cfl_target / inv_dt_max
That max is the whole story: one for every cell, dictated by the one fastest cell. This is global (synchronous) adaptive stepping.
Global vs. Local
| Global (what OPM uses) | Local (per-cell, "LTS") | |
|---|---|---|
| Δt rule | one \Delta t = C_\text{target}\,\Delta x/c_\max | each cell its own |
| Courant reached | only at the fastest cell | everywhere |
| Slow cells | run at → over-diffuse | stay at → minimal diffusion |
| Sync / conservation | trivial (all cells same time level) | must match fluxes across Δt interfaces |
| Cost | wastes tiny steps on slow cells | each cell stepped only as often as needed |
The hidden tax of going global
Under the one global , a cell's actual Courant number is
C_i = \frac{c_i\,\Delta t}{\Delta x} = C_\text{target}\cdot\frac{c_i}{c_\max}.Only the single fastest cell runs at ; a flat valley cell with c_i\ll c_\max runs at . Recall from §4.4 that the smear a peak collects crossing a reach of length is — it depends on and geometry, not on how fast the wave is. So at the steep cell the factor is , while at the flat cell it is :
The flat reaches over-damp the flood peak ~6× harder than the cell that set the timestep — purely because the global forced them far below their own stability limit. It is the grid attenuating the peak, not the physics.
This is most damning for the kinematic wave, whose true attenuation is zero — every bit of flat-reach peak loss is a grid artifact. For the diffusive wave there is real attenuation (), but the global stacks uncalibrated numerical diffusion on top, and since and occupy the same slot, the model can no longer tell you which fraction of a flat-valley hydrograph's attenuation is real. (Chapter 5 §5.5 puts OPM's true m numbers on this: the flat cell's is 164× smaller than the steep cell's, and its reaches of .)
One clock for the whole river — or one each?
A global Δt is set by the single fastest cell. Watch what that does to the slow, flat reaches.
- flat reach runs at C=0.08 — 6.2× the numerical diffusion of the steep cell that set Δt
- peak kept: 71% — yet the true kinematic peak is 100%, so 29% of the loss is a grid artifact, not physics
- flat reach is integrated 11× more often than its accuracy needs
✓ GPU-ideal. Every cell does the identical update at the identical time — perfect SIMT lockstep, one kernel. This is exactly why OPM uses it.
Can a GPU do these? (the architecture answer)
This is the real reason OPM went global. A global has every cell doing the identical update at the identical time — perfect SIMT lockstep, one kernel, coalesced memory, no divergence. It is the GPU's happy path (and fine on CPU).
Local stepping is the opposite. Different cells take different numbers of steps at different times, which on a GPU means warp divergence (threads in a warp branch apart), load imbalance (a fast cell's thread does 10× the work of a slow one in the same launch), irregular synchronization, and conservative flux matching across interfaces (atomic "reflux"). On a CPU none of that hurts — irregular scheduling and event queues are natural, and LTS is a classic CPU speed-up for heterogeneous hydraulics. The GPU-tractable compromise is block / class LTS: bin cells into a few power-of-two classes, give each class its own kernel, sub-cycle the fast classes, and reconcile fluxes at synchronization points — coarser than per-cell, but it keeps each kernel in lockstep.
How to keep it physical
Four routes, from cheapest-to-bolt-on to most general:
- Local / block time stepping — hold every cell at so the numerical diffusion is uniform and minimal; the flat reaches stop over-damping. Direct cure, GPU-costly (above), needs conservative inter-Δt coupling.
- Higher-order / TVD spatial flux (Lax–Wendroff, MUSCL + a limiter: minmod / van Leer / superbee) — cancels the leading diffusion, leaving error that barely depends on . So even a small- flat cell stops over-smearing — while keeping the GPU-friendly global . For a GPU code this is usually the best value.
- Muskingum–Cunge — make the numerical diffusion be the physics. Instead of fighting , calibrate it: choose the weighting so the scheme's numerical diffusivity equals the hydraulic diffusivity . Then the "artificial" diffusion is no longer artificial — it is exactly the attenuation a diffusive wave should have. The most physically honest answer.
- Implicit (Preissmann θ) — unconditionally stable, so is chosen for accuracy, not CFL; the fast cell no longer forces a tiny global step. But on OPM's branching DAG the confluences break the cheap bidiagonal solve (Chapter 5 §5.6), it needs a general sparse solver at – cells, and OPM's own implicit attempt was removed for divergence.
Verdict. Global explicit is the right GPU call, but it leaves the flat-reach diffusion on the table. The cheapest physics-preserving upgrade that stays on the GPU is a higher-order/TVD flux (route 2); the most physically honest is Muskingum–Cunge matched diffusion (route 3); the most general is block/class LTS (route 1) — none of which require the full implicit rewrite of route 4.
4.6 — Beating the CFL Limit: Implicit Schemes & Flux Limiters
When must be large (computational cost, large ), two strategies break the CFL barrier:
Preissmann θ-implicit scheme — weight spatial differences at the new time level by and at by :
This produces a lower-bidiagonal linear system solved by forward substitution (Thomas algorithm). Unconditionally stable for ; is the Preissmann industry standard (HEC-RAS, ISIS, SWMM).
OPM flux limiter — no matrix, no extra storage. Simply cap each cell's outflow:
Both keep the simulation stable at s (C ≈ 1.16). Explore the trade-offs — the matrix display in Tab 1 shows actual numerical values; the table in Tab 2 highlights exactly which cells hit the limiter. Tab 1 also walks through what "implicit" means and animates the Thomas-algorithm forward sweep row by row, using the same numbers shown in the matrix.
Beating the CFL Limit
Preissmann implicit scheme vs. OPM flux limiter — same C>1 problem, different solutions
Each future value depends only on past values — already known. Compute left→right, one at a time.
Each future value also depends on its neighbor's future value — none are known until ALL are solved together.
Analogy: explicit is a relay race — each runner starts only once the previous one has arrived. Implicit is a small group writing a joint statement — nobody finalizes their sentence until everyone's sentences are mutually consistent.
Why coupling prevents blow-up: an explicit step extrapolates forward from the past — if Δt is too large, the extrapolation overshoots, and that overshoot compounds every subsequent step (the same runaway oscillation you saw with the explicit scheme once C>1). An implicit step does not extrapolate; it solves for the one set of future values that satisfies every reach's equation simultaneously. There is no overshoot to compound — a larger Δt only costs accuracy (more numerical diffusion), never stability.
| row | A1 | A2 | A3 | A4 | A5 | A6 | RHS (d) | Aⁿ⁺¹ |
|---|---|---|---|---|---|---|---|---|
| 1 | 0.003 | 0 | 0 | 0 | 0 | 0 | 0.014 | 8.982 |
| 2 | −0.001 | 0.003 | 0 | 0 | 0 | 0 | 0.009 | 5.824 |
| 3 | 0 | −0.001 | 0.003 | 0 | 0 | 0 | 0.009 | 4.835 |
| 4 | 0 | 0 | −0.001 | 0.003 | 0 | 0 | 0.009 | 4.525 |
| 5 | 0 | 0 | 0 | −0.001 | 0.003 | 0 | 0.009 | 4.428 |
| 6 | 0 | 0 | 0 | 0 | −0.001 | 0.003 | 0.009 | 4.398 |
Nothing solved yet — row 1 only needs the boundary value (already known), so it can go first. Press “▶ Next row” to reveal each step of the sweep.
4.7 — Kinematic vs. Diffusive Wave
The kinematic wave translates a flood pulse without changing its shape — peak discharge is perfectly preserved. Real floods don't behave that way: they flatten as they travel, and they back up behind anything that obstructs them. The diffusive wave fixes both by trading the bed slope for the water-surface slope, which lets the wave attenuate and feel backwater. A quick preview, dragging the blend weight from 0 (pure kinematic) to 1 (OPM's default, full diffusive):
Kinematic vs. Diffusive Wave
θ_diff controls attenuation — kinematic translates, diffusive spreads
8-Reach Channel — t = 0 / 11
Outlet Hydrograph (reach i = 7)
Outlet Statistics
| Scheme | Peak Q (m³/s) | Peak at t= | Attenuation | Mass (m³) |
|---|---|---|---|---|
| Kinematic | 2.3 | 11 | 0% | 7293 |
| θ=0.00 | 2.3 | 11 | 0.0% | 7293 |
| Diffusive | 1936.9 | 11 | -85388.7% | 588248 |
Routing Scheme Limitations
| Limitation | Kinematic | Diffusive | Dynamic |
|---|---|---|---|
| No backwater effects | ✗ blind | partial | ✓ |
| Hydrograph attenuation | ✗ zero | ✓ some | ✓ full |
| Flat reaches (S→0) | ✗ fails | ~ marginal | ✓ |
| Computational cost | O(N) | O(N) | O(N log N) |
| Valid when | S₀ > 0.001 | S₀ > 0.0001 | always |
ROUTING_SCHEME = 'kinematic' → θ_diff = 0
ROUTING_SCHEME = 'diffusive' → uses DIFFUSION_THETA
DIFFUSION_THETA = 1.0 (full diffusion by default)
| Limitation | Kinematic | Diffusive | Dynamic |
|---|---|---|---|
| No backwater effects | ✗ blind | partial | ✓ |
| Hydrograph attenuation | ✗ zero | ✓ some | ✓ full |
| Flat reaches () | ✗ fails | ∼ marginal | ✓ |
| Computational cost | |||
| Valid when | always |
Chapter 5 is entirely about this — a real example of the kinematic wave giving the wrong answer, the diffusive equation derived from scratch, OPM's actual GSSHA-style production code walked through line by line, and what running it all under one global timestep costs on flat terrain.
4.8 — Explore Channel Routing
Try the four presets — Mountain Torrent gives small Courant numbers and sharp wave translation; Lowland River pushes Courant toward instability with the large needed for long low-slope channels. Click any reach to pin its time series on the right chart and watch peaks arrive with a lag of .
Explore Channel Routing
Adjust parameters, pick presets, watch the flood wave propagate
4.9 — For Grad Students
Method of Characteristics (MOC) — the kinematic wave equation is hyperbolic; along the characteristic the solution reduces to an ODE. The CFL condition is geometric: the numerical grid must contain the characteristic within one cell per step.
Modified equation & von Neumann (§4.4 in full rigor) — the upwind scheme's leading truncation error is a diffusion term: it actually solves with . The Fourier (von Neumann) amplification factor gives , so — the CFL bound, with the high-wavenumber damping being the spectral fingerprint of that same .
Lax-Wendroff (2nd order) — adds a correction term to the upwind scheme that cancels the numerical diffusion to leading order, at the cost of mild dispersive overshoot near discontinuities.
Muskingum-Cunge — analytically equivalent to the linear kinematic finite-difference scheme; parameters (travel time) and (shape factor) match diffusive-wave attenuation exactly when chosen correctly.
OPM flux limiter derivation — is equivalent to capping the effective Courant number at 1 for each cell independently, introducing numerical diffusion; the global mass balance closes to floating-point precision.
GPU vectorisation — topological sort converts the 2-D routing problem to a 1-D array operation; CuPy scatter_add propagates all cell discharges to downstream neighbours in a single vectorised kernel, achieving steps/s on cells.
Adaptive (§4.5 in full) — OPM uses global adaptive stepping, () with a 1.5× growth limiter — recomputed each step from the fastest wet cell. Local (per-cell) stepping would hold everywhere and kill the flat-reach over-diffusion, but breaks SIMT lockstep; the GPU-tractable middle ground is block/class LTS (power-of-two bins). Physics-preserving alternatives without LTS: a TVD/higher-order flux (removes the term, keeps global ) or Muskingum–Cunge matched diffusion ().
Complexity summary:
| Algorithm | Complexity | Notes |
|---|---|---|
| KW explicit (CPU) | one pass per step | |
| Preissmann implicit | banded solve, larger | |
| GPU explicit | effective | parallelised |
| Full SVE solver | iterative Newton per step |
Summary
| Concept | Formula | Key point |
|---|---|---|
| Kinematic assumption | Drop ①②③ from momentum | |
| Power law | , | Manning + wide rectangular |
| Wave celerity | Wave always outruns water | |
| Courant number | Stability limit for explicit | |
| Upwind update | Weighted average; at → blow-up | |
| Numerical diffusion | smears, exact (), explodes | |
| The trap | smaller smaller larger | Tiny step over-smears; aim , not |
| Global adaptive Δt | \Delta t = C_\text{target}\Delta x/c_\max (one for all) | GPU-ideal, but slow cells get → over-diffuse |
| Keeping it physical | local/block LTS · TVD flux · Muskingum–Cunge | uniform , or kill / calibrate |
| Flux limiter | OPM's CFL-free strategy | |
| Preissmann | → unconditionally stable | Industry standard |
| Attenuation | Kinematic: 0%; Diffusive: | Controlled by |