def power_iteration(A, max_iter=1000): v = np.random.randn(A.shape[0]) vs = [v.copy()] for i in range(max_iter): v = A @ v v /= utils.l2_norm(v) vs.append(v.copy()) return vs
def rayleigh_quotient_iteration(A, mu, max_iter=1000): v = np.random.randn(A.shape[0]) vs = [v.copy()] for i in range(max_iter): v = solve(A - mu * np.eye(A.shape[0]), v) v /= utils.l2_norm(v) vs.append(v.copy()) mu = single.rayleigh_quotient(A, v) return vs
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 hessenberg(A, calc_q=False): """Reduce a square matrix to upper Hessenberg form using Householder reflections. If the input matrix is symmetric, the resulting Hessenberg form is reduced to tridiagonal form. Args: A: A square matrix of shape (M, M). calc_q (bool): Whether to explicitly compute the product of similarity transform Householder matrices. Returns: A: The upper Hessenberg form of the matrix of shape (M, M). Q: The product of Householder matrices of shape (M, M). Only returned if `calc_q=True`. """ assert utils.is_square(A), "[!] Matrix must be square." is_symm = utils.is_symmetric(A) A = np.array(A) M, _ = A.shape vs = [] for i in range(M - 2): a = A[i + 1:, i] c = utils.l2_norm(a) s = utils.sign(a[0]) e = utils.basis_vec(0, len(a), flat=True) v = a + s * c * e vs.append(v) # left transform for j in range(i, M): A[i + 1:, j] = A[i + 1:, j] - (2 * v.T @ A[i + 1:, j]) / (v.T @ v) * v # right transform for j in range(i if is_symm else 0, M): A[j, i + 1:M] = A[j, i + 1:M] - 2 * ((A[j, i + 1:M].T @ v) / (v.T @ v)) * v.T if calc_q: Q = np.eye(M) for i in range(M): for j, v in enumerate(reversed(vs)): Q[M - j - 2:, i] -= (2 * v.T @ Q[M - j - 2:, i]) / (v.T @ v) * v return A, Q return A
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
def householder(self, ret=True): """Computes QR using the Householder method. """ self.A = np.array(self.backup) M, N = self.A.shape K = min(M, N) vs = [] for i in range(K): # extract subdiagonal entries of column i a = self.A[i:, i] # construct reflection vector c = utils.l2_norm(a) s = utils.sign(a[0]) e = utils.basis_vec(0, len(a), flat=True) v = a + s*c*e vs.append(v) # annihlate subdiagonal entries of all columns to the right if not np.isclose(v.T @ v, 0): for j in range(i, K): self.A[i:, j] -= (2 * v.T @ self.A[i:, j]) / (v.T @ v) * v # construct Q implicitly self.Q = np.eye(M) for i in range(M): for j, v in enumerate(reversed(vs)): if not np.isclose(v.T @ v, 0): self.Q[K-j-1:, i] -= (2 * v.T @ self.Q[K-j-1:, i]) / (v.T @ v) * v self.R = self.A if self.reduce: self.Q = self.Q[:, :K] self.R = self.R[:K, :] if ret: return self.Q, self.R