Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/linalg/laplacianmatrix.py: 23%

88 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-20 07:00 +0000

1"""Laplacian matrix of graphs. 

2""" 

3import networkx as nx 

4from networkx.utils import not_implemented_for 

5 

6__all__ = [ 

7 "laplacian_matrix", 

8 "normalized_laplacian_matrix", 

9 "total_spanning_tree_weight", 

10 "directed_laplacian_matrix", 

11 "directed_combinatorial_laplacian_matrix", 

12] 

13 

14 

15@not_implemented_for("directed") 

16@nx._dispatch(edge_attrs="weight") 

17def laplacian_matrix(G, nodelist=None, weight="weight"): 

18 """Returns the Laplacian matrix of G. 

19 

20 The graph Laplacian is the matrix L = D - A, where 

21 A is the adjacency matrix and D is the diagonal matrix of node degrees. 

22 

23 Parameters 

24 ---------- 

25 G : graph 

26 A NetworkX graph 

27 

28 nodelist : list, optional 

29 The rows and columns are ordered according to the nodes in nodelist. 

30 If nodelist is None, then the ordering is produced by G.nodes(). 

31 

32 weight : string or None, optional (default='weight') 

33 The edge data key used to compute each value in the matrix. 

34 If None, then each edge has weight 1. 

35 

36 Returns 

37 ------- 

38 L : SciPy sparse array 

39 The Laplacian matrix of G. 

40 

41 Notes 

42 ----- 

43 For MultiGraph, the edges weights are summed. 

44 

45 See Also 

46 -------- 

47 :func:`~networkx.convert_matrix.to_numpy_array` 

48 normalized_laplacian_matrix 

49 :func:`~networkx.linalg.spectrum.laplacian_spectrum` 

50 

51 Examples 

52 -------- 

53 For graphs with multiple connected components, L is permutation-similar 

54 to a block diagonal matrix where each block is the respective Laplacian 

55 matrix for each component. 

56 

57 >>> G = nx.Graph([(1, 2), (2, 3), (4, 5)]) 

58 >>> print(nx.laplacian_matrix(G).toarray()) 

59 [[ 1 -1 0 0 0] 

60 [-1 2 -1 0 0] 

61 [ 0 -1 1 0 0] 

62 [ 0 0 0 1 -1] 

63 [ 0 0 0 -1 1]] 

64 

65 """ 

66 import scipy as sp 

67 

68 if nodelist is None: 

69 nodelist = list(G) 

70 A = nx.to_scipy_sparse_array(G, nodelist=nodelist, weight=weight, format="csr") 

71 n, m = A.shape 

72 # TODO: rm csr_array wrapper when spdiags can produce arrays 

73 D = sp.sparse.csr_array(sp.sparse.spdiags(A.sum(axis=1), 0, m, n, format="csr")) 

74 return D - A 

75 

76 

77@not_implemented_for("directed") 

78@nx._dispatch(edge_attrs="weight") 

79def normalized_laplacian_matrix(G, nodelist=None, weight="weight"): 

80 r"""Returns the normalized Laplacian matrix of G. 

81 

82 The normalized graph Laplacian is the matrix 

83 

84 .. math:: 

85 

86 N = D^{-1/2} L D^{-1/2} 

87 

88 where `L` is the graph Laplacian and `D` is the diagonal matrix of 

89 node degrees [1]_. 

90 

91 Parameters 

92 ---------- 

93 G : graph 

94 A NetworkX graph 

95 

96 nodelist : list, optional 

97 The rows and columns are ordered according to the nodes in nodelist. 

98 If nodelist is None, then the ordering is produced by G.nodes(). 

99 

100 weight : string or None, optional (default='weight') 

101 The edge data key used to compute each value in the matrix. 

102 If None, then each edge has weight 1. 

103 

104 Returns 

105 ------- 

106 N : SciPy sparse array 

107 The normalized Laplacian matrix of G. 

108 

109 Notes 

110 ----- 

111 For MultiGraph, the edges weights are summed. 

112 See :func:`to_numpy_array` for other options. 

113 

114 If the Graph contains selfloops, D is defined as ``diag(sum(A, 1))``, where A is 

115 the adjacency matrix [2]_. 

116 

117 See Also 

118 -------- 

119 laplacian_matrix 

120 normalized_laplacian_spectrum 

121 

122 References 

123 ---------- 

124 .. [1] Fan Chung-Graham, Spectral Graph Theory, 

125 CBMS Regional Conference Series in Mathematics, Number 92, 1997. 

126 .. [2] Steve Butler, Interlacing For Weighted Graphs Using The Normalized 

127 Laplacian, Electronic Journal of Linear Algebra, Volume 16, pp. 90-98, 

128 March 2007. 

129 """ 

130 import numpy as np 

131 import scipy as sp 

132 

133 if nodelist is None: 

134 nodelist = list(G) 

135 A = nx.to_scipy_sparse_array(G, nodelist=nodelist, weight=weight, format="csr") 

136 n, m = A.shape 

137 diags = A.sum(axis=1) 

138 # TODO: rm csr_array wrapper when spdiags can produce arrays 

139 D = sp.sparse.csr_array(sp.sparse.spdiags(diags, 0, m, n, format="csr")) 

140 L = D - A 

141 with np.errstate(divide="ignore"): 

142 diags_sqrt = 1.0 / np.sqrt(diags) 

143 diags_sqrt[np.isinf(diags_sqrt)] = 0 

144 # TODO: rm csr_array wrapper when spdiags can produce arrays 

145 DH = sp.sparse.csr_array(sp.sparse.spdiags(diags_sqrt, 0, m, n, format="csr")) 

146 return DH @ (L @ DH) 

147 

148 

149@nx._dispatch(edge_attrs="weight") 

150def total_spanning_tree_weight(G, weight=None): 

151 """ 

152 Returns the total weight of all spanning trees of `G`. 

153 

154 Kirchoff's Tree Matrix Theorem states that the determinant of any cofactor of the 

155 Laplacian matrix of a graph is the number of spanning trees in the graph. For a 

156 weighted Laplacian matrix, it is the sum across all spanning trees of the 

157 multiplicative weight of each tree. That is, the weight of each tree is the 

158 product of its edge weights. 

159 

160 Parameters 

161 ---------- 

162 G : NetworkX Graph 

163 The graph to use Kirchhoff's theorem on. 

164 

165 weight : string or None 

166 The key for the edge attribute holding the edge weight. If `None`, then 

167 each edge is assumed to have a weight of 1 and this function returns the 

168 total number of spanning trees in `G`. 

169 

170 Returns 

171 ------- 

172 float 

173 The sum of the total multiplicative weights for all spanning trees in `G` 

174 """ 

175 import numpy as np 

176 

177 G_laplacian = nx.laplacian_matrix(G, weight=weight).toarray() 

178 # Determinant ignoring first row and column 

179 return abs(np.linalg.det(G_laplacian[1:, 1:])) 

180 

181 

182############################################################################### 

183# Code based on work from https://github.com/bjedwards 

184 

185 

186@not_implemented_for("undirected") 

187@not_implemented_for("multigraph") 

188@nx._dispatch(edge_attrs="weight") 

189def directed_laplacian_matrix( 

190 G, nodelist=None, weight="weight", walk_type=None, alpha=0.95 

191): 

