aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are hereHEADmaster
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__init__.py5
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/astar.py241
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/dense.py260
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/generic.py730
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_astar.py248
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_dense.py212
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py89
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_generic.py450
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_unweighted.py149
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_weighted.py972
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/unweighted.py579
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/weighted.py2520
13 files changed, 6455 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__init__.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__init__.py
new file mode 100644
index 00000000..eb0d91ce
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__init__.py
@@ -0,0 +1,5 @@
+from networkx.algorithms.shortest_paths.generic import *
+from networkx.algorithms.shortest_paths.unweighted import *
+from networkx.algorithms.shortest_paths.weighted import *
+from networkx.algorithms.shortest_paths.astar import *
+from networkx.algorithms.shortest_paths.dense import *
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/astar.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/astar.py
new file mode 100644
index 00000000..8d988477
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/astar.py
@@ -0,0 +1,241 @@
+"""Shortest paths and path lengths using the A* ("A star") algorithm."""
+
+from heapq import heappop, heappush
+from itertools import count
+
+import networkx as nx
+from networkx.algorithms.shortest_paths.weighted import _weight_function
+
+__all__ = ["astar_path", "astar_path_length"]
+
+
+@nx._dispatchable(edge_attrs="weight", preserve_node_attrs="heuristic")
+def astar_path(G, source, target, heuristic=None, weight="weight", *, cutoff=None):
+ """Returns a list of nodes in a shortest path between source and target
+ using the A* ("A-star") algorithm.
+
+ There may be more than one shortest path. This returns only one.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node
+ Starting node for path
+
+ target : node
+ Ending node for path
+
+ heuristic : function
+ A function to evaluate the estimate of the distance
+ from the a node to the target. The function takes
+ two nodes arguments and must return a number.
+ If the heuristic is inadmissible (if it might
+ overestimate the cost of reaching the goal from a node),
+ the result may not be a shortest path.
+ The algorithm does not support updating heuristic
+ values for the same node due to caching the first
+ heuristic calculation per node.
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+ 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.
+
+ cutoff : float, optional
+ If this is provided, the search will be bounded to this value. I.e. if
+ the evaluation function surpasses this value for a node n, the node will not
+ be expanded further and will be ignored. More formally, let h'(n) be the
+ heuristic function, and g(n) be the cost of reaching n from the source node. Then,
+ if g(n) + h'(n) > cutoff, the node will not be explored further.
+ Note that if the heuristic is inadmissible, it is possible that paths
+ are ignored even though they satisfy the cutoff.
+
+ Raises
+ ------
+ NetworkXNoPath
+ If no path exists between source and target.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> print(nx.astar_path(G, 0, 4))
+ [0, 1, 2, 3, 4]
+ >>> G = nx.grid_graph(dim=[3, 3]) # nodes are two-tuples (x,y)
+ >>> nx.set_edge_attributes(G, {e: e[1][0] * 2 for e in G.edges()}, "cost")
+ >>> def dist(a, b):
+ ... (x1, y1) = a
+ ... (x2, y2) = b
+ ... return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5
+ >>> print(nx.astar_path(G, (0, 0), (2, 2), heuristic=dist, weight="cost"))
+ [(0, 0), (0, 1), (0, 2), (1, 2), (2, 2)]
+
+ 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.
+
+ See Also
+ --------
+ shortest_path, dijkstra_path
+
+ """
+ if source not in G:
+ raise nx.NodeNotFound(f"Source {source} is not in G")
+
+ if target not in G:
+ raise nx.NodeNotFound(f"Target {target} is not in G")
+
+ if heuristic is None:
+ # The default heuristic is h=0 - same as Dijkstra's algorithm
+ def heuristic(u, v):
+ return 0
+
+ push = heappush
+ pop = heappop
+ weight = _weight_function(G, weight)
+
+ G_succ = G._adj # For speed-up (and works for both directed and undirected graphs)
+
+ # The queue stores priority, node, cost to reach, and parent.
+ # Uses Python heapq to keep in priority order.
+ # Add a counter to the queue to prevent the underlying heap from
+ # attempting to compare the nodes themselves. The hash breaks ties in the
+ # priority and is guaranteed unique for all nodes in the graph.
+ c = count()
+ queue = [(0, next(c), source, 0, None)]
+
+ # Maps enqueued nodes to distance of discovered paths and the
+ # computed heuristics to target. We avoid computing the heuristics
+ # more than once and inserting the node into the queue too many times.
+ enqueued = {}
+ # Maps explored nodes to parent closest to the source.
+ explored = {}
+
+ while queue:
+ # Pop the smallest item from queue.
+ _, __, curnode, dist, parent = pop(queue)
+
+ if curnode == target:
+ path = [curnode]
+ node = parent
+ while node is not None:
+ path.append(node)
+ node = explored[node]
+ path.reverse()
+ return path
+
+ if curnode in explored:
+ # Do not override the parent of starting node
+ if explored[curnode] is None:
+ continue
+
+ # Skip bad paths that were enqueued before finding a better one
+ qcost, h = enqueued[curnode]
+ if qcost < dist:
+ continue
+
+ explored[curnode] = parent
+
+ for neighbor, w in G_succ[curnode].items():
+ cost = weight(curnode, neighbor, w)
+ if cost is None:
+ continue
+ ncost = dist + cost
+ if neighbor in enqueued:
+ qcost, h = enqueued[neighbor]
+ # if qcost <= ncost, a less costly path from the
+ # neighbor to the source was already determined.
+ # Therefore, we won't attempt to push this neighbor
+ # to the queue
+ if qcost <= ncost:
+ continue
+ else:
+ h = heuristic(neighbor, target)
+
+ if cutoff and ncost + h > cutoff:
+ continue
+
+ enqueued[neighbor] = ncost, h
+ push(queue, (ncost + h, next(c), neighbor, ncost, curnode))
+
+ raise nx.NetworkXNoPath(f"Node {target} not reachable from {source}")
+
+
+@nx._dispatchable(edge_attrs="weight", preserve_node_attrs="heuristic")
+def astar_path_length(
+ G, source, target, heuristic=None, weight="weight", *, cutoff=None
+):
+ """Returns the length of the shortest path between source and target using
+ the A* ("A-star") algorithm.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node
+ Starting node for path
+
+ target : node
+ Ending node for path
+
+ heuristic : function
+ A function to evaluate the estimate of the distance
+ from the a node to the target. The function takes
+ two nodes arguments and must return a number.
+ If the heuristic is inadmissible (if it might
+ overestimate the cost of reaching the goal from a node),
+ the result may not be a shortest path.
+ The algorithm does not support updating heuristic
+ values for the same node due to caching the first
+ heuristic calculation per node.
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+ 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.
+
+ cutoff : float, optional
+ If this is provided, the search will be bounded to this value. I.e. if
+ the evaluation function surpasses this value for a node n, the node will not
+ be expanded further and will be ignored. More formally, let h'(n) be the
+ heuristic function, and g(n) be the cost of reaching n from the source node. Then,
+ if g(n) + h'(n) > cutoff, the node will not be explored further.
+ Note that if the heuristic is inadmissible, it is possible that paths
+ are ignored even though they satisfy the cutoff.
+
+ Raises
+ ------
+ NetworkXNoPath
+ If no path exists between source and target.
+
+ See Also
+ --------
+ astar_path
+
+ """
+ if source not in G or target not in G:
+ msg = f"Either source {source} or target {target} is not in G"
+ raise nx.NodeNotFound(msg)
+
+ weight = _weight_function(G, weight)
+ path = astar_path(G, source, target, heuristic, weight, cutoff=cutoff)
+ return sum(weight(u, v, G[u][v]) for u, v in zip(path[:-1], path[1:]))
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/dense.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/dense.py
new file mode 100644
index 00000000..107b9208
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/dense.py
@@ -0,0 +1,260 @@
+"""Floyd-Warshall algorithm for shortest paths."""
+
+import networkx as nx
+
+__all__ = [
+ "floyd_warshall",
+ "floyd_warshall_predecessor_and_distance",
+ "reconstruct_path",
+ "floyd_warshall_numpy",
+]
+
+
+@nx._dispatchable(edge_attrs="weight")
+def floyd_warshall_numpy(G, nodelist=None, weight="weight"):
+ """Find all-pairs shortest path lengths using Floyd's algorithm.
+
+ This algorithm for finding shortest paths takes advantage of
+ matrix representations of a graph and works well for dense
+ graphs where all-pairs shortest path lengths are desired.
+ The results are returned as a NumPy array, distance[i, j],
+ where i and j are the indexes of two nodes in nodelist.
+ The entry distance[i, j] is the distance along a shortest
+ path from i to j. If no path exists the distance is Inf.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ nodelist : list, optional (default=G.nodes)
+ The rows and columns are ordered by the nodes in nodelist.
+ If nodelist is None then the ordering is produced by G.nodes.
+ Nodelist should include all nodes in G.
+
+ weight: string, optional (default='weight')
+ Edge data key corresponding to the edge weight.
+
+ Returns
+ -------
+ distance : 2D numpy.ndarray
+ A numpy array of shortest path distances between nodes.
+ If there is no path between two nodes the value is Inf.
+
+ Examples
+ --------
+ >>> G = nx.DiGraph()
+ >>> G.add_weighted_edges_from(
+ ... [(0, 1, 5), (1, 2, 2), (2, 3, -3), (1, 3, 10), (3, 2, 8)]
+ ... )
+ >>> nx.floyd_warshall_numpy(G)
+ array([[ 0., 5., 7., 4.],
+ [inf, 0., 2., -1.],
+ [inf, inf, 0., -3.],
+ [inf, inf, 8., 0.]])
+
+ Notes
+ -----
+ Floyd's algorithm is appropriate for finding shortest paths in
+ dense graphs or graphs with negative weights when Dijkstra's
+ algorithm fails. This algorithm can still fail if there are negative
+ cycles. It has running time $O(n^3)$ with running space of $O(n^2)$.
+
+ Raises
+ ------
+ NetworkXError
+ If nodelist is not a list of the nodes in G.
+ """
+ import numpy as np
+
+ if nodelist is not None:
+ if not (len(nodelist) == len(G) == len(set(nodelist))):
+ raise nx.NetworkXError(
+ "nodelist must contain every node in G with no repeats."
+ "If you wanted a subgraph of G use G.subgraph(nodelist)"
+ )
+
+ # To handle cases when an edge has weight=0, we must make sure that
+ # nonedges are not given the value 0 as well.
+ A = nx.to_numpy_array(
+ G, nodelist, multigraph_weight=min, weight=weight, nonedge=np.inf
+ )
+ n, m = A.shape
+ np.fill_diagonal(A, 0) # diagonal elements should be zero
+ for i in range(n):
+ # The second term has the same shape as A due to broadcasting
+ A = np.minimum(A, A[i, :][np.newaxis, :] + A[:, i][:, np.newaxis])
+ return A
+
+
+@nx._dispatchable(edge_attrs="weight")
+def floyd_warshall_predecessor_and_distance(G, weight="weight"):
+ """Find all-pairs shortest path lengths using Floyd's algorithm.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ weight: string, optional (default= 'weight')
+ Edge data key corresponding to the edge weight.
+
+ Returns
+ -------
+ predecessor,distance : dictionaries
+ Dictionaries, keyed by source and target, of predecessors and distances
+ in the shortest path.
+
+ Examples
+ --------
+ >>> G = nx.DiGraph()
+ >>> G.add_weighted_edges_from(
+ ... [
+ ... ("s", "u", 10),
+ ... ("s", "x", 5),
+ ... ("u", "v", 1),
+ ... ("u", "x", 2),
+ ... ("v", "y", 1),
+ ... ("x", "u", 3),
+ ... ("x", "v", 5),
+ ... ("x", "y", 2),
+ ... ("y", "s", 7),
+ ... ("y", "v", 6),
+ ... ]
+ ... )
+ >>> predecessors, _ = nx.floyd_warshall_predecessor_and_distance(G)
+ >>> print(nx.reconstruct_path("s", "v", predecessors))
+ ['s', 'x', 'u', 'v']
+
+ Notes
+ -----
+ Floyd's algorithm is appropriate for finding shortest paths
+ in dense graphs or graphs with negative weights when Dijkstra's algorithm
+ fails. This algorithm can still fail if there are negative cycles.
+ It has running time $O(n^3)$ with running space of $O(n^2)$.
+
+ See Also
+ --------
+ floyd_warshall
+ floyd_warshall_numpy
+ all_pairs_shortest_path
+ all_pairs_shortest_path_length
+ """
+ from collections import defaultdict
+
+ # dictionary-of-dictionaries representation for dist and pred
+ # use some defaultdict magick here
+ # for dist the default is the floating point inf value
+ dist = defaultdict(lambda: defaultdict(lambda: float("inf")))
+ for u in G:
+ dist[u][u] = 0
+ pred = defaultdict(dict)
+ # initialize path distance dictionary to be the adjacency matrix
+ # also set the distance to self to 0 (zero diagonal)
+ undirected = not G.is_directed()
+ for u, v, d in G.edges(data=True):
+ e_weight = d.get(weight, 1.0)
+ dist[u][v] = min(e_weight, dist[u][v])
+ pred[u][v] = u
+ if undirected:
+ dist[v][u] = min(e_weight, dist[v][u])
+ pred[v][u] = v
+ for w in G:
+ dist_w = dist[w] # save recomputation
+ for u in G:
+ dist_u = dist[u] # save recomputation
+ for v in G:
+ d = dist_u[w] + dist_w[v]
+ if dist_u[v] > d:
+ dist_u[v] = d
+ pred[u][v] = pred[w][v]
+ return dict(pred), dict(dist)
+
+
+@nx._dispatchable(graphs=None)
+def reconstruct_path(source, target, predecessors):
+ """Reconstruct a path from source to target using the predecessors
+ dict as returned by floyd_warshall_predecessor_and_distance
+
+ Parameters
+ ----------
+ source : node
+ Starting node for path
+
+ target : node
+ Ending node for path
+
+ predecessors: dictionary
+ Dictionary, keyed by source and target, of predecessors in the
+ shortest path, as returned by floyd_warshall_predecessor_and_distance
+
+ Returns
+ -------
+ path : list
+ A list of nodes containing the shortest path from source to target
+
+ If source and target are the same, an empty list is returned
+
+ Notes
+ -----
+ This function is meant to give more applicability to the
+ floyd_warshall_predecessor_and_distance function
+
+ See Also
+ --------
+ floyd_warshall_predecessor_and_distance
+ """
+ if source == target:
+ return []
+ prev = predecessors[source]
+ curr = prev[target]
+ path = [target, curr]
+ while curr != source:
+ curr = prev[curr]
+ path.append(curr)
+ return list(reversed(path))
+
+
+@nx._dispatchable(edge_attrs="weight")
+def floyd_warshall(G, weight="weight"):
+ """Find all-pairs shortest path lengths using Floyd's algorithm.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ weight: string, optional (default= 'weight')
+ Edge data key corresponding to the edge weight.
+
+
+ Returns
+ -------
+ distance : dict
+ A dictionary, keyed by source and target, of shortest paths distances
+ between nodes.
+
+ Examples
+ --------
+ >>> G = nx.DiGraph()
+ >>> G.add_weighted_edges_from(
+ ... [(0, 1, 5), (1, 2, 2), (2, 3, -3), (1, 3, 10), (3, 2, 8)]
+ ... )
+ >>> fw = nx.floyd_warshall(G, weight="weight")
+ >>> results = {a: dict(b) for a, b in fw.items()}
+ >>> print(results)
+ {0: {0: 0, 1: 5, 2: 7, 3: 4}, 1: {1: 0, 2: 2, 3: -1, 0: inf}, 2: {2: 0, 3: -3, 0: inf, 1: inf}, 3: {3: 0, 2: 8, 0: inf, 1: inf}}
+
+ Notes
+ -----
+ Floyd's algorithm is appropriate for finding shortest paths
+ in dense graphs or graphs with negative weights when Dijkstra's algorithm
+ fails. This algorithm can still fail if there are negative cycles.
+ It has running time $O(n^3)$ with running space of $O(n^2)$.
+
+ See Also
+ --------
+ floyd_warshall_predecessor_and_distance
+ floyd_warshall_numpy
+ all_pairs_shortest_path
+ all_pairs_shortest_path_length
+ """
+ # could make this its own function to reduce memory costs
+ return floyd_warshall_predecessor_and_distance(G, weight=weight)[1]
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/generic.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/generic.py
new file mode 100644
index 00000000..9ac48c90
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/generic.py
@@ -0,0 +1,730 @@
+"""
+Compute the shortest paths and path lengths between nodes in the graph.
+
+These algorithms work with undirected and directed graphs.
+
+"""
+
+import warnings
+
+import networkx as nx
+
+__all__ = [
+ "shortest_path",
+ "all_shortest_paths",
+ "single_source_all_shortest_paths",
+ "all_pairs_all_shortest_paths",
+ "shortest_path_length",
+ "average_shortest_path_length",
+ "has_path",
+]
+
+
+@nx._dispatchable
+def has_path(G, source, target):
+ """Returns *True* if *G* has a path from *source* to *target*.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node
+ Starting node for path
+
+ target : node
+ Ending node for path
+ """
+ try:
+ nx.shortest_path(G, source, target)
+ except nx.NetworkXNoPath:
+ return False
+ return True
+
+
+@nx._dispatchable(edge_attrs="weight")
+def shortest_path(G, source=None, target=None, weight=None, method="dijkstra"):
+ """Compute shortest paths in the graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node, optional
+ Starting node for path. If not specified, compute shortest
+ paths for each possible starting node.
+
+ target : node, optional
+ Ending node for path. If not specified, compute shortest
+ paths to all possible nodes.
+
+ weight : None, string or function, optional (default = None)
+ If None, every edge has weight/distance/cost 1.
+ If a string, use this edge attribute as the edge weight.
+ Any edge attribute not present defaults to 1.
+ 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.
+
+ method : string, optional (default = 'dijkstra')
+ The algorithm to use to compute the path.
+ Supported options: 'dijkstra', 'bellman-ford'.
+ Other inputs produce a ValueError.
+ If `weight` is None, unweighted graph methods are used, and this
+ suggestion is ignored.
+
+ Returns
+ -------
+ path: list or dictionary
+ All returned paths include both the source and target in the path.
+
+ If the source and target are both specified, return a single list
+ of nodes in a shortest path from the source to the target.
+
+ If only the source is specified, return a dictionary keyed by
+ targets with a list of nodes in a shortest path from the source
+ to one of the targets.
+
+ If only the target is specified, return a dictionary keyed by
+ sources with a list of nodes in a shortest path from one of the
+ sources to the target.
+
+ If neither the source nor target are specified return a dictionary
+ of dictionaries with path[source][target]=[list of nodes in path].
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` is not in `G`.
+
+ ValueError
+ If `method` is not among the supported options.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> print(nx.shortest_path(G, source=0, target=4))
+ [0, 1, 2, 3, 4]
+ >>> p = nx.shortest_path(G, source=0) # target not specified
+ >>> p[3] # shortest path from source=0 to target=3
+ [0, 1, 2, 3]
+ >>> p = nx.shortest_path(G, target=4) # source not specified
+ >>> p[1] # shortest path from source=1 to target=4
+ [1, 2, 3, 4]
+ >>> p = dict(nx.shortest_path(G)) # source, target not specified
+ >>> p[2][4] # shortest path from source=2 to target=4
+ [2, 3, 4]
+
+ Notes
+ -----
+ There may be more than one shortest path between a source and target.
+ This returns only one of them.
+
+ See Also
+ --------
+ all_pairs_shortest_path
+ all_pairs_dijkstra_path
+ all_pairs_bellman_ford_path
+ single_source_shortest_path
+ single_source_dijkstra_path
+ single_source_bellman_ford_path
+ """
+ if method not in ("dijkstra", "bellman-ford"):
+ # so we don't need to check in each branch later
+ raise ValueError(f"method not supported: {method}")
+ method = "unweighted" if weight is None else method
+ if source is None:
+ if target is None:
+ warnings.warn(
+ (
+ "\n\nshortest_path will return an iterator that yields\n"
+ "(node, path) pairs instead of a dictionary when source\n"
+ "and target are unspecified beginning in version 3.5\n\n"
+ "To keep the current behavior, use:\n\n"
+ "\tdict(nx.shortest_path(G))"
+ ),
+ FutureWarning,
+ stacklevel=3,
+ )
+
+ # Find paths between all pairs.
+ if method == "unweighted":
+ paths = dict(nx.all_pairs_shortest_path(G))
+ elif method == "dijkstra":
+ paths = dict(nx.all_pairs_dijkstra_path(G, weight=weight))
+ else: # method == 'bellman-ford':
+ paths = dict(nx.all_pairs_bellman_ford_path(G, weight=weight))
+ else:
+ # Find paths from all nodes co-accessible to the target.
+ if G.is_directed():
+ G = G.reverse(copy=False)
+ if method == "unweighted":
+ paths = nx.single_source_shortest_path(G, target)
+ elif method == "dijkstra":
+ paths = nx.single_source_dijkstra_path(G, target, weight=weight)
+ else: # method == 'bellman-ford':
+ paths = nx.single_source_bellman_ford_path(G, target, weight=weight)
+ # Now flip the paths so they go from a source to the target.
+ for target in paths:
+ paths[target] = list(reversed(paths[target]))
+ else:
+ if target is None:
+ # Find paths to all nodes accessible from the source.
+ if method == "unweighted":
+ paths = nx.single_source_shortest_path(G, source)
+ elif method == "dijkstra":
+ paths = nx.single_source_dijkstra_path(G, source, weight=weight)
+ else: # method == 'bellman-ford':
+ paths = nx.single_source_bellman_ford_path(G, source, weight=weight)
+ else:
+ # Find shortest source-target path.
+ if method == "unweighted":
+ paths = nx.bidirectional_shortest_path(G, source, target)
+ elif method == "dijkstra":
+ _, paths = nx.bidirectional_dijkstra(G, source, target, weight)
+ else: # method == 'bellman-ford':
+ paths = nx.bellman_ford_path(G, source, target, weight)
+ return paths
+
+
+@nx._dispatchable(edge_attrs="weight")
+def shortest_path_length(G, source=None, target=None, weight=None, method="dijkstra"):
+ """Compute shortest path lengths in the graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node, optional
+ Starting node for path.
+ If not specified, compute shortest path lengths using all nodes as
+ source nodes.
+
+ target : node, optional
+ Ending node for path.
+ If not specified, compute shortest path lengths using all nodes as
+ target nodes.
+
+ weight : None, string or function, optional (default = None)
+ If None, every edge has weight/distance/cost 1.
+ If a string, use this edge attribute as the edge weight.
+ Any edge attribute not present defaults to 1.
+ 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.
+
+ method : string, optional (default = 'dijkstra')
+ The algorithm to use to compute the path length.
+ Supported options: 'dijkstra', 'bellman-ford'.
+ Other inputs produce a ValueError.
+ If `weight` is None, unweighted graph methods are used, and this
+ suggestion is ignored.
+
+ Returns
+ -------
+ length: number or iterator
+ If the source and target are both specified, return the length of
+ the shortest path from the source to the target.
+
+ If only the source is specified, return a dict keyed by target
+ to the shortest path length from the source to that target.
+
+ If only the target is specified, return a dict keyed by source
+ to the shortest path length from that source to the target.
+
+ If neither the source nor target are specified, return an iterator
+ over (source, dictionary) where dictionary is keyed by target to
+ shortest path length from source to that target.
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` is not in `G`.
+
+ NetworkXNoPath
+ If no path exists between source and target.
+
+ ValueError
+ If `method` is not among the supported options.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> nx.shortest_path_length(G, source=0, target=4)
+ 4
+ >>> p = nx.shortest_path_length(G, source=0) # target not specified
+ >>> p[4]
+ 4
+ >>> p = nx.shortest_path_length(G, target=4) # source not specified
+ >>> p[0]
+ 4
+ >>> p = dict(nx.shortest_path_length(G)) # source,target not specified
+ >>> p[0][4]
+ 4
+
+ Notes
+ -----
+ The length of the path is always 1 less than the number of nodes involved
+ in the path since the length measures the number of edges followed.
+
+ For digraphs this returns the shortest directed path length. To find path
+ lengths in the reverse direction use G.reverse(copy=False) first to flip
+ the edge orientation.
+
+ See Also
+ --------
+ all_pairs_shortest_path_length
+ all_pairs_dijkstra_path_length
+ all_pairs_bellman_ford_path_length
+ single_source_shortest_path_length
+ single_source_dijkstra_path_length
+ single_source_bellman_ford_path_length
+ """
+ if method not in ("dijkstra", "bellman-ford"):
+ # so we don't need to check in each branch later
+ raise ValueError(f"method not supported: {method}")
+ method = "unweighted" if weight is None else method
+ if source is None:
+ if target is None:
+ # Find paths between all pairs.
+ if method == "unweighted":
+ paths = nx.all_pairs_shortest_path_length(G)
+ elif method == "dijkstra":
+ paths = nx.all_pairs_dijkstra_path_length(G, weight=weight)
+ else: # method == 'bellman-ford':
+ paths = nx.all_pairs_bellman_ford_path_length(G, weight=weight)
+ else:
+ # Find paths from all nodes co-accessible to the target.
+ if G.is_directed():
+ G = G.reverse(copy=False)
+ if method == "unweighted":
+ path_length = nx.single_source_shortest_path_length
+ paths = path_length(G, target)
+ elif method == "dijkstra":
+ path_length = nx.single_source_dijkstra_path_length
+ paths = path_length(G, target, weight=weight)
+ else: # method == 'bellman-ford':
+ path_length = nx.single_source_bellman_ford_path_length
+ paths = path_length(G, target, weight=weight)
+ else:
+ if target is None:
+ # Find paths to all nodes accessible from the source.
+ if method == "unweighted":
+ paths = nx.single_source_shortest_path_length(G, source)
+ elif method == "dijkstra":
+ path_length = nx.single_source_dijkstra_path_length
+ paths = path_length(G, source, weight=weight)
+ else: # method == 'bellman-ford':
+ path_length = nx.single_source_bellman_ford_path_length
+ paths = path_length(G, source, weight=weight)
+ else:
+ # Find shortest source-target path.
+ if method == "unweighted":
+ p = nx.bidirectional_shortest_path(G, source, target)
+ paths = len(p) - 1
+ elif method == "dijkstra":
+ paths = nx.dijkstra_path_length(G, source, target, weight)
+ else: # method == 'bellman-ford':
+ paths = nx.bellman_ford_path_length(G, source, target, weight)
+ return paths
+
+
+@nx._dispatchable(edge_attrs="weight")
+def average_shortest_path_length(G, weight=None, method=None):
+ r"""Returns the average shortest path length.
+
+ The average shortest path length is
+
+ .. math::
+
+ a =\sum_{\substack{s,t \in V \\ s\neq t}} \frac{d(s, t)}{n(n-1)}
+
+ where `V` is the set of nodes in `G`,
+ `d(s, t)` is the shortest path from `s` to `t`,
+ and `n` is the number of nodes in `G`.
+
+ .. versionchanged:: 3.0
+ An exception is raised for directed graphs that are not strongly
+ connected.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ weight : None, string or function, optional (default = None)
+ If None, every edge has weight/distance/cost 1.
+ If a string, use this edge attribute as the edge weight.
+ Any edge attribute not present defaults to 1.
+ 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.
+
+ method : string, optional (default = 'unweighted' or 'dijkstra')
+ The algorithm to use to compute the path lengths.
+ Supported options are 'unweighted', 'dijkstra', 'bellman-ford',
+ 'floyd-warshall' and 'floyd-warshall-numpy'.
+ Other method values produce a ValueError.
+ The default method is 'unweighted' if `weight` is None,
+ otherwise the default method is 'dijkstra'.
+
+ Raises
+ ------
+ NetworkXPointlessConcept
+ If `G` is the null graph (that is, the graph on zero nodes).
+
+ NetworkXError
+ If `G` is not connected (or not strongly connected, in the case
+ of a directed graph).
+
+ ValueError
+ If `method` is not among the supported options.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> nx.average_shortest_path_length(G)
+ 2.0
+
+ For disconnected graphs, you can compute the average shortest path
+ length for each component
+
+ >>> G = nx.Graph([(1, 2), (3, 4)])
+ >>> for C in (G.subgraph(c).copy() for c in nx.connected_components(G)):
+ ... print(nx.average_shortest_path_length(C))
+ 1.0
+ 1.0
+
+ """
+ single_source_methods = ["unweighted", "dijkstra", "bellman-ford"]
+ all_pairs_methods = ["floyd-warshall", "floyd-warshall-numpy"]
+ supported_methods = single_source_methods + all_pairs_methods
+
+ if method is None:
+ method = "unweighted" if weight is None else "dijkstra"
+ if method not in supported_methods:
+ raise ValueError(f"method not supported: {method}")
+
+ n = len(G)
+ # For the special case of the null graph, raise an exception, since
+ # there are no paths in the null graph.
+ if n == 0:
+ msg = (
+ "the null graph has no paths, thus there is no average "
+ "shortest path length"
+ )
+ raise nx.NetworkXPointlessConcept(msg)
+ # For the special case of the trivial graph, return zero immediately.
+ if n == 1:
+ return 0
+ # Shortest path length is undefined if the graph is not strongly connected.
+ if G.is_directed() and not nx.is_strongly_connected(G):
+ raise nx.NetworkXError("Graph is not strongly connected.")
+ # Shortest path length is undefined if the graph is not connected.
+ if not G.is_directed() and not nx.is_connected(G):
+ raise nx.NetworkXError("Graph is not connected.")
+
+ # Compute all-pairs shortest paths.
+ def path_length(v):
+ if method == "unweighted":
+ return nx.single_source_shortest_path_length(G, v)
+ elif method == "dijkstra":
+ return nx.single_source_dijkstra_path_length(G, v, weight=weight)
+ elif method == "bellman-ford":
+ return nx.single_source_bellman_ford_path_length(G, v, weight=weight)
+
+ if method in single_source_methods:
+ # Sum the distances for each (ordered) pair of source and target node.
+ s = sum(l for u in G for l in path_length(u).values())
+ else:
+ if method == "floyd-warshall":
+ all_pairs = nx.floyd_warshall(G, weight=weight)
+ s = sum(sum(t.values()) for t in all_pairs.values())
+ elif method == "floyd-warshall-numpy":
+ s = float(nx.floyd_warshall_numpy(G, weight=weight).sum())
+ return s / (n * (n - 1))
+
+
+@nx._dispatchable(edge_attrs="weight")
+def all_shortest_paths(G, source, target, weight=None, method="dijkstra"):
+ """Compute all shortest simple paths in the graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node
+ Starting node for path.
+
+ target : node
+ Ending node for path.
+
+ weight : None, string or function, optional (default = None)
+ If None, every edge has weight/distance/cost 1.
+ If a string, use this edge attribute as the edge weight.
+ Any edge attribute not present defaults to 1.
+ 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.
+
+ method : string, optional (default = 'dijkstra')
+ The algorithm to use to compute the path lengths.
+ Supported options: 'dijkstra', 'bellman-ford'.
+ Other inputs produce a ValueError.
+ If `weight` is None, unweighted graph methods are used, and this
+ suggestion is ignored.
+
+ Returns
+ -------
+ paths : generator of lists
+ A generator of all paths between source and target.
+
+ Raises
+ ------
+ ValueError
+ If `method` is not among the supported options.
+
+ NetworkXNoPath
+ If `target` cannot be reached from `source`.
+
+ Examples
+ --------
+ >>> G = nx.Graph()
+ >>> nx.add_path(G, [0, 1, 2])
+ >>> nx.add_path(G, [0, 10, 2])
+ >>> print([p for p in nx.all_shortest_paths(G, source=0, target=2)])
+ [[0, 1, 2], [0, 10, 2]]
+
+ Notes
+ -----
+ There may be many shortest paths between the source and target. If G
+ contains zero-weight cycles, this function will not produce all shortest
+ paths because doing so would produce infinitely many paths of unbounded
+ length -- instead, we only produce the shortest simple paths.
+
+ See Also
+ --------
+ shortest_path
+ single_source_shortest_path
+ all_pairs_shortest_path
+ """
+ method = "unweighted" if weight is None else method
+ if method == "unweighted":
+ pred = nx.predecessor(G, source)
+ elif method == "dijkstra":
+ pred, dist = nx.dijkstra_predecessor_and_distance(G, source, weight=weight)
+ elif method == "bellman-ford":
+ pred, dist = nx.bellman_ford_predecessor_and_distance(G, source, weight=weight)
+ else:
+ raise ValueError(f"method not supported: {method}")
+
+ return _build_paths_from_predecessors({source}, target, pred)
+
+
+@nx._dispatchable(edge_attrs="weight")
+def single_source_all_shortest_paths(G, source, weight=None, method="dijkstra"):
+ """Compute all shortest simple paths from the given source in the graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node
+ Starting node for path.
+
+ weight : None, string or function, optional (default = None)
+ If None, every edge has weight/distance/cost 1.
+ If a string, use this edge attribute as the edge weight.
+ Any edge attribute not present defaults to 1.
+ 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.
+
+ method : string, optional (default = 'dijkstra')
+ The algorithm to use to compute the path lengths.
+ Supported options: 'dijkstra', 'bellman-ford'.
+ Other inputs produce a ValueError.
+ If `weight` is None, unweighted graph methods are used, and this
+ suggestion is ignored.
+
+ Returns
+ -------
+ paths : generator of dictionary
+ A generator of all paths between source and all nodes in the graph.
+
+ Raises
+ ------
+ ValueError
+ If `method` is not among the supported options.
+
+ Examples
+ --------
+ >>> G = nx.Graph()
+ >>> nx.add_path(G, [0, 1, 2, 3, 0])
+ >>> dict(nx.single_source_all_shortest_paths(G, source=0))
+ {0: [[0]], 1: [[0, 1]], 2: [[0, 1, 2], [0, 3, 2]], 3: [[0, 3]]}
+
+ Notes
+ -----
+ There may be many shortest paths between the source and target. If G
+ contains zero-weight cycles, this function will not produce all shortest
+ paths because doing so would produce infinitely many paths of unbounded
+ length -- instead, we only produce the shortest simple paths.
+
+ See Also
+ --------
+ shortest_path
+ all_shortest_paths
+ single_source_shortest_path
+ all_pairs_shortest_path
+ all_pairs_all_shortest_paths
+ """
+ method = "unweighted" if weight is None else method
+ if method == "unweighted":
+ pred = nx.predecessor(G, source)
+ elif method == "dijkstra":
+ pred, dist = nx.dijkstra_predecessor_and_distance(G, source, weight=weight)
+ elif method == "bellman-ford":
+ pred, dist = nx.bellman_ford_predecessor_and_distance(G, source, weight=weight)
+ else:
+ raise ValueError(f"method not supported: {method}")
+ for n in G:
+ try:
+ yield n, list(_build_paths_from_predecessors({source}, n, pred))
+ except nx.NetworkXNoPath:
+ pass
+
+
+@nx._dispatchable(edge_attrs="weight")
+def all_pairs_all_shortest_paths(G, weight=None, method="dijkstra"):
+ """Compute all shortest paths between all nodes.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ weight : None, string or function, optional (default = None)
+ If None, every edge has weight/distance/cost 1.
+ If a string, use this edge attribute as the edge weight.
+ Any edge attribute not present defaults to 1.
+ 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.
+
+ method : string, optional (default = 'dijkstra')
+ The algorithm to use to compute the path lengths.
+ Supported options: 'dijkstra', 'bellman-ford'.
+ Other inputs produce a ValueError.
+ If `weight` is None, unweighted graph methods are used, and this
+ suggestion is ignored.
+
+ Returns
+ -------
+ paths : generator of dictionary
+ Dictionary of arrays, keyed by source and target, of all shortest paths.
+
+ Raises
+ ------
+ ValueError
+ If `method` is not among the supported options.
+
+ Examples
+ --------
+ >>> G = nx.cycle_graph(4)
+ >>> dict(nx.all_pairs_all_shortest_paths(G))[0][2]
+ [[0, 1, 2], [0, 3, 2]]
+ >>> dict(nx.all_pairs_all_shortest_paths(G))[0][3]
+ [[0, 3]]
+
+ Notes
+ -----
+ There may be multiple shortest paths with equal lengths. Unlike
+ all_pairs_shortest_path, this method returns all shortest paths.
+
+ See Also
+ --------
+ all_pairs_shortest_path
+ single_source_all_shortest_paths
+ """
+ for n in G:
+ yield (
+ n,
+ dict(single_source_all_shortest_paths(G, n, weight=weight, method=method)),
+ )
+
+
+def _build_paths_from_predecessors(sources, target, pred):
+ """Compute all simple paths to target, given the predecessors found in
+ pred, terminating when any source in sources is found.
+
+ Parameters
+ ----------
+ sources : set
+ Starting nodes for path.
+
+ target : node
+ Ending node for path.
+
+ pred : dict
+ A dictionary of predecessor lists, keyed by node
+
+ Returns
+ -------
+ paths : generator of lists
+ A generator of all paths between source and target.
+
+ Raises
+ ------
+ NetworkXNoPath
+ If `target` cannot be reached from `source`.
+
+ Notes
+ -----
+ There may be many paths between the sources and target. If there are
+ cycles among the predecessors, this function will not produce all
+ possible paths because doing so would produce infinitely many paths
+ of unbounded length -- instead, we only produce simple paths.
+
+ See Also
+ --------
+ shortest_path
+ single_source_shortest_path
+ all_pairs_shortest_path
+ all_shortest_paths
+ bellman_ford_path
+ """
+ if target not in pred:
+ raise nx.NetworkXNoPath(f"Target {target} cannot be reached from given sources")
+
+ seen = {target}
+ stack = [[target, 0]]
+ top = 0
+ while top >= 0:
+ node, i = stack[top]
+ if node in sources:
+ yield [p for p, n in reversed(stack[: top + 1])]
+ if len(pred[node]) > i:
+ stack[top][1] = i + 1
+ next = pred[node][i]
+ if next in seen:
+ continue
+ else:
+ seen.add(next)
+ top += 1
+ if top == len(stack):
+ stack.append([next, 0])
+ else:
+ stack[top][:] = [next, 0]
+ else:
+ seen.discard(node)
+ top -= 1
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__init__.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_astar.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_astar.py
new file mode 100644
index 00000000..40a7d4e8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_astar.py
@@ -0,0 +1,248 @@
+import pytest
+
+import networkx as nx
+from networkx.utils import pairwise
+
+
+class TestAStar:
+ @classmethod
+ def setup_class(cls):
+ edges = [
+ ("s", "u", 10),
+ ("s", "x", 5),
+ ("u", "v", 1),
+ ("u", "x", 2),
+ ("v", "y", 1),
+ ("x", "u", 3),
+ ("x", "v", 5),
+ ("x", "y", 2),
+ ("y", "s", 7),
+ ("y", "v", 6),
+ ]
+ cls.XG = nx.DiGraph()
+ cls.XG.add_weighted_edges_from(edges)
+
+ def test_multiple_optimal_paths(self):
+ """Tests that A* algorithm finds any of multiple optimal paths"""
+ heuristic_values = {"a": 1.35, "b": 1.18, "c": 0.67, "d": 0}
+
+ def h(u, v):
+ return heuristic_values[u]
+
+ graph = nx.Graph()
+ points = ["a", "b", "c", "d"]
+ edges = [("a", "b", 0.18), ("a", "c", 0.68), ("b", "c", 0.50), ("c", "d", 0.67)]
+
+ graph.add_nodes_from(points)
+ graph.add_weighted_edges_from(edges)
+
+ path1 = ["a", "c", "d"]
+ path2 = ["a", "b", "c", "d"]
+ assert nx.astar_path(graph, "a", "d", h) in (path1, path2)
+
+ def test_astar_directed(self):
+ assert nx.astar_path(self.XG, "s", "v") == ["s", "x", "u", "v"]
+ assert nx.astar_path_length(self.XG, "s", "v") == 9
+
+ def test_astar_directed_weight_function(self):
+ w1 = lambda u, v, d: d["weight"]
+ assert nx.astar_path(self.XG, "x", "u", weight=w1) == ["x", "u"]
+ assert nx.astar_path_length(self.XG, "x", "u", weight=w1) == 3
+ assert nx.astar_path(self.XG, "s", "v", weight=w1) == ["s", "x", "u", "v"]
+ assert nx.astar_path_length(self.XG, "s", "v", weight=w1) == 9
+
+ w2 = lambda u, v, d: None if (u, v) == ("x", "u") else d["weight"]
+ assert nx.astar_path(self.XG, "x", "u", weight=w2) == ["x", "y", "s", "u"]
+ assert nx.astar_path_length(self.XG, "x", "u", weight=w2) == 19
+ assert nx.astar_path(self.XG, "s", "v", weight=w2) == ["s", "x", "v"]
+ assert nx.astar_path_length(self.XG, "s", "v", weight=w2) == 10
+
+ w3 = lambda u, v, d: d["weight"] + 10
+ assert nx.astar_path(self.XG, "x", "u", weight=w3) == ["x", "u"]
+ assert nx.astar_path_length(self.XG, "x", "u", weight=w3) == 13
+ assert nx.astar_path(self.XG, "s", "v", weight=w3) == ["s", "x", "v"]
+ assert nx.astar_path_length(self.XG, "s", "v", weight=w3) == 30
+
+ def test_astar_multigraph(self):
+ G = nx.MultiDiGraph(self.XG)
+ G.add_weighted_edges_from((u, v, 1000) for (u, v) in list(G.edges()))
+ assert nx.astar_path(G, "s", "v") == ["s", "x", "u", "v"]
+ assert nx.astar_path_length(G, "s", "v") == 9
+
+ def test_astar_undirected(self):
+ GG = self.XG.to_undirected()
+ # make sure we get lower weight
+ # to_undirected might choose either edge with weight 2 or weight 3
+ GG["u"]["x"]["weight"] = 2
+ GG["y"]["v"]["weight"] = 2
+ assert nx.astar_path(GG, "s", "v") == ["s", "x", "u", "v"]
+ assert nx.astar_path_length(GG, "s", "v") == 8
+
+ def test_astar_directed2(self):
+ XG2 = nx.DiGraph()
+ edges = [
+ (1, 4, 1),
+ (4, 5, 1),
+ (5, 6, 1),
+ (6, 3, 1),
+ (1, 3, 50),
+ (1, 2, 100),
+ (2, 3, 100),
+ ]
+ XG2.add_weighted_edges_from(edges)
+ assert nx.astar_path(XG2, 1, 3) == [1, 4, 5, 6, 3]
+
+ def test_astar_undirected2(self):
+ XG3 = nx.Graph()
+ edges = [(0, 1, 2), (1, 2, 12), (2, 3, 1), (3, 4, 5), (4, 5, 1), (5, 0, 10)]
+ XG3.add_weighted_edges_from(edges)
+ assert nx.astar_path(XG3, 0, 3) == [0, 1, 2, 3]
+ assert nx.astar_path_length(XG3, 0, 3) == 15
+
+ def test_astar_undirected3(self):
+ XG4 = nx.Graph()
+ edges = [
+ (0, 1, 2),
+ (1, 2, 2),
+ (2, 3, 1),
+ (3, 4, 1),
+ (4, 5, 1),
+ (5, 6, 1),
+ (6, 7, 1),
+ (7, 0, 1),
+ ]
+ XG4.add_weighted_edges_from(edges)
+ assert nx.astar_path(XG4, 0, 2) == [0, 1, 2]
+ assert nx.astar_path_length(XG4, 0, 2) == 4
+
+ """ Tests that A* finds correct path when multiple paths exist
+ and the best one is not expanded first (GH issue #3464)
+ """
+
+ def test_astar_directed3(self):
+ heuristic_values = {"n5": 36, "n2": 4, "n1": 0, "n0": 0}
+
+ def h(u, v):
+ return heuristic_values[u]
+
+ edges = [("n5", "n1", 11), ("n5", "n2", 9), ("n2", "n1", 1), ("n1", "n0", 32)]
+ graph = nx.DiGraph()
+ graph.add_weighted_edges_from(edges)
+ answer = ["n5", "n2", "n1", "n0"]
+ assert nx.astar_path(graph, "n5", "n0", h) == answer
+
+ """ Tests that parent is not wrongly overridden when a node
+ is re-explored multiple times.
+ """
+
+ def test_astar_directed4(self):
+ edges = [
+ ("a", "b", 1),
+ ("a", "c", 1),
+ ("b", "d", 2),
+ ("c", "d", 1),
+ ("d", "e", 1),
+ ]
+ graph = nx.DiGraph()
+ graph.add_weighted_edges_from(edges)
+ assert nx.astar_path(graph, "a", "e") == ["a", "c", "d", "e"]
+
+ # >>> MXG4=NX.MultiGraph(XG4)
+ # >>> MXG4.add_edge(0,1,3)
+ # >>> NX.dijkstra_path(MXG4,0,2)
+ # [0, 1, 2]
+
+ def test_astar_w1(self):
+ G = nx.DiGraph()
+ G.add_edges_from(
+ [
+ ("s", "u"),
+ ("s", "x"),
+ ("u", "v"),
+ ("u", "x"),
+ ("v", "y"),
+ ("x", "u"),
+ ("x", "w"),
+ ("w", "v"),
+ ("x", "y"),
+ ("y", "s"),
+ ("y", "v"),
+ ]
+ )
+ assert nx.astar_path(G, "s", "v") == ["s", "u", "v"]
+ assert nx.astar_path_length(G, "s", "v") == 2
+
+ def test_astar_nopath(self):
+ with pytest.raises(nx.NodeNotFound):
+ nx.astar_path(self.XG, "s", "moon")
+
+ def test_astar_cutoff(self):
+ with pytest.raises(nx.NetworkXNoPath):
+ # optimal path_length in XG is 9
+ nx.astar_path(self.XG, "s", "v", cutoff=8.0)
+ with pytest.raises(nx.NetworkXNoPath):
+ nx.astar_path_length(self.XG, "s", "v", cutoff=8.0)
+
+ def test_astar_admissible_heuristic_with_cutoff(self):
+ heuristic_values = {"s": 36, "y": 4, "x": 0, "u": 0, "v": 0}
+
+ def h(u, v):
+ return heuristic_values[u]
+
+ assert nx.astar_path_length(self.XG, "s", "v") == 9
+ assert nx.astar_path_length(self.XG, "s", "v", heuristic=h) == 9
+ assert nx.astar_path_length(self.XG, "s", "v", heuristic=h, cutoff=12) == 9
+ assert nx.astar_path_length(self.XG, "s", "v", heuristic=h, cutoff=9) == 9
+ with pytest.raises(nx.NetworkXNoPath):
+ nx.astar_path_length(self.XG, "s", "v", heuristic=h, cutoff=8)
+
+ def test_astar_inadmissible_heuristic_with_cutoff(self):
+ heuristic_values = {"s": 36, "y": 14, "x": 10, "u": 10, "v": 0}
+
+ def h(u, v):
+ return heuristic_values[u]
+
+ # optimal path_length in XG is 9. This heuristic gives over-estimate.
+ assert nx.astar_path_length(self.XG, "s", "v", heuristic=h) == 10
+ assert nx.astar_path_length(self.XG, "s", "v", heuristic=h, cutoff=15) == 10
+ with pytest.raises(nx.NetworkXNoPath):
+ nx.astar_path_length(self.XG, "s", "v", heuristic=h, cutoff=9)
+ with pytest.raises(nx.NetworkXNoPath):
+ nx.astar_path_length(self.XG, "s", "v", heuristic=h, cutoff=12)
+
+ def test_astar_cutoff2(self):
+ assert nx.astar_path(self.XG, "s", "v", cutoff=10.0) == ["s", "x", "u", "v"]
+ assert nx.astar_path_length(self.XG, "s", "v") == 9
+
+ def test_cycle(self):
+ C = nx.cycle_graph(7)
+ assert nx.astar_path(C, 0, 3) == [0, 1, 2, 3]
+ assert nx.dijkstra_path(C, 0, 4) == [0, 6, 5, 4]
+
+ def test_unorderable_nodes(self):
+ """Tests that A* accommodates nodes that are not orderable.
+
+ For more information, see issue #554.
+
+ """
+ # Create the cycle graph on four nodes, with nodes represented
+ # as (unorderable) Python objects.
+ nodes = [object() for n in range(4)]
+ G = nx.Graph()
+ G.add_edges_from(pairwise(nodes, cyclic=True))
+ path = nx.astar_path(G, nodes[0], nodes[2])
+ assert len(path) == 3
+
+ def test_astar_NetworkXNoPath(self):
+ """Tests that exception is raised when there exists no
+ path between source and target"""
+ G = nx.gnp_random_graph(10, 0.2, seed=10)
+ with pytest.raises(nx.NetworkXNoPath):
+ nx.astar_path(G, 4, 9)
+
+ def test_astar_NodeNotFound(self):
+ """Tests that exception is raised when either
+ source or target is not in graph"""
+ G = nx.gnp_random_graph(10, 0.2, seed=10)
+ with pytest.raises(nx.NodeNotFound):
+ nx.astar_path_length(G, 11, 9)
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_dense.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_dense.py
new file mode 100644
index 00000000..6923bfef
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_dense.py
@@ -0,0 +1,212 @@
+import pytest
+
+import networkx as nx
+
+
+class TestFloyd:
+ @classmethod
+ def setup_class(cls):
+ pass
+
+ def test_floyd_warshall_predecessor_and_distance(self):
+ XG = nx.DiGraph()
+ XG.add_weighted_edges_from(
+ [
+ ("s", "u", 10),
+ ("s", "x", 5),
+ ("u", "v", 1),
+ ("u", "x", 2),
+ ("v", "y", 1),
+ ("x", "u", 3),
+ ("x", "v", 5),
+ ("x", "y", 2),
+ ("y", "s", 7),
+ ("y", "v", 6),
+ ]
+ )
+ path, dist = nx.floyd_warshall_predecessor_and_distance(XG)
+ assert dist["s"]["v"] == 9
+ assert path["s"]["v"] == "u"
+ assert dist == {
+ "y": {"y": 0, "x": 12, "s": 7, "u": 15, "v": 6},
+ "x": {"y": 2, "x": 0, "s": 9, "u": 3, "v": 4},
+ "s": {"y": 7, "x": 5, "s": 0, "u": 8, "v": 9},
+ "u": {"y": 2, "x": 2, "s": 9, "u": 0, "v": 1},
+ "v": {"y": 1, "x": 13, "s": 8, "u": 16, "v": 0},
+ }
+
+ GG = XG.to_undirected()
+ # make sure we get lower weight
+ # to_undirected might choose either edge with weight 2 or weight 3
+ GG["u"]["x"]["weight"] = 2
+ path, dist = nx.floyd_warshall_predecessor_and_distance(GG)
+ assert dist["s"]["v"] == 8
+ # skip this test, could be alternate path s-u-v
+ # assert_equal(path['s']['v'],'y')
+
+ G = nx.DiGraph() # no weights
+ G.add_edges_from(
+ [
+ ("s", "u"),
+ ("s", "x"),
+ ("u", "v"),
+ ("u", "x"),
+ ("v", "y"),
+ ("x", "u"),
+ ("x", "v"),
+ ("x", "y"),
+ ("y", "s"),
+ ("y", "v"),
+ ]
+ )
+ path, dist = nx.floyd_warshall_predecessor_and_distance(G)
+ assert dist["s"]["v"] == 2
+ # skip this test, could be alternate path s-u-v
+ # assert_equal(path['s']['v'],'x')
+
+ # alternate interface
+ dist = nx.floyd_warshall(G)
+ assert dist["s"]["v"] == 2
+
+ # floyd_warshall_predecessor_and_distance returns
+ # dicts-of-defautdicts
+ # make sure we don't get empty dictionary
+ XG = nx.DiGraph()
+ XG.add_weighted_edges_from(
+ [("v", "x", 5.0), ("y", "x", 5.0), ("v", "y", 6.0), ("x", "u", 2.0)]
+ )
+ path, dist = nx.floyd_warshall_predecessor_and_distance(XG)
+ inf = float("inf")
+ assert dist == {
+ "v": {"v": 0, "x": 5.0, "y": 6.0, "u": 7.0},
+ "x": {"x": 0, "u": 2.0, "v": inf, "y": inf},
+ "y": {"y": 0, "x": 5.0, "v": inf, "u": 7.0},
+ "u": {"u": 0, "v": inf, "x": inf, "y": inf},
+ }
+ assert path == {
+ "v": {"x": "v", "y": "v", "u": "x"},
+ "x": {"u": "x"},
+ "y": {"x": "y", "u": "x"},
+ }
+
+ def test_reconstruct_path(self):
+ with pytest.raises(KeyError):
+ XG = nx.DiGraph()
+ XG.add_weighted_edges_from(
+ [
+ ("s", "u", 10),
+ ("s", "x", 5),
+ ("u", "v", 1),
+ ("u", "x", 2),
+ ("v", "y", 1),
+ ("x", "u", 3),
+ ("x", "v", 5),
+ ("x", "y", 2),
+ ("y", "s", 7),
+ ("y", "v", 6),
+ ]
+ )
+ predecessors, _ = nx.floyd_warshall_predecessor_and_distance(XG)
+
+ path = nx.reconstruct_path("s", "v", predecessors)
+ assert path == ["s", "x", "u", "v"]
+
+ path = nx.reconstruct_path("s", "s", predecessors)
+ assert path == []
+
+ # this part raises the keyError
+ nx.reconstruct_path("1", "2", predecessors)
+
+ def test_cycle(self):
+ path, dist = nx.floyd_warshall_predecessor_and_distance(nx.cycle_graph(7))
+ assert dist[0][3] == 3
+ assert path[0][3] == 2
+ assert dist[0][4] == 3
+
+ def test_weighted(self):
+ XG3 = nx.Graph()
+ XG3.add_weighted_edges_from(
+ [[0, 1, 2], [1, 2, 12], [2, 3, 1], [3, 4, 5], [4, 5, 1], [5, 0, 10]]
+ )
+ path, dist = nx.floyd_warshall_predecessor_and_distance(XG3)
+ assert dist[0][3] == 15
+ assert path[0][3] == 2
+
+ def test_weighted2(self):
+ XG4 = nx.Graph()
+ XG4.add_weighted_edges_from(
+ [
+ [0, 1, 2],
+ [1, 2, 2],
+ [2, 3, 1],
+ [3, 4, 1],
+ [4, 5, 1],
+ [5, 6, 1],
+ [6, 7, 1],
+ [7, 0, 1],
+ ]
+ )
+ path, dist = nx.floyd_warshall_predecessor_and_distance(XG4)
+ assert dist[0][2] == 4
+ assert path[0][2] == 1
+
+ def test_weight_parameter(self):
+ XG4 = nx.Graph()
+ XG4.add_edges_from(
+ [
+ (0, 1, {"heavy": 2}),
+ (1, 2, {"heavy": 2}),
+ (2, 3, {"heavy": 1}),
+ (3, 4, {"heavy": 1}),
+ (4, 5, {"heavy": 1}),
+ (5, 6, {"heavy": 1}),
+ (6, 7, {"heavy": 1}),
+ (7, 0, {"heavy": 1}),
+ ]
+ )
+ path, dist = nx.floyd_warshall_predecessor_and_distance(XG4, weight="heavy")
+ assert dist[0][2] == 4
+ assert path[0][2] == 1
+
+ def test_zero_distance(self):
+ XG = nx.DiGraph()
+ XG.add_weighted_edges_from(
+ [
+ ("s", "u", 10),
+ ("s", "x", 5),
+ ("u", "v", 1),
+ ("u", "x", 2),
+ ("v", "y", 1),
+ ("x", "u", 3),
+ ("x", "v", 5),
+ ("x", "y", 2),
+ ("y", "s", 7),
+ ("y", "v", 6),
+ ]
+ )
+ path, dist = nx.floyd_warshall_predecessor_and_distance(XG)
+
+ for u in XG:
+ assert dist[u][u] == 0
+
+ GG = XG.to_undirected()
+ # make sure we get lower weight
+ # to_undirected might choose either edge with weight 2 or weight 3
+ GG["u"]["x"]["weight"] = 2
+ path, dist = nx.floyd_warshall_predecessor_and_distance(GG)
+
+ for u in GG:
+ dist[u][u] = 0
+
+ def test_zero_weight(self):
+ G = nx.DiGraph()
+ edges = [(1, 2, -2), (2, 3, -4), (1, 5, 1), (5, 4, 0), (4, 3, -5), (2, 5, -7)]
+ G.add_weighted_edges_from(edges)
+ dist = nx.floyd_warshall(G)
+ assert dist[1][3] == -14
+
+ G = nx.MultiDiGraph()
+ edges.append((2, 5, -7))
+ G.add_weighted_edges_from(edges)
+ dist = nx.floyd_warshall(G)
+ assert dist[1][3] == -14
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py
new file mode 100644
index 00000000..1316e23e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py
@@ -0,0 +1,89 @@
+import pytest
+
+np = pytest.importorskip("numpy")
+
+
+import networkx as nx
+
+
+def test_cycle_numpy():
+ dist = nx.floyd_warshall_numpy(nx.cycle_graph(7))
+ assert dist[0, 3] == 3
+ assert dist[0, 4] == 3
+
+
+def test_weighted_numpy_three_edges():
+ XG3 = nx.Graph()
+ XG3.add_weighted_edges_from(
+ [[0, 1, 2], [1, 2, 12], [2, 3, 1], [3, 4, 5], [4, 5, 1], [5, 0, 10]]
+ )
+ dist = nx.floyd_warshall_numpy(XG3)
+ assert dist[0, 3] == 15
+
+
+def test_weighted_numpy_two_edges():
+ XG4 = nx.Graph()
+ XG4.add_weighted_edges_from(
+ [
+ [0, 1, 2],
+ [1, 2, 2],
+ [2, 3, 1],
+ [3, 4, 1],
+ [4, 5, 1],
+ [5, 6, 1],
+ [6, 7, 1],
+ [7, 0, 1],
+ ]
+ )
+ dist = nx.floyd_warshall_numpy(XG4)
+ assert dist[0, 2] == 4
+
+
+def test_weight_parameter_numpy():
+ XG4 = nx.Graph()
+ XG4.add_edges_from(
+ [
+ (0, 1, {"heavy": 2}),
+ (1, 2, {"heavy": 2}),
+ (2, 3, {"heavy": 1}),
+ (3, 4, {"heavy": 1}),
+ (4, 5, {"heavy": 1}),
+ (5, 6, {"heavy": 1}),
+ (6, 7, {"heavy": 1}),
+ (7, 0, {"heavy": 1}),
+ ]
+ )
+ dist = nx.floyd_warshall_numpy(XG4, weight="heavy")
+ assert dist[0, 2] == 4
+
+
+def test_directed_cycle_numpy():
+ G = nx.DiGraph()
+ nx.add_cycle(G, [0, 1, 2, 3])
+ pred, dist = nx.floyd_warshall_predecessor_and_distance(G)
+ D = nx.utils.dict_to_numpy_array(dist)
+ np.testing.assert_equal(nx.floyd_warshall_numpy(G), D)
+
+
+def test_zero_weight():
+ G = nx.DiGraph()
+ edges = [(1, 2, -2), (2, 3, -4), (1, 5, 1), (5, 4, 0), (4, 3, -5), (2, 5, -7)]
+ G.add_weighted_edges_from(edges)
+ dist = nx.floyd_warshall_numpy(G)
+ assert int(np.min(dist)) == -14
+
+ G = nx.MultiDiGraph()
+ edges.append((2, 5, -7))
+ G.add_weighted_edges_from(edges)
+ dist = nx.floyd_warshall_numpy(G)
+ assert int(np.min(dist)) == -14
+
+
+def test_nodelist():
+ G = nx.path_graph(7)
+ dist = nx.floyd_warshall_numpy(G, nodelist=[3, 5, 4, 6, 2, 1, 0])
+ assert dist[0, 3] == 3
+ assert dist[0, 1] == 2
+ assert dist[6, 2] == 4
+ pytest.raises(nx.NetworkXError, nx.floyd_warshall_numpy, G, [1, 3])
+ pytest.raises(nx.NetworkXError, nx.floyd_warshall_numpy, G, list(range(9)))
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_generic.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_generic.py
new file mode 100644
index 00000000..e30de517
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_generic.py
@@ -0,0 +1,450 @@
+import pytest
+
+import networkx as nx
+
+
+def validate_grid_path(r, c, s, t, p):
+ assert isinstance(p, list)
+ assert p[0] == s
+ assert p[-1] == t
+ s = ((s - 1) // c, (s - 1) % c)
+ t = ((t - 1) // c, (t - 1) % c)
+ assert len(p) == abs(t[0] - s[0]) + abs(t[1] - s[1]) + 1
+ p = [((u - 1) // c, (u - 1) % c) for u in p]
+ for u in p:
+ assert 0 <= u[0] < r
+ assert 0 <= u[1] < c
+ for u, v in zip(p[:-1], p[1:]):
+ assert (abs(v[0] - u[0]), abs(v[1] - u[1])) in [(0, 1), (1, 0)]
+
+
+class TestGenericPath:
+ @classmethod
+ def setup_class(cls):
+ from networkx import convert_node_labels_to_integers as cnlti
+
+ cls.grid = cnlti(nx.grid_2d_graph(4, 4), first_label=1, ordering="sorted")
+ cls.cycle = nx.cycle_graph(7)
+ cls.directed_cycle = nx.cycle_graph(7, create_using=nx.DiGraph())
+ cls.neg_weights = nx.DiGraph()
+ cls.neg_weights.add_edge(0, 1, weight=1)
+ cls.neg_weights.add_edge(0, 2, weight=3)
+ cls.neg_weights.add_edge(1, 3, weight=1)
+ cls.neg_weights.add_edge(2, 3, weight=-2)
+
+ def test_shortest_path(self):
+ assert nx.shortest_path(self.cycle, 0, 3) == [0, 1, 2, 3]
+ assert nx.shortest_path(self.cycle, 0, 4) == [0, 6, 5, 4]
+ validate_grid_path(4, 4, 1, 12, nx.shortest_path(self.grid, 1, 12))
+ assert nx.shortest_path(self.directed_cycle, 0, 3) == [0, 1, 2, 3]
+ # now with weights
+ assert nx.shortest_path(self.cycle, 0, 3, weight="weight") == [0, 1, 2, 3]
+ assert nx.shortest_path(self.cycle, 0, 4, weight="weight") == [0, 6, 5, 4]
+ validate_grid_path(
+ 4, 4, 1, 12, nx.shortest_path(self.grid, 1, 12, weight="weight")
+ )
+ assert nx.shortest_path(self.directed_cycle, 0, 3, weight="weight") == [
+ 0,
+ 1,
+ 2,
+ 3,
+ ]
+ # weights and method specified
+ assert nx.shortest_path(
+ self.directed_cycle, 0, 3, weight="weight", method="dijkstra"
+ ) == [0, 1, 2, 3]
+ assert nx.shortest_path(
+ self.directed_cycle, 0, 3, weight="weight", method="bellman-ford"
+ ) == [0, 1, 2, 3]
+ # when Dijkstra's will probably (depending on precise implementation)
+ # incorrectly return [0, 1, 3] instead
+ assert nx.shortest_path(
+ self.neg_weights, 0, 3, weight="weight", method="bellman-ford"
+ ) == [0, 2, 3]
+ # confirm bad method rejection
+ pytest.raises(ValueError, nx.shortest_path, self.cycle, method="SPAM")
+ # confirm absent source rejection
+ pytest.raises(nx.NodeNotFound, nx.shortest_path, self.cycle, 8)
+
+ def test_shortest_path_target(self):
+ answer = {0: [0, 1], 1: [1], 2: [2, 1]}
+ sp = nx.shortest_path(nx.path_graph(3), target=1)
+ assert sp == answer
+ # with weights
+ sp = nx.shortest_path(nx.path_graph(3), target=1, weight="weight")
+ assert sp == answer
+ # weights and method specified
+ sp = nx.shortest_path(
+ nx.path_graph(3), target=1, weight="weight", method="dijkstra"
+ )
+ assert sp == answer
+ sp = nx.shortest_path(
+ nx.path_graph(3), target=1, weight="weight", method="bellman-ford"
+ )
+ assert sp == answer
+
+ def test_shortest_path_length(self):
+ assert nx.shortest_path_length(self.cycle, 0, 3) == 3
+ assert nx.shortest_path_length(self.grid, 1, 12) == 5
+ assert nx.shortest_path_length(self.directed_cycle, 0, 4) == 4
+ # now with weights
+ assert nx.shortest_path_length(self.cycle, 0, 3, weight="weight") == 3
+ assert nx.shortest_path_length(self.grid, 1, 12, weight="weight") == 5
+ assert nx.shortest_path_length(self.directed_cycle, 0, 4, weight="weight") == 4
+ # weights and method specified
+ assert (
+ nx.shortest_path_length(
+ self.cycle, 0, 3, weight="weight", method="dijkstra"
+ )
+ == 3
+ )
+ assert (
+ nx.shortest_path_length(
+ self.cycle, 0, 3, weight="weight", method="bellman-ford"
+ )
+ == 3
+ )
+ # confirm bad method rejection
+ pytest.raises(ValueError, nx.shortest_path_length, self.cycle, method="SPAM")
+ # confirm absent source rejection
+ pytest.raises(nx.NodeNotFound, nx.shortest_path_length, self.cycle, 8)
+
+ def test_shortest_path_length_target(self):
+ answer = {0: 1, 1: 0, 2: 1}
+ sp = dict(nx.shortest_path_length(nx.path_graph(3), target=1))
+ assert sp == answer
+ # with weights
+ sp = nx.shortest_path_length(nx.path_graph(3), target=1, weight="weight")
+ assert sp == answer
+ # weights and method specified
+ sp = nx.shortest_path_length(
+ nx.path_graph(3), target=1, weight="weight", method="dijkstra"
+ )
+ assert sp == answer
+ sp = nx.shortest_path_length(
+ nx.path_graph(3), target=1, weight="weight", method="bellman-ford"
+ )
+ assert sp == answer
+
+ def test_single_source_shortest_path(self):
+ p = nx.shortest_path(self.cycle, 0)
+ assert p[3] == [0, 1, 2, 3]
+ assert p == nx.single_source_shortest_path(self.cycle, 0)
+ p = nx.shortest_path(self.grid, 1)
+ validate_grid_path(4, 4, 1, 12, p[12])
+ # now with weights
+ p = nx.shortest_path(self.cycle, 0, weight="weight")
+ assert p[3] == [0, 1, 2, 3]
+ assert p == nx.single_source_dijkstra_path(self.cycle, 0)
+ p = nx.shortest_path(self.grid, 1, weight="weight")
+ validate_grid_path(4, 4, 1, 12, p[12])
+ # weights and method specified
+ p = nx.shortest_path(self.cycle, 0, method="dijkstra", weight="weight")
+ assert p[3] == [0, 1, 2, 3]
+ assert p == nx.single_source_shortest_path(self.cycle, 0)
+ p = nx.shortest_path(self.cycle, 0, method="bellman-ford", weight="weight")
+ assert p[3] == [0, 1, 2, 3]
+ assert p == nx.single_source_shortest_path(self.cycle, 0)
+
+ def test_single_source_shortest_path_length(self):
+ ans = dict(nx.shortest_path_length(self.cycle, 0))
+ assert ans == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1}
+ assert ans == dict(nx.single_source_shortest_path_length(self.cycle, 0))
+ ans = dict(nx.shortest_path_length(self.grid, 1))
+ assert ans[16] == 6
+ # now with weights
+ ans = dict(nx.shortest_path_length(self.cycle, 0, weight="weight"))
+ assert ans == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1}
+ assert ans == dict(nx.single_source_dijkstra_path_length(self.cycle, 0))
+ ans = dict(nx.shortest_path_length(self.grid, 1, weight="weight"))
+ assert ans[16] == 6
+ # weights and method specified
+ ans = dict(
+ nx.shortest_path_length(self.cycle, 0, weight="weight", method="dijkstra")
+ )
+ assert ans == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1}
+ assert ans == dict(nx.single_source_dijkstra_path_length(self.cycle, 0))
+ ans = dict(
+ nx.shortest_path_length(
+ self.cycle, 0, weight="weight", method="bellman-ford"
+ )
+ )
+ assert ans == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1}
+ assert ans == dict(nx.single_source_bellman_ford_path_length(self.cycle, 0))
+
+ def test_single_source_all_shortest_paths(self):
+ cycle_ans = {0: [[0]], 1: [[0, 1]], 2: [[0, 1, 2], [0, 3, 2]], 3: [[0, 3]]}
+ ans = dict(nx.single_source_all_shortest_paths(nx.cycle_graph(4), 0))
+ assert sorted(ans[2]) == cycle_ans[2]
+ ans = dict(nx.single_source_all_shortest_paths(self.grid, 1))
+ grid_ans = [
+ [1, 2, 3, 7, 11],
+ [1, 2, 6, 7, 11],
+ [1, 2, 6, 10, 11],
+ [1, 5, 6, 7, 11],
+ [1, 5, 6, 10, 11],
+ [1, 5, 9, 10, 11],
+ ]
+ assert sorted(ans[11]) == grid_ans
+ ans = dict(
+ nx.single_source_all_shortest_paths(nx.cycle_graph(4), 0, weight="weight")
+ )
+ assert sorted(ans[2]) == cycle_ans[2]
+ ans = dict(
+ nx.single_source_all_shortest_paths(
+ nx.cycle_graph(4), 0, method="bellman-ford", weight="weight"
+ )
+ )
+ assert sorted(ans[2]) == cycle_ans[2]
+ ans = dict(nx.single_source_all_shortest_paths(self.grid, 1, weight="weight"))
+ assert sorted(ans[11]) == grid_ans
+ ans = dict(
+ nx.single_source_all_shortest_paths(
+ self.grid, 1, method="bellman-ford", weight="weight"
+ )
+ )
+ assert sorted(ans[11]) == grid_ans
+ G = nx.cycle_graph(4)
+ G.add_node(4)
+ ans = dict(nx.single_source_all_shortest_paths(G, 0))
+ assert sorted(ans[2]) == [[0, 1, 2], [0, 3, 2]]
+ ans = dict(nx.single_source_all_shortest_paths(G, 4))
+ assert sorted(ans[4]) == [[4]]
+
+ def test_all_pairs_shortest_path(self):
+ # shortest_path w/o source and target will return a generator instead of
+ # a dict beginning in version 3.5. Only the first call needs changed here.
+ p = nx.shortest_path(self.cycle)
+ assert p[0][3] == [0, 1, 2, 3]
+ assert p == dict(nx.all_pairs_shortest_path(self.cycle))
+ p = dict(nx.shortest_path(self.grid))
+ validate_grid_path(4, 4, 1, 12, p[1][12])
+ # now with weights
+ p = dict(nx.shortest_path(self.cycle, weight="weight"))
+ assert p[0][3] == [0, 1, 2, 3]
+ assert p == dict(nx.all_pairs_dijkstra_path(self.cycle))
+ p = dict(nx.shortest_path(self.grid, weight="weight"))
+ validate_grid_path(4, 4, 1, 12, p[1][12])
+ # weights and method specified
+ p = dict(nx.shortest_path(self.cycle, weight="weight", method="dijkstra"))
+ assert p[0][3] == [0, 1, 2, 3]
+ assert p == dict(nx.all_pairs_dijkstra_path(self.cycle))
+ p = dict(nx.shortest_path(self.cycle, weight="weight", method="bellman-ford"))
+ assert p[0][3] == [0, 1, 2, 3]
+ assert p == dict(nx.all_pairs_bellman_ford_path(self.cycle))
+
+ def test_all_pairs_shortest_path_length(self):
+ ans = dict(nx.shortest_path_length(self.cycle))
+ assert ans[0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1}
+ assert ans == dict(nx.all_pairs_shortest_path_length(self.cycle))
+ ans = dict(nx.shortest_path_length(self.grid))
+ assert ans[1][16] == 6
+ # now with weights
+ ans = dict(nx.shortest_path_length(self.cycle, weight="weight"))
+ assert ans[0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1}
+ assert ans == dict(nx.all_pairs_dijkstra_path_length(self.cycle))
+ ans = dict(nx.shortest_path_length(self.grid, weight="weight"))
+ assert ans[1][16] == 6
+ # weights and method specified
+ ans = dict(
+ nx.shortest_path_length(self.cycle, weight="weight", method="dijkstra")
+ )
+ assert ans[0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1}
+ assert ans == dict(nx.all_pairs_dijkstra_path_length(self.cycle))
+ ans = dict(
+ nx.shortest_path_length(self.cycle, weight="weight", method="bellman-ford")
+ )
+ assert ans[0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1}
+ assert ans == dict(nx.all_pairs_bellman_ford_path_length(self.cycle))
+
+ def test_all_pairs_all_shortest_paths(self):
+ ans = dict(nx.all_pairs_all_shortest_paths(nx.cycle_graph(4)))
+ assert sorted(ans[1][3]) == [[1, 0, 3], [1, 2, 3]]
+ ans = dict(nx.all_pairs_all_shortest_paths(nx.cycle_graph(4)), weight="weight")
+ assert sorted(ans[1][3]) == [[1, 0, 3], [1, 2, 3]]
+ ans = dict(
+ nx.all_pairs_all_shortest_paths(nx.cycle_graph(4)),
+ method="bellman-ford",
+ weight="weight",
+ )
+ assert sorted(ans[1][3]) == [[1, 0, 3], [1, 2, 3]]
+ G = nx.cycle_graph(4)
+ G.add_node(4)
+ ans = dict(nx.all_pairs_all_shortest_paths(G))
+ assert sorted(ans[4][4]) == [[4]]
+
+ def test_has_path(self):
+ G = nx.Graph()
+ nx.add_path(G, range(3))
+ nx.add_path(G, range(3, 5))
+ assert nx.has_path(G, 0, 2)
+ assert not nx.has_path(G, 0, 4)
+
+ def test_has_path_singleton(self):
+ G = nx.empty_graph(1)
+ assert nx.has_path(G, 0, 0)
+
+ def test_all_shortest_paths(self):
+ G = nx.Graph()
+ nx.add_path(G, [0, 1, 2, 3])
+ nx.add_path(G, [0, 10, 20, 3])
+ assert [[0, 1, 2, 3], [0, 10, 20, 3]] == sorted(nx.all_shortest_paths(G, 0, 3))
+ # with weights
+ G = nx.Graph()
+ nx.add_path(G, [0, 1, 2, 3])
+ nx.add_path(G, [0, 10, 20, 3])
+ assert [[0, 1, 2, 3], [0, 10, 20, 3]] == sorted(
+ nx.all_shortest_paths(G, 0, 3, weight="weight")
+ )
+ # weights and method specified
+ G = nx.Graph()
+ nx.add_path(G, [0, 1, 2, 3])
+ nx.add_path(G, [0, 10, 20, 3])
+ assert [[0, 1, 2, 3], [0, 10, 20, 3]] == sorted(
+ nx.all_shortest_paths(G, 0, 3, weight="weight", method="dijkstra")
+ )
+ G = nx.Graph()
+ nx.add_path(G, [0, 1, 2, 3])
+ nx.add_path(G, [0, 10, 20, 3])
+ assert [[0, 1, 2, 3], [0, 10, 20, 3]] == sorted(
+ nx.all_shortest_paths(G, 0, 3, weight="weight", method="bellman-ford")
+ )
+
+ def test_all_shortest_paths_raise(self):
+ with pytest.raises(nx.NetworkXNoPath):
+ G = nx.path_graph(4)
+ G.add_node(4)
+ list(nx.all_shortest_paths(G, 0, 4))
+
+ def test_bad_method(self):
+ with pytest.raises(ValueError):
+ G = nx.path_graph(2)
+ list(nx.all_shortest_paths(G, 0, 1, weight="weight", method="SPAM"))
+
+ def test_single_source_all_shortest_paths_bad_method(self):
+ with pytest.raises(ValueError):
+ G = nx.path_graph(2)
+ dict(
+ nx.single_source_all_shortest_paths(
+ G, 0, weight="weight", method="SPAM"
+ )
+ )
+
+ def test_all_shortest_paths_zero_weight_edge(self):
+ g = nx.Graph()
+ nx.add_path(g, [0, 1, 3])
+ nx.add_path(g, [0, 1, 2, 3])
+ g.edges[1, 2]["weight"] = 0
+ paths30d = list(
+ nx.all_shortest_paths(g, 3, 0, weight="weight", method="dijkstra")
+ )
+ paths03d = list(
+ nx.all_shortest_paths(g, 0, 3, weight="weight", method="dijkstra")
+ )
+ paths30b = list(
+ nx.all_shortest_paths(g, 3, 0, weight="weight", method="bellman-ford")
+ )
+ paths03b = list(
+ nx.all_shortest_paths(g, 0, 3, weight="weight", method="bellman-ford")
+ )
+ assert sorted(paths03d) == sorted(p[::-1] for p in paths30d)
+ assert sorted(paths03d) == sorted(p[::-1] for p in paths30b)
+ assert sorted(paths03b) == sorted(p[::-1] for p in paths30b)
+
+
+class TestAverageShortestPathLength:
+ def test_cycle_graph(self):
+ ans = nx.average_shortest_path_length(nx.cycle_graph(7))
+ assert ans == pytest.approx(2, abs=1e-7)
+
+ def test_path_graph(self):
+ ans = nx.average_shortest_path_length(nx.path_graph(5))
+ assert ans == pytest.approx(2, abs=1e-7)
+
+ def test_weighted(self):
+ G = nx.Graph()
+ nx.add_cycle(G, range(7), weight=2)
+ ans = nx.average_shortest_path_length(G, weight="weight")
+ assert ans == pytest.approx(4, abs=1e-7)
+ G = nx.Graph()
+ nx.add_path(G, range(5), weight=2)
+ ans = nx.average_shortest_path_length(G, weight="weight")
+ assert ans == pytest.approx(4, abs=1e-7)
+
+ def test_specified_methods(self):
+ G = nx.Graph()
+ nx.add_cycle(G, range(7), weight=2)
+ ans = nx.average_shortest_path_length(G, weight="weight", method="dijkstra")
+ assert ans == pytest.approx(4, abs=1e-7)
+ ans = nx.average_shortest_path_length(G, weight="weight", method="bellman-ford")
+ assert ans == pytest.approx(4, abs=1e-7)
+ ans = nx.average_shortest_path_length(
+ G, weight="weight", method="floyd-warshall"
+ )
+ assert ans == pytest.approx(4, abs=1e-7)
+
+ G = nx.Graph()
+ nx.add_path(G, range(5), weight=2)
+ ans = nx.average_shortest_path_length(G, weight="weight", method="dijkstra")
+ assert ans == pytest.approx(4, abs=1e-7)
+ ans = nx.average_shortest_path_length(G, weight="weight", method="bellman-ford")
+ assert ans == pytest.approx(4, abs=1e-7)
+ ans = nx.average_shortest_path_length(
+ G, weight="weight", method="floyd-warshall"
+ )
+ assert ans == pytest.approx(4, abs=1e-7)
+
+ def test_directed_not_strongly_connected(self):
+ G = nx.DiGraph([(0, 1)])
+ with pytest.raises(nx.NetworkXError, match="Graph is not strongly connected"):
+ nx.average_shortest_path_length(G)
+
+ def test_undirected_not_connected(self):
+ g = nx.Graph()
+ g.add_nodes_from(range(3))
+ g.add_edge(0, 1)
+ pytest.raises(nx.NetworkXError, nx.average_shortest_path_length, g)
+
+ def test_trivial_graph(self):
+ """Tests that the trivial graph has average path length zero,
+ since there is exactly one path of length zero in the trivial
+ graph.
+
+ For more information, see issue #1960.
+
+ """
+ G = nx.trivial_graph()
+ assert nx.average_shortest_path_length(G) == 0
+
+ def test_null_graph(self):
+ with pytest.raises(nx.NetworkXPointlessConcept):
+ nx.average_shortest_path_length(nx.null_graph())
+
+ def test_bad_method(self):
+ with pytest.raises(ValueError):
+ G = nx.path_graph(2)
+ nx.average_shortest_path_length(G, weight="weight", method="SPAM")
+
+
+class TestAverageShortestPathLengthNumpy:
+ @classmethod
+ def setup_class(cls):
+ global np
+ import pytest
+
+ np = pytest.importorskip("numpy")
+
+ def test_specified_methods_numpy(self):
+ G = nx.Graph()
+ nx.add_cycle(G, range(7), weight=2)
+ ans = nx.average_shortest_path_length(
+ G, weight="weight", method="floyd-warshall-numpy"
+ )
+ np.testing.assert_almost_equal(ans, 4)
+
+ G = nx.Graph()
+ nx.add_path(G, range(5), weight=2)
+ ans = nx.average_shortest_path_length(
+ G, weight="weight", method="floyd-warshall-numpy"
+ )
+ np.testing.assert_almost_equal(ans, 4)
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_unweighted.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_unweighted.py
new file mode 100644
index 00000000..f09c8b10
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_unweighted.py
@@ -0,0 +1,149 @@
+import pytest
+
+import networkx as nx
+
+
+def validate_grid_path(r, c, s, t, p):
+ assert isinstance(p, list)
+ assert p[0] == s
+ assert p[-1] == t
+ s = ((s - 1) // c, (s - 1) % c)
+ t = ((t - 1) // c, (t - 1) % c)
+ assert len(p) == abs(t[0] - s[0]) + abs(t[1] - s[1]) + 1
+ p = [((u - 1) // c, (u - 1) % c) for u in p]
+ for u in p:
+ assert 0 <= u[0] < r
+ assert 0 <= u[1] < c
+ for u, v in zip(p[:-1], p[1:]):
+ assert (abs(v[0] - u[0]), abs(v[1] - u[1])) in [(0, 1), (1, 0)]
+
+
+class TestUnweightedPath:
+ @classmethod
+ def setup_class(cls):
+ from networkx import convert_node_labels_to_integers as cnlti
+
+ cls.grid = cnlti(nx.grid_2d_graph(4, 4), first_label=1, ordering="sorted")
+ cls.cycle = nx.cycle_graph(7)
+ cls.directed_cycle = nx.cycle_graph(7, create_using=nx.DiGraph())
+
+ def test_bidirectional_shortest_path(self):
+ assert nx.bidirectional_shortest_path(self.cycle, 0, 3) == [0, 1, 2, 3]
+ assert nx.bidirectional_shortest_path(self.cycle, 0, 4) == [0, 6, 5, 4]
+ validate_grid_path(
+ 4, 4, 1, 12, nx.bidirectional_shortest_path(self.grid, 1, 12)
+ )
+ assert nx.bidirectional_shortest_path(self.directed_cycle, 0, 3) == [0, 1, 2, 3]
+ # test source = target
+ assert nx.bidirectional_shortest_path(self.cycle, 3, 3) == [3]
+
+ @pytest.mark.parametrize(
+ ("src", "tgt"),
+ (
+ (8, 3), # source not in graph
+ (3, 8), # target not in graph
+ (8, 10), # neither source nor target in graph
+ (8, 8), # src == tgt, neither in graph - tests order of input checks
+ ),
+ )
+ def test_bidirectional_shortest_path_src_tgt_not_in_graph(self, src, tgt):
+ with pytest.raises(
+ nx.NodeNotFound,
+ match=f"(Source {src}|Target {tgt}) is not in G",
+ ):
+ nx.bidirectional_shortest_path(self.cycle, src, tgt)
+
+ def test_shortest_path_length(self):
+ assert nx.shortest_path_length(self.cycle, 0, 3) == 3
+ assert nx.shortest_path_length(self.grid, 1, 12) == 5
+ assert nx.shortest_path_length(self.directed_cycle, 0, 4) == 4
+ # now with weights
+ assert nx.shortest_path_length(self.cycle, 0, 3, weight=True) == 3
+ assert nx.shortest_path_length(self.grid, 1, 12, weight=True) == 5
+ assert nx.shortest_path_length(self.directed_cycle, 0, 4, weight=True) == 4
+
+ def test_single_source_shortest_path(self):
+ p = nx.single_source_shortest_path(self.directed_cycle, 3)
+ assert p[0] == [3, 4, 5, 6, 0]
+ p = nx.single_source_shortest_path(self.cycle, 0)
+ assert p[3] == [0, 1, 2, 3]
+ p = nx.single_source_shortest_path(self.cycle, 0, cutoff=0)
+ assert p == {0: [0]}
+
+ def test_single_source_shortest_path_length(self):
+ pl = nx.single_source_shortest_path_length
+ lengths = {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1}
+ assert dict(pl(self.cycle, 0)) == lengths
+ lengths = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6}
+ assert dict(pl(self.directed_cycle, 0)) == lengths
+
+ def test_single_target_shortest_path(self):
+ p = nx.single_target_shortest_path(self.directed_cycle, 0)
+ assert p[3] == [3, 4, 5, 6, 0]
+ p = nx.single_target_shortest_path(self.cycle, 0)
+ assert p[3] == [3, 2, 1, 0]
+ p = nx.single_target_shortest_path(self.cycle, 0, cutoff=0)
+ assert p == {0: [0]}
+ # test missing targets
+ target = 8
+ with pytest.raises(nx.NodeNotFound, match=f"Target {target} not in G"):
+ nx.single_target_shortest_path(self.cycle, target)
+
+ def test_single_target_shortest_path_length(self):
+ pl = nx.single_target_shortest_path_length
+ lengths = {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1}
+ assert dict(pl(self.cycle, 0)) == lengths
+ lengths = {0: 0, 1: 6, 2: 5, 3: 4, 4: 3, 5: 2, 6: 1}
+ assert dict(pl(self.directed_cycle, 0)) == lengths
+ # test missing targets
+ target = 8
+ with pytest.raises(nx.NodeNotFound, match=f"Target {target} is not in G"):
+ nx.single_target_shortest_path_length(self.cycle, target)
+
+ def test_all_pairs_shortest_path(self):
+ p = dict(nx.all_pairs_shortest_path(self.cycle))
+ assert p[0][3] == [0, 1, 2, 3]
+ p = dict(nx.all_pairs_shortest_path(self.grid))
+ validate_grid_path(4, 4, 1, 12, p[1][12])
+
+ def test_all_pairs_shortest_path_length(self):
+ l = dict(nx.all_pairs_shortest_path_length(self.cycle))
+ assert l[0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1}
+ l = dict(nx.all_pairs_shortest_path_length(self.grid))
+ assert l[1][16] == 6
+
+ def test_predecessor_path(self):
+ G = nx.path_graph(4)
+ assert nx.predecessor(G, 0) == {0: [], 1: [0], 2: [1], 3: [2]}
+ assert nx.predecessor(G, 0, 3) == [2]
+
+ def test_predecessor_cycle(self):
+ G = nx.cycle_graph(4)
+ pred = nx.predecessor(G, 0)
+ assert pred[0] == []
+ assert pred[1] == [0]
+ assert pred[2] in [[1, 3], [3, 1]]
+ assert pred[3] == [0]
+
+ def test_predecessor_cutoff(self):
+ G = nx.path_graph(4)
+ p = nx.predecessor(G, 0, 3)
+ assert 4 not in p
+
+ def test_predecessor_target(self):
+ G = nx.path_graph(4)
+ p = nx.predecessor(G, 0, 3)
+ assert p == [2]
+ p = nx.predecessor(G, 0, 3, cutoff=2)
+ assert p == []
+ p, s = nx.predecessor(G, 0, 3, return_seen=True)
+ assert p == [2]
+ assert s == 3
+ p, s = nx.predecessor(G, 0, 3, cutoff=2, return_seen=True)
+ assert p == []
+ assert s == -1
+
+ def test_predecessor_missing_source(self):
+ source = 8
+ with pytest.raises(nx.NodeNotFound, match=f"Source {source} not in G"):
+ nx.predecessor(self.cycle, source)
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_weighted.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_weighted.py
new file mode 100644
index 00000000..dc88572d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_weighted.py
@@ -0,0 +1,972 @@
+import pytest
+
+import networkx as nx
+from networkx.utils import pairwise
+
+
+def validate_path(G, s, t, soln_len, path, weight="weight"):
+ assert path[0] == s
+ assert path[-1] == t
+
+ if callable(weight):
+ weight_f = weight
+ else:
+ if G.is_multigraph():
+
+ def weight_f(u, v, d):
+ return min(e.get(weight, 1) for e in d.values())
+
+ else:
+
+ def weight_f(u, v, d):
+ return d.get(weight, 1)
+
+ computed = sum(weight_f(u, v, G[u][v]) for u, v in pairwise(path))
+ assert soln_len == computed
+
+
+def validate_length_path(G, s, t, soln_len, length, path, weight="weight"):
+ assert soln_len == length
+ validate_path(G, s, t, length, path, weight=weight)
+
+
+class WeightedTestBase:
+ """Base class for test classes that test functions for computing
+ shortest paths in weighted graphs.
+
+ """
+
+ def setup_method(self):
+ """Creates some graphs for use in the unit tests."""
+ cnlti = nx.convert_node_labels_to_integers
+ self.grid = cnlti(nx.grid_2d_graph(4, 4), first_label=1, ordering="sorted")
+ self.cycle = nx.cycle_graph(7)
+ self.directed_cycle = nx.cycle_graph(7, create_using=nx.DiGraph())
+ self.XG = nx.DiGraph()
+ self.XG.add_weighted_edges_from(
+ [
+ ("s", "u", 10),
+ ("s", "x", 5),
+ ("u", "v", 1),
+ ("u", "x", 2),
+ ("v", "y", 1),
+ ("x", "u", 3),
+ ("x", "v", 5),
+ ("x", "y", 2),
+ ("y", "s", 7),
+ ("y", "v", 6),
+ ]
+ )
+ self.MXG = nx.MultiDiGraph(self.XG)
+ self.MXG.add_edge("s", "u", weight=15)
+ self.XG2 = nx.DiGraph()
+ self.XG2.add_weighted_edges_from(
+ [
+ [1, 4, 1],
+ [4, 5, 1],
+ [5, 6, 1],
+ [6, 3, 1],
+ [1, 3, 50],
+ [1, 2, 100],
+ [2, 3, 100],
+ ]
+ )
+
+ self.XG3 = nx.Graph()
+ self.XG3.add_weighted_edges_from(
+ [[0, 1, 2], [1, 2, 12], [2, 3, 1], [3, 4, 5], [4, 5, 1], [5, 0, 10]]
+ )
+
+ self.XG4 = nx.Graph()
+ self.XG4.add_weighted_edges_from(
+ [
+ [0, 1, 2],
+ [1, 2, 2],
+ [2, 3, 1],
+ [3, 4, 1],
+ [4, 5, 1],
+ [5, 6, 1],
+ [6, 7, 1],
+ [7, 0, 1],
+ ]
+ )
+ self.MXG4 = nx.MultiGraph(self.XG4)
+ self.MXG4.add_edge(0, 1, weight=3)
+ self.G = nx.DiGraph() # no weights
+ self.G.add_edges_from(
+ [
+ ("s", "u"),
+ ("s", "x"),
+ ("u", "v"),
+ ("u", "x"),
+ ("v", "y"),
+ ("x", "u"),
+ ("x", "v"),
+ ("x", "y"),
+ ("y", "s"),
+ ("y", "v"),
+ ]
+ )
+
+
+class TestWeightedPath(WeightedTestBase):
+ def test_dijkstra(self):
+ (D, P) = nx.single_source_dijkstra(self.XG, "s")
+ validate_path(self.XG, "s", "v", 9, P["v"])
+ assert D["v"] == 9
+
+ validate_path(
+ self.XG, "s", "v", 9, nx.single_source_dijkstra_path(self.XG, "s")["v"]
+ )
+ assert dict(nx.single_source_dijkstra_path_length(self.XG, "s"))["v"] == 9
+
+ validate_path(
+ self.XG, "s", "v", 9, nx.single_source_dijkstra(self.XG, "s")[1]["v"]
+ )
+ validate_path(
+ self.MXG, "s", "v", 9, nx.single_source_dijkstra_path(self.MXG, "s")["v"]
+ )
+
+ GG = self.XG.to_undirected()
+ # make sure we get lower weight
+ # to_undirected might choose either edge with weight 2 or weight 3
+ GG["u"]["x"]["weight"] = 2
+ (D, P) = nx.single_source_dijkstra(GG, "s")
+ validate_path(GG, "s", "v", 8, P["v"])
+ assert D["v"] == 8 # uses lower weight of 2 on u<->x edge
+ validate_path(GG, "s", "v", 8, nx.dijkstra_path(GG, "s", "v"))
+ assert nx.dijkstra_path_length(GG, "s", "v") == 8
+
+ validate_path(self.XG2, 1, 3, 4, nx.dijkstra_path(self.XG2, 1, 3))
+ validate_path(self.XG3, 0, 3, 15, nx.dijkstra_path(self.XG3, 0, 3))
+ assert nx.dijkstra_path_length(self.XG3, 0, 3) == 15
+ validate_path(self.XG4, 0, 2, 4, nx.dijkstra_path(self.XG4, 0, 2))
+ assert nx.dijkstra_path_length(self.XG4, 0, 2) == 4
+ validate_path(self.MXG4, 0, 2, 4, nx.dijkstra_path(self.MXG4, 0, 2))
+ validate_path(
+ self.G, "s", "v", 2, nx.single_source_dijkstra(self.G, "s", "v")[1]
+ )
+ validate_path(
+ self.G, "s", "v", 2, nx.single_source_dijkstra(self.G, "s")[1]["v"]
+ )
+
+ validate_path(self.G, "s", "v", 2, nx.dijkstra_path(self.G, "s", "v"))
+ assert nx.dijkstra_path_length(self.G, "s", "v") == 2
+
+ # NetworkXError: node s not reachable from moon
+ pytest.raises(nx.NetworkXNoPath, nx.dijkstra_path, self.G, "s", "moon")
+ pytest.raises(nx.NetworkXNoPath, nx.dijkstra_path_length, self.G, "s", "moon")
+
+ validate_path(self.cycle, 0, 3, 3, nx.dijkstra_path(self.cycle, 0, 3))
+ validate_path(self.cycle, 0, 4, 3, nx.dijkstra_path(self.cycle, 0, 4))
+
+ assert nx.single_source_dijkstra(self.cycle, 0, 0) == (0, [0])
+
+ def test_bidirectional_dijkstra(self):
+ validate_length_path(
+ self.XG, "s", "v", 9, *nx.bidirectional_dijkstra(self.XG, "s", "v")
+ )
+ validate_length_path(
+ self.G, "s", "v", 2, *nx.bidirectional_dijkstra(self.G, "s", "v")
+ )
+ validate_length_path(
+ self.cycle, 0, 3, 3, *nx.bidirectional_dijkstra(self.cycle, 0, 3)
+ )
+ validate_length_path(
+ self.cycle, 0, 4, 3, *nx.bidirectional_dijkstra(self.cycle, 0, 4)
+ )
+ validate_length_path(
+ self.XG3, 0, 3, 15, *nx.bidirectional_dijkstra(self.XG3, 0, 3)
+ )
+ validate_length_path(
+ self.XG4, 0, 2, 4, *nx.bidirectional_dijkstra(self.XG4, 0, 2)
+ )
+
+ # need more tests here
+ P = nx.single_source_dijkstra_path(self.XG, "s")["v"]
+ validate_path(
+ self.XG,
+ "s",
+ "v",
+ sum(self.XG[u][v]["weight"] for u, v in zip(P[:-1], P[1:])),
+ nx.dijkstra_path(self.XG, "s", "v"),
+ )
+
+ # check absent source
+ G = nx.path_graph(2)
+ pytest.raises(nx.NodeNotFound, nx.bidirectional_dijkstra, G, 3, 0)
+
+ def test_weight_functions(self):
+ def heuristic(*z):
+ return sum(val**2 for val in z)
+
+ def getpath(pred, v, s):
+ return [v] if v == s else getpath(pred, pred[v], s) + [v]
+
+ def goldberg_radzik(g, s, t, weight="weight"):
+ pred, dist = nx.goldberg_radzik(g, s, weight=weight)
+ dist = dist[t]
+ return dist, getpath(pred, t, s)
+
+ def astar(g, s, t, weight="weight"):
+ path = nx.astar_path(g, s, t, heuristic, weight=weight)
+ dist = nx.astar_path_length(g, s, t, heuristic, weight=weight)
+ return dist, path
+
+ def vlp(G, s, t, l, F, w):
+ res = F(G, s, t, weight=w)
+ validate_length_path(G, s, t, l, *res, weight=w)
+
+ G = self.cycle
+ s = 6
+ t = 4
+ path = [6] + list(range(t + 1))
+
+ def weight(u, v, _):
+ return 1 + v**2
+
+ length = sum(weight(u, v, None) for u, v in pairwise(path))
+ vlp(G, s, t, length, nx.bidirectional_dijkstra, weight)
+ vlp(G, s, t, length, nx.single_source_dijkstra, weight)
+ vlp(G, s, t, length, nx.single_source_bellman_ford, weight)
+ vlp(G, s, t, length, goldberg_radzik, weight)
+ vlp(G, s, t, length, astar, weight)
+
+ def weight(u, v, _):
+ return 2 ** (u * v)
+
+ length = sum(weight(u, v, None) for u, v in pairwise(path))
+ vlp(G, s, t, length, nx.bidirectional_dijkstra, weight)
+ vlp(G, s, t, length, nx.single_source_dijkstra, weight)
+ vlp(G, s, t, length, nx.single_source_bellman_ford, weight)
+ vlp(G, s, t, length, goldberg_radzik, weight)
+ vlp(G, s, t, length, astar, weight)
+
+ def test_bidirectional_dijkstra_no_path(self):
+ with pytest.raises(nx.NetworkXNoPath):
+ G = nx.Graph()
+ nx.add_path(G, [1, 2, 3])
+ nx.add_path(G, [4, 5, 6])
+ path = nx.bidirectional_dijkstra(G, 1, 6)
+
+ @pytest.mark.parametrize(
+ "fn",
+ (
+ nx.dijkstra_path,
+ nx.dijkstra_path_length,
+ nx.single_source_dijkstra_path,
+ nx.single_source_dijkstra_path_length,
+ nx.single_source_dijkstra,
+ nx.dijkstra_predecessor_and_distance,
+ ),
+ )
+ def test_absent_source(self, fn):
+ G = nx.path_graph(2)
+ with pytest.raises(nx.NodeNotFound):
+ fn(G, 3, 0)
+ # Test when source == target, which is handled specially by some functions
+ with pytest.raises(nx.NodeNotFound):
+ fn(G, 3, 3)
+
+ def test_dijkstra_predecessor1(self):
+ G = nx.path_graph(4)
+ assert nx.dijkstra_predecessor_and_distance(G, 0) == (
+ {0: [], 1: [0], 2: [1], 3: [2]},
+ {0: 0, 1: 1, 2: 2, 3: 3},
+ )
+
+ def test_dijkstra_predecessor2(self):
+ # 4-cycle
+ G = nx.Graph([(0, 1), (1, 2), (2, 3), (3, 0)])
+ pred, dist = nx.dijkstra_predecessor_and_distance(G, (0))
+ assert pred[0] == []
+ assert pred[1] == [0]
+ assert pred[2] in [[1, 3], [3, 1]]
+ assert pred[3] == [0]
+ assert dist == {0: 0, 1: 1, 2: 2, 3: 1}
+
+ def test_dijkstra_predecessor3(self):
+ XG = nx.DiGraph()
+ XG.add_weighted_edges_from(
+ [
+ ("s", "u", 10),
+ ("s", "x", 5),
+ ("u", "v", 1),
+ ("u", "x", 2),
+ ("v", "y", 1),
+ ("x", "u", 3),
+ ("x", "v", 5),
+ ("x", "y", 2),
+ ("y", "s", 7),
+ ("y", "v", 6),
+ ]
+ )
+ (P, D) = nx.dijkstra_predecessor_and_distance(XG, "s")
+ assert P["v"] == ["u"]
+ assert D["v"] == 9
+ (P, D) = nx.dijkstra_predecessor_and_distance(XG, "s", cutoff=8)
+ assert "v" not in D
+
+ def test_single_source_dijkstra_path_length(self):
+ pl = nx.single_source_dijkstra_path_length
+ assert dict(pl(self.MXG4, 0))[2] == 4
+ spl = pl(self.MXG4, 0, cutoff=2)
+ assert 2 not in spl
+
+ def test_bidirectional_dijkstra_multigraph(self):
+ G = nx.MultiGraph()
+ G.add_edge("a", "b", weight=10)
+ G.add_edge("a", "b", weight=100)
+ dp = nx.bidirectional_dijkstra(G, "a", "b")
+ assert dp == (10, ["a", "b"])
+
+ def test_dijkstra_pred_distance_multigraph(self):
+ G = nx.MultiGraph()
+ G.add_edge("a", "b", key="short", foo=5, weight=100)
+ G.add_edge("a", "b", key="long", bar=1, weight=110)
+ p, d = nx.dijkstra_predecessor_and_distance(G, "a")
+ assert p == {"a": [], "b": ["a"]}
+ assert d == {"a": 0, "b": 100}
+
+ def test_negative_edge_cycle(self):
+ G = nx.cycle_graph(5, create_using=nx.DiGraph())
+ assert not nx.negative_edge_cycle(G)
+ G.add_edge(8, 9, weight=-7)
+ G.add_edge(9, 8, weight=3)
+ graph_size = len(G)
+ assert nx.negative_edge_cycle(G)
+ assert graph_size == len(G)
+ pytest.raises(ValueError, nx.single_source_dijkstra_path_length, G, 8)
+ pytest.raises(ValueError, nx.single_source_dijkstra, G, 8)
+ pytest.raises(ValueError, nx.dijkstra_predecessor_and_distance, G, 8)
+ G.add_edge(9, 10)
+ pytest.raises(ValueError, nx.bidirectional_dijkstra, G, 8, 10)
+ G = nx.MultiDiGraph()
+ G.add_edge(2, 2, weight=-1)
+ assert nx.negative_edge_cycle(G)
+
+ def test_negative_edge_cycle_empty(self):
+ G = nx.DiGraph()
+ assert not nx.negative_edge_cycle(G)
+
+ def test_negative_edge_cycle_custom_weight_key(self):
+ d = nx.DiGraph()
+ d.add_edge("a", "b", w=-2)
+ d.add_edge("b", "a", w=-1)
+ assert nx.negative_edge_cycle(d, weight="w")
+
+ def test_weight_function(self):
+ """Tests that a callable weight is interpreted as a weight
+ function instead of an edge attribute.
+
+ """
+ # Create a triangle in which the edge from node 0 to node 2 has
+ # a large weight and the other two edges have a small weight.
+ G = nx.complete_graph(3)
+ G.adj[0][2]["weight"] = 10
+ G.adj[0][1]["weight"] = 1
+ G.adj[1][2]["weight"] = 1
+
+ # The weight function will take the multiplicative inverse of
+ # the weights on the edges. This way, weights that were large
+ # before now become small and vice versa.
+
+ def weight(u, v, d):
+ return 1 / d["weight"]
+
+ # The shortest path from 0 to 2 using the actual weights on the
+ # edges should be [0, 1, 2].
+ distance, path = nx.single_source_dijkstra(G, 0, 2)
+ assert distance == 2
+ assert path == [0, 1, 2]
+ # However, with the above weight function, the shortest path
+ # should be [0, 2], since that has a very small weight.
+ distance, path = nx.single_source_dijkstra(G, 0, 2, weight=weight)
+ assert distance == 1 / 10
+ assert path == [0, 2]
+
+ def test_all_pairs_dijkstra_path(self):
+ cycle = nx.cycle_graph(7)
+ p = dict(nx.all_pairs_dijkstra_path(cycle))
+ assert p[0][3] == [0, 1, 2, 3]
+
+ cycle[1][2]["weight"] = 10
+ p = dict(nx.all_pairs_dijkstra_path(cycle))
+ assert p[0][3] == [0, 6, 5, 4, 3]
+
+ def test_all_pairs_dijkstra_path_length(self):
+ cycle = nx.cycle_graph(7)
+ pl = dict(nx.all_pairs_dijkstra_path_length(cycle))
+ assert pl[0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1}
+
+ cycle[1][2]["weight"] = 10
+ pl = dict(nx.all_pairs_dijkstra_path_length(cycle))
+ assert pl[0] == {0: 0, 1: 1, 2: 5, 3: 4, 4: 3, 5: 2, 6: 1}
+
+ def test_all_pairs_dijkstra(self):
+ cycle = nx.cycle_graph(7)
+ out = dict(nx.all_pairs_dijkstra(cycle))
+ assert out[0][0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1}
+ assert out[0][1][3] == [0, 1, 2, 3]
+
+ cycle[1][2]["weight"] = 10
+ out = dict(nx.all_pairs_dijkstra(cycle))
+ assert out[0][0] == {0: 0, 1: 1, 2: 5, 3: 4, 4: 3, 5: 2, 6: 1}
+ assert out[0][1][3] == [0, 6, 5, 4, 3]
+
+
+class TestDijkstraPathLength:
+ """Unit tests for the :func:`networkx.dijkstra_path_length`
+ function.
+
+ """
+
+ def test_weight_function(self):
+ """Tests for computing the length of the shortest path using
+ Dijkstra's algorithm with a user-defined weight function.
+
+ """
+ # Create a triangle in which the edge from node 0 to node 2 has
+ # a large weight and the other two edges have a small weight.
+ G = nx.complete_graph(3)
+ G.adj[0][2]["weight"] = 10
+ G.adj[0][1]["weight"] = 1
+ G.adj[1][2]["weight"] = 1
+
+ # The weight function will take the multiplicative inverse of
+ # the weights on the edges. This way, weights that were large
+ # before now become small and vice versa.
+
+ def weight(u, v, d):
+ return 1 / d["weight"]
+
+ # The shortest path from 0 to 2 using the actual weights on the
+ # edges should be [0, 1, 2]. However, with the above weight
+ # function, the shortest path should be [0, 2], since that has a
+ # very small weight.
+ length = nx.dijkstra_path_length(G, 0, 2, weight=weight)
+ assert length == 1 / 10
+
+
+class TestMultiSourceDijkstra:
+ """Unit tests for the multi-source dialect of Dijkstra's shortest
+ path algorithms.
+
+ """
+
+ def test_no_sources(self):
+ with pytest.raises(ValueError):
+ nx.multi_source_dijkstra(nx.Graph(), {})
+
+ def test_path_no_sources(self):
+ with pytest.raises(ValueError):
+ nx.multi_source_dijkstra_path(nx.Graph(), {})
+
+ def test_path_length_no_sources(self):
+ with pytest.raises(ValueError):
+ nx.multi_source_dijkstra_path_length(nx.Graph(), {})
+
+ @pytest.mark.parametrize(
+ "fn",
+ (
+ nx.multi_source_dijkstra_path,
+ nx.multi_source_dijkstra_path_length,
+ nx.multi_source_dijkstra,
+ ),
+ )
+ def test_absent_source(self, fn):
+ G = nx.path_graph(2)
+ with pytest.raises(nx.NodeNotFound):
+ fn(G, [3], 0)
+ with pytest.raises(nx.NodeNotFound):
+ fn(G, [3], 3)
+
+ def test_two_sources(self):
+ edges = [(0, 1, 1), (1, 2, 1), (2, 3, 10), (3, 4, 1)]
+ G = nx.Graph()
+ G.add_weighted_edges_from(edges)
+ sources = {0, 4}
+ distances, paths = nx.multi_source_dijkstra(G, sources)
+ expected_distances = {0: 0, 1: 1, 2: 2, 3: 1, 4: 0}
+ expected_paths = {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [4, 3], 4: [4]}
+ assert distances == expected_distances
+ assert paths == expected_paths
+
+ def test_simple_paths(self):
+ G = nx.path_graph(4)
+ lengths = nx.multi_source_dijkstra_path_length(G, [0])
+ assert lengths == {n: n for n in G}
+ paths = nx.multi_source_dijkstra_path(G, [0])
+ assert paths == {n: list(range(n + 1)) for n in G}
+
+
+class TestBellmanFordAndGoldbergRadzik(WeightedTestBase):
+ def test_single_node_graph(self):
+ G = nx.DiGraph()
+ G.add_node(0)
+ assert nx.single_source_bellman_ford_path(G, 0) == {0: [0]}
+ assert nx.single_source_bellman_ford_path_length(G, 0) == {0: 0}
+ assert nx.single_source_bellman_ford(G, 0) == ({0: 0}, {0: [0]})
+ assert nx.bellman_ford_predecessor_and_distance(G, 0) == ({0: []}, {0: 0})
+ assert nx.goldberg_radzik(G, 0) == ({0: None}, {0: 0})
+
+ def test_absent_source_bellman_ford(self):
+ # the check is in _bellman_ford; this provides regression testing
+ # against later changes to "client" Bellman-Ford functions
+ G = nx.path_graph(2)
+ for fn in (
+ nx.bellman_ford_predecessor_and_distance,
+ nx.bellman_ford_path,
+ nx.bellman_ford_path_length,
+ nx.single_source_bellman_ford_path,
+ nx.single_source_bellman_ford_path_length,
+ nx.single_source_bellman_ford,
+ ):
+ pytest.raises(nx.NodeNotFound, fn, G, 3, 0)
+ pytest.raises(nx.NodeNotFound, fn, G, 3, 3)
+
+ def test_absent_source_goldberg_radzik(self):
+ with pytest.raises(nx.NodeNotFound):
+ G = nx.path_graph(2)
+ nx.goldberg_radzik(G, 3, 0)
+
+ def test_negative_cycle_heuristic(self):
+ G = nx.DiGraph()
+ G.add_edge(0, 1, weight=-1)
+ G.add_edge(1, 2, weight=-1)
+ G.add_edge(2, 3, weight=-1)
+ G.add_edge(3, 0, weight=3)
+ assert not nx.negative_edge_cycle(G, heuristic=True)
+ G.add_edge(2, 0, weight=1.999)
+ assert nx.negative_edge_cycle(G, heuristic=True)
+ G.edges[2, 0]["weight"] = 2
+ assert not nx.negative_edge_cycle(G, heuristic=True)
+
+ def test_negative_cycle_consistency(self):
+ import random
+
+ unif = random.uniform
+ for random_seed in range(2): # range(20):
+ random.seed(random_seed)
+ for density in [0.1, 0.9]: # .3, .7, .9]:
+ for N in [1, 10, 20]: # range(1, 60 - int(30 * density)):
+ for max_cost in [1, 90]: # [1, 10, 40, 90]:
+ G = nx.binomial_graph(N, density, seed=4, directed=True)
+ edges = ((u, v, unif(-1, max_cost)) for u, v in G.edges)
+ G.add_weighted_edges_from(edges)
+
+ no_heuristic = nx.negative_edge_cycle(G, heuristic=False)
+ with_heuristic = nx.negative_edge_cycle(G, heuristic=True)
+ assert no_heuristic == with_heuristic
+
+ def test_negative_cycle(self):
+ G = nx.cycle_graph(5, create_using=nx.DiGraph())
+ G.add_edge(1, 2, weight=-7)
+ for i in range(5):
+ pytest.raises(
+ nx.NetworkXUnbounded, nx.single_source_bellman_ford_path, G, i
+ )
+ pytest.raises(
+ nx.NetworkXUnbounded, nx.single_source_bellman_ford_path_length, G, i
+ )
+ pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford, G, i)
+ pytest.raises(
+ nx.NetworkXUnbounded, nx.bellman_ford_predecessor_and_distance, G, i
+ )
+ pytest.raises(nx.NetworkXUnbounded, nx.goldberg_radzik, G, i)
+ G = nx.cycle_graph(5) # undirected Graph
+ G.add_edge(1, 2, weight=-3)
+ for i in range(5):
+ pytest.raises(
+ nx.NetworkXUnbounded, nx.single_source_bellman_ford_path, G, i
+ )
+ pytest.raises(
+ nx.NetworkXUnbounded, nx.single_source_bellman_ford_path_length, G, i
+ )
+ pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford, G, i)
+ pytest.raises(
+ nx.NetworkXUnbounded, nx.bellman_ford_predecessor_and_distance, G, i
+ )
+ pytest.raises(nx.NetworkXUnbounded, nx.goldberg_radzik, G, i)
+ G = nx.DiGraph([(1, 1, {"weight": -1})])
+ pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford_path, G, 1)
+ pytest.raises(
+ nx.NetworkXUnbounded, nx.single_source_bellman_ford_path_length, G, 1
+ )
+ pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford, G, 1)
+ pytest.raises(
+ nx.NetworkXUnbounded, nx.bellman_ford_predecessor_and_distance, G, 1
+ )
+ pytest.raises(nx.NetworkXUnbounded, nx.goldberg_radzik, G, 1)
+ G = nx.MultiDiGraph([(1, 1, {"weight": -1})])
+ pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford_path, G, 1)
+ pytest.raises(
+ nx.NetworkXUnbounded, nx.single_source_bellman_ford_path_length, G, 1
+ )
+ pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford, G, 1)
+ pytest.raises(
+ nx.NetworkXUnbounded, nx.bellman_ford_predecessor_and_distance, G, 1
+ )
+ pytest.raises(nx.NetworkXUnbounded, nx.goldberg_radzik, G, 1)
+
+ def test_zero_cycle(self):
+ G = nx.cycle_graph(5, create_using=nx.DiGraph())
+ G.add_edge(2, 3, weight=-4)
+ # check that zero cycle doesn't raise
+ nx.goldberg_radzik(G, 1)
+ nx.bellman_ford_predecessor_and_distance(G, 1)
+
+ G.add_edge(2, 3, weight=-4.0001)
+ # check that negative cycle does raise
+ pytest.raises(
+ nx.NetworkXUnbounded, nx.bellman_ford_predecessor_and_distance, G, 1
+ )
+ pytest.raises(nx.NetworkXUnbounded, nx.goldberg_radzik, G, 1)
+
+ def test_find_negative_cycle_longer_cycle(self):
+ G = nx.cycle_graph(5, create_using=nx.DiGraph())
+ nx.add_cycle(G, [3, 5, 6, 7, 8, 9])
+ G.add_edge(1, 2, weight=-30)
+ assert nx.find_negative_cycle(G, 1) == [0, 1, 2, 3, 4, 0]
+ assert nx.find_negative_cycle(G, 7) == [2, 3, 4, 0, 1, 2]
+
+ def test_find_negative_cycle_no_cycle(self):
+ G = nx.path_graph(5, create_using=nx.DiGraph())
+ pytest.raises(nx.NetworkXError, nx.find_negative_cycle, G, 3)
+
+ def test_find_negative_cycle_single_edge(self):
+ G = nx.Graph()
+ G.add_edge(0, 1, weight=-1)
+ assert nx.find_negative_cycle(G, 1) == [1, 0, 1]
+
+ def test_negative_weight(self):
+ G = nx.cycle_graph(5, create_using=nx.DiGraph())
+ G.add_edge(1, 2, weight=-3)
+ assert nx.single_source_bellman_ford_path(G, 0) == {
+ 0: [0],
+ 1: [0, 1],
+ 2: [0, 1, 2],
+ 3: [0, 1, 2, 3],
+ 4: [0, 1, 2, 3, 4],
+ }
+ assert nx.single_source_bellman_ford_path_length(G, 0) == {
+ 0: 0,
+ 1: 1,
+ 2: -2,
+ 3: -1,
+ 4: 0,
+ }
+ assert nx.single_source_bellman_ford(G, 0) == (
+ {0: 0, 1: 1, 2: -2, 3: -1, 4: 0},
+ {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 1, 2, 3], 4: [0, 1, 2, 3, 4]},
+ )
+ assert nx.bellman_ford_predecessor_and_distance(G, 0) == (
+ {0: [], 1: [0], 2: [1], 3: [2], 4: [3]},
+ {0: 0, 1: 1, 2: -2, 3: -1, 4: 0},
+ )
+ assert nx.goldberg_radzik(G, 0) == (
+ {0: None, 1: 0, 2: 1, 3: 2, 4: 3},
+ {0: 0, 1: 1, 2: -2, 3: -1, 4: 0},
+ )
+
+ def test_not_connected(self):
+ G = nx.complete_graph(6)
+ G.add_edge(10, 11)
+ G.add_edge(10, 12)
+ assert nx.single_source_bellman_ford_path(G, 0) == {
+ 0: [0],
+ 1: [0, 1],
+ 2: [0, 2],
+ 3: [0, 3],
+ 4: [0, 4],
+ 5: [0, 5],
+ }
+ assert nx.single_source_bellman_ford_path_length(G, 0) == {
+ 0: 0,
+ 1: 1,
+ 2: 1,
+ 3: 1,
+ 4: 1,
+ 5: 1,
+ }
+ assert nx.single_source_bellman_ford(G, 0) == (
+ {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1},
+ {0: [0], 1: [0, 1], 2: [0, 2], 3: [0, 3], 4: [0, 4], 5: [0, 5]},
+ )
+ assert nx.bellman_ford_predecessor_and_distance(G, 0) == (
+ {0: [], 1: [0], 2: [0], 3: [0], 4: [0], 5: [0]},
+ {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1},
+ )
+ assert nx.goldberg_radzik(G, 0) == (
+ {0: None, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0},
+ {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1},
+ )
+
+ # not connected, with a component not containing the source that
+ # contains a negative cycle.
+ G = nx.complete_graph(6)
+ G.add_edges_from(
+ [
+ ("A", "B", {"load": 3}),
+ ("B", "C", {"load": -10}),
+ ("C", "A", {"load": 2}),
+ ]
+ )
+ assert nx.single_source_bellman_ford_path(G, 0, weight="load") == {
+ 0: [0],
+ 1: [0, 1],
+ 2: [0, 2],
+ 3: [0, 3],
+ 4: [0, 4],
+ 5: [0, 5],
+ }
+ assert nx.single_source_bellman_ford_path_length(G, 0, weight="load") == {
+ 0: 0,
+ 1: 1,
+ 2: 1,
+ 3: 1,
+ 4: 1,
+ 5: 1,
+ }
+ assert nx.single_source_bellman_ford(G, 0, weight="load") == (
+ {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1},
+ {0: [0], 1: [0, 1], 2: [0, 2], 3: [0, 3], 4: [0, 4], 5: [0, 5]},
+ )
+ assert nx.bellman_ford_predecessor_and_distance(G, 0, weight="load") == (
+ {0: [], 1: [0], 2: [0], 3: [0], 4: [0], 5: [0]},
+ {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1},
+ )
+ assert nx.goldberg_radzik(G, 0, weight="load") == (
+ {0: None, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0},
+ {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1},
+ )
+
+ def test_multigraph(self):
+ assert nx.bellman_ford_path(self.MXG, "s", "v") == ["s", "x", "u", "v"]
+ assert nx.bellman_ford_path_length(self.MXG, "s", "v") == 9
+ assert nx.single_source_bellman_ford_path(self.MXG, "s")["v"] == [
+ "s",
+ "x",
+ "u",
+ "v",
+ ]
+ assert nx.single_source_bellman_ford_path_length(self.MXG, "s")["v"] == 9
+ D, P = nx.single_source_bellman_ford(self.MXG, "s", target="v")
+ assert D == 9
+ assert P == ["s", "x", "u", "v"]
+ P, D = nx.bellman_ford_predecessor_and_distance(self.MXG, "s")
+ assert P["v"] == ["u"]
+ assert D["v"] == 9
+ P, D = nx.goldberg_radzik(self.MXG, "s")
+ assert P["v"] == "u"
+ assert D["v"] == 9
+ assert nx.bellman_ford_path(self.MXG4, 0, 2) == [0, 1, 2]
+ assert nx.bellman_ford_path_length(self.MXG4, 0, 2) == 4
+ assert nx.single_source_bellman_ford_path(self.MXG4, 0)[2] == [0, 1, 2]
+ assert nx.single_source_bellman_ford_path_length(self.MXG4, 0)[2] == 4
+ D, P = nx.single_source_bellman_ford(self.MXG4, 0, target=2)
+ assert D == 4
+ assert P == [0, 1, 2]
+ P, D = nx.bellman_ford_predecessor_and_distance(self.MXG4, 0)
+ assert P[2] == [1]
+ assert D[2] == 4
+ P, D = nx.goldberg_radzik(self.MXG4, 0)
+ assert P[2] == 1
+ assert D[2] == 4
+
+ def test_others(self):
+ assert nx.bellman_ford_path(self.XG, "s", "v") == ["s", "x", "u", "v"]
+ assert nx.bellman_ford_path_length(self.XG, "s", "v") == 9
+ assert nx.single_source_bellman_ford_path(self.XG, "s")["v"] == [
+ "s",
+ "x",
+ "u",
+ "v",
+ ]
+ assert nx.single_source_bellman_ford_path_length(self.XG, "s")["v"] == 9
+ D, P = nx.single_source_bellman_ford(self.XG, "s", target="v")
+ assert D == 9
+ assert P == ["s", "x", "u", "v"]
+ (P, D) = nx.bellman_ford_predecessor_and_distance(self.XG, "s")
+ assert P["v"] == ["u"]
+ assert D["v"] == 9
+ (P, D) = nx.goldberg_radzik(self.XG, "s")
+ assert P["v"] == "u"
+ assert D["v"] == 9
+
+ def test_path_graph(self):
+ G = nx.path_graph(4)
+ assert nx.single_source_bellman_ford_path(G, 0) == {
+ 0: [0],
+ 1: [0, 1],
+ 2: [0, 1, 2],
+ 3: [0, 1, 2, 3],
+ }
+ assert nx.single_source_bellman_ford_path_length(G, 0) == {
+ 0: 0,
+ 1: 1,
+ 2: 2,
+ 3: 3,
+ }
+ assert nx.single_source_bellman_ford(G, 0) == (
+ {0: 0, 1: 1, 2: 2, 3: 3},
+ {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 1, 2, 3]},
+ )
+ assert nx.bellman_ford_predecessor_and_distance(G, 0) == (
+ {0: [], 1: [0], 2: [1], 3: [2]},
+ {0: 0, 1: 1, 2: 2, 3: 3},
+ )
+ assert nx.goldberg_radzik(G, 0) == (
+ {0: None, 1: 0, 2: 1, 3: 2},
+ {0: 0, 1: 1, 2: 2, 3: 3},
+ )
+ assert nx.single_source_bellman_ford_path(G, 3) == {
+ 0: [3, 2, 1, 0],
+ 1: [3, 2, 1],
+ 2: [3, 2],
+ 3: [3],
+ }
+ assert nx.single_source_bellman_ford_path_length(G, 3) == {
+ 0: 3,
+ 1: 2,
+ 2: 1,
+ 3: 0,
+ }
+ assert nx.single_source_bellman_ford(G, 3) == (
+ {0: 3, 1: 2, 2: 1, 3: 0},
+ {0: [3, 2, 1, 0], 1: [3, 2, 1], 2: [3, 2], 3: [3]},
+ )
+ assert nx.bellman_ford_predecessor_and_distance(G, 3) == (
+ {0: [1], 1: [2], 2: [3], 3: []},
+ {0: 3, 1: 2, 2: 1, 3: 0},
+ )
+ assert nx.goldberg_radzik(G, 3) == (
+ {0: 1, 1: 2, 2: 3, 3: None},
+ {0: 3, 1: 2, 2: 1, 3: 0},
+ )
+
+ def test_4_cycle(self):
+ # 4-cycle
+ G = nx.Graph([(0, 1), (1, 2), (2, 3), (3, 0)])
+ dist, path = nx.single_source_bellman_ford(G, 0)
+ assert dist == {0: 0, 1: 1, 2: 2, 3: 1}
+ assert path[0] == [0]
+ assert path[1] == [0, 1]
+ assert path[2] in [[0, 1, 2], [0, 3, 2]]
+ assert path[3] == [0, 3]
+
+ pred, dist = nx.bellman_ford_predecessor_and_distance(G, 0)
+ assert pred[0] == []
+ assert pred[1] == [0]
+ assert pred[2] in [[1, 3], [3, 1]]
+ assert pred[3] == [0]
+ assert dist == {0: 0, 1: 1, 2: 2, 3: 1}
+
+ pred, dist = nx.goldberg_radzik(G, 0)
+ assert pred[0] is None
+ assert pred[1] == 0
+ assert pred[2] in [1, 3]
+ assert pred[3] == 0
+ assert dist == {0: 0, 1: 1, 2: 2, 3: 1}
+
+ def test_negative_weight_bf_path(self):
+ G = nx.DiGraph()
+ G.add_nodes_from("abcd")
+ G.add_edge("a", "d", weight=0)
+ G.add_edge("a", "b", weight=1)
+ G.add_edge("b", "c", weight=-3)
+ G.add_edge("c", "d", weight=1)
+
+ assert nx.bellman_ford_path(G, "a", "d") == ["a", "b", "c", "d"]
+ assert nx.bellman_ford_path_length(G, "a", "d") == -1
+
+ def test_zero_cycle_smoke(self):
+ D = nx.DiGraph()
+ D.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1), (3, 1, -2)])
+
+ nx.bellman_ford_path(D, 1, 3)
+ nx.dijkstra_path(D, 1, 3)
+ nx.bidirectional_dijkstra(D, 1, 3)
+ # FIXME nx.goldberg_radzik(D, 1)
+
+
+class TestJohnsonAlgorithm(WeightedTestBase):
+ def test_single_node_graph(self):
+ G = nx.DiGraph()
+ G.add_node(0)
+ assert nx.johnson(G) == {0: {0: [0]}}
+
+ def test_negative_cycle(self):
+ G = nx.DiGraph()
+ G.add_weighted_edges_from(
+ [
+ ("0", "3", 3),
+ ("0", "1", -5),
+ ("1", "0", -5),
+ ("0", "2", 2),
+ ("1", "2", 4),
+ ("2", "3", 1),
+ ]
+ )
+ pytest.raises(nx.NetworkXUnbounded, nx.johnson, G)
+ G = nx.Graph()
+ G.add_weighted_edges_from(
+ [
+ ("0", "3", 3),
+ ("0", "1", -5),
+ ("1", "0", -5),
+ ("0", "2", 2),
+ ("1", "2", 4),
+ ("2", "3", 1),
+ ]
+ )
+ pytest.raises(nx.NetworkXUnbounded, nx.johnson, G)
+
+ def test_negative_weights(self):
+ G = nx.DiGraph()
+ G.add_weighted_edges_from(
+ [("0", "3", 3), ("0", "1", -5), ("0", "2", 2), ("1", "2", 4), ("2", "3", 1)]
+ )
+ paths = nx.johnson(G)
+ assert paths == {
+ "1": {"1": ["1"], "3": ["1", "2", "3"], "2": ["1", "2"]},
+ "0": {
+ "1": ["0", "1"],
+ "0": ["0"],
+ "3": ["0", "1", "2", "3"],
+ "2": ["0", "1", "2"],
+ },
+ "3": {"3": ["3"]},
+ "2": {"3": ["2", "3"], "2": ["2"]},
+ }
+
+ def test_unweighted_graph(self):
+ G = nx.Graph()
+ G.add_edges_from([(1, 0), (2, 1)])
+ H = G.copy()
+ nx.set_edge_attributes(H, values=1, name="weight")
+ assert nx.johnson(G) == nx.johnson(H)
+
+ def test_partially_weighted_graph_with_negative_edges(self):
+ G = nx.DiGraph()
+ G.add_edges_from([(0, 1), (1, 2), (2, 0), (1, 0)])
+ G[1][0]["weight"] = -2
+ G[0][1]["weight"] = 3
+ G[1][2]["weight"] = -4
+
+ H = G.copy()
+ H[2][0]["weight"] = 1
+
+ I = G.copy()
+ I[2][0]["weight"] = 8
+
+ assert nx.johnson(G) == nx.johnson(H)
+ assert nx.johnson(G) != nx.johnson(I)
+
+ def test_graphs(self):
+ validate_path(self.XG, "s", "v", 9, nx.johnson(self.XG)["s"]["v"])
+ validate_path(self.MXG, "s", "v", 9, nx.johnson(self.MXG)["s"]["v"])
+ validate_path(self.XG2, 1, 3, 4, nx.johnson(self.XG2)[1][3])
+ validate_path(self.XG3, 0, 3, 15, nx.johnson(self.XG3)[0][3])
+ validate_path(self.XG4, 0, 2, 4, nx.johnson(self.XG4)[0][2])
+ validate_path(self.MXG4, 0, 2, 4, nx.johnson(self.MXG4)[0][2])
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/unweighted.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/unweighted.py
new file mode 100644
index 00000000..3aeef854
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/unweighted.py
@@ -0,0 +1,579 @@
+"""
+Shortest path algorithms for unweighted graphs.
+"""
+
+import warnings
+
+import networkx as nx
+
+__all__ = [
+ "bidirectional_shortest_path",
+ "single_source_shortest_path",
+ "single_source_shortest_path_length",
+ "single_target_shortest_path",
+ "single_target_shortest_path_length",
+ "all_pairs_shortest_path",
+ "all_pairs_shortest_path_length",
+ "predecessor",
+]
+
+
+@nx._dispatchable
+def single_source_shortest_path_length(G, source, cutoff=None):
+ """Compute the shortest path lengths from source to all reachable nodes.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node
+ Starting node for path
+
+ cutoff : integer, optional
+ Depth to stop the search. Only paths of length <= cutoff are returned.
+
+ Returns
+ -------
+ lengths : dict
+ Dict keyed by node to shortest path length to source.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> length = nx.single_source_shortest_path_length(G, 0)
+ >>> length[4]
+ 4
+ >>> for node in length:
+ ... print(f"{node}: {length[node]}")
+ 0: 0
+ 1: 1
+ 2: 2
+ 3: 3
+ 4: 4
+
+ See Also
+ --------
+ shortest_path_length
+ """
+ if source not in G:
+ raise nx.NodeNotFound(f"Source {source} is not in G")
+ if cutoff is None:
+ cutoff = float("inf")
+ nextlevel = [source]
+ return dict(_single_shortest_path_length(G._adj, nextlevel, cutoff))
+
+
+def _single_shortest_path_length(adj, firstlevel, cutoff):
+ """Yields (node, level) in a breadth first search
+
+ Shortest Path Length helper function
+ Parameters
+ ----------
+ adj : dict
+ Adjacency dict or view
+ firstlevel : list
+ starting nodes, e.g. [source] or [target]
+ cutoff : int or float
+ level at which we stop the process
+ """
+ seen = set(firstlevel)
+ nextlevel = firstlevel
+ level = 0
+ n = len(adj)
+ for v in nextlevel:
+ yield (v, level)
+ while nextlevel and cutoff > level:
+ level += 1
+ thislevel = nextlevel
+ nextlevel = []
+ for v in thislevel:
+ for w in adj[v]:
+ if w not in seen:
+ seen.add(w)
+ nextlevel.append(w)
+ yield (w, level)
+ if len(seen) == n:
+ return
+
+
+@nx._dispatchable
+def single_target_shortest_path_length(G, target, cutoff=None):
+ """Compute the shortest path lengths to target from all reachable nodes.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ target : node
+ Target node for path
+
+ cutoff : integer, optional
+ Depth to stop the search. Only paths of length <= cutoff are returned.
+
+ Returns
+ -------
+ lengths : iterator
+ (source, shortest path length) iterator
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5, create_using=nx.DiGraph())
+ >>> length = dict(nx.single_target_shortest_path_length(G, 4))
+ >>> length[0]
+ 4
+ >>> for node in range(5):
+ ... print(f"{node}: {length[node]}")
+ 0: 4
+ 1: 3
+ 2: 2
+ 3: 1
+ 4: 0
+
+ See Also
+ --------
+ single_source_shortest_path_length, shortest_path_length
+ """
+ if target not in G:
+ raise nx.NodeNotFound(f"Target {target} is not in G")
+
+ warnings.warn(
+ (
+ "\n\nsingle_target_shortest_path_length will return a dict instead of"
+ "\nan iterator in version 3.5"
+ ),
+ FutureWarning,
+ stacklevel=3,
+ )
+
+ if cutoff is None:
+ cutoff = float("inf")
+ # handle either directed or undirected
+ adj = G._pred if G.is_directed() else G._adj
+ nextlevel = [target]
+ # for version 3.3 we will return a dict like this:
+ # return dict(_single_shortest_path_length(adj, nextlevel, cutoff))
+ return _single_shortest_path_length(adj, nextlevel, cutoff)
+
+
+@nx._dispatchable
+def all_pairs_shortest_path_length(G, cutoff=None):
+ """Computes the shortest path lengths between all nodes in `G`.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ cutoff : integer, optional
+ Depth at which to stop the search. Only paths of length at most
+ `cutoff` are returned.
+
+ Returns
+ -------
+ lengths : iterator
+ (source, dictionary) iterator with dictionary keyed by target and
+ shortest path length as the key value.
+
+ Notes
+ -----
+ The iterator returned only has reachable node pairs.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> length = dict(nx.all_pairs_shortest_path_length(G))
+ >>> for node in [0, 1, 2, 3, 4]:
+ ... print(f"1 - {node}: {length[1][node]}")
+ 1 - 0: 1
+ 1 - 1: 0
+ 1 - 2: 1
+ 1 - 3: 2
+ 1 - 4: 3
+ >>> length[3][2]
+ 1
+ >>> length[2][2]
+ 0
+
+ """
+ length = single_source_shortest_path_length
+ # TODO This can be trivially parallelized.
+ for n in G:
+ yield (n, length(G, n, cutoff=cutoff))
+
+
+@nx._dispatchable
+def bidirectional_shortest_path(G, source, target):
+ """Returns a list of nodes in a shortest path between source and target.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node label
+ starting node for path
+
+ target : node label
+ ending node for path
+
+ Returns
+ -------
+ path: list
+ List of nodes in a path from source to target.
+
+ Raises
+ ------
+ NetworkXNoPath
+ If no path exists between source and target.
+
+ Examples
+ --------
+ >>> G = nx.Graph()
+ >>> nx.add_path(G, [0, 1, 2, 3, 0, 4, 5, 6, 7, 4])
+ >>> nx.bidirectional_shortest_path(G, 2, 6)
+ [2, 1, 0, 4, 5, 6]
+
+ See Also
+ --------
+ shortest_path
+
+ Notes
+ -----
+ This algorithm is used by shortest_path(G, source, target).
+ """
+
+ if source not in G:
+ raise nx.NodeNotFound(f"Source {source} is not in G")
+
+ if target not in G:
+ raise nx.NodeNotFound(f"Target {target} is not in G")
+
+ # call helper to do the real work
+ results = _bidirectional_pred_succ(G, source, target)
+ pred, succ, w = results
+
+ # build path from pred+w+succ
+ path = []
+ # from source to w
+ while w is not None:
+ path.append(w)
+ w = pred[w]
+ path.reverse()
+ # from w to target
+ w = succ[path[-1]]
+ while w is not None:
+ path.append(w)
+ w = succ[w]
+
+ return path
+
+
+def _bidirectional_pred_succ(G, source, target):
+ """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 target == source:
+ return ({target: None}, {source: None}, source)
+
+ # handle either directed or undirected
+ if G.is_directed():
+ Gpred = G.pred
+ Gsucc = G.succ
+ else:
+ Gpred = G.adj
+ Gsucc = G.adj
+
+ # 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: # path found
+ 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}.")
+
+
+@nx._dispatchable
+def single_source_shortest_path(G, source, cutoff=None):
+ """Compute shortest path between source
+ and all other nodes reachable from source.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node label
+ Starting node for path
+
+ cutoff : integer, optional
+ Depth to stop the search. Only paths of length <= cutoff are returned.
+
+ Returns
+ -------
+ paths : dictionary
+ Dictionary, keyed by target, of shortest paths.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> path = nx.single_source_shortest_path(G, 0)
+ >>> path[4]
+ [0, 1, 2, 3, 4]
+
+ Notes
+ -----
+ The shortest path is not necessarily unique. So there can be multiple
+ paths between the source and each target node, all of which have the
+ same 'shortest' length. For each target node, this function returns
+ only one of those paths.
+
+ See Also
+ --------
+ shortest_path
+ """
+ if source not in G:
+ raise nx.NodeNotFound(f"Source {source} not in G")
+
+ def join(p1, p2):
+ return p1 + p2
+
+ if cutoff is None:
+ cutoff = float("inf")
+ nextlevel = {source: 1} # list of nodes to check at next level
+ paths = {source: [source]} # paths dictionary (paths to key from source)
+ return dict(_single_shortest_path(G.adj, nextlevel, paths, cutoff, join))
+
+
+def _single_shortest_path(adj, firstlevel, paths, cutoff, join):
+ """Returns shortest paths
+
+ Shortest Path helper function
+ Parameters
+ ----------
+ adj : dict
+ Adjacency dict or view
+ firstlevel : dict
+ starting nodes, e.g. {source: 1} or {target: 1}
+ paths : dict
+ paths for starting nodes, e.g. {source: [source]}
+ cutoff : int or float
+ level at which we stop the process
+ join : function
+ function to construct a path from two partial paths. Requires two
+ list inputs `p1` and `p2`, and returns a list. Usually returns
+ `p1 + p2` (forward from source) or `p2 + p1` (backward from target)
+ """
+ level = 0 # the current level
+ nextlevel = firstlevel
+ while nextlevel and cutoff > level:
+ thislevel = nextlevel
+ nextlevel = {}
+ for v in thislevel:
+ for w in adj[v]:
+ if w not in paths:
+ paths[w] = join(paths[v], [w])
+ nextlevel[w] = 1
+ level += 1
+ return paths
+
+
+@nx._dispatchable
+def single_target_shortest_path(G, target, cutoff=None):
+ """Compute shortest path to target from all nodes that reach target.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ target : node label
+ Target node for path
+
+ cutoff : integer, optional
+ Depth to stop the search. Only paths of length <= cutoff are returned.
+
+ Returns
+ -------
+ paths : dictionary
+ Dictionary, keyed by target, of shortest paths.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5, create_using=nx.DiGraph())
+ >>> path = nx.single_target_shortest_path(G, 4)
+ >>> path[0]
+ [0, 1, 2, 3, 4]
+
+ Notes
+ -----
+ The shortest path is not necessarily unique. So there can be multiple
+ paths between the source and each target node, all of which have the
+ same 'shortest' length. For each target node, this function returns
+ only one of those paths.
+
+ See Also
+ --------
+ shortest_path, single_source_shortest_path
+ """
+ if target not in G:
+ raise nx.NodeNotFound(f"Target {target} not in G")
+
+ def join(p1, p2):
+ return p2 + p1
+
+ # handle undirected graphs
+ adj = G.pred if G.is_directed() else G.adj
+ if cutoff is None:
+ cutoff = float("inf")
+ nextlevel = {target: 1} # list of nodes to check at next level
+ paths = {target: [target]} # paths dictionary (paths to key from source)
+ return dict(_single_shortest_path(adj, nextlevel, paths, cutoff, join))
+
+
+@nx._dispatchable
+def all_pairs_shortest_path(G, cutoff=None):
+ """Compute shortest paths between all nodes.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ cutoff : integer, optional
+ Depth at which to stop the search. Only paths of length at most
+ `cutoff` are returned.
+
+ Returns
+ -------
+ paths : iterator
+ Dictionary, keyed by source and target, of shortest paths.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> path = dict(nx.all_pairs_shortest_path(G))
+ >>> print(path[0][4])
+ [0, 1, 2, 3, 4]
+
+ Notes
+ -----
+ There may be multiple shortest paths with the same length between
+ two nodes. For each pair, this function returns only one of those paths.
+
+ See Also
+ --------
+ floyd_warshall
+ all_pairs_all_shortest_paths
+
+ """
+ # TODO This can be trivially parallelized.
+ for n in G:
+ yield (n, single_source_shortest_path(G, n, cutoff=cutoff))
+
+
+@nx._dispatchable
+def predecessor(G, source, target=None, cutoff=None, return_seen=None):
+ """Returns dict of predecessors for the path from source to all nodes in G.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node label
+ Starting node for path
+
+ target : node label, optional
+ Ending node for path. If provided only predecessors between
+ source and target are returned
+
+ cutoff : integer, optional
+ Depth to stop the search. Only paths of length <= cutoff are returned.
+
+ return_seen : bool, optional (default=None)
+ Whether to return a dictionary, keyed by node, of the level (number of
+ hops) to reach the node (as seen during breadth-first-search).
+
+ Returns
+ -------
+ pred : dictionary
+ Dictionary, keyed by node, of predecessors in the shortest path.
+
+
+ (pred, seen): tuple of dictionaries
+ If `return_seen` argument is set to `True`, then a tuple of dictionaries
+ is returned. The first element is the dictionary, keyed by node, of
+ predecessors in the shortest path. The second element is the dictionary,
+ keyed by node, of the level (number of hops) to reach the node (as seen
+ during breadth-first-search).
+
+ Examples
+ --------
+ >>> G = nx.path_graph(4)
+ >>> list(G)
+ [0, 1, 2, 3]
+ >>> nx.predecessor(G, 0)
+ {0: [], 1: [0], 2: [1], 3: [2]}
+ >>> nx.predecessor(G, 0, return_seen=True)
+ ({0: [], 1: [0], 2: [1], 3: [2]}, {0: 0, 1: 1, 2: 2, 3: 3})
+
+
+ """
+ if source not in G:
+ raise nx.NodeNotFound(f"Source {source} not in G")
+
+ level = 0 # the current level
+ nextlevel = [source] # list of nodes to check at next level
+ seen = {source: level} # level (number of hops) when seen in BFS
+ pred = {source: []} # predecessor dictionary
+ while nextlevel:
+ level = level + 1
+ thislevel = nextlevel
+ nextlevel = []
+ for v in thislevel:
+ for w in G[v]:
+ if w not in seen:
+ pred[w] = [v]
+ seen[w] = level
+ nextlevel.append(w)
+ elif seen[w] == level: # add v to predecessor list if it
+ pred[w].append(v) # is at the correct level
+ if cutoff and cutoff <= level:
+ break
+
+ if target is not None:
+ if return_seen:
+ if target not in pred:
+ return ([], -1) # No predecessor
+ return (pred[target], seen[target])
+ else:
+ if target not in pred:
+ return [] # No predecessor
+ return pred[target]
+ else:
+ if return_seen:
+ return (pred, seen)
+ else:
+ return pred
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/weighted.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/weighted.py
new file mode 100644
index 00000000..f8421d42
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/weighted.py
@@ -0,0 +1,2520 @@
+"""
+Shortest path algorithms for weighted graphs.
+"""
+
+from collections import deque
+from heapq import heappop, heappush
+from itertools import count
+
+import networkx as nx
+from networkx.algorithms.shortest_paths.generic import _build_paths_from_predecessors
+
+__all__ = [
+ "dijkstra_path",
+ "dijkstra_path_length",
+ "bidirectional_dijkstra",
+ "single_source_dijkstra",
+ "single_source_dijkstra_path",
+ "single_source_dijkstra_path_length",
+ "multi_source_dijkstra",
+ "multi_source_dijkstra_path",
+ "multi_source_dijkstra_path_length",
+ "all_pairs_dijkstra",
+ "all_pairs_dijkstra_path",
+ "all_pairs_dijkstra_path_length",
+ "dijkstra_predecessor_and_distance",
+ "bellman_ford_path",
+ "bellman_ford_path_length",
+ "single_source_bellman_ford",
+ "single_source_bellman_ford_path",
+ "single_source_bellman_ford_path_length",
+ "all_pairs_bellman_ford_path",
+ "all_pairs_bellman_ford_path_length",
+ "bellman_ford_predecessor_and_distance",
+ "negative_edge_cycle",
+ "find_negative_cycle",
+ "goldberg_radzik",
+ "johnson",
+]
+
+
+def _weight_function(G, weight):
+ """Returns a function that returns the weight of an edge.
+
+ The returned function is specifically suitable for input to
+ functions :func:`_dijkstra` and :func:`_bellman_ford_relaxation`.
+
+ Parameters
+ ----------
+ G : NetworkX graph.
+
+ weight : string or function
+ If it is callable, `weight` itself is returned. If it is a string,
+ it is assumed to be the name of the edge attribute that represents
+ the weight of an edge. In that case, a function is returned that
+ gets the edge weight according to the specified edge attribute.
+
+ Returns
+ -------
+ function
+ This function returns a callable that accepts exactly three inputs:
+ a node, an node adjacent to the first one, and the edge attribute
+ dictionary for the eedge joining those nodes. That function returns
+ a number representing the weight of an edge.
+
+ If `G` is a multigraph, and `weight` is not callable, the
+ minimum edge weight over all parallel edges is returned. If any edge
+ does not have an attribute with key `weight`, it is assumed to
+ have weight one.
+
+ """
+ if callable(weight):
+ return weight
+ # If the weight keyword argument is not callable, we assume it is a
+ # string representing the edge attribute containing the weight of
+ # the edge.
+ if G.is_multigraph():
+ return lambda u, v, d: min(attr.get(weight, 1) for attr in d.values())
+ return lambda u, v, data: data.get(weight, 1)
+
+
+@nx._dispatchable(edge_attrs="weight")
+def dijkstra_path(G, source, target, weight="weight"):
+ """Returns the shortest weighted path from source to target in G.
+
+ Uses Dijkstra's Method to compute the shortest weighted path
+ between two nodes in a graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node
+ Starting node
+
+ target : node
+ Ending node
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ path : list
+ List of nodes in a shortest path.
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` is not in `G`.
+
+ NetworkXNoPath
+ If no path exists between source and target.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> print(nx.dijkstra_path(G, 0, 4))
+ [0, 1, 2, 3, 4]
+
+ Find edges of shortest path in Multigraph
+
+ >>> G = nx.MultiDiGraph()
+ >>> G.add_weighted_edges_from([(1, 2, 0.75), (1, 2, 0.5), (2, 3, 0.5), (1, 3, 1.5)])
+ >>> nodes = nx.dijkstra_path(G, 1, 3)
+ >>> edges = nx.utils.pairwise(nodes)
+ >>> list(
+ ... (u, v, min(G[u][v], key=lambda k: G[u][v][k].get("weight", 1)))
+ ... for u, v in edges
+ ... )
+ [(1, 2, 1), (2, 3, 0)]
+
+ 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.
+
+ The weight function can be used to include node weights.
+
+ >>> def func(u, v, d):
+ ... node_u_wt = G.nodes[u].get("node_weight", 1)
+ ... node_v_wt = G.nodes[v].get("node_weight", 1)
+ ... edge_wt = d.get("weight", 1)
+ ... return node_u_wt / 2 + node_v_wt / 2 + edge_wt
+
+ In this example we take the average of start and end node
+ weights of an edge and add it to the weight of the edge.
+
+ The function :func:`single_source_dijkstra` computes both
+ path and length-of-path if you need both, use that.
+
+ See Also
+ --------
+ bidirectional_dijkstra
+ bellman_ford_path
+ single_source_dijkstra
+ """
+ (length, path) = single_source_dijkstra(G, source, target=target, weight=weight)
+ return path
+
+
+@nx._dispatchable(edge_attrs="weight")
+def dijkstra_path_length(G, source, target, weight="weight"):
+ """Returns the shortest weighted path length in G from source to target.
+
+ Uses Dijkstra's Method to compute the shortest weighted path length
+ between two nodes in a graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node label
+ starting node for path
+
+ target : node label
+ ending node for path
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ length : number
+ Shortest path length.
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` is not in `G`.
+
+ NetworkXNoPath
+ If no path exists between source and target.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> nx.dijkstra_path_length(G, 0, 4)
+ 4
+
+ 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.
+
+ The function :func:`single_source_dijkstra` computes both
+ path and length-of-path if you need both, use that.
+
+ See Also
+ --------
+ bidirectional_dijkstra
+ bellman_ford_path_length
+ single_source_dijkstra
+
+ """
+ if source not in G:
+ raise nx.NodeNotFound(f"Node {source} not found in graph")
+ if source == target:
+ return 0
+ weight = _weight_function(G, weight)
+ length = _dijkstra(G, source, weight, target=target)
+ try:
+ return length[target]
+ except KeyError as err:
+ raise nx.NetworkXNoPath(f"Node {target} not reachable from {source}") from err
+
+
+@nx._dispatchable(edge_attrs="weight")
+def single_source_dijkstra_path(G, source, cutoff=None, weight="weight"):
+ """Find shortest weighted paths in G from a source node.
+
+ Compute shortest path between source and all other reachable
+ nodes for a weighted graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node
+ Starting node for path.
+
+ cutoff : integer or float, optional
+ Length (sum of edge weights) at which the search is stopped.
+ If cutoff is provided, only return paths with summed weight <= cutoff.
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ paths : dictionary
+ Dictionary of shortest path lengths keyed by target.
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` is not in `G`.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> path = nx.single_source_dijkstra_path(G, 0)
+ >>> path[4]
+ [0, 1, 2, 3, 4]
+
+ 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.
+
+ See Also
+ --------
+ single_source_dijkstra, single_source_bellman_ford
+
+ """
+ return multi_source_dijkstra_path(G, {source}, cutoff=cutoff, weight=weight)
+
+
+@nx._dispatchable(edge_attrs="weight")
+def single_source_dijkstra_path_length(G, source, cutoff=None, weight="weight"):
+ """Find shortest weighted path lengths in G from a source node.
+
+ Compute the shortest path length between source and all other
+ reachable nodes for a weighted graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node label
+ Starting node for path
+
+ cutoff : integer or float, optional
+ Length (sum of edge weights) at which the search is stopped.
+ If cutoff is provided, only return paths with summed weight <= cutoff.
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ length : dict
+ Dict keyed by node to shortest path length from source.
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` is not in `G`.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> length = nx.single_source_dijkstra_path_length(G, 0)
+ >>> length[4]
+ 4
+ >>> for node in [0, 1, 2, 3, 4]:
+ ... print(f"{node}: {length[node]}")
+ 0: 0
+ 1: 1
+ 2: 2
+ 3: 3
+ 4: 4
+
+ 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.
+
+ See Also
+ --------
+ single_source_dijkstra, single_source_bellman_ford_path_length
+
+ """
+ return multi_source_dijkstra_path_length(G, {source}, cutoff=cutoff, weight=weight)
+
+
+@nx._dispatchable(edge_attrs="weight")
+def single_source_dijkstra(G, source, target=None, cutoff=None, weight="weight"):
+ """Find shortest weighted paths and lengths from a source node.
+
+ Compute the shortest path length between source and all other
+ reachable nodes for a weighted graph.
+
+ Uses Dijkstra's algorithm to compute shortest paths and lengths
+ between a source and all other reachable nodes in a weighted graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node label
+ Starting node for path
+
+ target : node label, optional
+ Ending node for path
+
+ cutoff : integer or float, optional
+ Length (sum of edge weights) at which the search is stopped.
+ If cutoff is provided, only return paths with summed weight <= cutoff.
+
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ distance, path : pair of dictionaries, or numeric and list.
+ If target is None, paths and lengths to all nodes are computed.
+ The return value is a tuple of two dictionaries keyed by target nodes.
+ The first dictionary stores distance to each target node.
+ The second stores the path to each target node.
+ If target is not None, returns a tuple (distance, path), where
+ distance is the distance from source to target and path is a list
+ representing the path from source to target.
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` is not in `G`.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> length, path = nx.single_source_dijkstra(G, 0)
+ >>> length[4]
+ 4
+ >>> for node in [0, 1, 2, 3, 4]:
+ ... print(f"{node}: {length[node]}")
+ 0: 0
+ 1: 1
+ 2: 2
+ 3: 3
+ 4: 4
+ >>> path[4]
+ [0, 1, 2, 3, 4]
+ >>> length, path = nx.single_source_dijkstra(G, 0, 1)
+ >>> length
+ 1
+ >>> path
+ [0, 1]
+
+ 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.
+
+ Based on the Python cookbook recipe (119466) at
+ https://code.activestate.com/recipes/119466/
+
+ 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
+ --------
+ single_source_dijkstra_path
+ single_source_dijkstra_path_length
+ single_source_bellman_ford
+ """
+ return multi_source_dijkstra(
+ G, {source}, cutoff=cutoff, target=target, weight=weight
+ )
+
+
+@nx._dispatchable(edge_attrs="weight")
+def multi_source_dijkstra_path(G, sources, cutoff=None, weight="weight"):
+ """Find shortest weighted paths in G from a given set of source
+ nodes.
+
+ Compute shortest path between any of the source nodes and all other
+ reachable nodes for a weighted graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ sources : non-empty set of nodes
+ Starting nodes for paths. If this is just a set containing a
+ single node, then all paths computed by this function will start
+ from that node. If there are two or more nodes in the set, the
+ computed paths may begin from any one of the start nodes.
+
+ cutoff : integer or float, optional
+ Length (sum of edge weights) at which the search is stopped.
+ If cutoff is provided, only return paths with summed weight <= cutoff.
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ paths : dictionary
+ Dictionary of shortest paths keyed by target.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> path = nx.multi_source_dijkstra_path(G, {0, 4})
+ >>> path[1]
+ [0, 1]
+ >>> path[3]
+ [4, 3]
+
+ 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.
+
+ Raises
+ ------
+ ValueError
+ If `sources` is empty.
+ NodeNotFound
+ If any of `sources` is not in `G`.
+
+ See Also
+ --------
+ multi_source_dijkstra, multi_source_bellman_ford
+
+ """
+ length, path = multi_source_dijkstra(G, sources, cutoff=cutoff, weight=weight)
+ return path
+
+
+@nx._dispatchable(edge_attrs="weight")
+def multi_source_dijkstra_path_length(G, sources, cutoff=None, weight="weight"):
+ """Find shortest weighted path lengths in G from a given set of
+ source nodes.
+
+ Compute the shortest path length between any of the source nodes and
+ all other reachable nodes for a weighted graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ sources : non-empty set of nodes
+ Starting nodes for paths. If this is just a set containing a
+ single node, then all paths computed by this function will start
+ from that node. If there are two or more nodes in the set, the
+ computed paths may begin from any one of the start nodes.
+
+ cutoff : integer or float, optional
+ Length (sum of edge weights) at which the search is stopped.
+ If cutoff is provided, only return paths with summed weight <= cutoff.
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ length : dict
+ Dict keyed by node to shortest path length to nearest source.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> length = nx.multi_source_dijkstra_path_length(G, {0, 4})
+ >>> for node in [0, 1, 2, 3, 4]:
+ ... print(f"{node}: {length[node]}")
+ 0: 0
+ 1: 1
+ 2: 2
+ 3: 1
+ 4: 0
+
+ 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.
+
+ Raises
+ ------
+ ValueError
+ If `sources` is empty.
+ NodeNotFound
+ If any of `sources` is not in `G`.
+
+ See Also
+ --------
+ multi_source_dijkstra
+
+ """
+ if not sources:
+ raise ValueError("sources must not be empty")
+ for s in sources:
+ if s not in G:
+ raise nx.NodeNotFound(f"Node {s} not found in graph")
+ weight = _weight_function(G, weight)
+ return _dijkstra_multisource(G, sources, weight, cutoff=cutoff)
+
+
+@nx._dispatchable(edge_attrs="weight")
+def multi_source_dijkstra(G, sources, target=None, cutoff=None, weight="weight"):
+ """Find shortest weighted paths and lengths from a given set of
+ source nodes.
+
+ Uses Dijkstra's algorithm to compute the shortest paths and lengths
+ between one of the source nodes and the given `target`, or all other
+ reachable nodes if not specified, for a weighted graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ sources : non-empty set of nodes
+ Starting nodes for paths. If this is just a set containing a
+ single node, then all paths computed by this function will start
+ from that node. If there are two or more nodes in the set, the
+ computed paths may begin from any one of the start nodes.
+
+ target : node label, optional
+ Ending node for path
+
+ cutoff : integer or float, optional
+ Length (sum of edge weights) at which the search is stopped.
+ If cutoff is provided, only return paths with summed weight <= cutoff.
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ distance, path : pair of dictionaries, or numeric and list
+ If target is None, returns a tuple of two dictionaries keyed by node.
+ The first dictionary stores distance from one of the source nodes.
+ The second stores the path from one of the sources to that node.
+ If target is not None, returns a tuple of (distance, path) where
+ distance is the distance from source to target and path is a list
+ representing the path from source to target.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> length, path = nx.multi_source_dijkstra(G, {0, 4})
+ >>> for node in [0, 1, 2, 3, 4]:
+ ... print(f"{node}: {length[node]}")
+ 0: 0
+ 1: 1
+ 2: 2
+ 3: 1
+ 4: 0
+ >>> path[1]
+ [0, 1]
+ >>> path[3]
+ [4, 3]
+
+ >>> length, path = nx.multi_source_dijkstra(G, {0, 4}, 1)
+ >>> length
+ 1
+ >>> path
+ [0, 1]
+
+ 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.
+
+ Based on the Python cookbook recipe (119466) at
+ https://code.activestate.com/recipes/119466/
+
+ This algorithm is not guaranteed to work if edge weights
+ are negative or are floating point numbers
+ (overflows and roundoff errors can cause problems).
+
+ Raises
+ ------
+ ValueError
+ If `sources` is empty.
+ NodeNotFound
+ If any of `sources` is not in `G`.
+
+ See Also
+ --------
+ multi_source_dijkstra_path
+ multi_source_dijkstra_path_length
+
+ """
+ if not sources:
+ raise ValueError("sources must not be empty")
+ for s in sources:
+ if s not in G:
+ raise nx.NodeNotFound(f"Node {s} not found in graph")
+ if target in sources:
+ return (0, [target])
+ weight = _weight_function(G, weight)
+ paths = {source: [source] for source in sources} # dictionary of paths
+ dist = _dijkstra_multisource(
+ G, sources, weight, paths=paths, cutoff=cutoff, target=target
+ )
+ if target is None:
+ return (dist, paths)
+ try:
+ return (dist[target], paths[target])
+ except KeyError as err:
+ raise nx.NetworkXNoPath(f"No path to {target}.") from err
+
+
+def _dijkstra(G, source, weight, pred=None, paths=None, cutoff=None, target=None):
+ """Uses Dijkstra's algorithm to find shortest weighted paths from a
+ single source.
+
+ This is a convenience function for :func:`_dijkstra_multisource`
+ with all the arguments the same, except the keyword argument
+ `sources` set to ``[source]``.
+
+ """
+ return _dijkstra_multisource(
+ G, [source], weight, pred=pred, paths=paths, cutoff=cutoff, target=target
+ )
+
+
+def _dijkstra_multisource(
+ G, sources, weight, pred=None, paths=None, cutoff=None, target=None
+):
+ """Uses Dijkstra's algorithm to find shortest weighted paths
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ sources : non-empty iterable of nodes
+ Starting nodes for paths. If this is just an iterable containing
+ a single node, then all paths computed by this function will
+ start from that node. If there are two or more nodes in this
+ iterable, the computed paths may begin from any one of the start
+ nodes.
+
+ weight: function
+ Function with (u, v, data) input that returns that edge's weight
+ or None to indicate a hidden edge
+
+ pred: dict of lists, optional(default=None)
+ dict to store a list of predecessors keyed by that node
+ If None, predecessors are not stored.
+
+ paths: dict, optional (default=None)
+ dict to store the path list from source to each node, keyed by node.
+ If None, paths are not stored.
+
+ target : node label, optional
+ Ending node for path. Search is halted when target is found.
+
+ cutoff : integer or float, optional
+ Length (sum of edge weights) at which the search is stopped.
+ If cutoff is provided, only return paths with summed weight <= cutoff.
+
+ Returns
+ -------
+ distance : dictionary
+ A mapping from node to shortest distance to that node from one
+ of the source nodes.
+
+ Raises
+ ------
+ NodeNotFound
+ If any of `sources` is not in `G`.
+
+ Notes
+ -----
+ The optional predecessor and path dictionaries can be accessed by
+ the caller through the original pred and paths objects passed
+ as arguments. No need to explicitly return pred or paths.
+
+ """
+ G_succ = G._adj # For speed-up (and works for both directed and undirected graphs)
+
+ push = heappush
+ pop = heappop
+ dist = {} # dictionary of final distances
+ seen = {}
+ # fringe is heapq with 3-tuples (distance,c,node)
+ # use the count c to avoid comparing nodes (may not be able to)
+ c = count()
+ fringe = []
+ for source in sources:
+ seen[source] = 0
+ push(fringe, (0, next(c), source))
+ while fringe:
+ (d, _, v) = pop(fringe)
+ if v in dist:
+ continue # already searched this node.
+ dist[v] = d
+ if v == target:
+ break
+ for u, e in G_succ[v].items():
+ cost = weight(v, u, e)
+ if cost is None:
+ continue
+ vu_dist = dist[v] + cost
+ if cutoff is not None:
+ if vu_dist > cutoff:
+ continue
+ if u in dist:
+ u_dist = dist[u]
+ if vu_dist < u_dist:
+ raise ValueError("Contradictory paths found:", "negative weights?")
+ elif pred is not None and vu_dist == u_dist:
+ pred[u].append(v)
+ elif u not in seen or vu_dist < seen[u]:
+ seen[u] = vu_dist
+ push(fringe, (vu_dist, next(c), u))
+ if paths is not None:
+ paths[u] = paths[v] + [u]
+ if pred is not None:
+ pred[u] = [v]
+ elif vu_dist == seen[u]:
+ if pred is not None:
+ pred[u].append(v)
+
+ # The optional predecessor and path dictionaries can be accessed
+ # by the caller via the pred and paths objects passed as arguments.
+ return dist
+
+
+@nx._dispatchable(edge_attrs="weight")
+def dijkstra_predecessor_and_distance(G, source, cutoff=None, weight="weight"):
+ """Compute weighted shortest path length and predecessors.
+
+ Uses Dijkstra's Method to obtain the shortest weighted paths
+ and return dictionaries of predecessors for each node and
+ distance for each node from the `source`.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node label
+ Starting node for path
+
+ cutoff : integer or float, optional
+ Length (sum of edge weights) at which the search is stopped.
+ If cutoff is provided, only return paths with summed weight <= cutoff.
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ pred, distance : dictionaries
+ Returns two dictionaries representing a list of predecessors
+ of a node and the distance to each node.
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` is not in `G`.
+
+ Notes
+ -----
+ Edge weight attributes must be numerical.
+ Distances are calculated as sums of weighted edges traversed.
+
+ The list of predecessors contains more than one element only when
+ there are more than one shortest paths to the key node.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5, create_using=nx.DiGraph())
+ >>> pred, dist = nx.dijkstra_predecessor_and_distance(G, 0)
+ >>> sorted(pred.items())
+ [(0, []), (1, [0]), (2, [1]), (3, [2]), (4, [3])]
+ >>> sorted(dist.items())
+ [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
+
+ >>> pred, dist = nx.dijkstra_predecessor_and_distance(G, 0, 1)
+ >>> sorted(pred.items())
+ [(0, []), (1, [0])]
+ >>> sorted(dist.items())
+ [(0, 0), (1, 1)]
+ """
+ if source not in G:
+ raise nx.NodeNotFound(f"Node {source} is not found in the graph")
+ weight = _weight_function(G, weight)
+ pred = {source: []} # dictionary of predecessors
+ return (pred, _dijkstra(G, source, weight, pred=pred, cutoff=cutoff))
+
+
+@nx._dispatchable(edge_attrs="weight")
+def all_pairs_dijkstra(G, cutoff=None, weight="weight"):
+ """Find shortest weighted paths and lengths between all nodes.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ cutoff : integer or float, optional
+ Length (sum of edge weights) at which the search is stopped.
+ If cutoff is provided, only return paths with summed weight <= cutoff.
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edge[u][v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Yields
+ ------
+ (node, (distance, path)) : (node obj, (dict, dict))
+ Each source node has two associated dicts. The first holds distance
+ keyed by target and the second holds paths keyed by target.
+ (See single_source_dijkstra for the source/target node terminology.)
+ If desired you can apply `dict()` to this function to create a dict
+ keyed by source node to the two dicts.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> len_path = dict(nx.all_pairs_dijkstra(G))
+ >>> len_path[3][0][1]
+ 2
+ >>> for node in [0, 1, 2, 3, 4]:
+ ... print(f"3 - {node}: {len_path[3][0][node]}")
+ 3 - 0: 3
+ 3 - 1: 2
+ 3 - 2: 1
+ 3 - 3: 0
+ 3 - 4: 1
+ >>> len_path[3][1][1]
+ [3, 2, 1]
+ >>> for n, (dist, path) in nx.all_pairs_dijkstra(G):
+ ... print(path[1])
+ [0, 1]
+ [1]
+ [2, 1]
+ [3, 2, 1]
+ [4, 3, 2, 1]
+
+ Notes
+ -----
+ Edge weight attributes must be numerical.
+ Distances are calculated as sums of weighted edges traversed.
+
+ The yielded dicts only have keys for reachable nodes.
+ """
+ for n in G:
+ dist, path = single_source_dijkstra(G, n, cutoff=cutoff, weight=weight)
+ yield (n, (dist, path))
+
+
+@nx._dispatchable(edge_attrs="weight")
+def all_pairs_dijkstra_path_length(G, cutoff=None, weight="weight"):
+ """Compute shortest path lengths between all nodes in a weighted graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ cutoff : integer or float, optional
+ Length (sum of edge weights) at which the search is stopped.
+ If cutoff is provided, only return paths with summed weight <= cutoff.
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ distance : iterator
+ (source, dictionary) iterator with dictionary keyed by target and
+ shortest path length as the key value.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> length = dict(nx.all_pairs_dijkstra_path_length(G))
+ >>> for node in [0, 1, 2, 3, 4]:
+ ... print(f"1 - {node}: {length[1][node]}")
+ 1 - 0: 1
+ 1 - 1: 0
+ 1 - 2: 1
+ 1 - 3: 2
+ 1 - 4: 3
+ >>> length[3][2]
+ 1
+ >>> length[2][2]
+ 0
+
+ Notes
+ -----
+ Edge weight attributes must be numerical.
+ Distances are calculated as sums of weighted edges traversed.
+
+ The dictionary returned only has keys for reachable node pairs.
+ """
+ length = single_source_dijkstra_path_length
+ for n in G:
+ yield (n, length(G, n, cutoff=cutoff, weight=weight))
+
+
+@nx._dispatchable(edge_attrs="weight")
+def all_pairs_dijkstra_path(G, cutoff=None, weight="weight"):
+ """Compute shortest paths between all nodes in a weighted graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ cutoff : integer or float, optional
+ Length (sum of edge weights) at which the search is stopped.
+ If cutoff is provided, only return paths with summed weight <= cutoff.
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ paths : iterator
+ (source, dictionary) iterator with dictionary keyed by target and
+ shortest path as the key value.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> path = dict(nx.all_pairs_dijkstra_path(G))
+ >>> path[0][4]
+ [0, 1, 2, 3, 4]
+
+ Notes
+ -----
+ Edge weight attributes must be numerical.
+ Distances are calculated as sums of weighted edges traversed.
+
+ See Also
+ --------
+ floyd_warshall, all_pairs_bellman_ford_path
+
+ """
+ path = single_source_dijkstra_path
+ # TODO This can be trivially parallelized.
+ for n in G:
+ yield (n, path(G, n, cutoff=cutoff, weight=weight))
+
+
+@nx._dispatchable(edge_attrs="weight")
+def bellman_ford_predecessor_and_distance(
+ G, source, target=None, weight="weight", heuristic=False
+):
+ """Compute shortest path lengths and predecessors on shortest paths
+ in weighted graphs.
+
+ The algorithm has a running time of $O(mn)$ where $n$ is the number of
+ nodes and $m$ is the number of edges. It is slower than Dijkstra but
+ can handle negative edge weights.
+
+ If a negative cycle is detected, you can use :func:`find_negative_cycle`
+ to return the cycle and examine it. Shortest paths are not defined when
+ a negative cycle exists because once reached, the path can cycle forever
+ to build up arbitrarily low weights.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+ The algorithm works for all types of graphs, including directed
+ graphs and multigraphs.
+
+ source: node label
+ Starting node for path
+
+ target : node label, optional
+ Ending node for path
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ heuristic : bool
+ Determines whether to use a heuristic to early detect negative
+ cycles at a hopefully negligible cost.
+
+ Returns
+ -------
+ pred, dist : dictionaries
+ Returns two dictionaries keyed by node to predecessor in the
+ path and to the distance from the source respectively.
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` is not in `G`.
+
+ NetworkXUnbounded
+ If the (di)graph contains a negative (di)cycle, the
+ algorithm raises an exception to indicate the presence of the
+ negative (di)cycle. Note: any negative weight edge in an
+ undirected graph is a negative cycle.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5, create_using=nx.DiGraph())
+ >>> pred, dist = nx.bellman_ford_predecessor_and_distance(G, 0)
+ >>> sorted(pred.items())
+ [(0, []), (1, [0]), (2, [1]), (3, [2]), (4, [3])]
+ >>> sorted(dist.items())
+ [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
+
+ >>> pred, dist = nx.bellman_ford_predecessor_and_distance(G, 0, 1)
+ >>> sorted(pred.items())
+ [(0, []), (1, [0]), (2, [1]), (3, [2]), (4, [3])]
+ >>> sorted(dist.items())
+ [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
+
+ >>> G = nx.cycle_graph(5, create_using=nx.DiGraph())
+ >>> G[1][2]["weight"] = -7
+ >>> nx.bellman_ford_predecessor_and_distance(G, 0)
+ Traceback (most recent call last):
+ ...
+ networkx.exception.NetworkXUnbounded: Negative cycle detected.
+
+ See Also
+ --------
+ find_negative_cycle
+
+ Notes
+ -----
+ Edge weight attributes must be numerical.
+ Distances are calculated as sums of weighted edges traversed.
+
+ The dictionaries returned only have keys for nodes reachable from
+ the source.
+
+ In the case where the (di)graph is not connected, if a component
+ not containing the source contains a negative (di)cycle, it
+ will not be detected.
+
+ In NetworkX v2.1 and prior, the source node had predecessor `[None]`.
+ In NetworkX v2.2 this changed to the source node having predecessor `[]`
+ """
+ if source not in G:
+ raise nx.NodeNotFound(f"Node {source} is not found in the graph")
+ weight = _weight_function(G, weight)
+ if G.is_multigraph():
+ if any(
+ weight(u, v, {k: d}) < 0
+ for u, v, k, d in nx.selfloop_edges(G, keys=True, data=True)
+ ):
+ raise nx.NetworkXUnbounded("Negative cycle detected.")
+ else:
+ if any(weight(u, v, d) < 0 for u, v, d in nx.selfloop_edges(G, data=True)):
+ raise nx.NetworkXUnbounded("Negative cycle detected.")
+
+ dist = {source: 0}
+ pred = {source: []}
+
+ if len(G) == 1:
+ return pred, dist
+
+ weight = _weight_function(G, weight)
+
+ dist = _bellman_ford(
+ G, [source], weight, pred=pred, dist=dist, target=target, heuristic=heuristic
+ )
+ return (pred, dist)
+
+
+def _bellman_ford(
+ G,
+ source,
+ weight,
+ pred=None,
+ paths=None,
+ dist=None,
+ target=None,
+ heuristic=True,
+):
+ """Calls relaxation loop for Bellman–Ford algorithm and builds paths
+
+ This is an implementation of the SPFA variant.
+ See https://en.wikipedia.org/wiki/Shortest_Path_Faster_Algorithm
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source: list
+ List of source nodes. The shortest path from any of the source
+ nodes will be found if multiple sources are provided.
+
+ weight : 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.
+
+ pred: dict of lists, optional (default=None)
+ dict to store a list of predecessors keyed by that node
+ If None, predecessors are not stored
+
+ paths: dict, optional (default=None)
+ dict to store the path list from source to each node, keyed by node
+ If None, paths are not stored
+
+ dist: dict, optional (default=None)
+ dict to store distance from source to the keyed node
+ If None, returned dist dict contents default to 0 for every node in the
+ source list
+
+ target: node label, optional
+ Ending node for path. Path lengths to other destinations may (and
+ probably will) be incorrect.
+
+ heuristic : bool
+ Determines whether to use a heuristic to early detect negative
+ cycles at a hopefully negligible cost.
+
+ Returns
+ -------
+ dist : dict
+ Returns a dict keyed by node to the distance from the source.
+ Dicts for paths and pred are in the mutated input dicts by those names.
+
+ Raises
+ ------
+ NodeNotFound
+ If any of `source` is not in `G`.
+
+ NetworkXUnbounded
+ If the (di)graph contains a negative (di)cycle, the
+ algorithm raises an exception to indicate the presence of the
+ negative (di)cycle. Note: any negative weight edge in an
+ undirected graph is a negative cycle
+ """
+ if pred is None:
+ pred = {v: [] for v in source}
+
+ if dist is None:
+ dist = {v: 0 for v in source}
+
+ negative_cycle_found = _inner_bellman_ford(
+ G,
+ source,
+ weight,
+ pred,
+ dist,
+ heuristic,
+ )
+ if negative_cycle_found is not None:
+ raise nx.NetworkXUnbounded("Negative cycle detected.")
+
+ if paths is not None:
+ sources = set(source)
+ dsts = [target] if target is not None else pred
+ for dst in dsts:
+ gen = _build_paths_from_predecessors(sources, dst, pred)
+ paths[dst] = next(gen)
+
+ return dist
+
+
+def _inner_bellman_ford(
+ G,
+ sources,
+ weight,
+ pred,
+ dist=None,
+ heuristic=True,
+):
+ """Inner Relaxation loop for Bellman–Ford algorithm.
+
+ This is an implementation of the SPFA variant.
+ See https://en.wikipedia.org/wiki/Shortest_Path_Faster_Algorithm
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source: list
+ List of source nodes. The shortest path from any of the source
+ nodes will be found if multiple sources are provided.
+
+ weight : 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.
+
+ pred: dict of lists
+ dict to store a list of predecessors keyed by that node
+
+ dist: dict, optional (default=None)
+ dict to store distance from source to the keyed node
+ If None, returned dist dict contents default to 0 for every node in the
+ source list
+
+ heuristic : bool
+ Determines whether to use a heuristic to early detect negative
+ cycles at a hopefully negligible cost.
+
+ Returns
+ -------
+ node or None
+ Return a node `v` where processing discovered a negative cycle.
+ If no negative cycle found, return None.
+
+ Raises
+ ------
+ NodeNotFound
+ If any of `source` is not in `G`.
+ """
+ for s in sources:
+ if s not in G:
+ raise nx.NodeNotFound(f"Source {s} not in G")
+
+ if pred is None:
+ pred = {v: [] for v in sources}
+
+ if dist is None:
+ dist = {v: 0 for v in sources}
+
+ # Heuristic Storage setup. Note: use None because nodes cannot be None
+ nonexistent_edge = (None, None)
+ pred_edge = {v: None for v in sources}
+ recent_update = {v: nonexistent_edge for v in sources}
+
+ G_succ = G._adj # For speed-up (and works for both directed and undirected graphs)
+ inf = float("inf")
+ n = len(G)
+
+ count = {}
+ q = deque(sources)
+ in_q = set(sources)
+ while q:
+ u = q.popleft()
+ in_q.remove(u)
+
+ # Skip relaxations if any of the predecessors of u is in the queue.
+ if all(pred_u not in in_q for pred_u in pred[u]):
+ dist_u = dist[u]
+ for v, e in G_succ[u].items():
+ dist_v = dist_u + weight(u, v, e)
+
+ if dist_v < dist.get(v, inf):
+ # In this conditional branch we are updating the path with v.
+ # If it happens that some earlier update also added node v
+ # that implies the existence of a negative cycle since
+ # after the update node v would lie on the update path twice.
+ # The update path is stored up to one of the source nodes,
+ # therefore u is always in the dict recent_update
+ if heuristic:
+ if v in recent_update[u]:
+ # Negative cycle found!
+ pred[v].append(u)
+ return v
+
+ # Transfer the recent update info from u to v if the
+ # same source node is the head of the update path.
+ # If the source node is responsible for the cost update,
+ # then clear the history and use it instead.
+ if v in pred_edge and pred_edge[v] == u:
+ recent_update[v] = recent_update[u]
+ else:
+ recent_update[v] = (u, v)
+
+ if v not in in_q:
+ q.append(v)
+ in_q.add(v)
+ count_v = count.get(v, 0) + 1
+ if count_v == n:
+ # Negative cycle found!
+ return v
+
+ count[v] = count_v
+ dist[v] = dist_v
+ pred[v] = [u]
+ pred_edge[v] = u
+
+ elif dist.get(v) is not None and dist_v == dist.get(v):
+ pred[v].append(u)
+
+ # successfully found shortest_path. No negative cycles found.
+ return None
+
+
+@nx._dispatchable(edge_attrs="weight")
+def bellman_ford_path(G, source, target, weight="weight"):
+ """Returns the shortest path from source to target in a weighted graph G.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node
+ Starting node
+
+ target : node
+ Ending node
+
+ weight : string or function (default="weight")
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ path : list
+ List of nodes in a shortest path.
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` is not in `G`.
+
+ NetworkXNoPath
+ If no path exists between source and target.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> nx.bellman_ford_path(G, 0, 4)
+ [0, 1, 2, 3, 4]
+
+ Notes
+ -----
+ Edge weight attributes must be numerical.
+ Distances are calculated as sums of weighted edges traversed.
+
+ See Also
+ --------
+ dijkstra_path, bellman_ford_path_length
+ """
+ length, path = single_source_bellman_ford(G, source, target=target, weight=weight)
+ return path
+
+
+@nx._dispatchable(edge_attrs="weight")
+def bellman_ford_path_length(G, source, target, weight="weight"):
+ """Returns the shortest path length from source to target
+ in a weighted graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node label
+ starting node for path
+
+ target : node label
+ ending node for path
+
+ weight : string or function (default="weight")
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ length : number
+ Shortest path length.
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` is not in `G`.
+
+ NetworkXNoPath
+ If no path exists between source and target.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> nx.bellman_ford_path_length(G, 0, 4)
+ 4
+
+ Notes
+ -----
+ Edge weight attributes must be numerical.
+ Distances are calculated as sums of weighted edges traversed.
+
+ See Also
+ --------
+ dijkstra_path_length, bellman_ford_path
+ """
+ if source == target:
+ if source not in G:
+ raise nx.NodeNotFound(f"Node {source} not found in graph")
+ return 0
+
+ weight = _weight_function(G, weight)
+
+ length = _bellman_ford(G, [source], weight, target=target)
+
+ try:
+ return length[target]
+ except KeyError as err:
+ raise nx.NetworkXNoPath(f"node {target} not reachable from {source}") from err
+
+
+@nx._dispatchable(edge_attrs="weight")
+def single_source_bellman_ford_path(G, source, weight="weight"):
+ """Compute shortest path between source and all other reachable
+ nodes for a weighted graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node
+ Starting node for path.
+
+ weight : string or function (default="weight")
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ paths : dictionary
+ Dictionary of shortest path lengths keyed by target.
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` is not in `G`.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> path = nx.single_source_bellman_ford_path(G, 0)
+ >>> path[4]
+ [0, 1, 2, 3, 4]
+
+ Notes
+ -----
+ Edge weight attributes must be numerical.
+ Distances are calculated as sums of weighted edges traversed.
+
+ See Also
+ --------
+ single_source_dijkstra, single_source_bellman_ford
+
+ """
+ (length, path) = single_source_bellman_ford(G, source, weight=weight)
+ return path
+
+
+@nx._dispatchable(edge_attrs="weight")
+def single_source_bellman_ford_path_length(G, source, weight="weight"):
+ """Compute the shortest path length between source and all other
+ reachable nodes for a weighted graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node label
+ Starting node for path
+
+ weight : string or function (default="weight")
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ length : dictionary
+ Dictionary of shortest path length keyed by target
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` is not in `G`.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> length = nx.single_source_bellman_ford_path_length(G, 0)
+ >>> length[4]
+ 4
+ >>> for node in [0, 1, 2, 3, 4]:
+ ... print(f"{node}: {length[node]}")
+ 0: 0
+ 1: 1
+ 2: 2
+ 3: 3
+ 4: 4
+
+ Notes
+ -----
+ Edge weight attributes must be numerical.
+ Distances are calculated as sums of weighted edges traversed.
+
+ See Also
+ --------
+ single_source_dijkstra, single_source_bellman_ford
+
+ """
+ weight = _weight_function(G, weight)
+ return _bellman_ford(G, [source], weight)
+
+
+@nx._dispatchable(edge_attrs="weight")
+def single_source_bellman_ford(G, source, target=None, weight="weight"):
+ """Compute shortest paths and lengths in a weighted graph G.
+
+ Uses Bellman-Ford algorithm for shortest paths.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node label
+ Starting node for path
+
+ target : node label, optional
+ Ending node for path
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ distance, path : pair of dictionaries, or numeric and list
+ If target is None, returns a tuple of two dictionaries keyed by node.
+ The first dictionary stores distance from one of the source nodes.
+ The second stores the path from one of the sources to that node.
+ If target is not None, returns a tuple of (distance, path) where
+ distance is the distance from source to target and path is a list
+ representing the path from source to target.
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` is not in `G`.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> length, path = nx.single_source_bellman_ford(G, 0)
+ >>> length[4]
+ 4
+ >>> for node in [0, 1, 2, 3, 4]:
+ ... print(f"{node}: {length[node]}")
+ 0: 0
+ 1: 1
+ 2: 2
+ 3: 3
+ 4: 4
+ >>> path[4]
+ [0, 1, 2, 3, 4]
+ >>> length, path = nx.single_source_bellman_ford(G, 0, 1)
+ >>> length
+ 1
+ >>> path
+ [0, 1]
+
+ Notes
+ -----
+ Edge weight attributes must be numerical.
+ Distances are calculated as sums of weighted edges traversed.
+
+ See Also
+ --------
+ single_source_dijkstra
+ single_source_bellman_ford_path
+ single_source_bellman_ford_path_length
+ """
+ if source == target:
+ if source not in G:
+ raise nx.NodeNotFound(f"Node {source} is not found in the graph")
+ return (0, [source])
+
+ weight = _weight_function(G, weight)
+
+ paths = {source: [source]} # dictionary of paths
+ dist = _bellman_ford(G, [source], weight, paths=paths, target=target)
+ if target is None:
+ return (dist, paths)
+ try:
+ return (dist[target], paths[target])
+ except KeyError as err:
+ msg = f"Node {target} not reachable from {source}"
+ raise nx.NetworkXNoPath(msg) from err
+
+
+@nx._dispatchable(edge_attrs="weight")
+def all_pairs_bellman_ford_path_length(G, weight="weight"):
+ """Compute shortest path lengths between all nodes in a weighted graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ weight : string or function (default="weight")
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ distance : iterator
+ (source, dictionary) iterator with dictionary keyed by target and
+ shortest path length as the key value.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> length = dict(nx.all_pairs_bellman_ford_path_length(G))
+ >>> for node in [0, 1, 2, 3, 4]:
+ ... print(f"1 - {node}: {length[1][node]}")
+ 1 - 0: 1
+ 1 - 1: 0
+ 1 - 2: 1
+ 1 - 3: 2
+ 1 - 4: 3
+ >>> length[3][2]
+ 1
+ >>> length[2][2]
+ 0
+
+ Notes
+ -----
+ Edge weight attributes must be numerical.
+ Distances are calculated as sums of weighted edges traversed.
+
+ The dictionary returned only has keys for reachable node pairs.
+ """
+ length = single_source_bellman_ford_path_length
+ for n in G:
+ yield (n, dict(length(G, n, weight=weight)))
+
+
+@nx._dispatchable(edge_attrs="weight")
+def all_pairs_bellman_ford_path(G, weight="weight"):
+ """Compute shortest paths between all nodes in a weighted graph.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ weight : string or function (default="weight")
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ paths : iterator
+ (source, dictionary) iterator with dictionary keyed by target and
+ shortest path as the key value.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> path = dict(nx.all_pairs_bellman_ford_path(G))
+ >>> path[0][4]
+ [0, 1, 2, 3, 4]
+
+ Notes
+ -----
+ Edge weight attributes must be numerical.
+ Distances are calculated as sums of weighted edges traversed.
+
+ See Also
+ --------
+ floyd_warshall, all_pairs_dijkstra_path
+
+ """
+ path = single_source_bellman_ford_path
+ for n in G:
+ yield (n, path(G, n, weight=weight))
+
+
+@nx._dispatchable(edge_attrs="weight")
+def goldberg_radzik(G, source, weight="weight"):
+ """Compute shortest path lengths and predecessors on shortest paths
+ in weighted graphs.
+
+ The algorithm has a running time of $O(mn)$ where $n$ is the number of
+ nodes and $m$ is the number of edges. It is slower than Dijkstra but
+ can handle negative edge weights.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+ The algorithm works for all types of graphs, including directed
+ graphs and multigraphs.
+
+ source: node label
+ Starting node for path
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ pred, dist : dictionaries
+ Returns two dictionaries keyed by node to predecessor in the
+ path and to the distance from the source respectively.
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` is not in `G`.
+
+ NetworkXUnbounded
+ If the (di)graph contains a negative (di)cycle, the
+ algorithm raises an exception to indicate the presence of the
+ negative (di)cycle. Note: any negative weight edge in an
+ undirected graph is a negative cycle.
+
+ As of NetworkX v3.2, a zero weight cycle is no longer
+ incorrectly reported as a negative weight cycle.
+
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5, create_using=nx.DiGraph())
+ >>> pred, dist = nx.goldberg_radzik(G, 0)
+ >>> sorted(pred.items())
+ [(0, None), (1, 0), (2, 1), (3, 2), (4, 3)]
+ >>> sorted(dist.items())
+ [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
+
+ >>> G = nx.cycle_graph(5, create_using=nx.DiGraph())
+ >>> G[1][2]["weight"] = -7
+ >>> nx.goldberg_radzik(G, 0)
+ Traceback (most recent call last):
+ ...
+ networkx.exception.NetworkXUnbounded: Negative cycle detected.
+
+ Notes
+ -----
+ Edge weight attributes must be numerical.
+ Distances are calculated as sums of weighted edges traversed.
+
+ The dictionaries returned only have keys for nodes reachable from
+ the source.
+
+ In the case where the (di)graph is not connected, if a component
+ not containing the source contains a negative (di)cycle, it
+ will not be detected.
+
+ """
+ if source not in G:
+ raise nx.NodeNotFound(f"Node {source} is not found in the graph")
+ weight = _weight_function(G, weight)
+ if G.is_multigraph():
+ if any(
+ weight(u, v, {k: d}) < 0
+ for u, v, k, d in nx.selfloop_edges(G, keys=True, data=True)
+ ):
+ raise nx.NetworkXUnbounded("Negative cycle detected.")
+ else:
+ if any(weight(u, v, d) < 0 for u, v, d in nx.selfloop_edges(G, data=True)):
+ raise nx.NetworkXUnbounded("Negative cycle detected.")
+
+ if len(G) == 1:
+ return {source: None}, {source: 0}
+
+ G_succ = G._adj # For speed-up (and works for both directed and undirected graphs)
+
+ inf = float("inf")
+ d = {u: inf for u in G}
+ d[source] = 0
+ pred = {source: None}
+
+ def topo_sort(relabeled):
+ """Topologically sort nodes relabeled in the previous round and detect
+ negative cycles.
+ """
+ # List of nodes to scan in this round. Denoted by A in Goldberg and
+ # Radzik's paper.
+ to_scan = []
+ # In the DFS in the loop below, neg_count records for each node the
+ # number of edges of negative reduced costs on the path from a DFS root
+ # to the node in the DFS forest. The reduced cost of an edge (u, v) is
+ # defined as d[u] + weight[u][v] - d[v].
+ #
+ # neg_count also doubles as the DFS visit marker array.
+ neg_count = {}
+ for u in relabeled:
+ # Skip visited nodes.
+ if u in neg_count:
+ continue
+ d_u = d[u]
+ # Skip nodes without out-edges of negative reduced costs.
+ if all(d_u + weight(u, v, e) >= d[v] for v, e in G_succ[u].items()):
+ continue
+ # Nonrecursive DFS that inserts nodes reachable from u via edges of
+ # nonpositive reduced costs into to_scan in (reverse) topological
+ # order.
+ stack = [(u, iter(G_succ[u].items()))]
+ in_stack = {u}
+ neg_count[u] = 0
+ while stack:
+ u, it = stack[-1]
+ try:
+ v, e = next(it)
+ except StopIteration:
+ to_scan.append(u)
+ stack.pop()
+ in_stack.remove(u)
+ continue
+ t = d[u] + weight(u, v, e)
+ d_v = d[v]
+ if t < d_v:
+ is_neg = t < d_v
+ d[v] = t
+ pred[v] = u
+ if v not in neg_count:
+ neg_count[v] = neg_count[u] + int(is_neg)
+ stack.append((v, iter(G_succ[v].items())))
+ in_stack.add(v)
+ elif v in in_stack and neg_count[u] + int(is_neg) > neg_count[v]:
+ # (u, v) is a back edge, and the cycle formed by the
+ # path v to u and (u, v) contains at least one edge of
+ # negative reduced cost. The cycle must be of negative
+ # cost.
+ raise nx.NetworkXUnbounded("Negative cycle detected.")
+ to_scan.reverse()
+ return to_scan
+
+ def relax(to_scan):
+ """Relax out-edges of relabeled nodes."""
+ relabeled = set()
+ # Scan nodes in to_scan in topological order and relax incident
+ # out-edges. Add the relabled nodes to labeled.
+ for u in to_scan:
+ d_u = d[u]
+ for v, e in G_succ[u].items():
+ w_e = weight(u, v, e)
+ if d_u + w_e < d[v]:
+ d[v] = d_u + w_e
+ pred[v] = u
+ relabeled.add(v)
+ return relabeled
+
+ # Set of nodes relabled in the last round of scan operations. Denoted by B
+ # in Goldberg and Radzik's paper.
+ relabeled = {source}
+
+ while relabeled:
+ to_scan = topo_sort(relabeled)
+ relabeled = relax(to_scan)
+
+ d = {u: d[u] for u in pred}
+ return pred, d
+
+
+@nx._dispatchable(edge_attrs="weight")
+def negative_edge_cycle(G, weight="weight", heuristic=True):
+ """Returns True if there exists a negative edge cycle anywhere in G.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ heuristic : bool
+ Determines whether to use a heuristic to early detect negative
+ cycles at a negligible cost. In case of graphs with a negative cycle,
+ the performance of detection increases by at least an order of magnitude.
+
+ Returns
+ -------
+ negative_cycle : bool
+ True if a negative edge cycle exists, otherwise False.
+
+ Examples
+ --------
+ >>> G = nx.cycle_graph(5, create_using=nx.DiGraph())
+ >>> print(nx.negative_edge_cycle(G))
+ False
+ >>> G[1][2]["weight"] = -7
+ >>> print(nx.negative_edge_cycle(G))
+ True
+
+ Notes
+ -----
+ Edge weight attributes must be numerical.
+ Distances are calculated as sums of weighted edges traversed.
+
+ This algorithm uses bellman_ford_predecessor_and_distance() but finds
+ negative cycles on any component by first adding a new node connected to
+ every node, and starting bellman_ford_predecessor_and_distance on that
+ node. It then removes that extra node.
+ """
+ if G.size() == 0:
+ return False
+
+ # find unused node to use temporarily
+ newnode = -1
+ while newnode in G:
+ newnode -= 1
+ # connect it to all nodes
+ G.add_edges_from([(newnode, n) for n in G])
+
+ try:
+ bellman_ford_predecessor_and_distance(
+ G, newnode, weight=weight, heuristic=heuristic
+ )
+ except nx.NetworkXUnbounded:
+ return True
+ finally:
+ G.remove_node(newnode)
+ return False
+
+
+@nx._dispatchable(edge_attrs="weight")
+def find_negative_cycle(G, source, weight="weight"):
+ """Returns a cycle with negative total weight if it exists.
+
+ Bellman-Ford is used to find shortest_paths. That algorithm
+ stops if there exists a negative cycle. This algorithm
+ picks up from there and returns the found negative cycle.
+
+ The cycle consists of a list of nodes in the cycle order. The last
+ node equals the first to make it a cycle.
+ You can look up the edge weights in the original graph. In the case
+ of multigraphs the relevant edge is the minimal weight edge between
+ the nodes in the 2-tuple.
+
+ If the graph has no negative cycle, a NetworkXError is raised.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source: node label
+ The search for the negative cycle will start from this node.
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Examples
+ --------
+ >>> G = nx.DiGraph()
+ >>> G.add_weighted_edges_from(
+ ... [(0, 1, 2), (1, 2, 2), (2, 0, 1), (1, 4, 2), (4, 0, -5)]
+ ... )
+ >>> nx.find_negative_cycle(G, 0)
+ [4, 0, 1, 4]
+
+ Returns
+ -------
+ cycle : list
+ A list of nodes in the order of the cycle found. The last node
+ equals the first to indicate a cycle.
+
+ Raises
+ ------
+ NetworkXError
+ If no negative cycle is found.
+ """
+ weight = _weight_function(G, weight)
+ pred = {source: []}
+
+ v = _inner_bellman_ford(G, [source], weight, pred=pred)
+ if v is None:
+ raise nx.NetworkXError("No negative cycles detected.")
+
+ # negative cycle detected... find it
+ neg_cycle = []
+ stack = [(v, list(pred[v]))]
+ seen = {v}
+ while stack:
+ node, preds = stack[-1]
+ if v in preds:
+ # found the cycle
+ neg_cycle.extend([node, v])
+ neg_cycle = list(reversed(neg_cycle))
+ return neg_cycle
+
+ if preds:
+ nbr = preds.pop()
+ if nbr not in seen:
+ stack.append((nbr, list(pred[nbr])))
+ neg_cycle.append(node)
+ seen.add(nbr)
+ else:
+ stack.pop()
+ if neg_cycle:
+ neg_cycle.pop()
+ else:
+ if v in G[v] and weight(G, v, v) < 0:
+ return [v, v]
+ # should not reach here
+ raise nx.NetworkXError("Negative cycle is detected but not found")
+ # should not get here...
+ msg = "negative cycle detected but not identified"
+ raise nx.NetworkXUnbounded(msg)
+
+
+@nx._dispatchable(edge_attrs="weight")
+def bidirectional_dijkstra(G, source, target, weight="weight"):
+ r"""Dijkstra's algorithm for shortest paths using bidirectional search.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : node
+ Starting node.
+
+ target : node
+ Ending node.
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ length, path : number and list
+ length is the distance from source to target.
+ path is a list of nodes on a path from source to target.
+
+ Raises
+ ------
+ NodeNotFound
+ If `source` or `target` is not in `G`.
+
+ NetworkXNoPath
+ If no path exists between source and target.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(5)
+ >>> length, path = nx.bidirectional_dijkstra(G, 0, 4)
+ >>> print(length)
+ 4
+ >>> print(path)
+ [0, 1, 2, 3, 4]
+
+ 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 source not in G:
+ raise nx.NodeNotFound(f"Source {source} is not in G")
+
+ if target not in G:
+ raise nx.NodeNotFound(f"Target {target} is not in G")
+
+ if source == target:
+ return (0, [source])
+
+ weight = _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) for choosing node to expand
+ seen = [{source: 0}, {target: 0}] # dict of distances to seen nodes
+ 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
+ if G.is_directed():
+ neighs = [G._succ, G._pred]
+ else:
+ neighs = [G._adj, G._adj]
+ # 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, d in neighs[dir][v].items():
+ # weight(v, w, d) for forward and weight(w, v, d) for back direction
+ cost = weight(v, w, d) if dir == 0 else weight(w, v, d)
+ if cost is None:
+ continue
+ vwLength = dists[dir][v] + cost
+ 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}.")
+
+
+@nx._dispatchable(edge_attrs="weight")
+def johnson(G, weight="weight"):
+ r"""Uses Johnson's Algorithm to compute shortest paths.
+
+ Johnson's Algorithm finds a shortest path between each pair of
+ nodes in a weighted graph even if negative weights are present.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ weight : string or function
+ If this is a string, then edge weights will be accessed via the
+ edge attribute with this key (that is, the weight of the edge
+ joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
+ such edge attribute exists, the weight of the edge is assumed to
+ be one.
+
+ 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.
+
+ Returns
+ -------
+ distance : dictionary
+ Dictionary, keyed by source and target, of shortest paths.
+
+ Examples
+ --------
+ >>> graph = nx.DiGraph()
+ >>> graph.add_weighted_edges_from(
+ ... [("0", "3", 3), ("0", "1", -5), ("0", "2", 2), ("1", "2", 4), ("2", "3", 1)]
+ ... )
+ >>> paths = nx.johnson(graph, weight="weight")
+ >>> paths["0"]["2"]
+ ['0', '1', '2']
+
+ Notes
+ -----
+ Johnson's algorithm is suitable even for graphs with negative weights. It
+ works by using the Bellman–Ford algorithm to compute a transformation of
+ the input graph that removes all negative weights, allowing Dijkstra's
+ algorithm to be used on the transformed graph.
+
+ The time complexity of this algorithm is $O(n^2 \log n + n m)$,
+ where $n$ is the number of nodes and $m$ the number of edges in the
+ graph. For dense graphs, this may be faster than the Floyd–Warshall
+ algorithm.
+
+ See Also
+ --------
+ floyd_warshall_predecessor_and_distance
+ floyd_warshall_numpy
+ all_pairs_shortest_path
+ all_pairs_shortest_path_length
+ all_pairs_dijkstra_path
+ bellman_ford_predecessor_and_distance
+ all_pairs_bellman_ford_path
+ all_pairs_bellman_ford_path_length
+
+ """
+ dist = {v: 0 for v in G}
+ pred = {v: [] for v in G}
+ weight = _weight_function(G, weight)
+
+ # Calculate distance of shortest paths
+ dist_bellman = _bellman_ford(G, list(G), weight, pred=pred, dist=dist)
+
+ # Update the weight function to take into account the Bellman--Ford
+ # relaxation distances.
+ def new_weight(u, v, d):
+ return weight(u, v, d) + dist_bellman[u] - dist_bellman[v]
+
+ def dist_path(v):
+ paths = {v: [v]}
+ _dijkstra(G, v, new_weight, paths=paths)
+ return paths
+
+ return {v: dist_path(v) for v in G}