| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| real(kind=wp), | public, | allocatable | :: | b0(:,:) |
Surface buoyancy flux |
||
| real(kind=wp), | public | :: | bkgnd_delta | = | 222.0_wp |
Transition half-width (m): atan argument is (|z|-z0)/Delta. |
|
| logical, | public | :: | bkgnd_henyey | = | .false. |
Henyey, Wright & Flatte (1986) JGR 91:8487 latitude-dependent
internal-wave factor, scaling the SCALAR background tracer
diffusivities kt_floor(i,j) = max(bkgnd_kd_min, kt_bg * L(phi)) The implemented variant is the SIMPLIFIED one of Harrison &
Hallberg (2008) JPO 38:1894, which assumes the in-situ
stratification equals a constant reference MUTUALLY EXCLUSIVE with Also requires a non-cartesian SCOPE: the factor scales the two TRACER background floors only;
the momentum floor |
|
| real(kind=wp), | public | :: | bkgnd_henyey_max_lat | = | 95.0_wp |
Latitude (degN) poleward of which the factor is reset to its equatorial (near-zero) floor. Deliberately > 90 by default so the clamp is INERT for any real latitude out of the box; lower it to activate the optional poleward cutoff. |
|
| real(kind=wp), | public | :: | bkgnd_henyey_n0_2omega | = | 20.0_wp |
Ratio of the assumed reference buoyancy frequency |
|
| real(kind=wp), | public | :: | bkgnd_kd_deep | = | 1.3e-4_wp |
Deep-asymptote background tracer diffusivity (m^2/s). |
|
| real(kind=wp), | public | :: | bkgnd_kd_min | = | -1.0_wp |
Minimum background tracer diffusivity (m^2/s) under the Henyey
latitude scaling — MOM6 NEGATIVE = unset sentinel ⇒ resolved to
|
|
| real(kind=wp), | public | :: | bkgnd_kd_sfc | = | 1.0e-5_wp |
Surface-asymptote background tracer diffusivity (m^2/s). MOM6 BRYAN_LEWIS_C2-side default scale. |
|
| real(kind=wp), | public | :: | bkgnd_prandtl | = | 1.0_wp |
Background Prandtl number — Kv_bg = bkgnd_prandtl * Kd_bg (MOM6 ties the background viscosity to the background tracer diffusivity through a Prandtl factor). Default 1.0. |
|
| logical, | public | :: | bkgnd_profile | = | .false. |
Master switch for the depth-varying Bryan-Lewis background.
Default |
|
| real(kind=wp), | public | :: | bkgnd_z0 | = | 2500.0_wp |
Transition-centre depth (m, positive down) where the profile reaches the (sfc+deep)/2 midpoint. |
|
| real(kind=wp), | public, | allocatable | :: | bl_depth(:,:) |
KPP boundary-layer depth (m, positive down). |
||
| integer, | public | :: | buoyancy_coeffs | = | BUOY_COEFFS_CONSTANT |
|
|
| real(kind=wp), | public | :: | c_vt2 | = | 1.8_wp |
Unresolved-turbulence coefficient for the V_t² term in the
bulk-Ri denominator (LMD94 eq 23). Folded form of
|
|
| logical, | public | :: | conv_enable | = | .false. |
Master switch. Requires |
|
| real(kind=wp), | public | :: | conv_kd | = | 1.0_wp |
Convective tracer diffusivity (m^2/s). MOM6 |
|
| real(kind=wp), | public | :: | conv_n2_thresh | = | 0.0_wp |
Trigger threshold on N^2 (s^-2). MOM6 |
|
| real(kind=wp), | public | :: | conv_prandtl | = | 1.0_wp |
Kv_conv = conv_prandtl * kd_conv. MOM6 |
|
| real(kind=wp), | public | :: | cs_nonlocal | = | 6.3_wp |
Non-local transport coefficient C_s (LMD94 eq 20).
The full form |
|
| logical, | public | :: | ddiff_enable | = | .false. |
Master switch (MOM6 |
|
| real(kind=wp), | public | :: | ddiff_exp1 | = | 1.0_wp |
Inner (bracket) exponent of the fingering clamped form
(CVMix |
|
| real(kind=wp), | public | :: | ddiff_exp2 | = | 3.0_wp |
Outer exponent of the fingering clamped form (CVMix
|
|
| real(kind=wp), | public | :: | ddiff_kappa_s | = | 1.0e-4_wp |
Leading salt-fingering salinity diffusivity K_f (m^2/s,
CVMix |
|
| real(kind=wp), | public | :: | ddiff_mol_diff | = | 1.5e-6_wp |
Molecular diffusivity scaling the convection branch (m^2/s,
CVMix |
|
| real(kind=wp), | public | :: | ddiff_param1 | = | 0.909_wp |
MC76 diffusive-convection exterior coeff (CVMix
|
|
| real(kind=wp), | public | :: | ddiff_param2 | = | 4.6_wp |
MC76 middle coeff (CVMix |
|
| real(kind=wp), | public | :: | ddiff_param3 | = | -0.54_wp |
MC76 interior coeff (CVMix |
|
| real(kind=wp), | public | :: | ddiff_strat_param_max | = | 2.55_wp |
R_rho salt-fingering cutoff (CVMix |
|
| logical, | public | :: | ddiff_use_k90 | = | .false. |
Diffusive-convection form: |
|
| type(eos_t), | public | :: | eos | ||||
| real(kind=wp), | public, | allocatable | :: | gamma_s(:,:,:) |
Non-local salinity flux (PSU·m/s) at interfaces. |
||
| real(kind=wp), | public, | allocatable | :: | gamma_t(:,:,:) |
Non-local temperature flux (°C·m/s) at interfaces. |
||
| real(kind=wp), | public | :: | hmix_fixed | = | 20.0_wp |
Surface-band thickness (m) over which |
|
| integer, | public | :: | interior_closure | = | VMIX_INTERIOR_PP81 |
Interior mixing closure tag. |
|
| logical, | public | :: | is_init | = | .false. |
True between |
|
| real(kind=wp), | public, | allocatable | :: | kd_bg(:,:,:) |
Bryan-Lewis depth-profile tracer background (m^2/s) at interfaces. kv background floor = bkgnd_prandtl * kd_bg. |
||
| real(kind=wp), | public | :: | kd_max | = | huge(1.0_wp) |
Ceiling on tracer diffusivity (kt, ks) (m^2/s). Default
|
|
| integer, | public | :: | kd_smooth_iterations | = | 0 |
Number of 1-2-1 horizontal smoothing passes applied to
kv/kt at each interior interface. Default 0 = off (no
smoothing kernel runs, bit-identical). MOM6 |
|
| integer, | public | :: | kpp_sw_method | = | KPP_SW_MXL |
Shortwave-in-boundary-layer method for |
|
| real(kind=wp), | public, | allocatable | :: | ks(:,:,:) |
Salinity eddy diffusivity. |
||
| real(kind=wp), | public | :: | ks_bg | = | 1.0e-5_wp |
Background floor on salinity diffusivity (m^2/s).
Default matches |
|
| real(kind=wp), | public, | allocatable | :: | kt(:,:,:) |
Temperature eddy diffusivity. |
||
| real(kind=wp), | public | :: | kt_bg | = | 1.0e-5_wp |
Background floor on temperature diffusivity (m^2/s).
Default matches |
|
| real(kind=wp), | public, | allocatable | :: | kv(:,:,:) |
Momentum (u, v) eddy viscosity at interfaces. |
||
| real(kind=wp), | public | :: | kv_bg | = | 1.0e-4_wp |
Background floor on momentum viscosity (m^2/s). Default
matches |
|
| logical, | public | :: | kv_from_restart | = | .false. |
PR-2 (bt-rem-from-av-rem review): set by |
|
| real(kind=wp), | public | :: | kv_max | = | huge(1.0_wp) |
Ceiling on momentum viscosity (m^2/s). Default |
|
| real(kind=wp), | public | :: | kv_ml_invz2 | = | 0.0_wp |
Extra kinematic viscosity (m²/s) inside the surface band
of thickness |
|
| logical, | public | :: | p_top_in_eos | = | .false. |
Mirror of |
|
| real(kind=wp), | public | :: | pp81_alpha | = | 5.0_wp |
Richardson-number multiplier in (1 + α*Ri). Original paper uses 5; some implementations use 4 or 10. |
|
| real(kind=wp), | public | :: | pp81_kappa_bg | = | 1.0e-5_wp |
Background tracer diffusivity (m^2/s). |
|
| real(kind=wp), | public | :: | pp81_nu0 | = | 1.0e-2_wp |
Numerator viscosity at Ri=0 (m^2/s). |
|
| real(kind=wp), | public | :: | pp81_nu_bg | = | 1.0e-4_wp |
Background interior viscosity (m^2/s). |
|
| real(kind=wp), | public | :: | rho0 | = | 1035.0_wp |
Boussinesq reference density (kg/m^3). Used in the N² = -g/ρ_0 * dρ/dz expression, in the friction velocity u_* = √(|τ|/ρ_0), and in the kinematic surface fluxes q_T = Q_heat/(ρ_0·cp) / q_S = Q_salt/ρ_0 that set the KPP surface buoyancy flux B_0. ASSIGNED FROM CONFIG by |
|
| real(kind=wp), | public | :: | ri_crit | = | 0.3_wp |
Critical bulk Richardson number for KPP BL depth. |
|
| real(kind=wp), | public | :: | shear2_floor | = | 1.0e-10_wp |
Lower bound on |∂u/∂z|² to avoid Ri = N²/0 blow-up in quiescent water columns. |
|
| real(kind=wp), | public, | allocatable | :: | smooth_scratch(:,:,:) |
Scratch (nx, ny, nz+1) for one smoothing pass. |
||
| logical, | public | :: | use_closure | = | .false. |
Master switch. When |
|
| logical, | public | :: | use_kpp | = | .false. |
Enable KPP surface-boundary-layer scheme (future). |
|
| logical, | public | :: | vmix_guard | = | .false. |
Debug-gated negative/NaN guard. When |
Counted allocatable footprint of the vertical mixing 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_vmix_t), | intent(in) | :: | this |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_vmix_t), | intent(inout) | :: | this |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_vmix_t), | intent(inout) | :: | this |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_vmix_t), | intent(inout) | :: | this |
Allocate the kv / kt / ks diffusivity fields at layer
interfaces. Default values: kv = kv_bg (= pp81_nu_bg),
kt = ks = kt_bg (= pp81_kappa_bg) at interior interfaces;
boundary interfaces k=1 and k=nz+1 are zeroed (closed BC).
BL fields (bl_depth, gamma_*) seed at zero. ks is
allocated so the assembly stage (vmix_assemble) can
floor/clip it; vmix_split_kd_heat_salt derives its live
value from kt every stage, and vdiff_apply_tracers
consumes it for salinity + every passive tracer.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_vmix_t), | intent(inout) | :: | this | |||
| type(hgrid_t), | intent(in) | :: | grid | |||
| integer, | intent(in), | optional | :: | nz_ml |
Seed kv_bg/kt_bg/ks_bg and the kv/kt/ks/kd_bg arrays
from the current pp81_nu_bg/pp81_kappa_bg fields, then zero
the closed-BC boundary interfaces on kv/ks. Extracted out of
ocean_vmix_init (§2E structural invariant: kv_bg == pp81_nu_bg,
kt_bg == ks_bg == pp81_kappa_bg, so vmix_assemble’s background
floor is a no-op for the shipped PP81 closure) so BOTH the initial
seed and the &ocean_vmix_nml pp81_* config-copy re-derive
consistently — a bare field copy without this call would leave
the assembly floor clamped against the OLD (type-default)
background even after a user sets a new one. Requires
kv/kt/ks/kd_bg already allocated (true after init; the
config-copy call runs strictly after init_from_config).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(ocean_vmix_t), | intent(inout) | :: | this | |||
| logical, | intent(in), | optional | :: | skip_kv |
PR-2 (bt-rem-from-av-rem, fixed per review): default |
type :: ocean_vmix_t logical :: is_init = .false. !! True between `init` and `destroy`. Prefer this to !! `allocated(...)` — tracks GPU device attachment too. logical :: kv_from_restart = .false. !! PR-2 (bt-rem-from-av-rem review): set by `ocean_state_restart_read` !! (`rdb_ocean_state.F90`) immediately after a restart read, from !! the registry's `entry_found("vmix_kv")` — `.true.` iff THIS !! read actually found `vmix_kv` in the checkpoint (an older !! checkpoint without the field, or a cold start, both leave it !! `.false.`). `configure_ocean_lateral`'s mandatory `pp81_*` !! config-copy (`vmix_seed_backgrounds`) reads it to decide !! whether to skip reseeding `kv` — see that routine's docstring. !! Pure host bookkeeping: never device-mapped, never itself in !! the restart registry (it describes a read that already !! happened, not state to carry forward). ! ---- Scheme selection ---- logical :: use_closure = .false. !! Master switch. When `.false.` the driver skips the !! compute step and `rdb_ocean_vdiff` falls back to its !! scalar `K_v_*` constants. Default off so all existing !! tests stay on the scalar path. logical :: use_kpp = .false. !! Enable KPP surface-boundary-layer scheme (future). integer :: interior_closure = VMIX_INTERIOR_PP81 !! Interior mixing closure tag. ! ---- PP81 constants ---- ! Default values from Pacanowski & Philander (1981) JPO 11:1443. real(wp) :: pp81_nu0 = 1.0e-2_wp !! Numerator viscosity at Ri=0 (m^2/s). real(wp) :: pp81_nu_bg = 1.0e-4_wp !! Background interior viscosity (m^2/s). real(wp) :: pp81_kappa_bg = 1.0e-5_wp !! Background tracer diffusivity (m^2/s). real(wp) :: pp81_alpha = 5.0_wp !! Richardson-number multiplier in (1 + α*Ri). Original !! paper uses 5; some implementations use 4 or 10. real(wp) :: rho0 = 1035.0_wp !! Boussinesq reference density (kg/m^3). Used in the !! N² = -g/ρ_0 * dρ/dz expression, in the friction velocity !! u_* = √(|τ|/ρ_0), and in the kinematic surface fluxes !! q_T = Q_heat/(ρ_0·cp) / q_S = Q_salt/ρ_0 that set the KPP !! surface buoyancy flux B_0. !! !! ASSIGNED FROM CONFIG by `configure_ocean_reference_density`, !! which copies the single rho0 of record (`&ocean_ic_nml rho_0` !! -> `eos%rho0`). The literal here is only the pre-configure !! type default. UNLIKE the other reference densities this one !! IS read on-device (`this%rho0` inside the `do concurrent` !! bodies of `vmix_compute_pp81` / `vmix_kpp_overlay_impl` / !! `vmix_convective_impl`), so under `mem:separate` it is only !! correct because the configure pass runs strictly BEFORE !! `ocean_state_enter_data`'s `copyin` — same as the `pp81_*` !! scalars beside it. A later write needs `!$acc update device`. real(wp) :: shear2_floor = 1.0e-10_wp !! Lower bound on |∂u/∂z|² to avoid Ri = N²/0 blow-up in !! quiescent water columns. ! ---- EOS handle for the KPP buoyancy flux ---- ! Shared flat-POD copy of `ocean_state%eos`, set once at ! configure. The KPP convective-velocity scale B_0 needs the ! surface α (thermal expansion) and β (haline contraction); ! it reads `eos%alpha_T` / `eos%beta_S` so the closure tracks ! the dyn-core EOS coefficients (previously a private pair that ! was NEVER refreshed from the eos slot — the latent staleness ! bug this centralization fixes). Maps onto the device with ! the parent `this`. Both are DIMENSIONAL (kg/m^3 per degC / ! psu) — see `kpp_surface_buoyancy_flux` for the `1/rho_0` that ! turns them into a buoyancy flux. type(eos_t) :: eos ! ---- Source of the α/β pair (E4, `&ocean_vmix_nml buoyancy_coeffs`) ---- ! The handle members above are the LINEAR EOS's true coefficients. ! Under a NONLINEAR EOS they are a constant stand-in for a strongly ! state-dependent pair, and `BUOY_COEFFS_EOS` replaces them with ! `eos_buoyancy_coeffs(eos, T, S, p)` evaluated where each consumer ! needs it. Default `BUOY_COEFFS_CONSTANT` ⇒ bit-identical; under ! `eos = "linear"` the two settings are byte-identical by ! construction (that branch of `eos_buoyancy_coeffs` returns the ! handle members themselves, no round-trip through ρ²·dSV). integer :: buoyancy_coeffs = BUOY_COEFFS_CONSTANT !! `BUOY_COEFFS_CONSTANT` | `BUOY_COEFFS_EOS`. Read ON-DEVICE !! (inside `vmix_kpp_overlay_impl`'s `do concurrent` bodies), so !! it rides the same configure-precedes-`enter_data` contract as !! `rho0` and the `pp81_*` scalars beside it. logical :: p_top_in_eos = .false. !! Mirror of `&ocean_psurf_nml in_eos` — whether !! `multilayer_state_t%p_top` carries a surface load that the !! in-situ EOS pressure must be measured down from. Seeded at !! configure from the same knob EPBL's `in_eos` takes, so the two !! boundary-layer schemes build their pressure stacks the same !! way. Only read when `buoyancy_coeffs == BUOY_COEFFS_EOS` !! (`p_top` is the zero array when the knob is off anyway — this !! is the belt-and-braces gate EPBL already carries). ! ---- KPP-specific ---- real(wp) :: ri_crit = 0.3_wp !! Critical bulk Richardson number for KPP BL depth. ! ---- KV_ML_INVZ2 surface-band viscosity (MOM6) ---- real(wp) :: kv_ml_invz2 = 0.0_wp !! Extra kinematic viscosity (m²/s) inside the surface band !! of thickness `hmix_fixed`. Profile is `(hmix_fixed / z)²` !! where z is the distance from the surface to the interface. !! Mirrors MOM6's KV_ML_INVZ2 + HMIX_FIXED combo — provides !! the dissipation that lets the wind drive thin surface !! layers without exciting a numerical eigenmode. Zero !! (default) means no addition; the kv field comes purely !! from PP81 / KPP. real(wp) :: hmix_fixed = 20.0_wp !! Surface-band thickness (m) over which `kv_ml_invz2` !! profile is active. MOM6 production default 20 m. real(wp) :: cs_nonlocal = 6.3_wp !! Non-local transport coefficient C_s (LMD94 eq 20). !! The full form `6.32·(1 - 0.5·exp(-σ/0.1))` is asymptotic; !! at σ > 0.1 it sits within 1% of the limit value 6.32, and !! the non-local flux is most important in that range. Using !! the limit value keeps the kernel branch-free and matches !! MOM6's `KPP_Cstar` default. real(wp) :: c_vt2 = 1.8_wp !! Unresolved-turbulence coefficient for the V_t² term in the !! bulk-Ri denominator (LMD94 eq 23). Folded form of !! `(C_v · √(-β_T) · √(c_s · ε)) / κ`. Default 1.8 matches !! the standard LMD94 tuning. Set to 0 to disable V_t² — !! recovers the shear-only bulk-Ri sweep bit-identically; !! useful as a discriminator in unit tests. !! `bl_depth(:, :)` from the previous step seeds w_s for V_t², !! so on the very first call (h_b_lagged = 0) the w_* !! contribution evaluates to zero and the algorithm !! self-bootstraps. integer :: kpp_sw_method = KPP_SW_MXL !! Shortwave-in-boundary-layer method for `B_0` (MOM6 !! `KPP_SHORTWAVE_METHOD`). Only bites when penetrating SW is !! active (`sf%has_sw`); inert at `sw_pen_frac = 0` ⇒ the !! default `mxl_sw` is bit-identical to the legacy path there. ! ---- Assembly stage: backgrounds / ceilings / smoothing / guard ---- ! The single downstream gate (`vmix_assemble`) that every interior ! and overlay closure feeds into before vdiff consumes kv/kt/ks. ! Defaults reproduce the pre-assembly numerics bit-for-bit: the ! backgrounds match the constant `pp81_*_bg` that PP81 already adds ! (so the floor is a no-op for the closure path), the ceilings are ! `huge` (no clip), smoothing is off, and the guard is off. real(wp) :: kv_bg = 1.0e-4_wp !! Background floor on momentum viscosity (m^2/s). Default !! matches `pp81_nu_bg` so the floor never raises a PP81 value !! (PP81 writes `pp81_nu_bg + nu0·factor ≥ pp81_nu_bg`). real(wp) :: kt_bg = 1.0e-5_wp !! Background floor on temperature diffusivity (m^2/s). !! Default matches `pp81_kappa_bg`. real(wp) :: ks_bg = 1.0e-5_wp !! Background floor on salinity diffusivity (m^2/s). !! Default matches `pp81_kappa_bg`. real(wp) :: kv_max = huge(1.0_wp) !! Ceiling on momentum viscosity (m^2/s). Default `huge` = !! no clip (bit-identical). MOM6 `Kd_max` momentum analogue. real(wp) :: kd_max = huge(1.0_wp) !! Ceiling on tracer diffusivity (kt, ks) (m^2/s). Default !! `huge` = no clip. MOM6 `Kd_max`. integer :: kd_smooth_iterations = 0 !! Number of 1-2-1 horizontal smoothing passes applied to !! kv/kt at each interior interface. Default 0 = off (no !! smoothing kernel runs, bit-identical). MOM6 `Kd_smooth`. logical :: vmix_guard = .false. !! Debug-gated negative/NaN guard. When `.true.` the assembly !! scans kv/kt/ks for a negative or NaN value and `error stop`s !! (or returns a non-zero status via the testable path). !! Default off (cheap reduction skipped) so production runs pay !! nothing. ! ---- C7 background mixing: Bryan-Lewis XOR Henyey ---- ! Two MUTUALLY EXCLUSIVE alternatives to the scalar background floor, ! matching the reference code's one-background-scheme rule: ! * `bkgnd_profile` — Bryan & Lewis (1979) depth profile, replacing ! the scalar floor with a per-interface `kd_bg` field. ! * `bkgnd_henyey` — Henyey (1986) latitude factor on the SCALAR ! `kt_bg`/`ks_bg`, floored at `bkgnd_kd_min`. ! Enabling both fails loud at configure. Both default off ⇒ the ! scalar path is used verbatim (bit-identical). ! ! When `bkgnd_profile` is on, the SCALAR `kt_bg`/`ks_bg` additive ! floor in `vmix_assemble` is replaced by a per-interface ! Bryan & Lewis (1979) JGR 84:2503 depth profile ! Kd_bg(z) = Kd_sfc + (Kd_deep - Kd_sfc)*(0.5 + atan((|z|-z0)/Delta)/PI) ! evaluated from the CURRENT column interface depths (cumulative ! h_layer from the surface down) so it is correct under any vcoord. ! Momentum follows MOM6: Kv_bg = bkgnd_prandtl * Kd_bg. Default off ! ⇒ the scalar background path is used verbatim (bit-identical). logical :: bkgnd_profile = .false. !! Master switch for the depth-varying Bryan-Lewis background. !! Default `.false.` ⇒ `vmix_assemble` uses the scalar !! kv_bg/kt_bg/ks_bg floor exactly as before (bit-identical). real(wp) :: bkgnd_kd_sfc = 1.0e-5_wp !! Surface-asymptote background tracer diffusivity (m^2/s). !! MOM6 BRYAN_LEWIS_C2-side default scale. real(wp) :: bkgnd_kd_deep = 1.3e-4_wp !! Deep-asymptote background tracer diffusivity (m^2/s). real(wp) :: bkgnd_z0 = 2500.0_wp !! Transition-centre depth (m, positive down) where the profile !! reaches the (sfc+deep)/2 midpoint. real(wp) :: bkgnd_delta = 222.0_wp !! Transition half-width (m): atan argument is (|z|-z0)/Delta. real(wp) :: bkgnd_prandtl = 1.0_wp !! Background Prandtl number — Kv_bg = bkgnd_prandtl * Kd_bg !! (MOM6 ties the background viscosity to the background tracer !! diffusivity through a Prandtl factor). Default 1.0. logical :: bkgnd_henyey = .false. !! Henyey, Wright & Flatte (1986) JGR 91:8487 latitude-dependent !! internal-wave factor, scaling the SCALAR background tracer !! diffusivities `kt_bg`/`ks_bg` by a horizontal-only factor !! `L(phi)` computed from `geolatT` (see `henyey_lat_factor_impl` !! for the exact form), with the result floored at `bkgnd_kd_min`: !! !! kt_floor(i,j) = max(bkgnd_kd_min, kt_bg * L(phi)) !! !! The implemented variant is the SIMPLIFIED one of Harrison & !! Hallberg (2008) JPO 38:1894, which assumes the in-situ !! stratification equals a constant reference `N0` rather than the !! evolving column N — so the factor depends only on latitude + !! `bkgnd_henyey_n0_2omega` / `bkgnd_henyey_max_lat` and needs no !! per-step recompute. !! !! MUTUALLY EXCLUSIVE with `bkgnd_profile` (Bryan-Lewis), matching !! the reference formulation, which selects ONE background scheme !! and FATALs when a second is requested. Enabling both fails loud !! at configure. This is also the cheaper arrangement: no !! per-interface `kd_bg` field is filled at all, the latitude !! factor is a per-column scalar folded straight into the existing !! floor/ceiling clip. !! !! Also requires a non-cartesian `grid_config` (validated fail-loud !! at configure): `geolatT` is identically zero on a cartesian grid, !! so every column would take the equatorial factor `L(0 deg) = 0` !! and the background would collapse to a uniform `bkgnd_kd_min` !! everywhere — a latitude parameterisation on a grid with no !! meaningful latitude. Default `.false.` ⇒ the scalar background !! floor path is used verbatim (bit-identical). !! !! SCOPE: the factor scales the two TRACER background floors only; !! the momentum floor `kv_bg` is left alone. Roundabout's scalar !! background path deliberately carries `kv_bg` as an INDEPENDENT !! momentum floor rather than `prandtl * kt_bg` (the shipped !! defaults 1e-4 / 1e-5 imply Pr = 10), so there is no single !! background `Kd` for the reference code's `Kv_bkgnd = !! PRANDTL_BKGND * Kd` tie to reproduce here. Henyey scales the !! diapycnal DIFFUSIVITY, which is what `kt_bg`/`ks_bg` are. real(wp) :: bkgnd_kd_min = -1.0_wp !! Minimum background tracer diffusivity (m^2/s) under the Henyey !! latitude scaling — MOM6 `KD_MIN`, applied as !! `max(Kd_min, Kd * L(phi))`. Without it `kd_bg` would collapse !! toward zero at the equator (`L(0 deg) = 0` exactly) and at the !! poleward `max_lat` clamp, instead of the documented behaviour !! "the Henyey profile is returned to the MINIMUM diffusivity". !! !! NEGATIVE = unset sentinel ⇒ resolved to !! `HENYEY_KD_MIN_FRAC * kt_bg` (MOM6's `0.01*KD` default) by !! `vmix_resolve_kd_min`, which `vmix_assemble` calls on every !! entry so a directly-constructed slot (unit tests) gets the same !! default as the configure path. Read only when `bkgnd_henyey` !! is on. real(wp) :: bkgnd_henyey_n0_2omega = 20.0_wp !! Ratio of the assumed reference buoyancy frequency `N0` to twice !! the planetary rotation rate (nondim). Physically !! `N0 >> 2*Omega` always, so this stays well above 1 !! (configure-time `min=1` guard keeps the internal `acosh` !! argument in-domain even under a misconfigured value). real(wp) :: bkgnd_henyey_max_lat = 95.0_wp !! Latitude (degN) poleward of which the factor is reset to its !! equatorial (near-zero) floor. Deliberately > 90 by default so !! the clamp is INERT for any real latitude out of the box; lower !! it to activate the optional poleward cutoff. ! ---- Convective adjustment (Brunt-Vaisala trigger, CVMix_conv-style) ---- ! `&ocean_conv_nml`. A CONTRIBUTOR that raises kv/kt via max() where ! the interior N^2 < n2_thresh, applied strictly below the active ! surface boundary layer. Default off => bit-identical. See the ! module docstring for the D1-D4 divergences from MOM6's ! MOM_CVMix_conv and the kd_max interaction. logical :: conv_enable = .false. !! Master switch. Requires `use_closure` + thermodynamics !! (validated at configure). Default off => bit-identical. real(wp) :: conv_kd = 1.0_wp !! Convective tracer diffusivity (m^2/s). MOM6 `KD_CONV` !! default 1.0 -- ~1e5x the background, admissible only because !! vdiff is an unconditionally-stable backward-Euler solve. real(wp) :: conv_prandtl = 1.0_wp !! Kv_conv = conv_prandtl * kd_conv. MOM6 `PRANDTL_CONV` !! default 1.0. real(wp) :: conv_n2_thresh = 0.0_wp !! Trigger threshold on N^2 (s^-2). MOM6 `BV_SQR_CONV` default !! 0.0. Strict `<` so an exactly-neutral interface (N^2 == 0) !! does not trigger. ! ---- Double diffusion (salt fingering + diffusive convection) ---- ! `&ocean_ddiff_nml`. NOT a kv/kt contributor -- it is folded INTO ! `vmix_split_kd_heat_salt` (a pre-split ks write would be clobbered ! by `ks := kt`), producing an ASYMMETRIC ks-vs-kt divergence: ! ks = kt_pre + kd_extra_s ; kt = kt_pre + kd_extra_t ! Interior interfaces only, branched on the SIGNED alpha*dT / beta*dS ! (never a pre-divided R_rho -- dodges R_rho<=0, ->inf, 0/0). The ! CVMix (Large et al. 1994 / Marmorino-Caldwell 1976 / Kelley 1990) ! closed forms; constants are the CVMix defaults. Default off => ! bit-identical. v1 uses the constant linear-EOS alpha_T/beta_S off ! `this%eos` (bitwise-exact for the linear EOS); nonlinear ! per-interface derivatives are a deferred refinement. logical :: ddiff_enable = .false. !! Master switch (MOM6 `USE_CVMIX_DDIFF`). Requires !! thermodynamics (validated at configure). Default off. real(wp) :: ddiff_strat_param_max = 2.55_wp !! R_rho salt-fingering cutoff (CVMix `STRAT_PARAM_MAX`). Above !! it fingering diffusivity is zero. real(wp) :: ddiff_kappa_s = 1.0e-4_wp !! Leading salt-fingering salinity diffusivity K_f (m^2/s, !! CVMix `KAPPA_DDIFF_S`). K_T = 0.7*K_S (0.7 hard-wired). real(wp) :: ddiff_exp1 = 1.0_wp !! Inner (bracket) exponent of the fingering clamped form !! (CVMix `DDIFF_EXP1`). real(wp) :: ddiff_exp2 = 3.0_wp !! Outer exponent of the fingering clamped form (CVMix !! `DDIFF_EXP2`); `exp1=1, exp2=3` is the Large et al. cubic. real(wp) :: ddiff_param1 = 0.909_wp !! MC76 diffusive-convection exterior coeff (CVMix !! `KAPPA_DDIFF_PARAM1`). real(wp) :: ddiff_param2 = 4.6_wp !! MC76 middle coeff (CVMix `KAPPA_DDIFF_PARAM2`). real(wp) :: ddiff_param3 = -0.54_wp !! MC76 interior coeff (CVMix `KAPPA_DDIFF_PARAM3`). real(wp) :: ddiff_mol_diff = 1.5e-6_wp !! Molecular diffusivity scaling the convection branch (m^2/s, !! CVMix `MOL_DIFF`) -- the *molecular* value, NOT a background !! eddy diffusivity. logical :: ddiff_use_k90 = .false. !! Diffusive-convection form: `.false.` = Marmorino-Caldwell 1976 !! (MC76, default), `.true.` = Kelley 1990 (K90). ! ---- Diagnostic / prognostic 2D fields (KPP) ---- real(wp), allocatable :: bl_depth(:, :) !! KPP boundary-layer depth (m, positive down). real(wp), allocatable :: b0(:, :) !! Surface buoyancy flux `B_0` (m^2/s^3) the KPP overlay's !! convective scale was built from, persisted per column on the !! SECOND pass (the one that uses the freshly-diagnosed !! `bl_depth`). Sign convention matches EPBL's `epbl%b0`: !! `> 0` stabilizing (heating / freshening), `< 0` destabilizing !! (cooling / salting), and `w_*^3 = max(0, -b0)*bl_depth`. !! Diagnostic only — nothing reads it back into the closure; it !! exists so the two boundary-layer schemes' surface forcing can !! be compared directly (`test_ocean_buoyancy_flux`). Zero until !! the first KPP overlay call. ! ---- 3D mixing coefficients on layer interfaces ---- ! Shape (nx, ny, nz_ml+1); k=1 at the bed, k=nz_ml+1 at the ! free surface. Consumed by the vertical-diffusion solve. real(wp), allocatable :: kv(:, :, :) !! Momentum (u, v) eddy viscosity at interfaces. real(wp), allocatable :: kt(:, :, :) !! Temperature eddy diffusivity. real(wp), allocatable :: ks(:, :, :) !! Salinity eddy diffusivity. ! ---- KPP non-local (counter-gradient) transport ---- ! Interface-located downward flux in tracer-units · m/s. Only ! non-zero inside the BL under destabilizing surface forcing ! (B_0 < 0). Divergence ∂γ/∂z appears as a source in the ! tracer-vdiff RHS. Allocated alongside `kv` / `kt` when ! `nz_ml` is supplied; same shape `(nx, ny, nz_ml + 1)`. real(wp), allocatable :: gamma_t(:, :, :) !! Non-local temperature flux (°C·m/s) at interfaces. real(wp), allocatable :: gamma_s(:, :, :) !! Non-local salinity flux (PSU·m/s) at interfaces. ! ---- Assembly smoothing scratch ---- ! Double-buffer for the 1-2-1 horizontal smoothing kernel — a ! 1-2-1 pass cannot run in place (neighbours must read pre-pass ! values). Allocated in `init` and mapped in `enter_data` ! alongside kv/kt so the smoothing kernel never lazy-attaches a ! component to an already-mapped parent (that collides with the ! present table). Only read when `kd_smooth_iterations > 0`. real(wp), allocatable :: smooth_scratch(:, :, :) !! Scratch (nx, ny, nz+1) for one smoothing pass. ! ---- C7 Bryan-Lewis background floor field ---- ! Per-interface tracer-background diffusivity (m^2/s), shape ! (nx, ny, nz+1). Filled by `vmix_bkgnd_fill` (called from ! `vmix_assemble`) each stage from the current column thicknesses ! when `bkgnd_profile` is on; otherwise never read. Allocated in ! `init` so the enter_data orchestrator maps it unconditionally ! (avoids a lazy device attach on an already-mapped parent). real(wp), allocatable :: kd_bg(:, :, :) !! Bryan-Lewis depth-profile tracer background (m^2/s) at !! interfaces. kv background floor = bkgnd_prandtl * kd_bg. contains procedure, non_overridable :: init => ocean_vmix_init procedure, non_overridable :: destroy => ocean_vmix_destroy procedure, non_overridable :: enter_data => ocean_vmix_enter_data procedure, non_overridable :: exit_data => ocean_vmix_exit_data procedure, non_overridable :: bytes => ocean_vmix_bytes procedure, non_overridable :: seed_backgrounds => vmix_seed_backgrounds end type ocean_vmix_t