ClimateGraph 0.4.0__tar.gz → 0.4.2__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.
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/appkernel.py +6 -2
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/data/data.py +5 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/plot/plots.py +15 -1
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/reader.py +12 -2
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/utils/dataset_utils.py +2 -1
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/utils/general_utils.py +7 -2
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph.egg-info/PKG-INFO +1 -1
- {climategraph-0.4.0 → climategraph-0.4.2}/PKG-INFO +1 -1
- {climategraph-0.4.0 → climategraph-0.4.2}/pyproject.toml +1 -1
- {climategraph-0.4.0 → climategraph-0.4.2}/tests/test_appkernel.py +41 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/__init__.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/cli.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/data/__init__.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/data/point_surface.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/data/regular_grid.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/data/satellite_swath.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/domain/__init__.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/domain/domain.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/domain/domains.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/plot/__init__.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/plot/plot.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/plot/primitive.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/plot/primitives.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/__init__.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/__init__.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/point_surface/__init__.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/point_surface/csv_base.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/point_surface/default.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/point_surface/dmc.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/point_surface/sinca.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/point_surface/single_file.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/point_surface/station_per_file.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/point_surface/variable_per_file.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/regular_grid/__init__.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/regular_grid/chimere.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/regular_grid/default.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/regular_grid/wrf.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/utils/__init__.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/utils/control_model.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/utils/parser.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/utils/registry.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/utils/resample_engine.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph.egg-info/SOURCES.txt +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph.egg-info/dependency_links.txt +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph.egg-info/entry_points.txt +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph.egg-info/requires.txt +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph.egg-info/top_level.txt +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/LICENSE +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/README.md +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/setup.cfg +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/tests/test_against_manual_filter.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/tests/test_cli.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/tests/test_parser.py +0 -0
- {climategraph-0.4.0 → climategraph-0.4.2}/tests/test_parser_2.py +0 -0
|
@@ -129,6 +129,10 @@ class AppKernel:
|
|
|
129
129
|
def _dask_client(self):
|
|
130
130
|
"""Yield a dask client, or ``None`` when no cluster was requested.
|
|
131
131
|
|
|
132
|
+
One thread per worker: libnetcdf is not thread-safe, so reads serialise
|
|
133
|
+
behind a global lock that is held per process. Threads therefore queue
|
|
134
|
+
on it while processes read in parallel.
|
|
135
|
+
|
|
132
136
|
Yields
|
|
133
137
|
------
|
|
134
138
|
dask.distributed.Client or None
|
|
@@ -138,9 +142,9 @@ class AppKernel:
|
|
|
138
142
|
return
|
|
139
143
|
from dask.distributed import Client, LocalCluster
|
|
140
144
|
|
|
141
|
-
cluster = LocalCluster(n_workers=
|
|
145
|
+
cluster = LocalCluster(n_workers=self.workers, threads_per_worker=1)
|
|
142
146
|
client = Client(cluster)
|
|
143
|
-
log.info(f"Dask Client started:
|
|
147
|
+
log.info(f"Dask Client started: {self.workers} workers x 1 thread.")
|
|
144
148
|
try:
|
|
145
149
|
yield client
|
|
146
150
|
finally:
|
|
@@ -477,6 +477,11 @@ class Data(RegistryMixin, ABC):
|
|
|
477
477
|
var_src = other.get_var(var)
|
|
478
478
|
src_geom_dims = [d for d in var_src.dims if d in set(other.geom_dims)]
|
|
479
479
|
|
|
480
|
+
# apply_ufunc needs a core dim whole, and a NetCDF4 file's internal
|
|
481
|
+
# chunking splits the geometry dims when nothing names them.
|
|
482
|
+
if var_src.chunks:
|
|
483
|
+
var_src = var_src.chunk(dict.fromkeys(src_geom_dims, -1))
|
|
484
|
+
|
|
480
485
|
resampled = xr.apply_ufunc(
|
|
481
486
|
_resample,
|
|
482
487
|
var_src,
|
|
@@ -737,17 +737,31 @@ class TimeCycleConfig(BasePlotConfig):
|
|
|
737
737
|
return self
|
|
738
738
|
# map both to hours for comparison
|
|
739
739
|
bucket_hours = {
|
|
740
|
+
"minute": 1 / 60,
|
|
740
741
|
"hour": 1,
|
|
741
742
|
"day": 24,
|
|
743
|
+
"week": 168,
|
|
742
744
|
"month": 720,
|
|
743
745
|
"dayofyear": 24,
|
|
744
746
|
"season": 2160,
|
|
747
|
+
"quarter": 2160,
|
|
745
748
|
}
|
|
746
749
|
timestep_hours = {
|
|
750
|
+
TimestepEnum.nanoseconds: 1 / 3.6e12,
|
|
751
|
+
TimestepEnum.microseconds: 1 / 3.6e9,
|
|
752
|
+
TimestepEnum.milliseconds: 1 / 3.6e6,
|
|
753
|
+
TimestepEnum.secondly: 1 / 3600,
|
|
754
|
+
TimestepEnum.minutely: 1 / 60,
|
|
747
755
|
TimestepEnum.hourly: 1,
|
|
748
756
|
TimestepEnum.daily: 24,
|
|
757
|
+
TimestepEnum.business_day: 24,
|
|
758
|
+
TimestepEnum.weekly: 168,
|
|
749
759
|
TimestepEnum.monthly: 720,
|
|
750
|
-
|
|
760
|
+
TimestepEnum.monthly_start: 720,
|
|
761
|
+
TimestepEnum.quarterly: 2160,
|
|
762
|
+
TimestepEnum.quarterly_start: 2160,
|
|
763
|
+
TimestepEnum.yearly: 8760,
|
|
764
|
+
TimestepEnum.yearly_start: 8760,
|
|
751
765
|
}
|
|
752
766
|
if timestep_hours[self.timestep] > bucket_hours[self.time_buckets.value]:
|
|
753
767
|
raise ValueError(
|
|
@@ -277,6 +277,12 @@ class Reader(ABC):
|
|
|
277
277
|
return manage_path(spec.paths, sort=spec.load_mode == "unsafe")
|
|
278
278
|
|
|
279
279
|
open_engine: str = "netcdf4"
|
|
280
|
+
# One chunk per file, every dim. Chunking runs before `_preprocess`, so dim
|
|
281
|
+
# names are still file-native here and a dim-keyed spec silently misses;
|
|
282
|
+
# a scalar sidesteps that. The alternative, deferring to a NetCDF4 file's
|
|
283
|
+
# internal layout, is often one timestep per chunk. Override per dataset
|
|
284
|
+
# with `chunks:` in the data block, keyed on the file's own dim names.
|
|
285
|
+
default_chunks: int | dict = -1
|
|
280
286
|
|
|
281
287
|
@classmethod
|
|
282
288
|
def _open_many(cls, paths: list[Path], spec: ReadSpec) -> xr.Dataset:
|
|
@@ -301,7 +307,7 @@ class Reader(ABC):
|
|
|
301
307
|
engine = spec.engine or cls.open_engine
|
|
302
308
|
return xr.open_mfdataset(
|
|
303
309
|
paths,
|
|
304
|
-
chunks=
|
|
310
|
+
chunks=spec.extras.get("chunks", cls.default_chunks),
|
|
305
311
|
engine=engine,
|
|
306
312
|
parallel=engine != "netcdf4",
|
|
307
313
|
preprocess=per_file,
|
|
@@ -311,7 +317,11 @@ class Reader(ABC):
|
|
|
311
317
|
def _open_one(cls, path: Path, spec: ReadSpec) -> Any:
|
|
312
318
|
"""Open a single file (unsafe path)."""
|
|
313
319
|
engine = spec.engine or cls.open_engine
|
|
314
|
-
return xr.open_dataset(
|
|
320
|
+
return xr.open_dataset(
|
|
321
|
+
path,
|
|
322
|
+
chunks=spec.extras.get("chunks", cls.default_chunks),
|
|
323
|
+
engine=engine,
|
|
324
|
+
)
|
|
315
325
|
|
|
316
326
|
@classmethod
|
|
317
327
|
def _to_xarray(cls, raw: Any, spec: ReadSpec) -> xr.Dataset:
|
|
@@ -243,7 +243,8 @@ def time_resampling(
|
|
|
243
243
|
ds = ds.sel({"time": slice(start, end)})
|
|
244
244
|
_record(ds, f"selected time interval {start} to {end}")
|
|
245
245
|
if timestep is not None:
|
|
246
|
-
|
|
246
|
+
freq = getattr(timestep, "value", timestep)
|
|
247
|
+
ds = getattr(ds.resample(time=freq), reduction_method.value)()
|
|
247
248
|
_record(ds, f"resampled time to {timestep} ({reduction_method.value})")
|
|
248
249
|
return ds
|
|
249
250
|
|
|
@@ -29,8 +29,13 @@ class TimestepEnum(str, Enum):
|
|
|
29
29
|
daily = "D"
|
|
30
30
|
weekly = "W"
|
|
31
31
|
monthly = "ME"
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
# Period-end spellings: pandas 2.2 deprecated "Q"/"Y" and 3.0 removed them.
|
|
33
|
+
quarterly = "QE"
|
|
34
|
+
yearly = "YE"
|
|
35
|
+
# Period-start spellings: label each bucket with its first instant.
|
|
36
|
+
monthly_start = "MS"
|
|
37
|
+
quarterly_start = "QS"
|
|
38
|
+
yearly_start = "YS"
|
|
34
39
|
hourly = "h"
|
|
35
40
|
minutely = "min"
|
|
36
41
|
secondly = "s"
|
|
@@ -102,3 +102,44 @@ class TestDebugOverride:
|
|
|
102
102
|
kernel = AppKernel()
|
|
103
103
|
kernel.run(tmp_path / "ignored.yaml", debug_override=False)
|
|
104
104
|
assert kernel.debug is False
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class TestDaskClient:
|
|
108
|
+
"""``workers`` must buy worker processes, not threads.
|
|
109
|
+
|
|
110
|
+
libnetcdf is not thread-safe, so xarray serialises reads behind a lock held
|
|
111
|
+
per process. Threads queue on it and measure slower than no cluster at all;
|
|
112
|
+
processes each hold their own and read in parallel.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
@pytest.fixture
|
|
116
|
+
def captured_cluster(self, monkeypatch):
|
|
117
|
+
captured = {}
|
|
118
|
+
|
|
119
|
+
class _FakeCluster:
|
|
120
|
+
def __init__(self, **kwargs):
|
|
121
|
+
captured.update(kwargs)
|
|
122
|
+
|
|
123
|
+
def close(self):
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
import dask.distributed
|
|
127
|
+
|
|
128
|
+
monkeypatch.setattr(dask.distributed, "LocalCluster", _FakeCluster)
|
|
129
|
+
monkeypatch.setattr(dask.distributed, "Client", lambda cluster: MagicMock())
|
|
130
|
+
return captured
|
|
131
|
+
|
|
132
|
+
def test_workers_become_processes(self, captured_cluster):
|
|
133
|
+
kernel = AppKernel()
|
|
134
|
+
kernel.workers = 8
|
|
135
|
+
with kernel._dask_client() as client:
|
|
136
|
+
assert client is not None
|
|
137
|
+
assert captured_cluster["n_workers"] == 8
|
|
138
|
+
assert captured_cluster["threads_per_worker"] == 1
|
|
139
|
+
|
|
140
|
+
def test_no_workers_means_no_cluster(self, captured_cluster):
|
|
141
|
+
kernel = AppKernel()
|
|
142
|
+
kernel.workers = None
|
|
143
|
+
with kernel._dask_client() as client:
|
|
144
|
+
assert client is None
|
|
145
|
+
assert captured_cluster == {}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/point_surface/__init__.py
RENAMED
|
File without changes
|
{climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/point_surface/csv_base.py
RENAMED
|
File without changes
|
{climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/point_surface/default.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/point_surface/single_file.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/regular_grid/__init__.py
RENAMED
|
File without changes
|
{climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/regular_grid/chimere.py
RENAMED
|
File without changes
|
{climategraph-0.4.0 → climategraph-0.4.2}/ClimateGraph/reader/reader/regular_grid/default.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|