Skip to content

FFT

Each FFT object owns one plan and is reused across calls. Multiple instances of different sizes or signs coexist with no global state.

1-D FFT

from doppler.spectral import FFT
import numpy as np

rng = np.random.default_rng(0)
x32 = (rng.standard_normal(1024) + 1j * rng.standard_normal(1024)).astype(np.complex64)

f = FFT(1024)
X32 = f.execute_cf32(x32)    # complex64 in → complex64 out
assert X32.dtype == np.complex64

# Parseval: Σ|x|² == Σ|X|² / N. The error is float32 rounding (~5e-4 on
# this input), so bound it rather than comparing exactly.
err = abs(np.sum(np.abs(x32)**2) - np.sum(np.abs(X32)**2) / 1024)
assert err < 1e-2

CF64 input auto-selects the double-precision path:

x64 = rng.standard_normal(1024) + 1j * rng.standard_normal(1024)
X64 = f.execute_cf64(x64)    # complex128 in → complex128 out
assert X64.dtype == np.complex128

Reusing a plan

Plan once, transform many times — no reallocation on repeated calls:

f = FFT(1024)

for _ in range(1000):
    out = f.execute_cf32(x32)              # out-of-place, returns new array
    out = f.execute_inplace_cf32(x32)     # uses internal buffer, same result

2-D FFT

from doppler.spectral import FFT2D
import numpy as np

rng = np.random.default_rng(0)
x = (rng.standard_normal((64, 64)) + 1j * rng.standard_normal((64, 64))).astype(np.complex64)

f2 = FFT2D(64, 64)
out = f2.execute_cf32(x.ravel())   # flat row-major input and output
assert len(out) == 64 * 64 and out.dtype == np.complex64