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
예제 #2
0
 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)
예제 #3
0
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
예제 #4
0
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
예제 #5
0
 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()
예제 #6
0
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
예제 #7
0
 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
예제 #8
0
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)
예제 #9
0
 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
예제 #10
0
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
예제 #11
0
    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
예제 #12
0
 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
예제 #13
0
 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)
예제 #14
0
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
예제 #15
0
 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.")
예제 #17
0
 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.")