Test Cases

Cocoa includes several test cases that exercise different features and serve both as verification benchmarks and as examples for users to follow. Each test case includes a mesh, configuration file, and a plotting script that generates a summary figure.

The runnable cases live in their own repository, cocoa-examples, which carries additional cases beyond those described here along with configurations for checkpoint/restart, the implicit solver, and output control:

git clone https://github.com/cocoaorg/cocoa-examples
cd cocoa-examples/flow_channel
cocoa -i cocoa_config.yaml

The descriptions below cover the expected behavior and verification figures. The repository is the place to look for how to run a case.

The configurations shown leave physics.wetting_velocity at its default, linear. The integration fixtures under test/data/ in this repository pin wetting_velocity: inertial, the form Cocoa used for every run before 0.6.0, so their committed references carry that wetting rule.

Channel with Flow

A simple rectangular channel with a specified-flow (type 22) boundary on the upstream (left) end and an open tidal boundary on the downstream (right) end. The top and bottom are land boundaries.

Mesh: 100 x 25 node grid, 10 m uniform depth, ~50 km long channel oriented east-west near the equator.

Forcing: 10,000 m3/s steady inflow on the upstream boundary. The tidal boundary has zero amplitude (still water at the outlet). A 2-day hyperbolic tangent ramp is applied to the flow.

Physics: Manning’s n = 0.025, \(\tau_0\) = 0.005, explicit (lumped) GWCE solver with \(\Delta t\) = 2 s, 3-day simulation.

Expected behavior: Water level rises smoothly during the ramp period and reaches a steady gradient by day 2. The upstream end shows the highest elevation (~0.012 m) with a monotonic decrease toward the downstream open boundary. Velocity is approximately uniform across the channel width.

Flow channel (type 22) results showing elevation and velocity maps with probe time series

Fig. 15 Flow channel results. Top row: peak elevation and velocity maps. Middle row: final timestep snapshots. Bottom row: elevation (top) and velocity (bottom) time series at four probe locations along the channel centerline.

Configuration:

output:
  step_interval: 2h

diagnostics:
  screen_interval: 2h

mesh:
  filename: "flow_channel.nc"
  projection:
    type: "EquidistantCylindrical"
    center: [-89.0, 29.0]

simulation:
  start_time: 2026-01-01
  end_time: 2026-01-04
  time_step: 2s

physics:
  manning_n: 0.025
  cf_lower_limit: 0.0025
  tau0: 0.005

forcing:
  ramp:
    enabled: true
    duration: 2d

  tide:
    potential:
      enabled: false
    boundary:
      enabled: true
      constituents:
        - name: M2
          frequency: 1.4051890280e-04
          nodal_factor: 1.0
          equilibrium_arg: 0.0
          boundary_values:    # 26 open boundary nodes, all zero amplitude
            - { amplitude: 0.0, phase: 0.0 }
            # ... (repeated for all 26 nodes)

  flow_boundary:
    enabled: true
    ramp:
      enabled: true
      duration: 2d
    segments:
      - segment_index: 0
        time_series_file: "flow_segment0.yaml"

initial_conditions:
  water_level: 0.0

The flow time series file (flow_segment0.yaml) prescribes a constant 10,000 m3/s inflow:

time_series:
  - { datetime: "2026-01-01 00:00:00", flow: 10000.0 }
  - { datetime: "2026-02-01 00:00:00", flow: 10000.0 }

The tidal boundary is configured with zero amplitude at all nodes, which amounts to a still-water Dirichlet condition at the downstream open boundary; the tidal forcing section must still be present.

Location: flow_channel/ in the cocoa-examples repository

Channel with Sommerfeld Radiation Boundary and Specified Flow

The same rectangular channel as the type 22 case, but the upstream boundary uses type 32 (specified flow + Sommerfeld radiation) instead of type 22. An M2 tidal signal with 0.005 m amplitude is applied at the downstream open boundary.

Mesh: Identical grid geometry to the flow channel case, but with boundary code 32 on the upstream segment.

Forcing: 10,000 m3/s steady inflow with a prescribed elevation of 0.012 m on the upstream boundary. The downstream boundary has an M2 tidal signal (0.005 m amplitude). Both tidal and flow ramps are 2 days.

Physics: Same as the flow channel case.

