Skip to content

Mellor-Yamada Level-2.5 Turbulence Closure

NeuralPOM implements the Mellor-Yamada level-2.5 turbulence closure model (Mellor & Yamada, 1982) to parameterize vertical mixing. This second-moment closure scheme solves two prognostic equations for turbulence quantities, from which the vertical eddy viscosity KM and diffusivity KH are diagnostically determined.


Overview of the Mellor-Yamada Hierarchy

The Mellor-Yamada turbulence closure hierarchy classifies models by the level of complexity in the Reynolds stress equations:

LevelDescriptionPrognostic Variables
Level-2Full algebraic equilibrium; no transport of turbulenceNone
Level-2.5Turbulence kinetic energy and length scale prognosticq2, q2l
Level-3Adds temperature variance equationq2, q2l, θ2
Level-4Full Reynolds stress transportAll second moments

Level-2.5 provides a good balance between physical fidelity (it captures the evolution of turbulence in non-equilibrium conditions) and computational cost (only two additional prognostic equations).


Prognostic Equations

Turbulent Kinetic Energy (q2/2)

The quantity q2uiui is twice the turbulent kinetic energy (TKE). Its prognostic equation is:

q2t=z(Kqq2z)+2(Ps+Pbε)

Turbulence Length Scale Product (q2l)

The master length scale l is carried through the product q2l:

q2lt=z(Kqq2lz)+lE1(Ps+PbW~E1ε)

where:

SymbolDescription
q2Twice the turbulent kinetic energy (q22k)
lTurbulence master length scale
KqVertical diffusivity for turbulence quantities (Kq=qlSq)
PsShear production
PbBuoyancy production/destruction
εDissipation rate (ε=q3/(B1l))
W~Wall proximity function
E1Empirical constant

Production and Dissipation Terms

Shear Production Ps

Shear production represents the conversion of mean kinetic energy to turbulence by the Reynolds stresses working against the mean shear:

Ps=KM[(uz)2+(vz)2]

In the code (profq), the shear production is computed at internal interfaces:

python
for k in range(1, kbm1):
    du_sq = (u[s_i, s_j, k] - u[s_i, s_j, k-1] +
             u[s_ip1, s_j, k] - u[s_ip1, s_j, k-1])**2
    dv_sq = (v[s_i, s_j, k] - v[s_i, s_j, k-1] +
             v[s_i, s_jp1, k] - v[s_i, s_jp1, k-1])**2

    denom = (dzz[k-1] * dh[s_i, s_j])**2
    kn[s_i, s_j, k] = km[s_i, s_j, k] * 0.25 * sef * (du_sq + dv_sq) / denom

where sef = 1.0 is a scaling factor and dzz is the vertical layer spacing.

Buoyancy Production/Destruction Pb

Buoyancy effects can either produce turbulence (in unstable stratification) or destroy it (in stable stratification):

Pb=KHN2

where N is the Brunt-Vaisala frequency computed from density gradients:

N2=gρ0ρz

The implementation includes a sound speed correction:

python
for k in range(1, kbm1):
    drho = rho[:, :, k-1] - rho[:, :, k]
    term1 = grav * drho / (dzz[k-1] * dh)
    term2 = (grav**2) * 2.0 / (cc[:, :, k-1]**2 + cc[:, :, k]**2)
    boygr[:, :, k] = term1 + term2

Dissipation ε

The dissipation rate follows Kolmogorov scaling:

ε=q3B1l

where B1=16.6 is an empirical constant. The dissipation timescale is controlled by:

python
dtef = safe_sqrt(torch.abs(q2b)) * stf / (b1 * l + small)

Enhancing dissipation near boundaries:

εwall=ε(1+E2W~2)

Wall Proximity Function W~

The wall proximity function accounts for the suppression of the turbulence length scale near solid boundaries (both surface and bottom). It is defined as:

W~=lκ(1|zzs|+1|zzb|)1D

where:

SymbolDescription
κ=0.41von Karman constant
zsVertical position of the free surface
zbVertical position of the bottom
D=H+ηTotal water depth
lTurbulence length scale

This forces l0 as the boundaries are approached. In the code:

python
wall_term = (1.0 / torch.abs(z[k] - z_surf) + 1.0 / torch.abs(z[k] - z_bot)) * l[:, :, k] / (dh * kappa)

Stability Functions SM and SH

The stability functions are algebraic expressions derived from the level-2.5 closure assumptions. They depend on the Richardson number through the quantity GH:

GH=l2q2N2

The stability functions are:

SH=A2(16A1B1)1(3A2B2+18A1A2)GHSM=A1(13C16A1B1)+SH(18A12+9A1A2)GH19A1A2GH

These are computed in the code using the pre-defined coef1--coef5:

python
coef4 = 18.0 * a1 * a1 + 9.0 * a1 * a2
coef5 = 9.0 * a1 * a2
coef1 = a2 * (1.0 - 6.0 * a1 / b1)
coef2 = 3.0 * a2 * b2 + 18.0 * a1 * a2
coef3 = a1 * (1.0 - 3.0 * c1 - 6.0 * a1 / b1)

denom_sh = 1.0 - coef2 * gh
sh = coef1 / (denom_sh + 1e-20)

num_sm = coef3 + sh * coef4 * gh
denom_sm = 1.0 - coef5 * gh
sm = num_sm / (denom_sm + 1e-20)

The quantity gh is clamped to a maximum value of 0.028 to prevent numerical instability:

python
gh = torch.clamp(gh, max=0.028)

Vertical Eddy Viscosity and Diffusivity

The turbulence quantities determine the vertical mixing coefficients:

KM=qlSMKH=qlSHKq=0.41qlSH=0.2ql

(with Sq/SH=0.41, from the closure assumptions).

