1%%%%%%%%%%%%%%%%%%% 2% XLiFE++ is an extended library of finite elements written in C++ 3% Copyright (C) 2014 Lunéville, Eric; Kielbasiewicz, Nicolas; Lafranche, Yvon; Nguyen, Manh-Ha; Chambeyron, Colin 4% 5% This program is free software: you can redistribute it and/or modify 6% it under the terms of the GNU General Public License as published by 7% the Free Software Foundation, either version 3 of the License, or 8% (at your option) any later version. 9% This program is distributed in the hope that it will be useful, 10% but WITHOUT ANY WARRANTY; without even the implied warranty of 11% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12% GNU General Public License for more details. 13% You should have received a copy of the GNU General Public License 14% along with this program. If not, see <http://www.gnu.org/licenses/>. 15%%%%%%%%%%%%%%%%%%% 16 17\section {{\libtitle eigenSparse} sub-library} 18 19The {\lib eigenSparse} provides tools that are useful for solving a wide variety 20of eigenvalue problems. The solvers currently implemented within {\lib eigenSparse} compute a 21partial eigen-decomposition for the generalized eigenvalue problem \\ 22\[ Ax = Bx \lambda \] 23We assume that the matrices A and B are large, possibly sparse, and that only their application to a block of vectors is required. \\ 24This section outlines the eigenSparse. Three subsections describe the eigenSparse operator/multivector interface, the eigensolver, and the various implementations provided by the eigenSparse. \\ 25The lib {\lib eigenSparse} is highly independent of other packages of \xlifepp except for {\lib eigenCore} which provides essential functions in performing the dense arithmetic for Rayleigh-Ritz methods. 26 27\subsection {The Operator/Multivector Interface} 28 29{\lib eigenSparse} utilizes traits classes to define interfaces for 30the scalar field, multivectors, and matrix operators. This allows generic programming techniques to be used when developing numerical algorithms in the {\lib eigenSparse}. {\lib eigenSparse}’s eigensolver is comprised of abstract 31numerical interfaces that are all implemented using templates and the functionality 32of the template arguments is provided through their corresponding trait classes. 33Most classes in {\lib eigenSparse} accept three template parameters: 34\begin{itemize} 35\item a scalar type, describing the field over which the vectors and operators are defined; 36\item a multivector type over the given scalar field, providing a data structure that 37denotes a collection of vectors; and 38\item an operator type over the given scalar field, providing linear operators used to 39define eigenproblems and preconditioners. 40\end{itemize} 41We note that the {\lib eigenSparse} was designed to support block methods, 42defined as those that apply A or B 43to a collection of vectors (a multivector). 44Algorithms in {\lib eigenSparse} are developed at a high-level, where the underlying 45linear algebra objects are opaque. The choice in linear algebra is made through templating, 46and access to the functionality of the underlying objects is provided via the traits classes 47{\class MultiVecTraits} and {\class OperatorTraits}. \\ 48These classes define opaque interfaces, specifying the operations that multivector and 49operator classes must support in order to be used in {\lib eigenSparse} without exposing low-level 50details of the underlying data structures.\\ 51The {\class MultiVecTraits} and {\class OperatorTraits} classes specify the operations that 52the multivector and operator type must support in order for them to be used 53by {\lib eigenSparse}. \\ 54The {\class OperatorTraits} class only needs to provide one method, described in the Table, that 55applies an operator to a multivector. This interface defines the only interaction 56required from an operator, even though the underlying operator may be a matrix, 57spectral transformation, or preconditioner. \\ 58 59\begin{center} 60\begin{tabular}{|l|p{11.5cm}|} 61\hline 62\multicolumn{2}{|c|} {\textbf{OperatorTraits<ST,MV,OP>}} \\ 63\hline 64\textit{Method name} & \textit{Description}\\ 65\hline 66{\cmd apply}(A,X,Y) & Applies the operator A to the multivector X, placing the result in the multivector Y \\ 67\hline 68\end{tabular} 69\end{center} 70 71The methods defined by the {\class MultiVecTraits} class, listed in following table, are the nesseary 72creational and arithmetic ones. 73The creational methods generate empty or populated multivectors from a previously 74created multivector. The populated multivectors can be a deep copy, where the 75object contains the storage for the multivector entries, or a shallow copy, where the 76object has a view of another multivector’s storage. A shallow copy is useful when 77only a subset of the columns of a multivector is required for computation, which 78is a situation that commonly occurs during the generation of a subspace. 79\begin{center} 80\begin{tabular}{|l|p{11.5cm}|} 81\hline 82\multicolumn{2}{|c|} {\textbf{MultiVecTraits<ST,MV>}} \\ 83\hline 84\textit{Method name} & \textit{Description}\\ 85\hline 86{\cmd clone(X,numvecs)} & Creates a new multivector from X with {\itshape numvecs} vectors \\ 87{\cmd cloneCopy(X,index)} & Creates a new multivector with a copy of the contents of a subset of the multivector X (deep copy) \\ 88{\cmd cloneView(X,index)} & Creates a new multivector that shares the selected contents of a subset of the multivector X (shallow copy) \\ 89\hline 90{\cmd getVecLength(X)} & Returns the vector length of the multivector X \\ 91{\cmd getNumberVecs(X)} & Returns the number of vectors in the multivector X \\ 92\hline 93{\cmd mvTimesMatAddMv}(alpha,X, & Applies a dense matrix D to multivector X and \\ 94\noindent D,beta,Y) & accumulates the result into multivector Y: \\ 95 & $Y \leftarrow \alpha XD+\beta Y$ \\ 96{\cmd mvAddMv}(alpha,X,beta,Y) & Performs multivector: $Y \leftarrow \alpha X+ \beta Y$ \\ 97{\cmd mvTransMv}(alpha,X,Y,D) & Computes the dense matrix $D \leftarrow \alpha X^{H}Y$ \\ 98{\cmd mvDot}(X,Y,d) & Computes the corresponding dot products: $d[i] \leftarrow \bar a _{i} y _{i}$ \\ 99{\cmd mvScale}(X,d) & Scales the i-th column of a multivector X by $ d[i] $ \\ 100{\cmd mvNorm}(X,d) & Computes the 2-norm of each vector of X : $d[i] \leftarrow ||x _{i}|| _{2}$\\ 101\hline 102{\cmd setBlock}(X,Y,index)& Copies the vectors in X to a subset of vectors in Y \\ 103{\cmd mvInit}(X,alpha)& Replaces each entry in the multivector X with a scalar $\alpha$ \\ 104{\cmd mvRandom}(X)& Replaces the entries in the multivector X by random scalars \\ 105\hline 106{\cmd mvPrint}(X) & Print the multivector X \\ 107\hline 108\end{tabular} 109\end{center} 110The arithmetic methods defined by the {\class multiVecTraits} are essential to the computations required by the Rayleigh-Ritz method and the general eigen-iteration. The {\cmd mvTimesMatAddMv} and {\cmd mvAddMv} methods, for instance, are necessary for updating the approximate eigenpairs and their residuals. {\cmd The mvDot} and {\cmd mvTransMv} methods are required by the orthogonalization procedure of the eigen-iteration. The {\cmd mvScale} and {\cmd mvNorm} methods are necessary, at the very least, for the computation of approximate eigenpairs and for some termination criteria of the eigen-iteration. Deflation and locking of converged 111eigenvectors necessitates the {\cmd setBlock} method in many cases. Initialization of the bases for the eigen-iteration requires methods such as {\cmd mvRandom} and {\cmd mvInit}. The ability to perform error checking and debugging is supported by methods that give dimensional attributes ({\cmd getVecLength}, {\cmd getNumberVecs}) and allow the users to print out a multivector ({\cmd mvPrint}).\\ 112Calling methods of {\class MultiVecTraits} and {\class OperatorTraits} requires that specializations of these traits classes have been implemented for given template arguments. \xlifepp provides the following classes that can be used with these traits classes: 113\begin{itemize} 114\item {\class MultiVectorAdapter}, an extended version of {\class Vector}, allowing the process on block of vector. 115\item {\class LargeMatrixAdapter}, a wrapper of {\class LargeMatrix} in order to take adavantage of multiplication matrix-vector of sparse matrix 116\end{itemize} 117 118\subsubsection{The {\classtitle MultiVectorAdapter} class} 119 120This class provides the implementations of the interfaces along with abstract {\class MultiVec} class. By inheriting directly from {\class MultiVec} class, the implementaion works via run-time polymorphism. This option isn't optimal from performance point of view because of the dispatching cost during run-time. In the near future, we can improve it by implementing a specialization of {\class MultiVecTraits} class, which allows compile-time polymorphism. \\ 121By using the pointer to each vector, {\class MultiVectorAdapter} class allows to create different objects, which share a same view of memory (shallow copy). This feature is essential when we want only to work on specific column vectors of a multivector. 122\begin{lstlisting}[deletekeywords={[3] length}] 123template <class ScalarType> 124class MultiVecAdapter : public MultiVec<ScalarType> 125{ 126 public: 127 typedef std::vector<ScalarType>* VectorPointer; 128 129 //! Constructor for a \c numberVecs vectors of length \c length. 130 MultiVecAdapter(const Number length, const Dimen numberVecs) : 131 length_(length), 132 numberVecs_(numberVecs) 133 { 134 check(); 135 136 data_.resize(numberVecs); 137 ownership_.resize(numberVecs); 138 139 // Allocates memory to store the vectors. 140 for (Dimen v = 0 ; v < numberVecs_ ; ++v) 141 { 142 data_[v] = new std::vector<ScalarType>(length); 143 ownership_[v] = true; 144 } 145 146 // Initializes all elements to zero. 147 mvInit(0.0); 148 } 149 ... 150\end{lstlisting} 151 152\subsubsection{The {\classtitle LargeMatrixAdapter} class} 153 154This class is simply a wrapper of {\class LargeMatrix} class in order to take advantage of consuming-time matrix-vector multiplication provided by \xlifepp. The method {\cmd apply} implemnt multiplication of a LargeMatrix with a block of vector. Noting that it's only a way to implement application of an operator to vector. Depending on specific eigenproblem, we can create a new class with a different approach to apply operator; for example, operator in case of spectral transformation to find the not-well separated eigenvalues. 155\begin{lstlisting}[deletekeywords={[3] x, y}] 156 template<typename ScalarType> 157 class LargeMatrixAdapter : public Operator<ScalarType> 158 { 159 private: 160 typedef LargeMatrix<ScalarType> LMatrix; 161 public: 162 LargeMatrixAdapter() { lMatrix_p = NULL; } 163 LargeMatrixAdapter(LMatrix* p) { lMatrix_p = p; } 164 165 LargeMatrixAdapter& operator=(LMatrix matrix) { lMatrix_p = &matrix; return *this; } 166 167 168 //! Destructor. 169 virtual ~LargeMatrixAdapter() {}; 170 171 void apply(const MultiVec<ScalarType>& x, MultiVec<ScalarType>& y) const 172 { 173 for (int i = 0; i< x.getNumberVecs(); ++i) { 174 multMatrixVector(*lMatrix_p, *(x[i]), *(y[i])); 175 } 176 } 177 private: 178 LMatrix* lMatrix_p; 179 }; 180\end{lstlisting} 181 182\subsubsection{The {\classtitle LargeMatrixAdapterInverse} class} 183 184Similar to {\class LargeMatrixAdapter}, this class is simply a wrapper of {\class LargeMatrix} in order to take advantage of consuming-time matrix-vector multiplication provided by \xlifepp. The class operates on an already factorized matrix to carry out the multiplication of its inverse (if invertible) with a vector. Instead of invoking the function {\cmd multMatrixVector}, the method {\cmd apply} calls the routine {\cmd multInverMatrixVector} of class {\class LargeMatrix} with the type of factorization. 185 186\begin{lstlisting}[deletekeywords={[3] x, y}] 187 template<typename MatrixType, typename ScalarType> 188 class LargeMatrixAdapterInverse : public Operator<ScalarType> 189 { 190 private: 191 typedef const MatrixType LMatrix; 192 public: 193// LargeMatrixAdapterInverse() : lMatrix_p(NULL), fac_(_noFactorization) {} 194 LargeMatrixAdapterInverse(LMatrix* p, FactorizationType fac) : lMatrix_p(p), fac_(fac) {} 195 196 LargeMatrixAdapterInverse& operator=(LMatrix matrix) { lMatrix_p = &matrix; fac_ = _noFactorization; return (*this); } 197 198 199 //! Destructor. 200 virtual ~LargeMatrixAdapterInverse() {}; 201 202 void apply(const MultiVec<ScalarType>& x, MultiVec<ScalarType>& y) const 203 { 204 for (int i = 0; i< x.getNumberVecs(); ++i) { 205 multInverMatrixVector(*lMatrix_p, *(x[i]), *(y[i]), fac_); 206 } 207 } 208 209 private: 210 LMatrix* lMatrix_p; 211 FactorizationType fac_; 212 }; 213\end{lstlisting} 214 215\subsubsection{The {\classtitle LargeMatrixAdapterInverseGeneralized} class} 216 217On some specific eigen problems, it is better to do spectral transformation, especially, to find not well-seperated eigenvalues. This class serves for this purpose. As indicated by its name, this class works mainly for the spectral transformation of generalized eigen problem. The first operation is multiplication of the one matrix with a vector. The vector result is multiplied by the inverse of the second matrix which is provided in a form of factorized one. Both operations are done in the routine {\cmd apply}. 218\begin{lstlisting}[deletekeywords={[3] x, y}] 219 template<typename MatrixType, typename ScalarType> 220 class LargeMatrixAdapterInverseGeneralized : public Operator<ScalarType> 221 { 222 private: 223 typedef const MatrixType LMatrix; 224 public: 225 LargeMatrixAdapterInverseGeneralized(LMatrix* pA, LMatrix* pB, FactorizationType fac) : lMatrixA_p(pA), lMatrixB_p(pB), fac_(fac) {} 226 227// LargeMatrixAdapterInverseGeneralized& operator=(LMatrix matrix) { lMatrix_p = &matrix; fac_ = _noFactorization; return (*this); } 228 229 230 //! Destructor. 231 virtual ~LargeMatrixAdapterInverseGeneralized() {}; 232 233 void apply(const MultiVec<ScalarType>& x, MultiVec<ScalarType>& y) const 234 { 235 Vector<ScalarType> vecTemp(x.getVecLength()); 236 for (int i = 0; i< x.getNumberVecs(); ++i) { 237 multMatrixVector(*lMatrixB_p, *(x[i]), vecTemp); 238 multInverMatrixVector(*lMatrixA_p, vecTemp, *(y[i]), fac_); 239 } 240 } 241 242 private: 243 LMatrix* lMatrixA_p; 244 LMatrix* lMatrixB_p; 245 FactorizationType fac_; 246 }; 247\end{lstlisting} 248 249\subsection{{\libtitle eigenSparse} framework} 250 251{\lib eigenSparse} is organized with a multi-tiered access for eigensolver algorithms. Users have the choice of interfacing at one of two levels: either working at a high-level with a eigensolver manager or working at a low-level directly with an eigensolver.\\ 252Consider as an example the block Davidson iteration. \\ 253\begin{tabular} {l} 254\hline 255Block Davidson Algorithm \\ 256\hline 257Require: Set an initial guess \textbf{V} and \textbf{H} = [] \\ 2581: \textbf{for} $iter=1$ to $iter _{max}$ \textbf{do}\\ 2592: ~ \textbf{repeat} \\ 2603: ~ ~ ~ ~Compute \textbf{M}-orthonormal basis \textbf{V} for [\textbf{V},\textbf{H}]\\ 2614: ~ ~ ~ ~Project \textbf{A} onto \textbf{V}: $\hat A = V^{H}AV$ \\ 2625: ~ ~ ~ ~Compute selected eigenpairs (\textbf{Q},\textbf{$\Gamma$}) of $\hat A$: $\hat A Q = Q\Gamma $ \\ 2636: ~ ~ ~ ~Compute Ritz vectors: $X = VQ$ \\ 2647: ~ ~ ~ ~Compute residuals: $R = AX - MX\Gamma$\\ 2658: ~ ~ ~ ~Precondition the residuals: $H = NR$ \\ 2669: ~ ~ ~ ~Check convergence and possibly terminate iteration \\ 26710:~ \textbf{until} the matrix \textbf{V} is not expandable \\ 26811: ~Restart \textbf{V} \\ 26912: \textbf{end for} \\ 270\hline 271\end{tabular} 272 273\medskip 274 275\begin{figure} [H] 276\centering 277\includeTikZPict[width=13cm]{solverManager.png} 278\caption{\textbf{SolverManager} class collaboration graph} 279\end{figure} 280 281The linear operators \textbf{A} and \textbf{M} define the eigenproblem to be solved. The linear operator \textbf{N} 282is a preconditioner for the problem, and its application to a block of vectors is required. Example preconditioners \textbf{N} 283include the inverse of the diagonal of \textbf{A} (Jacobi preconditioner), an algebraic multigrid preconditioner for \textbf{A}, or 284a preconditioned iteration that approximates the solution of a linear set of equations with \textbf{A}, e.g., the preconditioned conjugate gradient algorithm. \\ 285The \textit{multivector} \textbf{V}, \textbf{X}, and \textbf{R} serve for some purpose. The column-vectors for matrix \textbf{V} 286form the basis for the Rayleigh-Ritz approximation conducted at Step 5-6. We make the specific choice that these vectors are orthonormal with respect to the inner product induced by the Hermitian positive-definite matrix \textbf{M}, but this is not a requirement. \\ 287The block Davidson eigensolver as described above is useful for examining some 288of the functionality provided by {\lib eigenSparse}. The algorithm above highlights three levels of 289functionality. The first level is given by Steps 1-12 that constitute the eigensolver 290strategy to solve problem (2). The second level is given by Steps 2-10 that form 291the eigen-iteration. The third level consists of computational steps that can be 292implemented in a variety of manners, so encouraging modularization. Step 3 requires an orthonormalization method. The decisions involved in Step 5 require a 293determination of the eigenvalues and invariant subspace of interest, as well as a 294definition of accuracy. To check convergence in Step 9, several criteria are possible. 295For instance, a norm induced by a matrix other than \textbf{M} may be employed. The 296restarting of this eigensolver (Step 11) can be performed in a variety of ways and 297therefore need not be tightly coupled to the eigensolver. Each of these mechanisms 298provide opportunity for decoupling functionality that need not be implemented in 299a specific manner. \\ 300Looking deeper into the algorithm, the iteration repeats until the basis V is full 301(in which case it is time to restart) or some stopping criterion has been satisfied. Many valid 302stopping criteria exist, as well as many different methods for restarting the basis. Both of 303these, however, are distinct from the essential iteration as described above. A user wanting 304to perform block Davidson iterations could ask the solver to perform these iterations until 305a user-specified stopping criterion was satisfied or the basis was full, at which time the user 306would perform a restart. This allows the user complete control over the stopping criteria and the restarting mechanism, and leaves the eigensolver responsible for a relatively simple bit of state and behavior. \\ 307The eigensolvers (encapsulating an iteration and the associated state) are derived classes of the abstract base class {\class Eigensolver.} 308The goals of this class are three-fold: 309\begin{itemize} 310\item to define an interface used for checking the status of a solver by a status test; 311\item to contain the essential iteration associated with a particular eigensolver iteration; 312\item to contain the state associated with that iteration. 313\end{itemize} 314The status tests, assembled to describe a specific stopping criterion and queried by the 315eigensolver during the iteration, are represented as subclasses of {\class StatusTest}. The 316communication between status test and eigensolver occurs inside of the \textit{iterate()} method 317provided by each {\class Eigensolver}. This code generally takes the form: 318\begin{lstlisting}[]{} 319SomeEigensolver::iterate() { 320while ( statustest.checkStatus(this) != _passed ) { 321// 322// perform eigensolver iterations 323// 324} 325r 326\end{lstlisting} 327Each {\class StatusTest} provides a method, \textit{checkStatus()}, which through queries to 328the methods provided by {\class Eigensolver}, determines whether the solver meets the criteria defined by that particular status test. After a solver returns from \textit{iterate()}, the caller has the option of accessing the state associated with the solver and re-initializing the solver with a new state. \\ 329While this method of interfacing with the solver is powerful, it can be tedious. This method requires that user construct a number of support classes, in addition to managing call to \textit{Eigensolver::iterate()}. The {\class SolverManager} class was developed to 330address this complaint. A solver manager is a class that wraps around an eigensolver, providing additional functionality while also handling lower-level interaction with the eigensolver that a user may not wish to handle. Solver managers are intended to be easy to 331use, while still providing the features and flexibility needed to solve real-world eigenvalue problems. For example, the {\class BlockDavidsonSolMgr} takes only two arguments in its constructor: an {\class Eigenproblem} specifying the eigenvalue problem to be solved and a {\class Parameters} of options specific to this solver manager. The solver manager instantiates an eigensolver, along with the status tests and other support classes needed by the eigensolver. To solve the eigenvalue problem, the user simply calls the \textit{solve()} method of the solver manager. The solver manager performs repeated calls to the eigensolver \textit{iterate()} 332method, performs restarts and locking, and places the final solution into the eigenproblem. 333 334\subsection{{\libtitle eigenSparse} classes} 335 336The following lists and briefly describes the current classes of {\lib eigenSparse}. 337 338\subsubsection{The {\classtitle EigenProblem} class} 339 340{\class EigenProblem} is a container for the components of an eigenvalue problem, as well as the solutions. By requiring eigen problems to derive from {\class EigenProblem}, {\lib eigenSparse} defines a minimum interface that can be expected of all eigenvalue problems by the classes 341that will work with the problems (e.g., eigensolvers and status testers).\\ 342\begin{lstlisting}[]{} 343/*! 344 * \class xlifepp::EigenProblem 345 \brief This provides a basic implementation for defining standard or 346 generalized eigenvalue problems. 347*/ 348 template<class ScalarType, class MV, class OP> 349 class EigenProblem 350 { 351 public: 352 353 //! Empty constructor - allows xlifepp::EigenProblem to be described at a later time through "Set Methods". 354 EigenProblem(); 355 356 //! Standard Eigenvalue Problem Constructor. 357 EigenProblem(const SmartPtr<const OP>& Op, const SmartPtr<MV>& initVec); 358 359 //! Generalized Eigenvalue Problem Constructor. 360 EigenProblem(const SmartPtr<const OP>& Op, const SmartPtr<const OP>& B, const SmartPtr<MV>& initVec); 361 362 //! Copy Constructor. 363 EigenProblem(const Eigenproblem<ScalarType, MV, OP>& Problem); 364 365 //! Destructor. 366 ~EigenProblem() {}; 367 ... 368\end{lstlisting} 369Both the eigen problem and the eigensolver in {\lib eigenSparse} are templated on the scalar type, 370the multivector type and the operator type. Before declaring an eigen problem, users must 371choose classes to represent these entities. Having done so, they can begin to specify the 372parameters of the eigen problem. The {\class EigenProblem} class defines set methods for 373the parameters of the eigen problem. These methods are: 374\begin{itemize} 375\item {\cmd setOperator} - set the operator for which the eigenvalues will be computed 376\item {\cmd setA} - set the A operator for the eigenvalue problem $Ax = Mx\lambda$ 377\item {\cmd setM} - set the M operator for the eigenvalue problem $Ax = Mx\lambda$ 378\item {\cmd setPrec} - set the preconditioner for the eigenvalue problem 379\item {\cmd setInitVec} - set the initial iterate 380\item {\cmd setAuxVecs} - set the auxiliary vectors, a subspace used to constrain the search space for the solution 381\item {\cmd setNEV} - set the number of eigenvalues to be computed 382\item {\cmd setHermitian} - specify whether the problem is Hermitian 383\end{itemize} 384In addition to these {\itshape set} methods, {\class EigenProblem} defines a method {\cmd setProblem()} that gives the class the opportunity to perform any initialization that may be necessary before the problem is handed off to an eigensolver, in addition to verifying that the problem has been adequately defined. \\ 385For each of the {\itshape set} methods listed above, there is a corresponding {\itshape get} function. These are the functions used by eigensolvers and solver managers to get the necessary information from the eigenvalue problem. In addition, there are two methods for storing and retrieving the results of the eigenvalue computation: 386\begin{lstlisting}[]{} 387const EigenSolverSolution & getSolution(); 388void setSolution(const EigenSolverSolution & sol); 389\end{lstlisting} 390In order to verify if all the parameters are set, {\class EigenProblem} provide a method {\cmd isProblemSet()} 391 392\subsubsection{The {\classtitle EigenSolverSolution} structure} 393 394The {\class EigenSolution} structure was developed in order to facilitate setting and retrieving of solution data. 395\begin{lstlisting}[]{} 396 //! Struct for storing an eigenproblem solution. 397 template <class ScalarType, class MV> 398 struct EigenSolverSolution { 399 //! The computed eigenvectors 400 SmartPtr<MV> evecs; 401 //! An orthonormal basis for the computed eigenspace 402 SmartPtr<MV> espace; 403 //! The computed eigenvalues 404 std::vector<ValueEigenSolver<ScalarType> > evals; 405 /*! \brief An index into evecs to allow compressed storage of eigenvectors for real, non-Hermitian problems. 406 * 407 * index has length numVecs, where each entry is 0, +1, or -1. These have the following interpretation: 408 * - index[i] == 0: signifies that the corresponding eigenvector is stored as the i column of evecs. This will usually be the 409 * case when ScalarType is complex, an eigenproblem is Hermitian, or a real, non-Hermitian eigen problem has a real eigenvector. 410 * - index[i] == +1: signifies that the corresponding eigenvector is stored in two vectors: the real part in the i column of evecs and the <i><b>positive</b></i> imaginary part in the i+1 column of evecs. 411 * - index[i] == -1: signifies that the corresponding eigenvector is stored in two vectors: the real part in the i-1 column of evecs and the <i><b>negative</b></i> imaginary part in the i column of evecs 412 */ 413 std::vector<int> index; 414 //! The number of computed eigenpairs 415 int numVecs; 416 417 EigenSolverSolution() : evecs(),espace(),evals(0),index(0),numVecs(0) {} 418 }; 419\end{lstlisting} 420 421All {\lib eigenSparse} solver managers place the results of the computation in the {\class EigenProblem} class using an {\class EigenSolverSolution} structure. The number of eigen solutions computed is given by field numVecs. The eigenvalues are always stored as two real values, even 422when templated on a complex data type or when the eigenvalues are real. Similarly, the eigen space can always be represented by a multivector of width numVecs, even for non-symmetric eigen problems over the real field. The storage scheme for eigenvectors requires 423more finesse.\\ 424When solving real symmetric eigen problems, the eigenvectors can always be chosen to 425be real, and therefore can be stored in a single column of a real multivector. When solving 426eigenproblems over a complex field, whether Hermitian or non-Hermitian, the eigenvectors may be complex, but the multivector is defined over the complex field, so that this poses no problem. However, real non-symmetric problems can have complex eigenvectors, which prohibits a one-for-one storage scheme using a real multivector. Fortunately, the eigenvectors 427in this scenario occur as complex conjugate pairs, so the pair can be stored in two real 428vectors. This permits a compressed storage scheme, which uses an index vector stored in the 429{\class EigenSolverSolution}, allowing conjugate pair eigenvectors to be easily retrieved from evecs. 430The integers in {\cmd EigenSolverSolution::index} take one of three values: $\{0, +1, -1\}$. These values allow the eigenvectors to be retrieved as follows: 431\begin{itemize} 432\item $index[i]=0$: the i-th eigenvector is stored uncompressed in column $i$ of evecs 433\item $index[i] = +1$: the i-th eigenvector is stored compressed, with the real component in column $i$ of evecs and the positive complex component stored in column $i$ + 1 of evecs 434\item $index[i] = -1$: the i-th eigenvector is stored compressed, with the real component in 435column $i$ -1 of evecs and the negative complex component stored in column $i$ of evecs 436\end{itemize} 437Because this storage scheme is only required for non-symmetric problems over the real field, 438all other eigenproblems will result in an index vector composed entirely of zeroes. For the 439real non-symmetric case, the +1 index will always immediately precede the corresponding -1 440index. \\ 441 442\subsubsection{The {\classtitle EigenSolver} class} 443 444The {\class EigenSolver} class defines the basic interface that must be met by any eigen solver class in {\lib eigenSparse}. The specific eigensolvers are implemented as derived classes of {\class EigenSolver}. There are two eigen solvers currently implemented in {\lib eigenSparse} 445\begin{itemize} 446\item {\class BlockDavidson} : A block Davidson solver for Hermitian eigenvalue problems 447\item {\class BlockKrylovSchur} : A block Krylov Schur solver for Hermitian or non-Hermitian 448eigenvalue problems. 449\end{itemize} 450The class {\class EigenSolver}, like {\class EigenProblem}, is templated on the scalar type, multivector type and operator type. The options for the eigen solver are passed through the constructor, defined by {\class EigenSolver} to have the following form: 451\begin{lstlisting}[]{} 452template<class ScalarType, class MV, class OP> 453class EigenSolver { 454 public: 455 456 //! @name Constructors/Destructor 457 //@{ 458 459 //! Default Constructor. 460 EigenSolver() {}; 461 462 //! Basic Constructor. 463 /*! This constructor, implemented by all xlifepp eigensolvers, takes an xlifepp::Eigenproblem, 464 xlifepp::SortManager, xlifepp::OutputManager, and Parameters as input. These 465 four arguments are sufficient enough for constructing any xlifepp::EigenSolver object. 466 */ 467 EigenSolver( const SmartPtr<EigenProblem<ScalarType,MV,OP> >& problem, 468 const SmartPtr<SortManager<ScalarType> >& sorter, 469 const SmartPtr<OutputManager<ScalarType> >& printer, 470 const SmartPtr<StatusTest<ScalarType,MV,OP> >& tester, 471 const SmartPtr<OrthoManager<ScalarType,MV> >& ortho, 472 Parameters& params ); 473... 474\end{lstlisting} 475These classes are used as follows: 476\begin{itemize} 477\item \textbf{problem} - the eigenproblem to be solved; the solver will get the problem operators from here. 478\item \textbf{sorter} - the sort manager selects the significant eigenvalues 479\item \textbf{printer} - the output manager dictates verbosity level in addition to processing output streams 480\item \textbf{tester} - the status tester dictates when the solver should quit iterate() and return to the caller 481\item \textbf{ortho} - the orthogonalization manager defines the inner product and other concepts 482related to orthogonality, in addition to performing these computations for the solver 483\item \textbf{params} - the parameter list specifies eigensolver-specific options; see the documentation 484for a list of options support by individual solvers 485\end{itemize} 486In addition to specifying an iteration, an eigensolver also specifies a concept of state, i.e. the current data associated with the iteration. After declaring an Eigensolver object, the state of the solver is in an uninitialized state. For most solver, to be initialized mean to be in 487a valid state, containing all of the information necessary for performing eigen solver iterations. \\ 488Eigensolver provides two methods concerning initialization: \textit{isInitialized()} indicates whether the solver is initialized or not, and \textit{initialize()} (with no arguments) instructs the solver to initialize itself using random data or the initial vectors stored in the eigenvalue problem. \\ 489To ensure that solvers can be used as efficiently as possible, the user needs access to the 490state of the solver. To this end, each eigensolver provides low-level methods for getting and 491setting the state of the solver: 492\begin{itemize} 493\item {\cmd getState()} - returns a solver-specific structure with read-only pointers to the current 494state of the solver. 495\item {\cmd initialize(...)} - accepts a solver-specific structure enabling the user to initialize the 496solver with a particular state. 497\end{itemize} 498The combination of these two methods, along with the flexibility provided by status tests, 499allows the user almost total control over eigen solver iterations. \\ 500Moreover, each eigen solver provides two significant types of methods: status methods and solver- 501specific state methods. The status methods are defined by the Eigensolver abstract 502base class and represent the information that a generic status test can request from any 503eigensolver. A list of these methods is given in following table 504\begin{center} 505\begin{tabular}{|l|p{11.5cm}|} 506 \hline 507 \textit{Method name} & \textit{Description}\\ 508 \hline 509 {\cmd getNumIters} & Get the current number of iterations. \\ 510 {\cmd getRitzValues} & Get the most recent Ritz values. \\ 511 {\cmd getRitzVectors} & Get the most recent Ritz vectors. \\ 512 {\cmd getRitzIndex} & Get the Ritz index needed for indexing compressed Ritz vectors. \\ 513 {\cmd getResNorms} & Get the most recent residual norms, with respect to the OrthoManager. \\ 514 {\cmd getRes2Norms} & Get the most recent residual 2-norms. \\ 515 {\cmd getRitzRes2Norms} & Get the most recent Ritz residual 2-norms. \\ 516 {\cmd getCurSubspaceDim} & Get the current subspace dimension. \\ 517 {\cmd getMaxSubspaceDim} & Get the maximum subspace dimension. \\ 518 {\cmd getBlockSize} & Get the block size. \\ 519 \hline 520\end{tabular} 521\end{center} 522 523\subsubsection{The {\classtitle SolverManager} class} 524 525Using {\lib eigenSparse} by interfacing directly with eigensolvers is extremely powerful, but can be 526tedious. Solver managers provide a way for users to encapsulate specific solving strategies 527inside of an easy-to-use class. Novice users may prefer to use existing solver managers, while 528advanced user may prefer to write custom solver managers. 529{\class SolverManager} defines only two methods: a constructor accepting an {\class EigenProblem} and a parameter list of solver manager-specific options; and a {\cmd solve()} method, taking no arguments and returning either {\tt \_converged} or {\tt \_unconverged}. Consider the following example code: 530\begin{lstlisting}[]{} 531// Create an eigen problem 532SmartPtr<EigenProblem<ST,MV,OP> > problem = 533// Create parameter list to pass into the solver manager 534 Parameters pl; 535 pl.set("Verbosity", (int)verbosity); 536 pl.set("Which", which); 537 pl.set("Orthogonalization", ortho); 538 pl.set("Block Size", blockSize); 539 pl.set("Num Blocks", numBlocks); 540 pl.set("Maximum Restarts", maxRestarts); 541// Create a solver manager 542 BlockDavidsonSolMgr<ST,MV,OP> bdSolverMan(problem, pl); 543// Solve the eigenvalue problem 544 ComputationInfo returnCode = bdSolverMan.solve(); 545// Get the solution from the problem 546 EigenSolverSolution<ST,MV> sol = problem->getSolution(); 547\end{lstlisting} 548As has been stated before, the goal of the solver manager is to create an eigensolver object, 549along with the support objects needed by the eigensolver. Another purpose of many solver 550managers is to manage and initiate the repeated calls to the underlying solver’s {\cmd iterate()} 551method. For solvers that build a Krylov subspace to some maximum dimension (e.g., {\class BlockKrylovSchur} and {\class BlockDavidson}), the solver manager will also assume the task of restarting the solver when the subspace is full. This is something for which multiple approaches are possible. Also, there may be substantial flexibility in creating the support classes (e.g., sort 552manager, status tests) for the solver. An aggressive solver manager could even go so far as to 553construct a preconditioner for the eigenvalue problem. \\ 554These examples are meant to illustrate the flexibility that specific solver managers may 555have in implementing the {\cmd solve()} routine. Some of these options might best be incorporated 556into a single solver manager, which takes orders from the user via the parameter list given in 557the constructor. Some of these options may better be contained in multiple solver managers, 558for the sake of code simplicity. It is even possible to write solver managers that contain other 559solvers managers; motivation for something like this would be to select the optimal solver 560manager at runtime based on some expert knowledge, or to create a hybrid method which 561uses the output from one solver manager to initialize another one. \\ 562 563\subsubsection{The {\classtitle StatusTest} class} 564 565the purpose of the {\class StatusTest} should be clear: to give the user or solver manager flexibility in stopping the eigen solver iterations in order to interact directly with the solver. \\ 566Many reasons exist for why a user would want to stop the solver from iterating: 567\begin{itemize} 568\item some convergence criterion has been satisfied and it is time to quit; 569\item some part of the current solution has reached a sufficient accuracy to removed from the 570iteration (“locking”); 571\item the solver has performed a sufficient or excessive number of iterations. 572\end{itemize} 573These are just some commonly seen reasons for ceasing the iteration, and each of these can be so varied in implementation/parametrization as to require some abstract mechanism controlling the iteration. \\ 574Below is a list of status tests: 575\begin{itemize} 576\item {\class StatusTestCombo} - this status test allows for the boolean combination of other 577status tests, creating near unlimited potential for complex status tests. 578\item {\class StatusTestOutput} - this status test acts as a wrapper around another status 579test, allowing for printing of status information on a call to checkStatus() 580\item {\class StatusTestMaxIters} - this status test monitors the number of iterations performed by the solver; it can be used to halt the solver at some maximum number of 581iterations or even to require some minimum number of iterations. 582\item {\class StatusTestResNorm} - this status test monitors the residual norms of the current iterate. 583\item {\class StatusTestWithOrdering} - this status test also monitors the residual norms 584of the current iterate, but only considers the residuals along with a set of auxiliary eigenvalues. 585\end{itemize} 586 587\subsubsection{The {\classtitle SortManager} class} 588 589The purpose of a sort manager is to separate the eigensolver classes from the sorting functionality required by those classes. This satisfies the flexibility principle sought by {\lib eigenSparse}, by giving users the opportunity to perform the sorting in whatever manner is deemed to be most appropriate. {\lib eigenSparse} defines an abstract class {\class SortManager} with two methods, 590one for sorting real values and one for sorting complex values: 591\begin{lstlisting}[]{} 592// Sorting with real value 593virtual void sort(std::vector<MagnitudeType>& evals, SmartPtr<std::vector<int> > perm = _smPtrNull, int n = -1) const = 0; 594// Sorting with complex value 595virtual void sort(std::vector<MagnitudeType>& rEvals, 596 std::vector<MagnitudeType>& iEvals, 597 SmartPtr<std::vector<int> > perm = _smPtrNull, 598 int n = -1) const = 0; 599\end{lstlisting} 600 601\subsubsection{The {\classtitle BasicSort} class} 602 603The concrete derived class implements sorting scheme for sorting real values and one for sorting complex values. 604\begin{lstlisting}[]{} 605template<class MagnitudeType> 606class BasicSort : public SortManager<MagnitudeType> 607{ 608public: 609 /*! \brief Parameter list driven constructor 610 611 This constructor accepts a paramter list with the following options: 612 - \c "Sort Strategy" - a \c string specifying the desired sorting strategy. See setSortType() for valid options. 613 */ 614 BasicSort(Parameters& pl); 615 616 /*! \brief String driven constructor 617 618 Directly pass the string specifying sort strategy. See setSortType() for valid options. 619 */ 620 BasicSort(const String& which = "LM"); 621 622 //! Destructor 623 virtual ~BasicSort(); 624 ... 625\end{lstlisting} 626By default, sort strategy Largest Magnitude (LM) is used. So as to change the strategy, user can easily specify it with {\cmd setSortType} method. 627 628\subsubsection{The {\classtitle OrthoManager} class} 629 630Orthogonalization and orthonormalization are commonly performed computations in iterative eigensolvers; in fact, for some eigensolvers, they represent the dominant cost. Different scenarios may require different approaches (e.g, Euclidean inner product versus $M$ inner 631product, orthogonal projections versus oblique projections). Combined with the plethora of 632available methods for performing these computations, Anasazi has left as much leeway to the 633users as possible. \\ 634Orthogonalization of multivectors is performed by derived classes of the abstract class {\class OrthoManager} that provides five methods: 635\begin{itemize} 636\item {\cmd innerProd(X,Y,Z)} - performs the inner product defined by the manager. 637\item {\cmd norm(X)} - computes the norm induced by {\cmd innerProd()}. 638\item {\cmd project(X,C,Q)} - given an orthonormal basis Q, projects X onto to the space perpindicular to colspan(Q), optionally returning the coefficients of X in Q. 639\item {\cmd normalize(X,B)} - returns an orthonormal basis for colspan(X), optionally returning 640the coefficients of X in the computed basis. 641\item {\cmd projectAndNormalize(X,C,B,Q)} - computes an orthonormal basis for subspace 642colspan(X) - colspan(Q), optionally returning the coefficients of X in Q and the new 643basis. 644\end{itemize} 645It should be noted that a call to {\cmd projectAndNormalize()} is not necessarily equivalent 646to a call to {\cmd project()} followed by {\cmd normalize()}. This follows from the fact that, for some 647orthogonalization managers, a call to {\cmd normalize()} may augment the column span of a rank- 648deficient multivector in order to create an orthonormal basis with the same number of columns 649as the input multivector. In this case, the code 650\begin{lstlisting}[]{} 651orthoman.project(X,C,Q); 652orthoman.normalize(X,B); 653\end{lstlisting} 654could result in an orthonormal basis $X$ that is not orthogonal to the basis in $Q$. \\ 655\xlifepp provides two orthogonalization managers: 656\begin{itemize} 657\item {\class BasicOrthoManager} - performs orthogonalization using multiple steps of classical Gram-Schmidt. 658\item {\class SVQBOrthoManager} - performs orthogonalization using the SVQB orthogonalization technique described by Stathapoulos and Wu 659\end{itemize} 660The core function of these orthogonalization managers is {\cmd findBasis}, which find an operator-orthonormal basis for a span, with the option of extending the subspace. 661 662\subsubsection{The {\classtitle OutputManager} class} 663 664The output manager exists to provide flexibility with regard to the verbosity of the eigen solver. Each output manager has two primary concerns: what output is printed and where the output is printed to. When working with the output manager, output is classified 665into one of the following message types: \\ 666\begin{lstlisting}[]{} 667enum MsgEigenType 668{ 669 _errorsEigen = 0, /*!< Errors [ always printed ] */ 670 _warningsEigen = 0x1, /*!< Internal warnings */ 671 _iterationDetailsEigen = 0x2, /*!< Approximate eigenvalues, errors */ 672 _orthoDetailsEigen = 0x4, /*!< Orthogonalization/orthonormalization details */ 673 _finalSummaryEigen = 0x8, /*!< Final computational summary */ 674 _timingDetailsEigen = 0x10, /*!< Timing details */ 675 _statusTestDetailsEigen = 0x20, /*!< Status test details */ 676 _debugEigen = 0x40 /*!< Debugging information */ 677}; 678 \end{lstlisting} 679Output manager are subclasses of the abstract base class {\class OutputManager}. This class provides the following output-related methods: 680\begin{lstlisting}[deletekeywords={[3] type}] 681template <class ScalarType> 682class OutputManager { 683 public: 684 ... 685 //! Find out whether we need to print out information for this message type. 686 /*! This method is used by the solver to determine whether computations are 687 necessary for this message type. 688 */ 689 virtual bool isVerbosity(MsgEigenType type) const = 0; 690 691 //! Send output to the output manager. 692 virtual void print(MsgEigenType type, const String output) = 0; 693 694 //! Create a stream for outputting to. 695 virtual std::ostream& stream(MsgEigenType type) = 0; 696... 697 \end{lstlisting} 698\xlifepp provides a single output manager, {\class BasicOutputManager}. This class accepts an output stream from the user. The output corresponding to the verbosity level of the manager is sent to this stream. 699 700\subsubsection{The {\classtitle SolverUtils} class} 701 702Most of the large-scale eigenvalue algorithms exploit projection processes so as to extract approximate eigenvectors from a given subspace. Inside this subspace, a small matrix eigenvalue problem is obtained. \xlifepp provides {\class SolverUtils} class, a collection of utilities necessary for the solvers. These utilities include sorting, orthogonalization, projecting/solving local eigen systems, 703\begin{lstlisting}[]{} 704 template<class ScalarType, class MV, class OP> 705 class SolverUtils 706 { 707 public: 708 typedef typename NumTraits<ScalarType>::RealScalar MagnitudeType; 709 typedef typename NumTraits<ScalarType>::Scalar SCT; 710 typedef MatrixEigenDense<SCT> MatrixDense; 711 712 //! @name Constructor/Destructor 713 //@{ 714 715 //! Constructor. 716 SolverUtils(); 717 718 //! Destructor. 719 virtual ~SolverUtils() {}; 720 ... 721 \end{lstlisting} 722In fact, all utilities along with this class make use of functions of {\lib eigenCore}. The most important function of this class is {\cmd directSolver}, a routine for computing eigen pairs of Hermitian pencil. This method is heavily used during the projection of vectors into a subspace and normalization of these vector with a specified norm. \\ 723 724\subsection{Examples} 725 726The following gives sample code for solving a symmetric eigenvalue problem using the Block Davidson solver manager.\\ 727The first step in solving an eigenvalue problem is to define the eigenvalue problem. However, before that, we need to make sure define the operator and the multivector. 728\begin{lstlisting}[]{} 729// Load LargeMatrix from file 730LargeMatrix<Real> rMatSym(dataPathTo(rMatrixSym), _dense, 782, _cs, _symmetric); 731// Define the operator (in this case, it's LargeMatrix) 732SmartPtr< const LargeMatrixAdapter<ST> > K(new LargeMatrixAdapter<ST>(&rMatSym)); 733// Sometimes, we need a preconditioner to be able to solve the eigenvalue problem 734SmartPtr< const LargeMatrixAdapter<ST> > Prec(new LargeMatrixAdapter<ST>(&matPrec)); 735// Define multivector 736SmartPtr<MV> ivec = SmartPtr<MultiVecAdapter<ST> >(new MultiVecAdapter<ST>(rMatSym.nbCols, blockSize)); 737// Initialize the multivector with random values 738ivec->mvRandom(); 739 \end{lstlisting} 740Everything is fine then eigenvalue problem is defined as follow: 741\begin{lstlisting} 742SmartPtr<EigenProblem<ST,MV,OP> > problem = SmartPtr<EigenProblem<ST,MV,OP> >(new EigenProblem<ST,MV,OP>(K,ivec)); 743// Inform the eigenProblem that the operator cK is symmetric 744problem->setHermitian(true); 745// Set the number of eigenvalues requested 746problem->setNEV(nev); 747\end{lstlisting} 748The second step is to specify parameters for the solver manager, depending on algorithm (Block Davidson or Block Krylov Schur), we can have a different parameters. Nevertheless, there are some essential ones to be specified 749\begin{lstlisting}[]{} 750const int nev = 4; //6 751int blockSize = 5;//6; 752int numBlocks = 5;//14; 753int maxRestarts = 25;//8; 754Real tol = 1.0e-6; 755String which("SM"), ortho("SVQB"); 756bool locking = true; 757bool insitu = false; 758MsgEigenType verbosity = _errorsEigen; 759 760Parameters pl; 761pl.set("Verbosity", (int)verbosity); 762pl.set("Which", which); 763pl.set("Orthogonalization", ortho); 764pl.set("Block Size", blockSize); 765pl.set("Num Blocks", numBlocks); 766pl.set("Maximum Restarts", maxRestarts); 767pl.set("Convergence Tolerance", tol); 768pl.set("Use Locking", locking); 769pl.set("Locking Tolerance", tol/10); 770pl.set("In Situ Restarting", insitu); 771\end{lstlisting} 772Till now, we have all the information needed to declare the solver manager and solve the problem: 773\begin{lstlisting} 774BlockDavidsonSolMgr<ST,MV,OP> bdSolverMan(problem, pl); 775ComputationInfo returnCode = bdSolverMan.solve(); 776 \end{lstlisting} 777The return value of the solver indicate whether the algorithm succeeds or not.(i.e: whether the requested number of eigenpairs were found to a sufficient accuracy (as defined by the solver manager). Output from {\cmd solve()} routine in this example might look as follows: 778\begin{lstlisting}[language=] 779BlockDavidson Eigen Solver Test : Standard symmetric matrix test with Smallest Magnitude 780-------------------------------------------------------------------------------- 781 Direct residual norms computed 782 Eigenvalue Residual(M) 783---------------------------------------- 784 -1.29891e-06 8.63079e-08 785 -1.29973e-06 6.27415e-08 786 -1.30048e-06 7.99246e-08 787 -1.33992e-06 7.09023e-07 788 789 \end{lstlisting} 790