Code
using LinearAlgebra
= tril( rand(1:9,3,3) ) L
3×3 Matrix{Int64}:
6 0 0
7 3 0
7 2 5
using LinearAlgebra
= tril( rand(1:9,3,3) ) L
3×3 Matrix{Int64}:
6 0 0
7 3 0
7 2 5
= triu( rand(1:9, 3,3) ) U
3×3 Matrix{Int64}:
4 6 9
0 6 6
0 0 9
*U L
3×3 Matrix{Int64}:
24 36 54
28 60 81
28 54 120
One view of matrix multiplication is as a sum of rank-one matrices formed as outer products of corresponding columns of \(\mathbf{A}\) and rows of \(\mathbf{B}\), \[\mathbf{C} = \sum_{k = 1}^n \mathbf{A}_{:k} \mathbf{B}_{k:}.\]
:,1]*U[1,:]' L[
3×3 Matrix{Int64}:
24 36 54
28 42 63
28 42 63
Only the first outer product contributes to the first row and column of the product \(\mathbf{L}\mathbf{U}\).
:,2]*U[2,:]' L[
3×3 Matrix{Int64}:
0 0 0
0 18 18
0 12 12
:,3]*U[3,:]' L[
3×3 Matrix{Int64}:
0 0 0
0 0 0
0 0 45
The triangular zero structures of these matrices create rows and columns of zeros in the inner product.
When factorizing \(n \times n\) matrix \(\mathbf{A}\) into a triangular product \(\mathbf{L}\mathbf{U}\), note that \(\mathbf{L}\) and \(\mathbf{U}\) have \(n^2 + n > n^2\) entries, so we may choose the diagonal entries of \(\mathbf{L}\) to be one (a unit lower triangular matrix).
= [
A₁ 2 0 4 3
-4 5 -7 -10
1 15 2 -4.5
-2 0 2 -13
];= diagm(ones(4))
L = zeros(4,4); U
Since \(L_{11} = 1\), the first row of \(\mathbf{U}\) is the first row of \(\mathbf{A}\).
1,:] = A₁[1,:]
U[ U
4×4 Matrix{Float64}:
2.0 0.0 4.0 3.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
The rest of the first column of \(\mathbf{L}\) can be computed from the first column of \(\mathbf{A}\) since only the first outer product contributes to this portion of \(\mathbf{A}\).
:,1] = A₁[:,1]/U[1,1]
L[ L
4×4 Matrix{Float64}:
1.0 0.0 0.0 0.0
-2.0 1.0 0.0 0.0
0.5 0.0 1.0 0.0
-1.0 0.0 0.0 1.0
= A₁ - L[:,1]*U[1,:]' A₂
4×4 Matrix{Float64}:
0.0 0.0 0.0 0.0
0.0 5.0 1.0 -4.0
0.0 15.0 0.0 -6.0
0.0 0.0 6.0 -10.0
Using the same logic as before, we may set the second rows and columns of \(\mathbf{U}\) and \(\mathbf{L}\) using \(\mathbf{A}_2\).
2,:] = A₂[2,:]
U[:,2] = A₂[:,2]/U[2,2]; L[
= A₂ - L[:,2]*U[2,:]' A₃
4×4 Matrix{Float64}:
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 -3.0 6.0
0.0 0.0 6.0 -10.0
3,:] = A₃[3,:]
U[:,3] = A₃[:,3]/U[3,3]
L[= A₃ - L[:,3]*U[3,:]' A₄
4×4 Matrix{Float64}:
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 2.0
4,4] = A₄[4,4]
U[
- L*U A₁
4×4 Matrix{Float64}:
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
"""
lufact(A)
Compute the LU factorization of square matrix `A`, returning the factors.
"""
function lufact(A)
= size(A,1)
n = diagm(ones(n)) #ones on diagonal, zeros elsewhere
L = zeros(n,n)
U = float(copy(A))
Ak
#Reduction by outer products
for k in 1:n-1
:] = Ak[k,:]
U[k,:,k] = Ak[:,k]/U[k,k]
L[-= L[:,k]*U[k,:]'
Ak end
= Ak[n,n]
U[n,n] return LowerTriangular(L),UpperTriangular(U)
end
lufact
We can solve \(\mathbf{A}\mathbf{x} = \mathbf{b}\) with three steps:
Let \(f(n)\) and \(g(n)\) be positive-valued functions. We say \(f(n) = \mathcal{O}(g(n))\) as \(n \rightarrow \infty\) if \(f(n)/g(n)\) is bounded above as \(n \rightarrow \infty\).
lu(randn(3,3)); #throwaway to force compilation of the lu function code_llvm
= 400:400:4000
n = []
t for n in n
= randn(n,n)
A = @elapsed for j in 1:12; lu(A); end
time push!(t,time)
end
using Plots
scatter(n,t,label="data",legend=:topleft,
=(:log10,"n"), yaxis=(:log10,"elapsed time"))
xaxisplot!(n,t[end]*(n/n[end]).^3,l=:dash,label="O(n^3)")
WARNING: using Plots.rotate! in module Main conflicts with an existing identifier.
= [2 0 4 3; -4 5 -7 -10; 1 15 2 -4.5; -2 0 2 -13];
A = lufact(A);
L,U L
4×4 LowerTriangular{Float64, Matrix{Float64}}:
1.0 ⋅ ⋅ ⋅
-2.0 1.0 ⋅ ⋅
0.5 3.0 1.0 ⋅
-1.0 0.0 -2.0 1.0
2,4],:] = A[[4,2],:]
A[[= lufact(A);
L,U L
4×4 LowerTriangular{Float64, Matrix{Float64}}:
1.0 ⋅ ⋅ ⋅
-1.0 NaN ⋅ ⋅
0.5 Inf NaN ⋅
-2.0 Inf NaN 1.0
After swapping the 2nd and 4th rows of \(\mathbf{A}\), the matrix is still nonsingular, but now the LU factorization fails due to dividing by a diagonal element of \(\mathbf{U}\) that is zero. The diagonal element of \(\mathbf{U}\) by which we divide is called the pivot element of its column.
In order to avoid a zero pivot in the factorization process, we use a technique known as row pivoting: when performing elimination in the \(j\)th column, choose as the pivot the element in column \(j\) that is largest in absolute value.
= [2 0 4 3; -2 0 2 -13; 1 15 2 -4.5; -4 5 -7 -10] A₁
4×4 Matrix{Float64}:
2.0 0.0 4.0 3.0
-2.0 0.0 2.0 -13.0
1.0 15.0 2.0 -4.5
-4.0 5.0 -7.0 -10.0
= argmax( abs.(A₁[:,1]) ) i
4
= zeros(4,4),zeros(4,4)
L,U 1,:] = A₁[i,:]
U[:,1]= A₁[:,1]/U[1,1]
L[= A₁ - L[:,1]*U[1,:]' A₂
4×4 Matrix{Float64}:
0.0 2.5 0.5 -2.0
0.0 -2.5 5.5 -8.0
0.0 16.25 0.25 -7.0
0.0 0.0 0.0 0.0
@show i = argmax( abs.(A₂[:,2]) )
2,:] = A₂[i,:]
U[:,2] = A₂[:,2]/U[2,2]
L[= A₂ - L[:,2]*U[2,:]' A₃
i = argmax(abs.(A₂[:, 2])) = 3
4×4 Matrix{Float64}:
0.0 0.0 0.461538 -0.923077
0.0 0.0 5.53846 -9.07692
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
@show i = argmax( abs.(A₃[:,3]) )
3,:] = A₃[i,:]
U[:,3] = A₃[:,3]/U[3,3]
L[= A₃ - L[:,3]*U[3,:]' A₄
i = argmax(abs.(A₃[:, 3])) = 2
4×4 Matrix{Float64}:
0.0 0.0 0.0 -0.166667
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
@show i = argmax( abs.(A₄[:,4]) )
4,:] = A₄[i,:]
U[:,4] = A₄[:,4]/U[4,4]; L[
i = argmax(abs.(A₄[:, 4])) = 1
- L*U A₁
4×4 Matrix{Float64}:
0.0 -1.38778e-16 0.0 0.0
0.0 1.38778e-16 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
U
4×4 Matrix{Float64}:
-4.0 5.0 -7.0 -10.0
0.0 16.25 0.25 -7.0
0.0 0.0 5.53846 -9.07692
0.0 0.0 0.0 -0.166667
L
4×4 Matrix{Float64}:
-0.5 0.153846 0.0833333 1.0
0.5 -0.153846 1.0 -0.0
-0.25 1.0 0.0 -0.0
1.0 0.0 0.0 -0.0
\(\mathbf{L}\) doesn’t have the required lower triangular structure!
Linear systems with uninvertible matrices have either no solution or infinitely many. We need techniques other than LU factorization to deal with such systems.
The \(\mathbf{L}\) matrix calculated in the last example is not lower-triangular, but is if we simply reverse the rows. In fact, it will be true in general that the \(\mathbf{L}\) calculated will be lower-triangular after a permutation of the rows.
We can think of the algorithm as behaving exactly the original LU factorization technique (without row pivoting) if we reorder the rows of the original matrix in order of the row pivot indices identified in each of the steps. (For our example, this would be putting row 4 at the top, then row 3, then row 2, and then row1.)
= A[ [4,3,2,1], : ]
B = lufact(B); L, U
U
4×4 UpperTriangular{Float64, Matrix{Float64}}:
-4.0 5.0 -7.0 -10.0
⋅ 16.25 0.25 -7.0
⋅ ⋅ 5.53846 -9.07692
⋅ ⋅ ⋅ -0.166667
L
4×4 LowerTriangular{Float64, Matrix{Float64}}:
1.0 ⋅ ⋅ ⋅
-0.25 1.0 ⋅ ⋅
0.5 -0.153846 1.0 ⋅
-0.5 0.153846 0.0833333 1.0
\(\mathbf{L}\) is the same matrix as before, but with the rows permuted to reverse order!
"""
plufact(A)
Compute the PLU factorization of square matrix `A`, returning factors and row permutation.
"""
function plufact(A)
= size(A,1)
n = zeros(n,n),zeros(n,n),fill(0,n),float(copy(A))
L,U,p,Ak
#Reduction by outer products
for k in 1:n-1
= argmax(abs.(Ak[:,k]))
p[k] :] = Ak[p[k],:]
U[k,:,k] = Ak[:,k]/U[k,k]
L[-= L[:,k]*U[k,:]'
Ak end
= argmax(abs.(Ak[:,n]))
p[n] = Ak[p[n],n]
U[n,n] :,n] = Ak[:,n]/U[n,n]
L[return LowerTriangular(L[p,:]),U,p
end
plufact
= rand(1:20,4,4)
A = plufact(A)
L,U,p :] - L*U A[p,
4×4 Matrix{Float64}:
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 -8.88178e-16 3.55271e-15
using FundamentalsNumericalComputation;
= rand(4)
b = FNC.forwardsub(L,b[p])
z = FNC.backsub(U,z) x
4-element Vector{Float64}:
0.02074712679808451
0.029105191666686472
-0.0019461283650047195
-0.019593097289861985
- A*x b
4-element Vector{Float64}:
1.1102230246251565e-16
1.1102230246251565e-16
0.0
5.551115123125783e-17
The reason for choosing the largest magnitude pivot during row-pivoting is numerical stability!
Consider the example \[\mathbf{A} = \begin{bmatrix} -\epsilon & 1 \\ 1 & -1 \end{bmatrix}.\]
= 1e-12
ϵ = [-ϵ 1; 1 -1]
A = A*[1,1]; b
= lufact(A)
L,U = FNC.backsub( U, FNC.forwardsub(L,b) ) x
2-element Vector{Float64}:
0.9999778782798785
1.0
We have only five digits of accuracy. This gets even worse if \(\epsilon\) is smaller!
= 1e-20
ϵ = [-ϵ 1; 1 -1]
A = A*[1,1]
b = lufact(A)
L,U = FNC.backsub( U, FNC.forwardsub(L,b) ) x
2-element Vector{Float64}:
-0.0
1.0
This is not due to ill-conditioning of the problem – a solution with PLU factorization works perfectly! (PLU factorization is used under the hood in the \
operator.)
\b A
2-element Vector{Float64}:
1.0
1.0