\documentclass[fleqn,12pt]{article} % \setlength{\oddsidemargin}{-0.25in} \setlength{\textwidth}{7.0in} \setlength{\topmargin}{-0.25in} \setlength{\textheight}{9.5in} % \usepackage{algorithmic} \newenvironment{algorithm}{\begin{quote} %\vspace{1em} \begin{algorithmic}\samepage}{\end{algorithmic} %\vspace{1em} \end{quote}} \newcommand{\Real}{\mathop{\mathrm{Re}}} \newcommand{\Imag}{\mathop{\mathrm{Im}}} \begin{document} \title{FFT Algorithms} \author{Brian Gough, {\tt bjg@network-theory.co.uk}} \date{May 1997} \maketitle \section{Introduction} Fast Fourier Transforms (FFTs) are efficient algorithms for calculating the discrete fourier transform (DFT), % \begin{eqnarray} h_a &=& \mathrm{DFT}(g_b) \\ &=& \sum_{b=0}^{N-1} g_b \exp(-2\pi i a b /N) \qquad 0 \leq a \leq N-1\\ &=& \sum_{b=0}^{N-1} g_b W_N^{ab} \qquad W_N= \exp(-2\pi i/N) \end{eqnarray} % The DFT usually arises as an approximation to the continuous fourier transform when functions are sampled at discrete intervals in space or time. The naive evaluation of the discrete fourier transform is a matrix-vector multiplication ${\mathbf W}\vec{g}$, and would take $O(N^2)$ operations for $N$ data-points. The general principle of the Fast Fourier Transform algorithms is to use a divide-and-conquer strategy to factorize the matrix $W$ into smaller sub-matrices, typically reducing the operation count to $O(N \sum f_i)$ if $N$ can be factorized into smaller integers, $N=f_1 f_2 \dots f_n$. This chapter explains the algorithms used in the GSL FFT routines and provides some information on how to extend them. To learn more about the FFT you should read the review article {\em Fast Fourier Transforms: A Tutorial Review and A State of the Art} by Duhamel and Vetterli~\cite{duhamel90}. There are several introductory books on the FFT with example programs, such as {\em The Fast Fourier Transform} by Brigham~\cite{brigham74} and {\em DFT/FFT and Convolution Algorithms} by Burrus and Parks~\cite{burrus84}. In 1979 the IEEE published a compendium of carefully-reviewed Fortran FFT programs in {\em Programs for Digital Signal Processing}~\cite{committee79} which is a useful reference for implementations of many different FFT algorithms. If you are interested in using DSPs then the {\em Handbook of Real-Time Fast Fourier Transforms}~\cite{smith95} provides detailed information on the algorithms and hardware needed to design, build and test DSP applications. Many FFT algorithms rely on results from number theory. These results are covered in the books {\em Fast transforms: algorithms, analyses, applications}, by Elliott and Rao~\cite{elliott82}, {\em Fast Algorithms for Digital Signal Processing} by Blahut~\cite{blahut} and {\em Number Theory in Digital Signal Processing} by McClellan and Rader~\cite{mcclellan79}. There is also an annotated bibliography of papers on the FFT and related topics by Burrus~\cite{burrus-note}. \section{Families of FFT algorithms} % There are two main families of FFT algorithms: the Cooley-Tukey algorithm and the Prime Factor algorithm. These differ in the way they map the full FFT into smaller sub-transforms. Of the Cooley-Tukey algorithms there are two types of routine in common use: mixed-radix (general-$N$) algorithms and radix-2 (power of 2) algorithms. Each type of algorithm can be further classified by additional characteristics, such as whether it operates in-place or uses additional scratch space, whether its output is in a sorted or scrambled order, and whether it uses decimation-in-time or -frequency iterations. Mixed-radix algorithms work by factorizing the data vector into shorter lengths. These can then be transformed by small-$N$ FFTs. Typical programs include FFTs for small prime factors, such as 2, 3, 5, \dots which are highly optimized. The small-$N$ FFT modules act as building blocks and can be multiplied together to make longer transforms. By combining a reasonable set of modules it is possible to compute FFTs of many different lengths. If the small-$N$ modules are supplemented by an $O(N^2)$ general-$N$ module then an FFT of any length can be computed, in principle. Of course, any lengths which contain large prime factors would perform only as $O(N^2)$. Radix-2 algorithms, or ``power of two'' algorithms, are simplified versions of the mixed-radix algorithm. They are restricted to lengths which are a power of two. The basic radix-2 FFT module only involves addition and subtraction, so the algorithms are very simple. Radix-2 algorithms have been the subject of much research into optimizing the FFT. Many of the most efficient radix-2 routines are based on the ``split-radix'' algorithm. This is actually a hybrid which combines the best parts of both radix-2 and radix-4 (``power of 4'') algorithms~\cite{sorenson86,duhamel86}. The prime factor algorithm (PFA) is an alternative form of general-$N$ algorithm based on a different way of recombining small-$N$ FFT modules~\cite{temperton83pfa,temperton85}. It has a very simple indexing scheme which makes it attractive. However it only works in the case where all factors are mutually prime. This requirement makes it more suitable as a specialized algorithm for given lengths. \begin{subsection}{FFTs of prime lengths} % Large prime lengths cannot be handled efficiently by any of these algorithms. However it may still possible to compute a DFT, by using results from number theory. Rader showed that it is possible to convert a length-$p$ FFT (where $p$ is prime) into a convolution of length-($p-1$). There is a simple identity between the convolution of length $N$ and the FFT of the same length, so if $p-1$ is easily factorizable this allows the convolution to be computed efficiently via the FFT. The idea is presented in the original paper by Rader~\cite{raderprimes} (also reprinted in~\cite{mcclellan79}), but for more details see the theoretical books mentioned earlier. \end{subsection} %\subsection{In-place algorithms vs algorithms using scratch space} %% %For simple algorithms, such as the Radix-2 algorithms, the FFT can be %performed in-place, without additional memory. For this to be possible %the index calculations must be simple enough that the calculation of %the result to be stored in index $k$ can be carried out at the same %time as the data in $k$ is used, so that no temporary storage is %needed. %The mixed-radix algorithm is too complicated for this. It requires an %area of scratch space sufficient to hold intermediate copies of the %input data. %\subsection{Self-sorting algorithms vs scrambling algorithms} %% %A self-sorting algorithm At each iteration of the FFT internal permutations are included %which leave the final iteration in its natural order. %The mixed-radix algorithm can be made self-sorting. The additional %scratch space helps here, although it is in fact possible to design %self-sorting algorithms which do not require scratch %space. %The in-place radix-2 algorithm is not self-sorting. The data is %permuted, and transformed into bit-reversed order. Note that %bit-reversal only refers to the order of the array, not the values for %each index which are of course not changed. More precisely, the data %stored in location $[b_n \dots b_2 b_1 b_0]$ is moved to location %$[b_0 b_1 b_2 \dots b_n]$, where $b_0 \dots b_n$ are the raw bits of a %given index. Placing the data in bit reversed order is easily done, %using right shifts to extract the least significant bits of the index %and left-shifts to feed them into a register for the bit-reversed %location. Simple radix-2 FFT usually recompute the bit reverse %ordering in the naive way for every call. For repeated calls it might %be worthwhile to precompute the permutation and cache it. There are %also more sophisticated algorithms which reduce the number of %operations needed to perform bit-reversal~\cite{bit-reversal}. %\begin{subsection}{Decimation-in-time (DIT) vs Decimation-in-Frequency (DIF)} %\end{subsection} \subsection{Optimization} % There is no such thing as the single fastest FFT {\em algorithm}. FFT algorithms involve a mixture of floating point calculations, integer arithmetic and memory access. Each of these operations will have different relative speeds on different platforms. The performance of an algorithm is a function of the hardware it is implemented on. The goal of optimization is thus to choose the algorithm best suited to the characteristics of a given hardware platform. For example, the Winograd Fourier Transform (WFTA) is an algorithm which is designed to reduce the number of floating point multiplications in the FFT. However, it does this at the expense of using many more additions and data transfers than other algorithms. As a consequence the WFTA might be a good candidate algorithm for machines where data transfers occupy a negligible time relative to floating point arithmetic. However on most modern machines, where the speed of data transfers is comparable to or slower than floating point operations, it would be outperformed by an algorithm which used a better mix of operations (i.e. more floating point operations but fewer data transfers). For a study of this sort of effect in detail, comparing the different algorithms on different platforms consult the paper {\it Effects of Architecture Implementation on DFT Algorithm Performance} by Mehalic, Rustan and Route~\cite{mehalic85}. The paper was written in the early 1980's and has data for super- and mini-computers which you are unlikely to see today, except in a museum. However, the methodology is still valid and it would be interesting to see similar results for present day computers. \section{FFT Concepts} % Factorization is the key principle of the mixed-radix FFT divide-and-conquer strategy. If $N$ can be factorized into a product of $n_f$ integers, % \begin{equation} N = f_1 f_2 ... f_{n_f} , \end{equation} % then the FFT itself can be divided into smaller FFTs for each factor. More precisely, an FFT of length $N$ can be broken up into, % \begin{quote} \begin{tabular}{l} $(N/f_1)$ FFTs of length $f_1$, \\ $(N/f_2)$ FFTs of length $f_2$, \\ \dots \\ $(N/f_{n_f})$ FFTs of length $f_{n_f}$. \end{tabular} \end{quote} % The total number of operations for these sub-operations will be $O( N(f_1 + f_2 + ... + f_{n_f}))$. When the factors of $N$ are all small integers this will be substantially less than $O(N^2)$. For example, when $N$ is a power of 2 an FFT of length $N=2^m$ can be reduced to $m N/2$ FFTs of length 2, or $O(N\log_2 N)$ operations. Here is a demonstration which shows this: We start with the full DFT, % \begin{eqnarray} h_a &=& \sum_{b=0}^{N-1} g_b W_N^{ab} \qquad W_N=\exp(-2\pi i/N) \end{eqnarray} % and split the sum into even and odd terms, % \begin{eqnarray} \phantom{h_a} % &=& \left(\sum_{b=0,2,4,\dots} + \sum_{b=1,3,5,\dots}\right) g_b W_N^{ab}\\ &=& \sum_{b=0}^{N/2-1} g_{2b} W_N^{a(2b)} + \sum_{b=0}^{N/2-1} g_{2b+1} W_N^{a(2b+1)}. \end{eqnarray} % This converts the original DFT of length $N$ into two DFTs of length $N/2$, % \begin{equation} h_a = \sum_{b=0}^{N/2-1} g_{2b} W_{(N/2)}^{ab} + W_N^a \sum_{b=0}^{N/2-1} g_{2b+1} W_{(N/2)}^{ab} \end{equation} % The first term is a DFT of the even elements of $g$. The second term is a DFT of the odd elements of $g$, premultiplied by an exponential factor $W_N^k$ (known as a {\em twiddle factor}). % \begin{equation} \mathrm{DFT}(h) = \mathrm{DFT}(g_{even}) + W_N^k \mathrm{DFT}(g_{odd}) \end{equation} % By splitting the DFT into its even and odd parts we have reduced the operation count from $N^2$ (for a DFT of length $N$) to $2 (N/2)^2$ (for two DFTs of length $N/2$). The cost of the splitting is that we need an additional $O(N)$ operations to multiply by the twiddle factor $W_N^k$ and recombine the two sums. We can repeat the splitting procedure recursively $\log_2 N$ times until the full DFT is reduced to DFTs of single terms. The DFT of a single value is just the identity operation, which costs nothing. However since $O(N)$ operations were needed at each stage to recombine the even and odd parts the total number of operations to obtain the full DFT is $O(N \log_2 N)$. If we had used a length which was a product of factors $f_1$, $f_2$, $\dots$ we could have split the sum in a similar way. First we would split terms corresponding to the factor $f_1$, instead of the even and odd terms corresponding to a factor of two. Then we would repeat this procedure for the subsequent factors. This would lead to a final operation count of $O(N \sum f_i)$. This procedure gives some motivation for why the number of operations in a DFT can in principle be reduced from $O(N^2)$ to $O(N \sum f_i)$. It does not give a good explanation of how to implement the algorithm in practice which is what we shall do in the next section. \section{Radix-2 Algorithms} % For radix-2 FFTs it is natural to write array indices in binary form because the length of the data is a power of two. This is nicely explained in the article {\em The FFT: Fourier Transforming One Bit at a Time} by P.B. Visscher~\cite{visscher96}. A binary representation for indices is the key to deriving the simplest efficient radix-2 algorithms. We can write an index $b$ ($0 \leq b < 2^{n-1}$) in binary representation like this, % \begin{equation} b = [b_{n-1} \dots b_1 b_0] = 2^{n-1}b_{n-1} + \dots + 2 b_1 + b_0 . \end{equation} % Each of the $b_0, b_1, \dots, b_{n-1}$ are the bits (either 0 or 1) of $b$. Using this notation the original definition of the DFT can be rewritten as a sum over the bits of $b$, % \begin{equation} h(a) = \sum_{b=0}^{N-1} g_b \exp(-2\pi i a b /N) \end{equation} % to give an equivalent summation like this, % \begin{equation} h([a_{n-1} \dots a_1 a_0]) = \sum_{b_0=0}^{1} \sum_{b_1=0}^{1} \dots \sum_{b_{n-1}=0}^{1} g([b_{n-1} \dots b_1 b_0]) W_N^{ab} \end{equation} % where the bits of $a$ are $a=[a_{n-1} \dots a_1 a_0]$. To reduce the number of operations in the sum we will use the periodicity of the exponential term, % \begin{equation} W_N^{x+N} = W_N^{x}. \end{equation} % Most of the products $ab$ in $W_N^{ab}$ are greater than $N$. By making use of this periodicity they can all be collapsed down into the range $0\dots N-1$. This allows us to reduce the number of operations by combining common terms, modulo $N$. Using this idea we can derive decimation-in-time or decimation-in-frequency algorithms, depending on how we break the DFT summation down into common terms. We'll first consider the decimation-in-time algorithm. \subsection{Radix-2 Decimation-in-Time (DIT)} % To derive the the decimation-in-time algorithm we start by separating out the most significant bit of the index $b$, % \begin{equation} [b_{n-1} \dots b_1 b_0] = 2^{n-1}b_{n-1} + [b_{n-2} \dots b_1 b_0] \end{equation} % Now we can evaluate the innermost sum of the DFT without any dependence on the remaining bits of $b$ in the exponential, % \begin{eqnarray} h([a_{n-1} \dots a_1 a_0]) &=& \sum_{b_0=0}^{1} \sum_{b_1=0}^{1} \dots \sum_{b_{n-1}=0}^{1} g(b) W_N^{a(2^{n-1}b_{n-1}+[b_{n-2} \dots b_1 b_0])} \\ &=& \sum_{b_0=0}^{1} \sum_{b_1=0}^{1} \dots \sum_{b_{n-2}=0}^{1} W_N^{a[b_{n-2} \dots b_1 b_0]} \sum_{b_{n-1}=0}^{1} g(b) W_N^{a(2^{n-1}b_{n-1})} \end{eqnarray} % Looking at the term $W_N^{a(2^{n-1}b_{n-1})}$ we see that we can also remove most of the dependence on $a$ as well, by using the periodicity of the exponential, % \begin{eqnarray} W_N^{a(2^{n-1}b_{n-1})} &=& \exp(-2\pi i [a_{n-1} \dots a_1 a_0] 2^{n-1} b_{n-1}/ 2^n )\\ &=& \exp(-2\pi i [a_{n-1} \dots a_1 a_0] b_{n-1}/ 2 )\\ &=& \exp(-2\pi i ( 2^{n-2}a_{n-1} + \dots + a_1 + (a_0/2)) b_{n-1} )\\ &=& \exp(-2\pi i a_0 b_{n-1}/2 ) \\ &=& W_2^{a_0 b_{n-1}} \end{eqnarray} % Thus the innermost exponential term simplifies so that it only involves the highest order bit of $b$ and the lowest order bit of $a$, and the sum can be reduced to, % \begin{equation} h([a_{n-1} \dots a_1 a_0]) = \sum_{b_0=0}^{1} \sum_{b_1=0}^{1} \dots \sum_{b_{n-2}=0}^{1} W_N^{a[b_{n-2} \dots b_1 b_0]} \sum_{b_{n-1}=0}^{1} g(b) W_2^{a_0 b_{n-1}}. \end{equation} % We can repeat this this procedure for the next most significant bit of $b$, $b_{n-2}$, using a similar identity, % \begin{eqnarray} W_N^{a(2^{n-2}b_{n-2})} &=& \exp(-2\pi i [a_{n-1} \dots a_1 a_0] 2^{n-2} b_{n-2}/ 2^n )\\ &=& W_4^{ [a_1 a_0] b_{n-2}}. \end{eqnarray} % to give a formula with even less dependence on the bits of $a$, % \begin{equation} h([a_{n-1} \dots a_1 a_0]) = \sum_{b_0=0}^{1} \sum_{b_1=0}^{1} \dots \sum_{b_{n-3}=0}^{1} W_N^{a[b_{n-3} \dots b_1 b_0]} \sum_{b_{n-2}=0}^{1} W_4^{[a_1 a_0] b_{n-2}} \sum_{b_{n-1}=0}^{1} g(b) W_2^{a_0 b_{n-1}}. \end{equation} % If we repeat the process for all the remaining bits we obtain a simplified DFT formula which is the basis of the radix-2 decimation-in-time algorithm, % \begin{eqnarray} h([a_{n-1} \dots a_1 a_0]) &=& \sum_{b_0=0}^{1} W_{N}^{[a_{n-1} \dots a_1 a_0]b_0} %\sum_{b_1=0}^{1} %W_{N/2}^{[a_{n-1} \dots a_1 a_0]b_1} \dots \sum_{b_{n-2}=0}^{1} W_4^{ [a_1 a_0] b_{n-2}} \sum_{b_{n-1}=0}^{1} W_2^{a_0 b_{n-1}} g(b) \end{eqnarray} % To convert the formula to an algorithm we expand out the sum recursively, evaluating each of the intermediate summations, which we denote by $g_1$, $g_2$, \dots, $g_n$, % \begin{eqnarray} g_1(a_0, b_{n-2}, b_{n-3}, \dots, b_1, b_0) &=& \sum_{b_{n-1}=0}^{1} W_2^{a_0 b_{n-1}} g([b_{n-1} b_{n-2} b_{n-3} \dots b_1 b_0]) \\ g_2(a_0, {}_{\phantom{-2}} a_{1}, b_{n-3}, \dots, b_1, b_0) &=& \sum_{b_{n-2}=0}^{1} W_4^{[a_1 a_0] b_{n-2}} g_1(a_0, b_{n-2}, b_{n-3}, \dots, b_1, b_0) \\ g_3(a_0, {}_{\phantom{-2}} a_{1}, {}_{\phantom{-3}} a_{2}, \dots, b_1, b_0) &=& \sum_{b_{n-3}=0}^{1} W_8^{[a_2 a_1 a_0] b_{n-2}} g_2(a_0, a_1, b_{n-3}, \dots, b_1, b_0) \\ \dots &=& \dots \\ g_n(a_0, a_{1}, a_{2}, \dots, a_{n-2}, a_{n-1}) &=& \sum_{b_{0}=0}^{1} W_N^{[a_{n-1} \dots a_1 a_0]b_0} g_{n-1}(a_0, a_1, a_2, \dots, a_{n-2}, b_0) \end{eqnarray} % After the final sum, we can obtain the transform $h$ from $g_n$, % \begin{equation} h([a_{n-1} \dots a_1 a_0]) = g_n(a_0, a_1, \dots, a_{n-1}) \end{equation} % Note that we left the storage arrangements of the intermediate sums unspecified by using the bits as function arguments and not as an index. The storage of intermediate sums is different for the decimation-in-time and decimation-in-frequency algorithms. Before deciding on the best storage scheme we'll show that the results of each stage, $g_1$, $g_2$, \dots, can be carried out {\em in-place}. For example, in the case of $g_1$, the inputs are, % \begin{equation} g([\underline{b_{n-1}} b_{n-2} b_{n-3} \dots b_1 b_0]) \end{equation} % for $b_{n-1}=(0,1)$, and the corresponding outputs are, % \begin{equation} g_1(\underline{a_0},b_{n-2}, b_{n-3}, \dots, b_1, b_0) \end{equation} % for $a_0=(0,1)$. It's clear that if we hold $b_{n-2}, b_{n-3}, \dots, b_1, b_0$ fixed and compute the sum over $b_{n-1}$ in memory for both values of $a_0 = 0,1$ then we can store the result for $a_0=0$ in the location which originally had $b_0=0$ and the result for $a_0=1$ in the location which originally had $b_0=1$. The two inputs and two outputs are known as {\em dual node pairs}. At each stage of the calculation the sums for each dual node pair are independent of the others. It is this property which allows an in-place calculation. So for an in-place pass our storage has to be arranged so that the two outputs $g_1(a_0,\dots)$ overwrite the two input terms $g([b_{n-1},\dots])$. Note that the order of $a$ is reversed from the natural order of $b$. i.e. the least significant bit of $a$ replaces the most significant bit of $b$. This is inconvenient because $a$ occurs in its natural order in all the exponentials, $W^{ab}$. We could keep track of both $a$ and its bit-reverse, $a^{\mathit bit-reversed}$ at all times but there is a neat trick which avoids this: if we bit-reverse the order of the input data $g$ before we start the calculation we can also bit-reverse the order of $a$ when storing intermediate results. Since the storage involving $a$ was originally in bit-reversed order the switch in the input $g$ now allows us to use normal ordered storage for $a$, the same ordering that occurs in the exponential factors. This is complicated to explain, so here is an example of the 4 passes needed for an $N=16$ decimation-in-time FFT, with the initial data stored in bit-reversed order, % \begin{eqnarray} g_1([b_0 b_1 b_2 a_0]) &=& \sum_{b_3=0}^{1} W_2^{a_0 b_3} g([b_0 b_1 b_2 b_3]) \\ g_2([b_0 b_1 a_1 a_0]) &=& \sum_{b_2=0}^{1} W_4^{[a_1 a_0] b_2} g_1([b_0 b_1 b_2 a_0]) \\ g_3([b_0 a_2 a_1 a_0]) &=& \sum_{b_1=0}^{1} W_8^{[a_2 a_1 a_0] b_1} g_2([b_0 b_1 a_1 a_0]) \\ h(a) = g_4([a_3 a_2 a_1 a_0]) &=& \sum_{b_0=0}^{1} W_{16}^{[a_3 a_2 a_1 a_0] b_0} g_3([b_0 a_2 a_1 a_0]) \end{eqnarray} % We compensate for the bit reversal of the input data by accessing $g$ with the bit-reversed form of $b$ in the first stage. This ensures that we are still carrying out the same calculation, using the same data, and not accessing different values. Only single bits of $b$ ever occur in the exponential so we never need the bit-reversed form of $b$. Let's examine the third pass in detail, % \begin{equation} g_3([b_0 a_2 a_1 a_0]) = \sum_{b_1=0}^{1} W_8^{[a_2 a_1 a_0] b_1} g_2([b_0 b_1 a_1 a_0]) \end{equation} % First note that only one bit, $b_1$, varies in each summation. The other bits of $b$ ($b_0$) and of $a$ ($a_1 a_0$) are essentially ``spectators'' -- we must loop over all combinations of these bits and carry out the same basic calculation for each, remembering to update the exponentials involving $W_8$ appropriately. If we are storing the results in-place (with $g_3$ overwriting $g_2$ we will need to compute the sums involving $b_1=0,1$ and $a_2=0,1$ simultaneously. % \begin{equation} \left( \begin{array}{c} g_3([b_0 0 a_1 a_0]) \vphantom{W_8^{[]}} \\ g_3([b_0 1 a_1 a_0]) \vphantom{W_8^{[]}} \end{array} \right) = \left( \begin{array}{c} g_2([b_0 0 a_1 a_0]) + W_8^{[0 a_1 a_0]} g_2([b_2 1 a_1 a_0]) \\ g_2([b_0 0 a_1 a_0]) + W_8^{[1 a_1 a_0]} g_2([b_2 1 a_1 a_0]) \end{array} \right) \end{equation} % We can write this in a more symmetric form by simplifying the exponential, % \begin{equation} W_8^{[a_2 a_1 a_0]} = W_8^{4 a_2 + [a_1 a_0]} = (-1)^{a_2} W_8^{[a_1 a_0]} \end{equation} % \begin{equation} \left( \begin{array}{c} g_3([b_0 0 a_1 a_0]) \vphantom{W_8^{[]}} \\ g_3([b_0 1 a_1 a_0]) \vphantom{W_8^{[]}} \end{array} \right) = \left( \begin{array}{c} g_2([b_0 0 a_1 a_0]) + W_8^{[a_1 a_0]} g_2([b_2 1 a_1 a_0]) \\ g_2([b_0 0 a_1 a_0]) - W_8^{[a_1 a_0]} g_2([b_2 1 a_1 a_0]) \end{array} \right) \end{equation} % The exponentials $W_8^{[a_1 a_0]}$ are referred to as {\em twiddle factors}. The form of this calculation, a symmetrical sum and difference involving a twiddle factor is called {\em a butterfly}. It is often shown diagrammatically, and in the case $b_0=a_0=a_1=0$ would be drawn like this, % \begin{center} \setlength{\unitlength}{1cm} \begin{picture}(9,4) % %\put(0,0){\line(1,0){8}} %\put(0,0){\line(0,1){4}} %\put(8,4){\line(0,-1){4}} %\put(8,4){\line(-1,0){8}} % \put(0,1){$g_2(4)$} \put(4.5,1){$g_3(4)=g_2(0) - W^a_8 g_2(4)$} \put(0,3){$g_2(0)$} \put(4.5,3){$g_3(0)=g_2(0) + W^a_8 g_2(4)$} \put(1,1){\vector(1,0){0.5}} \put(1.5,1){\line(1,0){0.5}} \put(1.5,0.5){$W^a_8$} \put(1,3){\vector(1,0){0.5}}\put(1.5,3){\line(1,0){0.5}} \put(2,1){\circle*{0.1}} \put(2,3){\circle*{0.1}} \put(2,1){\vector(1,1){2}} \put(2,1){\vector(1,0){1}} \put(3,1){\line(1,0){1}} \put(3,0.5){$-1$} \put(2,3){\vector(1,-1){2}} \put(2,3){\vector(1,0){1}} \put(3,3){\line(1,0){1}} \put(4,1){\circle*{0.1}} \put(4,3){\circle*{0.1}} \end{picture} \end{center} % The inputs are shown on the left and the outputs on the right. The outputs are computed by multiplying the incoming lines by their accompanying factors (shown next to the lines) and summing the results at each node. In general, denoting the bit for dual-node pairs by $\Delta$ and the remaining bits of $a$ and $b$ by ${\hat a}$ and ${\hat b}$, the butterfly is, % \begin{equation} \left( \begin{array}{c} g({\hat b} + {\hat a}) \\ g({\hat b} + \Delta + {\hat a}) \\ \end{array} \right) \leftarrow \left( \begin{array}{c} g({\hat b} + {\hat a}) + W_{2\Delta}^{\hat a} g({\hat b} + \Delta + {\hat a})\\ g({\hat b} + {\hat a}) - W_{2\Delta}^{\hat a} g({\hat b} + \Delta + {\hat a}) \end{array} \right) \end{equation} % where ${\hat a}$ runs from $0 \dots \Delta-1$ and ${\hat b}$ runs through $0 \times 2\Delta$, $1\times 2\Delta$, $\dots$, $(N/\Delta - 1)2\Delta$. The value of $\Delta$ is 1 on the first pass, 2 on the second pass and $2^{n-1}$ on the $n$-th pass. Each pass requires $N/2$ in-place computations, each involving two input locations and two output locations. In the example above $\Delta = [100] = 4$, ${\hat a} = [a_1 a_0]$ and ${\hat b} = [b_0 0 0 0]$. This leads to the canonical radix-2 decimation-in-time FFT algorithm for $2^n$ data points stored in the array $g(0) \dots g(2^n-1)$. % \begin{algorithm} \STATE bit-reverse ordering of $g$ \STATE {$\Delta \Leftarrow 1$} \FOR {$\mbox{pass} = 1 \dots n$} \STATE {$W \Leftarrow \exp(-2 \pi i / 2\Delta)$} \FOR {$(a = 0 ; a < \Delta ; a{++})$} \FOR{$(b = 0 ; b < N ; b {+=} 2*\Delta)$} \STATE{$t_0 \Leftarrow g(b+a) + W^a g(b+\Delta+a)$} \STATE{$t_1 \Leftarrow g(b+a) - W^a g(b+\Delta+a)$} \STATE{$g(b+a) \Leftarrow t_0$} \STATE{$g(b+\Delta+a) \Leftarrow t_1$} \ENDFOR \ENDFOR \STATE{$\Delta \Leftarrow 2\Delta$} \ENDFOR \end{algorithm} % %This algorithm appears in Numerical Recipes as the routine {\tt %four1}, where the implementation is attributed to N.M. Brenner. % \subsection{Details of the Implementation} It is straightforward to implement a simple radix-2 decimation-in-time routine from the algorithm above. Some optimizations can be made by pulling the special case of $a=0$ out of the loop over $a$, to avoid unnecessary multiplications when $W^a=1$, % \begin{algorithm} \FOR{$(b = 0 ; b < N ; b {+=} 2*\Delta)$} \STATE{$t_0 \Leftarrow g(b) + g(b+\Delta)$} \STATE{$t_1 \Leftarrow g(b) - g(b+\Delta)$} \STATE{$g(b) \Leftarrow t_0$} \STATE{$g(b+\Delta) \Leftarrow t_1$} \ENDFOR \end{algorithm} % There are several algorithms for doing fast bit-reversal. We use the Gold-Rader algorithm, which is simple and does not require any working space, % \begin{algorithm} \FOR{$i = 0 \dots n - 2$} \STATE {$ k = n / 2 $} \IF {$i < j$} \STATE {swap $g(i)$ and $g(j)$} \ENDIF \WHILE {$k \leq j$} \STATE{$j \Leftarrow j - k$} \STATE{$k \Leftarrow k / 2$} \ENDWHILE \STATE{$j \Leftarrow j + k$} \ENDFOR \end{algorithm} % The Gold-Rader algorithm is typically twice as fast as a naive bit-reversal algorithm (where the bit reversal is carried out by left-shifts and right-shifts on the index). The library also has a routine for the Rodriguez bit reversal algorithm, which also does not require any working space~\cite{rodriguez89}. There are faster bit reversal algorithms, but they all use additional scratch space~\cite{rosel89}. Within the loop for $a$ we can compute $W^a$ using a trigonometric recursion relation, % \begin{eqnarray} W^{a+1} &=& W W^a \\ &=& (\cos(2\pi/2\Delta) + i \sin(2\pi/2\Delta)) W^a \end{eqnarray} % This requires only $2 \log_2 N$ trigonometric calls, to compute the initial values of $\exp(2\pi i /2\Delta)$ for each pass. \subsection{Radix-2 Decimation-in-Frequency (DIF)} % To derive the decimation-in-frequency algorithm we start by separating out the lowest order bit of the index $a$. Here is an example for the decimation-in-frequency $N=16$ DFT. % \begin{eqnarray} W_{16}^{[a_3 a_2 a_1 a_0][b_3 b_2 b_1 b_0]} &=& W_{16}^{[a_3 a_2 a_1 a_0][b_2 b_1 b_0]} W_{16}^{[a_3 a_2 a_1 a_0] [b_3 0 0 0]} \\ &=& W_8^{[a_3 a_2 a_1][b_2 b_1 b_0]} W_{16}^{a_0 [b_2 b_1 b_0]} W_2^{a_0 b_3} \\ &=& W_8^{[a_3 a_2 a_1][b_2 b_1 b_0]} W_{16}^{a_0 [b_2 b_1 b_0]} (-1)^{a_0 b_3} \end{eqnarray} % By repeating the same type of the expansion on the term, % \begin{equation} W_8^{[a_3 a_2 a_1][b_2 b_1 b_0]} \end{equation} % we can reduce the transform to an alternative simple form, % \begin{equation} h(a) = \sum_{b_0=0}^1 (-1)^{a_3 b_0} W_4^{a_2 b_0} \sum_{b_1=0}^1 (-1)^{a_2 b_1} W_8^{a_1 [b_1 b_0]} \sum_{b_2=0}^1 (-1)^{a_1 b_2} W_{16}^{a_0 [b_2 b_1 b_0]} \sum_{b_3=0}^1 (-1)^{a_0 b_3} g(b) \end{equation} % To implement this we can again write the sum recursively. In this case we do not have the problem of the order of $a$ being bit reversed -- the calculation can be done in-place using the natural ordering of $a$ and $b$, % \begin{eqnarray} g_1([a_0 b_2 b_1 b_0]) &=& W_{16}^{a_0 [b_2 b_1 b_0]} \sum_{b_3=0}^1 (-1)^{a_0 b_3} g([b_3 b_2 b_1 b_0]) \\ g_2([a_0 a_1 b_1 b_0]) &=& W_{8}^{a_1 [b_1 b_0]} \sum_{b_2=0}^1 (-1)^{a_1 b_2} g_1([a_0 b_2 b_1 b_0]) \\ g_3([a_0 a_1 a_2 b_0]) &=& W_{4}^{a_2 b_0} \sum_{b_1=0}^1 (-1)^{a_2 b_1} g_2([a_0 a_1 b_1 b_0]) \\ h(a) = g_4([a_0 a_1 a_2 a_3]) &=& \sum_{b_0=0}^1 (-1)^{a_3 b_0} g_3([a_0 a_1 a_2 b_0]) \end{eqnarray} % The final pass leaves the data for $h(a)$ in bit-reversed order, but this is easily fixed by a final bit-reversal of the ordering. The basic in-place calculation or butterfly for each pass is slightly different from the decimation-in-time version, % \begin{equation} \left( \begin{array}{c} g({\hat a} + {\hat b}) \\ g({\hat a} + \Delta + {\hat b}) \\ \end{array} \right) \leftarrow \left( \begin{array}{c} g({\hat a} + {\hat b}) + g({\hat a} + \Delta + {\hat b})\\ W_{\Delta}^{\hat b} \left( g({\hat a} + {\hat b}) - g({\hat a} + \Delta + {\hat b}) \right) \end{array} \right) \end{equation} % In each pass ${\hat b}$ runs from $0 \dots \Delta-1$ and ${\hat a}$ runs from $0, 2\Delta, \dots, (N/\Delta -1) \Delta$. On the first pass we start with $\Delta=16$, and on subsequent passes $\Delta$ takes the values $8, 4, \dots, 1$. This leads to the canonical radix-2 decimation-in-frequency FFT algorithm for $2^n$ data points stored in the array $g(0) \dots g(2^n-1)$. % \begin{algorithm} \STATE {$\Delta \Leftarrow 2^{n-1}$} \FOR {$\mbox{pass} = 1 \dots n$} \STATE {$W \Leftarrow \exp(-2 \pi i / 2\Delta)$} \FOR {$(b = 0 ; b < \Delta ; b++)$} \FOR{$(a = 0 ; a < N ; a += 2*\Delta)$} \STATE{$t_0 \Leftarrow g(b+a) + g(a+\Delta+b)$} \STATE{$t_1 \Leftarrow W^b \left( g(a+b) - g(a+\Delta+b) \right)$} \STATE{$g(a+b) \Leftarrow t_0$} \STATE{$g(a+\Delta+b) \Leftarrow t_1$} \ENDFOR \ENDFOR \STATE{$\Delta \Leftarrow \Delta/2$} \ENDFOR \STATE bit-reverse ordering of $g$ \end{algorithm} % \section{Self-Sorting Mixed-Radix Complex FFTs} % This section is based on the review article {\em Self-sorting Mixed-Radix Fast Fourier Transforms} by Clive Temperton~\cite{temperton83}. You should consult his article for full details of all the possible algorithms (there are many variations). Here I have annotated the derivation of the simplest mixed-radix decimation-in-frequency algorithm. For general-$N$ FFT algorithms the simple binary-notation of radix-2 algorithms is no longer useful. The mixed-radix FFT has to be built up using products of matrices acting on a data vector. The aim is to take the full DFT matrix $W_N$ and factor it into a set of small, sparse matrices corresponding to each factor of $N$. We'll denote the components of matrices using either subscripts or function notation, % \begin{equation} M_{ij} = M(i,j) \end{equation} % with (C-like) indices running from 0 to $N-1$. Matrix products will be denoted using square brackets, % \begin{equation} [AB]_{ij} = \sum_{k} A_{ik} B_{kj} \end{equation} % % Three special matrices will be needed in the mixed-radix factorization of the DFT: the identity matrix, $I$, a permutation matrix, $P$ and a matrix of twiddle factors, $D$, as well as the normal DFT matrices $W_n$. We write the identity matrix of order $r$ as $I_r(n,m)$, % \begin{equation} I_r(n,m) = \delta_{nm} \end{equation} % for $0 \leq n,m \leq r-1$. We also need to define a permutation matrix $P^a_b$ that performs digit reversal of the ordering of a vector. If the index of a vector $j= 0\dots N-1$ is factorized into $j = la +m$, with $0 \leq l \leq b-1$ and $0 \leq m \leq a-1$ then the operation of the matrix $P$ will exchange positions $la+m$ and $mb+l$ in the vector (this sort of digit-reversal is the generalization of bit-reversal to a number system with exponents $a$ and $b$). In mathematical terms $P$ is a square matrix of size $ab \times ab$ with the property, % \begin{eqnarray} P^a_b(j,k) &=& 1 ~\mbox{if}~ j=ra+s ~\mbox{and}~ k=sb+r \\ &=& 0 ~\mbox{otherwise} \end{eqnarray} % Finally the FFT algorithm needs a matrix of twiddle factors, $D^a_b$, for the trigonometric sums. $D^a_b$ is a diagonal square matrix of size $ab \times ab$ with the definition, % \begin{eqnarray} D^a_b(j,k) &=& \omega^{sr}_{ab} ~\mbox{if}~ j=k=sb+r \\ &=& 0 ~\mbox{otherwise} \end{eqnarray} % where $\omega_{ab} = e^{-2\pi i/ab}$. \subsection{The Kronecker Product} The Kronecker matrix product plays an important role in all the algorithms for combining operations on different subspaces. The Kronecker product $A \otimes B$ of two square matrices $A$ and $B$, of sizes $a \times a$ and $b \times b$ respectively, is a square matrix of size $a b \times a b$, defined as, % \begin{equation} [A \otimes B] (tb+u, rb+s) = A(t,r) B(u,s) \end{equation} % where $0 \leq u,s < b$ and $0 \leq t,r < a$. Let's examine a specific example. If we take a $2 \times 2$ matrix and a $3 \times 3$ matrix, % \begin{equation} \begin{array}{ll} A = \left( \begin{array}{cc} a_{11} & a_{12} \\ a_{21} & a_{22} \end{array} \right) & B = \left( \begin{array}{ccc} b_{11} & b_{12} & b_{13} \\ b_{21} & b_{22} & b_{23} \\ b_{31} & b_{32} & b_{33} \end{array} \right) \end{array} \end{equation} % then the Kronecker product $A \otimes B$ is, % \begin{eqnarray} A \otimes B &= & \left( \begin{array}{cc} a_{11} B & a_{12} B \\ a_{12} B & a_{22} B \end{array} \right) \\ &=& \left( \begin{array}{cccccc} a_{11} b_{11} & a_{11} b_{12} & a_{11} b_{13} & a_{12} b_{11} & a_{12} b_{12} & a_{12} b_{13} \\ a_{11} b_{21} & a_{11} b_{22} & a_{11} b_{23} & a_{12} b_{21} & a_{12} b_{22} & a_{12} b_{23} \\ a_{11} b_{31} & a_{11} b_{32} & a_{11} b_{33} & a_{12} b_{31} & a_{12} b_{32} & a_{12} b_{33} \\ a_{21} b_{11} & a_{21} b_{12} & a_{21} b_{13} & a_{22} b_{11} & a_{22} b_{12} & a_{22} b_{13} \\ a_{21} b_{21} & a_{21} b_{22} & a_{21} b_{23} & a_{22} b_{21} & a_{22} b_{22} & a_{22} b_{23} \\ a_{21} b_{31} & a_{21} b_{32} & a_{21} b_{33} & a_{22} b_{31} & a_{22} b_{32} & a_{22} b_{33} \end{array} \right) \end{eqnarray} % When the Kronecker product $A \otimes B$ acts on a vector of length $ab$, each matrix operates on a different subspace of the vector. Writing the index $i$ as $i=t b + u$, with $0\leq u \leq b-1$ and $0\leq t\leq a$, we can see this explicitly by looking at components, % \begin{eqnarray} [(A \otimes B) v]_{(tb+u)} & = & \sum_{t'=0}^{a-1} \sum_{u'=0}^{b-1} [A \otimes B]_{(tb+u,t'b+u')} v_{t'b+u'} \\ & = & \sum_{t'u'} A_{tt'} B_{uu'} v_{t'b+u'} \end{eqnarray} % The matrix $B$ operates on the ``index'' $u'$, for all values of $t'$, and the matrix $A$ operates on the ``index'' $t'$, for all values of $u'$. % The most important property needed for deriving the FFT factorization is that the matrix product of two Kronecker products is the Kronecker product of the two matrix products, % \begin{equation} (A \otimes B)(C \otimes D) = (AC \otimes BD) \end{equation} % This follows straightforwardly from the original definition of the Kronecker product. \subsection{Two factor case, $N=ab$} % First consider the simplest possibility, where the data length $N$ can be divided into two factors, $N=ab$. The aim is to reduce the DFT matrix $W_N$ into simpler matrices corresponding to each factor. To make the derivation easier we will start from the known factorization and verify it (the initial factorization can be guessed by generalizing from simple cases). Here is the factorization we are going to prove, % \begin{equation} W_{ab} = (W_b \otimes I_a) P^a_b D^a_b (W_a \otimes I_b). \end{equation} % We can check it by expanding the product into components, % \begin{eqnarray} \lefteqn{[(W_b \otimes I_a) P^a_b D^a_b (W_a \otimes I_b)](la+m,rb+s)} \\ & = & \sum_{u=0}^{b-1} \sum_{t=0}^{a-1} [(W_b \otimes I_a)](la+m,ua+t) [P^a_b D^a_b (W_a \otimes I_b)](ua+t,rb+s) \end{eqnarray} % where we have split the indices to match the Kronecker product $0 \leq m, r \leq a$, $0 \leq l, s \leq b$. The first term in the sum can easily be reduced to its component form, % \begin{eqnarray} [(W_b \otimes I_a)](la+m,ua+t) &=& W_b(l,u) I_a(m,t) \\ &=& \omega_b^{lu} \delta_{mt} \end{eqnarray} % The second term is more complicated. We can expand the Kronecker product like this, \begin{eqnarray} (W_a \otimes I_b)(tb+u,rb+s) &=& W_a(t,r) I_a(u,s) \\ &=& \omega_a^{tr} \delta_{us} \end{eqnarray} % and use this term to build up the product, $P^a_b D^a_b (W_a \otimes I_b)$. We first multiply by $D^a_b$, % \begin{equation} [D^a_b (W_a \otimes I_b)](tb+u,rb+s) = \omega^{tu}_{ab} \omega^{tr}_{a} \delta_{su} \end{equation} % and then apply the permutation matrix, $P^a_b$, which digit-reverses the ordering of the first index, to obtain, % \begin{equation} [P^a_b D^a_b (W_a \otimes I_b)](ua+t,rb+s) = \omega^{tu}_{ab} \omega^{tr}_{a} \delta_{su} \end{equation} % Combining the two terms in the matrix product we can obtain the full expansion in terms of the exponential $\omega$, % \begin{eqnarray} [(W_b \otimes I_a) P^a_b D^a_b (W_a \otimes I_b)](la+m,rb+s) &=& \sum_{u=0}^{b-1} \sum_{t=0}^{a-1} \omega_b^{lu} \delta_{mt} \omega^{tu}_{ab} \omega^{tr}_{a} \delta_{su} \end{eqnarray} % If we evaluate this sum explicitly we can make the connection between the product involving $W_a$ and $W_b$ (above) and the expansion of the full DFT matrix $W_{ab}$, % \begin{eqnarray} \sum_{u=0}^{b-1} \sum_{t=0}^{a-1} \omega_b^{lu} \delta_{mt} \omega^{tu}_{ab} \omega^{tr}_{a} \delta_{su} &=& \omega^{ls}_b \omega^{ms}_{ab} \omega^{mr}_a \\ &=& \omega^{als + ms +bmr}_{ab} \\ &=& \omega^{als + ms +bmr}_{ab} \omega^{lrab}_{ab} \quad\mbox{using~} \omega^{ab}_{ab} =1\\ &=& \omega^{(la+m)(rb+s)}_{ab} \\ &=& W_{ab}(la+m,rb+s) \end{eqnarray} % The final line shows that matrix product given above is identical to the full two-factor DFT matrix, $W_{ab}$. % Thus the full DFT matrix $W_{ab}$ for two factors $a$, $b$ can be broken down into a product of sub-transforms, $W_a$ and $W_b$, plus permutations, $P$, and twiddle factors, $D$, according to the formula, % \begin{equation} W_{ab} = (W_b \otimes I_a) P^a_b D^a_b (W_a \otimes I_b). \end{equation} % This relation is the foundation of the general-$N$ mixed-radix FFT algorithm. \subsection{Three factor case, $N=abc$} % The result for the two-factor expansion can easily be generalized to three factors. We first consider $abc$ as being a product of two factors $a$ and $(bc)$, and then further expand the product $(bc)$ into $b$ and $c$. The first step of the expansion looks like this, % \begin{eqnarray} W_{abc} &=& W_{a(bc)}\\ &=& (W_{bc} \otimes I_a) P^a_{bc} D^a_{bc} (W_a \otimes I_{bc}) . \end{eqnarray} % And after using the two-factor result to expand out $W_{bc}$ we obtain the factorization of $W_{abc}$, % \begin{eqnarray} W_{abc} &=& (((W_c \otimes I_b) P^b_c D^b_c (W_b \otimes I_c)) \otimes I_a ) P^a_{bc} D^a_{bc} (W_a \otimes I_{bc}) \\ &=& (W_c \otimes I_{ab}) (P^b_c D^b_c \otimes I_a) (W_b \otimes I_{ac}) P^a_{bc} D^a_{bc} (W_a \otimes I_c) \end{eqnarray} % We can write this factorization in a product form, with one term for each factor, % \begin{equation} W_{abc} = T_3 T_2 T_1 \end{equation} % where we read off $T_1$, $T_2$ and $T_3$, % \begin{eqnarray} T_1 &=& P^a_{bc} D^a_{bc} (W_a \otimes I_{bc}) \\ T_2 &=& (P^b_c D^b_c \otimes I_a) (W_b \otimes I_{ac}) \\ T_3 &=& (W_c \otimes I_{ab} ) \end{eqnarray} % \subsection{General case, $N=f_1 f_2 \dots f_{n_f}$} % If we continue the procedure that we have used for two- and three-factors then a general pattern begins to emerge in the factorization of $W_{f_1 f_2 \dots f_{n_f}}$. To see the beginning of the pattern we can rewrite the three factor case as, % \begin{eqnarray} T_1 &=& (P^a_{bc} D^a_{bc} \otimes I_1) (W_a \otimes I_{bc}) \\ T_2 &=& (P^b_c D^b_c \otimes I_a) (W_b \otimes I_{ac}) \\ T_3 &=& (P^c_1 D^c_1 \otimes I_{ab}) (W_c \otimes I_{ab} ) \end{eqnarray} % using the special cases $P^c_1 = D^c_1 = I_c$. % In general, we can write the factorization of $W_N$ for $N= f_1 f_2 \dots f_{n_f}$ as, % \begin{equation} W_N = T_{n_f} \dots T_2 T_1 \end{equation} % where the matrix factors $T_i$ are, % \begin{equation} T_i = (P^{f_i}_{q_i} D^{f_i}_{q_i} \otimes I_{p_{i-1}}) ( W_{f_i} \otimes I_{m_i}) \end{equation} % We have defined the following three additional variables $p$, $q$ and $m$ to denote different partial products of the factors, % \begin{eqnarray} p_i &=& f_1 f_2 \dots f_i \quad (p_0 = 1) \\ q_i &=& N / p_i \\ m_i &=& N / f_i \end{eqnarray} % Note that the FFT modules $W$ are applied before the permutations $P$, which makes this a decimation-in-frequency algorithm. \subsection{Implementation} % Now to the implementation of the algorithm. We start with a vector of data, $z$, as input and want to apply the transform, % \begin{eqnarray} x &=& W_N z \\ &=& T_{n_f} \dots T_2 T_1 z \end{eqnarray} % where $T_i = (P^{f_i}_{q_i} D^{f_i}_{q_i} \otimes I_{p_{i-1}}) ( W_{f_i} \otimes I_{m_i})$. The outer structure of the implementation will be a loop over the $n_f$ factors, applying each matrix $T_i$ to the vector in turn to build up the complete transform. % \begin{algorithm} \FOR{$(i = 1 \dots n_f)$} \STATE{$v \Leftarrow T_i v $} \ENDFOR \end{algorithm} % The order of the factors is not important. Now we examine the iteration $v \Leftarrow T_i v$, which we'll write as, % \begin{equation} v' = (P^{f_i}_{q_i} D^{f_i}_{q_i} \otimes I_{p_{i-1}}) ~ ( W_{f_i} \otimes I_{m_i}) v \end{equation} % There are two Kronecker product matrices in this iteration. The rightmost matrix, which is the first to be applied, is a DFT of length $f_i$ applied to $N/f_i$ subsets of the data. We'll call this $t$, since it will be a temporary array, % \begin{equation} t = (W_{f_i} \otimes I_{m_i}) v \end{equation} % The second matrix applies a permutation and the exponential twiddle-factors. We'll call this $v'$, since it is the result of the full iteration on $v$, % \begin{equation} v' = (P^{f_i}_{q_i} D^{f_i}_{q_i} \otimes I_{p_{i-1}}) t \end{equation} The effect of the matrix $(W_{f_i} \otimes I_{m_i})$ is best seen by an example. Suppose the factor is $f_i = 3$, and the length of the FFT is $N=6$, then the relevant Kronecker product is, % \begin{equation} t = (W_3 \otimes I_2) v \end{equation} % which expands out to, % \begin{equation} \left( \begin{array}{c} t_0 \\ t_1 \\ t_2 \\ t_3 \\ t_4 \\ t_5 \end{array} \right) = \left( \begin{array}{cccccc} W_3(1,1) & 0 & W_3(1,2) & 0 & W_3(1,3) & 0 \\ 0 & W_3(1,1) & 0 & W_3(1,2) & 0 & W_3(1,3) \\ W_3(2,1) & 0 & W_3(2,2) & 0 & W_3(2,3) & 0 \\ 0 & W_3(2,1) & 0 & W_3(2,2) & 0 & W_3(2,3) \\ W_3(3,1) & 0 & W_3(3,2) & 0 & W_3(3,3) & 0 \\ 0 & W_3(3,1) & 0 & W_3(3,2) & 0 & W_3(3,3) \end{array} \right) \left( \begin{array}{c} v_0 \\ v_1 \\ v_2 \\ v_3 \\ v_4 \\ v_5 \end{array} \right) \end{equation} % We can rearrange the components in a computationally convenient form, \begin{equation} \left( \begin{array}{c} t_0 \\ t_2 \\ t_4 \\ t_1 \\ t_3 \\ t_5 \end{array} \right) = \left( \begin{array}{cccccc} W_3(1,1) & W_3(1,2) & W_3(1,3) & 0 & 0 & 0 \\ W_3(2,1) & W_3(2,2) & W_3(2,3) & 0 & 0 & 0 \\ W_3(3,1) & W_3(3,2) & W_3(3,3) & 0 & 0 & 0 \\ 0 & 0 & 0 & W_3(1,1) & W_3(1,2) & W_3(1,3) \\ 0 & 0 & 0 & W_3(2,1) & W_3(2,2) & W_3(2,3) \\ 0 & 0 & 0 & W_3(3,1) & W_3(3,2) & W_3(3,3) \end{array} \right) \left( \begin{array}{c} v_0 \\ v_2 \\ v_4 \\ v_1 \\ v_3 \\ v_5 \end{array} \right) \end{equation} % which clearly shows that we just need to apply the $3\times 3$ DFT matrix $W_3$ twice, once to the sub-vector of elements $(v_0, v_2, v_4)$, and independently to the remaining sub-vector $(v_1, v_3, v_5)$. In the general case, if we index $t$ as $t_k = t(\lambda,\mu) = t_{\lambda m + \mu}$ then $\lambda = 0 \dots f-1$ is an index within each transform of length $f$ and $\mu = 0 \dots m-1$ labels the independent subsets of data. We can see this by showing the calculation with all indices present, % \begin{equation} t = (W_f \otimes I_m) z \end{equation} % becomes, % \begin{eqnarray} t_{\lambda m + \mu} &=& \sum_{\lambda'=0}^{f-1} \sum_{\mu'=0}^{m-1} (W_f \otimes I_m)_{(\lambda m + \mu)(\lambda' m + \mu')} z_{\lambda'm + \mu} \\ &=& \sum_{\lambda'\mu'} (W_f)_{\lambda\lambda'} \delta_{\mu\mu'} z_{\lambda'm+\mu'} \\ &=& \sum_{\lambda'} (W_f)_{\lambda\lambda'} z_{\lambda'm+\mu} \end{eqnarray} % The DFTs on the index $\lambda$ will be computed using special optimized modules for each $f$. To calculate the next stage, % \begin{equation} v'=(P^f_q D^f_q \otimes I_{p_{i-1}}) t \end{equation} % we note that the Kronecker product has the property of performing $p_{i-1}$ independent multiplications of $PD$ on $q_{i-1}$ different subsets of $t$. The index $\mu$ of $t(\lambda,\mu)$ which runs from 0 to $m$ will include $q_i$ copies of each $PD$ operation because $m=p_{i-1}q$. i.e. we can split the index $\mu$ further into $\mu = a p_{i-1} + b$, where $a = 0 \dots q-1$ and $b=0 \dots p_{i-1}$, % \begin{eqnarray} \lambda m + \mu &=& \lambda m + a p_{i-1} + b \\ &=& (\lambda q + a) p_{i-1} + b. \end{eqnarray} % Now we can expand the second stage, % \begin{eqnarray} v'&=& (P^f_q D^f_q \otimes I_{p_{i-1}}) t \\ v'_{\lambda m + \mu} &=& \sum_{\lambda' \mu'} (P^f_q D^f_q \otimes I_{p_{i-1}})_{(\lambda m + \mu)(\lambda' m + \mu')} t_{\lambda' m + \mu'} \\ v'_{(\lambda q + a) p_{i-1} + b} &=& \sum_{\lambda' a' b'} ( P^f_q D^f_q \otimes I_{p_{i-1}} )_{((\lambda q+ a)p_{i-1} + b)((\lambda' q+ a')p_{i-1} + b')} t_{(\lambda' q + a')p_{i-1} +b'} \end{eqnarray} % The first step in removing redundant indices is to take advantage of the identity matrix $I$ and separate the subspaces of the Kronecker product, % \begin{equation} ( P^f_q D^f_q \otimes I_{p_{i-1}} )_{((\lambda q+ a)p_{i-1} + b)((\lambda' q+ a')p_{i-1} + b')} = (P^f_q D^f_q)_{(\lambda q + a)(\lambda' q + a')} \delta_{bb'} \end{equation} % This eliminates one sum, leaving us with, % \begin{equation} v'_{(\lambda q + a) p_{i-1} + b} = \sum_{\lambda' a' } (P^f_q D^f_q)_{(\lambda q + a)(\lambda' q + a')} t_{(\lambda'q+a')p_{i-1} + b} \end{equation} % We can insert the definition of $D^f_q$ to give, % \begin{equation} \phantom{v'_{(\lambda q + a) p_{i-1} + b}} = \sum_{\lambda'a'} (P^f_q)_{(\lambda q + a)(\lambda'q + a')} \omega^{\lambda'a'}_{q_{i-1}} t_{(\lambda'q+a')p_{i-1}+b} \end{equation} % Using the definition of $P^f_q$, which exchanges an index of $\lambda q + a$ with $a f + \lambda$, we get a final result with no matrix multiplication, % \begin{equation} v'_{(a f + \lambda) p_{i-1} + b} = \omega^{\lambda a}_{q_{i-1}} t_{(\lambda q + a)p_{i-1} + b} \end{equation} % All we have to do is premultiply each element of the temporary vector $t$ by an exponential twiddle factor and store the result in another index location, according to the digit reversal permutation of $P$. Here is the algorithm to implement the mixed-radix FFT, % \begin{algorithm} \FOR{$i = 1 \dots n_f$} \FOR{$a = 0 \dots q-1$} \FOR{$b = 0 \dots p_{i-1} - 1$} \FOR{$\lambda = 0 \dots f-1$} \STATE{$t_\lambda \Leftarrow \sum_{\lambda'=0}^{f-1} W_f(\lambda,\lambda') v_{b+\lambda'm+ap_{i-1}}$} \COMMENT{DFT matrix-multiply module} \ENDFOR \FOR{$\lambda = 0 \dots f-1$} \STATE{$v'_{(af+\lambda)p_{i-1}+b} \Leftarrow \omega^{\lambda a}_{q_{i-1}} t_\lambda$} \ENDFOR \ENDFOR \ENDFOR \STATE{$v \Leftarrow v'$} \ENDFOR \end{algorithm} % \subsection{Details of the implementation} % First the function {\tt gsl\_fft\_complex\_wavetable\_alloc} allocates $n$ elements of scratch space (to hold the vector $v'$ for each iteration) and $n$ elements for a trigonometric lookup table of twiddle factors. Then the length $n$ must be factorized. There is a general factorization function {\tt gsl\_fft\_factorize} which takes a list of preferred factors. It first factors out the preferred factors and then removes general remaining prime factors. The algorithm used to generate the trigonometric lookup table is % \begin{algorithm} \FOR {$a = 1 \dots n_f$} \FOR {$b = 1 \dots f_i - 1$} \FOR {$c = 1 \dots q_i$} \STATE $\mbox{trig[k++]} = \exp(- 2\pi i b c p_{a-1}/N)$ \ENDFOR \ENDFOR \ENDFOR \end{algorithm} % Note that $\sum_{1}^{n_f} \sum_{0}^{f_i-1} \sum_{1}^{q_i} = \sum_{1}^{n_f} (f_i-1)q_i = n - 1$ so $n$ elements are always sufficient to store the lookup table. This is chosen because we need to compute $\omega_{q_i-1}^{\lambda a} t_\lambda$ in the FFT. In terms of the lookup table we can write this as, % \begin{eqnarray} \omega_{q_{i-1}}^{\lambda a} t_\lambda &=& \exp(-2\pi i \lambda a/q_{i-1}) t_\lambda \\ &=& \exp(-2\pi i \lambda a p_{i-1}/N) t_\lambda \\ &=& \left\{ \begin{array}{ll} t_\lambda & a=0 \\ \mbox{trig}[\mbox{twiddle[i]}+\lambda q+(a-1)] t_\lambda & a\not=0 \end{array} \right. \end{eqnarray} % \begin{sloppypar} \noindent The array {\tt twiddle[i]} maintains a set of pointers into {\tt trig} for the starting points for the outer loop. The core of the implementation is {\tt gsl\_fft\_complex}. This function loops over the chosen factors of $N$, computing the iteration $v'=T_i v$ for each pass. When the DFT for a factor is implemented the iteration is handed-off to a dedicated small-$N$ module, such as {\tt gsl\_fft\_complex\_pass\_3} or {\tt gsl\_fft\_complex\_pass\_5}. Unimplemented factors are handled by the general-$N$ routine {\tt gsl\_fft\_complex\_pass\_n}. The structure of one of the small-$N$ modules is a simple transcription of the basic algorithm given above. Here is an example for {\tt gsl\_fft\_complex\_pass\_3}. For a pass with a factor of 3 we have to calculate the following expression, \end{sloppypar}% \begin{equation} v'_{(a f + \lambda) p_{i-1} + b} = \sum_{\lambda' = 0,1,2} \omega^{\lambda a}_{q_{i-1}} W^{\lambda \lambda'}_{3} v_{b + \lambda' m + a p_{i-1}} \end{equation} % for $b = 0 \dots p_{i-1} - 1$, $a = 0 \dots q_{i} - 1$ and $\lambda = 0, 1, 2$. This is implemented as, % \begin{algorithm} \FOR {$a = 0 \dots q-1$} \FOR {$b = 0 \dots p_{i-1} - 1$} \STATE {$ \left( \begin{array}{c} t_0 \\ t_1 \\ t_2 \end{array} \right) = \left( \begin{array}{ccc} W^{0}_3 & W^{0}_3 & W^{0}_3 \\ W^{0}_3 & W^{1}_3 & W^{2}_3 \\ W^{0}_3 & W^{2}_3 & W^{4}_3 \end{array} \right) \left( \begin{array}{l} v_{b + a p_{i-1}} \\ v_{b + a p_{i-1} + m} \\ v_{b + a p_{i-1} +2m} \end{array} \right)$} \STATE {$ v'_{a p_{i} + b} = t_0$} \STATE {$ v'_{a p_{i} + b + p_{i-1}} = \omega^{a}_{q_{i-1}} t_1$} \STATE {$ v'_{a p_{i} + b + 2 p_{i-1}} = \omega^{2a}_{q_{i-1}} t_2$} \ENDFOR \ENDFOR \end{algorithm} % In the code we use the variables {\tt from0}, {\tt from1}, {\tt from2} to index the input locations, % \begin{eqnarray} \mbox{\tt from0} &=& b + a p_{i-1} \\ \mbox{\tt from1} &=& b + a p_{i-1} + m \\ \mbox{\tt from2} &=& b + a p_{i-1} + 2m \end{eqnarray} % and the variables {\tt to0}, {\tt to1}, {\tt to2} to index the output locations in the scratch vector $v'$, % \begin{eqnarray} \mbox{\tt to0} &=& b + a p_{i} \\ \mbox{\tt to1} &=& b + a p_{i} + p_{i-1} \\ \mbox{\tt to2} &=& b + a p_{i} + 2 p_{i-1} \end{eqnarray} % The DFT matrix multiplication is computed using the optimized sub-transform modules given in the next section. The twiddle factors $\omega^a_{q_{i-1}}$ are taken out of the {\tt trig} array. To compute the inverse transform we go back to the definition of the fourier transform and note that the inverse matrix is just the complex conjugate of the forward matrix (with a factor of $1/N$), % \begin{equation} W^{-1}_N = W^*_N / N \end{equation} % Therefore we can easily compute the inverse transform by conjugating all the complex elements of the DFT matrices and twiddle factors that we apply. (An alternative strategy is to conjugate the input data, take a forward transform, and then conjugate the output data). \section{Fast Sub-transform Modules} % To implement the mixed-radix FFT we still need to compute the small-$N$ DFTs for each factor. Fortunately many highly-optimized small-$N$ modules are available, following the work of Winograd who showed how to derive efficient small-$N$ sub-transforms by number theoretic techniques. The algorithms in this section all compute, % \begin{equation} x_a = \sum_{b=0}^{N-1} W_N^{ab} z_b \end{equation} % The sub-transforms given here are the ones recommended by Temperton and differ slightly from the canonical Winograd modules. According to Temperton~\cite{temperton83} they are slightly more robust against rounding errors and trade off some additions for multiplications. % For the $N=2$ DFT, % \begin{equation} \begin{array}{ll} x_0 = z_0 + z_1, & x_1 = z_0 - z_1. \end{array} \end{equation} % For the $N=3$ DFT, % \begin{equation} \begin{array}{lll} t_1 = z_1 + z_2, & t_2 = z_0 - t_1/2, & t_3 = \sin(\pi/3) (z_1 - z_2), \end{array} \end{equation} \begin{equation} \begin{array}{lll} x_0 = z_0 + t_1, & x_1 = t_2 + i t_3, & x_2 = t_2 - i t_3. \end{array} \end{equation} % The $N=4$ transform involves only additions and subtractions, % \begin{equation} \begin{array}{llll} t_1 = z_0 + z_2, & t_2 = z_1 + z_3, & t_3 = z_0 - z_2, & t_4 = z_1 - z_3, \end{array} \end{equation} \begin{equation} \begin{array}{llll} x_0 = t_1 + t_2, & x_1 = t_3 + i t_4, & x_2 = t_1 - t_2, & x_3 = t_3 - i t_4. \end{array} \end{equation} % For the $N=5$ DFT, % \begin{equation} \begin{array}{llll} t_1 = z_1 + z_4, & t_2 = z_2 + z_3, & t_3 = z_1 - z_4, & t_4 = z_2 - z_3, \end{array} \end{equation} \begin{equation} \begin{array}{llll} t_5 = t_1 + t_2, & t_6 = (\sqrt{5}/4) (t_1 - t_2), & t_7 = z_0 - t_5/4, \\ \end{array} \end{equation} \begin{equation} \begin{array}{llll} t_8 = t_7 + t_6, & t_9 = t_7 - t_6, \\ \end{array} \end{equation} \begin{equation} \begin{array}{llll} t_{10} = \sin(2\pi/5) t_3 + \sin(2\pi/10) t_4, & t_{11} = \sin(2\pi/10) t_3 - \sin(2\pi/5) t_4, \end{array} \end{equation} \begin{equation} \begin{array}{llll} x_0 = z_0 + t_5, \end{array} \end{equation} \begin{equation} \begin{array}{llll} x_1 = t_8 + i t_{10}, & x_2 = t_9 + i t_{11}, \end{array} \end{equation} \begin{equation} \begin{array}{llll} x_3 = t_9 - i t_{11}, & x_4 = t_8 - i t_{10}. \end{array} \end{equation} % The DFT matrix for $N=6$ can be written as a combination of $N=3$ and $N=2$ transforms with index permutations, % \begin{equation} \left( \begin{array}{c} x_0 \\ x_4 \\ x_2 \\ \hline x_3 \\ x_1 \\ x_5 \end{array} \right) = \left( \begin{array}{ccc|ccc} & & & & & \\ &W_3& & &W_3& \\ & & & & & \\ \hline & & & & & \\ &W_3& & &-W_3& \\ & & & & & \end{array} \right) \left( \begin{array}{c} z_0 \\ z_2 \\ z_4 \\ \hline z_3 \\ z_5 \\ z_1 \end{array} \right) \end{equation} % This simplification is an example of the Prime Factor Algorithm, which can be used because the factors 2 and 3 are mutually prime. For more details consult one of the books on number theory for FFTs~\cite{elliott82,blahut}. We can take advantage of the simple indexing scheme of the PFA to write the $N=6$ DFT as, % \begin{equation} \begin{array}{lll} t_1 = z_2 + z_4, & t_2 = z_0 - t_1/2, & t_3 = \sin(\pi/3) (z_2 - z_4), \end{array} \end{equation} \begin{equation} \begin{array}{lll} t_4 = z_5 + z_1, & t_5 = z_3 - t_4/2, & t_6 = \sin(\pi/3) (z_5 - z_1), \end{array} \end{equation} \begin{equation} \begin{array}{lll} t_7 = z_0 + t_1, & t_8 = t_2 + i t_3, & t_9 = t_2 - i t_3, \end{array} \end{equation} \begin{equation} \begin{array}{lll} t_{10} = z_3 + t_4, & t_{11} = t_5 + i t_6, & t_{12} = t_5 - i t_6, \end{array} \end{equation} \begin{equation} \begin{array}{lll} x_0 = t_7 + t_{10}, & x_4 = t_8 + t_{11}, & x_2 = t_9 + t_{12}, \end{array} \end{equation} \begin{equation} \begin{array}{lll} x_3 = t_7 - t_{10}, & x_1 = t_8 - t_{11}, & x_5 = t_9 - t_{12}. \end{array} \end{equation} For any remaining general factors we use Singleton's efficient method for computing a DFT~\cite{singleton}. Although it is an $O(N^2)$ algorithm it does reduce the number of multiplications by a factor of 4 compared with a naive evaluation of the DFT. If we look at the general stucture of a DFT matrix, shown schematically below, % \begin{equation} \left( \begin{array}{c} h_0 \\ h_1 \\ h_2 \\ \vdots \\ h_{N-2} \\ h_{N-1} \end{array} \right) = \left( \begin{array}{cccccc} 1 & 1 & 1 & \cdots & 1 & 1 \\ 1 & W & W & \cdots & W & W \\ 1 & W & W & \cdots & W & W \\ \vdots & \vdots & \vdots & \cdots & \vdots & \vdots \\ 1 & W & W & \cdots & W & W \\ 1 & W & W & \cdots & W & W \end{array} \right) \left( \begin{array}{c} g_0 \\ g_1 \\ g_2 \\ \vdots \\ g_{N-2} \\ g_{N-1} \end{array} \right) \end{equation} % we see that the outer elements of the DFT matrix are all unity. We can remove these trivial multiplications but we will still be left with an $(N-1) \times (N-1)$ sub-matrix of complex entries, which would appear to require $(N-1)^2$ complex multiplications. Singleton's method, uses symmetries of the DFT matrix to convert the complex multiplications to an equivalent number of real multiplications. We start with the definition of the DFT in component form, % \begin{equation} a_k + i b_k = \sum_{j=0} (x_j+iy_j)(\cos(2\pi jk/f) - i\sin(2\pi jk/f)) \end{equation} % The zeroth component can be computed using only additions, % \begin{equation} a_0 + i b_0 = \sum_{j=0}^{(f-1)} x_j + i y_j \end{equation} % We can rewrite the remaining components as, % \begin{eqnarray} a_k + i b_k & = x_0 + i y_0 & + \sum_{j=1}^{(f-1)/2} (x_j + x_{f-j}) \cos(2\pi jk/f) + (y_j - y_{f-j}) \sin(2\pi jk/f) \\ & & + i\sum_{j=1}^{(f-1)/2} (y_j + y_{f-j}) \cos(2\pi jk/f) - (x_j - x_{f-j}) \sin(2\pi jk/f) \end{eqnarray} % by using the following trigonometric identities, % \begin{eqnarray} \cos(2\pi(f-j)k/f) &=& \phantom{-}\cos(2\pi jk/f) \\ \sin(2\pi(f-j)k/f) &=& -\sin(2\pi jk/f) \end{eqnarray} % These remaining components can all be computed using four partial sums, % \begin{eqnarray} a_k + i b_k & = & (a^+_k - a^-_k) + i (b^+_k + b^-_k) \\ a_{f-k} + i b_{f-k} & = & (a^+_k + a^-_k) + i (b^+_k - b^-_k) \end{eqnarray} % for $k = 1, 2, \dots, (f-1)/2$, where, % \begin{eqnarray} a^+_k &=& x_0 + \sum_{j=1}^{(f-1)/2} (x_j + x_{f-j}) \cos(2\pi jk/f) \\ a^-_k &=& \phantom{x_0} - \sum_{j=1}^{(f-1)/2} (y_j - y_{f-j}) \sin(2\pi jk/f) \\ b^+_k &=& y_0 + \sum_{j=1}^{(f-1)/2} (y_j + y_{f-j}) \cos(2\pi jk/f) \\ b^-_k &=& \phantom{y_0} - \sum_{j=1}^{(f-1)/2} (x_j - x_{f-j}) \sin(2\pi jk/f) \end{eqnarray} % Note that the higher components $k'=f-k$ can be obtained directly without further computation once $a^+$, $a^-$, $b^+$ and $b^-$ are known. There are $4 \times (f-1)/2$ different sums, each involving $(f-1)/2$ real multiplications, giving a total of $(f-1)^2$ real multiplications instead of $(f-1)^2$ complex multiplications. To implement Singleton's method we make use of the input and output vectors $v$ and $v'$ as scratch space, copying data back and forth between them to obtain the final result. First we use $v'$ to store the terms of the symmetrized and anti-symmetrized vectors of the form $x_j + x_{f-j}$ and $x_j - y_{f-j}$. Then we multiply these by the appropriate trigonometric factors to compute the partial sums $a^+$, $a^-$, $b^+$ and $b^-$, storing the results $a_k + i b_k$ and $a_{f-k} + i b_{f-k}$ back in $v$. Finally we multiply the DFT output by any necessary twiddle factors and place the results in $v'$. \section{FFTs for real data} % This section is based on the articles {\em Fast Mixed-Radix Real Fourier Transforms} by Clive Temperton~\cite{temperton83real} and {\em Real-Valued Fast Fourier Transform Algorithms} by Sorensen, Jones, Heideman and Burrus~\cite{burrus87real}. The DFT of a real sequence has a special symmetry, called a {\em conjugate-complex} or {\em half-complex} symmetry, % \begin{equation} h(a) = h(N-a)^* \end{equation} % The element $h(0)$ is real, and when $N$ is even $h(N/2)$ is also real. It is straightforward to prove the symmetry, % \begin{eqnarray} h(a) &=& \sum W^{ab}_N g(b) \\ h(N-a)^* &=& \sum W^{-(N-a)b}_N g(b)^* \\ &=& \sum W^{-Nb}_N W^{ab}_N g(b) \qquad{(W^N_N=1)} \\ &=& \sum W^{ab}_N g(b) \end{eqnarray} % Real-valued data is very common in practice (perhaps more common that complex data) so it is worth having efficient FFT routines for real data. In principle an FFT for real data should need half the operations of an FFT on the equivalent complex data (where the imaginary parts are set to zero). There are two different strategies for computing FFTs of real-valued data: One strategy is to ``pack'' the real data (of length $N$) into a complex array (of length $N/2$) by index transformations. A complex FFT routine can then be used to compute the transform of that array. By further index transformations the result can actually by ``unpacked'' to the FFT of the original real data. It is also possible to do two real FFTs simultaneously by packing one in the real part and the other in the imaginary part of the complex array. These techniques have some disadvantages. The packing and unpacking procedures always add $O(N)$ operations, and packing a real array of length $N$ into a complex array of length $N/2$ is only possible if $N$ is even. In addition, if two unconnected datasets with very different magnitudes are packed together in the same FFT there could be ``cross-talk'' between them due to a loss of precision. A more straightforward strategy is to start with an FFT algorithm, such as the complex mixed-radix algorithm, and prune out all the operations involving the zero imaginary parts of the initial data. The FFT is linear so the imaginary part of the data can be decoupled from the real part. This procedure leads to a dedicated FFT for real-valued data which works for any length and does not perform any unnecessary operations. It also allows us to derive a corresponding inverse FFT routine which transforms a half-complex sequence back into real data. \subsection{Radix-2 FFTs for real data} % Before embarking on the full mixed-radix real FFT we'll start with the radix-2 case. It contains all the essential features of the general-$N$ algorithm. To make it easier to see the analogy between the two we will use the mixed-radix notation to describe the factors. The factors are all 2, % \begin{equation} f_1 = 2, f_2 = 2, \dots, f_{n_f} = 2 \end{equation} % and the products $p_i$ are powers of 2, % \begin{eqnarray} p_0 & = & 1 \\ p_1 & = & f_1 = 2 \\ p_2 & = & f_1 f_2 = 4 \\ \dots &=& \dots \\ p_i & = & f_1 f_2 \dots f_i = 2^i \end{eqnarray} % Using this notation we can rewrite the radix-2 decimation-in-time algorithm as, % \begin{algorithm} \STATE bit-reverse ordering of $g$ \FOR {$i = 1 \dots n$} \FOR {$a = 0 \dots p_{i-1} - 1$} \FOR{$b = 0 \dots q_i - 1$} \STATE{$ \left( \begin{array}{c} g(b p_i + a) \\ g(b p_i + p_{i-1} + a) \end{array} \right) = \left( \begin{array}{c} g(b p_i + a) + W^a_{p_i} g(b p_i + p_{i-1} + a) \\ g(b p_i + a) - W^a_{p_i} g(b p_i + p_{i-1} + a) \end{array} \right) $} \ENDFOR \ENDFOR \ENDFOR \end{algorithm} % where we have used $p_i = 2 \Delta$, and factored $2 \Delta$ out of the original definition of $b$ ($b \to b p_i$). If we go back to the original recurrence relations we can see how to write the intermediate results in a way which make the real/half-complex symmetries explicit at each step. The first pass is just a set of FFTs of length-2 on real values, % \begin{equation} g_1([b_0 b_1 b_2 a_0]) = \sum_{b_3} W^{a_0 b_3}_2 g([b_0 b_1 b_2 b_3]) \end{equation} % Using the symmetry $FFT(x)_k = FFT(x)^*_{N-k}$ we have the reality condition, % \begin{eqnarray} g_1([b_0 b_1 b_2 0]) &=& \mbox{real} \\ g_1([b_0 b_1 b_2 1]) &=& \mbox{real'} \end{eqnarray} % In the next pass we have a set of length-4 FFTs on the original data, % \begin{eqnarray} g_2([b_0 b_1 b_1 a_0]) &=& \sum_{b_2}\sum_{b_3} W^{[a_1 a_0]b_2}_4 W^{a_0 b_3}_2 g([b_0 b_1 b_2 b_3]) \\ &=& \sum_{b_2}\sum_{b_3} W^{[a_1 a_0][b_3 b_2]}_4 g([b_0 b_1 b_2 b_3]) \end{eqnarray} % This time symmetry gives us the following conditions on the transformed data, % \begin{eqnarray} g_2([b_0 b_1 0 0]) &=& \mbox{real} \\ g_2([b_0 b_1 0 1]) &=& x + i y \\ g_2([b_0 b_1 1 0]) &=& \mbox{real'} \\ g_2([b_0 b_1 1 1]) &=& x - i y \end{eqnarray} % We can see a pattern emerging here: the $i$-th pass computes a set of independent length-$2^i$ FFTs on the original real data, % \begin{eqnarray} g_i ( b p_i + a ) = \sum_{a' = 0}^{p_i-1} W_{p_i}^{aa'} g(b p_i + a') \quad \mbox{for $b = 0 \dots q_i - 1$} \end{eqnarray} % As a consequence the we can apply the symmetry for an FFT of real data to all the intermediate results -- not just the final result. In general after the $i$-th pass we will have the symmetry, % \begin{eqnarray} g_i(b p_i) &=& \mbox{real} \\ g_i(b p_i + a) &=& g_i(b p_i + p_i - a)^* \qquad a = 1 \dots p_{i}/2 - 1 \\ g_i(b p_i + p_{i}/2) &=& \mbox{real'} \end{eqnarray} % In the next section we'll show that this is a general property of decimation-in-time algorithms. The same is not true for the decimation-in-frequency algorithm, which does not have any simple symmetries in the intermediate results. Since we can obtain the values of $g_i(b p_i + a)$ for $a > p_i/2$ from the values for $a < p_i/2$ we can cut our computation and storage in half compared with the full-complex case. % We can easily rewrite the algorithm to show how the computation can be halved, simply by limiting all terms to involve only values for $a \leq p_{i}/2$. Whenever we encounter a term $g_i(b p_i + a)$ with $a > p_{i}/2$ we rewrite it in terms of its complex symmetry partner, $g_i(b p_i + a')^*$, where $a' = p_i - a$. The butterfly computes two values for each value of $a$, $b p_i + a$ and $b p_i + p_{i-1} - a$, so we actually only need to compute from $a = 0$ to $p_{i-1}/2$. This gives the following algorithm, % \begin{algorithm} \FOR {$a = 0 \dots p_{i-1}/2$} \FOR{$b = 0 \dots q_i - 1$} \STATE{$ \left( \begin{array}{c} g(b p_i + a) \\ g(b p_i + p_{i-1} - a)^* \end{array} \right) = \left( \begin{array}{c} g(b p_{i} + a) + W^a_{p_i} g(b p_i + p_{i-1} + a) \\ g(b p_{i} + a) - W^a_{p_i} g(b p_i + p_{i-1} + a) \end{array} \right) $} \ENDFOR \ENDFOR \end{algorithm} % Although we have halved the number of operations we also need a storage arrangement which will halve the memory requirement. The algorithm above is still formulated in terms of a complex array $g$, but the input to our routine will naturally be an array of $N$ real values which we want to use in-place. Therefore we need a storage scheme which lays out the real and imaginary parts within the real array, in a natural way so that there is no need for complicated index calculations. In the radix-2 algorithm we do not have any additional scratch space. The storage scheme has to be designed to accommodate the in-place calculation taking account of dual node pairs. Here is a scheme which takes these restrictions into account: On the $i$-th pass we store the real part of $g(b p_i + a)$ in location $b p_i + a$. We store the imaginary part in location $b p_i + p_i - a$. This is the redundant location which corresponds to the conjugate term $g(b p_i + a)^* = g(b p_i + p_i -a)$, so it is not needed. When the results are purely real (as in the case $a = 0$ and $a = p_i/2$ we store only the real part and drop the zero imaginary part). This storage scheme has to work in-place, because the radix-2 routines should not use any scratch space. We will now check the in-place property for each butterfly. A crucial point is that the scheme is pass-dependent. Namely, when we are computing the result for pass $i$ we are reading the results of pass $i-1$, and we must access them using the scheme from the previous pass, i.e. we have to remember that the results from the previous pass were stored using $b p_{i-1} + a$, not $b p_i + a$, and the symmetry for these results will be $g_{i-1}(b p_{i-1} + a) = g_{i-1}(b p_{i-1} + p_{i-1} - a)^*$. To take this into account we'll write the right hand side of the iteration, $g_{i-1}$, in terms of $p_{i-1}$. For example, instead of $b p_i$, which occurs naturally in $g_i(b p_i + a)$ we will use $2 b p_{i-1}$, since $p_i = 2 p_{i-1}$. Let's start with the butterfly for $a = 0$, % \begin{equation} \left( \begin{array}{c} g(b p_i) \\ g(b p_i + p_{i-1})^* \end{array} \right) = \left( \begin{array}{c} g(2 b p_{i-1}) + g((2 b + 1) p_{i-1}) \\ g(2 b p_{i-1}) - g((2 b + 1) p_{i-1}) \end{array} \right) \end{equation} % By the symmetry $g_{i-1}(b p_{i-1} + a) = g_{i-1}(b p_{i-1} + p_{i-1} - a)^*$ all the inputs are purely real. The input $g(2 b p_{i-1})$ is read from location $2 b p_{i-1}$ and $g((2 b + 1) p_{i-1})$ is read from the location $(2 b + 1) p_{i-1}$. Here is the full breakdown, % \begin{center} \renewcommand{\arraystretch}{1.5} \begin{tabular}{|l|lll|} \hline Term & & Location & \\ \hline $g(2 b p_{i-1})$ & real part & $2 b p_{i-1} $ &$= b p_i$ \\ & imag part & --- & \\ $g((2 b+1) p_{i-1})$ & real part & $(2 b + 1) p_{i-1} $&$= b p_i + p_{i-1} $ \\ & imag part & --- & \\ \hline $g(b p_{i})$ & real part & $b p_i$ &\\ & imag part & --- & \\ $g(b p_{i} + p_{i-1})$ & real part & $b p_i + p_{i-1}$& \\ & imag part & --- & \\ \hline \end{tabular} \end{center} % The conjugation of the output term $g(b p_i + p_{i-1})^*$ is irrelevant here since the results are purely real. The real results are stored in locations $b p_i$ and $b p_i + p_{i-1}$, which overwrites the inputs in-place. The general butterfly for $a = 1 \dots p_{i-1}/2 - 1$ is, % \begin{equation} \left( \begin{array}{c} g(b p_i + a) \\ g(b p_i + p_{i-1} - a)^* \end{array} \right) = \left( \begin{array}{c} g(2 b p_{i-1} + a) + W^a_{p_i} g((2 b + 1) p_{i-1} + a) \\ g(2 b p_{i-1} + a) - W^a_{p_i} g((2 b + 1) p_{i-1} + a) \end{array} \right) \end{equation} % All the terms are complex. To store a conjugated term like $g(b' p_i + a')^*$ where $a > p_i/2$ we take the real part and store it in location $b' p_i + a'$ and then take imaginary part, negate it, and store the result in location $b' p_i + p_i - a'$. Here is the full breakdown of the inputs and outputs from the butterfly, % \begin{center} \renewcommand{\arraystretch}{1.5} \begin{tabular}{|l|lll|} \hline Term & & Location & \\ \hline $g(2 b p_{i-1} + a)$ & real part & $2 b p_{i-1} + a $ &$= b p_i + a$ \\ & imag part & $2 b p_{i-1} + p_{i-1} - a$ &$= b p_i + p_{i-1} - a$ \\ $g((2 b+1) p_{i-1} + a)$ & real part & $(2 b+1) p_{i-1} + a $&$= b p_i + p_{i-1} + a $ \\ & imag part & $(2 b+1) p_{i-1} + p_{i-1} - a $&$= b p_i + p_i - a$\\ \hline $g(b p_{i} + a)$ & real part & $b p_i + a$ &\\ & imag part & $b p_i + p_i - a$& \\ $g(b p_{i} + p_{i-1} - a)$ & real part & $b p_i + p_{i-1} - a$& \\ & imag part & $b p_i + p_{i-1} + a$& \\ \hline \end{tabular} \end{center} % By comparing the input locations and output locations we can see that the calculation is done in place. The final butterfly for $a = p_{i-1}/2$ is, % \begin{equation} \left( \begin{array}{c} g(b p_i + p_{i-1}/2) \\ g(b p_i + p_{i-1} - p_{i-1}/2)^* \end{array} \right) = \left( \begin{array}{c} g(2 b p_{i-1} + p_{i-1}/2) - i g((2 b + 1) p_{i-1} + p_{i-1}/2) \\ g(2 b p_{i-1} + p_{i-1}/2) + i g((2 b + 1) p_{i-1} + p_{i-1}/2) \end{array} \right) \end{equation} % where we have substituted for the twiddle factor, $W^a_{p_i} = -i$, % \begin{eqnarray} W^{p_{i-1}/2}_{p_i} &=& \exp(-2\pi i p_{i-1}/2 p_i) \\ &=& \exp(-2\pi i /4) \\ &=& -i \end{eqnarray} % For this butterfly the second line is just the conjugate of the first, because $p_{i-1} - p_{i-1}/2 = p_{i-1}/2$. Therefore we only need to consider the first line. The breakdown of the inputs and outputs is, % \begin{center} \renewcommand{\arraystretch}{1.5} \begin{tabular}{|l|lll|} \hline Term & & Location & \\ \hline $g(2 b p_{i-1} + p_{i-1}/2)$ & real part & $2 b p_{i-1} + p_{i-1}/2 $ &$= b p_i + p_{i-1}/2$ \\ & imag part & --- & \\ $g((2 b + 1) p_{i-1} + p_{i-1}/2)$ & real part & $(2 b + 1) p_{i-1} + p_{i-1}/2 $&$= b p_i + p_{i} - p_{i-1}/2 $ \\ & imag part & --- & \\ \hline $g(b p_{i} + p_{i-1}/2)$ & real part & $b p_i + p_{i-1}/2$ &\\ & imag part & $b p_i + p_i - p_{i-1}/2$& \\ \hline \end{tabular} \end{center} % By comparing the locations of the inputs and outputs with the operations in the butterfly we find that this computation is very simple: the effect of the butterfly is to negate the location $b p_i + p_i - p_{i-1}/2$ and leave other locations unchanged. This is clearly an in-place operation. Here is the radix-2 algorithm for real data, in full, with the cases of $a=0$, $a=1\dots p_{i-1}/2 - 1$ and $a = p_{i-1}/2$ in separate blocks, % \begin{algorithm} \STATE bit-reverse ordering of $g$ \FOR {$i = 1 \dots n$} \FOR{$b = 0 \dots q_i - 1$} \STATE{$\left( \begin{array}{c} g(b p_i) \\ g(b p_i + p_{i-1}) \end{array} \right) \Leftarrow \left( \begin{array}{c} g(b p_{i}) + g(b p_{i} + p_{i-1}) \\ g(b p_{i}) - g(b p_{i} + p_{i-1}) \end{array} \right)$} \ENDFOR \FOR {$a = 1 \dots p_{i-1}/2 - 1$} \FOR{$b = 0 \dots q_i - 1$} \STATE{$(\Real z_0, \Imag z_0) \Leftarrow (g(b p_i + a), g(b p_i + p_{i-1} - a))$} \STATE{$(\Real z_1, \Imag z_1) \Leftarrow (g(b p_i + p_{i-1} + a), g(b p_i + p_{i} - a))$} \STATE{$t_0 \Leftarrow z_0 + W^a_{p_i} z_1$} \STATE{$t_1 \Leftarrow z_0 - W^a_{p_i} z_1$} \STATE{$(g(b p_{i} + a),g(b p_{i} + p_i - a) \Leftarrow (\Real t_0, \Imag t_0)$} \STATE{$(g(b p_{i} + p_{i-1} - a), g(b p_{i} + p_{i-1} + a)) \Leftarrow (\Real t_1, -\Imag t_1)$} \ENDFOR \ENDFOR \FOR{$b = 0 \dots q_i - 1$} \STATE{$g(b p_{i} - p_{i-1}/2) \Leftarrow -g(b p_{i} - p_{i-1}/2)$} \ENDFOR \ENDFOR \end{algorithm} % We split the loop over $a$ into three parts, $a=0$, $a=1\dots p_{i-1}/2-1$ and $a = p_{i-1}/2$ for efficiency. When $a=0$ we have $W^a_{p_i}=1$ so we can eliminate a complex multiplication within the loop over $b$. When $a=p_{i-1}/2$ we have $W^a_{p_i} = -i$ which does not require a full complex multiplication either. \subsubsection{Calculating the Inverse} % The inverse FFT of complex data was easy to calculate, simply by taking the complex conjugate of the DFT matrix. The input data and output data were both complex and did not have any special symmetry. For real data the inverse FFT is more complicated because the half-complex symmetry of the transformed data is different from the purely real input data. We can compute an inverse by stepping backwards through the forward transform. To simplify the inversion it's convenient to write the forward algorithm with the butterfly in matrix form, % \begin{algorithm} \FOR {$i = 1 \dots n$} \FOR {$a = 0 \dots p_{i-1}/2$} \FOR{$b = 0 \dots q_i - 1$} \STATE{$ \left( \begin{array}{c} g(b p_i + a) \\ g(b p_i + p_{i-1} + a) \end{array} \right) = \left( \begin{array}{cc} 1 & W^a_{p_{i}} \\ 1 & -W^a_{p_{i}} \end{array} \right) \left( \begin{array}{c} g(2 b p_{i-1} + a) \\ g((2 b + 1) p_{i-1} + a) \end{array} \right) $} \ENDFOR \ENDFOR \ENDFOR \end{algorithm} % To invert the algorithm we run the iterations backwards and invert the matrix multiplication in the innermost loop, % \begin{algorithm} \FOR {$i = n \dots 1$} \FOR {$a = 0 \dots p_{i-1}/2$} \FOR{$b = 0 \dots q_i - 1$} \STATE{$ \left( \begin{array}{c} g(2 b p_{i-1} + a) \\ g((2 b + 1) p_{i-1} + a) \end{array} \right) = \left( \begin{array}{cc} 1 & W^a_{p_{i}} \\ 1 & -W^a_{p_{i}} \end{array} \right)^{-1} \left( \begin{array}{c} g(b p_i + a) \\ g(b p_i + p_{i-1} + a) \end{array} \right) $} \ENDFOR \ENDFOR \ENDFOR \end{algorithm} % There is no need to reverse the loops over $a$ and $b$ because the result is independent of their order. The inverse of the matrix that appears is, % \begin{equation} \left( \begin{array}{cc} 1 & W^a_{p_{i}} \\ 1 & -W^a_{p_{i}} \end{array} \right)^{-1} = {1 \over 2} \left( \begin{array}{cc} 1 & 1 \\ W^{-a}_{p_{i}} & -W^{-a}_{p_{i}} \end{array} \right) \end{equation} % To save divisions we remove the factor of $1/2$ inside the loop. This computes the unnormalized inverse, and the normalized inverse can be retrieved by dividing the final result by $N = 2^n$. Here is the radix-2 half-complex to real inverse FFT algorithm, taking into account the radix-2 storage scheme, % \begin{algorithm} \FOR {$i = n \dots 1$} \FOR{$b = 0 \dots q_i - 1$} \STATE{$\left( \begin{array}{c} g(b p_i) \\ g(b p_i + p_{i-1}) \end{array} \right) \Leftarrow \left( \begin{array}{c} g(b p_{i}) + g(b p_{i} + p_{i-1}) \\ g(b p_{i}) - g(b p_{i} + p_{i-1}) \end{array} \right)$} \ENDFOR \FOR {$a = 1 \dots p_{i-1}/2 - 1$} \FOR{$b = 0 \dots q_i - 1$} \STATE{$(\Real z_0, \Imag z_0) \Leftarrow (g(b p_i + a), g(b p_i + p_{i} - a))$} \STATE{$(\Real z_1, \Imag z_1) \Leftarrow (g(b p_i + p_{i-1} - a), -g(b p_i + p_{i-1} + a))$} \STATE{$t_0 \Leftarrow z_0 + z_1$} \STATE{$t_1 \Leftarrow z_0 - z_1$} \STATE{$(g(b p_{i} + a), g(b p_{i} + p_{i-1} - a)) \Leftarrow (\Real t_0, \Imag t_0) $} \STATE{$(g(b p_{i} + p_{i-1} + a),g(b p_{i} + p_{i} - a)) \Leftarrow (\Real(W^a_{p_i}t_1), \Imag(W^a_{p_i}t_1))$} \ENDFOR \ENDFOR \FOR{$b = 0 \dots q_i - 1$} \STATE{$g(b p_{i} + p_{i-1}/2) \Leftarrow 2 g(b p_{i} + p_{i-1}/2)$} \STATE{$g(b p_{i} + p_{i-1} + p_{i-1}/2) \Leftarrow -2 g(b p_{i} + p_{i-1} + p_{i-1}/2)$} \ENDFOR \ENDFOR \STATE bit-reverse ordering of $g$ \end{algorithm} \subsection{Mixed-Radix FFTs for real data} % As discussed earlier the radix-2 decimation-in-time algorithm had the special property that its intermediate passes are interleaved fourier transforms of the original data, and this generalizes to the mixed-radix algorithm. The complex mixed-radix algorithm that we derived earlier was a decimation-in-frequency algorithm, but we can obtain a decimation-in-time version by taking the transpose of the decimation-in-frequency DFT matrix like this, % \begin{eqnarray} W_N &=& W_N^T \\ &=& (T_{n_f} \dots T_2 T_1)^T \\ &=& T_1^T T_2^T \dots T_{n_f}^T \end{eqnarray} % with, % \begin{eqnarray} T_i^T &=& \left( (P^{f_i}_{q_i} D^{f_i}_{q_i} \otimes I_{p_{i-1}}) (W_{f_i} \otimes I_{m_i}) \right)^T \\ &=& (W_{f_i} \otimes I_{m_i}) ( D^{f_i}_{q_i} (P^{f_i}_{q_i})^T \otimes I_{p_{i-1}}). \end{eqnarray} % We have used the fact that $W$, $D$ and $I$ are symmetric and that the permutation matrix $P$ obeys, % \begin{equation} (P^a_b)^T = P^b_a. \end{equation} % From the definitions of $D$ and $P$ we can derive the following identity, % \begin{equation} D^a_b P^b_a = P^b_a D^b_a. \end{equation} % This allows us to put the transpose into a simple form, % \begin{equation} T_i^T = (W_{f_i} \otimes I_{m_i}) (P^{q_i}_{f_i} D^{q_i}_{f_i} \otimes I_{p_{i-1}}). \end{equation} % The transposed matrix, $T^T$ applies the digit-reversal $P$ before the DFT $W$, giving the required decimation-in-time algorithm. The transpose reverses the order of the factors --- $T_{n_f}$ is applied first and $T_1$ is applied last. For convenience we can reverse the order of the factors, $f_1 \leftrightarrow f_{n_f}$, $f_2 \leftrightarrow f_{n_f-1}$, \dots and make the corresponding substitution $p_{i-1} \leftrightarrow q_i$. These substitutions give us a decimation-in-time algorithm with the same ordering as the decimation-in-frequency algorithm, % \begin{equation} W_N = T_{n_f} \dots T_2 T_1 \end{equation} % \begin{equation} T_i = (W_{f_i} \otimes I_{m_i}) (P^{p_{i-1}}_{f_i} D^{p_{i-1}}_{f_i} \otimes I_{q_i}) \end{equation} % where $p_i$, $q_i$ and $m_i$ now have the same meanings as before, namely, % \begin{eqnarray} p_i &=& f_1 f_2 \dots f_i \quad (p_0 = 1) \\ q_i &=& N / p_i \\ m_i &=& N / f_i \end{eqnarray} % Now we would like to prove that the iteration for computing $x = W z = T_{n_f} \dots T_2 T_1 z$ has the special property interleaving property. If we write the result of each intermediate pass as $v^{(i)}$, % \begin{eqnarray} v^{(0)} &=& z \\ v^{(1)} &=& T_1 v^{(0)} \\ v^{(2)} &=& T_2 v^{(1)} \\ \dots &=& \dots \\ v^{(i)} &=& T_i v^{(i-1)} \end{eqnarray} % then we will show that the intermediate results $v^{(i)}$ on any pass can be written as, % \begin{equation} v^{(i)} = (W_{p_i} \otimes I_{q_i}) z \end{equation} % Each intermediate stage will be a set of $q_i$ interleaved fourier transforms, each of length $p_i$. We can prove this result by induction. First we assume that the result is true for $v^{(i-1)}$, % \begin{equation} v^{(i-1)} = (W_{p_{i-1}} \otimes I_{q_{i-1}}) z \qquad \mbox{(assumption)} \end{equation} % And then we examine the next iteration using this assumption, % \begin{eqnarray} v^{(i)} &=& T_i v^{(i-1)} \\ &=& T_i (W_{p_{i-1}} \otimes I_{q_{i-1}}) z \\ &=& (W_{f_i} \otimes I_{m_i}) (P^{p_{i-1}}_{f_i} D^{p_{i-1}}_{f_i} \otimes I_{q_i}) (W_{p_{i-1}} \otimes I_{q_{i-1}}) z \label{dit-induction} \end{eqnarray} % Using the relation $m_i = p_{i-1} q_i$, we can write $I_{m_i}$ as $I_{p_{i-1} q_i}$ and $I_{q_{i-1}}$ as $I_{f_i q_i}$. By combining these with the basic matrix identity, % \begin{equation} I_{ab} = I_a \otimes I_b \end{equation} % we can rewrite $v^{(i)}$ in the following form, % \begin{equation} v^{(i)} = (((W_{f_i} \otimes I_{p_{i-1}}) (P^{p_{i-1}}_{f_i} D^{p_{i-1}}_{f_i}) (W_{p_{i-1}} \otimes I_{f_i})) \otimes I_{q_i}) z . \end{equation} % The first part of this matrix product is the two-factor expansion of $W_{ab}$, for $a = p_{i-1}$ and $b = f_i$, % \begin{equation} W_{p_{i-1} f_i} = ((W_{f_i} \otimes I_{p_{i-1}}) (P^{p_{i-1}}_{f_i} D^{p_{i-1}}_{f_i}) (W_{p_{i-1}} \otimes I_{f_i})). \end{equation} % If we substitute this result, remembering that $p_i = p_{i-1} f_i$, we obtain, % \begin{equation} v^{(i)} = (W_{p_i} \otimes I_{q_i}) z \end{equation} % which is the desired result. The case $i=1$ can be verified explicitly, and induction then shows that the result is true for all $i$. As discussed for the radix-2 algorithm this result is important because if the initial data $z$ is real then each intermediate pass is a set of interleaved fourier transforms of $z$, having half-complex symmetries (appropriately applied in the subspaces of the Kronecker product). Consequently only $N$ real numbers are needed to store the intermediate and final results. \subsection{Implementation} % The implementation of the mixed-radix real FFT algorithm follows the same principles as the full complex transform. Some of the steps are applied in the opposite order because we are dealing with a decimation in time algorithm instead of a decimation in frequency algorithm, but the basic outer structure of the algorithm is the same. We want to apply the factorized version of the DFT matrix $W_N$ to the input vector $z$, % \begin{eqnarray} x &=& W_N z \\ &=& T_{n_f} \dots T_2 T_1 z \end{eqnarray} % We loop over the $n_f$ factors, applying each matrix $T_i$ to the vector in turn to build up the complete transform, % \begin{algorithm} \FOR{$(i = 1 \dots n_f)$} \STATE{$v \Leftarrow T_i v $} \ENDFOR \end{algorithm} % In this case the definition of $T_i$ is different because we have taken the transpose, % \begin{equation} T_i = (W_{f_i} \otimes I_{m_i}) (P^{p_{i-1}}_{f_i} D^{p_{i-1}}_{f_i} \otimes I_{q_i}). \end{equation} % We'll define a temporary vector $t$ to denote the results of applying the rightmost matrix, % \begin{equation} t = (P^{p_{i-1}}_{f_i} D^{p_{i-1}}_{f_i} \otimes I_{q_i}) v \end{equation} % If we expand this out into individual components, as before, we find a similar simplification, % \begin{eqnarray} t_{aq+b} &=& \sum_{a'b'} (P^{p_{i-1}}_{f_i} D^{p_{i-1}}_{f_i} \otimes I_{q_i})_{(aq+b)(a'q+b')} v_{a'q+b'} \\ &=& \sum_{a'} (P^{p_{i-1}}_{f_i} D^{p_{i-1}}_{f_i})_{a a'} v_{a'q+b} \end{eqnarray} % We have factorized the indices into the form $aq+b$, with $0 \leq a < p_{i}$ and $0 \leq b < q$. Just as in the decimation in frequency algorithm we can split the index $a$ to remove the matrix multiplication completely. We have to write $a$ as $\mu f + \lambda$, where $0 \leq \mu < p_{i-1}$ and $0 \leq \lambda < f$, % \begin{eqnarray} t_{(\mu f + \lambda)q+b} &=& \sum_{\mu'\lambda'} (P^{p_{i-1}}_{f_i} D^{p_{i-1}}_{f_i})_{(\mu f + \lambda)(\mu' f + \lambda')} v_{(\mu' f + \lambda')q+b} \\ &=& \sum_{\mu'\lambda'} (P^{p_{i-1}}_{f_i})_{(\mu f + \lambda)(\mu' f + \lambda')} \omega^{\mu'\lambda'}_{p_{i}} v_{(\mu' f + \lambda')q+b} \end{eqnarray} % The matrix $P^{p_{i-1}}_{f_i}$ exchanges an index of $(\mu f + \lambda) q + b$ with $(\lambda p_{i-1} + \mu) q + b$, giving a final result of, % \begin{eqnarray} t_{(\lambda p_{i-1} + \mu) q + b} = w^{\mu\lambda}_{p_i} v_{(\mu f + \lambda)q +b} \end{eqnarray} % To calculate the next stage, % \begin{equation} v' = (W_{f_i} \otimes I_{m_i}) t, \end{equation} % we temporarily rearrange the index of $t$ to separate the $m_{i}$ independent DFTs in the Kronecker product, % \begin{equation} v'_{(\lambda p_{i-1} + \mu) q + b} = \sum_{\lambda' \mu' b'} (W_{f_i} \otimes I_{m_i})_{ ((\lambda p_{i-1} + \mu) q + b) ((\lambda' p_{i-1} + \mu') q + b')} t_{(\lambda' p_{i-1} + \mu') q + b'} \end{equation} % If we use the identity $m = p_{i-1} q$ to rewrite the index of $t$ like this, % \begin{equation} t_{(\lambda p_{i-1} + \mu) q + b} = t_{\lambda m + (\mu q + b)} \end{equation} % we can split the Kronecker product, % \begin{eqnarray} v'_{(\lambda p_{i-1} + \mu) q + b} &=& \sum_{\lambda' \mu' b'} (W_{f_i} \otimes I_{m_i})_{ ((\lambda p_{i-1} + \mu) q + b) ((\lambda' p_{i-1} + \mu') q + b')} t_{(\lambda' p_{i-1} + \mu') q + b'}\\ &=& \sum_{\lambda'} (W_{f_i})_{\lambda \lambda'} t_{\lambda' m_i + (\mu q + b)} \end{eqnarray} % If we switch back to the original form of the index in the last line we obtain, % \begin{eqnarray} \phantom{v'_{(\lambda p_{i-1} + \mu) q + b}} &=& \sum_{\lambda'} (W_{f_i})_{\lambda \lambda'} t_{(\lambda p_{i-1} + \mu) q + b} \end{eqnarray} % which allows us to substitute our previous result for $t$, % \begin{equation} v'_{(\lambda p_{i-1} + \mu) q + b} = \sum_{\lambda'} (W_{f_i})_{\lambda \lambda'} w^{\mu\lambda'}_{p_i} v_{(\mu f + \lambda')q + b} \end{equation} % This gives us the basic decimation-in-time mixed-radix algorithm for complex data which we will be able to specialize to real data, % \begin{algorithm} \FOR{$i = 1 \dots n_f$} \FOR{$\mu = 0 \dots p_{i-1} - 1$} \FOR{$b = 0 \dots q-1$} \FOR{$\lambda = 0 \dots f-1$} \STATE{$t_\lambda \Leftarrow \omega^{\mu\lambda'}_{p_{i}} v_{(\mu f + \lambda')q + b}$} \ENDFOR \FOR{$\lambda = 0 \dots f-1$} \STATE{$v'_{(\lambda p_{i-1} + \mu)q + b} = \sum_{\lambda'=0}^{f-1} W_f(\lambda,\lambda') t_{\lambda'}$} \COMMENT{DFT matrix-multiply module} \ENDFOR \ENDFOR \ENDFOR \STATE{$v \Leftarrow v'$} \ENDFOR \end{algorithm} % We are now at the point where we can convert an algorithm formulated in terms of complex variables to one in terms of real variables by choosing a suitable storage scheme. We will adopt the FFTPACK storage convention. FFTPACK uses a scheme where individual FFTs are contiguous, not interleaved, and real-imaginary pairs are stored in neighboring locations. This has better locality than was possible for the radix-2 case. The interleaving of the intermediate FFTs results from the Kronecker product, $W_p \otimes I_q$. The FFTs can be made contiguous if we reorder the Kronecker product on the intermediate passes, % \begin{equation} W_p \otimes I_q \Rightarrow I_q \otimes W_p \end{equation} % This can be implemented by a simple change in indexing. On pass-$i$ we store element $v_{a q_i + b}$ in location $v_{b p_i+a}$. We compensate for this change by making the same transposition when reading the data. Note that this only affects the indices of the intermediate passes. On the zeroth iteration the transposition has no effect because $p_0 = 1$. Similarly there is no effect on the last iteration, which has $q_{n_f} = 1$. This is how the algorithm above looks after this index transformation, % \begin{algorithm} \FOR{$i = 1 \dots n_f$} \FOR{$\mu = 0 \dots p_{i-1} - 1$} \FOR{$b = 0 \dots q-1$} \FOR{$\lambda = 0 \dots f-1$} \STATE{$t_\lambda \Leftarrow \omega^{\mu\lambda'}_{p_{i}} v_{(\lambda'q + b)p_{i-1} + \mu}$} \ENDFOR \FOR{$\lambda = 0 \dots f-1$} \STATE{$v'_{b p + (\lambda p_{i-1} + \mu)} = \sum_{\lambda'=0}^{f-1} W_f(\lambda,\lambda') t_{\lambda'}$} \COMMENT{DFT matrix-multiply module} \ENDFOR \ENDFOR \ENDFOR \STATE{$v \Leftarrow v'$} \ENDFOR \end{algorithm} % We transpose the input terms by writing the index in the form $a q_{i-1} + b$, to take account of the pass-dependence of the scheme, % \begin{equation} v_{(\mu f + \lambda')q + b} = v_{\mu q_{i-1} + \lambda'q + b} \end{equation} % We used the identity $q_{i-1} = f q$ to split the index. Note that in this form $\lambda'q + b$ runs from 0 to $q_{i-1} - 1$ as $b$ runs from 0 to $q-1$ and $\lambda'$ runs from 0 to $f-1$. The transposition for the input terms then gives, % \begin{equation} v_{\mu q_{i-1} + \lambda'q + b} \Rightarrow v_{(\lambda'q + b) p_{i-1} + \mu} \end{equation} % In the FFTPACK scheme the intermediate output data have the same half-complex symmetry as the radix-2 example, namely, % \begin{equation} v^{(i)}_{b p + a} = v^{(i)*}_{b p + (p - a)} \end{equation} % and on the input data from the previous pass have the symmetry, % \begin{equation} v^{(i-1)}_{(\lambda' q + b) p_{i-1} + \mu} = v^{(i-1)*}_{(\lambda' q + b) p_{i-1} + (p_{i-1} - \mu)} \end{equation} % Using these symmetries we can halve the storage and computation requirements for each pass. Compared with the radix-2 algorithm we have more freedom because the computation does not have to be done in place. The storage scheme adopted by FFTPACK places elements sequentially with real and imaginary parts in neighboring locations. Imaginary parts which are known to be zero are not stored. Here are the full details of the scheme, % \begin{center} \renewcommand{\arraystretch}{1.5} \begin{tabular}{|l|lll|} \hline Term & & Location & \\ \hline $g(b p_i)$ & real part & $b p_{i} $ & \\ & imag part & --- & \\ \hline $g(b p_i + a)$ & real part & $b p_{i} + 2a - 1 $& for $a = 1 \dots p_i/2 - 1$ \\ & imag part & $b p_{i} + 2a$ & \\ \hline $g(b p_{i} + p_{i}/2)$ & real part & $b p_i + p_{i} - 1$ & if $p_i$ is even\\ & imag part & --- & \\ \hline \end{tabular} \end{center} % The real element for $a=0$ is stored in location $bp$. The real parts for $a = 1 \dots p/2 - 1$ are stored in locations $bp + 2a -1$ and the imaginary parts are stored in locations $b p + 2 a$. When $p$ is even the term for $a = p/2$ is purely real and we store it in location $bp + p - 1$. The zero imaginary part is not stored. When we compute the basic iteration, % \begin{equation} v^{(i)}_{b p + (\lambda p_{i-1} + \mu)} = \sum_{\lambda'} W^{\lambda \lambda'}_f \omega^{\mu\lambda'}_{p_i} v^{(i-1)}_{(\lambda' q + b)p_{i-1} + \mu} \end{equation} % we eliminate the redundant conjugate terms with $a > p_{i}/2$ as we did in the radix-2 algorithm. Whenever we need to store a term with $a > p_{i}/2$ we consider instead the corresponding conjugate term with $a' = p - a$. Similarly when reading data we replace terms with $\mu > p_{i-1}/2$ with the corresponding conjugate term for $\mu' = p_{i-1} - \mu$. Since the input data on each stage has half-complex symmetry we only need to consider the range $\mu=0 \dots p_{i-1}/2$. We can consider the best ways to implement the basic iteration for each pass, $\mu = 0 \dots p_{i-1}/2$. On the first pass where $\mu=0$ we will be accessing elements which are the zeroth components of the independent transforms $W_{p_{i-1}} \otimes I_{q_{i-1}}$, and are purely real. % We can code the pass with $\mu=0$ as a special case for real input data, and conjugate-complex output. When $\mu=0$ the twiddle factors $\omega^{\mu\lambda'}_{p_i}$ are all unity, giving a further saving. We can obtain small-$N$ real-data DFT modules by removing the redundant operations from the complex modules. % For example the $N=3$ module was, % \begin{equation} \begin{array}{lll} t_1 = z_1 + z_2, & t_2 = z_0 - t_1/2, & t_3 = \sin(\pi/3) (z_1 - z_2), \end{array} \end{equation} \begin{equation} \begin{array}{lll} x_0 = z_0 + t_1, & x_1 = t_2 + i t_3, & x_2 = t_2 - i t_3. \end{array} \end{equation} % In the complex case all the operations were complex, for complex input data $z_0$, $z_1$, $z_2$. In the real case $z_0$, $z_1$ and $z_2$ are all real. Consequently $t_1$, $t_2$ and $t_3$ are also real, and the symmetry $x_1 = t_1 + i t_2 = x^*_2$ means that we do not have to compute $x_2$ once we have computed $x_1$. For subsequent passes $\mu = 1 \dots p_{i-1}/2 - 1$ the input data is complex and we have to compute full complex DFTs using the same modules as in the complex case. Note that the inputs are all of the form $v_{(\lambda q + b) p_{i-1} + \mu}$ with $\mu < p_{i-1}/2$ so we never need to use the symmetry to access the conjugate elements with $\mu > p_{i-1}/2$. If $p_{i-1}$ is even then we reach the halfway point $\mu=p_{i-1}/2$, which is another special case. The input data in this case is purely real because $\mu = p_{i-1} - \mu$ for $\mu = p_{i-1}/2$. We can code this as a special case, using real inputs and real-data DFT modules as we did for $\mu=0$. However, for $\mu = p_{i-1}/2$ the twiddle factors are not unity, % \begin{eqnarray} \omega^{\mu\lambda'}_{p_i} &=& \omega^{(p_{i-1}/2)\lambda'}_{p_i} \\ &=& \exp(-i\pi\lambda'/f_i) \end{eqnarray} % These twiddle factors introduce an additional phase which modifies the symmetry of the outputs. Instead of the conjugate-complex symmetry which applied for $\mu=0$ there is a shifted conjugate-complex symmetry, % \begin{equation} t_\lambda = t^*_{f-(\lambda+1)} \end{equation} % This is easily proved, % \begin{eqnarray} t_\lambda &=& \sum e^{-2\pi i \lambda\lambda'/f_i} e^{-i\pi \lambda'/f_i} r_{\lambda'} \\ t_{f - (\lambda + 1)} &=& \sum e^{-2\pi i (f-\lambda-1)\lambda'/f_i} e^{-i\pi \lambda'/f_i} r_{\lambda'} \\ &=& \sum e^{2\pi i \lambda\lambda'/f_i} e^{i\pi \lambda'/f_i} r_{\lambda'} \\ &=& t^*_\lambda \end{eqnarray} % The symmetry of the output means that we only need to compute half of the output terms, the remaining terms being conjugates or zero imaginary parts. For example, when $f=4$ the outputs are $(x_0 + i y_0, x_1 + i y_1, x_1 - i y_1, x_0 - i y_0)$. For $f=5$ the outputs are $(x_0 + i y_0, x_1 + i y_1, x_2, x_1 - i y_1, x_0 - i y_0)$. By combining the twiddle factors and DFT matrix we can make a combined module which applies both at the same time. By starting from the complex DFT modules and bringing in twiddle factors we can derive optimized modules. Here are the modules given by Temperton for $z = W \Omega x$ where $x$ is real and $z$ has the shifted conjugate-complex symmetry. The matrix of twiddle factors, $\Omega$, is given by, % \begin{equation} \Omega = \mathrm{diag}(1, e^{-i\pi/f}, e^{-2\pi i/f}, \dots, e^{-i\pi(f-1)/f}) \end{equation} % We write $z$ in terms of two real vectors $z = a + i b$. % For $N=2$, % \begin{equation} \begin{array}{ll} a_0 = x_0, & b_0 = - x_1. \end{array} \end{equation} % For $N=3$, % \begin{equation} \begin{array}{l} t_1 = x_1 - x_2, \end{array} \end{equation} \begin{equation} \begin{array}{ll} a_0 = x_0 + t_1/2, & b_0 = x_0 - t_1, \end{array} \end{equation} \begin{equation} \begin{array}{l} a_1 = - \sin(\pi/3) (x_1 + x_2) \end{array} \end{equation} % For $N=4$, % \begin{equation} \begin{array}{ll} t_1 = (x_1 - x_3)/\sqrt{2}, & t_2 = (x_1 + x_3)/\sqrt{2}, \end{array} \end{equation} \begin{equation} \begin{array}{ll} a_0 = x_0 + t_1, & b_0 = -x_2 - t_2, \end{array} \end{equation} \begin{equation} \begin{array}{ll} a_1 = x_0 - t_1, & b_1 = x_2 - t_2. \end{array} \end{equation} % For $N=5$, % \begin{equation} \begin{array}{ll} t_1 = x_1 - x_4, & t_2 = x_1 + x_4, \end{array} \end{equation} \begin{equation} \begin{array}{ll} t_3 = x_2 - x_3, & t_4 = x_2 + x_3, \end{array} \end{equation} \begin{equation} \begin{array}{ll} t_5 = t_1 - t_3, & t_6 = x_0 + t_5 / 4, \end{array} \end{equation} \begin{equation} \begin{array}{ll} t_7 = (\sqrt{5}/4)(t_1 + t_3) & \end{array} \end{equation} \begin{equation} \begin{array}{ll} a_0 = t_6 + t_7, & b_0 = -\sin(2\pi/10) t_2 - \sin(2\pi/5) t_4, \end{array} \end{equation} \begin{equation} \begin{array}{ll} a_1 = t_6 - t_7, & b_1 = -\sin(2\pi/5) t_2 + \sin(2\pi/10) t_4, \end{array} \end{equation} \begin{equation} \begin{array}{ll} a_2 = x_0 - t_5 & \end{array} \end{equation} % For $N=6$, % \begin{equation} \begin{array}{ll} t_1 = \sin(\pi/3)(x_5 - x_1), & t_2 = \sin(\pi/3) (x_2 + x_4), \end{array} \end{equation} \begin{equation} \begin{array}{ll} t_3 = x_2 - x_4, & t_4 = x_1 + x_5, \end{array} \end{equation} \begin{equation} \begin{array}{ll} t_5 = x_0 + t_3 / 2, & t_6 = -x_3 - t_4 / 2, \end{array} \end{equation} \begin{equation} \begin{array}{ll} a_0 = t_5 - t_1, & b_0 = t_6 - t_2, \end{array} \end{equation} \begin{equation} \begin{array}{ll} a_1 = x_0 - t_3, & b_1 = x_3 - t_4, \end{array} \end{equation} \begin{equation} \begin{array}{ll} a_2 = t_5 + t_1, & b_2 = t_6 + t_2 \end{array} \end{equation} \section{Computing the mixed-radix inverse for real data} % To compute the inverse of the mixed-radix FFT on real data we step through the algorithm in reverse and invert each operation. This gives the following algorithm using FFTPACK indexing, % \begin{algorithm} \FOR{$i = n_f \dots 1$} \FOR{$\mu = 0 \dots p_{i-1} - 1$} \FOR{$b = 0 \dots q-1$} \FOR{$\lambda = 0 \dots f-1$} \STATE{$t_{\lambda'} = \sum_{\lambda'=0}^{f-1} W_f(\lambda,\lambda') v_{b p + (\lambda p_{i-1} + \mu)}$} \COMMENT{DFT matrix-multiply module} \ENDFOR \FOR{$\lambda = 0 \dots f-1$} \STATE{$v'_{(\lambda'q + b)p_{i-1} + \mu} \Leftarrow \omega^{-\mu\lambda'}_{p_{i}} t_\lambda$} \ENDFOR \ENDFOR \ENDFOR \STATE{$v \Leftarrow v'$} \ENDFOR \end{algorithm} % When $\mu=0$ we are applying an inverse DFT to half-complex data, giving a real result. The twiddle factors are all unity. We can code this as a special case, just as we did for the forward routine. We start with complex module and eliminate the redundant terms. In this case it is the final result which has the zero imaginary part, and we eliminate redundant terms by using the half-complex symmetry of the input data. When $\mu=1 \dots p_{i-1}/2 - 1$ we have full complex transforms on complex data, so no simplification is possible. When $\mu = p_{i-1}/2$ (which occurs only when $p_{i-1}$ is even) we have a combination of twiddle factors and DFTs on data with the shifted half-complex symmetry which give a real result. We implement this as a special module, essentially by inverting the system of equations given for the forward case. We use the modules given by Temperton, appropriately modified for our version of the algorithm. He uses a slightly different convention which differs by factors of two for some terms (consult his paper for details~\cite{temperton83real}). For $N=2$, % \begin{equation} \begin{array}{ll} x_0 = 2 a_0, & x_1 = - 2 b_0 . \end{array} \end{equation} % For $N=3$, % \begin{equation} \begin{array}{ll} t_1 = a_0 - a_1, & t_2 = \sqrt{3} b_1, \\ \end{array} \end{equation} \begin{equation} \begin{array}{lll} x_0 = 2 a_0 + a_1, & x_1 = t_1 - t_2, & x_2 = - t_1 - t_2 \end{array} \end{equation} % For $N=4$, \begin{equation} \begin{array}{ll} t_1 = \sqrt{2} (b_0 + b_1), & t_2 = \sqrt{2} (a_0 - a_1), \end{array} \end{equation} \begin{equation} \begin{array}{ll} x_0 = 2(a_0 + a_1), & x_1 = t_2 - t_1 , \end{array} \end{equation} \begin{equation} \begin{array}{ll} x_2 = 2(b_1 - b_0), & x_3 = -(t_2 + t_1). \end{array} \end{equation} % For $N=5$, % \begin{equation} \begin{array}{ll} t_1 = 2 (a_0 + a_1), & t_2 = t_1 / 4 - a_2, \end{array} \end{equation} \begin{equation} \begin{array}{ll} t_3 = (\sqrt{5}/2) (a_0 - a_1), \end{array} \end{equation} \begin{equation} \begin{array}{l} t_4 = 2(\sin(2\pi/10) b_0 + \sin(2\pi/5) b_1), \end{array} \end{equation} \begin{equation} \begin{array}{l} t_5 = 2(\sin(2\pi/10) b_0 - \sin(2\pi/5) b_1), \end{array} \end{equation} \begin{equation} \begin{array}{ll} t_6 = t_3 + t_2, & t_7 = t_3 - t_2, \end{array} \end{equation} \begin{equation} \begin{array}{ll} x_0 = t_1 + a_2, & x_1 = t_6 - t_4 , \end{array} \end{equation} \begin{equation} \begin{array}{ll} x_2 = t_7 - t_5, & x_3 = - t_7 - t_5, \end{array} \end{equation} \begin{equation} \begin{array}{ll} x_4 = -t_6 - t_4. \end{array} \end{equation} \section{Conclusions} % We have described the basic algorithms for one-dimensional radix-2 and mixed-radix FFTs. It would be nice to have a pedagogical explanation of the split-radix FFT algorithm, which is faster than the simple radix-2 algorithm we used. We could also have a whole chapter on multidimensional FFTs. % %\section{Multidimensional FFTs} %\section{Testing FFTs, Numerical Analysis} %\nocite{*} \bibliographystyle{unsrt} \bibliography{fftalgorithms} \end{document}