compute_fv_mom6_insitu_pcm_impl Subroutine

private pure subroutine compute_fv_mom6_insitu_pcm_impl(h_layer, hS, hT, b, conc_T, conc_S, e_face, pa, intz_dpa, intx_pa, inty_pa, intx_dpa, inty_dpa, dpdx_face, dpdy_face, rho0, rho_ref, h_neglect, gfs_scale, mass_weight, eos_variant, p_top, p_top_in_bc, idxCu, idyCv, nx, ny, nz)

FV_MOM6 pressure gradient, constant-by-layer (PCM) T/S, density at the IN-SITU pressure — MOM6 PressureForce_FV_Bouss with RECONSTRUCT_FOR_PRESSURE = False (int_density_dz_generic_pcm).

The PCM twin compute_fv_mom6_impl integrates ms%rho_layer, a POTENTIAL density at the one horizontally uniform p_ref. Its horizontal difference at depth is then the difference at the REFERENCE pressure, not at the local one: the thermal expansion coefficient roughly doubles between the surface and 4000 dbar (thermobaricity), so with p_ref = 0 the deep baroclinic pressure gradient — the bottom-pressure gradient that forces the barotropic mode over topography — is systematically too weak. On the global 1-degree WOA13 spin-up it held Drake Passage at ~80 Sv where MOM6 on the same protocol adjusts to ~155 Sv, and the transport tracked p_ref (0 / 2000 / 4000 dbar: 83 / 143 / 203 Sv) — the tell of a reference-pressure artefact.

Here every density is EOS(T, S, p = −g·rho0·z) at the point it is used, integrated (Roquet; Wright takes the closed form below) by the same 5-point Boole rules as the reconstruction branch, with the sub-layer profile flat:

  • Pass 1 (per column): dpa(k), intz_dpa(k) from boole_dpa_intz_layer with top = bottom = mean T/S.
  • Pass 2 (per face): intx_dpa / inty_dpa from boole_dpa_face_pcm — end points are the columns’ own dpa, the three interior sub-columns interpolate z linearly and T/S with MOM6’s near-bottom mass weighting (hWght, the same measure and blend as compute_fv_mom6_impl) when mass_weight.
  • Passes 3–5: the face assembly, identical to the other two FV_MOM6 branches.

Under Wright (wright_analytic) Passes 1–2 replace each vertical Boole rule by the closed-form integral wright_pcm_dpa_intz (MOM6 int_density_dz_wright), keeping the 5-point cross-face Boole rule and the same sub-columns (wright_pcm_dpa_face). The vertical Boole rule it replaces is accurate to (g·rho0·dz/(p + p0 + lambda/alpha0))^6 — round-off for any realistic layer — so the answers move at round-off, but each layer costs one polynomial evaluation instead of 5 generic-EOS calls and each face 3 instead of 15. Measured on the global 1° run (5 days, one V100): ocean_pgf 10.26 s → 1.90 s, against 1.09 s for insitu_density = .false..

Under Roquet the vertical rule stays 5-point Boole (no closed form for int dz/SV(p)), factored: roquet_pcm_dpa_intz evaluates the (T, S) part of the SpV polynomial once per sub-column and only the pressure Horner per point — the same five densities as boole_dpa_intz_layer, so answers are unchanged (to the digit on the global 1° run’s [stats] and En). ocean_pgf 10.73 s → 3.64 s on the same run, → 2.55 s (1.36x Wright) with the SpV value from the module-local include rdb_roquet_spv.inc: as a call into rdb_eos the (T, S) part was NOT inlined on the device (a real call, its four results through the stack, 130+ registers in the face kernels against 88 inlined).

Pass 1 and Pass 2 each run their integrals as a 3-D do concurrent over (k, j, i) — every layer’s and every face’s integral is independent — followed by a cheap per-column scan for the pa / intx_pa / inty_pa recurrences (Pass 1 parks dpa in pa(k) and sums it in place). Same operations in the same order, so bit-identical to the column-serial form; one GPU thread per CELL instead of per column.

The trapezoid 0.5·(dpa_L + dpa_R) of the potential-density twin is NOT kept: an in-situ density carries the compressibility gradient (~4.4e-3 kg m⁻⁴), and the trapezoid’s curvature residual g·(∂ρ/∂z)·Δe²/12 at a tilted interface (a partial-cell bed step) would be of the size of the signal.

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: h_layer(nx,ny,nz)
real(kind=wp), intent(in) :: hS(nx,ny,nz)

Salinity * thickness (PSU*m) — layer-mean S = hS / h.

real(kind=wp), intent(in) :: hT(nx,ny,nz)

Temperature * thickness (degC*m) — layer-mean T = hT / h.

