{-# LANGUAGE TypeSynonymInstances,NoImplicitPrelude #-}
module E8 where
import qualified Algebra.Ring
import Control.Applicative ((<$>),(<*>))
import qualified Data.Vector as V
import Data.List (intercalate,nubBy)
import qualified Data.MemoCombinators as Memo
import Data.Ratio (Ratio,numerator,denominator,(%))
import qualified Data.Set as Set
import Data.Typeable (Typeable)
import Math.Combinatorics.Species (ksubsets,set,ofSize,enumerate,Set(getSet,Set),Prod(Prod))
import MyPrelude hiding (numerator,denominator,(%))
import qualified Prelude
import System.Environment (getArgs)
import qualified Algebra.Additive
-- Some types and helper functions for dealing with "vectors" (implemented as arrays of rational numbers).
type Coordinate
= Ratio Int
type Vector
= V.Vector Coordinate
-- Inner product.
inp :: Vector -> Vector -> Coordinate
inp a b = V.sum (V.zipWith (*) a b)
showVector :: Vector -> String
showVector = intercalate " " . map f . V.toList where
f x = case denominator x of
1 -> show (numerator x)
2 -> show (numerator x) ++ "/2"
_ -> "bad denominator"
half :: Coordinate
half = 1 % 2
-- Product of scalar with vector.
l :: Coordinate -> Vector -> Vector
l = V.map . (*)
instance Algebra.Additive.C Vector where
(+) = V.zipWith (+)
(-) = V.zipWith (-)
negate = l (-1)
zero = V.fromList [0,0,0,0,0,0,0,0]
-- Some data regarding E_8
delta :: (Eq a,Algebra.Ring.C b) => a -> a -> b
delta i j = if i == j then 1 else 0
-- 'e i' gives the i'th standard basis vector of R^8.
e :: Int -> Vector
e i = V.fromList $ map (delta i) [1 .. 8]
-- This is the usual integral basis of the lattice E_8.
basis :: [Vector]
basis =
[
l half $ (e 1 + e 8) - (sum $ map e [2 .. 7])
, e 1 + e 2
] ++ map (\ i -> e (i - 1) - e (i - 2)) [3 .. 8]
roots :: [Vector]
roots = d8 ++ x118 where
d8 = concatMap ((\ [a,b] -> [a + b,a - b,b - a,negate a - b]) . map e . getSet) $
enumerate (ksubsets 2) [1 .. 8]
x118 = map (\ (Prod (Set neg) (Set pos)) -> l half $ sum (map (negate . e) neg) + sum (map e pos)) $
enumerate ((set `ofSize` even) * set) [1 .. 8]
-- 'posRoots' contains exactly one of every pair (a,-a) of roots.
posRoots :: [Vector]
posRoots = nubBy (\ a b -> a == b || a == negate b) roots
-- Generate elements l of the E_8 lattice with the property that l^2 = 2 d.
-- We need only one element of each orbit under the action of the Weyl group.
-- In particular, we may assume that all coordinates but one (say, the first)
-- are nonnegative, and that the successive coordinates are nondecreasing.
-- We generate exactly one element of each H-orbit, where H is the subgroup of
-- permutations and even sign changes.
gen :: Int -> [Vector]
gen d = genInt d ++ genHalfInt d
genInt :: Int -> [Vector]
genInt d = map (V.fromList . map fromIntegral) $ go [] 0 where
-- Given the length of a partial vector, compute the maximal new coordinate
-- which does not increase the length of the vector beyond 2 d.
maxCoordinate :: Int -> Int
maxCoordinate s = floor (sqrt (fromIntegral $ dD - s) :: Double)
dD :: Int
dD = 2 * d
-- We maintain a list of coordinates chosen so far, every one together with
-- the sum of squares of the coordinates up to and including that coordinate.
--
-- The generated vectors are elements of E_8, because the sum of the squares
-- of their components is even, hence the sum of the components as well.
go :: [(Int,Int)] -> Int -> [[Int]]
-- We have fixed all eight coordinates.
go fixed@((_,sq) : ps) 8
-- The vector has the right length; add the relevant solutions
-- (using 'vary'), and continue searching.
| sq == dD = vary (map fst fixed) ++ lower ps 7
-- The vector has the wrong length, continue searching.
| otherwise = lower ps 7
go fixed n = let
(m,s) = case fixed of
[] -> (maxCoordinate 0,0)
(c,s) : _ -> (Prelude.min (maxCoordinate s) c,s)
in
go ((m,s + m ^ 2) : fixed) (n + 1)
-- Lexicographically decrease the given vector, and continue the generation
-- from there.
lower :: [(Int,Int)] -> Int -> [[Int]]
lower [] _ = []
lower ((x,s) : ps) n
| x == 0 = lower ps (n - 1)
| otherwise = go ((x - 1,s + 1 - 2 * x) : ps) n
vary :: [Int] -> [[Int]]
vary (x : xs) = if x == 0
then [0 : xs]
else [x : xs,negate x : xs]
-- For vectors with all coordinates half-integers, we work with the doubles
-- of the coordinates.
genHalfInt :: Int -> [Vector]
genHalfInt d = map (V.fromList . map (% 2)) $ go [] 0 where
maxCoordinate :: Int -> Int
maxCoordinate = Memo.integral m where
m s = f $ floor (sqrt (fromIntegral $ dE - s) :: Double)
f k = if odd k then k else k - 1
dE :: Int
dE = 8 * d
go :: [(Int,Int)] -> Int -> [[Int]]
go fixed@((_,sq) : ps) 8
| sq == dE = filter e8 (vary $ map fst fixed) ++ lower ps 7
| otherwise = lower ps 7
go fixed n = let
(m,s) = case fixed of
[] -> (maxCoordinate 0,0)
(c,s) : _ -> (Prelude.min (maxCoordinate s) c,s)
in
go ((m,s + m ^ 2) : fixed) (n + 1)
-- Decides whether a given vector is an element of the lattice E_8
e8 :: [Int] -> Bool
e8 = (== 0) . flip rem 4 . sum
lower :: [(Int,Int)] -> Int -> [[Int]]
lower [] _ = []
lower ((x,s) : ps) n
| x == 1 = lower ps (n - 1)
| otherwise = go ((x - 2,s + 4 - 4 * x) : ps) n
vary :: [Int] -> [[Int]]
vary (x : xs) = [x : xs,negate x : xs]