-- Copyright (c) 2011, David Amos. All rights reserved. {-# LANGUAGE NoMonomorphismRestriction #-} module Math.Combinatorics.Poset where import Math.Common.ListSet as LS -- set operations on strictly ascending lists import Math.Core.Utils -- for set/multiset operations on ordered lists import Math.Core.Field import Math.Combinatorics.FiniteGeometry import Math.Algebra.LinearAlgebra import Math.Combinatorics.Digraph import Data.List as L import qualified Data.Map as M -- import qualified Data.Set as S -- |A poset is represented as a pair (set,po), where set is the underlying set of the poset, and po is the partial order relation newtype Poset t = Poset ([t], t -> t -> Bool) instance Eq t => Eq (Poset t) where Poset (set,po) == Poset (set',po') = set == set' && and [po x y == po' x y | x <- set, y <- set] -- There may be ways to avoid comparing every pair -- If we could calculate the coverGraph without comparing every pair, -- then it would be sufficient to test whether their cover graphs are equal instance Show t => Show (Poset t) where show (Poset (set,po)) = "Poset " ++ show set implies p q = q || not p isReflexive (set,po) = and [x `po` x | x <- set] isAntisymmetric (set,po) = and [((x `po` y) && (y `po` x)) `implies` (x == y) | x <- set, y <- set] isTransitive (set,po) = and [((x `po` y) && (y `po` z)) `implies` (x `po` z) | x <- set, y <- set, z <- set] isPoset poset = isReflexive poset && isAntisymmetric poset && isTransitive poset poset (set,po) | isPoset (set,po) = Poset (set,po) | otherwise = error "poset: Not a partial order" -- Most of the posets we will deal with are in fact lattices, meaning that any two elements -- have a meet (greatest lower bound) and join (least upper bound) intervals (Poset (set,po)) = [(a,b) | a <- set, b <- set, a `po` b] interval (Poset (set,po)) (x,z) = [y | y <- set, x `po` y, y `po` z] -- LINEAR ORDER POSET -- This is of course a lattice, with meet = min, join = max -- |A chain is a poset in which every pair of elements is comparable (ie either x <= y or y <= x). -- It is therefore a linear or total order. -- chainN n is the poset consisting of the numbers [1..n] ordered by (<=) chainN :: Int -> Poset Int chainN n = Poset ( [1..n], (<=) ) -- hasseN n = DG [1..n] [(i,i+1) | i <- [1..n-1]] -- |An antichain is a poset in which distinct elements are incomparable. -- antichainN n is the poset consisting of [1..n], with x <= y only when x == y. antichainN :: Int -> Poset Int antichainN n = Poset ( [1..n], (==) ) -- LATTICE OF (POSITIVE) DIVISORS OF N divides a b = b `rem` a == 0 divisors n = toSet [ d' | d <- takeWhile (\d -> d*d <= n) [1..], let (q,r) = n `quotRem` d, r == 0, d' <- [d,q] ] -- The toSet call sorts, and deduplicates if n is a square -- divisors n | n >= 1 = [a | a <- [1..n], a `divides` n] -- |posetD n is the lattice of (positive) divisors of n posetD :: Int -> Poset Int posetD n | n >= 1 = Poset ( divisors n, divides ) -- LATTICE OF SUBSETS OF [1..N] ORDERED BY INCLUSION -- (Boolean lattice) powerset [] = [[]] powerset (x:xs) = let p = powerset xs in p ++ map (x:) p {- -- subset test for sorted lists isSubset (x:xs) (y:ys) = case compare x y of LT -> False EQ -> isSubset xs ys GT -> isSubset (x:xs) ys isSubset [] _ = True isSubset _ [] = False -} -- |posetB n is the lattice of subsets of [1..n] ordered by inclusion posetB :: Int -> Poset [Int] posetB n = Poset ( powerset [1..n], LS.isSubset ) -- LATTICE OF SET PARTITIONS OF [1..N] ORDERED BY REFINEMENT partitions [] = [[]] partitions [x] = [[[x]]] partitions (x:xs) = let ps = partitions xs in map ([x]:) ps ++ [ (x:cell):p' | p <- ps, (cell,p') <- picks p] -- if the input is sorted, then so is the output isRefinement a b = and [or [acell `isSubset` bcell | bcell <- b] | acell <- a] -- if we know that a and b are appropriately sorted, then this can probably be done more efficiently -- |posetP n is the lattice of set partitions of [1..n], ordered by refinement posetP :: Int -> Poset [[Int]] posetP n = Poset ( partitions [1..n], isRefinement ) -- muP n = ... -- see van Lint and Wilson p336 -- LATTICE OF INTERVAL PARTITIONS OF [1..N] ORDERED BY REFINEMENT -- Interval partitions of [1..n] correspond to compositions of n intervalPartitions xs = filter (all isInterval) (partitions xs) isInterval (x1:x2:xs) = x1+1 == x2 && isInterval (x2:xs) isInterval _ = True intervalPartitions2 [] = [[]] intervalPartitions2 [x] = [[[x]]] intervalPartitions2 (x:xs) = let ips = intervalPartitions2 xs in map ([x]:) ips ++ [ (x:head):tail | (head:tail) <- ips] -- we're guaranteed that x+1 is at the head of the head -- LATTICE OF INTEGER PARTITIONS OF N ORDERED BY REFINEMENT -- For example, integerPartitions 5 -> [ [5], [4,1], [3,2], [3,1,1], [2,2,1], [2,1,1,1], [1,1,1,1,1] ] integerPartitions n | n >= 0 = ips n n where ips 0 _ = [[]] ips _ 0 = [] ips n m | m <= n = map (m:) (ips (n-m) m) ++ ips n (m-1) | otherwise = ips n n isIPRefinement ys xs = dfs xs ys where dfs (x:xs) (y:ys) | x < y = False | x == y = dfs xs ys | otherwise = or [dfs xs' ys' | y' <- y:ys, let ys' = L.delete y' (y:ys), let xs' = insertDesc (x-y') xs] dfs [] [] = True insertDesc = L.insertBy (flip compare) {- -- In theory it feels like this ought to be faster for large n, but in practice it's unclear isIPRefinement2 ys xs = isIPRefinement (ys \\ xs) (xs \\ ys) where (\\) = diffDesc -} -- |posetIP n is the poset of integer partitions of n, ordered by refinement posetIP :: Int -> Poset [Int] posetIP n = Poset (integerPartitions n, isIPRefinement) -- Could also implement the Young lattice (or the part up to n) -- Of integer partitions <= n, ordered by inclusion -- Kassel, Turaev, Braid Groups, p202 -- INTEGER COMPOSITIONS -- LATTICE OF SUBSPACES OF Fq^n subspaces fq n = [] : concatMap (flatsPG (n-1) fq) [0..n-1] -- note that flatsPG returns the subspaces as a matrix of row vectors in reduced row echelon form -- inSpanRE m v returns whether the vector v is in the span of the rows of the matrix m, where m is required to be in row echelon form isSubspace s1 s2 = all (inSpanRE s2) s1 -- This is the projective geometry PG(n,q) -- |posetL n fq is the lattice of subspaces of the vector space Fq^n, ordered by inclusion. -- Subspaces are represented by their reduced row echelon form. -- Example usage: posetL 2 f3 posetL :: (Eq fq, Num fq) => Int -> [fq] -> Poset [[fq]] posetL n fq = Poset ( subspaces fq n, isSubspace ) -- choose n k = product [n-k+1..n] `div` product [1..k] -- NEW FROM OLD CONSTRUCTIONS -- |The subposet of a poset satisfying a predicate subposet :: Poset a -> (a -> Bool) -> Poset a subposet (Poset (set,po)) p = Poset (filter p set, po) -- |The direct sum of two posets dsum :: Poset a -> Poset b -> Poset (Either a b) dsum (Poset (setA,poA)) (Poset (setB,poB)) = Poset (set,po) where set = map Left setA ++ map Right setB po (Left a1) (Left a2) = poA a1 a2 po (Right b1) (Right b2) = poB b1 b2 po _ _ = False -- |The direct product of two posets dprod :: Poset a -> Poset b -> Poset (a,b) dprod (Poset (setA,poA)) (Poset (setB,poB)) = Poset ( [(a,b) | a <- setA, b <- setB], \(a1,b1) (a2,b2) -> (a1 `poA` a2) && (b1 `poB` b2) ) -- |The dual of a poset dual :: Poset a -> Poset a dual (Poset (set, po)) = Poset (set, po') where po' x y = po y x -- ANALYSIS OF POSETS -- |Given a poset (X,<=), we say that y covers x, written x -< y, if x < y and there is no z in X with x < z < y. -- The Hasse digraph of a poset is the digraph whose vertices are the elements of the poset, -- with an edge between every pair (x,y) with x -< y. -- The Hasse digraph can be represented diagrammatically as a Hasse diagram, by drawing x below y whenever x -< y. hasseDigraph :: (Eq a) => Poset a -> Digraph a hasseDigraph (Poset (set,po)) = DG set [(x,y) | x <- set, y <- set, x -< y] where x -< y = x /= y && x `po` y && null [z | z <- set, x `po` z, x /= z, z `po` y, z /= y] -- The partial order can be recovered as the transitive closure of the covers relation -- !! Can we construct the cover graph without having to compare every pair ?? -- If we know in advance the poset we're interested in, -- then we're probably better off constructing the Hasse digraph directly -- (In effect, the transitive closure of the edge relation) -- |Given a DAG (directed acyclic graph), return the poset consisting of the vertices of the DAG, ordered by reachability. -- This can be used to recover a poset from its Hasse digraph. reachabilityPoset :: (Ord a) => Digraph a -> Poset a reachabilityPoset (DG vs es) = Poset (vs,tc') -- \u v -> tc M.! (u,v) where tc = M.fromList [ ((u,v), tc' u v) | u <- vs, v <- vs] tc' u v | u == v = True | otherwise = or [tc M.! (w,v) | w <- successors u] successors u = [v | (u',v) <- es, u' == u] -- !! looks like we could memoise more than we are doing {- -- For example: > let poset = posetB 3 in poset == reachabilityPoset (hasseDigraph poset) True -} isOrderPreserving :: (a -> b) -> Poset a -> Poset b -> Bool isOrderPreserving f (Poset (seta,poa)) (Poset (setb,pob)) = and [ x `poa` y == f x `pob` f y | x <- seta, y <- seta ] -- Find all order isomorphisms between two posets -- This is the most naive algorithm, and should not be used on larger posets -- For example, already on the following, this takes forever compared to almost instant for orderIsos: -- > head $ orderIsos01 (posetD $ 8*9*25*49) (posetD $ 4*27*25*121) orderIsos01 (Poset (seta,poa)) (Poset (setb,pob)) | length seta /= length setb = [] | otherwise = orderIsos' [] seta setb where orderIsos' xys [] [] = [xys] orderIsos' xys (x:xs) ys = concat [ orderIsos' ((x,y):xys) xs ys' | (y,ys') <- picks ys, and [ (x `poa` x', x' `poa` x) == (y `pob` y', y' `pob` y) | (x',y') <- xys ] ] -- |Are the two posets order-isomorphic? isOrderIso :: (Ord a, Ord b) => Poset a -> Poset b -> Bool isOrderIso poseta posetb = (not . null) (orderIsos poseta posetb) -- For small posets, it may be that the up-front cost of calculating the hasseDigraph and heightPartitionDAG -- are not justified. -- |Find all order isomorphisms between two posets orderIsos :: (Ord a, Ord b) => Poset a -> Poset b -> [[(a,b)]] orderIsos posetA@(Poset (_,poa)) posetB@(Poset (_,pob)) | map length heightPartA /= map length heightPartB = [] | otherwise = dfs [] heightPartA heightPartB where heightPartA = heightPartitionDAG (hasseDigraph posetA) heightPartB = heightPartitionDAG (hasseDigraph posetB) dfs xys [] [] = [xys] dfs xys ([]:las) ([]:lbs) = dfs xys las lbs dfs xys ((x:xs):las) (ys:lbs) = concat [ dfs ((x,y):xys) (xs:las) (ys' : lbs) | (y,ys') <- picks ys, and [ (x `poa` x', x' `poa` x) == (y `pob` y', y' `pob` y) | (x',y') <- xys ] ] -- A variant on this algorithm would use the Hasse digraph rather than the partial order in the test on the last line -- This might be faster, depending how expensive the partial order comparison function is -- In effect though, it would then be a DAG isomorphism function -- The order automorphisms of a poset orderAuts1 poset = orderIsos poset poset -- This returns all automorphisms -- What we really want is to return generators of the permutation group -- |A linear extension of a poset is a linear ordering of the elements which extends the partial order. -- Equivalently, it is an ordering [x1..xn] of the underlying set, such that if xi <= xj then i <= j. isLinext (Poset (set,po)) set' = all (\(x,y) -> not (y `po` x)) (pairs set') -- |Linear extensions of a poset linexts (Poset (set,po)) = linexts' [[]] set where linexts' lss (r:rs) = let lss' = [ lts ++ [r] ++ gts | ls <- lss, let ls' = takeWhile (not . (r `po`)) ls, (lts,gts) <- zip (inits ls') (tails ls), all (not . (`po` r)) gts ] in linexts' lss' rs linexts' lss [] = lss