subtract_fast_cor_ref Subroutine

public pure subroutine subtract_fast_cor_ref(grid, metrics, bt_work, f_corner, bc_w, bc_e, bc_s, bc_n, has_w, has_e, has_s, has_n)

Subtract the fast-loop Coriolis + vector-invariant advection, evaluated at the reference barotropic velocity bt_work%cor_ref_u/cor_ref_v (filled by set_cor_ref_velocity), from the substep forcing F_bt_u_fast/F_bt_v_fast.

The reference velocity is NOT free: it must be the depth mean of the same layer velocity the slow cor%pv_flux_* inside F_bt_u/v was evaluated on, or the difference survives as a near-constant per-substep forcing. Under ssp_rk2 that is the stage-entry bt_ubt/bt_vbt (what this routine used to read directly); under pred_corr it is the depth mean of u_av/v_av. See set_cor_ref_velocity and the cor_ref_u docstring in rdb_barotropic_workstate.

Why: F_bt_u is the depth mean of ALL slow layer tendencies — including the layer Coriolis-advection (cor%pv_flux_*), whose depth mean is ≈ the fast solver’s own (ζ+f)·v − ∇KE at the stage-entry state. The substep then integrates its own LIVE (ζ+f)·v − ∇KE on top, so without this subtraction the barotropic Coriolis/advection is integrated TWICE — the exact analogue of the PGF double-count set_fast_forcing_eta_pf already guards against by shedding the free-surface term the slow PGF carries (“√(gH) inflates to √(2gH)”). The Coriolis double-count is what pumps the exponential wall/corner barotropic mode on shelf rims under VCOORD_LAGRANGIAN (600² double-gyre h-guard trap). MOM6 removes it with a reference Coriolis/advection (Cor_ref_u/v) subtracted inside its barotropic substep loop; this is that subtraction, folded into the forcing so the substep kernel is untouched.

After this, at τ=0 the substep’s net Coriolis/advection contribution is zero and only the ANOMALY that develops over the substeps is integrated — so Δu = ubt_end − ubt_at_n − dt·F_bt_u hands the layers the genuine fast anomaly instead of an extra dt·f·v̄ rotation per stage.

Uses bt_work%bt_zeta_corner and bt_work%bt_ke_centre as scratch — both are per-substep scratch the substep recomputes from scratch before reading (Pass 1 precedes Pass 2b/2c). ζ/KE formulas and wall closures mirror the substep exactly (rdb_barotropic_substep Pass 1 + the corner-ζ closure), so the τ=0 cancellation holds at walls too — where the unstable mode lives. bt_halo = 0 index convention (the wide-halo march-in path receives the same interior-computed forcing via copy_in; its seam rows differ at O(ghost) — acceptable, the halo exchange owns them).

Arguments

Type IntentOptional Attributes Name
type(hgrid_t), intent(in) :: grid
type(ocean_metrics_t), intent(in) :: metrics
type(barotropic_workstate_t), intent(inout) :: bt_work
real(kind=wp), intent(in) :: f_corner(grid%nx_total+1,grid%ny_total+1)
integer, intent(in) :: bc_w

Per-edge OBC tags (OBC_WALL when no bc present).

integer, intent(in) :: bc_e

Per-edge OBC tags (OBC_WALL when no bc present).

integer, intent(in) :: bc_s

Per-edge OBC tags (OBC_WALL when no bc present).

integer, intent(in) :: bc_n

Per-edge OBC tags (OBC_WALL when no bc present).

logical, intent(in) :: has_w

Physical-edge flags (.false. at an MPI seam).

logical, intent(in) :: has_e

Physical-edge flags (.false. at an MPI seam).

logical, intent(in) :: has_s

Physical-edge flags (.false. at an MPI seam).

logical, intent(in) :: has_n

Physical-edge flags (.false. at an MPI seam).


Calls

proc~~subtract_fast_cor_ref~~CallsGraph proc~subtract_fast_cor_ref subtract_fast_cor_ref local local proc~subtract_fast_cor_ref->local

