第五章 矩阵和通用函数
5.13 斐波那契数列
斐波那契(Fibonacci)数列是基于递推关系生成的。直接用NumPy
代码来解释递推关系是比较麻烦的,不过我们可以用矩阵的形式或者黄金分割公式来解释它。因此,我们将介绍matrix
和rint
函数。使用matrix
函数创建矩阵,rint
函数对浮点数取整,但结果仍为浮点数类型。
5.14 动手实践:计算斐波那契数列
斐波那契数列的递推关系可以用矩阵来表示。斐波那契数列的计算等价于矩阵的连乘。
- (1) 创建斐波那契矩阵:
import numpy as np
F = np.matrix([[1, 1], [1, 0]])
print( "F", F)
创建的斐波那契矩阵如下所示:
F [[1 1]
[1 0]]
- (2) 计算斐波那契数列中的第8个数,即矩阵的幂为8减去1。计算出的斐波那契数位于矩阵的对角线上:
print( "8th Fibonacci", (F ** 7)[0, 0])
输出的第8个斐波那契数为:
8th Fibonacci 21
- (3) 利用黄金分割公式或通常所说的比奈公式(Binet’ s Formula),加上取整函数,就可以直接计算斐波那契数。计算前8个斐波那契数:
n = np.arange(1, 9)
sqrt5 = np.sqrt(5)
phi = (1 + sqrt5)/2
fibonacci = np.rint((phi**n - (-1/phi)**n)/sqrt5)
print( "Fibonacci", fibonacci)
输出的斐波那契数为:
Fibonacci [ 1. 1. 2. 3. 5. 8. 13. 21.]
案例完整代码如下:
import numpy as np
F = np.matrix([[1, 1], [1, 0]])
print( "F", F)
print( "8th Fibonacci", (F ** 7)[0, 0])
n = np.arange(1, 9)
sqrt5 = np.sqrt(5)
phi = (1 + sqrt5)/2
fibonacci = np.rint((phi**n - (-1/phi)**n)/sqrt5)
print( "Fibonacci", fibonacci)
5.15 利萨茹曲线
在NumPy
中,所有的标准三角函数如sin
、 cos
、 tan
等均有对应的通用函数。利萨茹曲线(Lissajous curve)是一种很有趣的使用三角函数的方式。我至今仍记得在物理实验室的示波器上显示出利萨茹曲线时的情景。利萨茹曲线由以下参数方程定义:
x = A sin(at + n/2)
y = B sin(bt)
5.16 动手实践:绘制利萨茹曲线
利萨茹曲线的参数包括A
、 B
、 a
和b
。为简单起见,我们令A
和B
均为1
。
- (1) 使用
linspace
函数初始化变量t
,即从-pi
到pi
上均匀分布的201
个点:
a = 9
b = 8
t = np.linspace(-np.pi, np.pi, 201)
- (2) 使用
sin
函数和NumPy
常量pi
计算变量x
:
x = np.sin(a * t + np.pi/2)
- (3) 使用
sin
函数计算变量y
:
y = np.sin(b * t)
(4) 绘制的曲线如下所示。
案例完整代码如下:
import numpy as np
import matplotlib.pyplot as plt
a = 9
b = 8
t = np.linspace(-np.pi, np.pi, 201)
x = np.sin(a * t + np.pi/2)
y = np.sin(b * t)
plt.plot(x, y)
plt.show()
5.17 方波
方波也是一种可以在示波器上显示的波形。方波可以近似表示为多个正弦波的叠加。事实上,任意一个方波信号都可以用无穷傅里叶级数来表示。
傅里叶级数(Fourier series)是以正弦函数和余弦函数为基函数的无穷级数,以著名的法国数学家Jean-Baptiste Fourier命名。
方波可以表示为如下的傅里叶级数:
5.18 动手实践:绘制方波
与前面的教程中一样,我们仍将以相同的方式初始化t
和k
。我们需要累加很多项级数,且级数越多结果越精确,这里取k=99
以保证足够的精度。绘制方波的步骤如下。
- (1) 我们从初始化
t
和k
开始,并将函数值初始化为0
:
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(-np.pi, np.pi, 201)
k = np.arange(1, 99)
k = 2 * k - 1
f = np.zeros_like(t)
- (2) 接下来,直接使用
sin
和sum
函数进行计算:
for i in range(len(t)):
f[i] = np.sum(np.sin(k * t[i])/k)
f = (4 / np.pi) * f
- (3) 绘制波形:
plt.plot(t, f)
plt.show()
案例完整代码如下:
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(-np.pi, np.pi, 201)
k = np.arange(1, 99)
k = 2 * k - 1
f = np.zeros_like(t)
for i in range(len(t)):
f[i] = np.sum(np.sin(k * t[i])/k)
f = (4 / np.pi) * f
plt.plot(t, f)
plt.show()
5.19 锯齿波和三角波
在示波器上,锯齿波和三角波也是常见的波形。和方波类似,我们也可以将它们表示成无穷傅里叶级数。对锯齿波取绝对值即可得到三角波。锯齿波的无穷级数表达式如下:
5.20 动手实践:绘制锯齿波和三角波
与前面的教程中一样,我们仍将以相同的方式初始化t
和k
。同样,取k=99
以保证足够的精度。绘制锯齿波和三角波的步骤如下。
- (1) 将函数值初始化为
0
:
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(-np.pi, np.pi, 201)
k = np.arange(1, 99)
f = np.zeros_like(t)
- (2) 同样,直接使用
sin
和sum
函数进行计算:
for i in range(len(t)):
f[i] = np.sum(np.sin(2 * np.pi * k * t[i])/k)
f = (-2 / np.pi) * f
- (3) 同时绘制锯齿波和三角波并不难,因为三角波函数的取值恰好是锯齿波函数值的绝对值。使用如下代码绘制波形:
plt.plot(t, f, lw=1.0)
plt.plot(t, np.abs(f), lw=2.0)
plt.show()
在下图中,较粗的曲线为三角波。
案例完整代码如下:
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(-np.pi, np.pi, 201)
k = np.arange(1, 99)
f = np.zeros_like(t)
for i in range(len(t)):
f[i] = np.sum(np.sin(2 * np.pi * k * t[i])/k)
f = (-2 / np.pi) * f
plt.plot(t, f, lw=1.0)
plt.plot(t, np.abs(f), lw=2.0)
plt.show()
5.21 位操作函数和比较函数
位操作函数可以在整数或整数数组的位上进行操作,它们都是通用函数。 ^
、 &
、 |
、 <<
、 >>
等位操作符在NumPy
中也有对应的部分, <
、>
、 ==
等比较运算符也是如此。有了这些操作符,你可以在代码中玩一些高级技巧以提升代码的性能。不过,它们会使代码变得难以理解,因此需谨慎使用。
5.22 动手实践:玩转二进制位
我们将学习三个小技巧——检查两个整数的符号是否一致,检查一个数是否为2的幂数,以及计算一个数被2的幂数整除后的余数。我们会分别展示两种方法,即使用位操作符和使用相应的NumPy函数。
- (1) 第一个小技巧需要用
XOR
或者^
操作符。XOR
操作符又被称为不等运算符,因此当两个操作数的符号不一致时,XOR
操作的结果为负数。在NumPy
中,^
操作符对应于bitwise_xor
函数,<
操作符对应于less
函数。
import numpy as np
x = np.arange(-9, 9)
y = -x
print( "Sign different?", (x ^ y) < 0)
print( "Sign different?", np.less(np.bitwise_xor(x, y), 0))
不出所料,除了都等于0
的情况,所有整数对的符号均相异。
- (2) 在二进制数中,
2
的幂数表示为一个1
后面跟一串0
的形式,例如10
、100
、1000
等。而比2
的幂数小1
的数表示为一串二进制的1
,例如11
、111
、1111
(即十进制里的3
、7
、15
)等。如果我们在2
的幂数以及比它小1
的数之间执行位与操作AND
,那么应该得到0
。在NumPy
中,&
操作符对应于bitwise_and
函数,==
操作符对应于equal
函数。
print( "Power of 2?\n", x, "\n", (x & (x - 1)) == 0)
print( "Power of 2?\n", x, "\n", np.equal(np.bitwise_and(x, (x - 1)), 0))
结果如下:
Power of 2?
[-9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8]
[False False False False False False False False False True True True
False True False False False True]
Power of 2?
[-9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8]
[False False False False False False False False False True True True
False True False False False True]
- (3) 计算余数的技巧实际上只在模为
2
的幂数(如4
、8
、16
等)时有效。二进制的位左移一位,则数值翻倍。在前一个小技巧中我们看到,将2
的幂数减去1
可以得到一串1
组成的二进制数,如11
、111
、1111
等。这为我们提供了掩码(mask),与这样的掩码做位与操作AND
即可得到以2
的幂数作为模的余数。在NumPy
中,<<
操作符对应于left_shift
函数。
print( "Modulus 4\n", x, "\n", x & ((1 << 2) - 1))
print( "Modulus 4\n", x, "\n", np.bitwise_and(x, np.left_shift(1, 2) - 1))
结果如下:
Modulus 4
[-9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8]
[3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0]
Modulus 4
[-9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8]
[3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0]
案例完整代码如下:
import numpy as np
x = np.arange(-9, 9)
y = -x
print( "Sign different?", (x ^ y) < 0)
print( "Sign different?", np.less(np.bitwise_xor(x, y), 0))
print( "Power of 2?\n", x, "\n", (x & (x - 1)) == 0)
print( "Power of 2?\n", x, "\n", np.equal(np.bitwise_and(x, (x - 1)), 0))
print( "Modulus 4\n", x, "\n", x & ((1 << 2) - 1))
print( "Modulus 4\n", x, "\n", np.bitwise_and(x, np.left_shift(1, 2) - 1))