LU Factorization
Routine Name : LU Factorization
Author : Kyle Hovey
Language : C++
Description/Purpose :
This algorithm will solve a linear system such that the linear operator is non-singular and square.
Input :
A square matrix \( A \) and a column vector \( b \) compatible with it.
Output :
The solution vector \( x \) such that \( Ax = b \).
Usage/Example :
Here I pre-generate a dummy vector, then operate on it with a tri-diagonal matrix \( A \) to get my \( b \) vector. This means that the solution should be very close to the dummy vector I supplied. As you can see from the output, it works well.
#include "../../matrix/src/matrix/matrix.h"
#include <iostream>
#include <vector>
using Mtx = Matrix :: Matrix < double > ;
int main () {
Mtx A ({
{ - 2 , 1 , 0 , 0 },
{ 1 , - 2 , 1 , 0 },
{ 0 , 1 , - 2 , 1 },
{ 0 , 0 , 1 , - 2 }
});
Mtx _x ({ { 1 },{ 2 },{ 3 },{ 4 } });
auto b = A * _x ;
auto x = Mtx :: solve ( A , b , Matrix :: Solve :: LU );
std :: cout << x << std :: endl ;
return EXIT_SUCCESS ;
}
Output:
1
2
3
4
Implementation/Code:
LU factorization requires that you do Gaussian Elimination, which requires \( O(n^3) \) operations. The only difference here is that I keep track of the operations required and assemble the \( L \) and \( U \) matrix (as well as the permutation matrix \( P \) ) so that they may be returned at the end. Because of this equivalence, this algorithm runs in:
\[ O(n^3) \]
template < typename T >
Matrix < T > Matrix < T >:: solve (
const Matrix < T >& A ,
const Matrix < T >& b
) {
// Factor A into components
auto [ P , L , U ] = A . LUFactorize ();
// Permute result vector
auto res = P * b ;
/* ===== Solve Ly = res ===== */
Matrix < T > y ( m , 1 );
// For each row (top to bottom)
for ( uint row = 0 ; row < m ; ++ row ) {
// Sum up the equation to the left (already solved)
T leftSum = 0 ;
for ( int col = row - 1 ; col >= 0 ; -- col ) {
leftSum += L . getVal ( row , col ) * y . getVal ( col , 0 );
}
// Solve for the value at that row
y . setVal ( row , 0 , ( res . getVal ( row , 0 ) - leftSum ) / L . getVal ( row , row ));
}
/* ===== Solve Ux = y ===== */
Matrix < T > x ( m , 1 );
// For each row (bottom to top)
for ( int row = m - 1 ; row >= 0 ; -- row ) {
// Sum up the equation to the right (already solved)
T rightSum = 0 ;
for ( uint col = row + 1 ; col < m ; ++ col ) {
rightSum += U . getVal ( row , col ) * x . getVal ( col , 0 );
}
// Solve for the value at that row
x . setVal ( row , 0 , ( y . getVal ( row , 0 ) - rightSum ) / U . getVal ( row , row ));
}
return x ;
}
Where LU factorization is done by:
template < typename T >
std :: tuple < Matrix < T > , Matrix < T > , Matrix < T >> Matrix < T >:: LUFactorize () const {
if ( ! this -> isSquare ()) {
throw std :: out_of_range ( "Cannot LU factorize non-square matrix." );
}
// Initialize outputs
const auto m = std :: get < 0 > ( this -> getSize ());
auto P = Matrix < T >:: identity ( m );
auto L = Matrix < T >:: identity ( m );
auto U = * this ;
// For each column
for ( uint col = 0 ; col < m ; ++ col ) {
// Find maximum absolute value in rows below row i
T max = 0 ;
uint swp = col ;
for ( uint row = col ; row < m ; ++ row ) {
auto val = U . getVal ( row , col );
auto sqVal = val * val ;
if ( sqVal > max ) {
max = sqVal ;
swp = row ;
}
}
if ( swp != col ) {
// Swap rows with max value to make it a pivot
U . swapRows ( swp , col );
// Store permutation in P matrix
P . swapRows ( swp , col );
} else if ( U . getVal ( swp , col ) == 0 ) {
throw std :: domain_error ( "Matrix is singular. Cannot LU factor." );
}
// Eliminate all values below pivot
const auto diag = U . getVal ( col , col );
for ( uint row = col + 1 ; row < m ; ++ row ) {
const auto mult = - ( U . getVal ( row , col ) / diag );
// Eliminate value
U . addRow ( col , row , mult );
// Add elementary matrix of operation to L
L . setVal ( row , col , - mult );
}
}
return std :: tuple < Matrix < T > , Matrix < T > , Matrix < T >> ( P , L , U );
}