使用便利类#

polynomial 包提供的便利类包括:

名称

提供

Polynomial

幂级数

Chebyshev

切比雪夫级数

Legendre

勒让德级数

Laguerre

拉盖尔级数

Hermite

埃尔米特级数

HermiteE

埃尔米特E级数

这里的级数是指多项式基函数乘以系数的有限和。例如,幂级数形式如下:

\[p(x) = 1 + 2x + 3x^2\]

其系数为 \([1, 2, 3]\)。具有相同系数的切比雪夫级数形式如下:

\[p(x) = 1 T_0(x) + 2 T_1(x) + 3 T_2(x)\]

更一般地,我们有:

\[p(x) = \sum_{i=0}^n c_i T_i(x)\]

其中 \(T_n\) 是 n 次的切比雪夫函数,但也可以是任何其他级数的基函数。所有类都遵循约定,即系数 \(c[i]\) 对应于 i 次的基函数。

所有类都是不可变的,并且具有相同的方法,特别地,它们实现了 Python 的数字运算符 +, -, *, //, %, divmod, **, ==, 和 !=。由于浮点舍入误差,最后两个可能有些问题。现在我们用 NumPy 版本 1.7.0 快速演示各种操作。

基本用法#

首先,我们需要一个多项式类和一个多项式实例来演示。类可以直接从 polynomial 包或相关类型的模块导入。这里我们从包导入,并使用习惯性的 Polynomial 类,因为它更熟悉。

