anemoi-datasets 0.5.16__py3-none-any.whl → 0.5.17__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 (155) hide show
  1. anemoi/datasets/__init__.py +4 -1
  2. anemoi/datasets/__main__.py +12 -2
  3. anemoi/datasets/_version.py +9 -4
  4. anemoi/datasets/commands/cleanup.py +17 -2
  5. anemoi/datasets/commands/compare.py +18 -2
  6. anemoi/datasets/commands/copy.py +196 -14
  7. anemoi/datasets/commands/create.py +50 -7
  8. anemoi/datasets/commands/finalise-additions.py +17 -2
  9. anemoi/datasets/commands/finalise.py +17 -2
  10. anemoi/datasets/commands/init-additions.py +17 -2
  11. anemoi/datasets/commands/init.py +16 -2
  12. anemoi/datasets/commands/inspect.py +283 -62
  13. anemoi/datasets/commands/load-additions.py +16 -2
  14. anemoi/datasets/commands/load.py +16 -2
  15. anemoi/datasets/commands/patch.py +17 -2
  16. anemoi/datasets/commands/publish.py +17 -2
  17. anemoi/datasets/commands/scan.py +31 -3
  18. anemoi/datasets/compute/recentre.py +47 -11
  19. anemoi/datasets/create/__init__.py +612 -85
  20. anemoi/datasets/create/check.py +142 -20
  21. anemoi/datasets/create/chunks.py +64 -4
  22. anemoi/datasets/create/config.py +185 -21
  23. anemoi/datasets/create/filter.py +50 -0
  24. anemoi/datasets/create/filters/__init__.py +33 -0
  25. anemoi/datasets/create/filters/empty.py +37 -0
  26. anemoi/datasets/create/filters/legacy.py +93 -0
  27. anemoi/datasets/create/filters/noop.py +37 -0
  28. anemoi/datasets/create/filters/orog_to_z.py +58 -0
  29. anemoi/datasets/create/{functions/filters → filters}/pressure_level_relative_humidity_to_specific_humidity.py +33 -10
  30. anemoi/datasets/create/{functions/filters → filters}/pressure_level_specific_humidity_to_relative_humidity.py +32 -8
  31. anemoi/datasets/create/filters/rename.py +205 -0
  32. anemoi/datasets/create/{functions/filters → filters}/rotate_winds.py +43 -28
  33. anemoi/datasets/create/{functions/filters → filters}/single_level_dewpoint_to_relative_humidity.py +32 -9
  34. anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_dewpoint.py +33 -9
  35. anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_specific_humidity.py +55 -7
  36. anemoi/datasets/create/{functions/filters → filters}/single_level_specific_humidity_to_relative_humidity.py +98 -37
  37. anemoi/datasets/create/filters/speeddir_to_uv.py +95 -0
  38. anemoi/datasets/create/{functions/filters → filters}/sum.py +24 -27
  39. anemoi/datasets/create/filters/transform.py +53 -0
  40. anemoi/datasets/create/{functions/filters → filters}/unrotate_winds.py +27 -18
  41. anemoi/datasets/create/filters/uv_to_speeddir.py +94 -0
  42. anemoi/datasets/create/{functions/filters → filters}/wz_to_w.py +51 -33
  43. anemoi/datasets/create/input/__init__.py +76 -5
  44. anemoi/datasets/create/input/action.py +149 -13
  45. anemoi/datasets/create/input/concat.py +81 -10
  46. anemoi/datasets/create/input/context.py +39 -4
  47. anemoi/datasets/create/input/data_sources.py +72 -6
  48. anemoi/datasets/create/input/empty.py +21 -3
  49. anemoi/datasets/create/input/filter.py +60 -12
  50. anemoi/datasets/create/input/function.py +154 -37
  51. anemoi/datasets/create/input/join.py +86 -14
  52. anemoi/datasets/create/input/misc.py +67 -17
  53. anemoi/datasets/create/input/pipe.py +33 -6
  54. anemoi/datasets/create/input/repeated_dates.py +189 -41
  55. anemoi/datasets/create/input/result.py +202 -87
  56. anemoi/datasets/create/input/step.py +119 -22
  57. anemoi/datasets/create/input/template.py +100 -13
  58. anemoi/datasets/create/input/trace.py +62 -7
  59. anemoi/datasets/create/patch.py +52 -4
  60. anemoi/datasets/create/persistent.py +134 -17
  61. anemoi/datasets/create/size.py +15 -1
  62. anemoi/datasets/create/source.py +51 -0
  63. anemoi/datasets/create/sources/__init__.py +36 -0
  64. anemoi/datasets/create/{functions/sources → sources}/accumulations.py +296 -30
  65. anemoi/datasets/create/{functions/sources → sources}/constants.py +27 -2
  66. anemoi/datasets/create/{functions/sources → sources}/eccc_fstd.py +7 -3
  67. anemoi/datasets/create/sources/empty.py +37 -0
  68. anemoi/datasets/create/{functions/sources → sources}/forcings.py +25 -1
  69. anemoi/datasets/create/sources/grib.py +297 -0
  70. anemoi/datasets/create/{functions/sources → sources}/hindcasts.py +38 -4
  71. anemoi/datasets/create/sources/legacy.py +93 -0
  72. anemoi/datasets/create/{functions/sources → sources}/mars.py +168 -20
  73. anemoi/datasets/create/sources/netcdf.py +42 -0
  74. anemoi/datasets/create/sources/opendap.py +43 -0
  75. anemoi/datasets/create/{functions/sources/__init__.py → sources/patterns.py} +35 -4
  76. anemoi/datasets/create/sources/recentre.py +150 -0
  77. anemoi/datasets/create/{functions/sources → sources}/source.py +27 -5
  78. anemoi/datasets/create/{functions/sources → sources}/tendencies.py +64 -7
  79. anemoi/datasets/create/sources/xarray.py +92 -0
  80. anemoi/datasets/create/sources/xarray_kerchunk.py +36 -0
  81. anemoi/datasets/create/sources/xarray_support/README.md +1 -0
  82. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/__init__.py +109 -8
  83. anemoi/datasets/create/sources/xarray_support/coordinates.py +442 -0
  84. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/field.py +94 -16
  85. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/fieldlist.py +90 -25
  86. anemoi/datasets/create/sources/xarray_support/flavour.py +1036 -0
  87. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/grid.py +92 -31
  88. anemoi/datasets/create/sources/xarray_support/metadata.py +395 -0
  89. anemoi/datasets/create/sources/xarray_support/patch.py +91 -0
  90. anemoi/datasets/create/sources/xarray_support/time.py +391 -0
  91. anemoi/datasets/create/sources/xarray_support/variable.py +331 -0
  92. anemoi/datasets/create/sources/xarray_zarr.py +41 -0
  93. anemoi/datasets/create/{functions/sources → sources}/zenodo.py +34 -5
  94. anemoi/datasets/create/statistics/__init__.py +233 -44
  95. anemoi/datasets/create/statistics/summary.py +52 -6
  96. anemoi/datasets/create/testing.py +76 -0
  97. anemoi/datasets/create/{functions/filters/noop.py → typing.py} +6 -3
  98. anemoi/datasets/create/utils.py +97 -6
  99. anemoi/datasets/create/writer.py +26 -4
  100. anemoi/datasets/create/zarr.py +170 -23
  101. anemoi/datasets/data/__init__.py +51 -4
  102. anemoi/datasets/data/complement.py +191 -40
  103. anemoi/datasets/data/concat.py +141 -16
  104. anemoi/datasets/data/dataset.py +552 -61
  105. anemoi/datasets/data/debug.py +197 -26
  106. anemoi/datasets/data/ensemble.py +93 -8
  107. anemoi/datasets/data/fill_missing.py +165 -18
  108. anemoi/datasets/data/forwards.py +428 -56
  109. anemoi/datasets/data/grids.py +323 -97
  110. anemoi/datasets/data/indexing.py +112 -19
  111. anemoi/datasets/data/interpolate.py +92 -12
  112. anemoi/datasets/data/join.py +158 -19
  113. anemoi/datasets/data/masked.py +129 -15
  114. anemoi/datasets/data/merge.py +137 -23
  115. anemoi/datasets/data/misc.py +172 -16
  116. anemoi/datasets/data/missing.py +233 -29
  117. anemoi/datasets/data/rescale.py +111 -10
  118. anemoi/datasets/data/select.py +168 -26
  119. anemoi/datasets/data/statistics.py +67 -6
  120. anemoi/datasets/data/stores.py +149 -64
  121. anemoi/datasets/data/subset.py +159 -25
  122. anemoi/datasets/data/unchecked.py +168 -57
  123. anemoi/datasets/data/xy.py +168 -25
  124. anemoi/datasets/dates/__init__.py +191 -16
  125. anemoi/datasets/dates/groups.py +189 -47
  126. anemoi/datasets/grids.py +270 -31
  127. anemoi/datasets/testing.py +28 -1
  128. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/METADATA +9 -6
  129. anemoi_datasets-0.5.17.dist-info/RECORD +137 -0
  130. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/WHEEL +1 -1
  131. anemoi/datasets/create/functions/__init__.py +0 -66
  132. anemoi/datasets/create/functions/filters/__init__.py +0 -9
  133. anemoi/datasets/create/functions/filters/empty.py +0 -17
  134. anemoi/datasets/create/functions/filters/orog_to_z.py +0 -58
  135. anemoi/datasets/create/functions/filters/rename.py +0 -79
  136. anemoi/datasets/create/functions/filters/speeddir_to_uv.py +0 -78
  137. anemoi/datasets/create/functions/filters/uv_to_speeddir.py +0 -56
  138. anemoi/datasets/create/functions/sources/empty.py +0 -15
  139. anemoi/datasets/create/functions/sources/grib.py +0 -150
  140. anemoi/datasets/create/functions/sources/netcdf.py +0 -15
  141. anemoi/datasets/create/functions/sources/opendap.py +0 -15
  142. anemoi/datasets/create/functions/sources/recentre.py +0 -60
  143. anemoi/datasets/create/functions/sources/xarray/coordinates.py +0 -255
  144. anemoi/datasets/create/functions/sources/xarray/flavour.py +0 -472
  145. anemoi/datasets/create/functions/sources/xarray/metadata.py +0 -148
  146. anemoi/datasets/create/functions/sources/xarray/patch.py +0 -44
  147. anemoi/datasets/create/functions/sources/xarray/time.py +0 -177
  148. anemoi/datasets/create/functions/sources/xarray/variable.py +0 -188
  149. anemoi/datasets/create/functions/sources/xarray_kerchunk.py +0 -42
  150. anemoi/datasets/create/functions/sources/xarray_zarr.py +0 -15
  151. anemoi/datasets/utils/fields.py +0 -47
  152. anemoi_datasets-0.5.16.dist-info/RECORD +0 -129
  153. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/entry_points.txt +0 -0
  154. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info/licenses}/LICENSE +0 -0
  155. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/top_level.txt +0 -0
