Fix default rank-detection threshold in QR and LU decompositions

libeigen/eigen!2232

Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
This commit is contained in:
Rasmus Munk Larsen
2026-03-03 18:44:22 -08:00
parent d36a7db7b5
commit b0ebf966a5
5 changed files with 118 additions and 10 deletions

View File

@@ -321,9 +321,10 @@ class FullPivLU : public SolverBase<FullPivLU<MatrixType_, PermutationIndex_> >
RealScalar threshold() const {
eigen_assert(m_isInitialized || m_usePrescribedThreshold);
return m_usePrescribedThreshold ? m_prescribedThreshold
// this formula comes from experimenting (see "LU precision tuning" thread on the
// list) and turns out to be identical to Higham's formula used already in LDLt.
: NumTraits<Scalar>::epsilon() * RealScalar(m_lu.diagonalSize());
// Higham's backward error bound for Gaussian elimination with
// complete pivoting (Theorem 9.4) is ||ΔA||₂ ≤ c·min(m,n)·u·||A||₂.
// The factor of 4 covers the constant c.
: NumTraits<Scalar>::epsilon() * RealScalar(4 * m_lu.diagonalSize());
}
/** \returns the rank of the matrix of which *this is the LU decomposition.

View File

@@ -375,9 +375,10 @@ class ColPivHouseholderQR : public SolverBase<ColPivHouseholderQR<MatrixType_, P
RealScalar threshold() const {
eigen_assert(m_isInitialized || m_usePrescribedThreshold);
return m_usePrescribedThreshold ? m_prescribedThreshold
// this formula comes from experimenting (see "LU precision tuning" thread on the
// list) and turns out to be identical to Higham's formula used already in LDLt.
: NumTraits<Scalar>::epsilon() * RealScalar(m_qr.diagonalSize());
// Higham's backward error bound for Householder QR (Theorem 19.4) is
// ||ΔA||₂ ≤ c·min(m,n)·u·||A||₂. The factor of 4 covers the
// constant c (typically 36 worst-case, ~1 probabilistically).
: NumTraits<Scalar>::epsilon() * RealScalar(4 * m_qr.diagonalSize());
}
/** \returns the number of nonzero pivots in the QR decomposition.

View File

@@ -97,7 +97,9 @@ struct ColPivHouseholderQR_LAPACKE_impl {
maxpivot = qr.diagonal().cwiseAbs().maxCoeff();
hCoeffs.adjointInPlace();
RealScalar defaultThreshold = NumTraits<RealScalar>::epsilon() * RealScalar(qr.diagonalSize());
// Higham's backward error bound (Theorem 19.4): ||ΔA||₂ ≤ c·min(m,n)·u·||A||₂.
// The factor of 4 covers the constant c (typically 36 worst-case).
RealScalar defaultThreshold = NumTraits<RealScalar>::epsilon() * RealScalar(4 * qr.diagonalSize());
RealScalar threshold = usePrescribedThreshold ? prescribedThreshold : defaultThreshold;
RealScalar premultiplied_threshold = maxpivot * threshold;
nonzero_pivots = (qr.diagonal().cwiseAbs().array() > premultiplied_threshold).count();

View File

@@ -396,9 +396,10 @@ class FullPivHouseholderQR : public SolverBase<FullPivHouseholderQR<MatrixType_,
RealScalar threshold() const {
eigen_assert(m_isInitialized || m_usePrescribedThreshold);
return m_usePrescribedThreshold ? m_prescribedThreshold
// this formula comes from experimenting (see "LU precision tuning" thread on the
// list) and turns out to be identical to Higham's formula used already in LDLt.
: NumTraits<Scalar>::epsilon() * RealScalar(m_qr.diagonalSize());
// Higham's backward error bound for Householder QR (Theorem 19.4) is
// ||ΔA||₂ ≤ c·min(m,n)·u·||A||₂. The factor of 4 covers the
// constant c (typically 36 worst-case, ~1 probabilistically).
: NumTraits<Scalar>::epsilon() * RealScalar(4 * m_qr.diagonalSize());
}
/** \returns the number of nonzero pivots in the QR decomposition.