Refresh bt_work%visc_rem_u/v from the CURRENT stage state
BEFORE the barotropic forcing assembly (PGF_BUG.md §9) — the
MOM6-order parity (vertvisc_coef runs before btstep every
stage). The stage-end producer alone leaves visc_rem at its
init value (≡ 1) for the whole first stage, so the rem-weighted
F_bt degenerates to the plain mean exactly when the spurious
grounded-layer PGF is at its ballistic worst, and the Δu
corrector then deposits the spurious column-mean into wet
layers. Remnant-only vdiff call: builds the same matrix the
stage-end solve will build (kv one stage stale — benign; the
thickness field, which the BBL glue keys on, is current) and
does NOT touch the velocities. Vertex kappa-shear: the corner
Kv source enters this matrix too (same operator as the
stage-end momentum solve — a remnant built without it would
weight the BT corrector with a different friction operator
than the one actually applied).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(hgrid_t), | intent(in) | :: | grid | |||
| type(barotropic_workstate_t), | intent(inout) | :: | bt_work | |||
| type(ocean_vmix_t), | intent(in) | :: | vmix | |||
| type(ocean_vdiff_t), | intent(inout) | :: | vd | |||
| type(ocean_surface_stress_t), | intent(in) | :: | ss | |||
| type(ocean_bottom_drag_t), | intent(in) | :: | bd | |||
| type(multilayer_state_t), | intent(inout) | :: | ms | |||
| real(kind=wp), | intent(in) | :: | dt | |||
| type(ocean_kappa_shear_t), | intent(in), | optional | :: | kshear |
Kappa-shear slot; only read when enabled + vertex mode (supplies the corner Kv source). |
|
| real(kind=wp), | intent(in), | optional | :: | lambda_top_u(grid%nx_total+1,grid%ny_total) | ||
| real(kind=wp), | intent(in), | optional | :: | lambda_top_v(grid%nx_total,grid%ny_total+1) |
Ice-shelf top-drag Rayleigh rate — forwarded so the REMNANT
is built from the same operator the stage-end momentum solve
will build. A remnant built without a sink the solve has
would weight the barotropic corrector with a friction
operator that is not the one applied. EXPLICIT SHAPE for the
reason spelled out on |
|
| real(kind=wp), | intent(in), | optional | :: | cover_u(grid%nx_total+1,grid%ny_total) | ||
| real(kind=wp), | intent(in), | optional | :: | cover_v(grid%nx_total,grid%ny_total+1) |
Face ice-cover masks, same reason. |
|
| type(ocean_bc_state_t), | intent(in), | optional | :: | bc |
Forwarded ONLY for the post-production halo/periodic/fold
refresh of |
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| logical, | private | :: | vertex_kv |
subroutine visc_rem_precompute(grid, bt_work, vmix, vd, ss, bd, ms, dt, kshear, & lambda_top_u, lambda_top_v, cover_u, cover_v, bc) !! Refresh `bt_work%visc_rem_u/v` from the CURRENT stage state !! BEFORE the barotropic forcing assembly (PGF_BUG.md §9) — the !! MOM6-order parity (`vertvisc_coef` runs before `btstep` every !! stage). The stage-end producer alone leaves visc_rem at its !! init value (≡ 1) for the whole first stage, so the rem-weighted !! `F_bt` degenerates to the plain mean exactly when the spurious !! grounded-layer PGF is at its ballistic worst, and the Δu !! corrector then deposits the spurious column-mean into wet !! layers. Remnant-only vdiff call: builds the same matrix the !! stage-end solve will build (kv one stage stale — benign; the !! thickness field, which the BBL glue keys on, is current) and !! does NOT touch the velocities. Vertex kappa-shear: the corner !! Kv source enters this matrix too (same operator as the !! stage-end momentum solve — a remnant built without it would !! weight the BT corrector with a different friction operator !! than the one actually applied). type(hgrid_t), intent(in) :: grid type(barotropic_workstate_t), intent(inout) :: bt_work type(ocean_vmix_t), intent(in) :: vmix type(ocean_vdiff_t), intent(inout) :: vd type(ocean_surface_stress_t), intent(in) :: ss type(ocean_bottom_drag_t), intent(in) :: bd type(multilayer_state_t), intent(inout) :: ms real(wp), intent(in) :: dt type(ocean_kappa_shear_t), intent(in), optional :: kshear !! Kappa-shear slot; only read when enabled + vertex mode !! (supplies the corner Kv source). real(wp), intent(in), optional :: lambda_top_u(grid%nx_total + 1, grid%ny_total) real(wp), intent(in), optional :: lambda_top_v(grid%nx_total, grid%ny_total + 1) !! Ice-shelf top-drag Rayleigh rate — forwarded so the REMNANT !! is built from the same operator the stage-end momentum solve !! will build. A remnant built without a sink the solve has !! would weight the barotropic corrector with a friction !! operator that is not the one applied. EXPLICIT SHAPE for the !! reason spelled out on `vmix_apply_in_stage`'s twin dummies. real(wp), intent(in), optional :: cover_u(grid%nx_total + 1, grid%ny_total) real(wp), intent(in), optional :: cover_v(grid%nx_total, grid%ny_total + 1) !! Face ice-cover masks, same reason. type(ocean_bc_state_t), intent(in), optional :: bc !! Forwarded ONLY for the post-production halo/periodic/fold !! refresh of `visc_rem_u/v` (`visc_rem_halo_refresh`, PR-1) — !! MOM6's `pass_visc_rem` group pass, run after every !! `vertvisc_remnant` call. logical :: vertex_kv vertex_kv = .false. if (present(kshear)) vertex_kv = kshear%enable .and. kshear%at_vertex if (vmix%use_closure) then if (vertex_kv) then call vdiff_apply_momentum(grid, vd, ms, dt, kv_source=vmix%kv, & tau_u=ss%tau_x, tau_v=ss%tau_y, & lambda_bot_u=bd%lambda_bot_u, & lambda_bot_v=bd%lambda_bot_v, rho0=ss%rho0, & lambda_top_u=lambda_top_u, lambda_top_v=lambda_top_v, & cover_u=cover_u, cover_v=cover_v, & visc_rem_u=bt_work%visc_rem_u, & visc_rem_v=bt_work%visc_rem_v, & remnant_only=.true., & kv_corner_source=kshear%kd_corner, & kv_corner_prandtl=kshear%prandtl_turb) else call vdiff_apply_momentum(grid, vd, ms, dt, kv_source=vmix%kv, & tau_u=ss%tau_x, tau_v=ss%tau_y, & lambda_bot_u=bd%lambda_bot_u, & lambda_bot_v=bd%lambda_bot_v, rho0=ss%rho0, & lambda_top_u=lambda_top_u, lambda_top_v=lambda_top_v, & cover_u=cover_u, cover_v=cover_v, & visc_rem_u=bt_work%visc_rem_u, & visc_rem_v=bt_work%visc_rem_v, & remnant_only=.true.) end if else call vdiff_apply_momentum(grid, vd, ms, dt, & tau_u=ss%tau_x, tau_v=ss%tau_y, & lambda_bot_u=bd%lambda_bot_u, & lambda_bot_v=bd%lambda_bot_v, rho0=ss%rho0, & lambda_top_u=lambda_top_u, lambda_top_v=lambda_top_v, & cover_u=cover_u, cover_v=cover_v, & visc_rem_u=bt_work%visc_rem_u, & visc_rem_v=bt_work%visc_rem_v, & remnant_only=.true.) end if ! PR-2 root cause (replaces the PR-1 consumer-flag gate that used to ! sit here): `is_pc` alone (every pred_corr stage, default knobs) ! already called this routine before PR-1 (MOM6-order parity, ! PGF_BUG.md §9) even though the output went completely unread with ! every consumer off. PR-1 found that making the new halo refresh ! UNCONDITIONAL broke `rdb_test_ocean_dyn_mpi_4rank`'s hand-derived ! `check_exchange_counts` canary (`tests/mpi/test_ocean_dyn_mpi.F90`) ! -- `face_x_3d`/`face_y_3d` came back exactly DOUBLE the expected ! count, and (at nprocs=2) `msgs` came back exactly ! `+4*N_STEPS` (wall) / `+8*N_STEPS` (periodic, 2 active x-dirs). ! That is the EXACT signature of the new, legitimate ! visc_rem_halo_refresh traffic (one face_x_3d + one face_y_3d per ! pred_corr stage, same `is_pc .and. decomposed` gating as the ! existing u_av/v_av seam fill) -- i.e. the counters were telling ! the truth and the test's hand-derived formula was stale, not ! reporting a defect. Verified directly: `visc_rem_u/v` starts at ! 1.0 everywhere (`barotropic_workstate_init`) and the exchange is a ! same-shape, blocking, self-contained isend/irecv/waitall pair ! (`ocean_halo_face_x_3d`/`_y_3d`) identical in structure to every ! other 3D face exchange in this module -- there is no unpaired ! request, no tag collision (each call posts and waits inside the ! same subroutine invocation, no module-level async state survives ! past the `waitall`), and no shape mismatch (`visc_rem_u/v` are ! allocated `(nx+1,ny,nz)`/`(nx,ny+1,nz)` off the SAME `nz_ml` as ! every other layered field). Running this routine's gate removed ! at 1/2/4 ranks for 100 steps on the wall/island/periodic/ ! poisoned-wall/poisoned-periodic legs produces IDENTICAL mass/KE/ ! salt/heat agreement to round-off (~1e-16) in every leg; the only ! failures were the stale counter formula (fixed in ! `check_exchange_counts`, `tests/mpi/test_ocean_dyn_mpi.F90`). The ! pre-existing `[nan-catch]` BT-correction messages some legs print ! are `apply_bt_correction`'s own defensive non-finite counter ! (unrelated to visc_rem -- it already fires in the GATED baseline, ! just later/rarer); it recovers the finite answer either way and ! is not evidence of poisoning. ! ! MOM6 semantics settle it anyway: `pass_visc_rem` runs ! UNCONDITIONALLY after every `vertvisc_remnant` call, with no ! consumer gate -- a face field ! that starts at 1.0 and is only ever READ by an opt-in consumer ! cannot be "poisoned" by being exchanged. So: drop the gate, keep ! the halo refresh unconditional, exactly like MOM6. call visc_rem_halo_refresh(grid, bt_work, bc) end subroutine visc_rem_precompute