// The code was adapted from Eigen/src/Eigenvaleus/EigenSolver.h // // Copyright (C) 2008 Gael Guennebaud // Copyright (C) 2010,2012 Jitse Niesen // Copyright (C) 2016-2022 Yixuan Qiu // // 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 https://mozilla.org/MPL/2.0/. #ifndef SPECTRA_UPPER_HESSENBERG_EIGEN_H #define SPECTRA_UPPER_HESSENBERG_EIGEN_H #include #include #include "UpperHessenbergSchur.h" namespace Spectra { template class UpperHessenbergEigen { private: using Index = Eigen::Index; using Matrix = Eigen::Matrix; using Vector = Eigen::Matrix; using GenericMatrix = Eigen::Ref; using ConstGenericMatrix = const Eigen::Ref; using Complex = std::complex; using ComplexMatrix = Eigen::Matrix; using ComplexVector = Eigen::Matrix; Index m_n; // Size of the matrix UpperHessenbergSchur m_schur; // Schur decomposition solver Matrix m_matT; // Schur T matrix Matrix m_eivec; // Storing eigenvectors ComplexVector m_eivalues; // Eigenvalues bool m_computed; void doComputeEigenvectors() { using std::abs; const Index size = m_eivec.cols(); const Scalar eps = Eigen::NumTraits::epsilon(); // inefficient! this is already computed in RealSchur Scalar norm(0); for (Index j = 0; j < size; ++j) { norm += m_matT.row(j).segment((std::max)(j - 1, Index(0)), size - (std::max)(j - 1, Index(0))).cwiseAbs().sum(); } // Backsubstitute to find vectors of upper triangular form if (norm == Scalar(0)) return; for (Index n = size - 1; n >= 0; n--) { Scalar p = m_eivalues.coeff(n).real(); Scalar q = m_eivalues.coeff(n).imag(); // Scalar vector if (q == Scalar(0)) { Scalar lastr(0), lastw(0); Index l = n; m_matT.coeffRef(n, n) = Scalar(1); for (Index i = n - 1; i >= 0; i--) { Scalar w = m_matT.coeff(i, i) - p; Scalar r = m_matT.row(i).segment(l, n - l + 1).dot(m_matT.col(n).segment(l, n - l + 1)); if (m_eivalues.coeff(i).imag() < Scalar(0)) { lastw = w; lastr = r; } else { l = i; if (m_eivalues.coeff(i).imag() == Scalar(0)) { if (w != Scalar(0)) m_matT.coeffRef(i, n) = -r / w; else m_matT.coeffRef(i, n) = -r / (eps * norm); } else // Solve real equations { Scalar x = m_matT.coeff(i, i + 1); Scalar y = m_matT.coeff(i + 1, i); Scalar denom = (m_eivalues.coeff(i).real() - p) * (m_eivalues.coeff(i).real() - p) + m_eivalues.coeff(i).imag() * m_eivalues.coeff(i).imag(); Scalar t = (x * lastr - lastw * r) / denom; m_matT.coeffRef(i, n) = t; if (abs(x) > abs(lastw)) m_matT.coeffRef(i + 1, n) = (-r - w * t) / x; else m_matT.coeffRef(i + 1, n) = (-lastr - y * t) / lastw; } // Overflow control Scalar t = abs(m_matT.coeff(i, n)); if ((eps * t) * t > Scalar(1)) m_matT.col(n).tail(size - i) /= t; } } } else if (q < Scalar(0) && n > 0) { // Complex vector Scalar lastra(0), lastsa(0), lastw(0); Index l = n - 1; // Last vector component imaginary so matrix is triangular if (abs(m_matT.coeff(n, n - 1)) > abs(m_matT.coeff(n - 1, n))) { m_matT.coeffRef(n - 1, n - 1) = q / m_matT.coeff(n, n - 1); m_matT.coeffRef(n - 1, n) = -(m_matT.coeff(n, n) - p) / m_matT.coeff(n, n - 1); } else { Complex cc = Complex(Scalar(0), -m_matT.coeff(n - 1, n)) / Complex(m_matT.coeff(n - 1, n - 1) - p, q); m_matT.coeffRef(n - 1, n - 1) = Eigen::numext::real(cc); m_matT.coeffRef(n - 1, n) = Eigen::numext::imag(cc); } m_matT.coeffRef(n, n - 1) = Scalar(0); m_matT.coeffRef(n, n) = Scalar(1); for (Index i = n - 2; i >= 0; i--) { Scalar ra = m_matT.row(i).segment(l, n - l + 1).dot(m_matT.col(n - 1).segment(l, n - l + 1)); Scalar sa = m_matT.row(i).segment(l, n - l + 1).dot(m_matT.col(n).segment(l, n - l + 1)); Scalar w = m_matT.coeff(i, i) - p; if (m_eivalues.coeff(i).imag() < Scalar(0)) { lastw = w; lastra = ra; lastsa = sa; } else { l = i; if (m_eivalues.coeff(i).imag() == Scalar(0)) { Complex cc = Complex(-ra, -sa) / Complex(w, q); m_matT.coeffRef(i, n - 1) = Eigen::numext::real(cc); m_matT.coeffRef(i, n) = Eigen::numext::imag(cc); } else { // Solve complex equations Scalar x = m_matT.coeff(i, i + 1); Scalar y = m_matT.coeff(i + 1, i); Scalar vr = (m_eivalues.coeff(i).real() - p) * (m_eivalues.coeff(i).real() - p) + m_eivalues.coeff(i).imag() * m_eivalues.coeff(i).imag() - q * q; Scalar vi = (m_eivalues.coeff(i).real() - p) * Scalar(2) * q; if ((vr == Scalar(0)) && (vi == Scalar(0))) vr = eps * norm * (abs(w) + abs(q) + abs(x) + abs(y) + abs(lastw)); Complex cc = Complex(x * lastra - lastw * ra + q * sa, x * lastsa - lastw * sa - q * ra) / Complex(vr, vi); m_matT.coeffRef(i, n - 1) = Eigen::numext::real(cc); m_matT.coeffRef(i, n) = Eigen::numext::imag(cc); if (abs(x) > (abs(lastw) + abs(q))) { m_matT.coeffRef(i + 1, n - 1) = (-ra - w * m_matT.coeff(i, n - 1) + q * m_matT.coeff(i, n)) / x; m_matT.coeffRef(i + 1, n) = (-sa - w * m_matT.coeff(i, n) - q * m_matT.coeff(i, n - 1)) / x; } else { cc = Complex(-lastra - y * m_matT.coeff(i, n - 1), -lastsa - y * m_matT.coeff(i, n)) / Complex(lastw, q); m_matT.coeffRef(i + 1, n - 1) = Eigen::numext::real(cc); m_matT.coeffRef(i + 1, n) = Eigen::numext::imag(cc); } } // Overflow control Scalar t = (std::max)(abs(m_matT.coeff(i, n - 1)), abs(m_matT.coeff(i, n))); if ((eps * t) * t > Scalar(1)) m_matT.block(i, n - 1, size - i, 2) /= t; } } // We handled a pair of complex conjugate eigenvalues, so need to skip them both n--; } } // Back transformation to get eigenvectors of original matrix Vector m_tmp(size); for (Index j = size - 1; j >= 0; j--) { m_tmp.noalias() = m_eivec.leftCols(j + 1) * m_matT.col(j).segment(0, j + 1); m_eivec.col(j) = m_tmp; } } public: UpperHessenbergEigen() : m_n(0), m_computed(false) {} UpperHessenbergEigen(ConstGenericMatrix& mat) : m_n(mat.rows()), m_computed(false) { compute(mat); } void compute(ConstGenericMatrix& mat) { using std::abs; using std::sqrt; if (mat.rows() != mat.cols()) throw std::invalid_argument("UpperHessenbergEigen: matrix must be square"); m_n = mat.rows(); // Scale matrix prior to the Schur decomposition const Scalar scale = mat.cwiseAbs().maxCoeff(); // Reduce to real Schur form m_schur.compute(mat / scale); m_schur.swap_T(m_matT); m_schur.swap_U(m_eivec); // Compute eigenvalues from matT m_eivalues.resize(m_n); Index i = 0; while (i < m_n) { // Real eigenvalue if (i == m_n - 1 || m_matT.coeff(i + 1, i) == Scalar(0)) { m_eivalues.coeffRef(i) = m_matT.coeff(i, i); ++i; } else // Complex eigenvalues { Scalar p = Scalar(0.5) * (m_matT.coeff(i, i) - m_matT.coeff(i + 1, i + 1)); Scalar z; // Compute z = sqrt(abs(p * p + m_matT.coeff(i+1, i) * m_matT.coeff(i, i+1))); // without overflow { Scalar t0 = m_matT.coeff(i + 1, i); Scalar t1 = m_matT.coeff(i, i + 1); Scalar maxval = (std::max)(abs(p), (std::max)(abs(t0), abs(t1))); t0 /= maxval; t1 /= maxval; Scalar p0 = p / maxval; z = maxval * sqrt(abs(p0 * p0 + t0 * t1)); } m_eivalues.coeffRef(i) = Complex(m_matT.coeff(i + 1, i + 1) + p, z); m_eivalues.coeffRef(i + 1) = Complex(m_matT.coeff(i + 1, i + 1) + p, -z); i += 2; } } // Compute eigenvectors doComputeEigenvectors(); // Scale eigenvalues back m_eivalues *= scale; m_computed = true; } const ComplexVector& eigenvalues() const { if (!m_computed) throw std::logic_error("UpperHessenbergEigen: need to call compute() first"); return m_eivalues; } ComplexMatrix eigenvectors() { using std::abs; if (!m_computed) throw std::logic_error("UpperHessenbergEigen: need to call compute() first"); Index n = m_eivec.cols(); ComplexMatrix matV(n, n); for (Index j = 0; j < n; ++j) { // imaginary part of real eigenvalue is already set to exact zero if (Eigen::numext::imag(m_eivalues.coeff(j)) == Scalar(0) || j + 1 == n) { // we have a real eigen value matV.col(j) = m_eivec.col(j).template cast(); matV.col(j).normalize(); } else { // we have a pair of complex eigen values for (Index i = 0; i < n; ++i) { matV.coeffRef(i, j) = Complex(m_eivec.coeff(i, j), m_eivec.coeff(i, j + 1)); matV.coeffRef(i, j + 1) = Complex(m_eivec.coeff(i, j), -m_eivec.coeff(i, j + 1)); } matV.col(j).normalize(); matV.col(j + 1).normalize(); ++j; } } return matV; } }; } // namespace Spectra #endif // SPECTRA_UPPER_HESSENBERG_EIGEN_H