Wave
程度★ 難度★★★
振動、振盪
這個世界天天都在振動。地面、空氣、海水、機械、人體等等,都是不斷振動。
振動、振盪是物理學名詞,振動(Vibration)是來回運動,振盪(Oscillation)是來回變化。
震動、震盪是自古以來就有的詞彙。
振動可以用函數表示
每個時間點的振動高低,可以描繪成函數圖形,橫向是時間軸,縱向是每個時刻的振動高低位置。
平穩的振動
最平穩的振動,就是高中物理教的簡諧運動,是等速圓周運動在座標軸上的投影,呈現sin函數。sin函數和cos函數長相一樣,只是起點不同而已。
舉例來說,敲打音叉產生的振動,就非常接近平穩的振動。
振動的快慢:頻率
單位時間振動的次數,稱做「頻率(Frequency)」。
一秒振動的次數,單位是赫茲Hz。
人類能感知頻率:耳朵能聽到20Hz至20000Hz的空氣振動,低頻低沉、高頻尖銳;眼睛能看到4×10^14Hz至8×10^14Hz的電磁振盪,低頻至高頻分別呈現紅橙黃綠藍靛紫。
振動的高低:振幅
振動的最高(低)距離,稱做「振幅(Amplitude)」。
人類能感知振幅,受頻率大小影響。就聽覺而言,振幅高則大聲、振幅低則小聲;就視覺而言,振幅高則亮、振幅低則暗。
題外話,人類對於頻率與振幅的區分能力,大略等於取log。
振動的起點:相位
振動的起點,稱做「相位(Phase)」。
注意到,相位是圓周運動cos函數的角度,而不是振動高低位置。物理學家喜歡用相位。
人類幾乎分辨不出相位的差異。
振動有疊加效果
現實世界當中,多個振動時常融合成一個振動,等於各個振動高低位置相加。相同方向則增益、相反方向則抵銷。
寫成數學式子,就是多個函數相加。
振動有傳遞效果:波
一個粒子振動,就會導致隔壁粒子振動,一傳十、十傳百。宏觀之下,形成「波(Wave)」。
傳遞速度取決於粒子之間的作用力、粒子的質量。作用力強、質量小,則傳遞速度快。
均勻分佈的粒子之中,某個粒子振動所產生的波,剛好也呈現sin函數,英文稱做Sine Wave或者Sinusoid。
http://140.111.56.210/file/ph/phy_6-1-1-1_2/1.html
水的高低起伏,就是水波。空氣的疏密,就是聲波。地的高低起伏與左右晃動,就是地震波。電場與磁場的交互作用,就是電磁波。光波經實驗證明是電磁波。原子的振動,也許是熱。有人覺得氣功也許是波,就叫做氣功波。
觀察波上任意一點,都是在振動。許多情況下,民眾喜歡用波來指稱振動。
Complex Number
程度★ 難度★★★
有物混成,先天地生,寂兮寥兮,
獨立而不改,周行而不殆,可以為天下母。《老子》
Complex Number
快速複習一下複數吧。實數,再額外考慮i = √-1,就是複數。例如2 + 3i、(1 - √2) + (1/3)i、1 / (-2i - 4)、∛i、sin(i)。
只要是複數,都可以重新整理成左邊實數不乘i、右邊實數有乘i,兩個部分相加的格式,證明省略之;其中不乘i的部分叫做實部(real part),有乘i的部分叫做虛部(imaginary part)。例如1 / (-2i - 4)可以重新整理成-0.2 + 0.1i,其中實部是-0.2,虛部是0.1i。
複數亦可以用圖形表示。
複數平面、二維平面、極座標平面是不同的事情,不要搞混了。
兩個複數相加,就是實部加實部、虛部加虛部。在複數平面上,外觀宛如向量相加。
兩個複數相乘,就是實乘實、虛乘虛、實乘虛、虛乘實,再累加這四個乘積。在複數平面上,外觀宛如長度相乘、角度相加。
一個複數可以重新表示成一個長度與一個角度,叫做極座標表示法。長度可以用畢氏定理求得,角度可以用arctan函數求得。
一個長度與一個角度也可以還原成一個複數。實部可以用cos函數求得,虛部可以用sin函數求得。
附帶一提,長度也有人稱作強度(magnitude),角度也有人稱作相位(phase)。
Euler's Formula
強者歐拉發現這世界上有一個神奇數字e,e的純虛數次方竟然在複數平面上繞圈兒。這真是一個超乎常理的發現!
寫成數學公式是:eiθ = cosθ + i * sinθ,複數的長度是常數1,複數的角度是變數θ。等式右邊,是將長度與角度,還原成一個複數,外觀很複雜但是本質很簡單。
有了歐拉公式,一個複數也可以重新表示成e的次方、另乘上倍率。次方值即是角度乘i,倍率即是長度。
歐拉公式,定量增加θ,在複數平面上,外觀宛如「等速圓周運動」,逆時針繞圈;只看實部或者只看虛部,外觀宛如「簡諧運動」,先上後下。
繞360°是一圈,剛好回到+1;繞180°是半圈,剛好是-1。因此有了eiπ + 1 = 0這條著名等式,π就是180°。
這個e,大約是2.71828183,是自然對數的底數e,是1/x積分後所出現的e。離題了。
Wave
eiθ運算簡單,考慮長度與角度即可。eiθ性質優美,每轉90°剛好是±1與±i。也許你會漸漸愛上它。
現在有了神奇數字e,就改用eiθ來表示波。
要把eiθ還原成sin波、cos波,有兩種方式。觀察eiθ = cosθ + i * sinθ這道式子:
一種是取實部得到cos波、取虛部得到sin波。
一種是用eiθ與e-iθ,相加除以二得到cos波,相減除以二得到sin波。
Fourier Transform
程度★ 難度★★★
Fourier Transform
傅立葉轉換的輸出入都是一串複數,是一對一函數。
輸入可以是連續函數或者離散數列,輸出亦如是。根據連續與離散的差異,傅立葉轉換有著不同的名稱。混淆視聽罷了。
輸入 輸出 名稱 連續 連續 Fourier Transform 連續 離散 Discrete Fourier Transform 離散 連續 Continuous Fourier Transform 離散 離散 Discrete-time Fourier Transform
電腦做運算,數值皆離散,最常用到離散到離散的傅立葉轉換。公式如下:
傅立葉轉換:
N-1
y[n] = Σ { x[k] ÷ ei*2π*(n/N)*k } ÷ sqrt(N)
k=0
傅立葉轉換的反函數,稱做逆向傅立葉轉換:
N-1
x[n] = Σ { y[k] * ei*2π*(n/N)*k } ÷ sqrt(N)
k=0
為了加快計算速度,正向傅立葉轉換經常改成不除以sqrt(N),逆向傅立葉轉換經常改成多除以sqrt(N)。
N-1
y[n] = Σ { x[k] ÷ ei*2π*(n/N)*k }
k=0
N-1
x[n] = Σ { y[k] * ei*2π*(n/N)*k } ÷ N
k=0
傅立葉轉換是線性變換,其矩陣恰是正交基底。
W = e^(i*2π*/N) [ y0 ] [ W-0*0 W-0*1 W-0*2 ... W-0*(N-1) ] [ x0 ] [ y1 ] [ W-1*0 W-1*1 W-1*2 ... W-1*(N-1) ] [ x1 ] [ . ] = [ . . . . ] * [ . ] [ . ] [ . . . . ] [ . ] [ yN-1 ] [ W-(N-1)*0 W-(N-1)*1 W-(N-1)*2 ... W-(N-1)*(N-1) ] [ xN-1 ] [ x0 ] [ W0*0 W0*1 W0*2 ... W0*(N-1) ] [ y0 ] [ x1 ] 1 [ W1*0 W1*1 W1*2 ... W1*(N-1) ] [ y1 ] [ . ] = --- [ . . . . ] * [ . ] [ . ] N [ . . . . ] [ . ] [ xN-1 ] [ W(N-1)*0 W(N-1)*1 W(N-1)*2 ... W(N-1)*(N-1) ] [ yN-1 ]
傅立葉轉換可以推廣到高維度。舉例來說,二維的傅立葉轉換,輸出入都是一個複數矩陣,轉換過程是:橫向每一條先各自傅立葉轉換,然後直向每一條再各自傅立葉轉換。
Fourier Transform
只給數學公式,讀者應該是霧裡看花。
N個波,頻率是n=0倍到n=N-1倍,分別是ei*2π*(n/N)。
輸入數列與一個波,N個對應位置點對點相除、再求總和,得到一個輸出數值。可以簡單想做:輸入數列除以波,求比例。
(學過線性代數的讀者,也可以想做內積、投影。)
輸入數列分別除以N個波,得到N個輸出數值。這就是傅立葉轉換。
正向傅立葉轉換,是把一個複雜的波,拆解成N個平穩的波,頻率是0倍到N-1倍,振幅與相位是N個輸出數值。
逆向傅立葉轉換,是把N個平穩的波,頻率是0倍到N-1倍,分別乘上振幅、加上相位,再疊加成一個複雜的波,
Frequency Spectrum
傅立葉轉換輸出數列有N個複數,可以畫成函數──一般不畫實部與虛部,而是畫長度與角度,具備物理意義。
這N個複數的長度(振幅)畫成函數,稱為「振幅頻譜」。
這N個複數的角度(相位)畫成函數,稱為「相位頻譜」。
兩者合稱為「頻譜」。
輸入數列是一串實數時,輸出數列會前後對稱並且共軛(長度相等、角度負號)。此時頻譜可以只畫一半。
我們得以運用傅立葉轉換分解一個波,運用逆向傅立葉轉換合成一個波。運用「頻譜」,就能解讀波的詳細內容。
演算法
按照公式實作,時間複雜度是O(N^2)。
Hartley Transform
程度★★ 難度★★
Hartley Transform
哈特利轉換的輸出入都是一串實數,是一對一函數。
哈特利轉換與傅立葉轉換如出一轍,只少了虛數i而已。
傅立葉轉換的基底:
2πnk 2πnk -i2πnk/N
cos ———— - i * sin ———— = e
N N
哈特利轉換的基底:
2πnk 2πnk 2πnk
cos ———— + sin ———— = cas ————
N N N
另一個哈特利轉換的基底,比較沒人用:
2πnk 2πnk
cos ———— - sin ————
N N
傅立葉轉換:
N-1
y[n] = Σ { x[k] ÷ ei*2π*(n/N)*k } ÷ sqrt(N)
k=0
N-1
= Σ { x[k] * e-i*2π*(n/N)*k } ÷ sqrt(N)
k=0
N-1
= Σ { x[k] * ( cos(2π*(n/N)*k) - i * sin(2π*(n/N)*k) ) } ÷ sqrt(N)
k=0
哈特利轉換:
N-1
y[n] = Σ { x[k] * ( cos(2π*(n/N)*k) + sin(2π*(n/N)*k) ) } ÷ sqrt(N)
k=0
N-1
= Σ { x[k] * cas(2π*(n/N)*k) } ÷ sqrt(N)
k=0
哈特利轉換的輸出,可以調整成傅立葉轉換的輸出,O(N):
http://mathworld.wolfram.com/HartleyTransform.html
實數運算比複數運算還要簡單,所以哈特利轉換比傅立葉轉換還要快速。聲音處理、影像處理時,訊號都是實數、甚至是整數,剛好也符合哈特利轉換的輸入格式。因此一般都是套用哈特利轉換進行計算,再把結果調整成傅立葉轉換。
演算法(Bracewell's Algorithm)
由於哈特利轉換與傅立葉轉換的公式幾乎相同,所以兩者的演算法也是一一對應。這裡介紹的也是運用Divide and Conquer的方法。不一樣的是奇數項的處理方式,提出常數的步驟變複雜了。
N-1
Σ { x[k] * cas(2π*(n/N)*k) }
k=1,3,5,...
N/2-1
= Σ { x[2k+1] * cas(2π*(n/N)*(2k+1)) }
k=0,1,2,...
N/2-1
= Σ { x[2k+1] * ( cas(2π*(n/N)*2k) * cos(2π*(n/N)*1)
k=0,1,2,... + cas(-2π*(n/N)*2k) * sin(2π*(n/N)*1) ) }
N/2-1
= Σ { x[2k+1] * ( cas(2π*(n/(N/2))*k) * cos(2π*(n/N)*1)
k=0,1,2,... + cas(-2π*(n/(N/2))*k) * sin(2π*(n/N)*1) ) }
N/2-1
= Σ { x[2k+1] * cas( 2π*(n/(N/2))*k) } * cos(2π*(n/N)*1)
k=0,1,2,...
N/2-1
+ Σ { x[2k+1] * cas(-2π*(n/(N/2))*k) } * sin(2π*(n/N)*1)
k=0,1,2,...
= y奇[n] * cos(2π*(n/N)*1) + y奇[-n] * sin(2π*(n/N)*1)
e.g. N = 8, θ = 2π / N y[0] = y偶[0] + y奇[0] * cos0θ + y奇[0] * sin0θ y[1] = y偶[1] + y奇[1] * cos1θ + y奇[3] * sin1θ y[2] = y偶[2] + y奇[2] * cos2θ + y奇[2] * sin2θ y[3] = y偶[3] + y奇[3] * cos3θ + y奇[1] * sin3θ y[4] = y偶[0] + y奇[0] * cos4θ + y奇[0] * sin4θ y[5] = y偶[1] + y奇[1] * cos5θ + y奇[3] * sin5θ y[6] = y偶[2] + y奇[2] * cos6θ + y奇[2] * sin6θ y[7] = y偶[3] + y奇[3] * cos7θ + y奇[1] * sin7θ
下面是筆者所能找到的效率最好的實作程式碼:
Number Theoretic Transform
程度★★★ 難度★
Number Theoretic Transform
數論轉換的輸出入都是一串餘數,是一對一函數。
e的純虛數次方會不斷繞圈,餘數的次方也會不斷繞圈。於是有人把傅立葉轉換的基底,從ei*2π*(n/N)改成原根的次方。
http://www.apfloat.org/prim.html
輸出入每個數值都是餘數,皆大於等於零、小於模數。當輸入數值很大,那麼模數也必須足夠大。
Convolution
程度★ 難度★★★
Multiplication
兩串數列做乘法運算,得到一個數列:對應項相乘。
a0 a1 aN-1
(a0,a1,...,aN-1) × (b0,b1,...,bN-1) = ( × , × ,..., × )
b0 b1 bN-1
a-1 a0 a1
(...,a-1,a0,a1,...) × (...,b-1,b0,b1,...) = (..., × , × , × ,...)
b-1 b0 b1
Dot Product
兩串數列做內積運算,得到一個值:對應項相乘後再求和。
橫著寫叫數列,直著寫叫向量。數列內積就是向量內積。
a0 a1 aN-1
(a0,a1,...,aN-1) dot (b0,b1,...,bN-1) = × + × + ... + ×
b0 b1 bN-1
a-1 a0 a1
(...,a-1,a0,a1,...) dot (...,b-1,b0,b1,...) = ... + × + × + × + ...
b-1 b0 b1
內積是線性變換!有限長數列的內積,對應的矩陣,是橫的一條;無限長數列的內積,對應的矩陣,是無限大的矩陣。
[a0 ]
[a1 ] a0 a1 aN-1
[b0 b1 b2 ⋯ bN-1] * [a2 ] = × + × + ... + ×
[⋮ ] b0 b1 bN-1
[aN-1]
[⋮ ]
[a-1] a-1 a0 a1
[⋯ b-1 b0 b1 ⋯] * [a0 ] = ... + × + × + × + ...
[a1 ] b-1 b0 b1
[⋮ ]
Cross Product
外積,本篇用不到,略過。
Convolution
兩串數列做摺積運算,得到一串數列:一次內積算得一項,第一個數列保持不變,第二個數列頭尾顛倒,並且由第0項到第N-1項輪流作為首項。
有限長數列的摺積,第二個數列會頭尾循環,因而也有人特別稱做循環摺積(circular convolution)。
(a0,a1,...,aN-1) convolution (b0,b1,...,bN-1) = (c0,c1,...,cN-1) c0 = (a0,a1,a2,...,aN-1) dot (b0 ,bN-1,bN-2,...,b1) c1 = (a0,a1,a2,...,aN-1) dot (b1 ,b0 ,bN-1,...,b2) c2 = (a0,a1,a2,...,aN-1) dot (b2 ,b1 ,b0 ,...,b3) ⋮ cN-1 = (a0,a1,a2,...,aN-1) dot (bN-1,bN-2,bN-3,...,b0)
(...,a-1,a0,a1,...) convolution (...,b-1,b0,b1,...) = (...,c-1,c0,c1,...) ⋮ c-1 = (...,a-1,a0,a1,...) dot (...,b0 ,b-1,b-2,...) c0 = (...,a-1,a0,a1,...) dot (...,b1 ,b0 ,b-1,...) c1 = (...,a-1,a0,a1,...) dot (...,b2 ,b1 ,b0 ,...) ⋮
摺積是線性變換!有限長數列的(循環)摺積,對應的矩陣,正好是循環的Toeplitz Matrix;無限長數列的摺積,對應的矩陣,是無限大的矩陣。
(a0,a1,...,aN-1) convolution (b0,b1,...,bN-1) = (c0,c1,...,cN-1) [b0 b-1 b-2 ⋯ bN-1] [a0 ] [c0 ] [b1 b0 b-1 ⋯ bN-2] [a1 ] [c1 ] [b2 b1 b0 ⋯ bN-3] * [a2 ] = [c2 ] [⋮ ⋮ ⋮ ⋮ ] [⋮ ] [⋮ ] [bN-1 bN-2 bN-3 ⋯ b0 ] [aN-1] [cN-1]
(...,a-1,a0,a1,...) convolution (...,b-1,b0,b1,...) = (...,c-1,c0,c1,...) [ ⋮ ⋮ ⋮ ] [⋮ ] [⋮ ] [⋯ b0 b-1 b-2 ⋯] [a-1] [c-1] [⋯ b1 b0 b-1 ⋯] * [a0 ] = [c0 ] [⋯ b2 b1 b0 ⋯] [a1 ] [c1 ] [ ⋮ ⋮ ⋮ ] [⋮ ] [⋮ ]
摺積運算的單位元素是脈衝函數。摺積運算擁有交換律、結合律、加法分配律、線性。Linear Time-Invariant System皆可寫成摺積。不懂沒關係,本篇用不到。
Z-transform
一串數列做Z-transform,得到一個複數多項式:數列每一項乘上複數z的次方,次方值是負的索引值,最後全部加起來。可以看作是一串數列與一串複數數列進行內積。
z是一個複數,可以代入數值;不過一般都是維持原本外貌,以代數z示人。
Z-transform of (a0,a1,...,aN-1) = a0z0 + a1z-1 + ... + aN-1z-(N-1)
Z-transform of (...,a-1,a0,a1,...) = ... + a-1z1 + a0z0 + a1z-1 + ...
數列摺積與多項式乘法(普通的演算法)
因為電腦無法處理無限長數列,所以此處只討論有限長數列。
兩串數列做Z-transform,變成多項式,然後相乘一下。因為是有限長數列,我們特意讓次方循環。
a0 * z0 + a1 * z-1 + a2 * z-2
×) b0 * z0 + b1 * z-1 + b2 * z-2
——————————————————————————————————————————————————————————————————
a0b2 * z-2 + a1b2 * z-3 + a2b2 * z-4
a0b1 * z-1 + a1b1 * z-2 + a2b1 * z-3
a0b0 * z0 + a1b0 * z-1 + a2b0 * z-2
——————————————————————————————————————————————————————————————————
a1b2 * z0 + a2b2 * z-1 + a0b2 * z-2
a2b1 * z0 + a0b1 * z-1 + a1b1 * z-2
a0b0 * z0 + a1b0 * z-1 + a2b0 * z-2
——————————————————————————————————————————————————————————————————
(a0,a1,a2) (a0,a1,a2) (a0,a1,a2)
dot * z0 + dot * z-1 + dot * z-2
(b0,b2,b1) (b1,b0,b2) (b2,b1,b0)
把相乘結果做逆向Z-transform,竟是原本兩串數列的摺積!
數列摺積就是多項式乘法、多項式乘法就是數列摺積,藉由Z-transform改變計算順序罷了(遮住z不看,就很明顯了)。
數列摺積與多項式乘法的時間複雜度都是O(N^2)。
【註:計算學家重視計算,數列摺積與多項式乘法是主角,Z-transform只是輔助;數學家重視性質,Z-transform才是主角,數列摺積與多項式乘法只是應用。坊間書籍行文風格傾向數學家;書讀不通的時候,不妨揣摩一下數學家的觀點吧。】
數列摺積與多項式乘法(Fourier Transform)
傅立葉轉換其實就是一串數列進行Z-transform,變成複數多項式,z分別代入ei*2π*(0/N)、ei*2π*(1/N)、……、ei*2π*((N-1)/N),求得N個實際數值。
兩個多項式相乘,可以簡化成這2N個數值點對點相乘!
正向與逆向傅立葉轉換需時O(NlogN),N個數值兩兩對應相乘需時O(N),總時間複雜度為O(NlogN)。
證明過程猜測如下:線性變換的本質,是在eigenvector上面進行倍率縮放,摺積也不例外。傅立葉轉換的矩陣,恰好是orthogonal eigenbasis,所以每個維度就可以各自縮放了。
數論轉換也有著相同的性質,哈特利轉換則有著類似的性質。
UVa 12298 ICPC 4671 5705
數列摺積與多項式乘法──循環的Toeplitz Matrix的乘法
循環的Toeplitz Matrix乘以向量,就是循環摺積。得運用傅立葉轉換、數論轉換。
一般的Toeplitz Matrix乘以向量,只要填充一下元素,也能變成循環的Toeplitz Matrix乘以向量。
數列摺積與多項式乘法──大數乘法
大數乘法是直式乘法,效果同多項式乘法、同數列摺積。得運用傅立葉轉換、數論轉換。
大數乘法的演算法有Schönhage-Strassen Algorithm與Fürer's Algorithm,事實上只是套用不同的數論轉換演算法而已。
注意到,此處談論的多項式乘法,次方是循環的;然而大數乘法,位數並不會循環。解決方式是:被乘數陣列與乘數陣列,預先增加長度,務必大於等於乘積陣列,如此就不受循環影響。
時域摺積等於頻域乘法,頻域摺積等於時域乘法。
傅立葉轉換的輸入稱做「時域」、輸出稱作「頻域」,呼應傅立葉轉換的功能──把波(時間軸)表示成頻譜(頻率軸)。
前面段落所講的是時域摺積等於頻域乘法,事實上頻域摺積也等於時域乘法。
Wavelet【尚未理解,歡迎討論】
程度★★ 難度★
Wavelet Transform【尚未理解,歡迎討論】
程度★★ 難度★★