rdb_ocean_min_thickness Module

Conservative minimum-layer-thickness adjustment for the isopycnal (VCOORD_LAGRANGIAN) ocean path.

Motivation: in the remap-free Lagrangian coordinate an eddy can displace an interface so far that a layer thins to ~0 (“outcropping”), and then u = hu/h blows up. The existing angstrom_h floor lifts a sub-floor layer with max(h_new, angstrom_h), which INJECTS mass (non-conservative: total column thickness, SSH, and tracer mass all drift up on every floored step) and the injected volume shapes the eddy field.

This module instead performs a CONSERVATIVE adjustment: when a layer thins below the floor, the deficit is borrowed from the surplus layers of the SAME water column — the interface is moved, no mass is created. Per water column the following are conserved to round-off: * total thickness Sigma_k h_k (SSH unchanged) * momentum Sigma_k h_face_k . u_face_k (per C-grid face) * every tracer mass Sigma_k h_k . Tr_k

The adjustment is a STRICT no-op on any column where all layers already meet the floor: those columns (and their faces) are left byte-unchanged so the isopycnal interface structure — and hence the baroclinic mode we are modelling — is never pinned. This is the load-bearing constraint that distinguishes this from a z*/sigma remap.

Implementation: reuse the conservative per-column remap_column primitive (the same engine the ALE remap uses) with a “floor-only” target column — each sub-floor layer inflated to the floor, the excess drawn conservatively from the surplus layers so the column total is preserved.

COST STRUCTURE (the 31%-of-runtime restructure, 2026-07-24): the borrow is a no-op on every non-grounded column, but the original implementation still paid ~8 full-field passes per call on a healthy domain (unconditional target build + copy-back, per-tracer concentration divisions before the gate, and per-face NEIGHBOUR COLUMN re-scans with k-strided access). Now: ONE coalesced pass builds a 2D grounded mask + a global count; zero grounded columns ⇒ the whole call returns after that single read; otherwise every kernel consults the mask (2 loads) and only grounded columns / active faces do work — and reads fall back to h_old on non-grounded neighbours, where h_new ≡ h_old by construction. Byte-identical results in all cases.


Uses

  • module~~rdb_ocean_min_thickness~~UsesGraph module~rdb_ocean_min_thickness rdb_ocean_min_thickness module~rdb_constants rdb_constants module~rdb_ocean_min_thickness->module~rdb_constants module~rdb_grid rdb_grid module~rdb_ocean_min_thickness->module~rdb_grid module~rdb_multilayer_state rdb_multilayer_state module~rdb_ocean_min_thickness->module~rdb_multilayer_state module~rdb_remap_column rdb_remap_column module~rdb_ocean_min_thickness->module~rdb_remap_column pic_types pic_types module~rdb_constants->pic_types module~rdb_grid->module~rdb_constants module~rdb_multilayer_state->module~rdb_constants module~rdb_multilayer_state->module~rdb_grid iso_fortran_env iso_fortran_env module~rdb_multilayer_state->iso_fortran_env module~rdb_efp rdb_efp module~rdb_multilayer_state->module~rdb_efp module~rdb_error_ring rdb_error_ring module~rdb_multilayer_state->module~rdb_error_ring module~rdb_mem_report rdb_mem_report module~rdb_multilayer_state->module~rdb_mem_report module~rdb_tracer rdb_tracer module~rdb_multilayer_state->module~rdb_tracer pic_logger pic_logger module~rdb_multilayer_state->pic_logger module~rdb_remap_column->module~rdb_constants module~rdb_efp->iso_fortran_env ieee_arithmetic ieee_arithmetic module~rdb_efp->ieee_arithmetic module~rdb_error_ring->pic_logger module~rdb_mem_report->module~rdb_constants module~rdb_mem_report->iso_fortran_env module~rdb_mem_report->pic_logger pic_strings pic_strings module~rdb_mem_report->pic_strings module~rdb_tracer->module~rdb_constants module~rdb_tracer->module~rdb_grid module~rdb_tracer->iso_fortran_env module~rdb_tracer->module~rdb_mem_report

Used by

  • module~~rdb_ocean_min_thickness~~UsedByGraph module~rdb_ocean_min_thickness rdb_ocean_min_thickness module~rdb_ocean_dyn rdb_ocean_dyn module~rdb_ocean_dyn->module~rdb_ocean_min_thickness module~rdb_driver rdb_driver module~rdb_driver->module~rdb_ocean_dyn module~rdb_ocean_engine rdb_ocean_engine module~rdb_driver->module~rdb_ocean_engine module~rdb_ocean_state rdb_ocean_state module~rdb_driver->module~rdb_ocean_state module~rdb_ocean_api rdb_ocean_api module~rdb_ocean_api->module~rdb_ocean_dyn module~rdb_ocean_api->module~rdb_ocean_engine module~rdb_handle rdb_handle module~rdb_ocean_api->module~rdb_handle module~rdb_ocean_diag_derived rdb_ocean_diag_derived module~rdb_ocean_api->module~rdb_ocean_diag_derived module~rdb_ocean_diag_fills rdb_ocean_diag_fills module~rdb_ocean_api->module~rdb_ocean_diag_fills module~rdb_ocean_engine->module~rdb_ocean_dyn module~rdb_ocean_setup rdb_ocean_setup module~rdb_ocean_engine->module~rdb_ocean_setup module~rdb_ocean_engine->module~rdb_ocean_state module~rdb_ocean_engine->module~rdb_ocean_diag_derived module~rdb_ocean_engine->module~rdb_ocean_diag_fills module~rdb_ocean_setup->module~rdb_ocean_dyn module~rdb_ocean_setup->module~rdb_ocean_state module~rdb_ocean_state->module~rdb_ocean_dyn module~rdb_handle->module~rdb_ocean_engine module~rdb_handle->module~rdb_ocean_state module~rdb_ocean_diag_derived->module~rdb_ocean_state module~rdb_ocean_diag_derived->module~rdb_ocean_diag_fills module~rdb_ocean_diag_fills->module~rdb_ocean_state

Variables

Type Visibility Attributes Name Initial
real(kind=wp), private, parameter :: H_CONC_FLOOR = 1.0e-20_wp

Pure 1/0 armour when forming a layer concentration c = q/h_old; only guards genuinely-zero source layers (never a physical thickness).


Subroutines

public pure subroutine min_thickness_target_column(nz, h_old, h_floor, h_new, grounded)

Build the floor-only conservative target thickness column.

Read more…

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nz
real(kind=wp), intent(in) :: h_old(nz)
real(kind=wp), intent(in) :: h_floor
real(kind=wp), intent(out) :: h_new(nz)
logical, intent(out) :: grounded

public subroutine ocean_apply_conservative_min_thickness(grid, ms, h_new, grounded_mask, h_floor)

Apply the conservative minimum-thickness adjustment in place on ms.

Read more…

Arguments

Type IntentOptional Attributes Name
type(hgrid_t), intent(in) :: grid
type(multilayer_state_t), intent(inout) :: ms
real(kind=wp), intent(inout) :: h_new(grid%nx_total,grid%ny_total,ms%nz_ml)
real(kind=wp), intent(inout) :: grounded_mask(grid%nx_total,grid%ny_total,1)
real(kind=wp), intent(in) :: h_floor

Minimum layer thickness (m); the isopycnal angstrom_h.

private pure subroutine assign_h_layer(nx, ny, nz, h_new, mask, h_layer)

Copy the target field into h_layer on grounded columns only — non-grounded columns’ h_new was never written, and their old h_layer is already the exact target.

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nx
integer, intent(in) :: ny
integer, intent(in) :: nz
real(kind=wp), intent(in) :: h_new(nx,ny,nz)
real(kind=wp), intent(in) :: mask(nx,ny,1)
real(kind=wp), intent(inout) :: h_layer(nx,ny,nz)

private subroutine build_grounded_mask(nx, ny, nz, h_old, h_floor, mask, n_grounded)

ONE coalesced pass: mask(i,j,1) = 1.0 iff any layer of column (i,j) is strictly below the floor; n_grounded counts them (explicit OpenACC reduction — a sum() on a present-mapped array would run host-side under NVHPC non-managed mode and read the stale shadow).

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nx
integer, intent(in) :: ny
integer, intent(in) :: nz
real(kind=wp), intent(in) :: h_old(nx,ny,nz)
real(kind=wp), intent(in) :: h_floor
real(kind=wp), intent(out) :: mask(nx,ny,1)
integer, intent(out) :: n_grounded

private pure subroutine build_target_field(nx, ny, nz, h_old, h_floor, mask, h_new)

target. Non-grounded columns are SKIPPED — their h_new is never read downstream (every consumer falls back to h_old via the mask).

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nx
integer, intent(in) :: ny
integer, intent(in) :: nz
real(kind=wp), intent(in) :: h_old(nx,ny,nz)
real(kind=wp), intent(in) :: h_floor
real(kind=wp), intent(in) :: mask(nx,ny,1)
real(kind=wp), intent(out) :: h_new(nx,ny,nz)

private pure subroutine remap_tracer_grounded(nx, ny, nz, h_old, h_new, mask, hTr)

Conservative tracer remap gated on the grounded mask. Non-grounded columns are skipped -> hTr byte-unchanged; the concentration divisions only happen on grounded columns.

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nx
integer, intent(in) :: ny
integer, intent(in) :: nz
real(kind=wp), intent(in) :: h_old(nx,ny,nz)
real(kind=wp), intent(in) :: h_new(nx,ny,nz)
real(kind=wp), intent(in) :: mask(nx,ny,1)
real(kind=wp), intent(inout) :: hTr(nx,ny,nz)

private pure subroutine remap_x_face_grounded(nx, ny, nz, h_old, h_new, mask, u_face_x)

Conservative east-face velocity remap gated on the grounded mask. A face is active iff either adjacent cell is grounded (2 mask loads — no neighbour-column re-scan); inactive faces are left byte-unchanged. Face thickness is the arithmetic mean of the two adjacent cells (outer walls take the single interior cell); the target side reads h_new only where the mask is set — elsewhere h_new ≡ h_old by construction, so h_old is read directly. remap_column preserves the per-face momentum Sigma h_face . u.

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nx
integer, intent(in) :: ny
integer, intent(in) :: nz
real(kind=wp), intent(in) :: h_old(nx,ny,nz)
real(kind=wp), intent(in) :: h_new(nx,ny,nz)
real(kind=wp), intent(in) :: mask(nx,ny,1)
real(kind=wp), intent(inout) :: u_face_x(nx+1,ny,nz)

private pure subroutine remap_y_face_grounded(nx, ny, nz, h_old, h_new, mask, v_face_y)

Conservative north-face velocity remap, mirror of the x routine.

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nx
integer, intent(in) :: ny
integer, intent(in) :: nz
real(kind=wp), intent(in) :: h_old(nx,ny,nz)
real(kind=wp), intent(in) :: h_new(nx,ny,nz)
real(kind=wp), intent(in) :: mask(nx,ny,1)
real(kind=wp), intent(inout) :: v_face_y(nx,ny+1,nz)