drain_avail_limit Subroutine

private pure subroutine drain_avail_limit(nx, ny, nz, areaT, h_min, h_end, uhtr, vhtr, scratch)

Conservative upfront availability limiter on the accumulated window transports uhtr/vhtr, applied BEFORE drain_reconstruct_hprev.

Guarantees, for every cell, that the reconstructed window-start volume stays positive (≥ areaT·h_min):

vol(i,j,k) = areaT·h_end + (uhtr(i+1)-uhtr(i)) + (vhtr(j+1)-vhtr(j)) ≥ areaT·h_min

so the non-conservative max(0,·) clamp + vanishing-layer hatch in drain_reconstruct_hprev never fire. Without this, the combined Fox-Kemper (2008,2011) overturning + resolved/barotropic transport can overdraw a thin z* surface layer (vol < 0), the clamp destroys volume and the hatch re-inflates hprev WITHOUT matching tracer mass → ~5%/day tracer leak. MOM6 avoids this by sizing its availability cap against the combined transport; this is the windowed-drain analogue (general, protects against any overdraw source).

Mechanism (Zalesak/FCT-style inflow scaling, GPU-uniform fixed budget): the cell deficit is covered by shrinking the cell’s INFLOW-face transports. Reducing an inflow magnitude raises the receiving cell’s vol AND the donor neighbour’s vol (it is that neighbour’s outflow), so the iteration is monotone in every cell’s vol and converges. Scaling is applied multiplicatively to the transport, so the limited uhtr/vhtr are used CONSISTENTLY for both the reconstruction and the sub-cycle ⇒ the drain still telescopes exactly onto h_end and Σ(areaT·hTr) is conserved to round-off.

Inert when every vol > areaT·h_min already (scale = 1 everywhere), so ratio=1 / FK-off / non-overdrawing windows are bit-identical.

ONE FCT pass — the caller loops it AVAIL_MAX_PASS times with a seam re-wrap of scratch and the faces between passes so the constraint diffuses consistently across periodic/fold ghosts.

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nx
integer, intent(in) :: ny
integer, intent(in) :: nz
real(kind=wp), intent(in) :: areaT(nx,ny)
real(kind=wp), intent(in) :: h_min
real(kind=wp), intent(in) :: h_end(nx,ny,nz)
real(kind=wp), intent(inout) :: uhtr(nx+1,ny,nz)
real(kind=wp), intent(inout) :: vhtr(nx,ny+1,nz)
real(kind=wp), intent(inout) :: scratch(nx,ny,nz)

Per-cell inflow scale factor in [0,1] (centre-shaped slot).


Calls

proc~~drain_avail_limit~~CallsGraph proc~drain_avail_limit drain_avail_limit local local proc~drain_avail_limit->local

Called by

proc~~drain_avail_limit~~CalledByGraph proc~drain_avail_limit drain_avail_limit proc~continuity_tracer_drain continuity_tracer_drain proc~continuity_tracer_drain->proc~drain_avail_limit proc~ocean_dyn_flush_tracer_window ocean_dyn_flush_tracer_window proc~ocean_dyn_flush_tracer_window->proc~continuity_tracer_drain proc~ocean_dyn_step ocean_dyn_step proc~ocean_dyn_step->proc~continuity_tracer_drain proc~ocean_dyn_step_split ocean_dyn_step_split proc~ocean_dyn_step_split->proc~continuity_tracer_drain proc~driver_run_ocean driver_run_ocean proc~driver_run_ocean->proc~ocean_dyn_flush_tracer_window proc~engine_step engine_step proc~driver_run_ocean->proc~engine_step proc~engine_step->proc~ocean_dyn_step proc~engine_step->proc~ocean_dyn_step_split proc~rdb_ocean_set_tracer rdb_ocean_set_tracer proc~rdb_ocean_set_tracer->proc~ocean_dyn_flush_tracer_window proc~driver_run driver_run proc~driver_run->proc~driver_run_ocean proc~rdb_ocean_step rdb_ocean_step proc~rdb_ocean_step->proc~engine_step

Variables

Type Visibility Attributes Name Initial
real(kind=wp), private :: deficit
integer, private :: i
real(kind=wp), private :: inflow
integer, private :: j
integer, private :: k
real(kind=wp), private :: sfac
real(kind=wp), private :: vmin
real(kind=wp), private :: vol