real(kind=wp), intent(in) :: b(nx,ny)
real(kind=wp), intent(inout) :: conc_T(nx,ny,nz)

Layer-mean T / S as every pass below reads them (Pass C).

real(kind=wp), intent(inout) :: conc_S(nx,ny,nz)

Layer-mean T / S as every pass below reads them (Pass C).

real(kind=wp), intent(inout) :: e_face(nx,ny,nz+1)
real(kind=wp), intent(inout) :: pa(nx,ny,nz+1)
real(kind=wp), intent(inout) :: intz_dpa(nx,ny,nz)
real(kind=wp), intent(inout) :: intx_pa(nx+1,ny,nz+1)
real(kind=wp), intent(inout) :: inty_pa(nx,ny+1,nz+1)
real(kind=wp), intent(inout) :: intx_dpa(nx+1,ny,nz)
real(kind=wp), intent(inout) :: inty_dpa(nx,ny+1,nz)
real(kind=wp), intent(inout) :: dpdx_face(nx+1,ny,nz)
real(kind=wp), intent(inout) :: dpdy_face(nx,ny+1,nz)
real(kind=wp), intent(in) :: rho0
real(kind=wp), intent(in) :: rho_ref
real(kind=wp), intent(in) :: h_neglect
real(kind=wp), intent(in) :: gfs_scale
logical, intent(in) :: mass_weight

MOM6 MASS_WEIGHT_IN_PRESSURE_GRADIENT (near-bottom hWght).

integer, intent(in) :: eos_variant

eos%variant, selected ONCE here, outside the loops – each variant has its own loop copies with its integrals inlined, no generic eos_t dispatch in the hot loop. use_insitu_pcm admits exactly two: * EOS_VARIANT_WRIGHT_97: the ANALYTIC Wright layer integral (wright_pcm_dpa_intz / wright_pcm_dpa_face, MOM6 int_density_dz_wright). * EOS_VARIANT_ROQUET_SPV (the else copies): the 5-point Boole rule of MOM6 int_density_dz_generic_pcm, with the (T, S) part of the EOS evaluated once per sub-column (roquet_pcm_dpa_intz / roquet_pcm_dpa_face).

real(kind=wp), intent(in) :: p_top(nx,ny)

Top-of-column pressure (Pa, >= 0), multilayer_state_t%p_top.

logical, intent(in) :: p_top_in_bc

Add p_top to the Pass-1 surface BC (.false. ⇒ the plain rho_ref·g·eta seed).

real(kind=wp), intent(in) :: idxCu(nx+1,ny)
real(kind=wp), intent(in) :: idyCv(nx,ny+1)
integer, intent(in) :: nx
integer, intent(in) :: ny
integer, intent(in) :: nz

Calls

proc~~compute_fv_mom6_insitu_pcm_impl~~CallsGraph proc~compute_fv_mom6_insitu_pcm_impl compute_fv_mom6_insitu_pcm_impl local local proc~compute_fv_mom6_insitu_pcm_impl->local proc~fv_mom6_mass_weights fv_mom6_mass_weights proc~compute_fv_mom6_insitu_pcm_impl->proc~fv_mom6_mass_weights proc~recon_rho_surf recon_rho_surf proc~compute_fv_mom6_insitu_pcm_impl->proc~recon_rho_surf proc~roquet_pcm_dpa_face roquet_pcm_dpa_face proc~compute_fv_mom6_insitu_pcm_impl->proc~roquet_pcm_dpa_face proc~roquet_pcm_dpa_intz roquet_pcm_dpa_intz proc~compute_fv_mom6_insitu_pcm_impl->proc~roquet_pcm_dpa_intz proc~wright_pcm_dpa_face wright_pcm_dpa_face proc~compute_fv_mom6_insitu_pcm_impl->proc~wright_pcm_dpa_face proc~wright_pcm_dpa_intz wright_pcm_dpa_intz proc~compute_fv_mom6_insitu_pcm_impl->proc~wright_pcm_dpa_intz rdb_vl_conc rdb_vl_conc proc~compute_fv_mom6_insitu_pcm_impl->rdb_vl_conc rdb_vl_is_live rdb_vl_is_live proc~compute_fv_mom6_insitu_pcm_impl->rdb_vl_is_live proc~roquet_pcm_dpa_face->proc~roquet_pcm_dpa_intz rdb_roq_spv_p rdb_roq_spv_p proc~roquet_pcm_dpa_intz->rdb_roq_spv_p rdb_roq_ts_coeffs rdb_roq_ts_coeffs proc~roquet_pcm_dpa_intz->rdb_roq_ts_coeffs proc~wright_pcm_dpa_face->proc~wright_pcm_dpa_intz

Called by

