Skip to content

Add BDCT kernel: paired radix-4 half-shift transform + DCT-IV#41

Merged
falseywinchnet merged 1 commit into
mainfrom
bdct-kernel
Jun 23, 2026
Merged

Add BDCT kernel: paired radix-4 half-shift transform + DCT-IV#41
falseywinchnet merged 1 commit into
mainfrom
bdct-kernel

Conversation

@falseywinchnet

Copy link
Copy Markdown
Owner

What

Adds BDCT, a new kernel alongside the Bruun real kernel that computes the native half-bin-shifted transform

H[k] = sum_n x[n] exp(-2*pi*i*(k+1/2)*n/N),   k = 0..N-1

for real input, plus a DCT-IV built on top of it. New files:

  • src/detail/bdct_kernel.hpp — the kernel (header-only)
  • include/bfft/bdct.h, include/bfft/bdct.hpp — public C ABI + RAII C++ wrapper, alongside the bfft headers
  • src/bdct.cpp — C ABI implementation compiled into libbfft
  • examples/dctiv_benchmark.cpp — validation + benchmark
  • Makefile, CMakeLists.txt — build wiring

Why / how

Same Bruun-style decimation logic, specialized to the half-shifted spectrum. The decimation is radix-4: for N = 4M, each child is itself a length-M half-shift transform of real data, so A_r[M-1-k] = conj(A_r[k]). Pairing each output position k with its partner kp = M-1-k inside the radix-4 combine lets the two share the three child twiddle multiplies (t, t^2, t^3) — halving the twiddle multiplies of the naive radix-4 half-shift combine, the same way the real kernel pairs conjugate residues.

Because the input is real, H[N-1-k] = conj(H[k]), so the kernel emits only the contiguous packed lower half H[0..N/2-1] — no DC/Nyquist special case, no final reorder. The inverse is the exact algebraic inverse of the forward (machine-precision roundtrip, no 1/N).

A length-N DCT-IV is recovered from a length-2N half-shift of the zero-padded input followed by one output rotation.

Notes for reviewers

  • Precision: float and double only (no long double — it is just double on the NEON target). The kernel is templated; half_shift_plan/_f32 and dctiv_plan/_f32.
  • Reuses heap_array, complex_t/complex_f32_t and the SIMD-backend macros from bruun_kernel.hpp; only the decimation arithmetic is new.
  • SIMD specialization: the double forward combine has an explicit 128-bit SoA path (bdct_combine_fwd_simd_f64) that runs on NEON and SSE2 through the shared Bruun V2_* macros — children/twiddles are transposed into real/imag lane registers and partner positions are written by address arithmetic (no vector reversal). The scalar path remains the exact reference for every backend, precision, and tail. Wider AVX / float / inverse vectorization are left as follow-ups rather than shipping intrinsics that can't be compiled or tested on the arm64 host.
  • Each public plan carries both a double and a float kernel so one handle serves both precisions (mirrors the bfft real plan), at the cost of twiddle tables in both precisions.

Validation

examples/dctiv_benchmark.cpp checks correctness in both precisions via:

  1. analytic exact tests — impulse half-shift (|H[k]| = 1, closed-form phase) and DCT-IV of a basis vector (exact N/2 spike);
  2. an independent same-precision direct-DFT cross-check;
  3. the exact inverse roundtrip.

Double: roundtrip ~1e-15, analytic/DFT agreement at the reference's own accumulation floor. Float: ~1e-7. The existing make test suite still passes.

Adds a new header-only kernel (src/detail/bdct_kernel.hpp) alongside the
Bruun real kernel, computing the native half-bin-shifted transform

    H[k] = sum_n x[n] exp(-2*pi*i*(k+1/2)*n/N)

for real input via a paired radix-4 decimation. Each output position k is
paired with its partner kp = M-1-k inside the radix-4 combine so the two
share the three child twiddle multiplies (t, t^2, t^3), halving the twiddle
multiplies of the naive radix-4 half-shift combine. Output is the contiguous
packed lower half H[0..N/2-1]; no DC/Nyquist special case, no final reorder.
The inverse is the exact algebraic inverse (machine-precision roundtrip).

A length-N DCT-IV is built on top via a length-2N half-shift of the
zero-padded input plus one output rotation.

Details:
- Templated for float and double (no long double; matches the real kernel).
- Reuses heap_array, complex_t/complex_f32_t and the SIMD-backend macros
  from bruun_kernel.hpp; only the decimation arithmetic is new.
- Explicit 128-bit SoA SIMD specialization for the double forward combine,
  running on NEON and SSE2 through the shared Bruun V2 macros; the scalar
  path remains the exact reference for every backend, precision and tail.

Public API alongside the bfft headers:
- include/bfft/bdct.h  (C ABI; reuses bfft_complex / bfft_status)
- include/bfft/bdct.hpp (RAII bfft::half_shift / bfft::dctiv)
- src/bdct.cpp implements the C ABI into libbfft.
- Makefile and CMakeLists wire the new source and headers into both builds.

Validation: examples/dctiv_benchmark.cpp checks correctness in float and
double via analytic exact tests (impulse half-shift, DCT-IV basis vector),
an independent same-precision direct-DFT cross-check, and the exact inverse
roundtrip, plus timing. Existing test suite still passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@falseywinchnet falseywinchnet merged commit 6d9bacd into main Jun 23, 2026
4 checks passed
@falseywinchnet falseywinchnet deleted the bdct-kernel branch June 23, 2026 12:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant