haskellify finite-field.3

This commit is contained in:
queue-miscreant 2025-08-04 05:33:30 -05:00
parent 8d14c5b414
commit 91cfd2397a
2 changed files with 218 additions and 259 deletions

View File

@ -0,0 +1 @@
../2/Previous.hs

View File

@ -5,9 +5,8 @@ description: |
format: format:
html: html:
html-math-method: katex html-math-method: katex
jupyter: python3
date: "2024-02-03" date: "2024-02-03"
date-modified: "2025-07-16" date-modified: "2025-08-04"
categories: categories:
- algebra - algebra
- finite field - finite field
@ -15,26 +14,64 @@ categories:
--- ---
<style> <style>
.red { .narrow {
color: red; max-width: 512px;
}
.orange {
color: orange;
}
.yellow {
color: yellow;
}
.green {
color: green;
}
.blue {
color: blue;
}
.purple {
color: purple;
} }
</style> </style>
```{haskell}
--| echo: false
:l Previous.hs
import Data.Array (ixmap, bounds, (!))
import Data.List (intercalate)
import IHaskell.Display (markdown)
import Previous (
Matrix(Mat, unMat), Polynomial(Poly, coeffs),
asPoly, evalPoly, synthDiv,
companion, zero, eye, determinant
)
-- Explicit Matrix operations
(|+|) :: Num a => Matrix a -> Matrix a -> Matrix a
(|+|) = (+)
(|*|) :: Num a => Matrix a -> Matrix a -> Matrix a
(|*|) = (*)
-- Original determinant implementation, instead of the imported one based on Faddeev-Leverrier
laplaceDet :: (Num a, Eq a) => Matrix a -> a
laplaceDet (Mat xs) = determinant' xs where
-- Evaluate (-1)^i without repeated multiplication
parity i = if even i then 1 else -1
-- Map old array addresses to new ones when eliminating row 0, column i
rowMap i (x,y) = (x+1, if y >= i then y+1 else y)
-- Recursive determinant Array
determinant' xs
-- Base case: 1x1 matrix
| n == 0 = xs!(0,0)
-- Sum of cofactor expansions
| otherwise = sum $ map cofactor [0..n] where
-- Produce the cofactor of row 0, column i
cofactor i
| xs!(0,i) == 0 = 0
| otherwise = (parity i) * xs!(0,i) * determinant' (minor i)
-- Furthest extent of the bounds, i.e., the size of the matrix
(_,(n,_)) = bounds xs
-- Build a new Array by eliminating row 0 and column i
minor i = ixmap ((0,0),(n-1,n-1)) (rowMap i) xs
-- Convert matrix to LaTeX string
texifyMatrix' f mat = surround mat' where
mat' = intercalate " \\\\ " $ map (intercalate " & " . map f) $
fromMatrix mat
surround = ("\\left( \\begin{matrix}" ++) . (++ "\\end{matrix} \\right)")
texifyMatrix = texifyMatrix' show
```
In the [previous post](../2), we focused on constructing finite fields using *n*×*n* matrices. In the [previous post](../2), we focused on constructing finite fields using *n*×*n* matrices.
These matrices came from from primitive polynomials of degree *n* over GF(*p*), These matrices came from from primitive polynomials of degree *n* over GF(*p*),
@ -59,12 +96,12 @@ $$
0 & 0 \\ 0 & 0 \\
0 & 0 0 & 0
\end{matrix}\right) \end{matrix}\right)
\quad ~~
1 \mapsto \left(\begin{matrix} 1 \mapsto \left(\begin{matrix}
1 & 0 \\ 1 & 0 \\
0 & 1 0 & 1
\end{matrix}\right) = I \end{matrix}\right) = I
\quad ~~
\alpha \mapsto \left(\begin{matrix} \alpha \mapsto \left(\begin{matrix}
0 & 1 \\ 0 & 1 \\
1 & 1 1 & 1
@ -93,9 +130,9 @@ $$
In the images of *f*, the zero matrix has determinant 0 and all other elements have determinant 1. In the images of *f*, the zero matrix has determinant 0 and all other elements have determinant 1.
Therefore, the product of any two nonzero matrices always has determinant 1, Therefore, the product of any two nonzero matrices always has determinant 1,
and a nonzero determinant means the matrix is invertible. and a nonzero determinant means the matrix is invertible.
This means that the non-zero elements of the field form their own group with respect to multiplication. Per the definition of the field, the non-zero elements form a group with respect to multiplication.
Here, they form a cyclic group of order 3, since *C*~*p*~^3^ = *I* mod 2. Here, they form a cyclic group of order 3, since *C*~*p*~^3^ = *I* mod 2.
This is also true using symbols, and we've already agreed that *α*^3^ = 1. This is also true using symbols, since *α*^3^ = 1.
### Other Matrices ### Other Matrices
@ -109,47 +146,51 @@ $$
\#\{a_{ij} = 1\} & \det = 0 & \det = 1 \#\{a_{ij} = 1\} & \det = 0 & \det = 1
\\ \hline \\ \hline
1 & 1 &
\scriptsize
\left(\begin{matrix} \left(\begin{matrix}
0 & 1 \\ 0 & 1 \\
0 & 0 0 & 0
\end{matrix}\right) \end{matrix}\right)
\quad ~~
\left(\begin{matrix} \left(\begin{matrix}
1 & 0 \\ 1 & 0 \\
0 & 0 0 & 0
\end{matrix}\right) \end{matrix}\right)
\quad ~~
\left(\begin{matrix} \left(\begin{matrix}
0 & 0 \\ 0 & 0 \\
0 & 1 0 & 1
\end{matrix}\right) \end{matrix}\right)
\quad ~~
\left(\begin{matrix} \left(\begin{matrix}
0 & 0 \\ 0 & 0 \\
1 & 0 1 & 0
\end{matrix}\right) \end{matrix}\right)
\\ \\
2 & 2 &
\scriptsize
\left(\begin{matrix} \left(\begin{matrix}
1 & 1 \\ 1 & 1 \\
0 & 0 0 & 0
\end{matrix}\right) \end{matrix}\right)
\quad ~~
\left(\begin{matrix} \left(\begin{matrix}
0 & 0 \\ 0 & 0 \\
1 & 1 1 & 1
\end{matrix}\right) \end{matrix}\right)
\quad ~~
\left(\begin{matrix} \left(\begin{matrix}
0 & 1 \\ 0 & 1 \\
0 & 1 0 & 1
\end{matrix}\right) \end{matrix}\right)
\quad ~~
\left(\begin{matrix} \left(\begin{matrix}
1 & 1 \\ 1 & 1 \\
0 & 0 0 & 0
\end{matrix}\right) \end{matrix}\right)
& \textcolor{red}{ &
\scriptsize
\textcolor{red}{
\left(\begin{matrix} \left(\begin{matrix}
0 & 1 \\ 0 & 1 \\
1 & 0 1 & 0
@ -157,13 +198,14 @@ $$
} }
\\ \\
3 & & 3 & &
\scriptsize
\textcolor{red}{ \textcolor{red}{
\left(\begin{matrix} \left(\begin{matrix}
1 & 1 \\ 1 & 1 \\
0 & 1 0 & 1
\end{matrix}\right) \end{matrix}\right)
} }
\quad ~~
\textcolor{red}{ \textcolor{red}{
\left(\begin{matrix} \left(\begin{matrix}
1 & 0 \\ 1 & 0 \\
@ -172,6 +214,7 @@ $$
} }
\\ \\
4 & 4 &
\scriptsize
\left(\begin{matrix} \left(\begin{matrix}
1 & 1 \\ 1 & 1 \\
1 & 1 1 & 1
@ -184,12 +227,15 @@ The matrices in the right column (in red) have determinant 1, which means they c
This forms a larger group, of which our field's multiplication group is a subgroup. This forms a larger group, of which our field's multiplication group is a subgroup.
However, it is *not* commutative, since matrix multiplication is not commutative in general. However, it is *not* commutative, since matrix multiplication is not commutative in general.
The group of all six matrices of determinant 1 is called the The group of all six matrices with nonzero determinant is called the
[*general linear group*](https://en.wikipedia.org/wiki/General_linear_group) [*general linear group*](https://en.wikipedia.org/wiki/General_linear_group)
of degree 2 over GF(2), written GL(2, 2). of degree 2 over GF(2), written[^1] GL(2, 2).
We can sort the elements into classes by their order, or the number of times we have We can sort the elements into classes by their order, or the number of times we have
to multiply them before getting to the identity matrix (mod 2): to multiply them before getting to the identity matrix (mod 2):
[^1]: Unfortunately, it's rather easy to confuse "GF" with "GL".
Remember that "F" is for "field", with the former standing for "Galois field".
$$ $$
\begin{array}{} \begin{array}{}
\text{Order 1} & \text{Order 2} & \text{Order 3} \text{Order 1} & \text{Order 2} & \text{Order 3}
@ -232,10 +278,10 @@ $$
If you've studied enough group theory, you know that there are two groups of order 6: If you've studied enough group theory, you know that there are two groups of order 6:
the cyclic group of order 6, *C*~6~, and the symmetric group on three elements, *S*~3~. the cyclic group of order 6, *C*~6~, and the symmetric group on three elements, *S*~3~.
Since the former group has order-6 elements, this group must be isomorphic to the latter. Since the former group has order-6 elements, but none of these matrices are of order 6,
the matrix group must be isomorphic to the latter.
Since the group is small, it's not too difficult to construct an isomorphism between the two. Since the group is small, it's not too difficult to construct an isomorphism between the two.
Writing the elements of *S*~3~ in [cycle notation](), we have: Writing the elements of *S*~3~ in [cycle notation](/posts/permutations/1/), we have:
<!-- TODO: permutations link -->
$$ $$
\begin{gather*} \begin{gather*}
@ -293,11 +339,9 @@ Haskell implementation of GL and SL for prime fields
This implementation will be based on the `Matrix` type from the first post. This implementation will be based on the `Matrix` type from the first post.
Assume we have already defined matrix multiplication and addition. Assume we have already defined matrix multiplication and addition.
```{.haskell} ```{haskell}
data Matrix a = Mat { unMat :: Array (Int, Int) a } import Data.Array (listArray, bounds, elems)
import Data.List (unfoldr)
-- instance Functor Matrix
-- instance Num a => Num (Matrix a)
-- Partition a list into lists of length n -- Partition a list into lists of length n
reshape :: Int -> [a] -> [[a]] reshape :: Int -> [a] -> [[a]]
@ -316,27 +360,29 @@ fromMatrix :: Matrix a -> [[a]]
fromMatrix (Mat m) = let (_,(_,n)) = bounds m in reshape (n+1) $ elems m fromMatrix (Mat m) = let (_,(_,n)) = bounds m in reshape (n+1) $ elems m
``` ```
With helper functions out of the way, we can move on to generating all matrices (mod *n*) With helper functions out of the way, we can move on to generating all matrices (mod *n*).
before filtering for matrices with nonzero determinant (in the case of GL) and determinant 1 Then, we filter for matrices with nonzero determinant (in the case of GL) and determinant 1
(in the case of SL). (in the case of SL).
```{.haskell} ```{haskell}
allMatrices :: Int -> Int -> Matrix Int import Control.Monad (replicateM)
-- All m x m matrices (mod n) -- All m x m matrices (mod n)
allMatrices m n = map toMatrix $ sequence $ replicate m vectors where allMatrices :: Int -> Int -> [Matrix Int]
allMatrices m n = map toMatrix $ replicateM m vectors where
-- Construct all vectors mod n using base-n expansions and padding -- Construct all vectors mod n using base-n expansions and padding
vectors = [pad $ coeffs $ asPoly n l | l <- [1..n^m-1]] vectors = [pad $ coeffs $ asPoly n l | l <- [1..n^m-1]]
-- Pad xs to length m with zero -- Pad xs to length m with zero
pad xs = xs ++ replicate 0 (m - length xs) pad xs = xs ++ replicate (m - length xs) 0
-- All matrices, but paired with their determinants -- All matrices, but paired with their determinants
matsWithDets :: Int -> Int -> [(Matrix Int, Int)] matsWithDets :: Int -> Int -> [(Matrix Int, Int)]
matsWithDets m n = map (\x -> (x, determinant x `mod` n)) $ allMatrices m n matsWithDets m n = map (\x -> (x, determinant x `mod` n)) $ allMatrices m n
-- Nonzero determinants -- Nonzero determinants
mGL m n = map fst $ filter (\(x,d) -> d /= 0) $ matsWithDets' m n mGL m n = map fst $ filter (\(x,d) -> d /= 0) $ matsWithDets m n
-- Determinant is 1 -- Determinant is 1
mSL m n = map fst $ filter (\(x,d) -> d == 1) $ matsWithDets' m n mSL m n = map fst $ filter (\(x,d) -> d == 1) $ matsWithDets m n
``` ```
</details> </details>
@ -346,13 +392,14 @@ mSL m n = map fst $ filter (\(x,d) -> d == 1) $ matsWithDets' m n
Another important matrix group is the Another important matrix group is the
[*projective general linear group*](https://en.wikipedia.org/wiki/Projective_linear_group), [*projective general linear group*](https://en.wikipedia.org/wiki/Projective_linear_group),
PGL(*n*, *K*). PGL(*n*, *K*).
In this group, two matrices are considered equal if one is a scalar multiple of the other. In this group, two matrices are considered equal if one is a scalar multiple of the other[^2].
Equivalently, the elements *are* these equivalence classes, and the product of two classes is
the set of all possible products of items from one class with items from the other.
Both this and the determinant 1 constraint can apply at the same time, Both this and the determinant 1 constraint can apply at the same time,
forming the *projective special linear group*, PSL(*n*, *K*). forming the *projective special linear group*, PSL(*n*, *K*).
[^2]: Equivalently, the elements *are* these equivalence classes.
The product of two classes is the set of all possible products between the two classes,
which is another class.
For GF(2), all of these groups are the same, since the only nonzero determinant and scalar multiple is 1. For GF(2), all of these groups are the same, since the only nonzero determinant and scalar multiple is 1.
Therefore, it's beneficial to contrast SL and PGL with another example. Therefore, it's beneficial to contrast SL and PGL with another example.
@ -369,23 +416,27 @@ $$
\large \text{GL}(2, 5) \large \text{GL}(2, 5)
\\ \\
\underset{\det = 4}{ \underset{\det = 4}{
\scriptsize
\left(\begin{matrix} \left(\begin{matrix}
0 & 1 \\ 0 & 1 \\
1 & 1 1 & 1
\end{matrix} \right) }, \end{matrix} \right) },
\textcolor{red}{ \underset{\det = 1}{ \textcolor{red}{ \underset{\det = 1}{
\scriptsize
\left(\begin{matrix} \left(\begin{matrix}
0 & 2 \\ 0 & 2 \\
2 & 2 2 & 2
\end{matrix} \right) \end{matrix} \right)
}}, }},
\underset{\det = 2}{ \underset{\det = 2}{
\scriptsize
\left(\begin{matrix} \left(\begin{matrix}
1 & 0 \\ 1 & 0 \\
0 & 2 0 & 2
\end{matrix} \right) \end{matrix} \right)
}, },
\underset{\det = 3}{ \underset{\det = 3}{
\scriptsize
\left(\begin{matrix} \left(\begin{matrix}
2 & 0 \\ 2 & 0 \\
0 & 4 0 & 4
@ -398,6 +449,7 @@ $$
\large \text{PGL}(2,5) \large \text{PGL}(2,5)
\\ \\
\underset{\det = 1, ~4}{ \underset{\det = 1, ~4}{
\scriptsize
\textcolor{red}{\left\{ \textcolor{red}{\left\{
\left(\begin{matrix} \left(\begin{matrix}
0 & 1 \\ 0 & 1 \\
@ -412,6 +464,7 @@ $$
}} }}
\\ \\
\underset{\det = 2, ~ 3}{ \underset{\det = 2, ~ 3}{
\scriptsize
\left\{ \left(\begin{matrix} \left\{ \left(\begin{matrix}
1 & 0 \\ 1 & 0 \\
0 & 2 0 & 2
@ -432,11 +485,13 @@ $$
\large \text{SL}(2,5) \large \text{SL}(2,5)
\\ \\
\textcolor{red}{ \textcolor{red}{
\scriptsize
\left(\begin{matrix} \left(\begin{matrix}
0 & 2 \\ 0 & 2 \\
2 & 2 2 & 2
\end{matrix} \right) \end{matrix} \right)
}, },
\scriptsize
\left(\begin{matrix} \left(\begin{matrix}
0 & 3 \\ 0 & 3 \\
3 & 3 3 & 3
@ -448,6 +503,7 @@ $$
\large \text{PSL}(2,5) \large \text{PSL}(2,5)
\\ \\
\textcolor{red}{ \left\{ \textcolor{red}{ \left\{
\scriptsize
\left(\begin{matrix} \left(\begin{matrix}
0 & 2 \\ 0 & 2 \\
2 & 2 2 & 2
@ -463,32 +519,30 @@ $$
\end{matrix} \end{matrix}
$$ $$
<details> ```{haskell}
<summary> --| code-fold: true
Haskell implementation of PGL and PSL for prime fields --| code-summary: "Haskell implementation of PGL and PSL for prime fields"
</summary>
PGL and PSL require special equality.
It's certainly possible to write a definition which makes the classes explicit, as its own new type.
We could then define equality on this type through `Eq`.
This is rather inefficient, though, so I'll choose to work with the representatives instead.
```{.haskell}
import Data.List (nubBy) import Data.List (nubBy)
scalarTimes :: Int -> Int -> Matrix Int -> Matrix Int -- PGL and PSL require special equality.
-- It's certainly possible to write a definition which makes the classes explicit, as its own new type.
-- We could then define equality on this type through `Eq`.
-- This is rather inefficient, though, so I'll choose to work with the representatives instead.
-- Scalar-multiply a matrix (mod p) -- Scalar-multiply a matrix (mod p)
scalarTimes :: Int -> Int -> Matrix Int -> Matrix Int
scalarTimes n k = fmap ((`mod` n) . (*k)) scalarTimes n k = fmap ((`mod` n) . (*k))
projEq :: Int -> Matrix Int -> Matrix Int -> Bool
-- Construct all scalar multiples mod n, then check if ys is any of them. -- Construct all scalar multiples mod n, then check if ys is any of them.
-- This is ludicrously inefficient, and only works for fields. -- This is ludicrously inefficient, and only works for fields.
projEq :: Int -> Matrix Int -> Matrix Int -> Bool
projEq n xs ys = ys `elem` [scalarTimes n k xs | k <- [1..n-1]] projEq n xs ys = ys `elem` [scalarTimes n k xs | k <- [1..n-1]]
-- Strip out duplicates in GL and SL with projective equality -- Strip out duplicates in GL and SL with projective equality
mPGL m n = nubBy (projEq n) $ mGL m n mPGL m n = nubBy (projEq n) $ mGL m n
mPSL m n = nubBy (projEq n) $ mSL m n mPSL m n = nubBy (projEq n) $ mSL m n
``` ```
</details>
### Exceptional Isomorphisms ### Exceptional Isomorphisms
@ -526,10 +580,10 @@ Within the group, there are three kinds of elements:
(or more precisely, 1/2 of a turn) about an edge (or more precisely, 1/2 of a turn) about an edge
- 5-cycles, such as *b* = (1 2 3 4 5) - 5-cycles, such as *b* = (1 2 3 4 5)
- This corresponds to a 72 degree rotation (1/5 of a turn) - This corresponds to a 72 degree rotation (1/5 of a turn)
around the center of a face around a vertex
- 3-cycles, such as *ab* = (2 4 5) - 3-cycles, such as *ab* = (2 4 5)
- This corresponds to a 120 degree rotation (1/3 of a turn) - This corresponds to a 120 degree rotation (1/3 of a turn)
around a vertex around the center of a face
It happens to be the case that all elements of the group can be expressed It happens to be the case that all elements of the group can be expressed
as a product between *a* and *b* -- they generate the group. as a product between *a* and *b* -- they generate the group.
@ -541,137 +595,82 @@ To create a correspondence with PSL(2, 5), we need to identify permutations with
Obviously, the identity permutation goes to the identity matrix. Obviously, the identity permutation goes to the identity matrix.
Then, since *a* and *b* generate the group, we can search for two matrices which Then, since *a* and *b* generate the group, we can search for two matrices which
obey the same relations (under projective equality, since we're working in PSL). obey the same relations (under projective equality, since we're working in PSL).
One such correspondence is:
$$ Fortunately, we have a computer, so we can search for candidates rather quickly.
\begin{array}{} First, let's note a matrix *B* which is cyclic of order 5 to correspond with *b*:
\begin{gather*}
A = \left(\begin{matrix}
1 & 1 \\
3 & 4
\end{matrix} \right)
\qquad
A^2 = \left(\begin{matrix}
4 & 0 \\
0 & 4
\end{matrix}\right) =
4 \left(\begin{matrix}
1 & 0 \\
0 & 1
\end{matrix}\right)
\qquad
\end{gather*}
\\ ~ \\ \hline \\
\begin{gather*}
B = \left(\begin{matrix}
0 & 2 \\
2 & 2
\end{matrix} \right)
\qquad
B^2 = \left(\begin{matrix}
4 & 4 \\
4 & 3
\end{matrix}\right)
\qquad
B^3 = \left(\begin{matrix}
3 & 1 \\
1 & 4
\end{matrix}\right)
\\
B^4 = \left(\begin{matrix}
2 & 3 \\
3 & 0
\end{matrix}\right)
\qquad
B^5 = \left(\begin{matrix}
1 & 0 \\
0 & 1
\end{matrix}\right)
\end{gather*}
\\ ~ \\ \hline \\
\begin{gather*}
(AB) = \left(\begin{matrix}
2 & 4 \\
3 & 4
\end{matrix} \right)
\qquad
(AB)^2 = \left(\begin{matrix}
1 & 4 \\
3 & 3
\end{matrix}\right)
\qquad
(AB)^3 = \left(\begin{matrix}
4 & 0 \\
0 & 4
\end{matrix}\right)
\end{gather*} =
4 \left(\begin{matrix}
1 & 0 \\
0 & 1
\end{matrix}\right)
\end{array}
$$
```{haskell}
--| code-fold: true
--| code-summary: "Haskell implementation of finding candidates for B"
<details>
<summary>
Haskell implementation using B as a generator to find candidates for A
</summary>
```{.haskell}
orderWith :: Eq a => (a -> a -> a) -> (a -> Bool) -> a -> Int
-- Repeatedly apply f to p, until the predicate z -- Repeatedly apply f to p, until the predicate z
-- (usually equality to some quantity) becomes True. -- (usually equality to some quantity) becomes True.
-- Get the length of the resulting list -- Get the length of the resulting list
orderWith :: Eq a => (a -> a -> a) -> (a -> Bool) -> a -> Int
orderWith f z p = (+1) $ length $ takeWhile (not . z) $ iterate (f p) p orderWith f z p = (+1) $ length $ takeWhile (not . z) $ iterate (f p) p
-- Order with respect to PSL(2, 5): using matrix multiplication (mod 5) -- Order with respect to PSL(2, 5): using matrix multiplication (mod 5)
-- and projective equality to the identity matrix -- and projective equality to the identity matrix
orderPSL25 = orderWith (\x -> fmap (`mod` 5) . (x |*|))) (projEq 5 $ eye 2) orderPSL25 = orderWith (\x -> fmap (`mod` 5) . (x |*|)) (projEq 5 $ eye 2)
-- Only order 2 elements of PSL(2, 5) -- Only order 5 elements of PSL(2, 5)
psl25_order2 = filter ((==2) . orderPSL25) $ mPSL 2 5 psl25_order5 = filter ((==5) . orderPSL25) $ mPSL 2 5
markdown $ ("$$B = " ++) $ (++ "... $$") $ intercalate " ~,~ " $
take 5 $ map texifyMatrix psl25_order5
```
Arbitrarily, let's pick the last entry on this list.
Now, we can search for order-2 elements in PSL(2, 5) whose product with *B* has order 3.
This matrix (*A*) matches exactly with *a* in *A*~5~.
```{haskell}
--| code-fold: true
--| code-summary: "Haskell implementation using B as a generator to find candidates for A"
-- Start with B as a generator -- Start with B as a generator
psl25_gen_B = toMatrix [[0,2],[2,2]] psl25_gen_B = toMatrix [[0,2],[2,2]]
-- Only order 2 elements of PSL(2, 5)
psl25_order2 = filter ((==2) . orderPSL25) $ mPSL 2 5
-- Find an order 2 element whose product with `psl25_gen_B` has order 3 -- Find an order 2 element whose product with `psl25_gen_B` has order 3
psl25_gen_A_candidates = filter ((==3) . orderPSL25 . (psl25_gen_B |*|)) \ psl25_gen_A_candidates = filter ((==3) . orderPSL25 . (psl25_gen_B |*|))
psl25_order2 psl25_order2
-- Candidate matrices: markdown $ ("$$A = " ++) $ (++ "$$") $ intercalate " ~,~ " $
-- map texifyMatrix psl25_gen_A_candidates
-- [1,1]
-- [3,4]
--
-- [1,3]
-- [1,4]
--
-- [2,0]
-- [0,3]
--
-- [2,0]
-- [4,3]
--
-- [2,4]
-- [0,3]
``` ```
If you're unsatisfied with starting from *B*, realize that we could have filtered out Again, arbitrarily, we'll pick the last entry from this list.
only the order 5 elements of PSL(2, 5) (`filter ((==5) . psl25Order) $ mPSL 2 5`), Let's also peek at what the matrix *AB* looks like.
and picked any element from this list to start.
</details>
```{haskell}
--| code-fold: true
psl25_gen_AB = (`mod` 5) <$> (psl25_gen_A_candidates !! 4) |*| psl25_gen_B
markdown $ ("$$" ++) $ (++ "$$") $ intercalate " \\quad " [
"(AB) = " ++ texifyMatrix psl25_gen_AB,
"(AB)^3 = " ++ texifyMatrix ((`mod` 5) <$> (psl25_gen_AB^3))
]
```
We now have a correspondence between three elements of *A*~5~ and PSL(2, 5). We now have a correspondence between three elements of *A*~5~ and PSL(2, 5).
We can "run" both sets of the generators until we associate all elements to one another. We can "run" both sets of the generators until we associate all elements to one another.
This is most visually appealing to see as a Cayley graph: This is most visually appealing to see as a Cayley graph:
[
![ ![
Cayley graph showing an isomorphism between A5 and PSL(2, 5). <br> Cayley graph showing an isomorphism between A5 and PSL(2, 5). <br>
Order-2 elements are red, order-3 elements are green, and order-5 elements are blue. <br> Order-2 elements are red, order-3 elements are green, and order-5 elements are blue.
Purple arrows are order-5 generators, orange arrows are order-2 generators. Purple arrows are order-5 generators, orange arrows are order-2 generators[^4].
](./a5_psl25_cayley.png) ](./a5_psl25_cayley.png){.narrow}
](./a5_psl24_cayley.png)
[^4]: Different generators appear to be used for *A* and *B* in the above image
due to some self-imposed turbulence when writing the original post.
Under projective equality, both are the same as our choices of *A* and *B*.
PSL(2, 4) PSL(2, 4)
@ -737,7 +736,7 @@ $$
Scalar multiplication by *α* multiplies the determinant by *α*^2^; Scalar multiplication by *α* multiplies the determinant by *α*^2^;
by *α*^2^ multiplies the determinant by *α*^4^ = *α*. by *α*^2^ multiplies the determinant by *α*^4^ = *α*.
Thus, SL(2, 4) is also PSL(2, 4), since no scalar multiple has determinant 1. Thus, SL(2, 4) is also PSL(2, 4), since a scalar multiple has determinant 1.
Let's start by looking at an order-5 matrix over PSL(2, 4). Let's start by looking at an order-5 matrix over PSL(2, 4).
We'll call this matrix *B*' to correspond with our order-5 generator in PSL(2, 5). We'll call this matrix *B*' to correspond with our order-5 generator in PSL(2, 5).
@ -777,8 +776,8 @@ $$
We need to be able to do three things over GL(2, 4) on a computer: We need to be able to do three things over GL(2, 4) on a computer:
- multiply matrices over GF(4), - multiply matrices over GF(4),
- compare those matrices, - compute their determinant,
- compute their determinant, and - visually distinguish between each of them, and
- be able to systematically write down all of them - be able to systematically write down all of them
It would then follow for us to repeat what we did with with SL(2, 5). It would then follow for us to repeat what we did with with SL(2, 5).
@ -826,7 +825,7 @@ $$
f(1) & f(1) \\ f(1) & f(1) \\
f(\alpha) & f(\alpha^2) f(\alpha) & f(\alpha^2)
\end{matrix} \right) = \end{matrix} \right) =
f^*((\bar B')^2) f^*((B')^2)
\end{align*} \end{align*}
\end{gather*} \end{gather*}
$$ $$
@ -877,14 +876,11 @@ All we need to do is find all possible 4-tuples of **0**, *I*, *C*~*p*~, and *C*
then arrange each into a 2x2 matrix. then arrange each into a 2x2 matrix.
Multiplication follows from the typical definition and the multiplicative identity is just *f*\*(*I*). Multiplication follows from the typical definition and the multiplicative identity is just *f*\*(*I*).
```{haskell}
--| code-fold: true
--| code-summary: "Haskell implementation of PSL(2, 4)"
<details> import Data.List (elemIndex)
<summary>
Haskell implementation of PSL(2, 4)
</summary>
```{.haskell}
import Data.List (findIndex)
-- Matrices which obey the same relations as the elements of GF(4) -- Matrices which obey the same relations as the elements of GF(4)
zero_f4 = zero 2 zero_f4 = zero 2
@ -896,7 +892,7 @@ alpha2_f4 = toMatrix [[1,1],[1,0]]
field4 = [zero_f4, one_f4, alpha_f4, alpha2_f4] field4 = [zero_f4, one_f4, alpha_f4, alpha2_f4]
-- Convenient show function for these matrices -- Convenient show function for these matrices
showF4 x = case findIndex (==x) field4 of showF4 x = case elemIndex x field4 of
Just 0 -> "0" Just 0 -> "0"
Just 1 -> "1" Just 1 -> "1"
Just 2 -> "α" Just 2 -> "α"
@ -909,101 +905,59 @@ psl_24_identity = toMatrix [[one_f4, zero_f4], [zero_f4, one_f4]]
-- All possible matrices over GF(4) -- All possible matrices over GF(4)
-- Create a list of 4-lists of elements from GF(4), then -- Create a list of 4-lists of elements from GF(4), then
-- Shape them into 2x2 matrices -- Shape them into 2x2 matrices
f4_matrices = map (toMatrix . reshape 2) $ sequence $ replicate 4 field4 f4_matrices = map (toMatrix . reshape 2) $ replicateM 4 field4
-- Sieve out those which have a determinant of 1 in the field -- Sieve out those which have a determinant of 1 in the field
mPSL24 = filter ((==one_f4) . (fmap (`mod` 2)) . laplaceDet) $ f4_matrices mPSL24 = filter ((==one_f4) . fmap (`mod` 2) . laplaceDet) f4_matrices
``` ```
</details>
Now that we can generate the group, we can finally repeat what we did with PSL(2, 5). Now that we can generate the group, we can finally repeat what we did with PSL(2, 5).
All we have to do is filter out order-2 elements, then further filter All we have to do is filter out order-2 elements, then further filter
for those which have an order-3 product with *B*'. for those which have an order-3 product with *B*'.
<details> ```{haskell}
<summary> --| code-fold: true
Haskell implementation using *B*' as a generator to find candidates for *A*' --| code-summary: "Haskell implementation using *B*' as a generator to find candidates for *A*'"
</summary>
```{.haskell}
-- Order with respect to PSL(2, 4): using matrix multiplication (mod 2) -- Order with respect to PSL(2, 4): using matrix multiplication (mod 2)
-- and projective equality to the identity matrix -- and projective equality to the identity matrix
orderPSL24 = orderWith (\x -> fmap (fmap (`mod` 2)) . (x*))) (== psl_24_identity) orderPSL24 = orderWith (\x -> fmap (fmap (`mod` 2)) . (x*)) (== psl_24_identity)
-- Only order 2 elements of PSL(2, 4) -- Only order 2 elements of PSL(2, 4)
psl24_order2 = filter ((==2) . orderPSL24) $ mPSL24 psl24_order2 = filter ((==2) . orderPSL24) mPSL24
-- Start with B as a generator -- Start with B as a generator
psl24_gen_B = toMatrix [[zero_f4, alpha_f4], [alpha2_f4, alpha2_f4]] psl24_gen_B = toMatrix [[zero_f4, alpha_f4], [alpha2_f4, alpha2_f4]]
-- Find an order 2 element whose product with `psl24_gen_B` has order 3 -- Find an order 2 element whose product with `psl24_gen_B` has order 3
psl24_gen_A_candidates = filter ((==3) . orderPSL24 . (psl24_gen_B*)) psl24_gen_A_candidates = filter ((==3) . orderPSL24 . (psl24_gen_B |*|))
psl24_order2 psl24_order2
-- Candidate matrices: markdown $ ("$$ A' = " ++) $ (++ "$$") $ intercalate " ~,~ " $
-- map (texifyMatrix' showF4) psl24_gen_A_candidates
-- ["0","1"]
-- ["1","0"]
--
-- ["0","α^2"]
-- ["α","0"]
--
-- ["1","0"]
-- ["1","1"]
--
-- ["1","α^2"]
-- ["0","1"]
--
-- ["α","1"]
-- ["α","α"]
``` ```
</details>
Finally, we can decide on an *A*', the order-2 generator with the properties we wanted. We'll pick the second entry as our choice of *A*'.
We note that the product *A'B'*, does indeed have order 3.
$$ ```{haskell}
\begin{array}{} --| code-fold: true
\begin{gather*}
A' = \left(\begin{matrix}
0 & \alpha^2 \\
\alpha & 0
\end{matrix} \right)
\qquad
(A')^2 = \left(\begin{matrix}
1 & 0 \\
0 & 1
\end{matrix}\right)
\end{gather*}
\\ \\ \hline \\
\begin{gather*}
A'B' =
\left(\begin{matrix}
\alpha & \alpha \\
0 & \alpha^2
\end{matrix} \right)
\qquad
(A'B')^2 =
\left(\begin{matrix}
\alpha^2 & \alpha \\
0 & \alpha
\end{matrix} \right)
\qquad
(A'B')^3 =
\left(\begin{matrix}
1 & 0 \\
0 & 1
\end{matrix} \right)
\qquad
\end{gather*}
\end{array}
$$
Then, we can arrange them on a Cayley graph in the same way as PSL(2, 5): psl24_gen_AB = fmap (`mod` 2) <$> (psl24_gen_A_candidates !! 1) |*| psl24_gen_B
markdown $ ("$$" ++) $ (++ "$$") $ intercalate " \\quad " [
"(A'B') = " ++ texifyMatrix' showF4 psl24_gen_AB,
"(A'B')^3 = " ++ texifyMatrix' showF4 (fmap (`mod` 2) <$> (psl24_gen_AB^3))
]
```
Finally, we can arrange these matrices on a Cayley graph in the same way as PSL(2, 5):
[
![ ![
Cayley graph showing an isomorphism between *A*^5^ and PSL(2, 4). <br> Cayley graph showing an isomorphism between *A*~5~ and PSL(2, 4). <br>
Colors indicate the same thing as in the previous diagram. Colors indicate the same thing as in the previous diagram.
](./a5_psl24_cayley.png){.narrow}
](./a5_psl24_cayley.png) ](./a5_psl24_cayley.png)
@ -1011,16 +965,20 @@ Closing
------- -------
This post addresses my original goal in implementing finite fields, This post addresses my original goal in implementing finite fields,
namely computationally finding an explicit map between *A*^5^ and PSL(2, 4). namely computationally finding an explicit map between *A*~5~ and PSL(2, 4).
I believe the results are a little more satisfying than attempting to wrap your head I believe the results are a little more satisfying than attempting to wrap your head
around group-theoretic proofs. around group-theoretic proofs.
That's not to discount the power and astounding amount of work that goes into the latter method. That's not to discount the power and incredible logic that goes into the latter method.
It does tend to leave things rather opaque, however. It does tend to leave things rather opaque, however.
If you'd prefer a more interactive diagram showing the above isomorphisms, If you'd prefer a more interactive diagram showing the above isomorphisms,
I've gone to the liberty of creating a hoverable SVG: I've gone to the liberty of creating a hoverable SVG:
![](./a5_psl24_psl25_isomorphism.svg) [
![
Click to open interactive version
](./a5_psl24_psl25_isomorphism.svg){.narrow}
](./a5_psl24_psl25_isomorphism.svg)
This post slightly diverts our course from the previous one's focus on fields. This post slightly diverts our course from the previous one's focus on fields.
The [next one](../4) will focus on more results regarding the treatment of layered matrices. The [next one](../4) will focus on more results regarding the treatment of layered matrices.