Skip to content

Breaking waves

EqGPT discovered a law for breaking-wave surface elevation from 12 wave-tank experiments (Xu et al., Nat Commun 16, 10255, 2025). KD hosts EqGPT as a search plugin and reproduces that published result, with permission of the authors, on the same scattered (t, x, eta) measurements:

\[\eta_t + c_1\,\eta_x + c_2\,\eta_{xxx} + c_3\,(\eta\,\eta_x)_{xx} = 0\]

In KD's notation the surface elevation is the field u, so the recovered right-hand side is the term set {u_x, u_xxx, diff2_x(mul(u, u_x))} with per-experiment coefficients.

import kd
from kd.core.expr import to_sympy
from kd.data.loaders import load_wave_breaking_datasets

Optional data preview

WaveBreaking.pkl holds surface-elevation records from the BUBER and EURUS campaigns in the 27.2 m glass-walled wave tank at Imperial College London, where the air-water interface was reconstructed frame by frame from three cameras running at 20 Hz; the paper's discovery uses the 12 "N" experiments. Each case is a scattered point cloud (t, x, eta) with no grid, so the dataset is SCATTERED topology. EqGPT fits all 12 cases; the preview below shows the first N case in sorted order. kd.preview describes its axes and field, and kd.viz.plots.plot_field draws the observed surface elevation. These preview calls are optional and do not affect the fit. The later report describes the fitted equation and its diagnostics.

cases = load_wave_breaking_datasets(case_filter="N")
primary_name = next(iter(cases))
primary_ds = cases[primary_name]

print(f"N cases ({len(cases)}): {', '.join(cases)}")
kd.preview(primary_ds)
kd.viz.plots.plot_field(primary_ds)
N cases (12): N_G2Tp12A080_broad, N_G2Tp12A090_broad, N_G2Tp12A100_broad, N_G2Tp12A105_broad, N_G2Tp13A090_broad, N_G2Tp13A096_broad, N_G2Tp13A105_broad, N_G2Tp13A130_broad, N_G3Tp12A083_broad, N_G3Tp12A095_broad, N_G3Tp12A100_broad, N_G3Tp12A117_broad
Dataset: wave-breaking-N_G2Tp12A080_broad
Axes:
     t | n=507868 | range [0.050, 5.250] | step n/a (scattered)
     x | n=507868 | range [8.148, 12.548] | step n/a (scattered)
Fields:
     u | dtype=float64 | shape=(507868,) | min=-0.071 max=0.109 mean=-0.000 (NaN=0)
LHS: u_t  (field='u', axis='t')
Status: ready to fit

png

Multi-case fit

EqGPTConfig.wave_preset pins the paper's setup: multi-case wave mode (case_filter="N"), sparsity_alpha=0.02. Per generation the pretrained GPT proposes candidate right-hand sides; KD scores each candidate on every experiment (per-case surrogate and least-squares fit) and keeps the candidate with the best mean reward over the cases that scored successfully. seed=21 makes the run reproduce the same result each time, through KD's CPU torch RNG stream.

The cell below runs a 5-generation search across all 12 experiments, loading the per-case surrogates and the measurement records.

DEMO_SEED = 21

model = kd.Model(
    algorithm="eqgpt",
    generations=5,
    config=kd.EqGPTConfig.wave_preset(seed=DEMO_SEED, primary_case=primary_name),
).fit(primary_ds)

print(f"Reproduced structure : {to_sympy(model.best_expr_)}")
print(f"Best mean reward     : {model.best_score_:.4f}")
[kd] Generation   1/5 | best EqGPT reward=0.9486 | expr=u_x + u_xxx + (u*u_x)_xx


[kd] Generation   2/5 | best EqGPT reward=0.9486 | expr=u_x + u_xxx + (u*u_x)_xx


[kd] Generation   3/5 | best EqGPT reward=0.9486 | expr=u_x + u_xxx + (u*u_x)_xx


[kd] Generation   4/5 | best EqGPT reward=0.9486 | expr=u_x + u_xxx + (u*u_x)_xx


[kd] Generation   5/5 | best EqGPT reward=0.9486 | expr=u_x + u_xxx + (u*u_x)_xx


[kd] Done. Best: u_x + u_xxx + (u*u_x)_xx (EqGPT reward=0.9486)


Reproduced structure : u_x + u_xxx + (u*u_x)_xx
Best mean reward     : 0.9486

Recovered law vs the published equation

The published structure is eta_t = -c1*eta_x - c2*eta_xxx - c3*(eta*eta_x)_xx (all three RHS terms). The fitted coefficients below are the primary case's least-squares fit in the declared u_t = sum(c * term) domain; every experiment gets its own coefficients (next section).

The coefficient panel shows their signs and magnitudes, with numeric labels when they span several orders of magnitude.

kd.viz.plots.plot_coefficient_bar(model.result_);

png

Render the report

model.report draws the figures this data supports and collects them into one HTML page, displayed below. The page includes notes for figure families that cannot be drawn; the same notes are available as report.warnings.

report = model.report("../out/wave_breaking")
report

Report: ../out/wave_breaking/report.html

Composable figure functions

Every figure in the report can also be drawn separately. The functions in kd.viz.plots create a figure when called without ax; passing an existing Matplotlib Axes lets you combine them in a larger layout. plot_plugin uses the algorithm's own panel names and rendering method.

EqGPT's per-case reward panel has one bar per experiment, with the same discovered structure fitted separately to each case.

kd.viz.plots.plot_plugin(model.algorithm_, "per_case_reward");

png

A platform figure works the same way. plot_parity puts the fitted right-hand side against the measured u_t of the primary case, one point per measurement.

kd.viz.plots.plot_parity(model.result_);

png

Reproduce this run

The run needs pretrained GPT weights, per-case surrogate checkpoints, and the measurement pickle. KD uses local assets when configured, and otherwise resolves the weights and surrogates from its Hub mirror (with permission of the authors). The measurement pickle must already be present.

Asset Location
gpt_model/PDEGPT_wave_breaking.pt KD_EQGPT_ASSET_DIR, or the Hub cache
model_save/wave_breaking/95_0_<case>(Non_unit)/Net_Sin_*.pkl KD_V1_WAVE_ASSETS, or the Hub cache
WaveBreaking.pkl data/hf-knowledgediscover/ under the checkout root

From examples/notebooks/:

uv run --with nbconvert python -m nbconvert --to notebook --execute --inplace wave_breaking.ipynb

To use local weight and surrogate trees, set KD_EQGPT_ASSET_DIR and KD_V1_WAVE_ASSETS to their respective asset directories before running.

Next steps

  • examples/16_eqgpt.py -- EqGPT on synthetic Burgers data, no wave assets needed beyond the GPT weights.
  • examples/09_compare_algorithms.py -- the same dataset scored by all seven algorithms against a single NMSE measure.
  • examples/03_visualize.py -- the report pipeline on a fast synthetic fit, no assets at all.