anemoi-datasets 0.4.0__py3-none-any.whl → 0.4.2__py3-none-any.whl
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.
- anemoi/datasets/_version.py +2 -2
- anemoi/datasets/commands/compare.py +59 -0
- anemoi/datasets/commands/create.py +84 -3
- anemoi/datasets/commands/inspect.py +3 -3
- anemoi/datasets/create/__init__.py +44 -17
- anemoi/datasets/create/check.py +6 -5
- anemoi/datasets/create/chunks.py +1 -1
- anemoi/datasets/create/config.py +5 -26
- anemoi/datasets/create/functions/filters/rename.py +9 -1
- anemoi/datasets/create/functions/filters/rotate_winds.py +10 -1
- anemoi/datasets/create/functions/sources/__init__.py +39 -0
- anemoi/datasets/create/functions/sources/accumulations.py +11 -41
- anemoi/datasets/create/functions/sources/constants.py +3 -0
- anemoi/datasets/create/functions/sources/grib.py +4 -0
- anemoi/datasets/create/functions/sources/hindcasts.py +32 -377
- anemoi/datasets/create/functions/sources/mars.py +53 -22
- anemoi/datasets/create/functions/sources/netcdf.py +2 -60
- anemoi/datasets/create/functions/sources/opendap.py +3 -2
- anemoi/datasets/create/functions/sources/xarray/__init__.py +73 -0
- anemoi/datasets/create/functions/sources/xarray/coordinates.py +234 -0
- anemoi/datasets/create/functions/sources/xarray/field.py +109 -0
- anemoi/datasets/create/functions/sources/xarray/fieldlist.py +171 -0
- anemoi/datasets/create/functions/sources/xarray/flavour.py +330 -0
- anemoi/datasets/create/functions/sources/xarray/grid.py +46 -0
- anemoi/datasets/create/functions/sources/xarray/metadata.py +161 -0
- anemoi/datasets/create/functions/sources/xarray/time.py +98 -0
- anemoi/datasets/create/functions/sources/xarray/variable.py +198 -0
- anemoi/datasets/create/functions/sources/xarray_kerchunk.py +42 -0
- anemoi/datasets/create/functions/sources/xarray_zarr.py +15 -0
- anemoi/datasets/create/functions/sources/zenodo.py +40 -0
- anemoi/datasets/create/input.py +290 -172
- anemoi/datasets/create/loaders.py +120 -71
- anemoi/datasets/create/patch.py +17 -14
- anemoi/datasets/create/persistent.py +1 -1
- anemoi/datasets/create/size.py +4 -5
- anemoi/datasets/create/statistics/__init__.py +49 -16
- anemoi/datasets/create/template.py +11 -61
- anemoi/datasets/create/trace.py +91 -0
- anemoi/datasets/create/utils.py +0 -48
- anemoi/datasets/create/zarr.py +24 -10
- anemoi/datasets/data/misc.py +9 -37
- anemoi/datasets/data/stores.py +29 -14
- anemoi/datasets/dates/__init__.py +7 -1
- anemoi/datasets/dates/groups.py +3 -0
- {anemoi_datasets-0.4.0.dist-info → anemoi_datasets-0.4.2.dist-info}/METADATA +18 -3
- anemoi_datasets-0.4.2.dist-info/RECORD +86 -0
- {anemoi_datasets-0.4.0.dist-info → anemoi_datasets-0.4.2.dist-info}/WHEEL +1 -1
- anemoi_datasets-0.4.0.dist-info/RECORD +0 -73
- {anemoi_datasets-0.4.0.dist-info → anemoi_datasets-0.4.2.dist-info}/LICENSE +0 -0
- {anemoi_datasets-0.4.0.dist-info → anemoi_datasets-0.4.2.dist-info}/entry_points.txt +0 -0
- {anemoi_datasets-0.4.0.dist-info → anemoi_datasets-0.4.2.dist-info}/top_level.txt +0 -0
anemoi/datasets/create/input.py
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
# nor does it submit to any jurisdiction.
|
|
8
8
|
#
|
|
9
9
|
import datetime
|
|
10
|
+
import itertools
|
|
10
11
|
import logging
|
|
12
|
+
import math
|
|
11
13
|
import time
|
|
12
14
|
from collections import defaultdict
|
|
13
15
|
from copy import deepcopy
|
|
@@ -15,7 +17,10 @@ from functools import cached_property
|
|
|
15
17
|
from functools import wraps
|
|
16
18
|
|
|
17
19
|
import numpy as np
|
|
20
|
+
from anemoi.utils.humanize import seconds_to_human
|
|
21
|
+
from anemoi.utils.humanize import shorten_list
|
|
18
22
|
from earthkit.data.core.fieldlist import FieldList
|
|
23
|
+
from earthkit.data.core.fieldlist import MultiFieldList
|
|
19
24
|
from earthkit.data.core.order import build_remapping
|
|
20
25
|
|
|
21
26
|
from anemoi.datasets.dates import Dates
|
|
@@ -25,29 +30,33 @@ from .template import Context
|
|
|
25
30
|
from .template import notify_result
|
|
26
31
|
from .template import resolve
|
|
27
32
|
from .template import substitute
|
|
28
|
-
from .
|
|
29
|
-
from .
|
|
30
|
-
from .
|
|
31
|
-
from .utils import seconds
|
|
33
|
+
from .trace import trace
|
|
34
|
+
from .trace import trace_datasource
|
|
35
|
+
from .trace import trace_select
|
|
32
36
|
|
|
33
37
|
LOG = logging.getLogger(__name__)
|
|
34
38
|
|
|
35
39
|
|
|
36
40
|
def parse_function_name(name):
|
|
37
|
-
if "-" in name:
|
|
38
|
-
name, delta = name.split("-")
|
|
39
|
-
sign = -1
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
name, delta = name.split("+")
|
|
43
|
-
sign = 1
|
|
42
|
+
if name.endswith("h") and name[:-1].isdigit():
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
if "-" in name:
|
|
45
|
+
name, delta = name.split("-")
|
|
46
|
+
sign = -1
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
elif "+" in name:
|
|
49
|
+
name, delta = name.split("+")
|
|
50
|
+
sign = 1
|
|
51
|
+
|
|
52
|
+
else:
|
|
53
|
+
return name, None
|
|
54
|
+
|
|
55
|
+
assert delta[-1] == "h", (name, delta)
|
|
56
|
+
delta = sign * int(delta[:-1])
|
|
57
|
+
return name, delta
|
|
58
|
+
|
|
59
|
+
return name, None
|
|
51
60
|
|
|
52
61
|
|
|
53
62
|
def time_delta_to_string(delta):
|
|
@@ -134,141 +143,6 @@ def _data_request(data):
|
|
|
134
143
|
return dict(param_level=params_levels, param_step=params_steps, area=area, grid=grid)
|
|
135
144
|
|
|
136
145
|
|
|
137
|
-
class Coords:
|
|
138
|
-
def __init__(self, owner):
|
|
139
|
-
self.owner = owner
|
|
140
|
-
|
|
141
|
-
@cached_property
|
|
142
|
-
def _build_coords(self):
|
|
143
|
-
from_data = self.owner.get_cube().user_coords
|
|
144
|
-
from_config = self.owner.context.order_by
|
|
145
|
-
|
|
146
|
-
keys_from_config = list(from_config.keys())
|
|
147
|
-
keys_from_data = list(from_data.keys())
|
|
148
|
-
assert (
|
|
149
|
-
keys_from_data == keys_from_config
|
|
150
|
-
), f"Critical error: {keys_from_data=} != {keys_from_config=}. {self.owner=}"
|
|
151
|
-
|
|
152
|
-
variables_key = list(from_config.keys())[1]
|
|
153
|
-
ensembles_key = list(from_config.keys())[2]
|
|
154
|
-
|
|
155
|
-
if isinstance(from_config[variables_key], (list, tuple)):
|
|
156
|
-
assert all([v == w for v, w in zip(from_data[variables_key], from_config[variables_key])]), (
|
|
157
|
-
from_data[variables_key],
|
|
158
|
-
from_config[variables_key],
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
self._variables = from_data[variables_key] # "param_level"
|
|
162
|
-
self._ensembles = from_data[ensembles_key] # "number"
|
|
163
|
-
|
|
164
|
-
first_field = self.owner.datasource[0]
|
|
165
|
-
grid_points = first_field.grid_points()
|
|
166
|
-
|
|
167
|
-
lats, lons = grid_points
|
|
168
|
-
north = np.amax(lats)
|
|
169
|
-
south = np.amin(lats)
|
|
170
|
-
east = np.amax(lons)
|
|
171
|
-
west = np.amin(lons)
|
|
172
|
-
|
|
173
|
-
assert -90 <= south <= north <= 90, (south, north, first_field)
|
|
174
|
-
assert (-180 <= west <= east <= 180) or (0 <= west <= east <= 360), (
|
|
175
|
-
west,
|
|
176
|
-
east,
|
|
177
|
-
first_field,
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
grid_values = list(range(len(grid_points[0])))
|
|
181
|
-
|
|
182
|
-
self._grid_points = grid_points
|
|
183
|
-
self._resolution = first_field.resolution
|
|
184
|
-
self._grid_values = grid_values
|
|
185
|
-
self._field_shape = first_field.shape
|
|
186
|
-
self._proj_string = first_field.proj_string if hasattr(first_field, "proj_string") else None
|
|
187
|
-
|
|
188
|
-
@cached_property
|
|
189
|
-
def variables(self):
|
|
190
|
-
self._build_coords
|
|
191
|
-
return self._variables
|
|
192
|
-
|
|
193
|
-
@cached_property
|
|
194
|
-
def ensembles(self):
|
|
195
|
-
self._build_coords
|
|
196
|
-
return self._ensembles
|
|
197
|
-
|
|
198
|
-
@cached_property
|
|
199
|
-
def resolution(self):
|
|
200
|
-
self._build_coords
|
|
201
|
-
return self._resolution
|
|
202
|
-
|
|
203
|
-
@cached_property
|
|
204
|
-
def grid_values(self):
|
|
205
|
-
self._build_coords
|
|
206
|
-
return self._grid_values
|
|
207
|
-
|
|
208
|
-
@cached_property
|
|
209
|
-
def grid_points(self):
|
|
210
|
-
self._build_coords
|
|
211
|
-
return self._grid_points
|
|
212
|
-
|
|
213
|
-
@cached_property
|
|
214
|
-
def field_shape(self):
|
|
215
|
-
self._build_coords
|
|
216
|
-
return self._field_shape
|
|
217
|
-
|
|
218
|
-
@cached_property
|
|
219
|
-
def proj_string(self):
|
|
220
|
-
self._build_coords
|
|
221
|
-
return self._proj_string
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
class HasCoordsMixin:
|
|
225
|
-
@cached_property
|
|
226
|
-
def variables(self):
|
|
227
|
-
return self._coords.variables
|
|
228
|
-
|
|
229
|
-
@cached_property
|
|
230
|
-
def ensembles(self):
|
|
231
|
-
return self._coords.ensembles
|
|
232
|
-
|
|
233
|
-
@cached_property
|
|
234
|
-
def resolution(self):
|
|
235
|
-
return self._coords.resolution
|
|
236
|
-
|
|
237
|
-
@cached_property
|
|
238
|
-
def grid_values(self):
|
|
239
|
-
return self._coords.grid_values
|
|
240
|
-
|
|
241
|
-
@cached_property
|
|
242
|
-
def grid_points(self):
|
|
243
|
-
return self._coords.grid_points
|
|
244
|
-
|
|
245
|
-
@cached_property
|
|
246
|
-
def field_shape(self):
|
|
247
|
-
return self._coords.field_shape
|
|
248
|
-
|
|
249
|
-
@cached_property
|
|
250
|
-
def proj_string(self):
|
|
251
|
-
return self._coords.proj_string
|
|
252
|
-
|
|
253
|
-
@cached_property
|
|
254
|
-
def shape(self):
|
|
255
|
-
return [
|
|
256
|
-
len(self.dates),
|
|
257
|
-
len(self.variables),
|
|
258
|
-
len(self.ensembles),
|
|
259
|
-
len(self.grid_values),
|
|
260
|
-
]
|
|
261
|
-
|
|
262
|
-
@cached_property
|
|
263
|
-
def coords(self):
|
|
264
|
-
return {
|
|
265
|
-
"dates": self.dates,
|
|
266
|
-
"variables": self.variables,
|
|
267
|
-
"ensembles": self.ensembles,
|
|
268
|
-
"values": self.grid_values,
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
146
|
class Action:
|
|
273
147
|
def __init__(self, context, action_path, /, *args, **kwargs):
|
|
274
148
|
if "args" in kwargs and "kwargs" in kwargs:
|
|
@@ -323,15 +197,15 @@ def shorten(dates):
|
|
|
323
197
|
return dates
|
|
324
198
|
|
|
325
199
|
|
|
326
|
-
class Result
|
|
200
|
+
class Result:
|
|
327
201
|
empty = False
|
|
202
|
+
_coords_already_built = False
|
|
328
203
|
|
|
329
204
|
def __init__(self, context, action_path, dates):
|
|
330
205
|
assert isinstance(context, ActionContext), type(context)
|
|
331
206
|
assert isinstance(action_path, list), action_path
|
|
332
207
|
|
|
333
208
|
self.context = context
|
|
334
|
-
self._coords = Coords(self)
|
|
335
209
|
self.dates = dates
|
|
336
210
|
self.action_path = action_path
|
|
337
211
|
|
|
@@ -353,19 +227,142 @@ class Result(HasCoordsMixin):
|
|
|
353
227
|
order_by = self.context.order_by
|
|
354
228
|
flatten_grid = self.context.flatten_grid
|
|
355
229
|
start = time.time()
|
|
356
|
-
LOG.
|
|
230
|
+
LOG.debug("Sorting dataset %s %s", dict(order_by), remapping)
|
|
357
231
|
assert order_by, order_by
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
232
|
+
|
|
233
|
+
patches = {"number": {None: 0}}
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
cube = ds.cube(
|
|
237
|
+
order_by,
|
|
238
|
+
remapping=remapping,
|
|
239
|
+
flatten_values=flatten_grid,
|
|
240
|
+
patches=patches,
|
|
241
|
+
)
|
|
242
|
+
cube = cube.squeeze()
|
|
243
|
+
LOG.debug(f"Sorting done in {seconds_to_human(time.time()-start)}.")
|
|
244
|
+
except ValueError:
|
|
245
|
+
self.explain(ds, order_by, remapping=remapping, patches=patches)
|
|
246
|
+
# raise ValueError(f"Error in {self}")
|
|
247
|
+
exit(1)
|
|
248
|
+
|
|
249
|
+
if LOG.isEnabledFor(logging.DEBUG):
|
|
250
|
+
LOG.debug("Cube shape: %s", cube)
|
|
251
|
+
for k, v in cube.user_coords.items():
|
|
252
|
+
LOG.debug(" %s %s", k, shorten_list(v, max_length=10))
|
|
366
253
|
|
|
367
254
|
return cube
|
|
368
255
|
|
|
256
|
+
def explain(self, ds, *args, remapping, patches):
|
|
257
|
+
|
|
258
|
+
METADATA = (
|
|
259
|
+
"date",
|
|
260
|
+
"time",
|
|
261
|
+
"step",
|
|
262
|
+
"hdate",
|
|
263
|
+
"valid_datetime",
|
|
264
|
+
"levtype",
|
|
265
|
+
"levelist",
|
|
266
|
+
"number",
|
|
267
|
+
"level",
|
|
268
|
+
"shortName",
|
|
269
|
+
"paramId",
|
|
270
|
+
"variable",
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# We redo the logic here
|
|
274
|
+
print()
|
|
275
|
+
print("❌" * 40)
|
|
276
|
+
print()
|
|
277
|
+
if len(args) == 1 and isinstance(args[0], (list, tuple)):
|
|
278
|
+
args = args[0]
|
|
279
|
+
|
|
280
|
+
names = []
|
|
281
|
+
for a in args:
|
|
282
|
+
if isinstance(a, str):
|
|
283
|
+
names.append(a)
|
|
284
|
+
elif isinstance(a, dict):
|
|
285
|
+
names += list(a.keys())
|
|
286
|
+
|
|
287
|
+
print(f"Building a {len(names)}D hypercube using", names)
|
|
288
|
+
|
|
289
|
+
ds = ds.order_by(*args, remapping=remapping, patches=patches)
|
|
290
|
+
user_coords = ds.unique_values(*names, remapping=remapping, patches=patches)
|
|
291
|
+
|
|
292
|
+
print()
|
|
293
|
+
print("Number of unique values found for each coordinate:")
|
|
294
|
+
for k, v in user_coords.items():
|
|
295
|
+
print(f" {k:20}:", len(v))
|
|
296
|
+
print()
|
|
297
|
+
user_shape = tuple(len(v) for k, v in user_coords.items())
|
|
298
|
+
print("Shape of the hypercube :", user_shape)
|
|
299
|
+
print(
|
|
300
|
+
"Number of expected fields :", math.prod(user_shape), "=", " x ".join([str(i) for i in user_shape])
|
|
301
|
+
)
|
|
302
|
+
print("Number of fields in the dataset :", len(ds))
|
|
303
|
+
print("Difference :", abs(len(ds) - math.prod(user_shape)))
|
|
304
|
+
print()
|
|
305
|
+
|
|
306
|
+
remapping = build_remapping(remapping, patches)
|
|
307
|
+
expected = set(itertools.product(*user_coords.values()))
|
|
308
|
+
|
|
309
|
+
if math.prod(user_shape) > len(ds):
|
|
310
|
+
print(f"This means that all the fields in the datasets do not exists for all combinations of {names}.")
|
|
311
|
+
|
|
312
|
+
for f in ds:
|
|
313
|
+
metadata = remapping(f.metadata)
|
|
314
|
+
expected.remove(tuple(metadata(n) for n in names))
|
|
315
|
+
|
|
316
|
+
print("Missing fields:")
|
|
317
|
+
print()
|
|
318
|
+
for i, f in enumerate(sorted(expected)):
|
|
319
|
+
print(" ", f)
|
|
320
|
+
if i >= 9 and len(expected) > 10:
|
|
321
|
+
print("...", len(expected) - i - 1, "more")
|
|
322
|
+
break
|
|
323
|
+
|
|
324
|
+
print()
|
|
325
|
+
print("To solve this issue, you can:")
|
|
326
|
+
print(
|
|
327
|
+
" - Provide a better selection, like 'step: 0' or 'level: 1000' to "
|
|
328
|
+
"reduce the number of selected fields."
|
|
329
|
+
)
|
|
330
|
+
print(
|
|
331
|
+
" - Split the 'input' part in smaller sections using 'join', "
|
|
332
|
+
"making sure that each section represent a full hypercube."
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
else:
|
|
336
|
+
print(f"More fields in dataset that expected for {names}. " "This means that some fields are duplicated.")
|
|
337
|
+
duplicated = defaultdict(list)
|
|
338
|
+
for f in ds:
|
|
339
|
+
# print(f.metadata(namespace="default"))
|
|
340
|
+
metadata = remapping(f.metadata)
|
|
341
|
+
key = tuple(metadata(n, default=None) for n in names)
|
|
342
|
+
duplicated[key].append(f)
|
|
343
|
+
|
|
344
|
+
print("Duplicated fields:")
|
|
345
|
+
print()
|
|
346
|
+
duplicated = {k: v for k, v in duplicated.items() if len(v) > 1}
|
|
347
|
+
for i, (k, v) in enumerate(sorted(duplicated.items())):
|
|
348
|
+
print(" ", k)
|
|
349
|
+
for f in v:
|
|
350
|
+
x = {k: f.metadata(k, default=None) for k in METADATA if f.metadata(k, default=None) is not None}
|
|
351
|
+
print(" ", f, x)
|
|
352
|
+
if i >= 9 and len(duplicated) > 10:
|
|
353
|
+
print("...", len(duplicated) - i - 1, "more")
|
|
354
|
+
break
|
|
355
|
+
|
|
356
|
+
print()
|
|
357
|
+
print("To solve this issue, you can:")
|
|
358
|
+
print(" - Provide a better selection, like 'step: 0' or 'level: 1000'")
|
|
359
|
+
print(" - Change the way 'param' is computed using 'variable_naming' " "in the 'build' section.")
|
|
360
|
+
|
|
361
|
+
print()
|
|
362
|
+
print("❌" * 40)
|
|
363
|
+
print()
|
|
364
|
+
exit(1)
|
|
365
|
+
|
|
369
366
|
def __repr__(self, *args, _indent_="\n", **kwargs):
|
|
370
367
|
more = ",".join([str(a)[:5000] for a in args])
|
|
371
368
|
more += ",".join([f"{k}={v}"[:5000] for k, v in kwargs.items()])
|
|
@@ -391,6 +388,109 @@ class Result(HasCoordsMixin):
|
|
|
391
388
|
def _trace_datasource(self, *args, **kwargs):
|
|
392
389
|
return f"{self.__class__.__name__}({shorten(self.dates)})"
|
|
393
390
|
|
|
391
|
+
def build_coords(self):
|
|
392
|
+
if self._coords_already_built:
|
|
393
|
+
return
|
|
394
|
+
from_data = self.get_cube().user_coords
|
|
395
|
+
from_config = self.context.order_by
|
|
396
|
+
|
|
397
|
+
keys_from_config = list(from_config.keys())
|
|
398
|
+
keys_from_data = list(from_data.keys())
|
|
399
|
+
assert keys_from_data == keys_from_config, f"Critical error: {keys_from_data=} != {keys_from_config=}. {self=}"
|
|
400
|
+
|
|
401
|
+
variables_key = list(from_config.keys())[1]
|
|
402
|
+
ensembles_key = list(from_config.keys())[2]
|
|
403
|
+
|
|
404
|
+
if isinstance(from_config[variables_key], (list, tuple)):
|
|
405
|
+
assert all([v == w for v, w in zip(from_data[variables_key], from_config[variables_key])]), (
|
|
406
|
+
from_data[variables_key],
|
|
407
|
+
from_config[variables_key],
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
self._variables = from_data[variables_key] # "param_level"
|
|
411
|
+
self._ensembles = from_data[ensembles_key] # "number"
|
|
412
|
+
|
|
413
|
+
first_field = self.datasource[0]
|
|
414
|
+
grid_points = first_field.grid_points()
|
|
415
|
+
|
|
416
|
+
lats, lons = grid_points
|
|
417
|
+
|
|
418
|
+
assert len(lats) == len(lons), (len(lats), len(lons), first_field)
|
|
419
|
+
assert len(lats) == math.prod(first_field.shape), (len(lats), first_field.shape, first_field)
|
|
420
|
+
|
|
421
|
+
north = np.amax(lats)
|
|
422
|
+
south = np.amin(lats)
|
|
423
|
+
east = np.amax(lons)
|
|
424
|
+
west = np.amin(lons)
|
|
425
|
+
|
|
426
|
+
assert -90 <= south <= north <= 90, (south, north, first_field)
|
|
427
|
+
assert (-180 <= west <= east <= 180) or (0 <= west <= east <= 360), (
|
|
428
|
+
west,
|
|
429
|
+
east,
|
|
430
|
+
first_field,
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
grid_values = list(range(len(grid_points[0])))
|
|
434
|
+
|
|
435
|
+
self._grid_points = grid_points
|
|
436
|
+
self._resolution = first_field.resolution
|
|
437
|
+
self._grid_values = grid_values
|
|
438
|
+
self._field_shape = first_field.shape
|
|
439
|
+
self._proj_string = first_field.proj_string if hasattr(first_field, "proj_string") else None
|
|
440
|
+
|
|
441
|
+
@property
|
|
442
|
+
def variables(self):
|
|
443
|
+
self.build_coords()
|
|
444
|
+
return self._variables
|
|
445
|
+
|
|
446
|
+
@property
|
|
447
|
+
def ensembles(self):
|
|
448
|
+
self.build_coords()
|
|
449
|
+
return self._ensembles
|
|
450
|
+
|
|
451
|
+
@property
|
|
452
|
+
def resolution(self):
|
|
453
|
+
self.build_coords()
|
|
454
|
+
return self._resolution
|
|
455
|
+
|
|
456
|
+
@property
|
|
457
|
+
def grid_values(self):
|
|
458
|
+
self.build_coords()
|
|
459
|
+
return self._grid_values
|
|
460
|
+
|
|
461
|
+
@property
|
|
462
|
+
def grid_points(self):
|
|
463
|
+
self.build_coords()
|
|
464
|
+
return self._grid_points
|
|
465
|
+
|
|
466
|
+
@property
|
|
467
|
+
def field_shape(self):
|
|
468
|
+
self.build_coords()
|
|
469
|
+
return self._field_shape
|
|
470
|
+
|
|
471
|
+
@property
|
|
472
|
+
def proj_string(self):
|
|
473
|
+
self.build_coords()
|
|
474
|
+
return self._proj_string
|
|
475
|
+
|
|
476
|
+
@cached_property
|
|
477
|
+
def shape(self):
|
|
478
|
+
return [
|
|
479
|
+
len(self.dates),
|
|
480
|
+
len(self.variables),
|
|
481
|
+
len(self.ensembles),
|
|
482
|
+
len(self.grid_values),
|
|
483
|
+
]
|
|
484
|
+
|
|
485
|
+
@cached_property
|
|
486
|
+
def coords(self):
|
|
487
|
+
return {
|
|
488
|
+
"dates": self.dates,
|
|
489
|
+
"variables": self.variables,
|
|
490
|
+
"ensembles": self.ensembles,
|
|
491
|
+
"values": self.grid_values,
|
|
492
|
+
}
|
|
493
|
+
|
|
394
494
|
|
|
395
495
|
class EmptyResult(Result):
|
|
396
496
|
empty = True
|
|
@@ -411,6 +511,22 @@ class EmptyResult(Result):
|
|
|
411
511
|
return []
|
|
412
512
|
|
|
413
513
|
|
|
514
|
+
def _flatten(ds):
|
|
515
|
+
if isinstance(ds, MultiFieldList):
|
|
516
|
+
return [_tidy(f) for s in ds._indexes for f in _flatten(s)]
|
|
517
|
+
return [ds]
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _tidy(ds, indent=0):
|
|
521
|
+
if isinstance(ds, MultiFieldList):
|
|
522
|
+
|
|
523
|
+
sources = [s for s in _flatten(ds) if len(s) > 0]
|
|
524
|
+
if len(sources) == 1:
|
|
525
|
+
return sources[0]
|
|
526
|
+
return MultiFieldList(sources)
|
|
527
|
+
return ds
|
|
528
|
+
|
|
529
|
+
|
|
414
530
|
class FunctionResult(Result):
|
|
415
531
|
def __init__(self, context, action_path, dates, action):
|
|
416
532
|
super().__init__(context, action_path, dates)
|
|
@@ -430,7 +546,7 @@ class FunctionResult(Result):
|
|
|
430
546
|
args, kwargs = resolve(self.context, (self.args, self.kwargs))
|
|
431
547
|
|
|
432
548
|
try:
|
|
433
|
-
return self.action.function(FunctionContext(self), self.dates, *args, **kwargs)
|
|
549
|
+
return _tidy(self.action.function(FunctionContext(self), self.dates, *args, **kwargs))
|
|
434
550
|
except Exception:
|
|
435
551
|
LOG.error(f"Error in {self.action.function.__name__}", exc_info=True)
|
|
436
552
|
raise
|
|
@@ -459,7 +575,7 @@ class JoinResult(Result):
|
|
|
459
575
|
ds = EmptyResult(self.context, self.action_path, self.dates).datasource
|
|
460
576
|
for i in self.results:
|
|
461
577
|
ds += i.datasource
|
|
462
|
-
return ds
|
|
578
|
+
return _tidy(ds)
|
|
463
579
|
|
|
464
580
|
def __repr__(self):
|
|
465
581
|
content = "\n".join([str(i) for i in self.results])
|
|
@@ -533,7 +649,7 @@ class UnShiftResult(Result):
|
|
|
533
649
|
|
|
534
650
|
ds = self.result.datasource
|
|
535
651
|
ds = FieldArray([DateShiftedField(fs, self.action.delta) for fs in ds])
|
|
536
|
-
return ds
|
|
652
|
+
return _tidy(ds)
|
|
537
653
|
|
|
538
654
|
|
|
539
655
|
class FunctionAction(Action):
|
|
@@ -620,11 +736,13 @@ class StepFunctionResult(StepResult):
|
|
|
620
736
|
@trace_datasource
|
|
621
737
|
def datasource(self):
|
|
622
738
|
try:
|
|
623
|
-
return
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
739
|
+
return _tidy(
|
|
740
|
+
self.action.function(
|
|
741
|
+
FunctionContext(self),
|
|
742
|
+
self.upstream_result.datasource,
|
|
743
|
+
*self.action.args[1:],
|
|
744
|
+
**self.action.kwargs,
|
|
745
|
+
)
|
|
628
746
|
)
|
|
629
747
|
|
|
630
748
|
except Exception:
|
|
@@ -643,7 +761,7 @@ class FilterStepResult(StepResult):
|
|
|
643
761
|
def datasource(self):
|
|
644
762
|
ds = self.upstream_result.datasource
|
|
645
763
|
ds = ds.sel(**self.action.kwargs)
|
|
646
|
-
return ds
|
|
764
|
+
return _tidy(ds)
|
|
647
765
|
|
|
648
766
|
|
|
649
767
|
class FilterStepAction(StepAction):
|
|
@@ -672,7 +790,7 @@ class ConcatResult(Result):
|
|
|
672
790
|
ds = EmptyResult(self.context, self.action_path, self.dates).datasource
|
|
673
791
|
for i in self.results:
|
|
674
792
|
ds += i.datasource
|
|
675
|
-
return ds
|
|
793
|
+
return _tidy(ds)
|
|
676
794
|
|
|
677
795
|
@property
|
|
678
796
|
def variables(self):
|
|
@@ -708,7 +826,7 @@ class DataSourcesResult(Result):
|
|
|
708
826
|
self.context.notify_result(i.action_path[:-1], i.datasource)
|
|
709
827
|
# then return the input result
|
|
710
828
|
# which can use the datasources of the included results
|
|
711
|
-
return self.input_result.datasource
|
|
829
|
+
return _tidy(self.input_result.datasource)
|
|
712
830
|
|
|
713
831
|
|
|
714
832
|
class DataSourcesAction(Action):
|