Skip to content

Commit 35efa45

Browse files
quentin kuttenkulerclaude
authored andcommitted
Rework BDCT into first-class BODFT primitive with iterative scheduling
Reframes the half-bin transform as BODFT (Bruun odd-frequency DFT), a first-class half-bin spectral primitive, and removes all DCT-I..IV framing and code. BODFT computes H[k] = sum_n x[n] exp(-2*pi*i*(k+1/2)*n/N) for real input -- the spectrum sampled halfway between the ordinary FFT bins -- emitting the packed lower half H[0..N/2-1]. Changes: - Rename kernel/API bdct -> bodft (namespace bodft, bodft::plan/plan_f32, C ABI bodft_plan/bodft_forward/bodft_inverse[_f32], C++ bfft::bodft). - Strip the DCT-IV path entirely (dctiv_plan, DCT-IV API, dctiv benchmark). - Replace the recursion with iterative scheduling: a build-time radix-4 digit-reversal permutation drives one leaf pass, then log4(N) ping-pong combine passes run leaves-up (forward) / top-down (inverse). This also improves locality (forward ~20% faster than the recursion at large N). - Forward and inverse are first class in both float and double. Forward uses the explicit 128-bit SoA SIMD combine (V2 double, V4F float, FMA, portable SSE+NEON); inverse is the exact algebraic inverse (scalar). - Precomputed t, t^2, t^3 twiddle tables (three muls per combine, no derivation chain). Validation: examples/bodft_benchmark.cpp checks both precisions across N=2..8192 -- forward vs direct DFT, exact impulse phase, exact inverse roundtrip (~1e-16 double, ~1e-7 float). Warning-clean under -Wall -Wextra -Wpedantic; existing make test suite still passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c89a72a commit 35efa45

11 files changed

Lines changed: 806 additions & 869 deletions

File tree

CMakeLists.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ option(BFFT_ENABLE_PFFFT_BENCHMARK "Enable PFFFT in the benchmark example when f
1717
set(BFFT_PUBLIC_HEADERS
1818
include/bfft/bfft.h
1919
include/bfft/bfft.hpp
20-
include/bfft/bdct.h
21-
include/bfft/bdct.hpp)
20+
include/bfft/bodft.h
21+
include/bfft/bodft.hpp)
2222

2323
set(BFFT_SOURCES
2424
src/bfft.cpp
25-
src/bdct.cpp)
25+
src/bodft.cpp)
2626

2727
set(BFFT_PLATFORM_LIBS)
2828
if(NOT WIN32)

Makefile

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@ LIB_CXXFLAGS := $(CXXFLAGS) $(AUTO_SIMD_FLAGS)
4646

4747
SRC := src/bfft.cpp
4848
OBJ := $(BUILD_DIR)/src/bfft.o
49-
BDCT_SRC := src/bdct.cpp
50-
BDCT_OBJ := $(BUILD_DIR)/src/bdct.o
51-
LIB_OBJS := $(OBJ) $(BDCT_OBJ)
49+
BODFT_SRC := src/bodft.cpp
50+
BODFT_OBJ := $(BUILD_DIR)/src/bodft.o
51+
LIB_OBJS := $(OBJ) $(BODFT_OBJ)
5252
STATIC_LIB := $(BUILD_DIR)/lib$(LIB_NAME).a
5353
SHARED_LIB := $(BUILD_DIR)/lib$(LIB_NAME).so
5454
PC_FILE := $(BUILD_DIR)/$(LIB_NAME).pc
5555
BENCH := $(BUILD_DIR)/examples/benchmark
56-
DCTIV_BENCH := $(BUILD_DIR)/examples/dctiv_benchmark
56+
BODFT_BENCH := $(BUILD_DIR)/examples/bodft_benchmark
5757
APPLE_BENCH := $(BUILD_DIR)/examples/apple_benchmark
5858
LOCALITY_PROBE := $(BUILD_DIR)/examples/locality_probe
5959
C_DEMO := $(BUILD_DIR)/examples/c_api_demo
@@ -85,7 +85,7 @@ $(BUILD_DIR):
8585
$(OBJ): $(SRC) include/bfft/bfft.h src/detail/bruun_kernel.hpp | $(BUILD_DIR)
8686
$(CXX) $(LIB_CPPFLAGS) $(LIB_CXXFLAGS) -c $< -o $@
8787

