pvtend 0.7.1__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.
Files changed (50) hide show
  1. pvtend-0.7.1/LICENSE +21 -0
  2. pvtend-0.7.1/PKG-INFO +304 -0
  3. pvtend-0.7.1/README.md +240 -0
  4. pvtend-0.7.1/pyproject.toml +56 -0
  5. pvtend-0.7.1/setup.cfg +4 -0
  6. pvtend-0.7.1/src/pvtend/__init__.py +123 -0
  7. pvtend-0.7.1/src/pvtend/_version.py +1 -0
  8. pvtend-0.7.1/src/pvtend/classify.py +425 -0
  9. pvtend-0.7.1/src/pvtend/cli.py +361 -0
  10. pvtend-0.7.1/src/pvtend/climatology.py +249 -0
  11. pvtend-0.7.1/src/pvtend/composite_builder.py +381 -0
  12. pvtend-0.7.1/src/pvtend/composites.py +225 -0
  13. pvtend-0.7.1/src/pvtend/constants.py +47 -0
  14. pvtend-0.7.1/src/pvtend/data/__init__.py +54 -0
  15. pvtend-0.7.1/src/pvtend/data/idealized_pv.npz +0 -0
  16. pvtend-0.7.1/src/pvtend/decomposition/__init__.py +33 -0
  17. pvtend-0.7.1/src/pvtend/decomposition/basis.py +255 -0
  18. pvtend-0.7.1/src/pvtend/decomposition/projection.py +191 -0
  19. pvtend-0.7.1/src/pvtend/decomposition/smoothing.py +97 -0
  20. pvtend-0.7.1/src/pvtend/derivatives.py +267 -0
  21. pvtend-0.7.1/src/pvtend/grid.py +251 -0
  22. pvtend-0.7.1/src/pvtend/helmholtz.py +514 -0
  23. pvtend-0.7.1/src/pvtend/io/__init__.py +20 -0
  24. pvtend-0.7.1/src/pvtend/io/era5.py +120 -0
  25. pvtend-0.7.1/src/pvtend/io/npz.py +66 -0
  26. pvtend-0.7.1/src/pvtend/io/pkl.py +72 -0
  27. pvtend-0.7.1/src/pvtend/isentropic.py +494 -0
  28. pvtend-0.7.1/src/pvtend/moist_dry.py +157 -0
  29. pvtend-0.7.1/src/pvtend/omega.py +1072 -0
  30. pvtend-0.7.1/src/pvtend/plotting/__init__.py +30 -0
  31. pvtend-0.7.1/src/pvtend/plotting/baroclinic.py +207 -0
  32. pvtend-0.7.1/src/pvtend/plotting/basis_plots.py +108 -0
  33. pvtend-0.7.1/src/pvtend/plotting/coefficient_plots.py +116 -0
  34. pvtend-0.7.1/src/pvtend/plotting/composite_explorer.py +385 -0
  35. pvtend-0.7.1/src/pvtend/plotting/field_plots.py +115 -0
  36. pvtend-0.7.1/src/pvtend/preprocessing.py +159 -0
  37. pvtend-0.7.1/src/pvtend/rwb.py +777 -0
  38. pvtend-0.7.1/src/pvtend/tendency.py +1293 -0
  39. pvtend-0.7.1/src/pvtend.egg-info/PKG-INFO +304 -0
  40. pvtend-0.7.1/src/pvtend.egg-info/SOURCES.txt +48 -0
  41. pvtend-0.7.1/src/pvtend.egg-info/dependency_links.txt +1 -0
  42. pvtend-0.7.1/src/pvtend.egg-info/entry_points.txt +2 -0
  43. pvtend-0.7.1/src/pvtend.egg-info/requires.txt +30 -0
  44. pvtend-0.7.1/src/pvtend.egg-info/top_level.txt +1 -0
  45. pvtend-0.7.1/tests/test_classify.py +276 -0
  46. pvtend-0.7.1/tests/test_cli.py +152 -0
  47. pvtend-0.7.1/tests/test_composite_builder.py +238 -0
  48. pvtend-0.7.1/tests/test_derivatives.py +86 -0
  49. pvtend-0.7.1/tests/test_helmholtz.py +92 -0
  50. pvtend-0.7.1/tests/test_tendency.py +107 -0
