continuity_meridional_flux Subroutine

private pure subroutine continuity_meridional_flux(grid, metrics, this, ms, dt, vhbt, bc, visc_rem, v_cor)

Meridional (y-only) PPM reconstruction + per-face mass flux. Mirror of continuity_zonal_flux, with the same optional vhbt transport-constraint renormalisation. Writes ms%mass_flux_y_layer. Walls at j=1 and j=ny+1 zeroed. In the Lie split this runs after the zonal apply, so it reconstructs against the already-updated ms%h_layer.

Arguments

Type IntentOptional Attributes Name
type(hgrid_t), intent(in) :: grid
type(ocean_metrics_t), intent(in) :: metrics
type(continuity_t), intent(inout) :: this
type(multilayer_state_t), intent(inout) :: ms
real(kind=wp), intent(in) :: dt

Time increment (s). Used only for the swept-volume CFL when this%vol_cfl = .true.; ignored (bit-identical) otherwise.

real(kind=wp), intent(in), optional :: vhbt(:,:)

Time-mean north-face transport from barotropic substep (m³/s), shape (nx, ny+1). Width-weighted (carries dx_cv).

type(ocean_bc_state_t), intent(in), optional :: bc
real(kind=wp), intent(in), optional :: visc_rem(:,:,:)

Per-layer viscous remnant gamma_k. Absent => 1, bit-identical.

real(kind=wp), intent(inout), optional :: v_cor(:,:,:)

Forwarded MOM6 v_cor destination — separate time-mean field.


Calls

proc~~continuity_meridional_flux~~CallsGraph proc~continuity_meridional_flux continuity_meridional_flux local local proc~continuity_meridional_flux->local proc~ocean_bc_outer_face_tag ocean_bc_outer_face_tag proc~continuity_meridional_flux->proc~ocean_bc_outer_face_tag proc~ppm_cell_limiter ppm_cell_limiter proc~continuity_meridional_flux->proc~ppm_cell_limiter proc~ppm_limit_pos ppm_limit_pos proc~continuity_meridional_flux->proc~ppm_limit_pos proc~ppm_limited_slope ppm_limited_slope proc~continuity_meridional_flux->proc~ppm_limited_slope proc~ppm_mirror_h ppm_mirror_h proc~continuity_meridional_flux->proc~ppm_mirror_h proc~renormalise_meridional_flux_to_vhbt renormalise_meridional_flux_to_vhbt proc~continuity_meridional_flux->proc~renormalise_meridional_flux_to_vhbt proc~volcfl_face volcfl_face proc~continuity_meridional_flux->proc~volcfl_face proc~renormalise_meridional_flux_to_vhbt->local

Called by

proc~~continuity_meridional_flux~~CalledByGraph proc~continuity_meridional_flux continuity_meridional_flux proc~continuity_step_split continuity_step_split proc~continuity_step_split->proc~continuity_meridional_flux proc~continuity_tracer_step_split continuity_tracer_step_split proc~continuity_tracer_step_split->proc~continuity_meridional_flux proc~run_continuity_chain run_continuity_chain proc~run_continuity_chain->proc~continuity_tracer_step_split proc~run_stage run_stage proc~run_stage->proc~continuity_tracer_step_split proc~ocean_dyn_step ocean_dyn_step proc~ocean_dyn_step->proc~run_stage proc~run_stage_split run_stage_split proc~run_stage_split->proc~run_continuity_chain proc~engine_step engine_step proc~engine_step->proc~ocean_dyn_step proc~ocean_dyn_step_split ocean_dyn_step_split proc~engine_step->proc~ocean_dyn_step_split proc~ocean_dyn_step_split->proc~run_stage_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

Variables

Type Visibility Attributes Name Initial
integer, private :: bc_n_tag
integer, private :: bc_s_tag
real(kind=wp), private :: cfl_d
real(kind=wp), private :: curv3_d
real(kind=wp), private :: dh_0
real(kind=wp), private :: dh_d
real(kind=wp), private :: dh_m1
real(kind=wp), private :: dh_p1
logical, private :: do_pd
logical, private :: do_pos
logical, private :: do_volcfl
real(kind=wp), private :: h0
real(kind=wp), private :: h_face
real(kind=wp), private :: h_left
real(kind=wp), private :: h_min_pos
real(kind=wp), private :: h_right
logical, private :: has_n_flux
logical, private :: has_s_flux
real(kind=wp), private :: hm1
real(kind=wp), private :: hm2
real(kind=wp), private :: hp1
real(kind=wp), private :: hp2
integer, private :: i
integer, private :: j
integer, private :: k
integer, private :: nx
integer, private :: ny
integer, private :: nz
logical, private :: renorm_skip_walls
real(kind=wp), private :: two_h_lim
real(kind=wp), private :: v

