def _evaluate_derivatives(self, x, der=None): n = self.n r = self.r if der is None: der = self.n pi = np.zeros((n, len(x))) w = np.zeros((n, len(x))) pi[0] = 1 p = np.zeros((len(x), self.r), dtype=self.dtype) p += self.c[0, np.newaxis, :] for k in range(1, n): w[k - 1] = x - self.xi[k - 1] pi[k] = w[k - 1] * pi[k - 1] p += pi[k, :, np.newaxis] * self.c[k] cn = np.zeros((max(der, n + 1), len(x), r), dtype=self.dtype) cn[:n + 1, :, :] += self.c[:n + 1, np.newaxis, :] cn[0] = p for k in range(1, n): for i in range(1, n - k + 1): pi[i] = w[k + i - 1] * pi[i - 1] + pi[i] cn[k] = cn[k] + pi[i, :, np.newaxis] * cn[k + i] cn[k] *= float_factorial(k) cn[n, :, :] = 0 return cn[:der]
def __init__(self, xi, yi, axis=0): _Interpolator1DWithDerivatives.__init__(self, xi, yi, axis) self.xi = np.asarray(xi) self.yi = self._reshape_yi(yi) self.n, self.r = self.yi.shape c = np.zeros((self.n + 1, self.r), dtype=self.dtype) c[0] = self.yi[0] Vk = np.zeros((self.n, self.r), dtype=self.dtype) for k in range(1, self.n): s = 0 while s <= k and xi[k - s] == xi[k]: s += 1 s -= 1 Vk[0] = self.yi[k] / float_factorial(s) for i in range(k - s): if xi[i] == xi[k]: raise ValueError("Elements if `xi` can't be equal.") if s == 0: Vk[i + 1] = (c[i] - Vk[i]) / (xi[i] - xi[k]) else: Vk[i + 1] = (Vk[i + 1] - Vk[i]) / (xi[i] - xi[k]) c[k] = Vk[k - s] self.c = c
def _ell(A, m): """ A helper function for expm_2009. Parameters ---------- A : linear operator A linear operator whose norm of power we care about. m : int The power of the linear operator Returns ------- value : int A value related to a bound. """ if len(A.shape) != 2 or A.shape[0] != A.shape[1]: raise ValueError('expected A to be like a square matrix') # The c_i are explained in (2.2) and (2.6) of the 2005 expm paper. # They are coefficients of terms of a generating function series expansion. choose_2m_m = scipy.special.comb(2 * m, m, exact=True) abs_c_recip = float(choose_2m_m) * float_factorial(2 * m + 1) # This is explained after Eq. (1.2) of the 2009 expm paper. # It is the "unit roundoff" of IEEE double precision arithmetic. u = 2**-53 # Compute the one-norm of matrix power p of abs(A). A_abs_onenorm = _onenorm_matrix_power_nnm(abs(A), 2 * m + 1) # Treat zero norm as a special case. if not A_abs_onenorm: return 0 alpha = A_abs_onenorm / (_onenorm(A) * abs_c_recip) log2_alpha_div_u = np.log2(alpha / u) value = int(np.ceil(log2_alpha_div_u / (2 * m))) return max(value, 0)
def _bspline_piecefunctions(order): """Returns the function defined over the left-side pieces for a bspline of a given order. The 0th piece is the first one less than 0. The last piece is a function identical to 0 (returned as the constant 0). (There are order//2 + 2 total pieces). Also returns the condition functions that when evaluated return boolean arrays for use with `numpy.piecewise`. """ try: return _splinefunc_cache[order] except KeyError: pass def condfuncgen(num, val1, val2): if num == 0: return lambda x: logical_and(less_equal(x, val1), greater_equal(x, val2)) elif num == 2: return lambda x: less_equal(x, val2) else: return lambda x: logical_and(less(x, val1), greater_equal(x, val2)) last = order // 2 + 2 if order % 2: startbound = -1.0 else: startbound = -0.5 condfuncs = [condfuncgen(0, 0, startbound)] bound = startbound for num in range(1, last - 1): condfuncs.append(condfuncgen(1, bound, bound - 1)) bound = bound - 1 condfuncs.append(condfuncgen(2, 0, -(order + 1) / 2.0)) # final value of bound is used in piecefuncgen below # the functions to evaluate are taken from the left-hand side # in the general expression derived from the central difference # operator (because they involve fewer terms). fval = float_factorial(order) def piecefuncgen(num): Mk = order // 2 - num if (Mk < 0): return 0 # final function is 0 coeffs = [(1 - 2 * (k % 2)) * float(comb(order + 1, k, exact=1)) / fval for k in range(Mk + 1)] shifts = [-bound - k for k in range(Mk + 1)] def thefunc(x): res = 0.0 for k in range(Mk + 1): res += coeffs[k] * (x + shifts[k])**order return res return thefunc funclist = [piecefuncgen(k) for k in range(last)] _splinefunc_cache[order] = (funclist, condfuncs) return funclist, condfuncs
def savgol_coeffs(window_length, polyorder, deriv=0, delta=1.0, pos=None, use="conv"): """Compute the coefficients for a 1-D Savitzky-Golay FIR filter. Parameters ---------- window_length : int The length of the filter window (i.e., the number of coefficients). polyorder : int The order of the polynomial used to fit the samples. `polyorder` must be less than `window_length`. deriv : int, optional The order of the derivative to compute. This must be a nonnegative integer. The default is 0, which means to filter the data without differentiating. delta : float, optional The spacing of the samples to which the filter will be applied. This is only used if deriv > 0. pos : int or None, optional If pos is not None, it specifies evaluation position within the window. The default is the middle of the window. use : str, optional Either 'conv' or 'dot'. This argument chooses the order of the coefficients. The default is 'conv', which means that the coefficients are ordered to be used in a convolution. With use='dot', the order is reversed, so the filter is applied by dotting the coefficients with the data set. Returns ------- coeffs : 1-D ndarray The filter coefficients. References ---------- A. Savitzky, M. J. E. Golay, Smoothing and Differentiation of Data by Simplified Least Squares Procedures. Analytical Chemistry, 1964, 36 (8), pp 1627-1639. Jianwen Luo, Kui Ying, and Jing Bai. 2005. Savitzky-Golay smoothing and differentiation filter for even number data. Signal Process. 85, 7 (July 2005), 1429-1434. See Also -------- savgol_filter Notes ----- .. versionadded:: 0.14.0 Examples -------- >>> from scipy.signal import savgol_coeffs >>> savgol_coeffs(5, 2) array([-0.08571429, 0.34285714, 0.48571429, 0.34285714, -0.08571429]) >>> savgol_coeffs(5, 2, deriv=1) array([ 2.00000000e-01, 1.00000000e-01, 2.07548111e-16, -1.00000000e-01, -2.00000000e-01]) Note that use='dot' simply reverses the coefficients. >>> savgol_coeffs(5, 2, pos=3) array([ 0.25714286, 0.37142857, 0.34285714, 0.17142857, -0.14285714]) >>> savgol_coeffs(5, 2, pos=3, use='dot') array([-0.14285714, 0.17142857, 0.34285714, 0.37142857, 0.25714286]) >>> savgol_coeffs(4, 2, pos=3, deriv=1, use='dot') array([0.45, -0.85, -0.65, 1.05]) `x` contains data from the parabola x = t**2, sampled at t = -1, 0, 1, 2, 3. `c` holds the coefficients that will compute the derivative at the last position. When dotted with `x` the result should be 6. >>> x = np.array([1, 0, 1, 4, 9]) >>> c = savgol_coeffs(5, 2, pos=4, deriv=1, use='dot') >>> c.dot(x) 6.0 """ # An alternative method for finding the coefficients when deriv=0 is # t = np.arange(window_length) # unit = (t == pos).astype(int) # coeffs = np.polyval(np.polyfit(t, unit, polyorder), t) # The method implemented here is faster. # To recreate the table of sample coefficients shown in the chapter on # the Savitzy-Golay filter in the Numerical Recipes book, use # window_length = nL + nR + 1 # pos = nL + 1 # c = savgol_coeffs(window_length, M, pos=pos, use='dot') if polyorder >= window_length: raise ValueError("polyorder must be less than window_length.") halflen, rem = divmod(window_length, 2) if pos is None: if rem == 0: pos = halflen - 0.5 else: pos = halflen if not (0 <= pos < window_length): raise ValueError("pos must be nonnegative and less than " "window_length.") if use not in ['conv', 'dot']: raise ValueError("`use` must be 'conv' or 'dot'") if deriv > polyorder: coeffs = np.zeros(window_length) return coeffs # Form the design matrix A. The columns of A are powers of the integers # from -pos to window_length - pos - 1. The powers (i.e., rows) range # from 0 to polyorder. (That is, A is a vandermonde matrix, but not # necessarily square.) x = np.arange(-pos, window_length - pos, dtype=float) if use == "conv": # Reverse so that result can be used in a convolution. x = x[::-1] order = np.arange(polyorder + 1).reshape(-1, 1) A = x**order # y determines which order derivative is returned. y = np.zeros(polyorder + 1) # The coefficient assigned to y[deriv] scales the result to take into # account the order of the derivative and the sample spacing. y[deriv] = float_factorial(deriv) / (delta**deriv) # Find the least-squares solution of A*c = y coeffs, _, _, _ = lstsq(A, y) return coeffs
class KroghInterpolator(_Interpolator1DWithDerivatives): """ Interpolating polynomial for a set of points. The polynomial passes through all the pairs (xi,yi). One may additionally specify a number of derivatives at each point xi; this is done by repeating the value xi and specifying the derivatives as successive yi values. Allows evaluation of the polynomial and all its derivatives. For reasons of numerical stability, this function does not compute the coefficients of the polynomial, although they can be obtained by evaluating all the derivatives. Parameters ---------- xi : array_like, length N Known x-coordinates. Must be sorted in increasing order. yi : array_like Known y-coordinates. When an xi occurs two or more times in a row, the corresponding yi's represent derivative values. axis : int, optional Axis in the yi array corresponding to the x-coordinate values. Notes ----- Be aware that the algorithms implemented here are not necessarily the most numerically stable known. Moreover, even in a world of exact computation, unless the x coordinates are chosen very carefully - Chebyshev zeros (e.g., cos(i*pi/n)) are a good choice - polynomial interpolation itself is a very ill-conditioned process due to the Runge phenomenon. In general, even with well-chosen x values, degrees higher than about thirty cause problems with numerical instability in this code. Based on [1]_. References ---------- .. [1] Krogh, "Efficient Algorithms for Polynomial Interpolation and Numerical Differentiation", 1970. Examples -------- To produce a polynomial that is zero at 0 and 1 and has derivative 2 at 0, call >>> from scipy.interpolate import KroghInterpolator >>> KroghInterpolator([0,0,1],[0,2,0]) This constructs the quadratic 2*X**2-2*X. The derivative condition is indicated by the repeated zero in the xi array; the corresponding yi values are 0, the function value, and 2, the derivative value. For another example, given xi, yi, and a derivative ypi for each point, appropriate arrays can be constructed as: >>> rng = np.random.default_rng() >>> xi = np.linspace(0, 1, 5) >>> yi, ypi = rng.random((2, 5)) >>> xi_k, yi_k = np.repeat(xi, 2), np.ravel(np.dstack((yi,ypi))) >>> KroghInterpolator(xi_k, yi_k) To produce a vector-valued polynomial, supply a higher-dimensional array for yi: >>> KroghInterpolator([0,1],[[2,3],[4,5]]) This constructs a linear polynomial giving (2,3) at 0 and (4,5) at 1. """ def __init__(self, xi, yi, axis=0): _Interpolator1DWithDerivatives.__init__(self, xi, yi, axis) self.xi = np.asarray(xi) self.yi = self._reshape_yi(yi) self.n, self.r = self.yi.shape if (deg := self.xi.size) > 30: warnings.warn( f"{deg} degrees provided, degrees higher than about" " thirty cause problems with numerical instability " "with 'KroghInterpolator'", stacklevel=2) c = np.zeros((self.n + 1, self.r), dtype=self.dtype) c[0] = self.yi[0] Vk = np.zeros((self.n, self.r), dtype=self.dtype) for k in range(1, self.n): s = 0 while s <= k and xi[k - s] == xi[k]: s += 1 s -= 1 Vk[0] = self.yi[k] / float_factorial(s) for i in range(k - s): if xi[i] == xi[k]: raise ValueError("Elements if `xi` can't be equal.") if s == 0: Vk[i + 1] = (c[i] - Vk[i]) / (xi[i] - xi[k]) else: Vk[i + 1] = (Vk[i + 1] - Vk[i]) / (xi[i] - xi[k]) c[k] = Vk[k - s] self.c = c