========== 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: .. code-block:: bash 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. .. _flow-channel-test: 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 m\ :sup:`3`/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, :math:`\tau_0` = 0.005, explicit (lumped) GWCE solver with :math:`\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. .. figure:: ../_static/images/test_cases/flow_channel_results.png :alt: Flow channel (type 22) results showing elevation and velocity maps with probe time series :width: 100% :align: center 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:** .. code-block:: yaml 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 m\ :sup:`3`/s inflow: .. code-block:: yaml 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 .. _flow-channel-radiation-test: 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 m\ :sup:`3`/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 :math:`Q_{\text{force}}` formulation (see :ref:`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. .. figure:: ../_static/images/test_cases/flow_channel_radiation_results.png :alt: Flow channel with radiation (type 32) results showing tidal oscillation superimposed on flow :width: 100% :align: center 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:** .. code-block:: yaml 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: .. code-block:: yaml 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 .. _wnat-test: 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, :math:`\tau_0` = 0.005, explicit solver with :math:`\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. .. figure:: ../_static/images/test_cases/wnat_results.png :alt: Western North Atlantic results showing tidal and meteorological forcing :width: 100% :align: center 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): .. code-block:: yaml 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-test: 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), :math:`\tau_0` = 0.0022, Smagorinsky lateral viscosity with coefficient 0.2, implicit (consistent) GWCE solver with :math:`\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. .. figure:: ../_static/images/test_cases/global_case_results.png :alt: Global ocean results showing tidal amphidromic patterns :width: 100% :align: center 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:** .. code-block:: yaml 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 :doc:`global_simulations`. **Location:** ``global_case/`` in the `cocoa-examples `_ repository .. _channel-quad-validation-test: 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 :ref:`channel-validation-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). ``utils/plot_cocoa.py`` 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. **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 :doc:`channel_validation`. **Regenerating the mesh fixtures**, and verifying the committed copies byte for byte: .. code-block:: bash 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``): .. code-block:: bash test/regenerate_references.sh --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.