From dcb0952b87a4c0d7f1af54efbd73cb26053b1cac Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Mon, 2 Mar 2020 10:47:46 +0100 Subject: [PATCH 1/9] Added various fixes for CMIP5 and CMIMP6 models --- esmvalcore/cmor/_fixes/cmip5/access1_0.py | 38 +- esmvalcore/cmor/_fixes/cmip5/access1_3.py | 17 +- esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1.py | 84 +- esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1_m.py | 62 +- esmvalcore/cmor/_fixes/cmip5/canesm2.py | 7 +- esmvalcore/cmor/_fixes/cmip5/ccsm4.py | 15 +- esmvalcore/cmor/_fixes/cmip5/csiro_mk3_6_0.py | 6 + esmvalcore/cmor/_fixes/cmip5/fgoals_g2.py | 45 +- esmvalcore/cmor/_fixes/cmip5/fgoals_s2.py | 39 + esmvalcore/cmor/_fixes/cmip5/giss_e2_h.py | 6 + esmvalcore/cmor/_fixes/cmip5/giss_e2_r.py | 6 + esmvalcore/cmor/_fixes/cmip5/hadgem2_es.py | 18 +- esmvalcore/cmor/_fixes/cmip5/inmcm4.py | 12 +- esmvalcore/cmor/_fixes/cmip5/ipsl_cm5a_lr.py | 6 + esmvalcore/cmor/_fixes/cmip5/ipsl_cm5a_mr.py | 6 + esmvalcore/cmor/_fixes/cmip5/ipsl_cm5b_lr.py | 6 + esmvalcore/cmor/_fixes/cmip5/miroc5.py | 14 + esmvalcore/cmor/_fixes/cmip5/miroc_esm.py | 14 +- esmvalcore/cmor/_fixes/cmip5/mpi_esm_lr.py | 9 +- esmvalcore/cmor/_fixes/cmip5/mpi_esm_mr.py | 6 + esmvalcore/cmor/_fixes/cmip5/mpi_esm_p.py | 6 + esmvalcore/cmor/_fixes/cmip5/mri_cgcm3.py | 8 +- esmvalcore/cmor/_fixes/cmip5/noresm1_m.py | 6 + esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py | 38 + esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py | 11 + esmvalcore/cmor/_fixes/cmip6/cams_csm1_0.py | 6 + esmvalcore/cmor/_fixes/cmip6/cesm2.py | 78 +- esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py | 5 + esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1.py | 50 + esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1_hr.py | 6 + esmvalcore/cmor/_fixes/cmip6/cnrm_esm2_1.py | 6 + esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py | 44 +- esmvalcore/cmor/_fixes/cmip6/giss_e2_1_g.py | 6 + esmvalcore/cmor/_fixes/cmip6/giss_e2_1_h.py | 6 + esmvalcore/cmor/_fixes/cmip6/miroc6.py | 25 + esmvalcore/cmor/_fixes/cmip6/miroc_es2l.py | 6 + esmvalcore/cmor/_fixes/cmip6/mpi_esm1_2_hr.py | 19 +- esmvalcore/cmor/_fixes/cmip6/mri_esm2_0.py | 6 + esmvalcore/cmor/_fixes/cmip6/nesm3.py | 6 + esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py | 6 + esmvalcore/cmor/_fixes/cmip6/ukesm1_0_ll.py | 43 + esmvalcore/cmor/_fixes/shared.py | 261 ++++- .../cmor/_fixes/us_standard_atmosphere.csv | 913 ++++++++++++++++++ esmvalcore/preprocessor/_derive/__init__.py | 4 +- esmvalcore/preprocessor/_derive/_shared.py | 2 +- esmvalcore/preprocessor/_derive/alb.py | 6 +- esmvalcore/preprocessor/_derive/lwp.py | 6 +- 47 files changed, 1838 insertions(+), 157 deletions(-) create mode 100644 esmvalcore/cmor/_fixes/cmip5/csiro_mk3_6_0.py create mode 100644 esmvalcore/cmor/_fixes/cmip5/fgoals_s2.py create mode 100644 esmvalcore/cmor/_fixes/cmip5/giss_e2_h.py create mode 100644 esmvalcore/cmor/_fixes/cmip5/giss_e2_r.py create mode 100644 esmvalcore/cmor/_fixes/cmip5/ipsl_cm5a_lr.py create mode 100644 esmvalcore/cmor/_fixes/cmip5/ipsl_cm5a_mr.py create mode 100644 esmvalcore/cmor/_fixes/cmip5/ipsl_cm5b_lr.py create mode 100644 esmvalcore/cmor/_fixes/cmip5/mpi_esm_mr.py create mode 100644 esmvalcore/cmor/_fixes/cmip5/mpi_esm_p.py create mode 100644 esmvalcore/cmor/_fixes/cmip5/noresm1_m.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/cams_csm1_0.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1_hr.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/cnrm_esm2_1.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/giss_e2_1_g.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/giss_e2_1_h.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/miroc6.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/miroc_es2l.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/mri_esm2_0.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/nesm3.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py create mode 100644 esmvalcore/cmor/_fixes/us_standard_atmosphere.csv diff --git a/esmvalcore/cmor/_fixes/cmip5/access1_0.py b/esmvalcore/cmor/_fixes/cmip5/access1_0.py index f5da46571f..7d06c06c80 100644 --- a/esmvalcore/cmor/_fixes/cmip5/access1_0.py +++ b/esmvalcore/cmor/_fixes/cmip5/access1_0.py @@ -1,11 +1,11 @@ """Fixes for the ACCESS1-0 model.""" - -from cf_units import Unit import iris +from cf_units import Unit + +from ..cmip6.ukesm1_0_ll import Cl as BaseCl from ..fix import Fix -# noinspection PyPep8 class AllVars(Fix): """Common fixes to all vars.""" @@ -13,15 +13,16 @@ def fix_metadata(self, cubes): """ Fix metadata. - Fixes wrong calendar 'gregorian' instead of 'proleptic_gregorian' + Fixes wrong calendar 'gregorian' instead of 'proleptic_gregorian'. Parameters ---------- - cube: iris.cube.Cube + cubes : iris.cube.CubeList + Input cubes which need to be fixed. Returns ------- - iris.cube.Cube + iris.cube.CubeList """ for cube in cubes: @@ -32,3 +33,28 @@ def fix_metadata(self, cubes): else: time.units = Unit(time.units.name, 'gregorian') return cubes + + +class Cl(BaseCl): + """Fixes for ``cl``.""" + + def fix_metadata(self, cubes): + """Remove attributes from ``vertical coordinate formula term: b(k)``. + + Additionally add pressure level coordiante. + + Parameters + ---------- + cubes : iris.cube.CubeList + Input cubes which need to be fixed. + + Returns + ------- + iris.cube.CubeList + + """ + cubes = super().fix_metadata(cubes) + cube = self.get_cube_from_list(cubes) + coord = cube.coord(long_name='vertical coordinate formula term: b(k)') + coord.attributes = {} + return cubes diff --git a/esmvalcore/cmor/_fixes/cmip5/access1_3.py b/esmvalcore/cmor/_fixes/cmip5/access1_3.py index 638613f1e8..4bb564d41b 100644 --- a/esmvalcore/cmor/_fixes/cmip5/access1_3.py +++ b/esmvalcore/cmor/_fixes/cmip5/access1_3.py @@ -1,26 +1,31 @@ """Fixes for ACCESS1-3 model.""" - -from cf_units import Unit import iris +from cf_units import Unit + from ..fix import Fix +from .access1_0 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" class AllVars(Fix): """Common fixes to all vars.""" def fix_metadata(self, cubes): - """ - Fix metadata. + """Fix metadata. Fixes wrong calendar 'gregorian' instead of 'proleptic_gregorian' Parameters ---------- - cube: iris.cube.Cube + cubes : iris.cube.CubeList + Input cubes which need to be fixed. Returns ------- - iris.cube.Cube + iris.cube.CubeList """ for cube in cubes: diff --git a/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1.py b/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1.py index aa1941cf56..1ed206fd84 100644 --- a/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1.py +++ b/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1.py @@ -1,9 +1,65 @@ """Fixes for bcc-csm1-1.""" +import iris import numpy as np from scipy.interpolate import InterpolatedUnivariateSpline from scipy.ndimage import map_coordinates from ..fix import Fix +from ..shared import fix_bounds + + +class Cl(Fix): + """Fixes for cl.""" + + def fix_metadata(self, cubes): + """Fix hybrid sigma pressure coordinate. + + Parameters + ---------- + cubes : iris.cube.CubeList + Input cubes which need to be fixed. + + Returns + ------- + iris.cube.CubeList + + """ + cl_cube = self.get_cube_from_list(cubes) + + # Remove all existing aux_factories + for aux_factory in cl_cube.aux_factories: + cl_cube.remove_aux_factory(aux_factory) + + # Fix bounds + coords_to_fix = ['b'] + try: + cl_cube.coord(var_name='a') + coords_to_fix.append('a') + except iris.exceptions.CoordinateNotFoundError: + coords_to_fix.append('ap') + fix_bounds(cl_cube, cubes, coords_to_fix) + + # Fix bounds for ap if only a is given in original file + ap_coord = cl_cube.coord(var_name='ap') + if ap_coord.bounds is None: + cl_cube.remove_coord(ap_coord) + a_coord = cl_cube.coord(var_name='a') + p0_coord = cl_cube.coord(var_name='p0') + ap_coord = a_coord * p0_coord.points[0] + ap_coord.units = a_coord.units * p0_coord.units + ap_coord.rename('vertical pressure') + ap_coord.var_name = 'ap' + cl_cube.add_aux_coord(ap_coord, cl_cube.coord_dims(a_coord)) + + # Add aux_factory again + pressure_coord_factory = iris.aux_factory.HybridPressureFactory( + delta=ap_coord, + sigma=cl_cube.coord(var_name='b'), + surface_air_pressure=cl_cube.coord(var_name='ps'), + ) + cl_cube.add_aux_factory(pressure_coord_factory) + + return iris.cube.CubeList([cl_cube]) class Tos(Fix): @@ -17,6 +73,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube to fix. Returns ------- @@ -27,10 +84,12 @@ def fix_data(self, cube): rlon = cube.coord('grid_longitude').points # Transform grid latitude/longitude to array indices [0, 1, 2, ...] - rlat_to_idx = InterpolatedUnivariateSpline( - rlat, np.arange(len(rlat)), k=1) - rlon_to_idx = InterpolatedUnivariateSpline( - rlon, np.arange(len(rlon)), k=1) + rlat_to_idx = InterpolatedUnivariateSpline(rlat, + np.arange(len(rlat)), + k=1) + rlon_to_idx = InterpolatedUnivariateSpline(rlon, + np.arange(len(rlon)), + k=1) rlat_idx_bnds = rlat_to_idx(cube.coord('grid_latitude').bounds) rlon_idx_bnds = rlon_to_idx(cube.coord('grid_longitude').bounds) @@ -38,16 +97,17 @@ def fix_data(self, cube): lat_vertices = [] lon_vertices = [] for (i, j) in [(0, 0), (0, 1), (1, 1), (1, 0)]: - (rlat_v, rlon_v) = np.meshgrid( - rlat_idx_bnds[:, i], rlon_idx_bnds[:, j], indexing='ij') + (rlat_v, rlon_v) = np.meshgrid(rlat_idx_bnds[:, i], + rlon_idx_bnds[:, j], + indexing='ij') lat_vertices.append( - map_coordinates( - cube.coord('latitude').points, [rlat_v, rlon_v], - mode='nearest')) + map_coordinates(cube.coord('latitude').points, + [rlat_v, rlon_v], + mode='nearest')) lon_vertices.append( - map_coordinates( - cube.coord('longitude').points, [rlat_v, rlon_v], - mode='wrap')) + map_coordinates(cube.coord('longitude').points, + [rlat_v, rlon_v], + mode='wrap')) lat_vertices = np.array(lat_vertices) lon_vertices = np.array(lon_vertices) lat_vertices = np.moveaxis(lat_vertices, 0, -1) diff --git a/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1_m.py b/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1_m.py index 266299c4f1..9d19e68a7b 100644 --- a/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1_m.py +++ b/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1_m.py @@ -1,61 +1,11 @@ - """Fixes for bcc-csm1-1-m.""" -import numpy as np -from scipy.interpolate import InterpolatedUnivariateSpline -from scipy.ndimage import map_coordinates - -from ..fix import Fix - - -class Tos(Fix): - """Fixes for tos.""" - - def fix_data(self, cube): - """Fix data. +from .bcc_csm1_1 import Cl as BaseCl +from .bcc_csm1_1 import Tos as BaseTos - Calculate missing latitude/longitude boundaries using interpolation. - Parameters - ---------- - cube: iris.cube.Cube +class Cl(BaseCl): + """Fixes for cl.""" - Returns - ------- - iris.cube.Cube - """ - rlat = cube.coord('grid_latitude').points - rlon = cube.coord('grid_longitude').points - - # Transform grid latitude/longitude to array indices [0, 1, 2, ...] - rlat_to_idx = InterpolatedUnivariateSpline( - rlat, np.arange(len(rlat)), k=1) - rlon_to_idx = InterpolatedUnivariateSpline( - rlon, np.arange(len(rlon)), k=1) - rlat_idx_bnds = rlat_to_idx(cube.coord('grid_latitude').bounds) - rlon_idx_bnds = rlon_to_idx(cube.coord('grid_longitude').bounds) - - # Calculate latitude/longitude vertices by interpolation - lat_vertices = [] - lon_vertices = [] - for (i, j) in [(0, 0), (0, 1), (1, 1), (1, 0)]: - (rlat_v, rlon_v) = np.meshgrid( - rlat_idx_bnds[:, i], rlon_idx_bnds[:, j], indexing='ij') - lat_vertices.append( - map_coordinates( - cube.coord('latitude').points, [rlat_v, rlon_v], - mode='nearest')) - lon_vertices.append( - map_coordinates( - cube.coord('longitude').points, [rlat_v, rlon_v], - mode='wrap')) - lat_vertices = np.array(lat_vertices) - lon_vertices = np.array(lon_vertices) - lat_vertices = np.moveaxis(lat_vertices, 0, -1) - lon_vertices = np.moveaxis(lon_vertices, 0, -1) - - # Copy vertices to cube - cube.coord('latitude').bounds = lat_vertices - cube.coord('longitude').bounds = lon_vertices - - return cube +class Tos(BaseTos): + """Fixes for tos.""" diff --git a/esmvalcore/cmor/_fixes/cmip5/canesm2.py b/esmvalcore/cmor/_fixes/cmip5/canesm2.py index e30578bb5f..fa8293dbb3 100644 --- a/esmvalcore/cmor/_fixes/cmip5/canesm2.py +++ b/esmvalcore/cmor/_fixes/cmip5/canesm2.py @@ -1,6 +1,10 @@ - """Fixes for CanESM2 model.""" from ..fix import Fix +from .bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for cl.""" class FgCo2(Fix): @@ -15,6 +19,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube to fix. Returns ------- diff --git a/esmvalcore/cmor/_fixes/cmip5/ccsm4.py b/esmvalcore/cmor/_fixes/cmip5/ccsm4.py index 60b8c901f1..29528e5652 100644 --- a/esmvalcore/cmor/_fixes/cmip5/ccsm4.py +++ b/esmvalcore/cmor/_fixes/cmip5/ccsm4.py @@ -1,8 +1,13 @@ """Fixes for CCSM4 model.""" +from .bcc_csm1_1 import Cl as BaseCl from ..fix import Fix from ..shared import round_coordinates +class Cl(BaseCl): + """Fixes for cl.""" + + class Rlut(Fix): """Fixes for rlut.""" @@ -15,11 +20,12 @@ def fix_metadata(self, cubes): Parameters ---------- - cube: iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- - iris.cube.Cube + iris.cube.CubeList """ return round_coordinates(cubes, 3) @@ -80,11 +86,12 @@ def fix_metadata(self, cubes): Parameters ---------- - cube: iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- - iris.cube.Cube + iris.cube.CubeList """ self.get_cube_from_list(cubes).units = '1e3' diff --git a/esmvalcore/cmor/_fixes/cmip5/csiro_mk3_6_0.py b/esmvalcore/cmor/_fixes/cmip5/csiro_mk3_6_0.py new file mode 100644 index 0000000000..67e6804609 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/csiro_mk3_6_0.py @@ -0,0 +1,6 @@ +"""Fixes for CSIRO-Mk3-6-0 model.""" +from ..cmip6.miroc6 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" diff --git a/esmvalcore/cmor/_fixes/cmip5/fgoals_g2.py b/esmvalcore/cmor/_fixes/cmip5/fgoals_g2.py index 91830baf94..39f7a192fd 100644 --- a/esmvalcore/cmor/_fixes/cmip5/fgoals_g2.py +++ b/esmvalcore/cmor/_fixes/cmip5/fgoals_g2.py @@ -1,40 +1,61 @@ - -"""Fix FGOALS-g2 model.""" +"""Fixes for FGOALS-g2 model.""" +import iris from cf_units import Unit -from iris.exceptions import CoordinateNotFoundError from ..fix import Fix -from ..shared import round_coordinates +from ..shared import add_pressure_level_coordinate, round_coordinates class AllVars(Fix): - """Fix errors common to all vars.""" + """Fixes for all variables.""" def fix_metadata(self, cubes): - """ - Fix metadata. + """Fix metadata. - Fixes time units + Fix time coordinate and round other coordinates to fix issue with + modulus in longitude coordinate. Fixes longitude precision Parameters ---------- - cube: iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- - iris.cube.Cube + iris.cube.CubeList """ for cube in cubes: try: time = cube.coord('time') - except CoordinateNotFoundError: + except iris.exceptions.CoordinateNotFoundError: pass else: time.units = Unit(time.units.name, time.units.calendar) round_coordinates(cubes, 4, coord_names=['longitude']) - return cubes + +class Cl(Fix): + """Fixes for ``cl``.""" + + def fix_metadata(self, cubes): + """Fix metadata. + + Add pressure level coordinate. + + Parameters + ---------- + cubes : iris.cube.CubeList + Input cubes. + + Returns + ------- + iris.cube.CubeList + + """ + cube = self.get_cube_from_list(cubes) + add_pressure_level_coordinate(cube) + return iris.cube.CubeList([cube]) diff --git a/esmvalcore/cmor/_fixes/cmip5/fgoals_s2.py b/esmvalcore/cmor/_fixes/cmip5/fgoals_s2.py new file mode 100644 index 0000000000..c82151226f --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/fgoals_s2.py @@ -0,0 +1,39 @@ +"""Fixes for FGOALS-s2 model.""" +import iris + +from ..fix import Fix + + +class AllVars(Fix): + """Fixes for all variables.""" + + def fix_metadata(self, cubes): + """Fix metadata. + + Fix wrong bounds of latitude coordinate at first and last index. + + Parameters + ---------- + cubes : iris.cube.CubeList + Input cubes. + + Returns + ------- + iris.cube.CubeList + + """ + for cube in cubes: + try: + lat_coord = cube.coord('latitude') + except iris.exceptions.CoordinateNotFoundError: + continue + if lat_coord.ndim != 1: + continue + if lat_coord.shape[0] < 3: + continue + lat_bounds = lat_coord.core_bounds().copy() + lat_diff = lat_bounds[1][1] - lat_bounds[1][0] + lat_bounds[0][0] = lat_bounds[0][1] - lat_diff + lat_bounds[-1][1] = lat_bounds[-1][0] + lat_diff + lat_coord.bounds = lat_bounds + return cubes diff --git a/esmvalcore/cmor/_fixes/cmip5/giss_e2_h.py b/esmvalcore/cmor/_fixes/cmip5/giss_e2_h.py new file mode 100644 index 0000000000..968dfd32e1 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/giss_e2_h.py @@ -0,0 +1,6 @@ +"""Fixes for GISS-E2-H.""" +from .bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for cl.""" diff --git a/esmvalcore/cmor/_fixes/cmip5/giss_e2_r.py b/esmvalcore/cmor/_fixes/cmip5/giss_e2_r.py new file mode 100644 index 0000000000..a356bcff54 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/giss_e2_r.py @@ -0,0 +1,6 @@ +"""Fixes for GISS-E2-R.""" +from .bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for cl.""" diff --git a/esmvalcore/cmor/_fixes/cmip5/hadgem2_es.py b/esmvalcore/cmor/_fixes/cmip5/hadgem2_es.py index c6c7c239c2..18f37d17dc 100644 --- a/esmvalcore/cmor/_fixes/cmip5/hadgem2_es.py +++ b/esmvalcore/cmor/_fixes/cmip5/hadgem2_es.py @@ -1,7 +1,7 @@ - """Fix HadGEM2_ES.""" import numpy as np +from ..cmip6.ukesm1_0_ll import Cl as BaseCl from ..fix import Fix @@ -9,12 +9,12 @@ class AllVars(Fix): """Fix errors common to all vars.""" def fix_metadata(self, cubes): - """ - Fix latitude. + """Fix latitude. Parameters ---------- - cube: iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes which need to be fixed. Returns ------- @@ -33,16 +33,20 @@ def fix_metadata(self, cubes): return cubes +class Cl(BaseCl): + """Fixes for ``cl``.""" + + class O2(Fix): """Fix o2.""" def fix_metadata(self, cubes): - """ - Fix standard and long name. + """Fix standard and long name. Parameters ---------- - cube: iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes which need to be fixed. Returns ------- diff --git a/esmvalcore/cmor/_fixes/cmip5/inmcm4.py b/esmvalcore/cmor/_fixes/cmip5/inmcm4.py index 8f07eb1904..eb5bb622d2 100644 --- a/esmvalcore/cmor/_fixes/cmip5/inmcm4.py +++ b/esmvalcore/cmor/_fixes/cmip5/inmcm4.py @@ -1,10 +1,14 @@ - """Fixes for inmcm4 model.""" import iris +from .bcc_csm1_1 import Cl as BaseCl from ..fix import Fix +class Cl(BaseCl): + """Fixes for cl.""" + + class Gpp(Fix): """Fixes for gpp.""" @@ -16,7 +20,8 @@ def fix_data(self, cube): Parameters ---------- - cube: iris.cube.Cube + cube : iris.cube.Cube + Input cube. Returns ------- @@ -40,7 +45,8 @@ def fix_data(self, cube): Parameters ---------- - cube: iris.cube.Cube + cube : iris.cube.Cube + Input cube. Returns ------- diff --git a/esmvalcore/cmor/_fixes/cmip5/ipsl_cm5a_lr.py b/esmvalcore/cmor/_fixes/cmip5/ipsl_cm5a_lr.py new file mode 100644 index 0000000000..ed01586f49 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/ipsl_cm5a_lr.py @@ -0,0 +1,6 @@ +"""Fixes for IPSL-CM5A-LR model.""" +from .bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for cl.""" diff --git a/esmvalcore/cmor/_fixes/cmip5/ipsl_cm5a_mr.py b/esmvalcore/cmor/_fixes/cmip5/ipsl_cm5a_mr.py new file mode 100644 index 0000000000..f5977a1c61 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/ipsl_cm5a_mr.py @@ -0,0 +1,6 @@ +"""Fixes for IPSL-CM5A-MR model.""" +from .bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for cl.""" diff --git a/esmvalcore/cmor/_fixes/cmip5/ipsl_cm5b_lr.py b/esmvalcore/cmor/_fixes/cmip5/ipsl_cm5b_lr.py new file mode 100644 index 0000000000..30ff79a4ba --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/ipsl_cm5b_lr.py @@ -0,0 +1,6 @@ +"""Fixes for IPSL-CM5B-LR model.""" +from .bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for cl.""" diff --git a/esmvalcore/cmor/_fixes/cmip5/miroc5.py b/esmvalcore/cmor/_fixes/cmip5/miroc5.py index b92f6a3bb4..da49992fdb 100644 --- a/esmvalcore/cmor/_fixes/cmip5/miroc5.py +++ b/esmvalcore/cmor/_fixes/cmip5/miroc5.py @@ -1,10 +1,15 @@ """Fixes for MIROC5 model.""" from dask import array as da +from ..cmip6.miroc6 import Cl as BaseCl from ..fix import Fix from ..shared import round_coordinates +class Cl(BaseCl): + """Fixes for ``cl``.""" + + class Sftof(Fix): """Fixes for sftof.""" @@ -17,6 +22,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube. Returns ------- @@ -41,6 +47,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube. Returns ------- @@ -98,6 +105,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube. Returns ------- @@ -121,6 +129,7 @@ def fix_metadata(self, cubes): Parameters ---------- cubes: iris.cube.CubeList + Input cubes. Returns ------- @@ -130,6 +139,10 @@ def fix_metadata(self, cubes): return round_coordinates(cubes) +class Hur(Tas): + """Fixes for hur.""" + + class Tos(Fix): """Fixes for tos.""" @@ -142,6 +155,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube. Returns ------- diff --git a/esmvalcore/cmor/_fixes/cmip5/miroc_esm.py b/esmvalcore/cmor/_fixes/cmip5/miroc_esm.py index 192ccb3996..e333b97584 100644 --- a/esmvalcore/cmor/_fixes/cmip5/miroc_esm.py +++ b/esmvalcore/cmor/_fixes/cmip5/miroc_esm.py @@ -3,9 +3,14 @@ from iris.coords import DimCoord from iris.exceptions import CoordinateNotFoundError +from .bcc_csm1_1 import Cl as BaseCl from ..fix import Fix +class Cl(BaseCl): + """Fixes for ``cl``.""" + + class Tro3(Fix): """Fixes for tro3.""" @@ -18,6 +23,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube. Returns ------- @@ -41,11 +47,12 @@ def fix_metadata(self, cubes): Parameters ---------- - cube: iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- - iris.cube.Cube + iris.cube.CubeList """ self.get_cube_from_list(cubes).units = '1.0e-6' @@ -63,7 +70,8 @@ def fix_metadata(self, cubes): Parameters ---------- - cube: iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- diff --git a/esmvalcore/cmor/_fixes/cmip5/mpi_esm_lr.py b/esmvalcore/cmor/_fixes/cmip5/mpi_esm_lr.py index 39a21a5f8f..0d4d1e2656 100644 --- a/esmvalcore/cmor/_fixes/cmip5/mpi_esm_lr.py +++ b/esmvalcore/cmor/_fixes/cmip5/mpi_esm_lr.py @@ -1,6 +1,10 @@ - -"""Fixes for MPI ESM LR model.""" +"""Fixes for MPI-ESM-LR model.""" from ..fix import Fix +from .bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for cl.""" class Pctisccp(Fix): @@ -15,6 +19,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube. Returns ------- diff --git a/esmvalcore/cmor/_fixes/cmip5/mpi_esm_mr.py b/esmvalcore/cmor/_fixes/cmip5/mpi_esm_mr.py new file mode 100644 index 0000000000..a311de5ef7 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/mpi_esm_mr.py @@ -0,0 +1,6 @@ +"""Fixes for MPI-ESM-MR model.""" +from .bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for cl.""" diff --git a/esmvalcore/cmor/_fixes/cmip5/mpi_esm_p.py b/esmvalcore/cmor/_fixes/cmip5/mpi_esm_p.py new file mode 100644 index 0000000000..6ed3c44827 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/mpi_esm_p.py @@ -0,0 +1,6 @@ +"""Fixes for MPI-ESM-P model.""" +from .bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for cl.""" diff --git a/esmvalcore/cmor/_fixes/cmip5/mri_cgcm3.py b/esmvalcore/cmor/_fixes/cmip5/mri_cgcm3.py index a2ca092a0d..8da1e66977 100644 --- a/esmvalcore/cmor/_fixes/cmip5/mri_cgcm3.py +++ b/esmvalcore/cmor/_fixes/cmip5/mri_cgcm3.py @@ -1,10 +1,14 @@ - """Fixes for MRI-CGCM3 model.""" from dask import array as da +from .bcc_csm1_1 import Cl as BaseCl from ..fix import Fix +class Cl(BaseCl): + """Fixes for ``cl``.""" + + class Msftmyz(Fix): """Fixes for msftmyz.""" @@ -17,6 +21,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube. Returns ------- @@ -39,6 +44,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube. Returns ------- diff --git a/esmvalcore/cmor/_fixes/cmip5/noresm1_m.py b/esmvalcore/cmor/_fixes/cmip5/noresm1_m.py new file mode 100644 index 0000000000..6748e14456 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/noresm1_m.py @@ -0,0 +1,6 @@ +"""Fixes for NorESM1-M.""" +from .bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py b/esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py new file mode 100644 index 0000000000..3f89888afa --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py @@ -0,0 +1,38 @@ +"""Fixes for BCC-CSM2-MR model.""" +from ..cmip5.bcc_csm1_1 import Cl as BaseCl +from ..cmip5.bcc_csm1_1 import Tos as BaseTos + + +class Cl(BaseCl): + """Fixes for ``cl``.""" + + +class Tos(BaseTos): + """Fixes for tos.""" + + def fix_metadata(self, cubes): + """Rename ``var_name`` of 1D-``latitude`` and 1D-``longitude``. + + Parameters + ---------- + cubes : iris.cube.CubeList + Input cubes. + + Returns + ------- + iris.cube.CubeList + + """ + cube = self.get_cube_from_list(cubes) + lat_coord = cube.coord('latitude', dimensions=(1, )) + lon_coord = cube.coord('longitude', dimensions=(2, )) + lat_coord.standard_name = None + lat_coord.long_name = 'grid_latitude' + lat_coord.var_name = 'i' + lat_coord.units = '1' + lon_coord.standard_name = None + lon_coord.long_name = 'grid_longitude' + lon_coord.var_name = 'j' + lon_coord.units = '1' + lon_coord.circular = False + return cubes diff --git a/esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py b/esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py new file mode 100644 index 0000000000..3af096a886 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py @@ -0,0 +1,11 @@ +"""Fixes for BCC-ESM1 model.""" +from ..cmip5.bcc_csm1_1 import Cl as BaseCl +from .bcc_csm2_mr import Tos as BaseTos + + +class Cl(BaseCl): + """Fixes for ``cl``.""" + + +class Tos(BaseTos): + """Fixes for tos.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/cams_csm1_0.py b/esmvalcore/cmor/_fixes/cmip6/cams_csm1_0.py new file mode 100644 index 0000000000..532cdf312a --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/cams_csm1_0.py @@ -0,0 +1,6 @@ +"""Fixes for CAMS-CSM1-0 model.""" +from ..cmip5.bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/cesm2.py b/esmvalcore/cmor/_fixes/cmip6/cesm2.py index 60fe83ebf5..84d34ba32c 100644 --- a/esmvalcore/cmor/_fixes/cmip6/cesm2.py +++ b/esmvalcore/cmor/_fixes/cmip6/cesm2.py @@ -1,21 +1,73 @@ """Fixes for CESM2 model.""" +import iris + +from ..cmip5.bcc_csm1_1 import Cl as BaseCl from ..fix import Fix -from ..shared import (add_scalar_depth_coord, add_scalar_height_coord, - add_scalar_typeland_coord, add_scalar_typesea_coord) +from ..shared import (add_aux_coords_from_cubes, add_scalar_depth_coord, + add_scalar_height_coord, add_scalar_typeland_coord, + add_scalar_typesea_coord) + + +class Cl(BaseCl): + """Fixes for ``cl``.""" + + def fix_metadata(self, cubes): + """Fix vertical hybrid sigma coordinate. + + Parameters + ---------- + cubes : iris.cube.CubeList + Input cubes. + + Returns + ------- + iris.cube.CubeList + + """ + cube = self.get_cube_from_list(cubes) + + # Add auxiliary coordinate from list of cubes + coords_to_add = { + 'a': 1, + 'b': 1, + 'ps': (0, 2, 3), + 'p0': (), + } + add_aux_coords_from_cubes(cube, cubes, coords_to_add) + + # Add ap coordinate + a_coord = cube.coord(var_name='a') + p0_coord = cube.coord(var_name='p0') + ap_coord = a_coord * p0_coord.points[0] + ap_coord.units = a_coord.units * p0_coord.units + ap_coord.rename('vertical pressure') + ap_coord.var_name = 'ap' + cube.add_aux_coord(ap_coord, cube.coord_dims(a_coord)) + + # Add aux_factory + pressure_coord_factory = iris.aux_factory.HybridPressureFactory( + delta=ap_coord, + sigma=cube.coord(var_name='b'), + surface_air_pressure=cube.coord(var_name='ps'), + ) + cube.add_aux_factory(pressure_coord_factory) + return cubes class Fgco2(Fix): """Fixes for fgco2.""" + def fix_metadata(self, cubes): """Add depth (0m) coordinate. Parameters ---------- - cube : iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- - iris.cube.Cube + iris.cube.CubeList """ cube = self.get_cube_from_list(cubes) @@ -25,16 +77,18 @@ def fix_metadata(self, cubes): class Tas(Fix): """Fixes for tas.""" + def fix_metadata(self, cubes): """Add height (2m) coordinate. Parameters ---------- - cube : iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- - iris.cube.Cube + iris.cube.CubeList """ cube = self.get_cube_from_list(cubes) @@ -44,16 +98,18 @@ def fix_metadata(self, cubes): class Sftlf(Fix): """Fixes for sftlf.""" + def fix_metadata(self, cubes): """Add typeland coordinate. Parameters ---------- - cube : iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- - iris.cube.Cube + iris.cube.CubeList """ cube = self.get_cube_from_list(cubes) @@ -63,16 +119,18 @@ def fix_metadata(self, cubes): class Sftof(Fix): """Fixes for sftof.""" + def fix_metadata(self, cubes): """Add typesea coordinate. Parameters ---------- - cube : iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- - iris.cube.Cube + iris.cube.CubeList """ cube = self.get_cube_from_list(cubes) diff --git a/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py b/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py index c3b4b93df2..b40b20b7b4 100644 --- a/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py +++ b/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py @@ -1,6 +1,11 @@ """Fixes for CESM2-WACCM model.""" +from .cesm2 import Cl as BaseCl from .cesm2 import Tas as BaseTas +class Cl(BaseCl): + """Fixes for cl.""" + + class Tas(BaseTas): """Fixes for tas.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1.py b/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1.py new file mode 100644 index 0000000000..8fefc82d4d --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1.py @@ -0,0 +1,50 @@ +"""Fixes for CNRM-CM6-1 model.""" +import iris + +from ..cmip5.bcc_csm1_1 import Cl as BaseCl +from ..shared import add_aux_coords_from_cubes, get_bounds_cube + + +class Cl(BaseCl): + """Fixes for ``cl``.""" + + def fix_metadata(self, cubes): + """Fix vertical hybrid sigma coordinate (incl. bounds). + + Parameters + ---------- + cubes : iris.cube.CubeList + Input cubes. + + Returns + ------- + iris.cube.Cube + + """ + cube = self.get_cube_from_list(cubes) + + # Add auxiliary coordinate from list of cubes + coords_to_add = { + 'ap': 1, + 'b': 1, + 'ps': (0, 2, 3), + } + add_aux_coords_from_cubes(cube, cubes, coords_to_add) + cube.coord(var_name='ap').units = 'Pa' + + # Fix vertical coordinate bounds + for coord_name in ('ap', 'b'): + bounds_cube = get_bounds_cube(cubes, coord_name) + bounds = bounds_cube.data.reshape(-1, 2) + new_bounds_cube = iris.cube.Cube(bounds, + **bounds_cube.metadata._asdict()) + cubes.remove(bounds_cube) + cubes.append(new_bounds_cube) + + # Fix hybrid sigma pressure coordinate + cubes = super().fix_metadata(cubes) + + # Fix horizontal coordinates bounds + for coord_name in ('latitude', 'longitude'): + cube.coord(coord_name).guess_bounds() + return cubes diff --git a/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1_hr.py b/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1_hr.py new file mode 100644 index 0000000000..73a0d8fafb --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1_hr.py @@ -0,0 +1,6 @@ +"""Fixes for CNRM-CM6-1-HR model.""" +from .cnrm_cm6_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/cnrm_esm2_1.py b/esmvalcore/cmor/_fixes/cmip6/cnrm_esm2_1.py new file mode 100644 index 0000000000..c11df2657f --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/cnrm_esm2_1.py @@ -0,0 +1,6 @@ +"""Fixes for CNRM-ESM2-1 model.""" +from .cnrm_cm6_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py b/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py index b16eb6bb12..faac0927be 100644 --- a/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py +++ b/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py @@ -1,8 +1,35 @@ """Fixes for GFDL-CM4 model.""" import iris +from ..cmip5.bcc_csm1_1 import Cl as BaseCl from ..fix import Fix -from ..shared import add_scalar_height_coord +from ..shared import add_aux_coords_from_cubes, add_scalar_height_coord + + +class Cl(BaseCl): + """Fixes for ``cl``.""" + + def fix_metadata(self, cubes): + """Fix hybrid sigma pressure coordinate. + + Parameters + ---------- + cubes : iris.cube.CubeList + Input cubes which need to be fixed. + + Returns + ------- + iris.cube.CubeList + + """ + cube = self.get_cube_from_list(cubes) + coords_to_add = { + 'ap': 1, + 'b': 1, + 'ps': (0, 2, 3), + } + add_aux_coords_from_cubes(cube, cubes, coords_to_add) + return super().fix_metadata(cubes) class Tas(Fix): @@ -14,11 +41,12 @@ def fix_metadata(self, cubes): Parameters ---------- - cube : iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- - iris.cube.Cube + iris.cube.CubeList """ cube = self.get_cube_from_list(cubes) @@ -38,11 +66,12 @@ def fix_metadata(self, cubes): Parameters ---------- - cube : iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- - iris.cube.Cube + iris.cube.CubeList """ cube = self.get_cube_from_list(cubes) @@ -59,11 +88,12 @@ def fix_metadata(self, cubes): Parameters ---------- - cube : iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- - iris.cube.Cube + iris.cube.CubeList """ cube = self.get_cube_from_list(cubes) diff --git a/esmvalcore/cmor/_fixes/cmip6/giss_e2_1_g.py b/esmvalcore/cmor/_fixes/cmip6/giss_e2_1_g.py new file mode 100644 index 0000000000..8e2a2b09ed --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/giss_e2_1_g.py @@ -0,0 +1,6 @@ +"""Fixes for GISS-E2-1-G model.""" +from ..cmip5.bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/giss_e2_1_h.py b/esmvalcore/cmor/_fixes/cmip6/giss_e2_1_h.py new file mode 100644 index 0000000000..b6e5a23c3a --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/giss_e2_1_h.py @@ -0,0 +1,6 @@ +"""Fixes for GISS-E2-1-H model.""" +from ..cmip5.bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/miroc6.py b/esmvalcore/cmor/_fixes/cmip6/miroc6.py new file mode 100644 index 0000000000..8a1ad27217 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/miroc6.py @@ -0,0 +1,25 @@ +"""Fixes for MIROC6 model.""" +from ..cmip5.bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" + + def fix_metadata(self, cubes): + """Remove attributes from ``Surface Air Pressure`` coordinate. + + Parameters + ---------- + cubes : iris.cube.CubeList + Input cubes. + + Returns + ------- + iris.cube.CubeList + + """ + cubes = super().fix_metadata(cubes) + cube = self.get_cube_from_list(cubes) + coord = cube.coord(long_name='Surface Air Pressure') + coord.attributes = {} + return cubes diff --git a/esmvalcore/cmor/_fixes/cmip6/miroc_es2l.py b/esmvalcore/cmor/_fixes/cmip6/miroc_es2l.py new file mode 100644 index 0000000000..d467d83796 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/miroc_es2l.py @@ -0,0 +1,6 @@ +"""Fixes for MIROC-ES2L model.""" +from ..cmip5.bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/mpi_esm1_2_hr.py b/esmvalcore/cmor/_fixes/cmip6/mpi_esm1_2_hr.py index 21648fc1dc..fcf3d9dd78 100644 --- a/esmvalcore/cmor/_fixes/cmip6/mpi_esm1_2_hr.py +++ b/esmvalcore/cmor/_fixes/cmip6/mpi_esm1_2_hr.py @@ -1,19 +1,24 @@ """Fixes for MPI-ESM1-2-HR model.""" - +from ..cmip5.bcc_csm1_1 import Cl as BaseCl from ..fix import Fix from ..shared import add_scalar_height_coord +class Cl(BaseCl): + """Fixes for ``cl``.""" + + class Tas(Fix): """Fixes for tas.""" def fix_metadata(self, cubes): """ - Adds missing height2m coordinate. + Add missing height2m coordinate. Parameters ---------- - cube: iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- @@ -35,7 +40,8 @@ def fix_metadata(self, cubes): Parameters ---------- - cube: iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- @@ -66,11 +72,12 @@ class SfcWind(Fix): def fix_metadata(self, cubes): """ - Adds missing height10m coordinate. + Add missing height10m coordinate. Parameters ---------- - cube: iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- diff --git a/esmvalcore/cmor/_fixes/cmip6/mri_esm2_0.py b/esmvalcore/cmor/_fixes/cmip6/mri_esm2_0.py new file mode 100644 index 0000000000..af5126d5f4 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/mri_esm2_0.py @@ -0,0 +1,6 @@ +"""Fixes for MRI-ESM2-0 model.""" +from ..cmip5.bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/nesm3.py b/esmvalcore/cmor/_fixes/cmip6/nesm3.py new file mode 100644 index 0000000000..9ded3e8fc2 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/nesm3.py @@ -0,0 +1,6 @@ +"""Fixes for NESM3 model.""" +from ..cmip5.bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py b/esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py new file mode 100644 index 0000000000..0eee658088 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py @@ -0,0 +1,6 @@ +"""Fixes for SAM0-UNICON model.""" +from ..cmip5.bcc_csm1_1 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/ukesm1_0_ll.py b/esmvalcore/cmor/_fixes/cmip6/ukesm1_0_ll.py index f111198cd8..d737f1d214 100644 --- a/esmvalcore/cmor/_fixes/cmip6/ukesm1_0_ll.py +++ b/esmvalcore/cmor/_fixes/cmip6/ukesm1_0_ll.py @@ -1,6 +1,49 @@ """Fixes for CMIP6 UKESM1-0-LL.""" +import iris + +from ..fix import Fix +from ..shared import add_pressure_level_coordinate, fix_bounds from .hadgem3_gc31_ll import AllVars as BaseAllVars class AllVars(BaseAllVars): """Fixes for all vars.""" + + +class Cl(Fix): + """Fixes for ``'cl'``.""" + + def fix_metadata(self, cubes): + """Fix hybrid sigma height coordinate and add pressure levels. + + Parameters + ---------- + cubes : iris.cube.CubeList + Input cubes which need to be fixed. + + Returns + ------- + iris.cube.CubeList + + """ + cl_cube = self.get_cube_from_list(cubes) + + # Remove all existing aux_factories + for aux_factory in cl_cube.aux_factories: + cl_cube.remove_aux_factory(aux_factory) + + # Fix bounds + fix_bounds(cl_cube, cubes, ('lev', 'b')) + + # Add aux_factory again + height_coord_factory = iris.aux_factory.HybridHeightFactory( + delta=cl_cube.coord(var_name='lev'), + sigma=cl_cube.coord(var_name='b'), + orography=cl_cube.coord(var_name='orog'), + ) + cl_cube.add_aux_factory(height_coord_factory) + + # Add pressure level coordinate + add_pressure_level_coordinate(cl_cube) + + return iris.cube.CubeList([cl_cube]) diff --git a/esmvalcore/cmor/_fixes/shared.py b/esmvalcore/cmor/_fixes/shared.py index 262e58690c..9b3e13c04a 100644 --- a/esmvalcore/cmor/_fixes/shared.py +++ b/esmvalcore/cmor/_fixes/shared.py @@ -1,13 +1,225 @@ """Shared functions for fixes.""" import logging +import os +import warnings import dask.array as da import iris +import pandas as pd from cf_units import Unit +from scipy.interpolate import interp1d + +from esmvalcore.preprocessor._derive._shared import var_name_constraint logger = logging.getLogger(__name__) +def _get_altitude_to_pressure_func(): + """Get function converting altitude [m] to air pressure [Pa].""" + base_dir = os.path.dirname(os.path.abspath(__file__)) + source_file = os.path.join(base_dir, 'us_standard_atmosphere.csv') + data_frame = pd.read_csv(source_file, comment='#') + func = interp1d(data_frame['Altitude [m]'], + data_frame['Pressure [Pa]'], + kind='cubic', + fill_value='extrapolate') + return func + + +ALTITUDE_TO_PRESSURE = _get_altitude_to_pressure_func() + + +class AtmosphereSigmaFactory(iris.aux_factory.AuxCoordFactory): + """Defines an atmosphere sigma coordinate factory.""" + + def __init__(self, pressure_at_top=None, sigma=None, + surface_air_pressure=None): + """Create class instance. + + Creates and atmosphere sigma coordinate factory with the formula: + + p(n, k, j, i) = pressure_at_top + sigma(k) * + (surface_air_pressure(n, j, i) - pressure_at_top) + + """ + super().__init__() + self._check_dependencies(pressure_at_top, sigma, surface_air_pressure) + self.pressure_at_top = pressure_at_top + self.sigma = sigma + self.surface_air_pressure = surface_air_pressure + self.standard_name = 'air_pressure' + self.attributes = {} + + @property + def units(self): + """Units.""" + units = self.pressure_at_top.units + return units + + @staticmethod + def _check_dependencies(pressure_at_top, sigma, surface_air_pressure): + """Check for sufficient coordinates.""" + if any([ + pressure_at_top is None, + sigma is None, + surface_air_pressure is None, + ]): + raise ValueError( + "Unable to contruct atmosphere sigma coordinate factory due " + "to insufficient source coordinates") + + # Check dimensions + if pressure_at_top.shape not in ((), (1, )): + raise ValueError( + f"Expected scalar 'pressure_at_top' coordinate, got shape " + f"{pressure_at_top.shape}") + + # Check bounds + if sigma.nbounds not in (0, 2): + raise ValueError( + "Invalid 'sigma' coordinate: must have either 0 or 2 bounds, " + "got {sigma.nbounds:d}") + for coord in (pressure_at_top, surface_air_pressure): + if coord.nbounds: + msg = (f"Coordinate '{coord.name()}' has bounds. These will " + "be disregarded") + warnings.warn(msg, UserWarning, stacklevel=2) + + # Check units + if not sigma.units.is_dimensionless(): + raise ValueError( + "Invalid units: 'sigma' must be dimensionless, got " + "'{sigma.units}'") + if pressure_at_top.units != surface_air_pressure.units: + raise ValueError( + "Incompatible units: 'pressure_at_top' and " + "'surface_air_pressure' must have the same units, got " + "'{pressure_at_top.units}' and '{surface_air_pressure.units}'") + if not pressure_at_top.units.is_convertible('Pa'): + raise ValueError( + "Invalid units: 'pressure_at_top' and 'surface_air_pressure' " + "must have units of pressure") + + @property + def dependencies(self): + """Return dependencies.""" + dependencies = { + 'pressure_at_top': self.pressure_at_top, + 'sigma': self.sigma, + 'surface_air_pressure': self.surface_air_pressure, + } + return dependencies + + @staticmethod + def _derive(pressure_at_top, sigma, surface_air_pressure): + """Derive coordinate.""" + return pressure_at_top + sigma * (surface_air_pressure - + pressure_at_top) + + def make_coord(self, coord_dims_func): + """Make new :class:`iris.coords.AuxCoord`.""" + # Which dimensions are relevant? + derived_dims = self.derived_dims(coord_dims_func) + dependency_dims = self._dependency_dims(coord_dims_func) + + # Build the points array + nd_points_by_key = self._remap(dependency_dims, derived_dims) + points = self._derive(nd_points_by_key['pressure_at_top'], + nd_points_by_key['sigma'], + nd_points_by_key['surface_air_pressure']) + + # Bounds + bounds = None + if self.sigma.nbounds: + nd_values_by_key = self._remap_with_bounds(dependency_dims, + derived_dims) + pressure_at_top = nd_values_by_key['pressure_at_top'] + sigma = nd_values_by_key['sigma'] + surface_air_pressure = nd_values_by_key['surface_air_pressure'] + ok_bound_shapes = [(), (1,), (2,)] + if sigma.shape[-1:] not in ok_bound_shapes: + raise ValueError("Invalid sigma coordinate bounds") + if pressure_at_top.shape[-1:] not in [(), (1,)]: + warnings.warn( + "Pressure at top coordinate has bounds. These are being " + "disregarded") + pressure_at_top_pts = nd_points_by_key['pressure_at_top'] + bds_shape = list(pressure_at_top_pts.shape) + [1] + pressure_at_top = pressure_at_top_pts.reshape(bds_shape) + if surface_air_pressure.shape[-1:] not in [(), (1,)]: + warnings.warn( + "Surface pressure coordinate has bounds. These are being " + "disregarded") + surface_air_pressure_pts = nd_points_by_key[ + 'surface_air_pressure'] + bds_shape = list(surface_air_pressure_pts.shape) + [1] + surface_air_pressure = surface_air_pressure_pts.reshape( + bds_shape) + bounds = self._derive(pressure_at_top, sigma, surface_air_pressure) + + # Create coordinate + return iris.coords.AuxCoord( + points, standard_name=self.standard_name, long_name=self.long_name, + var_name=self.var_name, units=self.units, bounds=bounds, + attributes=self.attributes, coord_system=self.coord_system) + + def update(self, old_coord, new_coord=None): + """Notify the factory of the removal/replacement of a coordinate.""" + new_dependencies = self.dependencies + for (name, coord) in self.dependencies.items(): + if old_coord is coord: + new_dependencies[name] = new_coord + try: + self._check_dependencies(**new_dependencies) + except ValueError as exc: + raise ValueError(f"Failed to update dependencies: {exc}") + else: + setattr(self, name, new_coord) + break + + +def add_aux_coords_from_cubes(cube, cubes, coord_dict): + """Add auxiliary coordinate to cube from another cube in list of cubes.""" + for (coord_name, coord_dims) in coord_dict.items(): + coord_cube = cubes.extract(var_name_constraint(coord_name)) + if len(coord_cube) != 1: + raise ValueError( + f"Expected exactly one coordinate cube '{coord_name}' in " + f"list of cubes {cubes}, got {len(coord_cube):d}") + coord_cube = coord_cube[0] + aux_coord = cube_to_aux_coord(coord_cube) + cube.add_aux_coord(aux_coord, coord_dims) + cubes.remove(coord_cube) + + +def add_pressure_level_coordinate(cube): + """Add pressure level coordinate.""" + if cube.coords('altitude'): + height_coord = cube.coord('altitude') + if height_coord.units != 'm': + height_coord.convert_units('m') + pressure_points = ALTITUDE_TO_PRESSURE(height_coord.core_points()) + pressure_bounds = ALTITUDE_TO_PRESSURE(height_coord.core_bounds()) + pressure_coord = iris.coords.AuxCoord(pressure_points, + bounds=pressure_bounds, + var_name='plev', + standard_name='air_pressure', + long_name='pressure', + units='Pa') + cube.add_aux_coord(pressure_coord, cube.coord_dims(height_coord)) + elif cube.coords('atmosphere_sigma_coordinate'): + aux_factory = AtmosphereSigmaFactory( + pressure_at_top=cube.coord(var_name='ptop'), + sigma=cube.coord(var_name='lev'), + surface_air_pressure=cube.coord(var_name='ps'), + ) + cube.add_aux_factory(aux_factory) + else: + raise ValueError( + "Cannot add 'air_pressure' coordinate, 'altitude' or " + "'atmosphere_sigma_coordinate' not available") + + def add_scalar_depth_coord(cube, depth=0.0): """Add scalar coordinate 'depth' with value of `depth`m.""" logger.debug("Adding depth coordinate (%sm)", depth) @@ -71,7 +283,7 @@ def add_scalar_typesea_coord(cube, value='default'): def cube_to_aux_coord(cube): - """Convert cube to iris AuxCoord""" + """Convert cube to iris AuxCoord.""" return iris.coords.AuxCoord( points=cube.core_data(), var_name=cube.var_name, @@ -81,8 +293,34 @@ def cube_to_aux_coord(cube): ) +def get_bounds_cube(cubes, coord_var_name): + """Find bound cube for a given variable in a list of cubes.""" + for bounds in ('bnds', 'bounds'): + bound_var = f'{coord_var_name}_{bounds}' + cube = cubes.extract(var_name_constraint(bound_var)) + if len(cube) == 1: + return cube[0] + if len(cube) > 1: + raise ValueError( + f"Multiple cubes with var_name '{bound_var}' found") + raise ValueError( + f"No bounds for coordinate variable '{coord_var_name}' available in " + f"cubes\n{cubes}") + + +def fix_bounds(cube, cubes, coord_var_names): + """Fix bounds for cube that could not be read correctly by :mod:`iris`.""" + for coord_var_name in coord_var_names: + coord = cube.coord(var_name=coord_var_name) + if coord.bounds is not None: + continue + bounds_cube = get_bounds_cube(cubes, coord_var_name) + cube.coord(var_name=coord_var_name).bounds = bounds_cube.core_data() + logger.debug("Fixed bounds of coordinate '%s'", coord_var_name) + + def round_coordinates(cubes, decimals=5, coord_names=None): - """Round all dimensional coordinates of all cubes in place + """Round all dimensional coordinates of all cubes in place. Cubes can be a list of Iris cubes, or an Iris `CubeList`. @@ -91,22 +329,23 @@ def round_coordinates(cubes, decimals=5, coord_names=None): Parameters ---------- - - cubes: iris.cube.CubeList (or a list of iris.cube.Cube). + cubes : iris.cube.CubeList or list of iris.cube.Cube + Cubes which are modified in place. - - decimals: number of decimals to round to. + decimals : int + Number of decimals to round to. - - coord_names: list of strings, or None. - If None (or a falsey value), all dimensional coordinates will - be rounded. - Otherwise, only coordinates given by the names in - `coord_names` are rounded. + coord_names : list of str or None + If ``None`` (or a falsey value), all dimensional coordinates will be + rounded. Otherwise, only coordinates given by the names in + ``coord_names`` are rounded. Returns ------- - The modified input `cubes` + iris.cube.CubeList or list of iris.cube.Cube + The modified input ``cubes``. """ - for cube in cubes: if not coord_names: coords = cube.coords(dim_coords=True) diff --git a/esmvalcore/cmor/_fixes/us_standard_atmosphere.csv b/esmvalcore/cmor/_fixes/us_standard_atmosphere.csv new file mode 100644 index 0000000000..1280d65543 --- /dev/null +++ b/esmvalcore/cmor/_fixes/us_standard_atmosphere.csv @@ -0,0 +1,913 @@ +# Source: https://www.digitaldutch.com/atmoscalc/table.htm +Altitude [m],Temperature [K],Pressure [Pa],Density [kg m-3],Speed of sound [m s-1],Viscosity [Pa s] +-5000,320.65,177687,1.93047,358.972,1.97023E-05 +-4900,320,175802,1.91387,358.608,1.96714E-05 +-4800,319.35,173933,1.89738,358.244,1.96405E-05 +-4700,318.7,172081,1.881,357.879,1.96096E-05 +-4600,318.05,170244,1.86472,357.514,1.95787E-05 +-4500,317.4,168423,1.84856,357.148,1.95477E-05 +-4400,316.75,166618,1.8325,356.782,1.95167E-05 +-4300,316.1,164829,1.81655,356.416,1.94856E-05 +-4200,315.45,163055,1.80071,356.049,1.94545E-05 +-4100,314.8,161297,1.78497,355.682,1.94234E-05 +-4000,314.15,159554,1.76933,355.315,1.93923E-05 +-3900,313.5,157827,1.75381,354.947,1.93611E-05 +-3800,312.85,156115,1.73838,354.579,1.93299E-05 +-3700,312.2,154417,1.72306,354.211,1.92987E-05 +-3600,311.55,152735,1.70785,353.842,1.92674E-05 +-3500,310.9,151068,1.69273,353.472,1.92362E-05 +-3400,310.25,149415,1.67772,353.103,1.92048E-05 +-3300,309.6,147777,1.66282,352.733,1.91735E-05 +-3200,308.95,146154,1.64801,352.362,1.91421E-05 +-3100,308.3,144545,1.6333,351.991,1.91107E-05 +-3000,307.65,142950,1.6187,351.62,1.90792E-05 +-2900,307,141370,1.60419,351.248,1.90477E-05 +-2800,306.35,139804,1.58979,350.876,1.90162E-05 +-2700,305.7,138252,1.57548,350.504,1.89847E-05 +-2600,305.05,136714,1.56127,350.131,1.89531E-05 +-2500,304.4,135190,1.54717,349.758,1.89215E-05 +-2400,303.75,133679,1.53315,349.384,1.88898E-05 +-2300,303.1,132183,1.51924,349.01,1.88582E-05 +-2200,302.45,130699,1.50542,348.636,1.88265E-05 +-2100,301.8,129230,1.4917,348.261,1.87947E-05 +-2000,301.15,127774,1.47808,347.886,1.8763E-05 +-1900,300.5,126331,1.46455,347.51,1.87311E-05 +-1800,299.85,124901,1.45111,347.134,1.86993E-05 +-1700,299.2,123485,1.43777,346.757,1.86674E-05 +-1600,298.55,122081,1.42452,346.381,1.86355E-05 +-1500,297.9,120691,1.41137,346.003,1.86036E-05 +-1400,297.25,119313,1.39831,345.626,1.85716E-05 +-1300,296.6,117948,1.38535,345.248,1.85396E-05 +-1200,295.95,116596,1.37247,344.869,1.85076E-05 +-1100,295.3,115256,1.35969,344.49,1.84755E-05 +-1000,294.65,113929,1.347,344.111,1.84434E-05 +-900,294,112614,1.33439,343.731,1.84113E-05 +-800,293.35,111312,1.32188,343.351,1.83791E-05 +-700,292.7,110022,1.30946,342.97,1.83469E-05 +-600,292.05,108744,1.29713,342.589,1.83147E-05 +-500,291.4,107478,1.28489,342.208,1.82824E-05 +-400,290.75,106223,1.27274,341.826,1.82501E-05 +-300,290.1,104981,1.26067,341.444,1.82178E-05 +-200,289.45,103751,1.24869,341.061,1.81854E-05 +-100,288.8,102532,1.2368,340.678,1.8153E-05 +0,288.15,101325,1.225,340.294,1.81206E-05 +100,287.5,100129,1.21328,339.91,1.80881E-05 +200,286.85,98945.3,1.20165,339.526,1.80556E-05 +300,286.2,97772.6,1.19011,339.141,1.8023E-05 +400,285.55,96611.1,1.17864,338.755,1.79905E-05 +500,284.9,95460.8,1.16727,338.37,1.79579E-05 +600,284.25,94321.7,1.15598,337.983,1.79252E-05 +700,283.6,93193.6,1.14477,337.597,1.78925E-05 +800,282.95,92076.4,1.13364,337.21,1.78598E-05 +900,282.3,90970.1,1.1226,336.822,1.78271E-05 +1000,281.65,89874.6,1.11164,336.434,1.77943E-05 +1100,281,88789.8,1.10077,336.046,1.77615E-05 +1200,280.35,87715.6,1.08997,335.657,1.77286E-05 +1300,279.7,86651.9,1.07925,335.267,1.76957E-05 +1400,279.05,85598.8,1.06862,334.878,1.76628E-05 +1500,278.4,84556,1.05807,334.487,1.76298E-05 +1600,277.75,83523.5,1.04759,334.097,1.75968E-05 +1700,277.1,82501.3,1.0372,333.705,1.75638E-05 +1800,276.45,81489.2,1.02688,333.314,1.75307E-05 +1900,275.8,80487.2,1.01665,332.922,1.74976E-05 +2000,275.15,79495.2,1.00649,332.529,1.74645E-05 +2100,274.5,78513.1,0.99641,332.136,1.74313E-05 +2200,273.85,77540.9,0.986407,331.743,1.73981E-05 +2300,273.2,76578.4,0.976481,331.349,1.73649E-05 +2400,272.55,75625.7,0.966632,330.954,1.73316E-05 +2500,271.9,74682.5,0.956859,330.56,1.72983E-05 +2600,271.25,73748.9,0.947162,330.164,1.72649E-05 +2700,270.6,72824.8,0.93754,329.768,1.72315E-05 +2800,269.95,71910.1,0.927993,329.372,1.71981E-05 +2900,269.3,71004.7,0.91852,328.975,1.71646E-05 +3000,268.65,70108.5,0.909122,328.578,1.71311E-05 +3100,268,69221.6,0.899798,328.18,1.70976E-05 +3200,267.35,68343.7,0.890546,327.782,1.7064E-05 +3300,266.7,67474.9,0.881368,327.383,1.70304E-05 +3400,266.05,66615,0.872262,326.984,1.69967E-05 +3500,265.4,65764.1,0.863229,326.584,1.6963E-05 +3600,264.75,64921.9,0.854267,326.184,1.69293E-05 +3700,264.1,64088.6,0.845377,325.784,1.68955E-05 +3800,263.45,63263.9,0.836557,325.382,1.68617E-05 +3900,262.8,62447.8,0.827808,324.981,1.68279E-05 +4000,262.15,61640.2,0.819129,324.579,1.6794E-05 +4100,261.5,60841.2,0.81052,324.176,1.67601E-05 +4200,260.85,60050.5,0.801981,323.773,1.67261E-05 +4300,260.2,59268.2,0.79351,323.369,1.66922E-05 +4400,259.55,58494.2,0.785108,322.965,1.66581E-05 +4500,258.9,57728.3,0.776775,322.56,1.66241E-05 +4600,258.25,56970.6,0.768509,322.155,1.659E-05 +4700,257.6,56221,0.76031,321.75,1.65558E-05 +4800,256.95,55479.4,0.752179,321.343,1.65216E-05 +4900,256.3,54745.7,0.744114,320.937,1.64874E-05 +5000,255.65,54019.9,0.736116,320.529,1.64531E-05 +5100,255,53301.9,0.728183,320.122,1.64188E-05 +5200,254.35,52591.7,0.720317,319.713,1.63845E-05 +5300,253.7,51889.1,0.712515,319.305,1.63501E-05 +5400,253.05,51194.2,0.704778,318.895,1.63157E-05 +5500,252.4,50506.8,0.697106,318.486,1.62813E-05 +5600,251.75,49826.9,0.689497,318.075,1.62468E-05 +5700,251.1,49154.5,0.681953,317.664,1.62122E-05 +5800,250.45,48489.4,0.674471,317.253,1.61777E-05 +5900,249.8,47831.6,0.667053,316.841,1.6143E-05 +6000,249.15,47181,0.659697,316.428,1.61084E-05 +6100,248.5,46537.7,0.652404,316.015,1.60737E-05 +6200,247.85,45901.4,0.645172,315.602,1.6039E-05 +6300,247.2,45272.3,0.638002,315.188,1.60042E-05 +6400,246.55,44650.1,0.630892,314.773,1.59694E-05 +6500,245.9,44034.8,0.623844,314.358,1.59345E-05 +6600,245.25,43426.5,0.616856,313.942,1.58996E-05 +6700,244.6,42825,0.609928,313.526,1.58647E-05 +6800,243.95,42230.2,0.60306,313.109,1.58297E-05 +6900,243.3,41642.2,0.596251,312.692,1.57947E-05 +7000,242.65,41060.7,0.589501,312.274,1.57596E-05 +7100,242,40485.9,0.58281,311.855,1.57245E-05 +7200,241.35,39917.6,0.576177,311.436,1.56894E-05 +7300,240.7,39355.8,0.569602,311.016,1.56542E-05 +7400,240.05,38800.4,0.563084,310.596,1.5619E-05 +7500,239.4,38251.4,0.556624,310.175,1.55837E-05 +7600,238.75,37708.7,0.55022,309.754,1.55484E-05 +7700,238.1,37172.2,0.543873,309.332,1.55131E-05 +7800,237.45,36642,0.537582,308.909,1.54777E-05 +7900,236.8,36117.8,0.531347,308.486,1.54423E-05 +8000,236.15,35599.8,0.525168,308.063,1.54068E-05 +8100,235.5,35087.8,0.519043,307.638,1.53713E-05 +8200,234.85,34581.8,0.512973,307.214,1.53357E-05 +8300,234.2,34081.7,0.506958,306.788,1.53001E-05 +8400,233.55,33587.5,0.500997,306.362,1.52645E-05 +8500,232.9,33099,0.49509,305.935,1.52288E-05 +8600,232.25,32616.4,0.489236,305.508,1.51931E-05 +8700,231.6,32139.5,0.483435,305.08,1.51573E-05 +8800,230.95,31668.2,0.477687,304.652,1.51215E-05 +8900,230.3,31202.6,0.471992,304.223,1.50857E-05 +9000,229.65,30742.5,0.466348,303.793,1.50498E-05 +9100,229,30287.9,0.460756,303.363,1.50138E-05 +9200,228.35,29838.7,0.455216,302.932,1.49778E-05 +9300,227.7,29395,0.449727,302.501,1.49418E-05 +9400,227.05,28956.7,0.444289,302.069,1.49058E-05 +9500,226.4,28523.6,0.438901,301.636,1.48696E-05 +9600,225.75,28095.8,0.433563,301.203,1.48335E-05 +9700,225.1,27673.2,0.428275,300.769,1.47973E-05 +9800,224.45,27255.8,0.423036,300.334,1.4761E-05 +9900,223.8,26843.5,0.417847,299.899,1.47247E-05 +10000,223.15,26436.3,0.412707,299.463,1.46884E-05 +10100,222.5,26034,0.407615,299.027,1.4652E-05 +10200,221.85,25636.8,0.402571,298.59,1.46156E-05 +10300,221.2,25244.5,0.397575,298.152,1.45792E-05 +10400,220.55,24857,0.392627,297.714,1.45426E-05 +10500,219.9,24474.4,0.387725,297.275,1.45061E-05 +10600,219.25,24096.5,0.382871,296.835,1.44695E-05 +10700,218.6,23723.4,0.378064,296.395,1.44328E-05 +10800,217.95,23355,0.373303,295.954,1.43962E-05 +10900,217.3,22991.2,0.368588,295.512,1.43594E-05 +11000,216.65,22632.1,0.363918,295.07,1.43226E-05 +11100,216.65,22278,0.358224,295.07,1.43226E-05 +11200,216.65,21929.4,0.35262,295.07,1.43226E-05 +11300,216.65,21586.3,0.347103,295.07,1.43226E-05 +11400,216.65,21248.6,0.341673,295.07,1.43226E-05 +11500,216.65,20916.2,0.336327,295.07,1.43226E-05 +11600,216.65,20589,0.331065,295.07,1.43226E-05 +11700,216.65,20266.8,0.325886,295.07,1.43226E-05 +11800,216.65,19949.8,0.320787,295.07,1.43226E-05 +11900,216.65,19637.6,0.315768,295.07,1.43226E-05 +12000,216.65,19330.4,0.310828,295.07,1.43226E-05 +12100,216.65,19028,0.305965,295.07,1.43226E-05 +12200,216.65,18730.3,0.301178,295.07,1.43226E-05 +12300,216.65,18437.2,0.296466,295.07,1.43226E-05 +12400,216.65,18148.8,0.291828,295.07,1.43226E-05 +12500,216.65,17864.8,0.287262,295.07,1.43226E-05 +12600,216.65,17585.4,0.282768,295.07,1.43226E-05 +12700,216.65,17310.2,0.278344,295.07,1.43226E-05 +12800,216.65,17039.4,0.273989,295.07,1.43226E-05 +12900,216.65,16772.8,0.269703,295.07,1.43226E-05 +13000,216.65,16510.4,0.265483,295.07,1.43226E-05 +13100,216.65,16252.1,0.26133,295.07,1.43226E-05 +13200,216.65,15997.8,0.257241,295.07,1.43226E-05 +13300,216.65,15747.5,0.253217,295.07,1.43226E-05 +13400,216.65,15501.2,0.249255,295.07,1.43226E-05 +13500,216.65,15258.7,0.245355,295.07,1.43226E-05 +13600,216.65,15019.9,0.241517,295.07,1.43226E-05 +13700,216.65,14784.9,0.237738,295.07,1.43226E-05 +13800,216.65,14553.6,0.234019,295.07,1.43226E-05 +13900,216.65,14325.9,0.230357,295.07,1.43226E-05 +14000,216.65,14101.8,0.226753,295.07,1.43226E-05 +14100,216.65,13881.2,0.223206,295.07,1.43226E-05 +14200,216.65,13664,0.219714,295.07,1.43226E-05 +14300,216.65,13450.2,0.216276,295.07,1.43226E-05 +14400,216.65,13239.8,0.212893,295.07,1.43226E-05 +14500,216.65,13032.7,0.209562,295.07,1.43226E-05 +14600,216.65,12828.8,0.206283,295.07,1.43226E-05 +14700,216.65,12628.1,0.203056,295.07,1.43226E-05 +14800,216.65,12430.5,0.199879,295.07,1.43226E-05 +14900,216.65,12236,0.196752,295.07,1.43226E-05 +15000,216.65,12044.6,0.193674,295.07,1.43226E-05 +15100,216.65,11856.1,0.190644,295.07,1.43226E-05 +15200,216.65,11670.6,0.187661,295.07,1.43226E-05 +15300,216.65,11488.1,0.184725,295.07,1.43226E-05 +15400,216.65,11308.3,0.181835,295.07,1.43226E-05 +15500,216.65,11131.4,0.17899,295.07,1.43226E-05 +15600,216.65,10957.2,0.17619,295.07,1.43226E-05 +15700,216.65,10785.8,0.173433,295.07,1.43226E-05 +15800,216.65,10617.1,0.17072,295.07,1.43226E-05 +15900,216.65,10451,0.168049,295.07,1.43226E-05 +16000,216.65,10287.5,0.16542,295.07,1.43226E-05 +16100,216.65,10126.5,0.162832,295.07,1.43226E-05 +16200,216.65,9968.08,0.160284,295.07,1.43226E-05 +16300,216.65,9812.13,0.157777,295.07,1.43226E-05 +16400,216.65,9658.61,0.155308,295.07,1.43226E-05 +16500,216.65,9507.5,0.152878,295.07,1.43226E-05 +16600,216.65,9358.76,0.150487,295.07,1.43226E-05 +16700,216.65,9212.34,0.148132,295.07,1.43226E-05 +16800,216.65,9068.21,0.145815,295.07,1.43226E-05 +16900,216.65,8926.34,0.143533,295.07,1.43226E-05 +17000,216.65,8786.68,0.141288,295.07,1.43226E-05 +17100,216.65,8649.21,0.139077,295.07,1.43226E-05 +17200,216.65,8513.89,0.136901,295.07,1.43226E-05 +17300,216.65,8380.69,0.134759,295.07,1.43226E-05 +17400,216.65,8249.58,0.132651,295.07,1.43226E-05 +17500,216.65,8120.51,0.130576,295.07,1.43226E-05 +17600,216.65,7993.46,0.128533,295.07,1.43226E-05 +17700,216.65,7868.4,0.126522,295.07,1.43226E-05 +17800,216.65,7745.3,0.124543,295.07,1.43226E-05 +17900,216.65,7624.13,0.122594,295.07,1.43226E-05 +18000,216.65,7504.84,0.120676,295.07,1.43226E-05 +18100,216.65,7387.43,0.118788,295.07,1.43226E-05 +18200,216.65,7271.85,0.11693,295.07,1.43226E-05 +18300,216.65,7158.08,0.1151,295.07,1.43226E-05 +18400,216.65,7046.09,0.113299,295.07,1.43226E-05 +18500,216.65,6935.86,0.111527,295.07,1.43226E-05 +18600,216.65,6827.34,0.109782,295.07,1.43226E-05 +18700,216.65,6720.53,0.108064,295.07,1.43226E-05 +18800,216.65,6615.39,0.106374,295.07,1.43226E-05 +18900,216.65,6511.89,0.10471,295.07,1.43226E-05 +19000,216.65,6410.01,0.103071,295.07,1.43226E-05 +19100,216.65,6309.72,0.101459,295.07,1.43226E-05 +19200,216.65,6211,0.0998714,295.07,1.43226E-05 +19300,216.65,6113.83,0.0983089,295.07,1.43226E-05 +19400,216.65,6018.18,0.0967709,295.07,1.43226E-05 +19500,216.65,5924.03,0.0952569,295.07,1.43226E-05 +19600,216.65,5831.34,0.0937666,295.07,1.43226E-05 +19700,216.65,5740.11,0.0922996,295.07,1.43226E-05 +19800,216.65,5650.31,0.0908555,295.07,1.43226E-05 +19900,216.65,5561.91,0.0894341,295.07,1.43226E-05 +20000,216.65,5474.89,0.0880349,295.07,1.43226E-05 +20100,216.75,5389.25,0.0866179,295.138,1.43283E-05 +20200,216.85,5304.99,0.0852243,295.206,1.4334E-05 +20300,216.95,5222.09,0.0838538,295.274,1.43396E-05 +20400,217.05,5140.52,0.082506,295.342,1.43453E-05 +20500,217.15,5060.26,0.0811804,295.41,1.43509E-05 +20600,217.25,4981.29,0.0798768,295.478,1.43566E-05 +20700,217.35,4903.59,0.0785946,295.546,1.43622E-05 +20800,217.45,4827.14,0.0773336,295.614,1.43679E-05 +20900,217.55,4751.91,0.0760934,295.682,1.43736E-05 +21000,217.65,4677.89,0.0748737,295.75,1.43792E-05 +21100,217.75,4605.05,0.073674,295.818,1.43849E-05 +21200,217.85,4533.38,0.0724941,295.886,1.43905E-05 +21300,217.95,4462.86,0.0713336,295.954,1.43962E-05 +21400,218.05,4393.47,0.0701923,296.021,1.44018E-05 +21500,218.15,4325.18,0.0690697,296.089,1.44074E-05 +21600,218.25,4257.99,0.0679655,296.157,1.44131E-05 +21700,218.35,4191.87,0.0668795,296.225,1.44187E-05 +21800,218.45,4126.81,0.0658114,296.293,1.44244E-05 +21900,218.55,4062.79,0.0647607,296.361,1.443E-05 +22000,218.65,3999.79,0.0637273,296.428,1.44357E-05 +22100,218.75,3937.79,0.0627109,296.496,1.44413E-05 +22200,218.85,3876.79,0.0617111,296.564,1.44469E-05 +22300,218.95,3816.75,0.0607278,296.632,1.44526E-05 +22400,219.05,3757.68,0.0597605,296.699,1.44582E-05 +22500,219.15,3699.54,0.0588091,296.767,1.44639E-05 +22600,219.25,3642.33,0.0578732,296.835,1.44695E-05 +22700,219.35,3586.03,0.0569526,296.903,1.44751E-05 +22800,219.45,3530.62,0.0560471,296.97,1.44808E-05 +22900,219.55,3476.09,0.0551564,297.038,1.44864E-05 +23000,219.65,3422.43,0.0542803,297.105,1.4492E-05 +23100,219.75,3369.63,0.0534184,297.173,1.44976E-05 +23200,219.85,3317.66,0.0525706,297.241,1.45033E-05 +23300,219.95,3266.51,0.0517367,297.308,1.45089E-05 +23400,220.05,3216.18,0.0509164,297.376,1.45145E-05 +23500,220.15,3166.65,0.0501094,297.443,1.45202E-05 +23600,220.25,3117.9,0.0493155,297.511,1.45258E-05 +23700,220.35,3069.92,0.0485346,297.579,1.45314E-05 +23800,220.45,3022.7,0.0477665,297.646,1.4537E-05 +23900,220.55,2976.23,0.0470108,297.714,1.45426E-05 +24000,220.65,2930.49,0.0462674,297.781,1.45483E-05 +24100,220.75,2885.48,0.045536,297.849,1.45539E-05 +24200,220.85,2841.18,0.0448166,297.916,1.45595E-05 +24300,220.95,2797.58,0.0441089,297.983,1.45651E-05 +24400,221.05,2754.66,0.0434126,298.051,1.45707E-05 +24500,221.15,2712.42,0.0427276,298.118,1.45763E-05 +24600,221.25,2670.85,0.0420538,298.186,1.4582E-05 +24700,221.35,2629.94,0.0413909,298.253,1.45876E-05 +24800,221.45,2589.67,0.0407387,298.32,1.45932E-05 +24900,221.55,2550.03,0.0400971,298.388,1.45988E-05 +25000,221.65,2511.02,0.0394658,298.455,1.46044E-05 +25100,221.75,2472.63,0.0388448,298.522,1.461E-05 +25200,221.85,2434.83,0.0382338,298.59,1.46156E-05 +25300,221.95,2397.63,0.0376327,298.657,1.46212E-05 +25400,222.05,2361.02,0.0370414,298.724,1.46268E-05 +25500,222.15,2324.98,0.0364595,298.791,1.46324E-05 +25600,222.25,2289.51,0.0358871,298.859,1.4638E-05 +25700,222.35,2254.59,0.0353239,298.926,1.46436E-05 +25800,222.45,2220.22,0.0347698,298.993,1.46492E-05 +25900,222.55,2186.39,0.0342246,299.06,1.46548E-05 +26000,222.65,2153.09,0.0336882,299.128,1.46604E-05 +26100,222.75,2120.32,0.0331605,299.195,1.4666E-05 +26200,222.85,2088.05,0.0326413,299.262,1.46716E-05 +26300,222.95,2056.29,0.0321304,299.329,1.46772E-05 +26400,223.05,2025.03,0.0316277,299.396,1.46828E-05 +26500,223.15,1994.26,0.0311331,299.463,1.46884E-05 +26600,223.25,1963.97,0.0306465,299.53,1.4694E-05 +26700,223.35,1934.15,0.0301677,299.597,1.46996E-05 +26800,223.45,1904.8,0.0296966,299.664,1.47052E-05 +26900,223.55,1875.9,0.029233,299.732,1.47108E-05 +27000,223.65,1847.46,0.0287769,299.799,1.47164E-05 +27100,223.75,1819.46,0.0283281,299.866,1.4722E-05 +27200,223.85,1791.89,0.0278865,299.933,1.47275E-05 +27300,223.95,1764.76,0.0274519,300,1.47331E-05 +27400,224.05,1738.05,0.0270244,300.067,1.47387E-05 +27500,224.15,1711.75,0.0266036,300.133,1.47443E-05 +27600,224.25,1685.87,0.0261896,300.2,1.47499E-05 +27700,224.35,1660.39,0.0257823,300.267,1.47555E-05 +27800,224.45,1635.3,0.0253814,300.334,1.4761E-05 +27900,224.55,1610.6,0.024987,300.401,1.47666E-05 +28000,224.65,1586.29,0.0245988,300.468,1.47722E-05 +28100,224.75,1562.35,0.0242169,300.535,1.47778E-05 +28200,224.85,1538.79,0.023841,300.602,1.47833E-05 +28300,224.95,1515.59,0.0234712,300.669,1.47889E-05 +28400,225.05,1492.75,0.0231072,300.735,1.47945E-05 +28500,225.15,1470.27,0.022749,300.802,1.48001E-05 +28600,225.25,1448.13,0.0223966,300.869,1.48056E-05 +28700,225.35,1426.34,0.0220498,300.936,1.48112E-05 +28800,225.45,1404.89,0.0217084,301.003,1.48168E-05 +28900,225.55,1383.76,0.0213726,301.069,1.48223E-05 +29000,225.65,1362.96,0.021042,301.136,1.48279E-05 +29100,225.75,1342.49,0.0207167,301.203,1.48335E-05 +29200,225.85,1322.33,0.0203966,301.269,1.4839E-05 +29300,225.95,1302.48,0.0200816,301.336,1.48446E-05 +29400,226.05,1282.94,0.0197716,301.403,1.48502E-05 +29500,226.15,1263.7,0.0194664,301.469,1.48557E-05 +29600,226.25,1244.76,0.0191662,301.536,1.48613E-05 +29700,226.35,1226.11,0.0188707,301.603,1.48669E-05 +29800,226.45,1207.75,0.0185798,301.669,1.48724E-05 +29900,226.55,1189.67,0.0182936,301.736,1.4878E-05 +30000,226.65,1171.87,0.0180119,301.803,1.48835E-05 +30100,226.75,1154.34,0.0177347,301.869,1.48891E-05 +30200,226.85,1137.08,0.0174619,301.936,1.48946E-05 +30300,226.95,1120.09,0.0171934,302.002,1.49002E-05 +30400,227.05,1103.36,0.0169291,302.069,1.49058E-05 +30500,227.15,1086.88,0.016669,302.135,1.49113E-05 +30600,227.25,1070.66,0.016413,302.202,1.49169E-05 +30700,227.35,1054.69,0.016161,302.268,1.49224E-05 +30800,227.45,1038.97,0.015913,302.335,1.4928E-05 +30900,227.55,1023.48,0.015669,302.401,1.49335E-05 +31000,227.65,1008.23,0.0154288,302.468,1.4939E-05 +31100,227.75,993.218,0.0151923,302.534,1.49446E-05 +31200,227.85,978.434,0.0149596,302.6,1.49501E-05 +31300,227.95,963.876,0.0147306,302.667,1.49557E-05 +31400,228.05,949.541,0.0145051,302.733,1.49612E-05 +31500,228.15,935.425,0.0142832,302.8,1.49668E-05 +31600,228.25,921.526,0.0140648,302.866,1.49723E-05 +31700,228.35,907.838,0.0138499,302.932,1.49778E-05 +31800,228.45,894.36,0.0136383,302.999,1.49834E-05 +31900,228.55,881.088,0.01343,303.065,1.49889E-05 +32000,228.65,868.019,0.013225,303.131,1.49945E-05 +32100,228.93,855.154,0.0130131,303.317,1.501E-05 +32200,229.21,842.495,0.0128048,303.502,1.50254E-05 +32300,229.49,830.038,0.0126001,303.688,1.50409E-05 +32400,229.77,817.781,0.0123989,303.873,1.50564E-05 +32500,230.05,805.719,0.0122011,304.058,1.50719E-05 +32600,230.33,793.849,0.0120068,304.243,1.50873E-05 +32700,230.61,782.168,0.0118157,304.428,1.51028E-05 +32800,230.89,770.674,0.011628,304.612,1.51182E-05 +32900,231.17,759.361,0.0114434,304.797,1.51336E-05 +33000,231.45,748.228,0.011262,304.982,1.51491E-05 +33100,231.73,737.272,0.0110837,305.166,1.51645E-05 +33200,232.01,726.489,0.0109084,305.35,1.51799E-05 +33300,232.29,715.876,0.0107361,305.535,1.51953E-05 +33400,232.57,705.431,0.0105667,305.719,1.52107E-05 +33500,232.85,695.15,0.0104002,305.903,1.52261E-05 +33600,233.13,685.032,0.0102365,306.086,1.52414E-05 +33700,233.41,675.072,0.0100756,306.27,1.52568E-05 +33800,233.69,665.269,0.00991734,306.454,1.52722E-05 +33900,233.97,655.62,0.00976181,306.637,1.52875E-05 +34000,234.25,646.122,0.00960889,306.821,1.53029E-05 +34100,234.53,636.773,0.00945855,307.004,1.53182E-05 +34200,234.81,627.57,0.00931073,307.187,1.53335E-05 +34300,235.09,618.511,0.0091654,307.37,1.53489E-05 +34400,235.37,609.593,0.0090225,307.553,1.53642E-05 +34500,235.65,600.814,0.008882,307.736,1.53795E-05 +34600,235.93,592.172,0.00874385,307.919,1.53948E-05 +34700,236.21,583.664,0.008608,308.102,1.54101E-05 +34800,236.49,575.288,0.00847443,308.284,1.54254E-05 +34900,236.77,567.042,0.00834308,308.467,1.54406E-05 +35000,237.05,558.924,0.00821392,308.649,1.54559E-05 +35100,237.33,550.931,0.00808691,308.831,1.54712E-05 +35200,237.61,543.062,0.00796201,309.013,1.54864E-05 +35300,237.89,535.314,0.00783918,309.195,1.55017E-05 +35400,238.17,527.686,0.00771839,309.377,1.55169E-05 +35500,238.45,520.175,0.00759959,309.559,1.55321E-05 +35600,238.73,512.78,0.00748277,309.741,1.55474E-05 +35700,239.01,505.498,0.00736787,309.922,1.55626E-05 +35800,239.29,498.329,0.00725486,310.104,1.55778E-05 +35900,239.57,491.269,0.00714372,310.285,1.5593E-05 +36000,239.85,484.317,0.00703441,310.467,1.56082E-05 +36100,240.13,477.471,0.0069269,310.648,1.56233E-05 +36200,240.41,470.73,0.00682115,310.829,1.56385E-05 +36300,240.69,464.092,0.00671714,311.01,1.56537E-05 +36400,240.97,457.555,0.00661483,311.191,1.56688E-05 +36500,241.25,451.118,0.00651419,311.371,1.5684E-05 +36600,241.53,444.778,0.0064152,311.552,1.56991E-05 +36700,241.81,438.535,0.00631783,311.733,1.57143E-05 +36800,242.09,432.386,0.00622205,311.913,1.57294E-05 +36900,242.37,426.331,0.00612782,312.093,1.57445E-05 +37000,242.65,420.367,0.00603513,312.274,1.57596E-05 +37100,242.93,414.494,0.00594394,312.454,1.57748E-05 +37200,243.21,408.709,0.00585424,312.634,1.57898E-05 +37300,243.49,403.011,0.00576599,312.814,1.58049E-05 +37400,243.77,397.399,0.00567917,312.993,1.582E-05 +37500,244.05,391.872,0.00559375,313.173,1.58351E-05 +37600,244.33,386.427,0.00550972,313.353,1.58502E-05 +37700,244.61,381.065,0.00542704,313.532,1.58652E-05 +37800,244.89,375.783,0.00534569,313.712,1.58803E-05 +37900,245.17,370.58,0.00526566,313.891,1.58953E-05 +38000,245.45,365.455,0.00518691,314.07,1.59104E-05 +38100,245.73,360.406,0.00510943,314.249,1.59254E-05 +38200,246.01,355.433,0.00503319,314.428,1.59404E-05 +38300,246.29,350.534,0.00495817,314.607,1.59554E-05 +38400,246.57,345.708,0.00488436,314.786,1.59704E-05 +38500,246.85,340.954,0.00481172,314.965,1.59854E-05 +38600,247.13,336.27,0.00474025,315.143,1.60004E-05 +38700,247.41,331.656,0.00466992,315.322,1.60154E-05 +38800,247.69,327.111,0.00460071,315.5,1.60304E-05 +38900,247.97,322.632,0.0045326,315.678,1.60454E-05 +39000,248.25,318.22,0.00446557,315.856,1.60603E-05 +39100,248.53,313.874,0.00439961,316.034,1.60753E-05 +39200,248.81,309.591,0.0043347,316.212,1.60902E-05 +39300,249.09,305.372,0.00427081,316.39,1.61052E-05 +39400,249.37,301.214,0.00420794,316.568,1.61201E-05 +39500,249.65,297.118,0.00414606,316.746,1.6135E-05 +39600,249.93,293.082,0.00408516,316.923,1.615E-05 +39700,250.21,289.105,0.00402522,317.101,1.61649E-05 +39800,250.49,285.187,0.00396623,317.278,1.61798E-05 +39900,250.77,281.326,0.00390816,317.455,1.61947E-05 +40000,251.05,277.522,0.00385101,317.633,1.62096E-05 +40100,251.33,273.773,0.00379476,317.81,1.62245E-05 +40200,251.61,270.079,0.00373939,317.987,1.62393E-05 +40300,251.89,266.438,0.00368488,318.164,1.62542E-05 +40400,252.17,262.851,0.00363123,318.34,1.62691E-05 +40500,252.45,259.316,0.00357842,318.517,1.62839E-05 +40600,252.73,255.832,0.00352644,318.694,1.62988E-05 +40700,253.01,252.399,0.00347527,318.87,1.63136E-05 +40800,253.29,249.016,0.00342489,319.047,1.63284E-05 +40900,253.57,245.682,0.0033753,319.223,1.63433E-05 +41000,253.85,242.395,0.00332648,319.399,1.63581E-05 +41100,254.13,239.157,0.00327842,319.575,1.63729E-05 +41200,254.41,235.965,0.00323111,319.751,1.63877E-05 +41300,254.69,232.819,0.00318453,319.927,1.64025E-05 +41400,254.97,229.719,0.00313867,320.103,1.64173E-05 +41500,255.25,226.663,0.00309352,320.279,1.6432E-05 +41600,255.53,223.651,0.00304907,320.454,1.64468E-05 +41700,255.81,220.683,0.00300531,320.63,1.64616E-05 +41800,256.09,217.757,0.00296222,320.805,1.64763E-05 +41900,256.37,214.873,0.00291979,320.981,1.64911E-05 +42000,256.65,212.03,0.00287802,321.156,1.65058E-05 +42100,256.93,209.228,0.00283689,321.331,1.65206E-05 +42200,257.21,206.466,0.00279639,321.506,1.65353E-05 +42300,257.49,203.743,0.00275651,321.681,1.655E-05 +42400,257.77,201.059,0.00271725,321.856,1.65647E-05 +42500,258.05,198.413,0.00267858,322.03,1.65795E-05 +42600,258.33,195.805,0.00264051,322.205,1.65942E-05 +42700,258.61,193.234,0.00260302,322.38,1.66088E-05 +42800,258.89,190.7,0.00256609,322.554,1.66235E-05 +42900,259.17,188.201,0.00252974,322.729,1.66382E-05 +43000,259.45,185.738,0.00249393,322.903,1.66529E-05 +43100,259.73,183.309,0.00245867,323.077,1.66676E-05 +43200,260.01,180.915,0.00242395,323.251,1.66822E-05 +43300,260.29,178.555,0.00238975,323.425,1.66969E-05 +43400,260.57,176.228,0.00235607,323.599,1.67115E-05 +43500,260.85,173.934,0.00232291,323.773,1.67261E-05 +43600,261.13,171.672,0.00229024,323.947,1.67408E-05 +43700,261.41,169.442,0.00225807,324.12,1.67554E-05 +43800,261.69,167.243,0.00222638,324.294,1.677E-05 +43900,261.97,165.075,0.00219517,324.467,1.67846E-05 +44000,262.25,162.937,0.00216443,324.641,1.67992E-05 +44100,262.53,160.83,0.00213415,324.814,1.68138E-05 +44200,262.81,158.751,0.00210433,324.987,1.68284E-05 +44300,263.09,156.702,0.00207496,325.16,1.6843E-05 +44400,263.37,154.682,0.00204602,325.333,1.68576E-05 +44500,263.65,152.689,0.00201752,325.506,1.68721E-05 +44600,263.93,150.724,0.00198945,325.679,1.68867E-05 +44700,264.21,148.787,0.0019618,325.851,1.69013E-05 +44800,264.49,146.877,0.00193456,326.024,1.69158E-05 +44900,264.77,144.993,0.00190772,326.197,1.69303E-05 +45000,265.05,143.135,0.00188129,326.369,1.69449E-05 +45100,265.33,141.303,0.00185525,326.541,1.69594E-05 +45200,265.61,139.496,0.0018296,326.714,1.69739E-05 +45300,265.89,137.714,0.00180432,326.886,1.69884E-05 +45400,266.17,135.957,0.00177943,327.058,1.70029E-05 +45500,266.45,134.224,0.0017549,327.23,1.70174E-05 +45600,266.73,132.515,0.00173074,327.402,1.70319E-05 +45700,267.01,130.829,0.00170693,327.574,1.70464E-05 +45800,267.29,129.167,0.00168348,327.745,1.70609E-05 +45900,267.57,127.527,0.00166037,327.917,1.70754E-05 +46000,267.85,125.91,0.0016376,328.088,1.70898E-05 +46100,268.13,124.315,0.00161517,328.26,1.71043E-05 +46200,268.41,122.742,0.00159307,328.431,1.71187E-05 +46300,268.69,121.191,0.00157129,328.602,1.71332E-05 +46400,268.97,119.66,0.00154983,328.774,1.71476E-05 +46500,269.25,118.151,0.00152869,328.945,1.7162E-05 +46600,269.53,116.662,0.00150786,329.116,1.71764E-05 +46700,269.81,115.193,0.00148733,329.287,1.71909E-05 +46800,270.09,113.745,0.0014671,329.457,1.72053E-05 +46900,270.37,112.316,0.00144717,329.628,1.72197E-05 +47000,270.65,110.906,0.00142753,329.799,1.72341E-05 +47100,270.65,109.515,0.00140963,329.799,1.72341E-05 +47200,270.65,108.141,0.00139195,329.799,1.72341E-05 +47300,270.65,106.785,0.00137449,329.799,1.72341E-05 +47400,270.65,105.446,0.00135725,329.799,1.72341E-05 +47500,270.65,104.123,0.00134022,329.799,1.72341E-05 +47600,270.65,102.817,0.00132341,329.799,1.72341E-05 +47700,270.65,101.527,0.00130681,329.799,1.72341E-05 +47800,270.65,100.254,0.00129042,329.799,1.72341E-05 +47900,270.65,98.9962,0.00127423,329.799,1.72341E-05 +48000,270.65,97.7545,0.00125825,329.799,1.72341E-05 +48100,270.65,96.5283,0.00124247,329.799,1.72341E-05 +48200,270.65,95.3176,0.00122688,329.799,1.72341E-05 +48300,270.65,94.122,0.00121149,329.799,1.72341E-05 +48400,270.65,92.9414,0.0011963,329.799,1.72341E-05 +48500,270.65,91.7756,0.00118129,329.799,1.72341E-05 +48600,270.65,90.6244,0.00116647,329.799,1.72341E-05 +48700,270.65,89.4877,0.00115184,329.799,1.72341E-05 +48800,270.65,88.3652,0.00113739,329.799,1.72341E-05 +48900,270.65,87.2568,0.00112313,329.799,1.72341E-05 +49000,270.65,86.1623,0.00110904,329.799,1.72341E-05 +49100,270.65,85.0815,0.00109513,329.799,1.72341E-05 +49200,270.65,84.0143,0.00108139,329.799,1.72341E-05 +49300,270.65,82.9605,0.00106783,329.799,1.72341E-05 +49400,270.65,81.9199,0.00105443,329.799,1.72341E-05 +49500,270.65,80.8924,0.00104121,329.799,1.72341E-05 +49600,270.65,79.8777,0.00102815,329.799,1.72341E-05 +49700,270.65,78.8758,0.00101525,329.799,1.72341E-05 +49800,270.65,77.8864,0.00100252,329.799,1.72341E-05 +49900,270.65,76.9095,0.000989942,329.799,1.72341E-05 +50000,270.65,75.9448,0.000977525,329.799,1.72341E-05 +50100,270.65,74.9922,0.000965264,329.799,1.72341E-05 +50200,270.65,74.0515,0.000953156,329.799,1.72341E-05 +50300,270.65,73.1227,0.0009412,329.799,1.72341E-05 +50400,270.65,72.2055,0.000929395,329.799,1.72341E-05 +50500,270.65,71.2998,0.000917737,329.799,1.72341E-05 +50600,270.65,70.4054,0.000906225,329.799,1.72341E-05 +50700,270.65,69.5223,0.000894858,329.799,1.72341E-05 +50800,270.65,68.6503,0.000883634,329.799,1.72341E-05 +50900,270.65,67.7892,0.00087255,329.799,1.72341E-05 +51000,270.65,66.9389,0.000861606,329.799,1.72341E-05 +51100,270.37,66.0988,0.000851674,329.628,1.72197E-05 +51200,270.09,65.2684,0.000841846,329.457,1.72053E-05 +51300,269.81,64.4476,0.000832122,329.287,1.71909E-05 +51400,269.53,63.6363,0.0008225,329.116,1.71764E-05 +51500,269.25,62.8344,0.00081298,328.945,1.7162E-05 +51600,268.97,62.0418,0.00080356,328.774,1.71476E-05 +51700,268.69,61.2583,0.00079424,328.602,1.71332E-05 +51800,268.41,60.484,0.000785018,328.431,1.71187E-05 +51900,268.13,59.7186,0.000775894,328.26,1.71043E-05 +52000,267.85,58.9622,0.000766867,328.088,1.70898E-05 +52100,267.57,58.2145,0.000757935,327.917,1.70754E-05 +52200,267.29,57.4756,0.000749098,327.745,1.70609E-05 +52300,267.01,56.7453,0.000740355,327.574,1.70464E-05 +52400,266.73,56.0235,0.000731705,327.402,1.70319E-05 +52500,266.45,55.3101,0.000723147,327.23,1.70174E-05 +52600,266.17,54.6051,0.000714681,327.058,1.70029E-05 +52700,265.89,53.9084,0.000706305,326.886,1.69884E-05 +52800,265.61,53.2198,0.000698018,326.714,1.69739E-05 +52900,265.33,52.5393,0.00068982,326.541,1.69594E-05 +53000,265.05,51.8668,0.00068171,326.369,1.69449E-05 +53100,264.77,51.2022,0.000673687,326.197,1.69303E-05 +53200,264.49,50.5454,0.000665749,326.024,1.69158E-05 +53300,264.21,49.8964,0.000657897,325.851,1.69013E-05 +53400,263.93,49.2551,0.00065013,325.679,1.68867E-05 +53500,263.65,48.6213,0.000642446,325.506,1.68721E-05 +53600,263.37,47.995,0.000634845,325.333,1.68576E-05 +53700,263.09,47.3761,0.000627326,325.16,1.6843E-05 +53800,262.81,46.7646,0.000619888,324.987,1.68284E-05 +53900,262.53,46.1603,0.00061253,324.814,1.68138E-05 +54000,262.25,45.5632,0.000605252,324.641,1.67992E-05 +54100,261.97,44.9731,0.000598053,324.467,1.67846E-05 +54200,261.69,44.3902,0.000590932,324.294,1.677E-05 +54300,261.41,43.8141,0.000583888,324.12,1.67554E-05 +54400,261.13,43.2449,0.000576921,323.947,1.67408E-05 +54500,260.85,42.6826,0.00057003,323.773,1.67261E-05 +54600,260.57,42.1269,0.000563214,323.599,1.67115E-05 +54700,260.29,41.5779,0.000556472,323.425,1.66969E-05 +54800,260.01,41.0354,0.000549803,323.251,1.66822E-05 +54900,259.73,40.4995,0.000543208,323.077,1.66676E-05 +55000,259.45,39.97,0.000536684,322.903,1.66529E-05 +55100,259.17,39.4469,0.000530232,322.729,1.66382E-05 +55200,258.89,38.93,0.000523851,322.554,1.66235E-05 +55300,258.61,38.4194,0.000517539,322.38,1.66088E-05 +55400,258.33,37.9149,0.000511298,322.205,1.65942E-05 +55500,258.05,37.4166,0.000505124,322.03,1.65795E-05 +55600,257.77,36.9242,0.000499019,321.856,1.65647E-05 +55700,257.49,36.4378,0.000492981,321.681,1.655E-05 +55800,257.21,35.9573,0.000487009,321.506,1.65353E-05 +55900,256.93,35.4826,0.000481104,321.331,1.65206E-05 +56000,256.65,35.0137,0.000475263,321.156,1.65058E-05 +56100,256.37,34.5504,0.000469488,320.981,1.64911E-05 +56200,256.09,34.0928,0.000463776,320.805,1.64763E-05 +56300,255.81,33.6408,0.000458128,320.63,1.64616E-05 +56400,255.53,33.1943,0.000452542,320.454,1.64468E-05 +56500,255.25,32.7532,0.000447019,320.279,1.6432E-05 +56600,254.97,32.3175,0.000441557,320.103,1.64173E-05 +56700,254.69,31.8871,0.000436156,319.927,1.64025E-05 +56800,254.41,31.462,0.000430815,319.751,1.63877E-05 +56900,254.13,31.0421,0.000425534,319.575,1.63729E-05 +57000,253.85,30.6274,0.000420311,319.399,1.63581E-05 +57100,253.57,30.2178,0.000415147,319.223,1.63433E-05 +57200,253.29,29.8131,0.000410041,319.047,1.63284E-05 +57300,253.01,29.4135,0.000404993,318.87,1.63136E-05 +57400,252.73,29.0188,0.000400001,318.694,1.62988E-05 +57500,252.45,28.629,0.000395065,318.517,1.62839E-05 +57600,252.17,28.2439,0.000390184,318.34,1.62691E-05 +57700,251.89,27.8637,0.000385359,318.164,1.62542E-05 +57800,251.61,27.4881,0.000380588,317.987,1.62393E-05 +57900,251.33,27.1172,0.000375871,317.81,1.62245E-05 +58000,251.05,26.7509,0.000371207,317.633,1.62096E-05 +58100,250.77,26.3891,0.000366596,317.455,1.61947E-05 +58200,250.49,26.0318,0.000362037,317.278,1.61798E-05 +58300,250.21,25.679,0.000357529,317.101,1.61649E-05 +58400,249.93,25.3306,0.000353073,316.923,1.615E-05 +58500,249.65,24.9865,0.000348668,316.746,1.6135E-05 +58600,249.37,24.6467,0.000344313,316.568,1.61201E-05 +58700,249.09,24.3112,0.000340007,316.39,1.61052E-05 +58800,248.81,23.9798,0.00033575,316.212,1.60902E-05 +58900,248.53,23.6526,0.000331542,316.034,1.60753E-05 +59000,248.25,23.3296,0.000327382,315.856,1.60603E-05 +59100,247.97,23.0105,0.00032327,315.678,1.60454E-05 +59200,247.69,22.6955,0.000319205,315.5,1.60304E-05 +59300,247.41,22.3844,0.000315186,315.322,1.60154E-05 +59400,247.13,22.0773,0.000311214,315.143,1.60004E-05 +59500,246.85,21.774,0.000307287,314.965,1.59854E-05 +59600,246.57,21.4746,0.000303405,314.786,1.59704E-05 +59700,246.29,21.1789,0.000299568,314.607,1.59554E-05 +59800,246.01,20.887,0.000295775,314.428,1.59404E-05 +59900,245.73,20.5988,0.000292027,314.249,1.59254E-05 +60000,245.45,20.3143,0.000288321,314.07,1.59104E-05 +60100,245.17,20.0333,0.000284658,313.891,1.58953E-05 +60200,244.89,19.7559,0.000281038,313.712,1.58803E-05 +60300,244.61,19.4821,0.000277459,313.532,1.58652E-05 +60400,244.33,19.2117,0.000273923,313.353,1.58502E-05 +60500,244.05,18.9448,0.000270427,313.173,1.58351E-05 +60600,243.77,18.6813,0.000266972,312.993,1.582E-05 +60700,243.49,18.4212,0.000263557,312.814,1.58049E-05 +60800,243.21,18.1644,0.000260182,312.634,1.57898E-05 +60900,242.93,17.9109,0.000256847,312.454,1.57748E-05 +61000,242.65,17.6606,0.00025355,312.274,1.57596E-05 +61100,242.37,17.4136,0.000250292,312.093,1.57445E-05 +61200,242.09,17.1697,0.000247072,311.913,1.57294E-05 +61300,241.81,16.929,0.00024389,311.733,1.57143E-05 +61400,241.53,16.6913,0.000240746,311.552,1.56991E-05 +61500,241.25,16.4568,0.000237638,311.371,1.5684E-05 +61600,240.97,16.2252,0.000234567,311.191,1.56688E-05 +61700,240.69,15.9967,0.000231532,311.01,1.56537E-05 +61800,240.41,15.7711,0.000228533,310.829,1.56385E-05 +61900,240.13,15.5485,0.000225569,310.648,1.56233E-05 +62000,239.85,15.3287,0.00022264,310.467,1.56082E-05 +62100,239.57,15.1118,0.000219746,310.285,1.5593E-05 +62200,239.29,14.8977,0.000216886,310.104,1.55778E-05 +62300,239.01,14.6864,0.000214061,309.922,1.55626E-05 +62400,238.73,14.4778,0.000211268,309.741,1.55474E-05 +62500,238.45,14.272,0.000208509,309.559,1.55321E-05 +62600,238.17,14.0689,0.000205783,309.377,1.55169E-05 +62700,237.89,13.8684,0.00020309,309.195,1.55017E-05 +62800,237.61,13.6705,0.000200428,309.013,1.54864E-05 +62900,237.33,13.4753,0.000197798,308.831,1.54712E-05 +63000,237.05,13.2826,0.0001952,308.649,1.54559E-05 +63100,236.77,13.0924,0.000192633,308.467,1.54406E-05 +63200,236.49,12.9047,0.000190097,308.284,1.54254E-05 +63300,236.21,12.7196,0.000187591,308.102,1.54101E-05 +63400,235.93,12.5368,0.000185115,307.919,1.53948E-05 +63500,235.65,12.3565,0.000182669,307.736,1.53795E-05 +63600,235.37,12.1785,0.000180253,307.553,1.53642E-05 +63700,235.09,12.0029,0.000177865,307.37,1.53489E-05 +63800,234.81,11.8297,0.000175507,307.187,1.53335E-05 +63900,234.53,11.6587,0.000173177,307.004,1.53182E-05 +64000,234.25,11.49,0.000170875,306.821,1.53029E-05 +64100,233.97,11.3235,0.000168601,306.637,1.52875E-05 +64200,233.69,11.1593,0.000166355,306.454,1.52722E-05 +64300,233.41,10.9973,0.000164136,306.27,1.52568E-05 +64400,233.13,10.8374,0.000161944,306.086,1.52414E-05 +64500,232.85,10.6796,0.000159778,305.903,1.52261E-05 +64600,232.57,10.524,0.000157639,305.719,1.52107E-05 +64700,232.29,10.3704,0.000155527,305.535,1.51953E-05 +64800,232.01,10.2189,0.00015344,305.35,1.51799E-05 +64900,231.73,10.0695,0.000151378,305.166,1.51645E-05 +65000,231.45,9.92203,0.000149342,304.982,1.51491E-05 +65100,231.17,9.77656,0.000147331,304.797,1.51336E-05 +65200,230.89,9.63306,0.000145344,304.612,1.51182E-05 +65300,230.61,9.49149,0.000143382,304.428,1.51028E-05 +65400,230.33,9.35183,0.000141444,304.243,1.50873E-05 +65500,230.05,9.21406,0.00013953,304.058,1.50719E-05 +65600,229.77,9.07816,0.000137639,303.873,1.50564E-05 +65700,229.49,8.9441,0.000135772,303.688,1.50409E-05 +65800,229.21,8.81186,0.000133928,303.502,1.50254E-05 +65900,228.93,8.68141,0.000132107,303.317,1.501E-05 +66000,228.65,8.55275,0.000130308,303.131,1.49945E-05 +66100,228.37,8.42583,0.000128532,302.946,1.4979E-05 +66200,228.09,8.30064,0.000126778,302.76,1.49634E-05 +66300,227.81,8.17717,0.000125046,302.574,1.49479E-05 +66400,227.53,8.05538,0.000123335,302.388,1.49324E-05 +66500,227.25,7.93526,0.000121645,302.202,1.49169E-05 +66600,226.97,7.81679,0.000119977,302.016,1.49013E-05 +66700,226.69,7.69994,0.00011833,301.829,1.48858E-05 +66800,226.41,7.5847,0.000116703,301.643,1.48702E-05 +66900,226.13,7.47104,0.000115096,301.456,1.48546E-05 +67000,225.85,7.35895,0.00011351,301.269,1.4839E-05 +67100,225.57,7.24841,0.000111944,301.083,1.48235E-05 +67200,225.29,7.13939,0.000110397,300.896,1.48079E-05 +67300,225.01,7.03187,0.00010887,300.709,1.47923E-05 +67400,224.73,6.92585,0.000107362,300.522,1.47767E-05 +67500,224.45,6.8213,0.000105873,300.334,1.4761E-05 +67600,224.17,6.71819,0.000104403,300.147,1.47454E-05 +67700,223.89,6.61652,0.000102952,299.959,1.47298E-05 +67800,223.61,6.51626,0.000101519,299.772,1.47141E-05 +67900,223.33,6.4174,0.000100104,299.584,1.46985E-05 +68000,223.05,6.31992,9.87069E-05,299.396,1.46828E-05 +68100,222.77,6.2238,9.73279E-05,299.208,1.46672E-05 +68200,222.49,6.12902,9.59663E-05,299.02,1.46515E-05 +68300,222.21,6.03557,9.46222E-05,298.832,1.46358E-05 +68400,221.93,5.94343,9.32952E-05,298.644,1.46201E-05 +68500,221.65,5.85259,9.19852E-05,298.455,1.46044E-05 +68600,221.37,5.76302,9.0692E-05,298.266,1.45887E-05 +68700,221.09,5.6747,8.94154E-05,298.078,1.4573E-05 +68800,220.81,5.58764,8.81551E-05,297.889,1.45573E-05 +68900,220.53,5.5018,8.69111E-05,297.7,1.45415E-05 +69000,220.25,5.41717,8.5683E-05,297.511,1.45258E-05 +69100,219.97,5.33374,8.44708E-05,297.322,1.451E-05 +69200,219.69,5.25149,8.32742E-05,297.133,1.44943E-05 +69300,219.41,5.17041,8.2093E-05,296.943,1.44785E-05 +69400,219.13,5.09047,8.09272E-05,296.754,1.44627E-05 +69500,218.85,5.01168,7.97764E-05,296.564,1.44469E-05 +69600,218.57,4.934,7.86406E-05,296.374,1.44312E-05 +69700,218.29,4.85743,7.75195E-05,296.184,1.44154E-05 +69800,218.01,4.78196,7.6413E-05,295.994,1.43995E-05 +69900,217.73,4.70756,7.53209E-05,295.804,1.43837E-05 +70000,217.45,4.63422,7.4243E-05,295.614,1.43679E-05 +70100,217.17,4.56194,7.31792E-05,295.423,1.43521E-05 +70200,216.89,4.49069,7.21293E-05,295.233,1.43362E-05 +70300,216.61,4.42046,7.10931E-05,295.042,1.43204E-05 +70400,216.33,4.35125,7.00705E-05,294.852,1.43045E-05 +70500,216.05,4.28303,6.90613E-05,294.661,1.42886E-05 +70600,215.77,4.21579,6.80654E-05,294.47,1.42728E-05 +70700,215.49,4.14952,6.70825E-05,294.279,1.42569E-05 +70800,215.21,4.08422,6.61126E-05,294.087,1.4241E-05 +70900,214.93,4.01985,6.51555E-05,293.896,1.42251E-05 +71000,214.65,3.95642,6.4211E-05,293.704,1.42092E-05 +71100,214.45,3.89392,6.32556E-05,293.568,1.41978E-05 +71200,214.25,3.83235,6.23136E-05,293.431,1.41864E-05 +71300,214.05,3.7717,6.13847E-05,293.294,1.41751E-05 +71400,213.85,3.71195,6.04688E-05,293.157,1.41637E-05 +71500,213.65,3.6531,5.95657E-05,293.019,1.41523E-05 +71600,213.45,3.59512,5.86753E-05,292.882,1.41409E-05 +71700,213.25,3.53801,5.77974E-05,292.745,1.41295E-05 +71800,213.05,3.48176,5.69318E-05,292.608,1.41181E-05 +71900,212.85,3.42634,5.60784E-05,292.47,1.41067E-05 +72000,212.65,3.37176,5.5237E-05,292.333,1.40953E-05 +72100,212.45,3.318,5.44074E-05,292.195,1.40839E-05 +72200,212.25,3.26505,5.35895E-05,292.058,1.40725E-05 +72300,212.05,3.21289,5.27832E-05,291.92,1.4061E-05 +72400,211.85,3.16152,5.19883E-05,291.783,1.40496E-05 +72500,211.65,3.11092,5.12046E-05,291.645,1.40382E-05 +72600,211.45,3.06109,5.0432E-05,291.507,1.40268E-05 +72700,211.25,3.012,4.96703E-05,291.369,1.40153E-05 +72800,211.05,2.96366,4.89195E-05,291.231,1.40039E-05 +72900,210.85,2.91605,4.81793E-05,291.093,1.39924E-05 +73000,210.65,2.86917,4.74496E-05,290.955,1.3981E-05 +73100,210.45,2.82299,4.67302E-05,290.817,1.39695E-05 +73200,210.25,2.77751,4.60212E-05,290.679,1.39581E-05 +73300,210.05,2.73272,4.53222E-05,290.54,1.39466E-05 +73400,209.85,2.68861,4.46331E-05,290.402,1.39351E-05 +73500,209.65,2.64518,4.3954E-05,290.264,1.39236E-05 +73600,209.45,2.6024,4.32845E-05,290.125,1.39122E-05 +73700,209.25,2.56028,4.26246E-05,289.987,1.39007E-05 +73800,209.05,2.5188,4.19741E-05,289.848,1.38892E-05 +73900,208.85,2.47795,4.13329E-05,289.709,1.38777E-05 +74000,208.65,2.43773,4.0701E-05,289.57,1.38662E-05 +74100,208.45,2.39812,4.00781E-05,289.432,1.38547E-05 +74200,208.25,2.35912,3.94642E-05,289.293,1.38432E-05 +74300,208.05,2.32071,3.8859E-05,289.154,1.38317E-05 +74400,207.85,2.2829,3.82626E-05,289.015,1.38202E-05 +74500,207.65,2.24567,3.76748E-05,288.876,1.38087E-05 +74600,207.45,2.209,3.70955E-05,288.737,1.37971E-05 +74700,207.25,2.17291,3.65245E-05,288.597,1.37856E-05 +74800,207.05,2.13737,3.59618E-05,288.458,1.37741E-05 +74900,206.85,2.10237,3.54072E-05,288.319,1.37625E-05 +75000,206.65,2.06792,3.48607E-05,288.179,1.3751E-05 +75100,206.45,2.034,3.43221E-05,288.04,1.37394E-05 +75200,206.25,2.0006,3.37912E-05,287.9,1.37279E-05 +75300,206.05,1.96772,3.32681E-05,287.761,1.37163E-05 +75400,205.85,1.93535,3.27526E-05,287.621,1.37048E-05 +75500,205.65,1.90348,3.22446E-05,287.481,1.36932E-05 +75600,205.45,1.8721,3.1744E-05,287.341,1.36817E-05 +75700,205.25,1.84121,3.12507E-05,287.201,1.36701E-05 +75800,205.05,1.81081,3.07645E-05,287.061,1.36585E-05 +75900,204.85,1.78087,3.02855E-05,286.921,1.36469E-05 +76000,204.65,1.7514,2.98135E-05,286.781,1.36353E-05 +76100,204.45,1.7224,2.93484E-05,286.641,1.36237E-05 +76200,204.25,1.69384,2.88901E-05,286.501,1.36122E-05 +76300,204.05,1.66573,2.84385E-05,286.361,1.36006E-05 +76400,203.85,1.63806,2.79935E-05,286.22,1.35889E-05 +76500,203.65,1.61082,2.75551E-05,286.08,1.35773E-05 +76600,203.45,1.58401,2.71231E-05,285.939,1.35657E-05 +76700,203.25,1.55762,2.66975E-05,285.799,1.35541E-05 +76800,203.05,1.53165,2.62781E-05,285.658,1.35425E-05 +76900,202.85,1.50608,2.5865E-05,285.517,1.35309E-05 +77000,202.65,1.48092,2.54579E-05,285.377,1.35192E-05 +77100,202.45,1.45615,2.50568E-05,285.236,1.35076E-05 +77200,202.25,1.43177,2.46617E-05,285.095,1.3496E-05 +77300,202.05,1.40778,2.42724E-05,284.954,1.34843E-05 +77400,201.85,1.38416,2.38889E-05,284.813,1.34727E-05 +77500,201.65,1.36092,2.35111E-05,284.672,1.3461E-05 +77600,201.45,1.33805,2.31389E-05,284.53,1.34493E-05 +77700,201.25,1.31554,2.27722E-05,284.389,1.34377E-05 +77800,201.05,1.29338,2.2411E-05,284.248,1.3426E-05 +77900,200.85,1.27158,2.20551E-05,284.106,1.34143E-05 +78000,200.65,1.25012,2.17046E-05,283.965,1.34027E-05 +78100,200.45,1.22901,2.13593E-05,283.823,1.3391E-05 +78200,200.25,1.20823,2.10191E-05,283.682,1.33793E-05 +78300,200.05,1.18778,2.06841E-05,283.54,1.33676E-05 +78400,199.85,1.16766,2.0354E-05,283.398,1.33559E-05 +78500,199.65,1.14786,2.00289E-05,283.256,1.33442E-05 +78600,199.45,1.12837,1.97087E-05,283.114,1.33325E-05 +78700,199.25,1.1092,1.93932E-05,282.972,1.33208E-05 +78800,199.05,1.09034,1.90826E-05,282.83,1.33091E-05 +78900,198.85,1.07177,1.87765E-05,282.688,1.32974E-05 +79000,198.65,1.05351,1.84751E-05,282.546,1.32856E-05 +79100,198.45,1.03554,1.81783E-05,282.404,1.32739E-05 +79200,198.25,1.01785,1.78859E-05,282.262,1.32622E-05 +79300,198.05,1.00045,1.75979E-05,282.119,1.32504E-05 +79400,197.85,0.983336,1.73143E-05,281.977,1.32387E-05 +79500,197.65,0.966494,1.70349E-05,281.834,1.3227E-05 +79600,197.45,0.949924,1.67598E-05,281.691,1.32152E-05 +79700,197.25,0.933621,1.64889E-05,281.549,1.32035E-05 +79800,197.05,0.917582,1.62221E-05,281.406,1.31917E-05 +79900,196.85,0.901803,1.59593E-05,281.263,1.31799E-05 +80000,196.65,0.88628,1.57005E-05,281.12,1.31682E-05 +80100,196.45,0.871008,1.54457E-05,280.977,1.31564E-05 +80200,196.25,0.855984,1.51948E-05,280.834,1.31446E-05 +80300,196.05,0.841205,1.49476E-05,280.691,1.31328E-05 +80400,195.85,0.826666,1.47043E-05,280.548,1.3121E-05 +80500,195.65,0.812363,1.44647E-05,280.405,1.31092E-05 +80600,195.45,0.798294,1.42287E-05,280.261,1.30974E-05 +80700,195.25,0.784455,1.39964E-05,280.118,1.30856E-05 +80800,195.05,0.770842,1.37676E-05,279.974,1.30738E-05 +80900,194.85,0.757451,1.35423E-05,279.831,1.3062E-05 +81000,194.65,0.74428,1.33205E-05,279.687,1.30502E-05 +81100,194.45,0.731324,1.31021E-05,279.543,1.30384E-05 +81200,194.25,0.718581,1.2887E-05,279.399,1.30266E-05 +81300,194.05,0.706047,1.26753E-05,279.256,1.30147E-05 +81400,193.85,0.69372,1.24668E-05,279.112,1.30029E-05 +81500,193.65,0.681595,1.22616E-05,278.968,1.29911E-05 +81600,193.45,0.66967,1.20595E-05,278.824,1.29792E-05 +81700,193.25,0.657941,1.18606E-05,278.679,1.29674E-05 +81800,193.05,0.646406,1.16647E-05,278.535,1.29555E-05 +81900,192.85,0.635062,1.14719E-05,278.391,1.29437E-05 +82000,192.65,0.623905,1.1282E-05,278.246,1.29318E-05 +82100,192.45,0.612933,1.10952E-05,278.102,1.29199E-05 +82200,192.25,0.602143,1.09112E-05,277.957,1.29081E-05 +82300,192.05,0.591532,1.07301E-05,277.813,1.28962E-05 +82400,191.85,0.581097,1.05518E-05,277.668,1.28843E-05 +82500,191.65,0.570835,1.03762E-05,277.523,1.28724E-05 +82600,191.45,0.560745,1.02035E-05,277.378,1.28605E-05 +82700,191.25,0.550822,1.00334E-05,277.234,1.28486E-05 +82800,191.05,0.541065,9.86599E-06,277.089,1.28367E-05 +82900,190.85,0.531471,9.7012E-06,276.943,1.28248E-05 +83000,190.65,0.522037,9.53899E-06,276.798,1.28129E-05 +83100,190.45,0.512761,9.37933E-06,276.653,1.2801E-05 +83200,190.25,0.50364,9.22218E-06,276.508,1.27891E-05 +83300,190.05,0.494672,9.06751E-06,276.362,1.27771E-05 +83400,189.85,0.485855,8.91526E-06,276.217,1.27652E-05 +83500,189.65,0.477186,8.76542E-06,276.071,1.27533E-05 +83600,189.45,0.468662,8.61794E-06,275.926,1.27413E-05 +83700,189.25,0.460282,8.47279E-06,275.78,1.27294E-05 +83800,189.05,0.452044,8.32994E-06,275.634,1.27174E-05 +83900,188.85,0.443944,8.18935E-06,275.489,1.27055E-05 +84000,188.65,0.435981,8.05098E-06,275.343,1.26935E-05 +84100,188.45,0.428153,7.91481E-06,275.197,1.26816E-05 +84200,188.25,0.420457,7.7808E-06,275.051,1.26696E-05 +84300,188.05,0.412891,7.64892E-06,274.904,1.26576E-05 +84400,187.85,0.405454,7.51914E-06,274.758,1.26457E-05 +84500,187.65,0.398143,7.39143E-06,274.612,1.26337E-05 +84600,187.45,0.390956,7.26576E-06,274.466,1.26217E-05 +84700,187.25,0.383892,7.14209E-06,274.319,1.26097E-05 +84800,187.05,0.376948,7.02039E-06,274.173,1.25977E-05 +84900,186.946,0.370123,6.89712E-06,274.096,1.25915E-05 +85000,186.946,0.36342,6.77222E-06,274.096,1.25915E-05 +85100,186.946,0.356839,6.64959E-06,274.096,1.25915E-05 +85200,186.946,0.350378,6.52917E-06,274.096,1.25915E-05 +85300,186.946,0.344033,6.41094E-06,274.096,1.25915E-05 +85400,186.946,0.337803,6.29485E-06,274.096,1.25915E-05 +85500,186.946,0.331686,6.18086E-06,274.096,1.25915E-05 +85600,186.946,0.32568,6.06893E-06,274.096,1.25915E-05 +85700,186.946,0.319782,5.95904E-06,274.096,1.25915E-05 +85800,186.946,0.313991,5.85113E-06,274.096,1.25915E-05 +85900,186.946,0.308305,5.74517E-06,274.096,1.25915E-05 +86000,186.946,0.302723,5.64114E-06,274.096,1.25915E-05 diff --git a/esmvalcore/preprocessor/_derive/__init__.py b/esmvalcore/preprocessor/_derive/__init__.py index 11e06de1bc..db4d2c169c 100644 --- a/esmvalcore/preprocessor/_derive/__init__.py +++ b/esmvalcore/preprocessor/_derive/__init__.py @@ -96,9 +96,7 @@ def derive(cubes, short_name, long_name, units, standard_name=None): except Exception as exc: msg = (f"Derivation of variable '{short_name}' failed. If you used " f"the option '--skip-nonexistent' for running your recipe, " - f"this might be caused by missing input data for derivation " - f"('{short_name}' needs the variables " - f"{DerivedVariable().required}).") + f"this might be caused by missing input data for derivation") raise ValueError(msg) from exc # Set standard attributes diff --git a/esmvalcore/preprocessor/_derive/_shared.py b/esmvalcore/preprocessor/_derive/_shared.py index 7f8abaf2d5..74083b4d71 100644 --- a/esmvalcore/preprocessor/_derive/_shared.py +++ b/esmvalcore/preprocessor/_derive/_shared.py @@ -8,7 +8,7 @@ logger = logging.getLogger(__name__) -def _var_name_constraint(var_name): +def var_name_constraint(var_name): """:mod:`iris.Constraint` using `var_name` of a :mod:`iris.cube.Cube`.""" return Constraint(cube_func=lambda c: c.var_name == var_name) diff --git a/esmvalcore/preprocessor/_derive/alb.py b/esmvalcore/preprocessor/_derive/alb.py index 6b6fd4b930..300353e857 100644 --- a/esmvalcore/preprocessor/_derive/alb.py +++ b/esmvalcore/preprocessor/_derive/alb.py @@ -6,7 +6,7 @@ """ from ._baseclass import DerivedVariableBase -from ._shared import _var_name_constraint +from ._shared import var_name_constraint class DerivedVariable(DerivedVariableBase): @@ -28,8 +28,8 @@ def required(project): @staticmethod def calculate(cubes): """Compute surface albedo.""" - rsdscs_cube = cubes.extract_strict(_var_name_constraint('rsdscs')) - rsuscs_cube = cubes.extract_strict(_var_name_constraint('rsuscs')) + rsdscs_cube = cubes.extract_strict(var_name_constraint('rsdscs')) + rsuscs_cube = cubes.extract_strict(var_name_constraint('rsuscs')) rsnscs_cube = rsuscs_cube / rsdscs_cube diff --git a/esmvalcore/preprocessor/_derive/lwp.py b/esmvalcore/preprocessor/_derive/lwp.py index 0208fc9d1f..fb9b8622c1 100644 --- a/esmvalcore/preprocessor/_derive/lwp.py +++ b/esmvalcore/preprocessor/_derive/lwp.py @@ -3,7 +3,7 @@ import logging from ._baseclass import DerivedVariableBase -from ._shared import _var_name_constraint +from ._shared import var_name_constraint logger = logging.getLogger(__name__) @@ -36,8 +36,8 @@ def calculate(cubes): """ # CMIP5 and CMIP6 names are slightly different, so use # variable name instead to extract cubes - clwvi_cube = cubes.extract_strict(_var_name_constraint('clwvi')) - clivi_cube = cubes.extract_strict(_var_name_constraint('clivi')) + clwvi_cube = cubes.extract_strict(var_name_constraint('clwvi')) + clivi_cube = cubes.extract_strict(var_name_constraint('clivi')) # CMIP5 and CMIP6 have different global attributes that we use # to determine model name and project name: From 06615af566d9ac86e5891dcef05d55198b989a66 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Mon, 2 Mar 2020 18:18:11 +0100 Subject: [PATCH 2/9] Added tests for fixes --- esmvalcore/cmor/_fixes/cmip5/fgoals_g2.py | 4 +- .../cmor/_fixes/cmip5/test_access1_0.py | 53 +++- .../cmor/_fixes/cmip5/test_access1_3.py | 21 +- .../cmor/_fixes/cmip5/test_bcc_csm1_1.py | 272 +++++++++++++++++- .../cmor/_fixes/cmip5/test_bcc_csm1_1_m.py | 35 ++- .../cmor/_fixes/cmip5/test_canesm2.py | 23 +- .../cmor/_fixes/cmip5/test_ccsm4.py | 27 +- .../cmor/_fixes/cmip5/test_csiro_mk3_6_0.py | 21 ++ .../cmor/_fixes/cmip5/test_fgoals_g2.py | 28 +- .../cmor/_fixes/cmip5/test_fgoals_s2.py | 53 ++++ .../cmor/_fixes/cmip5/test_giss_e2_h.py | 21 ++ .../cmor/_fixes/cmip5/test_giss_e2_r.py | 21 ++ .../cmor/_fixes/cmip5/test_hadgem2_es.py | 24 +- .../cmor/_fixes/cmip5/test_inmcm4.py | 29 +- .../cmor/_fixes/cmip5/test_ipsl_cm5a_lr.py | 21 ++ .../cmor/_fixes/cmip5/test_ipsl_cm5a_mr.py | 21 ++ .../cmor/_fixes/cmip5/test_ipsl_cm5b_lr.py | 21 ++ .../cmor/_fixes/cmip5/test_miroc5.py | 40 ++- .../cmor/_fixes/cmip5/test_miroc_esm.py | 27 +- .../cmor/_fixes/cmip5/test_mpi_esm_lr.py | 21 +- .../cmor/_fixes/cmip5/test_mpi_esm_mr.py | 21 ++ .../cmor/_fixes/cmip5/test_mpi_esm_p.py | 21 ++ .../cmor/_fixes/cmip5/test_mri_cgcm3.py | 26 +- .../cmor/_fixes/cmip5/test_noresm1_m.py | 21 ++ .../cmor/_fixes/cmip6/test_bcc_csm2_mr.py | 97 +++++++ .../cmor/_fixes/cmip6/test_bcc_esm1.py | 37 +++ .../cmor/_fixes/cmip6/test_cams_csm1_0.py | 21 ++ .../cmor/_fixes/cmip6/test_ukesm1_0_ll.py | 135 ++++++++- 28 files changed, 1116 insertions(+), 46 deletions(-) create mode 100644 tests/integration/cmor/_fixes/cmip5/test_csiro_mk3_6_0.py create mode 100644 tests/integration/cmor/_fixes/cmip5/test_fgoals_s2.py create mode 100644 tests/integration/cmor/_fixes/cmip5/test_giss_e2_h.py create mode 100644 tests/integration/cmor/_fixes/cmip5/test_giss_e2_r.py create mode 100644 tests/integration/cmor/_fixes/cmip5/test_ipsl_cm5a_lr.py create mode 100644 tests/integration/cmor/_fixes/cmip5/test_ipsl_cm5a_mr.py create mode 100644 tests/integration/cmor/_fixes/cmip5/test_ipsl_cm5b_lr.py create mode 100644 tests/integration/cmor/_fixes/cmip5/test_mpi_esm_mr.py create mode 100644 tests/integration/cmor/_fixes/cmip5/test_mpi_esm_p.py create mode 100644 tests/integration/cmor/_fixes/cmip5/test_noresm1_m.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_bcc_csm2_mr.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_bcc_esm1.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_cams_csm1_0.py diff --git a/esmvalcore/cmor/_fixes/cmip5/fgoals_g2.py b/esmvalcore/cmor/_fixes/cmip5/fgoals_g2.py index 39f7a192fd..8660468ecb 100644 --- a/esmvalcore/cmor/_fixes/cmip5/fgoals_g2.py +++ b/esmvalcore/cmor/_fixes/cmip5/fgoals_g2.py @@ -15,8 +15,6 @@ def fix_metadata(self, cubes): Fix time coordinate and round other coordinates to fix issue with modulus in longitude coordinate. - Fixes longitude precision - Parameters ---------- cubes : iris.cube.CubeList @@ -37,6 +35,8 @@ def fix_metadata(self, cubes): round_coordinates(cubes, 4, coord_names=['longitude']) + return cubes + class Cl(Fix): """Fixes for ``cl``.""" diff --git a/tests/integration/cmor/_fixes/cmip5/test_access1_0.py b/tests/integration/cmor/_fixes/cmip5/test_access1_0.py index 3669a2b380..d904780603 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_access1_0.py +++ b/tests/integration/cmor/_fixes/cmip5/test_access1_0.py @@ -1,16 +1,18 @@ """Test Access1-0 fixes.""" import unittest +import pytest from cf_units import Unit -from iris.cube import Cube from iris.coords import AuxCoord +from iris.cube import Cube, CubeList +from esmvalcore.cmor._fixes.cmip5.access1_0 import AllVars, Cl from esmvalcore.cmor._fixes.fix import Fix -from esmvalcore.cmor._fixes.cmip5.access1_0 import AllVars class TestAllVars(unittest.TestCase): """Test all vars fixes.""" + def setUp(self): """Prepare tests.""" self.cube = Cube([1.0], var_name='co2', units='J') @@ -33,3 +35,50 @@ def test_fix_metadata_if_not_time(self): """Test calendar fix do not fail if no time coord present.""" self.cube.remove_coord('time') self.fix.fix_metadata([self.cube]) + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'ACCESS1-0', 'Amon', 'cl') + assert fix == [Cl(None), AllVars(None)] + + +@pytest.fixture +def cl_cubes(): + """Cubes for ``cl.``.""" + b_coord = AuxCoord( + [1.0], + var_name='b', + long_name='vertical coordinate formula term: b(k)', + attributes={'a': 1, 'b': '2'}, + ) + cl_cube = Cube( + [0.0], + var_name='cl', + standard_name='cloud_area_fraction_in_atmosphere_layer', + aux_coords_and_dims=[(b_coord.copy(), 0)], + ) + x_cube = Cube([0.0], + long_name='x', + aux_coords_and_dims=[(b_coord.copy(), 0)]) + cubes = CubeList([cl_cube, x_cube]) + return cubes + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.access1_0.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata, cl_cubes): + """Test ``fix_metadata`` for ``cl``.""" + mock_base_fix_metadata.return_value = cl_cubes + fix = Cl(None) + fixed_cubes = fix.fix_metadata(cl_cubes) + mock_base_fix_metadata.assert_called_once_with(fix, cl_cubes) + assert len(fixed_cubes) == 2 + cl_cube = fixed_cubes.extract_strict( + 'cloud_area_fraction_in_atmosphere_layer') + b_coord_cl = cl_cube.coord('vertical coordinate formula term: b(k)') + assert not b_coord_cl.attributes + x_cube = fixed_cubes.extract_strict('x') + b_coord_x = x_cube.coord('vertical coordinate formula term: b(k)') + assert b_coord_x.attributes == {'a': 1, 'b': '2'} diff --git a/tests/integration/cmor/_fixes/cmip5/test_access1_3.py b/tests/integration/cmor/_fixes/cmip5/test_access1_3.py index a09b79f266..6c425491dd 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_access1_3.py +++ b/tests/integration/cmor/_fixes/cmip5/test_access1_3.py @@ -2,15 +2,16 @@ import unittest from cf_units import Unit -from iris.cube import Cube from iris.coords import AuxCoord +from iris.cube import Cube +from esmvalcore.cmor._fixes.cmip5.access1_3 import AllVars, Cl from esmvalcore.cmor._fixes.fix import Fix -from esmvalcore.cmor._fixes.cmip5.access1_3 import AllVars class TestAllVars(unittest.TestCase): """Test fixes for all vars.""" + def setUp(self): """Prepare tests.""" self.cube = Cube([1.0], var_name='co2', units='J') @@ -33,3 +34,19 @@ def test_fix_metadata_if_not_time(self): """Test calendar fix do not fail if no time coord present.""" self.cube.remove_coord('time') self.fix.fix_metadata([self.cube]) + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'ACCESS1-3', 'Amon', 'cl') + assert fix == [Cl(None), AllVars(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.access1_3.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1.py b/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1.py index 30c0c44466..d7b2c0bbeb 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1.py +++ b/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1.py @@ -1,13 +1,279 @@ -"""Test Access1-0 fixes.""" +"""Test bcc-csm1-1 fixes.""" +import os import unittest +import iris +import numpy as np +import pytest +from netCDF4 import Dataset + +from esmvalcore.cmor._fixes.cmip5.bcc_csm1_1 import Cl, Tos from esmvalcore.cmor.fix import Fix -from esmvalcore.cmor._fixes.cmip5.bcc_csm1_1 import Tos + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'bcc-csm1-1', 'Amon', 'cl') + assert fix == [Cl(None)] + + +def create_cl_file_without_ap(dataset): + """Create dataset without vertical auxiliary coordinate ``ap``.""" + dataset.createDimension('time', size=1) + dataset.createDimension('lev', size=2) + dataset.createDimension('lat', size=3) + dataset.createDimension('lon', size=4) + dataset.createDimension('bnds', size=2) + + # Dimensional variables + dataset.createVariable('time', np.float64, dimensions=('time',)) + dataset.createVariable('lev', np.float64, dimensions=('lev',)) + dataset.createVariable('lev_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.createVariable('lat', np.float64, dimensions=('lat',)) + dataset.createVariable('lon', np.float64, dimensions=('lon',)) + dataset.variables['time'][:] = [0.0] + dataset.variables['time'].standard_name = 'time' + dataset.variables['time'].units = 'days since 6543-2-1' + dataset.variables['lev'][:] = [1.0, 2.0] + dataset.variables['lev'].bounds = 'lev_bnds' + dataset.variables['lev'].standard_name = ( + 'atmosphere_hybrid_sigma_pressure_coordinate') + dataset.variables['lev'].units = '1' + dataset.variables['lev_bnds'][:] = [[0.5, 1.5], [1.5, 3.0]] + dataset.variables['lev_bnds'].standard_name = ( + 'atmosphere_hybrid_sigma_pressure_coordinate') + dataset.variables['lev_bnds'].units = '1' + dataset.variables['lat'][:] = [-30.0, 0.0, 30.0] + dataset.variables['lat'].standard_name = 'latitude' + dataset.variables['lat'].units = 'degrees_north' + dataset.variables['lon'][:] = [30.0, 60.0, 90.0, 120.0] + dataset.variables['lon'].standard_name = 'longitude' + dataset.variables['lon'].units = 'degrees_east' + + # Coordinates for derivation of pressure coordinate + dataset.createVariable('b', np.float64, dimensions=('lev',)) + dataset.createVariable('b_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.createVariable('ps', np.float64, + dimensions=('time', 'lat', 'lon')) + dataset.variables['b'][:] = [0.0, 1.0] + dataset.variables['b_bnds'][:] = [[-1.0, 0.5], [0.5, 2.0]] + dataset.variables['ps'][:] = np.arange(1 * 3 * 4).reshape(1, 3, 4) + dataset.variables['ps'].standard_name = 'surface_air_pressure' + dataset.variables['ps'].units = 'Pa' + + # Cl variable + dataset.createVariable('cl', np.float32, + dimensions=('time', 'lev', 'lat', 'lon')) + dataset.variables['cl'][:] = np.full((1, 2, 3, 4), 0.0, dtype=np.float32) + dataset.variables['cl'].standard_name = ( + 'cloud_area_fraction_in_atmosphere_layer') + dataset.variables['cl'].units = '%' + + +@pytest.fixture +def cl_file_with_a(tmp_path): + """Create netcdf file with similar issues as ``cl``.""" + nc_path = os.path.join(tmp_path, 'bcc_csm_1_1_cl_a.nc') + dataset = Dataset(nc_path, mode='w') + create_cl_file_without_ap(dataset) + dataset.createVariable('a', np.float64, dimensions=('lev',)) + dataset.createVariable('a_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.createVariable('p0', np.float64, dimensions=()) + dataset.variables['a'][:] = [1.0, 2.0] + dataset.variables['a_bnds'][:] = [[0.0, 1.5], [1.5, 3.0]] + dataset.variables['p0'][:] = 1.0 + dataset.variables['p0'].units = 'Pa' + dataset.variables['lev'].formula_terms = 'p0: p0 a: a b: b ps: ps' + dataset.variables['lev_bnds'].formula_terms = ( + 'p0: p0 a: a_bnds b: b_bnds ps: ps') + dataset.close() + return nc_path + + +@pytest.fixture +def cl_file_with_ap(tmp_path): + """Create netcdf file with similar issues as ``cl``.""" + nc_path = os.path.join(tmp_path, 'bcc_csm_1_1_cl_ap.nc') + dataset = Dataset(nc_path, mode='w') + create_cl_file_without_ap(dataset) + dataset.createVariable('ap', np.float64, dimensions=('lev',)) + dataset.createVariable('ap_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.variables['ap'][:] = [1.0, 2.0] + dataset.variables['ap_bnds'][:] = [[0.0, 1.5], [1.5, 3.0]] + dataset.variables['ap'].units = 'Pa' + dataset.variables['lev'].formula_terms = 'ap: ap b: b ps: ps' + dataset.variables['lev_bnds'].formula_terms = ( + 'ap: ap_bnds b: b_bnds ps: ps') + dataset.close() + return nc_path + + +AIR_PRESSURE_POINTS = np.array([[[[1.0, 1.0, 1.0, 1.0], + [1.0, 1.0, 1.0, 1.0], + [1.0, 1.0, 1.0, 1.0]], + [[2.0, 3.0, 4.0, 5.0], + [6.0, 7.0, 8.0, 9.0], + [10.0, 11.0, 12.0, 13.0]]]]) +AIR_PRESSURE_BOUNDS = np.array([[[[[0.0, 1.5], + [-1.0, 2.0], + [-2.0, 2.5], + [-3.0, 3.0]], + [[-4.0, 3.5], + [-5.0, 4.0], + [-6.0, 4.5], + [-7.0, 5.0]], + [[-8.0, 5.5], + [-9.0, 6.0], + [-10.0, 6.5], + [-11.0, 7.0]]], + [[[1.5, 3.0], + [2.0, 5.0], + [2.5, 7.0], + [3.0, 9.0]], + [[3.5, 11.0], + [4.0, 13.0], + [4.5, 15.0], + [5.0, 17.0]], + [[5.5, 19.0], + [6.0, 21.0], + [6.5, 23.0], + [7.0, 25.0]]]]]) + + +def test_cl_fix_metadata_with_a(cl_file_with_a): + """Test ``fix_metadata`` for ``cl``.""" + cubes = iris.load(cl_file_with_a) + + # Raw cubes + assert len(cubes) == 4 + var_names = [cube.var_name for cube in cubes] + assert 'cl' in var_names + assert 'ps' in var_names + assert 'a_bnds' in var_names + assert 'b_bnds' in var_names + + # Raw cl cube + cl_cube = cubes.extract_strict('cloud_area_fraction_in_atmosphere_layer') + air_pressure_coord = cl_cube.coord('air_pressure') + assert air_pressure_coord.points is not None + assert air_pressure_coord.bounds is None + np.testing.assert_allclose(air_pressure_coord.points, AIR_PRESSURE_POINTS) + + # Apply fix + fix = Cl(None) + fixed_cubes = fix.fix_metadata(cubes) + assert len(fixed_cubes) == 1 + fixed_cl_cube = fixed_cubes.extract_strict( + 'cloud_area_fraction_in_atmosphere_layer') + fixed_air_pressure_coord = fixed_cl_cube.coord('air_pressure') + assert fixed_air_pressure_coord.points is not None + assert fixed_air_pressure_coord.bounds is not None + np.testing.assert_allclose(fixed_air_pressure_coord.points, + AIR_PRESSURE_POINTS) + np.testing.assert_allclose(fixed_air_pressure_coord.bounds, + AIR_PRESSURE_BOUNDS) + + +def test_cl_fix_metadata_with_ap(cl_file_with_ap): + """Test ``fix_metadata`` for ``cl``.""" + cubes = iris.load(cl_file_with_ap) + + # Raw cubes + assert len(cubes) == 4 + var_names = [cube.var_name for cube in cubes] + assert 'cl' in var_names + assert 'ps' in var_names + assert 'ap_bnds' in var_names + assert 'b_bnds' in var_names + + # Raw cl cube + cl_cube = cubes.extract_strict('cloud_area_fraction_in_atmosphere_layer') + air_pressure_coord = cl_cube.coord('air_pressure') + assert air_pressure_coord.points is not None + assert air_pressure_coord.bounds is None + np.testing.assert_allclose(air_pressure_coord.points, AIR_PRESSURE_POINTS) + + # Apply fix + fix = Cl(None) + fixed_cubes = fix.fix_metadata(cubes) + assert len(fixed_cubes) == 1 + fixed_cl_cube = fixed_cubes.extract_strict( + 'cloud_area_fraction_in_atmosphere_layer') + fixed_air_pressure_coord = fixed_cl_cube.coord('air_pressure') + assert fixed_air_pressure_coord.points is not None + assert fixed_air_pressure_coord.bounds is not None + np.testing.assert_allclose(fixed_air_pressure_coord.points, + AIR_PRESSURE_POINTS) + np.testing.assert_allclose(fixed_air_pressure_coord.bounds, + AIR_PRESSURE_BOUNDS) class TestTos(unittest.TestCase): """Test tos fixes.""" + def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual( Fix.get_fixes('CMIP5', 'BCC-CSM1-1', 'Amon', 'tos'), [Tos(None)]) + + +def test_tos_fix_data(): + """Test ``fix_data`` for ``tos``.""" + grid_lat = iris.coords.DimCoord( + [20.0, 40.0], + bounds=[[10.0, 30.0], [30.0, 50.0]], + var_name='rlat', + standard_name='grid_latitude', + ) + grid_lon = iris.coords.DimCoord( + [10.0, 20.0, 30.0], + bounds=[[5.0, 15.0], [15.0, 25.0], [25.0, 35.0]], + var_name='rlon', + standard_name='grid_longitude', + ) + latitude = iris.coords.AuxCoord( + [[-40.0, -20.0, 0.0], [-20.0, 0.0, 20.0]], + var_name='lat', + standard_name='latitude', + units='degrees_north', + ) + longitude = iris.coords.AuxCoord( + [[100.0, 140.0, 180.0], [80.0, 100.0, 120.0]], + var_name='lon', + standard_name='longitude', + units='degrees_east', + ) + + # Create cube without bounds + cube = iris.cube.Cube( + np.full((2, 3), 300.0), + var_name='tos', + units='K', + dim_coords_and_dims=[(grid_lat, 0), (grid_lon, 1)], + aux_coords_and_dims=[(latitude, (0, 1)), (longitude, (0, 1))], + ) + assert cube.coord('latitude').bounds is None + assert cube.coord('longitude').bounds is None + + # Apply fix + fix = Tos(None) + fixed_cube = fix.fix_data(cube) + assert fixed_cube is cube + assert fixed_cube.coord('latitude').bounds is not None + assert fixed_cube.coord('longitude').bounds is not None + latitude_bounds = np.array([[[-40, -33.75, -23.75, -30.0], + [-33.75, -6.25, 3.75, -23.75], + [-6.25, -1.02418074021670e-14, 10.0, 3.75]], + [[-30.0, -23.75, -13.75, -20.0], + [-23.75, 3.75, 13.75, -13.75], + [3.75, 10.0, 20.0, 13.75]]]) + np.testing.assert_allclose(fixed_cube.coord('latitude').bounds, + latitude_bounds) + longitude_bounds = np.array([[[140.625, 99.375, 99.375, 140.625], + [99.375, 140.625, 140.625, 99.375], + [140.625, 99.375, 99.375, 140.625]], + [[140.625, 99.375, 99.375, 140.625], + [99.375, 140.625, 140.625, 99.375], + [140.625, 99.375, 99.375, 140.625]]]) + np.testing.assert_allclose(fixed_cube.coord('longitude').bounds, + longitude_bounds) diff --git a/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1_m.py b/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1_m.py index 01f427f478..ebbdce78f9 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1_m.py +++ b/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1_m.py @@ -1,13 +1,40 @@ -"""Test Access1-0 fixes.""" +"""Test fixes for bcc-csm1-1-m.""" import unittest +from esmvalcore.cmor._fixes.cmip5.bcc_csm1_1_m import Cl, Tos from esmvalcore.cmor._fixes.fix import Fix -from esmvalcore.cmor._fixes.cmip5.bcc_csm1_1_m import Tos + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'bcc-csm1-1-m', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.bcc_csm1_1_m.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') class TestTos(unittest.TestCase): """Test tos fixes.""" + def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual( - Fix.get_fixes('CMIP5', 'BCC-CSM1-1-M', 'Amon', 'tos'), [Tos(None)]) + Fix.get_fixes('CMIP5', 'bcc-csm1-1-m', 'Amon', 'tos'), [Tos(None)]) + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.bcc_csm1_1_m.BaseTos.fix_data', + autospec=True) +def test_tos_fix_data(mock_base_fix_data): + """Test ``fix_data`` for ``tos``.""" + fix = Tos(None) + fix.fix_data('cubes') + mock_base_fix_data.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip5/test_canesm2.py b/tests/integration/cmor/_fixes/cmip5/test_canesm2.py index ff9d75082e..f3d9f024f5 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_canesm2.py +++ b/tests/integration/cmor/_fixes/cmip5/test_canesm2.py @@ -1,22 +1,39 @@ -"""Test CANESM2 fixes.""" +"""Test CanESM2 fixes.""" import unittest from cf_units import Unit from iris.cube import Cube +from esmvalcore.cmor._fixes.cmip5.canesm2 import Cl, FgCo2 from esmvalcore.cmor.fix import Fix -from esmvalcore.cmor._fixes.cmip5.canesm2 import FgCo2 + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'CanESM2', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.canesm2.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') class TestCanESM2Fgco2(unittest.TestCase): """Test fgc02 fixes.""" + def setUp(self): """Prepare tests.""" self.cube = Cube([1.0], var_name='fgco2', units='J') self.fix = FgCo2(None) def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual( Fix.get_fixes('CMIP5', 'CANESM2', 'Amon', 'fgco2'), [FgCo2(None)]) diff --git a/tests/integration/cmor/_fixes/cmip5/test_ccsm4.py b/tests/integration/cmor/_fixes/cmip5/test_ccsm4.py index 1d326c5a03..0d46d5e596 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_ccsm4.py +++ b/tests/integration/cmor/_fixes/cmip5/test_ccsm4.py @@ -5,12 +5,29 @@ from iris.coords import DimCoord from iris.cube import Cube +from esmvalcore.cmor._fixes.cmip5.ccsm4 import Cl, Rlut, Rlutcs, So from esmvalcore.cmor.fix import Fix -from esmvalcore.cmor._fixes.cmip5.ccsm4 import Rlut, Rlutcs, So + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'CCSM4', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.ccsm4.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') class TestsRlut(unittest.TestCase): """Test for rlut fixes.""" + def setUp(self): """Prepare tests.""" self.cube = Cube([1.0, 2.0], var_name='rlut') @@ -24,7 +41,7 @@ def setUp(self): self.fix = Rlut(None) def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual(Fix.get_fixes('CMIP5', 'CCSM4', 'Amon', 'rlut'), [Rlut(None)]) @@ -41,6 +58,7 @@ def test_fix_metadata(self): class TestsRlutcs(unittest.TestCase): """Test for rlutcs fixes.""" + def setUp(self): """Prepare tests.""" self.cube = Cube([1.0, 2.0], var_name='rlutcs') @@ -54,7 +72,7 @@ def setUp(self): self.fix = Rlutcs(None) def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual(Fix.get_fixes('CMIP5', 'CCSM4', 'Amon', 'rlutcs'), [Rlutcs(None)]) @@ -71,13 +89,14 @@ def test_fix_metadata(self): class TestSo(unittest.TestCase): """Tests for so fixes.""" + def setUp(self): """Prepare tests.""" self.cube = Cube([1.0, 2.0], var_name='so', units='1.0') self.fix = So(None) def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual(Fix.get_fixes('CMIP5', 'CCSM4', 'Amon', 'so'), [So(None)]) diff --git a/tests/integration/cmor/_fixes/cmip5/test_csiro_mk3_6_0.py b/tests/integration/cmor/_fixes/cmip5/test_csiro_mk3_6_0.py new file mode 100644 index 0000000000..e070c65220 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_csiro_mk3_6_0.py @@ -0,0 +1,21 @@ +"""Test fixes for CSIRO-Mk3-6-0.""" +import unittest + +from esmvalcore.cmor._fixes.cmip5.csiro_mk3_6_0 import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'CSIRO-Mk3-6-0', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.csiro_mk3_6_0.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip5/test_fgoals_g2.py b/tests/integration/cmor/_fixes/cmip5/test_fgoals_g2.py index be33db3390..d016b152c1 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_fgoals_g2.py +++ b/tests/integration/cmor/_fixes/cmip5/test_fgoals_g2.py @@ -1,16 +1,18 @@ """Test FGOALS-g2 fixes.""" import unittest +import numpy as np from cf_units import Unit from iris.coords import DimCoord -from iris.cube import Cube +from iris.cube import Cube, CubeList +from esmvalcore.cmor._fixes.cmip5.fgoals_g2 import AllVars, Cl from esmvalcore.cmor.fix import Fix -from esmvalcore.cmor._fixes.cmip5.fgoals_g2 import AllVars class TestAll(unittest.TestCase): """Test fixes for all vars.""" + def setUp(self): """Prepare tests.""" self.cube = Cube([[1.0, 2.0]], var_name='co2', units='J') @@ -29,7 +31,7 @@ def setUp(self): self.fix = AllVars(None) def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual( Fix.get_fixes('CMIP5', 'FGOALS-G2', 'Amon', 'tas'), [AllVars(None)]) @@ -43,7 +45,25 @@ def test_fix_metadata(self): 'day since 1-01-01 00:00:00.000000') self.assertEqual(time.units.calendar, 'gregorian') - def test_fix_metadata_dont_gail_if_not_time(self): + def test_fix_metadata_dont_fail_if_not_time(self): """Test calendar fix.""" self.cube.remove_coord('time') self.fix.fix_metadata([self.cube]) + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'FGOALS-g2', 'Amon', 'cl') + assert fix == [Cl(None), AllVars(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.fgoals_g2.add_pressure_level_coordinate', + autospec=True) +def test_cl_fix_metadata(mock_add_pressure_level): + """Test ``fix_metadata`` for ``cl``.""" + cubes = CubeList([Cube(0.0, var_name='cl'), Cube(1.0, var_name='x')]) + fix = Cl(None) + fixed_cubes = fix.fix_metadata(cubes) + mock_add_pressure_level.assert_called_once_with(cubes[0]) + assert fixed_cubes == CubeList([cubes[0]]) diff --git a/tests/integration/cmor/_fixes/cmip5/test_fgoals_s2.py b/tests/integration/cmor/_fixes/cmip5/test_fgoals_s2.py new file mode 100644 index 0000000000..e77c095b3c --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_fgoals_s2.py @@ -0,0 +1,53 @@ +"""Test FGOALS-s2 fixes.""" +import numpy as np +from iris.coords import AuxCoord, DimCoord +from iris.cube import Cube, CubeList + +from esmvalcore.cmor._fixes.cmip5.fgoals_s2 import AllVars +from esmvalcore.cmor.fix import Fix + + +def test_get_allvars_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'FGOALS-s2', 'Amon', 'tas') + assert fix == [AllVars(None)] + + +LAT_COORD = DimCoord( + [-20.0, 0.0, 10.0], + bounds=[[-30.0, -10.0], [-10.0, 5.0], [5.0, 20.0]], + var_name='lat', + standard_name='latitude', +) +LAT_COORD_MULT = AuxCoord( + [[-20.0], [0.0], [10.0]], + bounds=[[[-30.0, -10.0]], [[-10.0, 5.0]], [[5.0, 20.0]]], + var_name='lat', + standard_name='latitude', +) +LAT_COORD_SMALL = DimCoord([0.0], + bounds=[-45.0, 45.0], + var_name='lat', + standard_name='latitude') + + +def test_allvars_fix_metadata(): + """Test ``fix_metadata`` for all variables.""" + cubes = CubeList([ + Cube([1, 2, 3], dim_coords_and_dims=[(LAT_COORD.copy(), 0)]), + Cube([[1], [2], [3]], + aux_coords_and_dims=[(LAT_COORD_MULT.copy(), (0, 1))]), + Cube([1], dim_coords_and_dims=[(LAT_COORD_SMALL.copy(), 0)]), + Cube(0.0), + ]) + fix = AllVars(None) + fixed_cubes = fix.fix_metadata(cubes) + assert len(fixed_cubes) == 4 + assert fixed_cubes[0].coord('latitude') != LAT_COORD + np.testing.assert_allclose(fixed_cubes[0].coord('latitude').points, + [-20.0, 0.0, 10.0]) + np.testing.assert_allclose(fixed_cubes[0].coord('latitude').bounds, + [[-25.0, -10.0], [-10.0, 5.0], [5.0, 20.0]]) + assert fixed_cubes[1].coord('latitude') == LAT_COORD_MULT + assert fixed_cubes[2].coord('latitude') == LAT_COORD_SMALL + assert fixed_cubes[3] == Cube(0.0) diff --git a/tests/integration/cmor/_fixes/cmip5/test_giss_e2_h.py b/tests/integration/cmor/_fixes/cmip5/test_giss_e2_h.py new file mode 100644 index 0000000000..0f32a68ca0 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_giss_e2_h.py @@ -0,0 +1,21 @@ +"""Test fixes for GISS-E2-H.""" +import unittest + +from esmvalcore.cmor._fixes.cmip5.giss_e2_h import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'GISS-E2-H', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.giss_e2_h.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip5/test_giss_e2_r.py b/tests/integration/cmor/_fixes/cmip5/test_giss_e2_r.py new file mode 100644 index 0000000000..05610eaf03 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_giss_e2_r.py @@ -0,0 +1,21 @@ +"""Test fixes for GISS-E2-R.""" +import unittest + +from esmvalcore.cmor._fixes.cmip5.giss_e2_r import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'GISS-E2-R', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.giss_e2_r.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip5/test_hadgem2_es.py b/tests/integration/cmor/_fixes/cmip5/test_hadgem2_es.py index b6eea1cd25..47270abafd 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_hadgem2_es.py +++ b/tests/integration/cmor/_fixes/cmip5/test_hadgem2_es.py @@ -1,14 +1,15 @@ """Test HADGEM2-ES fixes.""" import unittest +from esmvalcore.cmor._fixes.cmip5.hadgem2_es import O2, AllVars, Cl from esmvalcore.cmor.fix import Fix -from esmvalcore.cmor._fixes.cmip5.hadgem2_es import AllVars, O2 class TestAllVars(unittest.TestCase): """Test allvars fixes.""" + def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual( Fix.get_fixes('CMIP5', 'HADGEM2-ES', 'Amon', 'tas'), [AllVars(None)]) @@ -16,8 +17,25 @@ def test_get(self): class TestO2(unittest.TestCase): """Test o2 fixes.""" + def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual( Fix.get_fixes('CMIP5', 'HADGEM2-ES', 'Amon', 'o2'), [O2(None), AllVars(None)]) + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'HadGEM2-ES', 'Amon', 'cl') + assert fix == [Cl(None), AllVars(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.hadgem2_es.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip5/test_inmcm4.py b/tests/integration/cmor/_fixes/cmip5/test_inmcm4.py index 9da7f16103..322968cf5f 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_inmcm4.py +++ b/tests/integration/cmor/_fixes/cmip5/test_inmcm4.py @@ -1,22 +1,39 @@ """Tests for inmcm4 fixes.""" import unittest -from iris.cube import Cube from cf_units import Unit +from iris.cube import Cube +from esmvalcore.cmor._fixes.cmip5.inmcm4 import Cl, Gpp, Lai, Nbp from esmvalcore.cmor.fix import Fix -from esmvalcore.cmor._fixes.cmip5.inmcm4 import Gpp, Lai, Nbp + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'inmcm4', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.inmcm4.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') class TestGpp(unittest.TestCase): """Test gpp fixes.""" + def setUp(self): """Prepare tests.""" self.cube = Cube([1.0], var_name='gpp', units='J') self.fix = Gpp(None) def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual(Fix.get_fixes('CMIP5', 'INMCM4', 'Amon', 'gpp'), [Gpp(None)]) @@ -29,13 +46,14 @@ def test_fix_data(self): class TestLai(unittest.TestCase): """Test lai fixes.""" + def setUp(self): """Prepare tests.""" self.cube = Cube([1.0], var_name='lai', units='J') self.fix = Lai(None) def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual(Fix.get_fixes('CMIP5', 'INMCM4', 'Amon', 'lai'), [Lai(None)]) @@ -48,13 +66,14 @@ def test_fix_data(self): class TestNbp(unittest.TestCase): """Tests for nbp.""" + def setUp(self): """Prepare tests.""" self.cube = Cube([1.0], var_name='nbp') self.fix = Nbp(None) def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual(Fix.get_fixes('CMIP5', 'INMCM4', 'Amon', 'nbp'), [Nbp(None)]) diff --git a/tests/integration/cmor/_fixes/cmip5/test_ipsl_cm5a_lr.py b/tests/integration/cmor/_fixes/cmip5/test_ipsl_cm5a_lr.py new file mode 100644 index 0000000000..0bcf3bd201 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_ipsl_cm5a_lr.py @@ -0,0 +1,21 @@ +"""Test fixes for IPSL-CM5A-LR.""" +import unittest + +from esmvalcore.cmor._fixes.cmip5.ipsl_cm5a_lr import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'IPSL-CM5A-LR', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.ipsl_cm5a_lr.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip5/test_ipsl_cm5a_mr.py b/tests/integration/cmor/_fixes/cmip5/test_ipsl_cm5a_mr.py new file mode 100644 index 0000000000..48b364c2ec --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_ipsl_cm5a_mr.py @@ -0,0 +1,21 @@ +"""Test fixes for IPSL-CM5A-MR.""" +import unittest + +from esmvalcore.cmor._fixes.cmip5.ipsl_cm5a_mr import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'IPSL-CM5A-MR', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.ipsl_cm5a_mr.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip5/test_ipsl_cm5b_lr.py b/tests/integration/cmor/_fixes/cmip5/test_ipsl_cm5b_lr.py new file mode 100644 index 0000000000..18dd1ac578 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_ipsl_cm5b_lr.py @@ -0,0 +1,21 @@ +"""Test fixes for IPSL-CM5B-LR.""" +import unittest + +from esmvalcore.cmor._fixes.cmip5.ipsl_cm5b_lr import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'IPSL-CM5B-LR', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.ipsl_cm5b_lr.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip5/test_miroc5.py b/tests/integration/cmor/_fixes/cmip5/test_miroc5.py index d44093c405..10195f5bfe 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_miroc5.py +++ b/tests/integration/cmor/_fixes/cmip5/test_miroc5.py @@ -5,19 +5,52 @@ from cf_units import Unit from iris.cube import Cube -from esmvalcore.cmor._fixes.cmip5.miroc5 import Sftof, Tas +from esmvalcore.cmor._fixes.cmip5.miroc5 import Cl, Hur, Sftof, Tas from esmvalcore.cmor.fix import Fix +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'MIROC5', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.miroc5.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') + + +def test_get_hur_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'MIROC5', 'Amon', 'hur') + assert fix == [Hur(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.miroc5.Tas.fix_metadata', + autospec=True) +def test_hur_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``hur``.""" + fix = Hur(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') + + class TestSftof(unittest.TestCase): """Test sftof fixes.""" + def setUp(self): """Prepare tests.""" self.cube = Cube([1.0], var_name='sftof', units='J') self.fix = Sftof(None) def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual(Fix.get_fixes('CMIP5', 'MIROC5', 'Amon', 'sftof'), [Sftof(None)]) @@ -30,6 +63,7 @@ def test_fix_data(self): class TestTas(unittest.TestCase): """Test tas fixes.""" + def setUp(self): """Prepare tests.""" self.coord_name = 'latitude' @@ -40,7 +74,7 @@ def setUp(self): self.fix = Tas(None) def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual(Fix.get_fixes('CMIP5', 'MIROC5', 'Amon', 'tas'), [Tas(None)]) diff --git a/tests/integration/cmor/_fixes/cmip5/test_miroc_esm.py b/tests/integration/cmor/_fixes/cmip5/test_miroc_esm.py index 51d887c597..2f49a6bbb0 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_miroc_esm.py +++ b/tests/integration/cmor/_fixes/cmip5/test_miroc_esm.py @@ -6,19 +6,36 @@ from iris.cube import Cube from iris.exceptions import CoordinateNotFoundError +from esmvalcore.cmor._fixes.cmip5.miroc_esm import AllVars, Cl, Co2, Tro3 from esmvalcore.cmor.fix import Fix -from esmvalcore.cmor._fixes.cmip5.miroc_esm import AllVars, Co2, Tro3 + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'MIROC-ESM', 'Amon', 'cl') + assert fix == [Cl(None), AllVars(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.miroc_esm.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') class TestCo2(unittest.TestCase): """Test c02 fixes.""" + def setUp(self): """Prepare tests.""" self.cube = Cube([1.0], var_name='co2', units='J') self.fix = Co2(None) def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual( Fix.get_fixes('CMIP5', 'MIROC-ESM', 'Amon', 'co2'), [Co2(None), AllVars(None)]) @@ -32,13 +49,14 @@ def test_fix_metadata(self): class TestTro3(unittest.TestCase): """Test tro3 fixes.""" + def setUp(self): """Prepare tests.""" self.cube = Cube([1.0], var_name='tro3', units='J') self.fix = Tro3(None) def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual( Fix.get_fixes('CMIP5', 'MIROC-ESM', 'Amon', 'tro3'), [Tro3(None), AllVars(None)]) @@ -52,6 +70,7 @@ def test_fix_data(self): class TestAll(unittest.TestCase): """Test fixes for allvars.""" + def setUp(self): """Prepare tests.""" self.cube = Cube([[1.0, 2.0], [3.0, 4.0]], var_name='co2', units='J') @@ -65,7 +84,7 @@ def setUp(self): self.fix = AllVars(None) def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual( Fix.get_fixes('CMIP5', 'MIROC-ESM', 'Amon', 'tos'), [AllVars(None)]) diff --git a/tests/integration/cmor/_fixes/cmip5/test_mpi_esm_lr.py b/tests/integration/cmor/_fixes/cmip5/test_mpi_esm_lr.py index f8c90ec3e2..fa07e930d7 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_mpi_esm_lr.py +++ b/tests/integration/cmor/_fixes/cmip5/test_mpi_esm_lr.py @@ -4,19 +4,36 @@ from cf_units import Unit from iris.cube import Cube +from esmvalcore.cmor._fixes.cmip5.mpi_esm_lr import Cl, Pctisccp from esmvalcore.cmor.fix import Fix -from esmvalcore.cmor._fixes.cmip5.mpi_esm_lr import Pctisccp + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'MPI-ESM-LR', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.mpi_esm_lr.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') class TestPctisccp2(unittest.TestCase): """Test Pctisccp2 fixes.""" + def setUp(self): """Prepare tests.""" self.cube = Cube([1.0], var_name='pctisccp', units='J') self.fix = Pctisccp(None) def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual( Fix.get_fixes('CMIP5', 'MPI-ESM-LR', 'Amon', 'pctisccp'), [Pctisccp(None)]) diff --git a/tests/integration/cmor/_fixes/cmip5/test_mpi_esm_mr.py b/tests/integration/cmor/_fixes/cmip5/test_mpi_esm_mr.py new file mode 100644 index 0000000000..0bdeafab3b --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_mpi_esm_mr.py @@ -0,0 +1,21 @@ +"""Test fixes for MPI-ESM-MR.""" +import unittest + +from esmvalcore.cmor._fixes.cmip5.mpi_esm_mr import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'MPI-ESM-MR', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.mpi_esm_mr.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip5/test_mpi_esm_p.py b/tests/integration/cmor/_fixes/cmip5/test_mpi_esm_p.py new file mode 100644 index 0000000000..852070264a --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_mpi_esm_p.py @@ -0,0 +1,21 @@ +"""Test fixes for MPI-ESM-P.""" +import unittest + +from esmvalcore.cmor._fixes.cmip5.mpi_esm_p import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'MPI-ESM-P', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.mpi_esm_p.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip5/test_mri_cgcm3.py b/tests/integration/cmor/_fixes/cmip5/test_mri_cgcm3.py index 1b93fb3004..4825150b31 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_mri_cgcm3.py +++ b/tests/integration/cmor/_fixes/cmip5/test_mri_cgcm3.py @@ -1,14 +1,31 @@ -"""Test MRI-GCM3 fixes.""" +"""Test MRI-CGCM3 fixes.""" import unittest +from esmvalcore.cmor._fixes.cmip5.mri_cgcm3 import Cl, Msftmyz, ThetaO from esmvalcore.cmor.fix import Fix -from esmvalcore.cmor._fixes.cmip5.mri_cgcm3 import Msftmyz, ThetaO + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'MRI-CGCM3', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.mri_cgcm3.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') class TestMsftmyz(unittest.TestCase): """Test msftmyz fixes.""" + def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual( Fix.get_fixes('CMIP5', 'MRI-CGCM3', 'Amon', 'msftmyz'), [Msftmyz(None)]) @@ -16,8 +33,9 @@ def test_get(self): class TestThetao(unittest.TestCase): """Test thetao fixes.""" + def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual( Fix.get_fixes('CMIP5', 'MRI-CGCM3', 'Amon', 'thetao'), [ThetaO(None)]) diff --git a/tests/integration/cmor/_fixes/cmip5/test_noresm1_m.py b/tests/integration/cmor/_fixes/cmip5/test_noresm1_m.py new file mode 100644 index 0000000000..4e740a44d0 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_noresm1_m.py @@ -0,0 +1,21 @@ +"""Test fixes for NorESM1-M.""" +import unittest + +from esmvalcore.cmor._fixes.cmip5.noresm1_m import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP5', 'NorESM1-M', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.noresm1_m.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip6/test_bcc_csm2_mr.py b/tests/integration/cmor/_fixes/cmip6/test_bcc_csm2_mr.py new file mode 100644 index 0000000000..4635e0a9b4 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_bcc_csm2_mr.py @@ -0,0 +1,97 @@ +"""Test fixes for BCC-CSM2-MR.""" +import unittest + +import iris + +from esmvalcore.cmor._fixes.cmip6.bcc_csm2_mr import Cl, Tos +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'BCC-CSM2-MR', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.bcc_csm2_mr.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') + + +def test_get_tos_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'BCC-CSM2-MR', 'Omon', 'tos') + assert fix == [Tos(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.bcc_csm2_mr.BaseTos.fix_data', + autospec=True) +def test_tos_fix_data(mock_base_fix_data): + """Test ``fix_data`` for ``tos``.""" + fix = Tos(None) + fix.fix_data('cubes') + mock_base_fix_data.assert_called_once_with(fix, 'cubes') + + +def test_tos_fix_metadata(): + """Test ``fix_metadata`` for ``tos``.""" + grid_lat = iris.coords.DimCoord([1.0], + var_name='lat', + standard_name='latitude', + long_name='latitude', + units='degrees_north', + attributes={'1D': '1'}) + grid_lon = iris.coords.DimCoord([1.0], + var_name='lon', + standard_name='longitude', + long_name='longitude', + units='degrees_east', + circular=True, + attributes={'1D': '1'}) + latitude = iris.coords.AuxCoord([[0.0]], + var_name='lat', + standard_name='latitude', + long_name='latitude', + units='degrees_north') + longitude = iris.coords.AuxCoord([[0]], + var_name='lon', + standard_name='longitude', + long_name='longitude', + units='degrees_east') + cube = iris.cube.Cube( + [[[0.0]]], + var_name='tos', + long_name='sea_surface_temperature', + dim_coords_and_dims=[(grid_lat.copy(), 1), (grid_lon.copy(), 2)], + aux_coords_and_dims=[(latitude.copy(), (1, 2)), + (longitude.copy(), (1, 2))], + ) + cubes = iris.cube.CubeList([cube, iris.cube.Cube(0.0)]) + fix = Tos(None) + fixed_cubes = fix.fix_metadata(cubes) + tos_cube = fixed_cubes.extract_strict('sea_surface_temperature') + + # No duplicates anymore + assert len(tos_cube.coords('latitude')) == 1 + assert len(tos_cube.coords('longitude')) == 1 + + # Latitude + grid_lat = tos_cube.coord('grid_latitude') + assert grid_lat.var_name == 'i' + assert grid_lat.long_name == 'grid_latitude' + assert grid_lat.standard_name is None + assert grid_lat.units == '1' + + # Longitude + grid_lon = tos_cube.coord('grid_longitude') + assert grid_lon.var_name == 'j' + assert grid_lon.long_name == 'grid_longitude' + assert grid_lon.standard_name is None + assert grid_lon.units == '1' + assert not grid_lon.circular diff --git a/tests/integration/cmor/_fixes/cmip6/test_bcc_esm1.py b/tests/integration/cmor/_fixes/cmip6/test_bcc_esm1.py new file mode 100644 index 0000000000..1c7e0c04d6 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_bcc_esm1.py @@ -0,0 +1,37 @@ +"""Test fixes for BCC-ESM1.""" +import unittest + +from esmvalcore.cmor._fixes.cmip6.bcc_esm1 import Cl, Tos +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'BCC-ESM1', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.bcc_esm1.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') + + +def test_get_tos_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'BCC-ESM1', 'Omon', 'tos') + assert fix == [Tos(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.bcc_esm1.BaseTos.fix_metadata', + autospec=True) +def test_tos_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``tos``.""" + fix = Tos(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip6/test_cams_csm1_0.py b/tests/integration/cmor/_fixes/cmip6/test_cams_csm1_0.py new file mode 100644 index 0000000000..d5e023b6f3 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_cams_csm1_0.py @@ -0,0 +1,21 @@ +"""Test fixes for CAMS-CSM1-0.""" +import unittest + +from esmvalcore.cmor._fixes.cmip6.cams_csm1_0 import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CAMS-CSM1-0', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.cams_csm1_0.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip6/test_ukesm1_0_ll.py b/tests/integration/cmor/_fixes/cmip6/test_ukesm1_0_ll.py index fa09029520..1902dbf53c 100644 --- a/tests/integration/cmor/_fixes/cmip6/test_ukesm1_0_ll.py +++ b/tests/integration/cmor/_fixes/cmip6/test_ukesm1_0_ll.py @@ -1,8 +1,12 @@ """Tests for the fixes of UKESM1-0-LL.""" +import os + import iris +import numpy as np import pytest +from netCDF4 import Dataset -from esmvalcore.cmor._fixes.cmip6.ukesm1_0_ll import AllVars +from esmvalcore.cmor._fixes.cmip6.ukesm1_0_ll import AllVars, Cl from esmvalcore.cmor.fix import Fix @@ -34,3 +38,132 @@ def test_allvars_no_need_tofix_metadata(sample_cubes): assert out_cubes is sample_cubes for cube in out_cubes: assert cube.attributes['parent_time_units'] == 'days since 1850-01-01' + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'UKESM1-0-LL', 'Amon', 'cl') + assert fix == [Cl(None), AllVars(None)] + + +@pytest.fixture +def cl_file(tmp_path): + """Create netcdf file with similar issues as ``cl``.""" + nc_path = os.path.join(tmp_path, 'ukesm1_0_ll_cl.nc') + dataset = Dataset(nc_path, mode='w') + + # Dimensions + dataset.createDimension('time', size=1) + dataset.createDimension('lev', size=2) + dataset.createDimension('lat', size=1) + dataset.createDimension('lon', size=2) + dataset.createDimension('bnds', size=2) + + # Dimensional variables + dataset.createVariable('time', np.float64, dimensions=('time',)) + dataset.createVariable('lev', np.float64, dimensions=('lev',)) + dataset.createVariable('lev_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.createVariable('lat', np.float64, dimensions=('lat',)) + dataset.createVariable('lon', np.float64, dimensions=('lon',)) + dataset.variables['time'][:] = [0.0] + dataset.variables['time'].standard_name = 'time' + dataset.variables['time'].units = 'days since 6543-2-1' + dataset.variables['lev'][:] = [1.0, 2.0] + dataset.variables['lev'].bounds = 'lev_bnds' + dataset.variables['lev'].standard_name = ( + 'atmosphere_hybrid_height_coordinate') + dataset.variables['lev'].units = 'm' + dataset.variables['lev'].formula_terms = 'a: lev b: b orog: orog' + dataset.variables['lev_bnds'][:] = [[0.5, 1.5], [1.5, 3.0]] + dataset.variables['lev_bnds'].standard_name = ( + 'atmosphere_hybrid_height_coordinate') + dataset.variables['lev_bnds'].units = '1' + dataset.variables['lev_bnds'].formula_terms = ( + 'a: lev_bnds b: b_bnds orog: orog') + dataset.variables['lat'][:] = [0.0] + dataset.variables['lat'].standard_name = 'latitude' + dataset.variables['lat'].units = 'degrees_north' + dataset.variables['lon'][:] = [30.0, 60.0] + dataset.variables['lon'].standard_name = 'longitude' + dataset.variables['lon'].units = 'degrees_east' + + # Coordinates for derivation of height coordinate + dataset.createVariable('b', np.float64, dimensions=('lev',)) + dataset.createVariable('b_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.createVariable('orog', np.float64, dimensions=('lat', 'lon')) + dataset.variables['b'][:] = [0.0, 1.0] + dataset.variables['b_bnds'][:] = [[-1.0, 0.5], [0.5, 2.0]] + dataset.variables['orog'][:] = [[0.0, 1.0]] + dataset.variables['orog'].standard_name = 'surface_altitude' + dataset.variables['orog'].units = 'm' + + # Cl variable + dataset.createVariable('cl', np.float32, + dimensions=('time', 'lev', 'lat', 'lon')) + dataset.variables['cl'][:] = np.full((1, 2, 1, 2), 0.0, dtype=np.float32) + dataset.variables['cl'].standard_name = ( + 'cloud_area_fraction_in_atmosphere_layer') + dataset.variables['cl'].units = '%' + + dataset.close() + return nc_path + + +def test_cl_fix_metadata(cl_file): + """Test ``fix_metadata`` for ``cl``.""" + cubes = iris.load(cl_file) + + # Raw cubes + assert len(cubes) == 3 + var_names = [cube.var_name for cube in cubes] + assert 'cl' in var_names + assert 'orog' in var_names + assert 'b_bnds' in var_names + + # Height coordinate + height_points = np.array([[[1.0, 1.0]], + [[2.0, 3.0]]]) + height_bounds_wrong = np.array([[[[0.5, 1.5], + [0.5, 1.5]]], + [[[1.5, 3.0], + [2.5, 4.0]]]]) + height_bounds_right = np.array([[[[0.5, 1.5], + [-0.5, 2.0]]], + [[[1.5, 3.0], + [2.0, 5.0]]]]) + air_pressure_points = np.array([[[101312.98512207, 101312.98512207]], + [[101300.97123885, 101288.95835383]]]) + air_pressure_bounds = np.array([[[[101318.99243691, 101306.9780559], + [101331.00781103, 101300.97123885]]], + [[[101306.9780559, 101288.95835383], + [101300.97123885, 101264.93559234]]]]) + + # Raw cl cube + cl_cube = cubes.extract_strict('cloud_area_fraction_in_atmosphere_layer') + height_coord = cl_cube.coord('altitude') + assert height_coord.points is not None + assert height_coord.bounds is not None + np.testing.assert_allclose(height_coord.points, height_points) + np.testing.assert_allclose(height_coord.bounds, height_bounds_wrong) + assert not np.allclose(height_coord.bounds, height_bounds_right) + assert not cl_cube.coords('air_pressure') + + # Apply fix + fix = Cl(None) + fixed_cubes = fix.fix_metadata(cubes) + assert len(fixed_cubes) == 1 + fixed_cl_cube = fixed_cubes.extract_strict( + 'cloud_area_fraction_in_atmosphere_layer') + fixed_height_coord = fixed_cl_cube.coord('altitude') + assert fixed_height_coord.points is not None + assert fixed_height_coord.bounds is not None + np.testing.assert_allclose(fixed_height_coord.points, height_points) + np.testing.assert_allclose(fixed_height_coord.bounds, height_bounds_right) + assert not np.allclose(fixed_height_coord.bounds, height_bounds_wrong) + air_pressure_coord = cl_cube.coord('air_pressure') + np.testing.assert_allclose(air_pressure_coord.points, air_pressure_points) + np.testing.assert_allclose(air_pressure_coord.bounds, air_pressure_bounds) + assert air_pressure_coord.var_name == 'plev' + assert air_pressure_coord.standard_name == 'air_pressure' + assert air_pressure_coord.long_name == 'pressure' + assert air_pressure_coord.units == 'Pa' From 0af87374908347501dda7d48dbbd238939d3f0b6 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Tue, 3 Mar 2020 11:11:58 +0100 Subject: [PATCH 3/9] Fixed fixes for cl of CESM2 models and added tests --- esmvalcore/cmor/_fixes/cmip6/cesm2.py | 80 ++++----- esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py | 35 ++++ .../cmor/_fixes/cmip6/test_cesm2.py | 158 +++++++++++++++++- .../cmor/_fixes/cmip6/test_cesm2_waccm.py | 77 ++++++++- 4 files changed, 309 insertions(+), 41 deletions(-) diff --git a/esmvalcore/cmor/_fixes/cmip6/cesm2.py b/esmvalcore/cmor/_fixes/cmip6/cesm2.py index 84d34ba32c..62e8aae58d 100644 --- a/esmvalcore/cmor/_fixes/cmip6/cesm2.py +++ b/esmvalcore/cmor/_fixes/cmip6/cesm2.py @@ -1,57 +1,59 @@ """Fixes for CESM2 model.""" -import iris +from shutil import copyfile + +from netCDF4 import Dataset -from ..cmip5.bcc_csm1_1 import Cl as BaseCl from ..fix import Fix -from ..shared import (add_aux_coords_from_cubes, add_scalar_depth_coord, - add_scalar_height_coord, add_scalar_typeland_coord, - add_scalar_typesea_coord) +from ..shared import (add_scalar_depth_coord, add_scalar_height_coord, + add_scalar_typeland_coord, add_scalar_typesea_coord) -class Cl(BaseCl): +class Cl(Fix): """Fixes for ``cl``.""" - def fix_metadata(self, cubes): - """Fix vertical hybrid sigma coordinate. + def fix_file(self, filepath, output_dir): + """Fix hybrid pressure coordinate. + + Adds missing ``formula_terms`` attribute to file and fix ordering + of auxiliary coordinates ``a`` and ``b``. + + Note + ---- + Fixing this with :mod:`iris` in ``fix_metadata`` or ``fix_data`` is + **not** possible, since the bounds of the vertical coordinates ``a`` + and ``b`` are not present in the loaded :class:`iris.cube.CubeList`, + even when :func:`iris.load_raw` is used. Parameters ---------- - cubes : iris.cube.CubeList - Input cubes. + filepath : str + Path to the original file. + output_dir : str + Path of the directory where the fixed file is saved to. Returns ------- - iris.cube.CubeList + str + Path to the fixed file. """ - cube = self.get_cube_from_list(cubes) - - # Add auxiliary coordinate from list of cubes - coords_to_add = { - 'a': 1, - 'b': 1, - 'ps': (0, 2, 3), - 'p0': (), - } - add_aux_coords_from_cubes(cube, cubes, coords_to_add) - - # Add ap coordinate - a_coord = cube.coord(var_name='a') - p0_coord = cube.coord(var_name='p0') - ap_coord = a_coord * p0_coord.points[0] - ap_coord.units = a_coord.units * p0_coord.units - ap_coord.rename('vertical pressure') - ap_coord.var_name = 'ap' - cube.add_aux_coord(ap_coord, cube.coord_dims(a_coord)) - - # Add aux_factory - pressure_coord_factory = iris.aux_factory.HybridPressureFactory( - delta=ap_coord, - sigma=cube.coord(var_name='b'), - surface_air_pressure=cube.coord(var_name='ps'), - ) - cube.add_aux_factory(pressure_coord_factory) - return cubes + new_path = self.get_fixed_filepath(output_dir, filepath) + copyfile(filepath, new_path) + dataset = Dataset(new_path, mode='a') + + # Fix hybrid sigma pressure coordinate + dataset.variables['lev'].formula_terms = 'p0: p0 a: a b: b ps: ps' + dataset.variables['lev'].standard_name = ( + 'atmosphere_hybrid_sigma_pressure_coordinate') + dataset.variables['lev'].units = '1' + + # Fix auxiliary coordinates + dataset.variables['a'][:] = dataset.variables['a'][::-1] + dataset.variables['b'][:] = dataset.variables['b'][::-1] + + # Save + dataset.close() + return new_path class Fgco2(Fix): diff --git a/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py b/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py index b40b20b7b4..08d6f5ec21 100644 --- a/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py +++ b/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py @@ -1,4 +1,6 @@ """Fixes for CESM2-WACCM model.""" +from netCDF4 import Dataset + from .cesm2 import Cl as BaseCl from .cesm2 import Tas as BaseTas @@ -6,6 +8,39 @@ class Cl(BaseCl): """Fixes for cl.""" + def fix_file(self, filepath, output_dir): + """Fix hybrid pressure coordinate. + + Adds missing ``formula_terms`` attribute to file and fix ordering + of auxiliary coordinates ``a``, ``b``, ``a_bnds`` and ``b_bnds``. + + Note + ---- + Fixing this with :mod:`iris` in ``fix_metadata`` or ``fix_data`` is + **not** possible, since the bounds of the vertical coordinates ``a`` + and ``b`` are not present in the loaded :class:`iris.cube.CubeList`, + even when :func:`iris.load_raw` is used. + + Parameters + ---------- + filepath : str + Path to the original file. + output_dir : str + Path of the directory where the fixed file is saved to. + + Returns + ------- + str + Path to the fixed file. + + """ + new_path = super().fix_file(filepath, output_dir) + dataset = Dataset(new_path, mode='a') + dataset.variables['a_bnds'][:] = dataset.variables['a_bnds'][::-1] + dataset.variables['b_bnds'][:] = dataset.variables['b_bnds'][::-1] + dataset.close() + return new_path + class Tas(BaseTas): """Fixes for tas.""" diff --git a/tests/integration/cmor/_fixes/cmip6/test_cesm2.py b/tests/integration/cmor/_fixes/cmip6/test_cesm2.py index 826d2f6118..f2b548b9bf 100644 --- a/tests/integration/cmor/_fixes/cmip6/test_cesm2.py +++ b/tests/integration/cmor/_fixes/cmip6/test_cesm2.py @@ -1,25 +1,181 @@ """Tests for the fixes of CESM2.""" +import os +import unittest + import iris +import numpy as np import pytest from cf_units import Unit +from netCDF4 import Dataset -from esmvalcore.cmor._fixes.cmip6.cesm2 import Tas +from esmvalcore.cmor._fixes.cmip6.cesm2 import Cl, Tas from esmvalcore.cmor.fix import Fix +@pytest.fixture +def cl_file(tmp_path): + """Create netcdf file with similar issues as ``cl``.""" + nc_path = os.path.join(tmp_path, 'cesm2_cl.nc') + dataset = Dataset(nc_path, mode='w') + dataset.createDimension('time', size=1) + dataset.createDimension('lev', size=2) + dataset.createDimension('lat', size=3) + dataset.createDimension('lon', size=4) + dataset.createDimension('bnds', size=2) + + # Dimensional variables + dataset.createVariable('time', np.float64, dimensions=('time',)) + dataset.createVariable('lev', np.float64, dimensions=('lev',)) + dataset.createVariable('lev_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.createVariable('lat', np.float64, dimensions=('lat',)) + dataset.createVariable('lon', np.float64, dimensions=('lon',)) + dataset.variables['time'][:] = [0.0] + dataset.variables['time'].standard_name = 'time' + dataset.variables['time'].units = 'days since 6543-2-1' + dataset.variables['lev'][:] = [1.0, 2.0] + dataset.variables['lev'].bounds = 'lev_bnds' + dataset.variables['lev'].units = '1' + dataset.variables['lev_bnds'][:] = [[0.5, 1.5], [1.5, 3.0]] + dataset.variables['lev_bnds'].standard_name = ( + 'atmosphere_hybrid_sigma_pressure_coordinate') + dataset.variables['lev_bnds'].units = '1' + dataset.variables['lev_bnds'].formula_terms = ( + 'p0: p0 a: a_bnds b: b_bnds ps: ps') + dataset.variables['lat'][:] = [-30.0, 0.0, 30.0] + dataset.variables['lat'].standard_name = 'latitude' + dataset.variables['lat'].units = 'degrees_north' + dataset.variables['lon'][:] = [30.0, 60.0, 90.0, 120.0] + dataset.variables['lon'].standard_name = 'longitude' + dataset.variables['lon'].units = 'degrees_east' + + # Coordinates for derivation of pressure coordinate + dataset.createVariable('a', np.float64, dimensions=('lev',)) + dataset.createVariable('a_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.createVariable('b', np.float64, dimensions=('lev',)) + dataset.createVariable('b_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.createVariable('p0', np.float64, dimensions=()) + dataset.createVariable('ps', np.float64, + dimensions=('time', 'lat', 'lon')) + dataset.variables['a'][:] = [2.0, 1.0] # Wrong order intended + dataset.variables['a'].bounds = 'a_bnds' + dataset.variables['a_bnds'][:] = [[0.0, 1.5], [1.5, 3.0]] + dataset.variables['b'][:] = [1.0, 0.0] # Wrong order intended + dataset.variables['b'].bounds = 'b_bnds' + dataset.variables['b_bnds'][:] = [[-1.0, 0.5], [0.5, 2.0]] + dataset.variables['p0'][:] = 1.0 + dataset.variables['p0'].units = 'Pa' + dataset.variables['ps'][:] = np.arange(1 * 3 * 4).reshape(1, 3, 4) + dataset.variables['ps'].standard_name = 'surface_air_pressure' + dataset.variables['ps'].units = 'Pa' + + # Cl variable + dataset.createVariable('cl', np.float32, + dimensions=('time', 'lev', 'lat', 'lon')) + dataset.variables['cl'][:] = np.full((1, 2, 3, 4), 0.0, dtype=np.float32) + dataset.variables['cl'].standard_name = ( + 'cloud_area_fraction_in_atmosphere_layer') + dataset.variables['cl'].units = '%' + + dataset.close() + return nc_path + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CESM2', 'Amon', 'cl') + assert fix == [Cl(None)] + + +AIR_PRESSURE_POINTS = np.array([[[[1.0, 1.0, 1.0, 1.0], + [1.0, 1.0, 1.0, 1.0], + [1.0, 1.0, 1.0, 1.0]], + [[2.0, 3.0, 4.0, 5.0], + [6.0, 7.0, 8.0, 9.0], + [10.0, 11.0, 12.0, 13.0]]]]) +AIR_PRESSURE_BOUNDS = np.array([[[[[0.0, 1.5], + [-1.0, 2.0], + [-2.0, 2.5], + [-3.0, 3.0]], + [[-4.0, 3.5], + [-5.0, 4.0], + [-6.0, 4.5], + [-7.0, 5.0]], + [[-8.0, 5.5], + [-9.0, 6.0], + [-10.0, 6.5], + [-11.0, 7.0]]], + [[[1.5, 3.0], + [2.0, 5.0], + [2.5, 7.0], + [3.0, 9.0]], + [[3.5, 11.0], + [4.0, 13.0], + [4.5, 15.0], + [5.0, 17.0]], + [[5.5, 19.0], + [6.0, 21.0], + [6.5, 23.0], + [7.0, 25.0]]]]]) + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.cesm2.Fix.get_fixed_filepath', + autospec=True) +def test_cl_fix_file(mock_get_filepath, cl_file, tmp_path): + """Test ``fix_file`` for ``cl``.""" + cubes = iris.load(cl_file) + + # Raw cubes + assert len(cubes) == 5 + var_names = [cube.var_name for cube in cubes] + assert 'cl' in var_names + assert 'a' in var_names + assert 'b' in var_names + assert 'p0' in var_names + assert 'ps' in var_names + + # Raw cl cube + cl_cube = cubes.extract_strict('cloud_area_fraction_in_atmosphere_layer') + assert not cl_cube.coords('air_pressure') + + # Apply fix + mock_get_filepath.return_value = os.path.join(tmp_path, + 'fixed_cesm2_cl.nc') + fix = Cl(None) + fixed_file = fix.fix_file(cl_file, tmp_path) + mock_get_filepath.assert_called_once_with(tmp_path, cl_file) + fixed_cubes = iris.load(fixed_file) + assert len(fixed_cubes) == 2 + var_names = [cube.var_name for cube in fixed_cubes] + assert 'cl' in var_names + assert 'ps' in var_names + fixed_cl_cube = fixed_cubes.extract_strict( + 'cloud_area_fraction_in_atmosphere_layer') + fixed_air_pressure_coord = fixed_cl_cube.coord('air_pressure') + assert fixed_air_pressure_coord.points is not None + assert fixed_air_pressure_coord.bounds is not None + np.testing.assert_allclose(fixed_air_pressure_coord.points, + AIR_PRESSURE_POINTS) + np.testing.assert_allclose(fixed_air_pressure_coord.bounds, + AIR_PRESSURE_BOUNDS) + + @pytest.fixture def tas_cubes(): + """Cubes to test fixes for ``tas``.""" ta_cube = iris.cube.Cube([1.0], var_name='ta') tas_cube = iris.cube.Cube([3.0], var_name='tas') return iris.cube.CubeList([ta_cube, tas_cube]) def test_get_tas_fix(): + """Test getting of fix.""" fix = Fix.get_fixes('CMIP6', 'CESM2', 'Amon', 'tas') assert fix == [Tas(None)] def test_tas_fix_metadata(tas_cubes): + """Test ``fix_metadata`` for ``tas``.""" for cube in tas_cubes: with pytest.raises(iris.exceptions.CoordinateNotFoundError): cube.coord('height') diff --git a/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm.py b/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm.py index f5410a8393..b7a64f85ee 100644 --- a/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm.py +++ b/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm.py @@ -1,25 +1,100 @@ """Tests for the fixes of CESM2-WACCM.""" +import os +import unittest + import iris +import numpy as np import pytest from cf_units import Unit +from netCDF4 import Dataset -from esmvalcore.cmor._fixes.cmip6.cesm2_waccm import Tas +from esmvalcore.cmor._fixes.cmip6.cesm2_waccm import Cl, Tas from esmvalcore.cmor.fix import Fix +@pytest.fixture +def cl_file(tmp_path): + """Create netcdf file with similar issues as ``cl``.""" + nc_path = os.path.join(tmp_path, 'cesm2_waccm_cl.nc') + dataset = Dataset(nc_path, mode='w') + dataset.createDimension('lev', size=2) + dataset.createDimension('bnds', size=2) + + # Dimensional variables + dataset.createVariable('lev', np.float64, dimensions=('lev',)) + dataset.createVariable('lev_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.variables['lev'][:] = [1.0, 2.0] + dataset.variables['lev'].bounds = 'lev_bnds' + dataset.variables['lev'].units = '1' + dataset.variables['lev_bnds'][:] = [[0.5, 1.5], [1.5, 3.0]] + dataset.variables['lev_bnds'].standard_name = ( + 'atmosphere_hybrid_sigma_pressure_coordinate') + dataset.variables['lev_bnds'].units = '1' + dataset.variables['lev_bnds'].formula_terms = ( + 'p0: p0 a: a_bnds b: b_bnds ps: ps') + + # Coordinates for derivation of pressure coordinate + dataset.createVariable('a', np.float64, dimensions=('lev',)) + dataset.createVariable('a_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.createVariable('b', np.float64, dimensions=('lev',)) + dataset.createVariable('b_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.variables['a'][:] = [2.0, 1.0] + dataset.variables['a'].bounds = 'a_bnds' + dataset.variables['a_bnds'][:] = [[1.5, 3.0], [0.0, 1.5]] + dataset.variables['b'][:] = [1.0, 0.0] + dataset.variables['b'].bounds = 'b_bnds' + dataset.variables['b_bnds'][:] = [[0.5, 2.0], [-1.0, 0.5]] + + dataset.close() + return nc_path + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CESM2-WACCM', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.cesm2.Fix.get_fixed_filepath', + autospec=True) +def test_cl_fix_file(mock_get_filepath, cl_file, tmp_path): + """Test ``fix_file`` for ``cl``.""" + mock_get_filepath.return_value = os.path.join(tmp_path, + 'fixed_cesm2_waccm_cl.nc') + fix = Cl(None) + fixed_file = fix.fix_file(cl_file, tmp_path) + mock_get_filepath.assert_called_once_with(tmp_path, cl_file) + fixed_dataset = Dataset(fixed_file, mode='r') + assert fixed_dataset.variables['lev'].standard_name == ( + 'atmosphere_hybrid_sigma_pressure_coordinate') + assert fixed_dataset.variables['lev'].formula_terms == ( + 'p0: p0 a: a b: b ps: ps') + assert fixed_dataset.variables['lev'].units == '1' + np.testing.assert_allclose(fixed_dataset.variables['a'][:], [1.0, 2.0]) + np.testing.assert_allclose(fixed_dataset.variables['b'][:], [0.0, 1.0]) + np.testing.assert_allclose(fixed_dataset.variables['a_bnds'][:], + [[0.0, 1.5], [1.5, 3.0]]) + np.testing.assert_allclose(fixed_dataset.variables['b_bnds'][:], + [[-1.0, 0.5], [0.5, 2.0]]) + + @pytest.fixture def tas_cubes(): + """Cubes to test fixes for ``tas``.""" ta_cube = iris.cube.Cube([1.0], var_name='ta') tas_cube = iris.cube.Cube([3.0], var_name='tas') return iris.cube.CubeList([ta_cube, tas_cube]) def test_get_tas_fix(): + """Test getting of fix.""" fix = Fix.get_fixes('CMIP6', 'CESM2-WACCM', 'Amon', 'tas') assert fix == [Tas(None)] def test_tas_fix_metadata(tas_cubes): + """Test ``fix_metadata`` for ``tas``.""" for cube in tas_cubes: with pytest.raises(iris.exceptions.CoordinateNotFoundError): cube.coord('height') From dd93a52ef05f556b1db61cdb817ec5e70c32de7d Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Tue, 3 Mar 2020 12:31:48 +0100 Subject: [PATCH 4/9] Added remaining tests for fixes --- .../cmor/_fixes/cmip6/test_cnrm_cm6_1.py | 147 ++++++++++++++++++ .../cmor/_fixes/cmip6/test_cnrm_cm6_1_hr.py | 21 +++ .../cmor/_fixes/cmip6/test_cnrm_esm2_1.py | 21 +++ .../cmor/_fixes/cmip6/test_gfdl_cm4.py | 137 ++++++++++++++++ .../cmor/_fixes/cmip6/test_giss_e2_1_g.py | 21 +++ .../cmor/_fixes/cmip6/test_giss_e2_1_h.py | 21 +++ .../cmor/_fixes/cmip6/test_miroc6.py | 56 +++++++ .../cmor/_fixes/cmip6/test_miroc_es2l.py | 21 +++ .../cmor/_fixes/cmip6/test_mpi_esm1_2_hr.py | 21 +++ .../cmor/_fixes/cmip6/test_mri_esm2_0.py | 21 +++ .../cmor/_fixes/cmip6/test_nesm3.py | 21 +++ .../cmor/_fixes/cmip6/test_sam0_unicon.py | 21 +++ .../cmor/_fixes/cmip6/test_ukesm1_0_ll.py | 4 + 13 files changed, 533 insertions(+) create mode 100644 tests/integration/cmor/_fixes/cmip6/test_cnrm_cm6_1.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_cnrm_cm6_1_hr.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_cnrm_esm2_1.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_gfdl_cm4.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_giss_e2_1_g.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_giss_e2_1_h.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_miroc6.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_miroc_es2l.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_mpi_esm1_2_hr.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_mri_esm2_0.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_nesm3.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_sam0_unicon.py diff --git a/tests/integration/cmor/_fixes/cmip6/test_cnrm_cm6_1.py b/tests/integration/cmor/_fixes/cmip6/test_cnrm_cm6_1.py new file mode 100644 index 0000000000..29e8080498 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_cnrm_cm6_1.py @@ -0,0 +1,147 @@ +"""Tests for the fixes of CNRM-CM6-1.""" +import os + +import iris +import numpy as np +import pytest +from netCDF4 import Dataset + +from esmvalcore.cmor._fixes.cmip6.cnrm_cm6_1 import Cl +from esmvalcore.cmor.fix import Fix + + +@pytest.fixture +def cl_file(tmp_path): + """Create netcdf file with similar issues as ``cl``.""" + nc_path = os.path.join(tmp_path, 'cnrm_cm6_1_cl.nc') + dataset = Dataset(nc_path, mode='w') + dataset.createDimension('time', size=1) + dataset.createDimension('lev', size=3) + dataset.createDimension('lat', size=2) + dataset.createDimension('lon', size=2) + dataset.createDimension('bnds', size=2) + + # Dimensional variables + dataset.createVariable('time', np.float64, dimensions=('time',)) + dataset.createVariable('lev', np.float64, dimensions=('lev',)) + dataset.createVariable('lev_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.createVariable('lat', np.float64, dimensions=('lat',)) + dataset.createVariable('lon', np.float64, dimensions=('lon',)) + dataset.variables['time'][:] = [0.0] + dataset.variables['time'].standard_name = 'time' + dataset.variables['time'].units = 'days since 6543-2-1' + dataset.variables['lev'][:] = [1.0, 2.0, 4.0] + dataset.variables['lev'].standard_name = ( + 'atmosphere_hybrid_sigma_pressure_coordinate') + dataset.variables['lev'].bounds = 'lev_bnds' + dataset.variables['lev'].units = '1' + dataset.variables['lev'].formula_term = ( + 'ap: ap b: b ps: ps') # Error in attribute intended + dataset.variables['lev_bnds'][:] = [[0.5, 1.5], [1.5, 3.0], [3.0, 5.0]] + dataset.variables['lev_bnds'].standard_name = ( + 'atmosphere_hybrid_sigma_pressure_coordinate') + dataset.variables['lev_bnds'].units = '1' + dataset.variables['lev_bnds'].formula_term = ( + 'ap: ap b: b ps: ps') # Error in attribute intended + dataset.variables['lat'][:] = [-30.0, 0.0] + dataset.variables['lat'].standard_name = 'latitude' + dataset.variables['lat'].units = 'degrees_north' + dataset.variables['lon'][:] = [30.0, 60.0] + dataset.variables['lon'].standard_name = 'longitude' + dataset.variables['lon'].units = 'degrees_east' + + # Coordinates for derivation of pressure coordinate + # Wrong shape of bounds is intended + dataset.createVariable('ap', np.float64, dimensions=('lev',)) + dataset.createVariable('ap_bnds', np.float64, dimensions=('bnds', 'lev')) + dataset.createVariable('b', np.float64, dimensions=('lev',)) + dataset.createVariable('b_bnds', np.float64, dimensions=('bnds', 'lev')) + dataset.createVariable('ps', np.float64, + dimensions=('time', 'lat', 'lon')) + dataset.variables['ap'][:] = [1.0, 2.0, 5.0] + dataset.variables['ap_bnds'][:] = [[0.0, 1.5, 1.5], [3.0, 3.0, 6.0]] + dataset.variables['b'][:] = [0.0, 1.0, 3.0] + dataset.variables['b_bnds'][:] = [[-1.0, 0.5, 0.5], [2.0, 2.0, 5.0]] + dataset.variables['ps'][:] = np.arange(1 * 2 * 2).reshape(1, 2, 2) + dataset.variables['ps'].standard_name = 'surface_air_pressure' + dataset.variables['ps'].units = 'Pa' + + # Cl variable + dataset.createVariable('cl', np.float32, + dimensions=('time', 'lev', 'lat', 'lon')) + dataset.variables['cl'][:] = np.full((1, 3, 2, 2), 0.0, dtype=np.float32) + dataset.variables['cl'].standard_name = ( + 'cloud_area_fraction_in_atmosphere_layer') + dataset.variables['cl'].units = '%' + + dataset.close() + return nc_path + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CNRM-CM6-1', 'Amon', 'cl') + assert fix == [Cl(None)] + + +AIR_PRESSURE_POINTS = np.array([[[[1.0, 1.0], + [1.0, 1.0]], + [[2.0, 3.0], + [4.0, 5.0]], + [[5.0, 8.0], + [11.0, 14.0]]]]) +AIR_PRESSURE_BOUNDS = np.array([[[[[0.0, 1.5], + [-1.0, 2.0]], + [[-2.0, 2.5], + [-3.0, 3.0]]], + [[[1.5, 3.0], + [2.0, 5.0]], + [[2.5, 7.0], + [3.0, 9.0]]], + [[[3.0, 6.0], + [5.0, 11.0]], + [[7.0, 16.0], + [9.0, 21.0]]]]]) + + +def test_cl_fix_metadata(cl_file): + """Test ``fix_metadata`` for ``cl``.""" + cubes = iris.load(cl_file) + + # Raw cubes + assert len(cubes) == 6 + var_names = [cube.var_name for cube in cubes] + assert 'cl' in var_names + assert 'ap' in var_names + assert 'ap_bnds' in var_names + assert 'b' in var_names + assert 'b_bnds' in var_names + assert 'ps' in var_names + + # Raw cl cube + cl_cube = cubes.extract_strict('cloud_area_fraction_in_atmosphere_layer') + assert not cl_cube.coords('air_pressure') + + # Apply fix + fix = Cl(None) + fixed_cubes = fix.fix_metadata(cubes) + assert len(fixed_cubes) == 1 + fixed_cl_cube = fixed_cubes.extract_strict( + 'cloud_area_fraction_in_atmosphere_layer') + fixed_air_pressure_coord = fixed_cl_cube.coord('air_pressure') + assert fixed_air_pressure_coord.points is not None + assert fixed_air_pressure_coord.bounds is not None + assert fixed_air_pressure_coord.points.shape == (1, 3, 2, 2) + assert fixed_air_pressure_coord.bounds.shape == (1, 3, 2, 2, 2) + np.testing.assert_allclose(fixed_air_pressure_coord.points, + AIR_PRESSURE_POINTS) + np.testing.assert_allclose(fixed_air_pressure_coord.bounds, + AIR_PRESSURE_BOUNDS) + lat_coord = fixed_cl_cube.coord('latitude') + lon_coord = fixed_cl_cube.coord('longitude') + assert lat_coord.bounds is not None + assert lon_coord.bounds is not None + np.testing.assert_allclose(lat_coord.bounds, + [[-45.0, -15.0], [-15.0, 15.0]]) + np.testing.assert_allclose(lon_coord.bounds, + [[15.0, 45.0], [45.0, 75.0]]) diff --git a/tests/integration/cmor/_fixes/cmip6/test_cnrm_cm6_1_hr.py b/tests/integration/cmor/_fixes/cmip6/test_cnrm_cm6_1_hr.py new file mode 100644 index 0000000000..8e7f0560ee --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_cnrm_cm6_1_hr.py @@ -0,0 +1,21 @@ +"""Test fixes for CNRM-CM6-1-HR.""" +import unittest + +from esmvalcore.cmor._fixes.cmip6.cnrm_cm6_1_hr import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CNRM-CM6-1-HR', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.cnrm_cm6_1_hr.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip6/test_cnrm_esm2_1.py b/tests/integration/cmor/_fixes/cmip6/test_cnrm_esm2_1.py new file mode 100644 index 0000000000..d0d57b3d1b --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_cnrm_esm2_1.py @@ -0,0 +1,21 @@ +"""Test fixes for CNRM-ESM2-1.""" +import unittest + +from esmvalcore.cmor._fixes.cmip6.cnrm_esm2_1 import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CNRM-ESM2-1', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.cnrm_esm2_1.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip6/test_gfdl_cm4.py b/tests/integration/cmor/_fixes/cmip6/test_gfdl_cm4.py new file mode 100644 index 0000000000..3667e1d61e --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_gfdl_cm4.py @@ -0,0 +1,137 @@ +"""Tests for the fixes of GFDL-CM4.""" +import os + +import iris +import numpy as np +import pytest +from netCDF4 import Dataset + +from esmvalcore.cmor._fixes.cmip6.gfdl_cm4 import Cl +from esmvalcore.cmor.fix import Fix + + +@pytest.fixture +def cl_file(tmp_path): + """Create netcdf file with similar issues as ``cl``.""" + nc_path = os.path.join(tmp_path, 'gfdl_cm4_cl.nc') + dataset = Dataset(nc_path, mode='w') + dataset.createDimension('time', size=1) + dataset.createDimension('lev', size=3) + dataset.createDimension('lat', size=2) + dataset.createDimension('lon', size=2) + dataset.createDimension('bnds', size=2) + + # Dimensional variables + dataset.createVariable('time', np.float64, dimensions=('time',)) + dataset.createVariable('lev', np.float64, dimensions=('lev',)) + dataset.createVariable('lev_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.createVariable('lat', np.float64, dimensions=('lat',)) + dataset.createVariable('lon', np.float64, dimensions=('lon',)) + dataset.variables['time'][:] = [0.0] + dataset.variables['time'].standard_name = 'time' + dataset.variables['time'].units = 'days since 6543-2-1' + dataset.variables['lev'][:] = [1.0, 2.0, 4.0] + dataset.variables['lev'].standard_name = ( + 'atmosphere_hybrid_sigma_pressure_coordinate') + dataset.variables['lev'].bounds = 'lev_bnds' + dataset.variables['lev'].units = '1' + dataset.variables['lev'].formula_term = ( + 'ap: ap b: b ps: ps') # Error in attribute intended + dataset.variables['lev_bnds'][:] = [[0.5, 1.5], [1.5, 3.0], [3.0, 5.0]] + dataset.variables['lev_bnds'].standard_name = ( + 'atmosphere_hybrid_sigma_pressure_coordinate') + dataset.variables['lev_bnds'].units = '1' + dataset.variables['lev_bnds'].formula_term = ( + 'ap: ap_bnds b: b_bnds ps: ps') # Error in attribute intended + dataset.variables['lat'][:] = [-30.0, 0.0] + dataset.variables['lat'].standard_name = 'latitude' + dataset.variables['lat'].units = 'degrees_north' + dataset.variables['lon'][:] = [30.0, 60.0] + dataset.variables['lon'].standard_name = 'longitude' + dataset.variables['lon'].units = 'degrees_east' + + # Coordinates for derivation of pressure coordinate + dataset.createVariable('ap', np.float64, dimensions=('lev',)) + dataset.createVariable('ap_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.createVariable('b', np.float64, dimensions=('lev',)) + dataset.createVariable('b_bnds', np.float64, dimensions=('lev', 'bnds')) + dataset.createVariable('ps', np.float64, + dimensions=('time', 'lat', 'lon')) + dataset.variables['ap'][:] = [1.0, 2.0, 5.0] + dataset.variables['ap'].units = 'Pa' + dataset.variables['ap_bnds'][:] = [[0.0, 1.5], [1.5, 3.0], [3.0, 6.0]] + dataset.variables['b'][:] = [0.0, 1.0, 3.0] + dataset.variables['b_bnds'][:] = [[-1.0, 0.5], [0.5, 2.0], [2.0, 5.0]] + dataset.variables['ps'][:] = np.arange(1 * 2 * 2).reshape(1, 2, 2) + dataset.variables['ps'].standard_name = 'surface_air_pressure' + dataset.variables['ps'].units = 'Pa' + + # Cl variable + dataset.createVariable('cl', np.float32, + dimensions=('time', 'lev', 'lat', 'lon')) + dataset.variables['cl'][:] = np.full((1, 3, 2, 2), 0.0, dtype=np.float32) + dataset.variables['cl'].standard_name = ( + 'cloud_area_fraction_in_atmosphere_layer') + dataset.variables['cl'].units = '%' + + dataset.close() + return nc_path + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'GFDL-CM4', 'Amon', 'cl') + assert fix == [Cl(None)] + + +AIR_PRESSURE_POINTS = np.array([[[[1.0, 1.0], + [1.0, 1.0]], + [[2.0, 3.0], + [4.0, 5.0]], + [[5.0, 8.0], + [11.0, 14.0]]]]) +AIR_PRESSURE_BOUNDS = np.array([[[[[0.0, 1.5], + [-1.0, 2.0]], + [[-2.0, 2.5], + [-3.0, 3.0]]], + [[[1.5, 3.0], + [2.0, 5.0]], + [[2.5, 7.0], + [3.0, 9.0]]], + [[[3.0, 6.0], + [5.0, 11.0]], + [[7.0, 16.0], + [9.0, 21.0]]]]]) + + +def test_cl_fix_metadata(cl_file): + """Test ``fix_metadata`` for ``cl``.""" + cubes = iris.load(cl_file) + + # Raw cubes + assert len(cubes) == 6 + var_names = [cube.var_name for cube in cubes] + assert 'cl' in var_names + assert 'ap' in var_names + assert 'ap_bnds' in var_names + assert 'b' in var_names + assert 'b_bnds' in var_names + assert 'ps' in var_names + + # Raw cl cube + cl_cube = cubes.extract_strict('cloud_area_fraction_in_atmosphere_layer') + assert not cl_cube.coords('air_pressure') + + # Apply fix + fix = Cl(None) + fixed_cubes = fix.fix_metadata(cubes) + assert len(fixed_cubes) == 1 + fixed_cl_cube = fixed_cubes.extract_strict( + 'cloud_area_fraction_in_atmosphere_layer') + fixed_air_pressure_coord = fixed_cl_cube.coord('air_pressure') + assert fixed_air_pressure_coord.points is not None + assert fixed_air_pressure_coord.bounds is not None + np.testing.assert_allclose(fixed_air_pressure_coord.points, + AIR_PRESSURE_POINTS) + np.testing.assert_allclose(fixed_air_pressure_coord.bounds, + AIR_PRESSURE_BOUNDS) diff --git a/tests/integration/cmor/_fixes/cmip6/test_giss_e2_1_g.py b/tests/integration/cmor/_fixes/cmip6/test_giss_e2_1_g.py new file mode 100644 index 0000000000..733c97513a --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_giss_e2_1_g.py @@ -0,0 +1,21 @@ +"""Test fixes for GISS-E2-1-G.""" +import unittest + +from esmvalcore.cmor._fixes.cmip6.giss_e2_1_g import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'GISS-E2-1-G', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.giss_e2_1_g.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip6/test_giss_e2_1_h.py b/tests/integration/cmor/_fixes/cmip6/test_giss_e2_1_h.py new file mode 100644 index 0000000000..cf161d4bb2 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_giss_e2_1_h.py @@ -0,0 +1,21 @@ +"""Test fixes for GISS-E2-1-H.""" +import unittest + +from esmvalcore.cmor._fixes.cmip6.giss_e2_1_h import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'GISS-E2-1-H', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.giss_e2_1_h.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip6/test_miroc6.py b/tests/integration/cmor/_fixes/cmip6/test_miroc6.py new file mode 100644 index 0000000000..fd945cbccf --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_miroc6.py @@ -0,0 +1,56 @@ +"""Test fixes for MIROC6.""" +import unittest + +import pytest +from iris.coords import AuxCoord +from iris.cube import Cube, CubeList + +from esmvalcore.cmor._fixes.cmip6.miroc6 import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +@pytest.fixture +def cl_cubes(): + """Cubes for ``cl.``.""" + ps_coord = AuxCoord( + [[1.0]], + var_name='ps', + long_name='Surface Air Pressure', + attributes={'a': 1, 'b': '2'}, + ) + cl_cube = Cube( + [[0.0]], + var_name='cl', + standard_name='cloud_area_fraction_in_atmosphere_layer', + aux_coords_and_dims=[(ps_coord.copy(), (0, 1))], + ) + x_cube = Cube([[0.0]], + long_name='x', + aux_coords_and_dims=[(ps_coord.copy(), (0, 1))]) + cubes = CubeList([cl_cube, x_cube]) + return cubes + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'MIROC6', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.miroc6.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata, cl_cubes): + """Test ``fix_metadata`` for ``cl``.""" + mock_base_fix_metadata.return_value = cl_cubes + fix = Cl(None) + fixed_cubes = fix.fix_metadata(cl_cubes) + mock_base_fix_metadata.assert_called_once_with(fix, cl_cubes) + assert len(fixed_cubes) == 2 + cl_cube = fixed_cubes.extract_strict( + 'cloud_area_fraction_in_atmosphere_layer') + ps_coord_cl = cl_cube.coord('Surface Air Pressure') + assert not ps_coord_cl.attributes + x_cube = fixed_cubes.extract_strict('x') + ps_coord_x = x_cube.coord('Surface Air Pressure') + assert ps_coord_x.attributes == {'a': 1, 'b': '2'} diff --git a/tests/integration/cmor/_fixes/cmip6/test_miroc_es2l.py b/tests/integration/cmor/_fixes/cmip6/test_miroc_es2l.py new file mode 100644 index 0000000000..044faec07f --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_miroc_es2l.py @@ -0,0 +1,21 @@ +"""Test fixes for MIROC-ES2L.""" +import unittest + +from esmvalcore.cmor._fixes.cmip6.miroc_es2l import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'MIROC-ES2L', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.miroc_es2l.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip6/test_mpi_esm1_2_hr.py b/tests/integration/cmor/_fixes/cmip6/test_mpi_esm1_2_hr.py new file mode 100644 index 0000000000..2f6f2c6237 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_mpi_esm1_2_hr.py @@ -0,0 +1,21 @@ +"""Test fixes for MPI-ESM1-2-HR.""" +import unittest + +from esmvalcore.cmor._fixes.cmip6.mpi_esm1_2_hr import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'MPI-ESM1-2-HR', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.mpi_esm1_2_hr.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip6/test_mri_esm2_0.py b/tests/integration/cmor/_fixes/cmip6/test_mri_esm2_0.py new file mode 100644 index 0000000000..362e647940 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_mri_esm2_0.py @@ -0,0 +1,21 @@ +"""Test fixes for MRI-ESM2-0.""" +import unittest + +from esmvalcore.cmor._fixes.cmip6.mri_esm2_0 import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'MRI-ESM2-0', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.mri_esm2_0.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip6/test_nesm3.py b/tests/integration/cmor/_fixes/cmip6/test_nesm3.py new file mode 100644 index 0000000000..36e3c1b3d9 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_nesm3.py @@ -0,0 +1,21 @@ +"""Test fixes for NESM3.""" +import unittest + +from esmvalcore.cmor._fixes.cmip6.nesm3 import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'NESM3', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.nesm3.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip6/test_sam0_unicon.py b/tests/integration/cmor/_fixes/cmip6/test_sam0_unicon.py new file mode 100644 index 0000000000..fe296c221f --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_sam0_unicon.py @@ -0,0 +1,21 @@ +"""Test fixes for SAM0-UNICON.""" +import unittest + +from esmvalcore.cmor._fixes.cmip6.sam0_unicon import Cl +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'SAM0-UNICON', 'Amon', 'cl') + assert fix == [Cl(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.sam0_unicon.BaseCl.fix_metadata', + autospec=True) +def test_cl_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``cl``.""" + fix = Cl(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip6/test_ukesm1_0_ll.py b/tests/integration/cmor/_fixes/cmip6/test_ukesm1_0_ll.py index 1902dbf53c..796cc6225c 100644 --- a/tests/integration/cmor/_fixes/cmip6/test_ukesm1_0_ll.py +++ b/tests/integration/cmor/_fixes/cmip6/test_ukesm1_0_ll.py @@ -12,17 +12,20 @@ @pytest.fixture def sample_cubes(): + """Sample cubes.""" ta_cube = iris.cube.Cube([1.0], var_name='ta') tas_cube = iris.cube.Cube([3.0], var_name='tas') return iris.cube.CubeList([ta_cube, tas_cube]) def test_get_tas_fix(): + """Test getting of fix.""" fix = Fix.get_fixes('CMIP6', 'UKESM1-0-LL', 'Amon', 'tas') assert fix == [AllVars(None)] def test_allvars_fix_metadata(sample_cubes): + """Test ``fix_metadata`` for all variables.""" for cube in sample_cubes: cube.attributes['parent_time_units'] = 'days since 1850-01-01' out_cubes = AllVars(None).fix_metadata(sample_cubes) @@ -32,6 +35,7 @@ def test_allvars_fix_metadata(sample_cubes): def test_allvars_no_need_tofix_metadata(sample_cubes): + """Test ``fix_metadata`` for all variables.""" for cube in sample_cubes: cube.attributes['parent_time_units'] = 'days since 1850-01-01' out_cubes = AllVars(None).fix_metadata(sample_cubes) From 9c0abdf95c523ae81a7880476acd605e7331d581 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Tue, 3 Mar 2020 14:30:54 +0100 Subject: [PATCH 5/9] Added tests for shared fix functions --- tests/integration/cmor/_fixes/test_shared.py | 237 +++++++++++++++++-- 1 file changed, 216 insertions(+), 21 deletions(-) diff --git a/tests/integration/cmor/_fixes/test_shared.py b/tests/integration/cmor/_fixes/test_shared.py index b6d59b6a23..22a098d85d 100644 --- a/tests/integration/cmor/_fixes/test_shared.py +++ b/tests/integration/cmor/_fixes/test_shared.py @@ -1,15 +1,133 @@ """Tests for shared functions for fixes.""" -import numpy as np import iris +import numpy as np import pytest from cf_units import Unit -from esmvalcore.cmor._fixes.shared import (add_scalar_depth_coord, +from esmvalcore.cmor._fixes.shared import (ALTITUDE_TO_PRESSURE, + _get_altitude_to_pressure_func, + add_aux_coords_from_cubes, + add_pressure_level_coordinate, + add_scalar_depth_coord, add_scalar_height_coord, add_scalar_typeland_coord, add_scalar_typesea_coord, - round_coordinates, - cube_to_aux_coord) + cube_to_aux_coord, fix_bounds, + get_bounds_cube, round_coordinates) +from esmvalcore.preprocessor._derive._shared import var_name_constraint + + +@pytest.mark.parametrize('func', [ALTITUDE_TO_PRESSURE, + _get_altitude_to_pressure_func()]) +def test_altitude_to_pressure_func(func): + """Test altitude to pressure function.""" + assert callable(func) + np.testing.assert_allclose(func(-6000.0), 196968.01058487315) + np.testing.assert_allclose(func(-5000.0), 177687.0) + np.testing.assert_allclose(func(0.0), 101325.0) + np.testing.assert_allclose(func(50.0), 100725.54298598564) + np.testing.assert_allclose(func(80000.0), 0.88628) + np.testing.assert_allclose(func(90000.0), 0.1576523580997673) + np.testing.assert_allclose(func(np.array([0.0, 100.0])), + [101325.0, 100129.0]) + + +TEST_ADD_AUX_COORDS_FROM_CUBES = [ + ({}, 1), + ({'x': ()}, 0), + ({'x': 1, 'a': ()}, 0), + ({'a': ()}, 1), + ({'a': (), 'b': 1}, 1), + ({'a': (), 'b': 1}, 1), + ({'c': 1}, 2), + ({'a': (), 'b': 1, 'c': 1}, 2), + ({'d': (0, 1)}, 1), + ({'a': (), 'b': 1, 'd': (0, 1)}, 1), +] + + +@pytest.mark.parametrize('coord_dict,output', TEST_ADD_AUX_COORDS_FROM_CUBES) +def test_add_aux_coords_from_cubes(coord_dict, output): + """Test extraction of auxiliary coordinates from cubes.""" + cube = iris.cube.Cube([[0.0]]) + cubes = iris.cube.CubeList([ + iris.cube.Cube(0.0, var_name='a'), + iris.cube.Cube([0.0], var_name='b'), + iris.cube.Cube([0.0], var_name='c'), + iris.cube.Cube([0.0], var_name='c'), + iris.cube.Cube([[0.0]], var_name='d'), + ]) + if output == 1: + add_aux_coords_from_cubes(cube, cubes, coord_dict) + for (coord_name, coord_dims) in coord_dict.items(): + coord = cube.coord(var_name=coord_name) + if len(cube.coord_dims(coord)) == 1: + assert cube.coord_dims(coord)[0] == coord_dims + else: + assert cube.coord_dims(coord) == coord_dims + points = np.full(coord.shape, 0.0) + assert coord.points == points + assert not cubes.extract(var_name_constraint(coord_name)) + assert len(cubes) == 5 - len(coord_dict) + return + with pytest.raises(ValueError) as err: + add_aux_coords_from_cubes(cube, cubes, coord_dict) + if output == 0: + assert "Expected exactly one coordinate cube 'x'" in str(err.value) + assert "got 0" in str(err.value) + else: + assert "Expected exactly one coordinate cube 'c'" in str(err.value) + assert "got 2" in str(err.value) + + +ALT_COORD = iris.coords.AuxCoord([0.0], bounds=[[-100.0, 500.0]], + standard_name='altitude', units='m') +ALT_COORD_KM = iris.coords.AuxCoord([0.0], bounds=[[-0.1, 0.5]], + standard_name='altitude', units='km') +P_COORD = iris.coords.AuxCoord([101325.0], bounds=[[102532.0, 95460.8]], + var_name='plev', standard_name='air_pressure', + long_name='pressure', units='Pa') +PS_COORD = iris.coords.AuxCoord([[[101000.0]]], var_name='ps', units='Pa') +PTOP_COORD = iris.coords.AuxCoord(1000.0, var_name='ptop', units='Pa') +LEV_COORD = iris.coords.AuxCoord([0.5], bounds=[[0.2, 0.8]], var_name='lev', + units='1', + standard_name='atmosphere_sigma_coordinate') +P_COORD_HYBRID = iris.coords.AuxCoord([[[[51000.0]]]], + bounds=[[[[[21000.0, 81000.0]]]]], + standard_name='air_pressure', units='Pa') +CUBE_ALT = iris.cube.Cube([1.0], var_name='x', + aux_coords_and_dims=[(ALT_COORD, 0)]) +CUBE_ALT_KM = iris.cube.Cube([1.0], var_name='x', + aux_coords_and_dims=[(ALT_COORD_KM, 0)]) +CUBE_HYBRID = iris.cube.Cube([[[[1.0]]]], var_name='x', + aux_coords_and_dims=[(PS_COORD, (0, 2, 3)), + (PTOP_COORD, ()), + (LEV_COORD, 1)]) + + +TEST_ADD_PRESSURE_LEVEL_COORDINATE = [ + (CUBE_ALT.copy(), P_COORD.copy()), + (CUBE_ALT_KM.copy(), P_COORD.copy()), + (CUBE_HYBRID.copy(), P_COORD_HYBRID.copy()), + (iris.cube.Cube(0.0), None), +] + + +@pytest.mark.parametrize('cube,output', TEST_ADD_PRESSURE_LEVEL_COORDINATE) +def test_add_pressure_level_coordinate(cube, output): + """Test adding of pressure level coordinate.""" + if output is None: + with pytest.raises(ValueError) as err: + add_pressure_level_coordinate(cube) + msg = ("Cannot add 'air_pressure' coordinate, 'altitude' or " + "'atmosphere_sigma_coordinate' not available") + assert str(err.value) == msg + return + assert not cube.coords('air_pressure') + add_pressure_level_coordinate(cube) + air_pressure_coord = cube.coord('air_pressure') + assert air_pressure_coord == output + DIM_COORD = iris.coords.DimCoord([3.141592], bounds=[[1.23, 4.567891011]], @@ -26,6 +144,7 @@ @pytest.mark.parametrize('cube_in,depth', TEST_ADD_SCALAR_COORD) def test_add_scalar_depth_coord(cube_in, depth): + """Test adding of scalar depth coordinate.""" cube_in = cube_in.copy() if depth is None: depth = 0.0 @@ -52,6 +171,7 @@ def test_add_scalar_depth_coord(cube_in, depth): @pytest.mark.parametrize('cube_in,height', TEST_ADD_SCALAR_COORD) def test_add_scalar_height_coord(cube_in, height): + """Test adding of scalar height coordinate.""" cube_in = cube_in.copy() if height is None: height = 2.0 @@ -78,6 +198,7 @@ def test_add_scalar_height_coord(cube_in, height): @pytest.mark.parametrize('cube_in,typeland', TEST_ADD_SCALAR_COORD) def test_add_scalar_typeland_coord(cube_in, typeland): + """Test adding of scalar typeland coordinate.""" cube_in = cube_in.copy() if typeland is None: typeland = 'default' @@ -103,6 +224,7 @@ def test_add_scalar_typeland_coord(cube_in, typeland): @pytest.mark.parametrize('cube_in,typesea', TEST_ADD_SCALAR_COORD) def test_add_scalar_typesea_coord(cube_in, typesea): + """Test adding of scalar typesea coordinate.""" cube_in = cube_in.copy() if typesea is None: typesea = 'default' @@ -126,6 +248,94 @@ def test_add_scalar_typesea_coord(cube_in, typesea): assert coord == typesea_coord +def test_cube_to_aux_coord(): + """Test converting cube to auxiliary coordinate.""" + cube = iris.cube.Cube( + np.ones((2, 2)), + standard_name='longitude', + long_name='longitude', + var_name='lon', + units='degrees_north', + ) + coord = cube_to_aux_coord(cube) + assert coord.var_name == cube.var_name + assert coord.standard_name == cube.standard_name + assert coord.long_name == cube.long_name + assert coord.units == cube.units + assert np.all(coord.points == cube.data) + + +TEST_GET_BOUNDS_CUBE = [ + ('x', None), + ('a', iris.cube.Cube(0.0, var_name='a_bnds')), + ('b', iris.cube.Cube([0.0], var_name='b_bounds')), + ('c', False), + ('d', iris.cube.Cube([[0.0]], var_name='d_bnds')), + ('e', False), +] + + +@pytest.mark.parametrize('coord_name,output', TEST_GET_BOUNDS_CUBE) +def test_get_bounds_cube(coord_name, output): + """Test retrieving of bounds cube from list of cubes.""" + cubes = iris.cube.CubeList([ + iris.cube.Cube(0.0, var_name='a_bnds'), + iris.cube.Cube([0.0], var_name='b_bounds'), + iris.cube.Cube([0.0], var_name='c_bnds'), + iris.cube.Cube([0.0], var_name='c_bnds'), + iris.cube.Cube([[0.0]], var_name='d_bnds'), + iris.cube.Cube([[0.0]], var_name='d_bounds'), + iris.cube.Cube([[0.0]], var_name='e_bounds'), + iris.cube.Cube([[0.0]], var_name='e_bounds'), + ]) + if output is None: + with pytest.raises(ValueError) as err: + get_bounds_cube(cubes, coord_name) + msg = "No bounds for coordinate variable 'x' available in" + assert msg in str(err.value) + return + if not isinstance(output, iris.cube.Cube): + with pytest.raises(ValueError) as err: + get_bounds_cube(cubes, coord_name) + msg = f"Multiple cubes with var_name '{coord_name}" + assert msg in str(err.value) + return + bounds_cube = get_bounds_cube(cubes, coord_name) + assert bounds_cube == output + + +TEST_FIX_BOUNDS = [ + ([], [None, [[-3.0, 4.0]]]), + (['a'], [[[1.0, 2.0]], [[-3.0, 4.0]]]), + (['b'], [None, [[-3.0, 4.0]]]), + (['a', 'b'], [[[1.0, 2.0]], [[-3.0, 4.0]]]), +] + + +@pytest.mark.parametrize('var_names,output', TEST_FIX_BOUNDS) +def test_fix_bounds(var_names, output): + """Test retrieving of bounds cube from list of cubes.""" + a_coord = iris.coords.AuxCoord(1.5, var_name='a') + b_coord = iris.coords.AuxCoord(1.5, bounds=[-3.0, 4.0], var_name='b') + cube = iris.cube.Cube( + 0.0, + aux_coords_and_dims=[(a_coord, ()), (b_coord, ())], + var_name='x', + ) + cubes = iris.cube.CubeList([ + iris.cube.Cube([1.0, 2.0], var_name='a_bnds'), + iris.cube.Cube([1.0, 2.0], var_name='b_bounds'), + iris.cube.Cube([1000.0, 2000.0], var_name='c_bounds'), + ]) + assert cube.coord(var_name='a').bounds is None + fix_bounds(cube, cubes, var_names) + if output[0] is None: + assert cube.coord(var_name='a').bounds is None + else: + np.testing.assert_allclose(cube.coord(var_name='a').bounds, output[0]) + np.testing.assert_allclose(cube.coord(var_name='b').bounds, output[1]) + + DIM_COORD_NB = iris.coords.DimCoord([3.1415], standard_name='latitude') CUBE_3 = iris.cube.Cube([5.0], dim_coords_and_dims=[(DIM_COORD_NB, 0)]) COORD_3_DEC = DIM_COORD.copy([3.142], [[1.23, 4.568]]) @@ -142,6 +352,7 @@ def test_add_scalar_typesea_coord(cube_in, typesea): @pytest.mark.parametrize('cubes_in,decimals,out', TEST_ROUND) def test_round_coordinate(cubes_in, decimals, out): + """Test rounding of coordinates.""" kwargs = {} if decimals is None else {'decimals': decimals} cubes_out = round_coordinates(cubes_in, **kwargs) assert cubes_out is cubes_in @@ -153,24 +364,8 @@ def test_round_coordinate(cubes_in, decimals, out): assert coords[0] == out[idx] -def test_cube_to_aux_coord(): - cube = iris.cube.Cube( - np.ones((2, 2)), - standard_name='longitude', - long_name='longitude', - var_name='lon', - units='degrees_north', - ) - coord = cube_to_aux_coord(cube) - assert coord.var_name == cube.var_name - assert coord.standard_name == cube.standard_name - assert coord.long_name == cube.long_name - assert coord.units == cube.units - assert np.all(coord.points == cube.data) - - def test_round_coordinates_single_coord(): - """Test rounding of specified coordinate""" + """Test rounding of specified coordinate.""" coords, bounds = [10.0001], [[9.0001, 11.0001]] latcoord = iris.coords.DimCoord(coords.copy(), bounds=bounds.copy(), standard_name='latitude') From f0643c1ad51b83793c839801c6068766173df35c Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Tue, 3 Mar 2020 16:25:04 +0100 Subject: [PATCH 6/9] Fixed flake8 test --- tests/integration/cmor/_fixes/cmip5/test_fgoals_g2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/cmor/_fixes/cmip5/test_fgoals_g2.py b/tests/integration/cmor/_fixes/cmip5/test_fgoals_g2.py index d016b152c1..087d9c99d3 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_fgoals_g2.py +++ b/tests/integration/cmor/_fixes/cmip5/test_fgoals_g2.py @@ -1,7 +1,6 @@ """Test FGOALS-g2 fixes.""" import unittest -import numpy as np from cf_units import Unit from iris.coords import DimCoord from iris.cube import Cube, CubeList From 6348cb3867ad6c7adf68fe4411bd1e9519ccdbf9 Mon Sep 17 00:00:00 2001 From: Axel Lauer Date: Thu, 5 Mar 2020 09:56:46 +0100 Subject: [PATCH 7/9] added clw, cli, clcalipso to cmip6 fixes --- esmvalcore/cmor/_fixes/cmip6/access_cm2.py | 14 +++++++++ esmvalcore/cmor/_fixes/cmip6/access_esm1_5.py | 14 +++++++++ esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py | 8 +++++ esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py | 8 +++++ esmvalcore/cmor/_fixes/cmip6/cams_csm1_0.py | 8 +++++ esmvalcore/cmor/_fixes/cmip6/cesm2.py | 8 +++++ esmvalcore/cmor/_fixes/cmip6/cesm2_fv2.py | 19 ++++++++++++ esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py | 8 +++++ esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1.py | 30 +++++++++++++++++++ esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1_hr.py | 8 +++++ esmvalcore/cmor/_fixes/cmip6/cnrm_esm2_1.py | 14 ++++++++- esmvalcore/cmor/_fixes/cmip6/fgoals_g3.py | 14 +++++++++ esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py | 8 +++++ esmvalcore/cmor/_fixes/cmip6/giss_e2_1_g.py | 8 +++++ .../cmor/_fixes/cmip6/hadgem3_gc31_ll.py | 13 ++++++++ esmvalcore/cmor/_fixes/cmip6/kace_1_0_g.py | 19 ++++++++++++ esmvalcore/cmor/_fixes/cmip6/miroc6.py | 8 +++++ esmvalcore/cmor/_fixes/cmip6/miroc_es2l.py | 8 +++++ esmvalcore/cmor/_fixes/cmip6/mpi_esm1_2_hr.py | 8 +++++ esmvalcore/cmor/_fixes/cmip6/mri_esm2_0.py | 8 +++++ esmvalcore/cmor/_fixes/cmip6/nesm3.py | 8 +++++ esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py | 8 +++++ esmvalcore/cmor/_fixes/cmip6/ukesm1_0_ll.py | 8 +++++ 23 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 esmvalcore/cmor/_fixes/cmip6/access_cm2.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/access_esm1_5.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/cesm2_fv2.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/fgoals_g3.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/kace_1_0_g.py diff --git a/esmvalcore/cmor/_fixes/cmip6/access_cm2.py b/esmvalcore/cmor/_fixes/cmip6/access_cm2.py new file mode 100644 index 0000000000..4faeb04b4e --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/access_cm2.py @@ -0,0 +1,14 @@ +"""Fixes for CMIP6 ACCESS-CM2.""" +from .ukesm1_0_ll import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" + + +class Clw(BaseCl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(BaseCl): + """Fixes for ``cli (same as for cl)``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/access_esm1_5.py b/esmvalcore/cmor/_fixes/cmip6/access_esm1_5.py new file mode 100644 index 0000000000..df38697983 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/access_esm1_5.py @@ -0,0 +1,14 @@ +"""Fixes for CMIP6 ACCESS-ESM1-5.""" +from .ukesm1_0_ll import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" + + +class Clw(BaseCl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(BaseCl): + """Fixes for ``cli (same as for cl)``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py b/esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py index 3f89888afa..c46a2920a2 100644 --- a/esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py +++ b/esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py @@ -7,6 +7,14 @@ class Cl(BaseCl): """Fixes for ``cl``.""" +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" + + class Tos(BaseTos): """Fixes for tos.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py b/esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py index 3af096a886..8b585be560 100644 --- a/esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py +++ b/esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py @@ -7,5 +7,13 @@ class Cl(BaseCl): """Fixes for ``cl``.""" +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" + + class Tos(BaseTos): """Fixes for tos.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/cams_csm1_0.py b/esmvalcore/cmor/_fixes/cmip6/cams_csm1_0.py index 532cdf312a..45924c040b 100644 --- a/esmvalcore/cmor/_fixes/cmip6/cams_csm1_0.py +++ b/esmvalcore/cmor/_fixes/cmip6/cams_csm1_0.py @@ -4,3 +4,11 @@ class Cl(BaseCl): """Fixes for ``cl``.""" + + +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/cesm2.py b/esmvalcore/cmor/_fixes/cmip6/cesm2.py index 62e8aae58d..14e186bd24 100644 --- a/esmvalcore/cmor/_fixes/cmip6/cesm2.py +++ b/esmvalcore/cmor/_fixes/cmip6/cesm2.py @@ -56,6 +56,14 @@ def fix_file(self, filepath, output_dir): return new_path +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" + + class Fgco2(Fix): """Fixes for fgco2.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/cesm2_fv2.py b/esmvalcore/cmor/_fixes/cmip6/cesm2_fv2.py new file mode 100644 index 0000000000..2494fe779a --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/cesm2_fv2.py @@ -0,0 +1,19 @@ +"""Fixes for CESM2-FV2 model.""" +from .cesm2 import Cl as BaseCl +from .cesm2 import Tas as BaseTas + + +class Cl(BaseCl): + """Fixes for cl.""" + + +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" + + +class Tas(BaseTas): + """Fixes for tas.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py b/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py index 08d6f5ec21..aafd6fe8e3 100644 --- a/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py +++ b/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py @@ -42,5 +42,13 @@ def fix_file(self, filepath, output_dir): return new_path +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" + + class Tas(BaseTas): """Fixes for tas.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1.py b/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1.py index 8fefc82d4d..aa1ecb4d5e 100644 --- a/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1.py +++ b/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1.py @@ -1,6 +1,7 @@ """Fixes for CNRM-CM6-1 model.""" import iris +from ..fix import Fix from ..cmip5.bcc_csm1_1 import Cl as BaseCl from ..shared import add_aux_coords_from_cubes, get_bounds_cube @@ -48,3 +49,32 @@ def fix_metadata(self, cubes): for coord_name in ('latitude', 'longitude'): cube.coord(coord_name).guess_bounds() return cubes + + +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" + + +class Clcalipso(Fix): + """Fixes for clcalipso.""" + + def fix_metadata(self, cubes): + """ + Corrects alt40 coordinate standard_name. + Parameters + ---------- + cubes : iris.cube.CubeList + Returns + ------- + iris.cube.CubeList + """ + cl_cube = self.get_cube_from_list(cubes) + + alt40 = cl_cube.coord('alt40') + alt40.standard_name = 'altitude' + + return iris.cube.CubeList([cl_cube]) diff --git a/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1_hr.py b/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1_hr.py index 73a0d8fafb..7cfc35bc1f 100644 --- a/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1_hr.py +++ b/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1_hr.py @@ -4,3 +4,11 @@ class Cl(BaseCl): """Fixes for ``cl``.""" + + +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/cnrm_esm2_1.py b/esmvalcore/cmor/_fixes/cmip6/cnrm_esm2_1.py index c11df2657f..95d5a78722 100644 --- a/esmvalcore/cmor/_fixes/cmip6/cnrm_esm2_1.py +++ b/esmvalcore/cmor/_fixes/cmip6/cnrm_esm2_1.py @@ -1,6 +1,18 @@ """Fixes for CNRM-ESM2-1 model.""" -from .cnrm_cm6_1 import Cl as BaseCl +from .cnrm_cm6_1 import Cl as BaseCl, Clcalipso as BaseClcalipso class Cl(BaseCl): """Fixes for ``cl``.""" + + +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" + + +class Clcalipso(BaseClcalipso): + """Fixes for ``clcalipso``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/fgoals_g3.py b/esmvalcore/cmor/_fixes/cmip6/fgoals_g3.py new file mode 100644 index 0000000000..7ae5045664 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/fgoals_g3.py @@ -0,0 +1,14 @@ +"""Fixes for FGOALS-g3 model.""" +from ..cmip5.fgoals_g2 import Cl as BaseCl + + +class Cl(BaseCl): + """Fixes for ``cl``.""" + + +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py b/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py index faac0927be..5cee052f85 100644 --- a/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py +++ b/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py @@ -32,6 +32,14 @@ def fix_metadata(self, cubes): return super().fix_metadata(cubes) +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" + + class Tas(Fix): """Fixes for tas.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/giss_e2_1_g.py b/esmvalcore/cmor/_fixes/cmip6/giss_e2_1_g.py index 8e2a2b09ed..268762e0f3 100644 --- a/esmvalcore/cmor/_fixes/cmip6/giss_e2_1_g.py +++ b/esmvalcore/cmor/_fixes/cmip6/giss_e2_1_g.py @@ -4,3 +4,11 @@ class Cl(BaseCl): """Fixes for ``cl``.""" + + +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/hadgem3_gc31_ll.py b/esmvalcore/cmor/_fixes/cmip6/hadgem3_gc31_ll.py index f24bad44ac..e5b803a889 100644 --- a/esmvalcore/cmor/_fixes/cmip6/hadgem3_gc31_ll.py +++ b/esmvalcore/cmor/_fixes/cmip6/hadgem3_gc31_ll.py @@ -1,5 +1,6 @@ """Fixes for CMIP6 HadGEM-GC31-LL.""" from ..fix import Fix +from .ukesm1_0_ll import Cl as BaseCl class AllVars(Fix): @@ -27,3 +28,15 @@ def fix_metadata(self, cubes): except AttributeError: pass return cubes + + +class Cl(BaseCl): + """Fixes for ``cl``.""" + + +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/kace_1_0_g.py b/esmvalcore/cmor/_fixes/cmip6/kace_1_0_g.py new file mode 100644 index 0000000000..744ca3b139 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/kace_1_0_g.py @@ -0,0 +1,19 @@ +"""Fixes for CMIP6 KACE-1-0-G.""" +from .ukesm1_0_ll import Cl as BaseCl +from .ukesm1_0_ll import AllVars as BaseAllVars + + +class AllVars(BaseAllVars): + """Fixes for all vars.""" + + +class Cl(BaseCl): + """Fixes for ``cl``.""" + + +class Clw(BaseCl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(BaseCl): + """Fixes for ``cli (same as for cl)``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/miroc6.py b/esmvalcore/cmor/_fixes/cmip6/miroc6.py index 8a1ad27217..fdd9b97985 100644 --- a/esmvalcore/cmor/_fixes/cmip6/miroc6.py +++ b/esmvalcore/cmor/_fixes/cmip6/miroc6.py @@ -23,3 +23,11 @@ def fix_metadata(self, cubes): coord = cube.coord(long_name='Surface Air Pressure') coord.attributes = {} return cubes + + +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/miroc_es2l.py b/esmvalcore/cmor/_fixes/cmip6/miroc_es2l.py index d467d83796..a66feccae4 100644 --- a/esmvalcore/cmor/_fixes/cmip6/miroc_es2l.py +++ b/esmvalcore/cmor/_fixes/cmip6/miroc_es2l.py @@ -4,3 +4,11 @@ class Cl(BaseCl): """Fixes for ``cl``.""" + + +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/mpi_esm1_2_hr.py b/esmvalcore/cmor/_fixes/cmip6/mpi_esm1_2_hr.py index fcf3d9dd78..69c59cac3a 100644 --- a/esmvalcore/cmor/_fixes/cmip6/mpi_esm1_2_hr.py +++ b/esmvalcore/cmor/_fixes/cmip6/mpi_esm1_2_hr.py @@ -8,6 +8,14 @@ class Cl(BaseCl): """Fixes for ``cl``.""" +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" + + class Tas(Fix): """Fixes for tas.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/mri_esm2_0.py b/esmvalcore/cmor/_fixes/cmip6/mri_esm2_0.py index af5126d5f4..8e73995cde 100644 --- a/esmvalcore/cmor/_fixes/cmip6/mri_esm2_0.py +++ b/esmvalcore/cmor/_fixes/cmip6/mri_esm2_0.py @@ -4,3 +4,11 @@ class Cl(BaseCl): """Fixes for ``cl``.""" + + +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/nesm3.py b/esmvalcore/cmor/_fixes/cmip6/nesm3.py index 9ded3e8fc2..33a89ebd0a 100644 --- a/esmvalcore/cmor/_fixes/cmip6/nesm3.py +++ b/esmvalcore/cmor/_fixes/cmip6/nesm3.py @@ -4,3 +4,11 @@ class Cl(BaseCl): """Fixes for ``cl``.""" + + +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py b/esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py index 0eee658088..259ac56943 100644 --- a/esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py +++ b/esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py @@ -4,3 +4,11 @@ class Cl(BaseCl): """Fixes for ``cl``.""" + + +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/ukesm1_0_ll.py b/esmvalcore/cmor/_fixes/cmip6/ukesm1_0_ll.py index d737f1d214..1e8bd0022f 100644 --- a/esmvalcore/cmor/_fixes/cmip6/ukesm1_0_ll.py +++ b/esmvalcore/cmor/_fixes/cmip6/ukesm1_0_ll.py @@ -47,3 +47,11 @@ def fix_metadata(self, cubes): add_pressure_level_coordinate(cl_cube) return iris.cube.CubeList([cl_cube]) + + +class Clw(Cl): + """Fixes for ``clw (same as for cl)``.""" + + +class Cli(Cl): + """Fixes for ``cli (same as for cl)``.""" From 99d2dbbb77ef02480c16613d6f3b626377be6f80 Mon Sep 17 00:00:00 2001 From: Axel Lauer Date: Thu, 5 Mar 2020 10:04:01 +0100 Subject: [PATCH 8/9] added clcalipso to fix ipsl_cm6a_lr --- esmvalcore/cmor/_fixes/cmip6/ipsl_cm6a_lr.py | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/esmvalcore/cmor/_fixes/cmip6/ipsl_cm6a_lr.py b/esmvalcore/cmor/_fixes/cmip6/ipsl_cm6a_lr.py index 214a222d84..a767dd208b 100644 --- a/esmvalcore/cmor/_fixes/cmip6/ipsl_cm6a_lr.py +++ b/esmvalcore/cmor/_fixes/cmip6/ipsl_cm6a_lr.py @@ -44,3 +44,29 @@ def fix_metadata(self, cubes): cube.coord('longitude').var_name = 'lon' new_list.append(cube) return CubeList(new_list) + + +class Clcalipso(Fix): + """Fixes for clcalipso.""" + + def fix_metadata(self, cubes): + """ + Corrects alt40 coordinate var_name. + + Parameters + ---------- + cubes : iris.cube.CubeList + + Returns + ------- + iris.cube.CubeList + + """ + cl_cube = self.get_cube_from_list(cubes) + + alt40 = cube.coord('height') + alt40.var_name = 'alt40' + alt40.standard_name = 'altitude' + alt40.long_name = 'altitude' + + return iris.cube.CubeList([cl_cube]) From c7c3de1e86f7ecc7d21da746cf2ad5d67d1618eb Mon Sep 17 00:00:00 2001 From: Axel Lauer Date: Thu, 5 Mar 2020 10:14:41 +0100 Subject: [PATCH 9/9] cleaning up fixes ukesm1_0_ll and hadgem3_gc31_ll --- .../cmor/_fixes/cmip6/hadgem3_gc31_ll.py | 28 ++----------------- esmvalcore/cmor/_fixes/cmip6/ukesm1_0_ll.py | 26 +++++++++++++++-- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/esmvalcore/cmor/_fixes/cmip6/hadgem3_gc31_ll.py b/esmvalcore/cmor/_fixes/cmip6/hadgem3_gc31_ll.py index e5b803a889..4c6b280c40 100644 --- a/esmvalcore/cmor/_fixes/cmip6/hadgem3_gc31_ll.py +++ b/esmvalcore/cmor/_fixes/cmip6/hadgem3_gc31_ll.py @@ -1,34 +1,10 @@ """Fixes for CMIP6 HadGEM-GC31-LL.""" -from ..fix import Fix -from .ukesm1_0_ll import Cl as BaseCl +from .ukesm1_0_ll import Cl as BaseCl, AllVars as BaseAllVars -class AllVars(Fix): +class AllVars(BaseAllVars): """Fixes for all vars.""" - def fix_metadata(self, cubes): - """ - Fix parent time units. - - Parameters - ---------- - cube : iris.cube.CubeList - - Returns - ------- - iris.cube.Cube - - """ - parent_units = 'parent_time_units' - bad_value = 'days since 1850-01-01-00-00-00' - for cube in cubes: - try: - if cube.attributes[parent_units] == bad_value: - cube.attributes[parent_units] = 'days since 1850-01-01' - except AttributeError: - pass - return cubes - class Cl(BaseCl): """Fixes for ``cl``.""" diff --git a/esmvalcore/cmor/_fixes/cmip6/ukesm1_0_ll.py b/esmvalcore/cmor/_fixes/cmip6/ukesm1_0_ll.py index 1e8bd0022f..c6588a19b8 100644 --- a/esmvalcore/cmor/_fixes/cmip6/ukesm1_0_ll.py +++ b/esmvalcore/cmor/_fixes/cmip6/ukesm1_0_ll.py @@ -3,12 +3,34 @@ from ..fix import Fix from ..shared import add_pressure_level_coordinate, fix_bounds -from .hadgem3_gc31_ll import AllVars as BaseAllVars -class AllVars(BaseAllVars): +class AllVars(Fix): """Fixes for all vars.""" + def fix_metadata(self, cubes): + """ + Fix parent time units. + + Parameters + ---------- + cube : iris.cube.CubeList + + Returns + ------- + iris.cube.Cube + + """ + parent_units = 'parent_time_units' + bad_value = 'days since 1850-01-01-00-00-00' + for cube in cubes: + try: + if cube.attributes[parent_units] == bad_value: + cube.attributes[parent_units] = 'days since 1850-01-01' + except AttributeError: + pass + return cubes + class Cl(Fix): """Fixes for ``'cl'``."""