anemoi-datasets 0.5.16__py3-none-any.whl → 0.5.18__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 +558 -62
  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.18.dist-info}/METADATA +9 -6
  129. anemoi_datasets-0.5.18.dist-info/RECORD +137 -0
  130. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.18.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.18.dist-info}/entry_points.txt +0 -0
  154. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.18.dist-info/licenses}/LICENSE +0 -0
  155. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.18.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,50 @@
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 abc import ABC
11
+ from abc import abstractmethod
12
+ from typing import Any
13
+
14
+ import earthkit.data as ekd
15
+
16
+
17
+ class Filter(ABC):
18
+ """A base class for filters."""
19
+
20
+ def __init__(self, context: Any, *args: Any, **kwargs: Any) -> None:
21
+ """Initialise the filter.
22
+
23
+ Parameters
24
+ ----------
25
+ context : Any
26
+ The context in which the filter is created.
27
+ *args : tuple
28
+ Positional arguments.
29
+ **kwargs : dict
30
+ Keyword arguments.
31
+ """
32
+
33
+ self.context = context
34
+
35
+ @abstractmethod
36
+ def execute(self, data: ekd.FieldList) -> ekd.FieldList:
37
+ """Execute the filter.
38
+
39
+ Parameters
40
+ ----------
41
+ data : ekd.FieldList
42
+ The input data.
43
+
44
+ Returns
45
+ -------
46
+ ekd.FieldList
47
+ The output data.
48
+ """
49
+
50
+ pass
@@ -0,0 +1,33 @@
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
+
13
+ from anemoi.utils.registry import Registry
14
+
15
+ filter_registry = Registry(__name__)
16
+
17
+
18
+ def create_filter(context: Any, config: Any) -> Any:
19
+ """Create a filter based on the provided configuration.
20
+
21
+ Parameters
22
+ ----------
23
+ context : Any
24
+ The context in which the filter is created.
25
+ config : Any
26
+ The configuration for the filter.
27
+
28
+ Returns
29
+ -------
30
+ Any
31
+ The created filter.
32
+ """
33
+ return filter_registry.from_config(config, context)
@@ -0,0 +1,37 @@
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
+
13
+ import earthkit.data as ekd
14
+ from anemoi.transform.fields import new_empty_fieldlist
15
+
16
+ from .legacy import legacy_filter
17
+
18
+
19
+ @legacy_filter(__file__)
20
+ def execute(context: Any, input: ekd.FieldList, **kwargs: Any) -> ekd.FieldList:
21
+ """Create a pipeline that returns an empty result.
22
+
23
+ Parameters
24
+ ----------
25
+ context : Any
26
+ The context in which the function is executed.
27
+ input : List[Any]
28
+ List of input fields.
29
+ **kwargs : Any
30
+ Additional keyword arguments.
31
+
32
+ Returns
33
+ -------
34
+ Any
35
+ An empty result.
36
+ """
37
+ return new_empty_fieldlist()
@@ -0,0 +1,93 @@
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
+
11
+ import inspect
12
+ import logging
13
+ import os
14
+ from typing import Any
15
+ from typing import Callable
16
+
17
+ from ..filter import Filter
18
+ from . import filter_registry
19
+
20
+ LOG = logging.getLogger(__name__)
21
+
22
+
23
+ class LegacyFilter(Filter):
24
+ """A legacy filter class.
25
+
26
+ Parameters
27
+ ----------
28
+ context : Any
29
+ The context in which the filter is created.
30
+ *args : tuple
31
+ Positional arguments.
32
+ **kwargs : dict
33
+ Keyword arguments.
34
+ """
35
+
36
+ def __init__(self, context: Any, *args: Any, **kwargs: Any) -> None:
37
+ super().__init__(context, *args, **kwargs)
38
+ self.args = args
39
+ self.kwargs = kwargs
40
+
41
+
42
+ class legacy_filter:
43
+ """A decorator class for legacy filters.
44
+
45
+ Parameters
46
+ ----------
47
+ name : str
48
+ The name of the legacy filter.
49
+ """
50
+
51
+ def __init__(self, name: str) -> None:
52
+ name, _ = os.path.splitext(os.path.basename(name))
53
+ self.name = name
54
+
55
+ def __call__(self, execute: Callable) -> Callable:
56
+ """Call method to wrap the execute function.
57
+
58
+ Parameters
59
+ ----------
60
+ execute : Callable
61
+ The execute function to be wrapped.
62
+
63
+ Returns
64
+ -------
65
+ Callable
66
+ The wrapped execute function.
67
+ """
68
+ this = self
69
+ name = f"Legacy{self.name.title()}Filter"
70
+ source = ".".join([execute.__module__, execute.__name__])
71
+
72
+ def execute_wrapper(self, input) -> Any:
73
+ """Wrapper method to call the execute function."""
74
+ try:
75
+ return execute(self.context, input, *self.args, **self.kwargs)
76
+ except TypeError:
77
+ LOG.error(f"Error executing filter {this.name} from {source}")
78
+ LOG.error(f"Function signature is: {inspect.signature(execute)}")
79
+ LOG.error(f"Arguments are: {self.args=}, {self.kwargs=}")
80
+ raise
81
+
82
+ klass = type(
83
+ name,
84
+ (LegacyFilter,),
85
+ {
86
+ "execute": execute_wrapper,
87
+ "_source": source,
88
+ },
89
+ )
90
+
91
+ filter_registry.register(self.name)(klass)
92
+
93
+ return execute
@@ -0,0 +1,37 @@
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 typing import Any
11
+
12
+ import earthkit.data as ekd
13
+
14
+ from .legacy import legacy_filter
15
+
16
+
17
+ @legacy_filter(__file__)
18
+ def execute(context: Any, input: ekd.FieldList, *args: Any, **kwargs: Any) -> ekd.FieldList:
19
+ """No operation filter that returns the input as is.
20
+
21
+ Parameters
22
+ ----------
23
+ context : Any
24
+ The context in which the function is executed.
25
+ input : ekd.FieldList
26
+ List of input fields.
27
+ *args : Any
28
+ Additional arguments.
29
+ **kwargs : Any
30
+ Additional keyword arguments.
31
+
32
+ Returns
33
+ -------
34
+ List[Any]
35
+ The input list of fields.
36
+ """
37
+ return input
@@ -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
+ from collections import defaultdict
11
+ from typing import Any
12
+ from typing import Dict
13
+
14
+ import earthkit.data as ekd
15
+ from anemoi.transform.fields import new_field_from_numpy
16
+ from anemoi.transform.fields import new_fieldlist_from_list
17
+
18
+ from .legacy import legacy_filter
19
+
20
+
21
+ @legacy_filter(__file__)
22
+ def execute(context: Any, input: ekd.FieldList, orog: str, z: str = "z") -> ekd.FieldList:
23
+ """Convert orography [m] to z (geopotential height).
24
+
25
+ Parameters
26
+ ----------
27
+ context : Any
28
+ The context in which the function is executed.
29
+ input : FieldList
30
+ List of input fields.
31
+ orog : str
32
+ Orography parameter.
33
+ z : str, optional
34
+ Geopotential height parameter. Defaults to "z".
35
+
36
+ Returns
37
+ -------
38
+ FieldList
39
+ List of fields with geopotential height.
40
+ """
41
+ result = []
42
+ processed_fields: Dict[tuple, Dict[str, Any]] = 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(new_field_from_numpy(f, output, param=z))
55
+ else:
56
+ result.append(f)
57
+
58
+ return new_fieldlist_from_list(result)
@@ -7,21 +7,44 @@
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
 
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
21
+
17
22
 
23
+ @legacy_filter(__file__)
24
+ def execute(context: Any, input: ekd.FieldList, t: str, rh: str, q: str = "q") -> ekd.FieldList:
25
+ """Convert relative humidity on pressure levels to specific humidity.
18
26
 
19
- def execute(context, input, t, rh, q="q"):
20
- """Convert relative humidity on pressure levels to specific humidity"""
21
- result = FieldArray()
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
+ rh : str
36
+ Relative humidity parameter.
37
+ q : str, optional
38
+ Specific humidity parameter. Defaults to "q".
22
39
 
23
- params = (t, rh)
24
- pairs = defaultdict(dict)
40
+ Returns
41
+ -------
42
+ ekd.FieldList
43
+ Array of fields with specific humidity.
44
+ """
45
+ result = []
46
+ params: Tuple[str, str] = (t, rh)
47
+ pairs: Dict[Tuple[Any, ...], Dict[str, Any]] = defaultdict(dict)
25
48
 
26
49
  # Gather all necessary fields
27
50
  for f in input:
@@ -53,6 +76,6 @@ def execute(context, input, t, rh, q="q"):
53
76
 
54
77
  # actual conversion from rh --> q_v
55
78
  q_pl = thermo.specific_humidity_from_relative_humidity(t_pl, rh_pl, pressure)
