LU Decomposition#

import numpy as np
import scipy.linalg as la
import matplotlib.pyplot as plt

Implementation#

The function scipy.linalg.lu computes the LU decomposition with partial pivoting which is different than the LU decomposition we consider. So let’s write our own Python function called lu to compute the LU decomposition. The function lu returns \(L=I\) and \(U=A\) with a warning message if the LU decomposition of \(A\) does not exist.

def lu(A):
    "Compute LU decomposition of matrix A."
    m,n = A.shape
    L = np.eye(m)
    U = A.copy()
    # Keep track of the row index of the pivot entry
    pivot = 0
    for j in range(0,n):
        # Check if the pivot entry is 0
        if U[pivot,j] == 0:
            if np.any(U[pivot+1:,j]):
                # LU decomposition does not exist if entries below 0 pivot are nonzero
                print("LU decomposition for A does not exist.")
                return np.eye(m),A
            else:
                # All entries below 0 pivot are 0 therefore continue to next column
                # Row index of pivot remains the same
                continue
        # Use nonzero pivot entry to create 0 in each entry below
        for i in range(pivot+1,m):
            c = -U[i,j]/U[pivot,j]
            U[i,:] = c*U[pivot,:] + U[i,:]
            L[i,pivot] = -c
        # Move pivot to the next row
        pivot += 1
    return L,U

Let’s verify our function works:

A = np.array([[2.,1.,1.],[2.,0.,2.],[4.,3.,4.]])
A
array([[2., 1., 1.],
       [2., 0., 2.],
       [4., 3., 4.]])
L,U = lu(A)
L
array([[ 1.,  0.,  0.],
       [ 1.,  1.,  0.],
       [ 2., -1.,  1.]])
U
array([[ 2.,  1.,  1.],
       [ 0., -1.,  1.],
       [ 0.,  0.,  3.]])
L@U
array([[2., 1., 1.],
       [2., 0., 2.],
       [4., 3., 4.]])

Success! Let’s try an example where we know the LU decomposition does not exist:

A = np.array([[0.,1.],[1.,0.]])
L,U = lu(A)
LU decomposition for A does not exist.
L
array([[1., 0.],
       [0., 1.]])
U
array([[0., 1.],
       [1., 0.]])

Now let’s try an example with some zeros along the diagonal of the \(U\):

A = np.array([[2,2,-2,4,2],[1,-1,0,2,1],[3,1,-2,6,3],[1,3,-2,2,1]])
A
array([[ 2,  2, -2,  4,  2],
       [ 1, -1,  0,  2,  1],
       [ 3,  1, -2,  6,  3],
       [ 1,  3, -2,  2,  1]])
L,U = lu(A)
L
array([[ 1. ,  0. ,  0. ,  0. ],
       [ 0.5,  1. ,  0. ,  0. ],
       [ 1.5,  1. ,  1. ,  0. ],
       [ 0.5, -1. ,  0. ,  1. ]])
U
array([[ 2,  2, -2,  4,  2],
       [ 0, -2,  1,  0,  0],
       [ 0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0]])
L@U
array([[ 2.,  2., -2.,  4.,  2.],
       [ 1., -1.,  0.,  2.,  1.],
       [ 3.,  1., -2.,  6.,  3.],
       [ 1.,  3., -2.,  2.,  1.]])

Success! Let’s do onemore example:

A = np.array([[1,1,-1,2,1,3],[2,5,-5,5,2,10],[-1,2,-2,-1,4,2],[3,3,-3,6,-7,5],[-4,20,-20,0,1,15]])
A
array([[  1,   1,  -1,   2,   1,   3],
       [  2,   5,  -5,   5,   2,  10],
       [ -1,   2,  -2,  -1,   4,   2],
       [  3,   3,  -3,   6,  -7,   5],
       [ -4,  20, -20,   0,   1,  15]])
L,U = lu(A)
L
array([[ 1.,  0.,  0.,  0.,  0.],
       [ 2.,  1.,  0.,  0.,  0.],
       [-1.,  1.,  1.,  0.,  0.],
       [ 3., -0., -2.,  1.,  0.],
       [-4.,  8.,  1.,  3.,  1.]])
U
array([[ 1,  1, -1,  2,  1,  3],
       [ 0,  3, -3,  1,  0,  4],
       [ 0,  0,  0,  0,  5,  1],
       [ 0,  0,  0,  0,  0, -2],
       [ 0,  0,  0,  0,  0,  0]])
L@U
array([[  1.,   1.,  -1.,   2.,   1.,   3.],
       [  2.,   5.,  -5.,   5.,   2.,  10.],
       [ -1.,   2.,  -2.,  -1.,   4.,   2.],
       [  3.,   3.,  -3.,   6.,  -7.,   5.],
       [ -4.,  20., -20.,   0.,   1.,  15.]])

Example#

Consider the \(N\) by \(N\) matrix

\[\begin{split} A = \left[ \begin{array}{rrrrr} 2 & -1 & & & \\ -1 & 2 & -1 & & \\ & \ddots & \ddots & \ddots & \\ & & -1 & 2 & -1 \\ & & & -1 & 2 \\ \end{array} \right] \end{split}\]

Compute the solution of the system \(A \mathbf{x} = \mathbf{b}\) for

\[\begin{split} \mathbf{b} = \left[ \begin{array}{r} 1 \\ \vdots \\ 1 \end{array} \right] \end{split}\]

by computing the LU decomposition and using the function scipy.linalg.solve_triangular.

N = 20
A1 = 2*np.eye(N)
A2 = np.diag(-np.ones(N-1),1)
A = A1 + A2 + A2.T
plt.imshow(A,cmap='RdBu'), plt.clim([-2,2])
plt.colorbar()
plt.show()
../_images/cb556545b58c55b38c11df6761037a18dc363e4c737f749bde38de517047ece8.png

Compute the LU decomposition \(A=LU\) and visualize the matrices \(L\) and \(U\):

L,U = lu(A)
plt.imshow(L,cmap='RdBu'), plt.clim([-2,2]), plt.colorbar()
plt.show()
../_images/3afad486877a3fd679b5bc595cd3af1a4abb0bc18cf9a438d56a21df418eaaa2.png
plt.imshow(U,cmap='RdBu'), plt.clim([-2,2]), plt.colorbar()
plt.show()
../_images/b5a8e1941f817cbe32814222b5bedcc0f1f6efdb635f7f71f1f4a524e1899d0f.png

Solve the systems \(L \boldsymbol{y} = \boldsymbol{b}\) and \(U \boldsymbol{x} = \boldsymbol{y}\), then plot the solution \(\boldsymbol{x}\):

b = np.ones([N,1])
y = la.solve_triangular(L,b,lower=True)
x = la.solve_triangular(U,y,lower=False)
plt.plot(x,'b.'), plt.grid(True), plt.ylim([0,60])
plt.show()
../_images/76c194dc56dd9ee2d1bb55c065677022dcef715211cfad57aeff699d7f37ef0b.png