Skip to content

Commit bccee2d

Browse files
Add OifsGlobalSumYearMeanTimeseries Processing Task (#123)
* Add new processing task + test * Refactor OIFS Timeseries task - remove unused numpy import - add a class OifsTimeseries to inherit from (analogous to NemoTimeseries) - has methods: _load_input(), _adjust_metadata(), _compute_global_aggregate() that are shared by OifsGlobalMeanYearMeanTimeseries *and* OifsGlobalSumYearMeanTimeseries tasks - align text in added metadata with what we do in NemoTimeseries - use more concise variable names - remove compute_time_mean(): we can just use helpers.cubes.compute_annual_mean() - rename module (cf. nemo_timeseries.py) * Add documentation, update CHANGES * Add new task to entry points * Fix module docstring
1 parent 180af78 commit bccee2d

7 files changed

Lines changed: 193 additions & 113 deletions

File tree

CHANGES.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
Next Release
2+
=============
3+
4+
Features
5+
---------
6+
- Add a new processing task OifsGlobalSumYearMeanTimeseries (PR #123)
7+
8+
19
ScriptEngine Tasks for EC-Earth 0.9.0
210
======================================
311

docs/sphinx/source/tasks/monitoring/openifs-diagnostics.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,31 @@ If it is, e.g., six months long, the task will compute the six month global mean
3434
varname: 2t
3535
dst: "{{mondir}}/2t_oifs_global_mean_year_mean_timeseries.nc"
3636

37+
OifsGlobalSumYearMeanTimeseries
38+
================================
39+
40+
| Diagnostic Type: Time Series
41+
| Mapped to: ``ece.mon.oifs_global_sum_year_mean_timeseries``
42+
43+
This processing task computes the global area integral and temporal average of an atmospheric quantity, resulting in a time series diagnostic.
44+
Units are automatically converted.
45+
46+
To compute an annual mean, the leg has to be one year long.
47+
If it is, e.g., six months long, the task will compute the six month global mean of the input variable.
48+
49+
**Required arguments**
50+
51+
* ``src``: A string containing the path to the OpenIFS output file.
52+
* ``varname``: The name of the variable in the output file. Refer to the `ECMWF parameter database`_ for the meaning of the variables.
53+
* ``dst``: A string ending in ``.nc``. This is where the diagnostic will be saved.
54+
55+
::
56+
57+
- ece.mon.oifs_global_sum_year_mean_timeseries:
58+
src: "{{rundir}}/output/oifs/{{exp_id}}_atm_1m_1990-1990.nc"
59+
varname: tcwv
60+
dst: "{{mondir}}/tcwv_oifs_global_mean_year_mean_timeseries.nc"
61+
3762

3863
OifsAllMeanMap
3964
==============

helpers/cubes.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ def compute_time_weights(monthly_data_cube, cube_shape=None):
9898

9999
def compute_annual_mean(cube):
100100
# Remove auxiliary time coordinate before collapsing cube
101-
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))
102105

103106
annual_mean = cube.collapsed(
104107
"time",

monitoring/oifs_global_mean_year_mean_timeseries.py

Lines changed: 0 additions & 108 deletions
This file was deleted.

monitoring/oifs_timeseries.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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)

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@
5252
"ece.mon.si3_hemis_point_month_mean_temporalmap" = "monitoring.si3_hemis_point_month_mean_temporalmap:Si3HemisPointMonthMeanTemporalmap"
5353
"ece.mon.oifs_all_mean_map" = "monitoring.oifs_all_mean_map:OifsAllMeanMap"
5454
"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"
5657
"ece.mon.presentation.markdown" = "monitoring.markdown:Markdown"
5758
"ece.mon.presentation.redmine" = "monitoring.redmine:Redmine"
5859
"ece.mon.presentation.gitlab" = "monitoring.gitlab:Gitlab"

tests/test_oifs_global_mean_year_mean_timeseries.py renamed to tests/test_oifs_timeseries.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
"""Tests for monitoring/oifs_global_mean_year_mean_timeseries.py"""
1+
"""Tests for monitoring/oifs_timeseries.py"""
22

33
import iris
44
import pytest
55
import scriptengine.exceptions
66

7-
from monitoring.oifs_global_mean_year_mean_timeseries import (
7+
from monitoring.oifs_timeseries import (
88
OifsGlobalMeanYearMeanTimeseries,
9+
OifsGlobalSumYearMeanTimeseries,
910
)
1011

1112

@@ -23,8 +24,8 @@ def test_oifs_global_mean_year_mean_timeseries_working(tmp_path):
2324
assert cube.attributes["comment"] is not None
2425
assert cube.attributes["diagnostic_type"] == "time series"
2526
assert cube.cell_methods == (
26-
iris.coords.CellMethod("mean", coords="time", intervals="1 year"),
2727
iris.coords.CellMethod("mean", coords="area"),
28+
iris.coords.CellMethod("mean", coords="time", intervals="1 year"),
2829
)
2930

3031

@@ -61,3 +62,23 @@ def test_oifs_global_mean_year_mean_timeseries_wrong_varname(tmp_path):
6162
atmo_ts.run,
6263
init,
6364
)
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+
)

0 commit comments

Comments
 (0)