πŸ”’ Protected Content

This Python tutorial is for ELEC 360 students only. Please enter the password to continue.

❌ Incorrect password. Please try again.

Python Tutorial

Complete Guide for Signals and Systems Analysis

1️⃣ Getting Started with Python

↑ Go Back

Installing Python

🐍 Anaconda Distribution

Recommended for beginners. Includes Python, NumPy, SciPy, Matplotlib, and Jupyter Notebook.

Download Anaconda

πŸ’» Python.org

Standard Python installation. Install packages separately using pip.

Download Python

☁️ Google Colab

Run Python in your browser, no installation needed. Free GPU access!

Open Google Colab

Essential Libraries for Signal Processing

Terminal / Command Prompt
# Install required packages using pip
pip install numpy scipy matplotlib jupyter

# Or using conda (if using Anaconda)
conda install numpy scipy matplotlib jupyter

Python Development Environments

  • Jupyter Notebook: Interactive notebooks, great for learning and visualization
  • Spyder: MATLAB-like IDE, included with Anaconda
  • VS Code: Popular code editor with Python extensions
  • PyCharm: Full-featured Python IDE
πŸ’‘ Recommendation: Start with Jupyter Notebook or Google Colab for interactive learning, then move to Spyder or VS Code for larger projects.

2️⃣ Python Basics

↑ Go Back

Importing Libraries

Python Code
# Standard imports for signal processing
import numpy as np                    # Numerical operations
import matplotlib.pyplot as plt       # Plotting
from scipy import signal              # Signal processing
from scipy.fft import fft, fftshift   # Fourier transforms

# Display settings for better plots
plt.rcParams['figure.figsize'] = (10, 6)
plt.rcParams['font.size'] = 11

Basic Operations

Python Code
# Basic arithmetic
a = 5
b = 3
sum_val = a + b        # Addition
difference = a - b     # Subtraction
product = a * b        # Multiplication
quotient = a / b       # Division
power = a**2           # Exponentiation

print(f"Sum: {sum_val}")
print(f"Power: {power}")

# Multiple assignment
x, y, z = 1, 2, 3

NumPy Arrays (Similar to MATLAB matrices)

Python Code
# Creating arrays
t = np.arange(0, 10, 0.1)              # Array from 0 to 10, step 0.1
t = np.linspace(0, 10, 100)            # 100 points from 0 to 10
x = np.array([1, 2, 3, 4, 5])          # 1D array
y = np.array([[1], [2], [3], [4], [5]])  # Column vector

# Array operations (element-wise by default!)
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
c = a * b          # Element-wise multiplication [4, 10, 18]
d = a ** 2         # Element-wise power [1, 4, 9]

# Useful array functions
print(f"Length: {len(x)}")
print(f"Shape: {x.shape}")
print(f"Max: {np.max(x)}")
print(f"Min: {np.min(x)}")
print(f"Sum: {np.sum(x)}")
print(f"Mean: {np.mean(x)}")
⚠️ Important: NumPy arrays use element-wise operations by default (unlike MATLAB matrices). Use @ or np.dot() for matrix multiplication.

Common Mathematical Functions

Function Python/NumPy Code Description
Sine np.sin(x) Sine function
Cosine np.cos(x) Cosine function
Exponential np.exp(x) e^x
Natural Log np.log(x) ln(x)
Absolute Value np.abs(x) |x|
Square Root np.sqrt(x) √x

3️⃣ Plotting Signals

↑ Go Back

Basic Plotting

Python Code
# Simple plot
t = np.linspace(0, 2*np.pi, 1000)
y = np.sin(t)

plt.figure()
plt.plot(t, y)
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.title('Sine Wave')
plt.grid(True)
plt.show()

# Multiple plots on same figure
y1 = np.sin(t)
y2 = np.cos(t)

plt.figure()
plt.plot(t, y1, 'b-', label='sin(t)')
plt.plot(t, y2, 'r--', label='cos(t)')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.title('Sine and Cosine')
plt.legend()
plt.grid(True)
plt.show()

Subplots

Python Code
# Create multiple plots in one figure
t = np.linspace(0, 2*np.pi, 1000)

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))

ax1.plot(t, np.sin(t))
ax1.set_title('Sine Wave')
ax1.set_xlabel('Time (s)')
ax1.set_ylabel('Amplitude')
ax1.grid(True)

ax2.plot(t, np.cos(t))
ax2.set_title('Cosine Wave')
ax2.set_xlabel('Time (s)')
ax2.set_ylabel('Amplitude')
ax2.grid(True)

plt.tight_layout()
plt.show()

Plot Customization

