hvisc_compute_biharmonic_face_impl Subroutine

private pure subroutine hvisc_compute_biharmonic_face_impl(u_face, v_face, lap_u, lap_v, du_visc, dv_visc, nu4_face_x, nu4_face_y, bound_coef, dt, idxCu, idyCu, idxCv, idyCv, wet_q, dy_dxT, dx_dyBu, iareaCu, dx_dyT, dy_dxBu, iareaCv, nx, ny, nz, open_u, open_v)

Flow-aware biharmonic friction (MOM6 SMAGORINSKY_AH analogue). Identical to hvisc_compute_biharmonic_impl except Pass 2 multiplies the second Laplacian by the per-face viscosity nu4_face_x/y instead of the scalar nu_4. The face fields are filled upstream by ocean_lateral_mix_compute_smag_ah, which sets them to C_b · L⁴ · |D| clamped to [nu4_bg, nu4_max]. Pass 2 additionally clamps each face coefficient to the per-face explicit-biharmonic CFL ceiling (hvisc_nu4_cfl_bound, scaled by bound_coef) on top of the static nu4_max floor/ceiling applied upstream — so a strain spike on a fine cell can never violate the local CFL bound.

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: u_face(nx+1,ny,nz)
real(kind=wp), intent(in) :: v_face(nx,ny+1,nz)
real(kind=wp), intent(inout) :: lap_u(nx+1,ny,nz)
real(kind=wp), intent(inout) :: lap_v(nx,ny+1,nz)
real(kind=wp), intent(inout) :: du_visc(nx+1,ny,nz)
real(kind=wp), intent(inout) :: dv_visc(nx,ny+1,nz)
real(kind=wp), intent(in) :: nu4_face_x(nx+1,ny,nz)
real(kind=wp), intent(in) :: nu4_face_y(nx,ny+1,nz)
real(kind=wp), intent(in) :: bound_coef
real(kind=wp), intent(in) :: dt
real(kind=wp), intent(in) :: idxCu(nx+1,ny)
real(kind=wp), intent(in) :: idyCu(nx+1,ny)
real(kind=wp), intent(in) :: idxCv(nx,ny+1)
real(kind=wp), intent(in) :: idyCv(nx,ny+1)
real(kind=wp), intent(in) :: wet_q(nx+1,ny+1)

Bu-corner wet mask (metrics%wet_q). BOTH chained Laplacians mask their corner (shear) fluxes by it – free-slip, MOM6 sh_xy = mask2dBu·(...) for Del2u and str_xy·mask2dBu for the biharmonic stress. Unmasked, the zero stored at a land face read as a Dirichlet-0 wall and the k^4 operator rang against it at every staircase step. Always free-slip: MOM6 refuses NOSLIP with BIHARMONIC. All-wet corner => factor 1 => bit-identical.

real(kind=wp), intent(in) :: dy_dxT(nx,ny)
real(kind=wp), intent(in) :: dx_dyBu(nx+1,ny+1)
real(kind=wp), intent(in) :: iareaCu(nx+1,ny)
real(kind=wp), intent(in) :: dx_dyT(nx,ny)
real(kind=wp), intent(in) :: dy_dxBu(nx+1,ny+1)
real(kind=wp), intent(in) :: iareaCv(nx,ny+1)
integer, intent(in) :: nx
integer, intent(in) :: ny
integer, intent(in) :: nz
real(kind=wp), intent(in), optional :: open_u(nx+1,ny,nz)

Per-layer 0/1 u-face open mask (&vcoord_nml zfixed_closed_faces). ABSENT (the default path) => the loops below are textually the ones this routine has always run => bit-identical. PRESENT => see hvisc_biharm_lap_closed: in BOTH chained Laplacians every face-to-face difference carries open(a)*open(b), so a closed face-layer is a free-slip (Neumann/mirror) boundary of the stencil exactly as a land corner is under wet_q, and receives zero tendency.

real(kind=wp), intent(in), optional :: open_v(nx,ny+1,nz)

v-face twin. Present iff open_u is.


Calls

proc~~hvisc_compute_biharmonic_face_impl~~CallsGraph proc~hvisc_compute_biharmonic_face_impl hvisc_compute_biharmonic_face_impl local local proc~hvisc_compute_biharmonic_face_impl->local proc~hvisc_biharm_lap_closed hvisc_biharm_lap_closed proc~hvisc_compute_biharmonic_face_impl->proc~hvisc_biharm_lap_closed proc~hvisc_nu4_cfl_bound hvisc_nu4_cfl_bound proc~hvisc_compute_biharmonic_face_impl->proc~hvisc_nu4_cfl_bound proc~hvisc_biharm_lap_closed->local

