test_laplacian.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. import pytest
  2. import networkx as nx
  3. np = pytest.importorskip("numpy")
  4. pytest.importorskip("scipy")
  5. class TestLaplacian:
  6. @classmethod
  7. def setup_class(cls):
  8. deg = [3, 2, 2, 1, 0]
  9. cls.G = nx.havel_hakimi_graph(deg)
  10. cls.WG = nx.Graph(
  11. (u, v, {"weight": 0.5, "other": 0.3}) for (u, v) in cls.G.edges()
  12. )
  13. cls.WG.add_node(4)
  14. cls.MG = nx.MultiGraph(cls.G)
  15. # Graph with clsloops
  16. cls.Gsl = cls.G.copy()
  17. for node in cls.Gsl.nodes():
  18. cls.Gsl.add_edge(node, node)
  19. # Graph used as an example in Sec. 4.1 of Langville and Meyer,
  20. # "Google's PageRank and Beyond".
  21. cls.DiG = nx.DiGraph()
  22. cls.DiG.add_edges_from(
  23. (
  24. (1, 2),
  25. (1, 3),
  26. (3, 1),
  27. (3, 2),
  28. (3, 5),
  29. (4, 5),
  30. (4, 6),
  31. (5, 4),
  32. (5, 6),
  33. (6, 4),
  34. )
  35. )
  36. cls.DiMG = nx.MultiDiGraph(cls.DiG)
  37. cls.DiWG = nx.DiGraph(
  38. (u, v, {"weight": 0.5, "other": 0.3}) for (u, v) in cls.DiG.edges()
  39. )
  40. cls.DiGsl = cls.DiG.copy()
  41. for node in cls.DiGsl.nodes():
  42. cls.DiGsl.add_edge(node, node)
  43. def test_laplacian(self):
  44. "Graph Laplacian"
  45. # fmt: off
  46. NL = np.array([[ 3, -1, -1, -1, 0],
  47. [-1, 2, -1, 0, 0],
  48. [-1, -1, 2, 0, 0],
  49. [-1, 0, 0, 1, 0],
  50. [ 0, 0, 0, 0, 0]])
  51. # fmt: on
  52. WL = 0.5 * NL
  53. OL = 0.3 * NL
  54. # fmt: off
  55. DiNL = np.array([[ 2, -1, -1, 0, 0, 0],
  56. [ 0, 0, 0, 0, 0, 0],
  57. [-1, -1, 3, -1, 0, 0],
  58. [ 0, 0, 0, 2, -1, -1],
  59. [ 0, 0, 0, -1, 2, -1],
  60. [ 0, 0, 0, 0, -1, 1]])
  61. # fmt: on
  62. DiWL = 0.5 * DiNL
  63. DiOL = 0.3 * DiNL
  64. np.testing.assert_equal(nx.laplacian_matrix(self.G).todense(), NL)
  65. np.testing.assert_equal(nx.laplacian_matrix(self.MG).todense(), NL)
  66. np.testing.assert_equal(
  67. nx.laplacian_matrix(self.G, nodelist=[0, 1]).todense(),
  68. np.array([[1, -1], [-1, 1]]),
  69. )
  70. np.testing.assert_equal(nx.laplacian_matrix(self.WG).todense(), WL)
  71. np.testing.assert_equal(nx.laplacian_matrix(self.WG, weight=None).todense(), NL)
  72. np.testing.assert_equal(
  73. nx.laplacian_matrix(self.WG, weight="other").todense(), OL
  74. )
  75. np.testing.assert_equal(nx.laplacian_matrix(self.DiG).todense(), DiNL)
  76. np.testing.assert_equal(nx.laplacian_matrix(self.DiMG).todense(), DiNL)
  77. np.testing.assert_equal(
  78. nx.laplacian_matrix(self.DiG, nodelist=[1, 2]).todense(),
  79. np.array([[1, -1], [0, 0]]),
  80. )
  81. np.testing.assert_equal(nx.laplacian_matrix(self.DiWG).todense(), DiWL)
  82. np.testing.assert_equal(
  83. nx.laplacian_matrix(self.DiWG, weight=None).todense(), DiNL
  84. )
  85. np.testing.assert_equal(
  86. nx.laplacian_matrix(self.DiWG, weight="other").todense(), DiOL
  87. )
  88. def test_normalized_laplacian(self):
  89. "Generalized Graph Laplacian"
  90. # fmt: off
  91. G = np.array([[ 1. , -0.408, -0.408, -0.577, 0.],
  92. [-0.408, 1. , -0.5 , 0. , 0.],
  93. [-0.408, -0.5 , 1. , 0. , 0.],
  94. [-0.577, 0. , 0. , 1. , 0.],
  95. [ 0. , 0. , 0. , 0. , 0.]])
  96. GL = np.array([[ 1. , -0.408, -0.408, -0.577, 0. ],
  97. [-0.408, 1. , -0.5 , 0. , 0. ],
  98. [-0.408, -0.5 , 1. , 0. , 0. ],
  99. [-0.577, 0. , 0. , 1. , 0. ],
  100. [ 0. , 0. , 0. , 0. , 0. ]])
  101. Lsl = np.array([[ 0.75 , -0.2887, -0.2887, -0.3536, 0. ],
  102. [-0.2887, 0.6667, -0.3333, 0. , 0. ],
  103. [-0.2887, -0.3333, 0.6667, 0. , 0. ],
  104. [-0.3536, 0. , 0. , 0.5 , 0. ],
  105. [ 0. , 0. , 0. , 0. , 0. ]])
  106. DiG = np.array([[ 1. , 0. , -0.4082, 0. , 0. , 0. ],
  107. [ 0. , 0. , 0. , 0. , 0. , 0. ],
  108. [-0.4082, 0. , 1. , 0. , -0.4082, 0. ],
  109. [ 0. , 0. , 0. , 1. , -0.5 , -0.7071],
  110. [ 0. , 0. , 0. , -0.5 , 1. , -0.7071],
  111. [ 0. , 0. , 0. , -0.7071, 0. , 1. ]])
  112. DiGL = np.array([[ 1. , 0. , -0.4082, 0. , 0. , 0. ],
  113. [ 0. , 0. , 0. , 0. , 0. , 0. ],
  114. [-0.4082, 0. , 1. , -0.4082, 0. , 0. ],
  115. [ 0. , 0. , 0. , 1. , -0.5 , -0.7071],
  116. [ 0. , 0. , 0. , -0.5 , 1. , -0.7071],
  117. [ 0. , 0. , 0. , 0. , -0.7071, 1. ]])
  118. DiLsl = np.array([[ 0.6667, -0.5774, -0.2887, 0. , 0. , 0. ],
  119. [ 0. , 0. , 0. , 0. , 0. , 0. ],
  120. [-0.2887, -0.5 , 0.75 , -0.2887, 0. , 0. ],
  121. [ 0. , 0. , 0. , 0.6667, -0.3333, -0.4082],
  122. [ 0. , 0. , 0. , -0.3333, 0.6667, -0.4082],
  123. [ 0. , 0. , 0. , 0. , -0.4082, 0.5 ]])
  124. # fmt: on
  125. np.testing.assert_almost_equal(
  126. nx.normalized_laplacian_matrix(self.G, nodelist=range(5)).todense(),
  127. G,
  128. decimal=3,
  129. )
  130. np.testing.assert_almost_equal(
  131. nx.normalized_laplacian_matrix(self.G).todense(), GL, decimal=3
  132. )
  133. np.testing.assert_almost_equal(
  134. nx.normalized_laplacian_matrix(self.MG).todense(), GL, decimal=3
  135. )
  136. np.testing.assert_almost_equal(
  137. nx.normalized_laplacian_matrix(self.WG).todense(), GL, decimal=3
  138. )
  139. np.testing.assert_almost_equal(
  140. nx.normalized_laplacian_matrix(self.WG, weight="other").todense(),
  141. GL,
  142. decimal=3,
  143. )
  144. np.testing.assert_almost_equal(
  145. nx.normalized_laplacian_matrix(self.Gsl).todense(), Lsl, decimal=3
  146. )
  147. np.testing.assert_almost_equal(
  148. nx.normalized_laplacian_matrix(
  149. self.DiG,
  150. nodelist=range(1, 1 + 6),
  151. ).todense(),
  152. DiG,
  153. decimal=3,
  154. )
  155. np.testing.assert_almost_equal(
  156. nx.normalized_laplacian_matrix(self.DiG).todense(), DiGL, decimal=3
  157. )
  158. np.testing.assert_almost_equal(
  159. nx.normalized_laplacian_matrix(self.DiMG).todense(), DiGL, decimal=3
  160. )
  161. np.testing.assert_almost_equal(
  162. nx.normalized_laplacian_matrix(self.DiWG).todense(), DiGL, decimal=3
  163. )
  164. np.testing.assert_almost_equal(
  165. nx.normalized_laplacian_matrix(self.DiWG, weight="other").todense(),
  166. DiGL,
  167. decimal=3,
  168. )
  169. np.testing.assert_almost_equal(
  170. nx.normalized_laplacian_matrix(self.DiGsl).todense(), DiLsl, decimal=3
  171. )
  172. def test_directed_laplacian():
  173. "Directed Laplacian"
  174. # Graph used as an example in Sec. 4.1 of Langville and Meyer,
  175. # "Google's PageRank and Beyond". The graph contains dangling nodes, so
  176. # the pagerank random walk is selected by directed_laplacian
  177. G = nx.DiGraph()
  178. G.add_edges_from(
  179. (
  180. (1, 2),
  181. (1, 3),
  182. (3, 1),
  183. (3, 2),
  184. (3, 5),
  185. (4, 5),
  186. (4, 6),
  187. (5, 4),
  188. (5, 6),
  189. (6, 4),
  190. )
  191. )
  192. # fmt: off
  193. GL = np.array([[ 0.9833, -0.2941, -0.3882, -0.0291, -0.0231, -0.0261],
  194. [-0.2941, 0.8333, -0.2339, -0.0536, -0.0589, -0.0554],
  195. [-0.3882, -0.2339, 0.9833, -0.0278, -0.0896, -0.0251],
  196. [-0.0291, -0.0536, -0.0278, 0.9833, -0.4878, -0.6675],
  197. [-0.0231, -0.0589, -0.0896, -0.4878, 0.9833, -0.2078],
  198. [-0.0261, -0.0554, -0.0251, -0.6675, -0.2078, 0.9833]])
  199. # fmt: on
  200. L = nx.directed_laplacian_matrix(G, alpha=0.9, nodelist=sorted(G))
  201. np.testing.assert_almost_equal(L, GL, decimal=3)
  202. # Make the graph strongly connected, so we can use a random and lazy walk
  203. G.add_edges_from(((2, 5), (6, 1)))
  204. # fmt: off
  205. GL = np.array([[ 1. , -0.3062, -0.4714, 0. , 0. , -0.3227],
  206. [-0.3062, 1. , -0.1443, 0. , -0.3162, 0. ],
  207. [-0.4714, -0.1443, 1. , 0. , -0.0913, 0. ],
  208. [ 0. , 0. , 0. , 1. , -0.5 , -0.5 ],
  209. [ 0. , -0.3162, -0.0913, -0.5 , 1. , -0.25 ],
  210. [-0.3227, 0. , 0. , -0.5 , -0.25 , 1. ]])
  211. # fmt: on
  212. L = nx.directed_laplacian_matrix(
  213. G, alpha=0.9, nodelist=sorted(G), walk_type="random"
  214. )
  215. np.testing.assert_almost_equal(L, GL, decimal=3)
  216. # fmt: off
  217. GL = np.array([[ 0.5 , -0.1531, -0.2357, 0. , 0. , -0.1614],
  218. [-0.1531, 0.5 , -0.0722, 0. , -0.1581, 0. ],
  219. [-0.2357, -0.0722, 0.5 , 0. , -0.0456, 0. ],
  220. [ 0. , 0. , 0. , 0.5 , -0.25 , -0.25 ],
  221. [ 0. , -0.1581, -0.0456, -0.25 , 0.5 , -0.125 ],
  222. [-0.1614, 0. , 0. , -0.25 , -0.125 , 0.5 ]])
  223. # fmt: on
  224. L = nx.directed_laplacian_matrix(G, alpha=0.9, nodelist=sorted(G), walk_type="lazy")
  225. np.testing.assert_almost_equal(L, GL, decimal=3)
  226. # Make a strongly connected periodic graph
  227. G = nx.DiGraph()
  228. G.add_edges_from(((1, 2), (2, 4), (4, 1), (1, 3), (3, 4)))
  229. # fmt: off
  230. GL = np.array([[ 0.5 , -0.176, -0.176, -0.25 ],
  231. [-0.176, 0.5 , 0. , -0.176],
  232. [-0.176, 0. , 0.5 , -0.176],
  233. [-0.25 , -0.176, -0.176, 0.5 ]])
  234. # fmt: on
  235. L = nx.directed_laplacian_matrix(G, alpha=0.9, nodelist=sorted(G))
  236. np.testing.assert_almost_equal(L, GL, decimal=3)
  237. def test_directed_combinatorial_laplacian():
  238. "Directed combinatorial Laplacian"
  239. # Graph used as an example in Sec. 4.1 of Langville and Meyer,
  240. # "Google's PageRank and Beyond". The graph contains dangling nodes, so
  241. # the pagerank random walk is selected by directed_laplacian
  242. G = nx.DiGraph()
  243. G.add_edges_from(
  244. (
  245. (1, 2),
  246. (1, 3),
  247. (3, 1),
  248. (3, 2),
  249. (3, 5),
  250. (4, 5),
  251. (4, 6),
  252. (5, 4),
  253. (5, 6),
  254. (6, 4),
  255. )
  256. )
  257. # fmt: off
  258. GL = np.array([[ 0.0366, -0.0132, -0.0153, -0.0034, -0.0020, -0.0027],
  259. [-0.0132, 0.0450, -0.0111, -0.0076, -0.0062, -0.0069],
  260. [-0.0153, -0.0111, 0.0408, -0.0035, -0.0083, -0.0027],
  261. [-0.0034, -0.0076, -0.0035, 0.3688, -0.1356, -0.2187],
  262. [-0.0020, -0.0062, -0.0083, -0.1356, 0.2026, -0.0505],
  263. [-0.0027, -0.0069, -0.0027, -0.2187, -0.0505, 0.2815]])
  264. # fmt: on
  265. L = nx.directed_combinatorial_laplacian_matrix(G, alpha=0.9, nodelist=sorted(G))
  266. np.testing.assert_almost_equal(L, GL, decimal=3)
  267. # Make the graph strongly connected, so we can use a random and lazy walk
  268. G.add_edges_from(((2, 5), (6, 1)))
  269. # fmt: off
  270. GL = np.array([[ 0.1395, -0.0349, -0.0465, 0. , 0. , -0.0581],
  271. [-0.0349, 0.093 , -0.0116, 0. , -0.0465, 0. ],
  272. [-0.0465, -0.0116, 0.0698, 0. , -0.0116, 0. ],
  273. [ 0. , 0. , 0. , 0.2326, -0.1163, -0.1163],
  274. [ 0. , -0.0465, -0.0116, -0.1163, 0.2326, -0.0581],
  275. [-0.0581, 0. , 0. , -0.1163, -0.0581, 0.2326]])
  276. # fmt: on
  277. L = nx.directed_combinatorial_laplacian_matrix(
  278. G, alpha=0.9, nodelist=sorted(G), walk_type="random"
  279. )
  280. np.testing.assert_almost_equal(L, GL, decimal=3)
  281. # fmt: off
  282. GL = np.array([[ 0.0698, -0.0174, -0.0233, 0. , 0. , -0.0291],
  283. [-0.0174, 0.0465, -0.0058, 0. , -0.0233, 0. ],
  284. [-0.0233, -0.0058, 0.0349, 0. , -0.0058, 0. ],
  285. [ 0. , 0. , 0. , 0.1163, -0.0581, -0.0581],
  286. [ 0. , -0.0233, -0.0058, -0.0581, 0.1163, -0.0291],
  287. [-0.0291, 0. , 0. , -0.0581, -0.0291, 0.1163]])
  288. # fmt: on
  289. L = nx.directed_combinatorial_laplacian_matrix(
  290. G, alpha=0.9, nodelist=sorted(G), walk_type="lazy"
  291. )
  292. np.testing.assert_almost_equal(L, GL, decimal=3)
  293. E = nx.DiGraph(nx.margulis_gabber_galil_graph(2))
  294. L = nx.directed_combinatorial_laplacian_matrix(E)
  295. # fmt: off
  296. expected = np.array(
  297. [[ 0.16666667, -0.08333333, -0.08333333, 0. ],
  298. [-0.08333333, 0.16666667, 0. , -0.08333333],
  299. [-0.08333333, 0. , 0.16666667, -0.08333333],
  300. [ 0. , -0.08333333, -0.08333333, 0.16666667]]
  301. )
  302. # fmt: on
  303. np.testing.assert_almost_equal(L, expected, decimal=6)
  304. with pytest.raises(nx.NetworkXError):
  305. nx.directed_combinatorial_laplacian_matrix(G, walk_type="pagerank", alpha=100)
  306. with pytest.raises(nx.NetworkXError):
  307. nx.directed_combinatorial_laplacian_matrix(G, walk_type="silly")