Option Code Effect
Line Color 'r', 'b', 'g', 'k' Red, Blue, Green, Black
Line Style '-', '--', ':', '-.' Solid, Dashed, Dotted, Dash-dot
Marker 'o', '*', '+', 'x' Circle, Star, Plus, Cross
Line Width linewidth=2 Thicker line
πŸ’‘ Tip: Use plt.figure() to create new figure windows. Use plt.savefig('filename.png') to save figures.

4️⃣ Signal Operations

↑ Go Back

Creating Basic Signals

Python Code
# Unit Step Function
t = np.linspace(-5, 5, 1000)
u = (t >= 0).astype(float)

plt.figure()
plt.plot(t, u, linewidth=2)
plt.title('Unit Step Function u(t)')
plt.xlabel('Time (s)')
plt.ylabel('u(t)')
plt.grid(True)
plt.ylim([-0.2, 1.2])
plt.show()

# Shifted Unit Step
u_shifted = (t >= 2).astype(float)

plt.figure()
plt.plot(t, u, 'b-', label='u(t)', linewidth=2)
plt.plot(t, u_shifted, 'r--', label='u(t-2)', linewidth=2)
plt.title('Unit Step and Shifted Step')
plt.xlabel('Time (s)')
plt.legend()
plt.grid(True)
plt.show()

# Rectangular Pulse
rect = ((t >= -1) & (t <= 1)).astype(float)

plt.figure()
plt.plot(t, rect, linewidth=2)
plt.title('Rectangular Pulse')
plt.xlabel('Time (s)')
plt.ylabel('rect(t)')
plt.grid(True)
plt.ylim([-0.2, 1.2])
plt.show()

# Exponential Signals
t = np.linspace(0, 5, 1000)

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))

x1 = np.exp(-t)
ax1.plot(t, x1, linewidth=2)
ax1.set_title('Decaying Exponential: $e^{-t}$')
ax1.set_xlabel('Time (s)')
ax1.set_ylabel('Amplitude')
ax1.grid(True)

x2 = np.exp(-2*t)
ax2.plot(t, x2, 'r', linewidth=2)
ax2.set_title('Faster Decay: $e^{-2t}$')
ax2.set_xlabel('Time (s)')
ax2.set_ylabel('Amplitude')
ax2.grid(True)

plt.tight_layout()
plt.show()

Signal Transformations

Python Code
# Time Shifting (Delay)
t = np.linspace(-5, 5, 1000)
x = np.exp(-np.abs(t))

# Different time shifts
x_shift1 = np.exp(-np.abs(t-1))  # Shifted right by 1
x_shift2 = np.exp(-np.abs(t+1))  # Shifted left by 1

plt.figure()
plt.plot(t, x, 'b-', label='x(t)', linewidth=2)
plt.plot(t, x_shift1, 'r--', label='x(t-1)', linewidth=2)
plt.plot(t, x_shift2, 'g-.', label='x(t+1)', linewidth=2)
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.title('Time Shifting')
plt.legend()
plt.grid(True)
plt.show()

# Time Scaling (Compression and Expansion)
x = np.exp(-np.abs(t)) * np.sin(2*np.pi*t)

fig, axes = plt.subplots(3, 1, figsize=(10, 10))

axes[0].plot(t, x, linewidth=2)
axes[0].set_title('Original: x(t)')
axes[0].set_xlabel('Time (s)')
axes[0].set_ylabel('Amplitude')
axes[0].grid(True)

x_compressed = np.exp(-np.abs(2*t)) * np.sin(2*np.pi*2*t)
axes[1].plot(t, x_compressed, 'r', linewidth=2)
axes[1].set_title('Compressed: x(2t)')
axes[1].set_xlabel('Time (s)')
axes[1].set_ylabel('Amplitude')
axes[1].grid(True)

x_expanded = np.exp(-np.abs(0.5*t)) * np.sin(2*np.pi*0.5*t)
axes[2].plot(t, x_expanded, 'g', linewidth=2)
axes[2].set_title('Expanded: x(t/2)')
axes[2].set_xlabel('Time (s)')
axes[2].set_ylabel('Amplitude')
axes[2].grid(True)

plt.tight_layout()
plt.show()

Even and Odd Decomposition

Python Code
# Decompose signal into even and odd parts
t = np.linspace(-5, 5, 1000)
x = np.exp(-np.abs(t)) * np.sin(t)

# Create x(-t) by flipping
x_reversed = np.flip(x)

# Even and odd parts
x_even = (x + x_reversed) / 2
x_odd = (x - x_reversed) / 2

fig, axes = plt.subplots(4, 1, figsize=(10, 12))

axes[0].plot(t, x, linewidth=2)
axes[0].set_title('Original Signal x(t)')
axes[0].set_xlabel('Time (s)')
axes[0].set_ylabel('Amplitude')
axes[0].grid(True)

