// L============================================================================= // L This software is distributed under the MIT license. // L Copyright 2021 Péter Kardos // L============================================================================= #pragma once #include "../Common/Definitions.hpp" #include "../Common/Range.hpp" #include namespace mathter { /// A utility class that can do common operations with the LU decomposition, /// i.e. solving equation systems. template class DecompositionLU { using MatrixT = Matrix; template friend class DecompositionLUP; private: static Vector Solve(const MatrixT& L, const MatrixT& U, const Vector& b); public: // DecompositionLU(MatrixT L, MatrixT U) : L(L), U(U) {} /// Solves the equation system Ax=b, that is LUx=b. /// If the equation is singular or the LU decomposition fails, garbage is returned. /// The right hand side vector. /// The solution x. Vector Solve(const Vector& b) const { return Solve(L, U, b); } bool Solvable() const { T prod = L(0, 0); T sum = std::abs(prod); for (int i = 1; i < Dim; ++i) { prod *= L(i, i); sum += std::abs(L(i, i)); } sum /= Dim; return std::abs(prod) / sum > T(1e-6); } /// Lower triangular matrix, LU=P'A. MatrixT L; /// Upper triangular matrix, LU=P'A. MatrixT U; }; /// A utility class that can do common operations with the LUP decomposition, /// i.e. solving equation systems. template class DecompositionLUP { using MatrixT = Matrix; public: // DecompositionLUP(MatrixT L, MatrixT U, Vector P) : L(L), U(U), P(P) {} /// Solves the equation system Ax=b, that is LUx=Pb. /// If the equation is singular garbage is returned. /// The right hand side vector. /// The solution x. Vector Solve(const Vector& b) const; bool Solvable() { T prod = L(0, 0); T sum = std::abs(prod); for (int i = 1; i < Dim; ++i) { prod *= L(i, i); sum += std::abs(L(i, i)); } sum /= Dim; return std::abs(prod) / sum > T(1e-6); } /// Lower triangular matrix, LU=P'A. MatrixT L; /// Upper triangular matrix, LU=P'A. MatrixT U; /// Row permutations. LU=P'A, where P' is a matrix whose i-th row's P[i]-th element is one. Vector P; }; template auto DecomposeLU(const Matrix& m) { // From: https://www.gamedev.net/resources/_/technical/math-and-physics/matrix-inversion-using-lu-decomposition-r3637 Matrix L; Matrix U; const auto& A = m; constexpr int n = Dim; for (int i = 0; i < n; ++i) { for (int j = i + 1; j < n; ++j) { L(i, j) = 0; } for (int j = 0; j <= i; ++j) { U(i, j) = i == j; } } // Crout's algorithm for (int i = 0; i < n; ++i) { L(i, 0) = A(i, 0); } for (int j = 1; j < n; ++j) { U(0, j) = A(0, j) / L(0, 0); } for (int j = 1; j < n - 1; ++j) { for (int i = j; i < n; ++i) { float Lij; Lij = A(i, j); for (int k = 0; k <= j - 1; ++k) { Lij -= L(i, k) * U(k, j); } L(i, j) = Lij; } for (int k = j; k < n; ++k) { float Ujk; Ujk = A(j, k); for (int i = 0; i <= j - 1; ++i) { Ujk -= L(j, i) * U(i, k); } Ujk /= L(j, j); U(j, k) = Ujk; } } L(n - 1, n - 1) = A(n - 1, n - 1); for (int k = 0; k < n - 1; ++k) { L(n - 1, n - 1) -= L(n - 1, k) * U(k, n - 1); } return DecompositionLU{ L, U }; } /// Implements LU decomposition with partial pivoting. /// Handles singular matrices as well. /// Lower triangular matrix, LU=P'A. /// Upper triangular matrix, LU=P'A. /// Row permutations. LU=P'A, where P' is a matrix whose i-th row's P[i]-th element is one. /// The parity of the permutation described by P. Odd: 1, Even: -1. template auto DecomposeLUP(const Matrix& m, int& parity) { Matrix L; Matrix U; Vector P; U = m; int n = m.RowCount(); parity = 1; for (int i : impl::Range(0, n)) { P(i) = i; } for (int j : impl::Range(0, n)) { // find largest pivot elements T p = 0; int largest; for (int i : impl::Range(j, n)) { if (std::abs(U(i, j)) > p) { largest = i; p = std::abs(U(i, j)); } } // if pivot is zero TODO if (p == 0) { continue; } // swap rows to move pivot to top row std::swap(P(j), P(largest)); parity *= (j != largest ? -1 : 1); for (int i : impl::Range(0, n)) { std::swap(U(j, i), U(largest, i)); } // do some magic for (int i : impl::Range(j + 1, n)) { U(i, j) = U(i, j) / U(j, j); for (int k : impl::Range(j + 1, n)) { U(i, k) = U(i, k) - U(i, j) * U(j, k); } } } // copy elements to L for (int j : impl::Range(0, n)) { for (int i : impl::Range(j + 1, n)) { L(i, j) = U(i, j); U(i, j) = T(0); L(j, i) = T(0); } } for (int i : impl::Range(n)) { L(i, i) = 1; } return DecompositionLUP{ L, U, P }; } /// Implements LU decomposition with partial pivoting. /// Handles singular matrices as well. template auto DecomposeLUP(const Matrix& m) { int ignore; return DecomposeLUP(m, ignore); } template Vector DecompositionLU::Solve(const MatrixT& L, const MatrixT& U, const Vector& b) { // Matrix to do gaussian elimination with Matrix E; // Solve Ld = b; E.template Submatrix(0, 0) = L; E.Column(Dim) = b; for (int i = 0; i < Dim - 1; ++i) { for (int i2 = i + 1; i2 < Dim; ++i2) { E.stripes[i] /= E(i, i); T coeff = E(i2, i); E.stripes[i2] -= E.stripes[i] * coeff; } } E(Dim - 1, Dim) /= E(Dim - 1, Dim - 1); // d is now the last column of E // Solve Ux = d E.template Submatrix(0, 0) = U; for (int i = Dim - 1; i > 0; --i) { for (int i2 = i - 1; i2 >= 0; --i2) { E.stripes[i] /= E(i, i); T coeff = E(i2, i); E.stripes[i2] -= E.stripes[i] * coeff; } } E(0, Dim) /= E(0, 0); // x is now the last column of E return E.Column(Dim); } template Vector DecompositionLUP::Solve(const Vector& b) const { // Permute b Vector bp; for (int i : impl::Range(0, P.Dimension())) { bp(i) = b(P(i)); } // Solve auto x = DecompositionLU::Solve(L, U, bp); return x; } } // namespace mathter