Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 7339
Kernel: Python 3

Implementing DFT

Copyright 2019 Allen Downey, MIT License

import numpy as np

Let's start with a known result. The DFT of an impulse is a constant.

N = 4 x = [1, 0, 0, 0]
np.fft.fft(x)
array([1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])

Literal translation

The usual way the DFT is expressed is as a summation. Following the notation on Wikipedia:

Xk=n=0Nxne2πink/N X_k = \sum_{n=0}^N x_n \cdot e^{-2 \pi i n k / N}

Here's a straightforward translation of that formula into Python.

pi = np.pi exp = np.exp
k = 0 sum(x[n] * exp(-2j * pi * k * n / N) for n in range(N))
(1+0j)

Wrapping this code in a function makes the roles of k and n clearer, where k is a free parameter and n is the bound variable of the summation.

def dft_k(x, k): return sum(x[n] * exp(-2j * pi * k * n / N) for n in range(N))

Of course, we usually we compute XX all at once, so we can wrap this function in another function:

def dft(x): N = len(x) X = [dft_k(x, k) for k in range(N)] return X
dft(x)
[(1+0j), (1+0j), (1+0j), (1+0j)]

And the results check out.

DFT as a matrix operation

It is also common to express the DFT as a matrix operation:

X=Wx X = W x

with

Wj,k=ωjk W_{j, k} = \omega^{j k}

and

ω=e2πi/N \omega = e^{-2 \pi i / N}

If we recognize the construction of WW as an outer product, we can use np.outer to compute it.

Here's an implementation of DFT using outer product to construct the DFT matrix, and dot product to compute the DFT.

def dft(x): N = len(x) ks = range(N) args = -2j * pi * np.outer(ks, ks) / N W = exp(args) X = W.dot(x) return X

And the results check out.

dft(x)
array([1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])

Implementing FFT

Finally, we can implement the FFT by translating from math notation:

Xk=Ek+e2πik/NOk X_k = E_k + e^{-2 \pi i k / N} O_k

Where EE is the FFT of the even elements of xx and OO is the DFT of the odd elements of xx.

Here's what that looks like in code.

def fft(x): N = len(x) if N == 1: return x E = fft(x[::2]) O = fft(x[1::2]) ks = np.arange(N) args = -2j * pi * ks / N W = np.exp(args) return np.tile(E, 2) + W * np.tile(O, 2)

The length of EE and OO is half the length of WW, so I use np.tile to double them up.

And the results check out.

fft(x)
array([1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])