Expected behavior: The flow-driven stage ramps up similarly to the type 22 case, but with a superimposed tidal oscillation visible at all probe locations. The radiation term in the \(Q_{\text{force}}\) formulation (see flow boundaries in the GWCE) allows the tidal signal arriving at the upstream boundary to pass through rather than fully reflecting. The upstream probes show the tidal oscillation superimposed on the steady-state flow elevation.

Flow channel with radiation (type 32) results showing tidal oscillation superimposed on flow

Fig. 16 Flow channel with radiation results. The tidal signal from the downstream boundary propagates through the channel and is visible at all probe locations. The upstream probes show tidal oscillation superimposed on the flow-driven stage.

Configuration:

output:
  step_interval: 60

diagnostics:
  screen_interval: 3600

mesh:
  filename: "flow_channel_radiation.nc"
  projection:
    type: "EquidistantCylindrical"
    center: [-89.0, 0.0]

simulation:
  start_time: 2026-01-01
  end_time: 2026-01-04
  time_step: 2s

physics:
  manning_n: 0.025
  cf_lower_limit: 0.0025
  tau0: 0.005

numeric:
  solver: explicit
  gwce_coefficients: [0.0, 1.0, 0.0]

forcing:
  ramp:
    enabled: true
    duration: 2d

  tide:
    potential:
      enabled: false
    boundary:
      enabled: true
      constituents:
        - name: M2
          frequency: 1.4051890280e-04
          nodal_factor: 1.0
          equilibrium_arg: 0.0
          boundary_values:    # 26 open boundary nodes, uniform 0.005 m
            - { amplitude: 0.005, phase: 0.0 }
            # ... (repeated for all 26 nodes)

  flow_boundary:
    enabled: true
    ramp:
      enabled: true
      duration: 2d
    segments:
      - segment_index: 0
        time_series_file: "flow_segment0.yaml"

initial_conditions:
  water_level: 0.0

The flow time series file for this case includes an elevation field, which is required for the radiation term on type 32 boundaries:

time_series:
  - { datetime: "2026-01-01 00:00:00", flow: 10000.0, elevation: 0.012 }
  - { datetime: "2026-02-01 00:00:00", flow: 10000.0, elevation: 0.012 }

Location: flow_channel_radiation/ in the cocoa-examples repository

Western North Atlantic (WNAT)

A coarse Western North Atlantic domain with tidal and meteorological forcing. This test exercises the full simulation pipeline including tidal boundary conditions, tide potential, meteorological forcing, and wind stress.

Mesh: Coarse unstructured triangular mesh covering the Western North Atlantic, Gulf of Mexico, and Caribbean Sea.

Forcing: M2 tidal constituent on the open ocean boundary with spatially varying amplitude and phase. Tide potential forcing is enabled. CF-NetCDF meteorological forcing provides wind and pressure fields. A 1-day ramp is applied.

Physics: Manning’s n = 0.025, \(\tau_0\) = 0.005, explicit solver with \(\Delta t\) = 20 s, 22-day simulation.

Expected behavior: Tidal oscillations dominate at all probe locations. The Gulf of Mexico probe shows ~1 m tidal range. Wind-driven surge is visible as a low-frequency modulation of the tidal signal. Velocity patterns show strong currents through the Florida Straits and along the shelf break.

Western North Atlantic results showing tidal and meteorological forcing

Fig. 17 WNAT results. Top row: peak elevation and velocity maps. Middle row: peak wind speed and minimum atmospheric pressure. Bottom row: elevation and velocity time series at four probe locations (Gulf of Mexico, Atlantic Coast, Caribbean West, and Florida Straits).

Configuration (CF-NetCDF meteorological forcing variant):

output:
  step_interval: 180

diagnostics:
  screen_interval: 180
  log_file: log.json

mesh:
  filename: "wnat.nc"
  projection:
    type: "EquidistantCylindrical"
    center: [-76.572766, 23.241697]

simulation:
  start_time: 2026-01-15
  end_time: 2026-02-06
  time_step: 20s

physics:
  manning_n: 0.025
  cf_lower_limit: 0.0010
  tau0: 0.0050000

numeric:
  solver: explicit
  gwce_coefficients: [0.0, 1.0, 0.0]