88-
$(BDCT_OBJ): $(BDCT_SRC) include/bfft/bdct.h include/bfft/bfft.h src/detail/bdct_kernel.hpp src/detail/bruun_kernel.hpp | $(BUILD_DIR)
88+
$(BODFT_OBJ): $(BODFT_SRC) include/bfft/bodft.h include/bfft/bfft.h src/detail/bodft_kernel.hpp src/detail/bruun_kernel.hpp | $(BUILD_DIR)
8989
$(CXX) $(LIB_CPPFLAGS) $(LIB_CXXFLAGS) -c $< -o $@
9090

9191
$(STATIC_LIB): $(LIB_OBJS)
@@ -101,7 +101,7 @@ $(PC_FILE): pkgconfig/bfft.pc.in | $(BUILD_DIR)
101101
-e 's|@PROJECT_VERSION@|$(VERSION)|g' \
102102
$< > $@
103103

104-
examples: $(BENCH) $(DCTIV_BENCH) $(APPLE_EXAMPLES) $(LOCALITY_PROBE) $(C_DEMO) $(CPP_DEMO)
104+
examples: $(BENCH) $(BODFT_BENCH) $(APPLE_EXAMPLES) $(LOCALITY_PROBE) $(C_DEMO) $(CPP_DEMO)
105105

