/* * Example_pvdagm_halfplane.cc * * Standalone fine-operator diagnostic: the EES half-plane margin of the * (non-Hermitian) PV-preconditioned Mobius DWF operator * * A(m_adj) = D_adj^dag D_light (D_adj plays the Pauli-Villars role) * * as a function of the adjoint mass m_adj, dialled from the light quark mass * up to the Pauli-Villars mass (=1). No coarse grid, no subspace, no Lanczos * -- pure power-method spectral tests on the fine grid. * * Purpose: A is the LEFT preconditioner for inverting the light operator. * To solve D_light X = B we iterate the preconditioned system * (D_adj^dag D_light) X = D_adj^dag B , * whose solution X is independent of m_adj -- only the conditioning and the * iterative convergence change. m_adj = m_light is the usual CGNR (symmetric * normal equations); m_adj = 1 is the Pauli-Villars preconditioned system. * The sweep asks which m_adj keeps the preconditioned operator well-behaved * (positive-real / EES-guaranteed) while buying the wider spectral range. * * For the Hermitian part H(A) = (A + A^dag)/2 we measure, per m_adj: * * lambda_max(H) -- power method on H * lambda_min(H) -- power method on (sI - H) => min Re W(A), the half-plane * margin. EES (Eisenstat-Elman-Schultz 1983, Thm 3.3) * GUARANTEES GCR convergence with rate * [ 1 - lambda_min(H)^2 / sigma_max^2 ]^{1/2} * ONLY when lambda_min(H) > 0 (positive-real / A's field * of values in the open right half-plane). A negative * value means the guarantee is lost (not that GCR * diverges); the magnitude is then the distance-to- * positive-realness, i.e. the shift/deflation needed to * recover it. * sigma_max -- power method on A^dag A (= A.HermOp) * * Endpoints: * m_adj = m_light => A = M^dag M, Hermitian PD, positive-real by * construction, lambda_min(H) = sigma_min^2 > 0 (the * squared / CGNR operator). * m_adj = 1 => A = PV^dag M, the standard PVdagM operator. * * Env: MASS, M5, MOBIUS_B, MOBIUS_C, LS, CONFIG, * MADJ_LIST (comma separated) OR MADJ_MIN / MADJ_MAX / MADJ_N (geometric). * * Caveat: lambda_min(H) via a shifted power method can be soft when it sits * near zero over a dense low spectrum. The SIGN and the TREND across m_adj * are the robust signal; confirm an individual near-zero value with a proper * shifted Lanczos if it is load-bearing. */ #include using namespace std; using namespace Grid; ////////////////////////////////////////////////////////////////////// // A = PV^dag M : Op = _PV.Mdag . _Mat.M , AdjOp = _Mat.Mdag . _PV.M ////////////////////////////////////////////////////////////////////// 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){ // A^dag A Field tmp(in.Grid()); Op(in,tmp); AdjOp(tmp,out); } }; ////////////////////////////////////////////////////////////////////// // H = (A + A^dag)/2 for a general non-Hermitian LinearOperator A. ////////////////////////////////////////////////////////////////////// template class HermitianPartLinOp : public LinearOperatorBase { LinearOperatorBase &_A; public: HermitianPartLinOp(LinearOperatorBase &A): _A(A) {}; 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()); _A.Op(in,out); // A in _A.AdjOp(in,tmp); // A^dag in 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); RealD mass = 0.00078; RealD M5 = 1.8; RealD b = 1.5; RealD c = 0.5; int Ls = 24; std::string config("ckpoint_lat.1000"); if(getenv("MASS")) mass = atof(getenv("MASS")); if(getenv("M5")) M5 = atof(getenv("M5")); if(getenv("MOBIUS_B")) b = atof(getenv("MOBIUS_B")); if(getenv("MOBIUS_C")) c = atof(getenv("MOBIUS_C")); if(getenv("LS")) Ls = atoi(getenv("LS")); if(getenv("CONFIG")) config = std::string(getenv("CONFIG")); // Adjoint-mass sweep: explicit list, or geometric MADJ_MIN..MADJ_MAX in MADJ_N steps. std::vector madj_list; if(getenv("MADJ_LIST")){ std::stringstream ss(getenv("MADJ_LIST")); std::string tok; while(std::getline(ss,tok,',')) if(tok.size()) madj_list.push_back(std::stod(tok)); } else { int N = getenv("MADJ_N") ? atoi(getenv("MADJ_N")) : 6; RealD lo = getenv("MADJ_MIN") ? atof(getenv("MADJ_MIN")) : mass; RealD hi = getenv("MADJ_MAX") ? atof(getenv("MADJ_MAX")) : 1.0; GRID_ASSERT(N>=1); for(int i=0;i auto int HalfChebyOrder = getenv("HALF_CHEBY_ORDER") ? atoi(getenv("HALF_CHEBY_ORDER")) : 61; // Grid's Chebyshev filter MUST be odd order: only then is the polynomial positive // for x < -1, the region the low/negative modes map to. An even order flips the // sign there, the filtered operator explodes negative, and the IRL never converges. if(HalfChebyOrder%2==0){ HalfChebyOrder++; std::cout< "< lat = {48,48,48,96}; GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(lat, GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi()); GridRedBlackCartesian * UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid); GridCartesian * FGrid = SpaceTimeGrid::makeFiveDimGrid(Ls,UGrid); GridRedBlackCartesian * FrbGrid = SpaceTimeGrid::makeFiveDimRedBlackGrid(Ls,UGrid); GridParallelRNG RNG5(FGrid); RNG5.SeedFixedIntegers({5,6,7,8}); std::cout << GridLogMessage << "PARAM: MASS(light) " << mass << " M5 " << M5 << " b " << b << " c " << c << " Ls " << Ls << std::endl; std::cout << GridLogMessage << "PARAM: CONFIG " << config << std::endl; LatticeGaugeField Umu(UGrid); FieldMetaData header; std::cout << GridLogMessage << "Reading gauge field " << config << std::endl; NerscIO::readConfiguration(Umu,header,config); // Fixed light operator (never changes across the sweep). MobiusFermionD Dlight(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid, mass, M5, b, c); LatticeFermionD x(FGrid); std::cout << GridLogMessage << "=================================================" << std::endl; std::cout << GridLogMessage << "FINE HALF-PLANE SWEEP A(m_adj) = D_adj^dag D_light" << std::endl; std::cout << GridLogMessage << " m_adj = " << mass << " => M^dag M (positive-real); m_adj = 1 => PVdagM" << std::endl; std::cout << GridLogMessage << "=================================================" << std::endl; for(auto madj : madj_list){ MobiusFermionD Dadj(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid, madj, M5, b, c); PVdagMLinearOperator A(Dlight,Dadj); // A = Dadj^dag Dlight HermitianPartLinOp H(A); PowerMethod PM; random(RNG5,x); RealD lamHmax = PM(H,x); // lambda_min(H): most-negative eigenvalue via Chebyshev-filtered IRL on H. // Cheby(lo,hi) amplifies eigenvalues below lo; with hi>=lambda_max(H) the most // negative mode is amplified hardest, so IRL isolates the true bottom of the // (possibly indefinite) spectrum where the shifted power method could not. RealD fhi = (HalfChebyHi>0.0)? HalfChebyHi : 1.1*lamHmax; Chebyshev Cheby(HalfChebyLo,fhi,HalfChebyOrder); FunctionHermOp OpCheby(Cheby,H); PlainHermOp OpPlain(H); ImplicitlyRestartedLanczos IRL(OpCheby,OpPlain,HalfNstop,HalfNk,HalfNm,HalfTol,HalfMaxIt); std::vector heval(HalfNm); std::vector hevec(HalfNm,FGrid); int hNconv=0; random(RNG5,x); IRL.calc(heval,hevec,x,hNconv); RealD lamHmin = (hNconv>0) ? heval[0] : 9.99e99; for(int kk=0;kk 0.0); RealD ratefac = posreal ? std::sqrt(1.0 - lamHmin*lamHmin/sigmax2) : 0.0; // EES per-iter RealD iters8 = (posreal && ratefac < 1.0) ? std::log(1.0e-8)/std::log(ratefac) : 0.0; std::cout << GridLogMessage << "HALFPLANE: m_adj " << madj << " lambda_min(H) " << lamHmin << " lambda_max(H) " << lamHmax << " sigma_max " << sigmax << " positive_real " << (posreal ? "YES" : "NO ") << (posreal ? (" EES_rate " + std::to_string(ratefac) + " EES_iters(1e-8) " + std::to_string(iters8)) : (" margin_below_zero " + std::to_string(-lamHmin) + " (EES guarantee lost)")) << std::endl; } std::cout << GridLogMessage << "=================================================" << std::endl; std::cout << GridLogMessage << "Reading: lambda_min(H) > 0 => EES guarantees GCR at the quoted rate." << std::endl; std::cout << GridLogMessage << " crossing to < 0 as m_adj -> 1 marks loss of positive-realness." << std::endl; std::cout << GridLogMessage << " (non-normality: eigenvalues may still be right-half-plane.)" << std::endl; std::cout << GridLogMessage << "Done" << std::endl; Grid_finalize(); return 0; }