healpix_resample.clough_tocher (Delaunay / Clough-Tocher C1 cubic regridding)#
healpix_resample.clough_tocher.CloughTocherResampler maps unstructured (lon_deg, lat_deg, val) samples
onto a HEALPix grid using a Delaunay triangulation of the input samples, with a Clough-Tocher C1 cubic
Bezier macro-element fitted per triangle — the same construction behind
scipy.interpolate.griddata(method='cubic'). Unlike BicubicResampler, this is a genuine bivariate
interpolant (exact at the input sample points), not a radial kernel sum.
Note on geodesy: the package manages the HEALPix authalic definition and the Earth ellipsoid with WGS84 through its geometry helper (
healpix_geo).
Why this exists#
Comparing BicubicResampler against scipy.interpolate.griddata(method='cubic') on the same smooth,
curved synthetic field shows griddata produces visibly fewer small-scale artifacts. The likely cause:
BicubicResampler’s Npt=16 nearest-neighbour search is discrete — which cells belong to a given
sample’s neighbourhood can flip between one output cell and its immediate neighbour, causing small jumps
even though the weight formula itself is continuous for a fixed neighbour set. A Delaunay/Clough-Tocher
construction doesn’t have this failure mode: adjacent triangles share two vertices, and the macro-element
is built, by construction, to be C1 continuous (value and gradient) across shared edges.
What “Clough-Tocher” means here#
Local gnomonic (central) projection. Delaunay triangulation and Clough-Tocher patches are inherently planar constructions, so samples are projected from
(lon_deg, lat_deg)to a local tangent plane about the centroid of the sample set. Gnomonic projection is used specifically because it maps great-circle geodesics to straight lines, so triangle edges in the projected plane correspond to true geodesics on the sphere. This distorts for large angular extents — this resampler is intended for regional/local input extents (a single patch, not a global dataset in one construction call);__init__raises if any sample falls outside the projection’s well-behaved hemisphere around the centroid. Seedocs/user-guide/regrid_to_healpix_parent_cell_subsetting.mdfor processing a large/global dataset one local patch at a time.Delaunay triangulation (
scipy.spatial.Delaunay, CPU/NumPy — the one unavoidable non-torch step; no mature GPU-native Delaunay library exists) of the projected samples.Gradient estimation: a local least-squares plane fit at each triangulation vertex, using that vertex’s direct Delaunay 1-ring neighbours. Precomputed as two sparse
(N, N)matricesGx,Gy(geometry only). This is not claimed to be bit-identical toscipy.interpolate.CloughTocher2DInterpolator, which uses a different (Nielson-style) gradient estimator — it’s a correct, standard Clough-Tocher construction, not a scipy clone.Output validity = inside the convex hull. Like
scipy.interpolate.griddata, this resampler does not extrapolate: a HEALPix cell is only kept incell_idsif its projected center falls inside the Delaunay triangulation.Per-triangle Clough-Tocher macro-element: each triangle is split into 3 micro-triangles by connecting its vertices to the centroid; each micro-triangle carries a cubic Bezier patch built from corner values/gradients and a construction that guarantees C1 continuity across the internal split edges and across each triangle’s shared edges with its neighbours.
Everything except the Delaunay triangulation itself is precompiled into one sparse (N, K) matrix M, so
resample(val) is a single batched sparse matmul (hval = y @ M), matching every other resampler in this
package.
Class hierarchy and construction#
CloughTocherResampler does not subclass KNeighborsResampler — that base class’s KNN/Gaussian-
threshold geometry model (Npt, sigma_m, threshold) doesn’t apply to a triangulation-based
construction.
from healpix_resample import CloughTocherResampler
op = CloughTocherResampler(
lon_deg=lon, # (N,) sample longitudes, degrees -- regional/local extent
lat_deg=lat, # (N,) sample latitudes, degrees
level=level, # HEALPix level, nside = 2**level
device="cuda",
dtype=torch.float64,
nest=True,
)
Key parameters#
lon_deg, lat_deg: unstructured sample coordinates in degrees, shape(N,). Must span a regional/local extent (see above).level: HEALPix level (nside = 2**level).nest,ellipsoid: same meaning as every other resampler.device,dtype: PyTorch placement and numerical type forGx,Gy,M.out_cell_ids: optional caller-supplied subset of output cell ids, intersected with the convex-hull criterion.candidate_ring_expand: how many fine-levelHEALPix rings around the sample footprint to consider as candidate output cells before filtering to “inside the convex hull” — doesn’t affect correctness (over-generous candidates are simply dropped by the hull test), only whether the hull is fully covered.
Stored attributes#
N,K,cell_ids: as in every other resampler.points2d:(N, 2)NumPy array — samples projected to the local gnomonic tangent plane.tri: the underlyingscipy.spatial.Delaunaytriangulation.Gx,Gy: sparse(N, N)torch tensors —grad_x = Gx @ f,grad_y = Gy @ ffor any sample-space fieldf.M: sparse CSR(N, K)torch tensor —hval = y @ M.
Methods#
resample(val)#
Sample space → HEALPix cell space: hval = val @ self.M. Accepts val of shape (N,) or (B, N),
np.ndarray or torch.Tensor; returns the same type/batch shape, wrapped in
ResampleResults(cell_data, cell_ids).
invert(hval)#
Not implemented. Unlike the KNN-based resamplers (which get a natural MT “for free” from a
symmetric neighbour search), Delaunay/Clough-Tocher only defines a mapping from scattered samples to
arbitrary query points, not the reverse. Calling invert() raises NotImplementedError with a pointer
to planning/05_clough_tocher_resampler.md for the reasoning.
Notes and practical tips#
No benefit over
BicubicResampler/BilinearResampleron a perfectly affine field — all interpolate a plane exactly; the advantage shows up on fields with genuine curvature.Cells outside the sample convex hull are silently excluded from
cell_ids, not given a garbage extrapolated value.For a large/global dataset, use
subset_for_parent_cell(seedocs/user-guide/regrid_to_healpix_parent_cell_subsetting.md) to process one regional patch at a time — this resampler is not designed to be constructed once over a global dataset.