Physics · Electromagnetism · advanced
EM Wave Propagation
leapfrog E and B on a staggered grid
Light is E and B regenerating each other. A 1D Yee grid (FDTD) leapfrogs them in time, slows them in glass and blows up past the Courant limit.
Cyan is the electric field Ey (vertical), violet the magnetic field Hz (horizontal), launched from the orange source and absorbed at both ends; the translucent block is a dielectric slab. Raise εr to slow and shorten the wave inside the slab, and push the Courant number past 1 to watch the explicit scheme blow up.
What it is
An electromagnetic wave is a changing electric field that creates a changing magnetic field, which creates the electric field again. In a plane wave E and B are perpendicular to each other and to the direction of travel, in phase, and move at c = 1/√(με).
The finite-difference time-domain (FDTD) method solves Maxwell's curl equations directly. Kane Yee's trick is to store E and H on grids offset by half a cell in space and half a step in time, so every derivative is a centred difference. Each step updates H from the spatial change of E, then E from the spatial change of H: a leapfrog.
The scheme is explicit, so it has a stability limit. The Courant number S = cΔt/Δx must not exceed 1 in 1D (1/√2 in 2D, 1/√3 in 3D); above it, errors grow every step until the grid fills with garbage. Inside a dielectric with permittivity εr the wave slows by √εr, its wavelength shrinks, and part of it reflects at the boundary.
The same leapfrog pattern runs water ripples, audio propagation and sound waves in games; it is the grown-up version of the fixed timestep rule that stability depends on step size.
Key formulas
- Maxwell's curl equations (1D, Ey and Hz)
- Yee update (normalised: c = 1, Δx = 1)
- Courant stability
d = number of spatial dimensions.
- Speed and wavelength in a dielectric
- Mur absorbing boundary (first order)
Rules of thumb
- Resolve the shortest wavelength (inside the densest material) with at least 10–20 cells, or numerical dispersion slows short waves.
- Use S ≈ 0.9 of the limit; in 1D, S = 1 is the 'magic time step' where a pulse moves exactly one cell per step with zero dispersion.
- Ramp a sinusoidal source over 1–2 periods; switching it on instantly launches a broadband spike.
- A soft source (add to E) lets reflected waves pass through it; a hard source (overwrite E) reflects them.
- Without an absorbing boundary the grid ends are perfect mirrors and you get standing waves.
Implementation
export function step(ey: Float64Array, hz: Float64Array, eps: Float64Array,
S: number, src: number, value: number, mur: Float64Array) {
const n = ey.length
mur[0] = ey[0]; mur[1] = ey[1]; mur[2] = ey[n - 1]; mur[3] = ey[n - 2]
for (let i = 0; i < n - 1; i++) hz[i] -= S * (ey[i + 1] - ey[i])
for (let i = 1; i < n - 1; i++) ey[i] -= (S / eps[i]) * (hz[i] - hz[i - 1])
ey[src] += value // soft source
const m = (S - 1) / (S + 1)
ey[0] = mur[1] + m * (ey[1] - mur[0])
ey[n - 1] = mur[3] + m * (ey[n - 2] - mur[2])
}Common mistakes
- Storing E and H at the same grid points and times, which loses the second-order accuracy and the stability of the staggered scheme.
- Picking Δt from the frame rate instead of from the Courant limit; a slow frame then makes S > 1 and the sim explodes.
- Checking stability with the slow speed inside a dielectric. The limit is set by the fastest wave on the grid (vacuum).
- Forgetting an absorbing boundary and mistaking the resulting standing wave for physics.
Prompt
Write a 1D FDTD (Yee) electromagnetic solver in TypeScript for a Three.js visual. Use normalised units (c = 1, Δx = 1) with Float64Array Ey on n nodes, Hz on n−1 half nodes and a per-node relative permittivity array. stepFdtd(grid, S, sourceIndex, source(t)) updates H then E, adds a soft source, applies first-order Mur boundaries at both ends and allocates nothing. Render Ey along y and Hz along z by writing into existing line geometries each frame, clamping non-finite values. Tests: at S = 1 a matched Gaussian pulse moves exactly one cell per step; at S = 0.5 the energy after the pulse exits is < 2% of the start (Mur absorbs); at S = 1.1 energy exceeds 1e6 within 400 steps.
Related
Further reading
Updated 2026-11-01