Toeplitz and Circulant¶
Toeplitz and circulant matrices appear throughout signal processing, time series analysis, and numerical methods for differential equations. A Toeplitz matrix has constant values along each diagonal, which means the entire matrix is determined by its first row and first column. A circulant matrix is a special case where each row is a cyclic shift of the previous one, enabling fast \(O(n \log n)\) matrix-vector multiplication via the FFT. SciPy provides dedicated constructors for both.
import numpy as np
from scipy import linalg
Toeplitz Matrix¶
A Toeplitz matrix \(T\) has the property that each descending diagonal from left to right is constant. Formally, \(T_{ij} = t_{i-j}\) for some sequence \(\{t_k\}\). The general form is
The matrix is fully determined by \(2n - 1\) values (the first column and first row) rather than \(n^2\).
scipy.linalg.toeplitz¶
linalg.toeplitz(c, r) constructs a Toeplitz matrix from its first column c and first row r. The first element of c and r must agree (both define the top-left corner).
def main():
c = [1, 2, 3, 4] # First column
r = [1, 5, 6, 7] # First row (first element matches c[0])
T = linalg.toeplitz(c, r)
print(T)
if __name__ == "__main__":
main()
[[1 5 6 7]
[2 1 5 6]
[3 2 1 5]
[4 3 2 1]]
Each diagonal running from top-left to bottom-right contains a constant value: the main diagonal is all 1's, the first sub-diagonal is all 2's, and so on.
Symmetric Toeplitz¶
If only the first column is provided (no r), the result is a symmetric Toeplitz matrix where the first row equals the first column.
T_sym = linalg.toeplitz([1, 2, 3, 4])
print(T_sym)
[[1 2 3 4]
[2 1 2 3]
[3 2 1 2]
[4 3 2 1]]
Autocovariance Matrices
The autocovariance matrix of a stationary time series is a symmetric Toeplitz matrix, since \(\text{Cov}(X_t, X_s) = \gamma(|t - s|)\) depends only on the time lag.
Circulant Matrix¶
A circulant matrix is a Toeplitz matrix where each row is a cyclic (circular) shift of the row above it. Given a vector \(c = (c_0, c_1, \ldots, c_{n-1})\), the circulant matrix is
scipy.linalg.circulant¶
def main():
c = [1, 2, 3, 4]
C = linalg.circulant(c)
print(C)
if __name__ == "__main__":
main()
[[1 4 3 2]
[2 1 4 3]
[3 2 1 4]
[4 3 2 1]]
Each row is the previous row shifted one position to the right (with wraparound).
FFT Diagonalization of Circulant Matrices¶
The key computational property of circulant matrices is that they are diagonalized by the discrete Fourier transform (DFT) matrix \(F\):
where \(\Lambda = \mathrm{diag}(\hat{c})\) and \(\hat{c} = \mathrm{FFT}(c)\) is the DFT of the first column. This means matrix-vector multiplication \(Cx\) can be computed as
where \(\odot\) denotes element-wise multiplication. Since the FFT runs in \(O(n \log n)\), this is much faster than the standard \(O(n^2)\) matrix-vector multiply.
c = np.array([1, 2, 3, 4], dtype=float)
x = np.array([1, 0, 0, 1], dtype=float)
# Direct multiplication
C = linalg.circulant(c)
y_direct = C @ x
# FFT-based multiplication
c_hat = np.fft.fft(c)
x_hat = np.fft.fft(x)
y_fft = np.fft.ifft(c_hat * x_hat).real
print(f"Direct: {y_direct}") # [3. 6. 6. 8.]
print(f"FFT: {y_fft}") # [3. 6. 6. 8.]
When to Use FFT Multiplication
For small matrices, direct multiplication is faster due to FFT overhead. The FFT approach becomes worthwhile for \(n \gtrsim 64\), and the speedup grows with matrix size — at \(n = 10{,}000\), the FFT is roughly 1000 times faster.
Summary¶
| Function | Purpose | Key Property |
|---|---|---|
scipy.linalg.toeplitz(c, r) |
Construct Toeplitz matrix | Constant diagonals; \(2n-1\) parameters |
scipy.linalg.circulant(c) |
Construct circulant matrix | Cyclic row shifts; diagonalized by FFT |
Key Takeaways:
- A Toeplitz matrix is defined by its first column and first row (constant diagonals)
- A circulant matrix is a special Toeplitz matrix where rows are cyclic shifts of one another
- Circulant matrices are diagonalized by the DFT matrix, enabling \(O(n \log n)\) multiplication via FFT
- Symmetric Toeplitz matrices arise naturally as autocovariance matrices of stationary time series
- Use
linalg.toeplitz(c)(one argument) for symmetric Toeplitz,linalg.toeplitz(c, r)for general