test_multidigraph.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. from collections import UserDict
  2. import pytest
  3. import networkx as nx
  4. from networkx.utils import edges_equal
  5. from .test_multigraph import BaseMultiGraphTester
  6. from .test_multigraph import TestEdgeSubgraph as _TestMultiGraphEdgeSubgraph
  7. from .test_multigraph import TestMultiGraph as _TestMultiGraph
  8. class BaseMultiDiGraphTester(BaseMultiGraphTester):
  9. def test_edges(self):
  10. G = self.K3
  11. edges = [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
  12. assert sorted(G.edges()) == edges
  13. assert sorted(G.edges(0)) == [(0, 1), (0, 2)]
  14. pytest.raises((KeyError, nx.NetworkXError), G.edges, -1)
  15. def test_edges_data(self):
  16. G = self.K3
  17. edges = [(0, 1, {}), (0, 2, {}), (1, 0, {}), (1, 2, {}), (2, 0, {}), (2, 1, {})]
  18. assert sorted(G.edges(data=True)) == edges
  19. assert sorted(G.edges(0, data=True)) == [(0, 1, {}), (0, 2, {})]
  20. pytest.raises((KeyError, nx.NetworkXError), G.neighbors, -1)
  21. def test_edges_multi(self):
  22. G = self.K3
  23. assert sorted(G.edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
  24. assert sorted(G.edges(0)) == [(0, 1), (0, 2)]
  25. G.add_edge(0, 1)
  26. assert sorted(G.edges()) == [
  27. (0, 1),
  28. (0, 1),
  29. (0, 2),
  30. (1, 0),
  31. (1, 2),
  32. (2, 0),
  33. (2, 1),
  34. ]
  35. def test_out_edges(self):
  36. G = self.K3
  37. assert sorted(G.out_edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
  38. assert sorted(G.out_edges(0)) == [(0, 1), (0, 2)]
  39. pytest.raises((KeyError, nx.NetworkXError), G.out_edges, -1)
  40. assert sorted(G.out_edges(0, keys=True)) == [(0, 1, 0), (0, 2, 0)]
  41. def test_out_edges_multi(self):
  42. G = self.K3
  43. assert sorted(G.out_edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
  44. assert sorted(G.out_edges(0)) == [(0, 1), (0, 2)]
  45. G.add_edge(0, 1, 2)
  46. assert sorted(G.out_edges()) == [
  47. (0, 1),
  48. (0, 1),
  49. (0, 2),
  50. (1, 0),
  51. (1, 2),
  52. (2, 0),
  53. (2, 1),
  54. ]
  55. def test_out_edges_data(self):
  56. G = self.K3
  57. assert sorted(G.edges(0, data=True)) == [(0, 1, {}), (0, 2, {})]
  58. G.remove_edge(0, 1)
  59. G.add_edge(0, 1, data=1)
  60. assert sorted(G.edges(0, data=True)) == [(0, 1, {"data": 1}), (0, 2, {})]
  61. assert sorted(G.edges(0, data="data")) == [(0, 1, 1), (0, 2, None)]
  62. assert sorted(G.edges(0, data="data", default=-1)) == [(0, 1, 1), (0, 2, -1)]
  63. def test_in_edges(self):
  64. G = self.K3
  65. assert sorted(G.in_edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
  66. assert sorted(G.in_edges(0)) == [(1, 0), (2, 0)]
  67. pytest.raises((KeyError, nx.NetworkXError), G.in_edges, -1)
  68. G.add_edge(0, 1, 2)
  69. assert sorted(G.in_edges()) == [
  70. (0, 1),
  71. (0, 1),
  72. (0, 2),
  73. (1, 0),
  74. (1, 2),
  75. (2, 0),
  76. (2, 1),
  77. ]
  78. assert sorted(G.in_edges(0, keys=True)) == [(1, 0, 0), (2, 0, 0)]
  79. def test_in_edges_no_keys(self):
  80. G = self.K3
  81. assert sorted(G.in_edges()) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
  82. assert sorted(G.in_edges(0)) == [(1, 0), (2, 0)]
  83. G.add_edge(0, 1, 2)
  84. assert sorted(G.in_edges()) == [
  85. (0, 1),
  86. (0, 1),
  87. (0, 2),
  88. (1, 0),
  89. (1, 2),
  90. (2, 0),
  91. (2, 1),
  92. ]
  93. assert sorted(G.in_edges(data=True, keys=False)) == [
  94. (0, 1, {}),
  95. (0, 1, {}),
  96. (0, 2, {}),
  97. (1, 0, {}),
  98. (1, 2, {}),
  99. (2, 0, {}),
  100. (2, 1, {}),
  101. ]
  102. def test_in_edges_data(self):
  103. G = self.K3
  104. assert sorted(G.in_edges(0, data=True)) == [(1, 0, {}), (2, 0, {})]
  105. G.remove_edge(1, 0)
  106. G.add_edge(1, 0, data=1)
  107. assert sorted(G.in_edges(0, data=True)) == [(1, 0, {"data": 1}), (2, 0, {})]
  108. assert sorted(G.in_edges(0, data="data")) == [(1, 0, 1), (2, 0, None)]
  109. assert sorted(G.in_edges(0, data="data", default=-1)) == [(1, 0, 1), (2, 0, -1)]
  110. def is_shallow(self, H, G):
  111. # graph
  112. assert G.graph["foo"] == H.graph["foo"]
  113. G.graph["foo"].append(1)
  114. assert G.graph["foo"] == H.graph["foo"]
  115. # node
  116. assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
  117. G.nodes[0]["foo"].append(1)
  118. assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
  119. # edge
  120. assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
  121. G[1][2][0]["foo"].append(1)
  122. assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
  123. def is_deep(self, H, G):
  124. # graph
  125. assert G.graph["foo"] == H.graph["foo"]
  126. G.graph["foo"].append(1)
  127. assert G.graph["foo"] != H.graph["foo"]
  128. # node
  129. assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
  130. G.nodes[0]["foo"].append(1)
  131. assert G.nodes[0]["foo"] != H.nodes[0]["foo"]
  132. # edge
  133. assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
  134. G[1][2][0]["foo"].append(1)
  135. assert G[1][2][0]["foo"] != H[1][2][0]["foo"]
  136. def test_to_undirected(self):
  137. # MultiDiGraph -> MultiGraph changes number of edges so it is
  138. # not a copy operation... use is_shallow, not is_shallow_copy
  139. G = self.K3
  140. self.add_attributes(G)
  141. H = nx.MultiGraph(G)
  142. # self.is_shallow(H,G)
  143. # the result is traversal order dependent so we
  144. # can't use the is_shallow() test here.
  145. try:
  146. assert edges_equal(H.edges(), [(0, 1), (1, 2), (2, 0)])
  147. except AssertionError:
  148. assert edges_equal(H.edges(), [(0, 1), (1, 2), (1, 2), (2, 0)])
  149. H = G.to_undirected()
  150. self.is_deep(H, G)
  151. def test_has_successor(self):
  152. G = self.K3
  153. assert G.has_successor(0, 1)
  154. assert not G.has_successor(0, -1)
  155. def test_successors(self):
  156. G = self.K3
  157. assert sorted(G.successors(0)) == [1, 2]
  158. pytest.raises((KeyError, nx.NetworkXError), G.successors, -1)
  159. def test_has_predecessor(self):
  160. G = self.K3
  161. assert G.has_predecessor(0, 1)
  162. assert not G.has_predecessor(0, -1)
  163. def test_predecessors(self):
  164. G = self.K3
  165. assert sorted(G.predecessors(0)) == [1, 2]
  166. pytest.raises((KeyError, nx.NetworkXError), G.predecessors, -1)
  167. def test_degree(self):
  168. G = self.K3
  169. assert sorted(G.degree()) == [(0, 4), (1, 4), (2, 4)]
  170. assert dict(G.degree()) == {0: 4, 1: 4, 2: 4}
  171. assert G.degree(0) == 4
  172. assert list(G.degree(iter([0]))) == [(0, 4)]
  173. G.add_edge(0, 1, weight=0.3, other=1.2)
  174. assert sorted(G.degree(weight="weight")) == [(0, 4.3), (1, 4.3), (2, 4)]
  175. assert sorted(G.degree(weight="other")) == [(0, 5.2), (1, 5.2), (2, 4)]
  176. def test_in_degree(self):
  177. G = self.K3
  178. assert sorted(G.in_degree()) == [(0, 2), (1, 2), (2, 2)]
  179. assert dict(G.in_degree()) == {0: 2, 1: 2, 2: 2}
  180. assert G.in_degree(0) == 2
  181. assert list(G.in_degree(iter([0]))) == [(0, 2)]
  182. assert G.in_degree(0, weight="weight") == 2
  183. def test_out_degree(self):
  184. G = self.K3
  185. assert sorted(G.out_degree()) == [(0, 2), (1, 2), (2, 2)]
  186. assert dict(G.out_degree()) == {0: 2, 1: 2, 2: 2}
  187. assert G.out_degree(0) == 2
  188. assert list(G.out_degree(iter([0]))) == [(0, 2)]
  189. assert G.out_degree(0, weight="weight") == 2
  190. def test_size(self):
  191. G = self.K3
  192. assert G.size() == 6
  193. assert G.number_of_edges() == 6
  194. G.add_edge(0, 1, weight=0.3, other=1.2)
  195. assert round(G.size(weight="weight"), 2) == 6.3
  196. assert round(G.size(weight="other"), 2) == 7.2
  197. def test_to_undirected_reciprocal(self):
  198. G = self.Graph()
  199. G.add_edge(1, 2)
  200. assert G.to_undirected().has_edge(1, 2)
  201. assert not G.to_undirected(reciprocal=True).has_edge(1, 2)
  202. G.add_edge(2, 1)
  203. assert G.to_undirected(reciprocal=True).has_edge(1, 2)
  204. def test_reverse_copy(self):
  205. G = nx.MultiDiGraph([(0, 1), (0, 1)])
  206. R = G.reverse()
  207. assert sorted(R.edges()) == [(1, 0), (1, 0)]
  208. R.remove_edge(1, 0)
  209. assert sorted(R.edges()) == [(1, 0)]
  210. assert sorted(G.edges()) == [(0, 1), (0, 1)]
  211. def test_reverse_nocopy(self):
  212. G = nx.MultiDiGraph([(0, 1), (0, 1)])
  213. R = G.reverse(copy=False)
  214. assert sorted(R.edges()) == [(1, 0), (1, 0)]
  215. pytest.raises(nx.NetworkXError, R.remove_edge, 1, 0)
  216. def test_di_attributes_cached(self):
  217. G = self.K3.copy()
  218. assert id(G.in_edges) == id(G.in_edges)
  219. assert id(G.out_edges) == id(G.out_edges)
  220. assert id(G.in_degree) == id(G.in_degree)
  221. assert id(G.out_degree) == id(G.out_degree)
  222. assert id(G.succ) == id(G.succ)
  223. assert id(G.pred) == id(G.pred)
  224. class TestMultiDiGraph(BaseMultiDiGraphTester, _TestMultiGraph):
  225. def setup_method(self):
  226. self.Graph = nx.MultiDiGraph
  227. # build K3
  228. self.k3edges = [(0, 1), (0, 2), (1, 2)]
  229. self.k3nodes = [0, 1, 2]
  230. self.K3 = self.Graph()
  231. self.K3._succ = {0: {}, 1: {}, 2: {}}
  232. # K3._adj is synced with K3._succ
  233. self.K3._pred = {0: {}, 1: {}, 2: {}}
  234. for u in self.k3nodes:
  235. for v in self.k3nodes:
  236. if u == v:
  237. continue
  238. d = {0: {}}
  239. self.K3._succ[u][v] = d
  240. self.K3._pred[v][u] = d
  241. self.K3._node = {}
  242. self.K3._node[0] = {}
  243. self.K3._node[1] = {}
  244. self.K3._node[2] = {}
  245. def test_add_edge(self):
  246. G = self.Graph()
  247. G.add_edge(0, 1)
  248. assert G._adj == {0: {1: {0: {}}}, 1: {}}
  249. assert G._succ == {0: {1: {0: {}}}, 1: {}}
  250. assert G._pred == {0: {}, 1: {0: {0: {}}}}
  251. G = self.Graph()
  252. G.add_edge(*(0, 1))
  253. assert G._adj == {0: {1: {0: {}}}, 1: {}}
  254. assert G._succ == {0: {1: {0: {}}}, 1: {}}
  255. assert G._pred == {0: {}, 1: {0: {0: {}}}}
  256. with pytest.raises(ValueError, match="None cannot be a node"):
  257. G.add_edge(None, 3)
  258. def test_add_edges_from(self):
  259. G = self.Graph()
  260. G.add_edges_from([(0, 1), (0, 1, {"weight": 3})])
  261. assert G._adj == {0: {1: {0: {}, 1: {"weight": 3}}}, 1: {}}
  262. assert G._succ == {0: {1: {0: {}, 1: {"weight": 3}}}, 1: {}}
  263. assert G._pred == {0: {}, 1: {0: {0: {}, 1: {"weight": 3}}}}
  264. G.add_edges_from([(0, 1), (0, 1, {"weight": 3})], weight=2)
  265. assert G._succ == {
  266. 0: {1: {0: {}, 1: {"weight": 3}, 2: {"weight": 2}, 3: {"weight": 3}}},
  267. 1: {},
  268. }
  269. assert G._pred == {
  270. 0: {},
  271. 1: {0: {0: {}, 1: {"weight": 3}, 2: {"weight": 2}, 3: {"weight": 3}}},
  272. }
  273. G = self.Graph()
  274. edges = [
  275. (0, 1, {"weight": 3}),
  276. (0, 1, (("weight", 2),)),
  277. (0, 1, 5),
  278. (0, 1, "s"),
  279. ]
  280. G.add_edges_from(edges)
  281. keydict = {0: {"weight": 3}, 1: {"weight": 2}, 5: {}, "s": {}}
  282. assert G._succ == {0: {1: keydict}, 1: {}}
  283. assert G._pred == {1: {0: keydict}, 0: {}}
  284. # too few in tuple
  285. pytest.raises(nx.NetworkXError, G.add_edges_from, [(0,)])
  286. # too many in tuple
  287. pytest.raises(nx.NetworkXError, G.add_edges_from, [(0, 1, 2, 3, 4)])
  288. # not a tuple
  289. pytest.raises(TypeError, G.add_edges_from, [0])
  290. with pytest.raises(ValueError, match="None cannot be a node"):
  291. G.add_edges_from([(None, 3), (3, 2)])
  292. def test_remove_edge(self):
  293. G = self.K3
  294. G.remove_edge(0, 1)
  295. assert G._succ == {
  296. 0: {2: {0: {}}},
  297. 1: {0: {0: {}}, 2: {0: {}}},
  298. 2: {0: {0: {}}, 1: {0: {}}},
  299. }
  300. assert G._pred == {
  301. 0: {1: {0: {}}, 2: {0: {}}},
  302. 1: {2: {0: {}}},
  303. 2: {0: {0: {}}, 1: {0: {}}},
  304. }
  305. pytest.raises((KeyError, nx.NetworkXError), G.remove_edge, -1, 0)
  306. pytest.raises((KeyError, nx.NetworkXError), G.remove_edge, 0, 2, key=1)
  307. def test_remove_multiedge(self):
  308. G = self.K3
  309. G.add_edge(0, 1, key="parallel edge")
  310. G.remove_edge(0, 1, key="parallel edge")
  311. assert G._adj == {
  312. 0: {1: {0: {}}, 2: {0: {}}},
  313. 1: {0: {0: {}}, 2: {0: {}}},
  314. 2: {0: {0: {}}, 1: {0: {}}},
  315. }
  316. assert G._succ == {
  317. 0: {1: {0: {}}, 2: {0: {}}},
  318. 1: {0: {0: {}}, 2: {0: {}}},
  319. 2: {0: {0: {}}, 1: {0: {}}},
  320. }
  321. assert G._pred == {
  322. 0: {1: {0: {}}, 2: {0: {}}},
  323. 1: {0: {0: {}}, 2: {0: {}}},
  324. 2: {0: {0: {}}, 1: {0: {}}},
  325. }
  326. G.remove_edge(0, 1)
  327. assert G._succ == {
  328. 0: {2: {0: {}}},
  329. 1: {0: {0: {}}, 2: {0: {}}},
  330. 2: {0: {0: {}}, 1: {0: {}}},
  331. }
  332. assert G._pred == {
  333. 0: {1: {0: {}}, 2: {0: {}}},
  334. 1: {2: {0: {}}},
  335. 2: {0: {0: {}}, 1: {0: {}}},
  336. }
  337. pytest.raises((KeyError, nx.NetworkXError), G.remove_edge, -1, 0)
  338. def test_remove_edges_from(self):
  339. G = self.K3
  340. G.remove_edges_from([(0, 1)])
  341. assert G._succ == {
  342. 0: {2: {0: {}}},
  343. 1: {0: {0: {}}, 2: {0: {}}},
  344. 2: {0: {0: {}}, 1: {0: {}}},
  345. }
  346. assert G._pred == {
  347. 0: {1: {0: {}}, 2: {0: {}}},
  348. 1: {2: {0: {}}},
  349. 2: {0: {0: {}}, 1: {0: {}}},
  350. }
  351. G.remove_edges_from([(0, 0)]) # silent fail
  352. class TestEdgeSubgraph(_TestMultiGraphEdgeSubgraph):
  353. """Unit tests for the :meth:`MultiDiGraph.edge_subgraph` method."""
  354. def setup_method(self):
  355. # Create a quadruply-linked path graph on five nodes.
  356. G = nx.MultiDiGraph()
  357. nx.add_path(G, range(5))
  358. nx.add_path(G, range(5))
  359. nx.add_path(G, reversed(range(5)))
  360. nx.add_path(G, reversed(range(5)))
  361. # Add some node, edge, and graph attributes.
  362. for i in range(5):
  363. G.nodes[i]["name"] = f"node{i}"
  364. G.adj[0][1][0]["name"] = "edge010"
  365. G.adj[0][1][1]["name"] = "edge011"
  366. G.adj[3][4][0]["name"] = "edge340"
  367. G.adj[3][4][1]["name"] = "edge341"
  368. G.graph["name"] = "graph"
  369. # Get the subgraph induced by one of the first edges and one of
  370. # the last edges.
  371. self.G = G
  372. self.H = G.edge_subgraph([(0, 1, 0), (3, 4, 1)])
  373. class CustomDictClass(UserDict):
  374. pass
  375. class MultiDiGraphSubClass(nx.MultiDiGraph):
  376. node_dict_factory = CustomDictClass # type: ignore[assignment]
  377. node_attr_dict_factory = CustomDictClass # type: ignore[assignment]
  378. adjlist_outer_dict_factory = CustomDictClass # type: ignore[assignment]
  379. adjlist_inner_dict_factory = CustomDictClass # type: ignore[assignment]
  380. edge_key_dict_factory = CustomDictClass # type: ignore[assignment]
  381. edge_attr_dict_factory = CustomDictClass # type: ignore[assignment]
  382. graph_attr_dict_factory = CustomDictClass # type: ignore[assignment]
  383. class TestMultiDiGraphSubclass(TestMultiDiGraph):
  384. def setup_method(self):
  385. self.Graph = MultiDiGraphSubClass
  386. # build K3
  387. self.k3edges = [(0, 1), (0, 2), (1, 2)]
  388. self.k3nodes = [0, 1, 2]
  389. self.K3 = self.Graph()
  390. self.K3._succ = self.K3.adjlist_outer_dict_factory(
  391. {
  392. 0: self.K3.adjlist_inner_dict_factory(),
  393. 1: self.K3.adjlist_inner_dict_factory(),
  394. 2: self.K3.adjlist_inner_dict_factory(),
  395. }
  396. )
  397. # K3._adj is synced with K3._succ
  398. self.K3._pred = {0: {}, 1: {}, 2: {}}
  399. for u in self.k3nodes:
  400. for v in self.k3nodes:
  401. if u == v:
  402. continue
  403. d = {0: {}}
  404. self.K3._succ[u][v] = d
  405. self.K3._pred[v][u] = d
  406. self.K3._node = self.K3.node_dict_factory()
  407. self.K3._node[0] = self.K3.node_attr_dict_factory()
  408. self.K3._node[1] = self.K3.node_attr_dict_factory()
  409. self.K3._node[2] = self.K3.node_attr_dict_factory()