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