redi_apply_flux Subroutine

public subroutine redi_apply_flux(grid, metrics, this, ms, dt, khtr_u_ext, khtr_v_ext, bc)

Public Phase-B entry: apply the neutral-diffusion tracer update for every registered tracer. No-op if absent / uninit / disabled / zero diffusivity. Run at thermo cadence after redi_calc_coeffs and the along-coordinate tracer_hdiff (Redi augments it). Per-face KhTr comes from khtr_u_ext/khtr_v_ext (VarMix) when supplied, else the scalar this%khtr broadcast onto every face.

Arguments

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

VarMix per-face KhTr at u-faces (nx+1, ny) (m^2/s).

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

VarMix per-face KhTr at v-faces (nx, ny+1) (m^2/s).

type(ocean_bc_state_t), intent(in), optional :: bc

Per-edge OBC tags. No along-isopycnal flux crosses a no-normal-flow (OBC_WALL) physical-domain boundary face — else Redi bleeds tracer into the ghost halo. An OPEN (tracer-open) edge KEEPS its face flux, read against the OBC-filled ghost column — MOM6 neutral_diffusion gates its faces on G%mask2dCu, which open_boundary_impose_land_mask leaves at 1 on an open segment’s normal face (it zeroes only OBCmaskCu there), so neutral diffusion exchanges tracer with the exterior; that exchange is booked in *_budget_hdiff below so the closed budget stays closed. An MPI seam (has_* = .false.) is never a wall. Absent ⇒ all edges WALL.


Calls

proc~~redi_apply_flux~~CallsGraph proc~redi_apply_flux redi_apply_flux proc~ocean_bc_outer_face_tag ocean_bc_outer_face_tag proc~redi_apply_flux->proc~ocean_bc_outer_face_tag proc~redi_apply_flux_impl redi_apply_flux_impl proc~redi_apply_flux->proc~redi_apply_flux_impl proc~redi_budget_accumulate redi_budget_accumulate proc~redi_apply_flux->proc~redi_budget_accumulate proc~redi_face_const redi_face_const proc~redi_apply_flux->proc~redi_face_const proc~redi_face_copy redi_face_copy proc~redi_apply_flux->proc~redi_face_copy proc~redi_snapshot redi_snapshot proc~redi_apply_flux->proc~redi_snapshot local local proc~redi_apply_flux_impl->local proc~redi_face_flux redi_face_flux proc~redi_apply_flux_impl->proc~redi_face_flux proc~redi_sublayer_dt redi_sublayer_dT proc~redi_face_flux->proc~redi_sublayer_dt proc~redi_tracer_column redi_tracer_column proc~redi_face_flux->proc~redi_tracer_column rdb_vl_is_live rdb_vl_is_live proc~redi_face_flux->rdb_vl_is_live proc~redi_ppm_ave redi_ppm_ave proc~redi_sublayer_dt->proc~redi_ppm_ave proc~redi_signum1 redi_signum1 proc~redi_sublayer_dt->proc~redi_signum1 proc~redi_interface_scalar redi_interface_scalar proc~redi_tracer_column->proc~redi_interface_scalar proc~redi_tracer_column->proc~redi_signum1 rdb_vl_column_conc rdb_vl_column_conc proc~redi_tracer_column->rdb_vl_column_conc proc~redi_plm_diff redi_plm_diff proc~redi_interface_scalar->proc~redi_plm_diff proc~redi_ppm_edge redi_ppm_edge proc~redi_interface_scalar->proc~redi_ppm_edge

Called by

proc~~redi_apply_flux~~CalledByGraph proc~redi_apply_flux redi_apply_flux proc~run_continuity_chain run_continuity_chain proc~run_continuity_chain->proc~redi_apply_flux proc~run_stage_split run_stage_split proc~run_stage_split->proc~run_continuity_chain 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

Variables

Type Visibility Attributes Name Initial
integer, private :: it
integer, private :: nghost
integer, private :: nx
integer, private :: nxp
integer, private :: ny
integer, private :: nyp
integer, private :: nz
logical, private :: use_ext
logical, private :: wall_e
logical, private :: wall_n
logical, private :: wall_s
logical, private :: wall_w

