anemoi-datasets 0.5.24__py3-none-any.whl → 0.5.26__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 (58) hide show
  1. anemoi/datasets/_version.py +2 -2
  2. anemoi/datasets/commands/finalise-additions.py +2 -1
  3. anemoi/datasets/commands/finalise.py +2 -1
  4. anemoi/datasets/commands/grib-index.py +1 -1
  5. anemoi/datasets/commands/init-additions.py +2 -1
  6. anemoi/datasets/commands/load-additions.py +2 -1
  7. anemoi/datasets/commands/load.py +2 -1
  8. anemoi/datasets/create/__init__.py +24 -33
  9. anemoi/datasets/create/filter.py +22 -24
  10. anemoi/datasets/create/input/__init__.py +0 -20
  11. anemoi/datasets/create/input/step.py +2 -16
  12. anemoi/datasets/create/sources/accumulations.py +7 -6
  13. anemoi/datasets/create/sources/planetary_computer.py +44 -0
  14. anemoi/datasets/create/sources/xarray_support/__init__.py +6 -22
  15. anemoi/datasets/create/sources/xarray_support/coordinates.py +8 -0
  16. anemoi/datasets/create/sources/xarray_support/field.py +1 -4
  17. anemoi/datasets/create/sources/xarray_support/flavour.py +44 -6
  18. anemoi/datasets/create/sources/xarray_support/patch.py +44 -1
  19. anemoi/datasets/create/sources/xarray_support/variable.py +6 -2
  20. anemoi/datasets/data/complement.py +44 -10
  21. anemoi/datasets/data/dataset.py +29 -0
  22. anemoi/datasets/data/forwards.py +8 -2
  23. anemoi/datasets/data/misc.py +74 -16
  24. anemoi/datasets/data/observations/__init__.py +316 -0
  25. anemoi/datasets/data/observations/legacy_obs_dataset.py +200 -0
  26. anemoi/datasets/data/observations/multi.py +64 -0
  27. anemoi/datasets/data/padded.py +227 -0
  28. anemoi/datasets/data/records/__init__.py +442 -0
  29. anemoi/datasets/data/records/backends/__init__.py +157 -0
  30. anemoi/datasets/data/stores.py +7 -56
  31. anemoi/datasets/data/subset.py +5 -0
  32. anemoi/datasets/grids.py +6 -3
  33. {anemoi_datasets-0.5.24.dist-info → anemoi_datasets-0.5.26.dist-info}/METADATA +3 -2
  34. {anemoi_datasets-0.5.24.dist-info → anemoi_datasets-0.5.26.dist-info}/RECORD +38 -51
  35. {anemoi_datasets-0.5.24.dist-info → anemoi_datasets-0.5.26.dist-info}/WHEEL +1 -1
  36. anemoi/datasets/create/filters/__init__.py +0 -33
  37. anemoi/datasets/create/filters/empty.py +0 -37
  38. anemoi/datasets/create/filters/legacy.py +0 -93
  39. anemoi/datasets/create/filters/noop.py +0 -37
  40. anemoi/datasets/create/filters/orog_to_z.py +0 -58
  41. anemoi/datasets/create/filters/pressure_level_relative_humidity_to_specific_humidity.py +0 -83
  42. anemoi/datasets/create/filters/pressure_level_specific_humidity_to_relative_humidity.py +0 -84
  43. anemoi/datasets/create/filters/rename.py +0 -205
  44. anemoi/datasets/create/filters/rotate_winds.py +0 -105
  45. anemoi/datasets/create/filters/single_level_dewpoint_to_relative_humidity.py +0 -78
  46. anemoi/datasets/create/filters/single_level_relative_humidity_to_dewpoint.py +0 -84
  47. anemoi/datasets/create/filters/single_level_relative_humidity_to_specific_humidity.py +0 -163
  48. anemoi/datasets/create/filters/single_level_specific_humidity_to_relative_humidity.py +0 -451
  49. anemoi/datasets/create/filters/speeddir_to_uv.py +0 -95
  50. anemoi/datasets/create/filters/sum.py +0 -68
  51. anemoi/datasets/create/filters/transform.py +0 -51
  52. anemoi/datasets/create/filters/unrotate_winds.py +0 -105
  53. anemoi/datasets/create/filters/uv_to_speeddir.py +0 -94
  54. anemoi/datasets/create/filters/wz_to_w.py +0 -98
  55. anemoi/datasets/create/testing.py +0 -76
  56. {anemoi_datasets-0.5.24.dist-info → anemoi_datasets-0.5.26.dist-info}/entry_points.txt +0 -0
  57. {anemoi_datasets-0.5.24.dist-info → anemoi_datasets-0.5.26.dist-info}/licenses/LICENSE +0 -0
  58. {anemoi_datasets-0.5.24.dist-info → anemoi_datasets-0.5.26.dist-info}/top_level.txt +0 -0
