anemoi-datasets 0.5.12__py3-none-any.whl → 0.5.13__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.
Files changed (31) hide show
  1. anemoi/datasets/_version.py +2 -2
  2. anemoi/datasets/create/__init__.py +8 -4
  3. anemoi/datasets/create/check.py +1 -1
  4. anemoi/datasets/create/functions/__init__.py +15 -1
  5. anemoi/datasets/create/functions/filters/orog_to_z.py +58 -0
  6. anemoi/datasets/create/functions/filters/sum.py +71 -0
  7. anemoi/datasets/create/functions/filters/wz_to_w.py +79 -0
  8. anemoi/datasets/create/functions/sources/accumulations.py +1 -0
  9. anemoi/datasets/create/functions/sources/xarray/__init__.py +3 -3
  10. anemoi/datasets/create/functions/sources/xarray/field.py +5 -1
  11. anemoi/datasets/create/functions/sources/xarray/fieldlist.py +10 -1
  12. anemoi/datasets/create/functions/sources/xarray/metadata.py +5 -11
  13. anemoi/datasets/create/functions/sources/xarray/patch.py +44 -0
  14. anemoi/datasets/create/functions/sources/xarray/time.py +15 -0
  15. anemoi/datasets/create/functions/sources/xarray/variable.py +18 -2
  16. anemoi/datasets/create/input/repeated_dates.py +18 -0
  17. anemoi/datasets/create/statistics/__init__.py +2 -2
  18. anemoi/datasets/create/utils.py +4 -0
  19. anemoi/datasets/data/complement.py +164 -0
  20. anemoi/datasets/data/dataset.py +68 -5
  21. anemoi/datasets/data/ensemble.py +55 -0
  22. anemoi/datasets/data/join.py +1 -2
  23. anemoi/datasets/data/merge.py +3 -0
  24. anemoi/datasets/data/misc.py +10 -1
  25. anemoi/datasets/grids.py +23 -10
  26. {anemoi_datasets-0.5.12.dist-info → anemoi_datasets-0.5.13.dist-info}/METADATA +2 -2
  27. {anemoi_datasets-0.5.12.dist-info → anemoi_datasets-0.5.13.dist-info}/RECORD +31 -26
  28. {anemoi_datasets-0.5.12.dist-info → anemoi_datasets-0.5.13.dist-info}/WHEEL +1 -1
  29. {anemoi_datasets-0.5.12.dist-info → anemoi_datasets-0.5.13.dist-info}/LICENSE +0 -0
  30. {anemoi_datasets-0.5.12.dist-info → anemoi_datasets-0.5.13.dist-info}/entry_points.txt +0 -0
  31. {anemoi_datasets-0.5.12.dist-info → anemoi_datasets-0.5.13.dist-info}/top_level.txt +0 -0
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.5.12'
16
- __version_tuple__ = version_tuple = (0, 5, 12)
15
+ __version__ = version = '0.5.13'
16
+ __version_tuple__ = version_tuple = (0, 5, 13)
@@ -622,10 +622,14 @@ class Load(Actor, HasRegistryMixin, HasStatisticTempMixin, HasElementForDataMixi
622
622
 
623
623
  check_shape(cube, dates, dates_in_data)
624
624
 
625
- def check_dates_in_data(lst, lst2):
626
- lst2 = [np.datetime64(_) for _ in lst2]
627
- lst = [np.datetime64(_) for _ in lst]
628
- assert lst == lst2, ("Dates in data are not the requested ones:", lst, lst2)
625
+ def check_dates_in_data(dates_in_data, requested_dates):
626
+ requested_dates = [np.datetime64(_) for _ in requested_dates]
627
+ dates_in_data = [np.datetime64(_) for _ in dates_in_data]
628
+ assert dates_in_data == requested_dates, (
629
+ "Dates in data are not the requested ones:",
630
+ dates_in_data,
631
+ requested_dates,
632
+ )
629
633
 
630
634
  check_dates_in_data(dates_in_data, dates)
631
635
 
@@ -58,7 +58,7 @@ class DatasetName:
58
58
  raise ValueError(self.error_message)
59
59
 
60
60
  def _parse(self, name):
61
- pattern = r"^(\w+)-([\w-]+)-(\w+)-(\w+)-(\d\d\d\d)-(\d\d\d\d)-(\d+h)-v(\d+)-?([a-zA-Z0-9-]+)?$"
61
+ pattern = r"^(\w+)-([\w-]+)-(\w+)-(\w+)-(\d\d\d\d)-(\d\d\d\d)-(\d+h|\d+m)-v(\d+)-?([a-zA-Z0-9-]+)?$"
62
62
  match = re.match(pattern, name)
63
63
 
64
64
  if not match:
@@ -22,6 +22,7 @@ def assert_is_fieldlist(obj):
22
22
  def import_function(name, kind):
23
23
 
24
24
  from anemoi.transform.filters import filter_registry
25
+ from anemoi.transform.sources import source_registry
25
26
 
26
27
  name = name.replace("-", "_")
27
28
 
@@ -45,7 +46,20 @@ def import_function(name, kind):
45
46
  if filter_registry.lookup(name, return_none=True):
46
47
 
47
48
  def proc(context, data, *args, **kwargs):
48
- return filter_registry.create(name, *args, **kwargs)(data)
49
+ filter = filter_registry.create(name, *args, **kwargs)
50
+ filter.context = context
51
+ # filter = filter_registry.create(context, name, *args, **kwargs)
52
+ return filter.forward(data)
53
+
54
+ return proc
55
+
56
+ if kind == "sources":
57
+ if source_registry.lookup(name, return_none=True):
58
+
59
+ def proc(context, data, *args, **kwargs):
60
+ source = source_registry.create(name, *args, **kwargs)
61
+ # source = source_registry.create(context, name, *args, **kwargs)
62
+ return source.forward(data)
49
63
 
50
64
  return proc
51
65
 
@@ -0,0 +1,58 @@
1
+ # (C) Copyright 2024 Anemoi contributors.
2
+ #
3
+ # This software is licensed under the terms of the Apache Licence Version 2.0
4
+ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5
+ #
6
+ # In applying this licence, ECMWF does not waive the privileges and immunities
7
+ # granted to it by virtue of its status as an intergovernmental organisation
8
+ # nor does it submit to any jurisdiction.
9
+
10
+
11
+ from collections import defaultdict
12
+
13
+ from earthkit.data.indexing.fieldlist import FieldArray
14
+
15
+
16
+ class NewDataField:
17
+ def __init__(self, field, data, new_name):
18
+ self.field = field
19
+ self.data = data
20
+ self.new_name = new_name
21
+
22
+ def to_numpy(self, *args, **kwargs):
23
+ return self.data
24
+
25
+ def metadata(self, key=None, **kwargs):
26
+ if key is None:
27
+ return self.field.metadata(**kwargs)
28
+
29
+ value = self.field.metadata(key, **kwargs)
30
+ if key == "param":
31
+ return self.new_name
32
+ return value
33
+
34
+ def __getattr__(self, name):
35
+ return getattr(self.field, name)
36
+
37
+
38
+ def execute(context, input, orog, z="z"):
39
+ """Convert orography [m] to z (geopotential height)"""
40
+ result = FieldArray()
41
+
42
+ processed_fields = defaultdict(dict)
43
+
44
+ for f in input:
45
+ key = f.metadata(namespace="mars")
46
+ param = key.pop("param")
47
+ if param == orog:
48
+ key = tuple(key.items())
49
+
50
+ if param in processed_fields[key]:
51
+ raise ValueError(f"Duplicate field {param} for {key}")
52
+
53
+ output = f.to_numpy(flatten=True) * 9.80665
54
+ result.append(NewDataField(f, output, z))
55
+ else:
56
+ result.append(f)
57
+
58
+ return result
@@ -0,0 +1,71 @@
1
+ # (C) Copyright 2024 Anemoi contributors.
2
+ #
3
+ # This software is licensed under the terms of the Apache Licence Version 2.0
4
+ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5
+ #
6
+ # In applying this licence, ECMWF does not waive the privileges and immunities
7
+ # granted to it by virtue of its status as an intergovernmental organisation
8
+ # nor does it submit to any jurisdiction.
9
+
10
+
11
+ from collections import defaultdict
12
+
13
+ from earthkit.data.indexing.fieldlist import FieldArray
14
+
15
+
16
+ class NewDataField:
17
+ def __init__(self, field, data, new_name):
18
+ self.field = field
19
+ self.data = data
20
+ self.new_name = new_name
21
+
22
+ def to_numpy(self, *args, **kwargs):
23
+ return self.data
24
+
25
+ def metadata(self, key=None, **kwargs):
26
+ if key is None:
27
+ return self.field.metadata(**kwargs)
28
+
29
+ value = self.field.metadata(key, **kwargs)
30
+ if key == "param":
31
+ return self.new_name
32
+ return value
33
+
34
+ def __getattr__(self, name):
35
+ return getattr(self.field, name)
36
+
37
+
38
+ def execute(context, input, params, output):
39
+ """Computes the sum over a set of variables"""
40
+ result = FieldArray()
41
+
42
+ needed_fields = defaultdict(dict)
43
+
44
+ for f in input:
45
+ key = f.metadata(namespace="mars")
46
+ param = key.pop("param")
47
+ if param in params:
48
+ key = tuple(key.items())
49
+
50
+ if param in needed_fields[key]:
51
+ raise ValueError(f"Duplicate field {param} for {key}")
52
+
53
+ needed_fields[key][param] = f
54
+ else:
55
+ result.append(f)
56
+
57
+ for keys, values in needed_fields.items():
58
+
59
+ if len(values) != len(params):
60
+ raise ValueError("Missing fields")
61
+
62
+ s = None
63
+ for k, v in values.items():
64
+ c = v.to_numpy(flatten=True)
65
+ if s is None:
66
+ s = c
67
+ else:
68
+ s += c
69
+ result.append(NewDataField(values[list(values.keys())[0]], s, output))
70
+
71
+ return result
@@ -0,0 +1,79 @@
1
+ # (C) Copyright 2024 Anemoi contributors.
2
+ #
3
+ # This software is licensed under the terms of the Apache Licence Version 2.0
4
+ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5
+ #
6
+ # In applying this licence, ECMWF does not waive the privileges and immunities
7
+ # granted to it by virtue of its status as an intergovernmental organisation
8
+ # nor does it submit to any jurisdiction.
9
+
10
+
11
+ from collections import defaultdict
12
+
13
+ from earthkit.data.indexing.fieldlist import FieldArray
14
+
15
+
16
+ class NewDataField:
17
+ def __init__(self, field, data, new_name):
18
+ self.field = field
19
+ self.data = data
20
+ self.new_name = new_name
21
+
22
+ def to_numpy(self, *args, **kwargs):
23
+ return self.data
24
+
25
+ def metadata(self, key=None, **kwargs):
26
+ if key is None:
27
+ return self.field.metadata(**kwargs)
28
+
29
+ value = self.field.metadata(key, **kwargs)
30
+ if key == "param":
31
+ return self.new_name
32
+ return value
33
+
34
+ def __getattr__(self, name):
35
+ return getattr(self.field, name)
36
+
37
+
38
+ def execute(context, input, wz, t, w="w"):
39
+ """Convert geometric vertical velocity (m/s) to vertical velocity (Pa / s)"""
40
+ result = FieldArray()
41
+
42
+ params = (wz, t)
43
+ pairs = defaultdict(dict)
44
+
45
+ for f in input:
46
+ key = f.metadata(namespace="mars")
47
+ param = key.pop("param")
48
+ if param in params:
49
+ key = tuple(key.items())
50
+
51
+ if param in pairs[key]:
52
+ raise ValueError(f"Duplicate field {param} for {key}")
53
+
54
+ pairs[key][param] = f
55
+ if param == t:
56
+ result.append(f)
57
+ else:
58
+ result.append(f)
59
+
60
+ for keys, values in pairs.items():
61
+
62
+ if len(values) != 2:
63
+ raise ValueError("Missing fields")
64
+
65
+ wz_pl = values[wz].to_numpy(flatten=True)
66
+ t_pl = values[t].to_numpy(flatten=True)
67
+ pressure = keys[4][1] * 100 # TODO: REMOVE HARDCODED INDICES
68
+
69
+ w_pl = wz_to_w(wz_pl, t_pl, pressure)
70
+ result.append(NewDataField(values[wz], w_pl, w))
71
+
72
+ return result
73
+
74
+
75
+ def wz_to_w(wz, t, pressure):
76
+ g = 9.81
77
+ Rd = 287.058
78
+
79
+ return -wz * g * pressure / (t * Rd)
@@ -379,6 +379,7 @@ def accumulations(context, dates, **request):
379
379
  KWARGS = {
380
380
  ("od", "oper"): dict(patch=_scda),
381
381
  ("od", "elda"): dict(base_times=(6, 18)),
382
+ ("od", "enfo"): dict(base_times=(0, 6, 12, 18)),
382
383
  ("ea", "oper"): dict(data_accumulation_period=1, base_times=(6, 18)),
383
384
  ("ea", "enda"): dict(data_accumulation_period=3, base_times=(6, 18)),
384
385
  ("rr", "oper"): dict(base_times=(0, 3, 6, 9, 12, 15, 18, 21)),
@@ -29,7 +29,7 @@ def check(what, ds, paths, **kwargs):
29
29
  raise ValueError(f"Expected {count} fields, got {len(ds)} (kwargs={kwargs}, {what}s={paths})")
30
30
 
31
31
 
32
- def load_one(emoji, context, dates, dataset, options={}, flavour=None, **kwargs):
32
+ def load_one(emoji, context, dates, dataset, *, options={}, flavour=None, patch=None, **kwargs):
33
33
  import xarray as xr
34
34
 
35
35
  """
@@ -54,10 +54,10 @@ def load_one(emoji, context, dates, dataset, options={}, flavour=None, **kwargs)
54
54
  else:
55
55
  data = xr.open_dataset(dataset, **options)
56
56
 
57
- fs = XarrayFieldList.from_xarray(data, flavour)
57
+ fs = XarrayFieldList.from_xarray(data, flavour=flavour, patch=patch)
58
58
 
59
59
  if len(dates) == 0:
60
- return fs.sel(**kwargs)
60
+ result = fs.sel(**kwargs)
61
61
  else:
62
62
  result = MultiFieldList([fs.sel(valid_datetime=date, **kwargs) for date in dates])
63
63
 
@@ -92,6 +92,10 @@ class XArrayField(Field):
92
92
  def grid_points(self):
93
93
  return self.owner.grid_points()
94
94
 
95
+ def to_latlon(self, flatten=True):
96
+ assert flatten
97
+ return dict(lat=self.latitudes, lon=self.longitudes)
98
+
95
99
  @property
96
100
  def resolution(self):
97
101
  return None
@@ -120,6 +124,6 @@ class XArrayField(Field):
120
124
  def __repr__(self):
121
125
  return repr(self._metadata)
122
126
 
123
- def _values(self):
127
+ def _values(self, dtype=None):
124
128
  # we don't use .values as this will download the data
125
129
  return self.selection
@@ -16,6 +16,7 @@ from earthkit.data.core.fieldlist import FieldList
16
16
 
17
17
  from .field import EmptyFieldList
18
18
  from .flavour import CoordinateGuesser
19
+ from .patch import patch_dataset
19
20
  from .time import Time
20
21
  from .variable import FilteredVariable
21
22
  from .variable import Variable
@@ -49,7 +50,11 @@ class XarrayFieldList(FieldList):
49
50
  raise IndexError(k)
50
51
 
51
52
  @classmethod
52
- def from_xarray(cls, ds, flavour=None):
53
+ def from_xarray(cls, ds, *, flavour=None, patch=None):
54
+
55
+ if patch is not None:
56
+ ds = patch_dataset(ds, patch)
57
+
53
58
  variables = []
54
59
 
55
60
  if isinstance(flavour, str):
@@ -83,6 +88,8 @@ class XarrayFieldList(FieldList):
83
88
  _skip_attr(variable, "bounds")
84
89
  _skip_attr(variable, "grid_mapping")
85
90
 
91
+ LOG.debug("Xarray data_vars: %s", ds.data_vars)
92
+
86
93
  # Select only geographical variables
87
94
  for name in ds.data_vars:
88
95
 
@@ -97,6 +104,7 @@ class XarrayFieldList(FieldList):
97
104
  c = guess.guess(ds[coord], coord)
98
105
  assert c, f"Could not guess coordinate for {coord}"
99
106
  if coord not in variable.dims:
107
+ LOG.debug("%s: coord=%s (not a dimension): dims=%s", variable, coord, variable.dims)
100
108
  c.is_dim = False
101
109
  coordinates.append(c)
102
110
 
@@ -104,6 +112,7 @@ class XarrayFieldList(FieldList):
104
112
  assert grid_coords <= 2
105
113
 
106
114
  if grid_coords < 2:
115
+ LOG.debug("Skipping %s (not 2D): %s", variable, [(c, c.is_grid, c.is_dim) for c in coordinates])
107
116
  continue
108
117
 
109
118
  v = Variable(
@@ -24,6 +24,7 @@ class _MDMapping:
24
24
  def __init__(self, variable):
25
25
  self.variable = variable
26
26
  self.time = variable.time
27
+ # Aliases
27
28
  self.mapping = dict(param="variable")
28
29
  for c in variable.coordinates:
29
30
  for v in c.mars_names:
@@ -34,7 +35,6 @@ class _MDMapping:
34
35
  return self.mapping.get(key, key)
35
36
 
36
37
  def from_user(self, kwargs):
37
- print("from_user", kwargs, self)
38
38
  return {self._from_user(k): v for k, v in kwargs.items()}
39
39
 
40
40
  def __repr__(self):
@@ -81,22 +81,16 @@ class XArrayMetadata(RawMetadata):
81
81
  def _valid_datetime(self):
82
82
  return self._get("valid_datetime")
83
83
 
84
- def _get(self, key, **kwargs):
84
+ def get(self, key, astype=None, **kwargs):
85
85
 
86
86
  if key in self._d:
87
+ if astype is not None:
88
+ return astype(self._d[key])
87
89
  return self._d[key]
88
90
 
89
- if key.startswith("mars."):
90
- key = key[5:]
91
- if key not in self.MARS_KEYS:
92
- if kwargs.get("raise_on_missing", False):
93
- raise KeyError(f"Invalid key '{key}' in namespace='mars'")
94
- else:
95
- return kwargs.get("default", None)
96
-
97
91
  key = self._mapping._from_user(key)
98
92
 
99
- return super()._get(key, **kwargs)
93
+ return super().get(key, astype=astype, **kwargs)
100
94
 
101
95
 
102
96
  class XArrayFieldGeography(Geography):
@@ -0,0 +1,44 @@
1
+ # (C) Copyright 2024 Anemoi contributors.
2
+ #
3
+ # This software is licensed under the terms of the Apache Licence Version 2.0
4
+ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5
+ #
6
+ # In applying this licence, ECMWF does not waive the privileges and immunities
7
+ # granted to it by virtue of its status as an intergovernmental organisation
8
+ # nor does it submit to any jurisdiction.
9
+
10
+
11
+ import logging
12
+
13
+ LOG = logging.getLogger(__name__)
14
+
15
+
16
+ def patch_attributes(ds, attributes):
17
+ for name, value in attributes.items():
18
+ variable = ds[name]
19
+ variable.attrs.update(value)
20
+
21
+ return ds
22
+
23
+
24
+ def patch_coordinates(ds, coordinates):
25
+ for name in coordinates:
26
+ ds = ds.assign_coords({name: ds[name]})
27
+
28
+ return ds
29
+
30
+
31
+ PATCHES = {
32
+ "attributes": patch_attributes,
33
+ "coordinates": patch_coordinates,
34
+ }
35
+
36
+
37
+ def patch_dataset(ds, patch):
38
+ for what, values in patch.items():
39
+ if what not in PATCHES:
40
+ raise ValueError(f"Unknown patch type {what!r}")
41
+
42
+ ds = PATCHES[what](ds, values)
43
+
44
+ return ds
@@ -62,12 +62,18 @@ class Time:
62
62
 
63
63
  raise NotImplementedError(f"{len(date_coordinate)=} {len(time_coordinate)=} {len(step_coordinate)=}")
64
64
 
65
+ def select_valid_datetime(self, variable):
66
+ raise NotImplementedError(f"{self.__class__.__name__}.select_valid_datetime()")
67
+
65
68
 
66
69
  class Constant(Time):
67
70
 
68
71
  def fill_time_metadata(self, coords_values, metadata):
69
72
  return None
70
73
 
74
+ def select_valid_datetime(self, variable):
75
+ return None
76
+
71
77
 
72
78
  class Analysis(Time):
73
79
 
@@ -83,6 +89,9 @@ class Analysis(Time):
83
89
 
84
90
  return valid_datetime
85
91
 
92
+ def select_valid_datetime(self, variable):
93
+ return self.time_coordinate_name
94
+
86
95
 
87
96
  class ForecastFromValidTimeAndStep(Time):
88
97
 
@@ -116,6 +125,9 @@ class ForecastFromValidTimeAndStep(Time):
116
125
 
117
126
  return valid_datetime
118
127
 
128
+ def select_valid_datetime(self, variable):
129
+ return self.time_coordinate_name
130
+
119
131
 
120
132
  class ForecastFromValidTimeAndBaseTime(Time):
121
133
 
@@ -138,6 +150,9 @@ class ForecastFromValidTimeAndBaseTime(Time):
138
150
 
139
151
  return valid_datetime
140
152
 
153
+ def select_valid_datetime(self, variable):
154
+ return self.time_coordinate_name
155
+
141
156
 
142
157
  class ForecastFromBaseTimeAndDate(Time):
143
158
 
@@ -37,7 +37,7 @@ class Variable:
37
37
  self.coordinates = coordinates
38
38
 
39
39
  self._metadata = metadata.copy()
40
- self._metadata.update({"variable": variable.name})
40
+ self._metadata.update({"variable": variable.name, "param": variable.name})
41
41
 
42
42
  self.time = time
43
43
 
@@ -45,6 +45,9 @@ class Variable:
45
45
  self.names = {c.variable.name: c for c in coordinates if c.is_dim and not c.scalar and not c.is_grid}
46
46
  self.by_name = {c.variable.name: c for c in coordinates}
47
47
 
48
+ # We need that alias for the time dimension
49
+ self._aliases = dict(valid_datetime="time")
50
+
48
51
  self.length = math.prod(self.shape)
49
52
 
50
53
  @property
@@ -96,15 +99,28 @@ class Variable:
96
99
 
97
100
  k, v = kwargs.popitem()
98
101
 
102
+ user_provided_k = k
103
+
104
+ if k == "valid_datetime":
105
+ # Ask the Time object to select the valid datetime
106
+ k = self.time.select_valid_datetime(self)
107
+ if k is None:
108
+ return None
109
+
99
110
  c = self.by_name.get(k)
100
111
 
112
+ # assert c is not None, f"Could not find coordinate {k} in {self.variable.name} {self.coordinates} {list(self.by_name)}"
113
+
101
114
  if c is None:
102
115
  missing[k] = v
103
116
  return self.sel(missing, **kwargs)
104
117
 
105
118
  i = c.index(v)
106
119
  if i is None:
107
- LOG.warning(f"Could not find {k}={v} in {c}")
120
+ if k != user_provided_k:
121
+ LOG.warning(f"Could not find {user_provided_k}={v} in {c} (alias of {k})")
122
+ else:
123
+ LOG.warning(f"Could not find {k}={v} in {c}")
108
124
  return None
109
125
 
110
126
  coordinates = [x.reduced(i) if c is x else x for x in self.coordinates]
@@ -72,6 +72,11 @@ class DateMapperClosest(DateMapper):
72
72
  end += self.frequency
73
73
 
74
74
  to_try = sorted(to_try - self.tried)
75
+ info = {k: "no-data" for k in to_try}
76
+
77
+ if not to_try:
78
+ LOG.warning(f"No new dates to try for {group_of_dates} in {self.source}")
79
+ # return []
75
80
 
76
81
  if to_try:
77
82
  result = self.source.select(
@@ -82,19 +87,32 @@ class DateMapperClosest(DateMapper):
82
87
  )
83
88
  )
84
89
 
90
+ cnt = 0
85
91
  for f in result.datasource:
92
+ cnt += 1
86
93
  # We could keep the fields in a dictionary, but we don't want to keep the fields in memory
87
94
  date = as_datetime(f.metadata("valid_datetime"))
88
95
 
89
96
  if self.skip_all_nans:
90
97
  if np.isnan(f.to_numpy()).all():
91
98
  LOG.warning(f"Skipping {date} because all values are NaN")
99
+ info[date] = "all-nans"
92
100
  continue
93
101
 
102
+ info[date] = "ok"
94
103
  self.found.add(date)
95
104
 
105
+ if cnt == 0:
106
+ raise ValueError(f"No data found for {group_of_dates} in {self.source}")
107
+
96
108
  self.tried.update(to_try)
97
109
 
110
+ if not self.found:
111
+ for k, v in info.items():
112
+ LOG.warning(f"{k}: {v}")
113
+
114
+ raise ValueError(f"No matching data found for {asked_dates} in {self.source}")
115
+
98
116
  new_dates = defaultdict(list)
99
117
 
100
118
  for date in asked_dates:
@@ -98,7 +98,7 @@ def fix_variance(x, name, count, sums, squares):
98
98
 
99
99
  variances = squares / count - mean * mean
100
100
  assert variances.shape == squares.shape == mean.shape
101
- if all(variances >= 0):
101
+ if np.all(variances >= 0):
102
102
  LOG.warning(f"All individual variances for {name} are positive, setting variance to 0.")
103
103
  return 0
104
104
 
@@ -108,7 +108,7 @@ def fix_variance(x, name, count, sums, squares):
108
108
  # return 0
109
109
 
110
110
  LOG.warning(f"ERROR at least one individual variance is negative ({np.nanmin(variances)}).")
111
- return x
111
+ return 0
112
112
 
113
113
 
114
114
  def check_variance(x, variables_names, minimum, maximum, mean, count, sums, squares):
@@ -54,6 +54,10 @@ def to_datetime(*args, **kwargs):
54
54
 
55
55
 
56
56
  def make_list_int(value):
57
+ # Convert a string like "1/2/3" or "1/to/3" or "1/to/10/by/2" to a list of integers.
58
+ # Moved to anemoi.utils.humanize
59
+ # replace with from anemoi.utils.humanize import make_list_int
60
+ # when anemoi-utils is released and pyproject.toml is updated
57
61
  if isinstance(value, str):
58
62
  if "/" not in value:
59
63
  return [value]
@@ -0,0 +1,164 @@
1
+ # (C) Copyright 2024 Anemoi contributors.
2
+ #
3
+ # This software is licensed under the terms of the Apache Licence Version 2.0
4
+ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5
+ #
6
+ # In applying this licence, ECMWF does not waive the privileges and immunities
7
+ # granted to it by virtue of its status as an intergovernmental organisation
8
+ # nor does it submit to any jurisdiction.
9
+
10
+
11
+ import logging
12
+ from functools import cached_property
13
+
14
+ from ..grids import nearest_grid_points
15
+ from .debug import Node
16
+ from .forwards import Combined
17
+ from .indexing import apply_index_to_slices_changes
18
+ from .indexing import index_to_slices
19
+ from .indexing import update_tuple
20
+ from .misc import _auto_adjust
21
+ from .misc import _open
22
+
23
+ LOG = logging.getLogger(__name__)
24
+
25
+
26
+ class Complement(Combined):
27
+
28
+ def __init__(self, target, source, what="variables", interpolation="nearest"):
29
+ super().__init__([target, source])
30
+
31
+ # We had the variables of dataset[1] to dataset[0]
32
+ # interpoated on the grid of dataset[0]
33
+
34
+ self.target = target
35
+ self.source = source
36
+
37
+ self._variables = []
38
+
39
+ # Keep the same order as the original dataset
40
+ for v in self.source.variables:
41
+ if v not in self.target.variables:
42
+ self._variables.append(v)
43
+
44
+ if not self._variables:
45
+ raise ValueError("Augment: no missing variables")
46
+
47
+ @property
48
+ def variables(self):
49
+ return self._variables
50
+
51
+ @property
52
+ def name_to_index(self):
53
+ return {v: i for i, v in enumerate(self.variables)}
54
+
55
+ @property
56
+ def shape(self):
57
+ shape = self.target.shape
58
+ return (shape[0], len(self._variables)) + shape[2:]
59
+
60
+ @property
61
+ def variables_metadata(self):
62
+ return {k: v for k, v in self.source.variables_metadata.items() if k in self._variables}
63
+
64
+ def check_same_variables(self, d1, d2):
65
+ pass
66
+
67
+ @cached_property
68
+ def missing(self):
69
+ missing = self.source.missing.copy()
70
+ missing = missing | self.target.missing
71
+ return set(missing)
72
+
73
+ def tree(self):
74
+ """Generates a hierarchical tree structure for the `Cutout` instance and
75
+ its associated datasets.
76
+
77
+ Returns:
78
+ Node: A `Node` object representing the `Cutout` instance as the root
79
+ node, with each dataset in `self.datasets` represented as a child
80
+ node.
81
+ """
82
+ return Node(self, [d.tree() for d in (self.target, self.source)])
83
+
84
+ def __getitem__(self, index):
85
+ if isinstance(index, (int, slice)):
86
+ index = (index, slice(None), slice(None), slice(None))
87
+ return self._get_tuple(index)
88
+
89
+
90
+ class ComplementNone(Complement):
91
+
92
+ def __init__(self, target, source):
93
+ super().__init__(target, source)
94
+
95
+ def _get_tuple(self, index):
96
+ index, changes = index_to_slices(index, self.shape)
97
+ result = self.source[index]
98
+ return apply_index_to_slices_changes(result, changes)
99
+
100
+
101
+ class ComplementNearest(Complement):
102
+
103
+ def __init__(self, target, source):
104
+ super().__init__(target, source)
105
+
106
+ self._nearest_grid_points = nearest_grid_points(
107
+ self.source.latitudes,
108
+ self.source.longitudes,
109
+ self.target.latitudes,
110
+ self.target.longitudes,
111
+ )
112
+
113
+ def check_compatibility(self, d1, d2):
114
+ pass
115
+
116
+ def _get_tuple(self, index):
117
+ variable_index = 1
118
+ index, changes = index_to_slices(index, self.shape)
119
+ index, previous = update_tuple(index, variable_index, slice(None))
120
+ source_index = [self.source.name_to_index[x] for x in self.variables[previous]]
121
+ source_data = self.source[index[0], source_index, index[2], ...]
122
+ target_data = source_data[..., self._nearest_grid_points]
123
+
124
+ result = target_data[..., index[3]]
125
+
126
+ return apply_index_to_slices_changes(result, changes)
127
+
128
+
129
+ def complement_factory(args, kwargs):
130
+ from .select import Select
131
+
132
+ assert len(args) == 0, args
133
+
134
+ source = kwargs.pop("source")
135
+ target = kwargs.pop("complement")
136
+ what = kwargs.pop("what", "variables")
137
+ interpolation = kwargs.pop("interpolation", "none")
138
+
139
+ if what != "variables":
140
+ raise NotImplementedError(f"Complement what={what} not implemented")
141
+
142
+ if interpolation not in ("none", "nearest"):
143
+ raise NotImplementedError(f"Complement method={interpolation} not implemented")
144
+
145
+ source = _open(source)
146
+ target = _open(target)
147
+ # `select` is the same as `variables`
148
+ (source, target), kwargs = _auto_adjust([source, target], kwargs, exclude=["select"])
149
+
150
+ Class = {
151
+ None: ComplementNone,
152
+ "none": ComplementNone,
153
+ "nearest": ComplementNearest,
154
+ }[interpolation]
155
+
156
+ complement = Class(target=target, source=source)._subset(**kwargs)
157
+
158
+ # Will join the datasets along the variables axis
159
+ reorder = source.variables
160
+ complemented = _open([target, complement])
161
+ ordered = (
162
+ Select(complemented, complemented._reorder_to_columns(reorder), {"reoder": reorder})._subset(**kwargs).mutate()
163
+ )
164
+ return ordered
@@ -168,6 +168,16 @@ class Dataset:
168
168
  bbox = kwargs.pop("area")
169
169
  return Cropping(self, bbox)._subset(**kwargs).mutate()
170
170
 
171
+ if "number" in kwargs or "numbers" or "member" in kwargs or "members" in kwargs:
172
+ from .ensemble import Number
173
+
174
+ members = {}
175
+ for key in ["number", "numbers", "member", "members"]:
176
+ if key in kwargs:
177
+ members[key] = kwargs.pop(key)
178
+
179
+ return Number(self, **members)._subset(**kwargs).mutate()
180
+
171
181
  if "set_missing_dates" in kwargs:
172
182
  from .missing import MissingDates
173
183
 
@@ -251,13 +261,19 @@ class Dataset:
251
261
  return sorted([v for k, v in self.name_to_index.items() if k not in vars])
252
262
 
253
263
  def _reorder_to_columns(self, vars):
264
+ if isinstance(vars, str) and vars == "sort":
265
+ # Sorting the variables alphabetically.
266
+ # This is cruical for pre-training then transfer learning in combination with
267
+ # cutout and adjust = 'all'
268
+
269
+ indices = [self.name_to_index[k] for k, v in sorted(self.name_to_index.items(), key=lambda x: x[0])]
270
+ assert set(indices) == set(range(len(self.name_to_index)))
271
+ return indices
272
+
254
273
  if isinstance(vars, (list, tuple)):
255
274
  vars = {k: i for i, k in enumerate(vars)}
256
275
 
257
- indices = []
258
-
259
- for k, v in sorted(vars.items(), key=lambda x: x[1]):
260
- indices.append(self.name_to_index[k])
276
+ indices = [self.name_to_index[k] for k, v in sorted(vars.items(), key=lambda x: x[1])]
261
277
 
262
278
  # Make sure we don't forget any variables
263
279
  assert set(indices) == set(range(len(self.name_to_index)))
@@ -469,7 +485,7 @@ class Dataset:
469
485
  sample_count = min(4, len(indices))
470
486
  count = len(indices)
471
487
 
472
- p = slice(0, count, count // (sample_count - 1))
488
+ p = slice(0, count, count // max(1, sample_count - 1))
473
489
  samples = list(range(*p.indices(count)))
474
490
 
475
491
  samples.append(count - 1) # Add last
@@ -502,3 +518,50 @@ class Dataset:
502
518
  result.append(v)
503
519
 
504
520
  return result
521
+
522
+ def plot(self, date, variable, member=0, **kwargs):
523
+ """For debugging purposes, plot a field.
524
+
525
+ Parameters
526
+ ----------
527
+ date : int or datetime.datetime or numpy.datetime64 or str
528
+ The date to plot.
529
+ variable : int or str
530
+ The variable to plot.
531
+ member : int, optional
532
+ The ensemble member to plot.
533
+
534
+ **kwargs:
535
+ Additional arguments to pass to matplotlib.pyplot.tricontourf
536
+
537
+
538
+ Returns
539
+ -------
540
+ matplotlib.pyplot.Axes
541
+ """
542
+
543
+ from anemoi.utils.devtools import plot_values
544
+ from earthkit.data.utils.dates import to_datetime
545
+
546
+ if not isinstance(date, int):
547
+ date = np.datetime64(to_datetime(date)).astype(self.dates[0].dtype)
548
+ index = np.where(self.dates == date)[0]
549
+ if len(index) == 0:
550
+ raise ValueError(
551
+ f"Date {date} not found in the dataset {self.dates[0]} to {self.dates[-1]} by {self.frequency}"
552
+ )
553
+ date_index = index[0]
554
+ else:
555
+ date_index = date
556
+
557
+ if isinstance(variable, int):
558
+ variable_index = variable
559
+ else:
560
+ if variable not in self.variables:
561
+ raise ValueError(f"Unknown variable {variable} (available: {self.variables})")
562
+
563
+ variable_index = self.name_to_index[variable]
564
+
565
+ values = self[date_index, variable_index, member]
566
+
567
+ return plot_values(values, self.latitudes, self.longitudes, **kwargs)
@@ -10,13 +10,68 @@
10
10
 
11
11
  import logging
12
12
 
13
+ import numpy as np
14
+
13
15
  from .debug import Node
16
+ from .forwards import Forwards
14
17
  from .forwards import GivenAxis
18
+ from .indexing import apply_index_to_slices_changes
19
+ from .indexing import index_to_slices
20
+ from .indexing import update_tuple
15
21
  from .misc import _auto_adjust
16
22
  from .misc import _open
17
23
 
18
24
  LOG = logging.getLogger(__name__)
19
25
 
26
+ OFFSETS = dict(number=1, numbers=1, member=0, members=0)
27
+
28
+
29
+ class Number(Forwards):
30
+ def __init__(self, forward, **kwargs):
31
+ super().__init__(forward)
32
+
33
+ self.members = []
34
+ for key, values in kwargs.items():
35
+ if not isinstance(values, (list, tuple)):
36
+ values = [values]
37
+ self.members.extend([int(v) - OFFSETS[key] for v in values])
38
+
39
+ self.members = sorted(set(self.members))
40
+ for n in self.members:
41
+ if not (0 <= n < forward.shape[2]):
42
+ raise ValueError(f"Member {n} is out of range. `number(s)` is one-based, `member(s)` is zero-based.")
43
+
44
+ self.mask = np.array([n in self.members for n in range(forward.shape[2])], dtype=bool)
45
+ self._shape, _ = update_tuple(forward.shape, 2, len(self.members))
46
+
47
+ @property
48
+ def shape(self):
49
+ return self._shape
50
+
51
+ def __getitem__(self, index):
52
+ if isinstance(index, int):
53
+ result = self.forward[index]
54
+ result = result[:, self.mask, :]
55
+ return result
56
+
57
+ if isinstance(index, slice):
58
+ result = self.forward[index]
59
+ result = result[:, :, self.mask, :]
60
+ return result
61
+
62
+ index, changes = index_to_slices(index, self.shape)
63
+ result = self.forward[index]
64
+ result = result[:, :, self.mask, :]
65
+ return apply_index_to_slices_changes(result, changes)
66
+
67
+ def tree(self):
68
+ return Node(self, [self.forward.tree()], numbers=[n + 1 for n in self.members])
69
+
70
+ def metadata_specific(self):
71
+ return {
72
+ "numbers": [n + 1 for n in self.members],
73
+ }
74
+
20
75
 
21
76
  class Ensemble(GivenAxis):
22
77
  def tree(self):
@@ -118,6 +118,7 @@ class Join(Combined):
118
118
  def variables_metadata(self):
119
119
  result = {}
120
120
  variables = [v for v in self.variables if not (v.startswith("(") and v.endswith(")"))]
121
+
121
122
  for d in self.datasets:
122
123
  md = d.variables_metadata
123
124
  for v in variables:
@@ -130,8 +131,6 @@ class Join(Combined):
130
131
  if v not in result:
131
132
  LOG.error("Missing metadata for %r.", v)
132
133
 
133
- raise ValueError("Some variables are missing metadata.")
134
-
135
134
  return result
136
135
 
137
136
  @cached_property
@@ -134,6 +134,9 @@ class Merge(Combined):
134
134
  def tree(self):
135
135
  return Node(self, [d.tree() for d in self.datasets], allow_gaps_in_dates=self.allow_gaps_in_dates)
136
136
 
137
+ def metadata_specific(self):
138
+ return {"allow_gaps_in_dates": self.allow_gaps_in_dates}
139
+
137
140
  @debug_indexing
138
141
  def __getitem__(self, n):
139
142
  if isinstance(n, tuple):
@@ -194,7 +194,7 @@ def _open(a):
194
194
  raise NotImplementedError(f"Unsupported argument: {type(a)}")
195
195
 
196
196
 
197
- def _auto_adjust(datasets, kwargs):
197
+ def _auto_adjust(datasets, kwargs, exclude=None):
198
198
 
199
199
  if "adjust" not in kwargs:
200
200
  return datasets, kwargs
@@ -214,6 +214,9 @@ def _auto_adjust(datasets, kwargs):
214
214
  for a in adjust_list:
215
215
  adjust_set.update(ALIASES.get(a, [a]))
216
216
 
217
+ if exclude is not None:
218
+ adjust_set -= set(exclude)
219
+
217
220
  extra = set(adjust_set) - set(ALIASES["all"])
218
221
  if extra:
219
222
  raise ValueError(f"Invalid adjust keys: {extra}")
@@ -335,6 +338,12 @@ def _open_dataset(*args, **kwargs):
335
338
  assert not sets, sets
336
339
  return cutout_factory(args, kwargs).mutate()
337
340
 
341
+ if "complement" in kwargs:
342
+ from .complement import complement_factory
343
+
344
+ assert not sets, sets
345
+ return complement_factory(args, kwargs).mutate()
346
+
338
347
  for name in ("datasets", "dataset"):
339
348
  if name in kwargs:
340
349
  datasets = kwargs.pop(name)
anemoi/datasets/grids.py CHANGED
@@ -152,7 +152,7 @@ def cutout_mask(
152
152
  plot=None,
153
153
  ):
154
154
  """Return a mask for the points in [global_lats, global_lons] that are inside of [lats, lons]"""
155
- from scipy.spatial import KDTree
155
+ from scipy.spatial import cKDTree
156
156
 
157
157
  # TODO: transform min_distance from lat/lon to xyz
158
158
 
@@ -195,13 +195,13 @@ def cutout_mask(
195
195
  min_distance = min_distance_km / 6371.0
196
196
  else:
197
197
  points = {"lam": lam_points, "global": global_points, None: global_points}[min_distance_km]
198
- distances, _ = KDTree(points).query(points, k=2)
198
+ distances, _ = cKDTree(points).query(points, k=2)
199
199
  min_distance = np.min(distances[:, 1])
200
200
 
201
201
  LOG.info(f"cutout_mask using min_distance = {min_distance * 6371.0} km")
202
202
 
203
- # Use a KDTree to find the nearest points
204
- distances, indices = KDTree(lam_points).query(global_points, k=neighbours)
203
+ # Use a cKDTree to find the nearest points
204
+ distances, indices = cKDTree(lam_points).query(global_points, k=neighbours)
205
205
 
206
206
  # Centre of the Earth
207
207
  zero = np.array([0.0, 0.0, 0.0])
@@ -255,7 +255,7 @@ def thinning_mask(
255
255
  cropping_distance=2.0,
256
256
  ):
257
257
  """Return the list of points in [lats, lons] closest to [global_lats, global_lons]"""
258
- from scipy.spatial import KDTree
258
+ from scipy.spatial import cKDTree
259
259
 
260
260
  assert global_lats.ndim == 1
261
261
  assert global_lons.ndim == 1
@@ -291,20 +291,20 @@ def thinning_mask(
291
291
  xyx = latlon_to_xyz(lats, lons)
292
292
  points = np.array(xyx).transpose()
293
293
 
294
- # Use a KDTree to find the nearest points
295
- _, indices = KDTree(points).query(global_points, k=1)
294
+ # Use a cKDTree to find the nearest points
295
+ _, indices = cKDTree(points).query(global_points, k=1)
296
296
 
297
297
  return np.array([i for i in indices])
298
298
 
299
299
 
300
300
  def outline(lats, lons, neighbours=5):
301
- from scipy.spatial import KDTree
301
+ from scipy.spatial import cKDTree
302
302
 
303
303
  xyx = latlon_to_xyz(lats, lons)
304
304
  grid_points = np.array(xyx).transpose()
305
305
 
306
- # Use a KDTree to find the nearest points
307
- _, indices = KDTree(grid_points).query(grid_points, k=neighbours)
306
+ # Use a cKDTree to find the nearest points
307
+ _, indices = cKDTree(grid_points).query(grid_points, k=neighbours)
308
308
 
309
309
  # Centre of the Earth
310
310
  zero = np.array([0.0, 0.0, 0.0])
@@ -379,6 +379,19 @@ def serialise_mask(mask):
379
379
  return result
380
380
 
381
381
 
382
+ def nearest_grid_points(source_latitudes, source_longitudes, target_latitudes, target_longitudes):
383
+ from scipy.spatial import cKDTree
384
+
385
+ source_xyz = latlon_to_xyz(source_latitudes, source_longitudes)
386
+ source_points = np.array(source_xyz).transpose()
387
+
388
+ target_xyz = latlon_to_xyz(target_latitudes, target_longitudes)
389
+ target_points = np.array(target_xyz).transpose()
390
+
391
+ _, indices = cKDTree(source_points).query(target_points, k=1)
392
+ return indices
393
+
394
+
382
395
  if __name__ == "__main__":
383
396
  global_lats, global_lons = np.meshgrid(
384
397
  np.linspace(90, -90, 90),
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: anemoi-datasets
3
- Version: 0.5.12
3
+ Version: 0.5.13
4
4
  Summary: A package to hold various functions to support training of ML models on ECMWF data.
5
5
  Author-email: "European Centre for Medium-Range Weather Forecasts (ECMWF)" <software.support@ecmwf.int>
6
6
  License: Apache License
@@ -1,7 +1,7 @@
1
1
  anemoi/datasets/__init__.py,sha256=0GOHATiKgkUqLRgAVQhNP1aPO7ULfSr8DqUf2ANPEv8,1010
2
2
  anemoi/datasets/__main__.py,sha256=5NW2A3OgTimB4ptwYThivIRSeCrvabMuvnr8mmnVx0E,715
3
- anemoi/datasets/_version.py,sha256=ZArwdbjC4yDqbe9dEwhZVS693DGl1K3jnRxJy_dutjo,413
4
- anemoi/datasets/grids.py,sha256=bq7pB_6uswILT3t8C8SeUpUrBww31dw5au_USrped6c,10919
3
+ anemoi/datasets/_version.py,sha256=Ywt_J-kUrkp4bt0vX46Ot5kDjsAA9dtf8IfOdhjGcXA,413
4
+ anemoi/datasets/grids.py,sha256=xHZwE3pJs0wP05x9qCgMcTuBnBGoCU51YKx5JQ7Ukts,11398
5
5
  anemoi/datasets/testing.py,sha256=7HGOz5_V9MbkHTDJ4KbklGRndBMrFfVrBBu6a9k0_qY,1825
6
6
  anemoi/datasets/commands/__init__.py,sha256=O5W3yHZywRoAqmRUioAr3zMCh0hGVV18wZYGvc00ioM,698
7
7
  anemoi/datasets/commands/cleanup.py,sha256=2rD34bHtfOCLwQh7yXa02IJmmOYMOma4YDj0PM-2-Jc,1456
@@ -20,20 +20,21 @@ anemoi/datasets/commands/publish.py,sha256=z1MV9_1BsEnw81Y_17fHkKGYe8_ZJo9eeQ1kG
20
20
  anemoi/datasets/commands/scan.py,sha256=mXzYEcYsncxC7ItyL_TlVRiWji6OFYfVxO5OMD9mbEI,3304
21
21
  anemoi/datasets/compute/__init__.py,sha256=hCW0QcLHJmE-C1r38P27_ZOvCLNewex5iQEtZqx2ckI,393
22
22
  anemoi/datasets/compute/recentre.py,sha256=tKs-YZLhqsMRBNEUF41hcuMmyvbRuFX07xJq-Cqg2_w,4954
23
- anemoi/datasets/create/__init__.py,sha256=q8JIS6mcfJhMD8-uJN-EMkaG4DwKNTIJy53iGKHhiXw,36259
24
- anemoi/datasets/create/check.py,sha256=1fT1au1LmSGKTQYhnmMK9ImSZnLLevkKX1a0jUtEuVc,6148
23
+ anemoi/datasets/create/__init__.py,sha256=ImonAvvz-HqoZMF4vczyWEV48tGiZZ8RfM01SsxJ6ew,36427
24
+ anemoi/datasets/create/check.py,sha256=q205XxzR7UtBRI5qOANav_NAVqAERs0aLJ8oBL3VNc4,6153
25
25
  anemoi/datasets/create/chunks.py,sha256=c7ufk-EamAGqxOI4ScTFlBzdEiH6V1R0f0SPavtZ2Xw,2457
26
26
  anemoi/datasets/create/config.py,sha256=Tq9kJ-bKhYSTWCXNa8lZIJpO3RteZfCr5hQVM12IgD0,8892
27
27
  anemoi/datasets/create/patch.py,sha256=YkDiFGV0DDg4WShVrIHhtRaAp9c82mANnR93YTVl36o,4171
28
28
  anemoi/datasets/create/persistent.py,sha256=V3agRYKbmYqAyhsznsgC3PLSoFbFCSCXqcQZvvJfVTw,4381
29
29
  anemoi/datasets/create/size.py,sha256=5fIF0yo63dTowojfeG9-MNkgypP5SA94WralxTZwxw4,1055
30
- anemoi/datasets/create/utils.py,sha256=jwbGQ_o-4U8n-lgrrs2Oia1WrGonEHfxaCg8dUlA6S0,3088
30
+ anemoi/datasets/create/utils.py,sha256=3vRCPOrW0a7ZVx7gsflORwab74LV_BVVkgo_3SgyLpw,3347
31
31
  anemoi/datasets/create/writer.py,sha256=6XSIgNwKkjrkdnSvr69mXD8McjT3iYJ0d1rOnxaGuCQ,1394
32
32
  anemoi/datasets/create/zarr.py,sha256=0DkHwKaBpvTOrU1KmScLEfff_KfT1Rw5DXiRMD2d3Ws,5506
33
- anemoi/datasets/create/functions/__init__.py,sha256=e361XrjNV_4LCBmxVMluZ3KczFn9bwLSk5vcwy0zzkg,1382
33
+ anemoi/datasets/create/functions/__init__.py,sha256=qeoEndq4f0gosfyfb-t0CZcLdngJ4GlZIgGs0bp9j0U,1965
34
34
  anemoi/datasets/create/functions/filters/__init__.py,sha256=SP6ReV1WYIf2Typf1FUaRHhphFGpU9kBoYtI-bYdu5U,395
35
35
  anemoi/datasets/create/functions/filters/empty.py,sha256=EGLufFcNFoqIXTZj7jQFjtFahMfgCVWj6W5j--u5Q-Q,636
36
36
  anemoi/datasets/create/functions/filters/noop.py,sha256=5YqumPxlGDOZOrYWayLr8PTycVWG2X_0PmoFi3Hj584,458
37
+ anemoi/datasets/create/functions/filters/orog_to_z.py,sha256=PZwqiTVBLlwp2yuHCW_D8Epcb0fPNjzuYYtmL3Gu1XY,1658
37
38
  anemoi/datasets/create/functions/filters/pressure_level_relative_humidity_to_specific_humidity.py,sha256=jjmocA4WDKCAL49QUFk_3S0JRiPMmeVM7Wlxmfr1v6c,1857
38
39
  anemoi/datasets/create/functions/filters/pressure_level_specific_humidity_to_relative_humidity.py,sha256=e8LvXUq-qNKJrcjb1DSUXaPeFfxcWxFjGAkm47cOnE8,1855
39
40
  anemoi/datasets/create/functions/filters/rename.py,sha256=cDF3xmdhwzIZn_nwaO3hxG4fb2vpKtJtmy0ZdLGXyHI,2481
@@ -43,10 +44,12 @@ anemoi/datasets/create/functions/filters/single_level_relative_humidity_to_dewpo
43
44
  anemoi/datasets/create/functions/filters/single_level_relative_humidity_to_specific_humidity.py,sha256=BnuLrIFcOh_qJBmxwdJqjGqoH0ca5zyKdZgF6QPmJY8,4090
44
45
  anemoi/datasets/create/functions/filters/single_level_specific_humidity_to_relative_humidity.py,sha256=xqfklEwCqrQlhU6NV8vlVEZdY-hN3SpPpcNny2geVUI,12686
45
46
  anemoi/datasets/create/functions/filters/speeddir_to_uv.py,sha256=d5t78GToTTXCb1S3HyhTJ2tuwZDnk7UBsHPV4Wn4M_w,2249
47
+ anemoi/datasets/create/functions/filters/sum.py,sha256=_f_xyIAbGdKoCXdd5zO7XOL2AHq_c04DFO8s6PhRR8I,1979
46
48
  anemoi/datasets/create/functions/filters/unrotate_winds.py,sha256=tDFXUSF2flD83W7GgwP1RoVXBUO0445DvQdImulzDzA,2429
47
49
  anemoi/datasets/create/functions/filters/uv_to_speeddir.py,sha256=niNuTSmyxLn4MGeNL1lowl5M0dH7har-flXy3ZtmKPM,1762
50
+ anemoi/datasets/create/functions/filters/wz_to_w.py,sha256=SbTYE6rRjObR-sJEDYyc0-1Kw39zZOAheGMznD7Ic9A,2161
48
51
  anemoi/datasets/create/functions/sources/__init__.py,sha256=TMm8LerGY7--b0AMUqnz07ZGo-F7I9FF0DGlozcTtSg,1364
49
- anemoi/datasets/create/functions/sources/accumulations.py,sha256=tI5Aga1aLDp8DqfYbU_9AI1VIqDZzhMlZUAR4ik-CUI,12316
52
+ anemoi/datasets/create/functions/sources/accumulations.py,sha256=xs3Ql3h_jfJDR0mzGIh54adVaHacHE3LQpBL5_8Rx8k,12373
50
53
  anemoi/datasets/create/functions/sources/constants.py,sha256=GaiUpJPYupiLWl8O9GEZ9KmlD88pH6dlBiUVrhNl_uA,918
51
54
  anemoi/datasets/create/functions/sources/empty.py,sha256=YTpOJ3rcb_eS9CbnpwPWBR9r1APIAaG6a_N803YFZFE,500
52
55
  anemoi/datasets/create/functions/sources/forcings.py,sha256=p442lCOXm8TJFRlP0mgwZujveo9gCtdAGLS4KSIqYfk,661
@@ -61,15 +64,16 @@ anemoi/datasets/create/functions/sources/tendencies.py,sha256=z8iDelu0vvDE8S-Rus
61
64
  anemoi/datasets/create/functions/sources/xarray_kerchunk.py,sha256=8evD6Sype3ffCbmQ0jMBpgR97UeNvkTB5rwchhy4YzY,1446
62
65
  anemoi/datasets/create/functions/sources/xarray_zarr.py,sha256=3JvoGfQZ4NCUcfxDAbNZOL7z2VRNJzr1H3r8dsWbrgk,545
63
66
  anemoi/datasets/create/functions/sources/zenodo.py,sha256=rPL9uNPeFTdI9XvVEahtHkxzE18MyrjNXZjpt_sNeH4,1251
64
- anemoi/datasets/create/functions/sources/xarray/__init__.py,sha256=fEJI2OGOtk-CJqCzaege0uwPKGpqEkeKxgt2bN-W934,3136
67
+ anemoi/datasets/create/functions/sources/xarray/__init__.py,sha256=byu5zPP_4b7CjgSKvO3iL4xyZPmdoEVX93Tl7LBZc0c,3174
65
68
  anemoi/datasets/create/functions/sources/xarray/coordinates.py,sha256=-FkcAaio2KumOd20eb1hLv9rRhjnu-CyqtqzrMsZx18,6213
66
- anemoi/datasets/create/functions/sources/xarray/field.py,sha256=70_3wxBE-flftWqyHAyBP7FhwZOHgrSW6WDmA8QGszs,3680
67
- anemoi/datasets/create/functions/sources/xarray/fieldlist.py,sha256=wBHPQPm1oJnaVGO6p0opx9tEOIJkdINw_cCTG1y9GYk,5884
69
+ anemoi/datasets/create/functions/sources/xarray/field.py,sha256=VfEuY-o1KZS1Bn4l7pR8FCx9hTtDbzKzPqJfwunwvRE,3816
70
+ anemoi/datasets/create/functions/sources/xarray/fieldlist.py,sha256=3wCLbdqpPlBlzJHKp_ETxAochPA9iFDyF94JVn1DOB8,6281
68
71
  anemoi/datasets/create/functions/sources/xarray/flavour.py,sha256=6mqldGyx40Zgy4_VkuGWKgrSuPbWKe__nmEradQO5qg,14855
69
72
  anemoi/datasets/create/functions/sources/xarray/grid.py,sha256=OuLBVv_CdgtLgGACpqhjX8fwtYzM7tfJiwUOXbG_ifw,3644
70
- anemoi/datasets/create/functions/sources/xarray/metadata.py,sha256=GXHjPm8MsFU9fBfJCjllPWtKahBcKWtQwj0yA7zt4hU,4573
71
- anemoi/datasets/create/functions/sources/xarray/time.py,sha256=RKODUhgGtApvee3C8SdcBpz2wlQ4zJhyov1UPvblsr4,5987
72
- anemoi/datasets/create/functions/sources/xarray/variable.py,sha256=j2NN-zx-yjprIJLcidxMxFC4QQZ7I8Dd0nKMrgRpsdM,4385
73
+ anemoi/datasets/create/functions/sources/xarray/metadata.py,sha256=zbbb0ssKhZJvogLJ1WPJMBVVHl40GjHWbmE6RzLwAz4,4336
74
+ anemoi/datasets/create/functions/sources/xarray/patch.py,sha256=k1v7bUs-sO7-431T0bh5CSTE1FtgjhIlaPQ2-kSpc2E,1051
75
+ anemoi/datasets/create/functions/sources/xarray/time.py,sha256=jGnaupnNQr9x4F7ijahzxtMQltC5fLbrEKajq5dIxR8,6458
76
+ anemoi/datasets/create/functions/sources/xarray/variable.py,sha256=IdxZGOu1DMaUVlDGyVHuZiGUsN4buJoxexSFUD_NyFg,5029
73
77
  anemoi/datasets/create/input/__init__.py,sha256=cAwfW9AQiG2PfmZ2Irll7HX8HyiC0Nk1Q9OhoQ84ZAg,1625
74
78
  anemoi/datasets/create/input/action.py,sha256=SApZApq-_mlOwk1NTERgQlPdPL8lBlIk6rxYX3JBw_E,3857
75
79
  anemoi/datasets/create/input/concat.py,sha256=DwxgoTSTqNDsVcX5btUBAA7vXtX3G5m-zJ-jDrmAC-c,3279
@@ -81,28 +85,29 @@ anemoi/datasets/create/input/function.py,sha256=F5GQgbtFYmyqFAgNGoGDuWw-xqkcCLzu
81
85
  anemoi/datasets/create/input/join.py,sha256=wQP1-vVg4as-R5i3pstgK6HmTJAY7WyWYhCEF6FIU1c,1991
82
86
  anemoi/datasets/create/input/misc.py,sha256=r7NC_QRYA8iiJJbSFgQnNuixymATK0CPZknGxgYcLOk,1975
83
87
  anemoi/datasets/create/input/pipe.py,sha256=KfPCtiqyfqkXbmC-2LTqHkCQ7bJY46XMvNDnp9QeHTQ,1344
84
- anemoi/datasets/create/input/repeated_dates.py,sha256=biwnjgaRGzJiFk5fAY-YA4FotzdlwkY56GKUetzOulg,6840
88
+ anemoi/datasets/create/input/repeated_dates.py,sha256=59EvJ_cQwA-p_42cmMFy3pBAAWV0xwPg4E3q2PIofcM,7461
85
89
  anemoi/datasets/create/input/result.py,sha256=-pcVcaaj3G_xcNKWWTgzVH5Ds5-ETWmErN0KeQGitAw,20013
86
90
  anemoi/datasets/create/input/step.py,sha256=CoowF9mc3kepT8XQ2ObxO750rnQEkYNTviIHQ1m-4UA,2886
87
91
  anemoi/datasets/create/input/template.py,sha256=Vgi4wQ1aeswLbji0fIzshYhISmzdrt7b0BmgeJJjYGc,1859
88
92
  anemoi/datasets/create/input/trace.py,sha256=DYXMSnwKqOIx0XWZTKNJojWz4EqaFLknTh6ysxsW9uY,2198
89
- anemoi/datasets/create/statistics/__init__.py,sha256=l6VE00sfcfqBg6cDFJTian-DLnvwt1QYEYq0lCHZ0PY,12786
93
+ anemoi/datasets/create/statistics/__init__.py,sha256=iJ3mZ6eEI88wPXUKyOhNKqhakyHoceX9ICEKXVOriTo,12789
90
94
  anemoi/datasets/create/statistics/summary.py,sha256=wmnz4fZkr6fomXgI8JlMutU8gakfrXTc5ixf3Np7gZA,3385
91
95
  anemoi/datasets/data/__init__.py,sha256=AW1-Ycj77pWQsZcDGsp0pgTS5rFW6XC4CzuUEIUPAIk,1558
96
+ anemoi/datasets/data/complement.py,sha256=hTvA_zTGIHAvZYPv1npVpRpxPg8nXbNTEBAU8r33rlc,5105
92
97
  anemoi/datasets/data/concat.py,sha256=udtYINuoLOEYYKhi_VpG2-emv80pwZbFAZKwNwXJk3s,5244
93
- anemoi/datasets/data/dataset.py,sha256=73NjQo7wUSWqfdpyVVZIpWgEmy1x9-beN-kVtgfhBJE,15587
98
+ anemoi/datasets/data/dataset.py,sha256=x_ID6Ga_TbBfECqhOC4i3CTqo0UD34KJVqUWpvI9Si4,17926
94
99
  anemoi/datasets/data/debug.css,sha256=z2X_ZDSnZ9C3pyZPWnQiEyAxuMxUaxJxET4oaCImTAQ,211
95
100
  anemoi/datasets/data/debug.py,sha256=IjCMwtAvknF51PCl_YRYgMZB2iX_9DC5DKILNgl_UHQ,6300
96
- anemoi/datasets/data/ensemble.py,sha256=KNIXDfjYSIo6JVn1bD9X92yffd4Gg83wn_2sGxqAnWU,1111
101
+ anemoi/datasets/data/ensemble.py,sha256=7ognsmoHDGw0cCs3hsARoV32J1qlQys6iUCJ7XSrARI,2923
97
102
  anemoi/datasets/data/fill_missing.py,sha256=4btLi-D-hFTsS_57_gIC1nK5AVifAO-V4M-fqMrtrxk,4636
98
103
  anemoi/datasets/data/forwards.py,sha256=P9DfSY5B9w9gtkKfV6TIzXel_LY83g-2nEreJy2rYkU,8916
99
104
  anemoi/datasets/data/grids.py,sha256=p7_nT7RLH6uKcxeAzQiGYk9lFxU_OOikDrwlb2rdEqI,15765
100
105
  anemoi/datasets/data/indexing.py,sha256=9lycQXSqUIbYj52JlFv0w_Gf6soVZnbVGswYMvGPpqs,4773
101
106
  anemoi/datasets/data/interpolate.py,sha256=D27lSH8yNhm0aoO0U3UoRbr3kni7OWXSu_X4jCbIrA0,4137
102
- anemoi/datasets/data/join.py,sha256=xl7SrjhggZdX_bdni5-cn8-BYGUYfEtogQeIqCgSL7U,5525
107
+ anemoi/datasets/data/join.py,sha256=IG9Bj4o4Z25cl5YGMqtl75UuSZCWIJwGIUB0fsfnkE8,5456
103
108
  anemoi/datasets/data/masked.py,sha256=eAVGVmQR7tWsd3xXYGXGyq28uRLwL50vOXWTNNdHxl0,4530
104
- anemoi/datasets/data/merge.py,sha256=6vYRy3-P4J9GgTPkdoPFs9CbZ5F0m8FAukS-P66BR_M,5448
105
- anemoi/datasets/data/misc.py,sha256=IKqtvcU36nyB4z6dfu7W_gnCpaeX20fK2C5A2seWdCA,10061
109
+ anemoi/datasets/data/merge.py,sha256=dr0sX2ufm-qOgOAMV5oh8qQwPvSdYbU-mhux6u-cmQw,5547
110
+ anemoi/datasets/data/misc.py,sha256=J1v84jHpRgDK0DUrNmII5oqt3jft8rSTve2GtxqTKa8,10310
106
111
  anemoi/datasets/data/missing.py,sha256=SWEjiC1usBjZtlKMr73uKetnoQZoflVQVGqLP2gJR7A,7131
107
112
  anemoi/datasets/data/rescale.py,sha256=wMU7tFZebnOqJJxaIGOqNqhpNKGsPNZMC1YxuiHvri4,4112
108
113
  anemoi/datasets/data/select.py,sha256=XW_ohlhrF8FLe13pdM3DRZDxbHxntcsO0F56GRqZQY0,4293
@@ -115,9 +120,9 @@ anemoi/datasets/dates/__init__.py,sha256=wX2FvlmRfHV5HDmllIxwfrC1LuRlb7i6SguLLas
115
120
  anemoi/datasets/dates/groups.py,sha256=i7x8z0kv6E8qUfm1tMZR1aaOqNwQzEkV-VWpOvHjoX4,5390
116
121
  anemoi/datasets/utils/__init__.py,sha256=hCW0QcLHJmE-C1r38P27_ZOvCLNewex5iQEtZqx2ckI,393
117
122
  anemoi/datasets/utils/fields.py,sha256=l7xKOiRLgk9Eewykqu7xZP9xOajG2dx2CiDlGvBVejU,1411
118
- anemoi_datasets-0.5.12.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
119
- anemoi_datasets-0.5.12.dist-info/METADATA,sha256=x548Cd_PFUCse1QR5dj-8rNfUx94ZU45g__IjPaGLgY,15598
120
- anemoi_datasets-0.5.12.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
121
- anemoi_datasets-0.5.12.dist-info/entry_points.txt,sha256=yR-o-4uiPEA_GLBL81SkMYnUoxq3CAV3hHulQiRtGG0,66
122
- anemoi_datasets-0.5.12.dist-info/top_level.txt,sha256=DYn8VPs-fNwr7fNH9XIBqeXIwiYYd2E2k5-dUFFqUz0,7
123
- anemoi_datasets-0.5.12.dist-info/RECORD,,
123
+ anemoi_datasets-0.5.13.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
124
+ anemoi_datasets-0.5.13.dist-info/METADATA,sha256=ArEcpcTEQ_Tt270GtB5GEeDX_1SJMKM8P4NWMdSve8M,15598
125
+ anemoi_datasets-0.5.13.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
126
+ anemoi_datasets-0.5.13.dist-info/entry_points.txt,sha256=yR-o-4uiPEA_GLBL81SkMYnUoxq3CAV3hHulQiRtGG0,66
127
+ anemoi_datasets-0.5.13.dist-info/top_level.txt,sha256=DYn8VPs-fNwr7fNH9XIBqeXIwiYYd2E2k5-dUFFqUz0,7
128
+ anemoi_datasets-0.5.13.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5