mirror of
https://github.com/paboyle/Grid.git
synced 2026-08-27 04:49:36 +01:00
202 lines
9.7 KiB
C++
202 lines
9.7 KiB
C++
/*************************************************************************************
|
|
|
|
Grid physics library, www.github.com/paboyle/Grid
|
|
|
|
Source file: ./tests/debug/Test_poly_smoother.cc
|
|
|
|
Copyright (C) 2026
|
|
|
|
Author: Peter Boyle <pboyle@bnl.gov>
|
|
|
|
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 <Grid/Grid.h>
|
|
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
|
|
|
|
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 Field>
|
|
class ShiftedOp : public LinearOperatorBase<Field> {
|
|
LinearOperatorBase<Field> &_Op; RealD shift;
|
|
public:
|
|
ShiftedOp(RealD s, LinearOperatorBase<Field> &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<Field> &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<class Field>
|
|
RealD Residual(LinearOperatorBase<Field> &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<int> seeds({1,2,3,4});
|
|
GridParallelRNG RNG4(UGrid); RNG4.SeedFixedIntegers(seeds);
|
|
|
|
LatticeGaugeFieldD Umu(UGrid);
|
|
SU<Nc>::HotConfiguration(RNG4, Umu);
|
|
|
|
RealD mass = 0.5, shift = 0.1;
|
|
WilsonFermionD Dw(Umu, *UGrid, *UrbGrid, mass);
|
|
NonHermitianLinearOperator<WilsonFermionD, LatticeFermionD> Op(Dw);
|
|
ShiftedOp<LatticeFermionD> SOp(shift, Op);
|
|
TrivialPrecon<LatticeFermionD> simple;
|
|
|
|
const int mmax = 2, nstep = 8, ncal = 16;
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// record
|
|
//////////////////////////////////////////////////////////////////////
|
|
PrecGeneralisedConjugateResidualNonHermitian<LatticeFermionD> 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<ncal;c++){ gaussian(RNG4,src); x = Zero(); GCR(src,x); }
|
|
GCR.SetCoefficientRecorder(nullptr);
|
|
rec.Flush();
|
|
rec.Report("smoother");
|
|
Report("record: steps and calls", rec.Steps()==nstep && rec.Calls()==ncal,
|
|
std::to_string(rec.Steps())+" steps, "+std::to_string(rec.Calls())+" calls");
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// fresh source: live GCR vs replay
|
|
//////////////////////////////////////////////////////////////////////
|
|
gaussian(RNG4,src);
|
|
LatticeFermionD xg(UGrid), xr(UGrid), xr2(UGrid), xc(UGrid), d(UGrid);
|
|
xg = Zero(); GCR(src,xg);
|
|
RealD rg = Residual(SOp,src,xg);
|
|
|
|
GCRReplaySmoother<LatticeFermionD> 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<LatticeFermionD> 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<WilsonFermionD, LatticeFermionD> 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<LatticeFermionD> S4(0.5, 1.05*hhi, 12, HermOp);
|
|
ChebyshevSmoother<LatticeFermionD, WilsonFermionD> 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<LatticeFermionD> {
|
|
LinearOperatorBase<LatticeFermionD> &H;
|
|
HermAsOp(LinearOperatorBase<LatticeFermionD> &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<LatticeFermionD> &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<LatticeFermionD> 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;
|
|
}
|