Bridges ocean_sea_ice_t%salt_flux_diag (filled by
rdb_ice_frazil_uptake%ice_frazil_uptake) into
ocean_surface_flux_t%Q_salt — the field the REAL production
kernel ocean_surface_flux_apply_tracers
(rdb_ocean_surface_flux.F90) reads every thermo step.
TRAP #2 — sign convention (resolved against the CODE, not the
(now-fixed) stale module-header docstring of
rdb_ocean_surface_flux): apply_surface_src_2d_impl does
hTr_S(i,j,nz) += (dt/rho0) * Q_salt(i,j) * wet_mask(i,j), i.e.
dS/dt = +Q_salt/(rho0*h_top) — Q_salt is POSITIVE-SALINIFIES.
Brine rejection (freezing seawater leaves excess salt behind) must
RAISE the ocean surface salinity, so salt_flux_diag (already
computed with that sign in ice_frazil_uptake_impl) is added
directly, unnegated.
OWNERSHIP: with ice on and &ocean_forcing_nml enable_components =
.false. (the default), this coupler OWNS the Q_salt field-fill —
any future field-fill path (Area-A3 data-override) composing with
sea ice must add into salt_flux_diag or fold in here, not
bypass this call. With enable_components = .true. (PR-12), the
coupler instead OWNS the salt_flux / heat_added COMPONENTS —
ocean_surface_flux_assemble derives the net Q_salt/Q_heat
from Q_salt_const + salt_flux / Q_heat_const + heat_added at
the same point in the step, so the two modes produce identical
numbers (the components-on ice bit-identity gate,
tests/test_ocean_surface_forcing_type.F90:ice_components_bitident).
Q_heat was NOT written by the PR-3b coupler: the frazil latent heat
was already credited to the ocean by the PR-1 surface clamp (see
rdb_ice_frazil_uptake module docstring); writing it there would
have double-counted. PR 3c adds ice_ocean_heat_flux below, which
refreshes Q_heat from the melt-side heat_flux_diag (filled by
rdb_ice_thermo_driver — the column’s grow/melt exchange, NOT the
frazil bank) — a disjoint energy pathway from the frazil clamp, so
no double-count. Under enable_components, heat_flux_diag
lands in heat_added instead — MOM6’s slot for a net, already-
summed heat term that isn’t further decomposable (PR-12 plan §5.4).
PR 31 adds ice_ocean_sw_flux, which OWNS the shortwave the ice
transmits to the ocean (ocean_sea_ice_t%sw_thru_diag, W/m^2,
+down). Because q_sw is the shortwave SHARE OF the net heat
(Q_heat CONTAINS q_sw, it is not Q_heat + q_sw), the delivery is
component-mode-dependent and the coupler owns exactly ONE path in
each mode so the energy reaches Q_heat once, never twice:
* components ON — writes the q_sw COMPONENT; the assembler
sums it into Q_heat. heat_flux_diag (-> heat_added)
carries only the NON-shortwave heat, so there is no overlap.
* components OFF — q_sw is unallocated and there is no
assembler, so the shortwave is ADDED directly into the
components-off Q_heat (which ice_ocean_heat_flux
full-overwrote to Q_heat_const + heat_flux_diag immediately
before, per the driver’s mandated order), giving
Q_heat_const + heat_flux_diag + sw_thru_diag.
Either way the shortwave is NEVER folded into heat_flux_diag
(rdb_ice_thermo_driver), which is what keeps it out of
heat_added and prevents a double count.
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| real(kind=wp), | private, | allocatable, save | :: | ci_scratch(:,:) | |||
| real(kind=wp), | private, | allocatable, save | :: | mice_scratch(:,:) |
|
||
| real(kind=wp), | private, | allocatable, save | :: | mis_scratch(:,:) |
|
||
| integer, | private, | save | :: | ss_nx | = | 0 | |
| integer, | private, | save | :: | ss_ny | = | 0 | |
| logical, | private, | save | :: | stress_scratch_ready | = | .false. |
Refresh the ocean surface salt-flux field from the ice slot.
Components off (default):
Q_salt(i,j) = Q_salt_const + salt_flux_diag(i,j)
POSITIVE SALINIFIES (TRAP #2 above — matches
apply_surface_src_2d_impl’s hTr_S += dt/rho0*Q_salt). This
is a FULL OVERWRITE from the configure-time constant plus the
last uptake’s rate: no accumulation drift, and a thermo window
with no freezing resets the field back to the background
(salt_flux_diag == 0 when the bank was empty or the cell was
dry/land/vanished — see ice_frazil_uptake_impl’s unconditional
diag zeroing).
Components on (sf%use_components, PR-12): write the ice’s
own COMPONENT instead — sf%salt_flux(i,j) = salt_flux_diag(i,j)
(no Q_salt_const term: ocean_surface_flux_assemble adds it).
Sets has_salt either way (§13 item 6 of the PR-12 plan: any
filler that can produce non-zero net salt must set the latch).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(ocean_surface_flux_t), | intent(inout) | :: | sf | |||
| type(ocean_sea_ice_t), | intent(in) | :: | ice |
Refresh the ocean surface heat-flux field from the ice slot.
Components off (default):
Q_heat(i,j) = Q_heat_const + heat_flux_diag(i,j)
POSITIVE DOWN into the ocean (the apply-tracers convention:
d(hT) = Q_heatdt/(rho0cp)). Full overwrite from the const + last
window’s rate — a window with no ice exchange resets to background.
Components on (sf%use_components, PR-12): write
sf%heat_added(i,j) = heat_flux_diag(i,j) instead — MOM6’s slot
for a net, already-summed heat term (no _const term: the
assembler adds it). Sets has_heat either way.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(ocean_surface_flux_t), | intent(inout) | :: | sf | |||
| type(ocean_sea_ice_t), | intent(in) | :: | ice |
Release the persistent tau-blend scratch. Idempotent (safe on an
already-clean workspace — the driver calls it unconditionally at
ocean teardown, next to ice_evp_cleanup).
Ice->ocean momentum-mediation blend (PR 5), the momentum mirror
of ice_ocean_brine_flux: FULL overwrite each outer step from
the pristine wind snapshot + the lagged EVP drag, weighted by
ice concentration at each face.
a_u(i,j) = 0.5(ci(i-1,j) + ci(i,j))
tau_x(i,j) = (1-a_u)tau_a_x(i,j) + a_u*fxoc(i,j)
ditto y (a_v(i,j) = 0.5*(ci(i,j-1)+ci(i,j))). ci is
re-gathered via ice_cell_concentration_impl (shared with
rdb_ice_evp, so this module never depends on the EVP kernel).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(ocean_metrics_t), | intent(in) | :: | metrics | |||
| type(ocean_surface_stress_t), | intent(inout) | :: | stress | |||
| type(ocean_sea_ice_t), | intent(inout) | :: | ice | |||
| type(hgrid_t), | intent(in), | optional | :: | grid | ||
| type(ocean_bc_state_t), | intent(in), | optional | :: | bc |
Face-blend kernel. a_u/a_v interpolate ci onto the u/v
faces (Adcroft-style simple average — no mask needed since ci
is already 0 on land). Explicit-shape + decl-order.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(inout) | :: | tau_x(nx+1,ny) | |||
| real(kind=wp), | intent(inout) | :: | tau_y(nx,ny+1) | |||
| real(kind=wp), | intent(in) | :: | tau_a_x(nx+1,ny) | |||
| real(kind=wp), | intent(in) | :: | tau_a_y(nx,ny+1) | |||
| real(kind=wp), | intent(in) | :: | fxoc(nx+1,ny) | |||
| real(kind=wp), | intent(in) | :: | fyoc(nx,ny+1) | |||
| real(kind=wp), | intent(in) | :: | ci(nx,ny) | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
Configure-time resume apply (PR 63). tau_x/tau_y were just
re-seeded from the wind-stress config by configure_ocean_forcing;
if the checkpoint carried a blend (tau_ocn_valid > 0.5),
overwrite them with it — the EXACT stress the uninterrupted run
would have handed the ocean at this step boundary. Otherwise
leave the pristine wind (D8: a fresh run’s first outer step
drives the ocean with pure wind — and, for the first time, this
is now literally true: no (1-a)*tau_a residual from an
uninitialised fxoc).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(ocean_surface_stress_t), | intent(inout) | :: | stress | |||
| type(ocean_sea_ice_t), | intent(in) | :: | ice |
Refresh the ocean-surface shortwave from the ice slot’s
sw_thru_diag (PR 31) — the shortwave that penetrated the ice to
the water below (W/m^2, >= 0, POSITIVE DOWN into the ocean).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(ocean_surface_flux_t), | intent(inout) | :: | sf | |||
| type(ocean_sea_ice_t), | intent(in) | :: | ice |
Components-on branch: full-array overwrite of the salt_flux
COMPONENT (not Q_salt) — same sign convention, no _const
term (the assembler adds it). Explicit-shape + decl-order.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(inout) | :: | salt_flux(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | salt_flux_diag(nx,ny) | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
Full-array overwrite (including ghosts — they get
Q_salt_const + 0, the same value the configure-time seed
already gave them, so this is a no-op there). Explicit-shape
dummies + decl-order (integer dims before the arrays that use
them) so NVHPC stdpar compiles a device kernel against static
bounds. Runs on the device-resident Q_salt (mapped by
sf%enter_data) and salt_flux_diag (mapped by ice%enter_data).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(inout) | :: | Q_salt(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | salt_flux_diag(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | Q_salt_const | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
Components-on branch: full-array overwrite of the heat_added
COMPONENT (not Q_heat) — no _const term (the assembler adds
it). Explicit-shape + decl-order.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(inout) | :: | heat_added(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | heat_flux_diag(nx,ny) | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
Full-array overwrite (including ghosts — same no-op-there
reasoning as ice_ocean_brine_flux_impl). Explicit-shape dummies
+ decl-order. Runs on the device-resident Q_heat (mapped by
sf%enter_data) and heat_flux_diag (mapped by ice%enter_data).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(inout) | :: | Q_heat(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | heat_flux_diag(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | Q_heat_const | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
Components-off branch: ADD the ice-transmitted shortwave into the
net Q_heat (including ghosts — they get +0, since the ice
column gates sw_thru = 0 off-ice and sw_thru_diag is
correspondingly 0 there). This is an ADD, not an overwrite, and
is well-defined precisely because ice_ocean_heat_flux ran first
this window and full-overwrote Q_heat — so Q_heat holds
Q_heat_const + heat_flux_diag (SW-free) when this kernel lands
on it, and the net is recomputed fresh every window. Explicit-
shape + decl-order. Both arrays device-resident.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(inout) | :: | Q_heat(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | sw_thru_diag(nx,ny) | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
Components-on branch: full-array overwrite of the q_sw COMPONENT
(including ghosts — they get 0). No _const term.
Explicit-shape + decl-order. Runs on the device-resident q_sw
(mapped by sf%enter_data) and sw_thru_diag (mapped by
ice%enter_data).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(inout) | :: | q_sw(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | sw_thru_diag(nx,ny) | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
PR 63. Device copy of the blend’s OUTPUT into the restart-carried
mirror fields — deliberately NOT fused into
ice_ocean_stress_flux_impl (that kernel stays untouched so the
tau_coupling bitwise gate is unaffected by this PR).
Explicit-shape + decl-order (integer dims before the arrays that
use them).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(out) | :: | tau_ocn_x(nx+1,ny) | |||
| real(kind=wp), | intent(out) | :: | tau_ocn_y(nx,ny+1) | |||
| real(kind=wp), | intent(in) | :: | tau_x(nx+1,ny) | |||
| real(kind=wp), | intent(in) | :: | tau_y(nx,ny+1) | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |
Lazily (re)allocate + device-map the per-step tau-blend scratch (F6). Size change tears down and rebuilds. Never called inside a per-substep loop (this whole coupler runs once per outer step).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny |