anemoi-datasets 0.5.27__py3-none-any.whl → 0.5.29__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 (72) hide show
  1. anemoi/datasets/_version.py +2 -2
  2. anemoi/datasets/commands/recipe/__init__.py +93 -0
  3. anemoi/datasets/commands/recipe/format.py +55 -0
  4. anemoi/datasets/commands/recipe/migrate.py +555 -0
  5. anemoi/datasets/create/__init__.py +46 -13
  6. anemoi/datasets/create/config.py +52 -53
  7. anemoi/datasets/create/input/__init__.py +43 -63
  8. anemoi/datasets/create/input/action.py +296 -236
  9. anemoi/datasets/create/input/context/__init__.py +71 -0
  10. anemoi/datasets/create/input/context/field.py +54 -0
  11. anemoi/datasets/create/input/data_sources.py +2 -1
  12. anemoi/datasets/create/input/misc.py +0 -71
  13. anemoi/datasets/create/input/repeated_dates.py +0 -114
  14. anemoi/datasets/create/input/result/__init__.py +17 -0
  15. anemoi/datasets/create/input/{result.py → result/field.py} +10 -92
  16. anemoi/datasets/create/sources/accumulate.py +517 -0
  17. anemoi/datasets/create/sources/accumulate_utils/__init__.py +8 -0
  18. anemoi/datasets/create/sources/accumulate_utils/covering_intervals.py +221 -0
  19. anemoi/datasets/create/sources/accumulate_utils/field_to_interval.py +149 -0
  20. anemoi/datasets/create/sources/accumulate_utils/interval_generators.py +321 -0
  21. anemoi/datasets/create/sources/anemoi_dataset.py +46 -42
  22. anemoi/datasets/create/sources/constants.py +39 -38
  23. anemoi/datasets/create/sources/empty.py +26 -22
  24. anemoi/datasets/create/sources/forcings.py +29 -28
  25. anemoi/datasets/create/sources/grib.py +92 -72
  26. anemoi/datasets/create/sources/grib_index.py +102 -54
  27. anemoi/datasets/create/sources/hindcasts.py +56 -55
  28. anemoi/datasets/create/sources/legacy.py +10 -62
  29. anemoi/datasets/create/sources/mars.py +159 -154
  30. anemoi/datasets/create/sources/netcdf.py +28 -24
  31. anemoi/datasets/create/sources/opendap.py +28 -24
  32. anemoi/datasets/create/sources/recentre.py +42 -41
  33. anemoi/datasets/create/sources/repeated_dates.py +44 -0
  34. anemoi/datasets/create/sources/source.py +26 -48
  35. anemoi/datasets/create/sources/xarray_support/__init__.py +30 -24
  36. anemoi/datasets/create/sources/xarray_support/coordinates.py +1 -4
  37. anemoi/datasets/create/sources/xarray_support/field.py +4 -4
  38. anemoi/datasets/create/sources/xarray_support/flavour.py +2 -2
  39. anemoi/datasets/create/sources/xarray_support/patch.py +178 -5
  40. anemoi/datasets/create/sources/xarray_zarr.py +28 -24
  41. anemoi/datasets/create/sources/zenodo.py +43 -39
  42. anemoi/datasets/create/utils.py +0 -42
  43. anemoi/datasets/data/complement.py +26 -17
  44. anemoi/datasets/data/dataset.py +12 -0
  45. anemoi/datasets/data/grids.py +0 -152
  46. anemoi/datasets/data/masked.py +74 -13
  47. anemoi/datasets/data/missing.py +5 -0
  48. anemoi/datasets/data/rolling_average.py +141 -0
  49. anemoi/datasets/data/stores.py +7 -9
  50. anemoi/datasets/dates/__init__.py +2 -0
  51. anemoi/datasets/dumper.py +76 -0
  52. anemoi/datasets/grids.py +1 -178
  53. anemoi/datasets/schemas/recipe.json +131 -0
  54. {anemoi_datasets-0.5.27.dist-info → anemoi_datasets-0.5.29.dist-info}/METADATA +9 -6
  55. {anemoi_datasets-0.5.27.dist-info → anemoi_datasets-0.5.29.dist-info}/RECORD +59 -57
  56. {anemoi_datasets-0.5.27.dist-info → anemoi_datasets-0.5.29.dist-info}/WHEEL +1 -1
  57. anemoi/datasets/create/filter.py +0 -47
  58. anemoi/datasets/create/input/concat.py +0 -161
  59. anemoi/datasets/create/input/context.py +0 -86
  60. anemoi/datasets/create/input/empty.py +0 -53
  61. anemoi/datasets/create/input/filter.py +0 -117
  62. anemoi/datasets/create/input/function.py +0 -232
  63. anemoi/datasets/create/input/join.py +0 -129
  64. anemoi/datasets/create/input/pipe.py +0 -66
  65. anemoi/datasets/create/input/step.py +0 -173
  66. anemoi/datasets/create/input/template.py +0 -161
  67. anemoi/datasets/create/sources/accumulations.py +0 -1062
  68. anemoi/datasets/create/sources/accumulations2.py +0 -647
  69. anemoi/datasets/create/sources/tendencies.py +0 -198
  70. {anemoi_datasets-0.5.27.dist-info → anemoi_datasets-0.5.29.dist-info}/entry_points.txt +0 -0
  71. {anemoi_datasets-0.5.27.dist-info → anemoi_datasets-0.5.29.dist-info}/licenses/LICENSE +0 -0
  72. {anemoi_datasets-0.5.27.dist-info → anemoi_datasets-0.5.29.dist-info}/top_level.txt +0 -0
