test_flow.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import numpy as np
  2. from numpy.testing import assert_array_equal
  3. import pytest
  4. from scipy.sparse import csr_array, csc_array, csr_matrix
  5. from scipy.sparse.csgraph import maximum_flow
  6. from scipy.sparse.csgraph._flow import (
  7. _add_reverse_edges, _make_edge_pointers, _make_tails
  8. )
  9. methods = ['edmonds_karp', 'dinic']
  10. def test_raises_on_dense_input():
  11. with pytest.raises(TypeError):
  12. graph = np.array([[0, 1], [0, 0]])
  13. maximum_flow(graph, 0, 1)
  14. maximum_flow(graph, 0, 1, method='edmonds_karp')
  15. def test_raises_on_csc_input():
  16. with pytest.raises(TypeError):
  17. graph = csc_array([[0, 1], [0, 0]])
  18. maximum_flow(graph, 0, 1)
  19. maximum_flow(graph, 0, 1, method='edmonds_karp')
  20. def test_raises_on_floating_point_input():
  21. with pytest.raises(ValueError):
  22. graph = csr_array([[0, 1.5], [0, 0]], dtype=np.float64)
  23. maximum_flow(graph, 0, 1)
  24. maximum_flow(graph, 0, 1, method='edmonds_karp')
  25. def test_raises_on_non_square_input():
  26. with pytest.raises(ValueError):
  27. graph = csr_array([[0, 1, 2], [2, 1, 0]])
  28. maximum_flow(graph, 0, 1)
  29. def test_raises_when_source_is_sink():
  30. with pytest.raises(ValueError):
  31. graph = csr_array([[0, 1], [0, 0]])
  32. maximum_flow(graph, 0, 0)
  33. maximum_flow(graph, 0, 0, method='edmonds_karp')
  34. @pytest.mark.parametrize('method', methods)
  35. @pytest.mark.parametrize('source', [-1, 2, 3])
  36. def test_raises_when_source_is_out_of_bounds(source, method):
  37. with pytest.raises(ValueError):
  38. graph = csr_array([[0, 1], [0, 0]])
  39. maximum_flow(graph, source, 1, method=method)
  40. @pytest.mark.parametrize('method', methods)
  41. @pytest.mark.parametrize('sink', [-1, 2, 3])
  42. def test_raises_when_sink_is_out_of_bounds(sink, method):
  43. with pytest.raises(ValueError):
  44. graph = csr_array([[0, 1], [0, 0]])
  45. maximum_flow(graph, 0, sink, method=method)
  46. @pytest.mark.parametrize('method', methods)
  47. def test_simple_graph(method):
  48. # This graph looks as follows:
  49. # (0) --5--> (1)
  50. graph = csr_array([[0, 5], [0, 0]])
  51. res = maximum_flow(graph, 0, 1, method=method)
  52. assert res.flow_value == 5
  53. expected_flow = np.array([[0, 5], [-5, 0]])
  54. assert_array_equal(res.flow.toarray(), expected_flow)
  55. @pytest.mark.parametrize('method', methods)
  56. def test_return_type(method):
  57. graph = csr_array([[0, 5], [0, 0]])
  58. assert isinstance(maximum_flow(graph, 0, 1, method=method).flow, csr_array)
  59. graph = csr_matrix([[0, 5], [0, 0]])
  60. assert isinstance(maximum_flow(graph, 0, 1, method=method).flow, csr_matrix)
  61. @pytest.mark.parametrize('method', methods)
  62. def test_bottle_neck_graph(method):
  63. # This graph cannot use the full capacity between 0 and 1:
  64. # (0) --5--> (1) --3--> (2)
  65. graph = csr_array([[0, 5, 0], [0, 0, 3], [0, 0, 0]])
  66. res = maximum_flow(graph, 0, 2, method=method)
  67. assert res.flow_value == 3
  68. expected_flow = np.array([[0, 3, 0], [-3, 0, 3], [0, -3, 0]])
  69. assert_array_equal(res.flow.toarray(), expected_flow)
  70. @pytest.mark.parametrize('method', methods)
  71. def test_backwards_flow(method):
  72. # This example causes backwards flow between vertices 3 and 4,
  73. # and so this test ensures that we handle that accordingly. See
  74. # https://stackoverflow.com/q/38843963/5085211
  75. # for more information.
  76. graph = csr_array([[0, 10, 0, 0, 10, 0, 0, 0],
  77. [0, 0, 10, 0, 0, 0, 0, 0],
  78. [0, 0, 0, 10, 0, 0, 0, 0],
  79. [0, 0, 0, 0, 0, 0, 0, 10],
  80. [0, 0, 0, 10, 0, 10, 0, 0],
  81. [0, 0, 0, 0, 0, 0, 10, 0],
  82. [0, 0, 0, 0, 0, 0, 0, 10],
  83. [0, 0, 0, 0, 0, 0, 0, 0]])
  84. res = maximum_flow(graph, 0, 7, method=method)
  85. assert res.flow_value == 20
  86. expected_flow = np.array([[0, 10, 0, 0, 10, 0, 0, 0],
  87. [-10, 0, 10, 0, 0, 0, 0, 0],
  88. [0, -10, 0, 10, 0, 0, 0, 0],
  89. [0, 0, -10, 0, 0, 0, 0, 10],
  90. [-10, 0, 0, 0, 0, 10, 0, 0],
  91. [0, 0, 0, 0, -10, 0, 10, 0],
  92. [0, 0, 0, 0, 0, -10, 0, 10],
  93. [0, 0, 0, -10, 0, 0, -10, 0]])
  94. assert_array_equal(res.flow.toarray(), expected_flow)
  95. @pytest.mark.parametrize('method', methods)
  96. def test_example_from_clrs_chapter_26_1(method):
  97. # See page 659 in CLRS second edition, but note that the maximum flow
  98. # we find is slightly different than the one in CLRS; we push a flow of
  99. # 12 to v_1 instead of v_2.
  100. graph = csr_array([[0, 16, 13, 0, 0, 0],
  101. [0, 0, 10, 12, 0, 0],
  102. [0, 4, 0, 0, 14, 0],
  103. [0, 0, 9, 0, 0, 20],
  104. [0, 0, 0, 7, 0, 4],
  105. [0, 0, 0, 0, 0, 0]])
  106. res = maximum_flow(graph, 0, 5, method=method)
  107. assert res.flow_value == 23
  108. expected_flow = np.array([[0, 12, 11, 0, 0, 0],
  109. [-12, 0, 0, 12, 0, 0],
  110. [-11, 0, 0, 0, 11, 0],
  111. [0, -12, 0, 0, -7, 19],
  112. [0, 0, -11, 7, 0, 4],
  113. [0, 0, 0, -19, -4, 0]])
  114. assert_array_equal(res.flow.toarray(), expected_flow)
  115. @pytest.mark.parametrize('method', methods)
  116. def test_disconnected_graph(method):
  117. # This tests the following disconnected graph:
  118. # (0) --5--> (1) (2) --3--> (3)
  119. graph = csr_array([[0, 5, 0, 0],
  120. [0, 0, 0, 0],
  121. [0, 0, 9, 3],
  122. [0, 0, 0, 0]])
  123. res = maximum_flow(graph, 0, 3, method=method)
  124. assert res.flow_value == 0
  125. expected_flow = np.zeros((4, 4), dtype=np.int32)
  126. assert_array_equal(res.flow.toarray(), expected_flow)
  127. @pytest.mark.parametrize('method', methods)
  128. def test_add_reverse_edges_large_graph(method):
  129. # Regression test for https://github.com/scipy/scipy/issues/14385
  130. n = 100_000
  131. indices = np.arange(1, n)
  132. indptr = np.array(list(range(n)) + [n - 1])
  133. data = np.ones(n - 1, dtype=np.int32)
  134. graph = csr_array((data, indices, indptr), shape=(n, n))
  135. res = maximum_flow(graph, 0, n - 1, method=method)
  136. assert res.flow_value == 1
  137. expected_flow = graph - graph.transpose()
  138. assert_array_equal(res.flow.data, expected_flow.data)
  139. assert_array_equal(res.flow.indices, expected_flow.indices)
  140. assert_array_equal(res.flow.indptr, expected_flow.indptr)
  141. @pytest.mark.parametrize("a,b_data_expected", [
  142. ([[]], []),
  143. ([[0], [0]], []),
  144. ([[1, 0, 2], [0, 0, 0], [0, 3, 0]], [1, 2, 0, 0, 3]),
  145. ([[9, 8, 7], [4, 5, 6], [0, 0, 0]], [9, 8, 7, 4, 5, 6, 0, 0])])
  146. def test_add_reverse_edges(a, b_data_expected):
  147. """Test that the reversal of the edges of the input graph works
  148. as expected.
  149. """
  150. a = csr_array(a, dtype=np.int32, shape=(len(a), len(a)))
  151. b = _add_reverse_edges(a)
  152. assert_array_equal(b.data, b_data_expected)
  153. @pytest.mark.parametrize("a,expected", [
  154. ([[]], []),
  155. ([[0]], []),
  156. ([[1]], [0]),
  157. ([[0, 1], [10, 0]], [1, 0]),
  158. ([[1, 0, 2], [0, 0, 3], [4, 5, 0]], [0, 3, 4, 1, 2])
  159. ])
  160. def test_make_edge_pointers(a, expected):
  161. a = csr_array(a, dtype=np.int32)
  162. rev_edge_ptr = _make_edge_pointers(a)
  163. assert_array_equal(rev_edge_ptr, expected)
  164. @pytest.mark.parametrize("a,expected", [
  165. ([[]], []),
  166. ([[0]], []),
  167. ([[1]], [0]),
  168. ([[0, 1], [10, 0]], [0, 1]),
  169. ([[1, 0, 2], [0, 0, 3], [4, 5, 0]], [0, 0, 1, 2, 2])
  170. ])
  171. def test_make_tails(a, expected):
  172. a = csr_array(a, dtype=np.int32)
  173. tails = _make_tails(a)
  174. assert_array_equal(tails, expected)