r"""
Show how to compute a derivative spline.
Scipy's splines are represented in terms of the standard B-spline basis
functions. In short, a spline of degree ``k`` is represented in terms of the
knots ``t`` and coefficients ``c`` by:
.. math::
s(x) = \sum_{j=-\infty}^\infty c_{j} B^k_{j}(x)
\\
B^0_i(x) = 1, \text{if $t_i \le x < t_{i+1}$, otherwise $0$,}
\\
B^k_i(x) = \frac{x - t_i}{t_{i+k} - t_i} B^{k-1}_i(x)
+ \frac{t_{i+k+1} - x}{t_{i+k+1} - t_{i+1}} B^{k-1}_{i+1}(x)
where :math:`c_j` is nonzero only for ``0 <= j <= N`` and the first
and last `k` knots are at the same position and only used to set up
boundary conditions; terms with vanishing denominators are omitted.
One can follow standard spline textbooks here, and work out that by
differentiating this, one obtains another B-spline, of one order
lower:
.. math::
s'(x) = \sum_{j=-\infty}^\infty d_j B^{k-1}_j(x)
\\
d_j = k \frac{c_{j+1} - c_{j}}{t_{j+k+1} - t_{j+1}}
Care needs to be paid at the endpoints: the first knot
should be thrown away since the order is reduced by one.
"""
import numpy as np
from scipy.interpolate import splev
from scipy.interpolate import splrep
from scipy.interpolate import UnivariateSpline
import matplotlib.pyplot as plt
class MyUnivariateSpline(UnivariateSpline):
@classmethod
def _from_tck(cls, t, c, k):
self = cls.__new__(cls)
self._eval_args = t, c, k
#_data == x,y,w,xb,xe,k,s,n,t,c,fp,fpint,nrdata,ier
self._data = [None,None,None,None,None,k,None,len(t),t,
c,None,None,None,None]
return self
def derivative_spline(self):
"""
Compute the derivative spline of a spline in FITPACK tck
representation.
"""
t, c, k = self._eval_args
if k <= 0:
raise ValueError("Cannot differentiate order 0 spline")
# Compute the denominator in the differentiation formula.
dt = t[k+1:-1] - t[1:-k-1]
# Compute the new coefficients
d = (c[1:-1-k] - c[:-2-k]) * k / dt
# Adjust knots
t2 = t[1:-1]
# Pad coefficient array to same size as knots (FITPACK convention)
d = np.r_[d, [0]*k]
# Done, return a new spline
return self._from_tck(t2, d, k-1)
def main():
y = [-1, 5, 6, 4.3, 2, 5.2, 8, 5.6, 1]
x = range(len(y))
# Fit a spline (must use order 4, since roots() works only for order 3)
s0 = MyUnivariateSpline(x, y, s=0, k=5)
# Compute the derivative splines of the fitted spline
s1 = s0.derivative_spline()
s2 = s1.derivative_spline()
xs = np.linspace(0, len(y), 16*len(y))
ys0 = s0(xs)
ys1 = s1(xs)
ys2 = s2(xs)
# Roots (works only with order 3 splines)
inflection_points = s2.roots()
print inflection_points
print "inflection points: x =", zip(inflection_points, s0(inflection_points))
plt.ylim(-10, 15)
plt.plot(x, y, 'o',
inflection_points, s0(inflection_points), '*',
xs, ys0, '-',
xs, ys1, '-',
xs, ys2, '-')
plt.savefig('out.png', dpi=96)
plt.show()
if __name__ == "__main__":
main()
ciIiIgpTaG93IGhvdyB0byBjb21wdXRlIGEgZGVyaXZhdGl2ZSBzcGxpbmUuCgpTY2lweSdzIHNwbGluZXMgYXJlIHJlcHJlc2VudGVkIGluIHRlcm1zIG9mIHRoZSBzdGFuZGFyZCBCLXNwbGluZSBiYXNpcwpmdW5jdGlvbnMuICBJbiBzaG9ydCwgYSBzcGxpbmUgb2YgZGVncmVlIGBga2BgIGlzIHJlcHJlc2VudGVkIGluIHRlcm1zIG9mIHRoZQprbm90cyBgYHRgYCBhbmQgY29lZmZpY2llbnRzIGBgY2BgIGJ5OgoKLi4gbWF0aDo6CgogICAgcyh4KSA9IFxzdW1fe2o9LVxpbmZ0eX1eXGluZnR5IGNfe2p9IEJea197an0oeCkKICAgIFxcCiAgICBCXjBfaSh4KSA9IDEsIFx0ZXh0e2lmICR0X2kgXGxlIHggPCB0X3tpKzF9JCwgb3RoZXJ3aXNlICQwJCx9CiAgICBcXAogICAgQl5rX2koeCkgPSBcZnJhY3t4IC0gdF9pfXt0X3tpK2t9IC0gdF9pfSBCXntrLTF9X2koeCkKICAgICAgICAgICAgICsgXGZyYWN7dF97aStrKzF9IC0geH17dF97aStrKzF9IC0gdF97aSsxfX0gQl57ay0xfV97aSsxfSh4KQoKd2hlcmUgOm1hdGg6YGNfamAgaXMgbm9uemVybyBvbmx5IGZvciBgYDAgPD0gaiA8PSBOYGAgYW5kIHRoZSBmaXJzdAphbmQgbGFzdCBga2Aga25vdHMgYXJlIGF0IHRoZSBzYW1lIHBvc2l0aW9uIGFuZCBvbmx5IHVzZWQgdG8gc2V0IHVwCmJvdW5kYXJ5IGNvbmRpdGlvbnM7IHRlcm1zIHdpdGggdmFuaXNoaW5nIGRlbm9taW5hdG9ycyBhcmUgb21pdHRlZC4KCk9uZSBjYW4gZm9sbG93IHN0YW5kYXJkIHNwbGluZSB0ZXh0Ym9va3MgaGVyZSwgYW5kIHdvcmsgb3V0IHRoYXQgYnkKZGlmZmVyZW50aWF0aW5nIHRoaXMsIG9uZSBvYnRhaW5zIGFub3RoZXIgQi1zcGxpbmUsIG9mIG9uZSBvcmRlcgpsb3dlcjoKCi4uIG1hdGg6OgoKICAgcycoeCkgPSBcc3VtX3tqPS1caW5mdHl9XlxpbmZ0eSBkX2ogQl57ay0xfV9qKHgpCiAgIFxcCiAgIGRfaiA9IGsgXGZyYWN7Y197aisxfSAtIGNfe2p9fXt0X3tqK2srMX0gLSB0X3tqKzF9fQoKQ2FyZSBuZWVkcyB0byBiZSBwYWlkIGF0IHRoZSBlbmRwb2ludHM6IHRoZSBmaXJzdCBrbm90CnNob3VsZCBiZSB0aHJvd24gYXdheSBzaW5jZSB0aGUgb3JkZXIgaXMgcmVkdWNlZCBieSBvbmUuCgoiIiIKaW1wb3J0IG51bXB5IGFzIG5wCmZyb20gc2NpcHkuaW50ZXJwb2xhdGUgaW1wb3J0IHNwbGV2CmZyb20gc2NpcHkuaW50ZXJwb2xhdGUgaW1wb3J0IHNwbHJlcApmcm9tIHNjaXB5LmludGVycG9sYXRlIGltcG9ydCBVbml2YXJpYXRlU3BsaW5lCgppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0CgoKY2xhc3MgTXlVbml2YXJpYXRlU3BsaW5lKFVuaXZhcmlhdGVTcGxpbmUpOgogICAgQGNsYXNzbWV0aG9kCiAgICBkZWYgX2Zyb21fdGNrKGNscywgdCwgYywgayk6CiAgICAgICAgc2VsZiA9IGNscy5fX25ld19fKGNscykKICAgICAgICBzZWxmLl9ldmFsX2FyZ3MgPSB0LCBjLCBrCiAgICAgICAgI19kYXRhID09IHgseSx3LHhiLHhlLGsscyxuLHQsYyxmcCxmcGludCxucmRhdGEsaWVyCiAgICAgICAgc2VsZi5fZGF0YSA9IFtOb25lLE5vbmUsTm9uZSxOb25lLE5vbmUsayxOb25lLGxlbih0KSx0LAogICAgICAgICAgICAgICAgICAgICAgYyxOb25lLE5vbmUsTm9uZSxOb25lXQogICAgICAgIHJldHVybiBzZWxmCgogICAgZGVmIGRlcml2YXRpdmVfc3BsaW5lKHNlbGYpOgogICAgICAgICIiIgogICAgICAgIENvbXB1dGUgdGhlIGRlcml2YXRpdmUgc3BsaW5lIG9mIGEgc3BsaW5lIGluIEZJVFBBQ0sgdGNrCiAgICAgICAgcmVwcmVzZW50YXRpb24uCiAgICAgICAgIiIiCiAgICAgICAgdCwgYywgayA9IHNlbGYuX2V2YWxfYXJncwogICAgICAgIGlmIGsgPD0gMDoKICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigiQ2Fubm90IGRpZmZlcmVudGlhdGUgb3JkZXIgMCBzcGxpbmUiKQogICAgICAgICMgQ29tcHV0ZSB0aGUgZGVub21pbmF0b3IgaW4gdGhlIGRpZmZlcmVudGlhdGlvbiBmb3JtdWxhLgogICAgICAgIGR0ID0gdFtrKzE6LTFdIC0gdFsxOi1rLTFdCiAgICAgICAgIyBDb21wdXRlIHRoZSBuZXcgY29lZmZpY2llbnRzCiAgICAgICAgZCA9IChjWzE6LTEta10gLSBjWzotMi1rXSkgKiBrIC8gZHQKICAgICAgICAjIEFkanVzdCBrbm90cwogICAgICAgIHQyID0gdFsxOi0xXQogICAgICAgICMgUGFkIGNvZWZmaWNpZW50IGFycmF5IHRvIHNhbWUgc2l6ZSBhcyBrbm90cyAoRklUUEFDSyBjb252ZW50aW9uKQogICAgICAgIGQgPSBucC5yX1tkLCBbMF0qa10KICAgICAgICAjIERvbmUsIHJldHVybiBhIG5ldyBzcGxpbmUKICAgICAgICByZXR1cm4gc2VsZi5fZnJvbV90Y2sodDIsIGQsIGstMSkKCmRlZiBtYWluKCk6CiAgICB5ID0gWy0xLCA1LCA2LCA0LjMsIDIsIDUuMiwgOCwgNS42LCAxXQogICAgeCA9IHJhbmdlKGxlbih5KSkKCiAgICAjIEZpdCBhIHNwbGluZSAobXVzdCB1c2Ugb3JkZXIgNCwgc2luY2Ugcm9vdHMoKSB3b3JrcyBvbmx5IGZvciBvcmRlciAzKQogICAgczAgPSBNeVVuaXZhcmlhdGVTcGxpbmUoeCwgeSwgcz0wLCBrPTUpCgogICAgIyBDb21wdXRlIHRoZSBkZXJpdmF0aXZlIHNwbGluZXMgb2YgdGhlIGZpdHRlZCBzcGxpbmUKICAgIHMxID0gczAuZGVyaXZhdGl2ZV9zcGxpbmUoKQogICAgczIgPSBzMS5kZXJpdmF0aXZlX3NwbGluZSgpCgogICAgeHMgPSBucC5saW5zcGFjZSgwLCBsZW4oeSksIDE2Kmxlbih5KSkKICAgIHlzMCA9IHMwKHhzKQogICAgeXMxID0gczEoeHMpCiAgICB5czIgPSBzMih4cykKCiAgICAjIFJvb3RzICh3b3JrcyBvbmx5IHdpdGggb3JkZXIgMyBzcGxpbmVzKQogICAgaW5mbGVjdGlvbl9wb2ludHMgPSBzMi5yb290cygpCiAgICBwcmludCBpbmZsZWN0aW9uX3BvaW50cwogICAgcHJpbnQgImluZmxlY3Rpb24gcG9pbnRzOiAgeCA9IiwgemlwKGluZmxlY3Rpb25fcG9pbnRzLCBzMChpbmZsZWN0aW9uX3BvaW50cykpCgogICAgcGx0LnlsaW0oLTEwLCAxNSkKICAgIHBsdC5wbG90KHgsIHksICdvJywKICAgICAgICAgICAgaW5mbGVjdGlvbl9wb2ludHMsIHMwKGluZmxlY3Rpb25fcG9pbnRzKSwgJyonLAogICAgICAgIAl4cywgeXMwLCAnLScsCiAgICAgICAgICAgIHhzLCB5czEsICctJywKICAgICAgICAgICAgeHMsIHlzMiwgJy0nKQogICAgcGx0LnNhdmVmaWcoJ291dC5wbmcnLCBkcGk9OTYpCiAgICBwbHQuc2hvdygpCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgbWFpbigpCg==