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!
leapfrog: centered in time and space, second order, explicit
Checked
a standing wave against its exact solution; the measured order; and that the energy does not drift
A string fixed at both ends, plucked into its fundamental: u=sin(πx)cos(πct). Centered differences in both variables give the leapfrog scheme, un+1=2un−un−1+ν2(ui+1−2ui+ui−1), with ν=cΔt/Δx.
Leapfrog has no dissipation at all — its amplification factor has modulus one for ν≤1 — so the string's energy, 21∫(ut2+c2ux2)dx, should neither grow nor decay over many periods. That is the test a diffusive scheme cannot pass.
The first step needs care. Leapfrog wants two earlier levels and the problem gives one, plus an initial velocity; taking u1=u0+21ν2δ2u0 (a Taylor step, with zero velocity) keeps the whole run second order. A plain copy would make it first.
What the run shows
A standing wave over half a period
t = 0t = 1/4t = 1/2t = 3/4t = 1
✓leapfrog matches the standing wave sin(pi x) cos(pi c t)
✓it is second order
✓the energy does not drift over twenty periods
e1
0.00013371
e2
3.34241e-05
order
2.00015
drift
9.18755e-15
Computed when this page was built, by the same interpreter that runs it in your browser: 3 of 3 tests,
0.17 s. The numbers above are the program's show lines.
The program
89 lines, 3 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.
second_difference
The second difference of u, with both ends held at zero.
standing_wave_error
Leapfrog to t_end on n interior points; the L2 error against sin(pi x) cos(pi c t).
energy_drift
The discrete energy after each of `periods` periods, relative to the first.
Viv [1]
pi =3.141592653589793
C =1.0
NU =0.8#| The second difference of u, with both ends held at zero.defsecond_difference(u, n) {
d =-2.0* u
for i to n {
if i >0.0 { d[i] = d[i] + u[i -1.0] }
if i < n -1.0 { d[i] = d[i] + u[i +1.0] }
}
return d
}
#| Leapfrog to t_end on n interior points; the L2 error against sin(pi x) cos(pi c t).defstanding_wave_error(n, t_end) {
h =1.0/ (n +1.0)
steps =round(t_end * C / (NU * h))
dt = t_end / steps
nu = C * dt / h
x =linspace(h, 1.0- h, n)
old =sin(pi * x)
u = old +0.5* nu * nu *second_difference(old, n)
for s to steps -1.0 {
new =2.0* u - old + nu * nu *second_difference(u, n)
old = u
u = new
}
d = u -sin(pi * x) *cos(pi * C * t_end)
returnsqrt(sum(d * d) * h)
}
#| The discrete energy after each of `periods` periods, relative to the first.defenergy_drift(n, periods) {
h =1.0/ (n +1.0)
per_period =round(2.0/ (C * NU * h))
dt =2.0/ (C * per_period)
nu = C * dt / h
x =linspace(h, 1.0- h, n)
old =sin(pi * x)
u = old +0.5* nu * nu *second_difference(old, n)
first =0.0
worst =0.0for p to periods {
for s to per_period {
new =2.0* u - old + nu * nu *second_difference(u, n)
old = u
u = new
}
# energy at the half step: kinetic from (u - old)/dt, potential from the two levels
v = (u - old) / dt
e =0.5*sum(v * v) * h -0.5* C * C *sum(u *second_difference(old, n)) / h
if p ==0.0 { first = e }
worst =max(worst, abs(e - first) / first)
}
return worst
}
e1 =standing_wave_error(50.0, 1.3)
e2 =standing_wave_error(101.0, 1.3)
order =log(e1 / e2) /log(2.0)
drift =energy_drift(50.0, 20.0)
show e1
show e2
show order
show drift
N =80
H =1.0/ (N +1.0)
X =linspace(H, 1.0- H, N)
figure("A standing wave over half a period", "x", "u(x, t)")
plot(X, sin(pi * X), "t = 0")
plot(X, sin(pi * X) *cos(pi *0.25), "t = 1/4")
plot(X, sin(pi * X) *cos(pi *0.5), "t = 1/2")
plot(X, sin(pi * X) *cos(pi *0.75), "t = 3/4")
plot(X, sin(pi * X) *cos(pi), "t = 1")
test"leapfrog matches the standing wave sin(pi x) cos(pi c t)" {
assert_lt(e2, 1.0e-4)
}
test"it is second order" {
assert_near(order, 2.0, 0.1)
}
test"the energy does not drift over twenty periods" {
assert_lt(drift, 1.0e-10)
}
# SPDX-License-Identifier: MIT OR Apache-2.0# SPDX-FileCopyrightText: 2026 The Viv authors