@@ -9,44 +9,59 @@
9
9
 
10
10
 
11
11
  from collections import defaultdict
12
+ from typing import Any
13
+ from typing import Dict
14
+ from typing import Optional
12
15
 
16
+ import earthkit.data as ekd
13
17
  import tqdm
18
+ from anemoi.transform.fields import new_field_from_numpy
19
+ from anemoi.transform.fields import new_fieldlist_from_list
14
20
  from anemoi.utils.humanize import plural
15
- from earthkit.data.indexing.fieldlist import FieldArray
16
21
  from earthkit.geo.rotate import rotate_vector
17
22
 
18
-
19
- class NewDataField:
20
- def __init__(self, field, data):
21
- self.field = field
22
- self.data = data
23
-
24
- def to_numpy(self, *args, **kwargs):
25
- return self.data
26
-
27
- def __getattr__(self, name):
28
- return getattr(self.field, name)
29
-
30
- def __repr__(self) -> str:
31
- return repr(self.field)
23
+ from .legacy import legacy_filter
32
24
 
33
25
 
26
+ @legacy_filter(__file__)
34
27
  def execute(
35
- context,
36
- input,
37
- x_wind,
38
- y_wind,
39
- source_projection=None,
40
- target_projection="+proj=longlat",
41
- ):
28
+ context: Any,
29
+ input: ekd.FieldList,
30
+ x_wind: str,
31
+ y_wind: str,
32
+ source_projection: Optional[str] = None,
33
+ target_projection: str = "+proj=longlat",
34
+ ) -> ekd.FieldList:
35
+ """Rotate wind components from one projection to another.
36
+
37
+ Parameters
38
+ ----------
39
+ context : Any
40
+ The context in which the function is executed.
41
+ input : ekd.FieldList
42
+ List of input fields.
43
+ x_wind : str
44
+ X wind component parameter.
45
+ y_wind : str
46
+ Y wind component parameter.
47
+ source_projection : Optional[str], optional
48
+ Source projection, by default None.
49
+ target_projection : str, optional
50
+ Target projection, by default "+proj=longlat".
51
+
52
+ Returns
53
+ -------
54
+ ekd.FieldList
55
+ Array of fields with rotated wind components.
56
+ """
42
57
  from pyproj import CRS
