OFDM Tutorial#
In this tutorial, we compare the performance of a Single Carrier (SC) system
and an OFDM system over a frequency-selective multipath channel using the comnumpy library.
You will learn how to:
Define and simulate a frequency-selective channel.
Evaluate performance using the Symbol Error Rate (SER).
Understand why OFDM outperforms SC in multipath environments.
This tutorial is suitable for engineers and students interested in digital communications, combining practical examples with theoretical insights.
Introduction#
Import Libraries#
We start by importing the necessary libraries:
import numpy as np
import matplotlib.pyplot as plt
import time
from comnumpy.core import Sequential, Recorder
from comnumpy.core.generators import SymbolGenerator
from comnumpy.core.mappers import SymbolMapper, SymbolDemapper
from comnumpy.core.channels import AWGN, FIRChannel
from comnumpy.core.compensators import LinearEqualizer
from comnumpy.core.utils import get_alphabet
from comnumpy.core.metrics import compute_ser
from comnumpy.ofdm.chains import OFDMTransmitter, OFDMReceiver
Simulation Parameters#
Next, we define the parameters of the communication chain, including the modulation order and the channel impulse response for a frequency-selective channel:
# parameters
M = 16
N_h = 5
N = 1280
sigma2 = 0.015
alphabet = get_alphabet("QAM", M)
# generate a random selective channel
h = 0.1*(np.random.randn(N_h) + 1j*np.random.randn(N_h))
h[0] = 1
Here, h represents the channel impulse response.
The first tap is normalized to 1 to preserve the overall channel energy.
Frequency-Selective Channel#
The input-output relation of a frequency-selective channel is:
where \(h[l]\) are the channel taps and \(b[n]\) is the noise. This is also called a Finite Impulse Response (FIR) channel.
Stacking \(N\) samples into a vector form:
where \(\mathbf{H}\) is a Toeplitz convolution matrix constructed from the taps. This formulation highlights that ISI (Inter-Symbol Interference) is unavoidable in SC systems.
Single-Carrier Communication Chain#
The SC chain is defined as:
graph LR;
A[Generator] --> B[Mapper];
B --> C[Channel];
C --> D[AWGN];
D --> E[Equalizer];
E --> F[Demapper];
At the receiver, we apply a Zero-Forcing (ZF) equalizer:
where \(\mathbf{H}^{\dagger}\) is the pseudo-inverse of \(\mathbf{H}\).
Implementation#
The chain is implemented in comnumpy as follows:
# create a simple single carrier chain and simulate
simple_chain = Sequential([
SymbolGenerator(M),
Recorder(name="data_tx"),
SymbolMapper(alphabet),
FIRChannel(h),
AWGN(value=sigma2),
Recorder(name="data_rx"),
LinearEqualizer(h, method="zf"),
Recorder(name="data_rx_eq"),
SymbolDemapper(alphabet)
])
start_time = time.time()
s_rx = simple_chain(N)
stop_time = time.time()
Results#
We evaluate the performance by computing the SER and the execution time, then plot the constellation before and after equalization:
# extract signals, compute ser and elapsed time
s_tx = simple_chain["data_tx"].get_data()
ser = compute_ser(s_tx, s_rx)
elapsed_time = stop_time - start_time
print(f"SER: {ser}")
print(f"elapsed time: {elapsed_time} s")
# plot signal and save
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(8, 4))
for indice, processor_name in enumerate(["data_rx", "data_rx_eq"]):
data_rx = simple_chain[processor_name].get_data()
axes[indice].plot(np.real(data_rx), np.imag(data_rx), ".")
axes[indice].set_title(f"Received signal ({processor_name})")
axes[indice].set_aspect("equal", adjustable="box")
axes[indice].set_xlim([-2, 2])
axes[indice].set_ylim([-2, 2])
For the SC chain, we obtain:
SER = 0.007
Execution time = 0.562 s
OFDM Communication Chain#
In SC systems, equalization requires matrix inversion, which is computationally expensive. OFDM transforms the channel into a set of parallel flat-fading subchannels, each equalized with a simple one-tap filter. This drastically reduces computational complexity and improves performance.
The OFDM chain can be visualized as:
graph LR;
A[Generator] --> B[Mapper];
B --> C[OFDM Tx];
C --> D[Channel];
D --> E[AWGN];
E --> F[OFDM Rx];
F --> G[Demapper];
Transmitter (TX)
graph LR;
A[Mapper] --> B[S2P];
B --> C[IDFT];
C --> D[CP add];
D --> E[P2S];
Receiver (RX)
graph LR;
A[P2S] --> B[CP del];
B --> C[DFT];
C --> D[Equalizer];
D --> E[P2S];
Key blocks:
S2P / P2S: Serial-to-Parallel and Parallel-to-Serial converters.
IDFT / DFT: Transform between frequency and time domains.
CP add / CP del: Insert/remove Cyclic Prefix to handle ISI.
Equalizer: One-tap equalization per subcarrier.
Mathematically, the received vector is:
with \(\mathbf{D} = \mathrm{diag}(H[0], H[1], \dots, H[N-1])\), where \(H[k]\) is the channel frequency response. Thus, OFDM reduces equalization to a diagonal system.
Implementation#
The OFDM chain in comnumpy is implemented as:
plt.savefig(f"{img_dir}/one_shot_ofdm_fig1.png")
# create an OFDM chain and simulate
N_carrier = 128
N_cp = 10
ofdm_chain = Sequential([
SymbolGenerator(M),
Recorder(name="data_tx"),
SymbolMapper(alphabet),
OFDMTransmitter(N_carrier, N_cp), # <- add OFDM transmitter
FIRChannel(h),
AWGN(value=sigma2),
OFDMReceiver(N_carrier, N_cp, h=h), # <- add OFDM receiver
Recorder(name="data_rx"),
SymbolDemapper(alphabet)
])
start_time = time.time()
s_rx = ofdm_chain(N)
stop_time = time.time()
Results#
We compute the SER and runtime, then plot the received constellation:
# extract signals, compute ser and elapsed time
s_tx = ofdm_chain["data_tx"].get_data()
data_rx = ofdm_chain["data_rx"].get_data()
ser = compute_ser(s_tx, s_rx)
elapsed_time = stop_time - start_time
print(f"SER: {ser}")
print(f"elapsed time: {elapsed_time} s")
# plot signal and save
plt.figure()
plt.plot(np.real(data_rx), np.imag(data_rx), ".")
plt.title(f"OFDM Chain: received data")
For the OFDM chain, we obtain:
SER = 0.004 (improved vs SC)
Execution time = 0.007 s (much lower than SC)
Conclusion#
You have compared Single Carrier and OFDM systems over a multipath channel.
You have learned how to:
Model a frequency-selective FIR channel in
comnumpy.Simulate both SC and OFDM systems.
Apply ZF equalization (SC) vs. one-tap equalization (OFDM).
Compare performance in terms of SER and computational cost.
Key takeaway: OFDM transforms a frequency-selective channel into flat-fading subchannels, enabling simple per-subcarrier equalization and superior performance in realistic multipath environments.