test_multigraph.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. from collections import UserDict
  2. import pytest
  3. import networkx as nx
  4. from networkx.utils import edges_equal
  5. from .test_graph import BaseAttrGraphTester
  6. from .test_graph import TestGraph as _TestGraph
  7. class BaseMultiGraphTester(BaseAttrGraphTester):
  8. def test_has_edge(self):
  9. G = self.K3
  10. assert G.has_edge(0, 1)
  11. assert not G.has_edge(0, -1)
  12. assert G.has_edge(0, 1, 0)
  13. assert not G.has_edge(0, 1, 1)
  14. def test_get_edge_data(self):
  15. G = self.K3
  16. assert G.get_edge_data(0, 1) == {0: {}}
  17. assert G[0][1] == {0: {}}
  18. assert G[0][1][0] == {}
  19. assert G.get_edge_data(10, 20) is None
  20. assert G.get_edge_data(0, 1, 0) == {}
  21. def test_adjacency(self):
  22. G = self.K3
  23. assert dict(G.adjacency()) == {
  24. 0: {1: {0: {}}, 2: {0: {}}},
  25. 1: {0: {0: {}}, 2: {0: {}}},
  26. 2: {0: {0: {}}, 1: {0: {}}},
  27. }
  28. def deepcopy_edge_attr(self, H, G):
  29. assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
  30. G[1][2][0]["foo"].append(1)
  31. assert G[1][2][0]["foo"] != H[1][2][0]["foo"]
  32. def shallow_copy_edge_attr(self, H, G):
  33. assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
  34. G[1][2][0]["foo"].append(1)
  35. assert G[1][2][0]["foo"] == H[1][2][0]["foo"]
  36. def graphs_equal(self, H, G):
  37. assert G._adj == H._adj
  38. assert G._node == H._node
  39. assert G.graph == H.graph
  40. assert G.name == H.name
  41. if not G.is_directed() and not H.is_directed():
  42. assert H._adj[1][2][0] is H._adj[2][1][0]
  43. assert G._adj[1][2][0] is G._adj[2][1][0]
  44. else: # at least one is directed
  45. if not G.is_directed():
  46. G._pred = G._adj
  47. G._succ = G._adj
  48. if not H.is_directed():
  49. H._pred = H._adj
  50. H._succ = H._adj
  51. assert G._pred == H._pred
  52. assert G._succ == H._succ
  53. assert H._succ[1][2][0] is H._pred[2][1][0]
  54. assert G._succ[1][2][0] is G._pred[2][1][0]
  55. def same_attrdict(self, H, G):
  56. # same attrdict in the edgedata
  57. old_foo = H[1][2][0]["foo"]
  58. H.adj[1][2][0]["foo"] = "baz"
  59. assert G._adj == H._adj
  60. H.adj[1][2][0]["foo"] = old_foo
  61. assert G._adj == H._adj
  62. old_foo = H.nodes[0]["foo"]
  63. H.nodes[0]["foo"] = "baz"
  64. assert G._node == H._node
  65. H.nodes[0]["foo"] = old_foo
  66. assert G._node == H._node
  67. def different_attrdict(self, H, G):
  68. # used by graph_equal_but_different
  69. old_foo = H[1][2][0]["foo"]
  70. H.adj[1][2][0]["foo"] = "baz"
  71. assert G._adj != H._adj
  72. H.adj[1][2][0]["foo"] = old_foo
  73. assert G._adj == H._adj
  74. old_foo = H.nodes[0]["foo"]
  75. H.nodes[0]["foo"] = "baz"
  76. assert G._node != H._node
  77. H.nodes[0]["foo"] = old_foo
  78. assert G._node == H._node
  79. def test_to_undirected(self):
  80. G = self.K3
  81. self.add_attributes(G)
  82. H = nx.MultiGraph(G)
  83. self.is_shallow_copy(H, G)
  84. H = G.to_undirected()
  85. self.is_deepcopy(H, G)
  86. def test_to_directed(self):
  87. G = self.K3
  88. self.add_attributes(G)
  89. H = nx.MultiDiGraph(G)
  90. self.is_shallow_copy(H, G)
  91. H = G.to_directed()
  92. self.is_deepcopy(H, G)
  93. def test_number_of_edges_selfloops(self):
  94. G = self.K3
  95. G.add_edge(0, 0)
  96. G.add_edge(0, 0)
  97. G.add_edge(0, 0, key="parallel edge")
  98. G.remove_edge(0, 0, key="parallel edge")
  99. assert G.number_of_edges(0, 0) == 2
  100. G.remove_edge(0, 0)
  101. assert G.number_of_edges(0, 0) == 1
  102. def test_edge_lookup(self):
  103. G = self.Graph()
  104. G.add_edge(1, 2, foo="bar")
  105. G.add_edge(1, 2, "key", foo="biz")
  106. assert edges_equal(G.edges[1, 2, 0], {"foo": "bar"})
  107. assert edges_equal(G.edges[1, 2, "key"], {"foo": "biz"})
  108. def test_edge_attr(self):
  109. G = self.Graph()
  110. G.add_edge(1, 2, key="k1", foo="bar")
  111. G.add_edge(1, 2, key="k2", foo="baz")
  112. assert isinstance(G.get_edge_data(1, 2), G.edge_key_dict_factory)
  113. assert all(
  114. isinstance(d, G.edge_attr_dict_factory) for u, v, d in G.edges(data=True)
  115. )
  116. assert edges_equal(
  117. G.edges(keys=True, data=True),
  118. [(1, 2, "k1", {"foo": "bar"}), (1, 2, "k2", {"foo": "baz"})],
  119. )
  120. assert edges_equal(
  121. G.edges(keys=True, data="foo"), [(1, 2, "k1", "bar"), (1, 2, "k2", "baz")]
  122. )
  123. def test_edge_attr4(self):
  124. G = self.Graph()
  125. G.add_edge(1, 2, key=0, data=7, spam="bar", bar="foo")
  126. assert edges_equal(
  127. G.edges(data=True), [(1, 2, {"data": 7, "spam": "bar", "bar": "foo"})]
  128. )
  129. G[1][2][0]["data"] = 10 # OK to set data like this
  130. assert edges_equal(
  131. G.edges(data=True), [(1, 2, {"data": 10, "spam": "bar", "bar": "foo"})]
  132. )
  133. G.adj[1][2][0]["data"] = 20
  134. assert edges_equal(
  135. G.edges(data=True), [(1, 2, {"data": 20, "spam": "bar", "bar": "foo"})]
  136. )
  137. G.edges[1, 2, 0]["data"] = 21 # another spelling, "edge"
  138. assert edges_equal(
  139. G.edges(data=True), [(1, 2, {"data": 21, "spam": "bar", "bar": "foo"})]
  140. )
  141. G.adj[1][2][0]["listdata"] = [20, 200]
  142. G.adj[1][2][0]["weight"] = 20
  143. assert edges_equal(
  144. G.edges(data=True),
  145. [
  146. (
  147. 1,
  148. 2,
  149. {
  150. "data": 21,
  151. "spam": "bar",
  152. "bar": "foo",
  153. "listdata": [20, 200],
  154. "weight": 20,
  155. },
  156. )
  157. ],
  158. )
  159. class TestMultiGraph(BaseMultiGraphTester, _TestGraph):
  160. def setup_method(self):
  161. self.Graph = nx.MultiGraph
  162. # build K3
  163. ed1, ed2, ed3 = ({0: {}}, {0: {}}, {0: {}})
  164. self.k3adj = {0: {1: ed1, 2: ed2}, 1: {0: ed1, 2: ed3}, 2: {0: ed2, 1: ed3}}
  165. self.k3edges = [(0, 1), (0, 2), (1, 2)]
  166. self.k3nodes = [0, 1, 2]
  167. self.K3 = self.Graph()
  168. self.K3._adj = self.k3adj
  169. self.K3._node = {}
  170. self.K3._node[0] = {}
  171. self.K3._node[1] = {}
  172. self.K3._node[2] = {}
  173. def test_data_input(self):
  174. G = self.Graph({1: [2], 2: [1]}, name="test")
  175. assert G.name == "test"
  176. expected = [(1, {2: {0: {}}}), (2, {1: {0: {}}})]
  177. assert sorted(G.adj.items()) == expected
  178. def test_data_multigraph_input(self):
  179. # standard case with edge keys and edge data
  180. edata0 = {"w": 200, "s": "foo"}
  181. edata1 = {"w": 201, "s": "bar"}
  182. keydict = {0: edata0, 1: edata1}
  183. dododod = {"a": {"b": keydict}}
  184. multiple_edge = [("a", "b", 0, edata0), ("a", "b", 1, edata1)]
  185. single_edge = [("a", "b", 0, keydict)]
  186. G = self.Graph(dododod, multigraph_input=True)
  187. assert list(G.edges(keys=True, data=True)) == multiple_edge
  188. G = self.Graph(dododod, multigraph_input=None)
  189. assert list(G.edges(keys=True, data=True)) == multiple_edge
  190. G = self.Graph(dododod, multigraph_input=False)
  191. assert list(G.edges(keys=True, data=True)) == single_edge
  192. # test round-trip to_dict_of_dict and MultiGraph constructor
  193. G = self.Graph(dododod, multigraph_input=True)
  194. H = self.Graph(nx.to_dict_of_dicts(G))
  195. assert nx.is_isomorphic(G, H) is True # test that default is True
  196. for mgi in [True, False]:
  197. H = self.Graph(nx.to_dict_of_dicts(G), multigraph_input=mgi)
  198. assert nx.is_isomorphic(G, H) == mgi
  199. # Set up cases for when incoming_graph_data is not multigraph_input
  200. etraits = {"w": 200, "s": "foo"}
  201. egraphics = {"color": "blue", "shape": "box"}
  202. edata = {"traits": etraits, "graphics": egraphics}
  203. dodod1 = {"a": {"b": edata}}
  204. dodod2 = {"a": {"b": etraits}}
  205. dodod3 = {"a": {"b": {"traits": etraits, "s": "foo"}}}
  206. dol = {"a": ["b"]}
  207. multiple_edge = [("a", "b", "traits", etraits), ("a", "b", "graphics", egraphics)]
  208. single_edge = [("a", "b", 0, {})] # type: ignore[var-annotated]
  209. single_edge1 = [("a", "b", 0, edata)]
  210. single_edge2 = [("a", "b", 0, etraits)]
  211. single_edge3 = [("a", "b", 0, {"traits": etraits, "s": "foo"})]
  212. cases = [ # (dod, mgi, edges)
  213. (dodod1, True, multiple_edge),
  214. (dodod1, False, single_edge1),
  215. (dodod2, False, single_edge2),
  216. (dodod3, False, single_edge3),
  217. (dol, False, single_edge),
  218. ]
  219. @pytest.mark.parametrize("dod, mgi, edges", cases)
  220. def test_non_multigraph_input(self, dod, mgi, edges):
  221. G = self.Graph(dod, multigraph_input=mgi)
  222. assert list(G.edges(keys=True, data=True)) == edges
  223. G = nx.to_networkx_graph(dod, create_using=self.Graph, multigraph_input=mgi)
  224. assert list(G.edges(keys=True, data=True)) == edges
  225. mgi_none_cases = [
  226. (dodod1, multiple_edge),
  227. (dodod2, single_edge2),
  228. (dodod3, single_edge3),
  229. ]
  230. @pytest.mark.parametrize("dod, edges", mgi_none_cases)
  231. def test_non_multigraph_input_mgi_none(self, dod, edges):
  232. # test constructor without to_networkx_graph for mgi=None
  233. G = self.Graph(dod)
  234. assert list(G.edges(keys=True, data=True)) == edges
  235. raise_cases = [dodod2, dodod3, dol]
  236. @pytest.mark.parametrize("dod", raise_cases)
  237. def test_non_multigraph_input_raise(self, dod):
  238. # cases where NetworkXError is raised
  239. pytest.raises(nx.NetworkXError, self.Graph, dod, multigraph_input=True)
  240. pytest.raises(
  241. nx.NetworkXError,
  242. nx.to_networkx_graph,
  243. dod,
  244. create_using=self.Graph,
  245. multigraph_input=True,
  246. )
  247. def test_getitem(self):
  248. G = self.K3
  249. assert G[0] == {1: {0: {}}, 2: {0: {}}}
  250. with pytest.raises(KeyError):
  251. G.__getitem__("j")
  252. with pytest.raises(TypeError):
  253. G.__getitem__(["A"])
  254. def test_remove_node(self):
  255. G = self.K3
  256. G.remove_node(0)
  257. assert G.adj == {1: {2: {0: {}}}, 2: {1: {0: {}}}}
  258. with pytest.raises(nx.NetworkXError):
  259. G.remove_node(-1)
  260. def test_add_edge(self):
  261. G = self.Graph()
  262. G.add_edge(0, 1)
  263. assert G.adj == {0: {1: {0: {}}}, 1: {0: {0: {}}}}
  264. G = self.Graph()
  265. G.add_edge(*(0, 1))
  266. assert G.adj == {0: {1: {0: {}}}, 1: {0: {0: {}}}}
  267. G = self.Graph()
  268. with pytest.raises(ValueError):
  269. G.add_edge(None, "anything")
  270. def test_add_edge_conflicting_key(self):
  271. G = self.Graph()
  272. G.add_edge(0, 1, key=1)
  273. G.add_edge(0, 1)
  274. assert G.number_of_edges() == 2
  275. G = self.Graph()
  276. G.add_edges_from([(0, 1, 1, {})])
  277. G.add_edges_from([(0, 1)])
  278. assert G.number_of_edges() == 2
  279. def test_add_edges_from(self):
  280. G = self.Graph()
  281. G.add_edges_from([(0, 1), (0, 1, {"weight": 3})])
  282. assert G.adj == {
  283. 0: {1: {0: {}, 1: {"weight": 3}}},
  284. 1: {0: {0: {}, 1: {"weight": 3}}},
  285. }
  286. G.add_edges_from([(0, 1), (0, 1, {"weight": 3})], weight=2)
  287. assert G.adj == {
  288. 0: {1: {0: {}, 1: {"weight": 3}, 2: {"weight": 2}, 3: {"weight": 3}}},
  289. 1: {0: {0: {}, 1: {"weight": 3}, 2: {"weight": 2}, 3: {"weight": 3}}},
  290. }
  291. G = self.Graph()
  292. edges = [
  293. (0, 1, {"weight": 3}),
  294. (0, 1, (("weight", 2),)),
  295. (0, 1, 5),
  296. (0, 1, "s"),
  297. ]
  298. G.add_edges_from(edges)
  299. keydict = {0: {"weight": 3}, 1: {"weight": 2}, 5: {}, "s": {}}
  300. assert G._adj == {0: {1: keydict}, 1: {0: keydict}}
  301. # too few in tuple
  302. with pytest.raises(nx.NetworkXError):
  303. G.add_edges_from([(0,)])
  304. # too many in tuple
  305. with pytest.raises(nx.NetworkXError):
  306. G.add_edges_from([(0, 1, 2, 3, 4)])
  307. # not a tuple
  308. with pytest.raises(TypeError):
  309. G.add_edges_from([0])
  310. def test_multigraph_add_edges_from_four_tuple_misordered(self):
  311. """add_edges_from expects 4-tuples of the format (u, v, key, data_dict).
  312. Ensure 4-tuples of form (u, v, data_dict, key) raise exception.
  313. """
  314. G = nx.MultiGraph()
  315. with pytest.raises(TypeError):
  316. # key/data values flipped in 4-tuple
  317. G.add_edges_from([(0, 1, {"color": "red"}, 0)])
  318. def test_remove_edge(self):
  319. G = self.K3
  320. G.remove_edge(0, 1)
  321. assert G.adj == {0: {2: {0: {}}}, 1: {2: {0: {}}}, 2: {0: {0: {}}, 1: {0: {}}}}
  322. with pytest.raises(nx.NetworkXError):
  323. G.remove_edge(-1, 0)
  324. with pytest.raises(nx.NetworkXError):
  325. G.remove_edge(0, 2, key=1)
  326. def test_remove_edges_from(self):
  327. G = self.K3.copy()
  328. G.remove_edges_from([(0, 1)])
  329. kd = {0: {}}
  330. assert G.adj == {0: {2: kd}, 1: {2: kd}, 2: {0: kd, 1: kd}}
  331. G.remove_edges_from([(0, 0)]) # silent fail
  332. self.K3.add_edge(0, 1)
  333. G = self.K3.copy()
  334. G.remove_edges_from(list(G.edges(data=True, keys=True)))
  335. assert G.adj == {0: {}, 1: {}, 2: {}}
  336. G = self.K3.copy()
  337. G.remove_edges_from(list(G.edges(data=False, keys=True)))
  338. assert G.adj == {0: {}, 1: {}, 2: {}}
  339. G = self.K3.copy()
  340. G.remove_edges_from(list(G.edges(data=False, keys=False)))
  341. assert G.adj == {0: {}, 1: {}, 2: {}}
  342. G = self.K3.copy()
  343. G.remove_edges_from([(0, 1, 0), (0, 2, 0, {}), (1, 2)])
  344. assert G.adj == {0: {1: {1: {}}}, 1: {0: {1: {}}}, 2: {}}
  345. def test_remove_multiedge(self):
  346. G = self.K3
  347. G.add_edge(0, 1, key="parallel edge")
  348. G.remove_edge(0, 1, key="parallel edge")
  349. assert G.adj == {
  350. 0: {1: {0: {}}, 2: {0: {}}},
  351. 1: {0: {0: {}}, 2: {0: {}}},
  352. 2: {0: {0: {}}, 1: {0: {}}},
  353. }
  354. G.remove_edge(0, 1)
  355. kd = {0: {}}
  356. assert G.adj == {0: {2: kd}, 1: {2: kd}, 2: {0: kd, 1: kd}}
  357. with pytest.raises(nx.NetworkXError):
  358. G.remove_edge(-1, 0)
  359. class TestEdgeSubgraph:
  360. """Unit tests for the :meth:`MultiGraph.edge_subgraph` method."""
  361. def setup_method(self):
  362. # Create a doubly-linked path graph on five nodes.
  363. G = nx.MultiGraph()
  364. nx.add_path(G, range(5))
  365. nx.add_path(G, range(5))
  366. # Add some node, edge, and graph attributes.
  367. for i in range(5):
  368. G.nodes[i]["name"] = f"node{i}"
  369. G.adj[0][1][0]["name"] = "edge010"
  370. G.adj[0][1][1]["name"] = "edge011"
  371. G.adj[3][4][0]["name"] = "edge340"
  372. G.adj[3][4][1]["name"] = "edge341"
  373. G.graph["name"] = "graph"
  374. # Get the subgraph induced by one of the first edges and one of
  375. # the last edges.
  376. self.G = G
  377. self.H = G.edge_subgraph([(0, 1, 0), (3, 4, 1)])
  378. def test_correct_nodes(self):
  379. """Tests that the subgraph has the correct nodes."""
  380. assert [0, 1, 3, 4] == sorted(self.H.nodes())
  381. def test_correct_edges(self):
  382. """Tests that the subgraph has the correct edges."""
  383. assert [(0, 1, 0, "edge010"), (3, 4, 1, "edge341")] == sorted(
  384. self.H.edges(keys=True, data="name")
  385. )
  386. def test_add_node(self):
  387. """Tests that adding a node to the original graph does not
  388. affect the nodes of the subgraph.
  389. """
  390. self.G.add_node(5)
  391. assert [0, 1, 3, 4] == sorted(self.H.nodes())
  392. def test_remove_node(self):
  393. """Tests that removing a node in the original graph does
  394. affect the nodes of the subgraph.
  395. """
  396. self.G.remove_node(0)
  397. assert [1, 3, 4] == sorted(self.H.nodes())
  398. def test_node_attr_dict(self):
  399. """Tests that the node attribute dictionary of the two graphs is
  400. the same object.
  401. """
  402. for v in self.H:
  403. assert self.G.nodes[v] == self.H.nodes[v]
  404. # Making a change to G should make a change in H and vice versa.
  405. self.G.nodes[0]["name"] = "foo"
  406. assert self.G.nodes[0] == self.H.nodes[0]
  407. self.H.nodes[1]["name"] = "bar"
  408. assert self.G.nodes[1] == self.H.nodes[1]
  409. def test_edge_attr_dict(self):
  410. """Tests that the edge attribute dictionary of the two graphs is
  411. the same object.
  412. """
  413. for u, v, k in self.H.edges(keys=True):
  414. assert self.G._adj[u][v][k] == self.H._adj[u][v][k]
  415. # Making a change to G should make a change in H and vice versa.
  416. self.G._adj[0][1][0]["name"] = "foo"
  417. assert self.G._adj[0][1][0]["name"] == self.H._adj[0][1][0]["name"]
  418. self.H._adj[3][4][1]["name"] = "bar"
  419. assert self.G._adj[3][4][1]["name"] == self.H._adj[3][4][1]["name"]
  420. def test_graph_attr_dict(self):
  421. """Tests that the graph attribute dictionary of the two graphs
  422. is the same object.
  423. """
  424. assert self.G.graph is self.H.graph
  425. class CustomDictClass(UserDict):
  426. pass
  427. class MultiGraphSubClass(nx.MultiGraph):
  428. node_dict_factory = CustomDictClass # type: ignore[assignment]
  429. node_attr_dict_factory = CustomDictClass # type: ignore[assignment]
  430. adjlist_outer_dict_factory = CustomDictClass # type: ignore[assignment]
  431. adjlist_inner_dict_factory = CustomDictClass # type: ignore[assignment]
  432. edge_key_dict_factory = CustomDictClass # type: ignore[assignment]
  433. edge_attr_dict_factory = CustomDictClass # type: ignore[assignment]
  434. graph_attr_dict_factory = CustomDictClass # type: ignore[assignment]
  435. class TestMultiGraphSubclass(TestMultiGraph):
  436. def setup_method(self):
  437. self.Graph = MultiGraphSubClass
  438. # build K3
  439. self.k3edges = [(0, 1), (0, 2), (1, 2)]
  440. self.k3nodes = [0, 1, 2]
  441. self.K3 = self.Graph()
  442. self.K3._adj = self.K3.adjlist_outer_dict_factory(
  443. {
  444. 0: self.K3.adjlist_inner_dict_factory(),
  445. 1: self.K3.adjlist_inner_dict_factory(),
  446. 2: self.K3.adjlist_inner_dict_factory(),
  447. }
  448. )
  449. self.K3._pred = {0: {}, 1: {}, 2: {}}
  450. for u in self.k3nodes:
  451. for v in self.k3nodes:
  452. if u != v:
  453. d = {0: {}}
  454. self.K3._adj[u][v] = d
  455. self.K3._adj[v][u] = d
  456. self.K3._node = self.K3.node_dict_factory()
  457. self.K3._node[0] = self.K3.node_attr_dict_factory()
  458. self.K3._node[1] = self.K3.node_attr_dict_factory()
  459. self.K3._node[2] = self.K3.node_attr_dict_factory()