NumPy 與海龜繪圖(二)


接續〈NumPy 與海龜繪圖(一)〉,想要認識海龜繪圖的原理,直接來實作一隻海龜是最快的方式,基本上它只要兩個成員就可以了,目前座標與方向。

在資料的表現上,直覺會想到座標是二維座標,而方向用角度來表示,不過,採用向量來表現會更直覺,也就是座標與方向都用向量來表現。

使用向量來代表座標的話,位移也就可以使用向量來表示,例如,若原本座標向量是 (x, y),位移向量為 (dx, dy),位移後的座標就是 (x, y) + (dx, dy)

想使用向量來代表方向的話,可以使用單位向量,例如,若 (1, 0) 表示朝 x 軸正方向的方向向量,若前進 leng,位移向量就是 (1, 0) * leng,如果要旋轉方向,只要有個旋轉矩陣,因為是二維平面,只會繞 z 軸旋轉,旋轉矩陣就是:

NumPy 與海龜繪圖(二)

透過 NumPy 可以很簡單地實現:

def rotate(vt, angle):
    theta = np.radians(angle)
    c, s = np.cos(theta), np.sin(theta)
    rm = np.array([
       [c, -s], 
       [s, c]
    ])
    return rm @ vt # @ 是矩陣相乘

既然可以用向量來表示座標、方向與位移等,要實現一隻海龜就簡單了:

class Turtle:
    def __init__(self, x = 0, y = 0, angle = 0):
        self.coordinateVt = np.array([x, y])
        self.headingVt = rotate(np.array([1, 0]), angle)

    def forward(self, leng):
        self.coordinateVt = self.coordinateVt + self.headingVt * leng

    def turn(self, angle):
        self.headingVt = rotate(self.headingVt, angle)

可以用 Turtle 來取代 turtle 模組,例如〈NumPy 與海龜繪圖(一)〉最後的範例可以改寫如下:

import numpy as np
import matplotlib.pyplot as plt

class Turtle:
    def __init__(self, x = 0, y = 0, angle = 0):
        self.coordinateVt = np.array([x, y])
        self.headingVt = rotate(np.array([1, 0]), angle)

    def forward(self, leng):
        self.coordinateVt =  self.coordinateVt + self.headingVt * leng

    def turn(self, angle):
        self.headingVt = rotate(self.headingVt, angle)

def rotate(vt, angle):
    theta = np.radians(angle)
    c, s = np.cos(theta), np.sin(theta)
    rm = np.array([
       [c, -s], 
       [s, c]
    ])
    return rm @ vt

def forward_left(_, t, leng, a):
    t.forward(leng)
    t.turn(a)
    return t.coordinateVt
forward_left = np.frompyfunc(forward_left, 4, 1)

leng = 200
a = 170
n = 37

step = np.arange(n)
coord = forward_left(step, Turtle(), leng, a)
np_pos = np.array(coord.tolist())

x = np_pos[:,0] 
y = np_pos[:,1] 

plt.gca().set_aspect(1)
plt.plot(x, y)  
plt.show()

你有察覺什麼嗎?在實作 Turtleforward 時,其實你是在累加位移量:

self.coordinateVt =  self.coordinateVt + self.headingVt * leng

位移量實際上是來自於海龜的方向向量,方向向量會受到角度影響,就這個畫星形的例子來說,其實動作很規律,前進、轉動、前進、轉動,既然如此,可不可以先整組處理角度,再整組處理位移量?

step = np.arange(n)
angle = step * a

dx = leng * np.cos(angle)
dy = leng * np.sin(angle)

最後只要將 dxdy 的累計加總各自計算出來:

x = np.cumsum(dx) # cumsum 可以計算累計加總
y = np.cumsum(dy)

xy 餵給 plot 不就好了?也就是說,以下的程式就可以畫出星狀圖了:

import numpy as np
import matplotlib.pyplot as plt

leng = 200
a = np.radians(170)
n = 37

step = np.arange(n)
angle = step * a

dx = leng * np.cos(angle)
dy = leng * np.sin(angle)

x = np.cumsum(dx)
y = np.cumsum(dy)

plt.gca().set_aspect(1)
plt.plot(x, y)  
plt.show()

使用 NumPy 其實是種重新看待需求的過程,將一切都看成是資料的處理,有時你不會直接面對資料,如何找出資料才是難處,然而也是使用 NumPy 這類工具真正有趣的地方。