|
|
|
@ -1,4 +1,4 @@
|
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
|
#-------------------------------------------------------------------------
|
|
|
|
|
# Name: module1
|
|
|
|
|
# Purpose:
|
|
|
|
|
#
|
|
|
|
@ -7,15 +7,16 @@
|
|
|
|
|
# Created: 30.12.2008
|
|
|
|
|
# Copyright: (c) pab 2008
|
|
|
|
|
# Licence: <your licence>
|
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
|
#-------------------------------------------------------------------------
|
|
|
|
|
#!/usr/bin/env python
|
|
|
|
|
from __future__ import division
|
|
|
|
|
import numpy as np
|
|
|
|
|
import scipy.signal
|
|
|
|
|
import scipy.special as spec
|
|
|
|
|
import scipy.sparse as sp
|
|
|
|
|
import scipy.sparse.linalg #@UnusedImport
|
|
|
|
|
import scipy.sparse.linalg # @UnusedImport
|
|
|
|
|
from numpy.ma.core import ones, zeros, prod, sin
|
|
|
|
|
from numpy import diff, pi, inf #@UnresolvedImport
|
|
|
|
|
from numpy import diff, pi, inf # @UnresolvedImport
|
|
|
|
|
from numpy.lib.shape_base import vstack
|
|
|
|
|
from numpy.lib.function_base import linspace
|
|
|
|
|
from scipy.interpolate import PiecewisePolynomial
|
|
|
|
@ -23,11 +24,14 @@ from scipy.interpolate import PiecewisePolynomial
|
|
|
|
|
import polynomial as pl
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
__all__ = ['PPform', 'savitzky_golay', 'savitzky_golay_piecewise', 'sgolay2d','SmoothSpline',
|
|
|
|
|
'pchip_slopes','slopes','stineman_interp', 'Pchip','StinemanInterp', 'CubicHermiteSpline']
|
|
|
|
|
__all__ = [
|
|
|
|
|
'PPform', 'savitzky_golay', 'savitzky_golay_piecewise', 'sgolay2d',
|
|
|
|
|
'SmoothSpline', 'pchip_slopes', 'slopes', 'stineman_interp', 'Pchip',
|
|
|
|
|
'StinemanInterp', 'CubicHermiteSpline']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def savitzky_golay(y, window_size, order, deriv=0):
|
|
|
|
|
r"""Smooth (and optionally differentiate) data with a Savitzky-Golay filter.
|
|
|
|
|
"""Smooth (and optionally differentiate) data with a Savitzky-Golay filter.
|
|
|
|
|
The Savitzky-Golay filter removes high frequency noise from data.
|
|
|
|
|
It has the advantage of preserving the original shape and
|
|
|
|
|
features of the signal better than other types of filtering
|
|
|
|
@ -43,7 +47,8 @@ def savitzky_golay(y, window_size, order, deriv=0):
|
|
|
|
|
the order of the polynomial used in the filtering.
|
|
|
|
|
Must be less then `window_size` - 1.
|
|
|
|
|
deriv: int
|
|
|
|
|
the order of the derivative to compute (default = 0 means only smoothing)
|
|
|
|
|
order of the derivative to compute (default = 0 means only smoothing)
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
|
|
|
|
ys : ndarray, shape (N)
|
|
|
|
@ -87,25 +92,28 @@ def savitzky_golay(y, window_size, order, deriv=0):
|
|
|
|
|
raise TypeError("window_size size must be a positive odd number")
|
|
|
|
|
if window_size < order + 2:
|
|
|
|
|
raise TypeError("window_size is too small for the polynomials order")
|
|
|
|
|
order_range = range(order+1)
|
|
|
|
|
half_window = (window_size -1) // 2
|
|
|
|
|
order_range = range(order + 1)
|
|
|
|
|
half_window = (window_size - 1) // 2
|
|
|
|
|
# precompute coefficients
|
|
|
|
|
b = np.mat([[k**i for i in order_range] for k in range(-half_window, half_window+1)])
|
|
|
|
|
b = np.mat([[k ** i for i in order_range]
|
|
|
|
|
for k in range(-half_window, half_window + 1)])
|
|
|
|
|
m = np.linalg.pinv(b).A[deriv]
|
|
|
|
|
# pad the signal at the extremes with
|
|
|
|
|
# values taken from the signal itself
|
|
|
|
|
firstvals = y[0] - np.abs( y[1:half_window+1][::-1] - y[0] )
|
|
|
|
|
lastvals = y[-1] + np.abs(y[-half_window-1:-1][::-1] - y[-1])
|
|
|
|
|
firstvals = y[0] - np.abs(y[1:half_window + 1][::-1] - y[0])
|
|
|
|
|
lastvals = y[-1] + np.abs(y[-half_window - 1:-1][::-1] - y[-1])
|
|
|
|
|
y = np.concatenate((firstvals, y, lastvals))
|
|
|
|
|
return np.convolve( m, y, mode='valid')
|
|
|
|
|
return np.convolve(m, y, mode='valid')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def savitzky_golay_piecewise(xvals, data, kernel=11, order =4):
|
|
|
|
|
def savitzky_golay_piecewise(xvals, data, kernel=11, order=4):
|
|
|
|
|
'''
|
|
|
|
|
One of the most popular applications of S-G filter, apart from smoothing UV-VIS
|
|
|
|
|
and IR spectra, is smoothing of curves obtained in electroanalytical experiments.
|
|
|
|
|
In cyclic voltammetry, voltage (being the abcissa) changes like a triangle wave.
|
|
|
|
|
And in the signal there are cusps at the turning points (at switching potentials)
|
|
|
|
|
which should never be smoothed. In this case, Savitzky-Golay smoothing should be
|
|
|
|
|
One of the most popular applications of S-G filter, apart from smoothing
|
|
|
|
|
UV-VIS and IR spectra, is smoothing of curves obtained in electroanalytical
|
|
|
|
|
experiments. In cyclic voltammetry, voltage (being the abcissa) changes
|
|
|
|
|
like a triangle wave. And in the signal there are cusps at the turning
|
|
|
|
|
points (at switching potentials) which should never be smoothed.
|
|
|
|
|
In this case, Savitzky-Golay smoothing should be
|
|
|
|
|
done piecewise, ie. separately on pieces monotonic in x
|
|
|
|
|
|
|
|
|
|
Example
|
|
|
|
@ -117,46 +125,55 @@ def savitzky_golay_piecewise(xvals, data, kernel=11, order =4):
|
|
|
|
|
>>> y = np.round(sin(x))
|
|
|
|
|
>>> sig2 = linspace(0,0.5,50)
|
|
|
|
|
|
|
|
|
|
# As an example, this figure shows the effect of an additive noise with a variance
|
|
|
|
|
# of 0.2 (original signal (black), noisy signal (red) and filtered signal (blue dots)).
|
|
|
|
|
# As an example, this figure shows the effect of an additive noise with a
|
|
|
|
|
# variance of 0.2 (original signal (black), noisy signal (red) and filtered
|
|
|
|
|
# signal (blue dots)).
|
|
|
|
|
|
|
|
|
|
>>> yn = y + np.sqrt(0.2)*np.random.randn(*x.shape)
|
|
|
|
|
>>> yr = savitzky_golay_piecewise(x, yn, kernel=11, order=4)
|
|
|
|
|
>>> h=plt.plot(x, yn, 'r', x, y, 'k', x, yr, 'b.')
|
|
|
|
|
'''
|
|
|
|
|
turnpoint=0
|
|
|
|
|
last=len(xvals)
|
|
|
|
|
if xvals[1]>xvals[0] : #x is increasing?
|
|
|
|
|
for i in range(1,last) : #yes
|
|
|
|
|
if xvals[i]<xvals[i-1] : #search where x starts to fall
|
|
|
|
|
turnpoint=i
|
|
|
|
|
turnpoint = 0
|
|
|
|
|
last = len(xvals)
|
|
|
|
|
if xvals[1] > xvals[0]: # x is increasing?
|
|
|
|
|
for i in range(1, last): # yes
|
|
|
|
|
if xvals[i] < xvals[i - 1]: # search where x starts to fall
|
|
|
|
|
turnpoint = i
|
|
|
|
|
break
|
|
|
|
|
else: #no, x is decreasing
|
|
|
|
|
for i in range(1,last) : #search where it starts to rise
|
|
|
|
|
if xvals[i]>xvals[i-1] :
|
|
|
|
|
turnpoint=i
|
|
|
|
|
else: # no, x is decreasing
|
|
|
|
|
for i in range(1, last): # search where it starts to rise
|
|
|
|
|
if xvals[i] > xvals[i - 1]:
|
|
|
|
|
turnpoint = i
|
|
|
|
|
break
|
|
|
|
|
if turnpoint==0 : #no change in direction of x
|
|
|
|
|
if turnpoint == 0: # no change in direction of x
|
|
|
|
|
return savitzky_golay(data, kernel, order)
|
|
|
|
|
else:
|
|
|
|
|
#smooth the first piece
|
|
|
|
|
firstpart=savitzky_golay(data[0:turnpoint],kernel,order)
|
|
|
|
|
#recursively smooth the rest
|
|
|
|
|
rest=savitzky_golay_piecewise(xvals[turnpoint:], data[turnpoint:], kernel, order)
|
|
|
|
|
return np.concatenate((firstpart,rest))
|
|
|
|
|
# smooth the first piece
|
|
|
|
|
firstpart = savitzky_golay(data[0:turnpoint], kernel, order)
|
|
|
|
|
# recursively smooth the rest
|
|
|
|
|
rest = savitzky_golay_piecewise(
|
|
|
|
|
xvals[turnpoint:], data[turnpoint:], kernel, order)
|
|
|
|
|
return np.concatenate((firstpart, rest))
|
|
|
|
|
|
|
|
|
|
def sgolay2d ( z, window_size, order, derivative=None):
|
|
|
|
|
|
|
|
|
|
def sgolay2d(z, window_size, order, derivative=None):
|
|
|
|
|
"""
|
|
|
|
|
Savitsky - Golay filters can also be used to smooth two dimensional data affected
|
|
|
|
|
by noise. The algorithm is exactly the same as for the one dimensional case, only
|
|
|
|
|
the math is a bit more tricky. The basic algorithm is as follow:
|
|
|
|
|
for each point of the two dimensional matrix extract a sub - matrix, centered at
|
|
|
|
|
that point and with a size equal to an odd number "window_size".
|
|
|
|
|
for this sub - matrix compute a least - square fit of a polynomial surface, defined as
|
|
|
|
|
Savitsky - Golay filters can also be used to smooth two dimensional data
|
|
|
|
|
affected by noise. The algorithm is exactly the same as for the one
|
|
|
|
|
dimensional case, only the math is a bit more tricky. The basic algorithm
|
|
|
|
|
is as follow: for each point of the two dimensional matrix extract a sub
|
|
|
|
|
- matrix, centered at that point and with a size equal to an odd number
|
|
|
|
|
"window_size". for this sub - matrix compute a least - square fit of a
|
|
|
|
|
polynomial surface, defined as
|
|
|
|
|
p(x, y) = a0 + a1 * x + a2 * y + a3 * x2 + a4 * y2 + a5 * x * y + ... .
|
|
|
|
|
|
|
|
|
|
Note that x and y are equal to zero at the central point.
|
|
|
|
|
replace the initial central point with the value computed with the fit.
|
|
|
|
|
Note that because the fit coefficients are linear with respect to the data spacing, they can pre - computed for efficiency. Moreover, it is important to appropriately pad the borders of the data, with a mirror image of the data itself, so that the evaluation of the fit at the borders of the data can happen smoothly.
|
|
|
|
|
Note that because the fit coefficients are linear with respect to the data
|
|
|
|
|
spacing, they can pre - computed for efficiency. Moreover, it is important
|
|
|
|
|
to appropriately pad the borders of the data, with a mirror image of the
|
|
|
|
|
data itself, so that the evaluation of the fit at the borders of the data
|
|
|
|
|
can happen smoothly.
|
|
|
|
|
Here is the code for two dimensional filtering.
|
|
|
|
|
|
|
|
|
|
Example
|
|
|
|
@ -196,7 +213,7 @@ def sgolay2d ( z, window_size, order, derivative=None):
|
|
|
|
|
# the exponents of the k-th term. First element of tuple is for x
|
|
|
|
|
# second element for y.
|
|
|
|
|
# Ex. exps = [(0,0), (1,0), (0,1), (2,0), (1,1), (0,2), ...]
|
|
|
|
|
exps = [ (k - n, n) for k in range(order + 1) for n in range(k + 1) ]
|
|
|
|
|
exps = [(k - n, n) for k in range(order + 1) for n in range(k + 1)]
|
|
|
|
|
|
|
|
|
|
# coordinates of points
|
|
|
|
|
ind = np.arange(-half_size, half_size + 1, dtype=np.float64)
|
|
|
|
@ -213,32 +230,44 @@ def sgolay2d ( z, window_size, order, derivative=None):
|
|
|
|
|
Z = np.zeros((new_shape))
|
|
|
|
|
# top band
|
|
|
|
|
band = z[0, :]
|
|
|
|
|
Z[:half_size, half_size:-half_size] = band - np.abs(np.flipud(z[1:half_size + 1, :]) - band)
|
|
|
|
|
Z[:half_size, half_size:-half_size] = band - \
|
|
|
|
|
np.abs(np.flipud(z[1:half_size + 1, :]) - band)
|
|
|
|
|
# bottom band
|
|
|
|
|
band = z[-1, :]
|
|
|
|
|
Z[-half_size:, half_size:-half_size] = band + np.abs(np.flipud(z[-half_size - 1:-1, :]) - band)
|
|
|
|
|
Z[-half_size:, half_size:-half_size] = band + \
|
|
|
|
|
np.abs(np.flipud(z[-half_size - 1:-1, :]) - band)
|
|
|
|
|
# left band
|
|
|
|
|
band = np.tile(z[:, 0].reshape(-1, 1), [1, half_size])
|
|
|
|
|
Z[half_size:-half_size, :half_size] = band - np.abs(np.fliplr(z[:, 1:half_size + 1]) - band)
|
|
|
|
|
Z[half_size:-half_size, :half_size] = band - \
|
|
|
|
|
np.abs(np.fliplr(z[:, 1:half_size + 1]) - band)
|
|
|
|
|
# right band
|
|
|
|
|
band = np.tile(z[:, -1].reshape(-1, 1), [1, half_size])
|
|
|
|
|
Z[half_size:-half_size, -half_size:] = band + np.abs(np.fliplr(z[:, -half_size - 1:-1]) - band)
|
|
|
|
|
Z[half_size:-half_size, -half_size:] = band + \
|
|
|
|
|
np.abs(np.fliplr(z[:, -half_size - 1:-1]) - band)
|
|
|
|
|
# central band
|
|
|
|
|
Z[half_size:-half_size, half_size:-half_size] = z
|
|
|
|
|
|
|
|
|
|
# top left corner
|
|
|
|
|
band = z[0, 0]
|
|
|
|
|
Z[:half_size, :half_size] = band - np.abs(np.flipud(np.fliplr(z[1:half_size + 1, 1:half_size + 1])) - band)
|
|
|
|
|
Z[:half_size, :half_size] = band - \
|
|
|
|
|
np.abs(
|
|
|
|
|
np.flipud(np.fliplr(z[1:half_size + 1, 1:half_size + 1])) - band)
|
|
|
|
|
# bottom right corner
|
|
|
|
|
band = z[-1, -1]
|
|
|
|
|
Z[-half_size:, -half_size:] = band + np.abs(np.flipud(np.fliplr(z[-half_size - 1:-1, -half_size - 1:-1])) - band)
|
|
|
|
|
Z[-half_size:, -half_size:] = band + \
|
|
|
|
|
np.abs(np.flipud(np.fliplr(z[-half_size - 1:-1, -half_size - 1:-1])) -
|
|
|
|
|
band)
|
|
|
|
|
|
|
|
|
|
# top right corner
|
|
|
|
|
band = Z[half_size, -half_size:]
|
|
|
|
|
Z[:half_size, -half_size:] = band - np.abs(np.flipud(Z[half_size + 1:2 * half_size + 1, -half_size:]) - band)
|
|
|
|
|
Z[:half_size, -half_size:] = band - \
|
|
|
|
|
np.abs(
|
|
|
|
|
np.flipud(Z[half_size + 1:2 * half_size + 1, -half_size:]) - band)
|
|
|
|
|
# bottom left corner
|
|
|
|
|
band = Z[-half_size:, half_size].reshape(-1, 1)
|
|
|
|
|
Z[-half_size:, :half_size] = band - np.abs(np.fliplr(Z[-half_size:, half_size + 1:2 * half_size + 1]) - band)
|
|
|
|
|
Z[-half_size:, :half_size] = band - \
|
|
|
|
|
np.abs(
|
|
|
|
|
np.fliplr(Z[-half_size:, half_size + 1:2 * half_size + 1]) - band)
|
|
|
|
|
|
|
|
|
|
# solve system and convolve
|
|
|
|
|
if derivative == None:
|
|
|
|
@ -253,11 +282,15 @@ def sgolay2d ( z, window_size, order, derivative=None):
|
|
|
|
|
elif derivative == 'both':
|
|
|
|
|
c = np.linalg.pinv(A)[1].reshape((window_size, -1))
|
|
|
|
|
r = np.linalg.pinv(A)[2].reshape((window_size, -1))
|
|
|
|
|
return scipy.signal.fftconvolve(Z, -r, mode='valid'), scipy.signal.fftconvolve(Z, -c, mode='valid')
|
|
|
|
|
return (scipy.signal.fftconvolve(Z, -r, mode='valid'),
|
|
|
|
|
scipy.signal.fftconvolve(Z, -c, mode='valid'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PPform(object):
|
|
|
|
|
"""The ppform of the piecewise polynomials is given in terms of coefficients
|
|
|
|
|
and breaks. The polynomial in the ith interval is
|
|
|
|
|
|
|
|
|
|
"""The ppform of the piecewise polynomials
|
|
|
|
|
is given in terms of coefficients and breaks.
|
|
|
|
|
The polynomial in the ith interval is
|
|
|
|
|
x_{i} <= x < x_{i+1}
|
|
|
|
|
|
|
|
|
|
S_i = sum(coefs[m,i]*(x-breaks[i])^(k-m), m=0..k)
|
|
|
|
@ -274,6 +307,7 @@ class PPform(object):
|
|
|
|
|
>>> x = linspace(-1,3)
|
|
|
|
|
>>> h=plt.plot(x,self(x))
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, coeffs, breaks, fill=0.0, sort=False, a=None, b=None):
|
|
|
|
|
if sort:
|
|
|
|
|
self.breaks = np.sort(breaks)
|
|
|
|
@ -309,7 +343,8 @@ class PPform(object):
|
|
|
|
|
V = np.vander(dx, N=self.order)
|
|
|
|
|
# values = np.diag(dot(V,pp[:,indxs]))
|
|
|
|
|
dot = np.dot
|
|
|
|
|
values = np.array([dot(V[k, :], pp[:, indxs[k]]) for k in xrange(len(xx))])
|
|
|
|
|
values = np.array([dot(V[k, :], pp[:, indxs[k]])
|
|
|
|
|
for k in xrange(len(xx))])
|
|
|
|
|
|
|
|
|
|
res[mask] = values
|
|
|
|
|
res.shape = saveshape
|
|
|
|
@ -317,7 +352,7 @@ class PPform(object):
|
|
|
|
|
|
|
|
|
|
def linear_extrapolate(self, output=True):
|
|
|
|
|
'''
|
|
|
|
|
Return a 1D PPform which extrapolate linearly outside its basic interval
|
|
|
|
|
Return 1D PPform which extrapolate linearly outside its basic interval
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
max_order = 2
|
|
|
|
@ -347,24 +382,23 @@ class PPform(object):
|
|
|
|
|
dxN = dx[-1]
|
|
|
|
|
|
|
|
|
|
a_n = pl.polyreloc(a_nn, -dxN) # Relocate last polynomial
|
|
|
|
|
#set to zero all terms of order > maxOrder
|
|
|
|
|
# set to zero all terms of order > maxOrder
|
|
|
|
|
a_n[0:self.order - max_order] = 0
|
|
|
|
|
|
|
|
|
|
#Get the coefficients for the new first piece (a_1)
|
|
|
|
|
# Get the coefficients for the new first piece (a_1)
|
|
|
|
|
# by first setting all terms of order > maxOrder to zero and then
|
|
|
|
|
# relocate the polynomial.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#Set to zero all terms of order > maxOrder, i.e., not using them
|
|
|
|
|
# Set to zero all terms of order > maxOrder, i.e., not using them
|
|
|
|
|
a_11 = coefs[self.order - max_order::, 0]
|
|
|
|
|
dx1 = dx[0]
|
|
|
|
|
|
|
|
|
|
a_1 = pl.polyreloc(a_11, -dx1) # Relocate first polynomial
|
|
|
|
|
a_1 = np.hstack([zeros(self.order - max_order), a_1])
|
|
|
|
|
|
|
|
|
|
newcoefs = np.hstack([ a_1.reshape(-1, 1), coefs, a_n.reshape(-1, 1)])
|
|
|
|
|
newcoefs = np.hstack([a_1.reshape(-1, 1), coefs, a_n.reshape(-1, 1)])
|
|
|
|
|
if output:
|
|
|
|
|
return PPform(newcoefs, newbreaks, a= -inf, b=inf)
|
|
|
|
|
return PPform(newcoefs, newbreaks, a=-inf, b=inf)
|
|
|
|
|
else:
|
|
|
|
|
self.coeffs = newcoefs
|
|
|
|
|
self.breaks = newbreaks
|
|
|
|
@ -380,7 +414,6 @@ class PPform(object):
|
|
|
|
|
brks = self.breaks.copy()
|
|
|
|
|
return PPform(cof, brks, fill=self.fill)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def integrate(self):
|
|
|
|
|
"""
|
|
|
|
|
Return the indefinite integral of the piecewise polynomial
|
|
|
|
@ -388,8 +421,9 @@ class PPform(object):
|
|
|
|
|
cof = pl.polyint(self.coeffs)
|
|
|
|
|
|
|
|
|
|
pieces = len(self.breaks) - 1
|
|
|
|
|
if 1 < pieces :
|
|
|
|
|
# evaluate each integrated polynomial at the right endpoint of its interval
|
|
|
|
|
if 1 < pieces:
|
|
|
|
|
# evaluate each integrated polynomial at the right endpoint of its
|
|
|
|
|
# interval
|
|
|
|
|
xs = diff(self.breaks[:-1, ...], axis=0)
|
|
|
|
|
index = np.arange(pieces - 1)
|
|
|
|
|
|
|
|
|
@ -402,19 +436,19 @@ class PPform(object):
|
|
|
|
|
|
|
|
|
|
return PPform(cof, self.breaks, fill=self.fill)
|
|
|
|
|
|
|
|
|
|
# def fromspline(self, xk, cvals, order, fill=0.0):
|
|
|
|
|
# N = len(xk) - 1
|
|
|
|
|
# sivals = np.empty((order + 1, N), dtype=float)
|
|
|
|
|
# for m in xrange(order, -1, -1):
|
|
|
|
|
# fact = spec.gamma(m + 1)
|
|
|
|
|
# res = _fitpack._bspleval(xk[:-1], xk, cvals, order, m)
|
|
|
|
|
# res /= fact
|
|
|
|
|
# sivals[order - m, :] = res
|
|
|
|
|
# return self(sivals, xk, fill=fill)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## def fromspline(cls, xk, cvals, order, fill=0.0):
|
|
|
|
|
## N = len(xk)-1
|
|
|
|
|
## sivals = np.empty((order+1,N), dtype=float)
|
|
|
|
|
## for m in xrange(order,-1,-1):
|
|
|
|
|
## fact = spec.gamma(m+1)
|
|
|
|
|
## res = _fitpack._bspleval(xk[:-1], xk, cvals, order, m)
|
|
|
|
|
## res /= fact
|
|
|
|
|
## sivals[order-m,:] = res
|
|
|
|
|
## return cls(sivals, xk, fill=fill)
|
|
|
|
|
|
|
|
|
|
class SmoothSpline(PPform):
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
Cubic Smoothing Spline.
|
|
|
|
|
|
|
|
|
@ -472,6 +506,7 @@ class SmoothSpline(PPform):
|
|
|
|
|
Springer Verlag
|
|
|
|
|
Uses EqXIV.6--9, self 239
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, xx, yy, p=None, lin_extrap=True, var=1):
|
|
|
|
|
coefs, brks = self._compute_coefs(xx, yy, p, var)
|
|
|
|
|
super(SmoothSpline, self).__init__(coefs, brks)
|
|
|
|
@ -506,7 +541,7 @@ class SmoothSpline(PPform):
|
|
|
|
|
|
|
|
|
|
dydx = np.diff(y) / dx
|
|
|
|
|
|
|
|
|
|
if (n == 2) : #% straight line
|
|
|
|
|
if (n == 2): # % straight line
|
|
|
|
|
coefs = np.vstack([dydx.ravel(), y[0, :]])
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
|
@ -518,9 +553,11 @@ class SmoothSpline(PPform):
|
|
|
|
|
dx.shape = (n - 1, -1)
|
|
|
|
|
zrs = zeros(nd)
|
|
|
|
|
if p < 1:
|
|
|
|
|
ai = (y - (6 * (1 - p) * D * diff(vstack([zrs,
|
|
|
|
|
# faster than yi-6*(1-p)*Q*u
|
|
|
|
|
ai = (y - (6 * (1 - p) * D *
|
|
|
|
|
diff(vstack([zrs,
|
|
|
|
|
diff(vstack([zrs, u, zrs]), axis=0) * dx1,
|
|
|
|
|
zrs]), axis=0)).T).T #faster than yi-6*(1-p)*Q*u
|
|
|
|
|
zrs]), axis=0)).T).T
|
|
|
|
|
else:
|
|
|
|
|
ai = y.reshape(n, -1)
|
|
|
|
|
|
|
|
|
@ -532,7 +569,7 @@ class SmoothSpline(PPform):
|
|
|
|
|
# dfi = diff(ai)./dx-(ci+di.*dx).*dx = bi;
|
|
|
|
|
|
|
|
|
|
ci = np.vstack([zrs, 3 * p * u])
|
|
|
|
|
di = (diff(vstack([ci, zrs]), axis=0) * dx1 / 3);
|
|
|
|
|
di = (diff(vstack([ci, zrs]), axis=0) * dx1 / 3)
|
|
|
|
|
bi = (diff(ai, axis=0) * dx1 - (ci + di * dx) * dx)
|
|
|
|
|
ai = ai[:n - 1, ...]
|
|
|
|
|
if nd > 1:
|
|
|
|
@ -545,7 +582,8 @@ class SmoothSpline(PPform):
|
|
|
|
|
else:
|
|
|
|
|
coefs = vstack([ci.ravel(), bi.ravel(), ai.ravel()])
|
|
|
|
|
else:
|
|
|
|
|
coefs = vstack([di.ravel(), ci.ravel(), bi.ravel(), ai.ravel()])
|
|
|
|
|
coefs = vstack(
|
|
|
|
|
[di.ravel(), ci.ravel(), bi.ravel(), ai.ravel()])
|
|
|
|
|
|
|
|
|
|
return coefs, x
|
|
|
|
|
|
|
|
|
@ -555,11 +593,15 @@ class SmoothSpline(PPform):
|
|
|
|
|
R = sp.spdiags(data, [-1, 0, 1], n - 2, n - 2)
|
|
|
|
|
|
|
|
|
|
if p is None or p < 1:
|
|
|
|
|
Q = sp.spdiags([dx1[:n - 2], -(dx1[:n - 2] + dx1[1:n - 1]), dx1[1:n - 1]], [0, -1, -2], n, n - 2)
|
|
|
|
|
Q = sp.spdiags(
|
|
|
|
|
[dx1[:n - 2], -(dx1[:n - 2] + dx1[1:n - 1]), dx1[1:n - 1]],
|
|
|
|
|
[0, -1, -2], n, n - 2)
|
|
|
|
|
QDQ = (Q.T * D * Q)
|
|
|
|
|
if p is None or p < 0:
|
|
|
|
|
# Estimate p
|
|
|
|
|
p = 1. / (1. + QDQ.diagonal().sum() / (100. * R.diagonal().sum()** 2));
|
|
|
|
|
p = 1. / \
|
|
|
|
|
(1. + QDQ.diagonal().sum() /
|
|
|
|
|
(100. * R.diagonal().sum() ** 2))
|
|
|
|
|
|
|
|
|
|
if p == 0:
|
|
|
|
|
QQ = 6 * QDQ
|
|
|
|
@ -574,8 +616,10 @@ class SmoothSpline(PPform):
|
|
|
|
|
u = 2 * sp.linalg.spsolve((QQ + QQ.T), ddydx)
|
|
|
|
|
return u.reshape(n - 2, -1), p
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _edge_case(m0, d1):
|
|
|
|
|
return np.where((d1==0) | (m0==0), 0.0, 1.0/(1.0/m0+1.0/d1))
|
|
|
|
|
return np.where((d1 == 0) | (m0 == 0), 0.0, 1.0 / (1.0 / m0 + 1.0 / d1))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def pchip_slopes(x, y):
|
|
|
|
|
# Determine the derivatives at the points y_k, d_k, by using
|
|
|
|
@ -591,24 +635,25 @@ def pchip_slopes(x, y):
|
|
|
|
|
hk = x[1:] - x[:-1]
|
|
|
|
|
mk = (y[1:] - y[:-1]) / hk
|
|
|
|
|
smk = np.sign(mk)
|
|
|
|
|
condition = ((smk[1:] != smk[:-1]) | (mk[1:]==0) | (mk[:-1]==0))
|
|
|
|
|
condition = ((smk[1:] != smk[:-1]) | (mk[1:] == 0) | (mk[:-1] == 0))
|
|
|
|
|
|
|
|
|
|
w1 = 2*hk[1:] + hk[:-1]
|
|
|
|
|
w2 = hk[1:] + 2*hk[:-1]
|
|
|
|
|
whmean = 1.0/(w1+w2)*(w1/mk[1:] + w2/mk[:-1])
|
|
|
|
|
w1 = 2 * hk[1:] + hk[:-1]
|
|
|
|
|
w2 = hk[1:] + 2 * hk[:-1]
|
|
|
|
|
whmean = 1.0 / (w1 + w2) * (w1 / mk[1:] + w2 / mk[:-1])
|
|
|
|
|
|
|
|
|
|
dk = np.zeros_like(y)
|
|
|
|
|
dk[1:-1][condition] = 0.0
|
|
|
|
|
dk[1:-1][~condition] = 1.0/whmean[~condition]
|
|
|
|
|
dk[1:-1][~condition] = 1.0 / whmean[~condition]
|
|
|
|
|
|
|
|
|
|
# For end-points choose d_0 so that 1/d_0 = 1/m_0 + 1/d_1 unless
|
|
|
|
|
# one of d_1 or m_0 is 0, then choose d_0 = 0
|
|
|
|
|
|
|
|
|
|
dk[0] = _edge_case(mk[0],dk[1])
|
|
|
|
|
dk[-1] = _edge_case(mk[-1],dk[-2])
|
|
|
|
|
dk[0] = _edge_case(mk[0], dk[1])
|
|
|
|
|
dk[-1] = _edge_case(mk[-1], dk[-2])
|
|
|
|
|
return dk
|
|
|
|
|
|
|
|
|
|
def slopes(x,y, method='parabola', tension=0, monotone=False):
|
|
|
|
|
|
|
|
|
|
def slopes(x, y, method='parabola', tension=0, monotone=False):
|
|
|
|
|
'''
|
|
|
|
|
Return estimated slopes y'(x)
|
|
|
|
|
|
|
|
|
@ -645,27 +690,27 @@ def slopes(x,y, method='parabola', tension=0, monotone=False):
|
|
|
|
|
y = np.asarray(y, np.float_)
|
|
|
|
|
yp = np.zeros(y.shape, np.float_)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dx = x[1:] - x[:-1]
|
|
|
|
|
# Compute the slopes of the secant lines between successive points
|
|
|
|
|
dydx = (y[1:] - y[:-1]) / dx
|
|
|
|
|
|
|
|
|
|
method = method.lower()
|
|
|
|
|
if method.startswith('p'): #parabola'):
|
|
|
|
|
yp[1:-1] = (dydx[:-1] * dx[1:] + dydx[1:] * dx[:-1]) / (dx[1:] + dx[:-1])
|
|
|
|
|
if method.startswith('p'): # parabola'):
|
|
|
|
|
yp[1:-1] = (dydx[:-1] * dx[1:] + dydx[1:] * dx[:-1]) / \
|
|
|
|
|
(dx[1:] + dx[:-1])
|
|
|
|
|
yp[0] = 2.0 * dydx[0] - yp[1]
|
|
|
|
|
yp[-1] = 2.0 * dydx[-1] - yp[-2]
|
|
|
|
|
else:
|
|
|
|
|
# At the endpoints - use one-sided differences
|
|
|
|
|
yp[0] = dydx[0]
|
|
|
|
|
yp[-1] = dydx[-1]
|
|
|
|
|
if method.startswith('s'): #secant'):
|
|
|
|
|
if method.startswith('s'): # secant'):
|
|
|
|
|
# In the middle - use the average of the secants
|
|
|
|
|
yp[1:-1] = (dydx[:-1] + dydx[1:]) / 2.0
|
|
|
|
|
else: # Cardinal or Catmull-Rom method
|
|
|
|
|
yp[1:-1] = (y[2:] - y[:-2]) / (x[2:] - x[:-2])
|
|
|
|
|
if method.startswith('car'): #cardinal'):
|
|
|
|
|
yp = (1-tension) * yp
|
|
|
|
|
if method.startswith('car'): # cardinal'):
|
|
|
|
|
yp = (1 - tension) * yp
|
|
|
|
|
|
|
|
|
|
if monotone:
|
|
|
|
|
# Special case: intervals where y[k] == y[k+1]
|
|
|
|
@ -673,26 +718,28 @@ def slopes(x,y, method='parabola', tension=0, monotone=False):
|
|
|
|
|
# these points will be flat which preserves monotonicity
|
|
|
|
|
ii, = (dydx == 0.0).nonzero()
|
|
|
|
|
yp[ii] = 0.0
|
|
|
|
|
yp[ii+1] = 0.0
|
|
|
|
|
yp[ii + 1] = 0.0
|
|
|
|
|
|
|
|
|
|
alpha = yp[:-1]/dydx
|
|
|
|
|
beta = yp[1:]/dydx
|
|
|
|
|
dist = alpha**2 + beta**2
|
|
|
|
|
alpha = yp[:-1] / dydx
|
|
|
|
|
beta = yp[1:] / dydx
|
|
|
|
|
dist = alpha ** 2 + beta ** 2
|
|
|
|
|
tau = 3.0 / np.sqrt(dist)
|
|
|
|
|
|
|
|
|
|
# To prevent overshoot or undershoot, restrict the position vector
|
|
|
|
|
# (alpha, beta) to a circle of radius 3. If (alpha**2 + beta**2)>9,
|
|
|
|
|
# then set m[k] = tau[k]alpha[k]delta[k] and m[k+1] = tau[k]beta[b]delta[k]
|
|
|
|
|
# then set m[k] = tau[k]alpha[k]delta[k] and
|
|
|
|
|
# m[k+1] = tau[k]beta[b]delta[k]
|
|
|
|
|
# where tau = 3/sqrt(alpha**2 + beta**2).
|
|
|
|
|
|
|
|
|
|
# Find the indices that need adjustment
|
|
|
|
|
indices_to_fix, = (dist > 9.0).nonzero()
|
|
|
|
|
for ii in indices_to_fix:
|
|
|
|
|
yp[ii] = tau[ii] * alpha[ii] * dydx[ii]
|
|
|
|
|
yp[ii+1] = tau[ii] * beta[ii] * dydx[ii]
|
|
|
|
|
yp[ii + 1] = tau[ii] * beta[ii] * dydx[ii]
|
|
|
|
|
|
|
|
|
|
return yp
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def stineman_interp(xi, x, y, yp=None):
|
|
|
|
|
"""
|
|
|
|
|
Given data vectors *x* and *y*, the slope vector *yp* and a new
|
|
|
|
@ -752,14 +799,15 @@ def stineman_interp(xi, x, y, yp=None):
|
|
|
|
|
# calculate linear slopes
|
|
|
|
|
dx = x[1:] - x[:-1]
|
|
|
|
|
dy = y[1:] - y[:-1]
|
|
|
|
|
s = dy / dx #note length of s is N-1 so last element is #N-2
|
|
|
|
|
s = dy / dx # note length of s is N-1 so last element is #N-2
|
|
|
|
|
|
|
|
|
|
# find the segment each xi is in
|
|
|
|
|
# this line actually is the key to the efficiency of this implementation
|
|
|
|
|
idx = np.searchsorted(x[1:-1], xi)
|
|
|
|
|
|
|
|
|
|
# now we have generally: x[idx[j]] <= xi[j] <= x[idx[j]+1]
|
|
|
|
|
# except at the boundaries, where it may be that xi[j] < x[0] or xi[j] > x[-1]
|
|
|
|
|
# except at the boundaries, where it may be that xi[j] < x[0] or xi[j] >
|
|
|
|
|
# x[-1]
|
|
|
|
|
|
|
|
|
|
# the y-values that would come out from a linear interpolation:
|
|
|
|
|
sidx = s.take(idx)
|
|
|
|
@ -769,62 +817,82 @@ def stineman_interp(xi, x, y, yp=None):
|
|
|
|
|
yo = yidx + sidx * (xi - xidx)
|
|
|
|
|
|
|
|
|
|
# the difference that comes when using the slopes given in yp
|
|
|
|
|
dy1 = (yp.take(idx) - sidx) * (xi - xidx) # using the yp slope of the left point
|
|
|
|
|
dy2 = (yp.take(idx + 1) - sidx) * (xi - xidxp1) # using the yp slope of the right point
|
|
|
|
|
# using the yp slope of the left point
|
|
|
|
|
dy1 = (yp.take(idx) - sidx) * (xi - xidx)
|
|
|
|
|
# using the yp slope of the right point
|
|
|
|
|
dy2 = (yp.take(idx + 1) - sidx) * (xi - xidxp1)
|
|
|
|
|
|
|
|
|
|
dy1dy2 = dy1 * dy2
|
|
|
|
|
# The following is optimized for Python. The solution actually
|
|
|
|
|
# does more calculations than necessary but exploiting the power
|
|
|
|
|
# of numpy, this is far more efficient than coding a loop by hand
|
|
|
|
|
# in Python
|
|
|
|
|
dy1mdy2 = np.where(dy1dy2,dy1-dy2,np.inf)
|
|
|
|
|
dy1pdy2 = np.where(dy1dy2,dy1+dy2,np.inf)
|
|
|
|
|
yi = yo + dy1dy2 * np.choose(np.array(np.sign(dy1dy2), np.int32) + 1,
|
|
|
|
|
((2 * xi - xidx - xidxp1) / ((dy1mdy2) * (xidxp1 - xidx)),
|
|
|
|
|
0.0,
|
|
|
|
|
dy1mdy2 = np.where(dy1dy2, dy1 - dy2, np.inf)
|
|
|
|
|
dy1pdy2 = np.where(dy1dy2, dy1 + dy2, np.inf)
|
|
|
|
|
yi = yo + dy1dy2 * np.choose(
|
|
|
|
|
np.array(np.sign(dy1dy2), np.int32) + 1,
|
|
|
|
|
((2 * xi - xidx - xidxp1) / ((dy1mdy2) * (xidxp1 - xidx)), 0.0,
|
|
|
|
|
1 / (dy1pdy2)))
|
|
|
|
|
return yi
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class StinemanInterp(object):
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
Returns the values of an interpolating function that runs through a set of points according to the algorithm of Stineman (1980).
|
|
|
|
|
Returns an interpolating function
|
|
|
|
|
that runs through a set of points according to the algorithm of
|
|
|
|
|
Stineman (1980).
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
---------
|
|
|
|
|
----------
|
|
|
|
|
x,y : array-like
|
|
|
|
|
coordinates of points defining the interpolating function.
|
|
|
|
|
yp : array-like
|
|
|
|
|
slopes of the interpolating function at x. Optional: only given if they are known, else the argument is not used.
|
|
|
|
|
slopes of the interpolating function at x.
|
|
|
|
|
Optional: only given if they are known, else the argument is not used.
|
|
|
|
|
method : string
|
|
|
|
|
method for computing the slope at the given points if the slope is not known. With method=
|
|
|
|
|
"parabola" calculates the slopes from a parabola through every three points.
|
|
|
|
|
method for computing the slope at the given points if the slope is not
|
|
|
|
|
known. With method= "parabola" calculates the slopes from a parabola
|
|
|
|
|
through every three points.
|
|
|
|
|
|
|
|
|
|
Notes
|
|
|
|
|
-----
|
|
|
|
|
The interpolation method is described in an article by Russell W. Stineman (1980)
|
|
|
|
|
|
|
|
|
|
According to Stineman, the interpolation procedure has "the following properties:
|
|
|
|
|
|
|
|
|
|
If values of the ordinates of the specified points change monotonically, and the slopes of the line segments joining
|
|
|
|
|
the points change monotonically, then the interpolating curve and its slope will change monotonically.
|
|
|
|
|
If the slopes of the line segments joining the specified points change monotonically, then the slopes of the interpolating
|
|
|
|
|
curve will change monotonically. Suppose that the conditions in (1) or (2) are satisfied by a set of points, but a small
|
|
|
|
|
change in the ordinate or slope at one of the points will result conditions (1) or (2) being not longer satisfied. Then
|
|
|
|
|
making this small change in the ordinate or slope at a point will cause no more than a small change in the interpolating
|
|
|
|
|
curve." The method is based on rational interpolation with specially chosen rational functions to satisfy the above three
|
|
|
|
|
conditions.
|
|
|
|
|
|
|
|
|
|
Slopes computed at the given points with the methods provided by the `StinemanInterp' function satisfy Stineman's requirements.
|
|
|
|
|
The original method suggested by Stineman (method="scaledstineman", the default, and "stineman") result in lower slopes near
|
|
|
|
|
abrupt steps or spikes in the point sequence, and therefore a smaller tendency for overshooting. The method based on a second
|
|
|
|
|
degree polynomial (method="parabola") provides better approximation to smooth functions, but it results in in higher slopes
|
|
|
|
|
near abrupt steps or spikes and can lead to some overshooting where Stineman's method does not. Both methods lead to much
|
|
|
|
|
less tendency for `spurious' oscillations than traditional interplation methods based on polynomials, such as splines
|
|
|
|
|
The interpolation method is described by Russell W. Stineman (1980)
|
|
|
|
|
|
|
|
|
|
According to Stineman, the interpolation procedure has "the following
|
|
|
|
|
properties:
|
|
|
|
|
|
|
|
|
|
If values of the ordinates of the specified points change monotonically,
|
|
|
|
|
and the slopes of the line segments joining the points change
|
|
|
|
|
monotonically, then the interpolating curve and its slope will change
|
|
|
|
|
monotonically. If the slopes of the line segments joining the specified
|
|
|
|
|
points change monotonically, then the slopes of the interpolating curve
|
|
|
|
|
will change monotonically. Suppose that the conditions in (1) or (2) are
|
|
|
|
|
satisfied by a set of points, but a small change in the ordinate or slope
|
|
|
|
|
at one of the points will result conditions(1) or (2) being not longer
|
|
|
|
|
satisfied. Then making this small change in the ordinate or slope at a
|
|
|
|
|
point will cause no more than a small change in the interpolating
|
|
|
|
|
curve." The method is based on rational interpolation with specially chosen
|
|
|
|
|
rational functions to satisfy the above three conditions.
|
|
|
|
|
|
|
|
|
|
Slopes computed at the given points with the methods provided by the
|
|
|
|
|
`StinemanInterp' function satisfy Stineman's requirements.
|
|
|
|
|
The original method suggested by Stineman(method="scaledstineman", the
|
|
|
|
|
default, and "stineman") result in lower slopes near abrupt steps or spikes
|
|
|
|
|
in the point sequence, and therefore a smaller tendency for overshooting.
|
|
|
|
|
The method based on a second degree polynomial(method="parabola") provides
|
|
|
|
|
better approximation to smooth functions, but it results in in higher
|
|
|
|
|
slopes near abrupt steps or spikes and can lead to some overshooting where
|
|
|
|
|
Stineman's method does not. Both methods lead to much less tendency for
|
|
|
|
|
`spurious' oscillations than traditional interplation methods based on
|
|
|
|
|
polynomials, such as splines
|
|
|
|
|
(see the examples section).
|
|
|
|
|
|
|
|
|
|
Stineman states that "The complete assurance that the procedure will never generate `wild' points makes it attractive as a
|
|
|
|
|
general purpose procedure".
|
|
|
|
|
Stineman states that "The complete assurance that the procedure will never
|
|
|
|
|
generate `wild' points makes it attractive as a general purpose procedure".
|
|
|
|
|
|
|
|
|
|
This interpolation method has been implemented in Matlab and R in addition to Python.
|
|
|
|
|
This interpolation method has been implemented in Matlab and R in addition
|
|
|
|
|
to Python.
|
|
|
|
|
|
|
|
|
|
Examples
|
|
|
|
|
--------
|
|
|
|
@ -840,17 +908,20 @@ class StinemanInterp(object):
|
|
|
|
|
>>> h=plt.subplot(211)
|
|
|
|
|
>>> h=plt.plot(x,y,'o',xi,yi,'r', xi,yi1, 'g', xi,yi1, 'b')
|
|
|
|
|
>>> h=plt.subplot(212)
|
|
|
|
|
>>> h=plt.plot(xi,np.abs(sin(xi)-yi), 'r', xi, np.abs(sin(xi)-yi1), 'g', xi, np.abs(sin(xi)-yi2), 'b')
|
|
|
|
|
>>> h=plt.plot(xi,np.abs(sin(xi)-yi), 'r',
|
|
|
|
|
... xi, np.abs(sin(xi)-yi1), 'g',
|
|
|
|
|
... xi, np.abs(sin(xi)-yi2), 'b')
|
|
|
|
|
|
|
|
|
|
References
|
|
|
|
|
----------
|
|
|
|
|
Stineman, R. W. A Consistently Well Behaved Method of Interpolation. Creative Computing (1980), volume 6, number 7, p. 54-57.
|
|
|
|
|
Stineman, R. W. A Consistently Well Behaved Method of Interpolation.
|
|
|
|
|
Creative Computing (1980), volume 6, number 7, p. 54-57.
|
|
|
|
|
|
|
|
|
|
See Also
|
|
|
|
|
--------
|
|
|
|
|
slopes, Pchip
|
|
|
|
|
'''
|
|
|
|
|
def __init__(self, x,y,yp=None,method='parabola', monotone=False):
|
|
|
|
|
def __init__(self, x, y, yp=None, method='parabola', monotone=False):
|
|
|
|
|
if yp is None:
|
|
|
|
|
yp = slopes(x, y, method, monotone)
|
|
|
|
|
self.x = np.asarray(x, np.float_)
|
|
|
|
@ -865,14 +936,16 @@ class StinemanInterp(object):
|
|
|
|
|
# calculate linear slopes
|
|
|
|
|
dx = x[1:] - x[:-1]
|
|
|
|
|
dy = y[1:] - y[:-1]
|
|
|
|
|
s = dy / dx #note length of s is N-1 so last element is #N-2
|
|
|
|
|
s = dy / dx # note length of s is N-1 so last element is #N-2
|
|
|
|
|
|
|
|
|
|
# find the segment each xi is in
|
|
|
|
|
# this line actually is the key to the efficiency of this implementation
|
|
|
|
|
# this line actually is the key to the efficiency of this
|
|
|
|
|
# implementation
|
|
|
|
|
idx = np.searchsorted(x[1:-1], xi)
|
|
|
|
|
|
|
|
|
|
# now we have generally: x[idx[j]] <= xi[j] <= x[idx[j]+1]
|
|
|
|
|
# except at the boundaries, where it may be that xi[j] < x[0] or xi[j] > x[-1]
|
|
|
|
|
# except at the boundaries, where it may be that xi[j] < x[0] or xi[j]
|
|
|
|
|
# > x[-1]
|
|
|
|
|
|
|
|
|
|
# the y-values that would come out from a linear interpolation:
|
|
|
|
|
sidx = s.take(idx)
|
|
|
|
@ -882,39 +955,48 @@ class StinemanInterp(object):
|
|
|
|
|
yo = yidx + sidx * (xi - xidx)
|
|
|
|
|
|
|
|
|
|
# the difference that comes when using the slopes given in yp
|
|
|
|
|
dy1 = (yp.take(idx) - sidx) * (xi - xidx) # using the yp slope of the left point
|
|
|
|
|
dy2 = (yp.take(idx + 1) - sidx) * (xi - xidxp1) # using the yp slope of the right point
|
|
|
|
|
# using the yp slope of the left point
|
|
|
|
|
dy1 = (yp.take(idx) - sidx) * (xi - xidx)
|
|
|
|
|
# using the yp slope of the right point
|
|
|
|
|
dy2 = (yp.take(idx + 1) - sidx) * (xi - xidxp1)
|
|
|
|
|
|
|
|
|
|
dy1dy2 = dy1 * dy2
|
|
|
|
|
# The following is optimized for Python. The solution actually
|
|
|
|
|
# does more calculations than necessary but exploiting the power
|
|
|
|
|
# of numpy, this is far more efficient than coding a loop by hand
|
|
|
|
|
# in Python
|
|
|
|
|
dy1mdy2 = np.where(dy1dy2,dy1-dy2,np.inf)
|
|
|
|
|
dy1pdy2 = np.where(dy1dy2,dy1+dy2,np.inf)
|
|
|
|
|
yi = yo + dy1dy2 * np.choose(np.array(np.sign(dy1dy2), np.int32) + 1,
|
|
|
|
|
((2 * xi - xidx - xidxp1) / ((dy1mdy2) * (xidxp1 - xidx)),
|
|
|
|
|
0.0,
|
|
|
|
|
dy1mdy2 = np.where(dy1dy2, dy1 - dy2, np.inf)
|
|
|
|
|
dy1pdy2 = np.where(dy1dy2, dy1 + dy2, np.inf)
|
|
|
|
|
yi = yo + dy1dy2 * np.choose(
|
|
|
|
|
np.array(np.sign(dy1dy2), np.int32) + 1,
|
|
|
|
|
((2 * xi - xidx - xidxp1) / ((dy1mdy2) * (xidxp1 - xidx)), 0.0,
|
|
|
|
|
1 / (dy1pdy2)))
|
|
|
|
|
return yi
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class StinemanInterp2(PiecewisePolynomial):
|
|
|
|
|
|
|
|
|
|
def __init__(self, x, y, yp=None, method='parabola', monotone=False):
|
|
|
|
|
if yp is None:
|
|
|
|
|
yp = slopes(x, y, method, monotone=monotone)
|
|
|
|
|
super(StinemanInterp2,self).__init__(x, zip(y,yp))
|
|
|
|
|
super(StinemanInterp2, self).__init__(x, zip(y, yp))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CubicHermiteSpline(PiecewisePolynomial):
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
Piecewise Cubic Hermite Interpolation using Catmull-Rom
|
|
|
|
|
method for computing the slopes.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
def __init__(self, x, y, yp=None, method='Catmull-Rom'):
|
|
|
|
|
if yp is None:
|
|
|
|
|
yp = slopes(x, y, method, monotone=False)
|
|
|
|
|
super(CubicHermiteSpline, self).__init__(x, zip(y,yp), orders=3)
|
|
|
|
|
super(CubicHermiteSpline, self).__init__(x, zip(y, yp), orders=3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Pchip(PiecewisePolynomial):
|
|
|
|
|
|
|
|
|
|
"""PCHIP 1-d monotonic cubic interpolation
|
|
|
|
|
|
|
|
|
|
Description
|
|
|
|
@ -933,10 +1015,12 @@ class Pchip(PiecewisePolynomial):
|
|
|
|
|
A 1-D array of real values. y's length along the interpolation
|
|
|
|
|
axis must be equal to the length of x.
|
|
|
|
|
yp : array
|
|
|
|
|
slopes of the interpolating function at x. Optional: only given if they are known, else the argument is not used.
|
|
|
|
|
slopes of the interpolating function at x.
|
|
|
|
|
Optional: only given if they are known, else the argument is not used.
|
|
|
|
|
method : string
|
|
|
|
|
method for computing the slope at the given points if the slope is not known. With method=
|
|
|
|
|
"parabola" calculates the slopes from a parabola through every three points.
|
|
|
|
|
method for computing the slope at the given points if the slope is not
|
|
|
|
|
known. With method="parabola" calculates the slopes from a parabola
|
|
|
|
|
through every three points.
|
|
|
|
|
|
|
|
|
|
Assumes x is sorted in monotonic order (e.g. x[1] > x[0])
|
|
|
|
|
|
|
|
|
@ -980,14 +1064,16 @@ class Pchip(PiecewisePolynomial):
|
|
|
|
|
>>> plt.show()
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, x, y, yp=None, method='secant'):
|
|
|
|
|
if yp is None:
|
|
|
|
|
yp = slopes(x, y, method=method, monotone=True)
|
|
|
|
|
super(Pchip, self).__init__(x, zip(y,yp), orders=3)
|
|
|
|
|
super(Pchip, self).__init__(x, zip(y, yp), orders=3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_smoothing_spline():
|
|
|
|
|
x = linspace(0, 2 * pi + pi / 4, 20)
|
|
|
|
|
y = sin(x) #+ np.random.randn(x.size)
|
|
|
|
|
y = sin(x) # + np.random.randn(x.size)
|
|
|
|
|
pp = SmoothSpline(x, y, p=1)
|
|
|
|
|
x1 = linspace(-1, 2 * pi + pi / 4 + 1, 20)
|
|
|
|
|
y1 = pp(x1)
|
|
|
|
@ -1003,17 +1089,18 @@ def test_smoothing_spline():
|
|
|
|
|
pass
|
|
|
|
|
#tck = interpolate.splrep(x, y, s=len(x))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def compare_methods():
|
|
|
|
|
############################################################
|
|
|
|
|
#
|
|
|
|
|
# Sine wave test
|
|
|
|
|
############################################################
|
|
|
|
|
#
|
|
|
|
|
fun = np.sin
|
|
|
|
|
# Create a example vector containing a sine wave.
|
|
|
|
|
x = np.arange(30.0)/10.
|
|
|
|
|
x = np.arange(30.0) / 10.
|
|
|
|
|
y = fun(x)
|
|
|
|
|
|
|
|
|
|
# Interpolate the data above to the grid defined by "xvec"
|
|
|
|
|
xvec = np.arange(250.)/100.
|
|
|
|
|
xvec = np.arange(250.) / 100.
|
|
|
|
|
|
|
|
|
|
# Initialize the interpolator slopes
|
|
|
|
|
# Create the pchip slopes
|
|
|
|
@ -1031,17 +1118,18 @@ def compare_methods():
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
|
|
|
|
plt.figure()
|
|
|
|
|
plt.plot(x,y, 'ro', xvec, fun(xvec),'r')
|
|
|
|
|
plt.plot(x, y, 'ro', xvec, fun(xvec), 'r')
|
|
|
|
|
plt.title("pchip() Sin test code")
|
|
|
|
|
|
|
|
|
|
# Plot the interpolated points
|
|
|
|
|
plt.plot(xvec, yvec, xvec, yvec1, xvec, yvec2,'g.',xvec, yvec3)
|
|
|
|
|
plt.legend(['true','true','parbola_monoton','parabola','catmul','pchip'], frameon=False, loc=0)
|
|
|
|
|
plt.plot(xvec, yvec, xvec, yvec1, xvec, yvec2, 'g.', xvec, yvec3)
|
|
|
|
|
plt.legend(
|
|
|
|
|
['true', 'true', 'parbola_monoton', 'parabola', 'catmul', 'pchip'],
|
|
|
|
|
frameon=False, loc=0)
|
|
|
|
|
plt.ioff()
|
|
|
|
|
plt.show()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def demo_monoticity():
|
|
|
|
|
# Step function test...
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
@ -1049,13 +1137,13 @@ def demo_monoticity():
|
|
|
|
|
plt.title("pchip() step function test")
|
|
|
|
|
# Create a step function (will demonstrate monotonicity)
|
|
|
|
|
x = np.arange(7.0) - 3.0
|
|
|
|
|
y = np.array([-1.0, -1,-1,0,1,1,1])
|
|
|
|
|
y = np.array([-1.0, -1, -1, 0, 1, 1, 1])
|
|
|
|
|
|
|
|
|
|
# Interpolate using monotonic piecewise Hermite cubic spline
|
|
|
|
|
xvec = np.arange(599.)/100. - 3.0
|
|
|
|
|
xvec = np.arange(599.) / 100. - 3.0
|
|
|
|
|
|
|
|
|
|
# Create the pchip slopes
|
|
|
|
|
m = slopes(x,y, monotone=True)
|
|
|
|
|
m = slopes(x, y, monotone=True)
|
|
|
|
|
# m1 = slopes(x, y, monotone=False)
|
|
|
|
|
# m2 = slopes(x,y,method='catmul',monotone=False)
|
|
|
|
|
m3 = pchip_slopes(x, y)
|
|
|
|
@ -1069,9 +1157,9 @@ def demo_monoticity():
|
|
|
|
|
|
|
|
|
|
# Non-montonic cubic Hermite spline interpolator using
|
|
|
|
|
# Catmul-Rom method for computing slopes...
|
|
|
|
|
yvec3 = CubicHermiteSpline(x,y)(xvec)
|
|
|
|
|
yvec3 = CubicHermiteSpline(x, y)(xvec)
|
|
|
|
|
yvec4 = StinemanInterp(x, y)(xvec)
|
|
|
|
|
yvec5 = Pchip(x, y, m3)(xvec) #@UnusedVariable
|
|
|
|
|
yvec5 = Pchip(x, y, m3)(xvec) # @UnusedVariable
|
|
|
|
|
|
|
|
|
|
# Plot the results
|
|
|
|
|
plt.plot(x, y, 'ro', label='Data')
|
|
|
|
@ -1088,49 +1176,54 @@ def demo_monoticity():
|
|
|
|
|
plt.ioff()
|
|
|
|
|
plt.show()
|
|
|
|
|
|
|
|
|
|
def test_doctstrings():
|
|
|
|
|
|
|
|
|
|
def test_func():
|
|
|
|
|
from scipy import interpolate
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
import matplotlib
|
|
|
|
|
matplotlib.interactive(True)
|
|
|
|
|
matplotlib.interactive(False)
|
|
|
|
|
|
|
|
|
|
coef = np.array([[1, 1], [0, 1]]) # linear from 0 to 2
|
|
|
|
|
#coef = np.array([[1,1],[1,1],[0,2]]) # linear from 0 to 2
|
|
|
|
|
# coef = np.array([[1,1],[1,1],[0,2]]) # linear from 0 to 2
|
|
|
|
|
breaks = [0, 1, 2]
|
|
|
|
|
pp = PPform(coef, breaks, a= -100, b=100)
|
|
|
|
|
pp = PPform(coef, breaks, a=-100, b=100)
|
|
|
|
|
x = linspace(-1, 3, 20)
|
|
|
|
|
y = pp(x) #@UnusedVariable
|
|
|
|
|
y = pp(x) # @UnusedVariable
|
|
|
|
|
|
|
|
|
|
x = linspace(0, 2 * pi + pi / 4, 20)
|
|
|
|
|
y = x + np.random.randn(x.size)
|
|
|
|
|
tck = interpolate.splrep(x, y, s=len(x))
|
|
|
|
|
y = sin(x) + np.random.randn(x.size)
|
|
|
|
|
tck = interpolate.splrep(x, y, s=len(x)) # @UndefinedVariable
|
|
|
|
|
xnew = linspace(0, 2 * pi, 100)
|
|
|
|
|
ynew = interpolate.splev(xnew, tck, der=0)
|
|
|
|
|
tck0 = interpolate.splmake(xnew, ynew, order=3, kind='smoothest', conds=None)
|
|
|
|
|
pp = interpolate.ppform.fromspline(*tck0)
|
|
|
|
|
ynew = interpolate.splev(xnew, tck, der=0) # @UndefinedVariable
|
|
|
|
|
tck0 = interpolate.splmake( # @UndefinedVariable
|
|
|
|
|
xnew, ynew, order=3, kind='smoothest', conds=None)
|
|
|
|
|
pp = interpolate.ppform.fromspline(*tck0) # @UndefinedVariable
|
|
|
|
|
|
|
|
|
|
plt.plot(x, y, "x", xnew, ynew, xnew, sin(xnew), x, y, "b")
|
|
|
|
|
plt.plot(x, y, "x", xnew, ynew, xnew, sin(xnew), x, y, "b", x, pp(x), 'g')
|
|
|
|
|
plt.legend(['Linear', 'Cubic Spline', 'True'])
|
|
|
|
|
plt.title('Cubic-spline interpolation')
|
|
|
|
|
|
|
|
|
|
plt.show()
|
|
|
|
|
|
|
|
|
|
t = np.arange(0, 1.1, .1)
|
|
|
|
|
x = np.sin(2 * np.pi * t)
|
|
|
|
|
y = np.cos(2 * np.pi * t)
|
|
|
|
|
tck1, u = interpolate.splprep([t, y], s=0) #@UnusedVariable
|
|
|
|
|
tck2 = interpolate.splrep(t, y, s=len(t), task=0)
|
|
|
|
|
#interpolate.spl
|
|
|
|
|
tck = interpolate.splmake(t, y, order=3, kind='smoothest', conds=None)
|
|
|
|
|
self = interpolate.ppform.fromspline(*tck2)
|
|
|
|
|
_tck1, _u = interpolate.splprep([t, y], s=0) # @UndefinedVariable
|
|
|
|
|
tck2 = interpolate.splrep(t, y, s=len(t), task=0) # @UndefinedVariable
|
|
|
|
|
# interpolate.spl
|
|
|
|
|
tck = interpolate.splmake(t, y, order=3, kind='smoothest', conds=None) # @UndefinedVariable
|
|
|
|
|
self = interpolate.ppform.fromspline(*tck2) # @UndefinedVariable
|
|
|
|
|
plt.plot(t, self(t))
|
|
|
|
|
plt.show()
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_pp():
|
|
|
|
|
coef = np.array([[1, 1], [0, 0]]) # linear from 0 to 2 @UnusedVariable
|
|
|
|
|
|
|
|
|
|
coef = np.array([[1, 1], [1, 1], [0, 2]]) # quadratic from 0 to 1 and 1 to 2.
|
|
|
|
|
# quadratic from 0 to 1 and 1 to 2.
|
|
|
|
|
coef = np.array([[1, 1], [1, 1], [0, 2]])
|
|
|
|
|
dc = pl.polyder(coef, 1)
|
|
|
|
|
c2 = pl.polyint(dc, 1) #@UnusedVariable
|
|
|
|
|
c2 = pl.polyint(dc, 1) # @UnusedVariable
|
|
|
|
|
breaks = [0, 1, 2]
|
|
|
|
|
pp = PPform(coef, breaks)
|
|
|
|
|
pp(0.5)
|
|
|
|
@ -1149,8 +1242,8 @@ def test_docstrings():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
# test_docstrings()
|
|
|
|
|
#test_doctstrings()
|
|
|
|
|
#test_smoothing_spline()
|
|
|
|
|
#compare_methods()
|
|
|
|
|
demo_monoticity()
|
|
|
|
|
test_func()
|
|
|
|
|
# test_doctstrings()
|
|
|
|
|
# test_smoothing_spline()
|
|
|
|
|
# compare_methods()
|
|
|
|
|
#demo_monoticity()
|
|
|
|
|