diff --git a/Grid/algorithms/iterative/BlockKrylovSchur.h b/Grid/algorithms/iterative/BlockKrylovSchur.h index 5a730cb2f..ff9d11603 100644 --- a/Grid/algorithms/iterative/BlockKrylovSchur.h +++ b/Grid/algorithms/iterative/BlockKrylovSchur.h @@ -97,6 +97,10 @@ protected: GridBase* Grid_; RitzFilter ritzFilter; + // Prefix used in log messages; derived classes override in their constructor + // (e.g. "HarmonicBlockKrylovSchur") so the shared code reports the right name. + std::string className = "BlockKrylovSchur"; + // Flat storage: basis[s*Nblock + t] is the t-th vector of block s // After construction: basis has Nm entries std::vector basis; @@ -146,6 +150,8 @@ public: beta_k(0.0), rtol(0.0) {} + virtual ~BlockKrylovSchur() = default; + //-------------------------------------------------------------------- // Main entry point //-------------------------------------------------------------------- @@ -161,9 +167,9 @@ public: * _Nstop : stop after _Nstop eigenvalues converged * _Nblock : block size */ - void operator()(const std::vector& v0, int _maxIter, int _Nm, int _Nk, - int _Nstop, int _Nblock = 1, bool doubleOrthog = true, - bool doVerify = false) + virtual void operator()(const std::vector& v0, int _maxIter, int _Nm, int _Nk, + int _Nstop, int _Nblock = 1, bool doubleOrthog = true, + bool doVerify = false) { MaxIter = _maxIter; Nm = _Nm; @@ -176,7 +182,7 @@ public: if (useParityFlip) divisor *= 2; if (useGamma5) divisor *= 2; assert(Nblock % divisor == 0 && (int)v0.size() >= Nblock / divisor); - std::cout << GridLogMessage << "BlockKrylovSchur: divisor = " <= Nstop || iter == MaxIter - 1) { - std::cout << GridLogMessage << "BlockKrylovSchur: done after " << iter + std::cout << GridLogMessage << className << ": done after " << iter << " restarts, " << Nconv << " converged." << std::endl; std::cout << GridLogMessage << "Eigenvalues: " << evals << std::endl; @@ -306,7 +282,7 @@ public: ComplexD eval_est = toStdCmplx(innerProduct(evecs[k], w)); w -= eval_est * evecs[k]; RealD res = std::sqrt(norm2(w)); - std::cout << GridLogMessage << "BlockKrylovSchur: evec[" << k << "]" + std::cout << GridLogMessage << className << ": evec[" << k << "]" << " eval_reported = " << evals[k] << " eval_est = " << eval_est << " || A v - eval_est * v || = " << res << std::endl; @@ -385,7 +361,7 @@ public: if (nBasis == 0) { std::cout << GridLogMessage - << "BlockKrylovSchur::verify [" << label + << className << "::verify [" << label << "]: basis is empty." << std::endl; return; } @@ -394,7 +370,7 @@ public: int Nfull = (int)H.rows(); std::cout << GridLogMessage - << "======== BlockKrylovSchur::verify [" << label << "] ========" << std::endl; + << "======== " << className << "::verify [" << label << "] ========" << std::endl; std::cout << GridLogMessage << " nBasis = " << nBasis << " Nfull = " << Nfull << " Nblock = " << Nblock << " nF = " << nF << std::endl; @@ -529,7 +505,69 @@ public: */ virtual void preRun() {} -private: + /** + * Compute the restart rotation for the current Rayleigh quotient H. + * + * Returns Q (unitary) and Hnew = Q H Q^dag reordered so that the wanted + * Nk eigenvalues occupy the leading block, ready for truncation. + * + * Base implementation: the plain Schur decomposition of H, reordered by + * ritzFilter. HarmonicBlockKrylovSchur overrides this to Schur-decompose + * the shifted quotient (H - sigma I) so that eigenvalues nearest sigma are + * selected. + */ + virtual void restartRotation(CMat& Q, CMat& Hnew) + { + ComplexSchurDecomposition schur(H, false, ritzFilter); + std::cout << GridLogMessage << className << ": Schur decomposed." << std::endl; + schur.schurReorder(Nk); + std::cout << GridLogMessage << className << ": Schur reordered." << std::endl; + Q = schur.getMatrixQ(); + Hnew = schur.getMatrixS(); + } + +protected: + + //-------------------------------------------------------------------- + // Starting-block expansion (parity flip / gamma5 partners) + //-------------------------------------------------------------------- + /** + * Expand Nblock/divisor seed vectors into the full Nblock starting block by + * pairing each seed with its parity-flipped and/or gamma5 partners. Order: + * {v, flip(v)} then doubled by {., g5(.)} giving {v, flip(v), g5(v), + * g5(flip(v))} when both flags are set. Requires Nblock, Grid_ and the + * flags to be set. + */ + std::vector expandStartBlock(const std::vector& v0) + { + int divisor = (useParityFlip ? 2 : 1) * (useGamma5 ? 2 : 1); + std::vector startBlock; + startBlock.reserve(Nblock); + for (int i = 0; i < Nblock / divisor; i++) { + std::vector group; + group.push_back(v0[i]); + if (useParityFlip) { + int n = (int)group.size(); + for (int j = 0; j < n; j++) { + Field fp(Grid_); + parityFlippedField(group[j], fp); + group.push_back(std::move(fp)); + } + } + if (useGamma5) { + int n = (int)group.size(); + for (int j = 0; j < n; j++) { + Field g5v(Grid_); + gamma5Func(group[j], g5v); + group.push_back(std::move(g5v)); + } + } + for (auto& f : group) startBlock.push_back(std::move(f)); + } + return startBlock; + } + +protected: //-------------------------------------------------------------------- // Block Arnoldi iteration @@ -711,7 +749,7 @@ private: // deflation: zero this vector W[j] = Zero(); std::cout << GridLogMessage - << "BlockKrylovSchur: deflation at block column " << j + << className << ": deflation at block column " << j << " (norm = " << nrm << ")" << std::endl; } } @@ -806,7 +844,7 @@ private: CVec Bty = Bk.adjoint() * yk; // Nblock-vector RealD res = Bty.norm(); ritzEstimates.push_back(res); - std::cout << GridLogMessage << "BlockKrylovSchur: Ritz estimate[" << k + std::cout << GridLogMessage << className << ": Ritz estimate[" << k << "] = " << res << " eval = " << evals[k] << std::endl; if (res < rtol) Nconv++; else break; diff --git a/Grid/algorithms/iterative/KrylovSchur.h b/Grid/algorithms/iterative/KrylovSchur.h index 0c9c305af..5af80878b 100644 --- a/Grid/algorithms/iterative/KrylovSchur.h +++ b/Grid/algorithms/iterative/KrylovSchur.h @@ -43,6 +43,19 @@ using std::conj; inline const ComplexD& toStdCmplx(const ComplexD& c) { return c; } #endif +/** + * Small dense coefficient matrix of ComplexD (thrust::complex on GPU builds) + * for passing to basisRotate, whose device kernel cannot multiply Grid tensors + * by std::complex. Must be namespace-scope: nvcc rejects function-local types + * as template arguments to functions containing extended device lambdas. + */ +struct KSCoeffMat { + std::vector a; int n; + KSCoeffMat(int _n) : a(_n*_n), n(_n) {} + ComplexD& operator()(int i, int j) { return a[i*n+j]; } + const ComplexD& operator()(int i, int j) const { return a[i*n+j]; } +}; + /** * Options for which Ritz values to keep in implicit restart. TODO move this and utilities into a new file */ @@ -392,7 +405,45 @@ class KrylovSchur { std::cout << GridLogDebug << "b after Arnoldi " << b << std::endl; - nonHarmonicRestart(); + // --- Restart: Schur-decompose Rayleigh, reorder by ritzFilter, rotate + // the basis into the Schur vectors, and truncate to the leading Nk. + ComplexSchurDecomposition schur (Rayleigh, false, ritzFilter); + std::cout << GridLogDebug << "Schur decomp holds? " << schur.checkDecomposition() << std::endl; + + // Rearrange Schur matrix so wanted evals are on top left (like MATLAB's ordschur) + std::cout << GridLogMessage << "Reordering Schur eigenvalues" << std::endl; + schur.schurReorder(Nk); + std::cout << GridLogMessage << "Schur eigenvalues reordered." << std::endl; + + Eigen::MatrixXcd Q = schur.getMatrixQ(); + Qt = Q.adjoint(); // TODO should Q be real? + + std::cout << GridLogMessage << "*** ROTATING TO SCHUR BASIS *** " << std::endl; + + // Rotate Krylov basis, b vector, redefine Rayleigh quotient and evecs, and truncate. + Rayleigh = schur.getMatrixS(); + b = Q * b; // b^\dag = b^\dag * Q^\dag <==> b = Q*b + + std::vector basis2; + constructUR(basis2, basis, Qt, Nm,Nk); + + std::cout << GridLogMessage << "*** TRUNCATING FOR RESTART *** " << std::endl; + std::cout << GridLogDebug << "Rayleigh before truncation: " << std::endl << Rayleigh << std::endl; + + Eigen::MatrixXcd RayTmp = Rayleigh(Eigen::seqN(0, Nk), Eigen::seqN(0, Nk)); + Rayleigh = RayTmp; + + basis = std::vector (basis2.begin(), basis2.begin() + Nk); + + b = b.head(Nk).eval(); + + std::cout << GridLogDebug << "Rayleigh after truncation: " << std::endl << Rayleigh << std::endl; + + checkKSDecomposition(); + + // Compute eigensystem of Rayleigh. Note the eigenvectors correspond to the sorted eigenvalues. + computeEigensystem(Rayleigh); + std::cout << GridLogMessage << "Eigenvalues (first Nk sorted): " << std::endl << evals << std::endl; if (checkConvergedAndReport(i)) return; } @@ -433,7 +484,67 @@ class KrylovSchur { std::cout << GridLogDebug << "b after Arnoldi " << b << std::endl; - harmonicRestart(shiftVal); + // --- Harmonic restart: Schur-decompose the shift-augmented Rayleigh + // quotient so Ritz values close to shiftVal are reordered to the + // top, then rotate and truncate as in the non-harmonic case. + Eigen::MatrixXcd temp = Rayleigh; + for (int m=0;m b = Q*b - - std::vector basis2; - constructUR(basis2, basis, Qt, Nm,Nk); -// basis = basis2; - - std::cout << GridLogMessage << "*** TRUNCATING FOR RESTART *** " << std::endl; - std::cout << GridLogDebug << "Rayleigh before truncation: " << std::endl << Rayleigh << std::endl; - - Eigen::MatrixXcd RayTmp = Rayleigh(Eigen::seqN(0, Nk), Eigen::seqN(0, Nk)); - Rayleigh = RayTmp; - - std::vector basisTmp = std::vector (basis2.begin(), basis2.begin() + Nk); - basis = basisTmp; - - Eigen::VectorXcd btmp = b.head(Nk); - b = btmp; - - std::cout << GridLogDebug << "Rayleigh after truncation: " << std::endl << Rayleigh << std::endl; - - checkKSDecomposition(); - - // Compute eigensystem of Rayleigh. Note the eigenvectors correspond to the sorted eigenvalues. - computeEigensystem(Rayleigh); - std::cout << GridLogMessage << "Eigenvalues (first Nk sorted): " << std::endl << evals << std::endl; - } - - /** - * Harmonic restart step: Schur-decompose the shift-augmented Rayleigh quotient - * so Ritz values close to `shiftVal` are reordered to the top, then rotate and - * truncate exactly as in the non-harmonic case. - */ - void harmonicRestart(RealD shiftVal) { - Eigen::MatrixXcd temp = Rayleigh; - for (int m=0;m& UR, std::vector &U, Eigen::MatrixXcd& R, int N, int N2) { - Field tmp (Grid); + KSCoeffMat Rt(N); + for (int i = 0; i < N; i++) + for (int j = 0; j < N; j++) + Rt(i, j) = ComplexD(R(j, i).real(), R(j, i).imag()); - UR.clear(); - // UR.resize(N); - - std::cout << GridLogDebug << "R to rotate by (should be Rayleigh): " << R << std::endl; - - for (int i = 0; i < N; i++) { - tmp = Zero(); - if (i < N2) - for (int j = 0; j < N; j++) { - std::cout << GridLogDebug << "Adding R("<