import numpy as np
import matplotlib.pyplot as plt
try:
import executable_engineering as exe
except ImportError:
%pip install -q executable_engineering
import executable_engineering as exeCommon Matrix Types¶
Before discussing advanced structural properties, here is a formal classification of fundamental matrix types encountered in numerical methods:
| Matrix Type | Property | Example |
|---|---|---|
| Zero Matrix | for all | |
| Identity Matrix | , () | |
| Diagonal | for | |
| Upper Triangular | for | |
| Lower Triangular | for | |
| Symmetric | ||
| Positive Definite | for all | |
| Symmetric Positive Definite (SPD) | and | |
| Orthogonal |
What is Sparsity?¶
Sparsity refers to the fraction of non-zero elements in a matrix.
Sparse matrices have a significant fraction of exactly 0 elements. In real-world engineering problems (like discretizing differential equations or finite element analysis), matrices are often incredibly sparse.
Exploiting sparsity offers massive benefits:
Storage: Instead of storing a massive 2D array full of zeros, we can store only the non-zero entries (and their locations), saving gigabytes of memory.
Computation: Algorithms can be designed to ignore operations involving zeros, dramatically reducing execution time and computational complexity.
Sparsity patterns and matrix¶
Sparsity patterns are condensly viewed as monochrome matricies filled in according to 0 or non-zero.
Example: A sparsity pattern from a finite element problem in 2D.

Banded Matrices¶
Banded matrices only have non-zero elements running parallel to the main diagonal. The bandwidth describes how many parallel diagonal bands exist.
We have already seen how useful diagonal and triangular matrices are for controlling computational complexity (e.g. back-substitution).
Tridiagonal (Bandwidth 3)¶
A classic matrix structure in 1D physics problems (heat conduction, fluid flow) where every node only interacts with its immediate left and right neighbors.
n = 10
A = np.zeros((n, n))
np.fill_diagonal(A, 2) # Main diagonal
np.fill_diagonal(A[1:], -1) # Upper diagonal
np.fill_diagonal(A[:, 1:], -1) # Lower diagonal
print("Tridiagonal Matrix:\n", A)
plt.spy(A)
plt.title("Sparsity Pattern (Tridiagonal)")
plt.show()Pentadiagonal (Bandwidth 5)¶
Commonly arises in 2D grid problems or higher-order finite difference methods.
n = 10
A = np.zeros((n, n))
np.fill_diagonal(A, 2) # Main diagonal
np.fill_diagonal(A[1:], -1) # Upper diagonal
np.fill_diagonal(A[:, 1:], -1) # Lower diagonal
np.fill_diagonal(A[4:], -2) # Far upper diagonal
np.fill_diagonal(A[:, 4:], -1) # Far lower diagonal
plt.spy(A)
plt.title("Sparsity Pattern (Pentadiagonal)")
plt.show()Block Matrices¶
Sometimes, a large sparse matrix can be logically partitioned into smaller sub-matrices, called blocks.
This situation arises constantly in multi-physics applications (e.g., modeling Temperature and Pressure fields simultaneously).
Instead of solving the massive matrix as a whole (monolithic solution), we can solve the blocks individually (operator splitting or segregated solving).