43
58
 
44
59
  context.trace("🔄", "Rotating winds (extracting winds from ", plural(len(input), "field"))
45
60
 
46
- result = FieldArray()
61
+ result = []
47
62
 
48
- wind_params = (x_wind, y_wind)
49
- wind_pairs = defaultdict(dict)
63
+ wind_params: tuple[str, str] = (x_wind, y_wind)
64
+ wind_pairs: Dict[tuple, Dict[str, Any]] = defaultdict(dict)
50
65
 
51
66
  for f in input:
52
67
  key = f.metadata(namespace="mars")
@@ -84,7 +99,7 @@ def execute(
84
99
  target_projection,
85
100
  )
86
101
 
87
- result.append(NewDataField(x, x_new))
88
- result.append(NewDataField(y, y_new))
102
+ result.append(new_field_from_numpy(x, x_new))
103
+ result.append(new_field_from_numpy(y, y_new))
89
104
 
90
- return result
105
+ return new_fieldlist_from_list(result)
@@ -9,19 +9,42 @@
9
9
 
10
10
 
11
11
  from collections import defaultdict
12
+ from typing import Any
13
+ from typing import Dict
12
14
 
13
- from earthkit.data.indexing.fieldlist import FieldArray
15
+ import earthkit.data as ekd
16
+ from anemoi.transform.fields import new_field_from_numpy
17
+ from anemoi.transform.fields import new_fieldlist_from_list
14
18
  from earthkit.meteo import thermo
