/************************************************************************************* Grid physics library, www.github.com/paboyle/Grid Source file: ./examples/Example_pvdagm_census.cc Copyright (C) 2026 Author: Peter Boyle 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 */ // Spectral census of the coarsened PVdagM operator A_c. // // Measures the three sets that discriminate between the candidate explanations // for slow coarse-grid Krylov convergence: // // 0. Fine Ritz diagonal of RAW subspace vectors (pre-block-orthog). // NB CoarsenOperator block-orthogonalises subspace[] IN PLACE; all // nulliness/deflation bases must be built from a raw copy. // 1. Adjoint correctness check == (fail-fast) // 2. Raw-vector coarse images vs A_c: RQ (must equal CENSUS 0 by Galerkin), // ||A_c psi_c||/||psi_c||, and representability error // 3. sigma_max^2 = lambda_max(A_c^dag A_c) via power method // 4. Low singular values Chebyshev-filtered IRL on A_c^dag A_c // -> sigma_min census = pseudospectrum of A_c evaluated at the origin // 5. Half-plane margin lambda_min/max of H = (A_c + A_c^dag)/2 // -> min Re W(A_c); positive-real check (Eisenstat-Elman-Schultz bound) // // Interpretation: // sigma_min ~ min|lambda|, ~nbasis tiny then gap : effectively normal, bipartite // sigma_min ~ min|lambda|, dense low tail : normal but rank-starved // sigma_min << min|lambda| : non-normal near origin // lambda_min(H) < 0 : half-plane condition violated // // Requires the dagger code path in GeneralCoarsenedMatrix: // _Adag allocated, PopulateAdag active, _Adag exchanged, hermitian=0. // // Env vars: // MASS fermion mass (default 0.00078) // SUBSPACE_FILE subspace cache path // CoarseSolverShift shift baked into coarsening (default 0.0: pure Galerkin) // CENSUS_NSTOP converged low modes wanted (default 60) // CENSUS_NK Lanczos Nk (default 96) // CENSUS_NM Lanczos Nm (default 192) // CENSUS_TOL Lanczos residual (default 1e-5) // CENSUS_MAXIT Lanczos max restarts (default 50) // CHEBY_LO filter low edge in sigma^2 (default 4.0) // CHEBY_HI filter high edge; 0 = auto from power method x1.1 // CHEBY_ORDER filter order (default 401) // filter gain at 0 ~ cosh(order*2*sqrt(lo/hi)); with // hi~2200, lo=4, order=401 => gain ~ 1e14. lo=0.01 at // order 201 gives gain ~1.4 (stagnation). #include #include #include #include using namespace std; using namespace Grid; RealD mass = 0.00078; RealD CoarseSolverShift = 0.0; int CensusNstop = 60; int CensusNk = 96; int CensusNm = 192; RealD CensusTol = 1.0e-5; int CensusMaxIt = 50; RealD ChebyLo = 4.0; // sigma^2 cutoff: amplifies sigma < 2. Filter gain ~ cosh(order*2*sqrt(lo/hi)) RealD ChebyHi = 0.0; // 0 => auto: 1.1 * power-method sigma_max^2 int ChebyOrder = 401; RealD CGdeflTol = 1.0e-8; // CENSUS 6 deflated-CG tolerance int CGdeflMaxIt = 4000; // CENSUS 6 deflated-CG max iterations int DeflRank = 0; // CENSUS 6 deflation rank; 0 => all available per basis void ParseEnvironment(void) { if(getenv("MASS")) mass = atof(getenv("MASS")); if(getenv("CoarseSolverShift")) CoarseSolverShift = atof(getenv("CoarseSolverShift")); if(getenv("CENSUS_NSTOP")) CensusNstop = atoi(getenv("CENSUS_NSTOP")); if(getenv("CENSUS_NK")) CensusNk = atoi(getenv("CENSUS_NK")); if(getenv("CENSUS_NM")) CensusNm = atoi(getenv("CENSUS_NM")); if(getenv("CENSUS_TOL")) CensusTol = atof(getenv("CENSUS_TOL")); if(getenv("CENSUS_MAXIT")) CensusMaxIt = atoi(getenv("CENSUS_MAXIT")); if(getenv("CHEBY_LO")) ChebyLo = atof(getenv("CHEBY_LO")); if(getenv("CHEBY_HI")) ChebyHi = atof(getenv("CHEBY_HI")); if(getenv("CHEBY_ORDER")) ChebyOrder = atoi(getenv("CHEBY_ORDER")); if(getenv("CGDEFL_TOL")) CGdeflTol = atof(getenv("CGDEFL_TOL")); if(getenv("CGDEFL_MAXIT")) CGdeflMaxIt = atoi(getenv("CGDEFL_MAXIT")); if(getenv("DEFL_RANK")) DeflRank = atoi(getenv("DEFL_RANK")); std::cout << GridLogMessage << "PARAM: MASS " << mass << std::endl; std::cout << GridLogMessage << "PARAM: CoarseSolverShift " << CoarseSolverShift << std::endl; std::cout << GridLogMessage << "PARAM: CENSUS_NSTOP " << CensusNstop << std::endl; std::cout << GridLogMessage << "PARAM: CENSUS_NK " << CensusNk << std::endl; std::cout << GridLogMessage << "PARAM: CENSUS_NM " << CensusNm << std::endl; std::cout << GridLogMessage << "PARAM: CENSUS_TOL " << CensusTol << std::endl; std::cout << GridLogMessage << "PARAM: CENSUS_MAXIT " << CensusMaxIt << std::endl; std::cout << GridLogMessage << "PARAM: CHEBY_LO " << ChebyLo << std::endl; std::cout << GridLogMessage << "PARAM: CHEBY_HI " << ChebyHi << std::endl; std::cout << GridLogMessage << "PARAM: CHEBY_ORDER " << ChebyOrder << std::endl; } template void saveSubspace(std::vector &subspace, std::string const fname){ #ifdef HAVE_LIME std::cout << Grid::GridLogMessage << "Saving subspace (" << subspace.size() << " vectors) to: " << fname << std::endl; Grid::emptyUserRecord record; Grid::ScidacWriter SW(subspace[0].Grid()->IsBoss()); SW.open(fname); for (int k = 0; k < (int)subspace.size(); k++) SW.writeScidacFieldRecord(subspace[k], record); SW.close(); #endif } template void loadSubspace(std::vector &subspace, std::string const fname){ #ifdef HAVE_LIME std::cout << Grid::GridLogMessage << "Loading subspace (" << subspace.size() << " vectors) from: " << fname << std::endl; Grid::emptyUserRecord record; Grid::ScidacReader SR; SR.open(fname); for (int k = 0; k < (int)subspace.size(); k++) SR.readScidacFieldRecord(subspace[k], record); SR.close(); #endif } template class PVdagMLinearOperator : public LinearOperatorBase { 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 &out){ assert(0); }; void Op (const Field &in, Field &out){ Field tmp(in.Grid()); _Mat.M(in,tmp); _PV.Mdag(tmp,out); } void AdjOp (const Field &in, Field &out){ Field tmp(in.Grid()); _PV.M(in,tmp); _Mat.Mdag(tmp,out); } void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ HermOp(in,out); ComplexD dot = innerProduct(in,out); n1=real(dot); n2=norm2(out); } void HermOp(const Field &in, Field &out){ Field tmp(in.Grid()); Op(in,tmp); AdjOp(tmp,out); } }; template class ShiftedPVdagMLinearOperator : public LinearOperatorBase { Matrix &_Mat; Matrix &_PV; public: RealD shift; 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 &out){ assert(0); }; void Op (const Field &in, Field &out){ Field tmp(in.Grid()); _Mat.M(in,tmp); _PV.Mdag(tmp,out); out = out + shift * in; } void AdjOp (const Field &in, Field &out){ 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){ Field tmp(in.Grid()); Op(in,tmp); AdjOp(tmp,out); } }; // H = (A + A^dag)/2 : Hermitian part of the coarse operator. // lambda_min(H) = min Re W(A) is the half-plane margin; the EES GCR // convergence theorem requires it positive. template class HermitianPartOperator : public LinearOperatorBase { Matrix &_Mat; public: HermitianPartOperator(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 &out){ assert(0); }; void Op (const Field &in, Field &out){ HermOp(in,out); } void AdjOp (const Field &in, Field &out){ HermOp(in,out); } void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ HermOp(in,out); ComplexD dot = innerProduct(in,out); n1=real(dot); n2=norm2(out); } void HermOp(const Field &in, Field &out){ Field tmp(in.Grid()); _Mat.M(in,out); _Mat.Mdag(in,tmp); out = 0.5*(out + tmp); } }; // s*I - Op : power method on this gives s - lambda_min(Op) for Hermitian Op. template class ShiftedNegatedOperator : public LinearOperatorBase { LinearOperatorBase &_Op; RealD s; public: ShiftedNegatedOperator(RealD _s, LinearOperatorBase &Op): _Op(Op), s(_s) {}; 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 &out){ assert(0); }; void Op (const Field &in, Field &out){ HermOp(in,out); } void AdjOp (const Field &in, Field &out){ HermOp(in,out); } void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ HermOp(in,out); ComplexD dot = innerProduct(in,out); n1=real(dot); n2=norm2(out); } void HermOp(const Field &in, Field &out){ _Op.HermOp(in,out); out = s*in - out; } }; int main (int argc, char ** argv) { Grid_init(&argc,&argv); ParseEnvironment(); const int Ls=24; RealD M5=1.8; RealD b=1.5; RealD c=0.5; const int nbasis = 60; std::cout << GridLogMessage << "Census of coarse PVdagM: mass=" << mass << " Ls=" << Ls << " nbasis=" << nbasis << std::endl; std::vector lat_size {48, 48, 48, 96}; 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); // Blocking: default matches Example_pvdagm.cc; override with e.g. BLOCK=2.2.2.2 Coordinate clatt = lat_size; Coordinate Block({4,4,6,4}); if ( getenv("BLOCK") ) { GridCmdOptionIntVector(std::string(getenv("BLOCK")),Block); GRID_ASSERT(Block.size()==4); } for(int d=0;d PVdagM_t; typedef ShiftedPVdagMLinearOperator ShiftedPVdagM_t; typedef GeneralCoarsenedMatrix LittleDiracOperator; typedef LittleDiracOperator::CoarseVector CoarseVector; typedef Aggregation Subspace; PVdagM_t PVdagM(Ddwf,Dpv); ShiftedPVdagM_t ShiftedPVdagM(CoarseSolverShift,Ddwf,Dpv); NextToNearestStencilGeometry5D geom(Coarse5d); ////////////////////////////////////////////////////////////////////// // Subspace: load from cache or generate ////////////////////////////////////////////////////////////////////// std::string subspace_file = "/lustre/orion/phy157/proj-shared/phy157_dwf/paboyle/subspace_nb" + std::to_string(nbasis) + ".scidac"; if ( getenv("SUBSPACE_FILE") ) subspace_file = std::string(getenv("SUBSPACE_FILE")); uint64_t file_exists = 0; if ( UGrid->IsBoss() ) { std::ifstream f(subspace_file); file_exists = f.good() ? 1 : 0; } UGrid->GlobalSum(file_exists); const int cb = 0; Subspace AggregatesGCR(Coarse5d,FGrid,cb); if ( file_exists ) { std::cout << GridLogMessage << "*** Loading subspace from disk ***" << std::endl; loadSubspace(AggregatesGCR.subspace, subspace_file); } else { std::cout << GridLogMessage << "*** GCR subspace generation ***" << std::endl; AggregatesGCR.CreateSubspaceGCR(RNG5,PVdagM,nbasis); saveSubspace(AggregatesGCR.subspace, subspace_file); } ////////////////////////////////////////////////////////////////////// // Keep the RAW (pre-block-orthogonalisation) near-null vectors. // CoarsenOperator block-orthogonalises subspace[] IN PLACE, after which // subspace[k] is the orthonormal basis phi_k and Project(phi_k) = e_k, // the block-constant unit vector -- NOT a near-null direction. // All nulliness measurements and any deflation basis must use raw[]. ////////////////////////////////////////////////////////////////////// std::vector raw(nbasis,FGrid); for(int k=0;k/ ~ the nulliness achieved at generation // (~2e-3). O(0.1-10) values mean the cache holds orthogonalised vectors // and must be regenerated. ////////////////////////////////////////////////////////////////////// std::cout << GridLogMessage << "=================================================" << std::endl; std::cout << GridLogMessage << "CENSUS 0: fine Ritz diagonal of raw subspace vectors" << std::endl; std::cout << GridLogMessage << "=================================================" << std::endl; { LatticeFermionD Ap(FGrid); for(int k=0;k/ = " << rq << " ||A psi||/||psi|| = " << std::sqrt(norm2(Ap)/n2psi) << std::endl; } } ////////////////////////////////////////////////////////////////////// // Coarsen. hermitian=0 is REQUIRED: enables PopulateAdag so that // Mdag applies A^dag rather than silently aliasing to A. ////////////////////////////////////////////////////////////////////// LittleDiracOperator LittleDiracOpPV(geom,FGrid,Coarse5d,0); if ( CoarseSolverShift != 0.0 ) { std::cout << GridLogMessage << "Coarsening SHIFTED operator, shift=" << CoarseSolverShift << std::endl; LittleDiracOpPV.CoarsenOperator(ShiftedPVdagM, AggregatesGCR); } else { std::cout << GridLogMessage << "Coarsening pure Galerkin operator (no shift)" << std::endl; LittleDiracOpPV.CoarsenOperator(PVdagM, AggregatesGCR); } CoarseVector c_x(Coarse5d); CoarseVector c_y(Coarse5d); CoarseVector c_t1(Coarse5d); CoarseVector c_t2(Coarse5d); ////////////////////////////////////////////////////////////////////// // CENSUS 1: adjoint correctness (fail fast) // == for random x,y ////////////////////////////////////////////////////////////////////// std::cout << GridLogMessage << "=================================================" << std::endl; std::cout << GridLogMessage << "CENSUS 1: adjoint correctness of dagger code path" << std::endl; std::cout << GridLogMessage << "=================================================" << std::endl; random(CRNG,c_x); random(CRNG,c_y); LittleDiracOpPV.M (c_x,c_t1); // A x LittleDiracOpPV.Mdag(c_y,c_t2); // A^dag y ComplexD ip1 = innerProduct(c_y,c_t1); // ComplexD ip2 = innerProduct(c_t2,c_x); // RealD reldiff = abs(ip1-ip2)/abs(ip1); std::cout << GridLogMessage << "CENSUS: = " << ip1 << std::endl; std::cout << GridLogMessage << "CENSUS: = " << ip2 << std::endl; std::cout << GridLogMessage << "CENSUS: rel diff = " << reldiff << " (expect ~1e-14; FAIL if O(1))" << std::endl; GRID_ASSERT(reldiff < 1.0e-8); // Coarse near-null ("global") vectors psi_c[k] = P^dag raw[k], stored for the // Ritz-matrix + deflation study in CENSUS 6 (filled in CENSUS 2's projection // loop below, before raw[]/subspace[] are freed). std::vector psi_c(nbasis,Coarse5d); ////////////////////////////////////////////////////////////////////// // CENSUS 2: nulliness of the RAW vectors' coarse images against A_c. // psi_c[k] = P^dag raw[k]. Galerkin guarantees the Rayleigh quotient // equals CENSUS 0's fine value exactly (raw[k] is in span of its own // chopped pieces) -- agreement is a machine-precision validation of // the coarsening. ||A_c psi_c||/||psi_c|| is the sigma-relevant norm. // The representability column ||raw - P psi_c||/||raw|| must be ~eps. ////////////////////////////////////////////////////////////////////// std::cout << GridLogMessage << "=================================================" << std::endl; std::cout << GridLogMessage << "CENSUS 2: raw-vector coarse images against coarse operator" << std::endl; std::cout << GridLogMessage << "=================================================" << std::endl; { LatticeFermionD back(FGrid); for(int k=0;k/ = " << rq << " ||A psi||/||psi|| = " << std::sqrt(n2Apsi/n2psi) << " represent_err = " << represent << std::endl; } } // Fine subspace + raw copy are needed only through CENSUS 2; CENSUS 3-5 are // entirely coarse (LittleDiracOpPV only), and the CENSUS 4 evec save writes the // coarse vectors directly. Release the ~2*nbasis fine 5D fields (~14 GB/GCD at // 2^4) HERE, before the order-ChebyOrder Lanczos whose padded coarse temporaries // otherwise push host memory over the top on top of _A + _Adag (the AccCache // CpuPtr!=NULL abort seen mid-iteration). // Direct orthonormality check of the fine near-null vectors (GlobalOrthonormalise // in CreateSubspaceGCR). raw is freed just below, so this runs here, not CENSUS 6. // If this is ~0 but the coarse Gram S (CENSUS 6) is not, the gap is representability, // not orthonormality. { Eigen::MatrixXcd Gfine(nbasis,nbasis); for(int i=0;i(g.real(),g.imag()); Gfine(j,i) = std::conj(Gfine(i,j)); } } double GmI = (Gfine - Eigen::MatrixXcd::Identity(nbasis,nbasis)).norm(); std::cout << GridLogMessage << "CENSUS 2b: fine Gram || - I||_F = " << GmI << " (expect ~0 if fine vectors orthonormal)" << std::endl; } raw.clear(); raw.shrink_to_fit(); AggregatesGCR.subspace.clear(); AggregatesGCR.subspace.shrink_to_fit(); ////////////////////////////////////////////////////////////////////// // CENSUS 3: sigma_max^2 = lambda_max( A_c^dag A_c ) by power method ////////////////////////////////////////////////////////////////////// std::cout << GridLogMessage << "=================================================" << std::endl; std::cout << GridLogMessage << "CENSUS 3: power method for sigma_max" << std::endl; std::cout << GridLogMessage << "=================================================" << std::endl; MdagMLinearOperator HermOpAdagA(LittleDiracOpPV); random(CRNG,c_x); PowerMethod PM; RealD sigmax2 = PM(HermOpAdagA,c_x); std::cout << GridLogMessage << "CENSUS: lambda_max(AdagA) = " << sigmax2 << " sigma_max = " << std::sqrt(sigmax2) << std::endl; ////////////////////////////////////////////////////////////////////// // CENSUS 4: low singular values via Chebyshev-filtered IRL on A^dag A // The low end of sigma(A_c) is the pseudospectrum of A_c at z=0. ////////////////////////////////////////////////////////////////////// std::cout << GridLogMessage << "=================================================" << std::endl; std::cout << GridLogMessage << "CENSUS 4: Chebyshev-filtered Lanczos, low sigma^2" << std::endl; std::cout << GridLogMessage << "=================================================" << std::endl; RealD cheby_hi = (ChebyHi > 0.0) ? ChebyHi : 1.1*sigmax2; std::cout << GridLogMessage << "Chebyshev filter [" << ChebyLo << "," << cheby_hi << "] order " << ChebyOrder << std::endl; // eval/evec/Nconv hoisted out of the block so CENSUS 6 can deflate with them. std::vector eval(CensusNm); std::vector evec(CensusNm,Coarse5d); int Nconv=0; { Chebyshev Cheby(ChebyLo,cheby_hi,ChebyOrder); FunctionHermOp OpCheby(Cheby,HermOpAdagA); PlainHermOp Op (HermOpAdagA); ImplicitlyRestartedLanczos IRL(OpCheby,Op,CensusNstop,CensusNk,CensusNm,CensusTol,CensusMaxIt); random(CRNG,c_x); IRL.calc(eval,evec,c_x,Nconv); std::cout << GridLogMessage << "CENSUS: converged " << Nconv << " modes of AdagA" << std::endl; for(int i=0;i0 ) { #ifdef HAVE_LIME std::string evec_file(getenv("CENSUS_EVEC_FILE")); std::string eval_file = evec_file + ".evals.xml"; std::cout << GridLogMessage << "CENSUS: saving " << Nconv << " singular vectors to " << evec_file << std::endl; emptyUserRecord record; ScidacWriter WR(evec[0].Grid()->IsBoss()); WR.open(evec_file); for(int i=0;i eval_out(eval.begin(),eval.begin()+Nconv); // don't shrink shared eval write(WRx,"evals",eval_out); #endif } } // NB: evec/eval stay sized CensusNm (Lattice has no default ctor, so // std::vector::resize won't instantiate). They match in size, // which is all DeflatedGuesser asserts; CENSUS 6 only ever indexes [0,Nconv). ////////////////////////////////////////////////////////////////////// // CENSUS 5: half-plane margin from the Hermitian part // lambda_min(H) = min Re W(A_c) > 0 <=> positive-real (EES applies) ////////////////////////////////////////////////////////////////////// std::cout << GridLogMessage << "=================================================" << std::endl; std::cout << GridLogMessage << "CENSUS 5: Hermitian part H=(A+Adag)/2, half-plane margin" << std::endl; std::cout << GridLogMessage << "=================================================" << std::endl; HermitianPartOperator HermPart(LittleDiracOpPV); random(CRNG,c_x); RealD lamHmax = PM(HermPart,c_x); std::cout << GridLogMessage << "CENSUS: lambda_max(H) = " << lamHmax << std::endl; // lambda_min(H): most-negative eigenvalue via Chebyshev-filtered IRL on H. // A shifted power method cannot separate it from the dense low tail (which is // why the earlier -0.006 is suspect); Cheby(lo, hi>=lambda_max) amplifies the // most-negative mode hardest so IRL isolates the true bottom of the spectrum. RealD hpLo = getenv("HPLANE_CHEBY_LO") ? atof(getenv("HPLANE_CHEBY_LO")) : 0.1; RealD hpHi = getenv("HPLANE_CHEBY_HI") ? atof(getenv("HPLANE_CHEBY_HI")) : 1.1*lamHmax; int hpOrder = getenv("HPLANE_CHEBY_ORDER") ? atoi(getenv("HPLANE_CHEBY_ORDER")) : 61; // Grid's Chebyshev filter MUST be odd order (positive for x < -1, where the low/ // negative modes map); an even order flips the sign there and the IRL blows up. if(hpOrder%2==0){ hpOrder++; std::cout< "< HCheby(hpLo,hpHi,hpOrder); FunctionHermOp HOpCheby(HCheby,HermPart); PlainHermOp HOpPlain(HermPart); ImplicitlyRestartedLanczos HIRL(HOpCheby,HOpPlain,hpNstop,hpNk,hpNm,hpTol,hpMaxIt); std::vector heval(hpNm); std::vector hevec(hpNm,Coarse5d); int hNconv=0; random(CRNG,c_x); HIRL.calc(heval,hevec,c_x,hNconv); RealD lamHmin = (hNconv>0) ? heval[0] : 9.99e99; for(int kk=0;kk GCR unguaranteed)" << std::endl; ////////////////////////////////////////////////////////////////////// // CENSUS 6: Ritz matrix of the coarse near-null basis + deflated-CG study // // C_ij = , S_ij = . // psi_c are NOT orthonormal (raw near-null projected to coarse), so the // Rayleigh-Ritz problem is the GENERALISED Hermitian one C v = theta S v. // Its eigenpairs (theta_i, g_i = sum_j V(j,i) psi_c^j) are the best approximate // eigenpairs of A^dag A available from span{psi_c}; Eigen normalises so that // V^dag S V = I, hence = delta_ij and the g_i are an orthonormal // DeflatedGuesser basis. Compare theta_i to the Lanczos sigma_i^2, then run // three CG solves on A^dag A: [1] no deflation, [2] Lanczos-eigenvector // deflated guess, [3] Ritz global-vector deflated guess (g_i treated as pure // eigenvectors with eigenvalue theta_i). ////////////////////////////////////////////////////////////////////// std::cout << GridLogMessage << "=================================================" << std::endl; std::cout << GridLogMessage << "CENSUS 6: Ritz matrix C_ij = + deflated CG" << std::endl; std::cout << GridLogMessage << "=================================================" << std::endl; std::vector Apsi(nbasis,Coarse5d); for(int j=0;j(cij.real(),cij.imag()); Smat(i,j) = std::complex(sij.real(),sij.imag()); } } { Eigen::SelfAdjointEigenSolver ses(Smat); Eigen::MatrixXcd Id = Eigen::MatrixXcd::Identity(nbasis,nbasis); double SmI = (Smat - Id).norm(); // ||S - I||_F : ~0 iff psi_c orthonormal std::cout << GridLogMessage << "CENSUS 6: Gram S eig range [" << ses.eigenvalues()(0) << ", " << ses.eigenvalues()(nbasis-1) << "] ||S - I||_F = " << SmI << " (expect ~0: fine vectors are GlobalOrthonormalise'd => psi_c orthonormal)" << std::endl; } Eigen::GeneralizedSelfAdjointEigenSolver ges(Cmat,Smat); Eigen::VectorXd theta = ges.eigenvalues(); // ascending, real Eigen::MatrixXcd Vr = ges.eigenvectors(); // columns; V^dag S V = I int ncmp = std::min((int)nbasis,Nconv); std::cout << GridLogMessage << "CENSUS 6: Ritz theta vs Lanczos sigma^2 (both ascending):" << std::endl; for(int i=0;i0) ? std::min(DeflRank,Nconv) : Nconv; int rankRitz = (DeflRank>0) ? std::min(DeflRank,(int)nbasis) : (int)nbasis; std::cout << GridLogMessage << "CENSUS 6: CG tol "< CGdefl(CGdeflTol,CGdeflMaxIt,false); cg_x = Zero(); CGdefl(HermOpAdagA,cg_src,cg_x); std::cout << GridLogMessage << "CENSUS 6: [1] no deflation : iters = " << CGdefl.IterationsToComplete << " true_resid = " << CGdefl.TrueResidual << std::endl; if(rankLanc>0){ DeflatedGuesser guessL(evec,eval,rankLanc); guessL(cg_src,cg_x); CGdefl(HermOpAdagA,cg_src,cg_x); std::cout << GridLogMessage << "CENSUS 6: [2] Lanczos-evec deflation : iters = " << CGdefl.IterationsToComplete << " true_resid = " << CGdefl.TrueResidual << std::endl; } { DeflatedGuesser guessR(gvec,gval,rankRitz); guessR(cg_src,cg_x); CGdefl(HermOpAdagA,cg_src,cg_x); std::cout << GridLogMessage << "CENSUS 6: [3] Ritz-vector deflation : iters = " << CGdefl.IterationsToComplete << " true_resid = " << CGdefl.TrueResidual << std::endl; } ////////////////////////////////////////////////////////////////////// // Summary ////////////////////////////////////////////////////////////////////// std::cout << GridLogMessage << "=================================================" << std::endl; std::cout << GridLogMessage << "CENSUS SUMMARY" << std::endl; std::cout << GridLogMessage << " sigma_max = " << std::sqrt(sigmax2) << std::endl; std::cout << GridLogMessage << " lambda_max(H) = " << lamHmax << std::endl; std::cout << GridLogMessage << " lambda_min(H) = " << lamHmin << std::endl; std::cout << GridLogMessage << " low sigma census : see CENSUS 4 table above" << std::endl; std::cout << GridLogMessage << " Compare min sigma with |lambda| from Krylov-Schur (Patrick):" << std::endl; std::cout << GridLogMessage << " sigma_min ~ min|lambda| : effectively normal; deflation rank is the issue" << std::endl; std::cout << GridLogMessage << " sigma_min << min|lambda|: non-normal; need two-sided/singular-vector deflation" << std::endl; std::cout << GridLogMessage << "=================================================" << std::endl; std::cout << GridLogMessage << "Done" << std::endl; Grid_finalize(); return 0; }