diff --git a/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1.py b/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1.py index 0f1baa2f24..c61eeaa192 100644 --- a/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1.py +++ b/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1.py @@ -3,9 +3,13 @@ from scipy.interpolate import InterpolatedUnivariateSpline from scipy.ndimage import map_coordinates +from ..common import ClFixHybridPressureCoord from ..fix import Fix +Cl = ClFixHybridPressureCoord + + class Tos(Fix): """Fixes for tos.""" diff --git a/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1_m.py b/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1_m.py index 11956558fe..4507ac2141 100644 --- a/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1_m.py +++ b/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1_m.py @@ -1,5 +1,9 @@ """Fixes for bcc-csm1-1-m.""" from .bcc_csm1_1 import Tos as BaseTos +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord class Tos(BaseTos): diff --git a/esmvalcore/cmor/_fixes/cmip5/canesm2.py b/esmvalcore/cmor/_fixes/cmip5/canesm2.py index e30578bb5f..b445a4dc3a 100644 --- a/esmvalcore/cmor/_fixes/cmip5/canesm2.py +++ b/esmvalcore/cmor/_fixes/cmip5/canesm2.py @@ -1,8 +1,11 @@ - """Fixes for CanESM2 model.""" +from ..common import ClFixHybridPressureCoord from ..fix import Fix +Cl = ClFixHybridPressureCoord + + class FgCo2(Fix): """Fixes for fgco2.""" @@ -15,6 +18,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..cc14dddc02 100644 --- a/esmvalcore/cmor/_fixes/cmip5/ccsm4.py +++ b/esmvalcore/cmor/_fixes/cmip5/ccsm4.py @@ -1,8 +1,12 @@ """Fixes for CCSM4 model.""" +from ..common import ClFixHybridPressureCoord from ..fix import Fix from ..shared import round_coordinates +Cl = ClFixHybridPressureCoord + + class Rlut(Fix): """Fixes for rlut.""" @@ -15,11 +19,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 +85,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..4c0a5a7564 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/csiro_mk3_6_0.py @@ -0,0 +1,5 @@ +"""Fixes for CSIRO-Mk3-6-0 model.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord 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..5ee8c8aff1 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/giss_e2_h.py @@ -0,0 +1,5 @@ +"""Fixes for GISS-E2-H.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord 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..0d538e7960 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/giss_e2_r.py @@ -0,0 +1,5 @@ +"""Fixes for GISS-E2-R.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord diff --git a/esmvalcore/cmor/_fixes/cmip5/inmcm4.py b/esmvalcore/cmor/_fixes/cmip5/inmcm4.py index 8f07eb1904..e89cea019d 100644 --- a/esmvalcore/cmor/_fixes/cmip5/inmcm4.py +++ b/esmvalcore/cmor/_fixes/cmip5/inmcm4.py @@ -1,10 +1,13 @@ - """Fixes for inmcm4 model.""" import iris +from ..common import ClFixHybridPressureCoord from ..fix import Fix +Cl = ClFixHybridPressureCoord + + class Gpp(Fix): """Fixes for gpp.""" @@ -16,7 +19,8 @@ def fix_data(self, cube): Parameters ---------- - cube: iris.cube.Cube + cube : iris.cube.Cube + Input cube. Returns ------- @@ -40,7 +44,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..894af45317 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/ipsl_cm5a_lr.py @@ -0,0 +1,5 @@ +"""Fixes for IPSL-CM5A-LR model.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord 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..893b9779c2 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/ipsl_cm5a_mr.py @@ -0,0 +1,5 @@ +"""Fixes for IPSL-CM5A-MR model.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord 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..03bed332d6 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/ipsl_cm5b_lr.py @@ -0,0 +1,5 @@ +"""Fixes for IPSL-CM5B-LR model.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord diff --git a/esmvalcore/cmor/_fixes/cmip5/miroc5.py b/esmvalcore/cmor/_fixes/cmip5/miroc5.py index fadb625999..ae5ec6ee16 100644 --- a/esmvalcore/cmor/_fixes/cmip5/miroc5.py +++ b/esmvalcore/cmor/_fixes/cmip5/miroc5.py @@ -1,10 +1,14 @@ """Fixes for MIROC5 model.""" from dask import array as da +from ..common import ClFixHybridPressureCoord from ..fix import Fix from ..shared import round_coordinates +Cl = ClFixHybridPressureCoord + + class Sftof(Fix): """Fixes for sftof.""" @@ -17,6 +21,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube. Returns ------- @@ -41,6 +46,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube. Returns ------- @@ -98,6 +104,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube. Returns ------- @@ -121,6 +128,7 @@ def fix_metadata(self, cubes): Parameters ---------- cubes: iris.cube.CubeList + Input cubes. Returns ------- @@ -146,6 +154,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..eacb7398c1 100644 --- a/esmvalcore/cmor/_fixes/cmip5/miroc_esm.py +++ b/esmvalcore/cmor/_fixes/cmip5/miroc_esm.py @@ -1,11 +1,15 @@ -"""Fixes for MIROC ESM model.""" +"""Fixes for MIROC-ESM model.""" from iris.coords import DimCoord from iris.exceptions import CoordinateNotFoundError +from ..common import ClFixHybridPressureCoord from ..fix import Fix +Cl = ClFixHybridPressureCoord + + class Tro3(Fix): """Fixes for tro3.""" @@ -18,6 +22,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube. Returns ------- @@ -41,11 +46,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 +69,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..a24aebd499 100644 --- a/esmvalcore/cmor/_fixes/cmip5/mpi_esm_lr.py +++ b/esmvalcore/cmor/_fixes/cmip5/mpi_esm_lr.py @@ -1,8 +1,11 @@ - -"""Fixes for MPI ESM LR model.""" +"""Fixes for MPI-ESM-LR model.""" +from ..common import ClFixHybridPressureCoord from ..fix import Fix +Cl = ClFixHybridPressureCoord + + class Pctisccp(Fix): """Fixes for pctisccp.""" @@ -15,6 +18,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..92b1a6a06b --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/mpi_esm_mr.py @@ -0,0 +1,5 @@ +"""Fixes for MPI-ESM-MR model.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord 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..303e1b0fd1 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/mpi_esm_p.py @@ -0,0 +1,5 @@ +"""Fixes for MPI-ESM-P model.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord diff --git a/esmvalcore/cmor/_fixes/cmip5/mri_cgcm3.py b/esmvalcore/cmor/_fixes/cmip5/mri_cgcm3.py index a2ca092a0d..a21b82eb7f 100644 --- a/esmvalcore/cmor/_fixes/cmip5/mri_cgcm3.py +++ b/esmvalcore/cmor/_fixes/cmip5/mri_cgcm3.py @@ -1,10 +1,13 @@ - """Fixes for MRI-CGCM3 model.""" from dask import array as da +from ..common import ClFixHybridPressureCoord from ..fix import Fix +Cl = ClFixHybridPressureCoord + + class Msftmyz(Fix): """Fixes for msftmyz.""" @@ -17,6 +20,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube. Returns ------- @@ -39,6 +43,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..703ef054d7 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip5/noresm1_m.py @@ -0,0 +1,5 @@ +"""Fixes for NorESM1-M.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord diff --git a/esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py b/esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py index b0313e1eb9..2787e282e4 100644 --- a/esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py +++ b/esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py @@ -1,5 +1,15 @@ """Fixes for BCC-CSM2-MR model.""" from ..cmip5.bcc_csm1_1 import Tos as BaseTos +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord + + +Cli = ClFixHybridPressureCoord + + +Clw = ClFixHybridPressureCoord class Tos(BaseTos): diff --git a/esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py b/esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py index e05c4940a1..c42746bb92 100644 --- a/esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py +++ b/esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py @@ -1,5 +1,15 @@ """Fixes for BCC-ESM1 model.""" from .bcc_csm2_mr import Tos as BaseTos +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord + + +Cli = ClFixHybridPressureCoord + + +Clw = ClFixHybridPressureCoord class Tos(BaseTos): 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..59f09bcd28 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/cams_csm1_0.py @@ -0,0 +1,11 @@ +"""Fixes for CAMS-CSM1-0 model.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord + + +Cli = ClFixHybridPressureCoord + + +Clw = ClFixHybridPressureCoord 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..1e2762bdd4 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1.py @@ -0,0 +1,79 @@ +"""Fixes for CNRM-CM6-1 model.""" +import iris + +from ..common import ClFixHybridPressureCoord +from ..fix import Fix +from ..shared import add_aux_coords_from_cubes, get_bounds_cube + + +class Cl(ClFixHybridPressureCoord): + """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 + + +class Clcalipso(Fix): + """Fixes for ``clcalipso``.""" + + def fix_metadata(self, cubes): + """Fix ``alt40`` coordinate. + + Parameters + ---------- + cubes : iris.cube.CubeList + Input cubes + + Returns + ------- + iris.cube.CubeList + + """ + cube = self.get_cube_from_list(cubes) + alt_40_coord = cube.coord('alt40') + alt_40_coord.standard_name = 'altitude' + return iris.cube.CubeList([cube]) + + +Cli = Cl + + +Clw = Cl 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..1039b10b7d --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/cnrm_cm6_1_hr.py @@ -0,0 +1,11 @@ +"""Fixes for CNRM-CM6-1-HR model.""" +from .cnrm_cm6_1 import Cl as BaseCl + + +Cl = BaseCl + + +Cli = BaseCl + + +Clw = BaseCl 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..a449b642ef --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/cnrm_esm2_1.py @@ -0,0 +1,15 @@ +"""Fixes for CNRM-ESM2-1 model.""" +from .cnrm_cm6_1 import Cl as BaseCl +from .cnrm_cm6_1 import Clcalipso as BaseClcalipso + + +Cl = BaseCl + + +Clcalipso = BaseClcalipso + + +Cli = BaseCl + + +Clw = BaseCl diff --git a/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py b/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py index b16eb6bb12..ce61e7fab6 100644 --- a/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py +++ b/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py @@ -1,8 +1,41 @@ """Fixes for GFDL-CM4 model.""" import iris +from ..common import ClFixHybridPressureCoord 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(ClFixHybridPressureCoord): + """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) + + +Cli = Cl + + +Clw = Cl class Tas(Fix): @@ -14,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 """ cube = self.get_cube_from_list(cubes) @@ -38,11 +72,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 +94,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..fbab1684bf --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/giss_e2_1_g.py @@ -0,0 +1,11 @@ +"""Fixes for GISS-E2-1-G model.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord + + +Cli = ClFixHybridPressureCoord + + +Clw = ClFixHybridPressureCoord 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..054a42255f --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/giss_e2_1_h.py @@ -0,0 +1,11 @@ +"""Fixes for GISS-E2-1-H model.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord + + +Cli = ClFixHybridPressureCoord + + +Clw = ClFixHybridPressureCoord diff --git a/esmvalcore/cmor/_fixes/cmip6/ipsl_cm6a_lr.py b/esmvalcore/cmor/_fixes/cmip6/ipsl_cm6a_lr.py index 214a222d84..dbaf4902e5 100644 --- a/esmvalcore/cmor/_fixes/cmip6/ipsl_cm6a_lr.py +++ b/esmvalcore/cmor/_fixes/cmip6/ipsl_cm6a_lr.py @@ -44,3 +44,27 @@ 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): + """Fix ``alt40`` coordinate. + + Parameters + ---------- + cubes : iris.cube.CubeList + Input cubes + + Returns + ------- + iris.cube.CubeList + + """ + cube = self.get_cube_from_list(cubes) + alt_40_coord = cube.coord('height') + alt_40_coord.long_name = 'altitude' + alt_40_coord.standard_name = 'altitude' + alt_40_coord.var_name = 'alt40' + return CubeList([cube]) diff --git a/esmvalcore/cmor/_fixes/cmip6/miroc6.py b/esmvalcore/cmor/_fixes/cmip6/miroc6.py new file mode 100644 index 0000000000..cd29695fe3 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/miroc6.py @@ -0,0 +1,11 @@ +"""Fixes for MIROC6 model.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord + + +Cli = ClFixHybridPressureCoord + + +Clw = ClFixHybridPressureCoord diff --git a/esmvalcore/cmor/_fixes/cmip6/miroc_es2l.py b/esmvalcore/cmor/_fixes/cmip6/miroc_es2l.py new file mode 100644 index 0000000000..aa28ae13cc --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/miroc_es2l.py @@ -0,0 +1,11 @@ +"""Fixes for MIROC-ES2L model.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord + + +Cli = ClFixHybridPressureCoord + + +Clw = ClFixHybridPressureCoord diff --git a/esmvalcore/cmor/_fixes/cmip6/mpi_esm1_2_hr.py b/esmvalcore/cmor/_fixes/cmip6/mpi_esm1_2_hr.py index 21648fc1dc..72ac9cd7ae 100644 --- a/esmvalcore/cmor/_fixes/cmip6/mpi_esm1_2_hr.py +++ b/esmvalcore/cmor/_fixes/cmip6/mpi_esm1_2_hr.py @@ -1,19 +1,29 @@ """Fixes for MPI-ESM1-2-HR model.""" - +from ..common import ClFixHybridPressureCoord from ..fix import Fix from ..shared import add_scalar_height_coord +Cl = ClFixHybridPressureCoord + + +Cli = ClFixHybridPressureCoord + + +Clw = ClFixHybridPressureCoord + + 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 +45,8 @@ def fix_metadata(self, cubes): Parameters ---------- - cube: iris.cube.CubeList + cubes : iris.cube.CubeList + Input cubes. Returns ------- @@ -66,11 +77,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..720670b4d5 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/mri_esm2_0.py @@ -0,0 +1,11 @@ +"""Fixes for MRI-ESM2-0 model.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord + + +Cli = ClFixHybridPressureCoord + + +Clw = ClFixHybridPressureCoord diff --git a/esmvalcore/cmor/_fixes/cmip6/nesm3.py b/esmvalcore/cmor/_fixes/cmip6/nesm3.py new file mode 100644 index 0000000000..a7e0a71442 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/nesm3.py @@ -0,0 +1,11 @@ +"""Fixes for NESM3 model.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord + + +Cli = ClFixHybridPressureCoord + + +Clw = ClFixHybridPressureCoord diff --git a/esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py b/esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py new file mode 100644 index 0000000000..696574b9a4 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py @@ -0,0 +1,11 @@ +"""Fixes for SAM0-UNICON model.""" +from ..common import ClFixHybridPressureCoord + + +Cl = ClFixHybridPressureCoord + + +Cli = ClFixHybridPressureCoord + + +Clw = ClFixHybridPressureCoord diff --git a/esmvalcore/cmor/_fixes/common.py b/esmvalcore/cmor/_fixes/common.py new file mode 100644 index 0000000000..7988395bed --- /dev/null +++ b/esmvalcore/cmor/_fixes/common.py @@ -0,0 +1,64 @@ +"""Common fixes used for multiple datasets.""" +import iris + +from .fix import Fix +from .shared import fix_bounds + + +class ClFixHybridPressureCoord(Fix): + """Fixes for ``cl`` regarding hybrid sigma pressure coordinates.""" + + 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, short_name='cl') + + # 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 + # This was originally done by iris, but it has to be repeated since + # a has bounds now + 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) + + # Remove attributes from Surface Air Pressure coordinate + cl_cube.coord(var_name='ps').attributes = {} + + return iris.cube.CubeList([cl_cube]) diff --git a/esmvalcore/cmor/_fixes/shared.py b/esmvalcore/cmor/_fixes/shared.py index b6eb609943..53b1ca023f 100644 --- a/esmvalcore/cmor/_fixes/shared.py +++ b/esmvalcore/cmor/_fixes/shared.py @@ -6,6 +6,8 @@ import iris from cf_units import Unit +from esmvalcore.preprocessor._derive._shared import var_name_constraint + logger = logging.getLogger(__name__) @@ -173,6 +175,20 @@ def add_sigma_factory(cube): "coordinate not available") +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_scalar_depth_coord(cube, depth=0.0): """Add scalar coordinate 'depth' with value of `depth`m.""" logger.debug("Adding depth coordinate (%sm)", depth) @@ -236,7 +252,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, @@ -246,8 +262,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`. @@ -256,22 +298,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/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: diff --git a/setup.py b/setup.py index 3850b6f12b..2a68f5c6ef 100755 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ 'pytest-cov', 'pytest-env', 'pytest-flake8', - 'pytest-html', + 'pytest-html!=2.1.0', 'pytest-metadata>=1.5.1', ], # Development dependencies 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 28c838f3de..93e9912918 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1.py +++ b/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1.py @@ -1,11 +1,23 @@ -"""Test Access1-0 fixes.""" +"""Test bcc-csm1-1 fixes.""" import unittest import iris import numpy as np +from esmvalcore.cmor._fixes.cmip5.bcc_csm1_1 import Cl, Tos +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord 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 test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) class TestTos(unittest.TestCase): 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 efcec0b79c..e25d62709f 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,8 +1,20 @@ -"""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.common import ClFixHybridPressureCoord 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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) class TestTos(unittest.TestCase): diff --git a/tests/integration/cmor/_fixes/cmip5/test_canesm2.py b/tests/integration/cmor/_fixes/cmip5/test_canesm2.py index ff9d75082e..e63ff987b8 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_canesm2.py +++ b/tests/integration/cmor/_fixes/cmip5/test_canesm2.py @@ -1,22 +1,35 @@ -"""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._fixes.common import ClFixHybridPressureCoord 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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) 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..853367b7a5 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_ccsm4.py +++ b/tests/integration/cmor/_fixes/cmip5/test_ccsm4.py @@ -5,12 +5,25 @@ from iris.coords import DimCoord from iris.cube import Cube +from esmvalcore.cmor._fixes.cmip5.ccsm4 import Cl, Rlut, Rlutcs, So +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord 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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) 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 +37,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 +54,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 +68,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 +85,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..7b89ba4ec9 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_csiro_mk3_6_0.py @@ -0,0 +1,15 @@ +"""Test fixes for CSIRO-Mk3-6-0.""" +from esmvalcore.cmor._fixes.cmip5.csiro_mk3_6_0 import Cl +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) 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..f667219a6b --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_giss_e2_h.py @@ -0,0 +1,15 @@ +"""Test fixes for GISS-E2-H.""" +from esmvalcore.cmor._fixes.cmip5.giss_e2_h import Cl +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) 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..f42f98da5c --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_giss_e2_r.py @@ -0,0 +1,15 @@ +"""Test fixes for GISS-E2-R.""" +from esmvalcore.cmor._fixes.cmip5.giss_e2_r import Cl +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) diff --git a/tests/integration/cmor/_fixes/cmip5/test_inmcm4.py b/tests/integration/cmor/_fixes/cmip5/test_inmcm4.py index 9da7f16103..a86c6decad 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_inmcm4.py +++ b/tests/integration/cmor/_fixes/cmip5/test_inmcm4.py @@ -1,22 +1,35 @@ """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._fixes.common import ClFixHybridPressureCoord 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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) 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 +42,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 +62,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..c6e9f02b1a --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_ipsl_cm5a_lr.py @@ -0,0 +1,15 @@ +"""Test fixes for IPSL-CM5A-LR.""" +from esmvalcore.cmor._fixes.cmip5.ipsl_cm5a_lr import Cl +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) 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..08ddaec198 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_ipsl_cm5a_mr.py @@ -0,0 +1,15 @@ +"""Test fixes for IPSL-CM5A-MR.""" +from esmvalcore.cmor._fixes.cmip5.ipsl_cm5a_mr import Cl +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) 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..746a83335c --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_ipsl_cm5b_lr.py @@ -0,0 +1,15 @@ +"""Test fixes for IPSL-CM5B-LR.""" +from esmvalcore.cmor._fixes.cmip5.ipsl_cm5b_lr import Cl +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) diff --git a/tests/integration/cmor/_fixes/cmip5/test_miroc5.py b/tests/integration/cmor/_fixes/cmip5/test_miroc5.py index e9980a36cc..8cbf02fad8 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_miroc5.py +++ b/tests/integration/cmor/_fixes/cmip5/test_miroc5.py @@ -5,10 +5,22 @@ from cf_units import Unit from iris.cube import Cube -from esmvalcore.cmor._fixes.cmip5.miroc5 import Hur, Sftof, Tas +from esmvalcore.cmor._fixes.cmip5.miroc5 import Cl, Hur, Sftof, Tas +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord 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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) + + def test_get_hur_fix(): """Test getting of fix.""" fix = Fix.get_fixes('CMIP5', 'MIROC5', 'Amon', 'hur') @@ -27,13 +39,14 @@ def test_hur_fix_metadata(mock_base_fix_metadata): 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)]) @@ -46,6 +59,7 @@ def test_fix_data(self): class TestTas(unittest.TestCase): """Test tas fixes.""" + def setUp(self): """Prepare tests.""" self.coord_name = 'latitude' @@ -56,7 +70,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..9a034be48f 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_miroc_esm.py +++ b/tests/integration/cmor/_fixes/cmip5/test_miroc_esm.py @@ -6,19 +6,32 @@ 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._fixes.common import ClFixHybridPressureCoord 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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) 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 +45,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 +66,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 +80,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..93fb6c0e2d 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,32 @@ from cf_units import Unit from iris.cube import Cube +from esmvalcore.cmor._fixes.cmip5.mpi_esm_lr import Cl, Pctisccp +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord 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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) 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..715cfcab42 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_mpi_esm_mr.py @@ -0,0 +1,15 @@ +"""Test fixes for MPI-ESM-MR.""" +from esmvalcore.cmor._fixes.cmip5.mpi_esm_mr import Cl +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) 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..b88f032a43 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_mpi_esm_p.py @@ -0,0 +1,15 @@ +"""Test fixes for MPI-ESM-P.""" +from esmvalcore.cmor._fixes.cmip5.mpi_esm_p import Cl +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) diff --git a/tests/integration/cmor/_fixes/cmip5/test_mri_cgcm3.py b/tests/integration/cmor/_fixes/cmip5/test_mri_cgcm3.py index 1b93fb3004..598ceee0db 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_mri_cgcm3.py +++ b/tests/integration/cmor/_fixes/cmip5/test_mri_cgcm3.py @@ -1,14 +1,27 @@ -"""Test MRI-GCM3 fixes.""" +"""Test MRI-CGCM3 fixes.""" import unittest +from esmvalcore.cmor._fixes.cmip5.mri_cgcm3 import Cl, Msftmyz, ThetaO +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord 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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) 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 +29,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..dc1c25b49c --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip5/test_noresm1_m.py @@ -0,0 +1,15 @@ +"""Test fixes for NorESM1-M.""" +from esmvalcore.cmor._fixes.cmip5.noresm1_m import Cl +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) diff --git a/tests/integration/cmor/_fixes/cmip6/test_bcc_csm2_mr.py b/tests/integration/cmor/_fixes/cmip6/test_bcc_csm2_mr.py index c29e84cdb2..3ce07f65a9 100644 --- a/tests/integration/cmor/_fixes/cmip6/test_bcc_csm2_mr.py +++ b/tests/integration/cmor/_fixes/cmip6/test_bcc_csm2_mr.py @@ -3,10 +3,44 @@ import iris -from esmvalcore.cmor._fixes.cmip6.bcc_csm2_mr import Tos +from esmvalcore.cmor._fixes.cmip6.bcc_csm2_mr import Cl, Cli, Clw, Tos +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord 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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'BCC-CSM2-MR', 'Amon', 'cli') + assert fix == [Cli(None)] + + +def test_cli_fix(): + """Test fix for ``cli``.""" + assert Cli(None) == ClFixHybridPressureCoord(None) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'BCC-CSM2-MR', 'Amon', 'clw') + assert fix == [Clw(None)] + + +def test_clw_fix(): + """Test fix for ``clw``.""" + assert Clw(None) == ClFixHybridPressureCoord(None) + + def test_get_tos_fix(): """Test getting of fix.""" fix = Fix.get_fixes('CMIP6', 'BCC-CSM2-MR', 'Omon', 'tos') diff --git a/tests/integration/cmor/_fixes/cmip6/test_bcc_esm1.py b/tests/integration/cmor/_fixes/cmip6/test_bcc_esm1.py index c3479b0d45..ec2d3475f1 100644 --- a/tests/integration/cmor/_fixes/cmip6/test_bcc_esm1.py +++ b/tests/integration/cmor/_fixes/cmip6/test_bcc_esm1.py @@ -1,10 +1,44 @@ """Test fixes for BCC-ESM1.""" import unittest -from esmvalcore.cmor._fixes.cmip6.bcc_esm1 import Tos +from esmvalcore.cmor._fixes.cmip6.bcc_esm1 import Cl, Cli, Clw, Tos +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord 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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'BCC-ESM1', 'Amon', 'cli') + assert fix == [Cli(None)] + + +def test_cli_fix(): + """Test fix for ``cli``.""" + assert Cli(None) == ClFixHybridPressureCoord(None) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'BCC-ESM1', 'Amon', 'clw') + assert fix == [Clw(None)] + + +def test_clw_fix(): + """Test fix for ``clw``.""" + assert Clw(None) == ClFixHybridPressureCoord(None) + + def test_get_tos_fix(): """Test getting of fix.""" fix = Fix.get_fixes('CMIP6', 'BCC-ESM1', 'Omon', 'tos') 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..9bc0f83fec --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_cams_csm1_0.py @@ -0,0 +1,37 @@ +"""Test fixes for CAMS-CSM1-0.""" +from esmvalcore.cmor._fixes.cmip6.cams_csm1_0 import Cl, Cli, Clw +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CAMS-CSM1-0', 'Amon', 'cli') + assert fix == [Cli(None)] + + +def test_cli_fix(): + """Test fix for ``cli``.""" + assert Cli(None) == ClFixHybridPressureCoord(None) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CAMS-CSM1-0', 'Amon', 'clw') + assert fix == [Clw(None)] + + +def test_clw_fix(): + """Test fix for ``clw``.""" + assert Clw(None) == ClFixHybridPressureCoord(None) 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..fe14974f7d --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_cnrm_cm6_1.py @@ -0,0 +1,196 @@ +"""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, Clcalipso, Cli, Clw +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]]) + + +def test_get_clcalipso_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CNRM-CM6-1', 'Amon', 'clcalipso') + assert fix == [Clcalipso(None)] + + +@pytest.fixture +def clcalipso_cubes(): + """Cubes to test fix for ``clcalipso``.""" + alt_40_coord = iris.coords.DimCoord([0.0], var_name='alt40') + cube = iris.cube.Cube([0.0], var_name='clcalipso', + dim_coords_and_dims=[(alt_40_coord.copy(), 0)]) + x_cube = iris.cube.Cube([0.0], var_name='x', + dim_coords_and_dims=[(alt_40_coord.copy(), 0)]) + return iris.cube.CubeList([cube, x_cube]) + + +def test_clcalipso_fix_metadata(clcalipso_cubes): + """Test ``fix_metadata`` for ``clcalipso``.""" + fix = Clcalipso(None) + cubes = fix.fix_metadata(clcalipso_cubes) + assert len(cubes) == 1 + cube = cubes[0] + coord = cube.coord('altitude') + assert coord.standard_name == 'altitude' + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CNRM-CM6-1', 'Amon', 'cli') + assert fix == [Cli(None)] + + +def test_cli_fix(): + """Test fix for ``cli``.""" + assert Cli(None) == Cl(None) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CNRM-CM6-1', 'Amon', 'clw') + assert fix == [Clw(None)] + + +def test_clw_fix(): + """Test fix for ``clw``.""" + assert Clw(None) == Cl(None) 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..0ce697764c --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_cnrm_cm6_1_hr.py @@ -0,0 +1,37 @@ +"""Test fixes for CNRM-CM6-1-HR.""" +from esmvalcore.cmor._fixes.cmip6.cnrm_cm6_1 import Cl as BaseCl +from esmvalcore.cmor._fixes.cmip6.cnrm_cm6_1_hr import Cl, Cli, Clw +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == BaseCl(None) + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CNRM-CM6-1-HR', 'Amon', 'cli') + assert fix == [Cli(None)] + + +def test_cli_fix(): + """Test fix for ``cli``.""" + assert Cli(None) == BaseCl(None) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CNRM-CM6-1-HR', 'Amon', 'clw') + assert fix == [Clw(None)] + + +def test_clw_fix(): + """Test fix for ``clw``.""" + assert Clw(None) == BaseCl(None) 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..592bb508c3 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_cnrm_esm2_1.py @@ -0,0 +1,49 @@ +"""Test fixes for CNRM-ESM2-1.""" +from esmvalcore.cmor._fixes.cmip6.cnrm_cm6_1 import Cl as BaseCl +from esmvalcore.cmor._fixes.cmip6.cnrm_cm6_1 import Clcalipso as BaseClcalipso +from esmvalcore.cmor._fixes.cmip6.cnrm_esm2_1 import Cl, Clcalipso, Cli, Clw +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == BaseCl(None) + + +def test_get_clcalipso_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CNRM-ESM2-1', 'Amon', 'clcalipso') + assert fix == [Clcalipso(None)] + + +def test_clcalipso_fix(): + """Test fix for ``cl``.""" + assert Clcalipso(None) == BaseClcalipso(None) + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CNRM-ESM2-1', 'Amon', 'cli') + assert fix == [Cli(None)] + + +def test_cli_fix(): + """Test fix for ``cli``.""" + assert Cli(None) == BaseCl(None) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'CNRM-ESM2-1', 'Amon', 'clw') + assert fix == [Clw(None)] + + +def test_clw_fix(): + """Test fix for ``clw``.""" + assert Clw(None) == BaseCl(None) 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..2b359df924 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_gfdl_cm4.py @@ -0,0 +1,159 @@ +"""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, Cli, Clw +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) + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'GFDL-CM4', 'Amon', 'cli') + assert fix == [Cli(None)] + + +def test_cli_fix(): + """Test fix for ``cli``.""" + assert Cli(None) == Cl(None) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'GFDL-CM4', 'Amon', 'clw') + assert fix == [Clw(None)] + + +def test_clw_fix(): + """Test fix for ``clw``.""" + assert Clw(None) == Cl(None) 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..7be6b91faf --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_giss_e2_1_g.py @@ -0,0 +1,37 @@ +"""Test fixes for GISS-E2-1-G.""" +from esmvalcore.cmor._fixes.cmip6.giss_e2_1_g import Cl, Cli, Clw +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'GISS-E2-1-G', 'Amon', 'cli') + assert fix == [Cli(None)] + + +def test_cli_fix(): + """Test fix for ``cli``.""" + assert Cli(None) == ClFixHybridPressureCoord(None) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'GISS-E2-1-G', 'Amon', 'clw') + assert fix == [Clw(None)] + + +def test_clw_fix(): + """Test fix for ``clw``.""" + assert Clw(None) == ClFixHybridPressureCoord(None) 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..533ed4658c --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_giss_e2_1_h.py @@ -0,0 +1,37 @@ +"""Test fixes for GISS-E2-1-H.""" +from esmvalcore.cmor._fixes.cmip6.giss_e2_1_h import Cl, Cli, Clw +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'GISS-E2-1-H', 'Amon', 'cli') + assert fix == [Cli(None)] + + +def test_cli_fix(): + """Test fix for ``cli``.""" + assert Cli(None) == ClFixHybridPressureCoord(None) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'GISS-E2-1-H', 'Amon', 'clw') + assert fix == [Clw(None)] + + +def test_clw_fix(): + """Test fix for ``clw``.""" + assert Clw(None) == ClFixHybridPressureCoord(None) diff --git a/tests/integration/cmor/_fixes/cmip6/test_ipsl_cm6a_lr.py b/tests/integration/cmor/_fixes/cmip6/test_ipsl_cm6a_lr.py index 5edc8b8554..2622fe3120 100644 --- a/tests/integration/cmor/_fixes/cmip6/test_ipsl_cm6a_lr.py +++ b/tests/integration/cmor/_fixes/cmip6/test_ipsl_cm6a_lr.py @@ -1,16 +1,23 @@ +"""Tests for the fixes of IPSL-CM6A-LR.""" import unittest +import iris import numpy as np +import pytest from iris.cube import Cube, CubeList from iris.coords import AuxCoord from iris.exceptions import CoordinateNotFoundError -from esmvalcore.cmor._fixes.cmip6.ipsl_cm6a_lr import AllVars +from esmvalcore.cmor._fixes.cmip6.ipsl_cm6a_lr import AllVars, Clcalipso +from esmvalcore.cmor._fixes.fix import Fix class TestAllVars(unittest.TestCase): + """Tests for fixes of all variables.""" + def setUp(self): + """Set up tests.""" self.fix = AllVars(None) self.cube = Cube(np.random.rand(2, 2, 2), var_name='ch4') self.cube.add_aux_coord( @@ -23,6 +30,7 @@ def setUp(self): standard_name='longitude'), (1, 2)) def test_fix_metadata_ocean_var(self): + """Test ``fix_metadata`` for ocean variables.""" cell_area = Cube(np.random.rand(2, 2), standard_name='cell_area') cubes = self.fix.fix_metadata(CubeList([self.cube, cell_area])) @@ -33,6 +41,7 @@ def test_fix_metadata_ocean_var(self): self.cube.coord('cell_area') def test_fix_data_other_var(self): + """Test ``fix_metadata`` for other variables.""" cubes = self.fix.fix_metadata(CubeList([self.cube])) self.assertEqual(len(cubes), 1) @@ -41,3 +50,32 @@ def test_fix_data_other_var(self): self.assertEqual(cube.coord('longitude').var_name, 'nav_lon') with self.assertRaises(CoordinateNotFoundError): self.cube.coord('cell_area') + + +def test_get_clcalipso_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'IPSL-CM6A-LR', 'Amon', 'clcalipso') + assert fix == [Clcalipso(None), AllVars(None)] + + +@pytest.fixture +def clcalipso_cubes(): + """Cubes to test fix for ``clcalipso``.""" + alt_40_coord = iris.coords.DimCoord([0.0], var_name='height') + cube = iris.cube.Cube([0.0], var_name='clcalipso', + dim_coords_and_dims=[(alt_40_coord.copy(), 0)]) + x_cube = iris.cube.Cube([0.0], var_name='x', + dim_coords_and_dims=[(alt_40_coord.copy(), 0)]) + return iris.cube.CubeList([cube, x_cube]) + + +def test_clcalipso_fix_metadata(clcalipso_cubes): + """Test ``fix_metadata`` for ``clcalipso``.""" + fix = Clcalipso(None) + cubes = fix.fix_metadata(clcalipso_cubes) + assert len(cubes) == 1 + cube = cubes[0] + coord = cube.coord('altitude') + assert coord.long_name == 'altitude' + assert coord.standard_name == 'altitude' + assert coord.var_name == 'alt40' 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..5b52817bbe --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_miroc6.py @@ -0,0 +1,37 @@ +"""Test fixes for MIROC6.""" +from esmvalcore.cmor._fixes.cmip6.miroc6 import Cl, Cli, Clw +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'MIROC6', 'Amon', 'cl') + assert fix == [Cl(None)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'MIROC6', 'Amon', 'cli') + assert fix == [Cli(None)] + + +def test_cli_fix(): + """Test fix for ``cli``.""" + assert Cli(None) == ClFixHybridPressureCoord(None) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'MIROC6', 'Amon', 'clw') + assert fix == [Clw(None)] + + +def test_clw_fix(): + """Test fix for ``clw``.""" + assert Clw(None) == ClFixHybridPressureCoord(None) 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..354ea5d5b5 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_miroc_es2l.py @@ -0,0 +1,37 @@ +"""Test fixes for MIROC-ES2L.""" +from esmvalcore.cmor._fixes.cmip6.miroc_es2l import Cl, Cli, Clw +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'MIROC-ES2L', 'Amon', 'cli') + assert fix == [Cli(None)] + + +def test_cli_fix(): + """Test fix for ``cli``.""" + assert Cli(None) == ClFixHybridPressureCoord(None) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'MIROC-ES2L', 'Amon', 'clw') + assert fix == [Clw(None)] + + +def test_clw_fix(): + """Test fix for ``clw``.""" + assert Clw(None) == ClFixHybridPressureCoord(None) 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..ec0cc5daa0 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_mpi_esm1_2_hr.py @@ -0,0 +1,37 @@ +"""Test fixes for MPI-ESM1-2-HR.""" +from esmvalcore.cmor._fixes.cmip6.mpi_esm1_2_hr import Cl, Cli, Clw +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'MPI-ESM1-2-HR', 'Amon', 'cli') + assert fix == [Cli(None)] + + +def test_cli_fix(): + """Test fix for ``cli``.""" + assert Cli(None) == ClFixHybridPressureCoord(None) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'MPI-ESM1-2-HR', 'Amon', 'clw') + assert fix == [Clw(None)] + + +def test_clw_fix(): + """Test fix for ``clw``.""" + assert Clw(None) == ClFixHybridPressureCoord(None) 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..201620fab0 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_mri_esm2_0.py @@ -0,0 +1,37 @@ +"""Test fixes for MRI-ESM2-0.""" +from esmvalcore.cmor._fixes.cmip6.mri_esm2_0 import Cl, Cli, Clw +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'MRI-ESM2-0', 'Amon', 'cli') + assert fix == [Cli(None)] + + +def test_cli_fix(): + """Test fix for ``cli``.""" + assert Cli(None) == ClFixHybridPressureCoord(None) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'MRI-ESM2-0', 'Amon', 'clw') + assert fix == [Clw(None)] + + +def test_clw_fix(): + """Test fix for ``clw``.""" + assert Clw(None) == ClFixHybridPressureCoord(None) 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..040dabc9c6 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_nesm3.py @@ -0,0 +1,37 @@ +"""Test fixes for NESM3.""" +from esmvalcore.cmor._fixes.cmip6.nesm3 import Cl, Cli, Clw +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'NESM3', 'Amon', 'cli') + assert fix == [Cli(None)] + + +def test_cli_fix(): + """Test fix for ``cli``.""" + assert Cli(None) == ClFixHybridPressureCoord(None) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'NESM3', 'Amon', 'clw') + assert fix == [Clw(None)] + + +def test_clw_fix(): + """Test fix for ``clw``.""" + assert Clw(None) == ClFixHybridPressureCoord(None) 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..2e2d79df66 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_sam0_unicon.py @@ -0,0 +1,37 @@ +"""Test fixes for SAM0-UNICON.""" +from esmvalcore.cmor._fixes.cmip6.sam0_unicon import Cl, Cli, Clw +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord +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)] + + +def test_cl_fix(): + """Test fix for ``cl``.""" + assert Cl(None) == ClFixHybridPressureCoord(None) + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'SAM0-UNICON', 'Amon', 'cli') + assert fix == [Cli(None)] + + +def test_cli_fix(): + """Test fix for ``cli``.""" + assert Cli(None) == ClFixHybridPressureCoord(None) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'SAM0-UNICON', 'Amon', 'clw') + assert fix == [Clw(None)] + + +def test_clw_fix(): + """Test fix for ``clw``.""" + assert Clw(None) == ClFixHybridPressureCoord(None) diff --git a/tests/integration/cmor/_fixes/test_common.py b/tests/integration/cmor/_fixes/test_common.py new file mode 100644 index 0000000000..115030b538 --- /dev/null +++ b/tests/integration/cmor/_fixes/test_common.py @@ -0,0 +1,213 @@ +"""Test for common fixes used for multiple datasets.""" +import os + +import iris +import numpy as np +import pytest +from netCDF4 import Dataset + +from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord + + +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' + dataset.variables['ps'].additional_attribute = 'xyz' + + # 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_hybrid_pressure_coord_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) + + # Raw ps cube + ps_cube = cubes.extract_strict('surface_air_pressure') + assert ps_cube.attributes == {'additional_attribute': 'xyz'} + + # Apply fix + fix = ClFixHybridPressureCoord(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) + surface_pressure_coord = fixed_cl_cube.coord(var_name='ps') + assert surface_pressure_coord.attributes == {} + + +def test_cl_fix_hybrid_pressure_coord_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) + + # Raw ps cube + ps_cube = cubes.extract_strict('surface_air_pressure') + assert ps_cube.attributes == {'additional_attribute': 'xyz'} + + # Apply fix + fix = ClFixHybridPressureCoord(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) + surface_pressure_coord = fixed_cl_cube.coord(var_name='ps') + assert surface_pressure_coord.attributes == {} diff --git a/tests/integration/cmor/_fixes/test_shared.py b/tests/integration/cmor/_fixes/test_shared.py index 17f4af4800..2158f3c27f 100644 --- a/tests/integration/cmor/_fixes/test_shared.py +++ b/tests/integration/cmor/_fixes/test_shared.py @@ -1,16 +1,66 @@ """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 (add_aux_coords_from_cubes, + add_scalar_depth_coord, add_scalar_height_coord, add_scalar_typeland_coord, add_scalar_typesea_coord, add_sigma_factory, - 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 + + +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) DIM_COORD = iris.coords.DimCoord([3.141592], @@ -28,6 +78,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 @@ -54,6 +105,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 @@ -80,6 +132,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' @@ -105,6 +158,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' @@ -164,6 +218,94 @@ def test_add_sigma_factory(cube, output): assert air_pressure_coord == output +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]]) @@ -180,6 +322,7 @@ def test_add_sigma_factory(cube, output): @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 @@ -191,24 +334,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')