pyepwmorph 2.1.1__tar.gz → 3.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.
- {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/PKG-INFO +54 -40
- {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/README.md +47 -32
- pyepwmorph-3.0.0/pyepwmorph/__init__.py +8 -0
- {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/models/access.py +18 -21
- {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/models/assemble.py +25 -12
- {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/models/coordinate.py +7 -38
- {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/models/custom.py +0 -1
- pyepwmorph-3.0.0/pyepwmorph/morph/procedures.py +561 -0
- pyepwmorph-3.0.0/pyepwmorph/tools/cache.py +324 -0
- {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/tools/configuration.py +51 -18
- {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/tools/io.py +47 -27
- pyepwmorph-3.0.0/pyepwmorph/tools/psychrometrics.py +185 -0
- pyepwmorph-3.0.0/pyepwmorph/tools/solar.py +268 -0
- pyepwmorph-3.0.0/pyepwmorph/tools/utilities.py +407 -0
- pyepwmorph-3.0.0/pyepwmorph/tools/workflow.py +448 -0
- pyepwmorph-3.0.0/pyproject.toml +93 -0
- pyepwmorph-2.1.1/pyepwmorph/__init__.py +0 -8
- pyepwmorph-2.1.1/pyepwmorph/morph/procedures.py +0 -563
- pyepwmorph-2.1.1/pyepwmorph/tools/cache.py +0 -271
- pyepwmorph-2.1.1/pyepwmorph/tools/ladybug_psychrometrics.py +0 -531
- pyepwmorph-2.1.1/pyepwmorph/tools/solar.py +0 -326
- pyepwmorph-2.1.1/pyepwmorph/tools/utilities.py +0 -456
- pyepwmorph-2.1.1/pyepwmorph/tools/workflow.py +0 -529
- pyepwmorph-2.1.1/pyproject.toml +0 -65
- {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/.gitignore +0 -0
- {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/LICENSE +0 -0
- {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/models/__init__.py +0 -0
- {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/morph/__init__.py +0 -0
- {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/tools/__init__.py +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyepwmorph
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: A python package to enable simple and easy gathering of climate model data and morphing of EPW files
|
|
5
5
|
Project-URL: Homepage, https://github.com/justinfmccarty/pyepwmorph
|
|
6
6
|
Project-URL: Issues, https://github.com/justinfmccarty/pyepwmorph/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/justinfmccarty/pyepwmorph/blob/main/CHANGELOG.md
|
|
7
8
|
Author-email: Justin McCarty <mccarty.justin.f@gmail.com>
|
|
8
9
|
License: MIT
|
|
9
10
|
License-File: LICENSE
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
13
|
Classifier: Operating System :: OS Independent
|
|
12
14
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -14,20 +16,17 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
14
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
17
21
|
Requires-Python: >=3.9
|
|
18
22
|
Requires-Dist: dask>=2023.5.0
|
|
19
|
-
Requires-Dist: distributed>=2023.5.0
|
|
20
23
|
Requires-Dist: gcsfs>=2023.5.0
|
|
21
24
|
Requires-Dist: intake-esm>=2023.6.0
|
|
22
25
|
Requires-Dist: intake>=0.6.0
|
|
23
|
-
Requires-Dist:
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
Requires-Dist: numpy>=1.20.0
|
|
26
|
-
Requires-Dist: pandas>=1.3.0
|
|
26
|
+
Requires-Dist: numpy>=1.24
|
|
27
|
+
Requires-Dist: pandas>=2.2
|
|
27
28
|
Requires-Dist: pvlib<1.0.0,>=0.13.0
|
|
28
29
|
Requires-Dist: pyarrow>=13.0.0
|
|
29
|
-
Requires-Dist: skyfield>=1.40
|
|
30
|
-
Requires-Dist: timezonefinder>=6.0.0
|
|
31
30
|
Requires-Dist: xarray>=2022.3.0
|
|
32
31
|
Provides-Extra: dev
|
|
33
32
|
Requires-Dist: build>=0.10; extra == 'dev'
|
|
@@ -42,7 +41,7 @@ Description-Content-Type: text/markdown
|
|
|
42
41
|
|
|
43
42
|
[](LICENSE)
|
|
44
43
|
[](https://www.python.org/downloads/)
|
|
45
|
-
[](https://pypi.org/project/pyepwmorph/)
|
|
46
45
|
|
|
47
46
|
A Python package for morphing EnergyPlus Weather (EPW) files with climate model data. Supports CMIP6 projections from Google Cloud and custom CSV-based model data for both future and historical scenarios.
|
|
48
47
|
|
|
@@ -127,19 +126,21 @@ results = workflow.morphing_workflow(
|
|
|
127
126
|
|
|
128
127
|
Custom CSVs should have a `date` column (parseable by pandas) and a column named after the CMIP6 variable (e.g. `tas`, `tasmax`). Rows should be monthly.
|
|
129
128
|
|
|
129
|
+
The reference and target scenarios must cover **different years**, the same way the CMIP6 `historical` and `sspXXX` experiments do. The two series are concatenated before the baseline and target periods are sliced out, so overlapping years get averaged together and weaken the climate signal. Keep `baseline_range` inside the years the reference scenario covers.
|
|
130
|
+
|
|
130
131
|
## Climate scenarios
|
|
131
132
|
|
|
132
|
-
| Scenario
|
|
133
|
-
|
|
134
|
-
| Best Case Scenario
|
|
135
|
-
| Middle of the Road
|
|
136
|
-
| Upper Middle Scenario | ssp370 | Regional rivalry, slow convergence
|
|
137
|
-
| Worst Case Scenario
|
|
133
|
+
| Scenario | SSP | Description | Expected warming |
|
|
134
|
+
| --------------------- | ------ | --------------------------------------- | ---------------- |
|
|
135
|
+
| Best Case Scenario | ssp126 | Strong mitigation, renewable transition | ~1.8 C by 2100 |
|
|
136
|
+
| Middle of the Road | ssp245 | Moderate mitigation efforts | ~2.7 C by 2100 |
|
|
137
|
+
| Upper Middle Scenario | ssp370 | Regional rivalry, slow convergence | ~3.6 C by 2100 |
|
|
138
|
+
| Worst Case Scenario | ssp585 | Fossil-fueled development | ~4.4 C by 2100 |
|
|
138
139
|
|
|
139
140
|
## Morphing variables
|
|
140
141
|
|
|
141
142
|
- **Temperature** -- dry bulb temperature (shift + stretch)
|
|
142
|
-
- **Humidity** -- relative humidity
|
|
143
|
+
- **Humidity** -- relative humidity, stretched in specific humidity space
|
|
143
144
|
- **Pressure** -- atmospheric pressure (shift)
|
|
144
145
|
- **Wind** -- wind speed (stretch)
|
|
145
146
|
- **Clouds and Radiation** -- global/diffuse/direct radiation and sky cover
|
|
@@ -147,14 +148,16 @@ Custom CSVs should have a `date` column (parseable by pandas) and a column named
|
|
|
147
148
|
|
|
148
149
|
### Variable dependencies
|
|
149
150
|
|
|
150
|
-
Some variables
|
|
151
|
+
Some variables cannot be morphed on their own:
|
|
151
152
|
|
|
152
153
|
- **Humidity** requires Temperature and Pressure
|
|
153
154
|
- **Dew Point** requires Temperature, Humidity, and Pressure
|
|
154
155
|
|
|
156
|
+
Dependencies are added automatically and are **written to the output file**. Asking for `Dew Point` alone therefore returns an EPW with morphed pressure, temperature, relative humidity, and dew point, which keeps the file internally consistent. `MorphConfig.resolved_variables` shows exactly what will be written, in the order it is computed.
|
|
157
|
+
|
|
155
158
|
## Caching
|
|
156
159
|
|
|
157
|
-
Climate model data is cached locally after the first download
|
|
160
|
+
Climate model data is cached locally after the first download, and the cache is consulted before the remote catalogue is opened so a hit costs no network traffic.
|
|
158
161
|
|
|
159
162
|
```python
|
|
160
163
|
import pyepwmorph.models.access as access
|
|
@@ -163,6 +166,13 @@ stats = access.get_cmip6_cache_stats()
|
|
|
163
166
|
access.clear_cmip6_cache()
|
|
164
167
|
```
|
|
165
168
|
|
|
169
|
+
Cache entries live in the per-user cache directory (`~/Library/Caches/pyepwmorph` on macOS, `~/.cache/pyepwmorph` on Linux, `%LOCALAPPDATA%\pyepwmorph` on Windows) and are keyed by location, pathway, variable, model sources, and time slices. Two environment variables override the defaults:
|
|
170
|
+
|
|
171
|
+
| Variable | Purpose | Default |
|
|
172
|
+
| ------------------------- | ------------------------------ | ------- |
|
|
173
|
+
| `PYEPWMORPH_CACHE_DIR` | Where cache files are written | per-user cache directory |
|
|
174
|
+
| `PYEPWMORPH_CACHE_MAX_MB` | Size cap before old files go | 500 |
|
|
175
|
+
|
|
166
176
|
## Available climate models
|
|
167
177
|
|
|
168
178
|
```python
|
|
@@ -178,35 +188,39 @@ git clone https://github.com/justinfmccarty/pyepwmorph.git
|
|
|
178
188
|
cd pyepwmorph
|
|
179
189
|
uv sync --extra dev
|
|
180
190
|
|
|
181
|
-
# Run tests
|
|
182
|
-
pytest
|
|
191
|
+
# Run tests (fully offline)
|
|
192
|
+
uv run pytest
|
|
183
193
|
|
|
184
194
|
# Run with coverage
|
|
185
|
-
pytest --cov=pyepwmorph
|
|
195
|
+
uv run pytest --cov=pyepwmorph
|
|
196
|
+
|
|
197
|
+
# Lint
|
|
198
|
+
uv run ruff check pyepwmorph tests gui
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Releases
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
./release.sh [patch|minor|major]
|
|
186
205
|
```
|
|
187
206
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
- **`coordinate_cmip6_data`** now accepts an optional `time_slices` dict
|
|
200
|
-
for custom temporal bounds instead of hardcoded 1960-2014 / 2015-2100.
|
|
201
|
-
- **Removed** `requirements.txt`, `environment.yml`, `.bumpversion.cfg`.
|
|
202
|
-
Use `uv sync` or `pip install .` instead.
|
|
203
|
-
- **Per-module `__version__`** strings removed. Use
|
|
204
|
-
`pyepwmorph.__version__` or `importlib.metadata.version("pyepwmorph")`.
|
|
207
|
+
The script refuses to run on a dirty tree, off `main`, with failing lint or tests, or without a matching `CHANGELOG.md` section. It bumps the version, tags, and pushes; creating the GitHub Release then triggers the PyPI publish workflow.
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
gh release create v3.0.0 \
|
|
211
|
+
--title "v3.0.0" \
|
|
212
|
+
--notes "See CHANGELOG.md."
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Changes
|
|
216
|
+
|
|
217
|
+
See [CHANGELOG.md](CHANGELOG.md) for the full history. The most recent release corrects several morphing calculations, so morphed humidity, dew point, wind speed, cloud cover, and direct/diffuse radiation all differ from files produced by earlier versions.
|
|
205
218
|
|
|
206
219
|
## Requirements
|
|
207
220
|
|
|
208
221
|
- Python >= 3.9
|
|
209
|
-
-
|
|
222
|
+
- pandas >= 2.2
|
|
223
|
+
- Internet connection (for CMIP6 data download; the custom CSV workflow runs offline)
|
|
210
224
|
|
|
211
225
|
## License
|
|
212
226
|
|
|
@@ -216,6 +230,6 @@ MIT License. See [LICENSE](LICENSE).
|
|
|
216
230
|
|
|
217
231
|
```text
|
|
218
232
|
McCarty, J. (2026). pyepwmorph: A Python package for climate-informed
|
|
219
|
-
EPW file morphing. Version
|
|
233
|
+
EPW file morphing. Version 3.0.0.
|
|
220
234
|
https://github.com/justinfmccarty/pyepwmorph
|
|
221
235
|
```
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](LICENSE)
|
|
4
4
|
[](https://www.python.org/downloads/)
|
|
5
|
-
[](https://pypi.org/project/pyepwmorph/)
|
|
6
6
|
|
|
7
7
|
A Python package for morphing EnergyPlus Weather (EPW) files with climate model data. Supports CMIP6 projections from Google Cloud and custom CSV-based model data for both future and historical scenarios.
|
|
8
8
|
|
|
@@ -87,19 +87,21 @@ results = workflow.morphing_workflow(
|
|
|
87
87
|
|
|
88
88
|
Custom CSVs should have a `date` column (parseable by pandas) and a column named after the CMIP6 variable (e.g. `tas`, `tasmax`). Rows should be monthly.
|
|
89
89
|
|
|
90
|
+
The reference and target scenarios must cover **different years**, the same way the CMIP6 `historical` and `sspXXX` experiments do. The two series are concatenated before the baseline and target periods are sliced out, so overlapping years get averaged together and weaken the climate signal. Keep `baseline_range` inside the years the reference scenario covers.
|
|
91
|
+
|
|
90
92
|
## Climate scenarios
|
|
91
93
|
|
|
92
|
-
| Scenario
|
|
93
|
-
|
|
94
|
-
| Best Case Scenario
|
|
95
|
-
| Middle of the Road
|
|
96
|
-
| Upper Middle Scenario | ssp370 | Regional rivalry, slow convergence
|
|
97
|
-
| Worst Case Scenario
|
|
94
|
+
| Scenario | SSP | Description | Expected warming |
|
|
95
|
+
| --------------------- | ------ | --------------------------------------- | ---------------- |
|
|
96
|
+
| Best Case Scenario | ssp126 | Strong mitigation, renewable transition | ~1.8 C by 2100 |
|
|
97
|
+
| Middle of the Road | ssp245 | Moderate mitigation efforts | ~2.7 C by 2100 |
|
|
98
|
+
| Upper Middle Scenario | ssp370 | Regional rivalry, slow convergence | ~3.6 C by 2100 |
|
|
99
|
+
| Worst Case Scenario | ssp585 | Fossil-fueled development | ~4.4 C by 2100 |
|
|
98
100
|
|
|
99
101
|
## Morphing variables
|
|
100
102
|
|
|
101
103
|
- **Temperature** -- dry bulb temperature (shift + stretch)
|
|
102
|
-
- **Humidity** -- relative humidity
|
|
104
|
+
- **Humidity** -- relative humidity, stretched in specific humidity space
|
|
103
105
|
- **Pressure** -- atmospheric pressure (shift)
|
|
104
106
|
- **Wind** -- wind speed (stretch)
|
|
105
107
|
- **Clouds and Radiation** -- global/diffuse/direct radiation and sky cover
|
|
@@ -107,14 +109,16 @@ Custom CSVs should have a `date` column (parseable by pandas) and a column named
|
|
|
107
109
|
|
|
108
110
|
### Variable dependencies
|
|
109
111
|
|
|
110
|
-
Some variables
|
|
112
|
+
Some variables cannot be morphed on their own:
|
|
111
113
|
|
|
112
114
|
- **Humidity** requires Temperature and Pressure
|
|
113
115
|
- **Dew Point** requires Temperature, Humidity, and Pressure
|
|
114
116
|
|
|
117
|
+
Dependencies are added automatically and are **written to the output file**. Asking for `Dew Point` alone therefore returns an EPW with morphed pressure, temperature, relative humidity, and dew point, which keeps the file internally consistent. `MorphConfig.resolved_variables` shows exactly what will be written, in the order it is computed.
|
|
118
|
+
|
|
115
119
|
## Caching
|
|
116
120
|
|
|
117
|
-
Climate model data is cached locally after the first download
|
|
121
|
+
Climate model data is cached locally after the first download, and the cache is consulted before the remote catalogue is opened so a hit costs no network traffic.
|
|
118
122
|
|
|
119
123
|
```python
|
|
120
124
|
import pyepwmorph.models.access as access
|
|
@@ -123,6 +127,13 @@ stats = access.get_cmip6_cache_stats()
|
|
|
123
127
|
access.clear_cmip6_cache()
|
|
124
128
|
```
|
|
125
129
|
|
|
130
|
+
Cache entries live in the per-user cache directory (`~/Library/Caches/pyepwmorph` on macOS, `~/.cache/pyepwmorph` on Linux, `%LOCALAPPDATA%\pyepwmorph` on Windows) and are keyed by location, pathway, variable, model sources, and time slices. Two environment variables override the defaults:
|
|
131
|
+
|
|
132
|
+
| Variable | Purpose | Default |
|
|
133
|
+
| ------------------------- | ------------------------------ | ------- |
|
|
134
|
+
| `PYEPWMORPH_CACHE_DIR` | Where cache files are written | per-user cache directory |
|
|
135
|
+
| `PYEPWMORPH_CACHE_MAX_MB` | Size cap before old files go | 500 |
|
|
136
|
+
|
|
126
137
|
## Available climate models
|
|
127
138
|
|
|
128
139
|
```python
|
|
@@ -138,35 +149,39 @@ git clone https://github.com/justinfmccarty/pyepwmorph.git
|
|
|
138
149
|
cd pyepwmorph
|
|
139
150
|
uv sync --extra dev
|
|
140
151
|
|
|
141
|
-
# Run tests
|
|
142
|
-
pytest
|
|
152
|
+
# Run tests (fully offline)
|
|
153
|
+
uv run pytest
|
|
143
154
|
|
|
144
155
|
# Run with coverage
|
|
145
|
-
pytest --cov=pyepwmorph
|
|
156
|
+
uv run pytest --cov=pyepwmorph
|
|
157
|
+
|
|
158
|
+
# Lint
|
|
159
|
+
uv run ruff check pyepwmorph tests gui
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Releases
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
./release.sh [patch|minor|major]
|
|
146
166
|
```
|
|
147
167
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
- **`coordinate_cmip6_data`** now accepts an optional `time_slices` dict
|
|
160
|
-
for custom temporal bounds instead of hardcoded 1960-2014 / 2015-2100.
|
|
161
|
-
- **Removed** `requirements.txt`, `environment.yml`, `.bumpversion.cfg`.
|
|
162
|
-
Use `uv sync` or `pip install .` instead.
|
|
163
|
-
- **Per-module `__version__`** strings removed. Use
|
|
164
|
-
`pyepwmorph.__version__` or `importlib.metadata.version("pyepwmorph")`.
|
|
168
|
+
The script refuses to run on a dirty tree, off `main`, with failing lint or tests, or without a matching `CHANGELOG.md` section. It bumps the version, tags, and pushes; creating the GitHub Release then triggers the PyPI publish workflow.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
gh release create v3.0.0 \
|
|
172
|
+
--title "v3.0.0" \
|
|
173
|
+
--notes "See CHANGELOG.md."
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Changes
|
|
177
|
+
|
|
178
|
+
See [CHANGELOG.md](CHANGELOG.md) for the full history. The most recent release corrects several morphing calculations, so morphed humidity, dew point, wind speed, cloud cover, and direct/diffuse radiation all differ from files produced by earlier versions.
|
|
165
179
|
|
|
166
180
|
## Requirements
|
|
167
181
|
|
|
168
182
|
- Python >= 3.9
|
|
169
|
-
-
|
|
183
|
+
- pandas >= 2.2
|
|
184
|
+
- Internet connection (for CMIP6 data download; the custom CSV workflow runs offline)
|
|
170
185
|
|
|
171
186
|
## License
|
|
172
187
|
|
|
@@ -176,6 +191,6 @@ MIT License. See [LICENSE](LICENSE).
|
|
|
176
191
|
|
|
177
192
|
```text
|
|
178
193
|
McCarty, J. (2026). pyepwmorph: A Python package for climate-informed
|
|
179
|
-
EPW file morphing. Version
|
|
194
|
+
EPW file morphing. Version 3.0.0.
|
|
180
195
|
https://github.com/justinfmccarty/pyepwmorph
|
|
181
196
|
```
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""pyepwmorph -- Climate model data gathering and EPW file morphing."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("pyepwmorph")
|
|
7
|
+
except PackageNotFoundError: # running from a source tree that was never installed
|
|
8
|
+
__version__ = "0.0.0+unknown"
|
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
# coding=utf-8
|
|
2
1
|
"""
|
|
3
2
|
various scripts for accessing different flavors of climate models
|
|
4
3
|
"""
|
|
5
4
|
|
|
6
|
-
import gcsfs
|
|
7
|
-
import intake
|
|
8
|
-
import warnings
|
|
9
|
-
|
|
10
5
|
from pyepwmorph.tools import cache
|
|
11
6
|
|
|
12
|
-
warnings.filterwarnings("ignore")
|
|
13
|
-
|
|
14
7
|
__author__ = "Justin McCarty"
|
|
15
|
-
__copyright__ = "Copyright 2023"
|
|
8
|
+
__copyright__ = "Copyright 2023-2026"
|
|
16
9
|
__credits__ = ["Justin McCarty"]
|
|
17
10
|
__license__ = "MIT"
|
|
18
11
|
|
|
@@ -44,13 +37,16 @@ def access_cmip6_data(models, pathway, variable):
|
|
|
44
37
|
--------
|
|
45
38
|
>>> access_cmip6_data(['ACCESS-CM2', 'CanESM5', 'TaiESM1'], 'ssp126', 'tas')
|
|
46
39
|
"""
|
|
40
|
+
import gcsfs
|
|
41
|
+
import intake
|
|
42
|
+
|
|
47
43
|
# NOTE: No caching at this level because the data contains lazy dask arrays
|
|
48
|
-
# that reference the full global grid. Caching happens in
|
|
44
|
+
# that reference the full global grid. Caching happens in workflow.py once
|
|
49
45
|
# the data has been spatially selected and computed for a specific location.
|
|
50
|
-
|
|
46
|
+
|
|
51
47
|
table_id = 'Amon' # atmospheric variables (A) saved at monthly resolution (mon)
|
|
52
48
|
member_id = 'r1i1p1f1'
|
|
53
|
-
|
|
49
|
+
|
|
54
50
|
# Fetch from Google Cloud
|
|
55
51
|
gcsfs.GCSFileSystem(token='anon')
|
|
56
52
|
# datastore_json = 'pangeo-cmip6.json'
|
|
@@ -63,7 +59,7 @@ def access_cmip6_data(models, pathway, variable):
|
|
|
63
59
|
|
|
64
60
|
# convert data catalog into a dictionary of xarray datasets
|
|
65
61
|
dataset_dict = model_search.to_dataset_dict(zarr_kwargs={'consolidated': True, 'decode_times': False})
|
|
66
|
-
|
|
62
|
+
|
|
67
63
|
return dataset_dict
|
|
68
64
|
|
|
69
65
|
|
|
@@ -80,20 +76,21 @@ def build_accessible_data_list():
|
|
|
80
76
|
--------
|
|
81
77
|
>>> build_accessible_data_list()
|
|
82
78
|
"""
|
|
79
|
+
import gcsfs
|
|
80
|
+
import intake
|
|
83
81
|
gcsfs.GCSFileSystem(token='anon')
|
|
84
|
-
# datastore_json = 'pangeo-cmip6.json'
|
|
85
82
|
esm_data = intake.open_esm_datastore("https://storage.googleapis.com/cmip6/pangeo-cmip6.json")
|
|
86
|
-
|
|
83
|
+
return sorted(esm_data.df['source_id'].unique().tolist())
|
|
87
84
|
|
|
88
85
|
|
|
89
86
|
def clear_cmip6_cache():
|
|
90
87
|
"""
|
|
91
88
|
Clear all cached CMIP6 data.
|
|
92
|
-
|
|
89
|
+
|
|
93
90
|
This clears location-specific cached data that has been processed and stored
|
|
94
|
-
for faster repeated access. This is useful for development or when you want
|
|
91
|
+
for faster repeated access. This is useful for development or when you want
|
|
95
92
|
to free up disk space.
|
|
96
|
-
|
|
93
|
+
|
|
97
94
|
Examples
|
|
98
95
|
--------
|
|
99
96
|
>>> clear_cmip6_cache()
|
|
@@ -104,11 +101,11 @@ def clear_cmip6_cache():
|
|
|
104
101
|
def get_cmip6_cache_stats():
|
|
105
102
|
"""
|
|
106
103
|
Get statistics about the CMIP6 data cache.
|
|
107
|
-
|
|
104
|
+
|
|
108
105
|
Returns information about cache size, number of files, and individual
|
|
109
106
|
file details for development and monitoring purposes. The cache stores
|
|
110
107
|
location-specific processed data.
|
|
111
|
-
|
|
108
|
+
|
|
112
109
|
Returns
|
|
113
110
|
-------
|
|
114
111
|
dict
|
|
@@ -119,10 +116,10 @@ def get_cmip6_cache_stats():
|
|
|
119
116
|
- max_size_mb: Maximum allowed cache size
|
|
120
117
|
- usage_percent: Percentage of max size used
|
|
121
118
|
- files: List of individual file details
|
|
122
|
-
|
|
119
|
+
|
|
123
120
|
Examples
|
|
124
121
|
--------
|
|
125
122
|
>>> stats = get_cmip6_cache_stats()
|
|
126
123
|
>>> print(f"Cache using {stats['total_size_mb']} MB ({stats['usage_percent']}%)")
|
|
127
124
|
"""
|
|
128
|
-
return cache.get_cache_stats()
|
|
125
|
+
return cache.get_cache_stats()
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
# coding=utf-8
|
|
2
1
|
"""
|
|
3
|
-
This module
|
|
4
|
-
This is what enables
|
|
2
|
+
This module creates ensembles from multiple model inputs downloaded for a single pathway and variable.
|
|
3
|
+
This is what enables the slicing of the data from a percentile point of view.
|
|
5
4
|
"""
|
|
6
5
|
import pandas as pd
|
|
7
|
-
|
|
8
|
-
from pyepwmorph.tools import utilities
|
|
9
|
-
import warnings
|
|
6
|
+
import xarray as xr
|
|
10
7
|
|
|
11
|
-
|
|
8
|
+
from pyepwmorph.tools import utilities
|
|
12
9
|
|
|
13
10
|
__author__ = "Justin McCarty"
|
|
14
|
-
__copyright__ = "Copyright 2023"
|
|
11
|
+
__copyright__ = "Copyright 2023-2026"
|
|
15
12
|
__credits__ = ["Justin McCarty"]
|
|
16
13
|
__license__ = "MIT"
|
|
17
14
|
|
|
@@ -44,12 +41,28 @@ def build_cmip6_ensemble(percentiles, variable, datasets):
|
|
|
44
41
|
Examples
|
|
45
42
|
--------
|
|
46
43
|
>>> build_cmip6_ensemble(['1','50','99'], 'tas', datasets)
|
|
44
|
+
|
|
45
|
+
Raises
|
|
46
|
+
------
|
|
47
|
+
ValueError
|
|
48
|
+
If *datasets* is empty (no model data available).
|
|
47
49
|
"""
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
if not datasets:
|
|
51
|
+
raise ValueError(
|
|
52
|
+
f"No model datasets available for variable '{variable}'. "
|
|
53
|
+
f"The selected climate models may not provide this variable. "
|
|
54
|
+
f"Try selecting different model sources."
|
|
55
|
+
)
|
|
56
|
+
ens = xr.concat(
|
|
57
|
+
[ds.reset_coords(drop=True) for ds in datasets.values()],
|
|
58
|
+
dim='realization'
|
|
59
|
+
)
|
|
60
|
+
q_values = [int(p) / 100 for p in percentiles]
|
|
61
|
+
ens_perc = ens.quantile(q_values, dim='realization')
|
|
50
62
|
percentile_dict = dict()
|
|
51
63
|
for ptilekey in percentiles:
|
|
52
|
-
|
|
64
|
+
q = int(ptilekey) / 100
|
|
65
|
+
percentile_dict[ptilekey] = ens_perc.sel(quantile=q)[variable].to_dataframe()[variable].rename(ptilekey)
|
|
53
66
|
|
|
54
67
|
return pd.DataFrame(percentile_dict)
|
|
55
68
|
|
|
@@ -95,4 +108,4 @@ def calc_model_climatologies(baseline_range, future_range, baseline_data, future
|
|
|
95
108
|
future_means = utilities.monthly_means(future_data).rename(variable)
|
|
96
109
|
|
|
97
110
|
|
|
98
|
-
return baseline_means, future_means
|
|
111
|
+
return baseline_means, future_means
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# coding=utf-8
|
|
2
1
|
"""Coordinate CMIP6 model data into a standard grid and time system.
|
|
3
2
|
|
|
4
3
|
Climate model data comes in varied grid systems and temporal indices.
|
|
@@ -7,16 +6,10 @@ it is fed to the morphing algorithm.
|
|
|
7
6
|
"""
|
|
8
7
|
|
|
9
8
|
import logging
|
|
10
|
-
import time
|
|
11
|
-
import warnings
|
|
12
9
|
|
|
13
10
|
import dask
|
|
14
|
-
import pandas as pd
|
|
15
11
|
import xarray as xr
|
|
16
12
|
|
|
17
|
-
from pyepwmorph.tools import cache
|
|
18
|
-
|
|
19
|
-
warnings.filterwarnings("ignore")
|
|
20
13
|
logger = logging.getLogger(__name__)
|
|
21
14
|
|
|
22
15
|
__author__ = "Justin McCarty"
|
|
@@ -64,30 +57,17 @@ def coordinate_cmip6_data(
|
|
|
64
57
|
-------
|
|
65
58
|
dict
|
|
66
59
|
Dictionary of computed xarray Datasets keyed by source name.
|
|
60
|
+
|
|
61
|
+
Notes
|
|
62
|
+
-----
|
|
63
|
+
Caching lives in ``workflow.compile_climate_model_data`` so that a cache
|
|
64
|
+
hit avoids opening the remote catalogue at all. Calling this function
|
|
65
|
+
directly always recomputes.
|
|
67
66
|
"""
|
|
68
67
|
slices = {**DEFAULT_TIME_SLICES}
|
|
69
68
|
if time_slices:
|
|
70
69
|
slices.update(time_slices)
|
|
71
70
|
|
|
72
|
-
source_ids = list(dset_dict.keys())
|
|
73
|
-
model_names = []
|
|
74
|
-
for source_id in source_ids:
|
|
75
|
-
parts = source_id.split('.')
|
|
76
|
-
if len(parts) >= 3:
|
|
77
|
-
model_names.append(parts[2])
|
|
78
|
-
else:
|
|
79
|
-
model_names.append(source_id)
|
|
80
|
-
|
|
81
|
-
cached_data = cache.get_cached_coordinate_data(
|
|
82
|
-
latitude=latitude,
|
|
83
|
-
longitude=longitude,
|
|
84
|
-
pathway=pathway,
|
|
85
|
-
variable=variable,
|
|
86
|
-
source_id=model_names,
|
|
87
|
-
)
|
|
88
|
-
if cached_data is not None:
|
|
89
|
-
return cached_data
|
|
90
|
-
|
|
91
71
|
time_start, time_end = slices.get(pathway, DEFAULT_SSP_SLICE)
|
|
92
72
|
|
|
93
73
|
ds_dict = {}
|
|
@@ -118,15 +98,4 @@ def coordinate_cmip6_data(
|
|
|
118
98
|
|
|
119
99
|
ds_dict[name] = ds
|
|
120
100
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
cache.save_coordinate_to_cache(
|
|
124
|
-
data=datasets,
|
|
125
|
-
latitude=latitude,
|
|
126
|
-
longitude=longitude,
|
|
127
|
-
pathway=pathway,
|
|
128
|
-
variable=variable,
|
|
129
|
-
source_id=model_names,
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
return datasets
|
|
101
|
+
return dask.compute(ds_dict)[0]
|