Numerical Tools
This chapter covers the software environment used throughout the course. We use Python with scientific computing libraries to implement and visualize signal processing concepts.
Why Python?
Python has become the de facto language for scientific computing and data analysis due to:
- Readability - Clean syntax that resembles mathematical notation
- Rich ecosystem - Extensive libraries for numerical computing, visualization, and signal processing
- Interactive computing - Jupyter notebooks allow mixing code, equations, and plots
- Free and open-source - No licensing costs, large community support
Installing Python with Conda
What is Conda?
Conda is a package and environment manager that simplifies installing Python and scientific libraries. It handles dependencies automatically and allows you to create isolated environments for different projects.
We recommend Miniconda, a minimal installer that includes only Conda and Python. You can then install only the packages you need.
Installation Steps
1. Download Miniconda
Visit the official Miniconda page and download the installer for your operating system:
- Windows: Download the
.exeinstaller - macOS: Download the
.pkginstaller or use the bash script - Linux: Download the bash script
Official download page: https://docs.conda.io/en/latest/miniconda.html
2. Run the Installer
Windows:
Double-click the downloaded .exe file and follow the prompts.macOS/Linux:
# Make the script executable and run it
chmod +x Miniconda3-latest-Linux-x86_64.sh
./Miniconda3-latest-Linux-x86_64.shFollow the prompts, accept the license, and allow the installer to initialize Conda.
3. Verify Installation
Open a new terminal and run:
conda --versionYou should see something like conda 23.x.x.
Creating an Environment
It's good practice to create a dedicated environment for this course:
# Create a new environment named 'signal' with Python 3.11
conda create -n signal python=3.11
# Activate the environment
conda activate signalYour terminal prompt should now show (signal) indicating the active environment.
Installing Required Packages
Install the core scientific libraries:
conda install numpy scipy matplotlib jupyterlabThis installs:
- NumPy - Numerical arrays and operations
- SciPy - Scientific computing (signal processing, integration, etc.)
- Matplotlib - Plotting and visualization
- JupyterLab - Interactive notebook environment
Jupyter Notebooks
What is Jupyter?
Jupyter (formerly IPython Notebook) provides an interactive computing environment where you can combine:
- Executable code
- Rich text (Markdown)
- Mathematical equations (LaTeX)
- Visualizations
This makes it ideal for learning and experimenting with signal processing concepts.
Starting JupyterLab
With your environment activated, launch JupyterLab:
jupyter labThis opens a browser window with the JupyterLab interface. You can create new notebooks, organize files, and run code interactively.
Basic Usage
Creating a notebook:
- Click the "+" button or File → New → Notebook
- Select "Python 3" as the kernel
Running code:
- Type code in a cell
- Press
Shift+Enterto execute and move to the next cell - Press
Ctrl+Enterto execute and stay in the current cell
Cell types:
- Code cells - Execute Python code
- Markdown cells - Write formatted text and equations
Example Notebook Cell
import numpy as np
import matplotlib.pyplot as plt
# Generate a sinusoidal signal
t = np.linspace(0, 1, 1000)
f = 5 # frequency in Hz
x = np.sin(2 * np.pi * f * t)
# Plot
plt.figure(figsize=(10, 4))
plt.plot(t, x)
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.title('Sinusoidal Signal')
plt.grid(True)
plt.show()NumPy
Overview
NumPy (Numerical Python) is the foundation of scientific computing in Python. It provides:
- N-dimensional arrays (
ndarray) - Mathematical functions operating on arrays
- Linear algebra operations
- Random number generation
Arrays
The core object is the ndarray, an efficient multi-dimensional array:
import numpy as np
# Create arrays
a = np.array([1, 2, 3, 4, 5]) # From a list
b = np.zeros(10) # Array of zeros
c = np.ones((3, 4)) # 3x4 matrix of ones
d = np.linspace(0, 1, 100) # 100 points from 0 to 1
e = np.arange(0, 10, 0.1) # From 0 to 10, step 0.1
print(f"Array a: {a}")
print(f"Shape of c: {c.shape}")
print(f"Length of d: {len(d)}")Array Operations
Operations are element-wise by default:
import numpy as np
a = np.array([1, 2, 3, 4])
b = np.array([10, 20, 30, 40])
# Element-wise operations
print(f"a + b = {a + b}")
print(f"a * b = {a * b}")
print(f"a ** 2 = {a ** 2}")
print(f"np.sqrt(a) = {np.sqrt(a)}")
# Aggregations
print(f"Sum: {np.sum(a)}")
print(f"Mean: {np.mean(a)}")
print(f"Max: {np.max(a)}")Mathematical Functions
NumPy provides vectorized mathematical functions:
import numpy as np
t = np.linspace(0, 2*np.pi, 100)
# Trigonometric functions
sin_t = np.sin(t)
cos_t = np.cos(t)
# Exponential and logarithm
exp_t = np.exp(-t)
log_t = np.log(t + 1)
# Complex exponential (fundamental in signal processing)
omega = 2 * np.pi
z = np.exp(1j * omega * t) # e^(jωt)
print(f"z[0] = {z[0]}")
print(f"|z| = {np.abs(z[0])}")
print(f"angle(z) = {np.angle(z[0])}")Linear Algebra
NumPy includes linear algebra operations:
import numpy as np
A = np.array([[1, 2], [3, 4]])
b = np.array([5, 6])
# Matrix operations
print(f"A @ b = {A @ b}") # Matrix-vector product
print(f"A.T =\n{A.T}") # Transpose
print(f"det(A) = {np.linalg.det(A)}") # Determinant
# Solve Ax = b
x = np.linalg.solve(A, b)
print(f"Solution x = {x}")
# Eigenvalues
eigenvalues, eigenvectors = np.linalg.eig(A)
print(f"Eigenvalues: {eigenvalues}")SciPy
Overview
SciPy (Scientific Python) builds on NumPy and provides additional functionality for scientific computing:
scipy.signal- Signal processingscipy.fft- Fast Fourier Transformscipy.integrate- Numerical integrationscipy.linalg- Advanced linear algebrascipy.interpolate- Interpolation
Signal Processing with scipy.signal
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt
# Generate a noisy signal
t = np.linspace(0, 1, 1000)
x = np.sin(2 * np.pi * 5 * t) + 0.5 * np.random.randn(len(t))
# Design a low-pass Butterworth filter
order = 4
cutoff = 10 # Hz
fs = 1000 # Sampling frequency
b, a = signal.butter(order, cutoff, btype='low', fs=fs)
# Apply the filter
x_filtered = signal.filtfilt(b, a, x)
# Plot
plt.figure(figsize=(10, 4))
plt.plot(t, x, 'b-', alpha=0.5, label='Noisy signal')
plt.plot(t, x_filtered, 'r-', linewidth=2, label='Filtered signal')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid(True)
plt.show()Fourier Transform with scipy.fft
import numpy as np
from scipy import fft
import matplotlib.pyplot as plt
# Create a signal with two frequencies
fs = 1000 # Sampling frequency
t = np.linspace(0, 1, fs)
x = np.sin(2 * np.pi * 50 * t) + 0.5 * np.sin(2 * np.pi * 120 * t)
# Compute FFT
X = fft.fft(x)
freqs = fft.fftfreq(len(x), 1/fs)
# Plot magnitude spectrum (positive frequencies only)
plt.figure(figsize=(10, 4))
plt.plot(freqs[:len(freqs)//2], np.abs(X[:len(X)//2]) * 2/len(x))
plt.xlabel('Frequency (Hz)')
plt.ylabel('Magnitude')
plt.title('Frequency Spectrum')
plt.grid(True)
plt.xlim([0, 200])
plt.show()Numerical Integration with scipy.integrate
import numpy as np
from scipy import integrate
# Definite integral: ∫₀^∞ e^(-x²) dx = √π / 2
result, error = integrate.quad(lambda x: np.exp(-x**2), 0, np.inf)
print(f"∫₀^∞ e^(-x²) dx = {result:.6f}")
print(f"Analytical: √π/2 = {np.sqrt(np.pi)/2:.6f}")
# Signal energy: E = ∫ |x(t)|² dt
def signal(t):
return np.exp(-t) * np.sin(2 * np.pi * t)
energy, _ = integrate.quad(lambda t: signal(t)**2, 0, 10)
print(f"\nSignal energy: {energy:.6f}")Matplotlib
Overview
Matplotlib is the primary plotting library in Python. It provides:
- Line plots, scatter plots, bar charts
- Customizable figure aesthetics
- Multiple subplots
- Export to various formats (PNG, PDF, SVG)
Basic Plotting
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(0, 2, 200)
x = np.sin(2 * np.pi * t)
y = np.cos(2 * np.pi * t)
plt.figure(figsize=(10, 4))
plt.plot(t, x, 'b-', linewidth=2, label='sin(2πt)')
plt.plot(t, y, 'r--', linewidth=2, label='cos(2πt)')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.title('Sinusoidal Signals')
plt.legend()
plt.grid(True)
plt.show()Subplots
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(0, 1, 500)
x = np.sin(2 * np.pi * 5 * t)
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
# Top-left: time domain
axes[0, 0].plot(t, x)
axes[0, 0].set_title('Time Domain')
axes[0, 0].set_xlabel('Time (s)')
axes[0, 0].grid(True)
# Top-right: squared signal
axes[0, 1].plot(t, x**2)
axes[0, 1].set_title('Squared Signal')
axes[0, 1].set_xlabel('Time (s)')
axes[0, 1].grid(True)
# Bottom-left: histogram
axes[1, 0].hist(x, bins=30)
axes[1, 0].set_title('Histogram')
axes[1, 0].set_xlabel('Value')
axes[1, 0].grid(True)
# Bottom-right: cumulative sum
axes[1, 1].plot(t, np.cumsum(x) / len(x))
axes[1, 1].set_title('Cumulative Mean')
axes[1, 1].set_xlabel('Time (s)')
axes[1, 1].grid(True)
plt.tight_layout()
plt.show()Customization
import numpy as np
import matplotlib.pyplot as plt
# Set style
plt.style.use('seaborn-v0_8-whitegrid')
t = np.linspace(0, 1, 100)
fig, ax = plt.subplots(figsize=(8, 5))
# Plot with custom styling
ax.plot(t, np.sin(2*np.pi*t), color='#2ecc71', linewidth=2.5,
linestyle='-', marker='', label='Sine')
ax.plot(t, np.cos(2*np.pi*t), color='#e74c3c', linewidth=2.5,
linestyle='--', label='Cosine')
# Customize axes
ax.set_xlim([0, 1])
ax.set_ylim([-1.5, 1.5])
ax.set_xlabel('Time (s)', fontsize=12)
ax.set_ylabel('Amplitude', fontsize=12)
ax.set_title('Customized Plot', fontsize=14, fontweight='bold')
# Legend
ax.legend(loc='upper right', fontsize=10)
# Save figure
plt.savefig('my_figure.png', dpi=150, bbox_inches='tight')
plt.show()Specialized Plots for Signal Processing
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
# Create a chirp signal (frequency sweep)
fs = 1000
t = np.linspace(0, 2, 2*fs)
x = signal.chirp(t, f0=1, f1=100, t1=2, method='linear')
fig, axes = plt.subplots(2, 1, figsize=(10, 6))
# Time domain
axes[0].plot(t, x)
axes[0].set_xlabel('Time (s)')
axes[0].set_ylabel('Amplitude')
axes[0].set_title('Chirp Signal (Time Domain)')
# Spectrogram
f, t_spec, Sxx = signal.spectrogram(x, fs, nperseg=128)
axes[1].pcolormesh(t_spec, f, 10*np.log10(Sxx), shading='gouraud')
axes[1].set_xlabel('Time (s)')
axes[1].set_ylabel('Frequency (Hz)')
axes[1].set_title('Spectrogram')
axes[1].set_ylim([0, 150])
plt.tight_layout()
plt.show()Quick Reference
Environment Setup
# Install Miniconda, then:
conda create -n signal python=3.11
conda activate signal
conda install numpy scipy matplotlib jupyterlab
jupyter labCommon Imports
import numpy as np
from scipy import signal, fft, integrate
import matplotlib.pyplot as pltUseful NumPy Functions
| Function | Description |
|---|---|
np.array() | Create array from list |
np.zeros(n) | Array of zeros |
np.ones(n) | Array of ones |
np.linspace(a, b, n) | n points from a to b |
np.arange(a, b, step) | Points from a to b with step |
np.sin(), np.cos() | Trigonometric functions |
np.exp() | Exponential |
np.abs() | Absolute value / magnitude |
np.angle() | Phase angle |
np.fft.fft() | Fast Fourier Transform |
np.convolve() | Convolution |
Useful SciPy Functions
| Function | Description |
|---|---|
signal.butter() | Butterworth filter design |
signal.filtfilt() | Zero-phase filtering |
signal.freqz() | Frequency response |
signal.spectrogram() | Time-frequency analysis |
fft.fft() | FFT |
fft.ifft() | Inverse FFT |
fft.fftfreq() | FFT frequency bins |
integrate.quad() | Numerical integration |