""" Extended functions to operate on polynomials """ # ------------------------------------------------------------------------ # Name: polynomial # Purpose: Functions to operate on polynomials. # # Author: pab # polyXXX functions are based on functions found in the matlab toolbox polyutil # written by # Author: Peter J. Acklam # E-mail: pjacklam@online.no # WWW URL: http://home.online.no/~pjacklam # # Created: 30.12.2008 # Copyright: (c) pab 2008 # Licence: LGPL # ------------------------------------------------------------------------ # !/usr/bin/env python from __future__ import absolute_import import warnings # @UnusedImport from functools import reduce from numpy.polynomial import polyutils as pu from .plotbackend import plotbackend as plt import numpy as np from numpy import (newaxis, arange, pi) from scipy.fftpack import dct, idct as _idct from numpy.lib.polynomial import * # @UnusedWildImport from scipy.misc import pade # @UnresolvedImport __all__ = np.lib.polynomial.__all__ __all__ = __all__ + ['pade', 'padefit', 'polyreloc', 'polyrescl', 'polytrim', 'poly2hstr', 'poly2str', 'polyshift', 'polyishift', 'map_from_intervall', 'map_to_intervall', 'cheb2poly', 'chebextr', 'chebroot', 'chebpoly', 'chebfit', 'chebval', 'chebder', 'chebint', 'Cheb1d', 'dct', 'idct'] def polyint(p, m=1, k=None): """ Return an antiderivative (indefinite integral) of a polynomial. The returned order `m` antiderivative `P` of polynomial `p` satisfies :math:`\\frac{d^m}{dx^m}P(x) = p(x)` and is defined up to `m - 1` integration constants `k`. The constants determine the low-order polynomial part .. math:: \\frac{k_{m-1}}{0!} x^0 + \\ldots + \\frac{k_0}{(m-1)!}x^{m-1} of `P` so that :math:`P^{(j)}(0) = k_{m-j-1}`. Parameters ---------- p : {array_like, poly1d} Polynomial to differentiate. A sequence is interpreted as polynomial coefficients, see `poly1d`. m : int, optional Order of the antiderivative. (Default: 1) k : {None, list of `m` scalars, scalar}, optional Integration constants. They are given in the order of integration: those corresponding to highest-order terms come first. If ``None`` (default), all constants are assumed to be zero. If `m = 1`, a single scalar can be given instead of a list. See Also -------- polyder : derivative of a polynomial poly1d.integ : equivalent method Examples -------- The defining property of the antiderivative: >>> p = np.poly1d([1,1,1]) >>> P = np.polyint(p) >>> P poly1d([ 0.33333333, 0.5 , 1. , 0. ]) >>> np.polyder(P) == p True The integration constants default to zero, but can be specified: >>> P = np.polyint(p, 3) >>> P(0) 0.0 >>> np.polyder(P)(0) 0.0 >>> np.polyder(P, 2)(0) 0.0 >>> P = np.polyint(p, 3, k=[6, 5, 3]) >>> P poly1d([ 0.01666667, 0.04166667, 0.16666667, 3. , 5. , 3. ]) Note that 3 = 6 / 2!, and that the constants are given in the order of integrations. Constant of the highest-order polynomial term comes first: >>> np.polyder(P, 2)(0) 6.0 >>> np.polyder(P, 1)(0) 5.0 >>> P(0) 3.0 """ m = int(m) if m < 0: raise ValueError("Order of integral must be positive (see polyder)") if k is None: k = np.zeros(m, float) k = np.atleast_1d(k) if len(k) == 1 and m > 1: k = k[0] * np.ones(m, float) if len(k) < m: raise ValueError( "k must be a scalar or a rank-1 array of length 1 or >m.") truepoly = isinstance(p, poly1d) p = np.asarray(p) if m == 0: if truepoly: return poly1d(p) return p else: ix = arange(len(p), 0, -1) if p.ndim > 1: ix = ix[..., newaxis] pieces = p.shape[-1] k0 = k[0] * np.ones((1, pieces), dtype=int) else: k0 = [k[0]] y = np.concatenate((p.__truediv__(ix), k0), axis=0) val = polyint(y, m - 1, k=k[1:]) if truepoly: return poly1d(val) return val def polyder(p, m=1): """ Return the derivative of the specified order of a polynomial. Parameters ---------- p : poly1d or sequence Polynomial to differentiate. A sequence is interpreted as polynomial coefficients, see `poly1d`. m : int, optional Order of differentiation (default: 1) Returns ------- der : poly1d A new polynomial representing the derivative. See Also -------- polyint : Anti-derivative of a polynomial. poly1d : Class for one-dimensional polynomials. Examples -------- The derivative of the polynomial :math:`x^3 + x^2 + x^1 + 1` is: >>> p = np.poly1d([1,1,1,1]) >>> p2 = np.polyder(p) >>> p2 poly1d([3, 2, 1]) which evaluates to: >>> p2(2.) 17.0 We can verify this, approximating the derivative with ``(f(x + h) - f(x))/h``: >>> (p(2. + 0.001) - p(2.)) / 0.001 17.007000999997857 The fourth-order derivative of a 3rd-order polynomial is zero: >>> np.polyder(p, 2) poly1d([6, 2]) >>> np.polyder(p, 3) poly1d([6]) >>> np.polyder(p, 4) poly1d([ 0.]) """ m = int(m) if m < 0: raise ValueError("Order of derivative must be positive (see polyint)") truepoly = isinstance(p, poly1d) p = np.asarray(p) if m == 0: if truepoly: return poly1d(p) return p else: n = len(p) - 1 ix = arange(n, 0, -1) if p.ndim > 1: ix = ix[..., newaxis] y = ix * p[:-1] val = polyder(y, m - 1) if truepoly: return poly1d(val) return val def polydeg(x, y): ''' Return optimal degree for polynomial fitting N = POLYDEG(X,Y) finds the optimal degree for polynomial fitting, according to the Akaike's information criterion. Assuming that you want to find the degree N of a polynomial that fits the data Y(X) best in a least-squares sense, the Akaike's information criterion is defined by: 2*(N + 1) + n * (log(2 * pi * RSS / n) + 1) where n is the number of points and RSS is the residual sum of squares. The optimal degree N is defined here as that which minimizes AIC: http://en.wikipedia.org/wiki/Akaike_Information_Criterion Notes: ----- If the number of data is small, POLYDEG may tend to return: N = (number of points)-1. ORTHOFIT is more appropriate than POLYFIT for polynomial fitting with relatively high degrees. Example: ------- >>> x = np.linspace(0,10,300) >>> noise = 0.05 * np.random.randn(x.size) >>> noise = 0.05 * np.sin(100*x) >>> y = np.sin(x ** 3 / 100) ** 2 + noise >>> n = polydeg(x,y) >>> n 21 ys = orthofit(x,y,n); plt.plot(x, y, '.', x, ys, 'k') See also -------- polyfit, orthofit ''' x, y = np.atleast_1d(x, y) x = x.ravel() y = y.ravel() N = len(x) # Search the optimal degree minimizing the Akaike's information criterion # y(x) are fitted in a least-squares sense using a polynomial of degree n # developed in a series of orthogonal polynomials. ys = np.ones((N,)) * y.mean() # correction for small sample sizes logsum2 = (np.log(2 * pi * ((ys - y) ** 2).sum() / N) + 1) AIC = 2 + N * logsum2 + 4 / (N - 2) n = 1 nit = 0 # While-loop is stopped when a minimum is detected. 3 more steps are # required to take AIC noise into account and to ensure that this minimum # is a (likely) global minimum. while nit < 8: p = orthofit(x, y, n) ys = orthoval(p, x) # -- Akaike's Information Criterion aic = (2 * (n + 1) * (1 + (n + 2) / (N - n - 2)) + N * (np.log(2 * pi * np.sum((ys - y) ** 2) / N) + 1)) if aic >= AIC: nit += 1 else: nit = 0 AIC = aic n = n + 1 if n >= N: break n = n - nit - 1 return n def orthoval(p, x): ''' Evaluation of orthogonal polynomial Parameters ---------- p : array_like 2D array of polynomial coefficients (including coefficients equal to zero) from highest degree to the constant term. x : array_like A number or a 1D array of numbers "at" which to evaluate `p`. Returns ------- values : ndarray See Also -------- orthofit ''' p = np.atleast_2d(p) n = p.shape[1] - 1 xi = np.atleast_1d(x) shape0 = xi.shape if n == 0: return np.ones(shape0) * p[0] xi = xi.ravel() xn = np.ones((n + 1, len(xi))) xn[1] = xi - p[1, 1] for i in range(2, n + 1): xn[i, :] = (xi - p[1, i]) * xn[i - 1, :] - p[2, i] * xn[i - 2, :] ys = np.dot(p[0], xn) return ys.reshape(shape0) def ortho2poly(p): """ Converts orthogonal polynomial to ordinary polynomial coefficients Parameters ---------- p : array-like orthogonal polynomial coefficients Returns ------- p : ndarray ordinary polynomial coefficients It is not advised to do this for p.shape[1]>10 due to numerical cancellations. See also -------- orthoval orthofit Examples -------- >>> import numpy as np >>> x = np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0]) >>> y = np.array([0.0, 0.8, 0.9, 0.1, -0.8, -1.0]) >>> p = orthofit(x, y, 3) >>> p array([[ 0. , -0.30285714, -0.16071429, 0.08703704], [ 0. , 2.5 , 2.5 , 2.5 ], [ 0. , 0. , 2.91666667, 2.13333333]]) >>> ortho2poly(p) array([ 0.08703704, -0.81349206, 1.69312169, -0.03968254]) >>> np.polyfit(x, y, 3) array([ 0.08703704, -0.81349206, 1.69312169, -0.03968254]) References ---------- """ p = np.atleast_2d(p) n = p.shape[1] - 1 if n == 0: return p[0] x = [1, ] * (n + 1) x[1] = np.array([1, - p[1, 1]]) for i in range(2, n + 1): x[i] = polyadd(polymul([1, - p[1, i]], x[i - 1]), - p[2, i] * x[i - 2]) for i in range(n + 1): x[i] *= p[0, i] return reduce(polyadd, x) def orthofit(x, y, n): ''' Fit orthogonal polynomial to data. Parameters --------- x, y : arrays data Y(X) to fit to a polynomial. n : integer degree of fitted polynomial. Returns ------- p : array orthogonal polynomial Notes: ----- Orthofit smooths/fits data using a polynomial of degree N developed in a sequence of orthogonal polynomials. ORTHOFIT is more appropriate than polyfit for polynomial fitting and smoothing since this method does not involve any matrix linear system but a simple recursive procedure. Degrees much higher than 30 could be used with orthogonal polynomials, whereas badly conditioned matrices may appear with a classical polynomial fitting of degree typically higher than 10. To avoid using unnecessarily high degrees, you may let the function POLYDEG choose it for you. POLYDEG finds an optimal polynomial degree according to the Akaike's information criterion. Example: ------- >>> x = np.linspace(0,10,300); >>> y = np.sin(x**3/100)**2 + 0.05*np.random.randn(x.size) >>> p = orthofit(x, y, 25) >>> ys = orthoval(p, x) plot(x, y,'.',x, ys, 'k') See also -------- polydeg, polyfit, polyval Reference: --------- Methodes de calcul numerique 2. JP Nougier. Hermes Science Publications, 2001. Section 4.7 pp 116-121 ''' x, y = np.atleast_1d(x, y) x = x.ravel() y = y.ravel() # Particular case: n=0 if n == 0: return y.mean() # p = Coefficients of the orthogonal polynomials p = np.zeros((3, n + 1)) p[1, 1] = x.mean() N = len(x) PL = np.ones((n + 1, N)) PL[1] = x - p[1, 1] for i in range(2, n + 1): p[1, i] = np.dot(x, PL[i - 1] ** 2) / np.sum(PL[i - 1] ** 2) p[2, i] = np.dot(x, PL[i - 2] * PL[i - 1]) / np.sum(PL[i - 2] ** 2) PL[i] = (x - p[1, i]) * PL[i - 1] - p[2, i] * PL[i - 2] p[0, :] = np.dot(PL, y) / np.sum(PL ** 2, axis=1) return p # ys = np.dot(p[0, :], PL) # smoothed y def polyreloc(p, x, y=0.0): """ Relocate polynomial The polynomial `p` is relocated by "moving" it `x` units along the x-axis and `y` units along the y-axis. So the polynomial `r` is relative to the point (x,y) as the polynomial `p` is relative to the point (0,0). Parameters ---------- p : array-like, poly1d vector or matrix of column vectors of polynomial coefficients to relocate. (Polynomial coefficients are in decreasing order.) x : scalar distance to relocate P along x-axis y : scalar distance to relocate P along y-axis (default 0) Returns ------- r : ndarray, poly1d vector/matrix/poly1d of relocated polynomial coefficients. See also -------- polyrescl Example ------- >>> import numpy as np >>> p = np.arange(6); p.shape = (2,-1) >>> np.polyval(p,0) array([3, 4, 5]) >>> np.polyval(p,1) array([3, 5, 7]) >>> r = polyreloc(p,-1) # move to the left along x-axis >>> np.polyval(r,-1) # = polyval(p,0) array([3, 4, 5]) >>> np.polyval(r,0) # = polyval(p,1) array([3, 5, 7]) """ truepoly = isinstance(p, poly1d) r = np.atleast_1d(p).copy() n = r.shape[0] # Relocate polynomial using Horner's algorithm for ii in range(n, 1, -1): for i in range(1, ii): r[i] = r[i] - x * r[i - 1] r[-1] = r[-1] + y if r.ndim > 1 and r.shape[-1] == 1: r.shape = (r.size,) if truepoly: r = poly1d(r) return r def polyrescl(p, x, y=1.0): """ Rescale polynomial. Parameters ---------- p : array-like, poly1d vector or matrix of column vectors of polynomial coefficients to rescale. (Polynomial coefficients are in decreasing order.) x,y : scalars defining the factors to rescale the polynomial `p` in x-direction and y-direction, respectively. Returns ------- r : ndarray, poly1d vector/matrix/poly1d of rescaled polynomial coefficients. See also -------- polyreloc Example ------- >>> import numpy as np >>> p = np.arange(6); p.shape = (2,-1) >>> np.polyval(p,0) array([3, 4, 5]) >>> np.polyval(p,1) array([3, 5, 7]) >>> r = polyrescl(p,2) # scale by 2 along x-axis >>> np.polyval(r,0) # = polyval(p,0) array([ 3., 4., 5.]) >>> np.polyval(r,2) # = polyval(p,1) array([ 3., 5., 7.]) """ truepoly = isinstance(p, poly1d) r = np.atleast_1d(p) n = r.shape[0] xscale = (float(x) ** arange(1 - n, 1)) if r.ndim == 1: q = y * r * xscale else: q = y * r * xscale[:, newaxis] if truepoly: q = poly1d(q) return q def polytrim(p): """ Trim polynomial by stripping off leading zeros. Parameters ---------- p : array-like, poly1d vector or matrix of column vectors of polynomial coefficients in decreasing order. Returns ------- r : ndarray, poly1d vector/matrix/poly1d of trimmed polynomial coefficients. Example ------- >>> p = [0,1,2] >>> polytrim(p) array([1, 2]) >>> p1 = [[0,0],[1,2],[3,4]] >>> polytrim(p1) array([[1, 2], [3, 4]]) """ truepoly = isinstance(p, poly1d) if truepoly: return p else: r = np.atleast_1d(p).copy() # Remove leading zeros is_not_lead_zeros = np.logical_or.accumulate(r != 0, axis=0) if r.ndim == 1: r = r[is_not_lead_zeros] else: is_not_lead_zeros = np.any(is_not_lead_zeros, axis=1) r = r[is_not_lead_zeros, :] return r def poly2hstr(p, variable='x'): """ Return polynomial as a Horner represented string. Parameters ---------- p : array-like poly1d vector of polynomial coefficients in decreasing order. variable : string display character for variable Returns ------- p_str : string consisting of the polynomial coefficients in the vector P multiplied by powers of the given `variable`. Examples -------- >>> poly2hstr([1, 1, 2], 's' ) '(s + 1)*s + 2' See also -------- poly2str """ var = variable coefs = polytrim(np.atleast_1d(p)) order = len(coefs) - 1 # Order of polynomial. s = '' # Initialize output string. ix = 1 for expon in range(order, -1, -1): coef = coefs[order - expon] # There is no point in adding a zero term (except if it's the only # term, but we'll take care of that later). if coef == 0: ix += 1 else: # Append exponent if necessary. if ix > 1: exponstr = '%.0f' % ix s = '%s**%s' % (s, exponstr) ix = 1 # Is it the first term? isfirst = s == '' # We need the coefficient only if it is different from 1 or -1 or # when it is the constant term. needcoef = ((abs(coef) != 1) | (expon == 0) & isfirst) | 1 - isfirst # We need the variable except in the constant term. needvar = (expon != 0) # Add sign, but we don't need a leading plus-sign. if isfirst: if coef < 0: s = '-' # % Unary minus. else: if coef < 0: s = '%s - ' % s # % Binary minus (subtraction). else: s = '%s + ' % s # % Binary plus (addition). # Append the coefficient if it is different from one or when it is # the constant term. if needcoef: coefstr = '%.20g' % abs(coef) s = '%s%s' % (s, coefstr) # Append variable if necessary. if needvar: # Append a multiplication sign if necessary. if needcoef: if 1 - isfirst: s = '(%s)' % s s = '%s*' % s s = '%s%s' % (s, var) # Now treat the special case where the polynomial is zero. if s == '': s = '0' return s def poly2str(p, variable='x'): """ Return polynomial as a string. Parameters ---------- p : array-like poly1d vector of polynomial coefficients in decreasing order. variable : string display character for variable Returns ------- p_str : string consisting of the polynomial coefficients in the vector P multiplied by powers of the given `variable`. See also -------- poly2hstr Examples -------- >>> poly2str([1, 1, 2], 's' ) 's**2 + s + 2' """ thestr = "0" var = variable # Remove leading zeros coeffs = polytrim(np.atleast_1d(p)) N = len(coeffs) - 1 for k in range(N+1): coefstr = '%.4g' % abs(coeffs[k]) if coefstr[-4:] == '0000': coefstr = coefstr[:-5] power = (N - k) if power == 0: if coefstr != '0': newstr = '%s' % (coefstr,) else: newstr = '0' if k == 0 else '' elif power == 1: if coefstr == '0': newstr = '' elif coefstr in ['b', '1']: newstr = var else: newstr = '%s*%s' % (coefstr, var) else: if coefstr == '0': newstr = '' elif coefstr in ['b', '1']: newstr = '%s**%d' % (var, power,) else: newstr = '%s*%s**%d' % (coefstr, var, power) if k > 0: if newstr != '': if coeffs[k] < 0: thestr = "%s - %s" % (thestr, newstr) else: thestr = "%s + %s" % (thestr, newstr) elif (k == 0) and (newstr != '') and (coeffs[k] < 0): thestr = "-%s" % (newstr,) else: thestr = newstr return thestr def polyshift(py, a=-1, b=1): """ Polynomial coefficient shift Polyshift shift the polynomial coefficients by a variable shift: Y = 2*(X-.5*(b+a))/(b-a) i.e., the interval -1 <= Y <= 1 is mapped to the interval a <= X <= b Parameters ---------- py : array-like polynomial coefficients for the variable y. a,b : scalars lower and upper limit. Returns ------- px : ndarray polynomial coefficients for the variable x. See also -------- polyishift Example ------- >>> py = [1, 0] >>> px = polyshift(py,0,5) >>> polyval(px,[0, 2.5, 5]) #% This is the same as the line below array([-1., 0., 1.]) >>> polyval(py,[-1, 0, 1 ]) array([-1, 0, 1]) """ if (a == -1) & (b == 1): return py L = b - a return polyishift(py, -(2. + b + a) / L, (2. - b - a) / L) def polyishift(px, a=-1, b=1): """ Inverse polynomial coefficient shift Polyishift does the inverse of Polyshift, shift the polynomial coefficients by a variable shift: Y = 2*(X-.5*(b+a)/(b-a) i.e., the interval a <= X <= b is mapped to the interval -1 <= Y <= 1 Parameters ---------- px : array-like polynomial coefficients for the variable x. a,b : scalars lower and upper limit. Returns ------- py : ndarray polynomial coefficients for the variable y. See also -------- polyishift Example ------- >>> px = [1, 0] >>> py = polyishift(px,0,5); >>> polyval(px,[0, 2.5, 5]) #% This is the same as the line below array([ 0. , 2.5, 5. ]) >>> polyval(py,[-1, 0, 1]) array([ 0. , 2.5, 5. ]) """ if (a == -1) & (b == 1): return px L = b - a xscale = 2. / L xloc = -float(a + b) / L return polyreloc(polyrescl(px, xscale), xloc) def map_from_interval(x, a, b): """F(x), where F: [a,b] -> [-1,1].""" return (x - (b + a) / 2.0) * (2.0 / (b - a)) def map_to_interval(x, a, b): """F(x), where F: [-1,1] -> [a,b].""" return (x * (b - a) + (b + a)) / 2.0 def poly2cheb(p, a=-1, b=1): """ Convert polynomial coefficients into Chebyshev coefficients Parameters ---------- p : array-like polynomial coefficients a,b : real scalars lower and upper limits (Default -1,1) Returns ------- ck : ndarray Chebychef coefficients POLY2CHEB do the inverse of CHEB2POLY: given a vector of polynomial coefficients AK, returns an equivalent vector of Chebyshev coefficients CK. This is useful for economization of power series. The steps for doing so: 1. Convert polynomial coefficients to Chebychev coefficients, CK. 2. Truncate the CK series to a smaller number of terms, using the coefficient of the first neglected Chebychev polynomial as an error estimate. 3 Convert back to a polynomial by CHEB2POLY See also -------- cheb2poly chebval chebfit Examples -------- >>> import numpy as np >>> p = np.arange(5) >>> ck = poly2cheb(p) >>> cheb2poly(ck) array([ 1., 2., 3., 4.]) Reference --------- William H. Press, Saul Teukolsky, William T. Wetterling and Brian P. Flannery (1997) "Numerical recipes in Fortran 77", Vol. 1, pp 184-194 """ f = poly1d(p) n = len(f.coeffs) return chebfit(f, n, a, b) def cheb2poly(ck, a=-1, b=1): """ Converts Chebyshev coefficients to polynomial coefficients Parameters ---------- ck : array-like Chebychef coefficients a,b : real, scalars lower and upper limits (Default -1,1) Returns ------- p : ndarray polynomial coefficients It is not advised to do this for len(ck)>10 due to numerical cancellations. See also -------- chebval chebfit Examples -------- >>> import numpy as np >>> p = np.arange(5) >>> ck = poly2cheb(p) >>> cheb2poly(ck) array([ 1., 2., 3., 4.]) References ---------- http://en.wikipedia.org/wiki/Chebyshev_polynomials http://en.wikipedia.org/wiki/Chebyshev_form http://en.wikipedia.org/wiki/Clenshaw_algorithm """ n = len(ck) b_Nmi = np.zeros(1) b_Nmip1 = np.zeros(1) y = np.r_[2 / (b - a), -(a + b) / (b - a)] y2 = 2. * y # Clenshaw recurence for ix in range(n - 1): tmp = b_Nmi b_Nmi = polymul(y2, b_Nmi) # polynomial multiplication nb = len(b_Nmip1) b_Nmip1[-1] = b_Nmip1[-1] - ck[ix] b_Nmi[-nb::] = b_Nmi[-nb::] - b_Nmip1 b_Nmip1 = tmp p = polymul(y, b_Nmi) # polynomial multiplication nb = len(b_Nmip1) b_Nmip1[-1] = b_Nmip1[-1] - ck[n - 1] p[-nb::] = p[-nb::] - b_Nmip1 return polytrim(p) def chebextr(n): """ Return roots of derivative of Chebychev polynomial of the first kind. All local extreme values of the polynomial are either -1 or 1. So, CHEBPOLY( N, CHEBEXTR(N) ) ) return the same as (-1).^(N:-1:0) except for the numerical noise in the former. Because the extreme values of Chebychev polynomials of the first kind are either -1 or 1, their roots are often used as starting values for the nodes in minimax approximations. Parameters ---------- n : scalar, integer degree of Chebychev polynomial. Examples -------- >>> x = chebextr(4) >>> chebpoly(4,x) array([ 1., -1., 1., -1., 1.]) Reference --------- http://en.wikipedia.org/wiki/Chebyshev_nodes http://en.wikipedia.org/wiki/Chebyshev_polynomials """ return - np.cos((pi * arange(n + 1)) / n) def chebroot(n, kind=1): """ Return roots of Chebychev polynomial of the first or second kind. The roots of the Chebychev polynomial of the first kind form a particularly good set of nodes for polynomial interpolation because the resulting interpolation polynomial minimizes the problem of Runge's phenomenon. Parameters ---------- n : scalar, integer degree of Chebychev polynomial. kind: 1 or 2, optional kind of Chebychev polynomial (default 1) Examples -------- >>> import numpy as np >>> x = chebroot(3) >>> np.abs(chebpoly(3,x))<1e-15 array([ True, True, True], dtype=bool) >>> chebpoly(3) array([ 4., 0., -3., 0.]) >>> x2 = chebroot(4,kind=2) >>> np.abs(chebpoly(4,x2,kind=2))<1e-15 array([ True, True, True, True], dtype=bool) >>> chebpoly(4,kind=2) array([ 16., 0., -12., 0., 1.]) Reference --------- http://en.wikipedia.org/wiki/Chebyshev_nodes http://en.wikipedia.org/wiki/Chebyshev_polynomials """ if kind not in (1, 2): raise ValueError('kind must be 1 or 2') return - np.cos(pi * (arange(n) + 0.5 * kind) / (n + kind - 1)) def chebpoly(n, x=None, kind=1): """ Return Chebyshev polynomial of the first or second kind. These polynomials are orthogonal on the interval [-1,1], with respect to the weight function w(x) = (1-x**2)**(-1/2+kind-1). chebpoly(n) returns coefficients of the Chebychev polynomial of degree N. chebpoly(n,x) returns the Chebychev polynomial of degree N evaluated at X. Parameters ---------- n : integer, scalar degree of Chebychev polynomial. x : array-like, optional evaluation points kind: 1 or 2, optional kind of Chebychev polynomial (default 1) Returns ------- p : ndarray polynomial coefficients if x is None. Chebyshev polynomial evaluated at x otherwise Examples -------- >>> import numpy as np >>> x = chebroot(3) >>> np.abs(chebpoly(3,x))<1e-15 array([ True, True, True], dtype=bool) >>> chebpoly(3) array([ 4., 0., -3., 0.]) >>> x2 = chebroot(4,kind=2) >>> np.abs(chebpoly(4,x2,kind=2))<1e-15 array([ True, True, True, True], dtype=bool) >>> chebpoly(4,kind=2) array([ 16., 0., -12., 0., 1.]) Reference --------- http://en.wikipedia.org/wiki/Chebyshev_polynomials """ if x is None: # Calculate coefficients. if n == 0: p = np.ones(1) else: p = np.round(pow(2, n - 2 + kind) * poly(chebroot(n, kind=kind))) p[1::2] = 0 return p else: # Evaluate polynomial in chebychev form ck = np.zeros(n + 1) ck[0] = 1. return _chebval(np.atleast_1d(x), ck, kind=kind) def chebfit(fun, n=10, a=-1, b=1, trace=False): """ Computes the Chebyshevs coefficients so that f(x) can be approximated by: n-1 f(x) = sum ck*Tk(x) k=0 where Tk is the k'th Chebyshev polynomial of the first kind. Parameters ---------- fun : callable function to approximate n : integer, scalar, optional number of base points (abscissas). Default n=10 (maximum 50) a,b : real, scalars, optional integration limits Returns ------- ck : ndarray polynomial coefficients in Chebychev form. Examples -------- Fit exp(x) >>> import matplotlib.pyplot as plt >>> a = 0; b = 2 >>> ck = chebfit(np.exp,7,a,b); >>> x = np.linspace(0,4); >>> x1 = chebroot(9)*(b-a)/2+(b+a)/2 >>> ck1 = chebfit(np.exp(x1)) h=plt.plot(x, np.exp(x), 'r', x, chebval(x,ck,a,b), 'g.') h = plt.plot(x,np.exp(x), 'r', x, chebval(x,ck1,a,b),'g.') plt.close() See also -------- chebval Reference --------- http://en.wikipedia.org/wiki/Chebyshev_nodes http://mathworld.wolfram.com/ChebyshevApproximationFormula.html W. Fraser (1965) "A Survey of Methods of Computing Minimax and Near-Minimax Polynomial Approximations for Functions of a Single Independent Variable" Journal of the ACM (JACM), Vol. 12 , Issue 3, pp 295 - 314 """ if (n > 50): warnings.warn('CHEBFIT should only be used for n<50') if hasattr(fun, '__call__'): x = map_to_interval(chebroot(n), a, b) f = fun(x) if trace: plt.plot(x, f, '+') else: f = fun n = len(f) # N-1 # c[k] = (2/N) sum w[n] f[n]*cos(pi*k*(2n+1)/(2N)), 0 <= k < N. # n=0 # # w[0] = 0.5, w[n]=1 for n>0 ck = dct(f[::-1]) / n ck[0] = ck[0] / 2. return ck[::-1] def chebfit_dct(f, n=(10, ), domain=None): """ Fit Chebyshev series to N-dimensional function so that f(x1, x2,..., xn) can be approximated by: .. math:: f(x_1, x_2,...,x_n) = \\sum_{i,j,...k} c_i T_i(x_1)*...*c_k T_k(x_n) , where Tk is the k'th Chebyshev polynomial of the first kind. Parameters ---------- f : callable function to approximate n : list of integers, optional number of base points (abscissas) used for each dimension. Default n=10 (maximum 50) domain : vector [a1,b1,a2,b2 ,..., an, bn], optional defining the rectangle [a1, b1] x [a2, b2] x ...x [an, bn]. (default domain = (-1,1) * len(n)) Returns ------- ck : ndarray polynomial coefficients in Chebychev form. Examples -------- Fit exponential function >>> import matplotlib.pyplot as plt >>> domain = (0, 2) >>> ck = chebfit_dct(np.exp, 7, domain) >>> np.allclose(ck, [3.44152387e+00, 3.07252345e+00, 7.38000848e-01, ... 1.20520053e-01, 1.48805268e-02, 1.47579673e-03, ... 1.21719524e-04]) True >>> x1 = map_to_interval(chebroot(9), *domain) >>> ck1 = chebfit(np.exp(x1)) >>> np.allclose(ck1, [5.40019009e-07, 8.69418381e-06, 1.22261037e-04, ... 1.47582673e-03, 1.48805283e-02, 1.20520053e-01, ... 7.38000848e-01, 3.07252345e+00, 3.44152387e+00]) True x = np.linspace(0,4) h = plt.plot(x, np.exp(x), 'r', x, chebvalnd(ck, x,ck,a,b), 'g.') h = plt.plot(x, np.exp(x), 'r', x, chebvalnd(ck1, x,ck1,a,b),'b.') plt.close() See also -------- chebval, chebvalnd Reference --------- http://en.wikipedia.org/wiki/Chebyshev_nodes http://mathworld.wolfram.com/ChebyshevApproximationFormula.html W. Fraser (1965) "A Survey of Methods of Computing Minimax and Near-Minimax Polynomial Approximations for Functions of a Single Independent Variable" Journal of the ACM (JACM), Vol. 12 , Issue 3, pp 295 - 314 """ n = np.atleast_1d(n) if np.any(n > 50): warnings.warn('CHEBFIT should only be used for n<50') if hasattr(f, '__call__'): if domain is None: domain = (-1, 1) * len(n) domain = np.atleast_2d(domain).reshape((-1, 2)) xi = [map_to_interval(chebroot(ni), d[0], d[1]) for ni, d in zip(n, domain)] Xi = np.meshgrid(*xi) ck = f(*Xi) else: ck = f n = ck.shape ndim = len(n) for i in range(ndim): ck = dct(ck[..., ::-1]) ck[..., 0] = ck[..., 0] / 2. if i < ndim-1: ck = np.rollaxis(ck, axis=-1) return ck / np.product(n) def idct(x, n=None): """ Inverse Discrete Cosine Transform N-1 x[k] = 1/N sum w[n]*y[n]*cos(pi*k*(2n+1)/(2*N)), 0 <= k < N. n=0 w(0) = 1/2 w(n) = 1 for n>0 Examples -------- >>> import numpy as np >>> x = np.arange(5)*1.0 >>> np.abs(x-idct(dct(x)))<1e-14 array([ True, True, True, True, True], dtype=bool) >>> np.abs(x-dct(idct(x)))<1e-14 array([ True, True, True, True, True], dtype=bool) Reference --------- http://en.wikipedia.org/wiki/Discrete_cosine_transform http://users.ece.utexas.edu/~bevans/courses/ee381k/lectures/ """ return _idct(x, n=n, norm=None)*0.5/len(x) def _chebval(x, ck, kind=1): """ Evaluate polynomial in Chebyshev form. A polynomial of degree N in Chebyshev form is a polynomial p(x): N p(x) = sum ck*Tk(x) k=0 or N p(x) = sum ck*Uk(x) k=0 where Tk and Uk are the k'th Chebyshev polynomial of the first and second kind, respectively. References ---------- http://en.wikipedia.org/wiki/Clenshaw_algorithm http://mathworld.wolfram.com/ClenshawRecurrenceFormula.html """ n = len(ck) b_Nmi = np.zeros(x.shape) # b_(N-i) b_Nmip1 = b_Nmi.copy() # b_(N-i+1) x2 = 2 * x # Clenshaw reccurence for ix in range(n - 1): tmp = b_Nmi b_Nmi = x2 * b_Nmi - b_Nmip1 + ck[ix] b_Nmip1 = tmp return kind * x * b_Nmi - b_Nmip1 + ck[n - 1] def chebval(x, ck, a=-1, b=1, kind=1, fill=None): """ Evaluate polynomial in Chebyshev form at X A polynomial of degree N in Chebyshev form is a polynomial p(x) of the form N p(x) = sum ck*Tk(x) k=0 where Tk is the k'th Chebyshev polynomial of the first or second kind. Paramaters ---------- x : array-like points to evaluate ck : array-like polynomial coefficients in Chebyshev form ordered from highest degree to zero a,b : real, scalars, optional limits for polynomial (Default -1,1) kind: 1 or 2, optional kind of Chebychev polynomial (default 1) fill : scalar, optional If provided, define value to return for `x < a` or `b < x`. Examples -------- Plot Chebychev polynomial of the first kind and order 4: >>> import matplotlib.pyplot as plt >>> x = np.linspace(-1,1) >>> ck = np.zeros(5); ck[-1]=1 >>> y = chebval(x,ck) h = plt.plot(x, y, x, chebpoly(4,x),'.') plt.close() Fit exponential function: >>> import matplotlib.pyplot as plt >>> ck = chebfit(np.exp,7,0,2) >>> x = np.linspace(0,4); >>> y2 = chebval(x,ck,0,2) h=plt.plot(x, y2, 'g', x, np.exp(x)) plt.close() See also -------- chebfit References ---------- http://en.wikipedia.org/wiki/Clenshaw_algorithm http://mathworld.wolfram.com/ClenshawRecurrenceFormula.html """ y = map_from_interval(np.atleast_1d(x), a, b) if fill is None: f = _chebval(y, ck, kind=kind) else: cond = (abs(y) <= 1) f = np.where(cond, 0, fill) if np.any(cond): yk = np.extract(cond, y) f[cond] = _chebval(yk, ck, kind=kind) return f def chebder(ck, a=-1, b=1): """ Differentiate Chebyshev polynomial Parameters ---------- ck : array-like polynomial coefficients in Chebyshev form of function to differentiate a,b : real, scalars limits for polynomial(Default -1,1) Return ------ cder : ndarray polynomial coefficients in Chebyshev form of the derivative Examples -------- Fit exponential function: >>> import matplotlib.pyplot as plt >>> ck = chebfit(np.exp,7,0,2) >>> x = np.linspace(0,4) >>> ck2 = chebder(ck,0,2) >>> y = chebval(x,ck2,0,2) h = plt.plot(x, y, 'g', x, np.exp(x), 'r') plt.close() See also -------- chebint chebfit Reference --------- http://en.wikipedia.org/wiki/Chebyshev_polynomials W. Fraser (1965) "A Survey of Methods of Computing Minimax and Near-Minimax Polynomial Approximations for Functions of a Single Independent Variable" Journal of the ACM (JACM), Vol. 12 , Issue 3, pp 295 - 314 """ n = len(ck) - 1 cder = np.zeros(n, dtype=np.asarray(ck).dtype) cder[0] = 2 * n * ck[0] cder[1] = 2 * (n - 1) * ck[1] for j in range(2, n): cder[j] = cder[j - 2] + 2 * (n - j) * ck[j] return cder * 2. / (b - a) # Normalize to the interval b-a. def chebint(ck, a=-1, b=1): """ Integrate Chebyshev polynomial Parameters ---------- ck : array-like polynomial coefficients in Chebyshev form of function to integrate. a,b : real, scalars limits for polynomial(Default -1,1) Return ------ cint : ndarray polynomial coefficients in Chebyshev form of the integrated function Examples -------- Fit exponential function: >>> import matplotlib.pyplot as plt >>> ck = chebfit(np.exp,7,0,2) >>> x = np.linspace(0,4) >>> ck2 = chebint(ck,0,2); >>> y =chebval(x,ck2,0,2) h=plt.plot(x,y,'g',x,np.exp(x),'r.') plt.close() See also -------- chebder chebfit Reference --------- http://en.wikipedia.org/wiki/Chebyshev_polynomials W. Fraser (1965) "A Survey of Methods of Computing Minimax and Near-Minimax Polynomial Approximations for Functions of a Single Independent Variable" Journal of the ACM (JACM), Vol. 12 , Issue 3, pp 295 - 314 """ # int T0(x) = T1(x)+1 # int T1(x) = 0.5*(T2(x)/2-T0/2) # int Tn(x) dx = 0.5*{Tn+1(x)/(n+1) - Tn-1(x)/(n-1)} # N # p(x) = sum cn*Tn(x) # n=0 # int p(x) dx = sum cn * int(Tn(x)dx) = # 0.5*sum cn *{Tn+1(x)/(n+1) - Tn-1(x)/(n-1)} = 0.5 sum (cn-1-cn+1)*Tn/n n>0 n = len(ck) cint = np.zeros(n) con = 0.25 * (b - a) dif1 = np.diff(ck[-1::-2]) ix1 = np.r_[1:n - 1:2] cint[ix1] = -(con * dif1) / ix1 if n > 3: dif2 = np.diff(ck[-2::-2]) ix2 = np.r_[2:n - 1:2] cint[ix2] = -(con * dif2) / ix2 cint = cint[::-1] # cint(n) is a special case cint[-1] = (con * ck[n - 2]) / (n - 1) # Set integration constant cint[0] = 2 * np.sum((-1) ** np.r_[0:n - 1] * cint[-2::-1]) return cint class Cheb1d(object): coeffs = None order = None a = None b = None kind = None def __init__(self, ck, a=-1, b=1, kind=1): if isinstance(ck, Cheb1d): for key in ck.__dict__.keys(): self.__dict__[key] = ck.__dict__[key] return cki = trim_zeros(np.atleast_1d(ck), 'b') if len(cki.shape) > 1: raise ValueError("Polynomial must be 1d only.") self.__dict__['coeffs'] = cki self.__dict__['order'] = len(cki) - 1 self.__dict__['a'] = a self.__dict__['b'] = b self.__dict__['kind'] = kind def __call__(self, x): return chebval(x, self.coeffs, self.a, self.b, self.kind) def __array__(self, t=None): if t: return np.asarray(self.coeffs, t) else: return np.asarray(self.coeffs) def __repr__(self): vals = repr(self.coeffs) vals = vals[6:-1] return "Cheb1d(%s)" % vals def __len__(self): return self.order def __str__(self): pass def __neg__(self): new = Cheb1d(self) new.coeffs = -self.coeffs return new def __pos__(self): return self def __add__(self, other): other = Cheb1d(other) new = Cheb1d(self) new.coeffs = polyadd(self.coeffs, other.coeffs) return new def __radd__(self, other): return self.__add__(other) def __sub__(self, other): other = Cheb1d(other) new = Cheb1d(self) new.coeffs = polysub(self.coeffs, other.coeffs) return new def __rsub__(self, other): other = Cheb1d(other) new = Cheb1d(self) new.coeffs = polysub(other.coeffs, new.coeffs) return new def __eq__(self, other): other = Cheb1d(other) return (np.all(self.coeffs == other.coeffs) and (self.a == other.a) and (self.b == other.b) and (self.kind == other.kind)) def __ne__(self, other): return np.any(self.coeffs != other.coeffs) or (self.a != other.a) or ( self.b != other.b) or (self.kind != other.kind) def __setattr__(self, key, val): raise ValueError("Attributes cannot be changed this way.") def __getattr__(self, key): if key in ['c', 'coef', 'coefficients']: return self.coeffs elif key in ['o']: return self.order elif key in ['a']: return self.a elif key in ['b']: return self.b elif key in ['k']: return self.kind else: try: return self.__dict__[key] except KeyError: raise AttributeError( "'%s' has no attribute '%s'" % (self.__class__, key)) def __getitem__(self, val): if val > self.order: return 0 if val < 0: return 0 return self.coeffs[val] def __setitem__(self, key, val): # ind = self.order - key if key < 0: raise ValueError("Does not support negative powers.") if key > self.order: zr = np.zeros(key - self.order, self.coeffs.dtype) self.__dict__['coeffs'] = np.concatenate((self.coeffs, zr)) self.__dict__['order'] = key self.__dict__['coeffs'][key] = val return def __iter__(self): return iter(self.coeffs) def integ(self, m=1): """ Return an antiderivative (indefinite integral) of this polynomial. Refer to `chebint` for full documentation. See Also -------- chebint : equivalent function """ integ = Cheb1d(self) integ.coeffs = chebint(self.coeffs, self.a, self.b) return integ def deriv(self, m=1): """ Return a derivative of this polynomial. Refer to `chebder` for full documentation. See Also -------- chebder : equivalent function """ der = Cheb1d(self) der.coeffs = chebder(self.coeffs, self.a, self.b) return der def padefit(c, m=None): """ Rational polynomial fitting from polynomial coefficients Parameters ---------- c : array-like coefficients of power series expansion from highest degree to zero. m : scalar integer order of denominator polynomial. (Default floor((len(c)-1)/2)) Returns ------- num, den : poly1d numerator and denominator polynomials for the pade approximation If the function is well approximated by M+N+1 f(x) = sum c(2*n+2-k)*x^k k=0 then the pade approximation is given by M sum c1(n-k+1)*x^k k=0 f(x) = ------------------------ N sum c2(n-k+1)*x^k k=0 Note: c must be ordered for direct use with polyval Example ------- Pade approximation to exp(x) >>> import scipy.special as sp >>> import matplotlib.pyplot as plt >>> c = poly1d(1./sp.gamma(np.r_[6+1:0:-1])) >>> [p, q] = padefit(c) >>> p; q poly1d([ 0.00277778, 0.03333333, 0.2 , 0.66666667, 1. ]) poly1d([ 0.03333333, -0.33333333, 1. ]) x = np.linspace(0,4) h = plt.plot(x,c(x),x,p(x)/q(x),'g-', x,np.exp(x),'r.') plt.close() See also -------- scipy.misc.pade """ if not m: m = int(np.floor((len(c) - 1) * 0.5)) c = np.asarray(c) return pade(c[::-1], m) def test_pade(): cof = np.array(([1.0, 1.0, 1.0 / 2, 1. / 6, 1. / 24])) p, q = pade(cof, 2) t = arange(0, 2, 0.1) assert(np.all(abs(p(t) / q(t) - np.exp(t)) < 0.3)) def padefitlsq(fun, m, k, a=-1, b=1, trace=False, x=None, end_points=True): """ Rational polynomial fitting. A minimax solution by least squares. Parameters ---------- fun : callable or or a two column matrix f=[x,f(x)] where length(x)>(m+k+1)*8. m, k : integer number of coefficients of the numerator and denominater, respectively. a, b : real scalars evaluation limits, (default a=-1,b=1) Returns ------- num, den : poly1d numerator and denominator polynomials for the pade approximation dev : ndarray maximum absolute deviation of the approximation The pade approximation is given by m sum c1[m-i]*x**i i=0 f(x) = ------------------------ k sum c2[k-i]*x**i i=0 If F is a two column matrix, [x f(x)], a good choice for x is: x = cos(pi/(N-1)*(N-1:-1:0))*(b-a)/2+ (a+b)/2, where N = (m+k+1)*8; Note: c1 and c2 are ordered for direct use with polyval Example ------- Pade approximation to exp(x) between 0 and 2 >>> import matplotlib.pyplot as plt >>> [c1, c2] = padefitlsq(np.exp,3,3,0,2) >>> c1; c2 poly1d([ 0.01443847, 0.128842 , 0.55284547, 0.99999962]) poly1d([-0.0049658 , 0.07610473, -0.44716929, 1. ]) x = np.linspace(0,4) h = plt.plot(x, polyval(c1,x)/polyval(c2,x),'g') h = plt.plot(x, np.exp(x), 'r') See also -------- padefit Reference --------- William H. Press, Saul Teukolsky, William T. Wetterling and Brian P. Flannery (1997) "Numerical recipes in Fortran 77", Vol. 1, pp 197-20 """ NFAC = 8 BIG = 1e30 MAXIT = 5 smallest_devmax = BIG ncof = m + k + 1 # Number of points where function is evaluated, i.e. fineness of mesh npt = NFAC * ncof if x is None: if end_points: # Use the location of the local extreme values of # the Chebychev polynomial of the first kind of degree NPT-1. x = map_to_interval(chebextr(npt - 1), a, b) else: # Use the roots of the Chebychev polynomial of the first kind of # degree NPT. Note this is useful if there are singularities close # to the endpoints. x = map_to_interval(chebroot(npt, kind=1), a, b) if hasattr(fun, '__call__'): fs = fun(x) else: fs = fun n = len(fs) if n < npt: warnings.warn('Check the result! Number of function values ' + 'should be at least: %d' % npt) if trace: plt.plot(x, fs, '+') wt = np.ones((npt)) ee = np.ones((npt)) mad = 0 u = np.zeros((npt, ncof)) for ix in range(MAXIT): # Set up design matrix for least squares fit. pow1 = wt bb = pow1 * (fs + abs(mad) * np.sign(ee)) for jx in range(m + 1): u[:, jx] = pow1 pow1 = pow1 * x pow1 = -bb for jx in range(m + 1, ncof): pow1 = pow1 * x u[:, jx] = pow1 [u1, w, v] = np.linalg.svd(u, full_matrices=False) cof = np.where(w == 0, 0.0, np.dot(bb, u1) / w) cof = np.dot(cof, v) # Tabulate the deviations and revise the weights ee = polyval(cof[m::-1], x) / \ polyval(cof[ncof:m:-1].tolist() + [1, ], x) - fs wt = np.abs(ee) devmax = max(wt) mad = wt.mean() # mean absolute deviation # Save only the best coefficients found if (devmax <= smallest_devmax): smallest_devmax = devmax c1 = cof[m::-1] c2 = cof[ncof:m:-1].tolist() + [1, ] if trace: print('Iteration=%d, max error=%g' % (ix, devmax)) plt.plot(x, fs, x, ee + fs) return poly1d(c1), poly1d(c2) def main(): exp = np.exp [c1, c2] = padefitlsq(exp, 3, 3, 0, 2) x = np.linspace(0, 4) plt.plot(x, polyval(c1, x) / polyval(c2, x), 'g') plt.plot(x, exp(x), 'r') import scipy.special as sp p = [[1, 1, 1], [2, 2, 2]] pi = polyint(p, 1) _pr = polyreloc(p, 2) _pd = polyder(p) _st = poly2str(p) # polynomial coeff exponential function: c = poly1d(1. / sp.gamma(np.r_[6 + 1:0:-1])) [p, q] = padefit(c) x = np.linspace(0, 4) plt.plot(x, c(x), x, p(x) / q(x), 'g-', x, exp(x), 'r.') plt.close() x = arange(4) dx = dct(x) _idx = idct(dx) a = 0 b = 2 ck = chebfit(exp, 6, a, b) _t = chebval(0, ck, a, b) x = np.linspace(0, 2, 6) plt.plot(x, exp(x), 'r', x, chebval(x, ck, a, b), 'g.') # x1 = chebroot(9).'*(b-a)/2+(b+a)/2 ; # ck1 =chebfit([x1 exp(x1)],9,a,b); # plot(x,exp(x),'r'), hold on # plot(x,chebval(x,ck1,a,b),'g'), hold off _t = poly2hstr([1, 1, 2]) py = [1, 0] px = polyshift(py, 0, 5) _t1 = polyval(px, [0, 2.5, 5]) # % This is the same as the line below _t2 = polyval(py, [-1, 0, 1]) px = [1, 0] py = polyishift(px, 0, 5) t1 = polyval(px, [0, 2.5, 5]) # % This is the same as the line below t2 = polyval(py, [-1, 0, 1]) print(t1, t2) def test_polydeg(): x = np.linspace(0, 10, 300) y = np.sin(x ** 3 / 100) ** 2 + 0.05 * np.random.randn(x.size) n = polydeg(x, y) # n = 2 p = orthofit(x, y, n) xi = np.linspace(x.min(), x.max()) ys0 = orthoval(p, x) ys = orthoval(p, xi) ys2 = orthoval(p, xi) plt.plot(x, y, '.', x, ys0, 'k', xi, ys, 'r', xi, ys2, 'r.') p0 = ortho2poly(p) p1 = polyfit(x, ys0, n) plt.plot(xi, polyval(p0, xi), 'g-.', xi, polyval(p1, xi), 'go') plt.show('hold') def test_docstrings(): import doctest print('Testing docstrings in %s' % __file__) options = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS doctest.testmod(optionflags=options, verbose=False) def chebvandernd(deg, *xi): """Pseudo-Vandermonde matrix of given degrees. Returns the pseudo-Vandermonde matrix of degrees `deg` and sample points `(x1, x2, ..., xn)`. If `l, m, n` are the given degrees in `x1, x2, x3`, then The pseudo-Vandermonde matrix is defined by .. math:: V[..., (m+1)(n+1)i + (n+1)j + k] = T_i(x1)*T_j(x2)*T_k(x3), where `0 <= i <= l`, `0 <= j <= m`, and `0 <= k <= n`. The leading indices of `V` index the points `(x, y, z)` and the last index encodes the degrees of the Chebyshev polynomials. If ``V = chebvandernd([xdeg, ydeg, zdeg], x, y, z)``, then the columns of `V` correspond to the elements of a 3-D coefficient array `c` of shape (xdeg + 1, ydeg + 1, zdeg + 1) in the order .. math:: c_{000}, c_{001}, c_{002},... , c_{010}, c_{011}, c_{012},... and ``np.dot(V, c.flat)`` and ``chebvalnd(c, x, y, z)`` will be the same up to roundoff. This equivalence is useful both for least squares fitting and for the evaluation of a large number of N-D Chebyshev series of the same degrees and sample points. Parameters ---------- deg : list of ints List of maximum degrees of the form [x1_deg, x2_deg, ...,xn_deg]. x1, x2, ..., xn : array_like Arrays of point coordinates, all of the same shape. The dtypes will be converted to either float64 or complex128 depending on whether any of the elements are complex. Scalars are converted to 1-D arrays. Returns ------- vander : ndarray The shape of the returned matrix is ``x1.shape + (order,)``, where :math:`order = (deg[0]+1)*(deg([1]+1)*...*(deg[n-1]+1)`. The dtype will be the same as the converted `x1`, `x2`, ... `xn`. See Also -------- chebvander, chebvalnd, chebfitnd """ ideg = [int(d) for d in deg] is_valid = np.array([di == d and di >= 0 for di, d in zip(ideg, deg)]) if np.any(is_valid != 1): raise ValueError("degrees must be non-negative integers") ndim = len(xi) if len(ideg) != ndim: msg = 'length of deg must be the same as number of dimensions' raise ValueError(msg) xi = np.array(xi, copy=0) + 0.0 chebvander = np.polynomial.chebyshev.chebvander shape0 = xi[0].shape s0 = (1,) * ndim vxi = [chebvander(x, d).reshape(shape0 + s0[:i] + (-1,) + s0[i + 1::]) for i, (d, x) in enumerate(zip(ideg, xi))] v = reduce(np.multiply, vxi) return v.reshape(v.shape[:-ndim] + (-1,)) def chebfitnd(xi, f, deg, rcond=None, full=False, w=None): """ Least squares fit of Chebyshev series to N-dimensional data. Return the coefficients of a Chebyshev series of degree `deg` that is the least squares fit to the data values `f` given at points `x1`, `x2`,..., `xn` The fitted polynomial(s) are in the form .. math:: p(x,y) = c_00 + c_11 * T_1(x)*T_1(y) + ..c_ij * T_i(x)*T_j(y). + c_nm * T_n(x)*T_m(y), where `n`, `m` is `deg`. Parameters ---------- xi: tuple x1-, x2-,....xn-coordinates of the sample points. f : array_like function values at the sample points ``(x1[i], x2[i], ..., xn[i])``. deg : list Degrees of the fitting series in the x1, x2, ..., xn directions, respectively. rcond : float, optional Relative condition number of the fit. Singular values smaller than this relative to the largest singular value will be ignored. The default value is size(x1)*eps, where eps is the relative precision of the float type, about 2e-16 in most cases. full : bool, optional Switch determining nature of return value. When it is False (the default) just the coefficients are returned, when True diagnostic information from the singular value decomposition is also returned. w : array_like, optional Weights. If not None, the contribution of each point ``(x1[i], x2[i], ..., xn[i])`` to the fit is weighted by `w[i]`. Ideally the weights are chosen so that the errors of the products ``w[i]*f[i]`` all have the same variance. The default value is None. Returns ------- coef : ndarray, shape (M1, M2,..., Mn) Chebyshev coefficients ordered from low to high. [residuals, rank, singular_values, rcond] : list These values are only returned if `full` = True resid -- sum of squared residuals of the least squares fit rank -- the numerical rank of the scaled Vandermonde matrix sv -- singular values of the scaled Vandermonde matrix rcond -- value of `rcond`. For more details, see `linalg.lstsq`. Warns ----- RankWarning The rank of the coefficient matrix in the least-squares fit is deficient. The warning is only raised if `full` = False. The warnings can be turned off by >>> import warnings >>> warnings.simplefilter('ignore', RankWarning) See Also -------- chebvalnd, chebgridnd Notes ----- The solution is the coefficients of the Chebyshev series `p` that minimizes the sum of the weighted squared errors .. math:: E = \\sum_j w_j^2 * |y_j - p(x_j)|^2, where :math:`w_j` are the weights. This problem is solved by setting up as the (typically) overdetermined matrix equation .. math:: V(x, y) * c = w * y, where `V` is the weighted pseudo Vandermonde matrix of `x`, `c` are the coefficients to be solved for, `w` are the weights, and `y` are the observed values. This equation is then solved using the singular value decomposition of `V`. If some of the singular values of `V` are so small that they are neglected, then a `RankWarning` will be issued. This means that the coefficient values may be poorly determined. Using a lower order fit will usually get rid of the warning. The `rcond` parameter can also be set to a value smaller than its default, but the resulting fit may be spurious and have large contributions from roundoff error. Fits using Chebyshev series are usually better conditioned than fits using power series, but much can depend on the distribution of the sample points and the smoothness of the data. If the quality of the fit is inadequate splines may be a good alternative. References ---------- .. [1] Wikipedia, "Curve fitting", http://en.wikipedia.org/wiki/Curve_fitting Examples -------- """ # xi = np.array(xi, copy=0) + 0.0 z = np.array(f) degrees = np.asarray(deg, dtype=int) orders = degrees + 1 order = np.product(orders) ndims = np.array([x.ndim for x in xi]) ndim = len(ndims) sizes = np.array([x.size for x in xi]) if np.any(ndims != ndim) or z.ndim != ndim: raise TypeError("expected %dD array for x1, x2,...,xn and f" % ndim) if np.any(sizes == 0): raise TypeError("expected non-empty vector for xi") lhs = chebvandernd(degrees, *xi).reshape((-1, order)) rhs = z.ravel() if w is not None: w = np.asarray(w).ravel() + 0.0 if len(lhs) != len(w): raise TypeError("expected x and w to have same length") lhs = lhs * w rhs = rhs * w if rcond is None: rcond = xi[0].size * np.finfo(x.dtype).eps if issubclass(lhs.dtype.type, np.complexfloating): scl = np.sqrt((np.square(lhs.real) + np.square(lhs.imag)).sum(axis=0)) else: scl = np.sqrt(np.square(lhs).sum(axis=0)) scl[scl == 0] = 1 # Solve the least squares problem. c, resids, rank, s = np.linalg.lstsq(lhs/scl, rhs, rcond) c = (c/scl).reshape(orders) if rank != order and not full: msg = "The fit may be poorly conditioned" warnings.warn(msg, pu.RankWarning) if full: return c, [resids, rank, s, rcond] else: return c def chebvalnd(c, *xi): """ Evaluate a N-D Chebyshev series at points (x1, x2, ..., xn). This function returns the values: .. math:: p(x1,x2,...,xn) = \\sum_{i,j,...,k} c_{i,j,...,k} * T_i(x1) * T_j(x2)*...* T_k(xn) The parameters `x1`, `x2`, ...., `xn` are converted to arrays only if they are tuples or a lists, otherwise they are treated as a scalars and they must have the same shape after conversion. In either case, either `x1`, `x2`, ..., `xn` or their elements must support multiplication and addition both with themselves and with the elements of `c`. If `c` has fewer than N dimensions, ones are implicitly appended to its shape to make it N-D. The shape of the result will be c.shape[3:] + x1.shape. Parameters ---------- c : array_like Array of coefficients ordered so that the coefficient of the term of multi-degree i,j,...,k is contained in ``c[i,j,...,k]``. If `c` has dimension greater than N the remaining indices enumerate multiple sets of coefficients. x1, x2,..., xn : array_like, compatible object The N dimensional series is evaluated at the points `(x1, x2,...,xn)`, where `x1`, `x2`,..., `xn` must have the same shape. If any of `x1`, `x2`, ..., `xn` is a list or tuple, it is first converted to an ndarray, otherwise it is left unchanged and if it isn't an ndarray it is treated as a scalar. Returns ------- values : ndarray, compatible object The values of the multidimensional polynomial on points formed with triples of corresponding values from `x`, `y`, and `z`. See Also -------- chebval, chebgridnd, chebfitnd """ try: xi = np.array(xi, copy=0) except: raise ValueError('x, y, z are incompatible') chebval = np.polynomial.chebyshev.chebval c = chebval(xi[0], c) for x in xi[1:]: c = chebval(x, c, tensor=False) return c def chebgridnd(c, *xi): """ Evaluate a N-D Chebyshev series on the Cartesian product of x1, x2,..., xn. This function returns the values: .. math:: p(a,b,...) = \\sum_{i,j,...} c_{i,j,...} * T_i(a) * T_j(b) * ... where the points `(a, b, ...)` consist of all points formed by taking `a` from `x1`, `b` from `x2`, and so on. The resulting points form a grid with `x1` in the first dimension, `x2` in the second, and so on. The parameters `x1`, `x2`, ... and `xn` are converted to arrays only if they are tuples or a lists, otherwise they are treated as a scalars. In either case, either `x1`, `x2`,... and `xn` or their elements must support multiplication and addition both with themselves and with the elements of `c`. If `c` has fewer than N dimensions, ones are implicitly appended to its shape to make it N-D. The shape of the result will be c.shape[3:] + x1.shape + x2.shape + ... + xn.shape Parameters ---------- c : array_like Array of coefficients ordered so that the coefficients for terms of degree i,j are contained in ``c[i,j]``. If `c` has dimension greater than two the remaining indices enumerate multiple sets of coefficients. x1, x2,..., xn : ndarray, compatible object 1-D arrays representing the coordinates of a grid. The N dimensional series is evaluated at the points in the Cartesian product of `x1`, `x2`, ... and `xn`. If `xi`, is a list or tuple, it is first converted to an ndarray, otherwise it is left unchanged and, if it isn't an ndarray, it is treated as a scalar. Returns ------- values : ndarray, compatible object The values of the N dimensional polynomial at points in the Cartesian product of `x1`, `x2`, ... and `xn`. See Also -------- chebval, chebvalnd, chebfitnd """ chebval = np.polynomial.chebyshev.chebval for x in xi: c = chebval(x, c) return c def test_chebfit1d(): def f(x): return np.exp(-x**2) # x = chebroot(n=64, kind=1) # z = f(x) c = chebfit(f, n=64)[::-1] xi = np.linspace(-1, 1, 151) zi = np.polynomial.chebyshev.chebval(xi, c) # plt.plot(xi, zi,'.', xi, f(xi)) plt.semilogy(xi, np.abs(zi-f(xi))) plt.show('hold') def test_chebfit2d(): n = 3 xorder, yorder = n-1, n-1 x = chebroot(n=n, kind=1) xgrid, ygrid = np.meshgrid(x, x) def f(x, y): return np.exp(-x**2-6*y**2) zgrid = f(xgrid, ygrid) # v2d = np.polynomial.chebyshev.chebvander2d(xgrid, ygrid, # [xorder,yorder]).reshape((-1, (xorder+1)*(yorder+1))) # coeff, residuals, rank, s = np.linalg.lstsq(v2d, zgrid.ravel()) # doeff = coeff.reshape(xorder+1,yorder+1) _dcoeff2 = chebfitnd((xgrid, ygrid), zgrid, [xorder, yorder]) dcoeff = chebfit_dct(f, n=(xorder+1, yorder+1)) xi = np.linspace(-1, 1, 151) Xi, Yi = np.meshgrid(xi, xi) Zi = f(Xi, Yi) zzi = chebvalnd(dcoeff, Xi, Yi) _devi = Zi - zzi # plot residuals # zz = np.polynomial.chebyshev.chebval2d(xgrid, ygrid, dcoeff) zz = chebvalnd(dcoeff, xgrid, ygrid) dev = zgrid - zz # plt.spy(np.abs(dcoeff)>1e-13) plt.contourf(xgrid, ygrid, np.abs(dev)) # plt.contourf(Xi, Yi, np.abs(devi)) plt.colorbar() # plt.semilogy(np.abs(devi.ravel())) plt.show('hold') if __name__ == '__main__': if False: # True: # main() else: # test_chebfit2d() test_docstrings() # test_polydeg()