// 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_SELECTION_RULE_H #define SPECTRA_SELECTION_RULE_H #include // std::vector #include // std::abs #include // std::sort #include // std::complex #include // std::pair #include // std::invalid_argument #include #include "TypeTraits.h" namespace Spectra { /// /// \defgroup Enumerations Enumerations /// /// Enumeration types for the selection rule of eigenvalues. /// /// /// \ingroup Enumerations /// /// The enumeration of selection rules of desired eigenvalues. /// enum class SortRule { LargestMagn, ///< Select eigenvalues with largest magnitude. Magnitude ///< means the absolute value for real numbers and norm for ///< complex numbers. Applies to both symmetric and general ///< eigen solvers. LargestReal, ///< Select eigenvalues with largest real part. Only for general eigen solvers. LargestImag, ///< Select eigenvalues with largest imaginary part (in magnitude). Only for general eigen solvers. LargestAlge, ///< Select eigenvalues with largest algebraic value, considering ///< any negative sign. Only for symmetric eigen solvers. SmallestMagn, ///< Select eigenvalues with smallest magnitude. Applies to both symmetric and general ///< eigen solvers. SmallestReal, ///< Select eigenvalues with smallest real part. Only for general eigen solvers. SmallestImag, ///< Select eigenvalues with smallest imaginary part (in magnitude). Only for general eigen solvers. SmallestAlge, ///< Select eigenvalues with smallest algebraic value. Only for symmetric eigen solvers. BothEnds ///< Select eigenvalues half from each end of the spectrum. When ///< `nev` is odd, compute more from the high end. Only for symmetric eigen solvers. }; /// \cond // When comparing eigenvalues, we first calculate the "target" to sort. // For example, if we want to choose the eigenvalues with // largest magnitude, the target will be -abs(x). // The minus sign is due to the fact that std::sort() sorts in ascending order. // Default target: throw an exception template class SortingTarget { public: static ElemType get(const Scalar& val) { using std::abs; throw std::invalid_argument("incompatible selection rule"); return -abs(val); } }; // Specialization for SortRule::LargestMagn // This covers [float, double, complex] x [SortRule::LargestMagn] template class SortingTarget { public: static ElemType get(const Scalar& val) { using std::abs; return -abs(val); } }; // Specialization for SortRule::LargestReal // This covers [complex] x [SortRule::LargestReal] template class SortingTarget, SortRule::LargestReal> { public: static RealType get(const std::complex& val) { return -val.real(); } }; // Specialization for SortRule::LargestImag // This covers [complex] x [SortRule::LargestImag] template class SortingTarget, SortRule::LargestImag> { public: static RealType get(const std::complex& val) { using std::abs; return -abs(val.imag()); } }; // Specialization for SortRule::LargestAlge // This covers [float, double] x [SortRule::LargestAlge] template class SortingTarget { public: static Scalar get(const Scalar& val) { return -val; } }; // Here SortRule::BothEnds is the same as SortRule::LargestAlge, but // we need some additional steps, which are done in // SymEigsSolver.h => retrieve_ritzpair(). // There we move the smallest values to the proper locations. template class SortingTarget { public: static Scalar get(const Scalar& val) { return -val; } }; // Specialization for SortRule::SmallestMagn // This covers [float, double, complex] x [SortRule::SmallestMagn] template class SortingTarget { public: static ElemType get(const Scalar& val) { using std::abs; return abs(val); } }; // Specialization for SortRule::SmallestReal // This covers [complex] x [SortRule::SmallestReal] template class SortingTarget, SortRule::SmallestReal> { public: static RealType get(const std::complex& val) { return val.real(); } }; // Specialization for SortRule::SmallestImag // This covers [complex] x [SortRule::SmallestImag] template class SortingTarget, SortRule::SmallestImag> { public: static RealType get(const std::complex& val) { using std::abs; return abs(val.imag()); } }; // Specialization for SortRule::SmallestAlge // This covers [float, double] x [SortRule::SmallestAlge] template class SortingTarget { public: static Scalar get(const Scalar& val) { return val; } }; // Sort eigenvalues template class SortEigenvalue { private: using Index = Eigen::Index; using IndexArray = std::vector; const T* m_evals; IndexArray m_index; public: // Sort indices according to the eigenvalues they point to inline bool operator()(Index i, Index j) { return SortingTarget::get(m_evals[i]) < SortingTarget::get(m_evals[j]); } SortEigenvalue(const T* start, Index size) : m_evals(start), m_index(size) { for (Index i = 0; i < size; i++) { m_index[i] = i; } std::sort(m_index.begin(), m_index.end(), *this); } inline IndexArray index() const { return m_index; } inline void swap(IndexArray& other) { m_index.swap(other); } }; // Sort values[:len] according to the selection rule, and return the indices template std::vector argsort(SortRule selection, const Eigen::Matrix& values, Eigen::Index len) { using Index = Eigen::Index; // Sort Ritz values and put the wanted ones at the beginning std::vector ind; switch (selection) { case SortRule::LargestMagn: { SortEigenvalue sorting(values.data(), len); sorting.swap(ind); break; } case SortRule::BothEnds: case SortRule::LargestAlge: { SortEigenvalue sorting(values.data(), len); sorting.swap(ind); break; } case SortRule::SmallestMagn: { SortEigenvalue sorting(values.data(), len); sorting.swap(ind); break; } case SortRule::SmallestAlge: { SortEigenvalue sorting(values.data(), len); sorting.swap(ind); break; } default: throw std::invalid_argument("unsupported selection rule"); } // For SortRule::BothEnds, the eigenvalues are sorted according to the // SortRule::LargestAlge rule, so we need to move those smallest values to the left // The order would be // Largest => Smallest => 2nd largest => 2nd smallest => ... // We keep this order since the first k values will always be // the wanted collection, no matter k is nev_updated (used in SymEigsBase::restart()) // or is nev (used in SymEigsBase::sort_ritzpair()) if (selection == SortRule::BothEnds) { std::vector ind_copy(ind); for (Index i = 0; i < len; i++) { // If i is even, pick values from the left (large values) // If i is odd, pick values from the right (small values) if (i % 2 == 0) ind[i] = ind_copy[i / 2]; else ind[i] = ind_copy[len - 1 - i / 2]; } } return ind; } // Default vector length template std::vector argsort(SortRule selection, const Eigen::Matrix& values) { return argsort(selection, values, values.size()); } /// \endcond } // namespace Spectra #endif // SPECTRA_SELECTION_RULE_H