Source code for esmvalcore.iris_helpers
"""Auxiliary functions for :mod:`iris`."""
from typing import Dict, List, Sequence
import dask.array as da
import iris
import iris.cube
import iris.util
import numpy as np
from iris.cube import Cube
from iris.exceptions import CoordinateMultiDimError
from esmvalcore.typing import NetCDFAttr
[docs]
def add_leading_dim_to_cube(cube, dim_coord):
"""Add new leading dimension to cube.
An input cube with shape ``(x, ..., z)`` will be transformed to a cube with
shape ``(w, x, ..., z)`` where ``w`` is the length of ``dim_coord``. Note
that the data is broadcasted to the new shape.
Parameters
----------
cube: iris.cube.Cube
Input cube.
dim_coord: iris.coords.DimCoord
Dimensional coordinate that is used to describe the new leading
dimension. Needs to be 1D.
Returns
-------
iris.cube.Cube
Transformed input cube with new leading dimension.
Raises
------
CoordinateMultiDimError
``dim_coord`` is not 1D.
"""
# Only 1D dim_coords are supported
if dim_coord.ndim > 1:
raise CoordinateMultiDimError(dim_coord)
new_shape = (dim_coord.shape[0], *cube.shape)
# Cache ancillary variables and cell measures (iris.util.new_axis drops
# those) and determine corresponding dimensions in new cube
ancillary_variables = []
for ancillary_variable in cube.ancillary_variables():
new_dims = tuple(
d + 1 for d in cube.ancillary_variable_dims(ancillary_variable)
)
ancillary_variables.append((ancillary_variable, new_dims))
cell_measures = []
for cell_measure in cube.cell_measures():
new_dims = tuple(d + 1 for d in cube.cell_measure_dims(cell_measure))
cell_measures.append((cell_measure, new_dims))
# Transform cube from shape (x, ..., z) to (1, x, ..., z)
cube = iris.util.new_axis(cube)
# Create new cube with shape (w, x, ..., z) where w is length of dim_coord
# and already add ancillary variables and cell measures
new_data = da.broadcast_to(cube.core_data(), new_shape)
new_cube = Cube(
new_data,
ancillary_variables_and_dims=ancillary_variables,
cell_measures_and_dims=cell_measures,
)
# Add metadata
# Note: using cube.coord_dims() for determining the positions for the
# coordinates of the new cube is correct here since cube has the shape (1,
# x, ..., z) at this stage
new_cube.metadata = cube.metadata
new_cube.add_dim_coord(dim_coord, 0)
for coord in cube.coords(dim_coords=True):
new_cube.add_dim_coord(coord, cube.coord_dims(coord))
for coord in cube.coords(dim_coords=False):
new_cube.add_aux_coord(coord, cube.coord_dims(coord))
return new_cube
[docs]
def date2num(date, unit, dtype=np.float64):
"""Convert datetime object into numeric value with requested dtype.
This is a custom version of :meth:`cf_units.Unit.date2num` that
guarantees the correct dtype for the return value.
Arguments
---------
date : :class:`datetime.datetime` or :class:`cftime.datetime`
unit : :class:`cf_units.Unit`
dtype : a numpy dtype
Returns
-------
:class:`numpy.ndarray` of type `dtype`
The return value of ``unit.date2num`` with the requested dtype.
"""
num = unit.date2num(date)
try:
return num.astype(dtype)
except AttributeError:
return dtype(num)
[docs]
def merge_cube_attributes(
cubes: Sequence[Cube],
delimiter: str = ' ',
) -> None:
"""Merge attributes of all given cubes in-place.
After this operation, the attributes of all given cubes are equal. This is
useful for operations that combine cubes, such as
:meth:`iris.cube.CubeList.merge_cube` or
:meth:`iris.cube.CubeList.concatenate_cube`.
Note
----
This function differs from :func:`iris.util.equalise_attributes` in this
respect that it does not delete attributes that are not identical but
rather concatenates them (sorted) using the given ``delimiter``. E.g., the
attributes ``exp: historical`` and ``exp: ssp585`` end up as ``exp:
historical ssp585`` using the default ``delimiter = ' '``.
Parameters
----------
cubes:
Input cubes whose attributes will be modified in-place.
delimiter:
Delimiter that is used to concatenate non-identical attributes.
"""
if len(cubes) <= 1:
return
# Step 1: collect all attribute values in a list
attributes: Dict[str, List[NetCDFAttr]] = {}
for cube in cubes:
for (attr, val) in cube.attributes.items():
attributes.setdefault(attr, [])
attributes[attr].append(val)
# Step 2: if values are not equal, first convert them to strings (so that
# set() can be used); then extract unique elements from this list, sort it,
# and use the delimiter to join all elements to a single string
final_attributes: Dict[str, NetCDFAttr] = {}
for (attr, vals) in attributes.items():
set_of_str = sorted({str(v) for v in vals})
if len(set_of_str) == 1:
final_attributes[attr] = vals[0]
else:
final_attributes[attr] = delimiter.join(set_of_str)
# Step 3: modify the cubes in-place
for cube in cubes:
cube.attributes = final_attributes