forcing:
  meteorological:
    enabled: true
    format: cf_netcdf
    filename: "example_met_data/cf_meteo_example.nc"
  ramp:
    enabled: true
    duration: 1d
  tide:
    potential:
      enabled: true
    boundary:
      enabled: true
      constituents:
        - name: M2
          frequency: 1.405189028e-04
          nodal_factor: 0.964086
          equilibrium_arg: 23.057068
          boundary_values:    # 55 open boundary nodes
            - { amplitude: 5.2403779870e-01, phase: 344.168513 }
            - { amplitude: 5.1732976110e-01, phase: 345.153162 }
            # ... (55 nodes with spatially varying amplitude and phase)

This test case also ships with two alternative meteorological forcing configurations: cocoa_config_owi_ascii.yaml (OWI ASCII format) and cocoa_config_owi_netcdf.yaml (OWI NetCDF format). The only difference between the three is the forcing.meteorological section; all other parameters are identical.

Location: wnat/ in the cocoa-examples repository (configurations and met data), test/data/wnat/ in this repository (integration test data and reference solutions)

Global Ocean

A global ocean mesh test case that exercises coordinate rotation, Mercator projection, and self-attraction and loading (SAL) tide corrections.

Mesh: Global unstructured triangular mesh with coordinate rotation (pole relocated to avoid the North Pole singularity).

Forcing: Astronomical tide potential with SAL correction. No meteorological forcing. A 5-day ramp is applied.

Physics: Manning’s n from mesh (spatially varying), \(\tau_0\) = 0.0022, Smagorinsky lateral viscosity with coefficient 0.2, implicit (consistent) GWCE solver with \(\Delta t\) = 720 s, 31-day simulation.

Expected behavior: Tidal patterns reproduce the expected global amphidromic system. Large tidal ranges appear in shallow shelf regions (Bay of Fundy, Patagonian shelf, European shelf). Deep ocean amplitudes are modest (~0.5 m). The implicit solver allows the large time step.

Global ocean results showing tidal amphidromic patterns

Fig. 18 Global ocean results. Top: peak elevation map showing the amphidromic tidal pattern. Middle: peak velocity map. Bottom: elevation and velocity time series at four probe locations (North Atlantic, North Pacific, South Atlantic, Indian Ocean).

Configuration:

output:
  step_interval: 5

diagnostics:
  screen_interval: 60
  log_file: log.json

mesh:
  filename: "global.nc"
  projection:
    type: "Mercator"
    center: [0.0, 45.0]
  rotation:
    enabled: true
    pole_location: [114.16991, 0.77432]

simulation:
  start_time: 2025-01-01
  end_time: 2025-02-01
  time_step: 12m

physics:
  manning_n: "mesh"
  cf_lower_limit: 0.0010
  tau0: 0.0022222
  smagorinsky_coefficient: 0.2
  min_viscosity: 1.0e-6

numeric:
  solver: implicit
  gwce_coefficients: [0.5, 0.5, 0.0]

forcing:
  ramp:
    enabled: true
    duration: 5d
  tide:
    sal:
      enabled: true
    potential:
      enabled: true

initial_conditions:
  water_level: 0.0

The features this configuration exercises – pole rotation, the Mercator projection, mesh-based friction, SAL, the implicit solver’s 720-second step, and the absence of a tidal boundary section – are explained key by key under Complete Example in Global Simulations.

Location: global_case/ in the cocoa-examples repository, test/data/global_case/ in this repository (integration test data and reference solutions)

Ideal Channel (Quadrilateral Validation)

A family of idealized channels used to validate the quadrilateral-element CG solver against triangle-only and hybrid meshes at matched resolution, and both against ADCIRC. Unlike the cases above, these live entirely in this repository as committed fixtures – mesh, configuration and reference solution together under test/data/channel/ – rather than in cocoa-examples.