15
19
 
16
- from .single_level_specific_humidity_to_relative_humidity import NewDataField
20
+ from .legacy import legacy_filter
17
21
 
18
22
 
19
- def execute(context, input, t, td, rh="d"):
20
- """Convert relative humidity on single levels to dewpoint"""
21
- result = FieldArray()
23
+ @legacy_filter(__file__)
24
+ def execute(context: Any, input: ekd.FieldList, t: str, td: str, rh: str = "d") -> ekd.FieldList:
25
+ """Convert dewpoint on single levels to relative humidity.
22
26
 
23
- params = (t, td)
24
- pairs = defaultdict(dict)
27
+ Parameters
28
+ ----------
29
+ context : Any
30
+ The context in which the function is executed.
31
+ input : List[Any]
32
+ List of input fields.
33
+ t : str
34
+ Temperature parameter.
35
+ td : str
36
+ Dewpoint parameter.
37
+ rh : str, optional
38
+ Relative humidity parameter. Defaults to "d".
39
+
40
+ Returns
41
+ -------
42
+ ekd.FieldList
43
+ Array of fields with relative humidity.
44
+ """
45
+ result = []
46
+ params: tuple[str, str] = (t, td)
47
+ pairs: Dict[tuple, Dict[str, Any]] = defaultdict(dict)
25
48
 
26
49
  # Gather all necessary fields
27
50
  for f in input:
@@ -50,6 +73,6 @@ def execute(context, input, t, td, rh="d"):
50
73
  td_values = values[td].to_numpy(flatten=True)
51
74
  # actual conversion from td --> rh
52
75
  rh_values = thermo.relative_humidity_from_dewpoint(t=t_values, td=td_values)
53
- result.append(NewDataField(values[td], rh_values, rh))
76
+ result.append(new_field_from_numpy(values[td], rh_values, param=rh))
54
77
 
55
- return result
78
+ return new_fieldlist_from_list(result)
@@ -7,23 +7,47 @@
7
7
  # granted to it by virtue of its status as an intergovernmental organisation
8
8
  # nor does it submit to any jurisdiction.
9
9
 
