{- The idea of the ltmis algorithm is based on this paper:

Loukakis, E & Tsouros, Constantin. (1981). A depth first search algorithm to
generate the family of maximal independent sets of a graph
lexicographically. Computing. 27. 349-366. 10.1007/BF02277184.

This is basically the same as Bron-Kerbosch but with two special
optimizations, one to avoid needless backtracking and one to avoid needless
branching. For large graphs the gains in efficiency are significant. On my
computer generating all MIS for the first 100000 graphs of size 12 takes
0.757 seconds with ltmis (True,True) and over 10 seconds with bkmis.

-}

{-# OPTIONS_GHC -Wno-name-shadowing #-}
module Darcs.Util.Graph
  ( Graph
  , Vertex
  , VertexSet
  , Component(..)
  -- * Algorithms
  , ltmis
  , bkmis
  , components
  -- * Generating graphs
  , genGraphs
  , genComponents
  -- * Properties
  , prop_ltmis_eq_bkmis
  , prop_ltmis_maximal_independent_sets
  , prop_ltmis_all_maximal_independent_sets
  , prop_components
  ) where

import Control.Monad ( filterM )
import Control.Monad.ST ( runST, ST )

import Data.List ( sort )
import qualified Data.Set as S

import qualified Data.Vector as V
import qualified Data.Vector.Mutable as MV
import qualified Data.Vector.Unboxed as U
import qualified Data.Vector.Unboxed.Mutable as MU

import Safe ( tailErr )

import Darcs.Prelude

-- | Vertices are represented as 'Int'.
type Vertex = Int

-- | Set of vertices, represented as a list for efficiency (yes, indeed).
type VertexSet = [Vertex]

-- | Undirected graph represented as a 'V.Vector' of adjacency 'VertexSet's.
type Graph = V.Vector VertexSet

data Component = Component Graph VertexSet deriving Show

-- | The neighbors of a 'Vertex' in a 'Graph'.
neighbours :: Graph -> Vertex -> VertexSet
neighbours g v = g V.! v

has_edge :: Graph -> Vertex -> Vertex -> Bool
has_edge g u v = u `elem` neighbours g v

has_any_edge :: Graph -> VertexSet -> Vertex -> Bool
has_any_edge g vs u = any (has_edge g u) vs

all_vertices :: Graph -> VertexSet
all_vertices g = [0..(gsize g - 1)]

-- | The number of vertices in a 'Graph'.
gsize :: Graph -> Int
gsize v = V.length v

-- * Maximal independent sets

-- | Simple helper type used in the 'ltmis' and 'components' algorithms.
type Helper = U.Vector Bool

-- | Determine the maximal independent sets in a 'Component' of a 'Graph'.
ltmis :: (Bool,Bool) -> Component -> [VertexSet]
ltmis (bt1,bt2) (Component g comp) =
    -- the map reverse is because we use (:) to add vertices to r
    -- when branching
    map reverse $ go [] 0 init_h
  where
    size = gsize g
    init_h = U.replicate (gsize g) True U.// zip comp (repeat False)
    -- h[v] = neighbours g v `intersectsWith` r || v `elem` r || v `notElem` comp
    go :: VertexSet -> Vertex -> Helper -> [VertexSet]
    go r !sep h =
      case candidates sep h of
        [] -> [r]
        br:_ ->
          (if bt1 && done_branching sep' h' then [] else go (br:r) sep' h')
          ++
          (if bt2 && done_backtracking sep' h br then [] else go r sep' h)
          where
            h' = h U.// zip (br : neighbours g br) (repeat True)
            sep' = br + 1

    candidates :: Vertex -> Helper -> VertexSet
    candidates sep h = filter (not . (h U.!)) $ [sep..(size-1)]

    excludes :: Vertex -> Helper -> [Vertex]
    excludes sep h = filter (not . (h U.!)) [0 .. (sep-1)]

    is_candidate :: Vertex -> Helper -> Vertex -> Bool
    is_candidate sep h v = v >= sep && not ((h U.!) v)

    intersects_candidates :: Vertex -> Helper -> VertexSet -> Bool
    intersects_candidates sep h = any (is_candidate sep h)

    -- for some x in X, N(x) does not intersect C
    -- means whatever candidate we add we won't get an MIS
    -- so can stop branching
    done_branching :: Vertex -> Helper -> Bool
    done_branching sep h =
      any (not . intersects_candidates sep h) $ map (neighbours g) $ excludes sep h

    -- if done_backtracking (neighbours g v), then v must
    -- be a member of any MIS containing R
    done_backtracking :: Vertex -> Helper -> Vertex -> Bool
    done_backtracking sep h v = not $ intersects_candidates sep h $ neighbours g v

-- | The classic Bron-Kerbosch algorithm for determining the maximal
-- independent sets in a 'Graph'.
bkmis :: Graph -> [VertexSet]
bkmis g = reverse $ map reverse $ go [] [] (all_vertices g) where
  go r [] [] = [r]
  go r xs cs = loop xs cs where
    loop _ [] = []
    loop xs (c:cs) = loop (c:xs) cs ++ go (c:r) (res c xs) (res c cs)
    res v = filter (not . has_edge g v)

-- * Generating graphs

genGraph :: Monad m => (Int -> Int -> m VertexSet) -> Int -> m Graph
genGraph genSubset = go 0 where
  go _ 0 = return V.empty
  go s n = do -- list monad
    g <- go (s+1) (n-1)
    vs <- genSubset (s+1) (n-1)
    return $ V.modify (\h -> mapM_ (adjust h) vs) (V.cons vs g)
    where
      adjust g i = do
        vs <- MV.read g (i-s)
        MV.write g (i-s) (s:vs)

-- | Enumerate all (simple) graphs of a given size (number of vertices).
genGraphs :: Int -> [Graph]
genGraphs = genGraph subsets where
  -- Subsets of the n elements [s..(s+n-1)] (each subset is ordered)
  subsets _ 0 = return []
  subsets s n = do
    vs <- subsets (s+1) (n-1)
    [vs,s:vs]

genComponents :: Int -> [Component]
genComponents n = do
  g <- genGraphs n
  components g

-- * Connected components

-- | Split a 'Graph' into connected components. For efficiency we don't
-- represent the result as a list of Graphs, but rather of 'VertexSet's.
components :: Graph -> [Component]
components g = reverse $ map (Component g) $ runST go where
  size = gsize g
  go :: ST s [VertexSet]
  go = do
    mh <- MU.replicate size False
    loop 0 mh []
  loop v mh r
    | v == size = return r
    | otherwise = do
      c <- new_component v
      if null c
        then loop (v + 1) mh r
        else loop (v + 1) mh (c : r)
    where
      new_component v = do
        visited <- MU.read mh v
        if visited
          then return []
          else do
            -- mark v as visited
            MU.write mh v True
            cs <- mapM new_component (neighbours g v)
            return $ v : concat cs

-- * Properties

-- | Whether a 'VertexSet' is independent i.e. no edge exists between any
-- two of its vertices.
prop_is_independent_set :: Graph -> VertexSet -> Bool
prop_is_independent_set g vs = all (not . has_any_edge g vs) vs

-- | Whether a 'VertexSet' is maximally independent i.e. it is independent
-- and no longer independent if we add any other vertex.
prop_is_maximal_independent_set :: Component -> VertexSet -> Bool
prop_is_maximal_independent_set (Component g c) vs =
    prop_is_independent_set g vs &&
    all (has_any_edge g vs) other_vertices
  where
    other_vertices = filter (`notElem` vs) c

-- | Whether 'ltmis' is equivalent to 'bkmis'.
prop_ltmis_eq_bkmis :: Graph -> Bool
prop_ltmis_eq_bkmis g =
  ltmis (True, True) (Component g (all_vertices g)) == bkmis g

-- | Whether 'ltmis' generates only maximal independent sets.
prop_ltmis_maximal_independent_sets :: Component -> Bool
prop_ltmis_maximal_independent_sets sg =
  all (prop_is_maximal_independent_set sg) (ltmis (True, True) sg)

-- | Whether 'ltmis' generates /all/ maximal independent sets.
prop_ltmis_all_maximal_independent_sets :: Component -> Bool
prop_ltmis_all_maximal_independent_sets sg@(Component _ c) =
    all (not . prop_is_maximal_independent_set sg) other_subsets
  where
    mis = ltmis (True, True) sg
    all_subsets = powerset c
    other_subsets = filter (`notElem` mis) all_subsets

-- | Whether a list of 'VertexSet's of a 'Graph' is a partition of
-- the set of all its vertices.
prop_is_partition :: Graph -> [VertexSet] -> Bool
prop_is_partition g cs = sort (concat cs) == all_vertices g

-- | Whether there is no edge between a 'VertexSet' of a 'Graph' and the rest
-- of the 'Graph'.
prop_self_contained :: Graph -> VertexSet -> Bool
prop_self_contained g c =
  S.unions (map (S.fromList . neighbours g) c) `S.isSubsetOf` S.fromList c

-- | Whether a 'VertexSet' of a 'Graph' is connected.
prop_connected :: Graph -> VertexSet -> Bool
prop_connected g = not . any (prop_self_contained g) . proper_non_empty_subsets
  where
    proper_non_empty_subsets = filter (not . null) . tailErr . powerset

-- | Whether a 'VertexSet' is a connected component of the 'Graph'.
prop_connected_component :: Component -> Bool
prop_connected_component (Component g vs) =
  prop_self_contained g vs && prop_connected g vs

-- | Complete specification of the 'components' function.
prop_components :: Graph -> Bool
prop_components g =
    all prop_connected_component cs &&
      prop_is_partition g (map vertices cs) && all (== g) (map graph cs)
  where
    vertices (Component _ vs) = vs
    graph (Component g _) = g
    cs = components g

powerset :: VertexSet -> [VertexSet]
powerset = map sort . filterM (const [True, False])
