Seven numbers, one per diagonal, are enough for a 4 × 4 matrix. Watch each entry read from its diagonal’s number, then a filter built the same way.
A whole matrix from one row and column
Seven numbers fill a 4 × 4 matrix; a filter fills another
static const int t[2 * N - 1] = { 7, 6, 5, 1, 2, 3, 4 };static const int h[TAPS] = { 1, 2, 1 }; int get(int i, int j){ return t[N - 1 + j - i];} int conv_entry(int i, int j){ int k = i - j; return (k >= 0 && k < TAPS) ? h[k] : 0;} int main(void){ for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) printf("%d ", get(i, j)); printf("\n"); } const int x[N] = { 1, 2, 3, 4 }; printf("Mx ="); for (int i = 0; i < N; i++) { int s = 0; for (int j = 0; j < N; j++) s += get(i, j) * x[j]; printf(" %d", s); } const int u[LEN] = { 1, 2, 3, 4, 5 }; printf("\nfiltered ="); for (int i = 0; i < LEN + TAPS - 1; i++) { int s = 0; for (int j = 0; j < LEN; j++) s += conv_entry(i, j) * u[j]; printf(" %d", s); } printf("\n"); return 0;}Print the 4 × 4 matrix, each entry read from t, seven numbers.
- numbers stored
- 7 for 16 entries
Output
Describe this picture
The picture has two parts, side by side on a wide screen and one above the other on a phone. The first is a 4 × 4 grid, M, over a strip of 7 boxes, t, holding 7, 6, 5, 1, 2, 3, 4. The second is a 7 × 5 grid, F, over a strip of 3 boxes, h, holding the filter taps 1, 2, 1; in F, only the cells on three diagonals, from the top left downwards, are tied to a box of h, and the others are faint. Both grids start blank. Each grid cell takes the tint of its row, and the box it reads takes the same tint. Under each grid is a line of text with the current formula. Under the picture are the program toeplitz.c, with the line about to run marked, one sentence on the current step, a readout “numbers stored” reading “7 for 16 entries”, and the output. The clip plays once, about 16 seconds. First M’s top row fills cell by cell, each cell lit together with the box it reads: M(0, 0) is t[3] = 1, then t[4], t[5] and t[6], giving 1 2 3 4. Rows 1, 2 and 3 fill a beat each, each one box further left in t, so every diagonal of M holds a single number. Next the rows of Mx light one at a time, with x = 1, 2, 3, 4, giving 30, 25, 27 and 38. Last, F fills a row a beat: row i has the taps in the columns i − 2 to i, and its sum with u = 1, 2, 3, 4, 5 is output i, from filtered[0] = 1 to filtered[6] = 5. The clip rests on the output filtered = 1 4 8 12 16 14 5. Watch only: there is no control. The transport plays and pauses, steps one beat back or forward, and scrubs; a paused step is kept in the page address as toeplitz.step.
One number per diagonal
A Toeplitz matrix is constant along every diagonal: M(i, j) depends only on j − i. In an n × n matrix j − i runs from −(n − 1) to n − 1, so the whole matrix is fixed by 2n − 1 numbers: its first column and its first row, which share the corner. The program’s 4 × 4 example is
1 2 3 4
5 1 2 3
6 5 1 2
7 6 5 1
and its seven numbers are stored in one array, the first column read upwards and then the first row:
static const int t[2 * N - 1] = { 7, 6, 5, 1, 2, 3, 4 };
A Toeplitz matrix need not be symmetric. This one is not: M(1, 0) = 5 but M(0, 1) = 2. It is symmetric only when the first row and the first column hold the same numbers.
The code and its cost
The index
Diagonal j − i is stored at t[n − 1 + j − i]. The offset n − 1 moves the lowest diagonal, j − i = −(n − 1), to cell 0:
int get(int i, int j)
{
return t[N - 1 + j - i];
}
M(0, 0) is t[3] = 1, M(3, 0) is t[0] = 7, and M(0, 3) is t[6] = 4. The program prints all 16 entries through get; each row is the row above shifted one place right, with a new number coming in on the left.
The product
Mx needs every entry once, so it takes n² multiplications, as a full matrix does; the entries just come from 7 numbers instead of 16:
for (int i = 0; i < N; i++) {
int s = 0;
for (int j = 0; j < N; j++)
s += get(i, j) * x[j];
printf(" %d", s);
}
With x = (1, 2, 3, 4) the program prints Mx = 30 25 27 38. The saving is the memory: 2n − 1 numbers instead of n².
Convolution as a matrix
Convolution with a short filter is a Toeplitz matrix. Filter u = (1, 2, 3, 4, 5) with the taps h = (1, 2, 1): output i is the sum of h[k] × u[i − k]. Written as a matrix, entry (i, j) is h[i − j] when 0 ≤ i − j < 3, and 0 otherwise. It depends only on i − j, so the matrix is Toeplitz, and it is banded: only three diagonals are non-zero. A k-tap filter on L samples gives L + k − 1 outputs, so the matrix is (L + k − 1) × L, here 7 × 5:
int conv_entry(int i, int j)
{
int k = i - j;
return (k >= 0 && k < TAPS) ? h[k] : 0;
}
The program never stores the 35 entries; it works each one out from h when it needs it. Each column of the matrix holds the taps 1, 2, 1, one row lower than the column before. Multiplying by u gives
filtered = 1 4 8 12 16 14 5
which adds to 60, the input’s sum, 15, times the taps’ sum, 4.
The cost
A Toeplitz matrix takes 2n − 1 numbers instead of n². Reading an entry is one subtraction and one array read. The product with a vector still takes n² multiplications when done this way.
Running it yourself
gcc -std=c11 -Wall -Wextra -pedantic toeplitz.c -o toeplitz
./toeplitz
Worked example
Row 2 of Mx. Row 2 of M is 6 5 1 2, so its entry of Mx is 6 × 1 + 5 × 2 + 1 × 3 + 2 × 4 = 6 + 10 + 3 + 8 = 27.
One filtered sample. Row 3 of the filter matrix has the taps in columns 1, 2 and 3: filtered[3] = 1 × 2 + 2 × 3 + 1 × 4 = 12.
A large matrix. For n = 1,000 the store holds 2 × 1,000 − 1 = 1,999 numbers against 1,000,000 entries.
Two checks. Which cell of t holds M(3, 1)? (3 + 1 − 3 = 1, the value 6.) How large is the matrix for a 4-tap filter on 10 samples? (13 × 10.)
Where it’s used
Digital filters. A filter applied to a block of samples is a banded Toeplitz matrix times the block; writing it that way lets a filter be analysed with the tools of linear algebra.
Speech coding. Linear prediction solves a system whose matrix, the autocorrelation matrix, is symmetric Toeplitz; fast solvers use the structure to work in about n² steps instead of n³.
Image blurring. Blurring an image with a small kernel is a block Toeplitz matrix: Toeplitz blocks arranged in a Toeplitz pattern.
Time series. Models of a series that depend only on the lag between samples give Toeplitz covariance matrices.
Reference card
| Code or idea | Meaning |
|---|---|
| M(i, j) depends only on j − i | a Toeplitz matrix |
| 2n − 1 | numbers for an n × n Toeplitz matrix |
t[N - 1 + j - i] | where M(i, j) is stored |
| h[i − j], 0 ≤ i − j < k | entry (i, j) of a k-tap filter’s matrix |
| (L + k − 1) × L | size of the filter matrix for L samples |
Next, sparse matrices store only the entries that are not zero, wherever they are. Multiplying polynomials is the same convolution.
Platform: C11, gcc on x86-64 Linux: int 4 bytes, pointers 8, little-endian. Addresses in the pictures are short stand-ins, by one rule for the whole course; sizes are the real ones.