| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812 |
- __all__ = ['RegularGridInterpolator', 'interpn']
- import itertools
- from types import GenericAlias
- import numpy as np
- import scipy.sparse.linalg as ssl
- from scipy._lib._array_api import array_namespace, xp_capabilities
- from scipy._lib.array_api_compat import is_array_api_obj
- from ._interpnd import _ndim_coords_from_arrays
- from ._cubic import PchipInterpolator
- from ._rgi_cython import evaluate_linear_2d, find_indices
- from ._bsplines import make_interp_spline
- from ._fitpack2 import RectBivariateSpline
- from ._ndbspline import make_ndbspl
- def _check_points(points):
- descending_dimensions = []
- grid = []
- for i, p in enumerate(points):
- # early make points float
- # see https://github.com/scipy/scipy/pull/17230
- p = np.asarray(p, dtype=float)
- if not np.all(p[1:] > p[:-1]):
- if np.all(p[1:] < p[:-1]):
- # input is descending, so make it ascending
- descending_dimensions.append(i)
- p = np.flip(p)
- else:
- raise ValueError(
- f"The points in dimension {i} must be strictly ascending or "
- f"descending"
- )
- # see https://github.com/scipy/scipy/issues/17716
- p = np.ascontiguousarray(p)
- grid.append(p)
- return tuple(grid), tuple(descending_dimensions)
- def _check_dimensionality(points, values):
- if len(points) > values.ndim:
- raise ValueError(
- f"There are {len(points)} point arrays, but values has "
- f"{values.ndim} dimensions"
- )
- for i, p in enumerate(points):
- if not np.asarray(p).ndim == 1:
- raise ValueError(f"The points in dimension {i} must be 1-dimensional")
- if not values.shape[i] == len(p):
- raise ValueError(
- f"There are {len(p)} points and {values.shape[i]} values in "
- f"dimension {i}"
- )
- @xp_capabilities(
- cpu_only=True, jax_jit=False,
- skip_backends=[
- ("dask.array",
- "https://github.com/data-apis/array-api-extra/issues/488")
- ]
- )
- class RegularGridInterpolator:
- """Interpolator of specified order on a rectilinear grid in N ≥ 1 dimensions.
- The data must be defined on a rectilinear grid; that is, a rectangular
- grid with even or uneven spacing. Linear, nearest-neighbor, spline
- interpolations are supported. After setting up the interpolator object,
- the interpolation method may be chosen at each evaluation.
- Parameters
- ----------
- points : tuple of ndarray of float, with shapes (m1, ), ..., (mn, )
- The points defining the regular grid in n dimensions. The points in
- each dimension (i.e. every elements of the points tuple) must be
- strictly ascending or descending.
- values : array_like, shape (m1, ..., mn, ...)
- The data on the regular grid in n dimensions. Complex data is
- accepted.
- method : str, optional
- The method of interpolation to perform. Supported are "linear",
- "nearest", "slinear", "cubic", "quintic" and "pchip". This
- parameter will become the default for the object's ``__call__``
- method. Default is "linear".
- bounds_error : bool, optional
- If True, when interpolated values are requested outside of the
- domain of the input data, a ValueError is raised.
- If False, then `fill_value` is used.
- Default is True.
- fill_value : float or None, optional
- The value to use for points outside of the interpolation domain.
- If None, values outside the domain are extrapolated.
- Default is ``np.nan``.
- solver : callable, optional
- Only used for methods "slinear", "cubic" and "quintic".
- Sparse linear algebra solver for construction of the NdBSpline instance.
- Default is the iterative solver `scipy.sparse.linalg.gcrotmk`.
- .. versionadded:: 1.13
- solver_args: dict, optional
- Additional arguments to pass to `solver`, if any.
- .. versionadded:: 1.13
- Methods
- -------
- __call__
- Attributes
- ----------
- grid : tuple of ndarrays
- The points defining the regular grid in n dimensions.
- This tuple defines the full grid via
- ``np.meshgrid(*grid, indexing='ij')``
- values : ndarray
- Data values at the grid.
- method : str
- Interpolation method.
- fill_value : float or ``None``
- Use this value for out-of-bounds arguments to `__call__`.
- bounds_error : bool
- If ``True``, out-of-bounds argument raise a ``ValueError``.
- Notes
- -----
- Contrary to `LinearNDInterpolator` and `NearestNDInterpolator`, this class
- avoids expensive triangulation of the input data by taking advantage of the
- regular grid structure.
- In other words, this class assumes that the data is defined on a
- *rectilinear* grid.
- .. versionadded:: 0.14
- The 'slinear'(k=1), 'cubic'(k=3), and 'quintic'(k=5) methods are
- tensor-product spline interpolators, where `k` is the spline degree,
- If any dimension has fewer points than `k` + 1, an error will be raised.
- .. versionadded:: 1.9
- If the input data is such that dimensions have incommensurate
- units and differ by many orders of magnitude, the interpolant may have
- numerical artifacts. Consider rescaling the data before interpolating.
- **Choosing a solver for spline methods**
- Spline methods, "slinear", "cubic" and "quintic" involve solving a
- large sparse linear system at instantiation time. Depending on data,
- the default solver may or may not be adequate. When it is not, you may
- need to experiment with an optional `solver` argument, where you may
- choose between the direct solver (`scipy.sparse.linalg.spsolve`) or
- iterative solvers from `scipy.sparse.linalg`. You may need to supply
- additional parameters via the optional `solver_args` parameter (for instance,
- you may supply the starting value or target tolerance). See the
- `scipy.sparse.linalg` documentation for the full list of available options.
- Alternatively, you may instead use the legacy methods, "slinear_legacy",
- "cubic_legacy" and "quintic_legacy". These methods allow faster construction
- but evaluations will be much slower.
- **Rounding rule at half points with `nearest` method**
- The rounding rule with the `nearest` method at half points is rounding *down*.
- Examples
- --------
- **Evaluate a function on the points of a 3-D grid**
- As a first example, we evaluate a simple example function on the points of
- a 3-D grid:
- >>> from scipy.interpolate import RegularGridInterpolator
- >>> import numpy as np
- >>> def f(x, y, z):
- ... return 2 * x**3 + 3 * y**2 - z
- >>> x = np.linspace(1, 4, 11)
- >>> y = np.linspace(4, 7, 22)
- >>> z = np.linspace(7, 9, 33)
- >>> xg, yg ,zg = np.meshgrid(x, y, z, indexing='ij', sparse=True)
- >>> data = f(xg, yg, zg)
- ``data`` is now a 3-D array with ``data[i, j, k] = f(x[i], y[j], z[k])``.
- Next, define an interpolating function from this data:
- >>> interp = RegularGridInterpolator((x, y, z), data)
- Evaluate the interpolating function at the two points
- ``(x,y,z) = (2.1, 6.2, 8.3)`` and ``(3.3, 5.2, 7.1)``:
- >>> pts = np.array([[2.1, 6.2, 8.3],
- ... [3.3, 5.2, 7.1]])
- >>> interp(pts)
- array([ 125.80469388, 146.30069388])
- which is indeed a close approximation to
- >>> f(2.1, 6.2, 8.3), f(3.3, 5.2, 7.1)
- (125.54200000000002, 145.894)
- **Interpolate and extrapolate a 2D dataset**
- As a second example, we interpolate and extrapolate a 2D data set:
- >>> x, y = np.array([-2, 0, 4]), np.array([-2, 0, 2, 5])
- >>> def ff(x, y):
- ... return x**2 + y**2
- >>> xg, yg = np.meshgrid(x, y, indexing='ij')
- >>> data = ff(xg, yg)
- >>> interp = RegularGridInterpolator((x, y), data,
- ... bounds_error=False, fill_value=None)
- >>> import matplotlib.pyplot as plt
- >>> fig = plt.figure()
- >>> ax = fig.add_subplot(projection='3d')
- >>> ax.scatter(xg.ravel(), yg.ravel(), data.ravel(),
- ... s=60, c='k', label='data')
- Evaluate and plot the interpolator on a finer grid
- >>> xx = np.linspace(-4, 9, 31)
- >>> yy = np.linspace(-4, 9, 31)
- >>> X, Y = np.meshgrid(xx, yy, indexing='ij')
- >>> # interpolator
- >>> ax.plot_wireframe(X, Y, interp((X, Y)), rstride=3, cstride=3,
- ... alpha=0.4, color='m', label='linear interp')
- >>> # ground truth
- >>> ax.plot_wireframe(X, Y, ff(X, Y), rstride=3, cstride=3,
- ... alpha=0.4, label='ground truth')
- >>> plt.legend()
- >>> plt.show()
- Other examples are given
- :ref:`in the tutorial <tutorial-interpolate_regular_grid_interpolator>`.
- See Also
- --------
- NearestNDInterpolator : Nearest neighbor interpolator on *unstructured*
- data in N dimensions
- LinearNDInterpolator : Piecewise linear interpolator on *unstructured* data
- in N dimensions
- interpn : a convenience function which wraps `RegularGridInterpolator`
- scipy.ndimage.map_coordinates : interpolation on grids with equal spacing
- (suitable for e.g., N-D image resampling)
- References
- ----------
- .. [1] Python package *regulargrid* by Johannes Buchner, see
- https://pypi.python.org/pypi/regulargrid/
- .. [2] Wikipedia, "Trilinear interpolation",
- https://en.wikipedia.org/wiki/Trilinear_interpolation
- .. [3] Weiser, Alan, and Sergio E. Zarantonello. "A note on piecewise linear
- and multilinear table interpolation in many dimensions." MATH.
- COMPUT. 50.181 (1988): 189-196.
- https://www.ams.org/journals/mcom/1988-50-181/S0025-5718-1988-0917826-0/S0025-5718-1988-0917826-0.pdf
- :doi:`10.1090/S0025-5718-1988-0917826-0`
- """
- # this class is based on code originally programmed by Johannes Buchner,
- # see https://github.com/JohannesBuchner/regulargrid
- _SPLINE_DEGREE_MAP = {"slinear": 1, "cubic": 3, "quintic": 5, 'pchip': 3,
- "slinear_legacy": 1, "cubic_legacy": 3, "quintic_legacy": 5,}
- _SPLINE_METHODS_recursive = {"slinear_legacy", "cubic_legacy",
- "quintic_legacy", "pchip"}
- _SPLINE_METHODS_ndbspl = {"slinear", "cubic", "quintic"}
- _SPLINE_METHODS = list(_SPLINE_DEGREE_MAP.keys())
- _ALL_METHODS = ["linear", "nearest"] + _SPLINE_METHODS
- # generic type compatibility with scipy-stubs
- __class_getitem__ = classmethod(GenericAlias)
- def __init__(self, points, values, method="linear", bounds_error=True,
- fill_value=np.nan, *, solver=None, solver_args=None):
- if method not in self._ALL_METHODS:
- raise ValueError(f"Method '{method}' is not defined")
- elif method in self._SPLINE_METHODS:
- self._validate_grid_dimensions(points, method) # NB: uses np.atleast_1d
- try:
- xp = array_namespace(*points, values)
- except Exception as e:
- # either "duck-type" values or a user error?
- xp = array_namespace(*points) # still forbid mixed namespaces in `points`
- try:
- xp_v = array_namespace(values)
- except Exception:
- # "duck-type" values indeed, continue with `xp` as the namespace
- pass
- else:
- # both `points` and `values` are array API objects, check consistency
- if xp_v != xp:
- raise e
- self._asarray = xp.asarray
- self.method = method
- self._spline = None
- self.bounds_error = bounds_error
- self._grid, self._descending_dimensions = _check_points(points)
- self._values = self._check_values(values)
- self._check_dimensionality(self._grid, self._values)
- self.fill_value = self._check_fill_value(self._values, fill_value)
- if self._descending_dimensions:
- self._values = np.flip(values, axis=self._descending_dimensions)
- if self.method == "pchip" and np.iscomplexobj(self._values):
- msg = ("`PchipInterpolator` only works with real values. If you are trying "
- "to use the real components of the passed array, use `np.real` on "
- "the array before passing to `RegularGridInterpolator`.")
- raise ValueError(msg)
- if method in self._SPLINE_METHODS_ndbspl:
- if solver_args is None:
- solver_args = {}
- self._spline = self._construct_spline(method, solver, **solver_args)
- else:
- if solver is not None or solver_args:
- raise ValueError(
- f"{method =} does not accept the 'solver' argument. Got "
- f" {solver = } and with arguments {solver_args}."
- )
- def _construct_spline(self, method, solver=None, **solver_args):
- if solver is None:
- solver = ssl.gcrotmk
- spl = make_ndbspl(
- self._grid, self._values, self._SPLINE_DEGREE_MAP[method],
- solver=solver, **solver_args
- )
- return spl
- def _check_dimensionality(self, grid, values):
- _check_dimensionality(grid, values)
- def _check_points(self, points):
- return _check_points(points)
- def _check_values(self, values):
- if is_array_api_obj(values):
- values = np.asarray(values)
- if not hasattr(values, 'ndim'):
- # allow reasonable duck-typed values
- values = np.asarray(values)
- if hasattr(values, 'dtype') and hasattr(values, 'astype'):
- if not np.issubdtype(values.dtype, np.inexact):
- values = values.astype(float)
- return values
- def _check_fill_value(self, values, fill_value):
- if fill_value is not None:
- fill_value_dtype = np.asarray(fill_value).dtype
- if (hasattr(values, 'dtype') and not
- np.can_cast(fill_value_dtype, values.dtype,
- casting='same_kind')):
- raise ValueError("fill_value must be either 'None' or "
- "of a type compatible with values")
- return fill_value
- def __call__(self, xi, method=None, *, nu=None):
- """
- Interpolation at coordinates.
- Parameters
- ----------
- xi : ndarray of shape (..., ndim)
- The coordinates to evaluate the interpolator at.
- method : str, optional
- The method of interpolation to perform. Supported are "linear",
- "nearest", "slinear", "cubic", "quintic" and "pchip". Default is
- the method chosen when the interpolator was created.
- nu : sequence of ints, length ndim, optional
- If not None, the orders of the derivatives to evaluate.
- Each entry must be non-negative.
- Only allowed for methods "slinear", "cubic" and "quintic".
- .. versionadded:: 1.13
- Returns
- -------
- values_x : ndarray, shape xi.shape[:-1] + values.shape[ndim:]
- Interpolated values at `xi`. See notes for behaviour when
- ``xi.ndim == 1``.
- Notes
- -----
- In the case that ``xi.ndim == 1`` a new axis is inserted into
- the 0 position of the returned array, values_x, so its shape is
- instead ``(1,) + values.shape[ndim:]``.
- Examples
- --------
- Here we define a nearest-neighbor interpolator of a simple function
- >>> import numpy as np
- >>> x, y = np.array([0, 1, 2]), np.array([1, 3, 7])
- >>> def f(x, y):
- ... return x**2 + y**2
- >>> data = f(*np.meshgrid(x, y, indexing='ij', sparse=True))
- >>> from scipy.interpolate import RegularGridInterpolator
- >>> interp = RegularGridInterpolator((x, y), data, method='nearest')
- By construction, the interpolator uses the nearest-neighbor
- interpolation
- >>> interp([[1.5, 1.3], [0.3, 4.5]])
- array([2., 9.])
- We can however evaluate the linear interpolant by overriding the
- `method` parameter
- >>> interp([[1.5, 1.3], [0.3, 4.5]], method='linear')
- array([ 4.7, 24.3])
- """
- _spline = self._spline
- method = self.method if method is None else method
- is_method_changed = self.method != method
- if method not in self._ALL_METHODS:
- raise ValueError(f"Method '{method}' is not defined")
- if is_method_changed and method in self._SPLINE_METHODS_ndbspl:
- _spline = self._construct_spline(method)
- if nu is not None and method not in self._SPLINE_METHODS_ndbspl:
- raise ValueError(
- f"Can only compute derivatives for methods "
- f"{self._SPLINE_METHODS_ndbspl}, got {method =}."
- )
- xi, xi_shape, ndim, nans, out_of_bounds = self._prepare_xi(xi)
- if method == "linear":
- indices, norm_distances = self._find_indices(xi.T)
- if (ndim == 2 and hasattr(self._values, 'dtype') and
- self._values.ndim == 2 and self._values.flags.writeable and
- self._values.dtype in (np.float64, np.complex128) and
- self._values.dtype.byteorder == '='):
- # until cython supports const fused types, the fast path
- # cannot support non-writeable values
- # a fast path
- out = np.empty(indices.shape[1], dtype=self._values.dtype)
- result = evaluate_linear_2d(self._values,
- indices,
- norm_distances,
- self._grid,
- out)
- else:
- result = self._evaluate_linear(indices, norm_distances)
- elif method == "nearest":
- indices, norm_distances = self._find_indices(xi.T)
- result = self._evaluate_nearest(indices, norm_distances)
- elif method in self._SPLINE_METHODS:
- if is_method_changed:
- self._validate_grid_dimensions(self._grid, method)
- if method in self._SPLINE_METHODS_recursive:
- result = self._evaluate_spline(xi, method)
- else:
- result = _spline(xi, nu=nu)
- if not self.bounds_error and self.fill_value is not None:
- result[out_of_bounds] = self.fill_value
- # f(nan) = nan, if any
- if np.any(nans):
- result[nans] = np.nan
- return self._asarray(result.reshape(xi_shape[:-1] + self._values.shape[ndim:]))
- @property
- def grid(self):
- return tuple(self._asarray(p) for p in self._grid)
- @property
- def values(self):
- return self._asarray(self._values)
- def _prepare_xi(self, xi):
- ndim = len(self._grid)
- xi = _ndim_coords_from_arrays(xi, ndim=ndim)
- if xi.shape[-1] != ndim:
- raise ValueError("The requested sample points xi have dimension "
- f"{xi.shape[-1]} but this "
- f"RegularGridInterpolator has dimension {ndim}")
- xi_shape = xi.shape
- xi = xi.reshape(-1, xi_shape[-1])
- xi = np.asarray(xi, dtype=float)
- # find nans in input
- nans = np.any(np.isnan(xi), axis=-1)
- if self.bounds_error:
- for i, p in enumerate(xi.T):
- if not np.logical_and(np.all(self._grid[i][0] <= p),
- np.all(p <= self._grid[i][-1])):
- raise ValueError(
- f"One of the requested xi is out of bounds in dimension {i}"
- )
- out_of_bounds = None
- else:
- out_of_bounds = self._find_out_of_bounds(xi.T)
- return xi, xi_shape, ndim, nans, out_of_bounds
- def _evaluate_linear(self, indices, norm_distances):
- # slice for broadcasting over trailing dimensions in self._values
- vslice = (slice(None),) + (None,)*(self._values.ndim - len(indices))
- # Compute shifting up front before zipping everything together
- shift_norm_distances = [1 - yi for yi in norm_distances]
- shift_indices = [i + 1 for i in indices]
- # The formula for linear interpolation in 2d takes the form:
- # values = self._values[(i0, i1)] * (1 - y0) * (1 - y1) + \
- # self._values[(i0, i1 + 1)] * (1 - y0) * y1 + \
- # self._values[(i0 + 1, i1)] * y0 * (1 - y1) + \
- # self._values[(i0 + 1, i1 + 1)] * y0 * y1
- # We pair i with 1 - yi (zipped1) and i + 1 with yi (zipped2)
- zipped1 = zip(indices, shift_norm_distances)
- zipped2 = zip(shift_indices, norm_distances)
- # Take all products of zipped1 and zipped2 and iterate over them
- # to get the terms in the above formula. This corresponds to iterating
- # over the vertices of a hypercube.
- hypercube = itertools.product(*zip(zipped1, zipped2))
- value = np.array([0.])
- for h in hypercube:
- edge_indices, weights = zip(*h)
- weight = np.array([1.])
- for w in weights:
- weight = weight * w
- term = np.asarray(self._values[edge_indices]) * weight[vslice]
- value = value + term # cannot use += because broadcasting
- return value
- def _evaluate_nearest(self, indices, norm_distances):
- idx_res = [np.where(yi <= .5, i, i + 1)
- for i, yi in zip(indices, norm_distances)]
- return self._values[tuple(idx_res)]
- def _validate_grid_dimensions(self, points, method):
- k = self._SPLINE_DEGREE_MAP[method]
- for i, point in enumerate(points):
- ndim = len(np.atleast_1d(point))
- if ndim <= k:
- raise ValueError(f"There are {ndim} points in dimension {i},"
- f" but method {method} requires at least "
- f" {k+1} points per dimension.")
- def _evaluate_spline(self, xi, method):
- # ensure xi is 2D list of points to evaluate (`m` is the number of
- # points and `n` is the number of interpolation dimensions,
- # ``n == len(self._grid)``.)
- if xi.ndim == 1:
- xi = xi.reshape((1, xi.size))
- m, n = xi.shape
- # Reorder the axes: n-dimensional process iterates over the
- # interpolation axes from the last axis downwards: E.g. for a 4D grid
- # the order of axes is 3, 2, 1, 0. Each 1D interpolation works along
- # the 0th axis of its argument array (for 1D routine it's its ``y``
- # array). Thus permute the interpolation axes of `values` *and keep
- # trailing dimensions trailing*.
- axes = tuple(range(self._values.ndim))
- axx = axes[:n][::-1] + axes[n:]
- values = self._values.transpose(axx)
- if method == 'pchip':
- _eval_func = self._do_pchip
- else:
- _eval_func = self._do_spline_fit
- k = self._SPLINE_DEGREE_MAP[method]
- # Non-stationary procedure: difficult to vectorize this part entirely
- # into numpy-level operations. Unfortunately this requires explicit
- # looping over each point in xi.
- # can at least vectorize the first pass across all points in the
- # last variable of xi.
- last_dim = n - 1
- first_values = _eval_func(self._grid[last_dim],
- values,
- xi[:, last_dim],
- k)
- # the rest of the dimensions have to be on a per point-in-xi basis
- shape = (m, *self._values.shape[n:])
- result = np.empty(shape, dtype=self._values.dtype)
- for j in range(m):
- # Main process: Apply 1D interpolate in each dimension
- # sequentially, starting with the last dimension.
- # These are then "folded" into the next dimension in-place.
- folded_values = first_values[j, ...]
- for i in range(last_dim-1, -1, -1):
- # Interpolate for each 1D from the last dimensions.
- # This collapses each 1D sequence into a scalar.
- folded_values = _eval_func(self._grid[i],
- folded_values,
- xi[j, i],
- k)
- result[j, ...] = folded_values
- return result
- @staticmethod
- def _do_spline_fit(x, y, pt, k):
- local_interp = make_interp_spline(x, y, k=k, axis=0)
- values = local_interp(pt)
- return values
- @staticmethod
- def _do_pchip(x, y, pt, k):
- local_interp = PchipInterpolator(x, y, axis=0)
- values = local_interp(pt)
- return values
- def _find_indices(self, xi):
- return find_indices(self._grid, xi)
- def _find_out_of_bounds(self, xi):
- # check for out of bounds xi
- out_of_bounds = np.zeros((xi.shape[1]), dtype=bool)
- # iterate through dimensions
- for x, grid in zip(xi, self._grid):
- out_of_bounds += x < grid[0]
- out_of_bounds += x > grid[-1]
- return out_of_bounds
- def interpn(points, values, xi, method="linear", bounds_error=True,
- fill_value=np.nan):
- """
- Multidimensional interpolation on regular or rectilinear grids.
- Strictly speaking, not all regular grids are supported - this function
- works on *rectilinear* grids, that is, a rectangular grid with even or
- uneven spacing.
- Parameters
- ----------
- points : tuple of ndarray of float, with shapes (m1, ), ..., (mn, )
- The points defining the regular grid in n dimensions. The points in
- each dimension (i.e. every elements of the points tuple) must be
- strictly ascending or descending.
- values : array_like, shape (m1, ..., mn, ...)
- The data on the regular grid in n dimensions. Complex data is
- accepted.
- xi : ndarray of shape (..., ndim)
- The coordinates to sample the gridded data at
- method : str, optional
- The method of interpolation to perform. Supported are "linear",
- "nearest", "slinear", "cubic", "quintic", "pchip", and "splinef2d".
- "splinef2d" is only supported for 2-dimensional data.
- bounds_error : bool, optional
- If True, when interpolated values are requested outside of the
- domain of the input data, a ValueError is raised.
- If False, then `fill_value` is used.
- fill_value : number, optional
- If provided, the value to use for points outside of the
- interpolation domain. If None, values outside
- the domain are extrapolated. Extrapolation is not supported by method
- "splinef2d".
- Returns
- -------
- values_x : ndarray, shape xi.shape[:-1] + values.shape[ndim:]
- Interpolated values at `xi`. See notes for behaviour when
- ``xi.ndim == 1``.
- See Also
- --------
- NearestNDInterpolator : Nearest neighbor interpolation on unstructured
- data in N dimensions
- LinearNDInterpolator : Piecewise linear interpolant on unstructured data
- in N dimensions
- RegularGridInterpolator : interpolation on a regular or rectilinear grid
- in arbitrary dimensions (`interpn` wraps this
- class).
- RectBivariateSpline : Bivariate spline approximation over a rectangular mesh
- scipy.ndimage.map_coordinates : interpolation on grids with equal spacing
- (suitable for e.g., N-D image resampling)
- Notes
- -----
- .. versionadded:: 0.14
- In the case that ``xi.ndim == 1`` a new axis is inserted into
- the 0 position of the returned array, values_x, so its shape is
- instead ``(1,) + values.shape[ndim:]``.
- If the input data is such that input dimensions have incommensurate
- units and differ by many orders of magnitude, the interpolant may have
- numerical artifacts. Consider rescaling the data before interpolation.
- Examples
- --------
- Evaluate a simple example function on the points of a regular 3-D grid:
- >>> import numpy as np
- >>> from scipy.interpolate import interpn
- >>> def value_func_3d(x, y, z):
- ... return 2 * x + 3 * y - z
- >>> x = np.linspace(0, 4, 5)
- >>> y = np.linspace(0, 5, 6)
- >>> z = np.linspace(0, 6, 7)
- >>> points = (x, y, z)
- >>> values = value_func_3d(*np.meshgrid(*points, indexing='ij'))
- Evaluate the interpolating function at a point
- >>> point = np.array([2.21, 3.12, 1.15])
- >>> print(interpn(points, values, point))
- [12.63]
- Compare with value at point by function
- >>> value_func_3d(*point)
- 12.63 # up to rounding
- Since the function is linear, the interpolation is exact using linear method.
- """
- # sanity check 'method' kwarg
- if method not in ["linear", "nearest", "cubic", "quintic", "pchip",
- "splinef2d", "slinear",
- "slinear_legacy", "cubic_legacy", "quintic_legacy"]:
- raise ValueError("interpn only understands the methods 'linear', "
- "'nearest', 'slinear', 'cubic', 'quintic', 'pchip', "
- f"and 'splinef2d'. You provided {method}.")
- if not hasattr(values, 'ndim'):
- values = np.asarray(values)
- ndim = values.ndim
- if ndim > 2 and method == "splinef2d":
- raise ValueError("The method splinef2d can only be used for "
- "2-dimensional input data")
- if not bounds_error and fill_value is None and method == "splinef2d":
- raise ValueError("The method splinef2d does not support extrapolation.")
- # sanity check consistency of input dimensions
- if len(points) > ndim:
- raise ValueError(
- f"There are {len(points)} point arrays, but values has {ndim} dimensions"
- )
- if len(points) != ndim and method == 'splinef2d':
- raise ValueError("The method splinef2d can only be used for "
- "scalar data with one point per coordinate")
- grid, descending_dimensions = _check_points(points)
- _check_dimensionality(grid, values)
- # sanity check requested xi
- xi = _ndim_coords_from_arrays(xi, ndim=len(grid))
- if xi.shape[-1] != len(grid):
- raise ValueError(
- f"The requested sample points xi have dimension {xi.shape[-1]}, "
- f"but this RegularGridInterpolator has dimension {len(grid)}"
- )
- if bounds_error:
- for i, p in enumerate(xi.T):
- if not np.logical_and(np.all(grid[i][0] <= p),
- np.all(p <= grid[i][-1])):
- raise ValueError(
- f"One of the requested xi is out of bounds in dimension {i}"
- )
- # perform interpolation
- if method in RegularGridInterpolator._ALL_METHODS:
- interp = RegularGridInterpolator(points, values, method=method,
- bounds_error=bounds_error,
- fill_value=fill_value)
- return interp(xi)
- elif method == "splinef2d":
- xi_shape = xi.shape
- xi = xi.reshape(-1, xi.shape[-1])
- # RectBivariateSpline doesn't support fill_value; we need to wrap here
- idx_valid = np.all((grid[0][0] <= xi[:, 0], xi[:, 0] <= grid[0][-1],
- grid[1][0] <= xi[:, 1], xi[:, 1] <= grid[1][-1]),
- axis=0)
- result = np.empty_like(xi[:, 0])
- # make a copy of values for RectBivariateSpline
- interp = RectBivariateSpline(points[0], points[1], values[:])
- result[idx_valid] = interp.ev(xi[idx_valid, 0], xi[idx_valid, 1])
- result[np.logical_not(idx_valid)] = fill_value
- return result.reshape(xi_shape[:-1])
- else:
- raise ValueError(f"unknown {method = }")
|