Mesh: Four planforms, each 200 m wide, generated by test/data/channel/make_channel_meshes.py. The straight planform runs 4000 m at constant direction over a flat 10 m bed; meander covers the same 4000 m of arc length over the same bed as a straight 500 m lead-in, a sine-generated reach of three full wavelengths at theta(s) = 30 deg * sin(2*pi*(s - 500 m) / 1000 m), and a straight 500 m lead-out, so both its forcing boundaries sit on straight channel and the centerline is C1 at the two junctions; beach keeps the straight plan geometry and tilts the bed so a tide moves a wet/dry shoreline across it; skewbeach adds a cross-channel tilt to the beach so the shoreline crosses the mesh obliquely. Each planform exists in element-type variants on the same node set – quadrilateral-only, triangle-only, hybrid with quadrilaterals in the middle third, and for the two flat-bed planforms alternating-diagonal triangle and hybrid variants (triangle_alt, hybrid_alt) – at coarse and fine resolution. The generator self-checks every face for positive (CCW) signed area and quadrilateral convexity before writing. Every mesh also carries explicit mainland natural (ADCIRC type 20) boundary strings along the side walls, and along the tail where the tail is not already an open or flow boundary, so land-boundary code runs on the quad-adjacent wall nodes rather than the wall being only weakly closed.

Forcing:

  • Co-oscillating tide (cocoa_config_channel_straight_tide[_implicit].yaml, straight channel only): a single M2 elevation boundary (0.5 m amplitude) at the channel head, closed elsewhere, frictionless (manning_n: 0.0) so the response is the linear standing wave the analytic solution assumes.

  • Steady uniform flow (cocoa_config_channel_straight_flow.yaml, straight channel only): a specified-flow boundary (ADCIRC type 22, 200 m^3/s prescribed total flow) at the head paired with a fixed z = 0 m boundary at the tail, Manning’s n = 0.025.

  • Steady flow around the bends (cocoa_config_channel_meander_flow[_implicit].yaml, meander only): the same pair of boundaries at the validation study’s 2000 m^3/s and Manning’s n = 0.025, closed with a constant lateral eddy viscosity of 20 m^2/s (smagorinsky_coefficient: 0.0 with min_viscosity: 20.0, since nu = max(min_viscosity, Cs * Area * abs(S))); why the closure is constant is under Meander.

Physics: Explicit (lumped) and implicit (consistent) GWCE variants for the tide case and for the meander flow case; the straight uniform-flow case is lumped only – the consistent solver reproducibly diverges on it partway through the ramp at that case’s 4 s step, and so does ADCIRC’s. It does not do so on the meander, which runs a quarter of that case’s step: over 12 h, four times the committed length, its elevation and speed hold at 0.167 m and 1.240 m/s from the third hour on.

Expected behavior: The tide case reaches a bounded, periodic elevation response (well under the 10 m depth) on the hybrid meshes the integration tests run. The two flow cases’ committed runs are smoke cases only, not steady-state ones – 6 hours on the straight channel and 3 on the meander, where a steady state takes 24.

Hybrid output encoding: a hybrid run’s output NetCDF carries its element connectivity with an nvertex dimension of 4 and a -1 fill value in each triangle row’s unused fourth column (verified by test/data/channel/check_hybrid_element_encoding.py, wired into the hybrid integration tests). cocoa-plot reads this fill-aware: each quad is split along its 0-2 diagonal into two triangles for matplotlib’s Triangulation when rendering a field, but the mesh view (-v mesh) draws the actual face edges instead, so a quad never shows that split as a phantom diagonal.

Integration tests. The integration-labelled ctest entries that run on these fixtures, each in its own copy of test/data/channel/ under build/test/integration/<test name>/:

  • Integration_channel_straight_tide, Integration_channel_meander_flow and their _implicit variants: the coarse hybrid meshes against committed references at 1e-6 on zeta, ucx and ucy. Each has an _mpi variant that compares a 2-rank run against a 2-rank reference at 1e-3.

  • Integration_channel_straight_flow: the uniform-flow smoke case (completes, finite, bounded, hybrid encoding checked), lumped only.

  • Integration_hybrid_tide_lumped and Integration_hybrid_tide_consistent: an M2 smoke run per mass matrix on channel_hybrid_tide.nc, with the encoding check above; Integration_hybrid_tide_mpi_lumped and _consistent hold a 2-rank run to the serial one.

  • Integration_beach_cut_wetdry_mpi: the coarse triangle beach on 2 ranks through a committed partition cache (channel_beach_coarse_triangle_tide.nc.partition_2.nc, written by make_beach_cut_partition.py) whose cut the tidal front crosses. Every output’s wet/dry set must equal the serial run’s.

  • Integration_broad_inlet_flow_mpi2/3/4 and Integration_two_inlets_flow_mpi2/3/4: wide specified-flow inlets the partitioner has to cut, held to the serial run on the fields and on the cross-section discharge.

  • Integration_surplus_tide_values_rejected and its _mpi variant: a tidal boundary_values list one entry longer than the open boundary must be rejected the same way at every rank count.

