Home / Software / QG-Python

QG-Python

A reproducible 1.5-layer quasi-geostrophic model in Python: a direct sparse Helmholtz solver, RK4, Dormand-Prince and Leapfrog integrators under one API, and the diagnostics that tell you how stable and how predictable the flow is.

1.5-layer quasi-geostrophic model: potential vorticity and streamfunction on a 193 by 193 grid
QG-Python at mrefin = 7 (193 × 193). Potential vorticity q on the left, streamfunction ψ on the right, in the statistically stationary turbulent regime.

pip install qg-python

What it is

The 1.5-layer quasi-geostrophic model is the classic testbed for mesoscale ocean variability, Rossby waves, the inverse energy cascade and ensemble data assimilation. QG-Python is a faithful port of the QG-C model of Sakov (2024) in which the legacy Fortran multi-grid Helmholtz solver is replaced by a direct sparse LU factorization (SuperLU through SciPy). The streamfunction inversion is then exact to machine precision and deterministic, so the only numerical error left is the one introduced by the time integrator, which is exactly what the package is built to measure.

The integrators are exposed under a single API: the classical fourth-order Runge-Kutta scheme, the Dormand-Prince scheme, and the Leapfrog scheme with the Robert-Asselin (RA) or the Robert-Asselin-Williams (RAW) filter. Analysis modules compute the linearized operator, its leading eigenspectrum and the maximum Lyapunov exponent. The default configuration is the 97 × 97 grid of QG-C (mrefin = 6, 9,025 interior degrees of freedom), unchanged, so results are comparable with the body of prior work that uses QG-C as a dynamical core.

What the dataset says

  • The classical von Neumann bound on the Leapfrog time step is resolution-dependent: at mrefin = 6 it underestimates the true stability limit by a factor of 13.7, at mrefin = 5 it overestimates it by 1.54.
  • The maximum Lyapunov exponent of QG-C is σ1 = 0.03178, a Lyapunov time of 31.5 time units, beyond which trajectory-based comparison of schemes is meaningless and only statistics can be compared.
  • The RA filter reduces kinetic energy by about 21% and enstrophy by about 46% relative to RK4; the RAW filter brings both biases under 4% at the same cost, and is the recommended drop-in replacement for long integrations.

API

One module, qg_model, on top of NumPy and SciPy. A run is a parameter object, a model and one call.

from qg_model import QGModel, QGParams

prm = QGParams(mrefin=6,                 # 97 x 97
               scheme='leapfrog', dt=1.0, tend=110000, dtout=40,
               ra_alpha=0.1, raw_filter=True,
               outfname='qg_lf_raw.npz', save_q=True)

model = QGModel(prm)                     # factorizes the Helmholtz operator once
psi, q = model.run('restart', fname='qg_fourier.npz', record=-1)
CallWhat it gives you
QGParams(...)all parameters as a dataclass: grid (mrefin, 6 gives 97 × 97), physics (rkb, rkh, rkh2, f, r, a, k), time integration (scheme, dt, tend, dtout), Leapfrog filter (ra_alpha, raw_filter) and output (outfname, save_q). Defaults are those of QG-C.
QGParams.from_file("run.prm")the same parameters read from a QG-C style parameter file
QGModel(prm)builds the model: assembles the Helmholtz operator once and factorizes it (SuperLU)
model.run(ic, **kw)integrates from t = 0 to tend, saves a record every dtout to outfname (.npz, or .nc when netCDF4 is installed) and returns (psi, q)
ic = "zero" | "fourier" | "vortex" | "restart"initial condition: rest, a seeded Fourier perturbation (amplitude, kmax, seed), a vortex, or a record of a previous run (fname, record)
scheme = "rk4" | "dp5" | "leapfrog" | "order2" | "order1"the time integrator; with leapfrog, ra_alpha sets the filter strength and raw_filter=True switches from RA to RAW
model.set_ic(kind, **kw)set the state without running, for custom loops
model.psi, model.q, model.tthe current streamfunction, potential vorticity and time
qg-python --helpthe same model from the command line; this is what run_all.sh calls

