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).
Because this is a full overwrite of tau_x/tau_y, it also OWNS
the DERIVED stress_mag (cell-centred |tau|) that KPP and EPBL
take u_* from — refreshed on the device immediately after the
blend, so the boundary-layer schemes see the ice-mediated stress
rather than the configure-time wind. Any future writer of the
tau pair inherits the same obligation.
Momentum-budget caveat (F5, D7). This blend gives the ocean
(1-a)*tau_a + a*fxoc per face. With &ocean_ice_nml
a_face_stress=.false. (default, legacy) the ice absorbs the FULL
wind stress tau_a and sheds the FULL drag fxoc — so at
fractional cover a in (0,1) the coupled system sees a spurious
net input (1-a)*(tau_a - fxoc) per face. It is exact only at
a in {0,1} and at steady free drift (fxoc == tau_a); a
generic transient at fractional ci (ITD) leaks momentum.
&ocean_ice_nml a_face_stress=.true. (PR 62,
evp_u_momentum_impl/evp_v_momentum_impl in rdb_ice_evp)
CLOSES this identically, for every a and in every transient, by
weighting BOTH the wind AND the ice-ocean drag by the same a_u
inside the ice momentum balance (this blend needs no code
change — it already matches SIS2’s set_ocean_top_stress_Cgrid
exactly). Weighting the wind alone — the naive reading of “weight
the wind the ICE feels by a_face” — is WORSE than doing nothing:
it converts this leak (zero at steady free drift) into a
PERMANENT one, -(1-a)*a*tau_a, nonzero at steady state forever.
Do not implement that half-measure.
Seam ghosts (X5). With grid + bc present (the engine
always passes them) the blended pair goes through
ocean_seam_refresh_surface_stress — halo exchange, local
periodic wrap on an axis the halo does not own, fold — which also
re-derives stress_mag. The blend itself is correct only on the
faces this rank owns: a ghost face blends against fxoc = 0
(the EVP momentum writes physical faces only), yet the ocean’s
KPP/EPBL/MLE read tau one cell into the halo. The mirror into
tau_ocn_x/y is taken AFTER the refresh so a restart resumes the
exchanged ghosts the uninterrupted run stepped on. Absent (the
single-tile unit-test seam): the legacy stress_mag-only refresh.
| 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 |
subroutine ice_ocean_stress_flux(metrics, stress, ice, grid, bc) !! 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). !! !! Because this is a full overwrite of `tau_x`/`tau_y`, it also OWNS !! the DERIVED `stress_mag` (cell-centred `|tau|`) that KPP and EPBL !! take `u_*` from — refreshed on the device immediately after the !! blend, so the boundary-layer schemes see the ice-mediated stress !! rather than the configure-time wind. Any future writer of the !! `tau` pair inherits the same obligation. !! !! **Momentum-budget caveat (F5, D7).** This blend gives the ocean !! `(1-a)*tau_a + a*fxoc` per face. With `&ocean_ice_nml !! a_face_stress=.false.` (default, legacy) the ice absorbs the FULL !! wind stress `tau_a` and sheds the FULL drag `fxoc` — so at !! fractional cover `a in (0,1)` the coupled system sees a spurious !! net input `(1-a)*(tau_a - fxoc)` per face. It is exact only at !! `a in {0,1}` and at steady free drift (`fxoc == tau_a`); a !! generic transient at fractional `ci` (ITD) leaks momentum. !! `&ocean_ice_nml a_face_stress=.true.` (PR 62, !! `evp_u_momentum_impl`/`evp_v_momentum_impl` in `rdb_ice_evp`) !! CLOSES this identically, for every `a` and in every transient, by !! weighting BOTH the wind AND the ice-ocean drag by the same `a_u` !! *inside the ice momentum balance* (this blend needs no code !! change — it already matches SIS2's `set_ocean_top_stress_Cgrid` !! exactly). Weighting the wind alone — the naive reading of "weight !! the wind the ICE feels by `a_face`" — is WORSE than doing nothing: !! it converts this leak (zero at steady free drift) into a !! PERMANENT one, `-(1-a)*a*tau_a`, nonzero at steady state forever. !! Do not implement that half-measure. !! !! **Seam ghosts (X5).** With `grid` + `bc` present (the engine !! always passes them) the blended pair goes through !! `ocean_seam_refresh_surface_stress` — halo exchange, local !! periodic wrap on an axis the halo does not own, fold — which also !! re-derives `stress_mag`. The blend itself is correct only on the !! faces this rank owns: a ghost face blends against `fxoc = 0` !! (the EVP momentum writes physical faces only), yet the ocean's !! KPP/EPBL/MLE read `tau` one cell into the halo. The mirror into !! `tau_ocn_x/y` is taken AFTER the refresh so a restart resumes the !! exchanged ghosts the uninterrupted run stepped on. Absent (the !! single-tile unit-test seam): the legacy `stress_mag`-only refresh. 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 call stress_scratch_ensure(ice%nx_total, ice%ny_total) call ice_cell_concentration_impl(metrics%wet_T, ice%part_size, ice%m_ice, ice%m_snow, & mis_scratch, mice_scratch, ci_scratch, ice%ncat, & ice%nx_total, ice%ny_total) call ice_ocean_stress_flux_impl(stress%tau_x, stress%tau_y, ice%tau_a_x, ice%tau_a_y, & ice%fxoc, ice%fyoc, ci_scratch, ice%nx_total, ice%ny_total) ! The blend above OVERWROTE `tau_x`/`tau_y`, so the cell-centred ! `|tau|` the boundary-layer schemes take `u_* = sqrt(|tau|/rho0)` ! from is stale until it is re-derived from the new pair. KPP ! (`rdb_ocean_vmix`) and EPBL (`rdb_ocean_epbl`) read ! `stress%stress_mag` and NOTHING else, and no other per-step path ! refreshes it (the wind setters run at configure; the data-override ! seam refresh runs only under `&ocean_dataovr_nml`), so without this ! both schemes mix on the configure-time WIND under ice — identically ! zero, and therefore `u_* == 0`, in a windless ice-covered run. ! Device-resident and allocation-free. Reached only through this ! routine, which the engine calls only when `&ocean_ice_nml enable` ! AND `dynamics` are both on — an ice-free run never executes it and ! stays bit-identical. Gate: `test_ocean_ice_stress_mag`. if (present(grid) .and. present(bc)) then ! X5: seam ghosts first (exchange -> wrap -> fold), then ! `stress_mag` over the full array from the refreshed pair. call ocean_seam_refresh_surface_stress(stress, grid, bc) else call ocean_surface_stress_refresh_mag(stress) end if ! PR 63: mirror the exact blended value into the restart-carried ! tau_ocn_x/y (device copy — ice_tau_mirror_impl is NOT part of the ! blend math above, so ice_ocean_stress_flux_impl stays byte-identical ! by inspection and the tau_coupling gate is unaffected). After the ! seam refresh, so the checkpoint carries the exchanged ghosts. The ! host scalar write needs no signature change: this routine is ! already non-pure (stress_scratch_ensure) with ice intent(inout). call ice_tau_mirror_impl(ice%tau_ocn_x, ice%tau_ocn_y, stress%tau_x, stress%tau_y, & ice%nx_total, ice%ny_total) ice%tau_ocn_valid = 1.0_wp end subroutine ice_ocean_stress_flux