======================= Meteorological Forcing ======================= Cocoa supports atmospheric forcing from wind fields and surface pressure data for storm surge and other weather-driven simulations. Forcing data is read from external files, interpolated onto the computational mesh, and applied at each timestep. .. contents:: On This Page :local: :depth: 2 Overview -------- Meteorological forcing in Cocoa includes two components: - **Wind stress**: Surface drag from 10-meter wind velocity, computed via the Garratt (1977) bulk drag law - **Atmospheric pressure gradient**: Barotropic forcing from spatially varying surface pressure Both components are read together from the same source and share a common temporal interpolation framework. A run may also combine several sources -- of the same or different file formats -- into one composed forcing field; see `Composing Multiple Sources`_. See :doc:`../theory/meteorological_forcing` for the mathematical formulation. To generate the wind and pressure field analytically from a storm track instead of (or in addition to) gridded data, see :doc:`parametric_vortex`. .. toctree:: :hidden: parametric_vortex Supported File Formats ---------------------- Cocoa supports four meteorological file formats through the ``cocoa_meteo`` library: .. list-table:: :header-rows: 1 :widths: 20 15 15 50 * - Format - Config Value - Domains - Description * - CF-compliant NetCDF - ``cf_netcdf`` - Single - Standard climate/forecast NetCDF with configurable variable names * - OWI ASCII - ``owi_ascii`` - Multiple - ADCIRC-compatible paired pressure/wind ASCII files * - OWI NetCDF - ``owi_netcdf`` - Multiple - NetCDF variant of OWI with support for moving (vortex-tracking) grids * - GRIB2 - ``grib`` - Multiple - Operational weather products (GFS, HRRR, RRFS, NAM, HWRF, HAFS) read directly via ecCodes; requires a build with ``-Dcocoa_ENABLE_GRIB=ON`` The synonyms ``cf`` and ``netcdf`` (for ``cf_netcdf``), ``owi`` (for ``owi_ascii``), and ``grib2`` (for ``grib``) are also accepted. Configuration ------------- Meteorological forcing is configured in the ``forcing.meteorological`` section of the YAML configuration file. Enabling Meteorological Forcing ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To enable meteorological forcing, set ``enabled: true`` and specify the file format and path(s): .. code-block:: yaml forcing: meteorological: enabled: true format: cf_netcdf filename: "path/to/meteo_data.nc" ramp: enabled: true duration: 1d The ``ramp`` section controls the global spinup ramp applied to both tidal and meteorological forcing. To use a different ramp for meteorological forcing, add an optional ``ramp`` subsection under ``forcing.meteorological``: .. code-block:: yaml forcing: ramp: enabled: true duration: 5d # Applied to tidal forcing meteorological: enabled: true format: cf_netcdf filename: "path/to/meteo_data.nc" ramp: # Optional: overrides forcing.ramp for met enabled: true duration: 1d # Shorter met ramp When the ``forcing.meteorological.ramp`` section is absent, the global ramp is used for both tidal and meteorological forcing. CF NetCDF Format ^^^^^^^^^^^^^^^^ .. code-block:: yaml forcing: meteorological: enabled: true format: cf_netcdf filename: "path/to/meteo_data.nc" **Optional variable name overrides:** By default, Cocoa expects the following NetCDF variable names. Override them under a nested ``variables`` map if your data uses different conventions: .. list-table:: :header-rows: 1 :widths: 25 25 50 * - Key - Default - Description * - ``pressure`` - ``mslp`` - Mean sea level pressure variable name * - ``wind_u`` - ``wind_u`` - Eastward 10-m wind component * - ``wind_v`` - ``wind_v`` - Northward 10-m wind component * - ``time`` - ``time`` - Time coordinate variable * - ``lon`` - ``lon`` - Longitude coordinate variable * - ``lat`` - ``lat`` - Latitude coordinate variable Example with custom variable names: .. code-block:: yaml forcing: meteorological: enabled: true format: cf_netcdf filename: "era5_data.nc" variables: pressure: "sp" wind_u: "u10" wind_v: "v10" time: "time" lon: "longitude" lat: "latitude" OWI ASCII Format ^^^^^^^^^^^^^^^^ OWI ASCII uses paired pressure and wind files per domain. This is the traditional ADCIRC meteorological forcing format. **Single domain:** .. code-block:: yaml forcing: meteorological: enabled: true format: owi_ascii filenames: - pressure: "path/to/fort.221" wind: "path/to/fort.222" **Multiple nested domains:** .. code-block:: yaml forcing: meteorological: enabled: true format: owi_ascii filenames: - pressure: "path/to/basin.pre" wind: "path/to/basin.wnd" - pressure: "path/to/region.pre" wind: "path/to/region.wnd" Domain 0 (first entry) is the outer coarse grid. Subsequent entries are progressively finer inner grids. Where grids overlap, the innermost valid domain takes priority. Domains may also differ in time coverage; see :ref:`meteo-domain-time-windows`. OWI NetCDF Format ^^^^^^^^^^^^^^^^^ OWI NetCDF stores all domains in a single NetCDF file using group-based organization. It additionally supports moving (vortex-tracking) grids where the grid coordinates change at each time step. .. code-block:: yaml forcing: meteorological: enabled: true format: owi_netcdf filename: "path/to/meteo_owi.nc" GRIB2 Format ^^^^^^^^^^^^ The GRIB reader ingests operational GRIB2 products directly -- individual per-cycle snapshot files (each holding one valid time) or files carrying several times. At startup every file's message headers are scanned and indexed by valid time, so filename order does not matter; incomplete or duplicated fields, grid changes within a domain, and misaligned time axes are all rejected with a descriptive error before the simulation starts. GRIB support is compiled in with ``-Dcocoa_ENABLE_GRIB=ON``, which requires an installed ECMWF ecCodes (point CMake at it with ``-Deccodes_DIR``). ecCodes must be built with the JPEG2000 and CCSDS/AEC codecs that NCEP products use -- ``spack install eccodes +memfs +aec jp2k=openjpeg`` provides a suitable build -- and cocoa verifies those codec features are present at configure time (see :doc:`/getting_started/installation`). GRIB2 fields are self-describing SI (Pa, m/s): ``pressure_scale`` is rejected for this format. **Single domain (GFS):** .. code-block:: yaml forcing: meteorological: enabled: true format: grib product: gfs filenames: - "gfs.t00z.pgrb2.0p25.f000" - "gfs.t00z.pgrb2.0p25.f003" - "gfs.t00z.pgrb2.0p25.f006" The ``product`` preset selects which GRIB2 fields provide mean sea level pressure and 10-m winds. Available presets: ``gfs``, ``hrrr``, ``rrfs``, ``nam``, ``hwrf``, ``hafs``. GFS, NAM, HWRF, and HAFS use WMO-standard PRMSL; HRRR carries only the NCEP-local MSLMA reduction and RRFS only the NCEP-local MSLET (membrane) reduction, so those presets select the local parameters. NCEP files that pack the 10-m wind pair as a single multi-field GRIB message (e.g. NAM ``awphys``) are handled transparently. These products are 10-m winds from models that resolve land effects, so a mesh-based land wind reduction usually should not be applied on top: set ``wind_reduction: false`` at the meteorological level to leave the gridded field unreduced while, e.g., a composed parametric vortex is still reduced (see `Per-source application`_). **Multiple GRIB resolutions (HRRR nested inside GFS), or GRIB mixed with another format:** Nesting a fine product inside a coarser one, or composing GRIB with a different format entirely, uses the general ``domains:`` list -- each entry is a self-contained source with its own ``format`` (``grib`` here, on every entry): .. code-block:: yaml forcing: meteorological: enabled: true domains: - format: grib product: gfs # domain 0: outer/coarse filenames: ["gfs_f000.grib2", "gfs_f001.grib2"] - format: grib product: hrrr # domain 1: inner/fine, wins where it covers filenames: ["hrrr_f00.grib2", "hrrr_f01.grib2"] See `Composing Multiple Sources`_ for the self-containment rule, coverage priority, and per-entry ``wind_reduction``. Domains need not share a time step or phase -- see `Domain Time Windows`_. A long forecast's file list runs to hundreds of entries and is reusable across runs, so any ``filenames`` list (flat form or a ``domains`` entry) may instead be pulled from an external YAML file with an ``include`` node (see :doc:`../getting_started/configuration` for the inclusion rules): .. code-block:: yaml filenames: include: hafs_ida_files.yaml # a YAML list of file paths Regular latitude-longitude grids (GFS, HAFS parent domains) are handled natively, including global longitude wrapping and regional grids whose span crosses the antimeridian (routine for Pacific HAFS storms); projected grids (HRRR/RRFS/NAM Lambert conformal) are expanded to per-point coordinates and interpolated with the same inverse-bilinear machinery as irregular OWI grids. Bitmap-masked cells (missing data, e.g. outside a HAFS domain's integration footprint) make the affected area fall through to the next-coarser domain. Storm-following (moving) nests -- the HWRF/HAFS ``storm`` output, where a fixed-shape regular grid translates with the storm each forecast hour -- are detected automatically and handled by the same Lagrangian storm overlay used for OWI moving vortex grids (the grid origin is advected between snapshots and winds are blended magnitude-preserving, avoiding eye-wall vector cancellation). A moving nest may only change position: dimension, spacing, or projection changes are rejected, so a domain's file list must come from a single forecast cycle. Moving nests must be regular lat-lon grids; a typical hurricane setup lists three domains, coarsest first (each entry still needs its own ``format: grib``, like every ``domains:`` entry):: domains: - format: grib product: gfs filenames: [...] - format: grib product: hafs # fixed parent domain filenames: [...] - format: grib product: hafs # moving storm nest, highest priority filenames: [...] For products the presets do not cover, the three fields can be selected explicitly by their numeric GRIB2 identity (shown here with the HRRR MSLMA values); ``level`` defaults to 0: .. code-block:: yaml forcing: meteorological: enabled: true format: grib filenames: ["custom.grib2"] variables: pressure: {discipline: 0, category: 3, number: 198, surface_type: 101} wind_u: {discipline: 0, category: 2, number: 2, surface_type: 103, level: 10} wind_v: {discipline: 0, category: 2, number: 3, surface_type: 103, level: 10} The same ``variables`` block may be given on a ``domains:`` entry (which still needs its own ``format: grib``, like any other entry). ``surface_type`` is GRIB2 code table 4.5 (101 = mean sea level, 103 = height above ground). Composing Multiple Sources ^^^^^^^^^^^^^^^^^^^^^^^^^^ More than one meteorological source can drive a run at once -- a basin-scale OWI background overlaid by a fine HAFS storm nest, or a GFS background with a GRIB HRRR inset -- by listing them under ``forcing.meteorological.domains`` instead of the flat top-level keys used above. Each list entry is a **fully self-contained source**: it declares its own ``format`` and every key that format needs (files, ``product``/``variables`` for GRIB, ``wind_scale``, ``pressure_scale``, ``wind_reduction``), so sources of different formats compose freely: .. code-block:: yaml forcing: meteorological: enabled: true domains: - format: owi_ascii filenames: - pressure: "basin.pre" wind: "basin.wnd" - format: grib product: hafs filenames: ["hafs_storm_f00.grib2", "hafs_storm_f01.grib2"] ``domains:`` cannot be combined with any top-level source key (``format``, ``filename``/``filenames``, ``product``, ``variables``, ``wind_scale``, ``pressure_scale``, ``wind_reduction``) -- entries are self-contained, so there is nowhere else for that configuration to apply. In particular, ``format: grib`` at the top level combined with ``domains:`` is a startup error naming the offending key: .. code-block:: text forcing.meteorological.domains cannot be combined with the top-level 'format' key; move the configuration into the domains entries List order is coverage priority: entry 0 is the outermost/coarsest source, and later entries win at any node they cover (the same last-wins rule OWI's own nested domains use, see `OWI ASCII Format`_). A node not covered by any entry falls back to the ambient background (101300 Pa pressure, zero wind). The vortex block, if present, composes on top of whatever the ``domains:`` list produces exactly as it does over the flat form (`Domains with a vortex`_ below), unchanged -- see :doc:`parametric_vortex`. Per-entry wind_reduction """""""""""""""""""""""" Each entry carries its own ``wind_reduction`` flag (default ``true``, the same default as the flat form), so co-located sources can be corrected differently. A common pattern is a marine 10 m HAFS nest that still needs the land wind reduction, laid over an OWI background that is already a corrected surface wind and should not be reduced again: .. code-block:: yaml forcing: meteorological: enabled: true domains: - format: owi_ascii # basin-scale background, already a wind_reduction: false # surface wind -- do not reduce again filenames: - pressure: "basin.pre" wind: "basin.wnd" - format: grib # fine marine storm nest product: hafs wind_reduction: true # reduce to a land wind where it covers filenames: ["hafs_storm_f00.grib2", "hafs_storm_f01.grib2"] The flag is resolved per node from whichever entry actually covers it (the winner of the spatial combine), so a node under the HAFS nest is reduced even while a node left uncovered by it -- and served by the OWI background -- is not. See `Per-source application`_ for how the flag composes with the mesh's roughness/canopy attributes, and `Domain Time Windows`_ for how sources with different time steps compose. Domains with a vortex """""""""""""""""""""" A hurricane setup combining an OWI ASCII basin-scale background, a fine HAFS GRIB storm nest, and the GAHM parametric vortex -- the full innermost-wins stack, gridded reader composition plus the vortex layered on top: .. code-block:: yaml forcing: meteorological: enabled: true domains: - format: owi_ascii # basin-scale background: already a wind_reduction: false # surface wind, so leave it unreduced filenames: - pressure: "basin.pre" wind: "basin.wnd" - format: grib # fine marine 10 m storm nest: reduce product: hafs # it to a land wind where it covers wind_reduction: true filenames: ["hafs_storm_f00.grib2", "hafs_storm_f01.grib2"] vortex: model: gahm tracks: - "storm_bal.dat" wind_reduction: true # the vortex is also a marine wind ramp: enabled: true duration: 1d The HAFS nest and the OWI background can each be produced on their own native cadence (`Domain Time Windows`_) -- neither has to be resampled to match the other or the vortex's per-snapshot track times. Both gridded sources reduce or not per their own physical nature (the OWI background is already surface-level; the HAFS nest and the vortex are both marine winds that need the mesh's land correction), independent of composition order. .. _meteo-activation-ramp: Activation Ramp """""""""""""""" A ``domains:`` entry's data window, or a vortex track's own start/end, does not have to span the whole simulation -- a fine HAFS storm nest may only start at hour 48, arriving against an ocean that has already spun up but is still storm-free. By default that arrival is a **hard edge**: the source comes in over a single data interval (`Domain Time Windows`_), which for an intense, coarsely-sampled nest can still look like a step to the rest of the composed field. Each ``domains:`` list entry -- not the flat top-level source, which has no window of its own -- and the ``vortex:`` block accept a ``ramp: `` key (e.g. ``6h``; default ``0``, off) that smooths this into an explicit cross-fade. The fade is a **composition blend**, not a scale on the source's own values: at every node it interpolates between the composed field *without* that source and the composed field *with* it, .. code-block:: text node = (1 - w) * underlying + w * source with the weight ``w`` ramping from 0 to 1 as the window opens and back to 0 as it closes, symmetrically, using the same normalized ``tanh(2t/T) / tanh(2)`` shape as the top-level cold-start ``ramp`` block (:ref:`meteo-cold-start-ramp`) -- one curve shared by both uses so they cannot drift apart. Because the two sides of the blend already agree everywhere outside the source's own spatial coverage, the fade is spatially seamless: it cannot introduce a seam at a nest boundary, and the composed field can never dip below the underlying data, unlike a plain scale on the source. The fade is symmetric: the same duration applies at window-open and at window-close, each referenced to that edge's own time. A window shorter than twice the ramp duration never reaches full weight -- the fade-in and the fade-out are both still in progress when the other edge arrives -- which is deliberate, not an error: a very short-lived nest fades smoothly in and back out rather than plateauing at full weight. .. code-block:: yaml forcing: meteorological: enabled: true domains: - format: owi_ascii filenames: - pressure: "basin.pre" wind: "basin.wnd" - format: grib product: hafs ramp: 6h # cross-fade in/out over the nest's own window edges filenames: ["hafs_storm_f00.grib2", "hafs_storm_f01.grib2"] See :doc:`parametric_vortex` for the equivalent ``vortex.ramp`` key, which fades a storm in and out against its own track's first and last snapshot times rather than a data window. The full stack: a worked storm configuration """""""""""""""""""""""""""""""""""""""""""" Everything above composes in a single run. This configuration layers four gridded sources of three formats, two parametric storms, and every kind of ramp the model has -- each doing a different job on a different clock: .. code-block:: yaml forcing: meteorological: enabled: true # Model cold-start spinup: ONE ramp for the whole composed field, # referenced to the start of the meteorological data. Protects the # quiescent ocean at initialization; unrelated to the per-entry # activation ramps below. ramp: enabled: true duration: 12h domains: # Layer 0: basin-scale OWI background spanning the whole run. # Already a corrected surface wind, so opt out of the land # reduction. Present from t=0, so it needs no activation ramp. - format: owi_ascii wind_reduction: false filenames: - pressure: "basin.pre" wind: "basin.wnd" # Layer 1: regional GFS on its own (coarser) cadence. Marine # 10 m wind: leave wind_reduction at its default (true). Its # files start a day into the run, so fade it in over 3 hours # instead of stepping in at its first snapshot. - format: grib product: gfs ramp: 3h filenames: ["gfs_f000.grib2", "gfs_f003.grib2", "gfs_f006.grib2"] # Layer 2: fixed HAFS parent domain around the storm basin. - format: grib product: hafs ramp: 6h filenames: ["hafs_parent_f00.grib2", "hafs_parent_f01.grib2"] # Layer 3: the moving HAFS storm nest, produced only around # landfall. The reader detects the translating grid from the # files; the 6 h ramp cross-fades its arrival and departure. - format: grib product: hafs ramp: 6h filenames: ["hafs_nest_f00.grib2", "hafs_nest_f01.grib2"] # Innermost: the parametric vortex, layered over whatever the # domains produce. Two storms, applied in list order (the later # track wins where they overlap). The single vortex ramp fades # EACH storm against its own track's first/last snapshot times, # so the two storms fade in and out independently. vortex: model: gahm tracks: - "storm_a.dat" - "storm_b.dat" ramp: 6h wind_reduction: true Three kinds of ramp appear in that file, and they never share a clock: .. list-table:: :header-rows: 1 :widths: 22 30 48 * - Key - Clock - What it fades * - ``meteorological.ramp`` - The model cold start (one reference time for the run) - The entire composed field -- wind, stress, and the pressure anomaly about the ambient background -- during spinup (:ref:`meteo-cold-start-ramp`) * - ``domains[i].ramp`` - Each of that entry's data windows on the union time axis - That source's arrival and departure, cross-faded against the field composed without it (`Activation Ramp`_) * - ``vortex.ramp`` - Each storm track's own first and last snapshot times - Each storm's overlay weight, independently per track (:doc:`parametric_vortex`) Every gridded source above may also run on its own cadence -- 15-minute OWI, 3-hourly GFS, hourly HAFS -- with no resampling (`Domain Time Windows`_), and the per-node land reduction follows whichever entry wins each node (`Per-entry wind_reduction`_). .. _meteo-domain-time-windows: Domain Time Windows ^^^^^^^^^^^^^^^^^^^ Domains need not cover the same period or share a time step: each domain (an OWI ASCII file pair, an OWI NetCDF group, or a ``domains:`` list entry) carries its own time axis covering only the window it has data for. A typical layout pairs a basin-scale grid spanning the whole simulation with a fine storm grid produced only around landfall. Cocoa builds the sorted union of every domain's own times. At a union time that is exactly one of a domain's own samples, that domain's data is used verbatim (an exact-match short-circuit); at a union time strictly between two of a domain's own samples, its pressure and wind are linearly interpolated from that domain's own bracketing snapshots. A domain's sample rate therefore never has to match another's -- an hourly HRRR nest composes with a 3-hourly GFS background with no configuration needed. A moving (storm-following) domain's grid position is interpolated the same way, so a fine nest's origin moves continuously between its own reported positions even while a coarser domain refills on a different cadence. Outside its own window a domain is inactive: it contributes nothing, and its coverage falls through to the next outer domain or to the ambient background (background pressure, zero wind). By default the window edge is hard: the piecewise-linear interpolation between snapshots brings the domain in over a single data interval, which for a coarse cadence (a 3- or 6-hourly nest) can still look like a step to the rest of the composed field. Each ``domains:`` entry (and the ``vortex:`` block) can smooth this into an explicit, configurable-duration cross-fade with its own ``ramp`` key -- see `Activation Ramp`_ -- off by default, so the hard edge described above is unchanged unless configured. Each domain's data window is reported in the log at startup (debug level), and during the run each domain announces when the simulation first enters and leaves its window. A domain whose window never overlaps the simulated period produces a warning. The same announcements are made for each parametric vortex track (see :doc:`parametric_vortex`). Drag Law ^^^^^^^^ The wind drag law used to convert 10-m wind velocity to surface stress is selected with ``forcing.meteorological.drag_law``. The only accepted value is ``garratt`` (the Garratt 1977 bulk drag law), which is also the default: .. code-block:: yaml forcing: meteorological: enabled: true format: cf_netcdf filename: "data.nc" drag_law: garratt # default; only accepted value Scale Factors ^^^^^^^^^^^^^ Cocoa applies configurable scale factors to convert raw file values to SI units (Pa for pressure, m/s for wind): .. list-table:: :header-rows: 1 :widths: 25 15 60 * - Parameter - Default - Description * - ``pressure_scale`` - ``100.0`` - Multiplier for pressure values. Default converts hectopascals (hPa) to pascals (Pa). * - ``wind_scale`` - ``1.0`` - Multiplier for wind velocity values. Default assumes m/s input. Example for a dataset with pressure in Pa and wind in knots: .. code-block:: yaml forcing: meteorological: enabled: true format: cf_netcdf filename: "data.nc" pressure_scale: 1.0 # Already in Pa wind_scale: 0.514444 # Convert knots to m/s .. _meteo-file-formats: File Format Details ------------------- CF-Compliant NetCDF ^^^^^^^^^^^^^^^^^^^ The CF NetCDF reader expects a regular rectangular grid with the following structure: **Required dimensions and variables:** .. code-block:: text dimensions: time = UNLIMITED ; lat = ; lon = ; variables: double time(time) ; time:units = "hours since 2026-01-01 00:00:00" ; time:calendar = "standard" ; double lat(lat) ; double lon(lon) ; float mslp(time, lat, lon) ; // Surface pressure float wind_u(time, lat, lon) ; // Eastward 10-m wind float wind_v(time, lat, lon) ; // Northward 10-m wind - The time variable must use CF-compliant units (e.g., ``"hours since ..."`` or ``"seconds since ..."``) - Longitude and latitude are 1D coordinate arrays defining the grid - Global grids (360\ |deg| longitude span) are detected automatically; the reader handles wrap-around OWI ASCII ^^^^^^^^^ OWI ASCII files use a fixed-width format with one pressure file and one wind file per domain. **File header** (line 1): .. code-block:: text Oceanweather WIN/PRE Format YYYYMMDDHH YYYYMMDDHH The header line contains a format identifier, the start time, and the end time. **Snapshot header** (one per time step): .. code-block:: text iLat= NNNiLong= NNNdx=D.DDDDdy=D.DDDDSWLat=LL.LLLLLSWLon=LLL.LLLLDT=YYYYMMDDHHNN .. list-table:: :header-rows: 1 :widths: 20 20 60 * - Field - Position - Description * - ``iLat`` - cols 5-8 - Number of latitude points (NY) * - ``iLong`` - cols 15-18 - Number of longitude points (NX) * - ``dx`` - cols 22-27 - Longitude grid spacing [degrees] * - ``dy`` - cols 31-36 - Latitude grid spacing [degrees] * - ``SWLat`` - cols 43-50 - Southwest corner latitude [degrees] * - ``SWLon`` - cols 57-64 - Southwest corner longitude [degrees] * - ``DT`` - cols 68-79 - Snapshot time (YYYYMMDDHHNN) **Data records:** Following each snapshot header, ``NX * NY`` floating-point values are written in free format. Pressure files contain one field per snapshot; wind files contain two fields (eastward U, then northward V). OWI NetCDF ^^^^^^^^^^ OWI NetCDF files use NetCDF groups to organize multi-domain data: .. code-block:: text root group: dimensions: time = UNLIMITED ; variables: double time(time) ; group: domain_0 { dimensions: node = ; variables: double longitude(time, node) ; double latitude(time, node) ; float pressure(time, node) ; float wind_u(time, node) ; float wind_v(time, node) ; } group: domain_1 { ... } The per-domain coordinate arrays (``longitude``, ``latitude``) vary with time, enabling vortex-tracking grids that follow a storm center. For stationary grids, the coordinates are the same at each time step. .. _meteo-cold-start-ramp: Combining Meteorological and Tidal Forcing ------------------------------------------ Meteorological forcing is commonly used alongside tidal forcing for storm surge simulations. Both forcing types operate independently and can be configured together: .. code-block:: yaml forcing: ramp: enabled: true duration: 2d meteorological: enabled: true format: cf_netcdf filename: "hurricane_met.nc" tide: potential: enabled: true type: astronomical boundary: enabled: true constituents: - name: M2 frequency: 1.405189028e-04 nodal_factor: 0.964 equilibrium_arg: 23.06 boundary_values: - { amplitude: 0.50, phase: 340.0 } The ramp function is applied independently to tidal and meteorological components. If the simulation starts from a pre-spun-up tidal state, a shorter meteorological ramp avoids unnecessary delay: .. code-block:: yaml forcing: ramp: enabled: true duration: 5d # Long tidal spinup meteorological: enabled: true format: cf_netcdf filename: "hurricane_met.nc" ramp: enabled: true duration: 12h # Short met ramp This ``forcing.ramp`` / ``forcing.meteorological.ramp`` pair is a **cold-start spinup ramp**: it fades the whole composed field in exactly once, from the start of the run. It is distinct from the per-source **activation ramp** (`Activation Ramp`_), which fades one ``domains:`` entry or the ``vortex:`` block in and out mid-run, at its own data window's edges, and can recur every time that source's window opens or closes. The two compose independently -- a source with its own ``ramp`` still passes through the cold-start ramp on top. Wind Reduction -------------- Cocoa supports two complementary wind reduction mechanisms that modify wind velocity over land before computing wind stress. These are particularly important for storm surge simulations where overland wind fields need to account for surface roughness and canopy sheltering. Both reductions read spatially-varying data from the mesh file (configured in the ``physics`` section; see :ref:`wind-reduction-config`) and are applied **per meteorological source** at composition time, gated by each source's ``wind_reduction`` flag (see `Per-source application`_). A source's wind is corrected *before* it is blended into the combined field, so co-located sources can be treated differently -- e.g. a parametric vortex reduced to a land surface wind while a background GFS field, already a surface wind, is left unreduced. Directional Roughness Reduction ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When ``surface_directional_roughness: mesh`` is specified, Cocoa applies a direction-dependent wind reduction based on upwind land surface roughness. This reproduces ADCIRC's ``ApplyDirectionalWindReduction`` subroutine. The algorithm: 1. Determines the wind direction at each node 2. Interpolates the land roughness length from the two nearest directional bins (12 bins at 30-degree spacing) 3. Computes the marine roughness from the current drag coefficient 4. Applies a reduction factor based on the roughness ratio An overland flooding correction reduces the effective land roughness when the total water depth exceeds twice the minimum depth threshold (``h0``), accounting for the reduced influence of land surface features when they are submerged. **Configuration:** .. code-block:: yaml physics: surface_directional_roughness: mesh # Read from mesh file The mesh file must contain the ``surface_directional_effective_roughness_length`` variable with shape ``[num_nodes, 12]``. See :doc:`mesh_preparation` for details on the mesh variable format. Canopy Coefficient ^^^^^^^^^^^^^^^^^^ When ``surface_canopy_coefficient: mesh`` is specified, wind velocity components are multiplied by a per-node canopy sheltering factor. This represents wind attenuation under forest canopy. **Configuration:** .. code-block:: yaml physics: surface_canopy_coefficient: mesh # Read from mesh file The mesh file must contain the ``surface_canopy_coefficient`` variable with shape ``[num_nodes]``. Values must be **binary** -- exactly ``0`` (wind stress fully suppressed at that node) or ``1`` (no canopy effect). A fractional value fails loud at startup: the per-source model folds the canopy factor into the wind before the quadratic drag law, which is exact only for a 0/1 mask. Per-source application ^^^^^^^^^^^^^^^^^^^^^^^ Each meteorological source carries a ``wind_reduction`` flag that gates *both* corrections (directional roughness and canopy) for that source. The flag defaults to ``true`` (opt-out): whatever wind field is present is reduced unless a source opts out. Set it to ``false`` for a source that should not be reduced. .. code-block:: yaml forcing: meteorological: enabled: true format: cf_netcdf filename: gfs.nc wind_reduction: false # GFS is already a surface wind -- do not reduce vortex: model: gahm tracks: [storm.dat] wind_reduction: true # reduce the parametric vortex to a land wind The gridded (reader) flag also governs the reader's moving storm grids. The flags only have an effect when the mesh carries the corresponding nodal attributes; without them, no reduction is applied regardless of the flag. With a ``domains:`` list (`Composing Multiple Sources`_), each entry carries its own ``wind_reduction`` instead of one flag for the whole reader -- see `Per-entry wind_reduction`_ for a worked example of a marine nest reduced over an already-corrected background. Processing Order ^^^^^^^^^^^^^^^^ The corrections are applied per source, as each source is composited into the combined field: 1. **Directional roughness** (if the source is flagged and the attribute is present): reduce the source's wind based on upwind land roughness, using the marine drag coefficient computed from the source's own (pre-reduction) wind speed. 2. **Canopy coefficient** (if flagged and present): multiply the source's wind components by the binary canopy factor. The corrected sources are then blended. Once the combined field is assembled, the remaining steps run once: 3. **Ramp**: scale the blended wind by the meteorological ramp factor. 4. **Wind stress**: compute kinematic stress from the ramped wind using the Garratt drag law. Because the corrections are folded into each source's wind before the drag law, a node covered by a single source produces the same stress as a post-blend reduction would; only feather/blend rings between a reduced and an unreduced source differ. Diagnostics ----------- Cocoa tracks peak wind speed and minimum atmospheric pressure at each node over the course of the simulation. These peak values are written to the output file alongside the standard elevation and velocity fields, which is useful for post-processing storm surge maxima. Complete Example ---------------- A storm surge simulation with OWI ASCII forcing on a regional mesh: .. code-block:: yaml mesh: filename: "gulf_mesh.nc" projection: type: "EquidistantCylindrical" center: [-90.0, 25.0] simulation: start_time: 2026-08-01 end_time: 2026-08-10 time_step: 10s initial_conditions: water_level: 0.0 physics: manning_n: mesh tau0: mesh cf_lower_limit: 0.001 smagorinsky_coefficient: 0.2 numeric: solver: explicit gwce_coefficients: [0.0, 1.0, 0.0] forcing: ramp: enabled: true duration: 2d meteorological: enabled: true format: owi_ascii filenames: - pressure: "basin_scale.pre" wind: "basin_scale.wnd" - pressure: "inner_region.pre" wind: "inner_region.wnd" tide: potential: enabled: true type: astronomical boundary: enabled: true constituents: - name: M2 frequency: 1.405189028e-04 nodal_factor: 0.964 equilibrium_arg: 23.06 boundary_values: - { amplitude: 0.50, phase: 340.0 } - { amplitude: 0.48, phase: 342.0 } output: filename: "surge_output.nc" step_interval: 60 diagnostics: screen_interval: 3600