Diagnostics

The stability and predictability analysis of the paper lives in the analysis/ scripts of the repository, which import the same model.

ScriptWhat it computes
analysis/jacobian_spectrum.pybuilds the sparse Jacobian of the right-hand side at an equilibrium state by finite differences and computes its leading eigenvalues with ARPACK; compares them with the von Neumann bounds
analysis/stability_regions.pydraws the stability regions of RK4, dp5 and Leapfrog in the z-plane and overlays the scaled eigenvalues
analysis/lyapunov.pythe Benettin algorithm: maximum Lyapunov exponent, its convergence trace and the Lyapunov time
analysis/uncertainty.pykinetic energy, enstrophy, energy spectra and KL divergences across schemes
analysis/plot_alpha_sensitivity.pythe filter sensitivity sweep over the RA strength α
qg_viewer.pyinteractive viewer of the .npz records (needs matplotlib)

Getting it

pip install qg-python            # the model: numpy and scipy only
pip install "qg-python[all]"     # plus matplotlib (viewer) and netCDF4 (.nc output)

The package installs the qg_model module and the qg-python command. The experiments of the paper, the analysis scripts and the reproducer live in the repository; the Zenodo archive is the authoritative citation target and also holds the simulation outputs (equilibrium restart fields at both resolutions, eigenvalue clouds, the Benettin trace and the time-resolved diagnostics of every run).

Reproducing the paper

One command regenerates every experiment: the spin-up from rest, the RK4 reference run, the LF + RA and LF + RAW runs, the α sensitivity sweep over {0.05, 0.10, 0.20, 0.40}, the eigenspectra at both resolutions, the Lyapunov estimate and all the figures. About 4 to 5 hours on one laptop core, no GPU, bitwise reproducible given the same NumPy, SciPy and ARPACK versions. Each step can also be run on its own.

git clone https://github.com/enino84/qg-leapfrog-stability
cd qg-leapfrog-stability
bash run_all.sh

Paper and how to cite

If you use QG-Python or the dataset, please cite the Data Services Article and the Zenodo record.

Nino-Ruiz, E. D. (2026). QG-Python: A reproducible 1.5-layer quasi-geostrophic modelling service with direct sparse solvers, spectral stability diagnostics and filter sensitivity data. Geoscience Data Journal, 13, e70107. doi:10.1002/gdj3.70107

Nino-Ruiz, E. D. (2026). QG-Python: a reproducible 1.5-layer quasi-geostrophic modelling service with spectral stability and filter sensitivity diagnostics (Version 1.0) [Software and dataset]. Zenodo. doi:10.5281/zenodo.22168803

@article{ninoruiz2026qgpython,
  title   = {QG-Python: A reproducible 1.5-layer quasi-geostrophic modelling service
             with direct sparse solvers, spectral stability diagnostics and filter
             sensitivity data},
  author  = {Nino-Ruiz, Elias D.},
  journal = {Geoscience Data Journal},
  volume  = {13},
  pages   = {e70107},
  year    = {2026},
  doi     = {10.1002/gdj3.70107}
}
Package
qg-python 1.0.0 on PyPI · module qg_model
Type
Model · Python · NumPy, SciPy (SuperLU, ARPACK)
Default grid
97 × 97 (mrefin = 6), 9,025 interior points
Integrators
RK4 · Dormand-Prince · Leapfrog + RA · Leapfrog + RAW
Diagnostics
Jacobian, eigenspectrum, Lyapunov exponent
Paper
Geoscience Data Journal 13, e70107 (2026)
Dataset
Zenodo 10.5281/zenodo.22168803
Author
Elias D. Nino-Ruiz
Code
github.com/enino84/qg-leapfrog-stability
License
MIT (code) · CC-BY 4.0 (data)

GitHub   PyPI   Paper   Zenodo