Source Code

   subroutine redi_apply_flux(grid, metrics, this, ms, dt, khtr_u_ext, khtr_v_ext, bc)
      !! Public Phase-B entry: apply the neutral-diffusion tracer update for
      !! every registered tracer.  No-op if absent / uninit / disabled / zero
      !! diffusivity.  Run at thermo cadence after `redi_calc_coeffs` and the
      !! along-coordinate `tracer_hdiff` (Redi augments it).  Per-face KhTr
      !! comes from `khtr_u_ext`/`khtr_v_ext` (VarMix) when supplied, else the
      !! scalar `this%khtr` broadcast onto every face.
      type(hgrid_t), intent(in) :: grid
      type(ocean_metrics_t), intent(in) :: metrics
      type(ocean_redi_t), intent(inout) :: this
      type(multilayer_state_t), intent(inout) :: ms
      real(wp), intent(in) :: dt
      real(wp), intent(in), optional :: khtr_u_ext(:, :)
         !! VarMix per-face KhTr at u-faces `(nx+1, ny)` (m^2/s).
      real(wp), intent(in), optional :: khtr_v_ext(:, :)
         !! VarMix per-face KhTr at v-faces `(nx, ny+1)` (m^2/s).
      type(ocean_bc_state_t), intent(in), optional :: bc
         !! Per-edge OBC tags.  No along-isopycnal flux crosses a
         !! no-normal-flow (`OBC_WALL`) physical-domain boundary face — else
         !! Redi bleeds tracer into the ghost halo.  An OPEN (tracer-open)
         !! edge KEEPS its face flux, read against the OBC-filled ghost column
         !! — MOM6 `neutral_diffusion` gates its faces on `G%mask2dCu`, which
         !! `open_boundary_impose_land_mask` leaves at 1 on an open segment's
         !! normal face (it zeroes only `OBCmaskCu` there), so neutral
         !! diffusion exchanges tracer with the exterior; that exchange is
         !! booked in `*_budget_hdiff` below so the closed budget stays
         !! closed.  An MPI seam (`has_* = .false.`) is never a wall.
         !! Absent ⇒ all edges WALL.
      integer :: nx, ny, nz, it
      integer :: nghost, nxp, nyp
      logical :: use_ext, wall_w, wall_e, wall_s, wall_n

      if (.not. this%is_init) return
      if (.not. this%enable) return
      if (.not. allocated(ms%h_layer)) return
      if (.not. allocated(ms%tracers)) return

      nx = grid%nx_total
      ny = grid%ny_total
      nz = ms%nz_ml
      if (this%nz_ml /= nz) return
      nghost = grid%nghost
      nxp = grid%nx_phys
      nyp = grid%ny_phys
      ! Which physical-domain edges are no-normal-flow walls (default closed).
      ! The edge tags are GLOBAL: under a domain decomposition a tile edge
      ! that is not a physical domain edge (`.not. bc%has_*`) is an MPI
      ! seam, which the neutral flux must cross exactly as an interior face
      ! does — keyed on the tag alone, a wall-tagged edge would close every
      ! seam parallel to it (`test_ocean_decomp_bitid_mpi`, 1x2 split).
      ! Single rank: `has_* = .true.` ⇒ unchanged.
      wall_w = .true.
      wall_e = .true.
      wall_s = .true.
      wall_n = .true.
      if (present(bc)) then
         wall_w = (ocean_bc_outer_face_tag(bc%west%bc_type) == OBC_WALL) .and. bc%has_west
         wall_e = (ocean_bc_outer_face_tag(bc%east%bc_type) == OBC_WALL) .and. bc%has_east
         wall_s = (ocean_bc_outer_face_tag(bc%south%bc_type) == OBC_WALL) .and. bc%has_south
         wall_n = (ocean_bc_outer_face_tag(bc%north%bc_type) == OBC_WALL) .and. bc%has_north
      end if

      use_ext = present(khtr_u_ext) .and. present(khtr_v_ext)
      ! No diffusivity anywhere => nothing to do (scalar path only; the
      ! VarMix field may be non-zero even when the scalar floor is 0).
      if (.not. use_ext .and. this%khtr <= 0.0_wp) return

      ! Resolve the per-face KhTr into the slot fields (device-resident).
      if (use_ext) then
         call redi_face_copy(nx + 1, ny, khtr_u_ext, this%khtr_u)
         call redi_face_copy(nx, ny + 1, khtr_v_ext, this%khtr_v)
      else
         call redi_face_const(nx + 1, ny, this%khtr, this%khtr_u)
         call redi_face_const(nx, ny + 1, this%khtr, this%khtr_v)
      end if

      do it = 1, size(ms%tracers)
         ! Snapshot this tracer's hTr so the double-visit gather reads the
         ! pre-step field (order-independent — see `tr_snap`).
         call redi_snapshot(nx, ny, nz, ms%tracers(it)%hTr, this%tr_snap)
         call redi_apply_flux_impl(nx, ny, nz, this%nsurf, dt, &
                                   nghost, nxp, nyp, wall_w, wall_e, wall_s, wall_n, &
                                   this%khtr_u, this%khtr_v, &
                                   metrics%dy_cu, metrics%dx_cv, &
                                   metrics%idxCu, metrics%idyCv, metrics%areaT, &
                                   ms%h_layer, this%tr_snap, ms%tracers(it)%hTr, &
                                   this%uPoL, this%uPoR, this%uKoL, this%uKoR, this%uhEff, &
                                   this%vPoL, this%vPoR, this%vKoL, this%vKoR, this%vhEff, &
                                   this%uKb, this%uKt, this%vKb, this%vKt, &
                                   metrics%use_closed_faces)
         ! Closed-budget bookkeeping (salt / heat): fold the realised Redi
         ! change into the same lateral-diffusion accumulator `tracer_hdiff`
         ! uses.  Interior faces cancel in the interior sum, so what survives
         ! is exactly the flux through the OPEN physical faces — the term the
         ! console `out` column must carry.  `hTr - tr_snap` is the realised
         ! increment and is exact (Sterbenz: the two are within a factor 2).
         select case (ms%tracers(it)%budget_id)
         case (TRACER_BUDGET_SALT)
            call redi_budget_accumulate(nx, ny, nz, ms%tracers(it)%hTr, this%tr_snap, &
                                        ms%salt_budget_hdiff)
         case (TRACER_BUDGET_HEAT)
            call redi_budget_accumulate(nx, ny, nz, ms%tracers(it)%hTr, this%tr_snap, &
                                        ms%heat_budget_hdiff)
         case default
            ! Passive tracers carry no closed budget.
         end select
      end do
   end subroutine redi_apply_flux