algebraic-graphs-0.1.0: src/Algebra/Graph/Fold.hs
{-# LANGUAGE RankNTypes #-}
-----------------------------------------------------------------------------
-- |
-- Module : Algebra.Graph.Fold
-- Copyright : (c) Andrey Mokhov 2016-2018
-- License : MIT (see the file LICENSE)
-- Maintainer : andrey.mokhov@gmail.com
-- Stability : experimental
--
-- __Alga__ is a library for algebraic construction and manipulation of graphs
-- in Haskell. See <https://github.com/snowleopard/alga-paper this paper> for the
-- motivation behind the library, the underlying theory, and implementation details.
--
-- This module defines the 'Fold' data type -- the Boehm-Berarducci encoding of
-- algebraic graphs, which is used for generalised graph folding and for the
-- implementation of polymorphic graph construction and transformation algorithms.
-- 'Fold' is an instance of type classes defined in modules "Algebra.Graph.Class"
-- and "Algebra.Graph.HigherKinded.Class", which can be used for polymorphic
-- graph construction and manipulation.
-----------------------------------------------------------------------------
module Algebra.Graph.Fold (
-- * Boehm-Berarducci encoding of algebraic graphs
Fold,
-- * Basic graph construction primitives
empty, vertex, edge, overlay, connect, vertices, edges, overlays, connects,
-- * Graph folding
foldg,
-- * Relations on graphs
C.isSubgraphOf,
-- * Graph properties
isEmpty, size, hasVertex, hasEdge, vertexCount, edgeCount, vertexList,
edgeList, vertexSet, vertexIntSet, edgeSet,
-- * Standard families of graphs
C.path, C.circuit, C.clique, C.biclique, C.star, C.starTranspose, C.tree,
C.forest, mesh, torus, deBruijn,
-- * Graph transformation
removeVertex, removeEdge, replaceVertex, mergeVertices, splitVertex,
transpose, gmap, bind, induce, simplify,
-- * Graph composition
box
) where
import Prelude ()
import Prelude.Compat
import Control.Applicative hiding (empty)
import Control.Monad.Compat (MonadPlus (..), ap)
import Data.Foldable
import Algebra.Graph.Internal
import qualified Algebra.Graph.AdjacencyMap as AM
import qualified Algebra.Graph.Class as C
import qualified Algebra.Graph.HigherKinded.Class as H
import qualified Algebra.Graph.Relation as R
import qualified Data.IntSet as IntSet
import qualified Data.Set as Set
{-| The 'Fold' data type is the Boehm-Berarducci encoding of the core graph
construction primitives 'empty', 'vertex', 'overlay' and 'connect'. We define a
'Num' instance as a convenient notation for working with graphs:
> 0 == vertex 0
> 1 + 2 == overlay (vertex 1) (vertex 2)
> 1 * 2 == connect (vertex 1) (vertex 2)
> 1 + 2 * 3 == overlay (vertex 1) (connect (vertex 2) (vertex 3))
> 1 * (2 + 3) == connect (vertex 1) (overlay (vertex 2) (vertex 3))
The 'Show' instance is defined using basic graph construction primitives:
@show (empty :: Fold Int) == "empty"
show (1 :: Fold Int) == "vertex 1"
show (1 + 2 :: Fold Int) == "vertices [1,2]"
show (1 * 2 :: Fold Int) == "edge 1 2"
show (1 * 2 * 3 :: Fold Int) == "edges [(1,2),(1,3),(2,3)]"
show (1 * 2 + 3 :: Fold Int) == "overlay (vertex 3) (edge 1 2)"@
The 'Eq' instance is currently implemented using the 'AM.AdjacencyMap' as the
/canonical graph representation/ and satisfies all axioms of algebraic graphs:
* 'overlay' is commutative and associative:
> x + y == y + x
> x + (y + z) == (x + y) + z
* 'connect' is associative and has 'empty' as the identity:
> x * empty == x
> empty * x == x
> x * (y * z) == (x * y) * z
* 'connect' distributes over 'overlay':
> x * (y + z) == x * y + x * z
> (x + y) * z == x * z + y * z
* 'connect' can be decomposed:
> x * y * z == x * y + x * z + y * z
The following useful theorems can be proved from the above set of axioms.
* 'overlay' has 'empty' as the identity and is idempotent:
> x + empty == x
> empty + x == x
> x + x == x
* Absorption and saturation of 'connect':
> x * y + x + y == x * y
> x * x * x == x * x
When specifying the time and memory complexity of graph algorithms, /n/ will
denote the number of vertices in the graph, /m/ will denote the number of
edges in the graph, and /s/ will denote the /size/ of the corresponding
graph expression. For example, if g is a 'Fold' then /n/, /m/ and /s/ can be
computed as follows:
@n == 'vertexCount' g
m == 'edgeCount' g
s == 'size' g@
Note that 'size' is slightly different from the 'length' method of the
'Foldable' type class, as the latter does not count 'empty' leaves of the
expression:
@'length' 'empty' == 0
'size' 'empty' == 1
'length' ('vertex' x) == 1
'size' ('vertex' x) == 1
'length' ('empty' + 'empty') == 0
'size' ('empty' + 'empty') == 2@
The 'size' of any graph is positive, and the difference @('size' g - 'length' g)@
corresponds to the number of occurrences of 'empty' in an expression @g@.
Converting a 'Fold' to the corresponding 'AM.AdjacencyMap' takes /O(s + m * log(m))/
time and /O(s + m)/ memory. This is also the complexity of the graph equality test,
because it is currently implemented by converting graph expressions to canonical
representations based on adjacency maps.
-}
newtype Fold a = Fold { runFold :: forall b. b -> (a -> b) -> (b -> b -> b) -> (b -> b -> b) -> b }
instance (Ord a, Show a) => Show (Fold a) where
show f = show (C.toGraph f :: AM.AdjacencyMap a)
instance Ord a => Eq (Fold a) where
x == y = C.toGraph x == (C.toGraph y :: AM.AdjacencyMap a)
instance C.Graph (Fold a) where
type Vertex (Fold a) = a
empty = Fold $ \e _ _ _ -> e
vertex x = Fold $ \_ v _ _ -> v x
overlay x y = Fold $ \e v o c -> runFold x e v o c `o` runFold y e v o c
connect x y = Fold $ \e v o c -> runFold x e v o c `c` runFold y e v o c
instance Num a => Num (Fold a) where
fromInteger = vertex . fromInteger
(+) = overlay
(*) = connect
signum = const empty
abs = id
negate = id
instance Functor Fold where
fmap = gmap
instance Applicative Fold where
pure = vertex
(<*>) = ap
instance Alternative Fold where
empty = empty
(<|>) = overlay
instance MonadPlus Fold where
mzero = empty
mplus = overlay
instance Monad Fold where
return = vertex
(>>=) = bind
instance H.Graph Fold where
connect = connect
instance Foldable Fold where
foldMap f = foldg mempty f mappend mappend
instance Traversable Fold where
traverse f = foldg (pure empty) (fmap vertex . f) (liftA2 overlay) (liftA2 connect)
instance C.ToGraph (Fold a) where
type ToVertex (Fold a) = a
foldg e v o c g = runFold g e v o c
instance H.ToGraph Fold where
toGraph = foldg H.empty H.vertex H.overlay H.connect
-- | Construct the /empty graph/.
-- Complexity: /O(1)/ time, memory and size.
--
-- @
-- 'isEmpty' empty == True
-- 'hasVertex' x empty == False
-- 'vertexCount' empty == 0
-- 'edgeCount' empty == 0
-- 'size' empty == 1
-- @
empty :: C.Graph g => g
empty = C.empty
-- | Construct the graph comprising /a single isolated vertex/.
-- Complexity: /O(1)/ time, memory and size.
--
-- @
-- 'isEmpty' (vertex x) == False
-- 'hasVertex' x (vertex x) == True
-- 'vertexCount' (vertex x) == 1
-- 'edgeCount' (vertex x) == 0
-- 'size' (vertex x) == 1
-- @
vertex :: C.Graph g => C.Vertex g -> g
vertex = C.vertex
-- | Construct the graph comprising /a single edge/.
-- Complexity: /O(1)/ time, memory and size.
--
-- @
-- edge x y == 'connect' ('vertex' x) ('vertex' y)
-- 'hasEdge' x y (edge x y) == True
-- 'edgeCount' (edge x y) == 1
-- 'vertexCount' (edge 1 1) == 1
-- 'vertexCount' (edge 1 2) == 2
-- @
edge :: C.Graph g => C.Vertex g -> C.Vertex g -> g
edge = C.edge
-- | /Overlay/ two graphs. This is a commutative, associative and idempotent
-- operation with the identity 'empty'.
-- Complexity: /O(1)/ time and memory, /O(s1 + s2)/ size.
--
-- @
-- 'isEmpty' (overlay x y) == 'isEmpty' x && 'isEmpty' y
-- 'hasVertex' z (overlay x y) == 'hasVertex' z x || 'hasVertex' z y
-- 'vertexCount' (overlay x y) >= 'vertexCount' x
-- 'vertexCount' (overlay x y) <= 'vertexCount' x + 'vertexCount' y
-- 'edgeCount' (overlay x y) >= 'edgeCount' x
-- 'edgeCount' (overlay x y) <= 'edgeCount' x + 'edgeCount' y
-- 'size' (overlay x y) == 'size' x + 'size' y
-- 'vertexCount' (overlay 1 2) == 2
-- 'edgeCount' (overlay 1 2) == 0
-- @
overlay :: C.Graph g => g -> g -> g
overlay = C.overlay
-- | /Connect/ two graphs. This is an associative operation with the identity
-- 'empty', which distributes over 'overlay' and obeys the decomposition axiom.
-- Complexity: /O(1)/ time and memory, /O(s1 + s2)/ size. Note that the number
-- of edges in the resulting graph is quadratic with respect to the number of
-- vertices of the arguments: /m = O(m1 + m2 + n1 * n2)/.
--
-- @
-- 'isEmpty' (connect x y) == 'isEmpty' x && 'isEmpty' y
-- 'hasVertex' z (connect x y) == 'hasVertex' z x || 'hasVertex' z y
-- 'vertexCount' (connect x y) >= 'vertexCount' x
-- 'vertexCount' (connect x y) <= 'vertexCount' x + 'vertexCount' y
-- 'edgeCount' (connect x y) >= 'edgeCount' x
-- 'edgeCount' (connect x y) >= 'edgeCount' y
-- 'edgeCount' (connect x y) >= 'vertexCount' x * 'vertexCount' y
-- 'edgeCount' (connect x y) <= 'vertexCount' x * 'vertexCount' y + 'edgeCount' x + 'edgeCount' y
-- 'size' (connect x y) == 'size' x + 'size' y
-- 'vertexCount' (connect 1 2) == 2
-- 'edgeCount' (connect 1 2) == 1
-- @
connect :: C.Graph g => g -> g -> g
connect = C.connect
-- | Construct the graph comprising a given list of isolated vertices.
-- Complexity: /O(L)/ time, memory and size, where /L/ is the length of the
-- given list.
--
-- @
-- vertices [] == 'empty'
-- vertices [x] == 'vertex' x
-- 'hasVertex' x . vertices == 'elem' x
-- 'vertexCount' . vertices == 'length' . 'Data.List.nub'
-- 'vertexSet' . vertices == Set.'Set.fromList'
-- @
vertices :: C.Graph g => [C.Vertex g] -> g
vertices = C.vertices
-- | Construct the graph from a list of edges.
-- Complexity: /O(L)/ time, memory and size, where /L/ is the length of the
-- given list.
--
-- @
-- edges [] == 'empty'
-- edges [(x,y)] == 'edge' x y
-- 'edgeCount' . edges == 'length' . 'Data.List.nub'
-- @
edges :: C.Graph g => [(C.Vertex g, C.Vertex g)] -> g
edges = C.edges
-- | Overlay a given list of graphs.
-- Complexity: /O(L)/ time and memory, and /O(S)/ size, where /L/ is the length
-- of the given list, and /S/ is the sum of sizes of the graphs in the list.
--
-- @
-- overlays [] == 'empty'
-- overlays [x] == x
-- overlays [x,y] == 'overlay' x y
-- overlays == 'foldr' 'overlay' 'empty'
-- 'isEmpty' . overlays == 'all' 'isEmpty'
-- @
overlays :: C.Graph g => [g] -> g
overlays = C.overlays
-- | Connect a given list of graphs.
-- Complexity: /O(L)/ time and memory, and /O(S)/ size, where /L/ is the length
-- of the given list, and /S/ is the sum of sizes of the graphs in the list.
--
-- @
-- connects [] == 'empty'
-- connects [x] == x
-- connects [x,y] == 'connect' x y
-- connects == 'foldr' 'connect' 'empty'
-- 'isEmpty' . connects == 'all' 'isEmpty'
-- @
connects :: C.Graph g => [g] -> g
connects = C.connects
-- | Generalised graph folding: recursively collapse a 'Fold' by applying
-- the provided functions to the leaves and internal nodes of the expression.
-- The order of arguments is: empty, vertex, overlay and connect.
-- Complexity: /O(s)/ applications of given functions. As an example, the
-- complexity of 'size' is /O(s)/, since all functions have cost /O(1)/.
--
-- @
-- foldg 'empty' 'vertex' 'overlay' 'connect' == id
-- foldg 'empty' 'vertex' 'overlay' (flip 'connect') == 'transpose'
-- foldg [] return (++) (++) == 'Data.Foldable.toList'
-- foldg 0 (const 1) (+) (+) == 'Data.Foldable.length'
-- foldg 1 (const 1) (+) (+) == 'size'
-- foldg True (const False) (&&) (&&) == 'isEmpty'
-- @
foldg :: b -> (a -> b) -> (b -> b -> b) -> (b -> b -> b) -> Fold a -> b
foldg = C.foldg
-- | Check if a graph is empty. A convenient alias for 'null'.
-- Complexity: /O(s)/ time.
--
-- @
-- isEmpty 'empty' == True
-- isEmpty ('overlay' 'empty' 'empty') == True
-- isEmpty ('vertex' x) == False
-- isEmpty ('removeVertex' x $ 'vertex' x) == True
-- isEmpty ('removeEdge' x y $ 'edge' x y) == False
-- @
isEmpty :: Fold a -> Bool
isEmpty = H.isEmpty
-- | The /size/ of a graph, i.e. the number of leaves of the expression
-- including 'empty' leaves.
-- Complexity: /O(s)/ time.
--
-- @
-- size 'empty' == 1
-- size ('vertex' x) == 1
-- size ('overlay' x y) == size x + size y
-- size ('connect' x y) == size x + size y
-- size x >= 1
-- size x >= 'vertexCount' x
-- @
size :: Fold a -> Int
size = foldg 1 (const 1) (+) (+)
-- | Check if a graph contains a given vertex. A convenient alias for `elem`.
-- Complexity: /O(s)/ time.
--
-- @
-- hasVertex x 'empty' == False
-- hasVertex x ('vertex' x) == True
-- hasVertex 1 ('vertex' 2) == False
-- hasVertex x . 'removeVertex' x == const False
-- @
hasVertex :: Eq a => a -> Fold a -> Bool
hasVertex = H.hasVertex
-- | Check if a graph contains a given edge.
-- Complexity: /O(s)/ time.
--
-- @
-- hasEdge x y 'empty' == False
-- hasEdge x y ('vertex' z) == False
-- hasEdge x y ('edge' x y) == True
-- hasEdge x y . 'removeEdge' x y == const False
-- hasEdge x y == 'elem' (x,y) . 'edgeList'
-- @
hasEdge :: Ord a => a -> a -> Fold a -> Bool
hasEdge = H.hasEdge
-- | The number of vertices in a graph.
-- Complexity: /O(s * log(n))/ time.
--
-- @
-- vertexCount 'empty' == 0
-- vertexCount ('vertex' x) == 1
-- vertexCount == 'length' . 'vertexList'
-- @
vertexCount :: Ord a => Fold a -> Int
vertexCount = length . vertexList
-- | The number of edges in a graph.
-- Complexity: /O(s + m * log(m))/ time. Note that the number of edges /m/ of a
-- graph can be quadratic with respect to the expression size /s/.
--
-- @
-- edgeCount 'empty' == 0
-- edgeCount ('vertex' x) == 0
-- edgeCount ('edge' x y) == 1
-- edgeCount == 'length' . 'edgeList'
-- @
edgeCount :: Ord a => Fold a -> Int
edgeCount = length . edgeList
-- | The sorted list of vertices of a given graph.
-- Complexity: /O(s * log(n))/ time and /O(n)/ memory.
--
-- @
-- vertexList 'empty' == []
-- vertexList ('vertex' x) == [x]
-- vertexList . 'vertices' == 'Data.List.nub' . 'Data.List.sort'
-- @
vertexList :: Ord a => Fold a -> [a]
vertexList = Set.toAscList . vertexSet
-- | The sorted list of edges of a graph.
-- Complexity: /O(s + m * log(m))/ time and /O(m)/ memory. Note that the number of
-- edges /m/ of a graph can be quadratic with respect to the expression size /s/.
--
-- @
-- edgeList 'empty' == []
-- edgeList ('vertex' x) == []
-- edgeList ('edge' x y) == [(x,y)]
-- edgeList ('star' 2 [3,1]) == [(2,1), (2,3)]
-- edgeList . 'edges' == 'Data.List.nub' . 'Data.List.sort'
-- edgeList . 'transpose' == 'Data.List.sort' . map 'Data.Tuple.swap' . edgeList
-- @
edgeList :: Ord a => Fold a -> [(a, a)]
edgeList = AM.edgeList . C.toGraph
-- | The set of vertices of a given graph.
-- Complexity: /O(s * log(n))/ time and /O(n)/ memory.
--
-- @
-- vertexSet 'empty' == Set.'Set.empty'
-- vertexSet . 'vertex' == Set.'Set.singleton'
-- vertexSet . 'vertices' == Set.'Set.fromList'
-- vertexSet . 'clique' == Set.'Set.fromList'
-- @
vertexSet :: Ord a => Fold a -> Set.Set a
vertexSet = H.vertexSet
-- | The set of vertices of a given graph. Like 'vertexSet' but specialised for
-- graphs with vertices of type 'Int'.
-- Complexity: /O(s * log(n))/ time and /O(n)/ memory.
--
-- @
-- vertexIntSet 'empty' == IntSet.'IntSet.empty'
-- vertexIntSet . 'vertex' == IntSet.'IntSet.singleton'
-- vertexIntSet . 'vertices' == IntSet.'IntSet.fromList'
-- vertexIntSet . 'clique' == IntSet.'IntSet.fromList'
-- @
vertexIntSet :: Fold Int -> IntSet.IntSet
vertexIntSet = H.vertexIntSet
-- | The set of edges of a given graph.
-- Complexity: /O(s * log(m))/ time and /O(m)/ memory.
--
-- @
-- edgeSet 'empty' == Set.'Set.empty'
-- edgeSet ('vertex' x) == Set.'Set.empty'
-- edgeSet ('edge' x y) == Set.'Set.singleton' (x,y)
-- edgeSet . 'edges' == Set.'Set.fromList'
-- @
edgeSet :: Ord a => Fold a -> Set.Set (a, a)
edgeSet = R.edgeSet . C.toGraph
-- | Construct a /mesh graph/ from two lists of vertices.
-- Complexity: /O(L1 * L2)/ time, memory and size, where /L1/ and /L2/ are the
-- lengths of the given lists.
--
-- @
-- mesh xs [] == 'empty'
-- mesh [] ys == 'empty'
-- mesh [x] [y] == 'vertex' (x, y)
-- mesh xs ys == 'box' ('path' xs) ('path' ys)
-- mesh [1..3] "ab" == 'edges' [ ((1,\'a\'),(1,\'b\')), ((1,\'a\'),(2,\'a\')), ((1,\'b\'),(2,\'b\')), ((2,\'a\'),(2,\'b\'))
-- , ((2,\'a\'),(3,\'a\')), ((2,\'b\'),(3,\'b\')), ((3,\'a\'),(3,\'b\')) ]
-- @
mesh :: (C.Graph g, C.Vertex g ~ (a, b)) => [a] -> [b] -> g
mesh xs ys = C.path xs `box` C.path ys
-- | Construct a /torus graph/ from two lists of vertices.
-- Complexity: /O(L1 * L2)/ time, memory and size, where /L1/ and /L2/ are the
-- lengths of the given lists.
--
-- @
-- torus xs [] == 'empty'
-- torus [] ys == 'empty'
-- torus [x] [y] == 'edge' (x, y) (x, y)
-- torus xs ys == 'box' ('circuit' xs) ('circuit' ys)
-- torus [1,2] "ab" == 'edges' [ ((1,\'a\'),(1,\'b\')), ((1,\'a\'),(2,\'a\')), ((1,\'b\'),(1,\'a\')), ((1,\'b\'),(2,\'b\'))
-- , ((2,\'a\'),(1,\'a\')), ((2,\'a\'),(2,\'b\')), ((2,\'b\'),(1,\'b\')), ((2,\'b\'),(2,\'a\')) ]
-- @
torus :: (C.Graph g, C.Vertex g ~ (a, b)) => [a] -> [b] -> g
torus xs ys = C.circuit xs `box` C.circuit ys
-- | Construct a /De Bruijn graph/ of a given non-negative dimension using symbols
-- from a given alphabet.
-- Complexity: /O(A^(D + 1))/ time, memory and size, where /A/ is the size of the
-- alphabet and /D/ is the dimension of the graph.
--
-- @
-- deBruijn 0 xs == 'edge' [] []
-- n > 0 ==> deBruijn n [] == 'empty'
-- deBruijn 1 [0,1] == 'edges' [ ([0],[0]), ([0],[1]), ([1],[0]), ([1],[1]) ]
-- deBruijn 2 "0" == 'edge' "00" "00"
-- deBruijn 2 "01" == 'edges' [ ("00","00"), ("00","01"), ("01","10"), ("01","11")
-- , ("10","00"), ("10","01"), ("11","10"), ("11","11") ]
-- 'transpose' (deBruijn n xs) == 'gmap' 'reverse' $ deBruijn n xs
-- 'vertexCount' (deBruijn n xs) == ('length' $ 'Data.List.nub' xs)^n
-- n > 0 ==> 'edgeCount' (deBruijn n xs) == ('length' $ 'Data.List.nub' xs)^(n + 1)
-- @
deBruijn :: (C.Graph g, C.Vertex g ~ [a]) => Int -> [a] -> g
deBruijn 0 _ = edge [] []
deBruijn len alphabet = bind skeleton expand
where
overlaps = mapM (const alphabet) [2..len]
skeleton = C.edges [ (Left s, Right s) | s <- overlaps ]
expand v = C.vertices [ either ([a] ++) (++ [a]) v | a <- alphabet ]
-- | Remove a vertex from a given graph.
-- Complexity: /O(s)/ time, memory and size.
--
-- @
-- removeVertex x ('vertex' x) == 'empty'
-- removeVertex 1 ('vertex' 2) == 'vertex' 2
-- removeVertex x ('edge' x x) == 'empty'
-- removeVertex 1 ('edge' 1 2) == 'vertex' 2
-- removeVertex x . removeVertex x == removeVertex x
-- @
removeVertex :: (Eq (C.Vertex g), C.Graph g) => C.Vertex g -> Fold (C.Vertex g) -> g
removeVertex v = induce (/= v)
-- | Remove an edge from a given graph.
-- Complexity: /O(s)/ time, memory and size.
--
-- @
-- removeEdge x y ('edge' x y) == 'vertices' [x, y]
-- removeEdge x y . removeEdge x y == removeEdge x y
-- removeEdge x y . 'removeVertex' x == 'removeVertex' x
-- removeEdge 1 1 (1 * 1 * 2 * 2) == 1 * 2 * 2
-- removeEdge 1 2 (1 * 1 * 2 * 2) == 1 * 1 + 2 * 2
-- 'size' (removeEdge x y z) <= 3 * 'size' z
-- @
removeEdge :: (Eq (C.Vertex g), C.Graph g) => C.Vertex g -> C.Vertex g -> Fold (C.Vertex g) -> g
removeEdge s t = filterContext s (/=s) (/=t)
-- TODO: Export
-- | Filter vertices in a subgraph context.
filterContext :: (Eq (C.Vertex g), C.Graph g) => C.Vertex g -> (C.Vertex g -> Bool)
-> (C.Vertex g -> Bool) -> Fold (C.Vertex g) -> g
filterContext s i o g = maybe (C.toGraph g) go $ context (==s) g
where
go (Context is os) = overlays [ induce (/=s) g
, C.starTranspose s (filter i is)
, C.star s (filter o os) ]
-- | The function @'replaceVertex' x y@ replaces vertex @x@ with vertex @y@ in a
-- given graph expression. If @y@ already exists, @x@ and @y@ will be merged.
-- Complexity: /O(s)/ time, memory and size.
--
-- @
-- replaceVertex x x == id
-- replaceVertex x y ('vertex' x) == 'vertex' y
-- replaceVertex x y == 'mergeVertices' (== x) y
-- @
replaceVertex :: (Eq (C.Vertex g), C.Graph g) => C.Vertex g -> C.Vertex g -> Fold (C.Vertex g) -> g
replaceVertex u v = gmap $ \w -> if w == u then v else w
-- | Merge vertices satisfying a given predicate into a given vertex.
-- Complexity: /O(s)/ time, memory and size, assuming that the predicate takes
-- /O(1)/ to be evaluated.
--
-- @
-- mergeVertices (const False) x == id
-- mergeVertices (== x) y == 'replaceVertex' x y
-- mergeVertices even 1 (0 * 2) == 1 * 1
-- mergeVertices odd 1 (3 + 4 * 5) == 4 * 1
-- @
mergeVertices :: C.Graph g => (C.Vertex g -> Bool) -> C.Vertex g -> Fold (C.Vertex g) -> g
mergeVertices p v = gmap $ \u -> if p u then v else u
-- | Split a vertex into a list of vertices with the same connectivity.
-- Complexity: /O(s + k * L)/ time, memory and size, where /k/ is the number of
-- occurrences of the vertex in the expression and /L/ is the length of the
-- given list.
--
-- @
-- splitVertex x [] == 'removeVertex' x
-- splitVertex x [x] == id
-- splitVertex x [y] == 'replaceVertex' x y
-- splitVertex 1 [0,1] $ 1 * (2 + 3) == (0 + 1) * (2 + 3)
-- @
splitVertex :: (Eq (C.Vertex g), C.Graph g) => C.Vertex g -> [C.Vertex g] -> Fold (C.Vertex g) -> g
splitVertex v vs g = bind g $ \u -> if u == v then C.vertices vs else C.vertex u
-- | Transpose a given graph.
-- Complexity: /O(s)/ time, memory and size.
--
-- @
-- transpose 'empty' == 'empty'
-- transpose ('vertex' x) == 'vertex' x
-- transpose ('edge' x y) == 'edge' y x
-- transpose . transpose == id
-- transpose ('box' x y) == 'box' (transpose x) (transpose y)
-- 'edgeList' . transpose == 'Data.List.sort' . map 'Data.Tuple.swap' . 'edgeList'
-- @
transpose :: C.Graph g => Fold (C.Vertex g) -> g
transpose = foldg C.empty C.vertex C.overlay (flip C.connect)
-- | Transform a given graph by applying a function to each of its vertices.
-- This is similar to 'fmap' but can be used with non-fully-parametric graphs.
--
-- @
-- gmap f 'empty' == 'empty'
-- gmap f ('vertex' x) == 'vertex' (f x)
-- gmap f ('edge' x y) == 'edge' (f x) (f y)
-- gmap id == id
-- gmap f . gmap g == gmap (f . g)
-- @
gmap :: C.Graph g => (a -> C.Vertex g) -> Fold a -> g
gmap f = foldg C.empty (C.vertex . f) C.overlay C.connect
-- | Transform a given graph by substituting each of its vertices with a subgraph.
-- This is similar to Monad's bind '>>=' but can be used with non-fully-parametric
-- graphs.
--
-- @
-- bind 'empty' f == 'empty'
-- bind ('vertex' x) f == f x
-- bind ('edge' x y) f == 'connect' (f x) (f y)
-- bind ('vertices' xs) f == 'overlays' ('map' f xs)
-- bind x (const 'empty') == 'empty'
-- bind x 'vertex' == x
-- bind (bind x f) g == bind x (\\y -> bind (f y) g)
-- @
bind :: C.Graph g => Fold a -> (a -> g) -> g
bind g f = foldg C.empty f C.overlay C.connect g
-- | Construct the /induced subgraph/ of a given graph by removing the
-- vertices that do not satisfy a given predicate.
-- Complexity: /O(s)/ time, memory and size, assuming that the predicate takes
-- /O(1)/ to be evaluated.
--
-- @
-- induce (const True ) x == x
-- induce (const False) x == 'empty'
-- induce (/= x) == 'removeVertex' x
-- induce p . induce q == induce (\\x -> p x && q x)
-- 'isSubgraphOf' (induce p x) x == True
-- @
induce :: C.Graph g => (C.Vertex g -> Bool) -> Fold (C.Vertex g) -> g
induce p = C.toGraph . foldg empty (\x -> if p x then vertex x else empty) (k overlay) (k connect)
where
k f x y | isEmpty x = y -- Constant folding to get rid of Empty leaves
| isEmpty y = x
| otherwise = f x y
-- | Simplify a graph expression. Semantically, this is the identity function,
-- but it simplifies a given polymorphic graph expression according to the laws
-- of the algebra. The function does not compute the simplest possible expression,
-- but uses heuristics to obtain useful simplifications in reasonable time.
-- Complexity: the function performs /O(s)/ graph comparisons. It is guaranteed
-- that the size of the result does not exceed the size of the given expression.
-- Below the operator @~>@ denotes the /is simplified to/ relation.
--
-- @
-- simplify == id
-- 'size' (simplify x) <= 'size' x
-- simplify 'empty' ~> 'empty'
-- simplify 1 ~> 1
-- simplify (1 + 1) ~> 1
-- simplify (1 + 2 + 1) ~> 1 + 2
-- simplify (1 * 1 * 1) ~> 1 * 1
-- @
simplify :: (Eq g, C.Graph g) => Fold (C.Vertex g) -> g
simplify = foldg C.empty C.vertex (simple C.overlay) (simple C.connect)
simple :: Eq g => (g -> g -> g) -> g -> g -> g
simple op x y
| x == z = x
| y == z = y
| otherwise = z
where
z = op x y
-- | Compute the /Cartesian product/ of graphs.
-- Complexity: /O(s1 * s2)/ time, memory and size, where /s1/ and /s2/ are the
-- sizes of the given graphs.
--
-- @
-- box ('path' [0,1]) ('path' "ab") == 'edges' [ ((0,\'a\'), (0,\'b\'))
-- , ((0,\'a\'), (1,\'a\'))
-- , ((0,\'b\'), (1,\'b\'))
-- , ((1,\'a\'), (1,\'b\')) ]
-- @
-- Up to an isomorphism between the resulting vertex types, this operation
-- is /commutative/, /associative/, /distributes/ over 'overlay', has singleton
-- graphs as /identities/ and 'empty' as the /annihilating zero/. Below @~~@
-- stands for the equality up to an isomorphism, e.g. @(x, ()) ~~ x@.
--
-- @
-- box x y ~~ box y x
-- box x (box y z) ~~ box (box x y) z
-- box x ('overlay' y z) == 'overlay' (box x y) (box x z)
-- box x ('vertex' ()) ~~ x
-- box x 'empty' ~~ 'empty'
-- 'transpose' (box x y) == box ('transpose' x) ('transpose' y)
-- 'vertexCount' (box x y) == 'vertexCount' x * 'vertexCount' y
-- 'edgeCount' (box x y) <= 'vertexCount' x * 'edgeCount' y + 'edgeCount' x * 'vertexCount' y
-- @
box :: (C.Graph g, C.Vertex g ~ (a, b)) => Fold a -> Fold b -> g
box x y = C.overlays $ xs ++ ys
where
xs = map (\b -> gmap (,b) x) $ toList y
ys = map (\a -> gmap (a,) y) $ toList x