test_graph_hashing.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. import pytest
  2. import networkx as nx
  3. from networkx.generators import directed
  4. # Unit tests for the :func:`~networkx.weisfeiler_lehman_graph_hash` function
  5. def test_empty_graph_hash():
  6. """
  7. empty graphs should give hashes regardless of other params
  8. """
  9. G1 = nx.empty_graph()
  10. G2 = nx.empty_graph()
  11. h1 = nx.weisfeiler_lehman_graph_hash(G1)
  12. h2 = nx.weisfeiler_lehman_graph_hash(G2)
  13. h3 = nx.weisfeiler_lehman_graph_hash(G2, edge_attr="edge_attr1")
  14. h4 = nx.weisfeiler_lehman_graph_hash(G2, node_attr="node_attr1")
  15. h5 = nx.weisfeiler_lehman_graph_hash(
  16. G2, edge_attr="edge_attr1", node_attr="node_attr1"
  17. )
  18. h6 = nx.weisfeiler_lehman_graph_hash(G2, iterations=10)
  19. assert h1 == h2
  20. assert h1 == h3
  21. assert h1 == h4
  22. assert h1 == h5
  23. assert h1 == h6
  24. def test_directed():
  25. """
  26. A directed graph with no bi-directional edges should yield different a graph hash
  27. to the same graph taken as undirected if there are no hash collisions.
  28. """
  29. r = 10
  30. for i in range(r):
  31. G_directed = nx.gn_graph(10 + r, seed=100 + i)
  32. G_undirected = nx.to_undirected(G_directed)
  33. h_directed = nx.weisfeiler_lehman_graph_hash(G_directed)
  34. h_undirected = nx.weisfeiler_lehman_graph_hash(G_undirected)
  35. assert h_directed != h_undirected
  36. def test_reversed():
  37. """
  38. A directed graph with no bi-directional edges should yield different a graph hash
  39. to the same graph taken with edge directions reversed if there are no hash collisions.
  40. Here we test a cycle graph which is the minimal counterexample
  41. """
  42. G = nx.cycle_graph(5, create_using=nx.DiGraph)
  43. nx.set_node_attributes(G, {n: str(n) for n in G.nodes()}, name="label")
  44. G_reversed = G.reverse()
  45. h = nx.weisfeiler_lehman_graph_hash(G, node_attr="label")
  46. h_reversed = nx.weisfeiler_lehman_graph_hash(G_reversed, node_attr="label")
  47. assert h != h_reversed
  48. def test_isomorphic():
  49. """
  50. graph hashes should be invariant to node-relabeling (when the output is reindexed
  51. by the same mapping)
  52. """
  53. n, r = 100, 10
  54. p = 1.0 / r
  55. for i in range(1, r + 1):
  56. G1 = nx.erdos_renyi_graph(n, p * i, seed=200 + i)
  57. G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
  58. g1_hash = nx.weisfeiler_lehman_graph_hash(G1)
  59. g2_hash = nx.weisfeiler_lehman_graph_hash(G2)
  60. assert g1_hash == g2_hash
  61. def test_isomorphic_edge_attr():
  62. """
  63. Isomorphic graphs with differing edge attributes should yield different graph
  64. hashes if the 'edge_attr' argument is supplied and populated in the graph,
  65. and there are no hash collisions.
  66. The output should still be invariant to node-relabeling
  67. """
  68. n, r = 100, 10
  69. p = 1.0 / r
  70. for i in range(1, r + 1):
  71. G1 = nx.erdos_renyi_graph(n, p * i, seed=300 + i)
  72. for a, b in G1.edges:
  73. G1[a][b]["edge_attr1"] = f"{a}-{b}-1"
  74. G1[a][b]["edge_attr2"] = f"{a}-{b}-2"
  75. g1_hash_with_edge_attr1 = nx.weisfeiler_lehman_graph_hash(
  76. G1, edge_attr="edge_attr1"
  77. )
  78. g1_hash_with_edge_attr2 = nx.weisfeiler_lehman_graph_hash(
  79. G1, edge_attr="edge_attr2"
  80. )
  81. g1_hash_no_edge_attr = nx.weisfeiler_lehman_graph_hash(G1, edge_attr=None)
  82. assert g1_hash_with_edge_attr1 != g1_hash_no_edge_attr
  83. assert g1_hash_with_edge_attr2 != g1_hash_no_edge_attr
  84. assert g1_hash_with_edge_attr1 != g1_hash_with_edge_attr2
  85. G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
  86. g2_hash_with_edge_attr1 = nx.weisfeiler_lehman_graph_hash(
  87. G2, edge_attr="edge_attr1"
  88. )
  89. g2_hash_with_edge_attr2 = nx.weisfeiler_lehman_graph_hash(
  90. G2, edge_attr="edge_attr2"
  91. )
  92. assert g1_hash_with_edge_attr1 == g2_hash_with_edge_attr1
  93. assert g1_hash_with_edge_attr2 == g2_hash_with_edge_attr2
  94. def test_missing_edge_attr():
  95. """
  96. If the 'edge_attr' argument is supplied but is missing from an edge in the graph,
  97. we should raise a KeyError
  98. """
  99. G = nx.Graph()
  100. G.add_edges_from([(1, 2, {"edge_attr1": "a"}), (1, 3, {})])
  101. pytest.raises(KeyError, nx.weisfeiler_lehman_graph_hash, G, edge_attr="edge_attr1")
  102. def test_isomorphic_node_attr():
  103. """
  104. Isomorphic graphs with differing node attributes should yield different graph
  105. hashes if the 'node_attr' argument is supplied and populated in the graph, and
  106. there are no hash collisions.
  107. The output should still be invariant to node-relabeling
  108. """
  109. n, r = 100, 10
  110. p = 1.0 / r
  111. for i in range(1, r + 1):
  112. G1 = nx.erdos_renyi_graph(n, p * i, seed=400 + i)
  113. for u in G1.nodes():
  114. G1.nodes[u]["node_attr1"] = f"{u}-1"
  115. G1.nodes[u]["node_attr2"] = f"{u}-2"
  116. g1_hash_with_node_attr1 = nx.weisfeiler_lehman_graph_hash(
  117. G1, node_attr="node_attr1"
  118. )
  119. g1_hash_with_node_attr2 = nx.weisfeiler_lehman_graph_hash(
  120. G1, node_attr="node_attr2"
  121. )
  122. g1_hash_no_node_attr = nx.weisfeiler_lehman_graph_hash(G1, node_attr=None)
  123. assert g1_hash_with_node_attr1 != g1_hash_no_node_attr
  124. assert g1_hash_with_node_attr2 != g1_hash_no_node_attr
  125. assert g1_hash_with_node_attr1 != g1_hash_with_node_attr2
  126. G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
  127. g2_hash_with_node_attr1 = nx.weisfeiler_lehman_graph_hash(
  128. G2, node_attr="node_attr1"
  129. )
  130. g2_hash_with_node_attr2 = nx.weisfeiler_lehman_graph_hash(
  131. G2, node_attr="node_attr2"
  132. )
  133. assert g1_hash_with_node_attr1 == g2_hash_with_node_attr1
  134. assert g1_hash_with_node_attr2 == g2_hash_with_node_attr2
  135. def test_missing_node_attr():
  136. """
  137. If the 'node_attr' argument is supplied but is missing from a node in the graph,
  138. we should raise a KeyError
  139. """
  140. G = nx.Graph()
  141. G.add_nodes_from([(1, {"node_attr1": "a"}), (2, {})])
  142. G.add_edges_from([(1, 2), (2, 3), (3, 1), (1, 4)])
  143. pytest.raises(KeyError, nx.weisfeiler_lehman_graph_hash, G, node_attr="node_attr1")
  144. def test_isomorphic_edge_attr_and_node_attr():
  145. """
  146. Isomorphic graphs with differing node attributes should yield different graph
  147. hashes if the 'node_attr' and 'edge_attr' argument is supplied and populated in
  148. the graph, and there are no hash collisions.
  149. The output should still be invariant to node-relabeling
  150. """
  151. n, r = 100, 10
  152. p = 1.0 / r
  153. for i in range(1, r + 1):
  154. G1 = nx.erdos_renyi_graph(n, p * i, seed=500 + i)
  155. for u in G1.nodes():
  156. G1.nodes[u]["node_attr1"] = f"{u}-1"
  157. G1.nodes[u]["node_attr2"] = f"{u}-2"
  158. for a, b in G1.edges:
  159. G1[a][b]["edge_attr1"] = f"{a}-{b}-1"
  160. G1[a][b]["edge_attr2"] = f"{a}-{b}-2"
  161. g1_hash_edge1_node1 = nx.weisfeiler_lehman_graph_hash(
  162. G1, edge_attr="edge_attr1", node_attr="node_attr1"
  163. )
  164. g1_hash_edge2_node2 = nx.weisfeiler_lehman_graph_hash(
  165. G1, edge_attr="edge_attr2", node_attr="node_attr2"
  166. )
  167. g1_hash_edge1_node2 = nx.weisfeiler_lehman_graph_hash(
  168. G1, edge_attr="edge_attr1", node_attr="node_attr2"
  169. )
  170. g1_hash_no_attr = nx.weisfeiler_lehman_graph_hash(G1)
  171. assert g1_hash_edge1_node1 != g1_hash_no_attr
  172. assert g1_hash_edge2_node2 != g1_hash_no_attr
  173. assert g1_hash_edge1_node1 != g1_hash_edge2_node2
  174. assert g1_hash_edge1_node2 != g1_hash_edge2_node2
  175. assert g1_hash_edge1_node2 != g1_hash_edge1_node1
  176. G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
  177. g2_hash_edge1_node1 = nx.weisfeiler_lehman_graph_hash(
  178. G2, edge_attr="edge_attr1", node_attr="node_attr1"
  179. )
  180. g2_hash_edge2_node2 = nx.weisfeiler_lehman_graph_hash(
  181. G2, edge_attr="edge_attr2", node_attr="node_attr2"
  182. )
  183. assert g1_hash_edge1_node1 == g2_hash_edge1_node1
  184. assert g1_hash_edge2_node2 == g2_hash_edge2_node2
  185. def test_digest_size():
  186. """
  187. The hash string lengths should be as expected for a variety of graphs and
  188. digest sizes
  189. """
  190. n, r = 100, 10
  191. p = 1.0 / r
  192. for i in range(1, r + 1):
  193. G = nx.erdos_renyi_graph(n, p * i, seed=1000 + i)
  194. h16 = nx.weisfeiler_lehman_graph_hash(G)
  195. h32 = nx.weisfeiler_lehman_graph_hash(G, digest_size=32)
  196. assert h16 != h32
  197. assert len(h16) == 16 * 2
  198. assert len(h32) == 32 * 2
  199. # Unit tests for the :func:`~networkx.weisfeiler_lehman_hash_subgraphs` function
  200. def is_subiteration(a, b):
  201. """
  202. returns True if that each hash sequence in 'a' is a prefix for
  203. the corresponding sequence indexed by the same node in 'b'.
  204. """
  205. return all(b[node][: len(hashes)] == hashes for node, hashes in a.items())
  206. def hexdigest_sizes_correct(a, digest_size):
  207. """
  208. returns True if all hex digest sizes are the expected length in a node:subgraph-hashes
  209. dictionary. Hex digest string length == 2 * bytes digest length since each pair of hex
  210. digits encodes 1 byte (https://docs.python.org/3/library/hashlib.html)
  211. """
  212. hexdigest_size = digest_size * 2
  213. list_digest_sizes_correct = lambda l: all(len(x) == hexdigest_size for x in l)
  214. return all(list_digest_sizes_correct(hashes) for hashes in a.values())
  215. def test_empty_graph_subgraph_hash():
  216. """ "
  217. empty graphs should give empty dict subgraph hashes regardless of other params
  218. """
  219. G = nx.empty_graph()
  220. subgraph_hashes1 = nx.weisfeiler_lehman_subgraph_hashes(G)
  221. subgraph_hashes2 = nx.weisfeiler_lehman_subgraph_hashes(G, edge_attr="edge_attr")
  222. subgraph_hashes3 = nx.weisfeiler_lehman_subgraph_hashes(G, node_attr="edge_attr")
  223. subgraph_hashes4 = nx.weisfeiler_lehman_subgraph_hashes(G, iterations=2)
  224. subgraph_hashes5 = nx.weisfeiler_lehman_subgraph_hashes(G, digest_size=64)
  225. assert subgraph_hashes1 == {}
  226. assert subgraph_hashes2 == {}
  227. assert subgraph_hashes3 == {}
  228. assert subgraph_hashes4 == {}
  229. assert subgraph_hashes5 == {}
  230. def test_directed_subgraph_hash():
  231. """
  232. A directed graph with no bi-directional edges should yield different subgraph hashes
  233. to the same graph taken as undirected, if all hashes don't collide.
  234. """
  235. r = 10
  236. for i in range(r):
  237. G_directed = nx.gn_graph(10 + r, seed=100 + i)
  238. G_undirected = nx.to_undirected(G_directed)
  239. directed_subgraph_hashes = nx.weisfeiler_lehman_subgraph_hashes(G_directed)
  240. undirected_subgraph_hashes = nx.weisfeiler_lehman_subgraph_hashes(G_undirected)
  241. assert directed_subgraph_hashes != undirected_subgraph_hashes
  242. def test_reversed_subgraph_hash():
  243. """
  244. A directed graph with no bi-directional edges should yield different subgraph hashes
  245. to the same graph taken with edge directions reversed if there are no hash collisions.
  246. Here we test a cycle graph which is the minimal counterexample
  247. """
  248. G = nx.cycle_graph(5, create_using=nx.DiGraph)
  249. nx.set_node_attributes(G, {n: str(n) for n in G.nodes()}, name="label")
  250. G_reversed = G.reverse()
  251. h = nx.weisfeiler_lehman_subgraph_hashes(G, node_attr="label")
  252. h_reversed = nx.weisfeiler_lehman_subgraph_hashes(G_reversed, node_attr="label")
  253. assert h != h_reversed
  254. def test_isomorphic_subgraph_hash():
  255. """
  256. the subgraph hashes should be invariant to node-relabeling when the output is reindexed
  257. by the same mapping and all hashes don't collide.
  258. """
  259. n, r = 100, 10
  260. p = 1.0 / r
  261. for i in range(1, r + 1):
  262. G1 = nx.erdos_renyi_graph(n, p * i, seed=200 + i)
  263. G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
  264. g1_subgraph_hashes = nx.weisfeiler_lehman_subgraph_hashes(G1)
  265. g2_subgraph_hashes = nx.weisfeiler_lehman_subgraph_hashes(G2)
  266. assert g1_subgraph_hashes == {-1 * k: v for k, v in g2_subgraph_hashes.items()}
  267. def test_isomorphic_edge_attr_subgraph_hash():
  268. """
  269. Isomorphic graphs with differing edge attributes should yield different subgraph
  270. hashes if the 'edge_attr' argument is supplied and populated in the graph, and
  271. all hashes don't collide.
  272. The output should still be invariant to node-relabeling
  273. """
  274. n, r = 100, 10
  275. p = 1.0 / r
  276. for i in range(1, r + 1):
  277. G1 = nx.erdos_renyi_graph(n, p * i, seed=300 + i)
  278. for a, b in G1.edges:
  279. G1[a][b]["edge_attr1"] = f"{a}-{b}-1"
  280. G1[a][b]["edge_attr2"] = f"{a}-{b}-2"
  281. g1_hash_with_edge_attr1 = nx.weisfeiler_lehman_subgraph_hashes(
  282. G1, edge_attr="edge_attr1"
  283. )
  284. g1_hash_with_edge_attr2 = nx.weisfeiler_lehman_subgraph_hashes(
  285. G1, edge_attr="edge_attr2"
  286. )
  287. g1_hash_no_edge_attr = nx.weisfeiler_lehman_subgraph_hashes(G1, edge_attr=None)
  288. assert g1_hash_with_edge_attr1 != g1_hash_no_edge_attr
  289. assert g1_hash_with_edge_attr2 != g1_hash_no_edge_attr
  290. assert g1_hash_with_edge_attr1 != g1_hash_with_edge_attr2
  291. G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
  292. g2_hash_with_edge_attr1 = nx.weisfeiler_lehman_subgraph_hashes(
  293. G2, edge_attr="edge_attr1"
  294. )
  295. g2_hash_with_edge_attr2 = nx.weisfeiler_lehman_subgraph_hashes(
  296. G2, edge_attr="edge_attr2"
  297. )
  298. assert g1_hash_with_edge_attr1 == {
  299. -1 * k: v for k, v in g2_hash_with_edge_attr1.items()
  300. }
  301. assert g1_hash_with_edge_attr2 == {
  302. -1 * k: v for k, v in g2_hash_with_edge_attr2.items()
  303. }
  304. def test_missing_edge_attr_subgraph_hash():
  305. """
  306. If the 'edge_attr' argument is supplied but is missing from an edge in the graph,
  307. we should raise a KeyError
  308. """
  309. G = nx.Graph()
  310. G.add_edges_from([(1, 2, {"edge_attr1": "a"}), (1, 3, {})])
  311. pytest.raises(
  312. KeyError, nx.weisfeiler_lehman_subgraph_hashes, G, edge_attr="edge_attr1"
  313. )
  314. def test_isomorphic_node_attr_subgraph_hash():
  315. """
  316. Isomorphic graphs with differing node attributes should yield different subgraph
  317. hashes if the 'node_attr' argument is supplied and populated in the graph, and
  318. all hashes don't collide.
  319. The output should still be invariant to node-relabeling
  320. """
  321. n, r = 100, 10
  322. p = 1.0 / r
  323. for i in range(1, r + 1):
  324. G1 = nx.erdos_renyi_graph(n, p * i, seed=400 + i)
  325. for u in G1.nodes():
  326. G1.nodes[u]["node_attr1"] = f"{u}-1"
  327. G1.nodes[u]["node_attr2"] = f"{u}-2"
  328. g1_hash_with_node_attr1 = nx.weisfeiler_lehman_subgraph_hashes(
  329. G1, node_attr="node_attr1"
  330. )
  331. g1_hash_with_node_attr2 = nx.weisfeiler_lehman_subgraph_hashes(
  332. G1, node_attr="node_attr2"
  333. )
  334. g1_hash_no_node_attr = nx.weisfeiler_lehman_subgraph_hashes(G1, node_attr=None)
  335. assert g1_hash_with_node_attr1 != g1_hash_no_node_attr
  336. assert g1_hash_with_node_attr2 != g1_hash_no_node_attr
  337. assert g1_hash_with_node_attr1 != g1_hash_with_node_attr2
  338. G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
  339. g2_hash_with_node_attr1 = nx.weisfeiler_lehman_subgraph_hashes(
  340. G2, node_attr="node_attr1"
  341. )
  342. g2_hash_with_node_attr2 = nx.weisfeiler_lehman_subgraph_hashes(
  343. G2, node_attr="node_attr2"
  344. )
  345. assert g1_hash_with_node_attr1 == {
  346. -1 * k: v for k, v in g2_hash_with_node_attr1.items()
  347. }
  348. assert g1_hash_with_node_attr2 == {
  349. -1 * k: v for k, v in g2_hash_with_node_attr2.items()
  350. }
  351. def test_missing_node_attr_subgraph_hash():
  352. """
  353. If the 'node_attr' argument is supplied but is missing from a node in the graph,
  354. we should raise a KeyError
  355. """
  356. G = nx.Graph()
  357. G.add_nodes_from([(1, {"node_attr1": "a"}), (2, {})])
  358. G.add_edges_from([(1, 2), (2, 3), (3, 1), (1, 4)])
  359. pytest.raises(
  360. KeyError, nx.weisfeiler_lehman_subgraph_hashes, G, node_attr="node_attr1"
  361. )
  362. def test_isomorphic_edge_attr_and_node_attr_subgraph_hash():
  363. """
  364. Isomorphic graphs with differing node attributes should yield different subgraph
  365. hashes if the 'node_attr' and 'edge_attr' argument is supplied and populated in
  366. the graph, and all hashes don't collide
  367. The output should still be invariant to node-relabeling
  368. """
  369. n, r = 100, 10
  370. p = 1.0 / r
  371. for i in range(1, r + 1):
  372. G1 = nx.erdos_renyi_graph(n, p * i, seed=500 + i)
  373. for u in G1.nodes():
  374. G1.nodes[u]["node_attr1"] = f"{u}-1"
  375. G1.nodes[u]["node_attr2"] = f"{u}-2"
  376. for a, b in G1.edges:
  377. G1[a][b]["edge_attr1"] = f"{a}-{b}-1"
  378. G1[a][b]["edge_attr2"] = f"{a}-{b}-2"
  379. g1_hash_edge1_node1 = nx.weisfeiler_lehman_subgraph_hashes(
  380. G1, edge_attr="edge_attr1", node_attr="node_attr1"
  381. )
  382. g1_hash_edge2_node2 = nx.weisfeiler_lehman_subgraph_hashes(
  383. G1, edge_attr="edge_attr2", node_attr="node_attr2"
  384. )
  385. g1_hash_edge1_node2 = nx.weisfeiler_lehman_subgraph_hashes(
  386. G1, edge_attr="edge_attr1", node_attr="node_attr2"
  387. )
  388. g1_hash_no_attr = nx.weisfeiler_lehman_subgraph_hashes(G1)
  389. assert g1_hash_edge1_node1 != g1_hash_no_attr
  390. assert g1_hash_edge2_node2 != g1_hash_no_attr
  391. assert g1_hash_edge1_node1 != g1_hash_edge2_node2
  392. assert g1_hash_edge1_node2 != g1_hash_edge2_node2
  393. assert g1_hash_edge1_node2 != g1_hash_edge1_node1
  394. G2 = nx.relabel_nodes(G1, {u: -1 * u for u in G1.nodes()})
  395. g2_hash_edge1_node1 = nx.weisfeiler_lehman_subgraph_hashes(
  396. G2, edge_attr="edge_attr1", node_attr="node_attr1"
  397. )
  398. g2_hash_edge2_node2 = nx.weisfeiler_lehman_subgraph_hashes(
  399. G2, edge_attr="edge_attr2", node_attr="node_attr2"
  400. )
  401. assert g1_hash_edge1_node1 == {
  402. -1 * k: v for k, v in g2_hash_edge1_node1.items()
  403. }
  404. assert g1_hash_edge2_node2 == {
  405. -1 * k: v for k, v in g2_hash_edge2_node2.items()
  406. }
  407. def test_iteration_depth():
  408. """
  409. All nodes should have the correct number of subgraph hashes in the output when
  410. using degree as initial node labels
  411. Subsequent iteration depths for the same graph should be additive for each node
  412. """
  413. n, r = 100, 10
  414. p = 1.0 / r
  415. for i in range(1, r + 1):
  416. G = nx.erdos_renyi_graph(n, p * i, seed=600 + i)
  417. depth3 = nx.weisfeiler_lehman_subgraph_hashes(G, iterations=3)
  418. depth4 = nx.weisfeiler_lehman_subgraph_hashes(G, iterations=4)
  419. depth5 = nx.weisfeiler_lehman_subgraph_hashes(G, iterations=5)
  420. assert all(len(hashes) == 3 for hashes in depth3.values())
  421. assert all(len(hashes) == 4 for hashes in depth4.values())
  422. assert all(len(hashes) == 5 for hashes in depth5.values())
  423. assert is_subiteration(depth3, depth4)
  424. assert is_subiteration(depth4, depth5)
  425. assert is_subiteration(depth3, depth5)
  426. def test_iteration_depth_edge_attr():
  427. """
  428. All nodes should have the correct number of subgraph hashes in the output when
  429. setting initial node labels empty and using an edge attribute when aggregating
  430. neighborhoods.
  431. Subsequent iteration depths for the same graph should be additive for each node
  432. """
  433. n, r = 100, 10
  434. p = 1.0 / r
  435. for i in range(1, r + 1):
  436. G = nx.erdos_renyi_graph(n, p * i, seed=700 + i)
  437. for a, b in G.edges:
  438. G[a][b]["edge_attr1"] = f"{a}-{b}-1"
  439. depth3 = nx.weisfeiler_lehman_subgraph_hashes(
  440. G, edge_attr="edge_attr1", iterations=3
  441. )
  442. depth4 = nx.weisfeiler_lehman_subgraph_hashes(
  443. G, edge_attr="edge_attr1", iterations=4
  444. )
  445. depth5 = nx.weisfeiler_lehman_subgraph_hashes(
  446. G, edge_attr="edge_attr1", iterations=5
  447. )
  448. assert all(len(hashes) == 3 for hashes in depth3.values())
  449. assert all(len(hashes) == 4 for hashes in depth4.values())
  450. assert all(len(hashes) == 5 for hashes in depth5.values())
  451. assert is_subiteration(depth3, depth4)
  452. assert is_subiteration(depth4, depth5)
  453. assert is_subiteration(depth3, depth5)
  454. def test_iteration_depth_node_attr():
  455. """
  456. All nodes should have the correct number of subgraph hashes in the output when
  457. setting initial node labels to an attribute.
  458. Subsequent iteration depths for the same graph should be additive for each node
  459. """
  460. n, r = 100, 10
  461. p = 1.0 / r
  462. for i in range(1, r + 1):
  463. G = nx.erdos_renyi_graph(n, p * i, seed=800 + i)
  464. for u in G.nodes():
  465. G.nodes[u]["node_attr1"] = f"{u}-1"
  466. depth3 = nx.weisfeiler_lehman_subgraph_hashes(
  467. G, node_attr="node_attr1", iterations=3
  468. )
  469. depth4 = nx.weisfeiler_lehman_subgraph_hashes(
  470. G, node_attr="node_attr1", iterations=4
  471. )
  472. depth5 = nx.weisfeiler_lehman_subgraph_hashes(
  473. G, node_attr="node_attr1", iterations=5
  474. )
  475. assert all(len(hashes) == 3 for hashes in depth3.values())
  476. assert all(len(hashes) == 4 for hashes in depth4.values())
  477. assert all(len(hashes) == 5 for hashes in depth5.values())
  478. assert is_subiteration(depth3, depth4)
  479. assert is_subiteration(depth4, depth5)
  480. assert is_subiteration(depth3, depth5)
  481. def test_iteration_depth_node_edge_attr():
  482. """
  483. All nodes should have the correct number of subgraph hashes in the output when
  484. setting initial node labels to an attribute and also using an edge attribute when
  485. aggregating neighborhoods.
  486. Subsequent iteration depths for the same graph should be additive for each node
  487. """
  488. n, r = 100, 10
  489. p = 1.0 / r
  490. for i in range(1, r + 1):
  491. G = nx.erdos_renyi_graph(n, p * i, seed=900 + i)
  492. for u in G.nodes():
  493. G.nodes[u]["node_attr1"] = f"{u}-1"
  494. for a, b in G.edges:
  495. G[a][b]["edge_attr1"] = f"{a}-{b}-1"
  496. depth3 = nx.weisfeiler_lehman_subgraph_hashes(
  497. G, edge_attr="edge_attr1", node_attr="node_attr1", iterations=3
  498. )
  499. depth4 = nx.weisfeiler_lehman_subgraph_hashes(
  500. G, edge_attr="edge_attr1", node_attr="node_attr1", iterations=4
  501. )
  502. depth5 = nx.weisfeiler_lehman_subgraph_hashes(
  503. G, edge_attr="edge_attr1", node_attr="node_attr1", iterations=5
  504. )
  505. assert all(len(hashes) == 3 for hashes in depth3.values())
  506. assert all(len(hashes) == 4 for hashes in depth4.values())
  507. assert all(len(hashes) == 5 for hashes in depth5.values())
  508. assert is_subiteration(depth3, depth4)
  509. assert is_subiteration(depth4, depth5)
  510. assert is_subiteration(depth3, depth5)
  511. def test_digest_size_subgraph_hash():
  512. """
  513. The hash string lengths should be as expected for a variety of graphs and
  514. digest sizes
  515. """
  516. n, r = 100, 10
  517. p = 1.0 / r
  518. for i in range(1, r + 1):
  519. G = nx.erdos_renyi_graph(n, p * i, seed=1000 + i)
  520. digest_size16_hashes = nx.weisfeiler_lehman_subgraph_hashes(G)
  521. digest_size32_hashes = nx.weisfeiler_lehman_subgraph_hashes(G, digest_size=32)
  522. assert digest_size16_hashes != digest_size32_hashes
  523. assert hexdigest_sizes_correct(digest_size16_hashes, 16)
  524. assert hexdigest_sizes_correct(digest_size32_hashes, 32)