blob: eda5dad2f6cda985753c21243d1b86871320b60f [file] [edit]
// 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_> {
typedef MatrixXpr XprKind;
typedef SolverStorage StorageKind;
typedef int StorageIndex;
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:
typedef MatrixType_ MatrixType;
typedef SolverBase<BunchKaufman> Base;
friend class SolverBase<BunchKaufman>;
EIGEN_GENERIC_PUBLIC_INTERFACE(BunchKaufman)
enum {
MaxRowsAtCompileTime = MatrixType::MaxRowsAtCompileTime,
MaxColsAtCompileTime = MatrixType::MaxColsAtCompileTime,
UpLo = UpLo_
};
typedef Matrix<Scalar, RowsAtCompileTime, 1, 0, MaxRowsAtCompileTime, 1> TmpVectorType;
// Panel workspace for the blocked algorithm (only allocated for large dynamic-sized problems).
typedef Matrix<Scalar, Dynamic, Dynamic> WorkspaceType;
typedef Transpositions<RowsAtCompileTime, MaxRowsAtCompileTime> TranspositionType;
typedef PermutationMatrix<RowsAtCompileTime, MaxRowsAtCompileTime> PermutationType;
typedef internal::BunchKaufman_Traits<MatrixType, UpLo> Traits;
/** \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 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).
*/
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();
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) {
typedef typename MatrixType::Scalar 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;
typedef typename MatrixType::Scalar Scalar;
typedef typename MatrixType::RealScalar RealScalar;
typedef typename TranspositionType::StorageIndex 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). The reciprocals MUST use Eigen's overflow-safe Scalar division.
const Scalar id = Scalar(1) / d21;
const Scalar icjd = numext::conj(id); // 1 / conj(d21)
const Scalar ak = d22 * id;
const Scalar akm1 = d11 * icjd;
const RealScalar denom = numext::real(ak * akm1) - RealScalar(1);
// A non-finite 2x2 block (e.g. a NaN pulled in from a candidate row/column) is a numerical
// failure; flag it so it is reported rather than silently propagated.
if (info == 0 && (numext::isnan)(denom)) info = k + 1;
const Index rs = n - k - 2;
if (rs > 0) {
const RealScalar t = RealScalar(1) / denom;
auto A22 = mat.block(k + 2, k + 2, rs, rs);
auto c0 = mat.col(k).tail(rs);
auto c1 = mat.col(k + 1).tail(rs);
// Store the unit lower factor columns L = U D^{-1} (U = [c0 c1]) in scaled form:
// L_k = t*(ak*u0 - u1)*icjd, L_{k+1} = t*(akm1*u1 - u0)*id (= the unscaled rows of U D^{-1}).
for (Index i = 0; i < rs; ++i) {
const Scalar u0 = c0.coeff(i);
const Scalar u1 = c1.coeff(i);
c0.coeffRef(i) = t * (ak * u0 - u1) * icjd;
c1.coeffRef(i) = t * (akm1 * u1 - u0) * id;
}
// Trailing update A22 <- A22 - L D L^* (== A22 - U D^{-1} U^*, since L = U D^{-1}), using the
// just-stored scaled L columns and the ORIGINAL block entries d11,d22,d21 as coefficients --
// so no 1/det factor (which would over/underflow) appears. Two rank-1 (syr) and one rank-2
// (syr2) self-adjoint updates.
A22.template selfadjointView<Lower>().rankUpdate(c0, -d11);
A22.template selfadjointView<Lower>().rankUpdate(c1, -d22);
A22.template selfadjointView<Lower>().rankUpdate(c0, c1, -numext::conj(d21));
}
// 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;
typedef typename MatrixType::Scalar Scalar;
typedef typename MatrixType::RealScalar RealScalar;
typedef typename TranspositionType::StorageIndex StorageIndex;
const Index n = mat.rows();
const RealScalar alpha = bunch_kaufman_alpha<RealScalar>();
const 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);
if (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();
if (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);
if (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();
if (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 id = Scalar(1) / d21;
const Scalar icjd = numext::conj(id); // 1 / conj(d21)
const Scalar ak = d22 * id;
const Scalar akm1 = d11 * icjd;
const RealScalar denom = numext::real(ak * akm1) - RealScalar(1);
if (info == 0 && (numext::isnan)(denom)) 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:
// L_k = (t*icjd)*(ak*w0 - w1), L_{k+1} = (t*id)*(akm1*w1 - w0).
const RealScalar t = RealScalar(1) / denom;
const Scalar tic = t * icjd;
const Scalar tid = t * id;
auto w0 = W.col(j).segment(jc + 2, rs);
auto w1 = W.col(j + 1).segment(jc + 2, rs);
mat.col(jc).tail(rs) = tic * (ak * w0 - w1);
mat.col(jc + 1).tail(rs) = tid * (akm1 * w1 - w0);
}
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> {
typedef const TriangularView<const MatrixType, UnitLower> MatrixL;
typedef const TriangularView<const typename MatrixType::AdjointReturnType, UnitUpper> MatrixU;
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> {
typedef const TriangularView<const typename MatrixType::AdjointReturnType, UnitLower> MatrixL;
typedef const TriangularView<const MatrixType, UnitUpper> MatrixU;
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). Reciprocals use overflow-safe division.
const Scalar id = Scalar(1) / d21;
const Scalar icjd = numext::conj(id); // 1 / conj(d21)
const Scalar ak = d22 * id;
const Scalar akm1 = d11 * icjd;
const RealScalar t = RealScalar(1) / (numext::real(ak * akm1) - RealScalar(1));
for (Index j = 0; j < x.cols(); ++j) {
const Scalar x0 = x.coeff(k, j);
const Scalar x1 = x.coeff(k + 1, j);
const Scalar bk = x1 * id;
const Scalar bkm1 = x0 * icjd;
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;
}
}
}
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 Scalar d21 = m_subdiag.coeff(k);
// Scaled determinant denom = det/|d21|^2 (|d21|^2 > 0 for a 2x2 block), so sign(denom) == sign(det);
// avoids forming det = d11*d22 - |d21|^2, which over/underflows on extreme-scaled 2x2 blocks.
const Scalar id = Scalar(1) / d21;
const RealScalar denom = numext::real((d22 * id) * (d11 * numext::conj(id))) - RealScalar(1);
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;
}
/** \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