7.5. QR factorization#

Square matrix A can be decomposed as A=QR where Q is an orthogonal matrix (QQ=QQ=I) and R is an upper triangular matrix. If A is invertible and R is positive, then the factorization is unique.

Furthermore, there are several methods for computing the QR decomposition such as the Gram–Schmidt process, and the Householder transformations, … Here, we use the Gram–Schmidt process.

Consider matrix A=[|||a1a2an|||] were ai are the columns of the full column rank matrix A. Now, define the projection as follows:

projua=u, au, uu

Therefore,

u1=a1,e1=u1u1,u2=a2proju1a2,e2=u2u2,u3=a3proju1a3proju2a3,e3=u3u3,uk=akj=1k1projujak,ek=ukuk.

Now, ai can be expressed as

{a1=e1,a1e1a2=e1,a2e1+e2,a2e2a3=e1,a3e1+e2,a3e2+e3,a3e3ak=j=1kej,akej

where ei,ai=ui. This can be written in matrix form:

A=QR
[a11a12a1na21a22a2na31a32a3nan1an2ann]=[|||e1e2en|||][e1,a1e1,a2e1,a3e1,an0e2,a2e2,a3e2,an00e3,a3e3,an00en,an].

See [Allaire, Kaber, Trabelsi, and Allaire, 2008, Khoury and Harder, 2016] for the full derivation of this algorithm.

import numpy as np
import pandas as pd

def myQR(A):
    '''
    Square matrix $A$ can be decomposed as A=QR
    where Q is an orthogonal matrix and
    R is an upper triangular matrix.

    Parameters
    ----------
    A : numpy array
        DESCRIPTION. Matrix A

    Returns
    -------
    Q : numpy array
        DESCRIPTION. Matrix Q from A = QR
    R : numpy array
        DESCRIPTION. Matrix R from A = QR

    '''
    n = A.shape[0]
    Q = np.zeros([n,n], dtype = float)
    R = np.zeros([n,n], dtype = float)
    A = A.astype(float)
    for j in range(n):
        T = A[:, j]
        for i in range(j):
            R[i, j] = np.dot(Q[:, i], A[:, j])
            T -= R[i, j] * Q[:, i]
        R[j, j] = np.sqrt(np.dot(T, T))
        Q[:, j] = T /R[j, j]
        del T
    return Q, R
function [Q, R] = myQR(A)
%{
Square matrix $A$ can be decomposed as A=QR
where Q is an orthogonal matrix and
R is an upper triangular matrix.

Parameters
----------
A : numpy array
    DESCRIPTION. Matrix A

Returns
-------
Q : numpy array
    DESCRIPTION. Matrix Q from A = QR
R : numpy array
    DESCRIPTION. Matrix R from A = QR

%}
n=length(A);
Q =zeros(n,n);
R = zeros(n,n);
for j=1:n
    T = A(:, j);
    for i=1:j
        R(i, j) = dot(Q(:, i), A(:, j));
        T = T - R(i, j) * Q(:, i);
    end
    R(j, j) = sqrt(dot(T, T));
    Q(:, j) = T /R(j, j);
end
import sys
sys.path.insert(0,'..')
import hd_tools as hd
BokehJS 3.1.1 successfully loaded.

Example: Apply QR decomposition on the following matrix and identify Q and R.

A=[7312381411412416].

Solution: We have,

import numpy as np
from hd_Matrix_Decomposition import myQR
from IPython.display import display, Latex
from sympy import init_session, init_printing, Matrix
A = np.array([ [7, 3, -1, 2], [3, 8, 1, -4], [-1, 1, 4, -1], [2, -4, -1, 6] ])
Q, R = myQR(A)
display(Latex(r'Q ='), Matrix(np.round(Q, 2)))
display(Latex(r'R ='), Matrix(np.round(R, 2)))
display(Latex(r'QR ='), Matrix(np.round(Q@R, 2)))
display(Latex(r'QQ^T ='), Matrix(np.round(Q@(Q.T), 2)))
display(Latex(r'Q^TQ ='), Matrix(np.round((Q.T)@Q, 2)))
Q=
[0.880.120.110.440.380.750.060.530.130.190.970.060.250.620.20.72]
R=
[7.944.541.261.8908.332.257.15003.520.690001.35]
QR=
[7.03.01.02.03.08.01.04.01.01.04.01.02.04.01.06.0]
QQT=
[1.000001.000001.000001.0]
QTQ=
[1.000001.000001.000001.0]
hd.matrix_decomp_fig(mats = [Q, R, np.round(Q@(Q.T), 1), np.round((Q.T)@Q, 1), np.round(np.matmul(Q, R), 2)],
                     labels = ['$Q$', '$R$', '$QQ^T$', '$Q^TQ$', '$QR$'],
                     nrows=2, ncols=3, figsize=(14, 10))
../_images/547fe38696f56261ed0e80f1e71547f8fa9a46bda52dbd6c149cb7acf79727e1.png

Note that we could get a similar results using function, np.linalg.qr.

import scipy.linalg as linalg
Q, R = linalg.qr(A)
display(Latex(r'Q ='), Matrix(np.round(Q, 2)))
display(Latex(r'R ='), Matrix(np.round(R, 2)))
display(Latex(r'QR ='), Matrix(np.round(Q@R, 2)))
display(Latex(r'QQ^T ='), Matrix(np.round(Q@(Q.T), 2)))
display(Latex(r'Q^TQ ='), Matrix(np.round((Q.T)@Q, 2)))
Q=
[0.880.120.110.440.380.750.060.530.130.190.970.060.250.620.20.72]
R=
[7.944.541.261.8908.332.257.15003.520.690001.35]
QR=
[7.03.01.02.03.08.01.04.01.01.04.01.02.04.01.06.0]
QQT=
[1.000001.000001.000001.0]
QTQ=
[1.000001.000001.000001.0]

7.5.1. Solving Linear systems using QR factorization#

We can solve the linear system Ax=b for x using QR factorization. To demonstrate this, we use the following example,

Example: Solve the following linear system using QR decomposition.

{7x1+3x2x3+2x4=183x1+8x2+x34x4=6x2x1+4x3x4=92x14x2x3+6x4=15

Solution:

Let A=[7312381411412416] and b=[186915]. Then, this linear system can be also expressed as follows,

Ax=(QR)x=Q(Rx)=b.

We have,

A = np.array([ [7, 3, -1, 2], [3, 8, 1, -4], [-1, 1, 4, -1], [2, -4, -1, 6] ])
b = np.array([[18],[ 6],[ 9],[15]])
Q, R = myQR(A)
display(Latex(r'Q ='), Matrix(np.round(Q, 2)))
display(Latex(r'R ='), Matrix(np.round(R, 2)))
Q=
[0.880.120.110.440.380.750.060.530.130.190.970.060.250.620.20.72]
R=
[7.944.541.261.8908.332.257.15003.520.690001.35]

Now we can solve the following linear systems instead

{Qy=b,Rx=y.
hd.matrix_decomp_fig(mats = [A, Q, R, np.round(Q@(Q.T), 1), np.round((Q.T)@Q, 1)],
                     labels = ['$A$', '$Q$', '$R$', '$QQ^T$', '$Q^TQ$'],
                     nrows=2, ncols=3, figsize=(14, 10))
../_images/d0a33e4fc92d0b9bc9d42fbb85219714282fe2930a93ab6dd2aed8f440f08094.png
# solving y
y = np.linalg.solve(Q, b)
display(Latex(r'y ='), Matrix(np.round(y, 2)))
y=
[20.795.1913.325.42]
# solving Ux=y for x
x = np.linalg.solve(R, y)
display(Latex(r'x ='), Matrix(np.round(x, 2)))
x=
[1.02.03.04.0]

Let’s now solve the linear system directly and compare the results.

x_new = linalg.solve(A, b)
display(Latex(r'x ='), Matrix(np.round(x_new, 2)))
x=
[1.02.03.04.0]