Make the sea-ice CATEGORY state valid in every ghost cell (X1 of
the sea-ice MPI plan): part_size, m_ice, m_snow,
enth_ice, sal_ice, enth_snow, one two-pass centre exchange
each, all categories (and ice layers) in one message per
direction, THEN the tripolar north fold of each (added with the
fold-seam fix below).
Why. Nothing inside the ice step refreshes these ghosts — the
column thermodynamics, ITD and (PR 4b) transport compress write
PHYSICAL cells only — yet three consumers read them one cell into
the halo: the EVP’s category gather (mis/mice/ci over the
full array, then strength, face mass and corner ratios), and the
stress coupler’s face concentration a_u = (ci(i-1)+ci(i))/2 at
the west/south-most owned face. On one rank with a periodic axis
the primitives’ local wrap closes the seam the same way (the
pre-existing “D7” stale-ghost note in rdb_ice_evp).
The fold. On a north = 'tripolar_fold' grid the MPI/periodic
exchange above fills every ghost EXCEPT the north cap: the fold
seam needs its own 180-degree-rotated mirror (rdb_ocean_fold),
which ocean_halo_centre knows nothing about. Before this fix
the north-fold ghost band of every one of these fields was stale
(uninitialised / previous-step), so the ITD/transport readers one
cell into it on the fold-seam row effectively saw an unrelated
cell — the root cause of unbounded ice growth on the seam row
(a mass source with no physical origin). Every field here is a
per-category SCALAR (mass, enthalpy, salinity, fractional area),
never a vector, so the fold is always the negate=.false. copy
contract (ocean_fold_wrap_centre_flat).
When. At the end of every thermo block (the category state
changes only there) and once at cold-start configure, host-side,
BEFORE enter_data and NEVER on a warm restart (the checkpoint
carries the writer’s ghosts; re-deriving them resumed a different
state, 8e1931f20). Single-rank non-periodic: a no-op. Requires
ocean_halo_init; the caller skips it otherwise. The fold itself
is gated independently on bc%north_fold and no-ops on any
non-tripolar grid.
The rank-4 enth_ice/sal_ice/enth_snow and the 0:ncat
part_size are contiguous and go out as one flat nz each,
through an explicit-shape seam (ice_halo_centre_flat), never
the aggregate ice (CLAUDE.md: component arrays only).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(ocean_sea_ice_t), | intent(inout) | :: | ice |
Live sea-ice slot ( |
||
| type(hgrid_t), | intent(in) | :: | grid | |||
| type(ocean_bc_state_t), | intent(in) | :: | bc |
Supplies |
||
| logical, | intent(in), | optional | :: | device_resident |
Forwarded to the halo primitives; |
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| integer, | private | :: | nxt | ||||
| integer, | private | :: | nyt |
subroutine ocean_halo_exchange_ice_state(ice, grid, bc, device_resident) !! Make the sea-ice CATEGORY state valid in every ghost cell (X1 of !! the sea-ice MPI plan): `part_size`, `m_ice`, `m_snow`, !! `enth_ice`, `sal_ice`, `enth_snow`, one two-pass centre exchange !! each, all categories (and ice layers) in one message per !! direction, THEN the tripolar north fold of each (added with the !! fold-seam fix below). !! !! **Why.** Nothing inside the ice step refreshes these ghosts — the !! column thermodynamics, ITD and (PR 4b) transport compress write !! PHYSICAL cells only — yet three consumers read them one cell into !! the halo: the EVP's category gather (`mis`/`mice`/`ci` over the !! full array, then strength, face mass and corner ratios), and the !! stress coupler's face concentration `a_u = (ci(i-1)+ci(i))/2` at !! the west/south-most owned face. On one rank with a periodic axis !! the primitives' local wrap closes the seam the same way (the !! pre-existing "D7" stale-ghost note in `rdb_ice_evp`). !! !! **The fold.** On a `north = 'tripolar_fold'` grid the MPI/periodic !! exchange above fills every ghost EXCEPT the north cap: the fold !! seam needs its own 180-degree-rotated mirror (`rdb_ocean_fold`), !! which `ocean_halo_centre` knows nothing about. Before this fix !! the north-fold ghost band of every one of these fields was stale !! (uninitialised / previous-step), so the ITD/transport readers one !! cell into it on the fold-seam row effectively saw an unrelated !! cell — the root cause of unbounded ice growth on the seam row !! (a mass source with no physical origin). Every field here is a !! per-category SCALAR (mass, enthalpy, salinity, fractional area), !! never a vector, so the fold is always the `negate=.false.` copy !! contract (`ocean_fold_wrap_centre_flat`). !! !! **When.** At the end of every thermo block (the category state !! changes only there) and once at cold-start configure, host-side, !! BEFORE `enter_data` and NEVER on a warm restart (the checkpoint !! carries the writer's ghosts; re-deriving them resumed a different !! state, `8e1931f20`). Single-rank non-periodic: a no-op. Requires !! `ocean_halo_init`; the caller skips it otherwise. The fold itself !! is gated independently on `bc%north_fold` and no-ops on any !! non-tripolar grid. !! !! The rank-4 `enth_ice`/`sal_ice`/`enth_snow` and the `0:ncat` !! `part_size` are contiguous and go out as one flat `nz` each, !! through an explicit-shape seam (`ice_halo_centre_flat`), never !! the aggregate `ice` (CLAUDE.md: component arrays only). type(ocean_sea_ice_t), intent(inout) :: ice !! Live sea-ice slot (`ice%is_init`); a no-op otherwise. type(hgrid_t), intent(in) :: grid type(ocean_bc_state_t), intent(in) :: bc !! Supplies `north_fold` + the grid metrics the fold kernels need. logical, intent(in), optional :: device_resident !! Forwarded to the halo primitives; `.false.` for the host-side !! configure-time call. integer :: nxt, nyt if (.not. ice%is_init) return nxt = ice%nx_total nyt = ice%ny_total call profiler_start("ice_comms_state") call oh_count_suppress_on() call ice_halo_centre_flat(ice%part_size, nxt, nyt, ice%ncat + 1, device_resident) call ice_halo_centre_flat(ice%m_ice, nxt, nyt, ice%ncat, device_resident) call ice_halo_centre_flat(ice%m_snow, nxt, nyt, ice%ncat, device_resident) call ice_halo_centre_flat(ice%enth_ice, nxt, nyt, ice%ncat*ice%nk_ice, device_resident) call ice_halo_centre_flat(ice%sal_ice, nxt, nyt, ice%ncat*ice%nk_ice, device_resident) call ice_halo_centre_flat(ice%enth_snow, nxt, nyt, ice%ncat, device_resident) call oh_count_suppress_off() call profiler_stop("ice_comms_state") ! Tripolar north fold. Every field above is a per-category ! SCALAR (mass / enthalpy / salinity / fractional area), never a ! vector, so each fold is the plain copy (`negate=.false.`) ! contract — see `ocean_fold_wrap_centre_flat`. No-op off a ! tripolar grid (`bc%north_fold = .false.`). if (bc%north_fold) then call ocean_fold_wrap_centre_flat(grid, bc, ice%part_size, nxt, nyt, & ice%ncat + 1, device_resident) call ocean_fold_wrap_centre_flat(grid, bc, ice%m_ice, nxt, nyt, & ice%ncat, device_resident) call ocean_fold_wrap_centre_flat(grid, bc, ice%m_snow, nxt, nyt, & ice%ncat, device_resident) call ocean_fold_wrap_centre_flat(grid, bc, ice%enth_ice, nxt, nyt, & ice%ncat*ice%nk_ice, device_resident) call ocean_fold_wrap_centre_flat(grid, bc, ice%sal_ice, nxt, nyt, & ice%ncat*ice%nk_ice, device_resident) call ocean_fold_wrap_centre_flat(grid, bc, ice%enth_snow, nxt, nyt, & ice%ncat, device_resident) end if end subroutine ocean_halo_exchange_ice_state