test_kcomponents.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. # Test for approximation to k-components algorithm
  2. import pytest
  3. import networkx as nx
  4. from networkx.algorithms.approximation import k_components
  5. from networkx.algorithms.approximation.kcomponents import _AntiGraph, _same
  6. def build_k_number_dict(k_components):
  7. k_num = {}
  8. for k, comps in sorted(k_components.items()):
  9. for comp in comps:
  10. for node in comp:
  11. k_num[node] = k
  12. return k_num
  13. ##
  14. # Some nice synthetic graphs
  15. ##
  16. def graph_example_1():
  17. G = nx.convert_node_labels_to_integers(
  18. nx.grid_graph([5, 5]), label_attribute="labels"
  19. )
  20. rlabels = nx.get_node_attributes(G, "labels")
  21. labels = {v: k for k, v in rlabels.items()}
  22. for nodes in [
  23. (labels[(0, 0)], labels[(1, 0)]),
  24. (labels[(0, 4)], labels[(1, 4)]),
  25. (labels[(3, 0)], labels[(4, 0)]),
  26. (labels[(3, 4)], labels[(4, 4)]),
  27. ]:
  28. new_node = G.order() + 1
  29. # Petersen graph is triconnected
  30. P = nx.petersen_graph()
  31. G = nx.disjoint_union(G, P)
  32. # Add two edges between the grid and P
  33. G.add_edge(new_node + 1, nodes[0])
  34. G.add_edge(new_node, nodes[1])
  35. # K5 is 4-connected
  36. K = nx.complete_graph(5)
  37. G = nx.disjoint_union(G, K)
  38. # Add three edges between P and K5
  39. G.add_edge(new_node + 2, new_node + 11)
  40. G.add_edge(new_node + 3, new_node + 12)
  41. G.add_edge(new_node + 4, new_node + 13)
  42. # Add another K5 sharing a node
  43. G = nx.disjoint_union(G, K)
  44. nbrs = G[new_node + 10]
  45. G.remove_node(new_node + 10)
  46. for nbr in nbrs:
  47. G.add_edge(new_node + 17, nbr)
  48. G.add_edge(new_node + 16, new_node + 5)
  49. return G
  50. def torrents_and_ferraro_graph():
  51. G = nx.convert_node_labels_to_integers(
  52. nx.grid_graph([5, 5]), label_attribute="labels"
  53. )
  54. rlabels = nx.get_node_attributes(G, "labels")
  55. labels = {v: k for k, v in rlabels.items()}
  56. for nodes in [(labels[(0, 4)], labels[(1, 4)]), (labels[(3, 4)], labels[(4, 4)])]:
  57. new_node = G.order() + 1
  58. # Petersen graph is triconnected
  59. P = nx.petersen_graph()
  60. G = nx.disjoint_union(G, P)
  61. # Add two edges between the grid and P
  62. G.add_edge(new_node + 1, nodes[0])
  63. G.add_edge(new_node, nodes[1])
  64. # K5 is 4-connected
  65. K = nx.complete_graph(5)
  66. G = nx.disjoint_union(G, K)
  67. # Add three edges between P and K5
  68. G.add_edge(new_node + 2, new_node + 11)
  69. G.add_edge(new_node + 3, new_node + 12)
  70. G.add_edge(new_node + 4, new_node + 13)
  71. # Add another K5 sharing a node
  72. G = nx.disjoint_union(G, K)
  73. nbrs = G[new_node + 10]
  74. G.remove_node(new_node + 10)
  75. for nbr in nbrs:
  76. G.add_edge(new_node + 17, nbr)
  77. # Commenting this makes the graph not biconnected !!
  78. # This stupid mistake make one reviewer very angry :P
  79. G.add_edge(new_node + 16, new_node + 8)
  80. for nodes in [(labels[(0, 0)], labels[(1, 0)]), (labels[(3, 0)], labels[(4, 0)])]:
  81. new_node = G.order() + 1
  82. # Petersen graph is triconnected
  83. P = nx.petersen_graph()
  84. G = nx.disjoint_union(G, P)
  85. # Add two edges between the grid and P
  86. G.add_edge(new_node + 1, nodes[0])
  87. G.add_edge(new_node, nodes[1])
  88. # K5 is 4-connected
  89. K = nx.complete_graph(5)
  90. G = nx.disjoint_union(G, K)
  91. # Add three edges between P and K5
  92. G.add_edge(new_node + 2, new_node + 11)
  93. G.add_edge(new_node + 3, new_node + 12)
  94. G.add_edge(new_node + 4, new_node + 13)
  95. # Add another K5 sharing two nodes
  96. G = nx.disjoint_union(G, K)
  97. nbrs = G[new_node + 10]
  98. G.remove_node(new_node + 10)
  99. for nbr in nbrs:
  100. G.add_edge(new_node + 17, nbr)
  101. nbrs2 = G[new_node + 9]
  102. G.remove_node(new_node + 9)
  103. for nbr in nbrs2:
  104. G.add_edge(new_node + 18, nbr)
  105. return G
  106. # Helper function
  107. def _check_connectivity(G):
  108. result = k_components(G)
  109. for k, components in result.items():
  110. if k < 3:
  111. continue
  112. for component in components:
  113. C = G.subgraph(component)
  114. K = nx.node_connectivity(C)
  115. assert K >= k
  116. def test_torrents_and_ferraro_graph():
  117. G = torrents_and_ferraro_graph()
  118. _check_connectivity(G)
  119. def test_example_1():
  120. G = graph_example_1()
  121. _check_connectivity(G)
  122. def test_karate_0():
  123. G = nx.karate_club_graph()
  124. _check_connectivity(G)
  125. def test_karate_1():
  126. karate_k_num = {
  127. 0: 4,
  128. 1: 4,
  129. 2: 4,
  130. 3: 4,
  131. 4: 3,
  132. 5: 3,
  133. 6: 3,
  134. 7: 4,
  135. 8: 4,
  136. 9: 2,
  137. 10: 3,
  138. 11: 1,
  139. 12: 2,
  140. 13: 4,
  141. 14: 2,
  142. 15: 2,
  143. 16: 2,
  144. 17: 2,
  145. 18: 2,
  146. 19: 3,
  147. 20: 2,
  148. 21: 2,
  149. 22: 2,
  150. 23: 3,
  151. 24: 3,
  152. 25: 3,
  153. 26: 2,
  154. 27: 3,
  155. 28: 3,
  156. 29: 3,
  157. 30: 4,
  158. 31: 3,
  159. 32: 4,
  160. 33: 4,
  161. }
  162. approx_karate_k_num = karate_k_num.copy()
  163. approx_karate_k_num[24] = 2
  164. approx_karate_k_num[25] = 2
  165. G = nx.karate_club_graph()
  166. k_comps = k_components(G)
  167. k_num = build_k_number_dict(k_comps)
  168. assert k_num in (karate_k_num, approx_karate_k_num)
  169. def test_example_1_detail_3_and_4():
  170. G = graph_example_1()
  171. result = k_components(G)
  172. # In this example graph there are 8 3-components, 4 with 15 nodes
  173. # and 4 with 5 nodes.
  174. assert len(result[3]) == 8
  175. assert len([c for c in result[3] if len(c) == 15]) == 4
  176. assert len([c for c in result[3] if len(c) == 5]) == 4
  177. # There are also 8 4-components all with 5 nodes.
  178. assert len(result[4]) == 8
  179. assert all(len(c) == 5 for c in result[4])
  180. # Finally check that the k-components detected have actually node
  181. # connectivity >= k.
  182. for k, components in result.items():
  183. if k < 3:
  184. continue
  185. for component in components:
  186. K = nx.node_connectivity(G.subgraph(component))
  187. assert K >= k
  188. def test_directed():
  189. with pytest.raises(nx.NetworkXNotImplemented):
  190. G = nx.gnp_random_graph(10, 0.4, directed=True)
  191. kc = k_components(G)
  192. def test_same():
  193. equal = {"A": 2, "B": 2, "C": 2}
  194. slightly_different = {"A": 2, "B": 1, "C": 2}
  195. different = {"A": 2, "B": 8, "C": 18}
  196. assert _same(equal)
  197. assert not _same(slightly_different)
  198. assert _same(slightly_different, tol=1)
  199. assert not _same(different)
  200. assert not _same(different, tol=4)
  201. class TestAntiGraph:
  202. @classmethod
  203. def setup_class(cls):
  204. cls.Gnp = nx.gnp_random_graph(20, 0.8)
  205. cls.Anp = _AntiGraph(nx.complement(cls.Gnp))
  206. cls.Gd = nx.davis_southern_women_graph()
  207. cls.Ad = _AntiGraph(nx.complement(cls.Gd))
  208. cls.Gk = nx.karate_club_graph()
  209. cls.Ak = _AntiGraph(nx.complement(cls.Gk))
  210. cls.GA = [(cls.Gnp, cls.Anp), (cls.Gd, cls.Ad), (cls.Gk, cls.Ak)]
  211. def test_size(self):
  212. for G, A in self.GA:
  213. n = G.order()
  214. s = len(list(G.edges())) + len(list(A.edges()))
  215. assert s == (n * (n - 1)) / 2
  216. def test_degree(self):
  217. for G, A in self.GA:
  218. assert sorted(G.degree()) == sorted(A.degree())
  219. def test_core_number(self):
  220. for G, A in self.GA:
  221. assert nx.core_number(G) == nx.core_number(A)
  222. def test_connected_components(self):
  223. for G, A in self.GA:
  224. gc = [set(c) for c in nx.connected_components(G)]
  225. ac = [set(c) for c in nx.connected_components(A)]
  226. for comp in ac:
  227. assert comp in gc
  228. def test_adj(self):
  229. for G, A in self.GA:
  230. for n, nbrs in G.adj.items():
  231. a_adj = sorted((n, sorted(ad)) for n, ad in A.adj.items())
  232. g_adj = sorted((n, sorted(ad)) for n, ad in G.adj.items())
  233. assert a_adj == g_adj
  234. def test_adjacency(self):
  235. for G, A in self.GA:
  236. a_adj = list(A.adjacency())
  237. for n, nbrs in G.adjacency():
  238. assert (n, set(nbrs)) in a_adj
  239. def test_neighbors(self):
  240. for G, A in self.GA:
  241. node = list(G.nodes())[0]
  242. assert set(G.neighbors(node)) == set(A.neighbors(node))
  243. def test_node_not_in_graph(self):
  244. for G, A in self.GA:
  245. node = "non_existent_node"
  246. pytest.raises(nx.NetworkXError, A.neighbors, node)
  247. pytest.raises(nx.NetworkXError, G.neighbors, node)
  248. def test_degree_thingraph(self):
  249. for G, A in self.GA:
  250. node = list(G.nodes())[0]
  251. nodes = list(G.nodes())[1:4]
  252. assert G.degree(node) == A.degree(node)
  253. assert sum(d for n, d in G.degree()) == sum(d for n, d in A.degree())
  254. # AntiGraph is a ThinGraph, so all the weights are 1
  255. assert sum(d for n, d in A.degree()) == sum(
  256. d for n, d in A.degree(weight="weight")
  257. )
  258. assert sum(d for n, d in G.degree(nodes)) == sum(
  259. d for n, d in A.degree(nodes)
  260. )