def _solve(self, b, tol): import numpy as np from scipy.linalg.blas import dasum, daxpy, ddot A = self._A M = self._M tol *= dasum(b) # Initialize. x = np.zeros(b.shape) r = b.copy() z = M(r) rz = ddot(r, z) p = z.copy() # Iterate. while True: Ap = A(p) alpha = rz / ddot(p, Ap) x = daxpy(p, x, a=alpha) r = daxpy(Ap, r, a=-alpha) if dasum(r) < tol: return x z = M(r) beta = ddot(r, z) beta, rz = beta / rz, beta p = daxpy(p, z, a=beta)
def _solve(self, b, tol): A = self._A M = self._M tol *= dasum(b) # Initialize. x = zeros(b.shape) r = b.copy() z = M(r) rz = ddot(r, z) p = z.copy() # Iterate. while True: Ap = A(p) alpha = rz / ddot(p, Ap) x = daxpy(p, x, a=alpha) r = daxpy(Ap, r, a=-alpha) if dasum(r) < tol: return x z = M(r) beta = ddot(r, z) beta, rz = beta / rz, beta p = daxpy(p, z, a=beta)
def _tracemin_fiedler(L, X, normalized, tol, method): """Compute the Fiedler vector of L using the TraceMIN-Fiedler algorithm. The Fiedler vector of a connected undirected graph is the eigenvector corresponding to the second smallest eigenvalue of the Laplacian matrix of of the graph. This function starts with the Laplacian L, not the Graph. Parameters ---------- L : Laplacian of a possibly weighted or normalized, but undirected graph X : Initial guess for a solution. Usually a matrix of random numbers. This function allows more than one column in X to identify more than one eigenvector if desired. normalized : bool Whether the normalized Laplacian matrix is used. tol : float Tolerance of relative residual in eigenvalue computation. Warning: There is no limit on number of iterations. method : string Should be 'tracemin_pcg', 'tracemin_chol' or 'tracemin_lu'. Otherwise exception is raised. Returns ------- sigma, X : Two NumPy arrays of floats. The lowest eigenvalues and corresponding eigenvectors of L. The size of input X determines the size of these outputs. As this is for Fiedler vectors, the zero eigenvalue (and constant eigenvector) are avoided. """ n = X.shape[0] if normalized: # Form the normalized Laplacian matrix and determine the eigenvector of # its nullspace. e = sqrt(L.diagonal()) D = spdiags(1.0 / e, [0], n, n, format="csr") L = D * L * D e *= 1.0 / norm(e, 2) if normalized: def project(X): """Make X orthogonal to the nullspace of L. """ X = asarray(X) for j in range(X.shape[1]): X[:, j] -= dot(X[:, j], e) * e else: def project(X): """Make X orthogonal to the nullspace of L. """ X = asarray(X) for j in range(X.shape[1]): X[:, j] -= X[:, j].sum() / n if method == "tracemin_pcg": D = L.diagonal().astype(float) solver = _PCGSolver(lambda x: L * x, lambda x: D * x) elif method == "tracemin_chol" or method == "tracemin_lu": # Convert A to CSC to suppress SparseEfficiencyWarning. A = csc_matrix(L, dtype=float, copy=True) # Force A to be nonsingular. Since A is the Laplacian matrix of a # connected graph, its rank deficiency is one, and thus one diagonal # element needs to modified. Changing to infinity forces a zero in the # corresponding element in the solution. i = (A.indptr[1:] - A.indptr[:-1]).argmax() A[i, i] = float("inf") if method == "tracemin_chol": solver = _CholeskySolver(A) else: solver = _LUSolver(A) else: raise nx.NetworkXError("Unknown linear system solver: " + method) # Initialize. Lnorm = abs(L).sum(axis=1).flatten().max() project(X) W = asmatrix(ndarray(X.shape, order="F")) while True: # Orthonormalize X. X = qr(X)[0] # Compute iteration matrix H. W[:, :] = L * X H = X.T * W sigma, Y = eigh(H, overwrite_a=True) # Compute the Ritz vectors. X *= Y # Test for convergence exploiting the fact that L * X == W * Y. res = dasum(W * asmatrix(Y)[:, 0] - sigma[0] * X[:, 0]) / Lnorm if res < tol: break # Compute X = L \ X / (X' * (L \ X)). # L \ X can have an arbitrary projection on the nullspace of L, # which will be eliminated. W[:, :] = solver.solve(X, tol) X = (inv(W.T * X) * W.T).T # Preserves Fortran storage order. project(X) return sigma, asarray(X)
def _tracemin_fiedler(L, X, normalized, tol, method): """Compute the Fiedler vector of L using the TraceMIN-Fiedler algorithm. """ n = X.shape[0] if normalized: # Form the normalized Laplacian matrix and determine the eigenvector of # its nullspace. e = sqrt(L.diagonal()) D = spdiags(1. / e, [0], n, n, format='csr') L = D * L * D e *= 1. / norm(e, 2) if not normalized: def project(X): """Make X orthogonal to the nullspace of L. """ X = asarray(X) for j in range(X.shape[1]): X[:, j] -= X[:, j].sum() / n else: def project(X): """Make X orthogonal to the nullspace of L. """ X = asarray(X) for j in range(X.shape[1]): X[:, j] -= dot(X[:, j], e) * e if method is None: method = 'pcg' if method == 'pcg': # See comments below for the semantics of P and D. def P(x): x -= asarray(x * X * X.T)[0, :] if not normalized: x -= x.sum() / n else: x = daxpy(e, x, a=-ddot(x, e)) return x solver = _PCGSolver(lambda x: P(L * P(x)), lambda x: D * x) elif method == 'chol' or method == 'lu': # Convert A to CSC to suppress SparseEfficiencyWarning. A = csc_matrix(L, dtype=float, copy=True) # Force A to be nonsingular. Since A is the Laplacian matrix of a # connected graph, its rank deficiency is one, and thus one diagonal # element needs to modified. Changing to infinity forces a zero in the # corresponding element in the solution. i = (A.indptr[1:] - A.indptr[:-1]).argmax() A[i, i] = float('inf') solver = (_CholeskySolver if method == 'chol' else _LUSolver)(A) else: raise nx.NetworkXError('unknown linear system solver.') # Initialize. Lnorm = abs(L).sum(axis=1).flatten().max() project(X) W = asmatrix(ndarray(X.shape, order='F')) while True: # Orthonormalize X. X = qr(X)[0] # Compute interation matrix H. W[:, :] = L * X H = X.T * W sigma, Y = eigh(H, overwrite_a=True) # Compute the Ritz vectors. X *= Y # Test for convergence exploiting the fact that L * X == W * Y. res = dasum(W * asmatrix(Y)[:, 0] - sigma[0] * X[:, 0]) / Lnorm if res < tol: break # Depending on the linear solver to be used, two mathematically # equivalent formulations are used. if method == 'pcg': # Compute X = X - (P * L * P) \ (P * L * X) where # P = I - [e X] * [e X]' is a projection onto the orthogonal # complement of [e X]. W *= Y # L * X == W * Y W -= (W.T * X * X.T).T project(W) # Compute the diagonal of P * L * P as a Jacobi preconditioner. D = L.diagonal().astype(float) D += 2. * (asarray(X) * asarray(W)).sum(axis=1) D += (asarray(X) * asarray(X * (W.T * X))).sum(axis=1) D[D < tol * Lnorm] = 1. D = 1. / D # Since TraceMIN is globally convergent, the relative residual can # be loose. X -= solver.solve(W, 0.1) else: # Compute X = L \ X / (X' * (L \ X)). L \ X can have an arbitrary # projection on the nullspace of L, which will be eliminated. W[:, :] = solver.solve(X) project(W) X = (inv(W.T * X) * W.T).T # Preserves Fortran storage order. return sigma, asarray(X)
def _tracemin_fiedler(L, X, normalized, tol, method): """Compute the Fiedler vector of L using the TraceMIN-Fiedler algorithm. The Fiedler vector of a connected undirected graph is the eigenvector corresponding to the second smallest eigenvalue of the Laplacian matrix of of the graph. This function starts with the Laplacian L, not the Graph. Parameters ---------- L : Laplacian of a possibly weighted or normalized, but undirected graph X : Initial guess for a solution. Usually a matrix of random numbers. This function allows more than one column in X to identify more than one eigenvector if desired. normalized : bool Whether the normalized Laplacian matrix is used. tol : float Tolerance of relative residual in eigenvalue computation. Warning: There is no limit on number of iterations. method : string Should be 'tracemin_pcg', 'tracemin_chol' or 'tracemin_lu'. Otherwise exception is raised. Returns ------- sigma, X : Two NumPy arrays of floats. The lowest eigenvalues and corresponding eigenvectors of L. The size of input X determines the size of these outputs. As this is for Fiedler vectors, the zero eigenvalue (and constant eigenvector) are avoided. """ n = X.shape[0] if normalized: # Form the normalized Laplacian matrix and determine the eigenvector of # its nullspace. e = sqrt(L.diagonal()) D = spdiags(1. / e, [0], n, n, format='csr') L = D * L * D e *= 1. / norm(e, 2) if normalized: def project(X): """Make X orthogonal to the nullspace of L. """ X = asarray(X) for j in range(X.shape[1]): X[:, j] -= dot(X[:, j], e) * e else: def project(X): """Make X orthogonal to the nullspace of L. """ X = asarray(X) for j in range(X.shape[1]): X[:, j] -= X[:, j].sum() / n if method == 'tracemin_pcg': D = L.diagonal().astype(float) solver = _PCGSolver(lambda x: L * x, lambda x: D * x) elif method == 'tracemin_chol' or method == 'tracemin_lu': # Convert A to CSC to suppress SparseEfficiencyWarning. A = csc_matrix(L, dtype=float, copy=True) # Force A to be nonsingular. Since A is the Laplacian matrix of a # connected graph, its rank deficiency is one, and thus one diagonal # element needs to modified. Changing to infinity forces a zero in the # corresponding element in the solution. i = (A.indptr[1:] - A.indptr[:-1]).argmax() A[i, i] = float('inf') if method == 'tracemin_chol': solver = _CholeskySolver(A) else: solver = _LUSolver(A) else: raise nx.NetworkXError('Unknown linear system solver: ' + method) # Initialize. Lnorm = abs(L).sum(axis=1).flatten().max() project(X) W = asmatrix(ndarray(X.shape, order='F')) while True: # Orthonormalize X. X = qr(X)[0] # Compute iteration matrix H. W[:, :] = L * X H = X.T * W sigma, Y = eigh(H, overwrite_a=True) # Compute the Ritz vectors. X *= Y # Test for convergence exploiting the fact that L * X == W * Y. res = dasum(W * asmatrix(Y)[:, 0] - sigma[0] * X[:, 0]) / Lnorm if res < tol: break # Compute X = L \ X / (X' * (L \ X)). # L \ X can have an arbitrary projection on the nullspace of L, # which will be eliminated. W[:, :] = solver.solve(X, tol) X = (inv(W.T * X) * W.T).T # Preserves Fortran storage order. project(X) return sigma, asarray(X)