Skip to content

Commit

Permalink
Added unstructured linear regridding (#2350)
Browse files Browse the repository at this point in the history
  • Loading branch information
schlunma authored Apr 26, 2024
1 parent e147a51 commit 6a98408
Show file tree
Hide file tree
Showing 9 changed files with 752 additions and 55 deletions.
34 changes: 24 additions & 10 deletions doc/recipe/preprocessor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,19 @@ ESMValCore has a number of built-in regridding schemes, which are presented in
third party regridding schemes designed for use with :doc:`Iris
<iris:index>`. This is explained in :ref:`generic regridding schemes`.

Grid types
~~~~~~~~~~

In ESMValCore, we distinguish between three grid types (note that these might
differ from other definitions):

* **Regular grid**: A rectilinear grid with 1D latitude and 1D longitude
coordinates which are orthogonal to each other.
* **Irregular grid**: A general curvilinear grid with 2D latitude and 2D
longitude coordinates with common dimensions.
* **Unstructured grid**: A grid with 1D latitude and 1D longitude coordinates
with common dimensions (i.e., a simple list of points).

.. _built-in regridding schemes:

Built-in regridding schemes
Expand All @@ -899,7 +912,8 @@ Built-in regridding schemes
`extrapolation_mode='mask'`.
For source data on an irregular grid, uses
:class:`~esmvalcore.preprocessor.regrid_schemes.ESMPyLinear`.
Source data on an unstructured grid is not supported, yet.
For source data on an unstructured grid, uses
:class:`~esmvalcore.preprocessor.regrid_schemes.UnstructuredLinear`.
* ``nearest``: Nearest-neighbor regridding.
For source data on a regular grid, uses :obj:`~iris.analysis.Nearest` with
`extrapolation_mode='mask'`.
Expand All @@ -911,7 +925,7 @@ Built-in regridding schemes
For source data on a regular grid, uses :obj:`~iris.analysis.AreaWeighted`.
For source data on an irregular grid, uses
:class:`~esmvalcore.preprocessor.regrid_schemes.ESMPyAreaWeighted`.
Source data on an unstructured grid is not supported, yet.
Source data on an unstructured grid is not supported.

.. _generic regridding schemes:

Expand All @@ -924,9 +938,9 @@ to transform a source cube with a given grid into the grid defined by a given
target cube. Iris itself provides a number of useful schemes, but they are
largely limited to work with simple, regular grids. Other schemes can be
provided independently. This is interesting when special regridding-needs arise
or when more involved grids and meshes need to be considered. Furthermore, it
may be desirable to have finer control over the parameters of the scheme than
is afforded by the built-in schemes described above.
or when more involved grids need to be considered. Furthermore, it may be
desirable to have finer control over the parameters of the scheme than is
afforded by the built-in schemes described above.

To facilitate this, the :func:`~esmvalcore.preprocessor.regrid` preprocessor
allows the use of any scheme designed for Iris. The scheme must be installed
Expand Down Expand Up @@ -976,11 +990,11 @@ module, the second refers to the scheme, i.e. some callable that will be called
with the remaining entries of the ``scheme`` dictionary passed as keyword
arguments.

One package that aims to capitalize on the :ref:`support for unstructured
meshes introduced in Iris 3.2 <iris:ugrid>` is
:doc:`iris-esmf-regrid:index`. It aims to provide lazy regridding for
structured regular and irregular grids, as well as unstructured meshes. An
example of its usage in a preprocessor is:
One package that aims to capitalize on the :ref:`support for unstructured grids
introduced in Iris 3.2 <iris:ugrid>` is :doc:`iris-esmf-regrid:index`.
It aims to provide lazy regridding for structured regular and irregular grids,
as well as unstructured grids.
An example of its usage in a preprocessor is:

.. code-block:: yaml
Expand Down
35 changes: 35 additions & 0 deletions esmvalcore/iris_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,41 @@ def rechunk_cube(
return cube


def has_regular_grid(cube: Cube) -> bool:
"""Check if a cube has a regular grid.
"Regular" refers to a rectilinear grid with 1D latitude and 1D longitude
coordinates orthogonal to each other.
Parameters
----------
cube:
Cube to be checked.
Returns
-------
bool
``True`` if input cube has a regular grid, else ``False``.
"""
try:
lat = cube.coord('latitude')
lon = cube.coord('longitude')
except CoordinateNotFoundError:
return False
if lat.ndim != 1 or lon.ndim != 1:
return False
if cube.coord_dims(lat) == cube.coord_dims(lon):
return False
return True


def has_irregular_grid(cube: Cube) -> bool:
"""Check if a cube has an irregular grid.
"Irregular" refers to a general curvilinear grid with 2D latitude and 2D
longitude coordinates with common dimensions.
Parameters
----------
cube:
Expand All @@ -297,6 +329,9 @@ def has_irregular_grid(cube: Cube) -> bool:
def has_unstructured_grid(cube: Cube) -> bool:
"""Check if a cube has an unstructured grid.
"Unstructured" refers to a grid with 1D latitude and 1D longitude
coordinates with common dimensions (i.e., a simple list of points).
Parameters
----------
cube:
Expand Down
12 changes: 4 additions & 8 deletions esmvalcore/preprocessor/_area.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from iris.cube import Cube, CubeList
from iris.exceptions import CoordinateMultiDimError, CoordinateNotFoundError

from esmvalcore.iris_helpers import has_regular_grid
from esmvalcore.preprocessor._regrid import broadcast_to_shape
from esmvalcore.preprocessor._shared import (
get_iris_aggregator,
Expand Down Expand Up @@ -355,20 +356,15 @@ def _try_adding_calculated_cell_area(cube: Cube) -> None:
)
logger.debug("Attempting to calculate grid cell area")

regular_grid = all([
cube.coord('latitude').points.ndim == 1,
cube.coord('longitude').points.ndim == 1,
cube.coord_dims('latitude') != cube.coord_dims('longitude'),
])
rotated_pole_grid = all([
cube.coord('latitude').points.ndim == 2,
cube.coord('longitude').points.ndim == 2,
cube.coord('latitude').core_points().ndim == 2,
cube.coord('longitude').core_points().ndim == 2,
cube.coords('grid_latitude'),
cube.coords('grid_longitude'),
])

# For regular grids, calculate grid cell areas with iris function
if regular_grid:
if has_regular_grid(cube):
cube = guess_bounds(cube, ['latitude', 'longitude'])
logger.debug("Calculating grid cell areas for regular grid")
cell_areas = compute_area_weights(cube)
Expand Down
25 changes: 17 additions & 8 deletions esmvalcore/preprocessor/_regrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
ESMPyLinear,
ESMPyNearest,
GenericFuncScheme,
UnstructuredLinear,
UnstructuredNearest,
)

Expand Down Expand Up @@ -75,22 +76,29 @@
'nearest': Nearest(extrapolation_mode='mask'),
}

