def DijkstraShortestPaths(graph, source, reverse=False, target=None): """Returns shortest paths in a weighted graph.""" # . Check edge weights. if graph.MinimumEdgeWeight() < 0: raise GraphError("Graph has negative edge weights.") # . Edges to use. if reverse: outEdges = graph.adjacentEdges # inEdges else: outEdges = graph.adjacentEdges # outEdges # . Initialization. predecessorEdges = {source: None} predecessorNodes = {source: None} weights = {} priorityQueue = [] seen = {source: 0.0} heapq.heappush(priorityQueue, (0.0, source)) # . Loop until the queue is empty. while len(priorityQueue) > 0: (weight, node) = heapq.heappop(priorityQueue) if node in weights: continue weights[node] = weight if node is target: break for edge in outEdges.get(node, []): other = edge.Opposite(node) newWeight = weight + edge.weight if other in weights: if newWeight < weights[other]: raise GraphError("Logic error: shorter path found.") elif (other not in seen) or (newWeight < seen[other]): heapq.heappush(priorityQueue, (newWeight, other)) predecessorEdges[other] = edge predecessorNodes[other] = node seen[other] = newWeight # . Build paths. # . Make prettier. paths = {} for node in weights: paths[node] = PathHead(headNode=node) for node in weights: paths[node].UpdateTail(headEdge=predecessorEdges[node], tail=paths.get(predecessorNodes[node], None)) for node in weights: paths[node].CalculateWeight() # . Finish up. if target is None: result = paths else: result = paths[target] return result
def AddNode(self, node): """Add a node to the graph.""" if node in self.adjacentNodes: raise GraphError("Node already present in graph.") else: self.nodeIndex[node] = len(self.nodes) self.nodes.append(node)
def BellmanFordShortestPaths(graph, source): """Calculate the shortest path distance between the source node and all other nodes in the graph using Bellman-Ford's algorithm.""" # . Initialization. paths = {} paths[source] = PathHead(headNode=source) # . Iterate and relax. for i in range(len(graph.nodes) - 1): for edge in graph.edges: for (source, target) in edge.SourceTargetPairs(): sourcePath = paths.get(source, None) if sourcePath is not None: targetPath = paths.get(target, None) if targetPath is None: paths[target] = PathHead(headEdge=edge, headNode=target, tail=sourcePath) else: if targetPath.weight > sourcePath.weight + edge.weight: targetPath.UpdateTail(headEdge=edge, tail=sourcePath) # . Detect negative weight cycles. for edge in graph.edges: for (source, target) in edge.SourceTargetPairs(): if paths[target].weight > (paths[source].weight + edge.weight): raise GraphError( "Graph has negative weight cycle on edge {:s}.".format( edge)) # . Finish up. return paths
def _CalculateRelevantCycles(prototypes): """Calculate the relevant cycles given the prototypes.""" # . Initialization. relevants = [] # . Loop over prototypes. for prototype in prototypes: # . Get the cycle. cycle = prototype[1] length = len(cycle) # . There are no other cycles of length 3 and 4 in the prototype family as all nodes are adjacent. if length < 5: relevants.append(cycle) # . Calculate relevant cycles from the prototype. else: # . Get node indices. nodes = prototype[2] r = nodes[0] p = nodes[1] q = nodes[2] digraph = prototype[3] isEven = (len(nodes) > 5) pLength = nodes[-2] qLength = nodes[-1] # . All paths P(p,r) and P(q,r). pPaths = _AllDirectedPaths(digraph, p, r) qPaths = _AllDirectedPaths(digraph, q, r) for (pathLength, paths) in ((pLength, pPaths), (qLength, qPaths)): for path in paths: xnodes = set(path) if len(xnodes) != len(path): raise GraphError( "\nInvalid directed path: {:d} {:d} {:d}.".format( len(xnodes), len(path), pathLength)) # . Even cycles. if isEven: x = nodes[3] for pPath in pPaths: for qPath in qPaths: relevants.append(pPath[::-1] + [x] + qPath[:-1]) # . Odd cycles. else: for pPath in pPaths: for qPath in qPaths: relevants.append(pPath[::-1] + qPath[:-1]) # print "Number relevants - number prototypes = ", len ( relevants ) - len ( prototypes ) #. Finish up. relevants.sort(key=len) return relevants
def VerifyTail(self): """Verify the tail.""" isOK = True if self.headEdge is None: isOK = (self.tail is None) else: isOK = (self.tail is not None) and ( self.tail.headNode is self.headEdge.Opposite(self.headNode)) if not isOK: raise GraphError("Invalid tail for path.") self.CalculateWeight()
def EdgeVectorToPath(graph, vector): """Return a list of booleans in graph-edge order indicating which edges occur in the path.""" # . Initialization. path = [] # . Find the connection map. connections = {} for (e, isPresent) in enumerate(vector): if isPresent: node1 = graph.edges[e].node1 node2 = graph.edges[e].node2 for (node, other) in ((node1, node2), (node2, node1)): local = connections.get(node, set()) local.add(other) connections[node] = local # . Check the consistency of the map. endNode = None maximumConnections = 0 numberConnections = 0 numberOnes = 0 for (node, values) in connections.iteritems(): n = len(values) if n == 1: endNode = node numberOnes += 1 maximumConnections = max(maximumConnections, n) numberConnections += n # . No nodes. if len(connections) == 0: pass # . A closed or open path. elif (maximumConnections == 2) and (numberOnes in (0, 2)): # . Create the path. if numberOnes == 0: current = connections.keys()[0] else: current = endNode while True: path.append(current) local = connections[current] if len(local) == 0: break next = local.pop() connections[next].remove(current) current = next # . A problem. else: raise GraphError("Cannot create a valid path from the edge vector.") # . Finish up. return path
def AddEdge(self, edge): """Add an edge.""" if edge.node1 is self.nodes[-1]: newNode = edge.node2 elif edge.node2 is self.nodes[-1]: newNode = edge.node1 else: newNode = None if newNode is None: raise GraphError("Edge does not connect to last node.") else: self.AddNode(newNode) self.edges.append(edge) self.weight += edge.weight
def DijkstraSingleSource(graph, source, cutoff=None, target=None): """Returns shortest paths and lengths in a weighted graph.""" # . Initialization. distances = {source: 0} paths = {source: [source]} # . Do nothing if there are no edges or the source and target are identical. if (len(graph.edges) > 0) and (source is not target): # . Check edge weights. if graph.MinimumEdgeWeight() < 0: raise GraphError("Graph has negative edge weights.") # . Initialization. distances = {} # . Needed to avoid immediate exit. queue = [] seen = {source: 0} heapq.heappush(queue, (0, source)) # . Loop until the queue is empty. while len(queue) > 0: (distance, node) = heapq.heappop(queue) if node in distances: continue distances[node] = distance if node is target: break for edge in graph.adjacentEdges.get(node, []): other = edge.Opposite(node) distance = distances[node] + edge.weight if cutoff is not None: if distance > cutoff: continue if other in distances: if distance < distances[other]: raise GraphError("Logic error: shorter path found.") elif (other not in seen) or (distance < seen[other]): heapq.heappush(queue, (distance, other)) paths[other] = paths[node] + [other] seen[other] = distance # . Finish up. return (distances, paths)
def MakeSubgraph(self, nodes, induced=True): """Generate a subgraph with the selected nodes.""" pruned = self.__class__() if len(nodes) > 0: for node in nodes: if node in self.nodes: pruned.AddNode(node) else: raise GraphError("Node not present in graph.") if induced: for edge in self.edges: if (edge.node1 in nodes) and (edge.node2 in nodes): pruned.AddEdge(edge) return pruned
def _MakeBooleanEdgeVector(graph, path): """Return a list of booleans in graph-edge order indicating which edges occur in the path.""" vector = [False for i in range(len(graph.edges))] for (i, tail) in enumerate(path): if i == len(path) - 1: head = path[0] else: head = path[i + 1] found = False for (e, edge) in enumerate(graph.edges): if ((edge.node1 is head) and (edge.node2 is tail)) or ((edge.node2 is head) and (edge.node1 is tail)): found = True vector[e] = True break if not found: raise GraphError("No edges connect two nodes.") return vector
def RemoveNode(self, node): """Remove a node from the graph.""" try: self.nodes.remove(node) except: raise GraphError("Node not in graph.") del self.nodeIndex[node] edges = self.adjacentEdges.pop(node, set()) for edge in edges: self.edges.remove(edge) other = edge.Opposite(node) self.adjacentEdges[other].remove(edge) for other in self.adjacentNodes.pop(node, set()): self.adjacentNodes[other].remove(node) # print "Removed number of nodes = ", len ( self.nodes ), node.tag return edges
def MakePathFromNodes(self, nodes): """Generate a path from a sequence of nodes.""" path = Path() for node in nodes: path.AddNode(node) if len(nodes) > 1: tail = nodes[0] for head in nodes[1:]: found = False for edge in self.adjacentEdges[tail]: if ( ( edge.node1 is tail ) and ( edge.node2 is head ) ) or \ ( ( edge.node2 is tail ) and ( edge.node1 is head ) ) : found = True path.AddEdge(edge) break if not found: raise GraphError( "Unable to find edge between two putative path nodes.") tail = head return path
def RemoveEdge(self, edge): """Remove an edge from the graph.""" # print "Removing Edge> ", edge.node1.tag, edge.node2.tag try: self.edges.remove(edge) except: raise GraphError("Edge not in graph.") iNode = edge.node1 jNode = edge.node2 self.adjacentEdges[iNode].remove(edge) self.adjacentEdges[jNode].remove(edge) # . This won't work if the graph is a multigraph. # . if self.IsMultiGraph ( ): # adjacentNodes = set ( ) # for edge in self.adjacentEdges[iNode]: # adjacentNodes.add ( edge.Opposite ( iNode ) ) # self.adjacentNodes[iNode] = adjacentNodes # . and repeat for jNode. # . else: self.adjacentNodes[iNode].remove(jNode) self.adjacentNodes[jNode].remove(iNode)
def PathToEdgeVector(graph, path, closePath=False): """Return a list of booleans in graph-edge order indicating which edges occur in the path.""" # . Check whether to close the path. extraNode = [] if closePath and (len(path) > 0) and (path[0] is not path[-1]): extraNode = [path[0]] # . Create the vector. vector = [False for i in range(len(graph.edges))] tail = path[0] for (i, head) in enumerate((path + extraNode)[1:]): found = False for (e, edge) in enumerate(graph.edges): if ((edge.node1 is head) and (edge.node2 is tail)) or ((edge.node2 is head) and (edge.node1 is tail)): found = True vector[e] = True break if not found: raise GraphError( "A path contains two nodes without an accompanying edge.") tail = head return vector
def AddNode(self, node): """Add a node.""" if node not in self.nodes: self.nodes.append(node) else: raise GraphError("Node already in path.")
def NextShortestPath(graph, source, target): """Find the next shortest path.""" # . Initialization. candidateNumber = 0 pathCandidates = [] deviationNodeIndex = {} previousPaths = [] # . Checks. numberOfEdges = len(graph.edges) numberOfNodes = len(graph.nodes) # print "Initial number of edges and nodes = ", numberOfEdges, numberOfNodes # . Get the shortest path. shortestPath = DijkstraShortestPaths(graph, source, target=target) if shortestPath is not None: deviationNodeIndex[shortestPath] = source heapq.heappush(pathCandidates, (shortestPath.weight, candidateNumber, shortestPath)) candidateNumber += 1 # . Loop until there are no more path candidates. while len(pathCandidates) > 0: # . Get the current path. (weight, number, currentPath) = heapq.heappop(pathCandidates) yield currentPath # print "CurrentPath> ", PathList ( currentPath ), deviationNodeIndex[currentPath].tag # . Use the current path to generate more path candidates. # . Get edges and nodes in path in forward order and without terminating node. pathEdges = currentPath.Edges() pathNodes = currentPath.Nodes() pathNodes.pop() # . Remove nodes and edges along the path before the current deviation node. currentDeviationNode = deviationNodeIndex[currentPath] edgesToRemove = set() nodesToRemove = set() while True: if pathNodes[0] is currentDeviationNode: break node = pathNodes.pop(0) edge = pathEdges.pop(0) edgesToRemove.update(graph.RemoveNode(node)) nodesToRemove.add(node) # . Remove edges from the current deviation node which coincide with previous paths with identical tails. (node, edge, currentPathTail) = currentPath.Split(currentDeviationNode) for previousPath in previousPaths: (node, edge, previousPathTail) = previousPath.Split(currentDeviationNode) if previousPathTail is not None: if (currentPathTail == previousPathTail) and (edge not in edgesToRemove): edgesToRemove.add(edge) graph.RemoveEdge(edge) previousPaths.append(currentPath) # . Define the remaining nodes and edges along the current path that are to be removed and restored. elementsToRestore = [] for (node, edge) in zip(pathNodes, pathEdges): graph.RemoveEdge(edge) otherEdges = graph.RemoveNode(node) elementsToRestore.append((node, edge, otherEdges)) elementsToRestore.reverse() # . Get the reverse shortest distances from the target to all other nodes in the graph. reversePaths = DijkstraShortestPaths(graph, target, reverse=True) # . Restore the relevant nodes and edges. successorNode = target for (currentNode, currentEdge, otherEdges) in elementsToRestore: if currentEdge.Opposite(currentNode) is not successorNode: raise GraphError("Edge mismatch.") # . Restore the elements. graph.AddNode(currentNode) graph.AddEdges(otherEdges) # . Update the path to the node using forward star form. subPath = _UpdateForwardPath(graph, reversePaths, currentNode) # print "SubPath> ", PathList ( subPath ) # . Build the new path if possible. if subPath is not None: # . Correct paths. _CorrectBackwardPaths(graph, reversePaths, currentNode) # . Make prettier? Use split first? newPath = PathHead(headNode=source) for edge in currentPath.Edges(): headNode = newPath.headNode if headNode is currentNode: break newPath = newPath.PushHead(edge) # . Use MakeReversed. # newPath = subPath.MakeReversed ( ) # newPath??? subPathEdges = subPath.Edges() subPathEdges.reverse() for edge in subPathEdges: newPath = newPath.PushHead(edge) # . Save the path if it is new. # . Is this necessary and, if so, how check? # . Some hash key for path? # . Explicit check against all paths with same weight? if True: heapq.heappush(pathCandidates, (newPath.weight, candidateNumber, newPath)) candidateNumber += 1 deviationNodeIndex[newPath] = currentNode # . Restore the current edge. graph.AddEdge(currentEdge) # . Update the reverse paths if necessary. currentReversePath = reversePaths.get(currentNode, None) successorPath = reversePaths[successorNode] if currentReversePath is None: currentReversePath = PathHead(headEdge=currentEdge, headNode=currentNode, tail=successorPath) reversePaths[currentNode] = currentReversePath else: weight = currentEdge.weight + successorPath.weight if currentReversePath.weight > weight: currentReversePath.UpdateTail(headEdge=currentEdge, tail=successorPath) _CorrectBackwardPaths(graph, reversePaths, currentNode) # . Reset successor node. successorNode = currentNode # . Restore all nodes and edges to the graph. graph.AddNodes(nodesToRemove) graph.AddEdges(edgesToRemove) if (len(graph.edges) != numberOfEdges) or (len(graph.nodes) != numberOfNodes): # print "Edges = ", len ( graph.edges ), numberOfEdges # print "Nodes = ", len ( graph.nodes ), numberOfNodes raise GraphError( "Restored graph has invalid number of edges or nodes.")
def Opposite(self, node): """Return the other node for the edge.""" if node is self.node1: return self.node2 elif node is self.node2: return self.node1 else: raise GraphError("Node not associated with edge.")