Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/algorithms/centrality/flow_matrix.py: 26%
88 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
1# Helpers for current-flow betweenness and current-flow closeness
2# Lazy computations for inverse Laplacian and flow-matrix rows.
3import networkx as nx
6@nx._dispatch(edge_attrs="weight")
7def flow_matrix_row(G, weight=None, dtype=float, solver="lu"):
8 # Generate a row of the current-flow matrix
9 import numpy as np
11 solvername = {
12 "full": FullInverseLaplacian,
13 "lu": SuperLUInverseLaplacian,
14 "cg": CGInverseLaplacian,
15 }
16 n = G.number_of_nodes()
17 L = nx.laplacian_matrix(G, nodelist=range(n), weight=weight).asformat("csc")
18 L = L.astype(dtype)
19 C = solvername[solver](L, dtype=dtype) # initialize solver
20 w = C.w # w is the Laplacian matrix width
21 # row-by-row flow matrix
22 for u, v in sorted(sorted((u, v)) for u, v in G.edges()):
23 B = np.zeros(w, dtype=dtype)
24 c = G[u][v].get(weight, 1.0)
25 B[u % w] = c
26 B[v % w] = -c
27 # get only the rows needed in the inverse laplacian
28 # and multiply to get the flow matrix row
29 row = B @ C.get_rows(u, v)
30 yield row, (u, v)
33# Class to compute the inverse laplacian only for specified rows
34# Allows computation of the current-flow matrix without storing entire
35# inverse laplacian matrix
36class InverseLaplacian:
37 def __init__(self, L, width=None, dtype=None):
38 global np
39 import numpy as np
41 (n, n) = L.shape
42 self.dtype = dtype
43 self.n = n
44 if width is None:
45 self.w = self.width(L)
46 else:
47 self.w = width
48 self.C = np.zeros((self.w, n), dtype=dtype)
49 self.L1 = L[1:, 1:]
50 self.init_solver(L)
52 def init_solver(self, L):
53 pass
55 def solve(self, r):
56 raise nx.NetworkXError("Implement solver")
58 def solve_inverse(self, r):
59 raise nx.NetworkXError("Implement solver")
61 def get_rows(self, r1, r2):
62 for r in range(r1, r2 + 1):
63 self.C[r % self.w, 1:] = self.solve_inverse(r)
64 return self.C
66 def get_row(self, r):
67 self.C[r % self.w, 1:] = self.solve_inverse(r)
68 return self.C[r % self.w]
70 def width(self, L):
71 m = 0
72 for i, row in enumerate(L):
73 w = 0
74 x, y = np.nonzero(row)
75 if len(y) > 0:
76 v = y - i
77 w = v.max() - v.min() + 1
78 m = max(w, m)
79 return m
82class FullInverseLaplacian(InverseLaplacian):
83 def init_solver(self, L):
84 self.IL = np.zeros(L.shape, dtype=self.dtype)
85 self.IL[1:, 1:] = np.linalg.inv(self.L1.todense())
87 def solve(self, rhs):
88 s = np.zeros(rhs.shape, dtype=self.dtype)
89 s = self.IL @ rhs
90 return s
92 def solve_inverse(self, r):
93 return self.IL[r, 1:]
96class SuperLUInverseLaplacian(InverseLaplacian):
97 def init_solver(self, L):
98 import scipy as sp
100 self.lusolve = sp.sparse.linalg.factorized(self.L1.tocsc())
102 def solve_inverse(self, r):
103 rhs = np.zeros(self.n, dtype=self.dtype)
104 rhs[r] = 1
105 return self.lusolve(rhs[1:])
107 def solve(self, rhs):
108 s = np.zeros(rhs.shape, dtype=self.dtype)
109 s[1:] = self.lusolve(rhs[1:])
110 return s
113class CGInverseLaplacian(InverseLaplacian):
114 def init_solver(self, L):
115 global sp
116 import scipy as sp
118 ilu = sp.sparse.linalg.spilu(self.L1.tocsc())
119 n = self.n - 1
120 self.M = sp.sparse.linalg.LinearOperator(shape=(n, n), matvec=ilu.solve)
122 def solve(self, rhs):
123 s = np.zeros(rhs.shape, dtype=self.dtype)
124 s[1:] = sp.sparse.linalg.cg(self.L1, rhs[1:], M=self.M, atol=0)[0]
125 return s
127 def solve_inverse(self, r):
128 rhs = np.zeros(self.n, self.dtype)
129 rhs[r] = 1
130 return sp.sparse.linalg.cg(self.L1, rhs[1:], M=self.M, atol=0)[0]