axes[1].plot(t, x_even, 'r', linewidth=2)
axes[1].set_title('Even Part: $x_e(t) = [x(t) + x(-t)]/2$')
axes[1].set_xlabel('Time (s)')
axes[1].set_ylabel('Amplitude')
axes[1].grid(True)

axes[2].plot(t, x_odd, 'g', linewidth=2)
axes[2].set_title('Odd Part: $x_o(t) = [x(t) - x(-t)]/2$')
axes[2].set_xlabel('Time (s)')
axes[2].set_ylabel('Amplitude')
axes[2].grid(True)

axes[3].plot(t, x, 'b-', label='x(t)', linewidth=2)
axes[3].plot(t, x_even + x_odd, 'r--', label='$x_e(t) + x_o(t)$', linewidth=2)
axes[3].set_title('Verification: $x(t) = x_e(t) + x_o(t)$')
axes[3].set_xlabel('Time (s)')
axes[3].set_ylabel('Amplitude')
axes[3].legend()
axes[3].grid(True)

plt.tight_layout()
plt.show()
πŸ’‘ Key Concepts:
  • Time Shifting: x(t-tβ‚€) delays signal by tβ‚€
  • Time Scaling: x(at) compresses signal if a>1, expands if 0
  • Time Reversal: x(-t) flips signal about t=0

5️⃣ System Analysis

↑ Go Back

Convolution

Python Code
# Convolution: y(t) = x(t) * h(t)
from scipy.signal import convolve

# Define signals
t = np.linspace(-2, 5, 700)
dt = t[1] - t[0]
x = ((t >= 0) & (t <= 1)).astype(float)  # Rectangular pulse
h = np.exp(-2*t) * (t >= 0)              # Exponential

# Method 1: Using scipy.signal.convolve
y_conv = convolve(x, h, mode='same') * dt

# Plot results
fig, axes = plt.subplots(3, 1, figsize=(10, 10))

axes[0].plot(t, x, linewidth=2)
axes[0].set_title('Input Signal: x(t)')
axes[0].set_xlabel('Time (s)')
axes[0].set_ylabel('Amplitude')
axes[0].grid(True)

axes[1].plot(t, h, 'r', linewidth=2)
axes[1].set_title('Impulse Response: h(t)')
axes[1].set_xlabel('Time (s)')
axes[1].set_ylabel('Amplitude')
axes[1].grid(True)

axes[2].plot(t, y_conv, 'b', linewidth=2)
axes[2].set_title('Output: y(t) = x(t) * h(t)')
axes[2].set_xlabel('Time (s)')
axes[2].set_ylabel('Amplitude')
axes[2].grid(True)

plt.tight_layout()
plt.show()

Differential Equations - Zero-Input and Zero-State Response

Python Code
# Solving: dy/dt + ay = x(t)
a = 2  # System parameter
t = np.linspace(0, 5, 500)
dt = t[1] - t[0]

# Input signal
x = np.sin(2*np.pi*t)

# Initial condition
y0 = 1

# Zero-Input Response (x(t) = 0, y(0) = y0)
y_zi = y0 * np.exp(-a*t)

# Zero-State Response (y(0) = 0)
h = np.exp(-a*t)  # Impulse response
y_zs = convolve(x, h, mode='same') * dt

# Total Response
y_total = y_zi + y_zs

# Plot
fig, axes = plt.subplots(4, 1, figsize=(10, 12))

axes[0].plot(t, x, linewidth=2)
axes[0].set_title('Input: $x(t) = \\sin(2\\pi t)$')
axes[0].set_xlabel('Time (s)')
axes[0].set_ylabel('Amplitude')
axes[0].grid(True)

axes[1].plot(t, y_zi, 'r', linewidth=2)
axes[1].set_title('Zero-Input Response')
axes[1].set_xlabel('Time (s)')
axes[1].set_ylabel('Amplitude')
axes[1].grid(True)

axes[2].plot(t, y_zs, 'g', linewidth=2)
axes[2].set_title('Zero-State Response')
axes[2].set_xlabel('Time (s)')
axes[2].set_ylabel('Amplitude')
axes[2].grid(True)

axes[3].plot(t, y_total, 'b', linewidth=2)
axes[3].set_title('Total Response = ZIR + ZSR')
axes[3].set_xlabel('Time (s)')
axes[3].set_ylabel('Amplitude')
axes[3].grid(True)

plt.tight_layout()
plt.show()

Second-Order System Analysis

Python Code
# Second-order system using scipy.signal
from scipy.signal import lti, step

wn = 2*np.pi  # Natural frequency
zeta_values = [0.1, 0.5, 0.7, 1.0, 2.0]  # Damping ratios

t = np.linspace(0, 5, 1000)

