/************************************************************************************* Grid physics library, www.github.com/paboyle/Grid Source file: ./tests/debug/Test_poly_smoother.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. See the full license in the file "LICENSE" in the top level distribution directory *************************************************************************************/ /* END LEGAL */ ////////////////////////////////////////////////////////////////////////////// // Fixed-polynomial smoothers (Smoothers.h) against the adaptive GCR they // replace, on a shifted Wilson operator (non-Hermitian, spectrum in the right // half plane): // // record : GCR(mmax=2, 8 steps) with a coefficient recorder on 16 sources // T1 : GCRReplaySmoother on a FRESH source reduces the residual to // within a factor 2 of the live GCR on the same source // T2 : replay and GCR solutions agree to the coefficient spread (<10%) // T3 : ChebyshevNonHermitianSmoother with Op=HermOp reproduces the legacy // HermOp ChebyshevSmoother (real spectrum); on the complex-spectrum // Wilson op its (poor) reduction is printed for information only // T4 : the historical HermOp ChebyshevSmoother compiles with both // constructor signatures and reduces the MdagM residual // T5 : replay is bitwise repeatable (no reductions => no reordering) // // mpirun -n 1 ./Test_poly_smoother --grid 8.8.8.8 --mpi 1.1.1.1 ////////////////////////////////////////////////////////////////////////////// #include #include using namespace Grid; static int failures = 0; static void Report(const std::string &name, bool pass, const std::string &detail="") { std::cout << GridLogMessage << " " << name << (pass ? " PASS" : " ** FAIL **"); if ( detail.size() ) std::cout << " " << detail; std::cout << std::endl; if ( !pass ) failures++; } template class ShiftedOp : public LinearOperatorBase { LinearOperatorBase &_Op; RealD shift; public: ShiftedOp(RealD s, LinearOperatorBase &Op) : _Op(Op), shift(s) {} void OpDiag (const Field &in, Field &out) { GRID_ASSERT(0); } void OpDir (const Field &in, Field &out,int dir,int disp) { GRID_ASSERT(0); } void OpDirAll(const Field &in, std::vector &out) { GRID_ASSERT(0); } void Op (const Field &in, Field &out) { _Op.Op(in,out); out = out + shift*in; } void AdjOp (const Field &in, Field &out) { _Op.AdjOp(in,out); out = out + shift*in; } void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ GRID_ASSERT(0); } void HermOp (const Field &in, Field &out) { Field tmp(in.Grid()); Op(in,tmp); AdjOp(tmp,out); } }; template RealD Residual(LinearOperatorBase &Op, const Field &src, const Field &x) { Field r(src.Grid()); Op.Op(x,r); r = r - src; return std::sqrt(norm2(r)/norm2(src)); } int main(int argc, char **argv) { Grid_init(&argc, &argv); GridCartesian *UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(), GridDefaultSimd(Nd, vComplexD::Nsimd()), GridDefaultMpi()); GridRedBlackCartesian *UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid); std::vector seeds({1,2,3,4}); GridParallelRNG RNG4(UGrid); RNG4.SeedFixedIntegers(seeds); LatticeGaugeFieldD Umu(UGrid); SU::HotConfiguration(RNG4, Umu); RealD mass = 0.5, shift = 0.1; WilsonFermionD Dw(Umu, *UGrid, *UrbGrid, mass); NonHermitianLinearOperator Op(Dw); ShiftedOp SOp(shift, Op); TrivialPrecon simple; const int mmax = 2, nstep = 8, ncal = 16; ////////////////////////////////////////////////////////////////////// // record ////////////////////////////////////////////////////////////////////// PrecGeneralisedConjugateResidualNonHermitian GCR(0.0, 1, SOp, simple, mmax, nstep); GCR.SetZeroGuess(1); GCR.Name("smoother"); GCRCoefficients rec; GCR.SetCoefficientRecorder(&rec); LatticeFermionD src(UGrid), x(UGrid); for(int c=0;c Replay(SOp, rec); Replay(src,xr); RealD rr = Residual(SOp,src,xr); Report("T1 replay residual within 2x of live GCR", rr < 2.0*rg, "GCR |r|/|r0| = "+std::to_string(rg)+" replay "+std::to_string(rr)); d = xr - xg; RealD rel = std::sqrt(norm2(d)/norm2(xg)); Report("T2 replay solution vs GCR solution", rel < 0.1, "rel "+std::to_string(rel)); ////////////////////////////////////////////////////////////////////// // Chebyshev 1/x on [lo,hi], hi from a power iteration on SOp ////////////////////////////////////////////////////////////////////// RealD hi; { LatticeFermionD v(UGrid), Av(UGrid); gaussian(RNG4,v); RealD n = std::sqrt(norm2(v)); v = v*(1.0/n); for(int i=0;i<60;i++){ SOp.Op(v,Av); hi = std::sqrt(norm2(Av)); v = Av*(1.0/hi); } } RealD lo = 0.5; std::cout << GridLogMessage << "power iteration |lambda_max| ~ " << hi << " Chebyshev range [" << lo << "," << 1.05*hi << "]" << std::endl; ChebyshevNonHermitianSmoother Cheb(lo, 1.05*hi, nstep, SOp); Cheb(src,xc); RealD rc = Residual(SOp,src,xc); // Not gated: Wilson's spectrum has O(1) imaginary parts and a real-interval // Chebyshev fit to 1/x degrades exponentially off the axis (Bernstein // ellipse). Printed as the reminder of what a non-real spectrum does. std::cout << GridLogMessage << " (info) ChebyshevNonHermitian on the COMPLEX-spectrum Wilson op: |r|/|r0| = " << rc << " (GCR " << rg << ") -- expected poor; PVdagM smoother ops have real coefficients" << std::endl; ////////////////////////////////////////////////////////////////////// // legacy HermOp Chebyshev smoother, both constructor forms, on MdagM ////////////////////////////////////////////////////////////////////// { MdagMLinearOperator HermOp(Dw); LatticeFermionD hsrc(UGrid), hx(UGrid), hr(UGrid); gaussian(RNG4,hsrc); RealD hhi = 0.0; { LatticeFermionD v(UGrid), Av(UGrid); gaussian(RNG4,v); RealD n=std::sqrt(norm2(v)); v=v*(1.0/n); for(int i=0;i<40;i++){ HermOp.HermOp(v,Av); hhi=std::sqrt(norm2(Av)); v=Av*(1.0/hhi); } } ChebyshevSmoother S4(0.5, 1.05*hhi, 12, HermOp); ChebyshevSmoother S5(0.5, 1.05*hhi, 12, HermOp, Dw); // historical 5-arg form S4(hsrc,hx); HermOp.HermOp(hx,hr); hr = hr - hsrc; RealD r4 = std::sqrt(norm2(hr)/norm2(hsrc)); S5(hsrc,hx); HermOp.HermOp(hx,hr); hr = hr - hsrc; RealD r5 = std::sqrt(norm2(hr)/norm2(hsrc)); Report("T4 legacy ChebyshevSmoother (4- and 5-arg) reduces MdagM residual", r4 < 0.5 && r5 == r4, "|r|/|r0| = "+std::to_string(r4)+" / "+std::to_string(r5)); // T3: the Op()-based Clenshaw on a REAL-spectrum operator must reproduce // the legacy HermOp smoother: same coefficients, same recurrence. struct HermAsOp : public LinearOperatorBase { LinearOperatorBase &H; HermAsOp(LinearOperatorBase &h) : H(h) {} void OpDiag (const LatticeFermionD &in, LatticeFermionD &out) { GRID_ASSERT(0); } void OpDir (const LatticeFermionD &in, LatticeFermionD &out,int dir,int disp) { GRID_ASSERT(0); } void OpDirAll(const LatticeFermionD &in, std::vector &out) { GRID_ASSERT(0); } void Op (const LatticeFermionD &in, LatticeFermionD &out) { H.HermOp(in,out); } void AdjOp (const LatticeFermionD &in, LatticeFermionD &out) { H.HermOp(in,out); } void HermOpAndNorm(const LatticeFermionD &in, LatticeFermionD &out,RealD &n1,RealD &n2){ GRID_ASSERT(0); } void HermOp (const LatticeFermionD &in, LatticeFermionD &out) { H.HermOp(in,out); } } HOp(HermOp); ChebyshevNonHermitianSmoother C3(0.5, 1.05*hhi, 12, HOp); LatticeFermionD hx3(UGrid), dd(UGrid); C3(hsrc,hx3); HermOp.HermOp(hx3,hr); hr = hr - hsrc; RealD r3 = std::sqrt(norm2(hr)/norm2(hsrc)); S4(hsrc,hx); dd = hx - hx3; RealD reldiff = std::sqrt(norm2(dd)/norm2(hx)); Report("T3 ChebyshevNonHermitian(Op=HermOp) == legacy ChebyshevSmoother", r3 < 0.5 && reldiff < 1.0e-12, "|r|/|r0| = "+std::to_string(r3)+" rel diff of solutions "+std::to_string(reldiff)); } ////////////////////////////////////////////////////////////////////// // determinism ////////////////////////////////////////////////////////////////////// Replay(src,xr2); d = xr - xr2; Report("T5 replay bitwise repeatable", norm2(d)==0.0); std::cout << GridLogMessage << (failures ? "Test_poly_smoother: FAILURES" : "Test_poly_smoother: ALL PASS") << std::endl; Grid_finalize(); return failures ? 1 : 0; }