-- polyacounting.hs

module Math.PolyaCounting where

-- Cameron, Combinatorics, p247-253

import Math.PermutationGroups
import Math.SchreierSims
import Math.QQ
import Math.MPoly
import Math.CombinatoricsCounting (factorial, choose)


-- ROTATION GROUP OF CUBE (AS PERMUTATION OF FACES)
--   1
-- 2 3 4 5
--   6

cubeF = fromCycles [[1],[2,3,4,5],[6]]
cubeV = fromCycles [[1,2,3],[4,5,6]]
cubeE = fromCycles [[1,3],[2,4],[5,6]]

cubeGp = schreierSimsTransversals 6 [cubeF, cubeV, cubeE]



-- ROTATION GROUP OF DODECAHEDRON (AS PERMUTATION OF FACES)
-- The top faces are numbered (looking from above)
--      2
--  6       3
--      1
--    5   4
-- The bottom faces are numbered so that opposite faces add up to 13 (still looking from above)
--   9    8
--     12
-- 10       7
--     11

dodecF = fromCycles [[1],[2,3,4,5,6],[11,10,9,8,7],[12]]       -- rotation about a face
dodecV = fromCycles [[1,2,3],[4,6,8],[9,7,5],[12,11,10]]       -- rotation about a vertex
dodecE = fromCycles [[1,2],[3,6],[4,9],[5,8],[7,10],[11,12]]   -- rotation about an edge

dodecGp = schreierSimsTransversals 12 [dodecF, dodecV, dodecE]
-- in fact any two generate the group

-- The order of the group is 60. It's isomorphic to A5. See:
-- http://en.wikipedia.org/wiki/Icosahedral_symmetry


cycleIndexElt g = monomial (cycleType g)

cycleIndexGp gp = sum [cycleIndexElt g | g <- fix (eltsSS gp)] / fromInteger (orderSS gp)

fix gs = let n = maximum [length xs | PL xs <- gs]
         in map (\g -> if g == PL [] then PL [1..n] else g) gs

countingPoly gp vs = substMP (cycleIndexGp gp) [sum [v^i | v <- vs] | i <- [0..]]

polyaCount' gp vs = evalMP (countingPoly gp vs) (repeat 1)

polyaCount gp vs = sum [c | (c,as) <- termsMP (countingPoly gp vs)]


-- COUNTING NON-ISOMORPHIC GRAPHS

toEdge (a,b) = if a < b then (a,b) else (b,a)

(a,b) -^ g = toEdge (a .^ g, b .^ g)    -- action on edge induced from action on points

edges :: [(Int,Int)]
edges = doEdges 2
    where doEdges n = [(i,n) | i <- [1..n-1]] ++ doEdges (n+1)
    
numEdgesKn n = fromInteger (toInteger n `choose` 2)

edgesKn n = take (numEdgesKn n) edges

edgeToPoint (a,b) = (b-2)*(b-1) `div` 2 + a

pointToEdge c = edges !! (c-1)

-- given an elt of Sn, return the induced action on the edges of Kn
inducedPermutation n g = PL [edgeToPoint (edge -^ g) | edge <- edgesKn n]


generatorsEdgeGp n = map (inducedPermutation n) (generatorsSn n)

edgeGp n = schreierSimsTransversals m (generatorsEdgeGp n)
    where m = numEdgesKn n


{-
partitions n = boundedPartitions n n
    where boundedPartitions n k    -- partitions of n where the parts are bounded to be <= k
            | n <= 0    = [[]]
            | k == 0    = []
            | k > n     = boundedPartitions n (k-1)
            | otherwise = map (k:) (boundedPartitions (n-k) k) ++ boundedPartitions n (k-1)
-}
cycleTypesSn n = doCycleTypes n n [[]]
    where doCycleTypes n 1 types = map (n:) types
          doCycleTypes n i types = concat [doCycleTypes (n-a*i) (i-1) (map (a:) types) | a <- [0..n `div` i] ]

-- the argument is [a_1, ..., a_n], where we're asking about elements with cycle structure 1^a_1 ... n^a_n
numEltsWithCycleType n as = (factorial n) `div` product [i^a * factorial a | (i,a) <- zip [1..] as]

cycleIndexSn n = sum [fromInteger (numEltsWithCycleType n as) * monomial as | as <- cycleTypesSn n] / fromInteger (factorial n)

-- given n, and a cycle type for Sn, calculate the induced cycle index on the edges of Kn
inducedCycleType :: Int -> [Int] -> [Int]
inducedCycleType n as =
    let cyclePowers = [(i,a) | (i,a) <- zip [1..] as, a /= 0]
        withinCycles = [(i, (i-1) `div` 2 * a) | (i,a) <- cyclePowers] -- each cycle induces a cycle on edges within the cycle
                    ++ [(i `div` 2,a) | (i,a) <- cyclePowers, even i]  -- but in the even case, the edge joining opposite points of the cycle has half the period
        acrossCycles = [(lcm i j, a * b * gcd i j) | (i,a) <- cyclePowers, (j,b) <- cyclePowers, i < j] -- if the points of an edge fall in cycles of different length, the period of the cycle on the edge is the gcd
                    ++ [(i, i * (a * (a-1) `div` 2)) | (i,a) <- cyclePowers, a > 1] -- when the points of an edge fall in different cycles of the same length, the cycle on the edge also has this length
			-- there are (a `choose` 2) choices for pairs of cycles, and given a pair, there are i choices for edges
    in collectPowers (withinCycles ++ acrossCycles) -- withinCycles $+ collectPowers acrossCycles
    where collectPowers powers = [sum [a | (i,a) <- powers, i == j] | j <- [1..numEdgesKn n]] -- can be done more efficiently

inducedCycleIndex n as =
    let cyclePowers = [(i,a) | (i,a) <- zip [1..] as, a /= 0]
        withinCycles = [(i, (i-1) `div` 2 * a) | (i,a) <- cyclePowers, i > 1] -- each cycle induces a cycle on edges within the cycle
                    ++ [(i `div` 2,a) | (i,a) <- cyclePowers, even i]  -- but in the even case, the edge joining opposite points of the cycle has half the period
        acrossCycles = [(lcm i j, a * b * gcd i j) | (i,a) <- cyclePowers, (j,b) <- cyclePowers, i < j] -- if the points of an edge fall in cycles of different length, the period of the cycle on the edge is the gcd
                    ++ [(i, i * (a * (a-1) `div` 2)) | (i,a) <- cyclePowers, a > 1] -- when the points of an edge fall in different cycles of the same length, the cycle on the edge also has this length
			-- there are (a `choose` 2) choices for pairs of cycles, and given a pair, there are i choices for edges
    in product [x_ i ^ a | (i,a) <- withinCycles ++ acrossCycles]


inducedCycleIndexSn n = sum [fromInteger (numEltsWithCycleType n as) * inducedCycleIndex n as | as <- cycleTypesSn n] / fromInteger (factorial n)

graphCountingPoly n = substMP (inducedCycleIndexSn n) [1+t^i | i <- [0..]]
