packages feed

hgraph-1.10.0.0: src/HGraph/Directed.hs

{-# LANGUAGE GADTs #-}

module HGraph.Directed
       ( DirectedGraph(..)
       , Adjacency(..)
       , Mutable(..)
       , subgraphAround
       , renameVertices
       , topologicalOrdering
       , inducedSubgraph
       , lineDigraphI
       , transitiveClosure
       , isIsomorphicTo
       , isIsomorphicToI
       , splitVertices
       , union
       )
where

import qualified Data.Map as M
import qualified Data.Set as S
import HGraph.Utils
import Data.Maybe

class DirectedGraph t where
  empty :: t a -> t a
  vertices :: t a -> [a]
  numVertices :: Integral b => t a -> b
  numVertices d = fromIntegral $ length $ vertices d
  arcs :: t a -> [(a,a)]
  numArcs :: Integral b => t a -> b
  numArcs d = fromIntegral $ length $ arcs d
  linearizeVertices :: t a -> (t Int, [(Int, a)])
  isVertex :: t a -> a -> Bool

class Adjacency t where
  outneighbors :: t a -> a -> [a]
  inneighbors  :: t a -> a -> [a]
  outdegree :: Integral b => t a -> a -> b
  outdegree d v = fromIntegral $ length $ outneighbors d v
  indegree :: Integral b => t a -> a -> b
  indegree d v = fromIntegral $ length $ inneighbors d v
  incomingArcs :: t a -> a -> [(a,a)]
  incomingArcs d v = [(x,v) | x <- inneighbors d v]
  outgoingArcs :: t a -> a -> [(a,a)]
  outgoingArcs d v = [(v,x) | x <- outneighbors d v]
  arcExists :: t a -> (a,a) -> Bool
  metaBfs :: Ord a => t a -> a -> ([a] -> [a]) -> ([a] -> [a]) -> [a]
  metaBfs d v inFilter outFilter =
    metaBfs' S.empty (S.fromList $ (inFilter $ inneighbors d v) ++ (outFilter $ outneighbors d v))
    where
      metaBfs' visited toVisit = 
        let vs = S.toList toVisit
            newToVisit =
              (S.unions $ map
                (S.fromList . 
                  (\v -> (inFilter $ inneighbors d v) ++ (outFilter $ outneighbors d v)))
                vs
              )
              `S.difference` visited
        in if S.null newToVisit then vs else vs ++ metaBfs' (S.union (S.fromList vs) visited) newToVisit

class Mutable t where
  addVertex    :: a -> t a -> t a
  removeVertex :: a -> t a -> t a
  addArc    :: (a,a) -> t a -> t a
  removeArc :: (a,a) -> t a -> t a

renameVertices :: (DirectedGraph t, Mutable t, Ord a) => M.Map a b -> t b -> t a -> t b
renameVertices relabel emptyD d = 
  foldr addArc (foldr addVertex emptyD $ map (relabel M.!) $ vertices d)
               [(relabel M.! v, relabel M.! u) | (v,u) <- arcs d]

subgraphAround :: (DirectedGraph t, Adjacency t, Mutable t) => Int -> t a -> a -> t a
subgraphAround radius d v = around radius (addVertex v $ empty d) [v]
  where
    around _ h [] = h
    around 0 h _ = h
    around r h us = around (r - 1) (foldr addArc (foldr addVertex h ws) as) ws
      where
        ws = [w | u <- us, w <- inneighbors d u ++ outneighbors d u, not $ isVertex h w]
        as = [(u,w) | u <- us, w <- outneighbors d u] ++
             [(w,u) | u <- us, w <- inneighbors d u]

topologicalOrdering d = 
  ordering' d $ S.fromList sources
  where
    sources = filter (\v -> indegree d v == 0) $ vertices d
    ordering' d sources
      | numVertices d == 0 = Just []
      | S.null sources = Nothing
      | otherwise = 
        let d' = (foldr removeVertex d $ S.toList sources)
        in fmap ((S.toList sources) ++) $ ordering' d' $ S.fromList $ concatMap (\v -> filter (\u -> indegree d' u == 0) $ outneighbors d v) sources

inducedSubgraph d vs = 
  foldr addArc (foldr addVertex (empty d) $ S.toList vs) [ (v,u) | (v,u) <- arcs d, v `S.member` vs && u `S.member` vs ]

transitiveClosure d = 
  foldr addArc (foldr addVertex (empty d) $ vertices d) $ concat
    [ map (\u -> (v,u)) $ filter (/=v) $ metaBfs d v (\_ -> []) id
    | v <- vertices d
    ]

-- | Find an isomorphism from `d0` to `d1`, if it exists.
isIsomorphicTo d0 d1 = isIsomorphicToI d0i d1i
  where
    (d0i, _) = linearizeVertices d0
    (d1i, _) = linearizeVertices d1

isIsomorphicToI d0 d1 = isJust $findIso (vertices d0) M.empty candidates0
  where
    candidates0 = M.fromList
                  [ (v, S.fromList us)
                  | v <- vertices d0
                  , let ov = outdegree d0 v
                  , let iv = indegree d0 v
                  , let us = filter (\u -> outdegree d1 u == ov && indegree d1 u == iv) $ vertices d1
                  ]
    findIso [] phi _ = Just phi
    findIso (v:vs) phi candidates = mhead $ map fromJust $ filter isJust $ do      
      u <- S.toList $ candidates M.! v
      let phi' = M.insert v u phi
      let candidates' = M.map (S.delete u) $ M.delete v $ 
              foldr (uncurry $ M.insertWith (\n o -> S.intersection n o) )
                    candidates $
                    [ (w, S.fromList $ outneighbors d1 u)
                    | w <- outneighbors d0 v
                    ] ++
                    [ (w, S.fromList $ inneighbors d1 u)
                    | w <- inneighbors d0 v
                    ]
      if null $ M.filter S.null candidates' then
        return $ findIso vs phi' candidates'
      else
        []

union d0 d1 = foldr addArc (foldr addVertex d0 $ vertices d1) $ arcs d1

-- | Split each vertex `v` of the digraph into two vertices `v_in` and `v_out`.
-- All incoming arcs of `v` become incoming arcs of `v_in`, all
-- outgoing arcs from `v` become outgoing arcs from `v_out` and there is an arc `(v_in, v_out)`.
--
-- This operation is useful when obtaining a vertex variant of arc-based algorithms like maximum flow.
splitVertices d = d''
  where
    d'  = foldr addVertex (empty d) (concat [[2*v, 2*v+1] | v <- vertices d])
    d'' = foldr addArc d' ([(2*v, 2*v + 1) | v <- vertices d] ++ [(2*v+1, 2*u) | (v,u) <- arcs d])


-- | The line digraph of a digraph `d` is the digraph `(V', A')`, where `V'` is the set of arcs of `d`
-- there is an arc (a,b) if the head of `b` in `d` is the same as the tail of `a` in `d`.
lineDigraphI :: (DirectedGraph t, Adjacency t, Mutable t) => t Int -> (t Int, [(Int, (Int, Int))])
lineDigraphI d = 
  let outNeighborhoods = enumerateOutArcs 0 (vertices d)
      enumerateOutArcs _ [] = M.empty
      enumerateOutArcs i (v:vs) = 
        let rest = enumerateOutArcs (i + (outdegree d v)) vs
        in M.insert v (zip [i..] (outneighbors d v)) rest
      vs = [i | us <- map snd $ M.assocs outNeighborhoods, i <- map fst us]
  in
  (
    foldr addArc (foldr addVertex (empty d) vs)
             [ (i, j)
             | (v, us) <- M.assocs outNeighborhoods
             , (i, u) <- us
             , (j, w) <- outNeighborhoods M.! u
             ]
  , [ (i, (v,u))
    | (v, us) <- M.assocs outNeighborhoods
    , (i, u) <- us
    ]
  )