| // This file is part of Eigen, a lightweight C++ template library | |
| // for linear algebra. | |
| // | |
| // Copyright (C) 2008 Gael Guennebaud <gael.guennebaud@inria.fr> | |
| // Copyright (C) 2010 Jitse Niesen <jitse@maths.leeds.ac.uk> | |
| // | |
| // 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 | |
| // Enables the runtime malloc tracking that generalizedselfadjointeigensolver_no_malloc() needs. | |
| // Allocation stays allowed by default; only the explicit set_is_malloc_allowed(false) window checks. | |
| #define EIGEN_RUNTIME_NO_MALLOC | |
| #include "main.h" | |
| #include "svd_fill.h" | |
| #include "tridiag_test_matrices.h" | |
| #include "fp_control.h" | |
| #include <limits> | |
| #include <Eigen/Eigenvalues> | |
| #include <Eigen/SparseCore> | |
| #include <contrib/Eigen/MatrixFunctions> | |
| template <typename Scalar> | |
| struct selfadjoint_eigensolver_test_rescale { | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| explicit selfadjoint_eigensolver_test_rescale(RealScalar scale) : m_scale(scale) {} | |
| Scalar operator()(const Scalar& value) const { return value / m_scale; } | |
| RealScalar m_scale; | |
| }; | |
| template <> | |
| struct selfadjoint_eigensolver_test_rescale<float> { | |
| explicit selfadjoint_eigensolver_test_rescale(float scale) : m_scale(scale) {} | |
| // A scalar widened division keeps the residual calculation independent of NEON's subnormal flushing. | |
| float operator()(float value) const { return float(double(value) / double(m_scale)); } | |
| float m_scale; | |
| }; | |
| template <typename MatrixType> | |
| MatrixType scaled_selfadjoint_for_residual(const MatrixType& matrix, typename MatrixType::RealScalar scale) { | |
| using Scalar = typename MatrixType::Scalar; | |
| using RealScalar = typename MatrixType::RealScalar; | |
| MatrixType dense = matrix.template selfadjointView<Lower>(); | |
| if (!internal::is_same<Scalar, float>::value || | |
| scale >= RealScalar((std::numeric_limits<float>::min)() / NumTraits<float>::epsilon())) { | |
| dense /= scale; | |
| return dense; | |
| } | |
| return dense.unaryExpr(selfadjoint_eigensolver_test_rescale<Scalar>(scale)); | |
| } | |
| template <typename MatrixType> | |
| void selfadjointeigensolver_essential_check(const MatrixType& m) { | |
| typedef typename MatrixType::Scalar Scalar; | |
| typedef typename NumTraits<Scalar>::Real RealScalar; | |
| RealScalar eival_eps = | |
| numext::mini<RealScalar>(test_precision<RealScalar>(), NumTraits<Scalar>::dummy_precision() * 20000); | |
| SelfAdjointEigenSolver<MatrixType> eiSymm(m); | |
| VERIFY_IS_EQUAL(eiSymm.info(), Success); | |
| Index n = m.cols(); | |
| RealScalar scaling = m.cwiseAbs().maxCoeff(); | |
| if (scaling < (std::numeric_limits<RealScalar>::min)()) { | |
| VERIFY(eiSymm.eigenvalues().cwiseAbs().maxCoeff() <= (std::numeric_limits<RealScalar>::min)()); | |
| } else { | |
| // Columnwise residual check: for each eigenpair (lambda_i, v_i), | |
| // ||A*v_i - lambda_i*v_i|| / ||A||_max <= c * n * eps | |
| // This ensures accuracy for every eigenpair, including those corresponding | |
| // to small eigenvalues (which a Frobenius norm check would miss). | |
| // Computed in scaled space (dividing by ||A||_max) to avoid overflow. | |
| const MatrixType scaledA = scaled_selfadjoint_for_residual(m, scaling); | |
| MatrixType residual = | |
| scaledA * eiSymm.eigenvectors() - eiSymm.eigenvectors() * (eiSymm.eigenvalues() / scaling).asDiagonal(); | |
| RealScalar tol = RealScalar(8) * RealScalar(numext::maxi(Index(1), n)) * NumTraits<RealScalar>::epsilon(); | |
| for (Index i = 0; i < n; ++i) { | |
| VERIFY(residual.col(i).norm() <= tol); | |
| } | |
| } | |
| VERIFY_IS_APPROX(m.template selfadjointView<Lower>().eigenvalues(), eiSymm.eigenvalues()); | |
| // Eigenvectors must be unitary. Use a tolerance proportional to n*epsilon, | |
| // which is the expected rounding error for Householder-based orthogonal transformations. | |
| RealScalar unitary_tol = RealScalar(8) * RealScalar(numext::maxi(Index(1), n)) * NumTraits<RealScalar>::epsilon(); | |
| // But don't go below the test_precision floor (matters for float). | |
| unitary_tol = numext::maxi(unitary_tol, test_precision<RealScalar>()); | |
| VERIFY(eiSymm.eigenvectors().isUnitary(unitary_tol)); | |
| // Verify eigenvalues are sorted in non-decreasing order. | |
| for (Index i = 1; i < n; ++i) { | |
| VERIFY(eiSymm.eigenvalues()(i) >= eiSymm.eigenvalues()(i - 1)); | |
| } | |
| if (m.cols() <= 4) { | |
| SelfAdjointEigenSolver<MatrixType> eiDirect; | |
| eiDirect.computeDirect(m); | |
| VERIFY_IS_EQUAL(eiDirect.info(), Success); | |
| if (!eiSymm.eigenvalues().isApprox(eiDirect.eigenvalues(), eival_eps)) { | |
| std::cerr << "reference eigenvalues: " << eiSymm.eigenvalues().transpose() << "\n" | |
| << "obtained eigenvalues: " << eiDirect.eigenvalues().transpose() << "\n" | |
| << "diff: " << (eiSymm.eigenvalues() - eiDirect.eigenvalues()).transpose() << "\n" | |
| << "error (eps): " | |
| << (eiSymm.eigenvalues() - eiDirect.eigenvalues()).norm() / eiSymm.eigenvalues().norm() << " (" | |
| << eival_eps << ")\n"; | |
| } | |
| if (scaling < (std::numeric_limits<RealScalar>::min)()) { | |
| VERIFY(eiDirect.eigenvalues().cwiseAbs().maxCoeff() <= (std::numeric_limits<RealScalar>::min)()); | |
| } else { | |
| VERIFY_IS_APPROX(eiSymm.eigenvalues() / scaling, eiDirect.eigenvalues() / scaling); | |
| // TODO: the direct 3x3 solver can produce large backward errors (>>n*eps*||A||) | |
| // on some matrices. Investigate and fix, then tighten this to a Frobenius norm check. | |
| const MatrixType scaledA = scaled_selfadjoint_for_residual(m, scaling); | |
| VERIFY_IS_APPROX(scaledA * eiDirect.eigenvectors(), | |
| eiDirect.eigenvectors() * (eiDirect.eigenvalues() / scaling).asDiagonal()); | |
| VERIFY_IS_APPROX(m.template selfadjointView<Lower>().eigenvalues() / scaling, eiDirect.eigenvalues() / scaling); | |
| } | |
| // Direct solver eigenvectors must also be unitary. | |
| VERIFY(eiDirect.eigenvectors().isUnitary(unitary_tol)); | |
| // Direct solver eigenvalues must also be sorted. | |
| for (Index i = 1; i < n; ++i) { | |
| VERIFY(eiDirect.eigenvalues()(i) >= eiDirect.eigenvalues()(i - 1)); | |
| } | |
| } | |
| } | |
| template <typename MatrixType> | |
| void selfadjointeigensolver(const MatrixType& m) { | |
| /* this test covers the following files: | |
| EigenSolver.h, SelfAdjointEigenSolver.h (and indirectly: Tridiagonalization.h) | |
| */ | |
| Index rows = m.rows(); | |
| Index cols = m.cols(); | |
| typedef typename MatrixType::Scalar Scalar; | |
| typedef typename NumTraits<Scalar>::Real RealScalar; | |
| RealScalar largerEps = 10 * test_precision<RealScalar>(); | |
| MatrixType a = MatrixType::Random(rows, cols); | |
| MatrixType a1 = MatrixType::Random(rows, cols); | |
| MatrixType symmA = a.adjoint() * a + a1.adjoint() * a1; | |
| MatrixType symmC = symmA; | |
| svd_fill_random(symmA, SelfAdjoint); | |
| symmA.template triangularView<StrictlyUpper>().setZero(); | |
| symmC.template triangularView<StrictlyUpper>().setZero(); | |
| MatrixType b = MatrixType::Random(rows, cols); | |
| MatrixType b1 = MatrixType::Random(rows, cols); | |
| MatrixType symmB = b.adjoint() * b + b1.adjoint() * b1; | |
| symmB.template triangularView<StrictlyUpper>().setZero(); | |
| CALL_SUBTEST(selfadjointeigensolver_essential_check(symmA)); | |
| SelfAdjointEigenSolver<MatrixType> eiSymm(symmA); | |
| // generalized eigen pb | |
| GeneralizedSelfAdjointEigenSolver<MatrixType> eiSymmGen(symmC, symmB); | |
| SelfAdjointEigenSolver<MatrixType> eiSymmNoEivecs(symmA, false); | |
| VERIFY_IS_EQUAL(eiSymmNoEivecs.info(), Success); | |
| VERIFY_IS_APPROX(eiSymm.eigenvalues(), eiSymmNoEivecs.eigenvalues()); | |
| // generalized eigen problem Ax = lBx | |
| eiSymmGen.compute(symmC, symmB, Ax_lBx); | |
| VERIFY_IS_EQUAL(eiSymmGen.info(), Success); | |
| VERIFY((symmC.template selfadjointView<Lower>() * eiSymmGen.eigenvectors()) | |
| .isApprox(symmB.template selfadjointView<Lower>() * | |
| (eiSymmGen.eigenvectors() * eiSymmGen.eigenvalues().asDiagonal()), | |
| largerEps)); | |
| // generalized eigen problem BAx = lx | |
| eiSymmGen.compute(symmC, symmB, BAx_lx); | |
| VERIFY_IS_EQUAL(eiSymmGen.info(), Success); | |
| VERIFY( | |
| (symmB.template selfadjointView<Lower>() * (symmC.template selfadjointView<Lower>() * eiSymmGen.eigenvectors())) | |
| .isApprox((eiSymmGen.eigenvectors() * eiSymmGen.eigenvalues().asDiagonal()), largerEps)); | |
| // generalized eigen problem ABx = lx | |
| eiSymmGen.compute(symmC, symmB, ABx_lx); | |
| VERIFY_IS_EQUAL(eiSymmGen.info(), Success); | |
| VERIFY( | |
| (symmC.template selfadjointView<Lower>() * (symmB.template selfadjointView<Lower>() * eiSymmGen.eigenvectors())) | |
| .isApprox((eiSymmGen.eigenvectors() * eiSymmGen.eigenvalues().asDiagonal()), largerEps)); | |
| eiSymm.compute(symmC); | |
| MatrixType sqrtSymmA = eiSymm.operatorSqrt(); | |
| VERIFY_IS_APPROX(MatrixType(symmC.template selfadjointView<Lower>()), sqrtSymmA * sqrtSymmA); | |
| VERIFY_IS_APPROX(sqrtSymmA, symmC.template selfadjointView<Lower>() * eiSymm.operatorInverseSqrt()); | |
| MatrixType id = MatrixType::Identity(rows, cols); | |
| VERIFY_IS_APPROX(id.template selfadjointView<Lower>().operatorNorm(), RealScalar(1)); | |
| SelfAdjointEigenSolver<MatrixType> eiSymmUninitialized; | |
| VERIFY_RAISES_ASSERT(eiSymmUninitialized.info()); | |
| VERIFY_RAISES_ASSERT(eiSymmUninitialized.eigenvalues()); | |
| VERIFY_RAISES_ASSERT(eiSymmUninitialized.eigenvectors()); | |
| VERIFY_RAISES_ASSERT(eiSymmUninitialized.operatorSqrt()); | |
| VERIFY_RAISES_ASSERT(eiSymmUninitialized.operatorInverseSqrt()); | |
| VERIFY_RAISES_ASSERT(eiSymmUninitialized.operatorExp()); | |
| eiSymmUninitialized.compute(symmA, false); | |
| VERIFY_RAISES_ASSERT(eiSymmUninitialized.eigenvectors()); | |
| VERIFY_RAISES_ASSERT(eiSymmUninitialized.operatorSqrt()); | |
| VERIFY_RAISES_ASSERT(eiSymmUninitialized.operatorInverseSqrt()); | |
| VERIFY_RAISES_ASSERT(eiSymmUninitialized.operatorExp()); | |
| // test Tridiagonalization's methods | |
| Tridiagonalization<MatrixType> tridiag(symmC); | |
| VERIFY_IS_APPROX(tridiag.diagonal(), tridiag.matrixT().diagonal()); | |
| VERIFY_IS_APPROX(tridiag.subDiagonal(), tridiag.matrixT().template diagonal<-1>()); | |
| Matrix<RealScalar, Dynamic, Dynamic> T = tridiag.matrixT(); | |
| if (rows > 2) { | |
| // Verify that the tridiagonal matrix is actually tridiagonal (zero outside the three central diagonals). | |
| for (Index i = 0; i < rows; ++i) { | |
| for (Index j = 0; j < cols; ++j) { | |
| if (numext::abs(i - j) > 1) { | |
| VERIFY(numext::is_exactly_zero(T(i, j))); | |
| } | |
| } | |
| } | |
| } | |
| VERIFY_IS_APPROX(tridiag.subDiagonal(), T.template diagonal<1>()); | |
| VERIFY_IS_APPROX(MatrixType(symmC.template selfadjointView<Lower>()), | |
| tridiag.matrixQ() * tridiag.matrixT().eval() * MatrixType(tridiag.matrixQ()).adjoint()); | |
| VERIFY_IS_APPROX(MatrixType(symmC.template selfadjointView<Lower>()), | |
| tridiag.matrixQ() * tridiag.matrixT() * tridiag.matrixQ().adjoint()); | |
| // Test computation of eigenvalues from tridiagonal matrix | |
| if (rows > 1) { | |
| SelfAdjointEigenSolver<MatrixType> eiSymmTridiag; | |
| eiSymmTridiag.computeFromTridiagonal(tridiag.matrixT().diagonal(), tridiag.matrixT().diagonal(-1), | |
| ComputeEigenvectors); | |
| VERIFY_IS_APPROX(eiSymm.eigenvalues(), eiSymmTridiag.eigenvalues()); | |
| VERIFY_IS_APPROX(tridiag.matrixT(), eiSymmTridiag.eigenvectors().real() * eiSymmTridiag.eigenvalues().asDiagonal() * | |
| eiSymmTridiag.eigenvectors().real().transpose()); | |
| } | |
| // Test matrix exponential from eigendecomposition. | |
| // First scale to avoid overflow. | |
| symmB = symmB / symmB.norm(); | |
| eiSymm.compute(symmB); | |
| MatrixType expSymmB = eiSymm.operatorExp(); | |
| symmB = symmB.template selfadjointView<Lower>(); | |
| VERIFY_IS_APPROX(expSymmB, symmB.exp()); | |
| if (rows > 1 && rows < 20) { | |
| // Test matrix with NaN | |
| symmC(0, 0) = std::numeric_limits<typename MatrixType::RealScalar>::quiet_NaN(); | |
| SelfAdjointEigenSolver<MatrixType> eiSymmNaN(symmC); | |
| VERIFY_IS_EQUAL(eiSymmNaN.info(), NoConvergence); | |
| } | |
| // regression test for bug 1098 | |
| { | |
| SelfAdjointEigenSolver<MatrixType> eig(a.adjoint() * a); | |
| eig.compute(a.adjoint() * a); | |
| } | |
| // regression test for bug 478 | |
| { | |
| a.setZero(); | |
| SelfAdjointEigenSolver<MatrixType> ei3(a); | |
| VERIFY_IS_EQUAL(ei3.info(), Success); | |
| VERIFY_IS_MUCH_SMALLER_THAN(ei3.eigenvalues().norm(), RealScalar(1)); | |
| RealScalar tol = 2 * a.cols() * NumTraits<RealScalar>::epsilon(); | |
| VERIFY((ei3.eigenvectors().adjoint() * ei3.eigenvectors()).eval().isIdentity(tol)); | |
| } | |
| } | |
| // Test matrices with exact eigenvalue multiplicities. | |
| template <typename MatrixType> | |
| void selfadjointeigensolver_repeated_eigenvalues(const MatrixType& m) { | |
| typedef typename MatrixType::Scalar Scalar; | |
| typedef typename NumTraits<Scalar>::Real RealScalar; | |
| Index n = m.rows(); | |
| if (n < 2) return; | |
| // Create a random unitary matrix via QR. | |
| MatrixType q = MatrixType::Random(n, n); | |
| HouseholderQR<MatrixType> qr(q); | |
| q = qr.householderQ(); | |
| // All eigenvalues equal (scalar multiple of identity). | |
| { | |
| RealScalar lambda = internal::random<RealScalar>(-10, 10); | |
| MatrixType A = lambda * MatrixType::Identity(n, n); | |
| selfadjointeigensolver_essential_check(A); | |
| } | |
| // Eigenvalue of multiplicity n-1 (one distinct, rest equal). | |
| { | |
| Matrix<RealScalar, Dynamic, 1> d = Matrix<RealScalar, Dynamic, 1>::Constant(n, RealScalar(3)); | |
| d(0) = RealScalar(-2); | |
| MatrixType A = (q * d.template cast<Scalar>().asDiagonal() * q.adjoint()).eval(); | |
| A.template triangularView<StrictlyUpper>().setZero(); | |
| selfadjointeigensolver_essential_check(A); | |
| } | |
| // Two clusters: first half one value, second half another. | |
| if (n >= 4) { | |
| Matrix<RealScalar, Dynamic, 1> d(n); | |
| for (Index i = 0; i < n / 2; ++i) d(i) = RealScalar(1); | |
| for (Index i = n / 2; i < n; ++i) d(i) = RealScalar(5); | |
| MatrixType A = (q * d.template cast<Scalar>().asDiagonal() * q.adjoint()).eval(); | |
| A.template triangularView<StrictlyUpper>().setZero(); | |
| selfadjointeigensolver_essential_check(A); | |
| } | |
| // Nearly repeated eigenvalues: separated by O(epsilon). | |
| { | |
| Matrix<RealScalar, Dynamic, 1> d(n); | |
| for (Index i = 0; i < n; ++i) { | |
| d(i) = RealScalar(1) + RealScalar(i) * NumTraits<RealScalar>::epsilon() * RealScalar(10); | |
| } | |
| MatrixType A = (q * d.template cast<Scalar>().asDiagonal() * q.adjoint()).eval(); | |
| A.template triangularView<StrictlyUpper>().setZero(); | |
| selfadjointeigensolver_essential_check(A); | |
| } | |
| } | |
| // Test matrices with extreme condition numbers and eigenvalue ranges. | |
| template <typename MatrixType> | |
| void selfadjointeigensolver_extreme_eigenvalues(const MatrixType& m) { | |
| using std::pow; | |
| typedef typename MatrixType::Scalar Scalar; | |
| typedef typename NumTraits<Scalar>::Real RealScalar; | |
| Index n = m.rows(); | |
| if (n < 2) return; | |
| // Create a random unitary matrix. | |
| MatrixType q = MatrixType::Random(n, n); | |
| HouseholderQR<MatrixType> qr(q); | |
| q = qr.householderQ(); | |
| // Eigenvalues spanning many orders of magnitude (high condition number). | |
| { | |
| RealScalar maxExp = RealScalar(std::numeric_limits<RealScalar>::max_exponent10) / RealScalar(4); | |
| Matrix<RealScalar, Dynamic, 1> d(n); | |
| for (Index i = 0; i < n; ++i) { | |
| RealScalar exponent = -maxExp + RealScalar(2) * maxExp * RealScalar(i) / RealScalar(n - 1); | |
| d(i) = pow(RealScalar(10), exponent); | |
| } | |
| MatrixType A = (q * d.template cast<Scalar>().asDiagonal() * q.adjoint()).eval(); | |
| A.template triangularView<StrictlyUpper>().setZero(); | |
| SelfAdjointEigenSolver<MatrixType> eig(A); | |
| VERIFY_IS_EQUAL(eig.info(), Success); | |
| // For ill-conditioned matrices we can only check the relative residual. | |
| // ||A*V - V*D|| / ||A|| should be O(n * epsilon). | |
| RealScalar Anorm = A.template selfadjointView<Lower>().operatorNorm(); | |
| if (Anorm > (std::numeric_limits<RealScalar>::min)()) { | |
| MatrixType residual = A.template selfadjointView<Lower>() * eig.eigenvectors() - | |
| eig.eigenvectors() * eig.eigenvalues().asDiagonal(); | |
| RealScalar rel_err = residual.norm() / Anorm; | |
| RealScalar tol = RealScalar(4) * RealScalar(n) * NumTraits<RealScalar>::epsilon(); | |
| VERIFY(rel_err <= tol); | |
| } | |
| // Eigenvalues must still be sorted. | |
| for (Index i = 1; i < n; ++i) { | |
| VERIFY(eig.eigenvalues()(i) >= eig.eigenvalues()(i - 1)); | |
| } | |
| } | |
| // Very tiny eigenvalues (near underflow). | |
| { | |
| RealScalar tiny = (std::numeric_limits<RealScalar>::min)() * RealScalar(100); | |
| Matrix<RealScalar, Dynamic, 1> d(n); | |
| for (Index i = 0; i < n; ++i) { | |
| d(i) = tiny * (RealScalar(1) + RealScalar(i)); | |
| } | |
| MatrixType A = (q * d.template cast<Scalar>().asDiagonal() * q.adjoint()).eval(); | |
| A.template triangularView<StrictlyUpper>().setZero(); | |
| selfadjointeigensolver_essential_check(A); | |
| } | |
| // Very large eigenvalues (near overflow). | |
| { | |
| RealScalar huge = (std::numeric_limits<RealScalar>::max)() / (RealScalar(n) * RealScalar(100)); | |
| Matrix<RealScalar, Dynamic, 1> d(n); | |
| for (Index i = 0; i < n; ++i) { | |
| d(i) = huge * (RealScalar(1) + RealScalar(i) * RealScalar(0.01)); | |
| } | |
| MatrixType A = (q * d.template cast<Scalar>().asDiagonal() * q.adjoint()).eval(); | |
| A.template triangularView<StrictlyUpper>().setZero(); | |
| selfadjointeigensolver_essential_check(A); | |
| } | |
| // Mix of positive and negative eigenvalues. | |
| { | |
| Matrix<RealScalar, Dynamic, 1> d(n); | |
| for (Index i = 0; i < n; ++i) { | |
| d(i) = (i % 2 == 0) ? RealScalar(i + 1) : RealScalar(-(i + 1)); | |
| } | |
| MatrixType A = (q * d.template cast<Scalar>().asDiagonal() * q.adjoint()).eval(); | |
| A.template triangularView<StrictlyUpper>().setZero(); | |
| selfadjointeigensolver_essential_check(A); | |
| } | |
| // One zero eigenvalue among non-zero ones (rank-deficient). | |
| { | |
| Matrix<RealScalar, Dynamic, 1> d = Matrix<RealScalar, Dynamic, 1>::LinSpaced(n, RealScalar(0), RealScalar(n - 1)); | |
| MatrixType A = (q * d.template cast<Scalar>().asDiagonal() * q.adjoint()).eval(); | |
| A.template triangularView<StrictlyUpper>().setZero(); | |
| selfadjointeigensolver_essential_check(A); | |
| } | |
| } | |
| template <typename = void> | |
| void selfadjointeigensolver_subnormal_coefficients() { | |
| volatile float normalMinInput = (std::numeric_limits<float>::min)(); | |
| const double scale = 256.0 * double(normalMinInput); | |
| const auto scaled = [scale](float value) { return float(double(value) * scale); }; | |
| // The 1/512 entries are subnormal but significant relative to the matrix norm. | |
| Matrix2f matrix2 = Matrix2f::Zero(); | |
| matrix2(0, 0) = scaled(1.0f); | |
| matrix2(1, 0) = scaled(1.0f / 512.0f); | |
| matrix2(1, 1) = scaled(0.5f); | |
| Matrix3f matrix3 = Matrix3f::Zero(); | |
| matrix3(0, 0) = scaled(1.0f); | |
| matrix3(1, 0) = scaled(0.25f); | |
| matrix3(1, 1) = scaled(0.75f); | |
| matrix3(2, 0) = scaled(1.0f / 512.0f); | |
| matrix3(2, 1) = scaled(-0.125f); | |
| matrix3(2, 2) = scaled(0.5f); | |
| if (!numext::is_exactly_zero(matrix2(1, 0))) { | |
| selfadjointeigensolver_essential_check(matrix2); | |
| selfadjointeigensolver_essential_check(matrix3); | |
| } | |
| } | |
| // Test computeFromTridiagonal with scaled inputs (regression for missing scaling). | |
| template <typename MatrixType> | |
| void selfadjointeigensolver_tridiagonal_scaled(const MatrixType& m) { | |
| typedef typename MatrixType::Scalar Scalar; | |
| typedef typename NumTraits<Scalar>::Real RealScalar; | |
| Index n = m.rows(); | |
| if (n < 2) return; | |
| // Create a tridiagonal matrix with large entries. | |
| typedef Matrix<RealScalar, Dynamic, 1> RealVectorType; | |
| RealVectorType diag(n), subdiag(n - 1); | |
| // Case 1: Large values. | |
| RealScalar scale = (std::numeric_limits<RealScalar>::max)() / (RealScalar(n) * RealScalar(100)); | |
| for (Index i = 0; i < n; ++i) diag(i) = scale * RealScalar(i + 1); | |
| for (Index i = 0; i < n - 1; ++i) subdiag(i) = scale * RealScalar(0.5); | |
| SelfAdjointEigenSolver<MatrixType> eig1; | |
| eig1.computeFromTridiagonal(diag, subdiag, ComputeEigenvectors); | |
| VERIFY_IS_EQUAL(eig1.info(), Success); | |
| // Reconstruct tridiagonal and check residual. | |
| Matrix<RealScalar, Dynamic, Dynamic> T = Matrix<RealScalar, Dynamic, Dynamic>::Zero(n, n); | |
| T.diagonal() = diag; | |
| T.template diagonal<1>() = subdiag; | |
| T.template diagonal<-1>() = subdiag; | |
| VERIFY_IS_APPROX( | |
| T, eig1.eigenvectors().real() * eig1.eigenvalues().asDiagonal() * eig1.eigenvectors().real().transpose()); | |
| // Case 2: Tiny values. | |
| scale = (std::numeric_limits<RealScalar>::min)() * RealScalar(100); | |
| for (Index i = 0; i < n; ++i) diag(i) = scale * RealScalar(i + 1); | |
| for (Index i = 0; i < n - 1; ++i) subdiag(i) = scale * RealScalar(0.5); | |
| SelfAdjointEigenSolver<MatrixType> eig2; | |
| eig2.computeFromTridiagonal(diag, subdiag, ComputeEigenvectors); | |
| VERIFY_IS_EQUAL(eig2.info(), Success); | |
| // Eigenvalues-only mode should produce the same eigenvalues. | |
| SelfAdjointEigenSolver<MatrixType> eig2v; | |
| eig2v.computeFromTridiagonal(diag, subdiag, EigenvaluesOnly); | |
| VERIFY_IS_EQUAL(eig2v.info(), Success); | |
| VERIFY_IS_APPROX(eig2.eigenvalues(), eig2v.eigenvalues()); | |
| } | |
| template <typename RealScalar> | |
| void selfadjointeigensolver_dense_deflation_scale_invariance() { | |
| const RealScalar epsilon = NumTraits<RealScalar>::epsilon(); | |
| const RealScalar scales[] = {RealScalar(1), RealScalar(2)}; | |
| for (RealScalar scale : scales) { | |
| Matrix<RealScalar, 2, 1> diag = Matrix<RealScalar, 2, 1>::Constant(scale); | |
| Matrix<RealScalar, Dynamic, 1> subdiag(1); | |
| subdiag[0] = RealScalar(1.25) * epsilon * scale; | |
| Matrix<std::complex<RealScalar>, Dynamic, Dynamic> eigenvectors = | |
| Matrix<std::complex<RealScalar>, Dynamic, Dynamic>::Identity(2, 2); | |
| const ComputationInfo info = internal::computeFromTridiagonal_impl<false>(diag, subdiag, 30, true, eigenvectors); | |
| VERIFY_IS_EQUAL(info, Success); | |
| VERIFY(diag[0] < diag[1]); | |
| } | |
| } | |
| // Test computeFromTridiagonal with wide dynamic range across decoupled blocks. | |
| // This exercises the per-block scaling in computeFromTridiagonal_impl: a zero on the | |
| // subdiagonal decouples the matrix into blocks with vastly different scales. Global | |
| // scaling would underflow the small block; per-block scaling handles both correctly. | |
| template <typename RealScalar> | |
| void selfadjointeigensolver_tridiagonal_wide_range() { | |
| using std::sqrt; | |
| typedef Matrix<RealScalar, Dynamic, Dynamic> MatrixType; | |
| typedef Matrix<RealScalar, Dynamic, 1> VectorType; | |
| // Block 1: entries near overflow threshold. | |
| // Block 2: entries near 1. | |
| // Separated by a zero subdiagonal entry. | |
| const RealScalar big = sqrt(NumTraits<RealScalar>::highest()) / RealScalar(10); | |
| const Index n = 6; | |
| VectorType diag(n), subdiag(n - 1); | |
| // First block: [0..2], large scale. | |
| diag(0) = big; | |
| diag(1) = big * RealScalar(1.1); | |
| diag(2) = big * RealScalar(0.9); | |
| subdiag(0) = big * RealScalar(0.01); | |
| subdiag(1) = big * RealScalar(0.02); | |
| // Zero subdiagonal decouples the two blocks. | |
| subdiag(2) = RealScalar(0); | |
| // Second block: [3..5], O(1) scale. | |
| diag(3) = RealScalar(1); | |
| diag(4) = RealScalar(2); | |
| diag(5) = RealScalar(3); | |
| subdiag(3) = RealScalar(0.5); | |
| subdiag(4) = RealScalar(0.3); | |
| // Build the full tridiagonal matrix for residual checking. | |
| MatrixType T = MatrixType::Zero(n, n); | |
| T.diagonal() = diag; | |
| T.template diagonal<1>() = subdiag; | |
| T.template diagonal<-1>() = subdiag; | |
| SelfAdjointEigenSolver<MatrixType> eig; | |
| eig.computeFromTridiagonal(diag, subdiag, ComputeEigenvectors); | |
| VERIFY_IS_EQUAL(eig.info(), Success); | |
| // Eigenvalues must be sorted. | |
| for (Index i = 1; i < n; ++i) { | |
| VERIFY(eig.eigenvalues()(i) >= eig.eigenvalues()(i - 1)); | |
| } | |
| // Eigenvectors must be orthonormal. | |
| RealScalar unitary_tol = RealScalar(4) * RealScalar(n) * NumTraits<RealScalar>::epsilon(); | |
| VERIFY(eig.eigenvectors().isUnitary(unitary_tol)); | |
| // Full residual check in scaled coordinates. | |
| RealScalar Tnorm = T.cwiseAbs().maxCoeff(); | |
| MatrixType Tscaled = T / Tnorm; | |
| MatrixType residual = Tscaled * eig.eigenvectors() - eig.eigenvectors() * (eig.eigenvalues() / Tnorm).asDiagonal(); | |
| RealScalar rel_err = residual.norm() / Tscaled.norm(); | |
| VERIFY(rel_err <= RealScalar(8) * RealScalar(n) * NumTraits<RealScalar>::epsilon()); | |
| // The small eigenvalues (~1,2,3) must be accurate, not lost to underflow. | |
| // With global scaling to [-1,1], dividing by 'big' would underflow these to zero. | |
| // Verify the small eigenvalues are within O(eps) of their true values. | |
| // The small block is exactly [[1, 0.5, 0], [0.5, 2, 0.3], [0, 0.3, 3]]. | |
| MatrixType T_small(3, 3); | |
| T_small << RealScalar(1), RealScalar(0.5), RealScalar(0), RealScalar(0.5), RealScalar(2), RealScalar(0.3), | |
| RealScalar(0), RealScalar(0.3), RealScalar(3); | |
| SelfAdjointEigenSolver<MatrixType> eig_small(T_small); | |
| VectorType small_evals = eig_small.eigenvalues(); | |
| // Find the 3 smallest eigenvalues from the combined solver (they should be sorted first). | |
| VectorType combined_small = eig.eigenvalues().head(3); | |
| VERIFY_IS_APPROX(combined_small, small_evals); | |
| // Eigenvalues-only mode must agree. | |
| SelfAdjointEigenSolver<MatrixType> eig_vals; | |
| eig_vals.computeFromTridiagonal(diag, subdiag, EigenvaluesOnly); | |
| VERIFY_IS_EQUAL(eig_vals.info(), Success); | |
| VERIFY_IS_APPROX(eig.eigenvalues() / Tnorm, eig_vals.eigenvalues() / Tnorm); | |
| } | |
| // Test computeFromTridiagonal with structured hard-case matrices from the literature. | |
| template <typename RealScalar> | |
| void selfadjointeigensolver_structured_tridiagonal() { | |
| typedef Matrix<RealScalar, Dynamic, Dynamic> MatrixType; | |
| test::for_all_symmetric_tridiag_test_matrices<RealScalar>([](const auto& diag, const auto& offdiag) { | |
| Index n = diag.size(); | |
| // Build the full symmetric tridiagonal matrix for residual checking. | |
| MatrixType T = MatrixType::Zero(n, n); | |
| T.diagonal() = diag; | |
| if (n > 1) { | |
| T.template diagonal<1>() = offdiag; | |
| T.template diagonal<-1>() = offdiag; | |
| } | |
| RealScalar Tnorm = T.cwiseAbs().maxCoeff(); | |
| // Test with eigenvectors. | |
| SelfAdjointEigenSolver<MatrixType> eig; | |
| eig.computeFromTridiagonal(diag, offdiag, ComputeEigenvectors); | |
| VERIFY_IS_EQUAL(eig.info(), Success); | |
| // Eigenvalues must be sorted. | |
| for (Index i = 1; i < n; ++i) { | |
| VERIFY(eig.eigenvalues()(i) >= eig.eigenvalues()(i - 1)); | |
| } | |
| // Eigenvectors must be orthonormal. | |
| RealScalar unitary_tol = | |
| numext::maxi(RealScalar(4) * RealScalar(n) * NumTraits<RealScalar>::epsilon(), test_precision<RealScalar>()); | |
| VERIFY(eig.eigenvectors().isUnitary(unitary_tol)); | |
| // Residual check: ||T*V - V*D||_F / ||T||_max should be O(n*eps). | |
| // Scale T to avoid overflow in the matrix product when entries span extreme ranges. | |
| if (Tnorm > (std::numeric_limits<RealScalar>::min)()) { | |
| MatrixType Tscaled = T / Tnorm; | |
| MatrixType residual = | |
| Tscaled * eig.eigenvectors() - eig.eigenvectors() * (eig.eigenvalues() / Tnorm).asDiagonal(); | |
| RealScalar rel_err = residual.norm() / Tscaled.norm(); | |
| VERIFY(rel_err <= RealScalar(8) * RealScalar(n) * NumTraits<RealScalar>::epsilon()); | |
| } | |
| // Eigenvalues-only mode must produce the same eigenvalues. | |
| SelfAdjointEigenSolver<MatrixType> eig_vals; | |
| eig_vals.computeFromTridiagonal(diag, offdiag, EigenvaluesOnly); | |
| VERIFY_IS_EQUAL(eig_vals.info(), Success); | |
| if (Tnorm > (std::numeric_limits<RealScalar>::min)()) { | |
| VERIFY_IS_APPROX(eig.eigenvalues() / Tnorm, eig_vals.eigenvalues() / Tnorm); | |
| } | |
| }); | |
| } | |
| // Test with diagonal matrices (tridiagonalization is trivial). | |
| template <typename MatrixType> | |
| void selfadjointeigensolver_diagonal(const MatrixType& m) { | |
| typedef typename MatrixType::Scalar Scalar; | |
| typedef typename NumTraits<Scalar>::Real RealScalar; | |
| Index n = m.rows(); | |
| // Random diagonal matrix. | |
| MatrixType diag = MatrixType::Zero(n, n); | |
| for (Index i = 0; i < n; ++i) { | |
| diag(i, i) = internal::random<RealScalar>(-100, 100); | |
| } | |
| selfadjointeigensolver_essential_check(diag); | |
| // The eigenvalues should be the diagonal entries, sorted. | |
| SelfAdjointEigenSolver<MatrixType> eig(diag); | |
| VERIFY_IS_EQUAL(eig.info(), Success); | |
| Matrix<RealScalar, Dynamic, 1> expected_evals(n); | |
| for (Index i = 0; i < n; ++i) expected_evals(i) = numext::real(diag(i, i)); | |
| std::sort(expected_evals.data(), expected_evals.data() + n); | |
| VERIFY_IS_APPROX(eig.eigenvalues(), expected_evals); | |
| } | |
| // Test operatorInverseSqrt more thoroughly. | |
| template <typename MatrixType> | |
| void selfadjointeigensolver_inverse_sqrt(const MatrixType& m) { | |
| Index n = m.rows(); | |
| if (n < 1) return; | |
| // Create a positive-definite matrix. | |
| MatrixType a = MatrixType::Random(n, n); | |
| MatrixType spd = a.adjoint() * a + MatrixType::Identity(n, n); | |
| spd.template triangularView<StrictlyUpper>().setZero(); | |
| SelfAdjointEigenSolver<MatrixType> eig(spd); | |
| VERIFY_IS_EQUAL(eig.info(), Success); | |
| MatrixType sqrtA = eig.operatorSqrt(); | |
| MatrixType invSqrtA = eig.operatorInverseSqrt(); | |
| // sqrtA * invSqrtA should be identity. | |
| VERIFY_IS_APPROX(sqrtA * invSqrtA, MatrixType::Identity(n, n)); | |
| // invSqrtA * A * invSqrtA should be identity. | |
| VERIFY_IS_APPROX(invSqrtA * spd.template selfadjointView<Lower>() * invSqrtA, MatrixType::Identity(n, n)); | |
| // invSqrtA should be symmetric/selfadjoint. | |
| VERIFY_IS_APPROX(invSqrtA, invSqrtA.adjoint()); | |
| } | |
| // Test that RowMajor matrices work correctly with computeDirect. | |
| template <int> | |
| void selfadjointeigensolver_rowmajor() { | |
| typedef Matrix<double, 3, 3, RowMajor> RowMajorMatrix3d; | |
| typedef Matrix<double, 2, 2, RowMajor> RowMajorMatrix2d; | |
| typedef Matrix<float, 3, 3, RowMajor> RowMajorMatrix3f; | |
| typedef Matrix<float, 2, 2, RowMajor> RowMajorMatrix2f; | |
| // 3x3 RowMajor double | |
| { | |
| RowMajorMatrix3d a = RowMajorMatrix3d::Random(); | |
| RowMajorMatrix3d symmA = a.transpose() * a; | |
| SelfAdjointEigenSolver<RowMajorMatrix3d> eig; | |
| eig.computeDirect(symmA); | |
| VERIFY_IS_EQUAL(eig.info(), Success); | |
| // Compare with iterative solver. | |
| SelfAdjointEigenSolver<RowMajorMatrix3d> eigRef(symmA); | |
| VERIFY_IS_APPROX(eigRef.eigenvalues(), eig.eigenvalues()); | |
| } | |
| // 2x2 RowMajor double | |
| { | |
| RowMajorMatrix2d a = RowMajorMatrix2d::Random(); | |
| RowMajorMatrix2d symmA = a.transpose() * a; | |
| SelfAdjointEigenSolver<RowMajorMatrix2d> eig; | |
| eig.computeDirect(symmA); | |
| VERIFY_IS_EQUAL(eig.info(), Success); | |
| SelfAdjointEigenSolver<RowMajorMatrix2d> eigRef(symmA); | |
| VERIFY_IS_APPROX(eigRef.eigenvalues(), eig.eigenvalues()); | |
| } | |
| // 3x3 RowMajor float | |
| { | |
| RowMajorMatrix3f a = RowMajorMatrix3f::Random(); | |
| RowMajorMatrix3f symmA = a.transpose() * a; | |
| SelfAdjointEigenSolver<RowMajorMatrix3f> eig; | |
| eig.computeDirect(symmA); | |
| VERIFY_IS_EQUAL(eig.info(), Success); | |
| SelfAdjointEigenSolver<RowMajorMatrix3f> eigRef(symmA); | |
| VERIFY_IS_APPROX(eigRef.eigenvalues(), eig.eigenvalues()); | |
| } | |
| // 2x2 RowMajor float | |
| { | |
| RowMajorMatrix2f a = RowMajorMatrix2f::Random(); | |
| RowMajorMatrix2f symmA = a.transpose() * a; | |
| SelfAdjointEigenSolver<RowMajorMatrix2f> eig; | |
| eig.computeDirect(symmA); | |
| VERIFY_IS_EQUAL(eig.info(), Success); | |
| SelfAdjointEigenSolver<RowMajorMatrix2f> eigRef(symmA); | |
| VERIFY_IS_APPROX(eigRef.eigenvalues(), eig.eigenvalues()); | |
| } | |
| // Dynamic RowMajor with iterative solver | |
| { | |
| typedef Matrix<double, Dynamic, Dynamic, RowMajor> RowMajorMatrixXd; | |
| int s = internal::random<int>(2, 20); | |
| RowMajorMatrixXd a = RowMajorMatrixXd::Random(s, s); | |
| RowMajorMatrixXd symmA = a.transpose() * a; | |
| SelfAdjointEigenSolver<RowMajorMatrixXd> eig(symmA); | |
| VERIFY_IS_EQUAL(eig.info(), Success); | |
| double scaling = symmA.cwiseAbs().maxCoeff(); | |
| if (scaling > (std::numeric_limits<double>::min)()) { | |
| VERIFY_IS_APPROX((symmA.template selfadjointView<Lower>() * eig.eigenvectors()) / scaling, | |
| (eig.eigenvectors() * eig.eigenvalues().asDiagonal()) / scaling); | |
| } | |
| } | |
| } | |
| template <int> | |
| void bug_854() { | |
| Matrix3d m; | |
| m << 850.961, 51.966, 0, 51.966, 254.841, 0, 0, 0, 0; | |
| selfadjointeigensolver_essential_check(m); | |
| } | |
| template <int> | |
| void bug_1014() { | |
| Matrix3d m; | |
| m << 0.11111111111111114658, 0, 0, 0, 0.11111111111111109107, 0, 0, 0, 0.11111111111111107719; | |
| selfadjointeigensolver_essential_check(m); | |
| } | |
| template <int> | |
| void bug_1225() { | |
| Matrix3d m1, m2; | |
| m1.setRandom(); | |
| m1 = m1 * m1.transpose(); | |
| m2 = m1.triangularView<Upper>(); | |
| SelfAdjointEigenSolver<Matrix3d> eig1(m1); | |
| SelfAdjointEigenSolver<Matrix3d> eig2(m2.selfadjointView<Upper>()); | |
| VERIFY_IS_APPROX(eig1.eigenvalues(), eig2.eigenvalues()); | |
| } | |
| // Verify that non-finite inputs are detected for all sizes, including 1x1. | |
| template <int> | |
| void selfadjointeigensolver_nonfinite() { | |
| const double inf = std::numeric_limits<double>::infinity(); | |
| const double nan = std::numeric_limits<double>::quiet_NaN(); | |
| // 1x1 Inf. | |
| { | |
| Matrix<double, 1, 1> m; | |
| m << inf; | |
| SelfAdjointEigenSolver<Matrix<double, 1, 1>> eig(m); | |
| VERIFY_IS_EQUAL(eig.info(), NoConvergence); | |
| } | |
| // 1x1 NaN. | |
| { | |
| Matrix<double, 1, 1> m; | |
| m << nan; | |
| SelfAdjointEigenSolver<Matrix<double, 1, 1>> eig(m); | |
| VERIFY_IS_EQUAL(eig.info(), NoConvergence); | |
| } | |
| // 1x1 -Inf. | |
| { | |
| Matrix<double, 1, 1> m; | |
| m << -inf; | |
| SelfAdjointEigenSolver<Matrix<double, 1, 1>> eig(m); | |
| VERIFY_IS_EQUAL(eig.info(), NoConvergence); | |
| } | |
| // 3x3 with Inf. | |
| { | |
| Matrix3d m = Matrix3d::Identity(); | |
| m(1, 1) = inf; | |
| SelfAdjointEigenSolver<Matrix3d> eig(m); | |
| VERIFY_IS_EQUAL(eig.info(), NoConvergence); | |
| } | |
| // 3x3 with NaN. | |
| { | |
| Matrix3d m = Matrix3d::Identity(); | |
| m(0, 1) = m(1, 0) = nan; | |
| SelfAdjointEigenSolver<Matrix3d> eig(m); | |
| VERIFY_IS_EQUAL(eig.info(), NoConvergence); | |
| } | |
| // Dynamic size with Inf. | |
| { | |
| MatrixXd m = MatrixXd::Identity(5, 5); | |
| m(3, 3) = inf; | |
| SelfAdjointEigenSolver<MatrixXd> eig(m); | |
| VERIFY_IS_EQUAL(eig.info(), NoConvergence); | |
| } | |
| } | |
| template <int> | |
| void bug_1204() { | |
| SparseMatrix<double> A(2, 2); | |
| A.setIdentity(); | |
| SelfAdjointEigenSolver<Eigen::SparseMatrix<double>> eig(A); | |
| } | |
| template <int> | |
| void selfadjointeigensolver_tridiagonal_zerosized() { | |
| SelfAdjointEigenSolver<MatrixXd> eig; | |
| VectorXd diag(0), subdiag(0); | |
| eig.computeFromTridiagonal(diag, subdiag, EigenvaluesOnly); | |
| VERIFY_IS_EQUAL(eig.info(), Success); | |
| VERIFY_IS_EQUAL(eig.eigenvalues().size(), 0); | |
| VERIFY_RAISES_ASSERT(eig.eigenvectors()); | |
| eig.computeFromTridiagonal(diag, subdiag, ComputeEigenvectors); | |
| VERIFY_IS_EQUAL(eig.info(), Success); | |
| VERIFY_IS_EQUAL(eig.eigenvalues().size(), 0); | |
| VERIFY_IS_EQUAL(eig.eigenvectors().rows(), 0); | |
| VERIFY_IS_EQUAL(eig.eigenvectors().cols(), 0); | |
| } | |
| // Specific 3x3 test cases that stress the direct solver. | |
| template <int> | |
| void direct_3x3_stress() { | |
| // Near-planar point cloud covariance: two large eigenvalues, one near-zero. | |
| { | |
| Matrix3d m; | |
| m << 100, 50, 0.001, 50, 100, 0.002, 0.001, 0.002, 1e-10; | |
| selfadjointeigensolver_essential_check(m); | |
| } | |
| // All equal diagonal entries (triple eigenvalue). | |
| { | |
| Matrix3d m = Matrix3d::Identity() * 7.0; | |
| selfadjointeigensolver_essential_check(m); | |
| } | |
| // Two exactly equal eigenvalues (from explicit construction). | |
| { | |
| Matrix3d q; | |
| q << 1, 0, 0, 0, 1.0 / std::sqrt(2.0), 1.0 / std::sqrt(2.0), 0, -1.0 / std::sqrt(2.0), 1.0 / std::sqrt(2.0); | |
| Vector3d d(1.0, 5.0, 5.0); | |
| Matrix3d m = q * d.asDiagonal() * q.transpose(); | |
| selfadjointeigensolver_essential_check(m); | |
| } | |
| // Large off-diagonal relative to diagonal. | |
| { | |
| Matrix3d m; | |
| m << 1, 1000, 1000, 1000, 1, 1000, 1000, 1000, 1; | |
| selfadjointeigensolver_essential_check(m); | |
| } | |
| // Nearly singular: one eigenvalue much smaller than others. | |
| { | |
| Matrix3d m; | |
| m << 1, 0.5, 0.3, 0.5, 1, 0.4, 0.3, 0.4, 1; | |
| m *= 1e15; | |
| Matrix3d perturbation = Matrix3d::Zero(); | |
| perturbation(0, 0) = 1e-15; | |
| m += perturbation; | |
| selfadjointeigensolver_essential_check(m); | |
| } | |
| } | |
| // Specific 2x2 test cases that stress the direct solver. | |
| template <int> | |
| void direct_2x2_stress() { | |
| // Equal eigenvalues. | |
| { | |
| Matrix2d m = Matrix2d::Identity() * 42.0; | |
| selfadjointeigensolver_essential_check(m); | |
| } | |
| // Very small off-diagonal. | |
| { | |
| Matrix2d m; | |
| m << 1.0, 1e-15, 1e-15, 1.0; | |
| selfadjointeigensolver_essential_check(m); | |
| } | |
| // Huge ratio between diagonal entries. | |
| { | |
| Matrix2d m; | |
| m << 1e100, 0, 0, 1e-100; | |
| selfadjointeigensolver_essential_check(m); | |
| } | |
| // Anti-diagonal dominant. | |
| { | |
| Matrix2d m; | |
| m << 0, 1e10, 1e10, 0; | |
| selfadjointeigensolver_essential_check(m); | |
| } | |
| // Negative entries. | |
| { | |
| Matrix2d m; | |
| m << -5.0, 3.0, 3.0, -5.0; | |
| selfadjointeigensolver_essential_check(m); | |
| } | |
| } | |
| // Regression test for issue #1739: a solver constructed with its size must not | |
| // allocate in compute(), for any of the three generalized problem types. | |
| template <typename MatrixType> | |
| void generalizedselfadjointeigensolver_no_malloc() { | |
| typedef typename MatrixType::Scalar Scalar; | |
| typedef typename NumTraits<Scalar>::Real RealScalar; | |
| const Index n = 20; | |
| MatrixType a = MatrixType::Random(n, n); | |
| MatrixType symmA = a + a.adjoint(); | |
| MatrixType b = MatrixType::Random(n, n); | |
| // Make b positive definite so that its Cholesky decomposition succeeds. | |
| MatrixType symmB = b * b.adjoint() + RealScalar(n) * MatrixType::Identity(n, n); | |
| const int types[] = {Ax_lBx, ABx_lx, BAx_lx}; | |
| for (int type : types) { | |
| for (int vecs : {int(ComputeEigenvectors), int(EigenvaluesOnly)}) { | |
| GeneralizedSelfAdjointEigenSolver<MatrixType> eig(n); | |
| internal::set_is_malloc_allowed(false); | |
| eig.compute(symmA, symmB, vecs | type); | |
| internal::set_is_malloc_allowed(true); | |
| VERIFY_IS_EQUAL(eig.info(), Success); | |
| } | |
| } | |
| // The reused workspaces must not change the result. | |
| GeneralizedSelfAdjointEigenSolver<MatrixType> reused(n); | |
| reused.compute(symmA, symmB); | |
| reused.compute(symmA, symmB); | |
| GeneralizedSelfAdjointEigenSolver<MatrixType> fresh(symmA, symmB); | |
| VERIFY_IS_APPROX(reused.eigenvalues(), fresh.eigenvalues()); | |
| VERIFY_IS_APPROX(symmA * reused.eigenvectors(), symmB * reused.eigenvectors() * reused.eigenvalues().asDiagonal()); | |
| // A B that is not positive definite must not be reported as a successful decomposition. | |
| for (int type : types) { | |
| GeneralizedSelfAdjointEigenSolver<MatrixType> indefinite(symmA, -symmB, ComputeEigenvectors | type); | |
| VERIFY_IS_EQUAL(indefinite.info(), NumericalIssue); | |
| } | |
| } | |
| #if defined(EIGEN_TEST_PART_20) || defined(EIGEN_TEST_PART_ALL) | |
| template <typename = void> | |
| void selfadjoint_iterative_scaling_rounding() { | |
| Matrix4f matrix = Matrix4f::Zero(); | |
| matrix.diagonal() << numext::bit_cast<float>(numext::uint32_t(0x44123456)), | |
| numext::bit_cast<float>(numext::uint32_t(0x4f123456)), numext::bit_cast<float>(numext::uint32_t(0x537dcf0e)), | |
| numext::bit_cast<float>(numext::uint32_t(0x58f6aaed)); | |
| // A diagonal input requires no rotations: normalization must not round its representable eigenvalues. | |
| for (int options : {EigenvaluesOnly, ComputeEigenvectors}) { | |
| SelfAdjointEigenSolver<Matrix4f> fixed(matrix, options); | |
| SelfAdjointEigenSolver<MatrixXf> dynamic(matrix, options); | |
| VERIFY_IS_EQUAL(fixed.info(), Success); | |
| VERIFY_IS_EQUAL(dynamic.info(), Success); | |
| VERIFY_IS_EQUAL(fixed.eigenvalues(), matrix.diagonal()); | |
| VERIFY_IS_EQUAL(dynamic.eigenvalues(), matrix.diagonal()); | |
| } | |
| Matrix2f small = Matrix2f::Zero(); | |
| small.diagonal() << numext::bit_cast<float>(numext::uint32_t(0x06aceabe)), | |
| numext::bit_cast<float>(numext::uint32_t(0x072319ed)); | |
| // compute(), not computeDirect(): fixed-size matrices also use the iterative entry point. | |
| SelfAdjointEigenSolver<Matrix2f> solver(small); | |
| VERIFY_IS_EQUAL(solver.eigenvalues(), small.diagonal()); | |
| Vector2f diagonal = Vector2f::Zero(); | |
| Matrix<float, 1, 1> offDiagonal; | |
| offDiagonal[0] = numext::bit_cast<float>(numext::uint32_t(0x54fca0e4)); | |
| solver.computeFromTridiagonal(diagonal, offDiagonal, EigenvaluesOnly); | |
| VERIFY_IS_EQUAL(solver.info(), Success); | |
| VERIFY_IS_EQUAL(solver.eigenvalues()[0], -offDiagonal[0]); | |
| VERIFY_IS_EQUAL(solver.eigenvalues()[1], offDiagonal[0]); | |
| } | |
| template <typename Scalar, int Options> | |
| void selfadjoint_iterative_scaling_blocks() { | |
| using Real = typename NumTraits<Scalar>::Real; | |
| using MatrixType = Matrix<Scalar, Dynamic, Dynamic, Options>; | |
| using Vector = Matrix<Real, Dynamic, 1>; | |
| MatrixType base = MatrixType::Zero(4, 4); | |
| base.diagonal() << Scalar(2), Scalar(2), Scalar(10), Scalar(10); | |
| Scalar phase = Scalar(1); | |
| EIGEN_IF_CONSTEXPR (NumTraits<Scalar>::IsComplex) { | |
| phase = numext::sqrt(Scalar(-1)); | |
| } | |
| base(1, 0) = phase; | |
| base(0, 1) = numext::conj(phase); | |
| base(3, 2) = Scalar(2) * phase; | |
| base(2, 3) = numext::conj(base(3, 2)); | |
| Vector expected(4); | |
| expected << Real(1), Real(3), Real(8), Real(12); | |
| // 16*n*epsilon bounds the accumulated rotations and reconstruction for these well-separated 4x4 spectra. | |
| const Real tolerance = Real(64) * NumTraits<Real>::epsilon(); | |
| for (int exponent : {-60, 0, 60}) { | |
| const Real scale = numext::ldexp(Real(1), exponent); | |
| MatrixType input = base * scale; | |
| input.template triangularView<StrictlyUpper>().setConstant(Scalar(std::numeric_limits<Real>::quiet_NaN())); | |
| for (int options : {EigenvaluesOnly, ComputeEigenvectors}) { | |
| SelfAdjointEigenSolver<MatrixType> solver(input, options); | |
| VERIFY_IS_EQUAL(solver.info(), Success); | |
| const Vector values = solver.eigenvalues() / scale; | |
| VERIFY(values.allFinite()); | |
| VERIFY((values - expected).norm() <= tolerance * expected.norm()); | |
| if (options == ComputeEigenvectors) { | |
| const MatrixType& vectors = solver.eigenvectors(); | |
| VERIFY(vectors.allFinite()); | |
| VERIFY((base * vectors - vectors * values.asDiagonal()).norm() <= tolerance * base.norm()); | |
| VERIFY((vectors.adjoint() * vectors - MatrixType::Identity(4, 4)).norm() <= tolerance); | |
| } | |
| } | |
| } | |
| // Two nontrivial blocks at different scales exercise restoration when the active block changes. | |
| Vector diagonal = Vector::Zero(4), offDiagonal(3); | |
| const Real large = numext::ldexp(Real(1.5), 60); | |
| offDiagonal << Real(1.5), Real(0), large; | |
| expected << -large, Real(-1.5), Real(1.5), large; | |
| for (int options : {EigenvaluesOnly, ComputeEigenvectors}) { | |
| SelfAdjointEigenSolver<MatrixType> solver; | |
| solver.computeFromTridiagonal(diagonal, offDiagonal, options); | |
| VERIFY_IS_EQUAL(solver.info(), Success); | |
| VERIFY(solver.eigenvalues().allFinite()); | |
| VERIFY(((solver.eigenvalues() - expected).array().abs() <= tolerance * expected.array().abs()).all()); | |
| if (options == ComputeEigenvectors) { | |
| MatrixType matrix = MatrixType::Zero(4, 4); | |
| matrix(0, 1) = matrix(1, 0) = Scalar(1.5); | |
| matrix(2, 3) = matrix(3, 2) = Scalar(large); | |
| const MatrixType& vectors = solver.eigenvectors(); | |
| VERIFY(vectors.allFinite()); | |
| const MatrixType residual = matrix * vectors - vectors * solver.eigenvalues().asDiagonal(); | |
| // Normalize each block separately so the large block cannot mask errors in the small one. | |
| VERIFY((residual.template topRows<2>() / Real(1.5)).norm() <= tolerance); | |
| VERIFY((residual.template bottomRows<2>() / large).norm() <= tolerance); | |
| VERIFY((vectors.adjoint() * vectors - MatrixType::Identity(4, 4)).norm() <= tolerance); | |
| } | |
| } | |
| // An interrupted QR iteration must still restore the active block's units. | |
| diagonal << Real(1), Real(2), Real(3), Real(4); | |
| offDiagonal.setOnes(); | |
| Vector scaledDiagonal = diagonal * large, scaledOffDiagonal = offDiagonal * large; | |
| MatrixType unused; | |
| const auto info = internal::computeFromTridiagonal_impl<true>(diagonal, offDiagonal, 1, false, unused); | |
| const auto scaledInfo = | |
| internal::computeFromTridiagonal_impl<true>(scaledDiagonal, scaledOffDiagonal, 1, false, unused); | |
| VERIFY_IS_EQUAL(info, NoConvergence); | |
| VERIFY_IS_EQUAL(scaledInfo, info); | |
| VERIFY((scaledDiagonal / large - diagonal).norm() <= tolerance * diagonal.norm()); | |
| VERIFY((scaledOffDiagonal / large - offDiagonal).norm() <= tolerance * offDiagonal.norm()); | |
| } | |
| #endif | |
| #if defined(EIGEN_TEST_PART_21) || defined(EIGEN_TEST_PART_ALL) | |
| template <typename Scalar, int Size, int Options> | |
| void direct_selfadjoint_safe_scaling() { | |
| using MatrixType = Matrix<Scalar, Size, Size, Options>; | |
| using WideMatrix = Matrix<long double, Size, Size>; | |
| using WideVector = Matrix<long double, Size, 1>; | |
| const long double epsilon = static_cast<long double>(NumTraits<Scalar>::epsilon()); | |
| // Well-separated spectra: allow 32*n*epsilon for the direct formulas and reconstruction. | |
| const long double tolerance = 32 * Size * epsilon; | |
| MatrixType base = MatrixType::Zero(); | |
| base(0, 0) = Scalar(1); | |
| base(1, 1) = Scalar(1); | |
| base(0, 1) = base(1, 0) = Scalar(0.5); | |
| EIGEN_IF_CONSTEXPR (Size == 3) { | |
| base(2, 2) = Scalar(2); | |
| } | |
| const Scalar minimum = (std::numeric_limits<Scalar>::min)(); | |
| const Scalar maximum = NumTraits<Scalar>::highest(); | |
| for (Scalar scale : {Scalar(1), minimum, Scalar(minimum / Scalar(16)), Scalar(maximum / Scalar(8))}) { | |
| for (bool zeroTrace : {false, true}) { | |
| MatrixType input; | |
| // ARMv7 NEON flushes subnormal products even outside ScopedFlushToZero. | |
| // Construct the intended coefficients without packet arithmetic. | |
| for (Index j = 0; j < Size; ++j) | |
| for (Index i = 0; i < Size; ++i) { | |
| volatile Scalar coefficient = base(i, j); | |
| input(i, j) = coefficient * scale; | |
| } | |
| if (zeroTrace) input.diagonal().setZero(); | |
| const WideMatrix normalized = input.template cast<long double>() / static_cast<long double>(scale); | |
| input.template triangularView<StrictlyUpper>().setConstant(Scalar(std::numeric_limits<Scalar>::quiet_NaN())); | |
| for (int options : {EigenvaluesOnly, ComputeEigenvectors}) { | |
| #if EIGEN_ARCH_i386_OR_x86_64 && defined(_MM_SET_DENORMALS_ZERO_MODE) | |
| for (int flushMode : {0, 1, 2, 3}) { | |
| // Binary64 long double has no encoding-based scaling path yet. | |
| if (flushMode != 0 && std::is_same<Scalar, long double>::value && | |
| std::numeric_limits<long double>::digits == std::numeric_limits<double>::digits) | |
| continue; | |
| SelfAdjointEigenSolver<MatrixType> solver; | |
| { | |
| // Preserve the caller's FP state; select FTZ and DAZ independently inside this scope. | |
| ScopedFlushToZero mode; | |
| const unsigned int ftz = (flushMode & 1) ? _MM_FLUSH_ZERO_ON : _MM_FLUSH_ZERO_OFF; | |
| const unsigned int daz = (flushMode & 2) ? _MM_DENORMALS_ZERO_ON : _MM_DENORMALS_ZERO_OFF; | |
| _MM_SET_FLUSH_ZERO_MODE(ftz); | |
| _MM_SET_DENORMALS_ZERO_MODE(daz); | |
| VERIFY(mode.isSupported()); | |
| VERIFY_IS_EQUAL(_MM_GET_FLUSH_ZERO_MODE(), ftz); | |
| VERIFY_IS_EQUAL(_MM_GET_DENORMALS_ZERO_MODE(), daz); | |
| solver.computeDirect(input, options); | |
| } | |
| #else | |
| for (bool flush : {false, true}) { | |
| if (flush && std::is_same<Scalar, long double>::value && | |
| std::numeric_limits<long double>::digits == std::numeric_limits<double>::digits) | |
| continue; | |
| SelfAdjointEigenSolver<MatrixType> solver; | |
| if (flush) { | |
| ScopedFlushToZero mode; | |
| solver.computeDirect(input, options); | |
| } else { | |
| solver.computeDirect(input, options); | |
| } | |
| #endif | |
| VERIFY_IS_EQUAL(solver.info(), Success); | |
| VERIFY(solver.eigenvalues().allFinite()); | |
| const WideVector values = solver.eigenvalues().template cast<long double>() / static_cast<long double>(scale); | |
| SelfAdjointEigenSolver<WideMatrix> reference(normalized, EigenvaluesOnly); | |
| const long double quantum = | |
| static_cast<long double>(std::numeric_limits<Scalar>::denorm_min()) / static_cast<long double>(scale); | |
| VERIFY((values - reference.eigenvalues()).norm() <= tolerance * normalized.norm() + Size * quantum); | |
| for (Index i = 1; i < Size; ++i) VERIFY(values[i - 1] <= values[i]); | |
| if (options == ComputeEigenvectors) { | |
| VERIFY(solver.eigenvectors().allFinite()); | |
| const WideMatrix vectors = solver.eigenvectors().template cast<long double>(); | |
| VERIFY((normalized * vectors - vectors * values.asDiagonal()).norm() <= | |
| tolerance * normalized.norm() + Size * quantum); | |
| VERIFY((vectors.transpose() * vectors - WideMatrix::Identity()).norm() <= tolerance); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Same-sign traces and mixed-sign centering can overflow even though every eigenvalue is representable. | |
| for (int negativeCount = 0; negativeCount <= Size; ++negativeCount) { | |
| MatrixType input = MatrixType::Identity() * maximum; | |
| for (int i = 0; i < negativeCount; ++i) input(i, i) = -maximum; | |
| for (int options : {EigenvaluesOnly, ComputeEigenvectors}) { | |
| SelfAdjointEigenSolver<MatrixType> solver; | |
| solver.computeDirect(input, options); | |
| VERIFY_IS_EQUAL(solver.info(), Success); | |
| VERIFY(solver.eigenvalues().allFinite()); | |
| const WideVector values = solver.eigenvalues().template cast<long double>() / static_cast<long double>(maximum); | |
| for (Index i = 0; i < Size; ++i) { | |
| VERIFY(numext::abs(values[i] - (i < negativeCount ? -1.L : 1.L)) <= tolerance); | |
| } | |
| if (options == ComputeEigenvectors) { | |
| const WideMatrix vectors = solver.eigenvectors().template cast<long double>(); | |
| const WideMatrix normalized = input.template cast<long double>() / static_cast<long double>(maximum); | |
| VERIFY((normalized * vectors - vectors * values.asDiagonal()).norm() <= tolerance * normalized.norm()); | |
| VERIFY((vectors.transpose() * vectors - WideMatrix::Identity()).norm() <= tolerance); | |
| } | |
| } | |
| } | |
| } | |
| template <typename Scalar, int Options> | |
| void direct_selfadjoint_rotated_boundary() { | |
| using MatrixType = Matrix<Scalar, 3, 3, Options>; | |
| using WideMatrix = Matrix<long double, 3, 3>; | |
| using WideVector = Matrix<long double, 3, 1>; | |
| const long double epsilon = NumTraits<Scalar>::epsilon(); | |
| const Scalar scale = numext::ldexp(Scalar(1), std::numeric_limits<Scalar>::max_exponent - 1); | |
| const long double repeated = 2 - 64 * epsilon; | |
| const long double tolerance = 48 * epsilon; | |
| for (int i = 0; i < 32; ++i) { | |
| WideVector axis; | |
| axis << i + 1, (7 * i) % 17 + 1, (11 * i) % 23 + 1; | |
| axis.normalize(); | |
| for (long double distinct : {0.5L, -1.5L}) { | |
| // Rank-one perturbation: eigenvalues distinct, repeated, repeated. | |
| // The 64*epsilon boundary margin dominates coefficient rounding. | |
| const WideMatrix exact = repeated * WideMatrix::Identity() + (distinct - repeated) * axis * axis.transpose(); | |
| const MatrixType input = exact.template cast<Scalar>() * scale; | |
| const WideMatrix normalized = input.template cast<long double>() / static_cast<long double>(scale); | |
| WideVector expected; | |
| expected << distinct, repeated, repeated; | |
| // Weyl's bound includes coefficient rounding in the stored matrix. | |
| VERIFY(expected.maxCoeff() + (normalized - exact).norm() < | |
| static_cast<long double>(NumTraits<Scalar>::highest()) / static_cast<long double>(scale)); | |
| for (int options : {EigenvaluesOnly, ComputeEigenvectors}) { | |
| SelfAdjointEigenSolver<MatrixType> solver; | |
| solver.computeDirect(input, options); | |
| VERIFY_IS_EQUAL(solver.info(), Success); | |
| VERIFY(solver.eigenvalues().allFinite()); | |
| const WideVector values = solver.eigenvalues().template cast<long double>() / static_cast<long double>(scale); | |
| VERIFY((values - expected).norm() <= tolerance * normalized.norm()); | |
| if (options == ComputeEigenvectors) { | |
| const WideMatrix vectors = solver.eigenvectors().template cast<long double>(); | |
| VERIFY((normalized * vectors - vectors * values.asDiagonal()).norm() <= tolerance * normalized.norm()); | |
| VERIFY((vectors.transpose() * vectors - WideMatrix::Identity()).norm() <= tolerance); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| template <typename Scalar, int Size> | |
| void direct_selfadjoint_scaling_window() { | |
| using MatrixType = Matrix<Scalar, Size, Size>; | |
| using WideMatrix = Matrix<long double, Size, Size>; | |
| using WideVector = Matrix<long double, Size, 1>; | |
| // Independently tabulated float/double limits: test inside, on and outside both edges. | |
| const int lower = Size == 2 ? (sizeof(Scalar) == 4 ? -45 : -479) : (sizeof(Scalar) == 4 ? -15 : -159); | |
| const int upper = Size == 2 ? (sizeof(Scalar) == 4 ? 57 : 505) : (sizeof(Scalar) == 4 ? 19 : 168); | |
| MatrixType base = MatrixType::Zero(); | |
| base(0, 0) = Scalar(-1); | |
| base(Size - 1, Size - 1) = Scalar(1); | |
| for (Index i = 1; i < Size; ++i) base(i, i - 1) = base(i - 1, i) = Scalar(0.25); | |
| const WideMatrix normalized = base.template cast<long double>(); | |
| SelfAdjointEigenSolver<WideMatrix> reference(normalized); | |
| const long double tolerance = 32 * Size * static_cast<long double>(NumTraits<Scalar>::epsilon()); | |
| for (int exponent : {lower - 1, lower, lower + 1, upper - 1, upper, upper + 1}) { | |
| const Scalar scale = numext::ldexp(Scalar(1), exponent); | |
| const MatrixType input = base * scale; | |
| for (int options : {EigenvaluesOnly, ComputeEigenvectors}) { | |
| for (bool flush : {false, true}) { | |
| SelfAdjointEigenSolver<MatrixType> solver; | |
| if (flush) { | |
| ScopedFlushToZero mode; | |
| solver.computeDirect(input, options); | |
| } else { | |
| solver.computeDirect(input, options); | |
| } | |
| VERIFY_IS_EQUAL(solver.info(), Success); | |
| const WideVector values = solver.eigenvalues().template cast<long double>() / static_cast<long double>(scale); | |
| VERIFY((values - reference.eigenvalues()).norm() <= tolerance); | |
| if (options == ComputeEigenvectors) { | |
| const WideMatrix vectors = solver.eigenvectors().template cast<long double>(); | |
| VERIFY((normalized * vectors - vectors * values.asDiagonal()).norm() <= tolerance); | |
| VERIFY((vectors.transpose() * vectors - WideMatrix::Identity()).norm() <= tolerance); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| template <typename Scalar, int Options> | |
| void direct_selfadjoint_clustered_boundary() { | |
| using MatrixType = Matrix<Scalar, 3, 3, Options>; | |
| using WideMatrix = Matrix<long double, 3, 3>; | |
| using WideVector = Matrix<long double, 3, 1>; | |
| const long double epsilon = static_cast<long double>(NumTraits<Scalar>::epsilon()); | |
| const Scalar scale = numext::ldexp(Scalar(1), std::numeric_limits<Scalar>::max_exponent - 1); | |
| const long double normalizedMaximum = | |
| static_cast<long double>(NumTraits<Scalar>::highest()) / static_cast<long double>(scale); | |
| const long double diagonal = normalizedMaximum - 0.25L - 4 * epsilon; | |
| // A/scale = (d+b)*I - b*ones(3,3), b=1/4: eigenvalues d-2*b, d+b, d+b. | |
| // Keep the repeated roots below the actual boundary: highest()/scale is not | |
| // 2-epsilon for IBM double-double long double. | |
| MatrixType input = MatrixType::Constant(Scalar(-0.25) * scale); | |
| input.diagonal().setConstant(Scalar(diagonal) * scale); | |
| const WideMatrix normalized = input.template cast<long double>() / static_cast<long double>(scale); | |
| WideVector expected; | |
| expected << diagonal - 0.5L, diagonal + 0.25L, diagonal + 0.25L; | |
| VERIFY(expected.maxCoeff() < normalizedMaximum); | |
| VERIFY(input.cwiseAbs().maxCoeff() > (NumTraits<Scalar>::highest() * Scalar(0.5)) / Scalar(3)); | |
| // Confirm that tridiagonalization leaves a coupling requiring QR iteration. | |
| Tridiagonalization<MatrixType> tridiagonal(normalized.template cast<Scalar>()); | |
| Matrix<Scalar, 3, 1> diag = tridiagonal.diagonal(); | |
| Matrix<Scalar, 2, 1> subdiag = tridiagonal.subDiagonal(); | |
| MatrixType unusedVectors; | |
| VERIFY_IS_EQUAL(internal::computeFromTridiagonal_impl<false>(diag, subdiag, 0, false, unusedVectors), NoConvergence); | |
| // A 3x3 QR solve is backward stable to O(n*epsilon); allow 16*n rounding errors. | |
| const long double tolerance = 48 * epsilon; | |
| for (int options : {EigenvaluesOnly, ComputeEigenvectors}) { | |
| SelfAdjointEigenSolver<MatrixType> solver; | |
| solver.computeDirect(input, options); | |
| VERIFY_IS_EQUAL(solver.info(), Success); | |
| VERIFY(solver.eigenvalues().allFinite()); | |
| const WideVector values = solver.eigenvalues().template cast<long double>() / static_cast<long double>(scale); | |
| VERIFY((values - expected).norm() <= tolerance * normalized.norm()); | |
| VERIFY(values[0] <= values[1] && values[1] <= values[2]); | |
| if (options == ComputeEigenvectors) { | |
| VERIFY(solver.eigenvectors().allFinite()); | |
| const WideMatrix vectors = solver.eigenvectors().template cast<long double>(); | |
| VERIFY((normalized * vectors - vectors * values.asDiagonal()).norm() <= tolerance * normalized.norm()); | |
| VERIFY((vectors.transpose() * vectors - WideMatrix::Identity()).norm() <= tolerance); | |
| } | |
| } | |
| } | |
| template <typename Scalar, int Size> | |
| void direct_selfadjoint_low_precision_boundary() { | |
| using MatrixType = Matrix<Scalar, Size, Size>; | |
| using WideMatrix = Matrix<double, Size, Size>; | |
| const Scalar maximum = NumTraits<Scalar>::highest(); | |
| MatrixType input = MatrixType::Identity() * maximum; | |
| for (int negativeCount = 0; negativeCount <= Size; ++negativeCount) { | |
| if (negativeCount > 0) input(negativeCount - 1, negativeCount - 1) = -maximum; | |
| SelfAdjointEigenSolver<MatrixType> solver; | |
| solver.computeDirect(input); | |
| VERIFY_IS_EQUAL(solver.info(), Success); | |
| for (Index i = 0; i < Size; ++i) VERIFY_IS_EQUAL(solver.eigenvalues()[i], i < negativeCount ? -maximum : maximum); | |
| const WideMatrix vectors = solver.eigenvectors().template cast<double>(); | |
| const WideMatrix normalized = input.template cast<double>() / double(maximum); | |
| const auto values = solver.eigenvalues().template cast<double>() / double(maximum); | |
| const double tolerance = 8 * Size * double(NumTraits<Scalar>::epsilon()); | |
| VERIFY((normalized * vectors - vectors * values.asDiagonal()).norm() <= tolerance); | |
| VERIFY((vectors.transpose() * vectors - WideMatrix::Identity()).norm() <= tolerance); | |
| } | |
| } | |
| #endif | |
| EIGEN_DECLARE_TEST(eigensolver_selfadjoint) { | |
| CALL_SUBTEST_20(selfadjoint_iterative_scaling_rounding<>()); | |
| CALL_SUBTEST_20((selfadjoint_iterative_scaling_blocks<float, ColMajor>())); | |
| CALL_SUBTEST_20((selfadjoint_iterative_scaling_blocks<double, RowMajor>())); | |
| CALL_SUBTEST_20((selfadjoint_iterative_scaling_blocks<std::complex<float>, RowMajor>())); | |
| CALL_SUBTEST_20((selfadjoint_iterative_scaling_blocks<std::complex<double>, ColMajor>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_clustered_boundary<float, ColMajor>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_rotated_boundary<float, ColMajor>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_rotated_boundary<double, RowMajor>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_scaling_window<float, 2>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_scaling_window<float, 3>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_scaling_window<double, 2>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_scaling_window<double, 3>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_clustered_boundary<double, RowMajor>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_clustered_boundary<long double, ColMajor>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_clustered_boundary<half, RowMajor>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_clustered_boundary<bfloat16, ColMajor>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_safe_scaling<float, 2, ColMajor>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_safe_scaling<float, 3, RowMajor>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_safe_scaling<double, 2, RowMajor>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_safe_scaling<double, 3, ColMajor>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_safe_scaling<long double, 3, ColMajor>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_low_precision_boundary<half, 2>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_low_precision_boundary<half, 3>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_low_precision_boundary<bfloat16, 2>())); | |
| CALL_SUBTEST_21((direct_selfadjoint_low_precision_boundary<bfloat16, 3>())); | |
| int s = 0; | |
| CALL_SUBTEST_4(generalizedselfadjointeigensolver_no_malloc<MatrixXd>()); | |
| // An empty matrix took the maximum of no coefficients. | |
| CALL_SUBTEST_4({ | |
| MatrixXd A(0, 0); | |
| SelfAdjointEigenSolver<MatrixXd> solver(A); | |
| VERIFY_IS_EQUAL(solver.info(), Success); | |
| VERIFY_IS_EQUAL(solver.eigenvectors().size(), 0); | |
| VERIFY_IS_EQUAL(A.selfadjointView<Lower>().eigenvalues().size(), 0); | |
| }); | |
| CALL_SUBTEST_5(generalizedselfadjointeigensolver_no_malloc<MatrixXcd>()); | |
| CALL_SUBTEST_5(selfadjointeigensolver_dense_deflation_scale_invariance<float>()); | |
| CALL_SUBTEST_5(selfadjointeigensolver_dense_deflation_scale_invariance<double>()); | |
| CALL_SUBTEST_13(selfadjointeigensolver_subnormal_coefficients<>()); | |
| for (int i = 0; i < g_repeat; i++) { | |
| // trivial test for 1x1 matrices: | |
| CALL_SUBTEST_1(selfadjointeigensolver(Matrix<float, 1, 1>())); | |
| CALL_SUBTEST_10(selfadjointeigensolver(Matrix<double, 1, 1>())); | |
| CALL_SUBTEST_11(selfadjointeigensolver(Matrix<std::complex<double>, 1, 1>())); | |
| // very important to test 3x3 and 2x2 matrices since we provide special paths for them | |
| CALL_SUBTEST_12(selfadjointeigensolver(Matrix2f())); | |
| CALL_SUBTEST_15(selfadjointeigensolver(Matrix2d())); | |
| CALL_SUBTEST_16(selfadjointeigensolver(Matrix2cd())); | |
| CALL_SUBTEST_13(selfadjointeigensolver(Matrix3f())); | |
| CALL_SUBTEST_17(selfadjointeigensolver(Matrix3d())); | |
| CALL_SUBTEST_18(selfadjointeigensolver(Matrix3cd())); | |
| CALL_SUBTEST_2(selfadjointeigensolver(Matrix4d())); | |
| CALL_SUBTEST_14(selfadjointeigensolver(Matrix4cd())); | |
| s = internal::random<int>(1, EIGEN_TEST_MAX_SIZE / 4); | |
| CALL_SUBTEST_3(selfadjointeigensolver(MatrixXf(s, s))); | |
| CALL_SUBTEST_4(selfadjointeigensolver(MatrixXd(s, s))); | |
| CALL_SUBTEST_5(selfadjointeigensolver(MatrixXcd(s, s))); | |
| CALL_SUBTEST_9(selfadjointeigensolver(Matrix<std::complex<double>, Dynamic, Dynamic, RowMajor>(s, s))); | |
| TEST_SET_BUT_UNUSED_VARIABLE(s); | |
| // some trivial but implementation-wise tricky cases | |
| CALL_SUBTEST_4(selfadjointeigensolver(MatrixXd(1, 1))); | |
| CALL_SUBTEST_4(selfadjointeigensolver(MatrixXd(2, 2))); | |
| CALL_SUBTEST_5(selfadjointeigensolver(MatrixXcd(1, 1))); | |
| CALL_SUBTEST_5(selfadjointeigensolver(MatrixXcd(2, 2))); | |
| // repeated eigenvalues | |
| CALL_SUBTEST_17(selfadjointeigensolver_repeated_eigenvalues(Matrix3d())); | |
| CALL_SUBTEST_15(selfadjointeigensolver_repeated_eigenvalues(Matrix2d())); | |
| CALL_SUBTEST_2(selfadjointeigensolver_repeated_eigenvalues(Matrix4d())); | |
| CALL_SUBTEST_4(selfadjointeigensolver_repeated_eigenvalues(MatrixXd(s, s))); | |
| CALL_SUBTEST_13(selfadjointeigensolver_repeated_eigenvalues(Matrix3f())); | |
| CALL_SUBTEST_12(selfadjointeigensolver_repeated_eigenvalues(Matrix2f())); | |
| CALL_SUBTEST_18(selfadjointeigensolver_repeated_eigenvalues(Matrix3cd())); | |
| // extreme eigenvalues (near overflow/underflow, high condition number) | |
| CALL_SUBTEST_17(selfadjointeigensolver_extreme_eigenvalues(Matrix3d())); | |
| CALL_SUBTEST_2(selfadjointeigensolver_extreme_eigenvalues(Matrix4d())); | |
| CALL_SUBTEST_4(selfadjointeigensolver_extreme_eigenvalues(MatrixXd(s, s))); | |
| CALL_SUBTEST_13(selfadjointeigensolver_extreme_eigenvalues(Matrix3f())); | |
| CALL_SUBTEST_3(selfadjointeigensolver_extreme_eigenvalues(MatrixXf(s, s))); | |
| // computeFromTridiagonal with scaled inputs | |
| CALL_SUBTEST_4(selfadjointeigensolver_tridiagonal_scaled(MatrixXd(s, s))); | |
| CALL_SUBTEST_3(selfadjointeigensolver_tridiagonal_scaled(MatrixXf(s, s))); | |
| // structured tridiagonal hard cases from the literature | |
| CALL_SUBTEST_4(selfadjointeigensolver_structured_tridiagonal<double>()); | |
| CALL_SUBTEST_3(selfadjointeigensolver_structured_tridiagonal<float>()); | |
| // wide dynamic range tridiagonal (per-block scaling regression) | |
| CALL_SUBTEST_4(selfadjointeigensolver_tridiagonal_wide_range<double>()); | |
| CALL_SUBTEST_3(selfadjointeigensolver_tridiagonal_wide_range<float>()); | |
| // diagonal matrices | |
| CALL_SUBTEST_17(selfadjointeigensolver_diagonal(Matrix3d())); | |
| CALL_SUBTEST_4(selfadjointeigensolver_diagonal(MatrixXd(s, s))); | |
| // operatorInverseSqrt | |
| CALL_SUBTEST_17(selfadjointeigensolver_inverse_sqrt(Matrix3d())); | |
| CALL_SUBTEST_2(selfadjointeigensolver_inverse_sqrt(Matrix4d())); | |
| CALL_SUBTEST_4(selfadjointeigensolver_inverse_sqrt(MatrixXd(s, s))); | |
| CALL_SUBTEST_13(selfadjointeigensolver_inverse_sqrt(Matrix3f())); | |
| // RowMajor | |
| CALL_SUBTEST_19(selfadjointeigensolver_rowmajor<0>()); | |
| // Larger matrices to exercise the blocked tridiagonalization path (n >= 96). | |
| CALL_SUBTEST_4(selfadjointeigensolver(MatrixXd(256, 256))); | |
| CALL_SUBTEST_5(selfadjointeigensolver(MatrixXcd(256, 256))); | |
| CALL_SUBTEST_3(selfadjointeigensolver(MatrixXf(256, 256))); | |
| CALL_SUBTEST_9(selfadjointeigensolver(Matrix<std::complex<double>, Dynamic, Dynamic, RowMajor>(256, 256))); | |
| // Deterministic blocked-path stress: clustered + extreme spectra at n=128 | |
| // (>= the n>=96 blocking threshold). The repeated/near-repeated and high- | |
| // dynamic-range cases are the ones most likely to expose a stability | |
| // regression in the rank-2k trailing update. | |
| CALL_SUBTEST_4(selfadjointeigensolver_repeated_eigenvalues(MatrixXd(128, 128))); | |
| CALL_SUBTEST_4(selfadjointeigensolver_extreme_eigenvalues(MatrixXd(128, 128))); | |
| CALL_SUBTEST_3(selfadjointeigensolver_repeated_eigenvalues(MatrixXf(128, 128))); | |
| CALL_SUBTEST_5(selfadjointeigensolver_repeated_eigenvalues(MatrixXcd(128, 128))); | |
| } | |
| CALL_SUBTEST_17(bug_854<0>()); | |
| CALL_SUBTEST_17(bug_1014<0>()); | |
| CALL_SUBTEST_17(bug_1204<0>()); | |
| CALL_SUBTEST_17(bug_1225<0>()); | |
| CALL_SUBTEST_17(selfadjointeigensolver_nonfinite<0>()); | |
| CALL_SUBTEST_8(selfadjointeigensolver_tridiagonal_zerosized<0>()); | |
| // Stress tests for direct 3x3 and 2x2 solvers. | |
| CALL_SUBTEST_17(direct_3x3_stress<0>()); | |
| CALL_SUBTEST_15(direct_2x2_stress<0>()); | |
| // Test problem size constructors | |
| s = internal::random<int>(1, EIGEN_TEST_MAX_SIZE / 4); | |
| CALL_SUBTEST_8(SelfAdjointEigenSolver<MatrixXf> tmp1(s)); | |
| CALL_SUBTEST_8(Tridiagonalization<MatrixXf> tmp2(s)); | |
| TEST_SET_BUT_UNUSED_VARIABLE(s); | |
| } |