test_algebraic_connectivity.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. from math import sqrt
  2. import pytest
  3. np = pytest.importorskip("numpy")
  4. import networkx as nx
  5. methods = ("tracemin_pcg", "tracemin_lu", "lanczos", "lobpcg")
  6. def test_algebraic_connectivity_tracemin_chol():
  7. """Test that "tracemin_chol" raises an exception."""
  8. pytest.importorskip("scipy")
  9. G = nx.barbell_graph(5, 4)
  10. with pytest.raises(nx.NetworkXError):
  11. nx.algebraic_connectivity(G, method="tracemin_chol")
  12. def test_fiedler_vector_tracemin_chol():
  13. """Test that "tracemin_chol" raises an exception."""
  14. pytest.importorskip("scipy")
  15. G = nx.barbell_graph(5, 4)
  16. with pytest.raises(nx.NetworkXError):
  17. nx.fiedler_vector(G, method="tracemin_chol")
  18. def test_spectral_ordering_tracemin_chol():
  19. """Test that "tracemin_chol" raises an exception."""
  20. pytest.importorskip("scipy")
  21. G = nx.barbell_graph(5, 4)
  22. with pytest.raises(nx.NetworkXError):
  23. nx.spectral_ordering(G, method="tracemin_chol")
  24. def test_fiedler_vector_tracemin_unknown():
  25. """Test that "tracemin_unknown" raises an exception."""
  26. pytest.importorskip("scipy")
  27. G = nx.barbell_graph(5, 4)
  28. L = nx.laplacian_matrix(G)
  29. X = np.asarray(np.random.normal(size=(1, L.shape[0]))).T
  30. with pytest.raises(nx.NetworkXError, match="Unknown linear system solver"):
  31. nx.linalg.algebraicconnectivity._tracemin_fiedler(
  32. L, X, normalized=False, tol=1e-8, method="tracemin_unknown"
  33. )
  34. def test_spectral_bisection():
  35. pytest.importorskip("scipy")
  36. G = nx.barbell_graph(3, 0)
  37. C = nx.spectral_bisection(G)
  38. assert C == ({0, 1, 2}, {3, 4, 5})
  39. mapping = dict(enumerate("badfec"))
  40. G = nx.relabel_nodes(G, mapping)
  41. C = nx.spectral_bisection(G)
  42. assert C == (
  43. {mapping[0], mapping[1], mapping[2]},
  44. {mapping[3], mapping[4], mapping[5]},
  45. )
  46. def check_eigenvector(A, l, x):
  47. nx = np.linalg.norm(x)
  48. # Check zeroness.
  49. assert nx != pytest.approx(0, abs=1e-07)
  50. y = A @ x
  51. ny = np.linalg.norm(y)
  52. # Check collinearity.
  53. assert x @ y == pytest.approx(nx * ny, abs=1e-7)
  54. # Check eigenvalue.
  55. assert ny == pytest.approx(l * nx, abs=1e-7)
  56. class TestAlgebraicConnectivity:
  57. @pytest.mark.parametrize("method", methods)
  58. def test_directed(self, method):
  59. G = nx.DiGraph()
  60. pytest.raises(
  61. nx.NetworkXNotImplemented, nx.algebraic_connectivity, G, method=method
  62. )
  63. pytest.raises(nx.NetworkXNotImplemented, nx.fiedler_vector, G, method=method)
  64. @pytest.mark.parametrize("method", methods)
  65. def test_null_and_singleton(self, method):
  66. G = nx.Graph()
  67. pytest.raises(nx.NetworkXError, nx.algebraic_connectivity, G, method=method)
  68. pytest.raises(nx.NetworkXError, nx.fiedler_vector, G, method=method)
  69. G.add_edge(0, 0)
  70. pytest.raises(nx.NetworkXError, nx.algebraic_connectivity, G, method=method)
  71. pytest.raises(nx.NetworkXError, nx.fiedler_vector, G, method=method)
  72. @pytest.mark.parametrize("method", methods)
  73. def test_disconnected(self, method):
  74. G = nx.Graph()
  75. G.add_nodes_from(range(2))
  76. assert nx.algebraic_connectivity(G) == 0
  77. pytest.raises(nx.NetworkXError, nx.fiedler_vector, G, method=method)
  78. G.add_edge(0, 1, weight=0)
  79. assert nx.algebraic_connectivity(G) == 0
  80. pytest.raises(nx.NetworkXError, nx.fiedler_vector, G, method=method)
  81. def test_unrecognized_method(self):
  82. pytest.importorskip("scipy")
  83. G = nx.path_graph(4)
  84. pytest.raises(nx.NetworkXError, nx.algebraic_connectivity, G, method="unknown")
  85. pytest.raises(nx.NetworkXError, nx.fiedler_vector, G, method="unknown")
  86. @pytest.mark.parametrize("method", methods)
  87. def test_two_nodes(self, method):
  88. pytest.importorskip("scipy")
  89. G = nx.Graph()
  90. G.add_edge(0, 1, weight=1)
  91. A = nx.laplacian_matrix(G)
  92. assert nx.algebraic_connectivity(G, tol=1e-12, method=method) == pytest.approx(
  93. 2, abs=1e-7
  94. )
  95. x = nx.fiedler_vector(G, tol=1e-12, method=method)
  96. check_eigenvector(A, 2, x)
  97. @pytest.mark.parametrize("method", methods)
  98. def test_two_nodes_multigraph(self, method):
  99. pytest.importorskip("scipy")
  100. G = nx.MultiGraph()
  101. G.add_edge(0, 0, spam=1e8)
  102. G.add_edge(0, 1, spam=1)
  103. G.add_edge(0, 1, spam=-2)
  104. A = -3 * nx.laplacian_matrix(G, weight="spam")
  105. assert nx.algebraic_connectivity(
  106. G, weight="spam", tol=1e-12, method=method
  107. ) == pytest.approx(6, abs=1e-7)
  108. x = nx.fiedler_vector(G, weight="spam", tol=1e-12, method=method)
  109. check_eigenvector(A, 6, x)
  110. def test_abbreviation_of_method(self):
  111. pytest.importorskip("scipy")
  112. G = nx.path_graph(8)
  113. A = nx.laplacian_matrix(G)
  114. sigma = 2 - sqrt(2 + sqrt(2))
  115. ac = nx.algebraic_connectivity(G, tol=1e-12, method="tracemin")
  116. assert ac == pytest.approx(sigma, abs=1e-7)
  117. x = nx.fiedler_vector(G, tol=1e-12, method="tracemin")
  118. check_eigenvector(A, sigma, x)
  119. @pytest.mark.parametrize("method", methods)
  120. def test_path(self, method):
  121. pytest.importorskip("scipy")
  122. G = nx.path_graph(8)
  123. A = nx.laplacian_matrix(G)
  124. sigma = 2 - sqrt(2 + sqrt(2))
  125. ac = nx.algebraic_connectivity(G, tol=1e-12, method=method)
  126. assert ac == pytest.approx(sigma, abs=1e-7)
  127. x = nx.fiedler_vector(G, tol=1e-12, method=method)
  128. check_eigenvector(A, sigma, x)
  129. @pytest.mark.parametrize("method", methods)
  130. def test_problematic_graph_issue_2381(self, method):
  131. pytest.importorskip("scipy")
  132. G = nx.path_graph(4)
  133. G.add_edges_from([(4, 2), (5, 1)])
  134. A = nx.laplacian_matrix(G)
  135. sigma = 0.438447187191
  136. ac = nx.algebraic_connectivity(G, tol=1e-12, method=method)
  137. assert ac == pytest.approx(sigma, abs=1e-7)
  138. x = nx.fiedler_vector(G, tol=1e-12, method=method)
  139. check_eigenvector(A, sigma, x)
  140. @pytest.mark.parametrize("method", methods)
  141. def test_cycle(self, method):
  142. pytest.importorskip("scipy")
  143. G = nx.cycle_graph(8)
  144. A = nx.laplacian_matrix(G)
  145. sigma = 2 - sqrt(2)
  146. ac = nx.algebraic_connectivity(G, tol=1e-12, method=method)
  147. assert ac == pytest.approx(sigma, abs=1e-7)
  148. x = nx.fiedler_vector(G, tol=1e-12, method=method)
  149. check_eigenvector(A, sigma, x)
  150. @pytest.mark.parametrize("method", methods)
  151. def test_seed_argument(self, method):
  152. pytest.importorskip("scipy")
  153. G = nx.cycle_graph(8)
  154. A = nx.laplacian_matrix(G)
  155. sigma = 2 - sqrt(2)
  156. ac = nx.algebraic_connectivity(G, tol=1e-12, method=method, seed=1)
  157. assert ac == pytest.approx(sigma, abs=1e-7)
  158. x = nx.fiedler_vector(G, tol=1e-12, method=method, seed=1)
  159. check_eigenvector(A, sigma, x)
  160. @pytest.mark.parametrize(
  161. ("normalized", "sigma", "laplacian_fn"),
  162. (
  163. (False, 0.2434017461399311, nx.laplacian_matrix),
  164. (True, 0.08113391537997749, nx.normalized_laplacian_matrix),
  165. ),
  166. )
  167. @pytest.mark.parametrize("method", methods)
  168. def test_buckminsterfullerene(self, normalized, sigma, laplacian_fn, method):
  169. pytest.importorskip("scipy")
  170. G = nx.Graph(
  171. [
  172. (1, 10),
  173. (1, 41),
  174. (1, 59),
  175. (2, 12),
  176. (2, 42),
  177. (2, 60),
  178. (3, 6),
  179. (3, 43),
  180. (3, 57),
  181. (4, 8),
  182. (4, 44),
  183. (4, 58),
  184. (5, 13),
  185. (5, 56),
  186. (5, 57),
  187. (6, 10),
  188. (6, 31),
  189. (7, 14),
  190. (7, 56),
  191. (7, 58),
  192. (8, 12),
  193. (8, 32),
  194. (9, 23),
  195. (9, 53),
  196. (9, 59),
  197. (10, 15),
  198. (11, 24),
  199. (11, 53),
  200. (11, 60),
  201. (12, 16),
  202. (13, 14),
  203. (13, 25),
  204. (14, 26),
  205. (15, 27),
  206. (15, 49),
  207. (16, 28),
  208. (16, 50),
  209. (17, 18),
  210. (17, 19),
  211. (17, 54),
  212. (18, 20),
  213. (18, 55),
  214. (19, 23),
  215. (19, 41),
  216. (20, 24),
  217. (20, 42),
  218. (21, 31),
  219. (21, 33),
  220. (21, 57),
  221. (22, 32),
  222. (22, 34),
  223. (22, 58),
  224. (23, 24),
  225. (25, 35),
  226. (25, 43),
  227. (26, 36),
  228. (26, 44),
  229. (27, 51),
  230. (27, 59),
  231. (28, 52),
  232. (28, 60),
  233. (29, 33),
  234. (29, 34),
  235. (29, 56),
  236. (30, 51),
  237. (30, 52),
  238. (30, 53),
  239. (31, 47),
  240. (32, 48),
  241. (33, 45),
  242. (34, 46),
  243. (35, 36),
  244. (35, 37),
  245. (36, 38),
  246. (37, 39),
  247. (37, 49),
  248. (38, 40),
  249. (38, 50),
  250. (39, 40),
  251. (39, 51),
  252. (40, 52),
  253. (41, 47),
  254. (42, 48),
  255. (43, 49),
  256. (44, 50),
  257. (45, 46),
  258. (45, 54),
  259. (46, 55),
  260. (47, 54),
  261. (48, 55),
  262. ]
  263. )
  264. A = laplacian_fn(G)
  265. try:
  266. assert nx.algebraic_connectivity(
  267. G, normalized=normalized, tol=1e-12, method=method
  268. ) == pytest.approx(sigma, abs=1e-7)
  269. x = nx.fiedler_vector(G, normalized=normalized, tol=1e-12, method=method)
  270. check_eigenvector(A, sigma, x)
  271. except nx.NetworkXError as err:
  272. if err.args not in (
  273. ("Cholesky solver unavailable.",),
  274. ("LU solver unavailable.",),
  275. ):
  276. raise
  277. class TestSpectralOrdering:
  278. _graphs = (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)
  279. @pytest.mark.parametrize("graph", _graphs)
  280. def test_nullgraph(self, graph):
  281. G = graph()
  282. pytest.raises(nx.NetworkXError, nx.spectral_ordering, G)
  283. @pytest.mark.parametrize("graph", _graphs)
  284. def test_singleton(self, graph):
  285. G = graph()
  286. G.add_node("x")
  287. assert nx.spectral_ordering(G) == ["x"]
  288. G.add_edge("x", "x", weight=33)
  289. G.add_edge("x", "x", weight=33)
  290. assert nx.spectral_ordering(G) == ["x"]
  291. def test_unrecognized_method(self):
  292. G = nx.path_graph(4)
  293. pytest.raises(nx.NetworkXError, nx.spectral_ordering, G, method="unknown")
  294. @pytest.mark.parametrize("method", methods)
  295. def test_three_nodes(self, method):
  296. pytest.importorskip("scipy")
  297. G = nx.Graph()
  298. G.add_weighted_edges_from([(1, 2, 1), (1, 3, 2), (2, 3, 1)], weight="spam")
  299. order = nx.spectral_ordering(G, weight="spam", method=method)
  300. assert set(order) == set(G)
  301. assert {1, 3} in (set(order[:-1]), set(order[1:]))
  302. @pytest.mark.parametrize("method", methods)
  303. def test_three_nodes_multigraph(self, method):
  304. pytest.importorskip("scipy")
  305. G = nx.MultiDiGraph()
  306. G.add_weighted_edges_from([(1, 2, 1), (1, 3, 2), (2, 3, 1), (2, 3, 2)])
  307. order = nx.spectral_ordering(G, method=method)
  308. assert set(order) == set(G)
  309. assert {2, 3} in (set(order[:-1]), set(order[1:]))
  310. @pytest.mark.parametrize("method", methods)
  311. def test_path(self, method):
  312. pytest.importorskip("scipy")
  313. path = list(range(10))
  314. np.random.shuffle(path)
  315. G = nx.Graph()
  316. nx.add_path(G, path)
  317. order = nx.spectral_ordering(G, method=method)
  318. assert order in [path, list(reversed(path))]
  319. @pytest.mark.parametrize("method", methods)
  320. def test_seed_argument(self, method):
  321. pytest.importorskip("scipy")
  322. path = list(range(10))
  323. np.random.shuffle(path)
  324. G = nx.Graph()
  325. nx.add_path(G, path)
  326. order = nx.spectral_ordering(G, method=method, seed=1)
  327. assert order in [path, list(reversed(path))]
  328. @pytest.mark.parametrize("method", methods)
  329. def test_disconnected(self, method):
  330. pytest.importorskip("scipy")
  331. G = nx.Graph()
  332. nx.add_path(G, range(0, 10, 2))
  333. nx.add_path(G, range(1, 10, 2))
  334. order = nx.spectral_ordering(G, method=method)
  335. assert set(order) == set(G)
  336. seqs = [
  337. list(range(0, 10, 2)),
  338. list(range(8, -1, -2)),
  339. list(range(1, 10, 2)),
  340. list(range(9, -1, -2)),
  341. ]
  342. assert order[:5] in seqs
  343. assert order[5:] in seqs
  344. @pytest.mark.parametrize(
  345. ("normalized", "expected_order"),
  346. (
  347. (False, [[1, 2, 0, 3, 4, 5, 6, 9, 7, 8], [8, 7, 9, 6, 5, 4, 3, 0, 2, 1]]),
  348. (True, [[1, 2, 3, 0, 4, 5, 9, 6, 7, 8], [8, 7, 6, 9, 5, 4, 0, 3, 2, 1]]),
  349. ),
  350. )
  351. @pytest.mark.parametrize("method", methods)
  352. def test_cycle(self, normalized, expected_order, method):
  353. pytest.importorskip("scipy")
  354. path = list(range(10))
  355. G = nx.Graph()
  356. nx.add_path(G, path, weight=5)
  357. G.add_edge(path[-1], path[0], weight=1)
  358. A = nx.laplacian_matrix(G).todense()
  359. order = nx.spectral_ordering(G, normalized=normalized, method=method)
  360. assert order in expected_order