Called by

proc~~hvisc_compute_biharmonic_face_impl~~CalledByGraph proc~hvisc_compute_biharmonic_face_impl hvisc_compute_biharmonic_face_impl proc~ocean_horizontal_viscosity_compute_tendencies_on ocean_horizontal_viscosity_compute_tendencies_on proc~ocean_horizontal_viscosity_compute_tendencies_on->proc~hvisc_compute_biharmonic_face_impl proc~ocean_horizontal_viscosity_compute_tendencies ocean_horizontal_viscosity_compute_tendencies proc~ocean_horizontal_viscosity_compute_tendencies->proc~ocean_horizontal_viscosity_compute_tendencies_on proc~run_stage run_stage proc~run_stage->proc~ocean_horizontal_viscosity_compute_tendencies proc~run_stage_split run_stage_split proc~run_stage_split->proc~ocean_horizontal_viscosity_compute_tendencies proc~ocean_dyn_step ocean_dyn_step proc~ocean_dyn_step->proc~run_stage 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 proc~engine_step->proc~ocean_dyn_step_split

Variables

Type Visibility Attributes Name Initial
integer, private :: i
real(kind=wp), private :: idt
integer, private :: j
integer, private :: k
real(kind=wp), private :: l_u
real(kind=wp), private :: l_v
real(kind=wp), private :: nu4_u
real(kind=wp), private :: nu4_v
real(kind=wp), private :: sa
real(kind=wp), private :: sb