10
-
11
10
  from collections import defaultdict
11
+ from typing import Any
12
+ from typing import Dict
13
+ from typing import Tuple
12
14
 
15
+ import earthkit.data as ekd
16
+ from anemoi.transform.fields import new_field_from_numpy
17
+ from anemoi.transform.fields import new_fieldlist_from_list
13
18
  from earthkit.data.indexing.fieldlist import FieldArray
14
19
  from earthkit.meteo import thermo
15
20
 
16
- from .single_level_specific_humidity_to_relative_humidity import NewDataField
21
+ from .legacy import legacy_filter
17
22
 
18
23
  EPS = 1.0e-4
19
24
 
20
25
 
21
- def execute(context, input, t, rh, td="d"):
22
- """Convert relative humidity on single levels to dewpoint"""
23
- result = FieldArray()
26
+ @legacy_filter(__file__)
27
+ def execute(context: Any, input: ekd.FieldList, t: str, rh: str, td: str = "d") -> FieldArray:
28
+ """Convert relative humidity on single levels to dewpoint.
29
+
30
+ Parameters
31
+ ----------
32
+ context : Any
33
+ The context in which the function is executed.
34
+ input : List[Any]
35
+ List of input fields.
36
+ t : str
37
+ Temperature parameter.
38
+ rh : str
39
+ Relative humidity parameter.
40
+ td : str, optional
41
+ Dewpoint parameter. Defaults to "d".
24
42
 
25
- params = (t, rh)
26
- pairs = defaultdict(dict)
43
+ Returns
44
+ -------
45
+ FieldArray
46
+ Array of fields with dewpoint.
47
+ """
48
+ result = []
49
+ params: Tuple[str, str] = (t, rh)
50
+ pairs: Dict[Tuple[Any, ...], Dict[str, Any]] = defaultdict(dict)
27
51
 
28
52
  # Gather all necessary fields
29
53
  for f in input:
@@ -55,6 +79,6 @@ def execute(context, input, t, rh, td="d"):
55
79
  rh_values[rh_values == 0] = EPS
56
80
  # actual conversion from rh --> td
57
81
  td_values = thermo.dewpoint_from_relative_humidity(t=t_values, r=rh_values)
58
- result.append(NewDataField(values[rh], td_values, td))
82
+ result.append(new_field_from_numpy(values[rh], td_values, param=td))
59
83
 
60
- return result
84
+ return new_fieldlist_from_list(result)
@@ -8,18 +8,66 @@
8
8
  # nor does it submit to any jurisdiction.
9
9
 
10
10
 
11
+ from typing import Any
12
+ from typing import Dict
13
+ from typing import List
14
+
15
+ import earthkit.data as ekd
11
16
  import numpy as np
12
- from earthkit.data.indexing.fieldlist import FieldArray
17
+ from anemoi.transform.fields import new_field_from_numpy
18
+ from anemoi.transform.fields import new_fieldlist_from_list
13
19
  from earthkit.meteo import thermo
14
20
 
21
+ from .legacy import legacy_filter
15
22
  from .single_level_specific_humidity_to_relative_humidity import AutoDict
16
- from .single_level_specific_humidity_to_relative_humidity import NewDataField
17
23
  from .single_level_specific_humidity_to_relative_humidity import pressure_at_height_level
18
24
 
19
25
 
20
- def execute(context, input, height, t, rh, sp, new_name="2q", **kwargs):
21
- """Convert the single (height) level relative humidity to specific humidity"""
22
- result = FieldArray()
26
+ @legacy_filter(__file__)
27
+ def execute(
28
+ context: Any,
29
+ input: List[Any],
30
+ height: float,
31
+ t: str,
32
+ rh: str,
33
+ sp: str,
34
+ new_name: str = "2q",
35
+ **kwargs: Dict[str, Any],
36
+ ) -> ekd.FieldList:
37
+ """Convert the single (height) level relative humidity to specific humidity.
38
+
39
+ Parameters
40
+ ----------
41
+ context : Any
42
+ The context in which the function is executed.
43
+ input : List[Any]
44
+ List of input fields.
45
+ height : float
46
+ The height level.
47
+ t : str
48
+ Temperature parameter name.
49
+ rh : str
50
+ Relative humidity parameter name.
51
+ sp : str
52
+ Surface pressure parameter name.
53
+ new_name : str, optional
54
+ The new name for the specific humidity field, by default "2q".
55
+ **kwargs : Dict[str, Any]
56
+ Additional keyword arguments.
57
+
58
+ Returns
59
+ -------
60
+ ekd.FieldList
61
+ The resulting field list with specific humidity fields.
62
+
63
+ Raises
64
+ ------
65
+ KeyError
66
+ If mandatory keys are missing.
67
+ ValueError
68
+ If there are duplicate fields or missing fields.
69
+ """
70
+ result = []
23
71
 