>>> from numpy.polynomial import Polynomial as P
>>> p = P([1,2,3])
>>> p
Polynomial([1., 2., 3.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

注意,打印输出的长版本有三个部分。第一部分是系数,第二部分是定义域 (domain),第三部分是窗口 (window)。

>>> p.coef
array([1., 2., 3.])
>>> p.domain
array([-1.,  1.])
>>> p.window
array([-1.,  1.])

打印一个多项式会以更熟悉的形式显示多项式表达式。

>>> print(p)
1.0 + 2.0·x + 3.0·x²

请注意,默认情况下,多项式的字符串表示形式使用 Unicode 字符(Windows 除外)来表示幂和下标。也提供了一种基于 ASCII 的表示形式(Windows 上的默认设置)。可以使用包级别的 set_default_printstyle 函数来切换多项式字符串格式。

>>> np.polynomial.set_default_printstyle('ascii')
>>> print(p)
1.0 + 2.0 x + 3.0 x**2

或通过字符串格式化控制单个多项式实例:

>>> print(f"{p:unicode}")
1.0 + 2.0·x + 3.0·x²

当我们讨论拟合时,我们会处理定义域和窗口,现在我们忽略它们,然后进行基本代数和算术运算。

加法和减法

>>> p + p
Polynomial([2., 4., 6.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')
>>> p - p
Polynomial([0.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

乘法

>>> p * p
Polynomial([ 1.,   4.,  10.,  12.,   9.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

幂运算

>>> p**2
Polynomial([ 1.,   4., 10., 12.,  9.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

除法

地板除法 ‘//’ 是多项式类的除法运算符,多项式在这方面被视为整数。对于 Python 版本 < 3.x,‘/’ 运算符映射到 ‘//’,正如 Python 的情况一样。对于稍后的版本,‘/’ 仅适用于除以标量。迟早会被弃用。

>>> p // P([-1, 1])
Polynomial([5.,  3.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

余数

>>> p % P([-1, 1])
Polynomial([6.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

整除

>>> quo, rem = divmod(p, P([-1, 1]))
>>> quo
Polynomial([5.,  3.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')
>>> rem
Polynomial([6.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

求值

>>> x = np.arange(5)
>>> p(x)
array([  1.,   6.,  17.,  34.,  57.])
>>> x = np.arange(6).reshape(3,2)
>>> p(x)
array([[ 1.,   6.],
       [17.,  34.],
       [57.,  86.]])

代入

将一个多项式代入 x,并展开结果。这里我们将 p 代入自身,展开后得到一个 4 次的新多项式。如果将多项式视为函数,这就是函数的复合。

>>> p(p)
Polynomial([ 6., 16., 36., 36., 27.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

>>> p.roots()
array([-0.33333333-0.47140452j, -0.33333333+0.47140452j])

显式使用 Polynomial 实例并不总是方便,因此元组、列表、数组和标量在算术运算中会自动转换为多项式。

>>> p + [1, 2, 3]
Polynomial([2., 4., 6.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')
>>> [1, 2, 3] * p
Polynomial([ 1.,  4., 10., 12.,  9.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')
>>> p / 2
Polynomial([0.5, 1. , 1.5], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

在定义域、窗口或类上不同的多项式不能混合进行算术运算。

>>> from numpy.polynomial import Chebyshev as T
>>> p + P([1], domain=[0,1])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 213, in __add__
TypeError: Domains differ
>>> p + P([1], window=[0,1])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 215, in __add__
TypeError: Windows differ
>>> p + T([1])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 211, in __add__
TypeError: Polynomial types differ

但是可以使用不同的类型进行代入。实际上,这就是多项式类之间进行类型、定义域和窗口转换的方式。

>>> p(T([0, 1]))
Chebyshev([2.5, 2. , 1.5], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

这将得到切比雪夫形式的多项式 p。之所以可行,是因为 \(T_1(x) = x\),用 \(x\) 代入 \(x\) 并不会改变原始多项式。然而,所有的乘法和除法都将使用切比雪夫级数进行计算,因此结果的类型也是如此。

所有多项式实例都设计为不可变,因此,增量运算(+=, -= 等)以及任何可能违反多项式实例不可变性的功能,都被有意地未实现。

微积分#

多项式实例可以进行积分和微分。

>>> from numpy.polynomial import Polynomial as P
>>> p = P([2, 6])
>>> p.integ()
Polynomial([0., 2., 3.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')
>>> p.integ(2)
Polynomial([0., 0., 1., 1.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

第一个例子是对 p 进行一次积分,第二个例子进行两次积分。默认情况下,积分的下限和积分常数为 0,但两者都可以指定。

>>> p.integ(lbnd=-1)
Polynomial([-1.,  2.,  3.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')
>>> p.integ(lbnd=-1, k=1)
Polynomial([0., 2., 3.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

在第一种情况下,积分的下限设置为 -1,积分常数为 0。在第二种情况下,积分常数也设置为 1。微分更简单,因为唯一的选项是多项式微分的次数。

>>> p = P([1, 2, 3])
>>> p.deriv(1)
Polynomial([2., 6.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')
>>> p.deriv(2)
Polynomial([6.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

其他多项式构造器#

通过指定系数来构造多项式只是一种获得多项式实例的方法,它们也可以通过指定根、从其他多项式类型转换以及最小二乘拟合来创建。拟合将在其自己的章节中讨论,其他方法将在下面演示。

>>> from numpy.polynomial import Polynomial as P
>>> from numpy.polynomial import Chebyshev as T
>>> p = P.fromroots([1, 2, 3])
>>> p
Polynomial([-6., 11., -6.,  1.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')
>>> p.convert(kind=T)
Chebyshev([-9.  , 11.75, -3.  ,  0.25], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

convert 方法还可以转换定义域和窗口。

>>> p.convert(kind=T, domain=[0, 1])
Chebyshev([-2.4375 ,  2.96875, -0.5625 ,  0.03125], domain=[0.,  1.], window=[-1.,  1.], symbol='x')
>>> p.convert(kind=P, domain=[0, 1])
Polynomial([-1.875,  2.875, -1.125,  0.125], domain=[0.,  1.], window=[-1.,  1.], symbol='x')

在 numpy 版本 >= 1.7.0 中,还提供了 basiscast 类方法。cast 方法与 convert 方法类似,而 basis 方法则返回给定次数的基多项式。

>>> P.basis(3)
Polynomial([0., 0., 0., 1.], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')
>>> T.cast(p)
Chebyshev([-9.  , 11.75, -3. ,  0.25], domain=[-1.,  1.], window=[-1.,  1.], symbol='x')

类型之间的转换很有用,但**不**推荐常规使用。从 50 次的切比雪夫级数到同次数的 Polynomial 级数的数值精度损失,可能会导致数值求值结果变得几乎随机。

拟合#

拟合是 domainwindow 属性成为便利类一部分的原因。为了说明这个问题,下面绘制了最高次数为 5 的切比雪夫多项式的值。

>>> import matplotlib.pyplot as plt
>>> from numpy.polynomial import Chebyshev as T
>>> x = np.linspace(-1, 1, 100)
>>> for i in range(6):
...     ax = plt.plot(x, T.basis(i)(x), lw=2, label=f"$T_{i}$")
...
>>> plt.legend(loc="upper left")
>>> plt.show()
../_images/routines-polynomials-classes-1.png

在 -1 <= x <= 1 的范围内,它们是漂亮的、等波纹函数,值在 +/- 1 之间。而在 -2 <= x <= 2 的范围内,同样的图看起来非常不同。

>>> import matplotlib.pyplot as plt
>>> from numpy.polynomial import Chebyshev as T
>>> x = np.linspace(-2, 2, 100)
>>> for i in range(6):
...     ax = plt.plot(x, T.basis(i)(x), lw=2, label=f"$T_{i}$")
...
>>> plt.legend(loc="lower right")
>>> plt.show()
../_images/routines-polynomials-classes-2.png

正如所见,“好的”部分已经变得微不足道。在使用切比雪夫多项式进行拟合时,我们希望使用 x 在 -1 和 1 之间的区域,而这正是 window 所指定的。然而,要拟合的数据不太可能所有数据点都在该区间内,因此我们使用 domain 来指定数据点所在的区间。当进行拟合时,定义域首先通过线性变换映射到窗口,然后使用映射后的数据点进行标准的最小二乘拟合。拟合的窗口和定义域是返回的级数的一部分,并在计算值、导数等时自动使用。如果在调用中未指定它们,拟合例程将使用默认窗口和包含所有数据点的最小定义域。下面用一个拟合带噪声的正弦曲线的例子来说明。

>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> from numpy.polynomial import Chebyshev as T
>>> np.random.seed(11)
>>> x = np.linspace(0, 2*np.pi, 20)
>>> y = np.sin(x) + np.random.normal(scale=.1, size=x.shape)
>>> p = T.fit(x, y, 5)
>>> plt.plot(x, y, 'o')
>>> xx, yy = p.linspace()
>>> plt.plot(xx, yy, lw=2)
>>> p.domain
array([0.        ,  6.28318531])
>>> p.window
array([-1.,  1.])
>>> plt.show()
../_images/routines-polynomials-classes-3.png