about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/networkx/algorithms/simple_paths.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/algorithms/simple_paths.py')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/simple_paths.py950
1 files changed, 950 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/simple_paths.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/simple_paths.py
new file mode 100644
index 00000000..3605522f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/simple_paths.py
@@ -0,0 +1,950 @@
+from heapq import heappop, heappush
+from itertools import count
+
+import networkx as nx
+from networkx.algorithms.shortest_paths.weighted import _weight_function
+from networkx.utils import not_implemented_for, pairwise
+
+__all__ = [
+    "all_simple_paths",
+    "is_simple_path",
+    "shortest_simple_paths",
+    "all_simple_edge_paths",
+]
+
+
+@nx._dispatchable
+def is_simple_path(G, nodes):
+    """Returns True if and only if `nodes` form a simple path in `G`.
+
+    A *simple path* in a graph is a nonempty sequence of nodes in which
+    no node appears more than once in the sequence, and each adjacent
+    pair of nodes in the sequence is adjacent in the graph.
+
+    Parameters
+    ----------
+    G : graph
+        A NetworkX graph.
+    nodes : list
+        A list of one or more nodes in the graph `G`.
+
+    Returns
+    -------
+    bool
+        Whether the given list of nodes represents a simple path in `G`.
+
+    Notes
+    -----
+    An empty list of nodes is not a path but a list of one node is a
+    path. Here's an explanation why.
+
+    This function operates on *node paths*. One could also consider
+    *edge paths*. There is a bijection between node paths and edge
+    paths.
+
+    The *length of a path* is the number of edges in the path, so a list
+    of nodes of length *n* corresponds to a path of length *n* - 1.
+    Thus the smallest edge path would be a list of zero edges, the empty
+    path. This corresponds to a list of one node.
+
+    To convert between a node path and an edge path, you can use code
+    like the following::
+
+        >>> from networkx.utils import pairwise
+        >>> nodes = [0, 1, 2, 3]
+        >>> edges = list(pairwise(nodes))
+        >>> edges
+        [(0, 1), (1, 2), (2, 3)]
+        >>> nodes = [edges[0][0]] + [v for u, v in edges]
+        >>> nodes
+        [0, 1, 2, 3]
+
+    Examples
+    --------
+    >>> G = nx.cycle_graph(4)
+    >>> nx.is_simple_path(G, [2, 3, 0])
+    True
+    >>> nx.is_simple_path(G, [0, 2])
+    False
+
+    """
+    # The empty list is not a valid path. Could also return
+    # NetworkXPointlessConcept here.
+    if len(nodes) == 0:
+        return False
+
+    # If the list is a single node, just check that the node is actually
+    # in the graph.
+    if len(nodes) == 1:
+        return nodes[0] in G
+
+    # check that all nodes in the list are in the graph, if at least one
+    # is not in the graph, then this is not a simple path
+    if not all(n in G for n in nodes):
+        return False
+
+    # If the list contains repeated nodes, then it's not a simple path
+    if len(set(nodes)) != len(nodes):
+        return False
+
+    # Test that each adjacent pair of nodes is adjacent.
+    return all(v in G[u] for u, v in pairwise(nodes))
+
+
+@nx._dispatchable
+def all_simple_paths(G, source, target, cutoff=None):
+    """Generate all simple paths in the graph G from source to target.
+
+    A simple path is a path with no repeated nodes.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    source : node
+       Starting node for path
+
+    target : nodes
+       Single node or iterable of nodes at which to end path
+
+    cutoff : integer, optional
+        Depth to stop the search. Only paths of length <= cutoff are returned.
+
+    Returns
+    -------
+    path_generator: generator
+       A generator that produces lists of simple paths.  If there are no paths
+       between the source and target within the given cutoff the generator
+       produces no output. If it is possible to traverse the same sequence of
+       nodes in multiple ways, namely through parallel edges, then it will be
+       returned multiple times (once for each viable edge combination).
+
+    Examples
+    --------
+    This iterator generates lists of nodes::
+
+        >>> G = nx.complete_graph(4)
+        >>> for path in nx.all_simple_paths(G, source=0, target=3):
+        ...     print(path)
+        ...
+        [0, 1, 2, 3]
+        [0, 1, 3]
+        [0, 2, 1, 3]
+        [0, 2, 3]
+        [0, 3]
+
+    You can generate only those paths that are shorter than a certain
+    length by using the `cutoff` keyword argument::
+
+        >>> paths = nx.all_simple_paths(G, source=0, target=3, cutoff=2)
+        >>> print(list(paths))
+        [[0, 1, 3], [0, 2, 3], [0, 3]]
+
+    To get each path as the corresponding list of edges, you can use the
+    :func:`networkx.utils.pairwise` helper function::
+
+        >>> paths = nx.all_simple_paths(G, source=0, target=3)
+        >>> for path in map(nx.utils.pairwise, paths):
+        ...     print(list(path))
+        [(0, 1), (1, 2), (2, 3)]
+        [(0, 1), (1, 3)]
+        [(0, 2), (2, 1), (1, 3)]
+        [(0, 2), (2, 3)]
+        [(0, 3)]
+
+    Pass an iterable of nodes as target to generate all paths ending in any of several nodes::
+
+        >>> G = nx.complete_graph(4)
+        >>> for path in nx.all_simple_paths(G, source=0, target=[3, 2]):
+        ...     print(path)
+        ...
+        [0, 1, 2]
+        [0, 1, 2, 3]
+        [0, 1, 3]
+        [0, 1, 3, 2]
+        [0, 2]
+        [0, 2, 1, 3]
+        [0, 2, 3]
+        [0, 3]
+        [0, 3, 1, 2]
+        [0, 3, 2]
+
+    The singleton path from ``source`` to itself is considered a simple path and is
+    included in the results:
+
+        >>> G = nx.empty_graph(5)
+        >>> list(nx.all_simple_paths(G, source=0, target=0))
+        [[0]]
+
+        >>> G = nx.path_graph(3)
+        >>> list(nx.all_simple_paths(G, source=0, target={0, 1, 2}))
+        [[0], [0, 1], [0, 1, 2]]
+
+    Iterate over each path from the root nodes to the leaf nodes in a
+    directed acyclic graph using a functional programming approach::
+
+        >>> from itertools import chain
+        >>> from itertools import product
+        >>> from itertools import starmap
+        >>> from functools import partial
+        >>>
+        >>> chaini = chain.from_iterable
+        >>>
+        >>> G = nx.DiGraph([(0, 1), (1, 2), (0, 3), (3, 2)])
+        >>> roots = (v for v, d in G.in_degree() if d == 0)
+        >>> leaves = (v for v, d in G.out_degree() if d == 0)
+        >>> all_paths = partial(nx.all_simple_paths, G)
+        >>> list(chaini(starmap(all_paths, product(roots, leaves))))
+        [[0, 1, 2], [0, 3, 2]]
+
+    The same list computed using an iterative approach::
+
+        >>> G = nx.DiGraph([(0, 1), (1, 2), (0, 3), (3, 2)])
+        >>> roots = (v for v, d in G.in_degree() if d == 0)
+        >>> leaves = (v for v, d in G.out_degree() if d == 0)
+        >>> all_paths = []
+        >>> for root in roots:
+        ...     for leaf in leaves:
+        ...         paths = nx.all_simple_paths(G, root, leaf)
+        ...         all_paths.extend(paths)
+        >>> all_paths
+        [[0, 1, 2], [0, 3, 2]]
+
+    Iterate over each path from the root nodes to the leaf nodes in a
+    directed acyclic graph passing all leaves together to avoid unnecessary
+    compute::
+
+        >>> G = nx.DiGraph([(0, 1), (2, 1), (1, 3), (1, 4)])
+        >>> roots = (v for v, d in G.in_degree() if d == 0)
+        >>> leaves = [v for v, d in G.out_degree() if d == 0]
+        >>> all_paths = []
+        >>> for root in roots:
+        ...     paths = nx.all_simple_paths(G, root, leaves)
+        ...     all_paths.extend(paths)
+        >>> all_paths
+        [[0, 1, 3], [0, 1, 4], [2, 1, 3], [2, 1, 4]]
+
+    If parallel edges offer multiple ways to traverse a given sequence of
+    nodes, this sequence of nodes will be returned multiple times:
+
+        >>> G = nx.MultiDiGraph([(0, 1), (0, 1), (1, 2)])
+        >>> list(nx.all_simple_paths(G, 0, 2))
+        [[0, 1, 2], [0, 1, 2]]
+
+    Notes
+    -----
+    This algorithm uses a modified depth-first search to generate the
+    paths [1]_.  A single path can be found in $O(V+E)$ time but the
+    number of simple paths in a graph can be very large, e.g. $O(n!)$ in
+    the complete graph of order $n$.
+
+    This function does not check that a path exists between `source` and
+    `target`. For large graphs, this may result in very long runtimes.
+    Consider using `has_path` to check that a path exists between `source` and
+    `target` before calling this function on large graphs.
+
+    References
+    ----------
+    .. [1] R. Sedgewick, "Algorithms in C, Part 5: Graph Algorithms",
+       Addison Wesley Professional, 3rd ed., 2001.
+
+    See Also
+    --------
+    all_shortest_paths, shortest_path, has_path
+
+    """
+    for edge_path in all_simple_edge_paths(G, source, target, cutoff):
+        yield [source] + [edge[1] for edge in edge_path]
+
+
+@nx._dispatchable
+def all_simple_edge_paths(G, source, target, cutoff=None):
+    """Generate lists of edges for all simple paths in G from source to target.
+
+    A simple path is a path with no repeated nodes.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    source : node
+       Starting node for path
+
+    target : nodes
+       Single node or iterable of nodes at which to end path
+
+    cutoff : integer, optional
+        Depth to stop the search. Only paths of length <= cutoff are returned.
+
+    Returns
+    -------
+    path_generator: generator
+       A generator that produces lists of simple paths.  If there are no paths
+       between the source and target within the given cutoff the generator
+       produces no output.
+       For multigraphs, the list of edges have elements of the form `(u,v,k)`.
+       Where `k` corresponds to the edge key.
+
+    Examples
+    --------
+
+    Print the simple path edges of a Graph::
+
+        >>> g = nx.Graph([(1, 2), (2, 4), (1, 3), (3, 4)])
+        >>> for path in sorted(nx.all_simple_edge_paths(g, 1, 4)):
+        ...     print(path)
+        [(1, 2), (2, 4)]
+        [(1, 3), (3, 4)]
+
+    Print the simple path edges of a MultiGraph. Returned edges come with
+    their associated keys::
+
+        >>> mg = nx.MultiGraph()
+        >>> mg.add_edge(1, 2, key="k0")
+        'k0'
+        >>> mg.add_edge(1, 2, key="k1")
+        'k1'
+        >>> mg.add_edge(2, 3, key="k0")
+        'k0'
+        >>> for path in sorted(nx.all_simple_edge_paths(mg, 1, 3)):
+        ...     print(path)
+        [(1, 2, 'k0'), (2, 3, 'k0')]
+        [(1, 2, 'k1'), (2, 3, 'k0')]
+
+    When ``source`` is one of the targets, the empty path starting and ending at
+    ``source`` without traversing any edge is considered a valid simple edge path
+    and is included in the results:
+
+        >>> G = nx.Graph()
+        >>> G.add_node(0)
+        >>> paths = list(nx.all_simple_edge_paths(G, 0, 0))
+        >>> for path in paths:
+        ...     print(path)
+        []
+        >>> len(paths)
+        1
+
+
+    Notes
+    -----
+    This algorithm uses a modified depth-first search to generate the
+    paths [1]_.  A single path can be found in $O(V+E)$ time but the
+    number of simple paths in a graph can be very large, e.g. $O(n!)$ in
+    the complete graph of order $n$.
+
+    References
+    ----------
+    .. [1] R. Sedgewick, "Algorithms in C, Part 5: Graph Algorithms",
+       Addison Wesley Professional, 3rd ed., 2001.
+
+    See Also
+    --------
+    all_shortest_paths, shortest_path, all_simple_paths
+
+    """
+    if source not in G:
+        raise nx.NodeNotFound(f"source node {source} not in graph")
+
+    if target in G:
+        targets = {target}
+    else:
+        try:
+            targets = set(target)
+        except TypeError as err:
+            raise nx.NodeNotFound(f"target node {target} not in graph") from err
+
+    cutoff = cutoff if cutoff is not None else len(G) - 1
+
+    if cutoff >= 0 and targets:
+        yield from _all_simple_edge_paths(G, source, targets, cutoff)
+
+
+def _all_simple_edge_paths(G, source, targets, cutoff):
+    # We simulate recursion with a stack, keeping the current path being explored
+    # and the outgoing edge iterators at each point in the stack.
+    # To avoid unnecessary checks, the loop is structured in a way such that a path
+    # is considered for yielding only after a new node/edge is added.
+    # We bootstrap the search by adding a dummy iterator to the stack that only yields
+    # a dummy edge to source (so that the trivial path has a chance of being included).
+
+    get_edges = (
+        (lambda node: G.edges(node, keys=True))
+        if G.is_multigraph()
+        else (lambda node: G.edges(node))
+    )
+
+    # The current_path is a dictionary that maps nodes in the path to the edge that was
+    # used to enter that node (instead of a list of edges) because we want both a fast
+    # membership test for nodes in the path and the preservation of insertion order.
+    current_path = {None: None}
+    stack = [iter([(None, source)])]
+
+    while stack:
+        # 1. Try to extend the current path.
+        next_edge = next((e for e in stack[-1] if e[1] not in current_path), None)
+        if next_edge is None:
+            # All edges of the last node in the current path have been explored.
+            stack.pop()
+            current_path.popitem()
+            continue
+        previous_node, next_node, *_ = next_edge
+
+        # 2. Check if we've reached a target.
+        if next_node in targets:
+            yield (list(current_path.values()) + [next_edge])[2:]  # remove dummy edge
+
+        # 3. Only expand the search through the next node if it makes sense.
+        if len(current_path) - 1 < cutoff and (
+            targets - current_path.keys() - {next_node}
+        ):
+            current_path[next_node] = next_edge
+            stack.append(iter(get_edges(next_node)))
+
+
+@not_implemented_for("multigraph")
+@nx._dispatchable(edge_attrs="weight")
+def shortest_simple_paths(G, source, target, weight=None):
+    """Generate all simple paths in the graph G from source to target,
+       starting from shortest ones.
+
+    A simple path is a path with no repeated nodes.
+
+    If a weighted shortest path search is to be used, no negative weights
+    are allowed.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    source : node
+       Starting node for path
+
+    target : node
+       Ending node for path
+
+    weight : string or function
+        If it is a string, it is the name of the edge attribute to be
+        used as a weight.
+
+        If it is a function, the weight of an edge is the value returned
+        by the function. The function must accept exactly three positional
+        arguments: the two endpoints of an edge and the dictionary of edge
+        attributes for that edge. The function must return a number or None.
+        The weight function can be used to hide edges by returning None.
+        So ``weight = lambda u, v, d: 1 if d['color']=="red" else None``
+        will find the shortest red path.
+
+        If None all edges are considered to have unit weight. Default
+        value None.
+
+    Returns
+    -------
+    path_generator: generator
+       A generator that produces lists of simple paths, in order from
+       shortest to longest.
+
+    Raises
+    ------
+    NetworkXNoPath
+       If no path exists between source and target.
+
+    NetworkXError
+       If source or target nodes are not in the input graph.
+
+    NetworkXNotImplemented
+       If the input graph is a Multi[Di]Graph.
+
+    Examples
+    --------
+
+    >>> G = nx.cycle_graph(7)
+    >>> paths = list(nx.shortest_simple_paths(G, 0, 3))
+    >>> print(paths)
+    [[0, 1, 2, 3], [0, 6, 5, 4, 3]]
+
+    You can use this function to efficiently compute the k shortest/best
+    paths between two nodes.
+
+    >>> from itertools import islice
+    >>> def k_shortest_paths(G, source, target, k, weight=None):
+    ...     return list(
+    ...         islice(nx.shortest_simple_paths(G, source, target, weight=weight), k)
+    ...     )
+    >>> for path in k_shortest_paths(G, 0, 3, 2):
+    ...     print(path)
+    [0, 1, 2, 3]
+    [0, 6, 5, 4, 3]
+
+    Notes
+    -----
+    This procedure is based on algorithm by Jin Y. Yen [1]_.  Finding
+    the first $K$ paths requires $O(KN^3)$ operations.
+
+    See Also
+    --------
+    all_shortest_paths
+    shortest_path
+    all_simple_paths
+
+    References
+    ----------
+    .. [1] Jin Y. Yen, "Finding the K Shortest Loopless Paths in a
+       Network", Management Science, Vol. 17, No. 11, Theory Series
+       (Jul., 1971), pp. 712-716.
+
+    """
+    if source not in G:
+        raise nx.NodeNotFound(f"source node {source} not in graph")
+
+    if target not in G:
+        raise nx.NodeNotFound(f"target node {target} not in graph")
+
+    if weight is None:
+        length_func = len
+        shortest_path_func = _bidirectional_shortest_path
+    else:
+        wt = _weight_function(G, weight)
+
+        def length_func(path):
+            return sum(
+                wt(u, v, G.get_edge_data(u, v)) for (u, v) in zip(path, path[1:])
+            )
+
+        shortest_path_func = _bidirectional_dijkstra
+
+    listA = []
+    listB = PathBuffer()
+    prev_path = None
+    while True:
+        if not prev_path:
+            length, path = shortest_path_func(G, source, target, weight=weight)
+            listB.push(length, path)
+        else:
+            ignore_nodes = set()
+            ignore_edges = set()
+            for i in range(1, len(prev_path)):
+                root = prev_path[:i]
+                root_length = length_func(root)
+                for path in listA:
+                    if path[:i] == root:
+                        ignore_edges.add((path[i - 1], path[i]))
+                try:
+                    length, spur = shortest_path_func(
+                        G,
+                        root[-1],
+                        target,
+                        ignore_nodes=ignore_nodes,
+                        ignore_edges=ignore_edges,
+                        weight=weight,
+                    )
+                    path = root[:-1] + spur
+                    listB.push(root_length + length, path)
+                except nx.NetworkXNoPath:
+                    pass
+                ignore_nodes.add(root[-1])
+
+        if listB:
+            path = listB.pop()
+            yield path
+            listA.append(path)
+            prev_path = path
+        else:
+            break
+
+
+class PathBuffer:
+    def __init__(self):
+        self.paths = set()
+        self.sortedpaths = []
+        self.counter = count()
+
+    def __len__(self):
+        return len(self.sortedpaths)
+
+    def push(self, cost, path):
+        hashable_path = tuple(path)
+        if hashable_path not in self.paths:
+            heappush(self.sortedpaths, (cost, next(self.counter), path))
+            self.paths.add(hashable_path)
+
+    def pop(self):
+        (cost, num, path) = heappop(self.sortedpaths)
+        hashable_path = tuple(path)
+        self.paths.remove(hashable_path)
+        return path
+
+
+def _bidirectional_shortest_path(
+    G, source, target, ignore_nodes=None, ignore_edges=None, weight=None
+):
+    """Returns the shortest path between source and target ignoring
+       nodes and edges in the containers ignore_nodes and ignore_edges.
+
+    This is a custom modification of the standard bidirectional shortest
+    path implementation at networkx.algorithms.unweighted
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    source : node
+       starting node for path
+
+    target : node
+       ending node for path
+
+    ignore_nodes : container of nodes
+       nodes to ignore, optional
+
+    ignore_edges : container of edges
+       edges to ignore, optional
+
+    weight : None
+       This function accepts a weight argument for convenience of
+       shortest_simple_paths function. It will be ignored.
+
+    Returns
+    -------
+    path: list
+       List of nodes in a path from source to target.
+
+    Raises
+    ------
+    NetworkXNoPath
+       If no path exists between source and target.
+
+    See Also
+    --------
+    shortest_path
+
+    """
+    # call helper to do the real work
+    results = _bidirectional_pred_succ(G, source, target, ignore_nodes, ignore_edges)
+    pred, succ, w = results
+
+    # build path from pred+w+succ
+    path = []
+    # from w to target
+    while w is not None:
+        path.append(w)
+        w = succ[w]
+    # from source to w
+    w = pred[path[0]]
+    while w is not None:
+        path.insert(0, w)
+        w = pred[w]
+
+    return len(path), path
+
+
+def _bidirectional_pred_succ(G, source, target, ignore_nodes=None, ignore_edges=None):
+    """Bidirectional shortest path helper.
+    Returns (pred,succ,w) where
+    pred is a dictionary of predecessors from w to the source, and
+    succ is a dictionary of successors from w to the target.
+    """
+    # does BFS from both source and target and meets in the middle
+    if ignore_nodes and (source in ignore_nodes or target in ignore_nodes):
+        raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
+    if target == source:
+        return ({target: None}, {source: None}, source)
+
+    # handle either directed or undirected
+    if G.is_directed():
+        Gpred = G.predecessors
+        Gsucc = G.successors
+    else:
+        Gpred = G.neighbors
+        Gsucc = G.neighbors
+
+    # support optional nodes filter
+    if ignore_nodes:
+
+        def filter_iter(nodes):
+            def iterate(v):
+                for w in nodes(v):
+                    if w not in ignore_nodes:
+                        yield w
+
+            return iterate
+
+        Gpred = filter_iter(Gpred)
+        Gsucc = filter_iter(Gsucc)
+
+    # support optional edges filter
+    if ignore_edges:
+        if G.is_directed():
+
+            def filter_pred_iter(pred_iter):
+                def iterate(v):
+                    for w in pred_iter(v):
+                        if (w, v) not in ignore_edges:
+                            yield w
+
+                return iterate
+
+            def filter_succ_iter(succ_iter):
+                def iterate(v):
+                    for w in succ_iter(v):
+                        if (v, w) not in ignore_edges:
+                            yield w
+
+                return iterate
+
+            Gpred = filter_pred_iter(Gpred)
+            Gsucc = filter_succ_iter(Gsucc)
+
+        else:
+
+            def filter_iter(nodes):
+                def iterate(v):
+                    for w in nodes(v):
+                        if (v, w) not in ignore_edges and (w, v) not in ignore_edges:
+                            yield w
+
+                return iterate
+
+            Gpred = filter_iter(Gpred)
+            Gsucc = filter_iter(Gsucc)
+
+    # predecessor and successors in search
+    pred = {source: None}
+    succ = {target: None}
+
+    # initialize fringes, start with forward
+    forward_fringe = [source]
+    reverse_fringe = [target]
+
+    while forward_fringe and reverse_fringe:
+        if len(forward_fringe) <= len(reverse_fringe):
+            this_level = forward_fringe
+            forward_fringe = []
+            for v in this_level:
+                for w in Gsucc(v):
+                    if w not in pred:
+                        forward_fringe.append(w)
+                        pred[w] = v
+                    if w in succ:
+                        # found path
+                        return pred, succ, w
+        else:
+            this_level = reverse_fringe
+            reverse_fringe = []
+            for v in this_level:
+                for w in Gpred(v):
+                    if w not in succ:
+                        succ[w] = v
+                        reverse_fringe.append(w)
+                    if w in pred:
+                        # found path
+                        return pred, succ, w
+
+    raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
+
+
+def _bidirectional_dijkstra(
+    G, source, target, weight="weight", ignore_nodes=None, ignore_edges=None
+):
+    """Dijkstra's algorithm for shortest paths using bidirectional search.
+
+    This function returns the shortest path between source and target
+    ignoring nodes and edges in the containers ignore_nodes and
+    ignore_edges.
+
+    This is a custom modification of the standard Dijkstra bidirectional
+    shortest path implementation at networkx.algorithms.weighted
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    source : node
+        Starting node.
+
+    target : node
+        Ending node.
+
+    weight: string, function, optional (default='weight')
+        Edge data key or weight function corresponding to the edge weight
+        If this is a function, the weight of an edge is the value
+        returned by the function. The function must accept exactly three
+        positional arguments: the two endpoints of an edge and the
+        dictionary of edge attributes for that edge. The function must
+        return a number or None to indicate a hidden edge.
+
+    ignore_nodes : container of nodes
+        nodes to ignore, optional
+
+    ignore_edges : container of edges
+        edges to ignore, optional
+
+    Returns
+    -------
+    length : number
+        Shortest path length.
+
+    Returns a tuple of two dictionaries keyed by node.
+    The first dictionary stores distance from the source.
+    The second stores the path from the source to that node.
+
+    Raises
+    ------
+    NetworkXNoPath
+        If no path exists between source and target.
+
+    Notes
+    -----
+    Edge weight attributes must be numerical.
+    Distances are calculated as sums of weighted edges traversed.
+
+    The weight function can be used to hide edges by returning None.
+    So ``weight = lambda u, v, d: 1 if d['color']=="red" else None``
+    will find the shortest red path.
+
+    In practice  bidirectional Dijkstra is much more than twice as fast as
+    ordinary Dijkstra.
+
+    Ordinary Dijkstra expands nodes in a sphere-like manner from the
+    source. The radius of this sphere will eventually be the length
+    of the shortest path. Bidirectional Dijkstra will expand nodes
+    from both the source and the target, making two spheres of half
+    this radius. Volume of the first sphere is pi*r*r while the
+    others are 2*pi*r/2*r/2, making up half the volume.
+
+    This algorithm is not guaranteed to work if edge weights
+    are negative or are floating point numbers
+    (overflows and roundoff errors can cause problems).
+
+    See Also
+    --------
+    shortest_path
+    shortest_path_length
+    """
+    if ignore_nodes and (source in ignore_nodes or target in ignore_nodes):
+        raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
+    if source == target:
+        if source not in G:
+            raise nx.NodeNotFound(f"Node {source} not in graph")
+        return (0, [source])
+
+    # handle either directed or undirected
+    if G.is_directed():
+        Gpred = G.predecessors
+        Gsucc = G.successors
+    else:
+        Gpred = G.neighbors
+        Gsucc = G.neighbors
+
+    # support optional nodes filter
+    if ignore_nodes:
+
+        def filter_iter(nodes):
+            def iterate(v):
+                for w in nodes(v):
+                    if w not in ignore_nodes:
+                        yield w
+
+            return iterate
+
+        Gpred = filter_iter(Gpred)
+        Gsucc = filter_iter(Gsucc)
+
+    # support optional edges filter
+    if ignore_edges:
+        if G.is_directed():
+
+            def filter_pred_iter(pred_iter):
+                def iterate(v):
+                    for w in pred_iter(v):
+                        if (w, v) not in ignore_edges:
+                            yield w
+
+                return iterate
+
+            def filter_succ_iter(succ_iter):
+                def iterate(v):
+                    for w in succ_iter(v):
+                        if (v, w) not in ignore_edges:
+                            yield w
+
+                return iterate
+
+            Gpred = filter_pred_iter(Gpred)
+            Gsucc = filter_succ_iter(Gsucc)
+
+        else:
+
+            def filter_iter(nodes):
+                def iterate(v):
+                    for w in nodes(v):
+                        if (v, w) not in ignore_edges and (w, v) not in ignore_edges:
+                            yield w
+
+                return iterate
+
+            Gpred = filter_iter(Gpred)
+            Gsucc = filter_iter(Gsucc)
+
+    wt = _weight_function(G, weight)
+    push = heappush
+    pop = heappop
+    # Init:   Forward             Backward
+    dists = [{}, {}]  # dictionary of final distances
+    paths = [{source: [source]}, {target: [target]}]  # dictionary of paths
+    fringe = [[], []]  # heap of (distance, node) tuples for
+    # extracting next node to expand
+    seen = [{source: 0}, {target: 0}]  # dictionary of distances to
+    # nodes seen
+    c = count()
+    # initialize fringe heap
+    push(fringe[0], (0, next(c), source))
+    push(fringe[1], (0, next(c), target))
+    # neighs for extracting correct neighbor information
+    neighs = [Gsucc, Gpred]
+    # variables to hold shortest discovered path
+    # finaldist = 1e30000
+    finalpath = []
+    dir = 1
+    while fringe[0] and fringe[1]:
+        # choose direction
+        # dir == 0 is forward direction and dir == 1 is back
+        dir = 1 - dir
+        # extract closest to expand
+        (dist, _, v) = pop(fringe[dir])
+        if v in dists[dir]:
+            # Shortest path to v has already been found
+            continue
+        # update distance
+        dists[dir][v] = dist  # equal to seen[dir][v]
+        if v in dists[1 - dir]:
+            # if we have scanned v in both directions we are done
+            # we have now discovered the shortest path
+            return (finaldist, finalpath)
+
+        for w in neighs[dir](v):
+            if dir == 0:  # forward
+                minweight = wt(v, w, G.get_edge_data(v, w))
+            else:  # back, must remember to change v,w->w,v
+                minweight = wt(w, v, G.get_edge_data(w, v))
+            if minweight is None:
+                continue
+            vwLength = dists[dir][v] + minweight
+
+            if w in dists[dir]:
+                if vwLength < dists[dir][w]:
+                    raise ValueError("Contradictory paths found: negative weights?")
+            elif w not in seen[dir] or vwLength < seen[dir][w]:
+                # relaxing
+                seen[dir][w] = vwLength
+                push(fringe[dir], (vwLength, next(c), w))
+                paths[dir][w] = paths[dir][v] + [w]
+                if w in seen[0] and w in seen[1]:
+                    # see if this path is better than the already
+                    # discovered shortest path
+                    totaldist = seen[0][w] + seen[1][w]
+                    if finalpath == [] or finaldist > totaldist:
+                        finaldist = totaldist
+                        revpath = paths[1][w][:]
+                        revpath.reverse()
+                        finalpath = paths[0][w] + revpath[1:]
+    raise nx.NetworkXNoPath(f"No path between {source} and {target}.")