qg-python 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,41 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software or the accompanying dataset, please cite both the article and the archived release."
3
+ title: "QG-Python: a reproducible 1.5-layer quasi-geostrophic modelling service with spectral stability and filter sensitivity diagnostics"
4
+ version: "1.0"
5
+ date-released: 2026-08-30
6
+ type: software
7
+ license: MIT
8
+ doi: 10.5281/zenodo.22168803
9
+ url: "https://github.com/enino84/qg-leapfrog-stability"
10
+ repository-code: "https://github.com/enino84/qg-leapfrog-stability"
11
+ abstract: >-
12
+ Open, reproducible Python implementation of the 1.5-layer
13
+ quasi-geostrophic model originally distributed as QG-C, in which the
14
+ Fortran multigrid Helmholtz solver is replaced by a direct sparse LU
15
+ factorization. Exposes RK4, dp5 and Leapfrog with the Robert-Asselin
16
+ and Robert-Asselin-Williams filters under a single API, together with
17
+ diagnostic modules for the linearised operator, its eigenspectrum and
18
+ the maximum Lyapunov exponent.
19
+ keywords:
20
+ - quasi-geostrophic model
21
+ - Leapfrog scheme
22
+ - Robert-Asselin-Williams filter
23
+ - stability analysis
24
+ - Lyapunov exponent
25
+ - reproducible geoscience software
26
+ authors:
27
+ - family-names: "Nino-Ruiz"
28
+ given-names: "Elias D."
29
+ orcid: "https://orcid.org/0000-0001-7784-8163"
30
+ affiliation: "Applied Mathematics and Computer Science Laboratory, Department of Computer Science, Universidad del Norte, Barranquilla, Colombia"
31
+ email: "enino@uninorte.edu.co"
32
+ preferred-citation:
33
+ type: article
34
+ title: "QG-Python: A Reproducible 1.5-Layer Quasi-Geostrophic Modelling Service with Direct Sparse Solvers, Spectral Stability Diagnostics, and Filter Sensitivity Data"
35
+ journal: "Geoscience Data Journal"
36
+ year: 2026
37
+ doi: 10.1002/gdj3.70107
38
+ authors:
39
+ - family-names: "Nino-Ruiz"
40
+ given-names: "Elias D."
41
+ orcid: "https://orcid.org/0000-0001-7784-8163"
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ include LICENSE README.md CITATION.cff requirements.txt
2
+ prune paper
3
+ prune figures
4
+ prune data
5
+ prune experiments
6
+ prune analysis
@@ -0,0 +1,358 @@
1
+ Metadata-Version: 2.4
2
+ Name: qg-python
3
+ Version: 1.0.0
4
+ Summary: Reproducible 1.5-layer quasi-geostrophic model (port of QG-C) with a direct sparse Helmholtz solver, RK4 / dp5 / Leapfrog (RA, RAW) integrators and stability diagnostics
5
+ Author-email: "Elias D. Nino-Ruiz" <enino@uninorte.edu.co>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://aml-cs.github.io/software/qg-python/
8
+ Project-URL: Repository, https://github.com/enino84/qg-leapfrog-stability
9
+ Project-URL: Paper, https://doi.org/10.1002/gdj3.70107
10
+ Project-URL: Dataset, https://doi.org/10.5281/zenodo.22168803
11
+ Keywords: quasi-geostrophic,data assimilation,leapfrog,robert-asselin-williams,geophysical fluid dynamics
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
15
+ Classifier: Operating System :: OS Independent
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: numpy>=1.24
20
+ Requires-Dist: scipy>=1.10
21
+ Provides-Extra: plots
22
+ Requires-Dist: matplotlib>=3.7; extra == "plots"
23
+ Provides-Extra: netcdf
24
+ Requires-Dist: netCDF4; extra == "netcdf"
25
+ Provides-Extra: all
26
+ Requires-Dist: matplotlib>=3.7; extra == "all"
27
+ Requires-Dist: netCDF4; extra == "all"
28
+ Dynamic: license-file
29
+
30
+ # qg-leapfrog-stability
31
+
32
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22168803.svg)](https://doi.org/10.5281/zenodo.22168803)
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
34
+
35
+ **QG-Python: a reproducible 1.5-layer quasi-geostrophic modelling service
36
+ with spectral stability and filter sensitivity diagnostics**
37
+
38
+ This repository contains the QG-Python model, the analysis scripts, and the
39
+ shell drivers associated with the Data Services Article published in
40
+ *Geoscience Data Journal* (Royal Meteorological Society / Wiley):
41
+
42
+ > Nino-Ruiz, E. D. (2026). **QG-Python: A Reproducible 1.5-Layer
43
+ > Quasi-Geostrophic Modelling Service with Direct Sparse Solvers, Spectral
44
+ > Stability Diagnostics, and Filter Sensitivity Data**.
45
+ > *Geoscience Data Journal*. doi:[10.1002/gdj3.70107](https://doi.org/10.1002/gdj3.70107)
46
+
47
+ The archived snapshot of this repository **together with the simulation
48
+ outputs** is the citable version of record:
49
+ doi:[10.5281/zenodo.22168803](https://doi.org/10.5281/zenodo.22168803)
50
+
51
+ ---
52
+
53
+ ## Install
54
+
55
+ ```bash
56
+ pip install qg-python # model only (numpy, scipy)
57
+ pip install "qg-python[all]" # + matplotlib and netCDF4 for the viewer and .nc output
58
+ ```
59
+
60
+ ```python
61
+ from qg_model import QGModel, QGParams
62
+
63
+ prm = QGParams(mrefin=6, tend=1000, dtout=20, scheme='leapfrog',
64
+ ra_alpha=0.1, raw_filter=True, outfname='qg_run.npz')
65
+ psi, q = QGModel(prm).run('fourier', seed=42)
66
+ ```
67
+
68
+ The same model is available on the command line as `qg-python` (run `qg-python --help`).
69
+ To reproduce the experiments of the paper, clone this repository and run `bash run_all.sh`.
70
+
71
+ ---
72
+
73
+ ## Overview
74
+
75
+ We implement the Leapfrog scheme with Robert-Asselin (RA) and
76
+ Robert-Asselin-Williams (RAW) filters in a 1.5-layer quasi-geostrophic (QG)
77
+ model, and carry out a systematic numerical analysis covering:
78
+
79
+ - Replacement of the original Fortran multigrid Helmholtz solver with a direct
80
+ sparse LU factorization (SuperLU via SciPy), enabling unambiguous stability analysis
81
+ - Eigenspectrum of the linearized operator $J_\mathcal{F}(q_\mathrm{eq})$ at two
82
+ grid resolutions (49×49 and 97×97), revealing resolution-dependent deviation
83
+ from the classical von Neumann bound
84
+ - Maximum stable time step $\Delta t_{\max}$ as a function of grid spacing $h$
85
+ - First estimate of the maximum Lyapunov exponent for this model
86
+ ($\sigma_1 = 0.0318$, $\tau_L = 31.5$ time units)
87
+ - Statistical uncertainty propagation: kinetic energy and enstrophy PDFs,
88
+ energy spectra, and KL divergences across schemes
89
+ - Sensitivity of filter-induced dissipation to the RA parameter
90
+ $\alpha \in \{0.05, 0.10, 0.20, 0.40\}$
91
+
92
+ The QG model is a faithful Python port of the QG-C model by
93
+ [Pavel Sakov (Bureau of Meteorology)](https://github.com/sakov/qg-c),
94
+ extended with the Leapfrog family of integrators.
95
+
96
+ ---
97
+
98
+ ## Repository structure
99
+
100
+ ```
101
+ qg-leapfrog-stability/
102
+ │
103
+ ├── qg_model.py # QG model — all time-stepping schemes
104
+ ├── qg_viewer.py # Interactive field viewer (.npz files)
105
+ ├── run_all.sh # Single script to reproduce all results
106
+ │
107
+ ├── analysis/
108
+ │ ├── plot_style.py # Shared publication style settings
109
+ │ ├── jacobian_spectrum.py # Build J_F and compute eigenspectrum
110
+ │ ├── stability_regions.py # Plot eigenspectrum + stability regions
111
+ │ ├── lyapunov.py # Benettin algorithm for Lyapunov exponent
112
+ │ ├── uncertainty.py # PDFs, power spectra, KL divergence
113
+ │ ├── plot_fields.py # Spatial snapshots of streamfunction
114
+ │ └── plot_alpha_sensitivity.py # RA filter sensitivity figures
115
+ │
116
+ ├── experiments/
117
+ │ ├── run_reference.sh # Spin-up + RK4 reference run
118
+ │ ├── run_leapfrog_long.sh # LF+RA and LF+RAW main runs
119
+ │ ├── run_leapfrog_sweep.sh # RA filter alpha sweep
120
+ │ └── run_analysis.sh # Eigenspectrum + Lyapunov diagnostics
121
+ │
122
+ ├── data/
123
+ │ ├── README.md # Exact command line behind every run
124
+ │ ├── blowup_times.txt # dt=1.0 instability onset, LF+RA / LF+RAW
125
+ │ ├── lyapunov_results.txt # Benettin MLE summary
126
+ │ └── uncertainty_results.txt # KE / enstrophy / KL divergence summary
127
+ │
128
+ ├── paper/
129
+ │ └── README.md # Pointer to the published article
130
+ │
131
+ ├── figures/ # Generated locally — not tracked by git
132
+ ├── requirements.txt
133
+ ├── CITATION.cff
134
+ ├── LICENSE
135
+ └── README.md
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Dependencies
141
+
142
+ ```
143
+ numpy >= 1.24
144
+ scipy >= 1.10
145
+ matplotlib >= 3.7
146
+ ```
147
+
148
+ Install with:
149
+
150
+ ```bash
151
+ pip install -r requirements.txt
152
+ ```
153
+
154
+ No additional compiled extensions are required. The Helmholtz solver uses
155
+ `scipy.sparse.linalg.factorized` (SuperLU backend).
156
+
157
+ ---
158
+
159
+ ## Reproducing the results
160
+
161
+ All steps are collected in a single script. Run from the root of the repository:
162
+
163
+ ```bash
164
+ bash run_all.sh
165
+ ```
166
+
167
+ The script executes the following steps in order:
168
+
169
+ | Step | Description | Runtime (approx.) |
170
+ |------|-------------|-------------------|
171
+ | 1 | Spin-up from rest (mrefin=6, tend=100000, RK4, dt=1) | ~45 min |
172
+ | 2 | RK4 reference run (dt=1, t=100000→110000) | ~25 min |
173
+ | 3 | LF+RA main run (α=0.10, dt=0.5) | ~12 min |
174
+ | 4 | LF+RAW main run (α=0.10, dt=0.5) | ~12 min |
175
+ | 5 | Alpha sensitivity sweep (α=0.05, 0.20, 0.40) | ~36 min |
176
+ | 6 | Jacobian eigenspectrum, mrefin=6 (97×97) | ~2 min |
177
+ | 7a | Spin-up from rest, mrefin=5 (dt=0.1) | ~45 min |
178
+ | 7b | Jacobian eigenspectrum, mrefin=5 (49×49) | ~1 min |
179
+ | 8 | Lyapunov exponent (Benettin, 50000 steps) | ~90 min |
180
+ | 9 | All figures | ~1 min |
181
+
182
+ Total runtime on a modern laptop: approximately 4–5 hours.
183
+
184
+ > **Note:** The mrefin=5 spin-up (Step 7a) requires `dt=0.1` — larger time steps
185
+ > cause blow-up during spin-up at this resolution.
186
+
187
+ ### Running individual steps
188
+
189
+ The pipeline is also split into four self-contained drivers under
190
+ `experiments/`, which can be run in isolation for partial reproduction:
191
+
192
+ ```bash
193
+ bash experiments/run_reference.sh # spin-up + RK4 reference
194
+ bash experiments/run_leapfrog_long.sh # LF+RA and LF+RAW main runs
195
+ bash experiments/run_leapfrog_sweep.sh # alpha sensitivity sweep
196
+ bash experiments/run_analysis.sh # eigenspectrum + Lyapunov
197
+ ```
198
+
199
+ Each command in `run_all.sh` is likewise self-contained. For example, to
200
+ recompute only the eigenspectrum:
201
+
202
+ ```bash
203
+ python analysis/jacobian_spectrum.py --restart qg_zero.npz --neigs 200 --no-matrix --out jacobian_spectrum.npz
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Key results
209
+
210
+ | Quantity | Value |
211
+ |----------|-------|
212
+ | Grid (main experiments) | 97×97 (mrefin=6), $h=0.01042$ |
213
+ | Analytical $\Delta t_{\max}^{LF}$ (mrefin=6) | 0.090 |
214
+ | Numerical $\Delta t_{\max}^{LF}$ (mrefin=6) | 1.236 (factor **13.7×** larger) |
215
+ | Analytical $\Delta t_{\max}^{LF}$ (mrefin=5) | 0.362 |
216
+ | Numerical $\Delta t_{\max}^{LF}$ (mrefin=5) | 0.235 (factor **1.54×** smaller) |
217
+ | Maximum Lyapunov exponent $\sigma_1$ | 0.0318 |
218
+ | Lyapunov time $\tau_L$ | 31.5 time units |
219
+ | LF+RA kinetic energy bias (α=0.10) | −21% |
220
+ | LF+RA enstrophy bias (α=0.10) | −46% |
221
+ | LF+RAW kinetic energy bias | −2% |
222
+ | LF+RAW enstrophy bias | +4% |
223
+
224
+ ---
225
+
226
+ ## Time-stepping schemes
227
+
228
+ | Scheme | Order | RHS evals / step | Notes |
229
+ |--------|-------|-------------------|-------|
230
+ | `order1` | 1 | 1 | Explicit Euler |
231
+ | `order2` | 2 | 2 | Midpoint RK |
232
+ | `rk4` | 4 | 4 | Default in QG-C |
233
+ | `dp5` | 5 | 6 | Dormand-Prince |
234
+ | `leapfrog` | 2 | 1 | + RA or RAW filter |
235
+
236
+ The first Leapfrog step uses a single RK4 step as bootstrap to avoid
237
+ exciting the computational mode from the start.
238
+
239
+ ### Leapfrog filter options
240
+
241
+ ```bash
242
+ # Robert-Asselin filter (classic, Asselin 1972)
243
+ python qg_model.py --scheme leapfrog --ra-alpha 0.1 ...
244
+
245
+ # Robert-Asselin-Williams filter (less dissipative, Williams 2009)
246
+ python qg_model.py --scheme leapfrog --ra-alpha 0.1 --raw ...
247
+ ```
248
+
249
+ ---
250
+
251
+ ## Data availability
252
+
253
+ The simulation output files (`.npz`) are several hundred megabytes and are
254
+ therefore not tracked by git. They are archived, together with a snapshot of
255
+ this repository, on Zenodo:
256
+
257
+ > **DOI**: [10.5281/zenodo.22168803](https://doi.org/10.5281/zenodo.22168803)
258
+ > **Record**: <https://doi.org/10.5281/zenodo.22168803>
259
+ > **Development version**: <https://github.com/enino84/qg-leapfrog-stability>
260
+
261
+ The Zenodo record is the authoritative citation target: it holds both the
262
+ tagged source snapshot (`qg-python-v1.0.zip`) and the simulation outputs. This
263
+ GitHub repository is the development mirror and tracks the source only.
264
+
265
+ The archive contains the equilibrium restart fields at `t = 100000` for both
266
+ resolutions, the eigenvalue clouds of the linearised operator, the Benettin
267
+ trace of the maximum Lyapunov exponent, and the time-resolved kinetic energy,
268
+ enstrophy and isotropic spectra for RK4, LF+RA and LF+RAW across
269
+ alpha in {0.05, 0.10, 0.20, 0.40}. `data/README.md` documents the exact
270
+ command line behind each file, so every output can also be regenerated from
271
+ scratch.
272
+
273
+ ---
274
+
275
+ ## Reference model
276
+
277
+ This work builds on:
278
+
279
+ > Sakov, P. (2024). *QG-C: A quasi-geostrophic model in C*.
280
+ > Bureau of Meteorology, Australia.
281
+ > https://github.com/sakov/qg-c
282
+
283
+ Key numerical methods:
284
+
285
+ > Arakawa, A. (1966). Computational design for long-term numerical integration
286
+ > of the equations of fluid motion.
287
+ > *Journal of Computational Physics*, 1(1), 119–143.
288
+
289
+ > Asselin, R. (1972). Frequency filter for time integrations.
290
+ > *Monthly Weather Review*, 100(6), 487–490.
291
+
292
+ > Williams, P. D. (2009). A proposed modification to the Robert-Asselin
293
+ > time filter. *Monthly Weather Review*, 137(8), 2538–2546.
294
+
295
+ > Li, X. S., & Demmel, J. W. (2003). SuperLU_DIST: A scalable distributed-memory
296
+ > sparse direct solver for unsymmetric linear systems.
297
+ > *ACM Transactions on Mathematical Software*, 29(2), 110–140.
298
+
299
+ ---
300
+
301
+ ## License
302
+
303
+ The source code in this repository is released under the MIT License — see
304
+ [LICENSE](LICENSE). The simulation output files distributed through the Zenodo
305
+ archive are released under
306
+ [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/).
307
+
308
+ ---
309
+
310
+ ## Citation
311
+
312
+ Please cite both the article and the archived software release. Machine-readable
313
+ metadata is in [CITATION.cff](CITATION.cff).
314
+
315
+ ```bibtex
316
+ @article{NinoRuiz2026qgpython,
317
+ author = {Nino-Ruiz, Elias D.},
318
+ title = {{QG-Python}: A Reproducible 1.5-Layer Quasi-Geostrophic Modelling
319
+ Service with Direct Sparse Solvers, Spectral Stability Diagnostics,
320
+ and Filter Sensitivity Data},
321
+ journal = {Geoscience Data Journal},
322
+ year = {2026},
323
+ doi = {10.1002/gdj3.70107}
324
+ }
325
+
326
+ @software{NinoRuiz2026qgpythonsoftware,
327
+ author = {Nino-Ruiz, Elias D.},
328
+ title = {{QG-Python}: a reproducible 1.5-layer quasi-geostrophic modelling
329
+ service with spectral stability and filter sensitivity diagnostics},
330
+ version = {1.0},
331
+ year = {2026},
332
+ publisher = {Zenodo},
333
+ doi = {10.5281/zenodo.22168803}
334
+ }
335
+ ```
336
+
337
+ ---
338
+
339
+ ## A note on the enstrophy diagnostic
340
+
341
+ Enstrophy is reported throughout as the domain average of
342
+ `(nabla^2 psi)^2`, evaluated with the standard 5-point Laplacian on the
343
+ **interior nodes only**. This is the definition in
344
+ `analysis/uncertainty.py::enstrophy`, and it is the one every other script
345
+ imports.
346
+
347
+ The wall ring must be excluded from the average. `qg_model.py` imposes
348
+ homogeneous Dirichlet conditions `psi = 0` on all four walls, so at a wall node
349
+ the 5-point stencil collapses to `psi_adjacent / h^2`; with `h ~ 0.01` that is
350
+ `O(1e5)`, and squaring it lets a one-node ring — about 4% of the points —
351
+ dominate the domain average and inflate its variance by considerably more,
352
+ while leaving the *relative* biases between time-stepping schemes almost
353
+ unchanged.
354
+
355
+ `analysis/make_tables.py` regenerates the LaTeX rows of both tables in the
356
+ article directly from the `.npz` runs, so the statistical summary and the alpha
357
+ sweep cannot drift apart. Pass `--compare` to also print the wall-inclusive
358
+ average.