plt.figure(figsize=(10, 6))
for zeta in zeta_values:
    # Transfer function: H(s) = wn^2 / (s^2 + 2*zeta*wn*s + wn^2)
    num = [wn**2]
    den = [1, 2*zeta*wn, wn**2]
    system = lti(num, den)
    t_step, y = step(system, T=t)
    plt.plot(t_step, y, linewidth=2, label=f'ΞΆ = {zeta}')

plt.title('Step Response of Second-Order System')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.show()
βœ… Key System Analysis Tools:
  • scipy.signal.convolve() - Convolution of two signals
  • scipy.signal.lti() - Create LTI system
  • scipy.signal.step() - Step response
  • scipy.signal.impulse() - Impulse response

6️⃣ Fourier Analysis

↑ Go Back

Fourier Series - Square Wave

Python Code
# Fourier Series: x(t) = a0 + Σ[an*cos(nω0t) + bn*sin(nω0t)]
T = 2            # Period
f0 = 1/T         # Fundamental frequency
w0 = 2*np.pi*f0  # Angular frequency
t = np.linspace(-2*T, 2*T, 2000)

N_values = [1, 3, 5, 10, 50]
fig, axes = plt.subplots(3, 2, figsize=(12, 10))
axes = axes.flatten()

for idx, N in enumerate(N_values):
    x = np.zeros_like(t)
    
    # Fourier series for square wave (odd harmonics only)
    for n in range(1, N+1, 2):
        bn = 4/(n*np.pi)
        x += bn * np.sin(n*w0*t)
    
    axes[idx].plot(t, x, linewidth=2)
    axes[idx].set_ylim([-1.5, 1.5])
    axes[idx].set_title(f'N = {N} harmonics')
    axes[idx].set_xlabel('Time (s)')
    axes[idx].set_ylabel('Amplitude')
    axes[idx].grid(True)

# Last subplot with N=100
N = 100
x = np.zeros_like(t)
for n in range(1, N+1, 2):
    bn = 4/(n*np.pi)
    x += bn * np.sin(n*w0*t)
axes[5].plot(t, x, linewidth=2)
axes[5].set_ylim([-1.5, 1.5])
axes[5].set_title(f'N = {N} harmonics')
axes[5].set_xlabel('Time (s)')
axes[5].set_ylabel('Amplitude')
axes[5].grid(True)

plt.tight_layout()
plt.show()
print('Notice Gibbs phenomenon at discontinuities!')

Fast Fourier Transform (FFT)

Python Code
# FFT Example: Multi-component signal analysis
from scipy.fft import fft, fftfreq

Fs = 1000        # Sampling frequency (Hz)
T = 1/Fs         # Sampling period
N = 1024         # Length of signal
t = np.arange(N) * T

# Create signal with multiple frequencies
f1, f2, f3 = 50, 120, 200  # Hz
x = 1.0*np.sin(2*np.pi*f1*t) + 0.5*np.sin(2*np.pi*f2*t) + 0.3*np.sin(2*np.pi*f3*t)

# Add noise
x = x + 0.2*np.random.randn(N)

# Compute FFT
X = fft(x)
freqs = fftfreq(N, T)

