Skip to content

Commit f694d14

Browse files
authored
Refactored native model fixes by adding common base class NativeDatasetFix (#1694)
* Created bass class for native dataset fixes * Added tests for NativeDatasetFix * Added doc for native dataset base class * Updated doc * Fixed typo in doc
1 parent 76c9ffa commit f694d14

File tree

9 files changed

+999
-425
lines changed

9 files changed

+999
-425
lines changed

doc/develop/fixing_data.rst

+5
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,11 @@ For example, a ``native6`` dataset fix for ERA5 is located `here
450450
and the ``ICON`` fix is located `here
451451
<https://github.com/ESMValGroup/ESMValCore/blob/main/esmvalcore/cmor/_fixes/icon/icon.py>`__.
452452
453+
ESMValTool also provides a base class ``NativeDatasetFix`` that provides
454+
convenient functions useful for all native dataset fixes.
455+
An example for its usage can be found `here
456+
<https://github.com/ESMValGroup/ESMValCore/blob/main/esmvalcore/cmor/_fixes/icon/_base_fixes.py>`__.
457+
453458
.. _add_new_fix_native_datasets_extra_facets:
454459
455460
Extra facets for native datasets

esmvalcore/cmor/_fixes/emac/_base_fixes.py

+14-12
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,32 @@
55
from iris import NameConstraint
66
from iris.exceptions import ConstraintMismatchError
77

8-
from ..fix import Fix
8+
from ..native_datasets import NativeDatasetFix
99

1010
logger = logging.getLogger(__name__)
1111

1212

13-
class EmacFix(Fix):
13+
class EmacFix(NativeDatasetFix):
1414
"""Base class for all EMAC fixes."""
1515

16-
def get_cube(self, cubes, var_names=None):
16+
def get_cube(self, cubes, var_name=None):
1717
"""Extract single cube."""
18-
# If no var_names given, use the CMOR short_name
19-
if var_names is None:
20-
var_names = self.extra_facets.get('raw_name',
21-
self.vardef.short_name)
18+
# If no var_name given, use the CMOR short_name
19+
if var_name is None:
20+
var_name = self.extra_facets.get('raw_name',
21+
self.vardef.short_name)
2222

23-
# Convert var_names to list if only a single var_name is given
24-
if isinstance(var_names, str):
25-
var_names = [var_names]
23+
# Convert to list if only a single var_name is given
24+
if isinstance(var_name, str):
25+
var_names = [var_name]
26+
else:
27+
var_names = var_name
2628

2729
# Try to extract the variable (prioritize variables as given by the
2830
# list)
29-
for var_name in var_names:
31+
for v_name in var_names:
3032
try:
31-
return cubes.extract_cube(NameConstraint(var_name=var_name))
33+
return cubes.extract_cube(NameConstraint(var_name=v_name))
3234
except ConstraintMismatchError:
3335
pass
3436

esmvalcore/cmor/_fixes/emac/emac.py

+41-147
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,20 @@
2323
from netCDF4 import Dataset
2424
from scipy import constants
2525

26-
from ..shared import (
27-
add_aux_coords_from_cubes,
28-
add_scalar_height_coord,
29-
add_scalar_lambda550nm_coord,
30-
add_scalar_typesi_coord,
31-
)
26+
from ..shared import add_aux_coords_from_cubes
3227
from ._base_fixes import EmacFix, NegateData, SetUnitsTo1
3328

3429
logger = logging.getLogger(__name__)
3530

3631

37-
INVALID_UNITS = {
38-
'kg/m**2s': 'kg m-2 s-1',
39-
}
40-
41-
4232
class AllVars(EmacFix):
4333
"""Fixes for all variables."""
4434

35+
# Dictionary to map invalid units in the data to valid entries
36+
INVALID_UNITS = {
37+
'kg/m**2s': 'kg m-2 s-1',
38+
}
39+
4540
def fix_file(self, filepath, output_dir):
4641
"""Fix file.
4742
@@ -69,9 +64,10 @@ def fix_metadata(self, cubes):
6964
"""Fix metadata."""
7065
cube = self.get_cube(cubes)
7166

72-
# Fix time
73-
if 'time' in self.vardef.dimensions:
74-
self._fix_time(cube)
67+
# Fix time, latitude, and longitude coordinates
68+
self.fix_regular_time(cube)
69+
self.fix_regular_lat(cube)
70+
self.fix_regular_lon(cube)
7571

7672
# Fix regular pressure levels (considers plev19, plev39, etc.)
7773
for dim_name in self.vardef.dimensions:
@@ -83,38 +79,14 @@ def fix_metadata(self, cubes):
8379
if 'alevel' in self.vardef.dimensions:
8480
cube = self._fix_alevel(cube, cubes)
8581

86-
# Fix latitude
87-
if 'latitude' in self.vardef.dimensions:
88-
self._fix_lat(cube)
89-
90-
# Fix longitude
91-
if 'longitude' in self.vardef.dimensions:
92-
self._fix_lon(cube)
93-
9482
# Fix scalar coordinates
95-
self._fix_scalar_coords(cube)
83+
self.fix_scalar_coords(cube)
9684

9785
# Fix metadata of variable
98-
self._fix_var_metadata(cube)
86+
self.fix_var_metadata(cube)
9987

10088
return CubeList([cube])
10189

102-
@staticmethod
103-
def _fix_time(cube):
104-
"""Fix time coordinate of cube."""
105-
time_coord = cube.coord('time')
106-
time_coord.var_name = 'time'
107-
time_coord.standard_name = 'time'
108-
time_coord.long_name = 'time'
109-
110-
# Add bounds if possible (not possible if cube only contains single
111-
# time point)
112-
if not time_coord.has_bounds():
113-
try:
114-
time_coord.guess_bounds()
115-
except ValueError:
116-
pass
117-
11890
def _fix_plev(self, cube):
11991
"""Fix regular pressure level coordinate of cube."""
12092
for coord in cube.coords():
@@ -125,11 +97,7 @@ def _fix_plev(self, cube):
12597
if not coord.units.is_convertible('Pa'):
12698
continue
12799

128-
coord.var_name = 'plev'
129-
coord.standard_name = 'air_pressure'
130-
coord.long_name = 'pressure'
131-
coord.convert_units('Pa')
132-
coord.attributes['positive'] = 'down'
100+
self.fix_plev_metadata(cube, coord)
133101

134102
return
135103

@@ -227,80 +195,6 @@ def _fix_alevel(cube, cubes):
227195

228196
return cube
229197

230-
@staticmethod
231-
def _fix_lat(cube):
232-
"""Fix latitude coordinate of cube."""
233-
lat = cube.coord('latitude')
234-
lat.var_name = 'lat'
235-
lat.standard_name = 'latitude'
236-
lat.long_name = 'latitude'
237-
lat.convert_units('degrees_north')
238-
239-
# Add bounds if possible (not possible if cube only contains single
240-
# lat point)
241-
if not lat.has_bounds():
242-
try:
243-
lat.guess_bounds()
244-
except ValueError:
245-
pass
246-
247-
@staticmethod
248-
def _fix_lon(cube):
249-
"""Fix longitude coordinate of cube."""
250-
lon = cube.coord('longitude')
251-
lon.var_name = 'lon'
252-
lon.standard_name = 'longitude'
253-
lon.long_name = 'longitude'
254-
lon.convert_units('degrees_east')
255-
256-
# Add bounds if possible (not possible if cube only contains single
257-
# lon point)
258-
if not lon.has_bounds():
259-
try:
260-
lon.guess_bounds()
261-
except ValueError:
262-
pass
263-
264-
def _fix_scalar_coords(self, cube):
265-
"""Fix scalar coordinates."""
266-
if 'height2m' in self.vardef.dimensions:
267-
add_scalar_height_coord(cube, 2.0)
268-
if 'height10m' in self.vardef.dimensions:
269-
add_scalar_height_coord(cube, 10.0)
270-
if 'lambda550nm' in self.vardef.dimensions:
271-
add_scalar_lambda550nm_coord(cube)
272-
if 'typesi' in self.vardef.dimensions:
273-
add_scalar_typesi_coord(cube, 'sea_ice')
274-
275-
def _fix_var_metadata(self, cube):
276-
"""Fix metadata of variable."""
277-
if self.vardef.standard_name == '':
278-
cube.standard_name = None
279-
else:
280-
cube.standard_name = self.vardef.standard_name
281-
cube.var_name = self.vardef.short_name
282-
cube.long_name = self.vardef.long_name
283-
284-
# Fix units
285-
if 'invalid_units' in cube.attributes:
286-
invalid_units = cube.attributes.pop('invalid_units')
287-
new_units = INVALID_UNITS.get(
288-
invalid_units,
289-
invalid_units.replace('**', '^'),
290-
)
291-
try:
292-
cube.units = new_units
293-
except ValueError as exc:
294-
raise ValueError(
295-
f"Failed to fix invalid units '{invalid_units}' for "
296-
f"variable '{self.vardef.short_name}'") from exc
297-
if cube.units != self.vardef.units:
298-
cube.convert_units(self.vardef.units)
299-
300-
# Fix attributes
301-
if self.vardef.positive != '':
302-
cube.attributes['positive'] = self.vardef.positive
303-
304198

305199
Cl = SetUnitsTo1
306200

@@ -314,8 +208,8 @@ class Clwvi(EmacFix):
314208
def fix_metadata(self, cubes):
315209
"""Fix metadata."""
316210
cube = (
317-
self.get_cube(cubes, var_names=['xlvi_cav', 'xlvi_ave']) +
318-
self.get_cube(cubes, var_names=['xivi_cav', 'xivi_ave'])
211+
self.get_cube(cubes, var_name=['xlvi_cav', 'xlvi_ave']) +
212+
self.get_cube(cubes, var_name=['xivi_cav', 'xivi_ave'])
319213
)
320214
cube.var_name = self.vardef.short_name
321215
return CubeList([cube])
@@ -351,9 +245,9 @@ class Pr(EmacFix):
351245
def fix_metadata(self, cubes):
352246
"""Fix metadata."""
353247
cube = (
354-
self.get_cube(cubes, var_names=['aprl_cav', 'aprl_ave']) +
355-
self.get_cube(cubes, var_names=['aprc_cav', 'aprc_ave']) +
356-
self.get_cube(cubes, var_names=['aprs_cav', 'aprs_ave'])
248+
self.get_cube(cubes, var_name=['aprl_cav', 'aprl_ave']) +
249+
self.get_cube(cubes, var_name=['aprc_cav', 'aprc_ave']) +
250+
self.get_cube(cubes, var_name=['aprs_cav', 'aprs_ave'])
357251
)
358252
cube.var_name = self.vardef.short_name
359253
return CubeList([cube])
@@ -365,8 +259,8 @@ class Rlds(EmacFix):
365259
def fix_metadata(self, cubes):
366260
"""Fix metadata."""
367261
cube = (
368-
self.get_cube(cubes, var_names=['flxtbot_cav', 'flxtbot_ave']) -
369-
self.get_cube(cubes, var_names=['tradsu_cav', 'tradsu_ave'])
262+
self.get_cube(cubes, var_name=['flxtbot_cav', 'flxtbot_ave']) -
263+
self.get_cube(cubes, var_name=['tradsu_cav', 'tradsu_ave'])
370264
)
371265
cube.var_name = self.vardef.short_name
372266
return CubeList([cube])
@@ -387,8 +281,8 @@ class Rsds(EmacFix):
387281
def fix_metadata(self, cubes):
388282
"""Fix metadata."""
389283
cube = (
390-
self.get_cube(cubes, var_names=['flxsbot_cav', 'flxsbot_ave']) -
391-
self.get_cube(cubes, var_names=['sradsu_cav', 'sradsu_ave'])
284+
self.get_cube(cubes, var_name=['flxsbot_cav', 'flxsbot_ave']) -
285+
self.get_cube(cubes, var_name=['sradsu_cav', 'sradsu_ave'])
392286
)
393287
cube.var_name = self.vardef.short_name
394288
return CubeList([cube])
@@ -400,8 +294,8 @@ class Rsdt(EmacFix):
400294
def fix_metadata(self, cubes):
401295
"""Fix metadata."""
402296
cube = (
403-
self.get_cube(cubes, var_names=['flxstop_cav', 'flxstop_ave']) -
404-
self.get_cube(cubes, var_names=['srad0u_cav', 'srad0u_ave'])
297+
self.get_cube(cubes, var_name=['flxstop_cav', 'flxstop_ave']) -
298+
self.get_cube(cubes, var_name=['srad0u_cav', 'srad0u_ave'])
405299
)
406300
cube.var_name = self.vardef.short_name
407301
return CubeList([cube])
@@ -422,8 +316,8 @@ class Rtmt(EmacFix):
422316
def fix_metadata(self, cubes):
423317
"""Fix metadata."""
424318
cube = (
425-
self.get_cube(cubes, var_names=['flxttop_cav', 'flxttop_ave']) +
426-
self.get_cube(cubes, var_names=['flxstop_cav', 'flxstop_ave'])
319+
self.get_cube(cubes, var_name=['flxttop_cav', 'flxttop_ave']) +
320+
self.get_cube(cubes, var_name=['flxstop_cav', 'flxstop_ave'])
427321
)
428322
cube.var_name = self.vardef.short_name
429323
return CubeList([cube])
@@ -486,10 +380,10 @@ class MP_BC_tot(EmacFix): # noqa: N801
486380
def fix_metadata(self, cubes):
487381
"""Fix metadata."""
488382
cube = (
489-
self.get_cube(cubes, var_names=['MP_BC_ki_cav', 'MP_BC_ki_ave']) +
490-
self.get_cube(cubes, var_names=['MP_BC_ks_cav', 'MP_BC_ks_ave']) +
491-
self.get_cube(cubes, var_names=['MP_BC_as_cav', 'MP_BC_as_ave']) +
492-
self.get_cube(cubes, var_names=['MP_BC_cs_cav', 'MP_BC_cs_ave'])
383+
self.get_cube(cubes, var_name=['MP_BC_ki_cav', 'MP_BC_ki_ave']) +
384+
self.get_cube(cubes, var_name=['MP_BC_ks_cav', 'MP_BC_ks_ave']) +
385+
self.get_cube(cubes, var_name=['MP_BC_as_cav', 'MP_BC_as_ave']) +
386+
self.get_cube(cubes, var_name=['MP_BC_cs_cav', 'MP_BC_cs_ave'])
493387
)
494388
cube.var_name = self.vardef.short_name
495389
return CubeList([cube])
@@ -501,10 +395,10 @@ class MP_DU_tot(EmacFix): # noqa: N801
501395
def fix_metadata(self, cubes):
502396
"""Fix metadata."""
503397
cube = (
504-
self.get_cube(cubes, var_names=['MP_DU_ai_cav', 'MP_DU_ai_ave']) +
505-
self.get_cube(cubes, var_names=['MP_DU_as_cav', 'MP_DU_as_ave']) +
506-
self.get_cube(cubes, var_names=['MP_DU_ci_cav', 'MP_DU_ci_ave']) +
507-
self.get_cube(cubes, var_names=['MP_DU_cs_cav', 'MP_DU_cs_ave'])
398+
self.get_cube(cubes, var_name=['MP_DU_ai_cav', 'MP_DU_ai_ave']) +
399+
self.get_cube(cubes, var_name=['MP_DU_as_cav', 'MP_DU_as_ave']) +
400+
self.get_cube(cubes, var_name=['MP_DU_ci_cav', 'MP_DU_ci_ave']) +
401+
self.get_cube(cubes, var_name=['MP_DU_cs_cav', 'MP_DU_cs_ave'])
508402
)
509403
cube.var_name = self.vardef.short_name
510404
return CubeList([cube])
@@ -517,13 +411,13 @@ def fix_metadata(self, cubes):
517411
"""Fix metadata."""
518412
cube = (
519413
self.get_cube(
520-
cubes, var_names=['MP_SO4mm_ns_cav', 'MP_SO4mm_ns_ave']) +
414+
cubes, var_name=['MP_SO4mm_ns_cav', 'MP_SO4mm_ns_ave']) +
521415
self.get_cube(
522-
cubes, var_names=['MP_SO4mm_ks_cav', 'MP_SO4mm_ks_ave']) +
416+
cubes, var_name=['MP_SO4mm_ks_cav', 'MP_SO4mm_ks_ave']) +
523417
self.get_cube(
524-
cubes, var_names=['MP_SO4mm_as_cav', 'MP_SO4mm_as_ave']) +
418+
cubes, var_name=['MP_SO4mm_as_cav', 'MP_SO4mm_as_ave']) +
525419
self.get_cube(
526-
cubes, var_names=['MP_SO4mm_cs_cav', 'MP_SO4mm_cs_ave'])
420+
cubes, var_name=['MP_SO4mm_cs_cav', 'MP_SO4mm_cs_ave'])
527421
)
528422
cube.var_name = self.vardef.short_name
529423
return CubeList([cube])
@@ -535,9 +429,9 @@ class MP_SS_tot(EmacFix): # noqa: N801
535429
def fix_metadata(self, cubes):
536430
"""Fix metadata."""
537431
cube = (
538-
self.get_cube(cubes, var_names=['MP_SS_ks_cav', 'MP_SS_ks_ave']) +
539-
self.get_cube(cubes, var_names=['MP_SS_as_cav', 'MP_SS_as_ave']) +
540-
self.get_cube(cubes, var_names=['MP_SS_cs_cav', 'MP_SS_cs_ave'])
432+
self.get_cube(cubes, var_name=['MP_SS_ks_cav', 'MP_SS_ks_ave']) +
433+
self.get_cube(cubes, var_name=['MP_SS_as_cav', 'MP_SS_as_ave']) +
434+
self.get_cube(cubes, var_name=['MP_SS_cs_cav', 'MP_SS_cs_ave'])
541435
)
542436
cube.var_name = self.vardef.short_name
543437
return CubeList([cube])

0 commit comments

Comments
 (0)