diff --git a/esmvalcore/_recipe/to_datasets.py b/esmvalcore/_recipe/to_datasets.py index c5bf1e59bc..9c391f4507 100644 --- a/esmvalcore/_recipe/to_datasets.py +++ b/esmvalcore/_recipe/to_datasets.py @@ -33,12 +33,13 @@ _ALIAS_INFO_KEYS: tuple[str, ...] = ( "project", - "activity", - "driver", - "dataset", "exp", - "sub_experiment", + "dataset", + "rcm_version", + "driver", "ensemble", + "sub_experiment", + "grid", "version", ) """List of keys to be used to compose the alias, ordered by priority.""" diff --git a/esmvalcore/cmor/_fixes/cordex/cnrm_cerfacs_cnrm_cm5/cosmo_crclim_v1_1.py b/esmvalcore/cmor/_fixes/cordex/cnrm_cerfacs_cnrm_cm5/cosmo_crclim_v1_1.py new file mode 100644 index 0000000000..8dd6cbf562 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cordex/cnrm_cerfacs_cnrm_cm5/cosmo_crclim_v1_1.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from esmvalcore.cmor.fix import Fix + +if TYPE_CHECKING: + from collections.abc import Sequence + + from iris.cube import Cube + + +class Snw(Fix): + """Fixes for snw.""" + + def fix_metadata(self, cubes: Sequence[Cube]) -> Sequence[Cube]: + cube = self.get_cube_from_list(cubes) + cube = cube.copy() + cube.remove_coord("height") + return [cube] diff --git a/esmvalcore/cmor/_fixes/cordex/cnrm_cerfacs_cnrm_cm5/hadrem3_ga7_05.py b/esmvalcore/cmor/_fixes/cordex/cnrm_cerfacs_cnrm_cm5/hadrem3_ga7_05.py index ad169ebb0b..6ed60b4e06 100644 --- a/esmvalcore/cmor/_fixes/cordex/cnrm_cerfacs_cnrm_cm5/hadrem3_ga7_05.py +++ b/esmvalcore/cmor/_fixes/cordex/cnrm_cerfacs_cnrm_cm5/hadrem3_ga7_05.py @@ -1,9 +1,43 @@ """Fixes for rcm HadREM3-GA7-05 driven by CNRM-CERFACS-CNRM-CM5.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +import iris +import iris.cube + from esmvalcore.cmor._fixes.cordex.cordex_fixes import ( MOHCHadREM3GA705 as BaseFix, ) +from esmvalcore.cmor.fix import Fix + +if TYPE_CHECKING: + from collections.abc import Sequence + +AllVars = BaseFix + + +class Clivi(Fix): + """Fixes for variable clivi.""" + + def fix_metadata( + self, + cubes: Sequence[iris.cube.Cube], + ) -> Sequence[iris.cube.Cube]: + cube = self.get_cube_from_list(iris.cube.CubeList(cubes)).copy() + cube.data = cube.core_data() * 0.1 + return [cube] + -Tas = BaseFix +class Sic(Fix): + """Fixes for variable sic.""" -Pr = BaseFix + def fix_metadata( + self, + cubes: Sequence[iris.cube.Cube], + ) -> Sequence[iris.cube.Cube]: + cube = self.get_cube_from_list(iris.cube.CubeList(cubes)).copy() + cube.units = 1 + cube.convert_units("%") + return [cube] diff --git a/esmvalcore/cmor/_fixes/cordex/cnrm_cerfacs_cnrm_cm5/hirham5.py b/esmvalcore/cmor/_fixes/cordex/cnrm_cerfacs_cnrm_cm5/hirham5.py new file mode 100644 index 0000000000..93297c9d42 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cordex/cnrm_cerfacs_cnrm_cm5/hirham5.py @@ -0,0 +1,38 @@ +"""Fixes for rcm HIRHAM5 driven by CNRM-CERFACS-CNRM-CM5.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import iris +import iris.cube + +from esmvalcore.cmor.fix import Fix + +if TYPE_CHECKING: + from collections.abc import Sequence + + +class Clivi(Fix): + """Fixes for variable clivi.""" + + def fix_metadata( + self, + cubes: Sequence[iris.cube.Cube], + ) -> Sequence[iris.cube.Cube]: + cube = self.get_cube_from_list(iris.cube.CubeList(cubes)).copy() + cube.units = "g m-2" + cube.convert_units("kg m-2") + return [cube] + + +class Prw(Fix): + """Fixes for variable prw.""" + + def fix_metadata( + self, + cubes: Sequence[iris.cube.Cube], + ) -> Sequence[iris.cube.Cube]: + cube = self.get_cube_from_list(iris.cube.CubeList(cubes)).copy() + cube.data = cube.core_data() / 200.0 + return [cube] diff --git a/esmvalcore/cmor/_fixes/cordex/cordex_fixes.py b/esmvalcore/cmor/_fixes/cordex/cordex_fixes.py index 820bc42a18..9cc483ba11 100644 --- a/esmvalcore/cmor/_fixes/cordex/cordex_fixes.py +++ b/esmvalcore/cmor/_fixes/cordex/cordex_fixes.py @@ -72,6 +72,9 @@ def fix_metadata(self, cubes): iris.cube.CubeList """ for cube in cubes: + if self.vardef.standard_name: + cube.standard_name = self.vardef.standard_name + cube.long_name = self.vardef.long_name cube.coord("latitude").var_name = "lat" cube.coord("longitude").var_name = "lon" for coord in cube.coords("time"): diff --git a/esmvalcore/cmor/_fixes/cordex/ichec_ec_earth/cosmo_crclim_v1_1.py b/esmvalcore/cmor/_fixes/cordex/ichec_ec_earth/cosmo_crclim_v1_1.py new file mode 100644 index 0000000000..e9d8f26514 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cordex/ichec_ec_earth/cosmo_crclim_v1_1.py @@ -0,0 +1,7 @@ +from esmvalcore.cmor._fixes.cordex.cnrm_cerfacs_cnrm_cm5.cosmo_crclim_v1_1 import ( + Snw as BaseSnw, +) + + +class Snw(BaseSnw): + """Fixes for snw.""" diff --git a/esmvalcore/cmor/_fixes/cordex/ichec_ec_earth/hadrem3_ga7_05.py b/esmvalcore/cmor/_fixes/cordex/ichec_ec_earth/hadrem3_ga7_05.py index fd9743e0af..c1928f398d 100644 --- a/esmvalcore/cmor/_fixes/cordex/ichec_ec_earth/hadrem3_ga7_05.py +++ b/esmvalcore/cmor/_fixes/cordex/ichec_ec_earth/hadrem3_ga7_05.py @@ -1,9 +1,14 @@ """Fixes for rcm HadREM3-GA7-05 driven by ICHEC-EC-EARTH.""" +from esmvalcore.cmor._fixes.cordex.cnrm_cerfacs_cnrm_cm5.hadrem3_ga7_05 import ( + Clivi as BaseClivi, +) from esmvalcore.cmor._fixes.cordex.cordex_fixes import ( MOHCHadREM3GA705 as BaseFix, ) -Tas = BaseFix +AllVars = BaseFix + -Pr = BaseFix +class Clivi(BaseClivi): + """Fixes for variable clivi.""" diff --git a/esmvalcore/cmor/_fixes/cordex/ichec_ec_earth/hirham5.py b/esmvalcore/cmor/_fixes/cordex/ichec_ec_earth/hirham5.py new file mode 100644 index 0000000000..538582f940 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cordex/ichec_ec_earth/hirham5.py @@ -0,0 +1,9 @@ +"""Fixes for rcm HIRHAM5 driven by ICHEC-EC-EARTH.""" + +from esmvalcore.cmor._fixes.cordex.cnrm_cerfacs_cnrm_cm5.hirham5 import ( + Clivi as BaseClivi, +) + + +class Clivi(BaseClivi): + """Fixes for variable clivi.""" diff --git a/esmvalcore/cmor/_fixes/cordex/ipsl_ipsl_cm5a_mr/hirham5.py b/esmvalcore/cmor/_fixes/cordex/ipsl_ipsl_cm5a_mr/hirham5.py new file mode 100644 index 0000000000..86755cfb53 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cordex/ipsl_ipsl_cm5a_mr/hirham5.py @@ -0,0 +1,9 @@ +"""Fixes for rcm HIRHAM5 driven by IPSL-IPSL-CM5A-MR.""" + +from esmvalcore.cmor._fixes.cordex.cnrm_cerfacs_cnrm_cm5.hirham5 import ( + Clivi as BaseClivi, +) + + +class Clivi(BaseClivi): + """Fixes for variable clivi.""" diff --git a/esmvalcore/cmor/_fixes/cordex/mohc_hadgem2_es/cclm4_8_17.py b/esmvalcore/cmor/_fixes/cordex/mohc_hadgem2_es/cclm4_8_17.py index ebd246f5ce..743320502c 100644 --- a/esmvalcore/cmor/_fixes/cordex/mohc_hadgem2_es/cclm4_8_17.py +++ b/esmvalcore/cmor/_fixes/cordex/mohc_hadgem2_es/cclm4_8_17.py @@ -1,7 +1,44 @@ """Fixes for rcm CCLM4-8-17 driven by MOHC-HadGEM2-ES.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +import iris +import iris.cube + from esmvalcore.cmor._fixes.cordex.cordex_fixes import ( CLMcomCCLM4817 as BaseFix, ) +from esmvalcore.cmor.fix import Fix + +if TYPE_CHECKING: + from collections.abc import Sequence AllVars = BaseFix + + +class Clivi(Fix): + """Fixes for variable clivi.""" + + def fix_metadata( + self, + cubes: Sequence[iris.cube.Cube], + ) -> Sequence[iris.cube.Cube]: + cube = self.get_cube_from_list(iris.cube.CubeList(cubes)).copy() + cube.units = "Mg m-2" + cube.convert_units("kg m-2") + return [cube] + + +class Prw(Fix): + """Fixes for variable prw.""" + + def fix_metadata( + self, + cubes: Sequence[iris.cube.Cube], + ) -> Sequence[iris.cube.Cube]: + cube = self.get_cube_from_list(iris.cube.CubeList(cubes)).copy() + cube.units = "Mg m-2" + cube.convert_units("kg m-2") + return [cube] diff --git a/esmvalcore/cmor/_fixes/cordex/mohc_hadgem2_es/cosmo_crclim_v1_1.py b/esmvalcore/cmor/_fixes/cordex/mohc_hadgem2_es/cosmo_crclim_v1_1.py new file mode 100644 index 0000000000..e9d8f26514 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cordex/mohc_hadgem2_es/cosmo_crclim_v1_1.py @@ -0,0 +1,7 @@ +from esmvalcore.cmor._fixes.cordex.cnrm_cerfacs_cnrm_cm5.cosmo_crclim_v1_1 import ( + Snw as BaseSnw, +) + + +class Snw(BaseSnw): + """Fixes for snw.""" diff --git a/esmvalcore/cmor/_fixes/cordex/mohc_hadgem2_es/hadrem3_ga7_05.py b/esmvalcore/cmor/_fixes/cordex/mohc_hadgem2_es/hadrem3_ga7_05.py index 2852e95492..b96d7c561b 100644 --- a/esmvalcore/cmor/_fixes/cordex/mohc_hadgem2_es/hadrem3_ga7_05.py +++ b/esmvalcore/cmor/_fixes/cordex/mohc_hadgem2_es/hadrem3_ga7_05.py @@ -1,9 +1,14 @@ """Fixes for rcm HadREM3-GA7-05 driven by MOHC-HadGEM2-ES.""" +from esmvalcore.cmor._fixes.cordex.cnrm_cerfacs_cnrm_cm5.hadrem3_ga7_05 import ( + Sic as BaseSic, +) from esmvalcore.cmor._fixes.cordex.cordex_fixes import ( MOHCHadREM3GA705 as BaseFix, ) -Tas = BaseFix +AllVars = BaseFix + -Pr = BaseFix +class Sic(BaseSic): + """Fixes for variable sic.""" diff --git a/esmvalcore/cmor/_fixes/cordex/mohc_hadgem2_es/hirham5.py b/esmvalcore/cmor/_fixes/cordex/mohc_hadgem2_es/hirham5.py index 8a7e659c5a..1b0ad95caa 100644 --- a/esmvalcore/cmor/_fixes/cordex/mohc_hadgem2_es/hirham5.py +++ b/esmvalcore/cmor/_fixes/cordex/mohc_hadgem2_es/hirham5.py @@ -1,5 +1,8 @@ -"""Fixes for rcm HIRHAM driven by MOHC-HadGEM2.""" +"""Fixes for rcm HIRHAM5 driven by MOHC-HadGEM2.""" +from esmvalcore.cmor._fixes.cordex.cnrm_cerfacs_cnrm_cm5.hirham5 import ( + Clivi as BaseClivi, +) from esmvalcore.cmor.fix import Fix @@ -24,3 +27,7 @@ def fix_metadata(self, cubes): cube.coord("longitude").attributes = {} return cubes + + +class Clivi(BaseClivi): + """Fixes for variable clivi.""" diff --git a/esmvalcore/cmor/_fixes/cordex/mpi_m_mpi_esm_lr/cosmo_crclim_v1_1.py b/esmvalcore/cmor/_fixes/cordex/mpi_m_mpi_esm_lr/cosmo_crclim_v1_1.py new file mode 100644 index 0000000000..e9d8f26514 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cordex/mpi_m_mpi_esm_lr/cosmo_crclim_v1_1.py @@ -0,0 +1,7 @@ +from esmvalcore.cmor._fixes.cordex.cnrm_cerfacs_cnrm_cm5.cosmo_crclim_v1_1 import ( + Snw as BaseSnw, +) + + +class Snw(BaseSnw): + """Fixes for snw.""" diff --git a/esmvalcore/cmor/_fixes/cordex/mpi_m_mpi_esm_lr/hadrem3_ga7_05.py b/esmvalcore/cmor/_fixes/cordex/mpi_m_mpi_esm_lr/hadrem3_ga7_05.py index c2d52052e9..64949560fc 100644 --- a/esmvalcore/cmor/_fixes/cordex/mpi_m_mpi_esm_lr/hadrem3_ga7_05.py +++ b/esmvalcore/cmor/_fixes/cordex/mpi_m_mpi_esm_lr/hadrem3_ga7_05.py @@ -1,9 +1,21 @@ """Fixes for rcm HadREM3-GA7-05 driven by MPI-M-MPI-ESM-LR.""" +from esmvalcore.cmor._fixes.cordex.cnrm_cerfacs_cnrm_cm5.hadrem3_ga7_05 import ( + Clivi as BaseClivi, +) +from esmvalcore.cmor._fixes.cordex.cnrm_cerfacs_cnrm_cm5.hadrem3_ga7_05 import ( + Sic as BaseSic, +) from esmvalcore.cmor._fixes.cordex.cordex_fixes import ( MOHCHadREM3GA705 as BaseFix, ) -Tas = BaseFix +AllVars = BaseFix + + +class Clivi(BaseClivi): + """Fixes for variable clivi.""" + -Pr = BaseFix +class Sic(BaseSic): + """Fixes for variable sic.""" diff --git a/esmvalcore/cmor/_fixes/cordex/mpi_m_mpi_esm_lr/hirham5.py b/esmvalcore/cmor/_fixes/cordex/mpi_m_mpi_esm_lr/hirham5.py new file mode 100644 index 0000000000..538582f940 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cordex/mpi_m_mpi_esm_lr/hirham5.py @@ -0,0 +1,9 @@ +"""Fixes for rcm HIRHAM5 driven by ICHEC-EC-EARTH.""" + +from esmvalcore.cmor._fixes.cordex.cnrm_cerfacs_cnrm_cm5.hirham5 import ( + Clivi as BaseClivi, +) + + +class Clivi(BaseClivi): + """Fixes for variable clivi.""" diff --git a/esmvalcore/cmor/_fixes/cordex/mpi_m_mpi_esm_lr/wrf381p.py b/esmvalcore/cmor/_fixes/cordex/mpi_m_mpi_esm_lr/wrf381p.py new file mode 100644 index 0000000000..ccb830df1f --- /dev/null +++ b/esmvalcore/cmor/_fixes/cordex/mpi_m_mpi_esm_lr/wrf381p.py @@ -0,0 +1,41 @@ +"""Fixes for rcm WRF381P driven by MPI-M-MPI-ESM-LR.""" + +from esmvalcore.cmor.fix import Fix +from esmvalcore.preprocessor._regrid import _cordex_stock_cube + + +class AllVars(Fix): + """Fixes for all vars.""" + + def fix_metadata(self, cubes): + cube = self.get_cube_from_list(cubes).copy() + if cube.coords("projection_x_coordinate"): + # WRF381P datasets with the MPI-M-MPI-ESM-LR driver have bad + # projection coordinates containing all zeros, but the latitude + # and longitude points match those of the standard grid. Therefore + # we replace the bad coordinates with the standard rotated pole + # coordinates. + # https://github.com/ESMValGroup/ESMValCore/issues/3145 + standard_grid = _cordex_stock_cube(self.extra_facets["domain"]) + x_dim = cube.coord_dims("projection_x_coordinate") + y_dim = cube.coord_dims("projection_y_coordinate") + + # Remove the bad coordinates. + cube.remove_coord("projection_x_coordinate") + cube.remove_coord("projection_y_coordinate") + cube.remove_coord("longitude") + cube.remove_coord("latitude") + + # Add the standard rotated pole coordinates. + cube.add_dim_coord( + standard_grid.coord("grid_longitude"), + x_dim, + ) + cube.add_dim_coord( + standard_grid.coord("grid_latitude"), + y_dim, + ) + cube.add_aux_coord(standard_grid.coord("longitude"), y_dim + x_dim) + cube.add_aux_coord(standard_grid.coord("latitude"), y_dim + x_dim) + + return [cube] diff --git a/esmvalcore/cmor/_fixes/cordex/ncc_noresm1_m/cosmo_crclim_v1_1.py b/esmvalcore/cmor/_fixes/cordex/ncc_noresm1_m/cosmo_crclim_v1_1.py new file mode 100644 index 0000000000..e9d8f26514 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cordex/ncc_noresm1_m/cosmo_crclim_v1_1.py @@ -0,0 +1,7 @@ +from esmvalcore.cmor._fixes.cordex.cnrm_cerfacs_cnrm_cm5.cosmo_crclim_v1_1 import ( + Snw as BaseSnw, +) + + +class Snw(BaseSnw): + """Fixes for snw.""" diff --git a/esmvalcore/cmor/_fixes/cordex/ncc_noresm1_m/hadrem3_ga7_05.py b/esmvalcore/cmor/_fixes/cordex/ncc_noresm1_m/hadrem3_ga7_05.py index 97cac1a896..bb7a5094ec 100644 --- a/esmvalcore/cmor/_fixes/cordex/ncc_noresm1_m/hadrem3_ga7_05.py +++ b/esmvalcore/cmor/_fixes/cordex/ncc_noresm1_m/hadrem3_ga7_05.py @@ -1,9 +1,21 @@ """Fixes for rcm HadREM3-GA7-05 driven by NCC-NorESM1-M.""" +from esmvalcore.cmor._fixes.cordex.cnrm_cerfacs_cnrm_cm5.hadrem3_ga7_05 import ( + Clivi as BaseClivi, +) +from esmvalcore.cmor._fixes.cordex.cnrm_cerfacs_cnrm_cm5.hadrem3_ga7_05 import ( + Sic as BaseSic, +) from esmvalcore.cmor._fixes.cordex.cordex_fixes import ( MOHCHadREM3GA705 as BaseFix, ) -Tas = BaseFix +AllVars = BaseFix + + +class Clivi(BaseClivi): + """Fixes for variable clivi.""" + -Pr = BaseFix +class Sic(BaseSic): + """Fixes for variable sic.""" diff --git a/esmvalcore/cmor/_fixes/cordex/ncc_noresm1_m/hirham5.py b/esmvalcore/cmor/_fixes/cordex/ncc_noresm1_m/hirham5.py new file mode 100644 index 0000000000..538582f940 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cordex/ncc_noresm1_m/hirham5.py @@ -0,0 +1,9 @@ +"""Fixes for rcm HIRHAM5 driven by ICHEC-EC-EARTH.""" + +from esmvalcore.cmor._fixes.cordex.cnrm_cerfacs_cnrm_cm5.hirham5 import ( + Clivi as BaseClivi, +) + + +class Clivi(BaseClivi): + """Fixes for variable clivi.""" diff --git a/esmvalcore/config/configurations/defaults/extra_facets_cordex.yml b/esmvalcore/config/configurations/defaults/extra_facets_cordex.yml index 62f502a3f7..a8a042f24f 100644 --- a/esmvalcore/config/configurations/defaults/extra_facets_cordex.yml +++ b/esmvalcore/config/configurations/defaults/extra_facets_cordex.yml @@ -11,7 +11,19 @@ projects: '*': '*': use_standard_grid: true + COSMO-crCLIM-v1-1: + '*': + '*': + use_standard_grid: true # lat/lon coords missing bounds HIRHAM5: '*': '*': use_standard_grid: true + RACMO22E: + '*': + '*': + use_standard_grid: true # lat/lon coords missing bounds + RCA4: + '*': + '*': + use_standard_grid: true # lat/lon coords missing bounds diff --git a/esmvalcore/preprocessor/_multimodel.py b/esmvalcore/preprocessor/_multimodel.py index d8ce839ff7..e929f23670 100644 --- a/esmvalcore/preprocessor/_multimodel.py +++ b/esmvalcore/preprocessor/_multimodel.py @@ -889,7 +889,15 @@ def ensemble_statistics( :func:`esmvalcore.preprocessor.multi_model_statistics` for the full description of the core statistics function. """ - ensemble_grouping = ("project", "dataset", "exp", "sub_experiment") + ensemble_grouping = ( + "project", + "exp", + "dataset", + "rcm_version", + "driver", + "grid", + "sub_experiment", + ) return multi_model_statistics( products=products, span=span, diff --git a/esmvalcore/preprocessor/_regrid.py b/esmvalcore/preprocessor/_regrid.py index 3e7ecf7fd8..b826f7bd3c 100644 --- a/esmvalcore/preprocessor/_regrid.py +++ b/esmvalcore/preprocessor/_regrid.py @@ -249,6 +249,9 @@ def _cordex_stock_cube(domain_name: str) -> Cube: (cube,) = ncdata.iris_xarray.cubes_from_xarray(domain) cube.coord("grid_latitude").guess_bounds() cube.coord("grid_longitude").guess_bounds() + # Restore the units that iris changed to degrees. + cube.coord("latitude").units = domain.lat.attrs["units"] + cube.coord("longitude").units = domain.lon.attrs["units"] return cube diff --git a/tests/integration/cmor/_fixes/cordex/test_cnrm_cerfacs_cnrm_cm5.py b/tests/integration/cmor/_fixes/cordex/test_cnrm_cerfacs_cnrm_cm5.py index e60006b6df..2a295dca02 100644 --- a/tests/integration/cmor/_fixes/cordex/test_cnrm_cerfacs_cnrm_cm5.py +++ b/tests/integration/cmor/_fixes/cordex/test_cnrm_cerfacs_cnrm_cm5.py @@ -193,7 +193,7 @@ def test_wrf381p_height_fix(): var_name="tas", dim_coords_and_dims=[(time_coord, 0)], ) - vardef = get_var_info("CMIP6", "Amon", "tas") + vardef = get_var_info("CORDEX", "day", "tas") fix = wrf381p.Tas(vardef) out_cubes = fix.fix_metadata([cube]) assert out_cubes[0].coord("height").points == 2.0 @@ -208,3 +208,33 @@ def test_get_cclm4_8_17fix() -> None: extra_facets={"driver": "CNRM-CERFACS-CNRM-CM5"}, ) assert any(isinstance(fix, CLMcomCCLM4817) for fix in fixes) + + +def test_cosmo_crclim_v1_1_snw_drop_height_coord(): + height_coord = iris.coords.AuxCoord( + [2.0], + var_name="height", + standard_name="height", + long_name="height", + ) + cube = iris.cube.Cube( + [10.0], + var_name="snw", + aux_coords_and_dims=[(height_coord, 0)], + ) + fixes = Fix.get_fixes( + "CORDEX", + "COSMO-crCLIM-v1-1", + "day", + "snw", + extra_facets={ + "driver": "CNRM-CERFACS-CNRM-CM5", + "domain": "EUR-11", + }, + ) + cubes = [cube] + for fix in fixes: + cubes = fix.fix_metadata(cubes) + assert len(cubes) == 1 + assert cubes[0].var_name == "snw" + assert not cubes[0].coords("height") diff --git a/tests/integration/cmor/_fixes/cordex/test_cordex_fixes.py b/tests/integration/cmor/_fixes/cordex/test_cordex_fixes.py index 2b61d04bec..d6e96b3933 100644 --- a/tests/integration/cmor/_fixes/cordex/test_cordex_fixes.py +++ b/tests/integration/cmor/_fixes/cordex/test_cordex_fixes.py @@ -18,13 +18,14 @@ TimeLongName, ) from esmvalcore.cmor.fix import Fix +from esmvalcore.cmor.table import get_var_info if TYPE_CHECKING: import pytest_mock @pytest.fixture -def cubes(): +def cubes() -> iris.cube.CubeList: correct_time_coord = iris.coords.DimCoord( [0.0], var_name="time", @@ -83,7 +84,7 @@ def cubes(): @pytest.fixture -def cordex_cubes(): +def cordex_cubes() -> iris.cube.CubeList: coord_system = iris.coord_systems.RotatedGeogCS( grid_north_pole_latitude=39.25, grid_north_pole_longitude=-162, @@ -134,8 +135,15 @@ def cordex_cubes(): ("longitude", "lon", "longitude"), ], ) -def test_mohchadrem3ga705_fix_metadata(cubes, coord, var_name, long_name): - fix = MOHCHadREM3GA705(None) +def test_mohchadrem3ga705_fix_metadata( + cubes: iris.cube.CubeList, + coord: str, + var_name: str, + long_name: str, +) -> None: + vardef = get_var_info("CORDEX", "day", "ts") + assert vardef is not None + fix = MOHCHadREM3GA705(vardef) out_cubes = fix.fix_metadata(cubes) assert cubes is out_cubes for cube in out_cubes: @@ -148,7 +156,9 @@ def test_mohchadrem3ga705_fix_metadata_no_time_coord( ) -> None: for cube in cubes: cube.remove_coord("time") - fix = MOHCHadREM3GA705(None) # type: ignore[arg-type] + vardef = get_var_info("CORDEX", "day", "ts") + assert vardef is not None + fix = MOHCHadREM3GA705(vardef) out_cubes = fix.fix_metadata(cubes) assert cubes is out_cubes for cube in out_cubes: diff --git a/tests/integration/cmor/_fixes/cordex/test_ichec_ec_earth.py b/tests/integration/cmor/_fixes/cordex/test_ichec_ec_earth.py index f921f1b93f..7509bcb486 100644 --- a/tests/integration/cmor/_fixes/cordex/test_ichec_ec_earth.py +++ b/tests/integration/cmor/_fixes/cordex/test_ichec_ec_earth.py @@ -3,7 +3,10 @@ import iris import pytest -from esmvalcore.cmor._fixes.cordex.ichec_ec_earth import wrf381p +from esmvalcore.cmor._fixes.cordex.ichec_ec_earth import ( + cosmo_crclim_v1_1, + wrf381p, +) from esmvalcore.cmor.fix import Fix from esmvalcore.cmor.table import get_var_info @@ -81,7 +84,18 @@ def test_wrf381p_height_fix(): var_name="tas", dim_coords_and_dims=[(time_coord, 0)], ) - vardef = get_var_info("CMIP6", "Amon", "tas") + vardef = get_var_info("CORDEX", "day", "tas") fix = wrf381p.Tas(vardef) out_cubes = fix.fix_metadata([cube]) assert out_cubes[0].coord("height").points == 2.0 + + +def test_get_cosmo_crclim_v1_1_fix() -> None: + fixes = Fix.get_fixes( + "CORDEX", + "COSMO-crCLIM-v1-1", + "day", + "snw", + extra_facets={"driver": "ICHEC-EC-Earth"}, + ) + assert any(isinstance(fix, cosmo_crclim_v1_1.Snw) for fix in fixes) diff --git a/tests/integration/cmor/_fixes/cordex/test_ipsl_ipsl_cm5a_mr.py b/tests/integration/cmor/_fixes/cordex/test_ipsl_ipsl_cm5a_mr.py index 86c85abfd3..be00e84c39 100644 --- a/tests/integration/cmor/_fixes/cordex/test_ipsl_ipsl_cm5a_mr.py +++ b/tests/integration/cmor/_fixes/cordex/test_ipsl_ipsl_cm5a_mr.py @@ -35,7 +35,7 @@ def test_wrf381p_height_fix(): var_name="tas", dim_coords_and_dims=[(time_coord, 0)], ) - vardef = get_var_info("CMIP6", "Amon", "tas") + vardef = get_var_info("CORDEX", "day", "tas") fix = wrf381p.Tas(vardef) out_cubes = fix.fix_metadata([cube]) assert out_cubes[0].coord("height").points == 2.0 diff --git a/tests/integration/cmor/_fixes/cordex/test_mohc_hadgem2_es.py b/tests/integration/cmor/_fixes/cordex/test_mohc_hadgem2_es.py index fd85412cc4..90995a5d26 100644 --- a/tests/integration/cmor/_fixes/cordex/test_mohc_hadgem2_es.py +++ b/tests/integration/cmor/_fixes/cordex/test_mohc_hadgem2_es.py @@ -4,7 +4,11 @@ import pytest from esmvalcore.cmor._fixes.cordex.cordex_fixes import CLMcomCCLM4817 -from esmvalcore.cmor._fixes.cordex.mohc_hadgem2_es import hirham5, wrf381p +from esmvalcore.cmor._fixes.cordex.mohc_hadgem2_es import ( + cosmo_crclim_v1_1, + hirham5, + wrf381p, +) from esmvalcore.cmor.fix import Fix from esmvalcore.cmor.table import get_var_info @@ -153,7 +157,7 @@ def test_wrf381p_height_fix(): var_name="tas", dim_coords_and_dims=[(time_coord, 0)], ) - vardef = get_var_info("CMIP6", "Amon", "tas") + vardef = get_var_info("CORDEX", "day", "tas") fix = wrf381p.Tas(vardef) out_cubes = fix.fix_metadata([cube]) assert out_cubes[0].coord("height").points == 2.0 @@ -168,3 +172,14 @@ def test_get_cclm4_8_17fix() -> None: extra_facets={"driver": "MOHC-HadGEM2-ES"}, ) assert any(isinstance(fix, CLMcomCCLM4817) for fix in fixes) + + +def test_get_cosmo_crclim_v1_1_fix() -> None: + fixes = Fix.get_fixes( + "CORDEX", + "COSMO-crCLIM-v1-1", + "day", + "snw", + extra_facets={"driver": "MOHC-HadGEM2-ES"}, + ) + assert any(isinstance(fix, cosmo_crclim_v1_1.Snw) for fix in fixes) diff --git a/tests/integration/cmor/_fixes/cordex/test_mpi_m_mpi_esm_lr.py b/tests/integration/cmor/_fixes/cordex/test_mpi_m_mpi_esm_lr.py index e68301660c..cc4480c88b 100644 --- a/tests/integration/cmor/_fixes/cordex/test_mpi_m_mpi_esm_lr.py +++ b/tests/integration/cmor/_fixes/cordex/test_mpi_m_mpi_esm_lr.py @@ -1,8 +1,17 @@ """Tests for the fixes of driver MPI-M-MPI-ESM-LR.""" +import cordex as cx +import dask.array as da +import iris +import iris.coords +import iris.cube +import numpy as np import pytest from esmvalcore.cmor._fixes.cordex.cordex_fixes import CLMcomCCLM4817 +from esmvalcore.cmor._fixes.cordex.mpi_m_mpi_esm_lr import ( + cosmo_crclim_v1_1, +) from esmvalcore.cmor.fix import Fix @@ -50,3 +59,106 @@ def test_get_cclm4_8_17fix() -> None: extra_facets={"driver": "MPI-M-MPI-ESM-LR"}, ) assert any(isinstance(fix, CLMcomCCLM4817) for fix in fixes) + + +def test_get_cosmo_crclim_v1_1_fix() -> None: + fixes = Fix.get_fixes( + "CORDEX", + "COSMO-crCLIM-v1-1", + "day", + "snw", + extra_facets={"driver": "MPI-M-MPI-ESM-LR"}, + ) + assert any(isinstance(fix, cosmo_crclim_v1_1.Snw) for fix in fixes) + + +def test_wrf381p_rlut_standard_grid() -> None: + + standard_grid = cx.domain("EUR-11") + cube = iris.cube.Cube( + da.empty((1, 412, 424), dtype="float32"), + var_name="rlut", + standard_name="toa_outgoing_longwave_flux", + long_name="TOA Outgoing Longwave Radiation", + units="W m-2", + dim_coords_and_dims=[ + ( + iris.coords.DimCoord( + [0], + bounds=[[-0.5, 0.5]], + var_name="time", + standard_name="time", + units="days since 2000-01-01", + ), + 0, + ), + ], + aux_coords_and_dims=[ + ( + iris.coords.AuxCoord( + np.zeros(412), + var_name="y", + standard_name="projection_y_coordinate", + units="m", + ), + (1,), + ), + ( + iris.coords.AuxCoord( + np.zeros(424), + var_name="x", + standard_name="projection_x_coordinate", + units="m", + ), + (2,), + ), + ( + iris.coords.AuxCoord( + standard_grid.lat.data + 1e-7, + var_name="lat", + standard_name="latitude", + units="degrees_north", + ), + (1, 2), + ), + ( + iris.coords.AuxCoord( + standard_grid.lon.data + 1e-7, + var_name="lon", + standard_name="longitude", + units="degrees_east", + ), + (1, 2), + ), + ], + ) + fixes = Fix.get_fixes( + "CORDEX", + "WRF381P", + "day", + "rlut", + extra_facets={ + "driver": "MPI-M-MPI-ESM-LR", + "domain": "EUR-11", + }, + ) + cubes = [cube] + for fix in fixes: + cubes = fix.fix_metadata(cubes) + assert len(cubes) == 1 + result = cubes[0] + for standard_name, var_name in [ + ("grid_latitude", "rlat"), + ("grid_longitude", "rlon"), + ("latitude", "lat"), + ("longitude", "lon"), + ]: + print("Checking coordinate:", standard_name) + coord = result.coord(standard_name) + var = standard_grid[var_name] + assert coord.units == var.attrs["units"] + expected_points = ( + var.data % 360 if standard_name == "longitude" else var.data + ) + np.testing.assert_almost_equal(coord.points, expected_points) + assert coord.has_bounds() diff --git a/tests/integration/cmor/_fixes/cordex/test_ncc_noresm1_m.py b/tests/integration/cmor/_fixes/cordex/test_ncc_noresm1_m.py index 99291139c8..db401cba49 100644 --- a/tests/integration/cmor/_fixes/cordex/test_ncc_noresm1_m.py +++ b/tests/integration/cmor/_fixes/cordex/test_ncc_noresm1_m.py @@ -3,7 +3,10 @@ import iris import pytest -from esmvalcore.cmor._fixes.cordex.ncc_noresm1_m import wrf381p +from esmvalcore.cmor._fixes.cordex.ncc_noresm1_m import ( + cosmo_crclim_v1_1, + wrf381p, +) from esmvalcore.cmor.fix import Fix from esmvalcore.cmor.table import get_var_info @@ -81,7 +84,18 @@ def test_wrf381p_height_fix(): var_name="tas", dim_coords_and_dims=[(time_coord, 0)], ) - vardef = get_var_info("CMIP6", "Amon", "tas") + vardef = get_var_info("CORDEX", "day", "tas") fix = wrf381p.Tas(vardef) out_cubes = fix.fix_metadata([cube]) assert out_cubes[0].coord("height").points == 2.0 + + +def test_get_cosmo_crclim_v1_1_fix() -> None: + fixes = Fix.get_fixes( + "CORDEX", + "COSMO-crCLIM-v1-1", + "day", + "snw", + extra_facets={"driver": "NCC-NorESM1-M"}, + ) + assert any(isinstance(fix, cosmo_crclim_v1_1.Snw) for fix in fixes) diff --git a/tests/integration/recipe/test_recipe.py b/tests/integration/recipe/test_recipe.py index 17a0521120..72931db8b0 100644 --- a/tests/integration/recipe/test_recipe.py +++ b/tests/integration/recipe/test_recipe.py @@ -1730,10 +1730,10 @@ def test_alias_generation(tmp_path, patched_datafinder, session): # noqa: C901, - {dataset: FGOALS-g3, sub_experiment: s1961, ensemble: r1, institute: CAS} - {project: OBS, dataset: ERA-Interim, version: 1} - {project: OBS, dataset: ERA-Interim, version: 2} - - {project: CMIP6, activity: CMP, dataset: GF3, ensemble: r1, institute: fake} - - {project: CMIP6, activity: CMP, dataset: GF2, ensemble: r1, institute: fake} - - {project: CMIP6, activity: HRMP, dataset: EC, ensemble: r1, institute: fake} - - {project: CMIP6, activity: HRMP, dataset: HA, ensemble: r1, institute: fake} + - {project: CMIP6, activity: CMP, exp: a, dataset: GF3, ensemble: r1, institute: fake} + - {project: CMIP6, activity: CMP, exp: a, dataset: GF2, ensemble: r1, institute: fake} + - {project: CMIP6, activity: HRMP, exp: b, dataset: EC, ensemble: r1, institute: fake} + - {project: CMIP6, activity: HRMP, exp: b, dataset: HA, ensemble: r1, institute: fake} - {project: CORDEX, driver: ICHEC-EC-EARTH, dataset: RCA4, ensemble: r1, mip: mon, institute: SMHI} - {project: CORDEX, driver: MIROC-MIROC5, dataset: RCA4, ensemble: r1, mip: mon, institute: SMHI} scripts: null @@ -1758,13 +1758,13 @@ def test_alias_generation(tmp_path, patched_datafinder, session): # noqa: C901, assert dataset["alias"] == "my_alias" elif dataset["project"] == "CMIP6": if dataset["dataset"] == "GF3": - assert dataset["alias"] == "CMIP6_CMP_GF3" + assert dataset["alias"] == "CMIP6_a_GF3" elif dataset["dataset"] == "GF2": - assert dataset["alias"] == "CMIP6_CMP_GF2" + assert dataset["alias"] == "CMIP6_a_GF2" elif dataset["dataset"] == "EC": - assert dataset["alias"] == "CMIP6_HRMP_EC" + assert dataset["alias"] == "CMIP6_b_EC" else: - assert dataset["alias"] == "CMIP6_HRMP_HA" + assert dataset["alias"] == "CMIP6_b_HA" elif dataset["project"] == "CORDEX": if dataset["driver"] == "ICHEC-EC-EARTH": assert dataset["alias"] == "CORDEX_ICHEC-EC-EARTH" diff --git a/tests/unit/preprocessor/_multimodel/test_multimodel.py b/tests/unit/preprocessor/_multimodel/test_multimodel.py index 223ff454c4..e919740398 100644 --- a/tests/unit/preprocessor/_multimodel/test_multimodel.py +++ b/tests/unit/preprocessor/_multimodel/test_multimodel.py @@ -968,8 +968,8 @@ def test_ensemble_products(): output1 = PreprocessorFile() output2 = PreprocessorFile() output_products = { - "project_dataset_exp": {"mean": output1}, - "project_dataset2_exp": {"mean": output2}, + "project_exp_dataset": {"mean": output1}, + "project_exp_dataset2": {"mean": output2}, } kwargs = { @@ -1625,7 +1625,7 @@ def test_single_input_ensemble_statistics(products, stat): } products = {PreprocessorFile(cube, attributes=attributes)} output = PreprocessorFile() - output_products = {"project_dataset_exp": {stat_id: output}} + output_products = {"project_exp_dataset": {stat_id: output}} kwargs = { "statistics": [stat], "output_products": output_products, diff --git a/tests/unit/preprocessor/_regrid/test_cordex_target_grid.py b/tests/unit/preprocessor/_regrid/test_cordex_target_grid.py index d357dfccc1..cc6a8dbb6e 100644 --- a/tests/unit/preprocessor/_regrid/test_cordex_target_grid.py +++ b/tests/unit/preprocessor/_regrid/test_cordex_target_grid.py @@ -75,6 +75,10 @@ def test_cordex_stock_cube_eur11(): assert cube.coord(var_name="rlon").has_bounds() assert cube.coord(var_name="lat").has_bounds() assert cube.coord(var_name="lon").has_bounds() + assert cube.coord(var_name="rlat").units == "degrees" + assert cube.coord(var_name="rlon").units == "degrees" + assert cube.coord(var_name="lat").units == "degrees_north" + assert cube.coord(var_name="lon").units == "degrees_east" @pytest.fixture