proc~~compute_fv_mom6_insitu_pcm_impl~~CalledByGraph proc~compute_fv_mom6_insitu_pcm_impl compute_fv_mom6_insitu_pcm_impl proc~ocean_pressure_force_compute ocean_pressure_force_compute proc~ocean_pressure_force_compute->proc~compute_fv_mom6_insitu_pcm_impl proc~run_stage run_stage proc~run_stage->proc~ocean_pressure_force_compute proc~run_stage_split run_stage_split proc~run_stage_split->proc~ocean_pressure_force_compute 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 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
real(kind=wp), private :: dM_coeff
real(kind=wp), private :: ddM_dx
real(kind=wp), private :: ddM_dy
real(kind=wp), private :: denom
real(kind=wp), private :: dpa_L
real(kind=wp), private :: dpa_R
real(kind=wp), private :: dpa_kk
real(kind=wp), private :: e_bot_L
real(kind=wp), private :: e_bot_R
real(kind=wp), private :: h_L
real(kind=wp), private :: h_R
real(kind=wp), private :: hwt_ll
real(kind=wp), private :: hwt_lr
real(kind=wp), private :: hwt_rl
real(kind=wp), private :: hwt_rr
integer, private :: i
real(kind=wp), private :: intz_kk
real(kind=wp), private :: inv_rho0
integer, private :: j
integer, private :: k
integer, private :: k_don_c
integer, private :: k_top_c
real(kind=wp), private :: numer
real(kind=wp), private :: pa_h_intz_L
real(kind=wp), private :: pa_h_intz_R
real(kind=wp), private :: s_m
real(kind=wp), private :: s_m_L
real(kind=wp), private :: s_m_R
real(kind=wp), private :: t_m
real(kind=wp), private :: t_m_L
real(kind=wp), private :: t_m_R
logical, private :: wright_analytic