@@ -1,451 +0,0 @@
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 typing import Any
12
- from typing import Dict
13
- from typing import List
14
- from typing import Tuple
15
- from typing import Union
16
-
17
- import earthkit.data as ekd
18
- import numpy as np
19
- from anemoi.transform.fields import new_field_from_numpy
20
- from anemoi.transform.fields import new_fieldlist_from_list
21
- from earthkit.meteo import constants
22
- from earthkit.meteo import thermo
23
- from numpy.typing import NDArray
24
-
25
- from .legacy import legacy_filter
26
-
27
-
28
- # Alternative proposed by Baudouin Raoult
29
- class AutoDict(dict):
30
- """A dictionary that automatically creates nested dictionaries for missing keys."""
31
-
32
- def __missing__(self, key: Any) -> Any:
33
- """Handle missing keys by creating nested dictionaries.
34
-
35
- Parameters
36
- ----------
37
- key : Any
38
- The missing key.
39
-
40
- Returns
41
- -------
42
- Any
43
- A new nested dictionary.
44
- """
45
- value = self[key] = type(self)()
46
- return value
47
-
48
-
49
- def model_level_pressure(
50
- A: NDArray[Any], B: NDArray[Any], surface_pressure: Union[float, np.ndarray]
51
- ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
52
- """Calculates:
53
- - pressure at the model full- and half-levels
54
- - delta: depth of log(pressure) at full levels
55
- - alpha: alpha term #TODO: more descriptive information.
56
-
57
- Parameters
58
- ----------
59
- A : ndarray
60
- A-coefficients defining the model levels
61
- B : ndarray
62
- B-coefficients defining the model levels
63
- surface_pressure : number or ndarray
64
- surface pressure (Pa)
65
-
66
- Returns
67
- -------
68
- ndarray
69
- pressure at model full-levels
70
- ndarray
71
- pressure at model half-levels
72
- ndarray
73
- delta at full-levels
74
- ndarray
75
- alpha at full levels
76
- """
77
-
78
- # constants
79
- PRESSURE_TOA = 0.1 # safety when highest pressure level = 0.0
80
-
81
- # make the calculation agnostic to the number of dimensions
82
- ndim = surface_pressure.ndim
83
- new_shape_half = (A.shape[0],) + (1,) * ndim
84
- A_reshaped = A.reshape(new_shape_half)
85
- B_reshaped = B.reshape(new_shape_half)
86
-
87
- # calculate pressure on model half-levels
88
- p_half_level = A_reshaped + B_reshaped * surface_pressure[np.newaxis, ...]
89
-
90
- # calculate delta
91
- new_shape_full = (A.shape[0] - 1,) + surface_pressure.shape
92
- delta = np.zeros(new_shape_full)
93
- delta[1:, ...] = np.log(p_half_level[2:, ...] / p_half_level[1:-1, ...])
94
-
95
- # pressure at highest half level<= 0.1
96
- if np.any(p_half_level[0, ...] <= PRESSURE_TOA):
97
- delta[0, ...] = np.log(p_half_level[1, ...] / PRESSURE_TOA)
98
- # pressure at highest half level > 0.1
99
- else:
100
- delta[0, ...] = np.log(p_half_level[1, ...] / p_half_level[0, ...])
101
-
102
- # calculate alpha
103
- alpha = np.zeros(new_shape_full)
104
-
105
- alpha[1:, ...] = 1.0 - p_half_level[1:-1, ...] / (p_half_level[2:, ...] - p_half_level[1:-1, ...]) * delta[1:, ...]
106
-
107
- # pressure at highest half level <= 0.1
108
- if np.any(p_half_level[0, ...] <= PRESSURE_TOA):
109
- alpha[0, ...] = 1.0 # ARPEGE choice, ECMWF IFS uses log(2)
110
- # pressure at highest half level > 0.1
111
- else:
112
- alpha[0, ...] = 1.0 - p_half_level[0, ...] / (p_half_level[1, ...] - p_half_level[0, ...]) * delta[0, ...]
113
-
114
- # calculate pressure on model full levels
115
- # TODO: is there a faster way to calculate the averages?
116
- # TODO: introduce option to calculate full levels in more complicated way
117
- p_full_level = np.apply_along_axis(lambda m: np.convolve(m, np.ones(2) / 2, mode="valid"), axis=0, arr=p_half_level)
118
-
119
- return p_full_level, p_half_level, delta, alpha
120
-
121
-
122
- def calc_specific_gas_constant(q: Union[float, np.ndarray]) -> Union[float, NDArray[Any]]:
123
- """Calculates the specific gas constant of moist air
124
- (specific content of cloud particles and hydrometeors are neglected).
125
-
126
- Parameters
127
- ----------
128
- q : number or ndarray
129
- specific humidity
130
-
131
- Returns
132
- -------
133
- number or ndarray
134
- specific gas constant of moist air
135
- """
136
-
137
- R = constants.Rd + (constants.Rv - constants.Rd) * q
138
- return R
139
-
140
-
141
- def relative_geopotential_thickness(alpha: NDArray[Any], q: NDArray[Any], T: NDArray[Any]) -> NDArray[Any]:
142
- """Calculates the geopotential thickness w.r.t the surface on model full-levels.
143
-
144
- Parameters
145
- ----------
146
- alpha : ndarray
147
- alpha term of pressure calculations
148
- q : ndarray
149
- specific humidity (in kg/kg) on model full-levels
150
- T : ndarray
151
- temperature (in Kelvin) on model full-levels
152
-
153
- Returns
154
- -------
155
- ndarray
156
- geopotential thickness of model full-levels w.r.t. the surface
157
- """
158
-
159
- R = calc_specific_gas_constant(q)
160
- dphi = np.cumsum(np.flip(alpha * R * T, axis=0), axis=0)
161
- dphi = np.flip(dphi, axis=0)
162
-
163
- return dphi
164
-
165
-
166
- def pressure_at_height_level(
167
- height: float, q: NDArray[Any], T: NDArray[Any], sp: NDArray[Any], A: NDArray[Any], B: NDArray[Any]
168
- ) -> Union[float, NDArray[Any]]:
169
- """Calculates the pressure at a height level given in meters above surface.
170
- This is done by finding the model level above and below the specified height
171
- and interpolating the pressure.
172
-
173
- Parameters
174
- ----------
175
- height : number
176
- height (in meters) above the surface for which the pressure is wanted
177
- q : ndarray
178
- specific humidity (kg/kg) at model full-levels
179
- T : ndarray
180
- temperature (K) at model full-levels
181
- sp : ndarray
182
- surface pressure (Pa)
183
- A : ndarray
184
- A-coefficients defining the model levels
185
- B : ndarray
186
- B-coefficients defining the model levels
187
-
188
- Returns
189
- -------
190
- number or ndarray
191
- pressure (Pa) at the given height level
192
- """
193
-
194
- # geopotential thickness of the height level
195
- tdphi = height * constants.g
196
-
197
- # pressure(-related) variables
198
- p_full, p_half, _, alpha = model_level_pressure(A, B, sp)
199
-
200
- # relative geopot. thickness of full levels
201
- dphi = relative_geopotential_thickness(alpha, q, T)
202
-
203
- # find the model full level right above the height level
204
- i_phi = (tdphi > dphi).sum(0)
205
-
206
- # initialize the output array
207
- p_height = np.zeros_like(i_phi, dtype=np.float64)
208
-
209
- # define mask: requested height is below the lowest model full-level
210
- mask = i_phi == 0
211
-
212
- # CASE 1: requested height is below the lowest model full-level
213
- # --> interpolation between surface pressure and lowest model full-level
214
- p_height[mask] = (p_half[-1, ...] + tdphi / dphi[-1, ...] * (p_full[-1, ...] - p_half[-1, ...]))[mask]
215
-
216
- # CASE 2: requested height is above the lowest model full-level
217
- # --> interpolation between between model full-level above and below
218
-
219
- # define some indices for masking and readability
220
- i_lev = alpha.shape[0] - i_phi - 1 # convert phi index to model level index
221
- indices = np.indices(i_lev.shape)
222
- masked_indices = tuple(dim[~mask] for dim in indices)
223
- above = (i_lev[~mask],) + masked_indices
224
- below = (i_lev[~mask] + 1,) + masked_indices
225
-
226
- dphi_above = dphi[above]
227
- dphi_below = dphi[below]
228
-
229
- factor = (tdphi - dphi_above) / (dphi_below - dphi_above)
230
- p_height[~mask] = p_full[above] + factor * (p_full[below] - p_full[above])
231
-
232
- return p_height
233
-
234
-
235
- @legacy_filter(__file__)
236
- def execute(
237
- context: Any,
238
- input: List[Any],
239
- height: float,
240
- t: str,
241
- q: str,
242
- sp: str,
243
- new_name: str = "2r",
244
- **kwargs: Dict[str, Any],
245
- ) -> ekd.FieldList:
246
- """Convert the single (height) level specific humidity to relative humidity.
247
-
248
- Parameters
249
- ----------
250
- context : Any
251
- The context for the execution.
252
- input : list of Any
253
- The input data.
254
- height : float
255
- The height level in meters.
256
- t : str
257
- The temperature parameter name.
258
- q : str
259
- The specific humidity parameter name.
260
- sp : str
261
- The surface pressure parameter name.
262
- new_name : str, optional
263
- The new name for the relative humidity parameter, by default "2r".
264
- **kwargs : dict
265
- Additional keyword arguments.
266
- t_ml : str, optional
267
- The temperature parameter name for model levels, by default "t".
268
- q_ml : str, optional
269
- The specific humidity parameter name for model levels, by default "q".
270
- A : list of float
271
- A-coefficients defining the model levels.
272
- B : list of float
273
- B-coefficients defining the model levels.
274
- keep_q : bool, optional
275
- Whether to keep the specific humidity field in the result, by default False.
276
-
277
- Returns
278
- -------
279
- ekd.FieldList
280
- The resulting field array with relative humidity.
281
- """
282
- result = []
283
-
284
- MANDATORY_KEYS = ["A", "B"]
285
- OPTIONAL_KEYS = ["t_ml", "q_ml"]
286
- MISSING_KEYS = []
287
- DEFAULTS = dict(t_ml="t", q_ml="q")
288
-
289
- for key in OPTIONAL_KEYS:
290
- if key not in kwargs:
291
- print(f"key {key} not found in yaml-file, using default key: {DEFAULTS[key]}")
292
- kwargs[key] = DEFAULTS[key]
293
-
294
- for key in MANDATORY_KEYS:
295
- if key not in kwargs:
296
- MISSING_KEYS.append(key)
297
-
298
- if MISSING_KEYS:
299
- raise KeyError(f"Following keys are missing: {', '.join(MISSING_KEYS)}")
300
-
301
- single_level_params = (t, q, sp)
302
- model_level_params = (kwargs["t_ml"], kwargs["q_ml"])
303
-
304
- needed_fields = AutoDict()
305
-
306
- # Gather all necessary fields
307
- for f in input:
308
- key = f.metadata(namespace="mars")
309
- param = key.pop("param")
310
- # check single level parameters
311
- if param in single_level_params:
312
- levtype = key.pop("levtype")
313
- key = tuple(sorted(key.items()))
314
-
315
- if param in needed_fields[key][levtype]:
316
- raise ValueError(f"Duplicate single level field {param} for {key}")
317
-
318
- needed_fields[key][levtype][param] = f
319
- if param == q:
320
- if kwargs.get("keep_q", False):
321
- result.append(f)
322
- else:
323
- result.append(f)
324
-
325
- # check model level parameters
326
- elif param in model_level_params:
327
- levtype = key.pop("levtype")
328
- levelist = key.pop("levelist")
329
- key = tuple(sorted(key.items()))
330
-
331
- if param in needed_fields[key][levtype][levelist]:
332
- raise ValueError(f"Duplicate model level field {param} for {key} at level {levelist}")
333
-
334
- needed_fields[key][levtype][levelist][param] = f
335
-
336
- # all other parameters
337
- else:
338
- result.append(f)
339
-
340
- for _, values in needed_fields.items():
341
- # some checks
342
- if len(values["sfc"]) != 3:
343
- raise ValueError("Missing surface fields")
344
-
345
- q_sl = values["sfc"][q].to_numpy(flatten=True)
346
- t_sl = values["sfc"][t].to_numpy(flatten=True)
347
- sp_sl = values["sfc"][sp].to_numpy(flatten=True)
348
-
349
- nlevels = len(kwargs["A"]) - 1
350
- if len(values["ml"]) != nlevels:
351
- raise ValueError("Missing model levels")
352
-
353
- for key in values["ml"].keys():
354
- if len(values["ml"][key]) != 2:
355
- raise ValueError(f"Missing field on level {key}")
356
-
357
- # create 3D arrays for upper air fields
358
- levels = list(values["ml"].keys())
359
- levels.sort()
360
- t_ml = []
361
- q_ml = []
362
- for level in levels:
363
- t_ml.append(values["ml"][level][kwargs["t_ml"]].to_numpy(flatten=True))
364
- q_ml.append(values["ml"][level][kwargs["q_ml"]].to_numpy(flatten=True))
365
-
366
- t_ml = np.stack(t_ml)
367
- q_ml = np.stack(q_ml)
368
-
369
- # actual conversion from qv --> rh
370
- # FIXME:
371
- # For now We need to go from qv --> td --> rh to take into account
372
- # the mixed / ice phase when T ~ 0C / T < 0C
373
- # See https://github.com/ecmwf/earthkit-meteo/issues/15
374
- p_sl = pressure_at_height_level(height, q_ml, t_ml, sp_sl, np.array(kwargs["A"]), np.array(kwargs["B"]))
375
- td_sl = thermo.dewpoint_from_specific_humidity(q=q_sl, p=p_sl)
376
- rh_sl = thermo.relative_humidity_from_dewpoint(t=t_sl, td=td_sl)
377
-
378
- result.append(new_field_from_numpy(values["sfc"][q], rh_sl, param=new_name))
379
-
380
- return new_fieldlist_from_list(result)
381
-
382
-
383
- def test() -> None:
384
- """Test the conversion from specific humidity to relative humidity.
385
-
386
- This function fetches data from a source, performs the conversion, and prints
387
- the mean, median, and maximum differences in dewpoint temperature.
388
-
389
- Returns
390
- -------
391
- None
392
- """
393
- from earthkit.data import from_source
394
- from earthkit.data.readers.grib.index import GribFieldList
395
-
396
- # IFS forecasts have both specific humidity and dewpoint
397
- sl = from_source(
398
- "mars",
399
- {
400
- "date": "2022-01-01",
401
- "class": "od",
402
- "expver": "1",
403
- "stream": "oper",
404
- "levtype": "sfc",
405
- "param": "96.174/134.128/167.128/168.128",
406
- "time": "00:00:00",
407
- "type": "fc",
408
- "step": "2",
409
- "grid": "O640",
410
- },
411
- )
412
-
413
- ml = from_source(
414
- "mars",
415
- {
416
- "date": "2022-01-01",
417
- "class": "od",
418
- "expver": "1",
419
- "stream": "oper",
420
- "levtype": "ml",
421
- "levelist": "130/131/132/133/134/135/136/137",
422
- "param": "130/133",
423
- "time": "00:00:00",
424
- "type": "fc",
425
- "step": "2",
426
- "grid": "O640",
427
- },
428
- )
429
- source = GribFieldList.merge([sl, ml])
430
-
431
- # IFS A and B coeffients for level 137 - 129
432
- kwargs = {
433
- "A": [424.414063, 302.476563, 202.484375, 122.101563, 62.781250, 22.835938, 3.757813, 0.0, 0.0],
434
- "B": [0.969513, 0.975078, 0.980072, 0.984542, 0.988500, 0.991984, 0.995003, 0.997630, 1.000000],
435
- }
436
- source = execute(None, source, 2, "2t", "2sh", "sp", "2r", **kwargs)
437
-
438
- temperature = source[2].to_numpy(flatten=True)
439
- dewpoint = source[3].to_numpy(flatten=True)
440
- relhum = source[4].to_numpy()
441
- newdew = thermo.dewpoint_from_relative_humidity(temperature, relhum)
442
-
443
- print(f"Mean difference in dewpoint temperature: {np.abs(newdew - dewpoint).mean():02f} degC")
444
- print(f"Median difference in dewpoint temperature: {np.median(np.abs(newdew - dewpoint)):02f} degC")
445
- print(f"Maximum difference in dewpoint temperature: {np.abs(newdew - dewpoint).max():02f} degC")
446
-
447
- # source.save("source.grib")
448
-
449
-
450
- if __name__ == "__main__":
451
- test()
@@ -1,95 +0,0 @@
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
- from typing import Any
13
- from typing import List
14
-
15
- import earthkit.data as ekd
16
- import numpy as np
17
- from anemoi.transform.fields import new_field_from_numpy
18
- from anemoi.transform.fields import new_fieldlist_from_list
19
- from earthkit.meteo.wind.array import polar_to_xy
20
-
21
- from .legacy import legacy_filter
22
-
23
-
24
- @legacy_filter(__file__)
25
- def execute(
26
- context: Any,
27
- input: List[Any],
28
- wind_speed: str,
29
- wind_dir: str,
30
- u_component: str = "u",
31
- v_component: str = "v",
32
- in_radians: bool = False,
33
- ) -> ekd.FieldList:
34
- """Convert wind speed and direction to u and v components.
35
-
36
- Parameters
37
- ----------
38
- context : Any
39
- The context for the execution.
40
- input : List[Any]
41
- The input data fields.
42
- wind_speed : str
43
- The name of the wind speed parameter.
44
- wind_dir : str
45
- The name of the wind direction parameter.
46
- u_component : str, optional
47
- The name for the u component. Defaults to "u".
48
- v_component : str, optional
49
- The name for the v component. Defaults to "v".
50
- in_radians : bool, optional
51
- Whether the wind direction is in radians. Defaults to False.
52
-
53
- Returns
54
- -------
55
- ekd.FieldList
56
- The resulting field array with u and v components.
57
- """
58
-
59
- result = []
60
-
61
- wind_params = (wind_speed, wind_dir)
62
- wind_pairs = defaultdict(dict)
63
-
64
- for f in input:
65
- key = f.metadata(namespace="mars")
66
- param = key.pop("param")
67
-
68
- if param not in wind_params:
69
- result.append(f)
70
- continue
71
-
72
- key = tuple(key.items())
73
-
74
- if param in wind_pairs[key]:
75
- raise ValueError(f"Duplicate wind component {param} for {key}")
76
-
77
- wind_pairs[key][param] = f
78
-
79
- for _, pairs in wind_pairs.items():
80
- if len(pairs) != 2:
81
- raise ValueError("Missing wind component")
82
-
83
- magnitude = pairs[wind_speed]
84
- direction = pairs[wind_dir]
85
-
86
- # assert speed.grid_mapping == dir.grid_mapping
87
- if in_radians:
88
- direction = np.rad2deg(direction)
89
-
90
- u, v = polar_to_xy(magnitude.to_numpy(flatten=True), direction.to_numpy(flatten=True))
91
-
92
- result.append(new_field_from_numpy(magnitude, u, param=u_component))
93
- result.append(new_field_from_numpy(direction, v, param=v_component))
94
-
95
- return new_fieldlist_from_list(result)
@@ -1,68 +0,0 @@
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
- from collections import defaultdict
11
- from typing import Any
12
- from typing import Dict
13
- from typing import Hashable
14
- from typing import List
15
- from typing import Tuple
16
-
17
- import earthkit.data as ekd
18
- from anemoi.transform.fields import new_field_from_numpy
19
- from anemoi.transform.fields import new_fieldlist_from_list
20
-
21
- from .legacy import legacy_filter
22
-
23
-
24
- @legacy_filter(__file__)
25
- def execute(context: Any, input: ekd.FieldList, params: List[str], output: str) -> ekd.FieldList:
26
- """Computes the sum over a set of variables.
27
-
28
- Args:
29
- context (Any): The execution context.
30
- input (List[Any]): The list of input fields.
31
- params (List[str]): The list of parameters to sum over.
32
- output (str): The name for the output field.
33
-
34
- Returns:
35
- ekd.FieldList: The resulting FieldArray with summed fields.
36
- """
37
- result = []
38
-
39
- needed_fields: Dict[Tuple[Hashable, ...], Dict[str, ekd.Field]] = defaultdict(dict)
40
-
41
- for f in input:
42
- key = f.metadata(namespace="mars")
43
- param = key.pop("param")
44
- if param in params:
45
- key = tuple(key.items())
46
-
47
- if param in needed_fields[key]:
48
- raise ValueError(f"Duplicate field {param} for {key}")
49
-
50
- needed_fields[key][param] = f
51
- else:
52
- result.append(f)
53
-
54
- for keys, values in needed_fields.items():
55
-
56
- if len(values) != len(params):
57
- raise ValueError("Missing fields")
58
-
59
- s = None
60
- for k, v in values.items():
61
- c = v.to_numpy(flatten=True)
62
- if s is None:
63
- s = c
64
- else:
65
- s += c
66
- result.append(new_field_from_numpy(values[list(values.keys())[0]], s, param=output))
67
-
68
- return new_fieldlist_from_list(result)
@@ -1,51 +0,0 @@
1
- # (C) Copyright 2025 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
- from typing import Any
11
- from typing import Dict
12
-
13
- import earthkit.data as ekd
14
-
15
- from ..filter import Filter
16
-
17
-
18
- class TransformFilter(Filter):
19
- """Calls filters from anemoi.transform.filters
20
-
21
- Parameters
22
- ----------
23
- context : Any
24
- The context in which the filter is created.
25
- name : str
26
- The name of the filter.
27
- config : Dict[str, Any]
28
- The configuration for the filter.
29
- """
30
-
31
- def __init__(self, context: Any, name: str, config: Dict[str, Any]) -> None:
32
-
33
- from anemoi.transform.filters import create_filter
34
-
35
- self.name = name
36
- self.transform_filter = create_filter(context, config)
37
-
38
- def execute(self, input: ekd.FieldList) -> ekd.FieldList:
39
- """Execute the transformation filter.
40
-
41
- Parameters
42
- ----------
43
- input : ekd.FieldList
44
- The input data to be transformed.
45
-
46
- Returns
47
- -------
48
- ekd.FieldList
49
- The transformed data.
50
- """
51
- return self.transform_filter.forward(input)