/************************************************************************************* Grid physics library, www.github.com/paboyle/Grid Source file: ./tests/debug/Test_schur2d_scale.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 */ ////////////////////////////////////////////////////////////////////////////// // SCALE rehearsal for the 2D distributed dense inverse: the full // DENSE_SCHUR2D pipeline -- 1D rows -> redistribute -> invert -> // redistribute back -> certificate -- on a SYNTHETIC matrix of any size, // with no multigrid machinery, no configuration and no subspace file. // // This is the missing rung between the small-N oracle tests (which build // the whole matrix on every host, impossible at production N) and the // production example (which needs the full setup and 100 s of job time // before the inverse is even reached). Everything here is O(N^2/P) per // rank; at N=138240 on 288 ranks it is the production problem shape // exactly, in a driver that runs in minutes. // // S2D_N : global dimension (default 720, laptop friendly) // S2D_NB : block size (default N/P rows-per-rank if that // is exact, else 48) // // The matrix is diagonally dominant (the recursion does not pivot); its // conditioning is BENIGN, so this rehearses scale and speed, not the real // operator's numerics -- the production VERIFY does that. // // Certificate: Cert = A0 . Ainv by the (independently validated) SUMMA, // then every rank checks ITS OWN local elements against the identity. // One GlobalMax at the end to report; the pipeline itself is pure P2P. // // T1 : max|A.Ainv - I| < 1e-8 // T2 : round-trip redistribution of the INVERSE bitwise consistent // (CyclicToRows then RowsToCyclic reproduces the device data). ////////////////////////////////////////////////////////////////////////////// #include #include #include using namespace Grid; static ComplexD Fill(int64_t i, int64_t j, int64_t N) { double x = std::sin(0.7*i + 1.3*j); double y = std::cos(1.9*i - 0.4*j); if ( i==j ) return ComplexD(3.0*64 + x, 0.5); // dominance independent of N // band-limit the off-diagonal so row sums stay bounded as N grows: // only |i-j| <= 64 entries are non-zero => sum |offdiag| <= 128*1.42 < 3*64 if ( std::abs((double)(i-j)) > 64.0 ) return ComplexD(0.0,0.0); return ComplexD(x,y); } int main(int argc, char **argv) { Grid_init(&argc, &argv); GridCartesian *grid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(), GridDefaultSimd(Nd, vComplexD::Nsimd()), GridDefaultMpi()); const int P = grid->ProcessorCount(); const int me = grid->ThisRank(); int64_t N = getenv("S2D_N") ? atol(getenv("S2D_N")) : 720; int64_t nb; if ( getenv("S2D_NB") ) nb = atol(getenv("S2D_NB")); else if ( N % P == 0 ) nb = N/P; else nb = 48; GRID_ASSERT( N >= 1 ); GRID_ASSERT( nb >= 1 ); int Pr,Pc; BlockCyclicLayout::ChooseProcessGrid(P,Pr,Pc); // uniform-as-possible 1D ownership, as the production import produces std::vector rowStart(P+1); rowStart[0]=0; for(int r=0;r h((uint64_t)std::max(myrows,1)*N); thread_for(jj, N, { for(int64_t i=0;i rows1d(h.size()); acceleratorCopyToDevice(&h[0], &rows1d[0], h.size()*sizeof(ComplexD)); double t1 = usecond(); //////////////////////////////////////////////////////////////////////// // The DENSE_SCHUR2D pipeline, phase-timed. A0 keeps the original for // the certificate. //////////////////////////////////////////////////////////////////////// BlockCyclicMatrix A (grid,N,nb,Pr,Pc); BlockCyclicMatrix A0(grid,N,nb,Pr,Pc); BlockCyclicSchurInverse RSI2; BlockCyclicRedistribute::RowsToCyclic(grid,rowStart,&rows1d[0],myrows,A); double t2 = usecond(); if ( A.data.size() ) acceleratorCopyDeviceToDevice((void *)&A.data[0],(void *)&A0.data[0], A.data.size()*sizeof(ComplexD)); double t3 = usecond(); RSI2.Invert(A); double t4 = usecond(); BlockCyclicRedistribute::CyclicToRows(grid,rowStart,A,&rows1d[0],myrows); double t5 = usecond(); RSI2.ReportTelemetry(grid); //////////////////////////////////////////////////////////////////////// // T1 : certificate by SUMMA, checked locally, reported by one GlobalMax. //////////////////////////////////////////////////////////////////////// int failures = 0; { BlockCyclicMatrix Cert(grid,N,nb,Pr,Pc); BlockCyclicSumma SUMMA; SUMMA.Multiply(ComplexD(1.0,0.0),A0,A,ComplexD(0.0,0.0),Cert, 0,N,0,N,0,N); double t6 = usecond(); BlockCyclicLayout &L = Cert.layout; std::vector hc((uint64_t)std::max(L.mloc*L.nloc,1)); if ( L.mloc*L.nloc ) acceleratorCopyFromDevice(&Cert.data[0], &hc[0], (uint64_t)L.mloc*L.nloc*sizeof(ComplexD)); double mx = 0.0; for(int64_t lj=0;ljGlobalMax(gmx); if ( gmx > 1.0e-8 ) failures++; std::cout << GridLogMessage << "Test_schur2d_scale phases (s):" << " fill " << (t1-t0)/1.0e6 << " redist->2D " << (t2-t1)/1.0e6 << " invert " << (t4-t3)/1.0e6 << " redist->rows " << (t5-t4)/1.0e6 << " certify " << (t6-t5)/1.0e6 << std::endl; std::cout << GridLogMessage << "Test_schur2d_scale CERTIFICATE max|A.Ainv - I| = " << gmx << (gmx > 1.0e-8 ? " ** FAIL **" : " PASS") << std::endl; } //////////////////////////////////////////////////////////////////////// // T2 : the rows now hold the inverse; push them back out and compare // against A on device -- redistribution must be bitwise invertible on // real (non-synthetic-import) data too. //////////////////////////////////////////////////////////////////////// { BlockCyclicMatrix B(grid,N,nb,Pr,Pc); BlockCyclicRedistribute::RowsToCyclic(grid,rowStart,&rows1d[0],myrows,B); std::vector x((uint64_t)std::max(A.layout.mloc*A.layout.nloc,1)); std::vector y(x.size()); if ( A.layout.mloc*A.layout.nloc ){ acceleratorCopyFromDevice(&A.data[0], &x[0], x.size()*sizeof(ComplexD)); acceleratorCopyFromDevice(&B.data[0], &y[0], y.size()*sizeof(ComplexD)); } int bad = 0; for(uint64_t i=0;iGlobalSum(gbad); if ( gbad ) failures++; std::cout << GridLogMessage << "Test_schur2d_scale ROUND TRIP mismatches = " << gbad << (gbad ? " ** FAIL **" : " PASS") << std::endl; } { uint64_t f = failures; grid->GlobalSum(f); failures = (int)f; } std::cout << GridLogMessage << (failures ? "Test_schur2d_scale: FAILURES" : "Test_schur2d_scale: ALL PASS") << std::endl; Grid_finalize(); return failures ? 1 : 0; }