192 r"""Returns the directed Laplacian matrix of G. 

193 

194 The graph directed Laplacian is the matrix 

195 

196 .. math:: 

197 

198 L = I - (\Phi^{1/2} P \Phi^{-1/2} + \Phi^{-1/2} P^T \Phi^{1/2} ) / 2 

199 

200 where `I` is the identity matrix, `P` is the transition matrix of the 

201 graph, and `\Phi` a matrix with the Perron vector of `P` in the diagonal and 

202 zeros elsewhere [1]_. 

203 

204 Depending on the value of walk_type, `P` can be the transition matrix 

205 induced by a random walk, a lazy random walk, or a random walk with 

206 teleportation (PageRank). 

207 

208 Parameters 

209 ---------- 

210 G : DiGraph 

211 A NetworkX graph 

212 

213 nodelist : list, optional 

214 The rows and columns are ordered according to the nodes in nodelist. 

215 If nodelist is None, then the ordering is produced by G.nodes(). 

216 

217 weight : string or None, optional (default='weight') 

218 The edge data key used to compute each value in the matrix. 

219 If None, then each edge has weight 1. 

220 

221 walk_type : string or None, optional (default=None) 

222 If None, `P` is selected depending on the properties of the 

223 graph. Otherwise is one of 'random', 'lazy', or 'pagerank' 

224 

225 alpha : real 

226 (1 - alpha) is the teleportation probability used with pagerank 

227 

228 Returns 

229 ------- 

230 L : NumPy matrix 

231 Normalized Laplacian of G. 

232 

233 Notes 

234 ----- 

235 Only implemented for DiGraphs 

236 

237 See Also 

238 -------- 

239 laplacian_matrix 

240 

241 References 

242 ---------- 

243 .. [1] Fan Chung (2005). 

244 Laplacians and the Cheeger inequality for directed graphs. 

245 Annals of Combinatorics, 9(1), 2005 

246 """ 

247 import numpy as np 

248 import scipy as sp 

249 

250 # NOTE: P has type ndarray if walk_type=="pagerank", else csr_array 

251 P = _transition_matrix( 

252 G, nodelist=nodelist, weight=weight, walk_type=walk_type, alpha=alpha 

253 ) 

254 

255 n, m = P.shape 

256 

257 evals, evecs = sp.sparse.linalg.eigs(P.T, k=1) 

258 v = evecs.flatten().real 

259 p = v / v.sum() 

260 # p>=0 by Perron-Frobenius Thm. Use abs() to fix roundoff across zero gh-6865 

261 sqrtp = np.sqrt(np.abs(p)) 

262 Q = ( 

263 # TODO: rm csr_array wrapper when spdiags creates arrays 

264 sp.sparse.csr_array(sp.sparse.spdiags(sqrtp, 0, n, n)) 

265 @ P 

266 # TODO: rm csr_array wrapper when spdiags creates arrays 

267 @ sp.sparse.csr_array(sp.sparse.spdiags(1.0 / sqrtp, 0, n, n)) 

268 ) 

269 # NOTE: This could be sparsified for the non-pagerank cases 

270 I = np.identity(len(G)) 

271 

272 return I - (Q + Q.T) / 2.0 

273 

274 

275@not_implemented_for("undirected") 

276@not_implemented_for("multigraph") 

277@nx._dispatch(edge_attrs="weight") 

278def directed_combinatorial_laplacian_matrix( 

279 G, nodelist=None, weight="weight", walk_type=None, alpha=0.95 

280): 

281 r"""Return the directed combinatorial Laplacian matrix of G. 

282 

283 The graph directed combinatorial Laplacian is the matrix 

284 

285 .. math:: 

286 

287 L = \Phi - (\Phi P + P^T \Phi) / 2 

288 

289 where `P` is the transition matrix of the graph and `\Phi` a matrix 

290 with the Perron vector of `P` in the diagonal and zeros elsewhere [1]_. 

291 

292 Depending on the value of walk_type, `P` can be the transition matrix 

293 induced by a random walk, a lazy random walk, or a random walk with 

294 teleportation (PageRank). 

295 

296 Parameters 

297 ---------- 

298 G : DiGraph 

299 A NetworkX graph 

300 

301 nodelist : list, optional 

302 The rows and columns are ordered according to the nodes in nodelist. 

303 If nodelist is None, then the ordering is produced by G.nodes(). 

304 

305 weight : string or None, optional (default='weight') 

306 The edge data key used to compute each value in the matrix. 

307 If None, then each edge has weight 1. 

308 

309 walk_type : string or None, optional (default=None) 

310 If None, `P` is selected depending on the properties of the 

311 graph. Otherwise is one of 'random', 'lazy', or 'pagerank' 

312 

313 alpha : real 

314 (1 - alpha) is the teleportation probability used with pagerank 

315 

316 Returns 

317 ------- 

318 L : NumPy matrix 

319 Combinatorial Laplacian of G. 

320 

321 Notes 

322 ----- 

323 Only implemented for DiGraphs 

324 

325 See Also 

326 -------- 

327 laplacian_matrix 

328 

329 References 

330 ---------- 

331 .. [1] Fan Chung (2005). 

332 Laplacians and the Cheeger inequality for directed graphs. 

333 Annals of Combinatorics, 9(1), 2005 

334 """ 