# Single-sided spectrum
X_mag = np.abs(X/N)
X_mag[1:N//2] = 2 * X_mag[1:N//2]  # Double for single-sided
freqs_pos = freqs[:N//2]
X_mag_pos = X_mag[:N//2]

# Plot results
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))

ax1.plot(t[:200]*1000, x[:200], linewidth=1.5)
ax1.set_title('Time Domain Signal (first 200 samples)')
ax1.set_xlabel('Time (ms)')
ax1.set_ylabel('Amplitude')
ax1.grid(True)

ax2.plot(freqs_pos, X_mag_pos, linewidth=2)
ax2.set_title('Single-Sided Magnitude Spectrum')
ax2.set_xlabel('Frequency (Hz)')
ax2.set_ylabel('|X(f)|')
ax2.set_xlim([0, Fs/2])
ax2.grid(True)
ax2.plot([f1, f2, f3], [1.0, 0.5, 0.3], 'ro', markersize=10, label='Expected peaks')
ax2.legend()

plt.tight_layout()
plt.show()

# Peak detection
from scipy.signal import find_peaks
peaks, _ = find_peaks(X_mag_pos, height=0.2)
detected_freqs = freqs_pos[peaks]
print('Detected frequencies:', detected_freqs)

Fourier Transform Properties

Python Code
# Demonstrate Time Shifting Property
from scipy.fft import fft, fftshift, fftfreq

Fs = 1000
t = np.linspace(-2, 2, 4*Fs)
dt = t[1] - t[0]

# Original signal
x = np.exp(-t**2)

# Time shifted signal
tau = 0.5
x_shifted = np.exp(-(t-tau)**2)

# Compute FFTs
X = fftshift(fft(x)) * dt
X_shifted = fftshift(fft(x_shifted)) * dt
freqs = fftshift(fftfreq(len(t), dt))

# Plot
fig, axes = plt.subplots(2, 2, figsize=(12, 8))

axes[0, 0].plot(t, x, 'b-', label='x(t)', linewidth=2)
axes[0, 0].plot(t, x_shifted, 'r--', label='x(t-Ο„)', linewidth=2)
axes[0, 0].set_title('Time Shifting')
axes[0, 0].set_xlabel('Time (s)')
axes[0, 0].legend()
axes[0, 0].grid(True)

axes[0, 1].plot(freqs, np.abs(X), 'b-', label='X(f)', linewidth=2)
axes[0, 1].plot(freqs, np.abs(X_shifted), 'r--', label='X(f)$e^{-j2Ο€fΟ„}$', linewidth=2)
axes[0, 1].set_title('Magnitude Spectrum')
axes[0, 1].set_xlabel('Frequency (Hz)')
axes[0, 1].set_xlim([-50, 50])
axes[0, 1].legend()
axes[0, 1].grid(True)

plt.tight_layout()
plt.show()
⚠️ Important FFT Considerations:
  • Sampling: Use Fs > 2*f_max (Nyquist criterion)
  • Length: Power of 2 for efficiency (1024, 2048, 4096)
  • Resolution: Frequency resolution = Fs/N
  • Aliasing: Frequencies above Fs/2 will fold back

7️⃣ Course-Specific Examples

↑ Go Back

Example 1: RC Circuit Frequency Response

Problem: Analyze an RC low-pass filter with R = 1kΞ© and C = 1Β΅F

Python Code
# RC Circuit: Low-pass filter
R = 1000      # Resistance (Ohms)
C = 1e-6      # Capacitance (Farads)
fc = 1/(2*np.pi*R*C)  # Cutoff frequency

print(f'Cutoff frequency fc = {fc:.2f} Hz')

# Frequency range
f = np.logspace(0, 5, 1000)  # 1 Hz to 100 kHz
w = 2*np.pi*f

# Frequency response
H = 1 / (1 + 1j*w*R*C)
mag = np.abs(H)
mag_dB = 20*np.log10(mag)
phase = np.angle(H) * 180/np.pi

# Plot Bode diagram
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))

ax1.semilogx(f, mag_dB, linewidth=2)
ax1.grid(True, which='both', alpha=0.3)
ax1.set_xlabel('Frequency (Hz)')
ax1.set_ylabel('Magnitude (dB)')
ax1.set_title('RC Low-Pass Filter Bode Plot')
ax1.axvline(fc, color='r', linestyle='--', linewidth=1.5, label='Cutoff Frequency')
ax1.plot(fc, -3, 'ro', markersize=10, label='-3dB point')
ax1.legend()

ax2.semilogx(f, phase, linewidth=2)
ax2.grid(True, which='both', alpha=0.3)
ax2.set_xlabel('Frequency (Hz)')
ax2.set_ylabel('Phase (degrees)')
ax2.set_title('Phase Response')
ax2.axvline(fc, color='r', linestyle='--', linewidth=1.5)
ax2.plot(fc, -45, 'ro', markersize=10)

plt.tight_layout()
plt.show()

# Time domain step response
t = np.linspace(0, 0.005, 1000)
v_in = np.ones_like(t)
tau = R*C
v_out = 1 - np.exp(-t/tau)

plt.figure(figsize=(10, 6))
plt.plot(t*1000, v_in, 'b--', linewidth=2, label='Input')
plt.plot(t*1000, v_out, 'r-', linewidth=2, label='Output')
plt.axvline(tau*1000, color='k', linestyle='--', linewidth=1, label='Time Constant Ο„')
plt.plot(tau*1000, 1-np.exp(-1), 'ko', markersize=10, label='63.2% point')
plt.xlabel('Time (ms)')
plt.ylabel('Voltage (V)')
plt.title('Step Response of RC Circuit')
plt.legend()
plt.grid(True)
plt.show()

Example 2: AM Modulation and Demodulation

Problem: Demonstrate amplitude modulation with fm = 1 kHz, fc = 10 kHz

Python Code
# Amplitude Modulation (AM)
fm = 1000    # Message frequency (1 kHz)
fc = 10000   # Carrier frequency (10 kHz)
fs = 100000  # Sampling frequency (100 kHz)
t = np.arange(0, 0.01, 1/fs)  # Time vector (10 ms)

# Message signal
m = np.cos(2*np.pi*fm*t)

# Carrier signal
c = np.cos(2*np.pi*fc*t)

# Modulation index
mu = 0.8

# AM signal
s_AM = (1 + mu*m) * c

# Frequency domain
N = len(s_AM)
f_axis = fftshift(fftfreq(N, 1/fs))
M = fftshift(fft(m))/N
C = fftshift(fft(c))/N
S = fftshift(fft(s_AM))/N

