// This file is part of Eigen, a lightweight C++ template library // for linear algebra. // // Copyright (C) 2025 Rasmus Munk Larsen // // 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/. #ifndef EIGEN_TEST_TRIDIAG_TEST_MATRICES_H #define EIGEN_TEST_TRIDIAG_TEST_MATRICES_H // Structured tridiagonal test matrices from the numerical linear algebra // literature. Used by both the bidiagonal SVD and symmetric eigenvalue tests. // // Each generator writes into pre-allocated (diag, offdiag) vectors. // For SVD, offdiag is the superdiagonal of a bidiagonal matrix. // For eigenvalues, offdiag is the subdiagonal of a symmetric tridiagonal matrix. // // Usage: // Matrix diag(n), offdiag(n-1); // tridiag_identity(diag, offdiag); // fills diag and offdiag // my_verify(diag, offdiag); // solver-specific verification #include namespace Eigen { namespace test { // 1. Identity: d=[1,...,1], e=[0,...,0] template void tridiag_identity(VectorType& diag, VectorType& offdiag) { diag.setOnes(); offdiag.setZero(); } // 2. Zero: d=[0,...,0], e=[0,...,0] template void tridiag_zero(VectorType& diag, VectorType& offdiag) { diag.setZero(); offdiag.setZero(); } // 3. Constant: d=[c,...,c], e=[c,...,c] template void tridiag_constant(VectorType& diag, VectorType& offdiag, typename VectorType::Scalar c = typename VectorType::Scalar(2.5)) { diag.setConstant(c); offdiag.setConstant(c); } // 4. 1-2-1 Toeplitz: d=[2,...,2], e=[1,...,1] // Eigenvalues: 2 - 2*cos(k*pi/(n+1)) for k=1,...,n template void tridiag_1_2_1(VectorType& diag, VectorType& offdiag) { typedef typename VectorType::Scalar Scalar; diag.setConstant(Scalar(2)); offdiag.setOnes(); } // 5. Wilkinson W_{2m+1}: d_i = |m - i|, e=[1,...,1] // Has pairs of eigenvalues agreeing to many digits; stresses deflation. template void tridiag_wilkinson(VectorType& diag, VectorType& offdiag) { typedef typename VectorType::Scalar Scalar; Index n = diag.size(); for (Index i = 0; i < n; ++i) diag(i) = numext::abs(Scalar(n / 2) - Scalar(i)); offdiag.setOnes(); } // 6. Clement matrix: d=[0,...,0], e_i = sqrt(i*(n-1-i)) // Known eigenvalues: -(n-1), -(n-3), ..., (n-3), (n-1) template void tridiag_clement(VectorType& diag, VectorType& offdiag) { EIGEN_USING_STD(sqrt); typedef typename VectorType::Scalar Scalar; Index n = diag.size(); diag.setZero(); for (Index i = 0; i < n - 1; ++i) offdiag(i) = sqrt(Scalar(i + 1) * Scalar(n - 1 - i)); } // 7. Kahan-style: d_i = s^i, e_i = -c*s^i with s=sin(theta), c=cos(theta). // Geometric decay with controlled condition number. template void tridiag_kahan(VectorType& diag, VectorType& offdiag, typename VectorType::Scalar theta = typename VectorType::Scalar(0.3)) { EIGEN_USING_STD(sin); EIGEN_USING_STD(cos); EIGEN_USING_STD(pow); EIGEN_USING_STD(log); typedef typename VectorType::Scalar Scalar; Index n = diag.size(); const Scalar eps = NumTraits::epsilon(); const Scalar s = sin(theta); const Scalar c = cos(theta); const Scalar maxPower = -log(eps) / (-log(s)); for (Index i = 0; i < n; ++i) diag(i) = pow(s, numext::mini(Scalar(i), maxPower)); for (Index i = 0; i < n - 1; ++i) offdiag(i) = -c * pow(s, numext::mini(Scalar(i), maxPower)); } // 8. Graded: d_i = base^(-i), e_i = base^(-i) template void tridiag_graded(VectorType& diag, VectorType& offdiag, typename VectorType::Scalar base = typename VectorType::Scalar(10)) { EIGEN_USING_STD(pow); typedef typename VectorType::Scalar Scalar; Index n = diag.size(); for (Index i = 0; i < n; ++i) diag(i) = pow(base, -Scalar(i)); for (Index i = 0; i < n - 1; ++i) offdiag(i) = pow(base, -Scalar(i)); } // 9. Geometric decay diagonal: d_i = base^i, e=[0,...,0] template void tridiag_geometric_diagonal(VectorType& diag, VectorType& offdiag, typename VectorType::Scalar base = typename VectorType::Scalar(0.5)) { EIGEN_USING_STD(pow); EIGEN_USING_STD(log); typedef typename VectorType::Scalar Scalar; Index n = diag.size(); const Scalar eps = NumTraits::epsilon(); const Scalar maxPower = -log(eps) / (-log(base)); for (Index i = 0; i < n; ++i) diag(i) = pow(base, numext::mini(Scalar(i), maxPower)); offdiag.setZero(); } // 10. Geometric decay offdiagonal: d=[1,...,1], e_i = base^i template void tridiag_geometric_offdiag(VectorType& diag, VectorType& offdiag, typename VectorType::Scalar base = typename VectorType::Scalar(0.5)) { EIGEN_USING_STD(pow); typedef typename VectorType::Scalar Scalar; Index n = diag.size(); diag.setOnes(); for (Index i = 0; i < n - 1; ++i) offdiag(i) = pow(base, Scalar(i)); } // 11. Clustered eigenvalues: d_i = 1 + i*eps, e=[0,...,0] template void tridiag_clustered(VectorType& diag, VectorType& offdiag) { typedef typename VectorType::Scalar Scalar; Index n = diag.size(); const Scalar eps = NumTraits::epsilon(); for (Index i = 0; i < n; ++i) diag(i) = Scalar(1) + Scalar(i) * eps; offdiag.setZero(); } // 12. Two clusters: half at 1, half at eps. template void tridiag_two_clusters(VectorType& diag, VectorType& offdiag) { typedef typename VectorType::Scalar Scalar; Index n = diag.size(); const Scalar eps = NumTraits::epsilon(); for (Index i = 0; i < n; ++i) diag(i) = (i < n / 2) ? Scalar(1) : eps; offdiag.setZero(); } // 13. Single tiny value: d=[1,...,1,eps], e=[eps^2,...,eps^2] template void tridiag_single_tiny(VectorType& diag, VectorType& offdiag) { typedef typename VectorType::Scalar Scalar; Index n = diag.size(); const Scalar eps = NumTraits::epsilon(); diag.setOnes(); diag(n - 1) = eps; offdiag.setConstant(eps * eps); } // 14. Overflow/underflow: alternating big/tiny diagonal and offdiagonal. template void tridiag_overflow_underflow(VectorType& diag, VectorType& offdiag) { typedef typename VectorType::Scalar Scalar; Index n = diag.size(); const Scalar big = (std::numeric_limits::max)() / Scalar(1000); const Scalar tiny = (std::numeric_limits::min)() * Scalar(1000); for (Index i = 0; i < n; ++i) diag(i) = (i % 2 == 0) ? big : tiny; for (Index i = 0; i < n - 1; ++i) offdiag(i) = (i % 2 == 0) ? tiny : big; } // 15. Prescribed condition number: d_i = kappa^(-i/(n-1)), e_i = eps * random. template void tridiag_prescribed_cond(VectorType& diag, VectorType& offdiag) { EIGEN_USING_STD(pow); EIGEN_USING_STD(abs); typedef typename VectorType::Scalar Scalar; Index n = diag.size(); const Scalar eps = NumTraits::epsilon(); const Scalar kappa = Scalar(1) / eps; for (Index i = 0; i < n; ++i) diag(i) = pow(kappa, -Scalar(i) / Scalar(n - 1)); for (Index i = 0; i < n - 1; ++i) offdiag(i) = eps * abs(internal::random()); } // 16. Rank-deficient: d=[1,..,0,..,0,..,1], e=[0,...,0] template void tridiag_rank_deficient(VectorType& diag, VectorType& offdiag) { typedef typename VectorType::Scalar Scalar; Index n = diag.size(); for (Index i = 0; i < n; ++i) diag(i) = (i < n / 3 || i >= 2 * n / 3) ? Scalar(1) : Scalar(0); offdiag.setZero(); } // 17. Arrowhead-like: d_i = linspace(1,n), e_i = 1/(i+1) template void tridiag_arrowhead(VectorType& diag, VectorType& offdiag) { typedef typename VectorType::Scalar Scalar; Index n = diag.size(); for (Index i = 0; i < n; ++i) diag(i) = Scalar(1) + Scalar(i); for (Index i = 0; i < n - 1; ++i) offdiag(i) = Scalar(1) / Scalar(i + 1); } // 18. Repeated values: d=[1,2,3,1,2,3,...], e=[0,...,0] template void tridiag_repeated(VectorType& diag, VectorType& offdiag) { typedef typename VectorType::Scalar Scalar; Index n = diag.size(); for (Index i = 0; i < n; ++i) diag(i) = Scalar((i % 3) + 1); offdiag.setZero(); } // 19. Glued blocks: d=[1,...,1], e=0 except e[n/2-1]=eps. // Two identity blocks coupled by a tiny off-diagonal entry. template void tridiag_glued(VectorType& diag, VectorType& offdiag) { typedef typename VectorType::Scalar Scalar; Index n = diag.size(); diag.setOnes(); offdiag.setZero(); if (n > 2) offdiag(n / 2 - 1) = NumTraits::epsilon(); } // 20. Nearly diagonal: random diag, eps * random offdiag. template void tridiag_nearly_diagonal(VectorType& diag, VectorType& offdiag) { EIGEN_USING_STD(abs); typedef typename VectorType::Scalar Scalar; Index n = diag.size(); const Scalar eps = NumTraits::epsilon(); diag = VectorType::Random(n).cwiseAbs() + VectorType::Constant(n, Scalar(0.1)); for (Index i = 0; i < n - 1; ++i) offdiag(i) = eps * (Scalar(0.5) + abs(internal::random())); } // 21. Negative eigenvalues: d_i = -i, e=[1,...,1] // (Only meaningful for symmetric eigenvalue problems, not SVD.) template void tridiag_negative(VectorType& diag, VectorType& offdiag) { typedef typename VectorType::Scalar Scalar; Index n = diag.size(); for (Index i = 0; i < n; ++i) diag(i) = -Scalar(i + 1); offdiag.setOnes(); } // 22. Mixed sign diagonal: d_i = (-1)^i * (i+1), e=[1,...,1] // (Only meaningful for symmetric eigenvalue problems, not SVD.) template void tridiag_mixed_sign(VectorType& diag, VectorType& offdiag) { typedef typename VectorType::Scalar Scalar; Index n = diag.size(); for (Index i = 0; i < n; ++i) diag(i) = ((i % 2 == 0) ? Scalar(1) : Scalar(-1)) * Scalar(i + 1); offdiag.setOnes(); } // Helper: iterate over a set of sizes and call a functor with each (diag, offdiag) pair // generated by a generator function. // // Usage: // for_tridiag_sizes([](auto& diag, auto& offdiag) { // tridiag_wilkinson(diag, offdiag); // my_verify(diag, offdiag); // }); template void for_tridiag_sizes(Func&& func) { const int sizes[] = {1, 2, 3, 5, 10, 16, 20, 50, 100}; typedef Matrix VectorType; for (int si = 0; si < int(sizeof(sizes) / sizeof(sizes[0])); ++si) { const Index n = sizes[si]; VectorType diag(n), offdiag(n > 1 ? n - 1 : 0); func(diag, offdiag); } } // Helper: run all generators (suitable for both SVD and eigenvalue problems). // The callback receives (diag, offdiag) after each generator fills them. template void for_all_tridiag_test_matrices(Func&& verify) { const int sizes[] = {1, 2, 3, 5, 10, 16, 20, 50, 100}; typedef Matrix VectorType; for (int si = 0; si < int(sizeof(sizes) / sizeof(sizes[0])); ++si) { const Index n = sizes[si]; VectorType diag(n), offdiag(n > 1 ? n - 1 : 0); tridiag_identity(diag, offdiag); verify(diag, offdiag); tridiag_zero(diag, offdiag); verify(diag, offdiag); tridiag_constant(diag, offdiag); verify(diag, offdiag); tridiag_1_2_1(diag, offdiag); verify(diag, offdiag); tridiag_wilkinson(diag, offdiag); verify(diag, offdiag); if (n > 1) { tridiag_clement(diag, offdiag); verify(diag, offdiag); } tridiag_kahan(diag, offdiag); verify(diag, offdiag); tridiag_graded(diag, offdiag); verify(diag, offdiag); tridiag_geometric_diagonal(diag, offdiag); verify(diag, offdiag); if (n > 1) { tridiag_geometric_offdiag(diag, offdiag); verify(diag, offdiag); } tridiag_clustered(diag, offdiag); verify(diag, offdiag); tridiag_two_clusters(diag, offdiag); verify(diag, offdiag); tridiag_single_tiny(diag, offdiag); verify(diag, offdiag); tridiag_overflow_underflow(diag, offdiag); verify(diag, offdiag); if (n > 1) { tridiag_prescribed_cond(diag, offdiag); verify(diag, offdiag); } tridiag_rank_deficient(diag, offdiag); verify(diag, offdiag); tridiag_arrowhead(diag, offdiag); verify(diag, offdiag); tridiag_repeated(diag, offdiag); verify(diag, offdiag); tridiag_glued(diag, offdiag); verify(diag, offdiag); tridiag_nearly_diagonal(diag, offdiag); verify(diag, offdiag); } } // Helper: run all generators, including those with negative values // (suitable only for symmetric eigenvalue problems, not SVD). template void for_all_symmetric_tridiag_test_matrices(Func&& verify) { for_all_tridiag_test_matrices(verify); const int sizes[] = {1, 2, 3, 5, 10, 16, 20, 50, 100}; typedef Matrix VectorType; for (int si = 0; si < int(sizeof(sizes) / sizeof(sizes[0])); ++si) { const Index n = sizes[si]; VectorType diag(n), offdiag(n > 1 ? n - 1 : 0); tridiag_negative(diag, offdiag); verify(diag, offdiag); tridiag_mixed_sign(diag, offdiag); verify(diag, offdiag); } } } // namespace test } // namespace Eigen #endif // EIGEN_TEST_TRIDIAG_TEST_MATRICES_H