Neutral-density slope S = -∇ρ/∂_zρ and interface stratification
N² at C-grid layer interfaces (u-faces → slope_x/n2_u, v-faces
→ slope_y/n2_v). Purely diagnostic — consumed by GM/Redi/VarMix/
MLE; no flux consumer here. Harmonic-thickness-weighted FD form
(Griffies 1998).
Bottom-up convention (k=1 bed, k=nz_ml surface). Interface K
1..nz_ml+1; K=1 bed and K=nz_ml+1 surface forced to zero slope.
Interior K straddles layer k=K (above, surface side) and k=K-1
(below, bed side). A vert-fill pre-pass diffuses T/S into massless
layers so T=S=0 ghosts don’t corrupt gradients.
The along-layer density gradient is rotated to the horizontal by the
interface-tilt term −∂zρ·(e_W − e_E), so e_int must be the TRUE
geopotential height of each interface: it is built bed-up from
e_int(:,:,1) = −D, with D the slot’s own copy of the bathymetry
(barotropic%b, m, positive down below the z = 0 datum — the
same datum the FV-MOM6 PGF builds e_face from). The column top is
then Σh − D, i.e. η in open ocean and −z_draft + η under an
ice shelf (Σh = bt_H_ref + η, bt_H_ref = b − z_draft), and a
horizontally uniform stratification over ANY bathymetry reads zero
slope on every coordinate. (The pre-fix zero bed datum differenced
heights above the LOCAL bed and read a bathymetry step as an
isopycnal slope ~ΔD/Δx.) set_bathymetry fills the copy at setup
from the wrapped + halo-exchanged b (ghost-correct at periodic and
MPI seams), before enter_data; an enabled ocean_slopes_compute
fails loud if it was never set.
&vcoord_nml zfixed_closed_faces)The z_fixed target hangs every nominal interface at a fixed depth
below z = 0, so with geopotential e_int two columns’ common
interior interface differs in height by O(η_W − η_E) only and the
tilt term is the (small, correct) free-surface tilt of the
coordinate — the general formula, no special case. One addition,
host-gated on metrics%use_closed_faces (configure admits it under
z_fixed only), knob off ⇒ not taken:
open(ka) .and. open(kb)), so no consumer (GM, its gm_src,
VarMix) reads a gradient formed against a filler.Default off (&ocean_slopes_nml enable=.false.) ⇒ slot allocated but
ocean_slopes_compute no-ops ⇒ bit-identical.
exposed for the interface-pressure unit test
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| real(kind=wp), | public, | allocatable | :: | bathy(:,:) |
Bed depth |
||
| logical, | public | :: | bathy_set | = | .false. |
True once |
|
| real(kind=wp), | public, | allocatable | :: | e_int(:,:,:) |
Geopotential interface height (m, positive up from |
||
| logical, | public | :: | enable | = | .false. |
Master switch ( |
|
| logical, | public | :: | is_init | = | .false. |
True between |
|
| real(kind=wp), | public | :: | kd_smooth | = | 1.0e-6_wp |
Vertical diffusivity (m²/s) used by |
|
| real(kind=wp), | public | :: | min_dz_for_n2 | = | 1.0_wp |
Minimum layer thickness (m) used to floor |
|
| real(kind=wp), | public, | allocatable | :: | n2_u(:,:,:) |
Brunt-Väisälä N² at u-faces (s⁻²), shape |
||
| real(kind=wp), | public, | allocatable | :: | n2_v(:,:,:) |
Brunt-Väisälä N² at v-faces (s⁻²), shape |
||
| integer, | public | :: | nx_total | = | 0 | ||
| integer, | public | :: | ny_total | = | 0 | ||
| integer, | public | :: | nz_ml | = | 0 | ||
| real(kind=wp), | public | :: | rho0 | = | 1035.0_wp |
Reference density (kg/m³) for the N² scaling |
|
| real(kind=wp), | public, | allocatable | :: | s_fill(:,:,:) |
Massless-layer-filled salinity scratch, |
||
| real(kind=wp), | public, | allocatable | :: | slope_x(:,:,:) |
Isopycnal slope at u-faces, shape |
||
| real(kind=wp), | public, | allocatable | :: | slope_y(:,:,:) |
Isopycnal slope at v-faces, shape |
||
| real(kind=wp), | public, | allocatable | :: | t_fill(:,:,:) |
Massless-layer-filled temperature scratch, |
| procedure, public, non_overridable :: bytes => ocean_slopes_bytes | |
| procedure, public, non_overridable :: destroy => ocean_slopes_destroy | |
| procedure, public, non_overridable :: enter_data => ocean_slopes_enter_data | |
| procedure, public, non_overridable :: exit_data => ocean_slopes_exit_data | |
| procedure, public, non_overridable :: init => ocean_slopes_init | |
| procedure, public, non_overridable :: set_bathymetry => ocean_slopes_set_bathymetry |
Surface-relative hydrostatic pressure at the interface K straddled
by layer ka (above, surface-side) and ka-1 (below): the
interface sits at the BOTTOM of layer ka, so the water column
above it is layers ka..nz (bottom-up, k=nz the surface).
p = g·ρ₀·Σ_{k’=ka}^{nz} h(k’) — the sum INCLUDES ka (the layer
directly above the interface); omitting it shorts the pressure by
one layer (~5e5 Pa) and biases pressure-dependent EOS derivatives.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| integer, | intent(in) | :: | nz | |||
| real(kind=wp), | intent(in) | :: | h_layer(nx,ny,nz) | |||
| integer, | intent(in) | :: | ic | |||
| integer, | intent(in) | :: | jc | |||
| integer, | intent(in) | :: | ka | |||
| real(kind=wp), | intent(in) | :: | rho0 |
Counted allocatable footprint of the isopycnal slopes slot (0 when unallocated). One arr_bytes term per array — add a term here when a new allocatable joins the type.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_slopes_t), | intent(in) | :: | this |
Public entry point — fill slope_x/slope_y + n2_u/n2_v at
all interfaces. No-op if absent / uninitialised / disabled, so
the driver can call it unconditionally. Pipeline: vert-fill T/S
→ build interface heights e_int → u-face pass → v-face pass.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(hgrid_t), | intent(in) | :: | grid | |||
| type(ocean_metrics_t), | intent(in) | :: | metrics | |||
| type(eos_t), | intent(in) | :: | eos | |||
| type(ocean_slopes_t), | intent(inout), | optional | :: | slopes | ||
| type(multilayer_state_t), | intent(in) | :: | ms | |||
| real(kind=wp), | intent(in) | :: | dt |
Fill massless layers in T/S with sensible values via one pass of
constant-kappa·dt vertical diffusion — a SINGLE forward-elim +
back-sub Thomas sweep per column (no iteration). Operates on the
tracer-from-hTr conversion (T = hTr/h) and writes the scratch
t_fill/s_fill; the prognostic tracers are untouched.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| integer, | intent(in) | :: | nz | |||
| real(kind=wp), | intent(in) | :: | h_layer(nx,ny,nz) | |||
| real(kind=wp), | intent(in) | :: | t_htr(nx,ny,nz) | |||
| real(kind=wp), | intent(in) | :: | s_htr(nx,ny,nz) | |||
| real(kind=wp), | intent(in) | :: | kd_smooth | |||
| real(kind=wp), | intent(in) | :: | dt | |||
| real(kind=wp), | intent(out) | :: | t_fill(nx,ny,nz) | |||
| real(kind=wp), | intent(out) | :: | s_fill(nx,ny,nz) |
Build GEOPOTENTIAL interface heights bottom-up: e_int(:,:,1) =
−bathy (the bed, below the z = 0 datum),
e_int(:,:,K+1) = e_int(:,:,K) + h_layer(:,:,K). A per-column
serial cumulative sum (parallel over i,j). The across-face
difference e_W − e_E feeds the interface-tilt term, so the bed
datum is NOT irrelevant: it must be the true bed depth, or a
bathymetry step reads as an isopycnal slope.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| integer, | intent(in) | :: | nz | |||
| real(kind=wp), | intent(in) | :: | bathy(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | h_layer(nx,ny,nz) | |||
| real(kind=wp), | intent(out) | :: | e_int(nx,ny,nz+1) |
NVHPC doesn’t descriptor-walk per launch.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(hgrid_t), | intent(in) | :: | grid | |||
| type(ocean_metrics_t), | intent(in) | :: | metrics | |||
| type(eos_t), | intent(in) | :: | eos | |||
| type(ocean_slopes_t), | intent(inout) | :: | slopes | |||
| type(multilayer_state_t), | intent(in) | :: | ms | |||
| real(kind=wp), | intent(in) | :: | h_layer(slopes%nx_total,slopes%ny_total,slopes%nz_ml) | |||
| real(kind=wp), | intent(in) | :: | t_htr(slopes%nx_total,slopes%ny_total,slopes%nz_ml) | |||
| real(kind=wp), | intent(in) | :: | s_htr(slopes%nx_total,slopes%ny_total,slopes%nz_ml) | |||
| real(kind=wp), | intent(in) | :: | dt |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_slopes_t), | intent(inout) | :: | this |
Poly TBP delegating to a type(...)-arg _impl (AMD-crash rule:
bare polymorphic copyin(this) maps the stack descriptor → AMD
libomptarget cross-slot overlap crash).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_slopes_t), | intent(inout) | :: | this |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(ocean_slopes_t), | intent(inout) | :: | this |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_slopes_t), | intent(inout) | :: | this |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(ocean_slopes_t), | intent(inout) | :: | this |
Allocate the slope / N² outputs + the vert-fill T/S scratch +
the interface-height buffer. Default nz_ml = 1 preserves the
barotropic-only constructor; pass nz_ml = ms%nz_ml for the
multilayer driver. Setup code uses plain host allocation (no
do concurrent before enter_data).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_slopes_t), | intent(inout) | :: | this | |||
| type(hgrid_t), | intent(in) | :: | grid | |||
| integer, | intent(in), | optional | :: | nz_ml |
Zero slope / N² at every interior interface K that is NOT
strictly inside its face’s open column, i.e. unless both layers it
separates (K above, K-1 below) are open at that face
(&vcoord_nml zfixed_closed_faces). Assigned under a test, never
multiplied by the 0/1 mask, so a non-finite value formed against a
filler cannot survive as NaN·0.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| integer, | intent(in) | :: | nz | |||
| real(kind=wp), | intent(in) | :: | open_u(nx+1,ny,nz) | |||
| real(kind=wp), | intent(in) | :: | open_v(nx,ny+1,nz) | |||
| real(kind=wp), | intent(inout) | :: | slope_x(nx+1,ny,nz+1) | |||
| real(kind=wp), | intent(inout) | :: | slope_y(nx,ny+1,nz+1) | |||
| real(kind=wp), | intent(inout) | :: | n2_u(nx+1,ny,nz+1) | |||
| real(kind=wp), | intent(inout) | :: | n2_v(nx,ny+1,nz+1) |
u-face slope + N² pass. Interface K (interior 2..nz) straddles
layer k=K (above, surface side) and k=K-1 (below, bed side).
Bed (K=1) + surface (K=nz+1) are forced to zero. The u-face at
(i,j) sits between cells (i-1,j) and (i,j); pairs columns
iw=i-1 (west) and i (east), so loop i=2:nx.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| integer, | intent(in) | :: | nz | |||
| type(eos_t), | intent(in) | :: | eos | |||
| real(kind=wp), | intent(in) | :: | rho0 | |||
| real(kind=wp), | intent(in) | :: | min_dz | |||
| real(kind=wp), | intent(in) | :: | h_layer(nx,ny,nz) | |||
| real(kind=wp), | intent(in) | :: | t_fill(nx,ny,nz) | |||
| real(kind=wp), | intent(in) | :: | s_fill(nx,ny,nz) | |||
| real(kind=wp), | intent(in) | :: | e_int(nx,ny,nz+1) | |||
| real(kind=wp), | intent(in) | :: | idxCu(nx+1,ny) | |||
| real(kind=wp), | intent(in) | :: | wet_u(nx+1,ny) | |||
| real(kind=wp), | intent(out) | :: | slope_x(nx+1,ny,nz+1) | |||
| real(kind=wp), | intent(out) | :: | n2_u(nx+1,ny,nz+1) |
v-face slope + N² pass — mirror of pass_x with v-staggering.
The v-face at (i,j) sits between cells (i,j-1) and (i,j); pairs
columns js=j-1 (south) and j (north), loop j=2:ny.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| integer, | intent(in) | :: | nz | |||
| type(eos_t), | intent(in) | :: | eos | |||
| real(kind=wp), | intent(in) | :: | rho0 | |||
| real(kind=wp), | intent(in) | :: | min_dz | |||
| real(kind=wp), | intent(in) | :: | h_layer(nx,ny,nz) | |||
| real(kind=wp), | intent(in) | :: | t_fill(nx,ny,nz) | |||
| real(kind=wp), | intent(in) | :: | s_fill(nx,ny,nz) | |||
| real(kind=wp), | intent(in) | :: | e_int(nx,ny,nz+1) | |||
| real(kind=wp), | intent(in) | :: | idyCv(nx,ny+1) | |||
| real(kind=wp), | intent(in) | :: | wet_v(nx,ny+1) | |||
| real(kind=wp), | intent(out) | :: | slope_y(nx,ny+1,nz+1) | |||
| real(kind=wp), | intent(out) | :: | n2_v(nx,ny+1,nz+1) |
Copy the bed depth b (m, positive down, (nx_total, ny_total)
incl. ghosts) into this%bathy on the host. Call it with the
WRAPPED + halo-exchanged barotropic%b (the seam faces read the
ghosts) and BEFORE enter_data (the device copy is taken from
the host values); to refresh after enter_data the caller issues
!$acc update device(this%bathy) itself. No-op on an
uninitialised slot.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_slopes_t), | intent(inout) | :: | this | |||
| real(kind=wp), | intent(in) | :: | b(:,:) |