# Plot
fig = plt.figure(figsize=(12, 10))

# Time domain
ax1 = plt.subplot(3, 2, 1)
ax1.plot(t*1000, m, linewidth=1.5)
ax1.set_title('Message Signal m(t)')
ax1.set_xlabel('Time (ms)')
ax1.set_ylabel('Amplitude')
ax1.grid(True)
ax1.set_xlim([0, 5])

ax2 = plt.subplot(3, 2, 3)
ax2.plot(t*1000, c, 'r', linewidth=1)
ax2.set_title('Carrier Signal c(t)')
ax2.set_xlabel('Time (ms)')
ax2.set_ylabel('Amplitude')
ax2.grid(True)
ax2.set_xlim([0, 1])

ax3 = plt.subplot(3, 2, 5)
ax3.plot(t*1000, s_AM, 'g', linewidth=1)
envelope_upper = 1 + mu*m
envelope_lower = -(1 + mu*m)
ax3.plot(t*1000, envelope_upper, 'k--', linewidth=1.5)
ax3.plot(t*1000, envelope_lower, 'k--', linewidth=1.5)
ax3.set_title('AM Signal $s(t) = [1 + \\mu m(t)]c(t)$')
ax3.set_xlabel('Time (ms)')
ax3.set_ylabel('Amplitude')
ax3.grid(True)
ax3.set_xlim([0, 5])

# Frequency domain
ax4 = plt.subplot(3, 2, 2)
ax4.plot(f_axis/1000, np.abs(M), linewidth=2)
ax4.set_title('Message Spectrum M(f)')
ax4.set_xlabel('Frequency (kHz)')
ax4.set_ylabel('|M(f)|')
ax4.grid(True)
ax4.set_xlim([-5, 5])

ax5 = plt.subplot(3, 2, 4)
ax5.plot(f_axis/1000, np.abs(C), 'r', linewidth=2)
ax5.set_title('Carrier Spectrum C(f)')
ax5.set_xlabel('Frequency (kHz)')
ax5.set_ylabel('|C(f)|')
ax5.grid(True)
ax5.set_xlim([-15, 15])

ax6 = plt.subplot(3, 2, 6)
ax6.plot(f_axis/1000, np.abs(S), 'g', linewidth=2)
ax6.set_title('AM Signal Spectrum S(f)')
ax6.set_xlabel('Frequency (kHz)')
ax6.set_ylabel('|S(f)|')
ax6.grid(True)
ax6.set_xlim([-15, 15])

plt.tight_layout()
plt.show()

print(f'Modulation Index ΞΌ = {mu:.2f}')
print(f'Bandwidth = 2fm = {2*fm:.1f} Hz')

Example 3: First-Order Differential Equation

Problem: Solve dy/dt + 3y = 5u(t) with y(0) = 2

Python Code
# First-order system: dy/dt + ay = bx(t)
# Given: dy/dt + 3y = 5u(t), y(0) = 2

a = 3
b = 5
y0 = 2
t = np.linspace(0, 3, 300)

# Analytical Solution
y_zi = y0 * np.exp(-a*t)           # Zero-input response
y_zs = (b/a) * (1 - np.exp(-a*t))  # Zero-state response
y_total = y_zi + y_zs

# System parameters
y_ss = b/a   # Steady-state
tau = 1/a    # Time constant

# Plot
fig, axes = plt.subplots(3, 1, figsize=(10, 10))

axes[0].plot(t, y_zi, linewidth=2)
axes[0].set_title('Zero-Input Response (Natural Response)')
axes[0].set_xlabel('Time (s)')
axes[0].set_ylabel('$y_{zi}(t)$')
axes[0].axhline(0, color='k', linestyle='--')
axes[0].grid(True)

axes[1].plot(t, y_zs, 'r', linewidth=2)
axes[1].axhline(y_ss, color='k', linestyle='--', linewidth=1)
axes[1].text(1.5, y_ss*1.1, f'Steady State = {y_ss:.2f}')
axes[1].set_title('Zero-State Response (Forced Response)')
axes[1].set_xlabel('Time (s)')
axes[1].set_ylabel('$y_{zs}(t)$')
axes[1].grid(True)

axes[2].plot(t, y_total, 'g', linewidth=2, label='Total Response')
axes[2].axhline(y_ss, color='k', linestyle='--', linewidth=1, label='Steady State')
axes[2].axvline(tau, color='r', linestyle='--', linewidth=1)
y_at_tau = y0*np.exp(-1) + y_ss*(1-np.exp(-1))
axes[2].plot(tau, y_at_tau, 'ro', markersize=8, label='Time Constant Ο„')
axes[2].set_title('Total Response $y(t) = y_{zi}(t) + y_{zs}(t)$')
axes[2].set_xlabel('Time (s)')
axes[2].set_ylabel('y(t)')
axes[2].legend()
axes[2].grid(True)

