| // This file is part of Eigen, a lightweight C++ template library |
| // for linear algebra. |
| // |
| // Copyright (C) 2026 Rasmus Munk Larsen <rmlarsen@gmail.com> |
| // |
| // This Source Code Form is subject to the terms of the Mozilla |
| // Public License v. 2.0. If a copy of the MPL was not distributed |
| // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| #ifndef EIGEN_BUNCHKAUFMAN_H |
| #define EIGEN_BUNCHKAUFMAN_H |
| |
| // IWYU pragma: private |
| #include "./InternalHeaderCheck.h" |
| |
| namespace Eigen { |
| |
| namespace internal { |
| template <typename MatrixType_, int UpLo_> |
| struct traits<BunchKaufman<MatrixType_, UpLo_> > : traits<MatrixType_> { |
| using XprKind = MatrixXpr; |
| using StorageKind = SolverStorage; |
| using StorageIndex = int; |
| enum { Flags = 0 }; |
| }; |
| |
| template <typename MatrixType, int UpLo> |
| struct BunchKaufman_Traits; |
| |
| // Panel width for the blocked factorization (defined below); forward-declared so the size |
| // constructor can pre-allocate the panel workspace. |
| template <typename Scalar> |
| inline Index bunch_kaufman_blocksize(); |
| } // namespace internal |
| |
| /** \ingroup Cholesky_Module |
| * |
| * \class BunchKaufman |
| * |
| * \brief Bunch-Kaufman factorization of a symmetric / Hermitian indefinite matrix |
| * |
| * \tparam MatrixType_ the type of the matrix of which to compute the Bunch-Kaufman factorization |
| * \tparam UpLo_ the triangular part that will be used for the decomposition: Lower (default) or Upper. |
| * The other triangular part won't be read. |
| * |
| * Perform a symmetric-indefinite factorization of a real symmetric or complex Hermitian matrix |
| * \f$ A \f$ using the Bunch-Kaufman diagonal pivoting method, such that |
| * \f$ P A P^T = L D L^* \f$ (or \f$ P A P^T = U^* D U \f$ for the upper variant), where \f$ P \f$ is a |
| * permutation matrix, \f$ L \f$ is unit lower triangular and \f$ D \f$ is block diagonal with |
| * 1x1 and 2x2 diagonal blocks. |
| * |
| * Unlike LLT and LDLT, this factorization is numerically stable (backward stable, with a bounded |
| * element-growth factor) for \em indefinite symmetric / Hermitian matrices. It is the dense analog of |
| * LAPACK's xSYTRF / xHETRF routines. For positive- or negative-(semi)definite matrices LLT / LDLT are |
| * cheaper and should be preferred. |
| * |
| * The diagonal-pivoting threshold \f$ \alpha = (1+\sqrt{17})/8 \f$ and the pivot-selection logic follow: |
| * - J. R. Bunch and L. Kaufman, "Some stable methods for calculating inertia and solving symmetric |
| * linear systems", Math. Comp. 31 (1977), pp. 163-179. |
| * - G. H. Golub and C. F. Van Loan, "Matrix Computations", 4th ed., section 4.4. |
| * - N. J. Higham, "Accuracy and Stability of Numerical Algorithms", 2nd ed., chapter 11. |
| * |
| * The blocked (level-3 BLAS) variant follows the panel/trailing-update structure of LAPACK's |
| * xSYTRF / xLASYF. |
| * |
| * This factorization also reveals the \em inertia of \f$ A \f$ (the number of positive, negative and |
| * zero eigenvalues) by Sylvester's law of inertia, accessible through isPositive() / isNegative(). |
| * |
| * This class supports the \link InplaceDecomposition inplace decomposition \endlink mechanism. |
| * |
| * \sa MatrixBase::bunchKaufman(), SelfAdjointView::bunchKaufman(), class LDLT, class LLT |
| */ |
| template <typename MatrixType_, int UpLo_> |
| class BunchKaufman : public SolverBase<BunchKaufman<MatrixType_, UpLo_> > { |
| public: |
| using MatrixType = MatrixType_; |
| using Base = SolverBase<BunchKaufman>; |
| friend class SolverBase<BunchKaufman>; |
| |
| EIGEN_GENERIC_PUBLIC_INTERFACE(BunchKaufman) |
| enum { |
| MaxRowsAtCompileTime = MatrixType::MaxRowsAtCompileTime, |
| MaxColsAtCompileTime = MatrixType::MaxColsAtCompileTime, |
| UpLo = UpLo_ |
| }; |
| |
| using TmpVectorType = Matrix<Scalar, RowsAtCompileTime, 1, 0, MaxRowsAtCompileTime, 1>; |
| // Panel workspace for the blocked algorithm (only allocated for large dynamic-sized problems). |
| using WorkspaceType = Matrix<Scalar, Dynamic, Dynamic>; |
| using TranspositionType = Transpositions<RowsAtCompileTime, MaxRowsAtCompileTime>; |
| using PermutationType = PermutationMatrix<RowsAtCompileTime, MaxRowsAtCompileTime>; |
| |
| using Traits = internal::BunchKaufman_Traits<MatrixType, UpLo>; |
| |
| /** \brief Default Constructor. |
| * |
| * The default constructor is useful in cases in which the user intends to |
| * perform decompositions via BunchKaufman::compute(const MatrixType&). |
| */ |
| BunchKaufman() |
| : m_matrix(), |
| m_l1_norm(0), |
| m_transpositions(), |
| m_subdiag(), |
| m_n_pos(0), |
| m_n_neg(0), |
| m_n_zero(0), |
| m_isInitialized(false), |
| m_info(InvalidInput) {} |
| |
| /** \brief Default Constructor with memory preallocation |
| * |
| * Like the default constructor but with preallocation of the internal data |
| * according to the specified problem \a size. |
| * \sa BunchKaufman() |
| */ |
| explicit BunchKaufman(Index size) |
| : m_matrix(size, size), |
| m_l1_norm(0), |
| m_transpositions(size), |
| m_subdiag(size), |
| // Pre-allocate the panel workspace to the exact shape compute() needs, so that a subsequent |
| // compute() on a problem of this size performs no heap allocation (the blocked factorization |
| // resizes it to n x (nb+1); resizing to the same shape is a no-op). |
| m_workspace(size, internal::bunch_kaufman_blocksize<Scalar>() + 1), |
| m_n_pos(0), |
| m_n_neg(0), |
| m_n_zero(0), |
| m_isInitialized(false), |
| m_info(InvalidInput) {} |
| |
| /** \brief Constructor with decomposition |
| * |
| * This calculates the decomposition for the input \a matrix. |
| * |
| * \sa BunchKaufman(Index size) |
| */ |
| template <typename InputType> |
| explicit BunchKaufman(const EigenBase<InputType>& matrix) |
| : m_matrix(matrix.rows(), matrix.cols()), |
| m_l1_norm(0), |
| m_transpositions(matrix.rows()), |
| m_subdiag(matrix.rows()), |
| m_n_pos(0), |
| m_n_neg(0), |
| m_n_zero(0), |
| m_isInitialized(false), |
| m_info(InvalidInput) { |
| compute(matrix.derived()); |
| } |
| |
| /** \brief Constructs a Bunch-Kaufman factorization from a given matrix |
| * |
| * This overloaded constructor is provided for \link InplaceDecomposition inplace decomposition \endlink when \c |
| * MatrixType is a Eigen::Ref. |
| * |
| * \sa BunchKaufman(const EigenBase&) |
| */ |
| template <typename InputType> |
| explicit BunchKaufman(EigenBase<InputType>& matrix) |
| : m_matrix(matrix.derived()), |
| m_l1_norm(0), |
| m_transpositions(matrix.rows()), |
| m_subdiag(matrix.rows()), |
| m_n_pos(0), |
| m_n_neg(0), |
| m_n_zero(0), |
| m_isInitialized(false), |
| m_info(InvalidInput) { |
| compute(matrix.derived()); |
| } |
| |
| /** \returns a view of the unit upper triangular matrix U */ |
| inline typename Traits::MatrixU matrixU() const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| return Traits::getU(m_matrix); |
| } |
| |
| /** \returns a view of the unit lower triangular matrix L */ |
| inline typename Traits::MatrixL matrixL() const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| return Traits::getL(m_matrix); |
| } |
| |
| /** \returns the permutation matrix P as a transposition sequence. |
| */ |
| inline const TranspositionType& transpositionsP() const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| return m_transpositions; |
| } |
| |
| /** \returns the main diagonal of the block-diagonal matrix D. |
| * |
| * The block-diagonal matrix D is fully described by vectorD() (its main diagonal) together with |
| * subDiagonal() (the sub-diagonal entries of its 2x2 blocks). |
| */ |
| inline Diagonal<const MatrixType> vectorD() const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| return m_matrix.diagonal(); |
| } |
| |
| /** \returns the sub-diagonal of the block-diagonal matrix D. |
| * |
| * Entry \c k is non-zero if and only if a 2x2 diagonal block of D occupies rows/columns \c k and |
| * \c k+1; it then holds \f$ D_{k+1,k} \f$. All other entries are zero. \sa vectorD() |
| */ |
| inline const TmpVectorType& subDiagonal() const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| return m_subdiag; |
| } |
| |
| /** \returns true if the matrix is positive semidefinite (has no strictly negative eigenvalues). */ |
| inline bool isPositive() const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| return m_n_neg == 0; |
| } |
| |
| /** \returns true if the matrix is negative semidefinite (has no strictly positive eigenvalues). */ |
| inline bool isNegative() const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| return m_n_pos == 0; |
| } |
| |
| #ifdef EIGEN_PARSED_BY_DOXYGEN |
| /** \returns a solution x of \f$ A x = b \f$ using the current decomposition of A. |
| * |
| * This function also supports in-place solves using the syntax <tt>x = decompositionObject.solve(x)</tt> . |
| * |
| * \note_about_checking_solutions |
| * |
| * \sa MatrixBase::bunchKaufman(), SelfAdjointView::bunchKaufman() |
| */ |
| template <typename Rhs> |
| inline Solve<BunchKaufman, Rhs> solve(const MatrixBase<Rhs>& b) const; |
| #endif |
| |
| template <typename Derived> |
| bool solveInPlace(MatrixBase<Derived>& bAndX) const; |
| |
| template <typename InputType> |
| BunchKaufman& compute(const EigenBase<InputType>& matrix); |
| |
| /** \returns an estimate of the reciprocal condition number of the matrix of |
| * which \c *this is the Bunch-Kaufman decomposition. |
| */ |
| RealScalar rcond() const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| return internal::rcond_estimate_helper(m_l1_norm, *this); |
| } |
| |
| /** \returns the internal Bunch-Kaufman decomposition matrix |
| * |
| * The strictly lower (resp. upper) triangular part holds the unit triangular factor L (resp. U); the |
| * diagonal holds the main diagonal of D. The sub/super-diagonal entries of the 2x2 blocks of D are |
| * stored separately, in subDiagonal(). |
| */ |
| inline const MatrixType& matrixLDLT() const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| return m_matrix; |
| } |
| |
| MatrixType reconstructedMatrix() const; |
| |
| /** \returns the determinant of the matrix of which *this is the Bunch-Kaufman decomposition. |
| * |
| * It has only linear complexity (that is, O(n) where n is the dimension of the square matrix) |
| * as the decomposition has already been computed. |
| * |
| * \warning a determinant can be very big or small, so for matrices |
| * of large enough dimension, there is a risk of overflow/underflow. |
| * One way to work around that is to use logAbsDeterminant() and signDeterminant() instead. |
| * Also, do not rely on the determinant being exactly zero for testing |
| * singularity or rank-deficiency. |
| * |
| * \sa absDeterminant(), logAbsDeterminant(), signDeterminant(), MatrixBase::determinant() |
| */ |
| Scalar determinant() const; |
| |
| /** \returns the absolute value of the determinant of the matrix of which *this is the Bunch-Kaufman |
| * decomposition. |
| * |
| * It has only linear complexity (that is, O(n) where n is the dimension of the square matrix) |
| * as the decomposition has already been computed. |
| * |
| * \warning a determinant can be very big or small, so for matrices |
| * of large enough dimension, there is a risk of overflow/underflow. |
| * One way to work around that is to use logAbsDeterminant() instead. |
| * |
| * \sa determinant(), logAbsDeterminant(), signDeterminant(), MatrixBase::determinant() |
| */ |
| RealScalar absDeterminant() const; |
| |
| /** \returns the natural log of the absolute value of the determinant of the matrix of which *this is the |
| * Bunch-Kaufman decomposition. |
| * |
| * It has only linear complexity (that is, O(n) where n is the dimension of the square matrix) |
| * as the decomposition has already been computed. |
| * |
| * \note This method is useful to work around the risk of overflow/underflow that's inherent |
| * to determinant computation. The 2x2 blocks of D are scaled by their off-diagonal entry before their |
| * determinant is formed, so a block whose own determinant is out of range still contributes a |
| * finite term. |
| * |
| * \sa determinant(), absDeterminant(), signDeterminant(), MatrixBase::determinant() |
| */ |
| RealScalar logAbsDeterminant() const; |
| |
| /** \returns the sign of the determinant of the matrix of which *this is the Bunch-Kaufman decomposition, |
| * that is, \c 1, \c -1, or \c 0 if the matrix is singular. |
| * |
| * The sign is read off the inertia rather than computed from a product, so it is exact and cannot |
| * overflow: \f$ \mathrm{sign}(\det A) = (-1)^{n_-} \f$, with \f$ n_- \f$ the number of negative |
| * eigenvalues, which congruence leaves invariant (Sylvester's law of inertia). |
| * |
| * \sa determinant(), absDeterminant(), logAbsDeterminant(), MatrixBase::determinant() |
| */ |
| Scalar signDeterminant() const; |
| |
| /** \returns the adjoint of \c *this, that is, a const reference to the decomposition itself as the underlying matrix |
| * is self-adjoint. |
| * |
| * This method is provided for compatibility with other matrix decompositions, thus enabling generic code such as: |
| * \code x = decomposition.adjoint().solve(b) \endcode |
| */ |
| const BunchKaufman& adjoint() const { return *this; } |
| |
| EIGEN_DEVICE_FUNC constexpr Index rows() const noexcept { return m_matrix.rows(); } |
| EIGEN_DEVICE_FUNC constexpr Index cols() const noexcept { return m_matrix.cols(); } |
| |
| /** \brief Reports whether previous computation was successful. |
| * |
| * \returns \c Success if computation was successful, |
| * \c NumericalIssue if the factorization failed because of a zero pivot (the matrix is singular) |
| * or because a 2x2 pivot block has no representable inverse (a NaN entry, or an off-diagonal |
| * so much smaller than the diagonal that the scaled inverse overflows). |
| */ |
| ComputationInfo info() const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| return m_info; |
| } |
| |
| #ifndef EIGEN_PARSED_BY_DOXYGEN |
| template <typename RhsType, typename DstType> |
| void _solve_impl(const RhsType& rhs, DstType& dst) const; |
| |
| template <bool Conjugate, typename RhsType, typename DstType> |
| void _solve_impl_transposed(const RhsType& rhs, DstType& dst) const; |
| #endif |
| |
| protected: |
| EIGEN_STATIC_ASSERT_NON_INTEGER(Scalar) |
| |
| /** \internal Apply the block-diagonal matrix D in place: \c x <- D x. */ |
| template <typename Derived> |
| void applyD(MatrixBase<Derived>& x) const; |
| |
| /** \internal Apply the inverse of the block-diagonal matrix D in place: \c x <- D^{-1} x. |
| * For \c Conjugate == false the transpose of D is used instead (relevant for the complex case). */ |
| template <bool Conjugate, typename Derived> |
| void solveInPlaceD(MatrixBase<Derived>& x) const; |
| |
| /** \internal Compute the inertia (counts of positive / negative / zero eigenvalues) from D. */ |
| void computeInertia(); |
| |
| /** \internal \returns \f$ \det(D_k)/|d_{21}|^2 \f$ for a 2x2 block of D, which is real and shares the |
| * sign of \f$ \det(D_k) \f$ since \f$ |d_{21}| > 0 \f$ there. */ |
| static RealScalar scaledBlockDeterminant(const RealScalar& d11, const RealScalar& d22, const RealScalar& d21); |
| |
| /** \internal \returns \f$ \det(D) \f$, the product over the 1x1 and 2x2 diagonal blocks of D. D is |
| * Hermitian, so a block determinant is real: \f$ d_{11} \f$ for a 1x1 block and |
| * \f$ d_{11} d_{22} - |d_{21}|^2 \f$ for a 2x2 one. Accumulated as a mantissa and a power of two, |
| * so that only the result has to be representable. */ |
| RealScalar determinantD() const; |
| |
| MatrixType m_matrix; |
| RealScalar m_l1_norm; |
| TranspositionType m_transpositions; |
| TmpVectorType m_subdiag; |
| WorkspaceType m_workspace; |
| Index m_n_pos; |
| Index m_n_neg; |
| Index m_n_zero; |
| bool m_isInitialized; |
| ComputationInfo m_info; |
| }; |
| |
| namespace internal { |
| |
| /** \internal The Bunch-Kaufman diagonal-pivoting threshold \f$ \alpha = (1+\sqrt{17})/8 \approx 0.6404 \f$. |
| * It minimizes the bound on element growth across a 1x1 followed by a 1x1 step versus a single 2x2 step |
| * (Bunch & Kaufman, 1977). */ |
| template <typename RealScalar> |
| EIGEN_DEVICE_FUNC inline RealScalar bunch_kaufman_alpha() { |
| using std::sqrt; |
| return (RealScalar(1) + sqrt(RealScalar(17))) / RealScalar(8); |
| } |
| |
| /** \internal Panel width for the blocked (level-3) Bunch-Kaufman factorization. Can be overridden (mainly |
| * for testing the panel logic at small sizes) by defining EIGEN_BUNCHKAUFMAN_BLOCKSIZE. */ |
| template <typename Scalar> |
| inline Index bunch_kaufman_blocksize() { |
| #ifdef EIGEN_BUNCHKAUFMAN_BLOCKSIZE |
| return Index(EIGEN_BUNCHKAUFMAN_BLOCKSIZE); |
| #else |
| return 64; |
| #endif |
| } |
| |
| template <int UpLo> |
| struct bunch_kaufman; |
| |
| template <> |
| struct bunch_kaufman<Lower> { |
| // Interchange rows and columns kk and kp (kp >= kk) in the lower triangle of the Hermitian matrix |
| // `mat`, including the already-computed factor columns to the left of column `kfirst` (so that the |
| // stored unit triangular factor stays consistent with a single up-front permutation P). `kfirst` is |
| // the first column of the pivot block (== kk for a 1x1 pivot, == kk-1 for a 2x2 pivot). |
| template <typename MatrixType> |
| static void apply_symmetric_pivot(MatrixType& mat, Index kfirst, Index kk, Index kp, Index kstep) { |
| using Scalar = typename MatrixType::Scalar; |
| const Index n = mat.rows(); |
| const Index s = n - kp - 1; |
| if (s > 0) mat.col(kk).tail(s).swap(mat.col(kp).tail(s)); |
| for (Index i = kk + 1; i < kp; ++i) { |
| Scalar tmp = mat.coeff(i, kk); |
| mat.coeffRef(i, kk) = numext::conj(mat.coeff(kp, i)); |
| mat.coeffRef(kp, i) = numext::conj(tmp); |
| } |
| numext::swap(mat.coeffRef(kk, kk), mat.coeffRef(kp, kp)); |
| EIGEN_IF_CONSTEXPR (NumTraits<Scalar>::IsComplex) { |
| mat.coeffRef(kp, kk) = numext::conj(mat.coeff(kp, kk)); |
| } |
| if (kfirst > 0) mat.row(kk).head(kfirst).swap(mat.row(kp).head(kfirst)); |
| if (kstep == 2) { |
| numext::swap(mat.coeffRef(kfirst + 1, kfirst), mat.coeffRef(kp, kfirst)); |
| } |
| } |
| |
| // Unblocked (level-2 BLAS) Bunch-Kaufman factorization of the lower triangle of `mat`, in place. |
| // Columns [k0, n) are factorized. On output: |
| // - the strictly-lower triangle holds the unit lower factor L (with explicit zeros at the |
| // sub-diagonal positions of 2x2 blocks), |
| // - the diagonal holds the main diagonal of D, |
| // - subdiag(k) holds D(k+1,k) for each 2x2 block starting at column k (and 0 elsewhere), |
| // - transpositions encodes the symmetric permutation P (applied in increasing index order). |
| // Returns 0 on success, or the (1-based) index of the first exactly-zero pivot encountered. |
| template <typename MatrixType, typename TranspositionType, typename SubDiagType> |
| static Index unblocked(MatrixType& mat, TranspositionType& transpositions, SubDiagType& subdiag, Index k0 = 0) { |
| using numext::abs; |
| using Scalar = typename MatrixType::Scalar; |
| using RealScalar = typename MatrixType::RealScalar; |
| using StorageIndex = typename TranspositionType::StorageIndex; |
| const Index n = mat.rows(); |
| const RealScalar alpha = bunch_kaufman_alpha<RealScalar>(); |
| Index info = 0; |
| |
| Index k = k0; |
| while (k < n) { |
| Index kstep = 1; |
| Index kp = k; |
| const RealScalar absakk = abs(numext::real(mat.coeff(k, k))); |
| |
| // colmax = max_{i>k} |mat(i,k)|, attained at row imax. |
| Index imax = k; |
| RealScalar colmax(0); |
| if (k + 1 < n) { |
| Index rel = 0; |
| colmax = mat.col(k).tail(n - k - 1).cwiseAbs().maxCoeff(&rel); |
| imax = k + 1 + rel; |
| } |
| |
| if (numext::is_exactly_zero((numext::maxi)(absakk, colmax)) || (numext::isnan)(absakk)) { |
| // The whole remaining column is zero: 1x1 zero pivot, matrix is singular. |
| kp = k; |
| if (info == 0) info = k + 1; |
| mat.coeffRef(k, k) = Scalar(numext::real(mat.coeff(k, k))); |
| } else if (absakk >= alpha * colmax) { |
| // 1x1 pivot at k, no interchange. |
| kp = k; |
| } else { |
| // rowmax = largest off-diagonal magnitude in row/column imax. |
| RealScalar rowmax = mat.row(imax).segment(k, imax - k).cwiseAbs().maxCoeff(); |
| if (imax + 1 < n) { |
| RealScalar rowmax2 = mat.col(imax).tail(n - imax - 1).cwiseAbs().maxCoeff(); |
| rowmax = (numext::maxi)(rowmax, rowmax2); |
| } |
| if (absakk >= alpha * colmax * (colmax / rowmax)) { |
| // 1x1 pivot at k. |
| kp = k; |
| } else if (abs(numext::real(mat.coeff(imax, imax))) >= alpha * rowmax) { |
| // 1x1 pivot at imax: interchange rows/columns k and imax. |
| kp = imax; |
| } else { |
| // 2x2 pivot at (k, imax): interchange rows/columns k+1 and imax. |
| kp = imax; |
| kstep = 2; |
| } |
| } |
| |
| const Index kk = k + kstep - 1; // column of the pivot block to interchange with kp |
| if (kp != kk) apply_symmetric_pivot(mat, k, kk, kp, kstep); |
| |
| if (kstep == 1) { |
| transpositions.coeffRef(k) = StorageIndex(kp); |
| subdiag.coeffRef(k) = Scalar(0); |
| |
| const RealScalar dkk = numext::real(mat.coeff(k, k)); |
| mat.coeffRef(k, k) = Scalar(dkk); |
| const Index rs = n - k - 1; |
| if (rs > 0) { |
| if (!numext::is_exactly_zero(dkk)) { |
| // A22 <- A22 - (1/dkk) w w^*, with w = mat(k+1:n, k); then L column = w / dkk. |
| auto w = mat.col(k).tail(rs); |
| mat.block(k + 1, k + 1, rs, rs).template selfadjointView<Lower>().rankUpdate(w, RealScalar(-1) / dkk); |
| w /= dkk; |
| } else if (info == 0) { |
| info = k + 1; |
| } |
| } |
| k += 1; |
| } else { |
| transpositions.coeffRef(k) = StorageIndex(k); |
| transpositions.coeffRef(k + 1) = StorageIndex(kp); |
| |
| const RealScalar d11 = numext::real(mat.coeff(k, k)); |
| const RealScalar d22 = numext::real(mat.coeff(k + 1, k + 1)); |
| const Scalar d21 = mat.coeff(k + 1, k); |
| mat.coeffRef(k, k) = Scalar(d11); |
| mat.coeffRef(k + 1, k + 1) = Scalar(d22); |
| // Scaled 2x2 inverse (LAPACK xSYTF2/xHETF2 strategy). NEVER form det = d11*d22 - |d21|^2 or |
| // abs2(d21) directly: those over/underflow for well-conditioned but extreme-scaled blocks |
| // (e.g. [[0,s],[s,0]], s=1e200, where det = -s^2 overflows/underflows). Instead divide |
| // through by the off-diagonal d21, so the scaled determinant |
| // denom = real(ak*akm1) - 1 = det / |d21|^2 (with ak = d22/d21, akm1 = d11/conj(d21)) |
| // stays O(1). Divide by d21 itself, never by a hoisted reciprocal: 1/d21 overflows once |d21| |
| // is subnormal, where these quotients are still finite (issue #3142). |
| const Scalar cjd = numext::conj(d21); |
| const Scalar ak = d22 / d21; |
| const Scalar akm1 = d11 / cjd; |
| const RealScalar denom = numext::real(ak * akm1) - RealScalar(1); |
| // The pivot criterion gives |d11 d22| <= alpha^2 |d21|^2 with alpha < 1, so in exact arithmetic |
| // -1 - alpha^2 < denom < alpha^2 - 1. Outside that range ak overflowed -- the criterion bounds |
| // |d22| only against the largest entry of its own row -- or a NaN entry was pulled into the |
| // block. Either way the block's inverse is not representable: report it rather than silently |
| // propagate it. |
| if (info == 0 && !(denom < RealScalar(0) && denom > RealScalar(-2))) info = k + 1; |
| |
| const Index rs = n - k - 2; |
| if (rs > 0) { |
| // Fused factor-column computation and trailing update, in a single pass over the lower |
| // triangle of A22 (the xSYTF2/xHETF2 strategy). For each trailing column j, first form the |
| // two unit lower factor entries of row j (the rows of U D^{-1}, in the scaled form above), |
| // l0_j = t*((ak*u0_j - u1_j)/conj(d21)), l1_j = t*((akm1*u1_j - u0_j)/d21), |
| // dividing before scaling by t: the quotient is denom*l_j, within a factor 1 + alpha^2 of the |
| // result, whereas t*(ak*u0_j - u1_j) can overflow (|t| < 1/(1 - alpha^2)) where l_j is finite. |
| // Then update column j against the ORIGINAL pivot columns u = [u0 u1] (they carry the D |
| // scale, U = L*D, so no 1/det factor appears): |
| // A22(i,j) -= u0_i*conj(l0_j) + u1_i*conj(l1_j), i >= j. |
| // Rows < j of the pivot columns already hold L, rows >= j still hold U -- exactly the |
| // entries each column update needs, so no workspace is required. Compared with composing |
| // self-adjoint rank-1/rank-2 updates (syr + syr + syr2) this halves the flops and touches |
| // the trailing triangle once instead of three times. |
| const RealScalar t = RealScalar(1) / denom; |
| auto c0 = mat.col(k).tail(rs); |
| auto c1 = mat.col(k + 1).tail(rs); |
| for (Index j = 0; j < rs; ++j) { |
| const Scalar u0 = c0.coeff(j); |
| const Scalar u1 = c1.coeff(j); |
| const Scalar l0 = t * ((ak * u0 - u1) / cjd); |
| const Scalar l1 = t * ((akm1 * u1 - u0) / d21); |
| const Index len = rs - j; |
| mat.col(k + 2 + j).tail(len) -= numext::conj(l0) * c0.tail(len) + numext::conj(l1) * c1.tail(len); |
| c0.coeffRef(j) = l0; |
| c1.coeffRef(j) = l1; |
| // Keep the updated diagonal exactly real (the correction is Hermitian; roundoff would |
| // otherwise leave a tiny imaginary part). |
| EIGEN_IF_CONSTEXPR (NumTraits<Scalar>::IsComplex) { |
| mat.coeffRef(k + 2 + j, k + 2 + j) = Scalar(numext::real(mat.coeff(k + 2 + j, k + 2 + j))); |
| } |
| } |
| } |
| // Move the 2x2 off-diagonal of D out of the L storage. |
| subdiag.coeffRef(k) = d21; |
| subdiag.coeffRef(k + 1) = Scalar(0); |
| mat.coeffRef(k + 1, k) = Scalar(0); |
| k += 2; |
| } |
| } |
| return info; |
| } |
| |
| // Partial factorization of a panel of at most `nb` columns of the lower triangle of `mat`, starting |
| // at column k0, using the Bunch-Kaufman method (level-2 within the panel). Follows the panel/workspace |
| // structure of LAPACK's xLASYF. The trailing sub-matrix mat(k0+kb:n, k0+kb:n) is left untouched; the |
| // workspace `W` (n x nb) returns, in its first kb columns, the product L21*D restricted to the panel |
| // columns, so that the caller can apply the deferred trailing update A22 <- A22 - L21 * (L21*D)^* with |
| // a single level-3 (triangular) matrix product. Returns the number kb (<= nb) of columns factorized. |
| template <typename MatrixType, typename WorkspaceType, typename TranspositionType, typename SubDiagType> |
| static Index partial_factor(MatrixType& mat, Index k0, Index nb, WorkspaceType& W, TranspositionType& transpositions, |
| SubDiagType& subdiag, Index& info) { |
| using numext::abs; |
| using Scalar = typename MatrixType::Scalar; |
| using RealScalar = typename MatrixType::RealScalar; |
| using StorageIndex = typename TranspositionType::StorageIndex; |
| const Index n = mat.rows(); |
| const RealScalar alpha = bunch_kaufman_alpha<RealScalar>(); |
| constexpr bool is_complex = NumTraits<Scalar>::IsComplex; |
| |
| Index j = 0; |
| while (j < nb) { |
| const Index jc = k0 + j; |
| const Index h = n - jc; |
| |
| // W(jc:n, j) <- updated column jc = (column jc of A) minus the contributions of the panel columns |
| // [k0, jc) already factorized (those are stored as L in mat, and L*D in W). |
| W.col(j).segment(jc, h) = mat.col(jc).segment(jc, h); |
| EIGEN_IF_CONSTEXPR (is_complex) W.coeffRef(jc, j) = Scalar(numext::real(W.coeff(jc, j))); |
| if (j > 0) { |
| W.col(j).segment(jc, h).noalias() -= mat.block(jc, k0, h, j) * W.row(jc).head(j).adjoint(); |
| EIGEN_IF_CONSTEXPR (is_complex) W.coeffRef(jc, j) = Scalar(numext::real(W.coeff(jc, j))); |
| } |
| |
| Index kstep = 1; |
| Index kp = jc; |
| const RealScalar absakk = abs(numext::real(W.coeff(jc, j))); |
| Index imax = jc; |
| RealScalar colmax(0); |
| if (jc + 1 < n) { |
| Index rel = 0; |
| colmax = W.col(j).segment(jc + 1, n - jc - 1).cwiseAbs().maxCoeff(&rel); |
| imax = jc + 1 + rel; |
| } |
| |
| if (numext::is_exactly_zero((numext::maxi)(absakk, colmax)) || (numext::isnan)(absakk)) { |
| kp = jc; |
| if (info == 0) info = jc + 1; |
| } else if (absakk >= alpha * colmax) { |
| kp = jc; |
| } else { |
| // W(jc:n, j+1) <- updated column imax (its leading row part is read from row imax of mat and |
| // conjugated, the trailing part from column imax). |
| const Index hr = imax - jc; |
| if (hr > 0) W.col(j + 1).segment(jc, hr) = mat.row(imax).segment(jc, hr).adjoint(); |
| W.col(j + 1).segment(imax, n - imax) = mat.col(imax).segment(imax, n - imax); |
| EIGEN_IF_CONSTEXPR (is_complex) W.coeffRef(imax, j + 1) = Scalar(numext::real(W.coeff(imax, j + 1))); |
| if (j > 0) { |
| W.col(j + 1).segment(jc, n - jc).noalias() -= mat.block(jc, k0, n - jc, j) * W.row(imax).head(j).adjoint(); |
| EIGEN_IF_CONSTEXPR (is_complex) W.coeffRef(imax, j + 1) = Scalar(numext::real(W.coeff(imax, j + 1))); |
| } |
| |
| RealScalar rowmax(0); |
| if (imax > jc) rowmax = W.col(j + 1).segment(jc, imax - jc).cwiseAbs().maxCoeff(); |
| if (imax + 1 < n) { |
| RealScalar rowmax2 = W.col(j + 1).segment(imax + 1, n - imax - 1).cwiseAbs().maxCoeff(); |
| rowmax = (numext::maxi)(rowmax, rowmax2); |
| } |
| if (absakk >= alpha * colmax * (colmax / rowmax)) { |
| kp = jc; |
| } else if (abs(numext::real(W.coeff(imax, j + 1))) >= alpha * rowmax) { |
| kp = imax; |
| // imax becomes the 1x1 pivot column: its updated column (in W(:,j+1)) replaces W(:,j). |
| W.col(j).segment(jc, n - jc) = W.col(j + 1).segment(jc, n - jc); |
| } else { |
| kp = imax; |
| kstep = 2; |
| } |
| } |
| |
| // A 2x2 block must fit in the panel; otherwise defer this column to the next panel. |
| if (kstep == 2 && j + 1 >= nb) break; |
| |
| const Index kk = jc + kstep - 1; |
| if (kp != kk) { |
| apply_symmetric_pivot(mat, jc, kk, kp, kstep); |
| const Index nwc = j + kstep; // number of populated W columns |
| W.row(kk).head(nwc).swap(W.row(kp).head(nwc)); |
| } |
| |
| if (kstep == 1) { |
| transpositions.coeffRef(jc) = StorageIndex(kp); |
| subdiag.coeffRef(jc) = Scalar(0); |
| const RealScalar dval = numext::real(W.coeff(jc, j)); |
| mat.coeffRef(jc, jc) = Scalar(dval); |
| const Index rs = n - jc - 1; |
| if (rs > 0) { |
| if (!numext::is_exactly_zero(dval)) { |
| mat.col(jc).tail(rs) = W.col(j).segment(jc + 1, rs) / dval; |
| } else { |
| // Zero pivot (singular): the Schur column is zero too. Store an explicit zero L column so |
| // that matrixL() is well formed, matching the unblocked path (which leaves the already |
| // zeroed Schur column in place). |
| mat.col(jc).tail(rs).setZero(); |
| if (info == 0) info = jc + 1; |
| } |
| } |
| j += 1; |
| } else { |
| transpositions.coeffRef(jc) = StorageIndex(jc); |
| transpositions.coeffRef(jc + 1) = StorageIndex(kp); |
| const RealScalar d11 = numext::real(W.coeff(jc, j)); |
| const Scalar d21 = W.coeff(jc + 1, j); |
| const RealScalar d22 = numext::real(W.coeff(jc + 1, j + 1)); |
| mat.coeffRef(jc, jc) = Scalar(d11); |
| mat.coeffRef(jc + 1, jc + 1) = Scalar(d22); |
| // Scaled 2x2 inverse (see unblocked()): divide through by d21 so the scaled determinant |
| // denom = det/|d21|^2 stays O(1); det = d11*d22 - |d21|^2 and abs2(d21) are never formed (they |
| // over/underflow on extreme-scaled blocks). The deferred level-3 trailing update below uses W |
| // (= L*D, original scale), so it carries no 1/det factor either. |
| const Scalar cjd = numext::conj(d21); |
| const Scalar ak = d22 / d21; |
| const Scalar akm1 = d11 / cjd; |
| const RealScalar denom = numext::real(ak * akm1) - RealScalar(1); |
| if (info == 0 && !(denom < RealScalar(0) && denom > RealScalar(-2))) info = jc + 1; |
| const Index rs = n - jc - 2; |
| if (rs > 0) { |
| // L(jc+2:n, jc:jc+1) = W(jc+2:n, j:j+1) * D^{-1}, as vectorized column expressions, divided |
| // before being scaled by t for the reason given in unblocked(): |
| // L_k = t*((ak*w0 - w1)/conj(d21)), L_{k+1} = t*((akm1*w1 - w0)/d21). |
| const RealScalar t = RealScalar(1) / denom; |
| auto w0 = W.col(j).segment(jc + 2, rs); |
| auto w1 = W.col(j + 1).segment(jc + 2, rs); |
| mat.col(jc).tail(rs) = t * ((ak * w0 - w1) / cjd); |
| mat.col(jc + 1).tail(rs) = t * ((akm1 * w1 - w0) / d21); |
| } |
| subdiag.coeffRef(jc) = d21; |
| subdiag.coeffRef(jc + 1) = Scalar(0); |
| mat.coeffRef(jc + 1, jc) = Scalar(0); |
| j += 2; |
| } |
| } |
| return j; |
| } |
| |
| // Blocked (level-3 BLAS) Bunch-Kaufman factorization of the lower triangle of `mat`, in place. |
| template <typename MatrixType, typename TranspositionType, typename SubDiagType, typename WorkspaceType> |
| static Index blocked(MatrixType& mat, TranspositionType& transpositions, SubDiagType& subdiag, |
| WorkspaceType& workspace) { |
| const Index n = mat.rows(); |
| const Index nb = bunch_kaufman_blocksize<typename MatrixType::Scalar>(); |
| if (nb < 2 || n <= nb) return unblocked(mat, transpositions, subdiag, 0); |
| |
| // One extra workspace column holds the candidate ("imax") column examined during pivot selection. |
| workspace.resize(n, nb + 1); |
| Index info = 0; |
| Index k = 0; |
| while (k < n) { |
| if (n - k > nb) { |
| const Index kb = partial_factor(mat, k, nb, workspace, transpositions, subdiag, info); |
| const Index rs = n - k - kb; |
| if (rs > 0) { |
| // Deferred trailing update of the lower triangle: A22 <- A22 - L21 * (L21*D)^*. |
| mat.block(k + kb, k + kb, rs, rs).template triangularView<Lower>() -= |
| mat.block(k + kb, k, rs, kb) * workspace.block(k + kb, 0, rs, kb).adjoint(); |
| } |
| if (kb == 0) { // defensive: avoid an infinite loop (should not happen for nb >= 2) |
| info = (info == 0) ? unblocked(mat, transpositions, subdiag, k) : info; |
| break; |
| } |
| k += kb; |
| } else { |
| const Index info2 = unblocked(mat, transpositions, subdiag, k); |
| if (info == 0) info = info2; |
| break; |
| } |
| } |
| return info; |
| } |
| }; |
| |
| template <> |
| struct bunch_kaufman<Upper> { |
| template <typename MatrixType, typename TranspositionType, typename SubDiagType> |
| static EIGEN_STRONG_INLINE Index unblocked(MatrixType& mat, TranspositionType& transpositions, SubDiagType& subdiag, |
| Index k0 = 0) { |
| Transpose<MatrixType> matt(mat); |
| return bunch_kaufman<Lower>::unblocked(matt, transpositions, subdiag, k0); |
| } |
| |
| template <typename MatrixType, typename TranspositionType, typename SubDiagType, typename WorkspaceType> |
| static EIGEN_STRONG_INLINE Index blocked(MatrixType& mat, TranspositionType& transpositions, SubDiagType& subdiag, |
| WorkspaceType& workspace) { |
| Transpose<MatrixType> matt(mat); |
| return bunch_kaufman<Lower>::blocked(matt, transpositions, subdiag, workspace); |
| } |
| }; |
| |
| template <typename MatrixType> |
| struct BunchKaufman_Traits<MatrixType, Lower> { |
| using MatrixL = const TriangularView<const MatrixType, UnitLower>; |
| using MatrixU = const TriangularView<const typename MatrixType::AdjointReturnType, UnitUpper>; |
| static inline MatrixL getL(const MatrixType& m) { return MatrixL(m); } |
| static inline MatrixU getU(const MatrixType& m) { return MatrixU(m.adjoint()); } |
| }; |
| |
| template <typename MatrixType> |
| struct BunchKaufman_Traits<MatrixType, Upper> { |
| using MatrixL = const TriangularView<const typename MatrixType::AdjointReturnType, UnitLower>; |
| using MatrixU = const TriangularView<const MatrixType, UnitUpper>; |
| static inline MatrixL getL(const MatrixType& m) { return MatrixL(m.adjoint()); } |
| static inline MatrixU getU(const MatrixType& m) { return MatrixU(m); } |
| }; |
| |
| } // end namespace internal |
| |
| template <typename MatrixType, int UpLo_> |
| template <typename Derived> |
| void BunchKaufman<MatrixType, UpLo_>::applyD(MatrixBase<Derived>& x) const { |
| const Index n = m_matrix.rows(); |
| Index k = 0; |
| while (k < n) { |
| if (k + 1 < n && !numext::is_exactly_zero(m_subdiag.coeff(k))) { |
| const RealScalar d11 = numext::real(m_matrix.coeff(k, k)); |
| const RealScalar d22 = numext::real(m_matrix.coeff(k + 1, k + 1)); |
| const Scalar d21 = m_subdiag.coeff(k); |
| for (Index j = 0; j < x.cols(); ++j) { |
| const Scalar x0 = x.coeff(k, j); |
| const Scalar x1 = x.coeff(k + 1, j); |
| x.coeffRef(k, j) = d11 * x0 + numext::conj(d21) * x1; |
| x.coeffRef(k + 1, j) = d21 * x0 + d22 * x1; |
| } |
| k += 2; |
| } else { |
| x.row(k) *= numext::real(m_matrix.coeff(k, k)); |
| k += 1; |
| } |
| } |
| } |
| |
| template <typename MatrixType, int UpLo_> |
| template <bool Conjugate, typename Derived> |
| void BunchKaufman<MatrixType, UpLo_>::solveInPlaceD(MatrixBase<Derived>& x) const { |
| using numext::abs; |
| const Index n = m_matrix.rows(); |
| // Use the pseudo-inverse of singular 1x1 blocks (see Eigen bug 241 for LDLT). The 2x2 blocks |
| // produced by Bunch-Kaufman pivoting are non-singular by construction. |
| const RealScalar tol = (std::numeric_limits<RealScalar>::min)(); |
| Index k = 0; |
| while (k < n) { |
| if (k + 1 < n && !numext::is_exactly_zero(m_subdiag.coeff(k))) { |
| const RealScalar d11 = numext::real(m_matrix.coeff(k, k)); |
| const RealScalar d22 = numext::real(m_matrix.coeff(k + 1, k + 1)); |
| // D = [ d11 conj(d21) ; d21 d22 ]; for the transpose solve use conj(d21) instead of d21. |
| const Scalar d21 = Conjugate ? m_subdiag.coeff(k) : numext::conj(m_subdiag.coeff(k)); |
| // Scaled 2x2 solve (LAPACK xSYTRS/xHETRS): divide through by d21 so the scaled determinant |
| // denom = det/|d21|^2 is O(1); det = d11*d22 - |d21|^2 is never formed (it over/underflows on |
| // extreme-scaled blocks, e.g. [[0,s],[s,0]], s=1e+-200). Every quotient divides by d21 itself: |
| // a hoisted 1/d21 overflows once |d21| is subnormal (issue #3142). |
| const Scalar cjd = numext::conj(d21); |
| const Scalar ak = d22 / d21; |
| const Scalar akm1 = d11 / cjd; |
| const RealScalar t = RealScalar(1) / (numext::real(ak * akm1) - RealScalar(1)); |
| for (Index j = 0; j < x.cols(); ++j) { |
| const Scalar bk = x.coeff(k + 1, j) / d21; |
| const Scalar bkm1 = x.coeff(k, j) / cjd; |
| x.coeffRef(k, j) = t * (ak * bkm1 - bk); |
| x.coeffRef(k + 1, j) = t * (akm1 * bk - bkm1); |
| } |
| k += 2; |
| } else { |
| const RealScalar dk = numext::real(m_matrix.coeff(k, k)); |
| if (abs(dk) > tol) |
| x.row(k) /= dk; |
| else |
| x.row(k).setZero(); |
| k += 1; |
| } |
| } |
| } |
| |
| // det(D_k) itself over- or underflows on an extreme-scaled 2x2 block, so it is only ever formed scaled by |
| // |d21|^2. Divide by |d21| twice rather than multiply by its reciprocal, which overflows once |d21| is |
| // subnormal. The pivot criterion bounds |q| by a^2 < 1 with a = (1+sqrt(17))/8, so |q| >= 1 or NaN is an |
| // artifact of d22/d21 overflowing -- the criterion bounds |d22| only against its own row -- and |
| // det(D_k) = -|d21|^2 to within that same bound there. |
| template <typename MatrixType, int UpLo_> |
| typename BunchKaufman<MatrixType, UpLo_>::RealScalar BunchKaufman<MatrixType, UpLo_>::scaledBlockDeterminant( |
| const RealScalar& d11, const RealScalar& d22, const RealScalar& d21) { |
| const RealScalar q = (d11 / d21) * (d22 / d21); |
| return (q > RealScalar(-1) && q < RealScalar(1)) ? q - RealScalar(1) : RealScalar(-1); |
| } |
| |
| template <typename MatrixType, int UpLo_> |
| void BunchKaufman<MatrixType, UpLo_>::computeInertia() { |
| const Index n = m_matrix.rows(); |
| m_n_pos = m_n_neg = m_n_zero = 0; |
| Index k = 0; |
| while (k < n) { |
| if (k + 1 < n && !numext::is_exactly_zero(m_subdiag.coeff(k))) { |
| const RealScalar d11 = numext::real(m_matrix.coeff(k, k)); |
| const RealScalar d22 = numext::real(m_matrix.coeff(k + 1, k + 1)); |
| const RealScalar d21 = numext::abs(m_subdiag.coeff(k)); |
| const RealScalar denom = scaledBlockDeterminant(d11, d22, d21); |
| if (denom < RealScalar(0)) { |
| // Indefinite 2x2 block: one positive and one negative eigenvalue. |
| ++m_n_pos; |
| ++m_n_neg; |
| } else if (numext::is_exactly_zero(denom)) { |
| const RealScalar tr = d11 + d22; |
| if (tr > RealScalar(0)) |
| ++m_n_pos; |
| else if (tr < RealScalar(0)) |
| ++m_n_neg; |
| else |
| ++m_n_zero; |
| ++m_n_zero; |
| } else { |
| // denom > 0: both eigenvalues share the sign of the trace. |
| if (d11 + d22 > RealScalar(0)) |
| m_n_pos += 2; |
| else |
| m_n_neg += 2; |
| } |
| k += 2; |
| } else { |
| const RealScalar dk = numext::real(m_matrix.coeff(k, k)); |
| if (dk > RealScalar(0)) |
| ++m_n_pos; |
| else if (dk < RealScalar(0)) |
| ++m_n_neg; |
| else |
| ++m_n_zero; |
| k += 1; |
| } |
| } |
| } |
| |
| /** Compute / recompute the Bunch-Kaufman factorization \f$ P A P^T = L D L^* \f$ (or \f$ U^* D U \f$ for the |
| * upper variant) of \a a. */ |
| template <typename MatrixType, int UpLo_> |
| template <typename InputType> |
| BunchKaufman<MatrixType, UpLo_>& BunchKaufman<MatrixType, UpLo_>::compute(const EigenBase<InputType>& a) { |
| eigen_assert(a.rows() == a.cols()); |
| const Index size = a.rows(); |
| |
| m_matrix = a.derived(); |
| |
| // L1 norm of the implicit self-adjoint matrix, for rcond(). |
| m_l1_norm = m_matrix.template selfadjointView<UpLo_>().l1Norm(); |
| |
| m_transpositions.resize(size); |
| m_subdiag.resize(size); |
| m_isInitialized = false; |
| |
| Index info = internal::bunch_kaufman<UpLo>::blocked(m_matrix, m_transpositions, m_subdiag, m_workspace); |
| m_info = (info == 0) ? Success : NumericalIssue; |
| |
| // The upper variant factorizes the transpose of the (Hermitian) matrix, i.e. its complex conjugate, |
| // so the recorded 2x2 off-diagonals of D come out conjugated; undo that so that m_subdiag holds the |
| // true sub-diagonal D(k+1,k) consumed by applyD()/solveInPlaceD()/computeInertia(). (No-op when real.) |
| EIGEN_IF_CONSTEXPR (int(UpLo) == int(Upper) && NumTraits<Scalar>::IsComplex) { |
| m_subdiag = m_subdiag.conjugate(); |
| } |
| |
| computeInertia(); |
| |
| m_isInitialized = true; |
| return *this; |
| } |
| |
| #ifndef EIGEN_PARSED_BY_DOXYGEN |
| template <typename MatrixType_, int UpLo_> |
| template <typename RhsType, typename DstType> |
| void BunchKaufman<MatrixType_, UpLo_>::_solve_impl(const RhsType& rhs, DstType& dst) const { |
| _solve_impl_transposed<true>(rhs, dst); |
| } |
| |
| template <typename MatrixType_, int UpLo_> |
| template <bool Conjugate, typename RhsType, typename DstType> |
| void BunchKaufman<MatrixType_, UpLo_>::_solve_impl_transposed(const RhsType& rhs, DstType& dst) const { |
| // A^{-1} b = P^T L^{-*} D^{-1} L^{-1} P b (and the conjugated variants for transpose / adjoint solves). |
| dst = m_transpositions * rhs; |
| matrixL().template conjugateIf<!Conjugate>().solveInPlace(dst); |
| solveInPlaceD<Conjugate>(dst); |
| matrixL().transpose().template conjugateIf<Conjugate>().solveInPlace(dst); |
| dst = m_transpositions.transpose() * dst; |
| } |
| #endif |
| |
| /** \internal use x = bunchKaufman_object.solve(x); |
| * |
| * This is the \em in-place version of solve(). |
| * |
| * \returns true always. |
| */ |
| template <typename MatrixType, int UpLo_> |
| template <typename Derived> |
| bool BunchKaufman<MatrixType, UpLo_>::solveInPlace(MatrixBase<Derived>& bAndX) const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| eigen_assert(m_matrix.rows() == bAndX.rows()); |
| bAndX = this->solve(bAndX); |
| return true; |
| } |
| |
| template <typename MatrixType, int UpLo_> |
| typename BunchKaufman<MatrixType, UpLo_>::RealScalar BunchKaufman<MatrixType, UpLo_>::determinantD() const { |
| const Index n = m_matrix.rows(); |
| // det(D) is accumulated as mantissa * 2^exponent, the mantissa renormalized to [1/2, 1) after each |
| // block. Both a single 2x2 block determinant and the running product can leave the representable range |
| // while det(D) itself stays in it, and an overflowed block meeting an underflowed one gives NaN. |
| RealScalar mantissa(1); |
| Index exponent = 0; |
| Index k = 0; |
| while (k < n) { |
| RealScalar blockM, blockE; |
| if (k + 1 < n && !numext::is_exactly_zero(m_subdiag.coeff(k))) { |
| const RealScalar d11 = numext::real(m_matrix.coeff(k, k)); |
| const RealScalar d22 = numext::real(m_matrix.coeff(k + 1, k + 1)); |
| const RealScalar d21 = numext::abs(m_subdiag.coeff(k)); |
| RealScalar e21; |
| const RealScalar m21 = internal::pfrexp<RealScalar>(d21, e21); |
| blockM = m21 * m21 * scaledBlockDeterminant(d11, d22, d21); |
| blockE = RealScalar(2) * e21; |
| k += 2; |
| } else { |
| blockM = internal::pfrexp<RealScalar>(numext::real(m_matrix.coeff(k, k)), blockE); |
| k += 1; |
| } |
| // |blockM| < 2 and, unless the block is singular, above 1/16, so the product below stays normal and |
| // its rounding is the only error this step adds. |
| RealScalar renorm; |
| mantissa = internal::pfrexp<RealScalar>(mantissa * blockM, renorm); |
| exponent += Index(blockE) + Index(renorm); |
| } |
| // ldexp() saturates to zero or infinity but takes an int exponent; past this magnitude it has already |
| // saturated, so clamping first cannot change the result. |
| const Index limit = Index(NumTraits<RealScalar>::max_exponent()) - Index(NumTraits<RealScalar>::min_exponent()) + |
| Index(NumTraits<RealScalar>::digits()); |
| return numext::ldexp(mantissa, int(numext::mini(numext::maxi(exponent, -limit), limit))); |
| } |
| |
| // A = P^T L D L^* P with L unit triangular, so det(A) = det(D) = prod over the 1x1 and 2x2 blocks of D. |
| |
| template <typename MatrixType, int UpLo_> |
| typename BunchKaufman<MatrixType, UpLo_>::Scalar BunchKaufman<MatrixType, UpLo_>::determinant() const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| return Scalar(determinantD()); |
| } |
| |
| template <typename MatrixType, int UpLo_> |
| typename BunchKaufman<MatrixType, UpLo_>::RealScalar BunchKaufman<MatrixType, UpLo_>::absDeterminant() const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| return numext::abs(determinantD()); |
| } |
| |
| template <typename MatrixType, int UpLo_> |
| typename BunchKaufman<MatrixType, UpLo_>::RealScalar BunchKaufman<MatrixType, UpLo_>::logAbsDeterminant() const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| const Index n = m_matrix.rows(); |
| RealScalar result(0); |
| Index k = 0; |
| while (k < n) { |
| if (k + 1 < n && !numext::is_exactly_zero(m_subdiag.coeff(k))) { |
| // log|det(D_k)| = 2 log|d21| + log|det(D_k)/|d21|^2|. |
| const RealScalar d11 = numext::real(m_matrix.coeff(k, k)); |
| const RealScalar d22 = numext::real(m_matrix.coeff(k + 1, k + 1)); |
| const RealScalar d21 = numext::abs(m_subdiag.coeff(k)); |
| const RealScalar scaled = scaledBlockDeterminant(d11, d22, d21); |
| result += RealScalar(2) * numext::log(d21) + numext::log(numext::abs(scaled)); |
| k += 2; |
| } else { |
| result += numext::log(numext::abs(numext::real(m_matrix.coeff(k, k)))); |
| k += 1; |
| } |
| } |
| return result; |
| } |
| |
| template <typename MatrixType, int UpLo_> |
| typename BunchKaufman<MatrixType, UpLo_>::Scalar BunchKaufman<MatrixType, UpLo_>::signDeterminant() const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| if (m_n_zero > 0) return Scalar(0); |
| return Scalar((m_n_neg % 2 == 0) ? 1 : -1); |
| } |
| |
| /** \returns the matrix represented by the decomposition, i.e., the product \f$ P^T L D L^* P \f$. |
| * This function is provided for debug purposes. */ |
| template <typename MatrixType, int UpLo_> |
| MatrixType BunchKaufman<MatrixType, UpLo_>::reconstructedMatrix() const { |
| eigen_assert(m_isInitialized && "BunchKaufman is not initialized."); |
| const Index size = m_matrix.rows(); |
| MatrixType res(size, size); |
| |
| res.setIdentity(); |
| res = transpositionsP() * res; // P |
| res = matrixU() * res; // U P = L^* P |
| applyD(res); // D L^* P |
| res = matrixL() * res; // L D L^* P |
| res = transpositionsP().transpose() * res; // P^T L D L^* P |
| |
| return res; |
| } |
| |
| /** \cholesky_module |
| * \returns the Bunch-Kaufman factorization of \c *this |
| * \sa SelfAdjointView::bunchKaufman() |
| */ |
| template <typename MatrixType, unsigned int UpLo> |
| inline BunchKaufman<typename SelfAdjointView<MatrixType, UpLo>::PlainObject, UpLo> |
| SelfAdjointView<MatrixType, UpLo>::bunchKaufman() const { |
| return BunchKaufman<PlainObject, UpLo>(m_matrix); |
| } |
| |
| /** \cholesky_module |
| * \returns the Bunch-Kaufman factorization of \c *this |
| * \sa SelfAdjointView::bunchKaufman() |
| */ |
| template <typename Derived> |
| inline BunchKaufman<typename MatrixBase<Derived>::PlainObject> MatrixBase<Derived>::bunchKaufman() const { |
| return BunchKaufman<PlainObject>(derived()); |
| } |
| |
| } // end namespace Eigen |
| |
| #endif // EIGEN_BUNCHKAUFMAN_H |