Source code for esmvalcore.preprocessor._weighting

"""Weighting preprocessor module."""

import logging

import iris

logger = logging.getLogger(__name__)


def _get_land_fraction(cube, fx_variables):
    """Extract land fraction as :mod:`dask.array`."""
    land_fraction = None
    errors = []
    if not fx_variables:
        errors.append("No fx files given.")
        return (land_fraction, errors)
    for (fx_var, fx_path) in fx_variables.items():
        if not fx_path:
            errors.append(f"File for '{fx_var}' not found.")
            continue
        fx_cube = iris.load_cube(fx_path)
        if not _shape_is_broadcastable(fx_cube.shape, cube.shape):
            errors.append(
                f"Cube '{fx_var}' with shape {fx_cube.shape} not "
                f"broadcastable to cube '{cube.var_name}' with shape "
                f"{cube.shape}.")
            continue
        if fx_var == 'sftlf':
            land_fraction = fx_cube.core_data() / 100.0
            break
        if fx_var == 'sftof':
            land_fraction = 1.0 - fx_cube.core_data() / 100.0
            break
        errors.append(
            f"Cannot calculate land fraction from '{fx_var}', expected "
            f"'sftlf' or 'sftof'.")
    return (land_fraction, errors)


def _shape_is_broadcastable(shape_1, shape_2):
    """Check if two :mod:`numpy.array' shapes are broadcastable."""
    return all((m == n) or (m == 1) or (n == 1)
               for (m, n) in zip(shape_1[::-1], shape_2[::-1]))


[docs]def weighting_landsea_fraction(cube, fx_variables, area_type): """Weight fields using land or sea fraction. This preprocessor function weights a field with its corresponding land or sea area fraction (value between 0 and 1). The application of this is important for most carbon cycle variables (and other land-surface outputs), which are e.g. reported in units of `kgC m-2`. This actually refers to 'per square meter of land/sea' and NOT 'per square meter of gridbox'. So in order to integrate these globally or regionally one has to both area-weight the quantity but also weight by the land/sea fraction. Parameters ---------- cube : iris.cube.Cube Data cube to be weighted. fx_variables : dict Dictionary holding ``var_name`` (keys) and full paths (values) to the fx files as ``str`` or empty ``list`` (if not available). area_type : str Use land (``'land'``) or sea (``'sea'``) fraction for weighting. Returns ------- iris.cube.Cube Land/sea fraction weighted cube. Raises ------ TypeError ``area_type`` is not ``'land'`` or ``'sea'``. ValueError Land/sea fraction variables ``sftlf`` or ``sftof`` not found or shape of them is not broadcastable to ``cube``. """ if area_type not in ('land', 'sea'): raise TypeError( f"Expected 'land' or 'sea' for area_type, got '{area_type}'") (land_fraction, errors) = _get_land_fraction(cube, fx_variables) if land_fraction is None: raise ValueError( f"Weighting of '{cube.var_name}' with '{area_type}' fraction " f"failed because of the following errors: {' '.join(errors)}") core_data = cube.core_data() if area_type == 'land': cube.data = core_data * land_fraction elif area_type == 'sea': cube.data = core_data * (1.0 - land_fraction) return cube