plt.tight_layout()
plt.show()

print(f'Time Constant Ο„ = {tau:.3f} s')
print(f'Steady-State y(∞) = {y_ss:.3f}')
print(f'95% settling time β‰ˆ 3Ο„ = {3*tau:.3f} s')

Example 4: Sampling and Aliasing

Problem: Demonstrate Nyquist sampling theorem and aliasing effects

Python Code
# Sampling and Aliasing Demonstration
f_signal = 5  # Signal frequency (Hz)
t_continuous = np.linspace(0, 2, 20000)
x_continuous = np.sin(2*np.pi*f_signal*t_continuous)

# Different sampling frequencies
fs1 = 50   # Well above Nyquist
fs2 = 12   # Just above Nyquist
fs3 = 8    # Below Nyquist - aliasing!

t1 = np.arange(0, 2, 1/fs1)
x1 = np.sin(2*np.pi*f_signal*t1)

t2 = np.arange(0, 2, 1/fs2)
x2 = np.sin(2*np.pi*f_signal*t2)

t3 = np.arange(0, 2, 1/fs3)
x3 = np.sin(2*np.pi*f_signal*t3)

# Plot
fig, axes = plt.subplots(3, 1, figsize=(12, 10))

axes[0].plot(t_continuous, x_continuous, 'b-', linewidth=1, label='Original')
axes[0].stem(t1, x1, linefmt='r-', markerfmt='ro', basefmt='r-', label='Sampled')
axes[0].set_title(f'Sampling at fs = {fs1} Hz (Good!)')
axes[0].set_xlabel('Time (s)')
axes[0].set_ylabel('Amplitude')
axes[0].legend()
axes[0].grid(True)
axes[0].set_xlim([0, 1])

axes[1].plot(t_continuous, x_continuous, 'b-', linewidth=1, label='Original')
axes[1].stem(t2, x2, linefmt='g-', markerfmt='go', basefmt='g-', label='Sampled')
axes[1].set_title(f'Sampling at fs = {fs2} Hz (Marginal)')
axes[1].set_xlabel('Time (s)')
axes[1].set_ylabel('Amplitude')
axes[1].legend()
axes[1].grid(True)
axes[1].set_xlim([0, 1])

axes[2].plot(t_continuous, x_continuous, 'b-', linewidth=1, label='Original 5Hz')
axes[2].stem(t3, x3, linefmt='m-', markerfmt='mo', basefmt='m-', label='Sampled')
x3_interp = np.interp(t_continuous, t3, x3)
axes[2].plot(t_continuous, x3_interp, 'r--', linewidth=2, label='Reconstructed')
axes[2].set_title(f'Sampling at fs = {fs3} Hz (ALIASING!)')
axes[2].set_xlabel('Time (s)')
axes[2].set_ylabel('Amplitude')
axes[2].legend()
axes[2].grid(True)
axes[2].set_xlim([0, 1])

plt.tight_layout()
plt.show()

print('Nyquist Theorem: fs > 2*f_max')
print('For f_max = 5 Hz: fs > 10 Hz required')

8️⃣ Tips & Best Practices

↑ Go Back

Code Organization and Style

πŸ“ Good Commenting

Write clear comments with proper Python style

Good Example
# Calculate time constant for 95% settling
tau = 1/a
t_settle = 3*tau

🏷️ Variable Naming

Use descriptive names (snake_case in Python)

Good Practice
# Good
sampling_frequency = 1000
signal_amplitude = 2.5

# OK for standard notation
fs = 1000
fc = 50

πŸ“¦ Script Header

Start scripts with proper imports

Script Header
"""
ELEC 360 - Assignment 1
Student: Your Name (ID)
Date: October 2025
"""
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal

πŸ“ File Organization

  • One .py or .ipynb per assignment
  • Use functions for reusability
  • Save figures with descriptive names
  • Keep data files organized

Debugging Strategies

Debugging Techniques
# 1. Print intermediate values
print('Checking variable x:')
print(x)
print(f'Value: {x:.4f}')

# 2. Check array dimensions
print(f'Shape of x: {x.shape}')
print(f'Type: {type(x)}')

# 3. Visualize data quickly
plt.plot(x)
plt.title('Quick check')
plt.show()

# 4. Validate results
expected = 5
actual = np.sum(x)
assert abs(actual - expected) < 0.01, 'Sum mismatch!'

# 5. Check for NaN and Inf
if np.any(np.isnan(x)):
    print('Warning: Found NaN values')

# 6. Use try-except
try:
    result = risky_operation()
except Exception as e:
    print(f'Error: {e}')

Performance Optimization

Efficient Python Code
import time

