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 | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(multilayer_state_t), | intent(inout) | :: | this | |||
| integer, | intent(in) | :: | nx |
i-extent of |
||
| integer, | intent(in) | :: | ny |
j-extent (total, incl. halos). |
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| integer, | private | :: | t |
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