From ee4b2cd0a5e805df006505a963b62144454fb9a4 Mon Sep 17 00:00:00 2001 From: queue-miscreant Date: Sun, 3 Aug 2025 22:40:35 -0500 Subject: [PATCH] haskellify finite-field.2 --- posts/finite-field/2/MplIHaskell.hs | 1 + posts/finite-field/2/Previous.hs | 225 ++++ posts/finite-field/2/extra.qmd | 281 +++++ posts/finite-field/2/index.qmd | 1089 ++++++++--------- .../2/irreducibles-graphs/irred_121.png | 3 + .../2/irreducibles-graphs/irred_125.png | 3 + .../2/irreducibles-graphs/irred_25.png | 3 + .../2/irreducibles-graphs/irred_27.png | 3 + .../2/irreducibles-graphs/irred_343.png | 3 + .../2/irreducibles-graphs/irred_49.png | 3 + 10 files changed, 1026 insertions(+), 588 deletions(-) create mode 120000 posts/finite-field/2/MplIHaskell.hs create mode 100644 posts/finite-field/2/Previous.hs create mode 100644 posts/finite-field/2/extra.qmd create mode 100644 posts/finite-field/2/irreducibles-graphs/irred_121.png create mode 100644 posts/finite-field/2/irreducibles-graphs/irred_125.png create mode 100644 posts/finite-field/2/irreducibles-graphs/irred_25.png create mode 100644 posts/finite-field/2/irreducibles-graphs/irred_27.png create mode 100644 posts/finite-field/2/irreducibles-graphs/irred_343.png create mode 100644 posts/finite-field/2/irreducibles-graphs/irred_49.png diff --git a/posts/finite-field/2/MplIHaskell.hs b/posts/finite-field/2/MplIHaskell.hs new file mode 120000 index 0000000..ce22d79 --- /dev/null +++ b/posts/finite-field/2/MplIHaskell.hs @@ -0,0 +1 @@ +../../number-number/1/MplIHaskell.hs \ No newline at end of file diff --git a/posts/finite-field/2/Previous.hs b/posts/finite-field/2/Previous.hs new file mode 100644 index 0000000..d8db7dc --- /dev/null +++ b/posts/finite-field/2/Previous.hs @@ -0,0 +1,225 @@ +module Previous where + +import Control.Applicative (liftA2) +import Data.Array +import Data.List (unfoldr, transpose) +import Data.Tuple (swap) + +zipAdd :: Num a => [a] -> [a] -> [a] +zipAdd [] ds = ds +zipAdd cs [] = cs +zipAdd (c:cs) (d:ds) = (c + d):zipAdd cs ds + +{------------------------------------------------------------------------------ + - ___ _ _ _ + - | _ \___| |_ _ _ _ ___ _ __ (_)__ _| |___ + - | _/ _ \ | || | ' \/ _ \ ' \| / _` | (_-< + - |_| \___/_|\_, |_||_\___/_|_|_|_\__,_|_/__/ + - |__/ + ------------------------------------------------------------------------------} + +-- A polynomial is its ascending list of coefficients (of type a) +newtype Polynomial a = Poly { coeffs :: [a] } deriving Functor + +instance (Eq a, Num a) => Num (Polynomial a) where + (+) x@(Poly as) y@(Poly bs) = Poly $ zipAdd as bs + + --convolution + (*) (Poly []) (Poly bs) = Poly [] + (*) (Poly as) (Poly []) = Poly [] + (*) (Poly as) (Poly bs) = Poly $ zeroize $ finish $ foldl convolve' ([], []) as + where convolve' (xs, ys) a = (a:xs, sum (zipWith (*) (a:xs) bs):ys) + finish (xs, ys) = (reverse ys ++) $ finish' xs $ tail bs + finish' xs [] = [] + finish' xs ys = sum (zipWith (*) xs ys):finish' xs (tail ys) + zeroize xs = if all (==0) xs then [] else xs + + -- this definition works + negate = fmap negate + -- but these two might run into some problems + abs = fmap abs + signum = fmap signum + fromInteger 0 = Poly [] + fromInteger a = Poly [fromInteger a] + +instance (Eq a, Num a) => Eq (Polynomial a) where + (==) as bs = all (==0) $ coeffs $ as - bs + + +-- Interpret a number's base-b expansion as a polynomial +asPoly :: Int -> Int -> Polynomial Int +-- Build a list with f, which returns either Nothing +-- or Just (next element of list, next argument to f) +asPoly b = Poly . unfoldr f where + -- Divide x by b. Emit the remainder and recurse with the quotient. + f x | x /= 0 = Just $ swap $ divMod x b + -- If there's nothing left to divide out, terminate + | otherwise = Nothing + +-- Horner evaluation of a polynomial at the integer b +evalPoly :: Int -> Polynomial Int -> Int +-- Start with the highest coefficient +-- Multiply by b at each step and add the coefficient of the next term +evalPoly b (Poly p) = foldr (\y acc -> acc*b + y) 0 p + +-- Divide the polynomial ps by qs (coefficients in descending degree order) +synthDiv' :: (Eq a, Num a) => [a] -> [a] -> ([a], [a]) +synthDiv' ps qs + | head qs /= 1 = error "Cannot divide by non-monic polynomial" + | otherwise = splitAt deg $ doDiv ps deg + where + -- Negate the denominator and ignore leading term + qNeg = map negate $ tail qs + -- The degree of the result, based on the degrees of the numerator and denominator + deg = max 0 (length ps - length qs + 1) + -- Pluck off the head of the list and add a shifted and scaled version of + -- qs to the tail of the list. Repeat this d times + doDiv xs 0 = xs + doDiv (x:xs) d = x:doDiv (zipAdd xs $ map (*x) qNeg) (d - 1) + +-- Use Polynomial (coefficients in ascending degree order) instead of lists +synthDiv :: (Eq a, Num a) => Polynomial a -> Polynomial a + -> (Polynomial a, Polynomial a) +synthDiv (Poly p) (Poly q) = (Poly $ reverse quot, Poly $ reverse rem) + where (quot, rem) = synthDiv' (reverse p) (reverse q) + + +{------------------------------------------------------------------------------ + - ___ _ _ _ ___ + - | _ \___| |_ _ _ _ ___ _ __ (_)__ _| | / __| ___ __ _ _ _ ___ _ _ __ ___ ___ + - | _/ _ \ | || | ' \/ _ \ ' \| / _` | | \__ \/ -_) _` | || / -_) ' \/ _/ -_|_-< + - |_| \___/_|\_, |_||_\___/_|_|_|_\__,_|_| |___/\___\__, |\_,_\___|_||_\__\___/__/ + - |__/ |_| + ------------------------------------------------------------------------------} + +-- All monic polynomials of degree d with coefficients mod n +monics :: Int -> Int -> [Polynomial Int] +monics n d = map (asPoly n) [n^d..2*(n^d) - 1] + +-- All monic polynomials with coefficients mod n, ordered by degree +allMonics :: Int -> [Polynomial Int] +allMonics n = concat [monics n d | d <- [1..]] + +-- All irreducible monic polynomials with coefficients mod n +irreducibles :: Int -> [Polynomial Int] +irreducibles n = go [] $ allMonics n where + -- Divide the polynomial x by i, then take the remainder mod n + remModN x i = fmap (`mod` n) $ snd $ synthDiv x i + -- Find remainders of x divided by every irreducible in "is". + -- If any give the zero polynomial, then x is a multiple of an irreducible + notMultiple x is = and [not $ all (==0) $ coeffs $ remModN x i | i <- is] + -- Sieve out by notMultiple + go is (x:xs) + | notMultiple x is = x:go (x:is) xs + | otherwise = go is xs + + +{------------------------------------------------------------------------------ + - __ __ _ _ + - | \/ |__ _| |_ _ _(_)__ ___ ___ + - | |\/| / _` | _| '_| / _/ -_|_-< + - |_| |_\__,_|\__|_| |_\__\___/__/ + - + ------------------------------------------------------------------------------} + +newtype Matrix a = Mat { unMat :: Array (Int, Int) a } deriving (Functor, Eq) + +asScalar = Mat . listArray ((0,0),(0,0)) . pure + +transposeM :: Matrix a -> Matrix a +transposeM (Mat m) = Mat $ ixmap (bounds m) swap m + +dotMul :: Num a => [a] -> [a] -> a +dotMul = (sum .) . zipWith (*) + +matMul' xs ys = reshape (length ys) $ liftA2 dotMul xs ys +matMul xs ys = matMul' xs $ transpose ys + +-- Pointwise application +zipWithArr :: Ix i => (a -> b -> c) + -> Array i a -> Array i b -> Array i c +zipWithArr f a b + | ab == bb = array ab $ map (\x -> (x, f (a!x) (b!x))) $ indices a + | otherwise = error "Array dimension mismatch" where + ab = bounds a + bb = bounds b + +-- Maps +mapRange :: Ix i => (i -> e) -> (i, i) -> [(i, e)] +mapRange g r = map (\x -> (x, g x)) $ range r + +instance Num a => Num (Matrix a) where + (+) (Mat x) (Mat y) + | (0,0) == (snd $ bounds x) = Mat $ fmap ((x!(0,0))+) y --adding scalars + | otherwise = Mat $ zipWithArr (+) x y + (*) x y + | (0,0) == (snd $ bounds $ unMat x) = fmap (((unMat x)!(0,0))*) y --multiplying scalars + | otherwise = toMatrix $ matMul' (fromMatrix x) (fromMatrix $ transposeM y) + abs = fmap abs + negate = fmap negate + signum = undefined + fromInteger = asScalar . fromInteger --"scalars" + +reshape :: Int -> [a] -> [[a]] +reshape n = unfoldr (reshape' n) where + reshape' n x = if null x then Nothing else Just $ splitAt n x + +-- Simple function for building a Matrix from lists +toMatrix :: [[a]] -> Matrix a +toMatrix l = Mat $ listArray ((0,0),(n-1,m-1)) $ concat l where + m = length $ head l + n = length l + +-- ...and its inverse +fromMatrix :: Matrix a -> [[a]] +fromMatrix (Mat m) = let (_,(_,n)) = bounds m in reshape (n+1) $ elems m + +-- Zero matrix +zero :: Num a => Int -> Matrix a +zero n = Mat $ listArray ((0,0),(n-1,n-1)) $ repeat 0 + +-- Identity matrix +eye :: Num a => Int -> Matrix a +eye n = Mat $ unMat (zero n) // take n [((i,i), 1) | i <- [0..]] + +-- Companion matrix +companion :: Polynomial Int -> Matrix Int +companion (Poly ps) + | last ps' /= 1 = error "Cannot find companion matrix of non-monic polynomial" + | otherwise = Mat $ array ((0,0), (n-1,n-1)) $ lastRow ++ shiftI where + -- The degree of the polynomial, as well as the size of the matrix + n = length ps' - 1 + -- Remove trailing 0s from ps + ps' = reverse $ dropWhile (==0) $ reverse ps + -- Address/value tuples for a shifted identity matrix: + -- 1s on the diagonal just above the main diagonal, 0s elsewhere + shiftI = map (\p@(x,y) -> (p, if y == x + 1 then 1 else 0)) $ + range ((0,0),(n-2,n-1)) + -- Address/value tuples for the last row of the companion matrix: + -- ascending powers of the polynomial + lastRow = zipWith (\x y -> ((n-1, x), y)) [0..n-1] $ map negate ps' + +mDims = snd . bounds . unMat + +-- trace of a matrix: sum of diagonal entries +mTrace :: (Num a) => Matrix a -> a +mTrace a = sum [(unMat a)!(i,i) | i <- [0..(uncurry min $ mDims a)]] + +-- Faddeev-Leverrier algorithm for computing the determinant, characteristic polynomial, and inverse matrix +-- this relies on dividing the trace by the current iteration count, so it is only suitable for Integral types +fadeev :: (Num a, Integral a) => Matrix a -> ([a], a, Matrix a) +fadeev a = fadeev' [1] (eye $ n+1) 1 where + n = snd $ mDims a + fadeev' rs m i + | all (==0) (unMat nextm) = (replicate (n - (fromIntegral i) + 1) 0 ++ tram:rs , -tram, m) + | otherwise = fadeev' (tram:rs) nextm (i+1) where + am = a * m + tram = -mTrace am `div` i + nextm = am + (fmap (tram *) $ eye (n+1)) + +-- determinant from Faddeev-Leverrier algorithm +determinant :: (Num a, Integral a) => Matrix a -> a +determinant = (\(_,x,_) -> x) . fadeev +-- characteristic polynomial from Faddeev-Leverrier algorithm +charpoly :: (Num a, Integral a) => Matrix a -> Polynomial a +charpoly = Poly . (\(x,_,_) -> x) . fadeev diff --git a/posts/finite-field/2/extra.qmd b/posts/finite-field/2/extra.qmd new file mode 100644 index 0000000..1e64ce6 --- /dev/null +++ b/posts/finite-field/2/extra.qmd @@ -0,0 +1,281 @@ +Rather than redefining evaluation for each of these cases, + we should map our polynomial into a structure compatible with how we want to evaluate it. +Essentially, this means that from a polynomial in the base structure, + we can derive polynomials in these other structures. +In particular, we can either have a matrix of polynomials or a polynomial in matrices. + + +:::: {layout-ncol="2"} +::: {} +$$ +\begin{align*} + p &: K[x] + \\ + p(x) &= x^n + p_{n-1}x^{n-1} + ... + \\ + \phantom{= p} & + p_1 x + p_0 +\end{align*} +$$ +::: + +::: {} +$x$ is a scalar indeterminate + +```haskell +p :: Polynomial K +``` +::: +:::: + +:::: {layout-ncol="2"} +::: {} +$$ +\begin{align*} + P &: (K[x])^{m \times m} + \\ + P(x I) &= (x I)^n + (p_{n-1})(x I)^{n-1} + ... + \\ + & + p_1(x I)+ p_0 I +\end{align*} +$$ +::: + +::: {} +$x$ is a scalar indeterminate, $P(x I)= p(x) I$ is a matrix of polynomials in $x$ + +```haskell +asPolynomialMatrix + :: Polynomial K -> Matrix (Polynomial K) + +pMat :: Matrix (Polynomial K) +pMat = asPolynomialMatrix p +``` +::: +:::: + +:::: {layout-ncol="2"} +::: {} +$$ +\begin{align*} + \hat P &: K^{m \times m}[X] + \\ + \hat P(X) &= X^n + (p_{n-1}I)X^{n-1} + ... + \\ + & + (p_1 I) X + (p_0 I) +\end{align*} +$$ +::: + +::: {} +$X$ is a matrix indeterminate, $\hat P(X)$ is a polynomial over matrices + +```haskell +asMatrixPolynomial + :: Polynomial K -> Polynomial (Matrix K) + +pHat :: Polynomial (Matrix K) +pHat = asMatrixPolynomial p +``` +::: +:::: + + +### Cayley-Hamilton Theorem + +When evaluating the characteristic polynomial of a matrix *with* that matrix, + something strange happens. +Continuing from the previous article, using $x^2 + x + 1$ and its companion matrix, we have: + +$$ +\begin{gather*} + p(x) = x^2 + x + 1 \qquad C_{p} = C + = \left( \begin{matrix} + 0 & 1 \\ + -1 & -1 + \end{matrix} \right) + \\ \\ + \hat P(C) = C^2 + C + (1 \cdot I) + = \left( \begin{matrix} + -1 & -1 \\ + 1 & 0 + \end{matrix} \right) + + \left( \begin{matrix} + 0 & 1 \\ + -1 & -1 + \end{matrix} \right) + + \left( \begin{matrix} + 1 & 0 \\ + 0 & 1 + \end{matrix} \right) + \\ \\ + = \left( \begin{matrix} + 0 & 0 \\ + 0 & 0 + \end{matrix} \right) +\end{gather*} +$$ + +The result is the zero matrix. +This tells us that, at least in this case, the matrix *C* is a root of its own characteristic polynomial. +By the [Cayley-Hamilton theorem](https://en.wikipedia.org/wiki/Cayley%E2%80%93Hamilton_theorem), + this is true in general, no matter the degree of *p*, no matter its coefficients, + and importantly, no matter the choice of field. + +This is more powerful than it would otherwise seem. +For one, factoring a polynomial "inside" a matrix turns out to give the same answer + as factoring a polynomial over matrices. + +:::: {layout-ncol="2"} +::: {} + +$$ +\begin{gather*} + P(xI) = \left( \begin{matrix} + x^2 + x + 1 & 0 \\ + 0 & x^2 + x + 1 + \end{matrix}\right) + \\ \\ + = (xI - C)(xI - C') + \\ \\ + = \left( \begin{matrix} + x & -1 \\ + 1 & x + 1 + \end{matrix} \right) + \left( \begin{matrix} + x - a & -b \\ + -c & x - d + \end{matrix} \right) + \\ \\ + \begin{align*} + x(x - a) + c &= x^2 + x + 1 + \\ + \textcolor{green}{x(-b) - (x - d)} &\textcolor{green}{= 0} + \\ + \textcolor{blue}{(x - a) + (x + 1)(-c)} &\textcolor{blue}{= 0} + \\ + (-b) + (x + 1)(x - d) &= x^2 + x + 1 + \end{align*} + \\ \\ + \textcolor{green}{(-b -1)x +d = 0} \implies b = -1, ~ d = 0 \\ + \textcolor{blue}{(1 - c)x - a - c = 0} \implies c = 1, ~ a = -1 + \\ \\ + C' = + \left( \begin{matrix} + -1 & -1 \\ + 1 & 0 + \end{matrix} \right) +\end{gather*} +$$ +::: + +::: {} +$$ +\begin{gather*} + \hat P(X) = X^2 + X + 1I + \\[10pt] + = (X - C)(X - C') + \\[10pt] + = X^2 - (C + C')X + CC' + \\[10pt] + \implies + \\[10pt] + C + C' = -I, ~ C' = -I - C + \\[10pt] + CC' = I, ~ C^{-1} = C' + \\[10pt] + C' = \left( \begin{matrix} + -1 & -1 \\ + 1 & 0 + \end{matrix} \right) +\end{gather*} +$$ +::: +:::: + +It's important to not that a matrix factorization is not unique. +*Any* matrix with a given characteristic polynomial can be used as a root of that polynomial. +Of course, choosing one root affects the other matrix roots. + + +### Moving Roots + +All matrices commute with the identity and zero matrices. +A less obvious fact is that all of the matrix roots *also* commute with one another. +By the Fundamental Theorem of Algebra, + [Vieta's formulas](https://en.wikipedia.org/wiki/Vieta%27s_formulas) state: + +$$ +\begin{gather*} + \hat P(X) + = \prod_{[i]_n} (X - \Xi_i) + = (X - \Xi_0) (X - \Xi_1)...(X - \Xi_{n-1}) + \\ + = \left\{ \begin{align*} + & \phantom{+} X^n + \\ + & - (\Xi_0 + \Xi_1 + ... + \Xi_{n-1}) X^{n-1} + \\ + & + (\Xi_0 \Xi_1+ \Xi_0 \Xi_2 + ... + \Xi_0 \Xi_{n-1} + \Xi_1 \Xi_2 + ... + \Xi_{n-2} \Xi_{n-1})X^{n-2} + \\ + & \qquad \vdots + \\ + & + (-1)^n \Xi_0 \Xi_1 \Xi_2...\Xi_n + \end{align*} \right. + \\ + = X^n -\sigma_1([\Xi]_n)X^{n-1} + \sigma_2([\Xi]_n)X^{n-2} + ... + (-1)^n \sigma_n([\Xi]_n) +\end{gather*} +$$ + +The product range \[*i*\]~*n*~ means that the terms are ordered from 0 to *n* - 1 over the index given. +On the bottom line, *σ* are + [elementary symmetric polynomials](https://en.wikipedia.org/wiki/Elementary_symmetric_polynomial) + and \[*Ξ*\]~*n*~ is the list of root matrices from *Ξ*~*0*~ to Ξ~*n-1*~. + +By factoring the matrix with the roots in a different order, we get another factorization. +It suffices to only focus on *σ*~2~, which has all pairwise products. + +$$ +\begin{gather*} + \pi \in S_n + \\ + \qquad + \pi \circ \hat P(X) = \prod_{\pi ([i]_n)} (X - \Xi_i) + \\ \\ + = X^n + - \sigma_1 \left(\pi ([\Xi]_n) \vphantom{^{1}} \right)X^{n-1} + + + \sigma_2 \left(\pi ([\Xi]_n) \vphantom{^{1}} \right)X^{n-2} + ... + + (-1)^n \sigma_n \left(\pi ([\Xi]_n) \vphantom{^{1}} \right) + \\ \\ + \\ \\ + (0 ~ 1) \circ \hat P(X) = (X - \Xi_{1}) (X - \Xi_0)(X - \Xi_2)...(X - \Xi_{n-1}) + \\ + = X^n + ... + \sigma_2(\Xi_1, \Xi_0, \Xi_2, ...,\Xi_{n-1})X^{n-2} + ... + \\ \\ \\ \\ + \begin{array}{} + e & (0 ~ 1) & (1 ~ 2) & ... & (n-2 ~~ n-1) + \\ \hline + \textcolor{red}{\Xi_0 \Xi_1} & \textcolor{red}{\Xi_1 \Xi_0} & \Xi_0 \Xi_1 & & \Xi_0 \Xi_1 + \\ + \Xi_0 \Xi_2 & \Xi_0 \Xi_2 & \Xi_0 \Xi_2 & & \Xi_0 \Xi_2 + \\ + \Xi_0 \Xi_3 & \Xi_0 \Xi_3 & \Xi_0 \Xi_3 & & \Xi_0 \Xi_3 + \\ + \vdots & \vdots & \vdots & & \vdots + \\ + \Xi_0 \Xi_{n-1} & \Xi_0 \Xi_{n-1} & \Xi_{0} \Xi_{n-1} & & \Xi_{n-1} \Xi_0 + \\ + \textcolor{green}{\Xi_1 \Xi_2} & \Xi_1 \Xi_2 & \textcolor{green}{\Xi_2 \Xi_1} & & \Xi_1 \Xi_2 + \\ + \vdots & \vdots & \vdots & & \vdots + \\ + \textcolor{blue}{\Xi_{n-2} \Xi_{n-1}} & \Xi_{n-2} \Xi_{n-1} & \Xi_{n-2} \Xi_{n-1} & & \textcolor{blue}{\Xi_{n-1} \Xi_{n-2}} + \end{array} +\end{gather*} +$$ + + +The "[path swaps](/posts/permutations/1/)" shown commute only the adjacent elements. +By contrast, the permutation (0 2) commutes *Ξ*~0~ past both *Ξ*~1~ and *Ξ*~2~. +But since we already know *Ξ*~0~ and *Ξ*~1~ commute by the above list, + we learn at this step that *Ξ*~0~ and *Ξ*~2~ commute. +This can be repeated until we reach the permutation (0 *n*-1) to prove commutativity between all pairs. diff --git a/posts/finite-field/2/index.qmd b/posts/finite-field/2/index.qmd index 49d2e77..0f73102 100644 --- a/posts/finite-field/2/index.qmd +++ b/posts/finite-field/2/index.qmd @@ -5,38 +5,80 @@ description: | format: html: html-math-method: katex -jupyter: python3 date: "2024-01-15" -date-modified: "2025-07-16" +date-modified: "2025-08-03" categories: - algebra - finite field - haskell --- - +```{haskell} +--| echo: false + +:l Previous.hs +:l MplIHaskell.hs + +import Data.Array ((//), listArray, bounds) +import Data.Bifunctor (bimap) +import Data.Profunctor (rmap) +import Data.List (unfoldr, intercalate, nub, (\\), subsequences, sortOn, groupBy) +import Data.Maybe (fromMaybe, fromJust, listToMaybe) +import Data.Either (partitionEithers) +import Data.Function (on) + +import Colonnade hiding (fromMaybe) +import qualified Colonnade.Encode as CE +import qualified Graphics.Matplotlib.Internal as MPLI +import IHaskell.Display (markdown) + +import Previous ( + Matrix(Mat, unMat), Polynomial(Poly, coeffs), + asPoly, evalPoly, synthDiv, irreducibles, + charpoly, companion, toMatrix, fromMatrix, zero, eye + ) +import qualified MplIHaskell + +markdownTable col rows = unlines $ h:h':r where + toColumns = ("| " ++) . (++ " |") . intercalate " | " . foldr (:) [] + h = toColumns $ CE.header id col + h' = [if x == '|' then x else '-' | x <- h] + r = map (toColumns . CE.row id col) rows + +-- Convert Polynomial to LaTeX string +texifyPoly :: (Num a, Eq a, Show a) => Polynomial a -> String +texifyPoly (Poly xs) = texify' $ zip xs [0..] where + texify' [] = "0" + texify' ((c, n):xs) + | all ((==0) . fst) xs = showPow c n + | c == 0 = texify' xs + | otherwise = showPow c n ++ " + " ++ texify' xs + showPow c 0 = show c + showPow 1 1 = "x" + showPow c 1 = show c ++ showPow 1 1 + showPow 1 n = "x^{" ++ show n ++ "}" + showPow c n = show c ++ showPow 1 n + +-- Convert matrix to LaTeX string +texifyMatrix mat = surround mat' where + mat' = intercalate " \\\\ " $ map (intercalate " & " . map show) $ + fromMatrix mat + surround = ("\\left( \\begin{matrix}" ++) . (++ "\\end{matrix} \\right)") + +-- networkx.display tries printing a string that ruins the SVG this generates +plotDigraph x = MPLI.mplotString $ + "import networkx as nx \n\ + \import os \n\ + \original_stdout = sys.stdout \n\ + \sys.stdout = open(os.devnull, 'w') \n\ + \try: \n\ + \ nx.display(nx.DiGraph(" ++ MPLI.toPython x ++ ")) \n\ + \finally: \n\ + \ sys.stdout.close() \n\ + \ sys.stdout = original_stdout" +``` @@ -82,87 +124,6 @@ $$ \end{align*} $$ -Rather than redefining evaluation for each of these cases, - we should map our polynomial into a structure compatible with how we want to evaluate it. -Essentially, this means that from a polynomial in the base structure, - we can derive polynomials in these other structures. -In particular, we can either have a matrix of polynomials or a polynomial in matrices. - - -:::: {layout-ncol="2"} -::: {} -$$ -\begin{align*} - p &: K[x] - \\ - p(x) &= x^n + p_{n-1}x^{n-1} + ... - \\ - \phantom{= p} & + p_1 x + p_0 -\end{align*} -$$ -::: - -::: {} -$x$ is a scalar indeterminate - -```{.haskell} -p :: Polynomial K -``` -::: -:::: - -:::: {layout-ncol="2"} -::: {} -$$ -\begin{align*} - P &: (K[x])^{m \times m} - \\ - P(x I) &= (x I)^n + (p_{n-1})(x I)^{n-1} + ... - \\ - & + p_1(x I)+ p_0 I -\end{align*} -$$ -::: - -::: {} -$x$ is a scalar indeterminate, $P(x I)= p(x) I$ is a matrix of polynomials in $x$ - -```{.haskell} -asPolynomialMatrix - :: Polynomial K -> Matrix (Polynomial K) - -pMat :: Matrix (Polynomial K) -pMat = asPolynomialMatrix p -``` -::: -:::: - -:::: {layout-ncol="2"} -::: {} -$$ -\begin{align*} - \hat P &: K^{m \times m}[X] - \\ - \hat P(X) &= X^n + (p_{n-1}I)X^{n-1} + ... - \\ - & + (p_1 I) X + (p_0 I) -\end{align*} -$$ -::: - -::: {} -$X$ is a matrix indeterminate, $\hat P(X)$ is a polynomial over matrices - -```{.haskell} -asMatrixPolynomial - :: Polynomial K -> Polynomial (Matrix K) - -pHat :: Polynomial (Matrix K) -pHat = asMatrixPolynomial p -``` -::: -:::: - ### Cayley-Hamilton Theorem @@ -178,24 +139,27 @@ $$ -1 & -1 \end{matrix} \right) \\ \\ - \hat P(C) = C^2 + C + (1 \cdot I) - = \left( \begin{matrix} - -1 & -1 \\ - 1 & 0 - \end{matrix} \right) - + \left( \begin{matrix} - 0 & 1 \\ - -1 & -1 - \end{matrix} \right) - + \left( \begin{matrix} - 1 & 0 \\ - 0 & 1 - \end{matrix} \right) - \\ \\ - = \left( \begin{matrix} - 0 & 0 \\ - 0 & 0 - \end{matrix} \right) + \begin{align*} + \text{eval}_{mat}(p, C) &= C^2 + C + (1 \cdot I) + \\ + &= \left( \begin{matrix} + -1 & -1 \\ + 1 & 0 + \end{matrix} \right) + + \left( \begin{matrix} + 0 & 1 \\ + -1 & -1 + \end{matrix} \right) + + \left( \begin{matrix} + 1 & 0 \\ + 0 & 1 + \end{matrix} \right) + \\ + &= \left( \begin{matrix} + 0 & 0 \\ + 0 & 0 + \end{matrix} \right) + \end{align*} \end{gather*} $$ @@ -205,184 +169,27 @@ By the [Cayley-Hamilton theorem](https://en.wikipedia.org/wiki/Cayley%E2%80%93Ha this is true in general, no matter the degree of *p*, no matter its coefficients, and importantly, no matter the choice of field. -This is more powerful than it would otherwise seem. -For one, factoring a polynomial "inside" a matrix turns out to give the same answer - as factoring a polynomial over matrices. +In addition to this, we can also note the following: -:::: {layout-ncol="2"} -::: {} +- Irreducible polynomials cannot have a constant term 0, otherwise *x* could be factored out. + The constant term is equal to the determinant of the companion matrix (up to sign), + so *C*~*p*~ must be invertible. +- All powers of *C*~*p*~ are guaranteed to commute over multiplication, + since this follows from associativity. -$$ -\begin{gather*} - P(xI) = \left( \begin{matrix} - x^2 + x + 1 & 0 \\ - 0 & x^2 + x + 1 - \end{matrix}\right) - \\ \\ - = (xI - C)(xI - C') - \\ \\ - = \left( \begin{matrix} - x & -1 \\ - 1 & x + 1 - \end{matrix} \right) - \left( \begin{matrix} - x - a & -b \\ - -c & x - d - \end{matrix} \right) - \\ \\ - \begin{align*} - x(x - a) + c &= x^2 + x + 1 - \\ - \textcolor{green}{x(-b) - (x - d)} &\textcolor{green}{= 0} - \\ - \textcolor{blue}{(x - a) + (x + 1)(-c)} &\textcolor{blue}{= 0} - \\ - (-b) + (x + 1)(x - d) &= x^2 + x + 1 - \end{align*} - \\ \\ - \textcolor{green}{(-b -1)x +d = 0} \implies b = -1, ~ d = 0 \\ - \textcolor{blue}{(1 - c)x - a - c = 0} \implies c = 1, ~ a = -1 - \\ \\ - C' = - \left( \begin{matrix} - -1 & -1 \\ - 1 & 0 - \end{matrix} \right) -\end{gather*} -$$ -::: +Both of these facts narrow the ring of matrices to a full-on field. +This absolves us of needing to symbolically adjoin roots symbolically using α. +Instead, we can take the companion matrix of an irreducible polynomial *p* + and work with its powers in the same way we would a typical root[^1]. -::: {} -$$ -\begin{gather*} - \hat P(X) = X^2 + X + 1I - \\[10pt] - = (X - C)(X - C') - \\[10pt] - = X^2 - (C + C')X + CC' - \\[10pt] - \implies - \\[10pt] - C + C' = -I, ~ C' = -I - C - \\[10pt] - CC' = I, ~ C^{-1} = C' - \\[10pt] - C' = \left( \begin{matrix} - -1 & -1 \\ - 1 & 0 - \end{matrix} \right) -\end{gather*} -$$ -::: -:::: +[^1]: For finite fields, it might make sense to do the following procedure + to generate every possible element: -It's important to not that a matrix factorization is not unique. -*Any* matrix with a given characteristic polynomial can be used as a root of that polynomial. -Of course, choosing one root affects the other matrix roots. + - Take all powers of a companion matrix *C* + - Add all powers of *C* with prior elements of the field (times identity matrices) + - Repeat until no new elements are generated - -### Moving Roots - -All matrices commute with the identity and zero matrices. -A less obvious fact is that all of the matrix roots *also* commute with one another. -By the Fundamental Theorem of Algebra, - [Vieta's formulas](https://en.wikipedia.org/wiki/Vieta%27s_formulas) state: - -$$ -\begin{gather*} - \hat P(X) - = \prod_{[i]_n} (X - \Xi_i) - = (X - \Xi_0) (X - \Xi_1)...(X - \Xi_{n-1}) - \\ - = \left\{ \begin{align*} - & \phantom{+} X^n - \\ - & - (\Xi_0 + \Xi_1 + ... + \Xi_{n-1}) X^{n-1} - \\ - & + (\Xi_0 \Xi_1+ \Xi_0 \Xi_2 + ... + \Xi_0 \Xi_{n-1} + \Xi_1 \Xi_2 + ... + \Xi_{n-2} \Xi_{n-1})X^{n-2} - \\ - & \qquad \vdots - \\ - & + (-1)^n \Xi_0 \Xi_1 \Xi_2...\Xi_n - \end{align*} \right. - \\ - = X^n -\sigma_1([\Xi]_n)X^{n-1} + \sigma_2([\Xi]_n)X^{n-2} + ... + (-1)^n \sigma_n([\Xi]_n) -\end{gather*} -$$ - -The product range \[*i*\]~*n*~ means that the terms are ordered from 0 to *n* - 1 over the index given. -On the bottom line, *σ* are - [elementary symmetric polynomials](https://en.wikipedia.org/wiki/Elementary_symmetric_polynomial) - and \[*Ξ*\]~*n*~ is the list of root matrices from *Ξ*~*0*~ to Ξ~*n-1*~. - -By factoring the matrix with the roots in a different order, we get another factorization. -It suffices to only focus on *σ*~2~, which has all pairwise products. - -$$ -\begin{gather*} - \pi \in S_n - \\ - \qquad - \pi \circ \hat P(X) = \prod_{\pi ([i]_n)} (X - \Xi_i) - \\ \\ - = X^n - - \sigma_1 \left(\pi ([\Xi]_n) \vphantom{^{1}} \right)X^{n-1} + - + \sigma_2 \left(\pi ([\Xi]_n) \vphantom{^{1}} \right)X^{n-2} + ... - + (-1)^n \sigma_n \left(\pi ([\Xi]_n) \vphantom{^{1}} \right) - \\ \\ - \\ \\ - (0 ~ 1) \circ \hat P(X) = (X - \Xi_{1}) (X - \Xi_0)(X - \Xi_2)...(X - \Xi_{n-1}) - \\ - = X^n + ... + \sigma_2(\Xi_1, \Xi_0, \Xi_2, ...,\Xi_{n-1})X^{n-2} + ... - \\ \\ \\ \\ - \begin{array}{} - e & (0 ~ 1) & (1 ~ 2) & ... & (n-2 ~~ n-1) - \\ \hline - \textcolor{red}{\Xi_0 \Xi_1} & \textcolor{red}{\Xi_1 \Xi_0} & \Xi_0 \Xi_1 & & \Xi_0 \Xi_1 - \\ - \Xi_0 \Xi_2 & \Xi_0 \Xi_2 & \Xi_0 \Xi_2 & & \Xi_0 \Xi_2 - \\ - \Xi_0 \Xi_3 & \Xi_0 \Xi_3 & \Xi_0 \Xi_3 & & \Xi_0 \Xi_3 - \\ - \vdots & \vdots & \vdots & & \vdots - \\ - \Xi_0 \Xi_{n-1} & \Xi_0 \Xi_{n-1} & \Xi_{0} \Xi_{n-1} & & \Xi_{n-1} \Xi_0 - \\ - \textcolor{green}{\Xi_1 \Xi_2} & \Xi_1 \Xi_2 & \textcolor{green}{\Xi_2 \Xi_1} & & \Xi_1 \Xi_2 - \\ - \vdots & \vdots & \vdots & & \vdots - \\ - \textcolor{blue}{\Xi_{n-2} \Xi_{n-1}} & \Xi_{n-2} \Xi_{n-1} & \Xi_{n-2} \Xi_{n-1} & & \textcolor{blue}{\Xi_{n-1} \Xi_{n-2}} - \end{array} -\end{gather*} -$$ - - -The "[path swaps]()" shown commute only the adjacent elements. -By contrast, the permutation (0 2) commutes *Ξ*~0~ past both *Ξ*~1~ and *Ξ*~2~. -But since we already know *Ξ*~0~ and *Ξ*~1~ commute by the above list, - we learn at this step that *Ξ*~0~ and *Ξ*~2~ commute. -This can be repeated until we reach the permutation (0 *n*-1) to prove commutativity between all pairs. - - -### Matrix Fields? - -The above arguments tell us that if *p* is irreducible, we can take its companion matrix *C*~*p*~ - and work with its powers in the same way we would a typical root. -Irreducible polynomials cannot have a constant term 0, otherwise *x* could be factored out. -The constant term is equal to the determinant of the companion matrix (up to sign), - so *C*~*p*~ is invertible. -We get commutativity for free, since it follows from associativity - that all powers of *C*~*p*~ commute. - -This narrows the ring of matrices to a full-on field. -Importantly, it absolves us from the need to symbolically render elements using a power of the root. -Instead, they can be adjoined by going from scalars to matrices. -We can also find every element in the field arithmetically. -Starting with a root, every element, produce new elements taking its matrix powers. -Then, scalar-multiply them and add them to elements of the field which are already known. -For finite fields, we can repeat this process with the new matrices - until we have all *p*^*d*^ elements. + In fact, we can usually do a little better, as we'll see. GF(8) @@ -392,77 +199,64 @@ This is all rather abstract, so let's look at an example before we proceed any f The next smallest field of characteristic 2 is GF(8). We can construct this field from the two irreducible polynomials of degree 3 over GF(2): -$$ -\begin{gather*} - q(x) = x^3 + x + 1 = 1011_x \sim {}_2 11 \qquad - C_q = \left( \begin{matrix} - 0 & 1 & 0 \\ - 0 & 0 & 1 \\ - 1 & 1 & 0 - \end{matrix} \right) \mod 2 - \\ \\ - r(x) = x^3 + x^2 + 1 =1101_x \sim {}_2 13 \qquad - C_r = \left( \begin{matrix} - 0 & 1 & 0 \\ - 0 & 0 & 1 \\ - 1 & 0 & 1 - \end{matrix} \right) \mod 2 -\end{gather*} -$$ + +```{haskell} +--| code-fold: true + +irrsOfDegree d n = map fst $ takeWhile ((==d) . snd) $ dropWhile ((/=d) . snd) $ + map ((,) <*> (+(-1)) . length . coeffs) $ irreducibles n + +-- First (and only) two polynomials of degree 3 +qPoly:rPoly:_ = irrsOfDegree 3 2 + + +-- Display a polynomial in positional notation, base x +texPolyAsPositional (Poly xs) = (++ "_{x}") $ + reverse xs >>= (\x -> if x < 0 then "\\bar{" ++ show (-x) ++ "}" else show x) +-- Display a polynomial as its encoding in base b +texPolyAsNumeric b p = (("{}_{" ++ show b ++ "} ") ++) $ show $ evalPoly b p + +-- Display a polynomial and equivalent notations +texPolyPosNum b p = texifyPoly p ++ " = " ++ + texPolyAsPositional p ++ "\\sim" ++ + texPolyAsNumeric b p + +-- Display a polynomial and its companion matrix +texPolyAndMatrix b p name = name ++ "(x) = " ++ texPolyPosNum b p ++ + "\\qquad C_{" ++ name ++ "} = " ++ texifyMatrix ((`mod` b) <$> companion p) + +markdown $ "$$\\begin{gather*}" ++ + texPolyAndMatrix 2 qPoly "q" ++ + "\\\\" ++ texPolyAndMatrix 2 rPoly "r" ++ + "\\end{gather*}$$" +``` Notice how the bit strings for either of these polynomials is the other, reversed. Arbitrarily, let's work with C~r~. -The powers of this matrix, mod 2, are as follows: +The powers of this matrix (mod 2) are as follows: -$$ -\begin{gather*} - (C_r)^1 = \left( \begin{matrix} - 0 & 1 & 0 \\ - 0 & 0 & 1 \\ - 1 & 0 & 1 - \end{matrix} \right) - \quad - (C_r)^2 = \left( \begin{matrix} - 0 & 0 & 1 \\ - 1 & 0 & 1 \\ - 1 & 1 & 1 - \end{matrix} \right) - \quad - (C_r)^3 = \left( \begin{matrix} - 1 & 0 & 1 \\ - 1 & 1 & 1 \\ - 1 & 1 & 0 - \end{matrix} \right) - \\ - (C_r)^4 = \left( \begin{matrix} - 1 & 1 & 1 \\ - 1 & 1 & 0 \\ - 0 & 1 & 1 - \end{matrix} \right) \quad - (C_r)^5 = \left( \begin{matrix} - 1 & 1 & 0 \\ - 0 & 1 & 1 \\ - 1 & 0 & 0 - \end{matrix} \right) \quad - (C_r)^6 = \left( \begin{matrix} - 0 & 1 & 1 \\ - 1 & 0 & 0 \\ - 0 & 1 & 0 - \end{matrix} \right) - \\ - (C_r)^7 = \left( \begin{matrix} - 1 & 0 & 0 \\ - 0 & 1 & 0 \\ - 0 & 0 & 1 - \end{matrix} \right) = I - = (C_r)^0 \quad - (C_r)^8 = \left( \begin{matrix} - 0 & 1 & 0 \\ - 0 & 0 & 1 \\ - 1 & 0 & 1 - \end{matrix} \right) = C_r -\end{gather*} -$$ +```{haskell} +--| code-fold: true + +-- Compute all powers of a matrix, starting with the first +matrixPowersMod b mat = iterate (((`mod` b) <$>) . (mat*)) mat + +-- Show a matrix power +texMatrixPower n b mat name = "(" ++ name ++ ")^{" ++ show n ++ "} = " ++ + texifyMatrix ((`mod` b) <$> mat) + +-- Show all matrix powers +texPows b mat name = [texMatrixPower n b matPow name | + (n, matPow) <- zip [1..] (matrixPowersMod b mat)] + +let pows = texPows 2 (companion rPoly) "C_r" in + markdown $ "$$\\begin{gather*}" ++ + concat (take 3 pows) ++ "\\\\" ++ + concat (take 3 $ drop 3 pows) ++ "\\\\" ++ + (pows !! (7 - 1)) ++ " = I = (C_r)^0 \\quad" ++ + (pows !! (8 - 1)) ++ " = C_r" ++ + "\\end{gather*}$$" +``` As a reminder, these matrices are taken mod 2, so the elements can only be 0 or 1. The seventh power of *C*~*r*~ is just the identity matrix, @@ -473,7 +267,7 @@ Along with the zero matrix, this fully characterizes GF(8). If we picked *C*~*q*~ instead, we would have gotten different matrices. I'll omit writing them here, but we get the same result: *C*~*q*~ is also cyclic of order 7. Since every nonzero element of the field can be written as a power of the root, - the root (and the polynomial) is termed + the root (as well as the polynomial) is termed [primitive](https://en.wikipedia.org/wiki/Primitive_polynomial_%28field_theory%29). @@ -485,52 +279,37 @@ One of the things in which we know we should be interested is the characteristic since it is central to the definition and behavior of the matrices. Let's focus only on the characteristic polynomial for successive powers of *C*~*r*~ -$$ -\begin{gather*} - C_r = \left( \begin{matrix} - 0 & 1 & 0 \\ - 0 & 0 & 1 \\ - 1 & 0 & 1 - \end{matrix} \right) \mod 2 - \\ ~ \\ - \begin{array}{} - \text{charpoly}((C_r)^1) - &=& \color{blue} x^3 + x^2 + 1 - &=& \color{blue} 1101_x \sim {}_2 13 = r - \\ - \text{charpoly}((C_r)^2) - &=& \color{blue} x^3 + x^2 + 1 - &=& \color{blue} 1101_x \sim {}_2 13 = r - \\ - \text{charpoly}((C_r)^3) - &=& \color{red} x^3 + x + 1 - &=& \color{red} 1011_x \sim {}_2 11 = q - \\ - \text{charpoly}((C_r)^4) - &=& \color{blue} x^3 + x^2 + 1 - &=& \color{blue} 1101_x \sim {}_2 13 = r - \\ - \text{charpoly}((C_r)^5) - &=& \color{red} x^3 + x + 1 - &=& \color{red} 1011_x \sim {}_2 11 = q - \\ - \text{charpoly}((C_r)^6) - &=& \color{red} x^3 + x + 1 - &=& \color{red} 1011_x \sim {}_2 11 = q - \\ - \text{charpoly}((C_r)^7) - &=& x^3 + x^2 + x + 1 - &=& 1111_x \sim {}_2 15 = (x+1)^3 - \end{array} -\end{gather*} -$$ +```{haskell} +--| code-fold: true + +-- Create sequence of charpolys (mod b) from the powers of its companion matrix +charPolyPows b = map (((`mod` b) <$>) . charpoly) . matrixPowersMod b . companion + +-- Display row of charpoly array +texCharPolyRow b poly name extra = "\\text{charpoly}(" ++ name ++ ")" ++ "&=&" ++ + fst (extra poly) ++ texifyPoly poly ++ "&=&" ++ + fst (extra poly) ++ texPolyAsPositional poly ++ "\\sim" ++ + texPolyAsNumeric b poly ++ " = " ++ snd (extra poly) + +markdown $ "$$\\begin{array}{}" ++ + intercalate " \\\\ " [ + texCharPolyRow 2 mat ("(C_r)^{" ++ show n ++ "}") (\x -> + if x == rPoly then ("\\color{blue}", "r") + else if x == qPoly then ("\\color{red}", "q") + else ("", "(x + 1)^3") + ) + | (n, mat) <- zip [1..7] (charPolyPows 2 rPoly) + ] ++ + "\\end{array}$$" +``` Somehow, even though we start with one characteristic polynomial, the other manages to work its way in here. Both polynomials are of degree 3 and have 3 matrix roots (distinguished in red and blue). If we chose to use *C*~*q*~, we'd actually get the same sequence backwards (starting with ~2~11). It's beneficial to remember that 6, 5, and 3 can also be written as 7 - 1, 7 - 2, and 7 - 4. -This makes it clear that the powers of 2 (the field characteristic) less than the 8 (the order of the field) play a role with respect to both the initial and terminal items. +This makes it clear that the powers of 2 (the field characteristic) less than the 8 (the order of the field) + play a role with respect to both the initial and terminal items. ### Factoring @@ -538,39 +317,47 @@ This makes it clear that the powers of 2 (the field characteristic) less than th Intuitively, you may try using the roots to factor the matrix into powers of *C*~*r*~. This turns out to work: -$$ -\begin{gather*} - \hat R(X) \overset?= (X - C_r)(X - (C_r)^2)(X - (C_r)^4) - \\ - \hat Q(X) \overset?= (X - (C_r)^3)(X - (C_r)^5)(X - (C_r)^6) - \\ \\ - \textcolor{red}{ \sigma_1([(C_r)^i]_{i \in [1,2,4]}) } = C_r + (C_r)^2 + (C_r)^4 = \textcolor{red}I - \\ - \textcolor{brown}{ \sigma_1([(C_r)^i]_{i \in [3,5,6]}) } = (C_r)^3 + (C_r)^5 + (C_r)^6 = \textcolor{brown}0 - \\ \\ - \begin{align*} - \color{blue} \sigma_2([(C_r)^i]_{i \in [1,2,4]}) - &= (C_r)(C_r)^2 + (C_r)(C_r)^4 + (C_r)^2(C_r)^4 - \\ - &= (C_r)^3 + (C_r)^5 + (C_r)^6 = \color{blue}0 - \\ - \color{cyan} \sigma_2([(C_r)^i]_{i \in [3,5,6]}) - &= (C_r)^3(C_r)^5 + (C_r)^3(C_r)^6 + (C_r)^5(C_r)^6 - \\ - &= (C_r)^8 + (C_r)^9 + (C_r)^{11} - \\ - &= (C_r)^1 + (C_r)^2 + (C_r)^4 = \color{cyan} I - \end{align*} - \\ \\ - \textcolor{green}{ \sigma_3([(C_r)^i]_{i \in [1,2,4]}) } = (C_r)(C_r)^2(C_r)^4 = \textcolor{green}I - \\ - \textcolor{lightgreen}{ \sigma_3([(C_r)^i]_{i \in [3,5,6]}) }= (C_r)^3(C_r)^5(C_r)^6 = \textcolor{lightgreen}I - \\ \\ - \hat R(X) = X^3 + \textcolor{red}IX^2 + \textcolor{blue}0X + \textcolor{green}I - \\ - \hat Q(X) = X^3 + \textcolor{brown}0X^2 + \textcolor{cyan}IX + \textcolor{lightgreen}I -\end{gather*} -$$ +```{haskell} +--| code-fold: true + +-- Convert a list of roots to a polynomial with those as its roots +rootsToPoly :: (Num a, Eq a) => [a] -> Polynomial a +rootsToPoly xs = Poly $ reverse $ zipWith (*) (cycle [1,-1]) vieta where + -- Group by degree of subsequence + elemSyms = groupBy ((==) `on` length) . sortOn length . subsequences + -- Vieta's formulas over xs + vieta = map (sum . map product) $ elemSyms xs + +-- Make a polynomial from the powers of the companion matrix of p (mod b) +companionPowerPoly b p = fmap (fmap (`mod` b)) . rootsToPoly . + map ((matrixPowersMod b (companion p) !!) . (+(-1))) + + +-- Show a polynomial over matrices +showPolyMat :: (Show a, Num a, Eq a) => Polynomial (Matrix a) -> String +showPolyMat = intercalate " + " . showCoeffs where + showCoeffs = zipWith showCoeff [0..] . map showMatrix . coeffs + -- Show the indeterminate as "X" + showCoeff 0 x = x + showCoeff 1 x = x ++ "X" + showCoeff n x = x ++ "X^{" ++ show n ++ "}" + -- Show identity matrices (but not their multiples) as "I" + showMatrix x + | x `elem` [eye 1, eye $ mSize x] = "I" + | x `elem` [zero 1, zero $ mSize x] = "0" + | otherwise = texifyMatrix x + mSize = (+1) . snd . snd . bounds . unMat + +markdown $ "$$\\begin{align*}" ++ + "\\hat{R}(X) &= (X - (C_r)^1)(X - (C_r)^2)(X - (C_r)^4)" ++ + " \\\\ " ++ + " &= " ++ showPolyMat (companionPowerPoly 2 rPoly [1,2,4]) ++ + " \\\\[10pt] " ++ + "\\hat{Q}(X) &= (X - (C_r)^3)(X - (C_r)^5)(X - (C_r)^6)" ++ + " \\\\ " ++ + " &= " ++ showPolyMat (companionPowerPoly 2 rPoly [3,5,6]) ++ + "\\end{align*}$$" +``` We could have factored our polynomials differently if we used *C*~*q*~ instead. However, the effect of splitting both polynomials into monomial factors is the same. @@ -581,57 +368,52 @@ GF(16) GF(8) is simple to study, but too simple to study the sequence of characteristic polynomials alone. Let's widen our scope to GF(16). -There are three irreducible polynomials of degree 3 over GF(2). +There are three irreducible polynomials of degree 4 over GF(2). -$$ -\begin{gather*} - s(x) = x^4 + x + 1 = 10011_x \sim {}_2 19 \quad - C_s = \left( \begin{matrix} - 0 & 1 & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & 1 \\ - 1 & 1 & 0 & 0 - \end{matrix} \right) \mod 2 - \\ - t(x) = x^4 + x^3 + 1 = 11001_x \sim {}_2 25 \quad - C_t = \left( \begin{matrix} - 0 & 1 & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & 1 \\ - 1 & 0 & 0 & 1 - \end{matrix} \right) \mod 2 - \\ - u(x) = x^4 + x^3 + x^2 + x + 1 = 11111_x \sim {}_2 31 \quad - C_u = \left( \begin{matrix} - 0 & 1 & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & 1 \\ - 1 & 1 & 1 & 1 - \end{matrix} \right) \mod 2 -\end{gather*} -$$ +```{haskell} +--| code-fold: true + +-- First (and only) three polynomials of degree 4 +sPoly:tPoly:uPoly:_ = irrsOfDegree 4 2 + +markdown $ "$$\\begin{gather*}" ++ + texPolyAndMatrix 2 sPoly "s" ++ "\\\\" ++ + texPolyAndMatrix 2 tPoly "t" ++ "\\\\" ++ + texPolyAndMatrix 2 uPoly "u" ++ + "\\end{gather*}$$" +``` Again, *s* and *t* form a pair under the reversal of their bit strings, while *u* is palindromic. Both *C*~*s*~ and *C*~*t*~ are cyclic of order 15, so *s* and *t* are primitive polynomials. Using *s* = ~2~19 to generate the field, the powers of its companion matrix *C*~*s*~ have the following characteristic polynomials: -```{python} -#| echo: false +```{haskell} +--| code-fold: true +--| classes: plain -from IPython.display import Markdown -from tabulate import tabulate +sPolyCharPowers = charPolyPows 2 sPoly -charpolys = [19, 19, 31, 19, 21, 31, 25, 19, 31, 21, 25, 31, 25, 25, 17] -charpolyformat = lambda x: f"~2~{x}" +-- Horizontal table of entries +fromIndices ns = columns (\(_, f) r -> f r) (\(c, _) -> Headed c) $ + map (\i -> (show i, (!! i))) ns +fromIndices' = (singleton (Headed "m") head <>) . fromIndices -Markdown(tabulate( - [[ - "charpoly((*C*~*s*~)^*m*^)", - *[charpolyformat(charpoly) for charpoly in charpolys] - ]], - headers=["*m*", *[str(i + 1) for i in range(15)]], -)) +-- Symbolic representation of a power of a companion matrix (in Markdown) +compPowSymbolic "" m = "*f*((*C*)^*" ++ m ++ "*^)" +compPowSymbolic x m = "*f*((*C*~*" ++ x ++ "*~)^*" ++ m ++ "*^)" + +-- Spans of a given color +spanColor color = (("") ++) . (++ "") + +markdown $ markdownTable (fromIndices' [1..15]) [ + compPowSymbolic "s" "m": + map (( + \x -> if x == 19 then spanColor "blue" (show x) + else if x == 25 then spanColor "red" (show x) + else show x + ) . evalPoly 2) sPolyCharPowers + ] ``` The polynomial ~2~19 occurs at positions 1, 2, 4, and 8. @@ -651,30 +433,30 @@ It follows that the roots of *u* are cyclic of order 5, so this polynomial is ir Naturally, $\hat U(X)$ can be factored as powers of (*C*~*s*~)^3^. We can also factor it more naively as powers of *C*~*u*~. Either way, we get the same sequence. -:::: {layout-ncol = "2"} +:::: {layout-ncol="2" layout-valign="center"} ::: {} -```{python} -#| echo: false -upowers = [31, 31, 31, 31, 17] +```{haskell} +--| code-fold: true +--| classes: plain -Markdown(tabulate( - [[ - "charpoly((*C*~*s*~)^*3m*^)", - *[f"~2~{charpoly}" for charpoly in charpolys[2::3]] - ], [ - "charpoly((*C*~*u*~)^*m*^)", - *[f"~2~{upower}" for upower in upowers] - ]], - headers=["*m*", *[str(i + 1) for i in range(5)]], -)) +-- Get every entry of an (infinite) list which is a multiple of n +entriesEvery n = map head . unfoldr (Just . splitAt n) + +markdown $ markdownTable (fromIndices' [1..5]) [ + compPowSymbolic "s" "3m": + map (show . evalPoly 2) + (entriesEvery 3 $ drop 2 sPolyCharPowers), + compPowSymbolic "u" "m": + map (show . evalPoly 2) (charPolyPows 2 uPoly) + ] ``` Both of the matrices in column 5 happen to be the identity matrix. It follows that this root is only cyclic of order 5. -The polynomials ~2~19 and ~2~25 are reversals of one another and the sequences that their companion matrices +The polynomials ~2~19 and ~2~25 are reversals of one another, and the sequences that their companion matrices generate end one with another -- in this regard, they are dual. -However, ~2~31 = 11111~*x*~ is a palindrome and its sequence ends where it begins, so it is self-dual. +However, ${}_2 31 = 11111_x$ is a palindrome and its sequence ends where it begins, so it is self-dual. ::: ::: {width = "33%"} @@ -724,20 +506,24 @@ $$ ### Non-irreducible -In addition to the three irreducibles, a fourth polynomial, ~2~21 = 10101~*x*~, +In addition to the three irreducibles, a fourth polynomial, ${}_2 21 \sim 10101_x$, also appears in the sequence on entries 5 and 10 -- multiples of 5, which is also a factor of 15. Like ~2~31, this polynomial is palindromic. This polynomial is *not* irreducible mod 2, and factors as: -$$ -\begin{gather*} - {}_2 21 \sim 10101_x = x^4 + x^2 + 1 = (x^2 + x + 1)^2 \mod 2 - \\[10pt] - (X - (C_s)^5)(X - (C_s)^{10}) = X^2 + ((C_s)^5 + (C_s)^{10})X + (C_s)^{15} - \\ - = X^2 + IX + I -\end{gather*} -$$ +```{haskell} +--| code-fold: true + +poly21 = asPoly 2 21 + +markdown $ "$$\\begin{gather*}" ++ + texPolyAsPositional poly21 ++ " = " ++ texifyPoly poly21 ++ + " = \\left( 1 + x + x^2 \\right)^2 \\mod 2" ++ + " \\\\[10pt] " ++ + "(X - (C_s)^5)(X - (C_s)^{10}) = " ++ + showPolyMat (companionPowerPoly 2 sPoly [5,10]) ++ + "\\end{gather*}$$" +``` Just like how the fields we construct are powers of a prime, this extra element is a power of a smaller irreducible. @@ -747,19 +533,38 @@ Something a little more surprising is that the companion matrix is cyclic of deg rather than of degree 3 like the matrices encountered in GF(8). The powers of its companion matrix are: - +```{haskell} +--| code-fold: true +--| classes: plain + +companion21Pows = matrixPowersMod 2 (companion poly21) + +markdown $ markdownTable (fromIndices' [1..6]) [ + compPowSymbolic "s" "5m": + map (show . evalPoly 2) (entriesEvery 5 $ drop 4 sPolyCharPowers), + compPowSymbolic "21" "m": + map (\x -> let + p = (`mod` 2) <$> charpoly x + p' = evalPoly 2 p + comp21 = head companion21Pows + in + -- x shares its characteristic polynomial with the identity + if p' == 17 then + show p' ++ + (if x == eye 4 then " (identity)" else " (*Not* the identity)") + -- x is either the companion matrix of the polynomial 21 or its inverse + else if x == comp21 || + eye 4 == ((`mod` 2) <$> x * comp21) then + spanColor "red" $ show p' + else spanColor "blue" $ show p') + companion21Pows + ] +``` We can think of the repeated sequence as ensuring that there are enough roots of ~2~21. The Fundamental Theorem of Algebra states that there must be 4 roots. -For *numbers*, we'd allow duplicate roots with multiplicities greater than 1, but the matrix roots are all distinct. +For *numbers*, we'd allow duplicate roots with multiplicities greater than 1, + but the matrix roots are all distinct. Basic group theory tells us that as a cyclic group, the matrix's first and fifth powers (in red) are pairs of inverses. @@ -773,50 +578,45 @@ GF(32) GF(32) turns out to be special. There are six irreducible polynomials of degree 5 over GF(2). -Picking one of them at random, ~2~37, and looking at the polynomial sequence it generates, we see: +Picking the "smallest" at random, ~2~37, and looking at the polynomial sequence it generates, we see: -```{python} -#| echo: false -gf32powers = [ - 37, 37, 61, 37, 55, 61, 47, 37, 55, 55, 59, 61, 59, 47, 41, - 37, 61, 55, 47, 55, 59, 59, 41, 61, 47, 59, 41, 47, 41, 41, 51, -] -gf32colors = { - 37: "red", - 61: "blue", - 55: "yellow", - 47: "orange", - 59: "purple", - 41: "green", -} -gf32format = lambda x: f"~2~{x}" -Markdown(tabulate( - [[ - "charpoly((*C*~*u*~)^*m*^)", - "-", - *[gf32format(gf32power) for gf32power in gf32powers[:15]] - ]], - headers=["*m*", *[str(i) for i in range(16 + 1)]], -)) -``` -```{python} -#| echo: false -Markdown(tabulate( - [[ - "charpoly((*C*~*u*~)^*m*^)", - *[gf32format(gf32power) for gf32power in gf32powers[:-17:-1]] - ]], - headers=["*m*", *[str(i) for i in reversed(range(16, 32))]], -)) +```{haskell} +--| code-fold: true +--| classes: plain + +-- Get all degree 5 polynomials over GF(2) +deg5Char2Polys = irrsOfDegree 5 2 +leastDeg5Char2Poly = head deg5Char2Polys + +colorByEval b ps x = (maybe show (. show) . getColor <*> id) $ evalPoly b x where + getColor = flip lookup $ map (fmap spanColor) ps + +colorDeg5Char2 = colorByEval 2 [ + (37, "red"), + (47, "orange"), + (55, "yellow"), + (41, "green"), + (61, "blue"), + (59, "purple") + ] + +markdown $ markdownTable (fromIndices' [1..16]) [ + compPowSymbolic "" "m": + map colorDeg5Char2 (charPolyPows 2 leastDeg5Char2Poly) + ] +markdown $ markdownTable (fromIndices' [17..31]) [ + compPowSymbolic "" "m": + map colorDeg5Char2 (charPolyPows 2 leastDeg5Char2Poly) + ] ``` 31 is prime, so we don't have any sub-patterns that appear on multiples of factors. In fact, all six irreducible polynomials are present in this table. The pairs in complementary colors form pairs under reversing the polynomials: - ~2~37 and ~2~41, - ~2~61 and ~2~47, - and ~2~55 and ~2~59. + ~2~37 and ~2~41, + ~2~61 and ~2~47, + and ~2~55 and ~2~59. Since their roots have order 31, these polynomials are actually the distinct factors of *x*^31^ - 1 mod 2: @@ -869,29 +669,31 @@ We've only been looking at fields of characteristic 2, where the meaning of Let's look at an example over characteristic 3. One primitive of degree 2 is ~3~14, which gives rise to the following sequence over GF(9): -```{python} -#| echo: false -gf9powers = [14, 10, 14, 16, 17, 10, 17, 13] -gf9format = lambda x: f"~3~{x}" +```{haskell} +--| code-fold: true +--| classes: plain -Markdown(tabulate( - [[ - "charpoly((*C*~*14*~)^*m*^)", - *[gf9format(gf9power) for gf9power in gf9powers] - ]], - headers=["*m*", *[str(i + 1) for i in range(8)]], -)) +-- Get all degree 2 polynomials over GF(3) +deg2Char3Polys = map fst $ takeWhile ((==3) . snd) $ dropWhile ((/=3) . snd) $ + map ((,) <*> length . coeffs) $ irreducibles 3 +leastDeg2Char3Poly = deg2Char3Polys !! 1 + +colorDeg3Char3 = colorByEval 3 [(14, "red"), (17, "blue")] + +markdown $ markdownTable (fromIndices'[1..8]) [ + "":map colorDeg3Char3 (charPolyPows 3 leastDeg2Char3Poly) + ] ``` -The table suggests that ~3~14 = 112~*x*~ = *x*^2^ + *x* + 2 and ~3~17 = 122~*x*~ = *x*^2^ + 2*x* + 2 +The table suggests that ${}_3 14 = 112_x = x^2 + x + 2$ and ${}_3 17 = 122_x = x^2 + 2x + 2$ are reversals of one another. More naturally, you'd think that 112~*x*~ reversed is 211~*x*~. But remember that we prefer to work with monic polynomials. By multiplying the polynomial by the multiplicative inverse of the leading coefficient (in this case, 2), - we get 422~*x*~ ≡ 122~*x*~ mod 3. + we get $422_x \equiv 122_x \mod 3$. This is a rule that applies over larger characteristics in general. -Note that ~3~16 is 121~*x*~ = *x*^2^ + 2x + 1 and ~3~13 = 111~*x*~ = *x*^2^ + x + 1 = *x*^2^ - 2x + 1, +Note that ${}_3 16 \sim 121_x = x^2 + 2x + 1$ and ${}_3 13 \sim 111_x = x^2 + x + 1 = x^2 - 2x + 1$, both of which have factors over GF(3). @@ -921,37 +723,81 @@ Since all of the graphs share the identity node as a feature -- a node with incoming edges from every other node -- its convenient to omit it. Here are a few more of these graphs after doing so, over fields of other characteristics: - ::: {layout="[[1,1], [1,1], [1,1,1]]"} -![ - GF(9) -]() +```{haskell} +--| code-fold: true +--| fig-cap: "GF(9)" + +-- Convert a polynomial to the integer representing it in characteristic n +asPolyNum n = evalPoly n . fmap (`mod` n) + +irreducibleGraph d n = concatMap (\(x:xs) -> map (x,) xs) polyKinClasses where + -- All irreducible polynomials of degree d in characteristic n + irrsOfDegree' = irrsOfDegree d n + -- Get "kin" polynomials as integers -- all those who appear as characteristic + -- polynomials in the powers of its companion matrix + getKinPolys = map (asPolyNum n . charpoly) . matrixPowersMod n . companion + -- Kin classes corresponding to each irreducible polynomial, + -- which is the first entry + polyKinClasses = map (nub . take (n^d) . getKinPolys) irrsOfDegree' + +-- Characteristic polynomial of the identity matrix +eyePoly d n = asPolyNum n $ charpoly $ eye d +-- Remove edges directed toward the characteristic polynomial of the identity +irreducibleGraphNoEye d n = filter ((/=eyePoly d n) . snd) $ irreducibleGraph d n + +-- Only plot the graph for GF(9), since the others take too long to render +plotDigraph $ irreducibleGraphNoEye 2 3 +``` ![ GF(25) -]() +](./irreducibles-graphs/irred_25.png) ![ GF(49) -]() +](./irreducibles-graphs/irred_49.png) ![ GF(121) -]() +](./irreducibles-graphs/irred_121.png) ![ GF(27) -]() +](./irreducibles-graphs/irred_27.png) ![ GF(125) -]() +](./irreducibles-graphs/irred_125.png) ![ GF(343) -]() +](./irreducibles-graphs/irred_343.png) ::: + +```{haskell} +--| eval: false +--| echo: false +--| layout: [[1,1], [1,1], [1,1,1]] +--| fig-cap: +--| - GF(9) +--| - GF(25) +--| - GF(49) +--| - GF(121) +--| - GF(27) +--| - GF(125) +--| - GF(343) + +plotDigraph $ irreducibleGraphNoEye 2 3 +plotDigraph $ irreducibleGraphNoEye 2 5 +plotDigraph $ irreducibleGraphNoEye 2 7 +plotDigraph $ irreducibleGraphNoEye 2 11 +plotDigraph $ irreducibleGraphNoEye 3 3 +plotDigraph $ irreducibleGraphNoEye 3 5 +plotDigraph $ irreducibleGraphNoEye 3 7 +``` + ### Spectra @@ -963,19 +809,86 @@ It turns out that a removing a fully-connected node (like the one for the identi has a simple effect on characteristic polynomial of a graph: it just removes a factor of *x*. Here are a few of the (identity-reduced) spectra, arranged into a table. -| Characteristic | Order | Spectrum | Remark | -|----------------|-------|--------------------------------------|--------------------------| -| 2 | 4 | 0 | | -| | 8 | -1, 1 | Mersenne | -| | 16 | 0^2^, -1, 1 | | -| | 32 | -1^5^, 5 | Mersenne | -| 3 | 9 | 0^2^, -1, 1 | | -| | 27 | 0, -1^6^, 3^2^ | Pseudo-Mersenne? | -| 5 | 25 | 0^3^, -1^6^, 1^3^, 3 | | -| | 125 | 0, -1^38^, 1, 9^2^, 19 | Prime power in spectrum | -| 7 | 49 | 0^2, -1^17^, 1^4^, 3^2^, 7 | | -| | 343 | 0, -1^106^, 1^4^, 5^2^, 11^2^, 35^2^ | Composite in spectrum | -| 11 | 121 | 0^4^, -1^49^, 1^2^, 3^6^, 7^2^, 15 | Composite in spectrum | +```{haskell} +--| code-fold: true +--| classes: plain + +-- Not technically correct, but enough for this example +edgesToAdjacency [] = toMatrix [[0]] +edgesToAdjacency es = Mat asArray where + -- Vertices from the edge list + vs = nub $ es >>= (\(x,y) -> [x,y]) + -- Lookup table for new vertices + vs' = zip vs [0..] + -- Largest vertex index for array bounds + b = maximum $ map snd vs' + -- Lookup function for new edge reindexing + lookupVs = fromJust . flip lookup vs' + -- List of reindexed edges + reindexed = map (bimap lookupVs lookupVs) es + -- Use a list of 2-tuples to set addresses in a matrix to 1 + asArray = listArray ((0,0),(b,b)) (repeat 0) // map (, 1) reindexed + +-- Find roots of `p` by trial dividing the entries of `xs` +findRootsFrom xs p = fmap head $ partitionEithers $ recurse p xs where + -- We only need to test roots we haven't failed to divide + tails [] = [] + tails x@(_:xs) = x:tails xs + -- Try dividing `p` by every remaining 'integer' root from xs + trialDivisions p xs = map (\x -> (x, p `synthDiv` Poly [-head x, 1])) $ tails xs + -- Find the first root which has a zero remainder, or Nothing if none exists + firstRoot p xs = listToMaybe $ dropWhile ((/= 0) . snd . snd) $ + trialDivisions p xs + -- We either found a root (r) and need to recurse with the quotient (q) + -- Or we couldn't find a root, and terminate with the number of unfound roots + recurse p xs = case firstRoot p xs of + (Just (next@(r:_), (q,_))) -> Left r:recurse q next + _ -> [Right $ length (coeffs p) - 1] + +-- Show the spectrum +showSpectrum (xs, y) = intercalate ", " showMults ++ showMissing where + showMults = map showMult (rle Nothing xs) + -- Markdown notation for a root x repeated y times + showMult (x,y) = show x ++ "^" ++ show y ++ "^" + showMissing = if y == 0 then "" else " " ++ show y ++ " other roots" + -- Run-length encode a list to a list containing (original entry, count) + rle Nothing [] = [] + rle (Just x) [] = [x] + rle Nothing (x:xs) = rle (Just (x, 1)) xs + rle (Just (y, c)) (x:xs) + | x == y = rle (Just (y, c+1)) xs + | otherwise = (y, c):rle (Just (x, 1)) xs + +-- Characteristic, degree, remark +data CharGraphRow = CGR { + cgrCharacteristic :: Int, + cgrDegree :: Int, + cgrRemark :: String + } + +charGraphTable = columns (\(_, f) r -> f r) (\(c, _) -> Headed c) [ + ("Characteristic", \(CGR n d _) -> if d == 2 then show n else ""), + ("Order", show . \(CGR n d _) -> n^d), + ("Spectrum", \(CGR n d _) -> showSpectrum $ findRootsFrom [-1..35] $ + charpoly $ fmap fromIntegral $ edgesToAdjacency $ + irreducibleGraphNoEye d n), + ("Remark", cgrRemark) + ] + +markdown $ markdownTable charGraphTable [ + CGR 2 2 "", + CGR 2 3 "Mersenne", + CGR 2 4 "", + CGR 2 5 "Mersenne", + CGR 3 2 "", + CGR 3 3 "Pseudo-Mersenne?", + CGR 5 2 "", + CGR 5 3 "Prime power in spectrum", + CGR 7 2 "", + CGR 7 3 "Composite in spectrum", + CGR 11 2 "Composite in spectrum" + ] +``` Incredibly, all spectra shown are composed exclusively of integers, and thus, each of these graphs are integral graphs. @@ -987,7 +900,7 @@ From what I was able to tell, the following subgraphs were *also* integral over - the induced subgraph of vertices corresponding to non-primitives - the complement of the previous graph with respect to the whole graph -- the induced subgraph of vertices corresponding only to irreducibles +- the induced subgraph of vertices corresponding only to irreducibles Unfortunately, proving any such relationship is out of the scope of this post (and my abilities). diff --git a/posts/finite-field/2/irreducibles-graphs/irred_121.png b/posts/finite-field/2/irreducibles-graphs/irred_121.png new file mode 100644 index 0000000..94dc297 --- /dev/null +++ b/posts/finite-field/2/irreducibles-graphs/irred_121.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c816c320b07e4c37c133371acdf677d751cc460e2a19e4216ca70c290705f51 +size 74139 diff --git a/posts/finite-field/2/irreducibles-graphs/irred_125.png b/posts/finite-field/2/irreducibles-graphs/irred_125.png new file mode 100644 index 0000000..bfc9102 --- /dev/null +++ b/posts/finite-field/2/irreducibles-graphs/irred_125.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d18aba40a838e130aa50844d20c79382ab0dec1d65fb76b51764512f9efdf53 +size 22300 diff --git a/posts/finite-field/2/irreducibles-graphs/irred_25.png b/posts/finite-field/2/irreducibles-graphs/irred_25.png new file mode 100644 index 0000000..21e6848 --- /dev/null +++ b/posts/finite-field/2/irreducibles-graphs/irred_25.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c7a2d012c7f0acf05519bc237a11304f96a4584cfcee3c06290b451acf9fc44 +size 45414 diff --git a/posts/finite-field/2/irreducibles-graphs/irred_27.png b/posts/finite-field/2/irreducibles-graphs/irred_27.png new file mode 100644 index 0000000..5db926e --- /dev/null +++ b/posts/finite-field/2/irreducibles-graphs/irred_27.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b647d1c7ecc8297c4fea2489da7c4a6c8f49cc72a133e37a173c72bb9d616a2 +size 43835 diff --git a/posts/finite-field/2/irreducibles-graphs/irred_343.png b/posts/finite-field/2/irreducibles-graphs/irred_343.png new file mode 100644 index 0000000..34a9b0d --- /dev/null +++ b/posts/finite-field/2/irreducibles-graphs/irred_343.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ab8da090d422602801972315c296ccc6e8d79275bc7eab60cf21b6e157daa98 +size 50288 diff --git a/posts/finite-field/2/irreducibles-graphs/irred_49.png b/posts/finite-field/2/irreducibles-graphs/irred_49.png new file mode 100644 index 0000000..c35e4dc --- /dev/null +++ b/posts/finite-field/2/irreducibles-graphs/irred_49.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a18ff21be7d6bea0d75d1db62a35cbb19e4331903e0300cfa5215998d00d18c2 +size 48999