KPP Boundary Layer Mixing
This guide maps Omega KPP implementation details to runtime behavior and code locations. It complements the design page by focusing on concrete APIs, call flow, and developer test strategy.
Implementation Overview
The Omega implementation of KPP mostly follows directly from the MPAS-Ocean implementation.
Notably, by default it does not match diffusivity and viscosity from interior mixing
(below the ocean surface boundary layer) sources at the base of the boundary
layer. Instead these separate sources are added directly to the KPP diagnosed
diffusivity and viscosity. Matching can be enabled via the MatchTechnique parameter to
MatchBoth in the Omega yaml file. Boundary layer depth is computed as the depth
where the bulk Richardson number exceeds a critical value. It is then smoothed horizontally.
KPP diffusivity, viscosity, and non local fluxes are computed based on this boundary layer. Unlike MPAS-Ocean, the boundary layer depth, vertical vicosity and vertical diffusivity are calculated at the beginning of a time step to ensure consistency of the nonlocal and local parts of the KPP scheme.
Omega KPP is implemented in KPPMix as a singleton with two major compute
phases:
OSBL depth diagnosis (
computeOSBLDepth)Coefficient/profile construction (
computeMixingCoefficients)
Main class/API surface is in src/ocn/KPPMix.h and implementation is in
src/ocn/KPPMix.cpp.
Depth convention
All KPP depths are measured downward from the free surface, not from the geoid.
VertCoord::GeomZInterface and GeomZMid are geometric heights relative to
z = 0, with GeomZInterface(ICell, MinLayerCell) equal to
VertCoord::SshCell. KPP therefore forms depths as
SshCell(ICell) - GeomZ...(ICell, K). Layer thicknesses are differences of
geometric heights and are unaffected by the sea surface height.
KPP Notation
KPP splits the water column at the ocean surface boundary layer (OSBL) depth h. Inside the OSBL, diffusivity and viscosity are prescribed via a cubic shape function
scaled by h and a turbulent velocity scale; below it, only interior mixing
applies (e.g., shear instability driven mixing).
Symbol |
Code name |
Units |
Meaning |
|---|---|---|---|
|
|
m |
OSBL depth below the free surface |
|
|
m |
depth below the free surface |
|
|
- |
normalized depth in |
|
|
- |
|
|
|
m/s |
surface friction velocity from wind stress |
|
|
m^2/s^3 |
surface buoyancy flux; negative is destabilizing (convection) |
|
|
m |
Monin-Obukhov length, |
|
|
- |
stability coordinate |
|
|
- |
von Karman constant |
|
|
- |
surface layer as a fraction of |
|
|
m/s |
turbulent velocity scales for momentum and scalars |
|
|
m^2/s^2 |
unresolved turbulent shear, Large et al. (1994) Eq. 23 |
|
|
- |
bulk Richardson number, Eq. 21 |
|
|
- |
non-dimensional profile shapes |
|
|
- |
non-local (counter-gradient) tracer flux coefficient |
The OSBL depth is the shallowest d at which Ri_b(d) reaches the critical
value; the search is done per cell in computeOSBLDepth, and the crossing depth
is refined by a quadratic fit through the three nearest cell-center Ri_b
values.
Two properties of that search are worth knowing before editing it. The
surface-layer reference averages span the top epsilon*d, which grows
monotonically with the trial depth, so the running sums are carried across trial
depths in a two-pointer scan rather than rebuilt from the surface at each one;
resetting them inside the loop would make the search O(K^2). The loop also
stops at the crossing, since only the crossing level and the two above it feed
the refinement. DebugDiagnostics suppresses that early exit so the Ri_b
diagnostic profiles are filled over the whole column; it has no effect on
OSBLDepth or on any mixing coefficient.
Shape and stability functions
The non-dimensional functions live in src/ocn/KPPConstants.h in namespace
OMEGA::KPP. All shape functions take Sigma in [-1,0] and convert to
SigmaMu internally, so callers never flip signs:
kppShapeMomentum,kppShapeScalar– gradient shapes for viscosity and diffusivity (SimpleShapes)kppShapeMatched– gradient shape that additionally reaches a prescribed value at the OBL base, used byMatchBoththe non-local flux has no shape function of its own; it reuses whichever scalar shape is in effect, scaled by
C_sinstead ofh*w_skppPhiInvMomentum,kppPhiInvScalar– inverse Monin-Obukhov stability functionsphi^-1(zeta); they already return the reciprocal, so do not invert them again at the call site
Constants and defaults
src/ocn/KPPConstants.h is the single authoritative source for KPP default
values. The runtime-configurable members of KPPMix (SurfaceLayerExtent, the
two ice-fraction thresholds and MinimumOSBLUnderSeaIce) are initialized from
those constants rather than from inline literals, so a default is changed in
exactly one place.
Per-thread edge scratch arrays in computeOSBLDepth are sized from
HorzMesh::MaxEdgesBound, the shared compile-time bound on edges per cell;
KPP does not define its own maximum.
Runtime Call Flow
Tendency coupling
KPP coupling into tendencies occurs through:
KPPMix::computeKPPFields(...)
computeKPPFields(...) assembles required inputs:
the current tracer and normal-velocity state views
temperature and salinity tracer indices
the current EOS and surface forcing instances
the configured non-local tracer-flux policy
It then calls KPPMix::update(...). KPPMix owns preparation of its derived
state: EOS fields, surface-referenced potential density, edge tangential
velocity, surface friction velocity, surface buoyancy flux, and temporary ice
fraction. Temperature and salinity are zero-copy subviews of the canonical
tracer array, while surface tracer fluxes remain owned by the forcing state.
KPPMix::update(...) validates required non-local tracer fluxes and performs
the OSBL-depth and mixing-coefficient calculation.
KPPInstance->update(...)
Time stepper interaction
KPP is evaluated exactly once per time step by every Omega time stepper
(src/timeStepping/RungeKutta4Stepper.cpp,
src/timeStepping/RungeKutta2Stepper.cpp,
src/timeStepping/ForwardBackwardStepper.cpp) through two hooks in
src/timeStepping/TimeStepper.cpp:
Start-of-step compute:
TimeStepper::updateKPPFields(...)fetches the current tracer array and callsKPPMix::computeKPPFields(...). Each stepper calls it once at the top ofdoStep(), afterprescribeState/prescribeVelocityand before the first tendency evaluation:RungeKutta4Stepper: in the
Stage == 0branch, before the firstcomputeAllTendencies(...).RungeKutta2Stepper: after the initial
prescribeState(...).ForwardBackwardStepper: after
prescribeVelocity(...).
End-of-step application:
TimeStepper::applyImplicitVerticalMixing(...)is called by every stepper’sdoStep()immediately afterState->updateTimeLevels(). It recomputes auxiliary state and callsVertMix::VertMixImplicit(...)if enabled. It does not recompute KPP; the fields from the start of the step are reused.
The KPP fields are consumed in two places within the step:
Tendencies::computeTracerTendenciesOnly(...)adds the non-local tracer tendency fromKPPMix::VertNonLocalFluxat every stage.VertMix::computeVertMix(...), called fromVertMixImplicit(...), mergesKPPMix::VertDiff/VertViscinto the final coefficients usingKPPMix::OSBLDepthIndex.
Both therefore see the same boundary-layer depth and the same shape function, so the non-local flux and the diffusivity it is paired with cannot become inconsistent. KPP diagnostics written for a step describe the state at the beginning of that step.
Note that VertMix::VertMixImplicit(...) recomputes Brunt-Vaisala frequency
itself before calling computeVertMix(...), so shear and convective mixing
still use end-of-step stratification; only the KPP contribution is lagged.
Configuration Mapping
KPP reads configuration from the VertMix: KPP subgroup during KPPMix::init.
Important keys and class members:
Enable->EnabledCriticalBulkRichardsonNumber->CriticalRichardsonSurfaceLayerExtent->SurfaceLayerExtentMatchTechnique->MatchTechnique(aKPPMatchTypeenum, not a string)InterpType2->InterpType2StrUseEnhancedDiffusion->UseEnhancedDiffusionUseLangmuirTurbulence->UseLangmuirTurbulenceIceFractionThresholdForLangmuir->IceFractionThresholdForLangmuirIceFractionThresholdForMinimumOSBL->IceFractionThresholdForMinimumOSBLMinimumOSBLUnderSeaIce->MinimumOSBLUnderSeaIceDebugDiagnostics->DebugDiagnostics
See User KPP guide for defaults and runnable examples.
Output and Diagnostic Fields
KPPMix::defineFields() registers KPP outputs for I/O. Frequently used outputs:
OSBLDepthVertNonLocalFluxBulkRichardsonNumberBulkRichardsonShearUnresolvedShearBuoyancyJumpTurbulentVelocityScalePotentialDensitySurfaceFrictionVelocitySurfaceBuoyancyFlux
These can be enabled in output stream contents to diagnose OSBL and profile behavior in experiments.
Developer Notes
MatchTechniqueaccepts onlySimpleShapesandMatchBoth. Any other value aborts at init rather than falling back, so a typo cannot silently change the scheme being run.MatchBothmatches the interior coefficient value at the OBL base. The shape derivative there is zero, so the gradient is not yet matched despite the name.MatchBothneeds interior coefficients to be passed in; without themShapeAtBaseis zero and it degenerates exactly toSimpleShapes.Interptype2acceptsLMD94,Linear,Quadratic, andCubic, but onlyLMD94is recommended strongly recommended. ForMatchBothother options can result in negative diffusivities and viscosities.When
DebugDiagnosticsis enabled in debug builds, targeted diagnostic logging is available; behavior is compile/build-mode aware.
Testing Strategy
Code-level checks
Verify KPP initialization with explicit and default YAML keys.
Verify the
computeKPPFieldspath executes with KPP enabled and early-returns when disabled.Verify per-stepper sequencing: exactly one KPP evaluation per step for all three steppers, occurring before the first tendency evaluation and reused by the end-of-step implicit vertical mixing. The
KPP:computeKPPFieldsPacer region can be used to confirm the call count.
Diagnostics-based checks
Output
OSBLDepth,BulkRichardsonNumber, andVertDiff.Confirm OSBL depth and coefficient evolution under changing forcing.
Validate optional outputs (
VertNonLocalFlux) only when enabled.
Regression checks
Run CTests and appropriate OMEGA regression workflows documented in: Testing Code.