anemoi-utils 0.1.8__tar.gz → 0.1.9__tar.gz
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.
Potentially problematic release.
This version of anemoi-utils might be problematic. Click here for more details.
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/.gitignore +1 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/PKG-INFO +2 -1
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/pyproject.toml +4 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/src/anemoi/utils/_version.py +2 -2
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/src/anemoi/utils/dates.py +75 -2
- anemoi_utils-0.1.9/src/anemoi/utils/mars/__init__.py +76 -0
- anemoi_utils-0.1.9/src/anemoi/utils/mars/mars.yaml +5 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/src/anemoi_utils.egg-info/PKG-INFO +2 -1
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/src/anemoi_utils.egg-info/SOURCES.txt +3 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/src/anemoi_utils.egg-info/requires.txt +1 -0
- anemoi_utils-0.1.9/tests/test_dates.py +113 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/.github/workflows/python-publish.yml +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/.pre-commit-config.yaml +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/.readthedocs.yaml +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/LICENSE +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/README.md +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/docs/Makefile +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/docs/_static/logo.png +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/docs/_static/style.css +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/docs/_templates/.gitkeep +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/docs/conf.py +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/docs/index.rst +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/docs/installing.rst +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/docs/modules/checkpoints.rst +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/docs/modules/config.rst +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/docs/modules/dates.rst +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/docs/modules/grib.rst +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/docs/modules/humanize.rst +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/docs/modules/provenance.rst +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/docs/modules/text.rst +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/docs/requirements.txt +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/setup.cfg +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/src/anemoi/utils/__init__.py +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/src/anemoi/utils/caching.py +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/src/anemoi/utils/checkpoints.py +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/src/anemoi/utils/config.py +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/src/anemoi/utils/grib.py +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/src/anemoi/utils/humanize.py +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/src/anemoi/utils/provenance.py +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/src/anemoi/utils/text.py +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/src/anemoi_utils.egg-info/dependency_links.txt +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/src/anemoi_utils.egg-info/top_level.txt +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/tests/requirements.txt +0 -0
- {anemoi_utils-0.1.8 → anemoi_utils-0.1.9}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: anemoi-utils
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: A package to hold various functions to support training of ML models on ECMWF data.
|
|
5
5
|
Author-email: "European Centre for Medium-Range Weather Forecasts (ECMWF)" <software.support@ecmwf.int>
|
|
6
6
|
License: Apache License
|
|
@@ -223,6 +223,7 @@ Classifier: Operating System :: OS Independent
|
|
|
223
223
|
Requires-Python: >=3.9
|
|
224
224
|
License-File: LICENSE
|
|
225
225
|
Requires-Dist: tomli
|
|
226
|
+
Requires-Dist: pyyaml
|
|
226
227
|
Provides-Extra: provenance
|
|
227
228
|
Requires-Dist: GitPython; extra == "provenance"
|
|
228
229
|
Requires-Dist: nvsmi; extra == "provenance"
|
|
@@ -41,6 +41,7 @@ classifiers = [
|
|
|
41
41
|
|
|
42
42
|
dependencies = [
|
|
43
43
|
"tomli", # Only needed before 3.11
|
|
44
|
+
"pyyaml",
|
|
44
45
|
]
|
|
45
46
|
|
|
46
47
|
[project.optional-dependencies]
|
|
@@ -88,3 +89,6 @@ Issues = "https://github.com/ecmwf/anemoi-utils/issues"
|
|
|
88
89
|
|
|
89
90
|
[tool.setuptools_scm]
|
|
90
91
|
version_file = "src/anemoi/utils/_version.py"
|
|
92
|
+
|
|
93
|
+
[tool.setuptools.package-data]
|
|
94
|
+
"anemoi.utils.mars" = ["*.yaml"]
|
|
@@ -10,6 +10,16 @@ import calendar
|
|
|
10
10
|
import datetime
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
def normalise_frequency(frequency):
|
|
14
|
+
if isinstance(frequency, int):
|
|
15
|
+
return frequency
|
|
16
|
+
assert isinstance(frequency, str), (type(frequency), frequency)
|
|
17
|
+
|
|
18
|
+
unit = frequency[-1].lower()
|
|
19
|
+
v = int(frequency[:-1])
|
|
20
|
+
return {"h": v, "d": v * 24}[unit]
|
|
21
|
+
|
|
22
|
+
|
|
13
23
|
def no_time_zone(date):
|
|
14
24
|
"""Remove time zone information from a date.
|
|
15
25
|
|
|
@@ -27,6 +37,7 @@ def no_time_zone(date):
|
|
|
27
37
|
return date.replace(tzinfo=None)
|
|
28
38
|
|
|
29
39
|
|
|
40
|
+
# this function is use in anemoi-datasets
|
|
30
41
|
def as_datetime(date):
|
|
31
42
|
"""Convert a date to a datetime object, removing any time zone information.
|
|
32
43
|
|
|
@@ -162,11 +173,15 @@ class HindcastDatesTimes:
|
|
|
162
173
|
"""
|
|
163
174
|
|
|
164
175
|
self.reference_dates = reference_dates
|
|
165
|
-
|
|
176
|
+
|
|
177
|
+
if isinstance(years, list):
|
|
178
|
+
self.years = years
|
|
179
|
+
else:
|
|
180
|
+
self.years = range(1, years + 1)
|
|
166
181
|
|
|
167
182
|
def __iter__(self):
|
|
168
183
|
for reference_date in self.reference_dates:
|
|
169
|
-
for year in
|
|
184
|
+
for year in self.years:
|
|
170
185
|
if reference_date.month == 2 and reference_date.day == 29:
|
|
171
186
|
date = datetime.datetime(reference_date.year - year, 2, 28)
|
|
172
187
|
else:
|
|
@@ -246,3 +261,61 @@ class Autumn(DateTimes):
|
|
|
246
261
|
_description_
|
|
247
262
|
"""
|
|
248
263
|
super().__init__(datetime.datetime(year, 9, 1), datetime.datetime(year, 11, 30), **kwargs)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class ConcatDateTimes:
|
|
267
|
+
def __init__(self, *dates):
|
|
268
|
+
if len(dates) == 1 and isinstance(dates[0], list):
|
|
269
|
+
dates = dates[0]
|
|
270
|
+
|
|
271
|
+
self.dates = dates
|
|
272
|
+
|
|
273
|
+
def __iter__(self):
|
|
274
|
+
for date in self.dates:
|
|
275
|
+
yield from date
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class EnumDateTimes:
|
|
279
|
+
def __init__(self, dates):
|
|
280
|
+
self.dates = dates
|
|
281
|
+
|
|
282
|
+
def __iter__(self):
|
|
283
|
+
for date in self.dates:
|
|
284
|
+
yield as_datetime(date)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def datetimes_factory(*args, **kwargs):
|
|
288
|
+
if args and kwargs:
|
|
289
|
+
raise ValueError("Cannot provide both args and kwargs for a list of dates")
|
|
290
|
+
|
|
291
|
+
if not args and not kwargs:
|
|
292
|
+
raise ValueError("No dates provided")
|
|
293
|
+
|
|
294
|
+
if kwargs:
|
|
295
|
+
name = kwargs.get("name")
|
|
296
|
+
|
|
297
|
+
if name == "hindcast":
|
|
298
|
+
reference_dates = kwargs["reference_dates"]
|
|
299
|
+
reference_dates = datetimes_factory(reference_dates)
|
|
300
|
+
years = kwargs["years"]
|
|
301
|
+
return HindcastDatesTimes(reference_dates=reference_dates, years=years)
|
|
302
|
+
|
|
303
|
+
kwargs = kwargs.copy()
|
|
304
|
+
if "frequency" in kwargs:
|
|
305
|
+
freq = kwargs.pop("frequency")
|
|
306
|
+
kwargs["increment"] = normalise_frequency(freq)
|
|
307
|
+
return DateTimes(**kwargs)
|
|
308
|
+
|
|
309
|
+
if not any((isinstance(x, dict) or isinstance(x, list)) for x in args):
|
|
310
|
+
return EnumDateTimes(args)
|
|
311
|
+
|
|
312
|
+
if len(args) == 1:
|
|
313
|
+
a = args[0]
|
|
314
|
+
|
|
315
|
+
if isinstance(a, dict):
|
|
316
|
+
return datetimes_factory(**a)
|
|
317
|
+
|
|
318
|
+
if isinstance(a, list):
|
|
319
|
+
return datetimes_factory(*a)
|
|
320
|
+
|
|
321
|
+
return ConcatDateTimes(*[datetimes_factory(a) for a in args])
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# (C) Copyright 2024 European Centre for Medium-Range Weather Forecasts.
|
|
2
|
+
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
3
|
+
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
4
|
+
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
5
|
+
# granted to it by virtue of its status as an intergovernmental organisation
|
|
6
|
+
# nor does it submit to any jurisdiction.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
"""Utilities for working with Mars requests.
|
|
10
|
+
|
|
11
|
+
Has some konwledge of how certain streams are organised in Mars.
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import datetime
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
|
|
19
|
+
import yaml
|
|
20
|
+
|
|
21
|
+
LOG = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
DEFAULT_MARS_LABELLING = {
|
|
24
|
+
"class": "od",
|
|
25
|
+
"type": "an",
|
|
26
|
+
"stream": "oper",
|
|
27
|
+
"expver": "0001",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _expand_mars_labelling(request):
|
|
32
|
+
"""Expand the request with the default Mars labelling.
|
|
33
|
+
|
|
34
|
+
The default Mars labelling is:
|
|
35
|
+
|
|
36
|
+
{'class': 'od',
|
|
37
|
+
'type': 'an',
|
|
38
|
+
'stream': 'oper',
|
|
39
|
+
'expver': '0001'}
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
result = DEFAULT_MARS_LABELLING.copy()
|
|
43
|
+
result.update(request)
|
|
44
|
+
return result
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
STREAMS = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _lookup_mars_stream(request):
|
|
51
|
+
global STREAMS
|
|
52
|
+
|
|
53
|
+
if STREAMS is None:
|
|
54
|
+
|
|
55
|
+
with open(os.path.join(os.path.dirname(__file__), "mars.yaml")) as f:
|
|
56
|
+
STREAMS = yaml.safe_load(f)
|
|
57
|
+
|
|
58
|
+
request = _expand_mars_labelling(request)
|
|
59
|
+
for s in STREAMS:
|
|
60
|
+
match = s["match"]
|
|
61
|
+
if all(request.get(k) == v for k, v in match.items()):
|
|
62
|
+
return s["info"]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def recenter(date, center, members):
|
|
66
|
+
|
|
67
|
+
center = _lookup_mars_stream(center)
|
|
68
|
+
members = _lookup_mars_stream(members)
|
|
69
|
+
|
|
70
|
+
return (center, members)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
date = datetime.datetime(2024, 5, 9, 0)
|
|
75
|
+
|
|
76
|
+
print(recenter(date, {"type": "an"}, {"stream": "elda"}))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: anemoi-utils
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: A package to hold various functions to support training of ML models on ECMWF data.
|
|
5
5
|
Author-email: "European Centre for Medium-Range Weather Forecasts (ECMWF)" <software.support@ecmwf.int>
|
|
6
6
|
License: Apache License
|
|
@@ -223,6 +223,7 @@ Classifier: Operating System :: OS Independent
|
|
|
223
223
|
Requires-Python: >=3.9
|
|
224
224
|
License-File: LICENSE
|
|
225
225
|
Requires-Dist: tomli
|
|
226
|
+
Requires-Dist: pyyaml
|
|
226
227
|
Provides-Extra: provenance
|
|
227
228
|
Requires-Dist: GitPython; extra == "provenance"
|
|
228
229
|
Requires-Dist: nvsmi; extra == "provenance"
|
|
@@ -30,10 +30,13 @@ src/anemoi/utils/grib.py
|
|
|
30
30
|
src/anemoi/utils/humanize.py
|
|
31
31
|
src/anemoi/utils/provenance.py
|
|
32
32
|
src/anemoi/utils/text.py
|
|
33
|
+
src/anemoi/utils/mars/__init__.py
|
|
34
|
+
src/anemoi/utils/mars/mars.yaml
|
|
33
35
|
src/anemoi_utils.egg-info/PKG-INFO
|
|
34
36
|
src/anemoi_utils.egg-info/SOURCES.txt
|
|
35
37
|
src/anemoi_utils.egg-info/dependency_links.txt
|
|
36
38
|
src/anemoi_utils.egg-info/requires.txt
|
|
37
39
|
src/anemoi_utils.egg-info/top_level.txt
|
|
38
40
|
tests/requirements.txt
|
|
41
|
+
tests/test_dates.py
|
|
39
42
|
tests/test_utils.py
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from textwrap import dedent
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from anemoi.utils.dates import datetimes_factory
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _(txt):
|
|
10
|
+
txt = dedent(txt)
|
|
11
|
+
config = yaml.safe_load(txt)
|
|
12
|
+
return datetimes_factory(config)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_date_1():
|
|
16
|
+
d = _(
|
|
17
|
+
"""
|
|
18
|
+
- 2023-01-01
|
|
19
|
+
- 2023-01-02
|
|
20
|
+
- 2023-01-03
|
|
21
|
+
"""
|
|
22
|
+
)
|
|
23
|
+
assert len(list(d)) == 3
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_date_2():
|
|
27
|
+
d = _(
|
|
28
|
+
"""
|
|
29
|
+
start: 2023-01-01
|
|
30
|
+
end: 2023-01-07
|
|
31
|
+
frequency: 12
|
|
32
|
+
day_of_week: [monday, friday]
|
|
33
|
+
"""
|
|
34
|
+
)
|
|
35
|
+
assert len(list(d)) == 4
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_date_3():
|
|
39
|
+
d = _(
|
|
40
|
+
"""
|
|
41
|
+
- start: 2023-01-01
|
|
42
|
+
end: 2023-01-03
|
|
43
|
+
frequency: 24
|
|
44
|
+
- start: 2024-01-01T06:00:00
|
|
45
|
+
end: 2024-01-02T18:00:00
|
|
46
|
+
frequency: 6h
|
|
47
|
+
"""
|
|
48
|
+
)
|
|
49
|
+
assert datetime.datetime(2023, 1, 1, 0) in d
|
|
50
|
+
assert datetime.datetime(2023, 1, 2, 0) in d
|
|
51
|
+
assert datetime.datetime(2023, 1, 3, 0) in d
|
|
52
|
+
assert datetime.datetime(2024, 1, 1, 6) in d
|
|
53
|
+
assert datetime.datetime(2024, 1, 1, 12) in d
|
|
54
|
+
assert datetime.datetime(2024, 1, 1, 18) in d
|
|
55
|
+
assert datetime.datetime(2024, 1, 2, 0) in d
|
|
56
|
+
assert datetime.datetime(2024, 1, 2, 6) in d
|
|
57
|
+
assert datetime.datetime(2024, 1, 2, 12) in d
|
|
58
|
+
assert datetime.datetime(2024, 1, 2, 18) in d
|
|
59
|
+
assert len(list(d)) == 10
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_date_hindcast_1():
|
|
63
|
+
d = _(
|
|
64
|
+
"""
|
|
65
|
+
- name: hindcast
|
|
66
|
+
reference_dates:
|
|
67
|
+
start: 2023-01-01
|
|
68
|
+
end: 2023-01-03
|
|
69
|
+
frequency: 24
|
|
70
|
+
years: 20
|
|
71
|
+
"""
|
|
72
|
+
)
|
|
73
|
+
assert len(list(d)) == 60
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_date_hindcast_2():
|
|
77
|
+
d = _(
|
|
78
|
+
"""
|
|
79
|
+
- name: hindcast
|
|
80
|
+
reference_dates:
|
|
81
|
+
start: 2023-01-01
|
|
82
|
+
end: 2023-01-03
|
|
83
|
+
frequency: 24
|
|
84
|
+
years: [2018, 2019, 2020, 2021]
|
|
85
|
+
"""
|
|
86
|
+
)
|
|
87
|
+
assert len(list(d)) == 12
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_date_hindcast_3():
|
|
91
|
+
d = _(
|
|
92
|
+
"""
|
|
93
|
+
- name: hindcast
|
|
94
|
+
reference_dates:
|
|
95
|
+
start: 2022-12-25 00:00:00
|
|
96
|
+
end: 2022-12-31 12:00:00
|
|
97
|
+
frequency: 12h
|
|
98
|
+
day_of_week: tuesday
|
|
99
|
+
years: [2018, 2019, 2020, 2021]
|
|
100
|
+
"""
|
|
101
|
+
)
|
|
102
|
+
print(list(d))
|
|
103
|
+
assert len(list(d)) == 8
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
test_functions = [
|
|
108
|
+
obj for name, obj in globals().items() if name.startswith("test_") and isinstance(obj, type(lambda: 0))
|
|
109
|
+
]
|
|
110
|
+
for test_func in test_functions:
|
|
111
|
+
print(f"Running test: {test_func.__name__}")
|
|
112
|
+
test_func()
|
|
113
|
+
print("All tests passed!")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|