Validation¶
waveray is validated against the parent model it replaces. The test is strict: offshore spectra from a SWAN hindcast become the boundary condition, interior SWAN output sites are held out as truth, and the ray-traced operator transforms the boundary spectra to those sites. waveray never sees SWAN's answer at the truth site.
Both studies are reproducible notebooks in notebooks/.
Dutch coast — smooth sandy shoreface¶
Source: oceanum_wave_dutch_era5_v1_spec (SWAN 1 km, hourly, ERA5-forced),
bathymetry GEBCO 2023 (the same generation the parent model used). Period
15 Dec 2023 – 15 Jan 2024, spanning storms Pia and Henk (745 hourly
timesteps). Six boundary sites on an offshore arc; the held-out truth site sits
in 9.8 m of water.
| Metric | Value |
|---|---|
Correlation r |
0.992 |
| Bias | −0.06 m |
| RMSE | 0.10 m |
| Scatter index | 0.06 |
SWAN mean Hs |
1.61 m |
| Operator build | 13.8 s (one-off) |
| Throughput | 745 spectra in 105 ms (~7,100 spectra/s) |
A gently sloping sandy shoreface with smooth depth contours, no reefs and no diffracting structures is close to the ideal case for ray theory.
Abrolhos, Western Australia — reef-fronted coast¶
Source: oceanum_wave_ec_abrol500m_spec_nowcast (SWAN 500 m). Bathymetry GEBCO
2025. Five boundary sites on the western edge; three held-out interior sites.
385 timesteps.
| Truth site depth | r |
Bias | RMSE | SI |
|---|---|---|---|---|
| 4.3 m | 0.944 | +0.36 m | 0.60 m | 0.35 |
| 8.5 m | 0.925 | +0.18 m | 0.48 m | 0.22 |
| 10.5 m | 0.980 | −0.98 m | 1.05 m | 0.41 |
Correlation stays high, but the biases are larger than on the Dutch coast. Two causes, both structural:
- Bathymetry mismatch. The operator is fed GEBCO (~450 m); the parent SWAN model was built on the 250 m Australian Bathymetry and Topography grid, which resolves reef structure GEBCO smooths away. The worst site (−0.98 m, behind the Point Moore reefs) is the most sheltered one — exactly where unresolved bathymetry hurts most.
- Binary blocking. Rays either ground on a reef or pass; SWAN transmits energy partially across it.
Bottom friction is the leading correctable error¶
On the Abrolhos shelf, ablating JONSWAP bottom friction at the 8.5 m site:
| Configuration | r |
Bias | RMSE |
|---|---|---|---|
| Friction off | 0.940 | +1.11 m | 1.32 m |
Friction on (cf_jonswap=0.038) |
0.925 | +0.18 m | 0.48 m |
Over long shallow approaches, friction removes real energy. This is why it is on by default. Note the correlation barely moves while the bias collapses: friction is a systematic energy sink, not a timing correction.
Analytic validation¶
Beyond model-vs-model comparison, the operator is checked against closed-form
solutions, because linear ray theory has them (tests/):
- Snell refraction and energy-flux shoaling on a plane beach — the transfer
coefficient matches the analytic
Ks²·Kr²to within 5–7% - Flat bathymetry is the identity — every direction passes through with
T = 1.000 - Island sheltering — directions whose rays cross an island give exactly
T = 0.000; unobstructed directions give exactlyT = 1.000 - Friction attenuates on long shallow paths and is negligible in deep water
- Integrated parameters agree with wavespectra to 1e-12
Against SWAN 41.51A (docker)¶
The hindcast comparisons above measure waveray against a parent model run by
someone else, so grid, boundary and settings all differ. tests/validation/
removes those differences: it runs stationary SWAN 41.51A in the official
delftwaves/swan image on the same bathymetry, the same spectral grid and the
same boundary spectrum (written directly as a stationary SWAN ASCII spectral
file), so what is left is the transformation itself.
docker pull delftwaves/swan:latest
uv run pytest -m swan -s # excluded from the default suite; takes minutes
SWAN runs with its full physics — quadruplets and whitecapping on — because the question is how good a surrogate waveray is for a real SWAN run, not whether it reimplements a subset of SWAN (the closed-form tests settle that). Depth-induced breaking is the one term left off, since waveray applies it at the target rather than along the path.
| Case | SWAN | waveray | Difference |
|---|---|---|---|
| Plane beach, shore-normal swell, 20→6 m | Hs 1.962→2.147 m | 1.960→2.153 m | 0.31 % |
| Plane beach, 30° oblique — refracted direction | 246.0→256.7° | 246.7→257.4° | < 0.8° (Hs 0.62 %) |
| Real GEBCO bathymetry (Noordwijk, NL) | Hs 2.328→2.078 m | 2.483→2.490 m | 6.7 – 19.8 % |
| Circular island, deep lee | Hs 0.466 m | 0.675 m | ×1.45 |
| Swell + 12 m/s onshore wind, 15 km fetch | Hs 2.176→2.470 m | 2.096→2.686 m | ×0.95 – 1.09 |
| Swell + 12 m/s offshore wind (opposed) | Hs 2.158→2.142 m | 2.025→2.153 m | ×0.88 – 1.01 |
| Wind sea from calm, peak resolved | Hs 0.322→0.644 m | 0.332→0.653 m | ×1.01 – 1.05 |
Reading the table: on the analytic beach the operator is essentially exact — refraction and shoaling are reproduced to a fraction of a percent. On real bathymetry the gap widens shoreward, as the depth field stops being smooth and alongshore-uniform. The island lee is the deep shadow behind a blocking obstacle, where neither model has diffraction and a ray model and a spectral model are entitled to disagree; treat sheltered-lee heights as indicative.
The onshore wind rows are within a few percent, but only because of the wind-sea saturation cap. Without it the same cases run 1.03–1.25× and 2.18–3.25×: wind input is a source with no sink, and the operator amplifies the boundary spectrum's tail without limit. The cap supplies the outcome of the balance it cannot model, and never engages on a swell-only spectrum — which is why the propagation rows above are unaffected by it. It is an empirical closure calibrated against these SWAN runs, not one of SWAN's source terms.
The offshore row is the one structural failure in the table, and it is not a
calibration problem. SWAN raises an offshore-going wind sea over the fetch and
counts it at the target; waveray's Komen cutoff gives no growth to the
shoreward-running rays that reach the target, so it never generates that lobe
at all. Wind direction is covered in full — including the directional spectra
that show the missing lobe — in
notebooks/06_swan_validation_wind_direction.ipynb
and in the wind docs.
Two supporting tests make the point sharper. Strip SWAN's sinks and the cap,
so both models carry wind input alone, and the agreement is tens of percent —
the formulation is right, the balance is what was missing. Do the same on the
15 km swell case and both models run away, SWAN to 77.8 m and waveray to
35.0 m from the same 2 m swell, which is the evidence behind waveray's
max_growth ceiling.
Every SWAN run in the suite is checked for convergence, and rejected if it stopped at the iteration cap or collapsed to zero — a stationary run that did either is not reproducible and cannot serve as a reference.
Interpreting these numbers¶
The honest summary: on a smooth coast waveray reproduces its parent SWAN model
to within a few centimetres, at ~10,000 spectra per second. On a complex reef
coast it tracks the parent model's variability well (r ≈ 0.93–0.98) but
carries a bias that is dominated by the bathymetry you feed it, not by the
method. Feed it the bathymetry the parent model used, turn friction on, and
tune gamma against observations before trusting absolute heights at a new
site.