import linear,iops, sets, tables, algorithm, heapqueue, random export linear type GraphRepr = enum AdjMatrix Sparse Dense type Graph* = ref object #representation of vertices V*: int ##number of vertices. capacity: int #number of possible vertices. E*: int ##number of edges. directed*: bool representation: GraphRepr #AdjMatrix adjMatrix: matrix[int] #Sparse / Dense adjList: seq[HashSet[int]] inverseAdjList: seq[HashSet[int]] #only used for digraph func contains*(g: Graph, v, w: int): bool = ##Test if v ==> w is an edge in the graph g. if v >= g.V or w >= g.V: return false case g.representation: of AdjMatrix: return g.adjMatrix[v, w] > 0 of Sparse: return w in g.adjList[v] of Dense: return not (w in g.adjList[v]) func outDegree*(g: Graph, v: int): int = if v >= g.V: return 0 case g.representation: of AdjMatrix: for w in 0..= g.V: return 0 if not g.directed: return g.outDegree(v) #easier case g.representation: of AdjMatrix: for w in 0..= g.V: return 0 if g.directed: return g.inDegree(v) + g.outDegree(v) else: return g.outDegree(v) func edgeDensity*(g: Graph): float = var E = g.E.float var V = g.V.float if g.directed: return E / (V * (V-1)) else: return 2*E / (V * (V-1)) func adjacencyMatrix*(g: Graph): matrix[int] = ##Returns the V x V adjacency matrix for g. ##This may be different from the internal adjacency matrix if g.capacity > g.V. result.initMatrix(g.V, g.V) case g.representation of AdjMatrix: #trim it down for v in 0..= 0.95: # return #TODO TESTING # #switch to dense # #automatically reduce capacity as much as possible # g.representation = Dense # g.capacity = g.V # g.adjList = newSeq[HashSet[int]](g.V) # for i in 0..= 0.10: # # echo "Switching to adjmatrix (density is ", density, ")" # #convert to AdjMatrix # g.adjMatrix = g.adjacencyMatrix() # g.capacity = g.V #cuts down capacity automatically # g.adjList = @[] # if g.directed: g.inverseAdjList = @[] # g.representation = AdjMatrix # of Dense: # if density <= 0.90: # # echo "Switching to adjmatrix (density is ", density, ")" # #convert to AdjMatrix # g.adjMatrix = g.adjacencyMatrix() # g.capacity = g.V #cuts down capacity automatically # g.adjList = @[] # if g.directed: g.inverseAdjList = @[] # g.representation = AdjMatrix proc enlarge(g: var Graph, capNew: int) = var capNew = capNew if capNew == 0: capNew = 1 # echo "Graph (", g.representation, ") expanding. Capacity ", g.capacity, " ==> ", capNew case g.representation: of AdjMatrix: var newMatrix: matrix[int] newMatrix.initMatrix(capNew, capNew) for v in 0..= g.capacity or w >= g.capacity: g.enlarge(g.capacity * 2) if v >= g.V or w >= g.V: #increase g.V g.V = max(v, w) + 1 inc g.E if g.directed: case g.representation: of AdjMatrix: g.adjMatrix[v, w] = 1 of Sparse: g.adjList[v].incl w g.inverseAdjList[w].incl v of Dense: #remove (v, w) if it exists, and remove (w, v) from inverseAdjList g.adjList[v].excl w g.inverseAdjList[w].excl v else: #undirected case g.representation: of AdjMatrix: g.adjMatrix[v, w] = 1 g.adjMatrix[w, v] = 1 of Sparse: g.adjList[v].incl w g.adjList[w].incl v of Dense: #remove (v, w) and (w, v) if it exists g.adjList[v].excl(w) g.adjList[w].excl(v) g.regularize() proc disconnect*(g: var Graph, v, w: int) = if not g.contains(v, w): return #verify bounds while v >= g.capacity or w >= g.capacity: g.enlarge(g.capacity * 2) if v >= g.V or w >= g.V: #increase g.V g.V = max(v, w) + 1 dec g.E if g.directed: case g.representation: of AdjMatrix: g.adjMatrix[v, w] = 0 of Sparse: g.adjList[v].excl w g.inverseAdjList[w].excl v of Dense: g.adjList[v].incl w g.inverseAdjList[w].incl v else: #undirected case g.representation: of AdjMatrix: g.adjMatrix[v, w] = 0 g.adjMatrix[w, v] = 0 of Sparse: g.adjList[v].excl w g.adjList[w].excl v of Dense: #remove (v, w) and (w, v) if it exists g.adjList[v].incl(w) g.adjList[w].incl(v) g.regularize() template defineGraph*(graphName: untyped, V: untyped, body: untyped): untyped = var `graphName` {.inject.} = initGraph(V) block: proc `==>`(v, w: int) {.used.} = `graphName`.connect(v, w) proc cycle(vs: openArray[int]) {.used.} = for i in 0..vs.high: vs[i] ==> vs[(i+1) mod vs.len] body template defineDigraph*(graphName: untyped, V: untyped, body: untyped): untyped = var `graphName` {.inject.} = initDigraph(V) proc `==>`(v, w: int) {.used.} = `graphName`.connect(v, w) proc cycle(vs: openArray[int]) {.used.} = for i in 0..vs.high: vs[i] ==> vs[(i+1) mod vs.len] body #[ ===================== ADVANCED OPERATIONS ===================== ]# iterator edges*(g: Graph): (int, int) = case g.representation: of AdjMatrix: for v in 0.. v or g.directed: yield (v, w) of Dense: var forbidden = newSeq[bool](g.V) for v in 0..= 0 and result[k, j] >= 0 and (result[i, j] < 0 or result[i, j] > result[i, k] + result[k, j]): result[i, j] = result[i, k] + result[k, j] proc shortestPathCosts*[T](g: Graph, weights: matrix[T]): matrix[T] = ##Given the set of edge weights, result[v, w] is the lowest cost path from v to w. ##Uses Floyd-Warshall, runs in O(V^3). result.initMatrix(g.V, g.V) for v in 0.. result[i, k] + result[k, j]: result[i, j] = result[i, k] + result[k, j] proc clone*(g: Graph): Graph = new(result) result.representation = g.representation result.directed = g.directed result.V = g.V result.capacity = g.capacity result.E = g.E case g.representation of AdjMatrix: result.adjMatrix = g.adjMatrix.copy of Sparse, Dense: result.adjList = g.adjList proc countEdges(g: Graph): int = #used internally case g.representation: of AdjMatrix: for u in 0.. 0: result += 1 of Sparse: for u in 0.. w: (v, w) = (w, v) #relabel w as v #relabel g.V - 1 as w #track number of edges lost #[ any edge v -> w or w -> v will be lost. any edge u -> w will be turned into an edge u -> v. any edge w -> u will be turned into an edge v -> u. ]# case gc.representation of AdjMatrix: for u in 0.. 0: gc.adjMatrix[v, u] = 1 if gc.adjMatrix[u, w] > 0: gc.adjMatrix[u, w] = 0 gc.adjMatrix[u, v] = 1 for u in 0.. u #change it to an edge v ==> u gc.adjList[v].incl u for u in 0.. w #change it to an edge u ==> v gc.adjList[u].excl w gc.adjList[u].incl v gc.adjList[w] = gc.adjList[gc.V - 1] for u in 0.. u #change it to an edge v ==> u gc.adjList[v].incl u for u in 0.. w #change it to an edge u ==> v gc.adjList[u].excl w gc.adjList[u].incl v gc.adjList[w] = gc.adjList[gc.V - 1] gc.adjList[gc.V - 1] = initHashSet[int]() #TODO for adj and dense dec gc.V gc.E = gc.countEdges return gc proc `/`*(g: Graph, vw: (int, int)): Graph = g.contract(vw[0], vw[1]) proc `*`*(g, h: Graph): Graph = ##Returns the direct product of the graphs. if not g.directed and not h.directed: defineGraph(output, g.V * h.V): for e1 in g.edges: for e2 in h.edges: e1[0] * h.V + e2[0] ==> e1[1] * h.V + e2[1] e1[1] * h.V + e2[0] ==> e1[0] * h.V + e2[1] return output elif g.directed and h.directed: defineDigraph(output, g.V * h.V): for e1 in g.edges: for e2 in h.edges: e1[0] * h.V + e2[0] ==> e1[1] * h.V + e2[1] return output else: echo "Graph product not yet implemented." proc directProduct*(g, h: Graph): Graph = g*h proc cartesianProduct*(g, h: Graph): Graph = ##Returns the cartesian product of the graphs. if not g.directed and not h.directed: defineGraph(output, g.V * h.V): for e1 in g.edges: for v in 0.. e1[1] * h.V + v for e1 in h.edges: for v in 0.. e1[1] + v * h.V return output elif g.directed and h.directed: defineDigraph(output, g.V * h.V): for e1 in g.edges: for v in 0.. e1[1] * h.V + v for e1 in h.edges: for v in 0.. e1[1] + v * h.V return output proc lexProduct*(g, h: Graph): Graph = ##Returns the lexicographical product of the graphs. if not g.directed and not h.directed: defineGraph(output, g.V * h.V): for e1 in g.edges: for v in 0.. e1[1] * h.V + w for e1 in h.edges: for v in 0.. e1[1] + v * h.V return output elif g.directed and h.directed: defineDigraph(output, g.V * h.V): for e1 in g.edges: for v in 0.. e1[1] * h.V + w for e1 in h.edges: for v in 0.. e1[1] + v * h.V return output else: echo "Graph product not yet implemented." func `+`*(g, h: Graph): Graph = ##Returns the disjoint sum of the graphs g and h. if g.directed == h.directed: result = g.clone for e in h.edges: result.connect e[0] + g.V, e[1] + g.V #else: TODO proc union*(g, h: Graph): Graph = ##Returns the union of the graphs g and h. if g.directed == h.directed: result = g.clone for e in h.edges: result.connect e[0], e[1] else: echo "Graph union not yet implemented." func complement*(g: Graph): Graph = ##Returns the graph with the same vertices, but with opposite edges. result = g.clone case g.representation: of AdjMatrix: for v in 0.. w will be present iff v ==> w or w ==> v. ##If g is undirected it will return a clone of g. if not g.directed: return g.clone case g.representation: of AdjMatrix: result = g.clone for v in 0.." & $(e[1]) else: result &= $(e[0]) & "<->" & $(e[1]) return result & "}]" func lineGraph*(g: Graph): Graph = ##This returns a graph (isomorphic to) the graph whose vertices are the edges of the original graph, ##and two new vertices are connected if the corresponding edges are incident at a vertex. var g = g.toGraph() #forget direction result = initGraph(g.E) #label each edge in a matrix var edgeLabels: matrix[int] block: var i = 0 edgeLabels.initMatrix(g.V, g.V) for r in 0.. 0: var w = chunk.pop if seen[w]: continue ids[w] = u inc u for z in g.edges(w): if seen[z]: component.connect ids[w], ids[z] else: chunk.incl z for z in g.preimage(w): if seen[z]: component.connect ids[z], ids[w] else: chunk.incl z seen[w] = true result.add component func spanningForest*(g: Graph): seq[Graph] = ##Returns one spanning tree for each connected component of g. #TODO func grundyValues*(g: Graph): seq[int] = ##Treat the vertices as states of an impartial game. ##Then this returns the grundy values of the states of the game. ##result[v] = 0 if and only if v is a losing state under normal play. ##This assumes that g is an acyclic directed graph. if not g.directed: return @[] result = newSeq[int](g.V) var following = newSeq[seq[int]](g.V) for v in 0.. 0: for v in toProcess: if g.outDegree(v) == following[v].len: #we can compute the grundy value of v and add its preimage var u = 0 while u in following[v]: inc u result[v] = u for w in g.preimage(v): following[w].add u toProcess.incl w toProcess.excl v break #[ =============== GRAPH QUERIES =============== ]# type partial = tuple assignments: seq[int] remaining: HashSet[int] func findSubgraph*(g, h: Graph): seq[int] = ##Returns an isomorphic copy of g inside of h, or @[] if none exists. ##This is usually hard to do. if g.V > h.V: return @[] if g.E > h.E: return @[] if g.E == 0: return @[0] #after this point we know h.E > 0 var states = newSeq[partial]() #a state is an assignment of the first _ vertices of g to vertices of h. block: var initial: partial initial.assignments = @[] initial.remaining = initHashSet[int]() for v in 0.. 0: var state = states.pop for v in state.remaining: if hdegs[v] >= gdegs[state.assignments.len]: #try to use v next var useable = true for i, u in state.assignments.pairs: if g.contains(i, state.assignments.len) and not h.contains(state.assignments[i], v): useable = false break if useable: var stateNew: partial stateNew.assignments = state.assignments stateNew.assignments.add v if stateNew.assignments.len == g.V: return stateNew.assignments stateNew.remaining = state.remaining stateNew.remaining.excl v states.add stateNew return @[] func findInducedSubgraph*(g, h: Graph): seq[int] = ##TODO return @[] func findIsomorphism*(g, h: Graph): seq[int] = ##Returns an isomorphism between g and h, or @[] if none exists. ##This is usually hard to do. if g.V != h.V: return @[] if g.E != h.E: return @[] var gdegs = g.degreeMultiset var hdegs = h.degreeMultiset for i in 0.. 0: var state = states.pop for v in state.remaining: if hdegs[v] == gdegs[state.assignments.len]: #try to use v next var useable = true for i, u in state.assignments.pairs: if g.contains(i, state.assignments.len) and not h.contains(state.assignments[i], v): useable = false break if useable: var stateNew: partial stateNew.assignments = state.assignments stateNew.assignments.add v if stateNew.assignments.len == g.V: return stateNew.assignments stateNew.remaining = state.remaining stateNew.remaining.excl v states.add stateNew return @[] func subgraphQ*(g, h: Graph): bool = findSubgraph(g, h).len > 0 func inducedSubgraphQ*(g, h: Graph): bool = findSubgraph(g, h).len > 0 #TODO func isomorphicQ*(g, h: Graph): bool = findIsomorphism(g, h).len > 0 proc swapVertices*(g: var Graph, v, w: int) = ##Swaps the labels of vertices v and w. if g.directed: echo "Not implemented for directed graphs." quit(-1) if v == w: return case g.representation: of AdjMatrix: #todo return of Sparse: for u in (g.adjList[v] - g.adjList[w]): if u == w: continue g.adjList[u].excl v g.adjList[u].incl w for u in (g.adjList[w] - g.adjList[v]): if u == v: continue g.adjList[u].excl w g.adjList[u].incl v swap g.adjList[v], g.adjList[w] of Dense: #todo return proc relabel*(g: var Graph) = ##Relabels g so that the degree string is increasing. var list = g.degreeSequence var list2 = list.sorted for i in 0.. 0.01: g.relabel var id = $g.V for e in g.edges: id = id & $e if chromaticTable.hasKey(id): return chromaticTable[id] # if g.V < seenGraphs.len: # for (h, ph) in seenGraphs[g.V]: # if g.isomorphicQ(h): # # chromaticTable[id] = ph # return ph if g.edgeDensity <= 1.0: #change when dense graphs are implemented: for v in countdown(g.V - 1, 0): for w in g.edges(v): #only need the first edge we find var h = g.clone h.disconnect(v, w) var q = g / (v, w) var hp = h.chromaticPolynomial var qp = q.chromaticPolynomial # quit(-1) result = hp - qp chromaticTable[id] = result # if g.V < seenGraphs.len: # seenGraphs[g.V].add (g, result) return result else: #find first not-edge for v in countdown(g.V - 1, 0): for w in countdown(v - 1, 0): if not g.contains(v, w): #only need the first edge we find var h = g.clone h.connect(v, w) var q = g / (v, w) var hp = h.chromaticPolynomial var qp = q.chromaticPolynomial result = hp + qp chromaticTable[id] = result return result # echo g.V, ", ", g.E var chromaticTableMod = initTable[string, poly[zmod]]() proc chromaticPolynomialMod*(g: Graph, m: int64): poly[zmod] = if g.directed: quit(-1) if g.E == 0: return createPoly(@[(1'i64, m)]).shift(g.V) if g.E == 1: var x = createPoly(@[(1'i64, m)]).shift(1) if g.V == 2: return x * (x - (1'i64, m)) return x.shift(g.V - 2) * (x - (1'i64, m)) if g.E == (g.V * (g.V - 1)) div 2: var factor = createPoly(@[(1'i64, m)]).shift(1) result = factor for i in 1.. 0: var batch = initHashSet[int]() batch.incl unmarked.pop while batch.len > 0: var batchNew = initHashSet[int]() for v in batch: for w in g.edges(v): if not (w in unmarked) and not traversed[v, w]: return false traversed[v, w] = true if not g.directed: traversed[w, v] = true if w in unmarked: batchNew.incl w unmarked.excl w batch = batchNew return true func bipartiteQ*(g: Graph): bool = ##Returns whether g is bipartite. ##This is equivalent to g having no odd cycle. var unmarked = initHashSet[int]() for v in 0.. 0: var batch = initHashSet[int]() batch.incl unmarked.pop while batch.len > 0: var batchNew = initHashSet[int]() for v in batch: for w in g.edges(v): if w in unmarked: batchNew.incl w elif red[v] == red[w]: return false red[w] = not red[v] unmarked.excl w batch = batchNew return true func shortestPaths*(g: Graph): matrix[seq[int]] = ##Returns a sequence of vertices which form a shortest path from v to w, or an empty seq if none exists. ##result[v, v] will tell you the shortest circuit starting and ending at v. result.initMatrix(g.V, g.V) for v in 0.. ... -> v. # for v in 0.. 0 and result[k, j].len > 0 and (result[i, j].len == 0 or result[i, j].len > result[i, k].len + result[k, j].len): result[i, j] = result[i, k][0..^2] & result[k, j] proc connectedQ*(g: Graph): bool = ##Returns whether g is connected. if g.E <= 1: return true var lengths = g.shortestPaths for v in 0.. w return output func cycleGraph*(n: int): Graph = defineGraph(output, n): for v in 0.. (v + 1) mod n return output func starGraph*(n: int): Graph = completeBipartiteGraph(1, n) func cubicGraph*(n: int): Graph = ##Returns the n dimensional cube graph. defineGraph(output, 1 shl n): for v in 0..<(1 shl n): var z = 1 for i in 1..n: v ==> (v xor z) z = z shl 1 return output func petersenGraph*: Graph = defineGraph(output, 10): cycle [0, 1, 2, 3, 4] cycle [5, 7, 9, 6, 8] for i in 0..4: i ==> i+5 return output func octahedralGraph*: Graph = defineGraph(output, 6): cycle [0, 1, 2] cycle [3, 4, 5] for i in 0..2: i ==> i + 3 i ==> ((i + 1) mod 3) + 3 return output #generated by Mathematica func dodecahedralGraph*: Graph = defineGraph(output, 20): 0 ==> 13 0 ==> 14 0 ==> 15 1 ==> 4 1 ==> 5 1 ==> 12 2 ==> 6 2 ==> 13 2 ==> 18 3 ==> 7 3 ==> 14 3 ==> 19 4 ==> 10 4 ==> 18 5 ==> 11 5 ==> 19 6 ==> 10 6 ==> 15 7 ==> 11 7 ==> 15 8 ==> 9 8 ==> 13 8 ==> 16 9 ==> 14 9 ==> 17 10 ==> 11 12 ==> 16 12 ==> 17 16 ==> 18 17 ==> 19 return output func icosahedralGraph*: Graph = defineGraph(output, 12): 0 ==> 2 0 ==> 4 0 ==> 5 0 ==> 8 0 ==> 9 1 ==> 3 1 ==> 6 1 ==> 7 1 ==> 10 1 ==> 11 2 ==> 6 2 ==> 7 2 ==> 8 2 ==> 9 3 ==> 4 3 ==> 5 3 ==> 10 3 ==> 11 4 ==> 5 4 ==> 8 4 ==> 10 5 ==> 9 5 ==> 11 6 ==> 7 6 ==> 8 6 ==> 10 7 ==> 9 7 ==> 11 8 ==> 10 9 ==> 11 return output func moserSpindleGraph*: Graph = defineGraph(output, 7): 0 ==> 1 0 ==> 4 0 ==> 6 1 ==> 2 1 ==> 5 2 ==> 3 2 ==> 5 3 ==> 4 3 ==> 5 3 ==> 6 4 ==> 6 return output func wheelGraph*(n: int): Graph = result = cycleGraph(n-1) for v in 0..<(n-1): result.connect(v, n-1) func pathGraph*(n: int): Graph = defineGraph(output, n): for i in 0..<(n-1): i ==> i+1 return output func gridGraph*(r, c: int): Graph = ##Returns the rectangular r x c grid graph. defineGraph(output, r*c): for i in 0.. (i+1)*c + j if j < (c-1): i*c + j ==> i*c + (j+1) return output func rookGraph*(m, n: int): Graph = cartesianProduct(completeGraph(m), completeGraph(n)) #also edge graph of completeBipartiteGraph(m, n) func torusGridGraph*(m, n: int): Graph = cartesianProduct(cycleGraph(m), cycleGraph(n)) func turanGraph*(n, r: int): Graph = result = initGraph(1) var q = n div r var s = n mod r var at = 0 for u in 1..s: #each group is size (q+1) for v in at.. h.V: break if h.V mod r == 0: var c = h.V div r if h.E == 2*r*c - r - c and h.isomorphicQ(gridGraph(r, c)): return "Grid[" & $r & ", " & $c & "]" #test for complete graph if 2 * h.E == h.V * (h.V - 1): return "K[" & $h.V & "]" #test for complete bipartite graph if h.V * h.V - 4 * h.E >= 0: var u = h.V * h.V - 4 * h.E var urt = isqrt(u) if urt * urt == u and (h.V + urt) mod 2 == 0: var r = (h.V - urt) div 2 var c = (h.V + urt) div 2 if r >= 1 and c >= 1 and h.isomorphicQ(completeBipartiteGraph(r.int, c.int)): return "CompleteBipartite[" & $r & ", " & $c & "]" #test for prism if h.V * 3 == h.E * 2 and h.isomorphicQ(prismGraph(h.V div 2)): return "Prism[" & $(h.V div 2) & "]" #test for torus grid graph if h.E == 2 * h.V: for p in 1..h.V: if p*p > h.V: break if h.V mod p == 0: var q = h.V div p if h.isomorphicQ(torusGridGraph(p, q)): return $p & " x " & $q & " Torus Grid Graph / " & $p & " - " & $q & " Duoprism" #do not test for turan graph. sorry :[ #test for rook graph for r in 1..h.V: if r*r > h.V: break if h.V mod r == 0: var c = h.V div r if h.E == ((r*c*(r+c)) div 2) - r*c and h.isomorphicQ(rookGraph(r, c)): return "Rook[" & $r & ", " & $c & "]" #hasn't been recognized.. just describe it result = "V = " & $h.V & ", E = " & $h.E & ", " if h.bipartiteQ: result &= "Bipartite, " if h.acyclicQ: result &= "Acyclic, " result &= "with degree multiset " & $h.degreeMultiset & "." for i, h in components.pairs: var part = "" if components.len > 1: part = "Component " & $i & ": " part &= recognizeComponent(h) if i != components.high: part &= "\n" result &= part proc characteristicPoly*(g: Graph): poly[int64] = ##Returns the characteristic polynomial of a graph. var m = g.adjacencyMatrix castCopyMat(m, m64, int64) return m64.characteristicPoly type candidateMIS = tuple chosen: seq[int] remaining: HashSet[int] proc `<`(x, y: candidateMIS): bool = x.chosen.len > y.chosen.len proc maximalIndependentSet*(g: Graph): seq[int] = var maxSoFar = 0 var selections: seq[int] var stk = initHeapQueue[candidateMIS]() block: var initial = initHashSet[int]() for v in 0.. 0: var current = stk.pop # echo current if maxSoFar >= current.chosen.len + current.remaining.len: continue if maxSoFar < current.chosen.len: maxSoFar = current.chosen.len selections = current.chosen if current.remaining.len == 0: continue var remainingNew = current.remaining block: var v = remainingNew.pop stk.push (current.chosen, remainingNew) for w in g.edges(v): remainingNew.excl w var chosenNew = current.chosen chosenNew.add v stk.push (chosenNew, remainingNew) return selections