Chapter 2: Python Functions & Modules
This chapter covers the core Python libraries and functions used in the BPSK simulation, with
illustrative code examples and plots.
2.1 NumPy: Core Numerical Engine
2.1.1 Array Creation & Memory Layout
np.arange(start, stop, step): Creates a 1D array without Python loops.
np.zeros(shape) / np.ones(shape): Fast pre-allocation of arrays.
Views vs. Copies: Slicing returns a view (no new memory), while functions like
np.copy() force a copy.
python
CopyEdit
# Example: time vector for filter design
sps = 8
span = 10
N = span * sps
t = np.arange(-N, N+1) / float(sps)
print(t[:5], t[-5:]) # first and last five samples
Figure 2.1: Illustration of a NumPy arange array for filter time indices.
(Insert your generated plot here.)
2.1.2 Random Number Generation
Use the new Generator API for reproducibility:
python
CopyEdit
rng = np.random.default_rng(seed=42)
bits = rng.integers(0, 2, size=16)
noise = rng.standard_normal(size=1000)
2.2 Matplotlib: Visualization Library
2.2.1 Creating Multi-Panel Figures
python
CopyEdit
import matplotlib.pyplot as plt
fig, axs = plt.subplots(2, 2, figsize=(8, 6), constrained_layout=True)
axs = axs.flatten()
for ax in axs:
ax.grid(True)
plt.show()
2.2.2 Plot Types & Styling
Line plots: ax.plot(x, y, '-o', markersize=4)
Semilog plots: ax.semilogy(f, Pxx)
Scatter plots: ax.scatter(x, y, s=20)
Stem plots: ax.stem(indices, values, basefmt=' ', markerfmt='C0o')
python
CopyEdit
# Example: PSD line and markers
f = np.linspace(-0.5, 0.5, 512)
Pxx = np.abs(np.fft.fftshift(np.random.randn(512)))
axs[0].semilogy(f, Pxx, '-o', markersize=3)
Figure 2.2: Semilog PSD plot with markers.
(Insert your generated plot here.)
2.3 scipy.signal.upfirdn: Upsampling & FIR Filtering
Signature: y = upfirdn(h, x, up, down=1)
Upsampling: Inserts (up - 1) zeros between samples of x.
Filtering: Convolves the upsampled sequence with h in one call.
python
CopyEdit
from scipy.signal import upfirdn
# Impulse train (h=[1])
imp = upfirdn([1], symbols, up=sps)
# Pulse shaping (h=rrc_taps)
tx = upfirdn(rrc_taps, symbols, up=sps)
2.4 scipy.signal.lfilter: FIR Filtering / Matched Filter
Signature: y = lfilter(b, a, x, zi=None)
Matched Filter: b = rrc_taps, a = 1.0 for FIR.
Initial Conditions: Use zi to set filter memory if required.
python
CopyEdit
from scipy.signal import lfilter
mf_out = lfilter(rrc_taps, [1.0], rx_signal)
2.5 scipy.signal.welch: Power Spectral Density Estimation
Signature: f, Pxx = welch(x, fs, window, nperseg, noverlap)
Procedure: Split x into overlapping segments, apply window, compute FFT, average.
python
CopyEdit
from scipy.signal import welch
f, Pxx = welch(tx_signal, fs=1.0, nperseg=1024, window='hann', noverlap=512)
axs[1].semilogy(f - 0.5, np.fft.fftshift(Pxx), '-o', markersize=3)
Figure 2.3: Welch’s PSD estimate plotted on a semilog axis.
(Insert your generated plot here.)