# Supported horizontal regridding schemes for regular grids
# Supported horizontal regridding schemes for regular grids (= rectilinear
# grids; i.e., grids that can be described with 1D latitude and 1D longitude
# coordinates orthogonal to each other)
HORIZONTAL_SCHEMES_REGULAR = {
'area_weighted': AreaWeighted(),
'linear': Linear(extrapolation_mode='mask'),
'nearest': Nearest(extrapolation_mode='mask'),
}

# Supported horizontal regridding schemes for irregular grids
# Supported horizontal regridding schemes for irregular grids (= general
# curvilinear grids; i.e., grids that can be described with 2D latitude and 2D
# longitude coordinates with common dimensions)
HORIZONTAL_SCHEMES_IRREGULAR = {
'area_weighted': ESMPyAreaWeighted(),
'linear': ESMPyLinear(),
'nearest': ESMPyNearest(),
}

# Supported horizontal regridding schemes for unstructured grids
# Supported horizontal regridding schemes for unstructured grids (i.e., grids,
# that can be described with 1D latitude and 1D longitude coordinate with
# common dimensions)
HORIZONTAL_SCHEMES_UNSTRUCTURED = {
'linear': UnstructuredLinear(),
'nearest': UnstructuredNearest(),
}

Expand Down Expand Up @@ -750,10 +758,11 @@ def regrid(
be specified (see above).
scheme:
The regridding scheme to perform. If the source grid is structured
(regular or irregular), can be one of the built-in schemes ``linear``,
``nearest``, ``area_weighted``. If the source grid is unstructured, can
be one of the built-in schemes ``nearest``. Alternatively, a `dict`
that specifies generic regridding can be given (see below).
(i.e., rectilinear or curvilinear), can be one of the built-in schemes
``linear``, ``nearest``, ``area_weighted``. If the source grid is
unstructured, can be one of the built-in schemes ``linear``,
``nearest``. Alternatively, a `dict` that specifies generic regridding
can be given (see below).
lat_offset:
Offset the grid centers of the latitude coordinate w.r.t. the pole by
half a grid step. This argument is ignored if `target_grid` is a cube
Expand Down Expand Up @@ -786,7 +795,7 @@ def regrid(
regridding schemes, that is anything that can be passed as a scheme to
:meth:`iris.cube.Cube.regrid` is possible. This enables the use of further
parameters for existing schemes, as well as the use of more advanced
schemes for example for unstructured meshes.
schemes for example for unstructured grids.
To use this functionality, a dictionary must be passed for the scheme with
a mandatory entry of ``reference`` in the form specified for the object
reference of the `entry point data model <https://packaging.python.org/en/
Expand Down
Loading

0 comments on commit 6a98408

Please sign in to comment.