test_kcutsets.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. # Jordi Torrents
  2. # Test for k-cutsets
  3. import itertools
  4. import pytest
  5. import networkx as nx
  6. from networkx.algorithms import flow
  7. from networkx.algorithms.connectivity.kcutsets import _is_separating_set
  8. MAX_CUTSETS_TO_TEST = 4 # originally 100. cut to decrease testing time
  9. flow_funcs = [
  10. flow.boykov_kolmogorov,
  11. flow.dinitz,
  12. flow.edmonds_karp,
  13. flow.preflow_push,
  14. flow.shortest_augmenting_path,
  15. ]
  16. ##
  17. # Some nice synthetic graphs
  18. ##
  19. def graph_example_1():
  20. G = nx.convert_node_labels_to_integers(
  21. nx.grid_graph([5, 5]), label_attribute="labels"
  22. )
  23. rlabels = nx.get_node_attributes(G, "labels")
  24. labels = {v: k for k, v in rlabels.items()}
  25. for nodes in [
  26. (labels[(0, 0)], labels[(1, 0)]),
  27. (labels[(0, 4)], labels[(1, 4)]),
  28. (labels[(3, 0)], labels[(4, 0)]),
  29. (labels[(3, 4)], labels[(4, 4)]),
  30. ]:
  31. new_node = G.order() + 1
  32. # Petersen graph is triconnected
  33. P = nx.petersen_graph()
  34. G = nx.disjoint_union(G, P)
  35. # Add two edges between the grid and P
  36. G.add_edge(new_node + 1, nodes[0])
  37. G.add_edge(new_node, nodes[1])
  38. # K5 is 4-connected
  39. K = nx.complete_graph(5)
  40. G = nx.disjoint_union(G, K)
  41. # Add three edges between P and K5
  42. G.add_edge(new_node + 2, new_node + 11)
  43. G.add_edge(new_node + 3, new_node + 12)
  44. G.add_edge(new_node + 4, new_node + 13)
  45. # Add another K5 sharing a node
  46. G = nx.disjoint_union(G, K)
  47. nbrs = G[new_node + 10]
  48. G.remove_node(new_node + 10)
  49. for nbr in nbrs:
  50. G.add_edge(new_node + 17, nbr)
  51. G.add_edge(new_node + 16, new_node + 5)
  52. return G
  53. def torrents_and_ferraro_graph():
  54. G = nx.convert_node_labels_to_integers(
  55. nx.grid_graph([5, 5]), label_attribute="labels"
  56. )
  57. rlabels = nx.get_node_attributes(G, "labels")
  58. labels = {v: k for k, v in rlabels.items()}
  59. for nodes in [(labels[(0, 4)], labels[(1, 4)]), (labels[(3, 4)], labels[(4, 4)])]:
  60. new_node = G.order() + 1
  61. # Petersen graph is triconnected
  62. P = nx.petersen_graph()
  63. G = nx.disjoint_union(G, P)
  64. # Add two edges between the grid and P
  65. G.add_edge(new_node + 1, nodes[0])
  66. G.add_edge(new_node, nodes[1])
  67. # K5 is 4-connected
  68. K = nx.complete_graph(5)
  69. G = nx.disjoint_union(G, K)
  70. # Add three edges between P and K5
  71. G.add_edge(new_node + 2, new_node + 11)
  72. G.add_edge(new_node + 3, new_node + 12)
  73. G.add_edge(new_node + 4, new_node + 13)
  74. # Add another K5 sharing a node
  75. G = nx.disjoint_union(G, K)
  76. nbrs = G[new_node + 10]
  77. G.remove_node(new_node + 10)
  78. for nbr in nbrs:
  79. G.add_edge(new_node + 17, nbr)
  80. # Commenting this makes the graph not biconnected !!
  81. # This stupid mistake make one reviewer very angry :P
  82. G.add_edge(new_node + 16, new_node + 8)
  83. for nodes in [(labels[(0, 0)], labels[(1, 0)]), (labels[(3, 0)], labels[(4, 0)])]:
  84. new_node = G.order() + 1
  85. # Petersen graph is triconnected
  86. P = nx.petersen_graph()
  87. G = nx.disjoint_union(G, P)
  88. # Add two edges between the grid and P
  89. G.add_edge(new_node + 1, nodes[0])
  90. G.add_edge(new_node, nodes[1])
  91. # K5 is 4-connected
  92. K = nx.complete_graph(5)
  93. G = nx.disjoint_union(G, K)
  94. # Add three edges between P and K5
  95. G.add_edge(new_node + 2, new_node + 11)
  96. G.add_edge(new_node + 3, new_node + 12)
  97. G.add_edge(new_node + 4, new_node + 13)
  98. # Add another K5 sharing two nodes
  99. G = nx.disjoint_union(G, K)
  100. nbrs = G[new_node + 10]
  101. G.remove_node(new_node + 10)
  102. for nbr in nbrs:
  103. G.add_edge(new_node + 17, nbr)
  104. nbrs2 = G[new_node + 9]
  105. G.remove_node(new_node + 9)
  106. for nbr in nbrs2:
  107. G.add_edge(new_node + 18, nbr)
  108. return G
  109. # Helper function
  110. def _check_separating_sets(G):
  111. for cc in nx.connected_components(G):
  112. if len(cc) < 3:
  113. continue
  114. Gc = G.subgraph(cc)
  115. node_conn = nx.node_connectivity(Gc)
  116. all_cuts = nx.all_node_cuts(Gc)
  117. # Only test a limited number of cut sets to reduce test time.
  118. for cut in itertools.islice(all_cuts, MAX_CUTSETS_TO_TEST):
  119. assert node_conn == len(cut)
  120. assert not nx.is_connected(nx.restricted_view(G, cut, []))
  121. @pytest.mark.slow
  122. def test_torrents_and_ferraro_graph():
  123. G = torrents_and_ferraro_graph()
  124. _check_separating_sets(G)
  125. def test_example_1():
  126. G = graph_example_1()
  127. _check_separating_sets(G)
  128. def test_random_gnp():
  129. G = nx.gnp_random_graph(100, 0.1, seed=42)
  130. _check_separating_sets(G)
  131. def test_shell():
  132. constructor = [(20, 80, 0.8), (80, 180, 0.6)]
  133. G = nx.random_shell_graph(constructor, seed=42)
  134. _check_separating_sets(G)
  135. def test_configuration():
  136. deg_seq = nx.random_powerlaw_tree_sequence(100, tries=5, seed=72)
  137. G = nx.Graph(nx.configuration_model(deg_seq))
  138. G.remove_edges_from(nx.selfloop_edges(G))
  139. _check_separating_sets(G)
  140. def test_karate():
  141. G = nx.karate_club_graph()
  142. _check_separating_sets(G)
  143. def _generate_no_biconnected(max_attempts=50):
  144. attempts = 0
  145. while True:
  146. G = nx.fast_gnp_random_graph(100, 0.0575, seed=42)
  147. if nx.is_connected(G) and not nx.is_biconnected(G):
  148. attempts = 0
  149. yield G
  150. else:
  151. if attempts >= max_attempts:
  152. msg = f"Tried {attempts} times: no suitable Graph."
  153. raise Exception(msg)
  154. else:
  155. attempts += 1
  156. def test_articulation_points():
  157. Ggen = _generate_no_biconnected()
  158. for i in range(1): # change 1 to 3 or more for more realizations.
  159. G = next(Ggen)
  160. articulation_points = [{a} for a in nx.articulation_points(G)]
  161. for cut in nx.all_node_cuts(G):
  162. assert cut in articulation_points
  163. def test_grid_2d_graph():
  164. # All minimum node cuts of a 2d grid
  165. # are the four pairs of nodes that are
  166. # neighbors of the four corner nodes.
  167. G = nx.grid_2d_graph(5, 5)
  168. solution = [{(0, 1), (1, 0)}, {(3, 0), (4, 1)}, {(3, 4), (4, 3)}, {(0, 3), (1, 4)}]
  169. for cut in nx.all_node_cuts(G):
  170. assert cut in solution
  171. def test_disconnected_graph():
  172. G = nx.fast_gnp_random_graph(100, 0.01, seed=42)
  173. cuts = nx.all_node_cuts(G)
  174. pytest.raises(nx.NetworkXError, next, cuts)
  175. @pytest.mark.slow
  176. def test_alternative_flow_functions():
  177. graphs = [nx.grid_2d_graph(4, 4), nx.cycle_graph(5)]
  178. for G in graphs:
  179. node_conn = nx.node_connectivity(G)
  180. for flow_func in flow_funcs:
  181. all_cuts = nx.all_node_cuts(G, flow_func=flow_func)
  182. # Only test a limited number of cut sets to reduce test time.
  183. for cut in itertools.islice(all_cuts, MAX_CUTSETS_TO_TEST):
  184. assert node_conn == len(cut)
  185. assert not nx.is_connected(nx.restricted_view(G, cut, []))
  186. def test_is_separating_set_complete_graph():
  187. G = nx.complete_graph(5)
  188. assert _is_separating_set(G, {0, 1, 2, 3})
  189. def test_is_separating_set():
  190. for i in [5, 10, 15]:
  191. G = nx.star_graph(i)
  192. max_degree_node = max(G, key=G.degree)
  193. assert _is_separating_set(G, {max_degree_node})
  194. def test_non_repeated_cuts():
  195. # The algorithm was repeating the cut {0, 1} for the giant biconnected
  196. # component of the Karate club graph.
  197. K = nx.karate_club_graph()
  198. bcc = max(list(nx.biconnected_components(K)), key=len)
  199. G = K.subgraph(bcc)
  200. solution = [{32, 33}, {2, 33}, {0, 3}, {0, 1}, {29, 33}]
  201. cuts = list(nx.all_node_cuts(G))
  202. if len(solution) != len(cuts):
  203. print(f"Solution: {solution}")
  204. print(f"Result: {cuts}")
  205. assert len(solution) == len(cuts)
  206. for cut in cuts:
  207. assert cut in solution
  208. def test_cycle_graph():
  209. G = nx.cycle_graph(5)
  210. solution = [{0, 2}, {0, 3}, {1, 3}, {1, 4}, {2, 4}]
  211. cuts = list(nx.all_node_cuts(G))
  212. assert len(solution) == len(cuts)
  213. for cut in cuts:
  214. assert cut in solution
  215. def test_complete_graph():
  216. G = nx.complete_graph(5)
  217. solution = [{0, 1, 2, 3}, {0, 1, 2, 4}, {0, 1, 3, 4}, {0, 2, 3, 4}, {1, 2, 3, 4}]
  218. cuts = list(nx.all_node_cuts(G))
  219. assert len(solution) == len(cuts)
  220. for cut in cuts:
  221. assert cut in solution