Files
Ayush Baranwal ffcaef924a Add fft()/invfft() functions for DFT calculation (#1152)
Addresses #707

Implements the Radix-2 Cooley-Tukey Fast Fourier Transform (FFT) algorithm to
compute the discrete fourier transform and inverse discrete fourier transform of a signal.

Inputs are currently restricted to lengths of powers of 2.
2026-08-21 04:23:58 +03:00

111 lines
3.0 KiB
Python

from .calculus import defun
def _fft_cooley_tuckey(ctx, values, inverse=False):
"""
This function implements the Radix-2 Cooley-Tukey FFT algorithm iteratively.
It computes the Fast Fourier Transform (or Inverse FFT) of a sequence of
complex numbers.
https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm
"""
n = len(values)
if n <= 1:
return values
# Bit-Reversal Permutation
transformed = [ctx.zero] * n
num_bits = n.bit_length() - 1
for i in range(n):
rev = 0
val = values[i]
for _ in range(num_bits):
rev <<= 1
rev |= (i & 1)
i >>= 1
transformed[rev] = val
sign = ctx.one if inverse else -ctx.one
length = 2
while length <= n:
half = length // 2
w_len = ctx.expjpi(2 * sign / length)
for i in range(0, n, length):
w = ctx.one
for j in range(half):
u = transformed[i + j]
v = transformed[i + j + half] * w
transformed[i + j] = u + v
transformed[i + j + half] = u - v
w *= w_len
length <<= 1
return transformed
@defun
def fft(ctx, values):
r"""
Computes the Discrete Fourier Transform (DFT) of a sequence.
Raises NotImplementedError if the input sequence length is not a power of 2.
**Examples**
>>> from mpmath import mp
>>> mp.pretty = True
>>> mp.fft([1, 0, 0, 0])
[1.0, (1.0 + 0.0j), 1.0, (1.0 + 0.0j)]
>>> mp.fft([1 + 2j, 1 + 2j])
[(2.0 + 4.0j), (0.0 + 0.0j)]
>>> mp.fft([1, 2, 3, 4])
[10.0, (-2.0 + 2.0j), -2.0, (-2.0 - 2.0j)]
"""
n = len(values)
if n == 0:
return []
is_power_of_two = (n & (n - 1)) == 0
if not is_power_of_two:
raise NotImplementedError("FFT is only implemented for lengths that "
f"are powers of 2, got length: {n}")
converted_values = [ctx.convert(v) for v in values]
with ctx.extraprec(10):
result = _fft_cooley_tuckey(ctx, converted_values)
return [+v for v in result]
@defun
def invfft(ctx, values):
r"""
Computes the inverse Discrete Fourier Transform (IDFT) of a sequence.
Raises NotImplementedError if the input sequence length is not a power of 2.
**Examples**
>>> from mpmath import mp
>>> mp.pretty = True
>>> mp.invfft([1, 1, 1, 1])
[1.0, (0.0 + 0.0j), 0.0, (0.0 + 0.0j)]
>>> x = [1, 2, 3, 4]
>>> mp.invfft(mp.fft(x))
[(1.0 + 0.0j), (2.0 + 0.0j), (3.0 + 0.0j), (4.0 + 0.0j)]
"""
n = len(values)
if n == 0:
return []
is_power_of_two = (n & (n - 1)) == 0
if not is_power_of_two:
raise NotImplementedError("Inverse FFT is only implemented for lengths that "
f"are powers of 2, got length: {n}")
converted_values = [ctx.convert(v) for v in values]
with ctx.extraprec(10):
result = _fft_cooley_tuckey(ctx, converted_values, True)
return [val / n for val in result]