You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
419 lines
13 KiB
Python
419 lines
13 KiB
Python
#-------------------------------------------------------------------------------
|
|
# Name: module1
|
|
# Purpose:
|
|
#
|
|
# Author: pab
|
|
#
|
|
# 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.sparse as sp
|
|
import scipy.sparse.linalg #@UnusedImport
|
|
from numpy.ma.core import ones, zeros, prod, sin
|
|
from numpy import diff, pi, inf #@UnresolvedImport
|
|
from numpy.lib.shape_base import vstack
|
|
from numpy.lib.function_base import linspace
|
|
import polynomial as pl
|
|
|
|
class PPform1(object):
|
|
"""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)
|
|
where k is the degree of the polynomial.
|
|
|
|
Example
|
|
-------
|
|
>>> coef = np.array([[1,1]]) # unit step function
|
|
>>> 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
|
|
>>> breaks = [0,1,2]
|
|
>>> self = PPform(coef, breaks)
|
|
>>> x = linspace(-1,3)
|
|
>>> 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)
|
|
else:
|
|
self.breaks = np.asarray(breaks)
|
|
if a is None:
|
|
a = self.breaks[0]
|
|
if b is None:
|
|
b = self.breaks[-1]
|
|
self.coeffs = np.asarray(coeffs)
|
|
self.order = self.coeffs.shape[0]
|
|
self.fill = fill
|
|
self.a = a
|
|
self.b = b
|
|
|
|
def __call__(self, xnew):
|
|
saveshape = np.shape(xnew)
|
|
xnew = np.ravel(xnew)
|
|
res = np.empty_like(xnew)
|
|
mask = (self.a <= xnew) & (xnew <= self.b)
|
|
res[~mask] = self.fill
|
|
xx = xnew.compress(mask)
|
|
indxs = np.searchsorted(self.breaks[:-1], xx) - 1
|
|
indxs = indxs.clip(0, len(self.breaks))
|
|
pp = self.coeffs
|
|
dx = xx - self.breaks.take(indxs)
|
|
if True:
|
|
v = pp[0, indxs]
|
|
for i in xrange(1, self.order):
|
|
v = dx * v + pp[i, indxs]
|
|
values = v
|
|
else:
|
|
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))])
|
|
|
|
res[mask] = values
|
|
res.shape = saveshape
|
|
return res
|
|
|
|
def linear_extrapolate(self, output=True):
|
|
'''
|
|
Return a 1D PPform which extrapolate linearly outside its basic interval
|
|
'''
|
|
|
|
max_order = 2
|
|
|
|
if self.order <= max_order:
|
|
if output:
|
|
return self
|
|
else:
|
|
return
|
|
breaks = self.breaks.copy()
|
|
coefs = self.coeffs.copy()
|
|
#pieces = len(breaks) - 1
|
|
|
|
# Add new breaks beyond each end
|
|
breaks2add = breaks[[0, -1]] + np.array([-1, 1])
|
|
newbreaks = np.hstack([breaks2add[0], breaks, breaks2add[1]])
|
|
|
|
dx = newbreaks[[0, -2]] - breaks[[0, -2]]
|
|
|
|
dx = dx.ravel()
|
|
|
|
# Get coefficients for the new last polynomial piece (a_n)
|
|
# by just relocate the previous last polynomial and
|
|
# then set all terms of order > maxOrder to zero
|
|
|
|
a_nn = coefs[:, -1]
|
|
dxN = dx[-1]
|
|
|
|
a_n = pl.polyreloc(a_nn, -dxN) # Relocate last polynomial
|
|
#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)
|
|
# 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
|
|
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)])
|
|
if output:
|
|
return PPform(newcoefs, newbreaks, a= -inf, b=inf)
|
|
else:
|
|
self.coeffs = newcoefs
|
|
self.breaks = newbreaks
|
|
self.a = -inf
|
|
self.b = inf
|
|
|
|
def derivative(self):
|
|
"""
|
|
Return first derivative of the piecewise polynomial
|
|
"""
|
|
|
|
cof = pl.polyder(self.coeffs)
|
|
brks = self.breaks.copy()
|
|
return PPform(cof, brks, fill=self.fill)
|
|
|
|
|
|
def integrate(self):
|
|
"""
|
|
Return the indefinite integral of the piecewise polynomial
|
|
"""
|
|
cof = pl.polyint(self.coeffs)
|
|
|
|
pieces = len(self.breaks) - 1
|
|
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)
|
|
|
|
vv = xs * cof[0, index]
|
|
k = self.order
|
|
for i in xrange(1, k):
|
|
vv = xs * (vv + cof[i, index])
|
|
|
|
cof[-1] = np.hstack((0, vv)).cumsum()
|
|
|
|
return PPform(cof, self.breaks, fill=self.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.
|
|
|
|
Parameters
|
|
----------
|
|
x : array-like
|
|
x-coordinates of data. (vector)
|
|
y : array-like
|
|
y-coordinates of data. (vector or matrix)
|
|
p : real scalar
|
|
smoothing parameter between 0 and 1:
|
|
0 -> LS-straight line
|
|
1 -> cubic spline interpolant
|
|
lin_extrap : bool
|
|
if False regular smoothing spline
|
|
if True a smoothing spline with a constraint on the ends to
|
|
ensure linear extrapolation outside the range of the data (default)
|
|
var : array-like
|
|
variance of each y(i) (default 1)
|
|
|
|
Returns
|
|
-------
|
|
pp : ppform
|
|
If xx is not given, return self-form of the spline.
|
|
|
|
Given the approximate values
|
|
|
|
y(i) = g(x(i))+e(i)
|
|
|
|
of some smooth function, g, where e(i) is the error. SMOOTH tries to
|
|
recover g from y by constructing a function, f, which minimizes
|
|
|
|
p * sum (Y(i) - f(X(i)))^2/d2(i) + (1-p) * int (f'')^2
|
|
|
|
|
|
Example
|
|
-------
|
|
>>> import numpy as np
|
|
>>> x = np.linspace(0,1)
|
|
>>> y = exp(x)+1e-1*np.random.randn(x.shape)
|
|
>>> pp9 = SmoothSpline(x, y, p=.9)
|
|
>>> pp99 = SmoothSpline(x, y, p=.99, var=0.01)
|
|
>>> plot(x,y, x,pp99(x),'g', x,pp9(x),'k', x,exp(x),'r')
|
|
|
|
See also
|
|
--------
|
|
lc2tr, dat2tr
|
|
|
|
|
|
References
|
|
----------
|
|
Carl de Boor (1978)
|
|
'Practical Guide to Splines'
|
|
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)
|
|
if lin_extrap:
|
|
self.linear_extrapolate(output=False)
|
|
|
|
def _compute_coefs(self, xx, yy, p=None, var=1):
|
|
x, y = np.atleast_1d(xx, yy)
|
|
x = x.ravel()
|
|
dx = np.diff(x)
|
|
must_sort = (dx < 0).any()
|
|
if must_sort:
|
|
ind = x.argsort()
|
|
x = x[ind]
|
|
y = y[..., ind]
|
|
dx = np.diff(x)
|
|
|
|
n = len(x)
|
|
|
|
#ndy = y.ndim
|
|
szy = y.shape
|
|
|
|
nd = prod(szy[:-1])
|
|
ny = szy[-1]
|
|
|
|
if n < 2:
|
|
raise ValueError('There must be >=2 data points.')
|
|
elif (dx <= 0).any():
|
|
raise ValueError('Two consecutive values in x can not be equal.')
|
|
elif n != ny:
|
|
raise ValueError('x and y must have the same length.')
|
|
|
|
dydx = np.diff(y) / dx
|
|
|
|
if (n == 2) : #% straight line
|
|
coefs = np.vstack([dydx.ravel(), y[0, :]])
|
|
else:
|
|
|
|
dx1 = 1. / dx
|
|
D = sp.spdiags(var * ones(n), 0, n, n) # The variance
|
|
|
|
u, p = self._compute_u(p, D, dydx, dx, dx1, n)
|
|
dx1.shape = (n - 1, -1)
|
|
dx.shape = (n - 1, -1)
|
|
zrs = zeros(nd)
|
|
if p < 1:
|
|
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
|
|
else:
|
|
ai = y.reshape(n, -1)
|
|
|
|
# The piecewise polynominals are written as
|
|
# fi=ai+bi*(x-xi)+ci*(x-xi)^2+di*(x-xi)^3
|
|
# where the derivatives in the knots according to Carl de Boor are:
|
|
# ddfi = 6*p*[0;u] = 2*ci;
|
|
# dddfi = 2*diff([ci;0])./dx = 6*di;
|
|
# 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);
|
|
bi = (diff(ai, axis=0) * dx1 - (ci + di * dx) * dx)
|
|
ai = ai[:n - 1, ...]
|
|
if nd > 1:
|
|
di = di.T
|
|
ci = ci.T
|
|
ai = ai.T
|
|
#end
|
|
if not any(di):
|
|
if not any(ci):
|
|
coefs = vstack([bi.ravel(), ai.ravel()])
|
|
else:
|
|
coefs = vstack([ci.ravel(), bi.ravel(), ai.ravel()])
|
|
#end
|
|
else:
|
|
coefs = vstack([di.ravel(), ci.ravel(), bi.ravel(), ai.ravel()])
|
|
|
|
return coefs, x
|
|
|
|
def _compute_u(self, p, D, dydx, dx, dx1, n):
|
|
if p is None or p != 0:
|
|
data = [dx[1:n - 1], 2 * (dx[:n - 2] + dx[1:n - 1]), dx[:n - 2]]
|
|
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)
|
|
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));
|
|
|
|
if p == 0:
|
|
QQ = 6 * QDQ
|
|
else:
|
|
QQ = (6 * (1 - p)) * (QDQ) + p * R
|
|
else:
|
|
QQ = R
|
|
|
|
# Make sure it uses symmetric matrix solver
|
|
ddydx = diff(dydx, axis=0)
|
|
sp.linalg.use_solver(useUmfpack=True)
|
|
u = 2 * sp.linalg.spsolve((QQ + QQ.T), ddydx)
|
|
#faster than u=QQ\(Q' * yi);
|
|
return u.reshape(n - 2, -1), p
|
|
|
|
|
|
def test_smoothing_spline():
|
|
x = linspace(0, 2 * pi + pi / 4, 20)
|
|
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)
|
|
pp1 = pp.derivative()
|
|
pp0 = pp1.integrate()
|
|
dy1 = pp1(x1)
|
|
y01 = pp0(x1)
|
|
#dy = y-y1
|
|
import pylab as plb
|
|
|
|
plb.plot(x, y, x1, y1, '.', x1, dy1, 'ro', x1, y01, 'r-')
|
|
plb.show()
|
|
pass
|
|
#tck = interpolate.splrep(x, y, s=len(x))
|
|
|
|
def main():
|
|
from scipy import interpolate
|
|
import matplotlib.pyplot as plt
|
|
import matplotlib
|
|
matplotlib.interactive(True)
|
|
|
|
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
|
|
breaks = [0, 1, 2]
|
|
pp = PPform(coef, breaks, a= -100, b=100)
|
|
x = linspace(-1, 3, 20)
|
|
y = pp(x)
|
|
|
|
x = linspace(0, 2 * pi + pi / 4, 20)
|
|
y = x + np.random.randn(x.size)
|
|
tck = interpolate.splrep(x, y, s=len(x))
|
|
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)
|
|
|
|
plt.plot(x, y, "x", xnew, ynew, xnew, sin(xnew), x, y, "b")
|
|
plt.legend(['Linear', 'Cubic Spline', 'True'])
|
|
plt.title('Cubic-spline interpolation')
|
|
|
|
|
|
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)
|
|
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)
|
|
plt.plot(t, self(t))
|
|
pass
|
|
|
|
def test_pp():
|
|
import polynomial as pl
|
|
coef = np.array([[1, 1], [0, 0]]) # linear from 0 to 2
|
|
|
|
coef = np.array([[1, 1], [1, 1], [0, 2]]) # quadratic from 0 to 1 and 1 to 2.
|
|
dc = pl.polyder(coef, 1)
|
|
c2 = pl.polyint(dc, 1)
|
|
breaks = [0, 1, 2]
|
|
pp = PPform(coef, breaks)
|
|
pp(0.5)
|
|
pp(1)
|
|
pp(1.5)
|
|
dpp = pp.derivative()
|
|
import pylab as plb
|
|
x = plb.linspace(-1, 3)
|
|
plb.plot(x, pp(x), x, dpp(x), '.')
|
|
plb.show()
|
|
|
|
if __name__ == '__main__':
|
|
#main()
|
|
test_smoothing_spline()
|