Called by

proc~~subtract_fast_cor_ref~~CalledByGraph proc~subtract_fast_cor_ref subtract_fast_cor_ref proc~run_stage_split run_stage_split proc~run_stage_split->proc~subtract_fast_cor_ref proc~ocean_dyn_step_split ocean_dyn_step_split proc~ocean_dyn_step_split->proc~run_stage_split proc~engine_step engine_step proc~engine_step->proc~ocean_dyn_step_split proc~driver_run_ocean driver_run_ocean proc~driver_run_ocean->proc~engine_step proc~rdb_ocean_step rdb_ocean_step proc~rdb_ocean_step->proc~engine_step proc~driver_run driver_run proc~driver_run->proc~driver_run_ocean

Variables

Type Visibility Attributes Name Initial
real(kind=wp), private :: f_at_u
real(kind=wp), private :: f_at_v
integer, private :: i
integer, private :: j
real(kind=wp), private :: ke_grad_x
real(kind=wp), private :: ke_grad_y
integer, private :: nx
integer, private :: ny
real(kind=wp), private :: u_at_v
real(kind=wp), private :: v_at_u
real(kind=wp), private :: w_nl

Mirror of the substep’s live-nonlinear weight (bt_work%substep_zeta_ke): the reference MUST subtract exactly what the substep re-integrates live — full (ζ+f)·v − ∇KE when live (1), planetary f·v̄ only when the substep is planetary-only (0).

real(kind=wp), private :: zeta_at_u
real(kind=wp), private :: zeta_at_v

