mirror of
https://github.com/paboyle/Grid.git
synced 2026-08-12 13:53:30 +01:00
279 lines
13 KiB
C++
279 lines
13 KiB
C++
/*
|
|
* 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 <Grid/Grid.h>
|
|
|
|
using namespace std;
|
|
using namespace Grid;
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// A = PV^dag M : Op = _PV.Mdag . _Mat.M , AdjOp = _Mat.Mdag . _PV.M
|
|
//////////////////////////////////////////////////////////////////////
|
|
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){
|
|
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 Field>
|
|
class HermitianPartLinOp : public LinearOperatorBase<Field> {
|
|
LinearOperatorBase<Field> &_A;
|
|
public:
|
|
HermitianPartLinOp(LinearOperatorBase<Field> &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<Field> &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 Field>
|
|
class ShiftedNegatedOperator : public LinearOperatorBase<Field> {
|
|
LinearOperatorBase<Field> &_Op;
|
|
RealD s;
|
|
public:
|
|
ShiftedNegatedOperator(RealD _s, LinearOperatorBase<Field> &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<Field> &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<RealD> 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<N;i++)
|
|
madj_list.push_back( (N==1) ? lo : lo*std::pow(hi/lo, double(i)/double(N-1)) );
|
|
}
|
|
|
|
// lambda_min(H) is the most-negative eigenvalue; resolved by Chebyshev-filtered
|
|
// Lanczos on H (a shifted power method cannot separate it from the dense low tail).
|
|
RealD HalfChebyLo = getenv("HALF_CHEBY_LO") ? atof(getenv("HALF_CHEBY_LO")) : 0.1;
|
|
RealD HalfChebyHi = getenv("HALF_CHEBY_HI") ? atof(getenv("HALF_CHEBY_HI")) : 0.0; // 0 => 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<<GridLogMessage<<"HALF_CHEBY_ORDER forced odd -> "<<HalfChebyOrder<<std::endl; }
|
|
int HalfNstop = getenv("HALF_NSTOP") ? atoi(getenv("HALF_NSTOP")) : 8;
|
|
int HalfNk = getenv("HALF_NK") ? atoi(getenv("HALF_NK")) : 24;
|
|
int HalfNm = getenv("HALF_NM") ? atoi(getenv("HALF_NM")) : 48;
|
|
RealD HalfTol = getenv("HALF_TOL") ? atof(getenv("HALF_TOL")) : 1.0e-4;
|
|
int HalfMaxIt = getenv("HALF_MAXIT") ? atoi(getenv("HALF_MAXIT")) : 20;
|
|
|
|
std::vector<int> 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<MobiusFermionD,LatticeFermionD> A(Dlight,Dadj); // A = Dadj^dag Dlight
|
|
HermitianPartLinOp<LatticeFermionD> H(A);
|
|
|
|
PowerMethod<LatticeFermionD> 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<LatticeFermionD> Cheby(HalfChebyLo,fhi,HalfChebyOrder);
|
|
FunctionHermOp<LatticeFermionD> OpCheby(Cheby,H);
|
|
PlainHermOp<LatticeFermionD> OpPlain(H);
|
|
ImplicitlyRestartedLanczos<LatticeFermionD> IRL(OpCheby,OpPlain,HalfNstop,HalfNk,HalfNm,HalfTol,HalfMaxIt);
|
|
std::vector<RealD> heval(HalfNm);
|
|
std::vector<LatticeFermionD> 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<hNconv;kk++) lamHmin = std::min(lamHmin, heval[kk]);
|
|
std::cout << GridLogMessage << " (IRL H-bottom: " << hNconv
|
|
<< " converged, most-negative eval " << lamHmin << ")" << std::endl;
|
|
|
|
random(RNG5,x); RealD sigmax2 = PM(A,x); // A.HermOp = A^dag A
|
|
RealD sigmax = std::sqrt(sigmax2);
|
|
|
|
bool posreal = (lamHmin > 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;
|
|
}
|