anemoi-datasets 0.4.4__py3-none-any.whl → 0.5.0__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 (64) hide show
  1. anemoi/datasets/_version.py +2 -2
  2. anemoi/datasets/commands/cleanup.py +44 -0
  3. anemoi/datasets/commands/create.py +52 -21
  4. anemoi/datasets/commands/finalise-additions.py +45 -0
  5. anemoi/datasets/commands/finalise.py +39 -0
  6. anemoi/datasets/commands/init-additions.py +45 -0
  7. anemoi/datasets/commands/init.py +67 -0
  8. anemoi/datasets/commands/inspect.py +1 -1
  9. anemoi/datasets/commands/load-additions.py +47 -0
  10. anemoi/datasets/commands/load.py +47 -0
  11. anemoi/datasets/commands/patch.py +39 -0
  12. anemoi/datasets/create/__init__.py +959 -146
  13. anemoi/datasets/create/check.py +5 -3
  14. anemoi/datasets/create/config.py +54 -2
  15. anemoi/datasets/create/functions/filters/pressure_level_relative_humidity_to_specific_humidity.py +57 -0
  16. anemoi/datasets/create/functions/filters/pressure_level_specific_humidity_to_relative_humidity.py +57 -0
  17. anemoi/datasets/create/functions/filters/single_level_dewpoint_to_relative_humidity.py +54 -0
  18. anemoi/datasets/create/functions/filters/single_level_relative_humidity_to_dewpoint.py +59 -0
  19. anemoi/datasets/create/functions/filters/single_level_relative_humidity_to_specific_humidity.py +115 -0
  20. anemoi/datasets/create/functions/filters/single_level_specific_humidity_to_relative_humidity.py +390 -0
  21. anemoi/datasets/create/functions/filters/speeddir_to_uv.py +77 -0
  22. anemoi/datasets/create/functions/filters/uv_to_speeddir.py +55 -0
  23. anemoi/datasets/create/functions/sources/grib.py +86 -1
  24. anemoi/datasets/create/functions/sources/hindcasts.py +14 -73
  25. anemoi/datasets/create/functions/sources/mars.py +9 -3
  26. anemoi/datasets/create/functions/sources/xarray/__init__.py +12 -2
  27. anemoi/datasets/create/functions/sources/xarray/coordinates.py +7 -0
  28. anemoi/datasets/create/functions/sources/xarray/field.py +8 -2
  29. anemoi/datasets/create/functions/sources/xarray/fieldlist.py +0 -2
  30. anemoi/datasets/create/functions/sources/xarray/flavour.py +21 -1
  31. anemoi/datasets/create/functions/sources/xarray/metadata.py +40 -40
  32. anemoi/datasets/create/functions/sources/xarray/time.py +63 -30
  33. anemoi/datasets/create/functions/sources/xarray/variable.py +15 -38
  34. anemoi/datasets/create/input.py +62 -39
  35. anemoi/datasets/create/persistent.py +1 -1
  36. anemoi/datasets/create/statistics/__init__.py +39 -23
  37. anemoi/datasets/create/utils.py +6 -2
  38. anemoi/datasets/data/__init__.py +1 -0
  39. anemoi/datasets/data/concat.py +46 -2
  40. anemoi/datasets/data/dataset.py +119 -34
  41. anemoi/datasets/data/debug.py +5 -1
  42. anemoi/datasets/data/forwards.py +17 -8
  43. anemoi/datasets/data/grids.py +17 -3
  44. anemoi/datasets/data/interpolate.py +133 -0
  45. anemoi/datasets/data/masked.py +2 -2
  46. anemoi/datasets/data/misc.py +56 -66
  47. anemoi/datasets/data/missing.py +240 -0
  48. anemoi/datasets/data/rescale.py +147 -0
  49. anemoi/datasets/data/select.py +7 -1
  50. anemoi/datasets/data/stores.py +23 -10
  51. anemoi/datasets/data/subset.py +47 -5
  52. anemoi/datasets/data/unchecked.py +20 -22
  53. anemoi/datasets/data/xy.py +125 -0
  54. anemoi/datasets/dates/__init__.py +124 -95
  55. anemoi/datasets/dates/groups.py +85 -20
  56. anemoi/datasets/grids.py +66 -48
  57. {anemoi_datasets-0.4.4.dist-info → anemoi_datasets-0.5.0.dist-info}/METADATA +8 -17
  58. anemoi_datasets-0.5.0.dist-info/RECORD +105 -0
  59. {anemoi_datasets-0.4.4.dist-info → anemoi_datasets-0.5.0.dist-info}/WHEEL +1 -1
  60. anemoi/datasets/create/loaders.py +0 -936
  61. anemoi_datasets-0.4.4.dist-info/RECORD +0 -86
  62. {anemoi_datasets-0.4.4.dist-info → anemoi_datasets-0.5.0.dist-info}/LICENSE +0 -0
  63. {anemoi_datasets-0.4.4.dist-info → anemoi_datasets-0.5.0.dist-info}/entry_points.txt +0 -0
  64. {anemoi_datasets-0.4.4.dist-info → anemoi_datasets-0.5.0.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,6 @@
6
6
  # granted to it by virtue of its status as an intergovernmental organisation
7
7
  # nor does it submit to any jurisdiction.
8
8
  #
9
- import datetime
10
9
  import logging
11
10
 
12
11
  from earthkit.data.core.fieldlist import MultiFieldList
@@ -14,7 +13,6 @@ from earthkit.data.core.fieldlist import MultiFieldList
14
13
  from anemoi.datasets.create.functions.sources.mars import mars
15
14
 
16
15
  LOGGER = logging.getLogger(__name__)
17
- DEBUG = True
18
16
 
19
17
 
20
18
  def _to_list(x):
@@ -23,91 +21,34 @@ def _to_list(x):
23
21
  return [x]
24
22
 
25
23
 
26
- class HindcastCompute:
27
- def __init__(self, base_times, available_steps, request):
28
- self.base_times = base_times
29
- self.available_steps = available_steps
30
- self.request = request
31
-
32
- def compute_hindcast(self, date):
33
- result = []
34
- for step in sorted(self.available_steps): # Use the shortest step
35
- start_date = date - datetime.timedelta(hours=step)
36
- hours = start_date.hour
37
- if hours in self.base_times:
38
- r = self.request.copy()
39
- r["date"] = start_date
40
- r["time"] = f"{start_date.hour:02d}00"
41
- r["step"] = step
42
- result.append(r)
43
-
44
- if not result:
45
- raise ValueError(
46
- f"Cannot find data for {self.request} for {date} (base_times={self.base_times}, "
47
- f"available_steps={self.available_steps})"
48
- )
49
-
50
- if len(result) > 1:
51
- raise ValueError(
52
- f"Multiple requests for {self.request} for {date} (base_times={self.base_times}, "
53
- f"available_steps={self.available_steps})"
54
- )
55
-
56
- return result[0]
57
-
58
-
59
- def use_reference_year(reference_year, request):
60
- request = request.copy()
61
- hdate = request.pop("date")
62
-
63
- if hdate.year >= reference_year:
64
- return None, False
24
+ def hindcasts(context, dates, **request):
65
25
 
66
- try:
67
- date = datetime.datetime(reference_year, hdate.month, hdate.day)
68
- except ValueError:
69
- if hdate.month == 2 and hdate.day == 29:
70
- return None, False
71
- raise
26
+ from anemoi.datasets.dates import HindcastsDates
72
27
 
73
- request.update(date=date.strftime("%Y-%m-%d"), hdate=hdate.strftime("%Y-%m-%d"))
74
- return request, True
28
+ provider = context.dates_provider
29
+ assert isinstance(provider, HindcastsDates)
75
30
 
31
+ context.trace("H️", f"hindcasts {len(dates)=}")
76
32
 
77
- def hindcasts(context, dates, **request):
78
33
  request["param"] = _to_list(request["param"])
79
- request["step"] = _to_list(request["step"])
34
+ request["step"] = _to_list(request.get("step", 0))
80
35
  request["step"] = [int(_) for _ in request["step"]]
81
36
 
82
- if request.get("stream") == "enfh" and "base_times" not in request:
83
- request["base_times"] = [0]
84
-
85
- available_steps = request.pop("step")
86
- available_steps = _to_list(available_steps)
87
-
88
- base_times = request.pop("base_times")
89
-
90
- reference_year = request.pop("reference_year")
37
+ context.trace("H️", f"hindcast {request}")
91
38
 
92
- context.trace("H️", f"hindcast {request} {base_times} {available_steps} {reference_year}")
93
-
94
- c = HindcastCompute(base_times, available_steps, request)
95
39
  requests = []
96
40
  for d in dates:
97
- req = c.compute_hindcast(d)
98
- req, ok = use_reference_year(reference_year, req)
99
- if ok:
100
- requests.append(req)
101
-
102
- # print("HINDCASTS requests", reference_year, base_times, available_steps)
103
- # print("HINDCASTS dates", compress_dates(dates))
41
+ r = request.copy()
42
+ hindcast = provider.mapping[d]
43
+ r["hdate"] = hindcast.hdate.strftime("%Y-%m-%d")
44
+ r["date"] = hindcast.refdate.strftime("%Y-%m-%d")
45
+ r["time"] = hindcast.refdate.strftime("%H")
46
+ r["step"] = hindcast.step
47
+ requests.append(r)
104
48
 
105
49
  if len(requests) == 0:
106
- # print("HINDCASTS no requests")
107
50
  return MultiFieldList([])
108
51
 
109
- # print("HINDCASTS requests", requests)
110
-
111
52
  return mars(
112
53
  context,
113
54
  dates,
@@ -203,16 +203,22 @@ def mars(context, dates, *requests, request_already_using_valid_datetime=False,
203
203
  request_already_using_valid_datetime=request_already_using_valid_datetime,
204
204
  date_key=date_key,
205
205
  )
206
+
207
+ requests = list(requests)
208
+
206
209
  ds = from_source("empty")
210
+ context.trace("✅", f"{[str(d) for d in dates]}")
211
+ context.trace("✅", f"Will run {len(requests)} requests")
212
+ for r in requests:
213
+ r = {k: v for k, v in r.items() if v != ("-",)}
214
+ context.trace("✅", f"mars {r}")
215
+
207
216
  for r in requests:
208
217
  r = {k: v for k, v in r.items() if v != ("-",)}
209
218
 
210
219
  if context.use_grib_paramid and "param" in r:
211
220
  r = use_grib_paramid(r)
212
221
 
213
- if DEBUG:
214
- context.trace("✅", f"from_source(mars, {r}")
215
-
216
222
  for k, v in r.items():
217
223
  if k not in MARS_KEYS:
218
224
  raise ValueError(
@@ -52,9 +52,19 @@ def load_one(emoji, context, dates, dataset, options={}, flavour=None, **kwargs)
52
52
  result = MultiFieldList([fs.sel(valid_datetime=date, **kwargs) for date in dates])
53
53
 
54
54
  if len(result) == 0:
55
- LOG.warning(f"No data found for {dataset} and dates {dates}")
55
+ LOG.warning(f"No data found for {dataset} and dates {dates} and {kwargs}")
56
56
  LOG.warning(f"Options: {options}")
57
- LOG.warning(data)
57
+
58
+ for i, k in enumerate(fs):
59
+ a = ["valid_datetime", k.metadata("valid_datetime", default=None)]
60
+ for n in kwargs.keys():
61
+ a.extend([n, k.metadata(n, default=None)])
62
+ print([str(x) for x in a])
63
+
64
+ if i > 16:
65
+ break
66
+
67
+ # LOG.warning(data)
58
68
 
59
69
  return result
60
70
 
@@ -55,6 +55,7 @@ class Coordinate:
55
55
  is_time = False
56
56
  is_step = False
57
57
  is_date = False
58
+ is_member = False
58
59
 
59
60
  def __init__(self, variable):
60
61
  self.variable = variable
@@ -201,8 +202,14 @@ class LevelCoordinate(Coordinate):
201
202
 
202
203
 
203
204
  class EnsembleCoordinate(Coordinate):
205
+ is_member = True
204
206
  mars_names = ("number",)
205
207
 
208
+ def normalise(self, value):
209
+ if int(value) == value:
210
+ return int(value)
211
+ return value
212
+
206
213
 
207
214
  class LongitudeCoordinate(Coordinate):
208
215
  is_grid = True
@@ -7,6 +7,7 @@
7
7
  # nor does it submit to any jurisdiction.
8
8
  #
9
9
 
10
+ import datetime
10
11
  import logging
11
12
 
12
13
  from earthkit.data.core.fieldlist import Field
@@ -80,7 +81,7 @@ class XArrayField(Field):
80
81
  return values.reshape(self.shape)
81
82
 
82
83
  def _make_metadata(self):
83
- return XArrayMetadata(self, self.owner.mapping)
84
+ return XArrayMetadata(self)
84
85
 
85
86
  def grid_points(self):
86
87
  return self.owner.grid_points()
@@ -103,7 +104,12 @@ class XArrayField(Field):
103
104
 
104
105
  @property
105
106
  def forecast_reference_time(self):
106
- return self.owner.forecast_reference_time
107
+ date, time = self.metadata("date", "time")
108
+ assert len(time) == 4, time
109
+ assert len(date) == 8, date
110
+ yyyymmdd = int(date)
111
+ time = int(time) // 100
112
+ return datetime.datetime(yyyymmdd // 10000, yyyymmdd // 100 % 100, yyyymmdd % 100, time)
107
113
 
108
114
  def __repr__(self):
109
115
  return repr(self._metadata)
@@ -134,8 +134,6 @@ class XarrayFieldList(FieldList):
134
134
 
135
135
  for v in self.variables:
136
136
 
137
- v.update_metadata_mapping(kwargs)
138
-
139
137
  # First, select matching variables
140
138
  # This will consume 'param' or 'variable' from kwargs
141
139
  # and return the rest
@@ -9,6 +9,7 @@
9
9
 
10
10
 
11
11
  from .coordinates import DateCoordinate
12
+ from .coordinates import EnsembleCoordinate
12
13
  from .coordinates import LatitudeCoordinate
13
14
  from .coordinates import LevelCoordinate
14
15
  from .coordinates import LongitudeCoordinate
@@ -135,6 +136,17 @@ class CoordinateGuesser:
135
136
  if d is not None:
136
137
  return d
137
138
 
139
+ d = self._is_number(
140
+ c,
141
+ axis=axis,
142
+ name=name,
143
+ long_name=long_name,
144
+ standard_name=standard_name,
145
+ units=units,
146
+ )
147
+ if d is not None:
148
+ return d
149
+
138
150
  if c.shape in ((1,), tuple()):
139
151
  return ScalarCoordinate(c)
140
152
 
@@ -249,9 +261,13 @@ class DefaultCoordinateGuesser(CoordinateGuesser):
249
261
  if standard_name == "depth":
250
262
  return LevelCoordinate(c, "depth")
251
263
 
252
- if name == "pressure":
264
+ if name == "vertical" and units == "hPa":
253
265
  return LevelCoordinate(c, "pl")
254
266
 
267
+ def _is_number(self, c, *, axis, name, long_name, standard_name, units):
268
+ if name in ("realization", "number"):
269
+ return EnsembleCoordinate(c)
270
+
255
271
 
256
272
  class FlavourCoordinateGuesser(CoordinateGuesser):
257
273
  def __init__(self, ds, flavour):
@@ -328,3 +344,7 @@ class FlavourCoordinateGuesser(CoordinateGuesser):
328
344
  return self.flavour["levtype"]
329
345
 
330
346
  raise NotImplementedError(f"levtype for {c=}")
347
+
348
+ def _is_number(self, c, *, axis, name, long_name, standard_name, units):
349
+ if self._match(c, "number", locals()):
350
+ return DateCoordinate(c)
@@ -10,29 +10,37 @@
10
10
  import logging
11
11
  from functools import cached_property
12
12
 
13
+ from anemoi.utils.dates import as_datetime
13
14
  from earthkit.data.core.geography import Geography
14
15
  from earthkit.data.core.metadata import RawMetadata
15
- from earthkit.data.utils.dates import to_datetime
16
16
  from earthkit.data.utils.projections import Projection
17
17
 
18
18
  LOG = logging.getLogger(__name__)
19
19
 
20
20
 
21
- class MDMapping:
21
+ class _MDMapping:
22
22
 
23
- def __init__(self, mapping):
24
- self.user_to_internal = mapping
23
+ def __init__(self, variable):
24
+ self.variable = variable
25
+ self.time = variable.time
26
+ self.mapping = dict(param="variable")
27
+ for c in variable.coordinates:
28
+ for v in c.mars_names:
29
+ assert v not in self.mapping, f"Duplicate key '{v}' in {c}"
30
+ self.mapping[v] = c.variable.name
25
31
 
26
- def from_user(self, kwargs):
27
- if isinstance(kwargs, str):
28
- return self.user_to_internal.get(kwargs, kwargs)
29
- return {self.user_to_internal.get(k, k): v for k, v in kwargs.items()}
32
+ def _from_user(self, key):
33
+ return self.mapping.get(key, key)
30
34
 
31
- def __len__(self):
32
- return len(self.user_to_internal)
35
+ def from_user(self, kwargs):
36
+ print("from_user", kwargs, self)
37
+ return {self._from_user(k): v for k, v in kwargs.items()}
33
38
 
34
39
  def __repr__(self):
35
- return f"MDMapping({self.user_to_internal})"
40
+ return f"MDMapping({self.mapping})"
41
+
42
+ def fill_time_metadata(self, field, md):
43
+ md["valid_datetime"] = as_datetime(self.variable.time.fill_time_metadata(field._md, md)).isoformat()
36
44
 
37
45
 
38
46
  class XArrayMetadata(RawMetadata):
@@ -40,23 +48,11 @@ class XArrayMetadata(RawMetadata):
40
48
  NAMESPACES = ["default", "mars"]
41
49
  MARS_KEYS = ["param", "step", "levelist", "levtype", "number", "date", "time"]
42
50
 
43
- def __init__(self, field, mapping):
51
+ def __init__(self, field):
44
52
  self._field = field
45
53
  md = field._md.copy()
46
-
47
- self._mapping = mapping
48
- if mapping is None:
49
- time_coord = [c for c in field.owner.coordinates if c.is_time]
50
- if len(time_coord) == 1:
51
- time_key = time_coord[0].name
52
- else:
53
- time_key = "time"
54
- else:
55
- time_key = mapping.from_user("valid_datetime")
56
- self._time = to_datetime(md.pop(time_key))
57
- self._field.owner.time.fill_time_metadata(self._time, md)
58
- md["valid_datetime"] = self._time.isoformat()
59
-
54
+ self._mapping = _MDMapping(field.owner)
55
+ self._mapping.fill_time_metadata(field, md)
60
56
  super().__init__(md)
61
57
 
62
58
  @cached_property
@@ -74,24 +70,29 @@ class XArrayMetadata(RawMetadata):
74
70
  return self._as_mars()
75
71
 
76
72
  def _as_mars(self):
77
- return dict(
78
- param=self["variable"],
79
- step=self["step"],
80
- levelist=self["level"],
81
- levtype=self["levtype"],
82
- number=self["number"],
83
- date=self["date"],
84
- time=self["time"],
85
- )
73
+ return {}
74
+ # p = dict(
75
+ # param=self.get("variable", self.get("param")),
76
+ # step=self.get("step"),
77
+ # levelist=self.get("levelist", self.get("level")),
78
+ # levtype=self.get("levtype"),
79
+ # number=self.get("number"),
80
+ # date=self.get("date"),
81
+ # time=self.get("time"),
82
+ # )
83
+ # return {k: v for k, v in p.items() if v is not None}
86
84
 
87
85
  def _base_datetime(self):
88
86
  return self._field.forecast_reference_time
89
87
 
90
88
  def _valid_datetime(self):
91
- return self._time
89
+ return self._get("valid_datetime")
92
90
 
93
91
  def _get(self, key, **kwargs):
94
92
 
93
+ if key in self._d:
94
+ return self._d[key]
95
+
95
96
  if key.startswith("mars."):
96
97
  key = key[5:]
97
98
  if key not in self.MARS_KEYS:
@@ -100,8 +101,7 @@ class XArrayMetadata(RawMetadata):
100
101
  else:
101
102
  return kwargs.get("default", None)
102
103
 
103
- if self._mapping is not None:
104
- key = self._mapping.from_user(key)
104
+ key = self._mapping._from_user(key)
105
105
 
106
106
  return super()._get(key, **kwargs)
107
107
 
@@ -137,12 +137,12 @@ class XArrayFieldGeography(Geography):
137
137
  # TODO: implement resolution
138
138
  return None
139
139
 
140
- @property
140
+ # @property
141
141
  def mars_grid(self):
142
142
  # TODO: implement mars_grid
143
143
  return None
144
144
 
145
- @property
145
+ # @property
146
146
  def mars_area(self):
147
147
  # TODO: code me
148
148
  # return [self.north, self.west, self.south, self.east]
@@ -10,8 +10,11 @@
10
10
 
11
11
  import datetime
12
12
 
13
+ from anemoi.utils.dates import as_datetime
14
+
13
15
 
14
16
  class Time:
17
+
15
18
  @classmethod
16
19
  def from_coordinates(cls, coordinates):
17
20
  time_coordinate = [c for c in coordinates if c.is_time]
@@ -19,16 +22,16 @@ class Time:
19
22
  date_coordinate = [c for c in coordinates if c.is_date]
20
23
 
21
24
  if len(date_coordinate) == 0 and len(time_coordinate) == 1 and len(step_coordinate) == 1:
22
- return ForecasstFromValidTimeAndStep(step_coordinate[0])
25
+ return ForecastFromValidTimeAndStep(time_coordinate[0], step_coordinate[0])
23
26
 
24
27
  if len(date_coordinate) == 0 and len(time_coordinate) == 1 and len(step_coordinate) == 0:
25
- return Analysis()
28
+ return Analysis(time_coordinate[0])
26
29
 
27
30
  if len(date_coordinate) == 0 and len(time_coordinate) == 0 and len(step_coordinate) == 0:
28
31
  return Constant()
29
32
 
30
33
  if len(date_coordinate) == 1 and len(time_coordinate) == 1 and len(step_coordinate) == 0:
31
- return ForecastFromValidTimeAndBaseTime(date_coordinate[0])
34
+ return ForecastFromValidTimeAndBaseTime(date_coordinate[0], time_coordinate[0])
32
35
 
33
36
  if len(date_coordinate) == 1 and len(time_coordinate) == 0 and len(step_coordinate) == 1:
34
37
  return ForecastFromBaseTimeAndDate(date_coordinate[0], step_coordinate[0])
@@ -38,61 +41,91 @@ class Time:
38
41
 
39
42
  class Constant(Time):
40
43
 
41
- def fill_time_metadata(self, time, metadata):
42
- metadata["date"] = time.strftime("%Y%m%d")
43
- metadata["time"] = time.strftime("%H%M")
44
- metadata["step"] = 0
44
+ def fill_time_metadata(self, coords_values, metadata):
45
+ raise NotImplementedError("Constant time not implemented")
46
+ # print("Constant", coords_values, metadata)
47
+ # metadata["date"] = time.strftime("%Y%m%d")
48
+ # metadata["time"] = time.strftime("%H%M")
49
+ # metadata["step"] = 0
45
50
 
46
51
 
47
52
  class Analysis(Time):
48
53
 
49
- def fill_time_metadata(self, time, metadata):
50
- metadata["date"] = time.strftime("%Y%m%d")
51
- metadata["time"] = time.strftime("%H%M")
54
+ def __init__(self, time_coordinate):
55
+ self.time_coordinate_name = time_coordinate.variable.name
56
+
57
+ def fill_time_metadata(self, coords_values, metadata):
58
+ valid_datetime = coords_values[self.time_coordinate_name]
59
+
60
+ metadata["date"] = as_datetime(valid_datetime).strftime("%Y%m%d")
61
+ metadata["time"] = as_datetime(valid_datetime).strftime("%H%M")
52
62
  metadata["step"] = 0
53
63
 
64
+ return valid_datetime
54
65
 
55
- class ForecasstFromValidTimeAndStep(Time):
56
- def __init__(self, step_coordinate):
57
- self.step_name = step_coordinate.variable.name
58
66
 
59
- def fill_time_metadata(self, time, metadata):
60
- step = metadata.pop(self.step_name)
67
+ class ForecastFromValidTimeAndStep(Time):
68
+
69
+ def __init__(self, time_coordinate, step_coordinate):
70
+ self.time_coordinate_name = time_coordinate.variable.name
71
+ self.step_coordinate_name = step_coordinate.variable.name
72
+
73
+ def fill_time_metadata(self, coords_values, metadata):
74
+ valid_datetime = coords_values[self.time_coordinate_name]
75
+ step = coords_values[self.step_coordinate_name]
76
+
61
77
  assert isinstance(step, datetime.timedelta)
62
- base = time - step
78
+ base_datetime = valid_datetime - step
63
79
 
64
80
  hours = step.total_seconds() / 3600
65
81
  assert int(hours) == hours
66
82
 
67
- metadata["date"] = base.strftime("%Y%m%d")
68
- metadata["time"] = base.strftime("%H%M")
83
+ metadata["date"] = as_datetime(base_datetime).strftime("%Y%m%d")
84
+ metadata["time"] = as_datetime(base_datetime).strftime("%H%M")
69
85
  metadata["step"] = int(hours)
86
+ return valid_datetime
70
87
 
71
88
 
72
89
  class ForecastFromValidTimeAndBaseTime(Time):
73
- def __init__(self, date_coordinate):
74
- self.date_coordinate = date_coordinate
75
90
 
76
- def fill_time_metadata(self, time, metadata):
91
+ def __init__(self, date_coordinate, time_coordinate):
92
+ self.date_coordinate.name = date_coordinate.name
93
+ self.time_coordinate.name = time_coordinate.name
94
+
95
+ def fill_time_metadata(self, coords_values, metadata):
96
+ valid_datetime = coords_values[self.time_coordinate_name]
97
+ base_datetime = coords_values[self.date_coordinate_name]
77
98
 
78
- step = time - self.date_coordinate
99
+ step = valid_datetime - base_datetime
79
100
 
80
101
  hours = step.total_seconds() / 3600
81
102
  assert int(hours) == hours
82
103
 
83
- metadata["date"] = self.date_coordinate.single_value.strftime("%Y%m%d")
84
- metadata["time"] = self.date_coordinate.single_value.strftime("%H%M")
104
+ metadata["date"] = as_datetime(base_datetime).strftime("%Y%m%d")
105
+ metadata["time"] = as_datetime(base_datetime).strftime("%H%M")
85
106
  metadata["step"] = int(hours)
86
107
 
108
+ return valid_datetime
109
+
87
110
 
88
111
  class ForecastFromBaseTimeAndDate(Time):
112
+
89
113
  def __init__(self, date_coordinate, step_coordinate):
90
- self.date_coordinate = date_coordinate
91
- self.step_coordinate = step_coordinate
114
+ self.date_coordinate_name = date_coordinate.name
115
+ self.step_coordinate_name = step_coordinate.name
116
+
117
+ def fill_time_metadata(self, coords_values, metadata):
118
+
119
+ date = coords_values[self.date_coordinate_name]
120
+ step = coords_values[self.step_coordinate_name]
121
+ assert isinstance(step, datetime.timedelta)
122
+
123
+ metadata["date"] = as_datetime(date).strftime("%Y%m%d")
124
+ metadata["time"] = as_datetime(date).strftime("%H%M")
125
+
126
+ hours = step.total_seconds() / 3600
92
127
 
93
- def fill_time_metadata(self, time, metadata):
94
- metadata["date"] = time.strftime("%Y%m%d")
95
- metadata["time"] = time.strftime("%H%M")
96
- hours = metadata[self.step_coordinate.name].total_seconds() / 3600
97
128
  assert int(hours) == hours
98
129
  metadata["step"] = int(hours)
130
+
131
+ return date + step
@@ -14,34 +14,32 @@ from functools import cached_property
14
14
  import numpy as np
15
15
  from earthkit.data.utils.array import ensure_backend
16
16
 
17
- from anemoi.datasets.create.functions.sources.xarray.metadata import MDMapping
18
-
19
17
  from .field import XArrayField
20
18
 
21
19
  LOG = logging.getLogger(__name__)
22
20
 
23
21
 
24
22
  class Variable:
25
- def __init__(self, *, ds, var, coordinates, grid, time, metadata, mapping=None, array_backend=None):
23
+ def __init__(
24
+ self,
25
+ *,
26
+ ds,
27
+ var,
28
+ coordinates,
29
+ grid,
30
+ time,
31
+ metadata,
32
+ array_backend=None,
33
+ ):
26
34
  self.ds = ds
27
35
  self.var = var
28
36
 
29
37
  self.grid = grid
30
38
  self.coordinates = coordinates
31
39
 
32
- # print("Variable", var.name)
33
- # for c in coordinates:
34
- # print(" ", c)
35
-
36
40
  self._metadata = metadata.copy()
37
- # self._metadata.update(var.attrs)
38
41
  self._metadata.update({"variable": var.name})
39
42
 
40
- # self._metadata.setdefault("level", None)
41
- # self._metadata.setdefault("number", 0)
42
- # self._metadata.setdefault("levtype", "sfc")
43
- self._mapping = mapping
44
-
45
43
  self.time = time
46
44
 
47
45
  self.shape = tuple(len(c.variable) for c in coordinates if c.is_dim and not c.scalar and not c.is_grid)
@@ -51,23 +49,6 @@ class Variable:
51
49
  self.length = math.prod(self.shape)
52
50
  self.array_backend = ensure_backend(array_backend)
53
51
 
54
- def update_metadata_mapping(self, kwargs):
55
-
56
- result = {}
57
-
58
- for k, v in kwargs.items():
59
- if k == "param":
60
- result[k] = "variable"
61
- continue
62
-
63
- for c in self.coordinates:
64
- if k in c.mars_names:
65
- for v in c.mars_names:
66
- result[v] = c.variable.name
67
- break
68
-
69
- self._mapping = MDMapping(result)
70
-
71
52
  @property
72
53
  def name(self):
73
54
  return self.var.name
@@ -111,17 +92,11 @@ class Variable:
111
92
  kwargs = {k: v for k, v in zip(self.names, coords)}
112
93
  return XArrayField(self, self.var.isel(kwargs))
113
94
 
114
- @property
115
- def mapping(self):
116
- return self._mapping
117
-
118
95
  def sel(self, missing, **kwargs):
119
96
 
120
97
  if not kwargs:
121
98
  return self
122
99
 
123
- kwargs = self._mapping.from_user(kwargs)
124
-
125
100
  k, v = kwargs.popitem()
126
101
 
127
102
  c = self.by_name.get(k)
@@ -147,13 +122,15 @@ class Variable:
147
122
  grid=self.grid,
148
123
  time=self.time,
149
124
  metadata=metadata,
150
- mapping=self.mapping,
151
125
  )
152
126
 
153
127
  return variable.sel(missing, **kwargs)
154
128
 
155
129
  def match(self, **kwargs):
156
- kwargs = self._mapping.from_user(kwargs)
130
+
131
+ if "param" in kwargs:
132
+ assert "variable" not in kwargs
133
+ kwargs["variable"] = kwargs.pop("param")
157
134
 
158
135
  if "variable" in kwargs:
159
136
  name = kwargs.pop("variable")