Source Code

   pure subroutine subtract_fast_cor_ref(grid, metrics, bt_work, f_corner, &
                                         bc_w, bc_e, bc_s, bc_n, &
                                         has_w, has_e, has_s, has_n)
      !! Subtract the fast-loop Coriolis + vector-invariant advection,
      !! evaluated at the reference barotropic velocity
      !! `bt_work%cor_ref_u`/`cor_ref_v` (filled by
      !! `set_cor_ref_velocity`), from the substep forcing
      !! `F_bt_u_fast`/`F_bt_v_fast`.
      !!
      !! The reference velocity is NOT free: it must be the depth mean
      !! of the same layer velocity the slow `cor%pv_flux_*` inside
      !! `F_bt_u/v` was evaluated on, or the difference survives as a
      !! near-constant per-substep forcing.  Under `ssp_rk2` that is the
      !! stage-entry `bt_ubt/bt_vbt` (what this routine used to read
      !! directly); under `pred_corr` it is the depth mean of `u_av/v_av`.
      !! See `set_cor_ref_velocity` and the `cor_ref_u` docstring in
      !! `rdb_barotropic_workstate`.
      !!
      !! Why: `F_bt_u` is the depth mean of ALL slow layer tendencies —
      !! including the layer Coriolis-advection (`cor%pv_flux_*`), whose
      !! depth mean is ≈ the fast solver's own `(ζ+f)·v − ∇KE` at the
      !! stage-entry state.  The substep then integrates its own LIVE
      !! `(ζ+f)·v − ∇KE` on top, so without this subtraction the
      !! barotropic Coriolis/advection is integrated TWICE — the exact
      !! analogue of the PGF double-count `set_fast_forcing_eta_pf`
      !! already guards against by shedding the free-surface term the
      !! slow PGF carries ("√(gH) inflates to √(2gH)").  The Coriolis double-count is what pumps
      !! the exponential wall/corner barotropic mode on shelf rims under
      !! `VCOORD_LAGRANGIAN` (600² double-gyre h-guard trap).  MOM6
      !! removes it with a reference Coriolis/advection (`Cor_ref_u/v`)
      !! subtracted inside its barotropic substep loop; this is that
      !! subtraction, folded into the forcing so the substep kernel is
      !! untouched.
      !!
      !! After this, at τ=0 the substep's net Coriolis/advection
      !! contribution is zero and only the ANOMALY that develops over
      !! the substeps is integrated — so `Δu = ubt_end − ubt_at_n −
      !! dt·F_bt_u` hands the layers the genuine fast anomaly instead
      !! of an extra `dt·f·v̄` rotation per stage.
      !!
      !! Uses `bt_work%bt_zeta_corner` and `bt_work%bt_ke_centre` as
      !! scratch — both are per-substep scratch the substep recomputes
      !! from scratch before reading (Pass 1 precedes Pass 2b/2c).
      !! ζ/KE formulas and wall closures mirror the substep exactly
      !! (`rdb_barotropic_substep` Pass 1 + the corner-ζ closure), so
      !! the τ=0 cancellation holds at walls too — where the unstable
      !! mode lives.  bt_halo = 0 index convention (the wide-halo
      !! march-in path receives the same interior-computed forcing via
      !! `copy_in`; its seam rows differ at O(ghost) — acceptable, the
      !! halo exchange owns them).
      type(hgrid_t), intent(in) :: grid
      type(ocean_metrics_t), intent(in) :: metrics
      type(barotropic_workstate_t), intent(inout) :: bt_work
      real(wp), intent(in) :: f_corner(grid%nx_total + 1, grid%ny_total + 1)
      integer, intent(in) :: bc_w, bc_e, bc_s, bc_n
         !! Per-edge OBC tags (OBC_WALL when no bc present).
      logical, intent(in) :: has_w, has_e, has_s, has_n
         !! Physical-edge flags (.false. at an MPI seam).

      integer :: i, j, nx, ny
      real(wp) :: zeta_at_u, f_at_u, v_at_u, ke_grad_x
      real(wp) :: zeta_at_v, f_at_v, u_at_v, ke_grad_y
      real(wp) :: w_nl
         !! Mirror of the substep's live-nonlinear weight
         !! (`bt_work%substep_zeta_ke`): the reference MUST subtract
         !! exactly what the substep re-integrates live — full
         !! `(ζ+f)·v − ∇KE` when live (1), planetary `f·v̄` only when
         !! the substep is planetary-only (0).

      nx = grid%nx_total
      ny = grid%ny_total
      w_nl = merge(1.0_wp, 0.0_wp, bt_work%substep_zeta_ke)

      ! ---- ζ at corners from the stage-entry bt state (substep Pass-1
      ! formula + closure).  Corner ring + physical-wall lines → 0.
      do concurrent(j=1:ny + 1, i=1:nx + 1)
         if (i >= 2 .and. i <= nx .and. j >= 2 .and. j <= ny) then
            bt_work%bt_zeta_corner(i, j) = &
               ((bt_work%cor_ref_v(i, j)*metrics%dyCv(i, j) &
                 - bt_work%cor_ref_v(i - 1, j)*metrics%dyCv(i - 1, j)) - &
                (bt_work%cor_ref_u(i, j)*metrics%dxCu(i, j) &
                 - bt_work%cor_ref_u(i, j - 1)*metrics%dxCu(i, j - 1)))* &
               metrics%iareaBu(i, j)
         else
            bt_work%bt_zeta_corner(i, j) = 0.0_wp
         end if
         if (bc_w /= OBC_PERIODIC .and. has_w .and. i == grid%nghost + 1) then
            bt_work%bt_zeta_corner(i, j) = 0.0_wp
         end if
         if (bc_e /= OBC_PERIODIC .and. has_e .and. i == grid%nghost + grid%nx_phys + 1) then
            bt_work%bt_zeta_corner(i, j) = 0.0_wp
         end if
         if (bc_s /= OBC_PERIODIC .and. has_s .and. j == grid%nghost + 1) then
            bt_work%bt_zeta_corner(i, j) = 0.0_wp
         end if
         ! The tripolar fold line is a seam (interior corners), not a wall.
         if (bc_n /= OBC_PERIODIC .and. bc_n /= OBC_TRIPOLAR_FOLD .and. has_n .and. &
             j == grid%nghost + grid%ny_phys + 1) then
            bt_work%bt_zeta_corner(i, j) = 0.0_wp
         end if
      end do

      ! ---- KE at centres (substep Pass-1 formula). ----
      do concurrent(j=1:ny, i=1:nx)
         bt_work%bt_ke_centre(i, j) = 0.25_wp*metrics%iareaT(i, j)*( &
                                      metrics%areaCu(i, j)*bt_work%cor_ref_u(i, j)**2 + &
                                      metrics%areaCu(i + 1, j)*bt_work%cor_ref_u(i + 1, j)**2 + &
                                      metrics%areaCv(i, j)*bt_work%cor_ref_v(i, j)**2 + &
                                      metrics%areaCv(i, j + 1)*bt_work%cor_ref_v(i, j + 1)**2)
      end do

      ! ---- Subtract the u-face reference (substep Pass-2b operand). ----
      do concurrent(j=1:ny, i=2:nx) local(zeta_at_u, f_at_u, v_at_u, ke_grad_x)
         zeta_at_u = w_nl*0.5_wp*(bt_work%bt_zeta_corner(i, j) + bt_work%bt_zeta_corner(i, j + 1))
         f_at_u = 0.5_wp*(f_corner(i, j) + f_corner(i, j + 1))
         if (j > 1 .and. j < ny) then
            v_at_u = 0.25_wp*(bt_work%cor_ref_v(i - 1, j) + bt_work%cor_ref_v(i - 1, j + 1) + &
                              bt_work%cor_ref_v(i, j) + bt_work%cor_ref_v(i, j + 1))
         else if (j == 1) then
            v_at_u = 0.5_wp*(bt_work%cor_ref_v(i - 1, j + 1) + bt_work%cor_ref_v(i, j + 1))
         else
            v_at_u = 0.5_wp*(bt_work%cor_ref_v(i - 1, j) + bt_work%cor_ref_v(i, j))
         end if
         ke_grad_x = w_nl*(bt_work%bt_ke_centre(i, j) - bt_work%bt_ke_centre(i - 1, j))*metrics%idxCu(i, j)
         bt_work%F_bt_u_fast(i, j) = bt_work%F_bt_u_fast(i, j) - &
                                     ((zeta_at_u + f_at_u)*v_at_u - ke_grad_x)
      end do

      ! ---- Subtract the v-face reference (substep Pass-2c operand). ----
      do concurrent(j=2:ny, i=1:nx) local(zeta_at_v, f_at_v, u_at_v, ke_grad_y)
         zeta_at_v = w_nl*0.5_wp*(bt_work%bt_zeta_corner(i, j) + bt_work%bt_zeta_corner(i + 1, j))
         f_at_v = 0.5_wp*(f_corner(i, j) + f_corner(i + 1, j))
         if (i > 1 .and. i < nx) then
            u_at_v = 0.25_wp*(bt_work%cor_ref_u(i, j - 1) + bt_work%cor_ref_u(i + 1, j - 1) + &
                              bt_work%cor_ref_u(i, j) + bt_work%cor_ref_u(i + 1, j))
         else if (i == 1) then
            u_at_v = 0.5_wp*(bt_work%cor_ref_u(i + 1, j - 1) + bt_work%cor_ref_u(i + 1, j))
         else
            u_at_v = 0.5_wp*(bt_work%cor_ref_u(i, j - 1) + bt_work%cor_ref_u(i, j))
         end if
         ke_grad_y = w_nl*(bt_work%bt_ke_centre(i, j) - bt_work%bt_ke_centre(i, j - 1))*metrics%idyCv(i, j)
         bt_work%F_bt_v_fast(i, j) = bt_work%F_bt_v_fast(i, j) - &
                                     (-(zeta_at_v + f_at_v)*u_at_v - ke_grad_y)
      end do
   end subroutine subtract_fast_cor_ref