/************************************************************************************* Grid physics library, www.github.com/paboyle/Grid Source file: ./examples/Example_pvdagm_mrhs.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 */ // MultiRHS (valence) two-level multigrid for PVdagM. // // Philosophy: NO block Krylov, NO per-RHS lockstep coefficients. A single // GCR polynomial for the enlarged block-diagonal system diag(A,...,A): // all inner products are summed over the RHS index, giving one alpha/beta // per step shared by every RHS. // // Level structure mirrors Example_pvdagm: // outer: MrhsPGCRNonHermitian on PVdagM over std::vector // precon: V-cycle -- per-RHS fine post-smoother (16-step shifted GCR), // batched restriction (MultiRHSBlockProject / GEMM), // ONE coarse PGCR on the 6D mrhs coarse operator // (MultiGeneralCoarsenedMatrix, GEMM mults -- the ~10x win), // batched prolongation. // // The coarse operator is coarsened once with the standard single-RHS // machinery (subspace cache reused) and imported via CopyMatrix. // // Memory note: outer restart history is 2*mmax*nrhs fine fields // (49GB/field global at 48^3x96,Ls=24). Default mmax=8, nrhs=12 needs // ~10TB for history; use OuterMmax / NRHS to fit the partition. // // Env vars: MASS, SUBSPACE_FILE, BLOCK (dotted e.g. 4.4.3.4), // NRHS (default 12, multiple of Nsimd), // FineSmootherShift, FineSmootherOrder, // CoarseSolverTol, CoarseSolverOrder, // OuterMmax, OuterNstep, OuterTol #include #include #include #include using namespace std; using namespace Grid; RealD FineSmootherShift = 0.1; int FineSmootherOrder = 16; RealD CoarseSolverTol = 0.03; int CoarseSolverOrder = 200; RealD OuterTol = 1.0e-8; int OuterMmax = 8; int OuterNstep = 8; int Nrhs = 12; RealD mass = 0.00078; void ParseEnvironment(void) { if(getenv("MASS")) mass = atof(getenv("MASS")); if(getenv("FineSmootherShift")) FineSmootherShift = atof(getenv("FineSmootherShift")); if(getenv("FineSmootherOrder")) FineSmootherOrder = atoi(getenv("FineSmootherOrder")); if(getenv("CoarseSolverTol")) CoarseSolverTol = atof(getenv("CoarseSolverTol")); if(getenv("CoarseSolverOrder")) CoarseSolverOrder = atoi(getenv("CoarseSolverOrder")); if(getenv("OuterTol")) OuterTol = atof(getenv("OuterTol")); if(getenv("OuterMmax")) OuterMmax = atoi(getenv("OuterMmax")); if(getenv("OuterNstep")) OuterNstep = atoi(getenv("OuterNstep")); if(getenv("NRHS")) Nrhs = atoi(getenv("NRHS")); std::cout << GridLogMessage << "PARAM: MASS " << mass << std::endl; std::cout << GridLogMessage << "PARAM: NRHS " << Nrhs << std::endl; std::cout << GridLogMessage << "PARAM: FineSmootherShift " << FineSmootherShift << std::endl; std::cout << GridLogMessage << "PARAM: FineSmootherOrder " << FineSmootherOrder << std::endl; std::cout << GridLogMessage << "PARAM: CoarseSolverTol " << CoarseSolverTol << std::endl; std::cout << GridLogMessage << "PARAM: CoarseSolverOrder " << CoarseSolverOrder << std::endl; std::cout << GridLogMessage << "PARAM: OuterTol " << OuterTol << std::endl; std::cout << GridLogMessage << "PARAM: OuterMmax " << OuterMmax << std::endl; std::cout << GridLogMessage << "PARAM: OuterNstep " << OuterNstep << 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); } }; ////////////////////////////////////////////////////////////////////// // Minimal multi-RHS function interface (preconditioner slot) ////////////////////////////////////////////////////////////////////// template class MrhsLinearFunction { public: virtual void operator()(std::vector &in, std::vector &out) = 0; }; ////////////////////////////////////////////////////////////////////// // Single-polynomial multi-RHS PGCR (non-Hermitian). // // Verbatim adaptation of PrecGeneralisedConjugateResidualNonHermitian // to std::vector: every innerProduct / norm2 is SUMMED over the // RHS index, so one alpha/beta per step is shared by all RHS -- the // single GCR on the enlarged block-diagonal system. ////////////////////////////////////////////////////////////////////// template class MrhsPGCRNonHermitian { public: RealD Tolerance; Integer MaxIterations; int mmax; int nstep; int steps; int level; int ZeroGuess = 0; int FirstCycle = 0; // caller contract: zero guess => first-cycle r0 = src std::string name = "Level 1"; LinearOperatorBase &Linop; MrhsLinearFunction &Preconditioner; void Level(int lv) { name = "Level " + std::to_string(lv); level=lv; }; void Name(std::string n) { name = n; }; void SetZeroGuess(int z) { ZeroGuess=z; }; MrhsPGCRNonHermitian(RealD tol,Integer maxit, LinearOperatorBase &_Linop, MrhsLinearFunction &Prec, int _mmax,int _nstep) : Tolerance(tol), MaxIterations(maxit), Linop(_Linop), Preconditioner(Prec), mmax(_mmax), nstep(_nstep) { level=1; } /////////////////////////////////////////////////////////////// // vector-of-fields linear algebra, reductions summed over rhs /////////////////////////////////////////////////////////////// static RealD vnorm2(std::vector &x){ RealD s=0.0; for(auto &f : x) s+=norm2(f); return s; } static ComplexD vinnerProduct(std::vector &x, std::vector &y){ ComplexD s(0.0); for(int r=0;r<(int)x.size();r++) s+=innerProduct(x[r],y[r]); return s; } static void vaxpy(std::vector &z, ComplexD a, std::vector &x, std::vector &y){ for(int r=0;r<(int)z.size();r++) axpy(z[r],a,x[r],y[r]); } void vOp(std::vector &in, std::vector &out){ for(int r=0;r<(int)in.size();r++) Linop.Op(in[r],out[r]); } void operator() (std::vector &src, std::vector &psi){ RealD cp, ssq, rsq; int nrhs = src.size(); GridBase *grid = src[0].Grid(); ssq=vnorm2(src); rsq=Tolerance*Tolerance*ssq; std::vector r(nrhs,grid); GridStopWatch SolverTimer; SolverTimer.Start(); steps=0; FirstCycle=1; for(int k=0;k &src, std::vector &psi, RealD rsq){ RealD cp; ComplexD a, b, rq; RealD zAAz; int nrhs = src.size(); GridBase *grid = src[0].Grid(); std::vector r (nrhs,grid); std::vector z (nrhs,grid); std::vector Az(nrhs,grid); //////////////////////////////// // history for flexible orthog: [mmax][nrhs] //////////////////////////////// std::vector< std::vector > q(mmax, std::vector(nrhs,grid)); std::vector< std::vector > p(mmax, std::vector(nrhs,grid)); std::vector qq(mmax); std::cout<(mmax-1))?(mmax-1):(kp); for(int back=0;back=0); b = -real(vinnerProduct(q[peri_back],Az))/qq[peri_back]; vaxpy(p[peri_kp],b,p[peri_back],p[peri_kp]); vaxpy(q[peri_kp],b,q[peri_back],q[peri_kp]); } qq[peri_kp]=vnorm2(q[peri_kp]); } GRID_ASSERT(0); // never reached return cp; } }; ////////////////////////////////////////////////////////////////////// // Trivial multi-RHS preconditioner ////////////////////////////////////////////////////////////////////// template class TrivialMrhsPrecon : public MrhsLinearFunction { public: virtual void operator()(std::vector &in, std::vector &out){ for(int r=0;r<(int)in.size();r++) out[r]=in[r]; } }; ////////////////////////////////////////////////////////////////////// // MultiRHS two-level V-cycle. // // Mirrors MGPreconditioner in Example_pvdagm: // out = in (trivial pre) [per rhs] // r1 = in - A out [per rhs] // batched blockProject -> pack -> ONE mrhs coarse PGCR -> unpack // -> batched blockPromote; out += correction // r2 = in - A out [per rhs] // per-RHS fine post-smoother; out += smooth(r2) ////////////////////////////////////////////////////////////////////// template class MrhsTwoLevelMG : public MrhsLinearFunction { public: typedef MrhsCoarseVector CoarseVector; // same lattice type on Coarse5d and CoarseMrhs LinearOperatorBase &_FineOperator; FineSmoother &_PostSmoother; // single-RHS smoother, looped MultiRHSBlockProject &_Projector; LinearFunction &_CoarseSolve; // PGCR on the 6D mrhs field GridBase *_CoarseGrid; // Coarse5d (single rhs) GridBase *_CoarseGridMrhs; // 6D MrhsTwoLevelMG(LinearOperatorBase &FineOp, FineSmoother &Post, MultiRHSBlockProject &Projector, LinearFunction &CoarseSolve, GridBase *CoarseGrid, GridBase *CoarseGridMrhs) : _FineOperator(FineOp), _PostSmoother(Post), _Projector(Projector), _CoarseSolve(CoarseSolve), _CoarseGrid(CoarseGrid), _CoarseGridMrhs(CoarseGridMrhs) {} virtual void operator()(std::vector &in, std::vector &out){ int nrhs = in.size(); GridBase *fgrid = in[0].Grid(); double t; std::vector vec1(nrhs,fgrid); std::vector vec2(nrhs,fgrid); // Trivial pre-smoother: out = in (as in Example_pvdagm with simple_fine) for(int r=0;rcoarse, pack rhs into 6D field std::vector Csrc_split(nrhs,_CoarseGrid); std::vector Csol_split(nrhs,_CoarseGrid); CoarseVector CsrcMrhs(_CoarseGridMrhs); CoarseVector CsolMrhs(_CoarseGridMrhs); t=-usecond(); _Projector.blockProject(vec1,Csrc_split); for(int r=0;rfine, add correction t=-usecond(); for(int r=0;r PVdagM_t; typedef ShiftedPVdagMLinearOperator ShiftedPVdagM_t; typedef GeneralCoarsenedMatrix LittleDiracOperator; typedef MultiGeneralCoarsenedMatrix MrhsLittleDiracOperator; typedef LittleDiracOperator::CoarseVector CoarseVector; typedef Aggregation Subspace; PVdagM_t PVdagM(Ddwf,Dpv); ShiftedPVdagM_t ShiftedPVdagM(FineSmootherShift,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); } //////////////////////////////////////////////////////////// // Coarsen once (single-RHS machinery), import into mrhs op. // NB CoarsenOperator block-orthogonalises subspace in place; // ImportBasis AFTER so the projector matches the coarse op. //////////////////////////////////////////////////////////// MrhsLittleDiracOperator mrhsLittleDiracOpPV(geom,CoarseMrhs); { // Scope the single-RHS operator so its padded _A is freed after import. // At small local volumes the depth-2 padded cell inflates ~8x // (e.g. 2^4 blocking on 432 ranks: local 8x4x4x12 -> padded 12x8x8x16, // ~23GB/GCD for _A alone -> OOM if kept alive). LittleDiracOperator LittleDiracOpPV(geom,FGrid,Coarse5d); LittleDiracOpPV.CoarsenOperator(PVdagM, AggregatesGCR); mrhsLittleDiracOpPV.CopyMatrix(LittleDiracOpPV); } MultiRHSBlockProject MrhsProjector; MrhsProjector.Allocate(nbasis,FGrid,Coarse5d); MrhsProjector.ImportBasis(AggregatesGCR.subspace); //////////////////////////////////////////////////////////// // Solvers //////////////////////////////////////////////////////////// NonHermitianLinearOperator mrhsLinOpCoarse(mrhsLittleDiracOpPV); TrivialPrecon simpleC; PrecGeneralisedConjugateResidualNonHermitian L2PGCRmrhs(CoarseSolverTol, CoarseSolverOrder/20, mrhsLinOpCoarse, simpleC, 20, 20); L2PGCRmrhs.Level(2); L2PGCRmrhs.Name("Couter"); L2PGCRmrhs.SetZeroGuess(1); // caller zeroes CsolMrhs TrivialPrecon simple_fine; PrecGeneralisedConjugateResidualNonHermitian SmootherGCR(0.0, 1, ShiftedPVdagM, simple_fine, FineSmootherOrder, FineSmootherOrder); SmootherGCR.Level(1); SmootherGCR.Name("Fsmoother"); SmootherGCR.SetZeroGuess(1); // caller zeroes vec2[r] typedef PrecGeneralisedConjugateResidualNonHermitian FineSmoother_t; MrhsTwoLevelMG TwoLevelPrecon(PVdagM, SmootherGCR, MrhsProjector, L2PGCRmrhs, Coarse5d, CoarseMrhs); MrhsPGCRNonHermitian L1PGCRmrhs(OuterTol, 1000, PVdagM, TwoLevelPrecon, OuterMmax, OuterNstep); L1PGCRmrhs.Level(1); L1PGCRmrhs.Name("Fouter"); L1PGCRmrhs.SetZeroGuess(1); // sol[r]=Zero() at source setup //////////////////////////////////////////////////////////// // Sources and solve //////////////////////////////////////////////////////////// std::vector src(nrhs,FGrid); std::vector sol(nrhs,FGrid); for(int r=0;r