fft/engines/real.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <algorithm> | ||
| 4 | #include <cassert> | ||
| 5 | #include <span> | ||
| 6 | #include <utility> | ||
| 7 | #include <vector> | ||
| 8 | |||
| 9 | #include "fft/core.hpp" | ||
| 10 | #include "fft/engine.hpp" | ||
| 11 | |||
| 12 | namespace wala::fft::engines { | ||
| 13 | |||
| 14 | // Convolve real (floating point) values by packing into complex numbers with | ||
| 15 | // a'[t] = a[2t] + i * a[2t+1] | ||
| 16 | // We use conjugate symmetry to untangle/retangle the two. | ||
| 17 | // TODO: Add type bounds? | ||
| 18 | template <typename dbl = double> struct real { | ||
| 19 | using value_type = dbl; | ||
| 20 | static constexpr bool commutative = true; | ||
| 21 | using cnum = cplx<dbl>; | ||
| 22 | using core = fft_core<cnum>; | ||
| 23 |
2/2✓ Branch 67 → 68 taken 2 times.
✓ Branch 84 → 85 taken 6 times.
|
768 | struct transformed { |
| 24 | vector<cnum> v; | ||
| 25 | 435 | int size() const { return 2 * sz(v); } | |
| 26 | }; | ||
| 27 | using product = transformed; | ||
| 28 | // Precision is caller-managed for this engine (see add), so scale is untracked. | ||
| 29 | static constexpr int unit_scale = 0; | ||
| 30 | template <int A = 0> using transformed_t = transformed; | ||
| 31 | template <int K = 0> using product_t = product; | ||
| 32 | |||
| 33 | 428 | static int packed_size(int n) { return std::max(n / 2, 1); } | |
| 34 | 4 | static void pack(std::span<const dbl> a, std::span<cnum> c) { | |
| 35 |
4/4✓ Branch 6 → 7 taken 95 times.
✓ Branch 6 → 12 taken 99 times.
✓ Branch 18 → 3 taken 194 times.
✓ Branch 18 → 19 taken 4 times.
|
198 | for (int i = 0; i < sz(a); i++) (i & 1 ? c[i/2].y : c[i/2].x) = a[i]; |
| 36 | 4 | } | |
| 37 | // Spectrum of the real (odd = false) or imaginary (odd = true) part of the packed | ||
| 38 | // sequence at bitrev entry t, by conjugate symmetry with the entry of w^{-k}. | ||
| 39 | 17176 | static cnum part(const transformed& f, int t, bool odd) { | |
| 40 | 17176 | cnum g = conj(f.v[core::conj_index(t)]); | |
| 41 |
4/4✓ Branch 12 → 13 taken 8588 times.
✓ Branch 12 → 24 taken 8588 times.
✓ Branch 36 → 37 taken 8588 times.
✓ Branch 36 → 38 taken 8588 times.
|
17176 | return odd ? (f.v[t] - g) * cnum(0, dbl(-0.5)) : (f.v[t] + g) * cnum(dbl(0.5)); |
| 42 | } | ||
| 43 | // Given the spectra (s0, s1) of a real sequence x at w_{2mo}^q and w_{2mo}^{q+mo}, | ||
| 44 | // the packed-transform entry of x at packed size mo: the even/odd interleaves of x | ||
| 45 | // have spectra (s0 +- s1)/2 (the odd one twisted by w_{2mo}^{-q}). | ||
| 46 | 4150 | static cnum retangle(cnum s0, cnum s1, int mo, int q) { | |
| 47 | 4150 | cnum s = (s0 + s1) * cnum(dbl(0.5)); | |
| 48 | 4150 | cnum d = (s0 - s1) * cnum(dbl(0.5)) * core::inv_rt[mo + q]; | |
| 49 | 4150 | return s + cnum(-d.y, d.x); | |
| 50 | } | ||
| 51 | |||
| 52 | 252 | static transformed transform(std::span<const dbl> a, int n) { | |
| 53 | 252 | assert(sz(a) <= 2 * n); | |
| 54 | 252 | transformed r; | |
| 55 |
1/1✓ Branch 15 → 16 taken 252 times.
|
252 | r.v.assign(packed_size(n), cnum(0)); |
| 56 |
2/2✓ Branch 42 → 18 taken 6706 times.
✓ Branch 42 → 43 taken 252 times.
|
6958 | for (int i = 0; i < sz(a); i++) { |
| 57 |
2/2✓ Branch 18 → 19 taken 123 times.
✓ Branch 18 → 20 taken 6583 times.
|
6706 | int j = i < n ? i : i - n; |
| 58 |
2/2✓ Branch 23 → 24 taken 3281 times.
✓ Branch 23 → 33 taken 3425 times.
|
6706 | ((j & 1) ? r.v[j/2].y : r.v[j/2].x) += a[i]; |
| 59 | } | ||
| 60 |
1/1✓ Branch 46 → 47 taken 252 times.
|
252 | core::forward(std::span<cnum>(r.v)); |
| 61 | 252 | return r; | |
| 62 | } | ||
| 63 | 51 | static void extend_to(transformed& t, int m, std::span<const dbl> coeffs) { | |
| 64 | 51 | assert(!(m & (m-1)) && sz(coeffs) <= 2 * m); | |
| 65 |
2/2✓ Branch 11 → 12 taken 43 times.
✓ Branch 11 → 149 taken 8 times.
|
141 | if (t.size() >= m) return; |
| 66 |
3/3✓ Branch 17 → 18 taken 39 times.
✓ Branch 17 → 46 taken 4 times.
✓ Branch 19 → 20 taken 39 times.
|
164 | if (t.size() == 0) { t = transform(coeffs, m); return; } |
| 67 |
1/1✓ Branch 48 → 49 taken 4 times.
|
4 | auto buf = buffer_pool<cnum>::get((sz(coeffs) + 1) / 2); |
| 68 | 16 | std::fill(buf.span().begin(), buf.span().end(), cnum(0)); | |
| 69 | 4 | pack(coeffs, buf.span()); | |
| 70 |
2/2✓ Branch 146 → 98 taken 9 times.
✓ Branch 146 → 147 taken 4 times.
|
26 | while (t.size() < m) { |
| 71 | 9 | int s = sz(t.v); | |
| 72 |
1/1✓ Branch 103 → 104 taken 9 times.
|
9 | t.v.resize(2 * s); |
| 73 | // packed coeffs past 2s are zero: they didn't fit in the transform we're a prefix of | ||
| 74 |
1/1✓ Branch 134 → 135 taken 9 times.
|
18 | core::extend( |
| 75 | 9 | std::span<cnum>(t.v), | |
| 76 | 36 | std::span<const cnum>(buf.span()).first(size_t(min(sz(buf.span()), 2 * s))) | |
| 77 | ); | ||
| 78 | } | ||
| 79 | 4 | } | |
| 80 | 20 | static transformed downsample(const transformed& t, int n, bool odd) { return half(t, n, odd); } | |
| 81 | 12 | static transformed upsample(const transformed& t, int n, bool odd) { return unhalf(t, n, odd); } | |
| 82 | // A(-x) negates the odd (imaginary-slot) coefficients, i.e. conjugates the packed | ||
| 83 | // sequence; the transform of a conjugated sequence is the conjugate at w^(-k). | ||
| 84 | 4 | static transformed negate_arg(const transformed& t, int n) { | |
| 85 | 4 | int m = packed_size(n); | |
| 86 | 4 | assert(n >= 2 && sz(t.v) >= m); | |
| 87 |
1/1✓ Branch 16 → 35 taken 4 times.
|
4 | transformed r; r.v.resize(m); |
| 88 |
2/2✓ Branch 35 → 17 taken 43 times.
✓ Branch 35 → 36 taken 4 times.
|
47 | for (int j = 0; j < m; j++) r.v[j] = conj(t.v[core::conj_index(j)]); |
| 89 | 4 | return r; | |
| 90 | } | ||
| 91 | // b[k] = a[-k mod n] packs to b'[t] = Re a'[-t] + i Im a'[-t-1], so the packed spectrum | ||
| 92 | // at w = w_m^k is X(w^-1) + i w^-1 Y(w^-1) in terms of the real/imaginary-part spectra. | ||
| 93 | 15 | static transformed of_reverse(const transformed& t, int n) { | |
| 94 | 15 | int m = packed_size(n); | |
| 95 | 15 | assert(sz(t.v) >= m); | |
| 96 |
1/1✓ Branch 15 → 16 taken 15 times.
|
15 | transformed r; r.v.resize(m); |
| 97 |
2/2✓ Branch 16 → 17 taken 6 times.
✓ Branch 16 → 29 taken 9 times.
|
15 | if (m == 1) { r.v[0] = t.v[0]; return r; } |
| 98 |
1/1✓ Branch 29 → 63 taken 9 times.
|
9 | core::init(m); |
| 99 |
2/2✓ Branch 63 → 30 taken 126 times.
✓ Branch 63 → 64 taken 9 times.
|
135 | for (int j = 0; j < m; j++) { |
| 100 | 126 | int k = core::brev(j, m); | |
| 101 |
2/2✓ Branch 32 → 33 taken 63 times.
✓ Branch 32 → 38 taken 63 times.
|
126 | cnum iw = k < m / 2 ? core::inv_rt[m / 2 + k] : -core::inv_rt[k]; |
| 102 | 126 | int cj = core::conj_index(j); | |
| 103 | 126 | cnum x = part(t, cj, false), y = part(t, cj, true) * iw; | |
| 104 | 126 | r.v[j] = x + cnum(-y.y, y.x); | |
| 105 | } | ||
| 106 | return r; | ||
| 107 | } | ||
| 108 | 20 | static transformed half(const transformed& f, int n, bool odd) { | |
| 109 | 40 | assert(n >= 2 && f.size() >= 2 * n); | |
| 110 | 20 | int mo = n / 2; | |
| 111 | 20 | core::init(2 * mo); | |
| 112 |
1/1✓ Branch 17 → 31 taken 20 times.
|
20 | transformed r; r.v.resize(mo); |
| 113 |
2/2✓ Branch 31 → 18 taken 310 times.
✓ Branch 31 → 32 taken 20 times.
|
330 | for (int u = 0; u < mo; u++) { |
| 114 | 310 | r.v[u] = retangle(part(f, 2*u, odd), part(f, 2*u+1, odd), mo, core::brev(u, mo)); | |
| 115 | } | ||
| 116 | 20 | return r; | |
| 117 | } | ||
| 118 | // Inverse of half: the size-n packed transform of the input spread as evens or odds. | ||
| 119 | 12 | static transformed unhalf(const transformed& f, int n, bool odd) { | |
| 120 | 24 | assert(n >= 2 && 2 * f.size() >= n); | |
| 121 | 12 | int mo = packed_size(n); | |
| 122 |
1/1✓ Branch 17 → 18 taken 12 times.
|
12 | transformed r; r.v.resize(mo); |
| 123 |
1/2✗ Branch 18 → 19 not taken.
✓ Branch 18 → 28 taken 12 times.
|
12 | if (mo == 1) { |
| 124 | ✗ | r.v[0] = part(f, 0, false); | |
| 125 | } else { | ||
| 126 | // The spread-as-evens sequence packs to the plain (purely real) input sequence, | ||
| 127 | // so untangle its packed transform into the full spectrum: | ||
| 128 | // A(w) = E(w^2) + w O(w^2) at w = +-w_{2mi}^brev(u, mi). | ||
| 129 | 12 | int mi = mo / 2; | |
| 130 |
1/1✓ Branch 28 → 60 taken 12 times.
|
12 | core::init(2 * mi); |
| 131 |
2/2✓ Branch 60 → 29 taken 164 times.
✓ Branch 60 → 61 taken 12 times.
|
176 | for (int u = 0; u < mi; u++) { |
| 132 | 164 | cnum e = part(f, u, false); | |
| 133 | 164 | cnum o = part(f, u, true) * core::rt[mi + core::brev(u, mi)]; | |
| 134 | 164 | r.v[2*u+0] = e + o; | |
| 135 | 164 | r.v[2*u+1] = e - o; | |
| 136 | } | ||
| 137 | } | ||
| 138 | // spreading as odds instead packs into the imaginary slots, scaling the spectrum by i | ||
| 139 |
4/4✓ Branch 61 → 62 taken 6 times.
✓ Branch 61 → 110 taken 6 times.
✓ Branch 89 → 67 taken 164 times.
✓ Branch 89 → 90 taken 6 times.
|
188 | if (odd) for (auto& z : r.v) z = cnum(-z.y, z.x); |
| 140 | 12 | return r; | |
| 141 | } | ||
| 142 | 132 | static product mul(const transformed& a, const transformed& b, int n) { | |
| 143 | 132 | int m = packed_size(n); | |
| 144 | 396 | assert(a.size() >= n && b.size() >= n); | |
| 145 | 132 | core::init(2 * m); | |
| 146 |
1/1✓ Branch 23 → 67 taken 132 times.
|
132 | product p; p.v.resize(m); |
| 147 |
2/2✓ Branch 67 → 24 taken 3686 times.
✓ Branch 67 → 68 taken 132 times.
|
3818 | for (int t = 0; t < m; t++) { |
| 148 | 3686 | int k = core::brev(t, m); | |
| 149 | 3686 | cnum w = core::rt[m + k]; | |
| 150 | 3686 | cnum xa = part(a, t, false), ya = part(a, t, true); | |
| 151 | 3686 | cnum xb = part(b, t, false), yb = part(b, t, true); | |
| 152 | // full spectra at w_{2m}^k and w_{2m}^{k+m} = -w_{2m}^k | ||
| 153 | 3686 | cnum p0 = (xa + w * ya) * (xb + w * yb); | |
| 154 | 3686 | cnum p1 = (xa - w * ya) * (xb - w * yb); | |
| 155 | 3686 | p.v[t] = retangle(p0, p1, m, k); | |
| 156 | } | ||
| 157 | 132 | return p; | |
| 158 | } | ||
| 159 | 5 | static product sq(const transformed& a, int n) { return mul(a, a, n); } | |
| 160 | 8 | static product mul2( | |
| 161 | const transformed& a1, const transformed& b1, | ||
| 162 | const transformed& a2, const transformed& b2, | ||
| 163 | int n | ||
| 164 | ) { | ||
| 165 | 8 | int m = packed_size(n); | |
| 166 | 40 | assert(a1.size() >= n && b1.size() >= n && a2.size() >= n && b2.size() >= n); | |
| 167 | 8 | core::init(2 * m); | |
| 168 |
1/1✓ Branch 35 → 103 taken 8 times.
|
8 | product p; p.v.resize(m); |
| 169 |
2/2✓ Branch 103 → 36 taken 154 times.
✓ Branch 103 → 104 taken 8 times.
|
162 | for (int t = 0; t < m; t++) { |
| 170 | 154 | int k = core::brev(t, m); | |
| 171 | 154 | cnum w = core::rt[m + k]; | |
| 172 | 154 | cnum xa1 = part(a1, t, false), ya1 = part(a1, t, true); | |
| 173 | 154 | cnum xb1 = part(b1, t, false), yb1 = part(b1, t, true); | |
| 174 | 154 | cnum xa2 = part(a2, t, false), ya2 = part(a2, t, true); | |
| 175 | 154 | cnum xb2 = part(b2, t, false), yb2 = part(b2, t, true); | |
| 176 | 154 | cnum p0 = (xa1 + w * ya1) * (xb1 + w * yb1) + (xa2 + w * ya2) * (xb2 + w * yb2); | |
| 177 | 154 | cnum p1 = (xa1 - w * ya1) * (xb1 - w * yb1) + (xa2 - w * ya2) * (xb2 - w * yb2); | |
| 178 | 154 | p.v[t] = retangle(p0, p1, m, k); | |
| 179 | } | ||
| 180 | 8 | return p; | |
| 181 | } | ||
| 182 | ✗ | static product add(product&& a, const product& b) { | |
| 183 | ✗ | assert(a.size() == b.size()); | |
| 184 | ✗ | for (int i = 0; i < sz(a.v); i++) a.v[i] = a.v[i] + b.v[i]; | |
| 185 | ✗ | return std::move(a); | |
| 186 | } | ||
| 187 | // sum_k finish(p)[k] * b[k] = Re sum_t p'[t] conj(b'[t]) = Re (1/m) sum_j P[j] conj(B[j]) | ||
| 188 | 5 | static dbl dot(const product& p, const transformed& t, int n) { | |
| 189 | 5 | int m = packed_size(n); | |
| 190 | 5 | assert(sz(p.v) >= m && sz(t.v) >= m); | |
| 191 | dbl r = 0; | ||
| 192 |
2/2✓ Branch 41 → 14 taken 44 times.
✓ Branch 41 → 42 taken 5 times.
|
49 | for (int j = 0; j < m; j++) r += p.v[j].x * t.v[j].x + p.v[j].y * t.v[j].y; |
| 193 | 5 | return r / dbl(m); | |
| 194 | } | ||
| 195 | 152 | template <typename Op = assign_op> static void finish(product&& p, std::span<dbl> out, Op op = {}) { | |
| 196 | 152 | int m = sz(p.v); | |
| 197 | 152 | assert(sz(out) <= 2 * m); | |
| 198 |
2/2void wala::fft::engines::real<double>::finish<wala::fft::detail::cut_op<double, wala::fft::assign_op> >(wala::fft::engines::real<double>::transformed&&, std::span<double, 18446744073709551615ul>, wala::fft::detail::cut_op<double, wala::fft::assign_op>):
✓ Branch 12 → 13 taken 7 times.
void wala::fft::engines::real<double>::finish<wala::fft::assign_op>(wala::fft::engines::real<double>::transformed&&, std::span<double, 18446744073709551615ul>, wala::fft::assign_op):
✓ Branch 12 → 13 taken 145 times.
|
152 | core::inverse(std::span<cnum>(p.v)); |
| 199 | 152 | dbl d = dbl(1) / dbl(m); | |
| 200 |
8/8void wala::fft::engines::real<double>::finish<wala::fft::detail::cut_op<double, wala::fft::assign_op> >(wala::fft::engines::real<double>::transformed&&, std::span<double, 18446744073709551615ul>, wala::fft::detail::cut_op<double, wala::fft::assign_op>):
✓ Branch 15 → 16 taken 117 times.
✓ Branch 15 → 23 taken 118 times.
✓ Branch 34 → 15 taken 235 times.
✓ Branch 34 → 35 taken 7 times.
void wala::fft::engines::real<double>::finish<wala::fft::assign_op>(wala::fft::engines::real<double>::transformed&&, std::span<double, 18446744073709551615ul>, wala::fft::assign_op):
✓ Branch 15 → 16 taken 3706 times.
✓ Branch 15 → 23 taken 3718 times.
✓ Branch 35 → 15 taken 7424 times.
✓ Branch 35 → 36 taken 145 times.
|
7811 | for (int i = 0; i < sz(out); i++) op(out[i], (i & 1 ? p.v[i/2].y : p.v[i/2].x) * d); |
| 201 | 152 | } | |
| 202 | }; | ||
| 203 | |||
| 204 | } // namespace wala::fft::engines | ||
| 205 |