2.4.8 多项式函数类

numpy.polynomial模块中提供了更丰富的多项式函数类,例如Polynomial、Chebyshev、Legendre等。它们和前面介绍的numpy.poly1d相反,多项式各项的系数按照幂从小到大的顺序排列,下面使用Polynomial类表示多项式x3 -2x+1,并计算x=2处的值:

    from numpy.polynomial import Polynomial, Chebyshev
    p = Polynomial([1, -2, 0, 1])
    print p(2.0)
    5.0

Polynomial对象提供了众多的方法对多项式进行操作,例如deriv( )计算导函数:

    p.deriv( )
    Polynomial([-2., 0., 3.], [-1., 1.], [-1., 1.])

切比雪夫多项式是一个正交多项式序列Ti (x),一个n次多项式可以表示为多个切比雪夫多项式的加权和。在NumPy中,使用Chebyshev类表示由切比雪夫多项式组成的多项式p(x):

Ti (x)多项式可以通过Chebyshev.basis(i)获得,图2-11显示了0到4次切比雪夫多项式。通过多项式类的convert( )方法可以在不同类型的多项式之间相互转换,转换的目标类型由kind参数指定。例如下面将T4 (x)转换成Polynomial类。由结果可知:

图2-11 0到4次切比雪夫多项式

    Chebyshev.basis(4).convert(kind=Polynomial)
    Polynomial([ 1., 0., -8., 0., 8.], [-1., 1.], [-1., 1.])

切比雪夫多项式的根被称为切比雪夫节点,可以用于多项式插值。相应的插值多项式能最大限度地降低龙格现象,并且提供多项式在连续函数的最佳一致逼近。下面以函数插值为例演示切比雪夫节点与龙格现象。

❶在[-1,1]区间上等距离取n个取样点。❷使用n阶切比雪夫多项式的根作为取样点。❸使用两种取样点分别对f(x)进行多项式插值,即计算一个多项式经过所有的插值点。图2-12显示了两种插值点所得到的插值多项式,由下左图可知等距离插值多项式在两端有非常大的振荡,这种现象被称为龙格现象,n越大振荡也越大;而下右图采用切比雪夫节点作为插值点,插值多项式的振荡明显减小,并且n越大振荡越小。

图2-12 等距离插值点(左)、切比雪夫插值点(右)

插值与拟合

所谓多项式插值就是找到一个多项式经过所有的插值点。一个n阶多项式有n+1个系数,因此可以通过解方程求解经过n+1个插值点的n阶多项式的系数。fit( )方法虽然计算与目标点拟合的多项式系数,但是当使用n阶多项式拟合n+1的目标点时,多项式将经过所有目标点,因此其结果与多项式插值相同。

    def f(x):
     return 1.0 / ( 1 + 25 *
 x**
2)
    
    n = 11
    x1 = np.linspace(-1, 1, n) ❶
    x2 = Chebyshev.basis(n).roots( ) ❷
    xd = np.linspace(-1, 1, 200)
    
    c1 = Chebyshev.fit(x1, f(x1), n - 1, domain=[-1, 1]) ❸
    c2 = Chebyshev.fit(x2, f(x2), n - 1, domain=[-1, 1])
    
    print u"插值多项式的最大误差:",
    print u"等距离取样点:", abs(c1(xd) - f(xd)).max( ),
    print u"切比雪夫节点:", abs(c2(xd) - f(xd)).max( )
    插值多项式的最大误差:等距离取样点: 1.91556933029 切比雪夫节点: 0.109149825014

在使用多项式逼近函数时,使用切比雪夫多项式进行插值的误差比一般多项式要小许多。在下面的例子中,对g(x)在100个切比雪夫节点之上分别使用Polynomial和Chebyshev进行插值,结果如图2-13所示。在使用Polynomial.fit( )插值时,产生了RankWarning: The fit may be poorly conditioned警告,因此其结果多项式未能经过所有插值点。

    def g(x):
        x = (x - 1) *
 5
        return np.sin(x**
2) + np.sin(x)**
2
    
    n = 100
    x = Chebyshev.basis(n).roots( )
    xd = np.linspace(-1, 1, 1000)
    
    p_g = Polynomial.fit(x, g(x), n - 1, domain=[-1, 1])
    c_g = Chebyshev.fit(x, g(x), n - 1, domain=[-1, 1])
    
    print "Max Polynomial Error:", abs(g(xd) - p_g(xd)).max( )
    print "Max Chebyshev Error:", abs(g(xd) - c_g(xd)).max( )
    Max Polynomial Error: 1.19560558744
    Max Chebyshev Error: 6.47575726376e-09

trim( )方法可以降低多项式的次数,将尾部绝对值小于参数tol的高次系数截断。下面使用trim( )方法获取c中前68个系数,得到一个新的Chebyshev对象c_trimed,其最大误差上升到0.09左右。

    c_trimed = c_g.trim(tol=0.05)
    print "degree:", c_trimed.degree( )
    print "error:", abs(g(xd) - c_trimed(xd)).max( )
    degree: 68
    error: 0.0912094835458

下面用同样的方法对函数h(x)进行19阶的切比雪夫多项式插值,得到插值多项式c_h:

    def h(x):
        x = 5 *
 x
        return np.exp(-x**
2 / 10)
    
    n = 20
    x = Chebyshev.basis(n).roots( )
    c_h = Chebyshev.fit(x, h(x), n - 1, domain=[-1, 1])
    
    print "Max Chebyshev Error:", abs(h(xd) - c_h(xd)).max( )
    Max Chebyshev Error: 1.66544267266e-09

多项式类支持四则运算,下面将c_g和c_h相减得到c_diff,并调用其roots( )计算其所有根。然后找出其中所有的实数根real_roots,它们就是g(x)与h(x)交点的横坐标。图2-14显示了这两条函数曲线以及通过插值多项式计算的交点:

图2-14 使用Chebyshev插值计算两条曲线在[-1,1]之间的所有交点

    c_diff = c_g - c_h
    roots = c_diff.roots( )
    real_roots = roots[roots.imag == 0].real
    print np.allclose(c_diff(real_roots), 0)
    True

切比雪夫多项式在区间[-1,1]上为正交多项式,因此只有在该区间才能对目标函数正确插值。为了对任何区域的目标函数进行插值,需要对自变量的区间进行缩放和平移变换。可以通过domain参数指定拟合点的区间。在下面的例子中,对g2(x)在区间[-10,0]之内使用切比雪夫多项式进行插值。❶为了产生目标区间的切比雪夫节点,在通过basis( )方法创建Tn (x)时,通过domain参数指定目标区间。❷在调用fit( )方法进行拟合时,通过domain参数指定同样的区间。❸最后输出拟合得到的c_g2多项式在[-10,0]中与目标函数的最大误差。

    def g2(x):
        return np.sin(x**
2) + np.sin(x)**
2
    
    n = 100
    x = Chebyshev.basis(n, domain=[-10, 0]).roots( ) ❶
    xd = np.linspace(-10, 0, 1000)
    
    c_g2 = Chebyshev.fit(x, g2(x), n - 1, domain=[-10, 0]) ❷
    
    print "Max Chebyshev Error:", abs(g2(xd) - c_g2(xd)).max( ) ❸
    Max Chebyshev Error: 6.47574571744e-09