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.
- anemoi/datasets/__init__.py +4 -1
- anemoi/datasets/__main__.py +12 -2
- anemoi/datasets/_version.py +9 -4
- anemoi/datasets/commands/cleanup.py +17 -2
- anemoi/datasets/commands/compare.py +18 -2
- anemoi/datasets/commands/copy.py +196 -14
- anemoi/datasets/commands/create.py +50 -7
- anemoi/datasets/commands/finalise-additions.py +17 -2
- anemoi/datasets/commands/finalise.py +17 -2
- anemoi/datasets/commands/init-additions.py +17 -2
- anemoi/datasets/commands/init.py +16 -2
- anemoi/datasets/commands/inspect.py +283 -62
- anemoi/datasets/commands/load-additions.py +16 -2
- anemoi/datasets/commands/load.py +16 -2
- anemoi/datasets/commands/patch.py +17 -2
- anemoi/datasets/commands/publish.py +17 -2
- anemoi/datasets/commands/scan.py +31 -3
- anemoi/datasets/compute/recentre.py +47 -11
- anemoi/datasets/create/__init__.py +612 -85
- anemoi/datasets/create/check.py +142 -20
- anemoi/datasets/create/chunks.py +64 -4
- anemoi/datasets/create/config.py +185 -21
- anemoi/datasets/create/filter.py +50 -0
- anemoi/datasets/create/filters/__init__.py +33 -0
- anemoi/datasets/create/filters/empty.py +37 -0
- anemoi/datasets/create/filters/legacy.py +93 -0
- anemoi/datasets/create/filters/noop.py +37 -0
- anemoi/datasets/create/filters/orog_to_z.py +58 -0
- anemoi/datasets/create/{functions/filters → filters}/pressure_level_relative_humidity_to_specific_humidity.py +33 -10
- anemoi/datasets/create/{functions/filters → filters}/pressure_level_specific_humidity_to_relative_humidity.py +32 -8
- anemoi/datasets/create/filters/rename.py +205 -0
- anemoi/datasets/create/{functions/filters → filters}/rotate_winds.py +43 -28
- anemoi/datasets/create/{functions/filters → filters}/single_level_dewpoint_to_relative_humidity.py +32 -9
- anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_dewpoint.py +33 -9
- anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_specific_humidity.py +55 -7
- anemoi/datasets/create/{functions/filters → filters}/single_level_specific_humidity_to_relative_humidity.py +98 -37
- anemoi/datasets/create/filters/speeddir_to_uv.py +95 -0
- anemoi/datasets/create/{functions/filters → filters}/sum.py +24 -27
- anemoi/datasets/create/filters/transform.py +53 -0
- anemoi/datasets/create/{functions/filters → filters}/unrotate_winds.py +27 -18
- anemoi/datasets/create/filters/uv_to_speeddir.py +94 -0
- anemoi/datasets/create/{functions/filters → filters}/wz_to_w.py +51 -33
- anemoi/datasets/create/input/__init__.py +76 -5
- anemoi/datasets/create/input/action.py +149 -13
- anemoi/datasets/create/input/concat.py +81 -10
- anemoi/datasets/create/input/context.py +39 -4
- anemoi/datasets/create/input/data_sources.py +72 -6
- anemoi/datasets/create/input/empty.py +21 -3
- anemoi/datasets/create/input/filter.py +60 -12
- anemoi/datasets/create/input/function.py +154 -37
- anemoi/datasets/create/input/join.py +86 -14
- anemoi/datasets/create/input/misc.py +67 -17
- anemoi/datasets/create/input/pipe.py +33 -6
- anemoi/datasets/create/input/repeated_dates.py +189 -41
- anemoi/datasets/create/input/result.py +202 -87
- anemoi/datasets/create/input/step.py +119 -22
- anemoi/datasets/create/input/template.py +100 -13
- anemoi/datasets/create/input/trace.py +62 -7
- anemoi/datasets/create/patch.py +52 -4
- anemoi/datasets/create/persistent.py +134 -17
- anemoi/datasets/create/size.py +15 -1
- anemoi/datasets/create/source.py +51 -0
- anemoi/datasets/create/sources/__init__.py +36 -0
- anemoi/datasets/create/{functions/sources → sources}/accumulations.py +296 -30
- anemoi/datasets/create/{functions/sources → sources}/constants.py +27 -2
- anemoi/datasets/create/{functions/sources → sources}/eccc_fstd.py +7 -3
- anemoi/datasets/create/sources/empty.py +37 -0
- anemoi/datasets/create/{functions/sources → sources}/forcings.py +25 -1
- anemoi/datasets/create/sources/grib.py +297 -0
- anemoi/datasets/create/{functions/sources → sources}/hindcasts.py +38 -4
- anemoi/datasets/create/sources/legacy.py +93 -0
- anemoi/datasets/create/{functions/sources → sources}/mars.py +168 -20
- anemoi/datasets/create/sources/netcdf.py +42 -0
- anemoi/datasets/create/sources/opendap.py +43 -0
- anemoi/datasets/create/{functions/sources/__init__.py → sources/patterns.py} +35 -4
- anemoi/datasets/create/sources/recentre.py +150 -0
- anemoi/datasets/create/{functions/sources → sources}/source.py +27 -5
- anemoi/datasets/create/{functions/sources → sources}/tendencies.py +64 -7
- anemoi/datasets/create/sources/xarray.py +92 -0
- anemoi/datasets/create/sources/xarray_kerchunk.py +36 -0
- anemoi/datasets/create/sources/xarray_support/README.md +1 -0
- anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/__init__.py +109 -8
- anemoi/datasets/create/sources/xarray_support/coordinates.py +442 -0
- anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/field.py +94 -16
- anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/fieldlist.py +90 -25
- anemoi/datasets/create/sources/xarray_support/flavour.py +1036 -0
- anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/grid.py +92 -31
- anemoi/datasets/create/sources/xarray_support/metadata.py +395 -0
- anemoi/datasets/create/sources/xarray_support/patch.py +91 -0
- anemoi/datasets/create/sources/xarray_support/time.py +391 -0
- anemoi/datasets/create/sources/xarray_support/variable.py +331 -0
- anemoi/datasets/create/sources/xarray_zarr.py +41 -0
- anemoi/datasets/create/{functions/sources → sources}/zenodo.py +34 -5
- anemoi/datasets/create/statistics/__init__.py +233 -44
- anemoi/datasets/create/statistics/summary.py +52 -6
- anemoi/datasets/create/testing.py +76 -0
- anemoi/datasets/create/{functions/filters/noop.py → typing.py} +6 -3
- anemoi/datasets/create/utils.py +97 -6
- anemoi/datasets/create/writer.py +26 -4
- anemoi/datasets/create/zarr.py +170 -23
- anemoi/datasets/data/__init__.py +51 -4
- anemoi/datasets/data/complement.py +191 -40
- anemoi/datasets/data/concat.py +141 -16
- anemoi/datasets/data/dataset.py +552 -61
- anemoi/datasets/data/debug.py +197 -26
- anemoi/datasets/data/ensemble.py +93 -8
- anemoi/datasets/data/fill_missing.py +165 -18
- anemoi/datasets/data/forwards.py +428 -56
- anemoi/datasets/data/grids.py +323 -97
- anemoi/datasets/data/indexing.py +112 -19
- anemoi/datasets/data/interpolate.py +92 -12
- anemoi/datasets/data/join.py +158 -19
- anemoi/datasets/data/masked.py +129 -15
- anemoi/datasets/data/merge.py +137 -23
- anemoi/datasets/data/misc.py +172 -16
- anemoi/datasets/data/missing.py +233 -29
- anemoi/datasets/data/rescale.py +111 -10
- anemoi/datasets/data/select.py +168 -26
- anemoi/datasets/data/statistics.py +67 -6
- anemoi/datasets/data/stores.py +149 -64
- anemoi/datasets/data/subset.py +159 -25
- anemoi/datasets/data/unchecked.py +168 -57
- anemoi/datasets/data/xy.py +168 -25
- anemoi/datasets/dates/__init__.py +191 -16
- anemoi/datasets/dates/groups.py +189 -47
- anemoi/datasets/grids.py +270 -31
- anemoi/datasets/testing.py +28 -1
- {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/METADATA +9 -6
- anemoi_datasets-0.5.17.dist-info/RECORD +137 -0
- {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/WHEEL +1 -1
- anemoi/datasets/create/functions/__init__.py +0 -66
- anemoi/datasets/create/functions/filters/__init__.py +0 -9
- anemoi/datasets/create/functions/filters/empty.py +0 -17
- anemoi/datasets/create/functions/filters/orog_to_z.py +0 -58
- anemoi/datasets/create/functions/filters/rename.py +0 -79
- anemoi/datasets/create/functions/filters/speeddir_to_uv.py +0 -78
- anemoi/datasets/create/functions/filters/uv_to_speeddir.py +0 -56
- anemoi/datasets/create/functions/sources/empty.py +0 -15
- anemoi/datasets/create/functions/sources/grib.py +0 -150
- anemoi/datasets/create/functions/sources/netcdf.py +0 -15
- anemoi/datasets/create/functions/sources/opendap.py +0 -15
- anemoi/datasets/create/functions/sources/recentre.py +0 -60
- anemoi/datasets/create/functions/sources/xarray/coordinates.py +0 -255
- anemoi/datasets/create/functions/sources/xarray/flavour.py +0 -472
- anemoi/datasets/create/functions/sources/xarray/metadata.py +0 -148
- anemoi/datasets/create/functions/sources/xarray/patch.py +0 -44
- anemoi/datasets/create/functions/sources/xarray/time.py +0 -177
- anemoi/datasets/create/functions/sources/xarray/variable.py +0 -188
- anemoi/datasets/create/functions/sources/xarray_kerchunk.py +0 -42
- anemoi/datasets/create/functions/sources/xarray_zarr.py +0 -15
- anemoi/datasets/utils/fields.py +0 -47
- anemoi_datasets-0.5.16.dist-info/RECORD +0 -129
- {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/entry_points.txt +0 -0
- {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info/licenses}/LICENSE +0 -0
- {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 =
|
|
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(
|
|
88
|
-
result.append(
|
|
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)
|
anemoi/datasets/create/{functions/filters → filters}/single_level_dewpoint_to_relative_humidity.py
RENAMED
|
@@ -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
|
-
|
|
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 .
|
|
20
|
+
from .legacy import legacy_filter
|
|
17
21
|
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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(
|
|
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)
|
anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_dewpoint.py
RENAMED
|
@@ -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 .
|
|
21
|
+
from .legacy import legacy_filter
|
|
17
22
|
|
|
18
23
|
EPS = 1.0e-4
|
|
19
24
|
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
20
|
-
value = self[key] = type(self)()
|
|
21
|
-
return value
|
|
22
|
-
|
|
30
|
+
"""A dictionary that automatically creates nested dictionaries for missing keys."""
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
31
|
-
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
key : Any
|
|
38
|
+
The missing key.
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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(
|
|
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)
|