Block Matrix Multiplication
It's like a matrix of matrices. Also featuring Hamming Codes!
What if we partition matrix multiplication along the axis of contraction?
We segment the inner
matrix vertically and the outer
matrix horizontally, where inner and outer mean that when we use these matrices we apply the inner one first, then the outer. In English notation we compose right-to-left, $\b{M_2} \b{M_1} \b{v}$.
Segmented Vector
For simplicity's sake let's start with a matrix and vector.
$\begin{bmatrix} \b{A} | \b{B} \end{bmatrix} \begin{bmatrix} \b{u} \\ \hline \b{v} \end{bmatrix} = \b{A} \b{u} + \b{B} \b{v}$
=+For this to make sense, the width of $\b{A}$ and the height of $\b{u}$ have to be equal, as do the width of $\b{B}$ and the height of $\b{v}$.
An example use being 4x4 matrices for OpenGL (or any graphics programming or geometry manipulation in general). We can promote a vector $\b{v} \in \R^3$ to $\b{v'} \in \R^4$ by setting the fourth, w
, component to $1$. We can maintain this $1$ under 4x4 matrix transformations by promoting $\b{M} \in \text{Mat}_{3 \times 3}$ to $\b{M'} \in \text{Mat}_{4 \times 4}$ via placing $\b{M}$ into the top-left corner of a zeroed out 4x4 matrix, and setting the bottom-right component to $1$.
| a | d | g |
| b | e | h |
| c | f | i |
| x |
| y |
| z |
->| a | d | g | 0 |
| b | e | h | 0 |
| c | f | i | 0 |
| 0 | 0 | 0 | 1 |
| x |
| y |
| z |
| 1 |
=| a | d | g |
| b | e | h |
| c | f | i |
| 0 | 0 | 0 |
| x |
| y |
| z |
+| 0 |
| 0 |
| 0 |
| 1 |
| 1 |
The x, y, and z values don't contribute to the w component, as can be seen in the first multiplication on the RHS, and in the second multiplication on the RHS we can see that the w component is always set to 1. What a nice chiastic[1] sentence!
Sometimes in OpenGL we're interested in using the w component as a homogeneous scaling coordinate (particularly for the linear-perspective projection matrix[2]), which OpenGL vertex shaders divide by. Other times we're interested in combining translation and rotation transformations together. Let's focus on the latter.
We know that an $\R^3$ rotation matrix is 3x3. Embed that into a 4x4 matrix as above. If, instead of setting them to zero, we set the other three components of the matrix's fourth column to be p, q, and r, we see in the matrix decomposition that this adds p, q, and r to the x, y, and z output components; a translation!
| a | d | g | p |
| b | e | h | q |
| c | f | i | r |
| 0 | 0 | 0 | 1 |
| x |
| y |
| z |
| 1 |
=| a | d | g |
| b | e | h |
| c | f | i |
| 0 | 0 | 0 |
| x |
| y |
| z |
+| p |
| q |
| r |
| 1 |
| 1 |
This is most often what we want, applying roll, pitch, yaw, translate
to construct a model matrix
for mapping an object from its own localspace to worldspace. Since we expect to apply further transformations, eg the view matrix
mapping from worldspace to cameraspace, it makes sense to keep the matrix 4x4 instead of trying to prematurely optimize for space by using 3x4 matrices, pedantically seeing the three zeros below the rotation matrix as dead weight.
As a fun side note, the view matrix
maps from worldspace to cameraspace, which is really the camera's localspace. Thus the view matrix is just the inverse of the camera's model matrix, as the model matrix maps from its localspace (cameraspace) to worldspace. We transform an object from localspace -> worldspace -> cameraspace via $M_\text{cam}^{-1} M_\text{obj} = R_\text{cam}^{-1} P_\text{cam}^{-1} Y_\text{cam}^{-1} T_\text{cam}^{-1} T_\text{obj} Y_\text{obj} P_\text{obj} R_\text{obj}$.
Segmented Matrix
Our work above also applies if the inner tensor is not just a vector but a matrix. And as I just let slip, this works partitioning any tensor contraction index.
$\begin{bmatrix} \b{A} | \b{B} \end{bmatrix} \begin{bmatrix} \b{C} \\ \hline \b{D} \end{bmatrix} = \b{A} \b{C} + \b{B} \b{D}$
=+You can convince yourself of this by going through $\b{C}$ and $\b{D}$ column at a time.
Example: Hamming Codes
Example usage leads us to rederiving Hamming Codes[3]:
Suppose we have a linear map $\alpha: V \to W$.
We can create $\beta: V \to (V \times W)$ defined by $\vec{v} \mapsto (\text{id}_{V}(\vec{v}), \alpha(\vec{v}))$.
We can create $\gamma: (V \times W) \to W$ defined by $(\vec{v}, \vec{w}) \mapsto \alpha(\vec{v}) - \text{id}_{W}(\vec{w})$.
Then $\gamma \circ \beta: V \to W = \underline{\underline{0}}$ because $\vec{v} \mapsto_{\beta} (\text{id}_{V}(\vec{v}), \alpha(\vec{v})) \mapsto_{\gamma} \alpha(\text{id}_{V}(\vec{v})) - \text{id}_{W}(\alpha(\vec{v})) = \alpha(\vec{v}) - \alpha(\vec{v}) = \vec{0}$.
In other words: we can apply the identity map in $V$ then apply $\alpha$, or we can apply $\alpha$ then apply the identity map in $W$, and subtracting one from another we always end up with $\vec{0}$.
And also $\ker(\gamma) = \lbrace (\vec{v}, \vec{w}) \mid \alpha(\vec{v}) - \text{id}_{W}(\vec{w}) = \vec{0} \rbrace = \lbrace (\vec{v}, \vec{w}) \mid \alpha(\vec{v}) = \vec{w} \rbrace = \lbrace (\vec{v}, \alpha(\vec{v})) \rbrace = \im(\beta)$. I think category theorists call this an exact sequence[4] but we don't need to delve so deeply...
Using our matrix notation, with $\alpha$'s matrix $\b{A}$, this tells us $\begin{bmatrix} \b{A} | \b{-I}_{W} \end{bmatrix} \begin{bmatrix} \b{I}_{V} \\ \hline \b{A} \end{bmatrix} = \b{A} \b{I}_{V} - \b{I}_{W} \b{A} = \b{A} - \b{A} = \b{0}$.
| -1 | ||||||
| -1 | ||||||
| -1 |
| 1 | |||
| 1 | |||
| 1 | |||
| 1 | |||
=| 1 | |||
| 1 | |||
| 1 | |||
| 1 |
+| -1 | ||
| -1 | ||
| -1 |
=| 0 | 0 | 0 | 0 |
| 0 | 0 | 0 | 0 |
| 0 | 0 | 0 | 0 |
Now let us use this to construct Hamming Codes:
Consider vector spaces over $\mathbb{F}_2$, with which $\b{M}$ and $\b{-M}$ are the same for any matrix $\b{M}$. Choose $r = \dim(W)$ to be $\ge 2$.
The $\b{I}_{W} = \b{-I}_{W}$ in $\begin{bmatrix} \b{A} | \b{-I}_{W} \end{bmatrix}$ contains all columns with a single bit set to 1. Let $\b{A}$ be the matrix containing all of the other possible non-zero columns, each column appearing exactly once. $\b{I}_W$ has width $r$ and $\b{A}$ has width $2^r - 1 - r$.
| 1 | 1 | 0 | 1 | 1 | ||
| 1 | 0 | 1 | 1 | 1 | ||
| 0 | 1 | 1 | 1 | 1 |
| 1 | |||
| 1 | |||
| 1 | |||
| 1 | |||
| 1 | 1 | 0 | 1 |
| 1 | 0 | 1 | 1 |
| 0 | 1 | 1 | 1 |
=| 0 | 0 | 0 | 0 |
| 0 | 0 | 0 | 0 |
| 0 | 0 | 0 | 0 |
The matrix $\b{P} = \begin{bmatrix} \b{I} \\ \hline \b{A} \end{bmatrix}$ forms a linear code[5] that encodes binary messages of length $2^r - 1 - r$ as binary codewords of length $2^r - 1$ (ie adding $r$ bits of padding).
The matrix $\b{Q} = \begin{bmatrix} \b{A} | \b{I} \end{bmatrix}$ is known as the syndrome
matrix of $\b{P}$. From our work above we know that $\b{u} \in \im(\b{P}) \iff \b{u} \in \ker(\b{Q})$. In other words $\b{u}$ is a codeword of $\b{P}$ if and only if $\b{u}$ is annihilated by the syndrome $\b{Q}$.
This strict checking of ($\b{u} \text{ is a codeword} \iff \b{Q}\b{u} = \b{0}$) allows us to show that $\b{P}$ is an error detecting code, and even an error correcting code:
Consider us receiving binary codewords over a noisy channel, where there is a non-zero probability of bits being corrupted.
In particular consider if a single bit is corrupted. We receive the vector $\b{r} = \b{u} + \b{e}_i$, where $\b{u} \in \im(\b{P})$ and $\b{e}_i$ has only its $i$th bit set. Then $\b{Q}\b{r} = \b{Q}(\b{u} + \b{e}_i) = \b{Q}\b{u} + \b{Q}\b{e}_i = \b{0} + \b{Q}\b{e}_i = \b{Q}\b{e}_i$ by linearity of linear algebra and matrices (hence why we called these linear codes
). $\b{Q}\b{e}_i$ picks out the $i$th column of $\b{Q}$, which we know will always be non-zero by how we constructed $\b{A}$ to contain only non-zero columns, as does $\b{I}$. Hence $\b{Q}\b{r} = \b{Q}\b{e}_i \ne \b{0}$ and so $\b{r} \notin \im(\b{P})$. In other words mutating a codeword by any of its bits will never yield another codeword, meaning there is some good distance
between the codewords. More precisely the Hamming distance[6] of our code $\b{P}$ (the minimum number of bits that any two codewords differ by) is greater than 1. This allows us to detect single-bit errors; keep reading for how we can correct them!
What if two bits are corrupted? We receive the vector $\b{r} = \b{u} + \b{e}_i + \b{e}_j$. Then $\b{Q}\b{r} = \b{Q}\b{e}_i + \b{Q}\b{e}_j$. This is picking out two columns from $\b{Q}$ and adding them together. Can this ever be $\b{0}$ so that $\b{r} \in \im(\b{P})$? No. That would require two columns of $\b{Q}$ to be the same to sum to $\b{0}$ modulo 2, but we chose $\b{A}$'s columns to be unique. Thus the Hamming distance of $\b{P}$ is greater than 2.
As we have proven that the Hamming distance of our code $\b{P}$ is greater than or equal to 3, we can now prove that we can comfortably allocate a closed ball[7] of radius 1 around each codeword $\b{u}$ in ${\mathbb{F}_2}^{2^{r} - 1}$, $\bar B(\b{u}, 1)$, whose vectors $\b{r} \in \bar B(\b{u}, 1)$ should be corrected to $\b{u}$ if they are received from a noisy channel. In other words if $\b{r}$ differs from $\b{u}$ by 1 bit then error-correct $\b{r}$ to $\b{u}$
. Let us prove this:
- Hamming distance $d(\b{x}, \b{y})$ is a metric function satisfying the triangle inequality[8], that is $d(\b{x}, \b{y}) \le d(\b{x}, \b{z}) + d(\b{z}, \b{y})$ for any $\b{z}$.
- Let $\b{u'} \in \im(\b{P})$ be another legitimate codeword. The triangle inequality tells us that $d(\b{u}, \b{u'}) \le d(\b{u}, \b{r}) + d(\b{r}, \b{u'})$.
- We know that $d(\b{u}, \b{r}) = 1$ as $\b{r} \in \bar B(\b{u}, 1)$, thus $d(\b{u}, \b{u'}) \le 1 + d(\b{r}, \b{u'})$.
- We have shown that $3 \le d(\b{u}, \b{u'})$ by the clever construction of the matrix $\b{A}$, thus $3 \le d(\b{u}, \b{u'}) \le 1 + d(\b{r}, \b{u'})$.
- Thus $2 \le d(\b{r}, \b{u'}).$
As $\b{r}$ is at least distance 2 away from every other codeword $\b{u'}$ whenever it is distance 1 away from $\b{u}$, and because we expect the error rate of a noisy channel to be reasonably small, we feel comfortable in justifying this error correction method. More formally, minimum-distance decoding and maximum-likelihood decoding are coincident when the error rate is small. See section 7.1 of Codes and Cryptography[9] if you want the full details.
Computationally determining which bit to correct is very efficient. The syndrome columns (the columns of $\b{Q}$) are all unique, and the nth syndrome column corresponds to the nth bit being flipped. This correspondence means that we don't even have to look up in a table of closed balls which ball the received vector $\b{r}$ belongs to. Instead we just look at the syndrome vector $\b{Q}\b{r}$: if $\b{Q}\b{r} = \b{0}$ then $\b{r}$ is a codeword $\b{u}$, and if $\b{Q}\b{r} \ne \b{0}$ then flip the nth bit of $\b{r}$, where $\b{Q}\b{r}$ is the nth syndrome column of $\b{Q}$.
Example
Sender encodes and sends:
| 1 | |||
| 1 | |||
| 1 | |||
| 1 | |||
| 1 | 1 | 0 | 1 |
| 1 | 0 | 1 | 1 |
| 0 | 1 | 1 | 1 |
| 1 |
| 0 |
| 1 |
| 0 |
=| 1 |
| 0 |
| 1 |
| 0 |
| 1 |
| 0 |
| 1 |
Receive receives:
| 1 |
| 0 |
| 0 |
| 0 |
| 1 |
| 0 |
| 1 |
=| 1 |
| 0 |
| 1 |
| 0 |
| 1 |
| 0 |
| 1 |
+| 0 |
| 0 |
| 1 |
| 0 |
| 0 |
| 0 |
| 0 |
Do note that at this stage the receiver cannot immediately split up the legitimate codeword from the error bit, which we have done here for illustration purposes only. These annotations are the mathematics behind what goes on, as we have derived above.
Receiver checks syndrome:
| 1 | 1 | 0 | 1 | 1 | ||
| 1 | 0 | 1 | 1 | 1 | ||
| 0 | 1 | 1 | 1 | 1 |
| 1 |
| 0 |
| 0 |
| 0 |
| 1 |
| 0 |
| 1 |
=| 1 | 1 | 0 | 1 | 1 | ||
| 1 | 0 | 1 | 1 | 1 | ||
| 0 | 1 | 1 | 1 | 1 |
| 1 |
| 0 |
| 1 |
| 0 |
| 1 |
| 0 |
| 1 |
+| 1 | 1 | 0 | 1 | 1 | ||
| 1 | 0 | 1 | 1 | 1 | ||
| 0 | 1 | 1 | 1 | 1 |
| 0 |
| 0 |
| 1 |
| 0 |
| 0 |
| 0 |
| 0 |
=| 1 | 1 | 0 | 1 | 1 | ||
| 1 | 0 | 1 | 1 | 1 | ||
| 0 | 1 | 1 | 1 | 1 |
| 1 | |||
| 1 | |||
| 1 | |||
| 1 | |||
| 1 | 1 | 0 | 1 |
| 1 | 0 | 1 | 1 |
| 0 | 1 | 1 | 1 |
| 1 |
| 0 |
| 1 |
| 0 |
+| 1 | 1 | 0 | 1 | 1 | ||
| 1 | 0 | 1 | 1 | 1 | ||
| 0 | 1 | 1 | 1 | 1 |
| 0 |
| 0 |
| 1 |
| 0 |
| 0 |
| 0 |
| 0 |
=| 0 | 0 | 0 | 0 |
| 0 | 0 | 0 | 0 |
| 0 | 0 | 0 | 0 |
| 1 |
| 0 |
| 1 |
| 0 |
+| 1 | 1 | 0 | 1 | 1 | ||
| 1 | 0 | 1 | 1 | 1 | ||
| 0 | 1 | 1 | 1 | 1 |
| 0 |
| 0 |
| 1 |
| 0 |
| 0 |
| 0 |
| 0 |
=| 0 |
| 0 |
| 0 |
+| 0 |
| 1 |
| 1 |
=| 0 |
| 1 |
| 1 |
Again, the receiver does not directly perform the intermediate steps here. Instead they simply evaluate the first computation to obtain the final syndrome vector.
The computed syndrome vector is the third column of $\b{Q}$, thus the third bit of the received codeword $\b{r}$ needs flipping to correct it to the sent codeword $\b{u}$. One can then truncate $\b{u}$ to the first $2^r - 1 - r = 4$ bits to reveal the message embedded within.
This means that just the $r$ padding bits of each codeword $\b{u} \in \im(\b{P})$ of length $2^r - 1$ sufficiently space out the codewords to permit unambiguous single-bit error correction. How cool is that!
If we had constructed $\b{A}$ differently, to contain less than all of the non-zero non-identity columns, the code would still work, but it would be less efficient than Hamming codes; we maximise the opportunity to include all possible non-zero columns in $\b{P}$. Hamming codes give us bang for our buck
with an efficiency rate of $(2^r - 1 - r) / (2^r - 1) = 1 - r / (2^r - 1)$.
Compare that to eg another primitive way of error correction being to encode each message by tripling each of its digits so that each triplet is decoded as a best-of-three
: 111000101000 corrects to 111000111000 and decodes to 1010. This code has efficiency 1/3 which is pretty bad in comparison.
What was this article about again? Oh yeah, the power of partitioning up the axis of contraction in matrix multiplication! But now you also know how Hamming codes work. Check out Codes and Cryptography[10] for more interesting related material.
Misc thoughts
We could also partition the axis of contraction into more than two pieces.
In chapter 11.3 of Codes and Cryptography, it is pretty much just postulated (with a bit of previous motivation) that $\begin{bmatrix} \b{-A} | \b{I} \end{bmatrix} \begin{bmatrix} \b{I} \\ \hline \b{A} \end{bmatrix}$ can be produced, that both $\b{I}$ matrices can be square, and have dimensions compatible with $\b{A}$, and are compatible with this partitioning scheme. Sure, one can computationally verify that the dimensions add up correctly, but I'm much more satisfied with the constructive method I've come up with in $V \to_\beta (V \times W) \to_\gamma W$. I do love T. K. Carne's notes though :)