HORIZON HASKELLDocslts/ghc-9.14.xd82dee82026-10-12Search names, modules, packages, or :: a typeCtrl K

GHC 9.14.1 · lts/ghc-9.14.x · d82dee8 · 2026-10-12

Modulealgebraic-graphs-0.8Haskell2010

Algebra.Graph.NonEmpty.AdjacencyMap

Alga is a library for algebraic construction and manipulation of graphs in Haskell. See this paper for the motivation behind the library, the underlying theory, and implementation details.

This module defines the data type AdjacencyMap for graphs that are known to be non-empty at compile time. To avoid name clashes with Algebra.Graph.AdjacencyMap, this module can be imported qualified:

import qualified Algebra.Graph.NonEmpty.AdjacencyMap as NonEmpty

The naming convention generally follows that of Data.List.NonEmpty: we use suffix 1 to indicate the functions whose interface must be changed compared to Algebra.Graph.AdjacencyMap, e.g. vertices1.

  • 1 type
  • 41 values

Data structure

3 declarations
newtypenewtype AdjacencyMap a
#

The AdjacencyMap data type represents a graph by a map of vertices to their adjacency sets. 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))

Note: the signum method of the type class Num cannot be implemented and will throw an error. Furthermore, 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 (1         :: AdjacencyMap Int) == "vertex 1"
show (1 + 2     :: AdjacencyMap Int) == "vertices1 [1,2]"
show (1 * 2     :: AdjacencyMap Int) == "edge 1 2"
show (1 * 2 * 3 :: AdjacencyMap Int) == "edges1 [(1,2),(1,3),(2,3)]"
show (1 * 2 + 3 :: AdjacencyMap Int) == "overlay (vertex 3) (edge 1 2)"

The Eq instance satisfies the following laws of algebraic graphs:

  • overlay is commutative, associative and idempotent:

          x + y == y + x
    x + (y + z) == (x + y) + z
          x + x == x
  • connect is associative:

    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
  • connect satisfies absorption and saturation:

    x * y + x + y == x * y
        x * x * x == x * x

When specifying the time and memory complexity of graph algorithms, n and m will denote the number of vertices and edges in the graph, respectively.

The total order on graphs is defined using size-lexicographic comparison:

  • Compare the number of vertices. In case of a tie, continue.

  • Compare the sets of vertices. In case of a tie, continue.

  • Compare the number of edges. In case of a tie, continue.

  • Compare the sets of edges.

Here are a few examples:

vertex 1 < vertex 2
vertex 3 < edge 1 2
vertex 1 < edge 1 1
edge 1 1 < edge 1 2
edge 1 2 < edge 1 1 + edge 2 2
edge 1 2 < edge 1 3

Note that the resulting order refines the isSubgraphOf relation and is compatible with overlay and connect operations:

isSubgraphOf x y ==> x <= y
x     <= x + y
x + y <= x * y
Instances11Eq, Num, Ord, Show, IsString, Generic, …

Basic graph construction primitives

8 declarations
valueoverlay :: Ord a => AdjacencyMap a -> AdjacencyMap a -> AdjacencyMap a
#

Overlay two graphs. This is a commutative, associative and idempotent operation with the identity empty. Complexity: O((n + m) * log(n)) time and O(n + m) memory.

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
vertexCount (overlay 1 2) == 2
edgeCount   (overlay 1 2) == 0
valueconnect :: Ord a => AdjacencyMap a -> AdjacencyMap a -> AdjacencyMap a
#

Connect two graphs. This is an associative operation with the identity empty, which distributes over overlay and obeys the decomposition axiom. Complexity: O((n + m) * log(n)) time and O(n + m) memory. 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).

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
vertexCount (connect 1 2) == 2
edgeCount   (connect 1 2) == 1

Relations on graphs

1 declaration

Graph properties

10 declarations
valuevertexCount :: AdjacencyMap a -> Int
#

The number of vertices in a graph. Complexity: O(1) time.

vertexCount (vertex x)        ==  1
vertexCount                   ==  length . vertexList
vertexCount x < vertexCount y ==> x < y

Standard families of graphs

7 declarations
valuestar :: Ord a => a -> [a] -> AdjacencyMap a
#

The star formed by a centre vertex connected to a list of leaves. Complexity: O((n + m) * log(n)) time and O(n + m) memory.

