ADR 0005: FFT interface — split host and device transforms¶
Status¶
Accepted (2026-07-23).
Context¶
The audit (§5/§7, AB‑4) found the polymorphic FFT interface dishonest: a factory
returning std::unique_ptr<IFFT> for a GPU backend produced an object whose
every virtual method throws, because GPU transforms are non-virtual member
templates over DataBuffer while IFFT only declares host-std::vector
transforms. Polymorphic call sites (e.g. SpectralGradient holding IFFT*)
compile against a GPU backend and fail at runtime. The Backend enum also
omitted HIP (fixed in M3).
An interface must be substitutable by all of its implementations.
Decision¶
Split the single IFFT into two honest interfaces (M5):
IHostFft— forward/backward over host containers (std::vector/ hostFieldView). Implemented by the CPU (FFTW/HeFFTe) backend.IDeviceFft<MemorySpace>— forward/backward overDataBuffer<MemorySpace,T>. Implemented by the CUDA (cuFFT) and HIP (rocFFT) backends.
FFT_Impl<BackendTag> implements whichever interface(s) apply to its backend.
Factories are honest: host factories return IHostFft for host backends
only; device factories (create_cuda/create_hip and the string/enum-driven
equivalents) return IDeviceFft. Requesting a host FFT for a device backend (or
vice versa) throws at construction with a clear message — never returns an
object that throws on use.
Rejected alternative: a single interface templated on a buffer family. Rejected because the configuration layer already knows the backend when it constructs the session, so two concrete interfaces are simpler and avoid leaking a buffer-family template parameter through every call site.
Consequences¶
SpectralGradientand other spectral consumers bind to the interface matching their memory space; no runtime “throws on every method” objects exist.The
Backendenum andbackend_from_stringcover CPU/CUDA/HIP honestly (M3).GPU spectral models no longer need a dead host
CpuFft(dummy_fft); the session constructs the device FFT directly (M8/M10).Precision policy (ADR 0006) applies: workspace is allocated only for the instantiated precision.