Skip to content

Commit 950cf40

Browse files
authored
Merge pull request #677 from ESMValGroup/make_mm_yearly
Make multimodel work correctly with yearly data
2 parents 4a8776f + 9eb8de5 commit 950cf40

File tree

2 files changed

+60
-9
lines changed

2 files changed

+60
-9
lines changed

esmvalcore/preprocessor/_multimodel.py

+19-5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import iris
2121
import numpy as np
2222

23+
from ._time import regrid_time
24+
2325
logger = logging.getLogger(__name__)
2426

2527

@@ -109,10 +111,12 @@ def _put_in_cube(template_cube, cube_data, statistic, t_axis):
109111
if t_axis is None:
110112
times = template_cube.coord('time')
111113
else:
114+
unit_name = template_cube.coord('time').units.name
115+
tunits = cf_units.Unit(unit_name, calendar="standard")
112116
times = iris.coords.DimCoord(
113117
t_axis,
114118
standard_name='time',
115-
units=template_cube.coord('time').units)
119+
units=tunits)
116120

117121
coord_names = [c.long_name for c in template_cube.coords()]
118122
coord_names.extend([c.standard_name for c in template_cube.coords()])
@@ -164,25 +168,35 @@ def _put_in_cube(template_cube, cube_data, statistic, t_axis):
164168

165169
def _datetime_to_int_days(cube):
166170
"""Return list of int(days) converted from cube datetime cells."""
171+
cube = _align_yearly_axes(cube)
167172
time_cells = [cell.point for cell in cube.coord('time').cells()]
168-
time_unit = cube.coord('time').units.name
169-
time_offset = _get_time_offset(time_unit)
170173

171174
# extract date info
172175
real_dates = []
173176
for date_obj in time_cells:
174177
# real_date resets the actual data point day
175178
# to the 1st of the month so that there are no
176179
# wrong overlap indices
177-
# NOTE: this workaround is good only
178-
# for monthly data
179180
real_date = datetime(date_obj.year, date_obj.month, 1, 0, 0, 0)
180181
real_dates.append(real_date)
181182

183+
# get the number of days starting from the reference unit
184+
time_unit = cube.coord('time').units.name
185+
time_offset = _get_time_offset(time_unit)
182186
days = [(date_obj - time_offset).days for date_obj in real_dates]
187+
183188
return days
184189

185190

191+
def _align_yearly_axes(cube):
192+
"""Perform a time-regridding operation to align time axes for yr data."""
193+
years = [cell.point.year for cell in cube.coord('time').cells()]
194+
# be extra sure that the first point is not in the previous year
195+
if 0 not in np.diff(years):
196+
return regrid_time(cube, 'yr')
197+
return cube
198+
199+
186200
def _get_overlap(cubes):
187201
"""
188202
Get discrete time overlaps.

tests/unit/preprocessor/_multimodel/test_multimodel.py

+41-4
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,22 @@ def setUp(self):
4848
units=Unit(
4949
'days since 1950-01-01',
5050
calendar='gregorian'))
51-
51+
yr_time = iris.coords.DimCoord([15, 410],
52+
standard_name='time',
53+
bounds=[[1., 30.], [395., 425.]],
54+
units=Unit(
55+
'days since 1950-01-01',
56+
calendar='gregorian'))
57+
yr_time2 = iris.coords.DimCoord([1., 367., 733., 1099.],
58+
standard_name='time',
59+
bounds=[
60+
[0.5, 1.5],
61+
[366, 368],
62+
[732, 734],
63+
[1098, 1100], ],
64+
units=Unit(
65+
'days since 1950-01-01',
66+
calendar='gregorian'))
5267
zcoord = iris.coords.DimCoord([0.5, 5., 50.],
5368
standard_name='air_pressure',
5469
long_name='air_pressure',
@@ -75,6 +90,14 @@ def setUp(self):
7590
coords_spec5 = [(time2, 0), (zcoord, 1), (lats, 2), (lons, 3)]
7691
self.cube2 = iris.cube.Cube(data3, dim_coords_and_dims=coords_spec5)
7792

93+
coords_spec4_yr = [(yr_time, 0), (zcoord, 1), (lats, 2), (lons, 3)]
94+
self.cube1_yr = iris.cube.Cube(data2,
95+
dim_coords_and_dims=coords_spec4_yr)
96+
97+
coords_spec5_yr = [(yr_time2, 0), (zcoord, 1), (lats, 2), (lons, 3)]
98+
self.cube2_yr = iris.cube.Cube(data3,
99+
dim_coords_and_dims=coords_spec5_yr)
100+
78101
def test_get_time_offset(self):
79102
"""Test time unit."""
80103
result = _get_time_offset("days since 1950-01-01")
@@ -91,20 +114,34 @@ def test_compute_statistic(self):
91114
self.assert_array_equal(stat_mean, expected_mean)
92115
self.assert_array_equal(stat_median, expected_median)
93116

94-
def test_compute_full_statistic_cube(self):
117+
def test_compute_full_statistic_mon_cube(self):
95118
data = [self.cube1, self.cube2]
96119
stats = multi_model_statistics(data, 'full', ['mean'])
97120
expected_full_mean = np.ma.ones((2, 3, 2, 2))
98121
expected_full_mean.mask = np.zeros((2, 3, 2, 2))
99122
expected_full_mean.mask[1] = True
100123
self.assert_array_equal(stats['mean'].data, expected_full_mean)
101124

102-
def test_compute_overlap_statistic_cube(self):
125+
def test_compute_full_statistic_yr_cube(self):
126+
data = [self.cube1_yr, self.cube2_yr]
127+
stats = multi_model_statistics(data, 'full', ['mean'])
128+
expected_full_mean = np.ma.ones((4, 3, 2, 2))
129+
expected_full_mean.mask = np.zeros((4, 3, 2, 2))
130+
expected_full_mean.mask[2:4] = True
131+
self.assert_array_equal(stats['mean'].data, expected_full_mean)
132+
133+
def test_compute_overlap_statistic_mon_cube(self):
103134
data = [self.cube1, self.cube1]
104135
stats = multi_model_statistics(data, 'overlap', ['mean'])
105136
expected_ovlap_mean = np.ma.ones((2, 3, 2, 2))
106137
self.assert_array_equal(stats['mean'].data, expected_ovlap_mean)
107138

139+
def test_compute_overlap_statistic_yr_cube(self):
140+
data = [self.cube1_yr, self.cube1_yr]
141+
stats = multi_model_statistics(data, 'overlap', ['mean'])
142+
expected_ovlap_mean = np.ma.ones((2, 3, 2, 2))
143+
self.assert_array_equal(stats['mean'].data, expected_ovlap_mean)
144+
108145
def test_compute_std(self):
109146
"""Test statistic."""
110147
data = [self.cube1.data[0], self.cube2.data[0] * 2]
@@ -134,7 +171,7 @@ def test_put_in_cube(self):
134171
stat_cube = _put_in_cube(self.cube1, cube_data, "mean", t_axis=None)
135172
self.assert_array_equal(stat_cube.data, self.cube1.data)
136173

137-
def test_datetime_to_int_days(self):
174+
def test_datetime_to_int_days_no_overlap(self):
138175
"""Test _datetime_to_int_days."""
139176
computed_dats = _datetime_to_int_days(self.cube1)
140177
expected_dats = [0, 31]

0 commit comments

Comments
 (0)