The final values are time-smoothed (averaged with previous values):

python
kn_final = l_out * safe_sqrt(torch.abs(q2_in))

kq_out = (kn_final * 0.41 * sh + kq_in) * 0.5
km_out = (kn_final * sm + km_in) * 0.5
kh_out = (kn_final * sh + kh_in) * 0.5

Boundary values of KM and KH are set by extrapolation from interior points, masked by the land-sea mask fsm.


Boundary Conditions

Surface Boundary Condition for q2

At the free surface, the TKE is proportional to the friction velocity u from wind stress:

q2|z=η=(15.8Cbc)2/3u2

where Cbc=100.0 is a boundary constant, and u is computed from the wind stress components:

python
gg_list[0][s_i, s_j] = (15.8 * cbcnst)**(2.0/3.0) * utau2

The surface friction velocity is:

u2=(τsxρ0)2+(τsyρ0)2

Surface Length Scale

The surface value of the length scale is constrained by a surface roughness length L0:

l0=surflu2g

where surfl = 2.0e5 is an empirical constant. The length scale in the upper layer is limited:

python
l0[s_i, s_j] = surfl * utau2 / grav
l_out[:, :, 0] = kappa * l0

Bottom Boundary Condition

At the bottom, the turbulence length scale goes to zero:

q2l|z=H=0

The bottom TKE is determined by bottom friction:

q2|z=H=B12/3|τb/ρ0|
python
uf_bottom_val[s_i, s_j] = safe_sqrt(u_bot_val**2 + v_bot_val**2) * const1

where const1 = (16.6**(2.0/3.0)) * sef.


Implementation: The advq and profq Subroutines

advq: Horizontal Advection and Diffusion

The advq subroutine advects and diffuses q2 and q2l in the horizontal. Since these are positive-definite fields, the advection scheme uses the same centered-in-space formulation as momentum, with a blend of two time levels for stability.

Key steps in advq:

  1. Horizontal advection at interior U/V points
  2. Horizontal diffusion using the aam (ambient horizontal viscosity) field
  3. Vertical advection term: (wq)z
  4. Time stepping: Leapfrog update with depth scaling

profq: Vertical Diffusion and Production

The profq subroutine is the core of the Mellor-Yamada 2.5 implementation. It performs a vertical implicit solve for q2 and q2l using the Thomas algorithm (tridiagonal solver), with complete autograd safety via list-based forward/backward sweeps.

The sequence in profq:

1. Compute diffusion coefficients (a/c matrices) for Kq
2. Calculate buoyancy frequency (N^2) at interfaces
3. Determine length scale l = |q^2l| / |q^2|
4. Apply wall proximity correction to l
5. Calculate shear production (P_s)
6. Forward sweep for q^2 (Thomas algorithm)
7. Backward sweep for q^2
8. Forward sweep for q^2l (with wall-enhanced dissipation)
9. Backward sweep for q^2l
10. Clip negative values to small positive floor
11. Compute stability functions S_M, S_H from gh
12. Update K_M, K_H, K_q

Empirical Constants

The standard Mellor-Yamada constants used in NeuralPOM are:

SymbolValueDescription
A1 (a1)0.92Turbulence closure constant
B1 (b1)16.6Turbulence closure constant
A2 (a2)0.74Turbulence closure constant
B2 (b2)10.1Turbulence closure constant
C1 (c1)0.08Turbulence closure constant
E1 (e1)1.8Wall proximity constant
E2 (e2)1.33Wall proximity constant
E3--(not directly used in level-2.5)
κ (kappa)0.4von Karman constant
Cbc (cbcnst)100.0Surface boundary constant
surfl2×105Surface length scale constant
sef1.0Shear energy factor

These constants are defined at the beginning of profq:

python
a1=0.92; b1=16.6; a2=0.74; b2=10.1; c1=0.08
e1=1.8; e2=1.33; sef=1.0
cbcnst=100.0; surfl=2.0e5; shiw=0.0

The G_H Limiter

To prevent unrealistically large mixing in strongly stratified conditions, the stability-related quantity GH is clipped:

GHlimit=0.028

This is equivalent to a critical gradient Richardson number of approximately Rig0.2--0.25, beyond which turbulence is strongly suppressed. The limit prevents the denominator in the stability functions from approaching zero (which would cause KM,KH in the original algebraic formulation).

python
gh[:, :, 1:kbm1] = (l_out[:, :, 1:kbm1]**2) * boygr[:, :, 1:kbm1] / (q2b_abs[:, :, 1:kbm1] + 1e-20)
gh = torch.clamp(gh, max=0.028)

Background Vertical Mixing

A molecular background viscosity umol is added to the turbulent coefficients to prevent zero mixing and maintain numerical stability:

KMtotal=KMturbulent+umolKHtotal=KHturbulent+umol

In the Thomas algorithm tridiagonal coefficients:

python
num = -dti2 * (km_u[s_i, s_j, k] + umol)

A small floor value small is enforced on q2 and q2l to prevent negative values that would cause sqrt errors in the stability function computation:

python
uf_sl_new = torch.where(mask_small, torch.tensor(small), uf_sl)
vf_sl_new = torch.where(mask_small, 0.1 * dt_sl * small, vf_sl)

References

  • Mellor, G. L., & Yamada, T. (1982). Development of a turbulence closure model for geophysical fluid problems. Reviews of Geophysics, 20(4), 851--875.
  • Mellor, G. L. (2004). Users guide for a three-dimensional, primitive equation, numerical ocean model. Princeton University.
  • Burchard, H., & Bolding, K. (2001). Comparative analysis of four second-moment turbulence closure models for the oceanic mixed layer. Journal of Physical Oceanography, 31(8), 1943--1968.

Released under the MIT License.