106106
asm-check: $(ASM_OUTPUTS)
107107
@if [ -z "$(ASM_OUTPUTS)" ]; then echo "No x86 assembly variants supported by $(CXX)."; fi
@@ -115,7 +115,7 @@ $(SSE2_ASM): $(SRC) include/bfft/bfft.h src/detail/bruun_kernel.hpp | $(BUILD_DI
115115
$(BENCH): examples/benchmark.cpp include/bfft/bfft.hpp $(STATIC_LIB) | $(BUILD_DIR)
116116
$(CXX) $(CPPFLAGS) $(INCLUDES) $(CXXFLAGS) $(AUTO_SIMD_FLAGS) $< $(STATIC_LIB) $(LDLIBS) $(DL_LIBS) -o $@
117117

118-
$(DCTIV_BENCH): examples/dctiv_benchmark.cpp src/detail/bdct_kernel.hpp src/detail/bruun_kernel.hpp | $(BUILD_DIR)
118+
$(BODFT_BENCH): examples/bodft_benchmark.cpp src/detail/bodft_kernel.hpp src/detail/bruun_kernel.hpp | $(BUILD_DIR)
119119
$(CXX) $(CPPFLAGS) $(INCLUDES) $(CXXFLAGS) $(AUTO_SIMD_FLAGS) $< $(LDLIBS) -o $@
120120

121121
$(APPLE_BENCH): examples/apple_benchmark.cpp include/bfft/bfft.hpp $(STATIC_LIB) | $(BUILD_DIR)
@@ -177,17 +177,17 @@ install: all $(PC_FILE)
177177
$(INSTALL) -d $(DESTDIR)$(PREFIX)/lib/pkgconfig
178178
$(INSTALL) -m 0644 include/bfft/bfft.h $(DESTDIR)$(PREFIX)/include/bfft/bfft.h
179179
$(INSTALL) -m 0644 include/bfft/bfft.hpp $(DESTDIR)$(PREFIX)/include/bfft/bfft.hpp
180-
$(INSTALL) -m 0644 include/bfft/bdct.h $(DESTDIR)$(PREFIX)/include/bfft/bdct.h
181-
$(INSTALL) -m 0644 include/bfft/bdct.hpp $(DESTDIR)$(PREFIX)/include/bfft/bdct.hpp
180+
$(INSTALL) -m 0644 include/bfft/bodft.h $(DESTDIR)$(PREFIX)/include/bfft/bodft.h
181+
$(INSTALL) -m 0644 include/bfft/bodft.hpp $(DESTDIR)$(PREFIX)/include/bfft/bodft.hpp
182182
$(INSTALL) -m 0644 $(STATIC_LIB) $(DESTDIR)$(PREFIX)/lib/lib$(LIB_NAME).a
183183
$(INSTALL) -m 0755 $(SHARED_LIB) $(DESTDIR)$(PREFIX)/lib/lib$(LIB_NAME).so
184184
$(INSTALL) -m 0644 $(PC_FILE) $(DESTDIR)$(PREFIX)/lib/pkgconfig/$(LIB_NAME).pc
185185

186186
uninstall:
187187
rm -f $(DESTDIR)$(PREFIX)/include/bfft/bfft.h
188188
rm -f $(DESTDIR)$(PREFIX)/include/bfft/bfft.hpp
189-
rm -f $(DESTDIR)$(PREFIX)/include/bfft/bdct.h
190-
rm -f $(DESTDIR)$(PREFIX)/include/bfft/bdct.hpp
189+
rm -f $(DESTDIR)$(PREFIX)/include/bfft/bodft.h
190+
rm -f $(DESTDIR)$(PREFIX)/include/bfft/bodft.hpp
191191
rm -f $(DESTDIR)$(PREFIX)/lib/lib$(LIB_NAME).a
192192
rm -f $(DESTDIR)$(PREFIX)/lib/lib$(LIB_NAME).so
193193
rm -f $(DESTDIR)$(PREFIX)/lib/pkgconfig/$(LIB_NAME).pc
Lines changed: 34 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
// DCT-IV / half-shift (BDCT) benchmark and validation.
1+
// BODFT (odd-frequency DFT) benchmark and validation.
22
//
3-
// Validates the paired radix-4 half-shift kernel and the DCT-IV built on it,
4-
// in both double and single precision, without any long double (there is no
5-
// extended precision on the NEON target). Correctness is checked three ways:
3+
// Validates the iterative paired radix-4 BODFT kernel in both double and single
4+
// precision, without any long double (there is no extended precision on the NEON
5+
// target). Correctness is checked three ways:
66
//
7-
// 1. analytic exact: half-shift of a unit impulse has |H[k]| == 1 with a
8-
// closed-form phase, and DCT-IV of a single basis vector is a clean spike;
7+
// 1. analytic exact: the BODFT of a unit impulse has |H[k]| == 1 with a
8+
// closed-form phase;
99
// 2. independent cross-check against a direct same-precision DFT;
1010
// 3. exact inverse roundtrip.
1111
//
1212
// Build (from repo root):
13-
// c++ -O3 -std=c++17 -Iinclude examples/dctiv_benchmark.cpp -lm -o build/examples/dctiv_benchmark
13+
// c++ -O3 -std=c++17 -Iinclude examples/bodft_benchmark.cpp -lm -o build/examples/bodft_benchmark
1414

15-
#include "../src/detail/bdct_kernel.hpp"
15+
#include "../src/detail/bodft_kernel.hpp"
1616

1717
#include <chrono>
1818
#include <cmath>
@@ -33,48 +33,29 @@ std::vector<RT> make_input(int n, std::mt19937_64& rng) {
3333
return x;
3434
}
3535

36-
// --- correctness for one precision -----------------------------------------
37-
38-
template <class HS, class DCT, class CT, class RT>
36+
template <class Plan, class CT, class RT>
3937
void run_correctness(int N, std::mt19937_64& rng, const char* tag) {
40-
HS hs(N);
41-
DCT dct(N);
38+
Plan p(N);
4239
const int bins = N / 2;
4340

44-
// (1a) half-shift of a unit impulse at n0: H[k] = exp(-2*pi*i*(k+1/2)*n0/N).
41+
// (1) BODFT of a unit impulse at n0: H[k] = exp(-2*pi*i*(k+1/2)*n0/N).
4542
std::vector<RT> imp(static_cast<std::size_t>(N), static_cast<RT>(0));
4643
const int n0 = 1;
4744
imp[static_cast<std::size_t>(n0)] = static_cast<RT>(1);
4845
std::vector<CT> Himp(static_cast<std::size_t>(bins));
49-
hs.forward(imp.data(), Himp.data());
46+
p.forward(imp.data(), Himp.data());
5047
double imp_err = 0.0;
5148
for (int k = 0; k < bins; ++k) {
52-
const double ang = -2.0 * PI * (static_cast<double>(k) + 0.5) *
53-
static_cast<double>(n0) / static_cast<double>(N);
49+
const double ang = -2.0 * PI * (k + 0.5) * n0 / N;
5450
const double dr = static_cast<double>(Himp[static_cast<std::size_t>(k)].re) - std::cos(ang);
5551
const double di = static_cast<double>(Himp[static_cast<std::size_t>(k)].im) - std::sin(ang);
5652
imp_err = std::max(imp_err, std::sqrt(dr * dr + di * di));
5753
}
5854

59-
// (1b) DCT-IV of basis vector m: x[n]=cos(pi*(m+1/2)*(n+1/2)/N) -> (N/2)*delta_km.
60-
const int m = (N >= 8) ? 3 : 1;
61-
std::vector<RT> basis(static_cast<std::size_t>(N));
62-
for (int n = 0; n < N; ++n) {
63-
basis[static_cast<std::size_t>(n)] = static_cast<RT>(
64-
std::cos(PI * (m + 0.5) * (n + 0.5) / N));
65-
}
66-
std::vector<RT> c(static_cast<std::size_t>(N));
67-
dct.forward(basis.data(), c.data());
68-
double dct_err = 0.0;
69-
for (int k = 0; k < N; ++k) {
70-
const double expect = (k == m) ? 0.5 * N : 0.0;
71-
dct_err = std::max(dct_err, std::fabs(static_cast<double>(c[static_cast<std::size_t>(k)]) - expect));
72-
}
73-
7455
// (2) random signal vs direct same-precision DFT, and (3) roundtrip.
7556
std::vector<RT> x = make_input<RT>(N, rng);
7657
std::vector<CT> H(static_cast<std::size_t>(bins));
77-
hs.forward(x.data(), H.data());
58+
p.forward(x.data(), H.data());
7859
double xcheck = 0.0, ref_mag = 0.0;
7960
for (int k = 0; k < bins; ++k) {
8061
double sr = 0.0, si = 0.0;
@@ -90,29 +71,24 @@ void run_correctness(int N, std::mt19937_64& rng, const char* tag) {
9071
}
9172

9273
std::vector<RT> xr(static_cast<std::size_t>(N));
93-
hs.inverse(H.data(), xr.data());
74+
p.inverse(H.data(), xr.data());
9475
double round_err = 0.0;
9576
for (int n = 0; n < N; ++n) {
9677
round_err = std::max(round_err,
9778
std::fabs(static_cast<double>(xr[static_cast<std::size_t>(n)] -
9879
x[static_cast<std::size_t>(n)])));
9980
}
10081

101-
std::printf("%-6s N %7d hs_impulse %.3e dctiv_basis %.3e "
102-
"hs_vs_dft(rel) %.3e roundtrip %.3e\n",
103-
tag, N, imp_err, dct_err,
104-
ref_mag > 0 ? xcheck / ref_mag : 0.0, round_err);
82+
std::printf("%-6s N %7d impulse %.3e vs_dft(rel) %.3e roundtrip %.3e\n",
83+
tag, N, imp_err, ref_mag > 0 ? xcheck / ref_mag : 0.0, round_err);
10584
}
10685

107-
// --- timing ----------------------------------------------------------------
108-
109-
template <class HS, class DCT, class CT, class RT>
86+
template <class Plan, class CT, class RT>
11087
void run_timing(int N, std::mt19937_64& rng, const char* tag) {
11188
std::vector<RT> x = make_input<RT>(N, rng);
112-
HS hs(N);
113-
DCT dct(N);
89+
Plan p(N);
11490
std::vector<CT> H(static_cast<std::size_t>(N / 2));
115-
std::vector<RT> c(static_cast<std::size_t>(N));
91+
std::vector<RT> xr(static_cast<std::size_t>(N));
11692

11793
long iters = 1;
11894
{
@@ -122,19 +98,19 @@ void run_timing(int N, std::mt19937_64& rng, const char* tag) {
12298
}
12399

124100
using clock = std::chrono::steady_clock;
125-
hs.forward(x.data(), H.data());
126-
dct.forward(x.data(), c.data());
101+
p.forward(x.data(), H.data());
102+
p.inverse(H.data(), xr.data());
127103

128104
auto t0 = clock::now();
129-
for (long i = 0; i < iters; ++i) hs.forward(x.data(), H.data());
105+
for (long i = 0; i < iters; ++i) p.forward(x.data(), H.data());
130106
auto t1 = clock::now();
131-
for (long i = 0; i < iters; ++i) dct.forward(x.data(), c.data());
107+
for (long i = 0; i < iters; ++i) p.inverse(H.data(), xr.data());
132108
auto t2 = clock::now();
133109

134-
const double hs_ns = std::chrono::duration<double, std::nano>(t1 - t0).count() / iters;
135-
const double dct_ns = std::chrono::duration<double, std::nano>(t2 - t1).count() / iters;
136-
std::printf("%-6s N %7d halfshift %10.1f ns dctiv %10.1f ns (%ld iters)\n",
137-
tag, N, hs_ns, dct_ns, iters);
110+
const double fwd_ns = std::chrono::duration<double, std::nano>(t1 - t0).count() / iters;
111+
const double inv_ns = std::chrono::duration<double, std::nano>(t2 - t1).count() / iters;
112+
std::printf("%-6s N %7d forward %10.1f ns inverse %10.1f ns (%ld iters)\n",
113+
tag, N, fwd_ns, inv_ns, iters);
138114
}
139115

140116
} // namespace
@@ -145,20 +121,20 @@ int main(int argc, char** argv) {
145121
std::printf("backend: %s\n", bruun::simd_backend_name());
146122

147123
std::printf("\n== correctness (double) ==\n");
148-
for (int N = 4; N <= 8192; N <<= 1)
149-
run_correctness<bdct::half_shift_plan, bdct::dctiv_plan, bdct::complex_t, double>(N, rng, "f64");
124+
for (int N = 2; N <= 8192; N <<= 1)
125+
run_correctness<bodft::plan, bodft::complex_t, double>(N, rng, "f64");
150126

151127
std::printf("\n== correctness (float) ==\n");
152-
for (int N = 4; N <= 8192; N <<= 1)
153-
run_correctness<bdct::half_shift_plan_f32, bdct::dctiv_plan_f32, bdct::complex_f32_t, float>(N, rng, "f32");
128+
for (int N = 2; N <= 8192; N <<= 1)
129+
run_correctness<bodft::plan_f32, bodft::complex_f32_t, float>(N, rng, "f32");
154130

155131
std::printf("\n== timing (double) ==\n");
156132
for (int N = 64; N <= (1 << 20); N <<= 1)
157-
run_timing<bdct::half_shift_plan, bdct::dctiv_plan, bdct::complex_t, double>(N, rng, "f64");
133+
run_timing<bodft::plan, bodft::complex_t, double>(N, rng, "f64");
158134

159135
std::printf("\n== timing (float) ==\n");
160136
for (int N = 64; N <= (1 << 20); N <<= 1)
161-
run_timing<bdct::half_shift_plan_f32, bdct::dctiv_plan_f32, bdct::complex_f32_t, float>(N, rng, "f32");
137+
run_timing<bodft::plan_f32, bodft::complex_f32_t, float>(N, rng, "f32");
162138

163139
(void)argc;
164140
(void)argv;

include/bfft/bdct.h

Lines changed: 0 additions & 101 deletions
This file was deleted.

0 commit comments

Comments
 (0)