Chapter 4: Detailed Code Breakdown
This chapter provides a thorough, line-by-line commentary of the Python script
bpsk_practice.py, covering all major sections and implementation decisions.
Approximately 10–12 pages of code excerpts with explanatory notes.
4.1 Imports & Parameter Definitions
python
CopyEdit
import numpy as np # array & math operations
import matplotlib.pyplot as plt # plotting functions
from scipy.signal import upfirdn, lfilter, welch
# Simulation parameters
nbits = 1000 # number of bits
sps = 8 # samples per symbol
span = 10 # filter half-length
alpha = 0.35 # RRC roll-off
EbN0_dB = np.arange(0,11,2) # Eb/N₀ range (dB)
EbN0_demo= 6 # single Eb/N₀ for constellation demo
Comments explain the role and typical values of each import and parameter.
4.2 RRC Filter Function Implementation
python
CopyEdit
def rrc_filter(alpha, sps, span):
# Calculate filter taps (length=2*span*sps+1)
N = span * sps
t = np.linspace(-N, N, 2*N+1) / float(sps)
h = np.zeros_like(t)
for i, ti in enumerate(t):
if ti == 0:
h[i] = 1 - alpha + 4*alpha/np.pi
elif abs(ti) == 1/(4*alpha):
h[i] = (alpha/np.sqrt(2)) * (
(1+2/np.pi)*np.sin(np.pi/(4*alpha)) +
(1-2/np.pi)*np.cos(np.pi/(4*alpha))
)
else:
num = np.sin(np.pi*ti*(1-alpha)) +
4*alpha*ti*np.cos(np.pi*ti*(1+alpha))
den = np.pi*ti*(1-(4*alpha*ti)**2)
h[i] = num/den
return h / np.sqrt(np.sum(h**2)) # normalize energy
Explanation of special-case handling and normalization to unit energy.
4.3 Transmitter Section
python
CopyEdit
# Bit generation and mapping
bits = np.random.randint(0, 2, nbits)
symbols= 2*bits - 1
# Upsampling and pulse shaping
tx_signal = upfirdn(h=rrc_taps, x=symbols, up=sps)
Notes on vectorized operations and use of upfirdn for efficient convolution.
4.4 AWGN Channel & Matched Filter
python
CopyEdit
# AWGN noise addition
delta = 1/(2*10**(EbN0_demo/10))
noise = np.sqrt(delta) * np.random.randn(tx_signal.size)
rx_signal = tx_signal + noise
# Matched filtering
mf_output = lfilter(b=rrc_taps, a=[1.0], x=rx_signal)
Discussion on noise scaling and matched filter benefits.
4.5 Sampling & Decision Device
python
CopyEdit
delay = (len(rrc_taps)-1)//2
idx = delay + np.arange(nbits)*sps
samples = mf_output[idx]
decisions = (samples >= 0).astype(int)
Justification for delay compensation and threshold-based decoding.
4.6 BER Computation Loop
python
CopyEdit
ber = []
for eb in EbN0_dB:
noise_var=1/(2*10**(eb/10))
y = lfilter(rrc_taps, 1, tx_signal +
np.sqrt(noise_var)*np.random.randn(tx_signal.size))
s = y[delay + np.arange(nbits)*sps]
dec = (s>=0).astype(int)
ber.append(np.mean(dec != bits))
Discussion on Monte Carlo simulation, loop vectorization trade-offs, and convergence
criteria.
4.7 Plotting Routines
python
CopyEdit
fig1, axs1 = plt.subplots(3,2,figsize=(12,12), constrained_layout=True)
# Panel assignments and styling omitted for brevity
fig2, axs2 = plt.subplots(2,3,figsize=(14,8), constrained_layout=True)
plt.show()
Explanation of subplot grid, shared axes, marker choices, and layout settings.
End of README