test_triads.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. """Tests for the :mod:`networkx.algorithms.triads` module."""
  2. import itertools
  3. from collections import defaultdict
  4. from random import sample
  5. import pytest
  6. import networkx as nx
  7. def test_triadic_census():
  8. """Tests the triadic_census function."""
  9. G = nx.DiGraph()
  10. G.add_edges_from(["01", "02", "03", "04", "05", "12", "16", "51", "56", "65"])
  11. expected = {
  12. "030T": 2,
  13. "120C": 1,
  14. "210": 0,
  15. "120U": 0,
  16. "012": 9,
  17. "102": 3,
  18. "021U": 0,
  19. "111U": 0,
  20. "003": 8,
  21. "030C": 0,
  22. "021D": 9,
  23. "201": 0,
  24. "111D": 1,
  25. "300": 0,
  26. "120D": 0,
  27. "021C": 2,
  28. }
  29. actual = nx.triadic_census(G)
  30. assert expected == actual
  31. def test_is_triad():
  32. """Tests the is_triad function"""
  33. G = nx.karate_club_graph()
  34. G = G.to_directed()
  35. for i in range(100):
  36. nodes = sample(sorted(G.nodes()), 3)
  37. G2 = G.subgraph(nodes)
  38. assert nx.is_triad(G2)
  39. def test_all_triplets():
  40. """Tests the all_triplets function."""
  41. G = nx.DiGraph()
  42. G.add_edges_from(["01", "02", "03", "04", "05", "12", "16", "51", "56", "65"])
  43. expected = [
  44. f"{i},{j},{k}"
  45. for i in range(7)
  46. for j in range(i + 1, 7)
  47. for k in range(j + 1, 7)
  48. ]
  49. expected = [set(x.split(",")) for x in expected]
  50. actual = [set(x) for x in nx.all_triplets(G)]
  51. assert all(any(s1 == s2 for s1 in expected) for s2 in actual)
  52. def test_all_triads():
  53. """Tests the all_triplets function."""
  54. G = nx.DiGraph()
  55. G.add_edges_from(["01", "02", "03", "04", "05", "12", "16", "51", "56", "65"])
  56. expected = [
  57. f"{i},{j},{k}"
  58. for i in range(7)
  59. for j in range(i + 1, 7)
  60. for k in range(j + 1, 7)
  61. ]
  62. expected = [G.subgraph(x.split(",")) for x in expected]
  63. actual = list(nx.all_triads(G))
  64. assert all(any(nx.is_isomorphic(G1, G2) for G1 in expected) for G2 in actual)
  65. def test_triad_type():
  66. """Tests the triad_type function."""
  67. # 0 edges (1 type)
  68. G = nx.DiGraph({0: [], 1: [], 2: []})
  69. assert nx.triad_type(G) == "003"
  70. # 1 edge (1 type)
  71. G = nx.DiGraph({0: [1], 1: [], 2: []})
  72. assert nx.triad_type(G) == "012"
  73. # 2 edges (4 types)
  74. G = nx.DiGraph([(0, 1), (0, 2)])
  75. assert nx.triad_type(G) == "021D"
  76. G = nx.DiGraph({0: [1], 1: [0], 2: []})
  77. assert nx.triad_type(G) == "102"
  78. G = nx.DiGraph([(0, 1), (2, 1)])
  79. assert nx.triad_type(G) == "021U"
  80. G = nx.DiGraph([(0, 1), (1, 2)])
  81. assert nx.triad_type(G) == "021C"
  82. # 3 edges (4 types)
  83. G = nx.DiGraph([(0, 1), (1, 0), (2, 1)])
  84. assert nx.triad_type(G) == "111D"
  85. G = nx.DiGraph([(0, 1), (1, 0), (1, 2)])
  86. assert nx.triad_type(G) == "111U"
  87. G = nx.DiGraph([(0, 1), (1, 2), (0, 2)])
  88. assert nx.triad_type(G) == "030T"
  89. G = nx.DiGraph([(0, 1), (1, 2), (2, 0)])
  90. assert nx.triad_type(G) == "030C"
  91. # 4 edges (4 types)
  92. G = nx.DiGraph([(0, 1), (1, 0), (2, 0), (0, 2)])
  93. assert nx.triad_type(G) == "201"
  94. G = nx.DiGraph([(0, 1), (1, 0), (2, 0), (2, 1)])
  95. assert nx.triad_type(G) == "120D"
  96. G = nx.DiGraph([(0, 1), (1, 0), (0, 2), (1, 2)])
  97. assert nx.triad_type(G) == "120U"
  98. G = nx.DiGraph([(0, 1), (1, 0), (0, 2), (2, 1)])
  99. assert nx.triad_type(G) == "120C"
  100. # 5 edges (1 type)
  101. G = nx.DiGraph([(0, 1), (1, 0), (2, 1), (1, 2), (0, 2)])
  102. assert nx.triad_type(G) == "210"
  103. # 6 edges (1 type)
  104. G = nx.DiGraph([(0, 1), (1, 0), (1, 2), (2, 1), (0, 2), (2, 0)])
  105. assert nx.triad_type(G) == "300"
  106. def test_triads_by_type():
  107. """Tests the all_triplets function."""
  108. G = nx.DiGraph()
  109. G.add_edges_from(["01", "02", "03", "04", "05", "12", "16", "51", "56", "65"])
  110. all_triads = nx.all_triads(G)
  111. expected = defaultdict(list)
  112. for triad in all_triads:
  113. name = nx.triad_type(triad)
  114. expected[name].append(triad)
  115. actual = nx.triads_by_type(G)
  116. assert set(actual.keys()) == set(expected.keys())
  117. for tri_type, actual_Gs in actual.items():
  118. expected_Gs = expected[tri_type]
  119. for a in actual_Gs:
  120. assert any(nx.is_isomorphic(a, e) for e in expected_Gs)
  121. def test_random_triad():
  122. """Tests the random_triad function"""
  123. G = nx.karate_club_graph()
  124. G = G.to_directed()
  125. for i in range(100):
  126. assert nx.is_triad(nx.random_triad(G))
  127. def test_triadic_census_short_path_nodelist():
  128. G = nx.path_graph("abc", create_using=nx.DiGraph)
  129. expected = {"021C": 1}
  130. for nl in ["a", "b", "c", "ab", "ac", "bc", "abc"]:
  131. triad_census = nx.triadic_census(G, nodelist=nl)
  132. assert expected == {typ: cnt for typ, cnt in triad_census.items() if cnt > 0}
  133. def test_triadic_census_correct_nodelist_values():
  134. G = nx.path_graph(5, create_using=nx.DiGraph)
  135. msg = r"nodelist includes duplicate nodes or nodes not in G"
  136. with pytest.raises(ValueError, match=msg):
  137. nx.triadic_census(G, [1, 2, 2, 3])
  138. with pytest.raises(ValueError, match=msg):
  139. nx.triadic_census(G, [1, 2, "a", 3])
  140. def test_triadic_census_tiny_graphs():
  141. tc = nx.triadic_census(nx.empty_graph(0, create_using=nx.DiGraph))
  142. assert {} == {typ: cnt for typ, cnt in tc.items() if cnt > 0}
  143. tc = nx.triadic_census(nx.empty_graph(1, create_using=nx.DiGraph))
  144. assert {} == {typ: cnt for typ, cnt in tc.items() if cnt > 0}
  145. tc = nx.triadic_census(nx.empty_graph(2, create_using=nx.DiGraph))
  146. assert {} == {typ: cnt for typ, cnt in tc.items() if cnt > 0}
  147. tc = nx.triadic_census(nx.DiGraph([(1, 2)]))
  148. assert {} == {typ: cnt for typ, cnt in tc.items() if cnt > 0}
  149. def test_triadic_census_selfloops():
  150. GG = nx.path_graph("abc", create_using=nx.DiGraph)
  151. expected = {"021C": 1}
  152. for n in GG:
  153. G = GG.copy()
  154. G.add_edge(n, n)
  155. tc = nx.triadic_census(G)
  156. assert expected == {typ: cnt for typ, cnt in tc.items() if cnt > 0}
  157. GG = nx.path_graph("abcde", create_using=nx.DiGraph)
  158. tbt = nx.triads_by_type(GG)
  159. for n in GG:
  160. GG.add_edge(n, n)
  161. tc = nx.triadic_census(GG)
  162. assert tc == {tt: len(tbt[tt]) for tt in tc}
  163. def test_triadic_census_four_path():
  164. G = nx.path_graph("abcd", create_using=nx.DiGraph)
  165. expected = {"012": 2, "021C": 2}
  166. triad_census = nx.triadic_census(G)
  167. assert expected == {typ: cnt for typ, cnt in triad_census.items() if cnt > 0}
  168. def test_triadic_census_four_path_nodelist():
  169. G = nx.path_graph("abcd", create_using=nx.DiGraph)
  170. expected_end = {"012": 2, "021C": 1}
  171. expected_mid = {"012": 1, "021C": 2}
  172. a_triad_census = nx.triadic_census(G, nodelist=["a"])
  173. assert expected_end == {typ: cnt for typ, cnt in a_triad_census.items() if cnt > 0}
  174. b_triad_census = nx.triadic_census(G, nodelist=["b"])
  175. assert expected_mid == {typ: cnt for typ, cnt in b_triad_census.items() if cnt > 0}
  176. c_triad_census = nx.triadic_census(G, nodelist=["c"])
  177. assert expected_mid == {typ: cnt for typ, cnt in c_triad_census.items() if cnt > 0}
  178. d_triad_census = nx.triadic_census(G, nodelist=["d"])
  179. assert expected_end == {typ: cnt for typ, cnt in d_triad_census.items() if cnt > 0}
  180. def test_triadic_census_nodelist():
  181. """Tests the triadic_census function."""
  182. G = nx.DiGraph()
  183. G.add_edges_from(["01", "02", "03", "04", "05", "12", "16", "51", "56", "65"])
  184. expected = {
  185. "030T": 2,
  186. "120C": 1,
  187. "210": 0,
  188. "120U": 0,
  189. "012": 9,
  190. "102": 3,
  191. "021U": 0,
  192. "111U": 0,
  193. "003": 8,
  194. "030C": 0,
  195. "021D": 9,
  196. "201": 0,
  197. "111D": 1,
  198. "300": 0,
  199. "120D": 0,
  200. "021C": 2,
  201. }
  202. actual = {k: 0 for k in expected}
  203. for node in G.nodes():
  204. node_triad_census = nx.triadic_census(G, nodelist=[node])
  205. for triad_key in expected:
  206. actual[triad_key] += node_triad_census[triad_key]
  207. # Divide all counts by 3
  208. for k, v in actual.items():
  209. actual[k] //= 3
  210. assert expected == actual
  211. @pytest.mark.parametrize("N", [5, 10])
  212. def test_triandic_census_on_random_graph(N):
  213. G = nx.binomial_graph(N, 0.3, directed=True, seed=42)
  214. tc1 = nx.triadic_census(G)
  215. tbt = nx.triads_by_type(G)
  216. tc2 = {tt: len(tbt[tt]) for tt in tc1}
  217. assert tc1 == tc2
  218. for n in G:
  219. tc1 = nx.triadic_census(G, nodelist={n})
  220. tc2 = {tt: sum(1 for t in tbt.get(tt, []) if n in t) for tt in tc1}
  221. assert tc1 == tc2
  222. for ns in itertools.combinations(G, 2):
  223. ns = set(ns)
  224. tc1 = nx.triadic_census(G, nodelist=ns)
  225. tc2 = {
  226. tt: sum(1 for t in tbt.get(tt, []) if any(n in ns for n in t)) for tt in tc1
  227. }
  228. assert tc1 == tc2
  229. for ns in itertools.combinations(G, 3):
  230. ns = set(ns)
  231. tc1 = nx.triadic_census(G, nodelist=ns)
  232. tc2 = {
  233. tt: sum(1 for t in tbt.get(tt, []) if any(n in ns for n in t)) for tt in tc1
  234. }
  235. assert tc1 == tc2