Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Quasi-Geostrophic Models

Authors
Affiliations
University of Valencia

The quasi-geostrophic (QG) equations describe large-scale ocean and atmospheric flow where the Rossby number is small.

Barotropic QG

The barotropic QG equation for the streamfunction ψ\psi:

∂q∂t+J(ψ,q)=forcing+dissipation\frac{\partial q}{\partial t} + J(\psi, q) = \text{forcing} + \text{dissipation}

where q=∇2ψ+βyq = \nabla^2 \psi + \beta y is the potential vorticity and JJ is the Jacobian operator.

Multi-Layer QG

The multi-layer extension couples multiple fluid layers through the stretching term, representing baroclinic instability — the primary energy source for mesoscale ocean eddies.

Implementation in somax

somax uses a DST-based (Discrete Sine Transform) Poisson solver for the elliptic inversion ∇2ψ=q\nabla^2 \psi = q and the Arakawa Jacobian for the advection term.

Non-dimensional form

BarotropicQG.from_nondimensional builds the model from dimensionless numbers instead of SI coefficients. It uses the advective scale set (L=U=H=1L = U = H = 1, so T=L/U=1T = L/U = 1 and f0=1/Rof_0 = 1/Ro), and in those units the equation reads

∂tq+J(ψ, q+β^y)=δM3β^ ∇2q−δSβ^ ∇2ψ+τ^ curl τ.\partial_t q + J(\psi,\, q + \hat\beta y) = \delta_M^3 \hat\beta\, \nabla^2 q - \delta_S \hat\beta\, \nabla^2 \psi + \hat\tau\, \mathrm{curl}\,\tau .
InputDefinitionSets
rossbyRo=U/(f0L)Ro = U/(f_0 L)f0 = 1/Ro
beta_hatβ^=βL2/U\hat\beta = \beta L^2/Ubeta
delta_M(ν/β)1/3/L(\nu/\beta)^{1/3}/Llateral_viscosity
delta_Sκ/(βL)\kappa/(\beta L)bottom_drag
delta_I(U/β)1/2/L(U/\beta)^{1/2}/Lwind_amplitude
model, scales = BarotropicQG.from_nondimensional(
    nx=128,
    ny=128,
    rossby=0.02,
    beta_hat=50.0,
    delta_M=0.03,
    delta_S=0.01,
)

The point of specifying δM\delta_M and δS\delta_S rather than ν\nu and κ\kappa is that the western-boundary-layer widths are what has to be resolved. Picking them directly replaces tuning two coefficients until a run is both stable and resolved, and it lets the factory check the grid: the munk_width and stommel_width preflight assertions fail when δM/Δx<2\delta_M/\Delta x < 2 or δS/Δx<1\delta_S/\Delta x < 1, and warn just above those thresholds. Both guards compare two lengths taken from the same model, so they apply to dimensional runs too:

assertions:
  munk_width: {n_cells_min: 2.0}
  stommel_width: {n_cells_min: 1.0}

assertions is a flat {name: params} mapping — there is no preflight: level. Which phase a check runs in comes from the registry it is in, not from the config, and run_preflight treats every top-level key as an assertion name.

The wind amplitude follows the Sverdrup balance. Left unspecified it is τ^=β^\hat\tau = \hat\beta, the amplitude whose Sverdrup interior velocity is exactly the velocity scale UU; passing delta_I instead sets τ^=δI2β^2\hat\tau = \delta_I^2 \hat\beta^2.

The returned Scales is the unit scale set the model runs in — L=U=H=1L = U = H = 1 — so it is not by itself enough to convert a dimensional state: with L=U=1L = U = 1 the vorticity scale is 1 and a StateAffine.from_scales built from it would leave a dimensional qq untouched. Keep the physical scale set alongside it, the Scales.advective(L=..., U=...) describing the run being reproduced, and build the transform from that one. Times passed to the nondimensional model are in units of T=L/UT = L/U.

Layered QG

BaroclinicQG.from_nondimensional and ReparameterizedQG.from_nondimensional use the same advective set and add stratification, given as one Burger number per interface:

Buk=gk′Hk(f0L)2⟹gk′=Buk(f0L)2Hk.Bu_k = \frac{g'_k H_k}{(f_0 L)^2} \quad\Longrightarrow\quad g'_k = \frac{Bu_k (f_0 L)^2}{H_k}.
model, scales = BaroclinicQG.from_nondimensional(
    nx=128,
    ny=128,
    rossby=0.02,
    beta_hat=20.0,
    burger=[1.0, 0.02],
    thickness_ratio=[1.0, 4.0],
    delta_M=0.06,
)

The interface convention is not the per-mode one. The deformation radius of vertical mode mm comes from the eigenproblem and combines the interface values: for two layers the rigid-lid result is Ld2=g′H1H2/(f02(H1+H2))L_d^2 = g' H_1 H_2 / (f_0^2 (H_1+H_2)), and the free surface shifts it a few percent below that. Prescribing modal radii directly would mean inverting the eigenproblem, so the factory takes the interface numbers and the built model reports the resulting radii as model.modal.rossby_radii — already in units of LL, so directly comparable with Δx\Delta x. That is exactly what the deformation_radius guard reads, and from_nondimensional runs it alongside the Munk and Stommel guards.

The wind default is again Sverdrup-balanced, τ^=β^\hat\tau = \hat\beta. The right-hand side applies the wind as τ0F/H1\tau_0 F / H_1, so the dimensionless group is τ^=τ0L2/(U2H1)\hat\tau = \tau_0 L^2 / (U^2 H_1) and the factory multiplies by the top-layer thickness on the way in.