Source Code

   pure subroutine hvisc_compute_biharmonic_face_impl(u_face, v_face, lap_u, lap_v, &
                                                      du_visc, dv_visc, &
                                                      nu4_face_x, nu4_face_y, &
                                                      bound_coef, dt, &
                                                      idxCu, idyCu, idxCv, idyCv, wet_q, &
                                                      dy_dxT, dx_dyBu, iareaCu, &
                                                      dx_dyT, dy_dxBu, iareaCv, &
                                                      nx, ny, nz, open_u, open_v)
      !! Flow-aware biharmonic friction (MOM6 SMAGORINSKY_AH analogue).
      !! Identical to `hvisc_compute_biharmonic_impl` except Pass 2
      !! multiplies the second Laplacian by the per-face viscosity
      !! `nu4_face_x/y` instead of the scalar `nu_4`.  The face fields
      !! are filled upstream by `ocean_lateral_mix_compute_smag_ah`,
      !! which sets them to `C_b · L⁴ · |D|` clamped to
      !! `[nu4_bg, nu4_max]`.  Pass 2 additionally clamps each face
      !! coefficient to the per-face explicit-biharmonic CFL ceiling
      !! (`hvisc_nu4_cfl_bound`, scaled by `bound_coef`) on top of the
      !! static `nu4_max` floor/ceiling applied upstream — so a strain
      !! spike on a fine cell can never violate the local CFL bound.
      integer, intent(in)    :: nx, ny, nz
      real(wp), intent(in)    :: u_face(nx + 1, ny, nz), v_face(nx, ny + 1, nz)
      real(wp), intent(inout) :: lap_u(nx + 1, ny, nz), lap_v(nx, ny + 1, nz)
      real(wp), intent(inout) :: du_visc(nx + 1, ny, nz), dv_visc(nx, ny + 1, nz)
      real(wp), intent(in)    :: nu4_face_x(nx + 1, ny, nz), nu4_face_y(nx, ny + 1, nz)
      real(wp), intent(in)    :: bound_coef, dt
      real(wp), intent(in)    :: idxCu(nx + 1, ny), idyCu(nx + 1, ny)
      real(wp), intent(in)    :: idxCv(nx, ny + 1), idyCv(nx, ny + 1)
      real(wp), intent(in)    :: wet_q(nx + 1, ny + 1)
         !! Bu-corner wet mask (`metrics%wet_q`).  BOTH chained Laplacians mask
         !! their corner (shear) fluxes by it -- free-slip, MOM6
         !! `sh_xy = mask2dBu·(...)` for `Del2u` and `str_xy·mask2dBu` for the
         !! biharmonic stress.  Unmasked, the zero stored at a land face read
         !! as a Dirichlet-0 wall and the k^4 operator rang against it at every
         !! staircase step.  Always free-slip: MOM6 refuses NOSLIP with
         !! BIHARMONIC.  All-wet corner => factor 1 => bit-identical.
      real(wp), intent(in)    :: dy_dxT(nx, ny), dx_dyBu(nx + 1, ny + 1), iareaCu(nx + 1, ny)
      real(wp), intent(in)    :: dx_dyT(nx, ny), dy_dxBu(nx + 1, ny + 1), iareaCv(nx, ny + 1)
      real(wp), intent(in), optional :: open_u(nx + 1, ny, nz)
         !! Per-layer 0/1 u-face open mask (`&vcoord_nml
         !! zfixed_closed_faces`).  ABSENT (the default path) => the loops
         !! below are textually the ones this routine has always run =>
         !! bit-identical.  PRESENT => see `hvisc_biharm_lap_closed`: in
         !! BOTH chained Laplacians every face-to-face difference carries
         !! `open(a)*open(b)`, so a closed face-layer is a free-slip
         !! (Neumann/mirror) boundary of the stencil exactly as a land
         !! corner is under `wet_q`, and receives zero tendency.
      real(wp), intent(in), optional :: open_v(nx, ny + 1, nz)
         !! v-face twin.  Present iff `open_u` is.
      integer :: i, j, k
      real(wp) :: l_u, l_v, idt, nu4_u, nu4_v, sa, sb
      idt = 1.0_wp/dt
      sa = 1.0_wp
      sb = 0.0_wp

      if (present(open_u)) then
         ! z-level closed faces: Pass 1 is the gated Laplacian (closed
         ! face-layers hold lap = 0 and are never read as a value);
         ! Pass 2 gates every difference by the NEIGHBOUR's open flag
         ! and the tendency by the face's OWN.  Same per-face CFL clamp as below.
         call hvisc_biharm_lap_closed(u_face, v_face, lap_u, lap_v, wet_q, &
                                      dy_dxT, dx_dyBu, iareaCu, dx_dyT, dy_dxBu, iareaCv, &
                                      nx, ny, nz, open_u, open_v)
         do concurrent(k=1:nz, j=2:ny - 1, i=2:nx) local(l_u, nu4_u)
            l_u = iareaCu(i, j)*( &
                  (dy_dxT(i, j)*(lap_u(i + 1, j, k) - lap_u(i, j, k))*open_u(i + 1, j, k) - &
                   dy_dxT(i - 1, j)*(lap_u(i, j, k) - lap_u(i - 1, j, k))*open_u(i - 1, j, k)) + &
                  (dx_dyBu(i, j + 1)*wet_q(i, j + 1)*(lap_u(i, j + 1, k) - lap_u(i, j, k))*open_u(i, j + 1, k) - &
                   dx_dyBu(i, j)*wet_q(i, j)*(lap_u(i, j, k) - lap_u(i, j - 1, k))*open_u(i, j - 1, k)))
            nu4_u = min(nu4_face_x(i, j, k), &
                        hvisc_nu4_cfl_bound(idxCu(i, j), idyCu(i, j), bound_coef, idt))
            du_visc(i, j, k) = du_visc(i, j, k) - nu4_u*l_u*open_u(i, j, k)
         end do
         do concurrent(k=1:nz, j=2:ny, i=2:nx - 1) local(l_v, nu4_v)
            l_v = iareaCv(i, j)*( &
                  (dx_dyT(i, j)*(lap_v(i, j + 1, k) - lap_v(i, j, k))*open_v(i, j + 1, k) - &
                   dx_dyT(i, j - 1)*(lap_v(i, j, k) - lap_v(i, j - 1, k))*open_v(i, j - 1, k)) + &
                  (dy_dxBu(i + 1, j)*wet_q(i + 1, j)*(lap_v(i + 1, j, k) - lap_v(i, j, k))*open_v(i + 1, j, k) - &
                   dy_dxBu(i, j)*wet_q(i, j)*(lap_v(i, j, k) - lap_v(i - 1, j, k))*open_v(i - 1, j, k)))
            nu4_v = min(nu4_face_y(i, j, k), &
                        hvisc_nu4_cfl_bound(idxCv(i, j), idyCv(i, j), bound_coef, idt))
            dv_visc(i, j, k) = dv_visc(i, j, k) - nu4_v*l_v*open_v(i, j, k)
         end do
         return
      end if

      ! ---- Pass 1: lap_u, lap_v at interior u/v faces (metric FV) ----
      do concurrent(k=1:nz, j=2:ny - 1, i=2:nx) local(l_u)
         l_u = iareaCu(i, j)*( &
               (dy_dxT(i, j)*(u_face(i + 1, j, k) - u_face(i, j, k)) - &
                dy_dxT(i - 1, j)*(u_face(i, j, k) - u_face(i - 1, j, k))) + &
               (dx_dyBu(i, j + 1)*(sa*wet_q(i, j + 1) + sb)*(u_face(i, j + 1, k) - u_face(i, j, k)) - &
                dx_dyBu(i, j)*(sa*wet_q(i, j) + sb)*(u_face(i, j, k) - u_face(i, j - 1, k))))
         lap_u(i, j, k) = l_u
      end do
      do concurrent(k=1:nz, j=1:ny)
         lap_u(1, j, k) = lap_u(2, j, k)
         lap_u(nx + 1, j, k) = lap_u(nx, j, k)
      end do
      do concurrent(k=1:nz, i=1:nx + 1)
         lap_u(i, 1, k) = lap_u(i, 2, k)
         lap_u(i, ny, k) = lap_u(i, ny - 1, k)
      end do

      do concurrent(k=1:nz, j=2:ny, i=2:nx - 1) local(l_v)
         l_v = iareaCv(i, j)*( &
               (dx_dyT(i, j)*(v_face(i, j + 1, k) - v_face(i, j, k)) - &
                dx_dyT(i, j - 1)*(v_face(i, j, k) - v_face(i, j - 1, k))) + &
               (dy_dxBu(i + 1, j)*(sa*wet_q(i + 1, j) + sb)*(v_face(i + 1, j, k) - v_face(i, j, k)) - &
                dy_dxBu(i, j)*(sa*wet_q(i, j) + sb)*(v_face(i, j, k) - v_face(i - 1, j, k))))
         lap_v(i, j, k) = l_v
      end do
      do concurrent(k=1:nz, i=1:nx)
         lap_v(i, 1, k) = lap_v(i, 2, k)
         lap_v(i, ny + 1, k) = lap_v(i, ny, k)
      end do
      do concurrent(k=1:nz, j=1:ny + 1)
         lap_v(1, j, k) = lap_v(2, j, k)
         lap_v(nx, j, k) = lap_v(nx - 1, j, k)
      end do

      ! ---- Pass 2: -nu4_face · ∇²(lap_*) added into du_visc / dv_visc ----
      ! Each per-face nu4 is clamped to the local CFL ceiling; below it
      ! `min(...) == nu4_face` ⇒ bit-identical to the unclamped add.
      do concurrent(k=1:nz, j=2:ny - 1, i=2:nx) local(l_u, nu4_u)
         l_u = iareaCu(i, j)*( &
               (dy_dxT(i, j)*(lap_u(i + 1, j, k) - lap_u(i, j, k)) - &
                dy_dxT(i - 1, j)*(lap_u(i, j, k) - lap_u(i - 1, j, k))) + &
               (dx_dyBu(i, j + 1)*(sa*wet_q(i, j + 1) + sb)*(lap_u(i, j + 1, k) - lap_u(i, j, k)) - &
                dx_dyBu(i, j)*(sa*wet_q(i, j) + sb)*(lap_u(i, j, k) - lap_u(i, j - 1, k))))
         nu4_u = min(nu4_face_x(i, j, k), &
                     hvisc_nu4_cfl_bound(idxCu(i, j), idyCu(i, j), bound_coef, idt))
         du_visc(i, j, k) = du_visc(i, j, k) - nu4_u*l_u
      end do

      do concurrent(k=1:nz, j=2:ny, i=2:nx - 1) local(l_v, nu4_v)
         l_v = iareaCv(i, j)*( &
               (dx_dyT(i, j)*(lap_v(i, j + 1, k) - lap_v(i, j, k)) - &
                dx_dyT(i, j - 1)*(lap_v(i, j, k) - lap_v(i, j - 1, k))) + &
               (dy_dxBu(i + 1, j)*(sa*wet_q(i + 1, j) + sb)*(lap_v(i + 1, j, k) - lap_v(i, j, k)) - &
                dy_dxBu(i, j)*(sa*wet_q(i, j) + sb)*(lap_v(i, j, k) - lap_v(i - 1, j, k))))
         nu4_v = min(nu4_face_y(i, j, k), &
                     hvisc_nu4_cfl_bound(idxCv(i, j), idyCv(i, j), bound_coef, idt))
         dv_visc(i, j, k) = dv_visc(i, j, k) - nu4_v*l_v
      end do
   end subroutine hvisc_compute_biharmonic_face_impl