scriptengine-tasks-ecearth 0.8.0__tar.gz → 0.10.0__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.
Files changed (65) hide show
  1. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/PKG-INFO +3 -3
  2. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/helpers/cubes.py +45 -1
  3. scriptengine_tasks_ecearth-0.10.0/monitoring/oifs_timeseries.py +130 -0
  4. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/pyproject.toml +5 -4
  5. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/scriptengine_tasks_ecearth.egg-info/PKG-INFO +3 -3
  6. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/scriptengine_tasks_ecearth.egg-info/SOURCES.txt +2 -2
  7. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/scriptengine_tasks_ecearth.egg-info/entry_points.txt +2 -1
  8. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/scriptengine_tasks_ecearth.egg-info/requires.txt +1 -1
  9. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_oifs_all_mean_map.py +18 -0
  10. scriptengine_tasks_ecearth-0.10.0/tests/test_oifs_timeseries.py +84 -0
  11. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_oifs_year_mean_temporalmap.py +16 -0
  12. scriptengine_tasks_ecearth-0.8.0/monitoring/oifs_global_mean_year_mean_timeseries.py +0 -131
  13. scriptengine_tasks_ecearth-0.8.0/tests/test_oifs_global_mean_year_mean_timeseries.py +0 -42
  14. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/LICENSE +0 -0
  15. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/README.md +0 -0
  16. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/helpers/__init__.py +0 -0
  17. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/helpers/dates.py +0 -0
  18. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/helpers/exceptions.py +0 -0
  19. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/helpers/files.py +0 -0
  20. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/helpers/map_type_handling.py +0 -0
  21. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/helpers/nemo.py +0 -0
  22. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/helpers/presentation_objects.py +0 -0
  23. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/diskusage_rte_scalar.py +0 -0
  24. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/gitlab.py +0 -0
  25. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/linear_combination.py +0 -0
  26. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/map.py +0 -0
  27. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/markdown.py +0 -0
  28. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/nemo_all_mean_map.py +0 -0
  29. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/nemo_time_mean_temporalmap.py +0 -0
  30. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/nemo_timeseries.py +0 -0
  31. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/oifs_all_mean_map.py +0 -0
  32. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/oifs_year_mean_temporalmap.py +0 -0
  33. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/redmine.py +0 -0
  34. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/scalar.py +0 -0
  35. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/si3_hemis_point_month_mean_all_mean_map.py +0 -0
  36. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/si3_hemis_point_month_mean_temporalmap.py +0 -0
  37. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/si3_hemis_sum_month_mean_timeseries.py +0 -0
  38. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/simulatedyears_rte_scalar.py +0 -0
  39. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/temporalmap.py +0 -0
  40. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/monitoring/timeseries.py +0 -0
  41. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/scriptengine_tasks_ecearth.egg-info/dependency_links.txt +0 -0
  42. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/scriptengine_tasks_ecearth.egg-info/top_level.txt +0 -0
  43. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/setup.cfg +0 -0
  44. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/setup.py +0 -0
  45. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_cubes.py +0 -0
  46. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_dates.py +0 -0
  47. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_diskusage_rte_scalar.py +0 -0
  48. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_file_handling.py +0 -0
  49. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_gitlab.py +0 -0
  50. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_linear_combination.py +0 -0
  51. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_map.py +0 -0
  52. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_map_type_handling.py +0 -0
  53. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_markdown.py +0 -0
  54. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_nemo_all_mean_map.py +0 -0
  55. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_nemo_time_mean_temporalmap.py +0 -0
  56. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_nemo_timeseries.py +0 -0
  57. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_presentation_objects.py +0 -0
  58. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_redmine.py +0 -0
  59. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_scalar.py +0 -0
  60. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_si3_hemis_point_month_mean_all_mean_map.py +0 -0
  61. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_si3_hemis_point_month_mean_temporalmap.py +0 -0
  62. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_si3_hemis_sum_month_mean_timeseries.py +0 -0
  63. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_simulatedyears_rte_scalar.py +0 -0
  64. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_temporalmap.py +0 -0
  65. {scriptengine_tasks_ecearth-0.8.0 → scriptengine_tasks_ecearth-0.10.0}/tests/test_timeseries.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scriptengine-tasks-ecearth
3
- Version: 0.8.0
3
+ Version: 0.10.0
4
4
  Summary: ScriptEngine tasks for use with the EC-Earth climate model
