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']