diff --git a/examples/Example_pvdagm_coherence.cc b/examples/Example_pvdagm_coherence.cc new file mode 100644 index 000000000..e76815b17 --- /dev/null +++ b/examples/Example_pvdagm_coherence.cc @@ -0,0 +1,464 @@ +/************************************************************************************* + + Grid physics library, www.github.com/paboyle/Grid + + Source file: ./examples/Example_pvdagm_coherence.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 */ + +// Local-coherence census of the COARSE operator's low singular vectors. +// (Fig 4 of arXiv:2409.03904 transplanted one level down.) +// +// Inputs: the raw fine subspace cache AND the coarse right-singular +// vectors saved by Example_pvdagm_census (CENSUS_EVEC_FILE). +// +// Measurements: +// CHECK 0 u/v alignment per mode: u_k = A v_k/||A v_k||, align=||. +// Low-sector non-normality meter AND the "2-for-1 coupon" test +// (u adds deflation span iff align is small). Also re-measures +// sigma_k = ||A v_k|| as an evec-file integrity check. +// CHECK 1 Band overlap: |projection of v_k onto span(psi_c)|^2 where +// psi_c = coarse images of the 60 RAW fine null vectors. +// Detached-vs-submerged Ritz band question. +// CHECK A Self-coherence: level-2 blocks (BLOCK2) with basis = evecs +// 0..K-1 (block-orthonormalised); completeness of held-out +// evecs K..NEV-1. Local coherence of the coarse op's own tail. +// CHECK B Capture by the available basis: same blocks, basis = psi_c; +// completeness of every evec. What a 3-level would have had. +// GS census (inside every block orthonormalisation): per-vector global +// remainder fraction and MIN-over-blocks remainder fraction. +// Detects finite-precision rank loss (near-parallel vectors +// within a block -> normalised noise). Values ~0 flag fiction +// in the "orthonormal" basis. +// Idempotency assertion ||(PP+)^2 v - PP+ v||/||PP+ v|| ~ eps on the +// first probe of each check: catches non-orthonormal-basis and +// rank-loss corruption in the checks themselves. +// +// Level-2 machinery uses the free block primitives directly on +// std::vector, so basis rank K is a RUNTIME parameter +// (no Aggregation template instantiation at level 2). +// +// Env: +// MASS, SUBSPACE_FILE as elsewhere (cache MUST exist) +// CENSUS_EVEC_FILE REQUIRED: evecs from the census run +// NEV number of evecs to load (default 96) +// NBASIS2 K, basis size for CHECK A (default 48) +// BLOCK fine->coarse blocking; MUST match the +// census run that made the evecs (4.4.4.4) +// BLOCK2 coarse->coarsecoarse blocking (2.2.2.2) + +#include +#include +#include + +using namespace std; +using namespace Grid; + +RealD mass = 0.00078; +int Nev = 96; +int Nbasis2 = 48; + +void ParseEnvironment(void) +{ + if(getenv("MASS")) mass = atof(getenv("MASS")); + if(getenv("NEV")) Nev = atoi(getenv("NEV")); + if(getenv("NBASIS2")) Nbasis2 = atoi(getenv("NBASIS2")); + std::cout << GridLogMessage << "PARAM: MASS " << mass << std::endl; + std::cout << GridLogMessage << "PARAM: NEV " << Nev << std::endl; + std::cout << GridLogMessage << "PARAM: NBASIS2 " << Nbasis2 << std::endl; +} + +template +void loadFields(std::vector &v, std::string const fname){ +#ifdef HAVE_LIME + std::cout << Grid::GridLogMessage << "Loading " << v.size() << " fields from: " << fname << std::endl; + Grid::emptyUserRecord record; + Grid::ScidacReader SR; + SR.open(fname); + for (int k = 0; k < (int)v.size(); k++) + SR.readScidacFieldRecord(v[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); + } +}; + +////////////////////////////////////////////////////////////////////// +// Block Gram-Schmidt with remainder census. +// Mirrors blockOrthonormalize (one GS pass + normalise) but reports, +// per vector: the GLOBAL remainder fraction ||v_k^perp||^2/||v_k||^2 +// and the MIN over blocks of the same ratio. A second silent pass +// tightens orthonormality for the completeness measurements. +////////////////////////////////////////////////////////////////////// +template +void blockGSCensus(std::vector &basis, GridBase *ccGrid, const std::string tag) +{ + int n = basis.size(); + CField ip(ccGrid); + CField n0(ccGrid), n1(ccGrid); + + std::cout << GridLogMessage << "=== block GS census [" << tag << "] : " << n + << " vectors, blocks -> " << ccGrid->GlobalDimensions() << std::endl; + + for(int v=0; vlSites(); s++){ + Coordinate lcoor(ccGrid->_ndimension); + ccGrid->LocalIndexToLocalCoor(s,lcoor); + sobj s0, s1; + peekLocalSite(s0, n0, lcoor); + peekLocalSite(s1, n1, lcoor); + RealD r0 = real(TensorRemove(s0)); + RealD r1 = real(TensorRemove(s1)); + if ( r0 > 0.0 ) { + RealD frac = r1/r0; + if (frac < locmin) locmin = frac; + } + } + RealD neg = -locmin; + ccGrid->GlobalMax(neg); + locmin = -neg; + } + + std::cout << GridLogMessage << "GS[" << tag << "] vec " << v + << " global remainder " << after/before + << " min-block remainder " << locmin + << ( locmin < 1.0e-10 ? " <== RANK LOSS FLAG" : "" ) << std::endl; + + blockNormalise(ip, basis[v]); + } + // Second (silent) pass for numerical hygiene of downstream projections + for(int v=0; v +void blockPPdag(const std::vector &q, GridBase *ccGrid, const Field &v, Field &w) +{ + CField ip(ccGrid); + w = Zero(); + for(int j=0; j<(int)q.size(); j++){ + blockInnerProduct(ip, q[j], v); + blockZAXPY(w, ip, q[j], w); + } +} + +int main (int argc, char ** argv) +{ + Grid_init(&argc,&argv); + ParseEnvironment(); + + const int Ls=24; + RealD M5=1.8, b=1.5, c=0.5; + const int nbasis = 60; + + std::vector lat_size {48, 48, 48, 96}; + + GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(lat_size, GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi()); + GridRedBlackCartesian * UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid); + GridCartesian * FGrid = SpaceTimeGrid::makeFiveDimGrid(Ls,UGrid); + GridRedBlackCartesian * FrbGrid = SpaceTimeGrid::makeFiveDimRedBlackGrid(Ls,UGrid); + + // Fine->coarse blocking: MUST match the census run that made the evecs. + Coordinate clatt = lat_size; + Coordinate Block({4,4,4,4}); + if ( getenv("BLOCK") ) { + GridCmdOptionIntVector(std::string(getenv("BLOCK")),Block); + GRID_ASSERT(Block.size()==4); + } + for(int d=0;dcoarsecoarse blocking for the coherence checks + Coordinate cclatt = clatt; + Coordinate Block2({2,2,2,2}); + if ( getenv("BLOCK2") ) { + GridCmdOptionIntVector(std::string(getenv("BLOCK2")),Block2); + GRID_ASSERT(Block2.size()==4); + } + for(int d=0;d PVdagM_t; + typedef GeneralCoarsenedMatrix LittleDiracOperator; + typedef LittleDiracOperator::CoarseVector CoarseVector; + // Grid index contraction is positional (colour/spin/lorentz order is meaningful), + // so each MG projection adds one index to the tensor nest rather than reusing a slot: + // innerProduct(CoarseSiteObj,CoarseSiteObj) returns iScalar, one level + // deeper than the fine-level vTComplex (same convention as Example_pvdagm_3level.cc). + typedef Lattice > CoarseCoarseScalar; + typedef Aggregation Subspace; + + PVdagM_t PVdagM(Ddwf,Dpv); + NextToNearestStencilGeometry5D geom(Coarse5d); + + ////////////////////////////////////////////////////////////////////// + // Load the RAW subspace (cache REQUIRED; no generation here) + ////////////////////////////////////////////////////////////////////// + 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); + if ( !file_exists ) { + std::cout << GridLogMessage << "FATAL: subspace cache not found: " << subspace_file << std::endl; + GRID_ASSERT(file_exists); + } + + const int cb = 0; + Subspace AggregatesGCR(Coarse5d,FGrid,cb); + loadFields(AggregatesGCR.subspace, subspace_file); + + // RAW copy before CoarsenOperator block-orthogonalises in place + std::vector rawNull(nbasis, FGrid); + for (int k = 0; k < nbasis; k++) rawNull[k] = AggregatesGCR.subspace[k]; + + LittleDiracOperator LittleDiracOpPV(geom,FGrid,Coarse5d); + LittleDiracOpPV.CoarsenOperator(PVdagM, AggregatesGCR); + NonHermitianLinearOperator LinOpCoarse(LittleDiracOpPV); + + // Coarse images of the raw null vectors + std::vector psi_c(nbasis, Coarse5d); + for (int k = 0; k < nbasis; k++) + AggregatesGCR.ProjectToSubspace(psi_c[k], rawNull[k]); + + ////////////////////////////////////////////////////////////////////// + // Load census singular vectors + ////////////////////////////////////////////////////////////////////// + GRID_ASSERT(getenv("CENSUS_EVEC_FILE")); + std::string evec_file(getenv("CENSUS_EVEC_FILE")); + std::vector evec(Nev, Coarse5d); + loadFields(evec, evec_file); + + GRID_ASSERT(Nbasis2 < Nev); + + ////////////////////////////////////////////////////////////////////// + // CHECK 0: sigma re-measurement and u/v alignment per mode + ////////////////////////////////////////////////////////////////////// + std::cout << GridLogMessage << "=================================================" << std::endl; + std::cout << GridLogMessage << "CHECK 0: sigma and left/right alignment per mode" << std::endl; + std::cout << GridLogMessage << "=================================================" << std::endl; + { + CoarseVector Av(Coarse5d); + for(int k=0;k|, u = Av/||Av|| + std::cout << GridLogMessage << "COHERENCE: mode " << k + << " sigma = " << sigma + << " || = " << align << std::endl; + } + } + + ////////////////////////////////////////////////////////////////////// + // CHECK 1: band overlap of each evec with span(psi_c) (GLOBAL span) + ////////////////////////////////////////////////////////////////////// + std::cout << GridLogMessage << "=================================================" << std::endl; + std::cout << GridLogMessage << "CHECK 1: evec overlap with GLOBAL span of 60 psi_c" << std::endl; + std::cout << GridLogMessage << "=================================================" << std::endl; + { + // Global (not block) Gram-Schmidt of copies of psi_c + std::vector Q(nbasis, Coarse5d); + for(int k=0;k0.0); + Q[k] = Q[k] * (1.0/std::sqrt(nq)); + } + for(int k=0;k basisA(Nbasis2, Coarse5d); + for(int k=0;k(basisA, CC5d, "A:evecs"); + + CoarseVector w(Coarse5d), w2(Coarse5d); + // Idempotency assertion on first probe + blockPPdag(basisA, CC5d, evec[Nbasis2], w); + blockPPdag(basisA, CC5d, w, w2); + w2 = w2 - w; + RealD idem = std::sqrt(norm2(w2)/norm2(w)); + std::cout << GridLogMessage << "COHERENCE: CHECK A idempotency = " << idem + << " (expect ~1e-14; O(1) => basis corrupt)" << std::endl; + + RealD sum=0.0; + for(int k=Nbasis2;k(basisA, CC5d, evec[k], w); + RealD comp = norm2(w)/norm2(evec[k]); + sum += comp; + std::cout << GridLogMessage << "COHERENCE: A completeness mode " << k + << " = " << comp << std::endl; + } + std::cout << GridLogMessage << "COHERENCE: A mean completeness (modes " << Nbasis2 + << ".." << Nev-1 << ") = " << sum/(Nev-Nbasis2) << std::endl; + } + + ////////////////////////////////////////////////////////////////////// + // CHECK B: capture by the available basis -- basis = 60 psi_c in + // BLOCK2 blocks, probes = ALL evecs + ////////////////////////////////////////////////////////////////////// + std::cout << GridLogMessage << "=================================================" << std::endl; + std::cout << GridLogMessage << "CHECK B: capture by blocked psi_c basis" << std::endl; + std::cout << GridLogMessage << "=================================================" << std::endl; + { + std::vector basisB(nbasis, Coarse5d); + for(int k=0;k(basisB, CC5d, "B:psi_c"); + + CoarseVector w(Coarse5d), w2(Coarse5d); + blockPPdag(basisB, CC5d, evec[0], w); + blockPPdag(basisB, CC5d, w, w2); + w2 = w2 - w; + RealD idem = std::sqrt(norm2(w2)/norm2(w)); + std::cout << GridLogMessage << "COHERENCE: CHECK B idempotency = " << idem + << " (expect ~1e-14; O(1) => basis corrupt)" << std::endl; + + RealD sumLow=0.0, sumHigh=0.0; int nLow=0, nHigh=0; + for(int k=0;k(basisB, CC5d, evec[k], w); + RealD comp = norm2(w)/norm2(evec[k]); + if (k| ~ 1 : low sector effectively normal; 2-for-1 coupon worthless" << std::endl; + std::cout << GridLogMessage << " CHECK 1 high for low k : Ritz band submerged in continuum (not detached)" << std::endl; + std::cout << GridLogMessage << " CHECK A ~0.98 : local coherence manifests at coarse level (3-level rescuable)" << std::endl; + std::cout << GridLogMessage << " CHECK A ~0.8-0.9 : capture deficit is physical (effective-blocking law)" << std::endl; + std::cout << GridLogMessage << " CHECK B << CHECK A : psi_c basis fails to exploit coherence that exists" << std::endl; + std::cout << GridLogMessage << " GS min-block remainder ~0: rank loss; orthogonalisation partly fictional" << std::endl; + std::cout << GridLogMessage << "=================================================" << std::endl; + + std::cout << GridLogMessage << "Done" << std::endl; + Grid_finalize(); + return 0; +}