Meteorological Forcing
Cocoa applies atmospheric forcing through two mechanisms: wind stress at the water surface and atmospheric pressure gradients. Both enter the governing equations as body forces and are ramped up during spinup to suppress initial transients.
Wind Stress
Surface Wind Drag
Wind stress is computed from 10-meter wind velocity using the Garratt (1977) bulk drag law [Garratt1977]. The drag coefficient is a linear function of wind speed, capped at a configurable maximum:
where \(U_{10} = \sqrt{u_{10}^2 + v_{10}^2}\) is the 10-meter wind speed magnitude and \(C_{d,\max}\) is the drag coefficient cap (default 0.0025).
The wind stress vector is then:
with \(\rho_a = 1.293\) kg/m3 (air density at 0 °C, ADCIRC convention) and \(\rho_0 = 1000\) kg/m3 (reference seawater density). The result is a kinematic stress with units m2/s2.
Note
The cap \(C_{d,\max}\) prevents unrealistically large stress at extreme wind speeds, where observational evidence suggests the drag coefficient levels off or decreases. The default value of 0.0025 follows ADCIRC convention.
Fig. 25 Garratt (1977) drag coefficient showing linear growth capped at \(C_{d,\max} = 0.0025\). The transition occurs near 26 m/s.
Wind Stress Limiter
To prevent abrupt wind stress changes at the wet/dry boundary, Cocoa applies a depth-dependent limiter that smoothly ramps wind stress to zero as water depth approaches the drying threshold \(H_0\). This is always active and improves stability during wetting/drying cycles with strong winds.
The limiter computes a scaling factor \(f_w \in [0, 1]\):
Since both GWCE and momentum kernels iterate only over wet nodes/elements
(where \(H > H_0\) by construction), the \(H \le H_0\) case is never
reached. The early return for \(H > 4 H_0\) avoids the tanh evaluation
for the vast majority of the domain, where all threads in a GPU warp take the
same branch with no divergence.
This formulation follows ADCIRC’s windLimiter subroutine (DMW 2022).
Fig. 26 Wind stress limiter \(f_w\) smoothly transitions from 0 near the drying threshold \(H_0\) to 1 at \(4H_0\), suppressing wind forcing in nearly-dry cells.
Application in the Momentum Equations
In the momentum equations ((2), (3)), wind stress appears as a surface forcing divided by the water column depth:
where \(\tau_s/\rho_0\) is the kinematic wind stress ((13)), \(H\) is the total water depth, and \(f_w\) is the wind stress limiter ((14)).
Discretized form (nodal):
Wind stress is applied per node after the element-based RHS assembly, with Crank-Nicolson centering between the two time levels:
and identically for \(RHS_{y,i}\), where \(R_{met}\) is the meteorological ramp factor (see Meteorological Ramp; the stress fields are stored pre-ramped) and \(f_{w,i}\) is evaluated at the \(n+1\) depth.
Application in the GWCE
In the GWCE ((6)), wind stress enters the \(\mathbf{J}\) vector as a depth-integrated surface forcing, scaled per node by the wind limiter before element averaging:
where \(f_{w,i}\) is the wind limiter evaluated at node \(i\) using that node’s total depth \(H_i = h_i + \zeta_i\). Unlike the momentum equation, wind stress in the GWCE is not divided by depth. The GWCE operates on depth-integrated quantities, and wind stress is a surface force per unit area.
Atmospheric Pressure Gradient
The atmospheric pressure gradient force accelerates water from high to low atmospheric pressure. This is the primary mechanism by which storm surge is generated, with the “inverted barometer” effect producing roughly 1 cm of surge per 1 hPa of pressure deficit.
Application in the GWCE
The atmospheric pressure gradient enters the GWCE through the \(\mathbf{J}\) vector:
where:
\(p_{s,i}\) is the atmospheric pressure at node \(i\) [Pa]
\(\alpha_L\) is the wet/dry slope limiter
\(\bar{H}\) is the element-averaged total depth
\(FDX_i, FDY_i\) are the shape function derivative products
The slope limiter \(\alpha_L\) is applied consistently with the tide potential gradient (see Generalized Wave Continuity Equation (GWCE)), reducing pressure gradients in shallow water to suppress spurious oscillations.
Pressure ramping:
During spinup, the atmospheric pressure is blended from the background reference pressure toward the actual forcing data:
where \(p_{bg} = 101300\) Pa is the background atmospheric pressure and \(R_{met}\) is the meteorological ramp factor. This ensures a smooth transition from hydrostatic equilibrium at initialization.
Application in the Momentum Equations
In the momentum equations, the atmospheric pressure gradient appears alongside the barotropic pressure gradient:
This term is already present in the momentum equation formulation (see (2)). For storm surge simulations, the atmospheric pressure gradient typically dominates over wind stress in deep water, while wind stress becomes more important in shallow coastal areas.
Meteorological Ramp
To prevent impulse responses at initialization, meteorological forcing is gradually introduced using a normalized hyperbolic tangent ramp function:
where \(t\) is the elapsed time since cold start and \(T_{ramp}\) is the ramp duration. The normalization by \(\tanh(2)\) ensures the ramp reaches exactly 1.0 at \(t = T_{ramp}\), and the value is clamped to 1.0 for \(t \ge T_{ramp}\).
By default, the meteorological ramp uses the same configuration as the tidal
ramp (forcing.ramp). An optional forcing.meteorological.ramp section
allows a different ramp duration for meteorological forcing — useful when
starting from a pre-spun-up tidal state that needs only a short met ramp.
Internally, ForcingManager maintains separate ramp objects for tidal and
meteorological forcing.
Fig. 27 Normalized hyperbolic tangent ramp for several \(T_{ramp}\) values. Dots mark \(t = T_{ramp}\) where \(R_{met} = 1.0\).
Temporal Interpolation
Meteorological forcing data is provided at discrete time snapshots. Between snapshots, Cocoa linearly interpolates the forcing fields:
where \(\alpha = (t - t_k) / (t_{k+1} - t_k)\) is the interpolation weight and \(\phi\) represents any forcing field (pressure, wind_u, wind_v).
The interpolation is performed at each mesh node after spatial interpolation from the meteorological grid. Cocoa maintains a ring buffer of pre-read snapshots to amortize I/O costs across timesteps.
Spatial Interpolation
Meteorological data is typically provided on a regular rectangular grid at coarser resolution than the computational mesh. Cocoa uses bilinear interpolation to transfer forcing data from the meteorological grid to mesh nodes.
For a mesh node located at \((x, y)\) within a grid cell with corners at \((x_0, y_0)\) and \((x_1, y_1)\):
where the bilinear weights \(w_{ij}\) are products of the normalized distances:
The interpolation weights are precomputed once when the meteorological data is initialized and cached for reuse at each timestep. For regular (rectangular) grids, this is a simple index computation. For irregular grids (OWI NetCDF with moving vortex tracking), the weights are recomputed when the grid moves.
Multi-Domain Composition
Cocoa can compose several meteorological sources into one forcing field –
each a forcing.meteorological.domains entry, of any format, or the flat
single-source form – rather than being limited to one format per run.
During interpolation, Cocoa applies a priority rule: the innermost valid
domain takes precedence over outer domains, where “innermost” ranks first by
configured source order (later entries win at overlap) and then, within a
source that is itself multi-domain, by that source’s own domain index.
Mesh nodes that fall outside all domains receive background values (101300 Pa
pressure, zero wind). OWI’s internal nesting of several grids within one
file (a coarse outer grid plus fine inner grids) is the same last-wins rule
applied one level deeper, not a separate mechanism – composition is
format-agnostic.
This approach allows, for example, a coarse basin-scale field to cover the full model domain while a fine hurricane-following nest – from the same source or a different one entirely (e.g. a GRIB regional model nested inside a coarser OWI field) – provides high-resolution forcing in the region of interest.
Cadence Interpolation onto the Union Time Axis
Composed sources need not share a common time step. Cocoa builds a single union time axis as the sorted, deduplicated set of every domain’s own sample times, and reads each domain against it. Where a union time coincides with one of a domain’s own samples, that domain’s data is used verbatim – exact, not interpolated. Where a union time falls strictly between two of a domain’s own samples (a coarser-cadence source relative to the axis), the domain’s fields – and, for a moving grid, its coordinates – are linearly interpolated between its own bracketing snapshots:
where \(\phi_d\) is domain \(d\)’s field, \(t_{d,k}\) and \(t_{d,k+1}\) are its own bracketing sample times, and \(t\) is the union-axis time being synthesized. This source-side (grid-space) interpolation is distinct from the node-side temporal blend of Temporal Interpolation: it lets a coarse-cadence source (e.g. a 3-hourly field) coexist with a fine-cadence one (e.g. hourly) on the same union axis without forcing every composed source to share a time step.
Activation Window Fade
A source active only over part of the run – a moving nest, or any source
with a configured activation window – can be given an
activation/deactivation cross-fade duration \(T\)
(activation_ramp_seconds), applied at its window’s open and close times
\(t_{open}\), \(t_{close}\) on the union axis:
using the same normalized tanh shape as the cold-start meteorological ramp (Meteorological Ramp), but referenced to the source’s own window edges rather than to cold start. Where the source covers a node, the composed field cross-fades toward it, \(\text{node} = (1 - w) \cdot \text{underlying} + w \cdot \text{source}\), in place of the hard override of the priority rule above. \(T \le 0\) (the default) disables the fade, recovering hard window edges. The same weight gates the Eulerian per-domain combine, the Lagrangian moving-grid overlay, and the GAHM vortex overlay’s per-storm temporal factor, so a source – gridded or vortex – fades in and out identically regardless of which overlay mechanism carries it.
Wind Reduction
Cocoa optionally reduces wind velocity over land before applying the drag law.
Two reduction mechanisms are supported, applied in sequence. Both are applied
per meteorological source, folded into that source’s wind at composition
time (before it is blended into the combined field), gated by the source’s
wind_reduction flag. This lets co-located sources be treated differently –
for example reducing a parametric vortex while leaving an already-surface GFS
field unreduced. A source covered node produces the same stress as a
post-blend reduction would; only feather rings between a reduced and an
unreduced source differ.
Directional Roughness Reduction
The directional roughness reduction modifies wind components based on the
ratio of upwind land surface roughness to marine roughness. This follows
ADCIRC’s ApplyDirectionalWindReduction formulation.
Marine roughness is derived from the Garratt drag coefficient via Charnock’s relation:
where \(C_d\) is the drag coefficient computed from the pre-reduction wind speed.
Land roughness \(z_{0l}\) is obtained by linear interpolation from the two nearest directional bins. The mesh stores 12 roughness lengths per node (one per 30-degree bin, starting from East and proceeding counter-clockwise).
Overland flooding correction: When the total water depth \(H = h + \zeta\) exceeds \(2 h_0\) (twice the minimum depth threshold), the effective land roughness is reduced:
This accounts for the reduced influence of land surface features when they are submerged by floodwater.
Reduction factor: When \(z_{0l} > z_{0m}\), the wind reduction factor is:
When \(z_{0l} \le z_{0m}\) (land is smoother than open water), no reduction is applied (\(f_r = 1\)). The reduced wind components are:
Canopy Coefficient
The canopy coefficient \(C_c \in \{0, 1\}\) is a binary multiplicative factor representing wind attenuation under forest canopy:
A value of \(C_c = 1\) (default) means no canopy effect; \(C_c = 0\) suppresses the wind (and hence the stress) at that node. The coefficient is folded into the source wind here rather than scaling the post-drag stress. For a binary mask this is exact – \(0\) and \(1\) are unchanged by the drag law’s squaring, so \(C_c = 0\) gives zero stress and \(C_c = 1\) is a no-op either way – which is why fractional values are rejected at startup. After both reductions the corrected source winds are blended, and the combined field is passed to the Garratt drag law ((12), (13)) once, after the meteorological ramp.
Physical Constants
Constant |
Value |
Description |
|---|---|---|
\(\rho_0\) (seawater density) |
1000 kg/m3 |
Reference density (ADCIRC convention) |
\(\rho_a\) (air density) |
1.293 kg/m3 |
Dry air density at 0 °C (ADCIRC convention) |
\(p_{bg}\) (background pressure) |
101300 Pa |
Ambient pressure away from any storm (1013 mb, the ADCIRC/ATCF
convention). The parametric vortex resolves its OWN far-field
\(p_n\) per track snapshot from the ATCF |
\(g\) (gravity) |
9.81 m/s2 |
Gravitational acceleration |
\(C_{d,\max}\) (drag cap) |
0.0025 |
Default maximum drag coefficient |
Implementation Files
forcing/meteorological/DragLaw.hpp:wind_drag<DragLaw>()Garratt drag coefficient andwind_ice_drag<DragLaw, Attenuation>()ice attenuation (device-callable)forcing/meteorological/WindStress.hpp:wind_stress<DragLaw>()free function template for kinematic wind stress (device-callable)forcing/meteorological/WindReduction.hpp: Directional wind reduction kernel (device-callable)physics/WindLimiter.hpp: Depth-based wind stress limiter for drying cells (device-callable)numeric/terms/WindStressTerm.hpp:GwceWindStresswind stress GWCE contributionnumeric/terms/AtmPressureGradient.hpp:GwceAtmPressureGradientatmospheric pressure gradient GWCE contributionnumeric/momentum/MomentumRhsKernels.cpp: Wind stress in momentum RHSforcing/meteorological/MeteoForcingProvider.hpp: Ring buffer, temporal interpolation, and overlay orchestrationforcing/meteorological/MeteoSnapshotSource.hpp: Rank-0 reader thread; union time axis, per-domain windows, cadence interpolationcocoa_meteo/CompositeMeteoReader.hpp: Composes multiple configured sources into one flat, priority-ordered domain spacecocoa_meteo/interpolation/MultiDomainInterpolator.hpp: Host spatial combine, priority rule, winner mask, activation weightsforcing/WindowedRamp.hpp: Activation/deactivation window fade (shared tanh shape withRamp)forcing/ForcingManager.hpp: Ramp computation, wind reduction, and forcing orchestration