Skip to content

Commit d886899

Browse files
committed
feat: enhance optimal interpolation with coordinate dimension constants and improve validation checks
1 parent 56daed0 commit d886899

4 files changed

Lines changed: 178 additions & 109 deletions

File tree

pyinterp/optimal_interpolation.py

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@
113113
# z axis in 4D ⟵ 3D padding.
114114
_HUGE_LENGTH = 1.0e30
115115

116+
# Public coordinate arrays are 2-D with three columns: ``(x, y, t)`` in
117+
# cartesian mode, ``(lon, lat, t)`` in geographic mode.
118+
_COORDS_NDIM = 2
119+
_COORDS_NCOLS = 3
120+
116121

117122
def _kernel_from_r2(r2: np.ndarray, kernel: CovarianceFunction) -> np.ndarray:
118123
"""Evaluate an anisotropic covariance kernel from squared scaled distance.
@@ -127,26 +132,25 @@ def _kernel_from_r2(r2: np.ndarray, kernel: CovarianceFunction) -> np.ndarray:
127132
Covariance values with the same shape as ``r2``.
128133
129134
"""
135+
d = np.sqrt(r2)
130136
if kernel == "gaussian":
131-
return np.exp(-r2)
132-
if kernel == "cauchy":
133-
return 1.0 / (1.0 + r2)
134-
if kernel == "matern_12":
135-
return np.exp(-np.sqrt(r2))
136-
if kernel == "matern_32":
137-
d = np.sqrt(r2)
138-
return (1.0 + _SQRT3 * d) * np.exp(-_SQRT3 * d)
139-
if kernel == "matern_52":
140-
d = np.sqrt(r2)
141-
return (1.0 + _SQRT5 * d + (5.0 / 3.0) * r2) * np.exp(-_SQRT5 * d)
142-
if kernel == "spherical":
143-
d = np.sqrt(r2)
144-
return np.where(d < 1.0, 1.0 - 1.5 * d + 0.5 * d**3, 0.0)
145-
if kernel == "wendland":
146-
d = np.sqrt(r2)
147-
return np.where(d < 1.0, (1.0 - d) ** 2, 0.0)
148-
msg = f"Unknown covariance kernel: {kernel!r}"
149-
raise ValueError(msg)
137+
result = np.exp(-r2)
138+
elif kernel == "cauchy":
139+
result = 1.0 / (1.0 + r2)
140+
elif kernel == "matern_12":
141+
result = np.exp(-d)
142+
elif kernel == "matern_32":
143+
result = (1.0 + _SQRT3 * d) * np.exp(-_SQRT3 * d)
144+
elif kernel == "matern_52":
145+
result = (1.0 + _SQRT5 * d + (5.0 / 3.0) * r2) * np.exp(-_SQRT5 * d)
146+
elif kernel == "spherical":
147+
result = np.where(d < 1.0, 1.0 - 1.5 * d + 0.5 * d**3, 0.0)
148+
elif kernel == "wendland":
149+
result = np.where(d < 1.0, (1.0 - d) ** 2, 0.0)
150+
else:
151+
msg = f"Unknown covariance kernel: {kernel!r}"
152+
raise ValueError(msg)
153+
return result
150154

151155

152156
def _sample_scalar_or_grid(
@@ -200,7 +204,6 @@ def _lla_to_ecef(
200204
lon: np.ndarray, lat: np.ndarray, spheroid: Spheroid | None
201205
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
202206
"""Convert (lon, lat, alt=0) → (x, y, z) ECEF in meters."""
203-
204207
sph = spheroid if spheroid is not None else _S()
205208
coords = Coordinates(sph)
206209
alt = np.zeros_like(lon)
@@ -320,15 +323,16 @@ def __init__(
320323
spheroid: Spheroid | None = None,
321324
time_scale: float = 1.0,
322325
) -> None:
326+
"""Index the observations and build the internal 4D R-tree."""
323327
obs_coords = np.ascontiguousarray(obs_coords, dtype=np.float64)
324328
obs_values = np.ascontiguousarray(obs_values, dtype=np.float64)
325329
obs_sigma2 = np.ascontiguousarray(obs_sigma2, dtype=np.float64)
326330

327-
if obs_coords.ndim != 2 or obs_coords.shape[1] != 3:
328-
msg = (
329-
"obs_coords must have shape (N, 3); got "
330-
f"{obs_coords.shape}"
331-
)
331+
if (
332+
obs_coords.ndim != _COORDS_NDIM
333+
or obs_coords.shape[1] != _COORDS_NCOLS
334+
):
335+
msg = f"obs_coords must have shape (N, 3); got {obs_coords.shape}"
332336
raise ValueError(msg)
333337
n = obs_coords.shape[0]
334338
if obs_values.shape != (n,):
@@ -407,7 +411,7 @@ def n_observations(self) -> int:
407411
"""Number of indexed observations."""
408412
return self._obs_coords.shape[0]
409413

410-
def __call__(
414+
def __call__( # noqa: PLR0915
411415
self,
412416
query_coords: NDArray2DFloat64,
413417
*,
@@ -452,7 +456,10 @@ def __call__(
452456
453457
"""
454458
query_coords = np.ascontiguousarray(query_coords, dtype=np.float64)
455-
if query_coords.ndim != 2 or query_coords.shape[1] != 3:
459+
if (
460+
query_coords.ndim != _COORDS_NDIM
461+
or query_coords.shape[1] != _COORDS_NCOLS
462+
):
456463
msg = (
457464
"query_coords must have shape (M, 3); got "
458465
f"{query_coords.shape}"

0 commit comments

Comments
 (0)