Fun with FFT

This notebook is part of a lecture I gave in my DSP class on Halloween, Oct 31. I'll clean it up later, but need to post it (even in its current minimalist condition) for my students.

In [3]:
%pylab inline
from scipy.fftpack import fft, ifft
Populating the interactive namespace from numpy and matplotlib

In [4]:
x = array([1,-1,1,-1])
fft(x)
Out[4]:
array([ 0.+0.j,  0.+0.j,  4.+0.j,  0.-0.j])
In [5]:
fft(x,n=8)
Out[5]:
array([ 0.+0.j        ,  1.+0.41421356j,  0.-0.j        ,  1.+2.41421356j,
        4.+0.j        ,  1.-2.41421356j,  0.+0.j        ,  1.-0.41421356j])
In [6]:
xz = r_[x,zeros(4)]
xz
Out[6]:
array([ 1., -1.,  1., -1.,  0.,  0.,  0.,  0.])
In [7]:
fft(xz)
Out[7]:
array([ 0.+0.j        ,  1.+0.41421356j,  0.-0.j        ,  1.+2.41421356j,
        4.+0.j        ,  1.-2.41421356j,  0.+0.j        ,  1.-0.41421356j])
In [8]:
c_[x,zeros(4)]
Out[8]:
array([[ 1.,  0.],
       [-1.,  0.],
       [ 1.,  0.],
       [-1.,  0.]])
In [9]:
y = fft(x)
xhat = ifft(y)
In [10]:
x-xhat
Out[10]:
array([ 0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j])
In [11]:
x2 = rand(1024*1024)
y2 = fft(x2)
x2hat = ifft(y2)
r = x2-x2hat
r[:10]
Out[11]:
array([  1.11022302e-16 +0.00000000e+00j,
         2.22044605e-16 +5.14996032e-19j,
         2.22044605e-16 +4.07962330e-17j,
         2.22044605e-16 +5.33427469e-17j,
         1.11022302e-16 +4.61328024e-17j,
        -1.94289029e-16 -4.28259858e-17j,
         1.66533454e-16 +2.98801962e-17j,
         2.22044605e-16 -5.40169851e-17j,
         0.00000000e+00 -3.37720897e-17j,   3.33066907e-16 +8.51658483e-18j])
In [12]:
max(abs(x2-x2hat))
Out[12]:
1.0431292286976892e-15
In [13]:
x2hat[:10]
Out[13]:
array([ 0.41116206 +0.00000000e+00j,  0.33706664 -5.14996032e-19j,
        0.79717000 -4.07962330e-17j,  0.53664741 -5.33427469e-17j,
        0.79604319 -4.61328024e-17j,  0.19359754 +4.28259858e-17j,
        0.11685530 -2.98801962e-17j,  0.50703792 +5.40169851e-17j,
        0.35640277 +3.37720897e-17j,  0.73290768 -8.51658483e-18j])
In [14]:
real(x2hat)[:10]
Out[14]:
array([ 0.41116206,  0.33706664,  0.79717   ,  0.53664741,  0.79604319,
        0.19359754,  0.1168553 ,  0.50703792,  0.35640277,  0.73290768])
In [15]:
#x = array([0,0,0,1,0,0,0,0]) + 1j*array([0,0,0,0,0,0,0,0])
x = exp(1j*2.1*2*pi*arange(8)/8)
y = fft(x)
subplot(1,2,1)
stem(real(y))
subplot(1,2,2)
stem(imag(y))
xlim([0,8])
Out[15]:
(0, 8)
In [16]:
N=64
omega = 2.3*2*pi/N
n = arange(N)
x = cos(omega*n)
y = fft(x)
stem(abs(y))
Out[16]:
<Container object of 3 artists>

Signal Filtering with FFTs

In [17]:
N=256
sigma = 1.0
omega = 2*pi*35.5/N
omega2 = 2*pi*44/N
#x = exp(omega*1j*arange(N))
x = cos(omega*arange(N))+2*cos(omega2*arange(N))
x += sigma*randn(N)
y = fft(x)
plot(abs(y))
Out[17]:
[<matplotlib.lines.Line2D at 0x113865d10>]
In [18]:
plot(x)
Out[18]:
[<matplotlib.lines.Line2D at 0x1138931d0>]
In [19]:
from scipy.signal import kaiser
window = kaiser(N,beta=14)
plot(abs(fft(x*window)))
Out[19]:
[<matplotlib.lines.Line2D at 0x11309d3d0>]
In [20]:
plot(abs(fft(window)))
Out[20]:
[<matplotlib.lines.Line2D at 0x113514690>]
In [21]:
plot(window)
Out[21]:
[<matplotlib.lines.Line2D at 0x115bf6ad0>]

Dirichlet Kernel, the Discrete Time Sinc Function

The Dirichlet kernel is the discrete time version of the sinc function. For some reason, it gets little of the acclaim of the sinc function. I've even seen textbooks incorrectly use the sinc function when they should be using the Dirichlet function. Even the Wikipedia entry for the sinc function make no mention of the Dirichlet kernel.

The first version uses the piecewise function to take care of the division by 0 problem. However, as written it doesn't work for x being any other integer except 0. I suppose it could be improved, but Dirichlet2 solves the problem a different (and arguably better) way: it allows numpy to compute nan's, then replaced the nan's by the correct limit.

In [36]:
def dirichlet(x,N):
    return piecewise(x, [x==0, x != 0], [N, lambda x: sin(N*pi*x)/sin(pi*x)])

x = linspace(-0.5,0.5,201)
plot(x,abs(dirichlet(x,16)))
Out[36]:
[<matplotlib.lines.Line2D at 0x116bdbc10>]
In [30]:
def dirichlet2(x,N):
    seterr(divide='ignore')
    y = sin(N*pi*x)/sin(pi*x)
    y[isnan(y)] = N
    return y
In [43]:
plot(abs(dirichlet2(linspace(-0.5,0.5,201),16)))
Out[43]:
[<matplotlib.lines.Line2D at 0x11743b7d0>]
In []: