LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fft.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_UTIL_FFT_H
6#define LINE_UTIL_FFT_H
7
8/**
9 * @file
10 * @ingroup line_util
11 * Discrete Fourier transform of arbitrary length, in double complex.
12 *
13 * WHY IT IS HERE. `MG1_CR`, the Bini-Meini point-wise cyclic reduction behind
14 * every M/G/1-type G matrix, evaluates four matrix polynomials at the (nj+1)-th
15 * roots of unity, combines them through a point-wise inverse, and interpolates
16 * the result back. The transform length is nj+1 where nj+1 doubles from the
17 * degree of the block sequence, so it is NOT a power of two in general: a
18 * BMAP with three batch sizes gives a degree-4 sequence and a first transform
19 * of length 4, but a degree-5 one gives length 6. Both must work, and both must
20 * agree with MATLAB's `fft`/`ifft` to roundoff, because the reference's
21 * truncation test compares the interpolated tail against an absolute epsilon.
22 *
23 * The implementation is radix-2 Cooley-Tukey when the length is a power of two
24 * and Bluestein's chirp-z otherwise, which reduces the arbitrary length to a
25 * power-of-two convolution. Bluestein is exact in the same sense the radix-2
26 * transform is -- it is a rearrangement, not an approximation -- so the two
27 * paths differ only in rounding, and neither introduces the O(n) error a naive
28 * quadratic DFT accumulates at n = 2048 (the reference's MaxNumRoot).
29 *
30 * CONVENTION, matching MATLAB. `dft(a, false)` returns
31 * sum_k a_k exp(-2 pi i j k / n); `dft(a, true)` returns
32 * (1/n) sum_k a_k exp(+2 pi i j k / n). The inverse therefore carries the 1/n,
33 * exactly as `ifft` does, so a forward followed by an inverse is the identity.
34 *
35 * DOUBLE ONLY. The twiddle factors are cos/sin, which the exact and
36 * multiprecision number types do not provide; callers that need the transform
37 * at another arithmetic must say so rather than silently losing precision here.
38 */
39
40#include <cmath>
41#include <complex>
42#include <cstddef>
43#include <vector>
44
45#include "line/util/error.h"
46
47namespace line {
48
49namespace fft_detail {
50
51/** Iterative radix-2 Cooley-Tukey, in place. Length must be a power of two. */
52inline void radix2(std::vector<std::complex<double>>& a, bool conjugate) {
53 const std::size_t n = a.size();
54 if (n <= 1) return;
55 for (std::size_t i = 1, j = 0; i < n; ++i) {
56 std::size_t bit = n >> 1;
57 for (; j & bit; bit >>= 1) j ^= bit;
58 j ^= bit;
59 if (i < j) std::swap(a[i], a[j]);
60 }
61 const double pi = 3.14159265358979323846;
62 for (std::size_t len = 2; len <= n; len <<= 1) {
63 const double ang = 2.0 * pi / static_cast<double>(len) * (conjugate ? 1.0 : -1.0);
64 const std::complex<double> wlen(std::cos(ang), std::sin(ang));
65 for (std::size_t i = 0; i < n; i += len) {
66 std::complex<double> w(1.0, 0.0);
67 for (std::size_t k = 0; k < len / 2; ++k) {
68 const std::complex<double> u = a[i + k];
69 const std::complex<double> v = a[i + k + len / 2] * w;
70 a[i + k] = u + v;
71 a[i + k + len / 2] = u - v;
72 w *= wlen;
73 }
74 }
75 }
76}
77
78inline bool is_power_of_two(std::size_t n) { return n != 0 && (n & (n - 1)) == 0; }
79
80/** Bluestein's chirp-z transform for a length that is not a power of two. */
81inline void bluestein(std::vector<std::complex<double>>& a, bool conjugate) {
82 const std::size_t n = a.size();
83 const double pi = 3.14159265358979323846;
84 const double sign = conjugate ? 1.0 : -1.0;
85
86 // The chirp exp(sign i pi k^2 / n); k^2 is reduced mod 2n so the angle
87 // stays small and the cos/sin keep their relative accuracy at large k.
88 std::vector<std::complex<double>> chirp(n);
89 for (std::size_t k = 0; k < n; ++k) {
90 const std::size_t kk = (k * k) % (2 * n);
91 const double ang = sign * pi * static_cast<double>(kk) / static_cast<double>(n);
92 chirp[k] = std::complex<double>(std::cos(ang), std::sin(ang));
93 }
94
95 std::size_t m = 1;
96 while (m < 2 * n - 1) m <<= 1;
97 std::vector<std::complex<double>> x(m, std::complex<double>(0.0, 0.0));
98 std::vector<std::complex<double>> y(m, std::complex<double>(0.0, 0.0));
99 for (std::size_t k = 0; k < n; ++k) x[k] = a[k] * chirp[k];
100 y[0] = std::conj(chirp[0]);
101 for (std::size_t k = 1; k < n; ++k) {
102 y[k] = std::conj(chirp[k]);
103 y[m - k] = std::conj(chirp[k]);
104 }
105
106 radix2(x, false);
107 radix2(y, false);
108 for (std::size_t k = 0; k < m; ++k) x[k] *= y[k];
109 radix2(x, true);
110 const double scale = 1.0 / static_cast<double>(m);
111 for (std::size_t k = 0; k < n; ++k) a[k] = x[k] * scale * chirp[k];
112}
113
114} // namespace fft_detail
115
116/**
117 * In-place DFT of `a`. `inverse` selects the +i sign and the 1/n scaling, so
118 * the pair matches MATLAB's `fft` and `ifft`.
119 */
120inline void dft(std::vector<std::complex<double>>& a, bool inverse) {
121 const std::size_t n = a.size();
122 if (n <= 1) return;
123 if (fft_detail::is_power_of_two(n)) {
124 fft_detail::radix2(a, inverse);
125 } else {
126 fft_detail::bluestein(a, inverse);
127 }
128 if (inverse) {
129 const double scale = 1.0 / static_cast<double>(n);
130 for (std::size_t k = 0; k < n; ++k) a[k] *= scale;
131 }
132}
133
134} // namespace line
135
136#endif // LINE_UTIL_FFT_H
The exception types the port throws.
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Definition linalg.h:72
void dft(std::vector< std::complex< double > > &a, bool inverse)
In-place DFT of a.
Definition fft.h:120