VarMix capability [4]: produces the spatially-varying thickness- and
tracer-diffusion coefficient face fields (khth_u/khth_v,
khtr_u/khtr_v, m^2/s) that GM (rdb_ocean_gm) and the future Redi
path consume — replacing the constant khth they fill from for v1.
Two ingredients, both clean-room from the literature:
A resolution function Res_fn in [0,1] (Hallberg 2013) that
turns the parameterization OFF where the deformation radius is
well resolved (Ld >> dx) and ON where it is not. In the
divide-free form for power p:
f2_dx2 = (dx^2 + dy^2) * max(f^2, eps^2) beta_dx2 = oneOrTwo * (dx^2 + dy^2) * |grad f| dx_term = f2_dx2 + cg1 * beta_dx2 Res_fn = dx_term^(p/2) / (dx_term^(p/2) + (alpha*cg1)^p)
where cg1 is the first baroclinic gravity-wave speed (the
wavespeed slot), |grad f| the FULL discrete 2D Coriolis-
gradient magnitude (so the equatorial f -> 0 limit stays finite
via the beta term — no divide-by-zero), oneOrTwo = 2 under the
Gill (1982) equatorial-Ld convention (default). f2_dx2 and
beta_dx2 are STATIC (functions of metrics + f only) — precomputed
once at init with host loops; only Res_fn is rebuilt per thermo
step from the (slowly varying) cg1. eps = VERY_SMALL_FREQUENCY.
A Visbeck (1997) / Eady (1949) baroclinicity scaling
KhTh += khth_slope_cff * L2 * SN where SN is the Eady growth
rate (thickness-weighted vertical average of sqrt(S^2 N^2)) and
L2 = visbeck_l_scale^2 (or visbeck_l_scale^2 * areaCu if the
knob is negative ⇒ a nondimensional scale times the cell area).
SN_u (calc_Visbeck_coeffs thickness-weighted path):
SN_u = sum_k sqrt(S2 * N2) * H_geom / sum_k H_geom (k = 2..nz)
H_geom = sqrt( sqrt(h(i,k)*h(i+1,k)) * sqrt(h(i,k-1)*h(i+1,k-1)) )
S2 = slope_x(i,k)^2 + (h-weighted avg of the 4 corner slope_y^2)
N2 = max(0, n2_u(i,k))
(optional S2 limit S2 = S2*S2max/(S2+S2max) only if visbeck_max_slope>0)
The orthogonal slope is already folded into S2 (the h-weighted 4-corner slope_y^2 term), so SN_u is FINAL after the thickness-weighted sum — no separate SN_v combine (MOM6 calc_Visbeck_coeffs_old; the SN_v 4-corner combine belongs to the distinct calc_Eady_growth_rate_2D path).
Assembly order (Res_fn BEFORE the clamp) per face:
Kh = khth
Kh += khth_slope_cff * L2 * SN
Kh = Res_fn (when resoln_scaled_khth)
Kh = max(khth_min, min(Kh, khth_max)) (min only when khth_max>0)
This is the pre-CFL base* KhTh face field. The diffusive-CFL cap is
owned by GM (it holds dt + the native face metrics): GM does
KH = min(KH_CFL, base). When VarMix is OFF, GM falls back to its
constant khth ⇒ byte-identical.
Bottom-up convention (k=1 bed, k=nz surface; interface K=1 bed, K=nz+1 surface — both carry zero slope from the slopes slot).
Deferred (documented): EBT/SQG vertical-structure functions (KhTh stays
2D), the MEKE additive term, depth tapering, the
calc_Eady_growth_rate_2D outcrop-cropping filter, and the OBC-aware
face interpolation (interior straddle-average only for now).
Default off (&ocean_varmix_nml enable = .false.) ⇒ varmix_compute
is never called and GM keeps its constant khth ⇒ bit-identical.
References: Visbeck, Marshall, Haine & Spall (1997) JPO 27, 381-402; Hallberg (2013) Ocean Modelling 72, 92-103; Eady (1949) Tellus 1, 33-52; Chelton, deSzoeke, Schlax, El Naggar & Siwertz (1998) JPO 28, 433-460; Gill (1982) “Atmosphere-Ocean Dynamics”. No source ported.
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| real(kind=wp), | private, | parameter | :: | H_SUBROUNDOFF4 | = | 1.0e-40_wp |
Tiny denominator armour for the 4-corner slope_y weighted average
(mirrors the MOM6 |
| real(kind=wp), | private, | parameter | :: | VERY_SMALL_FREQUENCY | = | 1.0e-17_wp |
Floor on f (1/s) inside |
Spatially-varying lateral-diffusivity-coefficient state. All
fields default to the inert (enable=.false.) configuration so an
ocean run that never sets &ocean_varmix_nml is bit-identical.
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| real(kind=wp), | public, | allocatable | :: | beta_dx2_u(:,:) |
|
||
| real(kind=wp), | public, | allocatable | :: | beta_dx2_v(:,:) |
Same at v-faces, |
||
| logical, | public | :: | enable | = | .false. |
Master switch. Off ⇒ |
|
| real(kind=wp), | public, | allocatable | :: | f2_dx2_u(:,:) |
|
||
| real(kind=wp), | public, | allocatable | :: | f2_dx2_v(:,:) |
Same at v-faces, |
||
| logical, | public | :: | gill_equatorial_ld | = | .true. |
Gill (1982) equatorial-Ld convention ⇒ |
|
| logical, | public | :: | interpolate_res_fn | = | .false. |
|
|
| logical, | public | :: | is_init | = | .false. |
True between |
|
| integer, | public | :: | kh_res_fn_power | = | 2 |
Resolution-function power |
|
| real(kind=wp), | public | :: | kh_res_scale_coef | = | 1.0_wp |
Resolution-function |
|
| real(kind=wp), | public | :: | khth | = | 0.0_wp |
Background thickness diffusivity KhTh (m^2/s) — the constant the
Visbeck term and Res_fn scale. Mirrors |
|
| real(kind=wp), | public | :: | khth_max | = | 0.0_wp |
Upper clamp on KhTh (m^2/s); <= 0 ⇒ no upper cap. |
|
| real(kind=wp), | public | :: | khth_min | = | 0.0_wp |
Lower clamp on the assembled KhTh (m^2/s). |
|
| real(kind=wp), | public | :: | khth_slope_cff | = | 0.0_wp |
Visbeck coefficient |
|
| real(kind=wp), | public, | allocatable | :: | khth_u(:,:) |
Pre-CFL base thickness diffusivity at u-faces (m^2/s),
|
||
| real(kind=wp), | public, | allocatable | :: | khth_v(:,:) |
Pre-CFL base KhTh at v-faces, |
||
| real(kind=wp), | public | :: | khtr | = | 0.0_wp |
Background tracer diffusivity KhTr (m^2/s) for the future Redi. |
|
| real(kind=wp), | public | :: | khtr_max | = | 0.0_wp |
Upper clamp on KhTr (m^2/s); <= 0 ⇒ no upper cap. |
|
| real(kind=wp), | public | :: | khtr_min | = | 0.0_wp |
Lower clamp on KhTr (m^2/s). |
|
| real(kind=wp), | public | :: | khtr_slope_cff | = | 0.0_wp |
Visbeck coefficient for the KhTr chain. |
|
| real(kind=wp), | public, | allocatable | :: | khtr_u(:,:) |
Pre-CFL base tracer diffusivity at u-faces (m^2/s), |
||
| real(kind=wp), | public, | allocatable | :: | khtr_v(:,:) |
Pre-CFL base KhTr at v-faces, |
||
| real(kind=wp), | public, | allocatable | :: | l2_u(:,:) |
Visbeck |
||
| real(kind=wp), | public, | allocatable | :: | l2_v(:,:) |
Visbeck |
||
| integer, | public | :: | nx_total | = | 0 | ||
| integer, | public | :: | ny_total | = | 0 | ||
| integer, | public | :: | nz_ml | = | 0 | ||
| real(kind=wp), | public, | allocatable | :: | res_fn_u(:,:) |
Resolution function at u-faces (nondim, [0,1]), |
||
| real(kind=wp), | public, | allocatable | :: | res_fn_v(:,:) |
Resolution function at v-faces, |
||
| logical, | public | :: | resoln_scaled_khth | = | .false. |
Multiply the assembled KhTh by |
|
| logical, | public | :: | resoln_scaled_khtr | = | .false. |
Multiply the assembled KhTr by |
|
| real(kind=wp), | public, | allocatable | :: | sn_u(:,:) |
Eady growth rate |
||
| real(kind=wp), | public, | allocatable | :: | sn_v(:,:) |
Eady growth rate at v-faces, |
||
| logical, | public | :: | use_visbeck | = | .false. |
Add the Visbeck/Eady |
|
| real(kind=wp), | public | :: | visbeck_l_scale | = | 0.0_wp |
Visbeck length scale L (m); if < 0, |L|^2 * areaCu/areaCv is used (a nondimensional scale times the local cell area). |
|
| real(kind=wp), | public | :: | visbeck_max_slope | = | 0.0_wp |
S^2 limiter scale; <= 0 ⇒ no S^2 limit. |
| procedure, public, non_overridable :: build_static => ocean_varmix_build_static | |
| procedure, public, non_overridable :: bytes => ocean_varmix_bytes | |
| procedure, public, non_overridable :: destroy => ocean_varmix_destroy | |
| procedure, public, non_overridable :: enter_data => ocean_varmix_enter_data | |
| procedure, public, non_overridable :: exit_data => ocean_varmix_exit_data | |
| procedure, public, non_overridable :: init => ocean_varmix_init |
Counted allocatable footprint of the VarMix 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_varmix_t), | intent(in) | :: | this |
Assembly chain in the load-bearing order: background + Visbeck
addend, THEN Res_fn scale, THEN clamp. kh_max <= 0 ⇒ no upper cap.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | kh_bg | |||
| real(kind=wp), | intent(in) | :: | cff | |||
| real(kind=wp), | intent(in) | :: | l2 | |||
| real(kind=wp), | intent(in) | :: | sn | |||
| real(kind=wp), | intent(in) | :: | res_fn | |||
| logical, | intent(in) | :: | resoln | |||
| real(kind=wp), | intent(in) | :: | kh_min | |||
| real(kind=wp), | intent(in) | :: | kh_max | |||
| logical, | intent(in) | :: | do_visbeck |
Cross-face df/dx at a v-face (j interior): average of the centred x-derivatives in the south (j-1) and north (j) rows.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | f_centre(nx,ny) | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| integer, | intent(in) | :: | i | |||
| integer, | intent(in) | :: | j | |||
| real(kind=wp), | intent(in) | :: | idx |
Cross-face df/dy at a u-face (i interior): average of the two centred y-derivatives in the west (i-1) and east (i) columns, clamped at the j edges (one-sided / zero there).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | f_centre(nx,ny) | |||
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| integer, | intent(in) | :: | i | |||
| integer, | intent(in) | :: | j | |||
| real(kind=wp), | intent(in) | :: | idy |
Divide-free resolution function for power p (even). p=2:
dx_term/(dx_term + (alpha*cg1)^2); general even p:
dx_term^(p/2)/(dx_term^(p/2) + (alpha*cg1)^p). dx_term =
f2_dx2 + cg1*beta_dx2. -> 1 where unresolved, -> 0 where Ld>>dx.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | f2_dx2 | |||
| real(kind=wp), | intent(in) | :: | beta_dx2 | |||
| real(kind=wp), | intent(in) | :: | cg1 | |||
| real(kind=wp), | intent(in) | :: | alpha | |||
| integer, | intent(in) | :: | p |
Fill res_fn_*, sn_*, khth_*, khtr_* (the pre-CFL base face
coefficients) from the static grid terms, the slopes/N^2 slot, and
the first-mode wave speed cg1. No-op when disabled / the deps are
absent. The CFL cap is applied downstream by GM.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(hgrid_t), | intent(in) | :: | grid | |||
| type(ocean_metrics_t), | intent(in) | :: | metrics | |||
| type(ocean_varmix_t), | intent(inout) | :: | this | |||
| type(ocean_slopes_t), | intent(in) | :: | slopes | |||
| type(ocean_wave_speed_t), | intent(in) | :: | wavespeed | |||
| type(multilayer_state_t), | intent(in) | :: | ms |
Fill the static f2_dx2_*, beta_dx2_*, and l2_* face fields
from the (curvilinear) metrics + the cell-centre Coriolis magnitude
f_centre. Called ONCE after init + configure + metrics fill (so
oneOrTwo, visbeck_l_scale, and the device-resident metrics are
known), BEFORE the device map. HOST loops only — these are static
(functions of geometry + planetary f) and never change with time.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_varmix_t), | intent(inout) | :: | this | |||
| type(ocean_metrics_t), | intent(in) | :: | metrics | |||
| real(kind=wp), | intent(in) | :: | f_centre(this%nx_total,this%ny_total) |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_varmix_t), | intent(inout) | :: | this |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_varmix_t), | intent(inout) | :: | this |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(ocean_varmix_t), | intent(inout) | :: | this |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_varmix_t), | intent(inout) | :: | this |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(ocean_varmix_t), | intent(inout) | :: | this |
Allocate the static grid terms, the per-step diagnostics, and the
KhTh/KhTr base face fields. Always allocates (configure runs after
init); the static f2_dx2_* / beta_dx2_* / l2_* are filled by
build_static once the metrics + f_centre are known. Setup uses
plain host allocation (no do concurrent before enter_data).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_varmix_t), | intent(inout) | :: | this | |||
| type(hgrid_t), | intent(in) | :: | grid | |||
| integer, | intent(in), | optional | :: | nz_ml |
Flat-impl VarMix kernel (explicit-shape; NVHPC descriptor-walk-free).
Three phases: (1) Res_fn at faces (cg1 interpolated to faces or the
centre-Res_fn averaged, per interp_res), (2) Eady SN at u/v faces
(thickness-weighted column reductions with the orthogonal slope folded
into S^2, scalar accumulators — SN is final, no SN_v combine), (3) the
assembly (Visbeck addend, Res_fn scale, clamp) into the KhTh/KhTr base.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| integer, | intent(in) | :: | nz | |||
| integer, | intent(in) | :: | p | |||
| real(kind=wp), | intent(in) | :: | alpha | |||
| logical, | intent(in) | :: | resoln_khth | |||
| logical, | intent(in) | :: | resoln_khtr | |||
| logical, | intent(in) | :: | interp_res | |||
| logical, | intent(in) | :: | do_visbeck | |||
| real(kind=wp), | intent(in) | :: | s2max | |||
| real(kind=wp), | intent(in) | :: | khth | |||
| real(kind=wp), | intent(in) | :: | khtr | |||
| real(kind=wp), | intent(in) | :: | khth_cff | |||
| real(kind=wp), | intent(in) | :: | khtr_cff | |||
| real(kind=wp), | intent(in) | :: | khth_min | |||
| real(kind=wp), | intent(in) | :: | khth_max | |||
| real(kind=wp), | intent(in) | :: | khtr_min | |||
| real(kind=wp), | intent(in) | :: | khtr_max | |||
| real(kind=wp), | intent(in) | :: | f2_dx2_u(nx+1,ny) | |||
| real(kind=wp), | intent(in) | :: | f2_dx2_v(nx,ny+1) | |||
| real(kind=wp), | intent(in) | :: | beta_dx2_u(nx+1,ny) | |||
| real(kind=wp), | intent(in) | :: | beta_dx2_v(nx,ny+1) | |||
| real(kind=wp), | intent(in) | :: | l2_u(nx+1,ny) | |||
| real(kind=wp), | intent(in) | :: | l2_v(nx,ny+1) | |||
| real(kind=wp), | intent(in) | :: | cg1(nx,ny) | |||
| real(kind=wp), | intent(in) | :: | h_layer(nx,ny,nz) | |||
| real(kind=wp), | intent(in) | :: | slope_x(nx+1,ny,nz+1) | |||
| real(kind=wp), | intent(in) | :: | slope_y(nx,ny+1,nz+1) | |||
| real(kind=wp), | intent(in) | :: | n2_u(nx+1,ny,nz+1) | |||
| real(kind=wp), | intent(in) | :: | n2_v(nx,ny+1,nz+1) | |||
| real(kind=wp), | intent(out) | :: | res_fn_u(nx+1,ny) | |||
| real(kind=wp), | intent(out) | :: | res_fn_v(nx,ny+1) | |||
| real(kind=wp), | intent(out) | :: | sn_u(nx+1,ny) | |||
| real(kind=wp), | intent(out) | :: | sn_v(nx,ny+1) | |||
| real(kind=wp), | intent(out) | :: | khth_u(nx+1,ny) | |||
| real(kind=wp), | intent(out) | :: | khth_v(nx,ny+1) | |||
| real(kind=wp), | intent(out) | :: | khtr_u(nx+1,ny) | |||
| real(kind=wp), | intent(out) | :: | khtr_v(nx,ny+1) |
Thickness-weighted Eady growth rate at u-faces (own component).
Interior u-face (i=2..nx) pairs centre columns iw=i-1 (west) and i
(east). Interior interfaces K=2..nz; H_geom = sqrt(sqrt(h_iw,k *
h_i,k) * sqrt(h_iw,k-1 * h_i,k-1)). S2 = slope_x^2 + the four
corner slope_y^2 h-weighted to the u-face; S2 optionally limited.
SN_u = sum sqrt(S2*N2)*H_geom / sum H_geom.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| integer, | intent(in) | :: | nz | |||
| logical, | intent(in) | :: | do_visbeck | |||
| real(kind=wp), | intent(in) | :: | s2max | |||
| real(kind=wp), | intent(in) | :: | h_layer(nx,ny,nz) | |||
| real(kind=wp), | intent(in) | :: | slope_x(nx+1,ny,nz+1) | |||
| real(kind=wp), | intent(in) | :: | slope_y(nx,ny+1,nz+1) | |||
| real(kind=wp), | intent(in) | :: | n2_u(nx+1,ny,nz+1) | |||
| real(kind=wp), | intent(out) | :: | sn_u(nx+1,ny) |
Thickness-weighted Eady growth rate at v-faces (mirror of
varmix_sn_u). Interior v-face (j=2..ny) pairs js=j-1 (south) + j.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| integer, | intent(in) | :: | nz | |||
| logical, | intent(in) | :: | do_visbeck | |||
| real(kind=wp), | intent(in) | :: | s2max | |||
| real(kind=wp), | intent(in) | :: | h_layer(nx,ny,nz) | |||
| real(kind=wp), | intent(in) | :: | slope_x(nx+1,ny,nz+1) | |||
| real(kind=wp), | intent(in) | :: | slope_y(nx,ny+1,nz+1) | |||
| real(kind=wp), | intent(in) | :: | n2_v(nx,ny+1,nz+1) | |||
| real(kind=wp), | intent(out) | :: | sn_v(nx,ny+1) |