wavespectra 4.6.0__tar.gz → 4.7.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.
- {wavespectra-4.6.0/wavespectra.egg-info → wavespectra-4.7.0}/PKG-INFO +1 -1
- {wavespectra-4.6.0 → wavespectra-4.7.0}/pyproject.toml +1 -1
- {wavespectra-4.6.0 → wavespectra-4.7.0}/tests/test_partition.py +16 -1
- wavespectra-4.7.0/tests/test_specdataset.py +192 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/__init__.py +3 -1
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/construct/__init__.py +1 -1
- wavespectra-4.7.0/wavespectra/core/options.py +57 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/core/utils.py +30 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/swan.py +43 -24
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/triaxys.py +4 -2
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/output/octopus.py +1 -1
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/partition/partition.py +122 -38
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/specarray.py +38 -6
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/specdataset.py +78 -3
- {wavespectra-4.6.0 → wavespectra-4.7.0/wavespectra.egg-info}/PKG-INFO +1 -1
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra.egg-info/SOURCES.txt +2 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/AUTHORS.rst +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/LICENSE.txt +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/README.rst +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/setup.cfg +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/setup.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/tests/test_cli.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/tests/test_combine_hp01.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/tests/test_cookiecutter_template.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/tests/test_reader_registry.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/tests/test_specarray.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/cli.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/construct/direction.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/construct/frequency.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/core/__init__.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/core/attributes.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/core/attributes.yml +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/core/fitting.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/core/npstats.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/core/select.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/core/swan.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/core/xrstats.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/__init__.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/awac.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/dataset.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/datawell.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/era5.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/funwave.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/funwave_new.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/json.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/ncswan.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/ndbc.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/ndbc_ascii.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/netcdf.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/obscape.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/octopus.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/spotter.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/wavespectra.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/ww3.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/ww3_station.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/wwm.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/input/xwaves.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/output/__init__.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/output/funwave.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/output/json.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/output/netcdf.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/output/orcaflex.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/output/swan.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/output/ww3.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/output/ww3.yml +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/partition/__init__.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/partition/hanson_and_phillips_2001.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/partition/specpart/specpart.c +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/partition/specpart/specpart.h +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/partition/specpart/specpart_wrap.c +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/partition/tracking.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra/plot.py +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra.egg-info/dependency_links.txt +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra.egg-info/entry_points.txt +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra.egg-info/requires.txt +0 -0
- {wavespectra-4.6.0 → wavespectra-4.7.0}/wavespectra.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wavespectra
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.7.0
|
|
4
4
|
Summary: Library for ocean wave spectra
|
|
5
5
|
Author-email: Wavespectra Developers <r.guedes@oceanum.science>
|
|
6
6
|
Maintainer-email: Rafael Guedes <r.guedes@oceanum.science>, Tom Durrant <t.durrant@oceanum.science>
|
|
@@ -2,6 +2,7 @@ import pytest
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
5
|
+
import wavespectra
|
|
5
6
|
from wavespectra import read_ww3
|
|
6
7
|
from wavespectra.partition.partition import (
|
|
7
8
|
Partition,
|
|
@@ -18,6 +19,13 @@ from wavespectra.partition.tracking import track_partitions
|
|
|
18
19
|
HERE = Path(__file__).parent
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
@pytest.fixture(autouse=True)
|
|
23
|
+
def dataset_transforms():
|
|
24
|
+
"""Run under the future dataset transforms behaviour in this module."""
|
|
25
|
+
with wavespectra.set_options(dataset_transforms=True):
|
|
26
|
+
yield
|
|
27
|
+
|
|
28
|
+
|
|
21
29
|
class BasePTM:
|
|
22
30
|
def setup_class(self):
|
|
23
31
|
self.dset = read_ww3(HERE / "sample_files/ww3file.nc")
|
|
@@ -437,11 +445,18 @@ class TestPartitionAndTrack(BasePTM):
|
|
|
437
445
|
self._track("hp01", wspd=self.dset.wspd, wdir=self.dset.wdir, dpt=self.dset.dpt)
|
|
438
446
|
|
|
439
447
|
def test_track_hp01_no_wind(self):
|
|
448
|
+
# Wind variables are resolved from the dataset so partitioning from
|
|
449
|
+
# the DataArray is required to exercise the no-wind path
|
|
450
|
+
pt = Partition(self.dset.efth)
|
|
440
451
|
with pytest.warns(UserWarning):
|
|
441
|
-
dspart =
|
|
452
|
+
dspart = pt.track(method="hp01", swells=2)
|
|
442
453
|
dspart = dspart.load()
|
|
443
454
|
assert (dspart.ntracks.values > 0).all()
|
|
444
455
|
|
|
456
|
+
def test_track_hp01_wind_from_dataset(self):
|
|
457
|
+
dspart = self.pt.track(method="hp01", swells=2).load()
|
|
458
|
+
assert (dspart.ntracks.values > 0).all()
|
|
459
|
+
|
|
445
460
|
def test_track_invalid_method(self):
|
|
446
461
|
with pytest.raises(ValueError):
|
|
447
462
|
self.pt.track(method="ptm4")
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Testing the dataset-aware behaviour of the SpecDataset accessor."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pytest
|
|
7
|
+
import xarray as xr
|
|
8
|
+
|
|
9
|
+
from wavespectra import read_ww3, set_options, get_options
|
|
10
|
+
from wavespectra.partition.partition import Partition
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
FILES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "sample_files")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture(autouse=True)
|
|
17
|
+
def dataset_transforms():
|
|
18
|
+
"""Opt in to the future dataset transforms behaviour in this module."""
|
|
19
|
+
with set_options(dataset_transforms=True):
|
|
20
|
+
yield
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
TRANSFORMS = {
|
|
24
|
+
"oned": {},
|
|
25
|
+
"split": {"fmin": 0.05, "fmax": 0.4},
|
|
26
|
+
"smooth": {},
|
|
27
|
+
"rotate": {"angle": 30},
|
|
28
|
+
"interp": {"freq": np.arange(0.04, 0.4, 0.02)},
|
|
29
|
+
"to_energy": {},
|
|
30
|
+
"scale_by_hs": {"expr": "2 * hs"},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture(scope="module")
|
|
35
|
+
def dset():
|
|
36
|
+
yield read_ww3(os.path.join(FILES_DIR, "ww3file.nc"))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.mark.parametrize("method, kwargs", TRANSFORMS.items())
|
|
40
|
+
def test_transform_returns_dataset_with_variables(dset, method, kwargs):
|
|
41
|
+
"""Dataset accessor transforms return Dataset with non-spectral variables."""
|
|
42
|
+
dsout = getattr(dset.spec, method)(**kwargs)
|
|
43
|
+
assert isinstance(dsout, xr.Dataset)
|
|
44
|
+
for name in ["wspd", "wdir", "dpt", "lat", "lon"]:
|
|
45
|
+
assert name in dsout.data_vars
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.mark.parametrize("method, kwargs", TRANSFORMS.items())
|
|
49
|
+
def test_transform_dataarray_accessor_unchanged(dset, method, kwargs):
|
|
50
|
+
"""DataArray accessor transforms still return a DataArray with same values."""
|
|
51
|
+
darr = getattr(dset.efth.spec, method)(**kwargs)
|
|
52
|
+
assert isinstance(darr, xr.DataArray)
|
|
53
|
+
dsout = getattr(dset.spec, method)(**kwargs)
|
|
54
|
+
specname = "energy" if method == "to_energy" else "efth"
|
|
55
|
+
assert dsout[specname].values == pytest.approx(darr.values, nan_ok=True)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_interp_like_returns_dataset(dset):
|
|
59
|
+
dsout = dset.spec.interp_like(dset.efth)
|
|
60
|
+
assert isinstance(dsout, xr.Dataset)
|
|
61
|
+
assert "wspd" in dsout.data_vars
|
|
62
|
+
assert isinstance(dset.efth.spec.interp_like(dset.efth), xr.DataArray)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_transform_drops_spectral_variables(dset):
|
|
66
|
+
"""Extra variables that depend on spectral dims are dropped."""
|
|
67
|
+
ds = dset.copy()
|
|
68
|
+
ds["extra"] = ds.efth * 2
|
|
69
|
+
dsout = ds.spec.smooth()
|
|
70
|
+
assert "extra" not in dsout
|
|
71
|
+
assert "wspd" in dsout.data_vars
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_transform_keeps_dataset_attrs(dset):
|
|
75
|
+
ds = dset.copy()
|
|
76
|
+
ds.attrs["title"] = "test title"
|
|
77
|
+
assert ds.spec.oned().attrs["title"] == "test title"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_scalar_params_still_dataarray(dset):
|
|
81
|
+
assert isinstance(dset.spec.hs(), xr.DataArray)
|
|
82
|
+
assert isinstance(dset.spec.stats(["hs", "tp"]), xr.Dataset)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_transform_chaining(dset):
|
|
86
|
+
dsout = dset.spec.split(fmax=0.2).spec.oned()
|
|
87
|
+
assert isinstance(dsout, xr.Dataset)
|
|
88
|
+
assert "wspd" in dsout.data_vars
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class TestPartitionDatasetAware:
|
|
92
|
+
def test_partition_returns_dataset(self, dset):
|
|
93
|
+
dspart = dset.spec.partition.ptm1(swells=2)
|
|
94
|
+
assert isinstance(dspart, xr.Dataset)
|
|
95
|
+
for name in ["wspd", "wdir", "dpt"]:
|
|
96
|
+
assert name in dspart.data_vars
|
|
97
|
+
assert "part" in dspart.efth.dims
|
|
98
|
+
|
|
99
|
+
def test_partition_wind_defaults_from_dataset(self, dset):
|
|
100
|
+
implicit = dset.spec.partition.ptm1(swells=2)
|
|
101
|
+
explicit = dset.spec.partition.ptm1(
|
|
102
|
+
wspd=dset.wspd, wdir=dset.wdir, dpt=dset.dpt, swells=2
|
|
103
|
+
)
|
|
104
|
+
xr.testing.assert_allclose(implicit, explicit)
|
|
105
|
+
|
|
106
|
+
@pytest.mark.parametrize("method", ["ptm2", "ptm4"])
|
|
107
|
+
def test_partition_wind_defaults_other_methods(self, dset, method):
|
|
108
|
+
dspart = getattr(dset.spec.partition, method)()
|
|
109
|
+
assert isinstance(dspart, xr.Dataset)
|
|
110
|
+
|
|
111
|
+
def test_partition_dataarray_returns_dataarray(self, dset):
|
|
112
|
+
dspart = dset.efth.spec.partition.ptm1(
|
|
113
|
+
wspd=dset.wspd, wdir=dset.wdir, dpt=dset.dpt, swells=2
|
|
114
|
+
)
|
|
115
|
+
assert isinstance(dspart, xr.DataArray)
|
|
116
|
+
|
|
117
|
+
def test_partition_wind_required_without_dataset(self, dset):
|
|
118
|
+
with pytest.raises(ValueError):
|
|
119
|
+
dset.efth.spec.partition.ptm1()
|
|
120
|
+
|
|
121
|
+
def test_partition_no_wind_dims(self, dset):
|
|
122
|
+
dspart = dset.spec.partition.ptm5(fcut=0.1)
|
|
123
|
+
assert isinstance(dspart, xr.Dataset)
|
|
124
|
+
assert "wspd" in dspart.data_vars
|
|
125
|
+
|
|
126
|
+
def test_track_returns_dataset_with_variables(self, dset):
|
|
127
|
+
dsout = dset.spec.partition.track(swells=2)
|
|
128
|
+
assert isinstance(dsout, xr.Dataset)
|
|
129
|
+
for name in ["efth", "track_id", "ntracks", "wspd", "wdir", "dpt"]:
|
|
130
|
+
assert name in dsout.data_vars
|
|
131
|
+
|
|
132
|
+
def test_track_dataarray_no_extra_variables(self, dset):
|
|
133
|
+
dsout = Partition(dset.efth).track(
|
|
134
|
+
wspd=dset.wspd, wdir=dset.wdir, dpt=dset.dpt, swells=2
|
|
135
|
+
)
|
|
136
|
+
assert isinstance(dsout, xr.Dataset)
|
|
137
|
+
assert "wspd" not in dsout.data_vars
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class TestDefaultBehaviour:
|
|
141
|
+
"""Without the dataset_transforms option the old behaviour is preserved."""
|
|
142
|
+
|
|
143
|
+
def test_transform_warns_and_returns_dataarray(self, dset):
|
|
144
|
+
with set_options(dataset_transforms=False):
|
|
145
|
+
with pytest.warns(FutureWarning, match="oned"):
|
|
146
|
+
dsout = dset.spec.oned()
|
|
147
|
+
assert isinstance(dsout, xr.DataArray)
|
|
148
|
+
|
|
149
|
+
def test_transform_values_unchanged(self, dset):
|
|
150
|
+
with set_options(dataset_transforms=False):
|
|
151
|
+
with pytest.warns(FutureWarning):
|
|
152
|
+
darr = dset.spec.smooth()
|
|
153
|
+
xr.testing.assert_allclose(darr, dset.efth.spec.smooth())
|
|
154
|
+
|
|
155
|
+
def test_dataarray_accessor_does_not_warn(self, dset):
|
|
156
|
+
import warnings
|
|
157
|
+
|
|
158
|
+
with set_options(dataset_transforms=False):
|
|
159
|
+
with warnings.catch_warnings():
|
|
160
|
+
warnings.simplefilter("error", FutureWarning)
|
|
161
|
+
darr = dset.efth.spec.oned()
|
|
162
|
+
assert isinstance(darr, xr.DataArray)
|
|
163
|
+
|
|
164
|
+
def test_partition_warns_and_returns_dataarray(self, dset):
|
|
165
|
+
with set_options(dataset_transforms=False):
|
|
166
|
+
with pytest.warns(FutureWarning, match="Partitioning"):
|
|
167
|
+
dspart = dset.spec.partition.ptm1(
|
|
168
|
+
wspd=dset.wspd, wdir=dset.wdir, dpt=dset.dpt, swells=2
|
|
169
|
+
)
|
|
170
|
+
assert isinstance(dspart, xr.DataArray)
|
|
171
|
+
|
|
172
|
+
def test_hp01_does_not_default_wind_from_dataset(self, dset):
|
|
173
|
+
with set_options(dataset_transforms=False):
|
|
174
|
+
with pytest.warns((FutureWarning, UserWarning)):
|
|
175
|
+
dspart = dset.spec.partition.hp01(swells=2)
|
|
176
|
+
assert isinstance(dspart, xr.DataArray)
|
|
177
|
+
# No wind classification, partition 0 is null
|
|
178
|
+
assert float(dspart.isel(part=0).sum()) == 0
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class TestSetOptions:
|
|
182
|
+
def test_option_toggles_and_restores(self, dset):
|
|
183
|
+
with set_options(dataset_transforms=False):
|
|
184
|
+
assert get_options()["dataset_transforms"] is False
|
|
185
|
+
with set_options(dataset_transforms=True):
|
|
186
|
+
assert get_options()["dataset_transforms"] is True
|
|
187
|
+
assert isinstance(dset.spec.oned(), xr.Dataset)
|
|
188
|
+
assert get_options()["dataset_transforms"] is False
|
|
189
|
+
|
|
190
|
+
def test_invalid_option_raises(self):
|
|
191
|
+
with pytest.raises(ValueError):
|
|
192
|
+
set_options(not_an_option=True)
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import warnings
|
|
9
9
|
|
|
10
|
+
from wavespectra.core.options import set_options, get_options
|
|
11
|
+
|
|
10
12
|
try:
|
|
11
13
|
from wavespectra.specdataset import SpecDataset
|
|
12
14
|
from wavespectra.specarray import SpecArray
|
|
@@ -14,7 +16,7 @@ except ImportError as exc:
|
|
|
14
16
|
warnings.warn(f"Cannot import accessors at the main module level:\n{exc}")
|
|
15
17
|
|
|
16
18
|
|
|
17
|
-
__version__ = "4.
|
|
19
|
+
__version__ = "4.7.0"
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
def _import_functions(pkgname="input", prefix="read"):
|
|
@@ -145,7 +145,7 @@ def partition_and_reconstruct(
|
|
|
145
145
|
if v not in dset.data_vars:
|
|
146
146
|
raise ValueError(f"Missing variable '{v}' in dset for partitioning.")
|
|
147
147
|
kw = dict(wspd=dset.wspd, wdir=dset.wdir, dpt=dset.dpt, swells=parts - 1)
|
|
148
|
-
dspart = getattr(dset.spec.partition, partition_method)(**kw)
|
|
148
|
+
dspart = getattr(dset[attrs.SPECNAME].spec.partition, partition_method)(**kw)
|
|
149
149
|
|
|
150
150
|
# Calculating parameters
|
|
151
151
|
stats = list(set(STATS) - set(use_defaults))
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Package-level options."""
|
|
2
|
+
|
|
3
|
+
DATASET_TRANSFORMS = "dataset_transforms"
|
|
4
|
+
|
|
5
|
+
OPTIONS = {
|
|
6
|
+
DATASET_TRANSFORMS: False,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class set_options:
|
|
11
|
+
"""Set package-level options for wavespectra.
|
|
12
|
+
|
|
13
|
+
Can be used either as a function to set options globally or as a context
|
|
14
|
+
manager to set them within a controlled block, restoring the previous
|
|
15
|
+
values when exiting the block.
|
|
16
|
+
|
|
17
|
+
Currently supported options:
|
|
18
|
+
|
|
19
|
+
- ``dataset_transforms``: If True, methods that transform the spectral
|
|
20
|
+
variable such as ``interp``, ``smooth``, ``split``, ``oned`` and the
|
|
21
|
+
partitioning methods return a Dataset preserving the non-spectral
|
|
22
|
+
variables when called from the Dataset accessor, and the ``wspd``,
|
|
23
|
+
``wdir`` and ``dpt`` arguments of the ``hp01`` and ``track``
|
|
24
|
+
partitioning methods default to the dataset variables with those names.
|
|
25
|
+
If False (default), those methods return a bare spectral DataArray and
|
|
26
|
+
emit a FutureWarning. This will become the default behaviour in
|
|
27
|
+
wavespectra 5.0.
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
>>> import wavespectra
|
|
31
|
+
>>> wavespectra.set_options(dataset_transforms=True) # global
|
|
32
|
+
>>> with wavespectra.set_options(dataset_transforms=True):
|
|
33
|
+
... dsout = dset.spec.oned() # within context only
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, **kwargs):
|
|
38
|
+
self.old = {}
|
|
39
|
+
for key, value in kwargs.items():
|
|
40
|
+
if key not in OPTIONS:
|
|
41
|
+
raise ValueError(
|
|
42
|
+
f"Argument '{key}' is not in the set of valid options "
|
|
43
|
+
f"{set(OPTIONS)}"
|
|
44
|
+
)
|
|
45
|
+
self.old[key] = OPTIONS[key]
|
|
46
|
+
OPTIONS.update(kwargs)
|
|
47
|
+
|
|
48
|
+
def __enter__(self):
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
52
|
+
OPTIONS.update(self.old)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_options():
|
|
56
|
+
"""Return a dictionary with the current package-level options."""
|
|
57
|
+
return dict(OPTIONS)
|
|
@@ -280,6 +280,36 @@ def check_same_coordinates(*args):
|
|
|
280
280
|
)
|
|
281
281
|
|
|
282
282
|
|
|
283
|
+
def dataset_from_transform(transformed, dset):
|
|
284
|
+
"""Merge transformed spectral variable back into its source dataset.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
- transformed (DataArray, Dataset): Transformed spectral variable(s)
|
|
288
|
+
calculated from the spectral variable in dset.
|
|
289
|
+
- dset (Dataset): Source dataset the transform was calculated from.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
- dsout (Dataset): Dataset with the transformed spectral variable(s)
|
|
293
|
+
and the non-spectral variables from dset.
|
|
294
|
+
|
|
295
|
+
Note:
|
|
296
|
+
- Variables in dset that depend on the spectral dims (freq, dir) are
|
|
297
|
+
dropped since their coordinates may no longer be consistent with
|
|
298
|
+
those in the transformed spectra.
|
|
299
|
+
|
|
300
|
+
"""
|
|
301
|
+
if isinstance(transformed, xr.DataArray):
|
|
302
|
+
transformed = transformed.to_dataset(name=transformed.name or attrs.SPECNAME)
|
|
303
|
+
drop_dims = [d for d in (attrs.FREQNAME, attrs.DIRNAME) if d in dset.dims]
|
|
304
|
+
others = dset.drop_dims(drop_dims)
|
|
305
|
+
others = others.drop_vars(
|
|
306
|
+
[v for v in others.data_vars if v in transformed.data_vars]
|
|
307
|
+
)
|
|
308
|
+
dsout = xr.merge([transformed, others])
|
|
309
|
+
dsout.attrs = dset.attrs
|
|
310
|
+
return dsout
|
|
311
|
+
|
|
312
|
+
|
|
283
313
|
def load_function(module_name, func_name, prefix=None):
|
|
284
314
|
"""Returns a function object from string.
|
|
285
315
|
|
|
@@ -176,8 +176,11 @@ def read_swans(
|
|
|
176
176
|
|
|
177
177
|
Args:
|
|
178
178
|
- fileglob (str, list): glob pattern specifying files to read.
|
|
179
|
-
- ndays (float): number of days to keep from each file,
|
|
180
|
-
keep entire period.
|
|
179
|
+
- ndays (float): number of days to keep from the start of each file,
|
|
180
|
+
choose None to keep the entire period. Useful for constructing
|
|
181
|
+
continuous timeseries from files of overlapping forecast cycles.
|
|
182
|
+
Reading stops once the period is read and an exception is raised
|
|
183
|
+
if a file does not span the requested period.
|
|
181
184
|
- int_freq (ndarray, bool): frequency array for interpolating onto:
|
|
182
185
|
|
|
183
186
|
- ndarray: 1d array specifying frequencies to interpolate onto.
|
|
@@ -192,8 +195,11 @@ def read_swans(
|
|
|
192
195
|
- False: No interpolation performed in direction space.
|
|
193
196
|
|
|
194
197
|
- dirorder (bool): if True ensures directions are sorted.
|
|
195
|
-
- ntimes (int):
|
|
196
|
-
|
|
198
|
+
- ntimes (int): number of times to read from the start of each file,
|
|
199
|
+
choose None to read all times. Reading stops early once the records
|
|
200
|
+
are read which makes it useful for quickly inspecting the first few
|
|
201
|
+
records of large files. Can be combined with ndays, whichever limit
|
|
202
|
+
is reached first stops the reading.
|
|
197
203
|
|
|
198
204
|
Returns:
|
|
199
205
|
- dset (SpecDataset): spectra dataset object read from file with
|
|
@@ -207,6 +213,8 @@ def read_swans(
|
|
|
207
213
|
have same number of sites.
|
|
208
214
|
- Either all or none of the spectra in fileglob must have tabfile
|
|
209
215
|
associated to provide wind/depth data.
|
|
216
|
+
- Winds and depth from tab files are sliced at the times actually
|
|
217
|
+
read when the ndays or ntimes options are used.
|
|
210
218
|
- Concatenation is done with numpy arrays for efficiency.
|
|
211
219
|
|
|
212
220
|
"""
|
|
@@ -249,16 +257,40 @@ def read_swans(
|
|
|
249
257
|
freqs = swanfile.freqs
|
|
250
258
|
dirs = swanfile.dirs
|
|
251
259
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
260
|
+
# Read spectra records, stopping early once ntimes records or ndays
|
|
261
|
+
# worth of times have been read so the rest of the file is not parsed
|
|
262
|
+
spec_list = []
|
|
263
|
+
tend = None
|
|
264
|
+
truncated = False
|
|
265
|
+
while ntimes is None or len(spec_list) < ntimes:
|
|
266
|
+
sset = swanfile.read()
|
|
267
|
+
if not sset:
|
|
268
|
+
break
|
|
269
|
+
if ndays is not None:
|
|
270
|
+
if tend is None:
|
|
271
|
+
tend = times[0] + datetime.timedelta(days=ndays)
|
|
272
|
+
if times[-1] > tend:
|
|
273
|
+
# Ignore the time parsed beyond the period and stop reading
|
|
274
|
+
times.pop()
|
|
275
|
+
truncated = True
|
|
276
|
+
break
|
|
277
|
+
spec_list.append(sset)
|
|
278
|
+
|
|
279
|
+
# Ensure file spans the requested period unless ntimes stopped first
|
|
280
|
+
ntimes_reached = ntimes is not None and len(spec_list) == ntimes
|
|
281
|
+
if tend is not None and not truncated and not ntimes_reached:
|
|
282
|
+
if times[-1] < tend:
|
|
283
|
+
raise OSError(
|
|
284
|
+
"Times in %s does not extend for %0.2f days" % (filename, ndays)
|
|
285
|
+
)
|
|
256
286
|
|
|
257
|
-
# Read tab files for winds / depth
|
|
287
|
+
# Read tab files for winds / depth, slicing at the times actually read
|
|
288
|
+
tab = pd.DataFrame()
|
|
258
289
|
if swanfile.is_tab:
|
|
259
290
|
try:
|
|
260
291
|
tab = read_tab(swanfile.tabfile).rename(columns={"dep": attrs.DEPNAME})
|
|
261
|
-
if
|
|
292
|
+
if isinstance(times, list) and set(times).issubset(tab.index):
|
|
293
|
+
tab = tab.loc[times]
|
|
262
294
|
if "X-wsp" in tab and "Y-wsp" in tab:
|
|
263
295
|
tab[attrs.WSPDNAME], tab[attrs.WDIRNAME] = uv_to_spddir(
|
|
264
296
|
tab["X-wsp"], tab["Y-wsp"], coming_from=True
|
|
@@ -277,23 +309,10 @@ def read_swans(
|
|
|
277
309
|
)
|
|
278
310
|
]
|
|
279
311
|
except Exception as exc:
|
|
312
|
+
tab = pd.DataFrame()
|
|
280
313
|
warnings.warn(
|
|
281
314
|
f"Cannot parse depth and winds from {swanfile.tabfile}:\n{exc}"
|
|
282
315
|
)
|
|
283
|
-
else:
|
|
284
|
-
tab = pd.DataFrame()
|
|
285
|
-
|
|
286
|
-
# Shrinking times
|
|
287
|
-
if ndays is not None:
|
|
288
|
-
tend = times[0] + datetime.timedelta(days=ndays)
|
|
289
|
-
if tend > times[-1]:
|
|
290
|
-
raise OSError(
|
|
291
|
-
"Times in %s does not extend for %0.2f days" % (filename, ndays)
|
|
292
|
-
)
|
|
293
|
-
iend = times.index(min(times, key=lambda d: abs(d - tend)))
|
|
294
|
-
times = times[0 : iend + 1]
|
|
295
|
-
spec_list = spec_list[0 : iend + 1]
|
|
296
|
-
tab = tab.loc[times[0] : tend] if tab is not None else tab
|
|
297
316
|
|
|
298
317
|
spec_list = flatten_list(spec_list, [])
|
|
299
318
|
|
|
@@ -10,7 +10,7 @@ import xarray as xr
|
|
|
10
10
|
|
|
11
11
|
from wavespectra.specdataset import SpecDataset
|
|
12
12
|
from wavespectra.core.attributes import attrs, set_spec_attributes
|
|
13
|
-
from wavespectra.core.utils import interp_spec
|
|
13
|
+
from wavespectra.core.utils import interp_spec, dataset_from_transform
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def read_triaxys(
|
|
@@ -47,7 +47,9 @@ def read_triaxys(
|
|
|
47
47
|
dset = dset.assign_coords({"dir": dirs + magnetic_variation})
|
|
48
48
|
# Regrid
|
|
49
49
|
if regrid_dir:
|
|
50
|
-
dset =
|
|
50
|
+
dset = dataset_from_transform(
|
|
51
|
+
dset[attrs.SPECNAME].spec.interp(dir=dirs), dset
|
|
52
|
+
)
|
|
51
53
|
return dset
|
|
52
54
|
|
|
53
55
|
|
|
@@ -107,7 +107,7 @@ def to_octopus(
|
|
|
107
107
|
dset.spec.momf(mom=1).rename("momf1"),
|
|
108
108
|
dset.spec.momf(mom=2).rename("momf2"),
|
|
109
109
|
dset.spec.momd(mom=0)[0].rename("momd"),
|
|
110
|
-
dset.spec.to_energy(),
|
|
110
|
+
dset[attrs.SPECNAME].spec.to_energy(),
|
|
111
111
|
(dset.efth.spec.df * dset.spec.momd(mom=0)[0])
|
|
112
112
|
.transpose(attrs.TIMENAME, attrs.SITENAME, attrs.FREQNAME)
|
|
113
113
|
.rename("fSpec"),
|