remap_column_ppm_h4 Subroutine

public pure subroutine remap_column_ppm_h4(nz, dz_old, dz_new, q_old, q_new, bnd_extrap, nonunif)

PPM with non-uniform 4th-order (H4) edge values (White & Adcroft 2008). As remap_column_ppm but the interior edge estimate is the thickness-weighted exactly-4th-order stencil (reduces to PPM’s (7/12,-1/12) on uniform layers), cutting spurious diapycnal mixing per remap (Ilicak et al. 2012). Limiter/reconstruction/integration are identical to PPM. H4 edge algebra is inlined (helper extraction costs 4-6% on this hot kernel). Boundary edges: outermost = PCM, second-from-boundary = non-uniform 3-cell (H3) quadratic; CW limiter clamps all edges to local monotone bounds.

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nz
real(kind=wp), intent(in) :: dz_old(nz)

Old layer thicknesses

real(kind=wp), intent(in) :: dz_new(nz)

New layer thicknesses

real(kind=wp), intent(in) :: q_old(nz)

Old cell-average scalar values

real(kind=wp), intent(out) :: q_new(nz)

New cell-average scalar values (conservative)

logical, intent(in), optional :: bnd_extrap

Linear-exact boundary-cell closure (MOM6 BOUNDARY_EXTRAPOLATION).

logical, intent(in), optional :: nonunif

Non-uniform-grid weights. The H4/H3 stencils below are ALREADY thickness-weighted, so this only reaches the nz == 2 PLM fallback; passed through for consistency.


Calls

proc~~remap_column_ppm_h4~~CallsGraph proc~remap_column_ppm_h4 remap_column_ppm_h4 proc~boundary_half_jump boundary_half_jump proc~remap_column_ppm_h4->proc~boundary_half_jump proc~remap_column_plm remap_column_plm proc~remap_column_ppm_h4->proc~remap_column_plm proc~remap_column_plm->proc~boundary_half_jump proc~plm_slope_nonuniform plm_slope_nonuniform proc~remap_column_plm->proc~plm_slope_nonuniform

Called by

proc~~remap_column_ppm_h4~~CalledByGraph proc~remap_column_ppm_h4 remap_column_ppm_h4 proc~remap_column remap_column proc~remap_column->proc~remap_column_ppm_h4 proc~ocean_remap_tracer_column ocean_remap_tracer_column proc~ocean_remap_tracer_column->proc~remap_column proc~ocean_remap_tracer_field ocean_remap_tracer_field proc~ocean_remap_tracer_field->proc~remap_column proc~remap_layer_to_density_impl remap_layer_to_density_impl proc~remap_layer_to_density_impl->proc~remap_column proc~remap_layer_to_vcoord_impl remap_layer_to_vcoord_impl proc~remap_layer_to_vcoord_impl->proc~remap_column proc~remap_tracer_grounded remap_tracer_grounded proc~remap_tracer_grounded->proc~remap_column proc~remap_x_face_grounded remap_x_face_grounded proc~remap_x_face_grounded->proc~remap_column proc~remap_x_face_velocity remap_x_face_velocity proc~remap_x_face_velocity->proc~remap_column proc~remap_y_face_grounded remap_y_face_grounded proc~remap_y_face_grounded->proc~remap_column proc~remap_y_face_velocity remap_y_face_velocity proc~remap_y_face_velocity->proc~remap_column proc~ocean_apply_ale_remap_centres ocean_apply_ale_remap_centres proc~ocean_apply_ale_remap_centres->proc~ocean_remap_tracer_field proc~ocean_apply_ale_remap_faces ocean_apply_ale_remap_faces proc~ocean_apply_ale_remap_faces->proc~remap_x_face_velocity proc~ocean_apply_ale_remap_faces->proc~remap_y_face_velocity proc~ocean_apply_ale_remap_step ocean_apply_ale_remap_step proc~ocean_apply_ale_remap_step->proc~ocean_remap_tracer_field proc~ocean_apply_ale_remap_step->proc~remap_x_face_velocity proc~ocean_apply_ale_remap_step->proc~remap_y_face_velocity proc~ocean_apply_conservative_min_thickness ocean_apply_conservative_min_thickness proc~ocean_apply_conservative_min_thickness->proc~remap_tracer_grounded proc~ocean_apply_conservative_min_thickness->proc~remap_x_face_grounded proc~ocean_apply_conservative_min_thickness->proc~remap_y_face_grounded proc~remap_layer_to_density remap_layer_to_density proc~remap_layer_to_density->proc~remap_layer_to_density_impl proc~remap_layer_to_sigma remap_layer_to_sigma proc~remap_layer_to_sigma->proc~remap_layer_to_vcoord_impl proc~remap_layer_to_z remap_layer_to_z proc~remap_layer_to_z->proc~remap_layer_to_vcoord_impl proc~remap_layer_to_zstar remap_layer_to_zstar proc~remap_layer_to_zstar->proc~remap_layer_to_vcoord_impl proc~ocean_dyn_step_split ocean_dyn_step_split proc~ocean_dyn_step_split->proc~ocean_apply_ale_remap_step proc~run_continuity_chain run_continuity_chain proc~run_continuity_chain->proc~ocean_apply_conservative_min_thickness proc~engine_step engine_step proc~engine_step->proc~ocean_dyn_step_split proc~run_stage_split run_stage_split proc~run_stage_split->proc~run_continuity_chain

