multilayer_enforce_vanished_content Subroutine

private subroutine multilayer_enforce_vanished_content(this, nx, ny)

THE enforcement point for invariant I1′.

I1′:  `h_layer <= H_VANISHED  ⇒  hTr = h_layer·c_live`, every
      registered tracer, `c_live` = the concentration of the
      filler's donor live layer (the nearest live layer above;
      the topmost live layer for the fillers above it).

Walks the tracer registry and, per column, pools every filler with its donor live layer and mixes the pool to one concentration (rdb_vl_merge_content, the ONE definition of the rule — see src/shared_module_utilities/rdb_vanished_layer.inc).

Nothing is recorded in any budget and nothing should be: the pool lies WITHIN a column, so the column integral — which is what every budget contributor measures — is unchanged to round-off. A budget entry here would be a contributor that always sums to zero, i.e. noise in the one instrument that detects real leaks.

Why a state-owned sweep and not a rule each kernel remembers. The tree has ~40 sites that write tracer content and only the ALE remap checked h on the way in; the surface-flux, melt, sponge, hdiff, vdiff, vertical-advection and OBC writers all deposit into whatever layer they were handed. “Correct by construction, per author, forever” is exactly the property that failed and produced the day-16 z_fixed budget break. One sweep at a defined point of the step makes the invariant structural.

Why h·c_live and not zero. Every transport kernel reads a filler’s concentration as hTr/h, and the continuity step moves thickness out of (and into) fillers between two remaps. A filler holding ZERO content hands that thickness to the live layers as fresh, 0 °C water, so a uniform tracer does not stay uniform — the previous rule (I1, hTr = 0) left 258 thin live cells at S = 0–34.43 after 10 days of a uniform-S = 35 double_gyre_mom6 run. A filler holding its donor’s concentration preserves tracer constancy.

Placement: once per outer step, after every tracer update and after the ALE remap — ocean_dyn_step_split_multilayer’s tail. The remap already establishes I1′ internally; this catches everything that writes AFTER it, and the families with no vanishing layers at all (sigma, z*-lite, the whole double-gyre / Eady / channel set) see a textual no-op: rdb_vl_merge_content mutates nothing on a column with no sub-threshold layer.

Outer-shim + flat-impl: the registry is an array of derived types, so the per-tracer array is pulled out on the host and handed to the flat _impl as an explicit-shape dummy (CLAUDE.md, “Outer-shim + flat-impl call sites”).

Type Bound

multilayer_state_t

Arguments

Type IntentOptional Attributes Name
class(multilayer_state_t), intent(inout) :: this
integer, intent(in) :: nx

i-extent of h_layer / hTr (total, incl. halos).

integer, intent(in) :: ny

j-extent (total, incl. halos).


Calls

proc~~multilayer_enforce_vanished_content~~CallsGraph proc~multilayer_enforce_vanished_content multilayer_state_t%multilayer_enforce_vanished_content proc~enforce_vanished_one_impl enforce_vanished_one_impl proc~multilayer_enforce_vanished_content->proc~enforce_vanished_one_impl local local proc~enforce_vanished_one_impl->local rdb_vl_merge_content rdb_vl_merge_content proc~enforce_vanished_one_impl->rdb_vl_merge_content

Called by

proc~~multilayer_enforce_vanished_content~~CalledByGraph proc~multilayer_enforce_vanished_content multilayer_state_t%multilayer_enforce_vanished_content proc~ocean_dyn_step_split ocean_dyn_step_split proc~ocean_dyn_step_split->proc~multilayer_enforce_vanished_content proc~engine_step engine_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 proc~driver_run driver_run proc~driver_run->proc~driver_run_ocean

Variables

Type Visibility Attributes Name Initial
integer, private :: t

Source Code

   subroutine multilayer_enforce_vanished_content(this, nx, ny)
      !! **THE enforcement point for invariant I1′.**
      !!
      !!     I1′:  `h_layer <= H_VANISHED  ⇒  hTr = h_layer·c_live`, every
      !!           registered tracer, `c_live` = the concentration of the
      !!           filler's donor live layer (the nearest live layer above;
      !!           the topmost live layer for the fillers above it).
      !!
      !! Walks the tracer registry and, per column, pools every filler with
      !! its donor live layer and mixes the pool to one concentration
      !! (`rdb_vl_merge_content`, the ONE definition of the rule — see
      !! `src/shared_module_utilities/rdb_vanished_layer.inc`).
      !!
      !! **Nothing is recorded in any budget** and nothing should be: the pool
      !! lies WITHIN a column, so the column integral — which is what every
      !! budget contributor measures — is unchanged to round-off.  A budget
      !! entry here would be a contributor that always sums to zero, i.e.
      !! noise in the one instrument that detects real leaks.
      !!
      !! **Why a state-owned sweep and not a rule each kernel remembers.**  The
      !! tree has ~40 sites that write tracer content and only the ALE remap
      !! checked `h` on the way in; the surface-flux, melt, sponge, hdiff,
      !! vdiff, vertical-advection and OBC writers all deposit into whatever
      !! layer they were handed.  "Correct by construction, per author,
      !! forever" is exactly the property that failed and produced the day-16
      !! z_fixed budget break.  One sweep at a defined point of the step makes
      !! the invariant structural.
      !!
      !! **Why `h·c_live` and not zero.**  Every transport kernel reads a
      !! filler's concentration as `hTr/h`, and the continuity step moves
      !! thickness out of (and into) fillers between two remaps.  A filler
      !! holding ZERO content hands that thickness to the live layers as
      !! fresh, 0 °C water, so a uniform tracer does not stay uniform — the
      !! previous rule (I1, `hTr = 0`) left 258 thin live cells at S = 0–34.43
      !! after 10 days of a uniform-S = 35 `double_gyre_mom6` run.  A filler
      !! holding its
      !! donor's concentration preserves tracer constancy.
      !!
      !! **Placement:** once per outer step, after every tracer update and after
      !! the ALE remap — `ocean_dyn_step_split_multilayer`'s tail.  The remap
      !! already establishes I1′ internally; this catches everything that
      !! writes AFTER it, and the families with no vanishing layers at all
      !! (sigma, z*-lite, the whole double-gyre / Eady / channel set) see a
      !! textual no-op: `rdb_vl_merge_content` mutates nothing on a column
      !! with no sub-threshold layer.
      !!
      !! Outer-shim + flat-impl: the registry is an array of derived types, so
      !! the per-tracer array is pulled out on the host and handed to the flat
      !! `_impl` as an explicit-shape dummy (CLAUDE.md, "Outer-shim + flat-impl
      !! call sites").
      class(multilayer_state_t), intent(inout) :: this
      integer, intent(in) :: nx
         !! i-extent of `h_layer` / `hTr` (total, incl. halos).
      integer, intent(in) :: ny
         !! j-extent (total, incl. halos).
      integer :: t

      if (.not. allocated(this%h_layer)) return
      if (.not. allocated(this%tracers)) return
      do t = 1, size(this%tracers)
         if (.not. allocated(this%tracers(t)%hTr)) cycle
         call enforce_vanished_one_impl(nx, ny, this%nz_ml, this%h_layer, &
                                        this%tracers(t)%hTr)
      end do
   end subroutine multilayer_enforce_vanished_content