pvtend-0.7.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Xingjian Yan
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.
pvtend-0.7.1/PKG-INFO ADDED
@@ -0,0 +1,304 @@
1
+ Metadata-Version: 2.4
2
+ Name: pvtend
3
+ Version: 0.7.1
4
+ Summary: PV tendency decomposition diagnostics for blocking and weather extremes
5
+ Author: Xingjian Yan
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Xingjian Yan
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Documentation, https://pvtend.readthedocs.io/
29
+ Project-URL: Repository, https://github.com/yanxingjianken/pvtend
30
+ Project-URL: Bug Tracker, https://github.com/yanxingjianken/pvtend/issues
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Programming Language :: Python :: 3
33
+ Classifier: Intended Audience :: Science/Research
34
+ Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
35
+ Requires-Python: >=3.10
36
+ Description-Content-Type: text/markdown
37
+ License-File: LICENSE
38
+ Requires-Dist: numpy>=1.22
39
+ Requires-Dist: scipy
40
+ Requires-Dist: xarray
41
+ Requires-Dist: pandas
42
+ Requires-Dist: netCDF4
43
+ Requires-Dist: tqdm
44
+ Requires-Dist: matplotlib
45
+ Requires-Dist: scikit-image
46
+ Requires-Dist: cdsapi
47
+ Provides-Extra: maps
48
+ Requires-Dist: cartopy; extra == "maps"
49
+ Provides-Extra: sip
50
+ Requires-Dist: numba; extra == "sip"
51
+ Provides-Extra: test
52
+ Requires-Dist: pytest; extra == "test"
53
+ Requires-Dist: dask; extra == "test"
54
+ Provides-Extra: docs
55
+ Requires-Dist: sphinx; extra == "docs"
56
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
57
+ Requires-Dist: nbsphinx; extra == "docs"
58
+ Requires-Dist: ipykernel; extra == "docs"
59
+ Provides-Extra: dev
60
+ Requires-Dist: pvtend[docs,sip,test]; extra == "dev"
61
+ Requires-Dist: pre-commit; extra == "dev"
62
+ Requires-Dist: ruff; extra == "dev"
63
+ Dynamic: license-file
64
+
65
+ # pvtend
66
+
67
+ [![Tests](https://github.com/yanxingjianken/pvtend/actions/workflows/test.yml/badge.svg)](https://github.com/yanxingjianken/pvtend/actions)
68
+ [![Documentation](https://readthedocs.org/projects/pvtend/badge/?version=latest)](https://pvtend.readthedocs.io/en/latest/)
69
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
70
+
71
+ **PV tendency decomposition for atmospheric blocking, propagating anticyclones, and all synoptic-scale cyclonic event lifecycle analysis.**
72
+
73
+ `pvtend` diagnoses the growth, propagation, and decay of mid-latitude weather events by decomposing potential vorticity (PV) tendencies from ERA5 pressure-level data onto physically meaningful components using an orthogonal basis framework. This is the Part I work of Yan et al. (in prep.) about blocking lifecycle analyses on onset, peak, decay stages.
74
+
75
+ ## Gallery
76
+
77
+ <table>
78
+ <tr>
79
+ <td width="50%" align="center">
80
+ <img src="docs/_static/reconstruction_demo.png" alt="Idealized four-basis reconstruction" width="100%"/><br/>
81
+ <em>Idealized validation — a Gaussian PV anomaly with prescribed propagation,
82
+ intensification, and deformation is decomposed into four orthogonal bases
83
+ and reconstructed with near-zero residual.</em>
84
+ </td>
85
+ <td width="50%" align="center">
86
+ <img src="docs/_static/lifecycle_demo.gif" alt="Real blocking lifecycle decomposition" width="100%"/><br/>
87
+ <em>Real ERA5 blocking event (track 425) — animated lifecycle showing
88
+ total PV on a cartopy map (left) and the four projected basis components
89
+ (right) evolving from 13 h pre-onset to 12 h post-onset.
90
+ The analysis is done on a weighted average surface across 300, 250, 200 hPa levels.</em>
91
+ </td>
92
+ </tr>
93
+ <tr>
94
+ <td colspan="2" align="center">
95
+ <img src="docs/_static/z_lifecycle_demo.gif" alt="Geopotential-height lifecycle decomposition" width="100%"/><br/>
96
+ <em>Geopotential-height (Z500) variant of the four-basis decomposition
97
+ (track 425) — animated lifecycle showing Z anomaly from the 1990–2020
98
+ hourly climatology, with adaptive prenorm and blockid contour overlay.
99
+ See notebook <code>03z_four_basis_projection_geopotential</code>.</em>
100
+ </td>
101
+ </tr>
102
+ </table>
103
+
104
+ ### Event catalogues
105
+
106
+ Blocking and PRP-high events are identified as persistent anticyclonic anomalies in 500 hPa geopotential height.
107
+ We are using [**TempestExtremes** v2.1](https://gmd.copernicus.org/articles/14/5023/2021/) to track contiguous Z500 anomaly features that exceed a fixed threshold for ≥5 days, producing CSV catalogues with columns for event ID, centre lat/lon, onset/peak/decay timestamps, and area. Following the threshold as in [Drouard et al. (2021)](https://agupubs.onlinelibrary.wiley.com/doi/abs/10.1029/2020JD034082), we separate the tracked features into blocking and propagating (prp) high pressure systems.
108
+
109
+ > **Sample catalogue (ERA5, 1990–2020 blocking):** [`ERA5_TempestExtremes_z500_anticyclone_blocking.csv`](https://github.com/yanxingjianken/pvtend/raw/main/docs/_static/ERA5_TempestExtremes_z500_anticyclone_blocking.csv)
110
+
111
+ The CSVs are the inputs for `pvtend-pipeline compute`, which extracts event-centred patches and runs the full PV-tendency decomposition for each event in the blocking/prp catalogue.
112
+
113
+ ## Features
114
+
115
+ - **PV tendency computation**: RHS has zonal advection, baroclinic counter propagation, vertical advection, and approximated diabatic heating terms.
116
+ - **QG omega solver**: Hoskins Q-vector formulation with **two methods**: FFT+Thomas (default, fast) and 3-D direct/iterative (BiCGSTAB+ILU, full horizontal Laplacian). Optional `center_lat` for dynamic f₀ (Li & O'Gorman 2020).
117
+ - **Helmholtz decomposition**: 4 backends (direct, FFT, DCT, SOR) for limited-area domains
118
+ - **Moist/dry omega splitting**: Decomposes vertical motion into moist and dry contributions
119
+ - **Orthogonal basis decomposition**: Projects PV tendency onto intensification (β), propagation (αx, αy), and deformation (γ) modes
120
+ - **RWB detection**: Two classification methods — **bay** (path-order, recommended with circumpolar-cropped contours) and **tilt** (centerline slope ±0.15 dead zone). Circumpolar-first contour extraction for robust NH analysis.
121
+ - **Composite lifecycle**: Multi-stage ensemble averaging with onset/peak/decay staging
122
+ - **CLI pipeline**: End-to-end processing via `pvtend-pipeline` command
123
+
124
+ ## Installation
125
+
126
+ ```bash
127
+ # From source
128
+ git clone https://github.com/yanxingjianken/pvtend.git
129
+ cd pvtend
130
+ pip install -e ".[dev]"
131
+
132
+ # With micromamba
133
+ micromamba create -f environment.yml
134
+ micromamba activate pvtend_env
135
+ pip install -e ".[dev]"
136
+ ```
137
+
138
+ ## Quick Start
139
+
140
+ ```python
141
+ import numpy as np
142
+ from pvtend import NHGrid, ddx, ddy, compute_orthogonal_basis, project_field
143
+
144
+ # Grid setup
145
+ grid = NHGrid(lat=np.linspace(90, 0, 61), lon=np.linspace(-180, 178.5, 240))
146
+ dx_arr = grid.dx_arr # zonal spacing per latitude [m]
147
+ dy = grid.dy # meridional spacing [m]
148
+
149
+ # Compute zonal derivative
150
+ dfdx = ddx(field, dx_arr, periodic=False)
151
+
152
+ # Orthogonal basis decomposition
153
+ basis = compute_orthogonal_basis(pv_anom, pv_dx, pv_dy, x_rel, y_rel)
154
+ result = project_field(tendency, basis)
155
+ print(f"β = {result['beta']:.3e}") # intensification rate
156
+ ```
157
+
158
+ ### CLI Pipeline
159
+
160
+ ```bash
161
+ # Step 1: Compute PV tendencies → per-event NPZ files
162
+ pvtend-pipeline compute \
163
+ --event-type blocking \
164
+ --events-csv events.csv \
165
+ --era5-dir /data/era5/ \
166
+ --clim-path /data/climatology/era5_hourly_clim.nc \
167
+ --out-dir /data/composite_blocking_tempest/ \
168
+ --dh-range=-49:25 --skip-existing
169
+
170
+ # Step 2: RWB classification → variant tracksets PKL
171
+ pvtend-pipeline classify \
172
+ --npz-dir /data/composite_blocking_tempest/ \
173
+ --output /data/outputs/rwb_variant_tracksets.pkl \
174
+ --stages onset peak decay \
175
+ --levels 500 400 300 200 --threshold 3
176
+
177
+ # Step 3: Variant-aware composite accumulation → composite PKL
178
+ pvtend-pipeline composite \
179
+ --npz-dir /data/composite_blocking_tempest/ \
180
+ --rwb-pkl /data/outputs/rwb_variant_tracksets.pkl \
181
+ --pkl-out /data/outputs/composite.pkl
182
+
183
+ # Step 4 (optional): Orthogonal-basis decomposition
184
+ pvtend-pipeline decompose \
185
+ --pkl-in /data/outputs/composite.pkl \
186
+ --out-dir /data/outputs/decomp/
187
+ ```
188
+
189
+ ## Workflow
190
+
191
+ ```mermaid
192
+ graph TD
193
+ A[ERA5 Monthly NetCDF] --> B[pvtend.preprocessing]
194
+ B --> C[Regridded NH Grid]
195
+ C --> D[pvtend.climatology]
196
+ D --> E[Monthly Climatology]
197
+ C & E --> F[pvtend.tendency.TendencyComputer]
198
+ F --> G[PV Tendency Terms]
199
+ G --> H[pvtend.omega — QG ω solver]
200
+ G --> I[pvtend.helmholtz — Helmholtz decomp.]
201
+ H & I --> J[pvtend.moist_dry — ω splitting]
202
+ G & J --> K[Per-event NPZ patches]
203
+ K --> L1[pvtend.classify — RWB Pass 1]
204
+ L1 --> L1a[rwb_variant_tracksets.pkl]
205
+ K & L1a --> L2[pvtend.composite_builder — Pass 2]
206
+ L2 --> L2a[composite.pkl]
207
+ L2a --> M[pvtend.decomposition — Orthogonal basis]
208
+ M --> N[β, αx, αy, γ coefficients]
209
+ N --> O[pvtend.plotting — Publication figures]
210
+ ```
211
+
212
+ ## Package Structure
213
+
214
+ ```
215
+ src/pvtend/
216
+ ├── __init__.py # Public API
217
+ ├── _version.py # Version
218
+ ├── cli.py # CLI entry point (compute, classify, composite, decompose)
219
+ ├── constants.py # Physical constants
220
+ ├── grid.py # NH grid & event patches
221
+ ├── preprocessing.py # ERA5 loading & regridding
222
+ ├── derivatives.py # Finite difference operators
223
+ ├── climatology.py # Fourier-filtered climatology
224
+ ├── omega.py # QG omega solver (FFT+Thomas or 3-D direct)
225
+ ├── helmholtz.py # Helmholtz decomposition (direct/FFT/DCT/SOR)
226
+ ├── moist_dry.py # Moist/dry omega split
227
+ ├── isentropic.py # Isentropic PV-tendency diagnostics
228
+ ├── tendency.py # Main pipeline: data loading, derivatives, cross-terms, NPZ output
229
+ ├── classify.py # RWB classification Pass 1 (AWB/CWB/NEUTRAL → variant PKL)
230
+ ├── composite_builder.py # Variant-aware composite accumulation Pass 2
231
+ ├── rwb.py # RWB detection (bay & tilt methods, circumpolar-first)
232
+ ├── composites.py # Legacy composite lifecycle
233
+ ├── data/ # Bundled sample data
234
+ │ ├── __init__.py # load_idealized_pv() loader
235
+ │ └── idealized_pv.npz # Synthetic Gaussian PV evolution
236
+ ├── decomposition/ # Orthogonal basis framework
237
+ │ ├── __init__.py
238
+ │ ├── smoothing.py
239
+ │ ├── basis.py
240
+ │ └── projection.py
241
+ ├── plotting/ # Visualization
242
+ │ ├── __init__.py
243
+ │ ├── basis_plots.py
244
+ │ ├── coefficient_plots.py
245
+ │ ├── field_plots.py
246
+ │ ├── composite_explorer.py # plot_var: single-variable composite explorer with bootstrap
247
+ │ └── baroclinic.py # plot_baroclinic_tilt: two-level v′ overlay
248
+ └── io/ # File I/O
249
+ ├── __init__.py
250
+ ├── era5.py
251
+ ├── npz.py
252
+ └── pkl.py
253
+ ```
254
+
255
+ ## Example Notebooks
256
+
257
+ Notebooks using **real ERA5 blocking event data** from the `composite_blocking_tempest`, `composite_prp_tempest` (single event npz), `tempest_extreme_4_basis/outputs`, and `tempest_extreme_4_basis/outputs_prp` (composite pkl) pipeline:
258
+
259
+ | Notebook | Description |
260
+ |----------|-------------|
261
+ | [`00_idealized_pvtend_decomp`](examples/00_idealized_pvtend_decomp.ipynb) | Idealized Gaussian PV anomaly: prescribed β/αx/αy/γ at two timesteps, basis visualisation, Gram-Schmidt, projection & reconstruction |
262
+ | [`01_rwb_and_derivatives`](examples/01_rwb_and_derivatives.ipynb) | Grid setup, `ddx`/`ddy`/`ddp` derivatives, RWB detection on a real event |
263
+ | [`02_helmholtz_and_qg_omega`](examples/02_helmholtz_and_qg_omega.ipynb) | 3-D Helmholtz decomposition, QG omega (FFT vs 3-D direct), moist/dry ω split |
264
+ | [`03_four_basis_projection`](examples/03_four_basis_projection.ipynb) | Orthogonal basis (Φ₁–Φ₄), project dq'/dt → β/αx/αy/γ, lifecycle curves |
265
+ | [`03z_four_basis_projection_geopotential`](examples/03z_four_basis_projection_geopotential.ipynb) | ↳ *Supplement*: same 4-basis projection using **geopotential height Z** instead of PV |
266
+ | [`04_single_var_composite`](examples/04_single_var_composite.ipynb) | Single-variable composite explorer on pressure levels using `pvtend.plotting.plot_var` |
267
+ | [`04i_single_var_isentropic_composite`](examples/04i_single_var_isentropic_composite.ipynb) | ↳ *Supplement*: same as 04 but on **isentropic (θ) surfaces** |
268
+ | [`05_stacked_bar_beta`](examples/05_stacked_bar_beta.ipynb) | Stacked-bar β decomposition by PV-tendency term across lifecycle hours |
269
+ | [`05b_grouped_terms_bootstrap`](examples/05b_grouped_terms_bootstrap.ipynb) | ↳ *Supplement*: grouped PV-tendency terms with **bootstrap resampling & significance** |
270
+ | [`06_baroclinic_structure`](examples/06_baroclinic_structure.ipynb) | 3-D composite PV anomaly, lon–p cross-sections, 2-PVU tropopause, v′ tilt via `plot_baroclinic_tilt` |
271
+ | [`07_facet_blocking_vs_prp`](examples/07_facet_blocking_vs_prp.ipynb) | Facet comparison of blocking vs PRP: bar charts with bootstrap significance, shared-cbar spatial maps, baroclinic tilt |
272
+
273
+ ## Testing
274
+
275
+ ```bash
276
+ pytest tests/ -v
277
+ ```
278
+
279
+ ## Documentation
280
+
281
+ Full documentation at [pvtend.readthedocs.io](https://pvtend.readthedocs.io).
282
+
283
+ Build locally:
284
+
285
+ ```bash
286
+ cd docs && make html
287
+ ```
288
+
289
+ ## Citation
290
+
291
+ If you use this package in your research, please cite:
292
+
293
+ ```bibtex
294
+ @software{yan2025pvtend,
295
+ author = {Yan, Xingjian},
296
+ title = {pvtend: PV tendency decomposition for atmospheric blocking},
297
+ year = {2025},
298
+ url = {https://github.com/yanxingjianken/pvtend}
299
+ }
300
+ ```
301
+
302
+ ## License
303
+
304
+ MIT — see [LICENSE](LICENSE).
pvtend-0.7.1/README.md ADDED
@@ -0,0 +1,240 @@
1
+ # pvtend
2
+
3
+ [![Tests](https://github.com/yanxingjianken/pvtend/actions/workflows/test.yml/badge.svg)](https://github.com/yanxingjianken/pvtend/actions)
4
+ [![Documentation](https://readthedocs.org/projects/pvtend/badge/?version=latest)](https://pvtend.readthedocs.io/en/latest/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+
7
+ **PV tendency decomposition for atmospheric blocking, propagating anticyclones, and all synoptic-scale cyclonic event lifecycle analysis.**
8
+
9
+ `pvtend` diagnoses the growth, propagation, and decay of mid-latitude weather events by decomposing potential vorticity (PV) tendencies from ERA5 pressure-level data onto physically meaningful components using an orthogonal basis framework. This is the Part I work of Yan et al. (in prep.) about blocking lifecycle analyses on onset, peak, decay stages.
10
+
11
+ ## Gallery
12
+
13
+ <table>
14
+ <tr>
15
+ <td width="50%" align="center">
16
+ <img src="docs/_static/reconstruction_demo.png" alt="Idealized four-basis reconstruction" width="100%"/><br/>
17
+ <em>Idealized validation — a Gaussian PV anomaly with prescribed propagation,
18
+ intensification, and deformation is decomposed into four orthogonal bases
19
+ and reconstructed with near-zero residual.</em>
20
+ </td>
21
+ <td width="50%" align="center">
22
+ <img src="docs/_static/lifecycle_demo.gif" alt="Real blocking lifecycle decomposition" width="100%"/><br/>
23
+ <em>Real ERA5 blocking event (track 425) — animated lifecycle showing
24
+ total PV on a cartopy map (left) and the four projected basis components
25
+ (right) evolving from 13 h pre-onset to 12 h post-onset.
26
+ The analysis is done on a weighted average surface across 300, 250, 200 hPa levels.</em>
27
+ </td>
28
+ </tr>
29
+ <tr>
30
+ <td colspan="2" align="center">
31
+ <img src="docs/_static/z_lifecycle_demo.gif" alt="Geopotential-height lifecycle decomposition" width="100%"/><br/>
32
+ <em>Geopotential-height (Z500) variant of the four-basis decomposition
33
+ (track 425) — animated lifecycle showing Z anomaly from the 1990–2020
34
+ hourly climatology, with adaptive prenorm and blockid contour overlay.
35
+ See notebook <code>03z_four_basis_projection_geopotential</code>.</em>
36
+ </td>
37
+ </tr>
38
+ </table>
39
+
40
+ ### Event catalogues
41
+
42
+ Blocking and PRP-high events are identified as persistent anticyclonic anomalies in 500 hPa geopotential height.
43
+ We are using [**TempestExtremes** v2.1](https://gmd.copernicus.org/articles/14/5023/2021/) to track contiguous Z500 anomaly features that exceed a fixed threshold for ≥5 days, producing CSV catalogues with columns for event ID, centre lat/lon, onset/peak/decay timestamps, and area. Following the threshold as in [Drouard et al. (2021)](https://agupubs.onlinelibrary.wiley.com/doi/abs/10.1029/2020JD034082), we separate the tracked features into blocking and propagating (prp) high pressure systems.
44
+
45
+ > **Sample catalogue (ERA5, 1990–2020 blocking):** [`ERA5_TempestExtremes_z500_anticyclone_blocking.csv`](https://github.com/yanxingjianken/pvtend/raw/main/docs/_static/ERA5_TempestExtremes_z500_anticyclone_blocking.csv)
46
+
47
+ The CSVs are the inputs for `pvtend-pipeline compute`, which extracts event-centred patches and runs the full PV-tendency decomposition for each event in the blocking/prp catalogue.
48
+
49
+ ## Features
50
+
51
+ - **PV tendency computation**: RHS has zonal advection, baroclinic counter propagation, vertical advection, and approximated diabatic heating terms.
52
+ - **QG omega solver**: Hoskins Q-vector formulation with **two methods**: FFT+Thomas (default, fast) and 3-D direct/iterative (BiCGSTAB+ILU, full horizontal Laplacian). Optional `center_lat` for dynamic f₀ (Li & O'Gorman 2020).
53
+ - **Helmholtz decomposition**: 4 backends (direct, FFT, DCT, SOR) for limited-area domains
54
+ - **Moist/dry omega splitting**: Decomposes vertical motion into moist and dry contributions
55
+ - **Orthogonal basis decomposition**: Projects PV tendency onto intensification (β), propagation (αx, αy), and deformation (γ) modes
56
+ - **RWB detection**: Two classification methods — **bay** (path-order, recommended with circumpolar-cropped contours) and **tilt** (centerline slope ±0.15 dead zone). Circumpolar-first contour extraction for robust NH analysis.
57
+ - **Composite lifecycle**: Multi-stage ensemble averaging with onset/peak/decay staging
58
+ - **CLI pipeline**: End-to-end processing via `pvtend-pipeline` command
59
+
60
+ ## Installation
61
+
62
+ ```bash
63
+ # From source
64
+ git clone https://github.com/yanxingjianken/pvtend.git
65
+ cd pvtend
66
+ pip install -e ".[dev]"
67
+
68
+ # With micromamba
69
+ micromamba create -f environment.yml
70
+ micromamba activate pvtend_env
71
+ pip install -e ".[dev]"
72
+ ```
73
+
74
+ ## Quick Start
75
+
76
+ ```python
77
+ import numpy as np
78
+ from pvtend import NHGrid, ddx, ddy, compute_orthogonal_basis, project_field
79
+
80
+ # Grid setup
81
+ grid = NHGrid(lat=np.linspace(90, 0, 61), lon=np.linspace(-180, 178.5, 240))
82
+ dx_arr = grid.dx_arr # zonal spacing per latitude [m]
83
+ dy = grid.dy # meridional spacing [m]
84
+
85
+ # Compute zonal derivative
86
+ dfdx = ddx(field, dx_arr, periodic=False)
87
+
88
+ # Orthogonal basis decomposition
89
+ basis = compute_orthogonal_basis(pv_anom, pv_dx, pv_dy, x_rel, y_rel)
90
+ result = project_field(tendency, basis)
91
+ print(f"β = {result['beta']:.3e}") # intensification rate
92
+ ```
93
+
94
+ ### CLI Pipeline
95
+
96
+ ```bash
97
+ # Step 1: Compute PV tendencies → per-event NPZ files
98
+ pvtend-pipeline compute \
99
+ --event-type blocking \
100
+ --events-csv events.csv \
101
+ --era5-dir /data/era5/ \
102
+ --clim-path /data/climatology/era5_hourly_clim.nc \
103
+ --out-dir /data/composite_blocking_tempest/ \
104
+ --dh-range=-49:25 --skip-existing
105
+
106
+ # Step 2: RWB classification → variant tracksets PKL
107
+ pvtend-pipeline classify \
108
+ --npz-dir /data/composite_blocking_tempest/ \
109
+ --output /data/outputs/rwb_variant_tracksets.pkl \
110
+ --stages onset peak decay \
111
+ --levels 500 400 300 200 --threshold 3
112
+
113
+ # Step 3: Variant-aware composite accumulation → composite PKL
114
+ pvtend-pipeline composite \
115
+ --npz-dir /data/composite_blocking_tempest/ \
116
+ --rwb-pkl /data/outputs/rwb_variant_tracksets.pkl \
117
+ --pkl-out /data/outputs/composite.pkl
118
+
119
+ # Step 4 (optional): Orthogonal-basis decomposition
120
+ pvtend-pipeline decompose \
121
+ --pkl-in /data/outputs/composite.pkl \
122
+ --out-dir /data/outputs/decomp/
123
+ ```
124
+
125
+ ## Workflow
126
+
127
+ ```mermaid
128
+ graph TD
129
+ A[ERA5 Monthly NetCDF] --> B[pvtend.preprocessing]
130
+ B --> C[Regridded NH Grid]
131
+ C --> D[pvtend.climatology]
132
+ D --> E[Monthly Climatology]
133
+ C & E --> F[pvtend.tendency.TendencyComputer]
134
+ F --> G[PV Tendency Terms]
135
+ G --> H[pvtend.omega — QG ω solver]
136
+ G --> I[pvtend.helmholtz — Helmholtz decomp.]
137
+ H & I --> J[pvtend.moist_dry — ω splitting]
138
+ G & J --> K[Per-event NPZ patches]
139
+ K --> L1[pvtend.classify — RWB Pass 1]
140
+ L1 --> L1a[rwb_variant_tracksets.pkl]
141
+ K & L1a --> L2[pvtend.composite_builder — Pass 2]
142
+ L2 --> L2a[composite.pkl]
143
+ L2a --> M[pvtend.decomposition — Orthogonal basis]
144
+ M --> N[β, αx, αy, γ coefficients]
145
+ N --> O[pvtend.plotting — Publication figures]
146
+ ```
147
+
148
+ ## Package Structure
149
+
150
+ ```
151
+ src/pvtend/
152
+ ├── __init__.py # Public API
153
+ ├── _version.py # Version
154
+ ├── cli.py # CLI entry point (compute, classify, composite, decompose)
155
+ ├── constants.py # Physical constants
156
+ ├── grid.py # NH grid & event patches
157
+ ├── preprocessing.py # ERA5 loading & regridding
158
+ ├── derivatives.py # Finite difference operators
159
+ ├── climatology.py # Fourier-filtered climatology
160
+ ├── omega.py # QG omega solver (FFT+Thomas or 3-D direct)
161
+ ├── helmholtz.py # Helmholtz decomposition (direct/FFT/DCT/SOR)
162
+ ├── moist_dry.py # Moist/dry omega split
163
+ ├── isentropic.py # Isentropic PV-tendency diagnostics
164
+ ├── tendency.py # Main pipeline: data loading, derivatives, cross-terms, NPZ output
165
+ ├── classify.py # RWB classification Pass 1 (AWB/CWB/NEUTRAL → variant PKL)
166
+ ├── composite_builder.py # Variant-aware composite accumulation Pass 2
167
+ ├── rwb.py # RWB detection (bay & tilt methods, circumpolar-first)
168
+ ├── composites.py # Legacy composite lifecycle
169
+ ├── data/ # Bundled sample data
170
+ │ ├── __init__.py # load_idealized_pv() loader
171
+ │ └── idealized_pv.npz # Synthetic Gaussian PV evolution
172
+ ├── decomposition/ # Orthogonal basis framework
173
+ │ ├── __init__.py
174
+ │ ├── smoothing.py
175
+ │ ├── basis.py
176
+ │ └── projection.py
177
+ ├── plotting/ # Visualization
178
+ │ ├── __init__.py
179
+ │ ├── basis_plots.py
180
+ │ ├── coefficient_plots.py
181
+ │ ├── field_plots.py
182
+ │ ├── composite_explorer.py # plot_var: single-variable composite explorer with bootstrap
183
+ │ └── baroclinic.py # plot_baroclinic_tilt: two-level v′ overlay
184
+ └── io/ # File I/O
185
+ ├── __init__.py
186
+ ├── era5.py
187
+ ├── npz.py
188
+ └── pkl.py
189
+ ```
190
+
191
+ ## Example Notebooks
192
+
193
+ Notebooks using **real ERA5 blocking event data** from the `composite_blocking_tempest`, `composite_prp_tempest` (single event npz), `tempest_extreme_4_basis/outputs`, and `tempest_extreme_4_basis/outputs_prp` (composite pkl) pipeline:
194
+
195
+ | Notebook | Description |
196
+ |----------|-------------|
197
+ | [`00_idealized_pvtend_decomp`](examples/00_idealized_pvtend_decomp.ipynb) | Idealized Gaussian PV anomaly: prescribed β/αx/αy/γ at two timesteps, basis visualisation, Gram-Schmidt, projection & reconstruction |
198
+ | [`01_rwb_and_derivatives`](examples/01_rwb_and_derivatives.ipynb) | Grid setup, `ddx`/`ddy`/`ddp` derivatives, RWB detection on a real event |
199
+ | [`02_helmholtz_and_qg_omega`](examples/02_helmholtz_and_qg_omega.ipynb) | 3-D Helmholtz decomposition, QG omega (FFT vs 3-D direct), moist/dry ω split |
200
+ | [`03_four_basis_projection`](examples/03_four_basis_projection.ipynb) | Orthogonal basis (Φ₁–Φ₄), project dq'/dt → β/αx/αy/γ, lifecycle curves |
201
+ | [`03z_four_basis_projection_geopotential`](examples/03z_four_basis_projection_geopotential.ipynb) | ↳ *Supplement*: same 4-basis projection using **geopotential height Z** instead of PV |
202
+ | [`04_single_var_composite`](examples/04_single_var_composite.ipynb) | Single-variable composite explorer on pressure levels using `pvtend.plotting.plot_var` |
203
+ | [`04i_single_var_isentropic_composite`](examples/04i_single_var_isentropic_composite.ipynb) | ↳ *Supplement*: same as 04 but on **isentropic (θ) surfaces** |
204
+ | [`05_stacked_bar_beta`](examples/05_stacked_bar_beta.ipynb) | Stacked-bar β decomposition by PV-tendency term across lifecycle hours |
205
+ | [`05b_grouped_terms_bootstrap`](examples/05b_grouped_terms_bootstrap.ipynb) | ↳ *Supplement*: grouped PV-tendency terms with **bootstrap resampling & significance** |
206
+ | [`06_baroclinic_structure`](examples/06_baroclinic_structure.ipynb) | 3-D composite PV anomaly, lon–p cross-sections, 2-PVU tropopause, v′ tilt via `plot_baroclinic_tilt` |
207
+ | [`07_facet_blocking_vs_prp`](examples/07_facet_blocking_vs_prp.ipynb) | Facet comparison of blocking vs PRP: bar charts with bootstrap significance, shared-cbar spatial maps, baroclinic tilt |
208
+
209
+ ## Testing
210
+
211
+ ```bash
212
+ pytest tests/ -v
213
+ ```
214
+
215
+ ## Documentation
216
+
217
+ Full documentation at [pvtend.readthedocs.io](https://pvtend.readthedocs.io).
218
+
219
+ Build locally:
220
+
221
+ ```bash
222
+ cd docs && make html
223
+ ```
224
+
225
+ ## Citation
226
+
227
+ If you use this package in your research, please cite:
228
+
229
+ ```bibtex
230
+ @software{yan2025pvtend,
231
+ author = {Yan, Xingjian},
232
+ title = {pvtend: PV tendency decomposition for atmospheric blocking},
233
+ year = {2025},
234
+ url = {https://github.com/yanxingjianken/pvtend}
235
+ }
236
+ ```
237
+
238
+ ## License
239
+
240
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pvtend"
7
+ version = "0.7.1"
8
+ authors = [
9
+ { name = "Xingjian Yan" },
10
+ ]
11
+ description = "PV tendency decomposition diagnostics for blocking and weather extremes"
12
+ readme = "README.md"
13
+ license = { file = "LICENSE" }
14
+ requires-python = ">=3.10"
15
+ classifiers = [
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Intended Audience :: Science/Research",
19
+ "Topic :: Scientific/Engineering :: Atmospheric Science",
20
+ ]
21
+ dependencies = [
22
+ "numpy>=1.22",
23
+ "scipy",
24
+ "xarray",
25
+ "pandas",
26
+ "netCDF4",
27
+ "tqdm",
28
+ "matplotlib",
29
+ "scikit-image",
30
+ "cdsapi",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ maps = ["cartopy"]
35
+ sip = ["numba"]
36
+ test = ["pytest", "dask"]
37
+ docs = ["sphinx", "sphinx-rtd-theme", "nbsphinx", "ipykernel"]
38
+ dev = ["pvtend[test,docs,sip]", "pre-commit", "ruff"]
39
+
40
+ [project.urls]
41
+ Documentation = "https://pvtend.readthedocs.io/"
42
+ Repository = "https://github.com/yanxingjianken/pvtend"
43
+ "Bug Tracker" = "https://github.com/yanxingjianken/pvtend/issues"
44
+
45
+ [project.scripts]
46
+ pvtend-pipeline = "pvtend.cli:main"
47
+
48
+ [tool.setuptools.packages.find]
49
+ where = ["src"]
50
+
51
+ [tool.setuptools.package-data]
52
+ "pvtend.data" = ["*.npz"]
53
+
54
+ [tool.pytest.ini_options]
55
+ testpaths = ["tests"]
56
+ addopts = "-v --tb=short"
pvtend-0.7.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+