24
72
  MANDATORY_KEYS = ["A", "B"]
25
73
  OPTIONAL_KEYS = ["t_ml", "q_ml"]
@@ -110,6 +158,6 @@ def execute(context, input, height, t, rh, sp, new_name="2q", **kwargs):
110
158
  p_sl = pressure_at_height_level(height, q_ml, t_ml, sp_sl, np.array(kwargs["A"]), np.array(kwargs["B"]))
111
159
  q_sl = thermo.specific_humidity_from_relative_humidity(t_sl, rh_sl, p_sl)
112
160
 
113
- result.append(NewDataField(values["sfc"][rh], q_sl, new_name))
161
+ result.append(new_field_from_numpy(values["sfc"][rh], q_sl, param=new_name))
114
162
 
115
- return result
163
+ return new_fieldlist_from_list(result)
@@ -8,46 +8,51 @@
8
8
  # nor does it submit to any jurisdiction.
9
9
 
10
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
11
18
  import numpy as np
12
- from earthkit.data.indexing.fieldlist import FieldArray
19
+ from anemoi.transform.fields import new_field_from_numpy
20
+ from anemoi.transform.fields import new_fieldlist_from_list
13
21
  from earthkit.meteo import constants
14
22
  from earthkit.meteo import thermo
23
+ from numpy.typing import NDArray
24
+
25
+ from .legacy import legacy_filter
15
26
 
16
27
 
17
28
  # Alternative proposed by Baudouin Raoult
18
29
  class AutoDict(dict):
19
- def __missing__(self, key):
20
- value = self[key] = type(self)()
21
- return value
22
-
30
+ """A dictionary that automatically creates nested dictionaries for missing keys."""
23
31
 
24
- class NewDataField:
25
- def __init__(self, field, data, new_name):
26
- self.field = field
27
- self.data = data
28
- self.new_name = new_name
32
+ def __missing__(self, key: Any) -> Any:
33
+ """Handle missing keys by creating nested dictionaries.
29
34
 
30
- def to_numpy(self, *args, **kwargs):
31
- return self.data
35
+ Parameters
36
+ ----------
37
+ key : Any
38
+ The missing key.
32
39
 
33
- def metadata(self, key=None, **kwargs):
34
- if key is None:
35
- return self.field.metadata(**kwargs)
36
-
37
- value = self.field.metadata(key, **kwargs)
38
- if key == "param":
39
- return self.new_name
40
+ Returns
41
+ -------
42
+ Any
43
+ A new nested dictionary.
44
+ """
45
+ value = self[key] = type(self)()
40
46
  return value
41
47
 
42
- def __getattr__(self, name):
43
- return getattr(self.field, name)
44
-
45
48
 
46
- def model_level_pressure(A, B, surface_pressure):
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]:
47
52
  """Calculates:
48
53
  - pressure at the model full- and half-levels
49
54
  - delta: depth of log(pressure) at full levels
50
- - alpha: alpha term #TODO: more descriptive information
55
+ - alpha: alpha term #TODO: more descriptive information.
51
56
 
52
57
  Parameters
53
58
  ----------
@@ -55,7 +60,7 @@ def model_level_pressure(A, B, surface_pressure):
55
60
  A-coefficients defining the model levels
56
61
  B : ndarray
57
62
  B-coefficients defining the model levels
58
- surface_pressure: number or ndarray
63
+ surface_pressure : number or ndarray
59
64
  surface pressure (Pa)
60
65
 
61
66
  Returns
@@ -114,9 +119,9 @@ def model_level_pressure(A, B, surface_pressure):
114
119
  return p_full_level, p_half_level, delta, alpha
115
120
 
116
121
 
117
- def calc_specific_gas_constant(q):
122
+ def calc_specific_gas_constant(q: Union[float, np.ndarray]) -> Union[float, NDArray[Any]]:
118
123
  """Calculates the specific gas constant of moist air
