The QR (orthogonal triangular) decomposition is the most effective and widely used method to find all the eigenvalues of a general matrix. It decomposes the matrix into a normal orthogonal matrix Q and an upper triangular matrix R, so it is called the QR decomposition. There are two common methods of QR decomposition, one is based on Gram-Schmidt, and the other one is based on Householder reflectors.
QR decomposition based on Gram-Schmidt
Let say a matrix A=(a1a2a3) for example, then Ax=(a1a2a3)x=b. To find the Q and R for A, we need to orthogonalize and normalize every vector in A (* for the first vector a1, only normalization is required).
After the orthogonalization and normalization, we get Q=(g1g2g3) and R=⎝⎛∥a1∥00a2⊤g1∥a2∥0a3⊤g1a3⊤g2∥a3∥⎠⎞
One problem with Gram-Schmidt is that g1,g2,g3 won’t be exactly orthogonal due to error in the computations (round-off).
And as g3 depends on g1 and g2, the problem tends to get worse and worse. The vectors get less and less orthogonal in practice. Therefore, it’s better to use Householder reflectors when we apply QR decomposition.
For every reflector, it is orthogonal and doesn’t change length →H2=H⊤H=I
Now consider A as a 4∗3 matrix for example, then Qn(n=1,2,3), all are square matrix with size of 4∗4. We can find Qn one by one following the steps below.
Find Q1 that makes Q1A=⎝⎜⎜⎛x000xxxxxxxx⎠⎟⎟⎞:
v=a1+∥a1∥e1 (a1 is the first column in A), Q1=I−∥v∥222vv⊤
Find Q2 that makes Q2(Q1A)=⎝⎜⎜⎛x000xx00xxxx⎠⎟⎟⎞:
v=a2+∥a2∥e1 (a2 is last three elements of the second column in Q1A)
import math import argparse import numpy as np from typing import Union
# QR decomposition based on Gram-Schmidt def gram_schmidt(A): cols = A.shape[1] Q = np.copy(A) R = np.zeros((cols, cols)) forcolinrange(cols): for i inrange(col): k = np.sum(a[:, col] * Q[:, i]) / np.sum( np.square(Q[:, i]) ) Q[:, col] -= k*Q[:, i] Q[:, col] /= np.linalg.norm(Q[:, col]) for i inrange(cols): R[col, i] = Q[:, col].dot( A[:, i] ) return Q, R
# QR decomposition based on Householder reflectors def householder(alpha: float, x: np.ndarray) -> Union[np.ndarray, int]: s = math.pow(np.linalg.norm(x, ord=2), 2) v = x if s == 0: tau = 0 else: t = math.sqrt(alpha * alpha + s) v_one = alpha - t if alpha <= 0else -s / (alpha + t) tau = 2 * v_one * v_one / (s + v_one * v_one) v /= v_one return v, tau
def qr_decomposition(A: np.ndarray, m: int, n: int) -> Union[np.ndarray, np.ndarray]: H = [] R = A Q = A I = np.eye(m, m) for j inrange(0, n): # Apply Householder transformation. x = A[j + 1:m, j] v_householder, tau = householder(np.linalg.norm(x), x) v = np.zeros((1, m)) v[0, j] = 1 v[0, j + 1:m] = v_householder res = I - tau * v * np.transpose(v) R = np.matmul(res, R) H.append(res) return Q, R