The MPI entries are registered only when MPI tests are enabled. Two Fixture_channel_* entries also check the fixtures themselves: the byte-for-byte generator check below, and a cocoa-plot render of the hybrid strip’s mesh.

Zoltan2 partition weights. Zoltan2 partitions elements unweighted by default, and a quadrilateral is roughly 1.8x a triangle’s assembly work, so a count-balanced partition of a mesh whose quadrilaterals are segregated in one region leaves the quad-heavy ranks behind. On the segregated 60x20 strip (channel_segregated_strip.nc: all triangles on one half, all quadrilaterals on the other) an unweighted 4-rank partition splits 448/455/451/446 elements but 494/315/238/234 owned nodes (imbalance 1.54). The type weight 1.8 was chosen by measuring per-rank step time on that mesh (3600 steps, median of three): 4.453 s unweighted, 4.238 s at weight 1.33, 4.184 s at 1.8 (owned-node imbalance 1.09), and 2.5 turns back up to 1.15 – about 6% faster than unweighted. The weighting applies only when the mesh has quadrilaterals; a triangle-only partition and its cache are byte-identical to the unweighted case.

Quantitative results. The comparison of every study against its analytic solution and against ADCIRC, at every tier and element variant, with figures, the convergence orders and the validation ctest label that runs them, is Ideal Channel Validation.

Regenerating the mesh fixtures, and verifying the committed copies byte for byte:

cd test/data/channel
python make_channel_meshes.py .
python make_channel_meshes.py --check-committed .

Regenerating the committed integration references (toolchain-locked; run inside the CI container, never from a host build – see test/regenerate_references.sh):

test/regenerate_references.sh --cocoa <container-built cocoa> channel

Location: test/data/channel/ in this repository (mesh generator, configurations, reference solutions, and the hybrid-encoding check script); the comparison scripts (compare_bitwise.py, compare_tides_reference.py) live in test/data/wnat/ and are shared across test areas.

Internal Weir

A small tidal domain cut by an internal weir. It exercises the internal weir boundary under explicit tidal forcing, and checkpoint/restart with weirs present. A runnable copy of the case is internal_weir/ in cocoa-examples; the test fixtures and reference below are in this repository. The weir boundary itself is described under Weir Boundaries.

Mesh: internal_weir.nc: 992 nodes and 1800 triangles spanning 89.0–88.7 deg W and 29.0–29.075 deg N, with depths from 2 to 10 m. One open boundary of 16 nodes and one internal weir boundary (code 24) of 16 node pairs.

Forcing: M2 on the open boundary at 0.5 m amplitude and zero phase on every node, nodal factor 1.0, equilibrium argument 0. No tide potential and no meteorological forcing. A 12-hour ramp is applied.

Physics: Manning’s n = 0.025, \(\tau_0\) = 0.005, explicit solver with \(\Delta t\) = 2 s, 1-day simulation, output every 15 minutes.

Integration tests:

  • Integration_internal_weir: compares zeta at every output time against weir_reference.nc at 1e-6; the wet/dry pattern must match. Integration_internal_weir_mpi holds a 2-rank run to the same reference at 1e-3.

  • Integration_weir_checkpoint: runs the full day, then a cold start to 12:00 that writes a checkpoint, then a restart from it to the end. The restart’s zeta must match the full run at shared output times to 1e-10. Integration_weir_checkpoint_mpi runs it on 2 ranks at 1e-3.

  • Integration_internal_tide_checkpoint_mid_window (in test/data/internal_tide_checkpoint/): the same checkpoint test with internal-tide wave drag active (Baroclinic Coupling). The mesh internal_weir_it.nc is the weir mesh plus a 3-column (Lyard) internal_tide_friction attribute. The forcing file internal_tide_baroclinic_forcing.nc is a single frozen snapshot with n_bottom = 0.02, n_mean = 0.01 and zero BPG. The cold start ends at 11:55:00 (42900 s), not a multiple of the 720 s de-tiding sampling window, so the restart must resume the de-tiding ring mid-window. Its configs add physics.internal_tide_friction: mesh and a forcing.baroclinic block with internal_tide.enabled: true. Tolerance 1e-10; the _mpi variant runs on 2 ranks at 1e-3.

