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.
Files changed (29) hide show
  1. {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/PKG-INFO +54 -40
  2. {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/README.md +47 -32
  3. pyepwmorph-3.0.0/pyepwmorph/__init__.py +8 -0
  4. {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/models/access.py +18 -21
  5. {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/models/assemble.py +25 -12
  6. {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/models/coordinate.py +7 -38
  7. {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/models/custom.py +0 -1
  8. pyepwmorph-3.0.0/pyepwmorph/morph/procedures.py +561 -0
  9. pyepwmorph-3.0.0/pyepwmorph/tools/cache.py +324 -0
  10. {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/tools/configuration.py +51 -18
  11. {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/tools/io.py +47 -27
  12. pyepwmorph-3.0.0/pyepwmorph/tools/psychrometrics.py +185 -0
  13. pyepwmorph-3.0.0/pyepwmorph/tools/solar.py +268 -0
  14. pyepwmorph-3.0.0/pyepwmorph/tools/utilities.py +407 -0
  15. pyepwmorph-3.0.0/pyepwmorph/tools/workflow.py +448 -0
  16. pyepwmorph-3.0.0/pyproject.toml +93 -0
  17. pyepwmorph-2.1.1/pyepwmorph/__init__.py +0 -8
  18. pyepwmorph-2.1.1/pyepwmorph/morph/procedures.py +0 -563
  19. pyepwmorph-2.1.1/pyepwmorph/tools/cache.py +0 -271
  20. pyepwmorph-2.1.1/pyepwmorph/tools/ladybug_psychrometrics.py +0 -531
  21. pyepwmorph-2.1.1/pyepwmorph/tools/solar.py +0 -326
  22. pyepwmorph-2.1.1/pyepwmorph/tools/utilities.py +0 -456
  23. pyepwmorph-2.1.1/pyepwmorph/tools/workflow.py +0 -529
  24. pyepwmorph-2.1.1/pyproject.toml +0 -65
  25. {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/.gitignore +0 -0
  26. {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/LICENSE +0 -0
  27. {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/models/__init__.py +0 -0
  28. {pyepwmorph-2.1.1 → pyepwmorph-3.0.0}/pyepwmorph/morph/__init__.py +0 -0
  29. {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: 2.1.1
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: lz4>=4.0.0
24
- Requires-Dist: meteocalc>=1.1.0
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: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
44
43
  [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/downloads/)
45
- [![Version](https://img.shields.io/badge/version-2.0.0-green.svg)](https://github.com/justinfmccarty/pyepwmorph)
44
+ [![PyPI](https://img.shields.io/pypi/v/pyepwmorph.svg)](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 | SSP | Description | Expected warming |
133
- |----------|-----|-------------|------------------|
134
- | Best Case Scenario | ssp126 | Strong mitigation, renewable transition | ~1.8 C by 2100 |
135
- | Middle of the Road | ssp245 | Moderate mitigation efforts | ~2.7 C by 2100 |
136
- | Upper Middle Scenario | ssp370 | Regional rivalry, slow convergence | ~3.6 C by 2100 |
137
- | Worst Case Scenario | ssp585 | Fossil-fueled development | ~4.4 C by 2100 |
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 via specific humidity (stretch)
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 are automatically added when needed:
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 to speed up repeated analyses.
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
- ## Breaking changes in v2.0.0
189
-
190
- - **License changed** from GPL-3.0 to MIT.
191
- - **`future_years`** parameter renamed to **`target_years`** across the API.
192
- The old name is still accepted with a deprecation warning.
193
- - **`MorphConfig`** accepts new parameters: `data_source`, `custom_data`,
194
- `reference_scenario`, and `target_years`.
195
- - **EPW I/O** (`pyepwmorph.tools.io`) rewritten with stricter validation.
196
- Files that are not exactly 8760 data rows will now raise `ValueError`.
197
- The lat/lon parsing bug in the standalone `epw_location()` function has
198
- been fixed.
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
- - Internet connection (for CMIP6 data download)
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 2.0.0.
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: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
4
4
  [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/downloads/)
5
- [![Version](https://img.shields.io/badge/version-2.0.0-green.svg)](https://github.com/justinfmccarty/pyepwmorph)
5
+ [![PyPI](https://img.shields.io/pypi/v/pyepwmorph.svg)](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 | SSP | Description | Expected warming |
93
- |----------|-----|-------------|------------------|
94
- | Best Case Scenario | ssp126 | Strong mitigation, renewable transition | ~1.8 C by 2100 |
95
- | Middle of the Road | ssp245 | Moderate mitigation efforts | ~2.7 C by 2100 |
96
- | Upper Middle Scenario | ssp370 | Regional rivalry, slow convergence | ~3.6 C by 2100 |
97
- | Worst Case Scenario | ssp585 | Fossil-fueled development | ~4.4 C by 2100 |
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 via specific humidity (stretch)
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 are automatically added when needed:
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 to speed up repeated analyses.
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
- ## Breaking changes in v2.0.0
149
-
150
- - **License changed** from GPL-3.0 to MIT.
151
- - **`future_years`** parameter renamed to **`target_years`** across the API.
152
- The old name is still accepted with a deprecation warning.
153
- - **`MorphConfig`** accepts new parameters: `data_source`, `custom_data`,
154
- `reference_scenario`, and `target_years`.
155
- - **EPW I/O** (`pyepwmorph.tools.io`) rewritten with stricter validation.
156
- Files that are not exactly 8760 data rows will now raise `ValueError`.
157
- The lat/lon parsing bug in the standalone `epw_location()` function has
158
- been fixed.
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
- - Internet connection (for CMIP6 data download)
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 2.0.0.
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 coordinate.py after
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
- # return esm_data.df['source_id'].unique().tolist()
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 leverages xclim to create ensembles from the multiple model inputs downlaoded for a single pathway and variable.
4
- This is what enables to slicing of the data from a percentile point of view.
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
- from xclim import ensembles
8
- from pyepwmorph.tools import utilities
9
- import warnings
6
+ import xarray as xr
10
7
 
11
- warnings.filterwarnings("ignore")
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
- ens = ensembles.create_ensemble([ds.reset_coords(drop=True) for ds in datasets.values()])
49
- ens_perc = ensembles.ensemble_percentiles(ens, values=percentiles, split=False)
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
- percentile_dict[ptilekey] = ens_perc.sel(percentiles=ptilekey)[variable].to_dataframe()[variable].rename(ptilekey)
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
- datasets = dask.compute(ds_dict)[0]
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]
@@ -1,4 +1,3 @@
1
- # coding=utf-8
2
1
  """Load custom (non-CMIP6) climate model data from CSV files.
3
2
 
4
3
  The CSV format expected is: