Skip to content

Commit d43f268

Browse files
schlunmavaleriupredoi
authored andcommitted
Fix units after derivation (#754)
* Added check for units at the end of derivation function * Fixed tests for variable derivation interface * Adapted several derivation scripts * Adapted sm derivation script for CMIP6 * Fixed derivation of vegfrac and ohc * Fixed CMOR checker when coordinate standard_name is empty
1 parent 435fae2 commit d43f268

File tree

11 files changed

+217
-43
lines changed

11 files changed

+217
-43
lines changed

doc/changelog.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ Bug fixes
1212
- Fix diagnostic filter (`#713 <https://github.com/ESMValGroup/ESMValCore/pull/713>`__) `Javier Vegas-Regidor <https://github.com/jvegasbsc>`__
1313
- Set unit=1 if anomalies are standardized (`#727 <https://github.com/ESMValGroup/ESMValCore/pull/727>`__) `bascrezee <https://github.com/bascrezee>`__
1414
- Fix crash for FGOALS-g2 variables without longitude coordinate (`#729 <https://github.com/ESMValGroup/ESMValCore/pull/729>`__) `Bouwe Andela <https://github.com/bouweandela>`__
15-
- Improve variable alias managament (`#595 <https://github.com/ESMValGroup/ESMValCore/pull/595>`__) `Javier Vegas-Regidor <https://github.com/jvegasbsc>`__
15+
- Improve variable alias management (`#595 <https://github.com/ESMValGroup/ESMValCore/pull/595>`__) `Javier Vegas-Regidor <https://github.com/jvegasbsc>`__
1616
- Fix area_statistics fx files loading (`#798 <https://github.com/ESMValGroup/ESMValCore/pull/798>`__) `Javier Vegas-Regidor <https://github.com/jvegasbsc>`__
17+
- Fix units after derivation (`#754 <https://github.com/ESMValGroup/ESMValCore/pull/754>`__) `Manuel Schlund <https://github.com/schlunma>`__
1718

1819
Documentation
1920
~~~~~~~~~~~~~

esmvalcore/cmor/check.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,10 @@ def _check_dim_names(self):
338338
else:
339339
try:
340340
cube_coord = self._cube.coord(var_name=coordinate.out_name)
341-
if cube_coord.standard_name != coordinate.standard_name:
341+
if (cube_coord.standard_name is None and
342+
coordinate.standard_name == ''):
343+
pass
344+
elif cube_coord.standard_name != coordinate.standard_name:
342345
self.report_critical(
343346
self._attr_msg,
344347
coordinate.out_name,

esmvalcore/preprocessor/_derive/__init__.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,24 @@ def derive(cubes, short_name, long_name, units, standard_name=None):
103103
cube.var_name = short_name
104104
cube.standard_name = standard_name if standard_name else None
105105
cube.long_name = long_name
106-
cube.units = units
107106
for temp in cubes:
108107
if 'source_file' in temp.attributes:
109108
cube.attributes['source_file'] = temp.attributes['source_file']
110109

110+
# Check/convert units
111+
if cube.units is None or cube.units == units:
112+
cube.units = units
113+
elif cube.units.is_no_unit() or cube.units.is_unknown():
114+
logger.warning(
115+
"Units of cube after executing derivation script of '%s' are "
116+
"'%s', automatically setting them to '%s'. This might lead to "
117+
"incorrect data", short_name, cube.units, units)
118+
cube.units = units
119+
elif cube.units.is_convertible(units):
120+
cube.convert_units(units)
121+
else:
122+
raise ValueError(
123+
f"Units '{cube.units}' after executing derivation script of "
124+
f"'{short_name}' cannot be converted to target units '{units}'")
125+
111126
return cube

esmvalcore/preprocessor/_derive/_shared.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
import iris
66

7+
from esmvalcore.iris_helpers import var_name_constraint
8+
79
logger = logging.getLogger(__name__)
810

911

1012
def cloud_area_fraction(cubes, tau_constraint, plev_constraint):
1113
"""Calculate cloud area fraction for different parameters."""
12-
clisccp_cube = cubes.extract_strict(
13-
iris.Constraint(name='isccp_cloud_area_fraction'))
14+
clisccp_cube = cubes.extract_strict(var_name_constraint('clisccp'))
1415
new_cube = clisccp_cube
1516
new_cube = new_cube.extract(tau_constraint & plev_constraint)
1617
coord_names = [

esmvalcore/preprocessor/_derive/lvp.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
- weig_ka
55
66
"""
7-
from iris import Constraint
7+
8+
from esmvalcore.iris_helpers import var_name_constraint
89

910
from ._baseclass import DerivedVariableBase
1011

@@ -31,11 +32,9 @@ def required(project):
3132
@staticmethod
3233
def calculate(cubes):
3334
"""Compute Latent Heat Release from Precipitation."""
34-
hfls_cube = cubes.extract_strict(
35-
Constraint(name='surface_upward_latent_heat_flux'))
36-
pr_cube = cubes.extract_strict(Constraint(name='precipitation_flux'))
37-
evspsbl_cube = cubes.extract_strict(
38-
Constraint(name='water_evaporation_flux'))
35+
hfls_cube = cubes.extract_strict(var_name_constraint('hfls'))
36+
pr_cube = cubes.extract_strict(var_name_constraint('pr'))
37+
evspsbl_cube = cubes.extract_strict(var_name_constraint('evspsbl'))
3938

4039
lvp_cube = hfls_cube * (pr_cube / evspsbl_cube)
4140

esmvalcore/preprocessor/_derive/ohc.py

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def calculate(cubes):
6262
# 2. multiply with each other and with cprho0
6363
# some juggling with coordinates needed since Iris is very
6464
# restrictive in this regard
65+
cube.convert_units('K')
6566
try:
6667
t_coord_dims = cube.coord_dims('time')
6768
except iris.exceptions.CoordinateNotFoundError:

esmvalcore/preprocessor/_derive/sispeed.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@ class DerivedVariable(DerivedVariableBase):
1616
@staticmethod
1717
def required(project):
1818
"""Declare the variables needed for derivation."""
19-
required = [{
20-
'short_name': 'usi',
21-
}, {
22-
'short_name': 'vsi',
23-
}]
19+
if project == 'CMIP6':
20+
required = [{'short_name': 'siu'}, {'short_name': 'siv'}]
21+
else:
22+
required = [{'short_name': 'usi'}, {'short_name': 'vsi'}]
2423
return required
2524

2625
@staticmethod

esmvalcore/preprocessor/_derive/sm.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import cf_units
44
import numpy as np
5-
from iris import Constraint
5+
6+
from esmvalcore.iris_helpers import var_name_constraint
67

78
from ._baseclass import DerivedVariableBase
89

@@ -27,8 +28,7 @@ def calculate(cubes):
2728
20 deg C).
2829
2930
"""
30-
mrsos_cube = cubes.extract_strict(
31-
Constraint(name='moisture_content_of_soil_layer'))
31+
mrsos_cube = cubes.extract_strict(var_name_constraint('mrsos'))
3232

3333
depth = mrsos_cube.coord('depth').bounds.astype(np.float32)
3434
layer_thickness = depth[..., 1] - depth[..., 0]

esmvalcore/preprocessor/_derive/toz.py

+5-8
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,10 @@ class DerivedVariable(DerivedVariableBase):
2626
@staticmethod
2727
def required(project):
2828
"""Declare the variables needed for derivation."""
29-
required = [
30-
{
31-
'short_name': 'tro3'
32-
},
33-
{
34-
'short_name': 'ps'
35-
},
36-
]
29+
if project == 'CMIP6':
30+
required = [{'short_name': 'o3'}, {'short_name': 'ps'}]
31+
else:
32+
required = [{'short_name': 'tro3'}, {'short_name': 'ps'}]
3733
return required
3834

3935
@staticmethod
@@ -64,6 +60,7 @@ def calculate(cubes):
6460
toz_cube = toz_cube / MW_O3 * AVOGADRO_CONST
6561
toz_cube.units = toz_cube.units / MW_O3_UNIT * AVOGADRO_CONST_UNIT
6662
toz_cube.convert_units(DOBSON_UNIT)
63+
toz_cube.units = 'DU'
6764

6865
return toz_cube
6966

+33-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
"""Derivation of variable `vegFrac`."""
22

3+
import dask.array as da
34
import iris
5+
from esmvalcore.iris_helpers import var_name_constraint
6+
7+
from .._regrid import regrid
8+
49
from ._baseclass import DerivedVariableBase
510

611

@@ -10,16 +15,38 @@ class DerivedVariable(DerivedVariableBase):
1015
@staticmethod
1116
def required(project):
1217
"""Declare the variables needed for derivation."""
13-
required = [{
14-
'short_name': 'baresoilFrac',
15-
}]
18+
required = [
19+
{'short_name': 'baresoilFrac'},
20+
{'short_name': 'residualFrac'},
21+
{'short_name': 'sftlf', 'mip': 'fx'},
22+
]
1623
return required
1724

1825
@staticmethod
1926
def calculate(cubes):
2027
"""Compute vegetation fraction from bare soil fraction."""
21-
baresoilfrac_cube = cubes.extract_strict(
22-
iris.Constraint(name='area_fraction'))
28+
baresoilfrac_cube = cubes.extract_strict(var_name_constraint(
29+
'baresoilFrac'))
30+
residualfrac_cube = cubes.extract_strict(var_name_constraint(
31+
'residualFrac'))
32+
sftlf_cube = cubes.extract_strict(var_name_constraint('sftlf'))
33+
34+
# Add time dimension to sftlf
35+
target_shape_sftlf = (baresoilfrac_cube.shape[0], *sftlf_cube.shape)
36+
sftlf_data = iris.util.broadcast_to_shape(sftlf_cube.data,
37+
target_shape_sftlf, (1, 2))
38+
sftlf_cube = baresoilfrac_cube.copy(sftlf_data)
39+
sftlf_cube.data = sftlf_cube.lazy_data()
40+
41+
# Regrid sftlf if necessary and adapt mask
42+
if sftlf_cube.shape != baresoilfrac_cube.shape:
43+
sftlf_cube = regrid(sftlf_cube, baresoilfrac_cube, 'linear')
44+
sftlf_cube.data = da.ma.masked_array(
45+
sftlf_cube.core_data(),
46+
mask=da.ma.getmaskarray(baresoilfrac_cube.core_data()))
2347

24-
baresoilfrac_cube.data = 1. - baresoilfrac_cube.core_data()
48+
# Calculate vegetation fraction
49+
baresoilfrac_cube.data = (sftlf_cube.core_data() -
50+
baresoilfrac_cube.core_data() -
51+
residualfrac_cube.core_data())
2552
return baresoilfrac_cube

0 commit comments

Comments
 (0)