Source Code

   pure subroutine drain_avail_limit(nx, ny, nz, areaT, h_min, h_end, &
                                     uhtr, vhtr, scratch)
      !! Conservative upfront availability limiter on the accumulated window
      !! transports `uhtr/vhtr`, applied BEFORE drain_reconstruct_hprev.
      !!
      !! Guarantees, for every cell, that the reconstructed window-start
      !! volume stays positive (≥ areaT·h_min):
      !!
      !!   vol(i,j,k) = areaT·h_end + (uhtr(i+1)-uhtr(i)) + (vhtr(j+1)-vhtr(j))
      !!              ≥ areaT·h_min
      !!
      !! so the non-conservative max(0,·) clamp + vanishing-layer hatch in
      !! drain_reconstruct_hprev never fire.  Without this, the combined
      !! Fox-Kemper (2008,2011) overturning + resolved/barotropic transport
      !! can overdraw a thin z* surface layer (vol < 0), the clamp destroys
      !! volume and the hatch re-inflates hprev WITHOUT matching tracer mass
      !! → ~5%/day tracer leak.  MOM6 avoids this by sizing its availability
      !! cap against the combined transport; this is the windowed-drain
      !! analogue (general, protects against any overdraw source).
      !!
      !! Mechanism (Zalesak/FCT-style inflow scaling, GPU-uniform fixed
      !! budget): the cell deficit is covered by shrinking the cell's
      !! INFLOW-face transports.  Reducing an inflow magnitude raises the
      !! receiving cell's vol AND the donor neighbour's vol (it is that
      !! neighbour's outflow), so the iteration is monotone in every cell's
      !! vol and converges.  Scaling is applied multiplicatively to the
      !! transport, so the limited uhtr/vhtr are used CONSISTENTLY for both
      !! the reconstruction and the sub-cycle ⇒ the drain still telescopes
      !! exactly onto h_end and Σ(areaT·hTr) is conserved to round-off.
      !!
      !! Inert when every vol > areaT·h_min already (scale = 1 everywhere),
      !! so ratio=1 / FK-off / non-overdrawing windows are bit-identical.
      !!
      !! ONE FCT pass — the caller loops it `AVAIL_MAX_PASS` times with a
      !! seam re-wrap of `scratch` and the faces between passes so the
      !! constraint diffuses consistently across periodic/fold ghosts.
      integer, intent(in) :: nx, ny, nz
      real(wp), intent(in) :: areaT(nx, ny)
      real(wp), intent(in) :: h_min
      real(wp), intent(in) :: h_end(nx, ny, nz)
      real(wp), intent(inout) :: uhtr(nx + 1, ny, nz)
      real(wp), intent(inout) :: vhtr(nx, ny + 1, nz)
      real(wp), intent(inout) :: scratch(nx, ny, nz)
         !! Per-cell inflow scale factor in [0,1] (centre-shaped slot).
      integer :: i, j, k
      real(wp) :: vol, vmin, inflow, deficit, sfac
      ! ---- Per-cell inflow scale factor ----
      do concurrent(k=1:nz, j=1:ny, i=1:nx) &
         local(vol, vmin, inflow, deficit)
         vmin = areaT(i, j)*h_min
         vol = areaT(i, j)*h_end(i, j, k) &
               + (uhtr(i + 1, j, k) - uhtr(i, j, k)) &
               + (vhtr(i, j + 1, k) - vhtr(i, j, k))
         ! Inflow into this cell across its four faces (volume, ≥ 0):
         !   west  face uhtr(i)   inflow if > 0
         !   east  face uhtr(i+1) inflow if < 0
         !   south face vhtr(j)   inflow if > 0
         !   north face vhtr(j+1) inflow if < 0
         inflow = max(0.0_wp, uhtr(i, j, k)) &
                  + max(0.0_wp, -uhtr(i + 1, j, k)) &
                  + max(0.0_wp, vhtr(i, j, k)) &
                  + max(0.0_wp, -vhtr(i, j + 1, k))
         if (vol < vmin .and. inflow > 0.0_wp) then
            deficit = vmin - vol
            scratch(i, j, k) = max(0.0_wp, (inflow - deficit)/inflow)
         else
            scratch(i, j, k) = 1.0_wp
         end if
      end do
   end subroutine drain_avail_limit