Configuration (forcing section of cocoa_config_integration_test.yaml):

forcing:
  ramp:
    enabled: true
    duration: 12h
  tide:
    potential:
      enabled: false
    boundary:
      enabled: true
      constituents:
        - name: M2
          frequency: 1.4051890280e-04
          nodal_factor: 1.0
          equilibrium_arg: 0.0
          boundary_values:    # 16 open boundary nodes
            - { amplitude: 0.5, phase: 0.0 }
            # ... (same value on all 16 nodes)

Location: test/data/internal_weir/ and test/data/internal_tide_checkpoint/ in this repository (meshes, configurations, generators and the reference solution)

GAHM Parametric Vortex (Katrina 2005)

Hurricane Katrina (2005) driven by the GAHM parametric vortex over the WNAT mesh. The fixture exercises vortex-only forcing and a vortex composed with a gridded reader. The vortex model and its configuration keys are described under Parametric Vortex (GAHM).

Mesh: wnat.nc, the WNAT mesh.

Forcing: GAHM vortex from the ATCF track test/data/vortex/katrina_bal122005.dat. No tidal forcing. A 1-hour ramp is applied. The composed configs add a CF-NetCDF reader on example_met_data/cf_meteo_example.nc with a blend_factor of 1.25. That file’s data is in 2026, so the reader never covers the 2005 window and every step takes the vortex-over-background path.

Physics: Manning’s n = 0.025, \(\tau_0\) = 0.005, explicit solver with \(\Delta t\) = 20 s, 6-hour simulation from 2005-08-28 00:00. Output every 2 hours; velocity, pressure and wind output are off, since zeta is the regression quantity.

Integration tests: each compares zeta against a committed reference at 1e-6 with compare_tides_reference.py.

  • Integration_gahm_vortex_only: the vortex alone, with no blend ring, against gahm_vortex_only_reference.nc.

  • Integration_gahm_composed: the vortex plus the out-of-window gridded reader, against gahm_composed_reference.nc. It guards that the vortex does not vanish outside the reader’s time window.

  • Integration_gahm_composed_domains: the composed config with the gridded reader moved into a one-entry domains: list. It runs against the same reference as the flat form, so a domains: singleton must be indistinguishable from the flat keys.

Configuration (forcing section of cocoa_config_gahm_composed.yaml):

forcing:
  meteorological:
    enabled: true
    format: cf_netcdf
    filename: "example_met_data/cf_meteo_example.nc"
    vortex:
      model: gahm
      tracks:
        - "../vortex/katrina_bal122005.dat"
      blend_factor: 1.25
  ramp:
    enabled: true
    duration: 1h

cocoa_config_gahm_vortex_only.yaml drops format, filename and blend_factor.

Location: test/data/wnat/ (configurations and references) and test/data/vortex/ (track file) in this repository

Sea Ice (WNAT)

Two WNAT runs exercise sea-ice drag attenuation. The ice feature is described under Ice Concentration, and the design of these tests under Testing.

Mesh: wnat.nc, the WNAT mesh.

Forcing: The 1-day WNAT integration setup (cocoa_config_integration_test.yaml): M2 on the open boundary, tide potential with SAL, CF-NetCDF wind and pressure from cf_meteo_example.nc, and a 12-hour ramp. Both runs set meteorological.ice: true.

  • Zero ice: the met file is cf_meteo_zero_ice.nc, a copy of cf_meteo_example.nc plus an all-zero siconc variable. It is not committed; the Fixture_wnat_zero_ice_source ctest entry generates it with generate_cf_meteo_zero_ice.py.

  • Moving ice: a domains: list composes the wind and pressure source with the ice-only wnat_moving_ice.nc (generate_wnat_moving_ice.py), an ice edge that sweeps south across roughly the northern third of the mesh footprint over 24 hours. ice_drag_law: lupkes.

Physics: Manning’s n = 0.025, \(\tau_0\) = 0.005, explicit solver with \(\Delta t\) = 20 s, 1-day simulation, output every 10 hours.

