MN10: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Przykry (dyskusja | edycje)
mNie podano opisu zmian
m Zastępowanie tekstu – „,↵</math>” na „</math>,”
 
(Nie pokazano 12 wersji utworzonych przez 3 użytkowników)
Linia 1: Linia 1:


<!--  
<!--  
Konwertowane  z pliku LaTeX przez latex2mediawiki, zob. http://www.ii.uj.edu.pl/&nbsp;pawlik1/latex2mediawiki.php
Konwertowane  z pliku LaTeX przez latex2mediawiki, zob. http://www.ii.uj.edu.pl/&nbsp;pawlik1/latex2mediawiki.php.
Niezb�dne rozszerzenia i modyfikacje oryginalnego latex2mediawiki
wprowadzi� przykry@mimuw.edu.pl
-->
-->
   
   
=Szybka transformacja Fouriera (FFT)=
=Szybka transformacja Fouriera (FFT)=


Algorytm FFT dla dyskretnej transformacji Fouriera (DFT) i jego krewniacy dla
{{powrot |Metody numeryczne | do strony głównej
wyznaczania dyskretnej transformacji cosinusów (DCT i MDCT), choć dotyczą
przedmiotu <strong>Metody numeryczne</strong>}}
 
Algorytm FFT (''Fast Fourier Transform'') dla dyskretnej transformacji Fouriera (DFT, ''Discrete Fourier Transform'') i jego krewniacy dla
wyznaczania dyskretnej transformacji cosinusów (DCT, ''Discrete Cosine Transform'' i MDCT, ''Modified Discrete Cosine Transform''), choć dotyczą
pozornie dość abstrakcyjnych zadań, zrewolucjonizowały wiele dziedzin życia.
pozornie dość abstrakcyjnych zadań, zrewolucjonizowały wiele dziedzin życia.
Między innymi, wykorzystuje się je w
Między innymi wykorzystuje się je w
* kompresji obrazów w formacie JPEG (DCT)
* kompresji obrazów w formacie JPEG (DCT)
* kompresji dźwięku w formacie MP3 i pokrewnych (MDCT)
* kompresji dźwięku w formacie MP3 i pokrewnych (MDCT)
* rozwiązywaniu ważnych równań różniczkowych cząstkowych (DFT)
* rozwiązywaniu ważnych równań różniczkowych cząstkowych (DFT)
   
   
[[Image:XXX.png|thumb|400px||Odtwarzacz MP3]]
a także do  
a także do  
* fitrowania szumów (DFT)
* filtrowania szumów (DFT)
* [[Zaawansowane_algorytmy_i_struktury_danych/Wykład_4|szybkiego mnożenia wielomianów]] (DFT)
* [[Zaawansowane_algorytmy_i_struktury_danych/Wykład_4#Mnożenie wielomianów w punktach|szybkiego mnożenia wielomianów]] (DFT)
   
   
W niniejszym wykładzie ograniczymy się do przedstawienia szybkiego algorytmu
W niniejszym wykładzie ograniczymy się do przedstawienia szybkiego algorytmu
rozwiązywania zadania DFT, algorytmy rozwiązywania zadań pokrewnych (DCT, MDCT,
rozwiązywania zadania DFT. Algorytmy rozwiązywania zadań pokrewnych (DCT, MDCT,
itp.) opierają się na podobnych zasadach.
itp.) opierają się na podobnych zasadach.


Zacznijmy od postawienia zadania obliczeniowego DFT. Jest ono (pozornie!)
Zacznijmy od postawienia zadania obliczeniowego DFT. Jest ono (pozornie!)
jednocześnie banalne i wydumane:
banalne i jednocześnie wydumane:


Dla danego zestawu <math>\displaystyle N</math> liczb <math>\displaystyle f_\alpha\in C</math>, <math>\displaystyle \alpha = 0,\ldots, N-1</math>,
Dla danego zestawu <math>N</math> liczb <math>f_\alpha\in C</math>, <math>\alpha = 0,\ldots, N-1</math>,
wyznaczyć  <math>\displaystyle N</math> wartości
wyznaczyć  <math>N</math> wartości


<center><math>\displaystyle c_j = \sum_{\alpha = 0}^{N-1} f_\alpha \omega_N^{j\alpha},
<center><math>c_j = \sum_{\alpha = 0}^{N-1} f_\alpha \omega_N^{j\alpha}</math>,</center>
</math></center>


dla <math>\displaystyle j=0,\ldots, N-1</math>, przy czym <math>\displaystyle \omega_N = \exp(-i\frac{2\Pi}{N})</math>. Jak
dla <math>j=0,\ldots, N-1</math>, przy czym <math>\omega_N = \exp(-i\frac{2\Pi}{N})</math>. Jak
pamiętamy, jednostka urojona <math>\displaystyle i</math> spełnia <math>\displaystyle i = \sqrt{-1}</math>. Taką operację nazywamy
pamiętamy, jednostka urojona <math>i</math> spełnia <math>i = \sqrt{-1}</math>. Taką operację nazywamy
<strong>dyskretną transformacją Fouriera</strong>, DFT.
<strong>dyskretną transformacją Fouriera</strong>, DFT.


[[grafika:Fourier.jpg|thumb|right||Jean Baptiste Joseph Fourier<br>  [[Biografia Fourier|Zobacz biografię]]]]
[[grafika:Fourier.jpg|thumb|right||Jean Baptiste Joseph Fourier<br>  [[Biografia Fourier|Zobacz biografię]]]]


Ponieważ dane <math>\displaystyle f</math> są wektorem <math>\displaystyle f = [f_0,\ldots,f_{N-1}]^T</math>, wynik <math>\displaystyle c</math> też jest wektorem, a zadanie jest liniowe,
Ponieważ dane <math>f</math> są wektorem <math>f = [f_0,\ldots,f_{N-1}]^T</math>, wynik <math>c</math> też jest wektorem, a zadanie jest liniowe,
możemy wszystko zapisać macierzowo:
możemy wszystko zapisać macierzowo:


<center><math>\displaystyle c = F_N f,
<center><math>c = F_N f</math>,</center>
</math></center>


gdzie
gdzie


<center><math>\displaystyle
<center><math>
F_N = (\omega_N^{j\alpha})_{j,\alpha = 0,\ldots, N-1} =  
F_N = (\omega_N^{j\alpha})_{j,\alpha = 0,\ldots, N-1} =  
\begin{pmatrix}  
\begin{pmatrix}  
Linia 55: Linia 56:
   &            &            & \cdots & \\
   &            &            & \cdots & \\
1 & \omega_N^{N-1} & \omega_N^{2(N_1)} & \cdots & \omega_N^{(N-1)^2}  
1 & \omega_N^{N-1} & \omega_N^{2(N_1)} & \cdots & \omega_N^{(N-1)^2}  
\end{pmatrix} .
\end{pmatrix} </math></center>
</math></center>


Tak więc, gdyby naiwnie podejść do zadania, moglibyśmy je rozwiązać brutalnie,
Tak więc, gdyby naiwnie podejść do zadania, moglibyśmy je rozwiązać brutalnie,
tworząc na początek macierz <math>\displaystyle F_N</math>, a następnie wyznaczając iloczyn macierzy
tworząc na początek macierz <math>F_N</math>, a następnie wyznaczając iloczyn macierzy
przez wektor, co łatwo zrobić kosztem <math>\displaystyle O(N^2)</math> operacji. Dlatego istotne jest,
przez wektor, co łatwo zrobić kosztem <math>O(N^2)</math> operacji. Dlatego istotne jest,
że algorytm FFT, który za chwilę omówimy, będzie działać kosztem <math>\displaystyle O(N\log\,N)</math>,
że algorytm FFT, który za chwilę omówimy, będzie działać kosztem <math>O(N\log\,N)</math>,
czyli praktycznie liniowym (w obu przypadkach stałe proporcjonalności są równe
czyli praktycznie liniowym (w obu przypadkach stałe proporcjonalności są niewielkie) w dodatku będzie miał znacznie mniejsze wymagania pamięciowe.  
2) w dodatku będzie miał znacznie mniejsze wymagania pamięciowe. Już nawet gdy
 
<math>\displaystyle N=8</math> (przypadek kompresji JPEG), mamy <math>\displaystyle N^2 = 512</math>, gdy tymczasem  <math>\displaystyle N\log\,N =
==Algorytm FFT==
24</math>, więc zysk jest ponaddwudziestokrotny, co ma kapitalne znaczenie, bo zamiast
 
czekać na zapis zdjęcia (powiedzmy) pół sekundy, czekalibyśmy długie 10 (sic!)
Aby móc taniej mnożyć przez <math>F_N</math>, musimy odkryć kilka bardzo szczególnych własności tej macierzy.
sekund... albo nasza(?) cyfrówka kosztowałaby majątek...


{{fakt|||
{{fakt|||
Macierz <math>\displaystyle F</math> jest symetryczna oraz <math>\displaystyle \frac{1}{N} F_N^* F_N = I</math>.
Macierz <math>F</math> jest symetryczna oraz <math>\frac{1}{N} F_N^* F_N = I</math>.
}}
}}


==Algorytm FFT==
{{dowod|||
Dowód pozostawiamy jako [[MN10LAB|ćwiczenie]].
}}


Zauważmy, że nasza macierz ma jeszcze więcej specjalnej struktury, powtarza się
Zauważmy, że nasza macierz ma jeszcze więcej specjalnej struktury, powtarza się
w niej bardzo wiele takich samych współczynników (sprawdź dla <math>\displaystyle N=4</math>, w ogólności
w niej bardzo wiele takich samych współczynników (sprawdź dla <math>N=4</math>, w ogólności
ma tylko <math>\displaystyle N</math> różnych wyrazów), gdyż <math>\displaystyle \omega_N^N = 1</math> (dla
<math>F_N</math> ma tylko <math>N</math> różnych wyrazów), gdyż <math>\omega_N^N = 1</math> (dla
<math>\displaystyle j=0,\ldots,N-1</math>, <math>\displaystyle \omega_N^j</math>  to nic innego jak wszystkie zespolone pierwiastki z jedynki), a
<math>j=0,\ldots,N-1</math>, <math>\omega_N^j</math>  to nic innego jak kolejne zespolone pierwiastki z jedynki).
jak wiadomo, mnożenie przez 1 nic nie kosztuje --- o ile tylko się wie, że <strong>gdzie</strong> jest ta jedynka. Z grubsza na tym właśnie będzie polegać siła algorytmu
FFT!


W wyprowadzeniu algorytmu szybkiej transformacji Fouriera, ''Fast Fourier
W wyprowadzeniu algorytmu szybkiej transformacji Fouriera  
Transform, FFT ''oprzemy się poraz kolejny na regule "dziel i rządź". Dla
(''Fast Fourier Transform, FFT'') oprzemy się po raz kolejny na regule ''"dziel i rządź"''. Dla uproszczenia analizy przyjmiemy, że <math>N</math> jest naturalną potęgą dwójki, w szczególności <math>N = 2m</math> dla pewnego naturalnego <math>m</math>.
uproszczenia analizy przyjmiemy, że <math>\displaystyle N</math> jest naturalną potęgą dwójki, w
szczególności <math>\displaystyle N = 2m</math> dla pewnego naturalnego <math>\displaystyle m</math>.


Rzeczywiście, rozbijając naszą sumę na sumę po indeksach parzystych i
Rzeczywiście, rozbijając naszą sumę na sumę po indeksach parzystych i
sumę  po indeksach nieparzystych, mamy
sumę  po indeksach nieparzystych, mamy


<center><math>\displaystyle \aligned c_j &= f_0 \omega_N^{0\cdot j} + f_2 \omega_N^{2\cdot j} + \ldots + f_{N-2}
<center><math>\begin{align} c_j &= f_0 \omega_N^{0\cdot j} + f_2 \omega_N^{2\cdot j} + \ldots + f_{N-2}
\omega_N^{(N-2)\cdot j}\\
\omega_N^{(N-2)\cdot j}\\
&& + f_1 \omega_N^{1\cdot j} + f_3 \omega_N^{3\cdot j} + \ldots + f_{N-1}
&& + f_1 \omega_N^{1\cdot j} + f_3 \omega_N^{3\cdot j} + \ldots + f_{N-1}
\omega_N^{(N-1)\cdot j}.
\omega_N^{(N-1)\cdot j}.
\endaligned</math></center>
\end{align}</math></center>


Suma po indeksach parzystych da się zapisać w postaci
Suma po indeksach parzystych da się zapisać w postaci


<center><math>\displaystyle
<center><math>
\sum_{k=0}^{m-1} f_{2k} (\omega_m) ^ {jk}
\sum_{k=0}^{m-1} f_{2k} (\omega_m) ^ {jk}
</math></center>
</math></center>
Linia 104: Linia 101:
i analogicznie suma po indeksach nieparzystych da się zapisać  
i analogicznie suma po indeksach nieparzystych da się zapisać  


<center><math>\displaystyle
<center><math>
\omega_N^j\cdot \sum_{k=0}^{m-1} f_{2k+1} (\omega_m) ^ {jk}
\omega_N^j\cdot \sum_{k=0}^{m-1} f_{2k+1} (\omega_m) ^ {jk}
</math></center>
</math></center>


gdyż <math>\displaystyle \omega_N^2 = \omega_m</math>, a więc wygląda na to, że nasze zadanie wyznaczenia
gdyż <math>\omega_N^2 = \omega_m</math>, a więc wygląda na to, że nasze zadanie wyznaczenia
dyskretnej transformacji Fouriera wymiaru <math>\displaystyle N</math> da się sprowadzić do analogicznych zadań mniejszego
dyskretnej transformacji Fouriera wymiaru <math>N</math> da się sprowadzić do analogicznych zadań mniejszego
rozmiaru.
rozmiaru.


Rzeczywiście, korzystając z tego, że <math>\displaystyle j = \beta m + \widetilde{j}</math>, gdzie <math>\displaystyle \widetilde{j}
Rzeczywiście, korzystając z tego, że <math>j = \beta m + \widetilde{j}</math>, gdzie <math>\widetilde{j}
= 0,\ldots, m-1</math>, oraz <math>\displaystyle \beta = 0</math> lub <math>\displaystyle 1</math>, mamy <math>\displaystyle \omega_m^j =
= 0,\ldots, m-1</math>, oraz <math>\beta = 0</math> lub <math>1</math>, mamy <math>\omega_m^j =
\omega_m^{\widetilde{j}}</math>.
\omega_m^{\widetilde{j}}</math>.


Oznaczając  
Oznaczając  


<center><math>\displaystyle \aligned \Phi_{\widetilde{j}} &= \sum_{k=0}^{m-1} f_{2k} \omega_m^{\widetilde{j}k}, \qquad
<center><math>\begin{align} \Phi_{\widetilde{j}} &= \sum_{k=0}^{m-1} f_{2k} \omega_m^{\widetilde{j}k}, \qquad
\widetilde{j} = 0,\ldots, m-1,\\
\widetilde{j} = 0,\ldots, m-1,\\
\Psi_{\widetilde{j}} &= \sum_{k=0}^{m-1} f_{2k+1} \omega_m^{\widetilde{j}k},  
\Psi_{\widetilde{j}} &= \sum_{k=0}^{m-1} f_{2k+1} \omega_m^{\widetilde{j}k},  
\endaligned</math></center>
\end{align}</math></center>


(jak widać są to DFT dla dwa razy krótszych wektorów, złożonych z tylko
(jak widać są to DFT dla dwa razy krótszych wektorów, złożonych z tylko
parzystych lub tylko nieparzystych współrzędnych <math>\displaystyle f</math>) dostajemy ostatecznie
parzystych lub tylko nieparzystych współrzędnych <math>f</math>), dostajemy ostatecznie


<center><math>\displaystyle \aligned c_{\widetilde{j}} = \Phi_{\widetilde{j}} + \omega_N^{\widetilde{j}} \Psi_{\widetilde{j}},\\
<center><math>\begin{align} c_{\widetilde{j}} = \Phi_{\widetilde{j}} + \omega_N^{\widetilde{j}} \Psi_{\widetilde{j}},\\
c_{\widetilde{j}+m} = \Phi_{\widetilde{j}} - \omega_N^{\widetilde{j}} \Psi_{\widetilde{j}}.
c_{\widetilde{j}+m} = \Phi_{\widetilde{j}} - \omega_N^{\widetilde{j}} \Psi_{\widetilde{j}}.
\endaligned</math></center>
\end{align}</math></center>


Tym samym, wyznaczenie DFT wymiaru <math>\displaystyle N</math> udało się rzeczywiście sprowadzić
Tym samym, wyznaczenie DFT wymiaru <math>N</math> udało się rzeczywiście sprowadzić
do wyznaczenia dwóch DFT wymiaru <math>\displaystyle m = N/2</math> oraz drobnej manipulacji na
do wyznaczenia dwóch DFT wymiaru <math>m = N/2</math> oraz drobnej manipulacji na
ich wynikach zgodnie z powyższym wzorem. Oczywiście, te mniejsze transformacje
ich wynikach zgodnie z powyższym wzorem. Oczywiście, te mniejsze transformacje
można wyznaczyć takim samym sposobem, co prowadzi do zależności rekurencyjnej,
można wyznaczyć takim samym sposobem, co prowadzi do zależności rekurencyjnej,
Linia 138: Linia 135:


Proste sprawdzenie pokazuje, że koszt takiego algorytmu jest rzędu
Proste sprawdzenie pokazuje, że koszt takiego algorytmu jest rzędu
<math>\displaystyle O(N\log\,N)</math>, a nie <math>\displaystyle O(N^2)</math>, jak dla naiwnego algorytmu mnożenia wektora przez
<math>O(N\log\,N)</math>, a nie <math>O(N^2)</math>, jak dla naiwnego algorytmu mnożenia wektora przez
gęstą macierz. Zysk jest więc, nawet dla niewielkich <math>\displaystyle N</math>, istotny.
gęstą macierz. Zysk jest więc, nawet dla niewielkich <math>N</math>, istotny.
 
{{algorytm|Prosta wersja algorytmu FFT||
<pre>


function y = fft(x)
{{algorytm|Prosta wersja algorytmu FFT|Prosta wersja algorytmu FFT|
<Source>function y = fft(x)
N = length(f);
N = length(f);
if N == 1  
if N == 1  
y = x;
y = f;
else
else
<math>\displaystyle \omega</math> = <math>\displaystyle \exp(-\frac{2\Pi}{N}i)</math>;
<math>\omega</math> = <math>\exp(-\frac{2\Pi}{N}i)</math>;
<math>\displaystyle \omega_k</math> = <math>\displaystyle \omega^{N/2-1}</math>;
<math>\omega_k</math> = <math>\omega^{N/2-1}</math>;
u = fft( f[0:2:N-2] );
u = fft( f[0:2:N-2] );
v = fft( f[1:2:N-1] );
v = fft( f[1:2:N-1] );
v = v * <math>\displaystyle \omega_k</math>;
v = v * <math>\omega_k</math>;
y = [ u+v ; u-v ];
y = [ u+v ; u-v ];
end
end
end
end
</pre>}}
</Source>}}


Jak już zdążyliśmy się przyzwyczaić, w algorytmach numerycznych unikamy
Jak już zdążyliśmy się przyzwyczaić, gdy tylko to możliwe, w algorytmach numerycznych unikamy stosowania jawnej rekurencji. W przypadku FFT można jej
stosowania jawnej rekurencji, gdy tylko to możliwe. W przypadku FFT można jej
również uniknąć, wyznaczając zawczasu --- korzystając z tzw. odwrócenia bitów
również uniknąć, wyznaczając zawczasu --- korzystając z tzw. odwrócenia bitów
--- porządek, w którym należy składać 1-wymiarowe DFT w coraz dłuższe wektory
--- porządek, w którym należy składać 1-wymiarowe DFT w coraz dłuższe wektory
zgodnie ze wzorem powyżej tak, by na końcu dostać pożądany wektor <math>\displaystyle c = F_Nf</math>.
zgodnie ze wzorem powyżej tak, by na końcu dostać pożądany wektor <math>c = F_Nf</math>.


Ponadto, istnieją warianty algorytmu FFT, które np. działają na danych
Ponadto, istnieją warianty algorytmu FFT, które np. działają na danych
rzeczywistych. Na analogicznych zasadach co FFT oparte są również algorytmy
rzeczywistych. Na analogicznych zasadach co FFT oparte są również algorytmy
wykonujące tzw. dyskretną transformację cosinusów (DCT) i jej wariant MDCT
wykonujące tzw. dyskretną transformację cosinusów (DCT) i jej wariant MDCT
stosowany w kodekach audio takich jak MP3, AAC, czy [http://www.xiph.org/ogg  OggVorbis].  
stosowany w kodekach audio takich jak MP3, AAC, czy [http://www.xiph.org/ogg  OggVorbis].


==Interpolacja trygonometryczna==
==Interpolacja trygonometryczna==


Jednym z wielu zadań, w których daje się zastosować algorytm FFT, jest zadanie
Jednym z wielu zadań, w których daje się zastosować algorytm FFT, jest zadanie
interpolacji trygonometrycznej:  
<strong>interpolacji trygonometrycznej</strong>:  


Dla danych węzłów <math>\displaystyle x_k = \frac{2\pi k }{N}</math>, <math>\displaystyle k = 0,\ldots,N-1</math>, znaleźć
Dla danych węzłów <math>x_k = \frac{2\pi k }{N}</math>, <math>k = 0,\ldots,N-1</math>, znaleźć
wielomian <math>\displaystyle P</math> (zmiennej rzeczywistej, o wartościach zespolonych) stopnia <math>\displaystyle \leq N-1</math> postaci
wielomian <math>P</math> (zmiennej rzeczywistej, o wartościach zespolonych) stopnia <math>\leq N-1</math> postaci
<center><math>\displaystyle
<center><math>
P(x) = \sum_{k=0}^{N-1} \beta_k \bar{\omega}^k,
P(x) = \sum_{k=0}^{N-1} \beta_k \bar{\omega}^k</math>,</center>
</math></center>


gdzie <math>\displaystyle \bar{\omega} = e^{ix} = \cos(x) + i\, \sin(x)</math> (stąd nazwa: trygonometryczny)
gdzie <math>\bar{\omega} = e^{ix} = \cos(x) + i\, \sin(x)</math> (stąd nazwa: trygonometryczny)
taki, że  
taki, że  
<center><math>\displaystyle
<center><math>
P(x_k) = y_k, \qquad k = 0,\ldots,N-1,
P(x_k) = y_k, \qquad k = 0,\ldots,N-1</math>,</center>
</math></center>


dla zadanych wartości <math>\displaystyle y_k</math>.  
dla zadanych wartości <math>y_k</math>.  


W języku macierzy DFT możemy zapisać zadanie interpolacji trygonometrycznej jako
W języku macierzy DFT możemy zapisać zadanie interpolacji trygonometrycznej jako
zadanie wyznaczania wektora <math>\displaystyle \beta</math> takiego, że  
zadanie wyznaczania wektora <math>\beta</math> takiego, że  
<center><math>\displaystyle
<center><math>
\bar{F_N}\beta  = y
\overline{F_N}\beta  = y
</math></center>
</math></center>


dla zadanego wektora <math>\displaystyle y</math>.
dla zadanego wektora <math>y</math>.
 
{{twierdzenie|O współczynnikach wielomianu interpolacji trygonometrycznej|O współczynnikach wielomianu interpolacji trygonometrycznej|


{{twierdzenie|||
Współczynniki <math>\beta = [\beta_0,\ldots,\beta_{N-1}]^T</math> poszukiwanego wielomianu
Współczynniki <math>\displaystyle \beta = [\beta_0,\ldots,\beta_{N-1}]^T</math> poszukiwanego wielomianu
trygonometrycznego wyrażają się wzorem
trygonometrycznego wyrażają się wzorem
<center><math>\displaystyle
<center><math>
\beta = \frac{1}{N} F_N y.
\beta = \frac{1}{N} F_N y</math></center>
</math></center>


}}
}}


{{dowod|||
{{dowod|||
Jak wiemy, <math>\displaystyle \frac{1}{N} F_N^* F_N = I</math>, więc, z symetrii macierzy <math>\displaystyle F_N</math> wynika,
Jak wiemy, <math>\frac{1}{N} F_N^* F_N = I</math>, z symetrii macierzy <math>F_N</math> wynika więc,  
że <math>\displaystyle (\bar{F_N})^{-1} = \frac{1}{N} F_N</math>.
że <math>(\overline{F_N})^{-1} = \frac{1}{N} F_N</math>.
}}
}}


Można pokazać, że  gdy dane są rzeczywiste, zadanie interpolacji
Można pokazać, że  gdy dane są rzeczywiste, zadanie interpolacji
trygonometrycznej można wyrazić
trygonometrycznej możemy wyrazić
korzystając wyłącznie z liczb rzeczywistych.
korzystając wyłącznie z liczb rzeczywistych.
Jeśli <math>\displaystyle y_k\in R</math>, <math>\displaystyle k = 0,\ldots,N-1</math>, to wtedy (rzeczywisty) wielomian trygonometryczny
Jeśli <math>y_k\in R</math>, <math>k = 0,\ldots,N-1</math>, to wtedy (rzeczywisty) wielomian trygonometryczny
<center><math>\displaystyle
<center><math>
p(x) = \sum_{k=0}^{N-1} \left( a_k \cos(\frac{2k\pi}{N} x) - b_k \sin(\frac{2k\pi}{N} x)\right),
p(x) = \sum_{k=0}^{N-1} \left( a_k \cos(\frac{2k\pi}{N} x) - b_k \sin(\frac{2k\pi}{N} x)\right)</math>,</center>
</math></center>


gdzie <math>\displaystyle a_k = Re(\beta_k)</math>, <math>\displaystyle b_k = Im(\beta_k)</math>, interpoluje <math>\displaystyle y_k</math> w węzłach
gdzie <math>a_k = Re(\beta_k)</math>, <math>b_k = Im(\beta_k)</math>, interpoluje <math>y_k</math> w węzłach
<math>\displaystyle x_k</math>. Oczywiście, powyższa formuła w rzeczywistości ma o połowę mniej wyrazów,
<math>x_k</math>. Oczywiście, powyższa formuła w rzeczywistości ma o połowę mniej wyrazów,
ze względu na własności funkcji trygonometrycznych.
ze względu na własności funkcji trygonometrycznych.


Linia 229: Linia 220:


W niektórych zastosowaniach potrzebne jest wyznaczenie <strong>splotu</strong> dwóch
W niektórych zastosowaniach potrzebne jest wyznaczenie <strong>splotu</strong> dwóch
wektorów, to znaczy, wyznaczenie wyrażeń postaci
wektorów, to znaczy wyznaczenie wyrażeń postaci
<center><math>\displaystyle
<center><math>
z_k = \sum_{j=0}^{N-1} u_j v_{k-j}, \qquad k = 0, \ldots, N-1.
z_k = \sum_{j=0}^{N-1} u_j v_{k-j}, \qquad k = 0, \ldots, N-1</math></center>
</math></center>
(przyjmujemy tu konwencję, że wektory rozszerzamy <math>N</math>-periodyczne tak, że z definicji <math>v_{j+kN} = v_j</math> dla dowolnych całkowitych <math>j, k</math>).


Zapisując to macierzowo, szukamy iloczynu wektora <math>\displaystyle u</math> z cykliczną macierzą Toeplitza
Zapisując to macierzowo, szukamy iloczynu wektora <math>u</math> z cykliczną macierzą Toeplitza
(macierz Toeplitza ma stałe wyrazy wzdłuż diagonali) wyznaczoną przez <math>\displaystyle v</math>,
(macierz Toeplitza ma stałe wyrazy wzdłuż diagonali) wyznaczoną przez <math>v</math>,
<center><math>\displaystyle
<center><math>
z =  
z =  
\begin{pmatrix}  
\begin{pmatrix}  
Linia 243: Linia 234:
  \vdots  & \ddots & \ddots & \vdots \\
  \vdots  & \ddots & \ddots & \vdots \\
  v_1    &  \cdots    &  v_{N-1} & v_0
  v_1    &  \cdots    &  v_{N-1} & v_0
\end{pmatrix}  \cdot \begin{pmatrix}  u_0\\u_1\\\vdots\\u_{N-1}\end{pmatrix} ,
\end{pmatrix}  \cdot \begin{pmatrix}  u_0\\u_1\\\vdots\\u_{N-1}\end{pmatrix} .
</math></center>
</math></center>


tak więc pozornie zadanie wyznaczenia splotu powinno kosztować tyle, co
Mogłoby się zdawać, że zadanie wyznaczenia splotu powinno kosztować tyle, co
mnożenie macierzy przez wektor, a więc <math>\displaystyle O(N^2)</math> operacji. Tymczasem
mnożenie macierzy przez wektor, a więc <math>O(N^2)</math> operacji. Tymczasem
prosty rachunek pozwala sprawdzić, że odpowiednie transformacje Fouriera,
prosty rachunek pozwala sprawdzić, że odpowiednie transformacje Fouriera,
<math>\displaystyle \hat{z} = F_N z</math>, <math>\displaystyle \hat{u} = F_N u</math>, <math>\displaystyle \hat{v} = F_N v</math>  
<math>\hat{z} = F_N z</math>, <math>\hat{u} = F_N u</math>, <math>\hat{v} = F_N v</math>  
spełniają równanie z macierzą diagonalną!
spełniają równanie z macierzą diagonalną!


<center><math>\displaystyle
<center><math>
\hat{z} = N \cdot \begin{pmatrix} \hat{v}_0 & & \\
\hat{z} = N \cdot \begin{pmatrix} \hat{v}_0 & & \\
  & \ddots & \\
  & \ddots & \\
Linia 259: Linia 250:
  \vdots  \\
  \vdots  \\
  \hat{v}_{N-1}\hat{u}_{N-1}
  \hat{v}_{N-1}\hat{u}_{N-1}
\end{pmatrix} ,
\end{pmatrix} </math>,</center>
</math></center>


a to mnożenie daje się wykonać kosztem liniowym, tym samym całe zadanie daje się
a to mnożenie daje się wykonać kosztem liniowym, tym samym całe zadanie daje się
policzyć kosztem <math>\displaystyle O(N\log\,N)</math>.
policzyć kosztem <math>O(N\log\,N)</math>.


==Biblioteki==
==Biblioteki==


Najpopularniejszą obecnie biblioteką implementującą algorytm FFT dla  DFT, DCT i
Najpopularniejszą obecnie biblioteką implementującą algorytm FFT dla  DFT, DCT i
innych pokrewnych (bez ograniczenia, że  wymiar jest naturalną potęgą dwójki),
innych pokrewnych (bez ograniczenia, że  wymiar jest naturalną potęgą dwójki), jest biblioteka o niezbyt skromnie brzmiącej nazwie [http://www.fftw.org  FFTW] (''The Fastest Fourier Transform in the West''). Z tej biblioteki korzystają m.in. funkcje MATLABa i Octave'a <code style="color: #006">fft</code> oraz <code style="color: #006">ifft</code> dla transformacji DFT (to znaczy mnożenia przez <math>F_N</math>) i, odpowiednio, transformacji odwrotnej, <math>\frac{1}{N}\overline{F}_N</math>.
jest [http://www.fftw.org  FFTW] której nazwa jest skrótowcem od  niezbyt
skromnie brzmiącego ang. The Fastest Fourier Transform in the West. Z tej
biblioteki korzystają m.in. funkcje MATLABa i Octave'a <code>fft</code> oraz
<code>ifft</code> dla transformacji DFT (to znaczy mnożenia przez <math>\displaystyle F_N</math>) i,
odpowiednio, transformacji odwrotnej, <math>\displaystyle \frac{1}{N}\bar{F}_N</math>.


FFTW jest napisana w C i w dużym stopniu wykorzystuje możliwości współczesnych
FFTW jest napisana w C i w dużym stopniu wykorzystuje możliwości współczesnych
procesorów, takie jak potokowanie i instrukcje wektorowe SSE2 i SSE3. Poniżej
procesorów, takie jak potokowanie i instrukcje wektorowe SSE2 i SSE3. Poniżej
pokazujemy przykładowy prościutki kod w C, realizujący DFT na pojedynczym
pokazujemy przykładowy prościutki kod w C realizujący DFT na pojedynczym
wektorze zespolonym.
wektorze zespolonym.


<div class="code" style="background-color:#e8e8e8; padding:1em"><pre>
<Source>/* Kompilacja:
/* Kompilacja:
gcc -o dft dft.c -lfftw3 -lm */
gcc -o dft dft.c -lfftw3 -lm */
#include <complex.h> /* rozszerzenie GCC dające operacje na typach zespolonych */
#include <complex.h> /* rozszerzenie GCC dające operacje na typach zespolonych */
Linia 313: Linia 296:
for( i = 0; i < N; i++ )
for( i = 0; i < N; i++ )
{
{
printf("F[&#37;d] = &#37;8.3lf + &#37;8.3lfi \PIPEREAD  C[&#37;d] = &#37;8.3lf + &#37;8.3lfi\n",  
printf("F[%d] = %8.3lf + %8.3lfi | C[%d] = %8.3lf + %8.3lfi\n",  
i, creal(F[i]), cimag(F[i]), i, creal(C[i]), cimag(C[i]));  
i, creal(F[i]), cimag(F[i]), i, creal(C[i]), cimag(C[i]));  
}
}
Linia 328: Linia 311:
return(0);
return(0);
}
}
</pre></div>
</Source>
   
   
Zwrócmy uwagę na linię <code>Plan=fftw_plan_dft_1d(...)</code>. To tutaj dokonywane są
Zwrócmy uwagę na linię <code>Plan=fftw_plan_dft_1d(...)</code>. To tutaj dokonywane są
ustalenia, w jakiej kolejności mają być prowadzone obliczenia. Jest to operacja
ustalenia, w jakiej kolejności mają być prowadzone obliczenia. Jest to operacja
dość kosztowna, dlatego jeśli mamy wyznaczyć wiele takich samych DFT, tylko na
dość kosztowna, dlatego jeśli mamy wyznaczyć wiele takich samych DFT, ale na
różnych danych, należy przed pierwszą DFT taki plan zachować, a potem już gotowy
różnych danych <math>f</math>, należy przed pierwszą DFT taki plan zachować, a potem, aplikując DFT dla następnych danych, wykorzystać gotowy.
wykorzystać, gdy chcemy aplikować DFT dla następnych danych.
 
==Literatura==
 
W celu dogłębnego zapoznania się z omawianym na wykładzie materiałem, przeczytaj <b>rozdział 6.12--6.13</b> w
* D. Kincaid, W. Cheney <cite>Analiza numeryczna</cite>, Wydawnictwa Naukowo-Techniczne, Warszawa 2006, ISBN 83-204-3078-X.

Aktualna wersja na dzień 21:51, 11 wrz 2023


Szybka transformacja Fouriera (FFT)

<<< Powrót do strony głównej przedmiotu Metody numeryczne

Algorytm FFT (Fast Fourier Transform) dla dyskretnej transformacji Fouriera (DFT, Discrete Fourier Transform) i jego krewniacy dla wyznaczania dyskretnej transformacji cosinusów (DCT, Discrete Cosine Transform i MDCT, Modified Discrete Cosine Transform), choć dotyczą pozornie dość abstrakcyjnych zadań, zrewolucjonizowały wiele dziedzin życia. Między innymi wykorzystuje się je w

  • kompresji obrazów w formacie JPEG (DCT)
  • kompresji dźwięku w formacie MP3 i pokrewnych (MDCT)
  • rozwiązywaniu ważnych równań różniczkowych cząstkowych (DFT)

a także do

W niniejszym wykładzie ograniczymy się do przedstawienia szybkiego algorytmu rozwiązywania zadania DFT. Algorytmy rozwiązywania zadań pokrewnych (DCT, MDCT, itp.) opierają się na podobnych zasadach.

Zacznijmy od postawienia zadania obliczeniowego DFT. Jest ono (pozornie!) banalne i jednocześnie wydumane:

Dla danego zestawu N liczb fαC, α=0,,N1, wyznaczyć N wartości

cj=α=0N1fαωNjα,

dla j=0,,N1, przy czym ωN=exp(i2ΠN). Jak pamiętamy, jednostka urojona i spełnia i=1. Taką operację nazywamy dyskretną transformacją Fouriera, DFT.

Jean Baptiste Joseph Fourier
Zobacz biografię

Ponieważ dane f są wektorem f=[f0,,fN1]T, wynik c też jest wektorem, a zadanie jest liniowe, możemy wszystko zapisać macierzowo:

c=FNf,

gdzie

FN=(ωNjα)j,α=0,,N1=(11111ωNωN2ωNN11ωN2ωN4ωN2(N1)1ωNN1ωN2(N1)ωN(N1)2)

Tak więc, gdyby naiwnie podejść do zadania, moglibyśmy je rozwiązać brutalnie, tworząc na początek macierz FN, a następnie wyznaczając iloczyn macierzy przez wektor, co łatwo zrobić kosztem O(N2) operacji. Dlatego istotne jest, że algorytm FFT, który za chwilę omówimy, będzie działać kosztem O(NlogN), czyli praktycznie liniowym (w obu przypadkach stałe proporcjonalności są niewielkie) w dodatku będzie miał znacznie mniejsze wymagania pamięciowe.

Algorytm FFT

Aby móc taniej mnożyć przez FN, musimy odkryć kilka bardzo szczególnych własności tej macierzy.

Fakt

Macierz F jest symetryczna oraz 1NFN*FN=I.

Dowód

Dowód pozostawiamy jako ćwiczenie.

Zauważmy, że nasza macierz ma jeszcze więcej specjalnej struktury, powtarza się w niej bardzo wiele takich samych współczynników (sprawdź dla N=4, w ogólności FN ma tylko N różnych wyrazów), gdyż ωNN=1 (dla j=0,,N1, ωNj to nic innego jak kolejne zespolone pierwiastki z jedynki).

W wyprowadzeniu algorytmu szybkiej transformacji Fouriera (Fast Fourier Transform, FFT) oprzemy się po raz kolejny na regule "dziel i rządź". Dla uproszczenia analizy przyjmiemy, że N jest naturalną potęgą dwójki, w szczególności N=2m dla pewnego naturalnego m.

Rzeczywiście, rozbijając naszą sumę na sumę po indeksach parzystych i sumę po indeksach nieparzystych, mamy

cj=f0ωN0j+f2ωN2j++fN2ωN(N2)j+f1ωN1j+f3ωN3j++fN1ωN(N1)j.

Suma po indeksach parzystych da się zapisać w postaci

k=0m1f2k(ωm)jk

i analogicznie suma po indeksach nieparzystych da się zapisać

ωNjk=0m1f2k+1(ωm)jk

gdyż ωN2=ωm, a więc wygląda na to, że nasze zadanie wyznaczenia dyskretnej transformacji Fouriera wymiaru N da się sprowadzić do analogicznych zadań mniejszego rozmiaru.

Rzeczywiście, korzystając z tego, że j=βm+j~, gdzie j~=0,,m1, oraz β=0 lub 1, mamy ωmj=ωmj~.

Oznaczając

Φj~=k=0m1f2kωmj~k,j~=0,,m1,Ψj~=k=0m1f2k+1ωmj~k,

(jak widać są to DFT dla dwa razy krótszych wektorów, złożonych z tylko parzystych lub tylko nieparzystych współrzędnych f), dostajemy ostatecznie

cj~=Φj~+ωNj~Ψj~,cj~+m=Φj~ωNj~Ψj~.

Tym samym, wyznaczenie DFT wymiaru N udało się rzeczywiście sprowadzić do wyznaczenia dwóch DFT wymiaru m=N/2 oraz drobnej manipulacji na ich wynikach zgodnie z powyższym wzorem. Oczywiście, te mniejsze transformacje można wyznaczyć takim samym sposobem, co prowadzi do zależności rekurencyjnej, która kończy się na wektorach długości 1, na których DFT to po prostu identyczność.

Proste sprawdzenie pokazuje, że koszt takiego algorytmu jest rzędu O(NlogN), a nie O(N2), jak dla naiwnego algorytmu mnożenia wektora przez gęstą macierz. Zysk jest więc, nawet dla niewielkich N, istotny.

Algorytm Prosta wersja algorytmu FFT


function y = fft(x)
	N = length(f);
	if N == 1 
		y = f;
	else
		<math>\omega</math> = <math>\exp(-\frac{2\Pi}{N}i)</math>;
		<math>\omega_k</math> = <math>\omega^{N/2-1}</math>;
		
		u = fft( f[0:2:N-2] );
		v = fft( f[1:2:N-1] );
		
		v = v * <math>\omega_k</math>;
		
		y = [ u+v ; u-v ];
	end
end

Jak już zdążyliśmy się przyzwyczaić, gdy tylko to możliwe, w algorytmach numerycznych unikamy stosowania jawnej rekurencji. W przypadku FFT można jej również uniknąć, wyznaczając zawczasu --- korzystając z tzw. odwrócenia bitów --- porządek, w którym należy składać 1-wymiarowe DFT w coraz dłuższe wektory zgodnie ze wzorem powyżej tak, by na końcu dostać pożądany wektor c=FNf.

Ponadto, istnieją warianty algorytmu FFT, które np. działają na danych rzeczywistych. Na analogicznych zasadach co FFT oparte są również algorytmy wykonujące tzw. dyskretną transformację cosinusów (DCT) i jej wariant MDCT stosowany w kodekach audio takich jak MP3, AAC, czy OggVorbis.

Interpolacja trygonometryczna

Jednym z wielu zadań, w których daje się zastosować algorytm FFT, jest zadanie interpolacji trygonometrycznej:

Dla danych węzłów xk=2πkN, k=0,,N1, znaleźć wielomian P (zmiennej rzeczywistej, o wartościach zespolonych) stopnia N1 postaci

P(x)=k=0N1βkω¯k,

gdzie ω¯=eix=cos(x)+isin(x) (stąd nazwa: trygonometryczny) taki, że

P(xk)=yk,k=0,,N1,

dla zadanych wartości yk.

W języku macierzy DFT możemy zapisać zadanie interpolacji trygonometrycznej jako zadanie wyznaczania wektora β takiego, że

FNβ=y

dla zadanego wektora y.

Twierdzenie O współczynnikach wielomianu interpolacji trygonometrycznej

Współczynniki β=[β0,,βN1]T poszukiwanego wielomianu trygonometrycznego wyrażają się wzorem

β=1NFNy

Dowód

Jak wiemy, 1NFN*FN=I, z symetrii macierzy FN wynika więc, że (FN)1=1NFN.

Można pokazać, że gdy dane są rzeczywiste, zadanie interpolacji trygonometrycznej możemy wyrazić korzystając wyłącznie z liczb rzeczywistych. Jeśli ykR, k=0,,N1, to wtedy (rzeczywisty) wielomian trygonometryczny

p(x)=k=0N1(akcos(2kπNx)bksin(2kπNx)),

gdzie ak=Re(βk), bk=Im(βk), interpoluje yk w węzłach xk. Oczywiście, powyższa formuła w rzeczywistości ma o połowę mniej wyrazów, ze względu na własności funkcji trygonometrycznych.

Splot

W niektórych zastosowaniach potrzebne jest wyznaczenie splotu dwóch wektorów, to znaczy wyznaczenie wyrażeń postaci

zk=j=0N1ujvkj,k=0,,N1

(przyjmujemy tu konwencję, że wektory rozszerzamy N-periodyczne tak, że z definicji vj+kN=vj dla dowolnych całkowitych j,k).

Zapisując to macierzowo, szukamy iloczynu wektora u z cykliczną macierzą Toeplitza (macierz Toeplitza ma stałe wyrazy wzdłuż diagonali) wyznaczoną przez v,

z=(v0v1vN1vN1v0vN2v1vN1v0)(u0u1uN1).

Mogłoby się zdawać, że zadanie wyznaczenia splotu powinno kosztować tyle, co mnożenie macierzy przez wektor, a więc O(N2) operacji. Tymczasem prosty rachunek pozwala sprawdzić, że odpowiednie transformacje Fouriera, z^=FNz, u^=FNu, v^=FNv spełniają równanie z macierzą diagonalną!

z^=N(v^0v^N1)u^=N(v^0u^0v^N1u^N1),

a to mnożenie daje się wykonać kosztem liniowym, tym samym całe zadanie daje się policzyć kosztem O(NlogN).

Biblioteki

Najpopularniejszą obecnie biblioteką implementującą algorytm FFT dla DFT, DCT i innych pokrewnych (bez ograniczenia, że wymiar jest naturalną potęgą dwójki), jest biblioteka o niezbyt skromnie brzmiącej nazwie FFTW (The Fastest Fourier Transform in the West). Z tej biblioteki korzystają m.in. funkcje MATLABa i Octave'a fft oraz ifft dla transformacji DFT (to znaczy mnożenia przez FN) i, odpowiednio, transformacji odwrotnej, 1NFN.

FFTW jest napisana w C i w dużym stopniu wykorzystuje możliwości współczesnych procesorów, takie jak potokowanie i instrukcje wektorowe SSE2 i SSE3. Poniżej pokazujemy przykładowy prościutki kod w C realizujący DFT na pojedynczym wektorze zespolonym.

/* Kompilacja:
	gcc -o dft dft.c -lfftw3 -lm */
#include <complex.h> /* rozszerzenie GCC dające operacje na typach zespolonych */
#include <fftw3.h>
#include <math.h>
#define N 8

int main(void)
{
fftw_complex *F, *C;
fftw_plan Plan;
double normfactor = 1.0/N;
int i;

F = fftw_malloc( N * sizeof(fftw_complex) );
C = fftw_malloc( N * sizeof(fftw_complex) );

for( i = 0; i < N; i++ ) /* inicjalizacja wartości tablicy F */
{
	F[i] = i*M_PI*(1-0.5*I); 
}

Plan = fftw_plan_dft_1d( N, F, C, FFTW_FORWARD, FFTW_ESTIMATE );

fftw_execute( Plan );

for( i = 0; i < N; i++ ) /* normalizacja wyznaczonego C */
	C[i] *= normfactor; 

for( i = 0; i < N; i++ )
{
	printf("F[%d] = %8.3lf + %8.3lfi | C[%d] = %8.3lf + %8.3lfi\n", 
	i, creal(F[i]), cimag(F[i]), i, creal(C[i]), cimag(C[i])); 
}

/* .... teraz moglibyśmy zmienić wartości F i ponownie wyznaczyć C,
korzystając z tego samego Planu! */

/* sprzątamy */

fftw_destroy_plan( Plan );
fftw_free( F ); 
fftw_free( C ); 

return(0);
}

Zwrócmy uwagę na linię Plan=fftw_plan_dft_1d(...). To tutaj dokonywane są ustalenia, w jakiej kolejności mają być prowadzone obliczenia. Jest to operacja dość kosztowna, dlatego jeśli mamy wyznaczyć wiele takich samych DFT, ale na różnych danych f, należy przed pierwszą DFT taki plan zachować, a potem, aplikując DFT dla następnych danych, wykorzystać gotowy.

Literatura

W celu dogłębnego zapoznania się z omawianym na wykładzie materiałem, przeczytaj rozdział 6.12--6.13 w

  • D. Kincaid, W. Cheney Analiza numeryczna, Wydawnictwa Naukowo-Techniczne, Warszawa 2006, ISBN 83-204-3078-X.