119
- (specific content of cloud particles and hydrometeors are neglected)
124
+ (specific content of cloud particles and hydrometeors are neglected).
120
125
 
121
126
  Parameters
122
127
  ----------
@@ -133,8 +138,8 @@ def calc_specific_gas_constant(q):
133
138
  return R
134
139
 
135
140
 
136
- def relative_geopotential_thickness(alpha, q, T):
137
- """Calculates the geopotential thickness w.r.t the surface on model full-levels
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.
138
143
 
139
144
  Parameters
140
145
  ----------
@@ -158,10 +163,12 @@ def relative_geopotential_thickness(alpha, q, T):
158
163
  return dphi
159
164
 
160
165
 
161
- def pressure_at_height_level(height, q, T, sp, A, B):
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]]:
162
169
  """Calculates the pressure at a height level given in meters above surface.
163
170
  This is done by finding the model level above and below the specified height
164
- and interpolating the pressure
171
+ and interpolating the pressure.
165
172
 
166
173
  Parameters
167
174
  ----------
@@ -225,9 +232,54 @@ def pressure_at_height_level(height, q, T, sp, A, B):
225
232
  return p_height
226
233
 
227
234
 
228
- def execute(context, input, height, t, q, sp, new_name="2r", **kwargs):
229
- """Convert the single (height) level specific humidity to relative humidity"""
230
- result = FieldArray()
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 = []
231
283
 
232
284
  MANDATORY_KEYS = ["A", "B"]
233
285
  OPTIONAL_KEYS = ["t_ml", "q_ml"]
@@ -323,12 +375,21 @@ def execute(context, input, height, t, q, sp, new_name="2r", **kwargs):
323
375
  td_sl = thermo.dewpoint_from_specific_humidity(q=q_sl, p=p_sl)
324
376
  rh_sl = thermo.relative_humidity_from_dewpoint(t=t_sl, td=td_sl)
325
377
 
326
- result.append(NewDataField(values["sfc"][q], rh_sl, new_name))
378
+ result.append(new_field_from_numpy(values["sfc"][q], rh_sl, param=new_name))
327
379
 
328
- return result
380
+ return new_fieldlist_from_list(result)
329
381
 
330
382
 
331
- def test():
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
+ """
332
393
  from earthkit.data import from_source
333
394
  from earthkit.data.readers.grib.index import GribFieldList
334
395
 
@@ -0,0 +1,95 @@
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)
@@ -7,39 +7,36 @@
7
7
  # granted to it by virtue of its status as an intergovernmental organisation
8
8
  # nor does it submit to any jurisdiction.
9
9
 
10
-
11
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
12
16
 
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
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
24
20
 
25
- def metadata(self, key=None, **kwargs):
26
- if key is None:
27
- return self.field.metadata(**kwargs)
21
+ from .legacy import legacy_filter
28
22
 
29
- value = self.field.metadata(key, **kwargs)
30
- if key == "param":
31
- return self.new_name
32
- return value
33
23
 
34
- def __getattr__(self, name):
35
- return getattr(self.field, name)
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.
36
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.
37
33
 
38
- def execute(context, input, params, output):
39
- """Computes the sum over a set of variables"""
40
- result = FieldArray()
34
+ Returns:
35
+ ekd.FieldList: The resulting FieldArray with summed fields.
36
+ """
37
+ result = []
41
38
 
42
- needed_fields = defaultdict(dict)
39
+ needed_fields: Dict[Tuple[Hashable, ...], Dict[str, ekd.Field]] = defaultdict(dict)
43
40
 
44
41
  for f in input:
45
42
  key = f.metadata(namespace="mars")
@@ -66,6 +63,6 @@ def execute(context, input, params, output):
66
63
  s = c
67
64
  else:
68
65
  s += c
69
- result.append(NewDataField(values[list(values.keys())[0]], s, output))
66
+ result.append(new_field_from_numpy(values[list(values.keys())[0]], s, param=output))
70
67
 
71
- return result
68
+ return new_fieldlist_from_list(result)