Port of SIS2’s DEFAULT (velocity, non-merged) ice_cat_transport
(SIS_transport.F90:127) + finish_ice_transport (:255) +
compress_ice (:898), grounded per SPEC_ice-pr4b-transport.md.
Source citations below are SIS_transport.F90 / SIS_continuity.F90
/ SIS_tracer_advect.F90 unless noted; when this module and the
SIS2 source disagree, the source (re-verified against the running
SIS2 tree for this port) wins.
What SIS2 actually runs (MERGED_CONTINUITY defaults .false.,
SIS_dyn_trans.F90:2377): per-medium continuity driven by FACE
VELOCITIES (uc/vc), not the merged/proportionate-split variant.
ice_continuity (SIS_continuity.F90:69) reconstructs a PPM
parabola on the CATEGORY-SUMMED mass, computes ONE total-mass face
transport from that parabola, then splits it into per-category
flux proportionally to each category’s share of the summed mass
(SIS_continuity.F90:1184-1215) — there is no separate “split”
pass. Snow rides its OWN independent PPM solve on summed snow mass,
then is masked to zero wherever the CO-LOCATED ice flux is exactly
zero (SIS_continuity.F90:1210-1215; the frac_neglect second
masking clause is dead at SIS2’s own default frac_neglect=0, so it
is not ported). x_first is fixed by FIRST_DIRECTION (default 0);
the second (y) pass reads the ALREADY-UPDATED post-x-pass masses
(SIS_continuity.F90:170-216).
Algorithm per outer call (ice_transport_step):
Phase 0 — gates (is_init, ncat==1) + velocity sampling +
zero-velocity exact no-op.
Phase 1 — IST -> CAS (ice_state_to_cell_ave_state, :465):
mca_ice(c) = part_size(c)*m_ice(c), ditto snow.
Phase 2 — adv_substeps iterations, each an x-pass then y-pass;
each pass does (a) total-mass PPM + proportionate ice-flux
split, (b) same for snow + the co-located mask, (c) PCM tracer
riding (mass-weighted, BEFORE the mass update, with SIS2’s
H_NEGLECT conditioning guard for thin remainders), (d) the
mass update, then a fail-loud positivity/orphan-snow reduction.
Phase 3 — CAS -> IST (cell_ave_state_to_ice_state, :540):
re-derive part_size by division, with the pre-floor + optional
thin-ice rolling + general floor. part_size(0) may go
negative here — compress (Phase 4) is what fixes it.
Phase 4 — compress_ice (:898, no-ridge default, ponds
dropped): thinnest-first cascade that returns part_size(0) to
>= 0 by compacting / promoting categories, tracer-merging on
transfer.
Phase 5 — ice_adjust_categories (PR 4a, unchanged): restore the
ITD partition.
GPU race-hazard note (Phase 2c, tracer riding). A per-cell
update that reads a NEIGHBOUR cell’s intensive value (the upwind
donor of the west/east face) while another loop iteration may be
WRITING that same neighbour is a do concurrent race — iteration
order is unspecified. This module never reads a neighbour’s val
directly: it first runs a FACE-indexed gather kernel
(ice_gather_flux_{x,y}[_layer]_impl) that computes tr_flux(I) =
uh(I)*val(donor of I) — reading val ONLY at that face’s own donor
cell, writing to the SEPARATE tr_flux_x_work/tr_flux_y_work face
buffer (never val itself) — then a CELL-indexed update kernel
(ice_ride_update_{x,y}[_layer]_impl) that reads only the face
buffer plus its OWN cell’s prior mass/value and writes
val(i,j,c[,l]). This mirrors rdb_continuity’s Tr_face_left_x
gather/scatter split (tracer_advect_zonal_one_impl) verbatim in
spirit.
Reuse contract. The five SIS2-equivalent PPM helpers
(ppm_mirror_h, volcfl_face, ppm_limited_slope,
ppm_cell_limiter, ppm_limit_pos) are promoted to production
public in rdb_continuity and called verbatim here — their bodies
are NOT duplicated (SPEC §2).
Documented divergences from SIS2 (kept in sync with
SPEC_ice-pr4b-transport.md §10):
D1: continuity scheme fixed to Roundabout’s PPM (H3-style limited
edges + CW84 + limit_pos) rather than SIS2’s in-code legacy
default UPWIND_2D; matches SIS2’s modern PPM configs in
structure.
D2: no massless-category mH fill (SIS2 :509-520) — moot under
PCM tracer riding (a massless category’s scalar value is never
read: its face fluxes are always zero because mca=0).
D3: tracer riding is PCM (piecewise-constant upwind), one of
SIS2’s four schemes (SIS_TRACER_ADVECTION_SCHEME=PCM) — not
the PPM:H3 modern configs use. Upgrade path open.
D4: no melt ponds (Roundabout carries none).
D5: fixed x-first directional split (SIS2 FIRST_DIRECTION
default 0; no alternation).
D6: zero-velocity early-exit and ncat==1 early-exit are Roundabout
bit-identity contract additions; SIS2 has neither.
D7: fail-loud negativity/orphan-snow detection via a post-pass
device REDUCTION + driver abort, instead of SIS2’s in-loop
FATALs (GPU portability — a do concurrent kernel cannot abort
mid-loop). Also STRICTLY SAFER than SIS2 at the top-category
compaction: part(ncat) <= excess (negative- or, at the exact
tie, zero-denominator f) is routed into the fail-loud branch
(SIS2 has that hole).
D8: compress_ice tracer-merges INSIDE the thinnest-first cascade
at each transfer site, whereas SIS2 defers to
advect_tracers_thicker AFTER the category k-loop — equivalent
to round-off (each boundary visited once in a fixed order with
running masses; same argument as the PR-4a ice_adjust_categories
merge).
Multi-rank / periodic (ice_transport_step with bc): the ice
uses the ocean’s decomposition and nghost (>= 3 on a decomposed
run, ocean_halo_init). X4 — the CAS masses and every riding
tracer are halo-exchanged at the TOP of every advective substep
(ocean_halo_exchange_ice_transport; on one rank with a periodic
axis the same call wraps it). One exchange per substep suffices at
nghost = 3: the x-pass flux, ride and mass update run over every
row, ghost rows included, from corner-valid inputs, so the y-pass
reads ghost rows that are already post-x-pass. The physical-edge
wall faces are zeroed only on a PHYSICAL, non-periodic edge
(bc%has_*) — an MPI or periodic seam is an ordinary face. The
zero-velocity early exit and the validity / compress ok flags are
made RANK-UNIFORM (global max / min) before anyone acts on them: a
rank that skipped the CAS<->IST round trip or returned early while
another entered X4 would deadlock, and a rank-local abort would
strand the others in the next exchange.
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| real(kind=wp), | public, | parameter | :: | H_NEGLECT_ICE_TRANSPORT | = | 1.0e-30_wp |
SIS2 |
| real(kind=wp), | public, | parameter | :: | MASS_NEGLECT_ICE_TRANSPORT | = | 1.0e-60_wp |
SIS2 |
SIS2 zonal_mass_flux (SIS_continuity.F90:1064): PPM
reconstruction of the category-SUMMED mass htot, ONE total
face transport uhtot from the swept-volume parabola integral
(volcfl_face, bit-for-bit the SIS2 face expression), then the
PROPORTIONATE split uh(c) = uhtot*mca(donor,c)*I_htot(donor)
(SIS_continuity.F90:1199-1205, Adcroft reciprocal — I_htot=0
when htot(donor)<=0). Stencil + edge STORAGE CONVENTION +
swept-face orientation copied verbatim from continuity_zonal_flux
(rdb_continuity.F90:871-948): hl_x_work(i) == h_face_left_x(i)
is the value AT east face i from the LEFT cell i-1 (that
cell’s OWN downwind edge); hr_x_work(i) == h_face_right_x(i)
is from the RIGHT cell i (its OWN left edge). H3-style limited
edges + CW84 + ppm_limit_pos (SIS2 runs PPM_limit_pos
UNCONDITIONALLY on the PD scheme, so it is not optional here).
CFL metric: SIS2’s shipped default vol_CFL=.false. uses
CFL = |u|*dt*IdxT(donor) (SIS_continuity.F90:1165) — the
T-cell inverse spacing, NOT the dy_cu*iareaT swept-area ratio
(== SIS2’s vol_CFL=.true. variant). Identical on uniform
Cartesian; correct on spherical / anisotropic.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | wet_T(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | dy_cu(nx+1,ny) | |||
| real(kind=wp), | intent(in) | :: | idxT(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | u_ice(nx+1,ny) | |||
| real(kind=wp), | intent(in) | :: | mca(nx,ny,ncat) | |||
| real(kind=wp), | intent(inout) | :: | htot_work(nx,ny) | |||
| real(kind=wp), | intent(inout) | :: | hl_x_work(nx+1,ny) | |||
| real(kind=wp), | intent(inout) | :: | hr_x_work(nx+1,ny) | |||
| real(kind=wp), | intent(inout) | :: | uhtot_work(nx+1,ny) | |||
| real(kind=wp), | intent(out) | :: | uh_out(nx+1,ny,ncat) | |||
| real(kind=wp), | intent(in) | :: | dt_adv | |||
| integer, | intent(in) | :: | nghost | |||
| integer, | intent(in) | :: | nx_phys | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| logical, | intent(in) | :: | wall_w |
Zero face |
||
| logical, | intent(in) | :: | wall_e |
Zero face |
Meridional twin of ice_cat_flux_x_impl. Same face-indexed edge
STORAGE + swept orientation as continuity_meridional_flux
(rdb_continuity.F90:1159-1202): hl_y_work(i,j) == north-face
h_face_left_y (from the SOUTH cell j-1), hr_y_work(i,j) ==
h_face_right_y (from the NORTH cell j). CFL uses
IdyT(donor) (SIS2 vol_CFL=.false. default).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | wet_T(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | dx_cv(nx,ny+1) | |||
| real(kind=wp), | intent(in) | :: | idyT(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | v_ice(nx,ny+1) | |||
| real(kind=wp), | intent(in) | :: | mca(nx,ny,ncat) | |||
| real(kind=wp), | intent(inout) | :: | htot_work(nx,ny) | |||
| real(kind=wp), | intent(inout) | :: | hl_y_work(nx,ny+1) | |||
| real(kind=wp), | intent(inout) | :: | hr_y_work(nx,ny+1) | |||
| real(kind=wp), | intent(inout) | :: | vhtot_work(nx,ny+1) | |||
| real(kind=wp), | intent(out) | :: | vh_out(nx,ny+1,ncat) | |||
| real(kind=wp), | intent(in) | :: | dt_adv | |||
| integer, | intent(in) | :: | nghost | |||
| integer, | intent(in) | :: | ny_phys | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| logical, | intent(in) | :: | wall_s |
Zero face |
||
| logical, | intent(in) | :: | wall_n |
Zero face |
KEEP IN SYNC with ice_compress_cell_inline (the DEVICE production
twin, directly above). Same excess/ratio/compaction algorithm; this
HOST twin exists only as the directly unit-testable seam (small
standalone per-cell arrays, no !$acc routine seq), while the
device twin takes full device-present arrays + a scalar (i,j)
index. Any change to the compaction logic here MUST be mirrored
there (the fused device path is what production runs).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(inout) | :: | part_size(0:ncat) | |||
| real(kind=wp), | intent(inout) | :: | m_ice(ncat) | |||
| real(kind=wp), | intent(inout) | :: | m_snow(ncat) | |||
| real(kind=wp), | intent(inout) | :: | enth_ice(ncat,nk) | |||
| real(kind=wp), | intent(inout) | :: | enth_snow(ncat,1) | |||
| real(kind=wp), | intent(inout) | :: | sal_ice(ncat,nk) | |||
| real(kind=wp), | intent(in) | :: | mh_lim(ncat+1) | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nk | |||
| logical, | intent(out) | :: | ok |
Entry point (host orchestration; kernels inside). Outer-shim +
flat-impl: every phase below dispatches to a pure _impl
kernel; this routine only sequences them and owns the
host-visible ok fail-loud signal.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(hgrid_t), | intent(in) | :: | grid | |||
| type(ocean_metrics_t), | intent(in) | :: | metrics | |||
| type(multilayer_state_t), | intent(in) | :: | ms |
face velocities the v1 sampler reads. |
||
| type(ocean_sea_ice_t), | intent(inout) | :: | ice | |||
| real(kind=wp), | intent(in) | :: | dt |
The thermo-step dt (cadence = the ice slow step). |
||
| integer, | intent(in) | :: | adv_substeps |
SIS2 |
||
| real(kind=wp), | intent(in) | :: | roll_factor |
SIS2 |
||
| logical, | intent(out) | :: | ok |
|
||
| type(ocean_bc_state_t), | intent(in), | optional | :: | bc |
Edge policy ( |
SIS2 cell_ave_state_to_ice_state (:540). Per cell, per
category: pre-floor category 1, optional thin-ice rolling
(roll_factor > 0), general floor, then re-derive
part_size(c) = mca_ice(c)/m_ice(c) and
m_snow(c) = m_ice(c)*(mca_snow(c)/mca_ice(c)) (per-ICE-area).
part_size(0) = 1 - Sum_c part_size(c) — MAY be negative here;
ice_compress_impl (Phase 4) is what restores >= 0.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | wet_mask(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | areaT(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | mca_ice(nx,ny,ncat) | |||
| real(kind=wp), | intent(in) | :: | mca_snow(nx,ny,ncat) | |||
| real(kind=wp), | intent(inout) | :: | part_size(nx,ny,0:ncat) | |||
| real(kind=wp), | intent(inout) | :: | m_ice(nx,ny,ncat) | |||
| real(kind=wp), | intent(inout) | :: | m_snow(nx,ny,ncat) | |||
| real(kind=wp), | intent(in) | :: | mh_lim(ncat+1) | |||
| real(kind=wp), | intent(in) | :: | roll_factor | |||
| integer, | intent(in) | :: | nghost | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
KEEP IN SYNC with ice_transport_compress_cell (the HOST tested
seam twin, directly below). Same excess/ratio/compaction algorithm;
this twin exists only for a different argument shape — full
device-present arrays + scalar (i,j) indices for the fused device
path (fixed-size device locals), vs the twin’s small standalone
per-cell arrays for the unit test. Any change to the compaction
logic here MUST be mirrored there (the test only drives the twin).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(inout) | :: | part_size(nx,ny,0:ncat) | |||
| real(kind=wp), | intent(inout) | :: | m_ice(nx,ny,ncat) | |||
| real(kind=wp), | intent(inout) | :: | m_snow(nx,ny,ncat) | |||
| real(kind=wp), | intent(inout) | :: | enth_ice(nx,ny,ncat,nk) | |||
| real(kind=wp), | intent(inout) | :: | enth_snow(nx,ny,ncat,1) | |||
| real(kind=wp), | intent(inout) | :: | sal_ice(nx,ny,ncat,nk) | |||
| real(kind=wp), | intent(in) | :: | mh_lim(ncat+1) | |||
| integer, | intent(in) | :: | i | |||
| integer, | intent(in) | :: | j | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nk | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| logical, | intent(out) | :: | ok |
Outer per-cell dispatch: ONE do concurrent(j,i) over physical
cells (ghosts excluded), wet_mask > 0.5 inner gate, serial in
category within the cell (same shape as
ice_adjust_categories_impl). The per-cell algebra is the SAME
algorithm ice_transport_compress_cell implements (public,
directly unit-testable on small standalone arrays — SPEC §7 test
3’s single-cell hand-check) — but this production kernel does
NOT call it with a derived slice: part_size(i,j,:) etc. are
NON-CONTIGUOUS sections (the category axis is not the fastest
dimension), which a device !$acc routine seq call cannot take
safely (would force a compiler temporary — the array-of-
derived-type/strided-slice indirection trap, CLAUDE.md memory).
Instead the algorithm is INLINED here operating on (i,j,c)
triples directly, mirroring ice_adjust_categories_impl’s
established pattern. ok is a per-cell-then-reduced flag
(D7: SIS2 FATALs on the top-category overflow inconsistency;
ported as a !$acc parallel loop reduction instead, since
do concurrent cannot itself carry a boolean/min reduction in
the house style — CLAUDE.md “Reductions use !$acc parallel loop
reduction(...)”).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | wet_mask(nx,ny) | |||
| real(kind=wp), | intent(inout) | :: | part_size(nx,ny,0:ncat) | |||
| real(kind=wp), | intent(inout) | :: | m_ice(nx,ny,ncat) | |||
| real(kind=wp), | intent(inout) | :: | m_snow(nx,ny,ncat) | |||
| real(kind=wp), | intent(inout) | :: | enth_ice(nx,ny,ncat,nk) | |||
| real(kind=wp), | intent(inout) | :: | enth_snow(nx,ny,ncat,1) | |||
| real(kind=wp), | intent(inout) | :: | sal_ice(nx,ny,ncat,nk) | |||
| real(kind=wp), | intent(in) | :: | mh_lim(ncat+1) | |||
| integer, | intent(in) | :: | nghost | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nk | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| logical, | intent(out) | :: | ok |
Gather pass (race-free): tr_flux_x_work(I,c) = val(donor of
I,c), donor by the sign of the FLUX. uh is exactly 0.0 at
the array-edge faces I=1 and I=nx+1 (the flux kernel zeros
them unconditionally), so the I=1 donor-by-sign branch
(uh>=0) would read the out-of-bounds val(0,j,c) — guarded
explicitly below (I==1/I==nx+1 fall back to the IN-BOUNDS
neighbour; the value is never actually consumed by the update
kernel there since uh==0 at both those faces makes them
inert, but the gather must still avoid the invalid index).
Reads val ONLY at a donor cell — never writes val — so this
kernel has no race with any other iteration.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | uh(nx+1,ny,ncat) | |||
| real(kind=wp), | intent(in) | :: | val(nx,ny,ncat) | |||
| real(kind=wp), | intent(out) | :: | tr_flux_x_work(nx+1,ny,ncat) | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
Layer-indexed twin of ice_gather_flux_x_impl for
enth_ice/sal_ice/enth_snow (shape (nx,ny,ncat,nk)).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | uh(nx+1,ny,ncat) | |||
| real(kind=wp), | intent(in) | :: | val4(nx,ny,ncat,nk) | |||
| integer, | intent(in) | :: | layer | |||
| real(kind=wp), | intent(out) | :: | tr_flux_x_work(nx+1,ny,ncat) | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nk | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
Meridional twin of ice_gather_flux_x_impl.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | vh(nx,ny+1,ncat) | |||
| real(kind=wp), | intent(in) | :: | val(nx,ny,ncat) | |||
| real(kind=wp), | intent(out) | :: | tr_flux_y_work(nx,ny+1,ncat) | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
Meridional twin of ice_gather_flux_x_layer_impl.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | vh(nx,ny+1,ncat) | |||
| real(kind=wp), | intent(in) | :: | val4(nx,ny,ncat,nk) | |||
| integer, | intent(in) | :: | layer | |||
| real(kind=wp), | intent(out) | :: | tr_flux_y_work(nx,ny+1,ncat) | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nk | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
SIS2 ice_state_to_cell_ave_state (:465): mca(c) =
part_size(c)*m(c) per category, physical cells only. Ghost
cells are zeroed (never read as donors in the flux kernels below
— wet mirroring + wall zeroing exclude them).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | wet_mask(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | part_size(nx,ny,0:ncat) | |||
| real(kind=wp), | intent(in) | :: | m_ice(nx,ny,ncat) | |||
| real(kind=wp), | intent(in) | :: | m_snow(nx,ny,ncat) | |||
| real(kind=wp), | intent(out) | :: | mca_ice(nx,ny,ncat) | |||
| real(kind=wp), | intent(out) | :: | mca_snow(nx,ny,ncat) | |||
| integer, | intent(in) | :: | nghost | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
SIS2 masking_uh=uh_ice (SIS_continuity.F90:1210-1215):
uh_snow(I,c) = 0 wherever uh_ice(I,c) == 0. Face-shaped
array, works identically for the x-face (nx+1,ny,ncat) and
y-face (nx,ny+1,ncat) layouts (caller passes the right
extents). frac_neglect (SIS2’s second masking clause) is dead
at SIS2’s own default frac_neglect=0 — not ported (D-noted in
the module docstring).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | uh_ice(nfi,nfj,ncat) | |||
| real(kind=wp), | intent(inout) | :: | uh_snow(nfi,nfj,ncat) | |||
| integer, | intent(in) | :: | nfi | |||
| integer, | intent(in) | :: | nfj | |||
| integer, | intent(in) | :: | ncat |
SIS2 :183-191: mca(c) -= dt_adv*iareaT*(uh(I)-uh(I-1)),
physical cells only. No race (reads faces, writes cells).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | iareaT(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | uh(nx+1,ny,ncat) | |||
| real(kind=wp), | intent(inout) | :: | mca(nx,ny,ncat) | |||
| real(kind=wp), | intent(in) | :: | dt_adv | |||
| integer, | intent(in) | :: | nghost | |||
| integer, | intent(in) | :: | nx_phys | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
Meridional twin of ice_mass_update_x_impl.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | iareaT(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | vh(nx,ny+1,ncat) | |||
| real(kind=wp), | intent(inout) | :: | mca(nx,ny,ncat) | |||
| real(kind=wp), | intent(in) | :: | dt_adv | |||
| integer, | intent(in) | :: | nghost | |||
| integer, | intent(in) | :: | ny_phys | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
Max |u_ice|/|v_ice| over PHYSICAL faces only (the array-edge
ghost faces carry no meaning here). !$acc parallel loop
reduction (inert comment on non-OpenACC compilers) — GPU-safe
max reduction for the zero-velocity exact no-op gate.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | u_ice(nx+1,ny) | |||
| real(kind=wp), | intent(in) | :: | v_ice(nx,ny+1) | |||
| integer, | intent(in) | :: | nghost | |||
| integer, | intent(in) | :: | nx_phys | |||
| integer, | intent(in) | :: | ny_phys | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| real(kind=wp), | intent(out) | :: | vmax |
One zonal pass: total-mass PPM + proportionate ice-flux split, the snow twin + co-located mask, PCM tracer riding (gather then cell update), the mass update (AFTER every ride reads the pre-update mass), and the post-pass validity reduction.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(hgrid_t), | intent(in) | :: | grid | |||
| type(ocean_metrics_t), | intent(in) | :: | metrics | |||
| type(ocean_sea_ice_t), | intent(inout) | :: | ice | |||
| real(kind=wp), | intent(in) | :: | dt_adv | |||
| logical, | intent(in) | :: | wall_w |
Zero the west / east tile-edge face (a physical, non-periodic edge); a seam face is an ordinary face. |
||
| logical, | intent(in) | :: | wall_e |
Zero the west / east tile-edge face (a physical, non-periodic edge); a seam face is an ordinary face. |
||
| logical, | intent(out) | :: | ok |
Meridional twin of ice_pass_x. Reads the POST-x-pass masses
(SIS2 SIS_continuity.F90:170-216).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(hgrid_t), | intent(in) | :: | grid | |||
| type(ocean_metrics_t), | intent(in) | :: | metrics | |||
| type(ocean_sea_ice_t), | intent(inout) | :: | ice | |||
| real(kind=wp), | intent(in) | :: | dt_adv | |||
| logical, | intent(in) | :: | wall_s |
Zero the south / north tile-edge face (physical, non-periodic). |
||
| logical, | intent(in) | :: | wall_n |
Zero the south / north tile-edge face (physical, non-periodic). |
||
| logical, | intent(out) | :: | ok |
Cell-update pass (race-free): reads tr_flux_x_work (the
GATHERED donor VALUE at each face, from ice_gather_flux_x_impl)
+ its OWN cell’s mca/val (never a neighbour’s val), applies
SIS2’s advect_scalar_x flux-form update + the H_NEGLECT
conditioning guard (SIS_tracer_advect.F90:735-760), writes
val(i,j,c). hnew is the SAME expression the mass-update
kernel (d) uses, so the implied masses agree bitwise.
Bitwise no-op when both faces carry zero flux (F_W==F_E==0).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | iareaT(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | uh(nx+1,ny,ncat) | |||
| real(kind=wp), | intent(in) | :: | tr_flux_x_work(nx+1,ny,ncat) | |||
| real(kind=wp), | intent(in) | :: | mca(nx,ny,ncat) | |||
| real(kind=wp), | intent(inout) | :: | val(nx,ny,ncat) | |||
| real(kind=wp), | intent(in) | :: | dt_adv | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
Layer-indexed twin of ice_ride_update_x_impl for
enth_ice/sal_ice/enth_snow (shape (nx,ny,ncat,nk)). Same
algebra, applied to val4(:,:,:,layer).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | iareaT(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | uh(nx+1,ny,ncat) | |||
| real(kind=wp), | intent(in) | :: | tr_flux_x_work(nx+1,ny,ncat) | |||
| real(kind=wp), | intent(in) | :: | mca(nx,ny,ncat) | |||
| real(kind=wp), | intent(inout) | :: | val4(nx,ny,ncat,nk) | |||
| integer, | intent(in) | :: | layer | |||
| real(kind=wp), | intent(in) | :: | dt_adv | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nk | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
Meridional twin of ice_ride_update_x_impl.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | iareaT(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | vh(nx,ny+1,ncat) | |||
| real(kind=wp), | intent(in) | :: | tr_flux_y_work(nx,ny+1,ncat) | |||
| real(kind=wp), | intent(in) | :: | mca(nx,ny,ncat) | |||
| real(kind=wp), | intent(inout) | :: | val(nx,ny,ncat) | |||
| real(kind=wp), | intent(in) | :: | dt_adv | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
Layer-indexed twin of ice_ride_update_y_impl for
enth_ice/sal_ice/enth_snow (shape (nx,ny,ncat,nk)).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | iareaT(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | vh(nx,ny+1,ncat) | |||
| real(kind=wp), | intent(in) | :: | tr_flux_y_work(nx,ny+1,ncat) | |||
| real(kind=wp), | intent(in) | :: | mca(nx,ny,ncat) | |||
| real(kind=wp), | intent(inout) | :: | val4(nx,ny,ncat,nk) | |||
| integer, | intent(in) | :: | layer | |||
| real(kind=wp), | intent(in) | :: | dt_adv | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nk | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
u_ice(i,j) = u_surf(i,j)*wet_u(i,j), ditto v. v1 interim
filler (SPEC §5 Phase 0) — PR 5 EVP replaces this call with a
dynamics solve writing the SAME u_ice/v_ice faces.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | u_surf(nx+1,ny) | |||
| real(kind=wp), | intent(in) | :: | v_surf(nx,ny+1) | |||
| real(kind=wp), | intent(in) | :: | wet_u(nx+1,ny) | |||
| real(kind=wp), | intent(in) | :: | wet_v(nx,ny+1) | |||
| real(kind=wp), | intent(out) | :: | u_ice(nx+1,ny) | |||
| real(kind=wp), | intent(out) | :: | v_ice(nx,ny+1) | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
ok = .false. when min(mca_ice) < 0, min(mca_snow) < 0, or
an “orphan snow” cell exists (mca_snow > 0 where mca_ice <= 0
by more than H_NEGLECT_ICE_TRANSPORT) — SIS2 FATALs on any of
these; ported as a post-pass reduction (GPU-safe fail-loud, D7)
rather than an in-loop abort. Physical cells only.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | mca_ice(nx,ny,ncat) | |||
| real(kind=wp), | intent(in) | :: | mca_snow(nx,ny,ncat) | |||
| integer, | intent(in) | :: | nghost | |||
| integer, | intent(in) | :: | nx_phys | |||
| integer, | intent(in) | :: | ny_phys | |||
| integer, | intent(in) | :: | ncat | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| logical, | intent(out) | :: | ok |
Make a validity flag rank-uniform (global AND, as an exact min of 0/1) BEFORE anyone acts on it: a rank-local early return would strand the other ranks in the next substep’s exchange. No collective unless the run is decomposed (a serial run inside a multi-rank job — the bit-identity reference — stays local).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| logical, | intent(inout) | :: | ok |