Integration tests:

  • Integration_wnat_zero_ice: runs the no-ice config and the zero-ice config with the same binary. The primary check requires the two outputs to be bitwise identical (tolerance 0.0). A secondary check compares the zero-ice run against wnat_reference.nc at 1e-6. The zero-ice config turns off ice_concentration output so the two variable sets match.

  • Integration_wnat_moving_ice: compare_ice_moving.py checks that zeta differs from a no-ice run by more than 1e-4, that ice_concentration matches an independent bilinear interpolation of wnat_moving_ice.nc at every output time to 1e-6, and that the run matches wnat_moving_ice_reference.nc at 1e-6.

test/data/ice_drag/lupkes_golden.csv backs a unit test, not an integration test: generate_lupkes_golden.py computes the Lupkes drag blend in Python from the paper formulas, and test_ice_drag.cpp holds the C++ implementation to it at 1e-12.

Configuration (meteorological section of cocoa_config_moving_ice_test.yaml):

forcing:
  meteorological:
    enabled: true
    ice: true
    domains:
      - format: cf_netcdf
        filename: "example_met_data/cf_meteo_example.nc"
      - format: cf_netcdf
        filename: "wnat_moving_ice.nc"
        variables:
          ice: siconc
    ice_drag_law: lupkes

Location: test/data/wnat/ (configurations, generators, check script and reference) and test/data/ice_drag/ (golden table) in this repository

Baroclinic Forcing (WNAT)

Two WNAT runs exercise baroclinic coupling from a preprocessor-generated nodal forcing file. The coupling is described in Baroclinic Coupling, whose Regression checks section states the invariants these tests pin.

Mesh: wnat.nc for the zero-forcing test. The composed test uses wnat_baroclinic_composed.nc, the WNAT mesh plus a 3-column (Lyard) internal_tide_friction attribute (make_baroclinic_composed_mesh.py), for both its runs.

Forcing: The implicit WNAT setup (cocoa_config_implicit_test.yaml): M2 on the open boundary, tide potential with SAL, no meteorological forcing, and a 12-hour ramp. The baroclinic forcing has its own 12-hour ramp.

  • Zero forcing: baroclinic_zero_forcing.nc, all zeros (make_baroclinic_zero_forcing.py).

  • Composed: baroclinic_composed_forcing.nc (make_baroclinic_composed_forcing.py) drives the BPG, free-surface correction, internal-tide drag and dispersion together.

Physics: Manning’s n = 0.025, \(\tau_0\) = 0.005, implicit solver with gwce_coefficients: [0.35, 0.30, 0.35] and \(\Delta t\) = 60 s, 1-day simulation, output every 6 hours.

Integration tests: both run only on host backends. They require run-to-run determinism, and atomic scatter on GPU backends is not deterministic.

  • Integration_WNAT_baroclinic_zero_forcing: runs the implicit config with baroclinic off, then with baroclinic on against the all-zero file. compare_bitwise.py requires the outputs to be bitwise identical and the extra bpg_effective_x/bpg_effective_y outputs to be exactly zero.

  • Integration_WNAT_baroclinic_composed: runs cocoa_config_baroclinic_composed_off.yaml and _on.yaml. The ON run must differ from the OFF run by more than 0.05 m in zeta at the final output time, or in its wet/dry set. It must also match baroclinic_composed_reference.nc at 1e-6.

test/data/baroclinic/ holds unit-test fixtures, not integration ones: a 25-node tiny_mesh.nc and a 3-snapshot nodal_forcing.nc with analytic values (make_test_fixture.py), malformed variants of that file for the reader’s error paths, and ADCIRC-derived control tables for the internal-tide drag and dispersion-friction unit tests under reference/.

Configuration (baroclinic keys of cocoa_config_baroclinic_composed_on.yaml):

physics:
  internal_tide_friction: mesh

forcing:
  baroclinic:
    enabled: true
    filename: "baroclinic_composed_forcing.nc"
    internal_tide:
      enabled: true
    dispersion:
      enabled: true
      mode: mld
      deep_water_depth: 1000.0
      mld_coefficients:
        a: 0.01
        b: 0.5

The OFF config is the same file without these keys.

Location: test/data/wnat/ (configurations, generators, forcing files and reference) and test/data/baroclinic/ (unit-test fixtures) in this repository