Eigen  5.0.1
 
Loading...
Searching...
No Matches
BunchKaufman.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// Copyright (C) 2026 Rasmus Munk Larsen <rmlarsen@gmail.com>
5//
6// This Source Code Form is subject to the terms of the Mozilla
7// Public License v. 2.0. If a copy of the MPL was not distributed
8// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
9// SPDX-License-Identifier: MPL-2.0
10
11#ifndef EIGEN_BUNCHKAUFMAN_H
12#define EIGEN_BUNCHKAUFMAN_H
13
14// IWYU pragma: private
15#include "./InternalHeaderCheck.h"
16
17namespace Eigen {
18
19namespace internal {
20template <typename MatrixType_, int UpLo_>
21struct traits<BunchKaufman<MatrixType_, UpLo_> > : traits<MatrixType_> {
22 using XprKind = MatrixXpr;
23 using StorageKind = SolverStorage;
24 using StorageIndex = int;
25 enum { Flags = 0 };
26};
27
28template <typename MatrixType, int UpLo>
29struct BunchKaufman_Traits;
30
31// Panel width for the blocked factorization (defined below); forward-declared so the size
32// constructor can pre-allocate the panel workspace.
33template <typename Scalar>
34inline Index bunch_kaufman_blocksize();
35} // namespace internal
36
74template <typename MatrixType_, int UpLo_>
75class BunchKaufman : public SolverBase<BunchKaufman<MatrixType_, UpLo_> > {
76 public:
77 using MatrixType = MatrixType_;
78 using Base = SolverBase<BunchKaufman>;
79 friend class SolverBase<BunchKaufman>;
80
81 EIGEN_GENERIC_PUBLIC_INTERFACE(BunchKaufman)
82 enum {
83 MaxRowsAtCompileTime = MatrixType::MaxRowsAtCompileTime,
84 MaxColsAtCompileTime = MatrixType::MaxColsAtCompileTime,
85 UpLo = UpLo_
86 };
87
89 // Panel workspace for the blocked algorithm (only allocated for large dynamic-sized problems).
90 using WorkspaceType = Matrix<Scalar, Dynamic, Dynamic>;
93
94 using Traits = internal::BunchKaufman_Traits<MatrixType, UpLo>;
95
102 : m_matrix(),
103 m_l1_norm(0),
104 m_transpositions(),
105 m_subdiag(),
106 m_n_pos(0),
107 m_n_neg(0),
108 m_n_zero(0),
109 m_isInitialized(false),
110 m_info(InvalidInput) {}
111
118 explicit BunchKaufman(Index size)
119 : m_matrix(size, size),
120 m_l1_norm(0),
121 m_transpositions(size),
122 m_subdiag(size),
123 // Pre-allocate the panel workspace to the exact shape compute() needs, so that a subsequent
124 // compute() on a problem of this size performs no heap allocation (the blocked factorization
125 // resizes it to n x (nb+1); resizing to the same shape is a no-op).
126 m_workspace(size, internal::bunch_kaufman_blocksize<Scalar>() + 1),
127 m_n_pos(0),
128 m_n_neg(0),
129 m_n_zero(0),
130 m_isInitialized(false),
131 m_info(InvalidInput) {}
132
139 template <typename InputType>
140 explicit BunchKaufman(const EigenBase<InputType>& matrix)
141 : m_matrix(matrix.rows(), matrix.cols()),
142 m_l1_norm(0),
143 m_transpositions(matrix.rows()),
144 m_subdiag(matrix.rows()),
145 m_n_pos(0),
146 m_n_neg(0),
147 m_n_zero(0),
148 m_isInitialized(false),
149 m_info(InvalidInput) {
150 compute(matrix.derived());
151 }
152
160 template <typename InputType>
162 : m_matrix(matrix.derived()),
163 m_l1_norm(0),
164 m_transpositions(matrix.rows()),
165 m_subdiag(matrix.rows()),
166 m_n_pos(0),
167 m_n_neg(0),
168 m_n_zero(0),
169 m_isInitialized(false),
170 m_info(InvalidInput) {
171 computeInPlace();
172 }
173
175 inline typename Traits::MatrixU matrixU() const {
176 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
177 return Traits::getU(m_matrix);
178 }
179
181 inline typename Traits::MatrixL matrixL() const {
182 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
183 return Traits::getL(m_matrix);
184 }
185
188 inline const TranspositionType& transpositionsP() const {
189 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
190 return m_transpositions;
191 }
192
199 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
200 return m_matrix.diagonal();
201 }
202
208 inline const TmpVectorType& subDiagonal() const {
209 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
210 return m_subdiag;
211 }
212
214 inline bool isPositive() const {
215 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
216 return m_n_neg == 0;
217 }
218
220 inline bool isNegative() const {
221 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
222 return m_n_pos == 0;
223 }
224
225#ifdef EIGEN_PARSED_BY_DOXYGEN
234 template <typename Rhs>
236#endif
237
238 template <typename Derived>
239 bool solveInPlace(MatrixBase<Derived>& bAndX) const;
240
241 template <typename InputType>
242 BunchKaufman& compute(const EigenBase<InputType>& matrix);
243
247 RealScalar rcond() const {
248 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
249 return internal::rcond_estimate_helper(m_l1_norm, *this);
250 }
251
258 inline const MatrixType& matrixLDLT() const {
259 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
260 return m_matrix;
261 }
262
263 MatrixType reconstructedMatrix() const;
264
278 Scalar determinant() const;
279
292 RealScalar absDeterminant() const;
293
307 RealScalar logAbsDeterminant() const;
308
318 Scalar signDeterminant() const;
319
326 const BunchKaufman& adjoint() const { return *this; }
327
328 EIGEN_DEVICE_FUNC constexpr Index rows() const noexcept { return m_matrix.rows(); }
329 EIGEN_DEVICE_FUNC constexpr Index cols() const noexcept { return m_matrix.cols(); }
330
339 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
340 return m_info;
341 }
342
343#ifndef EIGEN_PARSED_BY_DOXYGEN
344 template <typename RhsType, typename DstType>
345 void _solve_impl(const RhsType& rhs, DstType& dst) const;
346
347 template <bool Conjugate, typename RhsType, typename DstType>
348 void _solve_impl_transposed(const RhsType& rhs, DstType& dst) const;
349#endif
350
351 protected:
352 EIGEN_STATIC_ASSERT_NON_INTEGER(Scalar)
353
354
355 template <typename Derived>
356 void applyD(MatrixBase<Derived>& x) const;
357
360 template <bool Conjugate, typename Derived>
361 void solveInPlaceD(MatrixBase<Derived>& x) const;
362
363 BunchKaufman& computeInPlace();
364
366 void computeInertia();
367
368 /** \internal \returns \f$ \det(D_k)/|d_{21}|^2 \f$ for a 2x2 block of D, which is real and shares the
369 * sign of \f$ \det(D_k) \f$ since \f$ |d_{21}| > 0 \f$ there. */
370 static RealScalar scaledBlockDeterminant(const RealScalar& d11, const RealScalar& d22, const RealScalar& d21);
371
376 RealScalar determinantD() const;
377
378 MatrixType m_matrix;
379 RealScalar m_l1_norm;
380 TranspositionType m_transpositions;
381 TmpVectorType m_subdiag;
382 WorkspaceType m_workspace;
383 Index m_n_pos;
384 Index m_n_neg;
385 Index m_n_zero;
386 bool m_isInitialized;
387 ComputationInfo m_info;
388};
389
390namespace internal {
391
395template <typename RealScalar>
396EIGEN_DEVICE_FUNC inline RealScalar bunch_kaufman_alpha() {
397 using std::sqrt;
398 return (RealScalar(1) + sqrt(RealScalar(17))) / RealScalar(8);
399}
400
403template <typename Scalar>
404inline Index bunch_kaufman_blocksize() {
405#ifdef EIGEN_BUNCHKAUFMAN_BLOCKSIZE
406 return Index(EIGEN_BUNCHKAUFMAN_BLOCKSIZE);
407#else
408 return 64;
409#endif
410}
411
412template <int UpLo>
413struct bunch_kaufman;
414
415template <>
416struct bunch_kaufman<Lower> {
417 // Interchange rows and columns kk and kp (kp >= kk) in the lower triangle of the Hermitian matrix
418 // `mat`, including the already-computed factor columns to the left of column `kfirst` (so that the
419 // stored unit triangular factor stays consistent with a single up-front permutation P). `kfirst` is
420 // the first column of the pivot block (== kk for a 1x1 pivot, == kk-1 for a 2x2 pivot).
421 template <typename MatrixType>
422 static void apply_symmetric_pivot(MatrixType& mat, Index kfirst, Index kk, Index kp, Index kstep) {
423 using Scalar = typename MatrixType::Scalar;
424 const Index n = mat.rows();
425 const Index s = n - kp - 1;
426 if (s > 0) mat.col(kk).tail(s).swap(mat.col(kp).tail(s));
427 for (Index i = kk + 1; i < kp; ++i) {
428 Scalar tmp = mat.coeff(i, kk);
429 mat.coeffRef(i, kk) = numext::conj(mat.coeff(kp, i));
430 mat.coeffRef(kp, i) = numext::conj(tmp);
431 }
432 numext::swap(mat.coeffRef(kk, kk), mat.coeffRef(kp, kp));
433 EIGEN_IF_CONSTEXPR (NumTraits<Scalar>::IsComplex) {
434 mat.coeffRef(kp, kk) = numext::conj(mat.coeff(kp, kk));
435 }
436 if (kfirst > 0) mat.row(kk).head(kfirst).swap(mat.row(kp).head(kfirst));
437 if (kstep == 2) {
438 numext::swap(mat.coeffRef(kfirst + 1, kfirst), mat.coeffRef(kp, kfirst));
439 }
440 }
441
442 // Unblocked (level-2 BLAS) Bunch-Kaufman factorization of the lower triangle of `mat`, in place.
443 // Columns [k0, n) are factorized. On output:
444 // - the strictly-lower triangle holds the unit lower factor L (with explicit zeros at the
445 // sub-diagonal positions of 2x2 blocks),
446 // - the diagonal holds the main diagonal of D,
447 // - subdiag(k) holds D(k+1,k) for each 2x2 block starting at column k (and 0 elsewhere),
448 // - transpositions encodes the symmetric permutation P (applied in increasing index order).
449 // Returns 0 on success, or the (1-based) index of the first exactly-zero pivot encountered.
450 template <typename MatrixType, typename TranspositionType, typename SubDiagType>
451 static Index unblocked(MatrixType& mat, TranspositionType& transpositions, SubDiagType& subdiag, Index k0 = 0) {
452 using numext::abs;
453 using Scalar = typename MatrixType::Scalar;
454 using RealScalar = typename MatrixType::RealScalar;
455 using StorageIndex = typename TranspositionType::StorageIndex;
456 const Index n = mat.rows();
457 const RealScalar alpha = bunch_kaufman_alpha<RealScalar>();
458 Index info = 0;
459
460 Index k = k0;
461 while (k < n) {
462 Index kstep = 1;
463 Index kp = k;
464 const RealScalar absakk = abs(numext::real(mat.coeff(k, k)));
465
466 // colmax = max_{i>k} |mat(i,k)|, attained at row imax.
467 Index imax = k;
468 RealScalar colmax(0);
469 if (k + 1 < n) {
470 Index rel = 0;
471 colmax = mat.col(k).tail(n - k - 1).cwiseAbs().maxCoeff(&rel);
472 imax = k + 1 + rel;
473 }
474
475 if (numext::is_exactly_zero((numext::maxi)(absakk, colmax)) || (numext::isnan)(absakk)) {
476 // The whole remaining column is zero: 1x1 zero pivot, matrix is singular.
477 kp = k;
478 if (info == 0) info = k + 1;
479 mat.coeffRef(k, k) = Scalar(numext::real(mat.coeff(k, k)));
480 } else if (absakk >= alpha * colmax) {
481 // 1x1 pivot at k, no interchange.
482 kp = k;
483 } else {
484 // rowmax = largest off-diagonal magnitude in row/column imax.
485 RealScalar rowmax = mat.row(imax).segment(k, imax - k).cwiseAbs().maxCoeff();
486 if (imax + 1 < n) {
487 RealScalar rowmax2 = mat.col(imax).tail(n - imax - 1).cwiseAbs().maxCoeff();
488 rowmax = (numext::maxi)(rowmax, rowmax2);
489 }
490 if (absakk >= alpha * colmax * (colmax / rowmax)) {
491 // 1x1 pivot at k.
492 kp = k;
493 } else if (abs(numext::real(mat.coeff(imax, imax))) >= alpha * rowmax) {
494 // 1x1 pivot at imax: interchange rows/columns k and imax.
495 kp = imax;
496 } else {
497 // 2x2 pivot at (k, imax): interchange rows/columns k+1 and imax.
498 kp = imax;
499 kstep = 2;
500 }
501 }
502
503 const Index kk = k + kstep - 1; // column of the pivot block to interchange with kp
504 if (kp != kk) apply_symmetric_pivot(mat, k, kk, kp, kstep);
505
506 if (kstep == 1) {
507 transpositions.coeffRef(k) = StorageIndex(kp);
508 subdiag.coeffRef(k) = Scalar(0);
509
510 const RealScalar dkk = numext::real(mat.coeff(k, k));
511 mat.coeffRef(k, k) = Scalar(dkk);
512 const Index rs = n - k - 1;
513 if (rs > 0) {
514 if (!numext::is_exactly_zero(dkk)) {
515 // A22 <- A22 - (1/dkk) w w^*, with w = mat(k+1:n, k); then L column = w / dkk.
516 auto w = mat.col(k).tail(rs);
517 mat.block(k + 1, k + 1, rs, rs).template selfadjointView<Lower>().rankUpdate(w, RealScalar(-1) / dkk);
518 w /= dkk;
519 } else if (info == 0) {
520 info = k + 1;
521 }
522 }
523 k += 1;
524 } else {
525 transpositions.coeffRef(k) = StorageIndex(k);
526 transpositions.coeffRef(k + 1) = StorageIndex(kp);
527
528 const RealScalar d11 = numext::real(mat.coeff(k, k));
529 const RealScalar d22 = numext::real(mat.coeff(k + 1, k + 1));
530 const Scalar d21 = mat.coeff(k + 1, k);
531 mat.coeffRef(k, k) = Scalar(d11);
532 mat.coeffRef(k + 1, k + 1) = Scalar(d22);
533 // Scaled 2x2 inverse (LAPACK xSYTF2/xHETF2 strategy). NEVER form det = d11*d22 - |d21|^2 or
534 // abs2(d21) directly: those over/underflow for well-conditioned but extreme-scaled blocks
535 // (e.g. [[0,s],[s,0]], s=1e200, where det = -s^2 overflows/underflows). Instead divide
536 // through by the off-diagonal d21, so the scaled determinant
537 // denom = real(ak*akm1) - 1 = det / |d21|^2 (with ak = d22/d21, akm1 = d11/conj(d21))
538 // stays O(1). Divide by d21 itself, never by a hoisted reciprocal: 1/d21 overflows once |d21|
539 // is subnormal, where these quotients are still finite (issue #3142).
540 const Scalar cjd = numext::conj(d21);
541 const Scalar ak = numext::divide(d22, d21);
542 const Scalar akm1 = numext::divide(d11, cjd);
543 const RealScalar denom = numext::real(ak * akm1) - RealScalar(1);
544 // The pivot criterion gives |d11 d22| <= alpha^2 |d21|^2 with alpha < 1, so in exact arithmetic
545 // -1 - alpha^2 < denom < alpha^2 - 1. Outside that range ak overflowed -- the criterion bounds
546 // |d22| only against the largest entry of its own row -- or a NaN entry was pulled into the
547 // block. Either way the block's inverse is not representable: report it rather than silently
548 // propagate it.
549 if (info == 0 && !(denom < RealScalar(0) && denom > RealScalar(-2))) info = k + 1;
550
551 const Index rs = n - k - 2;
552 if (rs > 0) {
553 // Fused factor-column computation and trailing update, in a single pass over the lower
554 // triangle of A22 (the xSYTF2/xHETF2 strategy). For each trailing column j, first form the
555 // two unit lower factor entries of row j (the rows of U D^{-1}, in the scaled form above),
556 // l0_j = t*((ak*u0_j - u1_j)/conj(d21)), l1_j = t*((akm1*u1_j - u0_j)/d21),
557 // dividing before scaling by t: the quotient is denom*l_j, within a factor 1 + alpha^2 of the
558 // result, whereas t*(ak*u0_j - u1_j) can overflow (|t| < 1/(1 - alpha^2)) where l_j is finite.
559 // Then update column j against the ORIGINAL pivot columns u = [u0 u1] (they carry the D
560 // scale, U = L*D, so no 1/det factor appears):
561 // A22(i,j) -= u0_i*conj(l0_j) + u1_i*conj(l1_j), i >= j.
562 // Rows < j of the pivot columns already hold L, rows >= j still hold U -- exactly the
563 // entries each column update needs, so no workspace is required. Compared with composing
564 // self-adjoint rank-1/rank-2 updates (syr + syr + syr2) this halves the flops and touches
565 // the trailing triangle once instead of three times.
566 const RealScalar t = RealScalar(1) / denom;
567 auto c0 = mat.col(k).tail(rs);
568 auto c1 = mat.col(k + 1).tail(rs);
569 for (Index j = 0; j < rs; ++j) {
570 const Scalar u0 = c0.coeff(j);
571 const Scalar u1 = c1.coeff(j);
572 const Scalar l0 = t * numext::divide(ak * u0 - u1, cjd);
573 const Scalar l1 = t * numext::divide(akm1 * u1 - u0, d21);
574 const Index len = rs - j;
575 mat.col(k + 2 + j).tail(len) -= numext::conj(l0) * c0.tail(len) + numext::conj(l1) * c1.tail(len);
576 c0.coeffRef(j) = l0;
577 c1.coeffRef(j) = l1;
578 // Keep the updated diagonal exactly real (the correction is Hermitian; roundoff would
579 // otherwise leave a tiny imaginary part).
580 EIGEN_IF_CONSTEXPR (NumTraits<Scalar>::IsComplex) {
581 mat.coeffRef(k + 2 + j, k + 2 + j) = Scalar(numext::real(mat.coeff(k + 2 + j, k + 2 + j)));
582 }
583 }
584 }
585 // Move the 2x2 off-diagonal of D out of the L storage.
586 subdiag.coeffRef(k) = d21;
587 subdiag.coeffRef(k + 1) = Scalar(0);
588 mat.coeffRef(k + 1, k) = Scalar(0);
589 k += 2;
590 }
591 }
592 return info;
593 }
594
595 // Partial factorization of a panel of at most `nb` columns of the lower triangle of `mat`, starting
596 // at column k0, using the Bunch-Kaufman method (level-2 within the panel). Follows the panel/workspace
597 // structure of LAPACK's xLASYF. The trailing sub-matrix mat(k0+kb:n, k0+kb:n) is left untouched; the
598 // workspace `W` (n x nb) returns, in its first kb columns, the product L21*D restricted to the panel
599 // columns, so that the caller can apply the deferred trailing update A22 <- A22 - L21 * (L21*D)^* with
600 // a single level-3 (triangular) matrix product. Returns the number kb (<= nb) of columns factorized.
601 template <typename MatrixType, typename WorkspaceType, typename TranspositionType, typename SubDiagType>
602 static Index partial_factor(MatrixType& mat, Index k0, Index nb, WorkspaceType& W, TranspositionType& transpositions,
603 SubDiagType& subdiag, Index& info) {
604 using numext::abs;
605 using Scalar = typename MatrixType::Scalar;
606 using RealScalar = typename MatrixType::RealScalar;
607 using StorageIndex = typename TranspositionType::StorageIndex;
608 const Index n = mat.rows();
609 const RealScalar alpha = bunch_kaufman_alpha<RealScalar>();
610 constexpr bool is_complex = NumTraits<Scalar>::IsComplex;
611
612 Index j = 0;
613 while (j < nb) {
614 const Index jc = k0 + j;
615 const Index h = n - jc;
616
617 // W(jc:n, j) <- updated column jc = (column jc of A) minus the contributions of the panel columns
618 // [k0, jc) already factorized (those are stored as L in mat, and L*D in W).
619 W.col(j).segment(jc, h) = mat.col(jc).segment(jc, h);
620 EIGEN_IF_CONSTEXPR (is_complex) W.coeffRef(jc, j) = Scalar(numext::real(W.coeff(jc, j)));
621 if (j > 0) {
622 W.col(j).segment(jc, h).noalias() -= mat.block(jc, k0, h, j) * W.row(jc).head(j).adjoint();
623 EIGEN_IF_CONSTEXPR (is_complex) W.coeffRef(jc, j) = Scalar(numext::real(W.coeff(jc, j)));
624 }
625
626 Index kstep = 1;
627 Index kp = jc;
628 const RealScalar absakk = abs(numext::real(W.coeff(jc, j)));
629 Index imax = jc;
630 RealScalar colmax(0);
631 if (jc + 1 < n) {
632 Index rel = 0;
633 colmax = W.col(j).segment(jc + 1, n - jc - 1).cwiseAbs().maxCoeff(&rel);
634 imax = jc + 1 + rel;
635 }
636
637 if (numext::is_exactly_zero((numext::maxi)(absakk, colmax)) || (numext::isnan)(absakk)) {
638 kp = jc;
639 if (info == 0) info = jc + 1;
640 } else if (absakk >= alpha * colmax) {
641 kp = jc;
642 } else {
643 // W(jc:n, j+1) <- updated column imax (its leading row part is read from row imax of mat and
644 // conjugated, the trailing part from column imax).
645 const Index hr = imax - jc;
646 if (hr > 0) W.col(j + 1).segment(jc, hr) = mat.row(imax).segment(jc, hr).adjoint();
647 W.col(j + 1).segment(imax, n - imax) = mat.col(imax).segment(imax, n - imax);
648 EIGEN_IF_CONSTEXPR (is_complex) W.coeffRef(imax, j + 1) = Scalar(numext::real(W.coeff(imax, j + 1)));
649 if (j > 0) {
650 W.col(j + 1).segment(jc, n - jc).noalias() -= mat.block(jc, k0, n - jc, j) * W.row(imax).head(j).adjoint();
651 EIGEN_IF_CONSTEXPR (is_complex) W.coeffRef(imax, j + 1) = Scalar(numext::real(W.coeff(imax, j + 1)));
652 }
653
654 RealScalar rowmax(0);
655 if (imax > jc) rowmax = W.col(j + 1).segment(jc, imax - jc).cwiseAbs().maxCoeff();
656 if (imax + 1 < n) {
657 RealScalar rowmax2 = W.col(j + 1).segment(imax + 1, n - imax - 1).cwiseAbs().maxCoeff();
658 rowmax = (numext::maxi)(rowmax, rowmax2);
659 }
660 if (absakk >= alpha * colmax * (colmax / rowmax)) {
661 kp = jc;
662 } else if (abs(numext::real(W.coeff(imax, j + 1))) >= alpha * rowmax) {
663 kp = imax;
664 // imax becomes the 1x1 pivot column: its updated column (in W(:,j+1)) replaces W(:,j).
665 W.col(j).segment(jc, n - jc) = W.col(j + 1).segment(jc, n - jc);
666 } else {
667 kp = imax;
668 kstep = 2;
669 }
670 }
671
672 // A 2x2 block must fit in the panel; otherwise defer this column to the next panel.
673 if (kstep == 2 && j + 1 >= nb) break;
674
675 const Index kk = jc + kstep - 1;
676 if (kp != kk) {
677 apply_symmetric_pivot(mat, jc, kk, kp, kstep);
678 const Index nwc = j + kstep; // number of populated W columns
679 W.row(kk).head(nwc).swap(W.row(kp).head(nwc));
680 }
681
682 if (kstep == 1) {
683 transpositions.coeffRef(jc) = StorageIndex(kp);
684 subdiag.coeffRef(jc) = Scalar(0);
685 const RealScalar dval = numext::real(W.coeff(jc, j));
686 mat.coeffRef(jc, jc) = Scalar(dval);
687 const Index rs = n - jc - 1;
688 if (rs > 0) {
689 if (!numext::is_exactly_zero(dval)) {
690 mat.col(jc).tail(rs) = W.col(j).segment(jc + 1, rs) / dval;
691 } else {
692 // Zero pivot (singular): the Schur column is zero too. Store an explicit zero L column so
693 // that matrixL() is well formed, matching the unblocked path (which leaves the already
694 // zeroed Schur column in place).
695 mat.col(jc).tail(rs).setZero();
696 if (info == 0) info = jc + 1;
697 }
698 }
699 j += 1;
700 } else {
701 transpositions.coeffRef(jc) = StorageIndex(jc);
702 transpositions.coeffRef(jc + 1) = StorageIndex(kp);
703 const RealScalar d11 = numext::real(W.coeff(jc, j));
704 const Scalar d21 = W.coeff(jc + 1, j);
705 const RealScalar d22 = numext::real(W.coeff(jc + 1, j + 1));
706 mat.coeffRef(jc, jc) = Scalar(d11);
707 mat.coeffRef(jc + 1, jc + 1) = Scalar(d22);
708 // Scaled 2x2 inverse (see unblocked()): divide through by d21 so the scaled determinant
709 // denom = det/|d21|^2 stays O(1); det = d11*d22 - |d21|^2 and abs2(d21) are never formed (they
710 // over/underflow on extreme-scaled blocks). The deferred level-3 trailing update below uses W
711 // (= L*D, original scale), so it carries no 1/det factor either.
712 const Scalar cjd = numext::conj(d21);
713 const Scalar ak = numext::divide(d22, d21);
714 const Scalar akm1 = numext::divide(d11, cjd);
715 const RealScalar denom = numext::real(ak * akm1) - RealScalar(1);
716 if (info == 0 && !(denom < RealScalar(0) && denom > RealScalar(-2))) info = jc + 1;
717 const Index rs = n - jc - 2;
718 if (rs > 0) {
719 // L(jc+2:n, jc:jc+1) = W(jc+2:n, j:j+1) * D^{-1}, as vectorized column expressions, divided
720 // before being scaled by t for the reason given in unblocked():
721 // L_k = t*((ak*w0 - w1)/conj(d21)), L_{k+1} = t*((akm1*w1 - w0)/d21).
722 const RealScalar t = RealScalar(1) / denom;
723 auto w0 = W.col(j).segment(jc + 2, rs);
724 auto w1 = W.col(j + 1).segment(jc + 2, rs);
725 mat.col(jc).tail(rs) = t * ((ak * w0 - w1) / cjd);
726 mat.col(jc + 1).tail(rs) = t * ((akm1 * w1 - w0) / d21);
727 }
728 subdiag.coeffRef(jc) = d21;
729 subdiag.coeffRef(jc + 1) = Scalar(0);
730 mat.coeffRef(jc + 1, jc) = Scalar(0);
731 j += 2;
732 }
733 }
734 return j;
735 }
736
737 // Blocked (level-3 BLAS) Bunch-Kaufman factorization of the lower triangle of `mat`, in place.
738 template <typename MatrixType, typename TranspositionType, typename SubDiagType, typename WorkspaceType>
739 static Index blocked(MatrixType& mat, TranspositionType& transpositions, SubDiagType& subdiag,
740 WorkspaceType& workspace) {
741 const Index n = mat.rows();
742 const Index nb = bunch_kaufman_blocksize<typename MatrixType::Scalar>();
743 if (nb < 2 || n <= nb) return unblocked(mat, transpositions, subdiag, 0);
744
745 // One extra workspace column holds the candidate ("imax") column examined during pivot selection.
746 workspace.resize(n, nb + 1);
747 Index info = 0;
748 Index k = 0;
749 while (k < n) {
750 if (n - k > nb) {
751 const Index kb = partial_factor(mat, k, nb, workspace, transpositions, subdiag, info);
752 const Index rs = n - k - kb;
753 if (rs > 0) {
754 // Deferred trailing update of the lower triangle: A22 <- A22 - L21 * (L21*D)^*.
755 mat.block(k + kb, k + kb, rs, rs).template triangularView<Lower>() -=
756 mat.block(k + kb, k, rs, kb) * workspace.block(k + kb, 0, rs, kb).adjoint();
757 }
758 if (kb == 0) { // defensive: avoid an infinite loop (should not happen for nb >= 2)
759 info = (info == 0) ? unblocked(mat, transpositions, subdiag, k) : info;
760 break;
761 }
762 k += kb;
763 } else {
764 const Index info2 = unblocked(mat, transpositions, subdiag, k);
765 if (info == 0) info = info2;
766 break;
767 }
768 }
769 return info;
770 }
771};
772
773template <>
774struct bunch_kaufman<Upper> {
775 template <typename MatrixType, typename TranspositionType, typename SubDiagType>
776 static EIGEN_STRONG_INLINE Index unblocked(MatrixType& mat, TranspositionType& transpositions, SubDiagType& subdiag,
777 Index k0 = 0) {
778 Transpose<MatrixType> matt(mat);
779 return bunch_kaufman<Lower>::unblocked(matt, transpositions, subdiag, k0);
780 }
781
782 template <typename MatrixType, typename TranspositionType, typename SubDiagType, typename WorkspaceType>
783 static EIGEN_STRONG_INLINE Index blocked(MatrixType& mat, TranspositionType& transpositions, SubDiagType& subdiag,
784 WorkspaceType& workspace) {
785 Transpose<MatrixType> matt(mat);
786 return bunch_kaufman<Lower>::blocked(matt, transpositions, subdiag, workspace);
787 }
788};
789
790template <typename MatrixType>
791struct BunchKaufman_Traits<MatrixType, Lower> {
792 using MatrixL = const TriangularView<const MatrixType, UnitLower>;
793 using MatrixU = const TriangularView<const typename MatrixType::AdjointReturnType, UnitUpper>;
794 static inline MatrixL getL(const MatrixType& m) { return MatrixL(m); }
795 static inline MatrixU getU(const MatrixType& m) { return MatrixU(m.adjoint()); }
796};
797
798template <typename MatrixType>
799struct BunchKaufman_Traits<MatrixType, Upper> {
800 using MatrixL = const TriangularView<const typename MatrixType::AdjointReturnType, UnitLower>;
801 using MatrixU = const TriangularView<const MatrixType, UnitUpper>;
802 static inline MatrixL getL(const MatrixType& m) { return MatrixL(m.adjoint()); }
803 static inline MatrixU getU(const MatrixType& m) { return MatrixU(m); }
804};
805
806} // end namespace internal
807
808template <typename MatrixType, int UpLo_>
809template <typename Derived>
810void BunchKaufman<MatrixType, UpLo_>::applyD(MatrixBase<Derived>& x) const {
811 const Index n = m_matrix.rows();
812 Index k = 0;
813 while (k < n) {
814 if (k + 1 < n && !numext::is_exactly_zero(m_subdiag.coeff(k))) {
815 const RealScalar d11 = numext::real(m_matrix.coeff(k, k));
816 const RealScalar d22 = numext::real(m_matrix.coeff(k + 1, k + 1));
817 const Scalar d21 = m_subdiag.coeff(k);
818 for (Index j = 0; j < x.cols(); ++j) {
819 const Scalar x0 = x.coeff(k, j);
820 const Scalar x1 = x.coeff(k + 1, j);
821 x.coeffRef(k, j) = d11 * x0 + numext::conj(d21) * x1;
822 x.coeffRef(k + 1, j) = d21 * x0 + d22 * x1;
823 }
824 k += 2;
825 } else {
826 x.row(k) *= numext::real(m_matrix.coeff(k, k));
827 k += 1;
828 }
829 }
830}
831
832template <typename MatrixType, int UpLo_>
833template <bool Conjugate, typename Derived>
834void BunchKaufman<MatrixType, UpLo_>::solveInPlaceD(MatrixBase<Derived>& x) const {
835 using numext::abs;
836 const Index n = m_matrix.rows();
837 // Use the pseudo-inverse of singular 1x1 blocks (see Eigen bug 241 for LDLT). The 2x2 blocks
838 // produced by Bunch-Kaufman pivoting are non-singular by construction.
839 const RealScalar tol = (std::numeric_limits<RealScalar>::min)();
840 Index k = 0;
841 while (k < n) {
842 if (k + 1 < n && !numext::is_exactly_zero(m_subdiag.coeff(k))) {
843 const RealScalar d11 = numext::real(m_matrix.coeff(k, k));
844 const RealScalar d22 = numext::real(m_matrix.coeff(k + 1, k + 1));
845 // D = [ d11 conj(d21) ; d21 d22 ]; for the transpose solve use conj(d21) instead of d21.
846 const Scalar d21 = Conjugate ? m_subdiag.coeff(k) : numext::conj(m_subdiag.coeff(k));
847 // Scaled 2x2 solve (LAPACK xSYTRS/xHETRS): divide through by d21 so the scaled determinant
848 // denom = det/|d21|^2 is O(1); det = d11*d22 - |d21|^2 is never formed (it over/underflows on
849 // extreme-scaled blocks, e.g. [[0,s],[s,0]], s=1e+-200). Every quotient divides by d21 itself:
850 // a hoisted 1/d21 overflows once |d21| is subnormal (issue #3142).
851 const Scalar cjd = numext::conj(d21);
852 const Scalar ak = numext::divide(d22, d21);
853 const Scalar akm1 = numext::divide(d11, cjd);
854 const RealScalar t = RealScalar(1) / (numext::real(ak * akm1) - RealScalar(1));
855 for (Index j = 0; j < x.cols(); ++j) {
856 const Scalar bk = numext::divide(x.coeff(k + 1, j), d21);
857 const Scalar bkm1 = numext::divide(x.coeff(k, j), cjd);
858 x.coeffRef(k, j) = t * (ak * bkm1 - bk);
859 x.coeffRef(k + 1, j) = t * (akm1 * bk - bkm1);
860 }
861 k += 2;
862 } else {
863 const RealScalar dk = numext::real(m_matrix.coeff(k, k));
864 if (abs(dk) > tol)
865 x.row(k) /= dk;
866 else
867 x.row(k).setZero();
868 k += 1;
869 }
870 }
871}
872
873// det(D_k) itself over- or underflows on an extreme-scaled 2x2 block, so it is only ever formed scaled by
874// |d21|^2. Divide by |d21| twice rather than multiply by its reciprocal, which overflows once |d21| is
875// subnormal. The pivot criterion bounds |q| by a^2 < 1 with a = (1+sqrt(17))/8, so |q| >= 1 or NaN is an
876// artifact of d22/d21 overflowing -- the criterion bounds |d22| only against its own row -- and
877// det(D_k) = -|d21|^2 to within that same bound there.
878template <typename MatrixType, int UpLo_>
879typename BunchKaufman<MatrixType, UpLo_>::RealScalar BunchKaufman<MatrixType, UpLo_>::scaledBlockDeterminant(
880 const RealScalar& d11, const RealScalar& d22, const RealScalar& d21) {
881 const RealScalar q = (d11 / d21) * (d22 / d21);
882 return (q > RealScalar(-1) && q < RealScalar(1)) ? q - RealScalar(1) : RealScalar(-1);
883}
884
885template <typename MatrixType, int UpLo_>
886void BunchKaufman<MatrixType, UpLo_>::computeInertia() {
887 const Index n = m_matrix.rows();
888 m_n_pos = m_n_neg = m_n_zero = 0;
889 Index k = 0;
890 while (k < n) {
891 if (k + 1 < n && !numext::is_exactly_zero(m_subdiag.coeff(k))) {
892 const RealScalar d11 = numext::real(m_matrix.coeff(k, k));
893 const RealScalar d22 = numext::real(m_matrix.coeff(k + 1, k + 1));
894 const RealScalar d21 = numext::abs(m_subdiag.coeff(k));
895 const RealScalar denom = scaledBlockDeterminant(d11, d22, d21);
896 if (denom < RealScalar(0)) {
897 // Indefinite 2x2 block: one positive and one negative eigenvalue.
898 ++m_n_pos;
899 ++m_n_neg;
900 } else if (numext::is_exactly_zero(denom)) {
901 const RealScalar tr = d11 + d22;
902 if (tr > RealScalar(0))
903 ++m_n_pos;
904 else if (tr < RealScalar(0))
905 ++m_n_neg;
906 else
907 ++m_n_zero;
908 ++m_n_zero;
909 } else {
910 // denom > 0: both eigenvalues share the sign of the trace.
911 if (d11 + d22 > RealScalar(0))
912 m_n_pos += 2;
913 else
914 m_n_neg += 2;
915 }
916 k += 2;
917 } else {
918 const RealScalar dk = numext::real(m_matrix.coeff(k, k));
919 if (dk > RealScalar(0))
920 ++m_n_pos;
921 else if (dk < RealScalar(0))
922 ++m_n_neg;
923 else
924 ++m_n_zero;
925 k += 1;
926 }
927 }
928}
929
932template <typename MatrixType, int UpLo_>
933template <typename InputType>
934BunchKaufman<MatrixType, UpLo_>& BunchKaufman<MatrixType, UpLo_>::compute(const EigenBase<InputType>& a) {
935 eigen_assert(a.rows() == a.cols());
936 m_matrix = a.derived();
937 return computeInPlace();
938}
939
941template <typename MatrixType, int UpLo_>
942BunchKaufman<MatrixType, UpLo_>& BunchKaufman<MatrixType, UpLo_>::computeInPlace() {
943 eigen_assert(m_matrix.rows() == m_matrix.cols());
944 const Index size = m_matrix.rows();
945
946 // L1 norm of the implicit self-adjoint matrix, for rcond().
947 m_l1_norm = m_matrix.template selfadjointView<UpLo_>().l1Norm();
948
949 m_transpositions.resize(size);
950 m_subdiag.resize(size);
951 m_isInitialized = false;
952
953 Index info = internal::bunch_kaufman<UpLo>::blocked(m_matrix, m_transpositions, m_subdiag, m_workspace);
954 m_info = (info == 0) ? Success : NumericalIssue;
955
956 // The upper variant factorizes the transpose of the (Hermitian) matrix, i.e. its complex conjugate,
957 // so the recorded 2x2 off-diagonals of D come out conjugated; undo that so that m_subdiag holds the
958 // true sub-diagonal D(k+1,k) consumed by applyD()/solveInPlaceD()/computeInertia(). (No-op when real.)
959 EIGEN_IF_CONSTEXPR (int(UpLo) == int(Upper) && NumTraits<Scalar>::IsComplex) {
960 m_subdiag = m_subdiag.conjugate();
961 }
962
963 computeInertia();
964
965 m_isInitialized = true;
966 return *this;
967}
968
969#ifndef EIGEN_PARSED_BY_DOXYGEN
970template <typename MatrixType_, int UpLo_>
971template <typename RhsType, typename DstType>
972void BunchKaufman<MatrixType_, UpLo_>::_solve_impl(const RhsType& rhs, DstType& dst) const {
973 _solve_impl_transposed<true>(rhs, dst);
974}
975
976template <typename MatrixType_, int UpLo_>
977template <bool Conjugate, typename RhsType, typename DstType>
978void BunchKaufman<MatrixType_, UpLo_>::_solve_impl_transposed(const RhsType& rhs, DstType& dst) const {
979 // A^{-1} b = P^T L^{-*} D^{-1} L^{-1} P b (and the conjugated variants for transpose / adjoint solves).
980 dst = m_transpositions * rhs;
981 matrixL().template conjugateIf<!Conjugate>().solveInPlace(dst);
982 solveInPlaceD<Conjugate>(dst);
983 matrixL().transpose().template conjugateIf<Conjugate>().solveInPlace(dst);
984 dst = m_transpositions.transpose() * dst;
985}
986#endif
987
994template <typename MatrixType, int UpLo_>
995template <typename Derived>
996bool BunchKaufman<MatrixType, UpLo_>::solveInPlace(MatrixBase<Derived>& bAndX) const {
997 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
998 eigen_assert(m_matrix.rows() == bAndX.rows());
999 bAndX = this->solve(bAndX);
1000 return true;
1001}
1002
1003template <typename MatrixType, int UpLo_>
1004typename BunchKaufman<MatrixType, UpLo_>::RealScalar BunchKaufman<MatrixType, UpLo_>::determinantD() const {
1005 const Index n = m_matrix.rows();
1006 // det(D) is accumulated as mantissa * 2^exponent, the mantissa renormalized to [1/2, 1) after each
1007 // block. Both a single 2x2 block determinant and the running product can leave the representable range
1008 // while det(D) itself stays in it, and an overflowed block meeting an underflowed one gives NaN.
1009 RealScalar mantissa(1);
1010 Index exponent = 0;
1011 Index k = 0;
1012 while (k < n) {
1013 RealScalar blockM, blockE;
1014 if (k + 1 < n && !numext::is_exactly_zero(m_subdiag.coeff(k))) {
1015 const RealScalar d11 = numext::real(m_matrix.coeff(k, k));
1016 const RealScalar d22 = numext::real(m_matrix.coeff(k + 1, k + 1));
1017 const RealScalar d21 = numext::abs(m_subdiag.coeff(k));
1018 RealScalar e21;
1019 const RealScalar m21 = internal::pfrexp<RealScalar>(d21, e21);
1020 blockM = m21 * m21 * scaledBlockDeterminant(d11, d22, d21);
1021 blockE = RealScalar(2) * e21;
1022 k += 2;
1023 } else {
1024 blockM = internal::pfrexp<RealScalar>(numext::real(m_matrix.coeff(k, k)), blockE);
1025 k += 1;
1026 }
1027 // |blockM| < 2 and, unless the block is singular, above 1/16, so the product below stays normal and
1028 // its rounding is the only error this step adds.
1029 RealScalar renorm;
1030 mantissa = internal::pfrexp<RealScalar>(mantissa * blockM, renorm);
1031 exponent += Index(blockE) + Index(renorm);
1032 }
1033 // ldexp() saturates to zero or infinity but takes an int exponent; past this magnitude it has already
1034 // saturated, so clamping first cannot change the result.
1035 const Index limit = Index(NumTraits<RealScalar>::max_exponent()) - Index(NumTraits<RealScalar>::min_exponent()) +
1036 Index(NumTraits<RealScalar>::digits());
1037 return numext::ldexp(mantissa, int(numext::mini(numext::maxi(exponent, -limit), limit)));
1038}
1039
1040// 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.
1041
1042template <typename MatrixType, int UpLo_>
1043typename BunchKaufman<MatrixType, UpLo_>::Scalar BunchKaufman<MatrixType, UpLo_>::determinant() const {
1044 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
1045 return Scalar(determinantD());
1046}
1047
1048template <typename MatrixType, int UpLo_>
1050 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
1051 return numext::abs(determinantD());
1052}
1053
1054template <typename MatrixType, int UpLo_>
1056 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
1057 const Index n = m_matrix.rows();
1058 RealScalar result(0);
1059 Index k = 0;
1060 while (k < n) {
1061 if (k + 1 < n && !numext::is_exactly_zero(m_subdiag.coeff(k))) {
1062 // log|det(D_k)| = 2 log|d21| + log|det(D_k)/|d21|^2|.
1063 const RealScalar d11 = numext::real(m_matrix.coeff(k, k));
1064 const RealScalar d22 = numext::real(m_matrix.coeff(k + 1, k + 1));
1065 const RealScalar d21 = numext::abs(m_subdiag.coeff(k));
1066 const RealScalar scaled = scaledBlockDeterminant(d11, d22, d21);
1067 result += RealScalar(2) * numext::log(d21) + numext::log(numext::abs(scaled));
1068 k += 2;
1069 } else {
1070 result += numext::log(numext::abs(numext::real(m_matrix.coeff(k, k))));
1071 k += 1;
1072 }
1073 }
1074 return result;
1075}
1076
1077template <typename MatrixType, int UpLo_>
1078typename BunchKaufman<MatrixType, UpLo_>::Scalar BunchKaufman<MatrixType, UpLo_>::signDeterminant() const {
1079 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
1080 if (m_n_zero > 0) return Scalar(0);
1081 return Scalar((m_n_neg % 2 == 0) ? 1 : -1);
1082}
1083
1086template <typename MatrixType, int UpLo_>
1088 eigen_assert(m_isInitialized && "BunchKaufman is not initialized.");
1089 const Index size = m_matrix.rows();
1090 MatrixType res(size, size);
1091
1092 res.setIdentity();
1093 res = transpositionsP() * res; // P
1094 res = matrixU() * res; // U P = L^* P
1095 applyD(res); // D L^* P
1096 res = matrixL() * res; // L D L^* P
1097 res = transpositionsP().transpose() * res; // P^T L D L^* P
1098
1099 return res;
1100}
1101
1106template <typename MatrixType, unsigned int UpLo>
1111
1116template <typename Derived>
1120
1121} // end namespace Eigen
1122
1123#endif // EIGEN_BUNCHKAUFMAN_H
Bunch-Kaufman factorization of a symmetric / Hermitian indefinite matrix.
Definition BunchKaufman.h:75
RealScalar logAbsDeterminant() const
Definition BunchKaufman.h:1055
Traits::MatrixU matrixU() const
Definition BunchKaufman.h:175
Scalar signDeterminant() const
Definition BunchKaufman.h:1078
RealScalar absDeterminant() const
Definition BunchKaufman.h:1049
const MatrixType & matrixLDLT() const
Definition BunchKaufman.h:258
Diagonal< const MatrixType > vectorD() const
Definition BunchKaufman.h:198
BunchKaufman(const EigenBase< InputType > &matrix)
Constructor with decomposition.
Definition BunchKaufman.h:140
const TranspositionType & transpositionsP() const
Definition BunchKaufman.h:188
bool isPositive() const
Definition BunchKaufman.h:214
Solve< BunchKaufman, Rhs > solve(const MatrixBase< Rhs > &b) const
bool isNegative() const
Definition BunchKaufman.h:220
Scalar determinant() const
Definition BunchKaufman.h:1043
BunchKaufman()
Default Constructor.
Definition BunchKaufman.h:101
RealScalar rcond() const
Definition BunchKaufman.h:247
Traits::MatrixL matrixL() const
Definition BunchKaufman.h:181
const TmpVectorType & subDiagonal() const
Definition BunchKaufman.h:208
BunchKaufman(Index size)
Default Constructor with memory preallocation.
Definition BunchKaufman.h:118
BunchKaufman(EigenBase< InputType > &matrix)
Constructs a Bunch-Kaufman factorization from a given matrix.
Definition BunchKaufman.h:161
ComputationInfo info() const
Reports whether previous computation was successful.
Definition BunchKaufman.h:338
MatrixType reconstructedMatrix() const
Definition BunchKaufman.h:1087
const BunchKaufman & adjoint() const
Definition BunchKaufman.h:326
Expression of a diagonal/subdiagonal/superdiagonal in a matrix.
Definition Diagonal.h:78
Base class for all dense matrices, vectors, and expressions.
Definition MatrixBase.h:53
BunchKaufman< PlainObject > bunchKaufman() const
Definition BunchKaufman.h:1117
The matrix class, also used for vectors and row-vectors.
Definition Matrix.h:188
Permutation matrix.
Definition PermutationMatrix.h:346
BunchKaufman< PlainObject, UpLo > bunchKaufman() const
Definition BunchKaufman.h:1108
Pseudo expression representing a solving operation.
Definition Solve.h:63
constexpr BunchKaufman< MatrixType_, UpLo_ > & derived()
Represents a sequence of transpositions (row/column interchange)
Definition Transpositions.h:144
ComputationInfo
Definition Constants.h:455
@ Lower
Definition Constants.h:212
@ Upper
Definition Constants.h:214
@ NumericalIssue
Definition Constants.h:459
@ InvalidInput
Definition Constants.h:464
@ Success
Definition Constants.h:457
Definition EigenBase.h:34
constexpr Derived & derived()
Definition EigenBase.h:50
constexpr Index size() const noexcept
Definition EigenBase.h:65