56
- result.append(NewDataField(values[rh], q_pl, q))
79
+ result.append(new_field_from_numpy(values[rh], q_pl, param=q))
57
80
 
58
- return result
81
+ return new_fieldlist_from_list(result)
@@ -9,19 +9,43 @@
9
9
 
10
10
 
11
11
  from collections import defaultdict
12
+ from typing import Any
13
+ from typing import Dict
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
 
19
- def execute(context, input, t, q, rh="r"):
20
- """Convert specific humidity on pressure levels to relative humidity"""
21
- result = FieldArray()
24
+ @legacy_filter(__file__)
25
+ def execute(context: Any, input: ekd.FieldList, t: str, q: str, rh: str = "r") -> FieldArray:
26
+ """Convert specific humidity on pressure levels to relative humidity.
22
27
 
23
- params = (t, q)
24
- pairs = defaultdict(dict)
28
+ Parameters
29
+ ----------
30
+ context : Any
31
+ The context in which the function is executed.
32
+ input : List[Any]
33
+ List of input fields.
34
+ t : str
35
+ Temperature parameter.
36
+ q : str
37
+ Specific humidity parameter.
38
+ rh : str, optional
39
+ Relative humidity parameter. Defaults to "r".
40
+
41
+ Returns
42
+ -------
43
+ ekd.FieldList
44
+ Array of fields with relative humidity.
45
+ """
46
+ result = []
47
+ params: tuple[str, str] = (t, q)
48
+ pairs: Dict[tuple, Dict[str, Any]] = defaultdict(dict)
25
49
 
26
50
  # Gather all necessary fields
27
51
  for f in input:
@@ -53,6 +77,6 @@ def execute(context, input, t, q, rh="r"):
53
77
 
54
78
  # actual conversion from rh --> q_v
55
79
  rh_pl = thermo.relative_humidity_from_specific_humidity(t_pl, q_pl, pressure)
56
- result.append(NewDataField(values[q], rh_pl, rh))
80
+ result.append(new_field_from_numpy(values[q], rh_pl, param=rh))
57
81
 