Variables

Type Visibility Attributes Name Initial
logical, private :: be
real(kind=wp), private :: ca
real(kind=wp), private :: cb
real(kind=wp), private :: cc
real(kind=wp), private :: d_bnd
real(kind=wp), private :: det
real(kind=wp), private :: dq
real(kind=wp), private :: dq_l
real(kind=wp), private :: dq_r
real(kind=wp), private :: edge_val
real(kind=wp), private :: et1
real(kind=wp), private :: et2
real(kind=wp), private :: et3
real(kind=wp), private :: f1
real(kind=wp), private :: f2
real(kind=wp), private :: f3
real(kind=wp), private :: h0
real(kind=wp), private :: h01
real(kind=wp), private :: h012
real(kind=wp), private :: h0123
real(kind=wp), private :: h1
real(kind=wp), private :: h12
real(kind=wp), private :: h123
real(kind=wp), private :: h2
real(kind=wp), private :: h23
real(kind=wp), private :: h3
real(kind=wp), private :: h_sum
real(kind=wp), private :: hf
real(kind=wp), private :: integral
integer, private :: k
integer, private :: ko
integer, private :: ko_start
real(kind=wp), private :: m11
real(kind=wp), private :: m12
real(kind=wp), private :: m13
real(kind=wp), private :: m21
real(kind=wp), private :: m22
real(kind=wp), private :: m23
real(kind=wp), private :: m31
real(kind=wp), private :: m32
real(kind=wp), private :: m33
logical, private :: nu
real(kind=wp), private :: overlap
real(kind=wp), private :: q6(NZ_STACK_MAX)
real(kind=wp), private :: q_L(NZ_STACK_MAX)
real(kind=wp), private :: q_R(NZ_STACK_MAX)
real(kind=wp), private :: q_max
real(kind=wp), private :: q_min
real(kind=wp), private :: xi_hi
real(kind=wp), private :: xi_lo
real(kind=wp), private :: z1
real(kind=wp), private :: z2
real(kind=wp), private :: z3
real(kind=wp), private :: z_hi
real(kind=wp), private :: z_lo
real(kind=wp), private :: z_new(0:NZ_STACK_MAX)
real(kind=wp), private :: z_old(0:NZ_STACK_MAX)

