Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Fourier Transforms

Fourier Series

The Fourier series enables us to represent periodic functions as infinite sums. In particular, it represents a function as a sum of weighted sin\sin and cos\cos functions.

This is possible because the sin\sin and cos\cos functions form a complete orthogonal set (basis functions). The Fourier series for a function f(x)f(x) with a period of 2π2{\pi} is given by:

Square function and Gibbs phenomenon

Now that we have introduced the Fourier series, let’s look at an example: the positive square wave. This is defined as

f(x)={0if πx<0,1if 0x<π,f(x) = \left\{ \begin{array}{ll} 0 & \text{if } -\pi \leq x < 0, \\ 1 & \text{if } 0 \leq x < \pi, \\ \end{array} \right.

Let’s plot this function.

Source
# Define a function to create a square wave
import numpy as np
import matplotlib.pyplot as plt

def square(x, period):
    # Create array with zeros
    y = np.zeros(len(x))
    # Change zeros to 1 based on a given period in radians (e.g. 2pi, 3pi)
    for i in range(len(x)):
        if (x[i] / period) % 1 < 0.50:
            y[i] = 1.0
    return y

N = 1000  # Number of points
x = np.linspace(-10.0, 10.0, N)

plt.figure(figsize=(7, 4))
plt.plot(x, square(x, 2 * np.pi))
plt.grid(True)
plt.ylim(-0.2, 1.2)
plt.xlabel('x')
plt.show()
<Figure size 700x400 with 1 Axes>

Now let’s try to express the square wave as a Fourier series. First, we calculate the coefficients using the expressions above. Carrying out the calculations, we find

a0=1π0πdx=1,an=1π0πcos(nx)dx=0,n1,a_0 = \frac{1}{\pi} \int_{0}^{\pi} dx = 1,\\ a_n = \frac{1}{\pi} \int_{0}^{\pi} \cos(nx)dx = 0, \,\,\,\,n\geq 1,
bn=1π0πsin(nx)dx={2nπif n is odd,0if n is even.b_n = \frac{1}{\pi} \int_{0}^{\pi} \sin(nx)\,dx = \left\{ \begin{array}{ll} \frac{2}{n\pi} & \text{if } n \text{ is odd}, \\ 0 & \text{if } n \text{ is even}. \\ \end{array} \right.

Now let’s plot the Fourier series for n=10,n=50, n=10, n=50, and n=100n=100.

Source
# Define the cos and sin terms of the Fourier series
def cosTerm(n):
    # Always zero except for n=0
    if n==0: return 1.0
    return 0.

def sinTerm(n):

    if n%2: # n modulo 2 = 1 (True) then Odd
        ret = 2. / (n* np.pi)
    else:
        ret = 0.
    return ret

def fourier(n,x):
    #a_0 term, remember 1/2
    sum = cosTerm(0)/2.0 * np.ones(len(x))
    
    #all other terms
    for i in range(1, n+1):
        sum += sinTerm(i)*np.sin(i*x) + cosTerm(i)*np.cos(i*x)
    return sum

fig, axes = plt.subplots(1, 3, figsize=(14,4), sharey=True)

# Loop over each subplot
for idx, i in enumerate([10, 50, 100]):
    ax = axes[idx]
    ax.plot(x, fourier(i, x), label="n = %g" % (i))
    ax.plot(x, square(x, 2*np.pi), color="black", label="square wave")
    ax.legend(loc="upper right", fontsize="small")
    ax.set_xlabel("x")
    ax.set_ylim(-0.2, 1.2)
    
plt.tight_layout()
plt.show()
<Figure size 1400x400 with 3 Axes>

Fourier Transform

Recall that Euler’s formula expresses eixe^{ix} in terms of sines and cosines, so we can also write the Fourier series using complex numbers:

f(x)=n=0cnexp(i2πnxL)f(x)=\sum_{n=0}^{\infty} c_{n} \exp \left(i \frac{2 \pi n x}{L}\right)

And the coefficients would then be:

cn=1LL2L2f(x)exp(i2πnxL)dxc_{n}=\frac{1}{L} \int_{-\frac{L}{2}}^{\frac{L}{2}} f(x) \exp \left(-i \frac{2 \pi n x}{L}\right) d x

We might not like the use of complex numbers, but it is clearly the same Fourier series. We can also simplify the notation a bit by introducing the “wave number”

kn=2πnL=2πλk_{n}=\frac{2 \pi n}{L} = \frac{2 \pi}{\lambda}

where λ\lambda is the wavelength. If we let the length LL go to infinity, then:

Δkn=2πnLdk\Delta k_{n}=\frac{2 \pi n}{L} \rightarrow d k

the coefficients are:

c(k)=12πf(x)eikxdxc(k)=\frac{1}{2 \pi} \int_{-\infty}^{\infty} f(x) \mathrm{e}^{-i k x} d x

Fourier analysis of time-series data

Periodic data

y=sin(302πt)+12sin(502πt)y = \sin(30 \cdot 2\pi t) + \frac{1}{2} \sin(50 \cdot 2 \pi t)
Source
import scipy.fftpack

# Number of sample points
N = 1000
# Sample spacing
delta = 1.0 / 1000.0

# Time array
t = np.linspace(0.0, delta * N, N)

# Time domain signal
y = np.sin(30.0 * 2.0 * np.pi * t) + 0.5 * np.sin(50.0 * 2.0 * np.pi * t)

# Compute the Fourier Transform
xf = np.linspace(0.0, 1.0 / (2.0 * delta), N // 2)
yf = scipy.fftpack.fft(y)

# Plot both time domain and frequency domain side by side
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6))

# Time domain plot
ax1.plot(t, y)
ax1.set_xlabel("Time")
ax1.set_ylabel("Amplitude")
ax1.set_title("Time Domain Signal")

# Frequency domain plot
ax2.plot(xf, 2.0 / N * np.abs(yf[:N // 2]))
ax2.set_xlabel("Frequency")
ax2.set_ylabel("Amplitude")
ax2.set_title("Frequency Domain Signal")

plt.tight_layout()
plt.show()
<Figure size 1200x600 with 2 Axes>

Noisy periodic data

Source
import numpy as np
import matplotlib.pyplot as plt
from scipy.fftpack import fft

# Generate sample data
t = np.linspace(0.0, 1.0, 500)
y = np.sin(30.0 * 2.0 * np.pi * t) + 0.5 * np.cos(50.0 * 2.0 * np.pi * t)
y = y + 1.5 * np.sin(165.0 * 2.0 * np.pi * t) + np.cos(104 * 2.0 * np.pi * t)

# Compute the Fourier Transform
N = len(t)
yf = fft(y)
xf = np.fft.fftfreq(N, t[1] - t[0])[:N//2]

# Plot both time domain and frequency domain side by side
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6))

# Time domain plot
ax1.plot(t, y)
ax1.set_xlabel("Time")
ax1.set_ylabel("Amplitude")
ax1.set_title("Time Domain Signal")

# Frequency domain plot
ax2.plot(xf, 2.0 / N * np.abs(yf[:N//2]))
ax2.set_xlabel("Frequency")
ax2.set_ylabel("Amplitude")
ax2.set_title("Frequency Domain Signal")

# Show the plots
plt.tight_layout()
plt.show()
<Figure size 1200x600 with 2 Axes>

Noisy non-periodic data

Source
# Apply the exponential decay to the time domain signal
y = y * np.exp(-2 * t)

# Compute the Fourier Transform after the decay
yf = fft(y)

# Plot both time domain and frequency domain side by side
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6))

# Time domain plot after exponential decay
ax1.plot(t, y)
ax1.set_xlabel("Time")
ax1.set_ylabel("Amplitude")
ax1.set_title("Time Domain Signal with Exponential Decay")

# Frequency domain plot after exponential decay
ax2.plot(xf, 2.0 / N * np.abs(yf[:N//2]))
ax2.set_xlabel("Frequency")
ax2.set_ylabel("Amplitude")
ax2.set_title("Frequency Domain Signal with Exponential Decay")

# Show the plots
plt.tight_layout()
plt.show()
<Figure size 1200x600 with 2 Axes>

Fourier transform and the uncertainty relation

Define the Square Function to represent ψ(x)\psi(x): Start with a square function (or rectangular pulse) that has a width ww and height 1, centered around zero. This function can be represented as:

ψ(x)={1,xw20,x>w2\psi(x) = \begin{cases} 1, & |x| \leq \frac{w}{2} \\ 0, & |x| > \frac{w}{2} \end{cases}

Fourier Transform gives us ψ(p)\psi(p): The Fourier transform f^(k)\hat{f}(k) of a square function is a sinc function:

ψ^(k)=wsinc(kw/2)\hat{\psi}(k) = w \, \text{sinc}(kw/2)
Source
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
import numpy as np

# Define the square function
def square_function(x, w):
    return np.where(np.abs(x) <= w / 2, 1, 0)

# Fourier transform of the square function (sinc function)
def fourier_transform_square_function(k, w):
    return w * np.sinc(k * w / (2 * np.pi))

# Define x and k ranges
x = np.linspace(-5, 5, 1000)
k = np.linspace(-20, 20, 1000)

# Set up the figure and axes for animation
fig, axes = plt.subplots(2, 1, figsize=(8, 8))

# Initialize the plots
line1, = axes[0].plot([], [], lw=2)
axes[0].set_ylim([-0.1, 1.1])
axes[0].set_title("Square Function")

line2, = axes[1].plot([], [], lw=2)
axes[1].set_ylim([-0.5, 2.5])
axes[1].set_title("Fourier Transform")

# Set the x-axis limits
axes[0].set_xlim(x.min(), x.max())
axes[1].set_xlim(k.min(), k.max())

# Initialization function
def init():
    line1.set_data([], [])
    line2.set_data([], [])
    return line1, line2

# Animation update function
def update(w):
    # Compute the square function and its Fourier transform
    sq_func = square_function(x, w)
    ft_sq_func = fourier_transform_square_function(k, w)
    
    # Update the data for both plots
    line1.set_data(x, sq_func)
    line2.set_data(k, ft_sq_func)
    
    return line1, line2

# Create the animation
ani = FuncAnimation(fig, update, frames=np.linspace(0.1, 5.0, 100), init_func=init, blit=True)

plt.close(fig)  # Prevents static display of the last frame
HTML(ani.to_jshtml())
Loading...

Fourier transforms of Gaussian functions

A Gaussian function is defined as

f(t)=exp(t22σ2),f(t) = \exp\left(- \frac{t^2}{2{\sigma^2}}\right),

where σ\sigma is the standard deviation of the Gaussian. The Fourier transform of a Gaussian is another Gaussian, such that

F[f(t)]=2πexp(ω2σ22)\mathcal{F}[f(t)] = \sqrt{2\pi}\, \exp\left(- \frac{\omega^2\sigma^2}{2}\right)

Thus we can see that as the Gaussian function gets broader, its Fourier transform gets narrower. To illustrate this, let’s plot the Gaussian and its transform for two different standard deviations.

Source
from scipy import signal

g1 = signal.windows.gaussian(100, std=10)
t = np.linspace(-10, 10, len(g1))

plt.subplot(1, 2, 1)
plt.plot(t, g1)
plt.title('Gaussian Function, std = 10')

FT_omega = np.fft.fftfreq(len(g1), t[1] - t[0])
FT = np.fft.fft(g1)
FT_omega = np.fft.fftshift(FT_omega)
FT = np.fft.fftshift(FT)

plt.subplot(1, 2, 2)
plt.plot(FT_omega, abs(FT))
plt.title('Fourier Transform')
plt.tight_layout()
plt.show()

g2 = signal.windows.gaussian(100, std=1)
t = np.linspace(-10, 10, len(g2))

plt.subplot(1, 2, 1)
plt.plot(t, g2)
plt.title('Gaussian Function, std = 1')

FT_omega = np.fft.fftfreq(len(g2), t[1] - t[0])
FT = np.fft.fft(g2)
FT_omega = np.fft.fftshift(FT_omega)
FT = np.fft.fftshift(FT)

plt.subplot(1, 2, 2)
plt.plot(FT_omega, abs(FT))
plt.title('Fourier Transform')
plt.tight_layout()
plt.show()
<Figure size 640x480 with 2 Axes>
<Figure size 640x480 with 2 Axes>

Delta Function

The delta function, δ(t)\delta(t), is defined as

δ(t)={0if t0,if t=0.\delta(t) = \left\{ \begin{array}{ll} 0 & \text{if } t \neq 0, \\ \infty & \text{if } t = 0. \end{array} \right.

Carrying out the transform, we see that the Fourier transform of the delta function is actually a constant, such that

F[δ(t)]=1\mathcal{F}[{\delta}(t)] = 1

Let’s plot the function and its transform.

Source
from scipy import signal
imp = signal.unit_impulse(100, 'mid') # creates the delta function
t = np.linspace(-5, 5, 100)

plt.subplot(1,2,1)
plt.plot(t, imp)
plt.title('Delta Function')

FT_omega = np.fft.fftfreq(100, t[1] - t[0])
FT = np.fft.fft(imp)

plt.subplot(1,2,2)
plt.plot(FT_omega, abs(FT))
plt.title('Fourier Transform')
plt.ylim([0,1.2])
plt.tight_layout()
plt.show()
<Figure size 640x480 with 2 Axes>

Free Electron and Delta Function Duality

ψ(x)=12πϕ(k)eikxdk,\psi(x) = \frac{1}{\sqrt{2\pi}} \int \phi(k)\, e^{ikx}\, dk,

where eikxe^{ikx} are eigenfunctions of the free-particle Hamiltonian and form a complete orthogonal basis. The coefficient ϕ(k)\phi(k) gives the amplitude of each momentum mode.