5
5
  Author-email: Valentina Schueller <valentina.schueller@gmail.com>, Uwe Fladrich <uwe.fladrich@protonmail.com>
6
6
  Project-URL: Homepage, https://github.com/uwefladrich/scriptengine-tasks-ecearth
@@ -8,7 +8,7 @@ Project-URL: Bug Tracker, https://github.com/uwefladrich/scriptengine-tasks-ecea
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
10
10
  Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.8
11
+ Requires-Python: >=3.11
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: scriptengine>=0.8.1
@@ -16,7 +16,7 @@ Requires-Dist: pyYAML>=5.1
16
16
  Requires-Dist: matplotlib>=3.1
17
17
  Requires-Dist: numpy>=1.18
18
18
  Requires-Dist: imageio>=2.18
19
- Requires-Dist: scitools-iris>=3.5
19
+ Requires-Dist: scitools-iris>=3.12.2
20
20
  Requires-Dist: cartopy>=0.20
21
21
  Requires-Dist: python-redmine
22
22
  Requires-Dist: python-gitlab
@@ -3,6 +3,7 @@
3
3
  import warnings
4
4
 
5
5
  import iris
6
+ import iris.analysis.cartography
6
7
  import iris.cube
7
8
  import numpy as np
8
9
  from iris.util import equalise_attributes
@@ -97,7 +98,10 @@ def compute_time_weights(monthly_data_cube, cube_shape=None):
97
98
 
98
99
  def compute_annual_mean(cube):
99
100
  # Remove auxiliary time coordinate before collapsing cube
100
- cube.remove_coord(cube.coord("time", dim_coords=False))
101
+ try:
102
+ cube.coord("time")
103
+ except iris.exceptions.CoordinateNotFoundError:
104
+ cube.remove_coord(cube.coord("time", dim_coords=False))
101
105
 
