Hurricane landfall (Holland 1980 wind + SWE surge + waves + rainfall + runoff)
Full description: Read the full narrative
The landfall scenario combines a parametric vortex wind field (Holland 1980 with the Atkinson–Hollan B estimate and Coriolis term, advected along a track that passes the pinned landfall point at mid-duration) with a depth-averaged nonlinear shallow-water surge solver forced by wind stress and bottom pressure, empirical wave growth (JONSWAP/Stokes–Moum–Bretschneider capped at Pierson–Moskowitz with USACE breaking/setup rules), Lonfat-shaped parametric rainfall, and USDA TR-55 SCS-CN runoff.
- Execution mode
- Local CPU (python3) — no Kaggle token required server/kaggle/simRunner.ts:126-141
- Verification
- MEASURED — executed directly (3 parameter sets incl. Cat 5 and Cat 1 boundaries); internal physics gate PASS observed
- Wire scenario type
hurricane_landfall— contract at src/services/kaggleSim.ts:28-240; job API at Simulation job API
MEASURED marks values produced by executing the actual code on this machine (commands in Reproduction; raw logs summarised in Methodology → Evidence runs).
Governing equations and coefficients
Source attribution as stated in the kernel header (verbatim):
Wind: Holland (1980, MWR 108:1212) gradient-wind balance with Coriolis: V(r) = sqrt( V_c^2 + (f·r/2)^2 ) − f·r/2, V_c^2 = (B·Δp/ρ)·x·e^(−x), x = (R_max/r)^B B from the observed Vmax–Δp relation (Atkinson & Hollan 2012, BAMS): B = e·ρ·Vmax^2/Δp, clipped to [1.0, 2.5].
Coefficients and named constants
| Constant | Value | Meaning / provenance | Source |
|---|---|---|---|
| RHO_AIR | 1.15 kg/m³ | Air density for stress/IB | kaggle-kernels/hurricane-sim/main.py:105 |
| RHO_WATER | 1025.0 kg/m³ | Seawater density | kaggle-kernels/hurricane-sim/main.py:106 |
| G | 9.81 m/s² | Gravity | kaggle-kernels/hurricane-sim/main.py:107 |
| C_DRAG | 0.0015 | Saturated high-wind drag (Powell 2003; Large & Pond 1981) | kaggle-kernels/hurricane-sim/main.py:108-110 |
| N_MANING | 0.03 | Typical shelf/channel Manning n | kaggle-kernels/hurricane-sim/main.py:111 |
| GAMMA_BREAKER | 0.78 | USACE CEM "(H/d)max = 0.78 agrees best with observations" | kaggle-kernels/hurricane-sim/main.py:112-113 |
| K_SETUP | 0.19 | Wave setup coefficient (USACE Eq II-4-24) | kaggle-kernels/hurricane-sim/main.py:114-116 |
| JONSWAP_CH | 1.6e-3 | SMB/JONSWAP fetch-growth coefficient | kaggle-kernels/hurricane-sim/main.py:117-118 |
| PM_LIMIT | 0.0248 | Pierson–Moskowitz cap coefficient | kaggle-kernels/hurricane-sim/main.py:119 |
| cat_winds | {1:43, 2:50, 3:58, 4:70, 5:90} m/s | Saffir–Simpson threshold wind per category | kaggle-kernels/hurricane-sim/main.py:351-352 |
| Ω (OMEGA_EARTH) | 7.2921159e-5 rad/s | Earth rotation for Coriolis | kaggle-kernels/hurricane-sim/main.py:138-143 |
Parameter contract
Hurricane parameters. Category drives derived pressure/radius via derivePhysicsFormOverrides. Shared request envelope (validated at the client boundary before dispatch).
| UI control | Wire field | Unit | Valid range (UI · wire · kernel) | Default | Physical meaning | Source |
|---|---|---|---|---|---|---|
| Saffir–Simpson Category slider | category | cat (int) | UI & wire 1–5 | 3 | Peak wind 43/50/58/70/90 m/s (kernel table) | src/components/scenarios/ScenarioEditor.tsx:69-76 src/services/kaggleSim.ts:140,604-612 kaggle-kernels/hurricane-sim/main.py:351-352 |
| Forward Speed slider | forward_speed_kmh | km/h | UI 5–60; wire 2–80 | UI 15 · kernel 30 | Track translation speed (asymmetric wind + accumulation) | src/components/scenarios/ScenarioEditor.tsx:69-76 src/services/kaggleSim.ts:141 kaggle-kernels/hurricane-sim/main.py:342 |
| Central Pressure slider | central_pressure_hpa | hPa | UI 880–1010 (overwritten to 1010−20·cat); wire 860–1020 | UI 950 · kernel 960 | Sets Δp, hence B and the whole radial profile | src/components/scenarios/ScenarioEditor.tsx:69-76 src/services/kaggleSim.ts:142,609 kaggle-kernels/hurricane-sim/main.py:343 |
| Radius of Max Winds slider | radius_max_wind_km | km | UI 10–200 (overwritten to max(20, 30+15·cat)); wire 5–300 | UI 50 · kernel 50 | Eyewall radius; needs ≤ ~R_max/2 cell size to be resolved | src/components/scenarios/ScenarioEditor.tsx:69-76 src/services/kaggleSim.ts:143,611 kaggle-kernels/hurricane-sim/main.py:344 |
| Track Heading slider + auto checkbox | heading_deg (optional) | °, clockwise from N | UI 0–360 step 5; wire 0–360; omitted in auto mode | 270 | Auto mode: kernel auto-aims the storm from the water centroid toward the pinned point | src/components/scenarios/ScenarioEditor.tsx:71-76,770-781 src/services/kaggleSim.ts:146-151,493 kaggle-kernels/hurricane-sim/main.py:393-400 |
| Hours to Landfall slider | duration_hours = 2 × landfallTime | h | UI 6–96 (wire ≤ 720) | UI 24 | The eye crosses the pinned point at mid-duration, so the run spans equal approach + inland halves | src/components/scenarios/ScenarioEditor.tsx:69-76 src/services/kaggleSim.ts:144,477-494 kaggle-kernels/hurricane-sim/main.py:376-381 |
| Landfall point (globe click) | track_frac_x/y (optional) | fraction 0–1 | clamped | 0.5, 0.5 | Column = east, row = south; default box centre | src/services/kaggleSim.ts:152-159 kaggle-kernels/hurricane-sim/main.py:382-383 |
| Terrain (auto-sampled) | terrain + terrain_gs | m (negative = below sea) | ≤ 65,536 cells | — | Real relief/bathymetry replaces the synthetic coastal profile | src/services/kaggleSim.ts:160-167 |
Outputs
| Field | Unit | Meaning | Source |
|---|---|---|---|
wind_speed.npy | m/s | Total 10 m wind (gradient + translation) | kaggle-kernels/hurricane-sim/main.py:724-745 |
wind_direction.npy | rad | atan2 of east/north wind components | kaggle-kernels/hurricane-sim/main.py:724-745 |
surge_height.npy | m | Composite water level: η + setup over ocean, inundation depth over land (NHC/USGS composite convention) | kaggle-kernels/hurricane-sim/main.py:612-615 |
wave_setup.npy / wave_height.npy | m / m (Hs) | Setup and significant wave height | kaggle-kernels/hurricane-sim/main.py:724-745 |
rainfall.npy / runoff.npy | mm (cumulative) | Rain accumulation and SCS-CN runoff | kaggle-kernels/hurricane-sim/main.py:582-591,724-745 |
inundation.npy | m above ground | On-land flooding depth | kaggle-kernels/hurricane-sim/main.py:724-745 |
terrain.npy | m | Effective terrain used | kaggle-kernels/hurricane-sim/main.py:724-745 |
snapshots_wind/surge.npy + snapshot_times.npy | —; h | ≈20-frame series | kaggle-kernels/hurricane-sim/main.py:724-745 |
metadata.json | — | model "holland_1980_wind + nonlinear_2d_swe_surge", physics/sources strings, limits | kaggle-kernels/hurricane-sim/main.py:654-674 |
Spatial-origin semantics
Square domain; row 0 = north, so northward storm motion decreases the row index (cy = cy0 − v_north·t/dx). The eye passes the pinned track point (x = east, y = south, fractions of the grid) at mid-duration; heading 0 = N clockwise; auto-aim computes the bearing from the water centroid. Without supplied terrain the synthetic profile is ocean west / land east with a ~1.5 m/km shelf. kaggle-kernels/hurricane-sim/main.py:476-482,376-383,431-440,393-400,240-256
Documented validity limits (verbatim from the code)
'point-vortex Holland field — no eyewall fine structure, constant Vmax (no intensification/decay)' · 'fetch = distance to nearest coast (upper-bound proxy)' · 'wave setup empirical (USACE Eq II-4-24, ≈0.19·H_b) — no wave propagation' · 'rainfall parametric surrogate of the Lonfat-2007 PHRaM radial structure (no shear/topography Fourier terms), not microphysical' · 'no tides, no river routing (backwater excluded)' · 'equirectangular grid — valid ≤ ~500 km domain'
The kernel refuses to emit fields if its physics gate fails — "Hurricane physics gate failed — refusing to emit fields from mis-wired parametric relations." Checks: V(R_max) ≈ V_max ± 15 %, B ∈ [1, 2.5], inverse-barometer 0.9–1.1 cm/hPa, CN75 @ 100 mm in (30, 50) mm, SCS zero below 0.2 S, wave growth vs PM cap, rain peak 40–130 mm/h with zero eye/500 km. kaggle-kernels/hurricane-sim/main.py:285-335,717-720
Measured runs and timings
| Run / check | Measured result |
|---|---|
| Physics gate (every run, observed stdout) | "B=2.29 V(Rmax)=69.0 m/s (target 70) | IB=0.99 cm/hPa | CN75@100mm=41.1 mm | waves 18.0→31.3 cap 5.00 m | rain peak=84mm/h eye=0 edge=0 → PASS" |
| Representative Cat 4 (950 hPa, 40 km RMW, 6 h, 240 km box) | max wind 77.0 m/s (right-front asymmetry above the 70 m/s threshold), coastal water 1.15 m, inundation 0.39 m, rainfall 482 mm, runoff 402 mm; wall 1.47 s |
| Boundary Cat 5 with 5 km RMW on 2.5 km cells | max wind 32.2 m/s — eyewall under-resolved by the grid ("coarse-grid dilution"); a real Cat 5 wind requires RMW ≳ 2× cell size. Documented behaviour, not an error |
| Boundary Cat 1 with 1020 hPa central pressure | max wind 1.6 m/s — Holland Δp floor (5 hPa) dominates; demonstrates that pressure, not category label, drives the field |
Reproduction
Commands
curl -s -X POST http://localhost:3001/api/kaggle/simulate \
-H 'Content-Type: application/json' -d '{
"type":"hurricane_landfall","lat":25.0,"lon":-80.0,"grid_size":96,
"extent_km":240,"category":4,"forward_speed_kmh":30,
"central_pressure_hpa":950,"radius_max_wind_km":40,"duration_hours":6 }'
# direct kernel:
cd /tmp && mkdir h && cp <repo>/kaggle-kernels/hurricane-sim/main.py h/ && cd h && \
echo '{"grid_size":96,"category":4,"central_pressure_hpa":950,"radius_max_wind_km":40,"forward_speed_kmh":30,"duration_hours":6,"extent_km":240}' > params.json && \
TERRANOETIS_OUT_DIR=$PWD python3 main.py | grep VERIFY