Source Code

   pure subroutine continuity_meridional_flux(grid, metrics, this, ms, dt, vhbt, bc, &
                                              visc_rem, v_cor)
      !! Meridional (y-only) PPM reconstruction + per-face mass
      !! flux.  Mirror of `continuity_zonal_flux`, with the same
      !! optional `vhbt` transport-constraint renormalisation.
      !! Writes `ms%mass_flux_y_layer`.  Walls at j=1 and j=ny+1
      !! zeroed.  In the Lie split this runs *after* the zonal
      !! apply, so it reconstructs against the already-updated
      !! `ms%h_layer`.
      type(hgrid_t), intent(in) :: grid
      type(ocean_metrics_t), intent(in) :: metrics
      type(continuity_t), intent(inout) :: this
      type(multilayer_state_t), intent(inout) :: ms
      real(wp), intent(in) :: dt
         !! Time increment (s).  Used only for the swept-volume CFL when
         !! `this%vol_cfl = .true.`; ignored (bit-identical) otherwise.
      real(wp), intent(in), optional :: vhbt(:, :)
         !! Time-mean north-face transport from barotropic substep (m³/s),
         !! shape `(nx, ny+1)`.  Width-weighted (carries `dx_cv`).
      type(ocean_bc_state_t), intent(in), optional :: bc
      ! assumed-shape-ok: pure passthrough to the renormaliser.
      real(wp), intent(in), optional :: visc_rem(:, :, :)
         !! Per-layer viscous remnant gamma_k. Absent => 1, bit-identical.
      ! assumed-shape-ok: pure passthrough to the renormaliser.
      real(wp), intent(inout), optional :: v_cor(:, :, :)
         !! Forwarded MOM6 `v_cor` destination — separate time-mean field.

      integer :: i, j, k, nx, ny, nz
      integer :: bc_s_tag, bc_n_tag
      real(wp) :: dh_m1, dh_0, dh_p1, h_left, h_right, v, h_face
      real(wp) :: hm2, hm1, h0, hp1, hp2
      real(wp) :: cfl_d, curv3_d, dh_d
      logical :: do_pos, do_volcfl, do_pd
      logical :: has_s_flux, has_n_flux, renorm_skip_walls
      real(wp) :: h_min_pos, two_h_lim

      nx = grid%nx_total
      ny = grid%ny_total
      nz = ms%nz_ml
      do_pos = this%use_ppm_limit_pos
      do_volcfl = this%vol_cfl
      h_min_pos = this%h_min
      ! P1 positive-definite reconstruction floor (MOM6's positive-definite
      ! PPM).  Scalars copied to locals so the DC loop never walks the
      ! continuity_t descriptor.  Off ⇒ untaken branch ⇒ bit-identical.
      do_pd = this%positive_definite
      two_h_lim = 2.0_wp*this%h_lim

      do concurrent(k=1:nz, j=3:ny - 2, i=1:nx) &
         local(dh_m1, dh_0, dh_p1, h_left, h_right, hm2, hm1, h0, hp1, hp2)
         h0 = ms%h_layer(i, j, k)
         hm1 = ppm_mirror_h(ms%h_layer(i, j - 1, k), h0, metrics%wet_T(i, j - 1))
         hp1 = ppm_mirror_h(ms%h_layer(i, j + 1, k), h0, metrics%wet_T(i, j + 1))
         hm2 = ppm_mirror_h(ms%h_layer(i, j - 2, k), hm1, metrics%wet_T(i, j - 2))
         hp2 = ppm_mirror_h(ms%h_layer(i, j + 2, k), hp1, metrics%wet_T(i, j + 2))
         call ppm_limited_slope(hm2, hm1, h0, dh_m1)
         call ppm_limited_slope(hm1, h0, hp1, dh_0)
         call ppm_limited_slope(h0, hp1, hp2, dh_p1)
         dh_0 = dh_0*metrics%wet_T(i, j - 1)*metrics%wet_T(i, j)*metrics%wet_T(i, j + 1)
         h_left = 0.5_wp*(hm1 + h0) - (dh_0 - dh_m1)/6.0_wp
         h_right = 0.5_wp*(h0 + hp1) - (dh_p1 - dh_0)/6.0_wp
         call ppm_cell_limiter(h0, h_left, h_right)
         if (do_pos) call ppm_limit_pos(h0, h_left, h_right, h_min_pos)
         if (do_pd) then
            h_left = max(h_left, two_h_lim)
            h_right = max(h_right, two_h_lim)
         end if
         this%h_face_right_y%data(i, j, k) = h_left
         this%h_face_left_y%data(i, j + 1, k) = h_right
      end do
      do concurrent(k=1:nz, i=1:nx)
         this%h_face_left_y%data(i, 1, k) = ms%h_layer(i, 1, k)
         this%h_face_right_y%data(i, 1, k) = ms%h_layer(i, 1, k)
         this%h_face_left_y%data(i, 2, k) = ms%h_layer(i, 1, k)
         this%h_face_right_y%data(i, 2, k) = ms%h_layer(i, 2, k)
         this%h_face_left_y%data(i, 3, k) = ms%h_layer(i, 2, k)
         this%h_face_right_y%data(i, 3, k) = ms%h_layer(i, 2, k)
         this%h_face_right_y%data(i, ny - 1, k) = ms%h_layer(i, ny - 1, k)
         this%h_face_left_y%data(i, ny, k) = ms%h_layer(i, ny - 1, k)
         this%h_face_right_y%data(i, ny, k) = ms%h_layer(i, ny, k)
         this%h_face_left_y%data(i, ny + 1, k) = ms%h_layer(i, ny, k)
         this%h_face_right_y%data(i, ny + 1, k) = ms%h_layer(i, ny, k)
      end do

      do concurrent(k=1:nz, j=2:ny, i=1:nx) &
         local(v, h_face, cfl_d, curv3_d, dh_d)
         v = ms%v_face_y_layer(i, j, k)
         if (v >= 0.0_wp) then
            ! Donor = cell j-1; downwind (north) edge = h_face_left_y(j).
            h_face = this%h_face_left_y%data(i, j, k)
            if (do_volcfl) then
               ! Swept-oriented donor edge diff dh = h_S - h_N (south-north).
               dh_d = this%h_face_right_y%data(i, j - 1, k) - h_face
               curv3_d = this%h_face_right_y%data(i, j - 1, k) + h_face &
                         - 2.0_wp*ms%h_layer(i, j - 1, k)
               cfl_d = v*dt*metrics%dx_cv(i, j)*metrics%iareaT(i, j - 1)
               h_face = volcfl_face(h_face, dh_d, curv3_d, cfl_d)
            end if
         else
            ! Donor = cell j; downwind (south) edge = h_face_right_y(j).
            h_face = this%h_face_right_y%data(i, j, k)
            if (do_volcfl) then
               ! Swept-oriented donor edge diff dh = h_N - h_S (north-south).
               dh_d = this%h_face_left_y%data(i, j + 1, k) - h_face
               curv3_d = h_face + this%h_face_left_y%data(i, j + 1, k) &
                         - 2.0_wp*ms%h_layer(i, j, k)
               cfl_d = (-v)*dt*metrics%dx_cv(i, j)*metrics%iareaT(i, j)
               h_face = volcfl_face(h_face, dh_d, curv3_d, cfl_d)
            end if
         end if
         ms%mass_flux_y_layer(i, j, k) = v*h_face*metrics%dx_cv(i, j)
      end do
      do concurrent(k=1:nz, i=1:nx)
         ms%mass_flux_y_layer(i, 1, k) = 0.0_wp
         ms%mass_flux_y_layer(i, ny + 1, k) = 0.0_wp
      end do
      ! Physical wall zeroing — see `continuity_zonal_flux` for the
      ! detailed rationale.  Without this, v_face_y at the physical
      ! south/north walls picks up Coriolis / wind contributions and
      ! the PPM transport leaks tracer through to ghost cells.
      ! OBC dispatch matches the zonal helper — see header.
      ! Under MPI decomposition bc%has_south / bc%has_north gates the
      ! zeroing so a subdomain seam face is left for the halo exchange.
      bc_s_tag = OBC_WALL
      bc_n_tag = OBC_WALL
      ! Physical-edge flags: default .true. => single-rank bit-identical
      ! behaviour; .false. at an MPI seam skips the hard zero (O0, D0).
      has_s_flux = .true.
      has_n_flux = .true.
      if (present(bc)) then
         bc_s_tag = ocean_bc_outer_face_tag(bc%south%bc_type)
         bc_n_tag = ocean_bc_outer_face_tag(bc%north%bc_type)
         has_s_flux = bc%has_south
         has_n_flux = bc%has_north
      end if
      do concurrent(k=1:nz, i=1:nx)
         if (bc_s_tag == OBC_WALL .and. has_s_flux) ms%mass_flux_y_layer(i, grid%nghost + 1, k) = 0.0_wp
         if (bc_n_tag == OBC_WALL .and. has_n_flux) ms%mass_flux_y_layer(i, grid%nghost + grid%ny_phys + 1, k) = 0.0_wp
      end do

      ! ---- Porous barriers: see the zonal twin (incl. why it is inline) ----
      if (metrics%use_porous) then
         do concurrent(k=1:nz, j=1:ny + 1, i=1:nx)
            ms%mass_flux_y_layer(i, j, k) = ms%mass_flux_y_layer(i, j, k)* &
                                            metrics%por_face_area_v(i, j, k)
         end do
      end if

      ! ---- z-level closed faces: see the zonal twin ----
      if (metrics%use_closed_faces) then
         do concurrent(k=1:nz, j=1:ny + 1, i=1:nx)
            ms%mass_flux_y_layer(i, j, k) = ms%mass_flux_y_layer(i, j, k)* &
                                            metrics%open_v(i, j, k)
         end do
      end if

      ! MOM6-style transport constraint — see continuity_zonal_flux
      ! for the rationale.
      if (present(vhbt)) then
         ! Any non-WALL meridional edge (periodic OR open-class) carries
         ! genuine transport at the physical-wall face and must be
         ! renormalised — see continuity_zonal_flux for the full rationale.
         ! Genuine closed WALL is skipped (default); all-WALL stays
         ! bit-identical to pre-OBC behaviour.
         ! One call site per `por` actual — see the zonal twin for why
         ! folding the two BC branches together is exact and why it
         ! matters for the default-path cost.
         renorm_skip_walls = (bc_s_tag == OBC_WALL .and. bc_n_tag == OBC_WALL)
         ! Four branches — see the zonal twin for why.
         if (metrics%use_porous) then
            if (metrics%use_closed_faces) then
               call renormalise_meridional_flux_to_vhbt(grid, metrics, this, ms, vhbt, dt, &
                                                        skip_walls=renorm_skip_walls, &
                                                        has_south=has_s_flux, has_north=has_n_flux, &
                                                        visc_rem=visc_rem, v_cor=v_cor, &
                                                        use_por=.true., por=metrics%por_face_area_v, &
                                                        use_open=.true., open_f=metrics%open_v)
            else
               call renormalise_meridional_flux_to_vhbt(grid, metrics, this, ms, vhbt, dt, &
                                                        skip_walls=renorm_skip_walls, &
                                                        has_south=has_s_flux, has_north=has_n_flux, &
                                                        visc_rem=visc_rem, v_cor=v_cor, &
                                                        use_por=.true., por=metrics%por_face_area_v, &
                                                        use_open=.false., open_f=this%h_face_right_y%data)
            end if
         else if (metrics%use_closed_faces) then
            call renormalise_meridional_flux_to_vhbt(grid, metrics, this, ms, vhbt, dt, &
                                                     skip_walls=renorm_skip_walls, &
                                                     has_south=has_s_flux, has_north=has_n_flux, &
                                                     visc_rem=visc_rem, v_cor=v_cor, &
                                                     use_por=.false., por=this%h_face_left_y%data, &
                                                     use_open=.true., open_f=metrics%open_v)
         else
            call renormalise_meridional_flux_to_vhbt(grid, metrics, this, ms, vhbt, dt, &
                                                     skip_walls=renorm_skip_walls, &
                                                     has_south=has_s_flux, has_north=has_n_flux, &
                                                     visc_rem=visc_rem, v_cor=v_cor, &
                                                     use_por=.false., por=this%h_face_left_y%data, &
                                                     use_open=.false., open_f=this%h_face_right_y%data)
         end if
      end if
   end subroutine continuity_meridional_flux