Source Code

   pure subroutine remap_column_ppm_h4(nz, dz_old, dz_new, q_old, q_new, bnd_extrap, nonunif)
      !$acc routine seq
      !! PPM with non-uniform 4th-order (H4) edge values (White & Adcroft 2008).
      !! As `remap_column_ppm` but the interior edge estimate is the
      !! thickness-weighted exactly-4th-order stencil (reduces to PPM's
      !! (7/12,-1/12) on uniform layers), cutting spurious diapycnal mixing per
      !! remap (Ilicak et al. 2012). Limiter/reconstruction/integration are
      !! identical to PPM. H4 edge algebra is inlined (helper extraction costs
      !! 4-6% on this hot kernel). Boundary edges: outermost = PCM,
      !! second-from-boundary = non-uniform 3-cell (H3) quadratic; CW limiter
      !! clamps all edges to local monotone bounds.
      integer, intent(in) :: nz
      real(wp), intent(in) :: dz_old(nz)
         !! Old layer thicknesses
      real(wp), intent(in) :: dz_new(nz)
         !! New layer thicknesses
      real(wp), intent(in) :: q_old(nz)
         !! Old cell-average scalar values
      real(wp), intent(out) :: q_new(nz)
         !! New cell-average scalar values (conservative)

      real(wp) :: z_old(0:NZ_STACK_MAX), z_new(0:NZ_STACK_MAX)
      real(wp) :: q_L(NZ_STACK_MAX), q_R(NZ_STACK_MAX), q6(NZ_STACK_MAX)
      real(wp) :: z_lo, z_hi, overlap, integral
      real(wp) :: xi_lo, xi_hi
      logical, intent(in), optional :: bnd_extrap
         !! Linear-exact boundary-cell closure (MOM6 BOUNDARY_EXTRAPOLATION).
      logical, intent(in), optional :: nonunif
         !! Non-uniform-grid weights.  The H4/H3 stencils below are ALREADY
         !! thickness-weighted, so this only reaches the `nz == 2` PLM
         !! fallback; passed through for consistency.

      real(wp) :: dq, dq_l, dq_r, q_min, q_max
      real(wp) :: d_bnd
      logical :: be, nu
      real(wp) :: h0, h1, h2, h3, hf, h_sum
      real(wp) :: h01, h12, h23, h012, h123, h0123
      real(wp) :: f1, f2, f3, et1, et2, et3
      real(wp) :: m11, m12, m13, m21, m22, m23, m31, m32, m33, det
      real(wp) :: z1, z2, z3, ca, cb, cc, edge_val
      integer :: k, ko, ko_start

      be = .false.
      if (present(bnd_extrap)) be = bnd_extrap
      nu = .false.
      if (present(nonunif)) nu = nonunif

      ! Trivial cases (identical to PPM)
      if (nz == 1) then
         q_new(1) = q_old(1)
         return
      end if
      if (nz == 2) then
         ! With only 2 layers, PPM reduces to PLM
         call remap_column_plm(nz, dz_old, dz_new, q_old, q_new, be, nu)
         return
      end if

      ! Build interface positions
      z_old(0) = 0.0_wp
      z_new(0) = 0.0_wp
      do k = 1, nz
         z_old(k) = z_old(k - 1) + dz_old(k)
         z_new(k) = z_new(k - 1) + dz_new(k)
      end do

      ! ---- Step 1 (PPM_H4): non-uniform 4th-order edge values ----
      ! Store edges in q_R(k) = right edge of layer k = q_L(k+1).

      ! Outermost edges: PCM (layer 1 left, layer nz right)
      q_L(1) = q_old(1)
      q_R(nz) = q_old(nz)

      ! Second-from-boundary edges: non-uniform 3-cell H3 quadratic.
      ! Bed side: edge between layer 1 and 2 — fit a quadratic through layers
      ! 1,2,3 (cell-average constraints) and evaluate at the 1|2 interface.
      ! z-coordinate with the left of layer 1 at 0:
      h1 = dz_old(1)
      h2 = dz_old(2)
      h3 = dz_old(3)
      ! H3 boundary singularity guard (mirror of the interior floor)
      if (h1 <= 0.0_wp .or. h2 <= 0.0_wp .or. h3 <= 0.0_wp) then
         hf = H_MIN_FRAC*max(H_NEGLECT, dz_old(1) + dz_old(2) + dz_old(3))
         h1 = max(h1, hf)
         h2 = max(h2, hf)
         h3 = max(h3, hf)
      end if
      z1 = h1
      z2 = h1 + h2
      z3 = h1 + h2 + h3
      ! Cell moments [int z^2 / h, int z / h, 1] over each layer:
      m11 = (z1*z1*z1)/(3.0_wp*h1)
      m12 = (z1*z1)/(2.0_wp*h1)
      m13 = 1.0_wp
      m21 = (z2*z2*z2 - z1*z1*z1)/(3.0_wp*h2)
      m22 = (z2*z2 - z1*z1)/(2.0_wp*h2)
      m23 = 1.0_wp
      m31 = (z3*z3*z3 - z2*z2*z2)/(3.0_wp*h3)
      m32 = (z3*z3 - z2*z2)/(2.0_wp*h3)
      m33 = 1.0_wp
      det = m11*(m22*m33 - m23*m32) - m12*(m21*m33 - m23*m31) + m13*(m21*m32 - m22*m31)
      ! Cramer's rule for the quadratic coefficients ca*z^2 + cb*z + cc:
      ca = (q_old(1)*(m22*m33 - m23*m32) - m12*(q_old(2)*m33 - m23*q_old(3)) &
            + m13*(q_old(2)*m32 - m22*q_old(3)))/det
      cb = (m11*(q_old(2)*m33 - m23*q_old(3)) - q_old(1)*(m21*m33 - m23*m31) &
            + m13*(m21*q_old(3) - q_old(2)*m31))/det
      cc = (m11*(m22*q_old(3) - q_old(2)*m32) - m12*(m21*q_old(3) - q_old(2)*m31) &
            + q_old(1)*(m21*m32 - m22*m31))/det
      edge_val = ca*z1*z1 + cb*z1 + cc   ! evaluate at 1|2 interface
      q_R(1) = edge_val
      q_L(2) = edge_val

      ! Surface side: edge between layers nz-1 and nz — quadratic through
      ! layers nz-2, nz-1, nz, evaluated at the (nz-1)|nz interface.
      h1 = dz_old(nz - 2)
      h2 = dz_old(nz - 1)
      h3 = dz_old(nz)
      ! H3 boundary singularity guard (mirror of the interior floor)
      if (h1 <= 0.0_wp .or. h2 <= 0.0_wp .or. h3 <= 0.0_wp) then
         hf = H_MIN_FRAC*max(H_NEGLECT, dz_old(nz - 2) + dz_old(nz - 1) + dz_old(nz))
         h1 = max(h1, hf)
         h2 = max(h2, hf)
         h3 = max(h3, hf)
      end if
      z1 = h1
      z2 = h1 + h2
      z3 = h1 + h2 + h3
      m11 = (z1*z1*z1)/(3.0_wp*h1)
      m12 = (z1*z1)/(2.0_wp*h1)
      m13 = 1.0_wp
      m21 = (z2*z2*z2 - z1*z1*z1)/(3.0_wp*h2)
      m22 = (z2*z2 - z1*z1)/(2.0_wp*h2)
      m23 = 1.0_wp
      m31 = (z3*z3*z3 - z2*z2*z2)/(3.0_wp*h3)
      m32 = (z3*z3 - z2*z2)/(2.0_wp*h3)
      m33 = 1.0_wp
      det = m11*(m22*m33 - m23*m32) - m12*(m21*m33 - m23*m31) + m13*(m21*m32 - m22*m31)
      ca = (q_old(nz - 2)*(m22*m33 - m23*m32) - m12*(q_old(nz - 1)*m33 - m23*q_old(nz)) &
            + m13*(q_old(nz - 1)*m32 - m22*q_old(nz)))/det
      cb = (m11*(q_old(nz - 1)*m33 - m23*q_old(nz)) - q_old(nz - 2)*(m21*m33 - m23*m31) &
            + m13*(m21*q_old(nz) - q_old(nz - 1)*m31))/det
      cc = (m11*(m22*q_old(nz) - q_old(nz - 1)*m32) - m12*(m21*q_old(nz) - q_old(nz - 1)*m31) &
            + q_old(nz - 2)*(m21*m32 - m22*m31))/det
      edge_val = ca*z2*z2 + cb*z2 + cc   ! evaluate at (nz-1)|nz interface
      q_R(nz - 1) = edge_val
      q_L(nz) = edge_val

      ! Interior edges: full non-uniform H4 stencil (4 cells k-1..k+2).
      ! q_R(k) is the edge between layer k and k+1; stencil thicknesses
      ! h0=dz(k-1), h1=dz(k), h2=dz(k+1), h3=dz(k+2).
      do k = 2, nz - 2
         h0 = dz_old(k - 1)
         h1 = dz_old(k)
         h2 = dz_old(k + 1)
         h3 = dz_old(k + 2)

         ! Conditional singularity guard: only floor when a consecutive
         ! thickness pair sums to ~0 (vanishing layers under ZSTAR_FULL).
         h_sum = h0 + h1 + h2 + h3
         if (h0 + h1 <= 0.0_wp .or. h1 + h2 <= 0.0_wp .or. h2 + h3 <= 0.0_wp) then
            hf = H_MIN_FRAC*max(H_NEGLECT, h_sum)
            h0 = max(h0, hf)
            h1 = max(h1, hf)
            h2 = max(h2, hf)
            h3 = max(h3, hf)
         end if

         h01 = h0 + h1
         h12 = h1 + h2
         h23 = h2 + h3
         h012 = h0 + h1 + h2
         h123 = h1 + h2 + h3
         h0123 = h0 + h1 + h2 + h3

         f1 = h01*h23/h12
         f2 = h2*q_old(k) + h1*q_old(k + 1)
         f3 = 1.0_wp/h012 + 1.0_wp/h123
         et1 = f1*f2*f3
         et2 = (h2*h23/(h012*h01))*((h0 + 2.0_wp*h1)*q_old(k) - h1*q_old(k - 1))
         et3 = (h1*h01/(h123*h23))*((2.0_wp*h2 + h3)*q_old(k + 1) - h2*q_old(k + 2))

         q_R(k) = (et1 + et2 + et3)/h0123
         q_L(k + 1) = q_R(k)
      end do

      ! ---- Step 2: Colella-Woodward monotonicity limiting (verbatim) ----
      do k = 1, nz
         q_min = q_old(k)
         q_max = q_old(k)
         if (k > 1) then
            q_min = min(q_min, q_old(k - 1))
            q_max = max(q_max, q_old(k - 1))
         end if
         if (k < nz) then
            q_min = min(q_min, q_old(k + 1))
            q_max = max(q_max, q_old(k + 1))
         end if

         ! Clip edges to local bounds
         q_L(k) = max(q_min, min(q_max, q_L(k)))
         q_R(k) = max(q_min, min(q_max, q_R(k)))

         ! CW monotonicity: if the cell is a local extremum, flatten
         dq = q_R(k) - q_L(k)
         dq_l = q_old(k) - q_L(k)
         dq_r = q_R(k) - q_old(k)
         if (dq_l*dq_r <= 0.0_wp) then
            ! Local extremum: flatten to PCM
            q_L(k) = q_old(k)
            q_R(k) = q_old(k)
         else
            ! Check if parabola overshoots
            q6(k) = 6.0_wp*q_old(k) - 3.0_wp*(q_L(k) + q_R(k))
            if (abs(q6(k)) > abs(dq)) then
               if (q6(k)*dq > 0.0_wp) then
                  ! Overshoot near left edge: adjust q_L
                  q_L(k) = 3.0_wp*q_old(k) - 2.0_wp*q_R(k)
               else
                  ! Overshoot near right edge: adjust q_R
                  q_R(k) = 3.0_wp*q_old(k) - 2.0_wp*q_L(k)
               end if
            end if
         end if

         ! Recompute q6 after limiting
         q6(k) = 6.0_wp*q_old(k) - 3.0_wp*(q_L(k) + q_R(k))
      end do

      ! ---- Step 2b: boundary-cell closure (opt-in; see remap_column_ppm) ----
      if (be) then
         call boundary_half_jump(dz_old(1), dz_old(2), q_old(2) - q_old(1), d_bnd)
         q_L(1) = q_old(1) - d_bnd
         q_R(1) = q_old(1) + d_bnd
         q6(1) = 0.0_wp
         call boundary_half_jump(dz_old(nz), dz_old(nz - 1), &
                                 q_old(nz) - q_old(nz - 1), d_bnd)
         q_L(nz) = q_old(nz) - d_bnd
         q_R(nz) = q_old(nz) + d_bnd
         q6(nz) = 0.0_wp
      end if

      ! ---- Step 3: Integrate parabolic reconstruction over new layers (verbatim) ----
      ko_start = 1
      do k = 1, nz
         if (dz_new(k) <= 0.0_wp) then
            q_new(k) = 0.0_wp
            cycle
         end if

         integral = 0.0_wp
         do ko = ko_start, nz
            z_lo = max(z_new(k - 1), z_old(ko - 1))
            z_hi = min(z_new(k), z_old(ko))
            overlap = z_hi - z_lo

            if (overlap <= 0.0_wp) then
               if (z_old(ko) > z_new(k)) exit
               cycle
            end if

            if (dz_old(ko) > 0.0_wp) then
               xi_lo = (z_lo - z_old(ko - 1))/dz_old(ko)
               xi_hi = (z_hi - z_old(ko - 1))/dz_old(ko)

               ! Parabolic integral
               integral = integral + dz_old(ko)*( &
                          (xi_hi - xi_lo)*q_L(ko) &
                          + 0.5_wp*(xi_hi*xi_hi - xi_lo*xi_lo)*(q_R(ko) - q_L(ko) + q6(ko)) &
                          - (xi_hi*xi_hi*xi_hi - xi_lo*xi_lo*xi_lo)*q6(ko)/3.0_wp)
            else
               integral = integral + q_old(ko)*overlap
            end if

            if (z_old(ko) <= z_new(k)) ko_start = ko
         end do

         q_new(k) = integral/dz_new(k)
      end do
   end subroutine remap_column_ppm_h4