From 5b0747b3490cfd55c0fcc44eb224b4bf842516d1 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Wed, 16 Mar 2022 12:25:08 +0100 Subject: [PATCH 01/52] Renamed ICON extra_facets file to comply with other files --- doc/develop/fixing_data.rst | 4 ++-- .../extra_facets/{icon-mapping.yml => icon-mappings.yml} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename esmvalcore/_config/extra_facets/{icon-mapping.yml => icon-mappings.yml} (100%) diff --git a/doc/develop/fixing_data.rst b/doc/develop/fixing_data.rst index 02369d12c3..c86ee237e0 100644 --- a/doc/develop/fixing_data.rst +++ b/doc/develop/fixing_data.rst @@ -396,8 +396,8 @@ Please note the duplication of the name ``ICON`` in ``project`` and CMORizing functionalities. Similar to any other fix, the ICON fix allows the use of :ref:`extra -facets`. By default, the file :download:`icon-mapping.yml -` is used for that +facets`. By default, the file :download:`icon-mappings.yml +` is used for that purpose. For some variables, extra facets are necessary; otherwise ESMValTool cannot read them properly. Supported keys for extra facets are: diff --git a/esmvalcore/_config/extra_facets/icon-mapping.yml b/esmvalcore/_config/extra_facets/icon-mappings.yml similarity index 100% rename from esmvalcore/_config/extra_facets/icon-mapping.yml rename to esmvalcore/_config/extra_facets/icon-mappings.yml From e570b657099c348476cbe1b7e72c2d3b69f04ae3 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Wed, 16 Mar 2022 12:26:13 +0100 Subject: [PATCH 02/52] Added EMAC extra_facets file and adapted config-developer.yml --- .../_config/extra_facets/emac-mappings.yml | 203 ++++++++++++++++++ esmvalcore/config-developer.yml | 10 +- 2 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 esmvalcore/_config/extra_facets/emac-mappings.yml diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml new file mode 100644 index 0000000000..32dba94109 --- /dev/null +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -0,0 +1,203 @@ +# Extra facets for native EMAC model output + +# All extra facets for EMAC are optional but might be necessary for some +# variables. Note that if the facet ``channel`` is not given here it has to be +# specified in the recipe. A complete list of supported keys is given in the +# documentation (see ESMValCore/doc/develop/fixing_data.rst). +--- + +EMAC: + '*': + awhea: # non-CMOR variable + raw_name: awhea_ave + channel: Omon + cod_sw_b01: # non-CMOR variable + raw_name: tau_cld_sw_B01_ave + channel: Amon + clivi: + raw_name: xivi_ave + channel: Amon + clt: + raw_name: aclcov_ave + channel: Amon + clwvi: + raw_name: xlvi_ave + channel: Amon + co2mass: + raw_name: MP_CO2_ave + channel: tracer_pdef_gp + evspsbl: + raw_name: evap_ave + channel: Amon + hfls: + raw_name: ahfl_ave + channel: Amon + hfss: + raw_name: ahfs_ave + channel: Amon + lnox: # non-CMOR variable; derived from NOxcg_ave, NOxic_ave + channel: lnox_PaR_T_gp + od550aer: + raw_name: aot_opt_TOT_550_total_ave + channel: Amon + pr: # derived from aprl_ave, aprc_ave, aprs_ave + channel: Amon + prc: + raw_name: aprc_ave + channel: Amon + prl: # non-CMOR variable + raw_name: aprl_ave + channel: Amon + prsn: + raw_name: aprs_ave + channel: Amon + prw: + raw_name: qvi_ave + channel: Amon + rlds: # derived from flxtbot_ave, tradsu_ave + channel: Amon + rlus: + raw_name: tradsu_ave + channel: Amon + rlut: + raw_name: flxttop_ave + channel: Amon + rlutcs: + raw_name: flxtftop_ave + channel: Amon + rsds: # derived from flxsbot_ave, sradsu_ave + channel: Amon + rsdt: # derived from flxstop_ave, srad0u_ave + channel: Amon + rsus: + raw_name: sradsu_ave + channel: Amon + rsut: + raw_name: srad0u_ave + channel: Amon + rsutcs: + raw_name: flxusftop_ave + channel: Amon + rtmt: # derived from flxttop_ave, flxstop_ave + channel: Amon + siconca: + raw_name: seaice_ave + channel: Amon + sithick: + raw_name: siced_ave + channel: Amon + tas: + raw_name: temp2_ave + channel: Amon + tos: + raw_name: tsw + channel: g3b + ts: + raw_name: tsurf_ave + channel: Amon + toz: + channel: Amon + + # Forcings (non-CMOR variables) + ANTHNT_AER_TC_s: # derived from ANTHNT_AER_BC, ANTHNT_AER_OC + channel: import_grid + ANTHNT_CO_s: + raw_name: ANTHNT_CO + channel: import_grid + ANTHNT_NO_s: + raw_name: ANTHNT_NO + channel: import_grid + ANTHNT_SO2_s: + raw_name: ANTHNT_SO2 + channel: import_grid + BB_AER_TC_s: # derived from BB_AER_BC, BB_AER_OC + channel: import_grid + BB_CO_s: + raw_name: BB_CO + channel: import_grid + BB_NO_s: + raw_name: BB_NO + channel: import_grid + BB_SO2_s: + raw_name: BB_SO2 + channel: import_grid + ROAD_AER_BC: + channel: import_grid + ROAD_NO: + channel: import_grid + SHIP_AER_BC_s: + raw_name: SHIP_AER_BC + channel: import_grid + SHIP_NO_s: + raw_name: SHIP_NO + channel: import_grid + SHIP_SO2_s: + raw_name: SHIP_SO2 + channel: import_grid + TN_GHG_CH4: + channel: import_grid + TN_GHG_CO2: + channel: import_grid + TN_GHG_N2O: + channel: import_grid + + # Tracers (non-CMOR variables) + MP_BC_tot: # derived from MP_BC_ks_ave, MP_BC_as_ave, MP_BC_cs_ave, MP_BC_ki_ave + channel: tracer_pdef_gp + MP_CFCl3: + raw_name: MP_CFCl3_ave + channel: tracer_pdef_gp + MP_ClOX: + raw_name: MP_ClOX_ave + channel: tracer_pdef_gp + MP_CH4: + raw_name: MP_CH4_ave + channel: tracer_pdef_gp + MP_CO: + raw_name: MP_CO_ave + channel: tracer_pdef_gp + MP_CO2: + raw_name: MP_CO2_ave + channel: tracer_pdef_gp + MP_DU_tot: # derived from MP_DU_as_ave, MP_DU_cs_ave, MP_DU_ai_ave, MP_DU_ci_ave + channel: tracer_pdef_gp + MP_N2O: + raw_name: MP_N2O_ave + channel: tracer_pdef_gp + MP_NH3: + raw_name: MP_NH3_ave + channel: tracer_pdef_gp + MP_NO: + raw_name: MP_NO_ave + channel: tracer_pdef_gp + MP_NO2: + raw_name: MP_NO2_ave + channel: tracer_pdef_gp + MP_NOX: + raw_name: MP_NOX_ave + channel: tracer_pdef_gp + MP_O3: + raw_name: MP_O3_ave + channel: tracer_pdef_gp + MP_OH: + raw_name: MP_OH_ave + channel: tracer_pdef_gp + MP_S: + raw_name: MP_S_ave + channel: tracer_pdef_gp + MP_SO2: + raw_name: MP_SO2_ave + channel: tracer_pdef_gp + MP_SO4mm_tot: # derived from MP_SO4mm_ns_ave, MP_SO4mm_ks_ave, MP_SO4mm_as_ave, MP_SO4mm_cs_ave + channel: tracer_pdef_gp + MP_SS_tot: # derived from MP_SS_ks_ave, MP_SS_as_ave, MP_SS_cs_ave + channel: tracer_pdef_gp + + +# Elements missing from original mapping table +# Note: mixed channels are not supported yet +# - AIRC_NO_s: {airc_NO: import_grid, geopot_ave: Amon} -> mixed channels +# - VOLC_SO2_s: {VOLC_SO2_SO2: import_grid, geopot_ave: Amon} -> mixed channels + +# TODO: +# - derivation of toz: [DU] -> [m] diff --git a/esmvalcore/config-developer.yml b/esmvalcore/config-developer.yml index 7d01407795..58ea6d40b8 100644 --- a/esmvalcore/config-developer.yml +++ b/esmvalcore/config-developer.yml @@ -119,11 +119,13 @@ ana4mips: # TODO: add cmor_path and table and set cmor_strict to true EMAC: + cmor_strict: false input_dir: - default: '{dataset}' - input_file: '' - output_file: '{dataset}_{ensemble}_{short_name}' - cmor_type: 'CMIP5' + default: '{exp}/{channel}' + input_file: + default: '{exp}*{channel}.nc' + output_file: '{dataset}_{exp}_{channel}_{mip}_{short_name}' + cmor_type: 'CMIP6' CORDEX: input_dir: From f43413a9ad25844f72199999c57420f080b6111e Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Wed, 16 Mar 2022 18:12:29 +0100 Subject: [PATCH 03/52] Added possibility to ignore warnings for certain projects --- esmvalcore/_recipe.py | 11 +++++++++++ esmvalcore/config-developer.yml | 5 +++++ esmvalcore/preprocessor/_io.py | 29 ++++++++++++++++------------- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/esmvalcore/_recipe.py b/esmvalcore/_recipe.py index 2cecc356c5..9c31c62fc3 100644 --- a/esmvalcore/_recipe.py +++ b/esmvalcore/_recipe.py @@ -724,6 +724,16 @@ def _update_multi_dataset_settings(variable, settings): _exclude_dataset(settings, variable, step) +def _update_warning_settings(settings, project): + """Update project-specific warning settings.""" + cfg = get_project_config(project) + if 'ignore_warnings' not in cfg: + return + for (step, ignored_warnings) in cfg['ignore_warnings'].items(): + if step in settings: + settings[step]['ignore_warnings'] = ignored_warnings + + def _get_tag(step, identifier, statistic): # Avoid . in filename for percentiles statistic = statistic.replace('.', '-') @@ -938,6 +948,7 @@ def _get_preprocessor_products(variables, profile, order, ancestor_products, config_user, derive='derive' in profile, ) + _update_warning_settings(settings, variable['project']) _apply_preprocessor_profile(settings, profile) _update_multi_dataset_settings(variable, settings) try: diff --git a/esmvalcore/config-developer.yml b/esmvalcore/config-developer.yml index 58ea6d40b8..3143ec98fd 100644 --- a/esmvalcore/config-developer.yml +++ b/esmvalcore/config-developer.yml @@ -126,6 +126,11 @@ EMAC: default: '{exp}*{channel}.nc' output_file: '{dataset}_{exp}_{channel}_{mip}_{short_name}' cmor_type: 'CMIP6' + ignore_warnings: + load: + - {message: 'Missing CF-netCDF formula term variable .*, referenced by netCDF variable .*', module: iris} + - {message: 'Ignored formula of unrecognised type: .*', module: iris} + # - {message: 'WARNING: _FillValue not used since it\ncannot be safely cast to variable data type', module: iris} CORDEX: input_dir: diff --git a/esmvalcore/preprocessor/_io.py b/esmvalcore/preprocessor/_io.py index 83cacef5ce..db7edbde57 100644 --- a/esmvalcore/preprocessor/_io.py +++ b/esmvalcore/preprocessor/_io.py @@ -110,22 +110,25 @@ def _delete_attributes(iris_object, atts): del iris_object.attributes[att] -def load(file, callback=None): +def load(file, callback=None, ignore_warnings=None): """Load iris cubes from files.""" logger.debug("Loading:\n%s", file) + if ignore_warnings is None: + ignore_warnings = [] + ignore_warnings.append({ + 'message': "Missing CF-netCDF measure variable .*", + 'category': UserWarning, + 'module': 'iris', + }) + ignore_warnings.append({ + 'message': "Ignoring netCDF variable '.*' invalid units '.*'", + 'category': UserWarning, + 'module': 'iris', + }) with catch_warnings(): - filterwarnings( - 'ignore', - message="Missing CF-netCDF measure variable .*", - category=UserWarning, - module='iris', - ) - filterwarnings( - 'ignore', - message="Ignoring netCDF variable '.*' invalid units '.*'", - category=UserWarning, - module='iris', - ) + for warning_kwargs in ignore_warnings: + warning_kwargs.setdefault('action', 'ignore') + filterwarnings(**warning_kwargs) raw_cubes = iris.load_raw(file, callback=callback) logger.debug("Done with loading %s", file) if not raw_cubes: From 014f7a391f4eaa5a401a84c861f80ce24a175d57 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Wed, 16 Mar 2022 18:12:55 +0100 Subject: [PATCH 04/52] First version of EMAC CMORizer --- .../_config/extra_facets/emac-mappings.yml | 10 + esmvalcore/cmor/_fixes/emac/__init__.py | 0 esmvalcore/cmor/_fixes/emac/emac.py | 449 ++++++++++++++++++ 3 files changed, 459 insertions(+) create mode 100644 esmvalcore/cmor/_fixes/emac/__init__.py create mode 100644 esmvalcore/cmor/_fixes/emac/emac.py diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml index 32dba94109..4a5ed210c6 100644 --- a/esmvalcore/_config/extra_facets/emac-mappings.yml +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -193,6 +193,16 @@ EMAC: MP_SS_tot: # derived from MP_SS_ks_ave, MP_SS_as_ave, MP_SS_cs_ave channel: tracer_pdef_gp + # 3D variables + Amon: + ta: # defined on plev19 + raw_name: tm1_p19_ave + channel: Amon + Eday: + ta: # defined on plev19 + raw_name: tm1_p19_ave + channel: Amon + # Elements missing from original mapping table # Note: mixed channels are not supported yet diff --git a/esmvalcore/cmor/_fixes/emac/__init__.py b/esmvalcore/cmor/_fixes/emac/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py new file mode 100644 index 0000000000..ca173190a1 --- /dev/null +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -0,0 +1,449 @@ +"""On-the-fly CMORizer for EMAC.""" + +import logging +import warnings +from datetime import datetime +from pathlib import Path +from shutil import copyfileobj +from urllib.parse import urlparse + +import cf_units +import dask.array as da +import iris +import iris.coords +import iris.cube +import iris.util +import numpy as np +import requests + +from esmvalcore.iris_helpers import ( + # add_leading_dim_to_cube, + date2num, +) + +from ..fix import Fix +from ..shared import add_scalar_height_coord, add_scalar_typesi_coord + +logger = logging.getLogger(__name__) + + +CACHE_DIR = Path.home() / '.esmvaltool' / 'cache' +CACHE_VALIDITY = 7 * 24 * 60 * 60 # [s]; = 1 week +TIMEOUT = 5 * 60 # [s]; = 5 min +GRID_FILE_ATTR = 'grid_file_uri' + + +class EmacFix(Fix): + """Base class for all EMAC fixes.""" + + def get_cube(self, cubes, var_name=None): + """Extract single cube.""" + if var_name is None: + var_name = self.extra_facets.get('raw_name', + self.vardef.short_name) + if not cubes.extract(iris.NameConstraint(var_name=var_name)): + raise ValueError( + f"Variable '{var_name}' used to extract " + f"'{self.vardef.short_name}' is not available in input " + f"file") + return cubes.extract_cube(iris.NameConstraint(var_name=var_name)) + + +class AllVars(EmacFix): + """Fixes for all variables.""" + + # def __init__(self, *args, **kwargs): + # """Initialize fix.""" + # super().__init__(*args, **kwargs) + # self._horizontal_grids = {} + + + def fix_data(self, cube): + """Fix data.""" + # Fix mask by masking all values where the absolute value is greater + # than a given threshold (affects mostly 3D variable) + mask_threshold = 1e20 + cube.data = da.ma.masked_outside( + cube.core_data(), -mask_threshold, mask_threshold, + ) + return cube + + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = self.get_cube(cubes) + + # Fix time + if 'time' in self.vardef.dimensions: + self._fix_time(cube, cubes) + + # Fix pressure levels (considers plev19, plev39, etc.) + for dim_name in self.vardef.dimensions: + if 'plev' in dim_name: + self._fix_plev(cube) + break + + # here since the name of the z-coord varies from variable to variable + # if cube.coords('height'): + # # In case a scalar height is required, remove it here (it will be + # # added later). This step here is designed to fix non-scalar height + # # coordinates. + # # Note: iris.util.squeeze is not used here since it might + # # accidentally squeeze other dimensions + # if (cube.coord('height').shape[0] == 1 and ( + # 'height2m' in self.vardef.dimensions or + # 'height10m' in self.vardef.dimensions)): + # slices = [slice(None)] * cube.ndim + # slices[cube.coord_dims('height')[0]] = 0 + # cube = cube[tuple(slices)] + # cube.remove_coord('height') + # else: + # cube = self._fix_height(cube, cubes) + + # Fix latitude + # lat_idx = None + if 'latitude' in self.vardef.dimensions: + lat_name = self.extra_facets.get('latitude', 'latitude') + # if not cube.coords(lat_name): + # self._add_coord_from_grid_file(cube, 'grid_latitude', lat_name) + self._fix_lat(cube, lat_name) + # lat_idx = cube.coord_dims('latitude') + + # Fix longitude + # lon_idx = None + if 'longitude' in self.vardef.dimensions: + lon_name = self.extra_facets.get('longitude', 'longitude') + # if not cube.coords(lon_name): + # self._add_coord_from_grid_file(cube, 'grid_longitude', + # lon_name) + self._fix_lon(cube, lon_name) + # lon_idx = cube.coord_dims('longitude') + + # Fix cell index for unstructured grid if necessary + # fix_cell_index = all([ + # lat_idx is not None, + # lon_idx is not None, + # lat_idx == lon_idx, + # len(lat_idx) == 1, + # ]) + # if fix_cell_index: + # self._fix_unstructured_cell_index(cube, lat_idx) + + # Fix scalar coordinates + self._fix_scalar_coords(cube) + + # Fix metadata of variable + self._fix_var_metadata(cube) + + return iris.cube.CubeList([cube]) + + # def get_horizontal_grid(self, grid_url): + # """Get horizontal grid.""" + # # If already loaded, return the horizontal grid (cube) + # grid_name = str(grid_url) + # if grid_name in self._horizontal_grids: + # return self._horizontal_grids[grid_name] + + # # Check if grid file has recently been downloaded and load it if + # # possible + # parsed_url = urlparse(grid_url) + # grid_path = CACHE_DIR / Path(parsed_url.path).name + # if grid_path.exists(): + # mtime = grid_path.stat().st_mtime + # now = datetime.now().timestamp() + # age = now - mtime + # if age < CACHE_VALIDITY: + # logger.debug("Using cached ICON grid file '%s'", grid_path) + # self._horizontal_grids[grid_name] = self._load_cubes(grid_path) + # return self._horizontal_grids[grid_name] + # logger.debug("Existing cached ICON grid file '%s' is outdated", + # grid_path) + + # # Download file if necessary + # logger.debug("Attempting to download ICON grid file from '%s' to '%s'", + # grid_url, grid_path) + # with requests.get(grid_url, stream=True, timeout=TIMEOUT) as response: + # response.raise_for_status() + # with open(grid_path, 'wb') as file: + # copyfileobj(response.raw, file) + # logger.info("Successfully downloaded ICON grid file from '%s' to '%s'", + # grid_url, grid_path) + + # self._horizontal_grids[grid_name] = self._load_cubes(grid_path) + # return self._horizontal_grids[grid_name] + + # def _add_coord_from_grid_file(self, cube, coord_name, target_coord_name): + # """Add latitude or longitude coordinate from grid file to cube.""" + # allowed_coord_names = ('grid_latitude', 'grid_longitude') + # if coord_name not in allowed_coord_names: + # raise ValueError( + # f"coord_name must be one of {allowed_coord_names}, got " + # f"'{coord_name}'") + # if GRID_FILE_ATTR not in cube.attributes: + # raise ValueError( + # f"Cube does not contain coordinate '{coord_name}' nor the " + # f"attribute '{GRID_FILE_ATTR}' necessary to download the ICON " + # f"horizontal grid file:\n{cube}") + # grid_file_url = cube.attributes[GRID_FILE_ATTR] + # horizontal_grid = self.get_horizontal_grid(grid_file_url) + + # # Use 'cell_area' as dummy cube to extract coordinates + # # Note: it might be necessary to expand this when more coord_names are + # # supported + # grid_cube = horizontal_grid.extract_cube( + # iris.NameConstraint(var_name='cell_area')) + # coord = grid_cube.coord(coord_name) + + # # Find index of horizontal coordinate (try 'ncells' and unnamed + # # dimension) + # if cube.coords('ncells'): + # coord_dims = cube.coord_dims('ncells') + # else: + # n_unnamed_dimensions = cube.ndim - len(cube.dim_coords) + # if n_unnamed_dimensions != 1: + # raise ValueError( + # f"Cannot determine coordinate dimension for coordinate " + # f"'{coord_name}', cube does not contain coordinate " + # f"'ncells' nor a single unnamed dimension:\n{cube}") + # coord_dims = () + # for idx in range(cube.ndim): + # if not cube.coords(dimensions=idx, dim_coords=True): + # coord_dims = (idx,) + # break + + # coord.standard_name = target_coord_name + # cube.add_aux_coord(coord, coord_dims) + + # def _add_time(self, cube, cubes): + # """Add time coordinate from other cube in cubes.""" + # # Try to find time cube from other cubes and it to target cube + # for other_cube in cubes: + # if not other_cube.coords('time'): + # continue + # time_coord = other_cube.coord('time') + # cube = add_leading_dim_to_cube(cube, time_coord) + # return cube + # raise ValueError( + # f"Cannot add required coordinate 'time' to variable " + # f"'{self.vardef.short_name}', cube and other cubes in file do not " + # f"contain it") + + def _fix_scalar_coords(self, cube): + """Fix scalar coordinates.""" + if 'height2m' in self.vardef.dimensions: + add_scalar_height_coord(cube, 2.0) + if 'height10m' in self.vardef.dimensions: + add_scalar_height_coord(cube, 10.0) + if 'typesi' in self.vardef.dimensions: + add_scalar_typesi_coord(cube, 'sea_ice') + + def _fix_time(self, cube, cubes): + """Fix time coordinate of cube.""" + # Add time coordinate if not already present + # if not cube.coords('time'): + # cube = self._add_time(cube, cubes) + + # Fix metadata + time_coord = cube.coord('time') + time_coord.var_name = 'time' + time_coord.standard_name = 'time' + time_coord.long_name = 'time' + + # Add bounds if possible (not possible if cube only contains single + # time point) + if not time_coord.has_bounds(): + try: + time_coord.guess_bounds() + except ValueError: + pass + + # if 'invalid_units' not in time_coord.attributes: + # return + + # # If necessary, convert invalid time units of the form "day as + # # %Y%m%d.%f" to CF format (e.g., "days since 1850-01-01") + # # Notes: + # # - It might be necessary to expand this to other time formats in the + # # raw file. + # # - This has not been tested with sub-daily data + # time_format = 'day as %Y%m%d.%f' + # t_unit = time_coord.attributes.pop('invalid_units') + # if t_unit != time_format: + # raise ValueError( + # f"Expected time units '{time_format}' in input file, got " + # f"'{t_unit}'") + # new_t_unit = cf_units.Unit('days since 1850-01-01', + # calendar='proleptic_gregorian') + + # new_datetimes = [datetime.strptime(str(dt), '%Y%m%d.%f') for dt in + # time_coord.points] + # new_dt_points = date2num(np.array(new_datetimes), new_t_unit) + + # time_coord.points = new_dt_points + # time_coord.units = new_t_unit + + def _fix_var_metadata(self, cube): + """Fix metadata of variable.""" + if self.vardef.standard_name == '': + cube.standard_name = None + else: + cube.standard_name = self.vardef.standard_name + cube.var_name = self.vardef.short_name + cube.long_name = self.vardef.long_name + if cube.units != self.vardef.units: + cube.convert_units(self.vardef.units) + + # @staticmethod + # def _fix_height(cube, cubes): + # """Fix height coordinate of cube.""" + # if cubes.extract(iris.NameConstraint(var_name='pfull')): + # plev_points_cube = cubes.extract_cube( + # iris.NameConstraint(var_name='pfull')) + # air_pressure_points = plev_points_cube.core_data() + + # # Get bounds from half levels and reshape array + # if cubes.extract(iris.NameConstraint(var_name='phalf')): + # plev_bounds_cube = cubes.extract_cube( + # iris.NameConstraint(var_name='phalf')) + # air_pressure_bounds = plev_bounds_cube.core_data() + # air_pressure_bounds = da.stack( + # (air_pressure_bounds[:, :-1], air_pressure_bounds[:, 1:]), + # axis=-1) + # else: + # air_pressure_bounds = None + + # # Setup air pressure coordinate with correct metadata and add to + # # cube + # air_pressure_coord = iris.coords.AuxCoord( + # air_pressure_points, + # bounds=air_pressure_bounds, + # var_name='plev', + # standard_name='air_pressure', + # long_name='pressure', + # units=plev_points_cube.units, + # attributes={'positive': 'down'}, + # ) + # cube.add_aux_coord(air_pressure_coord, np.arange(cube.ndim)) + + # # Reverse entire cube along height axis so that index 0 is surface + # # level + # cube = iris.util.reverse(cube, 'height') + + # # Fix metadata + # z_coord = cube.coord('height') + # if z_coord.units.is_convertible('m'): + # z_metadata = { + # 'var_name': 'height', + # 'standard_name': 'height', + # 'long_name': 'height', + # 'attributes': {'positive': 'up'}, + # } + # z_coord.convert_units('m') + # else: + # z_metadata = { + # 'var_name': 'model_level', + # 'standard_name': None, + # 'long_name': 'model level number', + # 'units': 'no unit', + # 'attributes': {'positive': 'up'}, + # 'points': np.arange(len(z_coord.points)), + # 'bounds': None, + # } + # for (attr, val) in z_metadata.items(): + # setattr(z_coord, attr, val) + + # return cube + + @staticmethod + def _fix_lat(cube, lat_name): + """Fix latitude coordinate of cube.""" + lat = cube.coord(lat_name) + lat.var_name = 'lat' + lat.standard_name = 'latitude' + lat.long_name = 'latitude' + lat.convert_units('degrees_north') + + # Add bounds if possible (not possible if cube only contains single + # lat point) + if not lat.has_bounds(): + try: + lat.guess_bounds() + except ValueError: + pass + + @staticmethod + def _fix_lon(cube, lon_name): + """Fix longitude coordinate of cube.""" + lon = cube.coord(lon_name) + lon.var_name = 'lon' + lon.standard_name = 'longitude' + lon.long_name = 'longitude' + lon.convert_units('degrees_east') + + # Add bounds if possible (not possible if cube only contains single + # lon point) + if not lon.has_bounds(): + try: + lon.guess_bounds() + except ValueError: + pass + + def _fix_plev(self, cube): + """Fix pressure level coordinate of cube.""" + for coord in cube.coords(): + coord_type = iris.util.guess_coord_axis(coord) + if coord_type != 'Z': + continue + if not coord.units.is_convertible('Pa'): + continue + coord.var_name = 'plev' + coord.standard_name = 'air_pressure' + coord.lon_name = 'pressure' + coord.convert_units('Pa') + return + raise ValueError( + f"Cannot find requested pressure level coordinate for variable " + f"'{self.vardef.short_name}', searched for Z coordinates with " + f"units that are convertible to Pa") + + # @staticmethod + # def _fix_unstructured_cell_index(cube, horizontal_idx): + # """Fix unstructured cell index coordinate.""" + # index_coord = iris.coords.DimCoord( + # np.arange(cube.shape[horizontal_idx[0]]), + # var_name='i', + # long_name=('first spatial index for variables stored on an ' + # 'unstructured grid'), + # units='1', + # ) + # cube.add_dim_coord(index_coord, horizontal_idx) + + # @staticmethod + # def _load_cubes(path): + # """Load cubes and ignore certain warnings.""" + # with warnings.catch_warnings(): + # warnings.filterwarnings( + # 'ignore', + # message="Ignoring netCDF variable .* invalid units .*", + # category=UserWarning, + # module='iris', + # ) + # cubes = iris.load(str(path)) + # return cubes + + +class Siconc(EmacFix): + """Fixes for ``siconc``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + # Note: This fix is called before the AllVars() fix. The wrong var_name + # and units (which need to be %) are fixed in a later step in + # AllVars(). This fix here is necessary to fix the "unknown" units that + # cannot be converted to % in AllVars(). + cube = self.get_cube(cubes) + cube.units = '1' + return cubes + + +Siconca = Siconc From 524d7bf51d981eb49f46d871de5758b6e5c17a4d Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 17 Mar 2022 12:55:45 +0100 Subject: [PATCH 05/52] Removed trailing whitespaces in custom CMOR tables --- esmvalcore/cmor/tables/custom/CMOR_alb.dat | 2 +- .../cmor/tables/custom/CMOR_albDiff.dat | 2 +- .../cmor/tables/custom/CMOR_albDiffiTr13.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_amoc.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_bdalb.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_bhalb.dat | 2 +- .../cmor/tables/custom/CMOR_clhmtisccp.dat | 2 +- .../cmor/tables/custom/CMOR_clhtkisccp.dat | 2 +- .../cmor/tables/custom/CMOR_clisccp.dat | 2 +- .../cmor/tables/custom/CMOR_cllmtisccp.dat | 2 +- .../cmor/tables/custom/CMOR_clltkisccp.dat | 2 +- .../cmor/tables/custom/CMOR_clmmtisccp.dat | 2 +- .../cmor/tables/custom/CMOR_clmtkisccp.dat | 2 +- .../cmor/tables/custom/CMOR_cltStderr.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_dos.dat | 2 +- .../cmor/tables/custom/CMOR_dosStderr.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_fapar.dat | 2 +- .../cmor/tables/custom/CMOR_husStderr.dat | 2 +- .../cmor/tables/custom/CMOR_iwpStderr.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_lvp.dat | 6 +-- esmvalcore/cmor/tables/custom/CMOR_lwcre.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_lwp.dat | 2 +- .../cmor/tables/custom/CMOR_lwpStderr.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_netcre.dat | 2 +- .../tables/custom/CMOR_od550aerStderr.dat | 2 +- .../tables/custom/CMOR_od870aerStderr.dat | 2 +- .../cmor/tables/custom/CMOR_prStderr.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_rlnst.dat | 4 +- .../cmor/tables/custom/CMOR_rlnstcs.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_rluscs.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_rsnst.dat | 2 +- .../cmor/tables/custom/CMOR_rsnstcs.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_rsnt.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_rtnt.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_sm.dat | 2 +- .../cmor/tables/custom/CMOR_smStderr.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_swcre.dat | 2 +- .../cmor/tables/custom/CMOR_tasConf5.dat | 2 +- .../cmor/tables/custom/CMOR_tasConf95.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_tasa.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_tasaga.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_toz.dat | 2 +- .../cmor/tables/custom/CMOR_tozStderr.dat | 2 +- .../cmor/tables/custom/CMOR_tro3prof.dat | 2 +- .../tables/custom/CMOR_tro3profStderr.dat | 2 +- .../cmor/tables/custom/CMOR_tsStderr.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_xch4.dat | 2 +- esmvalcore/cmor/tables/custom/CMOR_xco2.dat | 42 +++++++++---------- 48 files changed, 71 insertions(+), 71 deletions(-) diff --git a/esmvalcore/cmor/tables/custom/CMOR_alb.dat b/esmvalcore/cmor/tables/custom/CMOR_alb.dat index 9fd681155f..ebd6ff122c 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_alb.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_alb.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: 1 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_albDiff.dat b/esmvalcore/cmor/tables/custom/CMOR_albDiff.dat index ec4e30de57..5e890188ec 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_albDiff.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_albDiff.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: 1 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_albDiffiTr13.dat b/esmvalcore/cmor/tables/custom/CMOR_albDiffiTr13.dat index 833addcab5..3ac5009ac0 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_albDiffiTr13.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_albDiffiTr13.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: 1 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_amoc.dat b/esmvalcore/cmor/tables/custom/CMOR_amoc.dat index 0f951078df..84c905db84 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_amoc.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_amoc.dat @@ -6,7 +6,7 @@ modeling_realm: ocean !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: kg s-1 cell_methods: time: mean area: where sea cell_measures: area: areacello diff --git a/esmvalcore/cmor/tables/custom/CMOR_bdalb.dat b/esmvalcore/cmor/tables/custom/CMOR_bdalb.dat index e7cfc63a18..f42a9c3040 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_bdalb.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_bdalb.dat @@ -6,7 +6,7 @@ modeling_realm: land !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: 1 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_bhalb.dat b/esmvalcore/cmor/tables/custom/CMOR_bhalb.dat index 62af7f099f..d62e9d82a7 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_bhalb.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_bhalb.dat @@ -6,7 +6,7 @@ modeling_realm: land !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: 1 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_clhmtisccp.dat b/esmvalcore/cmor/tables/custom/CMOR_clhmtisccp.dat index 588264e55a..504b69164f 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_clhmtisccp.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_clhmtisccp.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: % cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_clhtkisccp.dat b/esmvalcore/cmor/tables/custom/CMOR_clhtkisccp.dat index 080fe2e76a..6ecb43a913 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_clhtkisccp.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_clhtkisccp.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: % cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_clisccp.dat b/esmvalcore/cmor/tables/custom/CMOR_clisccp.dat index b86353b3ed..01ccc580cb 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_clisccp.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_clisccp.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: % cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_cllmtisccp.dat b/esmvalcore/cmor/tables/custom/CMOR_cllmtisccp.dat index de48310ff6..a16279a424 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_cllmtisccp.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_cllmtisccp.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: % cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_clltkisccp.dat b/esmvalcore/cmor/tables/custom/CMOR_clltkisccp.dat index 01affe0170..c15c19d12e 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_clltkisccp.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_clltkisccp.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: % cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_clmmtisccp.dat b/esmvalcore/cmor/tables/custom/CMOR_clmmtisccp.dat index e2fa6bde2c..707709843b 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_clmmtisccp.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_clmmtisccp.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: % cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_clmtkisccp.dat b/esmvalcore/cmor/tables/custom/CMOR_clmtkisccp.dat index 4e3e41ceea..7ec19cf1a4 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_clmtkisccp.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_clmtkisccp.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: % cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_cltStderr.dat b/esmvalcore/cmor/tables/custom/CMOR_cltStderr.dat index 507e62e425..07e2ac41eb 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_cltStderr.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_cltStderr.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: % cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_dos.dat b/esmvalcore/cmor/tables/custom/CMOR_dos.dat index ef24f2e740..46fdf1b8bb 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_dos.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_dos.dat @@ -6,7 +6,7 @@ modeling_realm: land !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: m3 m-3 cell_methods: time: mean area: mean where land cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_dosStderr.dat b/esmvalcore/cmor/tables/custom/CMOR_dosStderr.dat index 41cbe68923..19570fbf52 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_dosStderr.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_dosStderr.dat @@ -6,7 +6,7 @@ modeling_realm: land !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: m3 m-3 cell_methods: time: mean area: mean where land cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_fapar.dat b/esmvalcore/cmor/tables/custom/CMOR_fapar.dat index 70eb3a7f92..1ced2a9caa 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_fapar.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_fapar.dat @@ -6,7 +6,7 @@ modeling_realm: land !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: 1 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_husStderr.dat b/esmvalcore/cmor/tables/custom/CMOR_husStderr.dat index b053d1d4fd..40aad66b1b 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_husStderr.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_husStderr.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: 1 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_iwpStderr.dat b/esmvalcore/cmor/tables/custom/CMOR_iwpStderr.dat index 088b372487..53214f4d9c 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_iwpStderr.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_iwpStderr.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: kg m-2 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_lvp.dat b/esmvalcore/cmor/tables/custom/CMOR_lvp.dat index 383ca48f36..8dc29321e3 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_lvp.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_lvp.dat @@ -6,12 +6,12 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: W m-2 cell_methods: time: mean cell_measures: area: areacella -long_name: Latent Heat Release from Precipitation -comment: +long_name: Latent Heat Release from Precipitation +comment: !---------------------------------- ! Additional variable information: !---------------------------------- diff --git a/esmvalcore/cmor/tables/custom/CMOR_lwcre.dat b/esmvalcore/cmor/tables/custom/CMOR_lwcre.dat index 1088e87bb0..67a613e5b8 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_lwcre.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_lwcre.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: W m-2 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_lwp.dat b/esmvalcore/cmor/tables/custom/CMOR_lwp.dat index 2e4dc38d24..86623eff5b 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_lwp.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_lwp.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: kg m-2 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_lwpStderr.dat b/esmvalcore/cmor/tables/custom/CMOR_lwpStderr.dat index fd2bf63555..451e1f4340 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_lwpStderr.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_lwpStderr.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: kg m-2 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_netcre.dat b/esmvalcore/cmor/tables/custom/CMOR_netcre.dat index 6525abd2b6..689afdf8a9 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_netcre.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_netcre.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: W m-2 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_od550aerStderr.dat b/esmvalcore/cmor/tables/custom/CMOR_od550aerStderr.dat index 5a43465e9a..0afb8500c7 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_od550aerStderr.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_od550aerStderr.dat @@ -6,7 +6,7 @@ modeling_realm: aerosol !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: 1 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_od870aerStderr.dat b/esmvalcore/cmor/tables/custom/CMOR_od870aerStderr.dat index fb8c316ca4..05ddb6697c 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_od870aerStderr.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_od870aerStderr.dat @@ -6,7 +6,7 @@ modeling_realm: aerosol !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: 1 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_prStderr.dat b/esmvalcore/cmor/tables/custom/CMOR_prStderr.dat index 4e8ecd30e7..97bd25b7f5 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_prStderr.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_prStderr.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: kg m-2 s-1 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_rlnst.dat b/esmvalcore/cmor/tables/custom/CMOR_rlnst.dat index 21924bebfa..c903a01d31 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_rlnst.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_rlnst.dat @@ -6,11 +6,11 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: W m-2 cell_methods: time: mean cell_measures: area: areacella -long_name: Net Atmospheric Longwave Cooling +long_name: Net Atmospheric Longwave Cooling comment: to surface and outer space !---------------------------------- ! Additional variable information: diff --git a/esmvalcore/cmor/tables/custom/CMOR_rlnstcs.dat b/esmvalcore/cmor/tables/custom/CMOR_rlnstcs.dat index 5550ddab56..a78160bac8 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_rlnstcs.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_rlnstcs.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: W m-2 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_rluscs.dat b/esmvalcore/cmor/tables/custom/CMOR_rluscs.dat index df4d2601ed..bb254e81a7 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_rluscs.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_rluscs.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: W m-2 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_rsnst.dat b/esmvalcore/cmor/tables/custom/CMOR_rsnst.dat index 7df26c7cdb..58a8d5fd30 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_rsnst.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_rsnst.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: W m-2 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_rsnstcs.dat b/esmvalcore/cmor/tables/custom/CMOR_rsnstcs.dat index 61191c0f5b..7381e2913c 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_rsnstcs.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_rsnstcs.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: W m-2 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_rsnt.dat b/esmvalcore/cmor/tables/custom/CMOR_rsnt.dat index 32a7da45de..f3c84d1d70 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_rsnt.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_rsnt.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: W m-2 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_rtnt.dat b/esmvalcore/cmor/tables/custom/CMOR_rtnt.dat index b3697aa344..3cbd45543c 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_rtnt.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_rtnt.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: W m-2 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_sm.dat b/esmvalcore/cmor/tables/custom/CMOR_sm.dat index 31858bb292..e683997c8a 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_sm.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_sm.dat @@ -6,7 +6,7 @@ modeling_realm: land !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: m3 m-3 cell_methods: time: mean area: mean where land cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_smStderr.dat b/esmvalcore/cmor/tables/custom/CMOR_smStderr.dat index 7da194878c..5652665943 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_smStderr.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_smStderr.dat @@ -6,7 +6,7 @@ modeling_realm: land !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: m3 m-3 cell_methods: time: mean area: mean where land cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_swcre.dat b/esmvalcore/cmor/tables/custom/CMOR_swcre.dat index 096d6ca77e..38caf0a638 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_swcre.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_swcre.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: W m-2 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_tasConf5.dat b/esmvalcore/cmor/tables/custom/CMOR_tasConf5.dat index edfc8134b9..1bf82964e3 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_tasConf5.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_tasConf5.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: K cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_tasConf95.dat b/esmvalcore/cmor/tables/custom/CMOR_tasConf95.dat index 6b7f27b8b4..9e19edd927 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_tasConf95.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_tasConf95.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: K cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_tasa.dat b/esmvalcore/cmor/tables/custom/CMOR_tasa.dat index 1ad03eb64e..c5c74bd931 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_tasa.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_tasa.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: K cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_tasaga.dat b/esmvalcore/cmor/tables/custom/CMOR_tasaga.dat index f62d34c5e4..4f3d88091d 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_tasaga.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_tasaga.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: K cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_toz.dat b/esmvalcore/cmor/tables/custom/CMOR_toz.dat index 6d319a171b..b875dcbe57 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_toz.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_toz.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: DU cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_tozStderr.dat b/esmvalcore/cmor/tables/custom/CMOR_tozStderr.dat index 240247c84c..7d8769bc7c 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_tozStderr.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_tozStderr.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: DU cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_tro3prof.dat b/esmvalcore/cmor/tables/custom/CMOR_tro3prof.dat index efe6e651c0..50edfdeae5 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_tro3prof.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_tro3prof.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: 1e-9 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_tro3profStderr.dat b/esmvalcore/cmor/tables/custom/CMOR_tro3profStderr.dat index d5fc63027d..3018c5b259 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_tro3profStderr.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_tro3profStderr.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: 1e-9 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_tsStderr.dat b/esmvalcore/cmor/tables/custom/CMOR_tsStderr.dat index 4bd6530a17..691dc643f7 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_tsStderr.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_tsStderr.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: K cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_xch4.dat b/esmvalcore/cmor/tables/custom/CMOR_xch4.dat index 779a6da6df..92c9431308 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_xch4.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_xch4.dat @@ -6,7 +6,7 @@ modeling_realm: atmos !---------------------------------- ! Variable attributes: !---------------------------------- -standard_name: +standard_name: units: 1 cell_methods: time: mean cell_measures: area: areacella diff --git a/esmvalcore/cmor/tables/custom/CMOR_xco2.dat b/esmvalcore/cmor/tables/custom/CMOR_xco2.dat index e95148154b..6bc471c8ff 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_xco2.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_xco2.dat @@ -1,22 +1,22 @@ -SOURCE: CMIP5 -!============ -variable_entry: xco2 -!============ -modeling_realm: atmos -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: 1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Column-average Dry-air Mole Fraction of Atmospheric Carbon Dioxide -comment: Satellite retrieved column-average dry-air mole fraction of atmospheric carbon dioxide (XCO2) -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: xco2 -type: real -!---------------------------------- +SOURCE: CMIP5 +!============ +variable_entry: xco2 +!============ +modeling_realm: atmos +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: 1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Column-average Dry-air Mole Fraction of Atmospheric Carbon Dioxide +comment: Satellite retrieved column-average dry-air mole fraction of atmospheric carbon dioxide (XCO2) +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: xco2 +type: real +!---------------------------------- ! \ No newline at end of file From 898099db3ede541af7722d854aaf0b97b405cd16 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 17 Mar 2022 12:59:26 +0100 Subject: [PATCH 06/52] Added newline to end of file for xco2 table --- esmvalcore/cmor/tables/custom/CMOR_xco2.dat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esmvalcore/cmor/tables/custom/CMOR_xco2.dat b/esmvalcore/cmor/tables/custom/CMOR_xco2.dat index 6bc471c8ff..09acb623bf 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_xco2.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_xco2.dat @@ -19,4 +19,4 @@ dimensions: longitude latitude time out_name: xco2 type: real !---------------------------------- -! \ No newline at end of file +! From 70d33c46798b97f0985ee1e2c1654ef5fe0677cc Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 17 Mar 2022 13:09:23 +0100 Subject: [PATCH 07/52] Added custom tables for EMAC variables --- .../cmor/tables/custom/CMOR_AIRC_NO_s.dat | 23 +++++++++++++++++++ .../tables/custom/CMOR_ANTHNT_AER_TC_s.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_ANTHNT_CO_s.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_ANTHNT_NO_s.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_ANTHNT_SO2_s.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_BB_AER_TC_s.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_BB_CO_s.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_BB_NO_s.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_BB_SO2_s.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_MP_BC_as.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_BC_ki.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_BC_ks.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_BC_tot.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_CFCl3.dat | 19 +++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_MP_CH4.dat | 19 +++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_MP_CO.dat | 19 +++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_MP_CO2.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_ClOX.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_DU_ai.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_DU_as.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_DU_ci.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_DU_cs.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_DU_tot.dat | 19 +++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_MP_N2O.dat | 19 +++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_MP_NH3.dat | 19 +++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_MP_NO.dat | 19 +++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_MP_NO2.dat | 19 +++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_MP_NOX.dat | 19 +++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_MP_O3.dat | 19 +++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_MP_OH.dat | 19 +++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_MP_S.dat | 19 +++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_MP_SO2.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_SO4mm_as.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_SO4mm_cs.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_SO4mm_ks.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_SO4mm_ns.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_SO4mm_tot.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_SS_as.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_SS_cs.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_SS_ks.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_MP_SS_tot.dat | 19 +++++++++++++++ .../cmor/tables/custom/CMOR_ROAD_AER_BC.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_ROAD_NO.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_SHIP_AER_BC_s.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_SHIP_NO_s.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_SHIP_SO2_s.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_TN_GHG_CH4.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_TN_GHG_CO2.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_TN_GHG_N2O.dat | 23 +++++++++++++++++++ .../cmor/tables/custom/CMOR_VOLC_SO2_s.dat | 23 +++++++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_awhea.dat | 22 ++++++++++++++++++ .../cmor/tables/custom/CMOR_cod_sw_b01.dat | 23 +++++++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_hfns.dat | 21 +++++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_lnox.dat | 23 +++++++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_prl.dat | 22 ++++++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_rldt.dat | 23 +++++++++++++++++++ esmvalcore/cmor/tables/custom/CMOR_rlnt.dat | 22 ++++++++++++++++++ 57 files changed, 1178 insertions(+) create mode 100644 esmvalcore/cmor/tables/custom/CMOR_AIRC_NO_s.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_ANTHNT_AER_TC_s.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_ANTHNT_CO_s.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_ANTHNT_NO_s.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_ANTHNT_SO2_s.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_BB_AER_TC_s.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_BB_CO_s.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_BB_NO_s.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_BB_SO2_s.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_BC_as.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_BC_ki.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_BC_ks.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_BC_tot.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_CFCl3.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_CH4.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_CO.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_CO2.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_ClOX.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_DU_ai.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_DU_as.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_DU_ci.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_DU_cs.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_DU_tot.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_N2O.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_NH3.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_NO.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_NO2.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_NOX.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_O3.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_OH.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_S.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SO2.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_as.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_cs.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ks.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ns.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_tot.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SS_as.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SS_cs.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SS_ks.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SS_tot.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_ROAD_AER_BC.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_ROAD_NO.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_SHIP_AER_BC_s.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_SHIP_NO_s.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_SHIP_SO2_s.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CH4.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CO2.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_TN_GHG_N2O.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_VOLC_SO2_s.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_awhea.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_cod_sw_b01.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_hfns.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_lnox.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_prl.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_rldt.dat create mode 100644 esmvalcore/cmor/tables/custom/CMOR_rlnt.dat diff --git a/esmvalcore/cmor/tables/custom/CMOR_AIRC_NO_s.dat b/esmvalcore/cmor/tables/custom/CMOR_AIRC_NO_s.dat new file mode 100644 index 0000000000..a0f8d77fa2 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_AIRC_NO_s.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: AIRC_NO_s +!============ +modeling_realm: atmos +frequency: mon +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Sum of NOx Aircraft Anthropogenic Emissions +comment: Integrated over all levels, considering the level thickness; units given as kg(NO2) m-3 s-1 +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: AIRC_NO_s +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_AER_TC_s.dat b/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_AER_TC_s.dat new file mode 100644 index 0000000000..c29ca8f326 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_AER_TC_s.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: ANTHNT_AER_TC_s +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Sum of Anthropogenic Aerosol Total Carbon +comment: Sum of Black Carbon and Organic Carbon; summed over all levels +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: ANTHNT_AER_TC_s +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_CO_s.dat b/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_CO_s.dat new file mode 100644 index 0000000000..5b99736d63 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_CO_s.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: ANTHNT_CO_s +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Sum of Anthropogenic CO +comment: Summed over all levels +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: ANTHNT_CO_s +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_NO_s.dat b/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_NO_s.dat new file mode 100644 index 0000000000..4c82e43a21 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_NO_s.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: ANTHNT_NO_s +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Sum of Anthropogenic NO +comment: Summation over all levels; units are given in kg(NO2) m-2 s-1 +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: ANTHNT_NO_s +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_SO2_s.dat b/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_SO2_s.dat new file mode 100644 index 0000000000..5a54580486 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_SO2_s.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: ANTHNT_SO2_s +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Sum of Anthropogenic SO2 +comment: Summed over all levels +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: ANTHNT_SO2_s +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_BB_AER_TC_s.dat b/esmvalcore/cmor/tables/custom/CMOR_BB_AER_TC_s.dat new file mode 100644 index 0000000000..3e84534af5 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_BB_AER_TC_s.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: BB_AER_TC_s +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Sum of Biomass Burning Aerosol Total Carbon +comment: Sum of BB Black Carbon and BB Organic Carbon; summed over all levels +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: BB_AER_TC_s +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_BB_CO_s.dat b/esmvalcore/cmor/tables/custom/CMOR_BB_CO_s.dat new file mode 100644 index 0000000000..51028dc13e --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_BB_CO_s.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: BB_CO_s +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Sum of Biomass Burning CO +comment: Summed over all levels +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: BB_CO_s +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_BB_NO_s.dat b/esmvalcore/cmor/tables/custom/CMOR_BB_NO_s.dat new file mode 100644 index 0000000000..1d42fba1d9 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_BB_NO_s.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: BB_NO_s +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Sum of Biomass Burning NO +comment: Summation over all levels; units are given in kg(NO) m-2 s-1 +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: BB_NO_s +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_BB_SO2_s.dat b/esmvalcore/cmor/tables/custom/CMOR_BB_SO2_s.dat new file mode 100644 index 0000000000..d150f4388e --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_BB_SO2_s.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: BB_SO2_s +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Sum of Biomass Burning SO2 +comment: Summed over all levels +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: BB_SO2_s +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_BC_as.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_BC_as.dat new file mode 100644 index 0000000000..2c25b76370 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_BC_as.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_BC_as +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of black carbon (mode as) +comment: positive mass of BC_as in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_BC_ki.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_BC_ki.dat new file mode 100644 index 0000000000..0097dda506 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_BC_ki.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_BC_ki +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of black carbon (mode ki) +comment: positive mass of BC_ki in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_BC_ks.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_BC_ks.dat new file mode 100644 index 0000000000..ff0fc9d995 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_BC_ks.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_BC_ks +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of black carbon (mode ks) +comment: positive mass of BC_ks in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_BC_tot.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_BC_tot.dat new file mode 100644 index 0000000000..8195980b2f --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_BC_tot.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_BC_tot +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of black carbon (sum of all aerosol modes) +comment: positive mass of BC_tot in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_CFCl3.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_CFCl3.dat new file mode 100644 index 0000000000..a6a630cb35 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_CFCl3.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_CFCl3 +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of CFCl3 (CFC-11) +comment: positive mass of CFCl3 +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_CH4.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_CH4.dat new file mode 100644 index 0000000000..df89b57004 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_CH4.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_CH4 +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of CH4 +comment: positive mass of CH4 in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_CO.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_CO.dat new file mode 100644 index 0000000000..5ead3362c4 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_CO.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_CO +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of CO +comment: positive mass of CO in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_CO2.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_CO2.dat new file mode 100644 index 0000000000..39e4ec1a95 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_CO2.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_CO2 +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of CO2 +comment: positive mass of CO2 in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_ClOX.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_ClOX.dat new file mode 100644 index 0000000000..de2aa4e60e --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_ClOX.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_ClOX +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of ClOX +comment: positive mass of ClOX +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_DU_ai.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_DU_ai.dat new file mode 100644 index 0000000000..0099b4f5b6 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_DU_ai.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_DU_ai +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of mineral dust (mode ai) +comment: positive mass of DU_ai in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_DU_as.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_DU_as.dat new file mode 100644 index 0000000000..35d396a379 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_DU_as.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_DU_as +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of mineral dust (mode as) +comment: positive mass of DU_as in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_DU_ci.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_DU_ci.dat new file mode 100644 index 0000000000..98317216a4 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_DU_ci.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_DU_ci +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of mineral dust (mode ci) +comment: positive mass of DU_ci in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_DU_cs.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_DU_cs.dat new file mode 100644 index 0000000000..0a6e514a4a --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_DU_cs.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_DU_cs +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of mineral dust (mode cs) +comment: positive mass of DU_cs in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_DU_tot.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_DU_tot.dat new file mode 100644 index 0000000000..bbeddf9354 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_DU_tot.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_DU_tot +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of mineral dust (sum of all aerosol modes) +comment: positive mass of DU_tot in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_N2O.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_N2O.dat new file mode 100644 index 0000000000..41be700a5a --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_N2O.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_N2O +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of N2O +comment: positive mass of N2O in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_NH3.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_NH3.dat new file mode 100644 index 0000000000..a4f682ad35 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_NH3.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_NH3 +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of NH3 +comment: positive mass of NH3 in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_NO.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_NO.dat new file mode 100644 index 0000000000..42e6557ed5 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_NO.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_NO +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of NO +comment: positive mass of NO in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_NO2.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_NO2.dat new file mode 100644 index 0000000000..010d7ab6d6 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_NO2.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_NO2 +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of NO2 +comment: positive mass of NO2 in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_NOX.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_NOX.dat new file mode 100644 index 0000000000..adaf6d9b25 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_NOX.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_NOX +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of NOX (NO+NO2) +comment: positive mass of NOX in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_O3.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_O3.dat new file mode 100644 index 0000000000..04cef2bb44 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_O3.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_O3 +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of O3 +comment: positive mass of O3 in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_OH.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_OH.dat new file mode 100644 index 0000000000..3ac1c7c666 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_OH.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_OH +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of OH +comment: positive mass of OH in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_S.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_S.dat new file mode 100644 index 0000000000..9df65edcb4 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_S.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_S +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of S +comment: positive mass of S in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SO2.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SO2.dat new file mode 100644 index 0000000000..5a2ab05363 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_SO2.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_SO2 +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of SO2 +comment: positive mass of SO2 in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_as.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_as.dat new file mode 100644 index 0000000000..be45560ee4 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_as.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_SO4mm_as +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of aerosol sulfate (mode as) +comment: positive mass of SO4mm_as in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_cs.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_cs.dat new file mode 100644 index 0000000000..4d30deade2 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_cs.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_SO4mm_cs +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of aerosol sulfate (mode cs) +comment: positive mass of SO4mm_cs in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ks.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ks.dat new file mode 100644 index 0000000000..4e58e6c4c7 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ks.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_SO4mm_ks +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of aerosol sulfate (mode ks) +comment: positive mass of SO4mm_ks in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ns.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ns.dat new file mode 100644 index 0000000000..b33f6176f7 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ns.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_SO4mm_ns +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of aerosol sulfate (mode ns) +comment: positive mass of SO4mm_ns in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_tot.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_tot.dat new file mode 100644 index 0000000000..2e2214d588 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_tot.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_SO4mm_tot +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of aerosol sulfate (sum of all aerosol modes) +comment: positive mass of SO4mm_tot in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SS_as.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SS_as.dat new file mode 100644 index 0000000000..2d3cce4673 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_SS_as.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_SS_as +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of sea salt (mode as) +comment: positive mass of SS_as in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SS_cs.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SS_cs.dat new file mode 100644 index 0000000000..c28980dc44 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_SS_cs.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_SS_cs +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of sea salt (mode cs) +comment: positive mass of SS_cs in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SS_ks.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SS_ks.dat new file mode 100644 index 0000000000..129fa10aa8 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_SS_ks.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_SS_ks +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of sea salt (mode ks) +comment: positive mass of SS_ks in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SS_tot.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SS_tot.dat new file mode 100644 index 0000000000..569d0117b9 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_MP_SS_tot.dat @@ -0,0 +1,19 @@ +SOURCE: CMIP6 +!============ +variable_entry: MP_SS_tot +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +long_name: total mass of sea salt (sum of all aerosol modes) +comment: positive mass of SS_tot in kg +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: time +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_ROAD_AER_BC.dat b/esmvalcore/cmor/tables/custom/CMOR_ROAD_AER_BC.dat new file mode 100644 index 0000000000..0fd3c85f5f --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_ROAD_AER_BC.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: ROAD_AER_BC +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Road Aerosol Black Carbon +comment: no summation necessary +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: ROAD_AER_BC +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_ROAD_NO.dat b/esmvalcore/cmor/tables/custom/CMOR_ROAD_NO.dat new file mode 100644 index 0000000000..5eb6d94b9c --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_ROAD_NO.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: ROAD_NO +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Road NO emissions +comment: no summation necessary; units given in kg(NO2) m-2 s-1 +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: ROAD_NO +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_SHIP_AER_BC_s.dat b/esmvalcore/cmor/tables/custom/CMOR_SHIP_AER_BC_s.dat new file mode 100644 index 0000000000..741e2d2d74 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_SHIP_AER_BC_s.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: SHIP_AER_BC_s +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Sum of Ship Aerosol Black Carbon emissions +comment: summed over all levels +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: SHIP_AER_BC_s +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_SHIP_NO_s.dat b/esmvalcore/cmor/tables/custom/CMOR_SHIP_NO_s.dat new file mode 100644 index 0000000000..e75f0726fd --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_SHIP_NO_s.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: SHIP_NO_s +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Sum of Ship NO emissions +comment: Summed over all levels; units given in kg(NO2) m-2 s-1 +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: SHIP_NO_s +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_SHIP_SO2_s.dat b/esmvalcore/cmor/tables/custom/CMOR_SHIP_SO2_s.dat new file mode 100644 index 0000000000..d261982d9b --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_SHIP_SO2_s.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: SHIP_SO2_s +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Sum of Ship SO2 emissions +comment: Summed over all levels +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: SHIP_SO2_s +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CH4.dat b/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CH4.dat new file mode 100644 index 0000000000..de43a33b29 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CH4.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: TN_GHG_CH4 +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: 1.e-9 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Mole Fraction of Methane in Air +comment: no summation necessary +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: TN_GHG_CH4 +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CO2.dat b/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CO2.dat new file mode 100644 index 0000000000..efa05812c3 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CO2.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: TN_GHG_CO2 +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: 1.e-6 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Mole Fraction of Carbon Dioxide in Air +comment: no summation necessary +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: TN_GHG_CO2 +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_N2O.dat b/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_N2O.dat new file mode 100644 index 0000000000..d61720ed87 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_N2O.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: TN_GHG_N2O +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: 1.e-9 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Mole Fraction of Nitrous Oxide in Air +comment: no summation necessary +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: TN_GHG_N2O +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_VOLC_SO2_s.dat b/esmvalcore/cmor/tables/custom/CMOR_VOLC_SO2_s.dat new file mode 100644 index 0000000000..d8a6f2ce8f --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_VOLC_SO2_s.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: VOLC_SO2_s +!============ +modeling_realm: atmos +frequency: mon +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Volcanic SO2 emissions +comment: Integrated over all levels; related to layer thickness +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: VOLC_SO2_s +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_awhea.dat b/esmvalcore/cmor/tables/custom/CMOR_awhea.dat new file mode 100644 index 0000000000..541eeea0ca --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_awhea.dat @@ -0,0 +1,22 @@ +SOURCE: CMIP5 +!============ +variable_entry: awhea +!============ +modeling_realm: atmos +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: W m-2 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Global Mean Net Surface Heat Flux Over Open Water +comment: global mean net surface heat flux over open water +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: awhea +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_cod_sw_b01.dat b/esmvalcore/cmor/tables/custom/CMOR_cod_sw_b01.dat new file mode 100644 index 0000000000..dfc38f6085 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_cod_sw_b01.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: cod_sw_b01 +!============ +modeling_realm: atmos +frequency: mon +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: 1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: cloud optical depth averaged over shortwave band 01 +comment: averaged over cloudy and cloud-free time steps +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: cod_sw_b01 +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_hfns.dat b/esmvalcore/cmor/tables/custom/CMOR_hfns.dat new file mode 100644 index 0000000000..4471defe32 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_hfns.dat @@ -0,0 +1,21 @@ +SOURCE: CMIP6 +!============ +variable_entry: hfns +!============ +modeling_realm: atmos +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: W m-2 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Surface Net Heat Flux +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: hfns +type: real +positive: up +!---------------------------------- diff --git a/esmvalcore/cmor/tables/custom/CMOR_lnox.dat b/esmvalcore/cmor/tables/custom/CMOR_lnox.dat new file mode 100644 index 0000000000..e83193479a --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_lnox.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP6 +!============ +variable_entry: lnox +!============ +modeling_realm: atmos +frequency: 10hr +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg +cell_methods: time: mean +cell_measures: area: areacella +long_name: total NOx lightning emissions +comment: sum of cloud-to-ground (GC) and intra-cloud (IC); given in kg(N) +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: lnox +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_prl.dat b/esmvalcore/cmor/tables/custom/CMOR_prl.dat new file mode 100644 index 0000000000..a3ec596af8 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_prl.dat @@ -0,0 +1,22 @@ +SOURCE: CMIP5 +!============ +variable_entry: prl +!============ +modeling_realm: atmos +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: kg m-2 s-1 +cell_methods: time: mean +cell_measures: area: areacella +long_name: Large Scale Precipitation +comment: large scale precipitation at surface; includes both liquid and solid phases from all types of clouds (both large-scale and convective) +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: prl +type: real +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_rldt.dat b/esmvalcore/cmor/tables/custom/CMOR_rldt.dat new file mode 100644 index 0000000000..92b7114e76 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_rldt.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP5 +!============ +variable_entry: rldt +!============ +modeling_realm: atmos +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: W m-2 +cell_methods: time: mean +cell_measures: area: areacella +long_name: TOA Incident Longwave Radiation +comment: Longwave radiation incident at the top of the atmosphere +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +out_name: rldt +type: real +positive: down +!---------------------------------- +! diff --git a/esmvalcore/cmor/tables/custom/CMOR_rlnt.dat b/esmvalcore/cmor/tables/custom/CMOR_rlnt.dat new file mode 100644 index 0000000000..a4b27e4302 --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_rlnt.dat @@ -0,0 +1,22 @@ +SOURCE: CMIP5 +!============ +variable_entry: rlnt +!============ +modeling_realm: atmos +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: +units: W m-2 +cell_methods: time: mean +cell_measures: area: areacella +long_name: TOA Net downward Longwave Radiation +comment: at the top of the atmosphere (to be compared with satellite measurements) +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +type: real +positive: up +!---------------------------------- +! \ No newline at end of file From 00db42c44e9395bdb7923649feaf7a3183f7eb6e Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 17 Mar 2022 14:26:39 +0100 Subject: [PATCH 08/52] Removed CMOR tables for variables that are not supported yet --- .../cmor/tables/custom/CMOR_AIRC_NO_s.dat | 23 ------------------- .../cmor/tables/custom/CMOR_VOLC_SO2_s.dat | 23 ------------------- 2 files changed, 46 deletions(-) delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_AIRC_NO_s.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_VOLC_SO2_s.dat diff --git a/esmvalcore/cmor/tables/custom/CMOR_AIRC_NO_s.dat b/esmvalcore/cmor/tables/custom/CMOR_AIRC_NO_s.dat deleted file mode 100644 index a0f8d77fa2..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_AIRC_NO_s.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: AIRC_NO_s -!============ -modeling_realm: atmos -frequency: mon -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg m-2 s-1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Sum of NOx Aircraft Anthropogenic Emissions -comment: Integrated over all levels, considering the level thickness; units given as kg(NO2) m-3 s-1 -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: AIRC_NO_s -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_VOLC_SO2_s.dat b/esmvalcore/cmor/tables/custom/CMOR_VOLC_SO2_s.dat deleted file mode 100644 index d8a6f2ce8f..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_VOLC_SO2_s.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: VOLC_SO2_s -!============ -modeling_realm: atmos -frequency: mon -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg m-2 s-1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Volcanic SO2 emissions -comment: Integrated over all levels; related to layer thickness -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: VOLC_SO2_s -type: real -!---------------------------------- -! From 1b515e274057635e3662d10d907e9c907aa19d82 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 17 Mar 2022 14:46:42 +0100 Subject: [PATCH 09/52] Cleaned CMORizer file --- esmvalcore/cmor/_fixes/emac/emac.py | 343 +++------------------------- 1 file changed, 37 insertions(+), 306 deletions(-) diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py index ca173190a1..e29f1ade3f 100644 --- a/esmvalcore/cmor/_fixes/emac/emac.py +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -1,25 +1,11 @@ """On-the-fly CMORizer for EMAC.""" import logging -import warnings -from datetime import datetime -from pathlib import Path -from shutil import copyfileobj -from urllib.parse import urlparse -import cf_units import dask.array as da import iris -import iris.coords import iris.cube import iris.util -import numpy as np -import requests - -from esmvalcore.iris_helpers import ( - # add_leading_dim_to_cube, - date2num, -) from ..fix import Fix from ..shared import add_scalar_height_coord, add_scalar_typesi_coord @@ -27,12 +13,6 @@ logger = logging.getLogger(__name__) -CACHE_DIR = Path.home() / '.esmvaltool' / 'cache' -CACHE_VALIDITY = 7 * 24 * 60 * 60 # [s]; = 1 week -TIMEOUT = 5 * 60 # [s]; = 5 min -GRID_FILE_ATTR = 'grid_file_uri' - - class EmacFix(Fix): """Base class for all EMAC fixes.""" @@ -52,30 +32,23 @@ def get_cube(self, cubes, var_name=None): class AllVars(EmacFix): """Fixes for all variables.""" - # def __init__(self, *args, **kwargs): - # """Initialize fix.""" - # super().__init__(*args, **kwargs) - # self._horizontal_grids = {} - - def fix_data(self, cube): """Fix data.""" # Fix mask by masking all values where the absolute value is greater - # than a given threshold (affects mostly 3D variable) + # than a given threshold (affects mostly 3D variables) mask_threshold = 1e20 cube.data = da.ma.masked_outside( cube.core_data(), -mask_threshold, mask_threshold, ) return cube - def fix_metadata(self, cubes): """Fix metadata.""" cube = self.get_cube(cubes) # Fix time if 'time' in self.vardef.dimensions: - self._fix_time(cube, cubes) + self._fix_time(cube) # Fix pressure levels (considers plev19, plev39, etc.) for dim_name in self.vardef.dimensions: @@ -83,51 +56,15 @@ def fix_metadata(self, cubes): self._fix_plev(cube) break - # here since the name of the z-coord varies from variable to variable - # if cube.coords('height'): - # # In case a scalar height is required, remove it here (it will be - # # added later). This step here is designed to fix non-scalar height - # # coordinates. - # # Note: iris.util.squeeze is not used here since it might - # # accidentally squeeze other dimensions - # if (cube.coord('height').shape[0] == 1 and ( - # 'height2m' in self.vardef.dimensions or - # 'height10m' in self.vardef.dimensions)): - # slices = [slice(None)] * cube.ndim - # slices[cube.coord_dims('height')[0]] = 0 - # cube = cube[tuple(slices)] - # cube.remove_coord('height') - # else: - # cube = self._fix_height(cube, cubes) - # Fix latitude - # lat_idx = None if 'latitude' in self.vardef.dimensions: lat_name = self.extra_facets.get('latitude', 'latitude') - # if not cube.coords(lat_name): - # self._add_coord_from_grid_file(cube, 'grid_latitude', lat_name) self._fix_lat(cube, lat_name) - # lat_idx = cube.coord_dims('latitude') # Fix longitude - # lon_idx = None if 'longitude' in self.vardef.dimensions: lon_name = self.extra_facets.get('longitude', 'longitude') - # if not cube.coords(lon_name): - # self._add_coord_from_grid_file(cube, 'grid_longitude', - # lon_name) self._fix_lon(cube, lon_name) - # lon_idx = cube.coord_dims('longitude') - - # Fix cell index for unstructured grid if necessary - # fix_cell_index = all([ - # lat_idx is not None, - # lon_idx is not None, - # lat_idx == lon_idx, - # len(lat_idx) == 1, - # ]) - # if fix_cell_index: - # self._fix_unstructured_cell_index(cube, lat_idx) # Fix scalar coordinates self._fix_scalar_coords(cube) @@ -137,223 +74,6 @@ def fix_metadata(self, cubes): return iris.cube.CubeList([cube]) - # def get_horizontal_grid(self, grid_url): - # """Get horizontal grid.""" - # # If already loaded, return the horizontal grid (cube) - # grid_name = str(grid_url) - # if grid_name in self._horizontal_grids: - # return self._horizontal_grids[grid_name] - - # # Check if grid file has recently been downloaded and load it if - # # possible - # parsed_url = urlparse(grid_url) - # grid_path = CACHE_DIR / Path(parsed_url.path).name - # if grid_path.exists(): - # mtime = grid_path.stat().st_mtime - # now = datetime.now().timestamp() - # age = now - mtime - # if age < CACHE_VALIDITY: - # logger.debug("Using cached ICON grid file '%s'", grid_path) - # self._horizontal_grids[grid_name] = self._load_cubes(grid_path) - # return self._horizontal_grids[grid_name] - # logger.debug("Existing cached ICON grid file '%s' is outdated", - # grid_path) - - # # Download file if necessary - # logger.debug("Attempting to download ICON grid file from '%s' to '%s'", - # grid_url, grid_path) - # with requests.get(grid_url, stream=True, timeout=TIMEOUT) as response: - # response.raise_for_status() - # with open(grid_path, 'wb') as file: - # copyfileobj(response.raw, file) - # logger.info("Successfully downloaded ICON grid file from '%s' to '%s'", - # grid_url, grid_path) - - # self._horizontal_grids[grid_name] = self._load_cubes(grid_path) - # return self._horizontal_grids[grid_name] - - # def _add_coord_from_grid_file(self, cube, coord_name, target_coord_name): - # """Add latitude or longitude coordinate from grid file to cube.""" - # allowed_coord_names = ('grid_latitude', 'grid_longitude') - # if coord_name not in allowed_coord_names: - # raise ValueError( - # f"coord_name must be one of {allowed_coord_names}, got " - # f"'{coord_name}'") - # if GRID_FILE_ATTR not in cube.attributes: - # raise ValueError( - # f"Cube does not contain coordinate '{coord_name}' nor the " - # f"attribute '{GRID_FILE_ATTR}' necessary to download the ICON " - # f"horizontal grid file:\n{cube}") - # grid_file_url = cube.attributes[GRID_FILE_ATTR] - # horizontal_grid = self.get_horizontal_grid(grid_file_url) - - # # Use 'cell_area' as dummy cube to extract coordinates - # # Note: it might be necessary to expand this when more coord_names are - # # supported - # grid_cube = horizontal_grid.extract_cube( - # iris.NameConstraint(var_name='cell_area')) - # coord = grid_cube.coord(coord_name) - - # # Find index of horizontal coordinate (try 'ncells' and unnamed - # # dimension) - # if cube.coords('ncells'): - # coord_dims = cube.coord_dims('ncells') - # else: - # n_unnamed_dimensions = cube.ndim - len(cube.dim_coords) - # if n_unnamed_dimensions != 1: - # raise ValueError( - # f"Cannot determine coordinate dimension for coordinate " - # f"'{coord_name}', cube does not contain coordinate " - # f"'ncells' nor a single unnamed dimension:\n{cube}") - # coord_dims = () - # for idx in range(cube.ndim): - # if not cube.coords(dimensions=idx, dim_coords=True): - # coord_dims = (idx,) - # break - - # coord.standard_name = target_coord_name - # cube.add_aux_coord(coord, coord_dims) - - # def _add_time(self, cube, cubes): - # """Add time coordinate from other cube in cubes.""" - # # Try to find time cube from other cubes and it to target cube - # for other_cube in cubes: - # if not other_cube.coords('time'): - # continue - # time_coord = other_cube.coord('time') - # cube = add_leading_dim_to_cube(cube, time_coord) - # return cube - # raise ValueError( - # f"Cannot add required coordinate 'time' to variable " - # f"'{self.vardef.short_name}', cube and other cubes in file do not " - # f"contain it") - - def _fix_scalar_coords(self, cube): - """Fix scalar coordinates.""" - if 'height2m' in self.vardef.dimensions: - add_scalar_height_coord(cube, 2.0) - if 'height10m' in self.vardef.dimensions: - add_scalar_height_coord(cube, 10.0) - if 'typesi' in self.vardef.dimensions: - add_scalar_typesi_coord(cube, 'sea_ice') - - def _fix_time(self, cube, cubes): - """Fix time coordinate of cube.""" - # Add time coordinate if not already present - # if not cube.coords('time'): - # cube = self._add_time(cube, cubes) - - # Fix metadata - time_coord = cube.coord('time') - time_coord.var_name = 'time' - time_coord.standard_name = 'time' - time_coord.long_name = 'time' - - # Add bounds if possible (not possible if cube only contains single - # time point) - if not time_coord.has_bounds(): - try: - time_coord.guess_bounds() - except ValueError: - pass - - # if 'invalid_units' not in time_coord.attributes: - # return - - # # If necessary, convert invalid time units of the form "day as - # # %Y%m%d.%f" to CF format (e.g., "days since 1850-01-01") - # # Notes: - # # - It might be necessary to expand this to other time formats in the - # # raw file. - # # - This has not been tested with sub-daily data - # time_format = 'day as %Y%m%d.%f' - # t_unit = time_coord.attributes.pop('invalid_units') - # if t_unit != time_format: - # raise ValueError( - # f"Expected time units '{time_format}' in input file, got " - # f"'{t_unit}'") - # new_t_unit = cf_units.Unit('days since 1850-01-01', - # calendar='proleptic_gregorian') - - # new_datetimes = [datetime.strptime(str(dt), '%Y%m%d.%f') for dt in - # time_coord.points] - # new_dt_points = date2num(np.array(new_datetimes), new_t_unit) - - # time_coord.points = new_dt_points - # time_coord.units = new_t_unit - - def _fix_var_metadata(self, cube): - """Fix metadata of variable.""" - if self.vardef.standard_name == '': - cube.standard_name = None - else: - cube.standard_name = self.vardef.standard_name - cube.var_name = self.vardef.short_name - cube.long_name = self.vardef.long_name - if cube.units != self.vardef.units: - cube.convert_units(self.vardef.units) - - # @staticmethod - # def _fix_height(cube, cubes): - # """Fix height coordinate of cube.""" - # if cubes.extract(iris.NameConstraint(var_name='pfull')): - # plev_points_cube = cubes.extract_cube( - # iris.NameConstraint(var_name='pfull')) - # air_pressure_points = plev_points_cube.core_data() - - # # Get bounds from half levels and reshape array - # if cubes.extract(iris.NameConstraint(var_name='phalf')): - # plev_bounds_cube = cubes.extract_cube( - # iris.NameConstraint(var_name='phalf')) - # air_pressure_bounds = plev_bounds_cube.core_data() - # air_pressure_bounds = da.stack( - # (air_pressure_bounds[:, :-1], air_pressure_bounds[:, 1:]), - # axis=-1) - # else: - # air_pressure_bounds = None - - # # Setup air pressure coordinate with correct metadata and add to - # # cube - # air_pressure_coord = iris.coords.AuxCoord( - # air_pressure_points, - # bounds=air_pressure_bounds, - # var_name='plev', - # standard_name='air_pressure', - # long_name='pressure', - # units=plev_points_cube.units, - # attributes={'positive': 'down'}, - # ) - # cube.add_aux_coord(air_pressure_coord, np.arange(cube.ndim)) - - # # Reverse entire cube along height axis so that index 0 is surface - # # level - # cube = iris.util.reverse(cube, 'height') - - # # Fix metadata - # z_coord = cube.coord('height') - # if z_coord.units.is_convertible('m'): - # z_metadata = { - # 'var_name': 'height', - # 'standard_name': 'height', - # 'long_name': 'height', - # 'attributes': {'positive': 'up'}, - # } - # z_coord.convert_units('m') - # else: - # z_metadata = { - # 'var_name': 'model_level', - # 'standard_name': None, - # 'long_name': 'model level number', - # 'units': 'no unit', - # 'attributes': {'positive': 'up'}, - # 'points': np.arange(len(z_coord.points)), - # 'bounds': None, - # } - # for (attr, val) in z_metadata.items(): - # setattr(z_coord, attr, val) - - # return cube - @staticmethod def _fix_lat(cube, lat_name): """Fix latitude coordinate of cube.""" @@ -406,30 +126,41 @@ def _fix_plev(self, cube): f"'{self.vardef.short_name}', searched for Z coordinates with " f"units that are convertible to Pa") - # @staticmethod - # def _fix_unstructured_cell_index(cube, horizontal_idx): - # """Fix unstructured cell index coordinate.""" - # index_coord = iris.coords.DimCoord( - # np.arange(cube.shape[horizontal_idx[0]]), - # var_name='i', - # long_name=('first spatial index for variables stored on an ' - # 'unstructured grid'), - # units='1', - # ) - # cube.add_dim_coord(index_coord, horizontal_idx) - - # @staticmethod - # def _load_cubes(path): - # """Load cubes and ignore certain warnings.""" - # with warnings.catch_warnings(): - # warnings.filterwarnings( - # 'ignore', - # message="Ignoring netCDF variable .* invalid units .*", - # category=UserWarning, - # module='iris', - # ) - # cubes = iris.load(str(path)) - # return cubes + def _fix_scalar_coords(self, cube): + """Fix scalar coordinates.""" + if 'height2m' in self.vardef.dimensions: + add_scalar_height_coord(cube, 2.0) + if 'height10m' in self.vardef.dimensions: + add_scalar_height_coord(cube, 10.0) + if 'typesi' in self.vardef.dimensions: + add_scalar_typesi_coord(cube, 'sea_ice') + + @staticmethod + def _fix_time(cube): + """Fix time coordinate of cube.""" + time_coord = cube.coord('time') + time_coord.var_name = 'time' + time_coord.standard_name = 'time' + time_coord.long_name = 'time' + + # Add bounds if possible (not possible if cube only contains single + # time point) + if not time_coord.has_bounds(): + try: + time_coord.guess_bounds() + except ValueError: + pass + + def _fix_var_metadata(self, cube): + """Fix metadata of variable.""" + if self.vardef.standard_name == '': + cube.standard_name = None + else: + cube.standard_name = self.vardef.standard_name + cube.var_name = self.vardef.short_name + cube.long_name = self.vardef.long_name + if cube.units != self.vardef.units: + cube.convert_units(self.vardef.units) class Siconc(EmacFix): From 8e1d85a67bef7386693dc80d67817d2883ae5523 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 17 Mar 2022 14:54:50 +0100 Subject: [PATCH 10/52] Added derivation for hfns --- esmvalcore/preprocessor/_derive/hfns.py | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 esmvalcore/preprocessor/_derive/hfns.py diff --git a/esmvalcore/preprocessor/_derive/hfns.py b/esmvalcore/preprocessor/_derive/hfns.py new file mode 100644 index 0000000000..b5d033019e --- /dev/null +++ b/esmvalcore/preprocessor/_derive/hfns.py @@ -0,0 +1,30 @@ +"""Derivation of variable `hfns`.""" + +from iris import NameConstraint + +from ._baseclass import DerivedVariableBase + + +class DerivedVariable(DerivedVariableBase): + """Derivation of variable `hfns`.""" + + # Required variables + required = [ + { + 'short_name': 'hfls', + }, + { + 'short_name': 'hfss', + }, + ] + + @staticmethod + def calculate(cubes): + """Compute surface net heat flux.""" + hfls_cube = cubes.extract_strict(NameConstraint(var_name='hfls')) + hfss_cube = cubes.extract_strict(NameConstraint(var_name='hfss')) + + hfns_cube = hfls_cube + hfss_cube + hfns_cube.units = hfls_cube.units + + return hfns_cube From 0e8c05866f4c2132fea956768511c9de4d4b02ce Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 17 Mar 2022 18:34:23 +0100 Subject: [PATCH 11/52] Added EMAC fixes for many atmospherics and tracer variables --- .../_config/extra_facets/emac-mappings.yml | 12 +- esmvalcore/cmor/_fixes/emac/emac.py | 256 +++++++++++++++++- esmvalcore/cmor/tables/custom/CMOR_lnox.dat | 2 +- 3 files changed, 252 insertions(+), 18 deletions(-) diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml index 4a5ed210c6..427dc7e7fd 100644 --- a/esmvalcore/_config/extra_facets/emac-mappings.yml +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -11,21 +11,20 @@ EMAC: awhea: # non-CMOR variable raw_name: awhea_ave channel: Omon - cod_sw_b01: # non-CMOR variable - raw_name: tau_cld_sw_B01_ave - channel: Amon clivi: raw_name: xivi_ave channel: Amon clt: raw_name: aclcov_ave channel: Amon - clwvi: - raw_name: xlvi_ave + clwvi: # derived from xlvi_ave, xivi_ave channel: Amon co2mass: raw_name: MP_CO2_ave channel: tracer_pdef_gp + cod_sw_b01: # non-CMOR variable + raw_name: tau_cld_sw_B01_ave + channel: Amon evspsbl: raw_name: evap_ave channel: Amon @@ -80,6 +79,9 @@ EMAC: channel: Amon rtmt: # derived from flxttop_ave, flxstop_ave channel: Amon + siconc: + raw_name: seaice_ave + channel: Amon siconca: raw_name: seaice_ave channel: Amon diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py index e29f1ade3f..440e255ff6 100644 --- a/esmvalcore/cmor/_fixes/emac/emac.py +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -1,11 +1,22 @@ -"""On-the-fly CMORizer for EMAC.""" +"""On-the-fly CMORizer for EMAC. + +Note +---- +For many variables, derivations from multiple variables (i.e., an output +variable is calculated from multiple other variables) are necessary. These are +implemented in ``fix_metadata``, not in ``fix_data``, here. The reason for this +is that ``fix_metadata`` takes all cubes (and thus all input variables of the +input file) as argument while ``fix_data`` only takes one cube (the output +variable) as single argument. + +""" import logging import dask.array as da -import iris -import iris.cube import iris.util +from iris import NameConstraint +from iris.cube import CubeList from ..fix import Fix from ..shared import add_scalar_height_coord, add_scalar_typesi_coord @@ -21,12 +32,19 @@ def get_cube(self, cubes, var_name=None): if var_name is None: var_name = self.extra_facets.get('raw_name', self.vardef.short_name) - if not cubes.extract(iris.NameConstraint(var_name=var_name)): + if not cubes.extract(NameConstraint(var_name=var_name)): raise ValueError( f"Variable '{var_name}' used to extract " f"'{self.vardef.short_name}' is not available in input " f"file") - return cubes.extract_cube(iris.NameConstraint(var_name=var_name)) + return cubes.extract_cube(NameConstraint(var_name=var_name)) + + @staticmethod + def sum_over_z_coord(cube): + """Perform sum over Z-coordinate.""" + z_coord = cube.coord(axis='Z') + cube = cube.collapsed(z_coord, iris.analysis.SUM) + return cube class AllVars(EmacFix): @@ -72,7 +90,7 @@ def fix_metadata(self, cubes): # Fix metadata of variable self._fix_var_metadata(cube) - return iris.cube.CubeList([cube]) + return CubeList([cube]) @staticmethod def _fix_lat(cube, lat_name): @@ -123,7 +141,7 @@ def _fix_plev(self, cube): return raise ValueError( f"Cannot find requested pressure level coordinate for variable " - f"'{self.vardef.short_name}', searched for Z coordinates with " + f"'{self.vardef.short_name}', searched for Z-coordinates with " f"units that are convertible to Pa") def _fix_scalar_coords(self, cube): @@ -163,18 +181,232 @@ def _fix_var_metadata(self, cube): cube.convert_units(self.vardef.units) +class Clwvi(EmacFix): + """Fixes for ``clwvi``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + cubes.extract_strict(NameConstraint(var_name='xlvi_ave')) + + cubes.extract_strict(NameConstraint(var_name='xivi_ave')) + ) + cube.var_name = self.vardef.short_name + return CubeList([cube]) + + +class Cod_sw_b01(EmacFix): # noqa: N801 + """Fixes for ``cod_sw_b01``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = self.get_cube(cubes) + cube.units = '1' + return CubeList([cube]) + + def fix_data(self, cube): + """Fix data.""" + return self.sum_over_z_coord(cube) + + +class Evspsbl(EmacFix): + """Fixes for ``evspsbl``.""" + + def fix_data(self, cube): + """Fix data.""" + cube.data = -cube.core_data() + return cube + + +class Lnox(EmacFix): + """Fixes for ``lnox``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + noxcg_cube = cubes.extract_strict(NameConstraint(var_name='NOxcg_ave')) + noxic_cube = cubes.extract_strict(NameConstraint(var_name='NOxic_ave')) + + # Fix units + noxcg_cube.units = 'kg' + noxic_cube.units = 'kg' + noxcg_cube = noxcg_cube.collapsed(['longitude', 'latitude'], + iris.analysis.SUM) + noxic_cube = noxic_cube.collapsed(['longitude', 'latitude'], + iris.analysis.SUM) + + # Calculate lnox + timestep = float(noxcg_cube.attributes['GCM_timestep']) + cube = (noxcg_cube + noxic_cube) / timestep * 365.0 * 24.0 * 3600.0 + + return CubeList([cube]) + + +class MP_BC_tot(EmacFix): # noqa: N801 + """Fixes for ``MP_BC_tot``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + cubes.extract_strict(NameConstraint(var_name='MP_BC_ki_ave')) + + cubes.extract_strict(NameConstraint(var_name='MP_BC_ks_ave')) + + cubes.extract_strict(NameConstraint(var_name='MP_BC_as_ave')) + + cubes.extract_strict(NameConstraint(var_name='MP_BC_cs_ave')) + ) + cube.var_name = self.vardef.short_name + return CubeList([cube]) + + +class MP_DU_tot(EmacFix): # noqa: N801 + """Fixes for ``MP_DU_tot``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + cubes.extract_strict(NameConstraint(var_name='MP_DU_ai_ave')) + + cubes.extract_strict(NameConstraint(var_name='MP_DU_as_ave')) + + cubes.extract_strict(NameConstraint(var_name='MP_DU_ci_ave')) + + cubes.extract_strict(NameConstraint(var_name='MP_DU_cs_ave')) + ) + cube.var_name = self.vardef.short_name + return CubeList([cube]) + + +class MP_SO4mm_tot(EmacFix): # noqa: N801 + """Fixes for ``MP_SO4mm_tot``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + cubes.extract_strict(NameConstraint(var_name='MP_SO4mm_ns_ave')) + + cubes.extract_strict(NameConstraint(var_name='MP_SO4mm_ks_ave')) + + cubes.extract_strict(NameConstraint(var_name='MP_SO4mm_as_ave')) + + cubes.extract_strict(NameConstraint(var_name='MP_SO4mm_cs_ave')) + ) + cube.var_name = self.vardef.short_name + return CubeList([cube]) + + +class MP_SS_tot(EmacFix): # noqa: N801 + """Fixes for ``MP_SS_tot``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + cubes.extract_strict(NameConstraint(var_name='MP_SS_ks_ave')) + + cubes.extract_strict(NameConstraint(var_name='MP_SS_as_ave')) + + cubes.extract_strict(NameConstraint(var_name='MP_SS_cs_ave')) + ) + cube.var_name = self.vardef.short_name + return CubeList([cube]) + + +Od550aer = Cod_sw_b01 + + +class Pr(EmacFix): + """Fixes for ``pr``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + cubes.extract_strict(NameConstraint(var_name='aprl_ave')) + + cubes.extract_strict(NameConstraint(var_name='aprc_ave')) + + cubes.extract_strict(NameConstraint(var_name='aprs_ave')) + ) + cube.var_name = self.vardef.short_name + return CubeList([cube]) + + +class Rlds(EmacFix): + """Fixes for ``rlds``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + cubes.extract_strict(NameConstraint(var_name='flxtbot_ave')) - + cubes.extract_strict(NameConstraint(var_name='tradsu_ave')) + ) + cube.var_name = self.vardef.short_name + return CubeList([cube]) + + +Rlus = Evspsbl + + +Rlut = Evspsbl + + +class Rsds(EmacFix): + """Fixes for ``rsds``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + cubes.extract_strict(NameConstraint(var_name='flxsbot_ave')) - + cubes.extract_strict(NameConstraint(var_name='sradsu_ave')) + ) + cube.var_name = self.vardef.short_name + return CubeList([cube]) + + +class Rsdt(EmacFix): + """Fixes for ``rsdt``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + cubes.extract_strict(NameConstraint(var_name='flxstop_ave')) - + cubes.extract_strict(NameConstraint(var_name='srad0u_ave')) + ) + cube.var_name = self.vardef.short_name + return CubeList([cube]) + + +Rsus = Evspsbl + + +Rsut = Evspsbl + + +class Rtmt(EmacFix): + """Fixes for ``rtmt``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + cubes.extract_strict(NameConstraint(var_name='flxttop_ave')) + + cubes.extract_strict(NameConstraint(var_name='flxstop_ave')) + ) + cube.var_name = self.vardef.short_name + return CubeList([cube]) + + class Siconc(EmacFix): """Fixes for ``siconc``.""" def fix_metadata(self, cubes): """Fix metadata.""" - # Note: This fix is called before the AllVars() fix. The wrong var_name - # and units (which need to be %) are fixed in a later step in - # AllVars(). This fix here is necessary to fix the "unknown" units that - # cannot be converted to % in AllVars(). cube = self.get_cube(cubes) cube.units = '1' - return cubes + return CubeList([cube]) Siconca = Siconc + + +class Sithick(EmacFix): + """Fixes for ``sithick``.""" + + def fix_data(self, cube): + """Fix data.""" + cube.data = da.ma.masked_equal(cube.core_data(), 0.0) + return cube + + +class Tosga(EmacFix): + """Fixes for ``tosga``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = self.get_cube(cubes) + cube.units = 'celsius' + return CubeList([cube]) diff --git a/esmvalcore/cmor/tables/custom/CMOR_lnox.dat b/esmvalcore/cmor/tables/custom/CMOR_lnox.dat index e83193479a..a82cec2b54 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_lnox.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_lnox.dat @@ -16,7 +16,7 @@ comment: sum of cloud-to-ground (GC) and intra-cloud (IC); given in kg !---------------------------------- ! Additional variable information: !---------------------------------- -dimensions: longitude latitude time +dimensions: time out_name: lnox type: real !---------------------------------- From ccb53b8e863c5e5d6bb882b362827494166b3c20 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Fri, 18 Mar 2022 13:57:11 +0100 Subject: [PATCH 12/52] Slightly extended and cleaned EMAC CMORizer --- .../_config/extra_facets/emac-mappings.yml | 14 +- esmvalcore/cmor/_fixes/emac/_base_fixes.py | 64 +++++++ esmvalcore/cmor/_fixes/emac/emac.py | 162 +++++++++--------- esmvalcore/cmor/_fixes/shared.py | 17 ++ esmvalcore/preprocessor/_derive/hfns.py | 25 +-- 5 files changed, 184 insertions(+), 98 deletions(-) create mode 100644 esmvalcore/cmor/_fixes/emac/_base_fixes.py diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml index 427dc7e7fd..cb51011538 100644 --- a/esmvalcore/_config/extra_facets/emac-mappings.yml +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -31,6 +31,8 @@ EMAC: hfls: raw_name: ahfl_ave channel: Amon + hfns: # ESMValCore-derivation + channel: Amon hfss: raw_name: ahfs_ave channel: Amon @@ -38,7 +40,7 @@ EMAC: channel: lnox_PaR_T_gp od550aer: raw_name: aot_opt_TOT_550_total_ave - channel: Amon + channel: AERmon pr: # derived from aprl_ave, aprc_ave, aprs_ave channel: Amon prc: @@ -55,6 +57,8 @@ EMAC: channel: Amon rlds: # derived from flxtbot_ave, tradsu_ave channel: Amon + rlns: # ESMValCore-derivation + channel: Amon rlus: raw_name: tradsu_ave channel: Amon @@ -68,6 +72,10 @@ EMAC: channel: Amon rsdt: # derived from flxstop_ave, srad0u_ave channel: Amon + rsns: # ESMValCore-derivation + channel: Amon + rsnt: # ESMValCore-derivation + channel: Amon rsus: raw_name: sradsu_ave channel: Amon @@ -94,11 +102,11 @@ EMAC: tos: raw_name: tsw channel: g3b + toz: + channel: column ts: raw_name: tsurf_ave channel: Amon - toz: - channel: Amon # Forcings (non-CMOR variables) ANTHNT_AER_TC_s: # derived from ANTHNT_AER_BC, ANTHNT_AER_OC diff --git a/esmvalcore/cmor/_fixes/emac/_base_fixes.py b/esmvalcore/cmor/_fixes/emac/_base_fixes.py new file mode 100644 index 0000000000..9bed2db84d --- /dev/null +++ b/esmvalcore/cmor/_fixes/emac/_base_fixes.py @@ -0,0 +1,64 @@ +"""Fix base classes for EMAC on-the-fly CMORizer.""" + +import logging + +import iris.analysis +from iris import NameConstraint +from iris.cube import CubeList + +from ..fix import Fix + +logger = logging.getLogger(__name__) + + +class EmacFix(Fix): + """Base class for all EMAC fixes.""" + + def get_cube(self, cubes, var_name=None): + """Extract single cube.""" + if var_name is None: + var_name = self.extra_facets.get('raw_name', + self.vardef.short_name) + if not cubes.extract(NameConstraint(var_name=var_name)): + raise ValueError( + f"Variable '{var_name}' used to extract " + f"'{self.vardef.short_name}' is not available in input " + f"file") + return cubes.extract_cube(NameConstraint(var_name=var_name)) + + +class NegateData(EmacFix): + """Base fix to negate data.""" + + def fix_data(self, cube): + """Fix data.""" + cube.data = -cube.core_data() + return cube + + +class SetUnitsTo1(EmacFix): + """Base fix to set units to '1'.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = self.get_cube(cubes) + cube.units = '1' + return CubeList([cube]) + + +class SetUnitsTo1SumOverZ(EmacFix): + """Base fix to set units to '1' and sum over Z-coordinate.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = self.get_cube(cubes) + cube.units = '1' + cube = self.sum_over_z_coord(cube) + return CubeList([cube]) + + @staticmethod + def sum_over_z_coord(cube): + """Perform sum over Z-coordinate.""" + z_coord = cube.coord(axis='Z') + cube = cube.collapsed(z_coord, iris.analysis.SUM) + return cube diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py index 440e255ff6..6f36e93682 100644 --- a/esmvalcore/cmor/_fixes/emac/emac.py +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -14,37 +14,24 @@ import logging import dask.array as da +import iris.analysis import iris.util from iris import NameConstraint from iris.cube import CubeList -from ..fix import Fix -from ..shared import add_scalar_height_coord, add_scalar_typesi_coord +from ..shared import ( + add_scalar_height_coord, + add_scalar_lambda550nm_coord, + add_scalar_typesi_coord, +) +from ._base_fixes import EmacFix, NegateData, SetUnitsTo1, SetUnitsTo1SumOverZ logger = logging.getLogger(__name__) -class EmacFix(Fix): - """Base class for all EMAC fixes.""" - - def get_cube(self, cubes, var_name=None): - """Extract single cube.""" - if var_name is None: - var_name = self.extra_facets.get('raw_name', - self.vardef.short_name) - if not cubes.extract(NameConstraint(var_name=var_name)): - raise ValueError( - f"Variable '{var_name}' used to extract " - f"'{self.vardef.short_name}' is not available in input " - f"file") - return cubes.extract_cube(NameConstraint(var_name=var_name)) - - @staticmethod - def sum_over_z_coord(cube): - """Perform sum over Z-coordinate.""" - z_coord = cube.coord(axis='Z') - cube = cube.collapsed(z_coord, iris.analysis.SUM) - return cube +INVALID_UNITS = { + 'kg/m**2s': 'kg m-2 s-1', +} class AllVars(EmacFix): @@ -150,6 +137,8 @@ def _fix_scalar_coords(self, cube): add_scalar_height_coord(cube, 2.0) if 'height10m' in self.vardef.dimensions: add_scalar_height_coord(cube, 10.0) + if 'lambda550nm' in self.vardef.dimensions: + add_scalar_lambda550nm_coord(cube) if 'typesi' in self.vardef.dimensions: add_scalar_typesi_coord(cube, 'sea_ice') @@ -177,6 +166,20 @@ def _fix_var_metadata(self, cube): cube.standard_name = self.vardef.standard_name cube.var_name = self.vardef.short_name cube.long_name = self.vardef.long_name + + # Fix units + if 'invalid_units' in cube.attributes: + invalid_units = cube.attributes.pop('invalid_units') + new_units = INVALID_UNITS.get( + invalid_units, + invalid_units.replace('**', '^'), + ) + try: + cube.units = new_units + except ValueError as exc: + raise ValueError( + f"Failed to fix invalid units '{invalid_units}' for " + f"variable '{self.vardef.short_name}'") from exc if cube.units != self.vardef.units: cube.convert_units(self.vardef.units) @@ -187,34 +190,20 @@ class Clwvi(EmacFix): def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_strict(NameConstraint(var_name='xlvi_ave')) + - cubes.extract_strict(NameConstraint(var_name='xivi_ave')) + cubes.extract_cube(NameConstraint(var_name='xlvi_ave')) + + cubes.extract_cube(NameConstraint(var_name='xivi_ave')) ) cube.var_name = self.vardef.short_name return CubeList([cube]) -class Cod_sw_b01(EmacFix): # noqa: N801 - """Fixes for ``cod_sw_b01``.""" - - def fix_metadata(self, cubes): - """Fix metadata.""" - cube = self.get_cube(cubes) - cube.units = '1' - return CubeList([cube]) +Cod_sw_b01 = SetUnitsTo1SumOverZ # noqa: N801 - def fix_data(self, cube): - """Fix data.""" - return self.sum_over_z_coord(cube) +Clt = SetUnitsTo1 -class Evspsbl(EmacFix): - """Fixes for ``evspsbl``.""" - def fix_data(self, cube): - """Fix data.""" - cube.data = -cube.core_data() - return cube +Evspsbl = NegateData class Lnox(EmacFix): @@ -222,8 +211,8 @@ class Lnox(EmacFix): def fix_metadata(self, cubes): """Fix metadata.""" - noxcg_cube = cubes.extract_strict(NameConstraint(var_name='NOxcg_ave')) - noxic_cube = cubes.extract_strict(NameConstraint(var_name='NOxic_ave')) + noxcg_cube = cubes.extract_cube(NameConstraint(var_name='NOxcg_ave')) + noxic_cube = cubes.extract_cube(NameConstraint(var_name='NOxic_ave')) # Fix units noxcg_cube.units = 'kg' @@ -246,10 +235,10 @@ class MP_BC_tot(EmacFix): # noqa: N801 def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_strict(NameConstraint(var_name='MP_BC_ki_ave')) + - cubes.extract_strict(NameConstraint(var_name='MP_BC_ks_ave')) + - cubes.extract_strict(NameConstraint(var_name='MP_BC_as_ave')) + - cubes.extract_strict(NameConstraint(var_name='MP_BC_cs_ave')) + cubes.extract_cube(NameConstraint(var_name='MP_BC_ki_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_BC_ks_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_BC_as_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_BC_cs_ave')) ) cube.var_name = self.vardef.short_name return CubeList([cube]) @@ -261,10 +250,10 @@ class MP_DU_tot(EmacFix): # noqa: N801 def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_strict(NameConstraint(var_name='MP_DU_ai_ave')) + - cubes.extract_strict(NameConstraint(var_name='MP_DU_as_ave')) + - cubes.extract_strict(NameConstraint(var_name='MP_DU_ci_ave')) + - cubes.extract_strict(NameConstraint(var_name='MP_DU_cs_ave')) + cubes.extract_cube(NameConstraint(var_name='MP_DU_ai_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_DU_as_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_DU_ci_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_DU_cs_ave')) ) cube.var_name = self.vardef.short_name return CubeList([cube]) @@ -276,10 +265,10 @@ class MP_SO4mm_tot(EmacFix): # noqa: N801 def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_strict(NameConstraint(var_name='MP_SO4mm_ns_ave')) + - cubes.extract_strict(NameConstraint(var_name='MP_SO4mm_ks_ave')) + - cubes.extract_strict(NameConstraint(var_name='MP_SO4mm_as_ave')) + - cubes.extract_strict(NameConstraint(var_name='MP_SO4mm_cs_ave')) + cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_ns_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_ks_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_as_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_cs_ave')) ) cube.var_name = self.vardef.short_name return CubeList([cube]) @@ -291,15 +280,15 @@ class MP_SS_tot(EmacFix): # noqa: N801 def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_strict(NameConstraint(var_name='MP_SS_ks_ave')) + - cubes.extract_strict(NameConstraint(var_name='MP_SS_as_ave')) + - cubes.extract_strict(NameConstraint(var_name='MP_SS_cs_ave')) + cubes.extract_cube(NameConstraint(var_name='MP_SS_ks_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_SS_as_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_SS_cs_ave')) ) cube.var_name = self.vardef.short_name return CubeList([cube]) -Od550aer = Cod_sw_b01 +Od550aer = SetUnitsTo1SumOverZ class Pr(EmacFix): @@ -308,9 +297,9 @@ class Pr(EmacFix): def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_strict(NameConstraint(var_name='aprl_ave')) + - cubes.extract_strict(NameConstraint(var_name='aprc_ave')) + - cubes.extract_strict(NameConstraint(var_name='aprs_ave')) + cubes.extract_cube(NameConstraint(var_name='aprl_ave')) + + cubes.extract_cube(NameConstraint(var_name='aprc_ave')) + + cubes.extract_cube(NameConstraint(var_name='aprs_ave')) ) cube.var_name = self.vardef.short_name return CubeList([cube]) @@ -322,17 +311,17 @@ class Rlds(EmacFix): def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_strict(NameConstraint(var_name='flxtbot_ave')) - - cubes.extract_strict(NameConstraint(var_name='tradsu_ave')) + cubes.extract_cube(NameConstraint(var_name='flxtbot_ave')) - + cubes.extract_cube(NameConstraint(var_name='tradsu_ave')) ) cube.var_name = self.vardef.short_name return CubeList([cube]) -Rlus = Evspsbl +Rlus = NegateData -Rlut = Evspsbl +Rlut = NegateData class Rsds(EmacFix): @@ -341,8 +330,8 @@ class Rsds(EmacFix): def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_strict(NameConstraint(var_name='flxsbot_ave')) - - cubes.extract_strict(NameConstraint(var_name='sradsu_ave')) + cubes.extract_cube(NameConstraint(var_name='flxsbot_ave')) - + cubes.extract_cube(NameConstraint(var_name='sradsu_ave')) ) cube.var_name = self.vardef.short_name return CubeList([cube]) @@ -354,17 +343,17 @@ class Rsdt(EmacFix): def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_strict(NameConstraint(var_name='flxstop_ave')) - - cubes.extract_strict(NameConstraint(var_name='srad0u_ave')) + cubes.extract_cube(NameConstraint(var_name='flxstop_ave')) - + cubes.extract_cube(NameConstraint(var_name='srad0u_ave')) ) cube.var_name = self.vardef.short_name return CubeList([cube]) -Rsus = Evspsbl +Rsus = NegateData -Rsut = Evspsbl +Rsut = NegateData class Rtmt(EmacFix): @@ -373,24 +362,17 @@ class Rtmt(EmacFix): def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_strict(NameConstraint(var_name='flxttop_ave')) + - cubes.extract_strict(NameConstraint(var_name='flxstop_ave')) + cubes.extract_cube(NameConstraint(var_name='flxttop_ave')) + + cubes.extract_cube(NameConstraint(var_name='flxstop_ave')) ) cube.var_name = self.vardef.short_name return CubeList([cube]) -class Siconc(EmacFix): - """Fixes for ``siconc``.""" - - def fix_metadata(self, cubes): - """Fix metadata.""" - cube = self.get_cube(cubes) - cube.units = '1' - return CubeList([cube]) +Siconc = SetUnitsTo1 -Siconca = Siconc +Siconca = SetUnitsTo1 class Sithick(EmacFix): @@ -410,3 +392,15 @@ def fix_metadata(self, cubes): cube = self.get_cube(cubes) cube.units = 'celsius' return CubeList([cube]) + + +class Toz(EmacFix): + """Fixes for ``tosga``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + # Note: 1 mm = 100 DU + cube = self.get_cube(cubes) + cube.data = cube.core_data() / 100.0 + cube.units = 'mm' + return CubeList([cube]) diff --git a/esmvalcore/cmor/_fixes/shared.py b/esmvalcore/cmor/_fixes/shared.py index fbace5d1e7..a68d6042c4 100644 --- a/esmvalcore/cmor/_fixes/shared.py +++ b/esmvalcore/cmor/_fixes/shared.py @@ -147,6 +147,23 @@ def add_scalar_height_coord(cube, height=2.0): return cube +def add_scalar_lambda550nm_coord(cube): + """Add scalar coordinate 'lambda550nm'.""" + logger.debug("Adding lambda550nm coordinate") + lambda550nm_coord = iris.coords.AuxCoord( + 550.0, + var_name='wavelength', + standard_name='radiation_wavelength', + long_name='Radiation Wavelength 550 nanometers', + units='nm', + ) + try: + cube.coord('wavelength') + except iris.exceptions.CoordinateNotFoundError: + cube.add_aux_coord(lambda550nm_coord, ()) + return cube + + def add_scalar_typeland_coord(cube, value='default'): """Add scalar coordinate 'typeland' with value of `value`.""" logger.debug("Adding typeland coordinate (%s)", value) diff --git a/esmvalcore/preprocessor/_derive/hfns.py b/esmvalcore/preprocessor/_derive/hfns.py index b5d033019e..5bbc93cc53 100644 --- a/esmvalcore/preprocessor/_derive/hfns.py +++ b/esmvalcore/preprocessor/_derive/hfns.py @@ -8,21 +8,24 @@ class DerivedVariable(DerivedVariableBase): """Derivation of variable `hfns`.""" - # Required variables - required = [ - { - 'short_name': 'hfls', - }, - { - 'short_name': 'hfss', - }, - ] + @staticmethod + def required(project): + """Declare the variables needed for derivation.""" + required = [ + { + 'short_name': 'hfls', + }, + { + 'short_name': 'hfss', + }, + ] + return required @staticmethod def calculate(cubes): """Compute surface net heat flux.""" - hfls_cube = cubes.extract_strict(NameConstraint(var_name='hfls')) - hfss_cube = cubes.extract_strict(NameConstraint(var_name='hfss')) + hfls_cube = cubes.extract_cube(NameConstraint(var_name='hfls')) + hfss_cube = cubes.extract_cube(NameConstraint(var_name='hfss')) hfns_cube = hfls_cube + hfss_cube hfns_cube.units = hfls_cube.units From efc4031cbd5ca59abdf6f7dc3037ecf7735161e6 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Mon, 28 Mar 2022 18:08:19 +0200 Subject: [PATCH 13/52] Added doc for filterwarnigns --- doc/quickstart/configure.rst | 29 +++++++++++++++++++ .../_config/extra_facets/emac-mappings.yml | 3 -- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/doc/quickstart/configure.rst b/doc/quickstart/configure.rst index c2c944c9c6..39f5c5c897 100644 --- a/doc/quickstart/configure.rst +++ b/doc/quickstart/configure.rst @@ -511,6 +511,35 @@ related to CMOR table settings available: to get the name of the file containing the ``mip`` table. Defaults to the value provided in ``cmor_type``. +.. _filterwarnings_config-developer: + +Filter preprocessor warnings +---------------------------- + +ESMValCore allows ignoring specific warnings for each project seperately. +This is particularly useful for native models which do not follow the CMOR +standard by default and consequently produce a lot of warnings when loaded. +This can be configured for each step of the preprocessing chain that supports +this feature. + +Supported preprocessor steps: + +* :func:`~esmvalcore.preprocessor.load` + +Here is an example to ignore specific warnings for the step ``load`` for all +datasets of project ``EMAC``: + +.. code-block:: yaml + + ignore_warnings: + load: + - {message: 'Missing CF-netCDF formula term variable .*, referenced by netCDF variable .*', module: iris} + - {message: 'Ignored formula of unrecognised type: .*', module: iris} + +The keyword arguments specified in the list items are directly passed to +:func:`warnings.filterwarnings`. + + .. _configure_native_models: Configuring native models and observation data sets diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml index cb51011538..b6704a3812 100644 --- a/esmvalcore/_config/extra_facets/emac-mappings.yml +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -218,6 +218,3 @@ EMAC: # Note: mixed channels are not supported yet # - AIRC_NO_s: {airc_NO: import_grid, geopot_ave: Amon} -> mixed channels # - VOLC_SO2_s: {VOLC_SO2_SO2: import_grid, geopot_ave: Amon} -> mixed channels - -# TODO: -# - derivation of toz: [DU] -> [m] From f57e34de7b19534245af4570530bec3fb350f46b Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Tue, 29 Mar 2022 09:48:38 +0200 Subject: [PATCH 14/52] Optimized doc --- doc/quickstart/configure.rst | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/doc/quickstart/configure.rst b/doc/quickstart/configure.rst index 39f5c5c897..0e31e39484 100644 --- a/doc/quickstart/configure.rst +++ b/doc/quickstart/configure.rst @@ -516,29 +516,32 @@ related to CMOR table settings available: Filter preprocessor warnings ---------------------------- -ESMValCore allows ignoring specific warnings for each project seperately. +It is possible to ignore specific warnings of the preprocessor for a given +``project``. This is particularly useful for native models which do not follow the CMOR -standard by default and consequently produce a lot of warnings when loaded. -This can be configured for each step of the preprocessing chain that supports -this feature. +standard by default and consequently produce a lot of warnings when handled by +Iris. +This can be configured in the ``config-developer.yml`` file for some steps of +the preprocessing chain. -Supported preprocessor steps: +Currently supported preprocessor steps: * :func:`~esmvalcore.preprocessor.load` -Here is an example to ignore specific warnings for the step ``load`` for all -datasets of project ``EMAC``: +Here is an example on how to ignore specific warnings during the preprocessor +step ``load`` for all datasets of project ``EMAC`` (taken from the default +``config-developer.yml`` file): .. code-block:: yaml ignore_warnings: load: - {message: 'Missing CF-netCDF formula term variable .*, referenced by netCDF variable .*', module: iris} - - {message: 'Ignored formula of unrecognised type: .*', module: iris} + - {message: 'Ignored formula of unrecognised type: .*', module: iris} The keyword arguments specified in the list items are directly passed to -:func:`warnings.filterwarnings`. - +:func:`warnings.filterwarnings` in addition to ``action=ignore`` (may be +overwritten in ``config-developer.yml``). .. _configure_native_models: From 0adb005400637784b01bfb59e9a6f6281ff8511d Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Wed, 30 Mar 2022 11:58:14 +0200 Subject: [PATCH 15/52] Added test for derivation of hfns --- tests/unit/preprocessor/_derive/test_hfns.py | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tests/unit/preprocessor/_derive/test_hfns.py diff --git a/tests/unit/preprocessor/_derive/test_hfns.py b/tests/unit/preprocessor/_derive/test_hfns.py new file mode 100644 index 0000000000..4eeb5889f4 --- /dev/null +++ b/tests/unit/preprocessor/_derive/test_hfns.py @@ -0,0 +1,45 @@ +"""Test derivation of ``hfns``.""" +import numpy as np +import pytest +from iris.cube import CubeList + +from esmvalcore.preprocessor._derive import hfns + +from .test_shared import get_cube + + +@pytest.fixture +def cubes(): + """Input cubes for derivation of ``xch4``.""" + hfls_cube = get_cube([[[1.0]]], air_pressure_coord=False, + standard_name='surface_upward_latent_heat_flux', + var_name='hfls', units='W m-2') + hfss_cube = get_cube([[[1.0]]], air_pressure_coord=False, + standard_name='surface_upward_sensible_heat_flux', + var_name='hfss', units='W m-2') + return CubeList([hfls_cube, hfss_cube]) + + +def test_hfns_calculate(cubes): + """Test function ``calculate``.""" + derived_var = hfns.DerivedVariable() + out_cube = derived_var.calculate(cubes) + assert out_cube.shape == (1, 1, 1) + assert out_cube.units == 'W m-2' + assert out_cube.coords('time') + assert out_cube.coords('latitude') + assert out_cube.coords('longitude') + np.testing.assert_allclose(out_cube.data, [[[2.0]]]) + np.testing.assert_allclose(out_cube.coord('time').points, [0.0]) + np.testing.assert_allclose(out_cube.coord('latitude').points, [45.0]) + np.testing.assert_allclose(out_cube.coord('longitude').points, [10.0]) + + +def test_hfns_required(): + """Test function ``required``.""" + derived_var = hfns.DerivedVariable() + output = derived_var.required(None) + assert output == [ + {'short_name': 'hfls'}, + {'short_name': 'hfss'}, + ] From 52efc74cd6d12c0ac23478d3b5611d49ebe1261b Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Wed, 30 Mar 2022 12:43:07 +0200 Subject: [PATCH 16/52] Added tests for ignoring warnings --- .../integration/preprocessor/_io/test_load.py | 39 ++++++++++++++++++- tests/unit/test_recipe.py | 28 +++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/tests/integration/preprocessor/_io/test_load.py b/tests/integration/preprocessor/_io/test_load.py index 1f36f4b492..9775392d04 100644 --- a/tests/integration/preprocessor/_io/test_load.py +++ b/tests/integration/preprocessor/_io/test_load.py @@ -3,11 +3,12 @@ import os import tempfile import unittest +import warnings import iris import numpy as np from iris.coords import DimCoord -from iris.cube import Cube +from iris.cube import Cube, CubeList from esmvalcore.preprocessor._io import concatenate_callback, load @@ -99,3 +100,39 @@ def test_callback_fix_lat_units(self): self.assertTrue((cube.coord('latitude').points == np.array([1, 2])).all()) self.assertEqual(cube.coord('latitude').units, 'degrees_north') + + @staticmethod + def load_with_warning(*_, **__): + """Mock load with a warning.""" + warnings.warn("This is a custom expected warning", + category=UserWarning) + return CubeList([Cube(0)]) + + @unittest.mock.patch('iris.load_raw', autospec=True) + def test_do_not_ignore_warnings(self, mock_load_raw): + """Test do not ignore specific warnings.""" + mock_load_raw.side_effect = self.load_with_warning + ignore_warnings = [{'message': "non-relevant warning"}] + + # Warning is not ignored -> assert warning has been issued + with self.assertWarns(UserWarning): + cubes = load('myfilename', ignore_warnings=ignore_warnings) + + # Check output + self.assertEqual(len(cubes), 1) + self.assertEqual(cubes[0].attributes, {'source_file': 'myfilename'}) + + @unittest.mock.patch('iris.load_raw', autospec=True) + def test_ignore_warnings(self, mock_load_raw): + """Test ignore specific warnings.""" + mock_load_raw.side_effect = self.load_with_warning + ignore_warnings = [{'message': "This is a custom expected warning"}] + + # Warning is ignored -> assert warning has not been issued + with self.assertRaises(AssertionError): + with self.assertWarns(UserWarning): + cubes = load('myfilename', ignore_warnings=ignore_warnings) + + # Check output + self.assertEqual(len(cubes), 1) + self.assertEqual(cubes[0].attributes, {'source_file': 'myfilename'}) diff --git a/tests/unit/test_recipe.py b/tests/unit/test_recipe.py index 6e1f88a227..31b2804c75 100644 --- a/tests/unit/test_recipe.py +++ b/tests/unit/test_recipe.py @@ -617,3 +617,31 @@ def test_create_diagnostic_tasks(mock_diag_task, tasks_to_run, tasks_run): name=f'{diag_name}{_recipe.TASKSEP}{task_name}', ) assert expected_call in mock_diag_task.mock_calls + + +def test_update_warning_settings_nonaffected_project(): + """Test ``_update_warning_settings``.""" + settings = {'save': {'filename': 'out.nc'}, 'load': {'filename': 'in.nc'}} + _recipe._update_warning_settings(settings, 'CMIP5') + assert settings == { + 'save': {'filename': 'out.nc'}, + 'load': {'filename': 'in.nc'}, + } + + +def test_update_warning_settings_step_not_present(): + """Test ``_update_warning_settings``.""" + settings = {'save': {'filename': 'out.nc'}} + _recipe._update_warning_settings(settings, 'EMAC') + assert settings == {'save': {'filename': 'out.nc'}} + + +def test_update_warning_settings_step_present(): + """Test ``_update_warning_settings``.""" + settings = {'save': {'filename': 'out.nc'}, 'load': {'filename': 'in.nc'}} + _recipe._update_warning_settings(settings, 'EMAC') + assert len(settings) == 2 + assert settings['save'] == {'filename': 'out.nc'} + assert len(settings['load']) == 2 + assert settings['load']['filename'] == 'in.nc' + assert 'ignore_warnings' in settings['load'] From aab40860f4bffc1efd2f835b5e4cd726cf0db310 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Wed, 30 Mar 2022 13:03:40 +0200 Subject: [PATCH 17/52] Added docstring to load() --- esmvalcore/preprocessor/_io.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/esmvalcore/preprocessor/_io.py b/esmvalcore/preprocessor/_io.py index db7edbde57..0481103a95 100644 --- a/esmvalcore/preprocessor/_io.py +++ b/esmvalcore/preprocessor/_io.py @@ -111,7 +111,30 @@ def _delete_attributes(iris_object, atts): def load(file, callback=None, ignore_warnings=None): - """Load iris cubes from files.""" + """Load iris cubes from files. + + Parameters + ---------- + file: str + File to be loaded. + callback: callable or None, optional (default: None) + Callback function passed to :func:`iris.load_raw`. + ignore_warnings: list of dict or None, optional (default: None) + Keyword arguments passed to :func:`warnings.filterwarnings` used to + ignore warnings issued by :func:`iris.load_raw`. Each list element + corresponds to one call to :func:`warnings.filterwarnings`. + + Returns + ------- + iris.cube.CubeList + Loaded cubes. + + Raises + ------ + ValueError + Cubes are empty. + + """ logger.debug("Loading:\n%s", file) if ignore_warnings is None: ignore_warnings = [] @@ -132,7 +155,7 @@ def load(file, callback=None, ignore_warnings=None): raw_cubes = iris.load_raw(file, callback=callback) logger.debug("Done with loading %s", file) if not raw_cubes: - raise Exception('Can not load cubes from {0}'.format(file)) + raise ValueError(f'Can not load cubes from {file}') for cube in raw_cubes: cube.attributes['source_file'] = file return raw_cubes From 8fcdc33a820b04ec99ee88f5990b6e4a09f71b45 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Wed, 13 Apr 2022 15:23:29 +0200 Subject: [PATCH 18/52] Added test for io.load --- tests/integration/preprocessor/_io/test_load.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/integration/preprocessor/_io/test_load.py b/tests/integration/preprocessor/_io/test_load.py index 9775392d04..cc68b88cb0 100644 --- a/tests/integration/preprocessor/_io/test_load.py +++ b/tests/integration/preprocessor/_io/test_load.py @@ -101,6 +101,14 @@ def test_callback_fix_lat_units(self): 2])).all()) self.assertEqual(cube.coord('latitude').units, 'degrees_north') + @unittest.mock.patch('iris.load_raw', autospec=True) + def test_fail_empty_cubes(self, mock_load_raw): + """Test that ValueError is raised when cubes are empty.""" + mock_load_raw.return_value = CubeList([]) + msg = "Can not load cubes from myfilename" + with self.assertRaises(ValueError, msg=msg): + load('myfilename') + @staticmethod def load_with_warning(*_, **__): """Mock load with a warning.""" From 1c78216b0930c328e120ffdc2e3473c492854359 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Wed, 13 Apr 2022 15:35:03 +0200 Subject: [PATCH 19/52] Added test for add_scalar_lambda550nm_coord --- esmvalcore/cmor/_fixes/shared.py | 2 +- tests/integration/cmor/_fixes/test_shared.py | 26 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/esmvalcore/cmor/_fixes/shared.py b/esmvalcore/cmor/_fixes/shared.py index a68d6042c4..71615c261f 100644 --- a/esmvalcore/cmor/_fixes/shared.py +++ b/esmvalcore/cmor/_fixes/shared.py @@ -158,7 +158,7 @@ def add_scalar_lambda550nm_coord(cube): units='nm', ) try: - cube.coord('wavelength') + cube.coord('radiation_wavelength') except iris.exceptions.CoordinateNotFoundError: cube.add_aux_coord(lambda550nm_coord, ()) return cube diff --git a/tests/integration/cmor/_fixes/test_shared.py b/tests/integration/cmor/_fixes/test_shared.py index a1a127066b..78275b2f5b 100644 --- a/tests/integration/cmor/_fixes/test_shared.py +++ b/tests/integration/cmor/_fixes/test_shared.py @@ -12,6 +12,7 @@ add_plev_from_altitude, add_scalar_depth_coord, add_scalar_height_coord, + add_scalar_lambda550nm_coord, add_scalar_typeland_coord, add_scalar_typesea_coord, add_scalar_typesi_coord, @@ -212,6 +213,7 @@ def test_add_altitude_from_plev(cube, output): (CUBE_2.copy(), None), (CUBE_2.copy(), 100.0), ] +TEST_ADD_SCALAR_COORD_NO_VALS = [CUBE_1.copy(), CUBE_2.copy()] @pytest.mark.sequential @@ -270,6 +272,30 @@ def test_add_scalar_height_coord(cube_in, height): assert coord == height_coord +@pytest.mark.sequential +@pytest.mark.parametrize('cube_in', TEST_ADD_SCALAR_COORD_NO_VALS) +def test_add_scalar_lambda550nm_coord(cube_in): + """Test adding of scalar lambda550nm coordinate.""" + cube_in = cube_in.copy() + lambda550nm_coord = iris.coords.AuxCoord( + 550.0, + var_name='wavelength', + standard_name='radiation_wavelength', + long_name='Radiation Wavelength 550 nanometers', + units='nm', + ) + with pytest.raises(iris.exceptions.CoordinateNotFoundError): + cube_in.coord('radiation_wavelength') + cube_out = add_scalar_lambda550nm_coord(cube_in) + assert cube_out is cube_in + coord = cube_in.coord('radiation_wavelength') + assert coord == lambda550nm_coord + cube_out_2 = add_scalar_lambda550nm_coord(cube_out) + assert cube_out_2 is cube_out + coord = cube_in.coord('radiation_wavelength') + assert coord == lambda550nm_coord + + @pytest.mark.sequential @pytest.mark.parametrize('cube_in,typeland', TEST_ADD_SCALAR_COORD) def test_add_scalar_typeland_coord(cube_in, typeland): From 970b4b85b54b1d0d6e29b2dc8417000ffb473468 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Wed, 13 Apr 2022 17:40:46 +0200 Subject: [PATCH 20/52] Added initial tests for EMAC CMORizer --- esmvalcore/cmor/_fixes/emac/_base_fixes.py | 10 +- esmvalcore/cmor/_fixes/emac/emac.py | 17 +- .../integration/cmor/_fixes/emac/__init__.py | 0 .../cmor/_fixes/emac/test_base_fixes.py | 1124 +++++++++++++++++ .../integration/cmor/_fixes/emac/test_emac.py | 760 +++++++++++ 5 files changed, 1897 insertions(+), 14 deletions(-) create mode 100644 tests/integration/cmor/_fixes/emac/__init__.py create mode 100644 tests/integration/cmor/_fixes/emac/test_base_fixes.py create mode 100644 tests/integration/cmor/_fixes/emac/test_emac.py diff --git a/esmvalcore/cmor/_fixes/emac/_base_fixes.py b/esmvalcore/cmor/_fixes/emac/_base_fixes.py index 9bed2db84d..dcb3b2ea0f 100644 --- a/esmvalcore/cmor/_fixes/emac/_base_fixes.py +++ b/esmvalcore/cmor/_fixes/emac/_base_fixes.py @@ -19,11 +19,11 @@ def get_cube(self, cubes, var_name=None): if var_name is None: var_name = self.extra_facets.get('raw_name', self.vardef.short_name) - if not cubes.extract(NameConstraint(var_name=var_name)): - raise ValueError( - f"Variable '{var_name}' used to extract " - f"'{self.vardef.short_name}' is not available in input " - f"file") + if not cubes.extract(NameConstraint(var_name=var_name)): + raise ValueError( + f"Variable '{var_name}' used to extract " + f"'{self.vardef.short_name}' is not available in input " + f"file") return cubes.extract_cube(NameConstraint(var_name=var_name)) diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py index 6f36e93682..af59bf58d7 100644 --- a/esmvalcore/cmor/_fixes/emac/emac.py +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -63,13 +63,11 @@ def fix_metadata(self, cubes): # Fix latitude if 'latitude' in self.vardef.dimensions: - lat_name = self.extra_facets.get('latitude', 'latitude') - self._fix_lat(cube, lat_name) + self._fix_lat(cube) # Fix longitude if 'longitude' in self.vardef.dimensions: - lon_name = self.extra_facets.get('longitude', 'longitude') - self._fix_lon(cube, lon_name) + self._fix_lon(cube) # Fix scalar coordinates self._fix_scalar_coords(cube) @@ -80,9 +78,9 @@ def fix_metadata(self, cubes): return CubeList([cube]) @staticmethod - def _fix_lat(cube, lat_name): + def _fix_lat(cube): """Fix latitude coordinate of cube.""" - lat = cube.coord(lat_name) + lat = cube.coord('latitude') lat.var_name = 'lat' lat.standard_name = 'latitude' lat.long_name = 'latitude' @@ -97,9 +95,9 @@ def _fix_lat(cube, lat_name): pass @staticmethod - def _fix_lon(cube, lon_name): + def _fix_lon(cube): """Fix longitude coordinate of cube.""" - lon = cube.coord(lon_name) + lon = cube.coord('longitude') lon.var_name = 'lon' lon.standard_name = 'longitude' lon.long_name = 'longitude' @@ -123,8 +121,9 @@ def _fix_plev(self, cube): continue coord.var_name = 'plev' coord.standard_name = 'air_pressure' - coord.lon_name = 'pressure' + coord.long_name = 'pressure' coord.convert_units('Pa') + coord.attributes['positive'] = 'down' return raise ValueError( f"Cannot find requested pressure level coordinate for variable " diff --git a/tests/integration/cmor/_fixes/emac/__init__.py b/tests/integration/cmor/_fixes/emac/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/integration/cmor/_fixes/emac/test_base_fixes.py b/tests/integration/cmor/_fixes/emac/test_base_fixes.py new file mode 100644 index 0000000000..c84d059d62 --- /dev/null +++ b/tests/integration/cmor/_fixes/emac/test_base_fixes.py @@ -0,0 +1,1124 @@ +"""Tests for the ICON on-the-fly CMORizer.""" +# from unittest import mock + +# import iris +# import numpy as np +# import pytest +# from cf_units import Unit +# from iris import NameConstraint +# from iris.coords import AuxCoord, DimCoord +# from iris.cube import Cube, CubeList + +# from esmvalcore._config import get_extra_facets +# from esmvalcore.cmor._fixes.icon.icon import AllVars, Siconc, Siconca +# from esmvalcore.cmor.fix import Fix +# from esmvalcore.cmor.table import get_var_info + +# # Note: test_data_path is defined in tests/integration/cmor/_fixes/conftest.py + + +# @pytest.fixture +# def cubes_2d(test_data_path): +# """2D sample cubes.""" +# nc_path = test_data_path / 'icon_2d.nc' +# return iris.load(str(nc_path)) + + +# @pytest.fixture +# def cubes_3d(test_data_path): +# """3D sample cubes.""" +# nc_path = test_data_path / 'icon_3d.nc' +# return iris.load(str(nc_path)) + + +# @pytest.fixture +# def cubes_grid(test_data_path): +# """Grid description sample cubes.""" +# nc_path = test_data_path / 'icon_grid.nc' +# return iris.load(str(nc_path)) + + +# @pytest.fixture +# def cubes_regular_grid(): +# """Cube with regular grid.""" +# time_coord = DimCoord([0], var_name='time', standard_name='time', +# units='days since 1850-01-01') +# lat_coord = DimCoord([0.0, 1.0], var_name='lat', standard_name='latitude', +# long_name='latitude', units='degrees_north') +# lon_coord = DimCoord([-1.0, 1.0], var_name='lon', +# standard_name='longitude', long_name='longitude', +# units='degrees_east') +# cube = Cube([[[0.0, 1.0], [2.0, 3.0]]], var_name='tas', units='K', +# dim_coords_and_dims=[(time_coord, 0), +# (lat_coord, 1), +# (lon_coord, 2)]) +# return CubeList([cube]) + + +# @pytest.fixture +# def cubes_2d_lat_lon_grid(): +# """Cube with 2D latitude and longitude.""" +# time_coord = DimCoord([0], var_name='time', standard_name='time', +# units='days since 1850-01-01') +# lat_coord = AuxCoord([[0.0, 0.0], [1.0, 1.0]], var_name='lat', +# standard_name='latitude', long_name='latitude', +# units='degrees_north') +# lon_coord = AuxCoord([[0.0, 1.0], [0.0, 1.0]], var_name='lon', +# standard_name='longitude', long_name='longitude', +# units='degrees_east') +# cube = Cube([[[0.0, 1.0], [2.0, 3.0]]], var_name='tas', units='K', +# dim_coords_and_dims=[(time_coord, 0)], +# aux_coords_and_dims=[(lat_coord, (1, 2)), +# (lon_coord, (1, 2))]) +# return CubeList([cube]) + + +# def get_allvars_fix(mip, short_name): +# """Get member of fix class.""" +# vardef = get_var_info('ICON', mip, short_name) +# extra_facets = get_extra_facets('ICON', 'ICON', mip, short_name, ()) +# fix = AllVars(vardef, extra_facets=extra_facets) +# return fix + + +# def check_ta_metadata(cubes): +# """Check ta metadata.""" +# assert len(cubes) == 1 +# cube = cubes[0] +# assert cube.var_name == 'ta' +# assert cube.standard_name == 'air_temperature' +# assert cube.long_name == 'Air Temperature' +# assert cube.units == 'K' +# return cube + + +# def check_tas_metadata(cubes): +# """Check tas metadata.""" +# assert len(cubes) == 1 +# cube = cubes[0] +# assert cube.var_name == 'tas' +# assert cube.standard_name == 'air_temperature' +# assert cube.long_name == 'Near-Surface Air Temperature' +# assert cube.units == 'K' +# return cube + + +# def check_siconc_metadata(cubes, var_name, long_name): +# """Check tas metadata.""" +# assert len(cubes) == 1 +# cube = cubes[0] +# assert cube.var_name == var_name +# assert cube.standard_name == 'sea_ice_area_fraction' +# assert cube.long_name == long_name +# assert cube.units == '%' +# return cube + + +# def check_time(cube): +# """Check time coordinate of cube.""" +# assert cube.coords('time', dim_coords=True) +# time = cube.coord('time', dim_coords=True) +# assert time.var_name == 'time' +# assert time.standard_name == 'time' +# assert time.long_name == 'time' +# assert time.units == Unit('days since 1850-01-01', +# calendar='proleptic_gregorian') +# np.testing.assert_allclose(time.points, [54786.0]) +# assert time.bounds is None +# assert time.attributes == {} + + +# def check_height(cube, plev_has_bounds=True): +# """Check height coordinate of cube.""" +# assert cube.coords('model level number', dim_coords=True) +# height = cube.coord('model level number', dim_coords=True) +# assert height.var_name == 'model_level' +# assert height.standard_name is None +# assert height.long_name == 'model level number' +# assert height.units == 'no unit' +# np.testing.assert_array_equal(height.points, np.arange(47)) +# assert height.bounds is None +# assert height.attributes == {'positive': 'up'} + +# assert cube.coords('air_pressure', dim_coords=False) +# plev = cube.coord('air_pressure', dim_coords=False) +# assert plev.var_name == 'plev' +# assert plev.standard_name == 'air_pressure' +# assert plev.long_name == 'pressure' +# assert plev.units == 'Pa' +# assert plev.attributes == {'positive': 'down'} +# assert cube.coord_dims('air_pressure') == (0, 1, 2) + +# if plev_has_bounds: +# assert plev.bounds is not None +# else: +# assert plev.bounds is None + + +# def check_heightxm(cube, height_value): +# """Check scalar heightxm coordinate of cube.""" +# assert cube.coords('height') +# height = cube.coord('height') +# assert height.var_name == 'height' +# assert height.standard_name == 'height' +# assert height.long_name == 'height' +# assert height.units == 'm' +# assert height.attributes == {'positive': 'up'} +# np.testing.assert_allclose(height.points, [height_value]) +# assert height.bounds is None + + +# def check_lat(cube): +# """Check latitude coordinate of cube.""" +# assert cube.coords('latitude', dim_coords=False) +# lat = cube.coord('latitude', dim_coords=False) +# assert lat.var_name == 'lat' +# assert lat.standard_name == 'latitude' +# assert lat.long_name == 'latitude' +# assert lat.units == 'degrees_north' +# np.testing.assert_allclose( +# lat.points, +# [-45.0, -45.0, -45.0, -45.0, 45.0, 45.0, 45.0, 45.0], +# rtol=1e-5 +# ) +# np.testing.assert_allclose( +# lat.bounds, +# [ +# [-90.0, 0.0, 0.0], +# [-90.0, 0.0, 0.0], +# [-90.0, 0.0, 0.0], +# [-90.0, 0.0, 0.0], +# [0.0, 0.0, 90.0], +# [0.0, 0.0, 90.0], +# [0.0, 0.0, 90.0], +# [0.0, 0.0, 90.0], +# ], +# rtol=1e-5 +# ) +# return lat + + +# def check_lon(cube): +# """Check longitude coordinate of cube.""" +# assert cube.coords('longitude', dim_coords=False) +# lon = cube.coord('longitude', dim_coords=False) +# assert lon.var_name == 'lon' +# assert lon.standard_name == 'longitude' +# assert lon.long_name == 'longitude' +# assert lon.units == 'degrees_east' +# np.testing.assert_allclose( +# lon.points, +# [-135.0, -45.0, 45.0, 135.0, -135.0, -45.0, 45.0, 135.0], +# rtol=1e-5 +# ) +# np.testing.assert_allclose( +# lon.bounds, +# [ +# [-135.0, -90.0, -180.0], +# [-45.0, 0.0, -90.0], +# [45.0, 90.0, 0.0], +# [135.0, 180.0, 90.0], +# [-180.0, -90.0, -135.0], +# [-90.0, 0.0, -45.0], +# [0.0, 90.0, 45.0], +# [90.0, 180.0, 135.0], +# ], +# rtol=1e-5 +# ) +# return lon + + +# def check_lat_lon(cube): +# """Check latitude, longitude and spatial index coordinates of cube.""" +# lat = check_lat(cube) +# lon = check_lon(cube) + +# # Check spatial index coordinate +# assert cube.coords('first spatial index for variables stored on an ' +# 'unstructured grid', dim_coords=True) +# i_coord = cube.coord('first spatial index for variables stored on an ' +# 'unstructured grid', dim_coords=True) +# assert i_coord.var_name == 'i' +# assert i_coord.standard_name is None +# assert i_coord.long_name == ('first spatial index for variables stored on ' +# 'an unstructured grid') +# assert i_coord.units == '1' +# np.testing.assert_allclose(i_coord.points, [0, 1, 2, 3, 4, 5, 6, 7]) +# assert i_coord.bounds is None + +# assert len(cube.coord_dims(lat)) == 1 +# assert cube.coord_dims(lat) == cube.coord_dims(lon) +# assert cube.coord_dims(lat) == cube.coord_dims(i_coord) + + +# def check_typesi(cube): +# """Check scalar typesi coordinate of cube.""" +# assert cube.coords('area_type') +# typesi = cube.coord('area_type') +# assert typesi.var_name == 'type' +# assert typesi.standard_name == 'area_type' +# assert typesi.long_name == 'Sea Ice area type' +# assert typesi.units.is_no_unit() +# np.testing.assert_array_equal(typesi.points, ['sea_ice']) +# assert typesi.bounds is None + + +# # Test areacella and areacello (for extra_facets, and grid_latitude and +# # grid_longitude coordinates) + + +# def test_get_areacella_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('ICON', 'ICON', 'fx', 'areacella') +# assert fix == [AllVars(None)] + + +# def test_areacella_fix(cubes_grid): +# """Test fix.""" +# fix = get_allvars_fix('fx', 'areacella') +# fixed_cubes = fix.fix_metadata(cubes_grid) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.var_name == 'areacella' +# assert cube.standard_name == 'cell_area' +# assert cube.long_name == 'Grid-Cell Area for Atmospheric Grid Variables' +# assert cube.units == 'm2' + +# check_lat_lon(cube) + + +# def test_get_areacello_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('ICON', 'ICON', 'Ofx', 'areacello') +# assert fix == [AllVars(None)] + + +# def test_areacello_fix(cubes_grid): +# """Test fix.""" +# fix = get_allvars_fix('Ofx', 'areacello') +# fixed_cubes = fix.fix_metadata(cubes_grid) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.var_name == 'areacello' +# assert cube.standard_name == 'cell_area' +# assert cube.long_name == 'Grid-Cell Area for Ocean Variables' +# assert cube.units == 'm2' + +# check_lat_lon(cube) + + +# # Test clwvi (for extra_facets) + + +# def test_get_clwvi_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('ICON', 'ICON', 'Amon', 'clwvi') +# assert fix == [AllVars(None)] + + +# def test_clwvi_fix(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'clwvi') +# fixed_cubes = fix.fix_metadata(cubes_2d) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.var_name == 'clwvi' +# assert cube.standard_name == ('atmosphere_mass_content_of_cloud_' +# 'condensed_water') +# assert cube.long_name == 'Condensed Water Path' +# assert cube.units == 'kg m-2' + +# check_time(cube) +# check_lat_lon(cube) + + +# # Test siconc and siconca (for extra_facets, extra fix and typesi coordinate) + + +# def test_get_siconc_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('ICON', 'ICON', 'SImon', 'siconc') +# assert fix == [Siconc(None), AllVars(None)] + + +# def test_siconc_fix(cubes_2d): +# """Test fix.""" +# vardef = get_var_info('ICON', 'SImon', 'siconc') +# extra_facets = get_extra_facets('ICON', 'ICON', 'SImon', 'siconc', ()) +# siconc_fix = Siconc(vardef, extra_facets=extra_facets) +# allvars_fix = get_allvars_fix('SImon', 'siconc') + +# fixed_cubes = siconc_fix.fix_metadata(cubes_2d) +# fixed_cubes = allvars_fix.fix_metadata(fixed_cubes) + +# cube = check_siconc_metadata(fixed_cubes, 'siconc', +# 'Sea-Ice Area Percentage (Ocean Grid)') +# check_time(cube) +# check_lat_lon(cube) +# check_typesi(cube) + +# np.testing.assert_allclose( +# cube.data, +# [[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]], +# ) + + +# def test_get_siconca_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('ICON', 'ICON', 'SImon', 'siconca') +# assert fix == [Siconca(None), AllVars(None)] + + +# def test_siconca_fix(cubes_2d): +# """Test fix.""" +# vardef = get_var_info('ICON', 'SImon', 'siconca') +# extra_facets = get_extra_facets('ICON', 'ICON', 'SImon', 'siconca', ()) +# siconca_fix = Siconca(vardef, extra_facets=extra_facets) +# allvars_fix = get_allvars_fix('SImon', 'siconca') + +# fixed_cubes = siconca_fix.fix_metadata(cubes_2d) +# fixed_cubes = allvars_fix.fix_metadata(fixed_cubes) + +# cube = check_siconc_metadata(fixed_cubes, 'siconca', +# 'Sea-Ice Area Percentage (Atmospheric Grid)') +# check_time(cube) +# check_lat_lon(cube) +# check_typesi(cube) + +# np.testing.assert_allclose( +# cube.data, +# [[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]], +# ) + + +# # Test ta (for height and plev coordinate) + + +# def test_get_ta_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('ICON', 'ICON', 'Amon', 'ta') +# assert fix == [AllVars(None)] + + +# def test_ta_fix(cubes_3d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'ta') +# fixed_cubes = fix.fix_metadata(cubes_3d) + +# cube = check_ta_metadata(fixed_cubes) +# check_time(cube) +# check_height(cube) +# check_lat_lon(cube) + + +# def test_ta_fix_no_plev_bounds(cubes_3d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'ta') +# cubes = CubeList([ +# cubes_3d.extract_cube(NameConstraint(var_name='ta')), +# cubes_3d.extract_cube(NameConstraint(var_name='pfull')), +# ]) +# fixed_cubes = fix.fix_metadata(cubes) + +# cube = check_ta_metadata(fixed_cubes) +# check_time(cube) +# check_height(cube, plev_has_bounds=False) +# check_lat_lon(cube) + + +# # Test tas (for height2m coordinate) + + +# def test_get_tas_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('ICON', 'ICON', 'Amon', 'tas') +# assert fix == [AllVars(None)] + + +# def test_tas_fix(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'tas') +# fixed_cubes = fix.fix_metadata(cubes_2d) + +# cube = check_tas_metadata(fixed_cubes) +# check_time(cube) +# check_lat_lon(cube) +# check_heightxm(cube, 2.0) + + +# def test_tas_spatial_index_coord_already_present(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'tas') + +# index_coord = DimCoord(np.arange(8), var_name='ncells') +# cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) +# cube.add_dim_coord(index_coord, 1) +# fixed_cubes = fix.fix_metadata(cubes_2d) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# check_lat_lon(cube) + + +# def test_tas_scalar_height2m_already_present(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'tas') + +# # Scalar height (with wrong metadata) already present +# height_coord = AuxCoord(2.0, var_name='h', standard_name='height') +# cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) +# cube.add_aux_coord(height_coord, ()) +# fixed_cubes = fix.fix_metadata(cubes_2d) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.shape == (1, 8) +# check_heightxm(cube, 2.0) + + +# def test_tas_dim_height2m_already_present(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'tas') + +# # Dimensional coordinate height (with wrong metadata) already present +# height_coord = AuxCoord(2.0, var_name='h', standard_name='height') +# cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) +# cube.add_aux_coord(height_coord, ()) +# cube = iris.util.new_axis(cube, scalar_coord='height') +# cube.transpose((1, 0, 2)) +# cubes = CubeList([cube]) +# fixed_cubes = fix.fix_metadata(cubes) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.shape == (1, 8) +# check_heightxm(cube, 2.0) + + +# # Test uas (for height10m coordinate) + + +# def test_get_uas_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('ICON', 'ICON', 'Amon', 'uas') +# assert fix == [AllVars(None)] + + +# def test_uas_fix(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'uas') +# fixed_cubes = fix.fix_metadata(cubes_2d) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.var_name == 'uas' +# assert cube.standard_name == 'eastward_wind' +# assert cube.long_name == 'Eastward Near-Surface Wind' +# assert cube.units == 'm s-1' + +# check_time(cube) +# check_lat_lon(cube) +# assert cube.coords('height') +# height = cube.coord('height') +# assert height.var_name == 'height' +# assert height.standard_name == 'height' +# assert height.long_name == 'height' +# assert height.units == 'm' +# assert height.attributes == {'positive': 'up'} +# np.testing.assert_allclose(height.points, [10.0]) +# assert height.bounds is None + + +# def test_uas_scalar_height10m_already_present(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'uas') + +# # Scalar height (with wrong metadata) already present +# height_coord = AuxCoord(10.0, var_name='h', standard_name='height') +# cube = cubes_2d.extract_cube(NameConstraint(var_name='uas')) +# cube.add_aux_coord(height_coord, ()) +# fixed_cubes = fix.fix_metadata(cubes_2d) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.shape == (1, 8) +# check_heightxm(cube, 10.0) + + +# def test_uas_dim_height10m_already_present(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'uas') + +# # Dimensional coordinate height (with wrong metadata) already present +# height_coord = AuxCoord(10.0, var_name='h', standard_name='height') +# cube = cubes_2d.extract_cube(NameConstraint(var_name='uas')) +# cube.add_aux_coord(height_coord, ()) +# cube = iris.util.new_axis(cube, scalar_coord='height') +# cube.transpose((1, 0, 2)) +# cubes = CubeList([cube]) +# fixed_cubes = fix.fix_metadata(cubes) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.shape == (1, 8) +# check_heightxm(cube, 10.0) + + +# # Test fix with regular grid and 2D latitudes and longitude + + +# def test_regular_grid_fix(cubes_regular_grid): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'tas') +# fixed_cubes = fix.fix_metadata(cubes_regular_grid) + +# cube = check_tas_metadata(fixed_cubes) +# assert cube.coords('time', dim_coords=True, dimensions=0) +# assert cube.coords('latitude', dim_coords=True, dimensions=1) +# assert cube.coords('longitude', dim_coords=True, dimensions=2) +# assert cube.coords('height', dim_coords=False, dimensions=()) + + +# def test_2d_lat_lon_grid_fix(cubes_2d_lat_lon_grid): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'tas') +# fixed_cubes = fix.fix_metadata(cubes_2d_lat_lon_grid) + +# cube = check_tas_metadata(fixed_cubes) +# assert cube.coords('time', dim_coords=True, dimensions=0) +# assert cube.coords('latitude', dim_coords=False, dimensions=(1, 2)) +# assert cube.coords('longitude', dim_coords=False, dimensions=(1, 2)) +# assert cube.coords('height', dim_coords=False, dimensions=()) + + +# # Test fix with empty standard_name + + +# def test_empty_standard_name_fix(cubes_2d): +# """Test fix.""" +# # We know that tas has a standard name, but this being native model output +# # there may be variables with no standard name. The code is designed to +# # handle this gracefully and here we test it with an artificial, but +# # realistic case. +# vardef = get_var_info('ICON', 'Amon', 'tas') +# original_standard_name = vardef.standard_name +# vardef.standard_name = '' +# extra_facets = get_extra_facets('ICON', 'ICON', 'Amon', 'tas', ()) +# fix = AllVars(vardef, extra_facets=extra_facets) +# fixed_cubes = fix.fix_metadata(cubes_2d) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.var_name == 'tas' +# assert cube.standard_name is None +# assert cube.long_name == 'Near-Surface Air Temperature' +# assert cube.units == 'K' + +# # Restore original standard_name of tas +# vardef.standard_name = original_standard_name + + +# # Test automatic addition of missing coordinates + + +# def test_add_time(cubes_2d): +# """Test fix.""" +# # Remove time from tas cube to test automatic addition +# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) +# uas_cube = cubes_2d.extract_cube(NameConstraint(var_name='uas')) +# tas_cube = tas_cube[0] +# tas_cube.remove_coord('time') +# cubes = CubeList([tas_cube, uas_cube]) + +# fix = get_allvars_fix('Amon', 'tas') +# fixed_cubes = fix.fix_metadata(cubes) + +# cube = check_tas_metadata(fixed_cubes) +# assert cube.shape == (1, 8) +# check_time(cube) + + +# def test_add_time_fail(): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'ta') +# cube = Cube(1, var_name='ta', units='K') +# cubes = CubeList([ +# cube, +# Cube(1, var_name='tas', units='K'), +# ]) +# msg = "Cannot add required coordinate 'time' to variable 'ta'" +# with pytest.raises(ValueError, match=msg): +# fix._add_time(cube, cubes) + + +# def test_add_latitude(cubes_2d, tmp_path): +# """Test fix.""" +# # Remove latitude from tas cube to test automatic addition +# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) +# tas_cube.remove_coord('latitude') +# tas_cube.attributes['grid_file_uri'] = ( +# 'https://github.com/ESMValGroup/ESMValCore/raw/main/tests/' +# 'integration/cmor/_fixes/test_data/icon_grid.nc' +# ) +# cubes = CubeList([tas_cube]) +# fix = get_allvars_fix('Amon', 'tas') + +# # Temporary overwrite default cache location for downloads +# original_cache_dir = fix.CACHE_DIR +# fix.CACHE_DIR = tmp_path + +# assert len(fix._horizontal_grids) == 0 +# fixed_cubes = fix.fix_metadata(cubes) + +# cube = check_tas_metadata(fixed_cubes) +# assert cube.shape == (1, 8) +# check_lat_lon(cube) +# assert len(fix._horizontal_grids) == 1 +# assert 'icon_grid.nc' in fix._horizontal_grids + +# # Restore cache location +# fix.CACHE_DIR = original_cache_dir + + +# def test_add_longitude(cubes_2d, tmp_path): +# """Test fix.""" +# # Remove longitude from tas cube to test automatic addition +# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) +# tas_cube.remove_coord('longitude') +# tas_cube.attributes['grid_file_uri'] = ( +# 'https://github.com/ESMValGroup/ESMValCore/raw/main/tests/' +# 'integration/cmor/_fixes/test_data/icon_grid.nc' +# ) +# cubes = CubeList([tas_cube]) +# fix = get_allvars_fix('Amon', 'tas') + +# # Temporary overwrite default cache location for downloads +# original_cache_dir = fix.CACHE_DIR +# fix.CACHE_DIR = tmp_path + +# assert len(fix._horizontal_grids) == 0 +# fixed_cubes = fix.fix_metadata(cubes) + +# cube = check_tas_metadata(fixed_cubes) +# assert cube.shape == (1, 8) +# check_lat_lon(cube) +# assert len(fix._horizontal_grids) == 1 +# assert 'icon_grid.nc' in fix._horizontal_grids + +# # Restore cache location +# fix.CACHE_DIR = original_cache_dir + + +# def test_add_latitude_longitude(cubes_2d, tmp_path): +# """Test fix.""" +# # Remove latitude and longitude from tas cube to test automatic addition +# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) +# tas_cube.remove_coord('latitude') +# tas_cube.remove_coord('longitude') +# tas_cube.attributes['grid_file_uri'] = ( +# 'https://github.com/ESMValGroup/ESMValCore/raw/main/tests/' +# 'integration/cmor/_fixes/test_data/icon_grid.nc' +# ) +# cubes = CubeList([tas_cube]) +# fix = get_allvars_fix('Amon', 'tas') + +# # Temporary overwrite default cache location for downloads +# original_cache_dir = fix.CACHE_DIR +# fix.CACHE_DIR = tmp_path + +# assert len(fix._horizontal_grids) == 0 +# fixed_cubes = fix.fix_metadata(cubes) + +# cube = check_tas_metadata(fixed_cubes) +# assert cube.shape == (1, 8) +# check_lat_lon(cube) +# assert len(fix._horizontal_grids) == 1 +# assert 'icon_grid.nc' in fix._horizontal_grids + +# # Restore cache location +# fix.CACHE_DIR = original_cache_dir + + +# def test_add_latitude_fail(cubes_2d): +# """Test fix.""" +# # Remove latitude from tas cube to test automatic addition +# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) +# tas_cube.remove_coord('latitude') +# cubes = CubeList([tas_cube]) +# fix = get_allvars_fix('Amon', 'tas') + +# msg = "Failed to add missing latitude coordinate to cube" +# with pytest.raises(ValueError, match=msg): +# fix.fix_metadata(cubes) + + +# def test_add_longitude_fail(cubes_2d): +# """Test fix.""" +# # Remove longitude from tas cube to test automatic addition +# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) +# tas_cube.remove_coord('longitude') +# cubes = CubeList([tas_cube]) +# fix = get_allvars_fix('Amon', 'tas') + +# msg = "Failed to add missing longitude coordinate to cube" +# with pytest.raises(ValueError, match=msg): +# fix.fix_metadata(cubes) + + +# def test_add_coord_from_grid_file_fail_invalid_coord(): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'tas') + +# msg = r"coord_name must be one of .* got 'invalid_coord_name'" +# with pytest.raises(ValueError, match=msg): +# fix._add_coord_from_grid_file(mock.sentinel.cube, 'invalid_coord_name', +# 'invalid_target_name') + + +# def test_add_coord_from_grid_file_fail_no_url(): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'tas') + +# msg = ("Cube does not contain the attribute 'grid_file_uri' necessary to " +# "download the ICON horizontal grid file") +# with pytest.raises(ValueError, match=msg): +# fix._add_coord_from_grid_file(Cube(0), 'grid_latitude', 'latitude') + + +# def test_add_coord_from_grid_fail_no_unnamed_dim(cubes_2d, tmp_path): +# """Test fix.""" +# # Remove latitude from tas cube to test automatic addition +# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) +# tas_cube.remove_coord('latitude') +# tas_cube.attributes['grid_file_uri'] = ( +# 'https://github.com/ESMValGroup/ESMValCore/raw/main/tests/' +# 'integration/cmor/_fixes/test_data/icon_grid.nc' +# ) +# index_coord = DimCoord(np.arange(8), var_name='ncells') +# tas_cube.add_dim_coord(index_coord, 1) +# fix = get_allvars_fix('Amon', 'tas') + +# # Temporary overwrite default cache location for downloads +# original_cache_dir = fix.CACHE_DIR +# fix.CACHE_DIR = tmp_path + +# msg = ("Cannot determine coordinate dimension for coordinate 'latitude', " +# "cube does not contain a single unnamed dimension") +# with pytest.raises(ValueError, match=msg): +# fix._add_coord_from_grid_file(tas_cube, 'grid_latitude', 'latitude') + +# # Restore cache location +# fix.CACHE_DIR = original_cache_dir + + +# def test_add_coord_from_grid_fail_two_unnamed_dims(cubes_2d, tmp_path): +# """Test fix.""" +# # Remove latitude from tas cube to test automatic addition +# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) +# tas_cube.remove_coord('latitude') +# tas_cube.attributes['grid_file_uri'] = ( +# 'https://github.com/ESMValGroup/ESMValCore/raw/main/tests/' +# 'integration/cmor/_fixes/test_data/icon_grid.nc' +# ) +# tas_cube = iris.util.new_axis(tas_cube) +# fix = get_allvars_fix('Amon', 'tas') + +# # Temporary overwrite default cache location for downloads +# original_cache_dir = fix.CACHE_DIR +# fix.CACHE_DIR = tmp_path + +# msg = ("Cannot determine coordinate dimension for coordinate 'latitude', " +# "cube does not contain a single unnamed dimension") +# with pytest.raises(ValueError, match=msg): +# fix._add_coord_from_grid_file(tas_cube, 'grid_latitude', 'latitude') + +# # Restore cache location +# fix.CACHE_DIR = original_cache_dir + + +# @mock.patch('esmvalcore.cmor._fixes.icon._base_fixes.requests', autospec=True) +# def test_get_horizontal_grid_cached_in_dict(mock_requests): +# """Test fix.""" +# cube = Cube(0, attributes={'grid_file_uri': 'cached_grid_url.nc'}) +# fix = get_allvars_fix('Amon', 'tas') +# fix._horizontal_grids['cached_grid_url.nc'] = mock.sentinel.grid + +# grid = fix.get_horizontal_grid(cube) +# assert grid == mock.sentinel.grid +# assert mock_requests.mock_calls == [] + + +# @mock.patch('esmvalcore.cmor._fixes.icon._base_fixes.requests', autospec=True) +# def test_get_horizontal_grid_cached_in_file(mock_requests, tmp_path): +# """Test fix.""" +# cube = Cube(0, attributes={ +# 'grid_file_uri': 'https://temporary.url/this/is/the/grid_file.nc'}) +# fix = get_allvars_fix('Amon', 'tas') +# assert len(fix._horizontal_grids) == 0 + +# # Save temporary grid file +# grid_cube = Cube(0, var_name='grid') +# iris.save(grid_cube, str(tmp_path / 'grid_file.nc')) + +# # Temporary overwrite default cache location for downloads +# original_cache_dir = fix.CACHE_DIR +# fix.CACHE_DIR = tmp_path + +# grid = fix.get_horizontal_grid(cube) +# assert isinstance(grid, CubeList) +# assert len(grid) == 1 +# assert grid[0].var_name == 'grid' +# assert len(fix._horizontal_grids) == 1 +# assert 'grid_file.nc' in fix._horizontal_grids +# assert mock_requests.mock_calls == [] + +# # Restore cache location +# fix.CACHE_DIR = original_cache_dir + + +# def test_get_horizontal_grid_cache_file_too_old(tmp_path): +# """Test fix.""" +# cube = Cube(0, attributes={ +# 'grid_file_uri': 'https://github.com/ESMValGroup/ESMValCore/raw/main/' +# 'tests/integration/cmor/_fixes/test_data/' +# 'icon_grid.nc'}) +# fix = get_allvars_fix('Amon', 'tas') +# assert len(fix._horizontal_grids) == 0 + +# # Save temporary grid file +# grid_cube = Cube(0, var_name='grid') +# iris.save(grid_cube, str(tmp_path / 'icon_grid.nc')) + +# # Temporary overwrite default cache location for downloads and cache +# # validity duration +# original_cache_dir = fix.CACHE_DIR +# original_cache_validity = fix.CACHE_VALIDITY +# fix.CACHE_DIR = tmp_path +# fix.CACHE_VALIDITY = -1 + +# grid = fix.get_horizontal_grid(cube) +# assert isinstance(grid, CubeList) +# assert len(grid) == 1 +# assert grid[0].var_name == 'cell_area' +# assert len(fix._horizontal_grids) == 1 +# assert 'icon_grid.nc' in fix._horizontal_grids + +# # Restore cache location +# fix.CACHE_DIR = original_cache_dir +# fix.CACHE_VALIDITY = original_cache_validity + + +# # Test with single-dimension cubes + + +# def test_only_time(): +# """Test fix.""" +# # We know that ta has dimensions time, plev19, latitude, longitude, but the +# # ICON CMORizer is designed to check for the presence of each dimension +# # individually. To test this, remove all but one dimension of ta to create +# # an artificial, but realistic test case. +# vardef = get_var_info('ICON', 'Amon', 'ta') +# original_dimensions = vardef.dimensions +# vardef.dimensions = ['time'] +# extra_facets = get_extra_facets('ICON', 'ICON', 'Amon', 'ta', ()) +# fix = AllVars(vardef, extra_facets=extra_facets) + +# # Create cube with only a single dimension +# time_coord = DimCoord([0.0, 1.0], var_name='time', standard_name='time', +# long_name='time', units='days since 1850-01-01') +# cubes = CubeList([ +# Cube([1, 1], var_name='ta', units='K', +# dim_coords_and_dims=[(time_coord, 0)]), +# ]) +# fixed_cubes = fix.fix_metadata(cubes) + +# # Check cube metadata +# cube = check_ta_metadata(fixed_cubes) + +# # Check cube data +# assert cube.shape == (2,) +# np.testing.assert_equal(cube.data, [1, 1]) + +# # Check time metadata +# assert cube.coords('time') +# new_time_coord = cube.coord('time', dim_coords=True) +# assert new_time_coord.var_name == 'time' +# assert new_time_coord.standard_name == 'time' +# assert new_time_coord.long_name == 'time' +# assert new_time_coord.units == 'days since 1850-01-01' + +# # Check time data +# np.testing.assert_allclose(new_time_coord.points, [0.0, 1.0]) +# np.testing.assert_allclose(new_time_coord.bounds, +# [[-0.5, 0.5], [0.5, 1.5]]) + +# # Restore original dimensions of ta +# vardef.dimensions = original_dimensions + + +# def test_only_height(): +# """Test fix.""" +# # We know that ta has dimensions time, plev19, latitude, longitude, but the +# # ICON CMORizer is designed to check for the presence of each dimension +# # individually. To test this, remove all but one dimension of ta to create +# # an artificial, but realistic test case. +# vardef = get_var_info('ICON', 'Amon', 'ta') +# original_dimensions = vardef.dimensions +# vardef.dimensions = ['plev19'] +# extra_facets = get_extra_facets('ICON', 'ICON', 'Amon', 'ta', ()) +# fix = AllVars(vardef, extra_facets=extra_facets) + +# # Create cube with only a single dimension +# height_coord = DimCoord([1000.0, 100.0], var_name='height', +# standard_name='height', units='cm') +# cubes = CubeList([ +# Cube([1, 1], var_name='ta', units='K', +# dim_coords_and_dims=[(height_coord, 0)]), +# ]) +# fixed_cubes = fix.fix_metadata(cubes) + +# # Check cube metadata +# cube = check_ta_metadata(fixed_cubes) + +# # Check cube data +# assert cube.shape == (2,) +# np.testing.assert_equal(cube.data, [1, 1]) + +# # Check height metadata +# assert cube.coords('height', dim_coords=True) +# new_height_coord = cube.coord('height') +# assert new_height_coord.var_name == 'height' +# assert new_height_coord.standard_name == 'height' +# assert new_height_coord.long_name == 'height' +# assert new_height_coord.units == 'm' +# assert new_height_coord.attributes == {'positive': 'up'} + +# # Check height data +# np.testing.assert_allclose(new_height_coord.points, [1.0, 10.0]) +# assert new_height_coord.bounds is None + +# # Restore original dimensions of ta +# vardef.dimensions = original_dimensions + + +# def test_only_latitude(): +# """Test fix.""" +# # We know that ta has dimensions time, plev19, latitude, longitude, but the +# # ICON CMORizer is designed to check for the presence of each dimension +# # individually. To test this, remove all but one dimension of ta to create +# # an artificial, but realistic test case. +# vardef = get_var_info('ICON', 'Amon', 'ta') +# original_dimensions = vardef.dimensions +# vardef.dimensions = ['latitude'] +# extra_facets = get_extra_facets('ICON', 'ICON', 'Amon', 'ta', ()) +# fix = AllVars(vardef, extra_facets=extra_facets) + +# # Create cube with only a single dimension +# lat_coord = DimCoord([0.0, 10.0], var_name='lat', standard_name='latitude', +# units='degrees') +# cubes = CubeList([ +# Cube([1, 1], var_name='ta', units='K', +# dim_coords_and_dims=[(lat_coord, 0)]), +# ]) +# fixed_cubes = fix.fix_metadata(cubes) + +# # Check cube metadata +# cube = check_ta_metadata(fixed_cubes) + +# # Check cube data +# assert cube.shape == (2,) +# np.testing.assert_equal(cube.data, [1, 1]) + +# # Check latitude metadata +# assert cube.coords('latitude', dim_coords=True) +# new_lat_coord = cube.coord('latitude') +# assert new_lat_coord.var_name == 'lat' +# assert new_lat_coord.standard_name == 'latitude' +# assert new_lat_coord.long_name == 'latitude' +# assert new_lat_coord.units == 'degrees_north' + +# # Check latitude data +# np.testing.assert_allclose(new_lat_coord.points, [0.0, 10.0]) +# assert new_lat_coord.bounds is None + +# # Restore original dimensions of ta +# vardef.dimensions = original_dimensions + + +# def test_only_longitude(): +# """Test fix.""" +# # We know that ta has dimensions time, plev19, latitude, longitude, but the +# # ICON CMORizer is designed to check for the presence of each dimension +# # individually. To test this, remove all but one dimension of ta to create +# # an artificial, but realistic test case. +# vardef = get_var_info('ICON', 'Amon', 'ta') +# original_dimensions = vardef.dimensions +# vardef.dimensions = ['longitude'] +# extra_facets = get_extra_facets('ICON', 'ICON', 'Amon', 'ta', ()) +# fix = AllVars(vardef, extra_facets=extra_facets) + +# # Create cube with only a single dimension +# lon_coord = DimCoord([0.0, 180.0], var_name='lon', +# standard_name='longitude', units='degrees') +# cubes = CubeList([ +# Cube([1, 1], var_name='ta', units='K', +# dim_coords_and_dims=[(lon_coord, 0)]), +# ]) +# fixed_cubes = fix.fix_metadata(cubes) + +# # Check cube metadata +# cube = check_ta_metadata(fixed_cubes) + +# # Check cube data +# assert cube.shape == (2,) +# np.testing.assert_equal(cube.data, [1, 1]) + +# # Check longitude metadata +# assert cube.coords('longitude', dim_coords=True) +# new_lon_coord = cube.coord('longitude') +# assert new_lon_coord.var_name == 'lon' +# assert new_lon_coord.standard_name == 'longitude' +# assert new_lon_coord.long_name == 'longitude' +# assert new_lon_coord.units == 'degrees_east' + +# # Check longitude data +# np.testing.assert_allclose(new_lon_coord.points, [0.0, 180.0]) +# assert new_lon_coord.bounds is None + +# # Restore original dimensions of ta +# vardef.dimensions = original_dimensions + + +# # Test variable not available in file + + +# def test_var_not_available_pr(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'pr') +# msg = "Variable 'pr' used to extract 'pr' is not available in input file" +# with pytest.raises(ValueError, match=msg): +# fix.fix_metadata(cubes_2d) + + +# def test_var_not_available_ps(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'ps') +# msg = "Variable 'x' used to extract 'ps' is not available in input file" +# with pytest.raises(ValueError, match=msg): +# fix.get_cube(cubes_2d, var_name='x') + + +# # Test fix with invalid time units + + +# def test_invalid_time_units(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'tas') +# for cube in cubes_2d: +# cube.coord('time').attributes['invalid_units'] = 'month as %Y%m%d.%f' +# msg = "Expected time units" +# with pytest.raises(ValueError, match=msg): +# fix.fix_metadata(cubes_2d) diff --git a/tests/integration/cmor/_fixes/emac/test_emac.py b/tests/integration/cmor/_fixes/emac/test_emac.py new file mode 100644 index 0000000000..9f99b4d2a7 --- /dev/null +++ b/tests/integration/cmor/_fixes/emac/test_emac.py @@ -0,0 +1,760 @@ +"""Tests for the EMAC on-the-fly CMORizer.""" +from unittest import mock + +import iris +import numpy as np +import pytest +from cf_units import Unit +from iris import NameConstraint +from iris.coords import AuxCoord, DimCoord +from iris.cube import Cube, CubeList + +from esmvalcore._config import get_extra_facets +from esmvalcore.cmor._fixes.emac.emac import AllVars +from esmvalcore.cmor.fix import Fix +from esmvalcore.cmor.table import get_var_info + +# Note: test_data_path is defined in tests/integration/cmor/_fixes/conftest.py + + +@pytest.fixture +def cubes_2d(test_data_path): + """2D sample cubes.""" + nc_path = test_data_path / 'emac_2d.nc' + return iris.load(str(nc_path)) + + +def get_allvars_fix(mip, short_name): + """Get member of fix class.""" + vardef = get_var_info('EMAC', mip, short_name) + extra_facets = get_extra_facets('EMAC', 'EMAC', mip, short_name, ()) + fix = AllVars(vardef, extra_facets=extra_facets) + return fix + + +def check_ta_metadata(cubes): + """Check ta metadata.""" + assert len(cubes) == 1 + cube = cubes[0] + assert cube.var_name == 'ta' + assert cube.standard_name == 'air_temperature' + assert cube.long_name == 'Air Temperature' + assert cube.units == 'K' + return cube + + +def check_tas_metadata(cubes): + """Check tas metadata.""" + assert len(cubes) == 1 + cube = cubes[0] + assert cube.var_name == 'tas' + assert cube.standard_name == 'air_temperature' + assert cube.long_name == 'Near-Surface Air Temperature' + assert cube.units == 'K' + return cube + + +def check_siconc_metadata(cubes, var_name, long_name): + """Check tas metadata.""" + assert len(cubes) == 1 + cube = cubes[0] + assert cube.var_name == var_name + assert cube.standard_name == 'sea_ice_area_fraction' + assert cube.long_name == long_name + assert cube.units == '%' + return cube + + +def check_time(cube): + """Check time coordinate of cube.""" + assert cube.coords('time', dim_coords=True) + time = cube.coord('time', dim_coords=True) + assert time.var_name == 'time' + assert time.standard_name == 'time' + assert time.long_name == 'time' + assert time.units == Unit('days since 1850-01-01', + calendar='proleptic_gregorian') + np.testing.assert_allclose(time.points, [54786.0]) + assert time.bounds is None + assert time.attributes == {} + + +def check_height(cube, plev_has_bounds=True): + """Check height coordinate of cube.""" + assert cube.coords('model level number', dim_coords=True) + height = cube.coord('model level number', dim_coords=True) + assert height.var_name == 'model_level' + assert height.standard_name is None + assert height.long_name == 'model level number' + assert height.units == 'no unit' + np.testing.assert_array_equal(height.points, np.arange(47)) + assert height.bounds is None + assert height.attributes == {'positive': 'up'} + + assert cube.coords('air_pressure', dim_coords=False) + plev = cube.coord('air_pressure', dim_coords=False) + assert plev.var_name == 'plev' + assert plev.standard_name == 'air_pressure' + assert plev.long_name == 'pressure' + assert plev.units == 'Pa' + assert plev.attributes == {'positive': 'down'} + assert cube.coord_dims('air_pressure') == (0, 1, 2) + + if plev_has_bounds: + assert plev.bounds is not None + else: + assert plev.bounds is None + + +def check_heightxm(cube, height_value): + """Check scalar heightxm coordinate of cube.""" + assert cube.coords('height') + height = cube.coord('height') + assert height.var_name == 'height' + assert height.standard_name == 'height' + assert height.long_name == 'height' + assert height.units == 'm' + assert height.attributes == {'positive': 'up'} + np.testing.assert_allclose(height.points, [height_value]) + assert height.bounds is None + + +def check_lat(cube): + """Check latitude coordinate of cube.""" + assert cube.coords('latitude', dim_coords=False) + lat = cube.coord('latitude', dim_coords=False) + assert lat.var_name == 'lat' + assert lat.standard_name == 'latitude' + assert lat.long_name == 'latitude' + assert lat.units == 'degrees_north' + np.testing.assert_allclose( + lat.points, + [-45.0, -45.0, -45.0, -45.0, 45.0, 45.0, 45.0, 45.0], + rtol=1e-5 + ) + np.testing.assert_allclose( + lat.bounds, + [ + [-90.0, 0.0, 0.0], + [-90.0, 0.0, 0.0], + [-90.0, 0.0, 0.0], + [-90.0, 0.0, 0.0], + [0.0, 0.0, 90.0], + [0.0, 0.0, 90.0], + [0.0, 0.0, 90.0], + [0.0, 0.0, 90.0], + ], + rtol=1e-5 + ) + return lat + + +def check_lon(cube): + """Check longitude coordinate of cube.""" + assert cube.coords('longitude', dim_coords=False) + lon = cube.coord('longitude', dim_coords=False) + assert lon.var_name == 'lon' + assert lon.standard_name == 'longitude' + assert lon.long_name == 'longitude' + assert lon.units == 'degrees_east' + np.testing.assert_allclose( + lon.points, + [-135.0, -45.0, 45.0, 135.0, -135.0, -45.0, 45.0, 135.0], + rtol=1e-5 + ) + np.testing.assert_allclose( + lon.bounds, + [ + [-135.0, -90.0, -180.0], + [-45.0, 0.0, -90.0], + [45.0, 90.0, 0.0], + [135.0, 180.0, 90.0], + [-180.0, -90.0, -135.0], + [-90.0, 0.0, -45.0], + [0.0, 90.0, 45.0], + [90.0, 180.0, 135.0], + ], + rtol=1e-5 + ) + return lon + + +def check_lat_lon(cube): + """Check latitude, longitude and spatial index coordinates of cube.""" + lat = check_lat(cube) + lon = check_lon(cube) + + # Check spatial index coordinate + assert cube.coords('first spatial index for variables stored on an ' + 'unstructured grid', dim_coords=True) + i_coord = cube.coord('first spatial index for variables stored on an ' + 'unstructured grid', dim_coords=True) + assert i_coord.var_name == 'i' + assert i_coord.standard_name is None + assert i_coord.long_name == ('first spatial index for variables stored on ' + 'an unstructured grid') + assert i_coord.units == '1' + np.testing.assert_allclose(i_coord.points, [0, 1, 2, 3, 4, 5, 6, 7]) + assert i_coord.bounds is None + + assert len(cube.coord_dims(lat)) == 1 + assert cube.coord_dims(lat) == cube.coord_dims(lon) + assert cube.coord_dims(lat) == cube.coord_dims(i_coord) + + +def check_typesi(cube): + """Check scalar typesi coordinate of cube.""" + assert cube.coords('area_type') + typesi = cube.coord('area_type') + assert typesi.var_name == 'type' + assert typesi.standard_name == 'area_type' + assert typesi.long_name == 'Sea Ice area type' + assert typesi.units.is_no_unit() + np.testing.assert_array_equal(typesi.points, ['sea_ice']) + assert typesi.bounds is None + + +# Test with single-dimension cubes + + +def test_only_time(): + """Test fix.""" + # We know that ta has dimensions time, plev19, latitude, longitude, but the + # EMAC CMORizer is designed to check for the presence of each dimension + # individually. To test this, remove all but one dimension of ta to create + # an artificial, but realistic test case. + vardef = get_var_info('EMAC', 'Amon', 'ta') + original_dimensions = vardef.dimensions + vardef.dimensions = ['time'] + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'ta', ()) + fix = AllVars(vardef, extra_facets=extra_facets) + + # Create cube with only a single dimension + time_coord = DimCoord([0.0, 1.0], var_name='time', standard_name='time', + long_name='time', units='days since 1850-01-01') + cubes = CubeList([ + Cube([1, 1], var_name='tm1_p19_ave', units='K', + dim_coords_and_dims=[(time_coord, 0)]), + ]) + fixed_cubes = fix.fix_metadata(cubes) + + # Check cube metadata + cube = check_ta_metadata(fixed_cubes) + + # Check cube data + assert cube.shape == (2,) + np.testing.assert_equal(cube.data, [1, 1]) + + # Check time metadata + assert cube.coords('time') + new_time_coord = cube.coord('time', dim_coords=True) + assert new_time_coord.var_name == 'time' + assert new_time_coord.standard_name == 'time' + assert new_time_coord.long_name == 'time' + assert new_time_coord.units == 'days since 1850-01-01' + + # Check time data + np.testing.assert_allclose(new_time_coord.points, [0.0, 1.0]) + np.testing.assert_allclose(new_time_coord.bounds, + [[-0.5, 0.5], [0.5, 1.5]]) + + # Restore original dimensions of ta + vardef.dimensions = original_dimensions + + +def test_only_plev(): + """Test fix.""" + # We know that ta has dimensions time, plev19, latitude, longitude, but the + # EMAC CMORizer is designed to check for the presence of each dimension + # individually. To test this, remove all but one dimension of ta to create + # an artificial, but realistic test case. + vardef = get_var_info('EMAC', 'Amon', 'ta') + original_dimensions = vardef.dimensions + vardef.dimensions = ['plev19'] + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'ta', ()) + fix = AllVars(vardef, extra_facets=extra_facets) + + # Create cube with only a single dimension + plev_coord = DimCoord([1000.0, 900.0], var_name='plev', + standard_name='air_pressure', units='hPa') + cubes = CubeList([ + Cube([1, 1], var_name='tm1_p19_ave', units='K', + dim_coords_and_dims=[(plev_coord, 0)]), + ]) + fixed_cubes = fix.fix_metadata(cubes) + + # Check cube metadata + cube = check_ta_metadata(fixed_cubes) + + # Check cube data + assert cube.shape == (2,) + np.testing.assert_equal(cube.data, [1, 1]) + + # Check plev metadata + assert cube.coords('air_pressure', dim_coords=True) + new_plev_coord = cube.coord('air_pressure') + assert new_plev_coord.var_name == 'plev' + assert new_plev_coord.standard_name == 'air_pressure' + assert new_plev_coord.long_name == 'pressure' + assert new_plev_coord.units == 'Pa' + assert new_plev_coord.attributes == {'positive': 'down'} + + # Check plev data + np.testing.assert_allclose(new_plev_coord.points, [100000.0, 90000.0]) + assert new_plev_coord.bounds is None + + # Restore original dimensions of ta + vardef.dimensions = original_dimensions + + +def test_only_latitude(): + """Test fix.""" + # We know that ta has dimensions time, plev19, latitude, longitude, but the + # EMAC CMORizer is designed to check for the presence of each dimension + # individually. To test this, remove all but one dimension of ta to create + # an artificial, but realistic test case. + vardef = get_var_info('EMAC', 'Amon', 'ta') + original_dimensions = vardef.dimensions + vardef.dimensions = ['latitude'] + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'ta', ()) + fix = AllVars(vardef, extra_facets=extra_facets) + + # Create cube with only a single dimension + lat_coord = DimCoord([0.0, 10.0], var_name='lat', standard_name='latitude', + units='degrees') + cubes = CubeList([ + Cube([1, 1], var_name='tm1_p19_ave', units='K', + dim_coords_and_dims=[(lat_coord, 0)]), + ]) + fixed_cubes = fix.fix_metadata(cubes) + + # Check cube metadata + cube = check_ta_metadata(fixed_cubes) + + # Check cube data + assert cube.shape == (2,) + np.testing.assert_equal(cube.data, [1, 1]) + + # Check latitude metadata + assert cube.coords('latitude', dim_coords=True) + new_lat_coord = cube.coord('latitude') + assert new_lat_coord.var_name == 'lat' + assert new_lat_coord.standard_name == 'latitude' + assert new_lat_coord.long_name == 'latitude' + assert new_lat_coord.units == 'degrees_north' + + # Check latitude data + np.testing.assert_allclose(new_lat_coord.points, [0.0, 10.0]) + np.testing.assert_allclose(new_lat_coord.bounds, + [[-5.0, 5.0], [5.0, 15.0]]) + + # Restore original dimensions of ta + vardef.dimensions = original_dimensions + + +def test_only_longitude(): + """Test fix.""" + # We know that ta has dimensions time, plev19, latitude, longitude, but the + # EMAC CMORizer is designed to check for the presence of each dimension + # individually. To test this, remove all but one dimension of ta to create + # an artificial, but realistic test case. + vardef = get_var_info('EMAC', 'Amon', 'ta') + original_dimensions = vardef.dimensions + vardef.dimensions = ['longitude'] + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'ta', ()) + fix = AllVars(vardef, extra_facets=extra_facets) + + # Create cube with only a single dimension + lon_coord = DimCoord([0.0, 180.0], var_name='lon', + standard_name='longitude', units='degrees') + cubes = CubeList([ + Cube([1, 1], var_name='tm1_p19_ave', units='K', + dim_coords_and_dims=[(lon_coord, 0)]), + ]) + fixed_cubes = fix.fix_metadata(cubes) + + # Check cube metadata + cube = check_ta_metadata(fixed_cubes) + + # Check cube data + assert cube.shape == (2,) + np.testing.assert_equal(cube.data, [1, 1]) + + # Check longitude metadata + assert cube.coords('longitude', dim_coords=True) + new_lon_coord = cube.coord('longitude') + assert new_lon_coord.var_name == 'lon' + assert new_lon_coord.standard_name == 'longitude' + assert new_lon_coord.long_name == 'longitude' + assert new_lon_coord.units == 'degrees_east' + + # Check longitude data + np.testing.assert_allclose(new_lon_coord.points, [0.0, 180.0]) + np.testing.assert_allclose(new_lon_coord.bounds, + [[-90.0, 90.0], [90.0, 270.0]]) + + # Restore original dimensions of ta + vardef.dimensions = original_dimensions + + +# Test areacella and areacello (for extra_facets, and grid_latitude and +# grid_longitude coordinates) + + +# def test_get_areacella_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('EMAC', 'EMAC', 'fx', 'areacella') +# assert fix == [AllVars(None)] + + +# def test_areacella_fix(cubes_grid): +# """Test fix.""" +# fix = get_allvars_fix('fx', 'areacella') +# fixed_cubes = fix.fix_metadata(cubes_grid) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.var_name == 'areacella' +# assert cube.standard_name == 'cell_area' +# assert cube.long_name == 'Grid-Cell Area for Atmospheric Grid Variables' +# assert cube.units == 'm2' + +# check_lat_lon(cube) + + +# def test_get_areacello_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('EMAC', 'EMAC', 'Ofx', 'areacello') +# assert fix == [AllVars(None)] + + +# def test_areacello_fix(cubes_grid): +# """Test fix.""" +# fix = get_allvars_fix('Ofx', 'areacello') +# fixed_cubes = fix.fix_metadata(cubes_grid) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.var_name == 'areacello' +# assert cube.standard_name == 'cell_area' +# assert cube.long_name == 'Grid-Cell Area for Ocean Variables' +# assert cube.units == 'm2' + +# check_lat_lon(cube) + + +# # Test clwvi (for extra_facets) + + +# def test_get_clwvi_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'clwvi') +# assert fix == [AllVars(None)] + + +# def test_clwvi_fix(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'clwvi') +# fixed_cubes = fix.fix_metadata(cubes_2d) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.var_name == 'clwvi' +# assert cube.standard_name == ('atmosphere_mass_content_of_cloud_' +# 'condensed_water') +# assert cube.long_name == 'Condensed Water Path' +# assert cube.units == 'kg m-2' + +# check_time(cube) +# check_lat_lon(cube) + + +# # Test siconc and siconca (for extra_facets, extra fix and typesi coordinate) + + +# def test_get_siconc_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('EMAC', 'EMAC', 'SImon', 'siconc') +# assert fix == [Siconc(None), AllVars(None)] + + +# def test_siconc_fix(cubes_2d): +# """Test fix.""" +# vardef = get_var_info('EMAC', 'SImon', 'siconc') +# extra_facets = get_extra_facets('EMAC', 'EMAC', 'SImon', 'siconc', ()) +# siconc_fix = Siconc(vardef, extra_facets=extra_facets) +# allvars_fix = get_allvars_fix('SImon', 'siconc') + +# fixed_cubes = siconc_fix.fix_metadata(cubes_2d) +# fixed_cubes = allvars_fix.fix_metadata(fixed_cubes) + +# cube = check_siconc_metadata(fixed_cubes, 'siconc', +# 'Sea-Ice Area Percentage (Ocean Grid)') +# check_time(cube) +# check_lat_lon(cube) +# check_typesi(cube) + +# np.testing.assert_allclose( +# cube.data, +# [[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]], +# ) + + +# def test_get_siconca_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('EMAC', 'EMAC', 'SImon', 'siconca') +# assert fix == [Siconca(None), AllVars(None)] + + +# def test_siconca_fix(cubes_2d): +# """Test fix.""" +# vardef = get_var_info('EMAC', 'SImon', 'siconca') +# extra_facets = get_extra_facets('EMAC', 'EMAC', 'SImon', 'siconca', ()) +# siconca_fix = Siconca(vardef, extra_facets=extra_facets) +# allvars_fix = get_allvars_fix('SImon', 'siconca') + +# fixed_cubes = siconca_fix.fix_metadata(cubes_2d) +# fixed_cubes = allvars_fix.fix_metadata(fixed_cubes) + +# cube = check_siconc_metadata(fixed_cubes, 'siconca', +# 'Sea-Ice Area Percentage (Atmospheric Grid)') +# check_time(cube) +# check_lat_lon(cube) +# check_typesi(cube) + +# np.testing.assert_allclose( +# cube.data, +# [[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]], +# ) + + +# # Test ta (for height and plev coordinate) + + +# def test_get_ta_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'ta') +# assert fix == [AllVars(None)] + + +# def test_ta_fix(cubes_3d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'ta') +# fixed_cubes = fix.fix_metadata(cubes_3d) + +# cube = check_ta_metadata(fixed_cubes) +# check_time(cube) +# check_height(cube) +# check_lat_lon(cube) + + +# def test_ta_fix_no_plev_bounds(cubes_3d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'ta') +# cubes = CubeList([ +# cubes_3d.extract_cube(NameConstraint(var_name='ta')), +# cubes_3d.extract_cube(NameConstraint(var_name='pfull')), +# ]) +# fixed_cubes = fix.fix_metadata(cubes) + +# cube = check_ta_metadata(fixed_cubes) +# check_time(cube) +# check_height(cube, plev_has_bounds=False) +# check_lat_lon(cube) + + +# # Test tas (for height2m coordinate) + + +# def test_get_tas_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'tas') +# assert fix == [AllVars(None)] + + +# def test_tas_fix(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'tas') +# fixed_cubes = fix.fix_metadata(cubes_2d) + +# cube = check_tas_metadata(fixed_cubes) +# check_time(cube) +# check_lat_lon(cube) +# check_heightxm(cube, 2.0) + + +# def test_tas_spatial_index_coord_already_present(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'tas') + +# index_coord = DimCoord(np.arange(8), var_name='ncells') +# cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) +# cube.add_dim_coord(index_coord, 1) +# fixed_cubes = fix.fix_metadata(cubes_2d) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# check_lat_lon(cube) + + +# def test_tas_scalar_height2m_already_present(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'tas') + +# # Scalar height (with wrong metadata) already present +# height_coord = AuxCoord(2.0, var_name='h', standard_name='height') +# cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) +# cube.add_aux_coord(height_coord, ()) +# fixed_cubes = fix.fix_metadata(cubes_2d) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.shape == (1, 8) +# check_heightxm(cube, 2.0) + + +# def test_tas_dim_height2m_already_present(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'tas') + +# # Dimensional coordinate height (with wrong metadata) already present +# height_coord = AuxCoord(2.0, var_name='h', standard_name='height') +# cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) +# cube.add_aux_coord(height_coord, ()) +# cube = iris.util.new_axis(cube, scalar_coord='height') +# cube.transpose((1, 0, 2)) +# cubes = CubeList([cube]) +# fixed_cubes = fix.fix_metadata(cubes) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.shape == (1, 8) +# check_heightxm(cube, 2.0) + + +# # Test uas (for height10m coordinate) + + +# def test_get_uas_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'uas') +# assert fix == [AllVars(None)] + + +# def test_uas_fix(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'uas') +# fixed_cubes = fix.fix_metadata(cubes_2d) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.var_name == 'uas' +# assert cube.standard_name == 'eastward_wind' +# assert cube.long_name == 'Eastward Near-Surface Wind' +# assert cube.units == 'm s-1' + +# check_time(cube) +# check_lat_lon(cube) +# assert cube.coords('height') +# height = cube.coord('height') +# assert height.var_name == 'height' +# assert height.standard_name == 'height' +# assert height.long_name == 'height' +# assert height.units == 'm' +# assert height.attributes == {'positive': 'up'} +# np.testing.assert_allclose(height.points, [10.0]) +# assert height.bounds is None + + +# def test_uas_scalar_height10m_already_present(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'uas') + +# # Scalar height (with wrong metadata) already present +# height_coord = AuxCoord(10.0, var_name='h', standard_name='height') +# cube = cubes_2d.extract_cube(NameConstraint(var_name='uas')) +# cube.add_aux_coord(height_coord, ()) +# fixed_cubes = fix.fix_metadata(cubes_2d) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.shape == (1, 8) +# check_heightxm(cube, 10.0) + + +# def test_uas_dim_height10m_already_present(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'uas') + +# # Dimensional coordinate height (with wrong metadata) already present +# height_coord = AuxCoord(10.0, var_name='h', standard_name='height') +# cube = cubes_2d.extract_cube(NameConstraint(var_name='uas')) +# cube.add_aux_coord(height_coord, ()) +# cube = iris.util.new_axis(cube, scalar_coord='height') +# cube.transpose((1, 0, 2)) +# cubes = CubeList([cube]) +# fixed_cubes = fix.fix_metadata(cubes) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.shape == (1, 8) +# check_heightxm(cube, 10.0) + + +# # Test fix with empty standard_name + + +# def test_empty_standard_name_fix(cubes_2d): +# """Test fix.""" +# # We know that tas has a standard name, but this being native model output +# # there may be variables with no standard name. The code is designed to +# # handle this gracefully and here we test it with an artificial, but +# # realistic case. +# vardef = get_var_info('EMAC', 'Amon', 'tas') +# original_standard_name = vardef.standard_name +# vardef.standard_name = '' +# extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'tas', ()) +# fix = AllVars(vardef, extra_facets=extra_facets) +# fixed_cubes = fix.fix_metadata(cubes_2d) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.var_name == 'tas' +# assert cube.standard_name is None +# assert cube.long_name == 'Near-Surface Air Temperature' +# assert cube.units == 'K' + +# # Restore original standard_name of tas +# vardef.standard_name = original_standard_name + + +# # Test variable not available in file + + +# def test_var_not_available_pr(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'pr') +# msg = "Variable 'pr' used to extract 'pr' is not available in input file" +# with pytest.raises(ValueError, match=msg): +# fix.fix_metadata(cubes_2d) + + +# def test_var_not_available_ps(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'ps') +# msg = "Variable 'x' used to extract 'ps' is not available in input file" +# with pytest.raises(ValueError, match=msg): +# fix.get_cube(cubes_2d, var_name='x') + + +# # Test fix with invalid time units + + +# def test_invalid_time_units(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'tas') +# for cube in cubes_2d: +# cube.coord('time').attributes['invalid_units'] = 'month as %Y%m%d.%f' +# msg = "Expected time units" +# with pytest.raises(ValueError, match=msg): +# fix.fix_metadata(cubes_2d) From 31486a05da5e215a69ec55c380fd100b8fbf699e Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 14 Apr 2022 17:23:08 +0200 Subject: [PATCH 21/52] Added EMAC sample data --- .../integration/cmor/_fixes/emac/test_emac.py | 157 ++++++++++-------- .../cmor/_fixes/test_data/emac_aermon.nc | Bin 0 -> 9120 bytes .../cmor/_fixes/test_data/emac_amon_2d.nc | Bin 0 -> 29828 bytes .../cmor/_fixes/test_data/emac_amon_3d.nc | Bin 0 -> 56764 bytes .../cmor/_fixes/test_data/emac_column.nc | Bin 0 -> 49787 bytes .../cmor/_fixes/test_data/emac_omon_2d.nc | Bin 0 -> 12140 bytes .../_fixes/test_data/emac_tracer_pdef_gp.nc | Bin 0 -> 40936 bytes 7 files changed, 90 insertions(+), 67 deletions(-) create mode 100644 tests/integration/cmor/_fixes/test_data/emac_aermon.nc create mode 100644 tests/integration/cmor/_fixes/test_data/emac_amon_2d.nc create mode 100644 tests/integration/cmor/_fixes/test_data/emac_amon_3d.nc create mode 100644 tests/integration/cmor/_fixes/test_data/emac_column.nc create mode 100644 tests/integration/cmor/_fixes/test_data/emac_omon_2d.nc create mode 100644 tests/integration/cmor/_fixes/test_data/emac_tracer_pdef_gp.nc diff --git a/tests/integration/cmor/_fixes/emac/test_emac.py b/tests/integration/cmor/_fixes/emac/test_emac.py index 9f99b4d2a7..d5fb91a238 100644 --- a/tests/integration/cmor/_fixes/emac/test_emac.py +++ b/tests/integration/cmor/_fixes/emac/test_emac.py @@ -1,12 +1,12 @@ """Tests for the EMAC on-the-fly CMORizer.""" -from unittest import mock +# from unittest import mock import iris import numpy as np import pytest from cf_units import Unit -from iris import NameConstraint -from iris.coords import AuxCoord, DimCoord +# from iris import NameConstraint +from iris.coords import DimCoord from iris.cube import Cube, CubeList from esmvalcore._config import get_extra_facets @@ -18,9 +18,44 @@ @pytest.fixture -def cubes_2d(test_data_path): - """2D sample cubes.""" - nc_path = test_data_path / 'emac_2d.nc' +def cubes_aermon(test_data_path): + """AERmon sample cubes.""" + nc_path = test_data_path / 'emac_aermon.nc' + return iris.load(str(nc_path)) + + +@pytest.fixture +def cubes_amon_2d(test_data_path): + """Amon 2D sample cubes.""" + nc_path = test_data_path / 'emac_amon_2d.nc' + return iris.load(str(nc_path)) + + +@pytest.fixture +def cubes_amon_3d(test_data_path): + """Amon 3D sample cubes.""" + nc_path = test_data_path / 'emac_amon_3d.nc' + return iris.load(str(nc_path)) + + +@pytest.fixture +def cubes_column(test_data_path): + """column sample cubes.""" + nc_path = test_data_path / 'emac_column.nc' + return iris.load(str(nc_path)) + + +@pytest.fixture +def cubes_omon_2d(test_data_path): + """Omon 2D sample cubes.""" + nc_path = test_data_path / 'emac_omon_2d.nc' + return iris.load(str(nc_path)) + + +@pytest.fixture +def cubes_tracer_pdef_gp(test_data_path): + """tracer_pdef_gp sample cubes.""" + nc_path = test_data_path / 'emac_tracer_pdef_gp.nc' return iris.load(str(nc_path)) @@ -72,9 +107,9 @@ def check_time(cube): assert time.var_name == 'time' assert time.standard_name == 'time' assert time.long_name == 'time' - assert time.units == Unit('days since 1850-01-01', - calendar='proleptic_gregorian') - np.testing.assert_allclose(time.points, [54786.0]) + assert time.units == Unit('day since 1849-01-01 00:00:00', + calendar='gregorian') + np.testing.assert_allclose(time.points, [55181.9930555556]) assert time.bounds is None assert time.attributes == {} @@ -121,85 +156,45 @@ def check_heightxm(cube, height_value): def check_lat(cube): """Check latitude coordinate of cube.""" - assert cube.coords('latitude', dim_coords=False) - lat = cube.coord('latitude', dim_coords=False) + assert cube.coords('latitude', dim_coords=True) + lat = cube.coord('latitude', dim_coords=True) assert lat.var_name == 'lat' assert lat.standard_name == 'latitude' assert lat.long_name == 'latitude' assert lat.units == 'degrees_north' np.testing.assert_allclose( lat.points, - [-45.0, -45.0, -45.0, -45.0, 45.0, 45.0, 45.0, 45.0], - rtol=1e-5 + [59.4444082891668, 19.8757191474409, -19.8757191474409, + -59.4444082891668], ) np.testing.assert_allclose( lat.bounds, - [ - [-90.0, 0.0, 0.0], - [-90.0, 0.0, 0.0], - [-90.0, 0.0, 0.0], - [-90.0, 0.0, 0.0], - [0.0, 0.0, 90.0], - [0.0, 0.0, 90.0], - [0.0, 0.0, 90.0], - [0.0, 0.0, 90.0], - ], - rtol=1e-5 + [[79.22875286, 39.66006372], + [39.66006372, 0.0], + [0.0, -39.66006372], + [-39.66006372, -79.22875286]], ) - return lat + assert lat.attributes == {} def check_lon(cube): """Check longitude coordinate of cube.""" - assert cube.coords('longitude', dim_coords=False) - lon = cube.coord('longitude', dim_coords=False) + assert cube.coords('longitude', dim_coords=True) + lon = cube.coord('longitude', dim_coords=True) assert lon.var_name == 'lon' assert lon.standard_name == 'longitude' assert lon.long_name == 'longitude' assert lon.units == 'degrees_east' np.testing.assert_allclose( lon.points, - [-135.0, -45.0, 45.0, 135.0, -135.0, -45.0, 45.0, 135.0], - rtol=1e-5 + [0.0, 45.0, 90.0, 135.0, 180.0, 225.0, 270.0, 315.0], ) np.testing.assert_allclose( lon.bounds, - [ - [-135.0, -90.0, -180.0], - [-45.0, 0.0, -90.0], - [45.0, 90.0, 0.0], - [135.0, 180.0, 90.0], - [-180.0, -90.0, -135.0], - [-90.0, 0.0, -45.0], - [0.0, 90.0, 45.0], - [90.0, 180.0, 135.0], - ], - rtol=1e-5 + [[-22.5, 22.5], [22.5, 67.5], [67.5, 112.5], [112.5, 157.5], + [157.5, 202.5], [202.5, 247.5], [247.5, 292.5], [292.5, 337.5]], ) - return lon - - -def check_lat_lon(cube): - """Check latitude, longitude and spatial index coordinates of cube.""" - lat = check_lat(cube) - lon = check_lon(cube) - - # Check spatial index coordinate - assert cube.coords('first spatial index for variables stored on an ' - 'unstructured grid', dim_coords=True) - i_coord = cube.coord('first spatial index for variables stored on an ' - 'unstructured grid', dim_coords=True) - assert i_coord.var_name == 'i' - assert i_coord.standard_name is None - assert i_coord.long_name == ('first spatial index for variables stored on ' - 'an unstructured grid') - assert i_coord.units == '1' - np.testing.assert_allclose(i_coord.points, [0, 1, 2, 3, 4, 5, 6, 7]) - assert i_coord.bounds is None - - assert len(cube.coord_dims(lat)) == 1 - assert cube.coord_dims(lat) == cube.coord_dims(lon) - assert cube.coord_dims(lat) == cube.coord_dims(i_coord) + assert lon.attributes == {} def check_typesi(cube): @@ -397,6 +392,33 @@ def test_only_longitude(): vardef.dimensions = original_dimensions +# Test each 2D variable in extra_facets/emac-mappings.yml + + +def test_get_awhea_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Omon', 'awhea') + assert fix == [AllVars(None)] + + +def test_awhea_fix(cubes_omon_2d): + """Test fix.""" + fix = get_allvars_fix('Omon', 'awhea') + fixed_cubes = fix.fix_metadata(cubes_omon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'awhea' + assert cube.standard_name is None + assert cube.long_name == ('Global Mean Net Surface Heat Flux Over Open ' + 'Water') + assert cube.units == 'W m-2' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + # Test areacella and areacello (for extra_facets, and grid_latitude and # grid_longitude coordinates) @@ -516,8 +538,8 @@ def test_only_longitude(): # fixed_cubes = siconca_fix.fix_metadata(cubes_2d) # fixed_cubes = allvars_fix.fix_metadata(fixed_cubes) -# cube = check_siconc_metadata(fixed_cubes, 'siconca', -# 'Sea-Ice Area Percentage (Atmospheric Grid)') +# cube = check_siconc_metadata( +# fixed_cubes, 'siconca', 'Sea-Ice Area Percentage (Atmospheric Grid)') # check_time(cube) # check_lat_lon(cube) # check_typesi(cube) @@ -706,7 +728,8 @@ def test_only_longitude(): # def test_empty_standard_name_fix(cubes_2d): # """Test fix.""" -# # We know that tas has a standard name, but this being native model output +# # We know that tas has a standard name, but this being native model +# # output # # there may be variables with no standard name. The code is designed to # # handle this gracefully and here we test it with an artificial, but # # realistic case. diff --git a/tests/integration/cmor/_fixes/test_data/emac_aermon.nc b/tests/integration/cmor/_fixes/test_data/emac_aermon.nc new file mode 100644 index 0000000000000000000000000000000000000000..34284e1a529e33fc284b36f60656ffc22b97fe71 GIT binary patch literal 9120 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 C>#lhK literal 0 HcmV?d00001 diff --git a/tests/integration/cmor/_fixes/test_data/emac_amon_2d.nc b/tests/integration/cmor/_fixes/test_data/emac_amon_2d.nc new file mode 100644 index 0000000000000000000000000000000000000000..9a482a172d9954aa5c98850b245e9c8cea0420ab GIT binary patch literal 29828 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 literal 0 HcmV?d00001 diff --git a/tests/integration/cmor/_fixes/test_data/emac_amon_3d.nc b/tests/integration/cmor/_fixes/test_data/emac_amon_3d.nc new file mode 100644 index 0000000000000000000000000000000000000000..1ce9fd65394874b069112211d513a94c9a1ab059 GIT binary patch literal 56764 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 literal 0 HcmV?d00001 diff --git a/tests/integration/cmor/_fixes/test_data/emac_column.nc b/tests/integration/cmor/_fixes/test_data/emac_column.nc new file mode 100644 index 0000000000000000000000000000000000000000..44ccb29027e81ac5eb28ea2119a998c1acbe589a GIT binary patch literal 49787 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 literal 0 HcmV?d00001 diff --git a/tests/integration/cmor/_fixes/test_data/emac_tracer_pdef_gp.nc b/tests/integration/cmor/_fixes/test_data/emac_tracer_pdef_gp.nc new file mode 100644 index 0000000000000000000000000000000000000000..145ba8c08b11211e75194f1a2f89835748b295a7 GIT binary patch literal 40936 zcmeI5d301&^2dWjB!Vm&1;M3NWEn7qPT0aSq&v&&ep^TaBqI=-BqS}J#UvmKqasNF z#bFT<1yK_GkO=$~5t%5cabOey6-Nez5hsqg;Z)tXHQtA((vN?B=X4)Jps9R6 zuj7!RCRkPs-|^8NzVP2JC=FKER8-xD&*9(I+|sG8 z)XFMX|77@;GGJgzav=ys>L$_oQEHN{1)8v?F`KlCdJRZI$m`W5x-=bD~SUg#<= z3ltXAR0Rr)s|qTMiYFJ8RKn9J8`E0xs`wrCCzco2c+)amYGrX%0KnC*X~k95!HRO% zfP}<^fv(;{MOkrWprqI}+*MjrQ<-w(jpcc5LxKH?VcGrAaWkq$cCOECw zRTikOc2!JvF$JdjTR+a5QldQ%zsIsa#ms&TzIOnwH^c1>d8%|Wjop=F>nfm0VXp=| z^x2z{WRfHEnJIu8jk^}?+96j+@)-_OxR>(&f&#;m{9Om87XnvJsy48sF^=cBpg{C> zgU$qB9Wp_I$lM5am{XEBHQS>qE#mSyrK=Fxo4~FUV+)d+?NicqI%RyjadDQsx(cY$ za;pLC*nAR_a?~Wjq3;lLR3Wmr>ug0$Dk~^#S&s2>*Hwt@9bkuZS*Wzgl$i7D7gGQ= zTD*6G9nNJbY*JCU{9Hx_h9%AAMlfYLUQu1`6_Oo}v!tmIeclUp7opGiQ>uw3ttDOf z9A_%%Ic3dm0z1QR#cjbUG3Rp(JpdIVdq3D2eyi4U=D=?imo$>!2XuB;RYj3=wx&X5 zAJ*9(U#d4f*Xej`Dpd9nFl044Z@?6DTJa_e{Syr`t^eZZvZjJ`%3N*+JLa=DWG-M4`V0z4 zrEyPz9s10=MWfk;=O}F6XHA95{uvBA8*KKYEsJ?~F2|S%`;B-FwP@FoT|?3tS3vRq zavQ#;f~utW*T#aK)^~eD#xhD_^R?QW0SaVpTd-u~F1S5a&c~gpAe@lvwYV>2J`0{I zhd!eMVoBUiV5j+vom|Uu%_i4bKS*WAgCXuW6vw?kC=mVa3U+KwNfHJd-coYQW*r3u z^pqCoE5I&nUPG}l=(Kg^tn(U_sMp{EL#2vp`s zFl1|(kYTRa6kl!DFi=1$&DS)r)9Yi4*Dt$T0rQExc42U|+)DCVT|wk|%~XhfOJJwX zWs6LSH=kQLpXjwY3%Kk&&YNQv3kuifJkFa#^fedkFt?OcZ>j;MF!|g<1rUP=W!=gH zJKRqhr*RY}zn@abym4U3dXE;K5%94kpAXbd1SaN@BDhs!xuE#T=MhsN`&y*?8s6Yh zxV*2R09ussECoB3N8qc&JOTygDv2A?xpG#jkxHcXn9pBSpmQt1l$|@t>7r_mZlvpz zq^W==CDt`M8%|@rLWc9Y%T$Q$>0pO@MV~iG7z9VMEg5TmuV|=%C_E^>&jdSG7u>=C z=W&O#5mX_v?=sjB_j7WVjw(cUE!eTX!<=2TT(jvr*zBUjdM+?={qp5lk*2I8{QAWd zK#exW^T7`HPLiNPmJPJv@H~aj@0~0q>NP9`Lo?6Au7JWnOP*_q2D+5kF9AEF|54pW z^`QB0*IPrzx~aLg4A{7qr^z{Hwo&}%*HlyhF?djXUIBLaTA7fam7X1Tv`k_1ua%i9 zWd37diGB-;xuqcG8}BzNFf7UM)nKain@Qe~zMxWk=I0q!1gwa(c&`OJ=C7(a9A^O( z7?$MkIxy98+noWglp7_46l=TOW-3IVH-H^G>x4+bVWC+(De>m#HO3oN=)P|>eTN$y z&Vlbth3NYxu){nPyw-w7@te;xrof!0%?thI*}<_=(L^Vw~7XOYF(D}Vy1(dP3mu*3NbT}_MfnqR+AfniDWxe-jY@x_~V z%;`*uv)%Zj3eqXD-V1g(zF||D#O23Vo5mDo6EJZNk>EA5A=XqParrgGP(hDrKJN!R z9A`zaHXfAx;>Q^k7?u?417K>tc3@^0ttYV?XLtpId>(Wd*f`FxQ8E*Z;xj+apa5Ew zaXzB^D~~y2i$@w~-d{}xBuVU}V8_PTtqw38rub_!#-M;y8ux3kGwP!X<*g-bKDV(x zq6)*4{Qd?^ah#!pGSH-W^WzK(M4y|%PVc|W2}OxDUl+kueL_+4>lAQV|7G3EK=PFz zV>mKt!Fv6(&h=DTt2M=6o{I_~1`mS2b!dlU3#VBQ#}*Wb%vi9)7{d$m9>?F@h?+E?XRH*)5q5B&)tw>zn zU#;t-`0D~D>$_TbuOYuq!`+IYMPc>;JDkhVofEmQz8cu=^#!47?G>1&+4 zujJP^>-qr`*AJ~bv=WN4PV?)Bu0r%T5$sr9YT@}VA7`k+^x9vCL!$SWVO=$C&EnS# zti@U}ri}4mU}Fz}?^ZCH9!jkF9)Kx;8ZEbmfgR=+w8G{hN#XLjr8UzOUq=Fy&7%R1 z?}-5g5Q7JWn+A4_3x~RfvlL(XyuxE)h9z-vi-UQEM~bRskR#12O$F%`pCzzEpP_p& z2RJgHQ30tmZWh>KUcs)zbeO{B^9mG*%v`Wz>jvDmV?ZfPethBJzXg3|9&pY5xHWxA zwSnKWu(h&9>()5n{6%DTae!nK=M`mJRoZVhntdh8Xf?RcaNzV=u*9oU##YX3BcxdjTu zIL{38*Gw*WjuLB|F~;2&OE5})-31)HAI5m4kHX~dhrvom%c)wh!}!9@!w%yM3Pk2y zof&ppC@Hr5dZFDGN{Q=y!%wRuMaYC(xrlx)1UuY+nukg7cglG3`!Ra|y2S7^Y;mG6 z`Pd4q&7j0~neJy;Xe0EKeBNKTLT84hZG=p1C8YTIm>yd-Da+wp0R_;aFjwnLPnEUs zMmCh^;j?q{|4Hdt^#uPMgO( zTT`L3kANY@9o|t=Ru)E&EbfL1(rLaQ1v@tG_@j>bg08~wq}YEAra11Z5KfOY_L>UP zDdYZ)&i01R1zS_0vYWvW=Q>RFGvJhQ=jXbvLiGC-*kOH!Pnj^&oxanh{wGG~;UGxC;&*$nvucV3r}%xgn!?~jekZUqYKldVIPOrHG&;ric%6;a zz&Tq}0ZCHayMmonQ!IL9ao1`}OX7Zo&ey7eQ@){s-qZYdft^uPEPABljx~io?mcw2 z7ix-gwx$A-q;c;Jc4kcpgHy)cW_`uV#2lyOdp}_7HN_-TxVAM#Pc{lS(eN3ofk~!~ zwT;i33NhZvU}w~nFm#0LI@T0=o(!?DiEvCxr?HB&$=i8~VP zkgIx~a+v~RN!&D@>v7Cw3S_PTmY82TYO3WhJV%K&-#2I~2&c>|3GB?iA#hImh5)s1 z$O5+2H&}3r-)HL^7@X*PF4!4;gGG-t?zk7#=#;qU>1?d{&e@s@NRq~U9N1ZXgGG-l z?poi_lDJRQ`C7$y$~RO%6dsh=7lED8H(2yY#~u3y`nZ?sY%lZ;&e@s@)$b4(ntekU zoHFh<~SwhmB7~f29r$T+V%~4vQfA-hR;~>O)_n)ZG6^LK$F6r4t7T05QdI$ zUB|wGo@X<4HdcJ+Y)u6uNq*l2c6#4nq9Yq?=o?xv*0nk}ZM5ZbWL%~IYBYc6>RjkH z9Qw-?$lUol7rG6HT&6(gE(A-XUopY(93|HF{R%y=mjK7?R~&!tD?2+cr1mSzfNk|F z7M$Yu+4>a*2VKf~vI6Xke#N3k8h7khG&+U-n9jyd!#P`10ZEeItHI9dS1fvDao75l zmc)In&eu8(r+h;Ny{GxV4(yD6#iB<#?%1!;$9;p&_CmknoUN%){oV+MtY5*Z8wRJ0 z`&nu?bDWazn{>ai>V}~sT;ntqs^1Md8>_B!wx&X5ZwEuYcAIEQy!k#2YPUYgDDmE* zbD{D&P_MDxy2O7KJVAL5GjXTra!BMd4Fy0<*5G}>@==*IvXihKMv>o zwc~zca;*+LQm($mB5|8duC;nZ%GJvQiF?ZAS~Dq9t~QHkXNe;wp|9p`kixehonCjXyFzkeM`#KQS?Xdh-lsIPObu(=N7jfc*!Lwf@vgv<;NgU zr_I|QHrHW%D7y1_3?^SkZ=35Fa?av0gnS+Sz!LAHO@-I=wAo7Q`}lj$K>@TVxsV8U z*l!|JG2ZM0&r#yf_nVpu{5Xk?GZ_0#;0`eUYYz&S@9miaVo6-g40i8u$oeD*3YV|X z@V=ish9z-_>0EdRg!u^+k#SLh&K(J+m{*F0C5r==nwX|8;7YU(xdm zTED>8PX+E>!kNq0l<4_IO#LZ{i+)aI9E*|awr!m0=Lq%D^9#0BCdE2>e!-?MtG&N} zZV)}cnCuEaSBjoroEeYk`Nf&>7|bRXWv>-Izlfe+M0So4J-;{)W|>V-KPP&A0cUd< z8~BO~Rg{+$ln2U+*^cJTGMY+AyX2cHh;K=LzbEd472lt+; ztKjnsWe}~SuN~bm+^0!&`x&P1p<8o-wcc>}%>6I(}_*srpdt{1y z@sb7joVe%e+KispxpL#4=`F_fZ5CqFyA5ys*~ikjp`)d-H_s5~HK&Qo#@s2#H7`_B zt_}&k^52jSla=&K~kDH@a|RO{nP#}4;#N(ot64Wv9McPIdl2<^7!i# z#rM}eE9bxE5x+P;sD>W-kMHJRR>+IDWh=+mKJCBwiKFt&9UqII59^ZN_xbyj2`|LS ze_j2S(&3UX)GIG@WiH>Bte)TMCgqpg7y6#5y2kVUsDN}&^PbEt9mZvT*?drXt!`EJ zN_Dx=^PQ;v(O9ohHczjUUUKColC4Ec(EIdgUGR%hRTK zGSfcuefZFGzP^ul6#IYFL2R1PQ`!B}3;xm*Pl%u1a<%ZsXFv5Vn>j?8JvboV_YFz0VxX^-Qc8Kl|k+6UN-wlz*)pEUq~CaN(XhIbrcnGZWgM zEbQg?+>|(b!CRt#e$Y2LW?*{!CG9f?jNG3Yx~5pDds7k?{^RMCKQ#Vb+|*;ui0&`W z8#*g^aM0q0XGWxS&y?=@zEG^X;K$5|F6$|jZ`kVZxJ2=-nbF_>-YaFo#gp%myM6jo zM#_5FGa*ywc;)Uvv_u7dEJ}gl?7j(^9?4B+US@O7gZK(ewG_7)7@WOYGPk#9E*^?GYNt6HX$|}l#p&_tk z(3Ac?zivp~nEXw8`h~d}KP>JfJXhcBTa!`aoBC6q%smq}rH$Fp&3k!%qI_^pM)Kx{ zWbx21Go&u(_YoJBJ)#tBRi)a~|5CFqC{?ex{wtWf-;IU`&I4)Bqm@$q&IbP-hoINcM^P+{(lbEzfEj;NPD+yrxXJIvjyVD7%Gz9_-mO}kn=IN;Czg;#zl54r7x zl6S{{*xX&d^G|;F^*j6>F54xYTEE!eX4O*vpR@ldPkE{YHRV42_Der*OH^v#&5- z7Rvei#*1HE^@dvZ!7AUBDbwV|JGUvvH&^*DS-wubbLfrY7qN%ZPJi936kPPZ{7R3d zN}H3vs8{xQC3EYsQEKcb%avaqUhP}6Z?<>HuuN&j>+zY5UsYxPsxFYW4Vad_vgku$ zSexnD$?Fnx?|b}qIc`9zu%XL#pQqP8bw%fp^3~nj#7*Oqlr@_Zr9-{S#QM*=%d0!5 z_+~wrBrh1;Ufg(72l?&FQU2H-?R-6gA-VX~>`M@@ z{odu|3Ro~`$NGCXZ4-D;+>}^EoirG@&~z{igJRp0_(r?`3J=PoVua! zbLk_#ZOZuXyFp?7uuR`mUoP|AlY1!hbaT73Q4_X!yDq7Qx!at)`L(Iyp<}YtWll$N z(ML0t39oID>f)-^tci!zE3(eV{Et4O5r{@08i8m8q7jHjAR2*a1fmg$Mj#r2Xau4W Wh(;h9foKGx5r{@08iD^$5%?c4CV=?> literal 0 HcmV?d00001 From 9ba047a4c187727a67b6f0663e49fd830c7b908b Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 14 Apr 2022 17:34:52 +0200 Subject: [PATCH 22/52] Added further tests --- esmvalcore/cmor/_fixes/emac/emac.py | 6 +- .../cmor/_fixes/emac/test_base_fixes.py | 1124 ----------------- .../integration/cmor/_fixes/emac/test_emac.py | 57 +- 3 files changed, 59 insertions(+), 1128 deletions(-) delete mode 100644 tests/integration/cmor/_fixes/emac/test_base_fixes.py diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py index af59bf58d7..2fcff1d2c4 100644 --- a/esmvalcore/cmor/_fixes/emac/emac.py +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -183,6 +183,9 @@ def _fix_var_metadata(self, cube): cube.convert_units(self.vardef.units) +Clt = SetUnitsTo1 + + class Clwvi(EmacFix): """Fixes for ``clwvi``.""" @@ -199,9 +202,6 @@ def fix_metadata(self, cubes): Cod_sw_b01 = SetUnitsTo1SumOverZ # noqa: N801 -Clt = SetUnitsTo1 - - Evspsbl = NegateData diff --git a/tests/integration/cmor/_fixes/emac/test_base_fixes.py b/tests/integration/cmor/_fixes/emac/test_base_fixes.py deleted file mode 100644 index c84d059d62..0000000000 --- a/tests/integration/cmor/_fixes/emac/test_base_fixes.py +++ /dev/null @@ -1,1124 +0,0 @@ -"""Tests for the ICON on-the-fly CMORizer.""" -# from unittest import mock - -# import iris -# import numpy as np -# import pytest -# from cf_units import Unit -# from iris import NameConstraint -# from iris.coords import AuxCoord, DimCoord -# from iris.cube import Cube, CubeList - -# from esmvalcore._config import get_extra_facets -# from esmvalcore.cmor._fixes.icon.icon import AllVars, Siconc, Siconca -# from esmvalcore.cmor.fix import Fix -# from esmvalcore.cmor.table import get_var_info - -# # Note: test_data_path is defined in tests/integration/cmor/_fixes/conftest.py - - -# @pytest.fixture -# def cubes_2d(test_data_path): -# """2D sample cubes.""" -# nc_path = test_data_path / 'icon_2d.nc' -# return iris.load(str(nc_path)) - - -# @pytest.fixture -# def cubes_3d(test_data_path): -# """3D sample cubes.""" -# nc_path = test_data_path / 'icon_3d.nc' -# return iris.load(str(nc_path)) - - -# @pytest.fixture -# def cubes_grid(test_data_path): -# """Grid description sample cubes.""" -# nc_path = test_data_path / 'icon_grid.nc' -# return iris.load(str(nc_path)) - - -# @pytest.fixture -# def cubes_regular_grid(): -# """Cube with regular grid.""" -# time_coord = DimCoord([0], var_name='time', standard_name='time', -# units='days since 1850-01-01') -# lat_coord = DimCoord([0.0, 1.0], var_name='lat', standard_name='latitude', -# long_name='latitude', units='degrees_north') -# lon_coord = DimCoord([-1.0, 1.0], var_name='lon', -# standard_name='longitude', long_name='longitude', -# units='degrees_east') -# cube = Cube([[[0.0, 1.0], [2.0, 3.0]]], var_name='tas', units='K', -# dim_coords_and_dims=[(time_coord, 0), -# (lat_coord, 1), -# (lon_coord, 2)]) -# return CubeList([cube]) - - -# @pytest.fixture -# def cubes_2d_lat_lon_grid(): -# """Cube with 2D latitude and longitude.""" -# time_coord = DimCoord([0], var_name='time', standard_name='time', -# units='days since 1850-01-01') -# lat_coord = AuxCoord([[0.0, 0.0], [1.0, 1.0]], var_name='lat', -# standard_name='latitude', long_name='latitude', -# units='degrees_north') -# lon_coord = AuxCoord([[0.0, 1.0], [0.0, 1.0]], var_name='lon', -# standard_name='longitude', long_name='longitude', -# units='degrees_east') -# cube = Cube([[[0.0, 1.0], [2.0, 3.0]]], var_name='tas', units='K', -# dim_coords_and_dims=[(time_coord, 0)], -# aux_coords_and_dims=[(lat_coord, (1, 2)), -# (lon_coord, (1, 2))]) -# return CubeList([cube]) - - -# def get_allvars_fix(mip, short_name): -# """Get member of fix class.""" -# vardef = get_var_info('ICON', mip, short_name) -# extra_facets = get_extra_facets('ICON', 'ICON', mip, short_name, ()) -# fix = AllVars(vardef, extra_facets=extra_facets) -# return fix - - -# def check_ta_metadata(cubes): -# """Check ta metadata.""" -# assert len(cubes) == 1 -# cube = cubes[0] -# assert cube.var_name == 'ta' -# assert cube.standard_name == 'air_temperature' -# assert cube.long_name == 'Air Temperature' -# assert cube.units == 'K' -# return cube - - -# def check_tas_metadata(cubes): -# """Check tas metadata.""" -# assert len(cubes) == 1 -# cube = cubes[0] -# assert cube.var_name == 'tas' -# assert cube.standard_name == 'air_temperature' -# assert cube.long_name == 'Near-Surface Air Temperature' -# assert cube.units == 'K' -# return cube - - -# def check_siconc_metadata(cubes, var_name, long_name): -# """Check tas metadata.""" -# assert len(cubes) == 1 -# cube = cubes[0] -# assert cube.var_name == var_name -# assert cube.standard_name == 'sea_ice_area_fraction' -# assert cube.long_name == long_name -# assert cube.units == '%' -# return cube - - -# def check_time(cube): -# """Check time coordinate of cube.""" -# assert cube.coords('time', dim_coords=True) -# time = cube.coord('time', dim_coords=True) -# assert time.var_name == 'time' -# assert time.standard_name == 'time' -# assert time.long_name == 'time' -# assert time.units == Unit('days since 1850-01-01', -# calendar='proleptic_gregorian') -# np.testing.assert_allclose(time.points, [54786.0]) -# assert time.bounds is None -# assert time.attributes == {} - - -# def check_height(cube, plev_has_bounds=True): -# """Check height coordinate of cube.""" -# assert cube.coords('model level number', dim_coords=True) -# height = cube.coord('model level number', dim_coords=True) -# assert height.var_name == 'model_level' -# assert height.standard_name is None -# assert height.long_name == 'model level number' -# assert height.units == 'no unit' -# np.testing.assert_array_equal(height.points, np.arange(47)) -# assert height.bounds is None -# assert height.attributes == {'positive': 'up'} - -# assert cube.coords('air_pressure', dim_coords=False) -# plev = cube.coord('air_pressure', dim_coords=False) -# assert plev.var_name == 'plev' -# assert plev.standard_name == 'air_pressure' -# assert plev.long_name == 'pressure' -# assert plev.units == 'Pa' -# assert plev.attributes == {'positive': 'down'} -# assert cube.coord_dims('air_pressure') == (0, 1, 2) - -# if plev_has_bounds: -# assert plev.bounds is not None -# else: -# assert plev.bounds is None - - -# def check_heightxm(cube, height_value): -# """Check scalar heightxm coordinate of cube.""" -# assert cube.coords('height') -# height = cube.coord('height') -# assert height.var_name == 'height' -# assert height.standard_name == 'height' -# assert height.long_name == 'height' -# assert height.units == 'm' -# assert height.attributes == {'positive': 'up'} -# np.testing.assert_allclose(height.points, [height_value]) -# assert height.bounds is None - - -# def check_lat(cube): -# """Check latitude coordinate of cube.""" -# assert cube.coords('latitude', dim_coords=False) -# lat = cube.coord('latitude', dim_coords=False) -# assert lat.var_name == 'lat' -# assert lat.standard_name == 'latitude' -# assert lat.long_name == 'latitude' -# assert lat.units == 'degrees_north' -# np.testing.assert_allclose( -# lat.points, -# [-45.0, -45.0, -45.0, -45.0, 45.0, 45.0, 45.0, 45.0], -# rtol=1e-5 -# ) -# np.testing.assert_allclose( -# lat.bounds, -# [ -# [-90.0, 0.0, 0.0], -# [-90.0, 0.0, 0.0], -# [-90.0, 0.0, 0.0], -# [-90.0, 0.0, 0.0], -# [0.0, 0.0, 90.0], -# [0.0, 0.0, 90.0], -# [0.0, 0.0, 90.0], -# [0.0, 0.0, 90.0], -# ], -# rtol=1e-5 -# ) -# return lat - - -# def check_lon(cube): -# """Check longitude coordinate of cube.""" -# assert cube.coords('longitude', dim_coords=False) -# lon = cube.coord('longitude', dim_coords=False) -# assert lon.var_name == 'lon' -# assert lon.standard_name == 'longitude' -# assert lon.long_name == 'longitude' -# assert lon.units == 'degrees_east' -# np.testing.assert_allclose( -# lon.points, -# [-135.0, -45.0, 45.0, 135.0, -135.0, -45.0, 45.0, 135.0], -# rtol=1e-5 -# ) -# np.testing.assert_allclose( -# lon.bounds, -# [ -# [-135.0, -90.0, -180.0], -# [-45.0, 0.0, -90.0], -# [45.0, 90.0, 0.0], -# [135.0, 180.0, 90.0], -# [-180.0, -90.0, -135.0], -# [-90.0, 0.0, -45.0], -# [0.0, 90.0, 45.0], -# [90.0, 180.0, 135.0], -# ], -# rtol=1e-5 -# ) -# return lon - - -# def check_lat_lon(cube): -# """Check latitude, longitude and spatial index coordinates of cube.""" -# lat = check_lat(cube) -# lon = check_lon(cube) - -# # Check spatial index coordinate -# assert cube.coords('first spatial index for variables stored on an ' -# 'unstructured grid', dim_coords=True) -# i_coord = cube.coord('first spatial index for variables stored on an ' -# 'unstructured grid', dim_coords=True) -# assert i_coord.var_name == 'i' -# assert i_coord.standard_name is None -# assert i_coord.long_name == ('first spatial index for variables stored on ' -# 'an unstructured grid') -# assert i_coord.units == '1' -# np.testing.assert_allclose(i_coord.points, [0, 1, 2, 3, 4, 5, 6, 7]) -# assert i_coord.bounds is None - -# assert len(cube.coord_dims(lat)) == 1 -# assert cube.coord_dims(lat) == cube.coord_dims(lon) -# assert cube.coord_dims(lat) == cube.coord_dims(i_coord) - - -# def check_typesi(cube): -# """Check scalar typesi coordinate of cube.""" -# assert cube.coords('area_type') -# typesi = cube.coord('area_type') -# assert typesi.var_name == 'type' -# assert typesi.standard_name == 'area_type' -# assert typesi.long_name == 'Sea Ice area type' -# assert typesi.units.is_no_unit() -# np.testing.assert_array_equal(typesi.points, ['sea_ice']) -# assert typesi.bounds is None - - -# # Test areacella and areacello (for extra_facets, and grid_latitude and -# # grid_longitude coordinates) - - -# def test_get_areacella_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('ICON', 'ICON', 'fx', 'areacella') -# assert fix == [AllVars(None)] - - -# def test_areacella_fix(cubes_grid): -# """Test fix.""" -# fix = get_allvars_fix('fx', 'areacella') -# fixed_cubes = fix.fix_metadata(cubes_grid) - -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.var_name == 'areacella' -# assert cube.standard_name == 'cell_area' -# assert cube.long_name == 'Grid-Cell Area for Atmospheric Grid Variables' -# assert cube.units == 'm2' - -# check_lat_lon(cube) - - -# def test_get_areacello_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('ICON', 'ICON', 'Ofx', 'areacello') -# assert fix == [AllVars(None)] - - -# def test_areacello_fix(cubes_grid): -# """Test fix.""" -# fix = get_allvars_fix('Ofx', 'areacello') -# fixed_cubes = fix.fix_metadata(cubes_grid) - -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.var_name == 'areacello' -# assert cube.standard_name == 'cell_area' -# assert cube.long_name == 'Grid-Cell Area for Ocean Variables' -# assert cube.units == 'm2' - -# check_lat_lon(cube) - - -# # Test clwvi (for extra_facets) - - -# def test_get_clwvi_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('ICON', 'ICON', 'Amon', 'clwvi') -# assert fix == [AllVars(None)] - - -# def test_clwvi_fix(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'clwvi') -# fixed_cubes = fix.fix_metadata(cubes_2d) - -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.var_name == 'clwvi' -# assert cube.standard_name == ('atmosphere_mass_content_of_cloud_' -# 'condensed_water') -# assert cube.long_name == 'Condensed Water Path' -# assert cube.units == 'kg m-2' - -# check_time(cube) -# check_lat_lon(cube) - - -# # Test siconc and siconca (for extra_facets, extra fix and typesi coordinate) - - -# def test_get_siconc_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('ICON', 'ICON', 'SImon', 'siconc') -# assert fix == [Siconc(None), AllVars(None)] - - -# def test_siconc_fix(cubes_2d): -# """Test fix.""" -# vardef = get_var_info('ICON', 'SImon', 'siconc') -# extra_facets = get_extra_facets('ICON', 'ICON', 'SImon', 'siconc', ()) -# siconc_fix = Siconc(vardef, extra_facets=extra_facets) -# allvars_fix = get_allvars_fix('SImon', 'siconc') - -# fixed_cubes = siconc_fix.fix_metadata(cubes_2d) -# fixed_cubes = allvars_fix.fix_metadata(fixed_cubes) - -# cube = check_siconc_metadata(fixed_cubes, 'siconc', -# 'Sea-Ice Area Percentage (Ocean Grid)') -# check_time(cube) -# check_lat_lon(cube) -# check_typesi(cube) - -# np.testing.assert_allclose( -# cube.data, -# [[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]], -# ) - - -# def test_get_siconca_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('ICON', 'ICON', 'SImon', 'siconca') -# assert fix == [Siconca(None), AllVars(None)] - - -# def test_siconca_fix(cubes_2d): -# """Test fix.""" -# vardef = get_var_info('ICON', 'SImon', 'siconca') -# extra_facets = get_extra_facets('ICON', 'ICON', 'SImon', 'siconca', ()) -# siconca_fix = Siconca(vardef, extra_facets=extra_facets) -# allvars_fix = get_allvars_fix('SImon', 'siconca') - -# fixed_cubes = siconca_fix.fix_metadata(cubes_2d) -# fixed_cubes = allvars_fix.fix_metadata(fixed_cubes) - -# cube = check_siconc_metadata(fixed_cubes, 'siconca', -# 'Sea-Ice Area Percentage (Atmospheric Grid)') -# check_time(cube) -# check_lat_lon(cube) -# check_typesi(cube) - -# np.testing.assert_allclose( -# cube.data, -# [[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]], -# ) - - -# # Test ta (for height and plev coordinate) - - -# def test_get_ta_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('ICON', 'ICON', 'Amon', 'ta') -# assert fix == [AllVars(None)] - - -# def test_ta_fix(cubes_3d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'ta') -# fixed_cubes = fix.fix_metadata(cubes_3d) - -# cube = check_ta_metadata(fixed_cubes) -# check_time(cube) -# check_height(cube) -# check_lat_lon(cube) - - -# def test_ta_fix_no_plev_bounds(cubes_3d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'ta') -# cubes = CubeList([ -# cubes_3d.extract_cube(NameConstraint(var_name='ta')), -# cubes_3d.extract_cube(NameConstraint(var_name='pfull')), -# ]) -# fixed_cubes = fix.fix_metadata(cubes) - -# cube = check_ta_metadata(fixed_cubes) -# check_time(cube) -# check_height(cube, plev_has_bounds=False) -# check_lat_lon(cube) - - -# # Test tas (for height2m coordinate) - - -# def test_get_tas_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('ICON', 'ICON', 'Amon', 'tas') -# assert fix == [AllVars(None)] - - -# def test_tas_fix(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'tas') -# fixed_cubes = fix.fix_metadata(cubes_2d) - -# cube = check_tas_metadata(fixed_cubes) -# check_time(cube) -# check_lat_lon(cube) -# check_heightxm(cube, 2.0) - - -# def test_tas_spatial_index_coord_already_present(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'tas') - -# index_coord = DimCoord(np.arange(8), var_name='ncells') -# cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) -# cube.add_dim_coord(index_coord, 1) -# fixed_cubes = fix.fix_metadata(cubes_2d) - -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# check_lat_lon(cube) - - -# def test_tas_scalar_height2m_already_present(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'tas') - -# # Scalar height (with wrong metadata) already present -# height_coord = AuxCoord(2.0, var_name='h', standard_name='height') -# cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) -# cube.add_aux_coord(height_coord, ()) -# fixed_cubes = fix.fix_metadata(cubes_2d) - -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.shape == (1, 8) -# check_heightxm(cube, 2.0) - - -# def test_tas_dim_height2m_already_present(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'tas') - -# # Dimensional coordinate height (with wrong metadata) already present -# height_coord = AuxCoord(2.0, var_name='h', standard_name='height') -# cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) -# cube.add_aux_coord(height_coord, ()) -# cube = iris.util.new_axis(cube, scalar_coord='height') -# cube.transpose((1, 0, 2)) -# cubes = CubeList([cube]) -# fixed_cubes = fix.fix_metadata(cubes) - -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.shape == (1, 8) -# check_heightxm(cube, 2.0) - - -# # Test uas (for height10m coordinate) - - -# def test_get_uas_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('ICON', 'ICON', 'Amon', 'uas') -# assert fix == [AllVars(None)] - - -# def test_uas_fix(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'uas') -# fixed_cubes = fix.fix_metadata(cubes_2d) - -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.var_name == 'uas' -# assert cube.standard_name == 'eastward_wind' -# assert cube.long_name == 'Eastward Near-Surface Wind' -# assert cube.units == 'm s-1' - -# check_time(cube) -# check_lat_lon(cube) -# assert cube.coords('height') -# height = cube.coord('height') -# assert height.var_name == 'height' -# assert height.standard_name == 'height' -# assert height.long_name == 'height' -# assert height.units == 'm' -# assert height.attributes == {'positive': 'up'} -# np.testing.assert_allclose(height.points, [10.0]) -# assert height.bounds is None - - -# def test_uas_scalar_height10m_already_present(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'uas') - -# # Scalar height (with wrong metadata) already present -# height_coord = AuxCoord(10.0, var_name='h', standard_name='height') -# cube = cubes_2d.extract_cube(NameConstraint(var_name='uas')) -# cube.add_aux_coord(height_coord, ()) -# fixed_cubes = fix.fix_metadata(cubes_2d) - -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.shape == (1, 8) -# check_heightxm(cube, 10.0) - - -# def test_uas_dim_height10m_already_present(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'uas') - -# # Dimensional coordinate height (with wrong metadata) already present -# height_coord = AuxCoord(10.0, var_name='h', standard_name='height') -# cube = cubes_2d.extract_cube(NameConstraint(var_name='uas')) -# cube.add_aux_coord(height_coord, ()) -# cube = iris.util.new_axis(cube, scalar_coord='height') -# cube.transpose((1, 0, 2)) -# cubes = CubeList([cube]) -# fixed_cubes = fix.fix_metadata(cubes) - -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.shape == (1, 8) -# check_heightxm(cube, 10.0) - - -# # Test fix with regular grid and 2D latitudes and longitude - - -# def test_regular_grid_fix(cubes_regular_grid): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'tas') -# fixed_cubes = fix.fix_metadata(cubes_regular_grid) - -# cube = check_tas_metadata(fixed_cubes) -# assert cube.coords('time', dim_coords=True, dimensions=0) -# assert cube.coords('latitude', dim_coords=True, dimensions=1) -# assert cube.coords('longitude', dim_coords=True, dimensions=2) -# assert cube.coords('height', dim_coords=False, dimensions=()) - - -# def test_2d_lat_lon_grid_fix(cubes_2d_lat_lon_grid): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'tas') -# fixed_cubes = fix.fix_metadata(cubes_2d_lat_lon_grid) - -# cube = check_tas_metadata(fixed_cubes) -# assert cube.coords('time', dim_coords=True, dimensions=0) -# assert cube.coords('latitude', dim_coords=False, dimensions=(1, 2)) -# assert cube.coords('longitude', dim_coords=False, dimensions=(1, 2)) -# assert cube.coords('height', dim_coords=False, dimensions=()) - - -# # Test fix with empty standard_name - - -# def test_empty_standard_name_fix(cubes_2d): -# """Test fix.""" -# # We know that tas has a standard name, but this being native model output -# # there may be variables with no standard name. The code is designed to -# # handle this gracefully and here we test it with an artificial, but -# # realistic case. -# vardef = get_var_info('ICON', 'Amon', 'tas') -# original_standard_name = vardef.standard_name -# vardef.standard_name = '' -# extra_facets = get_extra_facets('ICON', 'ICON', 'Amon', 'tas', ()) -# fix = AllVars(vardef, extra_facets=extra_facets) -# fixed_cubes = fix.fix_metadata(cubes_2d) - -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.var_name == 'tas' -# assert cube.standard_name is None -# assert cube.long_name == 'Near-Surface Air Temperature' -# assert cube.units == 'K' - -# # Restore original standard_name of tas -# vardef.standard_name = original_standard_name - - -# # Test automatic addition of missing coordinates - - -# def test_add_time(cubes_2d): -# """Test fix.""" -# # Remove time from tas cube to test automatic addition -# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) -# uas_cube = cubes_2d.extract_cube(NameConstraint(var_name='uas')) -# tas_cube = tas_cube[0] -# tas_cube.remove_coord('time') -# cubes = CubeList([tas_cube, uas_cube]) - -# fix = get_allvars_fix('Amon', 'tas') -# fixed_cubes = fix.fix_metadata(cubes) - -# cube = check_tas_metadata(fixed_cubes) -# assert cube.shape == (1, 8) -# check_time(cube) - - -# def test_add_time_fail(): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'ta') -# cube = Cube(1, var_name='ta', units='K') -# cubes = CubeList([ -# cube, -# Cube(1, var_name='tas', units='K'), -# ]) -# msg = "Cannot add required coordinate 'time' to variable 'ta'" -# with pytest.raises(ValueError, match=msg): -# fix._add_time(cube, cubes) - - -# def test_add_latitude(cubes_2d, tmp_path): -# """Test fix.""" -# # Remove latitude from tas cube to test automatic addition -# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) -# tas_cube.remove_coord('latitude') -# tas_cube.attributes['grid_file_uri'] = ( -# 'https://github.com/ESMValGroup/ESMValCore/raw/main/tests/' -# 'integration/cmor/_fixes/test_data/icon_grid.nc' -# ) -# cubes = CubeList([tas_cube]) -# fix = get_allvars_fix('Amon', 'tas') - -# # Temporary overwrite default cache location for downloads -# original_cache_dir = fix.CACHE_DIR -# fix.CACHE_DIR = tmp_path - -# assert len(fix._horizontal_grids) == 0 -# fixed_cubes = fix.fix_metadata(cubes) - -# cube = check_tas_metadata(fixed_cubes) -# assert cube.shape == (1, 8) -# check_lat_lon(cube) -# assert len(fix._horizontal_grids) == 1 -# assert 'icon_grid.nc' in fix._horizontal_grids - -# # Restore cache location -# fix.CACHE_DIR = original_cache_dir - - -# def test_add_longitude(cubes_2d, tmp_path): -# """Test fix.""" -# # Remove longitude from tas cube to test automatic addition -# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) -# tas_cube.remove_coord('longitude') -# tas_cube.attributes['grid_file_uri'] = ( -# 'https://github.com/ESMValGroup/ESMValCore/raw/main/tests/' -# 'integration/cmor/_fixes/test_data/icon_grid.nc' -# ) -# cubes = CubeList([tas_cube]) -# fix = get_allvars_fix('Amon', 'tas') - -# # Temporary overwrite default cache location for downloads -# original_cache_dir = fix.CACHE_DIR -# fix.CACHE_DIR = tmp_path - -# assert len(fix._horizontal_grids) == 0 -# fixed_cubes = fix.fix_metadata(cubes) - -# cube = check_tas_metadata(fixed_cubes) -# assert cube.shape == (1, 8) -# check_lat_lon(cube) -# assert len(fix._horizontal_grids) == 1 -# assert 'icon_grid.nc' in fix._horizontal_grids - -# # Restore cache location -# fix.CACHE_DIR = original_cache_dir - - -# def test_add_latitude_longitude(cubes_2d, tmp_path): -# """Test fix.""" -# # Remove latitude and longitude from tas cube to test automatic addition -# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) -# tas_cube.remove_coord('latitude') -# tas_cube.remove_coord('longitude') -# tas_cube.attributes['grid_file_uri'] = ( -# 'https://github.com/ESMValGroup/ESMValCore/raw/main/tests/' -# 'integration/cmor/_fixes/test_data/icon_grid.nc' -# ) -# cubes = CubeList([tas_cube]) -# fix = get_allvars_fix('Amon', 'tas') - -# # Temporary overwrite default cache location for downloads -# original_cache_dir = fix.CACHE_DIR -# fix.CACHE_DIR = tmp_path - -# assert len(fix._horizontal_grids) == 0 -# fixed_cubes = fix.fix_metadata(cubes) - -# cube = check_tas_metadata(fixed_cubes) -# assert cube.shape == (1, 8) -# check_lat_lon(cube) -# assert len(fix._horizontal_grids) == 1 -# assert 'icon_grid.nc' in fix._horizontal_grids - -# # Restore cache location -# fix.CACHE_DIR = original_cache_dir - - -# def test_add_latitude_fail(cubes_2d): -# """Test fix.""" -# # Remove latitude from tas cube to test automatic addition -# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) -# tas_cube.remove_coord('latitude') -# cubes = CubeList([tas_cube]) -# fix = get_allvars_fix('Amon', 'tas') - -# msg = "Failed to add missing latitude coordinate to cube" -# with pytest.raises(ValueError, match=msg): -# fix.fix_metadata(cubes) - - -# def test_add_longitude_fail(cubes_2d): -# """Test fix.""" -# # Remove longitude from tas cube to test automatic addition -# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) -# tas_cube.remove_coord('longitude') -# cubes = CubeList([tas_cube]) -# fix = get_allvars_fix('Amon', 'tas') - -# msg = "Failed to add missing longitude coordinate to cube" -# with pytest.raises(ValueError, match=msg): -# fix.fix_metadata(cubes) - - -# def test_add_coord_from_grid_file_fail_invalid_coord(): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'tas') - -# msg = r"coord_name must be one of .* got 'invalid_coord_name'" -# with pytest.raises(ValueError, match=msg): -# fix._add_coord_from_grid_file(mock.sentinel.cube, 'invalid_coord_name', -# 'invalid_target_name') - - -# def test_add_coord_from_grid_file_fail_no_url(): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'tas') - -# msg = ("Cube does not contain the attribute 'grid_file_uri' necessary to " -# "download the ICON horizontal grid file") -# with pytest.raises(ValueError, match=msg): -# fix._add_coord_from_grid_file(Cube(0), 'grid_latitude', 'latitude') - - -# def test_add_coord_from_grid_fail_no_unnamed_dim(cubes_2d, tmp_path): -# """Test fix.""" -# # Remove latitude from tas cube to test automatic addition -# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) -# tas_cube.remove_coord('latitude') -# tas_cube.attributes['grid_file_uri'] = ( -# 'https://github.com/ESMValGroup/ESMValCore/raw/main/tests/' -# 'integration/cmor/_fixes/test_data/icon_grid.nc' -# ) -# index_coord = DimCoord(np.arange(8), var_name='ncells') -# tas_cube.add_dim_coord(index_coord, 1) -# fix = get_allvars_fix('Amon', 'tas') - -# # Temporary overwrite default cache location for downloads -# original_cache_dir = fix.CACHE_DIR -# fix.CACHE_DIR = tmp_path - -# msg = ("Cannot determine coordinate dimension for coordinate 'latitude', " -# "cube does not contain a single unnamed dimension") -# with pytest.raises(ValueError, match=msg): -# fix._add_coord_from_grid_file(tas_cube, 'grid_latitude', 'latitude') - -# # Restore cache location -# fix.CACHE_DIR = original_cache_dir - - -# def test_add_coord_from_grid_fail_two_unnamed_dims(cubes_2d, tmp_path): -# """Test fix.""" -# # Remove latitude from tas cube to test automatic addition -# tas_cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) -# tas_cube.remove_coord('latitude') -# tas_cube.attributes['grid_file_uri'] = ( -# 'https://github.com/ESMValGroup/ESMValCore/raw/main/tests/' -# 'integration/cmor/_fixes/test_data/icon_grid.nc' -# ) -# tas_cube = iris.util.new_axis(tas_cube) -# fix = get_allvars_fix('Amon', 'tas') - -# # Temporary overwrite default cache location for downloads -# original_cache_dir = fix.CACHE_DIR -# fix.CACHE_DIR = tmp_path - -# msg = ("Cannot determine coordinate dimension for coordinate 'latitude', " -# "cube does not contain a single unnamed dimension") -# with pytest.raises(ValueError, match=msg): -# fix._add_coord_from_grid_file(tas_cube, 'grid_latitude', 'latitude') - -# # Restore cache location -# fix.CACHE_DIR = original_cache_dir - - -# @mock.patch('esmvalcore.cmor._fixes.icon._base_fixes.requests', autospec=True) -# def test_get_horizontal_grid_cached_in_dict(mock_requests): -# """Test fix.""" -# cube = Cube(0, attributes={'grid_file_uri': 'cached_grid_url.nc'}) -# fix = get_allvars_fix('Amon', 'tas') -# fix._horizontal_grids['cached_grid_url.nc'] = mock.sentinel.grid - -# grid = fix.get_horizontal_grid(cube) -# assert grid == mock.sentinel.grid -# assert mock_requests.mock_calls == [] - - -# @mock.patch('esmvalcore.cmor._fixes.icon._base_fixes.requests', autospec=True) -# def test_get_horizontal_grid_cached_in_file(mock_requests, tmp_path): -# """Test fix.""" -# cube = Cube(0, attributes={ -# 'grid_file_uri': 'https://temporary.url/this/is/the/grid_file.nc'}) -# fix = get_allvars_fix('Amon', 'tas') -# assert len(fix._horizontal_grids) == 0 - -# # Save temporary grid file -# grid_cube = Cube(0, var_name='grid') -# iris.save(grid_cube, str(tmp_path / 'grid_file.nc')) - -# # Temporary overwrite default cache location for downloads -# original_cache_dir = fix.CACHE_DIR -# fix.CACHE_DIR = tmp_path - -# grid = fix.get_horizontal_grid(cube) -# assert isinstance(grid, CubeList) -# assert len(grid) == 1 -# assert grid[0].var_name == 'grid' -# assert len(fix._horizontal_grids) == 1 -# assert 'grid_file.nc' in fix._horizontal_grids -# assert mock_requests.mock_calls == [] - -# # Restore cache location -# fix.CACHE_DIR = original_cache_dir - - -# def test_get_horizontal_grid_cache_file_too_old(tmp_path): -# """Test fix.""" -# cube = Cube(0, attributes={ -# 'grid_file_uri': 'https://github.com/ESMValGroup/ESMValCore/raw/main/' -# 'tests/integration/cmor/_fixes/test_data/' -# 'icon_grid.nc'}) -# fix = get_allvars_fix('Amon', 'tas') -# assert len(fix._horizontal_grids) == 0 - -# # Save temporary grid file -# grid_cube = Cube(0, var_name='grid') -# iris.save(grid_cube, str(tmp_path / 'icon_grid.nc')) - -# # Temporary overwrite default cache location for downloads and cache -# # validity duration -# original_cache_dir = fix.CACHE_DIR -# original_cache_validity = fix.CACHE_VALIDITY -# fix.CACHE_DIR = tmp_path -# fix.CACHE_VALIDITY = -1 - -# grid = fix.get_horizontal_grid(cube) -# assert isinstance(grid, CubeList) -# assert len(grid) == 1 -# assert grid[0].var_name == 'cell_area' -# assert len(fix._horizontal_grids) == 1 -# assert 'icon_grid.nc' in fix._horizontal_grids - -# # Restore cache location -# fix.CACHE_DIR = original_cache_dir -# fix.CACHE_VALIDITY = original_cache_validity - - -# # Test with single-dimension cubes - - -# def test_only_time(): -# """Test fix.""" -# # We know that ta has dimensions time, plev19, latitude, longitude, but the -# # ICON CMORizer is designed to check for the presence of each dimension -# # individually. To test this, remove all but one dimension of ta to create -# # an artificial, but realistic test case. -# vardef = get_var_info('ICON', 'Amon', 'ta') -# original_dimensions = vardef.dimensions -# vardef.dimensions = ['time'] -# extra_facets = get_extra_facets('ICON', 'ICON', 'Amon', 'ta', ()) -# fix = AllVars(vardef, extra_facets=extra_facets) - -# # Create cube with only a single dimension -# time_coord = DimCoord([0.0, 1.0], var_name='time', standard_name='time', -# long_name='time', units='days since 1850-01-01') -# cubes = CubeList([ -# Cube([1, 1], var_name='ta', units='K', -# dim_coords_and_dims=[(time_coord, 0)]), -# ]) -# fixed_cubes = fix.fix_metadata(cubes) - -# # Check cube metadata -# cube = check_ta_metadata(fixed_cubes) - -# # Check cube data -# assert cube.shape == (2,) -# np.testing.assert_equal(cube.data, [1, 1]) - -# # Check time metadata -# assert cube.coords('time') -# new_time_coord = cube.coord('time', dim_coords=True) -# assert new_time_coord.var_name == 'time' -# assert new_time_coord.standard_name == 'time' -# assert new_time_coord.long_name == 'time' -# assert new_time_coord.units == 'days since 1850-01-01' - -# # Check time data -# np.testing.assert_allclose(new_time_coord.points, [0.0, 1.0]) -# np.testing.assert_allclose(new_time_coord.bounds, -# [[-0.5, 0.5], [0.5, 1.5]]) - -# # Restore original dimensions of ta -# vardef.dimensions = original_dimensions - - -# def test_only_height(): -# """Test fix.""" -# # We know that ta has dimensions time, plev19, latitude, longitude, but the -# # ICON CMORizer is designed to check for the presence of each dimension -# # individually. To test this, remove all but one dimension of ta to create -# # an artificial, but realistic test case. -# vardef = get_var_info('ICON', 'Amon', 'ta') -# original_dimensions = vardef.dimensions -# vardef.dimensions = ['plev19'] -# extra_facets = get_extra_facets('ICON', 'ICON', 'Amon', 'ta', ()) -# fix = AllVars(vardef, extra_facets=extra_facets) - -# # Create cube with only a single dimension -# height_coord = DimCoord([1000.0, 100.0], var_name='height', -# standard_name='height', units='cm') -# cubes = CubeList([ -# Cube([1, 1], var_name='ta', units='K', -# dim_coords_and_dims=[(height_coord, 0)]), -# ]) -# fixed_cubes = fix.fix_metadata(cubes) - -# # Check cube metadata -# cube = check_ta_metadata(fixed_cubes) - -# # Check cube data -# assert cube.shape == (2,) -# np.testing.assert_equal(cube.data, [1, 1]) - -# # Check height metadata -# assert cube.coords('height', dim_coords=True) -# new_height_coord = cube.coord('height') -# assert new_height_coord.var_name == 'height' -# assert new_height_coord.standard_name == 'height' -# assert new_height_coord.long_name == 'height' -# assert new_height_coord.units == 'm' -# assert new_height_coord.attributes == {'positive': 'up'} - -# # Check height data -# np.testing.assert_allclose(new_height_coord.points, [1.0, 10.0]) -# assert new_height_coord.bounds is None - -# # Restore original dimensions of ta -# vardef.dimensions = original_dimensions - - -# def test_only_latitude(): -# """Test fix.""" -# # We know that ta has dimensions time, plev19, latitude, longitude, but the -# # ICON CMORizer is designed to check for the presence of each dimension -# # individually. To test this, remove all but one dimension of ta to create -# # an artificial, but realistic test case. -# vardef = get_var_info('ICON', 'Amon', 'ta') -# original_dimensions = vardef.dimensions -# vardef.dimensions = ['latitude'] -# extra_facets = get_extra_facets('ICON', 'ICON', 'Amon', 'ta', ()) -# fix = AllVars(vardef, extra_facets=extra_facets) - -# # Create cube with only a single dimension -# lat_coord = DimCoord([0.0, 10.0], var_name='lat', standard_name='latitude', -# units='degrees') -# cubes = CubeList([ -# Cube([1, 1], var_name='ta', units='K', -# dim_coords_and_dims=[(lat_coord, 0)]), -# ]) -# fixed_cubes = fix.fix_metadata(cubes) - -# # Check cube metadata -# cube = check_ta_metadata(fixed_cubes) - -# # Check cube data -# assert cube.shape == (2,) -# np.testing.assert_equal(cube.data, [1, 1]) - -# # Check latitude metadata -# assert cube.coords('latitude', dim_coords=True) -# new_lat_coord = cube.coord('latitude') -# assert new_lat_coord.var_name == 'lat' -# assert new_lat_coord.standard_name == 'latitude' -# assert new_lat_coord.long_name == 'latitude' -# assert new_lat_coord.units == 'degrees_north' - -# # Check latitude data -# np.testing.assert_allclose(new_lat_coord.points, [0.0, 10.0]) -# assert new_lat_coord.bounds is None - -# # Restore original dimensions of ta -# vardef.dimensions = original_dimensions - - -# def test_only_longitude(): -# """Test fix.""" -# # We know that ta has dimensions time, plev19, latitude, longitude, but the -# # ICON CMORizer is designed to check for the presence of each dimension -# # individually. To test this, remove all but one dimension of ta to create -# # an artificial, but realistic test case. -# vardef = get_var_info('ICON', 'Amon', 'ta') -# original_dimensions = vardef.dimensions -# vardef.dimensions = ['longitude'] -# extra_facets = get_extra_facets('ICON', 'ICON', 'Amon', 'ta', ()) -# fix = AllVars(vardef, extra_facets=extra_facets) - -# # Create cube with only a single dimension -# lon_coord = DimCoord([0.0, 180.0], var_name='lon', -# standard_name='longitude', units='degrees') -# cubes = CubeList([ -# Cube([1, 1], var_name='ta', units='K', -# dim_coords_and_dims=[(lon_coord, 0)]), -# ]) -# fixed_cubes = fix.fix_metadata(cubes) - -# # Check cube metadata -# cube = check_ta_metadata(fixed_cubes) - -# # Check cube data -# assert cube.shape == (2,) -# np.testing.assert_equal(cube.data, [1, 1]) - -# # Check longitude metadata -# assert cube.coords('longitude', dim_coords=True) -# new_lon_coord = cube.coord('longitude') -# assert new_lon_coord.var_name == 'lon' -# assert new_lon_coord.standard_name == 'longitude' -# assert new_lon_coord.long_name == 'longitude' -# assert new_lon_coord.units == 'degrees_east' - -# # Check longitude data -# np.testing.assert_allclose(new_lon_coord.points, [0.0, 180.0]) -# assert new_lon_coord.bounds is None - -# # Restore original dimensions of ta -# vardef.dimensions = original_dimensions - - -# # Test variable not available in file - - -# def test_var_not_available_pr(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'pr') -# msg = "Variable 'pr' used to extract 'pr' is not available in input file" -# with pytest.raises(ValueError, match=msg): -# fix.fix_metadata(cubes_2d) - - -# def test_var_not_available_ps(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'ps') -# msg = "Variable 'x' used to extract 'ps' is not available in input file" -# with pytest.raises(ValueError, match=msg): -# fix.get_cube(cubes_2d, var_name='x') - - -# # Test fix with invalid time units - - -# def test_invalid_time_units(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'tas') -# for cube in cubes_2d: -# cube.coord('time').attributes['invalid_units'] = 'month as %Y%m%d.%f' -# msg = "Expected time units" -# with pytest.raises(ValueError, match=msg): -# fix.fix_metadata(cubes_2d) diff --git a/tests/integration/cmor/_fixes/emac/test_emac.py b/tests/integration/cmor/_fixes/emac/test_emac.py index d5fb91a238..2cf1ec49c1 100644 --- a/tests/integration/cmor/_fixes/emac/test_emac.py +++ b/tests/integration/cmor/_fixes/emac/test_emac.py @@ -10,7 +10,7 @@ from iris.cube import Cube, CubeList from esmvalcore._config import get_extra_facets -from esmvalcore.cmor._fixes.emac.emac import AllVars +from esmvalcore.cmor._fixes.emac.emac import AllVars, Clt from esmvalcore.cmor.fix import Fix from esmvalcore.cmor.table import get_var_info @@ -419,6 +419,61 @@ def test_awhea_fix(cubes_omon_2d): check_lon(cube) +def test_get_clivi_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'clivi') + assert fix == [AllVars(None)] + + +def test_clivi_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'clivi') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'clivi' + assert cube.standard_name == 'atmosphere_mass_content_of_cloud_ice' + assert cube.long_name == 'Ice Water Path' + assert cube.units == 'kg m-2' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + +def test_get_clt_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'clt') + assert fix == [Clt(None), AllVars(None)] + + +def test_clt_fix(cubes_amon_2d): + """Test fix.""" + vardef = get_var_info('EMAC', 'Amon', 'clt') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'clt', ()) + fix = Clt(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + fix = get_allvars_fix('Amon', 'clt') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'clt' + assert cube.standard_name == 'cloud_area_fraction' + assert cube.long_name == 'Total Cloud Cover Percentage' + assert cube.units == '%' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + assert np.min(cube.data) > 0.0 + assert np.max(cube.data) > 1.0 + assert np.max(cube.data) <= 100.0 + + # Test areacella and areacello (for extra_facets, and grid_latitude and # grid_longitude coordinates) From b12741672a0950b90993bc0f791fc7727b3a5165 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 14 Apr 2022 18:19:28 +0200 Subject: [PATCH 23/52] Added further tests for EMAC CMORizer --- .../_config/extra_facets/emac-mappings.yml | 10 +- esmvalcore/cmor/_fixes/emac/emac.py | 3 - .../integration/cmor/_fixes/emac/test_emac.py | 135 +++++++++++++++++- 3 files changed, 133 insertions(+), 15 deletions(-) diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml index b6704a3812..41434cc0d9 100644 --- a/esmvalcore/_config/extra_facets/emac-mappings.yml +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -22,9 +22,6 @@ EMAC: co2mass: raw_name: MP_CO2_ave channel: tracer_pdef_gp - cod_sw_b01: # non-CMOR variable - raw_name: tau_cld_sw_B01_ave - channel: Amon evspsbl: raw_name: evap_ave channel: Amon @@ -216,5 +213,8 @@ EMAC: # Elements missing from original mapping table # Note: mixed channels are not supported yet -# - AIRC_NO_s: {airc_NO: import_grid, geopot_ave: Amon} -> mixed channels -# - VOLC_SO2_s: {VOLC_SO2_SO2: import_grid, geopot_ave: Amon} -> mixed channels +# AIRC_NO_s: {airc_NO: import_grid, geopot_ave: Amon} -> mixed channels +# VOLC_SO2_s: {VOLC_SO2_SO2: import_grid, geopot_ave: Amon} -> mixed channels +# cod_sw_b01: -> no test data available +# raw_name: tau_cld_sw_B01_ave +# channel: Amon diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py index 2fcff1d2c4..b7987be24a 100644 --- a/esmvalcore/cmor/_fixes/emac/emac.py +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -199,9 +199,6 @@ def fix_metadata(self, cubes): return CubeList([cube]) -Cod_sw_b01 = SetUnitsTo1SumOverZ # noqa: N801 - - Evspsbl = NegateData diff --git a/tests/integration/cmor/_fixes/emac/test_emac.py b/tests/integration/cmor/_fixes/emac/test_emac.py index 2cf1ec49c1..a00294c800 100644 --- a/tests/integration/cmor/_fixes/emac/test_emac.py +++ b/tests/integration/cmor/_fixes/emac/test_emac.py @@ -10,7 +10,7 @@ from iris.cube import Cube, CubeList from esmvalcore._config import get_extra_facets -from esmvalcore.cmor._fixes.emac.emac import AllVars, Clt +from esmvalcore.cmor._fixes.emac.emac import AllVars, Clt, Clwvi, Evspsbl from esmvalcore.cmor.fix import Fix from esmvalcore.cmor.table import get_var_info @@ -100,7 +100,7 @@ def check_siconc_metadata(cubes, var_name, long_name): return cube -def check_time(cube): +def check_time(cube, n_points=1): """Check time coordinate of cube.""" assert cube.coords('time', dim_coords=True) time = cube.coord('time', dim_coords=True) @@ -109,8 +109,17 @@ def check_time(cube): assert time.long_name == 'time' assert time.units == Unit('day since 1849-01-01 00:00:00', calendar='gregorian') - np.testing.assert_allclose(time.points, [55181.9930555556]) - assert time.bounds is None + if n_points == 1: + np.testing.assert_allclose(time.points, [55181.9930555556]) + assert time.bounds is None + elif n_points == 2: + np.testing.assert_allclose(time.points, [55151.25, 55151.666667]) + np.testing.assert_allclose( + time.bounds, + [[55151.04166667, 55151.45833333], [55151.45833333, 55151.875]], + ) + else: + assert False, "Invalid n_points" assert time.attributes == {} @@ -418,6 +427,12 @@ def test_awhea_fix(cubes_omon_2d): check_lat(cube) check_lon(cube) + np.testing.assert_allclose( + cube.data[:, :, 0], + [[-203.94414, -16.695345, 74.117096, 104.992195]], + rtol=1e-6, + ) + def test_get_clivi_fix(): """Test getting of fix.""" @@ -441,6 +456,12 @@ def test_clivi_fix(cubes_amon_2d): check_lat(cube) check_lon(cube) + np.testing.assert_allclose( + cube.data[:, :, 0], + [[0.01435195, 0.006420649, 0.0007885683, 0.01154814]], + rtol=1e-6, + ) + def test_get_clt_fix(): """Test getting of fix.""" @@ -469,9 +490,109 @@ def test_clt_fix(cubes_amon_2d): check_lat(cube) check_lon(cube) - assert np.min(cube.data) > 0.0 - assert np.max(cube.data) > 1.0 - assert np.max(cube.data) <= 100.0 + np.testing.assert_allclose( + cube.data[:, :, 0], + [[86.79899, 58.01009, 34.01953, 85.48493]], + rtol=1e-6, + ) + + +def test_get_clwvi_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'clwvi') + assert fix == [Clwvi(None), AllVars(None)] + + +def test_clwvi_fix(cubes_amon_2d): + """Test fix.""" + vardef = get_var_info('EMAC', 'Amon', 'clwvi') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'clwvi', ()) + fix = Clwvi(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + fix = get_allvars_fix('Amon', 'clwvi') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'clwvi' + assert cube.standard_name == ('atmosphere_mass_content_of_cloud_' + 'condensed_water') + assert cube.long_name == 'Condensed Water Path' + assert cube.units == 'kg m-2' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[0.20945302, 0.01015517, 0.01444221, 0.10618545]], + rtol=1e-6, + ) + + +def test_get_co2mass_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'co2mass') + assert fix == [AllVars(None)] + + +def test_co2mass_fix(cubes_tracer_pdef_gp): + """Test fix.""" + fix = get_allvars_fix('Amon', 'co2mass') + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'co2mass' + assert cube.standard_name == 'atmosphere_mass_of_carbon_dioxide' + assert cube.long_name == 'Total Atmospheric Mass of CO2' + assert cube.units == 'kg' + + check_time(cube, n_points=2) + + np.testing.assert_allclose( + cube.data, + [2.855254e+15, 2.85538e+15], + rtol=1e-6, + ) + + +def test_get_evspsbl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'evspsbl') + assert fix == [Evspsbl(None), AllVars(None)] + + +def test_evspsbl_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'evspsbl') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + + vardef = get_var_info('EMAC', 'Amon', 'evspsbl') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'evspsbl', ()) + fix = Evspsbl(vardef, extra_facets=extra_facets) + cube = fix.fix_data(cube) + + assert cube.var_name == 'evspsbl' + assert cube.standard_name == 'water_evapotranspiration_flux' + assert cube.long_name == ('Evaporation Including Sublimation and ' + 'Transpiration') + assert cube.units == 'kg m-2 s-1' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[3.636807e-05, 3.438968e-07, 6.235108e-05, 1.165336e-05]], + rtol=1e-6, + ) # Test areacella and areacello (for extra_facets, and grid_latitude and From 6b410507b4c3e93068dde5b9ee566a5b4ba42d0e Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Tue, 19 Apr 2022 13:21:10 +0200 Subject: [PATCH 24/52] Removed unused custom CMOR tables --- .../_config/extra_facets/emac-mappings.yml | 100 +++++++++--------- .../tables/custom/CMOR_ANTHNT_AER_TC_s.dat | 23 ---- .../cmor/tables/custom/CMOR_ANTHNT_CO_s.dat | 23 ---- .../cmor/tables/custom/CMOR_ANTHNT_NO_s.dat | 23 ---- .../cmor/tables/custom/CMOR_ANTHNT_SO2_s.dat | 23 ---- .../cmor/tables/custom/CMOR_BB_AER_TC_s.dat | 23 ---- .../cmor/tables/custom/CMOR_BB_CO_s.dat | 23 ---- .../cmor/tables/custom/CMOR_BB_NO_s.dat | 23 ---- .../cmor/tables/custom/CMOR_BB_SO2_s.dat | 23 ---- .../cmor/tables/custom/CMOR_ROAD_AER_BC.dat | 23 ---- .../cmor/tables/custom/CMOR_ROAD_NO.dat | 23 ---- .../cmor/tables/custom/CMOR_SHIP_AER_BC_s.dat | 23 ---- .../cmor/tables/custom/CMOR_SHIP_NO_s.dat | 23 ---- .../cmor/tables/custom/CMOR_SHIP_SO2_s.dat | 23 ---- .../cmor/tables/custom/CMOR_TN_GHG_CH4.dat | 23 ---- .../cmor/tables/custom/CMOR_TN_GHG_CO2.dat | 23 ---- .../cmor/tables/custom/CMOR_TN_GHG_N2O.dat | 23 ---- 17 files changed, 51 insertions(+), 417 deletions(-) delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_ANTHNT_AER_TC_s.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_ANTHNT_CO_s.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_ANTHNT_NO_s.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_ANTHNT_SO2_s.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_BB_AER_TC_s.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_BB_CO_s.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_BB_NO_s.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_BB_SO2_s.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_ROAD_AER_BC.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_ROAD_NO.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_SHIP_AER_BC_s.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_SHIP_NO_s.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_SHIP_SO2_s.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CH4.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CO2.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_TN_GHG_N2O.dat diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml index 41434cc0d9..7b0d25e444 100644 --- a/esmvalcore/_config/extra_facets/emac-mappings.yml +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -33,8 +33,6 @@ EMAC: hfss: raw_name: ahfs_ave channel: Amon - lnox: # non-CMOR variable; derived from NOxcg_ave, NOxic_ave - channel: lnox_PaR_T_gp od550aer: raw_name: aot_opt_TOT_550_total_ave channel: AERmon @@ -105,49 +103,6 @@ EMAC: raw_name: tsurf_ave channel: Amon - # Forcings (non-CMOR variables) - ANTHNT_AER_TC_s: # derived from ANTHNT_AER_BC, ANTHNT_AER_OC - channel: import_grid - ANTHNT_CO_s: - raw_name: ANTHNT_CO - channel: import_grid - ANTHNT_NO_s: - raw_name: ANTHNT_NO - channel: import_grid - ANTHNT_SO2_s: - raw_name: ANTHNT_SO2 - channel: import_grid - BB_AER_TC_s: # derived from BB_AER_BC, BB_AER_OC - channel: import_grid - BB_CO_s: - raw_name: BB_CO - channel: import_grid - BB_NO_s: - raw_name: BB_NO - channel: import_grid - BB_SO2_s: - raw_name: BB_SO2 - channel: import_grid - ROAD_AER_BC: - channel: import_grid - ROAD_NO: - channel: import_grid - SHIP_AER_BC_s: - raw_name: SHIP_AER_BC - channel: import_grid - SHIP_NO_s: - raw_name: SHIP_NO - channel: import_grid - SHIP_SO2_s: - raw_name: SHIP_SO2 - channel: import_grid - TN_GHG_CH4: - channel: import_grid - TN_GHG_CO2: - channel: import_grid - TN_GHG_N2O: - channel: import_grid - # Tracers (non-CMOR variables) MP_BC_tot: # derived from MP_BC_ks_ave, MP_BC_as_ave, MP_BC_cs_ave, MP_BC_ki_ave channel: tracer_pdef_gp @@ -212,9 +167,56 @@ EMAC: # Elements missing from original mapping table -# Note: mixed channels are not supported yet -# AIRC_NO_s: {airc_NO: import_grid, geopot_ave: Amon} -> mixed channels -# VOLC_SO2_s: {VOLC_SO2_SO2: import_grid, geopot_ave: Amon} -> mixed channels -# cod_sw_b01: -> no test data available +# +# Mixed channels (not supported yet; this probably needs to be implemented as +# derivation) +# AIRC_NO_s: {airc_NO: import_grid, geopot_ave: Amon} +# VOLC_SO2_s: {VOLC_SO2_SO2: import_grid, geopot_ave: Amon} +# +# No test data available +# cod_sw_b01: # raw_name: tau_cld_sw_B01_ave # channel: Amon +# lnox: # non-CMOR variable; derived from NOxcg_ave, NOxic_ave +# channel: lnox_PaR_T_gp +# ANTHNT_AER_TC_s: # derived from ANTHNT_AER_BC, ANTHNT_AER_OC +# channel: import_grid +# ANTHNT_CO_s: +# raw_name: ANTHNT_CO +# channel: import_grid +# ANTHNT_NO_s: +# raw_name: ANTHNT_NO +# channel: import_grid +# ANTHNT_SO2_s: +# raw_name: ANTHNT_SO2 +# channel: import_grid +# BB_AER_TC_s: # derived from BB_AER_BC, BB_AER_OC +# channel: import_grid +# BB_CO_s: +# raw_name: BB_CO +# channel: import_grid +# BB_NO_s: +# raw_name: BB_NO +# channel: import_grid +# BB_SO2_s: +# raw_name: BB_SO2 +# channel: import_grid +# ROAD_AER_BC: +# channel: import_grid +# ROAD_NO: +# channel: import_grid +# SHIP_AER_BC_s: +# raw_name: SHIP_AER_BC +# channel: import_grid +# SHIP_NO_s: +# raw_name: SHIP_NO +# channel: import_grid +# SHIP_SO2_s: +# raw_name: SHIP_SO2 +# channel: import_grid +# TN_GHG_CH4: +# channel: import_grid +# TN_GHG_CO2: +# channel: import_grid +# TN_GHG_N2O: +# channel: import_grid diff --git a/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_AER_TC_s.dat b/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_AER_TC_s.dat deleted file mode 100644 index c29ca8f326..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_AER_TC_s.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: ANTHNT_AER_TC_s -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg m-2 s-1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Sum of Anthropogenic Aerosol Total Carbon -comment: Sum of Black Carbon and Organic Carbon; summed over all levels -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: ANTHNT_AER_TC_s -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_CO_s.dat b/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_CO_s.dat deleted file mode 100644 index 5b99736d63..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_CO_s.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: ANTHNT_CO_s -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg m-2 s-1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Sum of Anthropogenic CO -comment: Summed over all levels -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: ANTHNT_CO_s -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_NO_s.dat b/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_NO_s.dat deleted file mode 100644 index 4c82e43a21..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_NO_s.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: ANTHNT_NO_s -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg m-2 s-1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Sum of Anthropogenic NO -comment: Summation over all levels; units are given in kg(NO2) m-2 s-1 -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: ANTHNT_NO_s -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_SO2_s.dat b/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_SO2_s.dat deleted file mode 100644 index 5a54580486..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_ANTHNT_SO2_s.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: ANTHNT_SO2_s -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg m-2 s-1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Sum of Anthropogenic SO2 -comment: Summed over all levels -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: ANTHNT_SO2_s -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_BB_AER_TC_s.dat b/esmvalcore/cmor/tables/custom/CMOR_BB_AER_TC_s.dat deleted file mode 100644 index 3e84534af5..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_BB_AER_TC_s.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: BB_AER_TC_s -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg m-2 s-1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Sum of Biomass Burning Aerosol Total Carbon -comment: Sum of BB Black Carbon and BB Organic Carbon; summed over all levels -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: BB_AER_TC_s -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_BB_CO_s.dat b/esmvalcore/cmor/tables/custom/CMOR_BB_CO_s.dat deleted file mode 100644 index 51028dc13e..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_BB_CO_s.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: BB_CO_s -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg m-2 s-1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Sum of Biomass Burning CO -comment: Summed over all levels -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: BB_CO_s -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_BB_NO_s.dat b/esmvalcore/cmor/tables/custom/CMOR_BB_NO_s.dat deleted file mode 100644 index 1d42fba1d9..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_BB_NO_s.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: BB_NO_s -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg m-2 s-1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Sum of Biomass Burning NO -comment: Summation over all levels; units are given in kg(NO) m-2 s-1 -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: BB_NO_s -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_BB_SO2_s.dat b/esmvalcore/cmor/tables/custom/CMOR_BB_SO2_s.dat deleted file mode 100644 index d150f4388e..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_BB_SO2_s.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: BB_SO2_s -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg m-2 s-1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Sum of Biomass Burning SO2 -comment: Summed over all levels -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: BB_SO2_s -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_ROAD_AER_BC.dat b/esmvalcore/cmor/tables/custom/CMOR_ROAD_AER_BC.dat deleted file mode 100644 index 0fd3c85f5f..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_ROAD_AER_BC.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: ROAD_AER_BC -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg m-2 s-1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Road Aerosol Black Carbon -comment: no summation necessary -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: ROAD_AER_BC -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_ROAD_NO.dat b/esmvalcore/cmor/tables/custom/CMOR_ROAD_NO.dat deleted file mode 100644 index 5eb6d94b9c..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_ROAD_NO.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: ROAD_NO -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg m-2 s-1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Road NO emissions -comment: no summation necessary; units given in kg(NO2) m-2 s-1 -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: ROAD_NO -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_SHIP_AER_BC_s.dat b/esmvalcore/cmor/tables/custom/CMOR_SHIP_AER_BC_s.dat deleted file mode 100644 index 741e2d2d74..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_SHIP_AER_BC_s.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: SHIP_AER_BC_s -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg m-2 s-1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Sum of Ship Aerosol Black Carbon emissions -comment: summed over all levels -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: SHIP_AER_BC_s -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_SHIP_NO_s.dat b/esmvalcore/cmor/tables/custom/CMOR_SHIP_NO_s.dat deleted file mode 100644 index e75f0726fd..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_SHIP_NO_s.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: SHIP_NO_s -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg m-2 s-1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Sum of Ship NO emissions -comment: Summed over all levels; units given in kg(NO2) m-2 s-1 -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: SHIP_NO_s -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_SHIP_SO2_s.dat b/esmvalcore/cmor/tables/custom/CMOR_SHIP_SO2_s.dat deleted file mode 100644 index d261982d9b..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_SHIP_SO2_s.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: SHIP_SO2_s -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg m-2 s-1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Sum of Ship SO2 emissions -comment: Summed over all levels -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: SHIP_SO2_s -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CH4.dat b/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CH4.dat deleted file mode 100644 index de43a33b29..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CH4.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: TN_GHG_CH4 -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: 1.e-9 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Mole Fraction of Methane in Air -comment: no summation necessary -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: TN_GHG_CH4 -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CO2.dat b/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CO2.dat deleted file mode 100644 index efa05812c3..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_CO2.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: TN_GHG_CO2 -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: 1.e-6 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Mole Fraction of Carbon Dioxide in Air -comment: no summation necessary -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: TN_GHG_CO2 -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_N2O.dat b/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_N2O.dat deleted file mode 100644 index d61720ed87..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_TN_GHG_N2O.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: TN_GHG_N2O -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: 1.e-9 -cell_methods: time: mean -cell_measures: area: areacella -long_name: Mole Fraction of Nitrous Oxide in Air -comment: no summation necessary -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: TN_GHG_N2O -type: real -!---------------------------------- -! From b2bd2ed042c483f341fed892d0a1604917a664ab Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Tue, 19 Apr 2022 15:34:17 +0200 Subject: [PATCH 25/52] Added further tests for EMAC CMORizer --- esmvalcore/cmor/_fixes/emac/emac.py | 23 ---- .../integration/cmor/_fixes/emac/test_emac.py | 126 +++++++++++++++++- .../cmor/_fixes/test_data/emac_aermon.nc | Bin 9120 -> 52948 bytes 3 files changed, 119 insertions(+), 30 deletions(-) diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py index b7987be24a..b09e345135 100644 --- a/esmvalcore/cmor/_fixes/emac/emac.py +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -202,29 +202,6 @@ def fix_metadata(self, cubes): Evspsbl = NegateData -class Lnox(EmacFix): - """Fixes for ``lnox``.""" - - def fix_metadata(self, cubes): - """Fix metadata.""" - noxcg_cube = cubes.extract_cube(NameConstraint(var_name='NOxcg_ave')) - noxic_cube = cubes.extract_cube(NameConstraint(var_name='NOxic_ave')) - - # Fix units - noxcg_cube.units = 'kg' - noxic_cube.units = 'kg' - noxcg_cube = noxcg_cube.collapsed(['longitude', 'latitude'], - iris.analysis.SUM) - noxic_cube = noxic_cube.collapsed(['longitude', 'latitude'], - iris.analysis.SUM) - - # Calculate lnox - timestep = float(noxcg_cube.attributes['GCM_timestep']) - cube = (noxcg_cube + noxic_cube) / timestep * 365.0 * 24.0 * 3600.0 - - return CubeList([cube]) - - class MP_BC_tot(EmacFix): # noqa: N801 """Fixes for ``MP_BC_tot``.""" diff --git a/tests/integration/cmor/_fixes/emac/test_emac.py b/tests/integration/cmor/_fixes/emac/test_emac.py index a00294c800..4325ac4cdd 100644 --- a/tests/integration/cmor/_fixes/emac/test_emac.py +++ b/tests/integration/cmor/_fixes/emac/test_emac.py @@ -10,7 +10,13 @@ from iris.cube import Cube, CubeList from esmvalcore._config import get_extra_facets -from esmvalcore.cmor._fixes.emac.emac import AllVars, Clt, Clwvi, Evspsbl +from esmvalcore.cmor._fixes.emac.emac import ( + AllVars, + Clt, + Clwvi, + Evspsbl, + Od550aer, +) from esmvalcore.cmor.fix import Fix from esmvalcore.cmor.table import get_var_info @@ -163,6 +169,18 @@ def check_heightxm(cube, height_value): assert height.bounds is None +def check_lambda550nm(cube): + """Check scalar lambda550nm coordinate of cube.""" + assert cube.coords('radiation_wavelength') + typesi = cube.coord('radiation_wavelength') + assert typesi.var_name == 'wavelength' + assert typesi.standard_name == 'radiation_wavelength' + assert typesi.long_name == 'Radiation Wavelength 550 nanometers' + assert typesi.units == 'nm' + np.testing.assert_array_equal(typesi.points, [550.0]) + assert typesi.bounds is None + + def check_lat(cube): """Check latitude coordinate of cube.""" assert cube.coords('latitude', dim_coords=True) @@ -430,7 +448,7 @@ def test_awhea_fix(cubes_omon_2d): np.testing.assert_allclose( cube.data[:, :, 0], [[-203.94414, -16.695345, 74.117096, 104.992195]], - rtol=1e-6, + rtol=1e-5, ) @@ -459,7 +477,7 @@ def test_clivi_fix(cubes_amon_2d): np.testing.assert_allclose( cube.data[:, :, 0], [[0.01435195, 0.006420649, 0.0007885683, 0.01154814]], - rtol=1e-6, + rtol=1e-5, ) @@ -493,7 +511,7 @@ def test_clt_fix(cubes_amon_2d): np.testing.assert_allclose( cube.data[:, :, 0], [[86.79899, 58.01009, 34.01953, 85.48493]], - rtol=1e-6, + rtol=1e-5, ) @@ -528,7 +546,7 @@ def test_clwvi_fix(cubes_amon_2d): np.testing.assert_allclose( cube.data[:, :, 0], [[0.20945302, 0.01015517, 0.01444221, 0.10618545]], - rtol=1e-6, + rtol=1e-5, ) @@ -555,7 +573,7 @@ def test_co2mass_fix(cubes_tracer_pdef_gp): np.testing.assert_allclose( cube.data, [2.855254e+15, 2.85538e+15], - rtol=1e-6, + rtol=1e-5, ) @@ -591,7 +609,101 @@ def test_evspsbl_fix(cubes_amon_2d): np.testing.assert_allclose( cube.data[:, :, 0], [[3.636807e-05, 3.438968e-07, 6.235108e-05, 1.165336e-05]], - rtol=1e-6, + rtol=1e-5, + ) + + +def test_get_hfls_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'hfls') + assert fix == [AllVars(None)] + + +def test_hfls_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'hfls') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'hfls' + assert cube.standard_name == 'surface_upward_latent_heat_flux' + assert cube.long_name == 'Surface Upward Latent Heat Flux' + assert cube.units == 'W m-2' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[-90.94926, -0.860017, -155.92758, -29.142715]], + rtol=1e-5, + ) + + +def test_get_hfss_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'hfss') + assert fix == [AllVars(None)] + + +def test_hfss_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'hfss') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'hfss' + assert cube.standard_name == 'surface_upward_sensible_heat_flux' + assert cube.long_name == 'Surface Upward Sensible Heat Flux' + assert cube.units == 'W m-2' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[-65.92767, -32.841537, -18.461172, -6.50319]], + rtol=1e-5, + ) + + +def test_get_od550aer_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'od550aer') + assert fix == [Od550aer(None), AllVars(None)] + + +def test_od550aer_fix(cubes_aermon): + """Test fix.""" + vardef = get_var_info('EMAC', 'Amon', 'od550aer') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'od550aer', ()) + fix = Od550aer(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_aermon) + + fix = get_allvars_fix('Amon', 'od550aer') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'od550aer' + assert cube.standard_name == ('atmosphere_optical_thickness_due_to_' + 'ambient_aerosol_particles') + assert cube.long_name == 'Ambient Aerosol Optical Thickness at 550nm' + assert cube.units == '1' + + check_time(cube) + check_lat(cube) + check_lon(cube) + check_lambda550nm(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[0.166031, 0.271185, 0.116384, 0.044266]], + rtol=1e-5, ) diff --git a/tests/integration/cmor/_fixes/test_data/emac_aermon.nc b/tests/integration/cmor/_fixes/test_data/emac_aermon.nc index 34284e1a529e33fc284b36f60656ffc22b97fe71..61f7931fd066a119501af680c34412d859fd9d5e 100644 GIT binary patch literal 52948 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 delta 2474 zcmdT`-EZ4e6i?cD24kZknbO63db_wQxR`W@AH0o|8C=K6XI!5T?y?4#NR;ozym`31-!6_z3~A501^*8fW*1B+cj$@p0Skp-rxP5-#tF( z_*_5#di2}kmkWn@abi?c_Cjza55=poZVzQG8*wSbZQkHAH#nX=h+a7)&%%==GIx0_ z48)P6wYk}FZ0QP&&vg%E748%tB{wIG$+>(0>qMNtMt+^B#OBZWP~#MH(wLn8DS!j! zWF>|^rI6}-*5EK3x9hs_)o-zYh-Haw~58KJ;*f3 zCyUEue)-*z#TVqk@>l((r)pEyB;9N&R0JLaezUsTYH13RUEOr7u3{@FQOd4*>(j+t zN$Q%m`mt%ar!1-6ct6qiYDu^h`G*1M?0MJh^3j;mNOAA?^wKTDTFGuvAmQm#^}3Syxq-7VC1 zg-RK4s&!Ruo3@J*!gi%rUxg$xEW;z7<@%c}&#hDQP$)Vekjsjqk5hp}(zM*9ZmG#e zPt*2cAnDqEl4E#gi{XO%XcV2I6 zC~mXWPWrc#DD{u?6@-aD2~+60L~~}#22L?4hR3SmDjJtcdL9}keq_rKDB13)esx`@ zVXxK-)mkMd{3OV!Vhu2RM_3U4}lYfdR;6?g>t?xWXk|ZqENnbFP(y6 zwOpv@^4s1hS1S~2LLSDRRJ7$bN;KA4 z>S^C^YN08b-~&llnoU_!KJqGx8lCUR4in4=I1Rt)QqHAZNvNiA7XFz49@MifuO(7$ zOjmUd`vxtg`LK^!zvLsQ9WYeqI83XFM51n^l@*jIqQ{TXj^eJ;*@Cc2r@O&yrxQHL zfKE%5JFqF;c*5UvFW4>=X?A79Q2LtJP9;Y(f#kJQxYO&RPk_>py`D@(#E1CCNRoPMsg)X3w>Heu@Tpff#5}y_GJxJu^d|%|z#52GKdz^vh9#SKY%$$nTfZ z@$r%R&~rFWofAqU3{ z&)+~s^C9%?3bcBJ_mFJ55_5fxyokOxOlcz~k8#{aDnq`BeKy8twzzfjM|6h#5Scqm zXOFb$@4|<_SmcZNpXbMQ-|)$-8eSv+#I6PA%a=F*?;k58OHYsfT7G_xh(CY+FWaVY AYXATM From 4173bc94d5b568e14089d325a8c4f6c9f976afbb Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Tue, 19 Apr 2022 16:44:27 +0200 Subject: [PATCH 26/52] Removed renaming of ICON extra facets (this is done in another PR) --- doc/develop/fixing_data.rst | 4 ++-- .../extra_facets/{icon-mappings.yml => icon-mapping.yml} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename esmvalcore/_config/extra_facets/{icon-mappings.yml => icon-mapping.yml} (100%) diff --git a/doc/develop/fixing_data.rst b/doc/develop/fixing_data.rst index c86ee237e0..02369d12c3 100644 --- a/doc/develop/fixing_data.rst +++ b/doc/develop/fixing_data.rst @@ -396,8 +396,8 @@ Please note the duplication of the name ``ICON`` in ``project`` and CMORizing functionalities. Similar to any other fix, the ICON fix allows the use of :ref:`extra -facets`. By default, the file :download:`icon-mappings.yml -` is used for that +facets`. By default, the file :download:`icon-mapping.yml +` is used for that purpose. For some variables, extra facets are necessary; otherwise ESMValTool cannot read them properly. Supported keys for extra facets are: diff --git a/esmvalcore/_config/extra_facets/icon-mappings.yml b/esmvalcore/_config/extra_facets/icon-mapping.yml similarity index 100% rename from esmvalcore/_config/extra_facets/icon-mappings.yml rename to esmvalcore/_config/extra_facets/icon-mapping.yml From 24413c44a2b9a148bed7f8b2f2ee787be311d7d8 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Wed, 20 Apr 2022 13:48:50 +0200 Subject: [PATCH 27/52] Added further tests for EMAC CMORizer --- .../_config/extra_facets/emac-mappings.yml | 9 + esmvalcore/cmor/_fixes/emac/emac.py | 27 +- .../integration/cmor/_fixes/emac/test_emac.py | 993 ++++++++++++++++-- .../cmor/_fixes/test_data/emac_g3b.nc | Bin 0 -> 8064 bytes 4 files changed, 922 insertions(+), 107 deletions(-) create mode 100644 tests/integration/cmor/_fixes/test_data/emac_g3b.nc diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml index 7b0d25e444..0efd73720c 100644 --- a/esmvalcore/_config/extra_facets/emac-mappings.yml +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -103,6 +103,15 @@ EMAC: raw_name: tsurf_ave channel: Amon + # TODO: CHECK IF THE CALCULATION IS CORRECT!!! + # -> it might be necessary to weight these fields with cos(lat) + uas: + raw_name: u10_ave + channel: Amon + vas: + raw_name: v10_ave + channel: Amon + # Tracers (non-CMOR variables) MP_BC_tot: # derived from MP_BC_ks_ave, MP_BC_as_ave, MP_BC_cs_ave, MP_BC_ki_ave channel: tracer_pdef_gp diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py index b09e345135..fc549e2472 100644 --- a/esmvalcore/cmor/_fixes/emac/emac.py +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -182,6 +182,10 @@ def _fix_var_metadata(self, cube): if cube.units != self.vardef.units: cube.convert_units(self.vardef.units) + # Fix attributes + if self.vardef.positive != '': + cube.attributes['positive'] = self.vardef.positive + Clt = SetUnitsTo1 @@ -202,6 +206,12 @@ def fix_metadata(self, cubes): Evspsbl = NegateData +Hfls = NegateData + + +Hfss = NegateData + + class MP_BC_tot(EmacFix): # noqa: N801 """Fixes for ``MP_BC_tot``.""" @@ -297,6 +307,9 @@ def fix_metadata(self, cubes): Rlut = NegateData +Rlutcs = NegateData + + class Rsds(EmacFix): """Fixes for ``rsds``.""" @@ -329,6 +342,9 @@ def fix_metadata(self, cubes): Rsut = NegateData +Rsutcs = NegateData + + class Rtmt(EmacFix): """Fixes for ``rtmt``.""" @@ -357,21 +373,12 @@ def fix_data(self, cube): return cube -class Tosga(EmacFix): - """Fixes for ``tosga``.""" - - def fix_metadata(self, cubes): - """Fix metadata.""" - cube = self.get_cube(cubes) - cube.units = 'celsius' - return CubeList([cube]) - - class Toz(EmacFix): """Fixes for ``tosga``.""" def fix_metadata(self, cubes): """Fix metadata.""" + # Convert DU to mm # Note: 1 mm = 100 DU cube = self.get_cube(cubes) cube.data = cube.core_data() / 100.0 diff --git a/tests/integration/cmor/_fixes/emac/test_emac.py b/tests/integration/cmor/_fixes/emac/test_emac.py index 4325ac4cdd..b46e73e90c 100644 --- a/tests/integration/cmor/_fixes/emac/test_emac.py +++ b/tests/integration/cmor/_fixes/emac/test_emac.py @@ -15,7 +15,24 @@ Clt, Clwvi, Evspsbl, + Hfls, + Hfss, Od550aer, + Pr, + Rlds, + Rlus, + Rlut, + Rlutcs, + Rsds, + Rsdt, + Rsus, + Rsut, + Rsutcs, + Rtmt, + Siconc, + Siconca, + Sithick, + Toz, ) from esmvalcore.cmor.fix import Fix from esmvalcore.cmor.table import get_var_info @@ -51,6 +68,13 @@ def cubes_column(test_data_path): return iris.load(str(nc_path)) +@pytest.fixture +def cubes_g3b(test_data_path): + """g3b sample cubes.""" + nc_path = test_data_path / 'emac_g3b.nc' + return iris.load(str(nc_path)) + + @pytest.fixture def cubes_omon_2d(test_data_path): """Omon 2D sample cubes.""" @@ -440,6 +464,7 @@ def test_awhea_fix(cubes_omon_2d): assert cube.long_name == ('Global Mean Net Surface Heat Flux Over Open ' 'Water') assert cube.units == 'W m-2' + assert 'positive' not in cube.attributes check_time(cube) check_lat(cube) @@ -469,6 +494,7 @@ def test_clivi_fix(cubes_amon_2d): assert cube.standard_name == 'atmosphere_mass_content_of_cloud_ice' assert cube.long_name == 'Ice Water Path' assert cube.units == 'kg m-2' + assert 'positive' not in cube.attributes check_time(cube) check_lat(cube) @@ -503,6 +529,7 @@ def test_clt_fix(cubes_amon_2d): assert cube.standard_name == 'cloud_area_fraction' assert cube.long_name == 'Total Cloud Cover Percentage' assert cube.units == '%' + assert 'positive' not in cube.attributes check_time(cube) check_lat(cube) @@ -538,6 +565,7 @@ def test_clwvi_fix(cubes_amon_2d): 'condensed_water') assert cube.long_name == 'Condensed Water Path' assert cube.units == 'kg m-2' + assert 'positive' not in cube.attributes check_time(cube) check_lat(cube) @@ -567,6 +595,7 @@ def test_co2mass_fix(cubes_tracer_pdef_gp): assert cube.standard_name == 'atmosphere_mass_of_carbon_dioxide' assert cube.long_name == 'Total Atmospheric Mass of CO2' assert cube.units == 'kg' + assert 'positive' not in cube.attributes check_time(cube, n_points=2) @@ -601,6 +630,7 @@ def test_evspsbl_fix(cubes_amon_2d): assert cube.long_name == ('Evaporation Including Sublimation and ' 'Transpiration') assert cube.units == 'kg m-2 s-1' + assert 'positive' not in cube.attributes check_time(cube) check_lat(cube) @@ -616,7 +646,7 @@ def test_evspsbl_fix(cubes_amon_2d): def test_get_hfls_fix(): """Test getting of fix.""" fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'hfls') - assert fix == [AllVars(None)] + assert fix == [Hfls(None), AllVars(None)] def test_hfls_fix(cubes_amon_2d): @@ -626,10 +656,17 @@ def test_hfls_fix(cubes_amon_2d): assert len(fixed_cubes) == 1 cube = fixed_cubes[0] + + vardef = get_var_info('EMAC', 'Amon', 'hfls') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'hfls', ()) + fix = Hfls(vardef, extra_facets=extra_facets) + cube = fix.fix_data(cube) + assert cube.var_name == 'hfls' assert cube.standard_name == 'surface_upward_latent_heat_flux' assert cube.long_name == 'Surface Upward Latent Heat Flux' assert cube.units == 'W m-2' + assert cube.attributes['positive'] == 'up' check_time(cube) check_lat(cube) @@ -637,7 +674,7 @@ def test_hfls_fix(cubes_amon_2d): np.testing.assert_allclose( cube.data[:, :, 0], - [[-90.94926, -0.860017, -155.92758, -29.142715]], + [[90.94926, 0.860017, 155.92758, 29.142715]], rtol=1e-5, ) @@ -645,7 +682,7 @@ def test_hfls_fix(cubes_amon_2d): def test_get_hfss_fix(): """Test getting of fix.""" fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'hfss') - assert fix == [AllVars(None)] + assert fix == [Hfss(None), AllVars(None)] def test_hfss_fix(cubes_amon_2d): @@ -655,10 +692,17 @@ def test_hfss_fix(cubes_amon_2d): assert len(fixed_cubes) == 1 cube = fixed_cubes[0] + + vardef = get_var_info('EMAC', 'Amon', 'hfss') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'hfss', ()) + fix = Hfss(vardef, extra_facets=extra_facets) + cube = fix.fix_data(cube) + assert cube.var_name == 'hfss' assert cube.standard_name == 'surface_upward_sensible_heat_flux' assert cube.long_name == 'Surface Upward Sensible Heat Flux' assert cube.units == 'W m-2' + assert cube.attributes['positive'] == 'up' check_time(cube) check_lat(cube) @@ -666,7 +710,7 @@ def test_hfss_fix(cubes_amon_2d): np.testing.assert_allclose( cube.data[:, :, 0], - [[-65.92767, -32.841537, -18.461172, -6.50319]], + [[65.92767, 32.841537, 18.461172, 6.50319]], rtol=1e-5, ) @@ -694,6 +738,7 @@ def test_od550aer_fix(cubes_aermon): 'ambient_aerosol_particles') assert cube.long_name == 'Ambient Aerosol Optical Thickness at 550nm' assert cube.units == '1' + assert 'positive' not in cube.attributes check_time(cube) check_lat(cube) @@ -707,135 +752,889 @@ def test_od550aer_fix(cubes_aermon): ) -# Test areacella and areacello (for extra_facets, and grid_latitude and -# grid_longitude coordinates) +def test_get_pr_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'pr') + assert fix == [Pr(None), AllVars(None)] -# def test_get_areacella_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('EMAC', 'EMAC', 'fx', 'areacella') -# assert fix == [AllVars(None)] +def test_pr_fix(cubes_amon_2d): + """Test fix.""" + vardef = get_var_info('EMAC', 'Amon', 'pr') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'pr', ()) + fix = Pr(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fix = get_allvars_fix('Amon', 'pr') + fixed_cubes = fix.fix_metadata(fixed_cubes) -# def test_areacella_fix(cubes_grid): -# """Test fix.""" -# fix = get_allvars_fix('fx', 'areacella') -# fixed_cubes = fix.fix_metadata(cubes_grid) + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'pr' + assert cube.standard_name == 'precipitation_flux' + assert cube.long_name == 'Precipitation' + assert cube.units == 'kg m-2 s-1' + assert 'positive' not in cube.attributes -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.var_name == 'areacella' -# assert cube.standard_name == 'cell_area' -# assert cube.long_name == 'Grid-Cell Area for Atmospheric Grid Variables' -# assert cube.units == 'm2' + check_time(cube) + check_lat(cube) + check_lon(cube) -# check_lat_lon(cube) + np.testing.assert_allclose( + cube.data[:, :, 0], + [[3.590828e-05, 5.637868e-07, 3.474401e-07, 1.853631e-05]], + rtol=1e-5, + ) -# def test_get_areacello_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('EMAC', 'EMAC', 'Ofx', 'areacello') -# assert fix == [AllVars(None)] +def test_get_prc_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'prc') + assert fix == [AllVars(None)] -# def test_areacello_fix(cubes_grid): -# """Test fix.""" -# fix = get_allvars_fix('Ofx', 'areacello') -# fixed_cubes = fix.fix_metadata(cubes_grid) +def test_prc_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'prc') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.var_name == 'areacello' -# assert cube.standard_name == 'cell_area' -# assert cube.long_name == 'Grid-Cell Area for Ocean Variables' -# assert cube.units == 'm2' + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'prc' + assert cube.standard_name == 'convective_precipitation_flux' + assert cube.long_name == 'Convective Precipitation' + assert cube.units == 'kg m-2 s-1' + assert 'positive' not in cube.attributes -# check_lat_lon(cube) + check_time(cube) + check_lat(cube) + check_lon(cube) + np.testing.assert_allclose( + cube.data[:, :, 0], + [[1.177248e-05, 0.0, 0.0, 2.419762e-06]], + rtol=1e-5, + ) -# # Test clwvi (for extra_facets) +def test_get_prl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'prl') + assert fix == [AllVars(None)] -# def test_get_clwvi_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'clwvi') -# assert fix == [AllVars(None)] +def test_prl_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'prl') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) -# def test_clwvi_fix(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'clwvi') -# fixed_cubes = fix.fix_metadata(cubes_2d) + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'prl' + assert cube.standard_name is None + assert cube.long_name == 'Large Scale Precipitation' + assert cube.units == 'kg m-2 s-1' + assert 'positive' not in cube.attributes -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.var_name == 'clwvi' -# assert cube.standard_name == ('atmosphere_mass_content_of_cloud_' -# 'condensed_water') -# assert cube.long_name == 'Condensed Water Path' -# assert cube.units == 'kg m-2' + check_time(cube) + check_lat(cube) + check_lon(cube) -# check_time(cube) -# check_lat_lon(cube) + np.testing.assert_allclose( + cube.data[:, :, 0], + [[2.091789e-05, 5.637868e-07, 3.474401e-07, 1.611654e-05]], + rtol=1e-5, + ) -# # Test siconc and siconca (for extra_facets, extra fix and typesi coordinate) +def test_get_prsn_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'prsn') + assert fix == [AllVars(None)] -# def test_get_siconc_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('EMAC', 'EMAC', 'SImon', 'siconc') -# assert fix == [Siconc(None), AllVars(None)] +def test_prsn_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'prsn') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'prsn' + assert cube.standard_name == 'snowfall_flux' + assert cube.long_name == 'Snowfall Flux' + assert cube.units == 'kg m-2 s-1' + assert 'positive' not in cube.attributes -# def test_siconc_fix(cubes_2d): -# """Test fix.""" -# vardef = get_var_info('EMAC', 'SImon', 'siconc') -# extra_facets = get_extra_facets('EMAC', 'EMAC', 'SImon', 'siconc', ()) -# siconc_fix = Siconc(vardef, extra_facets=extra_facets) -# allvars_fix = get_allvars_fix('SImon', 'siconc') + check_time(cube) + check_lat(cube) + check_lon(cube) -# fixed_cubes = siconc_fix.fix_metadata(cubes_2d) -# fixed_cubes = allvars_fix.fix_metadata(fixed_cubes) + np.testing.assert_allclose( + cube.data[:, :, 0], + [[3.217916e-06, 5.760116e-30, 5.894975e-30, 6.625394e-30]], + rtol=1e-5, + ) -# cube = check_siconc_metadata(fixed_cubes, 'siconc', -# 'Sea-Ice Area Percentage (Ocean Grid)') -# check_time(cube) -# check_lat_lon(cube) -# check_typesi(cube) -# np.testing.assert_allclose( -# cube.data, -# [[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]], -# ) +def test_get_prw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'prw') + assert fix == [AllVars(None)] -# def test_get_siconca_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('EMAC', 'EMAC', 'SImon', 'siconca') -# assert fix == [Siconca(None), AllVars(None)] +def test_prw_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'prw') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'prw' + assert cube.standard_name == 'atmosphere_mass_content_of_water_vapor' + assert cube.long_name == 'Water Vapor Path' + assert cube.units == 'kg m-2' + assert 'positive' not in cube.attributes -# def test_siconca_fix(cubes_2d): -# """Test fix.""" -# vardef = get_var_info('EMAC', 'SImon', 'siconca') -# extra_facets = get_extra_facets('EMAC', 'EMAC', 'SImon', 'siconca', ()) -# siconca_fix = Siconca(vardef, extra_facets=extra_facets) -# allvars_fix = get_allvars_fix('SImon', 'siconca') + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[9.398615, 10.207355, 22.597773, 11.342406]], + rtol=1e-5, + ) -# fixed_cubes = siconca_fix.fix_metadata(cubes_2d) -# fixed_cubes = allvars_fix.fix_metadata(fixed_cubes) -# cube = check_siconc_metadata( -# fixed_cubes, 'siconca', 'Sea-Ice Area Percentage (Atmospheric Grid)') -# check_time(cube) -# check_lat_lon(cube) -# check_typesi(cube) +def test_get_rlds_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'rlds') + assert fix == [Rlds(None), AllVars(None)] + + +def test_rlds_fix(cubes_amon_2d): + """Test fix.""" + vardef = get_var_info('EMAC', 'Amon', 'rlds') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'rlds', ()) + fix = Rlds(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + fix = get_allvars_fix('Amon', 'rlds') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'rlds' + assert cube.standard_name == 'surface_downwelling_longwave_flux_in_air' + assert cube.long_name == 'Surface Downwelling Longwave Radiation' + assert cube.units == 'W m-2' + assert cube.attributes['positive'] == 'down' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[297.55298, 310.508, 361.471, 302.51376]], + rtol=1e-5, + ) + + +def test_get_rlus_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'rlus') + assert fix == [Rlus(None), AllVars(None)] + + +def test_rlus_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'rlus') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + + vardef = get_var_info('EMAC', 'Amon', 'rlus') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'rlus', ()) + fix = Rlus(vardef, extra_facets=extra_facets) + cube = fix.fix_data(cube) + + assert cube.var_name == 'rlus' + assert cube.standard_name == 'surface_upwelling_longwave_flux_in_air' + assert cube.long_name == 'Surface Upwelling Longwave Radiation' + assert cube.units == 'W m-2' + assert cube.attributes['positive'] == 'up' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[351.59143, 411.6364, 438.25314, 339.71625]], + rtol=1e-5, + ) + + +def test_get_rlut_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'rlut') + assert fix == [Rlut(None), AllVars(None)] + + +def test_rlut_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'rlut') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + + vardef = get_var_info('EMAC', 'Amon', 'rlut') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'rlut', ()) + fix = Rlut(vardef, extra_facets=extra_facets) + cube = fix.fix_data(cube) + + assert cube.var_name == 'rlut' + assert cube.standard_name == 'toa_outgoing_longwave_flux' + assert cube.long_name == 'TOA Outgoing Longwave Radiation' + assert cube.units == 'W m-2' + assert cube.attributes['positive'] == 'up' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[181.34714, 240.24974, 282.01166, 203.07207]], + rtol=1e-5, + ) + + +def test_get_rlutcs_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'rlutcs') + assert fix == [Rlutcs(None), AllVars(None)] + + +def test_rlutcs_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'rlutcs') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + + vardef = get_var_info('EMAC', 'Amon', 'rlutcs') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'rlutcs', ()) + fix = Rlutcs(vardef, extra_facets=extra_facets) + cube = fix.fix_data(cube) + + assert cube.var_name == 'rlutcs' + assert cube.standard_name == ('toa_outgoing_longwave_flux_assuming_clear_' + 'sky') + assert cube.long_name == 'TOA Outgoing Clear-Sky Longwave Radiation' + assert cube.units == 'W m-2' + assert cube.attributes['positive'] == 'up' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[232.35957, 273.42227, 288.2262, 238.6909]], + rtol=1e-5, + ) + + +def test_get_rsds_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'rsds') + assert fix == [Rsds(None), AllVars(None)] + + +def test_rsds_fix(cubes_amon_2d): + """Test fix.""" + vardef = get_var_info('EMAC', 'Amon', 'rsds') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'rsds', ()) + fix = Rsds(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + fix = get_allvars_fix('Amon', 'rsds') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'rsds' + assert cube.standard_name == 'surface_downwelling_shortwave_flux_in_air' + assert cube.long_name == 'Surface Downwelling Shortwave Radiation' + assert cube.units == 'W m-2' + assert cube.attributes['positive'] == 'down' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[7.495961, 214.6077, 349.77203, 191.22644]], + rtol=1e-5, + ) + + +def test_get_rsdt_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'rsdt') + assert fix == [Rsdt(None), AllVars(None)] -# np.testing.assert_allclose( -# cube.data, -# [[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]], -# ) + +def test_rsdt_fix(cubes_amon_2d): + """Test fix.""" + vardef = get_var_info('EMAC', 'Amon', 'rsdt') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'rsdt', ()) + fix = Rsdt(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + fix = get_allvars_fix('Amon', 'rsdt') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'rsdt' + assert cube.standard_name == 'toa_incoming_shortwave_flux' + assert cube.long_name == 'TOA Incident Shortwave Radiation' + assert cube.units == 'W m-2' + assert cube.attributes['positive'] == 'down' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[44.4018, 312.62286, 481.91992, 473.25092]], + rtol=1e-5, + ) + + +def test_get_rsus_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'rsus') + assert fix == [Rsus(None), AllVars(None)] + + +def test_rsus_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'rsus') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + + vardef = get_var_info('EMAC', 'Amon', 'rsus') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'rsus', ()) + fix = Rsus(vardef, extra_facets=extra_facets) + cube = fix.fix_data(cube) + + assert cube.var_name == 'rsus' + assert cube.standard_name == 'surface_upwelling_shortwave_flux_in_air' + assert cube.long_name == 'Surface Upwelling Shortwave Radiation' + assert cube.units == 'W m-2' + assert cube.attributes['positive'] == 'up' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[0.524717, 82.92702, 24.484043, 13.38585]], + rtol=1e-5, + ) + + +def test_get_rsut_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'rsut') + assert fix == [Rsut(None), AllVars(None)] + + +def test_rsut_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'rsut') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + + vardef = get_var_info('EMAC', 'Amon', 'rsut') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'rsut', ()) + fix = Rsut(vardef, extra_facets=extra_facets) + cube = fix.fix_data(cube) + + assert cube.var_name == 'rsut' + assert cube.standard_name == 'toa_outgoing_shortwave_flux' + assert cube.long_name == 'TOA Outgoing Shortwave Radiation' + assert cube.units == 'W m-2' + assert cube.attributes['positive'] == 'up' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[26.967886, 114.11882, 70.44302, 203.26039]], + rtol=1e-5, + ) + + +def test_get_rsutcs_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'rsutcs') + assert fix == [Rsutcs(None), AllVars(None)] + + +def test_rsutcs_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'rsutcs') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + + vardef = get_var_info('EMAC', 'Amon', 'rsutcs') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'rsutcs', ()) + fix = Rsutcs(vardef, extra_facets=extra_facets) + cube = fix.fix_data(cube) + + assert cube.var_name == 'rsutcs' + assert cube.standard_name == ('toa_outgoing_shortwave_flux_assuming_clear_' + 'sky') + assert cube.long_name == 'TOA Outgoing Clear-Sky Shortwave Radiation' + assert cube.units == 'W m-2' + assert cube.attributes['positive'] == 'up' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[11.787124, 101.68645, 50.588364, 53.933403]], + rtol=1e-5, + ) + + +def test_get_rtmt_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'rtmt') + assert fix == [Rtmt(None), AllVars(None)] + + +def test_rtmt_fix(cubes_amon_2d): + """Test fix.""" + vardef = get_var_info('EMAC', 'Amon', 'rtmt') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'rtmt', ()) + fix = Rtmt(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + fix = get_allvars_fix('Amon', 'rtmt') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'rtmt' + assert cube.standard_name == ('net_downward_radiative_flux_at_top_of_' + 'atmosphere_model') + assert cube.long_name == 'Net Downward Radiative Flux at Top of Model' + assert cube.units == 'W m-2' + assert cube.attributes['positive'] == 'down' + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[-163.91322, -41.745697, 129.46524, 66.91847]], + rtol=1e-5, + ) + + +def test_get_siconc_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'SImon', 'siconc') + assert fix == [Siconc(None), AllVars(None)] + + +def test_siconc_fix(cubes_amon_2d): + """Test fix.""" + vardef = get_var_info('EMAC', 'SImon', 'siconc') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'SImon', 'siconc', ()) + fix = Siconc(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + fix = get_allvars_fix('SImon', 'siconc') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'siconc' + assert cube.standard_name == 'sea_ice_area_fraction' + assert cube.long_name == 'Sea-Ice Area Percentage (Ocean Grid)' + assert cube.units == '%' + assert 'positive' not in cube.attributes + + check_time(cube) + check_lat(cube) + check_lon(cube) + check_typesi(cube) + + np.testing.assert_allclose( + cube.data[:, :, 1], + [[61.51324, 0.0, 0.0, 0.0]], + rtol=1e-5, + ) + + +def test_get_siconca_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'SImon', 'siconca') + assert fix == [Siconca(None), AllVars(None)] + + +def test_siconca_fix(cubes_amon_2d): + """Test fix.""" + vardef = get_var_info('EMAC', 'SImon', 'siconca') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'SImon', 'siconca', ()) + fix = Siconca(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + fix = get_allvars_fix('SImon', 'siconca') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'siconca' + assert cube.standard_name == 'sea_ice_area_fraction' + assert cube.long_name == 'Sea-Ice Area Percentage (Atmospheric Grid)' + assert cube.units == '%' + assert 'positive' not in cube.attributes + + check_time(cube) + check_lat(cube) + check_lon(cube) + check_typesi(cube) + + np.testing.assert_allclose( + cube.data[:, :, 1], + [[61.51324, 0.0, 0.0, 0.0]], + rtol=1e-5, + ) + + +def test_get_sithick_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'SImon', 'sithick') + assert fix == [Sithick(None), AllVars(None)] + + +def test_sithick_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('SImon', 'sithick') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + + vardef = get_var_info('EMAC', 'SImon', 'sithick') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'SImon', 'sithick', ()) + fix = Sithick(vardef, extra_facets=extra_facets) + cube = fix.fix_data(cube) + + assert cube.var_name == 'sithick' + assert cube.standard_name == 'sea_ice_thickness' + assert cube.long_name == 'Sea Ice Thickness' + assert cube.units == 'm' + assert 'positive' not in cube.attributes + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose(cube.data[0, 0, 1], 0.798652, rtol=1e-5,) + np.testing.assert_equal( + cube.data[:, :, 1].mask, + [[False, True, True, True]], + ) + + +def test_get_tas_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'tas') + assert fix == [AllVars(None)] + + +def test_tas_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'tas') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'tas' + assert cube.standard_name == 'air_temperature' + assert cube.long_name == 'Near-Surface Air Temperature' + assert cube.units == 'K' + assert 'positive' not in cube.attributes + + check_time(cube) + check_lat(cube) + check_lon(cube) + check_heightxm(cube, 2.0) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[277.4016, 291.2251, 295.6336, 277.8235]], + rtol=1e-5, + ) + + +def test_get_tos_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Omon', 'tos') + assert fix == [AllVars(None)] + + +def test_tos_fix(cubes_g3b): + """Test fix.""" + fix = get_allvars_fix('Omon', 'tos') + fixed_cubes = fix.fix_metadata(cubes_g3b) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'tos' + assert cube.standard_name == 'sea_surface_temperature' + assert cube.long_name == 'Sea Surface Temperature' + assert cube.units == 'degC' + assert 'positive' not in cube.attributes + + print(cube.coord('time')) + + check_time(cube, n_points=2) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[0, :, 0], + [7.828393, 10.133539, 23.036158, 4.997858], + rtol=1e-5, + ) + + +def test_get_toz_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'AERmon', 'toz') + assert fix == [Toz(None), AllVars(None)] + + +def test_toz_fix(cubes_column): + """Test fix.""" + vardef = get_var_info('EMAC', 'AERmon', 'toz') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'AERmon', 'toz', ()) + fix = Toz(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_column) + + fix = get_allvars_fix('AERmon', 'toz') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'toz' + assert cube.standard_name == ('equivalent_thickness_at_stp_of_atmosphere_' + 'ozone_content') + assert cube.long_name == 'Total Column Ozone' + assert cube.units == 'm' + assert 'positive' not in cube.attributes + + check_time(cube, n_points=2) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[0, :, 0], + [0.003108, 0.002928, 0.002921, 0.003366], + rtol=1e-3, + ) + + +def test_get_ts_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'ts') + assert fix == [AllVars(None)] + + +def test_ts_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'ts') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'ts' + assert cube.standard_name == 'surface_temperature' + assert cube.long_name == 'Surface Temperature' + assert cube.units == 'K' + assert 'positive' not in cube.attributes + + check_time(cube) + check_lat(cube) + check_lon(cube) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[280.65475, 291.80563, 296.55356, 278.24164]], + rtol=1e-5, + ) + + +def test_get_uas_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'uas') + assert fix == [AllVars(None)] + + +def test_uas_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'uas') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'uas' + assert cube.standard_name == 'eastward_wind' + assert cube.long_name == 'Eastward Near-Surface Wind' + assert cube.units == 'm s-1' + assert 'positive' not in cube.attributes + + check_time(cube) + check_lat(cube) + check_lon(cube) + check_heightxm(cube, 10.0) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[-2.114626, -2.809653, -6.59721, -1.586884]], + rtol=1e-5, + ) + + +def test_get_vas_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'vas') + assert fix == [AllVars(None)] + + +def test_vas_fix(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'vas') + fixed_cubes = fix.fix_metadata(cubes_amon_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'vas' + assert cube.standard_name == 'northward_wind' + assert cube.long_name == 'Northward Near-Surface Wind' + assert cube.units == 'm s-1' + assert 'positive' not in cube.attributes + + check_time(cube) + check_lat(cube) + check_lon(cube) + check_heightxm(cube, 10.0) + + np.testing.assert_allclose( + cube.data[:, :, 0], + [[3.026835, -2.226409, 4.868941, 3.301589]], + rtol=1e-5, + ) + + +# Test areacella and areacello (for extra_facets, and grid_latitude and +# grid_longitude coordinates) + + +# def test_get_areacella_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('EMAC', 'EMAC', 'fx', 'areacella') +# assert fix == [AllVars(None)] + + +# def test_areacella_fix(cubes_grid): +# """Test fix.""" +# fix = get_allvars_fix('fx', 'areacella') +# fixed_cubes = fix.fix_metadata(cubes_grid) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.var_name == 'areacella' +# assert cube.standard_name == 'cell_area' +# assert cube.long_name == 'Grid-Cell Area for Atmospheric Grid Variables' +# assert cube.units == 'm2' + +# check_lat_lon(cube) + + +# def test_get_areacello_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('EMAC', 'EMAC', 'Ofx', 'areacello') +# assert fix == [AllVars(None)] + + +# def test_areacello_fix(cubes_grid): +# """Test fix.""" +# fix = get_allvars_fix('Ofx', 'areacello') +# fixed_cubes = fix.fix_metadata(cubes_grid) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.var_name == 'areacello' +# assert cube.standard_name == 'cell_area' +# assert cube.long_name == 'Grid-Cell Area for Ocean Variables' +# assert cube.units == 'm2' + +# check_lat_lon(cube) + + +# # Test clwvi (for extra_facets) + + +# def test_get_clwvi_fix(): +# """Test getting of fix.""" +# fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'clwvi') +# assert fix == [AllVars(None)] + + +# def test_clwvi_fix(cubes_2d): +# """Test fix.""" +# fix = get_allvars_fix('Amon', 'clwvi') +# fixed_cubes = fix.fix_metadata(cubes_2d) + +# assert len(fixed_cubes) == 1 +# cube = fixed_cubes[0] +# assert cube.var_name == 'clwvi' +# assert cube.standard_name == ('atmosphere_mass_content_of_cloud_' +# 'condensed_water') +# assert cube.long_name == 'Condensed Water Path' +# assert cube.units == 'kg m-2' + +# check_time(cube) +# check_lat_lon(cube) # # Test ta (for height and plev coordinate) diff --git a/tests/integration/cmor/_fixes/test_data/emac_g3b.nc b/tests/integration/cmor/_fixes/test_data/emac_g3b.nc new file mode 100644 index 0000000000000000000000000000000000000000..ac1b7fda3490e72bda8c517c2efeaef693280e44 GIT binary patch literal 8064 zcmeHLTWlLy8BR#kq;1mM(iQ|2XSq~ql^NR;J2xPWd+a2pb{rC?O;KTX>>1m$or`y7 zoH}d~MJ#HY>}pvxDKChd7T%Ed0U<8SiidsW0fbl<@lY-Tv{Dh$F7OEdf6f^<<0e{o zKthlskI$Sl-}(RleE)y0G&A##uC6Y84#Lv|&%VI44ahO|SgwP4KYP~#eBTGp06f^H zuWtH*>#e~nD_^*5=p^0nh!Q7>kx2!A@UAG6#HnaO0AD4I$mfP@mRr`WVSRX*xXmCT zI~B|!LrI`_o)$4j9Zxt$Kr=I>*f2Z|l=)=U@O;yCNJ@^&qhx5#wT*^WGss6tJqQ|; zv6xeFWxpyz!(l>QecP9s8PuC)x#HRg!sjgNIRsBHJYnCvu@VyY6YB^4Ky!4>(^W?c z$wAvf`~l#pw#&EPreg*^fFWU<)4H}seAB5IBr%?xkm3pWk$8NP{TQsPzoJar^g9`vyb% z9M=o#pbS#P#w)xIZ6y5l)XR|f1paj_;`qSCc!wNq?Sp40JKpOzy{ZOY4h)-xZqqY} zTP4@Ez(But+SYv$Dk1HwK*VAW4{}n2r#H~bmhekhe-r8lX(p%EJX2SLwTAePw)NMv zrtkA`IoifJO}8!1ls( z6COyXeh+{9eM2)VT=7oaAdfq7a~c|viVFjo3{^d?f{KxwgtU||uo3wv4?hE0|Ajof zO9#6K3fZNlHPy5m5LZw}UJr%SR6>qJvFiZGcn&sz)PYH0oxo^{i=_^X!sQ5En&G*P zP}g9n3ox?6+lDyP11Iew>kzl8V!2IbRm3KROFFy_!!>fd1|I56ltKQWz`4$xbVtffq&2I4+M19-15R8E@xUAZBZIuBd zb_xIVb4(qHh^)hslJVcHxdl8h{2kL9?nHm2RL5$-=>i)~aY=$5FQ5qZ1` zb!|+I&-fkoFdjop!*uOXPFt7gxv0Hd|DLO>J3%9}!QkBVZj| zC*%}b(YD?7$|j3~i0yHCB7$*iY+j9E5}a?*zXpT}+Z9AGf=e-8o+|G=UPe$?IPSEa z#+`_9q05J5_fcJ<|AI@vtfE-P<+fN$a__eL?^j?$6XM!jM*ZR&y4jYs4a1leelb$3 zx>>C#HK1GZR9sW^8Kj^ zeuWa)kX^}AmCj}9#iiu}t75nbJ+36i^l?S6jE`59iHWh%F3A!UP7X zx*jPQf#G_t<<{1Q$(&}Fn;x$DCqSm^H|o$MFkcP4rc+VF{U3bCy}`NNaD2G8*ZEy> z*FXTIeei_xj-g(d+XJT7=9>}tj(t06o4RfpL&~3SPhIbvnMkR$SXjvAvy1Ab?BY_cI1lwG>$C|D2*^-wrnp!- zOA=$ae8PbxF$VDjY_T2WXxalxrvrpmS94Y<1sja zSg_eAQvPkB(1=KvtzpS)XhD7WU&C9Bl0v#Pw|iSSp;h#1jAy%XB=$4N_c;~)1^Wgm zDZ`2@nNDC>iDZl!=(y5#PXn4XuV(Uratb+LSj;Xg7O9%a&gABECGh?dh+_V`DrIoy zxB|50bEQ%~t7hjjx%51g)qHM#d1W*SuM6|p5}rWujV@+q7jqeSXAQ&mgX>g~{5M-f z0gWSyG?q%rxT%sMg5}ous0yBdGXj4@F23X&^wtl)Lhtl_livDzfo?p1fD@oB(7UBS(2e5{==!TUy0P#K-H88{WSg3{@3Z|cV7b@zCpKs@fUh8?a;fw%5WY| zevYpH?g-s@^0Rbfbd_$r+(*~7Zo0m5m99Vk8|>%Q-wSU}$ Date: Wed, 20 Apr 2022 15:59:28 +0200 Subject: [PATCH 28/52] Added further tests for EMAC CMORizer --- esmvalcore/cmor/_fixes/emac/emac.py | 132 +-- .../cmor/tables/custom/CMOR_MP_BC_as.dat | 19 - .../cmor/tables/custom/CMOR_MP_BC_ki.dat | 19 - .../cmor/tables/custom/CMOR_MP_BC_ks.dat | 19 - .../cmor/tables/custom/CMOR_MP_DU_ai.dat | 19 - .../cmor/tables/custom/CMOR_MP_DU_as.dat | 19 - .../cmor/tables/custom/CMOR_MP_DU_ci.dat | 19 - .../cmor/tables/custom/CMOR_MP_DU_cs.dat | 19 - .../cmor/tables/custom/CMOR_MP_SO4mm_as.dat | 19 - .../cmor/tables/custom/CMOR_MP_SO4mm_cs.dat | 19 - .../cmor/tables/custom/CMOR_MP_SO4mm_ks.dat | 19 - .../cmor/tables/custom/CMOR_MP_SO4mm_ns.dat | 19 - .../cmor/tables/custom/CMOR_MP_SS_as.dat | 19 - .../cmor/tables/custom/CMOR_MP_SS_cs.dat | 19 - .../cmor/tables/custom/CMOR_MP_SS_ks.dat | 19 - .../integration/cmor/_fixes/emac/test_emac.py | 818 ++++++++++++------ 16 files changed, 615 insertions(+), 601 deletions(-) delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_BC_as.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_BC_ki.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_BC_ks.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_DU_ai.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_DU_as.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_DU_ci.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_DU_cs.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_as.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_cs.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ks.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ns.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SS_as.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SS_cs.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_MP_SS_ks.dat diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py index fc549e2472..bb7d541e93 100644 --- a/esmvalcore/cmor/_fixes/emac/emac.py +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -58,7 +58,7 @@ def fix_metadata(self, cubes): # Fix pressure levels (considers plev19, plev39, etc.) for dim_name in self.vardef.dimensions: if 'plev' in dim_name: - self._fix_plev(cube) + cube = self._fix_plev(cube) break # Fix latitude @@ -119,12 +119,19 @@ def _fix_plev(self, cube): continue if not coord.units.is_convertible('Pa'): continue + + # Fix metadata coord.var_name = 'plev' coord.standard_name = 'air_pressure' coord.long_name = 'pressure' coord.convert_units('Pa') coord.attributes['positive'] = 'down' - return + + # Reverse entire cube along height axis so that index 0 is surface + # level + cube = iris.util.reverse(cube, 'air_pressure') + return cube + raise ValueError( f"Cannot find requested pressure level coordinate for variable " f"'{self.vardef.short_name}', searched for Z-coordinates with " @@ -212,65 +219,6 @@ def fix_metadata(self, cubes): Hfss = NegateData -class MP_BC_tot(EmacFix): # noqa: N801 - """Fixes for ``MP_BC_tot``.""" - - def fix_metadata(self, cubes): - """Fix metadata.""" - cube = ( - cubes.extract_cube(NameConstraint(var_name='MP_BC_ki_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_BC_ks_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_BC_as_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_BC_cs_ave')) - ) - cube.var_name = self.vardef.short_name - return CubeList([cube]) - - -class MP_DU_tot(EmacFix): # noqa: N801 - """Fixes for ``MP_DU_tot``.""" - - def fix_metadata(self, cubes): - """Fix metadata.""" - cube = ( - cubes.extract_cube(NameConstraint(var_name='MP_DU_ai_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_DU_as_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_DU_ci_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_DU_cs_ave')) - ) - cube.var_name = self.vardef.short_name - return CubeList([cube]) - - -class MP_SO4mm_tot(EmacFix): # noqa: N801 - """Fixes for ``MP_SO4mm_tot``.""" - - def fix_metadata(self, cubes): - """Fix metadata.""" - cube = ( - cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_ns_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_ks_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_as_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_cs_ave')) - ) - cube.var_name = self.vardef.short_name - return CubeList([cube]) - - -class MP_SS_tot(EmacFix): # noqa: N801 - """Fixes for ``MP_SS_tot``.""" - - def fix_metadata(self, cubes): - """Fix metadata.""" - cube = ( - cubes.extract_cube(NameConstraint(var_name='MP_SS_ks_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_SS_as_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_SS_cs_ave')) - ) - cube.var_name = self.vardef.short_name - return CubeList([cube]) - - Od550aer = SetUnitsTo1SumOverZ @@ -384,3 +332,65 @@ def fix_metadata(self, cubes): cube.data = cube.core_data() / 100.0 cube.units = 'mm' return CubeList([cube]) + + +# Tracers + + +class MP_BC_tot(EmacFix): # noqa: N801 + """Fixes for ``MP_BC_tot``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + cubes.extract_cube(NameConstraint(var_name='MP_BC_ki_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_BC_ks_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_BC_as_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_BC_cs_ave')) + ) + cube.var_name = self.vardef.short_name + return CubeList([cube]) + + +class MP_DU_tot(EmacFix): # noqa: N801 + """Fixes for ``MP_DU_tot``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + cubes.extract_cube(NameConstraint(var_name='MP_DU_ai_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_DU_as_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_DU_ci_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_DU_cs_ave')) + ) + cube.var_name = self.vardef.short_name + return CubeList([cube]) + + +class MP_SO4mm_tot(EmacFix): # noqa: N801 + """Fixes for ``MP_SO4mm_tot``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_ns_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_ks_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_as_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_cs_ave')) + ) + cube.var_name = self.vardef.short_name + return CubeList([cube]) + + +class MP_SS_tot(EmacFix): # noqa: N801 + """Fixes for ``MP_SS_tot``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + cubes.extract_cube(NameConstraint(var_name='MP_SS_ks_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_SS_as_ave')) + + cubes.extract_cube(NameConstraint(var_name='MP_SS_cs_ave')) + ) + cube.var_name = self.vardef.short_name + return CubeList([cube]) diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_BC_as.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_BC_as.dat deleted file mode 100644 index 2c25b76370..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_MP_BC_as.dat +++ /dev/null @@ -1,19 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: MP_BC_as -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg -long_name: total mass of black carbon (mode as) -comment: positive mass of BC_as in kg -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: time -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_BC_ki.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_BC_ki.dat deleted file mode 100644 index 0097dda506..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_MP_BC_ki.dat +++ /dev/null @@ -1,19 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: MP_BC_ki -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg -long_name: total mass of black carbon (mode ki) -comment: positive mass of BC_ki in kg -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: time -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_BC_ks.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_BC_ks.dat deleted file mode 100644 index ff0fc9d995..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_MP_BC_ks.dat +++ /dev/null @@ -1,19 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: MP_BC_ks -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg -long_name: total mass of black carbon (mode ks) -comment: positive mass of BC_ks in kg -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: time -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_DU_ai.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_DU_ai.dat deleted file mode 100644 index 0099b4f5b6..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_MP_DU_ai.dat +++ /dev/null @@ -1,19 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: MP_DU_ai -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg -long_name: total mass of mineral dust (mode ai) -comment: positive mass of DU_ai in kg -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: time -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_DU_as.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_DU_as.dat deleted file mode 100644 index 35d396a379..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_MP_DU_as.dat +++ /dev/null @@ -1,19 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: MP_DU_as -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg -long_name: total mass of mineral dust (mode as) -comment: positive mass of DU_as in kg -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: time -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_DU_ci.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_DU_ci.dat deleted file mode 100644 index 98317216a4..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_MP_DU_ci.dat +++ /dev/null @@ -1,19 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: MP_DU_ci -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg -long_name: total mass of mineral dust (mode ci) -comment: positive mass of DU_ci in kg -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: time -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_DU_cs.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_DU_cs.dat deleted file mode 100644 index 0a6e514a4a..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_MP_DU_cs.dat +++ /dev/null @@ -1,19 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: MP_DU_cs -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg -long_name: total mass of mineral dust (mode cs) -comment: positive mass of DU_cs in kg -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: time -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_as.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_as.dat deleted file mode 100644 index be45560ee4..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_as.dat +++ /dev/null @@ -1,19 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: MP_SO4mm_as -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg -long_name: total mass of aerosol sulfate (mode as) -comment: positive mass of SO4mm_as in kg -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: time -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_cs.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_cs.dat deleted file mode 100644 index 4d30deade2..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_cs.dat +++ /dev/null @@ -1,19 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: MP_SO4mm_cs -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg -long_name: total mass of aerosol sulfate (mode cs) -comment: positive mass of SO4mm_cs in kg -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: time -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ks.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ks.dat deleted file mode 100644 index 4e58e6c4c7..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ks.dat +++ /dev/null @@ -1,19 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: MP_SO4mm_ks -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg -long_name: total mass of aerosol sulfate (mode ks) -comment: positive mass of SO4mm_ks in kg -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: time -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ns.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ns.dat deleted file mode 100644 index b33f6176f7..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_MP_SO4mm_ns.dat +++ /dev/null @@ -1,19 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: MP_SO4mm_ns -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg -long_name: total mass of aerosol sulfate (mode ns) -comment: positive mass of SO4mm_ns in kg -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: time -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SS_as.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SS_as.dat deleted file mode 100644 index 2d3cce4673..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_MP_SS_as.dat +++ /dev/null @@ -1,19 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: MP_SS_as -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg -long_name: total mass of sea salt (mode as) -comment: positive mass of SS_as in kg -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: time -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SS_cs.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SS_cs.dat deleted file mode 100644 index c28980dc44..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_MP_SS_cs.dat +++ /dev/null @@ -1,19 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: MP_SS_cs -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg -long_name: total mass of sea salt (mode cs) -comment: positive mass of SS_cs in kg -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: time -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_MP_SS_ks.dat b/esmvalcore/cmor/tables/custom/CMOR_MP_SS_ks.dat deleted file mode 100644 index 129fa10aa8..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_MP_SS_ks.dat +++ /dev/null @@ -1,19 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: MP_SS_ks -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg -long_name: total mass of sea salt (mode ks) -comment: positive mass of SS_ks in kg -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: time -!---------------------------------- -! diff --git a/tests/integration/cmor/_fixes/emac/test_emac.py b/tests/integration/cmor/_fixes/emac/test_emac.py index b46e73e90c..62eeb2c596 100644 --- a/tests/integration/cmor/_fixes/emac/test_emac.py +++ b/tests/integration/cmor/_fixes/emac/test_emac.py @@ -17,6 +17,10 @@ Evspsbl, Hfls, Hfss, + MP_BC_tot, + MP_DU_tot, + MP_SO4mm_tot, + MP_SS_tot, Od550aer, Pr, Rlds, @@ -105,28 +109,7 @@ def check_ta_metadata(cubes): assert cube.standard_name == 'air_temperature' assert cube.long_name == 'Air Temperature' assert cube.units == 'K' - return cube - - -def check_tas_metadata(cubes): - """Check tas metadata.""" - assert len(cubes) == 1 - cube = cubes[0] - assert cube.var_name == 'tas' - assert cube.standard_name == 'air_temperature' - assert cube.long_name == 'Near-Surface Air Temperature' - assert cube.units == 'K' - return cube - - -def check_siconc_metadata(cubes, var_name, long_name): - """Check tas metadata.""" - assert len(cubes) == 1 - cube = cubes[0] - assert cube.var_name == var_name - assert cube.standard_name == 'sea_ice_area_fraction' - assert cube.long_name == long_name - assert cube.units == '%' + assert 'positive' not in cube.attributes return cube @@ -153,56 +136,22 @@ def check_time(cube, n_points=1): assert time.attributes == {} -def check_height(cube, plev_has_bounds=True): - """Check height coordinate of cube.""" - assert cube.coords('model level number', dim_coords=True) - height = cube.coord('model level number', dim_coords=True) - assert height.var_name == 'model_level' - assert height.standard_name is None - assert height.long_name == 'model level number' - assert height.units == 'no unit' - np.testing.assert_array_equal(height.points, np.arange(47)) - assert height.bounds is None - assert height.attributes == {'positive': 'up'} - - assert cube.coords('air_pressure', dim_coords=False) - plev = cube.coord('air_pressure', dim_coords=False) +def check_plev(cube): + """Check plev coordinate of cube.""" + assert cube.coords('air_pressure', dim_coords=True) + plev = cube.coord('air_pressure', dim_coords=True) assert plev.var_name == 'plev' assert plev.standard_name == 'air_pressure' assert plev.long_name == 'pressure' assert plev.units == 'Pa' - assert plev.attributes == {'positive': 'down'} - assert cube.coord_dims('air_pressure') == (0, 1, 2) - - if plev_has_bounds: - assert plev.bounds is not None - else: - assert plev.bounds is None - - -def check_heightxm(cube, height_value): - """Check scalar heightxm coordinate of cube.""" - assert cube.coords('height') - height = cube.coord('height') - assert height.var_name == 'height' - assert height.standard_name == 'height' - assert height.long_name == 'height' - assert height.units == 'm' - assert height.attributes == {'positive': 'up'} - np.testing.assert_allclose(height.points, [height_value]) - assert height.bounds is None - - -def check_lambda550nm(cube): - """Check scalar lambda550nm coordinate of cube.""" - assert cube.coords('radiation_wavelength') - typesi = cube.coord('radiation_wavelength') - assert typesi.var_name == 'wavelength' - assert typesi.standard_name == 'radiation_wavelength' - assert typesi.long_name == 'Radiation Wavelength 550 nanometers' - assert typesi.units == 'nm' - np.testing.assert_array_equal(typesi.points, [550.0]) - assert typesi.bounds is None + assert plev.attributes == {'positive': 'down', 'interpolation': 'linear'} + np.testing.assert_allclose( + plev.points, + [100000.0, 92500.0, 85000.0, 70000.0, 60000.0, 50000.0, 40000.0, + 30000.0, 25000.0, 20000.0, 15000.0, 10000.0, 7000.0, 5000.0, 3000.0, + 2000.0, 1000.0, 500.0, 100.0], + ) + assert plev.bounds is None def check_lat(cube): @@ -248,6 +197,31 @@ def check_lon(cube): assert lon.attributes == {} +def check_heightxm(cube, height_value): + """Check scalar heightxm coordinate of cube.""" + assert cube.coords('height') + height = cube.coord('height') + assert height.var_name == 'height' + assert height.standard_name == 'height' + assert height.long_name == 'height' + assert height.units == 'm' + assert height.attributes == {'positive': 'up'} + np.testing.assert_allclose(height.points, [height_value]) + assert height.bounds is None + + +def check_lambda550nm(cube): + """Check scalar lambda550nm coordinate of cube.""" + assert cube.coords('radiation_wavelength') + typesi = cube.coord('radiation_wavelength') + assert typesi.var_name == 'wavelength' + assert typesi.standard_name == 'radiation_wavelength' + assert typesi.long_name == 'Radiation Wavelength 550 nanometers' + assert typesi.units == 'nm' + np.testing.assert_array_equal(typesi.points, [550.0]) + assert typesi.bounds is None + + def check_typesi(cube): """Check scalar typesi coordinate of cube.""" assert cube.coords('area_type') @@ -1565,277 +1539,573 @@ def test_vas_fix(cubes_amon_2d): ) -# Test areacella and areacello (for extra_facets, and grid_latitude and -# grid_longitude coordinates) +# Test each tracer variable in extra_facets/emac-mappings.yml -# def test_get_areacella_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('EMAC', 'EMAC', 'fx', 'areacella') -# assert fix == [AllVars(None)] +def test_get_MP_BC_tot_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_BC_tot') + assert fix == [MP_BC_tot(None), AllVars(None)] -# def test_areacella_fix(cubes_grid): -# """Test fix.""" -# fix = get_allvars_fix('fx', 'areacella') -# fixed_cubes = fix.fix_metadata(cubes_grid) +def test_MP_BC_tot_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + vardef = get_var_info('EMAC', 'TRAC10hr', 'MP_BC_tot') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'TRAC10hr', 'MP_BC_tot', + ()) + fix = MP_BC_tot(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.var_name == 'areacella' -# assert cube.standard_name == 'cell_area' -# assert cube.long_name == 'Grid-Cell Area for Atmospheric Grid Variables' -# assert cube.units == 'm2' + fix = get_allvars_fix('TRAC10hr', 'MP_BC_tot') + fixed_cubes = fix.fix_metadata(fixed_cubes) -# check_lat_lon(cube) + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_BC_tot' + assert cube.standard_name is None + assert cube.long_name == ('total mass of black carbon (sum of all aerosol ' + 'modes)') + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + check_time(cube, n_points=2) -# def test_get_areacello_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('EMAC', 'EMAC', 'Ofx', 'areacello') -# assert fix == [AllVars(None)] + np.testing.assert_allclose( + cube.data, + [6.361834e+08, 6.371043e+08], + rtol=1e-5, + ) -# def test_areacello_fix(cubes_grid): -# """Test fix.""" -# fix = get_allvars_fix('Ofx', 'areacello') -# fixed_cubes = fix.fix_metadata(cubes_grid) +def test_get_MP_CFCl3_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_CFCl3') + assert fix == [AllVars(None)] -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.var_name == 'areacello' -# assert cube.standard_name == 'cell_area' -# assert cube.long_name == 'Grid-Cell Area for Ocean Variables' -# assert cube.units == 'm2' -# check_lat_lon(cube) +def test_MP_CFCl3_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + fix = get_allvars_fix('TRAC10hr', 'MP_CFCl3') + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_CFCl3' + assert cube.standard_name is None + assert cube.long_name == 'total mass of CFCl3 (CFC-11)' + assert cube.units == 'kg' + assert 'positive' not in cube.attributes -# # Test clwvi (for extra_facets) + check_time(cube, n_points=2) + np.testing.assert_allclose( + cube.data, + [5.982788e+09, 5.982657e+09], + rtol=1e-5, + ) -# def test_get_clwvi_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'clwvi') -# assert fix == [AllVars(None)] +def test_get_MP_ClOX_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_ClOX') + assert fix == [AllVars(None)] -# def test_clwvi_fix(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'clwvi') -# fixed_cubes = fix.fix_metadata(cubes_2d) -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.var_name == 'clwvi' -# assert cube.standard_name == ('atmosphere_mass_content_of_cloud_' -# 'condensed_water') -# assert cube.long_name == 'Condensed Water Path' -# assert cube.units == 'kg m-2' +def test_MP_ClOX_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + fix = get_allvars_fix('TRAC10hr', 'MP_ClOX') + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) -# check_time(cube) -# check_lat_lon(cube) + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_ClOX' + assert cube.standard_name is None + assert cube.long_name == 'total mass of ClOX' + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + check_time(cube, n_points=2) -# # Test ta (for height and plev coordinate) + np.testing.assert_allclose( + cube.data, + [39589028.0, 39722044.0], + rtol=1e-5, + ) -# def test_get_ta_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'ta') -# assert fix == [AllVars(None)] +def test_get_MP_CH4_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_CH4') + assert fix == [AllVars(None)] -# def test_ta_fix(cubes_3d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'ta') -# fixed_cubes = fix.fix_metadata(cubes_3d) +def test_MP_CH4_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + fix = get_allvars_fix('TRAC10hr', 'MP_CH4') + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) -# cube = check_ta_metadata(fixed_cubes) -# check_time(cube) -# check_height(cube) -# check_lat_lon(cube) + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_CH4' + assert cube.standard_name is None + assert cube.long_name == 'total mass of CH4' + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + check_time(cube, n_points=2) -# def test_ta_fix_no_plev_bounds(cubes_3d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'ta') -# cubes = CubeList([ -# cubes_3d.extract_cube(NameConstraint(var_name='ta')), -# cubes_3d.extract_cube(NameConstraint(var_name='pfull')), -# ]) -# fixed_cubes = fix.fix_metadata(cubes) + np.testing.assert_allclose( + cube.data, + [4.866472e+12, 4.866396e+12], + rtol=1e-5, + ) -# cube = check_ta_metadata(fixed_cubes) -# check_time(cube) -# check_height(cube, plev_has_bounds=False) -# check_lat_lon(cube) +def test_get_MP_CO_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_CO') + assert fix == [AllVars(None)] -# # Test tas (for height2m coordinate) +def test_MP_CO_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + fix = get_allvars_fix('TRAC10hr', 'MP_CO') + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) -# def test_get_tas_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'tas') -# assert fix == [AllVars(None)] + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_CO' + assert cube.standard_name is None + assert cube.long_name == 'total mass of CO' + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + check_time(cube, n_points=2) -# def test_tas_fix(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'tas') -# fixed_cubes = fix.fix_metadata(cubes_2d) + np.testing.assert_allclose( + cube.data, + [3.399702e+11, 3.401483e+11], + rtol=1e-5, + ) -# cube = check_tas_metadata(fixed_cubes) -# check_time(cube) -# check_lat_lon(cube) -# check_heightxm(cube, 2.0) +def test_get_MP_CO2_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_CO2') + assert fix == [AllVars(None)] -# def test_tas_spatial_index_coord_already_present(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'tas') -# index_coord = DimCoord(np.arange(8), var_name='ncells') -# cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) -# cube.add_dim_coord(index_coord, 1) -# fixed_cubes = fix.fix_metadata(cubes_2d) +def test_MP_CO2_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + fix = get_allvars_fix('TRAC10hr', 'MP_CO2') + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# check_lat_lon(cube) + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_CO2' + assert cube.standard_name is None + assert cube.long_name == 'total mass of CO2' + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + check_time(cube, n_points=2) -# def test_tas_scalar_height2m_already_present(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'tas') + np.testing.assert_allclose( + cube.data, + [2.855254e+15, 2.855380e+15], + rtol=1e-5, + ) -# # Scalar height (with wrong metadata) already present -# height_coord = AuxCoord(2.0, var_name='h', standard_name='height') -# cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) -# cube.add_aux_coord(height_coord, ()) -# fixed_cubes = fix.fix_metadata(cubes_2d) -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.shape == (1, 8) -# check_heightxm(cube, 2.0) +def test_get_MP_DU_tot_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_DU_tot') + assert fix == [MP_DU_tot(None), AllVars(None)] -# def test_tas_dim_height2m_already_present(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'tas') +def test_MP_DU_tot_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + vardef = get_var_info('EMAC', 'TRAC10hr', 'MP_DU_tot') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'TRAC10hr', 'MP_DU_tot', + ()) + fix = MP_DU_tot(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) -# # Dimensional coordinate height (with wrong metadata) already present -# height_coord = AuxCoord(2.0, var_name='h', standard_name='height') -# cube = cubes_2d.extract_cube(NameConstraint(var_name='tas')) -# cube.add_aux_coord(height_coord, ()) -# cube = iris.util.new_axis(cube, scalar_coord='height') -# cube.transpose((1, 0, 2)) -# cubes = CubeList([cube]) -# fixed_cubes = fix.fix_metadata(cubes) + fix = get_allvars_fix('TRAC10hr', 'MP_DU_tot') + fixed_cubes = fix.fix_metadata(fixed_cubes) -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.shape == (1, 8) -# check_heightxm(cube, 2.0) + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_DU_tot' + assert cube.standard_name is None + assert cube.long_name == ('total mass of mineral dust (sum of all aerosol ' + 'modes)') + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + check_time(cube, n_points=2) -# # Test uas (for height10m coordinate) + np.testing.assert_allclose( + cube.data, + [1.797283e+10, 1.704390e+10], + rtol=1e-5, + ) -# def test_get_uas_fix(): -# """Test getting of fix.""" -# fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'uas') -# assert fix == [AllVars(None)] +def test_get_MP_N2O_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_N2O') + assert fix == [AllVars(None)] -# def test_uas_fix(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'uas') -# fixed_cubes = fix.fix_metadata(cubes_2d) - -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.var_name == 'uas' -# assert cube.standard_name == 'eastward_wind' -# assert cube.long_name == 'Eastward Near-Surface Wind' -# assert cube.units == 'm s-1' - -# check_time(cube) -# check_lat_lon(cube) -# assert cube.coords('height') -# height = cube.coord('height') -# assert height.var_name == 'height' -# assert height.standard_name == 'height' -# assert height.long_name == 'height' -# assert height.units == 'm' -# assert height.attributes == {'positive': 'up'} -# np.testing.assert_allclose(height.points, [10.0]) -# assert height.bounds is None - - -# def test_uas_scalar_height10m_already_present(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'uas') +def test_MP_N2O_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + fix = get_allvars_fix('TRAC10hr', 'MP_N2O') + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) -# # Scalar height (with wrong metadata) already present -# height_coord = AuxCoord(10.0, var_name='h', standard_name='height') -# cube = cubes_2d.extract_cube(NameConstraint(var_name='uas')) -# cube.add_aux_coord(height_coord, ()) -# fixed_cubes = fix.fix_metadata(cubes_2d) + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_N2O' + assert cube.standard_name is None + assert cube.long_name == 'total mass of N2O' + assert cube.units == 'kg' + assert 'positive' not in cube.attributes -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.shape == (1, 8) -# check_heightxm(cube, 10.0) + check_time(cube, n_points=2) + np.testing.assert_allclose( + cube.data, + [2.365061e+12, 2.365089e+12], + rtol=1e-5, + ) -# def test_uas_dim_height10m_already_present(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'uas') -# # Dimensional coordinate height (with wrong metadata) already present -# height_coord = AuxCoord(10.0, var_name='h', standard_name='height') -# cube = cubes_2d.extract_cube(NameConstraint(var_name='uas')) -# cube.add_aux_coord(height_coord, ()) -# cube = iris.util.new_axis(cube, scalar_coord='height') -# cube.transpose((1, 0, 2)) -# cubes = CubeList([cube]) -# fixed_cubes = fix.fix_metadata(cubes) +def test_get_MP_NH3_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_NH3') + assert fix == [AllVars(None)] -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.shape == (1, 8) -# check_heightxm(cube, 10.0) +def test_MP_NH3_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + fix = get_allvars_fix('TRAC10hr', 'MP_NH3') + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) -# # Test fix with empty standard_name + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_NH3' + assert cube.standard_name is None + assert cube.long_name == 'total mass of NH3' + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + check_time(cube, n_points=2) -# def test_empty_standard_name_fix(cubes_2d): -# """Test fix.""" -# # We know that tas has a standard name, but this being native model -# # output -# # there may be variables with no standard name. The code is designed to -# # handle this gracefully and here we test it with an artificial, but -# # realistic case. -# vardef = get_var_info('EMAC', 'Amon', 'tas') -# original_standard_name = vardef.standard_name -# vardef.standard_name = '' -# extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'tas', ()) -# fix = AllVars(vardef, extra_facets=extra_facets) -# fixed_cubes = fix.fix_metadata(cubes_2d) - -# assert len(fixed_cubes) == 1 -# cube = fixed_cubes[0] -# assert cube.var_name == 'tas' -# assert cube.standard_name is None -# assert cube.long_name == 'Near-Surface Air Temperature' -# assert cube.units == 'K' - -# # Restore original standard_name of tas -# vardef.standard_name = original_standard_name + np.testing.assert_allclose( + cube.data, + [1.931037e+08, 1.944860e+08], + rtol=1e-5, + ) + + +def test_get_MP_NO_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_NO') + assert fix == [AllVars(None)] + + +def test_MP_NO_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + fix = get_allvars_fix('TRAC10hr', 'MP_NO') + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_NO' + assert cube.standard_name is None + assert cube.long_name == 'total mass of NO' + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + + check_time(cube, n_points=2) + + np.testing.assert_allclose( + cube.data, + [5.399146e+08, 5.543320e+08], + rtol=1e-5, + ) + + +def test_get_MP_NO2_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_NO2') + assert fix == [AllVars(None)] + + +def test_MP_NO2_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + fix = get_allvars_fix('TRAC10hr', 'MP_NO2') + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_NO2' + assert cube.standard_name is None + assert cube.long_name == 'total mass of NO2' + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + + check_time(cube, n_points=2) + + np.testing.assert_allclose( + cube.data, + [1.734202e+09, 1.725541e+09], + rtol=1e-5, + ) + + +def test_get_MP_NOX_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_NOX') + assert fix == [AllVars(None)] + + +def test_MP_NOX_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + fix = get_allvars_fix('TRAC10hr', 'MP_NOX') + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_NOX' + assert cube.standard_name is None + assert cube.long_name == 'total mass of NOX (NO+NO2)' + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + + check_time(cube, n_points=2) + + np.testing.assert_allclose( + cube.data, + [9.384478e+08, 9.342440e+08], + rtol=1e-5, + ) + + +def test_get_MP_O3_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_O3') + assert fix == [AllVars(None)] + + +def test_MP_O3_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + fix = get_allvars_fix('TRAC10hr', 'MP_O3') + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_O3' + assert cube.standard_name is None + assert cube.long_name == 'total mass of O3' + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + + check_time(cube, n_points=2) + + np.testing.assert_allclose( + cube.data, + [3.339367e+12, 3.339434e+12], + rtol=1e-5, + ) + + +def test_get_MP_OH_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_OH') + assert fix == [AllVars(None)] + + +def test_MP_OH_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + fix = get_allvars_fix('TRAC10hr', 'MP_OH') + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_OH' + assert cube.standard_name is None + assert cube.long_name == 'total mass of OH' + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + + check_time(cube, n_points=2) + + np.testing.assert_allclose( + cube.data, + [3816360.8, 3820260.8], + rtol=1e-5, + ) + + +def test_get_MP_S_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_S') + assert fix == [AllVars(None)] + + +def test_MP_S_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + fix = get_allvars_fix('TRAC10hr', 'MP_S') + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_S' + assert cube.standard_name is None + assert cube.long_name == 'total mass of S' + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + + check_time(cube, n_points=2) + + np.testing.assert_allclose( + cube.data, + [0.0, 0.0], + rtol=1e-5, + ) + + +def test_get_MP_SO2_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_SO2') + assert fix == [AllVars(None)] + + +def test_MP_SO2_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + fix = get_allvars_fix('TRAC10hr', 'MP_SO2') + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_SO2' + assert cube.standard_name is None + assert cube.long_name == 'total mass of SO2' + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + + check_time(cube, n_points=2) + + np.testing.assert_allclose( + cube.data, + [1.383063e+09, 1.390189e+09], + rtol=1e-5, + ) + + +def test_get_MP_SO4mm_tot_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_SO4mm_tot') + assert fix == [MP_SO4mm_tot(None), AllVars(None)] + + +def test_MP_SO4mm_tot_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + vardef = get_var_info('EMAC', 'TRAC10hr', 'MP_SO4mm_tot') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'TRAC10hr', 'MP_SO4mm_tot', + ()) + fix = MP_SO4mm_tot(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + + fix = get_allvars_fix('TRAC10hr', 'MP_SO4mm_tot') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_SO4mm_tot' + assert cube.standard_name is None + assert cube.long_name == ('total mass of aerosol sulfate (sum of all ' + 'aerosol modes)') + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + + check_time(cube, n_points=2) + + np.testing.assert_allclose( + cube.data, + [1.350434e+09, 1.364699e+09], + rtol=1e-5, + ) + + +def test_get_MP_SS_tot_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'TRAC10hr', 'MP_SS_tot') + assert fix == [MP_SS_tot(None), AllVars(None)] + + +def test_MP_SS_tot_fix(cubes_tracer_pdef_gp): # noqa: N802 + """Test fix.""" + vardef = get_var_info('EMAC', 'TRAC10hr', 'MP_SS_tot') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'TRAC10hr', 'MP_SS_tot', + ()) + fix = MP_SS_tot(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + + fix = get_allvars_fix('TRAC10hr', 'MP_SS_tot') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'MP_SS_tot' + assert cube.standard_name is None + assert cube.long_name == ('total mass of sea salt (sum of all aerosol ' + 'modes)') + assert cube.units == 'kg' + assert 'positive' not in cube.attributes + + check_time(cube, n_points=2) + + np.testing.assert_allclose( + cube.data, + [2.322862e+08, 2.340771e+08], + rtol=1e-5, + ) + + +# Test each 3D variable in extra_facets/emac-mappings.yml + + +def test_get_ta_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'ta') + assert fix == [AllVars(None)] + + +def test_ta_fix(cubes_amon_3d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'ta') + fixed_cubes = fix.fix_metadata(cubes_amon_3d) + + cube = check_ta_metadata(fixed_cubes) + + fixed_cube = fix.fix_data(cube) + + check_time(fixed_cube) + check_plev(fixed_cube) + check_lat(fixed_cube) + check_lon(fixed_cube) + + np.testing.assert_allclose( + fixed_cube.data[0, 1:5, 0, 0], + [2.702699e+02, 2.664087e+02, 2.584884e+02, 2.509335e+02], + rtol=1e-5, + ) + np.testing.assert_equal( + fixed_cube.data.mask[0, :5, 0, 0], + [True, False, False, False, False], + ) # # Test variable not available in file From 17da485a78c9884c8ba94ed9046f3dfac30bedb3 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Wed, 20 Apr 2022 17:03:07 +0200 Subject: [PATCH 29/52] Removed automatic reversing of plev coord in EMAC CMORizer (this is done in CMOR checker automaticall) --- esmvalcore/cmor/_fixes/emac/emac.py | 9 +++------ .../integration/cmor/_fixes/emac/test_emac.py | 18 +++++++++++------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py index bb7d541e93..a118029337 100644 --- a/esmvalcore/cmor/_fixes/emac/emac.py +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -58,7 +58,7 @@ def fix_metadata(self, cubes): # Fix pressure levels (considers plev19, plev39, etc.) for dim_name in self.vardef.dimensions: if 'plev' in dim_name: - cube = self._fix_plev(cube) + self._fix_plev(cube) break # Fix latitude @@ -115,22 +115,19 @@ def _fix_plev(self, cube): """Fix pressure level coordinate of cube.""" for coord in cube.coords(): coord_type = iris.util.guess_coord_axis(coord) + if coord_type != 'Z': continue if not coord.units.is_convertible('Pa'): continue - # Fix metadata coord.var_name = 'plev' coord.standard_name = 'air_pressure' coord.long_name = 'pressure' coord.convert_units('Pa') coord.attributes['positive'] = 'down' - # Reverse entire cube along height axis so that index 0 is surface - # level - cube = iris.util.reverse(cube, 'air_pressure') - return cube + return raise ValueError( f"Cannot find requested pressure level coordinate for variable " diff --git a/tests/integration/cmor/_fixes/emac/test_emac.py b/tests/integration/cmor/_fixes/emac/test_emac.py index 62eeb2c596..15ad824377 100644 --- a/tests/integration/cmor/_fixes/emac/test_emac.py +++ b/tests/integration/cmor/_fixes/emac/test_emac.py @@ -145,11 +145,15 @@ def check_plev(cube): assert plev.long_name == 'pressure' assert plev.units == 'Pa' assert plev.attributes == {'positive': 'down', 'interpolation': 'linear'} + + # Note: plev is reversed (index 0 should be surface, but is TOA at the + # moment), but this is fixed in the CMOR checks in a later step + # automatically np.testing.assert_allclose( plev.points, - [100000.0, 92500.0, 85000.0, 70000.0, 60000.0, 50000.0, 40000.0, - 30000.0, 25000.0, 20000.0, 15000.0, 10000.0, 7000.0, 5000.0, 3000.0, - 2000.0, 1000.0, 500.0, 100.0], + [100.0, 500.0, 1000.0, 2000.0, 3000.0, 5000.0, 7000.0, 10000.0, + 15000.0, 20000.0, 25000.0, 30000.0, 40000.0, 50000.0, 60000.0, + 70000.0, 85000.0, 92500.0, 100000.0], ) assert plev.bounds is None @@ -2098,13 +2102,13 @@ def test_ta_fix(cubes_amon_3d): check_lon(fixed_cube) np.testing.assert_allclose( - fixed_cube.data[0, 1:5, 0, 0], - [2.702699e+02, 2.664087e+02, 2.584884e+02, 2.509335e+02], + fixed_cube.data[0, -5:-1, 0, 0], + [250.93347, 258.48843, 266.4087, 270.26993], rtol=1e-5, ) np.testing.assert_equal( - fixed_cube.data.mask[0, :5, 0, 0], - [True, False, False, False, False], + fixed_cube.data.mask[0, -5:, 0, 0], + [False, False, False, False, True], ) From 8a0ac3d3a523a928e17a485e63b97b14e84cd1f0 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 21 Apr 2022 17:06:33 +0200 Subject: [PATCH 30/52] Added support for hybrid pressure level coordinates --- .../_config/extra_facets/emac-mappings.yml | 26 ++- esmvalcore/cmor/_fixes/emac/emac.py | 200 ++++++++++++++---- .../integration/cmor/_fixes/emac/test_emac.py | 145 ++++++++++++- .../cmor/_fixes/test_data/emac_amon_3d.nc | Bin 56764 -> 65324 bytes 4 files changed, 324 insertions(+), 47 deletions(-) diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml index 0efd73720c..252e6214ae 100644 --- a/esmvalcore/_config/extra_facets/emac-mappings.yml +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -3,7 +3,7 @@ # All extra facets for EMAC are optional but might be necessary for some # variables. Note that if the facet ``channel`` is not given here it has to be # specified in the recipe. A complete list of supported keys is given in the -# documentation (see ESMValCore/doc/develop/fixing_data.rst). +# documentation (see ESMValCore/doc/quickstart/find_data.rst). --- EMAC: @@ -164,7 +164,7 @@ EMAC: MP_SS_tot: # derived from MP_SS_ks_ave, MP_SS_as_ave, MP_SS_cs_ave channel: tracer_pdef_gp - # 3D variables + # 3D variables with regular Z-coords Amon: ta: # defined on plev19 raw_name: tm1_p19_ave @@ -174,6 +174,28 @@ EMAC: raw_name: tm1_p19_ave channel: Amon + # 3D variables with hybrid Z-coords + 6hrLev: + ta: + raw_name: tm1_ave + channel: Amon + CF3hr: + ta: + raw_name: tm1_ave + channel: Amon + CFday: + ta: + raw_name: tm1_ave + channel: Amon + CFmon: + ta: + raw_name: tm1_ave + channel: Amon + Esubhr: + ta: + raw_name: tm1_ave + channel: Amon + # Elements missing from original mapping table # diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py index a118029337..3b43b03ca5 100644 --- a/esmvalcore/cmor/_fixes/emac/emac.py +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -12,14 +12,18 @@ """ import logging +from shutil import copyfile import dask.array as da import iris.analysis import iris.util from iris import NameConstraint +from iris.aux_factory import HybridPressureFactory from iris.cube import CubeList +from netCDF4 import Dataset from ..shared import ( + add_aux_coords_from_cubes, add_scalar_height_coord, add_scalar_lambda550nm_coord, add_scalar_typesi_coord, @@ -37,6 +41,29 @@ class AllVars(EmacFix): """Fixes for all variables.""" + def fix_file(self, filepath, output_dir): + """Fix file. + + Fixes hybrid pressure level coordinate. + + Note + ---- + This fix removes the ``formula_terms`` attribute of the hybrid pressure + level variables to make the corresponding coefficients appear correctly + in the class:`iris.cube.CubeList` object returned by :mod:`iris.load`. + + """ + if 'alevel' not in self.vardef.dimensions: + return filepath + new_path = self.get_fixed_filepath(output_dir, filepath) + copyfile(filepath, new_path) + with Dataset(new_path, mode='a') as dataset: + if 'formula_terms' in dataset.variables['lev'].ncattrs(): + del dataset.variables['lev'].formula_terms + if 'formula_terms' in dataset.variables['ilev'].ncattrs(): + del dataset.variables['ilev'].formula_terms + return new_path + def fix_data(self, cube): """Fix data.""" # Fix mask by masking all values where the absolute value is greater @@ -55,12 +82,16 @@ def fix_metadata(self, cubes): if 'time' in self.vardef.dimensions: self._fix_time(cube) - # Fix pressure levels (considers plev19, plev39, etc.) + # Fix regular pressure levels (considers plev19, plev39, etc.) for dim_name in self.vardef.dimensions: if 'plev' in dim_name: self._fix_plev(cube) break + # Fix hybrid pressure levels + if 'alevel' in self.vardef.dimensions: + cube = self._fix_alevel(cube, cubes) + # Fix latitude if 'latitude' in self.vardef.dimensions: self._fix_lat(cube) @@ -77,6 +108,134 @@ def fix_metadata(self, cubes): return CubeList([cube]) + @staticmethod + def _fix_time(cube): + """Fix time coordinate of cube.""" + time_coord = cube.coord('time') + time_coord.var_name = 'time' + time_coord.standard_name = 'time' + time_coord.long_name = 'time' + + # Add bounds if possible (not possible if cube only contains single + # time point) + if not time_coord.has_bounds(): + try: + time_coord.guess_bounds() + except ValueError: + pass + + def _fix_plev(self, cube): + """Fix regular pressure level coordinate of cube.""" + for coord in cube.coords(): + coord_type = iris.util.guess_coord_axis(coord) + + if coord_type != 'Z': + continue + if not coord.units.is_convertible('Pa'): + continue + + coord.var_name = 'plev' + coord.standard_name = 'air_pressure' + coord.long_name = 'pressure' + coord.convert_units('Pa') + coord.attributes['positive'] = 'down' + + return + + raise ValueError( + f"Cannot find requested pressure level coordinate for variable " + f"'{self.vardef.short_name}', searched for Z-coordinates with " + f"units that are convertible to Pa") + + @staticmethod + def _fix_alevel(cube, cubes): + """Fix hybrid pressure level coordinate of cube.""" + # Add coefficients for hybrid pressure level coordinate + coords_to_add = { + 'hyam': 1, + 'hybm': 1, + 'aps_ave': (0, 2, 3), + } + add_aux_coords_from_cubes(cube, cubes, coords_to_add) + + # Reverse entire cube along Z-axis so that index 0 is surface level + # Note: This would automatically be fixed by the CMOR checker, but this + # fails to fix the bounds of ap and b + cube = iris.util.reverse(cube, cube.coord(var_name='lev')) + + # Adapt metadata of coordinates + lev_coord = cube.coord(var_name='lev') + ap_coord = cube.coord(var_name='hyam') + b_coord = cube.coord(var_name='hybm') + ps_coord = cube.coord(var_name='aps_ave') + + lev_coord.var_name = 'lev' + lev_coord.standard_name = 'atmosphere_hybrid_sigma_pressure_coordinate' + lev_coord.long_name = 'hybrid sigma pressure coordinate' + lev_coord.units = '1' + lev_coord.attributes['positive'] = 'down' + + ap_coord.var_name = 'ap' + ap_coord.standard_name = None + ap_coord.long_name = 'vertical coordinate formula term: ap(k)' + ap_coord.attributes = {} + + b_coord.var_name = 'b' + b_coord.standard_name = None + b_coord.long_name = 'vertical coordinate formula term: b(k)' + b_coord.attributes = {} + + ps_coord.var_name = 'ps' + ps_coord.standard_name = 'surface_air_pressure' + ps_coord.long_name = 'Surface Air Pressure' + ps_coord.attributes = {} + + # Add bounds for coefficients + # (make sure to reverse cubes beforehand so index 0 is surface level) + ap_bnds_cube = iris.util.reverse( + cubes.extract_cube(NameConstraint(var_name='hyai')), + 0, + ) + b_bnds_cube = iris.util.reverse( + cubes.extract_cube(NameConstraint(var_name='hybi')), + 0, + ) + ap_bounds = da.stack( + [ap_bnds_cube.core_data()[:-1], ap_bnds_cube.core_data()[1:]], + axis=-1, + ) + b_bounds = da.stack( + [b_bnds_cube.core_data()[:-1], b_bnds_cube.core_data()[1:]], + axis=-1, + ) + ap_coord.bounds = ap_bounds + b_coord.bounds = b_bounds + + # Convert arrays to float64 + for coord in (ap_coord, b_coord, ps_coord): + coord.points = coord.core_points().astype( + float, casting='same_kind') + if coord.bounds is not None: + coord.bounds = coord.core_bounds().astype( + float, casting='same_kind') + + # Fix values of lev coordinate + # NOte: lev = a + b with a = ap / p0 (p0 = 100000 Pa) + lev_coord.points = (ap_coord.core_points() / 100000.0 + + b_coord.core_points()) + lev_coord.bounds = (ap_coord.core_bounds() / 100000.0 + + b_coord.core_bounds()) + + # Add HybridPressureFactory + pressure_coord_factory = HybridPressureFactory( + delta=ap_coord, + sigma=b_coord, + surface_air_pressure=ps_coord, + ) + cube.add_aux_factory(pressure_coord_factory) + + return cube + @staticmethod def _fix_lat(cube): """Fix latitude coordinate of cube.""" @@ -111,29 +270,6 @@ def _fix_lon(cube): except ValueError: pass - def _fix_plev(self, cube): - """Fix pressure level coordinate of cube.""" - for coord in cube.coords(): - coord_type = iris.util.guess_coord_axis(coord) - - if coord_type != 'Z': - continue - if not coord.units.is_convertible('Pa'): - continue - - coord.var_name = 'plev' - coord.standard_name = 'air_pressure' - coord.long_name = 'pressure' - coord.convert_units('Pa') - coord.attributes['positive'] = 'down' - - return - - raise ValueError( - f"Cannot find requested pressure level coordinate for variable " - f"'{self.vardef.short_name}', searched for Z-coordinates with " - f"units that are convertible to Pa") - def _fix_scalar_coords(self, cube): """Fix scalar coordinates.""" if 'height2m' in self.vardef.dimensions: @@ -145,22 +281,6 @@ def _fix_scalar_coords(self, cube): if 'typesi' in self.vardef.dimensions: add_scalar_typesi_coord(cube, 'sea_ice') - @staticmethod - def _fix_time(cube): - """Fix time coordinate of cube.""" - time_coord = cube.coord('time') - time_coord.var_name = 'time' - time_coord.standard_name = 'time' - time_coord.long_name = 'time' - - # Add bounds if possible (not possible if cube only contains single - # time point) - if not time_coord.has_bounds(): - try: - time_coord.guess_bounds() - except ValueError: - pass - def _fix_var_metadata(self, cube): """Fix metadata of variable.""" if self.vardef.standard_name == '': diff --git a/tests/integration/cmor/_fixes/emac/test_emac.py b/tests/integration/cmor/_fixes/emac/test_emac.py index 15ad824377..86b863b270 100644 --- a/tests/integration/cmor/_fixes/emac/test_emac.py +++ b/tests/integration/cmor/_fixes/emac/test_emac.py @@ -5,7 +5,7 @@ import numpy as np import pytest from cf_units import Unit -# from iris import NameConstraint +from iris import NameConstraint from iris.coords import DimCoord from iris.cube import Cube, CubeList @@ -144,7 +144,7 @@ def check_plev(cube): assert plev.standard_name == 'air_pressure' assert plev.long_name == 'pressure' assert plev.units == 'Pa' - assert plev.attributes == {'positive': 'down', 'interpolation': 'linear'} + assert plev.attributes['positive'] == 'down' # Note: plev is reversed (index 0 should be surface, but is TOA at the # moment), but this is fixed in the CMOR checks in a later step @@ -158,6 +158,101 @@ def check_plev(cube): assert plev.bounds is None +def check_alevel(cube): + """Check alevel coordinate of cube.""" + # atmosphere_hybrid_sigma_pressure_coordinate + assert cube.coords('atmosphere_hybrid_sigma_pressure_coordinate', + dim_coords=True) + lev = cube.coord('atmosphere_hybrid_sigma_pressure_coordinate', + dim_coords=True) + assert lev.var_name == 'lev' + assert lev.standard_name == 'atmosphere_hybrid_sigma_pressure_coordinate' + assert lev.long_name == 'hybrid sigma pressure coordinate' + assert lev.units == '1' + assert lev.attributes['positive'] == 'down' + np.testing.assert_allclose( + lev.points[:4], + [9.96150017e-01, 9.82649982e-01, 9.58960303e-01, 9.27668441e-01], + ) + np.testing.assert_allclose( + lev.bounds[:4], + [[1.00000000e+00, 9.92299974e-01], + [9.92299974e-01, 9.72999990e-01], + [9.72999990e-01, 9.44920615e-01], + [9.44920615e-01, 9.10416267e-01]], + ) + + # Coefficient ap + assert cube.coords('vertical coordinate formula term: ap(k)', + dim_coords=False) + ap_coord = cube.coord('vertical coordinate formula term: ap(k)', + dim_coords=False) + assert ap_coord.var_name == 'ap' + assert ap_coord.standard_name is None + assert ap_coord.long_name == 'vertical coordinate formula term: ap(k)' + assert ap_coord.units == 'Pa' + assert ap_coord.attributes == {} + np.testing.assert_allclose( + ap_coord.points[:4], + [0.0, 0.0, 36.03179932, 171.845047], + ) + np.testing.assert_allclose( + ap_coord.bounds[:4], + [[0.0, 0.0], + [0.0, 0.0], + [0.0, 72.06359863], + [72.06359863, 271.62649536]], + ) + + # Coefficient b + assert cube.coords('vertical coordinate formula term: b(k)', + dim_coords=False) + b_coord = cube.coord('vertical coordinate formula term: b(k)', + dim_coords=False) + assert b_coord.var_name == 'b' + assert b_coord.standard_name is None + assert b_coord.long_name == 'vertical coordinate formula term: b(k)' + assert b_coord.units == '1' + assert b_coord.attributes == {} + np.testing.assert_allclose( + b_coord.points[:4], + [0.99615002, 0.98264998, 0.95859998, 0.92594999], + ) + np.testing.assert_allclose( + b_coord.bounds[:4], + [[1.0, 0.99229997], + [0.99229997, 0.97299999], + [0.97299999, 0.94419998], + [0.94419998, 0.9077]], + ) + + # Coefficient ps + assert cube.coords('surface_air_pressure', dim_coords=False) + ps_coord = cube.coord('surface_air_pressure', dim_coords=False) + assert ps_coord.var_name == 'ps' + assert ps_coord.standard_name == 'surface_air_pressure' + assert ps_coord.long_name == 'Surface Air Pressure' + assert ps_coord.units == 'Pa' + assert ps_coord.attributes == {} + np.testing.assert_allclose( + ps_coord.points[:, :, 0], + [[100000.1875, 98240.7578125, 99601.09375, 96029.7109375]], + ) + assert ps_coord.bounds is None + + # air_pressure + assert cube.coords('air_pressure', dim_coords=False) + p_coord = cube.coord('air_pressure', dim_coords=False) + assert p_coord.var_name is None + assert p_coord.standard_name == 'air_pressure' + assert p_coord.long_name is None + assert p_coord.units == 'Pa' + assert p_coord.attributes == {} + assert p_coord.points[0, 0, 0, 0] > p_coord.points[0, -1, 0, 0] + assert p_coord.bounds[0, 0, 0, 0, 0] > p_coord.bounds[0, -1, 0, 0, 0] + assert p_coord.bounds[0, 0, 0, 0, 0] > p_coord.bounds[0, 0, 0, 0, 1] + + def check_lat(cube): """Check latitude coordinate of cube.""" assert cube.coords('latitude', dim_coords=True) @@ -2078,16 +2173,16 @@ def test_MP_SS_tot_fix(cubes_tracer_pdef_gp): # noqa: N802 ) -# Test each 3D variable in extra_facets/emac-mappings.yml +# Test each 3D variable with regular Z-coord in extra_facets/emac-mappings.yml -def test_get_ta_fix(): +def test_get_ta_amon_fix(): """Test getting of fix.""" fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'ta') assert fix == [AllVars(None)] -def test_ta_fix(cubes_amon_3d): +def test_ta_amon_fix(cubes_amon_3d): """Test fix.""" fix = get_allvars_fix('Amon', 'ta') fixed_cubes = fix.fix_metadata(cubes_amon_3d) @@ -2112,6 +2207,46 @@ def test_ta_fix(cubes_amon_3d): ) +# Test each 3D variable with hybrid Z-coord in extra_facets/emac-mappings.yml + + +def test_get_ta_cfon_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'CFmon', 'ta') + assert fix == [AllVars(None)] + + +def test_ta_cfmon_fix(test_data_path, tmp_path): + """Test fix.""" + fix = get_allvars_fix('CFmon', 'ta') + + filepath = test_data_path / 'emac_amon_3d.nc' + fixed_path = fix.fix_file(filepath, tmp_path) + cubes = iris.load(fixed_path) + + assert cubes.extract(NameConstraint(var_name='hyam')) + assert cubes.extract(NameConstraint(var_name='hybm')) + assert cubes.extract(NameConstraint(var_name='hyai')) + assert cubes.extract(NameConstraint(var_name='hybi')) + + fixed_cubes = fix.fix_metadata(cubes) + + cube = check_ta_metadata(fixed_cubes) + + fixed_cube = fix.fix_data(cube) + + check_time(fixed_cube) + check_alevel(fixed_cube) + check_lat(fixed_cube) + check_lon(fixed_cube) + + np.testing.assert_allclose( + fixed_cube.data[0, 0:5, 0, 0], + [272.32098, 271.45898, 270.3698, 269.20953, 267.84683], + rtol=1e-5, + ) + + # # Test variable not available in file diff --git a/tests/integration/cmor/_fixes/test_data/emac_amon_3d.nc b/tests/integration/cmor/_fixes/test_data/emac_amon_3d.nc index 1ce9fd65394874b069112211d513a94c9a1ab059..dc2d7552342b60d8e67e5f1e5b12660a183a4ea4 100644 GIT binary patch literal 65324 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઩kpB+c*3`E2KhZ`rqNde{!8Y3zct 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 From 8098233305a8381a8591744096c3f2f321ea79b5 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 21 Apr 2022 17:41:12 +0200 Subject: [PATCH 31/52] Fixed test --- tests/integration/cmor/_fixes/emac/test_emac.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/cmor/_fixes/emac/test_emac.py b/tests/integration/cmor/_fixes/emac/test_emac.py index 86b863b270..5dc2af1d85 100644 --- a/tests/integration/cmor/_fixes/emac/test_emac.py +++ b/tests/integration/cmor/_fixes/emac/test_emac.py @@ -2197,20 +2197,20 @@ def test_ta_amon_fix(cubes_amon_3d): check_lon(fixed_cube) np.testing.assert_allclose( - fixed_cube.data[0, -5:-1, 0, 0], - [250.93347, 258.48843, 266.4087, 270.26993], + fixed_cube.data[0, -5:-2, 0, 0], + [2.512135e+02, 2.585062e+02, 2.662863e+02], rtol=1e-5, ) np.testing.assert_equal( fixed_cube.data.mask[0, -5:, 0, 0], - [False, False, False, False, True], + [False, False, False, True, True], ) # Test each 3D variable with hybrid Z-coord in extra_facets/emac-mappings.yml -def test_get_ta_cfon_fix(): +def test_get_ta_cfmon_fix(): """Test getting of fix.""" fix = Fix.get_fixes('EMAC', 'EMAC', 'CFmon', 'ta') assert fix == [AllVars(None)] From c4dbb4de9341d244d50452483ab2b3d920f29ea4 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Fri, 22 Apr 2022 12:01:22 +0200 Subject: [PATCH 32/52] Added final tests for EMAC CMORizer --- esmvalcore/cmor/_fixes/emac/emac.py | 2 +- .../integration/cmor/_fixes/emac/test_emac.py | 189 +++++++++++++++--- 2 files changed, 167 insertions(+), 24 deletions(-) diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py index 3b43b03ca5..0027fe87b4 100644 --- a/esmvalcore/cmor/_fixes/emac/emac.py +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -220,7 +220,7 @@ def _fix_alevel(cube, cubes): float, casting='same_kind') # Fix values of lev coordinate - # NOte: lev = a + b with a = ap / p0 (p0 = 100000 Pa) + # Note: lev = a + b with a = ap / p0 (p0 = 100000 Pa) lev_coord.points = (ap_coord.core_points() / 100000.0 + b_coord.core_points()) lev_coord.bounds = (ap_coord.core_bounds() / 100000.0 + diff --git a/tests/integration/cmor/_fixes/emac/test_emac.py b/tests/integration/cmor/_fixes/emac/test_emac.py index 5dc2af1d85..707e48b231 100644 --- a/tests/integration/cmor/_fixes/emac/test_emac.py +++ b/tests/integration/cmor/_fixes/emac/test_emac.py @@ -1,5 +1,5 @@ """Tests for the EMAC on-the-fly CMORizer.""" -# from unittest import mock +from unittest import mock import iris import numpy as np @@ -2247,33 +2247,176 @@ def test_ta_cfmon_fix(test_data_path, tmp_path): ) -# # Test variable not available in file +# Test ``AllVars.fix_file`` -# def test_var_not_available_pr(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'pr') -# msg = "Variable 'pr' used to extract 'pr' is not available in input file" -# with pytest.raises(ValueError, match=msg): -# fix.fix_metadata(cubes_2d) +@mock.patch('esmvalcore.cmor._fixes.emac.emac.copyfile', autospec=True) +def test_fix_file_no_alevel(mock_copyfile): + """Test fix.""" + fix = get_allvars_fix('Amon', 'ta') + new_path = fix.fix_file(mock.sentinel.filepath, mock.sentinel.output_dir) + + assert new_path == mock.sentinel.filepath + mock_copyfile.assert_not_called() + + +# Test ``AllVars._fix_plev`` + + +def test_fix_plev_no_plev_coord(cubes_amon_3d): + """Test fix.""" + # Create cube with Z-coord whose units are not convertible to Pa + cube = cubes_amon_3d.extract_cube(NameConstraint(var_name='tm1_p19_ave')) + z_coord = cube.coord(axis='Z') + z_coord.var_name = 'height' + z_coord.standard_name = 'height' + z_coord.long_name = 'height' + z_coord.units = 'm' + z_coord.attributes = {'positive': 'up'} + z_coord.points = np.arange(z_coord.shape[0])[::-1] + + fix = get_allvars_fix('Amon', 'ta') + + msg = ("Cannot find requested pressure level coordinate for variable " + "'ta', searched for Z-coordinates with units that are convertible " + "to Pa") + with pytest.raises(ValueError, match=msg): + fix._fix_plev(cube) + + +# Test fix bounds of coordinates with dimensions of size 1 + + +def test_fix_bounds_time_size_1(): + """Test fix.""" + coord = DimCoord([0], standard_name='time', + units='days since 1850-01-01') + cube = Cube([0], dim_coords_and_dims=[(coord, 0)]) + + fix = get_allvars_fix('Amon', 'tas') + fix._fix_time(cube) + + time_coord = cube.coord('time') + assert time_coord is coord + assert time_coord.var_name == 'time' + assert time_coord.standard_name == 'time' + assert time_coord.long_name == 'time' + assert time_coord.units == 'days since 1850-01-01' + np.testing.assert_equal(time_coord.points, [0]) + assert time_coord.bounds is None + + +def test_fix_bounds_lat_size_1(): + """Test fix.""" + coord = DimCoord([3.14159265], standard_name='latitude', units='rad') + cube = Cube([0], dim_coords_and_dims=[(coord, 0)]) + + fix = get_allvars_fix('Amon', 'tas') + fix._fix_lat(cube) + + lat_coord = cube.coord('latitude') + assert lat_coord is coord + assert lat_coord.var_name == 'lat' + assert lat_coord.standard_name == 'latitude' + assert lat_coord.long_name == 'latitude' + assert lat_coord.units == 'degrees_north' + np.testing.assert_allclose(lat_coord.points, [180.0]) + assert lat_coord.bounds is None + + +def test_fix_bounds_lon_size_1(): + """Test fix.""" + coord = DimCoord([3.14159265], standard_name='longitude', units='rad') + cube = Cube([0], dim_coords_and_dims=[(coord, 0)]) + + fix = get_allvars_fix('Amon', 'tas') + fix._fix_lon(cube) + + lon_coord = cube.coord('longitude') + assert lon_coord is coord + assert lon_coord.var_name == 'lon' + assert lon_coord.standard_name == 'longitude' + assert lon_coord.long_name == 'longitude' + assert lon_coord.units == 'degrees_east' + np.testing.assert_allclose(lon_coord.points, [180.0]) + assert lon_coord.bounds is None + + +# Test fix invalid units + + +def test_fix_invalid_units_predefined(): + """Test fix.""" + cube = Cube(1.0, attributes={'invalid_units': 'kg/m**2s'}) + + fix = get_allvars_fix('Amon', 'pr') + fix._fix_var_metadata(cube) + assert cube.var_name == 'pr' + assert cube.standard_name == 'precipitation_flux' + assert cube.long_name == 'Precipitation' + assert cube.units == 'kg m-2 s-1' + assert cube.units.origin == 'kg m-2 s-1' + assert 'positive' not in cube.attributes + np.testing.assert_allclose(cube.data, 1.0) -# def test_var_not_available_ps(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'ps') -# msg = "Variable 'x' used to extract 'ps' is not available in input file" -# with pytest.raises(ValueError, match=msg): -# fix.get_cube(cubes_2d, var_name='x') +def test_fix_invalid_units_fix_exponent(): + """Test fix.""" + cube = Cube(1.0, attributes={'invalid_units': 'W/m**2'}) -# # Test fix with invalid time units + fix = get_allvars_fix('Amon', 'rlds') + fix._fix_var_metadata(cube) + assert cube.var_name == 'rlds' + assert cube.standard_name == 'surface_downwelling_longwave_flux_in_air' + assert cube.long_name == 'Surface Downwelling Longwave Radiation' + assert cube.units == 'W m-2' + assert cube.units.origin == 'W/m^2' + assert cube.attributes['positive'] == 'down' + np.testing.assert_allclose(cube.data, 1.0) -# def test_invalid_time_units(cubes_2d): -# """Test fix.""" -# fix = get_allvars_fix('Amon', 'tas') -# for cube in cubes_2d: -# cube.coord('time').attributes['invalid_units'] = 'month as %Y%m%d.%f' -# msg = "Expected time units" -# with pytest.raises(ValueError, match=msg): -# fix.fix_metadata(cubes_2d) + +def test_fix_invalid_units_convert(): + """Test fix.""" + cube = Cube(1.0, attributes={'invalid_units': 'kW/m**2'}) + + fix = get_allvars_fix('Amon', 'rlds') + fix._fix_var_metadata(cube) + + assert cube.var_name == 'rlds' + assert cube.standard_name == 'surface_downwelling_longwave_flux_in_air' + assert cube.long_name == 'Surface Downwelling Longwave Radiation' + assert cube.units == 'W m-2' + assert cube.attributes['positive'] == 'down' + np.testing.assert_allclose(cube.data, 1000.0) + + +def test_fix_invalid_units_fail(): + """Test fix.""" + cube = Cube(1.0, attributes={'invalid_units': 'invalid_units'}) + + fix = get_allvars_fix('Amon', 'rlds') + msg = "Failed to fix invalid units 'invalid_units' for variable 'rlds'" + with pytest.raises(ValueError, match=msg): + fix._fix_var_metadata(cube) + + +# Test error message if variable not available in cube + + +def test_var_not_available_ta(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'ta') + msg = ("Variable 'tm1_p19_ave' used to extract 'ta' is not available in " + "input file") + with pytest.raises(ValueError, match=msg): + fix.fix_metadata(cubes_amon_2d) + + +def test_var_not_available_ps(cubes_amon_2d): + """Test fix.""" + fix = get_allvars_fix('Amon', 'ps') + msg = "Variable 'x' used to extract 'ps' is not available in input file" + with pytest.raises(ValueError, match=msg): + fix.get_cube(cubes_amon_2d, var_name='x') From 2e714de2079583fe610a1487bffc305ba175eb03 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 19 May 2022 18:12:54 +0200 Subject: [PATCH 33/52] Ignored UDUNITS-2 warnings --- esmvalcore/preprocessor/_io.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/esmvalcore/preprocessor/_io.py b/esmvalcore/preprocessor/_io.py index 0481103a95..7b0ead0291 100644 --- a/esmvalcore/preprocessor/_io.py +++ b/esmvalcore/preprocessor/_io.py @@ -11,6 +11,7 @@ import iris.exceptions import numpy as np import yaml +from cf_units import suppress_errors from .._task import write_ncl_settings from ._time import extract_time @@ -152,7 +153,11 @@ def load(file, callback=None, ignore_warnings=None): for warning_kwargs in ignore_warnings: warning_kwargs.setdefault('action', 'ignore') filterwarnings(**warning_kwargs) - raw_cubes = iris.load_raw(file, callback=callback) + # Suppress UDUNITS-2 error messages that cannot be ignored with + # warnings.filterwarnings + # (see https://github.com/SciTools/cf-units/issues/240) + with suppress_errors(): + raw_cubes = iris.load_raw(file, callback=callback) logger.debug("Done with loading %s", file) if not raw_cubes: raise ValueError(f'Can not load cubes from {file}') From c91968a21cfcf973acfb393ff752fbdf5d1a4ad6 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 19 May 2022 18:13:09 +0200 Subject: [PATCH 34/52] Allowed the usage of postprocessing flags --- .../_config/extra_facets/emac-mappings.yml | 20 ++++++++++++++++--- esmvalcore/config-developer.yml | 6 +++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml index 252e6214ae..3989a8a010 100644 --- a/esmvalcore/_config/extra_facets/emac-mappings.yml +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -1,10 +1,24 @@ # Extra facets for native EMAC model output # All extra facets for EMAC are optional but might be necessary for some -# variables. Note that if the facet ``channel`` is not given here it has to be -# specified in the recipe. A complete list of supported keys is given in the -# documentation (see ESMValCore/doc/quickstart/find_data.rst). +# variables. + +# Notes: +# - The facets ``channel`` and ``postproc_flag`` have to be specified in the +# recipe if they are not given here. +# - If ``raw_name`` is omitted and no derivation in the EMAC fix is given, the +# CMOR short_name is used by default. To accept multiple raw names for a +# variable, ``raw_name`` can be given as a list. In this case, the +# prioritization is given by the order of the list; if possible, use the +# first entry, if this is not present, use the second, etc. + +# A complete list of supported keys is given in the documentation (see +# ESMValCore/doc/quickstart/find_data.rst). --- +'*': + '*': + '*': + postproc_flag: '' EMAC: '*': diff --git a/esmvalcore/config-developer.yml b/esmvalcore/config-developer.yml index 3143ec98fd..90b2597769 100644 --- a/esmvalcore/config-developer.yml +++ b/esmvalcore/config-developer.yml @@ -123,14 +123,14 @@ EMAC: input_dir: default: '{exp}/{channel}' input_file: - default: '{exp}*{channel}.nc' + default: '{exp}*{channel}{postproc_flag}.nc' output_file: '{dataset}_{exp}_{channel}_{mip}_{short_name}' cmor_type: 'CMIP6' ignore_warnings: load: - - {message: 'Missing CF-netCDF formula term variable .*, referenced by netCDF variable .*', module: iris} - {message: 'Ignored formula of unrecognised type: .*', module: iris} - # - {message: 'WARNING: _FillValue not used since it\ncannot be safely cast to variable data type', module: iris} + - {message: 'Missing CF-netCDF formula term variable .*, referenced by netCDF variable .*', module: iris} + - {message: 'NetCDF variable .* contains unknown cell method .*', module: iris} CORDEX: input_dir: From 4f01f4e2bcd2442b11c3cb952480db6e8ffdb365 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 19 May 2022 18:19:03 +0200 Subject: [PATCH 35/52] Added further variables to EMAC CMORizer --- .../_config/extra_facets/emac-mappings.yml | 163 +++++++++++++++++- esmvalcore/cmor/_fixes/emac/_base_fixes.py | 4 +- esmvalcore/cmor/_fixes/emac/emac.py | 6 + 3 files changed, 162 insertions(+), 11 deletions(-) diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml index 3989a8a010..b26b23106b 100644 --- a/esmvalcore/_config/extra_facets/emac-mappings.yml +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -21,7 +21,15 @@ postproc_flag: '' EMAC: + + # 1D/2D dynamical/meteorological variables '*': + areacella: + raw_name: gboxarea + channel: grid_def + areacello: + raw_name: gboxarea + channel: grid_def awhea: # non-CMOR variable raw_name: awhea_ave channel: Omon @@ -47,6 +55,9 @@ EMAC: hfss: raw_name: ahfs_ave channel: Amon + hurs: + raw_name: rh_2m_ave + channel: Amon od550aer: raw_name: aot_opt_TOT_550_total_ave channel: AERmon @@ -64,6 +75,12 @@ EMAC: prw: raw_name: qvi_ave channel: Amon + ps: + raw_name: aps_ave + channel: Amon + psl: + raw_name: slp_ave + channel: Amon rlds: # derived from flxtbot_ave, tradsu_ave channel: Amon rlns: # ESMValCore-derivation @@ -96,6 +113,9 @@ EMAC: channel: Amon rtmt: # derived from flxttop_ave, flxstop_ave channel: Amon + sfcWind: + raw_name: wind10_ave + channel: Amon siconc: raw_name: seaice_ave channel: Amon @@ -108,6 +128,18 @@ EMAC: tas: raw_name: temp2_ave channel: Amon + tasmax: + raw_name: temp2_max + channel: Amon + tasmin: + raw_name: temp2_min + channel: Amon + tauu: + raw_name: ustr_ave + channel: Amon + tauv: + raw_name: vstr_ave + channel: Amon tos: raw_name: tsw channel: g3b @@ -178,37 +210,150 @@ EMAC: MP_SS_tot: # derived from MP_SS_ks_ave, MP_SS_as_ave, MP_SS_cs_ave channel: tracer_pdef_gp - # 3D variables with regular Z-coords + # 3D dynamical/meteorological variables + 6hrLev: + ta: + raw_name: tm1_ave + channel: Amon + ua: + raw_name: um1_cav + channel: Amon + va: + raw_name: vm1_cav + channel: Amon + AERmon: + ua: + raw_name: um1_cav + channel: Amon + va: + raw_name: vm1_cav + channel: Amon + zg: + raw_name: geopot_ave + channel: Amon Amon: - ta: # defined on plev19 - raw_name: tm1_p19_ave + cl: + raw_name: aclcac_ave + channel: Amon + cli: + raw_name: xim1_ave + channel: Amon + clw: + raw_name: xlm1_ave + channel: Amon + hur: # defined on plev19 + raw_name: rhum_p19_ave + channel: Amon + hus: # defined on plev19 + raw_name: qm1_p19_ave channel: Amon - Eday: ta: # defined on plev19 raw_name: tm1_p19_ave channel: Amon - - # 3D variables with hybrid Z-coords - 6hrLev: - ta: - raw_name: tm1_ave + ua: # defined on plev19 + raw_name: um1_p19_cav + channel: Amon + va: # defined on plev19 + raw_name: vm1_p19_cav + channel: Amon + zg: # defined on plev19 + raw_name: geopot_p19_ave channel: Amon CF3hr: ta: raw_name: tm1_ave channel: Amon CFday: + cl: + raw_name: aclcac_ave + channel: Amon + cli: + raw_name: xim1_ave + channel: Amon + clw: + raw_name: xlm1_ave + channel: Amon + hur: + raw_name: rhum_ave + channel: Amon + hus: + raw_name: qm1_ave + channel: Amon ta: raw_name: tm1_ave channel: Amon + ua: + raw_name: um1_cav + channel: Amon + va: + raw_name: vm1_cav + channel: Amon + zg: + raw_name: geopot_ave + channel: Amon CFmon: + hur: + raw_name: rhum_ave + channel: Amon + hus: + raw_name: qm1_ave + channel: Amon ta: raw_name: tm1_ave channel: Amon + day: + hur: # defined on plev8 + raw_name: rhum_p19_ave + channel: Amon + hus: # defined on plev8 + raw_name: qm1_p19_ave + channel: Amon + ua: # defined on plev8 + raw_name: um1_p19_cav + channel: Amon + va: # defined on plev8 + raw_name: vm1_p19_cav + channel: Amon + zg: # defined on plev8 + raw_name: geopot_p19_ave + channel: Amon + E1hr: + ua: # defined on plev3 + raw_name: um1_p19_cav + channel: Amon + va: # defined on plev3 + raw_name: vm1_p19_cav + channel: Amon + E3hrPt: + hus: + raw_name: qm1_ave + channel: Amon + Eday: + ta: # defined on plev19 + raw_name: tm1_p19_ave + channel: Amon + hus: # defined on plev19 + raw_name: qm1_p19_ave + channel: Amon + ua: # defined on plev19 + raw_name: um1_p19_cav + channel: Amon + va: # defined on plev19 + raw_name: vm1_p19_cav + channel: Amon + zg: # defined on plev19 + raw_name: geopot_p19_ave + channel: Amon Esubhr: ta: raw_name: tm1_ave channel: Amon + ua: + raw_name: um1_cav + channel: Amon + va: + raw_name: vm1_cav + channel: Amon # Elements missing from original mapping table diff --git a/esmvalcore/cmor/_fixes/emac/_base_fixes.py b/esmvalcore/cmor/_fixes/emac/_base_fixes.py index dcb3b2ea0f..8705af2b72 100644 --- a/esmvalcore/cmor/_fixes/emac/_base_fixes.py +++ b/esmvalcore/cmor/_fixes/emac/_base_fixes.py @@ -43,7 +43,7 @@ def fix_metadata(self, cubes): """Fix metadata.""" cube = self.get_cube(cubes) cube.units = '1' - return CubeList([cube]) + return cubes class SetUnitsTo1SumOverZ(EmacFix): @@ -54,7 +54,7 @@ def fix_metadata(self, cubes): cube = self.get_cube(cubes) cube.units = '1' cube = self.sum_over_z_coord(cube) - return CubeList([cube]) + return cubes @staticmethod def sum_over_z_coord(cube): diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py index 0027fe87b4..9ecf34e7a1 100644 --- a/esmvalcore/cmor/_fixes/emac/emac.py +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -311,6 +311,9 @@ def _fix_var_metadata(self, cube): cube.attributes['positive'] = self.vardef.positive +Cl = SetUnitsTo1 + + Clt = SetUnitsTo1 @@ -336,6 +339,9 @@ def fix_metadata(self, cubes): Hfss = NegateData +Hurs = SetUnitsTo1 + + Od550aer = SetUnitsTo1SumOverZ From f1cec514c85e50d1156b189b1b03ec0103f0326a Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 19 May 2022 18:21:20 +0200 Subject: [PATCH 36/52] Fixed variable raw_names for EMAC CMORizer --- .../_config/extra_facets/emac-mappings.yml | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml index b26b23106b..91afb812b7 100644 --- a/esmvalcore/_config/extra_facets/emac-mappings.yml +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -216,17 +216,17 @@ EMAC: raw_name: tm1_ave channel: Amon ua: - raw_name: um1_cav + raw_name: um1_ave channel: Amon va: - raw_name: vm1_cav + raw_name: vm1_ave channel: Amon AERmon: ua: - raw_name: um1_cav + raw_name: um1_ave channel: Amon va: - raw_name: vm1_cav + raw_name: vm1_ave channel: Amon zg: raw_name: geopot_ave @@ -251,10 +251,10 @@ EMAC: raw_name: tm1_p19_ave channel: Amon ua: # defined on plev19 - raw_name: um1_p19_cav + raw_name: um1_p19_ave channel: Amon va: # defined on plev19 - raw_name: vm1_p19_cav + raw_name: vm1_p19_ave channel: Amon zg: # defined on plev19 raw_name: geopot_p19_ave @@ -283,10 +283,10 @@ EMAC: raw_name: tm1_ave channel: Amon ua: - raw_name: um1_cav + raw_name: um1_ave channel: Amon va: - raw_name: vm1_cav + raw_name: vm1_ave channel: Amon zg: raw_name: geopot_ave @@ -309,20 +309,20 @@ EMAC: raw_name: qm1_p19_ave channel: Amon ua: # defined on plev8 - raw_name: um1_p19_cav + raw_name: um1_p19_ave channel: Amon va: # defined on plev8 - raw_name: vm1_p19_cav + raw_name: vm1_p19_ave channel: Amon zg: # defined on plev8 raw_name: geopot_p19_ave channel: Amon E1hr: ua: # defined on plev3 - raw_name: um1_p19_cav + raw_name: um1_p19_ave channel: Amon va: # defined on plev3 - raw_name: vm1_p19_cav + raw_name: vm1_p19_ave channel: Amon E3hrPt: hus: @@ -336,10 +336,10 @@ EMAC: raw_name: qm1_p19_ave channel: Amon ua: # defined on plev19 - raw_name: um1_p19_cav + raw_name: um1_p19_ave channel: Amon va: # defined on plev19 - raw_name: vm1_p19_cav + raw_name: vm1_p19_ave channel: Amon zg: # defined on plev19 raw_name: geopot_p19_ave @@ -349,10 +349,10 @@ EMAC: raw_name: tm1_ave channel: Amon ua: - raw_name: um1_cav + raw_name: um1_ave channel: Amon va: - raw_name: vm1_cav + raw_name: vm1_ave channel: Amon From f94ded95ec163af2e5c4feaeb9ad5ab6d189e62f Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Thu, 19 May 2022 18:33:31 +0200 Subject: [PATCH 37/52] Ignored further warning message for EMAC --- esmvalcore/config-developer.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/esmvalcore/config-developer.yml b/esmvalcore/config-developer.yml index 90b2597769..d3fd46013a 100644 --- a/esmvalcore/config-developer.yml +++ b/esmvalcore/config-developer.yml @@ -129,6 +129,7 @@ EMAC: ignore_warnings: load: - {message: 'Ignored formula of unrecognised type: .*', module: iris} + - {message: 'Ignoring formula terms variable .* referenced by data variable .* via variable .*', module: iris} - {message: 'Missing CF-netCDF formula term variable .*, referenced by netCDF variable .*', module: iris} - {message: 'NetCDF variable .* contains unknown cell method .*', module: iris} From 8b243e755639fa3e682d9230db392b1cc3f15800 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Fri, 20 May 2022 16:10:43 +0200 Subject: [PATCH 38/52] Added further variables (and corresponding tests) to EMAC CMORizer --- .../_config/extra_facets/emac-mappings.yml | 279 ++- esmvalcore/cmor/_fixes/emac/_base_fixes.py | 54 +- esmvalcore/cmor/_fixes/emac/emac.py | 91 +- .../integration/cmor/_fixes/emac/test_emac.py | 1745 +++++++++++------ .../cmor/_fixes/test_data/emac_aermon.nc | Bin 52948 -> 0 bytes .../cmor/_fixes/test_data/emac_amon_2d.nc | Bin 29828 -> 0 bytes .../cmor/_fixes/test_data/emac_amon_3d.nc | Bin 65324 -> 0 bytes .../cmor/_fixes/test_data/emac_column.nc | Bin 49787 -> 0 bytes .../cmor/_fixes/test_data/emac_g3b.nc | Bin 8064 -> 0 bytes .../cmor/_fixes/test_data/emac_omon_2d.nc | Bin 12140 -> 0 bytes .../_fixes/test_data/emac_tracer_pdef_gp.nc | Bin 40936 -> 0 bytes 11 files changed, 1291 insertions(+), 878 deletions(-) delete mode 100644 tests/integration/cmor/_fixes/test_data/emac_aermon.nc delete mode 100644 tests/integration/cmor/_fixes/test_data/emac_amon_2d.nc delete mode 100644 tests/integration/cmor/_fixes/test_data/emac_amon_3d.nc delete mode 100644 tests/integration/cmor/_fixes/test_data/emac_column.nc delete mode 100644 tests/integration/cmor/_fixes/test_data/emac_g3b.nc delete mode 100644 tests/integration/cmor/_fixes/test_data/emac_omon_2d.nc delete mode 100644 tests/integration/cmor/_fixes/test_data/emac_tracer_pdef_gp.nc diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml index 91afb812b7..55582673d9 100644 --- a/esmvalcore/_config/extra_facets/emac-mappings.yml +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -4,13 +4,24 @@ # variables. # Notes: +# - All facets can also be specified in the recipes. The values given here are +# only defaults. # - The facets ``channel`` and ``postproc_flag`` have to be specified in the # recipe if they are not given here. # - If ``raw_name`` is omitted and no derivation in the EMAC fix is given, the -# CMOR short_name is used by default. To accept multiple raw names for a -# variable, ``raw_name`` can be given as a list. In this case, the -# prioritization is given by the order of the list; if possible, use the -# first entry, if this is not present, use the second, etc. +# CMOR short_name is used by default. To accept single and multiple raw names +# for a variable, ``raw_name`` can be given as str and list. In the latter +# case, the prioritization is given by the order of the list; if possible, +# use the first entry, if this is not present, use the second, etc. This is +# particularly useful for variables where regular averages ("*_ave") or +# conditional averages ("*_cav") exist. For 3D variables defined on pressure +# levels, only the pressure levels defined by the CMOR table (e.g., for +# Amon's ta: "tm1_p19_cav" and "tm1_p19_ave") are given. If other pressure +# levels are desired, e.g., "tm1_p39_cav", this has to be explicitly +# specified in the recipe using "raw_name: tm1_p39_cav" or "raw_name: +# [tm1_p19_cav, tm1_p39_cav]". +# - Asterisks ("*") in the comments in list below refer to either "cav" or +# "ave". "cav" is prioritized. # A complete list of supported keys is given in the documentation (see # ESMValCore/doc/quickstart/find_data.rst). @@ -24,109 +35,103 @@ EMAC: # 1D/2D dynamical/meteorological variables '*': - areacella: - raw_name: gboxarea - channel: grid_def - areacello: - raw_name: gboxarea - channel: grid_def awhea: # non-CMOR variable - raw_name: awhea_ave + raw_name: [awhea_cav, awhea_ave] channel: Omon clivi: - raw_name: xivi_ave + raw_name: [xivi_cav, xivi_ave] channel: Amon clt: - raw_name: aclcov_ave + raw_name: [aclcov_cav, aclcov_ave] channel: Amon - clwvi: # derived from xlvi_ave, xivi_ave + clwvi: # derived from xlvi_*, xivi_* channel: Amon co2mass: - raw_name: MP_CO2_ave + raw_name: [MP_CO2_cav, MP_CO2_ave] channel: tracer_pdef_gp evspsbl: - raw_name: evap_ave + raw_name: [evap_cav, evap_ave] channel: Amon hfls: - raw_name: ahfl_ave + raw_name: [ahfl_cav, ahfl_ave] channel: Amon hfns: # ESMValCore-derivation channel: Amon hfss: - raw_name: ahfs_ave + raw_name: [ahfs_cav, ahfs_ave] channel: Amon hurs: - raw_name: rh_2m_ave + raw_name: [rh_2m_cav, rh_2m_ave] channel: Amon od550aer: - raw_name: aot_opt_TOT_550_total_ave + raw_name: [aot_opt_TOT_550_total_cav, aot_opt_TOT_550_total_ave] channel: AERmon - pr: # derived from aprl_ave, aprc_ave, aprs_ave + pr: # derived from aprl_*, aprc_*, aprs_* channel: Amon prc: - raw_name: aprc_ave + raw_name: [aprc_cav, aprc_ave] channel: Amon prl: # non-CMOR variable - raw_name: aprl_ave + raw_name: [aprl_cav, aprl_ave] channel: Amon prsn: - raw_name: aprs_ave + raw_name: [aprs_cav, aprs_ave] channel: Amon prw: - raw_name: qvi_ave + raw_name: [qvi_cav, qvi_ave] channel: Amon ps: - raw_name: aps_ave + raw_name: [aps_cav, aps_ave] channel: Amon psl: - raw_name: slp_ave + raw_name: [slp_cav, slp_ave] channel: Amon - rlds: # derived from flxtbot_ave, tradsu_ave + rlds: # derived from flxtbot_*, tradsu_* channel: Amon rlns: # ESMValCore-derivation channel: Amon rlus: - raw_name: tradsu_ave + raw_name: [tradsu_cav, tradsu_ave] channel: Amon rlut: - raw_name: flxttop_ave + raw_name: [flxttop_cav, flxttop_ave] channel: Amon rlutcs: - raw_name: flxtftop_ave + raw_name: [flxtftop_cav, flxtftop_ave] channel: Amon - rsds: # derived from flxsbot_ave, sradsu_ave + rsds: # derived from flxsbot_*, sradsu_* channel: Amon - rsdt: # derived from flxstop_ave, srad0u_ave + rsdt: # derived from flxstop_*, srad0u_* channel: Amon rsns: # ESMValCore-derivation channel: Amon rsnt: # ESMValCore-derivation channel: Amon rsus: - raw_name: sradsu_ave + raw_name: [sradsu_cav, sradsu_ave] channel: Amon rsut: - raw_name: srad0u_ave + raw_name: [srad0u_cav, srad0u_ave] channel: Amon rsutcs: - raw_name: flxusftop_ave + raw_name: [flxusftop_cav, flxusftop_ave] channel: Amon - rtmt: # derived from flxttop_ave, flxstop_ave + rtmt: # derived from flxttop_*, flxstop_* channel: Amon sfcWind: - raw_name: wind10_ave + raw_name: [wind10_cav, wind10_ave] channel: Amon siconc: - raw_name: seaice_ave + raw_name: [seaice_cav, seaice_ave] channel: Amon siconca: - raw_name: seaice_ave + raw_name: [seaice_cav, seaice_ave] channel: Amon sithick: - raw_name: siced_ave + raw_name: [siced_cav, siced_ave] channel: Amon tas: - raw_name: temp2_ave + raw_name: [temp2_cav, temp2_ave] channel: Amon tasmax: raw_name: temp2_max @@ -135,10 +140,10 @@ EMAC: raw_name: temp2_min channel: Amon tauu: - raw_name: ustr_ave + raw_name: [ustr_cav, ustr_ave] channel: Amon tauv: - raw_name: vstr_ave + raw_name: [vstr_cav, vstr_ave] channel: Amon tos: raw_name: tsw @@ -146,267 +151,211 @@ EMAC: toz: channel: column ts: - raw_name: tsurf_ave + raw_name: [tsurf_cav, tsurf_ave] channel: Amon # TODO: CHECK IF THE CALCULATION IS CORRECT!!! # -> it might be necessary to weight these fields with cos(lat) uas: - raw_name: u10_ave + raw_name: [u10_cav, u10_ave] channel: Amon vas: - raw_name: v10_ave + raw_name: [v10_cav, v10_ave] channel: Amon # Tracers (non-CMOR variables) - MP_BC_tot: # derived from MP_BC_ks_ave, MP_BC_as_ave, MP_BC_cs_ave, MP_BC_ki_ave + MP_BC_tot: # derived from MP_BC_ks_*, MP_BC_as_*, MP_BC_cs_*, MP_BC_ki_* channel: tracer_pdef_gp MP_CFCl3: - raw_name: MP_CFCl3_ave + raw_name: [MP_CFCl3_cav, MP_CFCl3_ave] channel: tracer_pdef_gp MP_ClOX: - raw_name: MP_ClOX_ave + raw_name: [MP_ClOX_cav, MP_ClOX_ave] channel: tracer_pdef_gp MP_CH4: - raw_name: MP_CH4_ave + raw_name: [MP_CH4_cav, MP_CH4_ave] channel: tracer_pdef_gp MP_CO: - raw_name: MP_CO_ave + raw_name: [MP_CO_cav, MP_CO_ave] channel: tracer_pdef_gp MP_CO2: - raw_name: MP_CO2_ave + raw_name: [MP_CO2_cav, MP_CO2_ave] channel: tracer_pdef_gp - MP_DU_tot: # derived from MP_DU_as_ave, MP_DU_cs_ave, MP_DU_ai_ave, MP_DU_ci_ave + MP_DU_tot: # derived from MP_DU_as_*, MP_DU_cs_*, MP_DU_ai_*, MP_DU_ci_* channel: tracer_pdef_gp MP_N2O: - raw_name: MP_N2O_ave + raw_name: [MP_N2O_cav, MP_N2O_ave] channel: tracer_pdef_gp MP_NH3: - raw_name: MP_NH3_ave + raw_name: [MP_NH3_cav, MP_NH3_ave] channel: tracer_pdef_gp MP_NO: - raw_name: MP_NO_ave + raw_name: [MP_NO_cav, MP_NO_ave] channel: tracer_pdef_gp MP_NO2: - raw_name: MP_NO2_ave + raw_name: [MP_NO2_cav, MP_NO2_ave] channel: tracer_pdef_gp MP_NOX: - raw_name: MP_NOX_ave + raw_name: [MP_NOX_cav, MP_NOX_ave] channel: tracer_pdef_gp MP_O3: - raw_name: MP_O3_ave + raw_name: [MP_O3_cav, MP_O3_ave] channel: tracer_pdef_gp MP_OH: - raw_name: MP_OH_ave + raw_name: [MP_OH_cav, MP_OH_ave] channel: tracer_pdef_gp MP_S: - raw_name: MP_S_ave + raw_name: [MP_S_cav, MP_S_ave] channel: tracer_pdef_gp MP_SO2: - raw_name: MP_SO2_ave + raw_name: [MP_SO2_cav, MP_SO2_ave] channel: tracer_pdef_gp - MP_SO4mm_tot: # derived from MP_SO4mm_ns_ave, MP_SO4mm_ks_ave, MP_SO4mm_as_ave, MP_SO4mm_cs_ave + MP_SO4mm_tot: # derived from MP_SO4mm_ns_*, MP_SO4mm_ks_*, MP_SO4mm_as_*, MP_SO4mm_cs_* channel: tracer_pdef_gp - MP_SS_tot: # derived from MP_SS_ks_ave, MP_SS_as_ave, MP_SS_cs_ave + MP_SS_tot: # derived from MP_SS_ks_*, MP_SS_as_*, MP_SS_cs_* channel: tracer_pdef_gp # 3D dynamical/meteorological variables 6hrLev: ta: - raw_name: tm1_ave + raw_name: [tm1_cav, tm1_ave] channel: Amon ua: - raw_name: um1_ave + raw_name: [um1_cav, um1_ave] channel: Amon va: - raw_name: vm1_ave + raw_name: [vm1_cav, vm1_ave] channel: Amon AERmon: ua: - raw_name: um1_ave + raw_name: [um1_cav, um1_ave] channel: Amon va: - raw_name: vm1_ave + raw_name: [vm1_cav, vm1_ave] channel: Amon zg: - raw_name: geopot_ave + raw_name: [geopot_cav, geopot_ave] channel: Amon Amon: cl: - raw_name: aclcac_ave + raw_name: [aclcac_cav, aclcac_ave] channel: Amon cli: - raw_name: xim1_ave + raw_name: [xim1_cav, xim1_ave] channel: Amon clw: - raw_name: xlm1_ave + raw_name: [xlm1_cav, xlm1_ave] channel: Amon hur: # defined on plev19 - raw_name: rhum_p19_ave + raw_name: [rhum_p19_cav, rhum_p19_ave] channel: Amon hus: # defined on plev19 - raw_name: qm1_p19_ave + raw_name: [qm1_p19_cav, qm1_p19_ave] channel: Amon ta: # defined on plev19 - raw_name: tm1_p19_ave + raw_name: [tm1_p19_cav, tm1_p19_ave] channel: Amon ua: # defined on plev19 - raw_name: um1_p19_ave + raw_name: [um1_p19_cav, um1_p19_ave] channel: Amon va: # defined on plev19 - raw_name: vm1_p19_ave + raw_name: [vm1_p19_cav, vm1_p19_ave] channel: Amon zg: # defined on plev19 - raw_name: geopot_p19_ave + raw_name: [geopot_p19_cav, geopot_p19_ave] channel: Amon CF3hr: ta: - raw_name: tm1_ave + raw_name: [tm1_cav, tm1_ave] channel: Amon CFday: cl: - raw_name: aclcac_ave + raw_name: [aclcac_cav, aclcac_ave] channel: Amon cli: - raw_name: xim1_ave + raw_name: [xim1_cav, xim1_ave] channel: Amon clw: - raw_name: xlm1_ave + raw_name: [xlm1_cav, xlm1_ave] channel: Amon hur: - raw_name: rhum_ave + raw_name: [rhum_cav, rhum_ave] channel: Amon hus: - raw_name: qm1_ave + raw_name: [qm1_cav, qm1_ave] channel: Amon ta: - raw_name: tm1_ave + raw_name: [tm1_cav, tm1_ave] channel: Amon ua: - raw_name: um1_ave + raw_name: [um1_cav, um1_ave] channel: Amon va: - raw_name: vm1_ave + raw_name: [vm1_cav, vm1_ave] channel: Amon zg: - raw_name: geopot_ave + raw_name: [geopot_cav, geopot_ave] channel: Amon CFmon: hur: - raw_name: rhum_ave + raw_name: [rhum_cav, rhum_ave] channel: Amon hus: - raw_name: qm1_ave + raw_name: [qm1_cav, qm1_ave] channel: Amon ta: - raw_name: tm1_ave + raw_name: [tm1_cav, tm1_ave] channel: Amon day: hur: # defined on plev8 - raw_name: rhum_p19_ave + raw_name: [rhum_p8_cav, rhum_p8_ave] channel: Amon hus: # defined on plev8 - raw_name: qm1_p19_ave + raw_name: [qm1_p8_cav, qm1_p8_ave] channel: Amon ua: # defined on plev8 - raw_name: um1_p19_ave + raw_name: [um1_p8_cav, um1_p8_ave] channel: Amon va: # defined on plev8 - raw_name: vm1_p19_ave + raw_name: [vm1_p8_cav, vm1_p8_ave] channel: Amon zg: # defined on plev8 - raw_name: geopot_p19_ave + raw_name: [geopot_p8_cav, geopot_p8_ave] channel: Amon E1hr: ua: # defined on plev3 - raw_name: um1_p19_ave + raw_name: [um1_p3_cav, um1_p3_ave] channel: Amon va: # defined on plev3 - raw_name: vm1_p19_ave + raw_name: [vm1_p3_cav, vm1_p3_ave] channel: Amon E3hrPt: hus: - raw_name: qm1_ave + raw_name: [qm1_cav, qm1_ave] channel: Amon Eday: ta: # defined on plev19 - raw_name: tm1_p19_ave + raw_name: [tm1_p19_cav, tm1_p19_ave] channel: Amon hus: # defined on plev19 - raw_name: qm1_p19_ave + raw_name: [qm1_p19_cav, qm1_p19_ave] channel: Amon ua: # defined on plev19 - raw_name: um1_p19_ave + raw_name: [um1_p19_cav, um1_p19_ave] channel: Amon va: # defined on plev19 - raw_name: vm1_p19_ave + raw_name: [vm1_p19_cav, vm1_p19_ave] channel: Amon zg: # defined on plev19 - raw_name: geopot_p19_ave + raw_name: [geopot_p19_cav, geopot_p19_ave] channel: Amon Esubhr: ta: - raw_name: tm1_ave + raw_name: [tm1_cav, tm1_ave] channel: Amon ua: - raw_name: um1_ave + raw_name: [um1_cav, um1_ave] channel: Amon va: - raw_name: vm1_ave + raw_name: [vm1_cav, vm1_ave] channel: Amon - - -# Elements missing from original mapping table -# -# Mixed channels (not supported yet; this probably needs to be implemented as -# derivation) -# AIRC_NO_s: {airc_NO: import_grid, geopot_ave: Amon} -# VOLC_SO2_s: {VOLC_SO2_SO2: import_grid, geopot_ave: Amon} -# -# No test data available -# cod_sw_b01: -# raw_name: tau_cld_sw_B01_ave -# channel: Amon -# lnox: # non-CMOR variable; derived from NOxcg_ave, NOxic_ave -# channel: lnox_PaR_T_gp -# ANTHNT_AER_TC_s: # derived from ANTHNT_AER_BC, ANTHNT_AER_OC -# channel: import_grid -# ANTHNT_CO_s: -# raw_name: ANTHNT_CO -# channel: import_grid -# ANTHNT_NO_s: -# raw_name: ANTHNT_NO -# channel: import_grid -# ANTHNT_SO2_s: -# raw_name: ANTHNT_SO2 -# channel: import_grid -# BB_AER_TC_s: # derived from BB_AER_BC, BB_AER_OC -# channel: import_grid -# BB_CO_s: -# raw_name: BB_CO -# channel: import_grid -# BB_NO_s: -# raw_name: BB_NO -# channel: import_grid -# BB_SO2_s: -# raw_name: BB_SO2 -# channel: import_grid -# ROAD_AER_BC: -# channel: import_grid -# ROAD_NO: -# channel: import_grid -# SHIP_AER_BC_s: -# raw_name: SHIP_AER_BC -# channel: import_grid -# SHIP_NO_s: -# raw_name: SHIP_NO -# channel: import_grid -# SHIP_SO2_s: -# raw_name: SHIP_SO2 -# channel: import_grid -# TN_GHG_CH4: -# channel: import_grid -# TN_GHG_CO2: -# channel: import_grid -# TN_GHG_N2O: -# channel: import_grid diff --git a/esmvalcore/cmor/_fixes/emac/_base_fixes.py b/esmvalcore/cmor/_fixes/emac/_base_fixes.py index 8705af2b72..d6639d2668 100644 --- a/esmvalcore/cmor/_fixes/emac/_base_fixes.py +++ b/esmvalcore/cmor/_fixes/emac/_base_fixes.py @@ -4,7 +4,7 @@ import iris.analysis from iris import NameConstraint -from iris.cube import CubeList +from iris.exceptions import ConstraintMismatchError from ..fix import Fix @@ -14,17 +14,35 @@ class EmacFix(Fix): """Base class for all EMAC fixes.""" - def get_cube(self, cubes, var_name=None): + def get_cube(self, cubes, var_names=None): """Extract single cube.""" - if var_name is None: - var_name = self.extra_facets.get('raw_name', - self.vardef.short_name) - if not cubes.extract(NameConstraint(var_name=var_name)): - raise ValueError( - f"Variable '{var_name}' used to extract " - f"'{self.vardef.short_name}' is not available in input " - f"file") - return cubes.extract_cube(NameConstraint(var_name=var_name)) + # If no var_names given, use the CMOR short_name + if var_names is None: + var_names = self.extra_facets.get('raw_name', + self.vardef.short_name) + + # Convert var_names to list if only a single var_name is given + if isinstance(var_names, str): + var_names = [var_names] + + # Try to extract the variable (prioritize variables as given by the + # list) + for var_name in var_names: + try: + return cubes.extract_cube(NameConstraint(var_name=var_name)) + except ConstraintMismatchError: + pass + + # If no cube could be extracted, raise an error + raise ValueError( + f"No variable of {var_names} necessary for the extraction/" + f"derivation the CMOR variable '{self.vardef.short_name}' is " + f"available in the input file. Hint: in case you tried to extract " + f"a 3D variable defined on pressure levels, it might be necessary " + f"to define the EMAC variable name in the recipe (e.g., " + f"'raw_name: tm1_p39_cav') if the default number of pressure " + f"levels is not available in the input file." + ) class NegateData(EmacFix): @@ -46,19 +64,11 @@ def fix_metadata(self, cubes): return cubes -class SetUnitsTo1SumOverZ(EmacFix): +class SetUnitsTo1SumOverZ(SetUnitsTo1): """Base fix to set units to '1' and sum over Z-coordinate.""" - def fix_metadata(self, cubes): - """Fix metadata.""" - cube = self.get_cube(cubes) - cube.units = '1' - cube = self.sum_over_z_coord(cube) - return cubes - - @staticmethod - def sum_over_z_coord(cube): - """Perform sum over Z-coordinate.""" + def fix_data(self, cube): + """Fix data.""" z_coord = cube.coord(axis='Z') cube = cube.collapsed(z_coord, iris.analysis.SUM) return cube diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py index 9ecf34e7a1..495526472b 100644 --- a/esmvalcore/cmor/_fixes/emac/emac.py +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -21,6 +21,7 @@ from iris.aux_factory import HybridPressureFactory from iris.cube import CubeList from netCDF4 import Dataset +from scipy import constants from ..shared import ( add_aux_coords_from_cubes, @@ -64,16 +65,6 @@ def fix_file(self, filepath, output_dir): del dataset.variables['ilev'].formula_terms return new_path - def fix_data(self, cube): - """Fix data.""" - # Fix mask by masking all values where the absolute value is greater - # than a given threshold (affects mostly 3D variables) - mask_threshold = 1e20 - cube.data = da.ma.masked_outside( - cube.core_data(), -mask_threshold, mask_threshold, - ) - return cube - def fix_metadata(self, cubes): """Fix metadata.""" cube = self.get_cube(cubes) @@ -323,8 +314,8 @@ class Clwvi(EmacFix): def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_cube(NameConstraint(var_name='xlvi_ave')) + - cubes.extract_cube(NameConstraint(var_name='xivi_ave')) + self.get_cube(cubes, var_names=['xlvi_cav', 'xlvi_ave']) + + self.get_cube(cubes, var_names=['xivi_cav', 'xivi_ave']) ) cube.var_name = self.vardef.short_name return CubeList([cube]) @@ -351,9 +342,9 @@ class Pr(EmacFix): def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_cube(NameConstraint(var_name='aprl_ave')) + - cubes.extract_cube(NameConstraint(var_name='aprc_ave')) + - cubes.extract_cube(NameConstraint(var_name='aprs_ave')) + self.get_cube(cubes, var_names=['aprl_cav', 'aprl_ave']) + + self.get_cube(cubes, var_names=['aprc_cav', 'aprc_ave']) + + self.get_cube(cubes, var_names=['aprs_cav', 'aprs_ave']) ) cube.var_name = self.vardef.short_name return CubeList([cube]) @@ -365,8 +356,8 @@ class Rlds(EmacFix): def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_cube(NameConstraint(var_name='flxtbot_ave')) - - cubes.extract_cube(NameConstraint(var_name='tradsu_ave')) + self.get_cube(cubes, var_names=['flxtbot_cav', 'flxtbot_ave']) - + self.get_cube(cubes, var_names=['tradsu_cav', 'tradsu_ave']) ) cube.var_name = self.vardef.short_name return CubeList([cube]) @@ -387,8 +378,8 @@ class Rsds(EmacFix): def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_cube(NameConstraint(var_name='flxsbot_ave')) - - cubes.extract_cube(NameConstraint(var_name='sradsu_ave')) + self.get_cube(cubes, var_names=['flxsbot_cav', 'flxsbot_ave']) - + self.get_cube(cubes, var_names=['sradsu_cav', 'sradsu_ave']) ) cube.var_name = self.vardef.short_name return CubeList([cube]) @@ -400,8 +391,8 @@ class Rsdt(EmacFix): def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_cube(NameConstraint(var_name='flxstop_ave')) - - cubes.extract_cube(NameConstraint(var_name='srad0u_ave')) + self.get_cube(cubes, var_names=['flxstop_cav', 'flxstop_ave']) - + self.get_cube(cubes, var_names=['srad0u_cav', 'srad0u_ave']) ) cube.var_name = self.vardef.short_name return CubeList([cube]) @@ -422,8 +413,8 @@ class Rtmt(EmacFix): def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_cube(NameConstraint(var_name='flxttop_ave')) + - cubes.extract_cube(NameConstraint(var_name='flxstop_ave')) + self.get_cube(cubes, var_names=['flxttop_cav', 'flxttop_ave']) + + self.get_cube(cubes, var_names=['flxstop_cav', 'flxstop_ave']) ) cube.var_name = self.vardef.short_name return CubeList([cube]) @@ -457,6 +448,26 @@ def fix_metadata(self, cubes): return CubeList([cube]) +class Zg(EmacFix): + """Fixes for ``zg``.""" + + def fix_metadata(self, cubes): + """Fix metadata. + + Convert geopotential Phi given by EMAC to geopotential height Z using + Z = Phi / g0 (g0 is standard acceleration of gravity) + + """ + g0_value = constants.value('standard acceleration of gravity') + g0_units = constants.unit('standard acceleration of gravity') + + cube = self.get_cube(cubes) + cube.data = cube.core_data() / g0_value + cube.units /= g0_units + + return cubes + + # Tracers @@ -466,10 +477,10 @@ class MP_BC_tot(EmacFix): # noqa: N801 def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_cube(NameConstraint(var_name='MP_BC_ki_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_BC_ks_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_BC_as_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_BC_cs_ave')) + self.get_cube(cubes, var_names=['MP_BC_ki_cav', 'MP_BC_ki_ave']) + + self.get_cube(cubes, var_names=['MP_BC_ks_cav', 'MP_BC_ks_ave']) + + self.get_cube(cubes, var_names=['MP_BC_as_cav', 'MP_BC_as_ave']) + + self.get_cube(cubes, var_names=['MP_BC_cs_cav', 'MP_BC_cs_ave']) ) cube.var_name = self.vardef.short_name return CubeList([cube]) @@ -481,10 +492,10 @@ class MP_DU_tot(EmacFix): # noqa: N801 def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_cube(NameConstraint(var_name='MP_DU_ai_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_DU_as_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_DU_ci_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_DU_cs_ave')) + self.get_cube(cubes, var_names=['MP_DU_ai_cav', 'MP_DU_ai_ave']) + + self.get_cube(cubes, var_names=['MP_DU_as_cav', 'MP_DU_as_ave']) + + self.get_cube(cubes, var_names=['MP_DU_ci_cav', 'MP_DU_ci_ave']) + + self.get_cube(cubes, var_names=['MP_DU_cs_cav', 'MP_DU_cs_ave']) ) cube.var_name = self.vardef.short_name return CubeList([cube]) @@ -496,10 +507,14 @@ class MP_SO4mm_tot(EmacFix): # noqa: N801 def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_ns_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_ks_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_as_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_SO4mm_cs_ave')) + self.get_cube( + cubes, var_names=['MP_SO4mm_ns_cav', 'MP_SO4mm_ns_ave']) + + self.get_cube( + cubes, var_names=['MP_SO4mm_ks_cav', 'MP_SO4mm_ks_ave']) + + self.get_cube( + cubes, var_names=['MP_SO4mm_as_cav', 'MP_SO4mm_as_ave']) + + self.get_cube( + cubes, var_names=['MP_SO4mm_cs_cav', 'MP_SO4mm_cs_ave']) ) cube.var_name = self.vardef.short_name return CubeList([cube]) @@ -511,9 +526,9 @@ class MP_SS_tot(EmacFix): # noqa: N801 def fix_metadata(self, cubes): """Fix metadata.""" cube = ( - cubes.extract_cube(NameConstraint(var_name='MP_SS_ks_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_SS_as_ave')) + - cubes.extract_cube(NameConstraint(var_name='MP_SS_cs_ave')) + self.get_cube(cubes, var_names=['MP_SS_ks_cav', 'MP_SS_ks_ave']) + + self.get_cube(cubes, var_names=['MP_SS_as_cav', 'MP_SS_as_ave']) + + self.get_cube(cubes, var_names=['MP_SS_cs_cav', 'MP_SS_cs_ave']) ) cube.var_name = self.vardef.short_name return CubeList([cube]) diff --git a/tests/integration/cmor/_fixes/emac/test_emac.py b/tests/integration/cmor/_fixes/emac/test_emac.py index 707e48b231..baf77ef27b 100644 --- a/tests/integration/cmor/_fixes/emac/test_emac.py +++ b/tests/integration/cmor/_fixes/emac/test_emac.py @@ -6,17 +6,19 @@ import pytest from cf_units import Unit from iris import NameConstraint -from iris.coords import DimCoord +from iris.coords import AuxCoord, DimCoord from iris.cube import Cube, CubeList from esmvalcore._config import get_extra_facets from esmvalcore.cmor._fixes.emac.emac import ( AllVars, + Cl, Clt, Clwvi, Evspsbl, Hfls, Hfss, + Hurs, MP_BC_tot, MP_DU_tot, MP_SO4mm_tot, @@ -37,60 +39,147 @@ Siconca, Sithick, Toz, + Zg, ) from esmvalcore.cmor.fix import Fix from esmvalcore.cmor.table import get_var_info -# Note: test_data_path is defined in tests/integration/cmor/_fixes/conftest.py - - -@pytest.fixture -def cubes_aermon(test_data_path): - """AERmon sample cubes.""" - nc_path = test_data_path / 'emac_aermon.nc' - return iris.load(str(nc_path)) - - -@pytest.fixture -def cubes_amon_2d(test_data_path): - """Amon 2D sample cubes.""" - nc_path = test_data_path / 'emac_amon_2d.nc' - return iris.load(str(nc_path)) - @pytest.fixture -def cubes_amon_3d(test_data_path): - """Amon 3D sample cubes.""" - nc_path = test_data_path / 'emac_amon_3d.nc' - return iris.load(str(nc_path)) - - -@pytest.fixture -def cubes_column(test_data_path): - """column sample cubes.""" - nc_path = test_data_path / 'emac_column.nc' - return iris.load(str(nc_path)) - - -@pytest.fixture -def cubes_g3b(test_data_path): - """g3b sample cubes.""" - nc_path = test_data_path / 'emac_g3b.nc' - return iris.load(str(nc_path)) +def cubes_1d(): + """1D cube.""" + time_coord = DimCoord( + 0.0, + var_name='time', + long_name='time', + units=Unit('day since 1950-01-01 00:00:00', calendar='gregorian'), + ) + cube = Cube([1.0], dim_coords_and_dims=[(time_coord, 0)]) + cubes = CubeList([ + cube.copy(), + cube.copy(), + cube.copy(), + cube.copy(), + ]) + return cubes @pytest.fixture -def cubes_omon_2d(test_data_path): - """Omon 2D sample cubes.""" - nc_path = test_data_path / 'emac_omon_2d.nc' - return iris.load(str(nc_path)) +def cubes_2d(): + """2D cube.""" + time_coord = DimCoord( + 0.0, + var_name='time', + long_name='time', + units=Unit('day since 1950-01-01 00:00:00', calendar='gregorian'), + ) + lat_coord = DimCoord( + 0.0, + var_name='lat', + long_name='latitude', + units='degrees_north', + ) + lon_coord = DimCoord( + 0.0, + var_name='lon', + long_name='longitude', + units='degrees_east', + ) + cube = Cube( + [[[1.0]]], + dim_coords_and_dims=[(time_coord, 0), (lat_coord, 1), (lon_coord, 2)], + ) + cubes = CubeList([ + cube.copy(), + cube.copy(), + cube.copy(), + cube.copy(), + ]) + return cubes @pytest.fixture -def cubes_tracer_pdef_gp(test_data_path): - """tracer_pdef_gp sample cubes.""" - nc_path = test_data_path / 'emac_tracer_pdef_gp.nc' - return iris.load(str(nc_path)) +def cubes_3d(): + """3D cube.""" + time_coord = DimCoord( + 0.0, + var_name='time', + long_name='time', + units=Unit('day since 1950-01-01 00:00:00', calendar='gregorian'), + ) + plev_coord = DimCoord( + [100000.0, 90000.0], + var_name='pax_2', + units='Pa', + attributes={'positive': 'down'}, + ) + lev_coord = AuxCoord( + [1, 2], + var_name='lev', + long_name='hybrid level at layer midpoints', + ) + lat_coord = DimCoord( + 0.0, + var_name='lat', + long_name='latitude', + units='degrees_north', + ) + lon_coord = DimCoord( + 0.0, + var_name='lon', + long_name='longitude', + units='degrees_east', + ) + cube = Cube( + [[[[1.0]], [[2.0]]]], + dim_coords_and_dims=[(time_coord, 0), + (plev_coord, 1), + (lat_coord, 2), + (lon_coord, 3)], + aux_coords_and_dims=[(lev_coord, 1)], + ) + hyam_cube = Cube( + [100000.0, 90000.0], + var_name='hyam', + long_name='hybrid A coefficient at layer midpoints', + units='Pa', + ) + hybm_cube = Cube( + [0.8, 0.4], + var_name='hybm', + long_name='hybrid B coefficient at layer midpoints', + units='1', + ) + hyai_cube = Cube( + [110000.0, 95000.0, 80000.0], + var_name='hyai', + long_name='hybrid A coefficient at layer interfaces', + units='Pa', + ) + hybi_cube = Cube( + [0.9, 0.5, 0.2], + var_name='hybi', + long_name='hybrid B coefficient at layer interfaces', + units='1', + ) + aps_ave_cube = Cube( + [[[100000.0]]], + var_name='aps_ave', + long_name='surface pressure', + units='Pa', + ) + cubes = CubeList([ + cube.copy(), + cube.copy(), + cube.copy(), + cube.copy(), + hyam_cube, + hybm_cube, + hyai_cube, + hybi_cube, + aps_ave_cube, + ]) + return cubes def get_allvars_fix(mip, short_name): @@ -101,6 +190,18 @@ def get_allvars_fix(mip, short_name): return fix +def check_tas_metadata(cubes): + """Check tas metadata.""" + assert len(cubes) == 1 + cube = cubes[0] + assert cube.var_name == 'tas' + assert cube.standard_name == 'air_temperature' + assert cube.long_name == 'Near-Surface Air Temperature' + assert cube.units == 'K' + assert 'positive' not in cube.attributes + return cube + + def check_ta_metadata(cubes): """Check ta metadata.""" assert len(cubes) == 1 @@ -113,26 +214,17 @@ def check_ta_metadata(cubes): return cube -def check_time(cube, n_points=1): +def check_time(cube): """Check time coordinate of cube.""" assert cube.coords('time', dim_coords=True) time = cube.coord('time', dim_coords=True) assert time.var_name == 'time' assert time.standard_name == 'time' assert time.long_name == 'time' - assert time.units == Unit('day since 1849-01-01 00:00:00', + assert time.units == Unit('day since 1950-01-01 00:00:00', calendar='gregorian') - if n_points == 1: - np.testing.assert_allclose(time.points, [55181.9930555556]) - assert time.bounds is None - elif n_points == 2: - np.testing.assert_allclose(time.points, [55151.25, 55151.666667]) - np.testing.assert_allclose( - time.bounds, - [[55151.04166667, 55151.45833333], [55151.45833333, 55151.875]], - ) - else: - assert False, "Invalid n_points" + np.testing.assert_allclose(time.points, [54786.9916666667]) + assert time.bounds is None assert time.attributes == {} @@ -151,9 +243,10 @@ def check_plev(cube): # automatically np.testing.assert_allclose( plev.points, - [100.0, 500.0, 1000.0, 2000.0, 3000.0, 5000.0, 7000.0, 10000.0, - 15000.0, 20000.0, 25000.0, 30000.0, 40000.0, 50000.0, 60000.0, - 70000.0, 85000.0, 92500.0, 100000.0], + [3, 5, 7, 10, 15, 20, 30, 40, 50, 70, 100, 150, 200, 300, 500, 700, + 1000, 1500, 2000, 3000, 5000, 7000, 8000, 9000, 10000, 11500, 13000, + 15000, 17000, 20000, 25000, 30000, 40000, 50000, 60000, 70000, 85000, + 92500, 100000], ) assert plev.bounds is None @@ -172,14 +265,16 @@ def check_alevel(cube): assert lev.attributes['positive'] == 'down' np.testing.assert_allclose( lev.points[:4], - [9.96150017e-01, 9.82649982e-01, 9.58960303e-01, 9.27668441e-01], + [0.996141, 0.982633, 0.954782, 0.909258], + rtol=1e-5, ) np.testing.assert_allclose( lev.bounds[:4], - [[1.00000000e+00, 9.92299974e-01], - [9.92299974e-01, 9.72999990e-01], - [9.72999990e-01, 9.44920615e-01], - [9.44920615e-01, 9.10416267e-01]], + [[1.0, 0.992281], + [0.992281, 0.972985], + [0.972985, 0.936579], + [0.936579, 0.881937]], + rtol=1e-5, ) # Coefficient ap @@ -194,14 +289,16 @@ def check_alevel(cube): assert ap_coord.attributes == {} np.testing.assert_allclose( ap_coord.points[:4], - [0.0, 0.0, 36.03179932, 171.845047], + [0.0, 0.0, 391.597504, 1666.582031], + rtol=1e-5, ) np.testing.assert_allclose( ap_coord.bounds[:4], [[0.0, 0.0], [0.0, 0.0], - [0.0, 72.06359863], - [72.06359863, 271.62649536]], + [0.0, 783.195007], + [783.195007, 2549.968994]], + rtol=1e-5, ) # Coefficient b @@ -216,14 +313,16 @@ def check_alevel(cube): assert b_coord.attributes == {} np.testing.assert_allclose( b_coord.points[:4], - [0.99615002, 0.98264998, 0.95859998, 0.92594999], + [0.996141, 0.982633, 0.950866, 0.892592], + rtol=1e-5, ) np.testing.assert_allclose( b_coord.bounds[:4], - [[1.0, 0.99229997], - [0.99229997, 0.97299999], - [0.97299999, 0.94419998], - [0.94419998, 0.9077]], + [[1.0, 0.992281], + [0.992281, 0.972985], + [0.972985, 0.928747], + [0.928747, 0.856438]], + rtol=1e-5, ) # Coefficient ps @@ -236,7 +335,8 @@ def check_alevel(cube): assert ps_coord.attributes == {} np.testing.assert_allclose( ps_coord.points[:, :, 0], - [[100000.1875, 98240.7578125, 99601.09375, 96029.7109375]], + [[99915.351562, 98339.820312, 99585.25, 96572.765625]], + rtol=1e-5, ) assert ps_coord.bounds is None @@ -253,6 +353,25 @@ def check_alevel(cube): assert p_coord.bounds[0, 0, 0, 0, 0] > p_coord.bounds[0, 0, 0, 0, 1] +def check_hybrid_z(cube): + """Check hybrid Z-coordinates of 3D cubes.""" + assert len(cube.aux_factories) == 1 + + air_pressure_coord = cube.coord('air_pressure') + np.testing.assert_allclose( + air_pressure_coord.points, + [[[[130000.0]], [[180000.0]]]], + ) + np.testing.assert_allclose( + air_pressure_coord.bounds, + [[[[[100000.0, 145000.0]]], [[[145000.0, 200000.0]]]]], + ) + + lev_coord = cube.coord('atmosphere_hybrid_sigma_pressure_coordinate') + np.testing.assert_allclose(lev_coord.points, [1.3, 1.8]) + np.testing.assert_allclose(lev_coord.bounds, [[1.0, 1.45], [1.45, 2.0]]) + + def check_lat(cube): """Check latitude coordinate of cube.""" assert cube.coords('latitude', dim_coords=True) @@ -333,6 +452,86 @@ def check_typesi(cube): assert typesi.bounds is None +# Test variable extraction + + +def test_get_cube_cav(): + """Test fix.""" + fix = get_allvars_fix('Amon', 'tas') + cubes = CubeList([ + Cube(0.0), + Cube(0.0, var_name='temp2_cav'), + ]) + cube = fix.get_cube(cubes) + assert cube.var_name == 'temp2_cav' + + +def test_get_cube_ave(): + """Test fix.""" + fix = get_allvars_fix('Amon', 'tas') + cubes = CubeList([ + Cube(0.0), + Cube(0.0, var_name='temp2_ave'), + ]) + cube = fix.get_cube(cubes) + assert cube.var_name == 'temp2_ave' + + +def test_get_cube_cav_ave(): + """Test fix.""" + fix = get_allvars_fix('Amon', 'tas') + cubes = CubeList([ + Cube(0.0, var_name='temp2_ave'), + Cube(0.0, var_name='temp2_cav'), + ]) + cube = fix.get_cube(cubes) + assert cube.var_name == 'temp2_cav' + + +def test_get_cube_str_input(): + """Test fix.""" + fix = get_allvars_fix('Amon', 'tas') + cubes = CubeList([ + Cube(0.0), + Cube(0.0, var_name='x'), + ]) + cube = fix.get_cube(cubes, var_names='x') + assert cube.var_name == 'x' + + +def test_get_cube_list_input(): + """Test fix.""" + fix = get_allvars_fix('Amon', 'tas') + cubes = CubeList([ + Cube(0.0), + Cube(0.0, var_name='x'), + Cube(0.0, var_name='y'), + ]) + cube = fix.get_cube(cubes, var_names=['y', 'x']) + assert cube.var_name == 'y' + + +def test_var_not_available_fix(): + """Test fix.""" + fix = get_allvars_fix('Amon', 'ta') + cubes = CubeList([Cube(0.0)]) + msg = (r"No variable of \['tm1_p19_cav', 'tm1_p19_ave'\] necessary for " + r"the extraction/derivation the CMOR variable 'ta' is available in " + r"the input file.") + with pytest.raises(ValueError, match=msg): + fix.fix_metadata(cubes) + + +def test_var_not_available_get_cube(): + """Test fix.""" + fix = get_allvars_fix('Amon', 'ta') + cubes = CubeList([Cube(0.0)]) + msg = (r"No variable of \['x'\] necessary for the extraction/derivation " + r"the CMOR variable 'ta' is available in the input file.") + with pytest.raises(ValueError, match=msg): + fix.get_cube(cubes, var_names='x') + + # Test with single-dimension cubes @@ -516,7 +715,101 @@ def test_only_longitude(): vardef.dimensions = original_dimensions -# Test each 2D variable in extra_facets/emac-mappings.yml +# Tests with sample data +# Note: test_data_path is defined in tests/integration/cmor/_fixes/conftest.py + + +def test_sample_data_tas(test_data_path, tmp_path): + """Test fix.""" + fix = get_allvars_fix('Amon', 'tas') + + filepath = test_data_path / 'emac.nc' + fixed_path = fix.fix_file(filepath, tmp_path) + assert fixed_path == filepath + + cubes = iris.load(fixed_path) + fixed_cubes = fix.fix_metadata(cubes) + + cube = check_tas_metadata(fixed_cubes) + + check_time(cube) + check_lat(cube) + check_lon(cube) + + assert cube.shape == (1, 4, 8) + np.testing.assert_allclose( + cube.data[:, :, 0], + [[277.3045, 293.08575, 295.9718, 275.26523]], + rtol=1e-5, + ) + + +def test_sample_data_ta_plev(test_data_path, tmp_path): + """Test fix.""" + # Note: raw_name needs to be modified since the sample file only contains + # plev39, while Amon's ta needs plev19 by default + vardef = get_var_info('EMAC', 'Amon', 'ta') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'ta', ()) + original_raw_name = extra_facets['raw_name'] + extra_facets['raw_name'] = ['tm1_p39_cav', 'tm1_p39_ave'] + fix = AllVars(vardef, extra_facets=extra_facets) + + filepath = test_data_path / 'emac.nc' + fixed_path = fix.fix_file(filepath, tmp_path) + assert fixed_path == filepath + + cubes = iris.load(fixed_path) + fixed_cubes = fix.fix_metadata(cubes) + + cube = check_ta_metadata(fixed_cubes) + + check_time(cube) + check_plev(cube) + check_lat(cube) + check_lon(cube) + + assert cube.shape == (1, 39, 4, 8) + np.testing.assert_allclose( + cube.data[0, :5, 0, 0], + [204.34882, 209.85188, 215.6242, 223.81247, 232.94002], + rtol=1e-5, + ) + + fix.extra_facets['raw_name'] = original_raw_name + + +def test_sample_data_ta_alevel(test_data_path, tmp_path): + """Test fix.""" + fix = get_allvars_fix('CFmon', 'ta') + + filepath = test_data_path / 'emac.nc' + fixed_path = fix.fix_file(filepath, tmp_path) + assert fixed_path != filepath + + cubes = iris.load(fixed_path) + assert cubes.extract(NameConstraint(var_name='hyam')) + assert cubes.extract(NameConstraint(var_name='hybm')) + assert cubes.extract(NameConstraint(var_name='hyai')) + assert cubes.extract(NameConstraint(var_name='hybi')) + + fixed_cubes = fix.fix_metadata(cubes) + + cube = check_ta_metadata(fixed_cubes) + + check_time(cube) + check_alevel(cube) + check_lat(cube) + check_lon(cube) + + assert cube.shape == (1, 90, 4, 8) + np.testing.assert_allclose( + cube.data[0, :5, 0, 0], + [276.98267, 276.10773, 275.07455, 273.53384, 270.64545], + rtol=1e-5, + ) + + +# Test 2D variables in extra_facets/emac-mappings.yml def test_get_awhea_fix(): @@ -525,10 +818,12 @@ def test_get_awhea_fix(): assert fix == [AllVars(None)] -def test_awhea_fix(cubes_omon_2d): +def test_awhea_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'awhea_cav' + cubes_2d[0].units = 'W m-2' fix = get_allvars_fix('Omon', 'awhea') - fixed_cubes = fix.fix_metadata(cubes_omon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -539,15 +834,7 @@ def test_awhea_fix(cubes_omon_2d): assert cube.units == 'W m-2' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[-203.94414, -16.695345, 74.117096, 104.992195]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[1.0]]]) def test_get_clivi_fix(): @@ -556,10 +843,12 @@ def test_get_clivi_fix(): assert fix == [AllVars(None)] -def test_clivi_fix(cubes_amon_2d): +def test_clivi_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'xivi_cav' + cubes_2d[0].units = 'kg m-2' fix = get_allvars_fix('Amon', 'clivi') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -569,15 +858,7 @@ def test_clivi_fix(cubes_amon_2d): assert cube.units == 'kg m-2' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[0.01435195, 0.006420649, 0.0007885683, 0.01154814]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[1.0]]]) def test_get_clt_fix(): @@ -586,12 +867,13 @@ def test_get_clt_fix(): assert fix == [Clt(None), AllVars(None)] -def test_clt_fix(cubes_amon_2d): +def test_clt_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'aclcov_cav' vardef = get_var_info('EMAC', 'Amon', 'clt') extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'clt', ()) fix = Clt(vardef, extra_facets=extra_facets) - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) fix = get_allvars_fix('Amon', 'clt') fixed_cubes = fix.fix_metadata(fixed_cubes) @@ -604,15 +886,7 @@ def test_clt_fix(cubes_amon_2d): assert cube.units == '%' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[86.79899, 58.01009, 34.01953, 85.48493]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[100.0]]]) def test_get_clwvi_fix(): @@ -621,12 +895,16 @@ def test_get_clwvi_fix(): assert fix == [Clwvi(None), AllVars(None)] -def test_clwvi_fix(cubes_amon_2d): +def test_clwvi_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'xlvi_cav' + cubes_2d[1].var_name = 'xivi_cav' + cubes_2d[0].units = 'kg m-2' + cubes_2d[1].units = 'kg m-2' vardef = get_var_info('EMAC', 'Amon', 'clwvi') extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'clwvi', ()) fix = Clwvi(vardef, extra_facets=extra_facets) - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) fix = get_allvars_fix('Amon', 'clwvi') fixed_cubes = fix.fix_metadata(fixed_cubes) @@ -640,15 +918,7 @@ def test_clwvi_fix(cubes_amon_2d): assert cube.units == 'kg m-2' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[0.20945302, 0.01015517, 0.01444221, 0.10618545]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[2.0]]]) def test_get_co2mass_fix(): @@ -657,10 +927,12 @@ def test_get_co2mass_fix(): assert fix == [AllVars(None)] -def test_co2mass_fix(cubes_tracer_pdef_gp): +def test_co2mass_fix(cubes_1d): """Test fix.""" + cubes_1d[0].var_name = 'MP_CO2_cav' + cubes_1d[0].units = 'kg' fix = get_allvars_fix('Amon', 'co2mass') - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -670,13 +942,7 @@ def test_co2mass_fix(cubes_tracer_pdef_gp): assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [2.855254e+15, 2.85538e+15], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [1.0]) def test_get_evspsbl_fix(): @@ -685,10 +951,12 @@ def test_get_evspsbl_fix(): assert fix == [Evspsbl(None), AllVars(None)] -def test_evspsbl_fix(cubes_amon_2d): +def test_evspsbl_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'evap_cav' + cubes_2d[0].units = 'kg m-2 s-1' fix = get_allvars_fix('Amon', 'evspsbl') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -705,15 +973,7 @@ def test_evspsbl_fix(cubes_amon_2d): assert cube.units == 'kg m-2 s-1' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[3.636807e-05, 3.438968e-07, 6.235108e-05, 1.165336e-05]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[-1.0]]]) def test_get_hfls_fix(): @@ -722,10 +982,12 @@ def test_get_hfls_fix(): assert fix == [Hfls(None), AllVars(None)] -def test_hfls_fix(cubes_amon_2d): +def test_hfls_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'ahfl_cav' + cubes_2d[0].units = 'W m-2' fix = get_allvars_fix('Amon', 'hfls') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -741,15 +1003,7 @@ def test_hfls_fix(cubes_amon_2d): assert cube.units == 'W m-2' assert cube.attributes['positive'] == 'up' - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[90.94926, 0.860017, 155.92758, 29.142715]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[-1.0]]]) def test_get_hfss_fix(): @@ -758,10 +1012,12 @@ def test_get_hfss_fix(): assert fix == [Hfss(None), AllVars(None)] -def test_hfss_fix(cubes_amon_2d): +def test_hfss_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'ahfs_cav' + cubes_2d[0].units = 'W m-2' fix = get_allvars_fix('Amon', 'hfss') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -777,15 +1033,35 @@ def test_hfss_fix(cubes_amon_2d): assert cube.units == 'W m-2' assert cube.attributes['positive'] == 'up' - check_time(cube) - check_lat(cube) - check_lon(cube) + np.testing.assert_allclose(cube.data, [[[-1.0]]]) - np.testing.assert_allclose( - cube.data[:, :, 0], - [[65.92767, 32.841537, 18.461172, 6.50319]], - rtol=1e-5, - ) + +def test_get_hurs_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'hurs') + assert fix == [Hurs(None), AllVars(None)] + + +def test_hurs_fix(cubes_2d): + """Test fix.""" + cubes_2d[0].var_name = 'rh_2m_cav' + vardef = get_var_info('EMAC', 'Amon', 'hurs') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'hurs', ()) + fix = Hurs(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_2d) + + fix = get_allvars_fix('Amon', 'hurs') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'hurs' + assert cube.standard_name == 'relative_humidity' + assert cube.long_name == 'Near-Surface Relative Humidity' + assert cube.units == '%' + assert 'positive' not in cube.attributes + + np.testing.assert_allclose(cube.data, [[[100.0]]]) def test_get_od550aer_fix(): @@ -794,18 +1070,23 @@ def test_get_od550aer_fix(): assert fix == [Od550aer(None), AllVars(None)] -def test_od550aer_fix(cubes_aermon): +def test_od550aer_fix(cubes_3d): """Test fix.""" + cubes_3d[0].var_name = 'aot_opt_TOT_550_total_cav' + cubes_3d[0].units = '1' vardef = get_var_info('EMAC', 'Amon', 'od550aer') extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'od550aer', ()) fix = Od550aer(vardef, extra_facets=extra_facets) - fixed_cubes = fix.fix_metadata(cubes_aermon) + fixed_cubes = fix.fix_metadata(cubes_3d) - fix = get_allvars_fix('Amon', 'od550aer') - fixed_cubes = fix.fix_metadata(fixed_cubes) + allvars_fix = get_allvars_fix('Amon', 'od550aer') + fixed_cubes = allvars_fix.fix_metadata(fixed_cubes) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] + + cube = fix.fix_data(cube) + assert cube.var_name == 'od550aer' assert cube.standard_name == ('atmosphere_optical_thickness_due_to_' 'ambient_aerosol_particles') @@ -813,16 +1094,9 @@ def test_od550aer_fix(cubes_aermon): assert cube.units == '1' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) check_lambda550nm(cube) - np.testing.assert_allclose( - cube.data[:, :, 0], - [[0.166031, 0.271185, 0.116384, 0.044266]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[3.0]]]) def test_get_pr_fix(): @@ -831,12 +1105,18 @@ def test_get_pr_fix(): assert fix == [Pr(None), AllVars(None)] -def test_pr_fix(cubes_amon_2d): +def test_pr_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'aprl_cav' + cubes_2d[1].var_name = 'aprc_cav' + cubes_2d[2].var_name = 'aprs_cav' + cubes_2d[0].units = 'kg m-2 s-1' + cubes_2d[1].units = 'kg m-2 s-1' + cubes_2d[2].units = 'kg m-2 s-1' vardef = get_var_info('EMAC', 'Amon', 'pr') extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'pr', ()) fix = Pr(vardef, extra_facets=extra_facets) - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) fix = get_allvars_fix('Amon', 'pr') fixed_cubes = fix.fix_metadata(fixed_cubes) @@ -849,15 +1129,7 @@ def test_pr_fix(cubes_amon_2d): assert cube.units == 'kg m-2 s-1' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[3.590828e-05, 5.637868e-07, 3.474401e-07, 1.853631e-05]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[3.0]]]) def test_get_prc_fix(): @@ -866,10 +1138,12 @@ def test_get_prc_fix(): assert fix == [AllVars(None)] -def test_prc_fix(cubes_amon_2d): +def test_prc_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'aprc_cav' + cubes_2d[0].units = 'kg m-2 s-1' fix = get_allvars_fix('Amon', 'prc') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -879,15 +1153,7 @@ def test_prc_fix(cubes_amon_2d): assert cube.units == 'kg m-2 s-1' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[1.177248e-05, 0.0, 0.0, 2.419762e-06]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[1.0]]]) def test_get_prl_fix(): @@ -896,10 +1162,12 @@ def test_get_prl_fix(): assert fix == [AllVars(None)] -def test_prl_fix(cubes_amon_2d): +def test_prl_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'aprl_cav' + cubes_2d[0].units = 'kg m-2 s-1' fix = get_allvars_fix('Amon', 'prl') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -909,15 +1177,7 @@ def test_prl_fix(cubes_amon_2d): assert cube.units == 'kg m-2 s-1' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[2.091789e-05, 5.637868e-07, 3.474401e-07, 1.611654e-05]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[1.0]]]) def test_get_prsn_fix(): @@ -926,10 +1186,12 @@ def test_get_prsn_fix(): assert fix == [AllVars(None)] -def test_prsn_fix(cubes_amon_2d): +def test_prsn_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'aprs_cav' + cubes_2d[0].units = 'kg m-2 s-1' fix = get_allvars_fix('Amon', 'prsn') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -939,15 +1201,7 @@ def test_prsn_fix(cubes_amon_2d): assert cube.units == 'kg m-2 s-1' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[3.217916e-06, 5.760116e-30, 5.894975e-30, 6.625394e-30]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[1.0]]]) def test_get_prw_fix(): @@ -956,10 +1210,12 @@ def test_get_prw_fix(): assert fix == [AllVars(None)] -def test_prw_fix(cubes_amon_2d): +def test_prw_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'qvi_cav' + cubes_2d[0].units = 'kg m-2' fix = get_allvars_fix('Amon', 'prw') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -969,15 +1225,55 @@ def test_prw_fix(cubes_amon_2d): assert cube.units == 'kg m-2' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) + np.testing.assert_allclose(cube.data, [[[1.0]]]) + + +def test_get_ps_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'ps') + assert fix == [AllVars(None)] - np.testing.assert_allclose( - cube.data[:, :, 0], - [[9.398615, 10.207355, 22.597773, 11.342406]], - rtol=1e-5, - ) + +def test_ps_fix(cubes_2d): + """Test fix.""" + cubes_2d[0].var_name = 'aps_cav' + cubes_2d[0].units = 'Pa' + fix = get_allvars_fix('Amon', 'ps') + fixed_cubes = fix.fix_metadata(cubes_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'ps' + assert cube.standard_name == 'surface_air_pressure' + assert cube.long_name == 'Surface Air Pressure' + assert cube.units == 'Pa' + assert 'positive' not in cube.attributes + + np.testing.assert_allclose(cube.data, [[[1.0]]]) + + +def test_get_psl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'psl') + assert fix == [AllVars(None)] + + +def test_psl_fix(cubes_2d): + """Test fix.""" + cubes_2d[0].var_name = 'slp_cav' + cubes_2d[0].units = 'Pa' + fix = get_allvars_fix('Amon', 'psl') + fixed_cubes = fix.fix_metadata(cubes_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'psl' + assert cube.standard_name == 'air_pressure_at_mean_sea_level' + assert cube.long_name == 'Sea Level Pressure' + assert cube.units == 'Pa' + assert 'positive' not in cube.attributes + + np.testing.assert_allclose(cube.data, [[[1.0]]]) def test_get_rlds_fix(): @@ -986,12 +1282,16 @@ def test_get_rlds_fix(): assert fix == [Rlds(None), AllVars(None)] -def test_rlds_fix(cubes_amon_2d): +def test_rlds_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'flxtbot_cav' + cubes_2d[1].var_name = 'tradsu_cav' + cubes_2d[0].units = 'W m-2' + cubes_2d[1].units = 'W m-2' vardef = get_var_info('EMAC', 'Amon', 'rlds') extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'rlds', ()) fix = Rlds(vardef, extra_facets=extra_facets) - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) fix = get_allvars_fix('Amon', 'rlds') fixed_cubes = fix.fix_metadata(fixed_cubes) @@ -1004,15 +1304,7 @@ def test_rlds_fix(cubes_amon_2d): assert cube.units == 'W m-2' assert cube.attributes['positive'] == 'down' - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[297.55298, 310.508, 361.471, 302.51376]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[0.0]]]) def test_get_rlus_fix(): @@ -1021,10 +1313,12 @@ def test_get_rlus_fix(): assert fix == [Rlus(None), AllVars(None)] -def test_rlus_fix(cubes_amon_2d): +def test_rlus_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'tradsu_cav' + cubes_2d[0].units = 'W m-2' fix = get_allvars_fix('Amon', 'rlus') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1040,15 +1334,7 @@ def test_rlus_fix(cubes_amon_2d): assert cube.units == 'W m-2' assert cube.attributes['positive'] == 'up' - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[351.59143, 411.6364, 438.25314, 339.71625]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[-1.0]]]) def test_get_rlut_fix(): @@ -1057,10 +1343,12 @@ def test_get_rlut_fix(): assert fix == [Rlut(None), AllVars(None)] -def test_rlut_fix(cubes_amon_2d): +def test_rlut_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'flxttop_cav' + cubes_2d[0].units = 'W m-2' fix = get_allvars_fix('Amon', 'rlut') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1076,15 +1364,7 @@ def test_rlut_fix(cubes_amon_2d): assert cube.units == 'W m-2' assert cube.attributes['positive'] == 'up' - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[181.34714, 240.24974, 282.01166, 203.07207]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[-1.0]]]) def test_get_rlutcs_fix(): @@ -1093,10 +1373,12 @@ def test_get_rlutcs_fix(): assert fix == [Rlutcs(None), AllVars(None)] -def test_rlutcs_fix(cubes_amon_2d): +def test_rlutcs_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'flxtftop_cav' + cubes_2d[0].units = 'W m-2' fix = get_allvars_fix('Amon', 'rlutcs') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1113,15 +1395,7 @@ def test_rlutcs_fix(cubes_amon_2d): assert cube.units == 'W m-2' assert cube.attributes['positive'] == 'up' - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[232.35957, 273.42227, 288.2262, 238.6909]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[-1.0]]]) def test_get_rsds_fix(): @@ -1130,12 +1404,16 @@ def test_get_rsds_fix(): assert fix == [Rsds(None), AllVars(None)] -def test_rsds_fix(cubes_amon_2d): +def test_rsds_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'flxsbot_cav' + cubes_2d[1].var_name = 'sradsu_cav' + cubes_2d[0].units = 'W m-2' + cubes_2d[1].units = 'W m-2' vardef = get_var_info('EMAC', 'Amon', 'rsds') extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'rsds', ()) fix = Rsds(vardef, extra_facets=extra_facets) - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) fix = get_allvars_fix('Amon', 'rsds') fixed_cubes = fix.fix_metadata(fixed_cubes) @@ -1148,15 +1426,7 @@ def test_rsds_fix(cubes_amon_2d): assert cube.units == 'W m-2' assert cube.attributes['positive'] == 'down' - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[7.495961, 214.6077, 349.77203, 191.22644]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[0.0]]]) def test_get_rsdt_fix(): @@ -1165,12 +1435,16 @@ def test_get_rsdt_fix(): assert fix == [Rsdt(None), AllVars(None)] -def test_rsdt_fix(cubes_amon_2d): +def test_rsdt_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'flxstop_cav' + cubes_2d[1].var_name = 'srad0u_cav' + cubes_2d[0].units = 'W m-2' + cubes_2d[1].units = 'W m-2' vardef = get_var_info('EMAC', 'Amon', 'rsdt') extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'rsdt', ()) fix = Rsdt(vardef, extra_facets=extra_facets) - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) fix = get_allvars_fix('Amon', 'rsdt') fixed_cubes = fix.fix_metadata(fixed_cubes) @@ -1183,15 +1457,7 @@ def test_rsdt_fix(cubes_amon_2d): assert cube.units == 'W m-2' assert cube.attributes['positive'] == 'down' - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[44.4018, 312.62286, 481.91992, 473.25092]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[0.0]]]) def test_get_rsus_fix(): @@ -1200,10 +1466,12 @@ def test_get_rsus_fix(): assert fix == [Rsus(None), AllVars(None)] -def test_rsus_fix(cubes_amon_2d): +def test_rsus_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'sradsu_cav' + cubes_2d[0].units = 'W m-2' fix = get_allvars_fix('Amon', 'rsus') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1219,15 +1487,7 @@ def test_rsus_fix(cubes_amon_2d): assert cube.units == 'W m-2' assert cube.attributes['positive'] == 'up' - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[0.524717, 82.92702, 24.484043, 13.38585]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[-1.0]]]) def test_get_rsut_fix(): @@ -1236,10 +1496,12 @@ def test_get_rsut_fix(): assert fix == [Rsut(None), AllVars(None)] -def test_rsut_fix(cubes_amon_2d): +def test_rsut_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'srad0u_cav' + cubes_2d[0].units = 'W m-2' fix = get_allvars_fix('Amon', 'rsut') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1255,15 +1517,7 @@ def test_rsut_fix(cubes_amon_2d): assert cube.units == 'W m-2' assert cube.attributes['positive'] == 'up' - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[26.967886, 114.11882, 70.44302, 203.26039]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[-1.0]]]) def test_get_rsutcs_fix(): @@ -1272,10 +1526,12 @@ def test_get_rsutcs_fix(): assert fix == [Rsutcs(None), AllVars(None)] -def test_rsutcs_fix(cubes_amon_2d): +def test_rsutcs_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'flxusftop_cav' + cubes_2d[0].units = 'W m-2' fix = get_allvars_fix('Amon', 'rsutcs') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1292,15 +1548,7 @@ def test_rsutcs_fix(cubes_amon_2d): assert cube.units == 'W m-2' assert cube.attributes['positive'] == 'up' - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[11.787124, 101.68645, 50.588364, 53.933403]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[-1.0]]]) def test_get_rtmt_fix(): @@ -1309,12 +1557,16 @@ def test_get_rtmt_fix(): assert fix == [Rtmt(None), AllVars(None)] -def test_rtmt_fix(cubes_amon_2d): +def test_rtmt_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'flxttop_cav' + cubes_2d[1].var_name = 'flxstop_cav' + cubes_2d[0].units = 'W m-2' + cubes_2d[1].units = 'W m-2' vardef = get_var_info('EMAC', 'Amon', 'rtmt') extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'rtmt', ()) fix = Rtmt(vardef, extra_facets=extra_facets) - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) fix = get_allvars_fix('Amon', 'rtmt') fixed_cubes = fix.fix_metadata(fixed_cubes) @@ -1328,15 +1580,33 @@ def test_rtmt_fix(cubes_amon_2d): assert cube.units == 'W m-2' assert cube.attributes['positive'] == 'down' - check_time(cube) - check_lat(cube) - check_lon(cube) + np.testing.assert_allclose(cube.data, [[[2.0]]]) - np.testing.assert_allclose( - cube.data[:, :, 0], - [[-163.91322, -41.745697, 129.46524, 66.91847]], - rtol=1e-5, - ) + +def test_get_sfcWind_fix(): # noqa: N802 + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'sfcWind') + assert fix == [AllVars(None)] + + +def test_sfcWind_fix(cubes_2d): # noqa: N802 + """Test fix.""" + cubes_2d[0].var_name = 'wind10_cav' + cubes_2d[0].units = 'm s-1' + fix = get_allvars_fix('Amon', 'sfcWind') + fixed_cubes = fix.fix_metadata(cubes_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'sfcWind' + assert cube.standard_name == 'wind_speed' + assert cube.long_name == 'Near-Surface Wind Speed' + assert cube.units == 'm s-1' + assert 'positive' not in cube.attributes + + check_heightxm(cube, 10.0) + + np.testing.assert_allclose(cube.data, [[[1.0]]]) def test_get_siconc_fix(): @@ -1345,12 +1615,13 @@ def test_get_siconc_fix(): assert fix == [Siconc(None), AllVars(None)] -def test_siconc_fix(cubes_amon_2d): +def test_siconc_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'seaice_cav' vardef = get_var_info('EMAC', 'SImon', 'siconc') extra_facets = get_extra_facets('EMAC', 'EMAC', 'SImon', 'siconc', ()) fix = Siconc(vardef, extra_facets=extra_facets) - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) fix = get_allvars_fix('SImon', 'siconc') fixed_cubes = fix.fix_metadata(fixed_cubes) @@ -1363,16 +1634,9 @@ def test_siconc_fix(cubes_amon_2d): assert cube.units == '%' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) check_typesi(cube) - np.testing.assert_allclose( - cube.data[:, :, 1], - [[61.51324, 0.0, 0.0, 0.0]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[100.0]]]) def test_get_siconca_fix(): @@ -1381,12 +1645,13 @@ def test_get_siconca_fix(): assert fix == [Siconca(None), AllVars(None)] -def test_siconca_fix(cubes_amon_2d): +def test_siconca_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'seaice_cav' vardef = get_var_info('EMAC', 'SImon', 'siconca') extra_facets = get_extra_facets('EMAC', 'EMAC', 'SImon', 'siconca', ()) fix = Siconca(vardef, extra_facets=extra_facets) - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) fix = get_allvars_fix('SImon', 'siconca') fixed_cubes = fix.fix_metadata(fixed_cubes) @@ -1399,16 +1664,9 @@ def test_siconca_fix(cubes_amon_2d): assert cube.units == '%' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) check_typesi(cube) - np.testing.assert_allclose( - cube.data[:, :, 1], - [[61.51324, 0.0, 0.0, 0.0]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[100.0]]]) def test_get_sithick_fix(): @@ -1417,10 +1675,12 @@ def test_get_sithick_fix(): assert fix == [Sithick(None), AllVars(None)] -def test_sithick_fix(cubes_amon_2d): +def test_sithick_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'siced_cav' + cubes_2d[0].units = 'm' fix = get_allvars_fix('SImon', 'sithick') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1436,15 +1696,14 @@ def test_sithick_fix(cubes_amon_2d): assert cube.units == 'm' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) + np.testing.assert_allclose(cube.data, [[[1.0]]]) + np.testing.assert_equal(np.ma.getmaskarray(cube.data), [[[False]]]) - np.testing.assert_allclose(cube.data[0, 0, 1], 0.798652, rtol=1e-5,) - np.testing.assert_equal( - cube.data[:, :, 1].mask, - [[False, True, True, True]], - ) + # Check masking + cube.data = [[[0.0]]] + cube = fix.fix_data(cube) + np.testing.assert_allclose(cube.data, [[[0.0]]]) + np.testing.assert_equal(np.ma.getmaskarray(cube.data), [[[True]]]) def test_get_tas_fix(): @@ -1453,10 +1712,12 @@ def test_get_tas_fix(): assert fix == [AllVars(None)] -def test_tas_fix(cubes_amon_2d): +def test_tas_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'temp2_cav' + cubes_2d[0].units = 'K' fix = get_allvars_fix('Amon', 'tas') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1466,16 +1727,109 @@ def test_tas_fix(cubes_amon_2d): assert cube.units == 'K' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) check_heightxm(cube, 2.0) - np.testing.assert_allclose( - cube.data[:, :, 0], - [[277.4016, 291.2251, 295.6336, 277.8235]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[1.0]]]) + + +def test_get_tasmax_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'tasmax') + assert fix == [AllVars(None)] + + +def test_tasmax_fix(cubes_2d): + """Test fix.""" + cubes_2d[0].var_name = 'temp2_max' + cubes_2d[0].units = 'K' + fix = get_allvars_fix('Amon', 'tasmax') + fixed_cubes = fix.fix_metadata(cubes_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'tasmax' + assert cube.standard_name == 'air_temperature' + assert cube.long_name == 'Daily Maximum Near-Surface Air Temperature' + assert cube.units == 'K' + assert 'positive' not in cube.attributes + + check_heightxm(cube, 2.0) + + np.testing.assert_allclose(cube.data, [[[1.0]]]) + + +def test_get_tasmin_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'tasmin') + assert fix == [AllVars(None)] + + +def test_tasmin_fix(cubes_2d): + """Test fix.""" + cubes_2d[0].var_name = 'temp2_min' + cubes_2d[0].units = 'K' + fix = get_allvars_fix('Amon', 'tasmin') + fixed_cubes = fix.fix_metadata(cubes_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'tasmin' + assert cube.standard_name == 'air_temperature' + assert cube.long_name == 'Daily Minimum Near-Surface Air Temperature' + assert cube.units == 'K' + assert 'positive' not in cube.attributes + + check_heightxm(cube, 2.0) + + np.testing.assert_allclose(cube.data, [[[1.0]]]) + + +def test_get_tauu_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'tauu') + assert fix == [AllVars(None)] + + +def test_tauu_fix(cubes_2d): + """Test fix.""" + cubes_2d[0].var_name = 'ustr_cav' + cubes_2d[0].units = 'Pa' + fix = get_allvars_fix('Amon', 'tauu') + fixed_cubes = fix.fix_metadata(cubes_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'tauu' + assert cube.standard_name == 'surface_downward_eastward_stress' + assert cube.long_name == 'Surface Downward Eastward Wind Stress' + assert cube.units == 'Pa' + assert cube.attributes['positive'] == 'down' + + np.testing.assert_allclose(cube.data, [[[1.0]]]) + + +def test_get_tauv_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'tauv') + assert fix == [AllVars(None)] + + +def test_tauv_fix(cubes_2d): + """Test fix.""" + cubes_2d[0].var_name = 'vstr_cav' + cubes_2d[0].units = 'Pa' + fix = get_allvars_fix('Amon', 'tauv') + fixed_cubes = fix.fix_metadata(cubes_2d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'tauv' + assert cube.standard_name == 'surface_downward_northward_stress' + assert cube.long_name == 'Surface Downward Northward Wind Stress' + assert cube.units == 'Pa' + assert cube.attributes['positive'] == 'down' + + np.testing.assert_allclose(cube.data, [[[1.0]]]) def test_get_tos_fix(): @@ -1484,10 +1838,12 @@ def test_get_tos_fix(): assert fix == [AllVars(None)] -def test_tos_fix(cubes_g3b): +def test_tos_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'tsw' + cubes_2d[0].units = 'degC' fix = get_allvars_fix('Omon', 'tos') - fixed_cubes = fix.fix_metadata(cubes_g3b) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1497,17 +1853,7 @@ def test_tos_fix(cubes_g3b): assert cube.units == 'degC' assert 'positive' not in cube.attributes - print(cube.coord('time')) - - check_time(cube, n_points=2) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[0, :, 0], - [7.828393, 10.133539, 23.036158, 4.997858], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[1.0]]]) def test_get_toz_fix(): @@ -1516,12 +1862,14 @@ def test_get_toz_fix(): assert fix == [Toz(None), AllVars(None)] -def test_toz_fix(cubes_column): +def test_toz_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'toz' + cubes_2d[0].units = 'DU' vardef = get_var_info('EMAC', 'AERmon', 'toz') extra_facets = get_extra_facets('EMAC', 'EMAC', 'AERmon', 'toz', ()) fix = Toz(vardef, extra_facets=extra_facets) - fixed_cubes = fix.fix_metadata(cubes_column) + fixed_cubes = fix.fix_metadata(cubes_2d) fix = get_allvars_fix('AERmon', 'toz') fixed_cubes = fix.fix_metadata(fixed_cubes) @@ -1535,15 +1883,7 @@ def test_toz_fix(cubes_column): assert cube.units == 'm' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[0, :, 0], - [0.003108, 0.002928, 0.002921, 0.003366], - rtol=1e-3, - ) + np.testing.assert_allclose(cube.data, [[[1e-5]]]) def test_get_ts_fix(): @@ -1552,10 +1892,12 @@ def test_get_ts_fix(): assert fix == [AllVars(None)] -def test_ts_fix(cubes_amon_2d): +def test_ts_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'tsurf_cav' + cubes_2d[0].units = 'K' fix = get_allvars_fix('Amon', 'ts') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1565,15 +1907,7 @@ def test_ts_fix(cubes_amon_2d): assert cube.units == 'K' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) - - np.testing.assert_allclose( - cube.data[:, :, 0], - [[280.65475, 291.80563, 296.55356, 278.24164]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[1.0]]]) def test_get_uas_fix(): @@ -1582,10 +1916,12 @@ def test_get_uas_fix(): assert fix == [AllVars(None)] -def test_uas_fix(cubes_amon_2d): +def test_uas_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'u10_cav' + cubes_2d[0].units = 'm s-1' fix = get_allvars_fix('Amon', 'uas') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1595,16 +1931,9 @@ def test_uas_fix(cubes_amon_2d): assert cube.units == 'm s-1' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) check_heightxm(cube, 10.0) - np.testing.assert_allclose( - cube.data[:, :, 0], - [[-2.114626, -2.809653, -6.59721, -1.586884]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[1.0]]]) def test_get_vas_fix(): @@ -1613,10 +1942,12 @@ def test_get_vas_fix(): assert fix == [AllVars(None)] -def test_vas_fix(cubes_amon_2d): +def test_vas_fix(cubes_2d): """Test fix.""" + cubes_2d[0].var_name = 'v10_cav' + cubes_2d[0].units = 'm s-1' fix = get_allvars_fix('Amon', 'vas') - fixed_cubes = fix.fix_metadata(cubes_amon_2d) + fixed_cubes = fix.fix_metadata(cubes_2d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1626,19 +1957,12 @@ def test_vas_fix(cubes_amon_2d): assert cube.units == 'm s-1' assert 'positive' not in cube.attributes - check_time(cube) - check_lat(cube) - check_lon(cube) check_heightxm(cube, 10.0) - np.testing.assert_allclose( - cube.data[:, :, 0], - [[3.026835, -2.226409, 4.868941, 3.301589]], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [[[1.0]]]) -# Test each tracer variable in extra_facets/emac-mappings.yml +# Test 1D tracers in extra_facets/emac-mappings.yml def test_get_MP_BC_tot_fix(): # noqa: N802 @@ -1647,13 +1971,21 @@ def test_get_MP_BC_tot_fix(): # noqa: N802 assert fix == [MP_BC_tot(None), AllVars(None)] -def test_MP_BC_tot_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_BC_tot_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_BC_ki_cav' + cubes_1d[1].var_name = 'MP_BC_ks_cav' + cubes_1d[2].var_name = 'MP_BC_as_cav' + cubes_1d[3].var_name = 'MP_BC_cs_cav' + cubes_1d[0].units = 'kg' + cubes_1d[1].units = 'kg' + cubes_1d[2].units = 'kg' + cubes_1d[3].units = 'kg' vardef = get_var_info('EMAC', 'TRAC10hr', 'MP_BC_tot') extra_facets = get_extra_facets('EMAC', 'EMAC', 'TRAC10hr', 'MP_BC_tot', ()) fix = MP_BC_tot(vardef, extra_facets=extra_facets) - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) fix = get_allvars_fix('TRAC10hr', 'MP_BC_tot') fixed_cubes = fix.fix_metadata(fixed_cubes) @@ -1667,13 +1999,7 @@ def test_MP_BC_tot_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [6.361834e+08, 6.371043e+08], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [4.0]) def test_get_MP_CFCl3_fix(): # noqa: N802 @@ -1682,10 +2008,12 @@ def test_get_MP_CFCl3_fix(): # noqa: N802 assert fix == [AllVars(None)] -def test_MP_CFCl3_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_CFCl3_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_CFCl3_cav' + cubes_1d[0].units = 'kg' fix = get_allvars_fix('TRAC10hr', 'MP_CFCl3') - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1695,13 +2023,7 @@ def test_MP_CFCl3_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [5.982788e+09, 5.982657e+09], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [1.0]) def test_get_MP_ClOX_fix(): # noqa: N802 @@ -1710,10 +2032,12 @@ def test_get_MP_ClOX_fix(): # noqa: N802 assert fix == [AllVars(None)] -def test_MP_ClOX_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_ClOX_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_ClOX_cav' + cubes_1d[0].units = 'kg' fix = get_allvars_fix('TRAC10hr', 'MP_ClOX') - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1723,13 +2047,7 @@ def test_MP_ClOX_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [39589028.0, 39722044.0], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [1.0]) def test_get_MP_CH4_fix(): # noqa: N802 @@ -1738,10 +2056,12 @@ def test_get_MP_CH4_fix(): # noqa: N802 assert fix == [AllVars(None)] -def test_MP_CH4_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_CH4_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_CH4_cav' + cubes_1d[0].units = 'kg' fix = get_allvars_fix('TRAC10hr', 'MP_CH4') - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1751,13 +2071,7 @@ def test_MP_CH4_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [4.866472e+12, 4.866396e+12], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [1.0]) def test_get_MP_CO_fix(): # noqa: N802 @@ -1766,10 +2080,12 @@ def test_get_MP_CO_fix(): # noqa: N802 assert fix == [AllVars(None)] -def test_MP_CO_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_CO_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_CO_cav' + cubes_1d[0].units = 'kg' fix = get_allvars_fix('TRAC10hr', 'MP_CO') - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1779,13 +2095,7 @@ def test_MP_CO_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [3.399702e+11, 3.401483e+11], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [1.0]) def test_get_MP_CO2_fix(): # noqa: N802 @@ -1794,10 +2104,12 @@ def test_get_MP_CO2_fix(): # noqa: N802 assert fix == [AllVars(None)] -def test_MP_CO2_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_CO2_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_CO2_cav' + cubes_1d[0].units = 'kg' fix = get_allvars_fix('TRAC10hr', 'MP_CO2') - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1807,13 +2119,7 @@ def test_MP_CO2_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [2.855254e+15, 2.855380e+15], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [1.0]) def test_get_MP_DU_tot_fix(): # noqa: N802 @@ -1822,13 +2128,21 @@ def test_get_MP_DU_tot_fix(): # noqa: N802 assert fix == [MP_DU_tot(None), AllVars(None)] -def test_MP_DU_tot_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_DU_tot_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_DU_ai_cav' + cubes_1d[1].var_name = 'MP_DU_as_cav' + cubes_1d[2].var_name = 'MP_DU_ci_cav' + cubes_1d[3].var_name = 'MP_DU_cs_cav' + cubes_1d[0].units = 'kg' + cubes_1d[1].units = 'kg' + cubes_1d[2].units = 'kg' + cubes_1d[3].units = 'kg' vardef = get_var_info('EMAC', 'TRAC10hr', 'MP_DU_tot') extra_facets = get_extra_facets('EMAC', 'EMAC', 'TRAC10hr', 'MP_DU_tot', ()) fix = MP_DU_tot(vardef, extra_facets=extra_facets) - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) fix = get_allvars_fix('TRAC10hr', 'MP_DU_tot') fixed_cubes = fix.fix_metadata(fixed_cubes) @@ -1842,13 +2156,7 @@ def test_MP_DU_tot_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [1.797283e+10, 1.704390e+10], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [4.0]) def test_get_MP_N2O_fix(): # noqa: N802 @@ -1857,10 +2165,12 @@ def test_get_MP_N2O_fix(): # noqa: N802 assert fix == [AllVars(None)] -def test_MP_N2O_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_N2O_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_N2O_cav' + cubes_1d[0].units = 'kg' fix = get_allvars_fix('TRAC10hr', 'MP_N2O') - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1870,13 +2180,7 @@ def test_MP_N2O_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [2.365061e+12, 2.365089e+12], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [1.0]) def test_get_MP_NH3_fix(): # noqa: N802 @@ -1885,10 +2189,12 @@ def test_get_MP_NH3_fix(): # noqa: N802 assert fix == [AllVars(None)] -def test_MP_NH3_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_NH3_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_NH3_cav' + cubes_1d[0].units = 'kg' fix = get_allvars_fix('TRAC10hr', 'MP_NH3') - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1898,13 +2204,7 @@ def test_MP_NH3_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [1.931037e+08, 1.944860e+08], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [1.0]) def test_get_MP_NO_fix(): # noqa: N802 @@ -1913,10 +2213,12 @@ def test_get_MP_NO_fix(): # noqa: N802 assert fix == [AllVars(None)] -def test_MP_NO_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_NO_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_NO_cav' + cubes_1d[0].units = 'kg' fix = get_allvars_fix('TRAC10hr', 'MP_NO') - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1926,13 +2228,7 @@ def test_MP_NO_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [5.399146e+08, 5.543320e+08], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [1.0]) def test_get_MP_NO2_fix(): # noqa: N802 @@ -1941,10 +2237,12 @@ def test_get_MP_NO2_fix(): # noqa: N802 assert fix == [AllVars(None)] -def test_MP_NO2_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_NO2_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_NO2_cav' + cubes_1d[0].units = 'kg' fix = get_allvars_fix('TRAC10hr', 'MP_NO2') - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1954,13 +2252,7 @@ def test_MP_NO2_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [1.734202e+09, 1.725541e+09], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [1.0]) def test_get_MP_NOX_fix(): # noqa: N802 @@ -1969,10 +2261,12 @@ def test_get_MP_NOX_fix(): # noqa: N802 assert fix == [AllVars(None)] -def test_MP_NOX_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_NOX_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_NOX_cav' + cubes_1d[0].units = 'kg' fix = get_allvars_fix('TRAC10hr', 'MP_NOX') - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -1982,13 +2276,7 @@ def test_MP_NOX_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [9.384478e+08, 9.342440e+08], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [1.0]) def test_get_MP_O3_fix(): # noqa: N802 @@ -1997,10 +2285,12 @@ def test_get_MP_O3_fix(): # noqa: N802 assert fix == [AllVars(None)] -def test_MP_O3_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_O3_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_O3_cav' + cubes_1d[0].units = 'kg' fix = get_allvars_fix('TRAC10hr', 'MP_O3') - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -2010,13 +2300,7 @@ def test_MP_O3_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [3.339367e+12, 3.339434e+12], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [1.0]) def test_get_MP_OH_fix(): # noqa: N802 @@ -2025,10 +2309,12 @@ def test_get_MP_OH_fix(): # noqa: N802 assert fix == [AllVars(None)] -def test_MP_OH_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_OH_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_OH_cav' + cubes_1d[0].units = 'kg' fix = get_allvars_fix('TRAC10hr', 'MP_OH') - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -2038,13 +2324,7 @@ def test_MP_OH_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [3816360.8, 3820260.8], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [1.0]) def test_get_MP_S_fix(): # noqa: N802 @@ -2053,10 +2333,12 @@ def test_get_MP_S_fix(): # noqa: N802 assert fix == [AllVars(None)] -def test_MP_S_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_S_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_S_cav' + cubes_1d[0].units = 'kg' fix = get_allvars_fix('TRAC10hr', 'MP_S') - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -2066,13 +2348,7 @@ def test_MP_S_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [0.0, 0.0], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [1.0]) def test_get_MP_SO2_fix(): # noqa: N802 @@ -2081,10 +2357,12 @@ def test_get_MP_SO2_fix(): # noqa: N802 assert fix == [AllVars(None)] -def test_MP_SO2_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_SO2_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_SO2_cav' + cubes_1d[0].units = 'kg' fix = get_allvars_fix('TRAC10hr', 'MP_SO2') - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) assert len(fixed_cubes) == 1 cube = fixed_cubes[0] @@ -2094,13 +2372,7 @@ def test_MP_SO2_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [1.383063e+09, 1.390189e+09], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [1.0]) def test_get_MP_SO4mm_tot_fix(): # noqa: N802 @@ -2109,13 +2381,21 @@ def test_get_MP_SO4mm_tot_fix(): # noqa: N802 assert fix == [MP_SO4mm_tot(None), AllVars(None)] -def test_MP_SO4mm_tot_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_SO4mm_tot_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_SO4mm_ns_cav' + cubes_1d[1].var_name = 'MP_SO4mm_ks_cav' + cubes_1d[2].var_name = 'MP_SO4mm_as_cav' + cubes_1d[3].var_name = 'MP_SO4mm_cs_cav' + cubes_1d[0].units = 'kg' + cubes_1d[1].units = 'kg' + cubes_1d[2].units = 'kg' + cubes_1d[3].units = 'kg' vardef = get_var_info('EMAC', 'TRAC10hr', 'MP_SO4mm_tot') extra_facets = get_extra_facets('EMAC', 'EMAC', 'TRAC10hr', 'MP_SO4mm_tot', ()) fix = MP_SO4mm_tot(vardef, extra_facets=extra_facets) - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) fix = get_allvars_fix('TRAC10hr', 'MP_SO4mm_tot') fixed_cubes = fix.fix_metadata(fixed_cubes) @@ -2129,13 +2409,7 @@ def test_MP_SO4mm_tot_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) - - np.testing.assert_allclose( - cube.data, - [1.350434e+09, 1.364699e+09], - rtol=1e-5, - ) + np.testing.assert_allclose(cube.data, [4.0]) def test_get_MP_SS_tot_fix(): # noqa: N802 @@ -2144,13 +2418,19 @@ def test_get_MP_SS_tot_fix(): # noqa: N802 assert fix == [MP_SS_tot(None), AllVars(None)] -def test_MP_SS_tot_fix(cubes_tracer_pdef_gp): # noqa: N802 +def test_MP_SS_tot_fix(cubes_1d): # noqa: N802 """Test fix.""" + cubes_1d[0].var_name = 'MP_SS_ks_cav' + cubes_1d[1].var_name = 'MP_SS_as_cav' + cubes_1d[2].var_name = 'MP_SS_cs_cav' + cubes_1d[0].units = 'kg' + cubes_1d[1].units = 'kg' + cubes_1d[2].units = 'kg' vardef = get_var_info('EMAC', 'TRAC10hr', 'MP_SS_tot') extra_facets = get_extra_facets('EMAC', 'EMAC', 'TRAC10hr', 'MP_SS_tot', ()) fix = MP_SS_tot(vardef, extra_facets=extra_facets) - fixed_cubes = fix.fix_metadata(cubes_tracer_pdef_gp) + fixed_cubes = fix.fix_metadata(cubes_1d) fix = get_allvars_fix('TRAC10hr', 'MP_SS_tot') fixed_cubes = fix.fix_metadata(fixed_cubes) @@ -2164,85 +2444,261 @@ def test_MP_SS_tot_fix(cubes_tracer_pdef_gp): # noqa: N802 assert cube.units == 'kg' assert 'positive' not in cube.attributes - check_time(cube, n_points=2) + np.testing.assert_allclose(cube.data, [3.0]) + + +# Test 3D variables in extra_facets/emac-mappings.yml + + +def test_get_cl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'cl') + assert fix == [Cl(None), AllVars(None)] - np.testing.assert_allclose( - cube.data, - [2.322862e+08, 2.340771e+08], - rtol=1e-5, - ) +def test_cl_fix(cubes_3d): + """Test fix.""" + cubes_3d[0].var_name = 'aclcac_cav' + vardef = get_var_info('EMAC', 'Amon', 'cl') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'cl', ()) + fix = Cl(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_3d) + + fix = get_allvars_fix('Amon', 'cl') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'cl' + assert cube.standard_name == 'cloud_area_fraction_in_atmosphere_layer' + assert cube.long_name == 'Percentage Cloud Cover' + assert cube.units == '%' + assert 'positive' not in cube.attributes + + check_hybrid_z(cube) + + np.testing.assert_allclose(cube.data, [[[[200.0]], [[100.0]]]]) + + +def test_get_cli_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'cli') + assert fix == [AllVars(None)] + + +def test_cli_fix(cubes_3d): + """Test fix.""" + cubes_3d[0].var_name = 'xim1_cav' + cubes_3d[0].units = 'kg kg-1' + fix = get_allvars_fix('Amon', 'cli') + fixed_cubes = fix.fix_metadata(cubes_3d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'cli' + assert cube.standard_name == 'mass_fraction_of_cloud_ice_in_air' + assert cube.long_name == 'Mass Fraction of Cloud Ice' + assert cube.units == 'kg kg-1' + assert 'positive' not in cube.attributes + + check_hybrid_z(cube) + + np.testing.assert_allclose(cube.data, [[[[2.0]], [[1.0]]]]) + + +def test_get_clw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'clw') + assert fix == [AllVars(None)] + + +def test_clw_fix(cubes_3d): + """Test fix.""" + cubes_3d[0].var_name = 'xlm1_cav' + cubes_3d[0].units = 'kg kg-1' + fix = get_allvars_fix('Amon', 'clw') + fixed_cubes = fix.fix_metadata(cubes_3d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'clw' + assert cube.standard_name == 'mass_fraction_of_cloud_liquid_water_in_air' + assert cube.long_name == 'Mass Fraction of Cloud Liquid Water' + assert cube.units == 'kg kg-1' + assert 'positive' not in cube.attributes + + check_hybrid_z(cube) + + np.testing.assert_allclose(cube.data, [[[[2.0]], [[1.0]]]]) + + +def test_get_hur_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'hur') + assert fix == [AllVars(None)] + + +def test_hur_fix(cubes_3d): + """Test fix.""" + cubes_3d[0].var_name = 'rhum_p19_cav' + cubes_3d[0].units = '1' + fix = get_allvars_fix('Amon', 'hur') + fixed_cubes = fix.fix_metadata(cubes_3d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'hur' + assert cube.standard_name == 'relative_humidity' + assert cube.long_name == 'Relative Humidity' + assert cube.units == '%' + assert 'positive' not in cube.attributes -# Test each 3D variable with regular Z-coord in extra_facets/emac-mappings.yml + assert not cube.aux_factories + assert cube.coords('air_pressure') + np.testing.assert_allclose(cube.data, [[[[100.0]], [[200.0]]]]) -def test_get_ta_amon_fix(): + +def test_get_hus_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'hus') + assert fix == [AllVars(None)] + + +def test_hus_fix(cubes_3d): + """Test fix.""" + cubes_3d[0].var_name = 'qm1_p19_cav' + cubes_3d[0].units = '1' + fix = get_allvars_fix('Amon', 'hus') + fixed_cubes = fix.fix_metadata(cubes_3d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'hus' + assert cube.standard_name == 'specific_humidity' + assert cube.long_name == 'Specific Humidity' + assert cube.units == '1' + assert 'positive' not in cube.attributes + + assert not cube.aux_factories + assert cube.coords('air_pressure') + + np.testing.assert_allclose(cube.data, [[[[1.0]], [[2.0]]]]) + + +def test_get_ta_fix(): """Test getting of fix.""" fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'ta') assert fix == [AllVars(None)] -def test_ta_amon_fix(cubes_amon_3d): +def test_ta_fix(cubes_3d): """Test fix.""" + cubes_3d[0].var_name = 'tm1_p19_cav' + cubes_3d[0].units = 'K' fix = get_allvars_fix('Amon', 'ta') - fixed_cubes = fix.fix_metadata(cubes_amon_3d) + fixed_cubes = fix.fix_metadata(cubes_3d) - cube = check_ta_metadata(fixed_cubes) + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'ta' + assert cube.standard_name == 'air_temperature' + assert cube.long_name == 'Air Temperature' + assert cube.units == 'K' + assert 'positive' not in cube.attributes - fixed_cube = fix.fix_data(cube) + assert not cube.aux_factories + assert cube.coords('air_pressure') - check_time(fixed_cube) - check_plev(fixed_cube) - check_lat(fixed_cube) - check_lon(fixed_cube) + np.testing.assert_allclose(cube.data, [[[[1.0]], [[2.0]]]]) - np.testing.assert_allclose( - fixed_cube.data[0, -5:-2, 0, 0], - [2.512135e+02, 2.585062e+02, 2.662863e+02], - rtol=1e-5, - ) - np.testing.assert_equal( - fixed_cube.data.mask[0, -5:, 0, 0], - [False, False, False, True, True], - ) +def test_get_ua_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'ua') + assert fix == [AllVars(None)] + + +def test_ua_fix(cubes_3d): + """Test fix.""" + cubes_3d[0].var_name = 'um1_p19_cav' + cubes_3d[0].units = 'm s-1' + fix = get_allvars_fix('Amon', 'ua') + fixed_cubes = fix.fix_metadata(cubes_3d) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'ua' + assert cube.standard_name == 'eastward_wind' + assert cube.long_name == 'Eastward Wind' + assert cube.units == 'm s-1' + assert 'positive' not in cube.attributes + + assert not cube.aux_factories + assert cube.coords('air_pressure') -# Test each 3D variable with hybrid Z-coord in extra_facets/emac-mappings.yml + np.testing.assert_allclose(cube.data, [[[[1.0]], [[2.0]]]]) -def test_get_ta_cfmon_fix(): +def test_get_va_fix(): """Test getting of fix.""" - fix = Fix.get_fixes('EMAC', 'EMAC', 'CFmon', 'ta') + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'va') assert fix == [AllVars(None)] -def test_ta_cfmon_fix(test_data_path, tmp_path): +def test_va_fix(cubes_3d): """Test fix.""" - fix = get_allvars_fix('CFmon', 'ta') + cubes_3d[0].var_name = 'vm1_p19_cav' + cubes_3d[0].units = 'm s-1' + fix = get_allvars_fix('Amon', 'va') + fixed_cubes = fix.fix_metadata(cubes_3d) - filepath = test_data_path / 'emac_amon_3d.nc' - fixed_path = fix.fix_file(filepath, tmp_path) - cubes = iris.load(fixed_path) + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'va' + assert cube.standard_name == 'northward_wind' + assert cube.long_name == 'Northward Wind' + assert cube.units == 'm s-1' + assert 'positive' not in cube.attributes - assert cubes.extract(NameConstraint(var_name='hyam')) - assert cubes.extract(NameConstraint(var_name='hybm')) - assert cubes.extract(NameConstraint(var_name='hyai')) - assert cubes.extract(NameConstraint(var_name='hybi')) + assert not cube.aux_factories + assert cube.coords('air_pressure') - fixed_cubes = fix.fix_metadata(cubes) + np.testing.assert_allclose(cube.data, [[[[1.0]], [[2.0]]]]) + + +def test_get_zg_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'zg') + assert fix == [Zg(None), AllVars(None)] - cube = check_ta_metadata(fixed_cubes) - fixed_cube = fix.fix_data(cube) +def test_zg_fix(cubes_3d): + """Test fix.""" + cubes_3d[0].var_name = 'geopot_p19_cav' + cubes_3d[0].units = 'm2 s-2' + vardef = get_var_info('EMAC', 'Amon', 'zg') + extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'zg', ()) + fix = Zg(vardef, extra_facets=extra_facets) + fixed_cubes = fix.fix_metadata(cubes_3d) + + fix = get_allvars_fix('Amon', 'zg') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + assert len(fixed_cubes) == 1 + cube = fixed_cubes[0] + assert cube.var_name == 'zg' + assert cube.standard_name == 'geopotential_height' + assert cube.long_name == 'Geopotential Height' + assert cube.units == 'm' + assert 'positive' not in cube.attributes - check_time(fixed_cube) - check_alevel(fixed_cube) - check_lat(fixed_cube) - check_lon(fixed_cube) + assert not cube.aux_factories + assert cube.coords('air_pressure') np.testing.assert_allclose( - fixed_cube.data[0, 0:5, 0, 0], - [272.32098, 271.45898, 270.3698, 269.20953, 267.84683], + cube.data, + [[[[0.101971]], [[0.203943]]]], rtol=1e-5, ) @@ -2263,10 +2719,10 @@ def test_fix_file_no_alevel(mock_copyfile): # Test ``AllVars._fix_plev`` -def test_fix_plev_no_plev_coord(cubes_amon_3d): +def test_fix_plev_no_plev_coord(cubes_3d): """Test fix.""" # Create cube with Z-coord whose units are not convertible to Pa - cube = cubes_amon_3d.extract_cube(NameConstraint(var_name='tm1_p19_ave')) + cube = cubes_3d[0] z_coord = cube.coord(axis='Z') z_coord.var_name = 'height' z_coord.standard_name = 'height' @@ -2358,6 +2814,7 @@ def test_fix_invalid_units_predefined(): assert cube.units == 'kg m-2 s-1' assert cube.units.origin == 'kg m-2 s-1' assert 'positive' not in cube.attributes + np.testing.assert_allclose(cube.data, 1.0) @@ -2374,6 +2831,7 @@ def test_fix_invalid_units_fix_exponent(): assert cube.units == 'W m-2' assert cube.units.origin == 'W/m^2' assert cube.attributes['positive'] == 'down' + np.testing.assert_allclose(cube.data, 1.0) @@ -2389,6 +2847,7 @@ def test_fix_invalid_units_convert(): assert cube.long_name == 'Surface Downwelling Longwave Radiation' assert cube.units == 'W m-2' assert cube.attributes['positive'] == 'down' + np.testing.assert_allclose(cube.data, 1000.0) @@ -2400,23 +2859,3 @@ def test_fix_invalid_units_fail(): msg = "Failed to fix invalid units 'invalid_units' for variable 'rlds'" with pytest.raises(ValueError, match=msg): fix._fix_var_metadata(cube) - - -# Test error message if variable not available in cube - - -def test_var_not_available_ta(cubes_amon_2d): - """Test fix.""" - fix = get_allvars_fix('Amon', 'ta') - msg = ("Variable 'tm1_p19_ave' used to extract 'ta' is not available in " - "input file") - with pytest.raises(ValueError, match=msg): - fix.fix_metadata(cubes_amon_2d) - - -def test_var_not_available_ps(cubes_amon_2d): - """Test fix.""" - fix = get_allvars_fix('Amon', 'ps') - msg = "Variable 'x' used to extract 'ps' is not available in input file" - with pytest.raises(ValueError, match=msg): - fix.get_cube(cubes_amon_2d, var_name='x') diff --git a/tests/integration/cmor/_fixes/test_data/emac_aermon.nc b/tests/integration/cmor/_fixes/test_data/emac_aermon.nc deleted file mode 100644 index 61f7931fd066a119501af680c34412d859fd9d5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52948 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 diff --git a/tests/integration/cmor/_fixes/test_data/emac_amon_2d.nc b/tests/integration/cmor/_fixes/test_data/emac_amon_2d.nc deleted file mode 100644 index 9a482a172d9954aa5c98850b245e9c8cea0420ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29828 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 diff --git a/tests/integration/cmor/_fixes/test_data/emac_amon_3d.nc b/tests/integration/cmor/_fixes/test_data/emac_amon_3d.nc deleted file mode 100644 index dc2d7552342b60d8e67e5f1e5b12660a183a4ea4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65324 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઩kpB+c*3`E2KhZ`rqNde{!8Y3zct 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 diff --git a/tests/integration/cmor/_fixes/test_data/emac_tracer_pdef_gp.nc b/tests/integration/cmor/_fixes/test_data/emac_tracer_pdef_gp.nc deleted file mode 100644 index 145ba8c08b11211e75194f1a2f89835748b295a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40936 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 Wh(;h9foKGx5r{@08iD^$5%?c4CV=?> From 8445bc0e96182b2d5b5907986889ffc62d7e31ca Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Fri, 20 May 2022 16:17:11 +0200 Subject: [PATCH 39/52] Removed unused custom CMOR tables --- .../cmor/tables/custom/CMOR_cod_sw_b01.dat | 23 ------------------- esmvalcore/cmor/tables/custom/CMOR_lnox.dat | 23 ------------------- esmvalcore/cmor/tables/custom/CMOR_rldt.dat | 23 ------------------- esmvalcore/cmor/tables/custom/CMOR_rlnt.dat | 22 ------------------ 4 files changed, 91 deletions(-) delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_cod_sw_b01.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_lnox.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_rldt.dat delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_rlnt.dat diff --git a/esmvalcore/cmor/tables/custom/CMOR_cod_sw_b01.dat b/esmvalcore/cmor/tables/custom/CMOR_cod_sw_b01.dat deleted file mode 100644 index dfc38f6085..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_cod_sw_b01.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: cod_sw_b01 -!============ -modeling_realm: atmos -frequency: mon -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: 1 -cell_methods: time: mean -cell_measures: area: areacella -long_name: cloud optical depth averaged over shortwave band 01 -comment: averaged over cloudy and cloud-free time steps -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: cod_sw_b01 -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_lnox.dat b/esmvalcore/cmor/tables/custom/CMOR_lnox.dat deleted file mode 100644 index a82cec2b54..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_lnox.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP6 -!============ -variable_entry: lnox -!============ -modeling_realm: atmos -frequency: 10hr -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: kg -cell_methods: time: mean -cell_measures: area: areacella -long_name: total NOx lightning emissions -comment: sum of cloud-to-ground (GC) and intra-cloud (IC); given in kg(N) -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: time -out_name: lnox -type: real -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_rldt.dat b/esmvalcore/cmor/tables/custom/CMOR_rldt.dat deleted file mode 100644 index 92b7114e76..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_rldt.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP5 -!============ -variable_entry: rldt -!============ -modeling_realm: atmos -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: W m-2 -cell_methods: time: mean -cell_measures: area: areacella -long_name: TOA Incident Longwave Radiation -comment: Longwave radiation incident at the top of the atmosphere -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -out_name: rldt -type: real -positive: down -!---------------------------------- -! diff --git a/esmvalcore/cmor/tables/custom/CMOR_rlnt.dat b/esmvalcore/cmor/tables/custom/CMOR_rlnt.dat deleted file mode 100644 index a4b27e4302..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_rlnt.dat +++ /dev/null @@ -1,22 +0,0 @@ -SOURCE: CMIP5 -!============ -variable_entry: rlnt -!============ -modeling_realm: atmos -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: -units: W m-2 -cell_methods: time: mean -cell_measures: area: areacella -long_name: TOA Net downward Longwave Radiation -comment: at the top of the atmosphere (to be compared with satellite measurements) -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -type: real -positive: up -!---------------------------------- -! \ No newline at end of file From d305c5a0539bb96fdba3c34eca31bae92d951d5f Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Fri, 20 May 2022 16:34:41 +0200 Subject: [PATCH 40/52] Added EMAC sample file --- tests/integration/cmor/_fixes/test_data/emac.nc | Bin 0 -> 28040 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/integration/cmor/_fixes/test_data/emac.nc diff --git a/tests/integration/cmor/_fixes/test_data/emac.nc b/tests/integration/cmor/_fixes/test_data/emac.nc new file mode 100644 index 0000000000000000000000000000000000000000..a1ffe4f03b7b60e6b04c277b45e4693c93b3385a GIT binary patch literal 28040 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 Date: Mon, 23 May 2022 15:44:45 +0200 Subject: [PATCH 41/52] Fixed od550aer fix for EMAC --- esmvalcore/cmor/_fixes/emac/_base_fixes.py | 11 ----------- esmvalcore/cmor/_fixes/emac/emac.py | 13 +++++++++++-- tests/integration/cmor/_fixes/emac/test_emac.py | 10 +++------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/esmvalcore/cmor/_fixes/emac/_base_fixes.py b/esmvalcore/cmor/_fixes/emac/_base_fixes.py index d6639d2668..6da3d22573 100644 --- a/esmvalcore/cmor/_fixes/emac/_base_fixes.py +++ b/esmvalcore/cmor/_fixes/emac/_base_fixes.py @@ -2,7 +2,6 @@ import logging -import iris.analysis from iris import NameConstraint from iris.exceptions import ConstraintMismatchError @@ -62,13 +61,3 @@ def fix_metadata(self, cubes): cube = self.get_cube(cubes) cube.units = '1' return cubes - - -class SetUnitsTo1SumOverZ(SetUnitsTo1): - """Base fix to set units to '1' and sum over Z-coordinate.""" - - def fix_data(self, cube): - """Fix data.""" - z_coord = cube.coord(axis='Z') - cube = cube.collapsed(z_coord, iris.analysis.SUM) - return cube diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py index 495526472b..8fd7e06de0 100644 --- a/esmvalcore/cmor/_fixes/emac/emac.py +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -29,7 +29,7 @@ add_scalar_lambda550nm_coord, add_scalar_typesi_coord, ) -from ._base_fixes import EmacFix, NegateData, SetUnitsTo1, SetUnitsTo1SumOverZ +from ._base_fixes import EmacFix, NegateData, SetUnitsTo1 logger = logging.getLogger(__name__) @@ -333,7 +333,16 @@ def fix_metadata(self, cubes): Hurs = SetUnitsTo1 -Od550aer = SetUnitsTo1SumOverZ +class Od550aer(SetUnitsTo1): + """Fixes for ``od550aer``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cubes = super().fix_metadata(cubes) + cube = self.get_cube(cubes) + z_coord = cube.coord(axis='Z') + cube = cube.collapsed(z_coord, iris.analysis.SUM) + return CubeList([cube]) class Pr(EmacFix): diff --git a/tests/integration/cmor/_fixes/emac/test_emac.py b/tests/integration/cmor/_fixes/emac/test_emac.py index baf77ef27b..80178c5a98 100644 --- a/tests/integration/cmor/_fixes/emac/test_emac.py +++ b/tests/integration/cmor/_fixes/emac/test_emac.py @@ -727,7 +727,7 @@ def test_sample_data_tas(test_data_path, tmp_path): fixed_path = fix.fix_file(filepath, tmp_path) assert fixed_path == filepath - cubes = iris.load(fixed_path) + cubes = iris.load(str(fixed_path)) fixed_cubes = fix.fix_metadata(cubes) cube = check_tas_metadata(fixed_cubes) @@ -758,7 +758,7 @@ def test_sample_data_ta_plev(test_data_path, tmp_path): fixed_path = fix.fix_file(filepath, tmp_path) assert fixed_path == filepath - cubes = iris.load(fixed_path) + cubes = iris.load(str(fixed_path)) fixed_cubes = fix.fix_metadata(cubes) cube = check_ta_metadata(fixed_cubes) @@ -786,7 +786,7 @@ def test_sample_data_ta_alevel(test_data_path, tmp_path): fixed_path = fix.fix_file(filepath, tmp_path) assert fixed_path != filepath - cubes = iris.load(fixed_path) + cubes = iris.load(str(fixed_path)) assert cubes.extract(NameConstraint(var_name='hyam')) assert cubes.extract(NameConstraint(var_name='hybm')) assert cubes.extract(NameConstraint(var_name='hyai')) @@ -1073,7 +1073,6 @@ def test_get_od550aer_fix(): def test_od550aer_fix(cubes_3d): """Test fix.""" cubes_3d[0].var_name = 'aot_opt_TOT_550_total_cav' - cubes_3d[0].units = '1' vardef = get_var_info('EMAC', 'Amon', 'od550aer') extra_facets = get_extra_facets('EMAC', 'EMAC', 'Amon', 'od550aer', ()) fix = Od550aer(vardef, extra_facets=extra_facets) @@ -1084,9 +1083,6 @@ def test_od550aer_fix(cubes_3d): assert len(fixed_cubes) == 1 cube = fixed_cubes[0] - - cube = fix.fix_data(cube) - assert cube.var_name == 'od550aer' assert cube.standard_name == ('atmosphere_optical_thickness_due_to_' 'ambient_aerosol_particles') From 10afa38b23a5d9336b05d56fbc0bfe8150891efe Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Mon, 23 May 2022 15:56:07 +0200 Subject: [PATCH 42/52] Finalized EMAC mappings --- esmvalcore/_config/extra_facets/emac-mappings.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml index 55582673d9..1cfb60bb0f 100644 --- a/esmvalcore/_config/extra_facets/emac-mappings.yml +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -153,9 +153,6 @@ EMAC: ts: raw_name: [tsurf_cav, tsurf_ave] channel: Amon - - # TODO: CHECK IF THE CALCULATION IS CORRECT!!! - # -> it might be necessary to weight these fields with cos(lat) uas: raw_name: [u10_cav, u10_ave] channel: Amon From de24cfb7754ae7248c3f498282bae4e717d8bf96 Mon Sep 17 00:00:00 2001 From: Valeriu Predoi Date: Mon, 23 May 2022 16:16:44 +0100 Subject: [PATCH 43/52] figure out where and what is in test data path on Circle --- tests/integration/cmor/_fixes/cmip6/test_cesm2.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/integration/cmor/_fixes/cmip6/test_cesm2.py b/tests/integration/cmor/_fixes/cmip6/test_cesm2.py index 212902dfed..d91f42fde8 100644 --- a/tests/integration/cmor/_fixes/cmip6/test_cesm2.py +++ b/tests/integration/cmor/_fixes/cmip6/test_cesm2.py @@ -70,6 +70,10 @@ def test_get_cl_fix(): def test_cl_fix_file(mock_get_filepath, tmp_path, test_data_path): """Test ``fix_file`` for ``cl``.""" nc_path = test_data_path / 'cesm2_cl.nc' + print("TEST DATA PATH CESM2", test_data_path) + for i, j, k in os.walk(test_data_path): + print(i, j, k) + print(x) cubes = iris.load(str(nc_path)) # Raw cubes From 2bc71e5498f5c361830b36e6e4a464e7c8447ed5 Mon Sep 17 00:00:00 2001 From: Valeriu Predoi Date: Mon, 23 May 2022 16:33:32 +0100 Subject: [PATCH 44/52] turn off sequential decorator for now --- tests/integration/cmor/_fixes/cmip6/test_cesm2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/cmor/_fixes/cmip6/test_cesm2.py b/tests/integration/cmor/_fixes/cmip6/test_cesm2.py index d91f42fde8..a95f24bbf6 100644 --- a/tests/integration/cmor/_fixes/cmip6/test_cesm2.py +++ b/tests/integration/cmor/_fixes/cmip6/test_cesm2.py @@ -61,7 +61,7 @@ def test_get_cl_fix(): [7.0, 25.0]]]]]) -@pytest.mark.sequential +# @pytest.mark.sequential @pytest.mark.skipif(sys.version_info < (3, 7, 6), reason="requires python3.7.6 or newer") @unittest.mock.patch( From e9b21f033b6d5e221901eabcf8927540a9d90e0c Mon Sep 17 00:00:00 2001 From: Valeriu Predoi Date: Mon, 23 May 2022 16:43:26 +0100 Subject: [PATCH 45/52] restore test module --- tests/integration/cmor/_fixes/cmip6/test_cesm2.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/integration/cmor/_fixes/cmip6/test_cesm2.py b/tests/integration/cmor/_fixes/cmip6/test_cesm2.py index a95f24bbf6..212902dfed 100644 --- a/tests/integration/cmor/_fixes/cmip6/test_cesm2.py +++ b/tests/integration/cmor/_fixes/cmip6/test_cesm2.py @@ -61,7 +61,7 @@ def test_get_cl_fix(): [7.0, 25.0]]]]]) -# @pytest.mark.sequential +@pytest.mark.sequential @pytest.mark.skipif(sys.version_info < (3, 7, 6), reason="requires python3.7.6 or newer") @unittest.mock.patch( @@ -70,10 +70,6 @@ def test_get_cl_fix(): def test_cl_fix_file(mock_get_filepath, tmp_path, test_data_path): """Test ``fix_file`` for ``cl``.""" nc_path = test_data_path / 'cesm2_cl.nc' - print("TEST DATA PATH CESM2", test_data_path) - for i, j, k in os.walk(test_data_path): - print(i, j, k) - print(x) cubes = iris.load(str(nc_path)) # Raw cubes From 529969d2810725477021d0ad49d8b8ef43ab5005 Mon Sep 17 00:00:00 2001 From: Bouwe Andela Date: Mon, 23 May 2022 19:39:35 +0200 Subject: [PATCH 46/52] Install git and ssh for correct checkout --- .circleci/config.yml | 9 +++++---- docker/Dockerfile.dev | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 231c5a1ab7..d31cc9df89 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -58,6 +58,11 @@ commands: type: string default: "" steps: + - run: + name: Install git+ssh + environment: + DEBIAN_FRONTEND: noninteractive # needed to install tzdata + command: apt update && apt install -y git ssh - checkout - check_changes - run: @@ -124,10 +129,6 @@ jobs: - /root/.cache/pip - .mypy_cache - .pytest_cache - - run: - # Install curl for codecov upload - when: always - command: apt update && apt install -y curl - codecov/upload: when: always file: 'test-reports/coverage.xml' diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index 4d5e42ed20..ef0d3df52c 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -1,8 +1,10 @@ # To build this container, go to ESMValCore root folder and execute: +# This container is used to run the tests on CircleCI. # docker build -t esmvalcore:development . -f docker/Dockerfile.dev FROM condaforge/mambaforge WORKDIR /src/ESMValCore +RUN apt update && DEBIAN_FRONTEND=noninteractive apt install curl git ssh && apt clean COPY environment.yml . RUN mamba update -y conda mamba pip && mamba env create --name esmvaltool --file environment.yml && conda clean --all -y From e6683b297de00bdbb226029b6227aa22e58acd8d Mon Sep 17 00:00:00 2001 From: Bouwe Andela Date: Mon, 23 May 2022 19:41:11 +0200 Subject: [PATCH 47/52] Fix apt install command --- docker/Dockerfile.dev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index ef0d3df52c..3e9165cd5b 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -4,7 +4,7 @@ FROM condaforge/mambaforge WORKDIR /src/ESMValCore -RUN apt update && DEBIAN_FRONTEND=noninteractive apt install curl git ssh && apt clean +RUN apt update && DEBIAN_FRONTEND=noninteractive apt install -y curl git ssh && apt clean COPY environment.yml . RUN mamba update -y conda mamba pip && mamba env create --name esmvaltool --file environment.yml && conda clean --all -y From 0c6db0d7f9958d7267427b92c2256e7364398872 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Mon, 30 May 2022 13:10:12 +0200 Subject: [PATCH 48/52] Added doc for EMAC CMORizer --- doc/quickstart/find_data.rst | 66 +++++++++++++++++++ .../_config/extra_facets/emac-mappings.yml | 18 ++--- 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/doc/quickstart/find_data.rst b/doc/quickstart/find_data.rst index bbe01fed20..93fed93f49 100644 --- a/doc/quickstart/find_data.rst +++ b/doc/quickstart/find_data.rst @@ -139,6 +139,72 @@ The following models are natively supported by ESMValCore. In contrast to the native observational datasets listed above, they use dedicated projects instead of the project ``native6``. +.. _read_emac: + +EMAC +^^^^ + +ESMValTool is able to read native `EMAC +` (using the +default DRS ``drs: default`` in the :ref:`user configuration file`). + +Thus, example dataset entries could look like this: + +.. code-block:: yaml + + datasets: + - {project: EMAC, dataset: EMAC, exp: historical, mip: Amon, short_name: tas, start_year: 2000, end_year: 2014} + - {project: EMAC, dataset: EMAC, exp: historical, mip: Omon, short_name: tos, postproc_flag: "-p-mm", start_year: 2000, end_year: 2014} + - {project: EMAC, dataset: EMAC, exp: historical, mip: Amon, short_name: ta, raw_name: tm1_p39_cav, start_year: 2000, end_year: 2014} + +Please note the duplication of the name ``EMAC`` in ``project`` and +``dataset``, which is necessary to comply with ESMValTool's data finding and +CMORizing functionalities. + +Similar to any other fix, the EMAC fix allows the use of :ref:`extra +facets`. +By default, the file :download:`emac-mappings.yml +` is used for that +purpose. +For some variables, extra facets are necessary; otherwise ESMValTool cannot +read them properly. +Supported keys for extra facets are: + +============================= ================================================== ================================= +Key Description Default value if not specified +============================= ================================================== ================================= +``channel`` Channel in which the desired variable is stored No default (needs to be specified + in extra facets or recipe if + default DRS is used) +``postproc_flag`` Postprocessing flag of the (e.g., ``-p-mm``) ``''`` (empty string) +``raw_name`` Variable name of the variable in the raw input CMOR variable name of the + file. To support single and multiple raw names for corresponding variable. + a variable, ``raw_name`` can be given as ``str`` + and ``list``. In the latter case, the + prioritization is given by the order of the list; + if possible, use the first entry, if this is not + present, use the second, etc. This is + particularly useful for variables where regular + averages (``*_ave``) or conditional averages + (``*_cav``) exist. For 3D variables defined on + pressure levels, only the pressure levels defined + by the CMOR table (e.g., for `Amon`'s `ta`: + ``tm1_p19_cav`` and ``tm1_p19_ave``) are given. + If other pressure levels are desired, e.g., + ``tm1_p39_cav``, this has to be explicitly + specified in the recipe using ``raw_name: + tm1_p39_cav`` or ``raw_name: [tm1_p19_cav, + tm1_p39_cav]``. +============================= ================================================== ================================= + .. _read_icon: ICON diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml index 1cfb60bb0f..ca14f32166 100644 --- a/esmvalcore/_config/extra_facets/emac-mappings.yml +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -9,15 +9,15 @@ # - The facets ``channel`` and ``postproc_flag`` have to be specified in the # recipe if they are not given here. # - If ``raw_name`` is omitted and no derivation in the EMAC fix is given, the -# CMOR short_name is used by default. To accept single and multiple raw names -# for a variable, ``raw_name`` can be given as str and list. In the latter -# case, the prioritization is given by the order of the list; if possible, -# use the first entry, if this is not present, use the second, etc. This is -# particularly useful for variables where regular averages ("*_ave") or -# conditional averages ("*_cav") exist. For 3D variables defined on pressure -# levels, only the pressure levels defined by the CMOR table (e.g., for -# Amon's ta: "tm1_p19_cav" and "tm1_p19_ave") are given. If other pressure -# levels are desired, e.g., "tm1_p39_cav", this has to be explicitly +# CMOR short_name is used by default. To support single and multiple raw +# names for a variable, ``raw_name`` can be given as str and list. In the +# latter case, the prioritization is given by the order of the list; if +# possible, use the first entry, if this is not present, use the second, etc. +# This is particularly useful for variables where regular averages ("*_ave") +# or conditional averages ("*_cav") exist. For 3D variables defined on +# pressure levels, only the pressure levels defined by the CMOR table (e.g., +# for Amon's ta: "tm1_p19_cav" and "tm1_p19_ave") are given. If other +# pressure levels are desired, e.g., "tm1_p39_cav", this has to be explicitly # specified in the recipe using "raw_name: tm1_p39_cav" or "raw_name: # [tm1_p19_cav, tm1_p39_cav]". # - Asterisks ("*") in the comments in list below refer to either "cav" or From fd6e872a179fa1b15dc7281d2bdbcb45a51fdb31 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Mon, 30 May 2022 15:47:42 +0200 Subject: [PATCH 49/52] Fixed doc --- doc/quickstart/find_data.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/quickstart/find_data.rst b/doc/quickstart/find_data.rst index 93fed93f49..09e5a1d069 100644 --- a/doc/quickstart/find_data.rst +++ b/doc/quickstart/find_data.rst @@ -145,7 +145,7 @@ EMAC ^^^^ ESMValTool is able to read native `EMAC -`_ model output. The default naming conventions for input directories and files for EMAC are From 4c916ef118ad5d6bb697cb6d1e560765748247a0 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Mon, 30 May 2022 16:08:56 +0200 Subject: [PATCH 50/52] Improved doc --- doc/quickstart/find_data.rst | 53 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/doc/quickstart/find_data.rst b/doc/quickstart/find_data.rst index 09e5a1d069..0627ba5929 100644 --- a/doc/quickstart/find_data.rst +++ b/doc/quickstart/find_data.rst @@ -178,32 +178,33 @@ For some variables, extra facets are necessary; otherwise ESMValTool cannot read them properly. Supported keys for extra facets are: -============================= ================================================== ================================= -Key Description Default value if not specified -============================= ================================================== ================================= -``channel`` Channel in which the desired variable is stored No default (needs to be specified - in extra facets or recipe if - default DRS is used) -``postproc_flag`` Postprocessing flag of the (e.g., ``-p-mm``) ``''`` (empty string) -``raw_name`` Variable name of the variable in the raw input CMOR variable name of the - file. To support single and multiple raw names for corresponding variable. - a variable, ``raw_name`` can be given as ``str`` - and ``list``. In the latter case, the - prioritization is given by the order of the list; - if possible, use the first entry, if this is not - present, use the second, etc. This is - particularly useful for variables where regular - averages (``*_ave``) or conditional averages - (``*_cav``) exist. For 3D variables defined on - pressure levels, only the pressure levels defined - by the CMOR table (e.g., for `Amon`'s `ta`: - ``tm1_p19_cav`` and ``tm1_p19_ave``) are given. - If other pressure levels are desired, e.g., - ``tm1_p39_cav``, this has to be explicitly - specified in the recipe using ``raw_name: - tm1_p39_cav`` or ``raw_name: [tm1_p19_cav, - tm1_p39_cav]``. -============================= ================================================== ================================= +==================== ====================================== ================================= +Key Description Default value if not specified +==================== ====================================== ================================= +``channel`` Channel in which the desired variable No default (needs to be specified + is stored in extra facets or recipe if + default DRS is used) +``postproc_flag`` Postprocessing flag of the data ``''`` (empty string) +``raw_name`` Variable name of the variable in the CMOR variable name of the + raw input file corresponding variable. +==================== ====================================== ================================= + +.. note:: + + ``raw_name`` can be given as ``str`` or ``list``. + The latter is used to support multiple different variables names in the + input file. + In this case, the prioritization is given by the order of the list; if + possible, use the first entry, if this is not present, use the second, etc. + This is particularly useful for files in which regular averages (``*_ave``) + or conditional averages (``*_cav``) exist. + + For 3D variables defined on pressure levels, only the pressure levels + defined by the CMOR table (e.g., for `Amon`'s `ta`: ``tm1_p19_cav`` and + ``tm1_p19_ave``) are given in the default extra facets file. + If other pressure levels are desired, e.g., ``tm1_p39_cav``, this has to be + explicitly specified in the recipe using ``raw_name: tm1_p39_cav`` or + ``raw_name: [tm1_p19_cav, tm1_p39_cav]``. .. _read_icon: From ace248b34e89909b3c12bb73bcef99163d0c43cd Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Mon, 30 May 2022 16:23:46 +0200 Subject: [PATCH 51/52] Improved doc --- doc/quickstart/find_data.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/quickstart/find_data.rst b/doc/quickstart/find_data.rst index 0627ba5929..4b813dcaff 100644 --- a/doc/quickstart/find_data.rst +++ b/doc/quickstart/find_data.rst @@ -186,7 +186,7 @@ Key Description Default value if not default DRS is used) ``postproc_flag`` Postprocessing flag of the data ``''`` (empty string) ``raw_name`` Variable name of the variable in the CMOR variable name of the - raw input file corresponding variable. + raw input file corresponding variable ==================== ====================================== ================================= .. note:: From bd0b9af4f13333fc5c1353b226c3819ec264b016 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Tue, 31 May 2022 11:01:12 +0200 Subject: [PATCH 52/52] Fixed bug in load where ignored warnings are added for each call of load() --- esmvalcore/preprocessor/_io.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/esmvalcore/preprocessor/_io.py b/esmvalcore/preprocessor/_io.py index 7b0ead0291..d0d1339e8a 100644 --- a/esmvalcore/preprocessor/_io.py +++ b/esmvalcore/preprocessor/_io.py @@ -139,6 +139,12 @@ def load(file, callback=None, ignore_warnings=None): logger.debug("Loading:\n%s", file) if ignore_warnings is None: ignore_warnings = [] + + # Avoid duplication of ignored warnings when load() is called more often + # than once + ignore_warnings = list(ignore_warnings) + + # Default warnings ignored for every dataset ignore_warnings.append({ 'message': "Missing CF-netCDF measure variable .*", 'category': UserWarning, @@ -149,6 +155,8 @@ def load(file, callback=None, ignore_warnings=None): 'category': UserWarning, 'module': 'iris', }) + + # Filter warnings with catch_warnings(): for warning_kwargs in ignore_warnings: warning_kwargs.setdefault('action', 'ignore')