// Copyright (C) 2009 Davis E. King (davis@dlib.net) // License: Boost Software License See LICENSE.txt for the full license. // This code was adapted from code from the JAMA part of NIST's TNT library. // See: http://math.nist.gov/tnt/ #ifndef DLIB_MATRIX_CHOLESKY_DECOMPOSITION_H #define DLIB_MATRIX_CHOLESKY_DECOMPOSITION_H #include "matrix.h" #include "matrix_utilities.h" #include "matrix_subexp.h" #include <cmath> #ifdef DLIB_USE_LAPACK #include "lapack/potrf.h" #endif #include "matrix_trsm.h" namespace dlib { template < typename matrix_exp_type > class cholesky_decomposition { public: const static long NR = matrix_exp_type::NR; const static long NC = matrix_exp_type::NC; typedef typename matrix_exp_type::type type; typedef typename matrix_exp_type::mem_manager_type mem_manager_type; typedef typename matrix_exp_type::layout_type layout_type; typedef matrix<type,0,0,mem_manager_type,layout_type> matrix_type; typedef matrix<type,NR,1,mem_manager_type,layout_type> column_vector_type; // You have supplied an invalid type of matrix_exp_type. You have // to use this object with matrices that contain float or double type data. COMPILE_TIME_ASSERT((is_same_type<float, type>::value || is_same_type<double, type>::value )); template <typename EXP> cholesky_decomposition( const matrix_exp<EXP>& A ); bool is_spd( ) const; const matrix_type& get_l( ) const; template <typename EXP> const typename EXP::matrix_type solve ( const matrix_exp<EXP>& B ) const; private: matrix_type L_; // lower triangular factor bool isspd; // true if matrix to be factored was SPD }; // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // Member functions // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- template <typename matrix_exp_type> bool cholesky_decomposition<matrix_exp_type>:: is_spd( ) const { return isspd; } // ---------------------------------------------------------------------------------------- template <typename matrix_exp_type> const typename cholesky_decomposition<matrix_exp_type>::matrix_type& cholesky_decomposition<matrix_exp_type>:: get_l( ) const { return L_; } // ---------------------------------------------------------------------------------------- template <typename matrix_exp_type> template <typename EXP> cholesky_decomposition<matrix_exp_type>:: cholesky_decomposition( const matrix_exp<EXP>& A_ ) { using std::sqrt; COMPILE_TIME_ASSERT((is_same_type<type, typename EXP::type>::value)); // make sure requires clause is not broken DLIB_ASSERT(A_.nr() == A_.nc() && A_.size() > 0, "\tcholesky_decomposition::cholesky_decomposition(A_)" << "\n\tYou can only use this on square matrices" << "\n\tA_.nr(): " << A_.nr() << "\n\tA_.nc(): " << A_.nc() << "\n\tA_.size(): " << A_.size() << "\n\tthis: " << this ); #ifdef DLIB_USE_LAPACK L_ = A_; const type eps = max(abs(diag(L_)))*std::sqrt(std::numeric_limits<type>::epsilon())/100; // check if the matrix is actually symmetric bool is_symmetric = true; for (long r = 0; r < L_.nr() && is_symmetric; ++r) { for (long c = r+1; c < L_.nc() && is_symmetric; ++c) { // this is approximately doing: is_symmetric = is_symmetric && ( L_(k,j) == L_(j,k)) is_symmetric = is_symmetric && (std::abs(L_(r,c) - L_(c,r)) < eps ); } } // now compute the actual cholesky decomposition int info = lapack::potrf('L', L_); // check if it's really SPD if (info == 0 && is_symmetric && min(abs(diag(L_))) > eps*100) isspd = true; else isspd = false; L_ = lowerm(L_); #else const_temp_matrix<EXP> A(A_); isspd = true; const long n = A.nc(); L_.set_size(n,n); // do nothing if the matrix is empty if (A.size() == 0) return; const type eps = std::numeric_limits<type>::epsilon(); const type eps2 = max(abs(diag(A)))*std::sqrt(std::numeric_limits<type>::epsilon())/100; // compute the upper left corner if (A(0,0) > 0) { L_(0,0) = std::sqrt(A(0,0)); if (A(0,0) <= eps2) isspd = false; } else { isspd = false; L_(0,0) = 0; } // compute the first column for (long r = 1; r < A.nr(); ++r) { // if (L_(0,0) > 0) if (L_(0,0) > eps*std::abs(A(r,0))) { L_(r,0) = A(r,0)/L_(0,0); } else { isspd = false; L_(r,0) = 0; } isspd = isspd && (std::abs(A(r,0) - A(0,r)) <= eps*std::abs(A(r,0)) ); } // now compute all the other columns for (long c = 1; c < A.nc(); ++c) { // compute the diagonal element type temp = A(c,c); for (long i = 0; i < c; ++i) temp -= L_(c,i)*L_(c,i); if (temp > 0) { L_(c,c) = std::sqrt(temp); if (temp <= eps2) isspd = false; } else { L_(c,c) = 0; isspd = false; } for (long r = 0; r < c; ++r) L_(r,c) = 0; // compute the non diagonal elements for (long r = c+1; r < A.nr(); ++r) { temp = A(r,c); for (long i = 0; i < c; ++i) { temp -= L_(r,i)*L_(c,i); } // if (L_(c,c) > 0) if (L_(c,c) > eps*std::abs(temp)) { L_(r,c) = temp/L_(c,c); } else { isspd = false; L_(r,c) = 0; } isspd = isspd && (std::abs(A(r,c) - A(c,r)) <= eps*std::abs(A(r,c)) ); } } #endif } // ---------------------------------------------------------------------------------------- template <typename matrix_exp_type> template <typename EXP> const typename EXP::matrix_type cholesky_decomposition<matrix_exp_type>:: solve( const matrix_exp<EXP>& B ) const { COMPILE_TIME_ASSERT((is_same_type<type, typename EXP::type>::value)); // make sure requires clause is not broken DLIB_ASSERT(L_.nr() == B.nr(), "\tconst matrix cholesky_decomposition::solve(B)" << "\n\tInvalid arguments were given to this function." << "\n\tL_.nr(): " << L_.nr() << "\n\tB.nr(): " << B.nr() << "\n\tthis: " << this ); matrix<type, NR, EXP::NC, mem_manager_type, layout_type> X(B); using namespace blas_bindings; // Solve L*y = b; triangular_solver(CblasLeft, CblasLower, CblasNoTrans, CblasNonUnit, L_, X); // Solve L'*X = Y; triangular_solver(CblasLeft, CblasLower, CblasTrans, CblasNonUnit, L_, X); return X; } // ---------------------------------------------------------------------------------------- } #endif // DLIB_MATRIX_CHOLESKY_DECOMPOSITION_H