102
106
  annual_mean = cube.collapsed(
103
107
  "time",
@@ -157,3 +161,43 @@ def mask_other_hemisphere(cube, hemisphere):
157
161
  else:
158
162
  raise ValueError("Invalid hemisphere, must be 'north' or 'south'")
159
163
  return cube
164
+
165
+
166
+ def compute_area_weights(cube):
167
+ if is_grid_regular(cube):
168
+ return compute_regular_grid_weights(cube)
169
+ return compute_reduced_grid_weights(cube)
170
+
171
+
172
+ def is_grid_regular(cube) -> bool:
173
+ if not cube.coords("latitude", dim_coords=True):
174
+ return False
175
+ return True
176
+
177
+
178
+ def compute_reduced_grid_weights(cube):
179
+ """compute area weights for the reduced gaussian grid"""
180
+ nh_latitudes = np.ma.masked_less(cube.coord("latitude").points, 0)
181
+ unique_lats, gridpoints_per_lat = np.unique(nh_latitudes, return_counts=True)
182
+ unique_lats, gridpoints_per_lat = unique_lats[0:-1], gridpoints_per_lat[0:-1]
183
+ areas = []
184
+ last_angle = 0
185
+ earth_radius = 6371
186
+ for latitude, amount in zip(unique_lats, gridpoints_per_lat):
187
+ delta = latitude - last_angle
188
+ current_angle = last_angle + 2 * delta
189
+ sin_diff = np.sin(np.deg2rad(current_angle)) - np.sin(np.deg2rad(last_angle))
190
+ ring_area = 2 * np.pi * earth_radius**2 * sin_diff
191
+ grid_area = ring_area / amount
192
+ areas.extend([grid_area] * amount)
193
+ last_angle = current_angle
194
+ areas = np.append(areas[::-1], areas)
195
+ area_weights = np.broadcast_to(areas, cube.data.shape)
196
+ return area_weights
197
+
198
+
199
+ def compute_regular_grid_weights(cube):
200
+ """compute area weights for a regular lat/lon grid"""
201
+ cube.coord("latitude").guess_bounds()
202
+ cube.coord("longitude").guess_bounds()
203
+ return iris.analysis.cartography.area_weights(cube)
@@ -0,0 +1,130 @@
1
+ """Processing Tasks for computing Timeseries from OpenIFS output."""
2
+
3
+ import warnings
4
+ from pathlib import Path
5
+
6
+ import iris
7
+ from iris.analysis import WeightedAggregator
8
+ from iris.coords import CellMeasure
9
+ from scriptengine.tasks.core import timed_runner
10
+
11
+ import helpers.cubes
12
+
13
+ from .timeseries import Timeseries
14
+
15
+
16
+ class OifsTimeseries(Timeseries):
17
+ """OifsTimeseries Processing Task"""
18
+
19
+ _required_arguments = (
20
+ "src",
21
+ "varname",
22
+ "dst",
23
+ )
24
+
25
+ def __init__(self, arguments):
26
+ OifsTimeseries.check_arguments(arguments)
27
+ super().__init__(
28
+ {**arguments, "title": None, "coord_value": None, "data_value": None}
29
+ )
30
+
31
+ def _load_input(self, context):
32
+ src = self.getarg("src", context)
33
+ var_name = self.getarg("varname", context)
34
+ self.log_info(f"Create time series for atmosphere variable {var_name}.")
35
+ self.log_debug(f"Source file(s): {src}")
36
+
37
+ var_data = helpers.cubes.load_input_cube(src, var_name)
38
+ return var_data
39
+
40
+ def _adjust_metadata(self, cube):
41
+ """Adjustments to the cube metadata before saving."""
42
+ long_name = cube.long_name
43
+ var_name = cube.standard_name
44
+ comment = f"Annual mean of {long_name} / **{var_name}**."
45
+ cube.add_cell_method(
46
+ iris.coords.CellMethod("mean", coords="time", intervals="1 year")
47
+ )
48
+ cube = helpers.cubes.set_metadata(
49
+ cube,
50
+ title=f"{long_name} (annual mean)",
51
+ comment=comment,
52
+ )
53
+ return helpers.cubes.convert_units(cube)
54
+
55
+ def _compute_global_aggregate(self, cube, operation: WeightedAggregator):
56
+ """Area-weighted aggregate of cube (e.g., sum, mean)."""
57
+ area_weights = helpers.cubes.compute_area_weights(cube)
58
+ cell_size = CellMeasure(
59
+ area_weights, var_name="cell_size", units="m2", measure="area"
60
+ )
61
+ dims = tuple(range(len(area_weights.shape)))
62
+ cube.add_cell_measure(cell_size, dims)
63
+ # Remove duplicate boundary values before averaging
64
+ cube.coord("latitude").bounds = cube.coord("latitude").bounds[:, [0, 1]]
65
+ cube.coord("longitude").bounds = cube.coord("longitude").bounds[:, [0, 1]]
66
+ with warnings.catch_warnings():
67
+ # Suppress warning about insufficient metadata.
68
+ warnings.filterwarnings(
69
+ "ignore",
70
+ "Collapsing a non-contiguous coordinate.",
71
+ UserWarning,
72
+ )
73
+ global_aggregate = cube.collapsed(
74
+ ["latitude", "longitude"],
75
+ operation,
76
+ weights="cell_size",
77
+ )
78
+ return global_aggregate
79
+
80
+
81
+ class OifsGlobalMeanYearMeanTimeseries(OifsTimeseries):
82
+ """OifsGlobalMeanYearMeanTimeseries Processing Task"""
83
+
84
+ _required_arguments = ("src", "dst", "varname")
85
+
86
+ def __init__(self, arguments=None):
87
+ OifsGlobalMeanYearMeanTimeseries.check_arguments(arguments)
88
+ super().__init__(
89
+ {**arguments, "title": None, "coord_value": None, "data_value": None}
90
+ )
91
+
92
+ @timed_runner
93
+ def run(self, context):
94
+ oifs_cube = self._load_input(context)
95
+
96
+ global_mean = self._compute_global_aggregate(oifs_cube, iris.analysis.MEAN)
97
+ annual_mean = helpers.cubes.compute_annual_mean(global_mean)
98
+ annual_mean.cell_methods = (iris.coords.CellMethod("mean", coords="area"),)
99
+
100
+ annual_mean = self._adjust_metadata(annual_mean)
101
+
102
+ dst = Path(self.getarg("dst", context))
103
+ self.check_file_extension(dst)
104
+ self.save(annual_mean, dst)
105
+
106
+
107
+ class OifsGlobalSumYearMeanTimeseries(OifsTimeseries):
108
+ """OifsGlobalSumYearMeanTimeseries Processing Task"""
109
+
110
+ _required_arguments = ("src", "dst", "varname")
111
+
112
+ def __init__(self, arguments=None):
113
+ OifsGlobalSumYearMeanTimeseries.check_arguments(arguments)
114
+ super().__init__(
115
+ {**arguments, "title": None, "coord_value": None, "data_value": None}
116
+ )
117
+
118
+ @timed_runner
119
+ def run(self, context):
120
+ oifs_cube = self._load_input(context)
121
+
122
+ global_sum = self._compute_global_aggregate(oifs_cube, iris.analysis.SUM)
123
+ annual_mean = helpers.cubes.compute_annual_mean(global_sum)
124
+ annual_mean.cell_methods = (iris.coords.CellMethod("sum", coords="area"),)
125
+
126
+ annual_mean = self._adjust_metadata(annual_mean)
127
+
128
+ dst = Path(self.getarg("dst", context))
129
+ self.check_file_extension(dst)
130
+ self.save(annual_mean, dst)
@@ -7,7 +7,7 @@
7
7
 
8
8
  [project]
9
9
  name = "scriptengine-tasks-ecearth"
10
- version = "0.8.0"
10
+ version = "0.10.0"
11
11
  authors = [
12
12
  { name = "Valentina Schueller", email = "valentina.schueller@gmail.com" },
13
13
  { name = "Uwe Fladrich", email = "uwe.fladrich@protonmail.com" },
@@ -19,14 +19,14 @@
19
19
  "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
20
20
  "Operating System :: OS Independent",
21
21
  ]
22
- requires-python = ">=3.8"
22
+ requires-python = ">=3.11"
23
23
  dependencies = [
24
24
  "scriptengine>=0.8.1",
25
25
  "pyYAML>=5.1",
26
26
  "matplotlib>=3.1",
27
27
  "numpy>=1.18",
28
28
  "imageio>=2.18",
29
- "scitools-iris>=3.5",
29
+ "scitools-iris>=3.12.2", # https://github.com/SciTools/iris/issues/6417
30
30
  "cartopy>=0.20",
31
31
  "python-redmine",
32
32
  "python-gitlab",
@@ -52,7 +52,8 @@
52
52
  "ece.mon.si3_hemis_point_month_mean_temporalmap" = "monitoring.si3_hemis_point_month_mean_temporalmap:Si3HemisPointMonthMeanTemporalmap"
53
53
  "ece.mon.oifs_all_mean_map" = "monitoring.oifs_all_mean_map:OifsAllMeanMap"
54
54
  "ece.mon.oifs_year_mean_temporalmap" = "monitoring.oifs_year_mean_temporalmap:OifsYearMeanTemporalmap"
55
- "ece.mon.oifs_global_mean_year_mean_timeseries" = "monitoring.oifs_global_mean_year_mean_timeseries:OifsGlobalMeanYearMeanTimeseries"
55
+ "ece.mon.oifs_global_mean_year_mean_timeseries" = "monitoring.oifs_timeseries:OifsGlobalMeanYearMeanTimeseries"
56
+ "ece.mon.oifs_global_sum_year_mean_timeseries" = "monitoring.oifs_timeseries:OifsGlobalSumYearMeanTimeseries"
56
57
  "ece.mon.presentation.markdown" = "monitoring.markdown:Markdown"
57
58
  "ece.mon.presentation.redmine" = "monitoring.redmine:Redmine"
58
59
  "ece.mon.presentation.gitlab" = "monitoring.gitlab:Gitlab"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scriptengine-tasks-ecearth
3
- Version: 0.8.0
3
+ Version: 0.10.0
4
4
  Summary: ScriptEngine tasks for use with the EC-Earth climate model
5
5
  Author-email: Valentina Schueller <valentina.schueller@gmail.com>, Uwe Fladrich <uwe.fladrich@protonmail.com>
6
6
  Project-URL: Homepage, https://github.com/uwefladrich/scriptengine-tasks-ecearth
@@ -8,7 +8,7 @@ Project-URL: Bug Tracker, https://github.com/uwefladrich/scriptengine-tasks-ecea
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
10
10
  Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.8
11
+ Requires-Python: >=3.11
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: scriptengine>=0.8.1
@@ -16,7 +16,7 @@ Requires-Dist: pyYAML>=5.1
16
16
  Requires-Dist: matplotlib>=3.1
17
17
  Requires-Dist: numpy>=1.18
18
18
  Requires-Dist: imageio>=2.18
19
- Requires-Dist: scitools-iris>=3.5
19
+ Requires-Dist: scitools-iris>=3.12.2
20
20
  Requires-Dist: cartopy>=0.20
21
21
  Requires-Dist: python-redmine
22
22
  Requires-Dist: python-gitlab
@@ -19,7 +19,7 @@ monitoring/nemo_all_mean_map.py
19
19
  monitoring/nemo_time_mean_temporalmap.py
20
20
  monitoring/nemo_timeseries.py
21
21
  monitoring/oifs_all_mean_map.py
22
- monitoring/oifs_global_mean_year_mean_timeseries.py
22
+ monitoring/oifs_timeseries.py
23
23
  monitoring/oifs_year_mean_temporalmap.py
24
24
  monitoring/redmine.py
25
25
  monitoring/scalar.py
@@ -48,7 +48,7 @@ tests/test_nemo_all_mean_map.py
48
48
  tests/test_nemo_time_mean_temporalmap.py
49
49
  tests/test_nemo_timeseries.py
50
50
  tests/test_oifs_all_mean_map.py
51
- tests/test_oifs_global_mean_year_mean_timeseries.py
51
+ tests/test_oifs_timeseries.py
52
52
  tests/test_oifs_year_mean_temporalmap.py
53
53
  tests/test_presentation_objects.py
54
54
  tests/test_redmine.py
@@ -8,7 +8,8 @@ ece.mon.nemo_month_mean_temporalmap = monitoring.nemo_time_mean_temporalmap:Nemo
8
8
  ece.mon.nemo_year_mean_temporalmap = monitoring.nemo_time_mean_temporalmap:NemoYearMeanTemporalmap
9
9
  ece.mon.nemo_year_mean_timeseries = monitoring.nemo_timeseries:NemoYearMeanTimeseries
10
10
  ece.mon.oifs_all_mean_map = monitoring.oifs_all_mean_map:OifsAllMeanMap
11
- ece.mon.oifs_global_mean_year_mean_timeseries = monitoring.oifs_global_mean_year_mean_timeseries:OifsGlobalMeanYearMeanTimeseries
11
+ ece.mon.oifs_global_mean_year_mean_timeseries = monitoring.oifs_timeseries:OifsGlobalMeanYearMeanTimeseries
12
+ ece.mon.oifs_global_sum_year_mean_timeseries = monitoring.oifs_timeseries:OifsGlobalSumYearMeanTimeseries
12
13
  ece.mon.oifs_year_mean_temporalmap = monitoring.oifs_year_mean_temporalmap:OifsYearMeanTemporalmap
13
14
  ece.mon.presentation.gitlab = monitoring.gitlab:Gitlab
14
15
  ece.mon.presentation.markdown = monitoring.markdown:Markdown
@@ -3,7 +3,7 @@ pyYAML>=5.1
3
3
  matplotlib>=3.1
4
4
  numpy>=1.18
5
5
  imageio>=2.18
6
- scitools-iris>=3.5
6
+ scitools-iris>=3.12.2
7
7
  cartopy>=0.20
8
8
  python-redmine
9
9
  python-gitlab
@@ -25,6 +25,24 @@ def test_oifs_all_mean_map_working(tmp_path):
25
25
  assert len(cube.coord("time").points) == 1
26
26
 
27
27
 
28
+ def test_oifs_all_mean_map_regular_grid(tmp_path):
29
+ init = {
30
+ "src": ["./tests/testdata/regular_grid_tas.nc"],
31
+ "dst": str(tmp_path / "test.nc"),
32
+ "varname": "tas",
33
+ }
34
+ atmo_map = OifsAllMeanMap(init)
35
+ atmo_map.run(init)
36
+ cube = iris.load_cube(init["dst"])
37
+ assert cube.name() == "air_temperature"
38
+ assert cube.attributes["title"] is not None
39
+ assert cube.attributes["comment"] is not None
40
+ assert cube.attributes["diagnostic_type"] == "map"
41
+ assert cube.attributes["map_type"] == "global atmosphere"
42
+ assert cube.coord("time").climatological
43
+ assert len(cube.coord("time").points) == 1
44
+
45
+
28
46
  def test_oifs_all_mean_map_wrong_code(tmp_path):
29
47
  init = {
30
48
  "src": ["./tests/testdata/TES1_atm_1m_1990_2t.nc"],
@@ -0,0 +1,84 @@
1
+ """Tests for monitoring/oifs_timeseries.py"""
2
+
3
+ import iris
4
+ import pytest
5
+ import scriptengine.exceptions
6
+
7
+ from monitoring.oifs_timeseries import (
8
+ OifsGlobalMeanYearMeanTimeseries,
9
+ OifsGlobalSumYearMeanTimeseries,
10
+ )
11
+
12
+
13
+ def test_oifs_global_mean_year_mean_timeseries_working(tmp_path):
14
+ init = {
15
+ "src": ["./tests/testdata/TES1_atm_1m_1990_2t.nc"],
16
+ "dst": str(tmp_path / "test.nc"),
17
+ "varname": "2t",
18
+ }
19
+ atmo_ts = OifsGlobalMeanYearMeanTimeseries(init)
20
+ atmo_ts.run(init)
21
+ cube = iris.load_cube(init["dst"])
22
+ assert cube.name() == "2 metre temperature"
23
+ assert cube.attributes["title"] is not None
24
+ assert cube.attributes["comment"] is not None
25
+ assert cube.attributes["diagnostic_type"] == "time series"
26
+ assert cube.cell_methods == (
27
+ iris.coords.CellMethod("mean", coords="area"),
28
+ iris.coords.CellMethod("mean", coords="time", intervals="1 year"),
29
+ )
30
+
31
+
32
+ def test_oifs_timeseries_compare_grids(tmp_path):
33
+ init = {
34
+ "src": ["./tests/testdata/regular_grid_tas.nc"],
35
+ "dst": str(tmp_path / "test_reg.nc"),
36
+ "varname": "tas",
37
+ }
38
+ atmo_ts = OifsGlobalMeanYearMeanTimeseries(init)
39
+ atmo_ts.run(init)
40
+ cube_reg = iris.load_cube(init["dst"])
41
+
42
+ init = {
43
+ "src": ["./tests/testdata/reduced_grid_tas.nc"],
44
+ "dst": str(tmp_path / "test_red.nc"),
45
+ "varname": "tas",
46
+ }
47
+ atmo_ts = OifsGlobalMeanYearMeanTimeseries(init)
48
+ atmo_ts.run(init)
49
+ cube_red = iris.load_cube(init["dst"])
50
+ assert abs(cube_red.data - cube_reg.data) < 1e-3
51
+
52
+
53
+ def test_oifs_global_mean_year_mean_timeseries_wrong_varname(tmp_path):
54
+ init = {
55
+ "src": ["./tests/testdata/TES1_atm_1m_1990_2t.nc"],
56
+ "dst": str(tmp_path / "test.nc"),
57
+ "varname": "sivolu",
58
+ }
59
+ atmo_ts = OifsGlobalMeanYearMeanTimeseries(init)
60
+ pytest.raises(
61
+ scriptengine.exceptions.ScriptEngineTaskArgumentInvalidError,
62
+ atmo_ts.run,
63
+ init,
64
+ )
65
+
66
+
67
+ def test_oifs_global_sum_year_mean_timeseries_working(tmp_path):
68
+ init = {
69
+ "src": ["./tests/testdata/TES1_atm_1m_1990_2t.nc"],
70
+ "dst": str(tmp_path / "test.nc"),
71
+ "varname": "2t",
72
+ }
73
+ atmo_ts = OifsGlobalSumYearMeanTimeseries(init)
74
+ atmo_ts.run(init)
75
+ cube = iris.load_cube(init["dst"])
76
+ assert cube.name() == "2 metre temperature"
77
+ assert cube.units == "K m2"
78
+ assert cube.attributes["title"] is not None
79
+ assert cube.attributes["comment"] is not None
80
+ assert cube.attributes["diagnostic_type"] == "time series"
81
+ assert cube.cell_methods == (
82
+ iris.coords.CellMethod("sum", coords="area"),
83
+ iris.coords.CellMethod("mean", coords="time", intervals="1 year"),
84
+ )
@@ -23,6 +23,22 @@ def test_oifs_year_mean_temporalmap_working(tmp_path):
23
23
  assert cube.attributes["map_type"] == "global atmosphere"
24
24
 
25
25
 
26
+ def test_oifs_year_mean_temporalmap_regular_grid(tmp_path):
27
+ init = {
28
+ "src": ["./tests/testdata/regular_grid_tas.nc"],
29
+ "dst": str(tmp_path / "test.nc"),
30
+ "varname": "tas",
31
+ }
32
+ atmos_time_map = OifsYearMeanTemporalmap(init)
33
+ atmos_time_map.run(init)
34
+ cube = iris.load_cube(init["dst"])
35
+ assert cube.name() == "air_temperature"
36
+ assert cube.attributes["title"] is not None
37
+ assert cube.attributes["comment"] is not None
38
+ assert cube.attributes["diagnostic_type"] == "temporal map"
39
+ assert cube.attributes["map_type"] == "global atmosphere"
40
+
41
+
26
42
  def test_oifs_year_mean_temporalmap_wrong_varname(tmp_path):
27
43
  init = {
28
44
  "src": ["./tests/testdata/TES1_atm_1m_1990_2t.nc"],
@@ -1,131 +0,0 @@
1
- """Processing Task that creates a 2D map of a given extensive ocean quantity."""
2
-
3
- import warnings
4
- from pathlib import Path
5
-
6
- import iris
7
- import numpy as np
8
- from scriptengine.tasks.core import timed_runner
9
-
10
- import helpers.cubes
11
-
12
- from .timeseries import Timeseries
13
-
14
-
15
- class OifsGlobalMeanYearMeanTimeseries(Timeseries):
16
- """OifsGlobalMeanYearMeanTimeseries Processing Task"""
17
-
18
- _required_arguments = ("src", "dst", "varname")
19
-
20
- def __init__(self, arguments=None):
21
- OifsGlobalMeanYearMeanTimeseries.check_arguments(arguments)
22
- super().__init__(
23
- {**arguments, "title": None, "coord_value": None, "data_value": None}
24
- )
25
-
26
- @timed_runner
27
- def run(self, context):
28
- src = self.getarg("src", context)
29
- dst = Path(self.getarg("dst", context))
30
- varname = self.getarg("varname", context)
31
- self.log_info(f"Create time series for atmosphere variable {varname} at {dst}.")
32
- self.log_debug(f"Source file(s): {src}")
33
-
34
- self.check_file_extension(dst)
35
-
36
- oifs_cube = helpers.cubes.load_input_cube(src, varname)
37
-
38
- time_mean_cube = self.compute_time_mean(oifs_cube)
39
-
40
- area_weights = self.compute_area_weights(time_mean_cube)
41
- timeseries_cube = self.compute_spatial_mean(time_mean_cube, area_weights)
42
-
43
- self.set_cell_methods(timeseries_cube)
44
- timeseries_cube = self.adjust_metadata(timeseries_cube, varname)
45
- self.save(timeseries_cube, dst)
46
-
47
- def compute_area_weights(self, cube):
48
- """compute area weights for the reduced gaussian grid"""
49
- self.log_debug("Computing area weights.")
50
- nh_latitudes = np.ma.masked_less(cube.coord("latitude").points, 0)
51
- unique_lats, gridpoints_per_lat = np.unique(nh_latitudes, return_counts=True)
52
- unique_lats, gridpoints_per_lat = unique_lats[0:-1], gridpoints_per_lat[0:-1]
53
- areas = []
54
- last_angle = 0
55
- earth_radius = 6371
56
- for latitude, amount in zip(unique_lats, gridpoints_per_lat):
57
- delta = latitude - last_angle
58
- current_angle = last_angle + 2 * delta
59
- sin_diff = np.sin(np.deg2rad(current_angle)) - np.sin(
60
- np.deg2rad(last_angle)
61
- )
62
- ring_area = 2 * np.pi * earth_radius**2 * sin_diff
63
- grid_area = ring_area / amount
64
- areas.extend([grid_area] * amount)
65
- last_angle = current_angle
66
- areas = np.append(areas[::-1], areas)
67
- area_weights = np.broadcast_to(areas, cube.data.shape)
68
- return area_weights
69
-
70
- def set_cell_methods(self, timeseries_cube):
71
- """add the correct cell methods"""
72
- timeseries_cube.cell_methods = ()
73
- timeseries_cube.add_cell_method(
74
- iris.coords.CellMethod("mean", coords="time", intervals="1 year")
75
- )
76
- timeseries_cube.add_cell_method(iris.coords.CellMethod("mean", coords="area"))
77
-
78
- def compute_time_mean(self, output_cube):
79
- """Apply the temporal average."""
80
- self.log_debug("Averaging over the time coordinate.")
81
- # Remove auxiliary time coordinate before collapsing cube
82
- try:
83
- output_cube.coord("time")
84
- except iris.exceptions.CoordinateNotFoundError:
85
- output_cube.remove_coord(output_cube.coord("time", dim_coords=False))
86
- time_mean_cube = output_cube.collapsed(
87
- "time",
88
- iris.analysis.MEAN,
89
- )
90
- # Promote time from scalar to dimension coordinate
91
- time_mean_cube = iris.util.new_axis(time_mean_cube, "time")
92
- return time_mean_cube
93
-
94
- def compute_spatial_mean(self, time_mean_cube, area_weights):
95
- """Apply the spatial average."""
96
- self.log_debug("Averaging over latitude and longitude.")
97
- # Remove duplicate boundary values before averaging
98
- time_mean_cube.coord("latitude").bounds = time_mean_cube.coord(
99
- "latitude"
100
- ).bounds[:, [0, 1]]
101
- time_mean_cube.coord("longitude").bounds = time_mean_cube.coord(
102
- "longitude"
103
- ).bounds[:, [0, 2]]
104
- with warnings.catch_warnings():
105
- # Suppress warning about insufficient metadata.
106
- warnings.filterwarnings(
107
- "ignore",
108
- "Collapsing a non-contiguous coordinate.",
109
- UserWarning,
110
- )
111
- spatial_mean_cube = time_mean_cube.collapsed(
112
- ["latitude", "longitude"],
113
- iris.analysis.MEAN,
114
- weights=area_weights,
115
- )
116
- return spatial_mean_cube
117
-
118
- def adjust_metadata(self, timeseries_cube, varname: str):
119
- """Do further adjustments to the cube metadata before saving."""
120
- # Add File Metadata
121
- comment = (
122
- f"Global average time series of **{varname}**. "
123
- f"Each data point represents the (spatial and temporal) "
124
- f"average over one year."
125
- )
126
- timeseries_cube = helpers.cubes.set_metadata(
127
- timeseries_cube,
128
- title=f"{timeseries_cube.long_name} (annual mean)",
129
- comment=comment,
130
- )
131
- return helpers.cubes.convert_units(timeseries_cube)
@@ -1,42 +0,0 @@
1
- """Tests for monitoring/oifs_global_mean_year_mean_timeseries.py"""
2
-
3
- import iris
4
- import pytest
5
- import scriptengine.exceptions
6
-
7
- from monitoring.oifs_global_mean_year_mean_timeseries import (
8
- OifsGlobalMeanYearMeanTimeseries,
9
- )
10
-
11
-
12
- def test_oifs_global_mean_year_mean_timeseries_working(tmp_path):
13
- init = {
14
- "src": ["./tests/testdata/TES1_atm_1m_1990_2t.nc"],
15
- "dst": str(tmp_path / "test.nc"),
16
- "varname": "2t",
17
- }
18
- atmo_ts = OifsGlobalMeanYearMeanTimeseries(init)
19
- atmo_ts.run(init)
20
- cube = iris.load_cube(init["dst"])
21
- assert cube.name() == "2 metre temperature"
22
- assert cube.attributes["title"] is not None
23
- assert cube.attributes["comment"] is not None
24
- assert cube.attributes["diagnostic_type"] == "time series"
25
- assert cube.cell_methods == (
26
- iris.coords.CellMethod("mean", coords="time", intervals="1 year"),
27
- iris.coords.CellMethod("mean", coords="area"),
28
- )
29
-
30
-
31
- def test_oifs_global_mean_year_mean_timeseries_wrong_varname(tmp_path):
32
- init = {
33
- "src": ["./tests/testdata/TES1_atm_1m_1990_2t.nc"],
34
- "dst": str(tmp_path / "test.nc"),
35
- "varname": "sivolu",
36
- }
37
- atmo_ts = OifsGlobalMeanYearMeanTimeseries(init)
38
- pytest.raises(
39
- scriptengine.exceptions.ScriptEngineTaskArgumentInvalidError,
40
- atmo_ts.run,
41
- init,
42
- )