test_maxflow.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. """Maximum flow algorithms test suite.
  2. """
  3. import pytest
  4. import networkx as nx
  5. from networkx.algorithms.flow import (
  6. boykov_kolmogorov,
  7. build_flow_dict,
  8. build_residual_network,
  9. dinitz,
  10. edmonds_karp,
  11. preflow_push,
  12. shortest_augmenting_path,
  13. )
  14. flow_funcs = {
  15. boykov_kolmogorov,
  16. dinitz,
  17. edmonds_karp,
  18. preflow_push,
  19. shortest_augmenting_path,
  20. }
  21. max_min_funcs = {nx.maximum_flow, nx.minimum_cut}
  22. flow_value_funcs = {nx.maximum_flow_value, nx.minimum_cut_value}
  23. interface_funcs = max_min_funcs & flow_value_funcs
  24. all_funcs = flow_funcs & interface_funcs
  25. def compute_cutset(G, partition):
  26. reachable, non_reachable = partition
  27. cutset = set()
  28. for u, nbrs in ((n, G[n]) for n in reachable):
  29. cutset.update((u, v) for v in nbrs if v in non_reachable)
  30. return cutset
  31. def validate_flows(G, s, t, flowDict, solnValue, capacity, flow_func):
  32. errmsg = f"Assertion failed in function: {flow_func.__name__}"
  33. assert set(G) == set(flowDict), errmsg
  34. for u in G:
  35. assert set(G[u]) == set(flowDict[u]), errmsg
  36. excess = {u: 0 for u in flowDict}
  37. for u in flowDict:
  38. for v, flow in flowDict[u].items():
  39. if capacity in G[u][v]:
  40. assert flow <= G[u][v][capacity]
  41. assert flow >= 0, errmsg
  42. excess[u] -= flow
  43. excess[v] += flow
  44. for u, exc in excess.items():
  45. if u == s:
  46. assert exc == -solnValue, errmsg
  47. elif u == t:
  48. assert exc == solnValue, errmsg
  49. else:
  50. assert exc == 0, errmsg
  51. def validate_cuts(G, s, t, solnValue, partition, capacity, flow_func):
  52. errmsg = f"Assertion failed in function: {flow_func.__name__}"
  53. assert all(n in G for n in partition[0]), errmsg
  54. assert all(n in G for n in partition[1]), errmsg
  55. cutset = compute_cutset(G, partition)
  56. assert all(G.has_edge(u, v) for (u, v) in cutset), errmsg
  57. assert solnValue == sum(G[u][v][capacity] for (u, v) in cutset), errmsg
  58. H = G.copy()
  59. H.remove_edges_from(cutset)
  60. if not G.is_directed():
  61. assert not nx.is_connected(H), errmsg
  62. else:
  63. assert not nx.is_strongly_connected(H), errmsg
  64. def compare_flows_and_cuts(G, s, t, solnFlows, solnValue, capacity="capacity"):
  65. for flow_func in flow_funcs:
  66. errmsg = f"Assertion failed in function: {flow_func.__name__}"
  67. R = flow_func(G, s, t, capacity)
  68. # Test both legacy and new implementations.
  69. flow_value = R.graph["flow_value"]
  70. flow_dict = build_flow_dict(G, R)
  71. assert flow_value == solnValue, errmsg
  72. validate_flows(G, s, t, flow_dict, solnValue, capacity, flow_func)
  73. # Minimum cut
  74. cut_value, partition = nx.minimum_cut(
  75. G, s, t, capacity=capacity, flow_func=flow_func
  76. )
  77. validate_cuts(G, s, t, solnValue, partition, capacity, flow_func)
  78. class TestMaxflowMinCutCommon:
  79. def test_graph1(self):
  80. # Trivial undirected graph
  81. G = nx.Graph()
  82. G.add_edge(1, 2, capacity=1.0)
  83. solnFlows = {1: {2: 1.0}, 2: {1: 1.0}}
  84. compare_flows_and_cuts(G, 1, 2, solnFlows, 1.0)
  85. def test_graph2(self):
  86. # A more complex undirected graph
  87. # adapted from www.topcoder.com/tc?module=Statc&d1=tutorials&d2=maxFlow
  88. G = nx.Graph()
  89. G.add_edge("x", "a", capacity=3.0)
  90. G.add_edge("x", "b", capacity=1.0)
  91. G.add_edge("a", "c", capacity=3.0)
  92. G.add_edge("b", "c", capacity=5.0)
  93. G.add_edge("b", "d", capacity=4.0)
  94. G.add_edge("d", "e", capacity=2.0)
  95. G.add_edge("c", "y", capacity=2.0)
  96. G.add_edge("e", "y", capacity=3.0)
  97. H = {
  98. "x": {"a": 3, "b": 1},
  99. "a": {"c": 3, "x": 3},
  100. "b": {"c": 1, "d": 2, "x": 1},
  101. "c": {"a": 3, "b": 1, "y": 2},
  102. "d": {"b": 2, "e": 2},
  103. "e": {"d": 2, "y": 2},
  104. "y": {"c": 2, "e": 2},
  105. }
  106. compare_flows_and_cuts(G, "x", "y", H, 4.0)
  107. def test_digraph1(self):
  108. # The classic directed graph example
  109. G = nx.DiGraph()
  110. G.add_edge("a", "b", capacity=1000.0)
  111. G.add_edge("a", "c", capacity=1000.0)
  112. G.add_edge("b", "c", capacity=1.0)
  113. G.add_edge("b", "d", capacity=1000.0)
  114. G.add_edge("c", "d", capacity=1000.0)
  115. H = {
  116. "a": {"b": 1000.0, "c": 1000.0},
  117. "b": {"c": 0, "d": 1000.0},
  118. "c": {"d": 1000.0},
  119. "d": {},
  120. }
  121. compare_flows_and_cuts(G, "a", "d", H, 2000.0)
  122. def test_digraph2(self):
  123. # An example in which some edges end up with zero flow.
  124. G = nx.DiGraph()
  125. G.add_edge("s", "b", capacity=2)
  126. G.add_edge("s", "c", capacity=1)
  127. G.add_edge("c", "d", capacity=1)
  128. G.add_edge("d", "a", capacity=1)
  129. G.add_edge("b", "a", capacity=2)
  130. G.add_edge("a", "t", capacity=2)
  131. H = {
  132. "s": {"b": 2, "c": 0},
  133. "c": {"d": 0},
  134. "d": {"a": 0},
  135. "b": {"a": 2},
  136. "a": {"t": 2},
  137. "t": {},
  138. }
  139. compare_flows_and_cuts(G, "s", "t", H, 2)
  140. def test_digraph3(self):
  141. # A directed graph example from Cormen et al.
  142. G = nx.DiGraph()
  143. G.add_edge("s", "v1", capacity=16.0)
  144. G.add_edge("s", "v2", capacity=13.0)
  145. G.add_edge("v1", "v2", capacity=10.0)
  146. G.add_edge("v2", "v1", capacity=4.0)
  147. G.add_edge("v1", "v3", capacity=12.0)
  148. G.add_edge("v3", "v2", capacity=9.0)
  149. G.add_edge("v2", "v4", capacity=14.0)
  150. G.add_edge("v4", "v3", capacity=7.0)
  151. G.add_edge("v3", "t", capacity=20.0)
  152. G.add_edge("v4", "t", capacity=4.0)
  153. H = {
  154. "s": {"v1": 12.0, "v2": 11.0},
  155. "v2": {"v1": 0, "v4": 11.0},
  156. "v1": {"v2": 0, "v3": 12.0},
  157. "v3": {"v2": 0, "t": 19.0},
  158. "v4": {"v3": 7.0, "t": 4.0},
  159. "t": {},
  160. }
  161. compare_flows_and_cuts(G, "s", "t", H, 23.0)
  162. def test_digraph4(self):
  163. # A more complex directed graph
  164. # from www.topcoder.com/tc?module=Statc&d1=tutorials&d2=maxFlow
  165. G = nx.DiGraph()
  166. G.add_edge("x", "a", capacity=3.0)
  167. G.add_edge("x", "b", capacity=1.0)
  168. G.add_edge("a", "c", capacity=3.0)
  169. G.add_edge("b", "c", capacity=5.0)
  170. G.add_edge("b", "d", capacity=4.0)
  171. G.add_edge("d", "e", capacity=2.0)
  172. G.add_edge("c", "y", capacity=2.0)
  173. G.add_edge("e", "y", capacity=3.0)
  174. H = {
  175. "x": {"a": 2.0, "b": 1.0},
  176. "a": {"c": 2.0},
  177. "b": {"c": 0, "d": 1.0},
  178. "c": {"y": 2.0},
  179. "d": {"e": 1.0},
  180. "e": {"y": 1.0},
  181. "y": {},
  182. }
  183. compare_flows_and_cuts(G, "x", "y", H, 3.0)
  184. def test_wikipedia_dinitz_example(self):
  185. # Nice example from https://en.wikipedia.org/wiki/Dinic's_algorithm
  186. G = nx.DiGraph()
  187. G.add_edge("s", 1, capacity=10)
  188. G.add_edge("s", 2, capacity=10)
  189. G.add_edge(1, 3, capacity=4)
  190. G.add_edge(1, 4, capacity=8)
  191. G.add_edge(1, 2, capacity=2)
  192. G.add_edge(2, 4, capacity=9)
  193. G.add_edge(3, "t", capacity=10)
  194. G.add_edge(4, 3, capacity=6)
  195. G.add_edge(4, "t", capacity=10)
  196. solnFlows = {
  197. 1: {2: 0, 3: 4, 4: 6},
  198. 2: {4: 9},
  199. 3: {"t": 9},
  200. 4: {3: 5, "t": 10},
  201. "s": {1: 10, 2: 9},
  202. "t": {},
  203. }
  204. compare_flows_and_cuts(G, "s", "t", solnFlows, 19)
  205. def test_optional_capacity(self):
  206. # Test optional capacity parameter.
  207. G = nx.DiGraph()
  208. G.add_edge("x", "a", spam=3.0)
  209. G.add_edge("x", "b", spam=1.0)
  210. G.add_edge("a", "c", spam=3.0)
  211. G.add_edge("b", "c", spam=5.0)
  212. G.add_edge("b", "d", spam=4.0)
  213. G.add_edge("d", "e", spam=2.0)
  214. G.add_edge("c", "y", spam=2.0)
  215. G.add_edge("e", "y", spam=3.0)
  216. solnFlows = {
  217. "x": {"a": 2.0, "b": 1.0},
  218. "a": {"c": 2.0},
  219. "b": {"c": 0, "d": 1.0},
  220. "c": {"y": 2.0},
  221. "d": {"e": 1.0},
  222. "e": {"y": 1.0},
  223. "y": {},
  224. }
  225. solnValue = 3.0
  226. s = "x"
  227. t = "y"
  228. compare_flows_and_cuts(G, s, t, solnFlows, solnValue, capacity="spam")
  229. def test_digraph_infcap_edges(self):
  230. # DiGraph with infinite capacity edges
  231. G = nx.DiGraph()
  232. G.add_edge("s", "a")
  233. G.add_edge("s", "b", capacity=30)
  234. G.add_edge("a", "c", capacity=25)
  235. G.add_edge("b", "c", capacity=12)
  236. G.add_edge("a", "t", capacity=60)
  237. G.add_edge("c", "t")
  238. H = {
  239. "s": {"a": 85, "b": 12},
  240. "a": {"c": 25, "t": 60},
  241. "b": {"c": 12},
  242. "c": {"t": 37},
  243. "t": {},
  244. }
  245. compare_flows_and_cuts(G, "s", "t", H, 97)
  246. # DiGraph with infinite capacity digon
  247. G = nx.DiGraph()
  248. G.add_edge("s", "a", capacity=85)
  249. G.add_edge("s", "b", capacity=30)
  250. G.add_edge("a", "c")
  251. G.add_edge("c", "a")
  252. G.add_edge("b", "c", capacity=12)
  253. G.add_edge("a", "t", capacity=60)
  254. G.add_edge("c", "t", capacity=37)
  255. H = {
  256. "s": {"a": 85, "b": 12},
  257. "a": {"c": 25, "t": 60},
  258. "c": {"a": 0, "t": 37},
  259. "b": {"c": 12},
  260. "t": {},
  261. }
  262. compare_flows_and_cuts(G, "s", "t", H, 97)
  263. def test_digraph_infcap_path(self):
  264. # Graph with infinite capacity (s, t)-path
  265. G = nx.DiGraph()
  266. G.add_edge("s", "a")
  267. G.add_edge("s", "b", capacity=30)
  268. G.add_edge("a", "c")
  269. G.add_edge("b", "c", capacity=12)
  270. G.add_edge("a", "t", capacity=60)
  271. G.add_edge("c", "t")
  272. for flow_func in all_funcs:
  273. pytest.raises(nx.NetworkXUnbounded, flow_func, G, "s", "t")
  274. def test_graph_infcap_edges(self):
  275. # Undirected graph with infinite capacity edges
  276. G = nx.Graph()
  277. G.add_edge("s", "a")
  278. G.add_edge("s", "b", capacity=30)
  279. G.add_edge("a", "c", capacity=25)
  280. G.add_edge("b", "c", capacity=12)
  281. G.add_edge("a", "t", capacity=60)
  282. G.add_edge("c", "t")
  283. H = {
  284. "s": {"a": 85, "b": 12},
  285. "a": {"c": 25, "s": 85, "t": 60},
  286. "b": {"c": 12, "s": 12},
  287. "c": {"a": 25, "b": 12, "t": 37},
  288. "t": {"a": 60, "c": 37},
  289. }
  290. compare_flows_and_cuts(G, "s", "t", H, 97)
  291. def test_digraph5(self):
  292. # From ticket #429 by mfrasca.
  293. G = nx.DiGraph()
  294. G.add_edge("s", "a", capacity=2)
  295. G.add_edge("s", "b", capacity=2)
  296. G.add_edge("a", "b", capacity=5)
  297. G.add_edge("a", "t", capacity=1)
  298. G.add_edge("b", "a", capacity=1)
  299. G.add_edge("b", "t", capacity=3)
  300. flowSoln = {
  301. "a": {"b": 1, "t": 1},
  302. "b": {"a": 0, "t": 3},
  303. "s": {"a": 2, "b": 2},
  304. "t": {},
  305. }
  306. compare_flows_and_cuts(G, "s", "t", flowSoln, 4)
  307. def test_disconnected(self):
  308. G = nx.Graph()
  309. G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity")
  310. G.remove_node(1)
  311. assert nx.maximum_flow_value(G, 0, 3) == 0
  312. flowSoln = {0: {}, 2: {3: 0}, 3: {2: 0}}
  313. compare_flows_and_cuts(G, 0, 3, flowSoln, 0)
  314. def test_source_target_not_in_graph(self):
  315. G = nx.Graph()
  316. G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity")
  317. G.remove_node(0)
  318. for flow_func in all_funcs:
  319. pytest.raises(nx.NetworkXError, flow_func, G, 0, 3)
  320. G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity")
  321. G.remove_node(3)
  322. for flow_func in all_funcs:
  323. pytest.raises(nx.NetworkXError, flow_func, G, 0, 3)
  324. def test_source_target_coincide(self):
  325. G = nx.Graph()
  326. G.add_node(0)
  327. for flow_func in all_funcs:
  328. pytest.raises(nx.NetworkXError, flow_func, G, 0, 0)
  329. def test_multigraphs_raise(self):
  330. G = nx.MultiGraph()
  331. M = nx.MultiDiGraph()
  332. G.add_edges_from([(0, 1), (1, 0)], capacity=True)
  333. for flow_func in all_funcs:
  334. pytest.raises(nx.NetworkXError, flow_func, G, 0, 0)
  335. class TestMaxFlowMinCutInterface:
  336. def setup_method(self):
  337. G = nx.DiGraph()
  338. G.add_edge("x", "a", capacity=3.0)
  339. G.add_edge("x", "b", capacity=1.0)
  340. G.add_edge("a", "c", capacity=3.0)
  341. G.add_edge("b", "c", capacity=5.0)
  342. G.add_edge("b", "d", capacity=4.0)
  343. G.add_edge("d", "e", capacity=2.0)
  344. G.add_edge("c", "y", capacity=2.0)
  345. G.add_edge("e", "y", capacity=3.0)
  346. self.G = G
  347. H = nx.DiGraph()
  348. H.add_edge(0, 1, capacity=1.0)
  349. H.add_edge(1, 2, capacity=1.0)
  350. self.H = H
  351. def test_flow_func_not_callable(self):
  352. elements = ["this_should_be_callable", 10, {1, 2, 3}]
  353. G = nx.Graph()
  354. G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity")
  355. for flow_func in interface_funcs:
  356. for element in elements:
  357. pytest.raises(nx.NetworkXError, flow_func, G, 0, 1, flow_func=element)
  358. pytest.raises(nx.NetworkXError, flow_func, G, 0, 1, flow_func=element)
  359. def test_flow_func_parameters(self):
  360. G = self.G
  361. fv = 3.0
  362. for interface_func in interface_funcs:
  363. for flow_func in flow_funcs:
  364. errmsg = (
  365. f"Assertion failed in function: {flow_func.__name__} "
  366. f"in interface {interface_func.__name__}"
  367. )
  368. result = interface_func(G, "x", "y", flow_func=flow_func)
  369. if interface_func in max_min_funcs:
  370. result = result[0]
  371. assert fv == result, errmsg
  372. def test_minimum_cut_no_cutoff(self):
  373. G = self.G
  374. pytest.raises(
  375. nx.NetworkXError,
  376. nx.minimum_cut,
  377. G,
  378. "x",
  379. "y",
  380. flow_func=preflow_push,
  381. cutoff=1.0,
  382. )
  383. pytest.raises(
  384. nx.NetworkXError,
  385. nx.minimum_cut_value,
  386. G,
  387. "x",
  388. "y",
  389. flow_func=preflow_push,
  390. cutoff=1.0,
  391. )
  392. def test_kwargs(self):
  393. G = self.H
  394. fv = 1.0
  395. to_test = (
  396. (shortest_augmenting_path, {"two_phase": True}),
  397. (preflow_push, {"global_relabel_freq": 5}),
  398. )
  399. for interface_func in interface_funcs:
  400. for flow_func, kwargs in to_test:
  401. errmsg = (
  402. f"Assertion failed in function: {flow_func.__name__} "
  403. f"in interface {interface_func.__name__}"
  404. )
  405. result = interface_func(G, 0, 2, flow_func=flow_func, **kwargs)
  406. if interface_func in max_min_funcs:
  407. result = result[0]
  408. assert fv == result, errmsg
  409. def test_kwargs_default_flow_func(self):
  410. G = self.H
  411. for interface_func in interface_funcs:
  412. pytest.raises(
  413. nx.NetworkXError, interface_func, G, 0, 1, global_relabel_freq=2
  414. )
  415. def test_reusing_residual(self):
  416. G = self.G
  417. fv = 3.0
  418. s, t = "x", "y"
  419. R = build_residual_network(G, "capacity")
  420. for interface_func in interface_funcs:
  421. for flow_func in flow_funcs:
  422. errmsg = (
  423. f"Assertion failed in function: {flow_func.__name__} "
  424. f"in interface {interface_func.__name__}"
  425. )
  426. for i in range(3):
  427. result = interface_func(
  428. G, "x", "y", flow_func=flow_func, residual=R
  429. )
  430. if interface_func in max_min_funcs:
  431. result = result[0]
  432. assert fv == result, errmsg
  433. # Tests specific to one algorithm
  434. def test_preflow_push_global_relabel_freq():
  435. G = nx.DiGraph()
  436. G.add_edge(1, 2, capacity=1)
  437. R = preflow_push(G, 1, 2, global_relabel_freq=None)
  438. assert R.graph["flow_value"] == 1
  439. pytest.raises(nx.NetworkXError, preflow_push, G, 1, 2, global_relabel_freq=-1)
  440. def test_preflow_push_makes_enough_space():
  441. # From ticket #1542
  442. G = nx.DiGraph()
  443. nx.add_path(G, [0, 1, 3], capacity=1)
  444. nx.add_path(G, [1, 2, 3], capacity=1)
  445. R = preflow_push(G, 0, 3, value_only=False)
  446. assert R.graph["flow_value"] == 1
  447. def test_shortest_augmenting_path_two_phase():
  448. k = 5
  449. p = 1000
  450. G = nx.DiGraph()
  451. for i in range(k):
  452. G.add_edge("s", (i, 0), capacity=1)
  453. nx.add_path(G, ((i, j) for j in range(p)), capacity=1)
  454. G.add_edge((i, p - 1), "t", capacity=1)
  455. R = shortest_augmenting_path(G, "s", "t", two_phase=True)
  456. assert R.graph["flow_value"] == k
  457. R = shortest_augmenting_path(G, "s", "t", two_phase=False)
  458. assert R.graph["flow_value"] == k
  459. class TestCutoff:
  460. def test_cutoff(self):
  461. k = 5
  462. p = 1000
  463. G = nx.DiGraph()
  464. for i in range(k):
  465. G.add_edge("s", (i, 0), capacity=2)
  466. nx.add_path(G, ((i, j) for j in range(p)), capacity=2)
  467. G.add_edge((i, p - 1), "t", capacity=2)
  468. R = shortest_augmenting_path(G, "s", "t", two_phase=True, cutoff=k)
  469. assert k <= R.graph["flow_value"] <= (2 * k)
  470. R = shortest_augmenting_path(G, "s", "t", two_phase=False, cutoff=k)
  471. assert k <= R.graph["flow_value"] <= (2 * k)
  472. R = edmonds_karp(G, "s", "t", cutoff=k)
  473. assert k <= R.graph["flow_value"] <= (2 * k)
  474. R = dinitz(G, "s", "t", cutoff=k)
  475. assert k <= R.graph["flow_value"] <= (2 * k)
  476. R = boykov_kolmogorov(G, "s", "t", cutoff=k)
  477. assert k <= R.graph["flow_value"] <= (2 * k)
  478. def test_complete_graph_cutoff(self):
  479. G = nx.complete_graph(5)
  480. nx.set_edge_attributes(G, {(u, v): 1 for u, v in G.edges()}, "capacity")
  481. for flow_func in [
  482. shortest_augmenting_path,
  483. edmonds_karp,
  484. dinitz,
  485. boykov_kolmogorov,
  486. ]:
  487. for cutoff in [3, 2, 1]:
  488. result = nx.maximum_flow_value(
  489. G, 0, 4, flow_func=flow_func, cutoff=cutoff
  490. )
  491. assert cutoff == result, f"cutoff error in {flow_func.__name__}"