-- Graph.hs
-- Copyright (c) David Amos, 2006

module MPS.Math.Graph where

import Array -- !! temporary
import MPS.Math.RedBlackTree
import List (sort, partition, intersect, transpose, (\\) )
import MPS.Math.MathsPrimitives (fMatrix)
import MPS.Math.CombinatoricsGeneration (cartProd, combinationsOf, powerset, permutationsOf)
import MPS.Math.CombinatoricsCounting (choose)
import MPS.Math.PermutationGroups

import MPS.Math.Primes (isPrime)
import MPS.Math.NumberTheoryFundamentals (legendreSymbol)

-- Source
-- Godsil & Royle, Algebraic Graph Theory

set -^ g = sort [x .^ g | x <- set]

system +^ g = sort [set -^ g | set <- system]


{-
countVector _ [] = []
countVector i xs = let (ys,zs) = partition (==i) xs in length ys : countVector (i+1) zs
-}
countVector i xs = countSorted i (sort xs)

countSorted _ [] = []
countSorted i xs = let (ys,zs) = span (==i) xs in length ys : countSorted (i+1) zs

-- FAMILIES OF GRAPHS

type Graph = (Int,[[Int]])

-- cyclic graph
c n = (n, sort ([[i,i+1] | i <- [1..n-1]] ++ [[1,n]])) :: Graph

-- complete graph
k n = (n, [[i,j] | i <- [1..n-1], j <- [i+1..n]]) :: Graph
-- == subsets 2 [1..n]

-- complete bipartite graph
kb m n = (m+n, [[i,j] | i <- [1..m], j <- [m+1..m+n]]) :: Graph

-- Godsil & Royle p8
-- circulantGraph n c = [[i,j] | i <- [1..n-1], j <- [i+1..n], ((j-i) `mod` n) `elem` c]
-- !! NOT WORKING CORRECTLY


-- Generalized Johnson graph, Godsil & Royle p9
j v k i = let vertices = zip [1..] (combinationsOf k [1..v])
              edges = [ [x,y] | (x,xset) <- vertices, (y,yset) <- vertices, x < y, length (xset `intersect` yset) == i ]
          in (fromInteger (v `choose` k), edges) :: Graph

kneser v k = j v k 0

johnson v k = j v k (k-1)

petersenGraph = j 5 2 0
-- == complement $ lineGraph $ k 5

-- k-cube
q k = let vs = zip (2^k : [1..]) (powerset [1..k])
      in [ [i,j] | (i,iset) <- vs, (j,jset) <- vs, i < j, length (iset `setDiff` jset) == 1 ]
      where setDiff xs ys = (xs \\ ys) ++ (ys \\ xs)


-- actually defined for prime powers
-- (if p /= 1 mod 4, then the paley graph is a directed graph)
paleyGraph p | isPrime p' && p `mod` 4 == 1
    = (p, [ [i,j] | [i,j] <- combinationsOf 2 [1..p], legendreSymbol (j-i) p == 1 ])
    where p' = toInteger p


-- NEW GRAPHS FROM OLD

lineGraph (n,edges) = let vs = zip [1..] edges
                      in (length edges, [ [i,j] | (i,ei) <- vs, (j,ej) <- vs, i < j, ei `intersect` ej /= [] ])


complement (n,edges) = let (_,edges') = k n in (n, edges' \\ edges)


-- UTILITIES

neighbours (_,edges) i = filter (/=i) $ concat [e | e <- edges, i `elem` e]

isAdjacent (n,edges) i j = sort [i,j] `elem` edges

adjacencyMatrix graph@(n,_) = fMatrix (\i j -> if isAdjacent graph i j then 1 else 0) n

incidenceMatrix (n,edges) = [ [if v `elem` e then 1 else 0 | e <- edges] | v <- [1..n]]

valencies (n,edges) = let v = countVector 1 (concat edges) in v ++ replicate (n - length v) 0
-- the last bit is to ensure that we include vertices with valency 0

valencyVector graph = countVector 0 (valencies graph)

valencyPartition graph@(n,_) = let vvs = zip [1..] (valencies graph)
                               in [ [vertex | (vertex,valency) <- vvs, valency == i] | i <- [0..n] ]

connectedComponent graph i = cc (rbfromlist [i]) [i]
    where cc interior [] = rbtolist interior
          cc interior boundary = let boundary' = toSet [k | j <- boundary, k <- neighbours graph j, not (k `rbmember` interior)]
                                     interior' = foldl rbinsert interior boundary'
                                 in cc interior' boundary'

isConnected graph@(n,_) = connectedComponent graph 1 == [1..n]


-- AUTOMORPHISMS

eltsSymGpOf xs = [fromMapping (zip xs ys) | ys <- permutationsOf xs]
    where n = maximum xs
          fromMapping [] = PL []
          fromMapping xys = PL (elems (array (1,n) [(i,i) | i <- [1..n]] // xys))

valencyPreservingEltsSn graph = let partition = filter (not . null) (valencyPartition graph)
                               in map product $ cartProd $ map eltsSymGpOf partition

automorphisms graph@(_,edges) = [g | g <- valencyPreservingEltsSn graph, isAutomorphism2 edges g]

isAutomorphism1 edges g = edges +^ g == edges

isAutomorphism2 edges g = all (`elem` edges) [edge -^ g | edge <- edges]
-- isAutomorphism2 will spot a failure earlier, though it takes longer to verify a success


-- ISOMORPHISMS

-- prerequisites: two graphs with the same number of vertices, and the same valency vectors
-- if they don't have the same valency vectors, the result of this function won't be meaningful
valencyPreservingMap :: Graph -> Graph -> Permutation
valencyPreservingMap graph1 graph2 = let xs = concat (valencyPartition graph1)
                                         ys = concat (valencyPartition graph2)
                                     in PL (map snd $ sort $ zip xs ys)

isIsomorphism edges1 edges2 g = all (`elem` edges2) [edge -^ g | edge <- edges1]

isomorphisms1 (n,es) (m,es') | n == m    = [h | h <- eltsSn n, isIsomorphism es es' h ]
                            | otherwise = []

isomorphisms2 g1@(n1,es1) g2@(n2,es2)
    | n1 == n2 && valencyVector g1 == valencyVector g2
                = let k = valencyPreservingMap g1 g2 in [h | h <- map (k *>) (valencyPreservingEltsSn g2), isIsomorphism es1 es2 h]
    | otherwise = []

isIsomorphic g1 g2 = not $ null $ isomorphisms1 g1 g2
