test_graph.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950
  1. import gc
  2. import pickle
  3. import platform
  4. import weakref
  5. import pytest
  6. import networkx as nx
  7. from networkx.utils import edges_equal, graphs_equal, nodes_equal
  8. def test_degree_node_not_found_exception_message():
  9. """See gh-7740"""
  10. G = nx.path_graph(5)
  11. with pytest.raises(nx.NetworkXError, match="Node.*is not in the graph"):
  12. G.degree(100)
  13. class BaseGraphTester:
  14. """Tests for data-structure independent graph class features."""
  15. def test_contains(self):
  16. G = self.K3
  17. assert 1 in G
  18. assert 4 not in G
  19. assert "b" not in G
  20. assert [] not in G # no exception for nonhashable
  21. assert {1: 1} not in G # no exception for nonhashable
  22. def test_order(self):
  23. G = self.K3
  24. assert len(G) == 3
  25. assert G.order() == 3
  26. assert G.number_of_nodes() == 3
  27. def test_nodes(self):
  28. G = self.K3
  29. assert isinstance(G._node, G.node_dict_factory)
  30. assert isinstance(G._adj, G.adjlist_outer_dict_factory)
  31. assert all(
  32. isinstance(adj, G.adjlist_inner_dict_factory) for adj in G._adj.values()
  33. )
  34. assert sorted(G.nodes()) == self.k3nodes
  35. assert sorted(G.nodes(data=True)) == [(0, {}), (1, {}), (2, {})]
  36. def test_none_node(self):
  37. G = self.Graph()
  38. with pytest.raises(ValueError):
  39. G.add_node(None)
  40. with pytest.raises(ValueError):
  41. G.add_nodes_from([None])
  42. with pytest.raises(ValueError):
  43. G.add_edge(0, None)
  44. with pytest.raises(ValueError):
  45. G.add_edges_from([(0, None)])
  46. def test_has_node(self):
  47. G = self.K3
  48. assert G.has_node(1)
  49. assert not G.has_node(4)
  50. assert not G.has_node([]) # no exception for nonhashable
  51. assert not G.has_node({1: 1}) # no exception for nonhashable
  52. def test_has_edge(self):
  53. G = self.K3
  54. assert G.has_edge(0, 1)
  55. assert not G.has_edge(0, -1)
  56. def test_neighbors(self):
  57. G = self.K3
  58. assert sorted(G.neighbors(0)) == [1, 2]
  59. with pytest.raises(nx.NetworkXError):
  60. G.neighbors(-1)
  61. @pytest.mark.skipif(
  62. platform.python_implementation() == "PyPy", reason="PyPy gc is different"
  63. )
  64. def test_memory_leak(self):
  65. G = self.Graph()
  66. def count_objects_of_type(_type):
  67. # Iterating over all objects tracked by gc can include weak references
  68. # whose weakly-referenced objects may no longer exist. Calling `isinstance`
  69. # on such a weak reference will raise ReferenceError. There are at least
  70. # three workarounds for this: one is to compare type names instead of using
  71. # `isinstance` such as `type(obj).__name__ == typename`, another is to use
  72. # `type(obj) == _type`, and the last is to ignore ProxyTypes as we do below.
  73. # NOTE: even if this safeguard is deemed unnecessary to pass NetworkX tests,
  74. # we should still keep it for maximum safety for other NetworkX backends.
  75. return sum(
  76. 1
  77. for obj in gc.get_objects()
  78. if not isinstance(obj, weakref.ProxyTypes) and isinstance(obj, _type)
  79. )
  80. gc.collect()
  81. before = count_objects_of_type(self.Graph)
  82. G.copy()
  83. gc.collect()
  84. after = count_objects_of_type(self.Graph)
  85. assert before == after
  86. # test a subgraph of the base class
  87. class MyGraph(self.Graph):
  88. pass
  89. gc.collect()
  90. G = MyGraph()
  91. before = count_objects_of_type(MyGraph)
  92. G.copy()
  93. gc.collect()
  94. after = count_objects_of_type(MyGraph)
  95. assert before == after
  96. def test_edges(self):
  97. G = self.K3
  98. assert isinstance(G._adj, G.adjlist_outer_dict_factory)
  99. assert edges_equal(G.edges(), [(0, 1), (0, 2), (1, 2)])
  100. assert edges_equal(G.edges(0), [(0, 1), (0, 2)])
  101. assert edges_equal(G.edges([0, 1]), [(0, 1), (0, 2), (1, 2)])
  102. with pytest.raises(nx.NetworkXError):
  103. G.edges(-1)
  104. def test_degree(self):
  105. G = self.K3
  106. assert sorted(G.degree()) == [(0, 2), (1, 2), (2, 2)]
  107. assert dict(G.degree()) == {0: 2, 1: 2, 2: 2}
  108. assert G.degree(0) == 2
  109. with pytest.raises(nx.NetworkXError):
  110. G.degree(-1) # node not in graph
  111. def test_size(self):
  112. G = self.K3
  113. assert G.size() == 3
  114. assert G.number_of_edges() == 3
  115. def test_nbunch_iter(self):
  116. G = self.K3
  117. assert nodes_equal(G.nbunch_iter(), self.k3nodes) # all nodes
  118. assert nodes_equal(G.nbunch_iter(0), [0]) # single node
  119. assert nodes_equal(G.nbunch_iter([0, 1]), [0, 1]) # sequence
  120. # sequence with none in graph
  121. assert nodes_equal(G.nbunch_iter([-1]), [])
  122. # string sequence with none in graph
  123. assert nodes_equal(G.nbunch_iter("foo"), [])
  124. # node not in graph doesn't get caught upon creation of iterator
  125. bunch = G.nbunch_iter(-1)
  126. # but gets caught when iterator used
  127. with pytest.raises(nx.NetworkXError, match="is not in the graph"):
  128. list(bunch)
  129. # unhashable doesn't get caught upon creation of iterator
  130. bunch = G.nbunch_iter([0, 1, 2, {}])
  131. # but gets caught when iterator hits the unhashable
  132. with pytest.raises(
  133. nx.NetworkXError, match="in sequence nbunch is not a valid node"
  134. ):
  135. list(bunch)
  136. def test_nbunch_iter_node_format_raise(self):
  137. # Tests that a node that would have failed string formatting
  138. # doesn't cause an error when attempting to raise a
  139. # :exc:`nx.NetworkXError`.
  140. # For more information, see pull request #1813.
  141. G = self.Graph()
  142. nbunch = [("x", set())]
  143. with pytest.raises(nx.NetworkXError):
  144. list(G.nbunch_iter(nbunch))
  145. def test_selfloop_degree(self):
  146. G = self.Graph()
  147. G.add_edge(1, 1)
  148. assert sorted(G.degree()) == [(1, 2)]
  149. assert dict(G.degree()) == {1: 2}
  150. assert G.degree(1) == 2
  151. assert sorted(G.degree([1])) == [(1, 2)]
  152. assert G.degree(1, weight="weight") == 2
  153. def test_selfloops(self):
  154. G = self.K3.copy()
  155. G.add_edge(0, 0)
  156. assert nodes_equal(nx.nodes_with_selfloops(G), [0])
  157. assert edges_equal(nx.selfloop_edges(G), [(0, 0)])
  158. assert nx.number_of_selfloops(G) == 1
  159. G.remove_edge(0, 0)
  160. G.add_edge(0, 0)
  161. G.remove_edges_from([(0, 0)])
  162. G.add_edge(1, 1)
  163. G.remove_node(1)
  164. G.add_edge(0, 0)
  165. G.add_edge(1, 1)
  166. G.remove_nodes_from([0, 1])
  167. def test_cache_reset(self):
  168. G = self.K3.copy()
  169. old_adj = G.adj
  170. assert id(G.adj) == id(old_adj)
  171. G._adj = {}
  172. assert id(G.adj) != id(old_adj)
  173. old_nodes = G.nodes
  174. assert id(G.nodes) == id(old_nodes)
  175. G._node = {}
  176. assert id(G.nodes) != id(old_nodes)
  177. def test_attributes_cached(self):
  178. G = self.K3.copy()
  179. assert id(G.nodes) == id(G.nodes)
  180. assert id(G.edges) == id(G.edges)
  181. assert id(G.degree) == id(G.degree)
  182. assert id(G.adj) == id(G.adj)
  183. class BaseAttrGraphTester(BaseGraphTester):
  184. """Tests of graph class attribute features."""
  185. def test_weighted_degree(self):
  186. G = self.Graph()
  187. G.add_edge(1, 2, weight=2, other=3)
  188. G.add_edge(2, 3, weight=3, other=4)
  189. assert sorted(d for n, d in G.degree(weight="weight")) == [2, 3, 5]
  190. assert dict(G.degree(weight="weight")) == {1: 2, 2: 5, 3: 3}
  191. assert G.degree(1, weight="weight") == 2
  192. assert nodes_equal((G.degree([1], weight="weight")), [(1, 2)])
  193. assert nodes_equal((d for n, d in G.degree(weight="other")), [3, 7, 4])
  194. assert dict(G.degree(weight="other")) == {1: 3, 2: 7, 3: 4}
  195. assert G.degree(1, weight="other") == 3
  196. assert edges_equal((G.degree([1], weight="other")), [(1, 3)])
  197. def add_attributes(self, G):
  198. G.graph["foo"] = []
  199. G.nodes[0]["foo"] = []
  200. G.remove_edge(1, 2)
  201. ll = []
  202. G.add_edge(1, 2, foo=ll)
  203. G.add_edge(2, 1, foo=ll)
  204. def test_name(self):
  205. G = self.Graph(name="")
  206. assert G.name == ""
  207. G = self.Graph(name="test")
  208. assert G.name == "test"
  209. def test_str_unnamed(self):
  210. G = self.Graph()
  211. G.add_edges_from([(1, 2), (2, 3)])
  212. assert str(G) == f"{type(G).__name__} with 3 nodes and 2 edges"
  213. def test_str_named(self):
  214. G = self.Graph(name="foo")
  215. G.add_edges_from([(1, 2), (2, 3)])
  216. assert str(G) == f"{type(G).__name__} named 'foo' with 3 nodes and 2 edges"
  217. def test_graph_chain(self):
  218. G = self.Graph([(0, 1), (1, 2)])
  219. DG = G.to_directed(as_view=True)
  220. SDG = DG.subgraph([0, 1])
  221. RSDG = SDG.reverse(copy=False)
  222. assert G is DG._graph
  223. assert DG is SDG._graph
  224. assert SDG is RSDG._graph
  225. def test_copy(self):
  226. G = self.Graph()
  227. G.add_node(0)
  228. G.add_edge(1, 2)
  229. self.add_attributes(G)
  230. # copy edge datadict but any container attr are same
  231. H = G.copy()
  232. self.graphs_equal(H, G)
  233. self.different_attrdict(H, G)
  234. self.shallow_copy_attrdict(H, G)
  235. def test_class_copy(self):
  236. G = self.Graph()
  237. G.add_node(0)
  238. G.add_edge(1, 2)
  239. self.add_attributes(G)
  240. # copy edge datadict but any container attr are same
  241. H = G.__class__(G)
  242. self.graphs_equal(H, G)
  243. self.different_attrdict(H, G)
  244. self.shallow_copy_attrdict(H, G)
  245. def test_fresh_copy(self):
  246. G = self.Graph()
  247. G.add_node(0)
  248. G.add_edge(1, 2)
  249. self.add_attributes(G)
  250. # copy graph structure but use fresh datadict
  251. H = G.__class__()
  252. H.add_nodes_from(G)
  253. H.add_edges_from(G.edges())
  254. assert len(G.nodes[0]) == 1
  255. ddict = G.adj[1][2][0] if G.is_multigraph() else G.adj[1][2]
  256. assert len(ddict) == 1
  257. assert len(H.nodes[0]) == 0
  258. ddict = H.adj[1][2][0] if H.is_multigraph() else H.adj[1][2]
  259. assert len(ddict) == 0
  260. def is_deepcopy(self, H, G):
  261. self.graphs_equal(H, G)
  262. self.different_attrdict(H, G)
  263. self.deep_copy_attrdict(H, G)
  264. def deep_copy_attrdict(self, H, G):
  265. self.deepcopy_graph_attr(H, G)
  266. self.deepcopy_node_attr(H, G)
  267. self.deepcopy_edge_attr(H, G)
  268. def deepcopy_graph_attr(self, H, G):
  269. assert G.graph["foo"] == H.graph["foo"]
  270. G.graph["foo"].append(1)
  271. assert G.graph["foo"] != H.graph["foo"]
  272. def deepcopy_node_attr(self, H, G):
  273. assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
  274. G.nodes[0]["foo"].append(1)
  275. assert G.nodes[0]["foo"] != H.nodes[0]["foo"]
  276. def deepcopy_edge_attr(self, H, G):
  277. assert G[1][2]["foo"] == H[1][2]["foo"]
  278. G[1][2]["foo"].append(1)
  279. assert G[1][2]["foo"] != H[1][2]["foo"]
  280. def is_shallow_copy(self, H, G):
  281. self.graphs_equal(H, G)
  282. self.shallow_copy_attrdict(H, G)
  283. def shallow_copy_attrdict(self, H, G):
  284. self.shallow_copy_graph_attr(H, G)
  285. self.shallow_copy_node_attr(H, G)
  286. self.shallow_copy_edge_attr(H, G)
  287. def shallow_copy_graph_attr(self, H, G):
  288. assert G.graph["foo"] == H.graph["foo"]
  289. G.graph["foo"].append(1)
  290. assert G.graph["foo"] == H.graph["foo"]
  291. def shallow_copy_node_attr(self, H, G):
  292. assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
  293. G.nodes[0]["foo"].append(1)
  294. assert G.nodes[0]["foo"] == H.nodes[0]["foo"]
  295. def shallow_copy_edge_attr(self, H, G):
  296. assert G[1][2]["foo"] == H[1][2]["foo"]
  297. G[1][2]["foo"].append(1)
  298. assert G[1][2]["foo"] == H[1][2]["foo"]
  299. def same_attrdict(self, H, G):
  300. old_foo = H[1][2]["foo"]
  301. H.adj[1][2]["foo"] = "baz"
  302. assert G.edges == H.edges
  303. H.adj[1][2]["foo"] = old_foo
  304. assert G.edges == H.edges
  305. old_foo = H.nodes[0]["foo"]
  306. H.nodes[0]["foo"] = "baz"
  307. assert G.nodes == H.nodes
  308. H.nodes[0]["foo"] = old_foo
  309. assert G.nodes == H.nodes
  310. def different_attrdict(self, H, G):
  311. old_foo = H[1][2]["foo"]
  312. H.adj[1][2]["foo"] = "baz"
  313. assert G._adj != H._adj
  314. H.adj[1][2]["foo"] = old_foo
  315. assert G._adj == H._adj
  316. old_foo = H.nodes[0]["foo"]
  317. H.nodes[0]["foo"] = "baz"
  318. assert G._node != H._node
  319. H.nodes[0]["foo"] = old_foo
  320. assert G._node == H._node
  321. def graphs_equal(self, H, G):
  322. assert G._adj == H._adj
  323. assert G._node == H._node
  324. assert G.graph == H.graph
  325. assert G.name == H.name
  326. if not G.is_directed() and not H.is_directed():
  327. assert H._adj[1][2] is H._adj[2][1]
  328. assert G._adj[1][2] is G._adj[2][1]
  329. else: # at least one is directed
  330. if not G.is_directed():
  331. G._pred = G._adj
  332. G._succ = G._adj
  333. if not H.is_directed():
  334. H._pred = H._adj
  335. H._succ = H._adj
  336. assert G._pred == H._pred
  337. assert G._succ == H._succ
  338. assert H._succ[1][2] is H._pred[2][1]
  339. assert G._succ[1][2] is G._pred[2][1]
  340. def test_graph_attr(self):
  341. G = self.K3.copy()
  342. G.graph["foo"] = "bar"
  343. assert isinstance(G.graph, G.graph_attr_dict_factory)
  344. assert G.graph["foo"] == "bar"
  345. del G.graph["foo"]
  346. assert G.graph == {}
  347. H = self.Graph(foo="bar")
  348. assert H.graph["foo"] == "bar"
  349. def test_node_attr(self):
  350. G = self.K3.copy()
  351. G.add_node(1, foo="bar")
  352. assert all(
  353. isinstance(d, G.node_attr_dict_factory) for u, d in G.nodes(data=True)
  354. )
  355. assert nodes_equal(G.nodes(), [0, 1, 2])
  356. assert nodes_equal(G.nodes(data=True), [(0, {}), (1, {"foo": "bar"}), (2, {})])
  357. G.nodes[1]["foo"] = "baz"
  358. assert nodes_equal(G.nodes(data=True), [(0, {}), (1, {"foo": "baz"}), (2, {})])
  359. assert nodes_equal(G.nodes(data="foo"), [(0, None), (1, "baz"), (2, None)])
  360. assert nodes_equal(
  361. G.nodes(data="foo", default="bar"), [(0, "bar"), (1, "baz"), (2, "bar")]
  362. )
  363. def test_node_attr2(self):
  364. G = self.K3.copy()
  365. a = {"foo": "bar"}
  366. G.add_node(3, **a)
  367. assert nodes_equal(G.nodes(), [0, 1, 2, 3])
  368. assert nodes_equal(
  369. G.nodes(data=True), [(0, {}), (1, {}), (2, {}), (3, {"foo": "bar"})]
  370. )
  371. def test_edge_lookup(self):
  372. G = self.Graph()
  373. G.add_edge(1, 2, foo="bar")
  374. assert edges_equal(G.edges[1, 2], {"foo": "bar"})
  375. def test_edge_attr(self):
  376. G = self.Graph()
  377. G.add_edge(1, 2, foo="bar")
  378. assert all(
  379. isinstance(d, G.edge_attr_dict_factory) for u, v, d in G.edges(data=True)
  380. )
  381. assert edges_equal(G.edges(data=True), [(1, 2, {"foo": "bar"})])
  382. assert edges_equal(G.edges(data="foo"), [(1, 2, "bar")])
  383. def test_edge_attr2(self):
  384. G = self.Graph()
  385. G.add_edges_from([(1, 2), (3, 4)], foo="foo")
  386. assert edges_equal(
  387. G.edges(data=True), [(1, 2, {"foo": "foo"}), (3, 4, {"foo": "foo"})]
  388. )
  389. assert edges_equal(G.edges(data="foo"), [(1, 2, "foo"), (3, 4, "foo")])
  390. def test_edge_attr3(self):
  391. G = self.Graph()
  392. G.add_edges_from([(1, 2, {"weight": 32}), (3, 4, {"weight": 64})], foo="foo")
  393. assert edges_equal(
  394. G.edges(data=True),
  395. [
  396. (1, 2, {"foo": "foo", "weight": 32}),
  397. (3, 4, {"foo": "foo", "weight": 64}),
  398. ],
  399. )
  400. G.remove_edges_from([(1, 2), (3, 4)])
  401. G.add_edge(1, 2, data=7, spam="bar", bar="foo")
  402. assert edges_equal(
  403. G.edges(data=True), [(1, 2, {"data": 7, "spam": "bar", "bar": "foo"})]
  404. )
  405. def test_edge_attr4(self):
  406. G = self.Graph()
  407. G.add_edge(1, 2, data=7, spam="bar", bar="foo")
  408. assert edges_equal(
  409. G.edges(data=True), [(1, 2, {"data": 7, "spam": "bar", "bar": "foo"})]
  410. )
  411. G[1][2]["data"] = 10 # OK to set data like this
  412. assert edges_equal(
  413. G.edges(data=True), [(1, 2, {"data": 10, "spam": "bar", "bar": "foo"})]
  414. )
  415. G.adj[1][2]["data"] = 20
  416. assert edges_equal(
  417. G.edges(data=True), [(1, 2, {"data": 20, "spam": "bar", "bar": "foo"})]
  418. )
  419. G.edges[1, 2]["data"] = 21 # another spelling, "edge"
  420. assert edges_equal(
  421. G.edges(data=True), [(1, 2, {"data": 21, "spam": "bar", "bar": "foo"})]
  422. )
  423. G.adj[1][2]["listdata"] = [20, 200]
  424. G.adj[1][2]["weight"] = 20
  425. dd = {
  426. "data": 21,
  427. "spam": "bar",
  428. "bar": "foo",
  429. "listdata": [20, 200],
  430. "weight": 20,
  431. }
  432. assert edges_equal(G.edges(data=True), [(1, 2, dd)])
  433. def test_to_undirected(self):
  434. G = self.K3
  435. self.add_attributes(G)
  436. H = nx.Graph(G)
  437. self.is_shallow_copy(H, G)
  438. self.different_attrdict(H, G)
  439. H = G.to_undirected()
  440. self.is_deepcopy(H, G)
  441. def test_to_directed_as_view(self):
  442. H = nx.path_graph(2, create_using=self.Graph)
  443. H2 = H.to_directed(as_view=True)
  444. assert H is H2._graph
  445. assert H2.has_edge(0, 1)
  446. assert H2.has_edge(1, 0) or H.is_directed()
  447. pytest.raises(nx.NetworkXError, H2.add_node, -1)
  448. pytest.raises(nx.NetworkXError, H2.add_edge, 1, 2)
  449. H.add_edge(1, 2)
  450. assert H2.has_edge(1, 2)
  451. assert H2.has_edge(2, 1) or H.is_directed()
  452. def test_to_undirected_as_view(self):
  453. H = nx.path_graph(2, create_using=self.Graph)
  454. H2 = H.to_undirected(as_view=True)
  455. assert H is H2._graph
  456. assert H2.has_edge(0, 1)
  457. assert H2.has_edge(1, 0)
  458. pytest.raises(nx.NetworkXError, H2.add_node, -1)
  459. pytest.raises(nx.NetworkXError, H2.add_edge, 1, 2)
  460. H.add_edge(1, 2)
  461. assert H2.has_edge(1, 2)
  462. assert H2.has_edge(2, 1)
  463. def test_directed_class(self):
  464. G = self.Graph()
  465. class newGraph(G.to_undirected_class()):
  466. def to_directed_class(self):
  467. return newDiGraph
  468. def to_undirected_class(self):
  469. return newGraph
  470. class newDiGraph(G.to_directed_class()):
  471. def to_directed_class(self):
  472. return newDiGraph
  473. def to_undirected_class(self):
  474. return newGraph
  475. G = newDiGraph() if G.is_directed() else newGraph()
  476. H = G.to_directed()
  477. assert isinstance(H, newDiGraph)
  478. H = G.to_undirected()
  479. assert isinstance(H, newGraph)
  480. def test_to_directed(self):
  481. G = self.K3
  482. self.add_attributes(G)
  483. H = nx.DiGraph(G)
  484. self.is_shallow_copy(H, G)
  485. self.different_attrdict(H, G)
  486. H = G.to_directed()
  487. self.is_deepcopy(H, G)
  488. def test_subgraph(self):
  489. G = self.K3
  490. self.add_attributes(G)
  491. H = G.subgraph([0, 1, 2, 5])
  492. self.graphs_equal(H, G)
  493. self.same_attrdict(H, G)
  494. self.shallow_copy_attrdict(H, G)
  495. H = G.subgraph(0)
  496. assert H.adj == {0: {}}
  497. H = G.subgraph([])
  498. assert H.adj == {}
  499. assert G.adj != {}
  500. def test_selfloops_attr(self):
  501. G = self.K3.copy()
  502. G.add_edge(0, 0)
  503. G.add_edge(1, 1, weight=2)
  504. assert edges_equal(
  505. nx.selfloop_edges(G, data=True), [(0, 0, {}), (1, 1, {"weight": 2})]
  506. )
  507. assert edges_equal(
  508. nx.selfloop_edges(G, data="weight"), [(0, 0, None), (1, 1, 2)]
  509. )
  510. class TestGraph(BaseAttrGraphTester):
  511. """Tests specific to dict-of-dict-of-dict graph data structure"""
  512. def setup_method(self):
  513. self.Graph = nx.Graph
  514. # build dict-of-dict-of-dict K3
  515. ed1, ed2, ed3 = ({}, {}, {})
  516. self.k3adj = {0: {1: ed1, 2: ed2}, 1: {0: ed1, 2: ed3}, 2: {0: ed2, 1: ed3}}
  517. self.k3edges = [(0, 1), (0, 2), (1, 2)]
  518. self.k3nodes = [0, 1, 2]
  519. self.K3 = self.Graph()
  520. self.K3._adj = self.k3adj
  521. self.K3._node = {}
  522. self.K3._node[0] = {}
  523. self.K3._node[1] = {}
  524. self.K3._node[2] = {}
  525. def test_pickle(self):
  526. G = self.K3
  527. pg = pickle.loads(pickle.dumps(G, -1))
  528. self.graphs_equal(pg, G)
  529. pg = pickle.loads(pickle.dumps(G))
  530. self.graphs_equal(pg, G)
  531. def test_data_input(self):
  532. G = self.Graph({1: [2], 2: [1]}, name="test")
  533. assert G.name == "test"
  534. assert sorted(G.adj.items()) == [(1, {2: {}}), (2, {1: {}})]
  535. def test_adjacency(self):
  536. G = self.K3
  537. assert dict(G.adjacency()) == {
  538. 0: {1: {}, 2: {}},
  539. 1: {0: {}, 2: {}},
  540. 2: {0: {}, 1: {}},
  541. }
  542. def test_getitem(self):
  543. G = self.K3
  544. assert G.adj[0] == {1: {}, 2: {}}
  545. assert G[0] == {1: {}, 2: {}}
  546. with pytest.raises(KeyError):
  547. G.__getitem__("j")
  548. with pytest.raises(TypeError):
  549. G.__getitem__(["A"])
  550. def test_add_node(self):
  551. G = self.Graph()
  552. G.add_node(0)
  553. assert G.adj == {0: {}}
  554. # test add attributes
  555. G.add_node(1, c="red")
  556. G.add_node(2, c="blue")
  557. G.add_node(3, c="red")
  558. assert G.nodes[1]["c"] == "red"
  559. assert G.nodes[2]["c"] == "blue"
  560. assert G.nodes[3]["c"] == "red"
  561. # test updating attributes
  562. G.add_node(1, c="blue")
  563. G.add_node(2, c="red")
  564. G.add_node(3, c="blue")
  565. assert G.nodes[1]["c"] == "blue"
  566. assert G.nodes[2]["c"] == "red"
  567. assert G.nodes[3]["c"] == "blue"
  568. def test_add_nodes_from(self):
  569. G = self.Graph()
  570. G.add_nodes_from([0, 1, 2])
  571. assert G.adj == {0: {}, 1: {}, 2: {}}
  572. # test add attributes
  573. G.add_nodes_from([0, 1, 2], c="red")
  574. assert G.nodes[0]["c"] == "red"
  575. assert G.nodes[2]["c"] == "red"
  576. # test that attribute dicts are not the same
  577. assert G.nodes[0] is not G.nodes[1]
  578. # test updating attributes
  579. G.add_nodes_from([0, 1, 2], c="blue")
  580. assert G.nodes[0]["c"] == "blue"
  581. assert G.nodes[2]["c"] == "blue"
  582. assert G.nodes[0] is not G.nodes[1]
  583. # test tuple input
  584. H = self.Graph()
  585. H.add_nodes_from(G.nodes(data=True))
  586. assert H.nodes[0]["c"] == "blue"
  587. assert H.nodes[2]["c"] == "blue"
  588. assert H.nodes[0] is not H.nodes[1]
  589. # specific overrides general
  590. H.add_nodes_from([0, (1, {"c": "green"}), (3, {"c": "cyan"})], c="red")
  591. assert H.nodes[0]["c"] == "red"
  592. assert H.nodes[1]["c"] == "green"
  593. assert H.nodes[2]["c"] == "blue"
  594. assert H.nodes[3]["c"] == "cyan"
  595. def test_remove_node(self):
  596. G = self.K3.copy()
  597. G.remove_node(0)
  598. assert G.adj == {1: {2: {}}, 2: {1: {}}}
  599. with pytest.raises(nx.NetworkXError):
  600. G.remove_node(-1)
  601. # generator here to implement list,set,string...
  602. def test_remove_nodes_from(self):
  603. G = self.K3.copy()
  604. G.remove_nodes_from([0, 1])
  605. assert G.adj == {2: {}}
  606. G.remove_nodes_from([-1]) # silent fail
  607. def test_add_edge(self):
  608. G = self.Graph()
  609. G.add_edge(0, 1)
  610. assert G.adj == {0: {1: {}}, 1: {0: {}}}
  611. G = self.Graph()
  612. G.add_edge(*(0, 1))
  613. assert G.adj == {0: {1: {}}, 1: {0: {}}}
  614. G = self.Graph()
  615. with pytest.raises(ValueError):
  616. G.add_edge(None, "anything")
  617. def test_add_edges_from(self):
  618. G = self.Graph()
  619. G.add_edges_from([(0, 1), (0, 2, {"weight": 3})])
  620. assert G.adj == {
  621. 0: {1: {}, 2: {"weight": 3}},
  622. 1: {0: {}},
  623. 2: {0: {"weight": 3}},
  624. }
  625. G = self.Graph()
  626. G.add_edges_from([(0, 1), (0, 2, {"weight": 3}), (1, 2, {"data": 4})], data=2)
  627. assert G.adj == {
  628. 0: {1: {"data": 2}, 2: {"weight": 3, "data": 2}},
  629. 1: {0: {"data": 2}, 2: {"data": 4}},
  630. 2: {0: {"weight": 3, "data": 2}, 1: {"data": 4}},
  631. }
  632. with pytest.raises(nx.NetworkXError):
  633. G.add_edges_from([(0,)]) # too few in tuple
  634. with pytest.raises(nx.NetworkXError):
  635. G.add_edges_from([(0, 1, 2, 3)]) # too many in tuple
  636. with pytest.raises(TypeError):
  637. G.add_edges_from([0]) # not a tuple
  638. with pytest.raises(ValueError):
  639. G.add_edges_from([(None, 3), (3, 2)]) # None cannot be a node
  640. def test_remove_edge(self):
  641. G = self.K3.copy()
  642. G.remove_edge(0, 1)
  643. assert G.adj == {0: {2: {}}, 1: {2: {}}, 2: {0: {}, 1: {}}}
  644. with pytest.raises(nx.NetworkXError):
  645. G.remove_edge(-1, 0)
  646. def test_remove_edges_from(self):
  647. G = self.K3.copy()
  648. G.remove_edges_from([(0, 1)])
  649. assert G.adj == {0: {2: {}}, 1: {2: {}}, 2: {0: {}, 1: {}}}
  650. G.remove_edges_from([(0, 0)]) # silent fail
  651. def test_clear(self):
  652. G = self.K3.copy()
  653. G.graph["name"] = "K3"
  654. G.clear()
  655. assert list(G.nodes) == []
  656. assert G.adj == {}
  657. assert G.graph == {}
  658. def test_clear_edges(self):
  659. G = self.K3.copy()
  660. G.graph["name"] = "K3"
  661. nodes = list(G.nodes)
  662. G.clear_edges()
  663. assert list(G.nodes) == nodes
  664. assert G.adj == {0: {}, 1: {}, 2: {}}
  665. assert list(G.edges) == []
  666. assert G.graph["name"] == "K3"
  667. def test_edges_data(self):
  668. G = self.K3
  669. all_edges = [(0, 1, {}), (0, 2, {}), (1, 2, {})]
  670. assert edges_equal(G.edges(data=True), all_edges)
  671. assert edges_equal(G.edges(0, data=True), [(0, 1, {}), (0, 2, {})])
  672. assert edges_equal(G.edges([0, 1], data=True), all_edges)
  673. with pytest.raises(nx.NetworkXError):
  674. G.edges(-1, True)
  675. def test_get_edge_data(self):
  676. G = self.K3.copy()
  677. assert G.get_edge_data(0, 1) == {}
  678. assert G[0][1] == {}
  679. assert G.get_edge_data(10, 20) is None
  680. assert G.get_edge_data(-1, 0) is None
  681. assert G.get_edge_data(-1, 0, default=1) == 1
  682. def test_update(self):
  683. # specify both edges and nodes
  684. G = self.K3.copy()
  685. G.update(nodes=[3, (4, {"size": 2})], edges=[(4, 5), (6, 7, {"weight": 2})])
  686. nlist = [
  687. (0, {}),
  688. (1, {}),
  689. (2, {}),
  690. (3, {}),
  691. (4, {"size": 2}),
  692. (5, {}),
  693. (6, {}),
  694. (7, {}),
  695. ]
  696. assert sorted(G.nodes.data()) == nlist
  697. if G.is_directed():
  698. elist = [
  699. (0, 1, {}),
  700. (0, 2, {}),
  701. (1, 0, {}),
  702. (1, 2, {}),
  703. (2, 0, {}),
  704. (2, 1, {}),
  705. (4, 5, {}),
  706. (6, 7, {"weight": 2}),
  707. ]
  708. else:
  709. elist = [
  710. (0, 1, {}),
  711. (0, 2, {}),
  712. (1, 2, {}),
  713. (4, 5, {}),
  714. (6, 7, {"weight": 2}),
  715. ]
  716. assert sorted(G.edges.data()) == elist
  717. assert G.graph == {}
  718. # no keywords -- order is edges, nodes
  719. G = self.K3.copy()
  720. G.update([(4, 5), (6, 7, {"weight": 2})], [3, (4, {"size": 2})])
  721. assert sorted(G.nodes.data()) == nlist
  722. assert sorted(G.edges.data()) == elist
  723. assert G.graph == {}
  724. # update using only a graph
  725. G = self.Graph()
  726. G.graph["foo"] = "bar"
  727. G.add_node(2, data=4)
  728. G.add_edge(0, 1, weight=0.5)
  729. GG = G.copy()
  730. H = self.Graph()
  731. GG.update(H)
  732. assert graphs_equal(G, GG)
  733. H.update(G)
  734. assert graphs_equal(H, G)
  735. # update nodes only
  736. H = self.Graph()
  737. H.update(nodes=[3, 4])
  738. assert H.nodes ^ {3, 4} == set()
  739. assert H.size() == 0
  740. # update edges only
  741. H = self.Graph()
  742. H.update(edges=[(3, 4)])
  743. assert sorted(H.edges.data()) == [(3, 4, {})]
  744. assert H.size() == 1
  745. # No inputs -> exception
  746. with pytest.raises(nx.NetworkXError):
  747. nx.Graph().update()
  748. class TestEdgeSubgraph:
  749. """Unit tests for the :meth:`Graph.edge_subgraph` method."""
  750. def setup_method(self):
  751. # Create a path graph on five nodes.
  752. G = nx.path_graph(5)
  753. # Add some node, edge, and graph attributes.
  754. for i in range(5):
  755. G.nodes[i]["name"] = f"node{i}"
  756. G.edges[0, 1]["name"] = "edge01"
  757. G.edges[3, 4]["name"] = "edge34"
  758. G.graph["name"] = "graph"
  759. # Get the subgraph induced by the first and last edges.
  760. self.G = G
  761. self.H = G.edge_subgraph([(0, 1), (3, 4)])
  762. def test_correct_nodes(self):
  763. """Tests that the subgraph has the correct nodes."""
  764. assert [0, 1, 3, 4] == sorted(self.H.nodes())
  765. def test_correct_edges(self):
  766. """Tests that the subgraph has the correct edges."""
  767. assert [(0, 1, "edge01"), (3, 4, "edge34")] == sorted(self.H.edges(data="name"))
  768. def test_add_node(self):
  769. """Tests that adding a node to the original graph does not
  770. affect the nodes of the subgraph.
  771. """
  772. self.G.add_node(5)
  773. assert [0, 1, 3, 4] == sorted(self.H.nodes())
  774. def test_remove_node(self):
  775. """Tests that removing a node in the original graph does
  776. affect the nodes of the subgraph.
  777. """
  778. self.G.remove_node(0)
  779. assert [1, 3, 4] == sorted(self.H.nodes())
  780. def test_node_attr_dict(self):
  781. """Tests that the node attribute dictionary of the two graphs is
  782. the same object.
  783. """
  784. for v in self.H:
  785. assert self.G.nodes[v] == self.H.nodes[v]
  786. # Making a change to G should make a change in H and vice versa.
  787. self.G.nodes[0]["name"] = "foo"
  788. assert self.G.nodes[0] == self.H.nodes[0]
  789. self.H.nodes[1]["name"] = "bar"
  790. assert self.G.nodes[1] == self.H.nodes[1]
  791. def test_edge_attr_dict(self):
  792. """Tests that the edge attribute dictionary of the two graphs is
  793. the same object.
  794. """
  795. for u, v in self.H.edges():
  796. assert self.G.edges[u, v] == self.H.edges[u, v]
  797. # Making a change to G should make a change in H and vice versa.
  798. self.G.edges[0, 1]["name"] = "foo"
  799. assert self.G.edges[0, 1]["name"] == self.H.edges[0, 1]["name"]
  800. self.H.edges[3, 4]["name"] = "bar"
  801. assert self.G.edges[3, 4]["name"] == self.H.edges[3, 4]["name"]
  802. def test_graph_attr_dict(self):
  803. """Tests that the graph attribute dictionary of the two graphs
  804. is the same object.
  805. """
  806. assert self.G.graph is self.H.graph
  807. def test_graph_new_extra_args():
  808. """Test that subclasses can accept additional arguments.
  809. See: https://github.com/networkx/networkx/issues/8367
  810. """
  811. class MyGraph(nx.Graph):
  812. def __init__(self, incoming_graph_data=None, extra_arg=None, **attr):
  813. super().__init__(incoming_graph_data, **attr)
  814. self.extra_arg = extra_arg
  815. G = MyGraph(extra_arg="extra arg")
  816. assert G.extra_arg == "extra arg"
  817. G = MyGraph([], "extra arg")
  818. assert G.extra_arg == "extra arg"
  819. G = MyGraph([(0, 1)], extra_arg="foo", name="bar")
  820. assert G.extra_arg == "foo"
  821. assert G.graph["name"] == "bar"
  822. assert nx.utils.edges_equal(G.edges, [(0, 1)])