58
- return result
82
+ return new_fieldlist_from_list(result)
@@ -0,0 +1,205 @@
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
+ import re
11
+ from typing import Any
12
+ from typing import Dict
13
+ from typing import Optional
14
+
15
+ import earthkit.data as ekd
16
+ from earthkit.data.indexing.fieldlist import FieldArray
17
+
18
+ from .legacy import legacy_filter
19
+
20
+
21
+ class RenamedFieldMapping:
22
+ """Rename a field based on the value of another field.
23
+
24
+ Parameters
25
+ ----------
26
+ field : Any
27
+ The field to be renamed.
28
+ what : str
29
+ The name of the field that will be used to rename the field.
30
+ renaming : dict of dict of str
31
+ A dictionary mapping the values of 'what' to the new names.
32
+ """
33
+
34
+ def __init__(self, field: Any, what: str, renaming: Dict[str, Dict[str, str]]) -> None:
35
+ """Initialize a RenamedFieldMapping instance.
36
+
37
+ Parameters
38
+ ----------
39
+ field : Any
40
+ The field to be renamed.
41
+ what : str
42
+ The name of the field that will be used to rename the field.
43
+ renaming : dict of dict of str
44
+ A dictionary mapping the values of 'what' to the new names.
45
+ """
46
+ self.field = field
47
+ self.what = what
48
+ self.renaming = {}
49
+ for k, v in renaming.items():
50
+ self.renaming[k] = {str(a): str(b) for a, b in v.items()}
51
+
52
+ def metadata(self, key: Optional[str] = None, **kwargs: Any) -> Any:
53
+ """Get metadata from the original field, with the option to rename the parameter.
54
+
55
+ Parameters
56
+ ----------
57
+ key : str, optional
58
+ The metadata key.
59
+ **kwargs : Any
60
+ Additional keyword arguments.
61
+
62
+ Returns
63
+ -------
64
+ Any
65
+ The metadata value.
66
+ """
67
+ if key is None:
68
+ return self.field.metadata(**kwargs)
69
+
70
+ value = self.field.metadata(key, **kwargs)
71
+ if key == self.what:
72
+ return self.renaming.get(self.what, {}).get(value, value)
73
+
74
+ return value
75
+
76
+ def __getattr__(self, name: str) -> Any:
77
+ """Get an attribute from the original field.
78
+
79
+ Parameters
80
+ ----------
81
+ name : str
82
+ The name of the attribute.
83
+
84
+ Returns
85
+ -------
86
+ Any
87
+ The attribute value.
88
+ """
89
+ return getattr(self.field, name)
90
+
91
+ def __repr__(self) -> str:
92
+ """Get the string representation of the original field.
93
+
94
+ Returns
95
+ -------
96
+ str
97
+ The string representation of the original field.
98
+ """
99
+ return repr(self.field)
100
+
101
+
102
+ class RenamedFieldFormat:
103
+ """Rename a field based on a format string.
104
+
105
+ Parameters
106
+ ----------
107
+ field : Any
108
+ The field to be renamed.
109
+ what : str
110
+ The name of the field that will be used to rename the field.
111
+ format : str
112
+ The format string for renaming.
113
+ """
114
+
115
+ def __init__(self, field: Any, what: str, format: str) -> None:
116
+ """Initialize a RenamedFieldFormat instance.
117
+
118
+ Parameters
119
+ ----------
120
+ field : Any
121
+ The field to be renamed.
122
+ what : str
123
+ The name of the field that will be used to rename the field.
124
+ format : str
125
+ The format string for renaming.
126
+ """
127
+ self.field = field
128
+ self.what = what
129
+ self.format = format
130
+ self.bits = re.findall(r"{(\w+)}", format)
131
+
132
+ def metadata(self, *args: Any, **kwargs: Any) -> Any:
133
+ """Get metadata from the original field, with the option to rename the parameter using a format string.
134
+
135
+ Parameters
136
+ ----------
137
+ *args : Any
138
+ Positional arguments.
139
+ **kwargs : Any
140
+ Additional keyword arguments.
141
+
142
+ Returns
143
+ -------
144
+ Any
145
+ The metadata value.
146
+ """
147
+ value = self.field.metadata(*args, **kwargs)
148
+ if args:
149
+ assert len(args) == 1
150
+ if args[0] == self.what:
151
+ bits = {b: self.field.metadata(b, **kwargs) for b in self.bits}
152
+ return self.format.format(**bits)
153
+ return value
154
+
155
+ def __getattr__(self, name: str) -> Any:
156
+ """Get an attribute from the original field.
157
+
158
+ Parameters
159
+ ----------
160
+ name : str
161
+ The name of the attribute.
162
+
163
+ Returns
164
+ -------
165
+ Any
166
+ The attribute value.
167
+ """
168
+ return getattr(self.field, name)
169
+
170
+ def __repr__(self) -> str:
171
+ """Get the string representation of the original field.
172
+
173
+ Returns
174
+ -------
175
+ str
176
+ The string representation of the original field.
177
+ """
178
+ return repr(self.field)
179
+
180
+
181
+ @legacy_filter(__file__)
182
+ def execute(context: Any, input: ekd.FieldList, what: str = "param", **kwargs: Any) -> ekd.FieldList:
183
+ """Rename fields based on the value of another field or a format string.
184
+
185
+ Parameters
186
+ ----------
187
+ context : Any
188
+ The context in which the function is executed.
189
+ input : List[Any]
190
+ List of input fields.
191
+ what : str, optional
192
+ The field to be used for renaming. Defaults to "param".
193
+ **kwargs : Any
194
+ Additional keyword arguments for renaming.
195
+
196
+ Returns
197
+ -------
198
+ ekd.FieldList
199
+ Array of renamed fields.
200
+ """
201
+
202
+ if what in kwargs and isinstance(kwargs[what], str):
203
+ return FieldArray([RenamedFieldFormat(fs, what, kwargs[what]) for fs in input])
204
+
205
+ return FieldArray([RenamedFieldMapping(fs, what, kwargs) for fs in input])