diff --git a/doc/quickstart/configure.rst b/doc/quickstart/configure.rst index cf62f11fec..14a6d4a21f 100644 --- a/doc/quickstart/configure.rst +++ b/doc/quickstart/configure.rst @@ -520,6 +520,38 @@ 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 +---------------------------- + +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 handled by +Iris. +This can be configured in the ``config-developer.yml`` file for some steps of +the preprocessing chain. + +Currently supported preprocessor steps: + +* :func:`~esmvalcore.preprocessor.load` + +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} + +The keyword arguments specified in the list items are directly passed to +:func:`warnings.filterwarnings` in addition to ``action=ignore`` (may be +overwritten in ``config-developer.yml``). + .. _configure_native_models: Configuring datasets in native format diff --git a/doc/quickstart/find_data.rst b/doc/quickstart/find_data.rst index bbe01fed20..4b813dcaff 100644 --- a/doc/quickstart/find_data.rst +++ b/doc/quickstart/find_data.rst @@ -139,6 +139,73 @@ 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 +`_ +model output. + +The default naming conventions for input directories and files for EMAC are + +* input directories: ``[exp]/[channel]`` +* input files: ``[exp]*[channel][postproc_flag].nc`` + +as configured in the :ref:`config-developer file ` (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 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: ICON diff --git a/esmvalcore/_config/extra_facets/emac-mappings.yml b/esmvalcore/_config/extra_facets/emac-mappings.yml new file mode 100644 index 0000000000..ca14f32166 --- /dev/null +++ b/esmvalcore/_config/extra_facets/emac-mappings.yml @@ -0,0 +1,358 @@ +# Extra facets for native EMAC model output + +# All extra facets for EMAC are optional but might be necessary for some +# 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 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 +# "ave". "cav" is prioritized. + +# A complete list of supported keys is given in the documentation (see +# ESMValCore/doc/quickstart/find_data.rst). +--- +'*': + '*': + '*': + postproc_flag: '' + +EMAC: + + # 1D/2D dynamical/meteorological variables + '*': + awhea: # non-CMOR variable + raw_name: [awhea_cav, awhea_ave] + channel: Omon + clivi: + raw_name: [xivi_cav, xivi_ave] + channel: Amon + clt: + raw_name: [aclcov_cav, aclcov_ave] + channel: Amon + clwvi: # derived from xlvi_*, xivi_* + channel: Amon + co2mass: + raw_name: [MP_CO2_cav, MP_CO2_ave] + channel: tracer_pdef_gp + evspsbl: + raw_name: [evap_cav, evap_ave] + channel: Amon + hfls: + raw_name: [ahfl_cav, ahfl_ave] + channel: Amon + hfns: # ESMValCore-derivation + channel: Amon + hfss: + raw_name: [ahfs_cav, ahfs_ave] + channel: Amon + hurs: + raw_name: [rh_2m_cav, rh_2m_ave] + channel: Amon + od550aer: + raw_name: [aot_opt_TOT_550_total_cav, aot_opt_TOT_550_total_ave] + channel: AERmon + pr: # derived from aprl_*, aprc_*, aprs_* + channel: Amon + prc: + raw_name: [aprc_cav, aprc_ave] + channel: Amon + prl: # non-CMOR variable + raw_name: [aprl_cav, aprl_ave] + channel: Amon + prsn: + raw_name: [aprs_cav, aprs_ave] + channel: Amon + prw: + raw_name: [qvi_cav, qvi_ave] + channel: Amon + ps: + raw_name: [aps_cav, aps_ave] + channel: Amon + psl: + raw_name: [slp_cav, slp_ave] + channel: Amon + rlds: # derived from flxtbot_*, tradsu_* + channel: Amon + rlns: # ESMValCore-derivation + channel: Amon + rlus: + raw_name: [tradsu_cav, tradsu_ave] + channel: Amon + rlut: + raw_name: [flxttop_cav, flxttop_ave] + channel: Amon + rlutcs: + raw_name: [flxtftop_cav, flxtftop_ave] + channel: Amon + rsds: # derived from flxsbot_*, sradsu_* + channel: Amon + rsdt: # derived from flxstop_*, srad0u_* + channel: Amon + rsns: # ESMValCore-derivation + channel: Amon + rsnt: # ESMValCore-derivation + channel: Amon + rsus: + raw_name: [sradsu_cav, sradsu_ave] + channel: Amon + rsut: + raw_name: [srad0u_cav, srad0u_ave] + channel: Amon + rsutcs: + raw_name: [flxusftop_cav, flxusftop_ave] + channel: Amon + rtmt: # derived from flxttop_*, flxstop_* + channel: Amon + sfcWind: + raw_name: [wind10_cav, wind10_ave] + channel: Amon + siconc: + raw_name: [seaice_cav, seaice_ave] + channel: Amon + siconca: + raw_name: [seaice_cav, seaice_ave] + channel: Amon + sithick: + raw_name: [siced_cav, siced_ave] + channel: Amon + tas: + raw_name: [temp2_cav, temp2_ave] + channel: Amon + tasmax: + raw_name: temp2_max + channel: Amon + tasmin: + raw_name: temp2_min + channel: Amon + tauu: + raw_name: [ustr_cav, ustr_ave] + channel: Amon + tauv: + raw_name: [vstr_cav, vstr_ave] + channel: Amon + tos: + raw_name: tsw + channel: g3b + toz: + channel: column + ts: + raw_name: [tsurf_cav, tsurf_ave] + channel: Amon + uas: + raw_name: [u10_cav, u10_ave] + channel: Amon + vas: + raw_name: [v10_cav, v10_ave] + channel: Amon + + # Tracers (non-CMOR variables) + 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_cav, MP_CFCl3_ave] + channel: tracer_pdef_gp + MP_ClOX: + raw_name: [MP_ClOX_cav, MP_ClOX_ave] + channel: tracer_pdef_gp + MP_CH4: + raw_name: [MP_CH4_cav, MP_CH4_ave] + channel: tracer_pdef_gp + MP_CO: + raw_name: [MP_CO_cav, MP_CO_ave] + channel: tracer_pdef_gp + MP_CO2: + raw_name: [MP_CO2_cav, MP_CO2_ave] + channel: tracer_pdef_gp + 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_cav, MP_N2O_ave] + channel: tracer_pdef_gp + MP_NH3: + raw_name: [MP_NH3_cav, MP_NH3_ave] + channel: tracer_pdef_gp + MP_NO: + raw_name: [MP_NO_cav, MP_NO_ave] + channel: tracer_pdef_gp + MP_NO2: + raw_name: [MP_NO2_cav, MP_NO2_ave] + channel: tracer_pdef_gp + MP_NOX: + raw_name: [MP_NOX_cav, MP_NOX_ave] + channel: tracer_pdef_gp + MP_O3: + raw_name: [MP_O3_cav, MP_O3_ave] + channel: tracer_pdef_gp + MP_OH: + raw_name: [MP_OH_cav, MP_OH_ave] + channel: tracer_pdef_gp + MP_S: + raw_name: [MP_S_cav, MP_S_ave] + channel: tracer_pdef_gp + MP_SO2: + raw_name: [MP_SO2_cav, MP_SO2_ave] + channel: tracer_pdef_gp + 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_*, MP_SS_as_*, MP_SS_cs_* + channel: tracer_pdef_gp + + # 3D dynamical/meteorological variables + 6hrLev: + ta: + raw_name: [tm1_cav, tm1_ave] + channel: Amon + ua: + raw_name: [um1_cav, um1_ave] + channel: Amon + va: + raw_name: [vm1_cav, vm1_ave] + channel: Amon + AERmon: + ua: + raw_name: [um1_cav, um1_ave] + channel: Amon + va: + raw_name: [vm1_cav, vm1_ave] + channel: Amon + zg: + raw_name: [geopot_cav, geopot_ave] + channel: Amon + Amon: + cl: + raw_name: [aclcac_cav, aclcac_ave] + channel: Amon + cli: + raw_name: [xim1_cav, xim1_ave] + channel: Amon + clw: + raw_name: [xlm1_cav, xlm1_ave] + channel: Amon + hur: # defined on plev19 + raw_name: [rhum_p19_cav, rhum_p19_ave] + channel: Amon + hus: # defined on plev19 + raw_name: [qm1_p19_cav, qm1_p19_ave] + channel: Amon + ta: # defined on plev19 + raw_name: [tm1_p19_cav, tm1_p19_ave] + channel: Amon + ua: # defined on plev19 + raw_name: [um1_p19_cav, um1_p19_ave] + channel: Amon + va: # defined on plev19 + raw_name: [vm1_p19_cav, vm1_p19_ave] + channel: Amon + zg: # defined on plev19 + raw_name: [geopot_p19_cav, geopot_p19_ave] + channel: Amon + CF3hr: + ta: + raw_name: [tm1_cav, tm1_ave] + channel: Amon + CFday: + cl: + raw_name: [aclcac_cav, aclcac_ave] + channel: Amon + cli: + raw_name: [xim1_cav, xim1_ave] + channel: Amon + clw: + raw_name: [xlm1_cav, xlm1_ave] + channel: Amon + hur: + raw_name: [rhum_cav, rhum_ave] + channel: Amon + hus: + raw_name: [qm1_cav, qm1_ave] + channel: Amon + ta: + raw_name: [tm1_cav, tm1_ave] + channel: Amon + ua: + raw_name: [um1_cav, um1_ave] + channel: Amon + va: + raw_name: [vm1_cav, vm1_ave] + channel: Amon + zg: + raw_name: [geopot_cav, geopot_ave] + channel: Amon + CFmon: + hur: + raw_name: [rhum_cav, rhum_ave] + channel: Amon + hus: + raw_name: [qm1_cav, qm1_ave] + channel: Amon + ta: + raw_name: [tm1_cav, tm1_ave] + channel: Amon + day: + hur: # defined on plev8 + raw_name: [rhum_p8_cav, rhum_p8_ave] + channel: Amon + hus: # defined on plev8 + raw_name: [qm1_p8_cav, qm1_p8_ave] + channel: Amon + ua: # defined on plev8 + raw_name: [um1_p8_cav, um1_p8_ave] + channel: Amon + va: # defined on plev8 + raw_name: [vm1_p8_cav, vm1_p8_ave] + channel: Amon + zg: # defined on plev8 + raw_name: [geopot_p8_cav, geopot_p8_ave] + channel: Amon + E1hr: + ua: # defined on plev3 + raw_name: [um1_p3_cav, um1_p3_ave] + channel: Amon + va: # defined on plev3 + raw_name: [vm1_p3_cav, vm1_p3_ave] + channel: Amon + E3hrPt: + hus: + raw_name: [qm1_cav, qm1_ave] + channel: Amon + Eday: + ta: # defined on plev19 + raw_name: [tm1_p19_cav, tm1_p19_ave] + channel: Amon + hus: # defined on plev19 + raw_name: [qm1_p19_cav, qm1_p19_ave] + channel: Amon + ua: # defined on plev19 + raw_name: [um1_p19_cav, um1_p19_ave] + channel: Amon + va: # defined on plev19 + raw_name: [vm1_p19_cav, vm1_p19_ave] + channel: Amon + zg: # defined on plev19 + raw_name: [geopot_p19_cav, geopot_p19_ave] + channel: Amon + Esubhr: + ta: + raw_name: [tm1_cav, tm1_ave] + channel: Amon + ua: + raw_name: [um1_cav, um1_ave] + channel: Amon + va: + raw_name: [vm1_cav, vm1_ave] + channel: Amon diff --git a/esmvalcore/_recipe.py b/esmvalcore/_recipe.py index f43a7b5ae3..b9344965b9 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/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/_base_fixes.py b/esmvalcore/cmor/_fixes/emac/_base_fixes.py new file mode 100644 index 0000000000..6da3d22573 --- /dev/null +++ b/esmvalcore/cmor/_fixes/emac/_base_fixes.py @@ -0,0 +1,63 @@ +"""Fix base classes for EMAC on-the-fly CMORizer.""" + +import logging + +from iris import NameConstraint +from iris.exceptions import ConstraintMismatchError + +from ..fix import Fix + +logger = logging.getLogger(__name__) + + +class EmacFix(Fix): + """Base class for all EMAC fixes.""" + + def get_cube(self, cubes, var_names=None): + """Extract single cube.""" + # 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): + """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 cubes diff --git a/esmvalcore/cmor/_fixes/emac/emac.py b/esmvalcore/cmor/_fixes/emac/emac.py new file mode 100644 index 0000000000..8fd7e06de0 --- /dev/null +++ b/esmvalcore/cmor/_fixes/emac/emac.py @@ -0,0 +1,543 @@ +"""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 +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 scipy import constants + +from ..shared import ( + add_aux_coords_from_cubes, + add_scalar_height_coord, + add_scalar_lambda550nm_coord, + add_scalar_typesi_coord, +) +from ._base_fixes import EmacFix, NegateData, SetUnitsTo1 + +logger = logging.getLogger(__name__) + + +INVALID_UNITS = { + 'kg/m**2s': 'kg m-2 s-1', +} + + +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_metadata(self, cubes): + """Fix metadata.""" + cube = self.get_cube(cubes) + + # Fix time + if 'time' in self.vardef.dimensions: + self._fix_time(cube) + + # 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) + + # Fix longitude + if 'longitude' in self.vardef.dimensions: + self._fix_lon(cube) + + # Fix scalar coordinates + self._fix_scalar_coords(cube) + + # Fix metadata of variable + self._fix_var_metadata(cube) + + 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.""" + lat = cube.coord('latitude') + 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): + """Fix longitude coordinate of cube.""" + lon = cube.coord('longitude') + 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_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 'lambda550nm' in self.vardef.dimensions: + add_scalar_lambda550nm_coord(cube) + if 'typesi' in self.vardef.dimensions: + add_scalar_typesi_coord(cube, 'sea_ice') + + 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 + + # 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) + + # Fix attributes + if self.vardef.positive != '': + cube.attributes['positive'] = self.vardef.positive + + +Cl = SetUnitsTo1 + + +Clt = SetUnitsTo1 + + +class Clwvi(EmacFix): + """Fixes for ``clwvi``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + 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]) + + +Evspsbl = NegateData + + +Hfls = NegateData + + +Hfss = NegateData + + +Hurs = SetUnitsTo1 + + +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): + """Fixes for ``pr``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + 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]) + + +class Rlds(EmacFix): + """Fixes for ``rlds``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + 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]) + + +Rlus = NegateData + + +Rlut = NegateData + + +Rlutcs = NegateData + + +class Rsds(EmacFix): + """Fixes for ``rsds``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + 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]) + + +class Rsdt(EmacFix): + """Fixes for ``rsdt``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + 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]) + + +Rsus = NegateData + + +Rsut = NegateData + + +Rsutcs = NegateData + + +class Rtmt(EmacFix): + """Fixes for ``rtmt``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + 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]) + + +Siconc = SetUnitsTo1 + + +Siconca = SetUnitsTo1 + + +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 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 + cube.units = 'mm' + 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 + + +class MP_BC_tot(EmacFix): # noqa: N801 + """Fixes for ``MP_BC_tot``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + 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]) + + +class MP_DU_tot(EmacFix): # noqa: N801 + """Fixes for ``MP_DU_tot``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + 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]) + + +class MP_SO4mm_tot(EmacFix): # noqa: N801 + """Fixes for ``MP_SO4mm_tot``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + 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]) + + +class MP_SS_tot(EmacFix): # noqa: N801 + """Fixes for ``MP_SS_tot``.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + cube = ( + 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/esmvalcore/cmor/_fixes/shared.py b/esmvalcore/cmor/_fixes/shared.py index 3f49f5a693..3883520b71 100644 --- a/esmvalcore/cmor/_fixes/shared.py +++ b/esmvalcore/cmor/_fixes/shared.py @@ -146,6 +146,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('radiation_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/config-developer.yml b/esmvalcore/config-developer.yml index 7d01407795..d3fd46013a 100644 --- a/esmvalcore/config-developer.yml +++ b/esmvalcore/config-developer.yml @@ -119,11 +119,19 @@ 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}{postproc_flag}.nc' + output_file: '{dataset}_{exp}_{channel}_{mip}_{short_name}' + cmor_type: 'CMIP6' + 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} CORDEX: input_dir: diff --git a/esmvalcore/preprocessor/_io.py b/esmvalcore/preprocessor/_io.py index 83cacef5ce..d0d1339e8a 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 @@ -110,26 +111,64 @@ def _delete_attributes(iris_object, atts): del iris_object.attributes[att] -def load(file, callback=None): - """Load iris cubes from files.""" +def load(file, callback=None, ignore_warnings=None): + """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 = [] + + # 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, + 'module': 'iris', + }) + ignore_warnings.append({ + 'message': "Ignoring netCDF variable '.*' invalid units '.*'", + 'category': UserWarning, + 'module': 'iris', + }) + + # Filter warnings 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', - ) - raw_cubes = iris.load_raw(file, callback=callback) + for warning_kwargs in ignore_warnings: + warning_kwargs.setdefault('action', 'ignore') + filterwarnings(**warning_kwargs) + # 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 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 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_emac.py b/tests/integration/cmor/_fixes/emac/test_emac.py new file mode 100644 index 0000000000..80178c5a98 --- /dev/null +++ b/tests/integration/cmor/_fixes/emac/test_emac.py @@ -0,0 +1,2857 @@ +"""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, + Cl, + Clt, + Clwvi, + Evspsbl, + Hfls, + Hfss, + Hurs, + MP_BC_tot, + MP_DU_tot, + MP_SO4mm_tot, + MP_SS_tot, + Od550aer, + Pr, + Rlds, + Rlus, + Rlut, + Rlutcs, + Rsds, + Rsdt, + Rsus, + Rsut, + Rsutcs, + Rtmt, + Siconc, + Siconca, + Sithick, + Toz, + Zg, +) +from esmvalcore.cmor.fix import Fix +from esmvalcore.cmor.table import get_var_info + + +@pytest.fixture +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_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_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): + """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_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 + cube = 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 + 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('day since 1950-01-01 00:00:00', + calendar='gregorian') + np.testing.assert_allclose(time.points, [54786.9916666667]) + assert time.bounds is None + assert time.attributes == {} + + +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' + + # 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, + [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 + + +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], + [0.996141, 0.982633, 0.954782, 0.909258], + rtol=1e-5, + ) + np.testing.assert_allclose( + lev.bounds[:4], + [[1.0, 0.992281], + [0.992281, 0.972985], + [0.972985, 0.936579], + [0.936579, 0.881937]], + rtol=1e-5, + ) + + # 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, 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, 783.195007], + [783.195007, 2549.968994]], + rtol=1e-5, + ) + + # 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.996141, 0.982633, 0.950866, 0.892592], + rtol=1e-5, + ) + np.testing.assert_allclose( + b_coord.bounds[:4], + [[1.0, 0.992281], + [0.992281, 0.972985], + [0.972985, 0.928747], + [0.928747, 0.856438]], + rtol=1e-5, + ) + + # 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], + [[99915.351562, 98339.820312, 99585.25, 96572.765625]], + rtol=1e-5, + ) + 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_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) + 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, + [59.4444082891668, 19.8757191474409, -19.8757191474409, + -59.4444082891668], + ) + np.testing.assert_allclose( + lat.bounds, + [[79.22875286, 39.66006372], + [39.66006372, 0.0], + [0.0, -39.66006372], + [-39.66006372, -79.22875286]], + ) + assert lat.attributes == {} + + +def check_lon(cube): + """Check longitude coordinate of cube.""" + 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, + [0.0, 45.0, 90.0, 135.0, 180.0, 225.0, 270.0, 315.0], + ) + np.testing.assert_allclose( + lon.bounds, + [[-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]], + ) + 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') + 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 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 + + +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 + + +# 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(str(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(str(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(str(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(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Omon', 'awhea') + assert fix == [AllVars(None)] + + +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_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' + assert 'positive' not in cube.attributes + + np.testing.assert_allclose(cube.data, [[[1.0]]]) + + +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_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_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' + assert 'positive' not in cube.attributes + + np.testing.assert_allclose(cube.data, [[[1.0]]]) + + +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_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_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 == '%' + assert 'positive' not in cube.attributes + + np.testing.assert_allclose(cube.data, [[[100.0]]]) + + +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_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_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' + assert 'positive' not in cube.attributes + + np.testing.assert_allclose(cube.data, [[[2.0]]]) + + +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_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_1d) + + 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' + assert 'positive' not in cube.attributes + + np.testing.assert_allclose(cube.data, [1.0]) + + +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_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_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' + assert 'positive' not in cube.attributes + + np.testing.assert_allclose(cube.data, [[[-1.0]]]) + + +def test_get_hfls_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'hfls') + assert fix == [Hfls(None), AllVars(None)] + + +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_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' + + np.testing.assert_allclose(cube.data, [[[-1.0]]]) + + +def test_get_hfss_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'hfss') + assert fix == [Hfss(None), AllVars(None)] + + +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_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' + + np.testing.assert_allclose(cube.data, [[[-1.0]]]) + + +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(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'od550aer') + assert fix == [Od550aer(None), AllVars(None)] + + +def test_od550aer_fix(cubes_3d): + """Test fix.""" + cubes_3d[0].var_name = 'aot_opt_TOT_550_total_cav' + 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_3d) + + allvars_fix = get_allvars_fix('Amon', 'od550aer') + fixed_cubes = allvars_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' + assert 'positive' not in cube.attributes + + check_lambda550nm(cube) + + np.testing.assert_allclose(cube.data, [[[3.0]]]) + + +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_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_2d) + + fix = get_allvars_fix('Amon', 'pr') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + 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 + + np.testing.assert_allclose(cube.data, [[[3.0]]]) + + +def test_get_prc_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'prc') + assert fix == [AllVars(None)] + + +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_2d) + + 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 + + np.testing.assert_allclose(cube.data, [[[1.0]]]) + + +def test_get_prl_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'prl') + assert fix == [AllVars(None)] + + +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_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 + + np.testing.assert_allclose(cube.data, [[[1.0]]]) + + +def test_get_prsn_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'prsn') + assert fix == [AllVars(None)] + + +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_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 + + np.testing.assert_allclose(cube.data, [[[1.0]]]) + + +def test_get_prw_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'prw') + assert fix == [AllVars(None)] + + +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_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 + + 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)] + + +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(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'rlds') + assert fix == [Rlds(None), AllVars(None)] + + +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_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' + + np.testing.assert_allclose(cube.data, [[[0.0]]]) + + +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_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_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' + + np.testing.assert_allclose(cube.data, [[[-1.0]]]) + + +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_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_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' + + np.testing.assert_allclose(cube.data, [[[-1.0]]]) + + +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_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_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' + + np.testing.assert_allclose(cube.data, [[[-1.0]]]) + + +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_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_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' + + np.testing.assert_allclose(cube.data, [[[0.0]]]) + + +def test_get_rsdt_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'rsdt') + assert fix == [Rsdt(None), AllVars(None)] + + +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_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' + + np.testing.assert_allclose(cube.data, [[[0.0]]]) + + +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_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_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' + + np.testing.assert_allclose(cube.data, [[[-1.0]]]) + + +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_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_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' + + np.testing.assert_allclose(cube.data, [[[-1.0]]]) + + +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_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_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' + + np.testing.assert_allclose(cube.data, [[[-1.0]]]) + + +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_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_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' + + np.testing.assert_allclose(cube.data, [[[2.0]]]) + + +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(): + """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.""" + 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_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_typesi(cube) + + np.testing.assert_allclose(cube.data, [[[100.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.""" + 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_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_typesi(cube) + + np.testing.assert_allclose(cube.data, [[[100.0]]]) + + +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_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_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 + + np.testing.assert_allclose(cube.data, [[[1.0]]]) + np.testing.assert_equal(np.ma.getmaskarray(cube.data), [[[False]]]) + + # 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(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'tas') + assert fix == [AllVars(None)] + + +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_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_heightxm(cube, 2.0) + + 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(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Omon', 'tos') + assert fix == [AllVars(None)] + + +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_2d) + + 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 + + np.testing.assert_allclose(cube.data, [[[1.0]]]) + + +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_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_2d) + + 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 + + np.testing.assert_allclose(cube.data, [[[1e-5]]]) + + +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_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_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 + + np.testing.assert_allclose(cube.data, [[[1.0]]]) + + +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.""" + 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_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_heightxm(cube, 10.0) + + np.testing.assert_allclose(cube.data, [[[1.0]]]) + + +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_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_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_heightxm(cube, 10.0) + + np.testing.assert_allclose(cube.data, [[[1.0]]]) + + +# Test 1D tracers in extra_facets/emac-mappings.yml + + +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_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_1d) + + fix = get_allvars_fix('TRAC10hr', 'MP_BC_tot') + fixed_cubes = fix.fix_metadata(fixed_cubes) + + 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 + + np.testing.assert_allclose(cube.data, [4.0]) + + +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)] + + +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_1d) + + 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 + + np.testing.assert_allclose(cube.data, [1.0]) + + +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_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_1d) + + 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 + + np.testing.assert_allclose(cube.data, [1.0]) + + +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_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_1d) + + 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 + + np.testing.assert_allclose(cube.data, [1.0]) + + +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)] + + +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_1d) + + 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 + + np.testing.assert_allclose(cube.data, [1.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_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_1d) + + 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 + + np.testing.assert_allclose(cube.data, [1.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_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_1d) + + 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.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 + + np.testing.assert_allclose(cube.data, [4.0]) + + +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_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_1d) + + 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 + + np.testing.assert_allclose(cube.data, [1.0]) + + +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)] + + +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_1d) + + 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 + + np.testing.assert_allclose(cube.data, [1.0]) + + +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_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_1d) + + 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 + + np.testing.assert_allclose(cube.data, [1.0]) + + +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_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_1d) + + 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 + + np.testing.assert_allclose(cube.data, [1.0]) + + +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_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_1d) + + 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 + + np.testing.assert_allclose(cube.data, [1.0]) + + +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_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_1d) + + 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 + + np.testing.assert_allclose(cube.data, [1.0]) + + +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_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_1d) + + 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 + + np.testing.assert_allclose(cube.data, [1.0]) + + +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_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_1d) + + 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 + + np.testing.assert_allclose(cube.data, [1.0]) + + +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_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_1d) + + 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 + + np.testing.assert_allclose(cube.data, [1.0]) + + +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_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_1d) + + 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 + + np.testing.assert_allclose(cube.data, [4.0]) + + +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_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_1d) + + 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 + + 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)] + + +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 + + assert not cube.aux_factories + assert cube.coords('air_pressure') + + np.testing.assert_allclose(cube.data, [[[[100.0]], [[200.0]]]]) + + +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_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_3d) + + 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 + + assert not cube.aux_factories + assert cube.coords('air_pressure') + + np.testing.assert_allclose(cube.data, [[[[1.0]], [[2.0]]]]) + + +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') + + np.testing.assert_allclose(cube.data, [[[[1.0]], [[2.0]]]]) + + +def test_get_va_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('EMAC', 'EMAC', 'Amon', 'va') + assert fix == [AllVars(None)] + + +def test_va_fix(cubes_3d): + """Test fix.""" + 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) + + 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 not cube.aux_factories + assert cube.coords('air_pressure') + + 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)] + + +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 + + assert not cube.aux_factories + assert cube.coords('air_pressure') + + np.testing.assert_allclose( + cube.data, + [[[[0.101971]], [[0.203943]]]], + rtol=1e-5, + ) + + +# Test ``AllVars.fix_file`` + + +@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_3d): + """Test fix.""" + # Create cube with Z-coord whose units are not convertible to Pa + cube = cubes_3d[0] + 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_fix_invalid_units_fix_exponent(): + """Test fix.""" + cube = Cube(1.0, attributes={'invalid_units': 'W/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.units.origin == 'W/m^2' + assert cube.attributes['positive'] == 'down' + + np.testing.assert_allclose(cube.data, 1.0) + + +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) 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 0000000000..a1ffe4f03b Binary files /dev/null and b/tests/integration/cmor/_fixes/test_data/emac.nc differ diff --git a/tests/integration/cmor/_fixes/test_shared.py b/tests/integration/cmor/_fixes/test_shared.py index b4c63e51b7..5029c9306f 100644 --- a/tests/integration/cmor/_fixes/test_shared.py +++ b/tests/integration/cmor/_fixes/test_shared.py @@ -13,6 +13,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): diff --git a/tests/integration/preprocessor/_io/test_load.py b/tests/integration/preprocessor/_io/test_load.py index 1f36f4b492..cc68b88cb0 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,47 @@ 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') + + @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.""" + 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']