ocean_vcoord_rho_target_column Subroutine

private pure subroutine ocean_vcoord_rho_target_column(i, j, nx, ny, nz, target_h, remap_h_old, total_h, eta, t_conc, s_conc, dsig, floor_dz, rho_target, eos, p_ref, h_min, h_floor_eff, hybrid, floor_mode, h_nominal)

One column of the RHO / HYCOM regrid (steps 0-5 of ocean_vcoord_compute_target_h_rho_impl) — the per-thread body of ocean_vcoord_rho_target. Same module as its caller (the project rule for !$acc routine seq callees of a do concurrent).

Arguments

Type IntentOptional Attributes Name
integer, intent(in), value :: i

Column i-index.

integer, intent(in), value :: j

Column j-index.

integer, intent(in), value :: nx

i-extent of every horizontal array (total, incl. halos).

integer, intent(in), value :: ny

j-extent of every horizontal array (total, incl. halos).

integer, intent(in), value :: nz

Number of layers; k = 1 is the bed, k = nz the surface.

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

Target layer thickness (m), bottom-up.

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

Pre-remap layer thickness snapshot (m), bottom-up.

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

Column reference depth H (m).

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

Free-surface anomaly η (m).

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

Layer-mean potential temperature (°C), bottom-up.

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

Layer-mean salinity (PSU), bottom-up.

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

Nominal layer fractions, bottom-up (dsig(nz) = surface) — the HYCOM floor only under HYCOM_FLOOR_SIGMA.

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

HYCOM z* nominal layer thicknesses (m), bottom-up (floor_dz(nz) = surface) — read only under HYCOM_FLOOR_PROFILE.

real(kind=wp), intent(in) :: rho_target(0:nz)

Target potential densities (kg/m³), 0 = lightest = surface.

type(eos_t), intent(in) :: eos

Shared EOS handle (flat POD).

real(kind=wp), intent(in), value :: p_ref

Coordinate reference pressure (Pa) — rho_ref_pressure.

real(kind=wp), intent(in), value :: h_min

Pre-compaction strip threshold (m) — zstar_h_min.

real(kind=wp), intent(in), value :: h_floor_eff

Min-thickness inflation floor (m), > H_VANISHED.

logical, intent(in), value :: hybrid

.true. = HYCOM deltas; .false. = pure RHO.

integer, intent(in), value :: floor_mode

HYCOM z* floor source: HYCOM_FLOOR_PROFILE (floor_dz), HYCOM_FLOOR_UNIFORM (h_nominal) or HYCOM_FLOOR_SIGMA (dsig·H, the unconfigured-slot fallback).

real(kind=wp), intent(in), value :: h_nominal

Uniform z* nominal thickness (m) for HYCOM_FLOOR_UNIFORM.


Calls

proc~~ocean_vcoord_rho_target_column~~CallsGraph proc~ocean_vcoord_rho_target_column ocean_vcoord_rho_target_column proc~eos_density_point eos_density_point proc~ocean_vcoord_rho_target_column->proc~eos_density_point proc~invert_density_targets invert_density_targets proc~ocean_vcoord_rho_target_column->proc~invert_density_targets proc~roquet_spv_value roquet_spv_value proc~eos_density_point->proc~roquet_spv_value rdb_roq_spv_p rdb_roq_spv_p proc~roquet_spv_value->rdb_roq_spv_p rdb_roq_ts_coeffs rdb_roq_ts_coeffs proc~roquet_spv_value->rdb_roq_ts_coeffs

Called by

proc~~ocean_vcoord_rho_target_column~~CalledByGraph proc~ocean_vcoord_rho_target_column ocean_vcoord_rho_target_column proc~ocean_vcoord_rho_target ocean_vcoord_rho_target proc~ocean_vcoord_rho_target->proc~ocean_vcoord_rho_target_column proc~ocean_vcoord_compute_target_h_rho_impl ocean_vcoord_compute_target_h_rho_impl proc~ocean_vcoord_compute_target_h_rho_impl->proc~ocean_vcoord_rho_target proc~ocean_vcoord_compute_target_h_rho ocean_vcoord_t%ocean_vcoord_compute_target_h_rho proc~ocean_vcoord_compute_target_h_rho->proc~ocean_vcoord_compute_target_h_rho_impl proc~ocean_apply_ale_remap_centres ocean_apply_ale_remap_centres proc~ocean_apply_ale_remap_centres->proc~ocean_vcoord_compute_target_h_rho proc~ocean_apply_ale_remap_step ocean_apply_ale_remap_step proc~ocean_apply_ale_remap_step->proc~ocean_vcoord_compute_target_h_rho proc~ocean_dyn_step_split ocean_dyn_step_split proc~ocean_dyn_step_split->proc~ocean_apply_ale_remap_step

Variables

