Compare commits

...
Author SHA1 Message Date
Chulwoo Jung 01c3189d17 Reverted restart to inline in KrylovSchur
Making constructUR to use basisRotate()
2026-07-13 14:38:50 -04:00
Chulwoo Jung f1249685aa Refactording KrylovSchur to have separate operator() for non-shift and shift 2026-07-10 17:39:36 -04:00
Chulwoo Jung 722d68b0d9 Updating KrylovSchur example to include True Block KS 2026-07-10 14:02:22 -04:00
Chulwoo Jung c1284e080a Adding TrueHarmonicBlockKrylovSchur 2026-07-09 20:38:08 -04:00
Chulwoo Jung d2189d59e0 Adding parity flip and gamma 5 option for Block KS starting vector 2026-06-16 16:05:06 -04:00
Chulwoo Jung e862dd662b Commenting out debugging info 2026-05-26 15:28:17 -04:00
Chulwoo Jung 42ff77cec8 Merge branch 'KS_shifted' of github.com:chulwoo1/Grid into KS_shifted 2026-05-21 10:49:21 -04:00
Chulwoo Jung d11ad4d2f0 Change below threshold output cerr to force output from every node` 2026-05-21 10:48:23 -04:00
Chulwoo Jung 9ebf2fec79 SciDAC format outputs for below threshold plaquettes 2026-05-21 09:39:08 -04:00
Chulwoo Jung ab0d6d3706 Merge branch 'KS_shifted' of github.com:chulwoo1/Grid into KS_shifted 2026-05-08 18:54:47 -04:00
Chulwoo Jung 4aa96a16c5 Merge branch 'KS_shifted' of github.com:chulwoo1/Grid into KS_shifted 2026-05-08 18:54:39 -04:00
Chulwoo Jung bb99123e6d Adding threshold in Plaquette testing fft5d bugfix 2026-05-08 18:53:30 -04:00
Chulwoo Jung 9e8cba3944 Reinstating HarmonicBlockKS 2026-05-08 17:30:17 -04:00
Chulwoo Jung d5cab295b2 Still debugging Gamma5BlockLanczos, fft5d 2026-05-08 16:47:46 -04:00
Chulwoo Jung 5a67f0602c Broken 2026-05-01 19:28:38 -04:00
Chulwoo Jung d57c49d73d iChanging to 2 starting vectors 2026-05-01 17:44:07 -04:00
Chulwoo Jung 5754c965d5 More debugging outputs 2026-05-01 11:35:45 -04:00
Chulwoo Jung 64ee6e90c9 Still debugging Gamma5BlockLanczos restart 2026-04-25 02:38:40 -04:00
Chulwoo Jung 7752e7ead1 Sorting eval/evec according to RitzFilter 2026-04-23 16:28:48 -04:00
Chulwoo Jung f14be5e331 Implicit restart in Gamma5BlockLanczos 2026-04-22 23:42:41 -04:00
Chulwoo Jung 5a77a4a052 Adding verify() routines to gamma5BlockLanczos 2026-04-20 17:19:44 -04:00
Chulwoo Jung 83b752bdd3 First (Claude) try to add Gamma5BlockLanczos 2026-04-20 12:23:44 -04:00
Chulwoo Jung 34a6ba3475 Merge branch 'KS_shifted' of github.com:chulwoo1/Grid into KS_shifted 2026-04-17 21:01:02 -04:00
Chulwoo Jung 819f706778 Merge branch 'KS_shifted' of github.com:chulwoo1/Grid into KS_shifted 2026-04-17 16:35:17 -04:00
Chulwoo Jung 3b28e3c98f Added flags for explicit eval checks at the end of operator() 2026-04-17 11:08:34 -04:00
Chulwoo Jung a696953485 Finally Block Harmonic(?) KS working 2026-04-14 14:40:55 -04:00
Chulwoo Jung aecc50869c Another try at block Harmonic KS. Still not working 2026-04-09 21:13:41 -04:00
Chulwoo Jung 99be36c891 Fixing tests/core/Test_plaquette_stats 2026-04-08 23:37:26 -04:00
Chulwoo Jung af1fb41d55 Precision reduction in Block KS verify() for better readability
Adding Plaquette stat testing
2026-04-08 22:50:37 -04:00
Chulwoo Jung b4c77ca762 Commenting out files not checked in 2026-04-06 17:24:23 -04:00
Chulwoo Jung 82a8f1342e Adding Lanczos parameter file in examples directory 2026-04-06 13:35:43 -04:00
Chulwoo Jung d4b685cf2d Expanding debug output after restart in block KS 2026-04-06 12:00:33 -04:00
Chulwoo Jung 84707cc3a8 Changing definition of Nm and Nk in BlockKrylovSchur 2026-04-02 15:22:21 -04:00
Chulwoo Jung 88ea24687f Blocked->Block. SplitGridBlockKrylovSchur 2026-04-02 12:25:55 -04:00
Chulwoo Jung 167f94e86c Adding (Harmonic) Block KS 2026-03-31 12:36:18 -04:00
Chulwoo Jung 09aa843984 Changed batchedInnerProduct for portability 2026-03-17 18:54:18 -04:00
Chulwoo Jung 24752002fa Verbosity reduction batched inner product for reorthogonalization 2026-03-17 13:02:16 -04:00
Chulwoo Jung f3223021fd RestartedLanczosBidiagonalization seems to have been fixed 2026-03-16 14:34:56 -04:00
Chulwoo Jung 4e1d95d3bb Claude implementation of Thick Restarted Lanczos Bidiagonalization 2026-03-13 19:12:54 -04:00
Chulwoo Jung 2ed38f63ca Merge branch 'develop' of https://github.com/paboyle/Grid into KS_shifted 2026-03-12 10:49:21 -04:00
Chulwoo Jung 80d2a8d88d wqMerge branch 'develop' of https://github.com/paboyle/Grid into KS_shifted 2026-03-11 21:49:26 -04:00
Chulwoo Jung 2ac5431401 Turning of NERSC header checking 2026-03-06 14:16:20 -05:00
Chulwoo Jung 3e71cac7ae Merge branch 'specflow2' of github.com:chulwoo1/Grid into KS_shifted 2026-01-15 03:40:52 +00:00
Chulwoo Jung e8e7ef08fc KrylovSchur and spectral flow updates 2026-01-15 03:20:01 +00:00
Chulwoo Jung 5c00fe6bef Merge branch 'develop' of github.com:poare/Grid into KS_shifted 2026-01-12 06:26:15 +00:00
Chulwoo Jung 3175788f97 Added explicit shift before pulling 2026-01-12 06:25:09 +00:00
Patrick Oare 6f1788bb38 modified Givens rotation to implement a sparse multiplication 2026-01-06 16:19:48 -05:00
Chulwoo Jung dcda74f924 Timing info for schurReorder,etc 2025-12-18 18:23:50 +00:00
Chulwoo Jung df4c2a082b Reducing comments 2025-12-09 14:23:22 +00:00
Chulwoo Jung 88611659a3 Appear to be working 2025-12-08 21:08:14 -05:00
Chulwoo Jung 504b85dfc0 Restarting and adding codes back in 2025-12-08 13:27:06 -05:00
Chulwoo Jung 43ea83e5e1 Checking in to move back to genoa 2025-12-05 23:56:40 +00:00
Chulwoo Jung 376150c3df Adding 2025-12-04 21:29:31 -05:00
Chulwoo Jung 842e0391e7 Checking in to move back to aurora 2025-12-04 20:13:44 -05:00
Chulwoo Jung 17e3799bcc Necessary code for Harmonic KS added 2025-12-03 19:38:45 -05:00
Chulwoo Jung 985ab70f85 Checking in without adjusting Nk 2025-12-03 14:46:34 -05:00
Chulwoo Jung 1e85081986 Adding shift and debugging 2025-12-03 00:16:51 -05:00
Chulwoo Jung 3876fe5a29 Merge branch 'KrylovSchur' of github.com:chulwoo1/Grid into KS_shifted 2025-12-02 17:46:40 -05:00
Chulwoo Jung 6692425aa2 Checking in before pulling 2025-11-26 17:17:22 -05:00
Chulwoo Jung d5ac4fc67f Starting to modified KS 2025-11-26 22:13:27 +00:00
Chulwoo Jung 3538faf449 Starting Harmonic (shift and inverse) 2025-11-24 17:05:35 -05:00
Patrick Oare 0b457b9d52 fixed ritz estimate bug 2025-11-07 18:56:08 +00:00
Chulwoo Jung fe0ab5f1a9 Merge branch 'develop' of github.com:poare/Grid into develop 2025-11-07 15:50:22 +00:00
Chulwoo Jung caa66418bd Checking in before pulling 2025-11-06 22:44:05 +00:00
Chulwoo Jung 786496f22e Checking in before pulling KrylovSchur 2025-11-03 21:18:56 +00:00
Patrick R Oare 68af1bba67 commented some slow code out 2025-10-31 11:47:29 -04:00
Patrick Oare bf2a715ef7 bug in wilson eigenvectors: ritz estimates not equalling deviation from being an evec 2025-10-31 15:31:46 +00:00
Patrick Oare 4042ebf1bf added ImNorm to sort 2025-10-20 19:01:53 +00:00
Patrick R Oare 82f35001ff small bug fix for wilson spectrum since we're actually running DWF 2025-09-25 15:36:42 -04:00
Patrick Oare fa30c791aa updated wilson spec 2025-09-23 15:24:50 +00:00
Patrick Oare 612049f1dd commented out evec writer because it was taking up all the space on SDCC 2025-09-18 15:09:31 -04:00
Patrick Oare 0b92ef990c found bug in unprec DWF: was using |\cdot| in comparison for the eigenvalue sorting 2025-09-12 13:31:39 -04:00
Patrick Oare 82d411ca7b added inline to rf functions 2025-09-10 17:16:48 -04:00
Patrick Oare 597086a031 added wilson spectrum example 2025-09-10 15:41:00 -04:00
Patrick Oare b210ddf9a7 added commented out line to run un-preconditioned DWF 2025-09-09 15:14:11 -04:00
Patrick Oare c5d02e5799 updated RitzFilter enum and the input to run krylov schur 2025-09-09 13:02:11 -04:00
Patrick Oare 9dcd7ca761 added IO for evecs / evals 2025-09-08 12:59:48 -04:00
Chulwoo Jung c1e5ef9476 Adding config input 2025-08-15 20:52:36 +00:00
Patrick Oare 6fd71aea9d may have found bug 2025-08-15 12:13:01 -04:00
Patrick Oare a18b0d496c added more debug output 2025-08-15 11:51:15 -04:00
Patrick Oare 19f0737b98 trying one more thing 2025-08-14 14:47:38 -04:00
Patrick Oare 16d3c9cf75 added another debug feature 2025-08-14 14:37:49 -04:00
Patrick Oare 3b9fc72451 modified debug output slightly 2025-08-14 14:06:32 -04:00
Patrick Oare 99644f5d0a commented out arg assert in kryschur spec example 2025-08-13 14:57:57 -04:00
Chulwoo Jung 7780d88d26 Adding simple lanczos, boundary to specflow(!) 2025-08-06 23:41:53 +00:00
Chulwoo Jung 2bf9179d2c Adding mass step 2025-08-06 16:52:51 +00:00
Chulwoo Jung c606f5dca0 Move out src initialization for re-use / Adding antiperiodic BC 2025-08-06 16:51:14 +00:00
Patrick Oare 632f5916c7 small log change for KS 2025-08-04 15:43:23 -04:00
Patrick Oare 9057694895 added double orthog to KS 2025-08-04 15:30:18 -04:00
Patrick Oare 5e85aef19d added updates to GCR polynomial code 2025-07-31 16:42:35 -04:00
Patrick Oare 2b6d40c7e1 added example files 2025-07-31 16:41:28 -04:00
Patrick Oare 33b80c4e8e added eigensolver code for arnoldi and krylov schur 2025-07-31 16:40:24 -04:00
Chulwoo Jung 8419cc5c64 specflow evec I/O added, 2025-07-11 15:57:23 -04:00
Chulwoo Jung 2cc6deb8e0 Merge branch 'develop' of https://github.com/paboyle/Grid into ic2 2025-04-25 10:48:41 -04:00
Chulwoo Jung 19d0590579 Checking in for merging 2025-04-25 10:48:22 -04:00
39 changed files with 12273 additions and 79 deletions
+15
View File
@@ -55,6 +55,7 @@ NAMESPACE_CHECK(approx);
#include <Grid/algorithms/blas/MomentumProject.h>
NAMESPACE_CHECK(deflation);
#include <Grid/algorithms/iterative/ConjugateGradient.h>
//#include <Grid/algorithms/iterative/EigCG.h>
NAMESPACE_CHECK(ConjGrad);
#include <Grid/algorithms/iterative/BiCGSTAB.h>
NAMESPACE_CHECK(BiCGSTAB);
@@ -75,6 +76,7 @@ NAMESPACE_CHECK(BiCGSTAB);
#include <Grid/algorithms/iterative/FlexibleCommunicationAvoidingGeneralisedMinimalResidual.h>
#include <Grid/algorithms/iterative/MixedPrecisionFlexibleGeneralisedMinimalResidual.h>
#include <Grid/algorithms/iterative/ImplicitlyRestartedLanczos.h>
#include <Grid/algorithms/iterative/SimpleLanczos.h>
#include <Grid/algorithms/iterative/PowerMethod.h>
#include <Grid/algorithms/iterative/AdefGeneric.h>
#include <Grid/algorithms/iterative/AdefMrhs.h>
@@ -83,4 +85,17 @@ NAMESPACE_CHECK(PowerMethod);
NAMESPACE_CHECK(multigrid);
#include <Grid/algorithms/FFT.h>
#include <Grid/algorithms/iterative/KrylovSchur.h>
#include <Grid/algorithms/iterative/BlockKrylovSchur.h>
#include <Grid/algorithms/iterative/SplitGridBlockKrylovSchur.h>
#include <Grid/algorithms/iterative/HarmonicBlockKrylovSchur.h>
#include <Grid/algorithms/iterative/TrueHarmonicBlockKrylovSchur.h>
#include <Grid/algorithms/iterative/Gamma5BlockLanczos.h>
//#include <Grid/algorithms/iterative/Gamma5ScalarLanczos.h>
#include <Grid/algorithms/iterative/Arnoldi.h>
#include <Grid/algorithms/iterative/LanczosBidiagonalization.h>
#include <Grid/algorithms/iterative/RestartedLanczosBidiagonalization.h>
//#include <Grid/algorithms/iterative/GCR.h>
#include <Grid/algorithms/iterative/MultiSplittingPreconditionedCG.h>
#endif
+433
View File
@@ -0,0 +1,433 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/Arnoldi.h
Copyright (C) 2015
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: paboyle <paboyle@ph.ed.ac.uk>
Author: Patrick Oare <poare@bnl.gov>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#ifndef GRID_ARNOLDI_H
#define GRID_ARNOLDI_H
NAMESPACE_BEGIN(Grid);
//Moved to KrylovSchur
#if 0
/**
<<<<<<< HEAD
* Options for which Ritz values to keep in implicit restart.
*/
enum RitzFilter {
EvalNormSmall, // Keep evals with smallest norm
EvalNormLarge, // Keep evals with largest norm
EvalReSmall, // Keep evals with smallest real part
EvalReLarge // Keep evals with largest real part
};
// Select comparison function from RitzFilter
struct ComplexComparator
{
RitzFilter f;
ComplexComparator (RitzFilter _f) : f(_f) {}
bool operator()(std::complex<double> z1, std::complex<double> z2) {
switch (f) {
RealD tmp1, tmp2;
tmp1=std::abs(std::imag(z1));
tmp2=std::abs(std::imag(z2));
case EvalNormSmall:
return std::abs(z1) < std::abs(z2);
case EvalNormLarge:
return std::abs(z1) > std::abs(z2);
// Terrible hack
// return std::abs(std::real(z1)) < std::abs(std::real(z2));
// if ( std::abs(std::real(z1)) >4.) tmp1 +=1.;
// if ( std::abs(std::real(z2)) >4.) tmp2 +=1.;
case EvalReSmall:
return tmp1 < tmp2;
// return std::abs(std::imag(z1)) < std::abs(std::imag(z2));
case EvalReLarge:
return tmp1 > tmp2;
// return std::abs(std::real(z1)) > std::abs(std::real(z2));
default:
assert(0);
}
}
};
=======
>>>>>>> 68af1bba67dd62881ead5ab1e54962a5486a0791
#endif
/**
* Implementation of the Arnoldi algorithm.
*/
template<class Field>
class Arnoldi {
private:
std::string cname = std::string("Arnoldi");
int MaxIter; // Max iterations
RealD Tolerance;
RealD ssq;
RealD rtol;
int Nm; // Number of basis vectors to track (equals MaxIter if no restart)
int Nk; // Number of basis vectors to keep every restart (equals -1 if no restart)
int Nstop; // Stop after converging Nstop eigenvectors.
LinearOperatorBase<Field> &Linop;
GridBase *Grid;
RealD approxLambdaMax;
RealD beta_k;
Field f;
std::vector<Field> basis; // orthonormal Arnoldi basis
Eigen::MatrixXcd Hess; // Hessenberg matrix of size Nbasis (after construction)
Eigen::MatrixXcd Qt; // Transpose of basis rotation which projects out high modes.
Eigen::VectorXcd evals; // evals of Hess
Eigen::MatrixXcd littleEvecs; // Nm x Nm evecs matrix
std::vector<Field> evecs; // Vector of evec fields
RitzFilter ritzFilter; // how to sort evals
public:
Arnoldi(LinearOperatorBase<Field> &_Linop, GridBase *_Grid, RealD _Tolerance, RitzFilter filter = EvalReSmall)
: Linop(_Linop), Grid(_Grid), Tolerance(_Tolerance), ritzFilter(filter), f(_Grid), MaxIter(-1), Nm(-1), Nk(-1),
Nstop (-1), evals (0), evecs (), ssq (0.0), rtol (0.0), beta_k (0.0), approxLambdaMax (0.0)
{
f = Zero();
};
/**
* Runs the Arnoldi loop with(out) implicit restarting. For each iteration:
* - Runs an Arnoldi step.
* - Computes the eigensystem of the Hessenberg matrix.
* - Performs implicit restarting.
*/
void operator()(const Field& v0, int _maxIter, int _Nm, int _Nk, int _Nstop, bool doubleOrthog = false) {
MaxIter = _maxIter;
Nm = _Nm; Nk = _Nk;
Nstop = _Nstop;
ssq = norm2(v0);
RealD approxLambdaMax = approxMaxEval(v0);
rtol = Tolerance * approxLambdaMax;
ComplexComparator compareComplex (ritzFilter);
std::cout << GridLogMessage << "Comparing Ritz values with: " << ritzFilter << std::endl;
int start = 1;
Field startVec = v0;
littleEvecs = Eigen::MatrixXcd::Zero(Nm, Nm);
for (int i = 0; i < MaxIter; i++) {
std::cout << GridLogMessage << "Restart Iteration " << i << std::endl;
// Perform Arnoldi steps to compute Krylov basis and Rayleigh quotient (Hess)
arnoldiIteration(startVec, Nm, start, doubleOrthog);
startVec = f;
// compute eigensystem and sort evals
// compute_eigensystem();
compute_eigensystem(Hess);
std::cout << GridLogMessage << "Eigenvalues after Arnoldi step: " << std::endl << evals << std::endl;
std::sort(evals.begin(), evals.end(), compareComplex);
std::cout << GridLogMessage << "Ritz values after sorting (first Nk preserved): " << std::endl << evals << std::endl;
// SU(N)::tepidConfiguration
// Implicit restart to de-weight unwanted eigenvalues
implicitRestart(_Nm, _Nk); // probably can delete _Nm and _Nk from function args
start = Nk;
// check convergence and return if needed.
int Nconv = converged();
std::cout << GridLogMessage << "Number of evecs converged: " << Nconv << std::endl;
if (Nconv >= Nstop || i == MaxIter - 1) {
std::cout << GridLogMessage << "Converged with " << Nconv << " / " << Nstop << " eigenvectors on iteration "
<< i << "." << std::endl;
basisRotate(evecs, Qt, 0, Nk, 0, Nk, Nm);
std::cout << GridLogMessage << "Eigenvalues [first " << Nconv << " converged]: " << std::endl << evals << std::endl;
return;
}
}
}
/**
* Approximates the maximum eigenvalue of Linop.Op to normalize the residual and test for convergence.
*
* Parameters
* ----------
* Field& v0
* Source field to start with. Must have non-zero norm.
* int MAX_ITER (default = 50)
* Maximum number of iterations for power approximation.
*
* Returns
* -------
* RealD lamApprox
* Approximation of largest eigenvalue.
*/
RealD approxMaxEval(const Field& v0, int MAX_ITER = 50) {
assert (norm2(v0) > 1e-8); // must have relatively large source norm to start
RealD lamApprox = 0.0;
RealD denom = 1.0; RealD num = 1.0;
Field v0cp (Grid); Field tmp (Grid);
v0cp = v0;
denom = std::sqrt(norm2(v0cp));
for (int i = 0; i < MAX_ITER; i++) {
Linop.Op(v0cp, tmp); // CAREFUL: do not do Op(tmp, tmp)
v0cp = tmp;
num = std::sqrt(norm2(v0cp)); // num = |A^{n+1} v0|
lamApprox = num / denom; // lam = |A^{n+1} v0| / |A^n v0|
std::cout << GridLogDebug << "Approx for max eval: " << lamApprox << std::endl;
denom = num; // denom = |A^{n} v0|
}
return lamApprox;
}
/**
* Constructs the Arnoldi basis for the Krylov space K_n(D, src). (TODO make private)
*
* Parameters
* ----------
* v0 : Field&
* Source to generate Krylov basis.
* Nm : int
* Final size of the basis desired. If the basis becomes complete before a basis of size Nm is constructed
* (determined by relative tolerance Tolerance), stops iteration there.
* doubleOrthog : bool (default = false)
* Whether to double orthogonalize the basis (for numerical cancellations) or not.
* start : int (default = 0)
* If non-zero, assumes part of the Arnoldi basis has already been constructed.
*/
void arnoldiIteration(const Field& v0, int Nm, int start = 1, bool doubleOrthog = false)
{
ComplexD coeff;
Field w (Grid); // A acting on last Krylov vector.
if (start == 1) { // initialize everything that we need.
RealD v0Norm = 1 / std::sqrt(ssq);
basis.push_back(v0Norm * v0); // normalized source
Hess = Eigen::MatrixXcd::Zero(Nm, Nm);
f = Zero();
} else {
assert( start == basis.size() ); // should be starting at the end of basis (start = Nk)
Eigen::MatrixXcd HessCp = Hess;
Hess = Eigen::MatrixXcd::Zero(Nm, Nm);
Hess(Eigen::seqN(0, Nk), Eigen::seqN(0, Nk)) = HessCp;
}
// Construct next Arnoldi vector by normalizing w_i = Dv_i - \sum_j v_j h_{ji}
for (int i = start - 1; i < Nm; i++) {
Linop.Op(basis.back(), w);
for (int j = 0; j < basis.size(); j++) {
coeff = innerProduct(basis[j], w); // coeff = h_{ij}. Note that since {vi} is ONB it's OK to subtract it off after.
Hess(j, i) = coeff;
w -= coeff * basis[j];
}
if (doubleOrthog) {
// TODO implement
}
// add w_i to the pile
if (i < Nm - 1) {
coeff = std::sqrt(norm2(w));
Hess(i+1, i) = coeff;
basis.push_back(
(1.0/coeff) * w
);
}
// after iterations, update f and beta_k = ||f||
f = w; // make sure f is not normalized
beta_k = std::sqrt(norm2(f)); // beta_k = ||f_k|| determines convergence.
}
std::cout << GridLogMessage << "|f|^2 after Arnoldi step = " << norm2(f) << std::endl;
std::cout << GridLogDebug << "Computed Hessenberg matrix = " << std::endl << Hess << std::endl;
return;
}
/**
* Approximates the eigensystem of the linear operator by computing the eigensystem of
* the Hessenberg matrix. Assumes that the Hessenberg matrix has already been constructed (by
* calling the operator() function).
*
* TODO implement in parent class eventually.
*
* Parameters
* ----------
* Eigen::MatrixXcd& S
* Schur matrix (upper triangular) similar to original Rayleigh quotient.
*/
void compute_eigensystem(Eigen::MatrixXcd& S)
{
std::cout << GridLogMessage << "Computing eigenvalues." << std::endl;
evecs.clear();
Eigen::ComplexEigenSolver<Eigen::MatrixXcd> es;
es.compute(S);
evals = es.eigenvalues();
littleEvecs = es.eigenvectors();
// Convert evecs to lattice fields
for (int k = 0; k < evals.size(); k++) {
Eigen::VectorXcd vec = littleEvecs.col(k);
Field tmp (basis[0].Grid());
tmp = Zero();
for (int j = 0; j < basis.size(); j++) {
tmp = tmp + vec[j] * basis[j];
}
evecs.push_back(tmp);
}
std::cout << GridLogMessage << "Eigenvalues: " << std::endl << evals << std::endl;
}
/**
* Verifies the factorization DV = V^\dag H + f e^\dag with the last-computed
* V, H, f.
*/
// RealD verifyFactorization() {
// int k = basis.size(); // number of basis vectors, also the size of H.
// std::vector<Field> factorized (k, Zero());
// Field tmp (FGrid); tmp = Zero();
// for (int i = 0; i < basis.size(); i++) {
// Linop.Op(basis[i], tmp);
// }
// // basisRotate(basis, Q, 0, Nk, 0, Nk, Nm);
// // Linop.Op(, )
// }
/* Getters */
Eigen::MatrixXcd getHessenbergMat() { return Hess; }
Field getF() { return f; }
std::vector<Field> getBasis() { return basis; }
Eigen::VectorXcd getEvals() { return evals; }
std::vector<Field> getEvecs() { return evecs; }
/**
* Implements implicit restarting for Arnoldi. Assumes eigenvalues are sorted.
*
* Parameters
* ----------
* int _Nm
* Size of basis to keep (Hessenberg is MxM).
* int Nk
* Number of basis vectors to keep at each restart.
*/
void implicitRestart(int _Nm, int _Nk) {
assert ( _Nk <= _Nm );
Nm = _Nm; Nk = _Nk;
int Np = Nm - Nk; // keep Nk smallest (or largest, depends on sort function) evecs
std::cout << GridLogMessage << "Computing QR Factorizations." << std::endl;
Eigen::MatrixXcd Q = Eigen::MatrixXcd::Identity(Nm, Nm);
Eigen::MatrixXcd Qi (Nm, Nm);
Eigen::MatrixXcd R (Nm, Nm);
for (int i = Nk; i < Nm; i++) { // keep the first Nk eigenvalues and iterate through the last Np. Should loop Np times
// Useful debugging output
std::cout << GridLogDebug << "Computing QR factorization for i = " << i << std::endl;
std::cout << GridLogDebug << "Eval shift = " << evals[i] << std::endl;
std::cout << GridLogDebug << "Hess before rotation: " << Hess << std::endl;
// QR factorize
Eigen::HouseholderQR<Eigen::MatrixXcd> QR (Hess - evals[i] * Eigen::MatrixXcd::Identity(Nm, Nm));
Qi = QR.householderQ();
Q = Q * Qi;
Hess = Qi.adjoint() * Hess * Qi;
std::cout << GridLogDebug << "Qt up to i = " << Q.transpose() << std::endl;
}
std::cout << GridLogDebug << "Hess after all rotations: " << std::endl << Hess << std::endl;
// form Arnoldi vector f: f is normal to the basis vectors and its norm \beta is used to determine the Ritz estimate.
std::complex<double> beta = Hess(Nk, Nk-1);
std::complex<double> sigma = Q(Nm-1, Nk-1);
f = basis[Nk] * beta + f * sigma;
RealD betak = std::sqrt(norm2(f));
std::cout << GridLogMessage << "|f|^2 after implicit restart = " << norm2(f) << std::endl;
// Rotate basis by Qt
Qt = Q.transpose();
basisRotate(basis, Qt, 0, Nk + 1, 0, Nm, Nm);
// rotate
basisRotate(evecs, Qt, 0, Nk + 1, 0, Nm, Nm);
// Truncate the basis and restart
basis = std::vector<Field> (basis.begin(), basis.begin() + Nk);
// evecs = std::vector<Field> (evecs.begin(), evecs.begin() + Nk);
Hess = Hess(Eigen::seqN(0, Nk), Eigen::seqN(0, Nk));
std::cout << "evecs size: " << evecs.size() << std::endl;
}
/**
* Computes the number of Arnoldi eigenvectors that have converged. An eigenvector s is considered converged
* for a tolerance epsilon if
* r(s) := |\beta e_m^T s| < epsilon
* where beta is the norm of f_{m+1}.
*
* Parameters
* ----------
*
* Returns
* -------
* int : Number of converged eigenvectors.
*/
int converged() {
int Nconv = 0;
for (int k = 0; k < evecs.size(); k++) {
RealD emTs = std::abs(littleEvecs(Nm - 1, k)); // e_m^T s
RealD ritzEstimate = beta_k * emTs;
// TODO should be ritzEstimate < Tolerance * lambda_max
std::cout << GridLogMessage << "Ritz estimate for evec " << k << " = " << ritzEstimate << std::endl;
if (ritzEstimate < rtol) {
Nconv++;
}
}
return Nconv;
}
};
NAMESPACE_END(Grid);
#endif
@@ -0,0 +1,878 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/BlockKrylovSchur.h
Copyright (C) 2015
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: Chulwoo Jung <chulwoo@bnl.gov>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#ifndef GRID_BLOCKED_KRYLOV_SCHUR_H
#define GRID_BLOCKED_KRYLOV_SCHUR_H
#include <iomanip>
#include <numeric>
NAMESPACE_BEGIN(Grid);
/**
* Block (block-Arnoldi) restarted Krylov-Schur eigensolver for
* general non-Hermitian operators.
*
* Algorithm
* ---------
* Uses a block Arnoldi factorisation of block size Nblock:
*
* A V_k = V_k H_k + F_k B_k^dag
*
* where
* V_k = Nm orthonormal basis vectors (stored flat in basis[])
* H_k = Nm x Nm upper block-Hessenberg Rayleigh quotient
* F_k = Nblock residual vectors (the next block beyond V_k)
* B_k = Nm x Nblock coupling matrix (non-zero only in last Nblock rows)
*
* Each block Arnoldi step applies A to each of the Nblock vectors in the
* current block, orthogonalises against all previous basis vectors, and
* reduces the residual block to upper-triangular form via Householder QR
* (implemented here as modified Gram-Schmidt within the block).
*
* The restart is a thick restart via the Schur decomposition of H_k:
* H_k = Q^dag S Q
* The leading Nk Schur vectors (chosen by RitzFilter) are retained,
* the basis and Rayleigh quotient are truncated, and block Arnoldi continues
* from the (Nk/Nblock)-th block.
*
* Parameters
* ----------
* Nblock : block size p
* Nm : total Krylov dimension (must be divisible by Nblock)
* Nk : total vectors to keep after each restart (must be divisible by Nblock, Nk < Nm)
* Nstop : declare convergence when this many eigenpairs have converged
* MaxIter : maximum number of outer (restart) iterations
* Tolerance : relative convergence tolerance (||r|| < Tolerance * |lambda_max|)
*/
template<class Field>
class BlockKrylovSchur {
protected:
//--------------------------------------------------------------------
// Types
//--------------------------------------------------------------------
typedef Eigen::MatrixXcd CMat;
typedef Eigen::VectorXcd CVec;
//--------------------------------------------------------------------
// Parameters (set by operator())
//--------------------------------------------------------------------
int Nblock; // block size
int Nm; // total Krylov dimension (multiple of Nblock)
int Nk; // total vectors retained after restart (multiple of Nblock)
int Nstop;
int MaxIter;
RealD Tolerance;
//--------------------------------------------------------------------
// Internal state
//--------------------------------------------------------------------
LinearOperatorBase<Field>& Linop;
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<Field> basis;
// Rayleigh quotient Nm x Nm
CMat H;
// Residual block: Nblock vectors (the (Nm/Nblock+1)-th block, unnormalised before
// QR; normalised and orthogonalised as part of block Arnoldi)
std::vector<Field> F;
// Coupling matrix B: Nm x Nblock.
// In exact arithmetic only the last Nblock rows are non-zero:
// B(Nm - Nblock + t, s) = H_{Nm/Nblock+1, Nm/Nblock}(t, s) (the subdiagonal block)
// We keep it as a full matrix for generality after restarts.
CMat B;
RealD beta_k; // Frobenius norm of the last subdiagonal block
RealD rtol; // absolute tolerance = Tolerance * approxLambdaMax
// Output
CVec evals;
CMat littleEvecs; // Nm columns
std::vector<RealD> ritzEstimates;
public:
std::vector<Field> evecs;
bool doEvalCheck = false;
// When true (and Nblock even), only Nblock/2 starting vectors are required.
// The remaining Nblock/2 slots are filled by pairing each supplied vector
// with its parity-flipped partner (sign negated on odd checkerboard sites).
bool useParityFlip = false;
// Same pairing behaviour but the partner is produced by gamma5Func(v).
// gamma5Func must be set by the caller before operator() is called.
// It is a std::function so that BlockKrylovSchur.h does not need to know
// about the Grid QCD Gamma class (which is defined at a later include layer).
bool useGamma5 = false;
std::function<void(const Field&, Field&)> gamma5Func;
//--------------------------------------------------------------------
// Constructor
//--------------------------------------------------------------------
BlockKrylovSchur(LinearOperatorBase<Field>& _Linop, GridBase* _Grid,
RealD _Tolerance, RitzFilter _rf = EvalReSmall)
: Linop(_Linop), Grid_(_Grid), Tolerance(_Tolerance), ritzFilter(_rf),
Nblock(-1), Nm(-1), Nk(-1), Nstop(-1), MaxIter(-1),
beta_k(0.0), rtol(0.0)
{}
virtual ~BlockKrylovSchur() = default;
//--------------------------------------------------------------------
// Main entry point
//--------------------------------------------------------------------
/**
* Run the blocked Krylov-Schur algorithm.
*
* Parameters
* ----------
* v0 : block of Nblock starting vectors (size >= Nblock)
* _maxIter : maximum outer (restart) iterations
* _Nm : total Krylov dimension (must be divisible by _Nblock)
* _Nk : total vectors to keep after restart (must be divisible by _Nblock, _Nk < _Nm)
* _Nstop : stop after _Nstop eigenvalues converged
* _Nblock : block size
*/
virtual void operator()(const std::vector<Field>& v0, int _maxIter, int _Nm, int _Nk,
int _Nstop, int _Nblock = 1, bool doubleOrthog = true,
bool doVerify = false)
{
MaxIter = _maxIter;
Nm = _Nm;
Nk = _Nk;
Nstop = _Nstop;
Nblock = _Nblock;
{
int divisor = 1;
if (useParityFlip) divisor *= 2;
if (useGamma5) divisor *= 2;
assert(Nblock % divisor == 0 && (int)v0.size() >= Nblock / divisor);
std::cout << GridLogMessage << className << ": divisor = " << divisor << std::endl;
}
assert(Nm % Nblock == 0);
assert(Nk % Nblock == 0);
assert(Nk < Nm);
if (useGamma5) assert(gamma5Func && "useGamma5: gamma5Func must be set");
preRun(); // hook: derived classes add parameter assertions here
int N = Nm; // total Krylov dimension
// Approximate largest eigenvalue for tolerance normalisation
RealD approxLambdaMax = approxMaxEval(v0[0]);
rtol = Tolerance * approxLambdaMax;
std::cout << GridLogMessage << className << ": approx max eval = "
<< approxLambdaMax << ", rtol = " << rtol << std::endl;
// Initialise
H = CMat::Zero(N, N);
B = CMat::Zero(N, Nblock);
int start = 0;
std::vector<Field> startBlock = expandStartBlock(v0);
for (int iter = 0; iter < MaxIter; iter++) {
std::cout << GridLogMessage << className << ": restart iteration " << iter << std::endl;
// ---- Block Arnoldi: extend from block start to block Nm/Nblock ----
blockArnoldiIteration(startBlock, Nm/Nblock, start, doubleOrthog);
// After first full cycle start from block Nk/Nblock
start = Nk/Nblock;
if (doVerify) {
std::string lbl = "iter " + std::to_string(iter) + " after Arnoldi";
verify(lbl);
}
// ---- Restart rotation: compute Q and the rotated+reordered Rayleigh
// quotient Hnew. Base class uses the plain Schur decomposition of
// H; derived classes (e.g. HarmonicBlockKrylovSchur) override this
// to target a shift.
CMat Q, Hnew;
restartRotation(Q, Hnew);
CMat Qt = Q.adjoint();
// Rotate Krylov basis: basis_new[i] = sum_j basis[j] * Qt(j,i)
std::vector<Field> basis2;
constructUR(basis2, basis, Qt, N);
basis = basis2;
// Update B and H
B = Q * B;
H = Hnew;
// ---- Truncate to Nk ----
int Nkeep = Nk;
CMat Htmp = H(Eigen::seqN(0, Nkeep), Eigen::seqN(0, Nkeep));
H = CMat::Zero(N, N);
H(Eigen::seqN(0, Nkeep), Eigen::seqN(0, Nkeep)) = Htmp;
std::vector<Field> basisTmp(basis.begin(), basis.begin() + Nkeep);
basis = basisTmp;
CMat Btmp = B(Eigen::seqN(0, Nkeep), Eigen::all);
B = CMat::Zero(N, Nblock);
B(Eigen::seqN(0, Nkeep), Eigen::all) = Btmp;
// beta_k = Frobenius norm of the effective coupling
beta_k = Btmp.norm();
std::cout << GridLogMessage << className << ": beta_k = " << beta_k << std::endl;
// Restart: the new starting block is F (the residual block from Arnoldi)
startBlock = F;
if (doVerify) {
std::string lbl = "iter " + std::to_string(iter) + " after restart+truncation";
verify(lbl);
}
// ---- Compute eigensystem of truncated H for convergence check ----
CMat Hk = H(Eigen::seqN(0, Nkeep), Eigen::seqN(0, Nkeep));
computeEigensystem(Hk, Nkeep);
int Nconv = converged(Nkeep);
std::cout << GridLogMessage << className << ": converged " << Nconv
<< " / " << Nstop << std::endl;
if (Nconv >= Nstop || iter == MaxIter - 1) {
std::cout << GridLogMessage << className << ": done after " << iter
<< " restarts, " << Nconv << " converged." << std::endl;
std::cout << GridLogMessage << "Eigenvalues: " << evals << std::endl;
if (doEvalCheck) {
Field w(Grid_);
for (int k = 0; k < (int)evecs.size(); k++) {
Linop.Op(evecs[k], w);
ComplexD eval_est = toStdCmplx(innerProduct(evecs[k], w));
w -= eval_est * evecs[k];
RealD res = std::sqrt(norm2(w));
std::cout << GridLogMessage << className << ": evec[" << k << "]"
<< " eval_reported = " << evals[k]
<< " eval_est = " << eval_est
<< " || A v - eval_est * v || = " << res << std::endl;
}
}
return;
}
}
}
// Accessors
std::vector<Field> getEvecs() { return evecs; }
CVec getEvals() { return evals; }
std::vector<RealD> getRitzEstimates() { return ritzEstimates; }
// Negate the field on all odd-parity (checkerboard) sites.
// Uses pickCheckerboard/setCheckerboard so the SIMD layout is handled
// correctly and the negation runs in parallel.
static void parityFlippedField(const Field& v, Field& out) {
GridCartesian* cgrid = dynamic_cast<GridCartesian*>(v.Grid());
assert(cgrid != nullptr);
GridRedBlackCartesian rbgrid(cgrid);
Field veven(&rbgrid), vodd(&rbgrid);
pickCheckerboard(Even, veven, v);
pickCheckerboard(Odd, vodd, v);
vodd = -vodd;
setCheckerboard(out, veven);
setCheckerboard(out, vodd);
}
//--------------------------------------------------------------------
// Verification: print H and B, check A V = V H + F B^dag explicitly
//--------------------------------------------------------------------
/**
* Checks the block Arnoldi / Krylov-Schur decomposition
*
* A V = V H + F B^dag (KS)
*
* by explicit operator applications. For each basis vector j:
*
* w_j = A basis[j]
*
* The nBasis × nBasis matrix M of inner products is computed:
*
* M[i, j] = <basis[i] | A basis[j]>
*
* and compared column-by-column against H. Separately, the nBasis × Nblock
* residual coupling matrix R is computed:
*
* R[j, t] = <basis[j] | F[t]> * ||F[t]|| (scaled by F-block norms)
*
* but since F is already normalised, R[j,t] = <basis[j] | F[t]>.
*
* The KS relation for column j reads:
* w_j = sum_i basis[i] H[i,j] + sum_t F[t] B[j,t]*
* so the deviation in column j is
* dev_j = w_j - sum_i basis[i] M[i,j] (should be zero for exact arithmetic)
* augmented by the F B^dag term in the last block.
*
* Prints:
* - H (current Rayleigh quotient, nBasis × nBasis)
* - B (coupling matrix, nBasis × Nblock)
* - M (explicit inner product matrix <V | A V>)
* - max |H[i,j] - M[i,j]| (should be O(machine epsilon))
* - for each basis column j: || A v_j - V H[:,j] - F B[j,:]^* ||
*
* Parameters
* ----------
* label : string printed at the start (e.g. "after restart 2")
*/
void verify(const std::string& label = "")
{
int nBasis = (int)basis.size();
int nF = (int)F.size();
if (nBasis == 0) {
std::cout << GridLogMessage
<< className << "::verify [" << label
<< "]: basis is empty." << std::endl;
return;
}
// Full allocated dimension of H (= Nm); may be larger than nBasis after restart+truncation
int Nfull = (int)H.rows();
std::cout << GridLogMessage
<< "======== " << className << "::verify [" << label << "] ========" << std::endl;
std::cout << GridLogMessage
<< " nBasis = " << nBasis << " Nfull = " << Nfull
<< " Nblock = " << Nblock << " nF = " << nF << std::endl;
// ---- Print H (full Nm x Nm matrix) ----
// After restart+truncation nBasis < Nfull; entries outside [0:nBasis,0:nBasis]
// should be ~0 — printing the full matrix makes this visible.
std::cout << GridLogMessage << "H (" << Nfull << " x " << Nfull << "):" << std::endl;
for (int i = 0; i < Nfull; i++) {
for (int j = 0; j < Nfull; j++)
std::cout << " " << std::setprecision(4) << std::setw(14) << H(i, j);
std::cout << std::endl;
}
// ---- Print B (full Nm x Nblock matrix) ----
std::cout << GridLogMessage << "B (" << Nfull << " x " << nF << "):" << std::endl;
for (int i = 0; i < Nfull; i++) {
for (int t = 0; t < nF; t++)
std::cout << " " << std::setprecision(4) << std::setw(14) << B(i, t);
std::cout << std::endl;
}
// ---- Compute M[i,j] = <basis[i] | A basis[j]> ----
CMat M = CMat::Zero(nBasis, nBasis);
Field w(Grid_);
for (int j = 0; j < nBasis; j++) {
Linop.Op(basis[j], w);
for (int i = 0; i < nBasis; i++)
M(i, j) = toStdCmplx(innerProduct(basis[i], w));
}
std::cout << GridLogMessage << "M = <V|AV> (" << nBasis << " x " << nBasis << "):" << std::endl;
for (int i = 0; i < nBasis; i++) {
for (int j = 0; j < nBasis; j++)
std::cout << " " << std::setprecision(4) << std::setw(14) << M(i, j);
std::cout << std::endl;
}
// ---- max |H - M| (for the nBasis x nBasis Rayleigh-quotient block) ----
RealD maxHM = 0.0;
for (int i = 0; i < nBasis; i++)
for (int j = 0; j < nBasis; j++)
maxHM = std::max(maxHM, std::abs(H(i,j) - M(i,j)));
std::cout << GridLogMessage
<< " max |H[i,j] - M[i,j]| (i,j < nBasis) = " << maxHM << std::endl;
// ---- Check entries of H OUTSIDE the nBasis x nBasis block ----
// These live in rows i >= nBasis or columns j >= nBasis.
// They must be ~0 after restart+truncation (before the coupling rows are
// re-filled by the next blockArnoldiIteration call).
// During a full Arnoldi run (nBasis == Nfull) this loop is a no-op.
RealD maxOutside = 0.0;
for (int i = 0; i < Nfull; i++)
for (int j = 0; j < Nfull; j++)
if (i >= nBasis || j >= nBasis)
maxOutside = std::max(maxOutside, std::abs(H(i,j)));
if (Nfull > nBasis)
std::cout << GridLogMessage
<< " max |H[i,j]| outside [0:" << nBasis << ",0:" << nBasis
<< "] block = " << maxOutside
<< " (should be ~0 after restart+truncation)" << std::endl;
// ---- Check orthonormality of basis ----
CMat G = CMat::Zero(nBasis, nBasis);
for (int i = 0; i < nBasis; i++)
for (int j = 0; j < nBasis; j++)
G(i, j) = toStdCmplx(innerProduct(basis[i], basis[j]));
CMat Gerr = G - CMat::Identity(nBasis, nBasis);
std::cout << GridLogMessage
<< " max |<V_i|V_j> - delta_ij| = " << Gerr.cwiseAbs().maxCoeff() << std::endl;
// ---- Per-column residual: || A v_j - V H[:,j] - F B[j,:]^* || ----
// For each basis vector j, compute A v_j then subtract V H[:,j] and F B[j,:]^*
RealD maxColDev = 0.0;
for (int j = 0; j < nBasis; j++) {
Linop.Op(basis[j], w);
// subtract V H[:,j]
for (int i = 0; i < nBasis; i++)
w -= basis[i] * H(i, j);
// subtract F B[j,:]^* (F[t] * conj(B[j,t]))
for (int t = 0; t < nF; t++)
w -= F[t] * std::conj(B(j, t));
RealD dev = std::sqrt(norm2(w));
std::cout << GridLogMessage
<< " || A v[" << j << "] - V H[:,j] - F B[j,:]* || = " << dev << std::endl;
maxColDev = std::max(maxColDev, dev);
}
std::cout << GridLogMessage
<< " max column deviation = " << maxColDev << std::endl;
// ---- Check F block orthogonality against basis ----
if (nF > 0) {
RealD maxFV = 0.0;
for (int t = 0; t < nF; t++)
for (int i = 0; i < nBasis; i++) {
RealD ip = std::abs(toStdCmplx(innerProduct(basis[i], F[t])));
maxFV = std::max(maxFV, ip);
}
std::cout << GridLogMessage
<< " max |<V_i | F_t>| (should be ~0) = " << maxFV << std::endl;
}
std::cout << GridLogMessage
<< "======== end verify ========" << std::endl;
}
//--------------------------------------------------------------------
// Extension hooks for derived classes
//--------------------------------------------------------------------
/**
* Apply the operator (or poly-filtered operator) to the current Arnoldi
* block and return the results in W.
*
* Base implementation: W[t] = Linop.Op(basis[kBase + t]).
* Derived classes (e.g. SplitGridBlockKrylovSchur) override this to
* apply poly(A) via Grid_split / Grid_unsplit instead.
*/
virtual void applyBlock(std::vector<Field>& W, int kBase)
{
for (int t = 0; t < Nblock; t++)
Linop.Op(basis[kBase + t], W[t]);
}
/**
* Called once at the start of operator(), after parameters are set.
* Base implementation is a no-op; derived classes override to add
* parameter assertions (e.g. Nblock % mrhs == 0).
*/
virtual void preRun() {}
/**
* 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<Field> expandStartBlock(const std::vector<Field>& v0)
{
int divisor = (useParityFlip ? 2 : 1) * (useGamma5 ? 2 : 1);
std::vector<Field> startBlock;
startBlock.reserve(Nblock);
for (int i = 0; i < Nblock / divisor; i++) {
std::vector<Field> 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
//--------------------------------------------------------------------
/**
* Extends the block Arnoldi factorisation from block index 'start' to
* block index 'Nm'.
*
* On entry (start > 0): basis[0..start*Nblock-1] already set,
* H[0..start*Nblock-1, 0..start*Nblock-1] already set,
* B[start*Nblock-1, :] set (coupling from prior residual block).
* startBlock = the normalised residual block F from the previous cycle.
*
* On entry (start == 0): initialises everything from startBlock.
*/
void blockArnoldiIteration(std::vector<Field>& startBlock, int endBlock,
int startIdx, bool doubleOrthog)
{
int N = Nm;
if (startIdx == 0) {
basis.clear();
F.clear();
H = CMat::Zero(N, N);
B = CMat::Zero(N, Nblock);
// Orthonormalise starting block via modified Gram-Schmidt
std::vector<Field> V0 = startBlock;
blockOrthonormalise(V0);
for (auto& v : V0) basis.push_back(v);
} else {
// Append residual block (startBlock = F_old) to basis.
// The truncated KS relation after restart is:
//
// A V_k = V_k S_k + F_old B_old^dag (*)
//
// where V_k = basis[0:Nkeep], S_k is stored in H[0:Nkeep,0:Nkeep],
// B_old = B[0:Nkeep,:], F_old = startBlock.
//
// Once F_old is appended as basis[Nkeep:Nkeep+Nblock], (*) becomes
// a statement about the extended H matrix:
//
// H[Nkeep+t, j] = (B_old^dag)[t,j] = conj(B_old[j,t])
// for t=0..Nblock-1, j=0..Nkeep-1
//
// These entries are the "restart coupling rows" that connect the new
// block to all retained Schur vectors and must be set before Arnoldi
// continues, otherwise A V_k = V_k H[:,0:Nkeep] would be missing the
// F_old B_old^dag term for those columns.
int Nkeep = startIdx * Nblock;
for (auto& v : startBlock) basis.push_back(v);
// Fill restart coupling rows into H
for (int t = 0; t < Nblock; t++)
for (int j = 0; j < Nkeep; j++)
H(Nkeep + t, j) = std::conj(B(j, t));
// Zero out B for the retained columns now that the coupling is in H
for (int j = 0; j < Nkeep; j++)
for (int t = 0; t < Nblock; t++)
B(j, t) = 0.0;
}
// Main block Arnoldi loop
for (int k = startIdx; k < endBlock; k++) {
blockArnoldiStep(k, doubleOrthog);
}
}
//--------------------------------------------------------------------
/**
* One block Arnoldi step: extends by one block (Nblock vectors).
*
* Computes block column k of H and the next basis block V_{k+1}.
*
* Layout of basis (flat):
* basis[j*Nblock + t] = t-th vector of j-th block, j = 0..k
*
* After this call:
* H[i, k*Nblock : (k+1)*Nblock] filled for i = 0..(k+1)*Nblock - 1
* basis[k*Nblock .. (k+1)*Nblock - 1] normalised (already set on entry)
* F = residual block (to become V_{k+1} after this step if k < Nm-1)
*
* If k < Nm-1, also:
* H[(k+1)*Nblock : (k+2)*Nblock, k*Nblock : (k+1)*Nblock] = subdiag block (from QR of residual)
* basis extended by Nblock (the normalised residual vectors)
*/
void blockArnoldiStep(int k, bool doubleOrthog)
{
int kBase = k * Nblock; // first flat index of current block
int prevN = kBase + Nblock; // number of basis vectors so far after this step
int N = Nm;
// W[t] = op(basis[kBase + t]) — dispatches to applyBlock() virtual
std::vector<Field> W(Nblock, Field(Grid_));
applyBlock(W, kBase);
// Orthogonalise W against all current basis vectors (full reorthogonalisation)
// H[i, kBase + t] = <basis[i] | W[t]>
for (int pass = 0; pass < (doubleOrthog ? 2 : 1); pass++) {
for (int i = 0; i < prevN; i++) {
for (int t = 0; t < Nblock; t++) {
ComplexD coeff = innerProduct(basis[i], W[t]);
if (pass == 0)
H(i, kBase + t) = toStdCmplx(coeff);
else
H(i, kBase + t) += toStdCmplx(coeff);
W[t] -= coeff * basis[i];
}
}
}
// Store residual block F
F = W;
if (k == Nm/Nblock - 1) {
// Last block: compute coupling matrix B for KS decomp.
//
// blockQR modifies F in-place (F → Q orthonormal) and returns R
// such that W_orig[t] = sum_s F[s] * R[s,t] (W_orig = F_after * R).
//
// The KS relation for column j = kBase+t requires the coefficient of F[s]
// to be (B†)[s,j] = conj(B[j,s]). Matching with R[s,t]:
// conj(B[kBase+t, s]) = R[s,t] → B[kBase+t, s] = conj(R[s,t])
//
// Equivalently the last Nblock rows of B are R^H (Hermitian conjugate of R).
// Note: for Nblock=1, R is scalar real positive, so this reduces to B = R. ✓
CMat R = blockQR(F); // F is modified in-place to become Q; returns R
for (int t = 0; t < Nblock; t++)
for (int s = 0; s < Nblock; s++)
B(kBase + t, s) = std::conj(R(s, t)); // B_block = R^H
beta_k = R.norm();
return;
}
// Not last block: QR-decompose residual to get V_{k+1}
CMat R = blockQR(F); // F orthonormalised in-place, R is upper triangular
// Subdiagonal block of H: H[(k+1)*Nblock : (k+2)*Nblock, kBase : kBase+Nblock] = R
int nextBase = (k + 1) * Nblock;
for (int i = 0; i < Nblock; i++)
for (int j = 0; j < Nblock; j++)
H(nextBase + i, kBase + j) = R(i, j);
// Append normalised residual block to basis
for (int t = 0; t < Nblock; t++)
basis.push_back(F[t]);
}
//--------------------------------------------------------------------
// Block QR via modified Gram-Schmidt within the block
//--------------------------------------------------------------------
/**
* Given a block of Nblock vectors W (not necessarily orthonormal),
* orthonormalises them in-place and returns the upper-triangular R
* such that W_in = W_out * R.
*
* Handles (near-)linear dependence by zeroing vectors below threshold.
*/
CMat blockQR(std::vector<Field>& W)
{
CMat R = CMat::Zero(Nblock, Nblock);
const RealD deflThresh = 1e-14;
for (int j = 0; j < Nblock; j++) {
// Orthogonalise W[j] against W[0..j-1]
for (int i = 0; i < j; i++) {
ComplexD coeff = innerProduct(W[i], W[j]);
R(i, j) = toStdCmplx(coeff);
W[j] -= coeff * W[i];
}
RealD nrm = std::sqrt(norm2(W[j]));
R(j, j) = nrm;
if (nrm > deflThresh) {
W[j] *= (1.0 / nrm);
} else {
// deflation: zero this vector
W[j] = Zero();
std::cout << GridLogMessage
<< className << ": deflation at block column " << j
<< " (norm = " << nrm << ")" << std::endl;
}
}
return R;
}
//--------------------------------------------------------------------
// Orthonormalise a block against itself (no prior basis)
//--------------------------------------------------------------------
void blockOrthonormalise(std::vector<Field>& V)
{
for (int j = 0; j < (int)V.size(); j++) {
for (int i = 0; i < j; i++) {
ComplexD c = innerProduct(V[i], V[j]);
V[j] -= c * V[i];
}
RealD nrm = std::sqrt(norm2(V[j]));
assert(nrm > 1e-14);
V[j] *= (1.0 / nrm);
}
}
//--------------------------------------------------------------------
// Basis rotation: UR[i] = sum_j U[j] * R(j, i)
//--------------------------------------------------------------------
void constructUR(std::vector<Field>& UR, std::vector<Field>& U,
CMat& R, int N)
{
UR.clear();
Field tmp(Grid_);
for (int i = 0; i < N; i++) {
tmp = Zero();
for (int j = 0; j < N; j++)
tmp += U[j] * R(j, i);
UR.push_back(tmp);
}
}
//--------------------------------------------------------------------
// Eigensystem of the truncated Rayleigh quotient
//--------------------------------------------------------------------
void computeEigensystem(CMat& Hk, int Nkeep)
{
Eigen::ComplexEigenSolver<CMat> es;
es.compute(Hk);
// Sort to match schurReorder ordering.
int n = es.eigenvalues().size();
ComplexComparator cComp(ritzFilter);
std::vector<int> idx(n);
std::iota(idx.begin(), idx.end(), 0);
std::sort(idx.begin(), idx.end(), [&](int a, int b){
return cComp(toStdCmplx(es.eigenvalues()(a)), toStdCmplx(es.eigenvalues()(b)));
});
evals.resize(n);
littleEvecs.resize(n, n);
for (int k = 0; k < n; k++) {
evals(k) = es.eigenvalues()(idx[k]);
littleEvecs.col(k) = es.eigenvectors().col(idx[k]);
}
evecs.clear();
for (int k = 0; k < Nkeep; k++) {
CVec vec = littleEvecs.col(k);
Field tmp(Grid_);
tmp = Zero();
for (int j = 0; j < (int)basis.size() && j < Nkeep; j++)
tmp += vec[j] * basis[j];
evecs.push_back(tmp);
}
}
//--------------------------------------------------------------------
// Convergence check
//--------------------------------------------------------------------
/**
* An eigenpair (lambda, y) is converged if the Ritz estimate
* r = || B^dag y ||
* satisfies r < rtol. Here B is the (Nkeep x Nblock) coupling matrix
* and y is the little eigenvector (Nkeep-vector) of H.
*/
int converged(int Nkeep)
{
ritzEstimates.clear();
int Nconv = 0;
CMat Bk = B(Eigen::seqN(0, Nkeep), Eigen::all); // Nkeep x Nblock
for (int k = 0; k < Nkeep; k++) {
CVec yk = littleEvecs.col(k); // Nkeep-vector
CVec Bty = Bk.adjoint() * yk; // Nblock-vector
RealD res = Bty.norm();
ritzEstimates.push_back(res);
std::cout << GridLogMessage << className << ": Ritz estimate[" << k
<< "] = " << res << " eval = " << evals[k] << std::endl;
if (res < rtol) Nconv++;
else break;
}
return Nconv;
}
//--------------------------------------------------------------------
// Approximate maximum eigenvalue via power iteration
//--------------------------------------------------------------------
RealD approxMaxEval(const Field& v0, int MAX_ITER = 50)
{
assert(norm2(v0) > 1e-8);
RealD lam = 0.0, denom = std::sqrt(norm2(v0));
Field vcur(Grid_), vtmp(Grid_);
vcur = v0;
for (int i = 0; i < MAX_ITER; i++) {
Linop.Op(vcur, vtmp);
vcur = vtmp;
RealD num = std::sqrt(norm2(vcur));
lam = num / denom;
denom = num;
}
return lam;
}
};
NAMESPACE_END(Grid);
#endif // GRID_BLOCKED_KRYLOV_SCHUR_H
@@ -0,0 +1,277 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/ConjugateGradientTimeslice.h
Copyright (C) 2015
Author: Azusa Yamaguchi <ayamaguc@staffmail.ed.ac.uk>
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: paboyle <paboyle@ph.ed.ac.uk>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#ifndef GRID_CONJUGATE_GRADIENT_TIMESLICE_H
#define GRID_CONJUGATE_GRADIENT_TIMESLICE_H
NAMESPACE_BEGIN(Grid);
/////////////////////////////////////////////////////////////
// Base classes for iterative processes based on operators
// single input vec, single output vec.
/////////////////////////////////////////////////////////////
/**
* Simple modification of conjugate gradient that outputs the residual as a function
* of time, in order to study the large wavelength behavior of the solver.
*/
template <class Field>
class ConjugateGradientTimeslice : public OperatorFunction<Field> {
public:
using OperatorFunction<Field>::operator();
bool ErrorOnNoConverge; // throw an assert when the CG fails to converge.
// Defaults true.
RealD Tolerance;
Integer MaxIterations;
Integer IterationsToComplete; //Number of iterations the CG took to finish. Filled in upon completion
RealD TrueResidual;
ConjugateGradientTimeslice(RealD tol, Integer maxit, bool err_on_no_conv = true)
: Tolerance(tol),
MaxIterations(maxit),
ErrorOnNoConverge(err_on_no_conv)
{};
virtual void LogIteration(int k,RealD a,RealD b){
// std::cout << "ConjugageGradient::LogIteration() "<<std::endl;
};
virtual void LogBegin(void){
std::cout << "ConjugageGradient::LogBegin() "<<std::endl;
};
void operator()(LinearOperatorBase<Field> &Linop, const Field &src, Field &psi) {
this->LogBegin();
GRID_TRACE("ConjugateGradientTimeslice");
GridStopWatch PreambleTimer;
GridStopWatch ConstructTimer;
GridStopWatch NormTimer;
GridStopWatch AssignTimer;
PreambleTimer.Start();
psi.Checkerboard() = src.Checkerboard();
conformable(psi, src);
RealD cp, c, a, d, b, ssq, qq;
//RealD b_pred;
// Was doing copies
ConstructTimer.Start();
Field p (src.Grid());
Field mmp(src.Grid());
Field r (src.Grid());
ConstructTimer.Stop();
// Initial residual computation & set up
NormTimer.Start();
ssq = norm2(src); // Norm of source vector ||b||^2
ssqtx = localNorm2(src); // Norm |b(x, t)|^2 as a field
std::vector<RealD> ssqt; // Norm of source not summed over time slices, ssq(t) = \sum_x |b(x, t)|^2
sliceSum(ssqtx, ssqt, Tdir); // TODO make sure Tdir is globally defined
RealD guess = norm2(psi); // Norm of initial guess ||psi||^2
NormTimer.Stop();
assert(std::isnan(guess) == 0);
AssignTimer.Start();
if ( guess == 0.0 ) {
r = src;
p = r;
a = ssq;
} else {
Linop.HermOpAndNorm(psi, mmp, d, b); //
r = src - mmp; // Initial residual r0 = b - A guess
p = r; // initial conj vector p0 = r0
a = norm2(p);
}
cp = a;
AssignTimer.Stop();
// Handle trivial case of zero src
if (ssq == 0.){
psi = Zero();
IterationsToComplete = 1;
TrueResidual = 0.;
return;
}
std::cout << GridLogIterative << std::setprecision(8) << "ConjugateGradient: guess " << guess << std::endl;
std::cout << GridLogIterative << std::setprecision(8) << "ConjugateGradient: src " << ssq << std::endl;
std::cout << GridLogIterative << std::setprecision(8) << "ConjugateGradient: mp " << d << std::endl;
std::cout << GridLogIterative << std::setprecision(8) << "ConjugateGradient: mmp " << b << std::endl;
std::cout << GridLogIterative << std::setprecision(8) << "ConjugateGradient: cp,r " << cp << std::endl;
std::cout << GridLogIterative << std::setprecision(8) << "ConjugateGradient: p " << a << std::endl;
RealD rsq = Tolerance * Tolerance * ssq;
// Check if guess is really REALLY good :)
if (cp <= rsq) {
TrueResidual = std::sqrt(a/ssq);
std::cout << GridLogMessage << "ConjugateGradient guess is converged already " << std::endl;
IterationsToComplete = 0;
return;
}
std::cout << GridLogIterative << std::setprecision(8)
<< "ConjugateGradient: k=0 residual " << cp << " target " << rsq << std::endl;
PreambleTimer.Stop();
GridStopWatch LinalgTimer;
GridStopWatch InnerTimer;
GridStopWatch AxpyNormTimer;
GridStopWatch LinearCombTimer;
GridStopWatch MatrixTimer;
GridStopWatch SolverTimer;
RealD usecs = -usecond();
SolverTimer.Start();
int k;
for (k = 1; k <= MaxIterations; k++) {
GridStopWatch IterationTimer;
IterationTimer.Start();
c = cp;
MatrixTimer.Start();
Linop.HermOp(p, mmp); // Computes mmp = Ap
MatrixTimer.Stop();
LinalgTimer.Start();
InnerTimer.Start();
ComplexD dc = innerProduct(p,mmp); // p^\dagger A p
InnerTimer.Stop();
d = dc.real();
a = c / d;
// What is axpy? Some accelerator or something? Check Lattice_arith.h
AxpyNormTimer.Start();
// axpy_norm computes ax+by for vectors x and y compatible with a GPU. Here b is set to 1 (see the function in Lattice_reduction.h).
// The first argument passes r by reference, so it stores r --> -a * Ap + 1 * r, i.e. it performs an update on
// r_k --> r_{k+1} = r_k - \alpha_k A p_k. The function returns the norm squared of the first variable, i.e. ||r_{k+1}||^2.
cp = axpy_norm(r, -a, mmp, r);
AxpyNormTimer.Stop();
b = cp / c;
LinearCombTimer.Start();
{
autoView( psi_v , psi, AcceleratorWrite);
autoView( p_v , p, AcceleratorWrite);
autoView( r_v , r, AcceleratorWrite);
accelerator_for(ss,p_v.size(), Field::vector_object::Nsimd(),{
coalescedWrite(psi_v[ss], a * p_v(ss) + psi_v(ss));
coalescedWrite(p_v[ss] , b * p_v(ss) + r_v (ss));
});
}
LinearCombTimer.Stop();
LinalgTimer.Stop();
LogIteration(k,a,b);
IterationTimer.Stop();
if ( (k % 500) == 0 ) {
std::cout << GridLogMessage << "ConjugateGradient: Iteration " << k
<< " residual " << sqrt(cp/ssq) << " target " << Tolerance << std::endl;
} else {
std::cout << GridLogIterative << "ConjugateGradient: Iteration " << k
<< " residual " << sqrt(cp/ssq) << " target " << Tolerance << " took " << IterationTimer.Elapsed() << std::endl;
}
// Stopping condition
if (cp <= rsq) {
usecs +=usecond();
SolverTimer.Stop();
Linop.HermOpAndNorm(psi, mmp, d, qq);
p = mmp - src;
GridBase *grid = src.Grid();
RealD DwfFlops = (1452. )*grid->gSites()*4*k
+ (8+4+8+4+4)*12*grid->gSites()*k; // CG linear algebra
RealD srcnorm = std::sqrt(norm2(src));
RealD resnorm = std::sqrt(norm2(p));
RealD true_residual = resnorm / srcnorm;
std::cout << GridLogMessage << "ConjugateGradient Converged on iteration " << k
<< "\tComputed residual " << std::sqrt(cp / ssq)
<< "\tTrue residual " << true_residual
<< "\tTarget " << Tolerance << std::endl;
// GridLogMessage logs the message to the terminal output; GridLogPerformance probably writes to a log file?
// std::cout << GridLogMessage << "\tPreamble " << PreambleTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tSolver Elapsed " << SolverTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "Time breakdown "<<std::endl;
std::cout << GridLogPerformance << "\tMatrix " << MatrixTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\tLinalg " << LinalgTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tInner " << InnerTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tAxpyNorm " << AxpyNormTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tLinearComb " << LinearCombTimer.Elapsed() <<std::endl;
std::cout << GridLogDebug << "\tMobius flop rate " << DwfFlops/ usecs<< " Gflops " <<std::endl;
if (ErrorOnNoConverge) assert(true_residual / Tolerance < 10000.0);
IterationsToComplete = k;
TrueResidual = true_residual;
return;
}
}
// Failed. Calculate true residual before giving up
// Linop.HermOpAndNorm(psi, mmp, d, qq);
// p = mmp - src;
//TrueResidual = sqrt(norm2(p)/ssq);
// TrueResidual = 1;
std::cout << GridLogMessage << "ConjugateGradient did NOT converge "<<k<<" / "<< MaxIterations
<<" residual "<< std::sqrt(cp / ssq)<< std::endl;
SolverTimer.Stop();
std::cout << GridLogMessage << "\tPreamble " << PreambleTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tConstruct " << ConstructTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tNorm " << NormTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tAssign " << AssignTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tSolver " << SolverTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "Solver breakdown "<<std::endl;
std::cout << GridLogMessage << "\tMatrix " << MatrixTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage<< "\tLinalg " << LinalgTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tInner " << InnerTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tAxpyNorm " << AxpyNormTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tLinearComb " << LinearCombTimer.Elapsed() <<std::endl;
if (ErrorOnNoConverge) assert(0);
IterationsToComplete = k;
}
};
NAMESPACE_END(Grid);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,230 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
γ5-Scalar Lanczos algorithm for γ5-Hermitian operators (Wilson Dirac).
Single-vector Lanczos using the γ5-inner product (u,v)_{γ5} = u†γ5v.
Builds the Krylov space {q, D_W q, D_W² q, ...} and projects D_W onto it,
producing a real tridiagonal matrix T_m whose eigenvalues approximate those
of D_W. Complex conjugate pairs of D_W eigenvalues appear as complex
eigenvalues of the real non-symmetric T_m.
Recurrence (G_k = sign(q_k†γ5 q_k) = ±1):
r_k = D_W q_k α_k q_k β_sup[k-1] q_{k-1}
β_sub[k] = √|r_k†γ5 r_k| (lower subdiag T_{k+1,k})
β_sup[k] = β_sub[k] G_{k+1}/G_k (upper superdiag T_{k,k+1})
q_{k+1} = r_k / β_sub[k]
T_m is real but not symmetric in general; eigenvalues via EigenSolver.
*************************************************************************************/
#ifndef GRID_GAMMA5_SCALAR_LANCZOS_H
#define GRID_GAMMA5_SCALAR_LANCZOS_H
#include <functional>
#include <numeric>
#include <iomanip>
NAMESPACE_BEGIN(Grid);
template<class Field>
class Gamma5ScalarLanczos {
public:
using Gamma5Func = std::function<void(const Field&, Field&)>;
private:
typedef Eigen::MatrixXcd CMat;
typedef Eigen::VectorXcd CVec;
typedef Eigen::MatrixXd RMat;
LinearOperatorBase<Field>& Linop;
GridBase* Grid_;
Gamma5Func applyGamma5;
RealD Tolerance;
int nSteps;
std::vector<Field> basis;
std::vector<RealD> alpha_; // diagonal of T_m
std::vector<RealD> beta_sub_; // lower subdiagonal: T_{k+1,k}
std::vector<RealD> beta_sup_; // upper superdiagonal: T_{k,k+1}
std::vector<int> gsign_; // G_k = ±1
CVec evals_;
std::vector<Field> evecs_;
std::vector<RealD> residuals_;
public:
bool doEvalCheck = false;
Gamma5ScalarLanczos(LinearOperatorBase<Field>& op, GridBase* grid,
Gamma5Func g5, RealD tol = 1e-8)
: Linop(op), Grid_(grid), applyGamma5(g5), Tolerance(tol), nSteps(0)
{}
CVec getEvals() { return evals_; }
std::vector<Field> getEvecs() { return evecs_; }
std::vector<RealD> getResiduals() { return residuals_; }
// v0 : starting vector
// maxSteps : maximum Lanczos steps
// Nstop : target Ritz pairs to return (0 = all)
// reorthog : full γ5-reorthogonalisation at each step
// filter : eigenvalue selection criterion
void operator()(const Field& v0, int maxSteps, int Nstop,
bool reorthog = false, RitzFilter filter = EvalImNormSmall)
{
basis.clear(); alpha_.clear(); beta_sub_.clear(); beta_sup_.clear(); gsign_.clear();
nSteps = 0;
// Normalise starting vector in γ5-norm: q_0 = v0 / √|v0†γ5 v0|
Field q(Grid_); q = v0;
Field g5q(Grid_);
applyGamma5(q, g5q);
RealD omega = std::real(toStdCmplx(innerProduct(g5q, q)));
RealD beta0 = std::sqrt(std::abs(omega));
assert(beta0 > 1e-14 && "starting vector has zero γ5-norm");
q *= (1.0 / beta0);
gsign_.push_back(omega >= 0 ? +1 : -1);
basis.push_back(q);
std::cout << GridLogMessage
<< "Gamma5ScalarLanczos: start (v0†γ5 v0)=" << omega
<< " G[0]=" << gsign_[0] << std::endl;
for (int k = 0; k < maxSteps; k++) {
// ── Apply operator ─────────────────────────────────────────────────
Field p(Grid_);
Linop.Op(basis[k], p);
// ── α_k = (q_k†γ5 D_W q_k) / G_k (real) ─────────────────────────
applyGamma5(basis[k], g5q);
RealD a = std::real(toStdCmplx(innerProduct(g5q, p))) / RealD(gsign_[k]);
alpha_.push_back(a);
// ── r = D_W q_k α_k q_k β_sup[k-1] q_{k-1} ───────────────────
Field r(Grid_); r = p;
r -= basis[k] * a;
if (k > 0)
r -= basis[k-1] * beta_sup_[k-1];
// ── Optional γ5-reorthogonalisation ────────────────────────────────
if (reorthog) {
for (int i = 0; i <= k; i++) {
applyGamma5(basis[i], g5q);
ComplexD c = toStdCmplx(innerProduct(g5q, r)) / RealD(gsign_[i]);
r -= basis[i] * c;
}
}
// ── β_sub[k] = √|r†γ5 r| ───────────────────────────────────────────
applyGamma5(r, g5q);
omega = std::real(toStdCmplx(innerProduct(g5q, r)));
RealD beta = std::sqrt(std::abs(omega));
std::cout << GridLogMessage
<< "Gamma5ScalarLanczos: k=" << k
<< " alpha=" << a
<< " beta=" << beta
<< " G[k]=" << gsign_[k]
<< " (r†γ5r)=" << omega << std::endl;
beta_sub_.push_back(beta);
if (beta < Tolerance) {
std::cout << GridLogMessage
<< "Gamma5ScalarLanczos: invariant subspace at step " << k << std::endl;
nSteps = k + 1;
break;
}
int G_next = (omega >= 0) ? +1 : -1;
gsign_.push_back(G_next);
// β_sup[k] = T_{k,k+1} = β_sub[k] * G_{k+1} / G_k
beta_sup_.push_back(beta * RealD(G_next) / RealD(gsign_[k]));
Field qnew(Grid_); qnew = r; qnew *= (1.0 / beta);
basis.push_back(qnew);
nSteps = k + 1;
}
if (nSteps == 0) return;
computeRitzPairs(nSteps, Nstop, filter);
}
private:
void computeRitzPairs(int m, int Nstop, RitzFilter filter)
{
// Assemble real tridiagonal T_m
RMat Tm = RMat::Zero(m, m);
for (int k = 0; k < m; k++) {
Tm(k, k) = alpha_[k];
if (k + 1 < m) {
Tm(k+1, k) = beta_sub_[k];
Tm(k, k+1) = beta_sup_[k];
}
}
std::cout << GridLogMessage
<< "Gamma5ScalarLanczos: T_m (" << m << "×" << m << "):" << std::endl;
for (int i = 0; i < m; i++) {
for (int j = 0; j < m; j++)
std::cout << " " << std::setprecision(8) << std::setw(16) << Tm(i,j);
std::cout << std::endl;
}
Eigen::EigenSolver<RMat> es(Tm);
CVec lambdas = es.eigenvalues();
CMat Y = es.eigenvectors();
// Sort by filter criterion
ComplexComparator cComp(filter);
std::vector<int> idx(m);
std::iota(idx.begin(), idx.end(), 0);
std::sort(idx.begin(), idx.end(), [&](int a, int b){
return cComp(toStdCmplx(lambdas(a)), toStdCmplx(lambdas(b)));
});
int Nout = (Nstop > 0 && Nstop < m) ? Nstop : m;
evals_.resize(Nout);
evecs_.clear(); evecs_.reserve(Nout);
residuals_.clear(); residuals_.reserve(Nout);
// Trailing β for residual estimate: β_sub[m-1] * |y_j[m-1]|
RealD beta_trail = (m - 1 < (int)beta_sub_.size()) ? beta_sub_[m-1] : 0.0;
for (int ji = 0; ji < Nout; ji++) {
int j = idx[ji];
evals_(ji) = lambdas(j);
CVec yj = Y.col(j);
// Ritz vector: u_j = Σ_k q_k y_j[k]
Field uj(Grid_); uj = Zero();
for (int k = 0; k < m && k < (int)basis.size(); k++)
uj += basis[k] * toStdCmplx(yj(k));
evecs_.push_back(uj);
RealD res = beta_trail * std::abs(toStdCmplx(yj(m-1)));
if (!std::isfinite(res)) res = std::numeric_limits<RealD>::infinity();
residuals_.push_back(res);
std::cout << GridLogMessage
<< " Ritz[" << std::setw(3) << ji << "]"
<< " lambda=" << evals_(ji)
<< " |res|=" << res;
if (doEvalCheck) {
Field w(Grid_);
Linop.Op(uj, w);
w -= uj * evals_(ji);
RealD actual = std::sqrt(norm2(w));
std::cout << " ||D_W u - λu||=" << actual;
}
std::cout << std::endl;
}
}
};
NAMESPACE_END(Grid);
#endif // GRID_GAMMA5_SCALAR_LANCZOS_H
@@ -0,0 +1,719 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/HarmonicBlockKrylovSchur.h
Copyright (C) 2015
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: Chulwoo Jung <chulwoo@bnl.gov>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#ifndef GRID_HARMONIC_BLOCKED_KRYLOV_SCHUR_H
#define GRID_HARMONIC_BLOCKED_KRYLOV_SCHUR_H
#include <iomanip>
#include <numeric>
NAMESPACE_BEGIN(Grid);
/**
* Block shift-targeted Krylov-Schur eigensolver.
*
* Algorithm
* ---------
* Uses a block Arnoldi factorisation:
*
* A V = V H + F B^dag (1)
*
* with V orthonormal (Nm columns), H the Nm×Nm block upper-Hessenberg
* Rayleigh quotient, F the Nblock residual vectors and B the Nm×Nblock
* coupling matrix.
*
* Thick restart
* -------------
* To target eigenvalues near shift σ, the Schur decomposition is computed
* for the shifted Rayleigh quotient:
*
* (H - σI) = Q^dag S Q (2)
*
* Sorting the Schur values of (H - σI) by smallest |S(i,i)| = |λ - σ| and
* retaining the leading Nk is equivalent to selecting the Ritz values of H
* closest to σ. Since Q diagonalises H - σI (and hence H itself), the
* rotated Rayleigh quotient is exactly upper triangular:
*
* H_new = Q H Q^dag = S + σI (upper triangular) (3)
*
* Truncation to Nk is therefore exact: the off-diagonal coupling block
* H_new[Nk:, :Nk] = 0 by triangularity. The Krylov-Schur relation after
* restart is exact and block Arnoldi resumes cleanly from F.
*
* Convergence
* -----------
* Convergence is declared when || B^H y_k || < Tolerance * approxLambdaMax
* for each Ritz pair (λ_k, y_k) of the truncated H.
*
* Parameters
* ----------
* shift : target shift σ (default 0.0); Schur values sorted by |λ - σ|
* Nblock : block size p
* Nm : total Krylov dimension (must be divisible by Nblock)
* Nk : total vectors to retain after each restart (must be divisible by Nblock, Nk < Nm)
* Nstop : stop when this many eigenpairs converge
* MaxIter : maximum outer (restart) iterations
* Tolerance: relative convergence tolerance
*
* Usage
* -----
* HarmonicBlockKrylovSchur<Field> hbks(LinOp, Grid, tol, shift, EvalNormSmall);
* std::vector<Field> v0(Nblock, Field(Grid));
* // fill v0 with random starting vectors
* hbks(v0, maxIter, Nm, Nk, Nstop, Nblock);
* auto evals = hbks.getEvals();
* auto evecs = hbks.getEvecs();
*/
template<class Field>
class HarmonicBlockKrylovSchur {
protected:
typedef Eigen::MatrixXcd CMat;
typedef Eigen::VectorXcd CVec;
//--------------------------------------------------------------------
// Parameters
//--------------------------------------------------------------------
int Nblock;
int Nm; // total Krylov dimension (multiple of Nblock)
int Nk; // total vectors retained after restart (multiple of Nblock)
int Nstop;
int MaxIter;
RealD Tolerance;
ComplexD shift; // target shift σ
//--------------------------------------------------------------------
// Internal state
//--------------------------------------------------------------------
LinearOperatorBase<Field>& Linop;
GridBase* Grid_;
RitzFilter ritzFilter;
std::vector<Field> basis; // Nm*Nblock flat basis
CMat H; // (Nm*Nblock)² block-Hessenberg Rayleigh quotient
std::vector<Field> F; // Nblock residual vectors
CMat B; // (Nm*Nblock) × Nblock coupling matrix
RealD beta_k;
RealD rtol;
CVec evals;
CMat littleEvecs;
std::vector<RealD> ritzEstimates;
public:
std::vector<Field> evecs;
bool doEvalCheck = false;
bool useParityFlip = false;
bool useGamma5 = false;
std::function<void(const Field&, Field&)> gamma5Func;
//--------------------------------------------------------------------
// Constructor
//--------------------------------------------------------------------
HarmonicBlockKrylovSchur(LinearOperatorBase<Field>& _Linop, GridBase* _Grid,
RealD _Tolerance, ComplexD _shift = 0.0,
RitzFilter _rf = EvalNormSmall)
: Linop(_Linop), Grid_(_Grid), Tolerance(_Tolerance), shift(_shift),
ritzFilter(_rf),
Nblock(-1), Nm(-1), Nk(-1), Nstop(-1), MaxIter(-1),
beta_k(0.0), rtol(0.0)
{}
virtual ~HarmonicBlockKrylovSchur() = default;
//--------------------------------------------------------------------
// Main entry point
//--------------------------------------------------------------------
virtual void operator()(const std::vector<Field>& v0, int _maxIter, int _Nm, int _Nk,
int _Nstop, int _Nblock = 1, bool doubleOrthog = true,
bool doVerify = false)
{
MaxIter = _maxIter;
Nm = _Nm;
Nk = _Nk;
Nstop = _Nstop;
Nblock = _Nblock;
{
int divisor = 1;
if (useParityFlip) divisor *= 2;
if (useGamma5) divisor *= 2;
assert(Nblock % divisor == 0 && (int)v0.size() >= Nblock / divisor);
std::cout << GridLogMessage << "divisor= " << divisor << std::endl;
}
assert(Nm % Nblock == 0);
assert(Nk % Nblock == 0);
assert(Nk < Nm);
if (useGamma5) assert(gamma5Func && "useGamma5: gamma5Func must be set");
int N = Nm;
RealD approxLambdaMax = approxMaxEval(v0[0]);
rtol = Tolerance * approxLambdaMax;
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: approx max eval = " << approxLambdaMax
<< ", rtol = " << rtol
<< ", shift = " << shift << std::endl;
H = CMat::Zero(N, N);
B = CMat::Zero(N, Nblock);
int start = 0;
std::vector<Field> startBlock = expandStartBlock(v0);
for (int iter = 0; iter < MaxIter; iter++) {
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: restart iteration " << iter << std::endl;
// ---- Block Arnoldi: extend from block 'start' to block Nm/Nblock ----
blockArnoldiIteration(startBlock, Nm/Nblock, start, doubleOrthog);
start = Nk/Nblock;
if (doVerify) {
std::string lbl = "iter " + std::to_string(iter) + " after Arnoldi";
verify(lbl);
}
// ---- Schur decompose (H - σI) to select Schur vectors closest to σ ----
// Sorting the Schur values of (H - σI) by |S(i,i)| = |λ - σ| gives the
// Ritz values of H nearest the target shift without any matrix inversion.
// Because Q diagonalises (H - σI), it also diagonalises H:
// H_new = Q H Q^dag = S + σI (upper triangular)
// Truncation is therefore exact.
CMat Hshift = H - shift * CMat::Identity(N, N);
ComplexSchurDecomposition schur(Hshift, false, ritzFilter);
schur.schurReorder(Nk);
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: Ritz values nearest shift (first Nk):" << std::endl;
CMat S = schur.getMatrixS();
for (int i = 0; i < Nk; i++)
std::cout << GridLogMessage << " [" << i << "] " << S(i, i) + shift << std::endl;
CMat Q = schur.getMatrixQ();
CMat Qt = Q.adjoint();
// ---- Rotate Krylov basis ----
std::vector<Field> basis2;
constructUR(basis2, basis, Qt, N);
basis = basis2;
// ---- Update H and B ----
// H_new = S + σI is upper triangular; off-diagonal block H_new[Nk:,:Nk] = 0
H = S + shift * CMat::Identity(N, N);
B = Q * B;
// ---- Truncate to Nk (exact: H upper triangular) ----
int Nkeep = Nk;
CMat Htmp = H(Eigen::seqN(0, Nkeep), Eigen::seqN(0, Nkeep));
H = CMat::Zero(N, N);
H(Eigen::seqN(0, Nkeep), Eigen::seqN(0, Nkeep)) = Htmp;
std::vector<Field> basisTmp(basis.begin(), basis.begin() + Nkeep);
basis = basisTmp;
CMat Btmp = B(Eigen::seqN(0, Nkeep), Eigen::all);
B = CMat::Zero(N, Nblock);
B(Eigen::seqN(0, Nkeep), Eigen::all) = Btmp;
beta_k = Btmp.norm();
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: beta_k = " << beta_k << std::endl;
// Restart from F (exact: no discarded-basis correction needed)
startBlock = F;
if (doVerify) {
std::string lbl = "iter " + std::to_string(iter) + " after restart+truncation";
verify(lbl);
}
// ---- Eigensystem of truncated H for convergence ----
CMat Hk = H(Eigen::seqN(0, Nkeep), Eigen::seqN(0, Nkeep));
computeEigensystem(Hk, Nkeep);
int Nconv = converged(Nkeep);
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: converged " << Nconv
<< " / " << Nstop << std::endl;
if (Nconv >= Nstop || iter == MaxIter - 1) {
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: done after " << iter
<< " restarts, " << Nconv << " converged." << std::endl;
std::cout << GridLogMessage << "Eigenvalues: " << evals.transpose() << std::endl;
if (doEvalCheck) {
Field w(Grid_);
for (int k = 0; k < (int)evecs.size(); k++) {
Linop.Op(evecs[k], w);
ComplexD eval_est = toStdCmplx(innerProduct(evecs[k], w));
w -= eval_est * evecs[k];
RealD res = std::sqrt(norm2(w));
std::cout << GridLogMessage << "HarmonicBlockKrylovSchur: evec[" << k << "]"
<< " eval_reported = " << evals[k]
<< " eval_est = " << eval_est
<< " || A v - eval_est * v || = " << res << std::endl;
}
}
return;
}
}
}
// Accessors
std::vector<Field> getEvecs() { return evecs; }
CVec getEvals() { return evals; }
std::vector<RealD> getRitzEstimates() { return ritzEstimates; }
static void parityFlippedField(const Field& v, Field& out) {
GridCartesian* cgrid = dynamic_cast<GridCartesian*>(v.Grid());
assert(cgrid != nullptr);
GridRedBlackCartesian rbgrid(cgrid);
Field veven(&rbgrid), vodd(&rbgrid);
pickCheckerboard(Even, veven, v);
pickCheckerboard(Odd, vodd, v);
vodd = -vodd;
setCheckerboard(out, veven);
setCheckerboard(out, vodd);
}
//--------------------------------------------------------------------
// Verification: check A V = V H + F B^dag explicitly
//--------------------------------------------------------------------
/**
* Checks the block Arnoldi / Krylov-Schur decomposition
*
* A V = V H + F B^dag (KS)
*
* by explicit operator applications. H here is the standard Rayleigh
* quotient, so the KS relation is the same as for BlockKrylovSchur.
*
* Prints:
* - H (current Rayleigh quotient, nBasis × nBasis)
* - B (coupling matrix, nBasis × Nblock)
* - M (explicit inner product matrix <V | A V>)
* - max |H[i,j] - M[i,j]| (should be O(machine epsilon))
* - max |<V_i|V_j> - delta_ij| (orthonormality check)
* - for each basis column j: || A v_j - V H[:,j] - F B[j,:]^* ||
* - max |<V_i | F_t>| (F orthogonal to basis)
*/
void verify(const std::string& label = "")
{
int nBasis = (int)basis.size();
int nF = (int)F.size();
if (nBasis == 0) {
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur::verify [" << label
<< "]: basis is empty." << std::endl;
return;
}
std::cout << GridLogMessage
<< "======== HarmonicBlockKrylovSchur::verify [" << label << "] ========" << std::endl;
std::cout << GridLogMessage
<< " nBasis = " << nBasis << " Nblock = " << Nblock
<< " nF = " << nF << std::endl;
// ---- Print H ----
std::cout << GridLogMessage << "H (" << nBasis << " x " << nBasis << "):" << std::endl;
for (int i = 0; i < nBasis; i++) {
for (int j = 0; j < nBasis; j++)
std::cout << " " << std::setprecision(4) << std::setw(14) << H(i, j);
std::cout << std::endl;
}
// ---- Print B ----
std::cout << GridLogMessage << "B (" << nBasis << " x " << nF << "):" << std::endl;
for (int i = 0; i < nBasis; i++) {
for (int t = 0; t < nF; t++)
std::cout << " " << std::setprecision(4) << std::setw(14) << B(i, t);
std::cout << std::endl;
}
// ---- Compute M[i,j] = <basis[i] | A basis[j]> ----
CMat M = CMat::Zero(nBasis, nBasis);
Field w(Grid_);
for (int j = 0; j < nBasis; j++) {
Linop.Op(basis[j], w);
for (int i = 0; i < nBasis; i++)
M(i, j) = toStdCmplx(innerProduct(basis[i], w));
}
std::cout << GridLogMessage << "M = <V|AV> (" << nBasis << " x " << nBasis << "):" << std::endl;
for (int i = 0; i < nBasis; i++) {
for (int j = 0; j < nBasis; j++)
std::cout << " " << std::setprecision(4) << std::setw(14) << M(i, j);
std::cout << std::endl;
}
// ---- max |H - M| ----
RealD maxHM = 0.0;
for (int i = 0; i < nBasis; i++)
for (int j = 0; j < nBasis; j++)
maxHM = std::max(maxHM, std::abs(H(i,j) - M(i,j)));
std::cout << GridLogMessage
<< " max |H[i,j] - M[i,j]| = " << maxHM << std::endl;
// ---- Check orthonormality of basis ----
CMat G = CMat::Zero(nBasis, nBasis);
for (int i = 0; i < nBasis; i++)
for (int j = 0; j < nBasis; j++)
G(i, j) = toStdCmplx(innerProduct(basis[i], basis[j]));
CMat Gerr = G - CMat::Identity(nBasis, nBasis);
std::cout << GridLogMessage
<< " max |<V_i|V_j> - delta_ij| = " << Gerr.cwiseAbs().maxCoeff() << std::endl;
// ---- Per-column residual: || A v_j - V H[:,j] - F B[j,:]^* || ----
RealD maxColDev = 0.0;
for (int j = 0; j < nBasis; j++) {
Linop.Op(basis[j], w);
// subtract V H[:,j]
for (int i = 0; i < nBasis; i++)
w -= basis[i] * H(i, j);
// subtract F B[j,:]^* (F[t] * conj(B[j,t]))
for (int t = 0; t < nF; t++)
w -= F[t] * std::conj(B(j, t));
RealD dev = std::sqrt(norm2(w));
std::cout << GridLogMessage
<< " || A v[" << j << "] - V H[:,j] - F B[j,:]* || = " << dev << std::endl;
maxColDev = std::max(maxColDev, dev);
}
std::cout << GridLogMessage
<< " max column deviation = " << maxColDev << std::endl;
// ---- Check F block orthogonality against basis ----
if (nF > 0) {
RealD maxFV = 0.0;
for (int t = 0; t < nF; t++)
for (int i = 0; i < nBasis; i++) {
RealD ip = std::abs(toStdCmplx(innerProduct(basis[i], F[t])));
maxFV = std::max(maxFV, ip);
}
std::cout << GridLogMessage
<< " max |<V_i | F_t>| (should be ~0) = " << maxFV << std::endl;
}
std::cout << GridLogMessage
<< "======== end verify ========" << std::endl;
}
protected:
//--------------------------------------------------------------------
// Starting-block expansion (parity flip / gamma5 partners)
//--------------------------------------------------------------------
// Expands Nblock/divisor seed vectors into the full Nblock starting
// block, pairing each seed with its parity-flipped and/or gamma5
// partners. Requires Nblock, Grid_ and the flags to be set.
std::vector<Field> expandStartBlock(const std::vector<Field>& v0)
{
int divisor = (useParityFlip ? 2 : 1) * (useGamma5 ? 2 : 1);
std::vector<Field> startBlock;
startBlock.reserve(Nblock);
for (int i = 0; i < Nblock / divisor; i++) {
std::vector<Field> 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;
}
//--------------------------------------------------------------------
// Block Arnoldi iteration
//--------------------------------------------------------------------
void blockArnoldiIteration(std::vector<Field>& startBlock, int endBlock,
int startIdx, bool doubleOrthog)
{
int N = Nm;
if (startIdx == 0) {
basis.clear();
F.clear();
H = CMat::Zero(N, N);
B = CMat::Zero(N, Nblock);
std::vector<Field> V0 = startBlock;
blockOrthonormalise(V0);
for (auto& v : V0) basis.push_back(v);
} else {
// Append the new starting block to the retained basis.
//
// The exact truncated KS relation is A V_k = V_k H_k + F B_k^dag,
// so the coupling rows are H[Nkeep+t, j] = conj(B_k[j,t]).
// Since H_new = S + σI is upper triangular, the off-diagonal block
// H_new[Nkeep:, :Nkeep] = 0 and the restart from F is exact.
int Nkeep = startIdx * Nblock;
for (auto& v : startBlock) basis.push_back(v);
// Fill restart coupling rows into H (exact: H_new is upper triangular,
// so B encodes the only non-zero coupling to the new block).
for (int t = 0; t < Nblock; t++)
for (int j = 0; j < Nkeep; j++)
H(Nkeep + t, j) = std::conj(B(j, t));
// Zero out B for the retained columns now that the coupling is in H
for (int j = 0; j < Nkeep; j++)
for (int t = 0; t < Nblock; t++)
B(j, t) = 0.0;
}
for (int k = startIdx; k < endBlock; k++)
blockArnoldiStep(k, doubleOrthog);
}
//--------------------------------------------------------------------
// One block Arnoldi step
//--------------------------------------------------------------------
void blockArnoldiStep(int k, bool doubleOrthog)
{
int kBase = k * Nblock;
int prevN = kBase + Nblock;
int N = Nm;
std::vector<Field> W(Nblock, Field(Grid_));
for (int t = 0; t < Nblock; t++)
Linop.Op(basis[kBase + t], W[t]);
// Full reorthogonalisation against all current basis vectors
for (int pass = 0; pass < (doubleOrthog ? 2 : 1); pass++) {
for (int i = 0; i < prevN; i++) {
for (int t = 0; t < Nblock; t++) {
ComplexD coeff = innerProduct(basis[i], W[t]);
if (pass == 0)
H(i, kBase + t) = toStdCmplx(coeff);
else
H(i, kBase + t) += toStdCmplx(coeff);
W[t] -= coeff * basis[i];
}
}
}
F = W;
if (k == Nm/Nblock - 1) {
// Last block: record coupling in B as R^H (Hermitian conjugate of QR factor)
// KS relation requires B[kBase+t, s] = conj(R[s,t])
CMat R = blockQR(F);
for (int t = 0; t < Nblock; t++)
for (int s = 0; s < Nblock; s++)
B(kBase + t, s) = std::conj(R(s, t)); // B_block = R^H
beta_k = R.norm();
return;
}
// Not last: QR the residual, extend basis
CMat R = blockQR(F);
int nextBase = (k + 1) * Nblock;
for (int i = 0; i < Nblock; i++)
for (int j = 0; j < Nblock; j++)
H(nextBase + i, kBase + j) = R(i, j);
for (int t = 0; t < Nblock; t++)
basis.push_back(F[t]);
}
//--------------------------------------------------------------------
// Block QR (modified Gram-Schmidt within the block)
//--------------------------------------------------------------------
CMat blockQR(std::vector<Field>& W)
{
CMat R = CMat::Zero(Nblock, Nblock);
const RealD deflThresh = 1e-14;
for (int j = 0; j < Nblock; j++) {
for (int i = 0; i < j; i++) {
ComplexD coeff = innerProduct(W[i], W[j]);
R(i, j) = toStdCmplx(coeff);
W[j] -= coeff * W[i];
}
RealD nrm = std::sqrt(norm2(W[j]));
R(j, j) = nrm;
if (nrm > deflThresh) {
W[j] *= (1.0 / nrm);
} else {
W[j] = Zero();
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: deflation at block column " << j
<< " (norm = " << nrm << ")" << std::endl;
}
}
return R;
}
//--------------------------------------------------------------------
// Orthonormalise starting block
//--------------------------------------------------------------------
void blockOrthonormalise(std::vector<Field>& V)
{
for (int j = 0; j < (int)V.size(); j++) {
for (int i = 0; i < j; i++) {
ComplexD c = innerProduct(V[i], V[j]);
V[j] -= c * V[i];
}
RealD nrm = std::sqrt(norm2(V[j]));
assert(nrm > 1e-14);
V[j] *= (1.0 / nrm);
}
}
//--------------------------------------------------------------------
// Basis rotation: UR[i] = sum_j U[j] * R(j,i)
//--------------------------------------------------------------------
void constructUR(std::vector<Field>& UR, std::vector<Field>& U,
CMat& R, int N)
{
UR.clear();
Field tmp(Grid_);
for (int i = 0; i < N; i++) {
tmp = Zero();
for (int j = 0; j < N; j++)
tmp += U[j] * R(j, i);
UR.push_back(tmp);
}
}
//--------------------------------------------------------------------
// Eigensystem of the truncated H (not Hhat)
//--------------------------------------------------------------------
/**
* Eigenvalues of H_k are the standard Ritz values in the retained
* subspace. After convergence has been declared via harmonic estimates,
* the final reported eigenvalues and vectors come from H_k (not Hhat_k),
* since H_k contains the true projected operator.
*/
void computeEigensystem(CMat& Hk, int Nkeep)
{
Eigen::ComplexEigenSolver<CMat> es;
es.compute(Hk);
// Sort to match schurReorder ordering.
int n = es.eigenvalues().size();
ComplexComparator cComp(ritzFilter);
std::vector<int> idx(n);
std::iota(idx.begin(), idx.end(), 0);
std::sort(idx.begin(), idx.end(), [&](int a, int b){
return cComp(toStdCmplx(es.eigenvalues()(a)), toStdCmplx(es.eigenvalues()(b)));
});
evals.resize(n);
littleEvecs.resize(n, n);
for (int k = 0; k < n; k++) {
evals(k) = es.eigenvalues()(idx[k]);
littleEvecs.col(k) = es.eigenvectors().col(idx[k]);
}
evecs.clear();
for (int k = 0; k < Nkeep; k++) {
CVec vec = littleEvecs.col(k);
Field tmp(Grid_);
tmp = Zero();
for (int j = 0; j < (int)basis.size() && j < Nkeep; j++)
tmp += vec[j] * basis[j];
evecs.push_back(tmp);
}
}
//--------------------------------------------------------------------
// Convergence check
//--------------------------------------------------------------------
/**
* Ritz estimate for eigenpair k: || B^H y_k ||
* where y_k is the k-th eigenvector of the truncated H.
* The same bound applies whether using Ritz or harmonic Ritz restart.
*/
int converged(int Nkeep)
{
ritzEstimates.clear();
int Nconv = 0;
CMat Bk = B(Eigen::seqN(0, Nkeep), Eigen::all);
for (int k = 0; k < Nkeep; k++) {
CVec yk = littleEvecs.col(k);
CVec Bty = Bk.adjoint() * yk;
RealD res = Bty.norm();
ritzEstimates.push_back(res);
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: Ritz estimate[" << k
<< "] = " << res << " eval = " << evals[k] << std::endl;
if (res < rtol) Nconv++;
else break;
}
return Nconv;
}
//--------------------------------------------------------------------
// Approximate maximum eigenvalue (power iteration)
//--------------------------------------------------------------------
RealD approxMaxEval(const Field& v0, int MAX_ITER = 50)
{
assert(norm2(v0) > 1e-8);
RealD lam = 0.0, denom = std::sqrt(norm2(v0));
Field vcur(Grid_), vtmp(Grid_);
vcur = v0;
for (int i = 0; i < MAX_ITER; i++) {
Linop.Op(vcur, vtmp);
vcur = vtmp;
RealD num = std::sqrt(norm2(vcur));
lam = num / denom;
denom = num;
}
return lam;
}
};
NAMESPACE_END(Grid);
#endif // GRID_HARMONIC_BLOCKED_KRYLOV_SCHUR_H
@@ -53,6 +53,18 @@ enum IRLdiagonalisation {
IRLdiagonaliseWithEigen
};
enum IRLeigsort {
IRLeigsortMax,
IRLeigsortSqMin
};
#if 0
bool square_comp(RealD a, RealD b){
if (a*a<b*b) return true;
return false;
}
#endif
template<class Field> class ImplicitlyRestartedLanczosHermOpTester : public ImplicitlyRestartedLanczosTester<Field>
{
public:
@@ -119,9 +131,10 @@ class ImplicitlyRestartedLanczos {
/////////////////////////
// Constructor
/////////////////////////
public:
public:
IRLeigsort EigSort;
//////////////////////////////////////////////////////////////////
// PAB:
//////////////////////////////////////////////////////////////////
@@ -154,6 +167,7 @@ public:
Nstop(_Nstop) , Nk(_Nk), Nm(_Nm),
eresid(_eresid), betastp(_betastp),
MaxIter(_MaxIter) , MinRestart(_MinRestart),
EigSort(IRLeigsortMax),
orth_period(_orth_period), diagonalisation(_diagonalisation) { };
ImplicitlyRestartedLanczos(LinearFunction<Field> & PolyOp,
@@ -170,6 +184,7 @@ public:
Nstop(_Nstop) , Nk(_Nk), Nm(_Nm),
eresid(_eresid), betastp(_betastp),
MaxIter(_MaxIter) , MinRestart(_MinRestart),
EigSort(IRLeigsortMax),
orth_period(_orth_period), diagonalisation(_diagonalisation) { };
////////////////////////////////
@@ -316,8 +331,12 @@ until convergence
// sorting
//////////////////////////////////
eval2_copy = eval2;
// if (EigSort==IRLeigsortMax)
// std::partial_sort(eval2.begin(),eval2.begin()+Nm,eval2.end(),square_comp);
// else
std::partial_sort(eval2.begin(),eval2.begin()+Nm,eval2.end(),std::greater<RealD>());
std::cout<<GridLogIRL <<" evals sorted "<<std::endl;
// eval2_copy = eval2;
const int chunk=8;
for(int io=0; io<k2;io+=chunk){
std::cout<<GridLogIRL << "eval "<< std::setw(3) << io ;
@@ -333,6 +352,7 @@ until convergence
//////////////////////////////////
Qt = Eigen::MatrixXd::Identity(Nm,Nm);
for(int ip=k2; ip<Nm; ++ip){
// std::cout<<GridLogIRL <<"QR decompose "<<eval2[ip]<<std::endl;
QR_decomp(eval,lme,Nm,Nm,Qt,eval2[ip],k1,Nm);
}
std::cout<<GridLogIRL <<"QR decomposed "<<std::endl;
@@ -375,7 +395,8 @@ until convergence
// power of two search pattern; not every evalue in eval2 is assessed.
int allconv =1;
for(int jj = 1; jj<=Nstop; jj*=2){
// for(int jj = 1; jj<=Nstop; jj*=2){
for(int jj = 1; jj<=Nstop; jj++){
int j = Nstop-jj;
RealD e = eval2_copy[j]; // Discard the evalue
basisRotateJ(B,evec,Qt,j,0,Nk,Nm);
+994
View File
@@ -0,0 +1,994 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/KrylovSchur.h
Copyright (C) 2015
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: paboyle <paboyle@ph.ed.ac.uk>
Author: Patrick Oare <poare@bnl.gov>
Author: Chulwoo Jung <chulwoo@bnl.gov>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#ifndef GRID_KRYLOVSCHUR_H
#define GRID_KRYLOVSCHUR_H
NAMESPACE_BEGIN(Grid);
#if defined(GRID_CUDA) || defined(GRID_HIP)
using thrust::abs;
using thrust::conj;
inline std::complex<double> toStdCmplx(const ComplexD& c) { return {c.real(), c.imag()}; }
#else
using std::abs;
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<ComplexD> 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
*/
enum RitzFilter {
EvalNormSmall, // Keep evals with smallest norm
EvalNormLarge, // Keep evals with largest norm
EvalReSmall, // Keep evals with smallest real part
EvalReLarge, // Keep evals with largest real part
EvalReNormLarge, // Keep evals with largest real part
EvalImSmall, // Keep evals with smallest imaginary part
EvalImLarge, // Keep evals with largest imaginary part
EvalImNormSmall, // Keep evals with smallest |imaginary| part
EvalImNormLarge, // Keep evals with largest |imaginary| part
};
/** Selects the RitzFilter corresponding to the input string. */
inline RitzFilter selectRitzFilter(std::string s) {
if (s == "EvalNormSmall") { return EvalNormSmall; } else
if (s == "EvalNormLarge") { return EvalNormLarge; } else
if (s == "EvalReSmall") { return EvalReSmall; } else
if (s == "EvalReLarge") { return EvalReLarge; } else
if (s == "EvalReNormLarge") { return EvalReNormLarge; } else
if (s == "EvalImSmall") { return EvalImSmall; } else
if (s == "EvalImLarge") { return EvalImLarge; } else
if (s == "EvalImNormSmall") { return EvalImNormSmall; } else
if (s == "EvalImNormLarge") { return EvalImNormLarge; } else
{ assert(0); }
}
/** Returns a string saying which RitzFilter it is. */
inline std::string rfToString(RitzFilter RF) {
switch (RF) {
case EvalNormSmall:
return "EvalNormSmall";
case EvalNormLarge:
return "EvalNormLarge";
case EvalReSmall:
return "EvalReSmall";
case EvalReLarge:
return "EvalReLarge";
case EvalReNormLarge:
return "EvalReNormLarge";
case EvalImSmall:
return "EvalImSmall";
case EvalImLarge:
return "EvalImLarge";
case EvalImNormSmall:
return "EvalImNormSmall";
case EvalImNormLarge:
return "EvalImNormLarge";
default:
assert(0);
}
}
// Select comparison function from RitzFilter
struct ComplexComparator
{
RitzFilter RF;
ComplexComparator (RitzFilter _rf) : RF(_rf) {}
bool operator()(std::complex<double> z1, std::complex<double> z2) {
RealD tmp1=std::abs(std::imag(z1));
RealD tmp2=std::abs(std::imag(z2));
if ( std::abs(std::real(z1)) >2.) tmp1 += 100.;
if ( std::abs(std::real(z2)) >2.) tmp2 += 100.;
switch (RF) {
case EvalNormSmall:
return std::abs(z1) < std::abs(z2);
case EvalNormLarge:
return std::abs(z1) > std::abs(z2);
case EvalReSmall:
return std::real(z1) < std::real(z2); // DELETE THE ABS HERE!!!
case EvalReLarge:
return std::real(z1) > std::real(z2);
case EvalReNormLarge:
return std::abs(std::real(z1)) < std::abs(std::real(z2));
case EvalImSmall:
return std::imag(z1) < std::imag(z2);
case EvalImLarge:
return std::imag(z1) > std::imag(z2);
case EvalImNormSmall:
// return std::abs(std::imag(z1)) < std::abs(std::imag(z2));
return tmp1 < tmp2 ;
case EvalImNormLarge:
return std::abs(std::imag(z1)) > std::abs(std::imag(z2));
default:
assert(0);
}
}
};
/**
* Computes a complex Schur decomposition of a complex matrix A using Eigen's matrix library. The Schur decomposition,
* A = Q^\dag S Q
* factorizes A into a unitary matrix Q and an upper triangular matrix S. The eigenvalues of A lie on the diagonal of the upper triangular matrix S.
* The Schur decomposition is not unique: in particular, any ordering of the eigenvalues of A can be used as the diagonal of the matrix S.
* This class supports eigenvalue reordering by swapping two adjacent eigenvalues with a unitary transformation.
*/
class ComplexSchurDecomposition {
private:
typedef Eigen::MatrixXcd CMat;
CMat A; // Matrix to decompose, A = Q^\dag S Q
CMat Q; // Unitary matrix Q
CMat S; // Upper triangular matrix S
// Placeholders for Givens rotation
CMat Givens; // Givens rotation
ComplexD s; // off-diagonal element
ComplexD lam1; // First eval for swap
ComplexD lam2; // Second eval for swap
ComplexD phi; // phase of s
RealD r; // norm of s and lam2 - lam1
int Nm; // size of matrix problem
ComplexComparator cCompare; // function to sort the Schur matrix.
public:
/**
* If the input matrix _A is in Hessenberg form (upper triangular + first subdiagonal non-zero), then the Schur
* decomposition is easier to compute.
*/
ComplexSchurDecomposition(CMat _A, bool isHess, RitzFilter ritzFilter = EvalReSmall) : A(_A), Nm (_A.rows()), cCompare (ritzFilter)
{
Eigen::ComplexSchur<CMat> schur (Nm);
if (isHess) {
schur.computeFromHessenberg(_A, CMat::Identity(Nm, Nm), true);
} else {
schur.compute(_A, true);
}
S = schur.matrixT();
Q = schur.matrixU().adjoint(); // Eigen computes A = Q S Q^\dag, we want A = Q^\dag S Q
}
// Getters
int getNm() { return Nm; } // size of matrix problem
CMat getMatrixA() { return A; } // matrix for decomposition
CMat getMatrixQ() { return Q; } // unitary matrix Q
CMat getMatrixS() { return S; } // Schur matrix (upper triangular) S
CMat getRitz() { return S.diagonal(); }
/**
* Checks the Schur decomposition A = Q^\dag S Q holds for the computed matrices. Returns if the relative
* Frobenius norm || A - Q^\dag S Q || / || A || is less than rtol.
*/
bool checkDecomposition(RealD rtol = 1e-8) {
RealD Anorm = A.norm();
if (Anorm < rtol) {
std::cout << GridLogMessage << "Zero matrix" << std::endl;
return true;
}
std::cout << GridLogDebug << "S = " << std::endl << S << std::endl;
std::cout << GridLogDebug << "Q = " << std::endl << Q << std::endl;
CMat A2 = Q.adjoint() * S * Q;
std::cout << GridLogDebug << "Q^dag S Q = " << std::endl << A2 << std::endl;
RealD dA = (A - A2).norm() / Anorm;
return (dA < rtol);
}
/**
* Swaps the components on the diagonal of the Schur matrix at index i with index i + 1.
* Updates the orthogonal matrix Q accordingly.
*/
void swapEvals(int i) {
assert(0 <= i && i <= Nm - 1); // can only swap blocks with upper left index between 0 and Nm - 1
// get parameters for rotation
s = S(i, i+1);
lam1 = S(i, i);
lam2 = S(i+1, i+1);
phi = s / abs(s);
r = std::sqrt(std::pow(abs(s), 2) + std::pow(abs(lam2 - lam1), 2));
// // Original code which performs Givens rotations by manual matrix multiplication
// // compute Givens rotation corresponding to these parameters
// Givens = CMat::Identity(Nm, Nm);
// Givens(i, i) = std::abs(s) / r;
// Givens(i+1, i+1) = Givens(i, i);
// Givens(i, i+1) = (phi / r) * std::conj(lam2 - lam1);
// Givens(i+1, i) = -std::conj(Givens(i, i+1));
// // rotate Schur matrix and unitary change of basis matrix Q
// S = Givens * S * Givens.adjoint();
// Q = Givens * Q;
// Modified code
Givens = CMat::Identity(2, 2);
Givens(0, 0) = abs(s) / r;
Givens(1, 1) = Givens(0, 0);
Givens(0, 1) = (phi / r) * conj(lam2 - lam1);
Givens(1, 0) = -conj(Givens(0, 1));
// TODO: make sure these are correct
Eigen::MatrixXcd tmp;
tmp = Givens * S.block(i, 0, 2, Nm);
S.block(i, 0, 2, Nm) = tmp;
// S_{:, i:i+2} = S.block<Nm, 2>(0, i);
tmp = S.block(0, i, Nm, 2) * Givens.adjoint();
S.block(0, i, Nm, 2) = tmp;
// Q_{i:i+2, :} = Q.block<2, Nm>(i, 0);
tmp = Givens * Q.block(i, 0, 2, Nm);
Q.block(i, 0, 2, Nm) = tmp;
return;
}
/**
* Reorders a Schur matrix &Schur to have the Ritz values that we would like to keep for
* restart as the first Nk elements on the diagonal.
*
* This algorithm is implemented as Nk iterations of a a reverse bubble sort with comparator compare.
* TODO: pass in compare function as an argument, default to compare with <.
*/
// void schurReorder(int Nk, std::function compare) {
void schurReorder(int Nk) {
std::cout << GridLogMessage << "schurReorder start " << std::endl;
int nswap=0;
for (int i = 0; i < Nk; i++) {
for (int k = 0; k <= Nm - 2; k++) {
int idx = Nm - 2 - k;
// TODO use RitzFilter enum here
// if (std::abs(S(idx, idx)) < std::abs(S(idx+1, idx+1))) { // sort by largest modulus of eigenvalue
// if (std::real(S(idx, idx)) > std::real(S(idx+1, idx+1))) { // sort by smallest real eigenvalue
if ( cCompare(S(idx+1, idx+1), S(idx, idx)) ) { // sort by largest modulus of eigenvalue
swapEvals(idx);
nswap++;
}
}
}
std::cout << GridLogMessage << "schurReorder end "<< nswap << std::endl;
for (int i = 0; i < Nk; i++) {
std::cout << GridLogMessage << "schurR "<<i<<" "<<S(i,i) << std::endl;
}
return;
}
void schurReorderBlock() {
// TODO method stub
return;
}
};
// template<class Field>
// inline void writeFile(const Field &field, const std::string &fname) {
// emptyUserRecord record;
// ScidacWriter WR(field.Grid()->IsBoss());
// WR.open(fname);
// WR.writeScidacFieldRecord(field, record, 0); // 0 = Lexico
// WR.close();
// }
/**
* Implementation of the Krylov-Schur algorithm.
*/
template<class Field>
class KrylovSchur {
private:
std::string cname = std::string("KrylovSchur");
int MaxIter; // Max iterations
RealD Tolerance;
RealD ssq;
RealD rtol;
int Nm; // Number of basis vectors to track (equals MaxIter if no restart)
int Nk; // Number of basis vectors to keep every restart (equals -1 if no restart)
int Nstop; // Stop after converging Nstop eigenvectors.
LinearOperatorBase<Field> &Linop;
GridBase *Grid;
RealD approxLambdaMax;
RealD beta_k;
Field u; // Residual vector perpendicular to Krylov space (u_{k+1} in notes)
Eigen::VectorXcd b; // b vector in Schur decomposition (e_{k+1} in Arnoldi).
std::vector<Field> basis; // orthonormal Krylov basis
Eigen::MatrixXcd Rayleigh; // Rayleigh quotient of size Nbasis (after construction)
Eigen::MatrixXcd Qt; // Transpose of basis rotation which projects out high modes.
Eigen::VectorXcd evals; // evals of Rayleigh quotient
std::vector<RealD> ritzEstimates; // corresponding ritz estimates for evals
Eigen::MatrixXcd littleEvecs; // Nm x Nm evecs matrix
RitzFilter ritzFilter; // how to sort evals
public:
bool doEvalCheck = false;
KrylovSchur(LinearOperatorBase<Field> &_Linop, GridBase *_Grid, RealD _Tolerance, RitzFilter filter = EvalReSmall)
: Linop(_Linop), Grid(_Grid), Tolerance(_Tolerance), ritzFilter(filter), u(_Grid), MaxIter(-1), Nm(-1), Nk(-1), Nstop (-1),
evals (0), ritzEstimates (), evecs (), ssq (0.0), rtol (0.0), beta_k (0.0), approxLambdaMax (0.0)
{
u = Zero();
};
std::vector<Field> evecs; // Vector of evec fields
/* Getters */
int getNk() { return Nk; }
Eigen::MatrixXcd getRayleighQuotient() { return Rayleigh; }
Field getU() { return u; }
std::vector<Field> getBasis() { return basis; }
Eigen::VectorXcd getEvals() { return evals; }
std::vector<RealD> getRitzEstimates() { return ritzEstimates; }
std::vector<Field> getEvecs() { return evecs; }
/**
* Runs the non-harmonic Krylov-Schur loop.
* - Runs an Arnoldi step to generate the Rayleigh quotient and Krylov basis.
* - Schur decompose the Rayleigh quotient.
* - Permutes the Rayleigh quotient according to the eigenvalues.
* - Truncate the Krylov-Schur expansion.
*/
void operator()(const Field& v0, int _maxIter, int _Nm, int _Nk, int _Nstop, bool doubleOrthog = true) {
MaxIter = _maxIter;
Nm = _Nm; Nk = _Nk;
Nstop = _Nstop;
ssq = norm2(v0);
RealD approxLambdaMax = approxMaxEval(v0);
rtol = Tolerance * approxLambdaMax;
std::cout << GridLogMessage << "Approximate max eigenvalue: " << approxLambdaMax << std::endl;
b = Eigen::VectorXcd::Zero(Nm); // start as e_{k+1}
b(Nm-1) = 1.0;
int start = 0;
Field startVec = v0;
littleEvecs = Eigen::MatrixXcd::Zero(Nm, Nm);
for (int i = 0; i < MaxIter; i++) {
std::cout << GridLogMessage << "Restart Iteration " << i << std::endl;
// Perform Arnoldi steps to compute Krylov basis and Rayleigh quotient (Hess)
arnoldiIteration(startVec, Nm, start, doubleOrthog);
startVec = u;
start = Nk;
std::cout << GridLogDebug << "b after Arnoldi " << b << std::endl;
// --- 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<Field> 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<Field> (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;
}
}
/**
* Runs the harmonic (shifted) Krylov-Schur loop: extracts Ritz values of a
* shift-augmented Rayleigh quotient so that eigenvalues near `*_shift` are
* reordered to the top instead of the extremal ones.
*/
void operator()(const Field& v0, int _maxIter, int _Nm, int _Nk, int _Nstop, RealD *_shift, bool doubleOrthog = true) {
assert(_shift && "harmonic KrylovSchur: shift must be non-null");
RealD shiftVal = *_shift;
MaxIter = _maxIter;
Nm = _Nm; Nk = _Nk;
Nstop = _Nstop;
ssq = norm2(v0);
RealD approxLambdaMax = approxMaxEval(v0);
rtol = Tolerance * approxLambdaMax;
std::cout << GridLogMessage << "Approximate max eigenvalue: " << approxLambdaMax << std::endl;
b = Eigen::VectorXcd::Zero(Nm); // start as e_{k+1}
b(Nm-1) = 1.0;
int start = 0;
Field startVec = v0;
littleEvecs = Eigen::MatrixXcd::Zero(Nm, Nm);
for (int i = 0; i < MaxIter; i++) {
std::cout << GridLogMessage << "Restart Iteration " << i << std::endl;
// Perform Arnoldi steps to compute Krylov basis and Rayleigh quotient (Hess)
arnoldiIteration(startVec, Nm, start, doubleOrthog);
startVec = u;
start = Nk;
std::cout << GridLogDebug << "b after Arnoldi " << b << std::endl;
// --- 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<Nm;m++) temp(m,m) -= shiftVal;
Eigen::MatrixXcd RayleighS = temp.inverse(); // (B-tI)^-1
Eigen::MatrixXcd temp2 = RayleighS.adjoint(); //(B-tI)^-1*
Eigen::VectorXcd g = temp2*b; //g = (B-tI)^-1* * b
Eigen::MatrixXcd Btilde = Rayleigh + g*(b.adjoint());
Field utilde(Grid);
utilde = u;
for (int j = 0; j<Nm; j++){
utilde -= basis[j]*g(j);
}
ComplexSchurDecomposition schurS (Btilde, false, ritzFilter);
std::cout << GridLogMessage << "Shifted Schur eigenvalues shift = "<<shiftVal << std::endl;
schurS.schurReorder(Nk);
Eigen::MatrixXcd Q_s = schurS.getMatrixQ();
Eigen::MatrixXcd Qt_s = Q_s.adjoint(); // TODO should Q be real?
Btilde=schurS.getMatrixS();
Eigen::VectorXcd b_s = Q_s*b; // Q is Qt in SlepC, b_s=bhat
std::vector<Field> basis2_s;
constructUR(basis2_s, basis, Qt_s, Nm,Nk);
Eigen::MatrixXcd RayTmp_s = Btilde(Eigen::seqN(0, Nk), Eigen::seqN(0, Nk));
Btilde = RayTmp_s;
basis2_s = std::vector<Field> (basis2_s.begin(), basis2_s.begin() + Nk);
b_s = b_s.head(Nk).eval();
Eigen::VectorXcd ghat = -Q_s * g;
ghat = ghat.head(Nk).eval();
Field uhat(Grid);
uhat = utilde;
for (int j = 0; j<Nk; j++){
uhat -= basis2_s[j]*ghat(j);
}
RealD gamma = std::sqrt(norm2(uhat));
uhat *= 1.0/gamma;
std::cout << GridLogMessage << " gamma "<<gamma << std::endl;
Btilde += ghat*(b_s.adjoint());
b_s *=gamma;
Rayleigh = Btilde;
basis= basis2_s;
b = b_s;
beta_k = gamma;
u= uhat;
checkKSDecomposition();
computeEigensystem(Rayleigh);
std::cout << GridLogMessage << "Eigenvalues (first Nk sorted): " << std::endl << evals << std::endl;
if (checkConvergedAndReport(i)) return;
}
}
private:
/**
* Shared post-restart convergence check. On convergence (or the final
* iteration) reports eigenvalues and optionally the explicit residual
* check. Returns true if operator() should return.
*/
bool checkConvergedAndReport(int i) {
int Nconv = converged();
std::cout << GridLogMessage << "Number of evecs converged: " << Nconv << std::endl;
if (Nconv >= Nstop || i == MaxIter - 1) {
std::cout << GridLogMessage << "Converged with " << Nconv << " / " << Nstop << " eigenvectors on iteration "
<< i << "." << std::endl;
std::cout << GridLogMessage << "Eigenvalues: " << std::endl << evals << std::endl;
if (doEvalCheck) {
Field w(Grid);
for (int k = 0; k < (int)evecs.size(); k++) {
Linop.Op(evecs[k], w);
ComplexD eval_est = toStdCmplx(innerProduct(evecs[k], w));
w -= eval_est * evecs[k];
RealD res = std::sqrt(norm2(w));
std::cout << GridLogMessage << "KrylovSchur: evec[" << k << "]"
<< " eval_reported = " << evals[k]
<< " eval_est = " << eval_est
<< " || A v - eval_est * v || = " << res << std::endl;
}
}
return true;
}
return false;
}
public:
/**
* Constructs the Arnoldi basis for the Krylov space K_n(D, src). (TODO make private)
*
* Parameters
* ----------
* v0 : Field&
* Source to generate Krylov basis.
* Nm : int
* Final size of the basis desired. If the basis becomes complete before a basis of size Nm is constructed
* (determined by relative tolerance Tolerance), stops iteration there.
* doubleOrthog : bool (default = false)
* Whether to double orthogonalize the basis (for numerical cancellations) or not.
* start : int (default = 0)
* If non-zero, assumes part of the Arnoldi basis has already been constructed.
*/
void arnoldiIteration(const Field& v0, int Nm, int start = 0, bool doubleOrthog = true)
{
ComplexD coeff;
Field w (Grid); // A acting on last Krylov vector.
// basis.reserve(Nm);
// for (int i = start; i < Nm; i++) {
// basis.emplace_back(Grid);
// }
// basis.assign(Nm, Field(Grid));
// basis.resize(Nm);
// for (int i = start; i < Nm; i++) {
// basis[i] = Field(Grid);
// }
if (start == 0) { // initialize everything that we need.
RealD v0Norm = 1 / std::sqrt(ssq);
basis.push_back(v0Norm * v0); // normalized source
// basis[0] = v0Norm * v0; // normalized source
Rayleigh = Eigen::MatrixXcd::Zero(Nm, Nm);
u = Zero();
} else {
// assert( start == basis.size() ); // should be starting at the end of basis (start = Nk)
std::cout << GridLogMessage << "Resetting Rayleigh and b" << std::endl;
Eigen::MatrixXcd RayleighCp = Rayleigh;
Rayleigh = Eigen::MatrixXcd::Zero(Nm, Nm);
Rayleigh(Eigen::seqN(0, Nk), Eigen::seqN(0, Nk)) = RayleighCp;
// append b^\dag to Rayleigh, add u to basis
Rayleigh(Nk, Eigen::seqN(0, Nk)) = b.adjoint();
basis.push_back(u);
// basis[start] = u; // TODO make sure this is correct
b = Eigen::VectorXcd::Zero(Nm);
}
// Construct next Arnoldi vector by normalizing w_i = Dv_i - \sum_j v_j h_{ji}
for (int i = start; i < Nm; i++) {
Linop.Op(basis.back(), w);
// Linop.Op(basis[i], w);
for (int j = 0; j < basis.size(); j++) {
coeff = innerProduct(basis[j], w); // coeff = h_{ij}. Note that since {vi} is ONB it's OK to subtract it off after.
Rayleigh(j, i) = toStdCmplx(coeff);
w -= coeff * basis[j];
}
if (doubleOrthog) {
std::cout << GridLogDebug << "Double orthogonalizing." << std::endl;
for (int j = 0; j < basis.size(); j++) {
coeff = innerProduct(basis[j], w); // see if there is any residual component in basis[j] direction
Rayleigh(j, i) += toStdCmplx(coeff); // if coeff is non-zero, adjust Rayleigh
w -= coeff * basis[j];
}
}
// add w_i to the pile
if (i < Nm - 1) {
coeff = std::sqrt(norm2(w));
Rayleigh(i+1, i) = toStdCmplx(coeff);
basis.push_back(
(1.0/coeff) * w
);
// basis[i+1] = (1.0/coeff) * w;
}
// after iterations, update u and beta_k = ||u|| before norm
u = w; // make sure u is normalized
beta_k = std::sqrt(norm2(u)); // beta_k = ||f_k|| determines convergence.
u = (1/beta_k) * u;
}
b(Nm - 1) = beta_k;
// std::cout << GridLogMessage << "|f|^2 after Arnoldi step = " << norm2(f) << std::endl;
std::cout << GridLogMessage << "beta_k = |u| (before norm) after Arnoldi step = " << beta_k << std::endl;
std::cout << GridLogDebug << "Computed Rayleigh quotient = " << std::endl << Rayleigh << std::endl;
return;
}
/**
* Approximates the eigensystem of the linear operator by computing the eigensystem of
* the Rayleigh quotient. Assumes that the Rayleigh quotient has already been constructed (by
* calling the operator() function).
*
* Parameters
* ----------
* Eigen::MatrixXcd& S
* Schur matrix (upper triangular) similar to original Rayleigh quotient.
*/
void computeEigensystem(Eigen::MatrixXcd& S)
{
std::cout << GridLogMessage << "Computing eigenvalues." << std::endl;
evecs.clear();
Eigen::ComplexEigenSolver<Eigen::MatrixXcd> es;
es.compute(S);
// Sort eigenvalues/evecs to match the schurReorder ordering.
int n = es.eigenvalues().size();
ComplexComparator cComp(ritzFilter);
std::vector<int> idx(n);
std::iota(idx.begin(), idx.end(), 0);
std::sort(idx.begin(), idx.end(), [&](int a, int b){
return cComp(toStdCmplx(es.eigenvalues()(a)), toStdCmplx(es.eigenvalues()(b)));
});
evals.resize(n);
littleEvecs.resize(n, n);
for (int k = 0; k < n; k++) {
evals(k) = es.eigenvalues()(idx[k]);
littleEvecs.col(k) = es.eigenvectors().col(idx[k]);
}
// Convert evecs to lattice fields
for (int k = 0; k < n; k++) {
Eigen::VectorXcd vec = littleEvecs.col(k);
Field tmp (basis[0].Grid());
tmp = Zero();
for (int j = 0; j < (int)basis.size(); j++) {
tmp = tmp + vec[j] * basis[j];
}
evecs.push_back(tmp);
}
}
/**
* Approximates the maximum eigenvalue of Linop.Op to normalize the residual and test for convergence.
*
* TODO implement in parent class eventually
*
* Parameters
* ----------
* Field& v0
* Source field to start with. Must have non-zero norm.
* int MAX_ITER (default = 50)
* Maximum number of iterations for power approximation.
*
* Returns
* -------
* RealD lamApprox
* Approximation of largest eigenvalue.
*/
RealD approxMaxEval(const Field& v0, int MAX_ITER = 50) {
assert (norm2(v0) > 1e-8); // must have relatively large source norm to start
RealD lamApprox = 0.0;
RealD denom = 1.0; RealD num = 1.0;
Field v0cp (Grid); Field tmp (Grid);
v0cp = v0;
denom = std::sqrt(norm2(v0cp));
for (int i = 0; i < MAX_ITER; i++) {
Linop.Op(v0cp, tmp); // CAREFUL: do not do Op(tmp, tmp)
v0cp = tmp;
num = std::sqrt(norm2(v0cp)); // num = |A^{n+1} v0|
lamApprox = num / denom; // lam = |A^{n+1} v0| / |A^n v0|
std::cout << GridLogDebug << "Approx for max eval: " << lamApprox << std::endl;
denom = num; // denom = |A^{n} v0|
}
return lamApprox;
}
/**
* Computes the number of Krylov-Schur eigenvectors that have converged. An eigenvector s is considered converged
* for a tolerance epsilon if
* r(s) := |\beta e_m^T s| < epsilon
* where beta is the norm of f_{m+1}.
*
* TODO implement in parent class eventually
*
* Parameters
* ----------
*
* Returns
* -------
* int : Number of converged eigenvectors.
*/
int converged() {
int Nconv = 0;
int _Nm = evecs.size();
std::cout << GridLogDebug << "b: " << b << std::endl;
Field tmp (Grid); Field fullEvec (Grid);
ritzEstimates.clear();
// ritzEstimates.resize(_Nm);
for (int k = 0; k < _Nm; k++) {
Eigen::VectorXcd evec_k = littleEvecs.col(k);
RealD ritzEstimate = std::abs(b.dot(evec_k)); // b^\dagger s
ritzEstimates.push_back(ritzEstimate);
std::cout << GridLogMessage << "Ritz estimate for evec " << k << " = " << ritzEstimate << std::endl;
if (ritzEstimate < rtol) {
Nconv++;
} else {
break;
}
}
// Check that Ritz estimate is explicitly || D (Uy) - lambda (Uy) ||
// checkRitzEstimate();
return Nconv;
}
/**
* Checks the Krylov-Schur decomposition DU = UR + f b^\dag with the last-computed
* U, R, f, and b.
*/
bool checkKSDecomposition(RealD tol = 1e-8) {
std::cout << GridLogMessage << "*** CHECKING KRYLOV-SCHUR DECOMPOSITION *** " << std::endl;
int k = basis.size(); // number of basis vectors, also the size of Rayleigh.
// rotate basis by Rayleigh to construct UR
// std::vector<Field> rotated;
// std::cout << GridLogDebug << "Rayleigh in KSDecomposition: " << std::endl << Rayleigh << std::endl;
std::vector<Field> rotated = basis;
constructUR(rotated, basis, Rayleigh, k,k); // manually rotate
// Eigen::MatrixXcd Rt = Rayleigh.adjoint();
// basisRotate(rotated, Rt, 0, k, 0, k, k); // UR
// TODO: make a new function that I'm positive does what this is doing
// just take the basis U = (u1 u2 ... uNm) and form the linear combination UR from R
// For each i, form D u(i) and subtract off (US - u b^\dag)(i)
RealD delta = 0.0; RealD deltaSum = 0;
Field tmp (Grid); tmp = Zero();
for (int i = 0; i < k; i++) {
Linop.Op(basis[i], tmp); // tmp = D u(i)
delta = norm2(tmp - rotated[i] - u * std::conj(b(i)));
delta = delta / norm2(tmp); // relative tolerance
deltaSum += delta;
// std::cout << GridLogDebug << "Iteration " << i << std::endl;
// std::cout << GridLogDebug << "Du = " << norm2(tmp) << std::endl;
// std::cout << GridLogDebug << "rotated = " << norm2(rotated[i]) << std::endl;
// std::cout << GridLogDebug << "b[i] = " << b(i) << std::endl;
std::cout << GridLogMessage << "Deviation in decomp, column " << i << ": " << delta << std::endl;
}
std::cout << GridLogMessage << "Squared sum of relative deviations in decomposition: " << deltaSum << std::endl;
// std::cout << "[DEBUG] testing basis rotate" << std::endl;
// std::vector<Field> rotated2;
// constructUR(rotated2, basis, Rayleigh, k);
// for (int i = 0; i < k; i++) {
// std::cout << "rotated[i] - UR[i] = " << norm2(rotated[i] - rotated2[i]) << std::endl;
// }
return deltaSum < tol;
}
/**
* Checks the Ritz vector s was rotated correctly by explicitly recomputing the
* eigenvectors of the rotated Rayleigh quotient.
*/
bool checkRitzRotation(RealD tol = 1e-8) {
std::cout << GridLogMessage << "*** CHECKING RITZ VECTOR ROTATION *** " << std::endl;
Eigen::ComplexEigenSolver<Eigen::MatrixXcd> es;
es.compute(Rayleigh);
Eigen::MatrixXcd littleEvecs2 = es.eigenvectors();
RealD dLittle = (littleEvecs2 - littleEvecs).norm() / littleEvecs.norm();
std::cout << GridLogMessage << "|littleEvecs2 - littleEvecs| / |littleEvecs| = " << dLittle << std::endl;
std::cout << GridLogMessage << "Forming full eigenvectors" << std::endl;
RealD delta = 0.0; RealD deltaSum = 0;
for (int k = 0; k < evals.size(); k++) {
Eigen::VectorXcd vec = littleEvecs.col(k);
Field tmpEvec (Grid);
tmpEvec = Zero();
for (int j = 0; j < basis.size(); j++) {
tmpEvec = tmpEvec + vec[j] * basis[j];
}
delta = norm2(tmpEvec - evecs[k]) / norm2(evecs[k]);
std::cout << GridLogMessage << "Deviation in evec " << k << ": " << delta << std::endl;
deltaSum += delta;
}
return deltaSum < tol;
}
/**
* Checks the Ritz estimate R(s) is indeed the deviation of a Ritz eigenvector from being a true eigenvector.
*/
void checkRitzEstimate(RealD tol = 1e-8) {
std::cout << GridLogMessage << "*** CHECKING RITZ ESTIMATE *** " << std::endl;
// The issue was that the Eigen::eigensolver occasionally returned the complex conjugate pairs in the wrong
// order compared to the diagonal, which is how I was reading them out. When this happened, the Ritz estimate would
// be wrong. So, just need to be more careful and actually read out the eigenvalues.
Field tmp (Grid);
// std::cout << "n evecs: " << evecs.size() << std::endl;
for (int k = 0; k < evecs.size(); k++) {
tmp = Zero();
Linop.Op(evecs[k], tmp); // D evec
RealD ritz = std::sqrt(norm2(tmp - evals[k] * evecs[k]));
std::cout << "Ritz estimate " << k << " = " << ritz << std::endl;
// Checking little Ritz estimate
// Eigen::VectorXcd littleEvec = littleEvecs.col(k);
// Eigen::VectorXcd dev = Rayleigh * littleEvec - evals[k] * littleEvec;
// std::cout << GridLogMessage << "Little Ritz estimate = " << dev.norm() << std::endl;
}
return;
}
/**
* Given a vector of fields U (equivalently, a LxN matrix, where L is the number of degrees of
* freedom on the lattice field) and an NxN matrix R, forms the product UR.
* Only the first N2 output columns are computed; columns [N2, N) are Zero.
*
* Implemented with the accelerator basisRotate kernel, which computes
* basis[j] <- sum_k Qt(j,k) basis[k] in place, so we copy U into UR and
* pass R^T. The coefficients are staged as ComplexD (thrust::complex on
* GPU builds) since std::complex has no device arithmetic with Grid tensors.
*/
void constructUR(std::vector<Field>& UR, std::vector<Field> &U, Eigen::MatrixXcd& R, int N, int N2) {
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 = U;
basisRotate(UR, Rt, 0, N2, 0, N, N);
for (int i = N2; i < N; i++) UR[i] = Zero();
return;
}
/**
* Same as constructUR but for the product order RU.
*/
void constructRU(std::vector<Field>& RU, std::vector<Field> &U, Eigen::MatrixXcd& R, int N) {
Field tmp (Grid);
RU.clear();
// RU.resize(N);
for (int i = 0; i < N; i++) {
tmp = Zero();
for (int j = 0; j < N; j++) {
tmp = tmp + R(i, j) * U[j];
}
RU.push_back(tmp);
// RU[i] = tmp;
}
return;
}
// void writeEvec(Field& in, std::string const fname){
// #ifdef HAVE_LIME
// // Ref: https://github.com/paboyle/Grid/blob/feature/scidac-wp1/tests/debug/Test_general_coarse_hdcg_phys48.cc#L111
// std::cout << GridLogMessage << "Writing evec to: " << fname << std::endl;
// Grid::emptyUserRecord record;
// Grid::ScidacWriter WR(in.Grid()->IsBoss());
// WR.open(fname);
// WR.writeScidacFieldRecord(in,record,0); // Lexico
// WR.close();
// #endif
// // What is the appropriate way to throw error?
// }
// /**
// * Writes the eigensystem of a Krylov Schur object to a directory.
// *
// * Parameters
// * ----------
// * std::string path
// * Directory to write to.
// */
// void writeEigensystem(std::string outDir) {
// std::cout << GridLogMessage << "Writing output to directory: " << outDir << std::endl;
// // TODO write a scidac density file so that we can easily integrate with visualization toolkit
// std::string evalPath = outDir + "/evals.txt";
// std::ofstream fEval;
// fEval.open(evalPath);
// for (int i = 0; i < Nk; i++) {
// // write Eigenvalues
// fEval << i << " " << evals(i);
// if (i < Nk - 1) { fEval << "\n"; }
// }
// fEval.close();
// for (int i = 0; i < Nk; i++) {
// std::string fName = outDir + "/evec" + std::to_string(i);
// // writeFile(evecs[i], fName); // using method from Grid/HMC/ComputeWilsonFlow.cc
// // writeEvec(evecs[i], fName);
// }
// }
};
NAMESPACE_END(Grid);
#endif
@@ -0,0 +1,276 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./Grid/algorithms/iterative/LanczosBidiagonalization.h
Copyright (C) 2015
Author: Chulwoo Jung <chulwoo@bnl.gov>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#ifndef GRID_LANCZOS_BIDIAGONALIZATION_H
#define GRID_LANCZOS_BIDIAGONALIZATION_H
NAMESPACE_BEGIN(Grid);
/**
* Lanczos Bidiagonalization (Golub-Kahan)
*
* For a linear operator A with adjoint A^dag, constructs the bidiagonal
* decomposition:
*
* A V_m = U_m B_m
* A^dag U_m = V_m B_m^T + beta_{m+1} v_{m+1} e_m^T
*
* where:
* V_m = [v_1, ..., v_m] right Lanczos vectors (orthonormal)
* U_m = [u_1, ..., u_m] left Lanczos vectors (orthonormal)
* B_m is upper bidiagonal with diag(alpha_1,...,alpha_m) and
* superdiag(beta_2,...,beta_m)
*
* The singular values of A are approximated by those of B_m.
* The singular values of B_m are the square roots of the eigenvalues of
* the symmetric tridiagonal matrix B_m^T B_m.
*
* Usage:
* LanczosBidiagonalization<Field> lb(Linop, grid);
* lb.run(src, Nm, tol);
* // Access results via getters.
*/
template <class Field>
class LanczosBidiagonalization {
public:
LinearOperatorBase<Field> &Linop;
GridBase *Grid;
int Nm; // number of Lanczos steps taken
RealD Tolerance; // convergence threshold on beta_{k+1} / alpha_k
std::vector<Field> V; // right Lanczos vectors v_1 ... v_m
std::vector<Field> U; // left Lanczos vectors u_1 ... u_m
std::vector<RealD> alpha; // diagonal of bidiagonal matrix
std::vector<RealD> beta; // super-diagonal (beta[k] couples u_k and v_{k+1})
// SVD of the bidiagonal matrix (filled after computeSVD())
Eigen::VectorXd singularValues;
Eigen::MatrixXd leftSVecs; // columns are left singular vectors of B
Eigen::MatrixXd rightSVecs; // columns are right singular vectors of B
public:
LanczosBidiagonalization(LinearOperatorBase<Field> &_Linop, GridBase *_Grid,
RealD _tol = 1.0e-8)
: Linop(_Linop), Grid(_Grid), Tolerance(_tol), Nm(0)
{}
/**
* Run the Golub-Kahan Lanczos bidiagonalization.
*
* Parameters
* ----------
* src : starting vector (need not be normalised)
* Nmax : maximum number of Lanczos steps
* reorth : if true, full reorthogonalisation of both V and U bases
*/
void run(const Field &src, int Nmax, bool reorth = true)
{
assert(norm2(src) > 0.0);
V.clear(); U.clear();
alpha.clear(); beta.clear();
Nm = 0;
Field p(Grid), r(Grid);
// --- initialise: v_1 = src / ||src|| ---
Field v(Grid);
v = src;
RealD nrm = std::sqrt(norm2(v));
v = (1.0 / nrm) * v;
V.push_back(v);
for (int k = 0; k < Nmax; ++k) {
// p = A v_k
Linop.Op(V[k], p);
// p = p - beta_k * u_{k-1} (remove previous left vector)
if (k > 0) {
p = p - beta[k-1] * U[k-1];
}
// alpha_k = ||p||
RealD ak = std::sqrt(norm2(p));
if (ak < 1.0e-14) {
std::cout << GridLogMessage
<< "LanczosBidiagonalization: lucky breakdown at step "
<< k << " (alpha = " << ak << ")" << std::endl;
break;
}
alpha.push_back(ak);
// u_k = p / alpha_k
Field u(Grid);
u = (1.0 / ak) * p;
// full reorthogonalisation of u against previous U
if (reorth) {
for (int j = 0; j < (int)U.size(); ++j) {
ComplexD ip = innerProduct(U[j], u);
u = u - ip * U[j];
}
RealD unrm = std::sqrt(norm2(u));
if (unrm > 1.0e-14) u = (1.0 / unrm) * u;
}
U.push_back(u);
// r = A^dag u_k - alpha_k * v_k
Linop.AdjOp(U[k], r);
r = r - ak * V[k];
// full reorthogonalisation of r against previous V
if (reorth) {
for (int j = 0; j < (int)V.size(); ++j) {
ComplexD ip = innerProduct(V[j], r);
r = r - ip * V[j];
}
}
// beta_{k+1} = ||r||
RealD bk = std::sqrt(norm2(r));
beta.push_back(bk);
Nm = k + 1;
std::cout << GridLogMessage
<< "LanczosBidiagonalization step " << k
<< " alpha = " << ak
<< " beta = " << bk << std::endl;
// convergence: residual beta / alpha small enough
if (bk / ak < Tolerance) {
std::cout << GridLogMessage
<< "LanczosBidiagonalization converged at step " << k
<< " (beta/alpha = " << bk / ak << ")" << std::endl;
break;
}
if (k == Nmax - 1) break; // no v_{k+2} needed after last step
// v_{k+1} = r / beta_{k+1}
Field vnext(Grid);
vnext = (1.0 / bk) * r;
V.push_back(vnext);
}
}
/**
* Compute the SVD of the bidiagonal matrix B using Eigen.
* Singular values are stored in descending order.
*/
void computeSVD()
{
int m = Nm;
Eigen::MatrixXd B = Eigen::MatrixXd::Zero(m, m);
for (int k = 0; k < m; ++k) {
B(k, k) = alpha[k];
if (k + 1 < m && k < (int)beta.size())
B(k, k+1) = beta[k];
}
Eigen::JacobiSVD<Eigen::MatrixXd> svd(B,
Eigen::ComputeThinU | Eigen::ComputeThinV);
singularValues = svd.singularValues(); // already sorted descending
leftSVecs = svd.matrixU();
rightSVecs = svd.matrixV();
std::cout << GridLogMessage
<< "LanczosBidiagonalization: singular values of B_" << m
<< std::endl;
for (int k = 0; k < m; ++k)
std::cout << GridLogMessage << " sigma[" << k << "] = "
<< singularValues(k) << std::endl;
}
/**
* Return the k-th approximate left singular vector of A in the full
* lattice space. computeSVD() must have been called first.
*/
Field leftSingularVector(int k)
{
assert(k < (int)leftSVecs.cols());
Field svec(Grid);
svec = Zero();
for (int j = 0; j < Nm; ++j)
svec = svec + leftSVecs(j, k) * U[j];
return svec;
}
/**
* Return the k-th approximate right singular vector of A in the full
* lattice space. computeSVD() must have been called first.
*/
Field rightSingularVector(int k)
{
assert(k < (int)rightSVecs.cols());
Field svec(Grid);
svec = Zero();
for (int j = 0; j < Nm; ++j)
svec = svec + rightSVecs(j, k) * V[j];
return svec;
}
/**
* Verify the bidiagonalization: returns max residual
* max_k || A v_k - alpha_k u_k - beta_k u_{k-1} ||
*/
RealD verify()
{
Field tmp(Grid);
RealD maxres = 0.0;
for (int k = 0; k < Nm; ++k) {
Linop.Op(V[k], tmp);
tmp = tmp - alpha[k] * U[k];
if (k > 0 && k-1 < (int)beta.size())
tmp = tmp - beta[k-1] * U[k-1];
RealD res = std::sqrt(norm2(tmp));
if (res > maxres) maxres = res;
std::cout << GridLogMessage
<< "LanczosBidiagonalization verify step " << k
<< " ||A v_k - alpha_k u_k - beta_{k-1} u_{k-1}|| = "
<< res << std::endl;
}
return maxres;
}
/* Getters */
int getNm() const { return Nm; }
const std::vector<Field>& getV() const { return V; }
const std::vector<Field>& getU() const { return U; }
const std::vector<RealD>& getAlpha() const { return alpha; }
const std::vector<RealD>& getBeta() const { return beta; }
Eigen::VectorXd getSingularValues() const { return singularValues; }
};
NAMESPACE_END(Grid);
#endif
@@ -60,18 +60,29 @@ public:
void Level(int lv) { level=lv; };
PrecGeneralisedConjugateResidualNonHermitian(RealD tol,Integer maxit,LinearOperatorBase<Field> &_Linop,LinearFunction<Field> &Prec,int _mmax,int _nstep) :
PrecGeneralisedConjugateResidualNonHermitian(RealD tol,Integer maxit,LinearOperatorBase<Field> &_Linop,LinearFunction<Field> &Prec,int _mmax, int _nstep) :
Tolerance(tol),
MaxIterations(maxit),
Linop(_Linop),
Preconditioner(Prec),
mmax(_mmax),
nstep(_nstep)
nstep(_nstep) // what is nstep vs mmax? one is the number of inner iterations
{
level=1;
verbose=1;
};
// virtual method stubs for updating GCR polynomial
virtual void LogBegin(void){
std::cout << "GCR::LogBegin() "<<std::endl;
};
virtual void LogIteration(int k, ComplexD a, std::vector<ComplexD> betas){
std::cout << "GCR::LogIteration() "<<std::endl;
};
virtual void LogComplete(std::vector<ComplexD>& alphas, std::vector<std::vector<ComplexD>>& betas) {
std::cout << "GCR::LogComplete() "<<std::endl;
};
void operator() (const Field &src, Field &psi){
// psi=Zero();
@@ -96,19 +107,18 @@ public:
GCRLogLevel <<"PGCR("<<mmax<<","<<nstep<<") "<< steps <<" steps cp = "<<cp<<" target "<<rsq <<std::endl;
if(cp<rsq) {
SolverTimer.Stop();
SolverTimer.Stop();
Linop.Op(psi,r);
axpy(r,-1.0,src,r);
RealD tr = norm2(r);
GCRLogLevel<<"PGCR: Converged on iteration " <<steps
<< " computed residual "<<sqrt(cp/ssq)
<< " true residual " <<sqrt(tr/ssq)
<< " target " <<Tolerance <<std::endl;
Linop.Op(psi,r);
axpy(r,-1.0,src,r);
RealD tr = norm2(r);
GCRLogLevel<<"PGCR: Converged on iteration " <<steps
<< " computed residual "<<sqrt(cp/ssq)
<< " true residual " <<sqrt(tr/ssq)
<< " target " <<Tolerance <<std::endl;
GCRLogLevel<<"PGCR Time elapsed: Total "<< SolverTimer.Elapsed() <<std::endl;
return;
GCRLogLevel<<"PGCR Time elapsed: Total "<< SolverTimer.Elapsed() <<std::endl;
return;
}
}
@@ -135,9 +145,9 @@ public:
////////////////////////////////
// history for flexible orthog
////////////////////////////////
std::vector<Field> q(mmax,grid);
std::vector<Field> p(mmax,grid);
std::vector<RealD> qq(mmax);
std::vector<Field> q(mmax,grid); // q = Ap
std::vector<Field> p(mmax,grid); // store mmax conjugate momenta
std::vector<RealD> qq(mmax); // qq = (Ap)^2 = <p|A^\dagger A |p> (denom of \alpha)
GCRLogLevel<< "PGCR nStep("<<nstep<<")"<<std::endl;
@@ -155,7 +165,9 @@ public:
LinalgTimer.Start();
r=src-Az;
LinalgTimer.Stop();
GCRLogLevel<< "PGCR true residual r = src - A psi "<<norm2(r) <<std::endl;
GCRLogLevel<< "PGCR true residual r = src - A psi "<< norm2(r) <<std::endl;
this->LogBegin(); // initialize polynomial GCR if needed (TODO think about placement of this)
/////////////////////
// p = Prec(r)
@@ -178,32 +190,45 @@ public:
p[0]= z;
q[0]= Az;
qq[0]= zAAz;
std::cout << "||init p - src||: " << norm2(p[0] - src) << std::endl; // for debugging
cp =norm2(r);
LinalgTimer.Stop();
std::vector<ComplexD> all_alphas;
std::vector<std::vector<ComplexD>> all_betas;
for(int k=0;k<nstep;k++){
steps++;
int kp = k+1;
int peri_k = k %mmax;
int peri_k = k %mmax; // only store mmax vectors; just roll around if needed
int peri_kp= kp%mmax;
// std::cout << "peri_kp = " << peri_kp << std::endl;
LinalgTimer.Start();
rq= innerProduct(q[peri_k],r); // what if rAr not real?
a = rq/qq[peri_k];
a = rq/qq[peri_k]; // compute alpha_j
axpy(psi,a,p[peri_k],psi);
all_alphas.push_back(a);
cp = axpy_norm(r,-a,q[peri_k],r);
axpy(psi,a,p[peri_k],psi); // update psi --> psi + \alpha p
cp = axpy_norm(r,-a,q[peri_k],r); // update r --> r - \alpha D p. Note q = Dp
LinalgTimer.Stop();
GCRLogLevel<< "PGCR step["<<steps<<"] resid " << cp << " target " <<rsq<<std::endl;
// LogIterationA(k + 1, a);
if((k==nstep-1)||(cp<rsq)){
return cp;
}
GCRLogLevel<< "GCR step["<<steps<<"] resid " << cp << " target " <<rsq<<std::endl;
// moving this to end of loop so that it doesn't exit beforehand
// TODO if I want to uncomment this, I have to split the LogIteration again and put LogIterationA() beforehand
// if((k==nstep-1)||(cp<rsq)){
// return cp;
// }
PrecTimer.Start();
@@ -221,22 +246,205 @@ public:
q[peri_kp]=Az;
p[peri_kp]=z;
// Field Dsrc (grid);
// Linop.Op(src, Dsrc);
// std::cout << "||q[peri_kp] - D(src)||: " << norm2(q[peri_kp] - Dsrc) << std::endl; // for debugging
// // delete after testing
// std::cout << "Testing Dsq on one for GCR: " << std::endl;
// Field myField (grid);
// myField = 1.0;
// Field out1 (grid); Field out2 (grid);
// Linop.HermOp(myField, out1);
// Linop.Op(myField, out2);
// std::cout << "Dsq.Hermop(ones) has norm " << norm2(out1) << std::endl;
// std::cout << "Dsq.Op(ones) has norm " << norm2(out2) << std::endl;
// basically northog = k+1 if mmax is large
int northog = ((kp)>(mmax-1))?(mmax-1):(kp); // if more than mmax done, we orthog all mmax history.
// std::cout << "northog: " << northog << std::endl;
std::vector<ComplexD> betas (northog);
// std::cout << "peri_kp: " << peri_kp << std::endl;
// we iterate backwards counting down from the current k+1 index (peri_kp) because we
for(int back=0;back<northog;back++){
int peri_back=(k-back)%mmax; GRID_ASSERT((k-back)>=0);
b=-real(innerProduct(q[peri_back],Az))/qq[peri_back];
p[peri_kp]=p[peri_kp]+b*p[peri_back];
q[peri_kp]=q[peri_kp]+b*q[peri_back];
// b=-real(innerProduct(q[peri_back],Az))/qq[peri_back];
b=-(innerProduct(q[peri_back],Az))/qq[peri_back]; // TODO try complex beta
p[peri_kp]=p[peri_kp]+b*p[peri_back];
q[peri_kp]=q[peri_kp]+b*q[peri_back];
// LogIterationB(peri_back, b);
// betas[back] = b; // may need to change the indexing if I ever do it with restarts
// std::cout << "[DEBUG] pushing beta for back = " << back << ", peri_back = " << peri_back << std::endl;
betas[peri_back] = b; // may need to change the indexing if I ever do it with restarts
}
qq[peri_kp]=norm2(q[peri_kp]); // could use axpy_norm
LinalgTimer.Stop();
// log iteration and update GCR polynomial if necessary.
all_betas.push_back(betas);
LogIteration(k + 1, a, betas);
// finish if necessary
if((k==nstep-1)||(cp<rsq)){
std::cout << "All alphas: " << std::endl << all_alphas << std::endl;
std::cout << "All betas: " << std::endl << all_betas << std::endl;
LogComplete(all_alphas, all_betas);
std::cout << "Exiting GCR." << std::endl;
return cp;
}
}
GRID_ASSERT(0); // never reached
return cp;
}
};
class PolynomialFile: Serializable {
public:
GRID_SERIALIZABLE_CLASS_MEMBERS(PolynomialFile,
std::vector<std::vector<std::complex<double>>>, data,
std::vector<std::vector<std::complex<double>>>, betas,
std::vector<std::complex<double>>, alphas
);
};
// Optionally record the GCR polynomial. [PO]: TODO
template <class Field>
class PGCRPolynomial : public PrecGeneralisedConjugateResidualNonHermitian<Field> {
public:
std::vector<ComplexD> ak;
std::vector<std::vector<ComplexD>> bk;
// std::vector<ComplexD> poly_p;
std::vector<std::vector<ComplexD>> poly_p;
std::vector<ComplexD> poly_Ap; // polynomial in Ap_j (only store it for last p)
std::vector<ComplexD> poly_r;
std::vector<ComplexD> polynomial;
PolynomialFile& PF;
public:
PGCRPolynomial(RealD tol, Integer maxit,LinearOperatorBase<Field> &_Linop, LinearFunction<Field> &Prec, int _mmax, int _nstep, PolynomialFile& _PF)
: PrecGeneralisedConjugateResidualNonHermitian<Field>(tol, maxit, _Linop, Prec, _mmax, _nstep), PF(_PF)
{};
// think this applies the polynomial in A = Linop to a field src. The coeffs are
// stored in the vector `polynomial`.
void PolyOp(const Field &src, Field &psi)
{
Field tmp(src.Grid());
Field AtoN(src.Grid());
AtoN = src;
psi=AtoN*polynomial[0];
for(int n=1;n<polynomial.size();n++){
tmp = AtoN;
this->Linop.Op(tmp,AtoN); // iterate A^n
psi = psi + polynomial[n]*AtoN; // psi += poly_n A^n src
}
}
// [PO TODO] debug this
void PGCRsequence(const Field &src, Field &x)
{
Field Ap(src.Grid());
Field r(src.Grid());
// Field p(src.Grid());
// p=src;
std::vector<Field> p;
p.push_back(src);
r=src;
x=Zero();
x.Checkerboard()=src.Checkerboard();
for(int k=0;k<ak.size();k++){
x = x + ak[k]*p[k];
this->Linop.Op(p[k], Ap);
r = r - ak[k] * Ap;
// p[k] = r;
p.push_back(r);
for (int i = 0; i < k; i++) { // [PO TODO] check indices
p[k+1] += bk[i, k+1] * p[i];
}
// p = r + bk[k] * p;
}
}
void Solve(const Field &src, Field &psi)
{
psi=Zero();
this->operator()(src, psi);
}
virtual void LogBegin(void)
{
std::cout << "PGCR::LogBegin() "<<std::endl;
ak.resize(0);
bk.resize(0);
polynomial.resize(0);
poly_Ap.push_back(0.0); // start with (0.0); during first iteration should change to (0.0, 1.0)
std::vector<ComplexD> p0_tmp;
p0_tmp.push_back(1.0);
poly_p.push_back(p0_tmp);
poly_r.push_back(1.0);
};
// Updates vector psi and r and initializes vector p[k+1]
virtual void LogIteration(int k, ComplexD a, std::vector<ComplexD> betas){
std::cout << "PGCR::LogIteration(k = " << k << ")" << std::endl;
ak.push_back(a);
bk.push_back(betas);
// update Ap by pushing p[k] to the right
poly_Ap.push_back(0.0); // need to pad the end with an element
poly_Ap[0] = 0.0; // technically this should be unnecessary, as the first component is never set
for(int i = 0; i < k; i++){
poly_Ap[i+1]=poly_p[k-1][i]; // A\vec{p} = (0, \vec{p}) bc A shifts components of p to the right
}
// update psi_{k+1} --> psi_k + a_k p_k
polynomial.push_back(0.0);
for(int i = 0; i < k; i++) {
polynomial[i] += a * poly_p[k-1][i];
}
{
std::vector<std::complex<double>> poly_stdcmplx(polynomial.begin(), polynomial.end());
PF.data.push_back(poly_stdcmplx);
}
// r_{k+1} --> r_k - a_k A p_k
// p_{k+1} --> r_k + \sum_{i=0}^k \beta_{ik} p_i, input betas = (\beta_{ik})_i
poly_r.push_back(0.0); // should be of size k+1 if we start with k = 1
std::vector<ComplexD> p_next (k + 1, ComplexD(0.0)); // p_{k+1} = same size as r_{k+1}
for(int i = 0; i < k + 1; i++){
poly_r[i] = poly_r[i] - a * poly_Ap[i]; // update r_{k+1} --> r_k - \alpha_k A p_k
p_next[i] = poly_r[i]; // init new vector as r_{k+1}
}
// p_{k+1} --> p_{k+1} + \sum_i \beta_{ij} p_i
int nbeta = betas.size();
std::cout << "Betas: " << betas << std::endl;
for (int j = 0; j < nbeta; j++) {
for (int i = 0; i < j+1; i++) {
p_next[i] += betas[j] * poly_p[j][i];
}
}
poly_p.push_back(p_next); // add p_{k+1} to the list of p's
}
virtual void LogComplete(std::vector<ComplexD>& alphas, std::vector<std::vector<ComplexD>>& betas) {
/** Logs all alphas and betas to complete the iterations. */
std::cout << "PGCR::LogComplete() "<<std::endl;
for (int i = 0; i < alphas.size(); i++) {
PF.alphas.push_back(std::complex<double>(alphas[i].real(), alphas[i].imag()));
std::vector<std::complex<double>> beta_stdcmplx(betas[i].begin(), betas[i].end());
PF.betas.push_back(beta_stdcmplx);
}
};
};
NAMESPACE_END(Grid);
#endif
@@ -0,0 +1,753 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./Grid/algorithms/iterative/RestartedLanczosBidiagonalization.h
Copyright (C) 2015
Author: Chulwoo Jung <chulwoo@bnl.gov>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#ifndef GRID_RESTARTED_LANCZOS_BIDIAGONALIZATION_H
#define GRID_RESTARTED_LANCZOS_BIDIAGONALIZATION_H
NAMESPACE_BEGIN(Grid);
/**
* Implicitly Restarted Lanczos Bidiagonalization (IRLBA)
*
* Computes the p largest (or p smallest) singular triplets of a linear
* operator A using the Golub-Kahan-Lanczos bidiagonalization with implicit
* restart via thick-restart / QR shifts.
*
* Algorithm (Baglama & Reichel, SIAM J. Sci. Comput. 27(1):19-42, 2005):
*
* Outer loop:
* 1. Extend the p-step (or seed) bidiagonalization to k steps:
* A V_k = U_k B_k
* A^dag U_k = V_k B_k^T + beta_{k+1} v_{k+1} e_k^T
* 2. Compute SVD: B_k = X Sigma Y^T
* 3. Check convergence of the p desired singular values via
* |beta_{k+1} * y_{k,i}| < tol * sigma_i
* where y_{k,i} is the last component of the i-th right singular vector.
* 4. Apply k-p implicit QR shifts to implicitly compress the basis
* to p steps (Sorensen-Lehoucq thick restart):
* B_p^+ = X_p^T B_k Y_p (upper bidiagonal, p x p)
* and update the lattice vectors:
* V_p^+ = V_k Y_p
* U_p^+ = U_k X_p
* The new residual coupling is
* beta_p^+ v_{p+1}^+ = beta_{k+1} v_{k+1} * (e_k^T Y_p)_p
* + B_k(p,p+1) * (orthogonal tail from QR)
* 5. Go to step 1.
*
* Template parameter
* ------------------
* Field : lattice field type (must support Grid algebra operations)
*
* Usage
* -----
* RestartedLanczosBidiagonalization<Field> irlba(Linop, grid, p, k, tol, maxIter);
* irlba.run(src);
* // Results available via getters.
*/
template <class Field>
class RestartedLanczosBidiagonalization {
public:
LinearOperatorBase<Field> &Linop;
GridBase *Grid;
int Nk; // number of desired singular triplets
int Nm; // Lanczos basis size (Nm > Nk)
RealD Tolerance;
int MaxIter;
bool largest; // if true, target largest singular values; otherwise smallest
// Converged singular triplets (filled after run())
std::vector<RealD> singularValues; // sigma_0 >= sigma_1 >= ...
std::vector<Field> leftVectors; // approximate left singular vectors
std::vector<Field> rightVectors; // approximate right singular vectors
private:
// Working bases (size up to Nm+1)
std::vector<Field> V; // right Lanczos vectors
std::vector<Field> U; // left Lanczos vectors
std::vector<RealD> alpha;
std::vector<RealD> beta;
// After a thick restart, the column at index restart_col of U^dag A V
// has extra non-zero entries (rows 0..restart_col-2) beyond what the
// upper bidiagonal captures. fvec[j] = <U[j] | A V[restart_col]> for
// j = 0..restart_col-1. (fvec[restart_col-1] == beta[restart_col-1].)
// reset_col == -1 means no restart has occurred yet (pure bidiagonal).
std::vector<RealD> fvec;
int restart_col;
public:
RestartedLanczosBidiagonalization(LinearOperatorBase<Field> &_Linop,
GridBase *_Grid,
int _Nk, int _Nm,
RealD _tol = 1.0e-8,
int _maxIt = 300,
bool _largest = true)
: Linop(_Linop), Grid(_Grid),
Nk(_Nk), Nm(_Nm),
Tolerance(_tol), MaxIter(_maxIt),
largest(_largest)
{
assert(Nm > Nk);
}
/**
* Run IRLBA starting from src.
* On exit, singularValues, leftVectors, rightVectors are filled with
* the Nk converged singular triplets.
*/
void run(const Field &src)
{
assert(norm2(src) > 0.0);
singularValues.clear();
leftVectors.clear();
rightVectors.clear();
// Allocate working bases
V.clear(); U.clear();
alpha.clear(); beta.clear();
fvec.clear(); restart_col = -1;
V.reserve(Nm + 1);
U.reserve(Nm);
// Seed: v_0 = src / ||src||
Field vtmp(Grid);
vtmp = src;
RealD nrm = std::sqrt(norm2(vtmp));
vtmp = (1.0 / nrm) * vtmp;
V.push_back(vtmp);
int pStart = 0; // current basis size at start of extension
RealD betaRestart = 0.0; // coupling from previous restart
for (int iter = 0; iter < MaxIter; ++iter) {
// ----------------------------------------------------------------
// Step 1: extend from pStart steps to Nm steps
// ----------------------------------------------------------------
extendBasis(pStart, Nm, betaRestart);
// verify();
// ----------------------------------------------------------------
// Step 2: SVD of the Nm x Nm B matrix.
// iter=0 (pStart==0): B is exactly bidiagonal — use buildBidiagonal.
// iter>0 (pStart==Nk): after a thick restart, column restart_col of
// U^dag A V has extra off-diagonal entries captured by fvec; use
// buildFullB so the Ritz values and restart vectors are computed from
// the exact projected matrix A V = U B_full.
// ----------------------------------------------------------------
Eigen::MatrixXd B = (pStart == 0) ? buildBidiagonal(Nm) : buildFullB(Nm);
Eigen::JacobiSVD<Eigen::MatrixXd> svd(B,
Eigen::ComputeThinU | Eigen::ComputeThinV);
Eigen::VectorXd sigma = svd.singularValues(); // descending
Eigen::MatrixXd X = svd.matrixU(); // Nm x Nm left SVecs of B
Eigen::MatrixXd Y = svd.matrixV(); // Nm x Nm right SVecs of B
// If targeting smallest, reorder so desired ones come first
Eigen::VectorXi order = sortOrder(sigma);
// ----------------------------------------------------------------
// Step 3: check convergence of the Nk desired singular values
// ----------------------------------------------------------------
RealD betaK = beta.back(); // beta_{k+1}
// In our convention A V = U B (exact), the residual is in the A^dag
// direction: A^dag u_j - sigma_j v_j = betaK * X[Nm-1,j] * V[Nm].
// Convergence criterion: |betaK * X[Nm-1, idx]| < tol * sigma_idx.
int nconv = 0;
for (int i = 0; i < Nk; ++i) {
int idx = order(i);
RealD res = std::abs(betaK * X(Nm - 1, idx));
RealD thr = Tolerance * std::max(sigma(idx), 1.0e-14);
std::cout << GridLogMessage
<< "IRLBA iter " << iter
<< " sigma[" << i << "] = " << sigma(idx)
<< " res = " << res
<< " thr = " << thr << std::endl;
if (res < thr) ++nconv;
else break; // residuals not strictly ordered but break is conservative
}
if (nconv >= Nk) {
std::cout << GridLogMessage
<< "IRLBA converged: " << nconv << " singular values after "
<< iter + 1 << " iterations." << std::endl;
// Collect converged triplets
extractTriplets(Nm, sigma, X, Y, order, Nk);
return;
}
// ----------------------------------------------------------------
// Step 4: implicit restart — compress to Nk steps
// ----------------------------------------------------------------
implicitRestart(Nm, Nk, sigma, X, Y, order, betaK, betaRestart);
// verify();
// Lucky breakdown: exact invariant subspace found; convergence is exact.
// B_p^+ = diag(alpha[0..Nk-1]); extract directly from restart basis.
if (betaRestart < 1.0e-14) {
std::cout << GridLogMessage
<< "IRLBA: lucky breakdown after restart (betaRestart = 0)."
<< " Extracting " << Nk << " exact Ritz triplets." << std::endl;
// Re-run SVD on the p-step diagonal B^+ to get sorted Ritz triplets.
Eigen::MatrixXd Bp = buildBidiagonal(Nk);
Eigen::JacobiSVD<Eigen::MatrixXd> svdp(Bp,
Eigen::ComputeThinU | Eigen::ComputeThinV);
Eigen::VectorXi ordp = sortOrder(svdp.singularValues());
extractTriplets(Nk, svdp.singularValues(), svdp.matrixU(),
svdp.matrixV(), ordp, Nk);
return;
}
pStart = Nk;
}
std::cout << GridLogMessage
<< "IRLBA: did not converge in " << MaxIter
<< " iterations. Returning best approximations." << std::endl;
// Return best available approximations
Eigen::MatrixXd B = buildFullB((int)alpha.size());
Eigen::JacobiSVD<Eigen::MatrixXd> svd(B,
Eigen::ComputeThinU | Eigen::ComputeThinV);
Eigen::VectorXd sigma = svd.singularValues();
Eigen::MatrixXd X = svd.matrixU();
Eigen::MatrixXd Y = svd.matrixV();
Eigen::VectorXi order = sortOrder(sigma);
int nout = std::min(Nk, (int)alpha.size());
extractTriplets((int)alpha.size(), sigma, X, Y, order, nout);
}
/* Getters */
int getNk() const { return (int)singularValues.size(); }
const std::vector<RealD>& getSingularValues() const { return singularValues; }
const std::vector<Field>& getLeftVectors() const { return leftVectors; }
const std::vector<Field>& getRightVectors() const { return rightVectors; }
/**
* Print B_k and U^dag A V to verify the bidiagonalization relation
* A V_m = U_m B_m (exact in our GK convention)
* On the first call (pStart=0), max|B - U^dag A V| should be ~machine epsilon.
* After a restart and extension, the column p of U^dag A V deviates from B
* by O(betaK): this is expected because the thick restart breaks the Krylov
* structure at column p, introducing off-diagonal terms proportional to betaK.
* These terms vanish as betaK -> 0 (convergence), so the algorithm is correct.
*/
void verify()
{
int m = (int)alpha.size();
int nU = (int)U.size();
int nV = (int)V.size();
if (m == 0) { std::cout << GridLogMessage << "IRLBA verify: empty basis" << std::endl; return; }
// Build reference matrix Bref (nU x nV):
// Columns 0..m-1 : buildFullB(m) (bidiagonal + fvec column at restart_col)
// Column m : residual column, two cases:
// (a) restart_col == m (right after implicitRestart, before extendBasis):
// V[m] = sgn*V_old[Nm], so <U[i]|A|V[m]> = fvec[i] for all i
// (b) otherwise (pure GK or after extendBasis):
// only entry (m-1, m) = beta[m-1] (GK recurrence residual)
Eigen::MatrixXd Bref = Eigen::MatrixXd::Zero(nU, nV);
{
Eigen::MatrixXd Bfull = buildFullB(m);
int cols = std::min(m, nV);
Bref.block(0, 0, m, cols) = Bfull.block(0, 0, m, cols);
}
if (nV > m && m > 0) {
if (restart_col == m && (int)fvec.size() == m) {
// Case (a): right after implicitRestart
for (int i = 0; i < m; ++i) Bref(i, m) = fvec[i];
} else if ((int)beta.size() >= m) {
// Case (b): standard GK residual column
Bref(m - 1, m) = beta[m - 1];
}
}
// Compute M[i,j] = <U[i] | A | V[j]>
Eigen::MatrixXd M = Eigen::MatrixXd::Zero(nU, nV);
Field Avj(Grid);
for (int j = 0; j < nV; ++j) {
Linop.Op(V[j], Avj);
for (int i = 0; i < nU; ++i) {
ComplexD ip = innerProduct(U[i], Avj);
M(i, j) = ip.real();
}
}
// Print Bref
std::cout << GridLogMessage
<< "IRLBA verify: Bref (" << nU << "x" << nV << "):" << std::endl;
for (int i = 0; i < nU; ++i) {
std::cout << GridLogMessage << " row " << i << ": ";
for (int j = 0; j < nV; ++j) std::cout << Bref(i,j) << " ";
std::cout << std::endl;
}
// Print U^dag A V
std::cout << GridLogMessage
<< "IRLBA verify: U^dag A V (" << nU << "x" << nV << "):" << std::endl;
for (int i = 0; i < nU; ++i) {
std::cout << GridLogMessage << " row " << i << ": ";
for (int j = 0; j < nV; ++j) std::cout << M(i,j) << " ";
std::cout << std::endl;
}
// Max deviation over the full nU x nV matrix
RealD maxdev = (Bref - M).cwiseAbs().maxCoeff();
std::cout << GridLogMessage
<< "IRLBA verify: max|Bref - U^dag A V| = " << maxdev << std::endl;
// Beta
std::cout << GridLogMessage << "IRLBA verify: beta[0.." << (int)beta.size()-1 << "] = ";
for (auto b : beta) std::cout << b << " ";
std::cout << std::endl;
}
private:
// ------------------------------------------------------------------
// Build the m x m upper-bidiagonal matrix from alpha[0..m-1], beta[0..m-2]
// ------------------------------------------------------------------
Eigen::MatrixXd buildBidiagonal(int m) const
{
Eigen::MatrixXd B = Eigen::MatrixXd::Zero(m, m);
for (int k = 0; k < m; ++k) {
B(k, k) = alpha[k];
if (k + 1 < m && k < (int)beta.size())
B(k, k + 1) = beta[k];
}
return B;
}
// ------------------------------------------------------------------
// Build the full m x m B matrix, including the non-bidiagonal column
// at restart_col that arises after a thick restart.
//
// After restart, A V[restart_col] has projections onto all U[0..restart_col-1]
// (not just U[restart_col-1]). These are stored in fvec[0..restart_col-1]
// and make column restart_col of U^dag A V non-bidiagonal.
// ------------------------------------------------------------------
Eigen::MatrixXd buildFullB(int m) const
{
Eigen::MatrixXd B = buildBidiagonal(m);
if (restart_col >= 0 && restart_col < m && (int)fvec.size() > 0) {
for (int j = 0; j < restart_col && j < (int)fvec.size(); ++j){
B(j, restart_col) = fvec[j];
std::cout << GridLogDebug << "buildFullB: B " <<j<<" "<<restart_col<<B(j, restart_col)<<std::endl;
}
}
return B;
}
// ------------------------------------------------------------------
// Return a permutation vector that puts the desired Nk singular values
// first (largest first if largest==true, smallest first otherwise).
// Eigen's JacobiSVD already returns sigma in descending order, so for
// largest we just return 0,1,...,m-1; for smallest we reverse.
// ------------------------------------------------------------------
Eigen::VectorXi sortOrder(const Eigen::VectorXd &sigma) const
{
int m = (int)sigma.size();
Eigen::VectorXi ord(m);
if (largest) {
for (int i = 0; i < m; ++i) ord(i) = i;
} else {
for (int i = 0; i < m; ++i) ord(i) = m - 1 - i;
}
return ord;
}
// ------------------------------------------------------------------
// Extend the Lanczos bidiagonalization from pStart to kEnd steps.
// On first call pStart==0 (V[0] already set).
// On restart calls V[0..pStart], U[0..pStart-1], alpha[0..pStart-1],
// beta[0..pStart-1] are already set; betaRestart is the coupling
// beta_{pStart} that drives the first new U step.
// ------------------------------------------------------------------
void extendBasis(int pStart, int kEnd, RealD betaRestart)
{
// Truncate containers to pStart (Lattice has no default constructor)
if ((int)V.size() > pStart + 1) V.erase(V.begin() + pStart + 1, V.end());
if ((int)U.size() > pStart) U.erase(U.begin() + pStart, U.end());
alpha.resize(pStart);
beta.resize(pStart);
Field p(Grid), r(Grid);
for (int k = pStart; k < kEnd; ++k) {
// p = A v_k
Linop.Op(V[k], p);
// Remove previous left vector coupling
if (k > 0) {
p = p - beta[k - 1] * U[k - 1];
}
// On the first step after a restart, beta[pStart-1] was already set;
// but V[pStart] was already constructed including the beta correction,
// so no extra subtraction needed here beyond the standard recurrence.
// Reorthogonalize p against U, then alpha_k = ||p||, u_k = p/alpha_k
reorthogonalize(p, U);
RealD ak = std::sqrt(norm2(p));
if (ak < 1.0e-14) {
std::cout << GridLogMessage
<< "IRLBA extendBasis: lucky breakdown at step " << k
<< " (alpha = " << ak << ")" << std::endl;
alpha.push_back(ak);
Field zero(Grid); zero = Zero();
U.push_back(zero);
beta.push_back(0.0);
V.push_back(zero);
break;
}
alpha.push_back(ak);
Field u(Grid);
u = (1.0 / ak) * p;
U.push_back(u);
// r = A^dag u_k - alpha_k v_k, reorthogonalize, then beta_{k+1} = ||r||
Linop.AdjOp(U[k], r);
r = r - ak * V[k];
reorthogonalize(r, V);
RealD bk = std::sqrt(norm2(r));
beta.push_back(bk);
std::cout << GridLogMessage
<< "IRLBA extend step " << k
<< " alpha = " << ak
<< " beta = " << bk << std::endl;
// Always push v_{k+1} (needed as residual direction for restart)
if (bk < 1.0e-14) {
std::cout << GridLogMessage
<< "IRLBA extendBasis: lucky breakdown (beta = 0) at step "
<< k << std::endl;
Field zero(Grid); zero = Zero();
V.push_back(zero);
break;
}
Field vnext(Grid);
vnext = (1.0 / bk) * r;
V.push_back(vnext);
if (k == kEnd - 1) break; // v_{k+1} pushed above; stop here
}
}
public:
// ------------------------------------------------------------------
// Block reorthogonalization helpers.
// Declared public because CUDA extended lambdas cannot live inside
// private/protected member functions.
//
// batchInnerProducts: computes c[j] = <basis[j], vec> for all j
// in a single GPU pass (one accelerator_barrier instead of n).
// Queues n pairs of (per-site kernel, reduceKernel) to computeStream
// without intermediate CPU syncs, then syncs once at the end.
//
// batchUpdate: computes vec -= sum_j c[j]*basis[j] in one GPU kernel.
//
// reorthogonalize: two-pass Classical Gram-Schmidt (CGS2) using the
// two helpers above. Each pass costs 2 GPU syncs (1 IP + 1 update)
// instead of 2n syncs per pass in the old sequential MGS.
// ------------------------------------------------------------------
void batchInnerProducts(const Field &vec,
const std::vector<Field> &basis,
std::vector<ComplexD> &c)
{
int n = (int)basis.size();
c.resize(n);
if (n == 0) return;
typedef typename Field::vector_object vobj;
typedef decltype(innerProduct(vobj(), vobj())) inner_t;
typedef decltype(basis[0].View(AcceleratorRead)) View;
GridBase *grid = vec.Grid();
uint64_t oSites = grid->oSites();
uint64_t nsimd = grid->Nsimd();
// all_ip[j * oSites + ss] = per-site inner product of basis[j] and vec at site ss.
// Layout: n contiguous blocks of oSites each.
deviceVector<inner_t> all_ip((uint64_t)n * oSites);
inner_t *all_ip_p = &all_ip[0];
hostVector<View> h_basis_v(n);
deviceVector<View> d_basis_v(n);
for (int j = 0; j < n; ++j) {
h_basis_v[j] = basis[j].View(AcceleratorRead);
acceleratorPut(d_basis_v[j], h_basis_v[j]);
}
View *basis_vp = &d_basis_v[0];
// Queue n per-site kernels to the accelerator stream — no intermediate barriers.
autoView(vec_v, vec, AcceleratorRead);
for (int j = 0; j < n; ++j) {
int jj = j;
uint64_t oSites_ = oSites;
accelerator_for(ss, oSites, nsimd, {
auto x = coalescedRead(basis_vp[jj][ss]);
auto y = coalescedRead(vec_v[ss]);
coalescedWrite(all_ip_p[jj * oSites_ + ss], innerProduct(x, y));
});
}
// ONE sync after all n kernels
accelerator_barrier();
// Copy all per-site results to host
hostVector<inner_t> all_ip_h((uint64_t)n * oSites);
acceleratorCopyFromDevice(all_ip_p, &all_ip_h[0], (uint64_t)n * oSites * sizeof(inner_t));
// Reduce on host: sum over oSites, then collapse SIMD lanes via Reduce(TensorRemove(...))
// TensorRemove strips the iSinglet tensor wrapper to expose the SIMD scalar type.
// Reduce sums all nsimd lanes and returns a plain scalar (RealD or ComplexD).
std::vector<ComplexD> raw(n);
for (int j = 0; j < n; ++j) {
inner_t sum = Zero();
for (uint64_t ss = 0; ss < oSites; ++ss)
sum += all_ip_h[(uint64_t)j * oSites + ss];
raw[j] = ComplexD(Reduce(TensorRemove(sum)));
}
grid->GlobalSumVector(&raw[0], n);
for (int j = 0; j < n; ++j) c[j] = raw[j];
for (int j = 0; j < n; ++j) h_basis_v[j].ViewClose();
}
void batchUpdate(Field &vec,
const std::vector<Field> &basis,
const std::vector<ComplexD> &c)
{
int n = (int)basis.size();
if (n == 0) return;
typedef typename Field::vector_object vobj;
typedef decltype(basis[0].View(AcceleratorRead)) View;
GridBase *grid = vec.Grid();
uint64_t oSites = grid->oSites();
uint64_t nsimd = grid->Nsimd();
// Split complex coefficients into real/imag double arrays on device.
// Using doubles avoids potential ComplexD-device-code compatibility issues.
hostVector<double> h_re(n), h_im(n);
deviceVector<double> d_re(n), d_im(n);
for (int k = 0; k < n; ++k) {
h_re[k] = c[k].real();
h_im[k] = c[k].imag();
}
acceleratorCopyToDevice(&h_re[0], &d_re[0], n * sizeof(double));
acceleratorCopyToDevice(&h_im[0], &d_im[0], n * sizeof(double));
double *re_p = &d_re[0];
double *im_p = &d_im[0];
// Basis views
hostVector<View> h_basis_v(n);
deviceVector<View> d_basis_v(n);
for (int k = 0; k < n; ++k) {
h_basis_v[k] = basis[k].View(AcceleratorRead);
acceleratorPut(d_basis_v[k], h_basis_v[k]);
}
View *basis_vp = &d_basis_v[0];
// Single kernel: vec[ss] -= sum_k (re[k] + i*im[k]) * basis[k][ss]
autoView(vec_v, vec, AcceleratorWrite);
accelerator_for(ss, oSites, nsimd, {
auto v = coalescedRead(vec_v[ss]);
for (int k = 0; k < n; ++k) {
auto b = coalescedRead(basis_vp[k][ss]);
v = v - re_p[k] * b - timesI(im_p[k] * b);
}
coalescedWrite(vec_v[ss], v);
});
for (int k = 0; k < n; ++k) h_basis_v[k].ViewClose();
}
// ------------------------------------------------------------------
// Full reorthogonalization using two-pass Classical Gram-Schmidt (CGS2).
// Each pass calls batchInnerProducts (1 GPU sync) + batchUpdate (1 sync),
// replacing the old 2n GPU syncs per pass from sequential MGS.
// ------------------------------------------------------------------
void reorthogonalize(Field &vec, const std::vector<Field> &basis)
{
if (basis.empty()) return;
std::vector<ComplexD> c;
for (int pass = 0; pass < 2; ++pass) {
batchInnerProducts(vec, basis, c);
batchUpdate(vec, basis, c);
}
}
// ------------------------------------------------------------------
// Implicit restart: given the Nm-step bidiagonalization and its SVD,
// compress to Nk steps via implicit QR shifts applied to B_k.
//
// The "shifts" are the Nm - Nk singular values we want to deflate
// (those NOT in the desired set). We apply them as implicit QR steps
// to the bidiagonal matrix, then update the lattice bases accordingly.
//
// After this call:
// V[0..Nk], U[0..Nk-1], alpha[0..Nk-1], beta[0..Nk-1] are updated.
// betaRestart ← new beta_Nk coupling for the next extension.
// ------------------------------------------------------------------
void implicitRestart(int k, int p,
const Eigen::VectorXd &sigma,
const Eigen::MatrixXd &X,
const Eigen::MatrixXd &Y,
const Eigen::VectorXi &order,
RealD betaK,
RealD &betaRestart)
{
// Thick restart (Baglama & Reichel, Sec. 2.2):
//
// Given B_k = X Sigma Y^T, define the new p-step basis by:
// V^+_i = V_k * y_{order(i)} (right sing. vec. of B_k)
// U^+_i = U_k * x_{order(i)} (left sing. vec. of B_k)
//
// Then A V^+_i = A V_k y_{order(i)} = U_k B_k y_{order(i)}
// = sigma_{order(i)} U_k x_{order(i)} = sigma_{order(i)} U^+_i
//
// So B_p^+ = diag(sigma_{order(0)}, ..., sigma_{order(p-1)}) — DIAGONAL,
// all internal betas are zero.
//
// The residual coupling comes from A^dag U_k = V_k B_k^T + betaK V[k] e_{k-1}^T:
// A^dag U^+_{p-1} - sigma_{order(p-1)} V^+_{p-1}
// = V_k (B_k^T x_{order(p-1)} - sigma_{order(p-1)} y_{order(p-1)})
// + betaK * X(k-1, order(p-1)) * V[k]
// = betaK * X(k-1, order(p-1)) * V[k] (since B_k^T x_j = sigma_j y_j)
//
// Therefore: betaRestart = |betaK * X(k-1, order(p-1))|
// V[p] = sign(X(k-1, order(p-1))) * V[k]
// ---- Build new lattice vectors ----
std::vector<Field> Vnew, Unew;
Vnew.reserve(p + 1);
Unew.reserve(p);
for (int i = 0; i < p; ++i) {
int idx = order(i);
Field vi(Grid); vi = Zero();
for (int j = 0; j < k; ++j)
vi = vi + Y(j, idx) * V[j];
Vnew.push_back(vi);
}
for (int i = 0; i < p; ++i) {
int idx = order(i);
Field ui(Grid); ui = Zero();
for (int j = 0; j < k; ++j)
ui = ui + X(j, idx) * U[j];
Unew.push_back(ui);
}
// New v_{p} (0-indexed: V[p]) = sign * V[k]
// From A^dag U_k = V_k B_k^T + betaK V[k] e_{k-1}^T:
// A^dag U^+_j - sigma_j V^+_j = betaK * X(k-1, order(j)) * V[k]
// The last Ritz pair (j=p-1) defines betaRestart and the sign of V[p].
// All p couplings (j=0..p-1) are stored in fvec so that buildFullB can
// reconstruct the exact column p of U^dag A V after the next extension.
RealD coeff = betaK * X(k - 1, order(p - 1));
betaRestart = std::abs(coeff);
RealD sgn = (coeff >= 0.0) ? 1.0 : -1.0;
fvec.resize(p);
for (int j = 0; j < p; ++j)
fvec[j] = betaK * X(k - 1, order(j)) * sgn;
// fvec[p-1] == betaRestart by construction
restart_col = p;
Field vp(Grid);
if (betaRestart > 1.0e-14) {
vp = sgn * V[k];
} else {
betaRestart = 0.0;
vp = Zero();
}
Vnew.push_back(vp); // V[p]
// ---- New alpha, beta ----
// B_p^+ is diagonal: alpha^+_i = sigma_{order(i)}, all internal beta = 0
std::vector<RealD> alpha_new(p), beta_new(p);
for (int i = 0; i < p; ++i) alpha_new[i] = sigma(order(i));
for (int i = 0; i < p - 1; ++i) beta_new[i] = 0.0;
beta_new[p - 1] = betaRestart;
// ---- Commit new state ----
V = Vnew;
U = Unew;
alpha = alpha_new;
beta = beta_new;
std::cout << GridLogMessage
<< "IRLBA restart: compressed to " << p << " steps,"
<< " new beta_p = " << betaRestart << std::endl;
}
// ------------------------------------------------------------------
// Extract the desired singular triplets into the public output vectors.
// ------------------------------------------------------------------
void extractTriplets(int m,
const Eigen::VectorXd &sigma,
const Eigen::MatrixXd &X,
const Eigen::MatrixXd &Y,
const Eigen::VectorXi &order,
int nout)
{
singularValues.resize(nout);
leftVectors.clear(); leftVectors.reserve(nout);
rightVectors.clear(); rightVectors.reserve(nout);
for (int i = 0; i < nout; ++i) {
int idx = order(i);
singularValues[i] = sigma(idx);
// Left singular vector of A: svec_L = U_m * x_i
Field svL(Grid); svL = Zero();
for (int j = 0; j < m && j < (int)U.size(); ++j)
svL = svL + X(j, idx) * U[j];
leftVectors.push_back(svL);
// Right singular vector of A: svec_R = V_m * y_i
Field svR(Grid); svR = Zero();
for (int j = 0; j < m && j < (int)V.size(); ++j)
svR = svR + Y(j, idx) * V[j];
rightVectors.push_back(svR);
}
}
};
NAMESPACE_END(Grid);
#endif
+931
View File
@@ -0,0 +1,931 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/ImplicitlyRestartedLanczos.h
Copyright (C) 2015
Author: Chulwoo Jung <chulwoo@bnl.gov>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#ifndef GRID_LANC_H
#define GRID_LANC_H
#include <string.h> //memset
#ifdef USE_LAPACK
#ifdef USE_MKL
#include<mkl_lapack.h>
#else
void LAPACK_dstegr (char *jobz, char *range, int *n, double *d, double *e,
double *vl, double *vu, int *il, int *iu, double *abstol,
int *m, double *w, double *z, int *ldz, int *isuppz,
double *work, int *lwork, int *iwork, int *liwork,
int *info);
//#include <lapacke/lapacke.h>
#endif
#endif
//#include <Grid/algorithms/densematrix/DenseMatrix.h>
// eliminate temorary vector in calc()
#define MEM_SAVE
namespace Grid
{
struct Bisection
{
#if 0
static void get_eig2 (int row_num, std::vector < RealD > &ALPHA,
std::vector < RealD > &BETA,
std::vector < RealD > &eig)
{
int i, j;
std::vector < RealD > evec1 (row_num + 3);
std::vector < RealD > evec2 (row_num + 3);
RealD eps2;
ALPHA[1] = 0.;
BETHA[1] = 0.;
for (i = 0; i < row_num - 1; i++)
{
ALPHA[i + 1] = A[i * (row_num + 1)].real ();
BETHA[i + 2] = A[i * (row_num + 1) + 1].real ();
}
ALPHA[row_num] = A[(row_num - 1) * (row_num + 1)].real ();
bisec (ALPHA, BETHA, row_num, 1, row_num, 1e-10, 1e-10, evec1, eps2);
bisec (ALPHA, BETHA, row_num, 1, row_num, 1e-16, 1e-16, evec2, eps2);
// Do we really need to sort here?
int begin = 1;
int end = row_num;
int swapped = 1;
while (swapped)
{
swapped = 0;
for (i = begin; i < end; i++)
{
if (mag (evec2[i]) > mag (evec2[i + 1]))
{
swap (evec2 + i, evec2 + i + 1);
swapped = 1;
}
}
end--;
for (i = end - 1; i >= begin; i--)
{
if (mag (evec2[i]) > mag (evec2[i + 1]))
{
swap (evec2 + i, evec2 + i + 1);
swapped = 1;
}
}
begin++;
}
for (i = 0; i < row_num; i++)
{
for (j = 0; j < row_num; j++)
{
if (i == j)
H[i * row_num + j] = evec2[i + 1];
else
H[i * row_num + j] = 0.;
}
}
}
#endif
static void bisec (std::vector < RealD > &c,
std::vector < RealD > &b,
int n,
int m1,
int m2,
RealD eps1,
RealD relfeh, std::vector < RealD > &x, RealD & eps2)
{
std::vector < RealD > wu (n + 2);
RealD h, q, x1, xu, x0, xmin, xmax;
int i, a, k;
b[1] = 0.0;
xmin = c[n] - fabs (b[n]);
xmax = c[n] + fabs (b[n]);
for (i = 1; i < n; i++)
{
h = fabs (b[i]) + fabs (b[i + 1]);
if (c[i] + h > xmax)
xmax = c[i] + h;
if (c[i] - h < xmin)
xmin = c[i] - h;
}
xmax *= 2.;
eps2 = relfeh * ((xmin + xmax) > 0.0 ? xmax : -xmin);
if (eps1 <= 0.0)
eps1 = eps2;
eps2 = 0.5 * eps1 + 7.0 * (eps2);
x0 = xmax;
for (i = m1; i <= m2; i++)
{
x[i] = xmax;
wu[i] = xmin;
}
for (k = m2; k >= m1; k--)
{
xu = xmin;
i = k;
do
{
if (xu < wu[i])
{
xu = wu[i];
i = m1 - 1;
}
i--;
}
while (i >= m1);
if (x0 > x[k])
x0 = x[k];
while ((x0 - xu) > 2 * relfeh * (fabs (xu) + fabs (x0)) + eps1)
{
x1 = (xu + x0) / 2;
a = 0;
q = 1.0;
for (i = 1; i <= n; i++)
{
q =
c[i] - x1 -
((q != 0.0) ? b[i] * b[i] / q : fabs (b[i]) / relfeh);
if (q < 0)
a++;
}
// printf("x1=%0.14e a=%d\n",x1,a);
if (a < k)
{
if (a < m1)
{
xu = x1;
wu[m1] = x1;
}
else
{
xu = x1;
wu[a + 1] = x1;
if (x[a] > x1)
x[a] = x1;
}
}
else
x0 = x1;
}
printf ("x0=%0.14e xu=%0.14e k=%d\n", x0, xu, k);
x[k] = (x0 + xu) / 2;
}
}
};
/////////////////////////////////////////////////////////////
// Implicitly restarted lanczos
/////////////////////////////////////////////////////////////
template < class Field > class SimpleLanczos
{
const RealD small = 1.0e-16;
public:
int lock;
int get;
int Niter;
int converged;
int Nstop; // Number of evecs checked for convergence
int Nk; // Number of converged sought
int Np; // Np -- Number of spare vecs in kryloc space
int Nm; // Nm -- total number of vectors
RealD OrthoTime;
RealD eresid;
// SortEigen < Field > _sort;
LinearFunction < Field > &_Linop;
// OperatorFunction < Field > &_poly;
/////////////////////////
// Constructor
/////////////////////////
void init (void)
{
};
// void Abort (int ff, std::vector < RealD > &evals, DenseVector < Denstd::vector < RealD > >&evecs);
SimpleLanczos (LinearFunction < Field > &Linop, // op
// OperatorFunction < Field > &poly, // polynmial
int _Nstop, // sought vecs
int _Nk, // sought vecs
int _Nm, // spare vecs
RealD _eresid, // resid in lmdue deficit
int _Niter): // Max iterations
_Linop (Linop),
// _poly (poly),
Nstop (_Nstop), Nk (_Nk), Nm (_Nm), eresid (_eresid), Niter (_Niter)
{
Np = Nm - Nk;
assert (Np > 0);
};
/////////////////////////
// Sanity checked this routine (step) against Saad.
/////////////////////////
void RitzMatrix (std::vector < Field > &evec, int k)
{
if (1)
return;
GridBase *grid = evec[0].Grid();
Field w (grid);
std::cout << GridLogMessage << "RitzMatrix " << std::endl;
for (int i = 0; i < k; i++)
{
_Linop(evec[i], w);
// _poly(_Linop,evec[i],w);
std::cout << GridLogMessage << "[" << i << "] ";
for (int j = 0; j < k; j++)
{
ComplexD in = innerProduct (evec[j], w);
if (fabs ((double) i - j) > 1)
{
if (abs (in) > 1.0e-9)
{
std::cout << GridLogMessage << "oops" << std::endl;
abort ();
}
else
std::cout << GridLogMessage << " 0 ";
}
else
{
std::cout << GridLogMessage << " " << in << " ";
}
}
std::cout << GridLogMessage << std::endl;
}
}
void step (std::vector < RealD > &lmd,
std::vector < RealD > &lme,
Field & last, Field & current, Field & next, uint64_t k)
{
if (lmd.size () <= k)
lmd.resize (k + Nm);
if (lme.size () <= k)
lme.resize (k + Nm);
// _poly(_Linop,current,next ); // 3. wk:=Avk−βkv_{k1}
_Linop(current, next); // 3. wk:=Avk−βkv_{k1}
if (k > 0)
{
next -= lme[k - 1] * last;
}
// std::cout<<GridLogMessage << "<last|next>" << innerProduct(last,next) <<std::endl;
ComplexD zalph = innerProduct (current, next); // 4. αk:=(wk,vk)
RealD alph = real (zalph);
next = next - alph * current; // 5. wk:=wk−αkvk
// std::cout<<GridLogMessage << "<current|next>" << innerProduct(current,next) <<std::endl;
RealD beta = normalise (next); // 6. βk+1 := ∥wk∥2. If βk+1 = 0 then Stop
// 7. vk+1 := wk/βk+1
// norm=beta;
int interval = Nm / 100 + 1;
if ((k % interval) == 0)
std::
cout << GridLogMessage << k << " : alpha = " << zalph << " beta " <<
beta << std::endl;
const RealD tiny = 1.0e-20;
if (beta < tiny)
{
std::cout << GridLogMessage << " beta is tiny " << beta << std::
endl;
}
lmd[k] = alph;
lme[k] = beta;
}
void qr_decomp (std::vector < RealD > &lmd,
std::vector < RealD > &lme,
int Nk,
int Nm,
std::vector < RealD > &Qt, RealD Dsh, int kmin, int kmax)
{
int k = kmin - 1;
RealD x;
RealD Fden = 1.0 / hypot (lmd[k] - Dsh, lme[k]);
RealD c = (lmd[k] - Dsh) * Fden;
RealD s = -lme[k] * Fden;
RealD tmpa1 = lmd[k];
RealD tmpa2 = lmd[k + 1];
RealD tmpb = lme[k];
lmd[k] = c * c * tmpa1 + s * s * tmpa2 - 2.0 * c * s * tmpb;
lmd[k + 1] = s * s * tmpa1 + c * c * tmpa2 + 2.0 * c * s * tmpb;
lme[k] = c * s * (tmpa1 - tmpa2) + (c * c - s * s) * tmpb;
x = -s * lme[k + 1];
lme[k + 1] = c * lme[k + 1];
for (int i = 0; i < Nk; ++i)
{
RealD Qtmp1 = Qt[i + Nm * k];
RealD Qtmp2 = Qt[i + Nm * (k + 1)];
Qt[i + Nm * k] = c * Qtmp1 - s * Qtmp2;
Qt[i + Nm * (k + 1)] = s * Qtmp1 + c * Qtmp2;
}
// Givens transformations
for (int k = kmin; k < kmax - 1; ++k)
{
RealD Fden = 1.0 / hypot (x, lme[k - 1]);
RealD c = lme[k - 1] * Fden;
RealD s = -x * Fden;
RealD tmpa1 = lmd[k];
RealD tmpa2 = lmd[k + 1];
RealD tmpb = lme[k];
lmd[k] = c * c * tmpa1 + s * s * tmpa2 - 2.0 * c * s * tmpb;
lmd[k + 1] = s * s * tmpa1 + c * c * tmpa2 + 2.0 * c * s * tmpb;
lme[k] = c * s * (tmpa1 - tmpa2) + (c * c - s * s) * tmpb;
lme[k - 1] = c * lme[k - 1] - s * x;
if (k != kmax - 2)
{
x = -s * lme[k + 1];
lme[k + 1] = c * lme[k + 1];
}
for (int i = 0; i < Nk; ++i)
{
RealD Qtmp1 = Qt[i + Nm * k];
RealD Qtmp2 = Qt[i + Nm * (k + 1)];
Qt[i + Nm * k] = c * Qtmp1 - s * Qtmp2;
Qt[i + Nm * (k + 1)] = s * Qtmp1 + c * Qtmp2;
}
}
}
#if 0
#ifdef USE_LAPACK
#ifdef USE_MKL
#define LAPACK_INT MKL_INT
#else
#define LAPACK_INT long long
#endif
void diagonalize_lapack (std::vector < RealD > &lmd, std::vector < RealD > &lme, int N1, // all
int N2, // get
GridBase * grid)
{
const int size = Nm;
LAPACK_INT NN = N1;
double evals_tmp[NN];
double DD[NN];
double EE[NN];
for (int i = 0; i < NN; i++)
for (int j = i - 1; j <= i + 1; j++)
if (j < NN && j >= 0)
{
if (i == j)
DD[i] = lmd[i];
if (i == j)
evals_tmp[i] = lmd[i];
if (j == (i - 1))
EE[j] = lme[j];
}
LAPACK_INT evals_found;
LAPACK_INT lwork =
((18 * NN) >
(1 + 4 * NN + NN * NN) ? (18 * NN) : (1 + 4 * NN + NN * NN));
LAPACK_INT liwork = 3 + NN * 10;
LAPACK_INT iwork[liwork];
double work[lwork];
LAPACK_INT isuppz[2 * NN];
char jobz = 'N'; // calculate evals only
char range = 'I'; // calculate il-th to iu-th evals
// char range = 'A'; // calculate all evals
char uplo = 'U'; // refer to upper half of original matrix
char compz = 'I'; // Compute eigenvectors of tridiagonal matrix
int ifail[NN];
LAPACK_INT info;
// int total = QMP_get_number_of_nodes();
// int node = QMP_get_node_number();
// GridBase *grid = evec[0]._grid;
int total = grid->_Nprocessors;
int node = grid->_processor;
int interval = (NN / total) + 1;
double vl = 0.0, vu = 0.0;
LAPACK_INT il = interval * node + 1, iu = interval * (node + 1);
if (iu > NN)
iu = NN;
double tol = 0.0;
if (1)
{
memset (evals_tmp, 0, sizeof (double) * NN);
if (il <= NN)
{
printf ("total=%d node=%d il=%d iu=%d\n", total, node, il, iu);
#ifdef USE_MKL
dstegr (&jobz, &range, &NN,
#else
LAPACK_dstegr (&jobz, &range, &NN,
#endif
(double *) DD, (double *) EE, &vl, &vu, &il, &iu, // these four are ignored if second parameteris 'A'
&tol, // tolerance
&evals_found, evals_tmp, (double *) NULL, &NN,
isuppz, work, &lwork, iwork, &liwork, &info);
for (int i = iu - 1; i >= il - 1; i--)
{
printf ("node=%d evals_found=%d evals_tmp[%d] = %g\n", node,
evals_found, i - (il - 1), evals_tmp[i - (il - 1)]);
evals_tmp[i] = evals_tmp[i - (il - 1)];
if (il > 1)
evals_tmp[i - (il - 1)] = 0.;
}
}
{
grid->GlobalSumVector (evals_tmp, NN);
}
}
// cheating a bit. It is better to sort instead of just reversing it, but the document of the routine says evals are sorted in increasing order. qr gives evals in decreasing order.
}
#undef LAPACK_INT
#endif
void diagonalize (std::vector < RealD > &lmd,
std::vector < RealD > &lme,
int N2, int N1, GridBase * grid)
{
#ifdef USE_LAPACK
const int check_lapack = 0; // just use lapack if 0, check against lapack if 1
if (!check_lapack)
return diagonalize_lapack (lmd, lme, N2, N1, grid);
// diagonalize_lapack(lmd2,lme2,Nm2,Nm,Qt,grid);
#endif
}
#endif
static RealD normalise (Field & v)
{
RealD nn = norm2 (v);
nn = sqrt (nn);
v = v * (1.0 / nn);
return nn;
}
void orthogonalize (Field & w, std::vector < Field > &evec, int k)
{
double t0 = -usecond () / 1e6;
typedef typename Field::scalar_type MyComplex;
MyComplex ip;
if (0)
{
for (int j = 0; j < k; ++j)
{
normalise (evec[j]);
for (int i = 0; i < j; i++)
{
ip = innerProduct (evec[i], evec[j]); // are the evecs normalised? ; this assumes so.
evec[j] = evec[j] - ip * evec[i];
}
}
}
for (int j = 0; j < k; ++j)
{
ip = innerProduct (evec[j], w); // are the evecs normalised? ; this assumes so.
w = w - ip * evec[j];
}
normalise (w);
t0 += usecond () / 1e6;
OrthoTime += t0;
}
void setUnit_Qt (int Nm, std::vector < RealD > &Qt)
{
for (int i = 0; i < Qt.size (); ++i)
Qt[i] = 0.0;
for (int k = 0; k < Nm; ++k)
Qt[k + k * Nm] = 1.0;
}
void calc (std::vector < RealD > &eval, const Field & src, int &Nconv)
{
GridBase *grid = src.Grid();
// assert(grid == src._grid);
std::
cout << GridLogMessage << " -- Nk = " << Nk << " Np = " << Np << std::
endl;
std::cout << GridLogMessage << " -- Nm = " << Nm << std::endl;
std::cout << GridLogMessage << " -- size of eval = " << eval.
size () << std::endl;
// assert(c.size() && Nm == eval.size());
std::vector < RealD > lme (Nm);
std::vector < RealD > lmd (Nm);
Field current (grid);
Field last (grid);
Field next (grid);
Nconv = 0;
RealD beta_k;
// Set initial vector
// (uniform vector) Why not src??
// evec[0] = 1.0;
current = src;
std::cout << GridLogMessage << "norm2(src)= " << norm2 (src) << std::
endl;
normalise (current);
std::
cout << GridLogMessage << "norm2(evec[0])= " << norm2 (current) <<
std::endl;
// Initial Nk steps
OrthoTime = 0.;
double t0 = usecond () / 1e6;
RealD norm; // sqrt norm of last vector
uint64_t iter = 0;
bool initted = false;
std::vector < RealD > low (Nstop * 10);
std::vector < RealD > high (Nstop * 10);
RealD cont = 0.;
while (1) {
cont = 0.;
std::vector < RealD > lme2 (Nm);
std::vector < RealD > lmd2 (Nm);
for (uint64_t k = 0; k < Nm; ++k, iter++) {
step (lmd, lme, last, current, next, iter);
last = current;
current = next;
}
double t1 = usecond () / 1e6;
std::cout << GridLogMessage << "IRL::Initial steps: " << t1 -
t0 << "seconds" << std::endl;
t0 = t1;
std::
cout << GridLogMessage << "IRL::Initial steps:OrthoTime " <<
OrthoTime << "seconds" << std::endl;
// getting eigenvalues
lmd2.resize (iter + 2);
lme2.resize (iter + 2);
for (uint64_t k = 0; k < iter; ++k) {
lmd2[k + 1] = lmd[k];
lme2[k + 2] = lme[k];
}
t1 = usecond () / 1e6;
std::cout << GridLogMessage << "IRL:: copy: " << t1 -
t0 << "seconds" << std::endl;
t0 = t1;
{
int total = grid->_Nprocessors;
int node = grid->_processor;
int interval = (Nstop / total) + 1;
int iu = (iter + 1) - (interval * node + 1);
int il = (iter + 1) - (interval * (node + 1));
std::vector < RealD > eval2 (iter + 3);
RealD eps2;
Bisection::bisec (lmd2, lme2, iter, il, iu, 1e-16, 1e-10, eval2,
eps2);
// diagonalize(eval2,lme2,iter,Nk,grid);
RealD diff = 0.;
for (int i = il; i <= iu; i++) {
if (initted)
diff =
fabs (eval2[i] - high[iu-i]) / (fabs (eval2[i]) +
fabs (high[iu-i]));
if (initted && (diff > eresid))
cont = 1.;
if (initted)
printf ("eval[%d]=%0.14e %0.14e, %0.14e\n", i, eval2[i],
high[iu-i], diff);
high[iu-i] = eval2[i];
}
il = (interval * node + 1);
iu = (interval * (node + 1));
Bisection::bisec (lmd2, lme2, iter, il, iu, 1e-16, 1e-10, eval2,
eps2);
for (int i = il; i <= iu; i++) {
if (initted)
diff =
fabs (eval2[i] - low[i]) / (fabs (eval2[i]) +
fabs (low[i]));
if (initted && (diff > eresid))
cont = 1.;
if (initted)
printf ("eval[%d]=%0.14e %0.14e, %0.14e\n", i, eval2[i],
low[i], diff);
low[i] = eval2[i];
}
t1 = usecond () / 1e6;
std::cout << GridLogMessage << "IRL:: diagonalize: " << t1 -
t0 << "seconds" << std::endl;
t0 = t1;
}
for (uint64_t k = 0; k < Nk; ++k) {
// eval[k] = eval2[k];
}
if (initted)
{
grid->GlobalSumVector (&cont, 1);
if (cont < 1.) return;
}
initted = true;
}
}
#if 0
/**
There is some matrix Q such that for any vector y
Q.e_1 = y and Q is unitary.
**/
template < class T >
static T orthQ (DenseMatrix < T > &Q, std::vector < T > y)
{
int N = y.size (); //Matrix Size
Fill (Q, 0.0);
T tau;
for (int i = 0; i < N; i++)
{
Q[i][0] = y[i];
}
T sig = conj (y[0]) * y[0];
T tau0 = fabs (sqrt (sig));
for (int j = 1; j < N; j++)
{
sig += conj (y[j]) * y[j];
tau = abs (sqrt (sig));
if (abs (tau0) > 0.0)
{
T gam = conj ((y[j] / tau) / tau0);
for (int k = 0; k <= j - 1; k++)
{
Q[k][j] = -gam * y[k];
}
Q[j][j] = tau0 / tau;
}
else
{
Q[j - 1][j] = 1.0;
}
tau0 = tau;
}
return tau;
}
/**
There is some matrix Q such that for any vector y
Q.e_k = y and Q is unitary.
**/
template < class T >
static T orthU (DenseMatrix < T > &Q, std::vector < T > y)
{
T tau = orthQ (Q, y);
SL (Q);
return tau;
}
/**
Wind up with a matrix with the first con rows untouched
say con = 2
Q is such that Qdag H Q has {x, x, val, 0, 0, 0, 0, ...} as 1st colum
and the matrix is upper hessenberg
and with f and Q appropriately modidied with Q is the arnoldi factorization
**/
template < class T > static void Lock (DenseMatrix < T > &H, ///Hess mtx
DenseMatrix < T > &Q, ///Lock Transform
T val, ///value to be locked
int con, ///number already locked
RealD small, int dfg, bool herm)
{
//ForceTridiagonal(H);
int M = H.dim;
DenseVector < T > vec;
Resize (vec, M - con);
DenseMatrix < T > AH;
Resize (AH, M - con, M - con);
AH = GetSubMtx (H, con, M, con, M);
DenseMatrix < T > QQ;
Resize (QQ, M - con, M - con);
Unity (Q);
Unity (QQ);
DenseVector < T > evals;
Resize (evals, M - con);
DenseMatrix < T > evecs;
Resize (evecs, M - con, M - con);
Wilkinson < T > (AH, evals, evecs, small);
int k = 0;
RealD cold = abs (val - evals[k]);
for (int i = 1; i < M - con; i++)
{
RealD cnew = abs (val - evals[i]);
if (cnew < cold)
{
k = i;
cold = cnew;
}
}
vec = evecs[k];
ComplexD tau;
orthQ (QQ, vec);
//orthQM(QQ,AH,vec);
AH = Hermitian (QQ) * AH;
AH = AH * QQ;
for (int i = con; i < M; i++)
{
for (int j = con; j < M; j++)
{
Q[i][j] = QQ[i - con][j - con];
H[i][j] = AH[i - con][j - con];
}
}
for (int j = M - 1; j > con + 2; j--)
{
DenseMatrix < T > U;
Resize (U, j - 1 - con, j - 1 - con);
DenseVector < T > z;
Resize (z, j - 1 - con);
T nm = norm (z);
for (int k = con + 0; k < j - 1; k++)
{
z[k - con] = conj (H (j, k + 1));
}
normalise (z);
RealD tmp = 0;
for (int i = 0; i < z.size () - 1; i++)
{
tmp = tmp + abs (z[i]);
}
if (tmp < small / ((RealD) z.size () - 1.0))
{
continue;
}
tau = orthU (U, z);
DenseMatrix < T > Hb;
Resize (Hb, j - 1 - con, M);
for (int a = 0; a < M; a++)
{
for (int b = 0; b < j - 1 - con; b++)
{
T sum = 0;
for (int c = 0; c < j - 1 - con; c++)
{
sum += H[a][con + 1 + c] * U[c][b];
} //sum += H(a,con+1+c)*U(c,b);}
Hb[b][a] = sum;
}
}
for (int k = con + 1; k < j; k++)
{
for (int l = 0; l < M; l++)
{
H[l][k] = Hb[k - 1 - con][l];
}
} //H(Hb[k-1-con][l] , l,k);}}
DenseMatrix < T > Qb;
Resize (Qb, M, M);
for (int a = 0; a < M; a++)
{
for (int b = 0; b < j - 1 - con; b++)
{
T sum = 0;
for (int c = 0; c < j - 1 - con; c++)
{
sum += Q[a][con + 1 + c] * U[c][b];
} //sum += Q(a,con+1+c)*U(c,b);}
Qb[b][a] = sum;
}
}
for (int k = con + 1; k < j; k++)
{
for (int l = 0; l < M; l++)
{
Q[l][k] = Qb[k - 1 - con][l];
}
} //Q(Qb[k-1-con][l] , l,k);}}
DenseMatrix < T > Hc;
Resize (Hc, M, M);
for (int a = 0; a < j - 1 - con; a++)
{
for (int b = 0; b < M; b++)
{
T sum = 0;
for (int c = 0; c < j - 1 - con; c++)
{
sum += conj (U[c][a]) * H[con + 1 + c][b];
} //sum += conj( U(c,a) )*H(con+1+c,b);}
Hc[b][a] = sum;
}
}
for (int k = 0; k < M; k++)
{
for (int l = con + 1; l < j; l++)
{
H[l][k] = Hc[k][l - 1 - con];
}
} //H(Hc[k][l-1-con] , l,k);}}
}
}
#endif
};
}
#endif
@@ -0,0 +1,124 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/SplitGridBlockKrylovSchur.h
Copyright (C) 2015
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: Chulwoo Jung <chulwoo@bnl.gov>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#ifndef GRID_SPLIT_BLOCK_KRYLOV_SCHUR_H
#define GRID_SPLIT_BLOCK_KRYLOV_SCHUR_H
NAMESPACE_BEGIN(Grid);
/**
* Block Krylov-Schur eigensolver with Split-Grid batched operator application.
*
* Derives from BlockKrylovSchur<Field> and overrides only the operator
* application: instead of calling Linop.Op() once per vector, mrhs vectors
* are packed onto a smaller split grid with Grid_split, the polynomial-
* filtered operator poly(SLinop) is applied once to the combined field, and
* the results are unpacked with Grid_unsplit. All other algorithmic logic
* (Arnoldi orthogonalisation, Schur restart, convergence test, etc.) is
* inherited unchanged from the base class.
*
* Constructor extras (beyond BlockKrylovSchur)
* -------------------------------------------
* SLinop : split-grid linear operator used inside poly
* poly : polynomial filter OperatorFunction applied on the split grid
* SGrid : split-grid GridBase (mrhs fields packed here)
* mrhs : RHS batched per split-grid call; Nblock must be divisible by mrhs
*
* Notes
* -----
* - The Rayleigh quotient H is built from poly(A), so its Ritz values are
* eigenvalues of poly(A). Convergence is assessed against the full-grid
* Linop (inherited from base), giving true A-eigenvalues.
* - Grid_ (inherited) is the full grid; SGrid is the split grid.
*/
template<class Field>
class SplitGridBlockKrylovSchur : public BlockKrylovSchur<Field> {
using Base = BlockKrylovSchur<Field>;
// Bring protected base members into scope
using Base::Nblock;
using Base::Grid_;
using Base::basis;
// Split-grid extras
LinearOperatorBase<Field>& SLinop;
OperatorFunction<Field>& poly;
GridBase* SGrid;
int mrhs;
public:
SplitGridBlockKrylovSchur(LinearOperatorBase<Field>& _Linop,
LinearOperatorBase<Field>& _SLinop,
OperatorFunction<Field>& _poly,
GridBase* _FGrid,
GridBase* _SGrid,
int _mrhs,
RealD _Tolerance,
RitzFilter _rf = EvalReSmall)
: Base(_Linop, _FGrid, _Tolerance, _rf),
SLinop(_SLinop), poly(_poly), SGrid(_SGrid), mrhs(_mrhs)
{}
protected:
// Validate mrhs divisibility before the Arnoldi loop starts
void preRun() override
{
assert(Nblock % mrhs == 0 && "Nblock must be divisible by mrhs");
}
// Apply poly(A) to basis[kBase .. kBase+Nblock-1] via Grid_split / Grid_unsplit,
// batching mrhs vectors per split-grid call.
void applyBlock(std::vector<Field>& W, int kBase) override
{
std::vector<Field> in(mrhs, Field(Grid_));
Field s_in(SGrid);
Field s_out(SGrid);
int k_start = 0;
while (k_start < Nblock) {
for (int u = 0; u < mrhs; u++)
in[u] = basis[kBase + k_start + u];
Grid_split(in, s_in);
poly(SLinop, s_in, s_out);
Grid_unsplit(in, s_out);
for (int u = 0; u < mrhs; u++)
W[k_start + u] = in[u];
k_start += mrhs;
}
}
};
NAMESPACE_END(Grid);
#endif // GRID_SPLIT_BLOCK_KRYLOV_SCHUR_H
@@ -0,0 +1,366 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/TrueHarmonicBlockKrylovSchur.h
Copyright (C) 2015
Author: Chulwoo Jung <chulwoo@bnl.gov>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#ifndef GRID_TRUE_HARMONIC_BLOCKED_KRYLOV_SCHUR_H
#define GRID_TRUE_HARMONIC_BLOCKED_KRYLOV_SCHUR_H
#include <Grid/algorithms/iterative/HarmonicBlockKrylovSchur.h>
NAMESPACE_BEGIN(Grid);
/**
* True harmonic-Ritz block Krylov-Schur eigensolver.
*
* Difference from the base class
* ------------------------------
* HarmonicBlockKrylovSchur is a *shift-targeted* Krylov-Schur: it Schur-
* decomposes (H - sigma I) and sorts the standard Ritz values by |lambda -
* sigma|. For interior eigenvalues of Hermitian indefinite operators the
* standard Ritz values can be spurious ("ghosts"), so the sort can select
* junk. This variant performs genuine harmonic Ritz extraction instead.
*
* Harmonic extraction
* -------------------
* From the exact block Arnoldi relation
*
* A V = V H + F B^dag (1)
*
* harmonic Ritz pairs (theta, g) w.r.t. shift sigma satisfy the Petrov-
* Galerkin condition (A - sigma)Vg - (theta - sigma)Vg perp (A - sigma)V,
* which reduces (with Hs = H - sigma I) to the small eigenproblem
*
* Hhat g = (theta - sigma) g, Hhat = Hs + Hs^{-H} B B^dag (2)
*
* Exact thick restart
* -------------------
* From (2), H g = theta g - Hs^{-H} B (B^dag g), so the residual of every
* harmonic pair lies in the Nblock-dimensional column space of the single
* block
*
* W = F - V Hs^{-H} B :
* A (V g) - theta (V g) = W (B^dag g) (3)
*
* Hence {V g_1 .. g_Nk} + cols(W) admits an EXACT truncated Krylov-Schur
* relation A Y = Y H_k + What Bc^dag with dense H_k, and block Arnoldi
* resumes cleanly from What. Krylov-Schur (Stewart, SIMAX 23(3), 2001)
* does not require H to be triangular after restart -- only that the
* decomposition holds exactly, which (3) guarantees.
*
* This is the block analogue of the GMRES-DR restart (Morgan, SISC 24(1),
* 2002; block version: Morgan, Appl. Numer. Math. 54, 2005). The exact
* composition "block harmonic Krylov-Schur" is a derivation from these
* ingredients rather than a published algorithm; the relation (1) is
* checked explicitly at every restart when doVerify=true, and identity (3)
* was validated to machine precision in a standalone Eigen prototype.
*
* Caveats
* -------
* - If sigma is (numerically) an eigenvalue of H, Hs^{-H} is singular;
* an assert fires -- perturb the shift slightly.
* - Reported eigenvalues come from the eigensystem of the retained H_k
* (the true projected Rayleigh quotient Y^dag A Y), as in the base
* class, so convergence tests and reporting are inherited unchanged.
*
* Usage: identical to HarmonicBlockKrylovSchur.
*/
template<class Field>
class TrueHarmonicBlockKrylovSchur : public HarmonicBlockKrylovSchur<Field> {
typedef HarmonicBlockKrylovSchur<Field> Base;
typedef typename Base::CMat CMat;
typedef typename Base::CVec CVec;
// Members of the dependent base class need explicit scope.
using Base::Nblock;
using Base::Nm;
using Base::Nk;
using Base::Nstop;
using Base::MaxIter;
using Base::Tolerance;
using Base::shift;
using Base::Linop;
using Base::Grid_;
using Base::ritzFilter;
using Base::basis;
using Base::H;
using Base::F;
using Base::B;
using Base::beta_k;
using Base::rtol;
using Base::evals;
using Base::littleEvecs;
using Base::ritzEstimates;
using Base::expandStartBlock;
using Base::blockArnoldiIteration;
using Base::blockQR;
using Base::computeEigensystem;
using Base::converged;
using Base::approxMaxEval;
public:
using Base::evecs;
using Base::doEvalCheck;
using Base::useParityFlip;
using Base::useGamma5;
using Base::gamma5Func;
using Base::verify;
TrueHarmonicBlockKrylovSchur(LinearOperatorBase<Field>& _Linop, GridBase* _Grid,
RealD _Tolerance, ComplexD _shift = 0.0,
RitzFilter _rf = EvalNormSmall)
: Base(_Linop, _Grid, _Tolerance, _shift, _rf)
{}
//--------------------------------------------------------------------
// Main entry point (same signature and outer structure as the base;
// the Schur-sort restart is replaced by harmonicRestart()).
//--------------------------------------------------------------------
virtual void operator()(const std::vector<Field>& v0, int _maxIter, int _Nm, int _Nk,
int _Nstop, int _Nblock = 1, bool doubleOrthog = true,
bool doVerify = false) override
{
MaxIter = _maxIter;
Nm = _Nm;
Nk = _Nk;
Nstop = _Nstop;
Nblock = _Nblock;
{
int divisor = 1;
if (useParityFlip) divisor *= 2;
if (useGamma5) divisor *= 2;
assert(Nblock % divisor == 0 && (int)v0.size() >= Nblock / divisor);
}
assert(Nm % Nblock == 0);
assert(Nk % Nblock == 0);
assert(Nk < Nm);
if (useGamma5) assert(gamma5Func && "useGamma5: gamma5Func must be set");
int N = Nm;
RealD approxLambdaMax = approxMaxEval(v0[0]);
rtol = Tolerance * approxLambdaMax;
std::cout << GridLogMessage
<< "TrueHarmonicBlockKrylovSchur: approx max eval = " << approxLambdaMax
<< ", rtol = " << rtol
<< ", shift = " << shift << std::endl;
H = CMat::Zero(N, N);
B = CMat::Zero(N, Nblock);
int start = 0;
std::vector<Field> startBlock = expandStartBlock(v0);
for (int iter = 0; iter < MaxIter; iter++) {
std::cout << GridLogMessage
<< "TrueHarmonicBlockKrylovSchur: restart iteration " << iter << std::endl;
// ---- Block Arnoldi: extend from block 'start' to block Nm/Nblock ----
blockArnoldiIteration(startBlock, Nm/Nblock, start, doubleOrthog);
std::cout << GridLogMessage << "blockArnoldiIteration done " << std::endl;
start = Nk/Nblock;
if (doVerify) {
std::string lbl = "iter " + std::to_string(iter) + " after Arnoldi";
verify(lbl);
}
// ---- Harmonic extraction + exact thick restart ----
harmonicRestart();
std::cout << GridLogMessage << "harmonicRestart done " << std::endl;
// Restart from the residual block W (exact by identity (3))
startBlock = F;
if (doVerify) {
std::string lbl = "iter " + std::to_string(iter) + " after harmonic restart";
verify(lbl);
}
// ---- Eigensystem of retained H_k = Y^dag A Y for convergence ----
CMat Hk = H(Eigen::seqN(0, Nk), Eigen::seqN(0, Nk));
computeEigensystem(Hk, Nk);
int Nconv = converged(Nk);
std::cout << GridLogMessage
<< "TrueHarmonicBlockKrylovSchur: converged " << Nconv
<< " / " << Nstop << std::endl;
if (Nconv >= Nstop || iter == MaxIter - 1) {
std::cout << GridLogMessage
<< "TrueHarmonicBlockKrylovSchur: done after " << iter
<< " restarts, " << Nconv << " converged." << std::endl;
std::cout << GridLogMessage << "Eigenvalues: " << evals.transpose() << std::endl;
if (doEvalCheck) {
Field w(Grid_);
for (int k = 0; k < (int)evecs.size(); k++) {
Linop.Op(evecs[k], w);
ComplexD eval_est = toStdCmplx(innerProduct(evecs[k], w));
w -= eval_est * evecs[k];
RealD res = std::sqrt(norm2(w));
std::cout << GridLogMessage << "TrueHarmonicBlockKrylovSchur: evec[" << k << "]"
<< " eval_reported = " << evals[k]
<< " eval_est = " << eval_est
<< " || A v - eval_est * v || = " << res << std::endl;
}
}
return;
}
}
}
protected:
//--------------------------------------------------------------------
// Harmonic thick restart
//--------------------------------------------------------------------
// On entry: A V = V H + F B^dag exact, V = basis (Nm fields), F Nblock
// residual fields, H/B the Nm-sized small matrices.
// On exit: basis = Y (Nk orthonormal fields spanning the harmonic
// Ritz space), F = What (Nblock orthonormal fields, perp Y),
// H top-left Nk x Nk = Y^dag A Y (dense), B top Nk rows the
// new coupling; relation A Y = Y H_k + F B^dag again exact.
void harmonicRestart()
{
int N = Nm;
// ---- Small harmonic eigenproblem: Hhat g = (theta - sigma) g ----
CMat Hs = H - shift * CMat::Identity(N, N);
Eigen::FullPivLU<CMat> lu(Hs.adjoint());
assert(lu.isInvertible() &&
"TrueHarmonicBlockKrylovSchur: H - shift*I singular; perturb the shift");
CMat M = lu.solve(B); // N x Nblock : Hs^{-H} B
CMat Hhat = Hs + M * B.adjoint(); // N x N
Eigen::ComplexEigenSolver<CMat> es(Hhat);
// Sort harmonic values (theta - sigma) by the Ritz filter
// (EvalNormSmall -> closest to the shift).
ComplexComparator cComp(ritzFilter);
std::vector<int> idx(N);
std::iota(idx.begin(), idx.end(), 0);
std::sort(idx.begin(), idx.end(), [&](int a, int b){
return cComp(toStdCmplx(es.eigenvalues()(a)), toStdCmplx(es.eigenvalues()(b)));
});
CMat G(N, Nk);
CVec theta(Nk);
for (int k = 0; k < Nk; k++) {
G.col(k) = es.eigenvectors().col(idx[k]);
theta(k) = es.eigenvalues()(idx[k]) + toStdCmplx(shift);
}
std::cout << GridLogMessage
<< "TrueHarmonicBlockKrylovSchur: harmonic Ritz values nearest shift:" << std::endl;
for (int k = 0; k < Nk; k++)
std::cout << GridLogMessage << " [" << k << "] " << theta(k) << std::endl;
// ---- Orthonormalise the little vectors: G = Gt Rg ----
Eigen::HouseholderQR<CMat> qr(G);
CMat Gt = qr.householderQ() * CMat::Identity(N, Nk);
CMat Rg = qr.matrixQR().topLeftCorner(Nk, Nk).template triangularView<Eigen::Upper>();
for (int k = 0; k < Nk; k++)
assert(std::abs(Rg(k, k)) > 1e-14 &&
"TrueHarmonicBlockKrylovSchur: harmonic vectors linearly dependent");
CMat RgInv = Rg.inverse();
// A (V G) = (V G) diag(theta) + W (B^dag G), W = F - V M
// => A (V Gt) = (V Gt) T + W Ct
CMat T = Rg * theta.asDiagonal() * RgInv; // Nk x Nk
CMat Ct = (B.adjoint() * G) * RgInv; // Nblock x Nk
// ---- Large-field work ----
// Y = V Gt : new orthonormal basis (Nk fields)
std::vector<Field> Y;
constructThin(Y, basis, Gt, Nk);
// W = F - V M : Nblock residual directions containing every harmonic
// residual (identity (3) in the class comment)
std::vector<Field> W = F;
for (int t = 0; t < Nblock; t++)
for (int j = 0; j < N; j++)
W[t] -= basis[j] * M(j, t);
// Orthogonalise W against Y (two passes), recording P = Y^dag W
CMat P = CMat::Zero(Nk, Nblock);
for (int pass = 0; pass < 2; pass++) {
for (int j = 0; j < Nk; j++) {
for (int t = 0; t < Nblock; t++) {
ComplexD c = innerProduct(Y[j], W[t]);
P(j, t) += toStdCmplx(c);
W[t] -= c * Y[j];
}
}
}
// Block QR of the projected residual: W <- What, returns Rw
CMat Rw = blockQR(W);
// ---- Reassemble the exact Krylov-Schur relation ----
// A Y = Y (T + P Ct) + What (Rw Ct)
CMat Hk = T + P * Ct; // Nk x Nk, dense
CMat Bc = Rw * Ct; // Nblock x Nk
H = CMat::Zero(N, N);
H(Eigen::seqN(0, Nk), Eigen::seqN(0, Nk)) = Hk;
B = CMat::Zero(N, Nblock);
for (int j = 0; j < Nk; j++)
for (int t = 0; t < Nblock; t++)
B(j, t) = std::conj(Bc(t, j));
basis = Y;
F = W;
beta_k = Bc.norm();
std::cout << GridLogMessage
<< "TrueHarmonicBlockKrylovSchur: beta_k = " << beta_k << std::endl;
}
//--------------------------------------------------------------------
// Thin basis combination: out[i] = sum_j U[j] * R(j,i), i < ncol
//--------------------------------------------------------------------
void constructThin(std::vector<Field>& out, const std::vector<Field>& U,
const CMat& R, int ncol)
{
out.clear();
Field tmp(Grid_);
for (int i = 0; i < ncol; i++) {
tmp = Zero();
for (int j = 0; j < (int)U.size(); j++)
tmp += U[j] * R(j, i);
out.push_back(tmp);
}
}
};
NAMESPACE_END(Grid);
#endif // GRID_TRUE_HARMONIC_BLOCKED_KRYLOV_SCHUR_H
+2 -1
View File
@@ -260,7 +260,8 @@ class GridLimeReader : public BinaryIO {
<< " / field= " << n2ck << " / rdiff= " << GRID_FIELD_NORM_CALC(FieldNormMetaData_,n2ck) << std::endl;
GRID_FIELD_NORM_CHECK(FieldNormMetaData_,n2ck);
}
assert(scidacChecksumVerify(scidacChecksum_,scidac_csuma,scidac_csumb)==1);
// assert(scidacChecksumVerify(scidacChecksum_,scidac_csuma,scidac_csumb)==1);
scidacChecksumVerify(scidacChecksum_,scidac_csuma,scidac_csumb);
// find out if next field is a GridFieldNorm
return;
+1 -1
View File
@@ -122,7 +122,7 @@ public:
field.checksum = std::stoul(header["CHECKSUM"],0,16);
field.ensemble_id = header["ENSEMBLE_ID"];
field.ensemble_label = header["ENSEMBLE_LABEL"];
field.sequence_number = std::stol(header["SEQUENCE_NUMBER"]);
// field.sequence_number = std::stol(header["SEQUENCE_NUMBER"]);
field.creator = header["CREATOR"];
field.creator_hardware = header["CREATOR_HARDWARE"];
field.creation_date = header["CREATION_DATE"];
+5
View File
@@ -28,6 +28,11 @@ Author: paboyle <paboyle@ph.ed.ac.uk>
/* END LEGAL */
#pragma once
#ifndef MIN
#define MIN(x,y) ((x)>(y)?(y):(x))
#endif
// Introduce a class to gain deterministic bit reproducible reduction.
// make static; perhaps just a namespace is required.
NAMESPACE_BEGIN(Grid);
+1 -1
View File
@@ -877,7 +877,7 @@ int main (int argc, char ** argv)
int do_su4=0;
int do_memory=1;
int do_comms =1;
int do_blas =1;
int do_blas =0;
int do_dslash=1;
int sel=4;
+1 -1
View File
@@ -174,7 +174,7 @@ esac
############### fermions
AC_ARG_ENABLE([fermion-instantiations],
[AS_HELP_STRING([--enable-fermion-instantiations=yes|no],[enable fermion instantiations])],
[ac_FERMION_REPS=${enable_fermion_instantiations}], [ac_FERMION_INSTANTIATIONS=yes])
[ac_FERMION_INSTANTIATIONS=${enable_fermion_instantiations}], [ac_FERMION_INSTANTIATIONS=yes])
AM_CONDITIONAL(BUILD_FERMION_INSTANTIATIONS, [ test "${ac_FERMION_INSTANTIATIONS}X" == "yesX" ])
+430
View File
@@ -0,0 +1,430 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./tests/Test_padded_cell.cc
Copyright (C) 2023
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
// copied here from Test_general_coarse_pvdagm.cc
#include <cstdlib>
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidual.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
#include <Grid/algorithms/iterative/BiCGSTAB.h>
using namespace std;
using namespace Grid;
// Hermitize a DWF operator by squaring it
template<class Matrix,class Field>
class SquaredLinearOperator : public LinearOperatorBase<Field> {
public:
Matrix &_Mat;
public:
SquaredLinearOperator(Matrix &Mat): _Mat(Mat) {};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
// std::cout << "Op is overloaded as HermOp" << std::endl;
HermOp(in, out);
}
void AdjOp (const Field &in, Field &out){
HermOp(in, out);
}
void _Op (const Field &in, Field &out){
// std::cout << "Op: M "<<std::endl;
_Mat.M(in, out);
}
void _AdjOp (const Field &in, Field &out){
// std::cout << "AdjOp: Mdag "<<std::endl;
_Mat.Mdag(in, out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
// std::cout << "HermOp: Mdag M Mdag M"<<std::endl;
Field tmp(in.Grid());
_Op(in,tmp);
_AdjOp(tmp,out);
}
};
template<class Matrix,class Field>
class PVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
public:
PVdagMLinearOperator(Matrix &Mat,Matrix &PV): _Mat(Mat),_PV(PV){};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(in,tmp);
_Mat.Mdag(tmp,out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
// _Mat.M(in,tmp);
// _PV.Mdag(tmp,out);
// _PV.M(out,tmp);
// _Mat.Mdag(tmp,out);
Op(in,tmp);
AdjOp(tmp,out);
// std::cout << "HermOp done "<<norm2(out)<<std::endl;
}
};
template<class Matrix,class Field>
class ShiftedPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
RealD shift;
public:
ShiftedPVdagMLinearOperator(RealD _shift,Matrix &Mat,Matrix &PV): shift(_shift),_Mat(Mat),_PV(PV){};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(tmp,out);
_Mat.Mdag(in,tmp);
out = out + shift * in;
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
};
template<class Matrix, class Field>
class ShiftedComplexPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
ComplexD shift;
public:
ShiftedComplexPVdagMLinearOperator(ComplexD _shift,Matrix &Mat,Matrix &PV): shift(_shift),_Mat(Mat),_PV(PV){};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(tmp,out);
_Mat.Mdag(in,tmp);
out = out + shift * in;
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
void resetShift(ComplexD newShift) {
shift = newShift;
}
};
template<class Fobj,class CComplex,int nbasis>
class MGPreconditioner : public LinearFunction< Lattice<Fobj> > {
public:
using LinearFunction<Lattice<Fobj> >::operator();
typedef Aggregation<Fobj,CComplex,nbasis> Aggregates;
typedef typename Aggregation<Fobj,CComplex,nbasis>::FineField FineField;
typedef typename Aggregation<Fobj,CComplex,nbasis>::CoarseVector CoarseVector;
typedef typename Aggregation<Fobj,CComplex,nbasis>::CoarseMatrix CoarseMatrix;
typedef LinearOperatorBase<FineField> FineOperator;
typedef LinearFunction <FineField> FineSmoother;
typedef LinearOperatorBase<CoarseVector> CoarseOperator;
typedef LinearFunction <CoarseVector> CoarseSolver;
Aggregates & _Aggregates;
FineOperator & _FineOperator;
FineSmoother & _PreSmoother;
FineSmoother & _PostSmoother;
CoarseOperator & _CoarseOperator;
CoarseSolver & _CoarseSolve;
int level; void Level(int lv) {level = lv; };
MGPreconditioner(Aggregates &Agg,
FineOperator &Fine,
FineSmoother &PreSmoother,
FineSmoother &PostSmoother,
CoarseOperator &CoarseOperator_,
CoarseSolver &CoarseSolve_)
: _Aggregates(Agg),
_FineOperator(Fine),
_PreSmoother(PreSmoother),
_PostSmoother(PostSmoother),
_CoarseOperator(CoarseOperator_),
_CoarseSolve(CoarseSolve_),
level(1) { }
virtual void operator()(const FineField &in, FineField & out)
{
GridBase *CoarseGrid = _Aggregates.CoarseGrid;
// auto CoarseGrid = _CoarseOperator.Grid();
CoarseVector Csrc(CoarseGrid);
CoarseVector Csol(CoarseGrid);
FineField vec1(in.Grid());
FineField vec2(in.Grid());
std::cout<<GridLogMessage << "Calling PreSmoother " <<std::endl;
// std::cout<<GridLogMessage << "Calling PreSmoother input residual "<<norm2(in) <<std::endl;
double t;
// Fine Smoother
// out = in;
out = Zero();
t=-usecond();
_PreSmoother(in,out);
t+=usecond();
std::cout<<GridLogMessage << "PreSmoother took "<< t/1000.0<< "ms" <<std::endl;
// Update the residual
_FineOperator.Op(out,vec1); sub(vec1, in ,vec1);
// std::cout<<GridLogMessage <<"Residual-1 now " <<norm2(vec1)<<std::endl;
// Fine to Coarse
t=-usecond();
_Aggregates.ProjectToSubspace (Csrc,vec1);
t+=usecond();
std::cout<<GridLogMessage << "Project to coarse took "<< t/1000.0<< "ms" <<std::endl;
// Coarse correction
t=-usecond();
Csol = Zero();
_CoarseSolve(Csrc,Csol);
//Csol=Zero();
t+=usecond();
std::cout<<GridLogMessage << "Coarse solve took "<< t/1000.0<< "ms" <<std::endl;
// Coarse to Fine
t=-usecond();
// _CoarseOperator.PromoteFromSubspace(_Aggregates,Csol,vec1);
_Aggregates.PromoteFromSubspace(Csol,vec1);
add(out,out,vec1);
t+=usecond();
std::cout<<GridLogMessage << "Promote to this level took "<< t/1000.0<< "ms" <<std::endl;
// Residual
_FineOperator.Op(out,vec1); sub(vec1 ,in , vec1);
// std::cout<<GridLogMessage <<"Residual-2 now " <<norm2(vec1)<<std::endl;
// Fine Smoother
t=-usecond();
// vec2=vec1;
vec2=Zero();
_PostSmoother(vec1,vec2);
t+=usecond();
std::cout<<GridLogMessage << "PostSmoother took "<< t/1000.0<< "ms" <<std::endl;
add( out,out,vec2);
std::cout<<GridLogMessage << "Done " <<std::endl;
}
};
int main (int argc, char ** argv)
{
Grid_init(&argc,&argv);
const int Ls=16;
// GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(), GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());
std::vector<int> lat_size {16, 16, 16, 32};
std::cout << "Lattice size: " << lat_size << std::endl;
GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(lat_size,
GridDefaultSimd(Nd,vComplex::Nsimd()),
GridDefaultMpi());
GridRedBlackCartesian * UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid);
GridCartesian * FGrid = SpaceTimeGrid::makeFiveDimGrid(Ls,UGrid);
GridRedBlackCartesian * FrbGrid = SpaceTimeGrid::makeFiveDimRedBlackGrid(Ls,UGrid);
// Construct a coarsened grid
// poare TODO: replace this with the following line?
Coordinate clatt = lat_size;
// Coordinate clatt = GridDefaultLatt(); // [PO] initial line before I edited it
for(int d=0;d<clatt.size();d++){
clatt[d] = clatt[d]/2;
// clatt[d] = clatt[d]/4;
}
GridCartesian *Coarse4d = SpaceTimeGrid::makeFourDimGrid(clatt, GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());;
GridCartesian *Coarse5d = SpaceTimeGrid::makeFiveDimGrid(1,Coarse4d);
std::vector<int> seeds4({1,2,3,4});
std::vector<int> seeds5({5,6,7,8});
std::vector<int> cseeds({5,6,7,8});
GridParallelRNG RNG5(FGrid); RNG5.SeedFixedIntegers(seeds5);
GridParallelRNG RNG4(UGrid); RNG4.SeedFixedIntegers(seeds4);
GridParallelRNG CRNG(Coarse5d);CRNG.SeedFixedIntegers(cseeds);
LatticeFermion src(FGrid); random(RNG5,src);
LatticeFermion result(FGrid); result=Zero();
LatticeFermion ref(FGrid); ref=Zero();
LatticeFermion tmp(FGrid);
LatticeFermion err(FGrid);
LatticeGaugeField Umu(UGrid);
FieldMetaData header;
// std::string file("ckpoint_lat.4000");
std::string file("/Users/patrickoare/libraries/PETSc-Grid/ckpoint_lat.4000");
NerscIO::readConfiguration(Umu,header,file);
RealD mass=0.01;
RealD M5=1.8;
DomainWallFermionD Ddwf(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,mass,M5);
DomainWallFermionD Dpv(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,1.0,M5);
// const int nbasis = 20; // size of approximate basis for low-mode space
const int nbasis = 3; // size of approximate basis for low-mode space
const int cb = 0 ;
LatticeFermion prom(FGrid);
typedef GeneralCoarsenedMatrix<vSpinColourVector,vTComplex,nbasis> LittleDiracOperator;
typedef LittleDiracOperator::CoarseVector CoarseVector;
NextToNearestStencilGeometry5D geom(Coarse5d);
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
typedef PVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> PVdagM_t;
typedef ShiftedPVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> ShiftedPVdagM_t;
typedef ShiftedComplexPVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> ShiftedComplexPVdagM_t;
PVdagM_t PVdagM(Ddwf, Dpv);
ShiftedPVdagM_t ShiftedPVdagM(0.1,Ddwf,Dpv);
SquaredLinearOperator<DomainWallFermionD, LatticeFermionD> Dsq (Ddwf);
NonHermitianLinearOperator<DomainWallFermionD, LatticeFermionD> DLinOp (Ddwf);
// PowerMethod<LatticeFermion> PM; PM(PVdagM, src);
int Nm = 10;
int Nk = 6;
// int Nm = 6; // Nm = 6 case is acting really strangely... with Nm = 6 and Nm = 3 it zeros out the Hessenberg and also makes it imaginary?
// int Nk = 2;
// int Nk = Nm+1; // if just running once
// int maxIter = 5;
// int maxIter = 1;
int maxIter = 3;
// int maxIter = 100;
int Nstop = 6;
Coordinate origin ({0,0,0,0});
auto tmpSrc = peekSite(src, origin);
std::cout << "[DEBUG] Source at origin = " << tmpSrc << std::endl;
LatticeFermion src2 = src;
// Run Lanczos and Arnoldi on a Hermitian matrix
// Arnoldi Arn (Dsq, FGrid, 1e-8, false);
// Arn(src, 1, Nm, -1);
Arnoldi Arn (Dsq, FGrid, 1e-8, EvalNormLarge); // for comparison to Lanczos
// Arn(src, maxIter, Nm, Nk, Nstop);
// auto tmpSrcDup = peekSite(src, origin);
// std::cout << "[DEBUG] Source at origin = " << tmpSrcDup << std::endl;
// auto tmpSrc2Dup = peekSite(src2, origin);
// std::cout << "[DEBUG] Source2 at origin = " << tmpSrc2Dup << std::endl;
Arn(src, maxIter, Nm, Nk, Nstop);
std::cout << "Hessenberg mat for symmetric N = " << Nm << std::endl;
std::cout << Arn.getHessenbergMat() << std::endl;
// ImplicitlyRestartedLanczosHermOpTester<LatticeFermionD> SimpleTester (Dsq);
// ImplicitlyRestartedLanczos<LatticeFermionD> Lanc (Dsq, Dsq, SimpleTester, Nm, Nm, Nm, 1e-8, Nm);
int Nconv;
PlainHermOp DsqHermOp (Dsq);
// std::vector<RealD> levals (Nm+1); std::vector<LatticeFermionD> levecs (Nm+1, src);
// ImplicitlyRestartedLanczos<LatticeFermionD> Lanc (DsqHermOp, DsqHermOp, Nm, Nm, Nm + 1, 1e-8, Nm);
std::vector<RealD> levals (Nm+1); std::vector<LatticeFermionD> levecs (Nm, src);
ImplicitlyRestartedLanczos<LatticeFermionD> Lanc (DsqHermOp, DsqHermOp, Nstop, Nk, Nm, 1e-8, maxIter);
std::cout << GridLogMessage << "Calculating with Lanczos" << std::endl;
// auto tmpSrc1 = peekSite(src, origin);
// std::cout << "[DEBUG] Source at origin = " << tmpSrc1 << std::endl;
// auto tmpSrc2 = peekSite(src2, origin);
// std::cout << "[DEBUG] Source2 at origin = " << tmpSrc2 << std::endl;
// std::cout << "[DEBUG] Source norm2: " << norm2(src) << std::endl;
std::cout << "running Lanczos now" << std::endl;
Lanc.calc(levals, levecs, src2, Nconv);
std::cout<<GridLogMessage << "*******************************************" << std::endl;
std::cout<<GridLogMessage << "***************** RESULTS *****************" << std::endl;
std::cout<<GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << "Arnoldi eigenvalues: " << std::endl << Arn.getEvals() << std::endl;
std::cout << GridLogMessage << "Lanczos eigenvalues: " << std::endl << levals << std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage << "Done "<< std::endl;
Grid_finalize();
return 0;
}
+188
View File
@@ -0,0 +1,188 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./examples/Example_gamma5_block_lanczos.cc
Copyright (C) 2026
Author: Chulwoo Jung <chulwoo@bnl.gov>
γ5-Block Lanczos example for the Wilson Dirac operator.
Reads a gauge configuration from "config" (NERSC format) and Lanczos
parameters from "LanParams.xml". Runs Gamma5BlockLanczos to
compute eigenvalues of D_W directly (not H_W = γ5 D_W).
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <cstdlib>
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
using namespace std;
using namespace Grid;
namespace Grid {
struct LanczosParameters : Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(LanczosParameters,
RealD, mass,
Integer, Nstop,
Integer, Nk,
Integer, Np,
Integer, maxIter,
Integer, reorthog,
Integer, verify,
Integer, ReadEvec,
RealD, resid)
LanczosParameters()
: mass(-0.5), Nstop(10), Nk(20), maxIter(100),
reorthog(1), verify(0), ReadEvec(0), resid(1e-8)
{}
template<class ReaderClass>
LanczosParameters(Reader<ReaderClass>& r) { initialize(r); }
template<class ReaderClass>
void initialize(Reader<ReaderClass>& r) {
read(r, "LanczosParameters", *this);
}
};
} // namespace Grid
template<class T>
void writeField(T& in, std::string const& fname) {
#ifdef HAVE_LIME
std::cout << GridLogMessage << "Writing to: " << fname << std::endl;
Grid::emptyUserRecord record;
Grid::ScidacWriter WR(in.Grid()->IsBoss());
WR.open(fname);
WR.writeScidacFieldRecord(in, record, 0);
WR.close();
#endif
}
typedef WilsonFermionD WilsonOp;
typedef typename WilsonFermionD::FermionField FermionField;
int main(int argc, char** argv)
{
Grid_init(&argc, &argv);
GridCartesian* UGrid = SpaceTimeGrid::makeFourDimGrid(
GridDefaultLatt(),
GridDefaultSimd(Nd, vComplex::Nsimd()),
GridDefaultMpi());
GridRedBlackCartesian* UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid);
std::vector<int> seeds4({1, 2, 3, 4});
GridParallelRNG RNG4(UGrid);
RNG4.SeedFixedIntegers(seeds4);
// Read gauge configuration
LatticeGaugeField Umu(UGrid);
FieldMetaData header;
std::string configFile("config");
NerscIO::readConfiguration(Umu, header, configFile);
std::cout << GridLogMessage << "Loaded gauge configuration: " << configFile << std::endl;
// Read Lanczos parameters
LanczosParameters Params;
{
XmlReader rd("LanParams.xml");
read(rd, "LanczosParameters", Params);
}
std::cout << GridLogMessage << Params << std::endl;
// Build Wilson Dirac operator and wrap in a non-Hermitian linear operator
std::vector<Complex> boundary = {1, 1, 1, -1};
WilsonOp::ImplParams WilsonParams(boundary);
WilsonOp Dwilson(Umu, *UGrid, *UrbGrid, Params.mass, WilsonParams);
NonHermitianLinearOperator<WilsonOp, FermionField> DLinOp(Dwilson);
// γ5 functor: for 4D Wilson fermions γ5 is Gamma(Gamma5)
Gamma G5(Gamma::Algebra::Gamma5);
auto gamma5 = [&G5](const FermionField& in, FermionField& out) {
out = G5 * in;
};
// Starting vectors: two independent random vectors
FermionField src(UGrid), src2(UGrid);
random(RNG4, src);
random(RNG4, src2);
std::cout << GridLogMessage << "Using two random starting vectors" << std::endl;
std::cout << GridLogMessage << std::endl;
std::cout << GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << " Running γ5-Block Lanczos" << std::endl;
std::cout << GridLogMessage << " mass = " << Params.mass << std::endl;
std::cout << GridLogMessage << " Nk = " << Params.Nk << std::endl;
std::cout << GridLogMessage << " maxIter = " << Params.maxIter << std::endl;
std::cout << GridLogMessage << " Nstop = " << Params.Nstop << std::endl;
std::cout << GridLogMessage << " resid = " << Params.resid << std::endl;
std::cout << GridLogMessage << " reorthog = " << Params.reorthog << std::endl;
std::cout << GridLogMessage << " verify = " << Params.verify << std::endl;
std::cout << GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << std::endl;
Gamma5BlockLanczos<FermionField> G5BL(DLinOp, UGrid, gamma5, Params.resid);
G5BL.doEvalCheck = (Params.verify != 0);
G5BL.doVerify = (Params.verify != 0);
// G5BL(src, src2, Params.maxIter, Params.Nstop, Params.reorthog != 0);
G5BL.restart(src, src2, Params.maxIter, Params.Nk+Params.Np, Params.Nk, Params.Nstop, Params.reorthog != 0, EvalNormSmall);
// G5BL.implicitRestart(src, src2, Params.maxIter, Params.Nk+Params.Np, Params.Nk, Params.Nstop, Params.reorthog != 0, EvalNormSmall);
if (Params.verify ) G5BL.verify("after restart");
// Summary of results
Eigen::VectorXcd evals = G5BL.getEvals();
std::vector<RealD> residuals = G5BL.getResiduals();
std::vector<FermionField> evecs = G5BL.getEvecs();
int Nout = (int)evals.size();
std::cout << GridLogMessage << std::endl;
std::cout << GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << " γ5-Block Lanczos: " << Nout << " Ritz pairs" << std::endl;
std::cout << GridLogMessage << "*******************************************" << std::endl;
for (int i = 0; i < Nout; i++) {
std::cout << GridLogMessage
<< " [" << std::setw(3) << i << "]"
<< " lambda = " << evals(i)
<< " |res| = " << residuals[i] << std::endl;
}
// Write the first Nstop eigenvectors (in SCIDAC format when LIME is available)
int Nwrite = std::min((int)Params.Nstop, Nout);
for (int i = 0; i < Nwrite; i++) {
std::string fname = "./g5bl_evec_m" + std::to_string(Params.mass)
+ "_" + std::to_string(i);
writeField(evecs[i], fname);
}
std::cout << GridLogMessage << std::endl;
std::cout << GridLogMessage << "Done" << std::endl;
Grid_finalize();
return 0;
}
+405
View File
@@ -0,0 +1,405 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./tests/Test_padded_cell.cc
Copyright (C) 2023
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
// Tests code written to read off the Krylov coefficients
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidual.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
#include <Grid/algorithms/iterative/BiCGSTAB.h>
#include <Grid/algorithms/iterative/ConjugateGradient.h>
using namespace std;
using namespace Grid;
// Hermitize a DWF operator by squaring it
template<class Matrix,class Field>
class SquaredLinearOperator : public LinearOperatorBase<Field> {
public:
Matrix &_Mat;
public:
SquaredLinearOperator(Matrix &Mat): _Mat(Mat) {};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
// std::cout << "Op is overloaded as HermOp" << std::endl;
HermOp(in, out);
}
void AdjOp (const Field &in, Field &out){
HermOp(in, out);
}
void _Op (const Field &in, Field &out){
// std::cout << "Op: M "<<std::endl;
_Mat.M(in, out);
}
void _AdjOp (const Field &in, Field &out){
// std::cout << "AdjOp: Mdag "<<std::endl;
_Mat.Mdag(in, out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
// std::cout << "HermOp: Mdag M Mdag M"<<std::endl;
Field tmp(in.Grid());
_Op(in,tmp);
_AdjOp(tmp,out);
}
};
/**
* Computes the coefficients in the Krylov expansion for 1/D ~ \sum_{i=0}^N c_i D^i.
*
* Parameters
* ----------
* std::vector<double> &coeffs
* Polynomial coeffients to return, with indexing order (c_0, c_1, c_2, ..., c_n).
* LinearOperatorBase<FineField> &DiracOp
* Dirac operator D.
* FineField src
* Source field b.
* FineField psiStar
* Output approximation for D^{-1} b coming from a Krylov method.
* int N
* Dimension of the polynomial approximation (Krylov space K_{N-1} = {b, Db, D^2 b, ..., D^{N-1} b}).
*/
void poly_coeffs(std::vector<ComplexD> &coeffs, LinearOperatorBase<LatticeFermion> &DiracOp, LatticeFermion src,
LatticeFermion psiStar, GridCartesian* FGrid, int N, bool use_herm = false)
{
// stdBasis = {b, Db, D^2 b, ..., D^N b}, kryBasis = {k0, k1, ..., kN}
std::vector<LatticeFermion> kryBasis;
Eigen::VectorXcd psiStarCoeffs (N);
// Normalize by 1 / ||src||; does not change the polynomial coefficients
double srcNorm = 1 / std::sqrt(norm2(src));
kryBasis.push_back(srcNorm * src); // normalized source
psiStar = srcNorm * psiStar;
psiStarCoeffs(0) = innerProduct(kryBasis[0], psiStar);
// orthonormalize canonical Krylov basis {b, Db, D^2 b, ..., D^{N-1} b} <--> {k_i} and compute components <k_i | psi*>
LatticeFermion tmp (FGrid);
for (int i = 0; i < N - 1; i++) { // construct ONB for {b, Db, ..., D^{i+1} b}
if (use_herm) {
DiracOp.HermOp(kryBasis.back(), tmp); // tmp \in span{(D^\dag D)^{i+1} b} \oplus span{(D^\dag D)^i b, ..., D^\dag D b, b}
} else {
DiracOp.Op(kryBasis.back(), tmp); // tmp \in span{D^{i+1} b} \oplus span{D^i b, ..., Db, b}
}
for (int j = 0; j < i+1; j++) { // orthogonalize tmp with previous basis vectors
ComplexD coeff = innerProduct(kryBasis[j], tmp); // <k_j | tmp>
tmp -= coeff * kryBasis[j]; // subtract off |k_j><k_j | tmp>; now tmp is perp to |k_j>
}
double tmpNorm = 1 / std::sqrt(norm2(tmp));
kryBasis.push_back(
tmpNorm * tmp
); // normalize |k_i> and add to kryBasis
psiStarCoeffs(i+1) = innerProduct(kryBasis[i+1], psiStar); // compute < k_i | psi* >
}
// To verify the basis is ONB
// for (int i = 0; i < N; i++) {
// for (int j = 0; j < N; j++) {
// std::cout << "<ki|kj> for (i,j) = (" << i << ", " << j << ") = " << innerProduct(kryBasis[i], kryBasis[j]) << std::endl;
// }
// }
// Compute change of basis matrix
LatticeFermion tmp2 (FGrid);
Eigen::MatrixXcd M = Eigen::MatrixXcd::Zero(N, N);
tmp = kryBasis[0]; // current Krylov vector; starts with tmp = src (normalized)
for (int i = 0; i < N; i++) {
for (int j = 0; j < i + 1; j++) { // fill column with components of kryVec. Only need j <= i to get orthonormal components
M(j, i) = innerProduct(kryBasis[j], tmp);
}
if (use_herm) { // tmp --> D^\dag D(tmp)
DiracOp.HermOp(tmp, tmp2);
tmp = tmp2;
} else { // tmp --> D(tmp). Note that DiracOp.Op(tmp, tmp) will cause a bug
DiracOp.Op(tmp, tmp2);
tmp = tmp2;
}
}
// Compute M^{-1} @ psiStarCoeffs and copy to coeffs
Eigen::VectorXcd res (N);
res = M.inverse() * psiStarCoeffs;
for (int i = 0; i < N; i++) {
coeffs[i] = res(i);
}
}
// out file for poly coefficients (should it be complex?)
// class PolynomialFile: Serializable {
// public:
// GRID_SERIALIZABLE_CLASS_MEMBERS(OutputFile, std::vector< Real >, data);
// };
std::complex<double> poly_approx(std::complex<double> x, std::vector<std::complex<double>> coeffs) {
std::complex<double> px;
for (int i = 0; i < coeffs.size(); i++) {
px += coeffs[i] * std::pow(x, i);
}
return px;
}
/**
* Returns the approximation psi = \sum_i c_i D^i b resulting from a Krylov solver.
*
* Parameters
* ----------
* LatticeFermion &psi
* Approximation field, returned psi = \sum_i c_i D^i b.
* LatticeFermion src
* Source b used to generate the Krylov space K_n(D, b).
* LinearOperatorBase<LatticeFermion> &Linop
* Dirac operator used to generate the Krylov space K_n(D, b).
* std::vector<std::complex<double>> coeffs
* Polynomial coefficients returned from the solver.
*/
void krylovApprox(LatticeFermion &psi, LatticeFermion src, LinearOperatorBase<LatticeFermion> &Linop, std::vector<ComplexD> coeffs) {
psi = Zero();
LatticeFermion tmp (psi.Grid());
tmp = src;
LatticeFermion tmp2 (psi.Grid());
for (int i = 0; i < coeffs.size(); i++) {
psi = psi + coeffs[i] * tmp;
Linop.Op(tmp, tmp2); // tmp = D*tmp
tmp = tmp2;
}
}
int main (int argc, char ** argv)
{
Grid_init(&argc, &argv);
const int Ls = 8;
std::vector<int> lat_size {16, 16, 16, 32};
std::cout << "Lattice size: " << lat_size << std::endl;
GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(lat_size,
GridDefaultSimd(Nd,vComplex::Nsimd()),
GridDefaultMpi());
GridRedBlackCartesian * UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid);
GridCartesian * FGrid = SpaceTimeGrid::makeFiveDimGrid(Ls,UGrid);
GridRedBlackCartesian * FrbGrid = SpaceTimeGrid::makeFiveDimRedBlackGrid(Ls,UGrid);
//////////////////////////////////////////////////////////////////////
// You can manage seeds however you like.
// Recommend SeedUniqueString.
//////////////////////////////////////////////////////////////////////
// std::vector<int> seeds4({1, 2, 3, 4});
// GridParallelRNG RNG4(UGrid);
// RNG4.SeedFixedIntegers(seeds4);
// std::vector<int> seeds5({1, 2, 3, 4, 5});
// GridParallelRNG RNG5(FGrid);
// RNG5.SeedFixedIntegers(seeds5);
// std::string outStrStem = "/Users/patrickoare/Dropbox (MIT)/research/multigrid/grid_out/";
LatticeGaugeField Umu(UGrid);
FieldMetaData header;
std::string file("/Users/patrickoare/libraries/PETSc-Grid/ckpoint_lat.4000");
NerscIO::readConfiguration(Umu, header, file);
RealD mass=0.01;
RealD M5=1.8;
// RealD M5=1.0;
RealD b=1.5;// Scale factor b+c=2, b-c=1
RealD c=0.5;
// load in Dirac operators that we'll use; square it to Hermitize
// Dsq just needs to be a Hermitian operator so we can use CG on it
DomainWallFermionD Ddwf(Umu, *FGrid, *FrbGrid, *UGrid, *UrbGrid, mass, M5);
SquaredLinearOperator<DomainWallFermionD, LatticeFermionD> Dsq (Ddwf);
NonHermitianLinearOperator<DomainWallFermionD, LatticeFermionD> DLinOp (Ddwf);
LatticeFermion src (FGrid); src = 1.0; // Source to use
LatticeFermion psiCG (FGrid); psiCG = Zero(); // Field to solve with for CG
LatticeFermion psiGCR (FGrid); psiGCR = Zero(); // Field to solve with for GCR
std::cout << GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << "********** TESTING CG POLY COEFFS *********" << std::endl;
std::cout << GridLogMessage << "*******************************************" << std::endl << std::endl;
double tol = 1.0e-8;
int N = 5; // max iterations (size of Krylov basis)
// GCR variables
int outer_iters = 1; // num restarts for GCR
TrivialPrecon<LatticeFermionD> prec; // trivial preconditioner
ConjugateGradientPolynomial<LatticeFermion> CGP(tol, N, false);
CGP(Dsq, src, psiCG);
// Compute Krylov coeffs directly and compare
std::vector<ComplexD> cg_coeffs (N);
poly_coeffs(cg_coeffs, Dsq, src, psiCG, FGrid, N, true);
PolynomialFile PF;
// Use GCR solver, also get poly coeffs
std::vector<ComplexD> gcr_sym_coeffs (N); // Can try N --> N + 3 to test to see if the last 3 comps are 0
PGCRPolynomial<LatticeFermionD> GCRPolySym(tol, outer_iters, Dsq, prec, N+1, N, PF); // mmax sets the memory, note the last beta doesn't really matter for updating the polynomial
GCRPolySym(src, psiGCR);
// poly_coeffs(gcr_sym_coeffs, Dsq, src, psi, FGrid, N, true);
poly_coeffs(gcr_sym_coeffs, Dsq, src, psiGCR, FGrid, N, true);
std::cout << GridLogMessage << std::endl << "******** CG POLYNOMIAL COEFFICIENTS *******" << std::endl;
std::cout << GridLogMessage << CGP.polynomial << std::endl << std::endl;
std::cout << GridLogMessage << "****** DIRECT POLYNOMIAL COEFFICIENTS *****" << std::endl;
std::cout << GridLogMessage << cg_coeffs << std::endl << std::endl;
// TODO: try GCR with a Hermitian operator (Dsq)
std::cout << GridLogMessage << "****** GCR COEFFICIENTS *****" << std::endl;
std::cout << GridLogMessage << GCRPolySym.polynomial << std::endl << std::endl;
std::cout << GridLogMessage << "****** DIRECT GCR COEFFICIENTS *****" << std::endl;
std::cout << GridLogMessage << gcr_sym_coeffs << std::endl << std::endl;
// test how good the decomposition is
std::cout << "Testing fidelity of decomposition by computing ||psi* - sum_i c_i D^i b||^2!" << std::endl;
LatticeFermion psiPrime (FGrid);
// for CG
krylovApprox(psiPrime, src, Dsq, cg_coeffs);
std::cout << "CG with Dsq, ||psi - psiPrime||^2 = " << norm2(psiCG - psiPrime) << std::endl;
// for GCR with alpha / beta computation
krylovApprox(psiPrime, src, Dsq, GCRPolySym.polynomial);
std::cout << "GCR with Dsq, ||psi - psiPrime||^2 = " << norm2(psiGCR - psiPrime) << std::endl;
// for GCR with alpha / beta computation
krylovApprox(psiPrime, src, Dsq, gcr_sym_coeffs);
std::cout << "GCR direct with Dsq, ||psi - psiPrime||^2 = " << norm2(psiGCR - psiPrime) << std::endl;
// std::vector<double> real_cg_diff (N);
// for (int i = 0; i < N; i++) { real_cg_diff[i] = std::abs(cg_coeffs[i].real() - CGP.polynomial[i]); }
// std::cout << GridLogMessage << "************* COEFF DIFFERENCE ************" << std::endl;
// std::cout << GridLogMessage << real_cg_diff << std::endl << std::endl;
// GCR polynomial reconstruction with Ddwf!
std::cout << GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << "********* TESTING GCR POLY COEFFS *********" << std::endl;
std::cout << GridLogMessage << "*******************************************" << std::endl << std::endl;
// re-init variables
src = 1.0;
src = (1 / std::sqrt(norm2(src))) * src;
psiGCR = Zero(); psiPrime = Zero();
// test GCR poly
PGCRPolynomial<LatticeFermionD> GCRPoly(tol, outer_iters, DLinOp, prec, N+1, N, PF); // mmax sets the memory, note the last beta doesn't really matter for updating the polynomial
GCRPoly(src, psiGCR);
// Compute Krylov coeffs directly and compare
// N = 1; // compare the N > 1 decomposition with the psi* resulting from N = 1
std::vector<ComplexD> gcr_coeffs (N); // note N --> N + k should just give k coeffs that are 0; this works as intended
poly_coeffs(gcr_coeffs, DLinOp, src, psiGCR, FGrid, N, false);
std::cout << GridLogMessage << "******* GCR POLYNOMIAL COEFFICIENTS *******" << std::endl;
std::cout << GridLogMessage << GCRPoly.polynomial << std::endl << std::endl;
std::cout << GridLogMessage << "****** DIRECT POLYNOMIAL COEFFICIENTS *****" << std::endl;
std::cout << GridLogMessage << gcr_coeffs << std::endl << std::endl;
// test how good the decomposition is
std::cout << "Testing fidelity of decomposition by computing ||psi* - sum_i c_i D^i b||^2!" << std::endl;
// for GCR with alpha / beta computation
krylovApprox(psiPrime, src, DLinOp, GCRPoly.polynomial);
std::cout << "GCR with Dsq, ||psi - psiPrime||^2 = " << norm2(psiGCR - psiPrime) << std::endl;
// for GCR with alpha / beta computation
krylovApprox(psiPrime, src, DLinOp, gcr_coeffs);
std::cout << "GCR direct with Dsq, ||psi - psiPrime||^2 = " << norm2(psiGCR - psiPrime) << std::endl;
// TESTS TO DO THE N = 2 CASE DIRECTLY
/*
std::vector<std::complex<double>> alphas {
std::complex(0.244300601, 0.00013007545),
std::complex(0.285370971, -0.000160704481)
};
std::complex<double> beta00 (-0.184661284, -6.52153945e-05);
LatticeFermion psi2 (FGrid);
LatticeFermion Dsrc (FGrid);
DLinOp.Op(src, Dsrc);
std::complex<double> c1 = alphas[0] + alphas[1] * (1. + beta00);
std::complex<double> c2 = -alphas[0] * alphas[1];
psi2 = c1 * src + c2 * Dsrc;
std::cout << "||b|| = " << norm2(src) << std::endl;
std::cout << "||Db|| = " << norm2(Dsrc) << std::endl;
// fail; so far this is giving something different than what's being computed in krylovApprox (idk how?)
std::cout << "c1 and c2 are: " << c1 << " and " << c2 << std::endl;
std::cout << "GCRPoly polynomial coeffs are (should equal c1 and c2): " << GCRPoly.polynomial << std::endl;
std::cout << "||GCRpsi - psi2||_2^2 = " << norm2(psiGCR - psi2) << std::endl;
// pass
LatticeFermion src2 (FGrid);
src2 = 1.0;
src2 = (1 / std::sqrt(norm2(src2))) * src2;
std::cout << "||ones - src|| (to verify that src is the same throughout, should be 0) = " << norm2(src2 - src) << std::endl;
// pass
krylovApprox(psiPrime, src, DLinOp, GCRPoly.polynomial);
std::cout << "GCR with Dsq, ||psi2 - psiPrime||^2 = " << norm2(psi2 - psiPrime) << std::endl;
std::vector<ComplexD> psi2_coeffs (N); // note N --> N + k should just give k coeffs that are 0; this works as intended
poly_coeffs(psi2_coeffs, DLinOp, src, psi2, FGrid, N, false);
krylovApprox(psiPrime, src, DLinOp, psi2_coeffs);
std::cout << "GCR direct with Dsq, ||psi - psiPrime||^2 = " << norm2(psi2 - psiPrime) << std::endl;
*/
// std::complex z (10.0, 0.0); // z = 10
// std::cout << GridLogMessage << "************* GCR POLY(z = 10) *************" << std::endl;
// std::cout << GridLogMessage << poly_approx(z, GCRPoly.polynomial) << std::endl;
// std::cout << GridLogMessage << "************ DIRECT POLY(z = 10) ***********" << std::endl;
// std::cout << GridLogMessage << poly_approx(z, gcr_coeffs) << std::endl;
// std::vector<std::complex<double>> gcr_diff (N);
// for (int i = 0; i < N; i++) { gcr_diff[i] = gcr_coeffs[i] - GCRPoly.polynomial[i]; }
// std::cout << GridLogMessage << "*********** GCR COEFF DIFFERENCE **********" << std::endl;
// std::cout << GridLogMessage << gcr_diff << std::endl << std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage << "Done "<< std::endl;
Grid_finalize();
return 0;
}
+425
View File
@@ -0,0 +1,425 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./examples/Example_krylov_schur.cc
Copyright (C) 2023
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: Patrick Oare <patrickoare@gmail.com>
Author: Chulwoo Jung <chulwoo@bnl.gov>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
// copied here from Test_general_coarse_pvdagm.cc
#include <cstdlib>
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidual.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
#include <Grid/algorithms/iterative/BiCGSTAB.h>
using namespace std;
using namespace Grid;
namespace Grid {
struct LanczosParameters: Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(LanczosParameters,
RealD, mass ,
RealD, mstep ,
Integer, Nstop,
Integer, Nk,
Integer, Np,
Integer, ReadEvec,
Integer, maxIter,
Integer, Nblock,
Integer, verify,
RealD, shift ,
RealD, resid,
RealD, ChebyLow,
RealD, ChebyHigh,
Integer, ChebyOrder)
LanczosParameters() {
////////////////////////////// Default values
mass = 0;
/////////////////////////////////
}
template <class ReaderClass >
LanczosParameters(Reader<ReaderClass> & TheReader){
initialize(TheReader);
}
template < class ReaderClass >
void initialize(Reader<ReaderClass> &TheReader){
// std::cout << GridLogMessage << "Reading HMC\n";
read(TheReader, "HMC", *this);
}
void print_parameters() const {
// std::cout << GridLogMessage << "[HMC parameters] Trajectories : " << Trajectories << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Start trajectory : " << StartTrajectory << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Metropolis test (on/off): " << std::boolalpha << MetropolisTest << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Thermalization trajs : " << NoMetropolisUntil << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Starting type : " << StartingType << "\n";
// MD.print_parameters();
}
};
}
template <class T> void writeFile(T& in, std::string const fname){
#if 1
// Ref: https://github.com/paboyle/Grid/blob/feature/scidac-wp1/tests/debug/Test_general_coarse_hdcg_phys48.cc#L111
std::cout << Grid::GridLogMessage << "Writes to: " << fname << std::endl;
Grid::emptyUserRecord record;
Grid::ScidacWriter WR(in.Grid()->IsBoss());
WR.open(fname);
WR.writeScidacFieldRecord(in,record,0);
WR.close();
#endif
// What is the appropriate way to throw error?
}
typedef WilsonFermionD WilsonOp;
typedef typename WilsonFermionD::FermionField FermionField;
template<class Matrix,class Field>
class InvertNonHermitianLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
RealD _stp;
public:
InvertNonHermitianLinearOperator(Matrix &Mat,RealD stp=1e-8): _Mat(Mat),_stp(stp){};
// Support for coarsening to a multigrid
void OpDiag (const Field &in, Field &out) {
assert(0);
}
void OpDir (const Field &in, Field &out,int dir,int disp) {
assert(0);
}
void OpDirAll (const Field &in, std::vector<Field> &out){
// _Mat.MdirAll(in,out);
assert(0);
};
void Op (const Field &in, Field &out){
Field tmp(in.Grid());
_Mat.Mdag(in,tmp);
MdagMLinearOperator<Matrix,Field> HermOp(_Mat);
ConjugateGradient<Field> CG(_stp,10000);
CG(HermOp,tmp,out);
// out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
_Mat.Mdag(in,out);
// out = out + shift * in;
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){
assert(0);
}
void HermOp(const Field &in, Field &out){
assert(0);
}
};
template<class Field>
void testSchurFromHess(Arnoldi<Field>& Arn, Field& src, int Nlarge, int Nm, int Nk) {
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout << GridLogMessage << "Testing Schur reordering, Nm = " << Nm << ", Nk = " << Nk << std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout << GridLogMessage << "Running Arnoldi for 1 iteration to get a Hessenberg." << std::endl;
Arn(src, 1, Nlarge, Nm, Nlarge);
Eigen::MatrixXcd Hess = Arn.getHessenbergMat();
std::cout << GridLogMessage << "Hessenberg for use: " << std::endl << Hess << std::endl;
ComplexSchurDecomposition schur (Hess, true);
bool isDecomposed = schur.checkDecomposition();
std::cout << "Schur decomp holds? " << isDecomposed << std::endl;
std::cout << GridLogMessage << "S = " << std::endl << schur.getMatrixS() << std::endl;
std::cout << GridLogMessage << "Swapping S(3, 3) with S(4, 4)" << std::endl;
schur.swapEvals(3);
std::cout << GridLogMessage << "S after swap = " << std::endl << schur.getMatrixS() << std::endl;
std::cout << "Schur decomp still holds? " << schur.checkDecomposition() << std::endl;
// Now move last diagonal element all the way to the front.
std::cout << GridLogMessage << "Moving last eval to front. S at start = " << std::endl << schur.getMatrixS() << std::endl;
for (int i = 0; i < Nk - 1; i++) {
int swapIdx = Nk - 2 - i;
schur.swapEvals(swapIdx);
std::cout << GridLogMessage << "S after swap of index " << swapIdx << " = " << std::endl << schur.getMatrixS() << std::endl;
std::cout << "Schur decomp still holds? " << schur.checkDecomposition() << std::endl;
}
std::cout << GridLogMessage << "Testing Schur reorder" << std::endl;
schur.schurReorder(Nk);
std::cout << GridLogMessage << "S after reorder = " << std::endl << schur.getMatrixS() << std::endl;
std::cout << "Schur decomp still holds? " << schur.checkDecomposition() << std::endl;
}
int main (int argc, char ** argv)
{
Grid_init(&argc,&argv);
const int Ls=16;
// GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(), GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());
// std::vector<int> lat_size {32, 32, 32, 32};
// std::cout << "Lattice size: " << lat_size << std::endl;
GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(),
GridDefaultSimd(Nd,vComplex::Nsimd()),
GridDefaultMpi());
GridRedBlackCartesian * UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid);
// GridCartesian * FGrid = SpaceTimeGrid::makeFiveDimGrid(Ls,UGrid);
// GridRedBlackCartesian * FrbGrid = SpaceTimeGrid::makeFiveDimRedBlackGrid(Ls,UGrid);
GridCartesian * FGrid = UGrid;
GridRedBlackCartesian * FrbGrid = UrbGrid;
// Construct a coarsened grid
// poare TODO: replace this with the following line?
Coordinate clatt = GridDefaultLatt();
// Coordinate clatt = GridDefaultLatt(); // [PO] initial line before I edited it
for(int d=0;d<clatt.size();d++){
std::cout << GridLogMessage<< clatt[d] <<std::endl;
clatt[d] = clatt[d]/2;
// clatt[d] = clatt[d]/4;
}
GridCartesian *Coarse4d = SpaceTimeGrid::makeFourDimGrid(clatt, GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());;
GridCartesian *Coarse5d = SpaceTimeGrid::makeFiveDimGrid(1,Coarse4d);
std::vector<int> seeds4({1,2,3,4});
std::vector<int> seeds5({5,6,7,8});
std::vector<int> cseeds({5,6,7,8});
GridParallelRNG RNG5(FGrid); RNG5.SeedFixedIntegers(seeds5);
GridParallelRNG RNG4(UGrid); RNG4.SeedFixedIntegers(seeds4);
GridParallelRNG CRNG(Coarse5d);CRNG.SeedFixedIntegers(cseeds);
LatticeFermion result(FGrid); result=Zero();
LatticeFermion ref(FGrid); ref=Zero();
LatticeFermion tmp(FGrid);
LatticeFermion err(FGrid);
LatticeGaugeField Umu(UGrid);
FieldMetaData header;
std::string file("config");
// std::string file("Users/patrickoare/libraries/PETSc-Grid/ckpoint_lat.4000");
NerscIO::readConfiguration(Umu,header,file);
LanczosParameters LanParams;
{
XmlReader HMCrd("LanParams.xml");
read(HMCrd,"LanczosParameters",LanParams);
}
std::cout << GridLogMessage<< LanParams <<std::endl;
{
XmlWriter HMCwr("LanParams.xml.out");
write(HMCwr,"LanczosParameters",LanParams);
}
RealD mass=0.01;
RealD M5=1.8;
// PowerMethod<LatticeFermion> PM; PM(PVdagM, src);
int Nm = 50;
int Nk = 12;
int Np = 38;
// int Nk = Nm+1; // if just running once
int maxIter = 10000;
int Nstop = 10;
RealD resid = 1.0e-5;
std::vector<Complex> boundary = {1,1,1,-1};
WilsonOp::ImplParams Params(boundary);
// DomainWallFermionD Ddwf(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,mass,M5);
// DomainWallFermionD Dpv(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,1.0,M5);
mass=LanParams.mass;
std::cout << GridLogIRL<< "mass "<<mass<<std::endl;
WilsonOp WilsonOperator(Umu,*UGrid,*UrbGrid,mass,Params);
// const int nbasis = 20; // size of approximate basis for low-mode space
const int nbasis = 3; // size of approximate basis for low-mode space
const int cb = 0 ;
LatticeFermion prom(FGrid);
typedef GeneralCoarsenedMatrix<vSpinColourVector,vTComplex,nbasis> LittleDiracOperator;
typedef LittleDiracOperator::CoarseVector CoarseVector;
NextToNearestStencilGeometry5D geom(Coarse5d);
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
// typedef PVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> PVdagM_t;
// typedef ShiftedPVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> ShiftedPVdagM_t;
// typedef ShiftedComplexPVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> ShiftedComplexPVdagM_t;
// PVdagM_t PVdagM(Ddwf, Dpv);
// ShiftedPVdagM_t ShiftedPVdagM(0.1,Ddwf,Dpv);
// SquaredLinearOperator<DomainWallFermionD, LatticeFermionD> Dsq (Ddwf);
// NonHermitianLinearOperator<DomainWallFermionD, LatticeFermionD> DLinOp (Ddwf);
NonHermitianLinearOperator<WilsonOp,FermionField> Dwilson(WilsonOperator); /// <-----
InvertNonHermitianLinearOperator<WilsonOp,FermionField> Iwilson(WilsonOperator); /// <-----
MdagMLinearOperator<WilsonOp,FermionField> HermOp(WilsonOperator); /// <-----
Gamma5HermitianLinearOperator <WilsonOp,LatticeFermion> HermOp2(WilsonOperator); /// <----
// PowerMethod<LatticeFermion> PM; PM(PVdagM, src);
resid=LanParams.resid;
Nstop=LanParams.Nstop;
Nk=LanParams.Nk;
Np=LanParams.Np;
maxIter=LanParams.maxIter;
Nm = Nk + Np;
int Nu=16;
std::vector<LatticeFermion> src(Nu,FGrid);
for(int i=0;i<Nu;i++){
random(RNG5,src[i]);
#if 0
LatticeFermion src_e(FrbGrid);
pickCheckerboard(Even, src_e, src[i]);
src_e=Zero();
setCheckerboard(src[i],src_e);
std::cout << GridLogMessage << "src["<<i<<"] even sites zeroed " << std::endl;
#endif
}
if(LanParams.ReadEvec) {
std::string evecs_file="evec_in";
std::cout << GridLogIRL<< "Reading evecs from "<<evecs_file<<std::endl;
Grid::emptyUserRecord record;
Grid::ScidacReader RD;
RD.open(evecs_file);
RD.readScidacFieldRecord(src[0],record);
RD.close();
}
Coordinate origin ({0,0,0,0});
auto tmpSrc = peekSite(src[0], origin);
std::cout << "[DEBUG] Source at origin = " << tmpSrc << std::endl;
LatticeFermion src2 = src[0];
// Run KrylovSchur / BlockKrylovSchur / TrueHarmonicBlockKrylovSchur on a Hermitian matrix
RealD shift=LanParams.shift;
// --use-blockKS : off -> plain (non-block) KrylovSchur
// on -> block family (BlockKrylovSchur / TrueHarmonicBlockKrylovSchur)
// --use-harmonic: off -> no shift/harmonic Ritz extraction
// on, without --use-blockKS -> plain KrylovSchur with shifted (harmonic) Schur extraction
// on, with --use-blockKS -> TrueHarmonicBlockKrylovSchur (true harmonic Ritz)
bool useBlockKS = GridCmdOptionExists(argv, argv+argc, std::string("--use-blockKS"));
bool useHarmonic = GridCmdOptionExists(argv, argv+argc, std::string("--use-harmonic"));
std::vector<LatticeFermion> finalEvecs;
if (!useBlockKS) {
KrylovSchur KrySchur (Dwilson, UGrid, resid,EvalImNormSmall);
KrySchur.doEvalCheck=true;
if (useHarmonic) {
std::cout << GridLogMessage << "Running KrylovSchur (shifted/harmonic Ritz)" << std::endl;
KrySchur(src[0], maxIter, Nm, Nk, Nstop,&shift);
} else {
std::cout << GridLogMessage << "Running KrylovSchur" << std::endl;
KrySchur(src[0], maxIter, Nm, Nk, Nstop);
}
std::cout << GridLogMessage << "KrylovSchur evec.size= " << KrySchur.evecs.size()<< std::endl;
finalEvecs = KrySchur.evecs;
} else {
int Nblock=4;
Nblock=LanParams.Nblock;
bool if_verify=false;
if(LanParams.verify) if_verify=true;
auto gamma5Lambda = [](const FermionField& v, FermionField& out) {
Gamma g5(Gamma::Algebra::Gamma5);
out = g5 * v;
};
if (useHarmonic) {
std::cout << GridLogMessage << "Running TrueHarmonicBlockKrylovSchur" << std::endl;
TrueHarmonicBlockKrylovSchur<FermionField> KrySchur (Dwilson, UGrid, resid,shift,EvalImNormSmall);
KrySchur.doEvalCheck=true;
KrySchur.gamma5Func = gamma5Lambda;
KrySchur(src, maxIter, Nm, Nk, Nstop,Nblock,true,if_verify);
std::cout << GridLogMessage << "TrueHarmonicBlockKrylovSchur evec.size= " << KrySchur.evecs.size()<< std::endl;
finalEvecs = KrySchur.evecs;
} else {
std::cout << GridLogMessage << "Running BlockKrylovSchur" << std::endl;
BlockKrylovSchur<FermionField> KrySchur (Dwilson, UGrid, resid,EvalImNormSmall);
KrySchur.doEvalCheck=true;
KrySchur.gamma5Func = gamma5Lambda;
KrySchur(src, maxIter, Nm, Nk, Nstop,Nblock,true,if_verify);
std::cout << GridLogMessage << "BlockKrylovSchur evec.size= " << KrySchur.evecs.size()<< std::endl;
finalEvecs = KrySchur.evecs;
}
}
src[0]=finalEvecs[0];
std::cout << GridLogMessage << "finalEvecs= "<< finalEvecs.size() <<std::endl;
for (int i=1;i<Nk;i++) src[0]+=finalEvecs[i];
for (int i=0;i<Nstop;i++)
{
std::string evfile ("./evec_"+std::to_string(mass)+"_"+std::to_string(i));
auto evdensity = localInnerProduct(finalEvecs[i],finalEvecs[i] );
writeFile(evdensity,evfile);
}
{
std::string evfile ("./evec_"+std::to_string(mass)+"_sum");
// auto evdensity = localInnerProduct(evec[i],evec[i] );
writeFile(src[0],evfile);
}
/*
std::cout << GridLogMessage << "Running Arnoldi" << std::endl;
// Arnoldi Arn (Dsq, FGrid, 1e-8);
Arnoldi Arn (DLinOp, FGrid, 1e-8);
testSchurFromHess<LatticeFermion>(Arn, src, 10, 6, 4);
Arnoldi Arn2 (DLinOp, FGrid, 1e-8);
testSchurFromHess<LatticeFermion>(Arn2, src, 16, 12, 8);
*/
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage << "Done "<< std::endl;
Grid_finalize();
return 0;
}
+498
View File
@@ -0,0 +1,498 @@
/*************************************************************************************
Runs the Krylov-Schur algorithm on a (pre-conditioned) domain-wall fermion operator
to determine part of its spectrum.
Usage :
$ ./Example_spec_kryschur <Nm> <Nk> <maxiter> <Nstop> <inFile> <outDir> <?rf>
Nm = Maximum size of approximation subspace.
Nk = Size of truncation subspace
maxiter = Maximum number of iterations.
Nstop = Stop when Nstop eigenvalues have converged.
inFile = Gauge configuration to read in.
outDir = Directory to write output to.
rf = (Optional) RitzFilter to sort with. Takes in any string in
{EvalNormSmall, EvalNormLarge, EvalReSmall, EvalReLarge, EvalImSmall, EvalImLarge}
Output:
${outDir}/evals.txt = Contains all eigenvalues. Each line is formatted as `$idx $eval $ritz`, where:
- $idx is the index of the eigenvalue.
- $eval is the eigenvalue, formated as "(re,im)".
- $ritz is the Ritz estimate of the eigenvalue (deviation from being a true eigenvalue)
${outDir}/evec${idx} = Eigenvector $idx written out in SCIDAC format (if LIME is enabled).
Grid physics library, www.github.com/paboyle/Grid
Source file: ./tests/Test_padded_cell.cc
Copyright (C) 2023
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: Patrick Oare <poare@bnl.edu>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include <cstdlib>
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidual.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
#include <Grid/algorithms/iterative/BiCGSTAB.h>
#include <Grid/parallelIO/IldgIOtypes.h>
#include <Grid/parallelIO/IldgIO.h>
using namespace std;
using namespace Grid;
namespace Grid {
struct LanczosParameters: Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(LanczosParameters,
RealD, mass ,
RealD, mstep ,
Integer, Nstop,
Integer, Nk,
Integer, Np,
Integer, ReadEvec,
RealD, resid,
RealD, ChebyLow,
RealD, ChebyHigh,
Integer, ChebyOrder)
LanczosParameters() {
/////////////////////////////////
}
template <class ReaderClass >
LanczosParameters(Reader<ReaderClass> & TheReader){
initialize(TheReader);
}
template < class ReaderClass >
void initialize(Reader<ReaderClass> &TheReader){
read(TheReader, "HMC", *this);
}
void print_parameters() const {
// std::cout << GridLogMessage << "[HMC parameters] Trajectories : " << Trajectories << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Start trajectory : " << StartTrajectory << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Metropolis test (on/off): " << std::boolalpha << MetropolisTest << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Thermalization trajs : " << NoMetropolisUntil << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Starting type : " << StartingType << "\n";
// MD.print_parameters();
}
};
}
template <class T> void writeFile(T& in, std::string const fname){
#ifdef HAVE_LIME
// Ref: https://github.com/paboyle/Grid/blob/feature/scidac-wp1/tests/debug/Test_general_coarse_hdcg_phys48.cc#L111
std::cout << Grid::GridLogMessage << "Writes to: " << fname << std::endl;
Grid::emptyUserRecord record;
Grid::ScidacWriter WR(in.Grid()->IsBoss());
WR.open(fname);
WR.writeScidacFieldRecord(in,record,0); // Lexico
WR.close();
#endif
}
/**
* Writes the eigensystem of a Krylov Schur object to a directory.
*
* Parameters
* ----------
* std::string path
* Directory to write to.
*/
template <class Field>
void writeEigensystem(KrylovSchur<Field> KS, std::string outDir) {
int Nk = KS.getNk();
std::cout << GridLogMessage << "Writing output to directory: " << outDir << std::endl;
// Write evals
std::string evalPath = outDir + "/evals.txt";
std::ofstream fEval;
fEval.open(evalPath);
Eigen::VectorXcd evals = KS.getEvals();
std::vector<RealD> ritz = KS.getRitzEstimates();
for (int i = 0; i < Nk; i++) {
// write eigenvalues and Ritz estimates
fEval << i << " " << evals(i) << " " << ritz[i];
if (i < Nk - 1) { fEval << "\n"; }
}
fEval.close();
// Write evecs (TODO: very heavy on storage costs! Don't write them all out)
// std::vector<Field> evecs = KS.getEvecs();
// for (int i = 0; i < Nk; i++) {
// std::string fName = outDir + "/evec" + std::to_string(i);
// writeFile(evecs[i], fName); // using method from Grid/HMC/ComputeWilsonFlow.cc
// }
}
// Hermitize a DWF operator by squaring it
template<class Matrix,class Field>
class SquaredLinearOperator : public LinearOperatorBase<Field> {
public:
Matrix &_Mat;
public:
SquaredLinearOperator(Matrix &Mat): _Mat(Mat) {};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
// std::cout << "Op is overloaded as HermOp" << std::endl;
HermOp(in, out);
}
void AdjOp (const Field &in, Field &out){
HermOp(in, out);
}
void _Op (const Field &in, Field &out){
// std::cout << "Op: M "<<std::endl;
_Mat.M(in, out);
}
void _AdjOp (const Field &in, Field &out){
// std::cout << "AdjOp: Mdag "<<std::endl;
_Mat.Mdag(in, out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
// std::cout << "HermOp: Mdag M Mdag M"<<std::endl;
Field tmp(in.Grid());
_Op(in,tmp);
_AdjOp(tmp,out);
}
};
template<class Matrix,class Field>
class PVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
public:
PVdagMLinearOperator(Matrix &Mat,Matrix &PV): _Mat(Mat),_PV(PV){};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(in,tmp);
_Mat.Mdag(tmp,out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
// _Mat.M(in,tmp);
// _PV.Mdag(tmp,out);
// _PV.M(out,tmp);
// _Mat.Mdag(tmp,out);
Op(in,tmp);
AdjOp(tmp,out);
// std::cout << "HermOp done "<<norm2(out)<<std::endl;
}
};
template<class Matrix,class Field>
class ShiftedPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
RealD shift;
public:
ShiftedPVdagMLinearOperator(RealD _shift,Matrix &Mat,Matrix &PV): shift(_shift),_Mat(Mat),_PV(PV){};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(tmp,out);
_Mat.Mdag(in,tmp);
out = out + shift * in;
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
};
template<class Matrix, class Field>
class ShiftedComplexPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
ComplexD shift;
public:
ShiftedComplexPVdagMLinearOperator(ComplexD _shift,Matrix &Mat,Matrix &PV): shift(_shift),_Mat(Mat),_PV(PV){};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(tmp,out);
_Mat.Mdag(in,tmp);
out = out + shift * in;
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
void resetShift(ComplexD newShift) {
shift = newShift;
}
};
#if 0
template<class Fobj,class CComplex,int nbasis>
class MGPreconditioner : public LinearFunction< Lattice<Fobj> > {
public:
using LinearFunction<Lattice<Fobj> >::operator();
typedef Aggregation<Fobj,CComplex,nbasis> Aggregates;
typedef typename Aggregation<Fobj,CComplex,nbasis>::FineField FineField;
typedef typename Aggregation<Fobj,CComplex,nbasis>::CoarseVector CoarseVector;
typedef typename Aggregation<Fobj,CComplex,nbasis>::CoarseMatrix CoarseMatrix;
typedef LinearOperatorBase<FineField> FineOperator;
typedef LinearFunction <FineField> FineSmoother;
typedef LinearOperatorBase<CoarseVector> CoarseOperator;
typedef LinearFunction <CoarseVector> CoarseSolver;
Aggregates & _Aggregates;
FineOperator & _FineOperator;
FineSmoother & _PreSmoother;
FineSmoother & _PostSmoother;
CoarseOperator & _CoarseOperator;
CoarseSolver & _CoarseSolve;
int level; void Level(int lv) {level = lv; };
MGPreconditioner(Aggregates &Agg,
FineOperator &Fine,
FineSmoother &PreSmoother,
FineSmoother &PostSmoother,
CoarseOperator &CoarseOperator_,
CoarseSolver &CoarseSolve_)
: _Aggregates(Agg),
_FineOperator(Fine),
_PreSmoother(PreSmoother),
_PostSmoother(PostSmoother),
_CoarseOperator(CoarseOperator_),
_CoarseSolve(CoarseSolve_),
level(1) { }
virtual void operator()(const FineField &in, FineField & out)
{
GridBase *CoarseGrid = _Aggregates.CoarseGrid;
// auto CoarseGrid = _CoarseOperator.Grid();
CoarseVector Csrc(CoarseGrid);
CoarseVector Csol(CoarseGrid);
FineField vec1(in.Grid());
FineField vec2(in.Grid());
std::cout<<GridLogMessage << "Calling PreSmoother " <<std::endl;
// std::cout<<GridLogMessage << "Calling PreSmoother input residual "<<norm2(in) <<std::endl;
double t;
// Fine Smoother
// out = in;
out = Zero();
t=-usecond();
_PreSmoother(in,out);
t+=usecond();
std::cout<<GridLogMessage << "PreSmoother took "<< t/1000.0<< "ms" <<std::endl;
// Update the residual
_FineOperator.Op(out,vec1); sub(vec1, in ,vec1);
// std::cout<<GridLogMessage <<"Residual-1 now " <<norm2(vec1)<<std::endl;
// Fine to Coarse
t=-usecond();
_Aggregates.ProjectToSubspace (Csrc,vec1);
t+=usecond();
std::cout<<GridLogMessage << "Project to coarse took "<< t/1000.0<< "ms" <<std::endl;
// Coarse correction
t=-usecond();
Csol = Zero();
_CoarseSolve(Csrc,Csol);
//Csol=Zero();
t+=usecond();
std::cout<<GridLogMessage << "Coarse solve took "<< t/1000.0<< "ms" <<std::endl;
// Coarse to Fine
t=-usecond();
// _CoarseOperator.PromoteFromSubspace(_Aggregates,Csol,vec1);
_Aggregates.PromoteFromSubspace(Csol,vec1);
add(out,out,vec1);
t+=usecond();
std::cout<<GridLogMessage << "Promote to this level took "<< t/1000.0<< "ms" <<std::endl;
// Residual
_FineOperator.Op(out,vec1); sub(vec1 ,in , vec1);
// std::cout<<GridLogMessage <<"Residual-2 now " <<norm2(vec1)<<std::endl;
// Fine Smoother
t=-usecond();
// vec2=vec1;
vec2=Zero();
_PostSmoother(vec1,vec2);
t+=usecond();
std::cout<<GridLogMessage << "PostSmoother took "<< t/1000.0<< "ms" <<std::endl;
add( out,out,vec2);
std::cout<<GridLogMessage << "Done " <<std::endl;
}
};
#endif
int main (int argc, char ** argv)
{
Grid_init(&argc,&argv);
// Usage : $ ./Example_spec_kryschur <Nm> <Nk> <maaxiter> <Nstop> <inFile> <outDir>
std::string NmStr = argv[1];
std::string NkStr = argv[2];
std::string maxIterStr = argv[3];
std::string NstopStr = argv[4];
std::string file = argv[5];
std::string outDir = argv[6];
RitzFilter RF;
if (argc == 8) {
std::string rf = argv[7];
RF = selectRitzFilter(rf);
} else {
RF = EvalReSmall;
}
std::cout << "Sorting eigenvalues using " << rfToString(RF) << std::endl;
//const int Ls=16;
const int Ls = 8;
// GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(), GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());
//std::vector<int> lat_size {16, 16, 16, 32};
std::vector<int> lat_size {8, 8, 8, 8};
std::cout << "Lattice size: " << lat_size << std::endl;
GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(lat_size,
GridDefaultSimd(Nd,vComplex::Nsimd()),
GridDefaultMpi());
GridRedBlackCartesian * UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid);
GridCartesian * FGrid = SpaceTimeGrid::makeFiveDimGrid(Ls,UGrid);
GridRedBlackCartesian * FrbGrid = SpaceTimeGrid::makeFiveDimRedBlackGrid(Ls,UGrid);
std::vector<int> seeds4({1,2,3,4});
std::vector<int> seeds5({5,6,7,8});
GridParallelRNG RNG5(FGrid); RNG5.SeedFixedIntegers(seeds5);
GridParallelRNG RNG4(UGrid); RNG4.SeedFixedIntegers(seeds4);
LatticeFermion src(FGrid); random(RNG5,src);
LatticeGaugeField Umu(UGrid);
FieldMetaData header;
NerscIO::readConfiguration(Umu,header,file);
// RealD mass=0.01;
RealD mass=0.001;
RealD M5=1.8;
DomainWallFermionD Ddwf(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,mass,M5);
DomainWallFermionD Dpv(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,1.0,M5);
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
typedef PVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> PVdagM_t;
typedef ShiftedPVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> ShiftedPVdagM_t;
typedef ShiftedComplexPVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> ShiftedComplexPVdagM_t;
PVdagM_t PVdagM(Ddwf, Dpv);
ShiftedPVdagM_t ShiftedPVdagM(0.1,Ddwf,Dpv);
SquaredLinearOperator<DomainWallFermionD, LatticeFermionD> Dsq (Ddwf);
NonHermitianLinearOperator<DomainWallFermionD, LatticeFermionD> DLinOp (Ddwf);
int Nm = std::stoi(NmStr);
int Nk = std::stoi(NkStr);
int maxIter = std::stoi(maxIterStr);
int Nstop = std::stoi(NstopStr);
std::cout << GridLogMessage << "Runnning Krylov Schur. Nm = " << Nm << ", Nk = " << Nk << ", maxIter = " << maxIter
<< ", Nstop = " << Nstop << std::endl;
KrylovSchur KrySchur (PVdagM, FGrid, 1e-8, RF); // use preconditioned PV^\dag D_{dwf}
// KrylovSchur KrySchur (DLinOp, FGrid, 1e-8, RF); // use D_{dwf}
KrySchur(src, maxIter, Nm, Nk, Nstop);
std::cout<<GridLogMessage << "*******************************************" << std::endl;
std::cout<<GridLogMessage << "***************** RESULTS *****************" << std::endl;
std::cout<<GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << "Krylov Schur eigenvalues: " << std::endl << KrySchur.getEvals() << std::endl;
writeEigensystem(KrySchur, outDir);
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage << "Done "<< std::endl;
Grid_finalize();
return 0;
}
+383
View File
@@ -0,0 +1,383 @@
/*************************************************************************************
Script for studying the Wilson eigenvectors resulting from the Krylov-Schur process.
Usage :
$ ./Example_spec_kryschur <Nm> <Nk> <maxiter> <Nstop> <inFile> <outDir> <?rf>
Nm = Maximum size of approximation subspace.
Nk = Size of truncation subspace
maxiter = Maximum number of iterations.
Nstop = Stop when Nstop eigenvalues have converged.
inFile = Gauge configuration to read in.
outDir = Directory to write output to.
rf = (Optional) RitzFilter to sort with. Takes in any string in
{EvalNormSmall, EvalNormLarge, EvalReSmall, EvalReLarge, EvalImSmall, EvalImLarge}
Output:
${outDir}/evals.txt = Contains all eigenvalues. Each line is formatted as `$idx $eval $ritz`, where:
- $idx is the index of the eigenvalue.
- $eval is the eigenvalue, formated as "(re,im)".
- $ritz is the Ritz estimate of the eigenvalue (deviation from being a true eigenvalue)
${outDir}/evec${idx} = Eigenvector $idx written out in SCIDAC format (if LIME is enabled).
Grid physics library, www.github.com/paboyle/Grid
Source file: ./tests/Test_padded_cell.cc
Copyright (C) 2023
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: Patrick Oare <poare@bnl.edu>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include <cstdlib>
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidual.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
#include <Grid/algorithms/iterative/BiCGSTAB.h>
#include <Grid/parallelIO/IldgIOtypes.h>
#include <Grid/parallelIO/IldgIO.h>
using namespace std;
using namespace Grid;
template <class T> void writeFile(T& in, std::string const fname){
#ifdef HAVE_LIME
// Ref: https://github.com/paboyle/Grid/blob/feature/scidac-wp1/tests/debug/Test_general_coarse_hdcg_phys48.cc#L111
std::cout << Grid::GridLogMessage << "Writes to: " << fname << std::endl;
Grid::emptyUserRecord record;
Grid::ScidacWriter WR(in.Grid()->IsBoss());
WR.open(fname);
WR.writeScidacFieldRecord(in,record,0); // Lexico
WR.close();
#endif
}
template <class T> void readFile(T& out, std::string const fname){
#ifdef HAVE_LIME
// Ref: https://github.com/paboyle/Grid/blob/feature/scidac-wp1/tests/debug/Test_general_coarse_hdcg_phys48.cc#L111
std::cout << Grid::GridLogMessage << "Reads at: " << fname << std::endl;
Grid::emptyUserRecord record;
// Grid::ScidacReader SR(out.Grid()->IsBoss());
Grid::ScidacReader SR;
SR.open(fname);
SR.readScidacFieldRecord(out, record);
SR.close();
#endif
}
/**
* Writes the eigensystem of a Krylov Schur object to a directory.
*
* Parameters
* ----------
* std::string path
* Directory to write to.
*/
template <class Field>
void writeEigensystem(KrylovSchur<Field> KS, std::string outDir) {
int Nk = KS.getNk();
std::cout << GridLogMessage << "Writing output to directory: " << outDir << std::endl;
// Write evals
std::string evalPath = outDir + "/evals.txt";
std::ofstream fEval;
fEval.open(evalPath);
Eigen::VectorXcd evals = KS.getEvals();
std::vector<RealD> ritz = KS.getRitzEstimates();
for (int i = 0; i < Nk; i++) {
// write eigenvalues and Ritz estimates
fEval << i << " " << evals(i) << " " << ritz[i];
if (i < Nk - 1) { fEval << "\n"; }
}
fEval.close();
// Write evecs
int Nevecs = Nk; // don't write all of them
std::vector<Field> evecs = KS.getEvecs();
for (int i = 0; i < Nevecs; i++) {
std::string fName = outDir + "/evec" + std::to_string(i);
writeFile(evecs[i], fName); // using method from Grid/HMC/ComputeWilsonFlow.cc
}
}
// Hermitize a DWF operator by squaring it
template<class Matrix,class Field>
class SquaredLinearOperator : public LinearOperatorBase<Field> {
public:
Matrix &_Mat;
public:
SquaredLinearOperator(Matrix &Mat): _Mat(Mat) {};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
// std::cout << "Op is overloaded as HermOp" << std::endl;
HermOp(in, out);
}
void AdjOp (const Field &in, Field &out){
HermOp(in, out);
}
void _Op (const Field &in, Field &out){
// std::cout << "Op: M "<<std::endl;
_Mat.M(in, out);
}
void _AdjOp (const Field &in, Field &out){
// std::cout << "AdjOp: Mdag "<<std::endl;
_Mat.Mdag(in, out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
// std::cout << "HermOp: Mdag M Mdag M"<<std::endl;
Field tmp(in.Grid());
_Op(in,tmp);
_AdjOp(tmp,out);
}
};
template<class Matrix,class Field>
class PVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
public:
PVdagMLinearOperator(Matrix &Mat,Matrix &PV): _Mat(Mat),_PV(PV){};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(in,tmp);
_Mat.Mdag(tmp,out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
// _Mat.M(in,tmp);
// _PV.Mdag(tmp,out);
// _PV.M(out,tmp);
// _Mat.Mdag(tmp,out);
Op(in,tmp);
AdjOp(tmp,out);
// std::cout << "HermOp done "<<norm2(out)<<std::endl;
}
};
template<class Matrix,class Field>
class ShiftedPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
RealD shift;
public:
ShiftedPVdagMLinearOperator(RealD _shift,Matrix &Mat,Matrix &PV): shift(_shift),_Mat(Mat),_PV(PV){};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(tmp,out);
_Mat.Mdag(in,tmp);
out = out + shift * in;
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
};
template<class Matrix, class Field>
class ShiftedComplexPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
ComplexD shift;
public:
ShiftedComplexPVdagMLinearOperator(ComplexD _shift,Matrix &Mat,Matrix &PV): shift(_shift),_Mat(Mat),_PV(PV){};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(tmp,out);
_Mat.Mdag(in,tmp);
out = out + shift * in;
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
void resetShift(ComplexD newShift) {
shift = newShift;
}
};
int main (int argc, char ** argv)
{
Grid_init(&argc,&argv);
// Usage : $ ./Example_wilson_evecs ${inFile}
std::string file = argv[1];
const int Ls=16;
// GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(), GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());
//std::vector<int> lat_size {16, 16, 16, 32};
std::vector<int> lat_size {32, 32, 32, 32};
std::cout << "Lattice size: " << lat_size << std::endl;
GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(lat_size,
GridDefaultSimd(Nd,vComplex::Nsimd()),
GridDefaultMpi());
GridRedBlackCartesian * UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid);
// GridCartesian * FGrid = SpaceTimeGrid::makeFiveDimGrid(Ls,UGrid);
// GridRedBlackCartesian * FrbGrid = SpaceTimeGrid::makeFiveDimRedBlackGrid(Ls,UGrid);
GridCartesian * FGrid = UGrid;
GridRedBlackCartesian * FrbGrid = UrbGrid;
std::vector<int> seeds4({1,2,3,4});
GridParallelRNG RNG4(UGrid);
RNG4.SeedFixedIntegers(seeds4);
LatticeFermion src(FGrid); random(RNG4, src);
LatticeGaugeField Umu(UGrid);
FieldMetaData header;
NerscIO::readConfiguration(Umu, header, file);
std::cout << GridLogMessage << "Loaded configuration" << std::endl;
// RealD mass = 0.01;
RealD M5 = 1.8;
// Wilson mass
RealD mass = -1.6;
std::cout << GridLogMessage << "masses specified" << std::endl;
std::vector<Complex> boundary = {1,1,1,-1};
WilsonFermionD::ImplParams Params(boundary);
// DomainWallFermionD Ddwf(Umu, *FGrid, *FrbGrid, *UGrid, *UrbGrid, mass, M5);
// NonHermitianLinearOperator<DomainWallFermionD, LatticeFermionD> DLinOp (Ddwf);
// WilsonFermionD Dwilson(Umu, *FGrid, *FrbGrid, mass);
WilsonFermionD Dwilson(Umu, *UGrid, *UrbGrid, mass, Params);
NonHermitianLinearOperator<WilsonFermionD, LatticeFermionD> DLinOp (Dwilson);
std::cout << GridLogMessage << "Dirac operator defined" << std::endl;
std::string eigenPath = "/home/poare/lqcd/multigrid/spectra/32cube-rho0.124-tau4/U_smr_3.000000/Nm72_Nk24_8111835.aurora-pbs-0001.hostmgmt.cm.aurora.alcf.anl.gov/";
std::cout << GridLogMessage << "Loading eigenvalues" << std::endl;
std::ifstream evalFile(eigenPath + "evals.txt");
std::string str;
std::vector<ComplexD> evals;
while (std::getline(evalFile, str)) {
std::cout << GridLogMessage << "Reading line: " << str << std::endl;
int i1 = str.find("(") + 1;
int i2 = str.find(",") + 1;
int i3 = str.find(")");
std::cout << "i1,i2,i3 = " << i1 << "," << i2 << "," << i3 << std::endl;
std::string reStr = str.substr(i1, i2 - i1);
std::string imStr = str.substr(i2, i3 - i2);
std::cout << GridLogMessage << "Parsed re = " << reStr << " and im = " << imStr << std::endl;
// ComplexD z (std::stof(reStr), std::stof(imStr));
ComplexD z (std::stod(reStr), std::stod(imStr));
evals.push_back(z);
}
std::cout << GridLogMessage << "Eigenvalues: " << evals << std::endl;
int Nevecs = 24;
std::vector<LatticeFermion> evecs;
LatticeFermion evec (FGrid);
for (int i = 0; i < Nevecs; i++) {
std::string evecPath = eigenPath + "evec" + std::to_string(i);
readFile(evec, evecPath);
evecs.push_back(evec);
}
std::cout << GridLogMessage << "Evecs loaded" << std::endl;
// Compute < evec | D - \lambda | evec >
std::cout << GridLogMessage << "Testing eigenvectors" << std::endl;
LatticeFermion Devec (FGrid);
ComplexD ritz;
for (int i = 0; i < Nevecs; i++) {
Devec = Zero();
DLinOp.Op(evecs[i], Devec);
ritz = std::sqrt(norm2(Devec - evals[i] * evecs[i]));
std::cout << GridLogMessage << "i = " << i << ", || (D - lambda) |vi> || = " << ritz << std::endl;
}
// Eigen::MatrixXcd Dw_evecs;
// Dw_evecs = Eigen::MatrixXcd::Zero(Nevecs, Nevecs);
// for (int i = 0; i < Nevecs; i++) {
// Linop.Op(evecs[i], Devec);
// for (int j = 0; j < Nevecs; j++) {
// }
// }
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage << "Done "<< std::endl;
Grid_finalize();
return 0;
}
+374
View File
@@ -0,0 +1,374 @@
/*************************************************************************************
Runs the Krylov-Schur algorithm on a Wilson fermion operator to determine part of its spectrum.
TODO rename this file: really is running the topology change jobs on Aurora.
Usage :
$ ./Example_spec_kryschur <Nm> <Nk> <maxiter> <Nstop> <inFile> <outDir> <?rf>
Nm = Maximum size of approximation subspace.
Nk = Size of truncation subspace
maxiter = Maximum number of iterations.
Nstop = Stop when Nstop eigenvalues have converged.
inFile = Gauge configuration to read in.
outDir = Directory to write output to.
rf = (Optional) RitzFilter to sort with. Takes in any string in
{EvalNormSmall, EvalNormLarge, EvalReSmall, EvalReLarge, EvalImSmall, EvalImLarge}
Output:
${outDir}/evals.txt = Contains all eigenvalues. Each line is formatted as `$idx $eval $ritz`, where:
- $idx is the index of the eigenvalue.
- $eval is the eigenvalue, formated as "(re,im)".
- $ritz is the Ritz estimate of the eigenvalue (deviation from being a true eigenvalue)
${outDir}/evec${idx} = Eigenvector $idx written out in SCIDAC format (if LIME is enabled).
Grid physics library, www.github.com/paboyle/Grid
Source file: ./tests/Test_padded_cell.cc
Copyright (C) 2023
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: Patrick Oare <poare@bnl.edu>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include <cstdlib>
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidual.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
#include <Grid/algorithms/iterative/BiCGSTAB.h>
#include <Grid/parallelIO/IldgIOtypes.h>
#include <Grid/parallelIO/IldgIO.h>
using namespace std;
using namespace Grid;
template <class T> void writeFile(T& in, std::string const fname){
#ifdef HAVE_LIME
// Ref: https://github.com/paboyle/Grid/blob/feature/scidac-wp1/tests/debug/Test_general_coarse_hdcg_phys48.cc#L111
std::cout << Grid::GridLogMessage << "Writes to: " << fname << std::endl;
Grid::emptyUserRecord record;
Grid::ScidacWriter WR(in.Grid()->IsBoss());
WR.open(fname);
WR.writeScidacFieldRecord(in,record,0); // Lexico
WR.close();
#endif
}
/**
* Writes the eigensystem of a Krylov Schur object to a directory.
*
* Parameters
* ----------
* std::string path
* Directory to write to.
*/
template <class Field>
void writeEigensystem(KrylovSchur<Field> KS, std::string outDir) {
int Nk = KS.getNk();
std::cout << GridLogMessage << "Writing output to directory: " << outDir << std::endl;
// Write evals
std::string evalPath = outDir + "/evals.txt";
std::ofstream fEval;
fEval.open(evalPath);
Eigen::VectorXcd evals = KS.getEvals();
std::vector<RealD> ritz = KS.getRitzEstimates();
for (int i = 0; i < Nk; i++) {
// write eigenvalues and Ritz estimates
fEval << i << " " << evals(i) << " " << ritz[i];
if (i < Nk - 1) { fEval << "\n"; }
}
fEval.close();
// Write evecs
int Nevecs = Nk; // don't write all of them
std::vector<Field> evecs = KS.getEvecs();
for (int i = 0; i < Nevecs; i++) {
std::string fName = outDir + "/evec" + std::to_string(i);
writeFile(evecs[i], fName); // using method from Grid/HMC/ComputeWilsonFlow.cc
}
}
// Hermitize a DWF operator by squaring it
template<class Matrix,class Field>
class SquaredLinearOperator : public LinearOperatorBase<Field> {
public:
Matrix &_Mat;
public:
SquaredLinearOperator(Matrix &Mat): _Mat(Mat) {};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
// std::cout << "Op is overloaded as HermOp" << std::endl;
HermOp(in, out);
}
void AdjOp (const Field &in, Field &out){
HermOp(in, out);
}
void _Op (const Field &in, Field &out){
// std::cout << "Op: M "<<std::endl;
_Mat.M(in, out);
}
void _AdjOp (const Field &in, Field &out){
// std::cout << "AdjOp: Mdag "<<std::endl;
_Mat.Mdag(in, out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
// std::cout << "HermOp: Mdag M Mdag M"<<std::endl;
Field tmp(in.Grid());
_Op(in,tmp);
_AdjOp(tmp,out);
}
};
template<class Matrix,class Field>
class PVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
public:
PVdagMLinearOperator(Matrix &Mat,Matrix &PV): _Mat(Mat),_PV(PV){};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(in,tmp);
_Mat.Mdag(tmp,out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
// _Mat.M(in,tmp);
// _PV.Mdag(tmp,out);
// _PV.M(out,tmp);
// _Mat.Mdag(tmp,out);
Op(in,tmp);
AdjOp(tmp,out);
// std::cout << "HermOp done "<<norm2(out)<<std::endl;
}
};
template<class Matrix,class Field>
class ShiftedPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
RealD shift;
public:
ShiftedPVdagMLinearOperator(RealD _shift,Matrix &Mat,Matrix &PV): shift(_shift),_Mat(Mat),_PV(PV){};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(tmp,out);
_Mat.Mdag(in,tmp);
out = out + shift * in;
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
};
template<class Matrix, class Field>
class ShiftedComplexPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
ComplexD shift;
public:
ShiftedComplexPVdagMLinearOperator(ComplexD _shift,Matrix &Mat,Matrix &PV): shift(_shift),_Mat(Mat),_PV(PV){};
void OpDiag (const Field &in, Field &out) { assert(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(tmp,out);
_Mat.Mdag(in,tmp);
out = out + shift * in;
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
void resetShift(ComplexD newShift) {
shift = newShift;
}
};
int main (int argc, char ** argv)
{
Grid_init(&argc,&argv);
// Usage : $ ./Example_spec_kryschur <Nm> <Nk> <maaxiter> <Nstop> <inFile> <outDir>
std::string NmStr = argv[1];
std::string NkStr = argv[2];
std::string maxIterStr = argv[3];
std::string NstopStr = argv[4];
std::string file = argv[5];
std::string outDir = argv[6];
// RitzFilter RF;
// if (argc == 8) {
// std::string rf = argv[7];
// RF = selectRitzFilter(rf);
// } else {
// RF = EvalReSmall;
// }
// RitzFilter RF;
std::string rf = argv[7];
RitzFilter RF = selectRitzFilter(rf);
std::cout << "Sorting eigenvalues using " << rfToString(RF) << std::endl;
const int Ls=16;
// GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(), GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());
//std::vector<int> lat_size {16, 16, 16, 32};
std::vector<int> lat_size {32, 32, 32, 32};
std::cout << "Lattice size: " << lat_size << std::endl;
GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(lat_size,
GridDefaultSimd(Nd,vComplex::Nsimd()),
GridDefaultMpi());
GridRedBlackCartesian * UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid);
// GridCartesian * FGrid = SpaceTimeGrid::makeFiveDimGrid(Ls,UGrid);
// GridRedBlackCartesian * FrbGrid = SpaceTimeGrid::makeFiveDimRedBlackGrid(Ls,UGrid);
GridCartesian * FGrid = UGrid;
GridRedBlackCartesian * FrbGrid = UrbGrid;
std::vector<int> seeds4({1,2,3,4});
GridParallelRNG RNG4(UGrid);
RNG4.SeedFixedIntegers(seeds4);
LatticeFermion src(FGrid); random(RNG4, src);
LatticeGaugeField Umu(UGrid);
FieldMetaData header;
NerscIO::readConfiguration(Umu, header, file);
std::cout << GridLogMessage << "Loaded configuration" << std::endl;
// RealD mass = 0.01;
RealD M5 = 1.8;
// Wilson mass
RealD mass = -1.6;
std::cout << GridLogMessage << "masses specified" << std::endl;
std::vector<Complex> boundary = {1,1,1,-1};
WilsonFermionD::ImplParams Params(boundary);
// DomainWallFermionD Ddwf(Umu, *FGrid, *FrbGrid, *UGrid, *UrbGrid, mass, M5);
// NonHermitianLinearOperator<DomainWallFermionD, LatticeFermionD> DLinOp (Ddwf);
// WilsonFermionD Dwilson(Umu, *FGrid, *FrbGrid, mass);
WilsonFermionD Dwilson(Umu, *UGrid, *UrbGrid, mass, Params);
NonHermitianLinearOperator<WilsonFermionD, LatticeFermionD> DLinOp (Dwilson);
std::cout << GridLogMessage << "Dirac operator defined" << std::endl;
// Define PV^dag D (if we want)
// DomainWallFermionD Dpv(Umu, *FGrid, *FrbGrid, *UGrid, *UrbGrid, 1.0, M5);
// typedef PVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> PVdagM_t;
// PVdagM_t PVdagM(Ddwf, Dpv);
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
// SquaredLinearOperator<WilsonFermionD, LatticeFermionD> Dsq (DWilson);
// NonHermitianLinearOperator<WilsonFermionD, LatticeFermionD> DLinOp (DWilson);
int Nm = std::stoi(NmStr);
int Nk = std::stoi(NkStr);
int maxIter = std::stoi(maxIterStr);
int Nstop = std::stoi(NstopStr);
std::cout << GridLogMessage << "Runnning Krylov Schur. Nm = " << Nm << ", Nk = " << Nk << ", maxIter = " << maxIter
<< ", Nstop = " << Nstop << std::endl;
// KrylovSchur KrySchur (PVdagM, FGrid, 1e-8, RF); // use PV^\dag M
KrylovSchur KrySchur (DLinOp, FGrid, 1e-8, RF); // use Ddwf
KrySchur(src, maxIter, Nm, Nk, Nstop);
std::cout << GridLogMessage << "Checking eigensystem." << std::endl;
KrySchur.checkRitzEstimate();
std::cout<<GridLogMessage << "*******************************************" << std::endl;
std::cout<<GridLogMessage << "***************** RESULTS *****************" << std::endl;
std::cout<<GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << "Krylov Schur eigenvalues: " << std::endl << KrySchur.getEvals() << std::endl;
writeEigensystem(KrySchur, outDir);
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage << "Done "<< std::endl;
Grid_finalize();
return 0;
}
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0"?>
<grid>
<LanczosParameters>
<mass>0</mass>
<mstep>-0.025</mstep>
<M5>1.8</M5>
<Ls>48</Ls>
<Nstop>100</Nstop>
<Nk>100</Nk>
<Np>100</Np>
<ReadEvec>0</ReadEvec>
<maxIter>1000</maxIter>
<reorthog>1</reorthog>
<Nblock>4</Nblock>
<verify>0</verify>
<shift>1.5</shift>
<resid>1e-8</resid>
<ChebyLow>1</ChebyLow>
<ChebyHigh>100</ChebyHigh>
<ChebyOrder>51</ChebyOrder>
</LanczosParameters>
</grid>
+469
View File
@@ -0,0 +1,469 @@
/*************************************************************************************
fft5d.cc — Fourier analysis of a time series of 4-D lattice scalar fields.
Designed for force-norm files from FTHMC (one RealD per site, SCIDAC or binary).
Assembles a (4+1)-D structure [trajectory][t][z][y][x] using Grid lattice fields
and performs three FFT analyses:
--fft spatial
4-D spatial FFT using Grid's FFT class (MPI+GPU parallel via Cshift/cufft).
Outputs shell-averaged P(|k|^2) averaged over trajectories, and a
per-mode table P(kt,kz,ky,kx).
--fft traj
1-D trajectory-axis FFT at each lattice site using FFTW3 locally at each
MPI rank. Outputs site-averaged P(f_traj) and autocorrelation C(lag).
Results are combined with GlobalSumVector.
--fft all
Both of the above, plus a 2-D cross spectrum P(f_traj, |k_spatial|^2):
spatial FFT per trajectory, then trajectory FFTW on the momentum-space series.
Normalisations:
Spatial FFT (volume V): P_spatial(k) = |F(k)|^2 / V^2
=> (1/V) * sum_k P(k) = site mean-square per trajectory
Trajectory FFT (Ntraj): P_traj(f) = |F(f)|^2 / Ntraj^2
=> sum_f P(f) = site mean-square over trajectories
Build: add to examples/Make.inc (see bottom of this file), then make.
Usage (follows Grid conventions):
fft5d --grid Lx.Ly.Lz.Lt [--mpi Px.Py.Pz.Pt] \
[--fft spatial|traj|all] [--format scidac|binary] \
[--output PREFIX] file1 file2 ...
*************************************************************************************/
#include <Grid/Grid.h>
// Grid's FFT.h uses cufft on CUDA builds; for the trajectory axis we also need
// CPU-side FFTW3 (already linked via -lfftw3 in GRID_LIBS).
#include <fftw3.h>
#include <algorithm>
#include <cassert>
#include <cmath>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
using namespace Grid;
// ─────────────────────────────────────────────────────────────────────────────
// Global coordinate of a local site (osite, lane) on this MPI rank
// ─────────────────────────────────────────────────────────────────────────────
static void globalCoor(int osite, int lane, GridCartesian* g, Coordinate& gc)
{
Coordinate oc, ic;
Lexicographic::CoorFromIndex(oc, osite, g->_rdimensions);
Lexicographic::CoorFromIndex(ic, lane, g->_simd_layout);
for (int d = 0; d < Nd; d++)
gc[d] = g->_processor_coor[d] * g->_ldimensions[d]
+ oc[d] * g->_simd_layout[d] + ic[d];
}
// ─────────────────────────────────────────────────────────────────────────────
// Shell-average a power LatticeRealD over |k|^2 bins (MPI-aware via GlobalSumVector)
// ─────────────────────────────────────────────────────────────────────────────
static void shellAverage(const LatticeRealD& power, GridCartesian* g,
double norm, std::ostream& ofs)
{
const int Nsimd = vRealD::Nsimd();
Coordinate fdims = g->_fdimensions;
int maxk2 = 0;
for (int d = 0; d < Nd; d++) { int h = fdims[d]/2; maxk2 += h*h; }
std::vector<RealD> psum(maxk2+1, 0.0), cnt(maxk2+1, 0.0);
{
auto pv = power.View(CpuRead);
Coordinate gc(Nd);
for (int os = 0; os < (int)g->oSites(); os++) {
for (int lane = 0; lane < Nsimd; lane++) {
globalCoor(os, lane, g, gc);
int k2 = 0;
for (int d = 0; d < Nd; d++) {
int kd = std::min(gc[d], fdims[d] - gc[d]);
k2 += kd*kd;
}
psum[k2] += (RealD)extractLane(lane, pv[os]);
cnt [k2] += 1.0;
}
}
}
g->GlobalSumVector(psum.data(), (int)psum.size());
g->GlobalSumVector(cnt .data(), (int)cnt .size());
for (int k2 = 0; k2 <= maxk2; k2++)
if (cnt[k2] > 0)
ofs << k2 << " " << std::sqrt((double)k2) << " "
<< (psum[k2] / cnt[k2]) * norm << "\n";
}
// ─────────────────────────────────────────────────────────────────────────────
// Convert LatticeRealD → LatticeComplexD (zero imaginary part)
// ─────────────────────────────────────────────────────────────────────────────
static LatticeComplexD toComplex(const LatticeRealD& r)
{
std::vector<RealD> lr;
unvectorizeToLexOrdArray(lr, r);
std::vector<ComplexD> lc(lr.size());
for (size_t i = 0; i < lr.size(); i++) lc[i] = ComplexD(lr[i], 0.0);
LatticeComplexD c(r.Grid());
vectorizeFromLexOrdArray(lc, c);
return c;
}
// ─────────────────────────────────────────────────────────────────────────────
// File readers
// ─────────────────────────────────────────────────────────────────────────────
static LatticeComplexD readScidac(const std::string& fname, GridCartesian* g)
{
LatticeRealD field(g);
emptyUserRecord rec;
ScidacReader RD;
RD.open(fname);
RD.readScidacFieldRecord(field, rec);
RD.close();
return toComplex(field);
}
static LatticeComplexD readBinary(const std::string& fname, GridCartesian* g)
{
// Raw IEEE doubles in lex order [x][y][z][t] written serially.
// Rank 0 reads the file, broadcasts to all ranks via GlobalSumVector.
Coordinate fdims = g->_fdimensions;
long vol4 = 1; for (int d = 0; d < Nd; d++) vol4 *= fdims[d];
std::vector<RealD> buf(vol4, 0.0);
if (g->IsBoss()) {
std::ifstream f(fname, std::ios::binary);
if (!f) throw std::runtime_error("Cannot open: " + fname);
f.read(reinterpret_cast<char*>(buf.data()), vol4 * sizeof(RealD));
}
g->GlobalSumVector(buf.data(), (int)vol4); // broadcast from rank 0
LatticeRealD field(g);
Coordinate ldims = g->_ldimensions, pcoor = g->_processor_coor;
for (long ls = 0; ls < g->lSites(); ls++) {
Coordinate lc;
g->LocalIndexToLocalCoor(ls, lc);
long glex = 0, stride = 1;
for (int d = 0; d < Nd; d++) {
glex += (pcoor[d]*ldims[d] + lc[d]) * stride;
stride *= fdims[d];
}
pokeLocalSite(buf[glex], field, lc);
}
return toComplex(field);
}
// ─────────────────────────────────────────────────────────────────────────────
// Load all files into a trajectory vector
// ─────────────────────────────────────────────────────────────────────────────
static std::vector<LatticeComplexD>
loadFiles(const std::vector<std::string>& files, GridCartesian* g,
const std::string& fmt)
{
std::vector<LatticeComplexD> traj;
traj.reserve(files.size());
for (int n = 0; n < (int)files.size(); n++) {
std::cout << GridLogMessage << "[" << n << "] reading " << files[n] << "\n";
if (fmt == "scidac") traj.push_back(readScidac(files[n], g));
else traj.push_back(readBinary (files[n], g));
}
return traj;
}
// ─────────────────────────────────────────────────────────────────────────────
// Spatial FFT analysis
// ─────────────────────────────────────────────────────────────────────────────
static void analyzeSpatial(const std::vector<LatticeComplexD>& traj,
GridCartesian* g, const std::string& pfx)
{
int Ntraj = (int)traj.size();
long vol4 = 1; for (int d = 0; d < Nd; d++) vol4 *= g->_fdimensions[d];
FFT theFFT(g);
LatticeRealD pavg(g); pavg = Zero();
for (int n = 0; n < Ntraj; n++) {
LatticeComplexD fk(g);
theFFT.FFT_all_dim(fk, traj[n], FFT::forward);
// Evaluate product before applying real() — real() is not defined for
// unevaluated LatticeBinaryExpression.
LatticeComplexD fk_sq(g); fk_sq = conjugate(fk) * fk;
std::vector<ComplexD> lc; unvectorizeToLexOrdArray(lc, fk_sq);
std::vector<RealD> lr(lc.size());
for (size_t i = 0; i < lc.size(); i++) lr[i] = lc[i].real();
LatticeRealD pk(g); vectorizeFromLexOrdArray(lr, pk);
pavg += pk;
}
pavg *= (1.0 / Ntraj);
// Shell-averaged spectrum
{
std::ofstream fs(pfx + "_spatial_shell.dat");
fs << "# k2 |k| P_shell_avg (P = |F|^2 / V^2 / shell_count)\n";
shellAverage(pavg, g, 1.0 / ((double)vol4 * vol4), fs);
std::cout << GridLogMessage << "Written: " << pfx << "_spatial_shell.dat\n";
}
// Per-mode table (rank 0 only, via peekSite)
if (g->IsBoss()) {
std::ofstream fm(pfx + "_spatial_modes.dat");
fm << "# kt kz ky kx k2 |k| P_traj_avg\n";
Coordinate fdims = g->_fdimensions, site(Nd);
double norm = 1.0 / ((double)vol4 * vol4);
for (site[3]=0; site[3]<fdims[3]; site[3]++) { int kt=std::min(site[3],fdims[3]-site[3]);
for (site[2]=0; site[2]<fdims[2]; site[2]++) { int kz=std::min(site[2],fdims[2]-site[2]);
for (site[1]=0; site[1]<fdims[1]; site[1]++) { int ky=std::min(site[1],fdims[1]-site[1]);
for (site[0]=0; site[0]<fdims[0]; site[0]++) { int kx=std::min(site[0],fdims[0]-site[0]);
RealD p; peekSite(p, pavg, site);
int k2 = kt*kt+kz*kz+ky*ky+kx*kx;
fm << kt<<" "<<kz<<" "<<ky<<" "<<kx<<" "<<k2<<" "
<<std::sqrt((double)k2)<<" "<<p*norm<<"\n";
}}}}
std::cout << GridLogMessage << "Written: " << pfx << "_spatial_modes.dat\n";
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Local FFTW trajectory FFT: each rank FFTs its own local sites independently
// ─────────────────────────────────────────────────────────────────────────────
static void trajFFT(const std::vector<LatticeComplexD>& in,
std::vector<LatticeComplexD>& out)
{
int Ntraj = (int)in.size();
GridBase* g = in[0].Grid();
long lsites = g->lSites();
// fftw_complex is double[2] — std::vector<double[2]> is not supported by nvcc.
// Use std::vector<double> with 2x size and reinterpret_cast.
std::vector<double> ibuf((long)Ntraj * lsites * 2, 0.0);
for (int n = 0; n < Ntraj; n++) {
std::vector<ComplexD> lc;
unvectorizeToLexOrdArray(lc, in[n]);
for (long s = 0; s < lsites; s++) {
ibuf[((long)n*lsites + s)*2 + 0] = lc[s].real();
ibuf[((long)n*lsites + s)*2 + 1] = lc[s].imag();
}
}
// lsites transforms of length Ntraj, stride=lsites, dist=1
std::vector<double> obuf((long)Ntraj * lsites * 2, 0.0);
fftw_complex* iptr = reinterpret_cast<fftw_complex*>(ibuf.data());
fftw_complex* optr = reinterpret_cast<fftw_complex*>(obuf.data());
int n1[1] = {Ntraj};
fftw_plan p = fftw_plan_many_dft(
1, n1, (int)lsites,
iptr, nullptr, (int)lsites, 1,
optr, nullptr, (int)lsites, 1,
FFTW_FORWARD, FFTW_ESTIMATE);
fftw_execute(p);
fftw_destroy_plan(p);
// vector::assign triggers _M_fill_assign which needs a default constructor;
// Lattice has none. Use explicit emplace_back instead.
out.clear(); out.reserve(Ntraj);
for (int k = 0; k < Ntraj; k++) out.emplace_back(g);
for (int k = 0; k < Ntraj; k++) {
std::vector<ComplexD> lc(lsites);
for (long s = 0; s < lsites; s++)
lc[s] = ComplexD(obuf[((long)k*lsites+s)*2], obuf[((long)k*lsites+s)*2+1]);
vectorizeFromLexOrdArray(lc, out[k]);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Trajectory-axis FFT analysis
// ─────────────────────────────────────────────────────────────────────────────
static void analyzeTraj(const std::vector<LatticeComplexD>& traj,
GridCartesian* g, const std::string& pfx)
{
int Ntraj = (int)traj.size();
int Nf = Ntraj / 2 + 1;
long vol4 = 1; for (int d = 0; d < Nd; d++) vol4 *= g->_fdimensions[d];
std::vector<LatticeComplexD> ftraj;
trajFFT(traj, ftraj);
// P(k) = (1/vol4) * sum_sites |F_traj(k)|^2 / Ntraj^2
std::vector<RealD> Pavg(Ntraj, 0.0);
for (int k = 0; k < Ntraj; k++) {
LatticeComplexD tmp(g); tmp = conjugate(ftraj[k]) * ftraj[k];
std::vector<ComplexD> lc; unvectorizeToLexOrdArray(lc, tmp);
std::vector<RealD> lr(lc.size());
for (size_t i = 0; i < lc.size(); i++) lr[i] = lc[i].real();
LatticeRealD pk(g); vectorizeFromLexOrdArray(lr, pk);
RealD s = 0.0;
for (long ls = 0; ls < g->lSites(); ls++) {
Coordinate lc; g->LocalIndexToLocalCoor(ls, lc);
RealD v; peekLocalSite(v, pk, lc);
s += v;
}
g->GlobalSum(s);
Pavg[k] = s / ((double)vol4 * Ntraj * Ntraj);
}
{
std::ofstream f(pfx + "_traj_power.dat");
f << "# k freq P_avg (sum_k P = site mean-square)\n";
for (int k = 0; k < Nf; k++)
f << k << " " << (double)k/Ntraj << " " << Pavg[k] << "\n";
std::cout << GridLogMessage << "Written: " << pfx << "_traj_power.dat\n";
}
// Autocorrelation via IFFT of the power spectrum
std::vector<double> Pc_buf(Nf * 2, 0.0);
for (int k = 0; k < Nf; k++) { Pc_buf[k*2] = Pavg[k]; Pc_buf[k*2+1] = 0.0; }
fftw_complex* Pc = reinterpret_cast<fftw_complex*>(Pc_buf.data());
std::vector<double> acorr(Ntraj, 0.0);
fftw_plan ip = fftw_plan_dft_c2r(1, &Ntraj, Pc, acorr.data(), FFTW_ESTIMATE);
fftw_execute(ip); fftw_destroy_plan(ip);
double c0 = acorr[0] / Ntraj;
{
std::ofstream f(pfx + "_traj_autocorr.dat");
f << "# lag C(lag) C(lag)/C(0)\n";
for (int lag = 0; lag < Ntraj/2; lag++) {
double c = acorr[lag] / Ntraj;
f << lag << " " << c << " " << (c0 > 0 ? c/c0 : 0.0) << "\n";
}
std::cout << GridLogMessage << "Written: " << pfx << "_traj_autocorr.dat\n";
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Full 5-D analysis: spatial FFT → trajectory FFT → 2-D cross spectrum
// ─────────────────────────────────────────────────────────────────────────────
static void analyzeAll5D(const std::vector<LatticeComplexD>& traj,
GridCartesian* g, const std::string& pfx)
{
int Ntraj = (int)traj.size();
int Nf = Ntraj / 2 + 1;
long vol4 = 1; for (int d = 0; d < Nd; d++) vol4 *= g->_fdimensions[d];
Coordinate fdims = g->_fdimensions;
// Step 1: spatial FFT for each trajectory
FFT theFFT(g);
std::vector<LatticeComplexD> sfft(Ntraj, LatticeComplexD(g));
for (int n = 0; n < Ntraj; n++)
theFFT.FFT_all_dim(sfft[n], traj[n], FFT::forward);
// Step 2: trajectory FFTW on the momentum-space series
std::vector<LatticeComplexD> tfft;
trajFFT(sfft, tfft);
// Step 3: P(f_traj, |k_spatial|^2) shell-averaged
int maxk2 = 0;
for (int d = 0; d < Nd; d++) { int h = fdims[d]/2; maxk2 += h*h; }
long nb = (long)Nf * (maxk2+1);
std::vector<RealD> p2d(nb, 0.0), cnt2d(nb, 0.0);
const int Nsimd = vComplexD::Nsimd();
double norm = 1.0 / ((double)Ntraj * Ntraj * vol4 * vol4);
for (int kf = 0; kf < Nf; kf++) {
LatticeComplexD tmp(g); tmp = conjugate(tfft[kf]) * tfft[kf];
std::vector<ComplexD> lc2; unvectorizeToLexOrdArray(lc2, tmp);
std::vector<RealD> lr2(lc2.size());
for (size_t i = 0; i < lc2.size(); i++) lr2[i] = lc2[i].real();
LatticeRealD pk(g); vectorizeFromLexOrdArray(lr2, pk);
auto pv = pk.View(CpuRead);
Coordinate gc(Nd);
for (int os = 0; os < (int)g->oSites(); os++) {
for (int lane = 0; lane < Nsimd; lane++) {
globalCoor(os, lane, g, gc);
int k2 = 0;
for (int d = 0; d < Nd; d++) {
int kd = std::min(gc[d], fdims[d] - gc[d]);
k2 += kd*kd;
}
long b = (long)kf*(maxk2+1) + k2;
p2d [b] += (RealD)extractLane(lane, pv[os]);
cnt2d[b] += 1.0;
}
}
}
g->GlobalSumVector(p2d .data(), (int)nb);
g->GlobalSumVector(cnt2d.data(), (int)nb);
if (g->IsBoss()) {
std::ofstream f(pfx + "_5d_cross.dat");
f << "# k_traj freq_traj k2_spatial |k_spatial| P_avg\n";
for (int kf = 0; kf < Nf; kf++) {
double freq = (double)kf / Ntraj;
for (int k2 = 0; k2 <= maxk2; k2++) {
long b = (long)kf*(maxk2+1) + k2;
if (cnt2d[b] > 0)
f << kf << " " << freq << " " << k2 << " "
<< std::sqrt((double)k2) << " "
<< (p2d[b]/cnt2d[b]) * norm << "\n";
}
f << "\n"; // blank line between kf slices for gnuplot pm3d
}
std::cout << GridLogMessage << "Written: " << pfx << "_5d_cross.dat\n";
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Main
// ─────────────────────────────────────────────────────────────────────────────
static void usage(const char* prog)
{
std::cerr
<< "Usage: " << prog << " --grid Lx.Ly.Lz.Lt [--mpi Px.Py.Pz.Pt]\n"
<< " [--fft spatial|traj|all] [--format scidac|binary]\n"
<< " [--output PREFIX] file1 [file2 ...]\n";
exit(1);
}
int main(int argc, char** argv)
{
Grid_init(&argc, &argv); // consumes --grid, --mpi, etc.
std::string fftMode = "all";
std::string fmt = "scidac";
std::string pfx = "fft5d";
std::vector<std::string> files;
for (int i = 1; i < argc; i++) {
std::string a = argv[i];
if (a == "--fft" && i+1 < argc) fftMode = argv[++i];
else if (a == "--format" && i+1 < argc) fmt = argv[++i];
else if (a == "--output" && i+1 < argc) pfx = argv[++i];
else if (!a.empty() && a[0] != '-') files.push_back(a);
// unknown --flags skipped (may be Grid flags already consumed)
}
if (files.empty()) usage(argv[0]);
GridCartesian* grid = SpaceTimeGrid::makeFourDimGrid(
GridDefaultLatt(),
GridDefaultSimd(Nd, vComplexD::Nsimd()),
GridDefaultMpi());
Coordinate latt = GridDefaultLatt();
std::cout << GridLogMessage << "Lattice : "
<< latt[0]<<"x"<<latt[1]<<"x"<<latt[2]<<"x"<<latt[3] << "\n"
<< GridLogMessage << "Ntraj : " << files.size() << "\n"
<< GridLogMessage << "FFT : " << fftMode << "\n"
<< GridLogMessage << "Format : " << fmt << "\n"
<< GridLogMessage << "Output : " << pfx << "\n";
auto traj = loadFiles(files, grid, fmt);
if (fftMode == "spatial" || fftMode == "all") analyzeSpatial(traj, grid, pfx);
if (fftMode == "traj" || fftMode == "all") analyzeTraj (traj, grid, pfx);
if (fftMode == "all") analyzeAll5D (traj, grid, pfx);
Grid_finalize();
return 0;
}
/*
Add to examples/Make.inc to build:
bin_PROGRAMS += ... fft5d
fft5d_SOURCES = fft5d.cc
fft5d_LDADD = $(top_builddir)/Grid/libGrid.a
*/
+271
View File
@@ -0,0 +1,271 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: tests/core/Test_plaquette_stats.cc
Copyright (C) 2015
Author: Chulwoo Jung <chulwoo@bnl.gov>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
/**
* Test_plaquette_stats
*
* Measure every plaquette Re Tr[U_mu(x) U_nu(x+mu) U_mu†(x+nu) U_nu†(x)] / Nc
* across all sites and all (mu,nu) planes and report max, min, and average.
*
* Usage:
* ./Test_plaquette_stats [Grid options] [--file <nersc_config>] [--hot]
* [--threshold <val>]
*
* --file <path> Read gauge field from NERSC-format file
* --hot Use random (hot) SU(3) start (default: cold/unit start)
* --threshold <val> Print coordinates of every plaquette with Re Tr/Nc < val
*
* Grid size defaults to 8^3 x 16; override with --grid (e.g. --grid 4.4.4.8).
*/
#include <Grid/Grid.h>
#include <limits>
using namespace std;
using namespace Grid;
template<class T>
static void writeFile(T& in, const std::string& fname)
{
#ifdef HAVE_LIME
std::cout << GridLogMessage << "Writing: " << fname << std::endl;
Grid::emptyUserRecord record;
Grid::ScidacWriter WR(in.Grid()->IsBoss());
WR.open(fname);
WR.writeScidacFieldRecord(in, record, 0);
WR.close();
#endif
}
// Return the plane label string for output
static std::string planeName(int mu, int nu)
{
const char dirs[] = "xyzt";
std::string s;
s += dirs[mu];
s += dirs[nu];
return s;
}
int main(int argc, char** argv)
{
Grid_init(&argc, &argv);
// ---- Lattice geometry ----
Coordinate latt_size = GridDefaultLatt();
if (latt_size.size() == 0) {
latt_size = Coordinate(std::vector<int>{8, 8, 8, 16});
}
Coordinate simd_layout = GridDefaultSimd(Nd, vComplex::Nsimd());
Coordinate mpi_layout = GridDefaultMpi();
GridCartesian grid(latt_size, simd_layout, mpi_layout);
std::cout << GridLogMessage << "Lattice: ";
for (int d = 0; d < Nd; d++)
std::cout << latt_size[d] << (d < Nd-1 ? "x" : "\n");
// ---- Gauge field ----
LatticeGaugeField Umu(&grid);
// Check for --file argument
std::string config_file = "";
for (int i = 1; i < argc - 1; i++) {
if (std::string(argv[i]) == "--file") {
config_file = argv[i+1];
break;
}
}
bool doHot = false;
for (int i = 1; i < argc; i++) {
if (std::string(argv[i]) == "--hot") { doHot = true; break; }
}
double threshold = std::numeric_limits<double>::quiet_NaN();
bool doThresh = false;
for (int i = 1; i < argc - 1; i++) {
if (std::string(argv[i]) == "--threshold") {
threshold = std::stod(argv[i+1]);
doThresh = true;
break;
}
}
if (!config_file.empty()) {
std::cout << GridLogMessage << "Reading gauge field from " << config_file << std::endl;
FieldMetaData header;
NerscIO::readConfiguration(Umu, header, config_file);
} else {
std::vector<int> seeds({1, 2, 3, 4});
GridParallelRNG pRNG(&grid);
pRNG.SeedFixedIntegers(seeds);
if (doHot) {
std::cout << GridLogMessage << "Generating hot (random SU(3)) start" << std::endl;
SU<Nc>::HotConfiguration(pRNG, Umu);
} else {
std::cout << GridLogMessage << "Using cold (unit) gauge start" << std::endl;
SU<Nc>::ColdConfiguration(pRNG, Umu);
}
}
// ---- Extract link matrices ----
std::vector<LatticeColourMatrix> U(Nd, &grid);
for (int mu = 0; mu < Nd; mu++)
U[mu] = PeekIndex<LorentzIndex>(Umu, mu);
// ---- Per-plane plaquette statistics ----
//
// For each (mu, nu) plane (mu > nu) compute
// P_munu(x) = Re Tr[plaquette] / Nc
// then report max, min, mean over all sites.
//
// WilsonLoops::traceDirPlaquette gives Tr[U_mu U_nu U_mu† U_nu†] (complex).
double vol = grid.gSites();
// Accumulate site-average plaquette (sum over planes / Nplanes / Nc)
LatticeComplex plaq_all(&grid);
plaq_all = Zero();
int Nplanes = 0;
// Per-site running sum over planes (Re Tr/Nc, normalised per plane)
LatticeRealD plaq_sum(&grid);
plaq_sum = Zero();
std::cout << GridLogMessage
<< "======== Per-plane plaquette statistics ========" << std::endl;
std::cout << GridLogMessage
<< std::setw(6) << "plane"
<< std::setw(20) << "max"
<< std::setw(20) << "min"
<< std::setw(20) << "average"
<< std::endl;
for (int mu = 1; mu < Nd; mu++) {
for (int nu = 0; nu < mu; nu++) {
// Per-site trace of plaquette in (mu,nu) plane
LatticeComplex sitePlaq(&grid);
ColourWilsonLoops::traceDirPlaquette(sitePlaq, U, mu, nu);
plaq_all = plaq_all + sitePlaq;
Nplanes++;
// --- global average via sum() ---
TComplex Tsum = sum(sitePlaq);
ComplexD csum = TensorRemove(Tsum);
RealD avg = csum.real() / vol / Nc;
// --- global max and min via unvectorize + GlobalMax/GlobalMin ---
std::vector<TComplex> sv;
unvectorizeToLexOrdArray(sv, sitePlaq);
RealD local_max = -1e38, local_min = 1e38;
for (auto& tc : sv) {
RealD r = TensorRemove(tc).real() / Nc;
if (r > local_max) local_max = r;
if (r < local_min) local_min = r;
}
// Reduce across MPI ranks (no GlobalMin; negate to use GlobalMax)
grid.GlobalMax(local_max);
local_min = -local_min;
grid.GlobalMax(local_min);
local_min = -local_min;
std::cout << GridLogMessage
<< std::setw(6) << planeName(mu, nu)
<< std::setw(20) << std::setprecision(10) << local_max
<< std::setw(20) << std::setprecision(10) << local_min
<< std::setw(20) << std::setprecision(10) << avg
<< std::endl;
if (doThresh) {
std::vector<RealD> vr(sv.size(), 0.0);
for (int s = 0; s < (int)sv.size(); s++) {
RealD val = TensorRemove(sv[s]).real() / Nc;
if (val < threshold) {
Coordinate lc(Nd), gc(Nd);
Lexicographic::CoorFromIndex(lc, s, grid._ldimensions);
for (int d = 0; d < Nd; d++)
gc[d] = grid._processor_coor[d] * grid._ldimensions[d] + lc[d];
std::cerr << "BELOW_THRESHOLD plane=" << planeName(mu, nu)
// << " global=(" << grid._processor_coor[0] << "," << grid._processor_coor[1] << "," << grid._processor_coor[2] << "," << grid._processor_coor[3] << ")"
// << " dim=(" << grid._ldimensions[0] << "," << grid._ldimensions[1] << "," << grid._ldimensions[2] << "," << grid._ldimensions[3] << ")"
<< " site=(" << gc[0] << "," << gc[1] << "," << gc[2] << "," << gc[3] << ")"
<< " P=" << std::setprecision(10) << val << std::endl;
vr[s] = val;
}
}
LatticeRealD plaqRe(&grid);
vectorizeFromLexOrdArray(vr, plaqRe);
plaq_sum = plaq_sum + plaqRe;
}
}
}
// Write per-site sum of below-threshold plaquette values over all planes
if (doThresh)
writeFile(plaq_sum, "./plaq_sum");
// ---- Overall (averaged over all planes) statistics ----
// plaq_all = sum of Tr[...] over all 6 (mu,nu) planes
// Normalise to Re Tr / Nc per plane
plaq_all = plaq_all * (1.0 / Nc / Nplanes);
TComplex Tsum_all = sum(plaq_all);
RealD avg_all = TensorRemove(Tsum_all).real() / vol;
std::vector<TComplex> sv_all;
unvectorizeToLexOrdArray(sv_all, plaq_all);
RealD max_all = -1e38, min_all = 1e38;
for (auto& tc : sv_all) {
RealD r = TensorRemove(tc).real();
if (r > max_all) max_all = r;
if (r < min_all) min_all = r;
}
grid.GlobalMax(max_all);
min_all = -min_all;
grid.GlobalMax(min_all);
min_all = -min_all;
// Cross-check with built-in avgPlaquette
RealD avg_builtin = ColourWilsonLoops::avgPlaquette(Umu);
std::cout << GridLogMessage
<< "======== Overall plaquette statistics (all planes) ========" << std::endl;
std::cout << GridLogMessage << " max = " << max_all << std::endl;
std::cout << GridLogMessage << " min = " << min_all << std::endl;
std::cout << GridLogMessage << " average = " << avg_all << std::endl;
std::cout << GridLogMessage << " avgPlaquette (builtin check) = " << avg_builtin << std::endl;
Grid_finalize();
return 0;
}
+11 -7
View File
@@ -1,14 +1,18 @@
<?xml version="1.0"?>
<grid>
<LanczosParameters>
<mass>0.00107</mass>
<mass>0</mass>
<mstep>-0.025</mstep>
<M5>1.8</M5>
<Ls>48</Ls>
<Nstop>10</Nstop>
<Nk>15</Nk>
<Np>85</Np>
<ChebyLow>0.003</ChebyLow>
<ChebyHigh>60</ChebyHigh>
<ChebyOrder>201</ChebyOrder>
<Nstop>5</Nstop>
<Nk>5</Nk>
<Np>5</Np>
<ReadEvec>0</ReadEvec>
<maxIter>10000</maxIter>
<resid>1e-10</resid>
<ChebyLow>1</ChebyLow>
<ChebyHigh>100</ChebyHigh>
<ChebyOrder>51</ChebyOrder>
</LanczosParameters>
</grid>
@@ -0,0 +1,99 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Test for BlockedKrylovSchur: verifies the KS decomposition A V = V H + F B^dag
by explicit operator applies, before and after each restart.
Uses DumbOperator (diagonal, real, Hermitian) from Test_synthetic_lanczos.
Tests Nblock=1 (scalar, regression) and Nblock=2,3 (exercises the B^H fix).
*************************************************************************************/
#include <Grid/Grid.h>
using namespace std;
using namespace Grid;
// Diagonal real Hermitian operator (eigenvalues = scale lattice sites)
template<class Field>
class DumbOperator : public LinearOperatorBase<Field> {
public:
LatticeComplex scale;
DumbOperator(GridBase* grid) : scale(grid) {
GridParallelRNG pRNG(grid);
std::vector<int> seeds({5,6,7,8});
pRNG.SeedFixedIntegers(seeds);
random(pRNG, scale);
scale = exp(-Grid::real(scale) * 3.0);
}
void OpDirAll(const Field& in, std::vector<Field>& out) {}
void OpDiag(const Field& in, Field& out) {}
void OpDir(const Field& in, Field& out, int dir, int disp) {}
void Op(const Field& in, Field& out) { out = scale * in; }
void AdjOp(const Field& in, Field& out) { out = scale * in; }
void HermOp(const Field& in, Field& out) { out = scale * in; }
void HermOpAndNorm(const Field& in, Field& out, double& n1, double& n2) {
out = scale * in;
ComplexD d = innerProduct(in, out); n1 = real(d);
d = innerProduct(out, out); n2 = real(d);
}
};
int main(int argc, char** argv)
{
Grid_init(&argc, &argv);
GridCartesian* grid = SpaceTimeGrid::makeFourDimGrid(
GridDefaultLatt(),
GridDefaultSimd(Nd, vComplex::Nsimd()),
GridDefaultMpi());
GridParallelRNG RNG(grid);
RNG.SeedFixedIntegers({1,2,3,4});
typedef LatticeComplex Field;
DumbOperator<Field> op(grid);
int nFail = 0;
//--------------------------------------------------------------------
// Helper lambda: run BKS with doVerify and check it doesn't crash
//--------------------------------------------------------------------
auto runTest = [&](const std::string& label, int Nblock, int Nm, int Nk,
int maxIter, int Nstop) {
std::cout << GridLogMessage << "===== " << label << " =====" << std::endl;
BlockKrylovSchur<Field> bks(op, grid, 1e-6, EvalReSmall);
std::vector<Field> v0(Nblock, Field(grid));
for (int t = 0; t < Nblock; t++) random(RNG, v0[t]);
bks(v0, maxIter, Nm, Nk, Nstop, Nblock,
/*doubleOrthog=*/true, /*doVerify=*/true);
std::cout << GridLogMessage << label << " done." << std::endl;
};
// Test 1: Nblock=1 — scalar case, regression (Nm,Nk now total vectors)
runTest("Nblock=1 Nm=10 Nk=5 maxIter=3", 1, 10, 5, 3, 5);
// Test 2: Nblock=2 — exercises the B^H fix for off-diagonal elements
runTest("Nblock=2 Nm=16 Nk=8 maxIter=3", 2, 16, 8, 3, 4);
// Test 3: Nblock=3 — further stress-test the B^H fix
runTest("Nblock=3 Nm=27 Nk=9 maxIter=3", 3, 27, 9, 3, 3);
// Test 4: Nblock=2, larger cycle — more restarts
runTest("Nblock=2 Nm=24 Nk=12 maxIter=5", 2, 24, 12, 5, 6);
if (nFail == 0)
std::cout << GridLogMessage << "All BlockedKrylovSchur tests completed." << std::endl;
Grid_finalize();
return nFail;
}
+10 -4
View File
@@ -32,9 +32,13 @@ directory
using namespace std;
using namespace Grid;
//typedef WilsonFermionD FermionOp;
#if 0
typedef DomainWallFermionD FermionOp;
typedef typename DomainWallFermionD::FermionField FermionField;
#else
typedef MobiusFermionD FermionOp;
typedef typename MobiusFermionD::FermionField FermionField;
#endif
template <class T> void writeFile(T& in, std::string const fname){
#ifdef HAVE_LIME
@@ -179,12 +183,14 @@ int main(int argc, char** argv) {
Np=LanParams.Np;
int Nm = Nk + Np;
int MaxIt = 100;
RealD resid = 1.0e-4;
int MaxIt = 10000;
RealD resid = 1.0e-5;
RealD mob_b=1.5;
//while ( mass > - 5.0){
FermionOp Ddwf(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,mass,M5);
// FermionOp Ddwf(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,mass,M5);
FermionOp Ddwf(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,mass,M5,mob_b,mob_b-1.);
MdagMLinearOperator<FermionOp,FermionField> HermOp(Ddwf); /// <-----
// Gamma5HermitianLinearOperator <FermionOp,LatticeFermion> HermOp2(WilsonOperator); /// <-----
Gamma5R5HermitianLinearOperator<FermionOp, LatticeFermion> G5R5Herm(Ddwf);
@@ -0,0 +1,89 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Test for Gamma5BlockLanczos on a simple diagonal operator.
The operator is D = diag(scale_i) where scale is complex random.
γ5 is taken to be the identity (scalar field has no spin structure),
so the γ5-inner product reduces to the standard Euclidean inner product
and the algorithm should find the eigenvalues of D directly.
For a genuine Wilson Dirac test, pass the actual γ5 functor.
*************************************************************************************/
#include <Grid/Grid.h>
using namespace std;
using namespace Grid;
// Diagonal complex operator: out = scale * in
template<class Field>
class DumbOperator : public LinearOperatorBase<Field> {
public:
LatticeComplex scale;
DumbOperator(GridBase* grid) : scale(grid) {
GridParallelRNG pRNG(grid);
pRNG.SeedFixedIntegers({5,6,7,8});
random(pRNG, scale);
scale = exp(-Grid::real(scale) * 3.0);
}
void OpDirAll(const Field& in, std::vector<Field>& out) {}
void OpDiag(const Field& in, Field& out) {}
void OpDir(const Field& in, Field& out, int dir, int disp) {}
void Op(const Field& in, Field& out) { out = scale * in; }
void AdjOp(const Field& in, Field& out) { out = scale * in; }
void HermOp(const Field& in, Field& out) { out = scale * in; }
void HermOpAndNorm(const Field& in, Field& out, double& n1, double& n2) {
out = scale * in;
ComplexD d = innerProduct(in, out); n1 = real(d);
d = innerProduct(out, out); n2 = real(d);
}
};
int main(int argc, char** argv)
{
Grid_init(&argc, &argv);
GridCartesian* grid = SpaceTimeGrid::makeFourDimGrid(
GridDefaultLatt(),
GridDefaultSimd(Nd, vComplex::Nsimd()),
GridDefaultMpi());
GridParallelRNG RNG(grid);
RNG.SeedFixedIntegers({1,2,3,4});
typedef LatticeComplex Field;
DumbOperator<Field> op(grid);
// For LatticeComplex (scalar field) γ5 = identity
auto gamma5 = [](const Field& in, Field& out){ out = in; };
Field v0(grid);
random(RNG, v0);
const int maxSteps = 20;
const int Nstop = 4;
const RealD tol = 1e-6;
std::cout << GridLogMessage
<< "\n========================================" << std::endl;
std::cout << GridLogMessage
<< " Gamma5BlockLanczos (maxSteps=" << maxSteps
<< " Nstop=" << Nstop << ")" << std::endl;
std::cout << GridLogMessage
<< "========================================\n" << std::endl;
Gamma5BlockLanczos<Field> g5bl(op, grid, gamma5, tol);
g5bl.doEvalCheck = true;
g5bl(v0, maxSteps, Nstop, /*reorthog=*/true);
auto evals = g5bl.getEvals();
std::cout << GridLogMessage
<< "Gamma5BlockLanczos eigenvalues (" << evals.size() << "):" << std::endl;
for (int k = 0; k < (int)evals.size(); k++)
std::cout << GridLogMessage << " [" << k << "] " << evals(k) << std::endl;
Grid_finalize();
return 0;
}
@@ -0,0 +1,154 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Comparison test: HarmonicBlockedKrylovSchur vs BlockedKrylovSchur.
Both algorithms are run on the same diagonal Hermitian operator and the
resulting eigenvalues are compared. doVerify=true is used so the KS
decomposition check max|H-M| and the per-column residuals are printed
at each step. For BKS these should be O(machine epsilon) at all times.
For HBKS they should be O(machine epsilon) AFTER Arnoldi, but may show
large per-column deviations AFTER restart+truncation (because the rotation
Q from Schur(Hhat) does not give an upper-triangular H_new, so the
truncated KS relation is only approximate).
*************************************************************************************/
#include <Grid/Grid.h>
using namespace std;
using namespace Grid;
// Diagonal real Hermitian operator out = scale * in
template<class Field>
class DumbOperator : public LinearOperatorBase<Field> {
public:
LatticeComplex scale;
DumbOperator(GridBase* grid) : scale(grid) {
GridParallelRNG pRNG(grid);
pRNG.SeedFixedIntegers({5,6,7,8});
random(pRNG, scale);
scale = exp(-Grid::real(scale) * 3.0);
}
void OpDirAll(const Field& in, std::vector<Field>& out) {}
void OpDiag(const Field& in, Field& out) {}
void OpDir(const Field& in, Field& out, int dir, int disp) {}
void Op(const Field& in, Field& out) { out = scale * in; }
void AdjOp(const Field& in, Field& out) { out = scale * in; }
void HermOp(const Field& in, Field& out) { out = scale * in; }
void HermOpAndNorm(const Field& in, Field& out, double& n1, double& n2) {
out = scale * in;
ComplexD d = innerProduct(in, out); n1 = real(d);
d = innerProduct(out, out); n2 = real(d);
}
};
int main(int argc, char** argv)
{
Grid_init(&argc, &argv);
GridCartesian* grid = SpaceTimeGrid::makeFourDimGrid(
GridDefaultLatt(),
GridDefaultSimd(Nd, vComplex::Nsimd()),
GridDefaultMpi());
GridParallelRNG RNG(grid);
RNG.SeedFixedIntegers({1,2,3,4});
typedef LatticeComplex Field;
DumbOperator<Field> op(grid);
//----------------------------------------------------------------------
// Parameters (kept small so output is readable)
//----------------------------------------------------------------------
const int Nblock = 4;
const int Nm = 20; // total vectors (= 5 blocks * Nblock=4)
const int Nk = 8; // total kept (= 2 blocks * Nblock=4)
const int Nstop = 4;
const int maxIter = 8;
const RealD tol = 1e-6;
// Two identical starting blocks
std::vector<Field> v0(Nblock, Field(grid));
std::vector<Field> v0b(Nblock, Field(grid));
for (int t = 0; t < Nblock; t++) {
random(RNG, v0[t]);
v0b[t] = v0[t]; // identical start for fair comparison
}
//----------------------------------------------------------------------
// Run BlockedKrylovSchur with doVerify=true
//----------------------------------------------------------------------
std::cout << GridLogMessage
<< "\n========================================" << std::endl;
std::cout << GridLogMessage
<< " BlockKrylovSchur (Nblock=" << Nblock
<< " Nm=" << Nm << " Nk=" << Nk << ")" << std::endl;
std::cout << GridLogMessage
<< "========================================\n" << std::endl;
BlockKrylovSchur<Field> bks(op, grid, tol, EvalImNormSmall);
bks(v0, maxIter, Nm, Nk, Nstop, Nblock,
/*doubleOrthog=*/true, /*doVerify=*/true);
auto bks_evals = bks.getEvals();
std::cout << GridLogMessage
<< "BKS eigenvalues (" << bks_evals.size() << "):" << std::endl;
for (int k = 0; k < (int)bks_evals.size(); k++)
std::cout << GridLogMessage << " [" << k << "] " << bks_evals[k] << std::endl;
//----------------------------------------------------------------------
// Run HarmonicBlockedKrylovSchur with doVerify=true
//----------------------------------------------------------------------
std::cout << GridLogMessage
<< "\n========================================" << std::endl;
std::cout << GridLogMessage
<< " HarmonicBlockKrylovSchur (Nblock=" << Nblock
<< " Nm=" << Nm << " Nk=" << Nk << " shift=0)" << std::endl;
std::cout << GridLogMessage
<< "========================================\n" << std::endl;
HarmonicBlockKrylovSchur<Field> hbks(op, grid, tol, 0.0, EvalImNormSmall);
hbks(v0b, maxIter, Nm, Nk, Nstop, Nblock,
/*doubleOrthog=*/true, /*doVerify=*/true);
auto hbks_evals = hbks.getEvals();
std::cout << GridLogMessage
<< "HBKS eigenvalues (" << hbks_evals.size() << "):" << std::endl;
for (int k = 0; k < (int)hbks_evals.size(); k++)
std::cout << GridLogMessage << " [" << k << "] " << hbks_evals[k] << std::endl;
//----------------------------------------------------------------------
// Compare
//----------------------------------------------------------------------
std::cout << GridLogMessage
<< "\n========================================" << std::endl;
std::cout << GridLogMessage << " Eigenvalue comparison" << std::endl;
std::cout << GridLogMessage
<< "========================================" << std::endl;
// Sort both sets by real part for comparison
std::vector<ComplexD> bvec(bks_evals.data(),
bks_evals.data() + bks_evals.size());
std::vector<ComplexD> hvec(hbks_evals.data(),
hbks_evals.data() + hbks_evals.size());
auto cmpRe = [](const ComplexD& a, const ComplexD& b){ return a.real() < b.real(); };
std::sort(bvec.begin(), bvec.end(), cmpRe);
std::sort(hvec.begin(), hvec.end(), cmpRe);
int nCmp = std::min(bvec.size(), hvec.size());
double maxDiff = 0.0;
for (int k = 0; k < nCmp; k++) {
double diff = std::abs(bvec[k].real() - hvec[k].real()) + std::abs(bvec[k].imag() - hvec[k].imag());
maxDiff = std::max(maxDiff, diff);
std::cout << GridLogMessage
<< " k=" << k
<< " BKS=" << bvec[k]
<< " HBKS=" << hvec[k]
<< " |diff|=" << diff << std::endl;
}
std::cout << GridLogMessage << " max |BKS - HBKS| = " << maxDiff << std::endl;
Grid_finalize();
return 0;
}
+8 -2
View File
@@ -113,6 +113,9 @@ struct LanczosParameters: Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(LanczosParameters,
RealD, mass ,
RealD, resid,
Integer, Nstop,
Integer, Nk,
Integer, Np,
RealD, ChebyLow,
RealD, ChebyHigh,
Integer, ChebyOrder)
@@ -204,7 +207,6 @@ int main(int argc, char** argv) {
int Nstop = 5;
int Nk = 10;
int Np = 90;
int Nm = Nk + Np;
int MaxIt = 10000;
RealD resid = 1.0e-5;
@@ -226,10 +228,14 @@ int main(int argc, char** argv) {
XmlWriter HMCwr("LanParams.xml.out");
write(HMCwr,"LanczosParameters",LanParams);
}
Nstop=LanParams.Nstop;
Nk=LanParams.Nk;
Np=LanParams.Np;
mass=LanParams.mass;
resid=LanParams.resid;
int Nm = Nk + Np;
while ( mass > - 5.0){
FermionOp WilsonOperator(Umu,*FGrid,*FrbGrid,2.+mass);
+377
View File
@@ -0,0 +1,377 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./tests/lanczos/Test_wilson_bilanczos.cc
Copyright (C) 2025
Author: Chulwoo Jung <chulwoo@bnl.gov>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include <cstdlib>
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidual.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
#include <Grid/algorithms/iterative/BiCGSTAB.h>
using namespace std;
using namespace Grid;
namespace Grid {
struct LanczosParameters: Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(LanczosParameters,
RealD, mass ,
RealD, mstep ,
Integer, Nstop,
Integer, Nk,
Integer, Np,
Integer, ReadEvec,
Integer, maxIter,
RealD, resid,
RealD, ChebyLow,
RealD, ChebyHigh,
Integer, ChebyOrder)
LanczosParameters() {
////////////////////////////// Default values
mass = 0;
/////////////////////////////////
}
template <class ReaderClass >
LanczosParameters(Reader<ReaderClass> & TheReader){
initialize(TheReader);
}
template < class ReaderClass >
void initialize(Reader<ReaderClass> &TheReader){
// std::cout << GridLogMessage << "Reading HMC\n";
read(TheReader, "HMC", *this);
}
void print_parameters() const {
// std::cout << GridLogMessage << "[HMC parameters] Trajectories : " << Trajectories << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Start trajectory : " << StartTrajectory << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Metropolis test (on/off): " << std::boolalpha << MetropolisTest << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Thermalization trajs : " << NoMetropolisUntil << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Starting type : " << StartingType << "\n";
// MD.print_parameters();
}
};
}
template <class T> void writeFile(T& in, std::string const fname){
#if 1
// Ref: https://github.com/paboyle/Grid/blob/feature/scidac-wp1/tests/debug/Test_general_coarse_hdcg_phys48.cc#L111
std::cout << Grid::GridLogMessage << "Writes to: " << fname << std::endl;
Grid::emptyUserRecord record;
Grid::ScidacWriter WR(in.Grid()->IsBoss());
WR.open(fname);
WR.writeScidacFieldRecord(in,record,0);
WR.close();
#endif
// What is the appropriate way to throw error?
}
typedef WilsonFermionD WilsonOp;
typedef typename WilsonFermionD::FermionField FermionField;
template<class Matrix,class Field>
class InvertNonHermitianLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
RealD _stp;
public:
InvertNonHermitianLinearOperator(Matrix &Mat,RealD stp=1e-8): _Mat(Mat),_stp(stp){};
// Support for coarsening to a multigrid
void OpDiag (const Field &in, Field &out) {
// _Mat.Mdiag(in,out);
// out = out + shift*in;
assert(0);
}
void OpDir (const Field &in, Field &out,int dir,int disp) {
// _Mat.Mdir(in,out,dir,disp);
assert(0);
}
void OpDirAll (const Field &in, std::vector<Field> &out){
// _Mat.MdirAll(in,out);
assert(0);
};
void Op (const Field &in, Field &out){
Field tmp(in.Grid());
_Mat.Mdag(in,tmp);
MdagMLinearOperator<Matrix,Field> HermOp(_Mat);
ConjugateGradient<Field> CG(_stp,10000);
CG(HermOp,tmp,out);
}
void AdjOp (const Field &in, Field &out){
_Mat.Mdag(in,out);
// out = out + shift * in;
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){
assert(0);
}
void HermOp(const Field &in, Field &out){
assert(0);
}
};
template<class Field>
void testSchurFromHess(Arnoldi<Field>& Arn, Field& src, int Nlarge, int Nm, int Nk) {
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout << GridLogMessage << "Testing Schur reordering, Nm = " << Nm << ", Nk = " << Nk << std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout << GridLogMessage << "Running Arnoldi for 1 iteration to get a Hessenberg." << std::endl;
Arn(src, 1, Nlarge, Nm, Nlarge);
Eigen::MatrixXcd Hess = Arn.getHessenbergMat();
std::cout << GridLogMessage << "Hessenberg for use: " << std::endl << Hess << std::endl;
ComplexSchurDecomposition schur (Hess, true);
bool isDecomposed = schur.checkDecomposition();
std::cout << "Schur decomp holds? " << isDecomposed << std::endl;
std::cout << GridLogMessage << "S = " << std::endl << schur.getMatrixS() << std::endl;
std::cout << GridLogMessage << "Swapping S(3, 3) with S(4, 4)" << std::endl;
schur.swapEvals(3);
std::cout << GridLogMessage << "S after swap = " << std::endl << schur.getMatrixS() << std::endl;
std::cout << "Schur decomp still holds? " << schur.checkDecomposition() << std::endl;
// Now move last diagonal element all the way to the front.
std::cout << GridLogMessage << "Moving last eval to front. S at start = " << std::endl << schur.getMatrixS() << std::endl;
for (int i = 0; i < Nk - 1; i++) {
int swapIdx = Nk - 2 - i;
schur.swapEvals(swapIdx);
std::cout << GridLogMessage << "S after swap of index " << swapIdx << " = " << std::endl << schur.getMatrixS() << std::endl;
std::cout << "Schur decomp still holds? " << schur.checkDecomposition() << std::endl;
}
std::cout << GridLogMessage << "Testing Schur reorder" << std::endl;
schur.schurReorder(Nk);
std::cout << GridLogMessage << "S after reorder = " << std::endl << schur.getMatrixS() << std::endl;
std::cout << "Schur decomp still holds? " << schur.checkDecomposition() << std::endl;
}
int main (int argc, char ** argv)
{
Grid_init(&argc,&argv);
const int Ls=16;
// GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(), GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());
// std::vector<int> lat_size {32, 32, 32, 32};
// std::cout << "Lattice size: " << lat_size << std::endl;
GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(),
GridDefaultSimd(Nd,vComplex::Nsimd()),
GridDefaultMpi());
GridRedBlackCartesian * UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid);
// GridCartesian * FGrid = SpaceTimeGrid::makeFiveDimGrid(Ls,UGrid);
// GridRedBlackCartesian * FrbGrid = SpaceTimeGrid::makeFiveDimRedBlackGrid(Ls,UGrid);
GridCartesian * FGrid = UGrid;
GridRedBlackCartesian * FrbGrid = UrbGrid;
// Construct a coarsened grid
// poare TODO: replace this with the following line?
Coordinate clatt = GridDefaultLatt();
// Coordinate clatt = GridDefaultLatt(); // [PO] initial line before I edited it
for(int d=0;d<clatt.size();d++){
std::cout << GridLogMessage<< clatt[d] <<std::endl;
clatt[d] = clatt[d]/2;
// clatt[d] = clatt[d]/4;
}
GridCartesian *Coarse4d = SpaceTimeGrid::makeFourDimGrid(clatt, GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());;
GridCartesian *Coarse5d = SpaceTimeGrid::makeFiveDimGrid(1,Coarse4d);
std::vector<int> seeds4({1,2,3,4});
std::vector<int> seeds5({5,6,7,8});
std::vector<int> cseeds({5,6,7,8});
GridParallelRNG RNG5(FGrid); RNG5.SeedFixedIntegers(seeds5);
GridParallelRNG RNG4(UGrid); RNG4.SeedFixedIntegers(seeds4);
GridParallelRNG CRNG(Coarse5d);CRNG.SeedFixedIntegers(cseeds);
LatticeFermion result(FGrid); result=Zero();
LatticeFermion ref(FGrid); ref=Zero();
LatticeFermion tmp(FGrid);
LatticeFermion err(FGrid);
LatticeGaugeField Umu(UGrid);
FieldMetaData header;
std::string file("config");
// std::string file("Users/patrickoare/libraries/PETSc-Grid/ckpoint_lat.4000");
NerscIO::readConfiguration(Umu,header,file);
LanczosParameters LanParams;
{
XmlReader HMCrd("LanParams.xml");
read(HMCrd,"LanczosParameters",LanParams);
}
std::cout << GridLogMessage<< LanParams <<std::endl;
{
XmlWriter HMCwr("LanParams.xml.out");
write(HMCwr,"LanczosParameters",LanParams);
}
RealD mass=0.01;
RealD M5=1.8;
// PowerMethod<LatticeFermion> PM; PM(PVdagM, src);
int Nm = 50;
int Nk = 12;
int Np = 38;
// int Nk = Nm+1; // if just running once
int maxIter = 10000;
int Nstop = 10;
RealD resid = 1.0e-5;
std::vector<Complex> boundary = {1,1,1,-1};
WilsonOp::ImplParams Params(boundary);
// DomainWallFermionD Ddwf(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,mass,M5);
// DomainWallFermionD Dpv(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,1.0,M5);
mass=LanParams.mass;
std::cout << GridLogIRL<< "mass "<<mass<<std::endl;
WilsonOp WilsonOperator(Umu,*UGrid,*UrbGrid,mass,Params);
// const int nbasis = 20; // size of approximate basis for low-mode space
const int nbasis = 3; // size of approximate basis for low-mode space
const int cb = 0 ;
LatticeFermion prom(FGrid);
typedef GeneralCoarsenedMatrix<vSpinColourVector,vTComplex,nbasis> LittleDiracOperator;
typedef LittleDiracOperator::CoarseVector CoarseVector;
NextToNearestStencilGeometry5D geom(Coarse5d);
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
// typedef PVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> PVdagM_t;
// typedef ShiftedPVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> ShiftedPVdagM_t;
// typedef ShiftedComplexPVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> ShiftedComplexPVdagM_t;
// PVdagM_t PVdagM(Ddwf, Dpv);
// ShiftedPVdagM_t ShiftedPVdagM(0.1,Ddwf,Dpv);
// SquaredLinearOperator<DomainWallFermionD, LatticeFermionD> Dsq (Ddwf);
// NonHermitianLinearOperator<DomainWallFermionD, LatticeFermionD> DLinOp (Ddwf);
NonHermitianLinearOperator<WilsonOp,FermionField> Dwilson(WilsonOperator); /// <-----
// InvertNonHermitianLinearOperator<WilsonOp,FermionField> Iwilson(WilsonOperator); /// <-----
MdagMLinearOperator<WilsonOp,FermionField> HermOp(WilsonOperator); /// <-----
Gamma5HermitianLinearOperator <WilsonOp,LatticeFermion> HermOp2(WilsonOperator); /// <----
// PowerMethod<LatticeFermion> PM; PM(PVdagM, src);
resid=LanParams.resid;
Nstop=LanParams.Nstop;
Nk=LanParams.Nk;
Np=LanParams.Np;
maxIter=LanParams.maxIter;
Nm = Nk + Np;
int Nu=16;
std::vector<LatticeFermion> src(Nu,FGrid);
for(int i=0;i<Nu;i++) random(RNG5,src[i]);
if(LanParams.ReadEvec) {
std::string evecs_file="evec_in";
std::cout << GridLogIRL<< "Reading evecs from "<<evecs_file<<std::endl;
emptyUserRecord record;
Grid::ScidacReader RD;
RD.open(evecs_file);
RD.readScidacFieldRecord(src[0],record);
RD.close();
}
Coordinate origin ({0,0,0,0});
auto tmpSrc = peekSite(src[0], origin);
std::cout << "[DEBUG] Source at origin = " << tmpSrc << std::endl;
LatticeFermion src2 = src[0];
// Run KrylovSchur and Arnoldi on a Hermitian matrix
std::cout << GridLogMessage << "Running Krylov Schur" << std::endl;
#if 0
#if 1
RealD shift=1.5;
KrylovSchur KrySchur (Dwilson, UGrid, resid,EvalImNormSmall);
KrySchur(src[0], maxIter, Nm, Nk, Nstop,&shift);
#else
KrylovSchur KrySchur (Iwilson, UGrid, resid,EvalImNormSmall);
KrySchur(src[0], maxIter, Nm, Nk, Nstop);
#endif
std::cout << GridLogMessage << "evec.size= " << KrySchur.evecs.size()<< std::endl;
#else
LanczosBidiagonalization<FermionField> LB(Dwilson, UGrid);
LB.run(src[0], Nm, resid);
RestartedLanczosBidiagonalization<FermionField> IRLBA(Dwilson, UGrid, Nstop, Nm, resid, maxIter,false);
IRLBA.run(src[0]);
#endif
#if 0
src[0]=KrySchur.evecs[0];
for (int i=1;i<Nstop;i++) src[0]+=KrySchur.evecs[i];
for (int i=0;i<Nstop;i++)
{
std::string evfile ("./evec_"+std::to_string(mass)+"_"+std::to_string(i));
auto evdensity = localInnerProduct(KrySchur.evecs[i],KrySchur.evecs[i] );
writeFile(evdensity,evfile);
}
{
std::string evfile ("./evec_"+std::to_string(mass)+"_sum");
// auto evdensity = localInnerProduct(evec[i],evec[i] );
writeFile(src[0],evfile);
}
#endif
/*
std::cout << GridLogMessage << "Running Arnoldi" << std::endl;
// Arnoldi Arn (Dsq, FGrid, 1e-8);
Arnoldi Arn (DLinOp, FGrid, 1e-8);
testSchurFromHess<LatticeFermion>(Arn, src, 10, 6, 4);
Arnoldi Arn2 (DLinOp, FGrid, 1e-8);
testSchurFromHess<LatticeFermion>(Arn2, src, 16, 12, 8);
*/
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage << "Done "<< std::endl;
Grid_finalize();
return 0;
}
+229 -23
View File
@@ -6,7 +6,7 @@ Source file: ./tests/Test_dwf_lanczos.cc
Copyright (C) 2015
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: Chulwoo Jung <chulwoo@bnl.gov>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -27,6 +27,9 @@ directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include <Grid/parallelIO/IldgIOtypes.h>
#include <Grid/algorithms/iterative/ImplicitlyRestartedBlockLanczos.h>
using namespace std;
using namespace Grid;
@@ -38,18 +41,111 @@ typedef typename WilsonFermionD::FermionField FermionField;
RealD AllZero(RealD x) { return 0.; }
template <class T> void writeFile(T& in, std::string const fname){
#if 1
// Ref: https://github.com/paboyle/Grid/blob/feature/scidac-wp1/tests/debug/Test_general_coarse_hdcg_phys48.cc#L111
std::cout << Grid::GridLogMessage << "Writes to: " << fname << std::endl;
Grid::emptyUserRecord record;
Grid::ScidacWriter WR(in.Grid()->IsBoss());
WR.open(fname);
WR.writeScidacFieldRecord(in,record,0);
WR.close();
#endif
// What is the appropriate way to throw error?
}
namespace Grid {
struct LanczosParameters: Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(LanczosParameters,
RealD, mass ,
RealD, mstep ,
Integer, Nstop,
Integer, Nk,
Integer, Np,
Integer, ReadEvec,
RealD, resid,
RealD, ChebyLow,
RealD, ChebyHigh,
Integer, ChebyOrder)
// Integer, StartTrajectory,
// Integer, Trajectories, /* @brief Number of sweeps in this run */
// bool, MetropolisTest,
// Integer, NoMetropolisUntil,
// std::string, StartingType,
// Integer, SW,
// RealD, Kappa,
// IntegratorParameters, MD)
LanczosParameters() {
////////////////////////////// Default values
mass = 0;
// MetropolisTest = true;
// NoMetropolisUntil = 10;
// StartTrajectory = 0;
// SW = 2;
// Trajectories = 10;
// StartingType = "HotStart";
/////////////////////////////////
}
template <class ReaderClass >
LanczosParameters(Reader<ReaderClass> & TheReader){
initialize(TheReader);
}
template < class ReaderClass >
void initialize(Reader<ReaderClass> &TheReader){
// std::cout << GridLogMessage << "Reading HMC\n";
read(TheReader, "HMC", *this);
}
void print_parameters() const {
// std::cout << GridLogMessage << "[HMC parameters] Trajectories : " << Trajectories << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Start trajectory : " << StartTrajectory << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Metropolis test (on/off): " << std::boolalpha << MetropolisTest << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Thermalization trajs : " << NoMetropolisUntil << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Starting type : " << StartingType << "\n";
// MD.print_parameters();
}
};
}
int main(int argc, char** argv) {
Grid_init(&argc, &argv);
int Ndir=4;
auto mpi_layout = GridDefaultMpi();
std::vector<int> nblock(4,1);
std::vector<int> mpi_split(4,1);
//Interested in avoiding degeneracy only for now
nblock[3]=2;
int mrhs=1;
for(int i =0;i<Ndir;i++){
mpi_split[i] = mpi_layout[i] / nblock[i];
mrhs *= nblock[i];
}
GridCartesian* UGrid = SpaceTimeGrid::makeFourDimGrid(
GridDefaultLatt(), GridDefaultSimd(Nd, vComplex::Nsimd()),
GridDefaultMpi());
GridRedBlackCartesian* UrbGrid =
SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid);
GridCartesian * SGrid = new GridCartesian(GridDefaultLatt(),
GridDefaultSimd(Nd,vComplex::Nsimd()),
mpi_split,
*UGrid);
GridRedBlackCartesian* UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid);
GridCartesian* FGrid = UGrid;
GridRedBlackCartesian* FrbGrid = UrbGrid;
printf("UGrid=%p UrbGrid=%p FGrid=%p FrbGrid=%p\n", UGrid, UrbGrid, FGrid,
FrbGrid);
// printf("UGrid=%p UrbGrid=%p FGrid=%p FrbGrid=%p\n", UGrid, UrbGrid, FGrid, FrbGrid);
std::vector<int> seeds4({1, 2, 3, 4});
std::vector<int> seeds5({5, 6, 7, 8});
@@ -61,7 +157,16 @@ int main(int argc, char** argv) {
RNG5.SeedFixedIntegers(seeds5);
LatticeGaugeField Umu(UGrid);
SU<Nc>::HotConfiguration(RNG4, Umu);
// SU<Nc>::HotConfiguration(RNG4, Umu);
// SU<Nc>::ColdConfiguration(Umu);
FieldMetaData header;
std::string file("./config");
// int precision32 = 0;
// int tworow = 0;
// NerscIO::writeConfiguration(Umu,file,tworow,precision32);
NerscIO::readConfiguration(Umu,header,file);
/*
std::vector<LatticeColourMatrix> U(4, UGrid);
@@ -70,30 +175,100 @@ int main(int argc, char** argv) {
}
*/
RealD mass = -0.1;
FermionOp WilsonOperator(Umu,*FGrid,*FrbGrid,mass);
MdagMLinearOperator<FermionOp,LatticeFermion> HermOp(WilsonOperator); /// <-----
//SchurDiagTwoOperator<FermionOp,FermionField> HermOp(WilsonOperator);
int Nstop = 10;
int Nu = 1;
int Nk = 20;
int Np = 80;
int Nm = Nk + Np;
int MaxIt = 10000;
RealD resid = 1.0e-5;
const int Nstop = 20;
const int Nk = 60;
const int Np = 60;
const int Nm = Nk + Np;
const int MaxIt = 10000;
RealD resid = 1.0e-6;
RealD mass = -1.0;
LanczosParameters LanParams;
#if 1
{
XmlReader HMCrd("LanParams.xml");
read(HMCrd,"LanczosParameters",LanParams);
}
#else
{
LanParams.mass = mass;
}
#endif
std::cout << GridLogMessage<< LanParams <<std::endl;
{
XmlWriter HMCwr("LanParams.xml.out");
write(HMCwr,"LanczosParameters",LanParams);
}
mass=LanParams.mass;
resid=LanParams.resid;
Nstop=LanParams.Nstop;
Nu = mrhs;
Nk=LanParams.Nk;
Np=LanParams.Np;
Nm = Nk + Np;
// FermionField src(FGrid);
std::vector<FermionField> src(Nu,FGrid);
for(int i =0;i<Nu;i++) gaussian(RNG5, src[i]);
if(LanParams.ReadEvec) {
std::string evecs_file="evec_in";
std::cout << GridLogIRL<< "Reading evecs from "<<evecs_file<<std::endl;
emptyUserRecord record;
Grid::ScidacReader RD;
RD.open(evecs_file);
RD.readScidacFieldRecord(src[0],record);
RD.close();
}
std::vector<Complex> boundary = {1,1,1,-1};
// std::vector<Complex> boundary = {1,1,1,1};
FermionOp::ImplParams Params(boundary);
GridCartesian * SFGrid = SGrid;
GridRedBlackCartesian * SFrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(SFGrid);
// GridRedBlackCartesian * SFrbGrid = SpaceTimeGrid::makeFiveDimRedBlackGrid(JP.Ls,SGrid);
LatticeGaugeField s_Umu(SGrid);
Grid_split (Umu,s_Umu);
while ( mass > - 2.0){
FermionOp WilsonOperator(Umu,*FGrid,*FrbGrid,mass,Params);
MdagMLinearOperator<FermionOp,FermionField> HermOp(WilsonOperator); /// <-----
FermionOp WilsonSplit(s_Umu,*SFGrid,*SFrbGrid,mass,Params);
MdagMLinearOperator<FermionOp,FermionField> SHermOp(WilsonSplit); /// <-----
//SchurDiagTwoOperator<FermionOp,FermionField> HermOp(WilsonOperator);
Gamma5HermitianLinearOperator <FermionOp,LatticeFermion> HermOp2(WilsonOperator); /// <-----
std::vector<double> Coeffs{0, 1.};
Polynomial<FermionField> PolyX(Coeffs);
Chebyshev<FermionField> Cheby(0.0, 10., 12);
// Chebyshev<FermionField> Cheby(0.5, 60., 31);
// RealD, ChebyLow,
// RealD, ChebyHigh,
// Integer, ChebyOrder)
Chebyshev<FermionField> Cheby(LanParams.ChebyLow,LanParams.ChebyHigh,LanParams.ChebyOrder);
FunctionHermOp<FermionField> OpCheby(Cheby,HermOp);
PlainHermOp<FermionField> Op (HermOp);
PlainHermOp<FermionField> Op2 (HermOp2);
ImplicitlyRestartedLanczos<FermionField> IRL(OpCheby, Op, Nstop, Nk, Nm, resid, MaxIt);
// ImplicitlyRestartedLanczos<FermionField> IRL(OpCheby, Op2, Nstop, Nk, Nm, resid, MaxIt);
// SimpleLanczos<FermionField> IRL(Op,Nstop, Nk, Nm, resid, MaxIt);
ImplicitlyRestartedBlockLanczos<FermionField> IRBL(HermOp, SHermOp,
FrbGrid,SFrbGrid,mrhs,
Cheby,
Nstop, Nstop*2,
Nu, Nk, Nm,
resid, MaxIt,
IRBLdiagonaliseWithEigen);
IRBL.split_test=1;
std::vector<RealD> eval(Nm);
FermionField src(FGrid);
gaussian(RNG5, src);
std::vector<FermionField> evec(Nm, FGrid);
for (int i = 0; i < 1; i++) {
std::cout << i << " / " << Nm << " grid pointer " << evec[i].Grid()
@@ -101,9 +276,40 @@ int main(int argc, char** argv) {
};
int Nconv;
IRL.calc(eval, evec, src, Nconv);
// IRL.calc(eval, evec, src, Nconv);
IRBL.calc(eval, evec, src, Nconv,LanczosType::irbl);
std::cout << eval << std::endl;
std::cout << mass <<" : " << eval << std::endl;
Gamma g5(Gamma::Algebra::Gamma5) ;
ComplexD dot;
FermionField tmp(FGrid);
FermionField sav(FGrid);
sav=evec[0];
for (int i = 0; i < Nstop ; i++) {
tmp = g5*evec[i];
dot = innerProduct(tmp,evec[i]);
std::cout << mass << " : " << eval[i] << " " << real(dot) << " " << imag(dot) << std::endl ;
// if ( i<1)
{
std::string evfile ("./evec_"+std::to_string(mass)+"_"+std::to_string(i));
auto evdensity = localInnerProduct(evec[i],evec[i] );
writeFile(evdensity,evfile);
}
if (i>0) sav += evec[i];
}
{
std::string evfile ("./evec_"+std::to_string(mass)+"_sum");
// auto evdensity = localInnerProduct(evec[i],evec[i] );
writeFile(sav,evfile);
}
for(int i =0;i<Nu;i++) src[i]=evec[i];
for(int i=Nu;i<Nstop;i++) src[i%Nu] +=evec[i];
// src = evec[0]+evec[1]+evec[2];
// src += evec[3]+evec[4]+evec[5];
// src += evec[6]+evec[7]+evec[8];
mass += LanParams.mstep;
}
Grid_finalize();
}
+79 -8
View File
@@ -27,6 +27,7 @@ directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include <Grid/parallelIO/IldgIOtypes.h>
using namespace std;
using namespace Grid;
@@ -38,11 +39,32 @@ typedef typename WilsonFermionD::FermionField FermionField;
RealD AllZero(RealD x) { return 0.; }
template <class T> void writeFile(T& in, std::string const fname){
#if 1
// Ref: https://github.com/paboyle/Grid/blob/feature/scidac-wp1/tests/debug/Test_general_coarse_hdcg_phys48.cc#L111
std::cout << Grid::GridLogMessage << "Writes to: " << fname << std::endl;
Grid::emptyUserRecord record;
Grid::ScidacWriter WR(in.Grid()->IsBoss());
WR.open(fname);
WR.writeScidacFieldRecord(in,record,0);
WR.close();
#endif
// What is the appropriate way to throw error?
}
namespace Grid {
struct LanczosParameters: Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(LanczosParameters,
RealD, mass ,
RealD, mstep ,
Integer, Nstop,
Integer, Nk,
Integer, Np,
Integer, ReadEvec,
Integer, maxIter,
RealD, resid,
RealD, ChebyLow,
RealD, ChebyHigh,
Integer, ChebyOrder)
@@ -115,12 +137,13 @@ int main(int argc, char** argv) {
LatticeGaugeField Umu(UGrid);
// SU<Nc>::HotConfiguration(RNG4, Umu);
// SU<Nc>::ColdConfiguration(Umu);
FieldMetaData header;
std::string file("./config");
int precision32 = 0;
int tworow = 0;
// int precision32 = 0;
// int tworow = 0;
// NerscIO::writeConfiguration(Umu,file,tworow,precision32);
NerscIO::readConfiguration(Umu,header,file);
@@ -158,10 +181,32 @@ int main(int argc, char** argv) {
}
mass=LanParams.mass;
resid=LanParams.resid;
Nstop=LanParams.Nstop;
Nk=LanParams.Nk;
Np=LanParams.Np;
MaxIt=LanParams.maxIter;
Nm = Nk + Np;
FermionField src(FGrid);
gaussian(RNG5, src);
if(LanParams.ReadEvec) {
std::string evecs_file="evec_in";
std::cout << GridLogIRL<< "Reading evecs from "<<evecs_file<<std::endl;
emptyUserRecord record;
Grid::ScidacReader RD;
RD.open(evecs_file);
RD.readScidacFieldRecord(src,record);
RD.close();
}
std::vector<Complex> boundary = {1,1,1,-1};
// std::vector<Complex> boundary = {1,1,1,1};
FermionOp::ImplParams Params(boundary);
while ( mass > - 5.0){
FermionOp WilsonOperator(Umu,*FGrid,*FrbGrid,mass);
while ( mass > - 2.0){
FermionOp WilsonOperator(Umu,*FGrid,*FrbGrid,mass,Params);
MdagMLinearOperator<FermionOp,FermionField> HermOp(WilsonOperator); /// <-----
//SchurDiagTwoOperator<FermionOp,FermionField> HermOp(WilsonOperator);
Gamma5HermitianLinearOperator <FermionOp,LatticeFermion> HermOp2(WilsonOperator); /// <-----
@@ -179,11 +224,10 @@ while ( mass > - 5.0){
PlainHermOp<FermionField> Op (HermOp);
PlainHermOp<FermionField> Op2 (HermOp2);
ImplicitlyRestartedLanczos<FermionField> IRL(OpCheby, Op2, Nstop, Nk, Nm, resid, MaxIt);
ImplicitlyRestartedLanczos<FermionField> IRL(OpCheby, Op, Nstop, Nk, Nm, resid, MaxIt);
// SimpleLanczos<FermionField> IRL(Op,Nstop, Nk, Nm, resid, MaxIt);
std::vector<RealD> eval(Nm);
FermionField src(FGrid);
gaussian(RNG5, src);
std::vector<FermionField> evec(Nm, FGrid);
for (int i = 0; i < 1; i++) {
std::cout << i << " / " << Nm << " grid pointer " << evec[i].Grid()
@@ -192,19 +236,46 @@ while ( mass > - 5.0){
int Nconv;
IRL.calc(eval, evec, src, Nconv);
// IRL.calc(eval, src, Nconv);
std::cout << mass <<" : " << eval << std::endl;
Gamma g5(Gamma::Algebra::Gamma5) ;
ComplexD dot;
FermionField tmp(FGrid);
FermionField sav(FGrid);
sav=evec[0];
for (int i = 0; i < Nstop ; i++) {
tmp = g5*evec[i];
dot = innerProduct(tmp,evec[i]);
std::cout << mass << " : " << eval[i] << " " << real(dot) << " " << imag(dot) << std::endl ;
// if ( i<1)
{
std::string evfile ("./evec_"+std::to_string(mass)+"_"+std::to_string(i));
auto evdensity = localInnerProduct(evec[i],evec[i] );
writeFile(evdensity,evfile);
// if(LanParams.ReadEvec) {
// std::string evecs_file="evec_in";
{
std::cout << GridLogIRL<< "Reading evecs from "<<evfile<<std::endl;
emptyUserRecord record;
Grid::ScidacReader RD;
RD.open(evfile);
RD.readScidacFieldRecord(evdensity,record);
RD.close();
}
}
if (i>0) sav += evec[i];
}
{
std::string evfile ("./evec_"+std::to_string(mass)+"_sum");
// auto evdensity = localInnerProduct(evec[i],evec[i] );
writeFile(sav,evfile);
}
src = evec[0]+evec[1]+evec[2];
mass += -0.1;
src += evec[3]+evec[4]+evec[5];
src += evec[6]+evec[7]+evec[8];
mass += LanParams.mstep;
}
Grid_finalize();