1"""Floyd-Warshall algorithm for shortest paths."""
2
3from collections import defaultdict
4
5import networkx as nx
6from networkx.algorithms.shortest_paths.weighted import _weight_function
7
8__all__ = [
9 "floyd_warshall",
10 "floyd_warshall_predecessor_and_distance",
11 "reconstruct_path",
12 "floyd_warshall_numpy",
13 "floyd_warshall_tree",
14]
15
16
17def _init_pred_dist(G, weight):
18 """Initialize pred and dist to be used in Floyd Warshall algorithms"""
19 # dictionary-of-dictionaries representation for dist and pred
20 # use some defaultdict magick here
21 # for dist the default is the floating point inf value
22 dist = defaultdict(lambda: defaultdict(lambda: float("inf")))
23 pred = defaultdict(dict)
24 # initialize path distance dictionary to be the adjacency matrix
25 # also set the distance to self to 0 (zero diagonal)
26 weight = _weight_function(G, weight)
27 for u, unbr in G._adj.items():
28 for v, e_attr in unbr.items():
29 cost = weight(u, v, e_attr)
30 if cost is not None: # for hidden edge, weight() returns None
31 dist[u][v] = cost
32 pred[u][v] = u
33 dist[u][u] = 0
34 return pred, dist
35
36
37@nx._dispatchable(edge_attrs="weight")
38def floyd_warshall_numpy(G, nodelist=None, weight="weight"):
39 """Find all-pairs shortest path lengths using Floyd's algorithm.
40
41 This algorithm for finding shortest paths takes advantage of
42 matrix representations of a graph and works well for dense
43 graphs where all-pairs shortest path lengths are desired.
44 The results are returned as a NumPy array, distance[i, j],
45 where i and j are the indexes of two nodes in nodelist.
46 The entry distance[i, j] is the distance along a shortest
47 path from i to j. If no path exists the distance is Inf.
48
49 Parameters
50 ----------
51 G : NetworkX graph
52
53 nodelist : list, optional (default=G.nodes)
54 The rows and columns are ordered by the nodes in nodelist.
55 If nodelist is None then the ordering is produced by G.nodes.
56 Nodelist should include all nodes in G.
57
58 weight: string, optional (default='weight')
59 Edge data key corresponding to the edge weight.
60
61 Returns
62 -------
63 distance : 2D numpy.ndarray
64 A numpy array of shortest path distances between nodes.
65 If there is no path between two nodes the value is Inf.
66
67 Examples
68 --------
69 >>> G = nx.DiGraph()
70 >>> G.add_weighted_edges_from(
71 ... [(0, 1, 5), (1, 2, 2), (2, 3, -3), (1, 3, 10), (3, 2, 8)]
72 ... )
73 >>> nx.floyd_warshall_numpy(G)
74 array([[ 0., 5., 7., 4.],
75 [inf, 0., 2., -1.],
76 [inf, inf, 0., -3.],
77 [inf, inf, 8., 0.]])
78
79 Notes
80 -----
81 Floyd's algorithm is appropriate for finding shortest paths in
82 dense graphs or graphs with negative weights when Dijkstra's
83 algorithm fails. This algorithm can still fail if there are negative
84 cycles. It has running time $O(n^3)$ with running space of $O(n^2)$.
85
86 Raises
87 ------
88 NetworkXError
89 If nodelist is not a list of the nodes in G.
90 """
91 import numpy as np
92
93 if nodelist is not None:
94 if not (len(nodelist) == len(G) == len(set(nodelist))):
95 raise nx.NetworkXError(
96 "nodelist must contain every node in G with no repeats."
97 "If you wanted a subgraph of G use G.subgraph(nodelist)"
98 )
99
100 # To handle cases when an edge has weight=0, we must make sure that
101 # nonedges are not given the value 0 as well.
102 A = nx.to_numpy_array(
103 G, nodelist, multigraph_weight=min, weight=weight, nonedge=np.inf
104 )
105 n, m = A.shape
106 np.fill_diagonal(A, 0) # diagonal elements should be zero
107 for i in range(n):
108 # The second term has the same shape as A due to broadcasting
109 A = np.minimum(A, A[i, :][np.newaxis, :] + A[:, i][:, np.newaxis])
110 return A
111
112
113@nx._dispatchable(edge_attrs="weight")
114def floyd_warshall_tree(G, weight="weight"):
115 r"""Find all-pairs shortest path lengths using a Tree-based
116 modification of Floyd's algorithm.
117
118 This variant implements the Tree algorithm of Brodnik, Grgurovic and Pozar.
119 It differs from the classical Floyd Warshall algorithm by using a shortest
120 path tree rooted at each intermediate vertex w. For every w, the algorithm
121 builds a tree ``OUT_w`` of shortest paths for the current iteration and
122 scans the tree in depth first order. If an update at a vertex v cannot
123 improve any distance, the algorithm skips the entire subtree below v,
124 since none of its nodes can produce a shorter path, as proved in [1]_.
125
126 Parameters
127 ----------
128 G : NetworkX graph
129
130 weight : string or function (default= 'weight')
131 If this is a string, then edge weights will be accessed via the
132 edge attribute with this key (that is, the weight of the edge
133 joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
134 such edge attribute exists, the weight of the edge is assumed to
135 be one.
136
137 If this is a function, the weight of an edge is the value
138 returned by the function. The function must accept exactly three
139 positional arguments: the two endpoints of an edge and the
140 dictionary of edge attributes for that edge. The function must
141 return a number or None to indicate a hidden edge.
142
143 Returns
144 -------
145 predecessor, distance : dict and dict-of-dict
146 Predecessor is a dict keyed by node to the predecessor node in
147 the shortest path. The distance output is a dict keyed by source
148 node to a dict keyed by target node to the distance value of the
149 shortest path between the source and target.
150
151 Examples
152 --------
153 >>> G = nx.DiGraph()
154 >>> G.add_weighted_edges_from(
155 ... [
156 ... ("s", "u", 10),
157 ... ("s", "x", 5),
158 ... ("u", "v", 1),
159 ... ("u", "x", 2),
160 ... ("v", "y", 1),
161 ... ("x", "u", 3),
162 ... ("x", "v", 5),
163 ... ("x", "y", 2),
164 ... ("y", "s", 7),
165 ... ("y", "v", 6),
166 ... ]
167 ... )
168 >>> predecessors, distances = nx.floyd_warshall_tree(G)
169 >>> nx.reconstruct_path("s", "v", predecessors)
170 ['s', 'x', 'u', 'v']
171
172 Notes
173 -----
174 Floyd's algorithm is appropriate for finding shortest paths
175 in dense graphs or graphs with negative weights when Dijkstra's algorithm
176 fails. This algorithm can still fail if there are negative cycles.
177 It has worst case running time $O(|V|^3)$ with running space of $O(|V|^2)$.
178
179 For complete directed graphs with independent edge weights drawn from the
180 uniform distribution on [0, 1], the expected running time is
181 $O(|V|^2 (\log |V|)^2)$, as shown in [1]_.
182
183 See Also
184 --------
185 floyd_warshall_predecessor_and_distance
186 floyd_warshall
187 floyd_warshall_numpy
188
189 References
190 ----------
191 .. [1] Brodnik, Andrej, Marko Grgurovic, and Rok Pozar.
192 "Modifications of the Floyd-Warshall algorithm with
193 nearly quadratic expected-time."
194 Ars Math. Contemp. 22, no. 1 (2022).
195 https://doi.org/10.26493/1855-3974.2467.497
196 """
197 inf = float("inf")
198 pred, dist = _init_pred_dist(G, weight)
199
200 # dont check for those w, `from` which `no` path exists
201 for w, pred_w in pred.items():
202 # out_w will store the adjacency list of the OUT_W tree of the paper,
203 # it is a tree, parent --> list of children
204 out_w = {parent: [] for parent in G}
205 for v, parent in pred_w.items(): # w to v path exist
206 if v == w:
207 continue
208 out_w[parent].append(v)
209
210 # dfs order dict and skip dict in practical improvements in the paper
211 stack = [w]
212 dfs_dict = {} # {node: next node in dfs order}
213 skip_dict = {} # {node: next node after subtree is skipped}
214
215 node = None
216 while stack:
217 next_node = stack.pop()
218 dfs_dict[node] = next_node
219 if stack:
220 skip_dict[next_node] = stack[-1]
221 stack.extend(out_w[next_node])
222 node = next_node
223
224 dist_w = dist[w] # for speed
225 # main inner loop starts here
226 for u in G:
227 if u == w: # small optimization
228 continue
229 dist_u = dist[u]
230 dist_uw = dist_u[w]
231 if dist_uw == inf: # small optimization
232 continue
233
234 # note: we skip v=w as relaxation would always fail
235 v = dfs_dict[w]
236 while v is not None:
237 dist_uwv = dist_uw + dist_w[v]
238 if dist_u[v] > dist_uwv:
239 dist_u[v] = dist_uwv
240 # update/new entries to be created in pred[u]
241 pred[u][v] = pred_w[v] # v must be in pred_w as checked above
242 v = dfs_dict.get(v, None)
243 else:
244 v = skip_dict.get(v, None)
245
246 return dict(pred), dict(dist)
247
248
249@nx._dispatchable(edge_attrs="weight")
250def floyd_warshall_predecessor_and_distance(G, weight="weight"):
251 """Find all-pairs shortest path lengths using Floyd's algorithm.
252
253 Parameters
254 ----------
255 G : NetworkX graph
256
257 weight : string or function (default= 'weight')
258 If this is a string, then edge weights will be accessed via the
259 edge attribute with this key (that is, the weight of the edge
260 joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
261 such edge attribute exists, the weight of the edge is assumed to
262 be one.
263
264 If this is a function, the weight of an edge is the value
265 returned by the function. The function must accept exactly three
266 positional arguments: the two endpoints of an edge and the
267 dictionary of edge attributes for that edge. The function must
268 return a number or None to indicate a hidden edge.
269
270 Returns
271 -------
272 predecessor,distance : dictionaries
273 Dictionaries, keyed by source and target, of predecessors and distances
274 in the shortest path.
275
276 Examples
277 --------
278 >>> G = nx.DiGraph()
279 >>> G.add_weighted_edges_from(
280 ... [
281 ... ("s", "u", 10),
282 ... ("s", "x", 5),
283 ... ("u", "v", 1),
284 ... ("u", "x", 2),
285 ... ("v", "y", 1),
286 ... ("x", "u", 3),
287 ... ("x", "v", 5),
288 ... ("x", "y", 2),
289 ... ("y", "s", 7),
290 ... ("y", "v", 6),
291 ... ]
292 ... )
293 >>> predecessors, _ = nx.floyd_warshall_predecessor_and_distance(G)
294 >>> print(nx.reconstruct_path("s", "v", predecessors))
295 ['s', 'x', 'u', 'v']
296
297 Notes
298 -----
299 Floyd's algorithm is appropriate for finding shortest paths
300 in dense graphs or graphs with negative weights when Dijkstra's algorithm
301 fails. This algorithm can still fail if there are negative cycles.
302 It has running time $O(n^3)$ with running space of $O(n^2)$.
303
304 See Also
305 --------
306 floyd_warshall
307 floyd_warshall_numpy
308 all_pairs_shortest_path
309 all_pairs_shortest_path_length
310 """
311 pred, dist = _init_pred_dist(G, weight)
312 for w in G:
313 dist_w = dist[w] # save recomputation
314 for u in G:
315 dist_u = dist[u] # save recomputation
316 for v in G:
317 d = dist_u[w] + dist_w[v]
318 if dist_u[v] > d:
319 dist_u[v] = d
320 pred[u][v] = pred[w][v]
321 return dict(pred), dict(dist)
322
323
324@nx._dispatchable(graphs=None)
325def reconstruct_path(source, target, predecessors):
326 """Reconstruct a path from source to target using the predecessors
327 dict as returned by floyd_warshall_predecessor_and_distance
328
329 Parameters
330 ----------
331 source : node
332 Starting node for path
333
334 target : node
335 Ending node for path
336
337 predecessors: dictionary
338 Dictionary, keyed by source and target, of predecessors in the
339 shortest path, as returned by floyd_warshall_predecessor_and_distance
340
341 Returns
342 -------
343 path : list
344 A list of nodes containing the shortest path from source to target
345
346 If source and target are the same, an empty list is returned
347
348 Notes
349 -----
350 This function is meant to give more applicability to the
351 floyd_warshall_predecessor_and_distance function
352
353 See Also
354 --------
355 floyd_warshall_predecessor_and_distance
356 """
357 if source == target:
358 return []
359 prev = predecessors[source]
360 curr = prev[target]
361 path = [target, curr]
362 while curr != source:
363 curr = prev[curr]
364 path.append(curr)
365 return list(reversed(path))
366
367
368@nx._dispatchable(edge_attrs="weight")
369def floyd_warshall(G, weight="weight"):
370 """Find all-pairs shortest path lengths using Floyd's algorithm.
371
372 Parameters
373 ----------
374 G : NetworkX graph
375
376 weight : string or function (default= 'weight')
377 If this is a string, then edge weights will be accessed via the
378 edge attribute with this key (that is, the weight of the edge
379 joining `u` to `v` will be ``G.edges[u, v][weight]``). If no
380 such edge attribute exists, the weight of the edge is assumed to
381 be one.
382
383 If this is a function, the weight of an edge is the value
384 returned by the function. The function must accept exactly three
385 positional arguments: the two endpoints of an edge and the
386 dictionary of edge attributes for that edge. The function must
387 return a number or None to indicate a hidden edge.
388
389
390 Returns
391 -------
392 distance : dict
393 A dictionary, keyed by source and target, of shortest paths distances
394 between nodes.
395
396 Examples
397 --------
398 >>> from pprint import pprint
399 >>> G = nx.DiGraph()
400 >>> G.add_weighted_edges_from(
401 ... [(0, 1, 5), (1, 2, 2), (2, 3, -3), (1, 3, 10), (3, 2, 8)]
402 ... )
403 >>> fw = nx.floyd_warshall(G, weight="weight")
404 >>> results = {a: dict(b) for a, b in fw.items()}
405 >>> pprint(results)
406 {0: {0: 0, 1: 5, 2: 7, 3: 4},
407 1: {0: inf, 1: 0, 2: 2, 3: -1},
408 2: {0: inf, 1: inf, 2: 0, 3: -3},
409 3: {0: inf, 1: inf, 2: 8, 3: 0}}
410
411 Notes
412 -----
413 Floyd's algorithm is appropriate for finding shortest paths
414 in dense graphs or graphs with negative weights when Dijkstra's algorithm
415 fails. This algorithm can still fail if there are negative cycles.
416 It has running time $O(n^3)$ with running space of $O(n^2)$.
417
418 See Also
419 --------
420 floyd_warshall_predecessor_and_distance
421 floyd_warshall_numpy
422 all_pairs_shortest_path
423 all_pairs_shortest_path_length
424 """
425 # could make this its own function to reduce memory costs
426 return floyd_warshall_predecessor_and_distance(G, weight=weight)[1]