@@ -1,1062 +0,0 @@
1
- # (C) Copyright 2024 Anemoi contributors.
2
- #
3
- # This software is licensed under the terms of the Apache Licence Version 2.0
4
- # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5
- #
6
- # In applying this licence, ECMWF does not waive the privileges and immunities
7
- # granted to it by virtue of its status as an intergovernmental organisation
8
- # nor does it submit to any jurisdiction.
9
-
10
- import datetime
11
- import logging
12
- import warnings
13
- from collections.abc import Generator
14
- from copy import deepcopy
15
- from typing import Any
16
-
17
- import earthkit.data as ekd
18
- import numpy as np
19
- from earthkit.data.core.temporary import temp_file
20
- from earthkit.data.readers.grib.output import new_grib_output
21
- from numpy.typing import NDArray
22
-
23
- from anemoi.datasets.create.utils import to_datetime_list
24
-
25
- from .legacy import legacy_source
26
- from .mars import mars
27
-
28
- LOG = logging.getLogger(__name__)
29
-
30
-
31
- def _member(field: Any) -> int:
32
- """Retrieves the member number from the field metadata.
33
-
34
- Parameters
35
- ----------
36
- field : Any
37
- The field from which to retrieve the member number.
38
-
39
- Returns
40
- -------
41
- int
42
- The member number.
43
- """
44
- # Bug in eccodes has number=0 randomly
45
- number = field.metadata("number", default=0)
46
- if number is None:
47
- number = 0
48
- return number
49
-
50
-
51
- class Accumulation:
52
- """Class to handle data accumulation for a specific parameter, date, time, and member."""
53
-
54
- buggy_steps: bool = False
55
-
56
- def __init__(
57
- self,
58
- out: Any,
59
- /,
60
- param: str,
61
- date: int,
62
- time: int,
63
- number: int,
64
- step: list[int],
65
- frequency: int,
66
- accumulations_reset_frequency: int | None = None,
67
- user_date: str | None = None,
68
- **kwargs: Any,
69
- ) -> None:
70
- """Initialises an Accumulation instance.
71
-
72
- Parameters
73
- ----------
74
- out : Any
75
- Output object for writing data.
76
- param : str
77
- Parameter name.
78
- date : int
79
- Date of the accumulation.
80
- time : int
81
- Time of the accumulation.
82
- number : int
83
- Member number.
84
- step : List[int]
85
- List of steps.
86
- frequency : int
87
- Frequency of accumulation.
88
- accumulations_reset_frequency : Optional[int], optional
89
- Frequency at which accumulations reset. Defaults to None.
90
- user_date : Optional[str], optional
91
- User-defined date. Defaults to None.
92
- **kwargs : Any
93
- Additional keyword arguments.
94
- """
95
- self.out = out
96
- self.param = param
97
- self.date = date
98
- self.time = time
99
- self.steps = step
100
- self.number = number
101
- self.values: NDArray[None] | None = None
102
- self.seen = set()
103
- self.startStep: int | None = None
104
- self.endStep: int | None = None
105
- self.done = False
106
- self.frequency = frequency
107
- self.accumulations_reset_frequency = accumulations_reset_frequency
108
- self._check = None
109
- self.user_date = user_date
110
-
111
- @property
112
- def key(self) -> tuple[str, int, int, list[int], int]:
113
- """Returns the key for the accumulation."""
114
- return (self.param, self.date, self.time, self.steps, self.number)
115
-
116
- def check(self, field: Any) -> None:
117
- """Checks the field metadata against the accumulation parameters.
118
-
119
- Parameters
120
- ----------
121
- field : Any
122
- The field to check.
123
- """
124
- if self._check is None:
125
- self._check = field.metadata(namespace="mars")
126
-
127
- assert self.param == field.metadata("param"), (
128
- self.param,
129
- field.metadata("param"),
130
- )
131
- assert self.date == field.metadata("date"), (
132
- self.date,
133
- field.metadata("date"),
134
- )
135
- assert self.time == field.metadata("time"), (
136
- self.time,
137
- field.metadata("time"),
138
- )
139
- assert self.number == _member(field), (self.number, _member(field))
140
-
141
- return
142
-
143
- mars = field.metadata(namespace="mars")
144
- keys1 = sorted(self._check.keys())
145
- keys2 = sorted(mars.keys())
146
-
147
- assert keys1 == keys2, (keys1, keys2)
148
-
149
- for k in keys1:
150
- if k not in ("step",):
151
- assert self._check[k] == mars[k], (k, self._check[k], mars[k])
152
-
153
- def write(self, template: Any) -> None:
154
- """Writes the accumulated values to the output.
155
-
156
- Parameters
157
- ----------
158
- template : Any
159
- Template for writing the output.
160
- """
161
- assert self.startStep != self.endStep, (self.startStep, self.endStep)
162
- if np.all(self.values < 0):
163
- LOG.warning(
164
- f"Negative values when computing accumutation for {self.param} ({self.date} {self.time}): min={np.amin(self.values)} max={np.amax(self.values)}"
165
- )
166
-
167
- # In GRIB1, is the step is greater that 254 (one byte), we cannot use a range, because both P1 and P2 values
168
- # are used to store the end step
169
-
170
- edition = template.metadata("edition")
171
-
172
- if edition == 1 and self.endStep > 254:
173
- self.out.write(
174
- self.values,
175
- template=template,
176
- stepType="instant",
177
- step=self.endStep,
178
- check_nans=True,
179
- )
180
- else:
181
- self.out.write(
182
- self.values,
183
- template=template,
184
- stepType="accum",
185
- startStep=self.startStep,
186
- endStep=self.endStep,
187
- check_nans=True,
188
- )
189
- self.values = None
190
- self.done = True
191
-
192
- def add(self, field: Any, values: NDArray[Any]) -> None:
193
- """Adds values to the accumulation.
194
-
195
- Parameters
196
- ----------
197
- field : Any
198
- The field containing the values.
199
- values : NDArray[Any]
200
- The values to add.
201
- """
202
- self.check(field)
203
-
204
- step = field.metadata("step")
205
- if step not in self.steps:
206
- return
207
-
208
- if not np.all(values >= 0):
209
- warnings.warn(f"Negative values for {field}: {np.nanmin(values)} {np.nanmax(values)}")
210
-
211
- assert not self.done, (self.key, step)
212
- assert step not in self.seen, (self.key, step)
213
-
214
- startStep = field.metadata("startStep")
215
- endStep = field.metadata("endStep")
216
-
217
- if startStep == endStep:
218
- startStep, endStep = self.adjust_steps(startStep, endStep)
219
-
220
- assert step == endStep, (startStep, endStep, step)
221
-
222
- self.compute(values, startStep, endStep)
223
-
224
- self.seen.add(step)
225
-
226
- if len(self.seen) == len(self.steps):
227
- self.write(template=field)
228
-
229
- @classmethod
230
- def mars_date_time_steps(
231
- cls,
232
- *,
233
- dates: list[datetime.datetime],
234
- step1: int,
235
- step2: int,
236
- frequency: int | None,
237
- base_times: list[int],
238
- adjust_step: bool,
239
- accumulations_reset_frequency: int | None,
240
- user_date: str | None,
241
- ) -> Generator[tuple[int, int, tuple[int, ...]], None, None]:
242
- """Generates MARS date-time steps.
243
-
244
- Parameters
245
- ----------
246
- dates : List[datetime.datetime]
247
- List of dates.
248
- step1 : int
249
- First step.
250
- step2 : int
251
- Second step.
252
- frequency : Optional[int]
253
- Frequency of accumulation.
254
- base_times : List[int]
255
- List of base times.
256
- adjust_step : bool
257
- Whether to adjust the step.
258
- accumulations_reset_frequency : Optional[int], optional
259
- Frequency at which accumulations reset. Defaults to None.
260
- user_date : Optional[str], optional
261
- User-defined date. Defaults to None.
262
-
263
- Returns
264
- -------
265
- Generator[Tuple[int, int, Tuple[int, ...]], None, None]
266
- A generator of MARS date-time steps.
267
- """
268
- # assert step1 > 0, (step1, step2, frequency)
269
-
270
- for valid_date in dates:
271
- add_step = 0
272
- base_date = valid_date - datetime.timedelta(hours=step2)
273
- if user_date is not None:
274
- assert user_date == "????-??-01", user_date
275
- new_base_date = base_date.replace(day=1)
276
- assert new_base_date <= base_date, (new_base_date, base_date)
277
- add_step = int((base_date - new_base_date).total_seconds() // 3600)
278
-
279
- base_date = new_base_date
280
-
281
- if base_date.hour not in base_times:
282
- if not adjust_step:
283
- raise ValueError(
284
- f"Cannot find a base time in {base_times} that validates on {valid_date.isoformat()} for step={step2}"
285
- )
286
-
287
- while base_date.hour not in base_times:
288
- # print(f'{base_date=}, {base_times=}, {add_step=} {frequency=}')
289
- base_date -= datetime.timedelta(hours=1)
290
- add_step += 1
291
-
292
- yield cls._mars_date_time_step(
293
- base_date=base_date,
294
- step1=step1,
295
- step2=step2,
296
- add_step=add_step,
297
- frequency=frequency,
298
- accumulations_reset_frequency=accumulations_reset_frequency,
299
- user_date=user_date,
300
- requested_date=valid_date,
301
- )
302
-
303
- def compute(self, values: NDArray[Any], startStep: int, endStep: int) -> None:
304
- """Computes the accumulation.
305
-
306
- Parameters
307
- ----------
308
- values : NDArray[Any]
309
- The values to accumulate.
310
- startStep : int
311
- The start step.
312
- endStep : int
313
- The end step.
314
- """
315
- pass
316
-
317
- @classmethod
318
- def _mars_date_time_step(
319
- cls,
320
- *,
321
- base_date: datetime.datetime,
322
- step1: int,
323
- step2: int,
324
- add_step: int,
325
- frequency: int | None,
326
- accumulations_reset_frequency: int | None,
327
- user_date: str | None,
328
- requested_date: datetime.datetime | None = None,
329
- ) -> tuple[int, int, tuple[int, ...]]:
330
- """Generates a MARS date-time step.
331
-
332
- Parameters
333
- ----------
334
- base_date : datetime.datetime
335
- The base date.
336
- step1 : int
337
- First step.
338
- step2 : int
339
- Second step.
340
- add_step : int
341
- Additional step.
342
- frequency : Optional[int]
343
- Frequency of accumulation.
344
- accumulations_reset_frequency : Optional[int], optional
345
- Frequency at which accumulations reset. Defaults to None.
346
- user_date : Optional[str], optional
347
- User-defined date. Defaults to None.
348
- requested_date : Optional[datetime.datetime], optional
349
- Requested date. Defaults to None.
350
-
351
- Returns
352
- -------
353
- Tuple[int, int, Tuple[int, ...]]
354
- A tuple representing the MARS date-time step.
355
- """
356
- pass
357
-
358
-
359
- class AccumulationFromStart(Accumulation):
360
- """Class to handle data accumulation from the start of the forecast."""
361
-
362
- def adjust_steps(self, startStep: int, endStep: int) -> tuple[int, int]:
363
- """Adjusts the start and end steps.
364
-
365
- Parameters
366
- ----------
367
- startStep : int
368
- The start step.
369
- endStep : int
370
- The end step.
371
-
372
- Returns
373
- -------
374
- Tuple[int, int]
375
- The adjusted start and end steps.
376
- """
377
- assert endStep == startStep
378
- return (0, endStep)
379
-
380
- def compute(self, values: NDArray[Any], startStep: int, endStep: int) -> None:
381
- """Computes the accumulation from the start.
382
-
383
- Parameters
384
- ----------
385
- values : np.ndarray
386
- The values to accumulate.
387
- startStep : int
388
- The start step.
389
- endStep : int
390
- The end step.
391
- """
392
- assert startStep == 0, startStep
393
-
394
- if self.values is None:
395
-
396
- self.values = np.copy(values)
397
- self.startStep = 0
398
- self.endStep = endStep
399
-
400
- else:
401
- assert endStep != self.endStep, (self.endStep, endStep)
402
-
403
- if endStep > self.endStep:
404
- # assert endStep - self.endStep == self.stepping, (self.endStep, endStep, self.stepping)
405
- self.values = values - self.values
406
- self.startStep = self.endStep
407
- self.endStep = endStep
408
- else:
409
- # assert self.endStep - endStep == self.stepping, (self.endStep, endStep, self.stepping)
410
- self.values = self.values - values
411
- self.startStep = endStep
412
-
413
- if not np.all(self.values >= 0):
414
- warnings.warn(f"Negative values for {self.param}: {np.amin(self.values)} {np.amax(self.values)}")
415
- self.values = np.maximum(self.values, 0)
416
-
417
- @classmethod
418
- def _mars_date_time_step(
419
- cls,
420
- *,
421
- base_date: datetime.datetime,
422
- step1: int,
423
- step2: int,
424
- add_step: int,
425
- frequency: int | None,
426
- accumulations_reset_frequency: int | None,
427
- user_date: str | None,
428
- requested_date: datetime.datetime | None = None,
429
- ) -> tuple[int, int, tuple[int, ...]]:
430
- """Generates a MARS date-time step.
431
-
432
- Parameters
433
- ----------
434
- base_date : datetime.datetime
435
- The base date.
436
- step1 : int
437
- First step.
438
- step2 : int
439
- Second step.
440
- add_step : int
441
- Additional step.
442
- frequency : Optional[int]
443
- Frequency of accumulation.
444
- accumulations_reset_frequency : Optional[int], optional
445
- Frequency at which accumulations reset. Defaults to None.
446
- user_date : Optional[str], optional
447
- User-defined date. Defaults to None.
448
- requested_date : Optional[datetime.datetime], optional
449
- Requested date. Defaults to None.
450
-
451
- Returns
452
- -------
453
- Tuple[int, int, Tuple[int, ...]]
454
- A tuple representing the MARS date-time step.
455
- """
456
- assert user_date is None, user_date
457
-
458
- steps = (step1 + add_step, step2 + add_step)
459
- if steps[0] == 0:
460
- steps = (steps[1],)
461
-
462
- assert frequency == 0 or frequency == (step2 - step1), frequency
463
-
464
- return (
465
- base_date.year * 10000 + base_date.month * 100 + base_date.day,
466
- base_date.hour * 100 + base_date.minute,
467
- steps,
468
- )
469
-
470
-
471
- class AccumulationFromLastStep(Accumulation):
472
- """Class to handle data accumulation from the last step of the forecast."""
473
-
474
- def compute(self, values: NDArray[Any], startStep: int, endStep: int) -> None:
475
- """Computes the accumulation from the last step.
476
-
477
- Parameters
478
- ----------
479
- values : np.ndarray
480
- The values to accumulate.
481
- startStep : int
482
- The start step.
483
- endStep : int
484
- The end step.
485
- """
486
- assert endStep - startStep == self.frequency, (
487
- startStep,
488
- endStep,
489
- self.frequency,
490
- )
491
-
492
- if self.startStep is None:
493
- self.startStep = startStep
494
- else:
495
- self.startStep = min(self.startStep, startStep)
496
-
497
- if self.endStep is None:
498
- self.endStep = endStep
499
- else:
500
- self.endStep = max(self.endStep, endStep)
501
-
502
- if self.values is None:
503
- self.values = np.zeros_like(values)
504
-
505
- self.values += values
506
-
507
- @classmethod
508
- def _mars_date_time_step(
509
- cls,
510
- *,
511
- base_date: datetime.datetime,
512
- step1: int,
513
- step2: int,
514
- add_step: int,
515
- frequency: int,
516
- accumulations_reset_frequency: int | None,
517
- user_date: str | None = None,
518
- requested_date: datetime.datetime | None = None,
519
- ) -> tuple[int, int, tuple[int, ...]]:
520
- """Generates a MARS date-time step.
521
-
522
- Parameters
523
- ----------
524
- base_date : datetime.datetime
525
- The base date.
526
- step1 : int
527
- First step.
528
- step2 : int
529
- Second step.
530
- add_step : int
531
- Additional step.
532
- frequency : int
533
- Frequency of accumulation.
534
- accumulations_reset_frequency : Optional[int], optional
535
- Frequency at which accumulations reset. Defaults to None.
536
- user_date : Optional[str], optional
537
- User-defined date. Defaults to None.
538
- requested_date : Optional[datetime.datetime], optional
539
- Requested date. Defaults to None.
540
-
541
- Returns
542
- -------
543
- Tuple[int, int, Tuple[int, ...]]
544
- A tuple representing the MARS date-time step.
545
- """
546
-
547
- assert user_date is None, user_date
548
-
549
- assert frequency > 0, frequency
550
- # assert step1 > 0, (step1, step2, frequency, add_step, base_date)
551
-
552
- steps = []
553
- for step in range(step1 + frequency, step2 + frequency, frequency):
554
- steps.append(step + add_step)
555
-
556
- return (
557
- base_date.year * 10000 + base_date.month * 100 + base_date.day,
558
- base_date.hour * 100 + base_date.minute,
559
- tuple(steps),
560
- )
561
-
562
-
563
- class AccumulationFromLastReset(Accumulation):
564
- """Class to handle data accumulation from the last step of the forecast."""
565
-
566
- def adjust_steps(self, startStep: int, endStep: int) -> tuple[int, int]:
567
- """Adjusts the start and end steps.
568
-
569
- Parameters
570
- ----------
571
- startStep : int
572
- The start step.
573
- endStep : int
574
- The end step.
575
-
576
- Returns
577
- -------
578
- Tuple[int, int]
579
- The adjusted start and end steps.
580
- """
581
- return self.__class__._adjust_steps(startStep, endStep, self.frequency, self.accumulations_reset_frequency)
582
-
583
- @classmethod
584
- def _adjust_steps(
585
- self, startStep: int, endStep: int, frequency: int, accumulations_reset_frequency: int
586
- ) -> tuple[int, int]:
587
- """Adjusts the start and end steps.
588
-
589
- Parameters
590
- ----------
591
- startStep : int
592
- The start step.
593
- endStep : int
594
- The end step.
595
- frequency : int
596
- Frequency of accumulation.
597
- accumulations_reset_frequency : int
598
- Frequency at which accumulations reset.
599
-
600
- Returns
601
- -------
602
- Tuple[int, int]
603
- The adjusted start and end steps.
604
- """
605
-
606
- assert frequency == 1, (frequency, startStep, endStep)
607
- assert endStep - startStep <= accumulations_reset_frequency, (startStep, endStep)
608
-
609
- return ((startStep // accumulations_reset_frequency) * accumulations_reset_frequency, endStep)
610
-
611
- @classmethod
612
- def _steps(
613
- cls,
614
- valid_date: datetime.datetime,
615
- base_date: datetime.datetime,
616
- frequency: int,
617
- accumulations_reset_frequency: int,
618
- ) -> tuple[int, int]:
619
- """Calculates the steps for accumulation.
620
-
621
- Parameters
622
- ----------
623
- valid_date : datetime.datetime
624
- The valid date.
625
- base_date : datetime.datetime
626
- The base date.
627
- frequency : int
628
- Frequency of accumulation.
629
- accumulations_reset_frequency : int
630
- Frequency at which accumulations reset.
631
-
632
- Returns
633
- -------
634
- Tuple[int, int]
635
- A tuple representing the steps for accumulation.
636
- """
637
-
638
- assert base_date.day == 1, (base_date, valid_date)
639
-
640
- step = (valid_date - base_date).total_seconds()
641
- assert int(step) == step, (valid_date, base_date, step)
642
- assert int(step) % 3600 == 0, (valid_date, base_date, step)
643
- step = int(step // 3600)
644
- start, end = cls._adjust_steps(step, step, frequency, accumulations_reset_frequency)
645
-
646
- return start + frequency, end
647
-
648
- def compute(self, values: NDArray[Any], startStep: int, endStep: int) -> None:
649
- """Computes the accumulation from the last step.
650
-
651
- Parameters
652
- ----------
653
- values : NDArray[Any]
654
- The values to accumulate.
655
- startStep : int
656
- The start step.
657
- endStep : int
658
- The end step.
659
- """
660
-
661
- assert self.frequency == 1
662
-
663
- assert startStep % self.accumulations_reset_frequency == 0, (
664
- startStep,
665
- endStep,
666
- self.accumulations_reset_frequency,
667
- )
668
-
669
- if self.values is None:
670
-
671
- self.values = np.copy(values)
672
- self.startStep = startStep
673
- self.endStep = endStep
674
-
675
- if len(self.steps) == 1:
676
- assert self.startStep == self.endStep - self.frequency, (self.startStep, self.endStep)
677
-
678
- else:
679
- assert endStep != self.endStep, (self.endStep, endStep)
680
-
681
- if endStep > self.endStep:
682
- # assert endStep - self.endStep == self.stepping, (self.endStep, endStep, self.stepping)
683
- self.values = values - self.values
684
- self.startStep = self.endStep
685
- self.endStep = endStep
686
- else:
687
- # assert self.endStep - endStep == self.stepping, (self.endStep, endStep, self.stepping)
688
- self.values = self.values - values
689
- self.startStep = endStep
690
-
691
- assert self.endStep - self.startStep <= self.accumulations_reset_frequency, (self.startStep, startStep)
692
-
693
- @classmethod
694
- def _mars_date_time_step(
695
- cls,
696
- *,
697
- base_date: datetime.datetime,
698
- step1: int,
699
- step2: int,
700
- add_step: int,
701
- frequency: int,
702
- accumulations_reset_frequency: int | None,
703
- user_date: str | None,
704
- requested_date: datetime.datetime | None = None,
705
- ) -> tuple[int, int, tuple[int, ...]]:
706
- """Generates a MARS date-time step.
707
-
708
- Parameters
709
- ----------
710
- base_date : datetime.datetime
711
- The base date.
712
- step1 : int
713
- First step.
714
- step2 : int
715
- Second step.
716
- add_step : int
717
- Additional step.
718
- frequency : int
719
- Frequency of accumulation.
720
- accumulations_reset_frequency : Optional[int]
721
- Frequency at which accumulations reset.
722
- user_date : Optional[str]
723
- User-defined date.
724
- requested_date : Optional[datetime.datetime], optional
725
- Requested date. Defaults to None.
726
-
727
- Returns
728
- -------
729
- Tuple[int, int, Tuple[int, ...]]
730
- A tuple representing the MARS date-time step.
731
- """
732
- # assert frequency > 0, frequency
733
- # assert step1 > 0, (step1, step2, frequency, add_step, base_date)
734
-
735
- step1 += add_step
736
- step2 += add_step
737
-
738
- assert step2 - step1 == frequency, (step1, step2, frequency)
739
-
740
- adjust_step1 = cls._adjust_steps(step1, step1, frequency, accumulations_reset_frequency)
741
- adjust_step2 = cls._adjust_steps(step2, step2, frequency, accumulations_reset_frequency)
742
-
743
- if adjust_step1[1] % accumulations_reset_frequency == 0:
744
- # First step of a new accumulation
745
- steps = (adjust_step2[1],)
746
- else:
747
- steps = (adjust_step1[1], adjust_step2[1])
748
-
749
- return (
750
- base_date.year * 10000 + base_date.month * 100 + base_date.day,
751
- base_date.hour * 100 + base_date.minute,
752
- tuple(steps),
753
- )
754
-
755
-
756
- def _identity(x: Any) -> Any:
757
- """Identity function that returns the input as is.
758
-
759
- Parameters
760
- ----------
761
- x : Any
762
- Input value.
763
-
764
- Returns
765
- -------
766
- Any
767
- The input value.
768
- """
769
- return x
770
-
771
-
772
- def _compute_accumulations(
773
- context: Any,
774
- dates: list[datetime.datetime],
775
- request: dict[str, Any],
776
- user_accumulation_period: int | tuple[int, int] = 6,
777
- data_accumulation_period: int | None = None,
778
- accumulations_reset_frequency: int | None = None,
779
- user_date: str | None = None,
780
- patch: Any = _identity,
781
- base_times: list[int] | None = None,
782
- use_cdsapi_dataset: str | None = None,
783
- ) -> Any:
784
- """Computes accumulations based on the provided parameters.
785
-
786
- Parameters
787
- ----------
788
- context : Any
789
- Context for the computation.
790
- dates : List[datetime.datetime]
791
- List of dates.
792
- request : Dict[str, Any]
793
- Request parameters.
794
- user_accumulation_period : Union[int, Tuple[int, int]], optional
795
- User-defined accumulation period. Defaults to 6.
796
- data_accumulation_period : Optional[int], optional
797
- Data accumulation period. Defaults to None.
798
- accumulations_reset_frequency : Optional[int], optional
799
- Frequency at which accumulations reset. Defaults to None.
800
- user_date : Optional[str], optional
801
- User-defined date. Defaults to None.
802
- patch : Any, optional
803
- Patch function. Defaults to _identity.
804
- base_times : Optional[List[int]], optional
805
- List of base times. Defaults to None.
806
- use_cdsapi_dataset : Optional[str], optional
807
- CDSAPI dataset to use. Defaults to None.
808
-
809
- Returns
810
- -------
811
- Any
812
- The computed accumulations.
813
- """
814
- adjust_step = isinstance(user_accumulation_period, int)
815
-
816
- if not isinstance(user_accumulation_period, (list, tuple)):
817
- user_accumulation_period = (0, user_accumulation_period)
818
-
819
- assert len(user_accumulation_period) == 2, user_accumulation_period
820
- step1, step2 = user_accumulation_period
821
- assert step1 < step2, user_accumulation_period
822
-
823
- if accumulations_reset_frequency is not None:
824
- AccumulationClass = AccumulationFromLastReset
825
- else:
826
- AccumulationClass = AccumulationFromStart if data_accumulation_period in (0, None) else AccumulationFromLastStep
827
-
828
- if data_accumulation_period is None:
829
- data_accumulation_period = user_accumulation_period[1] - user_accumulation_period[0]
830
-
831
- if base_times is None:
832
- if "time" in request:
833
- time = request.pop("time")
834
- if time > 100:
835
- time = time // 100
836
- base_times = [time]
837
- else:
838
- base_times = [0, 6, 12, 18]
839
-
840
- base_times = [t // 100 if t > 100 else t for t in base_times]
841
-
842
- mars_date_time_steps = AccumulationClass.mars_date_time_steps(
843
- dates=dates,
844
- step1=step1,
845
- step2=step2,
846
- frequency=data_accumulation_period,
847
- base_times=base_times,
848
- adjust_step=adjust_step,
849
- accumulations_reset_frequency=accumulations_reset_frequency,
850
- user_date=user_date,
851
- )
852
-
853
- request = deepcopy(request)
854
-
855
- param = request["param"]
856
- if not isinstance(param, (list, tuple)):
857
- param = [param]
858
-
859
- number = request.get("number", [0])
860
- assert isinstance(number, (list, tuple))
861
-
862
- frequency = data_accumulation_period
863
-
864
- type_ = request.get("type", "an")
865
- if type_ == "an":
866
- type_ = "fc"
867
-
868
- request.update({"type": type_, "levtype": "sfc"})
869
-
870
- tmp = temp_file()
871
- path = tmp.path
872
- out = new_grib_output(path)
873
-
874
- requests = []
875
-
876
- accumulations = {}
877
-
878
- for date, time, steps in mars_date_time_steps:
879
- for p in param:
880
- for n in number:
881
- r = dict(request, param=p, date=date, time=time, step=sorted(steps), number=n)
882
-
883
- requests.append(patch(r))
884
-
885
- ds = mars(
886
- context, dates, *requests, request_already_using_valid_datetime=True, use_cdsapi_dataset=use_cdsapi_dataset
887
- )
888
-
889
- accumulations = {}
890
- for a in [
891
- AccumulationClass(out, frequency=frequency, accumulations_reset_frequency=accumulations_reset_frequency, **r)
892
- for r in requests
893
- ]:
894
- for s in a.steps:
895
- key = (a.param, a.date, a.time, s, a.number)
896
- accumulations.setdefault(key, []).append(a)
897
-
898
- for field in ds:
899
- key = (
900
- field.metadata("param"),
901
- field.metadata("date"),
902
- field.metadata("time"),
903
- field.metadata("step"),
904
- _member(field),
905
- )
906
- values = field.values # optimisation
907
- if key not in accumulations:
908
- raise ValueError(f"Key not found: {key}. Is it an accumulation field?")
909
-
910
- for a in accumulations[key]:
911
- a.add(field, values)
912
-
913
- for acc in accumulations.values():
914
- for a in acc:
915
- assert a.done, (a.key, a.seen, a.steps)
916
-
917
- out.close()
918
-
919
- ds = ekd.from_source("file", path)
920
-
921
- assert len(ds) / len(param) / len(number) == len(dates), (
922
- len(ds),
923
- len(param),
924
- len(dates),
925
- )
926
- ds._tmp = tmp
927
-
928
- return ds
929
-
930
-
931
- def _to_list(x: list[Any] | tuple[Any] | Any) -> list[Any]:
932
- """Converts the input to a list if it is not already a list or tuple.
933
-
934
- Parameters
935
- ----------
936
- x : Union[List[Any], Tuple[Any], Any]
937
- Input value.
938
-
939
- Returns
940
- -------
941
- List[Any]
942
- The input value as a list.
943
- """
944
- if isinstance(x, (list, tuple)):
945
- return x
946
- return [x]
947
-
948
-
949
- def _scda(request: dict[str, Any]) -> dict[str, Any]:
950
- """Modifies the request stream based on the time.
951
-
952
- Parameters
953
- ----------
954
- request : Dict[str, Any]
955
- Request parameters.
956
-
957
- Returns
958
- -------
959
- Dict[str, Any]
960
- The modified request parameters.
961
- """
962
- if request["time"] in (6, 18, 600, 1800):
963
- request["stream"] = "scda"
964
- else:
965
- request["stream"] = "oper"
966
- return request
967
-
968
-
969
- @legacy_source(__file__)
970
- def accumulations(
971
- context: Any, dates: list[datetime.datetime], use_cdsapi_dataset: str | None = None, **request: Any
972
- ) -> Any:
973
- """Computes accumulations based on the provided context, dates, and request parameters.
974
-
975
- Parameters
976
- ----------
977
- context : Any
978
- Context for the computation.
979
- dates : List[datetime.datetime]
980
- List of dates.
981
- use_cdsapi_dataset : Optional[str], optional
982
- CDSAPI dataset to use. Defaults to None.
983
- **request : Any
984
- Additional request parameters.
985
-
986
- Returns
987
- -------
988
- Any
989
- The computed accumulations.
990
- """
991
-
992
- if (
993
- request.get("class") == "ea"
994
- and request.get("stream", "oper") == "oper"
995
- and request.get("accumulation_period") == 24
996
- ):
997
- from .accumulations2 import accumulations as accumulations2
998
-
999
- LOG.warning(
1000
- "🧪️ Experimental features: Using accumulations2, because class=ea stream=oper and accumulation_period=24"
1001
- )
1002
- return accumulations2(context, dates, **request)
1003
-
1004
- _to_list(request["param"])
1005
- class_ = request.get("class", "od")
1006
- stream = request.get("stream", "oper")
1007
-
1008
- user_accumulation_period = request.pop("accumulation_period", 6)
1009
- accumulations_reset_frequency = request.pop("accumulations_reset_frequency", None)
1010
- user_date = request.pop("date", None)
1011
-
1012
- # If `data_accumulation_period` is not set, this means that the accumulations are from the start
1013
- # of the forecast.
1014
-
1015
- KWARGS = {
1016
- ("od", "oper"): dict(patch=_scda),
1017
- ("od", "elda"): dict(base_times=(6, 18)),
1018
- ("od", "enfo"): dict(base_times=(0, 6, 12, 18)),
1019
- ("ea", "oper"): dict(data_accumulation_period=1, base_times=(6, 18)),
1020
- ("ea", "enda"): dict(data_accumulation_period=3, base_times=(6, 18)),
1021
- ("rr", "oper"): dict(base_times=(0, 3, 6, 9, 12, 15, 18, 21)),
1022
- ("l5", "oper"): dict(data_accumulation_period=1, base_times=(0,)),
1023
- }
1024
-
1025
- kwargs = KWARGS.get((class_, stream), {})
1026
-
1027
- context.trace("🌧️", f"accumulations {request} {user_accumulation_period} {kwargs}")
1028
-
1029
- return _compute_accumulations(
1030
- context,
1031
- dates,
1032
- request,
1033
- user_accumulation_period=user_accumulation_period,
1034
- accumulations_reset_frequency=accumulations_reset_frequency,
1035
- use_cdsapi_dataset=use_cdsapi_dataset,
1036
- user_date=user_date,
1037
- **kwargs,
1038
- )
1039
-
1040
-
1041
- execute = accumulations
1042
-
1043
- if __name__ == "__main__":
1044
- import yaml
1045
-
1046
- config = yaml.safe_load(
1047
- """
1048
- class: ea
1049
- expver: '0001'
1050
- grid: 20./20.
1051
- levtype: sfc
1052
- # number: [0, 1]
1053
- # stream: enda
1054
- param: [cp, tp]
1055
- # accumulation_period: 6h
1056
- """
1057
- )
1058
- dates = yaml.safe_load("[2022-12-30 18:00, 2022-12-31 00:00, 2022-12-31 06:00, 2022-12-31 12:00]")
1059
- dates = to_datetime_list(dates)
1060
-
1061
- for f in accumulations(None, dates, **config):
1062
- print(f, f.to_numpy().mean())