comparison env/lib/python3.9/site-packages/networkx/algorithms/shortest_paths/tests/test_weighted.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 import pytest
2
3 import networkx as nx
4 from networkx.utils import pairwise
5
6
7 def validate_path(G, s, t, soln_len, path, weight="weight"):
8 assert path[0] == s
9 assert path[-1] == t
10
11 if callable(weight):
12 weight_f = weight
13 else:
14 if G.is_multigraph():
15
16 def weight_f(u, v, d):
17 return min(e.get(weight, 1) for e in d.values())
18
19 else:
20
21 def weight_f(u, v, d):
22 return d.get(weight, 1)
23
24 computed = sum(weight_f(u, v, G[u][v]) for u, v in pairwise(path))
25 assert soln_len == computed
26
27
28 def validate_length_path(G, s, t, soln_len, length, path, weight="weight"):
29 assert soln_len == length
30 validate_path(G, s, t, length, path, weight=weight)
31
32
33 class WeightedTestBase:
34 """Base class for test classes that test functions for computing
35 shortest paths in weighted graphs.
36
37 """
38
39 def setup(self):
40 """Creates some graphs for use in the unit tests."""
41 cnlti = nx.convert_node_labels_to_integers
42 self.grid = cnlti(nx.grid_2d_graph(4, 4), first_label=1, ordering="sorted")
43 self.cycle = nx.cycle_graph(7)
44 self.directed_cycle = nx.cycle_graph(7, create_using=nx.DiGraph())
45 self.XG = nx.DiGraph()
46 self.XG.add_weighted_edges_from(
47 [
48 ("s", "u", 10),
49 ("s", "x", 5),
50 ("u", "v", 1),
51 ("u", "x", 2),
52 ("v", "y", 1),
53 ("x", "u", 3),
54 ("x", "v", 5),
55 ("x", "y", 2),
56 ("y", "s", 7),
57 ("y", "v", 6),
58 ]
59 )
60 self.MXG = nx.MultiDiGraph(self.XG)
61 self.MXG.add_edge("s", "u", weight=15)
62 self.XG2 = nx.DiGraph()
63 self.XG2.add_weighted_edges_from(
64 [
65 [1, 4, 1],
66 [4, 5, 1],
67 [5, 6, 1],
68 [6, 3, 1],
69 [1, 3, 50],
70 [1, 2, 100],
71 [2, 3, 100],
72 ]
73 )
74
75 self.XG3 = nx.Graph()
76 self.XG3.add_weighted_edges_from(
77 [[0, 1, 2], [1, 2, 12], [2, 3, 1], [3, 4, 5], [4, 5, 1], [5, 0, 10]]
78 )
79
80 self.XG4 = nx.Graph()
81 self.XG4.add_weighted_edges_from(
82 [
83 [0, 1, 2],
84 [1, 2, 2],
85 [2, 3, 1],
86 [3, 4, 1],
87 [4, 5, 1],
88 [5, 6, 1],
89 [6, 7, 1],
90 [7, 0, 1],
91 ]
92 )
93 self.MXG4 = nx.MultiGraph(self.XG4)
94 self.MXG4.add_edge(0, 1, weight=3)
95 self.G = nx.DiGraph() # no weights
96 self.G.add_edges_from(
97 [
98 ("s", "u"),
99 ("s", "x"),
100 ("u", "v"),
101 ("u", "x"),
102 ("v", "y"),
103 ("x", "u"),
104 ("x", "v"),
105 ("x", "y"),
106 ("y", "s"),
107 ("y", "v"),
108 ]
109 )
110
111
112 class TestWeightedPath(WeightedTestBase):
113 def test_dijkstra(self):
114 (D, P) = nx.single_source_dijkstra(self.XG, "s")
115 validate_path(self.XG, "s", "v", 9, P["v"])
116 assert D["v"] == 9
117
118 validate_path(
119 self.XG, "s", "v", 9, nx.single_source_dijkstra_path(self.XG, "s")["v"]
120 )
121 assert dict(nx.single_source_dijkstra_path_length(self.XG, "s"))["v"] == 9
122
123 validate_path(
124 self.XG, "s", "v", 9, nx.single_source_dijkstra(self.XG, "s")[1]["v"]
125 )
126 validate_path(
127 self.MXG, "s", "v", 9, nx.single_source_dijkstra_path(self.MXG, "s")["v"]
128 )
129
130 GG = self.XG.to_undirected()
131 # make sure we get lower weight
132 # to_undirected might choose either edge with weight 2 or weight 3
133 GG["u"]["x"]["weight"] = 2
134 (D, P) = nx.single_source_dijkstra(GG, "s")
135 validate_path(GG, "s", "v", 8, P["v"])
136 assert D["v"] == 8 # uses lower weight of 2 on u<->x edge
137 validate_path(GG, "s", "v", 8, nx.dijkstra_path(GG, "s", "v"))
138 assert nx.dijkstra_path_length(GG, "s", "v") == 8
139
140 validate_path(self.XG2, 1, 3, 4, nx.dijkstra_path(self.XG2, 1, 3))
141 validate_path(self.XG3, 0, 3, 15, nx.dijkstra_path(self.XG3, 0, 3))
142 assert nx.dijkstra_path_length(self.XG3, 0, 3) == 15
143 validate_path(self.XG4, 0, 2, 4, nx.dijkstra_path(self.XG4, 0, 2))
144 assert nx.dijkstra_path_length(self.XG4, 0, 2) == 4
145 validate_path(self.MXG4, 0, 2, 4, nx.dijkstra_path(self.MXG4, 0, 2))
146 validate_path(
147 self.G, "s", "v", 2, nx.single_source_dijkstra(self.G, "s", "v")[1]
148 )
149 validate_path(
150 self.G, "s", "v", 2, nx.single_source_dijkstra(self.G, "s")[1]["v"]
151 )
152
153 validate_path(self.G, "s", "v", 2, nx.dijkstra_path(self.G, "s", "v"))
154 assert nx.dijkstra_path_length(self.G, "s", "v") == 2
155
156 # NetworkXError: node s not reachable from moon
157 pytest.raises(nx.NetworkXNoPath, nx.dijkstra_path, self.G, "s", "moon")
158 pytest.raises(nx.NetworkXNoPath, nx.dijkstra_path_length, self.G, "s", "moon")
159
160 validate_path(self.cycle, 0, 3, 3, nx.dijkstra_path(self.cycle, 0, 3))
161 validate_path(self.cycle, 0, 4, 3, nx.dijkstra_path(self.cycle, 0, 4))
162
163 assert nx.single_source_dijkstra(self.cycle, 0, 0) == (0, [0])
164
165 def test_bidirectional_dijkstra(self):
166 validate_length_path(
167 self.XG, "s", "v", 9, *nx.bidirectional_dijkstra(self.XG, "s", "v")
168 )
169 validate_length_path(
170 self.G, "s", "v", 2, *nx.bidirectional_dijkstra(self.G, "s", "v")
171 )
172 validate_length_path(
173 self.cycle, 0, 3, 3, *nx.bidirectional_dijkstra(self.cycle, 0, 3)
174 )
175 validate_length_path(
176 self.cycle, 0, 4, 3, *nx.bidirectional_dijkstra(self.cycle, 0, 4)
177 )
178 validate_length_path(
179 self.XG3, 0, 3, 15, *nx.bidirectional_dijkstra(self.XG3, 0, 3)
180 )
181 validate_length_path(
182 self.XG4, 0, 2, 4, *nx.bidirectional_dijkstra(self.XG4, 0, 2)
183 )
184
185 # need more tests here
186 P = nx.single_source_dijkstra_path(self.XG, "s")["v"]
187 validate_path(
188 self.XG,
189 "s",
190 "v",
191 sum(self.XG[u][v]["weight"] for u, v in zip(P[:-1], P[1:])),
192 nx.dijkstra_path(self.XG, "s", "v"),
193 )
194
195 # check absent source
196 G = nx.path_graph(2)
197 pytest.raises(nx.NodeNotFound, nx.bidirectional_dijkstra, G, 3, 0)
198
199 def test_weight_functions(self):
200 def heuristic(*z):
201 return hash(z)
202
203 def getpath(pred, v, s):
204 return [v] if v == s else getpath(pred, pred[v], s) + [v]
205
206 def goldberg_radzik(g, s, t, weight="weight"):
207 pred, dist = nx.goldberg_radzik(g, s, weight=weight)
208 dist = dist[t]
209 return dist, getpath(pred, t, s)
210
211 def astar(g, s, t, weight="weight"):
212 path = nx.astar_path(g, s, t, heuristic, weight=weight)
213 dist = nx.astar_path_length(g, s, t, heuristic, weight=weight)
214 return dist, path
215
216 def vlp(G, s, t, l, F, w):
217 res = F(G, s, t, weight=w)
218 validate_length_path(G, s, t, l, *res, weight=w)
219
220 G = self.cycle
221 s = 6
222 t = 4
223 path = [6] + list(range(t + 1))
224
225 def weight(u, v, _):
226 return 1 + v ** 2
227
228 length = sum(weight(u, v, None) for u, v in pairwise(path))
229 vlp(G, s, t, length, nx.bidirectional_dijkstra, weight)
230 vlp(G, s, t, length, nx.single_source_dijkstra, weight)
231 vlp(G, s, t, length, nx.single_source_bellman_ford, weight)
232 vlp(G, s, t, length, goldberg_radzik, weight)
233 vlp(G, s, t, length, astar, weight)
234
235 def weight(u, v, _):
236 return 2 ** (u * v)
237
238 length = sum(weight(u, v, None) for u, v in pairwise(path))
239 vlp(G, s, t, length, nx.bidirectional_dijkstra, weight)
240 vlp(G, s, t, length, nx.single_source_dijkstra, weight)
241 vlp(G, s, t, length, nx.single_source_bellman_ford, weight)
242 vlp(G, s, t, length, goldberg_radzik, weight)
243 vlp(G, s, t, length, astar, weight)
244
245 def test_bidirectional_dijkstra_no_path(self):
246 with pytest.raises(nx.NetworkXNoPath):
247 G = nx.Graph()
248 nx.add_path(G, [1, 2, 3])
249 nx.add_path(G, [4, 5, 6])
250 path = nx.bidirectional_dijkstra(G, 1, 6)
251
252 def test_absent_source(self):
253 # the check is in _dijkstra_multisource, but this will provide
254 # regression testing against later changes to any of the "client"
255 # Dijkstra or Bellman-Ford functions
256 G = nx.path_graph(2)
257 for fn in (
258 nx.dijkstra_path,
259 nx.dijkstra_path_length,
260 nx.single_source_dijkstra_path,
261 nx.single_source_dijkstra_path_length,
262 nx.single_source_dijkstra,
263 nx.dijkstra_predecessor_and_distance,
264 ):
265 pytest.raises(nx.NodeNotFound, fn, G, 3, 0)
266
267 def test_dijkstra_predecessor1(self):
268 G = nx.path_graph(4)
269 assert nx.dijkstra_predecessor_and_distance(G, 0) == (
270 {0: [], 1: [0], 2: [1], 3: [2]},
271 {0: 0, 1: 1, 2: 2, 3: 3},
272 )
273
274 def test_dijkstra_predecessor2(self):
275 # 4-cycle
276 G = nx.Graph([(0, 1), (1, 2), (2, 3), (3, 0)])
277 pred, dist = nx.dijkstra_predecessor_and_distance(G, (0))
278 assert pred[0] == []
279 assert pred[1] == [0]
280 assert pred[2] in [[1, 3], [3, 1]]
281 assert pred[3] == [0]
282 assert dist == {0: 0, 1: 1, 2: 2, 3: 1}
283
284 def test_dijkstra_predecessor3(self):
285 XG = nx.DiGraph()
286 XG.add_weighted_edges_from(
287 [
288 ("s", "u", 10),
289 ("s", "x", 5),
290 ("u", "v", 1),
291 ("u", "x", 2),
292 ("v", "y", 1),
293 ("x", "u", 3),
294 ("x", "v", 5),
295 ("x", "y", 2),
296 ("y", "s", 7),
297 ("y", "v", 6),
298 ]
299 )
300 (P, D) = nx.dijkstra_predecessor_and_distance(XG, "s")
301 assert P["v"] == ["u"]
302 assert D["v"] == 9
303 (P, D) = nx.dijkstra_predecessor_and_distance(XG, "s", cutoff=8)
304 assert "v" not in D
305
306 def test_single_source_dijkstra_path_length(self):
307 pl = nx.single_source_dijkstra_path_length
308 assert dict(pl(self.MXG4, 0))[2] == 4
309 spl = pl(self.MXG4, 0, cutoff=2)
310 assert 2 not in spl
311
312 def test_bidirectional_dijkstra_multigraph(self):
313 G = nx.MultiGraph()
314 G.add_edge("a", "b", weight=10)
315 G.add_edge("a", "b", weight=100)
316 dp = nx.bidirectional_dijkstra(G, "a", "b")
317 assert dp == (10, ["a", "b"])
318
319 def test_dijkstra_pred_distance_multigraph(self):
320 G = nx.MultiGraph()
321 G.add_edge("a", "b", key="short", foo=5, weight=100)
322 G.add_edge("a", "b", key="long", bar=1, weight=110)
323 p, d = nx.dijkstra_predecessor_and_distance(G, "a")
324 assert p == {"a": [], "b": ["a"]}
325 assert d == {"a": 0, "b": 100}
326
327 def test_negative_edge_cycle(self):
328 G = nx.cycle_graph(5, create_using=nx.DiGraph())
329 assert not nx.negative_edge_cycle(G)
330 G.add_edge(8, 9, weight=-7)
331 G.add_edge(9, 8, weight=3)
332 graph_size = len(G)
333 assert nx.negative_edge_cycle(G)
334 assert graph_size == len(G)
335 pytest.raises(ValueError, nx.single_source_dijkstra_path_length, G, 8)
336 pytest.raises(ValueError, nx.single_source_dijkstra, G, 8)
337 pytest.raises(ValueError, nx.dijkstra_predecessor_and_distance, G, 8)
338 G.add_edge(9, 10)
339 pytest.raises(ValueError, nx.bidirectional_dijkstra, G, 8, 10)
340
341 def test_weight_function(self):
342 """Tests that a callable weight is interpreted as a weight
343 function instead of an edge attribute.
344
345 """
346 # Create a triangle in which the edge from node 0 to node 2 has
347 # a large weight and the other two edges have a small weight.
348 G = nx.complete_graph(3)
349 G.adj[0][2]["weight"] = 10
350 G.adj[0][1]["weight"] = 1
351 G.adj[1][2]["weight"] = 1
352 # The weight function will take the multiplicative inverse of
353 # the weights on the edges. This way, weights that were large
354 # before now become small and vice versa.
355
356 def weight(u, v, d):
357 return 1 / d["weight"]
358
359 # The shortest path from 0 to 2 using the actual weights on the
360 # edges should be [0, 1, 2].
361 distance, path = nx.single_source_dijkstra(G, 0, 2)
362 assert distance == 2
363 assert path == [0, 1, 2]
364 # However, with the above weight function, the shortest path
365 # should be [0, 2], since that has a very small weight.
366 distance, path = nx.single_source_dijkstra(G, 0, 2, weight=weight)
367 assert distance == 1 / 10
368 assert path == [0, 2]
369
370 def test_all_pairs_dijkstra_path(self):
371 cycle = nx.cycle_graph(7)
372 p = dict(nx.all_pairs_dijkstra_path(cycle))
373 assert p[0][3] == [0, 1, 2, 3]
374
375 cycle[1][2]["weight"] = 10
376 p = dict(nx.all_pairs_dijkstra_path(cycle))
377 assert p[0][3] == [0, 6, 5, 4, 3]
378
379 def test_all_pairs_dijkstra_path_length(self):
380 cycle = nx.cycle_graph(7)
381 pl = dict(nx.all_pairs_dijkstra_path_length(cycle))
382 assert pl[0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1}
383
384 cycle[1][2]["weight"] = 10
385 pl = dict(nx.all_pairs_dijkstra_path_length(cycle))
386 assert pl[0] == {0: 0, 1: 1, 2: 5, 3: 4, 4: 3, 5: 2, 6: 1}
387
388 def test_all_pairs_dijkstra(self):
389 cycle = nx.cycle_graph(7)
390 out = dict(nx.all_pairs_dijkstra(cycle))
391 assert out[0][0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1}
392 assert out[0][1][3] == [0, 1, 2, 3]
393
394 cycle[1][2]["weight"] = 10
395 out = dict(nx.all_pairs_dijkstra(cycle))
396 assert out[0][0] == {0: 0, 1: 1, 2: 5, 3: 4, 4: 3, 5: 2, 6: 1}
397 assert out[0][1][3] == [0, 6, 5, 4, 3]
398
399
400 class TestDijkstraPathLength:
401 """Unit tests for the :func:`networkx.dijkstra_path_length`
402 function.
403
404 """
405
406 def test_weight_function(self):
407 """Tests for computing the length of the shortest path using
408 Dijkstra's algorithm with a user-defined weight function.
409
410 """
411 # Create a triangle in which the edge from node 0 to node 2 has
412 # a large weight and the other two edges have a small weight.
413 G = nx.complete_graph(3)
414 G.adj[0][2]["weight"] = 10
415 G.adj[0][1]["weight"] = 1
416 G.adj[1][2]["weight"] = 1
417 # The weight function will take the multiplicative inverse of
418 # the weights on the edges. This way, weights that were large
419 # before now become small and vice versa.
420
421 def weight(u, v, d):
422 return 1 / d["weight"]
423
424 # The shortest path from 0 to 2 using the actual weights on the
425 # edges should be [0, 1, 2]. However, with the above weight
426 # function, the shortest path should be [0, 2], since that has a
427 # very small weight.
428 length = nx.dijkstra_path_length(G, 0, 2, weight=weight)
429 assert length == 1 / 10
430
431
432 class TestMultiSourceDijkstra:
433 """Unit tests for the multi-source dialect of Dijkstra's shortest
434 path algorithms.
435
436 """
437
438 def test_no_sources(self):
439 with pytest.raises(ValueError):
440 nx.multi_source_dijkstra(nx.Graph(), {})
441
442 def test_path_no_sources(self):
443 with pytest.raises(ValueError):
444 nx.multi_source_dijkstra_path(nx.Graph(), {})
445
446 def test_path_length_no_sources(self):
447 with pytest.raises(ValueError):
448 nx.multi_source_dijkstra_path_length(nx.Graph(), {})
449
450 def test_absent_source(self):
451 G = nx.path_graph(2)
452 for fn in (
453 nx.multi_source_dijkstra_path,
454 nx.multi_source_dijkstra_path_length,
455 nx.multi_source_dijkstra,
456 ):
457 pytest.raises(nx.NodeNotFound, fn, G, [3], 0)
458
459 def test_two_sources(self):
460 edges = [(0, 1, 1), (1, 2, 1), (2, 3, 10), (3, 4, 1)]
461 G = nx.Graph()
462 G.add_weighted_edges_from(edges)
463 sources = {0, 4}
464 distances, paths = nx.multi_source_dijkstra(G, sources)
465 expected_distances = {0: 0, 1: 1, 2: 2, 3: 1, 4: 0}
466 expected_paths = {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [4, 3], 4: [4]}
467 assert distances == expected_distances
468 assert paths == expected_paths
469
470 def test_simple_paths(self):
471 G = nx.path_graph(4)
472 lengths = nx.multi_source_dijkstra_path_length(G, [0])
473 assert lengths == {n: n for n in G}
474 paths = nx.multi_source_dijkstra_path(G, [0])
475 assert paths == {n: list(range(n + 1)) for n in G}
476
477
478 class TestBellmanFordAndGoldbergRadzik(WeightedTestBase):
479 def test_single_node_graph(self):
480 G = nx.DiGraph()
481 G.add_node(0)
482 assert nx.single_source_bellman_ford_path(G, 0) == {0: [0]}
483 assert nx.single_source_bellman_ford_path_length(G, 0) == {0: 0}
484 assert nx.single_source_bellman_ford(G, 0) == ({0: 0}, {0: [0]})
485 assert nx.bellman_ford_predecessor_and_distance(G, 0) == ({0: []}, {0: 0})
486 assert nx.goldberg_radzik(G, 0) == ({0: None}, {0: 0})
487
488 def test_absent_source_bellman_ford(self):
489 # the check is in _bellman_ford; this provides regression testing
490 # against later changes to "client" Bellman-Ford functions
491 G = nx.path_graph(2)
492 for fn in (
493 nx.bellman_ford_predecessor_and_distance,
494 nx.bellman_ford_path,
495 nx.bellman_ford_path_length,
496 nx.single_source_bellman_ford_path,
497 nx.single_source_bellman_ford_path_length,
498 nx.single_source_bellman_ford,
499 ):
500 pytest.raises(nx.NodeNotFound, fn, G, 3, 0)
501
502 def test_absent_source_goldberg_radzik(self):
503 with pytest.raises(nx.NodeNotFound):
504 G = nx.path_graph(2)
505 nx.goldberg_radzik(G, 3, 0)
506
507 def test_negative_weight_cycle_heuristic(self):
508 G = nx.DiGraph()
509 G.add_edge(0, 1, weight=-1)
510 G.add_edge(1, 2, weight=-1)
511 G.add_edge(2, 3, weight=-1)
512 G.add_edge(3, 0, weight=3)
513 assert not nx.negative_edge_cycle(G, heuristic=True)
514 G.add_edge(2, 0, weight=1.999)
515 assert nx.negative_edge_cycle(G, heuristic=True)
516 G.edges[2, 0]["weight"] = 2
517 assert not nx.negative_edge_cycle(G, heuristic=True)
518
519 def test_negative_weight_cycle_consistency(self):
520 import random
521
522 unif = random.uniform
523 for random_seed in range(2): # range(20):
524 random.seed(random_seed)
525 for density in [0.1, 0.9]: # .3, .7, .9]:
526 for N in [1, 10, 20]: # range(1, 60 - int(30 * density)):
527 for max_cost in [1, 90]: # [1, 10, 40, 90]:
528 G = nx.binomial_graph(N, density, seed=4, directed=True)
529 edges = ((u, v, unif(-1, max_cost)) for u, v in G.edges)
530 G.add_weighted_edges_from(edges)
531
532 no_heuristic = nx.negative_edge_cycle(G, heuristic=False)
533 with_heuristic = nx.negative_edge_cycle(G, heuristic=True)
534 assert no_heuristic == with_heuristic
535
536 def test_negative_weight_cycle(self):
537 G = nx.cycle_graph(5, create_using=nx.DiGraph())
538 G.add_edge(1, 2, weight=-7)
539 for i in range(5):
540 pytest.raises(
541 nx.NetworkXUnbounded, nx.single_source_bellman_ford_path, G, i
542 )
543 pytest.raises(
544 nx.NetworkXUnbounded, nx.single_source_bellman_ford_path_length, G, i
545 )
546 pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford, G, i)
547 pytest.raises(
548 nx.NetworkXUnbounded, nx.bellman_ford_predecessor_and_distance, G, i
549 )
550 pytest.raises(nx.NetworkXUnbounded, nx.goldberg_radzik, G, i)
551 G = nx.cycle_graph(5) # undirected Graph
552 G.add_edge(1, 2, weight=-3)
553 for i in range(5):
554 pytest.raises(
555 nx.NetworkXUnbounded, nx.single_source_bellman_ford_path, G, i
556 )
557 pytest.raises(
558 nx.NetworkXUnbounded, nx.single_source_bellman_ford_path_length, G, i
559 )
560 pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford, G, i)
561 pytest.raises(
562 nx.NetworkXUnbounded, nx.bellman_ford_predecessor_and_distance, G, i
563 )
564 pytest.raises(nx.NetworkXUnbounded, nx.goldberg_radzik, G, i)
565 G = nx.DiGraph([(1, 1, {"weight": -1})])
566 pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford_path, G, 1)
567 pytest.raises(
568 nx.NetworkXUnbounded, nx.single_source_bellman_ford_path_length, G, 1
569 )
570 pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford, G, 1)
571 pytest.raises(
572 nx.NetworkXUnbounded, nx.bellman_ford_predecessor_and_distance, G, 1
573 )
574 pytest.raises(nx.NetworkXUnbounded, nx.goldberg_radzik, G, 1)
575 # no negative cycle but negative weight
576 G = nx.cycle_graph(5, create_using=nx.DiGraph())
577 G.add_edge(1, 2, weight=-3)
578 assert nx.single_source_bellman_ford_path(G, 0) == {
579 0: [0],
580 1: [0, 1],
581 2: [0, 1, 2],
582 3: [0, 1, 2, 3],
583 4: [0, 1, 2, 3, 4],
584 }
585 assert nx.single_source_bellman_ford_path_length(G, 0) == {
586 0: 0,
587 1: 1,
588 2: -2,
589 3: -1,
590 4: 0,
591 }
592 assert nx.single_source_bellman_ford(G, 0) == (
593 {0: 0, 1: 1, 2: -2, 3: -1, 4: 0},
594 {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 1, 2, 3], 4: [0, 1, 2, 3, 4]},
595 )
596 assert nx.bellman_ford_predecessor_and_distance(G, 0) == (
597 {0: [], 1: [0], 2: [1], 3: [2], 4: [3]},
598 {0: 0, 1: 1, 2: -2, 3: -1, 4: 0},
599 )
600 assert nx.goldberg_radzik(G, 0) == (
601 {0: None, 1: 0, 2: 1, 3: 2, 4: 3},
602 {0: 0, 1: 1, 2: -2, 3: -1, 4: 0},
603 )
604
605 def test_not_connected(self):
606 G = nx.complete_graph(6)
607 G.add_edge(10, 11)
608 G.add_edge(10, 12)
609 assert nx.single_source_bellman_ford_path(G, 0) == {
610 0: [0],
611 1: [0, 1],
612 2: [0, 2],
613 3: [0, 3],
614 4: [0, 4],
615 5: [0, 5],
616 }
617 assert nx.single_source_bellman_ford_path_length(G, 0) == {
618 0: 0,
619 1: 1,
620 2: 1,
621 3: 1,
622 4: 1,
623 5: 1,
624 }
625 assert nx.single_source_bellman_ford(G, 0) == (
626 {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1},
627 {0: [0], 1: [0, 1], 2: [0, 2], 3: [0, 3], 4: [0, 4], 5: [0, 5]},
628 )
629 assert nx.bellman_ford_predecessor_and_distance(G, 0) == (
630 {0: [], 1: [0], 2: [0], 3: [0], 4: [0], 5: [0]},
631 {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1},
632 )
633 assert nx.goldberg_radzik(G, 0) == (
634 {0: None, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0},
635 {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1},
636 )
637
638 # not connected, with a component not containing the source that
639 # contains a negative cost cycle.
640 G = nx.complete_graph(6)
641 G.add_edges_from(
642 [
643 ("A", "B", {"load": 3}),
644 ("B", "C", {"load": -10}),
645 ("C", "A", {"load": 2}),
646 ]
647 )
648 assert nx.single_source_bellman_ford_path(G, 0, weight="load") == {
649 0: [0],
650 1: [0, 1],
651 2: [0, 2],
652 3: [0, 3],
653 4: [0, 4],
654 5: [0, 5],
655 }
656 assert nx.single_source_bellman_ford_path_length(G, 0, weight="load") == {
657 0: 0,
658 1: 1,
659 2: 1,
660 3: 1,
661 4: 1,
662 5: 1,
663 }
664 assert nx.single_source_bellman_ford(G, 0, weight="load") == (
665 {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1},
666 {0: [0], 1: [0, 1], 2: [0, 2], 3: [0, 3], 4: [0, 4], 5: [0, 5]},
667 )
668 assert nx.bellman_ford_predecessor_and_distance(G, 0, weight="load") == (
669 {0: [], 1: [0], 2: [0], 3: [0], 4: [0], 5: [0]},
670 {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1},
671 )
672 assert nx.goldberg_radzik(G, 0, weight="load") == (
673 {0: None, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0},
674 {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1},
675 )
676
677 def test_multigraph(self):
678 assert nx.bellman_ford_path(self.MXG, "s", "v") == ["s", "x", "u", "v"]
679 assert nx.bellman_ford_path_length(self.MXG, "s", "v") == 9
680 assert nx.single_source_bellman_ford_path(self.MXG, "s")["v"] == [
681 "s",
682 "x",
683 "u",
684 "v",
685 ]
686 assert nx.single_source_bellman_ford_path_length(self.MXG, "s")["v"] == 9
687 D, P = nx.single_source_bellman_ford(self.MXG, "s", target="v")
688 assert D == 9
689 assert P == ["s", "x", "u", "v"]
690 P, D = nx.bellman_ford_predecessor_and_distance(self.MXG, "s")
691 assert P["v"] == ["u"]
692 assert D["v"] == 9
693 P, D = nx.goldberg_radzik(self.MXG, "s")
694 assert P["v"] == "u"
695 assert D["v"] == 9
696 assert nx.bellman_ford_path(self.MXG4, 0, 2) == [0, 1, 2]
697 assert nx.bellman_ford_path_length(self.MXG4, 0, 2) == 4
698 assert nx.single_source_bellman_ford_path(self.MXG4, 0)[2] == [0, 1, 2]
699 assert nx.single_source_bellman_ford_path_length(self.MXG4, 0)[2] == 4
700 D, P = nx.single_source_bellman_ford(self.MXG4, 0, target=2)
701 assert D == 4
702 assert P == [0, 1, 2]
703 P, D = nx.bellman_ford_predecessor_and_distance(self.MXG4, 0)
704 assert P[2] == [1]
705 assert D[2] == 4
706 P, D = nx.goldberg_radzik(self.MXG4, 0)
707 assert P[2] == 1
708 assert D[2] == 4
709
710 def test_others(self):
711 assert nx.bellman_ford_path(self.XG, "s", "v") == ["s", "x", "u", "v"]
712 assert nx.bellman_ford_path_length(self.XG, "s", "v") == 9
713 assert nx.single_source_bellman_ford_path(self.XG, "s")["v"] == [
714 "s",
715 "x",
716 "u",
717 "v",
718 ]
719 assert nx.single_source_bellman_ford_path_length(self.XG, "s")["v"] == 9
720 D, P = nx.single_source_bellman_ford(self.XG, "s", target="v")
721 assert D == 9
722 assert P == ["s", "x", "u", "v"]
723 (P, D) = nx.bellman_ford_predecessor_and_distance(self.XG, "s")
724 assert P["v"] == ["u"]
725 assert D["v"] == 9
726 (P, D) = nx.goldberg_radzik(self.XG, "s")
727 assert P["v"] == "u"
728 assert D["v"] == 9
729
730 def test_path_graph(self):
731 G = nx.path_graph(4)
732 assert nx.single_source_bellman_ford_path(G, 0) == {
733 0: [0],
734 1: [0, 1],
735 2: [0, 1, 2],
736 3: [0, 1, 2, 3],
737 }
738 assert nx.single_source_bellman_ford_path_length(G, 0) == {
739 0: 0,
740 1: 1,
741 2: 2,
742 3: 3,
743 }
744 assert nx.single_source_bellman_ford(G, 0) == (
745 {0: 0, 1: 1, 2: 2, 3: 3},
746 {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 1, 2, 3]},
747 )
748 assert nx.bellman_ford_predecessor_and_distance(G, 0) == (
749 {0: [], 1: [0], 2: [1], 3: [2]},
750 {0: 0, 1: 1, 2: 2, 3: 3},
751 )
752 assert nx.goldberg_radzik(G, 0) == (
753 {0: None, 1: 0, 2: 1, 3: 2},
754 {0: 0, 1: 1, 2: 2, 3: 3},
755 )
756 assert nx.single_source_bellman_ford_path(G, 3) == {
757 0: [3, 2, 1, 0],
758 1: [3, 2, 1],
759 2: [3, 2],
760 3: [3],
761 }
762 assert nx.single_source_bellman_ford_path_length(G, 3) == {
763 0: 3,
764 1: 2,
765 2: 1,
766 3: 0,
767 }
768 assert nx.single_source_bellman_ford(G, 3) == (
769 {0: 3, 1: 2, 2: 1, 3: 0},
770 {0: [3, 2, 1, 0], 1: [3, 2, 1], 2: [3, 2], 3: [3]},
771 )
772 assert nx.bellman_ford_predecessor_and_distance(G, 3) == (
773 {0: [1], 1: [2], 2: [3], 3: []},
774 {0: 3, 1: 2, 2: 1, 3: 0},
775 )
776 assert nx.goldberg_radzik(G, 3) == (
777 {0: 1, 1: 2, 2: 3, 3: None},
778 {0: 3, 1: 2, 2: 1, 3: 0},
779 )
780
781 def test_4_cycle(self):
782 # 4-cycle
783 G = nx.Graph([(0, 1), (1, 2), (2, 3), (3, 0)])
784 dist, path = nx.single_source_bellman_ford(G, 0)
785 assert dist == {0: 0, 1: 1, 2: 2, 3: 1}
786 assert path[0] == [0]
787 assert path[1] == [0, 1]
788 assert path[2] in [[0, 1, 2], [0, 3, 2]]
789 assert path[3] == [0, 3]
790
791 pred, dist = nx.bellman_ford_predecessor_and_distance(G, 0)
792 assert pred[0] == []
793 assert pred[1] == [0]
794 assert pred[2] in [[1, 3], [3, 1]]
795 assert pred[3] == [0]
796 assert dist == {0: 0, 1: 1, 2: 2, 3: 1}
797
798 pred, dist = nx.goldberg_radzik(G, 0)
799 assert pred[0] is None
800 assert pred[1] == 0
801 assert pred[2] in [1, 3]
802 assert pred[3] == 0
803 assert dist == {0: 0, 1: 1, 2: 2, 3: 1}
804
805 def test_negative_weight(self):
806 G = nx.DiGraph()
807 G.add_nodes_from("abcd")
808 G.add_edge("a", "d", weight=0)
809 G.add_edge("a", "b", weight=1)
810 G.add_edge("b", "c", weight=-3)
811 G.add_edge("c", "d", weight=1)
812
813 assert nx.bellman_ford_path(G, "a", "d") == ["a", "b", "c", "d"]
814 assert nx.bellman_ford_path_length(G, "a", "d") == -1
815
816 def test_zero_cycle_smoke(self):
817 D = nx.DiGraph()
818 D.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1), (3, 1, -2)])
819
820 nx.bellman_ford_path(D, 1, 3)
821 nx.dijkstra_path(D, 1, 3)
822 nx.bidirectional_dijkstra(D, 1, 3)
823 # FIXME nx.goldberg_radzik(D, 1)
824
825
826 class TestJohnsonAlgorithm(WeightedTestBase):
827 def test_single_node_graph(self):
828 with pytest.raises(nx.NetworkXError):
829 G = nx.DiGraph()
830 G.add_node(0)
831 nx.johnson(G)
832
833 def test_negative_cycle(self):
834 G = nx.DiGraph()
835 G.add_weighted_edges_from(
836 [
837 ("0", "3", 3),
838 ("0", "1", -5),
839 ("1", "0", -5),
840 ("0", "2", 2),
841 ("1", "2", 4),
842 ("2", "3", 1),
843 ]
844 )
845 pytest.raises(nx.NetworkXUnbounded, nx.johnson, G)
846 G = nx.Graph()
847 G.add_weighted_edges_from(
848 [
849 ("0", "3", 3),
850 ("0", "1", -5),
851 ("1", "0", -5),
852 ("0", "2", 2),
853 ("1", "2", 4),
854 ("2", "3", 1),
855 ]
856 )
857 pytest.raises(nx.NetworkXUnbounded, nx.johnson, G)
858
859 def test_negative_weights(self):
860 G = nx.DiGraph()
861 G.add_weighted_edges_from(
862 [("0", "3", 3), ("0", "1", -5), ("0", "2", 2), ("1", "2", 4), ("2", "3", 1)]
863 )
864 paths = nx.johnson(G)
865 assert paths == {
866 "1": {"1": ["1"], "3": ["1", "2", "3"], "2": ["1", "2"]},
867 "0": {
868 "1": ["0", "1"],
869 "0": ["0"],
870 "3": ["0", "1", "2", "3"],
871 "2": ["0", "1", "2"],
872 },
873 "3": {"3": ["3"]},
874 "2": {"3": ["2", "3"], "2": ["2"]},
875 }
876
877 def test_unweighted_graph(self):
878 with pytest.raises(nx.NetworkXError):
879 G = nx.path_graph(5)
880 nx.johnson(G)
881
882 def test_graphs(self):
883 validate_path(self.XG, "s", "v", 9, nx.johnson(self.XG)["s"]["v"])
884 validate_path(self.MXG, "s", "v", 9, nx.johnson(self.MXG)["s"]["v"])
885 validate_path(self.XG2, 1, 3, 4, nx.johnson(self.XG2)[1][3])
886 validate_path(self.XG3, 0, 3, 15, nx.johnson(self.XG3)[0][3])
887 validate_path(self.XG4, 0, 2, 4, nx.johnson(self.XG4)[0][2])
888 validate_path(self.MXG4, 0, 2, 4, nx.johnson(self.MXG4)[0][2])