Skip to content

Commit 73bc323

Browse files
committed
dev
1 parent 0c5af83 commit 73bc323

File tree

7 files changed

+125
-16
lines changed

7 files changed

+125
-16
lines changed

Changelog.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ version NEXTVERSION
33

44
**2024-??-??**
55

6+
* Allowed ``'nearest_dtos'`` 2-d regridding to work with discrete
7+
sampling geometry source grids ()
8+
(https://github.com/NCAS-CMS/cf-python/issues/811)
69
* New method: `cf.Field.filled`
710
(https://github.com/NCAS-CMS/cf-python/issues/811)
811
* New method: `cf.Field.is_discrete_axis`

cf/cfimplementation.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
TiePointIndex,
2727
)
2828
from .data import Data
29-
3029
from .data.array import (
3130
BoundsFromNodesArray,
3231
CellConnectivityArray,

cf/data/dask_regrid.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -514,10 +514,11 @@ def _regrid(
514514
data = weights.data
515515
for j, (i0, i1) in enumerate(zip(indptr[:-1], indptr[1:])):
516516
mask = src_mask[indices[i0:i1]]
517-
if not count_nonzero(mask):
517+
n_masked = count_nonzero(mask)
518+
if not n_masked:
518519
continue
519520

520-
if mask.all():
521+
if n_masked == mask.size:
521522
dst_mask[j] = True
522523
continue
523524

@@ -529,8 +530,8 @@ def _regrid(
529530

530531
del indptr
531532

532-
elif method in ("linear", "bilinear", "nearest_dtos"):
533-
# 2) Linear and nearest neighbour methods:
533+
elif method in ("linear", "bilinear"):
534+
# 2) Linear methods:
534535
#
535536
# Mask out any row j that contains at least one positive
536537
# (i.e. greater than or equal to 'min_weight') w_ji that
@@ -562,12 +563,43 @@ def _regrid(
562563

563564
del indptr, pos_data
564565

566+
elif method == "nearest_dtos":
567+
# 3) Nearest neighbour dtos method:
568+
#
569+
# Set to 0 any weight that corresponds to a masked source
570+
# grid cell.
571+
#
572+
# Mask out any row j for which all source grid cells are
573+
# masked.
574+
dst_size = weights.shape[0]
575+
if dst_mask is None:
576+
dst_mask = np.zeros((dst_size,), dtype=bool)
577+
else:
578+
dst_mask = dst_mask.copy()
579+
580+
# Note: It is much more efficient to access
581+
# 'weights.indptr', 'weights.indices', and
582+
# 'weights.data' directly, rather than iterating
583+
# over rows of 'weights' and using 'weights.getrow'.
584+
count_nonzero = np.count_nonzero
585+
indptr = weights.indptr.tolist()
586+
indices = weights.indices
587+
for j, (i0, i1) in enumerate(zip(indptr[:-1], indptr[1:])):
588+
mask = src_mask[indices[i0:i1]]
589+
n_masked = count_nonzero(mask)
590+
if n_masked == mask.size:
591+
dst_mask[j] = True
592+
elif n_masked:
593+
weights.data[np.arange(i0, i1)[mask]] = 0
594+
595+
del indptr
596+
565597
elif method in (
566598
"patch",
567599
"conservative_2nd",
568600
"nearest_stod",
569601
):
570-
# 3) Patch recovery and second-order conservative methods:
602+
# 4) Patch recovery and second-order conservative methods:
571603
#
572604
# A reference source data mask has already been
573605
# incorporated into the weights matrix, and 'a' is assumed

cf/data/data.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
from ..units import Units
4444
from .collapse import Collapse
4545
from .creation import generate_axis_identifiers, to_dask
46-
4746
from .dask_utils import (
4847
_da_ma_allclose,
4948
cf_asanyarray,

cf/docstring/docstring.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,11 @@
161161
mapped to the closest destination point. A
162162
destination point can be mapped to multiple source
163163
points. Some destination points may not be
164-
mapped. Useful for regridding of categorical data.
164+
mapped. Each regridded value is the sum of the
165+
contributing source elements. Masked source grid
166+
cells are mapped, but do not contribute to the
167+
regridded values. Useful for binning or for
168+
categorical data.
165169
166170
* `None`: This is the default and can only be used
167171
when *dst* is a `RegridOperator`.""",

cf/regrid/regrid.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -523,11 +523,10 @@ def regrid(
523523
"are a UGRID mesh"
524524
)
525525

526-
if src_grid.is_locstream or dst_grid.is_locstream:
526+
if dst_grid.is_locstream:
527527
raise ValueError(
528-
f"{method!r} regridding is (at the moment) only available "
529-
"when neither the source and destination grids are "
530-
"DSG featureTypes."
528+
f"{method!r} regridding is (at the moment) not available "
529+
"when the destination grid is a DSG featureType."
531530
)
532531

533532
elif cartesian and (src_grid.is_mesh or dst_grid.is_mesh):
@@ -656,6 +655,7 @@ def regrid(
656655
dst=dst,
657656
weights_file=weights_file if from_file else None,
658657
src_mesh_location=src_grid.mesh_location,
658+
src_featureType=src_grid.featureType,
659659
dst_featureType=dst_grid.featureType,
660660
src_z=src_grid.z,
661661
dst_z=dst_grid.z,
@@ -1279,7 +1279,7 @@ def spherical_grid(
12791279

12801280
# Set cyclicity of X axis
12811281
if mesh_location or featureType:
1282-
cyclic = None
1282+
cyclic = False
12831283
elif cyclic is None:
12841284
cyclic = f.iscyclic(x_axis)
12851285
else:
@@ -2281,6 +2281,11 @@ def create_esmpy_locstream(grid, mask=None):
22812281
# but the esmpy mask requires 0/1 for masked/unmasked
22822282
# elements.
22832283
mask = np.invert(mask).astype("int32")
2284+
if mask.size == 1:
2285+
# Make sure that there's a mask element for each point in
2286+
# the locstream (rather than a scalar that applies to all
2287+
# elements).
2288+
mask = np.full((location_count,), mask, dtype="int32")
22842289
else:
22852290
# No masked points
22862291
mask = np.full((location_count,), 1, dtype="int32")
@@ -2465,7 +2470,6 @@ def create_esmpy_weights(
24652470
from netCDF4 import Dataset
24662471

24672472
from .. import __version__
2468-
24692473
from ..data.array.locks import netcdf_lock
24702474

24712475
if (

cf/test/test_regrid_featureType.py

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
disallowed_methods = (
2929
"conservative",
3030
"conservative_2nd",
31-
"nearest_dtos",
31+
# "nearest_dtos",
3232
)
3333

3434
methods = (
@@ -161,6 +161,75 @@ def test_Field_regrid_grid_to_featureType_3d(self):
161161

162162
y = esmpy_regrid(coord_sys, method, src, dst, **kwargs)
163163

164+
self.assertEqual(y.size, a.size)
165+
self.assertTrue(np.allclose(y, a, atol=4e-3, rtol=rtol))
166+
167+
if isinstance(a, np.ma.MaskedArray):
168+
self.assertTrue((y.mask == a.mask).all())
169+
else:
170+
self.assertFalse(y.mask.any())
171+
172+
@unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.")
173+
def test_Field_regrid_featureType_to_grid_2d(self):
174+
self.assertFalse(cf.regrid_logging())
175+
176+
src = self.dst_featureType
177+
src.del_construct("cellmethod0")
178+
src = src[:12]
179+
src[...] = 273 + np.arange(12)
180+
x = src.coord("X")
181+
x[...] = [4, 6, 9, 11, 14, 16, 4, 6, 9, 11, 14, 16]
182+
y = src.coord("Y")
183+
y[...] = [41, 41, 31, 31, 21, 21, 39, 39, 29, 29, 19, 19]
184+
185+
dst = self.src_grid.copy()
186+
x = dst.coord("X")
187+
x[...] = [5, 10, 15, 20]
188+
y = dst.coord("Y")
189+
y[...] = [10, 20, 30, 40]
190+
191+
# Mask some destination grid points
192+
dst[0, 0, 1, 2] = cf.masked
193+
194+
y0 = np.ma.array(
195+
[[0, 0, 0, 0], [0, 0, 1122, 0], [0, 1114, 0, 0], [1106, 0, 0, 0]],
196+
mask=[
197+
[True, True, True, True],
198+
[True, True, False, True],
199+
[True, False, True, True],
200+
[False, True, True, True],
201+
],
202+
)
203+
204+
coord_sys = "spherical"
205+
206+
for src_masked in (False, True):
207+
y = y0.copy()
208+
if src_masked:
209+
src = src.copy()
210+
src[6:8] = cf.masked
211+
y[3, 0] = 547
212+
213+
# Loop over whether or not to use the destination grid
214+
# masked points
215+
for use_dst_mask in (False, True):
216+
if use_dst_mask:
217+
y = y.copy()
218+
y[1, 2] = np.ma.masked
219+
220+
kwargs = {"use_dst_mask": use_dst_mask}
221+
method = "nearest_dtos"
222+
for return_operator in (False, True):
223+
if return_operator:
224+
r = src.regrids(
225+
dst, method=method, return_operator=True, **kwargs
226+
)
227+
x = src.regrids(r)
228+
else:
229+
x = src.regrids(dst, method=method, **kwargs)
230+
231+
a = x.array
232+
164233
self.assertEqual(y.size, a.size)
165234
self.assertTrue(np.allclose(y, a, atol=atol, rtol=rtol))
166235

@@ -196,7 +265,6 @@ def test_Field_regrid_grid_to_featureType_2d(self):
196265
a = x.array
197266

198267
y = esmpy_regrid(coord_sys, method, src, dst, **kwargs)
199-
200268
self.assertEqual(y.size, a.size)
201269
self.assertTrue(np.allclose(y, a, atol=atol, rtol=rtol))
202270

0 commit comments

Comments
 (0)