Skip to content

Commit abd266c

Browse files
billsackspre-commit-ci[bot]raphaeldussin
authored
Enable the vector_regrid flag via the backend (#436)
Vector regridding was recently added to ESMF's C and Python APIs (esmf-org/esmf@c153f29350). This commit exposes this option via the xesmf backend to ESMPy. (See https://earthsystemmodeling.org/docs/release/latest/ESMF_refdoc/node5.html#SECTION050121700000000000000 for general information on vector regridding in ESMF.) I looked into making changes to expose this via the xesmf frontend as well, but it seems like this is going to be more involved, so I held off on this: For most regridding operations, the regridding matrix only considers the spatial dimensions, and then this matrix can be applied separately across all non-spatial dimensions. However, for vector regridding, the regridding matrix accounts for the vector dimension (which must be of size 2) in addition to the spatial dimensions, and then the regridding operation acts on all of these dimensions. My sense is that xesmf is set up to assume the typical operation, and some work will be needed to add flexibility to handle this new vector regridding configuration. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: raphael dussin <[email protected]>
1 parent 54214ac commit abd266c

File tree

2 files changed

+59
-2
lines changed

2 files changed

+59
-2
lines changed

xesmf/backend.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import os
1919
import warnings
20+
from collections.abc import Sequence
2021

2122
try:
2223
import esmpy as ESMF
@@ -313,7 +314,7 @@ def get_shape(self, loc=ESMF.MeshLoc.ELEMENT):
313314
return (self.size[loc], 1)
314315

315316

316-
def esmf_regrid_build(
317+
def esmf_regrid_build( # noqa: C901
317318
sourcegrid,
318319
destgrid,
319320
method,
@@ -323,6 +324,7 @@ def esmf_regrid_build(
323324
extrap_dist_exponent=None,
324325
extrap_num_src_pnts=None,
325326
ignore_degenerate=None,
327+
vector_regrid=None,
326328
):
327329
"""
328330
Create an ESMF.Regrid object, containing regridding weights.
@@ -381,9 +383,22 @@ def esmf_regrid_build(
381383
If False (default), raise error if grids contain degenerated cells
382384
(i.e. triangles or lines, instead of quadrilaterals)
383385
386+
vector_regrid : bool, optional
387+
If True, treat a single extra (non-spatial) dimension in the source and
388+
destination data fields as the components of a vector. (If True and
389+
there is more than one extra dimension in either the source or
390+
destination data fields, an error will be raised.) If not specified,
391+
defaults to False.
392+
393+
Only vector dimensions of size 2 are supported. The first entry is
394+
interpreted as the east component and the second as the north component.
395+
i.e., ``extra_dims`` must be ``[2]``.
396+
397+
Requires ESMPy 8.9.0 or newer.
398+
384399
Returns
385400
-------
386-
grid : ESMF.Grid object
401+
regrid : ESMF.Regrid object
387402
388403
"""
389404

@@ -430,6 +445,12 @@ def esmf_regrid_build(
430445
'destination grid has no corner information. ' 'cannot use conservative regridding.'
431446
)
432447

448+
if vector_regrid:
449+
# Check this ESMPy requirement in order to give a more helpful error message if it
450+
# isn't met
451+
if not (isinstance(extra_dims, Sequence) and len(extra_dims) == 1 and extra_dims[0] == 2):
452+
raise ValueError('`vector_regrid` currently requires `extra_dims` to be `[2]`')
453+
433454
# ESMF.Regrid requires Field (Grid+data) as input, not just Grid.
434455
# Extra dimensions are specified when constructing the Field objects,
435456
# not when constructing the Regrid object later on.
@@ -480,6 +501,12 @@ def esmf_regrid_build(
480501
)
481502
if allow_masked_values:
482503
kwargs.update(dict(src_mask_values=[0], dst_mask_values=[0]))
504+
# Only add the vector_regrid argument if it is given and true; this supports backwards
505+
# compatibility with versions of ESMPy prior to 8.9.0 that do not have this option.
506+
# (If the user explicitly sets the vector_regrid argument, however, that will still be
507+
# passed through; this will lead to an exception with older ESMPy versions.)
508+
if vector_regrid:
509+
kwargs['vector_regrid'] = vector_regrid
483510

484511
regrid = ESMF.Regrid(sourcefield, destfield, **kwargs)
485512

xesmf/tests/test_backend.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,36 @@ def test_read_weights(tmp_path):
295295
read_weights(ds.drop_vars('col'), lon_in.size, lon_out.size)
296296

297297

298+
def test_vector_regrid():
299+
grid_in = Grid.from_xarray(lon_in.T, lat_in.T)
300+
grid_out = Grid.from_xarray(lon_out.T, lat_out.T)
301+
data_in_vec = np.stack(
302+
[np.cos(data_in) * np.sign(lat_in), np.sin(data_in) * np.sign(lon_in)], axis=0
303+
)
304+
305+
try:
306+
regrid_vec = esmf_regrid_build(
307+
grid_in, grid_out, 'bilinear', extra_dims=[2], vector_regrid=True
308+
)
309+
except TypeError:
310+
pytest.skip('vector_regrid argument not supported by ESMPy versions < 8.9')
311+
assert regrid_vec.vector_regrid is True
312+
data_out_vec = esmf_regrid_apply(regrid_vec, data_in_vec.T).T
313+
314+
regrid_nonvec = esmf_regrid_build(grid_in, grid_out, 'bilinear', extra_dims=[2])
315+
assert regrid_nonvec.vector_regrid in (None, False)
316+
data_out_nonvec = esmf_regrid_apply(regrid_nonvec, data_in_vec.T).T
317+
318+
# The result of vector regridding should differ from the result of non-vector
319+
# regridding, but not by much. (This threshold of 0.1 was determined empirically and
320+
# may need to be adjusted if details of the test inputs change.)
321+
assert np.array_equal(data_out_vec, data_out_nonvec) is False
322+
assert np.max(np.abs(data_out_vec - data_out_nonvec)) < 0.1
323+
324+
esmf_regrid_finalize(regrid_vec)
325+
esmf_regrid_finalize(regrid_nonvec)
326+
327+
298328
def test_deprecated():
299329
from xesmf.backend import esmf_grid, esmf_locstream
300330

0 commit comments

Comments
 (0)