/**
* LU Decomposition (Doolittle decomposition) with pivoting via permutation matrix
*
* A matrix has an LU-factorization if it can be expressed as the product of a
* lower-triangular matrix L and an upper-triangular matrix U. If A is a nonsingular
* matrix, then we can find a permutation matrix P so that PA will have an LU decomposition:
* PA = LU
*
* https://en.wikipedia.org/wiki/LU_decomposition
* https://en.wikipedia.org/wiki/LU_decomposition#Doolittle_algorithm
*
* L: Lower triangular matrix--all entries above the main diagonal are zero.
* The main diagonal will be all ones.
* U: Upper tirangular matrix--all entries below the main diagonal are zero.
* P: Permutation matrix--Identity matrix with possible rows interchanged.
*
* Example:
* [1 3 5]
* A = [2 4 7]
* [1 1 0]
*
* Create permutation matrix P:
* [0 1 0]
* P = [1 0 1]
* [0 0 1]
*
* Pivot A to be PA:
* [0 1 0][1 3 5] [2 4 7]
* PA = [1 0 1][2 4 7] = [1 3 5]
* [0 0 1][1 1 0] [1 1 0]
*
* Calculate L and U
*
* [1 0 0] [2 4 7]
* L = [0.5 1 0] U = [0 1 1.5]
* [0.5 -1 1] [0 0 -2]
*
* @return array [
* L: Lower triangular matrix
* U: Upper triangular matrix
* P: Permutation matrix
* A: Original square matrix
* ]
*
* @throws MatrixException if matrix is not square
*/
public function LUDecomposition() : array
{
if (!$this->isSquare()) {
throw new Exception\MatrixException('LU decomposition only works on square matrices');
}
$n = $this->n;
// Initialize L as diagonal ones matrix, and U as zero matrix
$L = (new DiagonalMatrix(array_fill(0, $n, 1)))->getMatrix();
$U = MatrixFactory::zero($n, $n)->getMatrix();
// Create permutation matrix P and pivoted PA
$P = $this->pivotize();
$PA = $P->multiply($this);
// Fill out L and U
for ($i = 0; $i < $n; $i++) {
// Calculate Uⱼᵢ
for ($j = 0; $j <= $i; $j++) {
$sum = 0;
for ($k = 0; $k < $j; $k++) {
$sum += $U[$k][$i] * $L[$j][$k];
}
$U[$j][$i] = $PA[$j][$i] - $sum;
}
// Calculate Lⱼᵢ
for ($j = $i; $j < $n; $j++) {
$sum = 0;
for ($k = 0; $k < $i; $k++) {
$sum += $U[$k][$i] * $L[$j][$k];
}
$L[$j][$i] = $U[$i][$i] == 0 ? \NAN : ($PA[$j][$i] - $sum) / $U[$i][$i];
}
}
// Assemble return array items: [L, U, P, A]
$this->L = MatrixFactory::create($L);
$this->U = MatrixFactory::create($U);
$this->P = $P;
return ['L' => $this->L, 'U' => $this->U, 'P' => $this->P, 'A' => MatrixFactory::create($this->A)];
}