Skip to content

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 .exe installer
  • macOS: Download the .pkg installer 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:

bash
# Make the script executable and run it
chmod +x Miniconda3-latest-Linux-x86_64.sh
./Miniconda3-latest-Linux-x86_64.sh

Follow the prompts, accept the license, and allow the installer to initialize Conda.

3. Verify Installation

Open a new terminal and run:

bash
conda --version

You should see something like conda 23.x.x.

Creating an Environment

It's good practice to create a dedicated environment for this course:

bash
# Create a new environment named 'signal' with Python 3.11
conda create -n signal python=3.11

# Activate the environment
conda activate signal

Your terminal prompt should now show (signal) indicating the active environment.

Installing Required Packages

Install the core scientific libraries:

bash
conda install numpy scipy matplotlib jupyterlab

This 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:

bash
jupyter lab

This opens a browser window with the JupyterLab interface. You can create new notebooks, organize files, and run code interactively.

Basic Usage

Creating a notebook:

  1. Click the "+" button or File → New → Notebook
  2. Select "Python 3" as the kernel

Running code:

  • Type code in a cell
  • Press Shift+Enter to execute and move to the next cell
  • Press Ctrl+Enter to execute and stay in the current cell

Cell types:

  • Code cells - Execute Python code
  • Markdown cells - Write formatted text and equations

Example Notebook Cell

python
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:

python
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:

python
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:

python
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:

python
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 processing
  • scipy.fft - Fast Fourier Transform
  • scipy.integrate - Numerical integration
  • scipy.linalg - Advanced linear algebra
  • scipy.interpolate - Interpolation

Signal Processing with scipy.signal

python
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

python
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

python
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

python
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

python
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

python
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

python
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

bash
# Install Miniconda, then:
conda create -n signal python=3.11
conda activate signal
conda install numpy scipy matplotlib jupyterlab
jupyter lab

Common Imports

python
import numpy as np
from scipy import signal, fft, integrate
import matplotlib.pyplot as plt

Useful NumPy Functions

FunctionDescription
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

FunctionDescription
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