star x []    == vertex x
star x [y]   == edge x y
star x [y,z] == edges1 [(x,y), (x,z)]
valuestars1 :: Ord a => NonEmpty (a, [a]) -> AdjacencyMap a
#

The stars formed by overlaying a list of stars. An inverse of adjacencyList. Complexity: O(L * log(n)) time, memory and size, where L is the total size of the input.

stars1 [(x, [] )]               == vertex x
stars1 [(x, [y])]               == edge x y
stars1 [(x, ys )]               == star x ys
stars1                          == overlays1 . fmap (uncurry star)
overlay (stars1 xs) (stars1 ys) == stars1 (xs <> ys)
valuetree :: Ord a => Tree a -> AdjacencyMap a
#

The tree graph constructed from a given Tree data structure. Complexity: O((n + m) * log(n)) time and O(n + m) memory.

tree (Node x [])                                         == vertex x
tree (Node x [Node y [Node z []]])                       == path1 [x,y,z]
tree (Node x [Node y [], Node z []])                     == star x [y,z]
tree (Node 1 [Node 2 [], Node 3 [Node 4 [], Node 5 []]]) == edges1 [(1,2), (1,3), (3,4), (3,5)]

Graph transformation

8 declarations
valueremoveEdge :: Ord a => a -> a -> AdjacencyMap a -> AdjacencyMap a
#

Remove an edge from a given graph. Complexity: O(log(n)) time.

removeEdge x y (edge x y)       == vertices1 [x,y]
removeEdge x y . removeEdge x y == removeEdge x y
removeEdge 1 1 (1 * 1 * 2 * 2)  == 1 * 2 * 2
removeEdge 1 2 (1 * 1 * 2 * 2)  == 1 * 1 + 2 * 2
valuemergeVertices
  1. :: Ord a
  2. => a -> Bool
  3. -> a
  4. -> AdjacencyMap a
  5. -> AdjacencyMap a
#

Merge vertices satisfying a given predicate into a given vertex. Complexity: O((n + m) * log(n)) time, assuming that the predicate takes constant time.

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
valuegmap :: (Ord a, Ord b) => (a -> b) -> AdjacencyMap a -> AdjacencyMap b
#

Transform a graph by applying a function to each of its vertices. This is similar to Functor's fmap but can be used with non-fully-parametric AdjacencyMap. Complexity: O((n + m) * log(n)) time.

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)
valueinduce1 :: (a -> Bool) -> AdjacencyMap a -> Maybe (AdjacencyMap a)
#

Construct the induced subgraph of a given graph by removing the vertices that do not satisfy a given predicate. Complexity: O(m) time, assuming that the predicate takes constant time.

induce1 (const True ) x == Just x
induce1 (const False) x == Nothing
induce1 (/= x)          == removeVertex1 x
induce1 p >=> induce1 q == induce1 (\x -> p x && q x)

Graph closure

4 declarations
valueclosure :: Ord a => AdjacencyMap a -> AdjacencyMap a
#

Compute the reflexive and transitive closure of a graph. Complexity: O(n * m * log(n)^2) time.

closure (vertex x)       == edge x x
closure (edge x x)       == edge x x
closure (edge x y)       == edges1 [(x,x), (x,y), (y,y)]
closure (path1 $ nub xs) == reflexiveClosure (clique1 $ nub xs)
closure                  == reflexiveClosure . transitiveClosure
closure                  == transitiveClosure . reflexiveClosure
closure . closure        == closure
postSet x (closure y)    == Set.fromList (reachable y x)

Compute the reflexive closure of a graph by adding a self-loop to every vertex. Complexity: O(n * log(n)) time.

reflexiveClosure (vertex x)         == edge x x
reflexiveClosure (edge x x)         == edge x x
reflexiveClosure (edge x y)         == edges1 [(x,x), (x,y), (y,y)]
reflexiveClosure . reflexiveClosure == reflexiveClosure

Miscellaneous

1 declaration
valueconsistent :: Ord a => AdjacencyMap a -> Bool
#

Check that the internal graph representation is consistent, i.e. that all edges refer to existing vertices, and the graph is non-empty. It should be impossible to create an inconsistent adjacency map, and we use this function in testing.

consistent (vertex x)    == True
consistent (overlay x y) == True
consistent (connect x y) == True
consistent (edge x y)    == True
consistent (edges xs)    == True
consistent (stars xs)    == True