MN10: Różnice pomiędzy wersjami
Nie podano opisu zmian |
m Zastępowanie tekstu – „,↵</math>” na „</math>,” |
||
(Nie pokazano 10 wersji utworzonych przez 2 użytkowników) | |||
Linia 11: | Linia 11: | ||
przedmiotu <strong>Metody numeryczne</strong>}} | przedmiotu <strong>Metody numeryczne</strong>}} | ||
Algorytm FFT ( | Algorytm FFT (''Fast Fourier Transform'') dla dyskretnej transformacji Fouriera (DFT, ''Discrete Fourier Transform'') i jego krewniacy dla | ||
wyznaczania dyskretnej transformacji cosinusów (DCT, | 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 | ||
Linia 19: | Linia 19: | ||
* rozwiązywaniu ważnych równań różniczkowych cząstkowych (DFT) | * rozwiązywaniu ważnych równań różniczkowych cząstkowych (DFT) | ||
a także do | a także do | ||
* filtrowania 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 | ||
Linia 32: | Linia 30: | ||
banalne i jednocześnie wydumane: | banalne i jednocześnie wydumane: | ||
Dla danego zestawu <math> | Dla danego zestawu <math>N</math> liczb <math>f_\alpha\in C</math>, <math>\alpha = 0,\ldots, N-1</math>, | ||
wyznaczyć <math> | wyznaczyć <math>N</math> wartości | ||
<center><math> | <center><math>c_j = \sum_{\alpha = 0}^{N-1} f_\alpha \omega_N^{j\alpha}</math>,</center> | ||
</math></center> | |||
dla <math> | 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> | 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> | 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> | <center><math>c = F_N f</math>,</center> | ||
</math></center> | |||
gdzie | gdzie | ||
<center><math> | <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 60: | 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> | tworząc na początek macierz <math>F_N</math>, a następnie wyznaczając iloczyn macierzy | ||
przez wektor, co łatwo zrobić kosztem <math> | 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> | ż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ą | czyli praktycznie liniowym (w obu przypadkach stałe proporcjonalności są niewielkie) w dodatku będzie miał znacznie mniejsze wymagania pamięciowe. | ||
==Algorytm FFT== | ==Algorytm FFT== | ||
Aby móc taniej mnożyć przez <math> | Aby móc taniej mnożyć przez <math>F_N</math>, musimy odkryć kilka bardzo szczególnych własności tej macierzy. | ||
{{fakt||| | {{fakt||| | ||
Macierz <math> | Macierz <math>F</math> jest symetryczna oraz <math>\frac{1}{N} F_N^* F_N = I</math>. | ||
}} | }} | ||
Linia 87: | Linia 77: | ||
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> | w niej bardzo wiele takich samych współczynników (sprawdź dla <math>N=4</math>, w ogólności | ||
<math> | <math>F_N</math> ma tylko <math>N</math> różnych wyrazów), gdyż <math>\omega_N^N = 1</math> (dla | ||
<math> | <math>j=0,\ldots,N-1</math>, <math>\omega_N^j</math> to nic innego jak kolejne zespolone pierwiastki z jedynki). | ||
W wyprowadzeniu algorytmu szybkiej transformacji Fouriera (''Fast Fourier | W wyprowadzeniu algorytmu szybkiej transformacji Fouriera | ||
Transform, FFT'') oprzemy się po raz kolejny na regule ''"dziel i rządź"''. Dla uproszczenia analizy przyjmiemy, że <math> | (''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>. | ||
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>\ | <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}. | ||
\ | \end{align}</math></center> | ||
Suma po indeksach parzystych da się zapisać w postaci | Suma po indeksach parzystych da się zapisać w postaci | ||
<center><math> | <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 111: | Linia 101: | ||
i analogicznie suma po indeksach nieparzystych da się zapisać | i analogicznie suma po indeksach nieparzystych da się zapisać | ||
<center><math> | <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> | gdyż <math>\omega_N^2 = \omega_m</math>, a więc wygląda na to, że nasze zadanie wyznaczenia | ||
dyskretnej transformacji Fouriera wymiaru <math> | dyskretnej transformacji Fouriera wymiaru <math>N</math> da się sprowadzić do analogicznych zadań mniejszego | ||
rozmiaru. | rozmiaru. | ||
Rzeczywiście, korzystając z tego, że <math> | 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> | = 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>\ | <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}, | ||
\ | \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> | parzystych lub tylko nieparzystych współrzędnych <math>f</math>), dostajemy ostatecznie | ||
<center><math>\ | <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}}. | ||
\ | \end{align}</math></center> | ||
Tym samym, wyznaczenie DFT wymiaru <math> | Tym samym, wyznaczenie DFT wymiaru <math>N</math> udało się rzeczywiście sprowadzić | ||
do wyznaczenia dwóch DFT wymiaru <math> | 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 145: | Linia 135: | ||
Proste sprawdzenie pokazuje, że koszt takiego algorytmu jest rzędu | Proste sprawdzenie pokazuje, że koszt takiego algorytmu jest rzędu | ||
<math> | <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> | gęstą macierz. Zysk jest więc, nawet dla niewielkich <math>N</math>, istotny. | ||
{{algorytm|Prosta wersja algorytmu FFT|Prosta wersja algorytmu FFT| | {{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 = | y = f; | ||
else | else | ||
<math> | <math>\omega</math> = <math>\exp(-\frac{2\Pi}{N}i)</math>; | ||
<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> | v = v * <math>\omega_k</math>; | ||
y = [ u+v ; u-v ]; | y = [ u+v ; u-v ]; | ||
end | end | ||
end | end | ||
</ | </Source>}} | ||
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 | 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 | 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> | 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== | ||
Linia 182: | Linia 172: | ||
<strong>interpolacji trygonometrycznej</strong>: | <strong>interpolacji trygonometrycznej</strong>: | ||
Dla danych węzłów <math> | Dla danych węzłów <math>x_k = \frac{2\pi k }{N}</math>, <math>k = 0,\ldots,N-1</math>, znaleźć | ||
wielomian <math> | wielomian <math>P</math> (zmiennej rzeczywistej, o wartościach zespolonych) stopnia <math>\leq N-1</math> postaci | ||
<center><math> | <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> | gdzie <math>\bar{\omega} = e^{ix} = \cos(x) + i\, \sin(x)</math> (stąd nazwa: trygonometryczny) | ||
taki, że | taki, że | ||
<center><math> | <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> | 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> | zadanie wyznaczania wektora <math>\beta</math> takiego, że | ||
<center><math> | <center><math> | ||
\overline{F_N}\beta = y | \overline{F_N}\beta = y | ||
</math></center> | </math></center> | ||
dla zadanego wektora <math> | dla zadanego wektora <math>y</math>. | ||
{{twierdzenie|O współczynnikach wielomianu interpolacji trygonometrycznej|O współczynnikach wielomianu interpolacji trygonometrycznej| | {{twierdzenie|O współczynnikach wielomianu interpolacji trygonometrycznej|O współczynnikach wielomianu interpolacji trygonometrycznej| | ||
Współczynniki <math> | Współczynniki <math>\beta = [\beta_0,\ldots,\beta_{N-1}]^T</math> poszukiwanego wielomianu | ||
trygonometrycznego wyrażają się wzorem | trygonometrycznego wyrażają się wzorem | ||
<center><math> | <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> | Jak wiemy, <math>\frac{1}{N} F_N^* F_N = I</math>, z symetrii macierzy <math>F_N</math> wynika więc, | ||
że <math> | że <math>(\overline{F_N})^{-1} = \frac{1}{N} F_N</math>. | ||
}} | }} | ||
Linia 222: | Linia 209: | ||
trygonometrycznej możemy wyrazić | trygonometrycznej możemy wyrazić | ||
korzystając wyłącznie z liczb rzeczywistych. | korzystając wyłącznie z liczb rzeczywistych. | ||
Jeśli <math> | Jeśli <math>y_k\in R</math>, <math>k = 0,\ldots,N-1</math>, to wtedy (rzeczywisty) wielomian trygonometryczny | ||
<center><math> | <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> | 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> | <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 235: | Linia 221: | ||
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> | <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></ | (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> | 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> | (macierz Toeplitza ma stałe wyrazy wzdłuż diagonali) wyznaczoną przez <math>v</math>, | ||
<center><math> | <center><math> | ||
z = | z = | ||
\begin{pmatrix} | \begin{pmatrix} | ||
Linia 252: | Linia 238: | ||
Mogłoby się zdawać, że 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> | 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> | <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> | <center><math> | ||
\hat{z} = N \cdot \begin{pmatrix} \hat{v}_0 & & \\ | \hat{z} = N \cdot \begin{pmatrix} \hat{v}_0 & & \\ | ||
& \ddots & \\ | & \ddots & \\ | ||
Linia 264: | 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> | 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), 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> | 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>. | ||
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 | ||
Linia 280: | Linia 265: | ||
wektorze zespolonym. | wektorze zespolonym. | ||
< | <Source>/* 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 311: | Linia 296: | ||
for( i = 0; i < N; i++ ) | for( i = 0; i < N; i++ ) | ||
{ | { | ||
printf("F[ | 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 326: | Linia 311: | ||
return(0); | return(0); | ||
} | } | ||
</ | </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, ale na | dość kosztowna, dlatego jeśli mamy wyznaczyć wiele takich samych DFT, ale na | ||
różnych danych <math> | 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. | ||
==Literatura== | ==Literatura== |
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
- filtrowania szumów (DFT)
- szybkiego mnożenia wielomianów (DFT)
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 liczb , , wyznaczyć wartości
dla , przy czym . Jak pamiętamy, jednostka urojona spełnia . Taką operację nazywamy dyskretną transformacją Fouriera, DFT.

Zobacz biografię
Ponieważ dane są wektorem , wynik też jest wektorem, a zadanie jest liniowe, możemy wszystko zapisać macierzowo:
gdzie
Tak więc, gdyby naiwnie podejść do zadania, moglibyśmy je rozwiązać brutalnie, tworząc na początek macierz , a następnie wyznaczając iloczyn macierzy przez wektor, co łatwo zrobić kosztem operacji. Dlatego istotne jest, że algorytm FFT, który za chwilę omówimy, będzie działać kosztem , 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 , musimy odkryć kilka bardzo szczególnych własności tej macierzy.
Fakt
Macierz jest symetryczna oraz .
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 , w ogólności ma tylko różnych wyrazów), gdyż (dla , 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 jest naturalną potęgą dwójki, w szczególności dla pewnego naturalnego .
Rzeczywiście, rozbijając naszą sumę na sumę po indeksach parzystych i sumę po indeksach nieparzystych, mamy
Suma po indeksach parzystych da się zapisać w postaci
i analogicznie suma po indeksach nieparzystych da się zapisać
gdyż , a więc wygląda na to, że nasze zadanie wyznaczenia dyskretnej transformacji Fouriera wymiaru da się sprowadzić do analogicznych zadań mniejszego rozmiaru.
Rzeczywiście, korzystając z tego, że , gdzie , oraz lub , mamy .
Oznaczając
(jak widać są to DFT dla dwa razy krótszych wektorów, złożonych z tylko parzystych lub tylko nieparzystych współrzędnych ), dostajemy ostatecznie
Tym samym, wyznaczenie DFT wymiaru udało się rzeczywiście sprowadzić do wyznaczenia dwóch DFT wymiaru 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 , a nie , jak dla naiwnego algorytmu mnożenia wektora przez gęstą macierz. Zysk jest więc, nawet dla niewielkich , 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 .
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 , , znaleźć wielomian (zmiennej rzeczywistej, o wartościach zespolonych) stopnia postaci
gdzie (stąd nazwa: trygonometryczny) taki, że
dla zadanych wartości .
W języku macierzy DFT możemy zapisać zadanie interpolacji trygonometrycznej jako zadanie wyznaczania wektora takiego, że
dla zadanego wektora .
Twierdzenie O współczynnikach wielomianu interpolacji trygonometrycznej
Współczynniki poszukiwanego wielomianu trygonometrycznego wyrażają się wzorem
Dowód
Można pokazać, że gdy dane są rzeczywiste, zadanie interpolacji trygonometrycznej możemy wyrazić korzystając wyłącznie z liczb rzeczywistych. Jeśli , , to wtedy (rzeczywisty) wielomian trygonometryczny
gdzie , , interpoluje w węzłach . 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
(przyjmujemy tu konwencję, że wektory rozszerzamy -periodyczne tak, że z definicji dla dowolnych całkowitych ).
Zapisując to macierzowo, szukamy iloczynu wektora z cykliczną macierzą Toeplitza (macierz Toeplitza ma stałe wyrazy wzdłuż diagonali) wyznaczoną przez ,
Mogłoby się zdawać, że zadanie wyznaczenia splotu powinno kosztować tyle, co mnożenie macierzy przez wektor, a więc operacji. Tymczasem prosty rachunek pozwala sprawdzić, że odpowiednie transformacje Fouriera, , , spełniają równanie z macierzą diagonalną!
a to mnożenie daje się wykonać kosztem liniowym, tym samym całe zadanie daje się policzyć kosztem .
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 ) i, odpowiednio, transformacji odwrotnej, .
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 , 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.