test_laplacian.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import pytest
  2. np = pytest.importorskip("numpy")
  3. pytest.importorskip("scipy")
  4. import networkx as nx
  5. from networkx.generators.degree_seq import havel_hakimi_graph
  6. from networkx.generators.expanders import margulis_gabber_galil_graph
  7. class TestLaplacian:
  8. @classmethod
  9. def setup_class(cls):
  10. deg = [3, 2, 2, 1, 0]
  11. cls.G = havel_hakimi_graph(deg)
  12. cls.WG = nx.Graph(
  13. (u, v, {"weight": 0.5, "other": 0.3}) for (u, v) in cls.G.edges()
  14. )
  15. cls.WG.add_node(4)
  16. cls.MG = nx.MultiGraph(cls.G)
  17. # Graph with clsloops
  18. cls.Gsl = cls.G.copy()
  19. for node in cls.Gsl.nodes():
  20. cls.Gsl.add_edge(node, node)
  21. def test_laplacian(self):
  22. "Graph Laplacian"
  23. # fmt: off
  24. NL = np.array([[ 3, -1, -1, -1, 0],
  25. [-1, 2, -1, 0, 0],
  26. [-1, -1, 2, 0, 0],
  27. [-1, 0, 0, 1, 0],
  28. [ 0, 0, 0, 0, 0]])
  29. # fmt: on
  30. WL = 0.5 * NL
  31. OL = 0.3 * NL
  32. np.testing.assert_equal(nx.laplacian_matrix(self.G).todense(), NL)
  33. np.testing.assert_equal(nx.laplacian_matrix(self.MG).todense(), NL)
  34. np.testing.assert_equal(
  35. nx.laplacian_matrix(self.G, nodelist=[0, 1]).todense(),
  36. np.array([[1, -1], [-1, 1]]),
  37. )
  38. np.testing.assert_equal(nx.laplacian_matrix(self.WG).todense(), WL)
  39. np.testing.assert_equal(nx.laplacian_matrix(self.WG, weight=None).todense(), NL)
  40. np.testing.assert_equal(
  41. nx.laplacian_matrix(self.WG, weight="other").todense(), OL
  42. )
  43. def test_normalized_laplacian(self):
  44. "Generalized Graph Laplacian"
  45. # fmt: off
  46. G = np.array([[ 1. , -0.408, -0.408, -0.577, 0.],
  47. [-0.408, 1. , -0.5 , 0. , 0.],
  48. [-0.408, -0.5 , 1. , 0. , 0.],
  49. [-0.577, 0. , 0. , 1. , 0.],
  50. [ 0. , 0. , 0. , 0. , 0.]])
  51. GL = np.array([[ 1. , -0.408, -0.408, -0.577, 0. ],
  52. [-0.408, 1. , -0.5 , 0. , 0. ],
  53. [-0.408, -0.5 , 1. , 0. , 0. ],
  54. [-0.577, 0. , 0. , 1. , 0. ],
  55. [ 0. , 0. , 0. , 0. , 0. ]])
  56. Lsl = np.array([[ 0.75 , -0.2887, -0.2887, -0.3536, 0. ],
  57. [-0.2887, 0.6667, -0.3333, 0. , 0. ],
  58. [-0.2887, -0.3333, 0.6667, 0. , 0. ],
  59. [-0.3536, 0. , 0. , 0.5 , 0. ],
  60. [ 0. , 0. , 0. , 0. , 0. ]])
  61. # fmt: on
  62. np.testing.assert_almost_equal(
  63. nx.normalized_laplacian_matrix(self.G, nodelist=range(5)).todense(),
  64. G,
  65. decimal=3,
  66. )
  67. np.testing.assert_almost_equal(
  68. nx.normalized_laplacian_matrix(self.G).todense(), GL, decimal=3
  69. )
  70. np.testing.assert_almost_equal(
  71. nx.normalized_laplacian_matrix(self.MG).todense(), GL, decimal=3
  72. )
  73. np.testing.assert_almost_equal(
  74. nx.normalized_laplacian_matrix(self.WG).todense(), GL, decimal=3
  75. )
  76. np.testing.assert_almost_equal(
  77. nx.normalized_laplacian_matrix(self.WG, weight="other").todense(),
  78. GL,
  79. decimal=3,
  80. )
  81. np.testing.assert_almost_equal(
  82. nx.normalized_laplacian_matrix(self.Gsl).todense(), Lsl, decimal=3
  83. )
  84. def test_directed_laplacian():
  85. "Directed Laplacian"
  86. # Graph used as an example in Sec. 4.1 of Langville and Meyer,
  87. # "Google's PageRank and Beyond". The graph contains dangling nodes, so
  88. # the pagerank random walk is selected by directed_laplacian
  89. G = nx.DiGraph()
  90. G.add_edges_from(
  91. (
  92. (1, 2),
  93. (1, 3),
  94. (3, 1),
  95. (3, 2),
  96. (3, 5),
  97. (4, 5),
  98. (4, 6),
  99. (5, 4),
  100. (5, 6),
  101. (6, 4),
  102. )
  103. )
  104. # fmt: off
  105. GL = np.array([[ 0.9833, -0.2941, -0.3882, -0.0291, -0.0231, -0.0261],
  106. [-0.2941, 0.8333, -0.2339, -0.0536, -0.0589, -0.0554],
  107. [-0.3882, -0.2339, 0.9833, -0.0278, -0.0896, -0.0251],
  108. [-0.0291, -0.0536, -0.0278, 0.9833, -0.4878, -0.6675],
  109. [-0.0231, -0.0589, -0.0896, -0.4878, 0.9833, -0.2078],
  110. [-0.0261, -0.0554, -0.0251, -0.6675, -0.2078, 0.9833]])
  111. # fmt: on
  112. L = nx.directed_laplacian_matrix(G, alpha=0.9, nodelist=sorted(G))
  113. np.testing.assert_almost_equal(L, GL, decimal=3)
  114. # Make the graph strongly connected, so we can use a random and lazy walk
  115. G.add_edges_from(((2, 5), (6, 1)))
  116. # fmt: off
  117. GL = np.array([[ 1. , -0.3062, -0.4714, 0. , 0. , -0.3227],
  118. [-0.3062, 1. , -0.1443, 0. , -0.3162, 0. ],
  119. [-0.4714, -0.1443, 1. , 0. , -0.0913, 0. ],
  120. [ 0. , 0. , 0. , 1. , -0.5 , -0.5 ],
  121. [ 0. , -0.3162, -0.0913, -0.5 , 1. , -0.25 ],
  122. [-0.3227, 0. , 0. , -0.5 , -0.25 , 1. ]])
  123. # fmt: on
  124. L = nx.directed_laplacian_matrix(
  125. G, alpha=0.9, nodelist=sorted(G), walk_type="random"
  126. )
  127. np.testing.assert_almost_equal(L, GL, decimal=3)
  128. # fmt: off
  129. GL = np.array([[ 0.5 , -0.1531, -0.2357, 0. , 0. , -0.1614],
  130. [-0.1531, 0.5 , -0.0722, 0. , -0.1581, 0. ],
  131. [-0.2357, -0.0722, 0.5 , 0. , -0.0456, 0. ],
  132. [ 0. , 0. , 0. , 0.5 , -0.25 , -0.25 ],
  133. [ 0. , -0.1581, -0.0456, -0.25 , 0.5 , -0.125 ],
  134. [-0.1614, 0. , 0. , -0.25 , -0.125 , 0.5 ]])
  135. # fmt: on
  136. L = nx.directed_laplacian_matrix(G, alpha=0.9, nodelist=sorted(G), walk_type="lazy")
  137. np.testing.assert_almost_equal(L, GL, decimal=3)
  138. # Make a strongly connected periodic graph
  139. G = nx.DiGraph()
  140. G.add_edges_from(((1, 2), (2, 4), (4, 1), (1, 3), (3, 4)))
  141. # fmt: off
  142. GL = np.array([[ 0.5 , -0.176, -0.176, -0.25 ],
  143. [-0.176, 0.5 , 0. , -0.176],
  144. [-0.176, 0. , 0.5 , -0.176],
  145. [-0.25 , -0.176, -0.176, 0.5 ]])
  146. # fmt: on
  147. L = nx.directed_laplacian_matrix(G, alpha=0.9, nodelist=sorted(G))
  148. np.testing.assert_almost_equal(L, GL, decimal=3)
  149. def test_directed_combinatorial_laplacian():
  150. "Directed combinatorial Laplacian"
  151. # Graph used as an example in Sec. 4.1 of Langville and Meyer,
  152. # "Google's PageRank and Beyond". The graph contains dangling nodes, so
  153. # the pagerank random walk is selected by directed_laplacian
  154. G = nx.DiGraph()
  155. G.add_edges_from(
  156. (
  157. (1, 2),
  158. (1, 3),
  159. (3, 1),
  160. (3, 2),
  161. (3, 5),
  162. (4, 5),
  163. (4, 6),
  164. (5, 4),
  165. (5, 6),
  166. (6, 4),
  167. )
  168. )
  169. # fmt: off
  170. GL = np.array([[ 0.0366, -0.0132, -0.0153, -0.0034, -0.0020, -0.0027],
  171. [-0.0132, 0.0450, -0.0111, -0.0076, -0.0062, -0.0069],
  172. [-0.0153, -0.0111, 0.0408, -0.0035, -0.0083, -0.0027],
  173. [-0.0034, -0.0076, -0.0035, 0.3688, -0.1356, -0.2187],
  174. [-0.0020, -0.0062, -0.0083, -0.1356, 0.2026, -0.0505],
  175. [-0.0027, -0.0069, -0.0027, -0.2187, -0.0505, 0.2815]])
  176. # fmt: on
  177. L = nx.directed_combinatorial_laplacian_matrix(G, alpha=0.9, nodelist=sorted(G))
  178. np.testing.assert_almost_equal(L, GL, decimal=3)
  179. # Make the graph strongly connected, so we can use a random and lazy walk
  180. G.add_edges_from(((2, 5), (6, 1)))
  181. # fmt: off
  182. GL = np.array([[ 0.1395, -0.0349, -0.0465, 0. , 0. , -0.0581],
  183. [-0.0349, 0.093 , -0.0116, 0. , -0.0465, 0. ],
  184. [-0.0465, -0.0116, 0.0698, 0. , -0.0116, 0. ],
  185. [ 0. , 0. , 0. , 0.2326, -0.1163, -0.1163],
  186. [ 0. , -0.0465, -0.0116, -0.1163, 0.2326, -0.0581],
  187. [-0.0581, 0. , 0. , -0.1163, -0.0581, 0.2326]])
  188. # fmt: on
  189. L = nx.directed_combinatorial_laplacian_matrix(
  190. G, alpha=0.9, nodelist=sorted(G), walk_type="random"
  191. )
  192. np.testing.assert_almost_equal(L, GL, decimal=3)
  193. # fmt: off
  194. GL = np.array([[ 0.0698, -0.0174, -0.0233, 0. , 0. , -0.0291],
  195. [-0.0174, 0.0465, -0.0058, 0. , -0.0233, 0. ],
  196. [-0.0233, -0.0058, 0.0349, 0. , -0.0058, 0. ],
  197. [ 0. , 0. , 0. , 0.1163, -0.0581, -0.0581],
  198. [ 0. , -0.0233, -0.0058, -0.0581, 0.1163, -0.0291],
  199. [-0.0291, 0. , 0. , -0.0581, -0.0291, 0.1163]])
  200. # fmt: on
  201. L = nx.directed_combinatorial_laplacian_matrix(
  202. G, alpha=0.9, nodelist=sorted(G), walk_type="lazy"
  203. )
  204. np.testing.assert_almost_equal(L, GL, decimal=3)
  205. E = nx.DiGraph(margulis_gabber_galil_graph(2))
  206. L = nx.directed_combinatorial_laplacian_matrix(E)
  207. # fmt: off
  208. expected = np.array(
  209. [[ 0.16666667, -0.08333333, -0.08333333, 0. ],
  210. [-0.08333333, 0.16666667, 0. , -0.08333333],
  211. [-0.08333333, 0. , 0.16666667, -0.08333333],
  212. [ 0. , -0.08333333, -0.08333333, 0.16666667]]
  213. )
  214. # fmt: on
  215. np.testing.assert_almost_equal(L, expected, decimal=6)
  216. with pytest.raises(nx.NetworkXError):
  217. nx.directed_combinatorial_laplacian_matrix(G, walk_type="pagerank", alpha=100)
  218. with pytest.raises(nx.NetworkXError):
  219. nx.directed_combinatorial_laplacian_matrix(G, walk_type="silly")