test_subgraphviews.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. import pytest
  2. import networkx as nx
  3. from networkx.utils import edges_equal
  4. class TestSubGraphView:
  5. gview = staticmethod(nx.subgraph_view)
  6. graph = nx.Graph
  7. hide_edges_filter = staticmethod(nx.filters.hide_edges)
  8. show_edges_filter = staticmethod(nx.filters.show_edges)
  9. @classmethod
  10. def setup_class(cls):
  11. cls.G = nx.path_graph(9, create_using=cls.graph())
  12. cls.hide_edges_w_hide_nodes = {(3, 4), (4, 5), (5, 6)}
  13. def test_hidden_nodes(self):
  14. hide_nodes = [4, 5, 111]
  15. nodes_gone = nx.filters.hide_nodes(hide_nodes)
  16. gview = self.gview
  17. G = gview(self.G, filter_node=nodes_gone)
  18. assert self.G.nodes - G.nodes == {4, 5}
  19. assert self.G.edges - G.edges == self.hide_edges_w_hide_nodes
  20. if G.is_directed():
  21. assert list(G[3]) == []
  22. assert list(G[2]) == [3]
  23. else:
  24. assert list(G[3]) == [2]
  25. assert set(G[2]) == {1, 3}
  26. pytest.raises(KeyError, G.__getitem__, 4)
  27. pytest.raises(KeyError, G.__getitem__, 112)
  28. pytest.raises(KeyError, G.__getitem__, 111)
  29. assert G.degree(3) == (3 if G.is_multigraph() else 1)
  30. assert G.size() == (7 if G.is_multigraph() else 5)
  31. def test_hidden_edges(self):
  32. hide_edges = [(2, 3), (8, 7), (222, 223)]
  33. edges_gone = self.hide_edges_filter(hide_edges)
  34. gview = self.gview
  35. G = gview(self.G, filter_edge=edges_gone)
  36. assert self.G.nodes == G.nodes
  37. if G.is_directed():
  38. assert self.G.edges - G.edges == {(2, 3)}
  39. assert list(G[2]) == []
  40. assert list(G.pred[3]) == []
  41. assert list(G.pred[2]) == [1]
  42. assert G.size() == 7
  43. else:
  44. assert self.G.edges - G.edges == {(2, 3), (7, 8)}
  45. assert list(G[2]) == [1]
  46. assert G.size() == 6
  47. assert list(G[3]) == [4]
  48. pytest.raises(KeyError, G.__getitem__, 221)
  49. pytest.raises(KeyError, G.__getitem__, 222)
  50. assert G.degree(3) == 1
  51. def test_shown_node(self):
  52. induced_subgraph = nx.filters.show_nodes([2, 3, 111])
  53. gview = self.gview
  54. G = gview(self.G, filter_node=induced_subgraph)
  55. assert set(G.nodes) == {2, 3}
  56. if G.is_directed():
  57. assert list(G[3]) == []
  58. else:
  59. assert list(G[3]) == [2]
  60. assert list(G[2]) == [3]
  61. pytest.raises(KeyError, G.__getitem__, 4)
  62. pytest.raises(KeyError, G.__getitem__, 112)
  63. pytest.raises(KeyError, G.__getitem__, 111)
  64. assert G.degree(3) == (3 if G.is_multigraph() else 1)
  65. assert G.size() == (3 if G.is_multigraph() else 1)
  66. def test_shown_edges(self):
  67. show_edges = [(2, 3), (8, 7), (222, 223)]
  68. edge_subgraph = self.show_edges_filter(show_edges)
  69. G = self.gview(self.G, filter_edge=edge_subgraph)
  70. assert self.G.nodes == G.nodes
  71. if G.is_directed():
  72. assert G.edges == {(2, 3)}
  73. assert list(G[3]) == []
  74. assert list(G[2]) == [3]
  75. assert list(G.pred[3]) == [2]
  76. assert list(G.pred[2]) == []
  77. assert G.size() == 1
  78. else:
  79. assert G.edges == {(2, 3), (7, 8)}
  80. assert list(G[3]) == [2]
  81. assert list(G[2]) == [3]
  82. assert G.size() == 2
  83. pytest.raises(KeyError, G.__getitem__, 221)
  84. pytest.raises(KeyError, G.__getitem__, 222)
  85. assert G.degree(3) == 1
  86. class TestSubDiGraphView(TestSubGraphView):
  87. gview = staticmethod(nx.subgraph_view)
  88. graph = nx.DiGraph
  89. hide_edges_filter = staticmethod(nx.filters.hide_diedges)
  90. show_edges_filter = staticmethod(nx.filters.show_diedges)
  91. hide_edges = [(2, 3), (8, 7), (222, 223)]
  92. excluded = {(2, 3), (3, 4), (4, 5), (5, 6)}
  93. def test_inoutedges(self):
  94. edges_gone = self.hide_edges_filter(self.hide_edges)
  95. hide_nodes = [4, 5, 111]
  96. nodes_gone = nx.filters.hide_nodes(hide_nodes)
  97. G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
  98. assert self.G.in_edges - G.in_edges == self.excluded
  99. assert self.G.out_edges - G.out_edges == self.excluded
  100. def test_pred(self):
  101. edges_gone = self.hide_edges_filter(self.hide_edges)
  102. hide_nodes = [4, 5, 111]
  103. nodes_gone = nx.filters.hide_nodes(hide_nodes)
  104. G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
  105. assert list(G.pred[2]) == [1]
  106. assert list(G.pred[6]) == []
  107. def test_inout_degree(self):
  108. edges_gone = self.hide_edges_filter(self.hide_edges)
  109. hide_nodes = [4, 5, 111]
  110. nodes_gone = nx.filters.hide_nodes(hide_nodes)
  111. G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
  112. assert G.degree(2) == 1
  113. assert G.out_degree(2) == 0
  114. assert G.in_degree(2) == 1
  115. assert G.size() == 4
  116. # multigraph
  117. class TestMultiGraphView(TestSubGraphView):
  118. gview = staticmethod(nx.subgraph_view)
  119. graph = nx.MultiGraph
  120. hide_edges_filter = staticmethod(nx.filters.hide_multiedges)
  121. show_edges_filter = staticmethod(nx.filters.show_multiedges)
  122. @classmethod
  123. def setup_class(cls):
  124. cls.G = nx.path_graph(9, create_using=cls.graph())
  125. multiedges = {(2, 3, 4), (2, 3, 5)}
  126. cls.G.add_edges_from(multiedges)
  127. cls.hide_edges_w_hide_nodes = {(3, 4, 0), (4, 5, 0), (5, 6, 0)}
  128. def test_hidden_edges(self):
  129. hide_edges = [(2, 3, 4), (2, 3, 3), (8, 7, 0), (222, 223, 0)]
  130. edges_gone = self.hide_edges_filter(hide_edges)
  131. G = self.gview(self.G, filter_edge=edges_gone)
  132. assert self.G.nodes == G.nodes
  133. if G.is_directed():
  134. assert self.G.edges - G.edges == {(2, 3, 4)}
  135. assert list(G[3]) == [4]
  136. assert list(G[2]) == [3]
  137. assert list(G.pred[3]) == [2] # only one 2 but two edges
  138. assert list(G.pred[2]) == [1]
  139. assert G.size() == 9
  140. else:
  141. assert self.G.edges - G.edges == {(2, 3, 4), (7, 8, 0)}
  142. assert list(G[3]) == [2, 4]
  143. assert list(G[2]) == [1, 3]
  144. assert G.size() == 8
  145. assert G.degree(3) == 3
  146. pytest.raises(KeyError, G.__getitem__, 221)
  147. pytest.raises(KeyError, G.__getitem__, 222)
  148. def test_shown_edges(self):
  149. show_edges = [(2, 3, 4), (2, 3, 3), (8, 7, 0), (222, 223, 0)]
  150. edge_subgraph = self.show_edges_filter(show_edges)
  151. G = self.gview(self.G, filter_edge=edge_subgraph)
  152. assert self.G.nodes == G.nodes
  153. if G.is_directed():
  154. assert G.edges == {(2, 3, 4)}
  155. assert list(G[3]) == []
  156. assert list(G.pred[3]) == [2]
  157. assert list(G.pred[2]) == []
  158. assert G.size() == 1
  159. else:
  160. assert G.edges == {(2, 3, 4), (7, 8, 0)}
  161. assert G.size() == 2
  162. assert list(G[3]) == [2]
  163. assert G.degree(3) == 1
  164. assert list(G[2]) == [3]
  165. pytest.raises(KeyError, G.__getitem__, 221)
  166. pytest.raises(KeyError, G.__getitem__, 222)
  167. # multidigraph
  168. class TestMultiDiGraphView(TestMultiGraphView, TestSubDiGraphView):
  169. gview = staticmethod(nx.subgraph_view)
  170. graph = nx.MultiDiGraph
  171. hide_edges_filter = staticmethod(nx.filters.hide_multidiedges)
  172. show_edges_filter = staticmethod(nx.filters.show_multidiedges)
  173. hide_edges = [(2, 3, 0), (8, 7, 0), (222, 223, 0)]
  174. excluded = {(2, 3, 0), (3, 4, 0), (4, 5, 0), (5, 6, 0)}
  175. def test_inout_degree(self):
  176. edges_gone = self.hide_edges_filter(self.hide_edges)
  177. hide_nodes = [4, 5, 111]
  178. nodes_gone = nx.filters.hide_nodes(hide_nodes)
  179. G = self.gview(self.G, filter_node=nodes_gone, filter_edge=edges_gone)
  180. assert G.degree(2) == 3
  181. assert G.out_degree(2) == 2
  182. assert G.in_degree(2) == 1
  183. assert G.size() == 6
  184. # induced_subgraph
  185. class TestInducedSubGraph:
  186. @classmethod
  187. def setup_class(cls):
  188. cls.K3 = G = nx.complete_graph(3)
  189. G.graph["foo"] = []
  190. G.nodes[0]["foo"] = []
  191. G.remove_edge(1, 2)
  192. ll = []
  193. G.add_edge(1, 2, foo=ll)
  194. G.add_edge(2, 1, foo=ll)
  195. def test_full_graph(self):
  196. G = self.K3
  197. H = nx.induced_subgraph(G, [0, 1, 2, 5])
  198. assert H.name == G.name
  199. self.graphs_equal(H, G)
  200. self.same_attrdict(H, G)
  201. def test_partial_subgraph(self):
  202. G = self.K3
  203. H = nx.induced_subgraph(G, 0)
  204. assert dict(H.adj) == {0: {}}
  205. assert dict(G.adj) != {0: {}}
  206. H = nx.induced_subgraph(G, [0, 1])
  207. assert dict(H.adj) == {0: {1: {}}, 1: {0: {}}}
  208. def same_attrdict(self, H, G):
  209. old_foo = H[1][2]["foo"]
  210. H.edges[1, 2]["foo"] = "baz"
  211. assert G.edges == H.edges
  212. H.edges[1, 2]["foo"] = old_foo
  213. assert G.edges == H.edges
  214. old_foo = H.nodes[0]["foo"]
  215. H.nodes[0]["foo"] = "baz"
  216. assert G.nodes == H.nodes
  217. H.nodes[0]["foo"] = old_foo
  218. assert G.nodes == H.nodes
  219. def graphs_equal(self, H, G):
  220. assert G._adj == H._adj
  221. assert G._node == H._node
  222. assert G.graph == H.graph
  223. assert G.name == H.name
  224. if not G.is_directed() and not H.is_directed():
  225. assert H._adj[1][2] is H._adj[2][1]
  226. assert G._adj[1][2] is G._adj[2][1]
  227. else: # at least one is directed
  228. if not G.is_directed():
  229. G._pred = G._adj
  230. G._succ = G._adj
  231. if not H.is_directed():
  232. H._pred = H._adj
  233. H._succ = H._adj
  234. assert G._pred == H._pred
  235. assert G._succ == H._succ
  236. assert H._succ[1][2] is H._pred[2][1]
  237. assert G._succ[1][2] is G._pred[2][1]
  238. # edge_subgraph
  239. class TestEdgeSubGraph:
  240. @classmethod
  241. def setup_class(cls):
  242. # Create a path graph on five nodes.
  243. cls.G = G = nx.path_graph(5)
  244. # Add some node, edge, and graph attributes.
  245. for i in range(5):
  246. G.nodes[i]["name"] = f"node{i}"
  247. G.edges[0, 1]["name"] = "edge01"
  248. G.edges[3, 4]["name"] = "edge34"
  249. G.graph["name"] = "graph"
  250. # Get the subgraph induced by the first and last edges.
  251. cls.H = nx.edge_subgraph(G, [(0, 1), (3, 4)])
  252. def test_correct_nodes(self):
  253. """Tests that the subgraph has the correct nodes."""
  254. assert [(0, "node0"), (1, "node1"), (3, "node3"), (4, "node4")] == sorted(
  255. self.H.nodes.data("name")
  256. )
  257. def test_correct_edges(self):
  258. """Tests that the subgraph has the correct edges."""
  259. assert edges_equal(
  260. [(0, 1, "edge01"), (3, 4, "edge34")], self.H.edges.data("name")
  261. )
  262. def test_add_node(self):
  263. """Tests that adding a node to the original graph does not
  264. affect the nodes of the subgraph.
  265. """
  266. self.G.add_node(5)
  267. assert [0, 1, 3, 4] == sorted(self.H.nodes)
  268. self.G.remove_node(5)
  269. def test_remove_node(self):
  270. """Tests that removing a node in the original graph
  271. removes the nodes of the subgraph.
  272. """
  273. self.G.remove_node(0)
  274. assert [1, 3, 4] == sorted(self.H.nodes)
  275. self.G.add_node(0, name="node0")
  276. self.G.add_edge(0, 1, name="edge01")
  277. def test_node_attr_dict(self):
  278. """Tests that the node attribute dictionary of the two graphs is
  279. the same object.
  280. """
  281. for v in self.H:
  282. assert self.G.nodes[v] == self.H.nodes[v]
  283. # Making a change to G should make a change in H and vice versa.
  284. self.G.nodes[0]["name"] = "foo"
  285. assert self.G.nodes[0] == self.H.nodes[0]
  286. self.H.nodes[1]["name"] = "bar"
  287. assert self.G.nodes[1] == self.H.nodes[1]
  288. # Revert the change, so tests pass with pytest-randomly
  289. self.G.nodes[0]["name"] = "node0"
  290. self.H.nodes[1]["name"] = "node1"
  291. def test_edge_attr_dict(self):
  292. """Tests that the edge attribute dictionary of the two graphs is
  293. the same object.
  294. """
  295. for u, v in self.H.edges():
  296. assert self.G.edges[u, v] == self.H.edges[u, v]
  297. # Making a change to G should make a change in H and vice versa.
  298. self.G.edges[0, 1]["name"] = "foo"
  299. assert self.G.edges[0, 1]["name"] == self.H.edges[0, 1]["name"]
  300. self.H.edges[3, 4]["name"] = "bar"
  301. assert self.G.edges[3, 4]["name"] == self.H.edges[3, 4]["name"]
  302. # Revert the change, so tests pass with pytest-randomly
  303. self.G.edges[0, 1]["name"] = "edge01"
  304. self.H.edges[3, 4]["name"] = "edge34"
  305. def test_graph_attr_dict(self):
  306. """Tests that the graph attribute dictionary of the two graphs
  307. is the same object.
  308. """
  309. assert self.G.graph is self.H.graph
  310. def test_readonly(self):
  311. """Tests that the subgraph cannot change the graph structure"""
  312. pytest.raises(nx.NetworkXError, self.H.add_node, 5)
  313. pytest.raises(nx.NetworkXError, self.H.remove_node, 0)
  314. pytest.raises(nx.NetworkXError, self.H.add_edge, 5, 6)
  315. pytest.raises(nx.NetworkXError, self.H.remove_edge, 0, 1)
  316. @pytest.mark.parametrize("multigraph", (nx.MultiGraph, nx.MultiDiGraph))
  317. def test_multigraph_filtered_edges(self, multigraph):
  318. """Check edge visibility in FilterMultiInner on edge_subgraph's of
  319. multigraphs. See gh-7724."""
  320. G = multigraph([("a", "b"), ("a", "c"), ("c", "b")])
  321. H = nx.edge_subgraph(G, [("a", "b", 0), ("c", "b", 0)])
  322. assert "c" not in H["a"]
  323. assert not H.has_edge("a", "c")