Source Code

   pure subroutine compute_fv_mom6_insitu_pcm_impl(h_layer, hS, hT, b, conc_T, conc_S, &
                                                   e_face, pa, intz_dpa, &
                                                   intx_pa, inty_pa, &
                                                   intx_dpa, inty_dpa, &
                                                   dpdx_face, dpdy_face, &
                                                   rho0, rho_ref, h_neglect, &
                                                   gfs_scale, mass_weight, &
                                                   eos_variant, &
                                                   p_top, p_top_in_bc, &
                                                   idxCu, idyCv, nx, ny, nz)
      !! FV_MOM6 pressure gradient, constant-by-layer (PCM) T/S, density at
      !! the IN-SITU pressure — MOM6 `PressureForce_FV_Bouss` with
      !! `RECONSTRUCT_FOR_PRESSURE = False` (`int_density_dz_generic_pcm`).
      !!
      !! The PCM twin `compute_fv_mom6_impl` integrates `ms%rho_layer`, a
      !! POTENTIAL density at the one horizontally uniform `p_ref`.  Its
      !! horizontal difference at depth is then the difference at the
      !! REFERENCE pressure, not at the local one: the thermal expansion
      !! coefficient roughly doubles between the surface and 4000 dbar
      !! (thermobaricity), so with `p_ref = 0` the deep baroclinic
      !! pressure gradient — the bottom-pressure gradient that forces the
      !! barotropic mode over topography — is systematically too weak.
      !! On the global 1-degree WOA13 spin-up it held Drake Passage at
      !! ~80 Sv where MOM6 on the same protocol adjusts to ~155 Sv, and
      !! the transport tracked `p_ref` (0 / 2000 / 4000 dbar: 83 / 143 /
      !! 203 Sv) — the tell of a reference-pressure artefact.
      !!
      !! Here every density is `EOS(T, S, p = −g·rho0·z)` at the point it
      !! is used, integrated (Roquet; Wright takes the closed form below)
      !! by the same 5-point Boole rules as the reconstruction branch, with
      !! the sub-layer profile flat:
      !!
      !!   * Pass 1 (per column): `dpa(k)`, `intz_dpa(k)` from
      !!     `boole_dpa_intz_layer` with top = bottom = mean T/S.
      !!   * Pass 2 (per face): `intx_dpa` / `inty_dpa` from
      !!     `boole_dpa_face_pcm` — end points are the columns' own `dpa`,
      !!     the three interior sub-columns interpolate `z` linearly and
      !!     T/S with MOM6's near-bottom mass weighting (`hWght`, the same
      !!     measure and blend as `compute_fv_mom6_impl`) when
      !!     `mass_weight`.
      !!   * Passes 3–5: the face assembly, identical to the other two
      !!     FV_MOM6 branches.
      !!
      !! Under Wright (`wright_analytic`) Passes 1–2 replace each vertical
      !! Boole rule by the closed-form integral `wright_pcm_dpa_intz` (MOM6
      !! `int_density_dz_wright`), keeping the 5-point cross-face Boole
      !! rule and the same sub-columns (`wright_pcm_dpa_face`).  The
      !! vertical Boole rule it replaces is accurate to
      !! `(g·rho0·dz/(p + p0 + lambda/alpha0))^6` — round-off for any
      !! realistic layer — so the answers move at round-off, but each layer
      !! costs one polynomial evaluation instead of 5 generic-EOS calls and
      !! each face 3 instead of 15.  Measured on the global 1° run (5
      !! days, one V100): `ocean_pgf` 10.26 s → 1.90 s, against 1.09 s for
      !! `insitu_density = .false.`.
      !!
      !! Under Roquet the vertical rule stays 5-point Boole (no closed form
      !! for `int dz/SV(p)`), factored: `roquet_pcm_dpa_intz` evaluates the
      !! (T, S) part of the SpV polynomial once per sub-column and only the
      !! pressure Horner per point — the same five densities as
      !! `boole_dpa_intz_layer`, so answers are unchanged (to the digit on
      !! the global 1° run's `[stats]` and En).  `ocean_pgf` 10.73 s →
      !! 3.64 s on the same run, → 2.55 s (1.36x Wright) with the SpV value
      !! from the module-local include `rdb_roquet_spv.inc`: as a call into
      !! `rdb_eos` the (T, S) part was NOT inlined on the device (a real
      !! `call`, its four results through the stack, 130+ registers in the
      !! face kernels against 88 inlined).
      !!
      !! Pass 1 and Pass 2 each run their integrals as a 3-D `do concurrent`
      !! over (k, j, i) — every layer's and every face's integral is
      !! independent — followed by a cheap per-column scan for the `pa` /
      !! `intx_pa` / `inty_pa` recurrences (Pass 1 parks `dpa` in `pa(k)`
      !! and sums it in place).  Same operations in the same order, so
      !! bit-identical to the column-serial form; one GPU thread per CELL
      !! instead of per column.
      !!
      !! The trapezoid `0.5·(dpa_L + dpa_R)` of the potential-density twin
      !! is NOT kept: an in-situ density carries the compressibility
      !! gradient (`~4.4e-3 kg m⁻⁴`), and the trapezoid's curvature
      !! residual `g·(∂ρ/∂z)·Δe²/12` at a tilted interface (a partial-cell
      !! bed step) would be of the size of the signal.
      integer, intent(in) :: nx, ny, nz
      real(wp), intent(in)    :: h_layer(nx, ny, nz)
      real(wp), intent(in)    :: hS(nx, ny, nz)
         !! Salinity * thickness (PSU*m) — layer-mean S = hS / h.
      real(wp), intent(in)    :: hT(nx, ny, nz)
         !! Temperature * thickness (degC*m) — layer-mean T = hT / h.
      real(wp), intent(in)    :: b(nx, ny)
      real(wp), intent(inout) :: conc_T(nx, ny, nz), conc_S(nx, ny, nz)
         !! Layer-mean T / S as every pass below reads them (Pass C).
      real(wp), intent(inout) :: e_face(nx, ny, nz + 1)
      real(wp), intent(inout) :: pa(nx, ny, nz + 1)
      real(wp), intent(inout) :: intz_dpa(nx, ny, nz)
      real(wp), intent(inout) :: intx_pa(nx + 1, ny, nz + 1)
      real(wp), intent(inout) :: inty_pa(nx, ny + 1, nz + 1)
      real(wp), intent(inout) :: intx_dpa(nx + 1, ny, nz)
      real(wp), intent(inout) :: inty_dpa(nx, ny + 1, nz)
      real(wp), intent(inout) :: dpdx_face(nx + 1, ny, nz)
      real(wp), intent(inout) :: dpdy_face(nx, ny + 1, nz)
      real(wp), intent(in)    :: rho0, rho_ref, h_neglect, gfs_scale
      logical, intent(in)    :: mass_weight
         !! MOM6 `MASS_WEIGHT_IN_PRESSURE_GRADIENT` (near-bottom `hWght`).
      integer, intent(in)    :: eos_variant
         !! `eos%variant`, selected ONCE here, outside the loops -- each
         !! variant has its own loop copies with its integrals inlined, no
         !! generic `eos_t` dispatch in the hot loop.  `use_insitu_pcm`
         !! admits exactly two:
         !!   * `EOS_VARIANT_WRIGHT_97`: the ANALYTIC Wright layer integral
         !!     (`wright_pcm_dpa_intz` / `wright_pcm_dpa_face`, MOM6
         !!     `int_density_dz_wright`).
         !!   * `EOS_VARIANT_ROQUET_SPV` (the `else` copies): the 5-point
         !!     Boole rule of MOM6 `int_density_dz_generic_pcm`, with the
         !!     (T, S) part of the EOS evaluated once per sub-column
         !!     (`roquet_pcm_dpa_intz` / `roquet_pcm_dpa_face`).
      real(wp), intent(in)    :: p_top(nx, ny)
         !! Top-of-column pressure (Pa, `>= 0`), `multilayer_state_t%p_top`.
      logical, intent(in)    :: p_top_in_bc
         !! Add `p_top` to the Pass-1 surface BC (`.false.` ⇒ the plain
         !! `rho_ref·g·eta` seed).
      real(wp), intent(in)    :: idxCu(nx + 1, ny), idyCv(nx, ny + 1)

      integer  :: i, j, k
      real(wp) :: inv_rho0, dpa_kk, intz_kk, t_m, s_m
      real(wp) :: t_m_L, t_m_R, s_m_L, s_m_R, dpa_L, dpa_R
      real(wp) :: hwt_ll, hwt_lr, hwt_rr, hwt_rl
      real(wp) :: h_L, h_R, e_bot_L, e_bot_R
      real(wp) :: pa_h_intz_L, pa_h_intz_R, numer, denom
      real(wp) :: dM_coeff, ddM_dx, ddM_dy
      logical  :: wright_analytic
      integer  :: k_top_c, k_don_c

      inv_rho0 = 1.0_wp/rho0
      wright_analytic = (eos_variant == EOS_VARIANT_WRIGHT_97)

      ! ---- Pass C (per column): the layer-mean T/S every pass reads ----
      ! `hT/h` on a live layer and, on a vanished one, its I1′ DONOR's:
      ! the nearest live layer above it, or for a run of fillers reaching
      ! the top of the column the topmost live layer; 0 in a column with
      ! no live layer.  The per-column form of `rdb_vl_column_conc`, read
      ! off the donor so no near-zero thickness is ever a divisor.  MOM6
      ! carries T/S as concentrations, so its vanished layers hold the
      ! remapped value its `int_density_dz_*` reads; `c_live` is that value
      ! here.  NOT the floored `hT/max(h, H_VANISHED)` this replaced: that
      ! is `h/H_VANISHED` of the truth on a filler (2/3 at the default
      ! `zstar_h_min = 1e-4 m`), harmless in the vertical `pa` stack where
      ! it multiplies the filler's own thickness, but the cross-face Boole
      ! integral interpolates T/S over the INTERPOLATED -- live --
      ! thickness and the PLM/PPM stencil reads its neighbours, so at an
      ! OPEN z-like step (`zstar`, closed-faces-off `z_fixed`, a `z_fixed`
      ! cell whose liveness flipped with eta under a static closed-face
      ! mask) it integrated the wrong salinity over tens of metres of live
      ! water: 2.9e-3 m/s^2 at rest on a live|filler face against 1.4e-6
      ! (`test_ocean_pgf_insitu :: open_step_filler_faces_*`).  One O(nz)
      ! sweep per column, the shape of Pass 1a: a per-cell donor walk made
      ! `ocean_pgf` 4x slower on the global 1-degree grid (long bed-filler
      ! runs).  A live layer reads `hT/h` exactly as before (bit-identical
      ! on a column without fillers).
      do concurrent(j=1:ny, i=1:nx) local(k, k_top_c, k_don_c)
         k_top_c = 0
         do k = nz, 1, -1
            if (rdb_vl_is_live(h_layer(i, j, k))) then
               k_top_c = k
               exit
            end if
         end do
         if (k_top_c == 0) then
            do k = 1, nz
               conc_T(i, j, k) = 0.0_wp
               conc_S(i, j, k) = 0.0_wp
            end do
         else
            k_don_c = k_top_c
            do k = nz, 1, -1
               if (rdb_vl_is_live(h_layer(i, j, k))) k_don_c = k
               conc_T(i, j, k) = rdb_vl_conc(hT(i, j, k_don_c), h_layer(i, j, k_don_c))
               conc_S(i, j, k) = rdb_vl_conc(hS(i, j, k_don_c), h_layer(i, j, k_don_c))
            end do
         end if
      end do

      ! ---- Pass 1: per-column e_face, pa, intz_dpa (in-situ) ----
      ! Pass 1a (per column): interface heights and the surface seed of
      ! the pressure-anomaly stack.
      do concurrent(j=1:ny, i=1:nx) local(k)
         e_face(i, j, 1) = -b(i, j)
         do k = 1, nz
            e_face(i, j, k + 1) = e_face(i, j, k) + h_layer(i, j, k)
         end do
         if (p_top_in_bc) then
            pa(i, j, nz + 1) = rho_ref*GRAVITY*e_face(i, j, nz + 1) + p_top(i, j)
         else
            pa(i, j, nz + 1) = rho_ref*GRAVITY*e_face(i, j, nz + 1)
         end if
      end do
      ! Pass 1b (3-D parallel): every layer's `dpa` / `intz_dpa`.  `dpa`
      ! is parked in `pa(i, j, k)` and summed by Pass 1c.  Layer by layer
      ! the EOS work is independent; only the stack sum is a recurrence, so
      ! the expensive part no longer runs one thread per COLUMN (115k
      ! threads, latency-bound on the global 1-degree grid) but one per
      ! CELL.
      ! Two loop copies (not a branch inside one kernel) so the analytic
      ! Wright kernel carries none of the Boole path's register pressure.
      if (wright_analytic) then
         do concurrent(k=1:nz, j=1:ny, i=1:nx) local(dpa_kk, intz_kk, t_m, s_m)
            t_m = conc_T(i, j, k)
            s_m = conc_S(i, j, k)
            call wright_pcm_dpa_intz(t_m, s_m, e_face(i, j, k + 1), h_layer(i, j, k), &
                                     rho0, rho_ref, dpa_kk, intz_kk)
            pa(i, j, k) = dpa_kk
            intz_dpa(i, j, k) = intz_kk
         end do
      else
         do concurrent(k=1:nz, j=1:ny, i=1:nx) local(dpa_kk, intz_kk, t_m, s_m)
            t_m = conc_T(i, j, k)
            s_m = conc_S(i, j, k)
            call roquet_pcm_dpa_intz(t_m, s_m, e_face(i, j, k + 1), h_layer(i, j, k), &
                                     rho0, rho_ref, dpa_kk, intz_kk)
            pa(i, j, k) = dpa_kk
            intz_dpa(i, j, k) = intz_kk
         end do
      end if
      ! Pass 1c (per column): the pressure-anomaly stack, top down.
      do concurrent(j=1:ny, i=1:nx) local(k)
         do k = nz, 1, -1
            pa(i, j, k) = pa(i, j, k + 1) + pa(i, j, k)
         end do
      end do

      ! ---- Pass 2a: u-face horizontal integrals ----
      if (wright_analytic) then
         do concurrent(k=1:nz, j=1:ny, i=2:nx) &
            local(dpa_kk, t_m_L, t_m_R, s_m_L, s_m_R, dpa_L, dpa_R, hwt_ll, hwt_lr, hwt_rr, hwt_rl)
            call fv_mom6_mass_weights(mass_weight, e_face(i - 1, j, 1), e_face(i, j, 1), &
                                      e_face(i - 1, j, k + 1), e_face(i, j, k + 1), &
                                      h_layer(i - 1, j, k), h_layer(i, j, k), h_neglect, &
                                      hwt_ll, hwt_lr, hwt_rr, hwt_rl)
            t_m_L = conc_T(i - 1, j, k)
            t_m_R = conc_T(i, j, k)
            s_m_L = conc_S(i - 1, j, k)
            s_m_R = conc_S(i, j, k)
            dpa_L = pa(i - 1, j, k) - pa(i - 1, j, k + 1)
            dpa_R = pa(i, j, k) - pa(i, j, k + 1)
            call wright_pcm_dpa_face(e_face(i - 1, j, k + 1), e_face(i, j, k + 1), &
                                     h_layer(i - 1, j, k), h_layer(i, j, k), &
                                     t_m_L, t_m_R, s_m_L, s_m_R, dpa_L, dpa_R, &
                                     hwt_ll, hwt_lr, hwt_rr, hwt_rl, rho0, rho_ref, dpa_kk)
            intx_dpa(i, j, k) = dpa_kk
         end do
      else
         do concurrent(k=1:nz, j=1:ny, i=2:nx) &
            local(dpa_kk, t_m_L, t_m_R, s_m_L, s_m_R, dpa_L, dpa_R, hwt_ll, hwt_lr, hwt_rr, hwt_rl)
            call fv_mom6_mass_weights(mass_weight, e_face(i - 1, j, 1), e_face(i, j, 1), &
                                      e_face(i - 1, j, k + 1), e_face(i, j, k + 1), &
                                      h_layer(i - 1, j, k), h_layer(i, j, k), h_neglect, &
                                      hwt_ll, hwt_lr, hwt_rr, hwt_rl)
            t_m_L = conc_T(i - 1, j, k)
            t_m_R = conc_T(i, j, k)
            s_m_L = conc_S(i - 1, j, k)
            s_m_R = conc_S(i, j, k)
            dpa_L = pa(i - 1, j, k) - pa(i - 1, j, k + 1)
            dpa_R = pa(i, j, k) - pa(i, j, k + 1)
            call roquet_pcm_dpa_face(e_face(i - 1, j, k + 1), e_face(i, j, k + 1), &
                                     h_layer(i - 1, j, k), h_layer(i, j, k), &
                                     t_m_L, t_m_R, s_m_L, s_m_R, dpa_L, dpa_R, &
                                     hwt_ll, hwt_lr, hwt_rr, hwt_rl, rho0, rho_ref, dpa_kk)
            intx_dpa(i, j, k) = dpa_kk
         end do
      end if
      ! Column scan of the face integrals (cheap; the EOS work above is
      ! 3-D parallel).
      do concurrent(j=1:ny, i=2:nx) local(k)
         intx_pa(i, j, nz + 1) = 0.5_wp*(pa(i - 1, j, nz + 1) + pa(i, j, nz + 1))
         do k = nz, 1, -1
            intx_pa(i, j, k) = intx_pa(i, j, k + 1) + intx_dpa(i, j, k)
         end do
      end do
      do concurrent(k=1:nz, j=1:ny)
         intx_dpa(1, j, k) = 0.0_wp
         intx_dpa(nx + 1, j, k) = 0.0_wp
      end do
      do concurrent(k=1:nz + 1, j=1:ny)
         intx_pa(1, j, k) = 0.0_wp
         intx_pa(nx + 1, j, k) = 0.0_wp
      end do

      ! ---- Pass 2b: v-face horizontal integrals ----
      if (wright_analytic) then
         do concurrent(k=1:nz, j=2:ny, i=1:nx) &
            local(dpa_kk, t_m_L, t_m_R, s_m_L, s_m_R, dpa_L, dpa_R, hwt_ll, hwt_lr, hwt_rr, hwt_rl)
            call fv_mom6_mass_weights(mass_weight, e_face(i, j - 1, 1), e_face(i, j, 1), &
                                      e_face(i, j - 1, k + 1), e_face(i, j, k + 1), &
                                      h_layer(i, j - 1, k), h_layer(i, j, k), h_neglect, &
                                      hwt_ll, hwt_lr, hwt_rr, hwt_rl)
            t_m_L = conc_T(i, j - 1, k)
            t_m_R = conc_T(i, j, k)
            s_m_L = conc_S(i, j - 1, k)
            s_m_R = conc_S(i, j, k)
            dpa_L = pa(i, j - 1, k) - pa(i, j - 1, k + 1)
            dpa_R = pa(i, j, k) - pa(i, j, k + 1)
            call wright_pcm_dpa_face(e_face(i, j - 1, k + 1), e_face(i, j, k + 1), &
                                     h_layer(i, j - 1, k), h_layer(i, j, k), &
                                     t_m_L, t_m_R, s_m_L, s_m_R, dpa_L, dpa_R, &
                                     hwt_ll, hwt_lr, hwt_rr, hwt_rl, rho0, rho_ref, dpa_kk)
            inty_dpa(i, j, k) = dpa_kk
         end do
      else
         do concurrent(k=1:nz, j=2:ny, i=1:nx) &
            local(dpa_kk, t_m_L, t_m_R, s_m_L, s_m_R, dpa_L, dpa_R, hwt_ll, hwt_lr, hwt_rr, hwt_rl)
            call fv_mom6_mass_weights(mass_weight, e_face(i, j - 1, 1), e_face(i, j, 1), &
                                      e_face(i, j - 1, k + 1), e_face(i, j, k + 1), &
                                      h_layer(i, j - 1, k), h_layer(i, j, k), h_neglect, &
                                      hwt_ll, hwt_lr, hwt_rr, hwt_rl)
            t_m_L = conc_T(i, j - 1, k)
            t_m_R = conc_T(i, j, k)
            s_m_L = conc_S(i, j - 1, k)
            s_m_R = conc_S(i, j, k)
            dpa_L = pa(i, j - 1, k) - pa(i, j - 1, k + 1)
            dpa_R = pa(i, j, k) - pa(i, j, k + 1)
            call roquet_pcm_dpa_face(e_face(i, j - 1, k + 1), e_face(i, j, k + 1), &
                                     h_layer(i, j - 1, k), h_layer(i, j, k), &
                                     t_m_L, t_m_R, s_m_L, s_m_R, dpa_L, dpa_R, &
                                     hwt_ll, hwt_lr, hwt_rr, hwt_rl, rho0, rho_ref, dpa_kk)
            inty_dpa(i, j, k) = dpa_kk
         end do
      end if
      ! Column scan of the face integrals (cheap; the EOS work above is
      ! 3-D parallel).
      do concurrent(j=2:ny, i=1:nx) local(k)
         inty_pa(i, j, nz + 1) = 0.5_wp*(pa(i, j - 1, nz + 1) + pa(i, j, nz + 1))
         do k = nz, 1, -1
            inty_pa(i, j, k) = inty_pa(i, j, k + 1) + inty_dpa(i, j, k)
         end do
      end do
      do concurrent(k=1:nz, i=1:nx)
         inty_dpa(i, 1, k) = 0.0_wp
         inty_dpa(i, ny + 1, k) = 0.0_wp
      end do
      do concurrent(k=1:nz + 1, i=1:nx)
         inty_pa(i, 1, k) = 0.0_wp
         inty_pa(i, ny + 1, k) = 0.0_wp
      end do

      ! ---- Pass 3: PFu assembly (identical to compute_fv_mom6_impl) ----
      do concurrent(k=1:nz, j=1:ny, i=2:nx) &
         local(h_L, h_R, e_bot_L, e_bot_R, pa_h_intz_L, pa_h_intz_R, numer, denom)
         h_L = h_layer(i - 1, j, k)
         h_R = h_layer(i, j, k)
         e_bot_L = e_face(i - 1, j, k)
         e_bot_R = e_face(i, j, k)
         pa_h_intz_L = pa(i - 1, j, k + 1)*h_L + intz_dpa(i - 1, j, k)
         pa_h_intz_R = pa(i, j, k + 1)*h_R + intz_dpa(i, j, k)
         numer = (pa_h_intz_L - pa_h_intz_R) &
                 + (h_R - h_L)*intx_pa(i, j, k + 1) &
                 - (e_bot_R - e_bot_L)*intx_dpa(i, j, k)
         denom = h_L + h_R + h_neglect
         dpdx_face(i, j, k) = numer*(2.0_wp*inv_rho0*idxCu(i, j))/denom
      end do
      do concurrent(k=1:nz, j=1:ny)
         dpdx_face(1, j, k) = 0.0_wp
         dpdx_face(nx + 1, j, k) = 0.0_wp
      end do

      ! ---- Pass 4: PFv assembly ----
      do concurrent(k=1:nz, j=2:ny, i=1:nx) &
         local(h_L, h_R, e_bot_L, e_bot_R, pa_h_intz_L, pa_h_intz_R, numer, denom)
         h_L = h_layer(i, j - 1, k)
         h_R = h_layer(i, j, k)
         e_bot_L = e_face(i, j - 1, k)
         e_bot_R = e_face(i, j, k)
         pa_h_intz_L = pa(i, j - 1, k + 1)*h_L + intz_dpa(i, j - 1, k)
         pa_h_intz_R = pa(i, j, k + 1)*h_R + intz_dpa(i, j, k)
         numer = (pa_h_intz_L - pa_h_intz_R) &
                 + (h_R - h_L)*inty_pa(i, j, k + 1) &
                 - (e_bot_R - e_bot_L)*inty_dpa(i, j, k)
         denom = h_L + h_R + h_neglect
         dpdy_face(i, j, k) = numer*(2.0_wp*inv_rho0*idyCv(i, j))/denom
      end do
      do concurrent(k=1:nz, i=1:nx)
         dpdy_face(i, 1, k) = 0.0_wp
         dpdy_face(i, ny + 1, k) = 0.0_wp
      end do

      ! ---- Pass 5: Montgomery dM correction (MOM6 GFS_scale) ----
      ! Same depth-independent form as the reconstruction branch, with the
      ! surface layer's mean in-situ density recovered from its `dpa`.
      if (gfs_scale < 1.0_wp - 1.0e-12_wp) then
         dM_coeff = (gfs_scale - 1.0_wp)*GRAVITY*inv_rho0
         do concurrent(k=1:nz, j=1:ny, i=2:nx) local(ddM_dx)
            ddM_dx = dM_coeff*(recon_rho_surf(pa(i, j, nz), pa(i, j, nz + 1), &
                                              h_layer(i, j, nz), rho_ref) &
                               *e_face(i, j, nz + 1) &
                               - recon_rho_surf(pa(i - 1, j, nz), pa(i - 1, j, nz + 1), &
                                                h_layer(i - 1, j, nz), rho_ref) &
                               *e_face(i - 1, j, nz + 1))*idxCu(i, j)
            dpdx_face(i, j, k) = dpdx_face(i, j, k) - ddM_dx
         end do
         do concurrent(k=1:nz, j=2:ny, i=1:nx) local(ddM_dy)
            ddM_dy = dM_coeff*(recon_rho_surf(pa(i, j, nz), pa(i, j, nz + 1), &
                                              h_layer(i, j, nz), rho_ref) &
                               *e_face(i, j, nz + 1) &
                               - recon_rho_surf(pa(i, j - 1, nz), pa(i, j - 1, nz + 1), &
                                                h_layer(i, j - 1, nz), rho_ref) &
                               *e_face(i, j - 1, nz + 1))*idyCv(i, j)
            dpdy_face(i, j, k) = dpdy_face(i, j, k) - ddM_dy
         end do
      end if
   end subroutine compute_fv_mom6_insitu_pcm_impl