7.2. Matrix Factorization#
7.2.1. LU decomposition#
The \(LU\) decomposition decomposes a matrix into a lower triangular matrix \(L\) and an upper triangular matrix \(U\). Its main application is solving linear systems. Therefore, solving the linear system \(Ax = b\) where \(A\) is a \(n\times n\) matrix and \(x\) and \(b\) are vectors of length \(n\) can be as represented as follows.
This is equivalent to
Solving the last two linear systems is equivalent to the forward elimination and back substitution, respectively.
Theorem
Let \(A = (a_{ij})\) be an \(n\times n\) matrix, where \(1\leq i,j \leq n\), and let all of its diagonal submatrices of order \(k\) be nonsingular. We can assert the existence of matrices \(L\) and \(U\) satisfying the following conditions: \(L\) is a lower triangular matrix with ones along its diagonal, \(U\) is an upper triangular matrix, and \(A = LU\) [Allaire, Kaber, Trabelsi, and Allaire, 2008, Atkinson, 2004, Khoury and Harder, 2016].
Let \(A\) be a square matrix. Then, \(A\) can be decomposed into \(L\) and \(U\) as follows,
Observe that \(L\) is lower triangular and \(U\) upper triangular. Thus,
Solving this system:
Column 1. Set \(j = 1\) and for \(1\leq i\leq n\), we have,
Column j. For a fixed \(j\) and for \(1\leq i\leq n\), we have,
Thus, \(u_{ij}\) and \(l_{ij}\) can be identified as follows
See [1] for the full derivation of this algorithm.
import numpy as np
def myLU(A):
'''
Assuming that no row interchanges are required, this function
calculates a unit lower triangular matrix L, and an upper
triangular matrix U such that LU = A
Parameters
----------
A : numpy array
DESCRIPTION. Matrix A
Returns
-------
L : numpy array
DESCRIPTION. Matrix L from A = LU
U : numpy array
DESCRIPTION. Matrix U from A = LU
'''
n = A.shape[1]
# Consider U as a copy matrix A
U = A.copy().astype(float)
for j in range(0, n-1):
U[j+1:n,j] = U[j+1:n,j]/U[j,j];
for i in range(j+1, n):
U[i,j+1:n] = U[i,j+1:n]-U[i,j]*U[j,j+1:n]
L = np.tril(U,-1)+ np.eye(n, dtype=float)
U = np.triu(U)
return L, U
function [L,U] = myLU(A)
%{
Assuming that no row interchanges are required, this function
calculates a unit lower triangular matrix L, and an upper
triangular matrix U such that LU = A
Parameters
----------
A : array
DESCRIPTION. Matrix A
Returns
-------
L : array
DESCRIPTION. Matrix L from A = LU
U : array
DESCRIPTION. Matrix U from A = LU
%}
n=length(A);
U = A;
for j=1:n-1
U(j+1:n,j)= U(j+1:n,j)/U(j,j);
for i=j+1:n
U(i,j+1:n)=U(i,j+1:n)-U(i,j)*U(j,j+1:n);
end
end
L = tril(U,-1)+eye(n,n);
U = triu(U);
end
Example: Apply \(LU\) decomposition on the following matrix and identify \(L\) and \(U\).
Solution: We have,
import numpy as np
from hd_Matrix_Decomposition import myLU
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] ])
L, U = myLU(A)
display(Latex(r'L ='), Matrix(np.round(L, 2)))
display(Latex(r'U ='), Matrix(np.round(U, 2)))
display(Latex(r'LU ='), Matrix(L@U))
hd.matrix_decomp_fig(mats = [L, U, L@U], labels = ['L', 'U', 'LU'])
Note that we could get similar results using Scipy LU function, scipy.linalg.lu
.
import scipy.linalg as linalg
P, L, U = linalg.lu(A)
display(Latex(r'L ='), Matrix(np.round(L, 2)))
display(Latex(r'U ='), Matrix(np.round(U, 2)))
Note that in the above algorithm \(P\) is a permutation matrix that can perform a partial and full pivoting.
7.2.2. Solving Linear systems using LU decomposition#
We can solve the linear system \(Ax=b\) for \(x\) using \(LU\) decomposition. To demonstrate this, we use the following example,
Example: Solve the following linear system using \(LU\) decomposition.
Solution:
Let \(A=\left[\begin{array}{cccc}2 & 1 & 1 & 0\\ 1 & -2 & 0 & 1\\ 0 & 1 & 3 & 0\\ -1 & 0 & 0 & 4 \end{array}\right]\) and \(b=\left[\begin{array}{c} 7\\ 1\\ 11\\ 15 \end{array}\right]\).
Then, this linear system can be also expressed as follows,
We have,
A = np.array([[2,1,1,0],[1,-2,0,1],[0,1,3,0],[-1,0,0,4]])
b = np.array([[7],[1],[11],[15]])
L, U = myLU(A)
display(Latex(r'L ='), Matrix(np.round(L, 2)))
display(Latex(r'U ='), Matrix(np.round(U, 2)))
hd.matrix_decomp_fig(mats = [A, L, U], labels = ['A', 'L', 'U'])
Now, we can solve the following linear systems instead
# solving Ly=b for y
y = linalg.solve(L, b)
display(Latex(r'y ='), Matrix(np.round(y, 2)))
# solving Ux=y for x
x = linalg.solve(U, y)
display(Latex(r'x ='), Matrix(np.round(x, 2)))
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)))