def projected_iteration(A, k, max_iter=1000, sort=True): """Sequentially find the k eigenpairs of a symmetric matrix. Concretely, combines power iteration and deflation to find eigenpairs in order of decreasing magnitude. Args: A: a square symmetric array of shape (N, N). k (int): the number of eigenpairs to return. sort (bool): Whether to sort by decreasing eigenvalue magnitude. Returns: e, v: eigenvalues and eigenvectors. The eigenvectors are stacked column-wise. """ assert utils.is_symmetric(A), "[!] Matrix must be symmetric." assert k > 0 and k <= A.shape[0], "[!] k must be between 1 and {}.".format( A.shape[0]) eigvecs = np.zeros((A.shape[0], k)) eigvals = np.zeros(A.shape[0]) for i in range(k): v = np.random.randn(A.shape[0]) for _ in range(max_iter): # project out computed eigenvectors proj_sum = np.zeros_like(v) for j in range(i): proj_sum += utils.projection(v, eigvecs[:, j]) v -= proj_sum v_new = A @ v v_new = utils.normalize(v_new) if np.all(np.abs(v_new - v) < 1e-8): break v = v_new e = single.rayleigh_quotient(A, v) # store eigenpair eigvecs[:, i] = np.array(v) eigvals[i] = e # sort by largest absolute eigenvalue if sort: idx = np.abs(eigvals).argsort()[::-1] eigvecs = eigvecs[:, idx] eigvals = eigvals[idx] return eigvals, eigvecs
def gram_schmidt_modified(self): """Computes QR using the modified Gram-Schmidt method. More numerically stable than the naive Gram-Schmidt method, but there should be no reason to use this over the Householder method. """ self.A = np.array(self.backup) M, N = self.A.shape self.R = np.array(self.A) for i in range(N): self.A[:, i] /= utils.l2_norm(self.A[:, i]) for j in range(i+1, N): self.A[:, j] -= utils.projection(self.A[:, j], self.A[:, i]) self.Q = self.A self.R = self.Q.T @ self.R return self.Q, self.R
def gram_schmidt(self): """Computes QR using the Gram-Schmidt method. Suffers from numerical instabilities when 2 vectors are nearly orthogonal which may cause loss of orthogonality between the columns of Q. Since we compute a unique QR decomposition, we force the diagonal elements of R to be positive. """ self.A = np.array(self.backup) M, N = self.A.shape self.R = np.array(self.A) self.A[:, 0] /= utils.l2_norm(self.A[:, 0]) for i in range(1, N): for j in range(i): self.A[:, i] -= utils.projection(self.A[:, i], self.A[:, j]) utils.normalize(self.A[:, i], inplace=True) self.Q = self.A self.R = np.dot(self.Q.T, self.R) return self.Q, self.R