335 import scipy as sp 

336 

337 P = _transition_matrix( 

338 G, nodelist=nodelist, weight=weight, walk_type=walk_type, alpha=alpha 

339 ) 

340 

341 n, m = P.shape 

342 

343 evals, evecs = sp.sparse.linalg.eigs(P.T, k=1) 

344 v = evecs.flatten().real 

345 p = v / v.sum() 

346 # NOTE: could be improved by not densifying 

347 # TODO: Rm csr_array wrapper when spdiags array creation becomes available 

348 Phi = sp.sparse.csr_array(sp.sparse.spdiags(p, 0, n, n)).toarray() 

349 

350 return Phi - (Phi @ P + P.T @ Phi) / 2.0 

351 

352 

353def _transition_matrix(G, nodelist=None, weight="weight", walk_type=None, alpha=0.95): 

354 """Returns the transition matrix of G. 

355 

356 This is a row stochastic giving the transition probabilities while 

357 performing a random walk on the graph. Depending on the value of walk_type, 

358 P can be the transition matrix induced by a random walk, a lazy random walk, 

359 or a random walk with teleportation (PageRank). 

360 

361 Parameters 

362 ---------- 

363 G : DiGraph 

364 A NetworkX graph 

365 

366 nodelist : list, optional 

367 The rows and columns are ordered according to the nodes in nodelist. 

368 If nodelist is None, then the ordering is produced by G.nodes(). 

369 

370 weight : string or None, optional (default='weight') 

371 The edge data key used to compute each value in the matrix. 

372 If None, then each edge has weight 1. 

373 

374 walk_type : string or None, optional (default=None) 

375 If None, `P` is selected depending on the properties of the 

376 graph. Otherwise is one of 'random', 'lazy', or 'pagerank' 

377 

378 alpha : real 

379 (1 - alpha) is the teleportation probability used with pagerank 

380 

381 Returns 

382 ------- 

383 P : numpy.ndarray 

384 transition matrix of G. 

385 

386 Raises 

387 ------ 

388 NetworkXError 

389 If walk_type not specified or alpha not in valid range 

390 """ 

391 import numpy as np 

392 import scipy as sp 

393 

394 if walk_type is None: 

395 if nx.is_strongly_connected(G): 

396 if nx.is_aperiodic(G): 

397 walk_type = "random" 

398 else: 

399 walk_type = "lazy" 

400 else: 

401 walk_type = "pagerank" 

402 

403 A = nx.to_scipy_sparse_array(G, nodelist=nodelist, weight=weight, dtype=float) 

404 n, m = A.shape 

405 if walk_type in ["random", "lazy"]: 

406 # TODO: Rm csr_array wrapper when spdiags array creation becomes available 

407 DI = sp.sparse.csr_array(sp.sparse.spdiags(1.0 / A.sum(axis=1), 0, n, n)) 

408 if walk_type == "random": 

409 P = DI @ A 

410 else: 

411 # TODO: Rm csr_array wrapper when identity array creation becomes available 

412 I = sp.sparse.csr_array(sp.sparse.identity(n)) 

413 P = (I + DI @ A) / 2.0 

414 

415 elif walk_type == "pagerank": 

416 if not (0 < alpha < 1): 

417 raise nx.NetworkXError("alpha must be between 0 and 1") 

418 # this is using a dense representation. NOTE: This should be sparsified! 

419 A = A.toarray() 

420 # add constant to dangling nodes' row 

421 A[A.sum(axis=1) == 0, :] = 1 / n 

422 # normalize 

423 A = A / A.sum(axis=1)[np.newaxis, :].T 

424 P = alpha * A + (1 - alpha) / n 

425 else: 

426 raise nx.NetworkXError("walk_type must be random, lazy, or pagerank") 

427 

428 return P