-- Copyright (c) David Amos, 2008-2009. All rights reserved. -- |Constructions of the finite geometries AG(n,Fq) and PG(n,Fq), their points, lines and flats, -- together with the incidence graphs between points and lines. module Math.Combinatorics.FiniteGeometry where import Data.List as L import qualified Data.Set as S import Math.Common.ListSet (toListSet) import Math.Algebra.Field.Base import Math.Algebra.Field.Extension hiding ( (<+>) ) -- , (*>) ) import Math.Algebra.LinearAlgebra -- hiding ( det ) import Math.Combinatorics.Graph import Math.Combinatorics.GraphAuts -- for use in GHCi import Math.Algebra.Group.PermutationGroup -- for use in GHCi import Math.Algebra.Group.SchreierSims as SS -- for use in GHCi -- |ptsAG n fq returns the points of the affine geometry AG(n,Fq), where fq are the elements of Fq ptsAG :: (FiniteField a) => Int -> [a] -> [[a]] ptsAG 0 fq = [[]] ptsAG n fq = [x:xs | x <- fq, xs <- ptsAG (n-1) fq] -- |ptsPG n fq returns the points of the projective geometry PG(n,Fq), where fq are the elements of Fq ptsPG :: (FiniteField a) => Int -> [a] -> [[a]] ptsPG 0 _ = [[1]] ptsPG n fq = map (0:) (ptsPG (n-1) fq) ++ map (1:) (ptsAG n fq) -- "projective normal form" pnf (0:xs) = 0 : pnf xs pnf (1:xs) = 1 : xs pnf (x:xs) = 1 : map (* x') xs where x' = recip x ispnf (0:xs) = ispnf xs ispnf (1:xs) = True ispnf _ = False -- closure of points in AG(n,Fq) -- given p1, .., pk, we're looking for all a1 p1 + ... + ak pk, s.t. a1 + ... + ak = 1 -- if m is the matrix with p1, .., pk as rows, and vs are the vectors [a1, .., ak] -- then this is the same as [v <*>> m | v <- vs] == [m' <<*> v | v <- vs] closureAG ps = let vs = [ (1 - sum xs) : xs | xs <- ptsAG (k-1) fq ] -- k-vectors over fq whose sum is 1 in toListSet [m' <<*> v | v <- vs] where k = length ps -- the dimension of the flat (assuming ps are independent) m' = L.transpose ps fq = eltsFq undefined -- toListSet call sorts the result, and also removes duplicates in case the points weren't independent {- closureAG ps = let vs = [ (1 - sum xs) : xs | xs <- ptsAG (k-1) fq ] -- k-vectors over fq whose sum is 1 in toListSet [foldl1 (<+>) $ zipWith (*>) v ps | v <- vs] where k = length ps -- the dimension of the flat fq = eltsFq undefined -} lineAG [p1,p2] = L.sort [ p1 <+> (c *> dp) | c <- fq ] where dp = p2 <-> p1 fq = eltsFq undefined -- closure of points in PG(n,Fq) -- take all linear combinations of the points (ie the subspace generated by the points, considered as points in Fq ^(n+1) ) -- then discard all which aren't in PNF (thus dropping back into PG(n,Fq)) closurePG ps = toListSet $ filter ispnf $ map (<*>> ps) $ ptsAG k fq where k = length ps fq = eltsFq undefined -- toListSet call sorts the result, and also removes duplicates in case the points weren't independent linePG [p1,p2] = toListSet $ filter ispnf [(a *> p1) <+> (b *> p2) | a <- fq, b <- fq] where fq = eltsFq undefined -- van Lint & Wilson, p325, 332 qtorial n q | n >= 0 = product [(q^i - 1) `div` (q-1) | i <- [1..n]] -- van Lint & Wilson, p326 qnomial n k q = (n `qtorial` q) `div` ( (k `qtorial` q) * ((n-k) `qtorial` q) ) -- Cameron, p129 numFlatsPG n q k = qnomial (n+1) (k+1) q -- because it's the number of subspaces in AG n+1 -- Cameron, p137 numFlatsAG n q k = q^(n-k) * qnomial n k q qtorials q = scanl (*) 1 [(q^i - 1) `div` (q-1) | i <- [1..]] qnomials q = iterate succ [1] where succ xs = L.zipWith3 (\l r c -> l+c*r) (0:xs) (xs++[0]) (iterate (*q) 1) -- succ xs = zipWith (+) (0:xs) $ zipWith (*) (xs++[0]) $ iterate (*q) 1 -- This amounts to saying -- [n+1,k]_q = [n,k-1]_q + q^k [n,k]_q -- Cameron, Combinatorics, p126 -- FLATS VIA REDUCED ROW ECHELON FORMS -- Suggested by Cameron p125 data ZeroOneStar = Zero | One | Star deriving (Eq) instance Show ZeroOneStar where show Zero = "0" show One = "1" show Star = "*" -- reduced row echelon forms rrefs n k = map (rref 1 1) (combinationsOf k [1..n]) where rref r c (x:xs) = if c == x then zipWith (:) (oneColumn r) (rref (r+1) (c+1) xs) else zipWith (:) (starColumn r) (rref r (c+1) (x:xs)) rref _ c [] = replicate k (replicate (n+1-c) Star) oneColumn r = replicate (r-1) Zero ++ One : replicate (k-r) Zero starColumn r = replicate (r-1) Star ++ replicate (k+1-r) Zero -- |flatsPG n fq k returns the k-flats in PG(n,Fq), where fq are the elements of Fq flatsPG :: (FiniteField a) => Int -> [a] -> Int -> [[[a]]] flatsPG n fq k = concatMap substStars $ rrefs (n+1) (k+1) where substStars (r:rs) = [r':rs' | r' <- substStars' r, rs' <- substStars rs] substStars [] = [[]] substStars' (Star:xs) = [x':xs' | x' <- fq, xs' <- substStars' xs] substStars' (Zero:xs) = map (0:) $ substStars' xs substStars' (One:xs) = map (1:) $ substStars' xs substStars' [] = [[]] -- Flats in AG(n,Fq) are just the flats in PG(n,Fq) which are not "at infinity" -- |flatsAG n fq k returns the k-flats in AG(n,Fq), where fq are the elements of Fq flatsAG :: (FiniteField a) => Int -> [a] -> Int -> [[[a]]] flatsAG n fq k = [map tail (r : map (r <+>) rs) | r:rs <- flatsPG n fq k, head r == 1] -- The head r == 1 condition is saying that we want points which are in the "finite" part of PG(n,Fq), not points at infinity -- The reason we add r to each of the rs is to bring them into the "finite" part -- (If you don't do this, it can lead to incorrect results, for example some of the flats having the same closure) -- |The lines (1-flats) in PG(n,fq) linesPG :: (FiniteField a) => Int -> [a] -> [[[a]]] linesPG n fq = flatsPG n fq 1 -- |The lines (1-flats) in AG(n,fq) linesAG :: (FiniteField a) => Int -> [a] -> [[[a]]] linesAG n fq = flatsAG n fq 1 -- almost certainly not as efficient as linesAG, because requires lineAG/closureAG call -- among all pairs of distinct points, select those which are the first two in the line they generate linesAG1 n fq = [ [x,y] | [x,y] <- combinationsOf 2 (ptsAG n fq), [x,y] == take 2 (lineAG [x,y]) ] -- the point of the condition at the end is to avoid listing the same line more than once -- almost certainly not as efficient as linesAG, because requires lineAG/closureAG call -- a line in AG(n,fq) is a translation (x) of a line through the origin (y) linesAG2 n fq = [ [x,z] | x <- ptsAG n fq, y <- ptsPG (n-1) fq, z <- [x <+> y], [x,z] == take 2 (closureAG [x,z]) ] -- INCIDENCE GRAPH -- |Incidence graph of PG(n,fq), considered as an incidence structure between points and lines incidenceGraphPG :: (Ord a, FiniteField a) => Int -> [a] -> Graph (Either [a] [[a]]) incidenceGraphPG n fq = G vs es where points = ptsPG n fq lines = linesPG n fq vs = L.sort $ map Left points ++ map Right lines es = L.sort [ [Left x, Right b] | b <- lines, x <- closurePG b] -- Could also consider incidence structure between points and planes, etc -- incidenceAuts (incidenceGraphPG n fq) == PGL(n,fq) * auts fq -- For example, incidenceAuts (incidenceGraphPG 2 f4) = -- PGL(3,f4) * auts f4 -- where PGL(3,f4)/PSL(3,f4) == f4* (multiplicative group of f4), -- and auts f4 == { 1, x -> x^2 } (the Frobenius aut) -- The full group is called PGammaL(3,f4) -- |Incidence graph of AG(n,fq), considered as an incidence structure between points and lines incidenceGraphAG :: (Ord a, FiniteField a) => Int -> [a] -> Graph (Either [a] [[a]]) incidenceGraphAG n fq = G vs es where points = ptsAG n fq lines = linesAG n fq vs = L.sort $ map Left points ++ map Right lines es = L.sort [ [Left x, Right b] | b <- lines, x <- closureAG b] -- incidenceAuts (incidenceGraphAG n fq) == Aff(n,fq) * auts fq -- where Aff(n,fq), the affine group, is the semi-direct product GL(n,fq) * (fq^n)+ -- where (fq^n)+ is the additive group of translations -- Each elt of Aff(n,fq) is of the form x -> ax + b, where a <- GL(n,fq), b <- (fq^n)+ orderGL n q = product [q^n - q^i | i <- [0..n-1] ] -- for the first row, we can choose any vector except zero, hence q^n-1 -- for the second row, we can choose any vector except a multiple of the first, hence q^n-q -- etc orderAff n q = q^n * orderGL n q orderPGL n q = orderGL n q `div` (q-1) -- NOTE: -- AG(n,F2) is degenerate: -- Every pair of points is a line, so it is the complete graph on 2^n points -- And as such has aut group S(2^n) -- Heawood graph = incidence graph of Fano plane heawood = to1n $ incidenceGraphPG 2 f2