|
| 1 | +#ifndef BFFT_BRUUN_RADIX4_KERNEL_HPP |
| 2 | +#define BFFT_BRUUN_RADIX4_KERNEL_HPP |
| 3 | + |
| 4 | +// Experimental self-contained Bruun radix-4 forward kernel. |
| 5 | +// |
| 6 | +// Two forward paths: |
| 7 | +// forward_residues() – breadth-first, one norm_q per tree node |
| 8 | +// forward_residues_radix4() – depth-first, fused 2-level radix-4 nodes (norm2_fused) |
| 9 | +// |
| 10 | +// Both produce the same N-element residue vector as BFFT's forward_residues. |
| 11 | +// No SIMD – scalar reference only. |
| 12 | + |
| 13 | +#include <cassert> |
| 14 | +#include <cmath> |
| 15 | +#include <cstdlib> |
| 16 | +#include <cstring> |
| 17 | + |
| 18 | +namespace bruun_radix4 { |
| 19 | + |
| 20 | +static inline bool is_pow2(int n) { return n > 0 && (n & (n - 1)) == 0; } |
| 21 | +static inline int ilog2(int n) { int l = 0; while (n > 1) { n >>= 1; ++l; } return l; } |
| 22 | + |
| 23 | +static inline int gray_decode(int g) { |
| 24 | + for (int s = 1; s < 32; s <<= 1) g ^= g >> s; |
| 25 | + return g; |
| 26 | +} |
| 27 | + |
| 28 | +static inline int bit_rev(int r, int t) { |
| 29 | + int out = 0; |
| 30 | + for (int i = 0; i < t; ++i) { out = (out << 1) | (r & 1); r >>= 1; } |
| 31 | + return out; |
| 32 | +} |
| 33 | + |
| 34 | +static inline int bruun_idx(int m, int L) { |
| 35 | + int t = 0; |
| 36 | + for (int x = m; x > 1; x >>= 1) ++t; |
| 37 | + int r = m ^ (1 << t); |
| 38 | + return (2 * gray_decode(bit_rev(r, t)) + 1) << ((L - 2) - t); |
| 39 | +} |
| 40 | + |
| 41 | +// --------------------------------------------------------------------------- |
| 42 | +// Chebyshev radix-4 even/odd split: 14q flops for q coefficient lanes. |
| 43 | +// |
| 44 | +// f(y) = a0 + a1 y + a2 y^2 + a3 y^3 |
| 45 | +// E(y^2) = a0 + a2 y^2, O(y^2) = a1 + a3 y^2 |
| 46 | +// f(±u) = E(u²) ± u O(u²), f(±v) = E(v²) ± v O(v²) |
| 47 | +// --------------------------------------------------------------------------- |
| 48 | +struct Cheb4Const { double u, u2, v, v2; }; |
| 49 | + |
| 50 | +static inline void chebyshev_radix4_split( |
| 51 | + const double* a0, const double* a1, |
| 52 | + const double* a2, const double* a3, |
| 53 | + double* pu, double* mu, double* pv, double* mv, |
| 54 | + int q, double u, double u2, double v, double v2) |
| 55 | +{ |
| 56 | + for (int n = 0; n < q; ++n) { |
| 57 | + double eu = a0[n] + a2[n] * u2; |
| 58 | + double ou = a1[n] + a3[n] * u2; |
| 59 | + double sou = u * ou; |
| 60 | + double ev = a0[n] + a2[n] * v2; |
| 61 | + double ov = a1[n] + a3[n] * v2; |
| 62 | + double sov = v * ov; |
| 63 | + pu[n] = eu + sou; |
| 64 | + mu[n] = eu - sou; |
| 65 | + pv[n] = ev + sov; |
| 66 | + mv[n] = ev - sov; |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +// --------------------------------------------------------------------------- |
| 71 | +// RFFT_Radix4 – self-contained forward residue transform |
| 72 | +// --------------------------------------------------------------------------- |
| 73 | +class RFFT_Radix4 { |
| 74 | +public: |
| 75 | + RFFT_Radix4() = default; |
| 76 | + ~RFFT_Radix4() { std::free(C_); } |
| 77 | + |
| 78 | + RFFT_Radix4(const RFFT_Radix4&) = delete; |
| 79 | + RFFT_Radix4& operator=(const RFFT_Radix4&) = delete; |
| 80 | + |
| 81 | + bool init(int n) { |
| 82 | + if (!is_pow2(n) || n < 16) return false; |
| 83 | + N_ = n; |
| 84 | + L_ = ilog2(n); |
| 85 | + C_ = static_cast<double*>(std::calloc(n / 2, sizeof(double))); |
| 86 | + if (!C_) return false; |
| 87 | + |
| 88 | + if (n >= 4) C_[1] = std::sqrt(0.5); |
| 89 | + for (int m = 1; 2 * m < n / 2; ++m) { |
| 90 | + double c = C_[m], s = stw(m); |
| 91 | + double ce = std::sqrt(0.5 * (1.0 + c)); |
| 92 | + double se = s / (2.0 * ce); |
| 93 | + C_[2 * m] = ce; |
| 94 | + if (2 * m + 1 < n / 2) C_[2 * m + 1] = se; |
| 95 | + } |
| 96 | + return true; |
| 97 | + } |
| 98 | + |
| 99 | + int size() const { return N_; } |
| 100 | + |
| 101 | + // Breadth-first forward residue transform (single-level norm_q per node). |
| 102 | + void forward_residues(const double* input, double* v) const { |
| 103 | + std::memcpy(v, input, sizeof(double) * N_); |
| 104 | + for (int jj = 0; jj < L_ - 1; ++jj) |
| 105 | + fwd_stage(v, jj); |
| 106 | + } |
| 107 | + |
| 108 | + // Depth-first forward using fused 2-level radix-4 nodes where possible. |
| 109 | + void forward_residues_radix4(const double* input, double* v) const { |
| 110 | + if (N_ < 64) { forward_residues(input, v); return; } |
| 111 | + |
| 112 | + const int h = N_ / 2; |
| 113 | + for (int i = 0; i < h; ++i) { |
| 114 | + v[i] = input[i] + input[h + i]; |
| 115 | + v[h + i] = input[i] - input[h + i]; |
| 116 | + } |
| 117 | + |
| 118 | + for (int sp = h; sp >= 32; sp >>= 1) { |
| 119 | + subtree_r4(v + sp, sp >> 2, 1); |
| 120 | + binomial(v, sp >> 1); |
| 121 | + } |
| 122 | + spine_tail(v); |
| 123 | + } |
| 124 | + |
| 125 | + // Accessor for twiddle constants (for per-node tests). |
| 126 | + double cosval(int m) const { return C_[m]; } |
| 127 | + double sinval(int m) const { return stw(m); } |
| 128 | + |
| 129 | + Cheb4Const cheb4_const(int m) const { |
| 130 | + Cheb4Const k; |
| 131 | + k.u = C_[4 * m]; |
| 132 | + k.u2 = 0.5 * (1.0 + C_[2 * m]); |
| 133 | + k.v = C_[4 * m + 1]; |
| 134 | + k.v2 = 0.5 * (1.0 - C_[2 * m]); |
| 135 | + return k; |
| 136 | + } |
| 137 | + |
| 138 | +private: |
| 139 | + int N_ = 0, L_ = 0; |
| 140 | + double* C_ = nullptr; |
| 141 | + |
| 142 | + double stw(int m) const { |
| 143 | + return (m <= 1) ? (m == 1 ? C_[1] : 0.0) : C_[m ^ 1]; |
| 144 | + } |
| 145 | + |
| 146 | + // ----- primitive operations ----- |
| 147 | + |
| 148 | + static void binomial(double* v, int h) { |
| 149 | + for (int i = 0; i < h; ++i) { |
| 150 | + double a = v[i], b = v[h + i]; |
| 151 | + v[i] = a + b; |
| 152 | + v[h + i] = a - b; |
| 153 | + } |
| 154 | + } |
| 155 | + |
| 156 | + static void norm_q(double* p, int q, double c, double s) { |
| 157 | + double* A0 = p, *B0 = p + q, *A1 = p + 2 * q, *B1 = p + 3 * q; |
| 158 | + for (int n = 0; n < q; ++n) { |
| 159 | + double a0 = A0[n], b0 = B0[n], a1 = A1[n], b1 = B1[n]; |
| 160 | + double R = c * b0 - s * b1; |
| 161 | + double I = s * b0 + c * b1; |
| 162 | + A0[n] = a0 + R; |
| 163 | + B0[n] = a1 + I; |
| 164 | + A1[n] = a0 - R; |
| 165 | + B1[n] = -a1 + I; |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + static void norm2_fused(double* p, int q, |
| 170 | + double c, double s, |
| 171 | + double c0, double s0, |
| 172 | + double c1, double s1) |
| 173 | + { |
| 174 | + const int qh = q >> 1; |
| 175 | + double* A0 = p, *B0 = p + q, *A1 = p + 2 * q, *B1 = p + 3 * q; |
| 176 | + for (int n = 0; n < qh; ++n) { |
| 177 | + double a0n = A0[n], a0h = A0[qh + n]; |
| 178 | + double b0n = B0[n], b0h = B0[qh + n]; |
| 179 | + double a1n = A1[n], a1h = A1[qh + n]; |
| 180 | + double b1n = B1[n], b1h = B1[qh + n]; |
| 181 | + |
| 182 | + double Rn = c * b0n - s * b1n; |
| 183 | + double In = s * b0n + c * b1n; |
| 184 | + double Rh = c * b0h - s * b1h; |
| 185 | + double Ih = s * b0h + c * b1h; |
| 186 | + |
| 187 | + double u0 = a0n + Rn, uh = a0h + Rh; |
| 188 | + double w0 = a1n + In, wh = a1h + Ih; |
| 189 | + double v0 = a0n - Rn, vh = a0h - Rh; |
| 190 | + double x0 = In - a1n, xh = Ih - a1h; |
| 191 | + |
| 192 | + double R0 = c0 * uh - s0 * wh; |
| 193 | + double I0 = s0 * uh + c0 * wh; |
| 194 | + double R1 = c1 * vh - s1 * xh; |
| 195 | + double I1 = s1 * vh + c1 * xh; |
| 196 | + |
| 197 | + A0[n] = u0 + R0; |
| 198 | + A0[qh + n] = w0 + I0; |
| 199 | + B0[n] = u0 - R0; |
| 200 | + B0[qh + n] = I0 - w0; |
| 201 | + A1[n] = v0 + R1; |
| 202 | + A1[qh + n] = x0 + I1; |
| 203 | + B1[n] = v0 - R1; |
| 204 | + B1[qh + n] = I1 - x0; |
| 205 | + } |
| 206 | + } |
| 207 | + |
| 208 | + // ----- breadth-first stages ----- |
| 209 | + |
| 210 | + void fwd_stage(double* v, int jj) const { |
| 211 | + const int s = N_ >> jj; |
| 212 | + const int h = s >> 1; |
| 213 | + const int q = s >> 2; |
| 214 | + const int m_end = 1 << jj; |
| 215 | + |
| 216 | + binomial(v, h); |
| 217 | + |
| 218 | + for (int m = 1; m < m_end; ++m) |
| 219 | + norm_q(v + m * s, q, C_[m], stw(m)); |
| 220 | + } |
| 221 | + |
| 222 | + // ----- depth-first radix-4 subtree ----- |
| 223 | + |
| 224 | + void subtree_r4(double* v, int q, int m) const { |
| 225 | + if (q >= 16) { |
| 226 | + norm2_fused(v, q, C_[m], stw(m), |
| 227 | + C_[2 * m], stw(2 * m), |
| 228 | + C_[2 * m + 1], stw(2 * m + 1)); |
| 229 | + int qq = q >> 2, cm = 4 * m; |
| 230 | + subtree_r4(v, qq, cm); |
| 231 | + subtree_r4(v + q, qq, cm + 1); |
| 232 | + subtree_r4(v + 2 * q, qq, cm + 2); |
| 233 | + subtree_r4(v + 3 * q, qq, cm + 3); |
| 234 | + return; |
| 235 | + } |
| 236 | + if (q == 8) { |
| 237 | + norm_q(v, 8, C_[m], stw(m)); |
| 238 | + leaf_d3(v, 2 * m); |
| 239 | + leaf_d3(v + 16, 2 * m + 1); |
| 240 | + return; |
| 241 | + } |
| 242 | + if (q == 4) { |
| 243 | + leaf_d3(v, m); |
| 244 | + return; |
| 245 | + } |
| 246 | + if (q == 2) { |
| 247 | + norm_q(v, 2, C_[m], stw(m)); |
| 248 | + norm_q(v, 1, C_[2 * m], stw(2 * m)); |
| 249 | + norm_q(v + 4, 1, C_[2 * m + 1], stw(2 * m + 1)); |
| 250 | + return; |
| 251 | + } |
| 252 | + } |
| 253 | + |
| 254 | + void leaf_d3(double* p, int m) const { |
| 255 | + norm_q(p, 4, C_[m], stw(m)); |
| 256 | + norm_q(p, 2, C_[2 * m], stw(2 * m)); |
| 257 | + norm_q(p + 8, 2, C_[2 * m + 1], stw(2 * m + 1)); |
| 258 | + norm_q(p, 1, C_[4 * m], stw(4 * m)); |
| 259 | + norm_q(p + 4, 1, C_[4 * m + 1], stw(4 * m + 1)); |
| 260 | + norm_q(p + 8, 1, C_[4 * m + 2], stw(4 * m + 2)); |
| 261 | + norm_q(p + 12, 1, C_[4 * m + 3], stw(4 * m + 3)); |
| 262 | + } |
| 263 | + |
| 264 | + void spine_tail(double* v) const { |
| 265 | + leaf_d3(v + 16, 1); |
| 266 | + binomial(v, 8); |
| 267 | + norm_q(v + 8, 2, C_[1], stw(1)); |
| 268 | + norm_q(v + 8, 1, C_[2], stw(2)); |
| 269 | + norm_q(v + 12, 1, C_[3], stw(3)); |
| 270 | + binomial(v, 4); |
| 271 | + norm_q(v + 4, 1, C_[1], stw(1)); |
| 272 | + binomial(v, 2); |
| 273 | + } |
| 274 | +}; |
| 275 | + |
| 276 | +// --------------------------------------------------------------------------- |
| 277 | +// Standalone norm_q and norm2_fused for use by external test code. |
| 278 | +// --------------------------------------------------------------------------- |
| 279 | +static inline void norm_q_standalone(double* p, int q, double c, double s) { |
| 280 | + double* A0 = p, *B0 = p + q, *A1 = p + 2 * q, *B1 = p + 3 * q; |
| 281 | + for (int n = 0; n < q; ++n) { |
| 282 | + double a0 = A0[n], b0 = B0[n], a1 = A1[n], b1 = B1[n]; |
| 283 | + double R = c * b0 - s * b1; |
| 284 | + double I = s * b0 + c * b1; |
| 285 | + A0[n] = a0 + R; |
| 286 | + B0[n] = a1 + I; |
| 287 | + A1[n] = a0 - R; |
| 288 | + B1[n] = -a1 + I; |
| 289 | + } |
| 290 | +} |
| 291 | + |
| 292 | +static inline void norm2_fused_standalone(double* p, int q, |
| 293 | + double c, double s, |
| 294 | + double c0, double s0, |
| 295 | + double c1, double s1) |
| 296 | +{ |
| 297 | + const int qh = q >> 1; |
| 298 | + double* A0 = p, *B0 = p + q, *A1 = p + 2 * q, *B1 = p + 3 * q; |
| 299 | + for (int n = 0; n < qh; ++n) { |
| 300 | + double a0n = A0[n], a0h = A0[qh + n]; |
| 301 | + double b0n = B0[n], b0h = B0[qh + n]; |
| 302 | + double a1n = A1[n], a1h = A1[qh + n]; |
| 303 | + double b1n = B1[n], b1h = B1[qh + n]; |
| 304 | + |
| 305 | + double Rn = c * b0n - s * b1n; |
| 306 | + double In = s * b0n + c * b1n; |
| 307 | + double Rh = c * b0h - s * b1h; |
| 308 | + double Ih = s * b0h + c * b1h; |
| 309 | + |
| 310 | + double u0 = a0n + Rn, uh = a0h + Rh; |
| 311 | + double w0 = a1n + In, wh = a1h + Ih; |
| 312 | + double v0 = a0n - Rn, vh = a0h - Rh; |
| 313 | + double x0 = In - a1n, xh = Ih - a1h; |
| 314 | + |
| 315 | + double R0 = c0 * uh - s0 * wh; |
| 316 | + double I0 = s0 * uh + c0 * wh; |
| 317 | + double R1 = c1 * vh - s1 * xh; |
| 318 | + double I1 = s1 * vh + c1 * xh; |
| 319 | + |
| 320 | + A0[n] = u0 + R0; |
| 321 | + A0[qh + n] = w0 + I0; |
| 322 | + B0[n] = u0 - R0; |
| 323 | + B0[qh + n] = I0 - w0; |
| 324 | + A1[n] = v0 + R1; |
| 325 | + A1[qh + n] = x0 + I1; |
| 326 | + B1[n] = v0 - R1; |
| 327 | + B1[qh + n] = I1 - x0; |
| 328 | + } |
| 329 | +} |
| 330 | + |
| 331 | +} // namespace bruun_radix4 |
| 332 | + |
| 333 | +#endif // BFFT_BRUUN_RADIX4_KERNEL_HPP |
0 commit comments