This site is version 0.0.1. Most of the code on this site is drafted by an agent and needs review. I apologize
for the AI-speak, we are working on rewriting it!
upwind (first order) and Lax-Wendroff (second order), on a periodic grid
Checked
that one trip around the ring returns the pulse to where it started; and both measured orders
The simplest wave equation: whatever shape you start with slides along at speed c, unchanged. On a ring of length 1 it is back where it began after a time 1/c, which makes the exact answer the initial condition itself.
It is also the equation that teaches humility about finite differences. Upwind takes the difference from the side the wave is coming from; it is stable for ν=cΔt/Δx≤1 and first order, and what it does to the error is diffuse it: the pulse spreads and sinks. Lax-Wendroff adds the second-order term of the Taylor series in time, 21ν2(ui+1−2ui+ui−1), and is second order: the pulse keeps its height and grows a small oscillating wake instead.
Periodic boundaries are where roll earns its place: a neighbor is a circular shift, and each scheme is one line.
What the run shows
One trip around the ring
exact (the starting pulse)upwind: it diffusesLax-Wendroff: it keeps its height
✓upwind is first order (measured between n = 1600 and 3200: it gets there slowly)
✓Lax-Wendroff is second order
✓at the same resolution Lax-Wendroff is more than ten times closer
order_upwind
0.972834
order_lw
1.99629
up_coarse
0.0332305
lw2
0.00107067
Computed when this page was built, by the same interpreter that runs it in your browser: 3 of 3 tests,
0.23 s. The numbers above are the program's show lines.
The program
73 lines, 4 functions. It is editable: change it and press Run (or
⌘/Ctrl + Enter). The tests at the bottom run too, so if your change breaks the solver, a test fails.
pulse
A smooth pulse on the ring.
upwind_lap
Carry the pulse once around the ring with the upwind scheme; return what comes back.
lax_wendroff_lap
The same trip with Lax-Wendroff.
lap_error
L2 distance between what came back and what set out.
Viv [1]
pi =3.141592653589793
C =1.0
NU =0.5#: NU the Courant number c dt / dx#| A smooth pulse on the ring.defpulse(x) {
returnexp(-100.0* (x -0.5) * (x -0.5))
}
#| Carry the pulse once around the ring with the upwind scheme; return what comes back.defupwind_lap(n) {
x =linspace(0.0, 1.0-1.0/ n, n)
u =pulse(x)
steps = n / (C * NU)
for s to steps {
u = u - NU * (u -roll(u, 1.0))
}
return u
}
#| The same trip with Lax-Wendroff.deflax_wendroff_lap(n) {
x =linspace(0.0, 1.0-1.0/ n, n)
u =pulse(x)
steps = n / (C * NU)
for s to steps {
right =roll(u, -1.0)
left =roll(u, 1.0)
u = u -0.5* NU * (right - left) +0.5* NU * NU * (right -2.0* u + left)
}
return u
}
#| L2 distance between what came back and what set out.deflap_error(u, n) {
x =linspace(0.0, 1.0-1.0/ n, n)
d = u -pulse(x)
returnsqrt(sum(d * d) / n)
}
# upwind reaches its asymptotic order slowly, so it is measured on finer grids than it is drawn on
up1 =lap_error(upwind_lap(1600.0), 1600.0)
up2 =lap_error(upwind_lap(3200.0), 3200.0)
up_coarse =lap_error(upwind_lap(400.0), 400.0)
lw1 =lap_error(lax_wendroff_lap(200.0), 200.0)
lw2 =lap_error(lax_wendroff_lap(400.0), 400.0)
order_upwind =log(up1 / up2) /log(2.0)
order_lw =log(lw1 / lw2) /log(2.0)
show order_upwind
show order_lw
show up_coarse
show lw2
N =200
X =linspace(0.0, 1.0-1.0/ N, N)
figure("One trip around the ring", "x", "u")
plot(X, pulse(X), "exact (the starting pulse)")
plot(X, upwind_lap(N), "upwind: it diffuses")
plot(X, lax_wendroff_lap(N), "Lax-Wendroff: it keeps its height")
style("exact (the starting pulse)", "dash")
test"upwind is first order (measured between n = 1600 and 3200: it gets there slowly)" {
assert_near(order_upwind, 1.0, 0.05)
}
test"Lax-Wendroff is second order" {
assert_near(order_lw, 2.0, 0.15)
}
test"at the same resolution Lax-Wendroff is more than ten times closer" {
assert_lt(lw2 *10.0, up_coarse)
}
# SPDX-License-Identifier: MIT OR Apache-2.0# SPDX-FileCopyrightText: 2026 The Viv authors