Module conduction.interpolation.interpolator
Copyright 2017 Ben Mather
This file is part of Conduction https://git.dias.ie/itherc/conduction/
Conduction is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.
Conduction is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with Conduction. If not, see http://www.gnu.org/licenses/.
Expand source code
"""
Copyright 2017 Ben Mather
This file is part of Conduction <https://git.dias.ie/itherc/conduction/>
Conduction is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or any later version.
Conduction is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with Conduction. If not, see <http://www.gnu.org/licenses/>.
"""
from scipy.interpolate import RegularGridInterpolator as RGI
import itertools
import numpy as np
class RegularGridInterpolator(RGI):
def __init__(self, points, values, method="linear", bounds_error=False, fill_value=np.nan):
super(RegularGridInterpolator, self).__init__(points, values, method, bounds_error, fill_value)
def adjoint(self, xi, dxi, method=None):
"""
Interpolation adjoint using the derivatives dxi at coordinates xi
Parameters
----------
xi : ndarray of shape (..., ndim)
The coordinates to sample the gridded data at
dxi : ndarray of shape (..., ndim)
The derivatives at the coordinates xi
method : str
The method of interplolation to perform.
Supports either 'linear' or 'nearest'
"""
if method is None:
method = self.method
self.derivative = dxi
indices, norm_distances, out_of_bounds = self._find_indices(xi.T)
if method == "linear":
result = self._evaluate_linear_adjoint(indices, norm_distances, out_of_bounds)
elif method == "nearest":
result = self._evaluate_nearest_adjoint(indices, norm_distances, out_of_bounds)
return result
def _evaluate_linear_adjoint(self, indices, norm_distances, out_of_bounds):
# slice for broadcasting over trailing dimensions in self.values
vslice = (slice(None),) + (None,)*(self.values.ndim - len(indices))
# find relevant values
# each i and i+1 represents a edge
edges = itertools.product(*[[i, i + 1] for i in indices])
values = np.zeros_like(self.values)
for edge_indices in edges:
weight = 1.
for ei, i, yi in zip(edge_indices, indices, norm_distances):
weight *= np.where(ei == i, 1 - yi, yi)
values[edge_indices] += np.asarray(self.derivative) * weight[vslice]
return values
def _evaluate_nearest_adjoint(self, indices, norm_distances, out_of_bounds):
idx_res = []
values = np.zeros_like(self.values)
for i, yi in zip(indices, norm_distances):
idx_res.append(np.where(yi <= 0.5, i, i + 1))
values[idx_res] = self.derivative
return values
class KDTreeInterpolator(object):
"""
This object implements scipy.spatial.cKDTree for fast nearest-neighbour lookup
and interpolates the coordinate based on the next closes value
Arguments
---------
points : ndarray(n,dim) arbitrary set of points to interpolate over
values : ndarray(n,) values at the points
bounds_error : bool, optional
fill_value : number, optional
Methods
-------
__call__
adjoint
"""
def __init__(self, points, values, bounds_error=False, fill_value=np.nan):
from scipy.spatial import cKDTree
self.tree = cKDTree(points)
data = self.tree.data
npoints, ndim = data.shape
self.fill_value = fill_value
self.bounds_error = bounds_error
bbox = []
for i in range(0, ndim):
bbox.append((data[:,i].min(), data[:,i].max()))
self.npoints = npoints
self.ndim = ndim
self.bbox = bbox
self._values = np.ravel(values)
def __call__(self, xi, *args, **kwargs):
idx, d, bmask = self._find_indices(xi)
if self.bounds_error and bmask.any():
bidx = np.nonzero(bmask)[0]
raise ValueError("Coordinates in xi are out of bounds in:\n {}".format(bidx))
vi = self.values[idx]
if not self.bounds_error and self.fill_value is not None:
vi[bmask] = self.fill_value
return vi
def adjoint(self, xi, dxi, *args, **kwargs):
idx, d, bmask = self._find_indices(xi)
if self.bounds_error and bmask.any():
bidx = np.nonzero(bmask)[0]
raise ValueError("Coordinates in xi are out of bounds in:\n {}".format(bidx))
dv = np.zeros_like(self.values)
# remove indices that are out of bounds
idx_inbounds = idx[~bmask]
ux = np.unique(idx_inbounds)
for u in ux:
dv[u] = dxi[idx==u].sum()
if not self.bounds_error and self.fill_value is not None:
dv[idx][bmask] = self.fill_value
return dv
def _find_indices(self, xi):
d, idx = self.tree.query(xi)
bbox = self.bbox
ndim = self.ndim
bounds = np.zeros(idx.size, dtype=bool)
for i in range(0, ndim):
bmin, bmax = bbox[i]
mask = np.logical_or(xi[:,i] < bmin, xi[:,i] > bmax)
bounds[mask] = True
return idx, d, bounds
@property
def values(self):
return self._values
@values.setter
def values(self, value):
self._values = value.ravel()
@values.getter
def values(self):
return self._values
Classes
class KDTreeInterpolator (points, values, bounds_error=False, fill_value=nan)
-
This object implements scipy.spatial.cKDTree for fast nearest-neighbour lookup and interpolates the coordinate based on the next closes value
Arguments
points : ndarray(n,dim) arbitrary set of points to interpolate over values : ndarray(n,) values at the points bounds_error : bool, optional fill_value : number, optional
Methods
call adjoint
Expand source code
class KDTreeInterpolator(object): """ This object implements scipy.spatial.cKDTree for fast nearest-neighbour lookup and interpolates the coordinate based on the next closes value Arguments --------- points : ndarray(n,dim) arbitrary set of points to interpolate over values : ndarray(n,) values at the points bounds_error : bool, optional fill_value : number, optional Methods ------- __call__ adjoint """ def __init__(self, points, values, bounds_error=False, fill_value=np.nan): from scipy.spatial import cKDTree self.tree = cKDTree(points) data = self.tree.data npoints, ndim = data.shape self.fill_value = fill_value self.bounds_error = bounds_error bbox = [] for i in range(0, ndim): bbox.append((data[:,i].min(), data[:,i].max())) self.npoints = npoints self.ndim = ndim self.bbox = bbox self._values = np.ravel(values) def __call__(self, xi, *args, **kwargs): idx, d, bmask = self._find_indices(xi) if self.bounds_error and bmask.any(): bidx = np.nonzero(bmask)[0] raise ValueError("Coordinates in xi are out of bounds in:\n {}".format(bidx)) vi = self.values[idx] if not self.bounds_error and self.fill_value is not None: vi[bmask] = self.fill_value return vi def adjoint(self, xi, dxi, *args, **kwargs): idx, d, bmask = self._find_indices(xi) if self.bounds_error and bmask.any(): bidx = np.nonzero(bmask)[0] raise ValueError("Coordinates in xi are out of bounds in:\n {}".format(bidx)) dv = np.zeros_like(self.values) # remove indices that are out of bounds idx_inbounds = idx[~bmask] ux = np.unique(idx_inbounds) for u in ux: dv[u] = dxi[idx==u].sum() if not self.bounds_error and self.fill_value is not None: dv[idx][bmask] = self.fill_value return dv def _find_indices(self, xi): d, idx = self.tree.query(xi) bbox = self.bbox ndim = self.ndim bounds = np.zeros(idx.size, dtype=bool) for i in range(0, ndim): bmin, bmax = bbox[i] mask = np.logical_or(xi[:,i] < bmin, xi[:,i] > bmax) bounds[mask] = True return idx, d, bounds @property def values(self): return self._values @values.setter def values(self, value): self._values = value.ravel() @values.getter def values(self): return self._values
Instance variables
var values
-
Expand source code
@values.getter def values(self): return self._values
Methods
def adjoint(self, xi, dxi, *args, **kwargs)
-
Expand source code
def adjoint(self, xi, dxi, *args, **kwargs): idx, d, bmask = self._find_indices(xi) if self.bounds_error and bmask.any(): bidx = np.nonzero(bmask)[0] raise ValueError("Coordinates in xi are out of bounds in:\n {}".format(bidx)) dv = np.zeros_like(self.values) # remove indices that are out of bounds idx_inbounds = idx[~bmask] ux = np.unique(idx_inbounds) for u in ux: dv[u] = dxi[idx==u].sum() if not self.bounds_error and self.fill_value is not None: dv[idx][bmask] = self.fill_value return dv
class RegularGridInterpolator (points, values, method='linear', bounds_error=False, fill_value=nan)
-
Interpolation on a regular grid in arbitrary dimensions
The data must be defined on a regular grid; the grid spacing however may be uneven. Linear and nearest-neighbor interpolation are supported. After setting up the interpolator object, the interpolation method (linear or nearest) may be chosen at each evaluation.
Parameters
points
:tuple
ofndarray
offloat, with shapes (m1, ), …, (mn, )
- The points defining the regular grid in n dimensions.
values
:array_like, shape (m1, …, mn, …)
- The data on the regular grid in n dimensions.
method
:str
, optional- The method of interpolation to perform. Supported are "linear" and
"nearest". 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. 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.
Methods
call
Notes
Contrary to LinearNDInterpolator and NearestNDInterpolator, this class avoids expensive triangulation of the input data by taking advantage of the regular grid structure.
If any of
points
have a dimension of size 1, linear interpolation will return an array ofnan
values. Nearest-neighbor interpolation will work as usual in this case.Added in version: 0.14
Examples
Evaluate a simple example function on the points of a 3-D grid:
>>> from scipy.interpolate import RegularGridInterpolator >>> 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) >>> data = f(*np.meshgrid(x, y, z, indexing='ij', sparse=True))
data
is now a 3-D array withdata[i,j,k] = f(x[i], y[j], z[k])
. Next, define an interpolating function from this data:>>> my_interpolating_function = 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]]) >>> my_interpolating_function(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)]
.See Also
NearestNDInterpolator
- Nearest neighbor interpolation on unstructured data in N dimensions
LinearNDInterpolator : Piecewise linear interpolant on unstructured data in N dimensions
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
Expand source code
class RegularGridInterpolator(RGI): def __init__(self, points, values, method="linear", bounds_error=False, fill_value=np.nan): super(RegularGridInterpolator, self).__init__(points, values, method, bounds_error, fill_value) def adjoint(self, xi, dxi, method=None): """ Interpolation adjoint using the derivatives dxi at coordinates xi Parameters ---------- xi : ndarray of shape (..., ndim) The coordinates to sample the gridded data at dxi : ndarray of shape (..., ndim) The derivatives at the coordinates xi method : str The method of interplolation to perform. Supports either 'linear' or 'nearest' """ if method is None: method = self.method self.derivative = dxi indices, norm_distances, out_of_bounds = self._find_indices(xi.T) if method == "linear": result = self._evaluate_linear_adjoint(indices, norm_distances, out_of_bounds) elif method == "nearest": result = self._evaluate_nearest_adjoint(indices, norm_distances, out_of_bounds) return result def _evaluate_linear_adjoint(self, indices, norm_distances, out_of_bounds): # slice for broadcasting over trailing dimensions in self.values vslice = (slice(None),) + (None,)*(self.values.ndim - len(indices)) # find relevant values # each i and i+1 represents a edge edges = itertools.product(*[[i, i + 1] for i in indices]) values = np.zeros_like(self.values) for edge_indices in edges: weight = 1. for ei, i, yi in zip(edge_indices, indices, norm_distances): weight *= np.where(ei == i, 1 - yi, yi) values[edge_indices] += np.asarray(self.derivative) * weight[vslice] return values def _evaluate_nearest_adjoint(self, indices, norm_distances, out_of_bounds): idx_res = [] values = np.zeros_like(self.values) for i, yi in zip(indices, norm_distances): idx_res.append(np.where(yi <= 0.5, i, i + 1)) values[idx_res] = self.derivative return values
Ancestors
- scipy.interpolate.interpolate.RegularGridInterpolator
Methods
def adjoint(self, xi, dxi, method=None)
-
Interpolation adjoint using the derivatives dxi at coordinates xi Parameters
xi
:ndarray
ofshape (…, ndim)
- The coordinates to sample the gridded data at
dxi
:ndarray
ofshape (…, ndim)
- The derivatives at the coordinates xi
method
:str
- The method of interplolation to perform. Supports either 'linear' or 'nearest'
Expand source code
def adjoint(self, xi, dxi, method=None): """ Interpolation adjoint using the derivatives dxi at coordinates xi Parameters ---------- xi : ndarray of shape (..., ndim) The coordinates to sample the gridded data at dxi : ndarray of shape (..., ndim) The derivatives at the coordinates xi method : str The method of interplolation to perform. Supports either 'linear' or 'nearest' """ if method is None: method = self.method self.derivative = dxi indices, norm_distances, out_of_bounds = self._find_indices(xi.T) if method == "linear": result = self._evaluate_linear_adjoint(indices, norm_distances, out_of_bounds) elif method == "nearest": result = self._evaluate_nearest_adjoint(indices, norm_distances, out_of_bounds) return result