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.
- pvtend-0.7.1/LICENSE +21 -0
- pvtend-0.7.1/PKG-INFO +304 -0
- pvtend-0.7.1/README.md +240 -0
- pvtend-0.7.1/pyproject.toml +56 -0
- pvtend-0.7.1/setup.cfg +4 -0
- pvtend-0.7.1/src/pvtend/__init__.py +123 -0
- pvtend-0.7.1/src/pvtend/_version.py +1 -0
- pvtend-0.7.1/src/pvtend/classify.py +425 -0
- pvtend-0.7.1/src/pvtend/cli.py +361 -0
- pvtend-0.7.1/src/pvtend/climatology.py +249 -0
- pvtend-0.7.1/src/pvtend/composite_builder.py +381 -0
- pvtend-0.7.1/src/pvtend/composites.py +225 -0
- pvtend-0.7.1/src/pvtend/constants.py +47 -0
- pvtend-0.7.1/src/pvtend/data/__init__.py +54 -0
- pvtend-0.7.1/src/pvtend/data/idealized_pv.npz +0 -0
- pvtend-0.7.1/src/pvtend/decomposition/__init__.py +33 -0
- pvtend-0.7.1/src/pvtend/decomposition/basis.py +255 -0
- pvtend-0.7.1/src/pvtend/decomposition/projection.py +191 -0
- pvtend-0.7.1/src/pvtend/decomposition/smoothing.py +97 -0
- pvtend-0.7.1/src/pvtend/derivatives.py +267 -0
- pvtend-0.7.1/src/pvtend/grid.py +251 -0
- pvtend-0.7.1/src/pvtend/helmholtz.py +514 -0
- pvtend-0.7.1/src/pvtend/io/__init__.py +20 -0
- pvtend-0.7.1/src/pvtend/io/era5.py +120 -0
- pvtend-0.7.1/src/pvtend/io/npz.py +66 -0
- pvtend-0.7.1/src/pvtend/io/pkl.py +72 -0
- pvtend-0.7.1/src/pvtend/isentropic.py +494 -0
- pvtend-0.7.1/src/pvtend/moist_dry.py +157 -0
- pvtend-0.7.1/src/pvtend/omega.py +1072 -0
- pvtend-0.7.1/src/pvtend/plotting/__init__.py +30 -0
- pvtend-0.7.1/src/pvtend/plotting/baroclinic.py +207 -0
- pvtend-0.7.1/src/pvtend/plotting/basis_plots.py +108 -0
- pvtend-0.7.1/src/pvtend/plotting/coefficient_plots.py +116 -0
- pvtend-0.7.1/src/pvtend/plotting/composite_explorer.py +385 -0
- pvtend-0.7.1/src/pvtend/plotting/field_plots.py +115 -0
- pvtend-0.7.1/src/pvtend/preprocessing.py +159 -0
- pvtend-0.7.1/src/pvtend/rwb.py +777 -0
- pvtend-0.7.1/src/pvtend/tendency.py +1293 -0
- pvtend-0.7.1/src/pvtend.egg-info/PKG-INFO +304 -0
- pvtend-0.7.1/src/pvtend.egg-info/SOURCES.txt +48 -0
- pvtend-0.7.1/src/pvtend.egg-info/dependency_links.txt +1 -0
- pvtend-0.7.1/src/pvtend.egg-info/entry_points.txt +2 -0
- pvtend-0.7.1/src/pvtend.egg-info/requires.txt +30 -0
- pvtend-0.7.1/src/pvtend.egg-info/top_level.txt +1 -0
- pvtend-0.7.1/tests/test_classify.py +276 -0
- pvtend-0.7.1/tests/test_cli.py +152 -0
- pvtend-0.7.1/tests/test_composite_builder.py +238 -0
- pvtend-0.7.1/tests/test_derivatives.py +86 -0
- pvtend-0.7.1/tests/test_helmholtz.py +92 -0
- 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
|
+
[](https://github.com/yanxingjianken/pvtend/actions)
|
|
68
|
+
[](https://pvtend.readthedocs.io/en/latest/)
|
|
69
|
+
[](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
|
+
[](https://github.com/yanxingjianken/pvtend/actions)
|
|
4
|
+
[](https://pvtend.readthedocs.io/en/latest/)
|
|
5
|
+
[](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