The LU decomposition is a factoring of an arbitrary square matrix into an upper-triangular and lower-triangular pair of factors. This factoring is very useful for solving linear equations, since the inversion of such upper/lower matrices is trivial. We select a standard form for the lower triangular matrix; ones on its diagonal. For example
LU decomposition can be done very efficiently, it is significantly faster and easier to program than QR decomposition, and it is technically much simpler.
Start with
Dotting the first row of into each column of we see
Now dot the second row of into each column of and solve
Solve the first equation, so is known, now solve the remaining equations for :
so the second rows of both and are gotten from and the first row of
You can see that if , the process fails at the very first stage, and so we usually perform a permutation or pivot that rearranges the rows of of so that has the property that for all . In other words the diagonal entries of are larger in absolute value than any entries below them in the same column. We apply the LU algorithm to the permuted matrix
# in R
Pivot <- function(M){
N <- ncol(M)
P <- matrix(rep(0,N^2),nrow=N,ncol=N)
for (k in 1:N){P[k,k]<- 1.0}
Mc <- M
for (k in 1:(N-1)){
largest <- which.max(abs(Mc[k:N,k]))
if(largest>1){
Mc[c(k,largest+k-1),] <- Mc[c(largest+k-1,k),]
P[c(k,largest+k-1),] <- P[c(largest+k-1,k),]}
}
return (list("perm"=P,"matrix"=Mc))
}
Finally dot the last row of into ;
Solve the first, now is known, use this to solve the second
so now known, and the third equation contains one unknown
Some computations (for example the first row of ) can be saved by copying into immediately and creating the rest of right on top of , destroying in the process (construction in situ)
# this R code returns L,U,P
U <- function(M){
N <- ncol(M)
L <- matrix(rep(0,N^2),nrow=N,ncol=N)
U <- matrix(rep(0,N^2),nrow=N,ncol=N)
Q <- Pivot(M)
for (i in 1:N){
for (k in i:N){
sm <- -sum(L[i,1:i]*U[1:i,k])
U[i,k] <- Q$matrix[i,k]+sm}
for (k in i:N){
if(i==k){L[i,i] <- 1.0}
else{sm <- -sum(L[k,1:i]*U[1:i,i])
L[k,i]<- (Q$matrix[k,i]+sm)/U[i,i]}
}
}
return (list("l"=L,"u"=U,"p"=t(Q$perm)))
}
Inverting triangular linear systems
Back substitution
(1)
so for the row element
Forward substitution
(2)
Fsub <- function(L,b){
N <- ncol(L)
y <- matrix(rep(0,N),nrow=N,ncol=1)
for (j in 1:N){
y[j]<- (b[j]-sum(L[j,1:(j-1)]*y[1:j-1]))/L[j,j]}
return(y)
}
To write a solver for ,
apply the LU decomposition
solve for , then
solve for .
You probably can’t write any linear algebra code that can compete with GEMM or the solvers in LAPACK and BLAS when it comes to speed, but the point of “rolling your own” is to learn the algorithms, and to create self-contained codes that don’t require numerous calls to external libraries.