Skip to content

Commit

Permalink
Major refactoring of non-empty graphs and other changes to API and do…
Browse files Browse the repository at this point in the history
…cumentation (#136)

* Add Algebra.Graph.NonEmpty.AdjacencyMap

* Switch from NonEmptyGraph to NonEmpty.Graph

* Remove vertexIntSet from the API of basic graph datatypes

* Remove `Algebra.Graph.adjacencyMap` and `Algebra.Graph.adjacencyIntMap`

* Improve documentation of Num instances

* A lot of other improvements to docs and tests

* Drop unused extensions

* Update change log
  • Loading branch information
snowleopard committed Oct 29, 2018
1 parent f781409 commit 9f64c82
Show file tree
Hide file tree
Showing 22 changed files with 1,935 additions and 684 deletions.
5 changes: 5 additions & 0 deletions CHANGES.md
Expand Up @@ -2,6 +2,11 @@

## 0.3

* #136: Rename `Algebra.Graph.NonEmpty.NonEmptyGraph` to `Algebra.Graph.NonEmpty.Graph`.
* #136: Add `Algebra.Graph.NonEmpty.AdjacencyMap`.
* #136: Remove `vertexIntSet` from the API of basic graph data types. Also
remove `Algebra.Graph.adjacencyMap` and `Algebra.Graph.adjacencyIntMap`.
This functionality is still available from the type class `ToGraph`.
* #126, #131: Implement custom `Ord` instance.
* #122, #125: Further work on labelled algebraic graphs.
* #121: Drop `Foldable` and `Traversable` instances.
Expand Down
13 changes: 7 additions & 6 deletions algebraic-graphs.cabal
Expand Up @@ -59,15 +59,15 @@ source-repository head
library
hs-source-dirs: src
exposed-modules: Algebra.Graph,
Algebra.Graph.AdjacencyIntMap,
Algebra.Graph.AdjacencyIntMap.Internal,
Algebra.Graph.AdjacencyMap,
Algebra.Graph.AdjacencyMap.Internal,
Algebra.Graph.Class,
Algebra.Graph.Export,
Algebra.Graph.Export.Dot,
Algebra.Graph.Fold,
Algebra.Graph.HigherKinded.Class,
Algebra.Graph.AdjacencyIntMap,
Algebra.Graph.AdjacencyIntMap.Internal,
Algebra.Graph.Internal,
Algebra.Graph.Label,
Algebra.Graph.Labelled,
Expand All @@ -76,6 +76,8 @@ library
Algebra.Graph.Labelled.Example.Automaton,
Algebra.Graph.Labelled.Example.Network,
Algebra.Graph.NonEmpty,
Algebra.Graph.NonEmpty.AdjacencyMap,
Algebra.Graph.NonEmpty.AdjacencyMap.Internal,
Algebra.Graph.Relation,
Algebra.Graph.Relation.Internal,
Algebra.Graph.Relation.InternalDerived,
Expand All @@ -100,9 +102,7 @@ library
TupleSections
TypeFamilies
other-extensions: CPP
DeriveFoldable
DeriveFunctor
DeriveTraversable
OverloadedStrings
RecordWildCards
GHC-options: -Wall
Expand All @@ -119,16 +119,17 @@ test-suite test-alga
main-is: Main.hs
other-modules: Algebra.Graph.Test,
Algebra.Graph.Test.API,
Algebra.Graph.Test.AdjacencyIntMap,
Algebra.Graph.Test.AdjacencyMap,
Algebra.Graph.Test.Arbitrary,
Algebra.Graph.Test.Export,
Algebra.Graph.Test.Fold,
Algebra.Graph.Test.Generic,
Algebra.Graph.Test.Graph,
Algebra.Graph.Test.AdjacencyIntMap,
Algebra.Graph.Test.Internal,
Algebra.Graph.Test.NonEmpty.AdjacencyMap,
Algebra.Graph.Test.NonEmpty.Graph,
Algebra.Graph.Test.Labelled.AdjacencyMap,
Algebra.Graph.Test.NonEmptyGraph,
Algebra.Graph.Test.Relation,
Data.Graph.Test.Typed
build-depends: algebraic-graphs,
Expand Down
70 changes: 26 additions & 44 deletions src/Algebra/Graph.hs
Expand Up @@ -33,8 +33,7 @@ module Algebra.Graph (

-- * Graph properties
isEmpty, size, hasVertex, hasEdge, vertexCount, edgeCount, vertexList,
edgeList, vertexSet, vertexIntSet, edgeSet, adjacencyList, adjacencyMap,
adjacencyIntMap,
edgeList, vertexSet, edgeSet, adjacencyList,

-- * Standard families of graphs
path, circuit, clique, biclique, star, stars, tree, forest, mesh, torus,
Expand Down Expand Up @@ -65,11 +64,6 @@ import Data.Tree

import Algebra.Graph.Internal

import Data.IntMap (IntMap)
import Data.IntSet (IntSet)
import Data.Map (Map)
import Data.Set (Set)

import qualified Algebra.Graph.AdjacencyMap as AM
import qualified Algebra.Graph.AdjacencyIntMap as AIM
import qualified Control.Applicative as Ap
Expand All @@ -87,6 +81,14 @@ instance as a convenient notation for working with graphs:
> 1 + 2 * 3 == Overlay (Vertex 1) (Connect (Vertex 2) (Vertex 3))
> 1 * (2 + 3) == Connect (Vertex 1) (Overlay (Vertex 2) (Vertex 3))
__Note:__ the 'Num' instance does not satisfy several "customary laws" of 'Num',
which dictate that 'fromInteger' @0@ and 'fromInteger' @1@ should act as
additive and multiplicative identities, and 'negate' as additive inverse.
Nevertheless, overloading 'fromInteger', '+' and '*' is very convenient when
working with algebraic graphs; we hope that in future Haskell's Prelude will
provide a more fine-grained class hierarchy for algebraic structures, which we
would be able to utilise without violating any laws.
The 'Eq' instance is currently implemented using the 'AM.AdjacencyMap' as the
/canonical graph representation/ and satisfies all axioms of algebraic graphs:
Expand Down Expand Up @@ -390,10 +392,10 @@ concatg combine = fromMaybe empty . foldr1Safe combine
--
-- @
-- foldg 'empty' 'vertex' 'overlay' 'connect' == id
-- foldg 'empty' 'vertex' 'overlay' (flip 'connect') == 'transpose'
-- foldg 1 (const 1) (+) (+) == 'size'
-- foldg True (const False) (&&) (&&) == 'isEmpty'
-- foldg False ((==) x) (||) (||) == 'hasVertex x'
-- foldg 'empty' 'vertex' 'overlay' ('flip' 'connect') == 'transpose'
-- foldg 1 ('const' 1) (+) (+) == 'size'
-- foldg True ('const' False) (&&) (&&) == 'isEmpty'
-- foldg False (== x) (||) (||) == 'hasVertex' x
-- @
foldg :: b -> (a -> b) -> (b -> b -> b) -> (b -> b -> b) -> Graph a -> b
foldg e v o c = go
Expand Down Expand Up @@ -468,14 +470,14 @@ isEmpty = foldg True (const False) (&&) (&&)
size :: Graph a -> Int
size = foldg 1 (const 1) (+) (+)

-- | Check if a graph contains a given vertex. A convenient alias for `elem`.
-- | Check if a graph contains a given vertex.
-- 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 x . 'removeVertex' x == 'const' False
-- @
{-# SPECIALISE hasVertex :: Int -> Graph Int -> Bool #-}
hasVertex :: Eq a => a -> Graph a -> Bool
Expand All @@ -488,7 +490,7 @@ hasVertex x = foldg False (==x) (||) (||)
-- 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 . 'removeEdge' x y == 'const' False
-- hasEdge x y == 'elem' (x,y) . 'edgeList'
-- @
{-# SPECIALISE hasEdge :: Int -> Int -> Graph Int -> Bool #-}
Expand Down Expand Up @@ -570,7 +572,7 @@ vertexIntList = IntSet.toList . vertexIntSet
-- 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 . 'transpose' == 'Data.List.sort' . 'map' 'Data.Tuple.swap' . edgeList
-- @
{-# INLINE [1] edgeList #-}
{-# RULES "edgeList/Int" edgeList = edgeIntList #-}
Expand All @@ -593,16 +595,7 @@ edgeIntList = AIM.edgeList . toAdjacencyIntMap
vertexSet :: Ord a => Graph a -> Set.Set a
vertexSet = foldg Set.empty Set.singleton Set.union Set.union

-- | 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'
-- @
-- Like 'vertexSet' but specialised for graphs with vertices of type 'Int'.
vertexIntSet :: Graph Int -> IntSet.IntSet
vertexIntSet = foldg IntSet.empty IntSet.singleton IntSet.union IntSet.union

Expand Down Expand Up @@ -638,24 +631,13 @@ edgeIntSet = AIM.edgeSet . toAdjacencyIntMap
adjacencyList :: Ord a => Graph a -> [(a, [a])]
adjacencyList = AM.adjacencyList . toAdjacencyMap

-- | The /adjacency map/ of a graph: each vertex is associated with a set of its
-- direct successors.
-- 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/.
adjacencyMap :: Ord a => Graph a -> Map a (Set a)
adjacencyMap = AM.adjacencyMap . toAdjacencyMap

-- TODO: This is a very inefficient implementation. Find a way to construct an
-- adjacency map directly, without building intermediate representations for all
-- subgraphs.
-- | Convert a graph to 'AM.AdjacencyMap'.
toAdjacencyMap :: Ord a => Graph a -> AM.AdjacencyMap a
toAdjacencyMap = foldg AM.empty AM.vertex AM.overlay AM.connect

-- | Like 'adjacencyMap' but specialised for graphs with vertices of type 'Int'.
adjacencyIntMap :: Graph Int -> IntMap IntSet
adjacencyIntMap = AIM.adjacencyIntMap . toAdjacencyIntMap

-- | Like @toAdjacencyMap@ but specialised for graphs with vertices of type 'Int'.
toAdjacencyIntMap :: Graph Int -> AIM.AdjacencyIntMap
toAdjacencyIntMap = foldg AIM.empty AIM.vertex AIM.overlay AIM.connect
Expand Down Expand Up @@ -746,7 +728,7 @@ star x ys = connect (vertex x) (vertices ys)
-- stars [(x, [])] == 'vertex' x
-- stars [(x, [y])] == 'edge' x y
-- stars [(x, ys)] == 'star' x ys
-- stars == 'overlays' . map (uncurry 'star')
-- stars == 'overlays' . 'map' ('uncurry' 'star')
-- stars . 'adjacencyList' == id
-- 'overlay' (stars xs) (stars ys) == stars (xs ++ ys)
-- @
Expand Down Expand Up @@ -777,7 +759,7 @@ tree (Node x f ) = star x (map rootLabel f)
-- forest [] == 'empty'
-- forest [x] == 'tree' x
-- forest [Node 1 [Node 2 [], Node 3 []], Node 4 [Node 5 []]] == 'edges' [(1,2), (1,3), (4,5)]
-- forest == 'overlays' . map 'tree'
-- forest == 'overlays' . 'map' 'tree'
-- @
forest :: Tree.Forest a -> Graph a
forest = overlays . map tree
Expand Down Expand Up @@ -908,10 +890,10 @@ replaceVertex u v = fmap $ \w -> if w == u then v else w
-- /O(1)/ to be evaluated.
--
-- @
-- mergeVertices (const False) x == id
-- 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 'even' 1 (0 * 2) == 1 * 1
-- mergeVertices 'odd' 1 (3 + 4 * 5) == 4 * 1
-- @
mergeVertices :: (a -> Bool) -> a -> Graph a -> Graph a
mergeVertices p v = fmap $ \w -> if p w then v else w
Expand Down Expand Up @@ -940,7 +922,7 @@ splitVertex v us g = g >>= \w -> if w == v then vertices us else vertex w
-- 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'
-- 'edgeList' . transpose == 'Data.List.sort' . 'map' 'Data.Tuple.swap' . 'edgeList'
-- @
transpose :: Graph a -> Graph a
transpose = foldg Empty Vertex Overlay (flip Connect)
Expand All @@ -965,8 +947,8 @@ transpose = foldg Empty Vertex Overlay (flip Connect)
-- /O(1)/ to be evaluated.
--
-- @
-- induce (const True ) x == x
-- induce (const False) x == 'empty'
-- 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
Expand Down
24 changes: 12 additions & 12 deletions src/Algebra/Graph/AdjacencyIntMap.hs
Expand Up @@ -163,7 +163,7 @@ isEmpty = IntMap.null . adjacencyIntMap
-- hasVertex x 'empty' == False
-- hasVertex x ('vertex' x) == True
-- hasVertex 1 ('vertex' 2) == False
-- hasVertex x . 'removeVertex' x == const False
-- hasVertex x . 'removeVertex' x == 'const' False
-- @
hasVertex :: Int -> AdjacencyIntMap -> Bool
hasVertex x = IntMap.member x . adjacencyIntMap
Expand All @@ -175,7 +175,7 @@ hasVertex x = IntMap.member x . adjacencyIntMap
-- 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 . 'removeEdge' x y == 'const' False
-- hasEdge x y == 'elem' (x,y) . 'edgeList'
-- @
hasEdge :: Int -> Int -> AdjacencyIntMap -> Bool
Expand Down Expand Up @@ -227,7 +227,7 @@ vertexList = IntMap.keys . adjacencyIntMap
-- 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 . 'transpose' == 'Data.List.sort' . 'map' 'Data.Tuple.swap' . edgeList
-- @
edgeList :: AdjacencyIntMap -> [(Int, Int)]
edgeList (AM m) = [ (x, y) | (x, ys) <- IntMap.toAscList m, y <- IntSet.toAscList ys ]
Expand Down Expand Up @@ -383,7 +383,7 @@ star x ys = connect (vertex x) (vertices ys)
-- stars [(x, [])] == 'vertex' x
-- stars [(x, [y])] == 'edge' x y
-- stars [(x, ys)] == 'star' x ys
-- stars == 'overlays' . map (uncurry 'star')
-- stars == 'overlays' . 'map' ('uncurry' 'star')
-- stars . 'adjacencyList' == id
-- 'overlay' (stars xs) (stars ys) == stars (xs ++ ys)
-- @
Expand Down Expand Up @@ -411,7 +411,7 @@ tree (Node x f ) = star x (map rootLabel f)
-- forest [] == 'empty'
-- forest [x] == 'tree' x
-- forest [Node 1 [Node 2 [], Node 3 []], Node 4 [Node 5 []]] == 'edges' [(1,2), (1,3), (4,5)]
-- forest == 'overlays' . map 'tree'
-- forest == 'overlays' . 'map' 'tree'
-- @
forest :: Forest Int -> AdjacencyIntMap
forest = overlays . map tree
Expand Down Expand Up @@ -459,10 +459,10 @@ replaceVertex u v = gmap $ \w -> if w == u then v else w
-- /O(1)/ to be evaluated.
--
-- @
-- mergeVertices (const False) x == id
-- 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 'even' 1 (0 * 2) == 1 * 1
-- mergeVertices 'odd' 1 (3 + 4 * 5) == 4 * 1
-- @
mergeVertices :: (Int -> Bool) -> Int -> AdjacencyIntMap -> AdjacencyIntMap
mergeVertices p v = gmap $ \u -> if p u then v else u
Expand All @@ -475,7 +475,7 @@ mergeVertices p v = gmap $ \u -> if p u then v else u
-- transpose ('vertex' x) == 'vertex' x
-- transpose ('edge' x y) == 'edge' y x
-- transpose . transpose == id
-- 'edgeList' . transpose == 'Data.List.sort' . map 'Data.Tuple.swap' . 'edgeList'
-- 'edgeList' . transpose == 'Data.List.sort' . 'map' 'Data.Tuple.swap' . 'edgeList'
-- @
transpose :: AdjacencyIntMap -> AdjacencyIntMap
transpose (AM m) = AM $ IntMap.foldrWithKey combine vs m
Expand Down Expand Up @@ -518,8 +518,8 @@ gmap f = AM . IntMap.map (IntSet.map f) . IntMap.mapKeysWith IntSet.union f . ad
-- be evaluated.
--
-- @
-- induce (const True ) x == x
-- induce (const False) x == 'empty'
-- 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
Expand Down Expand Up @@ -617,7 +617,7 @@ reachable x = dfs [x]
-- @
-- topSort (1 * 2 + 3 * 1) == Just [3,1,2]
-- topSort (1 * 2 + 2 * 1) == Nothing
-- fmap (flip 'isTopSortOf' x) (topSort x) /= Just False
-- fmap ('flip' 'isTopSortOf' x) (topSort x) /= Just False
-- 'isJust' . topSort == 'isAcyclic'
-- @
topSort :: AdjacencyIntMap -> Maybe [Int]
Expand Down
8 changes: 8 additions & 0 deletions src/Algebra/Graph/AdjacencyIntMap/Internal.hs
Expand Up @@ -40,6 +40,14 @@ working with graphs:
> 1 + 2 * 3 == overlay (vertex 1) (connect (vertex 2) (vertex 3))
> 1 * (2 + 3) == connect (vertex 1) (overlay (vertex 2) (vertex 3))
__Note:__ the 'Num' instance does not satisfy several "customary laws" of 'Num',
which dictate that 'fromInteger' @0@ and 'fromInteger' @1@ should act as
additive and multiplicative identities, and 'negate' as additive inverse.
Nevertheless, overloading 'fromInteger', '+' and '*' is very convenient when
working with algebraic graphs; we hope that in future Haskell's Prelude will
provide a more fine-grained class hierarchy for algebraic structures, which we
would be able to utilise without violating any laws.
The 'Show' instance is defined using basic graph construction primitives:
@show (empty :: AdjacencyIntMap Int) == "empty"
Expand Down

0 comments on commit 9f64c82

Please sign in to comment.