Type Visibility Attributes Name Initial
real(kind=wp), private :: col_extent
real(kind=wp), private :: donate
real(kind=wp), private :: excess
real(kind=wp), private :: frac
real(kind=wp), private :: h_col(NZ_STACK_MAX)
real(kind=wp), private :: h_new(NZ_STACK_MAX)
real(kind=wp), private :: h_ref_col
real(kind=wp), private :: hc(NZ_STACK_MAX)
integer, private :: idx_thick
integer, private :: ii
integer, private :: k
integer, private :: kk
integer, private :: mapping(NZ_STACK_MAX)
integer, private :: nk
real(kind=wp), private :: nominal_z
integer, private :: ns
real(kind=wp), private :: rhoc(NZ_STACK_MAX)
real(kind=wp), private :: rtgt(NZ_STACK_MAX)
real(kind=wp), private :: s_col(NZ_STACK_MAX)
integer, private :: src
real(kind=wp), private :: stretching
real(kind=wp), private :: t_col(NZ_STACK_MAX)
real(kind=wp), private :: thick_max
real(kind=wp), private :: total_need
real(kind=wp), private :: z_new(NZ_STACK_MAX+1)

Source Code

   pure subroutine ocean_vcoord_rho_target_column(i, j, nx, ny, nz, target_h, remap_h_old, &
                                                  total_h, eta, t_conc, s_conc, dsig, &
                                                  floor_dz, rho_target, eos, p_ref, h_min, &
                                                  h_floor_eff, hybrid, floor_mode, h_nominal)
      !! One column of the RHO / HYCOM regrid (steps 0-5 of
      !! `ocean_vcoord_compute_target_h_rho_impl`) — the per-thread body of
      !! `ocean_vcoord_rho_target`.  Same module as its caller (the
      !! project rule for `!$acc routine seq` callees of a `do concurrent`).
      !$acc routine seq
      integer, intent(in), value :: i
         !! Column i-index.
      integer, intent(in), value :: j
         !! Column j-index.
      integer, intent(in), value :: nx
         !! i-extent of every horizontal array (total, incl. halos).
      integer, intent(in), value :: ny
         !! j-extent of every horizontal array (total, incl. halos).
      integer, intent(in), value :: nz
         !! Number of layers; `k = 1` is the bed, `k = nz` the surface.
      real(wp), intent(inout) :: target_h(nx, ny, nz)
         !! Target layer thickness (m), bottom-up.
      real(wp), intent(in) :: remap_h_old(nx, ny, nz)
         !! Pre-remap layer thickness snapshot (m), bottom-up.
      real(wp), intent(in) :: total_h(nx, ny)
         !! Column reference depth H (m).
      real(wp), intent(in) :: eta(nx, ny)
         !! Free-surface anomaly η (m).
      real(wp), intent(in) :: t_conc(nx, ny, nz)
         !! Layer-mean potential temperature (°C), bottom-up.
      real(wp), intent(in) :: s_conc(nx, ny, nz)
         !! Layer-mean salinity (PSU), bottom-up.
      real(wp), intent(in) :: dsig(nz)
         !! Nominal layer fractions, bottom-up (`dsig(nz)` = surface) —
         !! the HYCOM floor only under `HYCOM_FLOOR_SIGMA`.
      real(wp), intent(in) :: floor_dz(nz)
         !! HYCOM z* nominal layer thicknesses (m), bottom-up
         !! (`floor_dz(nz)` = surface) — read only under `HYCOM_FLOOR_PROFILE`.
      real(wp), intent(in) :: rho_target(0:nz)
         !! Target potential densities (kg/m³), `0` = lightest = surface.
      type(eos_t), intent(in) :: eos
         !! Shared EOS handle (flat POD).
      real(wp), intent(in), value :: p_ref
         !! Coordinate reference pressure (Pa) — `rho_ref_pressure`.
      real(wp), intent(in), value :: h_min
         !! Pre-compaction strip threshold (m) — `zstar_h_min`.
      real(wp), intent(in), value :: h_floor_eff
         !! Min-thickness inflation floor (m), `> H_VANISHED`.
      logical, intent(in), value :: hybrid
         !! `.true.` = HYCOM deltas; `.false.` = pure RHO.
      integer, intent(in), value :: floor_mode
         !! HYCOM z* floor source: `HYCOM_FLOOR_PROFILE` (`floor_dz`),
         !! `HYCOM_FLOOR_UNIFORM` (`h_nominal`) or `HYCOM_FLOOR_SIGMA`
         !! (`dsig·H`, the unconfigured-slot fallback).
      real(wp), intent(in), value :: h_nominal
         !! Uniform z* nominal thickness (m) for `HYCOM_FLOOR_UNIFORM`.

      integer :: k, kk, nk, ns, idx_thick, src, ii
      integer :: mapping(NZ_STACK_MAX)
      real(wp) :: h_col(NZ_STACK_MAX), t_col(NZ_STACK_MAX), s_col(NZ_STACK_MAX)
      real(wp) :: hc(NZ_STACK_MAX), rhoc(NZ_STACK_MAX), rtgt(NZ_STACK_MAX)
      real(wp) :: z_new(NZ_STACK_MAX + 1), h_new(NZ_STACK_MAX)
      real(wp) :: col_extent, donate
      real(wp) :: total_need, thick_max, excess, frac
      real(wp) :: nominal_z, stretching, h_ref_col

      ! One column per (j,i).  NZ_STACK_MAX fixed-size locals; no name
      ! shadows a Fortran intrinsic; cross-module pure EOS helper carries
      ! its own `!$acc routine seq`.

      ! --- gather TOP-DOWN: working index 1 = surface = state k=nz ---
      ! Source thicknesses come from the remap snapshot the
      ! orchestrator placed in `remap_h_old` (the live, pre-remap
      ! `h_layer`); T/S are the layer-mean concentrations.  Flip the
      ! bottom-up state index (k=nz surface) into the top-down work
      ! frame (work index 1 = surface).
      col_extent = max(total_h(i, j) + eta(i, j), 0.0_wp)
      do k = 1, nz
         ii = nz - k + 1                 ! state (bottom-up) index
         h_col(k) = remap_h_old(i, j, ii)
         t_col(k) = t_conc(i, j, ii)
         s_col(k) = s_conc(i, j, ii)
      end do

      ! --- too-thin column: cannot carry the coordinate, keep h_old ---
      ! A column thinner than `nz*h_floor_eff` (land / dry columns hold
      ! `nz*H_VANISHED`; a wet/dry or ice-cavity sliver can be any size)
      ! cannot have every layer at the inflation floor.  Step 5 then either
      ! minted mass (`ns == 0`: every layer to the floor) or debited the one
      ! surviving layer below zero (`ns > 0`: a 50 x 1.5e-4 m land column
      ! collapsed into one layer gave 0.0075 - 49*3e-4 = -0.0072 m).  Leave
      ! such a column exactly where it is — the same no-motion answer as the
      ! `nk <= 1` fast path below — so the remap is the identity on it: no
      ! negative thickness, no created mass, `sum(h)` preserved bit-for-bit.
      ! Every column the guard catches was mis-handled by step 5, so it is
      ! inert on every column that step handled correctly.
      if (col_extent < real(nz, wp)*h_floor_eff) then
         do k = 1, nz
            target_h(i, j, k) = remap_h_old(i, j, k)
         end do
         return
      end if

      ! --- step 0: pre-compaction (strip h <= h_min, donate) ---
      nk = 0
      do k = 1, nz
         if (h_col(k) > h_min) then
            nk = nk + 1
            mapping(nk) = k
            hc(nk) = h_col(k)
         end if
      end do
      if (nk <= 1) then
         ! Fast path: <= 1 finite layer.  nz == nk_state here, so
         ! keep the source thicknesses unchanged (h_new = h_old),
         ! flipped back into the bottom-up state.
         do k = 1, nz
            target_h(i, j, k) = remap_h_old(i, j, k)
         end do
         return
      end if

      ! Donate the stripped volume to the thickest survivor.
      donate = col_extent
      do kk = 1, nk
         donate = donate - hc(kk)
      end do
      if (donate > 0.0_wp) then
         idx_thick = 1
         do kk = 2, nk
            if (hc(kk) > hc(idx_thick)) idx_thick = kk
         end do
         hc(idx_thick) = hc(idx_thick) + donate
      end if

      ! --- step 1: layer potential densities on the compacted column ---
      do kk = 1, nk
         src = mapping(kk)
         rhoc(kk) = eos_density_point(eos, t_col(src), s_col(src), p_ref)
      end do

      ! --- step 1b (HYCOM only): bottom-up density monotonize ---
      ! Work frame is top-down (index 1 = surface, nk = bed): cap each
      ! cell by the one below it sweeping bed-up so density is
      ! non-decreasing downward.  Pure RHO (hybrid=.false.) skips this
      ! and stays bit-identical to the merged RHO regrid.
      if (hybrid) then
         do kk = nk - 1, 1, -1
            rhoc(kk) = min(rhoc(kk), rhoc(kk + 1))
         end do
      end if

      ! --- steps 2-4: PPM reconstruct + invert each interior target
      !     density to an interface depth + monotone interfaces.  Shared
      !     density-space inversion (also used by the DENSITY diagnostic
      !     remap, `rdb_ocean_diag_fills`) — single source of truth for
      !     the bracket + fixed-iter-Newton solve.  Copy the interior
      !     targets into a stack array so the device call passes a whole
      !     fixed-size local (no derived-type section descriptor in the
      !     hot per-column kernel).  For HYCOM the rhoc fed in was
      !     monotonized above; the z* floor below then lifts the result.
      do kk = 1, nz - 1
         rtgt(kk) = rho_target(kk)
      end do
      call invert_density_targets(nk, hc, rhoc, nz - 1, rtgt, z_new)

      ! --- step 4b (HYCOM only): z* nominal-floor sweep ---
      ! Surface-side minimum-depth floor on the isopycnal interfaces
      ! (MOM6 `build_hycom1_column`).  Walk interfaces from the surface
      ! down, accumulating the nominal z* depth and pushing any
      ! too-shallow interface DOWN to it (clamped to the column bottom);
      ! deep interfaces already below the floor are untouched.
      ! stretching = col_extent/total_h = (H+η)/H, the z* stretch.  The
      ! nominal increment is a THICKNESS IN METRES — the z* coordinate
      ! resolution (`floor_dz`, or uniform `h_nominal`) — so the band is
      ! the same depth range in every column and a shallow column clamps
      ! its deeper interfaces onto the bed, exactly like a z* grid.  Only
      ! the `HYCOM_FLOOR_SIGMA` fallback accumulates the column FRACTION
      ! `dsig·H` (a sigma floor: k/nz of every column's depth).  Both
      ! tables are bottom-up (index nz = surface layer); the work layer
      ! above interface kk maps to bottom-up index nz-kk+2.  The floor can
      ! break monotonicity, so re-monotonize after it.  Pure RHO
      ! (hybrid=.false.) skips this and is bit-identical.
      if (hybrid) then
         h_ref_col = total_h(i, j)
         if (h_ref_col > 0.0_wp) then
            stretching = col_extent/h_ref_col
         else
            stretching = 1.0_wp
         end if
         nominal_z = 0.0_wp
         do kk = 2, nz + 1
            select case (floor_mode)
            case (HYCOM_FLOOR_PROFILE)
               nominal_z = nominal_z + floor_dz(nz - kk + 2)*stretching
            case (HYCOM_FLOOR_UNIFORM)
               nominal_z = nominal_z + h_nominal*stretching
            case default
               nominal_z = nominal_z + dsig(nz - kk + 2)*h_ref_col*stretching
            end select
            if (z_new(kk) < nominal_z) z_new(kk) = nominal_z
            if (z_new(kk) > col_extent) z_new(kk) = col_extent
         end do
         do kk = 2, nz + 1
            if (z_new(kk) < z_new(kk - 1)) z_new(kk) = z_new(kk - 1)
         end do
      end if
      do kk = 1, nz
         h_new(kk) = z_new(kk + 1) - z_new(kk)
      end do

      ! --- step 5: MOM6 min-thickness inflation (floor h_floor_eff) ---
      ns = 0
      do kk = 1, nz
         if (h_new(kk) > h_floor_eff) ns = ns + 1
      end do
      if (ns == nz) then
         ! all OK
      else if (ns == 0) then
         do kk = 1, nz
            h_new(kk) = h_floor_eff
         end do
      else
         total_need = 0.0_wp
         do kk = 1, nz
            if (h_new(kk) <= h_floor_eff) then
               total_need = total_need + (h_floor_eff - h_new(kk))
               h_new(kk) = h_floor_eff
            end if
         end do
         ! debit the single thickest layer once
         idx_thick = 1
         thick_max = h_new(1)
         do kk = 2, nz
            if (h_new(kk) > thick_max) then
               thick_max = h_new(kk)
               idx_thick = kk
            end if
         end do
         if (thick_max - total_need >= h_floor_eff) then
            h_new(idx_thick) = h_new(idx_thick) - total_need
         else
            ! The thickest layer alone cannot pay (several comparably thin
            ! survivors): debit EVERY above-floor layer in proportion to its
            ! excess over the floor.  `col_extent >= nz*h_floor_eff` (guard
            ! above) makes the total excess >= total_need, so every layer
            ! stays >= h_floor_eff and the sum is unchanged to round-off.
            ! The single-layer debit used to drive the thickest one below
            ! the floor, or negative.
            excess = 0.0_wp
            do kk = 1, nz
               if (h_new(kk) > h_floor_eff) excess = excess + (h_new(kk) - h_floor_eff)
            end do
            if (excess > 0.0_wp) then
               frac = total_need/excess
               do kk = 1, nz
                  if (h_new(kk) > h_floor_eff) then
                     h_new(kk) = h_new(kk) - frac*(h_new(kk) - h_floor_eff)
                  end if
               end do
            end if
         end if
      end if

      ! --- assignment: FLIP top-down working -> bottom-up state ---
      do k = 1, nz
         target_h(i, j, k) = h_new(nz - k + 1)
      end do
   end subroutine ocean_vcoord_rho_target_column