blob: f22b822b35cbb6217e9c6ed226012a72e8d7ac70 [file]
// 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 = static_cast<long double>(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);
}