base/INTERPOLATION.md
The smile.interpolation package provides a comprehensive suite of interpolation
methods for one-dimensional, two-dimensional regular-grid, and scattered-data
problems.
Interpolation constructs a function that passes exactly through a set of
known data points (x_i, y_i). This is different from regression, which only
requires the function to be close to the data.
| Interface | Purpose |
|---|---|
Interpolation | 1-D interpolant — double interpolate(double x) |
Interpolation2D | 2-D interpolant — double interpolate(double x1, double x2) |
Both interfaces extend Serializable, so interpolants can be persisted and
reloaded.
All AbstractInterpolation subclasses automatically detect whether consecutive
calls are correlated (moving across the table sequentially). When they are, a
fast hunt algorithm is used; otherwise a full bisection search is
performed. This is invisible to the caller but makes dense sequential evaluation
significantly faster.
These methods require the x-values to be monotone (either increasing or decreasing).
Piecewise linear interpolation — each pair of adjacent knots is joined by a straight line segment.
| Property | Value |
|---|---|
| Continuity | C⁰ (function, but not derivative, is continuous at knots) |
| Complexity | O(log n) per query (bisection) |
| Extrapolation | Extends the slope of the nearest end segment |
| Best for | Simple, fast approximations; step-function-like data |
import smile.interpolation.LinearInterpolation;
double[] x = {0, 1, 2, 3, 4, 5, 6};
double[] y = {0, 0.8415, 0.9093, 0.1411, -0.7568, -0.9589, -0.2794};
LinearInterpolation interp = new LinearInterpolation(x, y);
System.out.println(interp.interpolate(2.5)); // ≈ 0.5252
System.out.println(interp.interpolate(0.0)); // 0.0 (exact at knot)
Natural cubic spline — piecewise cubic polynomials with continuous first and second derivatives. The "natural" boundary condition sets the second derivative to zero at both endpoints.
| Property | Value |
|---|---|
| Continuity | C² (function, first and second derivatives are continuous) |
| Complexity | O(n) construction; O(log n) per query |
| Extrapolation | Extends the cubic polynomial at the nearest end segment |
| Best for | Smooth functions; much better approximation than linear for C² data |
import smile.interpolation.CubicSplineInterpolation1D;
double[] x = {0, 1, 2, 3, 4, 5, 6};
double[] y = {0, 0.8415, 0.9093, 0.1411, -0.7568, -0.9589, -0.2794};
CubicSplineInterpolation1D spline = new CubicSplineInterpolation1D(x, y);
System.out.println(spline.interpolate(2.5)); // ≈ 0.5962 (closer to sin(2.5)≈0.5985)
double trueValue = Math.sin(2.5); // 0.5985
double errLinear = Math.abs(linear.interpolate(2.5) - trueValue); // ≈ 0.073
double errCubic = Math.abs(spline.interpolate(2.5) - trueValue); // ≈ 0.002
The cubic spline is about 35× more accurate here.
The natural (zero-second-derivative) boundary conditions mean the spline does not reproduce polynomials of degree > 1 exactly near the endpoints. For functions with known non-zero second derivatives at the boundary, consider clamped (specified-derivative) boundary conditions if your application requires that precision.
All three 2-D methods require the data to lie on a rectangular grid defined
by two monotone coordinate arrays x1[] and x2[] and a value matrix
y[x1.length][x2.length].
Extends linear interpolation to two dimensions by performing one linear interpolation per dimension in sequence.
| Property | Value |
|---|---|
| Continuity | C⁰ inside cells; discontinuous first derivatives across cell boundaries |
| Best for | Fast lookup on dense grids; image scaling |
import smile.interpolation.BilinearInterpolation;
double[] x1 = {1950, 1960, 1970, 1980, 1990};
double[] x2 = {10, 20, 30};
double[][] y = {
{150.697, 199.592, 187.625},
{179.323, 195.072, 250.287},
{203.212, 179.092, 322.767},
{226.505, 153.706, 426.730},
{249.633, 120.281, 598.243}
};
BilinearInterpolation interp = new BilinearInterpolation(x1, x2, y);
System.out.println(interp.interpolate(1975, 15)); // ≈ 190.6
At the midpoint of a cell, bilinear interpolation returns the exact average of the four corner values.
Uses a 4×4 neighbourhood of grid values at each query, computing 16 coefficients
that guarantee continuity of the value, both first partial derivatives, and the
cross-derivative ∂²f/∂x₁∂x₂.
| Property | Value |
|---|---|
| Continuity | C¹ plus cross-derivative; second partial derivatives may be discontinuous |
| Best for | Image resampling, smooth surface rendering |
| Requirement | At least 4 × 4 grid points |
import smile.interpolation.BicubicInterpolation;
BicubicInterpolation interp = new BicubicInterpolation(x1, x2, y);
// At a knot — exact reproduction
System.out.println(interp.interpolate(1970, 10)); // 203.212
// Interior point — smoother than bilinear
System.out.println(interp.interpolate(1975, 15)); // ≈ 175.7
Applies 1-D natural cubic splines first along x2 at each x1 row, then along x1 through the intermediate results. This guarantees continuity of both first and second partial derivatives, unlike bicubic interpolation.
| Property | Value |
|---|---|
| Continuity | C² in each dimension separately |
| Best for | Scientific data that is known to be twice-differentiable |
| Requirement | At least 2 points in each dimension |
import smile.interpolation.CubicSplineInterpolation2D;
CubicSplineInterpolation2D interp = new CubicSplineInterpolation2D(x1, x2, y);
System.out.println(interp.interpolate(1975, 15)); // ≈ 168.0
System.out.println(interp.interpolate(1975, 25)); // ≈ 244.3
| Method | Smoothness | Speed | Artifacts |
|---|---|---|---|
| Bilinear | C⁰ | Fastest | Visible edges at cell boundaries |
| Bicubic | C¹ + cross-deriv | Fast | Subtle ringing near sharp changes |
| Cubic Spline 2D | C² per-axis | Moderate | Fewest near smooth data |
These methods work for data points that are irregularly distributed in space
— no grid is required. They all implement the interpolate(double... x) pattern
(variadic double[] input) rather than a fixed-arity 2-D call.
Shepard (inverse-distance weighting) interpolation is the simplest scattered-data
method. The interpolated value at a query point x is a weighted average of all
training values:
f(x) = Σ w_i(x) y_i / Σ w_i(x), w_i(x) = ||x - x_i||^{-p}
Because w_i → ∞ as x → x_i, the interpolant passes exactly through every
training point.
| Property | Value |
|---|---|
| Complexity | O(n) per query |
| Default p | 2 |
| Recommended p | 1 < p ≤ 3 |
| Best for | Quick, allocation-free scattered-data approximation for large n |
Multi-dimensional:
import smile.interpolation.ShepardInterpolation;
double[][] x = {{0, 0}, {1, 0}, {0, 1}, {1, 1}};
double[] y = {0.0, 1.0, 1.0, 2.0};
ShepardInterpolation interp = new ShepardInterpolation(x, y);
System.out.println(interp.interpolate(0.5, 0.5)); // ≈ 1.0
System.out.println(interp.interpolate(0.0, 0.0)); // 0.0 (exact at knot)
1-D convenience class:
import smile.interpolation.ShepardInterpolation1D;
double[] x = {0, 1, 2, 3};
double[] y = {0, 1, 0, -1};
ShepardInterpolation1D s = new ShepardInterpolation1D(x, y); // p = 2
ShepardInterpolation1D s3 = new ShepardInterpolation1D(x, y, 3.0); // p = 3
2-D convenience class (ShepardInterpolation2D) accepts two separate
double[] coordinate arrays for the x₁ and x₂ dimensions.
| p | Behaviour |
|---|---|
| p = 1 | Smoother, more global influence |
| p = 2 | Good general default |
| p = 3 | More local; sharp transitions near knots |
Higher p makes the interpolant look more like nearest-neighbour as p → ∞.
Radial basis function (RBF) interpolation expresses the interpolant as a linear combination of radial basis functions centred at the training points:
f(x) = Σ w_i φ(||x - x_i||)
The weights w_i are found by solving the linear system G w = y, where
G[i][j] = φ(||x_i - x_j||) is the Gram matrix.
A normalized variant (NRBF) divides by the sum of basis function values, which gives a partition-of-unity property and a Bayesian interpretation:
f(x) = Σ w_i φ(||x - x_i||) / Σ φ(||x - x_i||)
| Property | Value |
|---|---|
| Complexity | O(n²) construction; O(n) per query |
| Exact at knots | Yes |
| Works in any dimension | Yes |
Available radial basis functions (all in smile.math.rbf):
| Class | Formula φ(r) | Properties |
|---|---|---|
GaussianRadialBasis | exp(−r²/2r₀²) | SPD Gram → Cholesky; sensitive to scale r₀ |
MultiquadricRadialBasis | √(r² + r₀²) | Less sensitive to r₀ |
InverseMultiquadricRadialBasis | 1/√(r² + r₀²) | SPD; decays to zero |
ThinPlateRadialBasis | r² log(r/r₀) | Scale-invariant conditioning |
Scale parameter r₀: Choose
r₀to be similar to the typical separation between data points. A value that is too small makes the Gram matrix nearly diagonal (poor approximation at non-knot points); too large makes it nearly rank-deficient.
Multi-dimensional:
import smile.interpolation.RBFInterpolation;
import smile.math.rbf.GaussianRadialBasis;
double[][] x = {{0, 0}, {1, 0}, {0, 1}, {1, 1}};
double[] y = {0.0, 1.0, 1.0, 2.0};
RBFInterpolation interp = new RBFInterpolation(x, y, new GaussianRadialBasis());
System.out.println(interp.interpolate(0.0, 0.0)); // 0.0 (exact at knot)
System.out.println(interp.interpolate(0.5, 0.5)); // ≈ 0.57
Normalized multi-dimensional:
RBFInterpolation norm = new RBFInterpolation(x, y, new GaussianRadialBasis(), true);
1-D convenience class (with correct scale for stability):
import smile.interpolation.RBFInterpolation1D;
double[] x = {0, 3, 6, 9, 12};
double[] y = {0.0, 0.14, -0.28, 0.41, -0.54};
// r0 = point spacing = 3, giving a well-conditioned Gram matrix
RBFInterpolation1D interp = new RBFInterpolation1D(x, y, new GaussianRadialBasis(3.0));
System.out.println(interp.interpolate(4.5)); // smooth value between knots
2-D convenience class (RBFInterpolation2D) accepts two separate double[]
coordinate arrays.
Kriging (also called Gaussian process regression) is a statistical interpolation method that models the unknown function as a random field and finds the best linear unbiased estimator (BLUE). The prediction at a new point minimises the expected squared error subject to an unbiasedness constraint.
The key ingredient is a variogram γ(r) that describes how variance grows
with separation distance r. SMILE implements ordinary kriging with a
configurable variogram.
| Property | Value |
|---|---|
| Complexity | O(n³) construction (SVD); O(n) per query |
| Exact at knots | Yes (interpolation mode, no noise) |
| Supports measurement error | Yes — pass error variances as double[] error |
| Best for | Spatial statistics; geo-statistics; any domain where variance structure is known |
Multi-dimensional (default power variogram):
import smile.interpolation.KrigingInterpolation;
double[][] x = {{0, 0}, {1, 0}, {0, 1}, {1, 1}, {0.5, 0.5}};
double[] y = {0, 1, 1, 2, 1};
KrigingInterpolation kriging = new KrigingInterpolation(x, y);
System.out.println(kriging.interpolate(0.5, 0.5)); // ≈ 1.0
With a custom variogram:
import smile.interpolation.variogram.GaussianVariogram;
var variogram = new GaussianVariogram(/*range=*/1.0, /*sill=*/2.0);
KrigingInterpolation kriging = new KrigingInterpolation(x, y, variogram, null);
With measurement errors (smoothing kriging):
double[] errors = {0.05, 0.05, 0.05, 0.05, 0.05}; // sqrt of noise variance
KrigingInterpolation kriging = new KrigingInterpolation(x, y, variogram, errors);
// No longer passes exactly through knots; smoothed by the error model
1-D convenience class:
import smile.interpolation.KrigingInterpolation1D;
double[] x = {0, 1, 2, 3, 4};
double[] y = {0, 1, 4, 9, 16};
// Default β = 1.5; valid range: 1 ≤ β < 2
KrigingInterpolation1D k = new KrigingInterpolation1D(x, y);
// With a custom β (larger β → stronger linear trend assumption)
KrigingInterpolation1D k2 = new KrigingInterpolation1D(x, y, 1.9);
2-D convenience class (KrigingInterpolation2D) accepts two separate
double[] coordinate arrays and an optional beta parameter.
LaplaceInterpolation fills missing values (represented as Double.NaN) in
a 2-D regular grid by solving the discrete Laplace equation:
∇²f = 0 (at every missing cell)
The Laplace solution produces the smoothest possible interpolant — it minimises the integral of the squared Laplacian, which is the same objective as a thin plate spline. The system is solved iteratively using the biconjugate gradient (BiCG) method, which is efficient for the very sparse stencil.
| Property | Value |
|---|---|
| Input | A 2-D double[][] array; NaN marks missing entries |
| Output | Modified in-place; also returns estimated error |
| Known values | Preserved exactly |
| Best for | Image inpainting; restoring missing sensor readings; gap filling |
import smile.interpolation.LaplaceInterpolation;
double[][] grid = {
{1.0, 2.0, Double.NaN, 4.0},
{2.0, Double.NaN, Double.NaN, 5.0},
{3.0, 4.0, 5.0, 6.0}
};
double error = LaplaceInterpolation.interpolate(grid);
System.out.printf("Converged with error = %.2e%n", error);
// grid now has NaN cells filled with smooth values
// With custom tolerance and iteration limit:
LaplaceInterpolation.interpolate(grid, 1e-8, 500);
The Laplace operator applied to any linear function f(i,j) = ai + bj + c
gives zero. Therefore, if the boundary is linear, all interior values will be
recovered exactly.
Variograms are used by KrigingInterpolation to model spatial dependence.
All variograms implement Variogram (which extends smile.util.function.Function)
via double f(double r).
γ(r) = nugget + α r^β, 1 ≤ β < 2
α is estimated from the data by unweighted least squares. The default β = 1.5
is a good general choice; use β closer to 2 for strongly linear spatial trends.
import smile.interpolation.variogram.PowerVariogram;
// α estimated automatically; β = 1.5; no nugget
PowerVariogram v = new PowerVariogram(x, y);
// Custom β with nugget
PowerVariogram v2 = new PowerVariogram(x, y, 1.8, 0.1);
γ(r) = c + b (1 − exp(−3r²/a²))
Reaches plateau (sill b + c) at approximately r = a (range).
import smile.interpolation.variogram.GaussianVariogram;
// range=2, sill=5, no nugget
GaussianVariogram v = new GaussianVariogram(2.0, 5.0);
// With nugget effect c=0.3
GaussianVariogram v2 = new GaussianVariogram(2.0, 5.0, 0.3);
γ(r) = c + b (1 − exp(−3r/a))
Rises faster near the origin than Gaussian; approaches sill asymptotically.
import smile.interpolation.variogram.ExponentialVariogram;
ExponentialVariogram v = new ExponentialVariogram(/*range=*/3.0, /*sill=*/4.0);
γ(r) = c + b (1.5 r/a − 0.5 (r/a)³) for r ≤ a
γ(r) = c + b for r > a
Unlike Gaussian and Exponential, the spherical model reaches its sill exactly
at the range parameter a.
import smile.interpolation.variogram.SphericalVariogram;
SphericalVariogram v = new SphericalVariogram(/*range=*/5.0, /*sill=*/3.0);
System.out.println(v.f(5.0)); // exactly 3.0 (sill reached)
System.out.println(v.f(10.0)); // also 3.0 (flat beyond range)
| Parameter | Meaning |
|---|---|
| range (a) | Distance at which spatial correlation becomes negligible |
| sill (b) | Maximum variogram value (variance of the process) |
| nugget (c) | Discontinuity at origin; represents micro-scale variability or measurement error |
Data lies on a regular 1-D grid?
├─ Need smoothness / accuracy → CubicSplineInterpolation1D
└─ Need speed / simplicity → LinearInterpolation
Data lies on a regular 2-D grid?
├─ Need C² smoothness → CubicSplineInterpolation2D
├─ Need C¹ + cross-deriv → BicubicInterpolation
└─ Need speed / simplicity → BilinearInterpolation
Data is scattered (irregular)?
├─ Large n, quick results → ShepardInterpolation
├─ Need statistical model → KrigingInterpolation
└─ Need smooth approximation → RBFInterpolation
Regular grid with missing values?
└─ LaplaceInterpolation
| Method | Accuracy | Construction | Query |
|---|---|---|---|
LinearInterpolation | Low | O(1) | O(log n) |
CubicSplineInterpolation1D | High | O(n) | O(log n) |
BilinearInterpolation | Low | O(1) | O(log n + log m) |
BicubicInterpolation | Medium | O(nm) | O(log n + log m) |
CubicSplineInterpolation2D | High | O(nm) | O(log nm) |
ShepardInterpolation | Medium | O(1) | O(n) |
RBFInterpolation | High | O(n³) | O(n) |
KrigingInterpolation | High | O(n³) | O(n) |
| Dimension | Recommended class |
|---|---|
| 1-D, grid | LinearInterpolation, CubicSplineInterpolation1D |
| 1-D, scattered | ShepardInterpolation1D, RBFInterpolation1D, KrigingInterpolation1D |
| 2-D, grid | BilinearInterpolation, BicubicInterpolation, CubicSplineInterpolation2D |
| 2-D, scattered | ShepardInterpolation2D, RBFInterpolation2D, KrigingInterpolation2D |
| n-D, scattered | ShepardInterpolation, RBFInterpolation, KrigingInterpolation |
| Class | Data layout | Dim | Exact at knots | Smoothness | Notes |
|---|---|---|---|---|---|
LinearInterpolation | Grid | 1-D | Yes | C⁰ | Fastest 1-D method |
CubicSplineInterpolation1D | Grid | 1-D | Yes | C² | Natural (zero-2nd-deriv) BCs |
BilinearInterpolation | Grid | 2-D | Yes | C⁰ | Bilinear per cell |
BicubicInterpolation | Grid | 2-D | Yes | C¹ + ∂²/∂x∂y | Needs ≥ 4×4 grid |
CubicSplineInterpolation2D | Grid | 2-D | Yes | C² per axis | Tensor-product spline |
ShepardInterpolation | Scattered | n-D | Yes | C∞ off-knot | O(n) query; no construction |
ShepardInterpolation1D | Scattered | 1-D | Yes | C∞ off-knot | 1-D convenience wrapper |
ShepardInterpolation2D | Scattered | 2-D | Yes | C∞ off-knot | 2-D convenience wrapper |
RBFInterpolation | Scattered | n-D | Yes | C∞ off-knot | Solves n×n linear system |
RBFInterpolation1D | Scattered | 1-D | Yes | C∞ off-knot | 1-D convenience wrapper |
RBFInterpolation2D | Scattered | 2-D | Yes | C∞ off-knot | 2-D convenience wrapper |
KrigingInterpolation | Scattered | n-D | Yes (no noise) | C∞ off-knot | Supports measurement errors |
KrigingInterpolation1D | Scattered | 1-D | Yes | C∞ off-knot | Power variogram; β ∈ [1,2) |
KrigingInterpolation2D | Scattered | 2-D | Yes | C∞ off-knot | 2-D convenience wrapper |
LaplaceInterpolation | Grid+NaN | 2-D | Yes (known) | C∞ (harmonic) | Inpainting via BiCG solver |
SMILE — © 2010-2026 Haifeng Li. GNU GPL licensed.