置頂

我的 VPython 教學文件 (HackMD 版本)

VPython 教學文件目錄 安裝及測試 基本語法 等速度直線運動 自由落下 終端速度 水平抛射 使用For迴圈計算水平抛射資料 斜向抛射 圓周運動 簡諧運動 單擺 木塊彈簧系統分離 重力及簡諧 行星運動 相疊木塊 雙重簡諧運動 一維彈性碰撞 ...

熱門文章

2021年3月28日 星期日

使用 SciPy 計算快速傅立葉變換

作者:王一哲
日期:2021/3/28


快速傅立葉變換


快速傅立葉變換 (fast Fourier transform, FFT) 是一種用來計算離散傅立葉變換 (discrete Fourier transform, DFT) 及其逆變換的計算方法,目前常用的是庫利-圖基演算法 (Cooley-Tukey FFT algorithm) 演算法。快速傅立葉變換通常被用在分析訊號的頻率及強度,以下是使用 SciPy 提供的工具計算 FFT 的方法。


SciPy FFT


SciPy 提供兩種計算 FFT 的工具,分別是 scipy.fft [1] 以及 scipy.fftpack [2],依據官方說明書,新版的程式碼為 scipy.fft。 以下是在 jupyter-notebook 中執行的程式碼,首先引入函式庫,並於儲存格中啟用 matplotlibe 繪圖功能。
import matplotlib.pyplot as plt
import numpy as np
from scipy.fft import fft, fftfreq, ifft

%matplotlib inline

設定資料點數量 N,sin 函數頻率 f1 ~ f5、振幅 A1 ~ A5,時間間隔 dt。
N = 4096
f1, f2, f3, f4, f5 = 10, 30, 50, 70, 90
A1, A2, A3, A4, A5 = 0.5, 0.4, 0.3, 0.2, 0.1
dt = 2/f1/N

使用 numpy.linspace 產生時間資料 t,計算對應的振幅 y,兩者的關係為 $$ y = A_1 \sin(2 \pi f_1 t) + A_2 \sin(2 \pi f_2 t) + A_3 \sin(2 \pi f_3 t) + A_4 \sin(2 \pi f_4 t) + A_5 \sin(2 \pi f_5 t) $$
t = np.linspace(0, 2/f1, N, endpoint=False)
y = A1*np.sin(2*np.pi*f1*t) + A2*np.sin(2*np.pi*f2*t) + A3*np.sin(2*np.pi*f3*t) + \
    A4*np.sin(2*np.pi*f4*t) + A5*np.sin(2*np.pi*f5*t)

繪製振幅 amplitude - 時間 t 關係圖
plt.figure(figsize=(15,4), dpi=72)
plt.plot(t, y, linestyle='-', color='blue')
plt.grid()
plt.xlabel('t (s)', fontsize=14)
plt.ylabel('amplitude', fontsize=14)
plt.show()

振幅 amplitude - 時間 t 關係圖

使用 scipy.fft.fft 由 y 計算對應的強度並儲存於 yf,使用 scipy.fft.fftfreq 由 N 及 dt 計算對應的頻率並儲存於 xf,繪製強度 intensity - 頻率 f 關係圖。從圖中可以看出峰值對應的頻率分別為10、30、50、70、90 Hz,峰值對應的強度分別為0.5、0.4、0.3、0.2、0.1,和我們產生的資料相符。
plt.figure(figsize=(15,4), dpi=72)
yf = fft(y)
xf = fftfreq(N, dt)
plt.plot(xf[:N//128], 2/N*np.abs(yf[:N//128]), linestyle='-', color='blue')
plt.grid()
plt.xlabel('frequency (Hz)', fontsize=14)
plt.ylabel('intensity', fontsize=14)

強度 intensity - 頻率 f 關係圖

使用 scipy.fft.ifft 由 yf 進行逆變換並儲存於 yif,繪製振幅 amplitude - 時間 t 關係圖,可以看出這張圖與第一張圖相同。
yif = ifft(yf)
plt.figure(figsize=(15,4), dpi=72)
plt.plot(t, yif.real, linestyle='-', color='blue')
plt.grid()
plt.xlabel('t (s)', fontsize=14)
plt.ylabel('amplitude', fontsize=14)
plt.show()

振幅 amplitude - 時間 t 關係圖

為了比較三者的差異,將三張小圖上、下排列畫在同一張大圖中。
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(15,12), dpi=72, sharex=False)

ax1.plot(t, y, linestyle='-', color='blue')
ax1.grid()
ax1.set_xlabel('t (s)', fontsize=14)
ax1.set_ylabel('amplitude', fontsize=14)

ax2.plot(xf[:N//128], 2/N*np.abs(yf[:N//128]), linestyle='-', color='blue')
ax2.grid()
ax2.set_xlabel('frequency (Hz)', fontsize=14)
ax2.set_ylabel('intensity', fontsize=14)

ax3.plot(t, yif.real, linestyle='-', color='blue')
ax3.grid()
ax3.set_xlabel('t (s)', fontsize=14)
ax3.set_ylabel('amplitude', fontsize=14)

plt.show()

同時畫出三張小圖



結語


由於我以前的工作並沒有用到 FFT,即使找到了對應的演算法也不想要花時間從頭開始再寫一次,我認識的人當中有人用 Fortran 寫了一支專門計算 FFT 的程式,這實在太厲害了。感謝 SciPy 的開發者,讓我有現成的 FFT 工具能用。


參考資料


  1. scipy.fft 官方說明書 https://docs.scipy.org/doc/scipy/reference/tutorial/fft.html
  2. scipy.fftpack 官方說明書 https://docs.scipy.org/doc/scipy/reference/fftpack.html




HackMD 版本連結:https://hackmd.io/@yizhewang/HJcTbMoVu

3 則留言:

  1. 您好
    非常感謝您的分享,非常清楚!

    但能否跟您請教一個問題,為何在做 強度 intensity - 頻率 f 關係圖 時,不是直接使用 xf 及 yf, 而是像您的程式碼這樣呢?
    plt.plot(xf[:N//128], 2/N*np.abs(yf[:N//128]), linestyle='-', color='blue')

    非常謝謝您!

    回覆刪除
    回覆
    1. 詳細的原因我有點忘記了,不過如果程式碼改成

      plt.plot(xf, 2/N*np.abs(yf), linestyle='-', color='blue')

      畫出來的圖會變成這樣 https://imgur.com/Ly3g29c.png

      刪除
    2. 好的,了解!

      非常謝謝您這麼迅速的回覆!

      刪除