Source code for solcore.interpolate

# from scipy import interpolate as I
import numpy as np
from scipy.interpolate import make_interp_spline, splev
from typing import Union


[docs]class interp1d(object): """ Interpolate a 1D function. See Also -------- splrep, splev - spline interpolation based on FITPACK UnivariateSpline - a more recent wrapper of the FITPACK routines """ def __init__(self, x: np.ndarray, y: np.ndarray, kind: str = 'linear', axis: int = -1, copy: bool = True, bounds_error: bool = False, fill_value: float = np.nan): """ Initialize a 1D linear interpolation class. Description ----------- x and y are arrays of values used to approximate some function f: y = f(x) This class returns a function whose call method uses linear interpolation to find the value of new points. Parameters ---------- x : array A 1D array of monotonically increasing real values. x cannot include duplicate values (otherwise f is overspecified) y : array An N-D array of real values. y's length along the interpolation axis must be equal to the length of x. kind : str or int Specifies the kind of interpolation as a string ('linear', 'nearest', 'zero', 'slinear', 'quadratic, 'cubic') or as an integer specifying the order of the spline interpolator to use. axis : int Specifies the axis of y along which to interpolate. Interpolation defaults to the last axis of y. copy : bool If True, the class makes internal copies of x and y. If False, references to x and y are used. The default is to copy. bounds_error : bool If True, an error is thrown any time interpolation is attempted on a value outside of the range of x (where extrapolation is necessary). If False, out of bounds values are assigned fill_value. By default, an error is raised. fill_value : float If provided, then this value will be used to fill in for requested points outside of the data range. If not provided, then the default is NaN. """ self.copy = copy self.bounds_error = bounds_error self.fill_value = fill_value if kind in ['zero', 'slinear', 'quadratic', 'cubic']: order = {'nearest': 0, 'zero': 0, 'slinear': 1, 'quadratic': 2, 'cubic': 3}[kind] kind = 'spline' elif isinstance(kind, int): order = kind kind = 'spline' elif kind not in ('linear', 'nearest'): raise NotImplementedError("%s is unsupported: Use fitpack " \ "routines for other types." % kind) x = np.array(x, copy=self.copy) y = np.array(y, copy=self.copy) if x.ndim != 1: raise ValueError("the x array must have exactly one dimension.") if y.ndim == 0: raise ValueError("the y array must have at least one dimension.") # Force-cast y to a floating-point type, if it's not yet one if not issubclass(y.dtype.type, np.inexact): y = y.astype(np.float_) # Normalize the axis to ensure that it is positive. self.axis = axis % len(y.shape) self._kind = kind if kind in ('linear', 'nearest'): # Make a "view" of the y array that is rotated to the interpolation # axis. axes = list(range(y.ndim)) del axes[self.axis] axes.append(self.axis) oriented_y = y.transpose(axes) minval = 2 len_y = oriented_y.shape[-1] if kind == 'linear': self._call = self._call_linear elif kind == 'nearest': self.x_bds = (x[1:] + x[:-1]) / 2.0 self._call = self._call_nearest else: axes = list(range(y.ndim)) del axes[self.axis] axes.insert(0, self.axis) oriented_y = y.transpose(axes) minval = order + 1 len_y = oriented_y.shape[0] self._call = self._call_spline self._spline = make_interp_spline(x, oriented_y, k=order) len_x = len(x) if len_x != len_y: raise ValueError("x and y arrays must be equal in length along " "interpolation axis.") if len_x < minval: raise ValueError("x and y arrays must have at " "least %d entries" % minval) self.x = x self.y = oriented_y def _call_linear(self, x_new: np.ndarray) -> np.ndarray: # 2. Find where in the orignal data, the values to interpolate # would be inserted. # Note: If x_new[n] == x[m], then m is returned by searchsorted. x_new_indices = np.searchsorted(self.x, x_new) # 3. Clip x_new_indices so that they are within the range of # self.x indices and at least 1. Removes mis-interpolation # of x_new[n] = x[0] x_new_indices = x_new_indices.clip(1, len(self.x) - 1).astype(int) # 4. Calculate the slope of regions that each x_new value falls in. lo = x_new_indices - 1 hi = x_new_indices x_lo = self.x[lo] x_hi = self.x[hi] y_lo = self.y[..., lo] y_hi = self.y[..., hi] # Note that the following two expressions rely on the specifics of the # broadcasting semantics. slope = (y_hi - y_lo) / (x_hi - x_lo) # 5. Calculate the actual value for each entry in x_new. y_new = slope * (x_new - x_lo) + y_lo return y_new def _call_nearest(self, x_new: np.ndarray) -> np.ndarray: """ Find nearest neighbour interpolated y_new = f(x_new).""" # 2. Find where in the averaged data the values to interpolate # would be inserted. # Note: use side='left' (right) to searchsorted() to define the # halfway point to be nearest to the left (right) neighbour x_new_indices = np.searchsorted(self.x_bds, x_new, side='left') # 3. Clip x_new_indices so that they are within the range of x indices. x_new_indices = x_new_indices.clip(0, len(self.x) - 1).astype(np.intp) # 4. Calculate the actual value for each entry in x_new. y_new = self.y[..., x_new_indices] return y_new def _call_spline(self, x_new: np.ndarray) -> np.ndarray: x_new = np.asarray(x_new) result = splev(x_new.ravel(), self._spline) return result.reshape(x_new.shape + result.shape[1:]) def __call__(self, x_new: np.ndarray) -> np.ndarray: """Find interpolated y_new = f(x_new). Parameters ---------- x_new : number or array New independent variable(s). Returns ------- y_new : ndarray Interpolated value(s) corresponding to x_new. """ # 1. Handle values in x_new that are outside of x. Throw error, # or return a list of mask array indicating the outofbounds values. # The behavior is set by the bounds_error variable. x_new = np.asarray(x_new) out_of_bounds = self._check_bounds(x_new) y_new = self._call(x_new) # Rotate the values of y_new back so that they correspond to the # correct x_new values. For N-D x_new, take the last (for linear) # or first (for other splines) N axes # from y_new and insert them where self.axis was in the list of axes. nx = x_new.ndim ny = y_new.ndim # 6. Fill any values that were out of bounds with fill_value. # and # 7. Rotate the values back to their proper place. if nx == 0: # special case: x is a scalar if out_of_bounds: if ny == 0: return np.asarray(self.fill_value) else: y_new[...] = self.fill_value return np.asarray(y_new) elif self._kind in ('linear', 'nearest'): y_new[..., out_of_bounds] = self.fill_value axes = list(range(ny - nx)) axes[self.axis:self.axis] = list(range(ny - nx, ny)) return y_new.transpose(axes) else: y_new[out_of_bounds] = self.fill_value axes = list(range(nx, ny)) axes[self.axis:self.axis] = list(range(nx)) return y_new.transpose(axes) def _check_bounds(self, x_new: np.ndarray) -> np.ndarray: """Check the inputs for being in the bounds of the interpolated data. Parameters ---------- x_new : array Returns ------- out_of_bounds : bool array The mask on x_new of values that are out of the bounds. """ # If self.bounds_error is True, we raise an error if any x_new values # fall outside the range of x. Otherwise, we return an array indicating # which values are outside the boundary region. below_bounds = x_new < self.x[0] above_bounds = x_new > self.x[-1] # !! Could provide more information about which values are out of bounds if self.bounds_error and below_bounds.any(): raise ValueError("A value in x_new is below the interpolation " "range.") if self.bounds_error and above_bounds.any(): raise ValueError("A value in x_new is above the interpolation " "range.") # !! Should we emit a warning if some values are out of bounds? # !! matlab does not. out_of_bounds = np.logical_or(below_bounds, above_bounds) return out_of_bounds
[docs]class BilinearInterpolation(object): """docstring for BilinearInterpolation""" def __init__(self, x: Union[list, np.ndarray], y: Union[list, np.ndarray], z: Union[list, np.ndarray], fill_value: float = 0.): super(BilinearInterpolation, self).__init__() self.x = np.array(x) self.y = np.array(y) self.z = np.array(z) # Check the shapes of x,y and z x_s = self.x.shape y_s = self.y.shape z_s = self.z.shape assert x_s[0] >= 3, "Need at least 3 points in x." assert y_s[0] >= 3, "Need at least 3 points in y." assert x_s[0] == z_s[ 0], "The number of rows in z (which is %s), must be the same as number of elements in x (which is %s)" % ( x_s[0], z_s[0]) assert y_s[0] == z_s[ 1], "The number of columns in z (which is %s), must be the same as number of elements in y (which is %s)" % ( y_s[0], z_s[1]) def __call__(self, xvalue: float, yvalue: float) -> np.ndarray: """The intepolated value of the surface.""" try: x_i = 0 found = False for i in list(range(0, len(self.x) - 1)): # print self.x[i], "<=", xvalue, "<", self.x[i+1] if self.x[i] <= xvalue < self.x[i + 1]: # print "Found, returning index", i x_i = i found = True break if not found: # print "Not found, returning 0." return 0. x_v = (self.x[x_i], self.x[x_i + 1]) x_i = (x_i, x_i + 1) y_i = 0 found = False for i in list(range(0, len(self.y) - 1)): # print self.y[i], "<=", yvalue, "<", self.y[i+1] if self.y[i] <= yvalue < self.y[i + 1]: # print "Found, returning index", i y_i = i found = True break if not found: # print "Not found, returning 0." return 0. y_v = (self.y[y_i], self.y[y_i + 1]) y_i = (y_i, y_i + 1) D = (x_v[1] - x_v[0]) * (y_v[1] - y_v[0]) ta = self.z[x_i[0], y_i[0]] tb = (x_v[1] - xvalue) tc = (y_v[1] - yvalue) t1 = ta * tb * tc / D # print t1, "=", ta, "*", tb, "*", tc, "/", D ta = self.z[x_i[1], y_i[0]] tb = (xvalue - x_v[0]) tc = (y_v[1] - yvalue) t2 = ta * tb * tc / D # print t1, "=", ta, "*", tb, "*", tc, "/", D ta = self.z[x_i[0], y_i[1]] tb = (x_v[1] - xvalue) tc = (yvalue - y_v[0]) t3 = ta * tb * tc / D # print t1, "=", ta, "*", tb, "*", tc, "/", D ta = self.z[x_i[1], y_i[1]] tb = (xvalue - x_v[0]) tc = (yvalue - y_v[0]) t4 = ta * tb * tc / D # print t1, "=", ta, "*", tb, "*", tc, "/", D # print "Therefore," # print t1, "+", t2, "+", t3, "+", t4 # print self.z return t1 + t2 + t3 + t4 except ValueError: # import pdb; pdb.set_trace() # Is one of the inputs an array? if type(xvalue) == list or type(xvalue) == tuple or type(xvalue) == type(np.array([])): constructed_list = [] for sub_xvalue in xvalue: constructed_list.append(self(sub_xvalue, yvalue)) return np.array(constructed_list)
if __name__ == "__main__": # Some wavelength points x = np.array([400., 600., 601, 1000.]) # Some angle values y = np.array([0., np.pi / 4, np.pi / 2]) # Some reflectivity values # rads @ 400nm, rads @ 600nm, rads @ 601m, rads @ 1000nm z = np.array([[0., 0., 0.], [1e-9, 1e-9, 1e-9], [1 - 1e-9, 1 - 1e-9, 1 - 1e-9], [1., 1., 1.]]) # print z.shape z_i = BilinearInterpolation(x, y, z) print(z_i(610, 0.1))