# BAD: Growing lists (slow!)
start = time.time()
y = []
for i in range(10000):
    y.append(np.sin(i))
time_bad = time.time() - start

# GOOD: Preallocate arrays
start = time.time()
y = np.zeros(10000)
for i in range(10000):
    y[i] = np.sin(i)
time_good = time.time() - start

# BEST: Vectorization
start = time.time()
i = np.arange(10000)
y = np.sin(i)
time_best = time.time() - start

print(f'Without preallocation: {time_bad:.4f} s')
print(f'With preallocation: {time_good:.4f} s')
print(f'Vectorized: {time_best:.4f} s ({time_bad/time_best:.1f}x faster!)')

Common Mistakes and Solutions

Error Cause Solution
ValueError: shapes not aligned Size mismatch Check with .shape and use .T for transpose
NameError: name not defined Typo or not defined Check spelling, define before use
IndexError: index out of bounds Accessing non-existent element Check with len()
TypeError: unsupported operand type(s) Wrong data type Convert types or check array vs. scalar
MemoryError Arrays too large Process in chunks, use data types wisely

Professional Plotting

Well-Formatted Plots
t = np.linspace(0, 10, 1000)
x1 = np.exp(-0.5*t) * np.sin(2*np.pi*t)
x2 = np.exp(-0.5*t) * np.cos(2*np.pi*t)

plt.figure(figsize=(10, 6))
plt.plot(t, x1, 'b-', linewidth=2, label='$x_1(t)$')
plt.plot(t, x2, 'r--', linewidth=2, label='$x_2(t)$')

plt.xlabel('Time (seconds)', fontsize=12, fontweight='bold')
plt.ylabel('Amplitude', fontsize=12, fontweight='bold')
plt.title('Damped Oscillations', fontsize=14, fontweight='bold')

plt.legend(loc='best', fontsize=11)
plt.grid(True, alpha=0.3)
plt.xlim([0, 10])
plt.ylim([-1, 1])

plt.tight_layout()
plt.savefig('damped_oscillations.png', dpi=300)
plt.show()

Keyboard Shortcuts (Jupyter Notebook)

Shortcut Action
Shift + Enter Run cell and move to next
Ctrl + Enter Run cell
Alt + Enter Run cell and insert below
A (Command mode) Insert cell above
B (Command mode) Insert cell below
DD (Command mode) Delete cell
M (Command mode) Change to Markdown
Y (Command mode) Change to Code
Tab Auto-complete

Useful Functions Quick Reference

Signal Generation

  • np.sin(), np.cos(), np.exp()
  • signal.square(), signal.sawtooth()
  • np.random.randn(), np.random.rand()
  • np.linspace(), np.logspace()

Signal Analysis

  • fft(), ifft()
  • signal.convolve(), signal.correlate()
  • np.abs(), np.angle()
  • signal.find_peaks()

System Analysis

  • signal.lti(), signal.TransferFunction()
  • signal.step(), signal.impulse()
  • signal.bode(), signal.freqz()
  • signal.zpk2tf(), signal.tf2zpk()

Filter Design

  • signal.butter(), signal.cheby1()
  • signal.lfilter(), signal.filtfilt()
  • signal.freqz(), signal.group_delay()
  • signal.firwin(), signal.firwin2()

Getting Help

Finding Documentation
# In Python/Jupyter:
help(function_name)        # Quick help
?function_name             # Jupyter notebook
??function_name            # Full source code (Jupyter)

# Examples:
help(np.fft.fft)
?signal.convolve

# Online documentation
# NumPy: https://numpy.org/doc/
# SciPy: https://docs.scipy.org/doc/scipy/
# Matplotlib: https://matplotlib.org/

βœ… Assignment Submission Checklist

  • βœ“ Header with name, ID, assignment number
  • βœ“ All imports at the top
  • βœ“ Code runs without errors from start to finish
  • βœ“ All variables properly defined
  • βœ“ Comments explain complex operations
  • βœ“ Figures have titles, labels, and legends
  • βœ“ Results displayed with print()
  • βœ“ Code follows PEP 8 style guide
  • βœ“ Debug code removed
  • βœ“ Tested in fresh kernel/environment

πŸ“š Additional Resources

πŸ’‘ Pro Tips

  • Start simple: Test with simple inputs first
  • Plot often: Visualize at every step
  • Save incrementally: Use version control (Git)
  • Learn by doing: Type code yourself
  • Read errors: Python errors are very informative
  • Use Jupyter: Great for learning and exploration
  • Vectorize: NumPy is much faster than loops
  • Check Stack Overflow: Most errors have been solved before

Need Help?

Contact Dr. Al Bataineh during office hours (Monday/Wednesday 11:00 AM - 12:00 PM)

Email: mffbataineh@uaeu.ac.ae | Office: F1-1175

← Back to ELEC 360 Course Page