=========
Changelog
=========
All notable changes to Cocoa will be documented in this file.
The format is based on `Keep a Changelog `_,
and this project adheres to `Semantic Versioning `_.
[0.7.2] - 2026-10-17
--------------------
A patch release: smaller GPU images, and a ``--device-info`` command that
shows which GPU Cocoa found and which target the build was compiled for. The
model, its configuration and its output files are unchanged from 0.7.1.
Added
^^^^^
- ``cocoa --device-info`` prints the version and the execution-space report a
run opens with, then exits; like ``--version`` it needs no ``-i``. The
container guide uses it to confirm that the GPU is visible and the right
build was selected.
- On a GPU, the startup report names the target the build was compiled for
(``Compiled For: sm_70``, ``gfx942``) beside the device's compute capability
or GCN architecture.
Changed
^^^^^^^
- The CUDA image runs on NVIDIA's ``runtime`` base instead of ``devel``, and
the ROCm image on AMD's plain ``dev`` base with rocBLAS, rocSPARSE,
rocSOLVER and hipBLASLt added, instead of ``-complete``. The images shrink
from 15.8 to 9.8 GB (CUDA) and from 24.5 to 13.6 GB (ROCm).
Fixed
^^^^^
- With no GPU visible, ``source select_cocoa`` reported the image's default
build as detected from the GPU. It now says whether the build was detected,
is the image default or is pinned by ``COCOA_ARCH``.
- On a GPU build with no device visible (a container started without
``--gpus`` or ``--nv``), a run aborted at startup with a core dump. It now
prints the reason, with a hint to pass the GPU into the container, and
exits 1; ``--device-info`` does the same.
[0.7.1] - 2026-10-17
--------------------
A container-only patch release; the model, its configuration and its output
files are unchanged from 0.7.0.
Changed
^^^^^^^
- The images take their Python dependencies from the cocoa-tools package
alone, still pre-fetch the Natural Earth coastlines, and build matplotlib's
font cache for the ``cocoa`` user.
- The images add nano, zstd, zip, unzip, rsync, tmux, bc, pv, ripgrep, strace,
``ip``, ``ping``, netcat and the HDF5 command-line tools, and put ``ncdump``,
``nccopy`` and ``ncgen`` on ``PATH``.
Fixed
^^^^^
- ``cocoa-plot --help`` ended with a note that read as if cartopy were not
installed.
[0.7.0] - 2026-10-16
--------------------
Cocoa now writes time series at stations, in a file of their own, and can
limit global output to a window of the run. The output configuration and the
output files' metadata change to make room for them, so existing
configurations and post-processing scripts need small edits; read
**Changed** before upgrading.
Added
^^^^^
- ``output.stations`` writes time series at listed points to
``cocoa_station_output.nc``, a CF-1.8 timeSeries file that xarray opens
directly (#70). Stations have their own interval (by default the global
one), time window and variable selection, and can be listed inline or in
a separate file through ``include:``. Every time-series variable of the
global file is available. Each station interpolates from the element that
contains it, linearly on triangles and bilinearly on quadrilaterals, and
the file does not depend on how the mesh is partitioned. A partly wet
element follows ADCIRC's station options through
``output.stations.partially_wet``: ``nearest_wet_depth`` (the default,
unlike ADCIRC, so shoreline stations report data), ``nearest_wet`` or
``fill``. Samples are buffered on the GPU: 200 stations sampled every step
of a Katrina run cost 0.7 % of stepping on a V100.
- ``output.global.start_time`` and ``output.global.end_time`` limit
time-series output to a window of the run (#69). Slices fall on
``start_time + k * step_interval``; peak variables and checkpoints ignore
the window.
Changed
^^^^^^^
- The global output file's keys move under ``output.global``:
``step_interval``, ``filename``, ``mode`` and ``variables``.
``output.compression`` stays under ``output`` and covers every output
file. A configuration with the old flat keys fails schema validation; to
upgrade, indent them under ``global:``.
- Output files name their node and station coordinates ``lon`` and ``lat``
instead of ``x`` and ``y``; scripts that read ``x``/``y`` from an output
file need the new names. ``ucx``/``ucy`` keep their names and carry the
``eastward_sea_water_velocity``/``northward_sea_water_velocity`` standard
names, vector long names say eastward and northward, and ``depth`` is
marked ``positive: down``. Mesh and checkpoint files keep ``x``/``y``.
Fixed
^^^^^
- On a rotated-pole mesh, ``bpg_effective`` was written in the rotated
computational frame. It is now rotated back to geographic east/north like
velocity.
- A ``simulation.end_time`` with a fractional-second remainder (for example
``01:00:00.300`` at a 1 s step) passed the run-length check, which counted
whole seconds, and the run then crashed. It is now rejected at startup.
[0.6.0] - 2026-10-11
--------------------
Partitioned runs reproduce the serial run, the implicit solver is about a
fifth faster on multiple GPUs, the wetting rule can be chosen to match
ADCIRC's, and Cocoa's Python tools become one installable package. Several
defaults and validation rules change; read **Changed** before upgrading.
Added
^^^^^
- ``physics.wetting_velocity`` selects the nodal wetting test's velocity
estimate: ``linear``, ``quadratic`` or ``inertial``, ADCIRC's default,
``directvelWD`` and ``directvelWD`` + ``useHF`` forms (``inertial`` is the
one Global STOFS runs).
- Wet-balanced partitioning (``mesh.partition.wet_balance``): ``none`` (the
default), ``initial_condition``, or a water level in metres. With a wet set,
ParMETIS balances each element's share of the nodes that start wet
alongside element cost, to a 1 % tolerance, so ranks finish their wet/dry
work together. The partition cache records the rule that built it; a cache
built under a different rule is reused with a warning naming both.
- ``cocoa-mesh-tools reorder``, and ``from_adcirc --reorder``, sort a mesh's
elements by their lowest-numbered node so that GPU warps read nearby node
data. On the global STOFS mesh every element kernel ran 1.5 to 2.4 times
faster and the time step 16% faster on one V100. By default only element
order changes; ``reorder --renumber-nodes`` (``from_adcirc --reorder all``)
first renumbers the nodes in reverse Cuthill-McKee order, for meshes whose
node numbering is not already bandwidth-ordered. The new mesh id invalidates
existing partition caches and checkpoints (see the Element Ordering section
of the mesh preparation guide).
- Container builds for B300 (``blackwell103``), RTX PRO 6000 Blackwell and
the GeForce RTX 50 series (``blackwell120``), and AMD Instinct MI350X and
MI355X (``mi350``). The ``-rocm`` images move from ROCm 6.3 to ROCm 7.2,
the first series that targets the MI350.
- The container's ``cocoa`` command runs the build for the visible GPU when
``COCOA_ARCH`` is unset. It resolves the build on every call, so
``COCOA_ARCH`` now works under ``singularity exec``, ``docker exec`` and a
host ``mpirun``. Before, only ``docker run`` and ``singularity run``
honoured it: ``singularity exec`` ran the ``ampere80`` build whatever
``COCOA_ARCH`` said, and an unknown ``COCOA_ARCH`` fell back to the default
with a warning. An unknown value is now an error.
Changed
^^^^^^^
- Cocoa's Python tools are one installable package, ``cocoa-tools``
(``pip install utils/cocoa_tools``), which installs their dependencies and
three commands: ``cocoa-mesh-tools`` (formerly
``utils/cocoa_mesh_tools.py``), ``cocoa-ocean3d`` (formerly the separate
``utils/cocoa_ocean3d`` package), and ``cocoa-plot`` (formerly
``utils/plot_cocoa.py``). The containers have it installed. Its version
follows Cocoa's. See the Cocoa Tools page of the user guide.
**Breaking:** the old paths are gone. Run ``cocoa-mesh-tools`` and
``cocoa-plot`` instead of ``python utils/cocoa_mesh_tools.py`` and
``python utils/plot_cocoa.py`` (in the containers, instead of
``python3 /opt/cocoa/utils/...``), install ``utils/cocoa_tools`` instead of
``utils/cocoa_ocean3d``, and import ``cocoa_mesh_tools`` rather than loading
the script by path. ``cocoa-ocean3d`` now always stamps the mesh id on the
forcing file, since the fingerprint code is installed with it.
- ``cocoa-mesh-tools from_adcirc`` and ``group_variables`` end with a
summary of the mesh they wrote (counts, extent, depth range, boundaries by
type, nodal attributes, SAL constituents, node and element ordering, mesh
id) in place of their per-group lines, and warn when elements are left out
of GPU order, when nodal attributes Cocoa does not read are written, or, for
``group_variables``, when a source variable is not copied.
- **Behavior change:** the wetting test defaults to ADCIRC's default form
(``linear``). It was previously fixed to the ``inertial`` form, ADCIRC's
``directvelWD`` + ``useHF``, so a configuration translated from a fort.15
without a ``&wetDryControl`` namelist ran a different wetting rule than
ADCIRC did. Set ``wetting_velocity: inertial`` to keep the old behavior.
- **Behavior change:** configuration parsing is stricter. Non-finite or
hexadecimal durations (``"nans"``, ``"inf"``, ``"0x10s"``), a leading
``+`` or whitespace in durations, step intervals and numeric constants, and
non-numeric trailing text in numeric constants now fail at startup with the
key named. A flat ``cf_netcdf`` or ``owi_netcdf`` meteorological source
with more than one entry in ``filenames`` is an error, as it already was in
a ``domains`` entry; it previously read the first file and ignored the
rest.
- **Behavior change:** meshes with NaN or fill-value depths or coordinates,
latitudes beyond 90 degrees, or zero-area triangles are rejected when read,
naming the file and element.
- The stability check bounds elevation from below as well as above. The
wet/dry pass records the lowest elevation of every wet node before it dries,
so a downward runaway fails the run whether the node is shallow (and was
hidden by drying) or deep.
- **Behavior change:** the Manning friction speed is formed in double
precision. Serial results move by about 1e-7; cases with an elevation slope
limiter can move more where its thresholds amplify the change.
- Checkpoints no longer carry ``total_element_area``. Earlier checkpoints
still restore.
- A restart that would resume past the end of the run is refused, and the
output step counter is 64-bit.
- Each integration test runs in its own copy of its fixture directory, so the
suite runs in parallel without workdir locks (the sanitizer CI job fell from
133 to 50 minutes). ``cocoa_LINK_JOBS`` bounds concurrent link steps, and
the clang-tidy gate uses clang-tidy 22.1.8.
Fixed
^^^^^
- **Behavior change:** the Gall Stereographic projection's easting carries
the Earth radius. Carried over from ADCIRC's ``CYLINDERMAP`` (ICS = 24), it
was in radians while the Gall scale factors, also ADCIRC's, assume metres,
so x-gradients on a ``GallStereographic`` mesh were off by a factor of about
6.4e6 and a run diverged within its first steps. It now agrees with the
other projections: on the WNAT tide case, Gall and equidistant cylindrical
elevations differ by 0.4 % of the signal. A test now checks every
projection's scale factors against the derivatives of its own map.
- Partitioned runs reproduce the serial run. Every element-to-node wet/dry
result is combined over all ranks before any rank reads it, including at
cold start; lateral stress is summed over ranks before it is normalized
(nodes on a partition cut were off by up to 53%); and the consistent GWCE
eliminates open-boundary columns on the rank that computed each entry. On a
beach whose tidal front crosses a partition cut, the front previously
stalled at the cut (888 wet/dry mismatches, 0.37 m elevation error); it now
matches serial to output rounding, and the WNAT implicit case differs from
serial by 1.8e-7 instead of 2.6e-4.
- A rank that owns no node no longer crashes the consistent GWCE solver's
constructor.
- The consistent GWCE matrix and right-hand side use one dry-row verdict;
they used different predicates and could disagree.
- An owned node in no element (a matrix row with no diagonal) fails at
construction instead of silently contributing zero to the elevation
penalty. Construction also checks that every triangle entry has a matrix
slot and guards the column-pair gather against overflow.
- The arm64 CPU base container shipped without Ifpack2's source headers
(CMake under QEMU silently globbed no files); the base build now preloads
jemalloc for the configure and fails if headers Cocoa needs are missing.
- The installation guide and FAQ gave GCC 10.4 as the minimum compiler, but
GCC 10 cannot build Cocoa: its standard library has no floating-point
``std::from_chars``. The minimum is GCC 11, and CMake now rejects GCC
older than 11 and Clang older than 14 at configure time.
Performance
^^^^^^^^^^^
- Multi-GPU implicit runs: a 6 h global STOFS run (12.8M nodes) on two H100s
takes 87.1 s instead of 111.1 s. The consistent GWCE matrix is
fill-completed once and reused, removing a ``cudaMalloc`` and a
synchronizing ``cudaFree`` from every solve (about 40,000 of each per rank,
now about 450 at startup); ghost exchanges pack straight from the field
views into persistent buffers and skip owned rows (exchange kernels on two
ranks sharing a V100 fall from 2.0-2.6 ms to under 0.1 ms per step); and
wet-balanced partitions and element-sorted meshes keep the two GPUs within
about 2% of each other.
[0.5.1] - 2026-09-25
--------------------
A patch release: three physics fixes, current dependencies and the Trilinos
17.2.1 containers.
Changed
^^^^^^^
- **Behavior change:** the tidal potential ``order`` defaults to 3. ``order:
4`` and higher had shared the ``order: 3`` code path, so a default run (which
asked for 4) computed the P2+P3 expansion; default results are unchanged.
``order: 4`` now sums the Legendre series to degree 11, adding up to 1e-4 m
of lunar potential over P3. This departs from ADCIRC on purpose: ADCIRC's
closed form loses the Sun's contribution to cancellation, while the summed
series keeps kernel noise near 2e-16 m for both bodies.
- **Behavior change:** a lone parametric storm with no gridded background has
no blend ring. The default ring (1.25 times the 34 kt footprint) tapered the
field to zero with nothing underneath, removing the outer wind and pressure
deficit (Milton lost its field beyond about 800 km compared with ADCIRC).
Several storms with no background keep a ring for the hand-off between them,
defaulting to ``blend_factor: 5.0``; runs composed with a gridded background
keep 1.25. ``blend_factor`` is now optional and resolved from the
composition.
- Containers use Trilinos 17.2.1, which is also the default for the Slurm base
build; CPM fetches fmt 12.2.0, valijson 1.1.3, CLI11 2.7.2 and Catch2
3.16.0.
- Library headers are included with a prefix relative to ``src/`` (for example
``#include "cocoa_kernel/..."``). Out-of-tree code that included kernel
headers unprefixed must update its includes.
- Every first-party target builds with the project warnings and ``-Werror``,
NetCDF is a ``NetCDF::NetCDF`` imported target, and CMake presets replace
the configure and ctest flags CI repeated.
Fixed
^^^^^
- The rewetting path floored the Manning drag coefficient at the compiled-in
``cf_lower_limit`` and ignored the configured value, so a run that set the
key applied two different floors. Runs on the default are unchanged.
- An out-of-range SAL node id raises an error naming SAL instead of reading
past the end, and CF variable names given for a non-CF meteorological source
warn instead of being ignored.
- A ``[[no_unique_address]]`` member, which nvcc lays out differently in device
kernel parameters than the host does, is removed from the momentum element
context.
- Trilinos base images are published under ``cocoaorg/trilinos_base``,
matching the documentation.
- Documentation corrections, notably that the meteorological reader guide said
to return unscaled fields for the pipeline to scale, although nothing
downstream scales them.
[0.5.0] - 2026-09-13
--------------------
Added
^^^^^
- Quadrilateral elements. The CG solver accepts bilinear quadrilaterals and
hybrid meshes that mix triangles and quadrilaterals in any proportion, in
serial and under MPI, with no configuration change beyond the mesh file.
Quadrilaterals work throughout: wet/dry, the lumped and consistent GWCE,
momentum, baroclinic forcing, checkpoints and output. A quadrilateral mesh
is a Cocoa NetCDF file with ``max_face_nodes = 4``, where a fill value in
the fourth column marks a triangle. Each quadrilateral is checked for
orientation and convexity at load, and a bad element is reported with the
file and the element named. The ``fort.14`` converter still rejects 4-node
elements. Output has ``nvertex = 4``, and ``plot_cocoa.py`` draws
quadrilateral faces. On a hybrid global shell (1.66M quadrilaterals, 3.85M
triangles), an 18-day tide run's M2 amplitude differs from the
all-triangle shell by 6.2 mm RMS. Triangle-only meshes run the same code
as before at the same speed. See the channel validation page.
- Advection state (``physics.advection``): advection may be disabled
along the open, flow and radiation boundary strings
(``off_at_open_boundaries``), applied everywhere (``true``), disabled
everywhere (``false``), or masked per node from the mesh file's binary
``advection_state`` nodal attribute (``mesh``). An element advects only when
all of its corner nodes do, matching ADCIRC's ``ADVECTLOCAL``.
**Behavior change:** the default is ``off_at_open_boundaries``, where
advection was previously applied everywhere. Results change in a one-element
band inside the open and flow strings; land walls and weirs keep advection.
Set ``advection: true`` to restore the previous behavior.
- UGRID connectivity with ``start_index`` 0 or 1.
Changed
^^^^^^^
- The mesh id is hashed on the canonical topology, so a triangle mesh is the
same mesh, with the same checkpoints and partition caches, whether written
with ``max_face_nodes`` 3 or 4.
Fixed
^^^^^
- **Behavior change:** the specified-flux boundary integral now carries the
projection map factor. Before, a prescribed discharge was delivered as
Q / cos^m(phi0), which is 1.307 times Q on Mercator at 29 N. The discharge
is now exactly Q at any latitude and orientation, for flux and weir
boundaries on projected meshes. Internal weir results move by up to 6 mm.
- Under MPI, the prescribed discharge depended on the rank count: each
partition cut through a flux string dropped half a segment of forcing.
Discharge now agrees to 7e-5 at 1 to 4 ranks. Open-boundary tidal values
were indexed correctly only when the open boundary was the first group in
the file.
- The land-boundary no-normal-flow condition uses the physical normal, which
is exact on any map projection, not only on an equidistant map.
- Duplicate flow ``segment_index`` values, tidal tables longer than the open
boundary, boundary string indices outside the node table, 1-based
connectivity with a non-negative fill value, and degenerate flux chords
were accepted silently. Each is now a startup error.
- ``cocoa_mesh_tools.py`` resolves the fort.13 ``advection_state`` depth
thresholds into the binary mask Cocoa reads, instead of copying the depths
verbatim, and reports how many nodes lose advection.
- ``plot_cocoa.py``, ``plot_partition.py`` and the ``cocoa_ocean3d`` node
footprint handle the unused fill slot in four-column connectivity
correctly.
[0.4.4] - 2026-09-02
--------------------
Changed
^^^^^^^
- **Behavior change:** ``bpg_effective_x/y`` are no longer written by
default. Like ``tide_potential``, they are forcing diagnostics that cost
full node fields at every output interval. Set
``output.variables.bpg_effective: true`` to write them.
[0.4.3] - 2026-09-02
--------------------
Added
^^^^^
- ``forcing.meteorological.vortex.taper_a`` sets the steepness of the vortex
blend-ring taper (default 2.0, matching GAHM2026).
Fixed
^^^^^
- **Behavior change:** the parametric vortex blend ring sits at a physical
footprint radius: the outermost reported isotach radius, or else where the
fitted profile falls to half its peak, but never less than twice the
radius to maximum winds. It previously used a bracket key that, where no
radius was reported, was the radius to maximum winds itself. That switched
the vortex off at its strongest point and left a concentric ring in the
peak-wind field. The ring now tapers with GAHM2026's hyperbolic tangent in
place of a linear ramp. Any GAHM run produces different output.
- Invest-stage ATCF decks and short lines that are truncated after the
quadrant radii now load. Missing trailing columns take the same fallbacks
as blank ones. Only lines truncated before the radii are skipped, with a
log message.
- An isotach radius that lies inside the same line's radius to maximum winds
is declined rather than fitted. Machine-learning cyclone products produce
these routinely. A track left with fewer than two usable snapshots is
ignored with a warning.
[0.4.2] - 2026-08-27
--------------------
Changed
^^^^^^^
- **Behavior change:** GAHM vortex winds resolve each bracketing track
snapshot spatially (radial bracket per quadrant, then the azimuthal blend)
against its own isotach count, then interpolate the two resolved fields in
time, following ADCIRC's NWS=20 scheme. Previously, when the isotach count
changed between snapshots, the radial structure froze for the interval and
jumped at the midpoint, and slot indexing could read the next snapshot's
data or run past the end of the track. Results change on every interval;
the measured effect on the vortex-only test case is a maximum zeta
difference of about 9e-3 m.
- Configuration errors are aggregated across all sections. A throw in one
section no longer hides errors in the sections after it, so a single run
reports the whole list.
- CI runs ctest in parallel.
Fixed
^^^^^
- GAHM tracks: a skipped snapshot (a (0, 0) genesis or DUMY line) no longer
serves as the previous eye of the next leg. It had given the first
surviving snapshot a spurious translation speed (52.485 m/s instead of
2.570 m/s on a synthetic track) that was added to its wind field.
- A malformed OWI ASCII record is rejected, naming the record. It was
previously treated as end of file, so the run used silently truncated
forcing.
- Output variables declare the fill value their ``_FillValue`` attribute
advertises (-99999.0). A partially written trailing record, such as after a
crash, previously read as about 9.97e36 rather than as missing.
- A mesh with a weir boundary that omits a required weir column, or with an
optional boundary column present but shorter than the boundary, is rejected
with a message naming the column, rather than read out of bounds.
- ``--create-partition`` validates inputs before reading the mesh, and a
failure inside it aborts every MPI rank instead of leaving the peers blocked
in a collective until walltime.
Performance
^^^^^^^^^^^
- The host mesh keeps element connectivity only; basis functions are
recomputed during device upload. This returns about 1037 MB per rank at
3.20M elements. Results are unchanged.
- Maximum node valence for the consistent GWCE matrix comes from the existing
node-to-element connectivity. Setup at 1.60M nodes drops from about 0.51 s
to 0.01 s, and about 50 MB that was held for the whole run is freed.
[0.4.1] - 2026-08-22
--------------------
Changed
^^^^^^^
- **Behavior change:** ``tide_potential`` is no longer written by default; it
is a diagnostic of the forcing, not a model result. Set
``output.variables.tide_potential: true`` to restore it.
- **Behavior change:** peak and extrema accumulators are no longer stored in
checkpoints, so peak fields reset on restart and cover exactly the run that
reports them. A preserved maximum cannot be retracted, so a single bad step
would have carried through every later cycle of a hot-started chain.
Continuous peak tracking needs a continuous run.
Fixed
^^^^^
- Global meshes no longer develop a seam in elevation and velocity at the
antimeridian. Element longitudes are unwrapped only on an actual branch
crossing (a span over 180 degrees), not on a negative Jacobian. The old
test widened every clockwise-wound antimeridian element to a full
revolution and flipped the sign of its gradients; on the 12.8M-node global
STOFS mesh, all 3942 crossing elements were affected.
- Projected-x differences in the wet/dry head gradient and in land boundary
chords are reduced to their shortest equivalent across the branch cut.
Previously, nodes next to the cut could never re-wet, and boundary normals
there collapsed to due north.
[0.4.0] - 2026-08-15
--------------------
Added
^^^^^
- Baroclinic coupling. Nodal baroclinic pressure gradient forcing with the
elemental free-surface correction (Crank-Nicolson centered, ramped),
internal-tide wave drag computed from a de-tided velocity (Nycander, Lyard
and scalar formulations, with the de-tiding window carried across restarts),
and momentum dispersion as an additive friction contribution in
``mixed_layer_ratio`` and ``dispersion_cd`` modes. Configured under
``forcing.baroclinic``; the friction tensor closes with a
positive-semidefinite eigenvalue clamp. ``bpg_effective_x/y`` are available
as inspection output.
- ``cocoa_ocean3d``, an installable Python preprocessor that turns HYCOM GOFS
3.1 fields into the nodal baroclinic forcing file: TEOS-10 density,
column-integrated pressure gradient, mixed-layer and dispersion products,
mesh regridding, and download.
- Sea-ice concentration as a first-class meteorological field: CF NetCDF and
GRIB ingestion, composition across multi-domain sources, and attenuation of
the wind drag coefficient via the Lupkes (2012) parameterization.
- Elevation slope limiter (``numeric.elemental_slope_limiter``), an optional
post-GWCE pass that replaces zeta at nodes whose surrounding elemental
elevation gradient exceeds a per-node threshold with the area-weighted
average of the node's element patch.
- GAHM2026 conformance for weak and degenerate tracks: ambient pressure from
the ATCF RADP column, a clamped Holland B, minimum-strength gates, neighbor
fill for unreported quadrant radii instead of fabricated ones, and snapshots
with missing scalars skipped with a warning rather than aborting the run.
Preprocessed fort.22 input (aswip NWS=19/20) is now rejected.
- Configurable output compression (``output.compression`` level and shuffle);
the default is deflate level 1 without shuffle, chosen by measurement.
- ``utils/plot_cocoa.py``, a single plotter for output, peak and mesh files
that discovers the mesh and the plottable variables from the file's
UGRID/CF attributes. Handles global and regional meshes, vector pairs, and
frame series or movies; shipped in the containers.
- Nightly and release container publication for the CPU, CUDA and ROCm
families.
Changed
^^^^^^^
- Output field variables are written as single precision. Coordinates,
bathymetry, the time axis and checkpoints are unchanged.
- ``simulation.start_time`` is optional when ``checkpoint.restart_file`` is
set; the cold-start clock is recovered from the checkpoint, and a
configured value that disagrees with it is rejected rather than silently
re-anchoring every forcing ramp.
- Checkpoint format version 4: baroclinic Crank-Nicolson levels and the
de-tiding window are persisted, so a restart resumes them mid-window rather
than rebuilding them. Version 3 checkpoints are refused.
- Checkpoint restart compares mesh identity per section. Node positions,
connectivity and boundaries must match; bathymetry, nodal attributes and
self-attraction/loading may change between segments.
- Profiling regions are nested rather than spelling the hierarchy in labels,
and the per-step time-level advance, field extrema and checkpoint write are
timed for the first time.
- Run progress and the time-to-completion estimate are reported against the
work this run performs, so a restart no longer starts at the resumed
percentage.
- Error and critical messages print from every MPI rank, prefixed with the
rank id; lower levels stay rank-0-only.
Fixed
^^^^^
- On rotated-pole meshes, wind stress and the baroclinic pressure gradient are
rotated into the computational frame, and the spherical metric term in
momentum is evaluated from the rotated coordinates. Previously geographic
vectors reached the equations untransformed.
- MPI runs wrote solver-frame velocity to output; velocity rotation to the
geographic frame now applies in serial and distributed runs alike.
- Flux boundary state is checkpointed in the mesh's global ordering, so a
checkpoint no longer depends on rank count. Truncated files written by
earlier versions are refused.
- The ``internal_tide_friction`` nodal attribute's column count is validated
at load, repairing an out-of-bounds read of a single-column attribute.
- The JSONL step log is written by one writer, one record per step.
- Non-binary canopy values are resolved during fort.13 conversion.
Performance
^^^^^^^^^^^
- Output staging is device-resident, packed on device and copied back on a
dedicated execution-space instance so the transfer overlaps compute. With
single-precision storage, the cost of 2-minute output on a 1.57M-node
hurricane case (V100, one simulated day) falls from roughly 26% to 3.3% of
runtime, and the file halves in size.
[0.3.1] - 2026-07-27
--------------------
A maintenance release; no new model capability.
Changed
^^^^^^^
- ``-i`` (now also spelled ``--input``) is required and must name an existing
file; the implicit ``cocoa_config.yaml`` default is gone. ``--partition``
gains the short spelling ``-p``.
Fixed
^^^^^
- Registering more extrema fields than the maximum truncated silently, and a
sources/configs size mismatch indexed out of range. Both now throw at the
boundary, naming the actual and allowed counts.
- ``cocoa_mesh_tools.py`` runs in a container with no username populated.
[0.3.0] - 2026-07-20
--------------------
Added
^^^^^
- GAHM parametric hurricane vortex forcing (``cocoa_vortex``), with ATCF
b-deck parsing and missing-central-pressure estimation.
- GRIB2 meteorological forcing via ecCodes (``cocoa_ENABLE_GRIB``).
- Multi-source meteorological composition: self-contained ``domains:``
entries, per-source wind reduction, and per-source activation ramps.
- Configuration schema validation on valijson (JSON Schema draft-07), with
the schema embedded in the binary and reproduced in the user guide.
- External YAML inclusion at designated configuration nodes.
- Checkpoint format version 3: peaks are preserved across a restart, mesh
identity is verified before restoring, and the end-of-run checkpoint is
gated on solution stability.
- Google Benchmark suite with scaling analysis, compile-gated in CI.
- New dependencies: valijson and CLI11 (CPM-fetched); ecCodes (optional,
GRIB builds only).
Changed
^^^^^^^
- Asynchronous I/O is now always on. Output is delivered on a background
writer thread and meteorological input is pre-read ahead of need, overlapping
file access with computation in every run. MPI is initialized with
``MPI_THREAD_FUNNELED`` to support this; a run aborts at startup if the MPI
build cannot provide it.
- Command-line parsing moved to CLI11: ``--option=value`` spellings now
work, malformed values fail loudly, and ``--help``/``--version`` follow
standard Unix formatting.
- Boolean configuration values must be ``true``/``false``; the YAML 1.1
spellings are rejected.
- CF NetCDF pressure units are read from the file and required.
Performance
^^^^^^^^^^^
- Explicit solver per-step host stalls eliminated, worth roughly 13-15%
throughput on a 1.57M-node hurricane case with meteorology; momentum
nodal assembly reduced from three sweeps to one; wet/dry list compaction
with no per-step device allocation; cached MPI ghost staging and
device-direct boundary forcing scatter.
Removed
^^^^^^^
- The ``--asyncio`` command-line flag and its modes. Asynchronous output is
now unconditional, so there is no mode to select. The dedicated-MPI-I/O-rank
mode (``--asyncio=mpi``) has been removed; all ranks now participate in
computation.
Fixed
^^^^^
- MPI runs fail fast on rank-local errors instead of hanging peers until
walltime.
- Land-boundary normals at multi-string junction nodes average the
per-string normals (a documented deviation from ADCIRC's last-wins).
- Binary-inexact time steps (e.g. 0.1 s) no longer drop the final step.
[0.2.0b0] - 2026-06-29
----------------------
Added
^^^^^
- Full OWI NWS13 support, with Lagrangian moving-vortex interpolation for
moving grids.
- Grouped mesh NetCDF format carrying boundaries, nodal attributes and
self-attraction/loading in one file, identified by a BLAKE2 content
fingerprint (``mesh_id``) that is stamped onto model output. The mesh
tooling is consolidated into ``utils/cocoa_mesh_tools.py``.
- Checkpoint format version 2: timestamped single files, written at the end of
a run by default, with a warning when a run would accumulate many of them.
Changed
^^^^^^^
- "Hot start" is now "checkpoint" throughout the configuration and docs.
- Container images moved to the ``cocoaorg`` Docker Hub namespace.
Removed
^^^^^^^
- The A/B checkpoint file pair and the FNV-1a mesh topology checksum,
superseded by timestamped checkpoints and ``mesh_id``.
Fixed
^^^^^
- OWI uniform-grid detection is de-biased with average spacing, so a grid that
is regular within round-off is no longer misread as irregular.
- Kokkos and KokkosKernels are found as standalone packages, and the
diagnostics-color flag is selected per language, fixing the ROCm build.
- Multi-value nodal attributes get their secondary dimension when a mesh is
converted to the grouped format.
Performance
^^^^^^^^^^^
- The static OWI grid is cached rather than reparsed, regular moving grids are
read by corners, and the continuity diagonal solve is fused into one kernel.
[0.1.0] - 2026-06-23
--------------------
The first documented release of Cocoa, a C++20 re-implementation of ADCIRC's
two-dimensional depth-integrated shallow water model, built on Kokkos and
Trilinos.
Added
^^^^^
- Solution of the depth-integrated momentum equations and the Generalized Wave
Continuity Equation (GWCE) on unstructured triangular meshes, with either an
explicit lumped-mass GWCE (``explicit``/``lumped``, the default) or a
semi-implicit consistent-mass GWCE solved by Belos conjugate gradient with an
Ifpack2 Jacobi preconditioner (``implicit``/``consistent``).
- Execution on NVIDIA (CUDA) and AMD (HIP) GPUs, multi-core CPUs (OpenMP), or
serially, selected with ``cocoa_BACKEND``. Computation is in double
precision, with float storage of bandwidth-sensitive fields.
- MPI domain decomposition using ParMETIS partitions, either created
automatically or precomputed with ``--create-partition`` and reused through
``--partition``.
- Tidal forcing from harmonic constituents on open boundaries, an astronomical
tide potential computed directly from Moon and Sun positions, and
self-attraction and loading (SAL) from precomputed data.
- Meteorological forcing (wind stress through the Garratt drag law, and
atmospheric pressure) from CF NetCDF, OWI ASCII, and OWI NetCDF files,
including moving vortex-tracking OWI grids. Wind is reduced by directional
roughness length and canopy coefficient nodal attributes.
- Open, land, specified-flux, radiation, flux-plus-radiation, external weir and
internal weir boundaries, using ADCIRC boundary type codes. Internal weir
pairs are supported across partitions.
- ADCIRC-style wetting and drying: nodal and elemental drying, nodal wetting
and landlocked drying.
- Quadratic bottom friction with spatially varying Manning's n, and
``tau0``, initial dry state and wind reduction nodal attributes.
- Selectable map projections and coordinate rotation for global meshes.
- NetCDF UGRID mesh input, YAML configuration, and
``utils/convert_adcirc_format.py`` to convert fort.14/fort.13 files.
- NetCDF output of elevation, velocity, pressure, wind and tide potential time
series, plus peak fields (``zeta_max``, ``velocity_max``, ``pressure_min``,
``wind_max``), in ``split`` or ``combined`` files.
- Hot start from checkpoint files written alternately to an A/B pair.
- Asynchronous I/O: a background writer thread delivers output and
meteorological input is read ahead of need, so file access overlaps
computation. MPI is initialized with ``MPI_THREAD_FUNNELED``, and a run
aborts at startup if the MPI build cannot provide it.
- Docker images for CPU (x86-64 and ARM64), CUDA and ROCm.
Migration Guide
---------------
Migrating from ADCIRC
^^^^^^^^^^^^^^^^^^^^^
Converting Mesh Files
"""""""""""""""""""""
Cocoa uses NetCDF format for mesh files instead of ADCIRC's fort.14/fort.13 format.
Convert them with ``cocoa-mesh-tools``, part of the ``cocoa-tools`` package
(``pip install utils/cocoa_tools``; installed in the containers):
.. code-block:: bash
cocoa-mesh-tools from_adcirc \
--mesh fort.14 \
--attributes fort.13 \
--sal fort.24.nc \
--output mesh.nc \
--reorder
**Arguments:**
- ``--mesh``: ADCIRC mesh file (fort.14) - required
- ``--attributes``: ADCIRC nodal attributes file (fort.13) - optional
- ``--sal``: self-attraction/loading file (fort.24 or fort.24.nc) - optional
- ``--output``: Output NetCDF file path - required
- ``--reorder``: sort elements for GPU performance (``--reorder all`` also
renumbers nodes) - optional, recommended
See :doc:`user_guide/mesh_preparation` and :doc:`user_guide/cocoa_tools`.
Configuration Files
"""""""""""""""""""
Cocoa uses YAML configuration files instead of ADCIRC's fort.15 format.
Key differences:
.. list-table::
:header-rows: 1
* - ADCIRC Parameter
- Cocoa Equivalent
* - DT
- ``simulation.time_step``
* - TAU0
- ``physics.tau0``
* - STATIM
- ``simulation.start_time``
* - RNDAY
- ``simulation.end_time``
.. note::
In ADCIRC, a negative TAU0 value selects a spatially varying scheme. Cocoa
does not compute those schemes and rejects a negative ``tau0``: set a
non-negative constant, or ``tau0: mesh`` to read per-node values from the
mesh's ``primitive_weighting_in_continuity_equation`` nodal attribute.
Example conversion:
**ADCIRC fort.15**:
.. code-block:: text
DT = 2.0
TAU0 = 0.005
RNDAY = 1.0
**Cocoa YAML**:
.. code-block:: yaml
simulation:
time_step: 2s
start_time: 2025-01-01
end_time: 2025-01-02
physics:
tau0: 0.005