Compare commits

...
139 Commits
Author SHA1 Message Date
Chulwoo Jung 01c3189d17 Reverted restart to inline in KrylovSchur
Making constructUR to use basisRotate()
2026-07-13 14:38:50 -04:00
Chulwoo Jung f1249685aa Refactording KrylovSchur to have separate operator() for non-shift and shift 2026-07-10 17:39:36 -04:00
Chulwoo Jung 722d68b0d9 Updating KrylovSchur example to include True Block KS 2026-07-10 14:02:22 -04:00
Chulwoo Jung c1284e080a Adding TrueHarmonicBlockKrylovSchur 2026-07-09 20:38:08 -04:00
Chulwoo Jung d2189d59e0 Adding parity flip and gamma 5 option for Block KS starting vector 2026-06-16 16:05:06 -04:00
Chulwoo Jung e862dd662b Commenting out debugging info 2026-05-26 15:28:17 -04:00
Chulwoo Jung 42ff77cec8 Merge branch 'KS_shifted' of github.com:chulwoo1/Grid into KS_shifted 2026-05-21 10:49:21 -04:00
Chulwoo Jung d11ad4d2f0 Change below threshold output cerr to force output from every node` 2026-05-21 10:48:23 -04:00
Chulwoo Jung 9ebf2fec79 SciDAC format outputs for below threshold plaquettes 2026-05-21 09:39:08 -04:00
Chulwoo Jung ab0d6d3706 Merge branch 'KS_shifted' of github.com:chulwoo1/Grid into KS_shifted 2026-05-08 18:54:47 -04:00
Chulwoo Jung 4aa96a16c5 Merge branch 'KS_shifted' of github.com:chulwoo1/Grid into KS_shifted 2026-05-08 18:54:39 -04:00
Chulwoo Jung bb99123e6d Adding threshold in Plaquette testing fft5d bugfix 2026-05-08 18:53:30 -04:00
Chulwoo Jung 9e8cba3944 Reinstating HarmonicBlockKS 2026-05-08 17:30:17 -04:00
Chulwoo Jung d5cab295b2 Still debugging Gamma5BlockLanczos, fft5d 2026-05-08 16:47:46 -04:00
Chulwoo Jung 5a67f0602c Broken 2026-05-01 19:28:38 -04:00
Chulwoo Jung d57c49d73d iChanging to 2 starting vectors 2026-05-01 17:44:07 -04:00
Chulwoo Jung 5754c965d5 More debugging outputs 2026-05-01 11:35:45 -04:00
Chulwoo Jung 64ee6e90c9 Still debugging Gamma5BlockLanczos restart 2026-04-25 02:38:40 -04:00
Chulwoo Jung 7752e7ead1 Sorting eval/evec according to RitzFilter 2026-04-23 16:28:48 -04:00
Chulwoo Jung f14be5e331 Implicit restart in Gamma5BlockLanczos 2026-04-22 23:42:41 -04:00
Chulwoo Jung 5a77a4a052 Adding verify() routines to gamma5BlockLanczos 2026-04-20 17:19:44 -04:00
Chulwoo Jung 83b752bdd3 First (Claude) try to add Gamma5BlockLanczos 2026-04-20 12:23:44 -04:00
Chulwoo Jung 34a6ba3475 Merge branch 'KS_shifted' of github.com:chulwoo1/Grid into KS_shifted 2026-04-17 21:01:02 -04:00
Chulwoo Jung 819f706778 Merge branch 'KS_shifted' of github.com:chulwoo1/Grid into KS_shifted 2026-04-17 16:35:17 -04:00
Chulwoo Jung 3b28e3c98f Added flags for explicit eval checks at the end of operator() 2026-04-17 11:08:34 -04:00
Chulwoo Jung a696953485 Finally Block Harmonic(?) KS working 2026-04-14 14:40:55 -04:00
Chulwoo Jung aecc50869c Another try at block Harmonic KS. Still not working 2026-04-09 21:13:41 -04:00
Chulwoo Jung 99be36c891 Fixing tests/core/Test_plaquette_stats 2026-04-08 23:37:26 -04:00
Chulwoo Jung af1fb41d55 Precision reduction in Block KS verify() for better readability
Adding Plaquette stat testing
2026-04-08 22:50:37 -04:00
Chulwoo Jung b4c77ca762 Commenting out files not checked in 2026-04-06 17:24:23 -04:00
Chulwoo Jung 82a8f1342e Adding Lanczos parameter file in examples directory 2026-04-06 13:35:43 -04:00
Chulwoo Jung d4b685cf2d Expanding debug output after restart in block KS 2026-04-06 12:00:33 -04:00
Chulwoo Jung 84707cc3a8 Changing definition of Nm and Nk in BlockKrylovSchur 2026-04-02 15:22:21 -04:00
Chulwoo Jung 88ea24687f Blocked->Block. SplitGridBlockKrylovSchur 2026-04-02 12:25:55 -04:00
Chulwoo Jung 167f94e86c Adding (Harmonic) Block KS 2026-03-31 12:36:18 -04:00
Chulwoo Jung 09aa843984 Changed batchedInnerProduct for portability 2026-03-17 18:54:18 -04:00
Chulwoo Jung 24752002fa Verbosity reduction batched inner product for reorthogonalization 2026-03-17 13:02:16 -04:00
Chulwoo Jung f3223021fd RestartedLanczosBidiagonalization seems to have been fixed 2026-03-16 14:34:56 -04:00
Chulwoo Jung 4e1d95d3bb Claude implementation of Thick Restarted Lanczos Bidiagonalization 2026-03-13 19:12:54 -04:00
Chulwoo Jung 2ed38f63ca Merge branch 'develop' of https://github.com/paboyle/Grid into KS_shifted 2026-03-12 10:49:21 -04:00
Chulwoo Jung 80d2a8d88d wqMerge branch 'develop' of https://github.com/paboyle/Grid into KS_shifted 2026-03-11 21:49:26 -04:00
Peter Boyle 595ceaac37 Include grid header and make the ENABLE correct 2026-03-11 17:24:44 -04:00
Peter Boyle daf5834e8e Fixing incorrect PR about disable fermion instantiations 2026-03-11 17:05:46 -04:00
Chulwoo Jung 2ac5431401 Turning of NERSC header checking 2026-03-06 14:16:20 -05:00
Peter Boyle 0d8658a039 Optimised 2026-03-05 06:06:32 -05:00
Peter Boyle 095e004d01 Setup change GCR 2026-03-05 06:06:32 -05:00
Peter Boyle 0acabee7f6 Modest change 2026-03-05 06:06:32 -05:00
Peter Boyle 76fbcffb60 Improvement to 16^3 hdcg 2026-03-05 06:06:32 -05:00
Peter BoyleandGitHub a0a62d7ead Merge pull request #478 from vataspro/PolyakovUpstream
Spatial Polyakov Loop implementation
2026-02-24 20:45:42 -05:00
Peter BoyleandGitHub c5038ea6a5 Merge pull request #483 from cmcknigh/bugfix/rocm7-rocblas-type-refactor
Adding a version check to handle rocBlas type refactor
2026-02-24 20:45:03 -05:00
Peter BoyleandGitHub a5120903eb Merge pull request #486 from RChrHill/fix/sp4-fp32
Define Sp4 ProjectOnGeneralGroup for generic vtype
2026-02-24 20:44:08 -05:00
Peter BoyleandGitHub 00b286a08a Merge pull request #488 from RChrHill/feature/additional-ET-traces
Add ET support for Lattice spin- and colour-traces
2026-02-24 20:43:45 -05:00
Peter BoyleandGitHub 24a9759353 Merge pull request #485 from edbennett/skip-fermion-instantiations
Be able to skip compiling fermion instantiations altogether
2026-02-24 20:43:20 -05:00
edbennett 1b56f6f46d be able to skip compiling fermion instantiations altogether 2026-02-24 23:52:18 +00:00
Peter Boyle 2a8084d569 Subspace setup 2026-02-13 17:26:11 -05:00
Peter Boyle 6ff29f9d4f Alternate multigrids 2026-02-13 17:25:45 -05:00
RChHill c4d3e79193 Add ET support for Lattice spin- and colour-traces 2026-01-29 14:46:52 +00:00
Chulwoo Jung 3e71cac7ae Merge branch 'specflow2' of github.com:chulwoo1/Grid into KS_shifted 2026-01-15 03:40:52 +00:00
Chulwoo Jung e8e7ef08fc KrylovSchur and spectral flow updates 2026-01-15 03:20:01 +00:00
Chulwoo Jung 5c00fe6bef Merge branch 'develop' of github.com:poare/Grid into KS_shifted 2026-01-12 06:26:15 +00:00
Chulwoo Jung 3175788f97 Added explicit shift before pulling 2026-01-12 06:25:09 +00:00
Patrick Oare 6f1788bb38 modified Givens rotation to implement a sparse multiplication 2026-01-06 16:19:48 -05:00
Peter Boyle 7cd3f21e6b preserving a bunch of experiments on setup and g5 subspace doubling 2026-01-06 05:57:39 -05:00
Chulwoo Jung dcda74f924 Timing info for schurReorder,etc 2025-12-18 18:23:50 +00:00
Chulwoo Jung df4c2a082b Reducing comments 2025-12-09 14:23:22 +00:00
Chulwoo Jung 88611659a3 Appear to be working 2025-12-08 21:08:14 -05:00
Chulwoo Jung 504b85dfc0 Restarting and adding codes back in 2025-12-08 13:27:06 -05:00
Chulwoo Jung 43ea83e5e1 Checking in to move back to genoa 2025-12-05 23:56:40 +00:00
Chulwoo Jung 376150c3df Adding 2025-12-04 21:29:31 -05:00
Chulwoo Jung 842e0391e7 Checking in to move back to aurora 2025-12-04 20:13:44 -05:00
Chulwoo Jung 17e3799bcc Necessary code for Harmonic KS added 2025-12-03 19:38:45 -05:00
Chulwoo Jung 985ab70f85 Checking in without adjusting Nk 2025-12-03 14:46:34 -05:00
Chulwoo Jung 1e85081986 Adding shift and debugging 2025-12-03 00:16:51 -05:00
Chulwoo Jung 3876fe5a29 Merge branch 'KrylovSchur' of github.com:chulwoo1/Grid into KS_shifted 2025-12-02 17:46:40 -05:00
Chulwoo Jung 6692425aa2 Checking in before pulling 2025-11-26 17:17:22 -05:00
Chulwoo Jung d5ac4fc67f Starting to modified KS 2025-11-26 22:13:27 +00:00
Chulwoo Jung 3538faf449 Starting Harmonic (shift and inverse) 2025-11-24 17:05:35 -05:00
paboyle 4a0aaf0786 Fix issue with Aurora compilers 2025-11-21 21:41:13 +00:00
paboyle 9c3835524c Fix compile warn 2025-11-21 21:41:12 +00:00
paboyle 549351bb8a Stag verbose clean up 2025-11-20 18:22:57 +00:00
RChHill b650b89682 Define Sp4 ProjectOnGeneralGroup for generic vtype 2025-11-19 13:26:52 +00:00
Peter Boyle 74e6b19f83 Looks like the reuse of xfers in staggered has bugs or corner cases depending on volume 2025-11-17 22:29:06 -05:00
Peter Boyle 2e684028de Improvements 2025-11-14 18:12:27 -05:00
Patrick Oare 0b457b9d52 fixed ritz estimate bug 2025-11-07 18:56:08 +00:00
Chulwoo Jung fe0ab5f1a9 Merge branch 'develop' of github.com:poare/Grid into develop 2025-11-07 15:50:22 +00:00
Chulwoo Jung caa66418bd Checking in before pulling 2025-11-06 22:44:05 +00:00
paboyle c54d87a472 Aurora compile fix for new compiler 2025-11-06 18:17:33 +00:00
Allen McKnightandGitHub 4304245c1b Merge branch 'develop' into bugfix/rocm7-rocblas-type-refactor 2025-11-04 08:50:11 -06:00
Chulwoo Jung 786496f22e Checking in before pulling KrylovSchur 2025-11-03 21:18:56 +00:00
Patrick R Oare 68af1bba67 commented some slow code out 2025-10-31 11:47:29 -04:00
Patrick Oare bf2a715ef7 bug in wilson eigenvectors: ritz estimates not equalling deviation from being an evec 2025-10-31 15:31:46 +00:00
Patrick Oare 4042ebf1bf added ImNorm to sort 2025-10-20 19:01:53 +00:00
Peter BoyleandGitHub 6165931afa Update GridStd.h 2025-10-03 14:35:37 -04:00
Your Name 1d1fd3bcaf adding a version check to handle rocblas type change 2025-10-02 15:24:24 -05:00
Patrick R Oare 82f35001ff small bug fix for wilson spectrum since we're actually running DWF 2025-09-25 15:36:42 -04:00
Patrick Oare fa30c791aa updated wilson spec 2025-09-23 15:24:50 +00:00
Patrick Oare 612049f1dd commented out evec writer because it was taking up all the space on SDCC 2025-09-18 15:09:31 -04:00
Patrick Oare 0b92ef990c found bug in unprec DWF: was using |\cdot| in comparison for the eigenvalue sorting 2025-09-12 13:31:39 -04:00
Patrick Oare 82d411ca7b added inline to rf functions 2025-09-10 17:16:48 -04:00
Patrick Oare 597086a031 added wilson spectrum example 2025-09-10 15:41:00 -04:00
Patrick Oare b210ddf9a7 added commented out line to run un-preconditioned DWF 2025-09-09 15:14:11 -04:00
Patrick Oare c5d02e5799 updated RitzFilter enum and the input to run krylov schur 2025-09-09 13:02:11 -04:00
Patrick Oare 9dcd7ca761 added IO for evecs / evals 2025-09-08 12:59:48 -04:00
paboyle 23581333e6 link cufft 2025-08-21 22:25:55 +01:00
paboyle e5fa3d887f Compile on CUDA 2025-08-21 22:10:27 +01:00
paboyle 583fa7bb0a FFTW guarded after CUDA adn HIP 2025-08-21 22:00:12 +01:00
Peter Boyle fe0db53842 FFT offload to GPU and MUCH faster comms.
40x speed up on Frontier
2025-08-21 16:45:38 -04:00
Peter Boyle 76c0ada1e1 Benchmark for En Hung 2025-08-21 16:45:38 -04:00
Peter BoyleandGitHub 92f49e9194 Merge pull request #482 from g-simonetti/wflow_sp2n_paboyle
Fixed Wilson flow for Nc not equal to 3
2025-08-21 09:10:25 -04:00
Peter BoyleandGitHub 44c8057b5f Merge pull request #481 from vataspro/sp-reps-fix
Only compile higher fermion representations for symplectic gauge group when requested via configure flag
2025-08-20 12:57:28 -04:00
Alexis Provatas 0ad837f595 Fix Sp representations compilation 2025-08-20 17:48:39 +01:00
Peter BoyleandGitHub bd2103c746 Merge pull request #480 from vataspro/fix-no-comms
Fix enable-comms=none
2025-08-20 12:26:47 -04:00
Alexis Provatas 9c18d2ddb0 Fix StencilSendToRecvFromBegin to agree with base 2025-08-20 17:17:06 +01:00
g-simonetti 1245a8c151 num_colours added to class S 2025-08-20 16:27:34 +01:00
g-simonetti 07113dc8ba Changed beta=3 to beta=Nc with comments 2025-08-20 16:18:34 +01:00
Chulwoo Jung c1e5ef9476 Adding config input 2025-08-15 20:52:36 +00:00
Patrick Oare 6fd71aea9d may have found bug 2025-08-15 12:13:01 -04:00
Patrick Oare a18b0d496c added more debug output 2025-08-15 11:51:15 -04:00
Patrick Oare 19f0737b98 trying one more thing 2025-08-14 14:47:38 -04:00
Patrick Oare 16d3c9cf75 added another debug feature 2025-08-14 14:37:49 -04:00
Patrick Oare 3b9fc72451 modified debug output slightly 2025-08-14 14:06:32 -04:00
Patrick Oare 99644f5d0a commented out arg assert in kryschur spec example 2025-08-13 14:57:57 -04:00
Chulwoo Jung 7780d88d26 Adding simple lanczos, boundary to specflow(!) 2025-08-06 23:41:53 +00:00
Chulwoo Jung 2bf9179d2c Adding mass step 2025-08-06 16:52:51 +00:00
Chulwoo Jung c606f5dca0 Move out src initialization for re-use / Adding antiperiodic BC 2025-08-06 16:51:14 +00:00
Patrick Oare 632f5916c7 small log change for KS 2025-08-04 15:43:23 -04:00
Patrick Oare 9057694895 added double orthog to KS 2025-08-04 15:30:18 -04:00
Patrick Oare 5e85aef19d added updates to GCR polynomial code 2025-07-31 16:42:35 -04:00
Patrick Oare 2b6d40c7e1 added example files 2025-07-31 16:41:28 -04:00
Patrick Oare 33b80c4e8e added eigensolver code for arnoldi and krylov schur 2025-07-31 16:40:24 -04:00
Chulwoo Jung 8419cc5c64 specflow evec I/O added, 2025-07-11 15:57:23 -04:00
Alexis Provatas c646d91527 Fix names, protect against bad index values, clean docstrings 2025-05-01 10:52:00 +01:00
Alexis Provatas a2b98d82e1 remove obsolete spatial polyakov observable file 2025-05-01 10:52:00 +01:00
Alexis Provatas 7b9415c088 Move observable logger to Polyakov Loop file and fix docstring 2025-05-01 10:52:00 +01:00
Alexis Provatas cb7110f492 Add Spatial Polyakov Loop observable 2025-05-01 10:52:00 +01:00
Alexis Provatas 0c7af66490 Create Spatial Polyakov Observable Module 2025-05-01 10:52:00 +01:00
Alexis Provatas 496d1b914a Generalise Polyakov loop and overload for temporal direction 2025-05-01 10:52:00 +01:00
Chulwoo Jung 2cc6deb8e0 Merge branch 'develop' of https://github.com/paboyle/Grid into ic2 2025-04-25 10:48:41 -04:00
Chulwoo Jung 19d0590579 Checking in for merging 2025-04-25 10:48:22 -04:00
121 changed files with 16159 additions and 397 deletions
+1
View File
@@ -42,6 +42,7 @@ void GridAbort(void);
#define ASSLOG(A) ::write(STDERR_FILENO,A,::strlen(A));
#ifdef HAVE_EXECINFO_H
#define GRID_ASSERT(b) if(!(b)) { \
fflush(stdout); \
ASSLOG(" GRID_ASSERT failure: "); \
ASSLOG(__FILE__); \
ASSLOG(" : "); \
+11 -7
View File
@@ -54,21 +54,25 @@ Version.h: version-cache
include Make.inc
include Eigen.inc
extra_sources+=$(WILS_FERMION_FILES)
extra_sources+=$(STAG_FERMION_FILES)
if BUILD_FERMION_INSTANTIATIONS
extra_sources+=$(WILS_FERMION_FILES)
extra_sources+=$(STAG_FERMION_FILES)
if BUILD_ZMOBIUS
extra_sources+=$(ZWILS_FERMION_FILES)
extra_sources+=$(ZWILS_FERMION_FILES)
endif
if BUILD_GPARITY
extra_sources+=$(GP_FERMION_FILES)
extra_sources+=$(GP_FERMION_FILES)
endif
if BUILD_FERMION_REPS
extra_sources+=$(ADJ_FERMION_FILES)
extra_sources+=$(TWOIND_FERMION_FILES)
extra_sources+=$(ADJ_FERMION_FILES)
extra_sources+=$(TWOIND_FERMION_FILES)
endif
if BUILD_SP
extra_sources+=$(SP_FERMION_FILES)
extra_sources+=$(SP_TWOIND_FERMION_FILES)
if BUILD_FERMION_REPS
extra_sources+=$(SP_TWOIND_FERMION_FILES)
endif
endif
endif
lib_LIBRARIES = libGrid.a
+15
View File
@@ -55,6 +55,7 @@ NAMESPACE_CHECK(approx);
#include <Grid/algorithms/blas/MomentumProject.h>
NAMESPACE_CHECK(deflation);
#include <Grid/algorithms/iterative/ConjugateGradient.h>
//#include <Grid/algorithms/iterative/EigCG.h>
NAMESPACE_CHECK(ConjGrad);
#include <Grid/algorithms/iterative/BiCGSTAB.h>
NAMESPACE_CHECK(BiCGSTAB);
@@ -75,6 +76,7 @@ NAMESPACE_CHECK(BiCGSTAB);
#include <Grid/algorithms/iterative/FlexibleCommunicationAvoidingGeneralisedMinimalResidual.h>
#include <Grid/algorithms/iterative/MixedPrecisionFlexibleGeneralisedMinimalResidual.h>
#include <Grid/algorithms/iterative/ImplicitlyRestartedLanczos.h>
#include <Grid/algorithms/iterative/SimpleLanczos.h>
#include <Grid/algorithms/iterative/PowerMethod.h>
#include <Grid/algorithms/iterative/AdefGeneric.h>
#include <Grid/algorithms/iterative/AdefMrhs.h>
@@ -83,4 +85,17 @@ NAMESPACE_CHECK(PowerMethod);
NAMESPACE_CHECK(multigrid);
#include <Grid/algorithms/FFT.h>
#include <Grid/algorithms/iterative/KrylovSchur.h>
#include <Grid/algorithms/iterative/BlockKrylovSchur.h>
#include <Grid/algorithms/iterative/SplitGridBlockKrylovSchur.h>
#include <Grid/algorithms/iterative/HarmonicBlockKrylovSchur.h>
#include <Grid/algorithms/iterative/TrueHarmonicBlockKrylovSchur.h>
#include <Grid/algorithms/iterative/Gamma5BlockLanczos.h>
//#include <Grid/algorithms/iterative/Gamma5ScalarLanczos.h>
#include <Grid/algorithms/iterative/Arnoldi.h>
#include <Grid/algorithms/iterative/LanczosBidiagonalization.h>
#include <Grid/algorithms/iterative/RestartedLanczosBidiagonalization.h>
//#include <Grid/algorithms/iterative/GCR.h>
#include <Grid/algorithms/iterative/MultiSplittingPreconditionedCG.h>
#endif
+322 -127
View File
@@ -28,6 +28,15 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
#ifndef _GRID_FFT_H_
#define _GRID_FFT_H_
#ifdef GRID_CUDA
#include <cufft.h>
#endif
#ifdef GRID_HIP
#include <hipfft/hipfft.h>
#endif
#if !defined(GRID_CUDA) && !defined(GRID_HIP)
#ifdef HAVE_FFTW
#if defined(USE_MKL) || defined(GRID_SYCL)
#include <fftw/fftw3.h>
@@ -35,88 +44,190 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
#include <fftw3.h>
#endif
#endif
#endif
NAMESPACE_BEGIN(Grid);
template<class scalar> struct FFTW { };
#ifndef FFTW_FORWARD
#define FFTW_FORWARD (-1)
#define FFTW_BACKWARD (+1)
#define FFTW_ESTIMATE (0)
#endif
#ifdef HAVE_FFTW
template<class scalar> struct FFTW {
};
#ifdef GRID_HIP
template<> struct FFTW<ComplexD> {
public:
static const int forward=FFTW_FORWARD;
static const int backward=FFTW_BACKWARD;
typedef hipfftDoubleComplex FFTW_scalar;
typedef hipfftHandle FFTW_plan;
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
FFTW_scalar *in, int *inembed,
int istride, int idist,
FFTW_scalar *out, int *onembed,
int ostride, int odist,
int sign, unsigned flags) {
FFTW_plan p;
auto rv = hipfftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,HIPFFT_Z2Z,howmany);
GRID_ASSERT(rv==HIPFFT_SUCCESS);
return p;
}
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
hipfftResult rv;
if ( sign == forward ) rv =hipfftExecZ2Z(p,in,out,HIPFFT_FORWARD);
else rv =hipfftExecZ2Z(p,in,out,HIPFFT_BACKWARD);
accelerator_barrier();
GRID_ASSERT(rv==HIPFFT_SUCCESS);
}
inline static void fftw_destroy_plan(const FFTW_plan p) {
hipfftDestroy(p);
}
};
template<> struct FFTW<ComplexF> {
public:
static const int forward=FFTW_FORWARD;
static const int backward=FFTW_BACKWARD;
typedef hipfftComplex FFTW_scalar;
typedef hipfftHandle FFTW_plan;
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
FFTW_scalar *in, int *inembed,
int istride, int idist,
FFTW_scalar *out, int *onembed,
int ostride, int odist,
int sign, unsigned flags) {
FFTW_plan p;
auto rv = hipfftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,HIPFFT_C2C,howmany);
GRID_ASSERT(rv==HIPFFT_SUCCESS);
return p;
}
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
hipfftResult rv;
if ( sign == forward ) rv =hipfftExecC2C(p,in,out,HIPFFT_FORWARD);
else rv =hipfftExecC2C(p,in,out,HIPFFT_BACKWARD);
accelerator_barrier();
GRID_ASSERT(rv==HIPFFT_SUCCESS);
}
inline static void fftw_destroy_plan(const FFTW_plan p) {
hipfftDestroy(p);
}
};
#endif
#ifdef GRID_CUDA
template<> struct FFTW<ComplexD> {
public:
static const int forward=FFTW_FORWARD;
static const int backward=FFTW_BACKWARD;
typedef cufftDoubleComplex FFTW_scalar;
typedef cufftHandle FFTW_plan;
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
FFTW_scalar *in, int *inembed,
int istride, int idist,
FFTW_scalar *out, int *onembed,
int ostride, int odist,
int sign, unsigned flags) {
FFTW_plan p;
cufftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,CUFFT_Z2Z,howmany);
return p;
}
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
if ( sign == forward ) cufftExecZ2Z(p,in,out,CUFFT_FORWARD);
else cufftExecZ2Z(p,in,out,CUFFT_INVERSE);
accelerator_barrier();
}
inline static void fftw_destroy_plan(const FFTW_plan p) {
cufftDestroy(p);
}
};
template<> struct FFTW<ComplexF> {
public:
static const int forward=FFTW_FORWARD;
static const int backward=FFTW_BACKWARD;
typedef cufftComplex FFTW_scalar;
typedef cufftHandle FFTW_plan;
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
FFTW_scalar *in, int *inembed,
int istride, int idist,
FFTW_scalar *out, int *onembed,
int ostride, int odist,
int sign, unsigned flags) {
FFTW_plan p;
cufftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,CUFFT_C2C,howmany);
return p;
}
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
if ( sign == forward ) cufftExecC2C(p,in,out,CUFFT_FORWARD);
else cufftExecC2C(p,in,out,CUFFT_INVERSE);
accelerator_barrier();
}
inline static void fftw_destroy_plan(const FFTW_plan p) {
cufftDestroy(p);
}
};
#endif
#if !defined(GRID_CUDA) && !defined(GRID_HIP)
#ifdef HAVE_FFTW
template<> struct FFTW<ComplexD> {
public:
typedef fftw_complex FFTW_scalar;
typedef fftw_plan FFTW_plan;
static FFTW_plan fftw_plan_many_dft(int rank, const int *n,int howmany,
FFTW_scalar *in, const int *inembed,
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
FFTW_scalar *in, int *inembed,
int istride, int idist,
FFTW_scalar *out, const int *onembed,
FFTW_scalar *out, int *onembed,
int ostride, int odist,
int sign, unsigned flags) {
return ::fftw_plan_many_dft(rank,n,howmany,in,inembed,istride,idist,out,onembed,ostride,odist,sign,flags);
}
static void fftw_flops(const FFTW_plan p,double *add, double *mul, double *fmas){
::fftw_flops(p,add,mul,fmas);
}
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out) {
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
::fftw_execute_dft(p,in,out);
}
inline static void fftw_destroy_plan(const FFTW_plan p) {
::fftw_destroy_plan(p);
}
};
template<> struct FFTW<ComplexF> {
public:
typedef fftwf_complex FFTW_scalar;
typedef fftwf_plan FFTW_plan;
static FFTW_plan fftw_plan_many_dft(int rank, const int *n,int howmany,
FFTW_scalar *in, const int *inembed,
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
FFTW_scalar *in, int *inembed,
int istride, int idist,
FFTW_scalar *out, const int *onembed,
FFTW_scalar *out, int *onembed,
int ostride, int odist,
int sign, unsigned flags) {
return ::fftwf_plan_many_dft(rank,n,howmany,in,inembed,istride,idist,out,onembed,ostride,odist,sign,flags);
}
static void fftw_flops(const FFTW_plan p,double *add, double *mul, double *fmas){
::fftwf_flops(p,add,mul,fmas);
}
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out) {
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
::fftwf_execute_dft(p,in,out);
}
inline static void fftw_destroy_plan(const FFTW_plan p) {
::fftwf_destroy_plan(p);
}
};
#endif
#ifndef FFTW_FORWARD
#define FFTW_FORWARD (-1)
#define FFTW_BACKWARD (+1)
#endif
class FFT {
private:
GridCartesian *vgrid;
GridCartesian *sgrid;
int Nd;
double flops;
double flops_call;
uint64_t usec;
Coordinate dimensions;
Coordinate processors;
Coordinate processor_coor;
public:
static const int forward=FFTW_FORWARD;
@@ -126,31 +237,25 @@ public:
double MFlops(void) {return flops/usec;}
double USec(void) {return (double)usec;}
FFT ( GridCartesian * grid ) :
vgrid(grid),
Nd(grid->_ndimension),
dimensions(grid->_fdimensions),
processors(grid->_processors),
processor_coor(grid->_processor_coor)
FFT ( GridCartesian * grid )
{
flops=0;
usec =0;
Coordinate layout(Nd,1);
sgrid = new GridCartesian(dimensions,layout,processors,*grid);
};
~FFT ( void) {
delete sgrid;
// delete sgrid;
}
template<class vobj>
void FFT_dim_mask(Lattice<vobj> &result,const Lattice<vobj> &source,Coordinate mask,int sign){
conformable(result.Grid(),vgrid);
conformable(source.Grid(),vgrid);
Lattice<vobj> tmp(vgrid);
tmp = source;
for(int d=0;d<Nd;d++){
// vgrid=result.Grid();
// conformable(result.Grid(),vgrid);
// conformable(source.Grid(),vgrid);
const int Ndim = source.Grid()->Nd();
Lattice<vobj> tmp = source;
for(int d=0;d<Ndim;d++){
if( mask[d] ) {
FFT_dim(result,tmp,d,sign);
tmp=result;
@@ -160,62 +265,70 @@ public:
template<class vobj>
void FFT_all_dim(Lattice<vobj> &result,const Lattice<vobj> &source,int sign){
Coordinate mask(Nd,1);
const int Ndim = source.Grid()->Nd();
Coordinate mask(Ndim,1);
FFT_dim_mask(result,source,mask,sign);
}
template<class vobj>
void FFT_dim(Lattice<vobj> &result,const Lattice<vobj> &source,int dim, int sign){
#ifndef HAVE_FFTW
std::cerr << "FFTW is not compiled but is called"<<std::endl;
GRID_ASSERT(0);
#else
conformable(result.Grid(),vgrid);
conformable(source.Grid(),vgrid);
const int Ndim = source.Grid()->Nd();
GridBase *grid = source.Grid();
conformable(result.Grid(),source.Grid());
int L = vgrid->_ldimensions[dim];
int G = vgrid->_fdimensions[dim];
Coordinate layout(Nd,1);
Coordinate pencil_gd(vgrid->_fdimensions);
pencil_gd[dim] = G*processors[dim];
// Pencil global vol LxLxGxLxL per node
GridCartesian pencil_g(pencil_gd,layout,processors,*vgrid);
int L = grid->_ldimensions[dim];
int G = grid->_fdimensions[dim];
Coordinate layout(Ndim,1);
// Construct pencils
typedef typename vobj::scalar_object sobj;
typedef typename sobj::scalar_type scalar;
typedef typename vobj::scalar_type scalar;
typedef typename vobj::scalar_type scalar_type;
typedef typename vobj::vector_type vector_type;
Lattice<sobj> pgbuf(&pencil_g);
autoView(pgbuf_v , pgbuf, CpuWrite);
//std::cout << "CPU view" << std::endl;
typedef typename FFTW<scalar>::FFTW_scalar FFTW_scalar;
typedef typename FFTW<scalar>::FFTW_plan FFTW_plan;
int Ncomp = sizeof(sobj)/sizeof(scalar);
int Nlow = 1;
int64_t Nlow = 1;
int64_t Nhigh = 1;
for(int d=0;d<dim;d++){
Nlow*=vgrid->_ldimensions[d];
Nlow*=grid->_ldimensions[d];
}
for(int d=dim+1;d<Ndim;d++){
Nhigh*=grid->_ldimensions[d];
}
int64_t Nperp=Nlow*Nhigh;
deviceVector<scalar> pgbuf; // Layout is [perp][component][dim]
pgbuf.resize(Nperp*Ncomp*G);
scalar *pgbuf_v = &pgbuf[0];
int rank = 1; /* 1d transforms */
int n[] = {G}; /* 1d transforms of length G */
int howmany = Ncomp;
int howmany = Ncomp * Nperp;
int odist,idist,istride,ostride;
idist = odist = 1; /* Distance between consecutive FT's */
istride = ostride = Ncomp*Nlow; /* distance between two elements in the same FT */
idist = odist = G; /* Distance between consecutive FT's */
istride = ostride = 1; /* Distance between two elements in the same FT */
int *inembed = n, *onembed = n;
scalar div;
if ( sign == backward ) div = 1.0/G;
else if ( sign == forward ) div = 1.0;
else GRID_ASSERT(0);
//std::cout << GridLogPerformance<<"Making FFTW plan" << std::endl;
double t_pencil=0;
double t_fft =0;
double t_total =-usecond();
// std::cout << GridLogPerformance<<"Making FFTW plan" << std::endl;
/*
*
*/
FFTW_plan p;
{
FFTW_scalar *in = (FFTW_scalar *)&pgbuf_v[0];
@@ -229,72 +342,154 @@ public:
}
// Barrel shift and collect global pencil
//std::cout << GridLogPerformance<<"Making pencil" << std::endl;
Coordinate lcoor(Nd), gcoor(Nd);
// std::cout << GridLogPerformance<<"Making pencil" << std::endl;
Coordinate lcoor(Ndim), gcoor(Ndim);
double t_copy=0;
double t_shift=0;
t_pencil = -usecond();
result = source;
int pc = processor_coor[dim];
int pc = grid->_processor_coor[dim];
const Coordinate ldims = grid->_ldimensions;
const Coordinate rdims = grid->_rdimensions;
const Coordinate sdims = grid->_simd_layout;
Coordinate processors = grid->_processors;
Coordinate pgdims(Ndim);
pgdims[0] = G;
for(int d=0, dd=1;d<Ndim;d++){
if ( d!=dim ) pgdims[dd++] = ldims[d];
}
int64_t pgvol=1;
for(int d=0;d<Ndim;d++) pgvol*=pgdims[d];
const int Nsimd = vobj::Nsimd();
for(int p=0;p<processors[dim];p++) {
t_copy-=usecond();
autoView(r_v,result,AcceleratorRead);
accelerator_for(idx, grid->oSites(), vobj::Nsimd(), {
#ifdef GRID_SIMT
{
autoView(r_v,result,CpuRead);
autoView(p_v,pgbuf,CpuWrite);
thread_for(idx, sgrid->lSites(),{
Coordinate cbuf(Nd);
sobj s;
sgrid->LocalIndexToLocalCoor(idx,cbuf);
peekLocalSite(s,r_v,cbuf);
cbuf[dim]+=((pc+p) % processors[dim])*L;
pokeLocalSite(s,p_v,cbuf);
});
int lane=acceleratorSIMTlane(Nsimd); // buffer lane
#else
for(int lane=0;lane<Nsimd;lane++) {
#endif
Coordinate icoor;
Coordinate ocoor;
Coordinate pgcoor;
Lexicographic::CoorFromIndex(icoor,lane,sdims);
Lexicographic::CoorFromIndex(ocoor,idx,rdims);
pgcoor[0] = ocoor[dim] + icoor[dim]*rdims[dim] + ((pc+p)%processors[dim])*L;
for(int d=0,dd=1;d<Ndim;d++){
if ( d!=dim ) {
pgcoor[dd] = ocoor[d] + icoor[d]*rdims[d];
dd++;
}
}
// Map coordinates in lattice layout to FFTW index
int64_t pgidx;
Lexicographic::IndexFromCoor(pgcoor,pgidx,pgdims);
vector_type *from = (vector_type *)&r_v[idx];
scalar_type stmp;
for(int w=0;w<Ncomp;w++){
int64_t pg_idx = pgidx + w*pgvol;
stmp = getlane(from[w], lane);
pgbuf_v[pg_idx] = stmp;
}
#ifdef GRID_SIMT
}
#else
}
#endif
});
t_copy+=usecond();
if (p != processors[dim] - 1) {
result = Cshift(result,dim,L);
Lattice<vobj> temp(grid);
t_shift-=usecond();
temp = Cshift(result,dim,L); result = temp;
t_shift+=usecond();
}
}
t_pencil += usecond();
//std::cout <<GridLogPerformance<< "Looping orthog" << std::endl;
// Loop over orthog coords
int NN=pencil_g.lSites();
GridStopWatch timer;
timer.Start();
thread_for( idx,NN,{
Coordinate cbuf(Nd);
pencil_g.LocalIndexToLocalCoor(idx, cbuf);
if ( cbuf[dim] == 0 ) { // restricts loop to plane at lcoor[dim]==0
FFTW_scalar *in = (FFTW_scalar *)&pgbuf_v[idx];
FFTW_scalar *out= (FFTW_scalar *)&pgbuf_v[idx];
FFTW<scalar>::fftw_execute_dft(p,in,out);
}
});
timer.Stop();
FFTW_scalar *in = (FFTW_scalar *)pgbuf_v;
FFTW_scalar *out= (FFTW_scalar *)pgbuf_v;
t_fft = -usecond();
FFTW<scalar>::fftw_execute_dft(p,in,out,sign);
t_fft += usecond();
// performance counting
double add,mul,fma;
FFTW<scalar>::fftw_flops(p,&add,&mul,&fma);
flops_call = add+mul+2.0*fma;
usec += timer.useconds();
flops+= flops_call*NN;
//std::cout <<GridLogPerformance<< "Writing back results " << std::endl;
// writing out result
flops_call = 5.0*howmany*G*log2(G);
usec = t_fft;
flops= flops_call;
result = Zero();
double t_insert = -usecond();
{
autoView(pgbuf_v,pgbuf,CpuRead);
autoView(result_v,result,CpuWrite);
thread_for(idx,sgrid->lSites(),{
Coordinate clbuf(Nd), cgbuf(Nd);
sobj s;
sgrid->LocalIndexToLocalCoor(idx,clbuf);
cgbuf = clbuf;
cgbuf[dim] = clbuf[dim]+L*pc;
peekLocalSite(s,pgbuf_v,cgbuf);
pokeLocalSite(s,result_v,clbuf);
autoView(r_v,result,AcceleratorWrite);
accelerator_for(idx,grid->oSites(),Nsimd,{
#ifdef GRID_SIMT
{
int lane=acceleratorSIMTlane(Nsimd); // buffer lane
#else
for(int lane=0;lane<Nsimd;lane++) {
#endif
Coordinate icoor(Ndim);
Coordinate ocoor(Ndim);
Coordinate pgcoor(Ndim);
Lexicographic::CoorFromIndex(icoor,lane,sdims);
Lexicographic::CoorFromIndex(ocoor,idx,rdims);
pgcoor[0] = ocoor[dim] + icoor[dim]*rdims[dim] + pc*L;
for(int d=0,dd=1;d<Ndim;d++){
if ( d!=dim ) {
pgcoor[dd] = ocoor[d] + icoor[d]*rdims[d];
dd++;
}
}
// Map coordinates in lattice layout to FFTW index
int64_t pgidx;
Lexicographic::IndexFromCoor(pgcoor,pgidx,pgdims);
vector_type *to = (vector_type *)&r_v[idx];
scalar_type stmp;
for(int w=0;w<Ncomp;w++){
int64_t pg_idx = pgidx + w*pgvol;
stmp = pgbuf_v[pg_idx];
putlane(to[w], stmp, lane);
}
#ifdef GRID_SIMT
}
#else
}
#endif
});
}
result = result*div;
//std::cout <<GridLogPerformance<< "Destroying plan " << std::endl;
t_insert +=usecond();
// destroying plan
FFTW<scalar>::fftw_destroy_plan(p);
#endif
t_total +=usecond();
std::cout <<GridLogPerformance<< " FFT took "<<t_total/1.0e6 <<" s" << std::endl;
std::cout <<GridLogPerformance<< " FFT pencil "<<t_pencil/1.0e6 <<" s" << std::endl;
std::cout <<GridLogPerformance<< " of which copy "<<t_copy/1.0e6 <<" s" << std::endl;
std::cout <<GridLogPerformance<< " of which shift"<<t_shift/1.0e6 <<" s" << std::endl;
std::cout <<GridLogPerformance<< " FFT kernels "<<t_fft/1.0e6 <<" s" << std::endl;
std::cout <<GridLogPerformance<< " FFT insert "<<t_insert/1.0e6 <<" s" << std::endl;
}
};
+79 -16
View File
@@ -28,6 +28,7 @@ Author: Peter Boyle <pboyle@bnl.gov>
#pragma once
#ifdef GRID_HIP
#include <hip/hip_version.h>
#include <hipblas/hipblas.h>
#endif
#ifdef GRID_CUDA
@@ -255,16 +256,29 @@ public:
if ( OpB == GridBLAS_OP_N ) hOpB = HIPBLAS_OP_N;
if ( OpB == GridBLAS_OP_T ) hOpB = HIPBLAS_OP_T;
if ( OpB == GridBLAS_OP_C ) hOpB = HIPBLAS_OP_C;
#if defined(HIP_VERSION_MAJOR) && (HIP_VERSION_MAJOR >=7)
auto err = hipblasZgemmBatched(gridblasHandle,
hOpA,
hOpB,
m,n,k,
(hipblasDoubleComplex *) &alpha_p[0],
(hipblasDoubleComplex **)&Amk[0], lda,
(hipblasDoubleComplex **)&Bkn[0], ldb,
(hipblasDoubleComplex *) &beta_p[0],
(hipblasDoubleComplex **)&Cmn[0], ldc,
(hipDoubleComplex *) &alpha_p[0],
(hipDoubleComplex **)&Amk[0], lda,
(hipDoubleComplex **)&Bkn[0], ldb,
(hipDoubleComplex *) &beta_p[0],
(hipDoubleComplex **)&Cmn[0], ldc,
batchCount);
#else
auto err = hipblasZgemmBatched(gridblasHandle,
hOpA,
hOpB,
m,n,k,
(hipblasDoubleComplex *) &alpha_p[0],
(hipblasDoubleComplex **)&Amk[0], lda,
(hipblasDoubleComplex **)&Bkn[0], ldb,
(hipblasDoubleComplex *) &beta_p[0],
(hipblasDoubleComplex **)&Cmn[0], ldc,
batchCount);
#endif
// std::cout << " hipblas return code " <<(int)err<<std::endl;
GRID_ASSERT(err==HIPBLAS_STATUS_SUCCESS);
#endif
@@ -503,17 +517,30 @@ public:
if ( OpB == GridBLAS_OP_N ) hOpB = HIPBLAS_OP_N;
if ( OpB == GridBLAS_OP_T ) hOpB = HIPBLAS_OP_T;
if ( OpB == GridBLAS_OP_C ) hOpB = HIPBLAS_OP_C;
#if defined(HIP_VERSION_MAJOR) && (HIP_VERSION_MAJOR >=7)
auto err = hipblasCgemmBatched(gridblasHandle,
hOpA,
hOpB,
m,n,k,
(hipblasComplex *) &alpha_p[0],
(hipblasComplex **)&Amk[0], lda,
(hipblasComplex **)&Bkn[0], ldb,
(hipblasComplex *) &beta_p[0],
(hipblasComplex **)&Cmn[0], ldc,
(hipComplex *) &alpha_p[0],
(hipComplex **)&Amk[0], lda,
(hipComplex **)&Bkn[0], ldb,
(hipComplex *) &beta_p[0],
(hipComplex **)&Cmn[0], ldc,
batchCount);
#else
auto err = hipblasCgemmBatched(gridblasHandle,
hOpA,
hOpB,
m,n,k,
(hipblasComplex *) &alpha_p[0],
(hipblasComplex **)&Amk[0], lda,
(hipblasComplex **)&Bkn[0], ldb,
(hipblasComplex *) &beta_p[0],
(hipblasComplex **)&Cmn[0], ldc,
batchCount);
#endif
GRID_ASSERT(err==HIPBLAS_STATUS_SUCCESS);
#endif
#ifdef GRID_CUDA
@@ -1094,11 +1121,19 @@ public:
GRID_ASSERT(info.size()==batchCount);
#ifdef GRID_HIP
#if defined(HIP_VERSION_MAJOR) && (HIP_VERSION_MAJOR >=7)
auto err = hipblasZgetrfBatched(gridblasHandle,(int)n,
(hipblasDoubleComplex **)&Ann[0], (int)n,
(hipDoubleComplex **)&Ann[0], (int)n,
(int*) &ipiv[0],
(int*) &info[0],
(int)batchCount);
#else
auto err = hipblasZgetrfBatched(gridblasHandle,(int)n,
(hipblasDoubleComplex **)&Ann[0], (int)n,
(int*) &ipiv[0],
(int*) &info[0],
(int)batchCount);
#endif
GRID_ASSERT(err==HIPBLAS_STATUS_SUCCESS);
#endif
#ifdef GRID_CUDA
@@ -1124,11 +1159,20 @@ public:
GRID_ASSERT(info.size()==batchCount);
#ifdef GRID_HIP
#if defined(HIP_VERSION_MAJOR) && (HIP_VERSION_MAJOR >=7)
auto err = hipblasCgetrfBatched(gridblasHandle,(int)n,
(hipblasComplex **)&Ann[0], (int)n,
(hipComplex **)&Ann[0], (int)n,
(int*) &ipiv[0],
(int*) &info[0],
(int)batchCount);
#else
auto err = hipblasCgetrfBatched(gridblasHandle,(int)n,
(hipblasComplex **)&Ann[0], (int)n,
(int*) &ipiv[0],
(int*) &info[0],
(int)batchCount);
#endif
GRID_ASSERT(err==HIPBLAS_STATUS_SUCCESS);
#endif
#ifdef GRID_CUDA
@@ -1201,12 +1245,22 @@ public:
GRID_ASSERT(Cnn.size()==batchCount);
#ifdef GRID_HIP
#if defined(HIP_VERSION_MAJOR) && (HIP_VERSION_MAJOR >=7)
auto err = hipblasZgetriBatched(gridblasHandle,(int)n,
(hipblasDoubleComplex **)&Ann[0], (int)n,
(hipDoubleComplex **)&Ann[0], (int)n,
(int*) &ipiv[0],
(hipblasDoubleComplex **)&Cnn[0], (int)n,
(hipDoubleComplex **)&Cnn[0], (int)n,
(int*) &info[0],
(int)batchCount);
#else
auto err = hipblasZgetriBatched(gridblasHandle,(int)n,
(hipblasDoubleComplex **)&Ann[0], (int)n,
(int*) &ipiv[0],
(hipblasDoubleComplex **)&Cnn[0], (int)n,
(int*) &info[0],
(int)batchCount);
#endif
GRID_ASSERT(err==HIPBLAS_STATUS_SUCCESS);
#endif
#ifdef GRID_CUDA
@@ -1235,12 +1289,21 @@ public:
GRID_ASSERT(Cnn.size()==batchCount);
#ifdef GRID_HIP
#if defined(HIP_VERSION_MAJOR) && (HIP_VERSION_MAJOR >=7)
auto err = hipblasCgetriBatched(gridblasHandle,(int)n,
(hipblasComplex **)&Ann[0], (int)n,
(hipComplex **)&Ann[0], (int)n,
(int*) &ipiv[0],
(hipblasComplex **)&Cnn[0], (int)n,
(hipComplex **)&Cnn[0], (int)n,
(int*) &info[0],
(int)batchCount);
#else
auto err = hipblasCgetriBatched(gridblasHandle,(int)n,
(hipblasComplex **)&Ann[0], (int)n,
(int*) &ipiv[0],
(hipblasComplex **)&Cnn[0], (int)n,
(int*) &info[0],
(int)batchCount);
#endif
GRID_ASSERT(err==HIPBLAS_STATUS_SUCCESS);
#endif
#ifdef GRID_CUDA
+2 -2
View File
@@ -92,8 +92,8 @@ class TwoLevelCGmrhs
// Vector case
virtual void operator() (std::vector<Field> &src, std::vector<Field> &x)
{
// SolveSingleSystem(src,x);
SolvePrecBlockCG(src,x);
SolveSingleSystem(src,x);
// SolvePrecBlockCG(src,x);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+433
View File
@@ -0,0 +1,433 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/Arnoldi.h
Copyright (C) 2015
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: paboyle <paboyle@ph.ed.ac.uk>
Author: Patrick Oare <poare@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.
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 */
#ifndef GRID_ARNOLDI_H
#define GRID_ARNOLDI_H
NAMESPACE_BEGIN(Grid);
//Moved to KrylovSchur
#if 0
/**
<<<<<<< HEAD
* Options for which Ritz values to keep in implicit restart.
*/
enum RitzFilter {
EvalNormSmall, // Keep evals with smallest norm
EvalNormLarge, // Keep evals with largest norm
EvalReSmall, // Keep evals with smallest real part
EvalReLarge // Keep evals with largest real part
};
// Select comparison function from RitzFilter
struct ComplexComparator
{
RitzFilter f;
ComplexComparator (RitzFilter _f) : f(_f) {}
bool operator()(std::complex<double> z1, std::complex<double> z2) {
switch (f) {
RealD tmp1, tmp2;
tmp1=std::abs(std::imag(z1));
tmp2=std::abs(std::imag(z2));
case EvalNormSmall:
return std::abs(z1) < std::abs(z2);
case EvalNormLarge:
return std::abs(z1) > std::abs(z2);
// Terrible hack
// return std::abs(std::real(z1)) < std::abs(std::real(z2));
// if ( std::abs(std::real(z1)) >4.) tmp1 +=1.;
// if ( std::abs(std::real(z2)) >4.) tmp2 +=1.;
case EvalReSmall:
return tmp1 < tmp2;
// return std::abs(std::imag(z1)) < std::abs(std::imag(z2));
case EvalReLarge:
return tmp1 > tmp2;
// return std::abs(std::real(z1)) > std::abs(std::real(z2));
default:
assert(0);
}
}
};
=======
>>>>>>> 68af1bba67dd62881ead5ab1e54962a5486a0791
#endif
/**
* Implementation of the Arnoldi algorithm.
*/
template<class Field>
class Arnoldi {
private:
std::string cname = std::string("Arnoldi");
int MaxIter; // Max iterations
RealD Tolerance;
RealD ssq;
RealD rtol;
int Nm; // Number of basis vectors to track (equals MaxIter if no restart)
int Nk; // Number of basis vectors to keep every restart (equals -1 if no restart)
int Nstop; // Stop after converging Nstop eigenvectors.
LinearOperatorBase<Field> &Linop;
GridBase *Grid;
RealD approxLambdaMax;
RealD beta_k;
Field f;
std::vector<Field> basis; // orthonormal Arnoldi basis
Eigen::MatrixXcd Hess; // Hessenberg matrix of size Nbasis (after construction)
Eigen::MatrixXcd Qt; // Transpose of basis rotation which projects out high modes.
Eigen::VectorXcd evals; // evals of Hess
Eigen::MatrixXcd littleEvecs; // Nm x Nm evecs matrix
std::vector<Field> evecs; // Vector of evec fields
RitzFilter ritzFilter; // how to sort evals
public:
Arnoldi(LinearOperatorBase<Field> &_Linop, GridBase *_Grid, RealD _Tolerance, RitzFilter filter = EvalReSmall)
: Linop(_Linop), Grid(_Grid), Tolerance(_Tolerance), ritzFilter(filter), f(_Grid), MaxIter(-1), Nm(-1), Nk(-1),
Nstop (-1), evals (0), evecs (), ssq (0.0), rtol (0.0), beta_k (0.0), approxLambdaMax (0.0)
{
f = Zero();
};
/**
* Runs the Arnoldi loop with(out) implicit restarting. For each iteration:
* - Runs an Arnoldi step.
* - Computes the eigensystem of the Hessenberg matrix.
* - Performs implicit restarting.
*/
void operator()(const Field& v0, int _maxIter, int _Nm, int _Nk, int _Nstop, bool doubleOrthog = false) {
MaxIter = _maxIter;
Nm = _Nm; Nk = _Nk;
Nstop = _Nstop;
ssq = norm2(v0);
RealD approxLambdaMax = approxMaxEval(v0);
rtol = Tolerance * approxLambdaMax;
ComplexComparator compareComplex (ritzFilter);
std::cout << GridLogMessage << "Comparing Ritz values with: " << ritzFilter << std::endl;
int start = 1;
Field startVec = v0;
littleEvecs = Eigen::MatrixXcd::Zero(Nm, Nm);
for (int i = 0; i < MaxIter; i++) {
std::cout << GridLogMessage << "Restart Iteration " << i << std::endl;
// Perform Arnoldi steps to compute Krylov basis and Rayleigh quotient (Hess)
arnoldiIteration(startVec, Nm, start, doubleOrthog);
startVec = f;
// compute eigensystem and sort evals
// compute_eigensystem();
compute_eigensystem(Hess);
std::cout << GridLogMessage << "Eigenvalues after Arnoldi step: " << std::endl << evals << std::endl;
std::sort(evals.begin(), evals.end(), compareComplex);
std::cout << GridLogMessage << "Ritz values after sorting (first Nk preserved): " << std::endl << evals << std::endl;
// SU(N)::tepidConfiguration
// Implicit restart to de-weight unwanted eigenvalues
implicitRestart(_Nm, _Nk); // probably can delete _Nm and _Nk from function args
start = Nk;
// check convergence and return if needed.
int Nconv = converged();
std::cout << GridLogMessage << "Number of evecs converged: " << Nconv << std::endl;
if (Nconv >= Nstop || i == MaxIter - 1) {
std::cout << GridLogMessage << "Converged with " << Nconv << " / " << Nstop << " eigenvectors on iteration "
<< i << "." << std::endl;
basisRotate(evecs, Qt, 0, Nk, 0, Nk, Nm);
std::cout << GridLogMessage << "Eigenvalues [first " << Nconv << " converged]: " << std::endl << evals << std::endl;
return;
}
}
}
/**
* Approximates the maximum eigenvalue of Linop.Op to normalize the residual and test for convergence.
*
* Parameters
* ----------
* Field& v0
* Source field to start with. Must have non-zero norm.
* int MAX_ITER (default = 50)
* Maximum number of iterations for power approximation.
*
* Returns
* -------
* RealD lamApprox
* Approximation of largest eigenvalue.
*/
RealD approxMaxEval(const Field& v0, int MAX_ITER = 50) {
assert (norm2(v0) > 1e-8); // must have relatively large source norm to start
RealD lamApprox = 0.0;
RealD denom = 1.0; RealD num = 1.0;
Field v0cp (Grid); Field tmp (Grid);
v0cp = v0;
denom = std::sqrt(norm2(v0cp));
for (int i = 0; i < MAX_ITER; i++) {
Linop.Op(v0cp, tmp); // CAREFUL: do not do Op(tmp, tmp)
v0cp = tmp;
num = std::sqrt(norm2(v0cp)); // num = |A^{n+1} v0|
lamApprox = num / denom; // lam = |A^{n+1} v0| / |A^n v0|
std::cout << GridLogDebug << "Approx for max eval: " << lamApprox << std::endl;
denom = num; // denom = |A^{n} v0|
}
return lamApprox;
}
/**
* Constructs the Arnoldi basis for the Krylov space K_n(D, src). (TODO make private)
*
* Parameters
* ----------
* v0 : Field&
* Source to generate Krylov basis.
* Nm : int
* Final size of the basis desired. If the basis becomes complete before a basis of size Nm is constructed
* (determined by relative tolerance Tolerance), stops iteration there.
* doubleOrthog : bool (default = false)
* Whether to double orthogonalize the basis (for numerical cancellations) or not.
* start : int (default = 0)
* If non-zero, assumes part of the Arnoldi basis has already been constructed.
*/
void arnoldiIteration(const Field& v0, int Nm, int start = 1, bool doubleOrthog = false)
{
ComplexD coeff;
Field w (Grid); // A acting on last Krylov vector.
if (start == 1) { // initialize everything that we need.
RealD v0Norm = 1 / std::sqrt(ssq);
basis.push_back(v0Norm * v0); // normalized source
Hess = Eigen::MatrixXcd::Zero(Nm, Nm);
f = Zero();
} else {
assert( start == basis.size() ); // should be starting at the end of basis (start = Nk)
Eigen::MatrixXcd HessCp = Hess;
Hess = Eigen::MatrixXcd::Zero(Nm, Nm);
Hess(Eigen::seqN(0, Nk), Eigen::seqN(0, Nk)) = HessCp;
}
// Construct next Arnoldi vector by normalizing w_i = Dv_i - \sum_j v_j h_{ji}
for (int i = start - 1; i < Nm; i++) {
Linop.Op(basis.back(), w);
for (int j = 0; j < basis.size(); j++) {
coeff = innerProduct(basis[j], w); // coeff = h_{ij}. Note that since {vi} is ONB it's OK to subtract it off after.
Hess(j, i) = coeff;
w -= coeff * basis[j];
}
if (doubleOrthog) {
// TODO implement
}
// add w_i to the pile
if (i < Nm - 1) {
coeff = std::sqrt(norm2(w));
Hess(i+1, i) = coeff;
basis.push_back(
(1.0/coeff) * w
);
}
// after iterations, update f and beta_k = ||f||
f = w; // make sure f is not normalized
beta_k = std::sqrt(norm2(f)); // beta_k = ||f_k|| determines convergence.
}
std::cout << GridLogMessage << "|f|^2 after Arnoldi step = " << norm2(f) << std::endl;
std::cout << GridLogDebug << "Computed Hessenberg matrix = " << std::endl << Hess << std::endl;
return;
}
/**
* Approximates the eigensystem of the linear operator by computing the eigensystem of
* the Hessenberg matrix. Assumes that the Hessenberg matrix has already been constructed (by
* calling the operator() function).
*
* TODO implement in parent class eventually.
*
* Parameters
* ----------
* Eigen::MatrixXcd& S
* Schur matrix (upper triangular) similar to original Rayleigh quotient.
*/
void compute_eigensystem(Eigen::MatrixXcd& S)
{
std::cout << GridLogMessage << "Computing eigenvalues." << std::endl;
evecs.clear();
Eigen::ComplexEigenSolver<Eigen::MatrixXcd> es;
es.compute(S);
evals = es.eigenvalues();
littleEvecs = es.eigenvectors();
// Convert evecs to lattice fields
for (int k = 0; k < evals.size(); k++) {
Eigen::VectorXcd vec = littleEvecs.col(k);
Field tmp (basis[0].Grid());
tmp = Zero();
for (int j = 0; j < basis.size(); j++) {
tmp = tmp + vec[j] * basis[j];
}
evecs.push_back(tmp);
}
std::cout << GridLogMessage << "Eigenvalues: " << std::endl << evals << std::endl;
}
/**
* Verifies the factorization DV = V^\dag H + f e^\dag with the last-computed
* V, H, f.
*/
// RealD verifyFactorization() {
// int k = basis.size(); // number of basis vectors, also the size of H.
// std::vector<Field> factorized (k, Zero());
// Field tmp (FGrid); tmp = Zero();
// for (int i = 0; i < basis.size(); i++) {
// Linop.Op(basis[i], tmp);
// }
// // basisRotate(basis, Q, 0, Nk, 0, Nk, Nm);
// // Linop.Op(, )
// }
/* Getters */
Eigen::MatrixXcd getHessenbergMat() { return Hess; }
Field getF() { return f; }
std::vector<Field> getBasis() { return basis; }
Eigen::VectorXcd getEvals() { return evals; }
std::vector<Field> getEvecs() { return evecs; }
/**
* Implements implicit restarting for Arnoldi. Assumes eigenvalues are sorted.
*
* Parameters
* ----------
* int _Nm
* Size of basis to keep (Hessenberg is MxM).
* int Nk
* Number of basis vectors to keep at each restart.
*/
void implicitRestart(int _Nm, int _Nk) {
assert ( _Nk <= _Nm );
Nm = _Nm; Nk = _Nk;
int Np = Nm - Nk; // keep Nk smallest (or largest, depends on sort function) evecs
std::cout << GridLogMessage << "Computing QR Factorizations." << std::endl;
Eigen::MatrixXcd Q = Eigen::MatrixXcd::Identity(Nm, Nm);
Eigen::MatrixXcd Qi (Nm, Nm);
Eigen::MatrixXcd R (Nm, Nm);
for (int i = Nk; i < Nm; i++) { // keep the first Nk eigenvalues and iterate through the last Np. Should loop Np times
// Useful debugging output
std::cout << GridLogDebug << "Computing QR factorization for i = " << i << std::endl;
std::cout << GridLogDebug << "Eval shift = " << evals[i] << std::endl;
std::cout << GridLogDebug << "Hess before rotation: " << Hess << std::endl;
// QR factorize
Eigen::HouseholderQR<Eigen::MatrixXcd> QR (Hess - evals[i] * Eigen::MatrixXcd::Identity(Nm, Nm));
Qi = QR.householderQ();
Q = Q * Qi;
Hess = Qi.adjoint() * Hess * Qi;
std::cout << GridLogDebug << "Qt up to i = " << Q.transpose() << std::endl;
}
std::cout << GridLogDebug << "Hess after all rotations: " << std::endl << Hess << std::endl;
// form Arnoldi vector f: f is normal to the basis vectors and its norm \beta is used to determine the Ritz estimate.
std::complex<double> beta = Hess(Nk, Nk-1);
std::complex<double> sigma = Q(Nm-1, Nk-1);
f = basis[Nk] * beta + f * sigma;
RealD betak = std::sqrt(norm2(f));
std::cout << GridLogMessage << "|f|^2 after implicit restart = " << norm2(f) << std::endl;
// Rotate basis by Qt
Qt = Q.transpose();
basisRotate(basis, Qt, 0, Nk + 1, 0, Nm, Nm);
// rotate
basisRotate(evecs, Qt, 0, Nk + 1, 0, Nm, Nm);
// Truncate the basis and restart
basis = std::vector<Field> (basis.begin(), basis.begin() + Nk);
// evecs = std::vector<Field> (evecs.begin(), evecs.begin() + Nk);
Hess = Hess(Eigen::seqN(0, Nk), Eigen::seqN(0, Nk));
std::cout << "evecs size: " << evecs.size() << std::endl;
}
/**
* Computes the number of Arnoldi eigenvectors that have converged. An eigenvector s is considered converged
* for a tolerance epsilon if
* r(s) := |\beta e_m^T s| < epsilon
* where beta is the norm of f_{m+1}.
*
* Parameters
* ----------
*
* Returns
* -------
* int : Number of converged eigenvectors.
*/
int converged() {
int Nconv = 0;
for (int k = 0; k < evecs.size(); k++) {
RealD emTs = std::abs(littleEvecs(Nm - 1, k)); // e_m^T s
RealD ritzEstimate = beta_k * emTs;
// TODO should be ritzEstimate < Tolerance * lambda_max
std::cout << GridLogMessage << "Ritz estimate for evec " << k << " = " << ritzEstimate << std::endl;
if (ritzEstimate < rtol) {
Nconv++;
}
}
return Nconv;
}
};
NAMESPACE_END(Grid);
#endif
@@ -0,0 +1,878 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/BlockKrylovSchur.h
Copyright (C) 2015
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: Chulwoo Jung <chulwoo@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.
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 */
#ifndef GRID_BLOCKED_KRYLOV_SCHUR_H
#define GRID_BLOCKED_KRYLOV_SCHUR_H
#include <iomanip>
#include <numeric>
NAMESPACE_BEGIN(Grid);
/**
* Block (block-Arnoldi) restarted Krylov-Schur eigensolver for
* general non-Hermitian operators.
*
* Algorithm
* ---------
* Uses a block Arnoldi factorisation of block size Nblock:
*
* A V_k = V_k H_k + F_k B_k^dag
*
* where
* V_k = Nm orthonormal basis vectors (stored flat in basis[])
* H_k = Nm x Nm upper block-Hessenberg Rayleigh quotient
* F_k = Nblock residual vectors (the next block beyond V_k)
* B_k = Nm x Nblock coupling matrix (non-zero only in last Nblock rows)
*
* Each block Arnoldi step applies A to each of the Nblock vectors in the
* current block, orthogonalises against all previous basis vectors, and
* reduces the residual block to upper-triangular form via Householder QR
* (implemented here as modified Gram-Schmidt within the block).
*
* The restart is a thick restart via the Schur decomposition of H_k:
* H_k = Q^dag S Q
* The leading Nk Schur vectors (chosen by RitzFilter) are retained,
* the basis and Rayleigh quotient are truncated, and block Arnoldi continues
* from the (Nk/Nblock)-th block.
*
* Parameters
* ----------
* Nblock : block size p
* Nm : total Krylov dimension (must be divisible by Nblock)
* Nk : total vectors to keep after each restart (must be divisible by Nblock, Nk < Nm)
* Nstop : declare convergence when this many eigenpairs have converged
* MaxIter : maximum number of outer (restart) iterations
* Tolerance : relative convergence tolerance (||r|| < Tolerance * |lambda_max|)
*/
template<class Field>
class BlockKrylovSchur {
protected:
//--------------------------------------------------------------------
// Types
//--------------------------------------------------------------------
typedef Eigen::MatrixXcd CMat;
typedef Eigen::VectorXcd CVec;
//--------------------------------------------------------------------
// Parameters (set by operator())
//--------------------------------------------------------------------
int Nblock; // block size
int Nm; // total Krylov dimension (multiple of Nblock)
int Nk; // total vectors retained after restart (multiple of Nblock)
int Nstop;
int MaxIter;
RealD Tolerance;
//--------------------------------------------------------------------
// Internal state
//--------------------------------------------------------------------
LinearOperatorBase<Field>& Linop;
GridBase* Grid_;
RitzFilter ritzFilter;
// Prefix used in log messages; derived classes override in their constructor
// (e.g. "HarmonicBlockKrylovSchur") so the shared code reports the right name.
std::string className = "BlockKrylovSchur";
// Flat storage: basis[s*Nblock + t] is the t-th vector of block s
// After construction: basis has Nm entries
std::vector<Field> basis;
// Rayleigh quotient Nm x Nm
CMat H;
// Residual block: Nblock vectors (the (Nm/Nblock+1)-th block, unnormalised before
// QR; normalised and orthogonalised as part of block Arnoldi)
std::vector<Field> F;
// Coupling matrix B: Nm x Nblock.
// In exact arithmetic only the last Nblock rows are non-zero:
// B(Nm - Nblock + t, s) = H_{Nm/Nblock+1, Nm/Nblock}(t, s) (the subdiagonal block)
// We keep it as a full matrix for generality after restarts.
CMat B;
RealD beta_k; // Frobenius norm of the last subdiagonal block
RealD rtol; // absolute tolerance = Tolerance * approxLambdaMax
// Output
CVec evals;
CMat littleEvecs; // Nm columns
std::vector<RealD> ritzEstimates;
public:
std::vector<Field> evecs;
bool doEvalCheck = false;
// When true (and Nblock even), only Nblock/2 starting vectors are required.
// The remaining Nblock/2 slots are filled by pairing each supplied vector
// with its parity-flipped partner (sign negated on odd checkerboard sites).
bool useParityFlip = false;
// Same pairing behaviour but the partner is produced by gamma5Func(v).
// gamma5Func must be set by the caller before operator() is called.
// It is a std::function so that BlockKrylovSchur.h does not need to know
// about the Grid QCD Gamma class (which is defined at a later include layer).
bool useGamma5 = false;
std::function<void(const Field&, Field&)> gamma5Func;
//--------------------------------------------------------------------
// Constructor
//--------------------------------------------------------------------
BlockKrylovSchur(LinearOperatorBase<Field>& _Linop, GridBase* _Grid,
RealD _Tolerance, RitzFilter _rf = EvalReSmall)
: Linop(_Linop), Grid_(_Grid), Tolerance(_Tolerance), ritzFilter(_rf),
Nblock(-1), Nm(-1), Nk(-1), Nstop(-1), MaxIter(-1),
beta_k(0.0), rtol(0.0)
{}
virtual ~BlockKrylovSchur() = default;
//--------------------------------------------------------------------
// Main entry point
//--------------------------------------------------------------------
/**
* Run the blocked Krylov-Schur algorithm.
*
* Parameters
* ----------
* v0 : block of Nblock starting vectors (size >= Nblock)
* _maxIter : maximum outer (restart) iterations
* _Nm : total Krylov dimension (must be divisible by _Nblock)
* _Nk : total vectors to keep after restart (must be divisible by _Nblock, _Nk < _Nm)
* _Nstop : stop after _Nstop eigenvalues converged
* _Nblock : block size
*/
virtual void operator()(const std::vector<Field>& v0, int _maxIter, int _Nm, int _Nk,
int _Nstop, int _Nblock = 1, bool doubleOrthog = true,
bool doVerify = false)
{
MaxIter = _maxIter;
Nm = _Nm;
Nk = _Nk;
Nstop = _Nstop;
Nblock = _Nblock;
{
int divisor = 1;
if (useParityFlip) divisor *= 2;
if (useGamma5) divisor *= 2;
assert(Nblock % divisor == 0 && (int)v0.size() >= Nblock / divisor);
std::cout << GridLogMessage << className << ": divisor = " << divisor << std::endl;
}
assert(Nm % Nblock == 0);
assert(Nk % Nblock == 0);
assert(Nk < Nm);
if (useGamma5) assert(gamma5Func && "useGamma5: gamma5Func must be set");
preRun(); // hook: derived classes add parameter assertions here
int N = Nm; // total Krylov dimension
// Approximate largest eigenvalue for tolerance normalisation
RealD approxLambdaMax = approxMaxEval(v0[0]);
rtol = Tolerance * approxLambdaMax;
std::cout << GridLogMessage << className << ": approx max eval = "
<< approxLambdaMax << ", rtol = " << rtol << std::endl;
// Initialise
H = CMat::Zero(N, N);
B = CMat::Zero(N, Nblock);
int start = 0;
std::vector<Field> startBlock = expandStartBlock(v0);
for (int iter = 0; iter < MaxIter; iter++) {
std::cout << GridLogMessage << className << ": restart iteration " << iter << std::endl;
// ---- Block Arnoldi: extend from block start to block Nm/Nblock ----
blockArnoldiIteration(startBlock, Nm/Nblock, start, doubleOrthog);
// After first full cycle start from block Nk/Nblock
start = Nk/Nblock;
if (doVerify) {
std::string lbl = "iter " + std::to_string(iter) + " after Arnoldi";
verify(lbl);
}
// ---- Restart rotation: compute Q and the rotated+reordered Rayleigh
// quotient Hnew. Base class uses the plain Schur decomposition of
// H; derived classes (e.g. HarmonicBlockKrylovSchur) override this
// to target a shift.
CMat Q, Hnew;
restartRotation(Q, Hnew);
CMat Qt = Q.adjoint();
// Rotate Krylov basis: basis_new[i] = sum_j basis[j] * Qt(j,i)
std::vector<Field> basis2;
constructUR(basis2, basis, Qt, N);
basis = basis2;
// Update B and H
B = Q * B;
H = Hnew;
// ---- Truncate to Nk ----
int Nkeep = Nk;
CMat Htmp = H(Eigen::seqN(0, Nkeep), Eigen::seqN(0, Nkeep));
H = CMat::Zero(N, N);
H(Eigen::seqN(0, Nkeep), Eigen::seqN(0, Nkeep)) = Htmp;
std::vector<Field> basisTmp(basis.begin(), basis.begin() + Nkeep);
basis = basisTmp;
CMat Btmp = B(Eigen::seqN(0, Nkeep), Eigen::all);
B = CMat::Zero(N, Nblock);
B(Eigen::seqN(0, Nkeep), Eigen::all) = Btmp;
// beta_k = Frobenius norm of the effective coupling
beta_k = Btmp.norm();
std::cout << GridLogMessage << className << ": beta_k = " << beta_k << std::endl;
// Restart: the new starting block is F (the residual block from Arnoldi)
startBlock = F;
if (doVerify) {
std::string lbl = "iter " + std::to_string(iter) + " after restart+truncation";
verify(lbl);
}
// ---- Compute eigensystem of truncated H for convergence check ----
CMat Hk = H(Eigen::seqN(0, Nkeep), Eigen::seqN(0, Nkeep));
computeEigensystem(Hk, Nkeep);
int Nconv = converged(Nkeep);
std::cout << GridLogMessage << className << ": converged " << Nconv
<< " / " << Nstop << std::endl;
if (Nconv >= Nstop || iter == MaxIter - 1) {
std::cout << GridLogMessage << className << ": done after " << iter
<< " restarts, " << Nconv << " converged." << std::endl;
std::cout << GridLogMessage << "Eigenvalues: " << evals << std::endl;
if (doEvalCheck) {
Field w(Grid_);
for (int k = 0; k < (int)evecs.size(); k++) {
Linop.Op(evecs[k], w);
ComplexD eval_est = toStdCmplx(innerProduct(evecs[k], w));
w -= eval_est * evecs[k];
RealD res = std::sqrt(norm2(w));
std::cout << GridLogMessage << className << ": evec[" << k << "]"
<< " eval_reported = " << evals[k]
<< " eval_est = " << eval_est
<< " || A v - eval_est * v || = " << res << std::endl;
}
}
return;
}
}
}
// Accessors
std::vector<Field> getEvecs() { return evecs; }
CVec getEvals() { return evals; }
std::vector<RealD> getRitzEstimates() { return ritzEstimates; }
// Negate the field on all odd-parity (checkerboard) sites.
// Uses pickCheckerboard/setCheckerboard so the SIMD layout is handled
// correctly and the negation runs in parallel.
static void parityFlippedField(const Field& v, Field& out) {
GridCartesian* cgrid = dynamic_cast<GridCartesian*>(v.Grid());
assert(cgrid != nullptr);
GridRedBlackCartesian rbgrid(cgrid);
Field veven(&rbgrid), vodd(&rbgrid);
pickCheckerboard(Even, veven, v);
pickCheckerboard(Odd, vodd, v);
vodd = -vodd;
setCheckerboard(out, veven);
setCheckerboard(out, vodd);
}
//--------------------------------------------------------------------
// Verification: print H and B, check A V = V H + F B^dag explicitly
//--------------------------------------------------------------------
/**
* Checks the block Arnoldi / Krylov-Schur decomposition
*
* A V = V H + F B^dag (KS)
*
* by explicit operator applications. For each basis vector j:
*
* w_j = A basis[j]
*
* The nBasis × nBasis matrix M of inner products is computed:
*
* M[i, j] = <basis[i] | A basis[j]>
*
* and compared column-by-column against H. Separately, the nBasis × Nblock
* residual coupling matrix R is computed:
*
* R[j, t] = <basis[j] | F[t]> * ||F[t]|| (scaled by F-block norms)
*
* but since F is already normalised, R[j,t] = <basis[j] | F[t]>.
*
* The KS relation for column j reads:
* w_j = sum_i basis[i] H[i,j] + sum_t F[t] B[j,t]*
* so the deviation in column j is
* dev_j = w_j - sum_i basis[i] M[i,j] (should be zero for exact arithmetic)
* augmented by the F B^dag term in the last block.
*
* Prints:
* - H (current Rayleigh quotient, nBasis × nBasis)
* - B (coupling matrix, nBasis × Nblock)
* - M (explicit inner product matrix <V | A V>)
* - max |H[i,j] - M[i,j]| (should be O(machine epsilon))
* - for each basis column j: || A v_j - V H[:,j] - F B[j,:]^* ||
*
* Parameters
* ----------
* label : string printed at the start (e.g. "after restart 2")
*/
void verify(const std::string& label = "")
{
int nBasis = (int)basis.size();
int nF = (int)F.size();
if (nBasis == 0) {
std::cout << GridLogMessage
<< className << "::verify [" << label
<< "]: basis is empty." << std::endl;
return;
}
// Full allocated dimension of H (= Nm); may be larger than nBasis after restart+truncation
int Nfull = (int)H.rows();
std::cout << GridLogMessage
<< "======== " << className << "::verify [" << label << "] ========" << std::endl;
std::cout << GridLogMessage
<< " nBasis = " << nBasis << " Nfull = " << Nfull
<< " Nblock = " << Nblock << " nF = " << nF << std::endl;
// ---- Print H (full Nm x Nm matrix) ----
// After restart+truncation nBasis < Nfull; entries outside [0:nBasis,0:nBasis]
// should be ~0 — printing the full matrix makes this visible.
std::cout << GridLogMessage << "H (" << Nfull << " x " << Nfull << "):" << std::endl;
for (int i = 0; i < Nfull; i++) {
for (int j = 0; j < Nfull; j++)
std::cout << " " << std::setprecision(4) << std::setw(14) << H(i, j);
std::cout << std::endl;
}
// ---- Print B (full Nm x Nblock matrix) ----
std::cout << GridLogMessage << "B (" << Nfull << " x " << nF << "):" << std::endl;
for (int i = 0; i < Nfull; i++) {
for (int t = 0; t < nF; t++)
std::cout << " " << std::setprecision(4) << std::setw(14) << B(i, t);
std::cout << std::endl;
}
// ---- Compute M[i,j] = <basis[i] | A basis[j]> ----
CMat M = CMat::Zero(nBasis, nBasis);
Field w(Grid_);
for (int j = 0; j < nBasis; j++) {
Linop.Op(basis[j], w);
for (int i = 0; i < nBasis; i++)
M(i, j) = toStdCmplx(innerProduct(basis[i], w));
}
std::cout << GridLogMessage << "M = <V|AV> (" << nBasis << " x " << nBasis << "):" << std::endl;
for (int i = 0; i < nBasis; i++) {
for (int j = 0; j < nBasis; j++)
std::cout << " " << std::setprecision(4) << std::setw(14) << M(i, j);
std::cout << std::endl;
}
// ---- max |H - M| (for the nBasis x nBasis Rayleigh-quotient block) ----
RealD maxHM = 0.0;
for (int i = 0; i < nBasis; i++)
for (int j = 0; j < nBasis; j++)
maxHM = std::max(maxHM, std::abs(H(i,j) - M(i,j)));
std::cout << GridLogMessage
<< " max |H[i,j] - M[i,j]| (i,j < nBasis) = " << maxHM << std::endl;
// ---- Check entries of H OUTSIDE the nBasis x nBasis block ----
// These live in rows i >= nBasis or columns j >= nBasis.
// They must be ~0 after restart+truncation (before the coupling rows are
// re-filled by the next blockArnoldiIteration call).
// During a full Arnoldi run (nBasis == Nfull) this loop is a no-op.
RealD maxOutside = 0.0;
for (int i = 0; i < Nfull; i++)
for (int j = 0; j < Nfull; j++)
if (i >= nBasis || j >= nBasis)
maxOutside = std::max(maxOutside, std::abs(H(i,j)));
if (Nfull > nBasis)
std::cout << GridLogMessage
<< " max |H[i,j]| outside [0:" << nBasis << ",0:" << nBasis
<< "] block = " << maxOutside
<< " (should be ~0 after restart+truncation)" << std::endl;
// ---- Check orthonormality of basis ----
CMat G = CMat::Zero(nBasis, nBasis);
for (int i = 0; i < nBasis; i++)
for (int j = 0; j < nBasis; j++)
G(i, j) = toStdCmplx(innerProduct(basis[i], basis[j]));
CMat Gerr = G - CMat::Identity(nBasis, nBasis);
std::cout << GridLogMessage
<< " max |<V_i|V_j> - delta_ij| = " << Gerr.cwiseAbs().maxCoeff() << std::endl;
// ---- Per-column residual: || A v_j - V H[:,j] - F B[j,:]^* || ----
// For each basis vector j, compute A v_j then subtract V H[:,j] and F B[j,:]^*
RealD maxColDev = 0.0;
for (int j = 0; j < nBasis; j++) {
Linop.Op(basis[j], w);
// subtract V H[:,j]
for (int i = 0; i < nBasis; i++)
w -= basis[i] * H(i, j);
// subtract F B[j,:]^* (F[t] * conj(B[j,t]))
for (int t = 0; t < nF; t++)
w -= F[t] * std::conj(B(j, t));
RealD dev = std::sqrt(norm2(w));
std::cout << GridLogMessage
<< " || A v[" << j << "] - V H[:,j] - F B[j,:]* || = " << dev << std::endl;
maxColDev = std::max(maxColDev, dev);
}
std::cout << GridLogMessage
<< " max column deviation = " << maxColDev << std::endl;
// ---- Check F block orthogonality against basis ----
if (nF > 0) {
RealD maxFV = 0.0;
for (int t = 0; t < nF; t++)
for (int i = 0; i < nBasis; i++) {
RealD ip = std::abs(toStdCmplx(innerProduct(basis[i], F[t])));
maxFV = std::max(maxFV, ip);
}
std::cout << GridLogMessage
<< " max |<V_i | F_t>| (should be ~0) = " << maxFV << std::endl;
}
std::cout << GridLogMessage
<< "======== end verify ========" << std::endl;
}
//--------------------------------------------------------------------
// Extension hooks for derived classes
//--------------------------------------------------------------------
/**
* Apply the operator (or poly-filtered operator) to the current Arnoldi
* block and return the results in W.
*
* Base implementation: W[t] = Linop.Op(basis[kBase + t]).
* Derived classes (e.g. SplitGridBlockKrylovSchur) override this to
* apply poly(A) via Grid_split / Grid_unsplit instead.
*/
virtual void applyBlock(std::vector<Field>& W, int kBase)
{
for (int t = 0; t < Nblock; t++)
Linop.Op(basis[kBase + t], W[t]);
}
/**
* Called once at the start of operator(), after parameters are set.
* Base implementation is a no-op; derived classes override to add
* parameter assertions (e.g. Nblock % mrhs == 0).
*/
virtual void preRun() {}
/**
* Compute the restart rotation for the current Rayleigh quotient H.
*
* Returns Q (unitary) and Hnew = Q H Q^dag reordered so that the wanted
* Nk eigenvalues occupy the leading block, ready for truncation.
*
* Base implementation: the plain Schur decomposition of H, reordered by
* ritzFilter. HarmonicBlockKrylovSchur overrides this to Schur-decompose
* the shifted quotient (H - sigma I) so that eigenvalues nearest sigma are
* selected.
*/
virtual void restartRotation(CMat& Q, CMat& Hnew)
{
ComplexSchurDecomposition schur(H, false, ritzFilter);
std::cout << GridLogMessage << className << ": Schur decomposed." << std::endl;
schur.schurReorder(Nk);
std::cout << GridLogMessage << className << ": Schur reordered." << std::endl;
Q = schur.getMatrixQ();
Hnew = schur.getMatrixS();
}
protected:
//--------------------------------------------------------------------
// Starting-block expansion (parity flip / gamma5 partners)
//--------------------------------------------------------------------
/**
* Expand Nblock/divisor seed vectors into the full Nblock starting block by
* pairing each seed with its parity-flipped and/or gamma5 partners. Order:
* {v, flip(v)} then doubled by {., g5(.)} giving {v, flip(v), g5(v),
* g5(flip(v))} when both flags are set. Requires Nblock, Grid_ and the
* flags to be set.
*/
std::vector<Field> expandStartBlock(const std::vector<Field>& v0)
{
int divisor = (useParityFlip ? 2 : 1) * (useGamma5 ? 2 : 1);
std::vector<Field> startBlock;
startBlock.reserve(Nblock);
for (int i = 0; i < Nblock / divisor; i++) {
std::vector<Field> group;
group.push_back(v0[i]);
if (useParityFlip) {
int n = (int)group.size();
for (int j = 0; j < n; j++) {
Field fp(Grid_);
parityFlippedField(group[j], fp);
group.push_back(std::move(fp));
}
}
if (useGamma5) {
int n = (int)group.size();
for (int j = 0; j < n; j++) {
Field g5v(Grid_);
gamma5Func(group[j], g5v);
group.push_back(std::move(g5v));
}
}
for (auto& f : group) startBlock.push_back(std::move(f));
}
return startBlock;
}
protected:
//--------------------------------------------------------------------
// Block Arnoldi iteration
//--------------------------------------------------------------------
/**
* Extends the block Arnoldi factorisation from block index 'start' to
* block index 'Nm'.
*
* On entry (start > 0): basis[0..start*Nblock-1] already set,
* H[0..start*Nblock-1, 0..start*Nblock-1] already set,
* B[start*Nblock-1, :] set (coupling from prior residual block).
* startBlock = the normalised residual block F from the previous cycle.
*
* On entry (start == 0): initialises everything from startBlock.
*/
void blockArnoldiIteration(std::vector<Field>& startBlock, int endBlock,
int startIdx, bool doubleOrthog)
{
int N = Nm;
if (startIdx == 0) {
basis.clear();
F.clear();
H = CMat::Zero(N, N);
B = CMat::Zero(N, Nblock);
// Orthonormalise starting block via modified Gram-Schmidt
std::vector<Field> V0 = startBlock;
blockOrthonormalise(V0);
for (auto& v : V0) basis.push_back(v);
} else {
// Append residual block (startBlock = F_old) to basis.
// The truncated KS relation after restart is:
//
// A V_k = V_k S_k + F_old B_old^dag (*)
//
// where V_k = basis[0:Nkeep], S_k is stored in H[0:Nkeep,0:Nkeep],
// B_old = B[0:Nkeep,:], F_old = startBlock.
//
// Once F_old is appended as basis[Nkeep:Nkeep+Nblock], (*) becomes
// a statement about the extended H matrix:
//
// H[Nkeep+t, j] = (B_old^dag)[t,j] = conj(B_old[j,t])
// for t=0..Nblock-1, j=0..Nkeep-1
//
// These entries are the "restart coupling rows" that connect the new
// block to all retained Schur vectors and must be set before Arnoldi
// continues, otherwise A V_k = V_k H[:,0:Nkeep] would be missing the
// F_old B_old^dag term for those columns.
int Nkeep = startIdx * Nblock;
for (auto& v : startBlock) basis.push_back(v);
// Fill restart coupling rows into H
for (int t = 0; t < Nblock; t++)
for (int j = 0; j < Nkeep; j++)
H(Nkeep + t, j) = std::conj(B(j, t));
// Zero out B for the retained columns now that the coupling is in H
for (int j = 0; j < Nkeep; j++)
for (int t = 0; t < Nblock; t++)
B(j, t) = 0.0;
}
// Main block Arnoldi loop
for (int k = startIdx; k < endBlock; k++) {
blockArnoldiStep(k, doubleOrthog);
}
}
//--------------------------------------------------------------------
/**
* One block Arnoldi step: extends by one block (Nblock vectors).
*
* Computes block column k of H and the next basis block V_{k+1}.
*
* Layout of basis (flat):
* basis[j*Nblock + t] = t-th vector of j-th block, j = 0..k
*
* After this call:
* H[i, k*Nblock : (k+1)*Nblock] filled for i = 0..(k+1)*Nblock - 1
* basis[k*Nblock .. (k+1)*Nblock - 1] normalised (already set on entry)
* F = residual block (to become V_{k+1} after this step if k < Nm-1)
*
* If k < Nm-1, also:
* H[(k+1)*Nblock : (k+2)*Nblock, k*Nblock : (k+1)*Nblock] = subdiag block (from QR of residual)
* basis extended by Nblock (the normalised residual vectors)
*/
void blockArnoldiStep(int k, bool doubleOrthog)
{
int kBase = k * Nblock; // first flat index of current block
int prevN = kBase + Nblock; // number of basis vectors so far after this step
int N = Nm;
// W[t] = op(basis[kBase + t]) — dispatches to applyBlock() virtual
std::vector<Field> W(Nblock, Field(Grid_));
applyBlock(W, kBase);
// Orthogonalise W against all current basis vectors (full reorthogonalisation)
// H[i, kBase + t] = <basis[i] | W[t]>
for (int pass = 0; pass < (doubleOrthog ? 2 : 1); pass++) {
for (int i = 0; i < prevN; i++) {
for (int t = 0; t < Nblock; t++) {
ComplexD coeff = innerProduct(basis[i], W[t]);
if (pass == 0)
H(i, kBase + t) = toStdCmplx(coeff);
else
H(i, kBase + t) += toStdCmplx(coeff);
W[t] -= coeff * basis[i];
}
}
}
// Store residual block F
F = W;
if (k == Nm/Nblock - 1) {
// Last block: compute coupling matrix B for KS decomp.
//
// blockQR modifies F in-place (F → Q orthonormal) and returns R
// such that W_orig[t] = sum_s F[s] * R[s,t] (W_orig = F_after * R).
//
// The KS relation for column j = kBase+t requires the coefficient of F[s]
// to be (B†)[s,j] = conj(B[j,s]). Matching with R[s,t]:
// conj(B[kBase+t, s]) = R[s,t] → B[kBase+t, s] = conj(R[s,t])
//
// Equivalently the last Nblock rows of B are R^H (Hermitian conjugate of R).
// Note: for Nblock=1, R is scalar real positive, so this reduces to B = R. ✓
CMat R = blockQR(F); // F is modified in-place to become Q; returns R
for (int t = 0; t < Nblock; t++)
for (int s = 0; s < Nblock; s++)
B(kBase + t, s) = std::conj(R(s, t)); // B_block = R^H
beta_k = R.norm();
return;
}
// Not last block: QR-decompose residual to get V_{k+1}
CMat R = blockQR(F); // F orthonormalised in-place, R is upper triangular
// Subdiagonal block of H: H[(k+1)*Nblock : (k+2)*Nblock, kBase : kBase+Nblock] = R
int nextBase = (k + 1) * Nblock;
for (int i = 0; i < Nblock; i++)
for (int j = 0; j < Nblock; j++)
H(nextBase + i, kBase + j) = R(i, j);
// Append normalised residual block to basis
for (int t = 0; t < Nblock; t++)
basis.push_back(F[t]);
}
//--------------------------------------------------------------------
// Block QR via modified Gram-Schmidt within the block
//--------------------------------------------------------------------
/**
* Given a block of Nblock vectors W (not necessarily orthonormal),
* orthonormalises them in-place and returns the upper-triangular R
* such that W_in = W_out * R.
*
* Handles (near-)linear dependence by zeroing vectors below threshold.
*/
CMat blockQR(std::vector<Field>& W)
{
CMat R = CMat::Zero(Nblock, Nblock);
const RealD deflThresh = 1e-14;
for (int j = 0; j < Nblock; j++) {
// Orthogonalise W[j] against W[0..j-1]
for (int i = 0; i < j; i++) {
ComplexD coeff = innerProduct(W[i], W[j]);
R(i, j) = toStdCmplx(coeff);
W[j] -= coeff * W[i];
}
RealD nrm = std::sqrt(norm2(W[j]));
R(j, j) = nrm;
if (nrm > deflThresh) {
W[j] *= (1.0 / nrm);
} else {
// deflation: zero this vector
W[j] = Zero();
std::cout << GridLogMessage
<< className << ": deflation at block column " << j
<< " (norm = " << nrm << ")" << std::endl;
}
}
return R;
}
//--------------------------------------------------------------------
// Orthonormalise a block against itself (no prior basis)
//--------------------------------------------------------------------
void blockOrthonormalise(std::vector<Field>& V)
{
for (int j = 0; j < (int)V.size(); j++) {
for (int i = 0; i < j; i++) {
ComplexD c = innerProduct(V[i], V[j]);
V[j] -= c * V[i];
}
RealD nrm = std::sqrt(norm2(V[j]));
assert(nrm > 1e-14);
V[j] *= (1.0 / nrm);
}
}
//--------------------------------------------------------------------
// Basis rotation: UR[i] = sum_j U[j] * R(j, i)
//--------------------------------------------------------------------
void constructUR(std::vector<Field>& UR, std::vector<Field>& U,
CMat& R, int N)
{
UR.clear();
Field tmp(Grid_);
for (int i = 0; i < N; i++) {
tmp = Zero();
for (int j = 0; j < N; j++)
tmp += U[j] * R(j, i);
UR.push_back(tmp);
}
}
//--------------------------------------------------------------------
// Eigensystem of the truncated Rayleigh quotient
//--------------------------------------------------------------------
void computeEigensystem(CMat& Hk, int Nkeep)
{
Eigen::ComplexEigenSolver<CMat> es;
es.compute(Hk);
// Sort to match schurReorder ordering.
int n = es.eigenvalues().size();
ComplexComparator cComp(ritzFilter);
std::vector<int> idx(n);
std::iota(idx.begin(), idx.end(), 0);
std::sort(idx.begin(), idx.end(), [&](int a, int b){
return cComp(toStdCmplx(es.eigenvalues()(a)), toStdCmplx(es.eigenvalues()(b)));
});
evals.resize(n);
littleEvecs.resize(n, n);
for (int k = 0; k < n; k++) {
evals(k) = es.eigenvalues()(idx[k]);
littleEvecs.col(k) = es.eigenvectors().col(idx[k]);
}
evecs.clear();
for (int k = 0; k < Nkeep; k++) {
CVec vec = littleEvecs.col(k);
Field tmp(Grid_);
tmp = Zero();
for (int j = 0; j < (int)basis.size() && j < Nkeep; j++)
tmp += vec[j] * basis[j];
evecs.push_back(tmp);
}
}
//--------------------------------------------------------------------
// Convergence check
//--------------------------------------------------------------------
/**
* An eigenpair (lambda, y) is converged if the Ritz estimate
* r = || B^dag y ||
* satisfies r < rtol. Here B is the (Nkeep x Nblock) coupling matrix
* and y is the little eigenvector (Nkeep-vector) of H.
*/
int converged(int Nkeep)
{
ritzEstimates.clear();
int Nconv = 0;
CMat Bk = B(Eigen::seqN(0, Nkeep), Eigen::all); // Nkeep x Nblock
for (int k = 0; k < Nkeep; k++) {
CVec yk = littleEvecs.col(k); // Nkeep-vector
CVec Bty = Bk.adjoint() * yk; // Nblock-vector
RealD res = Bty.norm();
ritzEstimates.push_back(res);
std::cout << GridLogMessage << className << ": Ritz estimate[" << k
<< "] = " << res << " eval = " << evals[k] << std::endl;
if (res < rtol) Nconv++;
else break;
}
return Nconv;
}
//--------------------------------------------------------------------
// Approximate maximum eigenvalue via power iteration
//--------------------------------------------------------------------
RealD approxMaxEval(const Field& v0, int MAX_ITER = 50)
{
assert(norm2(v0) > 1e-8);
RealD lam = 0.0, denom = std::sqrt(norm2(v0));
Field vcur(Grid_), vtmp(Grid_);
vcur = v0;
for (int i = 0; i < MAX_ITER; i++) {
Linop.Op(vcur, vtmp);
vcur = vtmp;
RealD num = std::sqrt(norm2(vcur));
lam = num / denom;
denom = num;
}
return lam;
}
};
NAMESPACE_END(Grid);
#endif // GRID_BLOCKED_KRYLOV_SCHUR_H
@@ -0,0 +1,277 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/ConjugateGradientTimeslice.h
Copyright (C) 2015
Author: Azusa Yamaguchi <ayamaguc@staffmail.ed.ac.uk>
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: paboyle <paboyle@ph.ed.ac.uk>
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 */
#ifndef GRID_CONJUGATE_GRADIENT_TIMESLICE_H
#define GRID_CONJUGATE_GRADIENT_TIMESLICE_H
NAMESPACE_BEGIN(Grid);
/////////////////////////////////////////////////////////////
// Base classes for iterative processes based on operators
// single input vec, single output vec.
/////////////////////////////////////////////////////////////
/**
* Simple modification of conjugate gradient that outputs the residual as a function
* of time, in order to study the large wavelength behavior of the solver.
*/
template <class Field>
class ConjugateGradientTimeslice : public OperatorFunction<Field> {
public:
using OperatorFunction<Field>::operator();
bool ErrorOnNoConverge; // throw an assert when the CG fails to converge.
// Defaults true.
RealD Tolerance;
Integer MaxIterations;
Integer IterationsToComplete; //Number of iterations the CG took to finish. Filled in upon completion
RealD TrueResidual;
ConjugateGradientTimeslice(RealD tol, Integer maxit, bool err_on_no_conv = true)
: Tolerance(tol),
MaxIterations(maxit),
ErrorOnNoConverge(err_on_no_conv)
{};
virtual void LogIteration(int k,RealD a,RealD b){
// std::cout << "ConjugageGradient::LogIteration() "<<std::endl;
};
virtual void LogBegin(void){
std::cout << "ConjugageGradient::LogBegin() "<<std::endl;
};
void operator()(LinearOperatorBase<Field> &Linop, const Field &src, Field &psi) {
this->LogBegin();
GRID_TRACE("ConjugateGradientTimeslice");
GridStopWatch PreambleTimer;
GridStopWatch ConstructTimer;
GridStopWatch NormTimer;
GridStopWatch AssignTimer;
PreambleTimer.Start();
psi.Checkerboard() = src.Checkerboard();
conformable(psi, src);
RealD cp, c, a, d, b, ssq, qq;
//RealD b_pred;
// Was doing copies
ConstructTimer.Start();
Field p (src.Grid());
Field mmp(src.Grid());
Field r (src.Grid());
ConstructTimer.Stop();
// Initial residual computation & set up
NormTimer.Start();
ssq = norm2(src); // Norm of source vector ||b||^2
ssqtx = localNorm2(src); // Norm |b(x, t)|^2 as a field
std::vector<RealD> ssqt; // Norm of source not summed over time slices, ssq(t) = \sum_x |b(x, t)|^2
sliceSum(ssqtx, ssqt, Tdir); // TODO make sure Tdir is globally defined
RealD guess = norm2(psi); // Norm of initial guess ||psi||^2
NormTimer.Stop();
assert(std::isnan(guess) == 0);
AssignTimer.Start();
if ( guess == 0.0 ) {
r = src;
p = r;
a = ssq;
} else {
Linop.HermOpAndNorm(psi, mmp, d, b); //
r = src - mmp; // Initial residual r0 = b - A guess
p = r; // initial conj vector p0 = r0
a = norm2(p);
}
cp = a;
AssignTimer.Stop();
// Handle trivial case of zero src
if (ssq == 0.){
psi = Zero();
IterationsToComplete = 1;
TrueResidual = 0.;
return;
}
std::cout << GridLogIterative << std::setprecision(8) << "ConjugateGradient: guess " << guess << std::endl;
std::cout << GridLogIterative << std::setprecision(8) << "ConjugateGradient: src " << ssq << std::endl;
std::cout << GridLogIterative << std::setprecision(8) << "ConjugateGradient: mp " << d << std::endl;
std::cout << GridLogIterative << std::setprecision(8) << "ConjugateGradient: mmp " << b << std::endl;
std::cout << GridLogIterative << std::setprecision(8) << "ConjugateGradient: cp,r " << cp << std::endl;
std::cout << GridLogIterative << std::setprecision(8) << "ConjugateGradient: p " << a << std::endl;
RealD rsq = Tolerance * Tolerance * ssq;
// Check if guess is really REALLY good :)
if (cp <= rsq) {
TrueResidual = std::sqrt(a/ssq);
std::cout << GridLogMessage << "ConjugateGradient guess is converged already " << std::endl;
IterationsToComplete = 0;
return;
}
std::cout << GridLogIterative << std::setprecision(8)
<< "ConjugateGradient: k=0 residual " << cp << " target " << rsq << std::endl;
PreambleTimer.Stop();
GridStopWatch LinalgTimer;
GridStopWatch InnerTimer;
GridStopWatch AxpyNormTimer;
GridStopWatch LinearCombTimer;
GridStopWatch MatrixTimer;
GridStopWatch SolverTimer;
RealD usecs = -usecond();
SolverTimer.Start();
int k;
for (k = 1; k <= MaxIterations; k++) {
GridStopWatch IterationTimer;
IterationTimer.Start();
c = cp;
MatrixTimer.Start();
Linop.HermOp(p, mmp); // Computes mmp = Ap
MatrixTimer.Stop();
LinalgTimer.Start();
InnerTimer.Start();
ComplexD dc = innerProduct(p,mmp); // p^\dagger A p
InnerTimer.Stop();
d = dc.real();
a = c / d;
// What is axpy? Some accelerator or something? Check Lattice_arith.h
AxpyNormTimer.Start();
// axpy_norm computes ax+by for vectors x and y compatible with a GPU. Here b is set to 1 (see the function in Lattice_reduction.h).
// The first argument passes r by reference, so it stores r --> -a * Ap + 1 * r, i.e. it performs an update on
// r_k --> r_{k+1} = r_k - \alpha_k A p_k. The function returns the norm squared of the first variable, i.e. ||r_{k+1}||^2.
cp = axpy_norm(r, -a, mmp, r);
AxpyNormTimer.Stop();
b = cp / c;
LinearCombTimer.Start();
{
autoView( psi_v , psi, AcceleratorWrite);
autoView( p_v , p, AcceleratorWrite);
autoView( r_v , r, AcceleratorWrite);
accelerator_for(ss,p_v.size(), Field::vector_object::Nsimd(),{
coalescedWrite(psi_v[ss], a * p_v(ss) + psi_v(ss));
coalescedWrite(p_v[ss] , b * p_v(ss) + r_v (ss));
});
}
LinearCombTimer.Stop();
LinalgTimer.Stop();
LogIteration(k,a,b);
IterationTimer.Stop();
if ( (k % 500) == 0 ) {
std::cout << GridLogMessage << "ConjugateGradient: Iteration " << k
<< " residual " << sqrt(cp/ssq) << " target " << Tolerance << std::endl;
} else {
std::cout << GridLogIterative << "ConjugateGradient: Iteration " << k
<< " residual " << sqrt(cp/ssq) << " target " << Tolerance << " took " << IterationTimer.Elapsed() << std::endl;
}
// Stopping condition
if (cp <= rsq) {
usecs +=usecond();
SolverTimer.Stop();
Linop.HermOpAndNorm(psi, mmp, d, qq);
p = mmp - src;
GridBase *grid = src.Grid();
RealD DwfFlops = (1452. )*grid->gSites()*4*k
+ (8+4+8+4+4)*12*grid->gSites()*k; // CG linear algebra
RealD srcnorm = std::sqrt(norm2(src));
RealD resnorm = std::sqrt(norm2(p));
RealD true_residual = resnorm / srcnorm;
std::cout << GridLogMessage << "ConjugateGradient Converged on iteration " << k
<< "\tComputed residual " << std::sqrt(cp / ssq)
<< "\tTrue residual " << true_residual
<< "\tTarget " << Tolerance << std::endl;
// GridLogMessage logs the message to the terminal output; GridLogPerformance probably writes to a log file?
// std::cout << GridLogMessage << "\tPreamble " << PreambleTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tSolver Elapsed " << SolverTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "Time breakdown "<<std::endl;
std::cout << GridLogPerformance << "\tMatrix " << MatrixTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\tLinalg " << LinalgTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tInner " << InnerTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tAxpyNorm " << AxpyNormTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tLinearComb " << LinearCombTimer.Elapsed() <<std::endl;
std::cout << GridLogDebug << "\tMobius flop rate " << DwfFlops/ usecs<< " Gflops " <<std::endl;
if (ErrorOnNoConverge) assert(true_residual / Tolerance < 10000.0);
IterationsToComplete = k;
TrueResidual = true_residual;
return;
}
}
// Failed. Calculate true residual before giving up
// Linop.HermOpAndNorm(psi, mmp, d, qq);
// p = mmp - src;
//TrueResidual = sqrt(norm2(p)/ssq);
// TrueResidual = 1;
std::cout << GridLogMessage << "ConjugateGradient did NOT converge "<<k<<" / "<< MaxIterations
<<" residual "<< std::sqrt(cp / ssq)<< std::endl;
SolverTimer.Stop();
std::cout << GridLogMessage << "\tPreamble " << PreambleTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tConstruct " << ConstructTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tNorm " << NormTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tAssign " << AssignTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tSolver " << SolverTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "Solver breakdown "<<std::endl;
std::cout << GridLogMessage << "\tMatrix " << MatrixTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage<< "\tLinalg " << LinalgTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tInner " << InnerTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tAxpyNorm " << AxpyNormTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tLinearComb " << LinearCombTimer.Elapsed() <<std::endl;
if (ErrorOnNoConverge) assert(0);
IterationsToComplete = k;
}
};
NAMESPACE_END(Grid);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,230 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
γ5-Scalar Lanczos algorithm for γ5-Hermitian operators (Wilson Dirac).
Single-vector Lanczos using the γ5-inner product (u,v)_{γ5} = u†γ5v.
Builds the Krylov space {q, D_W q, D_W² q, ...} and projects D_W onto it,
producing a real tridiagonal matrix T_m whose eigenvalues approximate those
of D_W. Complex conjugate pairs of D_W eigenvalues appear as complex
eigenvalues of the real non-symmetric T_m.
Recurrence (G_k = sign(q_k†γ5 q_k) = ±1):
r_k = D_W q_k α_k q_k β_sup[k-1] q_{k-1}
β_sub[k] = √|r_k†γ5 r_k| (lower subdiag T_{k+1,k})
β_sup[k] = β_sub[k] G_{k+1}/G_k (upper superdiag T_{k,k+1})
q_{k+1} = r_k / β_sub[k]
T_m is real but not symmetric in general; eigenvalues via EigenSolver.
*************************************************************************************/
#ifndef GRID_GAMMA5_SCALAR_LANCZOS_H
#define GRID_GAMMA5_SCALAR_LANCZOS_H
#include <functional>
#include <numeric>
#include <iomanip>
NAMESPACE_BEGIN(Grid);
template<class Field>
class Gamma5ScalarLanczos {
public:
using Gamma5Func = std::function<void(const Field&, Field&)>;
private:
typedef Eigen::MatrixXcd CMat;
typedef Eigen::VectorXcd CVec;
typedef Eigen::MatrixXd RMat;
LinearOperatorBase<Field>& Linop;
GridBase* Grid_;
Gamma5Func applyGamma5;
RealD Tolerance;
int nSteps;
std::vector<Field> basis;
std::vector<RealD> alpha_; // diagonal of T_m
std::vector<RealD> beta_sub_; // lower subdiagonal: T_{k+1,k}
std::vector<RealD> beta_sup_; // upper superdiagonal: T_{k,k+1}
std::vector<int> gsign_; // G_k = ±1
CVec evals_;
std::vector<Field> evecs_;
std::vector<RealD> residuals_;
public:
bool doEvalCheck = false;
Gamma5ScalarLanczos(LinearOperatorBase<Field>& op, GridBase* grid,
Gamma5Func g5, RealD tol = 1e-8)
: Linop(op), Grid_(grid), applyGamma5(g5), Tolerance(tol), nSteps(0)
{}
CVec getEvals() { return evals_; }
std::vector<Field> getEvecs() { return evecs_; }
std::vector<RealD> getResiduals() { return residuals_; }
// v0 : starting vector
// maxSteps : maximum Lanczos steps
// Nstop : target Ritz pairs to return (0 = all)
// reorthog : full γ5-reorthogonalisation at each step
// filter : eigenvalue selection criterion
void operator()(const Field& v0, int maxSteps, int Nstop,
bool reorthog = false, RitzFilter filter = EvalImNormSmall)
{
basis.clear(); alpha_.clear(); beta_sub_.clear(); beta_sup_.clear(); gsign_.clear();
nSteps = 0;
// Normalise starting vector in γ5-norm: q_0 = v0 / √|v0†γ5 v0|
Field q(Grid_); q = v0;
Field g5q(Grid_);
applyGamma5(q, g5q);
RealD omega = std::real(toStdCmplx(innerProduct(g5q, q)));
RealD beta0 = std::sqrt(std::abs(omega));
assert(beta0 > 1e-14 && "starting vector has zero γ5-norm");
q *= (1.0 / beta0);
gsign_.push_back(omega >= 0 ? +1 : -1);
basis.push_back(q);
std::cout << GridLogMessage
<< "Gamma5ScalarLanczos: start (v0†γ5 v0)=" << omega
<< " G[0]=" << gsign_[0] << std::endl;
for (int k = 0; k < maxSteps; k++) {
// ── Apply operator ─────────────────────────────────────────────────
Field p(Grid_);
Linop.Op(basis[k], p);
// ── α_k = (q_k†γ5 D_W q_k) / G_k (real) ─────────────────────────
applyGamma5(basis[k], g5q);
RealD a = std::real(toStdCmplx(innerProduct(g5q, p))) / RealD(gsign_[k]);
alpha_.push_back(a);
// ── r = D_W q_k α_k q_k β_sup[k-1] q_{k-1} ───────────────────
Field r(Grid_); r = p;
r -= basis[k] * a;
if (k > 0)
r -= basis[k-1] * beta_sup_[k-1];
// ── Optional γ5-reorthogonalisation ────────────────────────────────
if (reorthog) {
for (int i = 0; i <= k; i++) {
applyGamma5(basis[i], g5q);
ComplexD c = toStdCmplx(innerProduct(g5q, r)) / RealD(gsign_[i]);
r -= basis[i] * c;
}
}
// ── β_sub[k] = √|r†γ5 r| ───────────────────────────────────────────
applyGamma5(r, g5q);
omega = std::real(toStdCmplx(innerProduct(g5q, r)));
RealD beta = std::sqrt(std::abs(omega));
std::cout << GridLogMessage
<< "Gamma5ScalarLanczos: k=" << k
<< " alpha=" << a
<< " beta=" << beta
<< " G[k]=" << gsign_[k]
<< " (r†γ5r)=" << omega << std::endl;
beta_sub_.push_back(beta);
if (beta < Tolerance) {
std::cout << GridLogMessage
<< "Gamma5ScalarLanczos: invariant subspace at step " << k << std::endl;
nSteps = k + 1;
break;
}
int G_next = (omega >= 0) ? +1 : -1;
gsign_.push_back(G_next);
// β_sup[k] = T_{k,k+1} = β_sub[k] * G_{k+1} / G_k
beta_sup_.push_back(beta * RealD(G_next) / RealD(gsign_[k]));
Field qnew(Grid_); qnew = r; qnew *= (1.0 / beta);
basis.push_back(qnew);
nSteps = k + 1;
}
if (nSteps == 0) return;
computeRitzPairs(nSteps, Nstop, filter);
}
private:
void computeRitzPairs(int m, int Nstop, RitzFilter filter)
{
// Assemble real tridiagonal T_m
RMat Tm = RMat::Zero(m, m);
for (int k = 0; k < m; k++) {
Tm(k, k) = alpha_[k];
if (k + 1 < m) {
Tm(k+1, k) = beta_sub_[k];
Tm(k, k+1) = beta_sup_[k];
}
}
std::cout << GridLogMessage
<< "Gamma5ScalarLanczos: T_m (" << m << "×" << m << "):" << std::endl;
for (int i = 0; i < m; i++) {
for (int j = 0; j < m; j++)
std::cout << " " << std::setprecision(8) << std::setw(16) << Tm(i,j);
std::cout << std::endl;
}
Eigen::EigenSolver<RMat> es(Tm);
CVec lambdas = es.eigenvalues();
CMat Y = es.eigenvectors();
// Sort by filter criterion
ComplexComparator cComp(filter);
std::vector<int> idx(m);
std::iota(idx.begin(), idx.end(), 0);
std::sort(idx.begin(), idx.end(), [&](int a, int b){
return cComp(toStdCmplx(lambdas(a)), toStdCmplx(lambdas(b)));
});
int Nout = (Nstop > 0 && Nstop < m) ? Nstop : m;
evals_.resize(Nout);
evecs_.clear(); evecs_.reserve(Nout);
residuals_.clear(); residuals_.reserve(Nout);
// Trailing β for residual estimate: β_sub[m-1] * |y_j[m-1]|
RealD beta_trail = (m - 1 < (int)beta_sub_.size()) ? beta_sub_[m-1] : 0.0;
for (int ji = 0; ji < Nout; ji++) {
int j = idx[ji];
evals_(ji) = lambdas(j);
CVec yj = Y.col(j);
// Ritz vector: u_j = Σ_k q_k y_j[k]
Field uj(Grid_); uj = Zero();
for (int k = 0; k < m && k < (int)basis.size(); k++)
uj += basis[k] * toStdCmplx(yj(k));
evecs_.push_back(uj);
RealD res = beta_trail * std::abs(toStdCmplx(yj(m-1)));
if (!std::isfinite(res)) res = std::numeric_limits<RealD>::infinity();
residuals_.push_back(res);
std::cout << GridLogMessage
<< " Ritz[" << std::setw(3) << ji << "]"
<< " lambda=" << evals_(ji)
<< " |res|=" << res;
if (doEvalCheck) {
Field w(Grid_);
Linop.Op(uj, w);
w -= uj * evals_(ji);
RealD actual = std::sqrt(norm2(w));
std::cout << " ||D_W u - λu||=" << actual;
}
std::cout << std::endl;
}
}
};
NAMESPACE_END(Grid);
#endif // GRID_GAMMA5_SCALAR_LANCZOS_H
@@ -0,0 +1,719 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/HarmonicBlockKrylovSchur.h
Copyright (C) 2015
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: Chulwoo Jung <chulwoo@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.
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 */
#ifndef GRID_HARMONIC_BLOCKED_KRYLOV_SCHUR_H
#define GRID_HARMONIC_BLOCKED_KRYLOV_SCHUR_H
#include <iomanip>
#include <numeric>
NAMESPACE_BEGIN(Grid);
/**
* Block shift-targeted Krylov-Schur eigensolver.
*
* Algorithm
* ---------
* Uses a block Arnoldi factorisation:
*
* A V = V H + F B^dag (1)
*
* with V orthonormal (Nm columns), H the Nm×Nm block upper-Hessenberg
* Rayleigh quotient, F the Nblock residual vectors and B the Nm×Nblock
* coupling matrix.
*
* Thick restart
* -------------
* To target eigenvalues near shift σ, the Schur decomposition is computed
* for the shifted Rayleigh quotient:
*
* (H - σI) = Q^dag S Q (2)
*
* Sorting the Schur values of (H - σI) by smallest |S(i,i)| = |λ - σ| and
* retaining the leading Nk is equivalent to selecting the Ritz values of H
* closest to σ. Since Q diagonalises H - σI (and hence H itself), the
* rotated Rayleigh quotient is exactly upper triangular:
*
* H_new = Q H Q^dag = S + σI (upper triangular) (3)
*
* Truncation to Nk is therefore exact: the off-diagonal coupling block
* H_new[Nk:, :Nk] = 0 by triangularity. The Krylov-Schur relation after
* restart is exact and block Arnoldi resumes cleanly from F.
*
* Convergence
* -----------
* Convergence is declared when || B^H y_k || < Tolerance * approxLambdaMax
* for each Ritz pair (λ_k, y_k) of the truncated H.
*
* Parameters
* ----------
* shift : target shift σ (default 0.0); Schur values sorted by |λ - σ|
* Nblock : block size p
* Nm : total Krylov dimension (must be divisible by Nblock)
* Nk : total vectors to retain after each restart (must be divisible by Nblock, Nk < Nm)
* Nstop : stop when this many eigenpairs converge
* MaxIter : maximum outer (restart) iterations
* Tolerance: relative convergence tolerance
*
* Usage
* -----
* HarmonicBlockKrylovSchur<Field> hbks(LinOp, Grid, tol, shift, EvalNormSmall);
* std::vector<Field> v0(Nblock, Field(Grid));
* // fill v0 with random starting vectors
* hbks(v0, maxIter, Nm, Nk, Nstop, Nblock);
* auto evals = hbks.getEvals();
* auto evecs = hbks.getEvecs();
*/
template<class Field>
class HarmonicBlockKrylovSchur {
protected:
typedef Eigen::MatrixXcd CMat;
typedef Eigen::VectorXcd CVec;
//--------------------------------------------------------------------
// Parameters
//--------------------------------------------------------------------
int Nblock;
int Nm; // total Krylov dimension (multiple of Nblock)
int Nk; // total vectors retained after restart (multiple of Nblock)
int Nstop;
int MaxIter;
RealD Tolerance;
ComplexD shift; // target shift σ
//--------------------------------------------------------------------
// Internal state
//--------------------------------------------------------------------
LinearOperatorBase<Field>& Linop;
GridBase* Grid_;
RitzFilter ritzFilter;
std::vector<Field> basis; // Nm*Nblock flat basis
CMat H; // (Nm*Nblock)² block-Hessenberg Rayleigh quotient
std::vector<Field> F; // Nblock residual vectors
CMat B; // (Nm*Nblock) × Nblock coupling matrix
RealD beta_k;
RealD rtol;
CVec evals;
CMat littleEvecs;
std::vector<RealD> ritzEstimates;
public:
std::vector<Field> evecs;
bool doEvalCheck = false;
bool useParityFlip = false;
bool useGamma5 = false;
std::function<void(const Field&, Field&)> gamma5Func;
//--------------------------------------------------------------------
// Constructor
//--------------------------------------------------------------------
HarmonicBlockKrylovSchur(LinearOperatorBase<Field>& _Linop, GridBase* _Grid,
RealD _Tolerance, ComplexD _shift = 0.0,
RitzFilter _rf = EvalNormSmall)
: Linop(_Linop), Grid_(_Grid), Tolerance(_Tolerance), shift(_shift),
ritzFilter(_rf),
Nblock(-1), Nm(-1), Nk(-1), Nstop(-1), MaxIter(-1),
beta_k(0.0), rtol(0.0)
{}
virtual ~HarmonicBlockKrylovSchur() = default;
//--------------------------------------------------------------------
// Main entry point
//--------------------------------------------------------------------
virtual void operator()(const std::vector<Field>& v0, int _maxIter, int _Nm, int _Nk,
int _Nstop, int _Nblock = 1, bool doubleOrthog = true,
bool doVerify = false)
{
MaxIter = _maxIter;
Nm = _Nm;
Nk = _Nk;
Nstop = _Nstop;
Nblock = _Nblock;
{
int divisor = 1;
if (useParityFlip) divisor *= 2;
if (useGamma5) divisor *= 2;
assert(Nblock % divisor == 0 && (int)v0.size() >= Nblock / divisor);
std::cout << GridLogMessage << "divisor= " << divisor << std::endl;
}
assert(Nm % Nblock == 0);
assert(Nk % Nblock == 0);
assert(Nk < Nm);
if (useGamma5) assert(gamma5Func && "useGamma5: gamma5Func must be set");
int N = Nm;
RealD approxLambdaMax = approxMaxEval(v0[0]);
rtol = Tolerance * approxLambdaMax;
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: approx max eval = " << approxLambdaMax
<< ", rtol = " << rtol
<< ", shift = " << shift << std::endl;
H = CMat::Zero(N, N);
B = CMat::Zero(N, Nblock);
int start = 0;
std::vector<Field> startBlock = expandStartBlock(v0);
for (int iter = 0; iter < MaxIter; iter++) {
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: restart iteration " << iter << std::endl;
// ---- Block Arnoldi: extend from block 'start' to block Nm/Nblock ----
blockArnoldiIteration(startBlock, Nm/Nblock, start, doubleOrthog);
start = Nk/Nblock;
if (doVerify) {
std::string lbl = "iter " + std::to_string(iter) + " after Arnoldi";
verify(lbl);
}
// ---- Schur decompose (H - σI) to select Schur vectors closest to σ ----
// Sorting the Schur values of (H - σI) by |S(i,i)| = |λ - σ| gives the
// Ritz values of H nearest the target shift without any matrix inversion.
// Because Q diagonalises (H - σI), it also diagonalises H:
// H_new = Q H Q^dag = S + σI (upper triangular)
// Truncation is therefore exact.
CMat Hshift = H - shift * CMat::Identity(N, N);
ComplexSchurDecomposition schur(Hshift, false, ritzFilter);
schur.schurReorder(Nk);
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: Ritz values nearest shift (first Nk):" << std::endl;
CMat S = schur.getMatrixS();
for (int i = 0; i < Nk; i++)
std::cout << GridLogMessage << " [" << i << "] " << S(i, i) + shift << std::endl;
CMat Q = schur.getMatrixQ();
CMat Qt = Q.adjoint();
// ---- Rotate Krylov basis ----
std::vector<Field> basis2;
constructUR(basis2, basis, Qt, N);
basis = basis2;
// ---- Update H and B ----
// H_new = S + σI is upper triangular; off-diagonal block H_new[Nk:,:Nk] = 0
H = S + shift * CMat::Identity(N, N);
B = Q * B;
// ---- Truncate to Nk (exact: H upper triangular) ----
int Nkeep = Nk;
CMat Htmp = H(Eigen::seqN(0, Nkeep), Eigen::seqN(0, Nkeep));
H = CMat::Zero(N, N);
H(Eigen::seqN(0, Nkeep), Eigen::seqN(0, Nkeep)) = Htmp;
std::vector<Field> basisTmp(basis.begin(), basis.begin() + Nkeep);
basis = basisTmp;
CMat Btmp = B(Eigen::seqN(0, Nkeep), Eigen::all);
B = CMat::Zero(N, Nblock);
B(Eigen::seqN(0, Nkeep), Eigen::all) = Btmp;
beta_k = Btmp.norm();
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: beta_k = " << beta_k << std::endl;
// Restart from F (exact: no discarded-basis correction needed)
startBlock = F;
if (doVerify) {
std::string lbl = "iter " + std::to_string(iter) + " after restart+truncation";
verify(lbl);
}
// ---- Eigensystem of truncated H for convergence ----
CMat Hk = H(Eigen::seqN(0, Nkeep), Eigen::seqN(0, Nkeep));
computeEigensystem(Hk, Nkeep);
int Nconv = converged(Nkeep);
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: converged " << Nconv
<< " / " << Nstop << std::endl;
if (Nconv >= Nstop || iter == MaxIter - 1) {
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: done after " << iter
<< " restarts, " << Nconv << " converged." << std::endl;
std::cout << GridLogMessage << "Eigenvalues: " << evals.transpose() << std::endl;
if (doEvalCheck) {
Field w(Grid_);
for (int k = 0; k < (int)evecs.size(); k++) {
Linop.Op(evecs[k], w);
ComplexD eval_est = toStdCmplx(innerProduct(evecs[k], w));
w -= eval_est * evecs[k];
RealD res = std::sqrt(norm2(w));
std::cout << GridLogMessage << "HarmonicBlockKrylovSchur: evec[" << k << "]"
<< " eval_reported = " << evals[k]
<< " eval_est = " << eval_est
<< " || A v - eval_est * v || = " << res << std::endl;
}
}
return;
}
}
}
// Accessors
std::vector<Field> getEvecs() { return evecs; }
CVec getEvals() { return evals; }
std::vector<RealD> getRitzEstimates() { return ritzEstimates; }
static void parityFlippedField(const Field& v, Field& out) {
GridCartesian* cgrid = dynamic_cast<GridCartesian*>(v.Grid());
assert(cgrid != nullptr);
GridRedBlackCartesian rbgrid(cgrid);
Field veven(&rbgrid), vodd(&rbgrid);
pickCheckerboard(Even, veven, v);
pickCheckerboard(Odd, vodd, v);
vodd = -vodd;
setCheckerboard(out, veven);
setCheckerboard(out, vodd);
}
//--------------------------------------------------------------------
// Verification: check A V = V H + F B^dag explicitly
//--------------------------------------------------------------------
/**
* Checks the block Arnoldi / Krylov-Schur decomposition
*
* A V = V H + F B^dag (KS)
*
* by explicit operator applications. H here is the standard Rayleigh
* quotient, so the KS relation is the same as for BlockKrylovSchur.
*
* Prints:
* - H (current Rayleigh quotient, nBasis × nBasis)
* - B (coupling matrix, nBasis × Nblock)
* - M (explicit inner product matrix <V | A V>)
* - max |H[i,j] - M[i,j]| (should be O(machine epsilon))
* - max |<V_i|V_j> - delta_ij| (orthonormality check)
* - for each basis column j: || A v_j - V H[:,j] - F B[j,:]^* ||
* - max |<V_i | F_t>| (F orthogonal to basis)
*/
void verify(const std::string& label = "")
{
int nBasis = (int)basis.size();
int nF = (int)F.size();
if (nBasis == 0) {
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur::verify [" << label
<< "]: basis is empty." << std::endl;
return;
}
std::cout << GridLogMessage
<< "======== HarmonicBlockKrylovSchur::verify [" << label << "] ========" << std::endl;
std::cout << GridLogMessage
<< " nBasis = " << nBasis << " Nblock = " << Nblock
<< " nF = " << nF << std::endl;
// ---- Print H ----
std::cout << GridLogMessage << "H (" << nBasis << " x " << nBasis << "):" << std::endl;
for (int i = 0; i < nBasis; i++) {
for (int j = 0; j < nBasis; j++)
std::cout << " " << std::setprecision(4) << std::setw(14) << H(i, j);
std::cout << std::endl;
}
// ---- Print B ----
std::cout << GridLogMessage << "B (" << nBasis << " x " << nF << "):" << std::endl;
for (int i = 0; i < nBasis; i++) {
for (int t = 0; t < nF; t++)
std::cout << " " << std::setprecision(4) << std::setw(14) << B(i, t);
std::cout << std::endl;
}
// ---- Compute M[i,j] = <basis[i] | A basis[j]> ----
CMat M = CMat::Zero(nBasis, nBasis);
Field w(Grid_);
for (int j = 0; j < nBasis; j++) {
Linop.Op(basis[j], w);
for (int i = 0; i < nBasis; i++)
M(i, j) = toStdCmplx(innerProduct(basis[i], w));
}
std::cout << GridLogMessage << "M = <V|AV> (" << nBasis << " x " << nBasis << "):" << std::endl;
for (int i = 0; i < nBasis; i++) {
for (int j = 0; j < nBasis; j++)
std::cout << " " << std::setprecision(4) << std::setw(14) << M(i, j);
std::cout << std::endl;
}
// ---- max |H - M| ----
RealD maxHM = 0.0;
for (int i = 0; i < nBasis; i++)
for (int j = 0; j < nBasis; j++)
maxHM = std::max(maxHM, std::abs(H(i,j) - M(i,j)));
std::cout << GridLogMessage
<< " max |H[i,j] - M[i,j]| = " << maxHM << std::endl;
// ---- Check orthonormality of basis ----
CMat G = CMat::Zero(nBasis, nBasis);
for (int i = 0; i < nBasis; i++)
for (int j = 0; j < nBasis; j++)
G(i, j) = toStdCmplx(innerProduct(basis[i], basis[j]));
CMat Gerr = G - CMat::Identity(nBasis, nBasis);
std::cout << GridLogMessage
<< " max |<V_i|V_j> - delta_ij| = " << Gerr.cwiseAbs().maxCoeff() << std::endl;
// ---- Per-column residual: || A v_j - V H[:,j] - F B[j,:]^* || ----
RealD maxColDev = 0.0;
for (int j = 0; j < nBasis; j++) {
Linop.Op(basis[j], w);
// subtract V H[:,j]
for (int i = 0; i < nBasis; i++)
w -= basis[i] * H(i, j);
// subtract F B[j,:]^* (F[t] * conj(B[j,t]))
for (int t = 0; t < nF; t++)
w -= F[t] * std::conj(B(j, t));
RealD dev = std::sqrt(norm2(w));
std::cout << GridLogMessage
<< " || A v[" << j << "] - V H[:,j] - F B[j,:]* || = " << dev << std::endl;
maxColDev = std::max(maxColDev, dev);
}
std::cout << GridLogMessage
<< " max column deviation = " << maxColDev << std::endl;
// ---- Check F block orthogonality against basis ----
if (nF > 0) {
RealD maxFV = 0.0;
for (int t = 0; t < nF; t++)
for (int i = 0; i < nBasis; i++) {
RealD ip = std::abs(toStdCmplx(innerProduct(basis[i], F[t])));
maxFV = std::max(maxFV, ip);
}
std::cout << GridLogMessage
<< " max |<V_i | F_t>| (should be ~0) = " << maxFV << std::endl;
}
std::cout << GridLogMessage
<< "======== end verify ========" << std::endl;
}
protected:
//--------------------------------------------------------------------
// Starting-block expansion (parity flip / gamma5 partners)
//--------------------------------------------------------------------
// Expands Nblock/divisor seed vectors into the full Nblock starting
// block, pairing each seed with its parity-flipped and/or gamma5
// partners. Requires Nblock, Grid_ and the flags to be set.
std::vector<Field> expandStartBlock(const std::vector<Field>& v0)
{
int divisor = (useParityFlip ? 2 : 1) * (useGamma5 ? 2 : 1);
std::vector<Field> startBlock;
startBlock.reserve(Nblock);
for (int i = 0; i < Nblock / divisor; i++) {
std::vector<Field> group;
group.push_back(v0[i]);
if (useParityFlip) {
int n = (int)group.size();
for (int j = 0; j < n; j++) {
Field fp(Grid_);
parityFlippedField(group[j], fp);
group.push_back(std::move(fp));
}
}
if (useGamma5) {
int n = (int)group.size();
for (int j = 0; j < n; j++) {
Field g5v(Grid_);
gamma5Func(group[j], g5v);
group.push_back(std::move(g5v));
}
}
for (auto& f : group) startBlock.push_back(std::move(f));
}
return startBlock;
}
//--------------------------------------------------------------------
// Block Arnoldi iteration
//--------------------------------------------------------------------
void blockArnoldiIteration(std::vector<Field>& startBlock, int endBlock,
int startIdx, bool doubleOrthog)
{
int N = Nm;
if (startIdx == 0) {
basis.clear();
F.clear();
H = CMat::Zero(N, N);
B = CMat::Zero(N, Nblock);
std::vector<Field> V0 = startBlock;
blockOrthonormalise(V0);
for (auto& v : V0) basis.push_back(v);
} else {
// Append the new starting block to the retained basis.
//
// The exact truncated KS relation is A V_k = V_k H_k + F B_k^dag,
// so the coupling rows are H[Nkeep+t, j] = conj(B_k[j,t]).
// Since H_new = S + σI is upper triangular, the off-diagonal block
// H_new[Nkeep:, :Nkeep] = 0 and the restart from F is exact.
int Nkeep = startIdx * Nblock;
for (auto& v : startBlock) basis.push_back(v);
// Fill restart coupling rows into H (exact: H_new is upper triangular,
// so B encodes the only non-zero coupling to the new block).
for (int t = 0; t < Nblock; t++)
for (int j = 0; j < Nkeep; j++)
H(Nkeep + t, j) = std::conj(B(j, t));
// Zero out B for the retained columns now that the coupling is in H
for (int j = 0; j < Nkeep; j++)
for (int t = 0; t < Nblock; t++)
B(j, t) = 0.0;
}
for (int k = startIdx; k < endBlock; k++)
blockArnoldiStep(k, doubleOrthog);
}
//--------------------------------------------------------------------
// One block Arnoldi step
//--------------------------------------------------------------------
void blockArnoldiStep(int k, bool doubleOrthog)
{
int kBase = k * Nblock;
int prevN = kBase + Nblock;
int N = Nm;
std::vector<Field> W(Nblock, Field(Grid_));
for (int t = 0; t < Nblock; t++)
Linop.Op(basis[kBase + t], W[t]);
// Full reorthogonalisation against all current basis vectors
for (int pass = 0; pass < (doubleOrthog ? 2 : 1); pass++) {
for (int i = 0; i < prevN; i++) {
for (int t = 0; t < Nblock; t++) {
ComplexD coeff = innerProduct(basis[i], W[t]);
if (pass == 0)
H(i, kBase + t) = toStdCmplx(coeff);
else
H(i, kBase + t) += toStdCmplx(coeff);
W[t] -= coeff * basis[i];
}
}
}
F = W;
if (k == Nm/Nblock - 1) {
// Last block: record coupling in B as R^H (Hermitian conjugate of QR factor)
// KS relation requires B[kBase+t, s] = conj(R[s,t])
CMat R = blockQR(F);
for (int t = 0; t < Nblock; t++)
for (int s = 0; s < Nblock; s++)
B(kBase + t, s) = std::conj(R(s, t)); // B_block = R^H
beta_k = R.norm();
return;
}
// Not last: QR the residual, extend basis
CMat R = blockQR(F);
int nextBase = (k + 1) * Nblock;
for (int i = 0; i < Nblock; i++)
for (int j = 0; j < Nblock; j++)
H(nextBase + i, kBase + j) = R(i, j);
for (int t = 0; t < Nblock; t++)
basis.push_back(F[t]);
}
//--------------------------------------------------------------------
// Block QR (modified Gram-Schmidt within the block)
//--------------------------------------------------------------------
CMat blockQR(std::vector<Field>& W)
{
CMat R = CMat::Zero(Nblock, Nblock);
const RealD deflThresh = 1e-14;
for (int j = 0; j < Nblock; j++) {
for (int i = 0; i < j; i++) {
ComplexD coeff = innerProduct(W[i], W[j]);
R(i, j) = toStdCmplx(coeff);
W[j] -= coeff * W[i];
}
RealD nrm = std::sqrt(norm2(W[j]));
R(j, j) = nrm;
if (nrm > deflThresh) {
W[j] *= (1.0 / nrm);
} else {
W[j] = Zero();
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: deflation at block column " << j
<< " (norm = " << nrm << ")" << std::endl;
}
}
return R;
}
//--------------------------------------------------------------------
// Orthonormalise starting block
//--------------------------------------------------------------------
void blockOrthonormalise(std::vector<Field>& V)
{
for (int j = 0; j < (int)V.size(); j++) {
for (int i = 0; i < j; i++) {
ComplexD c = innerProduct(V[i], V[j]);
V[j] -= c * V[i];
}
RealD nrm = std::sqrt(norm2(V[j]));
assert(nrm > 1e-14);
V[j] *= (1.0 / nrm);
}
}
//--------------------------------------------------------------------
// Basis rotation: UR[i] = sum_j U[j] * R(j,i)
//--------------------------------------------------------------------
void constructUR(std::vector<Field>& UR, std::vector<Field>& U,
CMat& R, int N)
{
UR.clear();
Field tmp(Grid_);
for (int i = 0; i < N; i++) {
tmp = Zero();
for (int j = 0; j < N; j++)
tmp += U[j] * R(j, i);
UR.push_back(tmp);
}
}
//--------------------------------------------------------------------
// Eigensystem of the truncated H (not Hhat)
//--------------------------------------------------------------------
/**
* Eigenvalues of H_k are the standard Ritz values in the retained
* subspace. After convergence has been declared via harmonic estimates,
* the final reported eigenvalues and vectors come from H_k (not Hhat_k),
* since H_k contains the true projected operator.
*/
void computeEigensystem(CMat& Hk, int Nkeep)
{
Eigen::ComplexEigenSolver<CMat> es;
es.compute(Hk);
// Sort to match schurReorder ordering.
int n = es.eigenvalues().size();
ComplexComparator cComp(ritzFilter);
std::vector<int> idx(n);
std::iota(idx.begin(), idx.end(), 0);
std::sort(idx.begin(), idx.end(), [&](int a, int b){
return cComp(toStdCmplx(es.eigenvalues()(a)), toStdCmplx(es.eigenvalues()(b)));
});
evals.resize(n);
littleEvecs.resize(n, n);
for (int k = 0; k < n; k++) {
evals(k) = es.eigenvalues()(idx[k]);
littleEvecs.col(k) = es.eigenvectors().col(idx[k]);
}
evecs.clear();
for (int k = 0; k < Nkeep; k++) {
CVec vec = littleEvecs.col(k);
Field tmp(Grid_);
tmp = Zero();
for (int j = 0; j < (int)basis.size() && j < Nkeep; j++)
tmp += vec[j] * basis[j];
evecs.push_back(tmp);
}
}
//--------------------------------------------------------------------
// Convergence check
//--------------------------------------------------------------------
/**
* Ritz estimate for eigenpair k: || B^H y_k ||
* where y_k is the k-th eigenvector of the truncated H.
* The same bound applies whether using Ritz or harmonic Ritz restart.
*/
int converged(int Nkeep)
{
ritzEstimates.clear();
int Nconv = 0;
CMat Bk = B(Eigen::seqN(0, Nkeep), Eigen::all);
for (int k = 0; k < Nkeep; k++) {
CVec yk = littleEvecs.col(k);
CVec Bty = Bk.adjoint() * yk;
RealD res = Bty.norm();
ritzEstimates.push_back(res);
std::cout << GridLogMessage
<< "HarmonicBlockKrylovSchur: Ritz estimate[" << k
<< "] = " << res << " eval = " << evals[k] << std::endl;
if (res < rtol) Nconv++;
else break;
}
return Nconv;
}
//--------------------------------------------------------------------
// Approximate maximum eigenvalue (power iteration)
//--------------------------------------------------------------------
RealD approxMaxEval(const Field& v0, int MAX_ITER = 50)
{
assert(norm2(v0) > 1e-8);
RealD lam = 0.0, denom = std::sqrt(norm2(v0));
Field vcur(Grid_), vtmp(Grid_);
vcur = v0;
for (int i = 0; i < MAX_ITER; i++) {
Linop.Op(vcur, vtmp);
vcur = vtmp;
RealD num = std::sqrt(norm2(vcur));
lam = num / denom;
denom = num;
}
return lam;
}
};
NAMESPACE_END(Grid);
#endif // GRID_HARMONIC_BLOCKED_KRYLOV_SCHUR_H
@@ -53,6 +53,18 @@ enum IRLdiagonalisation {
IRLdiagonaliseWithEigen
};
enum IRLeigsort {
IRLeigsortMax,
IRLeigsortSqMin
};
#if 0
bool square_comp(RealD a, RealD b){
if (a*a<b*b) return true;
return false;
}
#endif
template<class Field> class ImplicitlyRestartedLanczosHermOpTester : public ImplicitlyRestartedLanczosTester<Field>
{
public:
@@ -119,9 +131,10 @@ class ImplicitlyRestartedLanczos {
/////////////////////////
// Constructor
/////////////////////////
public:
public:
IRLeigsort EigSort;
//////////////////////////////////////////////////////////////////
// PAB:
//////////////////////////////////////////////////////////////////
@@ -154,6 +167,7 @@ public:
Nstop(_Nstop) , Nk(_Nk), Nm(_Nm),
eresid(_eresid), betastp(_betastp),
MaxIter(_MaxIter) , MinRestart(_MinRestart),
EigSort(IRLeigsortMax),
orth_period(_orth_period), diagonalisation(_diagonalisation) { };
ImplicitlyRestartedLanczos(LinearFunction<Field> & PolyOp,
@@ -170,6 +184,7 @@ public:
Nstop(_Nstop) , Nk(_Nk), Nm(_Nm),
eresid(_eresid), betastp(_betastp),
MaxIter(_MaxIter) , MinRestart(_MinRestart),
EigSort(IRLeigsortMax),
orth_period(_orth_period), diagonalisation(_diagonalisation) { };
////////////////////////////////
@@ -316,8 +331,12 @@ until convergence
// sorting
//////////////////////////////////
eval2_copy = eval2;
// if (EigSort==IRLeigsortMax)
// std::partial_sort(eval2.begin(),eval2.begin()+Nm,eval2.end(),square_comp);
// else
std::partial_sort(eval2.begin(),eval2.begin()+Nm,eval2.end(),std::greater<RealD>());
std::cout<<GridLogIRL <<" evals sorted "<<std::endl;
// eval2_copy = eval2;
const int chunk=8;
for(int io=0; io<k2;io+=chunk){
std::cout<<GridLogIRL << "eval "<< std::setw(3) << io ;
@@ -333,6 +352,7 @@ until convergence
//////////////////////////////////
Qt = Eigen::MatrixXd::Identity(Nm,Nm);
for(int ip=k2; ip<Nm; ++ip){
// std::cout<<GridLogIRL <<"QR decompose "<<eval2[ip]<<std::endl;
QR_decomp(eval,lme,Nm,Nm,Qt,eval2[ip],k1,Nm);
}
std::cout<<GridLogIRL <<"QR decomposed "<<std::endl;
@@ -375,7 +395,8 @@ until convergence
// power of two search pattern; not every evalue in eval2 is assessed.
int allconv =1;
for(int jj = 1; jj<=Nstop; jj*=2){
// for(int jj = 1; jj<=Nstop; jj*=2){
for(int jj = 1; jj<=Nstop; jj++){
int j = Nstop-jj;
RealD e = eval2_copy[j]; // Discard the evalue
basisRotateJ(B,evec,Qt,j,0,Nk,Nm);
+994
View File
@@ -0,0 +1,994 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/KrylovSchur.h
Copyright (C) 2015
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: paboyle <paboyle@ph.ed.ac.uk>
Author: Patrick Oare <poare@bnl.gov>
Author: Chulwoo Jung <chulwoo@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.
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 */
#ifndef GRID_KRYLOVSCHUR_H
#define GRID_KRYLOVSCHUR_H
NAMESPACE_BEGIN(Grid);
#if defined(GRID_CUDA) || defined(GRID_HIP)
using thrust::abs;
using thrust::conj;
inline std::complex<double> toStdCmplx(const ComplexD& c) { return {c.real(), c.imag()}; }
#else
using std::abs;
using std::conj;
inline const ComplexD& toStdCmplx(const ComplexD& c) { return c; }
#endif
/**
* Small dense coefficient matrix of ComplexD (thrust::complex on GPU builds)
* for passing to basisRotate, whose device kernel cannot multiply Grid tensors
* by std::complex. Must be namespace-scope: nvcc rejects function-local types
* as template arguments to functions containing extended device lambdas.
*/
struct KSCoeffMat {
std::vector<ComplexD> a; int n;
KSCoeffMat(int _n) : a(_n*_n), n(_n) {}
ComplexD& operator()(int i, int j) { return a[i*n+j]; }
const ComplexD& operator()(int i, int j) const { return a[i*n+j]; }
};
/**
* Options for which Ritz values to keep in implicit restart. TODO move this and utilities into a new file
*/
enum RitzFilter {
EvalNormSmall, // Keep evals with smallest norm
EvalNormLarge, // Keep evals with largest norm
EvalReSmall, // Keep evals with smallest real part
EvalReLarge, // Keep evals with largest real part
EvalReNormLarge, // Keep evals with largest real part
EvalImSmall, // Keep evals with smallest imaginary part
EvalImLarge, // Keep evals with largest imaginary part
EvalImNormSmall, // Keep evals with smallest |imaginary| part
EvalImNormLarge, // Keep evals with largest |imaginary| part
};
/** Selects the RitzFilter corresponding to the input string. */
inline RitzFilter selectRitzFilter(std::string s) {
if (s == "EvalNormSmall") { return EvalNormSmall; } else
if (s == "EvalNormLarge") { return EvalNormLarge; } else
if (s == "EvalReSmall") { return EvalReSmall; } else
if (s == "EvalReLarge") { return EvalReLarge; } else
if (s == "EvalReNormLarge") { return EvalReNormLarge; } else
if (s == "EvalImSmall") { return EvalImSmall; } else
if (s == "EvalImLarge") { return EvalImLarge; } else
if (s == "EvalImNormSmall") { return EvalImNormSmall; } else
if (s == "EvalImNormLarge") { return EvalImNormLarge; } else
{ assert(0); }
}
/** Returns a string saying which RitzFilter it is. */
inline std::string rfToString(RitzFilter RF) {
switch (RF) {
case EvalNormSmall:
return "EvalNormSmall";
case EvalNormLarge:
return "EvalNormLarge";
case EvalReSmall:
return "EvalReSmall";
case EvalReLarge:
return "EvalReLarge";
case EvalReNormLarge:
return "EvalReNormLarge";
case EvalImSmall:
return "EvalImSmall";
case EvalImLarge:
return "EvalImLarge";
case EvalImNormSmall:
return "EvalImNormSmall";
case EvalImNormLarge:
return "EvalImNormLarge";
default:
assert(0);
}
}
// Select comparison function from RitzFilter
struct ComplexComparator
{
RitzFilter RF;
ComplexComparator (RitzFilter _rf) : RF(_rf) {}
bool operator()(std::complex<double> z1, std::complex<double> z2) {
RealD tmp1=std::abs(std::imag(z1));
RealD tmp2=std::abs(std::imag(z2));
if ( std::abs(std::real(z1)) >2.) tmp1 += 100.;
if ( std::abs(std::real(z2)) >2.) tmp2 += 100.;
switch (RF) {
case EvalNormSmall:
return std::abs(z1) < std::abs(z2);
case EvalNormLarge:
return std::abs(z1) > std::abs(z2);
case EvalReSmall:
return std::real(z1) < std::real(z2); // DELETE THE ABS HERE!!!
case EvalReLarge:
return std::real(z1) > std::real(z2);
case EvalReNormLarge:
return std::abs(std::real(z1)) < std::abs(std::real(z2));
case EvalImSmall:
return std::imag(z1) < std::imag(z2);
case EvalImLarge:
return std::imag(z1) > std::imag(z2);
case EvalImNormSmall:
// return std::abs(std::imag(z1)) < std::abs(std::imag(z2));
return tmp1 < tmp2 ;
case EvalImNormLarge:
return std::abs(std::imag(z1)) > std::abs(std::imag(z2));
default:
assert(0);
}
}
};
/**
* Computes a complex Schur decomposition of a complex matrix A using Eigen's matrix library. The Schur decomposition,
* A = Q^\dag S Q
* factorizes A into a unitary matrix Q and an upper triangular matrix S. The eigenvalues of A lie on the diagonal of the upper triangular matrix S.
* The Schur decomposition is not unique: in particular, any ordering of the eigenvalues of A can be used as the diagonal of the matrix S.
* This class supports eigenvalue reordering by swapping two adjacent eigenvalues with a unitary transformation.
*/
class ComplexSchurDecomposition {
private:
typedef Eigen::MatrixXcd CMat;
CMat A; // Matrix to decompose, A = Q^\dag S Q
CMat Q; // Unitary matrix Q
CMat S; // Upper triangular matrix S
// Placeholders for Givens rotation
CMat Givens; // Givens rotation
ComplexD s; // off-diagonal element
ComplexD lam1; // First eval for swap
ComplexD lam2; // Second eval for swap
ComplexD phi; // phase of s
RealD r; // norm of s and lam2 - lam1
int Nm; // size of matrix problem
ComplexComparator cCompare; // function to sort the Schur matrix.
public:
/**
* If the input matrix _A is in Hessenberg form (upper triangular + first subdiagonal non-zero), then the Schur
* decomposition is easier to compute.
*/
ComplexSchurDecomposition(CMat _A, bool isHess, RitzFilter ritzFilter = EvalReSmall) : A(_A), Nm (_A.rows()), cCompare (ritzFilter)
{
Eigen::ComplexSchur<CMat> schur (Nm);
if (isHess) {
schur.computeFromHessenberg(_A, CMat::Identity(Nm, Nm), true);
} else {
schur.compute(_A, true);
}
S = schur.matrixT();
Q = schur.matrixU().adjoint(); // Eigen computes A = Q S Q^\dag, we want A = Q^\dag S Q
}
// Getters
int getNm() { return Nm; } // size of matrix problem
CMat getMatrixA() { return A; } // matrix for decomposition
CMat getMatrixQ() { return Q; } // unitary matrix Q
CMat getMatrixS() { return S; } // Schur matrix (upper triangular) S
CMat getRitz() { return S.diagonal(); }
/**
* Checks the Schur decomposition A = Q^\dag S Q holds for the computed matrices. Returns if the relative
* Frobenius norm || A - Q^\dag S Q || / || A || is less than rtol.
*/
bool checkDecomposition(RealD rtol = 1e-8) {
RealD Anorm = A.norm();
if (Anorm < rtol) {
std::cout << GridLogMessage << "Zero matrix" << std::endl;
return true;
}
std::cout << GridLogDebug << "S = " << std::endl << S << std::endl;
std::cout << GridLogDebug << "Q = " << std::endl << Q << std::endl;
CMat A2 = Q.adjoint() * S * Q;
std::cout << GridLogDebug << "Q^dag S Q = " << std::endl << A2 << std::endl;
RealD dA = (A - A2).norm() / Anorm;
return (dA < rtol);
}
/**
* Swaps the components on the diagonal of the Schur matrix at index i with index i + 1.
* Updates the orthogonal matrix Q accordingly.
*/
void swapEvals(int i) {
assert(0 <= i && i <= Nm - 1); // can only swap blocks with upper left index between 0 and Nm - 1
// get parameters for rotation
s = S(i, i+1);
lam1 = S(i, i);
lam2 = S(i+1, i+1);
phi = s / abs(s);
r = std::sqrt(std::pow(abs(s), 2) + std::pow(abs(lam2 - lam1), 2));
// // Original code which performs Givens rotations by manual matrix multiplication
// // compute Givens rotation corresponding to these parameters
// Givens = CMat::Identity(Nm, Nm);
// Givens(i, i) = std::abs(s) / r;
// Givens(i+1, i+1) = Givens(i, i);
// Givens(i, i+1) = (phi / r) * std::conj(lam2 - lam1);
// Givens(i+1, i) = -std::conj(Givens(i, i+1));
// // rotate Schur matrix and unitary change of basis matrix Q
// S = Givens * S * Givens.adjoint();
// Q = Givens * Q;
// Modified code
Givens = CMat::Identity(2, 2);
Givens(0, 0) = abs(s) / r;
Givens(1, 1) = Givens(0, 0);
Givens(0, 1) = (phi / r) * conj(lam2 - lam1);
Givens(1, 0) = -conj(Givens(0, 1));
// TODO: make sure these are correct
Eigen::MatrixXcd tmp;
tmp = Givens * S.block(i, 0, 2, Nm);
S.block(i, 0, 2, Nm) = tmp;
// S_{:, i:i+2} = S.block<Nm, 2>(0, i);
tmp = S.block(0, i, Nm, 2) * Givens.adjoint();
S.block(0, i, Nm, 2) = tmp;
// Q_{i:i+2, :} = Q.block<2, Nm>(i, 0);
tmp = Givens * Q.block(i, 0, 2, Nm);
Q.block(i, 0, 2, Nm) = tmp;
return;
}
/**
* Reorders a Schur matrix &Schur to have the Ritz values that we would like to keep for
* restart as the first Nk elements on the diagonal.
*
* This algorithm is implemented as Nk iterations of a a reverse bubble sort with comparator compare.
* TODO: pass in compare function as an argument, default to compare with <.
*/
// void schurReorder(int Nk, std::function compare) {
void schurReorder(int Nk) {
std::cout << GridLogMessage << "schurReorder start " << std::endl;
int nswap=0;
for (int i = 0; i < Nk; i++) {
for (int k = 0; k <= Nm - 2; k++) {
int idx = Nm - 2 - k;
// TODO use RitzFilter enum here
// if (std::abs(S(idx, idx)) < std::abs(S(idx+1, idx+1))) { // sort by largest modulus of eigenvalue
// if (std::real(S(idx, idx)) > std::real(S(idx+1, idx+1))) { // sort by smallest real eigenvalue
if ( cCompare(S(idx+1, idx+1), S(idx, idx)) ) { // sort by largest modulus of eigenvalue
swapEvals(idx);
nswap++;
}
}
}
std::cout << GridLogMessage << "schurReorder end "<< nswap << std::endl;
for (int i = 0; i < Nk; i++) {
std::cout << GridLogMessage << "schurR "<<i<<" "<<S(i,i) << std::endl;
}
return;
}
void schurReorderBlock() {
// TODO method stub
return;
}
};
// template<class Field>
// inline void writeFile(const Field &field, const std::string &fname) {
// emptyUserRecord record;
// ScidacWriter WR(field.Grid()->IsBoss());
// WR.open(fname);
// WR.writeScidacFieldRecord(field, record, 0); // 0 = Lexico
// WR.close();
// }
/**
* Implementation of the Krylov-Schur algorithm.
*/
template<class Field>
class KrylovSchur {
private:
std::string cname = std::string("KrylovSchur");
int MaxIter; // Max iterations
RealD Tolerance;
RealD ssq;
RealD rtol;
int Nm; // Number of basis vectors to track (equals MaxIter if no restart)
int Nk; // Number of basis vectors to keep every restart (equals -1 if no restart)
int Nstop; // Stop after converging Nstop eigenvectors.
LinearOperatorBase<Field> &Linop;
GridBase *Grid;
RealD approxLambdaMax;
RealD beta_k;
Field u; // Residual vector perpendicular to Krylov space (u_{k+1} in notes)
Eigen::VectorXcd b; // b vector in Schur decomposition (e_{k+1} in Arnoldi).
std::vector<Field> basis; // orthonormal Krylov basis
Eigen::MatrixXcd Rayleigh; // Rayleigh quotient of size Nbasis (after construction)
Eigen::MatrixXcd Qt; // Transpose of basis rotation which projects out high modes.
Eigen::VectorXcd evals; // evals of Rayleigh quotient
std::vector<RealD> ritzEstimates; // corresponding ritz estimates for evals
Eigen::MatrixXcd littleEvecs; // Nm x Nm evecs matrix
RitzFilter ritzFilter; // how to sort evals
public:
bool doEvalCheck = false;
KrylovSchur(LinearOperatorBase<Field> &_Linop, GridBase *_Grid, RealD _Tolerance, RitzFilter filter = EvalReSmall)
: Linop(_Linop), Grid(_Grid), Tolerance(_Tolerance), ritzFilter(filter), u(_Grid), MaxIter(-1), Nm(-1), Nk(-1), Nstop (-1),
evals (0), ritzEstimates (), evecs (), ssq (0.0), rtol (0.0), beta_k (0.0), approxLambdaMax (0.0)
{
u = Zero();
};
std::vector<Field> evecs; // Vector of evec fields
/* Getters */
int getNk() { return Nk; }
Eigen::MatrixXcd getRayleighQuotient() { return Rayleigh; }
Field getU() { return u; }
std::vector<Field> getBasis() { return basis; }
Eigen::VectorXcd getEvals() { return evals; }
std::vector<RealD> getRitzEstimates() { return ritzEstimates; }
std::vector<Field> getEvecs() { return evecs; }
/**
* Runs the non-harmonic Krylov-Schur loop.
* - Runs an Arnoldi step to generate the Rayleigh quotient and Krylov basis.
* - Schur decompose the Rayleigh quotient.
* - Permutes the Rayleigh quotient according to the eigenvalues.
* - Truncate the Krylov-Schur expansion.
*/
void operator()(const Field& v0, int _maxIter, int _Nm, int _Nk, int _Nstop, bool doubleOrthog = true) {
MaxIter = _maxIter;
Nm = _Nm; Nk = _Nk;
Nstop = _Nstop;
ssq = norm2(v0);
RealD approxLambdaMax = approxMaxEval(v0);
rtol = Tolerance * approxLambdaMax;
std::cout << GridLogMessage << "Approximate max eigenvalue: " << approxLambdaMax << std::endl;
b = Eigen::VectorXcd::Zero(Nm); // start as e_{k+1}
b(Nm-1) = 1.0;
int start = 0;
Field startVec = v0;
littleEvecs = Eigen::MatrixXcd::Zero(Nm, Nm);
for (int i = 0; i < MaxIter; i++) {
std::cout << GridLogMessage << "Restart Iteration " << i << std::endl;
// Perform Arnoldi steps to compute Krylov basis and Rayleigh quotient (Hess)
arnoldiIteration(startVec, Nm, start, doubleOrthog);
startVec = u;
start = Nk;
std::cout << GridLogDebug << "b after Arnoldi " << b << std::endl;
// --- Restart: Schur-decompose Rayleigh, reorder by ritzFilter, rotate
// the basis into the Schur vectors, and truncate to the leading Nk.
ComplexSchurDecomposition schur (Rayleigh, false, ritzFilter);
std::cout << GridLogDebug << "Schur decomp holds? " << schur.checkDecomposition() << std::endl;
// Rearrange Schur matrix so wanted evals are on top left (like MATLAB's ordschur)
std::cout << GridLogMessage << "Reordering Schur eigenvalues" << std::endl;
schur.schurReorder(Nk);
std::cout << GridLogMessage << "Schur eigenvalues reordered." << std::endl;
Eigen::MatrixXcd Q = schur.getMatrixQ();
Qt = Q.adjoint(); // TODO should Q be real?
std::cout << GridLogMessage << "*** ROTATING TO SCHUR BASIS *** " << std::endl;
// Rotate Krylov basis, b vector, redefine Rayleigh quotient and evecs, and truncate.
Rayleigh = schur.getMatrixS();
b = Q * b; // b^\dag = b^\dag * Q^\dag <==> b = Q*b
std::vector<Field> basis2;
constructUR(basis2, basis, Qt, Nm,Nk);
std::cout << GridLogMessage << "*** TRUNCATING FOR RESTART *** " << std::endl;
std::cout << GridLogDebug << "Rayleigh before truncation: " << std::endl << Rayleigh << std::endl;
Eigen::MatrixXcd RayTmp = Rayleigh(Eigen::seqN(0, Nk), Eigen::seqN(0, Nk));
Rayleigh = RayTmp;
basis = std::vector<Field> (basis2.begin(), basis2.begin() + Nk);
b = b.head(Nk).eval();
std::cout << GridLogDebug << "Rayleigh after truncation: " << std::endl << Rayleigh << std::endl;
checkKSDecomposition();
// Compute eigensystem of Rayleigh. Note the eigenvectors correspond to the sorted eigenvalues.
computeEigensystem(Rayleigh);
std::cout << GridLogMessage << "Eigenvalues (first Nk sorted): " << std::endl << evals << std::endl;
if (checkConvergedAndReport(i)) return;
}
}
/**
* Runs the harmonic (shifted) Krylov-Schur loop: extracts Ritz values of a
* shift-augmented Rayleigh quotient so that eigenvalues near `*_shift` are
* reordered to the top instead of the extremal ones.
*/
void operator()(const Field& v0, int _maxIter, int _Nm, int _Nk, int _Nstop, RealD *_shift, bool doubleOrthog = true) {
assert(_shift && "harmonic KrylovSchur: shift must be non-null");
RealD shiftVal = *_shift;
MaxIter = _maxIter;
Nm = _Nm; Nk = _Nk;
Nstop = _Nstop;
ssq = norm2(v0);
RealD approxLambdaMax = approxMaxEval(v0);
rtol = Tolerance * approxLambdaMax;
std::cout << GridLogMessage << "Approximate max eigenvalue: " << approxLambdaMax << std::endl;
b = Eigen::VectorXcd::Zero(Nm); // start as e_{k+1}
b(Nm-1) = 1.0;
int start = 0;
Field startVec = v0;
littleEvecs = Eigen::MatrixXcd::Zero(Nm, Nm);
for (int i = 0; i < MaxIter; i++) {
std::cout << GridLogMessage << "Restart Iteration " << i << std::endl;
// Perform Arnoldi steps to compute Krylov basis and Rayleigh quotient (Hess)
arnoldiIteration(startVec, Nm, start, doubleOrthog);
startVec = u;
start = Nk;
std::cout << GridLogDebug << "b after Arnoldi " << b << std::endl;
// --- Harmonic restart: Schur-decompose the shift-augmented Rayleigh
// quotient so Ritz values close to shiftVal are reordered to the
// top, then rotate and truncate as in the non-harmonic case.
Eigen::MatrixXcd temp = Rayleigh;
for (int m=0;m<Nm;m++) temp(m,m) -= shiftVal;
Eigen::MatrixXcd RayleighS = temp.inverse(); // (B-tI)^-1
Eigen::MatrixXcd temp2 = RayleighS.adjoint(); //(B-tI)^-1*
Eigen::VectorXcd g = temp2*b; //g = (B-tI)^-1* * b
Eigen::MatrixXcd Btilde = Rayleigh + g*(b.adjoint());
Field utilde(Grid);
utilde = u;
for (int j = 0; j<Nm; j++){
utilde -= basis[j]*g(j);
}
ComplexSchurDecomposition schurS (Btilde, false, ritzFilter);
std::cout << GridLogMessage << "Shifted Schur eigenvalues shift = "<<shiftVal << std::endl;
schurS.schurReorder(Nk);
Eigen::MatrixXcd Q_s = schurS.getMatrixQ();
Eigen::MatrixXcd Qt_s = Q_s.adjoint(); // TODO should Q be real?
Btilde=schurS.getMatrixS();
Eigen::VectorXcd b_s = Q_s*b; // Q is Qt in SlepC, b_s=bhat
std::vector<Field> basis2_s;
constructUR(basis2_s, basis, Qt_s, Nm,Nk);
Eigen::MatrixXcd RayTmp_s = Btilde(Eigen::seqN(0, Nk), Eigen::seqN(0, Nk));
Btilde = RayTmp_s;
basis2_s = std::vector<Field> (basis2_s.begin(), basis2_s.begin() + Nk);
b_s = b_s.head(Nk).eval();
Eigen::VectorXcd ghat = -Q_s * g;
ghat = ghat.head(Nk).eval();
Field uhat(Grid);
uhat = utilde;
for (int j = 0; j<Nk; j++){
uhat -= basis2_s[j]*ghat(j);
}
RealD gamma = std::sqrt(norm2(uhat));
uhat *= 1.0/gamma;
std::cout << GridLogMessage << " gamma "<<gamma << std::endl;
Btilde += ghat*(b_s.adjoint());
b_s *=gamma;
Rayleigh = Btilde;
basis= basis2_s;
b = b_s;
beta_k = gamma;
u= uhat;
checkKSDecomposition();
computeEigensystem(Rayleigh);
std::cout << GridLogMessage << "Eigenvalues (first Nk sorted): " << std::endl << evals << std::endl;
if (checkConvergedAndReport(i)) return;
}
}
private:
/**
* Shared post-restart convergence check. On convergence (or the final
* iteration) reports eigenvalues and optionally the explicit residual
* check. Returns true if operator() should return.
*/
bool checkConvergedAndReport(int i) {
int Nconv = converged();
std::cout << GridLogMessage << "Number of evecs converged: " << Nconv << std::endl;
if (Nconv >= Nstop || i == MaxIter - 1) {
std::cout << GridLogMessage << "Converged with " << Nconv << " / " << Nstop << " eigenvectors on iteration "
<< i << "." << std::endl;
std::cout << GridLogMessage << "Eigenvalues: " << std::endl << evals << std::endl;
if (doEvalCheck) {
Field w(Grid);
for (int k = 0; k < (int)evecs.size(); k++) {
Linop.Op(evecs[k], w);
ComplexD eval_est = toStdCmplx(innerProduct(evecs[k], w));
w -= eval_est * evecs[k];
RealD res = std::sqrt(norm2(w));
std::cout << GridLogMessage << "KrylovSchur: evec[" << k << "]"
<< " eval_reported = " << evals[k]
<< " eval_est = " << eval_est
<< " || A v - eval_est * v || = " << res << std::endl;
}
}
return true;
}
return false;
}
public:
/**
* Constructs the Arnoldi basis for the Krylov space K_n(D, src). (TODO make private)
*
* Parameters
* ----------
* v0 : Field&
* Source to generate Krylov basis.
* Nm : int
* Final size of the basis desired. If the basis becomes complete before a basis of size Nm is constructed
* (determined by relative tolerance Tolerance), stops iteration there.
* doubleOrthog : bool (default = false)
* Whether to double orthogonalize the basis (for numerical cancellations) or not.
* start : int (default = 0)
* If non-zero, assumes part of the Arnoldi basis has already been constructed.
*/
void arnoldiIteration(const Field& v0, int Nm, int start = 0, bool doubleOrthog = true)
{
ComplexD coeff;
Field w (Grid); // A acting on last Krylov vector.
// basis.reserve(Nm);
// for (int i = start; i < Nm; i++) {
// basis.emplace_back(Grid);
// }
// basis.assign(Nm, Field(Grid));
// basis.resize(Nm);
// for (int i = start; i < Nm; i++) {
// basis[i] = Field(Grid);
// }
if (start == 0) { // initialize everything that we need.
RealD v0Norm = 1 / std::sqrt(ssq);
basis.push_back(v0Norm * v0); // normalized source
// basis[0] = v0Norm * v0; // normalized source
Rayleigh = Eigen::MatrixXcd::Zero(Nm, Nm);
u = Zero();
} else {
// assert( start == basis.size() ); // should be starting at the end of basis (start = Nk)
std::cout << GridLogMessage << "Resetting Rayleigh and b" << std::endl;
Eigen::MatrixXcd RayleighCp = Rayleigh;
Rayleigh = Eigen::MatrixXcd::Zero(Nm, Nm);
Rayleigh(Eigen::seqN(0, Nk), Eigen::seqN(0, Nk)) = RayleighCp;
// append b^\dag to Rayleigh, add u to basis
Rayleigh(Nk, Eigen::seqN(0, Nk)) = b.adjoint();
basis.push_back(u);
// basis[start] = u; // TODO make sure this is correct
b = Eigen::VectorXcd::Zero(Nm);
}
// Construct next Arnoldi vector by normalizing w_i = Dv_i - \sum_j v_j h_{ji}
for (int i = start; i < Nm; i++) {
Linop.Op(basis.back(), w);
// Linop.Op(basis[i], w);
for (int j = 0; j < basis.size(); j++) {
coeff = innerProduct(basis[j], w); // coeff = h_{ij}. Note that since {vi} is ONB it's OK to subtract it off after.
Rayleigh(j, i) = toStdCmplx(coeff);
w -= coeff * basis[j];
}
if (doubleOrthog) {
std::cout << GridLogDebug << "Double orthogonalizing." << std::endl;
for (int j = 0; j < basis.size(); j++) {
coeff = innerProduct(basis[j], w); // see if there is any residual component in basis[j] direction
Rayleigh(j, i) += toStdCmplx(coeff); // if coeff is non-zero, adjust Rayleigh
w -= coeff * basis[j];
}
}
// add w_i to the pile
if (i < Nm - 1) {
coeff = std::sqrt(norm2(w));
Rayleigh(i+1, i) = toStdCmplx(coeff);
basis.push_back(
(1.0/coeff) * w
);
// basis[i+1] = (1.0/coeff) * w;
}
// after iterations, update u and beta_k = ||u|| before norm
u = w; // make sure u is normalized
beta_k = std::sqrt(norm2(u)); // beta_k = ||f_k|| determines convergence.
u = (1/beta_k) * u;
}
b(Nm - 1) = beta_k;
// std::cout << GridLogMessage << "|f|^2 after Arnoldi step = " << norm2(f) << std::endl;
std::cout << GridLogMessage << "beta_k = |u| (before norm) after Arnoldi step = " << beta_k << std::endl;
std::cout << GridLogDebug << "Computed Rayleigh quotient = " << std::endl << Rayleigh << std::endl;
return;
}
/**
* Approximates the eigensystem of the linear operator by computing the eigensystem of
* the Rayleigh quotient. Assumes that the Rayleigh quotient has already been constructed (by
* calling the operator() function).
*
* Parameters
* ----------
* Eigen::MatrixXcd& S
* Schur matrix (upper triangular) similar to original Rayleigh quotient.
*/
void computeEigensystem(Eigen::MatrixXcd& S)
{
std::cout << GridLogMessage << "Computing eigenvalues." << std::endl;
evecs.clear();
Eigen::ComplexEigenSolver<Eigen::MatrixXcd> es;
es.compute(S);
// Sort eigenvalues/evecs to match the schurReorder ordering.
int n = es.eigenvalues().size();
ComplexComparator cComp(ritzFilter);
std::vector<int> idx(n);
std::iota(idx.begin(), idx.end(), 0);
std::sort(idx.begin(), idx.end(), [&](int a, int b){
return cComp(toStdCmplx(es.eigenvalues()(a)), toStdCmplx(es.eigenvalues()(b)));
});
evals.resize(n);
littleEvecs.resize(n, n);
for (int k = 0; k < n; k++) {
evals(k) = es.eigenvalues()(idx[k]);
littleEvecs.col(k) = es.eigenvectors().col(idx[k]);
}
// Convert evecs to lattice fields
for (int k = 0; k < n; k++) {
Eigen::VectorXcd vec = littleEvecs.col(k);
Field tmp (basis[0].Grid());
tmp = Zero();
for (int j = 0; j < (int)basis.size(); j++) {
tmp = tmp + vec[j] * basis[j];
}
evecs.push_back(tmp);
}
}
/**
* Approximates the maximum eigenvalue of Linop.Op to normalize the residual and test for convergence.
*
* TODO implement in parent class eventually
*
* Parameters
* ----------
* Field& v0
* Source field to start with. Must have non-zero norm.
* int MAX_ITER (default = 50)
* Maximum number of iterations for power approximation.
*
* Returns
* -------
* RealD lamApprox
* Approximation of largest eigenvalue.
*/
RealD approxMaxEval(const Field& v0, int MAX_ITER = 50) {
assert (norm2(v0) > 1e-8); // must have relatively large source norm to start
RealD lamApprox = 0.0;
RealD denom = 1.0; RealD num = 1.0;
Field v0cp (Grid); Field tmp (Grid);
v0cp = v0;
denom = std::sqrt(norm2(v0cp));
for (int i = 0; i < MAX_ITER; i++) {
Linop.Op(v0cp, tmp); // CAREFUL: do not do Op(tmp, tmp)
v0cp = tmp;
num = std::sqrt(norm2(v0cp)); // num = |A^{n+1} v0|
lamApprox = num / denom; // lam = |A^{n+1} v0| / |A^n v0|
std::cout << GridLogDebug << "Approx for max eval: " << lamApprox << std::endl;
denom = num; // denom = |A^{n} v0|
}
return lamApprox;
}
/**
* Computes the number of Krylov-Schur eigenvectors that have converged. An eigenvector s is considered converged
* for a tolerance epsilon if
* r(s) := |\beta e_m^T s| < epsilon
* where beta is the norm of f_{m+1}.
*
* TODO implement in parent class eventually
*
* Parameters
* ----------
*
* Returns
* -------
* int : Number of converged eigenvectors.
*/
int converged() {
int Nconv = 0;
int _Nm = evecs.size();
std::cout << GridLogDebug << "b: " << b << std::endl;
Field tmp (Grid); Field fullEvec (Grid);
ritzEstimates.clear();
// ritzEstimates.resize(_Nm);
for (int k = 0; k < _Nm; k++) {
Eigen::VectorXcd evec_k = littleEvecs.col(k);
RealD ritzEstimate = std::abs(b.dot(evec_k)); // b^\dagger s
ritzEstimates.push_back(ritzEstimate);
std::cout << GridLogMessage << "Ritz estimate for evec " << k << " = " << ritzEstimate << std::endl;
if (ritzEstimate < rtol) {
Nconv++;
} else {
break;
}
}
// Check that Ritz estimate is explicitly || D (Uy) - lambda (Uy) ||
// checkRitzEstimate();
return Nconv;
}
/**
* Checks the Krylov-Schur decomposition DU = UR + f b^\dag with the last-computed
* U, R, f, and b.
*/
bool checkKSDecomposition(RealD tol = 1e-8) {
std::cout << GridLogMessage << "*** CHECKING KRYLOV-SCHUR DECOMPOSITION *** " << std::endl;
int k = basis.size(); // number of basis vectors, also the size of Rayleigh.
// rotate basis by Rayleigh to construct UR
// std::vector<Field> rotated;
// std::cout << GridLogDebug << "Rayleigh in KSDecomposition: " << std::endl << Rayleigh << std::endl;
std::vector<Field> rotated = basis;
constructUR(rotated, basis, Rayleigh, k,k); // manually rotate
// Eigen::MatrixXcd Rt = Rayleigh.adjoint();
// basisRotate(rotated, Rt, 0, k, 0, k, k); // UR
// TODO: make a new function that I'm positive does what this is doing
// just take the basis U = (u1 u2 ... uNm) and form the linear combination UR from R
// For each i, form D u(i) and subtract off (US - u b^\dag)(i)
RealD delta = 0.0; RealD deltaSum = 0;
Field tmp (Grid); tmp = Zero();
for (int i = 0; i < k; i++) {
Linop.Op(basis[i], tmp); // tmp = D u(i)
delta = norm2(tmp - rotated[i] - u * std::conj(b(i)));
delta = delta / norm2(tmp); // relative tolerance
deltaSum += delta;
// std::cout << GridLogDebug << "Iteration " << i << std::endl;
// std::cout << GridLogDebug << "Du = " << norm2(tmp) << std::endl;
// std::cout << GridLogDebug << "rotated = " << norm2(rotated[i]) << std::endl;
// std::cout << GridLogDebug << "b[i] = " << b(i) << std::endl;
std::cout << GridLogMessage << "Deviation in decomp, column " << i << ": " << delta << std::endl;
}
std::cout << GridLogMessage << "Squared sum of relative deviations in decomposition: " << deltaSum << std::endl;
// std::cout << "[DEBUG] testing basis rotate" << std::endl;
// std::vector<Field> rotated2;
// constructUR(rotated2, basis, Rayleigh, k);
// for (int i = 0; i < k; i++) {
// std::cout << "rotated[i] - UR[i] = " << norm2(rotated[i] - rotated2[i]) << std::endl;
// }
return deltaSum < tol;
}
/**
* Checks the Ritz vector s was rotated correctly by explicitly recomputing the
* eigenvectors of the rotated Rayleigh quotient.
*/
bool checkRitzRotation(RealD tol = 1e-8) {
std::cout << GridLogMessage << "*** CHECKING RITZ VECTOR ROTATION *** " << std::endl;
Eigen::ComplexEigenSolver<Eigen::MatrixXcd> es;
es.compute(Rayleigh);
Eigen::MatrixXcd littleEvecs2 = es.eigenvectors();
RealD dLittle = (littleEvecs2 - littleEvecs).norm() / littleEvecs.norm();
std::cout << GridLogMessage << "|littleEvecs2 - littleEvecs| / |littleEvecs| = " << dLittle << std::endl;
std::cout << GridLogMessage << "Forming full eigenvectors" << std::endl;
RealD delta = 0.0; RealD deltaSum = 0;
for (int k = 0; k < evals.size(); k++) {
Eigen::VectorXcd vec = littleEvecs.col(k);
Field tmpEvec (Grid);
tmpEvec = Zero();
for (int j = 0; j < basis.size(); j++) {
tmpEvec = tmpEvec + vec[j] * basis[j];
}
delta = norm2(tmpEvec - evecs[k]) / norm2(evecs[k]);
std::cout << GridLogMessage << "Deviation in evec " << k << ": " << delta << std::endl;
deltaSum += delta;
}
return deltaSum < tol;
}
/**
* Checks the Ritz estimate R(s) is indeed the deviation of a Ritz eigenvector from being a true eigenvector.
*/
void checkRitzEstimate(RealD tol = 1e-8) {
std::cout << GridLogMessage << "*** CHECKING RITZ ESTIMATE *** " << std::endl;
// The issue was that the Eigen::eigensolver occasionally returned the complex conjugate pairs in the wrong
// order compared to the diagonal, which is how I was reading them out. When this happened, the Ritz estimate would
// be wrong. So, just need to be more careful and actually read out the eigenvalues.
Field tmp (Grid);
// std::cout << "n evecs: " << evecs.size() << std::endl;
for (int k = 0; k < evecs.size(); k++) {
tmp = Zero();
Linop.Op(evecs[k], tmp); // D evec
RealD ritz = std::sqrt(norm2(tmp - evals[k] * evecs[k]));
std::cout << "Ritz estimate " << k << " = " << ritz << std::endl;
// Checking little Ritz estimate
// Eigen::VectorXcd littleEvec = littleEvecs.col(k);
// Eigen::VectorXcd dev = Rayleigh * littleEvec - evals[k] * littleEvec;
// std::cout << GridLogMessage << "Little Ritz estimate = " << dev.norm() << std::endl;
}
return;
}
/**
* Given a vector of fields U (equivalently, a LxN matrix, where L is the number of degrees of
* freedom on the lattice field) and an NxN matrix R, forms the product UR.
* Only the first N2 output columns are computed; columns [N2, N) are Zero.
*
* Implemented with the accelerator basisRotate kernel, which computes
* basis[j] <- sum_k Qt(j,k) basis[k] in place, so we copy U into UR and
* pass R^T. The coefficients are staged as ComplexD (thrust::complex on
* GPU builds) since std::complex has no device arithmetic with Grid tensors.
*/
void constructUR(std::vector<Field>& UR, std::vector<Field> &U, Eigen::MatrixXcd& R, int N, int N2) {
KSCoeffMat Rt(N);
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
Rt(i, j) = ComplexD(R(j, i).real(), R(j, i).imag());
UR = U;
basisRotate(UR, Rt, 0, N2, 0, N, N);
for (int i = N2; i < N; i++) UR[i] = Zero();
return;
}
/**
* Same as constructUR but for the product order RU.
*/
void constructRU(std::vector<Field>& RU, std::vector<Field> &U, Eigen::MatrixXcd& R, int N) {
Field tmp (Grid);
RU.clear();
// RU.resize(N);
for (int i = 0; i < N; i++) {
tmp = Zero();
for (int j = 0; j < N; j++) {
tmp = tmp + R(i, j) * U[j];
}
RU.push_back(tmp);
// RU[i] = tmp;
}
return;
}
// void writeEvec(Field& in, std::string const fname){
// #ifdef HAVE_LIME
// // Ref: https://github.com/paboyle/Grid/blob/feature/scidac-wp1/tests/debug/Test_general_coarse_hdcg_phys48.cc#L111
// std::cout << GridLogMessage << "Writing evec to: " << fname << std::endl;
// Grid::emptyUserRecord record;
// Grid::ScidacWriter WR(in.Grid()->IsBoss());
// WR.open(fname);
// WR.writeScidacFieldRecord(in,record,0); // Lexico
// WR.close();
// #endif
// // What is the appropriate way to throw error?
// }
// /**
// * Writes the eigensystem of a Krylov Schur object to a directory.
// *
// * Parameters
// * ----------
// * std::string path
// * Directory to write to.
// */
// void writeEigensystem(std::string outDir) {
// std::cout << GridLogMessage << "Writing output to directory: " << outDir << std::endl;
// // TODO write a scidac density file so that we can easily integrate with visualization toolkit
// std::string evalPath = outDir + "/evals.txt";
// std::ofstream fEval;
// fEval.open(evalPath);
// for (int i = 0; i < Nk; i++) {
// // write Eigenvalues
// fEval << i << " " << evals(i);
// if (i < Nk - 1) { fEval << "\n"; }
// }
// fEval.close();
// for (int i = 0; i < Nk; i++) {
// std::string fName = outDir + "/evec" + std::to_string(i);
// // writeFile(evecs[i], fName); // using method from Grid/HMC/ComputeWilsonFlow.cc
// // writeEvec(evecs[i], fName);
// }
// }
};
NAMESPACE_END(Grid);
#endif
@@ -0,0 +1,276 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./Grid/algorithms/iterative/LanczosBidiagonalization.h
Copyright (C) 2015
Author: Chulwoo Jung <chulwoo@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.
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 */
#ifndef GRID_LANCZOS_BIDIAGONALIZATION_H
#define GRID_LANCZOS_BIDIAGONALIZATION_H
NAMESPACE_BEGIN(Grid);
/**
* Lanczos Bidiagonalization (Golub-Kahan)
*
* For a linear operator A with adjoint A^dag, constructs the bidiagonal
* decomposition:
*
* A V_m = U_m B_m
* A^dag U_m = V_m B_m^T + beta_{m+1} v_{m+1} e_m^T
*
* where:
* V_m = [v_1, ..., v_m] right Lanczos vectors (orthonormal)
* U_m = [u_1, ..., u_m] left Lanczos vectors (orthonormal)
* B_m is upper bidiagonal with diag(alpha_1,...,alpha_m) and
* superdiag(beta_2,...,beta_m)
*
* The singular values of A are approximated by those of B_m.
* The singular values of B_m are the square roots of the eigenvalues of
* the symmetric tridiagonal matrix B_m^T B_m.
*
* Usage:
* LanczosBidiagonalization<Field> lb(Linop, grid);
* lb.run(src, Nm, tol);
* // Access results via getters.
*/
template <class Field>
class LanczosBidiagonalization {
public:
LinearOperatorBase<Field> &Linop;
GridBase *Grid;
int Nm; // number of Lanczos steps taken
RealD Tolerance; // convergence threshold on beta_{k+1} / alpha_k
std::vector<Field> V; // right Lanczos vectors v_1 ... v_m
std::vector<Field> U; // left Lanczos vectors u_1 ... u_m
std::vector<RealD> alpha; // diagonal of bidiagonal matrix
std::vector<RealD> beta; // super-diagonal (beta[k] couples u_k and v_{k+1})
// SVD of the bidiagonal matrix (filled after computeSVD())
Eigen::VectorXd singularValues;
Eigen::MatrixXd leftSVecs; // columns are left singular vectors of B
Eigen::MatrixXd rightSVecs; // columns are right singular vectors of B
public:
LanczosBidiagonalization(LinearOperatorBase<Field> &_Linop, GridBase *_Grid,
RealD _tol = 1.0e-8)
: Linop(_Linop), Grid(_Grid), Tolerance(_tol), Nm(0)
{}
/**
* Run the Golub-Kahan Lanczos bidiagonalization.
*
* Parameters
* ----------
* src : starting vector (need not be normalised)
* Nmax : maximum number of Lanczos steps
* reorth : if true, full reorthogonalisation of both V and U bases
*/
void run(const Field &src, int Nmax, bool reorth = true)
{
assert(norm2(src) > 0.0);
V.clear(); U.clear();
alpha.clear(); beta.clear();
Nm = 0;
Field p(Grid), r(Grid);
// --- initialise: v_1 = src / ||src|| ---
Field v(Grid);
v = src;
RealD nrm = std::sqrt(norm2(v));
v = (1.0 / nrm) * v;
V.push_back(v);
for (int k = 0; k < Nmax; ++k) {
// p = A v_k
Linop.Op(V[k], p);
// p = p - beta_k * u_{k-1} (remove previous left vector)
if (k > 0) {
p = p - beta[k-1] * U[k-1];
}
// alpha_k = ||p||
RealD ak = std::sqrt(norm2(p));
if (ak < 1.0e-14) {
std::cout << GridLogMessage
<< "LanczosBidiagonalization: lucky breakdown at step "
<< k << " (alpha = " << ak << ")" << std::endl;
break;
}
alpha.push_back(ak);
// u_k = p / alpha_k
Field u(Grid);
u = (1.0 / ak) * p;
// full reorthogonalisation of u against previous U
if (reorth) {
for (int j = 0; j < (int)U.size(); ++j) {
ComplexD ip = innerProduct(U[j], u);
u = u - ip * U[j];
}
RealD unrm = std::sqrt(norm2(u));
if (unrm > 1.0e-14) u = (1.0 / unrm) * u;
}
U.push_back(u);
// r = A^dag u_k - alpha_k * v_k
Linop.AdjOp(U[k], r);
r = r - ak * V[k];
// full reorthogonalisation of r against previous V
if (reorth) {
for (int j = 0; j < (int)V.size(); ++j) {
ComplexD ip = innerProduct(V[j], r);
r = r - ip * V[j];
}
}
// beta_{k+1} = ||r||
RealD bk = std::sqrt(norm2(r));
beta.push_back(bk);
Nm = k + 1;
std::cout << GridLogMessage
<< "LanczosBidiagonalization step " << k
<< " alpha = " << ak
<< " beta = " << bk << std::endl;
// convergence: residual beta / alpha small enough
if (bk / ak < Tolerance) {
std::cout << GridLogMessage
<< "LanczosBidiagonalization converged at step " << k
<< " (beta/alpha = " << bk / ak << ")" << std::endl;
break;
}
if (k == Nmax - 1) break; // no v_{k+2} needed after last step
// v_{k+1} = r / beta_{k+1}
Field vnext(Grid);
vnext = (1.0 / bk) * r;
V.push_back(vnext);
}
}
/**
* Compute the SVD of the bidiagonal matrix B using Eigen.
* Singular values are stored in descending order.
*/
void computeSVD()
{
int m = Nm;
Eigen::MatrixXd B = Eigen::MatrixXd::Zero(m, m);
for (int k = 0; k < m; ++k) {
B(k, k) = alpha[k];
if (k + 1 < m && k < (int)beta.size())
B(k, k+1) = beta[k];
}
Eigen::JacobiSVD<Eigen::MatrixXd> svd(B,
Eigen::ComputeThinU | Eigen::ComputeThinV);
singularValues = svd.singularValues(); // already sorted descending
leftSVecs = svd.matrixU();
rightSVecs = svd.matrixV();
std::cout << GridLogMessage
<< "LanczosBidiagonalization: singular values of B_" << m
<< std::endl;
for (int k = 0; k < m; ++k)
std::cout << GridLogMessage << " sigma[" << k << "] = "
<< singularValues(k) << std::endl;
}
/**
* Return the k-th approximate left singular vector of A in the full
* lattice space. computeSVD() must have been called first.
*/
Field leftSingularVector(int k)
{
assert(k < (int)leftSVecs.cols());
Field svec(Grid);
svec = Zero();
for (int j = 0; j < Nm; ++j)
svec = svec + leftSVecs(j, k) * U[j];
return svec;
}
/**
* Return the k-th approximate right singular vector of A in the full
* lattice space. computeSVD() must have been called first.
*/
Field rightSingularVector(int k)
{
assert(k < (int)rightSVecs.cols());
Field svec(Grid);
svec = Zero();
for (int j = 0; j < Nm; ++j)
svec = svec + rightSVecs(j, k) * V[j];
return svec;
}
/**
* Verify the bidiagonalization: returns max residual
* max_k || A v_k - alpha_k u_k - beta_k u_{k-1} ||
*/
RealD verify()
{
Field tmp(Grid);
RealD maxres = 0.0;
for (int k = 0; k < Nm; ++k) {
Linop.Op(V[k], tmp);
tmp = tmp - alpha[k] * U[k];
if (k > 0 && k-1 < (int)beta.size())
tmp = tmp - beta[k-1] * U[k-1];
RealD res = std::sqrt(norm2(tmp));
if (res > maxres) maxres = res;
std::cout << GridLogMessage
<< "LanczosBidiagonalization verify step " << k
<< " ||A v_k - alpha_k u_k - beta_{k-1} u_{k-1}|| = "
<< res << std::endl;
}
return maxres;
}
/* Getters */
int getNm() const { return Nm; }
const std::vector<Field>& getV() const { return V; }
const std::vector<Field>& getU() const { return U; }
const std::vector<RealD>& getAlpha() const { return alpha; }
const std::vector<RealD>& getBeta() const { return beta; }
Eigen::VectorXd getSingularValues() const { return singularValues; }
};
NAMESPACE_END(Grid);
#endif
@@ -60,18 +60,29 @@ public:
void Level(int lv) { level=lv; };
PrecGeneralisedConjugateResidualNonHermitian(RealD tol,Integer maxit,LinearOperatorBase<Field> &_Linop,LinearFunction<Field> &Prec,int _mmax,int _nstep) :
PrecGeneralisedConjugateResidualNonHermitian(RealD tol,Integer maxit,LinearOperatorBase<Field> &_Linop,LinearFunction<Field> &Prec,int _mmax, int _nstep) :
Tolerance(tol),
MaxIterations(maxit),
Linop(_Linop),
Preconditioner(Prec),
mmax(_mmax),
nstep(_nstep)
nstep(_nstep) // what is nstep vs mmax? one is the number of inner iterations
{
level=1;
verbose=1;
};
// virtual method stubs for updating GCR polynomial
virtual void LogBegin(void){
std::cout << "GCR::LogBegin() "<<std::endl;
};
virtual void LogIteration(int k, ComplexD a, std::vector<ComplexD> betas){
std::cout << "GCR::LogIteration() "<<std::endl;
};
virtual void LogComplete(std::vector<ComplexD>& alphas, std::vector<std::vector<ComplexD>>& betas) {
std::cout << "GCR::LogComplete() "<<std::endl;
};
void operator() (const Field &src, Field &psi){
// psi=Zero();
@@ -96,19 +107,18 @@ public:
GCRLogLevel <<"PGCR("<<mmax<<","<<nstep<<") "<< steps <<" steps cp = "<<cp<<" target "<<rsq <<std::endl;
if(cp<rsq) {
SolverTimer.Stop();
SolverTimer.Stop();
Linop.Op(psi,r);
axpy(r,-1.0,src,r);
RealD tr = norm2(r);
GCRLogLevel<<"PGCR: Converged on iteration " <<steps
<< " computed residual "<<sqrt(cp/ssq)
<< " true residual " <<sqrt(tr/ssq)
<< " target " <<Tolerance <<std::endl;
Linop.Op(psi,r);
axpy(r,-1.0,src,r);
RealD tr = norm2(r);
GCRLogLevel<<"PGCR: Converged on iteration " <<steps
<< " computed residual "<<sqrt(cp/ssq)
<< " true residual " <<sqrt(tr/ssq)
<< " target " <<Tolerance <<std::endl;
GCRLogLevel<<"PGCR Time elapsed: Total "<< SolverTimer.Elapsed() <<std::endl;
return;
GCRLogLevel<<"PGCR Time elapsed: Total "<< SolverTimer.Elapsed() <<std::endl;
return;
}
}
@@ -135,9 +145,9 @@ public:
////////////////////////////////
// history for flexible orthog
////////////////////////////////
std::vector<Field> q(mmax,grid);
std::vector<Field> p(mmax,grid);
std::vector<RealD> qq(mmax);
std::vector<Field> q(mmax,grid); // q = Ap
std::vector<Field> p(mmax,grid); // store mmax conjugate momenta
std::vector<RealD> qq(mmax); // qq = (Ap)^2 = <p|A^\dagger A |p> (denom of \alpha)
GCRLogLevel<< "PGCR nStep("<<nstep<<")"<<std::endl;
@@ -155,7 +165,9 @@ public:
LinalgTimer.Start();
r=src-Az;
LinalgTimer.Stop();
GCRLogLevel<< "PGCR true residual r = src - A psi "<<norm2(r) <<std::endl;
GCRLogLevel<< "PGCR true residual r = src - A psi "<< norm2(r) <<std::endl;
this->LogBegin(); // initialize polynomial GCR if needed (TODO think about placement of this)
/////////////////////
// p = Prec(r)
@@ -178,32 +190,45 @@ public:
p[0]= z;
q[0]= Az;
qq[0]= zAAz;
std::cout << "||init p - src||: " << norm2(p[0] - src) << std::endl; // for debugging
cp =norm2(r);
LinalgTimer.Stop();
std::vector<ComplexD> all_alphas;
std::vector<std::vector<ComplexD>> all_betas;
for(int k=0;k<nstep;k++){
steps++;
int kp = k+1;
int peri_k = k %mmax;
int peri_k = k %mmax; // only store mmax vectors; just roll around if needed
int peri_kp= kp%mmax;
// std::cout << "peri_kp = " << peri_kp << std::endl;
LinalgTimer.Start();
rq= innerProduct(q[peri_k],r); // what if rAr not real?
a = rq/qq[peri_k];
a = rq/qq[peri_k]; // compute alpha_j
axpy(psi,a,p[peri_k],psi);
all_alphas.push_back(a);
cp = axpy_norm(r,-a,q[peri_k],r);
axpy(psi,a,p[peri_k],psi); // update psi --> psi + \alpha p
cp = axpy_norm(r,-a,q[peri_k],r); // update r --> r - \alpha D p. Note q = Dp
LinalgTimer.Stop();
GCRLogLevel<< "PGCR step["<<steps<<"] resid " << cp << " target " <<rsq<<std::endl;
// LogIterationA(k + 1, a);
if((k==nstep-1)||(cp<rsq)){
return cp;
}
GCRLogLevel<< "GCR step["<<steps<<"] resid " << cp << " target " <<rsq<<std::endl;
// moving this to end of loop so that it doesn't exit beforehand
// TODO if I want to uncomment this, I have to split the LogIteration again and put LogIterationA() beforehand
// if((k==nstep-1)||(cp<rsq)){
// return cp;
// }
PrecTimer.Start();
@@ -221,22 +246,205 @@ public:
q[peri_kp]=Az;
p[peri_kp]=z;
// Field Dsrc (grid);
// Linop.Op(src, Dsrc);
// std::cout << "||q[peri_kp] - D(src)||: " << norm2(q[peri_kp] - Dsrc) << std::endl; // for debugging
// // delete after testing
// std::cout << "Testing Dsq on one for GCR: " << std::endl;
// Field myField (grid);
// myField = 1.0;
// Field out1 (grid); Field out2 (grid);
// Linop.HermOp(myField, out1);
// Linop.Op(myField, out2);
// std::cout << "Dsq.Hermop(ones) has norm " << norm2(out1) << std::endl;
// std::cout << "Dsq.Op(ones) has norm " << norm2(out2) << std::endl;
// basically northog = k+1 if mmax is large
int northog = ((kp)>(mmax-1))?(mmax-1):(kp); // if more than mmax done, we orthog all mmax history.
// std::cout << "northog: " << northog << std::endl;
std::vector<ComplexD> betas (northog);
// std::cout << "peri_kp: " << peri_kp << std::endl;
// we iterate backwards counting down from the current k+1 index (peri_kp) because we
for(int back=0;back<northog;back++){
int peri_back=(k-back)%mmax; GRID_ASSERT((k-back)>=0);
b=-real(innerProduct(q[peri_back],Az))/qq[peri_back];
p[peri_kp]=p[peri_kp]+b*p[peri_back];
q[peri_kp]=q[peri_kp]+b*q[peri_back];
// b=-real(innerProduct(q[peri_back],Az))/qq[peri_back];
b=-(innerProduct(q[peri_back],Az))/qq[peri_back]; // TODO try complex beta
p[peri_kp]=p[peri_kp]+b*p[peri_back];
q[peri_kp]=q[peri_kp]+b*q[peri_back];
// LogIterationB(peri_back, b);
// betas[back] = b; // may need to change the indexing if I ever do it with restarts
// std::cout << "[DEBUG] pushing beta for back = " << back << ", peri_back = " << peri_back << std::endl;
betas[peri_back] = b; // may need to change the indexing if I ever do it with restarts
}
qq[peri_kp]=norm2(q[peri_kp]); // could use axpy_norm
LinalgTimer.Stop();
// log iteration and update GCR polynomial if necessary.
all_betas.push_back(betas);
LogIteration(k + 1, a, betas);
// finish if necessary
if((k==nstep-1)||(cp<rsq)){
std::cout << "All alphas: " << std::endl << all_alphas << std::endl;
std::cout << "All betas: " << std::endl << all_betas << std::endl;
LogComplete(all_alphas, all_betas);
std::cout << "Exiting GCR." << std::endl;
return cp;
}
}
GRID_ASSERT(0); // never reached
return cp;
}
};
class PolynomialFile: Serializable {
public:
GRID_SERIALIZABLE_CLASS_MEMBERS(PolynomialFile,
std::vector<std::vector<std::complex<double>>>, data,
std::vector<std::vector<std::complex<double>>>, betas,
std::vector<std::complex<double>>, alphas
);
};
// Optionally record the GCR polynomial. [PO]: TODO
template <class Field>
class PGCRPolynomial : public PrecGeneralisedConjugateResidualNonHermitian<Field> {
public:
std::vector<ComplexD> ak;
std::vector<std::vector<ComplexD>> bk;
// std::vector<ComplexD> poly_p;
std::vector<std::vector<ComplexD>> poly_p;
std::vector<ComplexD> poly_Ap; // polynomial in Ap_j (only store it for last p)
std::vector<ComplexD> poly_r;
std::vector<ComplexD> polynomial;
PolynomialFile& PF;
public:
PGCRPolynomial(RealD tol, Integer maxit,LinearOperatorBase<Field> &_Linop, LinearFunction<Field> &Prec, int _mmax, int _nstep, PolynomialFile& _PF)
: PrecGeneralisedConjugateResidualNonHermitian<Field>(tol, maxit, _Linop, Prec, _mmax, _nstep), PF(_PF)
{};
// think this applies the polynomial in A = Linop to a field src. The coeffs are
// stored in the vector `polynomial`.
void PolyOp(const Field &src, Field &psi)
{
Field tmp(src.Grid());
Field AtoN(src.Grid());
AtoN = src;
psi=AtoN*polynomial[0];
for(int n=1;n<polynomial.size();n++){
tmp = AtoN;
this->Linop.Op(tmp,AtoN); // iterate A^n
psi = psi + polynomial[n]*AtoN; // psi += poly_n A^n src
}
}
// [PO TODO] debug this
void PGCRsequence(const Field &src, Field &x)
{
Field Ap(src.Grid());
Field r(src.Grid());
// Field p(src.Grid());
// p=src;
std::vector<Field> p;
p.push_back(src);
r=src;
x=Zero();
x.Checkerboard()=src.Checkerboard();
for(int k=0;k<ak.size();k++){
x = x + ak[k]*p[k];
this->Linop.Op(p[k], Ap);
r = r - ak[k] * Ap;
// p[k] = r;
p.push_back(r);
for (int i = 0; i < k; i++) { // [PO TODO] check indices
p[k+1] += bk[i, k+1] * p[i];
}
// p = r + bk[k] * p;
}
}
void Solve(const Field &src, Field &psi)
{
psi=Zero();
this->operator()(src, psi);
}
virtual void LogBegin(void)
{
std::cout << "PGCR::LogBegin() "<<std::endl;
ak.resize(0);
bk.resize(0);
polynomial.resize(0);
poly_Ap.push_back(0.0); // start with (0.0); during first iteration should change to (0.0, 1.0)
std::vector<ComplexD> p0_tmp;
p0_tmp.push_back(1.0);
poly_p.push_back(p0_tmp);
poly_r.push_back(1.0);
};
// Updates vector psi and r and initializes vector p[k+1]
virtual void LogIteration(int k, ComplexD a, std::vector<ComplexD> betas){
std::cout << "PGCR::LogIteration(k = " << k << ")" << std::endl;
ak.push_back(a);
bk.push_back(betas);
// update Ap by pushing p[k] to the right
poly_Ap.push_back(0.0); // need to pad the end with an element
poly_Ap[0] = 0.0; // technically this should be unnecessary, as the first component is never set
for(int i = 0; i < k; i++){
poly_Ap[i+1]=poly_p[k-1][i]; // A\vec{p} = (0, \vec{p}) bc A shifts components of p to the right
}
// update psi_{k+1} --> psi_k + a_k p_k
polynomial.push_back(0.0);
for(int i = 0; i < k; i++) {
polynomial[i] += a * poly_p[k-1][i];
}
{
std::vector<std::complex<double>> poly_stdcmplx(polynomial.begin(), polynomial.end());
PF.data.push_back(poly_stdcmplx);
}
// r_{k+1} --> r_k - a_k A p_k
// p_{k+1} --> r_k + \sum_{i=0}^k \beta_{ik} p_i, input betas = (\beta_{ik})_i
poly_r.push_back(0.0); // should be of size k+1 if we start with k = 1
std::vector<ComplexD> p_next (k + 1, ComplexD(0.0)); // p_{k+1} = same size as r_{k+1}
for(int i = 0; i < k + 1; i++){
poly_r[i] = poly_r[i] - a * poly_Ap[i]; // update r_{k+1} --> r_k - \alpha_k A p_k
p_next[i] = poly_r[i]; // init new vector as r_{k+1}
}
// p_{k+1} --> p_{k+1} + \sum_i \beta_{ij} p_i
int nbeta = betas.size();
std::cout << "Betas: " << betas << std::endl;
for (int j = 0; j < nbeta; j++) {
for (int i = 0; i < j+1; i++) {
p_next[i] += betas[j] * poly_p[j][i];
}
}
poly_p.push_back(p_next); // add p_{k+1} to the list of p's
}
virtual void LogComplete(std::vector<ComplexD>& alphas, std::vector<std::vector<ComplexD>>& betas) {
/** Logs all alphas and betas to complete the iterations. */
std::cout << "PGCR::LogComplete() "<<std::endl;
for (int i = 0; i < alphas.size(); i++) {
PF.alphas.push_back(std::complex<double>(alphas[i].real(), alphas[i].imag()));
std::vector<std::complex<double>> beta_stdcmplx(betas[i].begin(), betas[i].end());
PF.betas.push_back(beta_stdcmplx);
}
};
};
NAMESPACE_END(Grid);
#endif
@@ -0,0 +1,753 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./Grid/algorithms/iterative/RestartedLanczosBidiagonalization.h
Copyright (C) 2015
Author: Chulwoo Jung <chulwoo@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.
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 */
#ifndef GRID_RESTARTED_LANCZOS_BIDIAGONALIZATION_H
#define GRID_RESTARTED_LANCZOS_BIDIAGONALIZATION_H
NAMESPACE_BEGIN(Grid);
/**
* Implicitly Restarted Lanczos Bidiagonalization (IRLBA)
*
* Computes the p largest (or p smallest) singular triplets of a linear
* operator A using the Golub-Kahan-Lanczos bidiagonalization with implicit
* restart via thick-restart / QR shifts.
*
* Algorithm (Baglama & Reichel, SIAM J. Sci. Comput. 27(1):19-42, 2005):
*
* Outer loop:
* 1. Extend the p-step (or seed) bidiagonalization to k steps:
* A V_k = U_k B_k
* A^dag U_k = V_k B_k^T + beta_{k+1} v_{k+1} e_k^T
* 2. Compute SVD: B_k = X Sigma Y^T
* 3. Check convergence of the p desired singular values via
* |beta_{k+1} * y_{k,i}| < tol * sigma_i
* where y_{k,i} is the last component of the i-th right singular vector.
* 4. Apply k-p implicit QR shifts to implicitly compress the basis
* to p steps (Sorensen-Lehoucq thick restart):
* B_p^+ = X_p^T B_k Y_p (upper bidiagonal, p x p)
* and update the lattice vectors:
* V_p^+ = V_k Y_p
* U_p^+ = U_k X_p
* The new residual coupling is
* beta_p^+ v_{p+1}^+ = beta_{k+1} v_{k+1} * (e_k^T Y_p)_p
* + B_k(p,p+1) * (orthogonal tail from QR)
* 5. Go to step 1.
*
* Template parameter
* ------------------
* Field : lattice field type (must support Grid algebra operations)
*
* Usage
* -----
* RestartedLanczosBidiagonalization<Field> irlba(Linop, grid, p, k, tol, maxIter);
* irlba.run(src);
* // Results available via getters.
*/
template <class Field>
class RestartedLanczosBidiagonalization {
public:
LinearOperatorBase<Field> &Linop;
GridBase *Grid;
int Nk; // number of desired singular triplets
int Nm; // Lanczos basis size (Nm > Nk)
RealD Tolerance;
int MaxIter;
bool largest; // if true, target largest singular values; otherwise smallest
// Converged singular triplets (filled after run())
std::vector<RealD> singularValues; // sigma_0 >= sigma_1 >= ...
std::vector<Field> leftVectors; // approximate left singular vectors
std::vector<Field> rightVectors; // approximate right singular vectors
private:
// Working bases (size up to Nm+1)
std::vector<Field> V; // right Lanczos vectors
std::vector<Field> U; // left Lanczos vectors
std::vector<RealD> alpha;
std::vector<RealD> beta;
// After a thick restart, the column at index restart_col of U^dag A V
// has extra non-zero entries (rows 0..restart_col-2) beyond what the
// upper bidiagonal captures. fvec[j] = <U[j] | A V[restart_col]> for
// j = 0..restart_col-1. (fvec[restart_col-1] == beta[restart_col-1].)
// reset_col == -1 means no restart has occurred yet (pure bidiagonal).
std::vector<RealD> fvec;
int restart_col;
public:
RestartedLanczosBidiagonalization(LinearOperatorBase<Field> &_Linop,
GridBase *_Grid,
int _Nk, int _Nm,
RealD _tol = 1.0e-8,
int _maxIt = 300,
bool _largest = true)
: Linop(_Linop), Grid(_Grid),
Nk(_Nk), Nm(_Nm),
Tolerance(_tol), MaxIter(_maxIt),
largest(_largest)
{
assert(Nm > Nk);
}
/**
* Run IRLBA starting from src.
* On exit, singularValues, leftVectors, rightVectors are filled with
* the Nk converged singular triplets.
*/
void run(const Field &src)
{
assert(norm2(src) > 0.0);
singularValues.clear();
leftVectors.clear();
rightVectors.clear();
// Allocate working bases
V.clear(); U.clear();
alpha.clear(); beta.clear();
fvec.clear(); restart_col = -1;
V.reserve(Nm + 1);
U.reserve(Nm);
// Seed: v_0 = src / ||src||
Field vtmp(Grid);
vtmp = src;
RealD nrm = std::sqrt(norm2(vtmp));
vtmp = (1.0 / nrm) * vtmp;
V.push_back(vtmp);
int pStart = 0; // current basis size at start of extension
RealD betaRestart = 0.0; // coupling from previous restart
for (int iter = 0; iter < MaxIter; ++iter) {
// ----------------------------------------------------------------
// Step 1: extend from pStart steps to Nm steps
// ----------------------------------------------------------------
extendBasis(pStart, Nm, betaRestart);
// verify();
// ----------------------------------------------------------------
// Step 2: SVD of the Nm x Nm B matrix.
// iter=0 (pStart==0): B is exactly bidiagonal — use buildBidiagonal.
// iter>0 (pStart==Nk): after a thick restart, column restart_col of
// U^dag A V has extra off-diagonal entries captured by fvec; use
// buildFullB so the Ritz values and restart vectors are computed from
// the exact projected matrix A V = U B_full.
// ----------------------------------------------------------------
Eigen::MatrixXd B = (pStart == 0) ? buildBidiagonal(Nm) : buildFullB(Nm);
Eigen::JacobiSVD<Eigen::MatrixXd> svd(B,
Eigen::ComputeThinU | Eigen::ComputeThinV);
Eigen::VectorXd sigma = svd.singularValues(); // descending
Eigen::MatrixXd X = svd.matrixU(); // Nm x Nm left SVecs of B
Eigen::MatrixXd Y = svd.matrixV(); // Nm x Nm right SVecs of B
// If targeting smallest, reorder so desired ones come first
Eigen::VectorXi order = sortOrder(sigma);
// ----------------------------------------------------------------
// Step 3: check convergence of the Nk desired singular values
// ----------------------------------------------------------------
RealD betaK = beta.back(); // beta_{k+1}
// In our convention A V = U B (exact), the residual is in the A^dag
// direction: A^dag u_j - sigma_j v_j = betaK * X[Nm-1,j] * V[Nm].
// Convergence criterion: |betaK * X[Nm-1, idx]| < tol * sigma_idx.
int nconv = 0;
for (int i = 0; i < Nk; ++i) {
int idx = order(i);
RealD res = std::abs(betaK * X(Nm - 1, idx));
RealD thr = Tolerance * std::max(sigma(idx), 1.0e-14);
std::cout << GridLogMessage
<< "IRLBA iter " << iter
<< " sigma[" << i << "] = " << sigma(idx)
<< " res = " << res
<< " thr = " << thr << std::endl;
if (res < thr) ++nconv;
else break; // residuals not strictly ordered but break is conservative
}
if (nconv >= Nk) {
std::cout << GridLogMessage
<< "IRLBA converged: " << nconv << " singular values after "
<< iter + 1 << " iterations." << std::endl;
// Collect converged triplets
extractTriplets(Nm, sigma, X, Y, order, Nk);
return;
}
// ----------------------------------------------------------------
// Step 4: implicit restart — compress to Nk steps
// ----------------------------------------------------------------
implicitRestart(Nm, Nk, sigma, X, Y, order, betaK, betaRestart);
// verify();
// Lucky breakdown: exact invariant subspace found; convergence is exact.
// B_p^+ = diag(alpha[0..Nk-1]); extract directly from restart basis.
if (betaRestart < 1.0e-14) {
std::cout << GridLogMessage
<< "IRLBA: lucky breakdown after restart (betaRestart = 0)."
<< " Extracting " << Nk << " exact Ritz triplets." << std::endl;
// Re-run SVD on the p-step diagonal B^+ to get sorted Ritz triplets.
Eigen::MatrixXd Bp = buildBidiagonal(Nk);
Eigen::JacobiSVD<Eigen::MatrixXd> svdp(Bp,
Eigen::ComputeThinU | Eigen::ComputeThinV);
Eigen::VectorXi ordp = sortOrder(svdp.singularValues());
extractTriplets(Nk, svdp.singularValues(), svdp.matrixU(),
svdp.matrixV(), ordp, Nk);
return;
}
pStart = Nk;
}
std::cout << GridLogMessage
<< "IRLBA: did not converge in " << MaxIter
<< " iterations. Returning best approximations." << std::endl;
// Return best available approximations
Eigen::MatrixXd B = buildFullB((int)alpha.size());
Eigen::JacobiSVD<Eigen::MatrixXd> svd(B,
Eigen::ComputeThinU | Eigen::ComputeThinV);
Eigen::VectorXd sigma = svd.singularValues();
Eigen::MatrixXd X = svd.matrixU();
Eigen::MatrixXd Y = svd.matrixV();
Eigen::VectorXi order = sortOrder(sigma);
int nout = std::min(Nk, (int)alpha.size());
extractTriplets((int)alpha.size(), sigma, X, Y, order, nout);
}
/* Getters */
int getNk() const { return (int)singularValues.size(); }
const std::vector<RealD>& getSingularValues() const { return singularValues; }
const std::vector<Field>& getLeftVectors() const { return leftVectors; }
const std::vector<Field>& getRightVectors() const { return rightVectors; }
/**
* Print B_k and U^dag A V to verify the bidiagonalization relation
* A V_m = U_m B_m (exact in our GK convention)
* On the first call (pStart=0), max|B - U^dag A V| should be ~machine epsilon.
* After a restart and extension, the column p of U^dag A V deviates from B
* by O(betaK): this is expected because the thick restart breaks the Krylov
* structure at column p, introducing off-diagonal terms proportional to betaK.
* These terms vanish as betaK -> 0 (convergence), so the algorithm is correct.
*/
void verify()
{
int m = (int)alpha.size();
int nU = (int)U.size();
int nV = (int)V.size();
if (m == 0) { std::cout << GridLogMessage << "IRLBA verify: empty basis" << std::endl; return; }
// Build reference matrix Bref (nU x nV):
// Columns 0..m-1 : buildFullB(m) (bidiagonal + fvec column at restart_col)
// Column m : residual column, two cases:
// (a) restart_col == m (right after implicitRestart, before extendBasis):
// V[m] = sgn*V_old[Nm], so <U[i]|A|V[m]> = fvec[i] for all i
// (b) otherwise (pure GK or after extendBasis):
// only entry (m-1, m) = beta[m-1] (GK recurrence residual)
Eigen::MatrixXd Bref = Eigen::MatrixXd::Zero(nU, nV);
{
Eigen::MatrixXd Bfull = buildFullB(m);
int cols = std::min(m, nV);
Bref.block(0, 0, m, cols) = Bfull.block(0, 0, m, cols);
}
if (nV > m && m > 0) {
if (restart_col == m && (int)fvec.size() == m) {
// Case (a): right after implicitRestart
for (int i = 0; i < m; ++i) Bref(i, m) = fvec[i];
} else if ((int)beta.size() >= m) {
// Case (b): standard GK residual column
Bref(m - 1, m) = beta[m - 1];
}
}
// Compute M[i,j] = <U[i] | A | V[j]>
Eigen::MatrixXd M = Eigen::MatrixXd::Zero(nU, nV);
Field Avj(Grid);
for (int j = 0; j < nV; ++j) {
Linop.Op(V[j], Avj);
for (int i = 0; i < nU; ++i) {
ComplexD ip = innerProduct(U[i], Avj);
M(i, j) = ip.real();
}
}
// Print Bref
std::cout << GridLogMessage
<< "IRLBA verify: Bref (" << nU << "x" << nV << "):" << std::endl;
for (int i = 0; i < nU; ++i) {
std::cout << GridLogMessage << " row " << i << ": ";
for (int j = 0; j < nV; ++j) std::cout << Bref(i,j) << " ";
std::cout << std::endl;
}
// Print U^dag A V
std::cout << GridLogMessage
<< "IRLBA verify: U^dag A V (" << nU << "x" << nV << "):" << std::endl;
for (int i = 0; i < nU; ++i) {
std::cout << GridLogMessage << " row " << i << ": ";
for (int j = 0; j < nV; ++j) std::cout << M(i,j) << " ";
std::cout << std::endl;
}
// Max deviation over the full nU x nV matrix
RealD maxdev = (Bref - M).cwiseAbs().maxCoeff();
std::cout << GridLogMessage
<< "IRLBA verify: max|Bref - U^dag A V| = " << maxdev << std::endl;
// Beta
std::cout << GridLogMessage << "IRLBA verify: beta[0.." << (int)beta.size()-1 << "] = ";
for (auto b : beta) std::cout << b << " ";
std::cout << std::endl;
}
private:
// ------------------------------------------------------------------
// Build the m x m upper-bidiagonal matrix from alpha[0..m-1], beta[0..m-2]
// ------------------------------------------------------------------
Eigen::MatrixXd buildBidiagonal(int m) const
{
Eigen::MatrixXd B = Eigen::MatrixXd::Zero(m, m);
for (int k = 0; k < m; ++k) {
B(k, k) = alpha[k];
if (k + 1 < m && k < (int)beta.size())
B(k, k + 1) = beta[k];
}
return B;
}
// ------------------------------------------------------------------
// Build the full m x m B matrix, including the non-bidiagonal column
// at restart_col that arises after a thick restart.
//
// After restart, A V[restart_col] has projections onto all U[0..restart_col-1]
// (not just U[restart_col-1]). These are stored in fvec[0..restart_col-1]
// and make column restart_col of U^dag A V non-bidiagonal.
// ------------------------------------------------------------------
Eigen::MatrixXd buildFullB(int m) const
{
Eigen::MatrixXd B = buildBidiagonal(m);
if (restart_col >= 0 && restart_col < m && (int)fvec.size() > 0) {
for (int j = 0; j < restart_col && j < (int)fvec.size(); ++j){
B(j, restart_col) = fvec[j];
std::cout << GridLogDebug << "buildFullB: B " <<j<<" "<<restart_col<<B(j, restart_col)<<std::endl;
}
}
return B;
}
// ------------------------------------------------------------------
// Return a permutation vector that puts the desired Nk singular values
// first (largest first if largest==true, smallest first otherwise).
// Eigen's JacobiSVD already returns sigma in descending order, so for
// largest we just return 0,1,...,m-1; for smallest we reverse.
// ------------------------------------------------------------------
Eigen::VectorXi sortOrder(const Eigen::VectorXd &sigma) const
{
int m = (int)sigma.size();
Eigen::VectorXi ord(m);
if (largest) {
for (int i = 0; i < m; ++i) ord(i) = i;
} else {
for (int i = 0; i < m; ++i) ord(i) = m - 1 - i;
}
return ord;
}
// ------------------------------------------------------------------
// Extend the Lanczos bidiagonalization from pStart to kEnd steps.
// On first call pStart==0 (V[0] already set).
// On restart calls V[0..pStart], U[0..pStart-1], alpha[0..pStart-1],
// beta[0..pStart-1] are already set; betaRestart is the coupling
// beta_{pStart} that drives the first new U step.
// ------------------------------------------------------------------
void extendBasis(int pStart, int kEnd, RealD betaRestart)
{
// Truncate containers to pStart (Lattice has no default constructor)
if ((int)V.size() > pStart + 1) V.erase(V.begin() + pStart + 1, V.end());
if ((int)U.size() > pStart) U.erase(U.begin() + pStart, U.end());
alpha.resize(pStart);
beta.resize(pStart);
Field p(Grid), r(Grid);
for (int k = pStart; k < kEnd; ++k) {
// p = A v_k
Linop.Op(V[k], p);
// Remove previous left vector coupling
if (k > 0) {
p = p - beta[k - 1] * U[k - 1];
}
// On the first step after a restart, beta[pStart-1] was already set;
// but V[pStart] was already constructed including the beta correction,
// so no extra subtraction needed here beyond the standard recurrence.
// Reorthogonalize p against U, then alpha_k = ||p||, u_k = p/alpha_k
reorthogonalize(p, U);
RealD ak = std::sqrt(norm2(p));
if (ak < 1.0e-14) {
std::cout << GridLogMessage
<< "IRLBA extendBasis: lucky breakdown at step " << k
<< " (alpha = " << ak << ")" << std::endl;
alpha.push_back(ak);
Field zero(Grid); zero = Zero();
U.push_back(zero);
beta.push_back(0.0);
V.push_back(zero);
break;
}
alpha.push_back(ak);
Field u(Grid);
u = (1.0 / ak) * p;
U.push_back(u);
// r = A^dag u_k - alpha_k v_k, reorthogonalize, then beta_{k+1} = ||r||
Linop.AdjOp(U[k], r);
r = r - ak * V[k];
reorthogonalize(r, V);
RealD bk = std::sqrt(norm2(r));
beta.push_back(bk);
std::cout << GridLogMessage
<< "IRLBA extend step " << k
<< " alpha = " << ak
<< " beta = " << bk << std::endl;
// Always push v_{k+1} (needed as residual direction for restart)
if (bk < 1.0e-14) {
std::cout << GridLogMessage
<< "IRLBA extendBasis: lucky breakdown (beta = 0) at step "
<< k << std::endl;
Field zero(Grid); zero = Zero();
V.push_back(zero);
break;
}
Field vnext(Grid);
vnext = (1.0 / bk) * r;
V.push_back(vnext);
if (k == kEnd - 1) break; // v_{k+1} pushed above; stop here
}
}
public:
// ------------------------------------------------------------------
// Block reorthogonalization helpers.
// Declared public because CUDA extended lambdas cannot live inside
// private/protected member functions.
//
// batchInnerProducts: computes c[j] = <basis[j], vec> for all j
// in a single GPU pass (one accelerator_barrier instead of n).
// Queues n pairs of (per-site kernel, reduceKernel) to computeStream
// without intermediate CPU syncs, then syncs once at the end.
//
// batchUpdate: computes vec -= sum_j c[j]*basis[j] in one GPU kernel.
//
// reorthogonalize: two-pass Classical Gram-Schmidt (CGS2) using the
// two helpers above. Each pass costs 2 GPU syncs (1 IP + 1 update)
// instead of 2n syncs per pass in the old sequential MGS.
// ------------------------------------------------------------------
void batchInnerProducts(const Field &vec,
const std::vector<Field> &basis,
std::vector<ComplexD> &c)
{
int n = (int)basis.size();
c.resize(n);
if (n == 0) return;
typedef typename Field::vector_object vobj;
typedef decltype(innerProduct(vobj(), vobj())) inner_t;
typedef decltype(basis[0].View(AcceleratorRead)) View;
GridBase *grid = vec.Grid();
uint64_t oSites = grid->oSites();
uint64_t nsimd = grid->Nsimd();
// all_ip[j * oSites + ss] = per-site inner product of basis[j] and vec at site ss.
// Layout: n contiguous blocks of oSites each.
deviceVector<inner_t> all_ip((uint64_t)n * oSites);
inner_t *all_ip_p = &all_ip[0];
hostVector<View> h_basis_v(n);
deviceVector<View> d_basis_v(n);
for (int j = 0; j < n; ++j) {
h_basis_v[j] = basis[j].View(AcceleratorRead);
acceleratorPut(d_basis_v[j], h_basis_v[j]);
}
View *basis_vp = &d_basis_v[0];
// Queue n per-site kernels to the accelerator stream — no intermediate barriers.
autoView(vec_v, vec, AcceleratorRead);
for (int j = 0; j < n; ++j) {
int jj = j;
uint64_t oSites_ = oSites;
accelerator_for(ss, oSites, nsimd, {
auto x = coalescedRead(basis_vp[jj][ss]);
auto y = coalescedRead(vec_v[ss]);
coalescedWrite(all_ip_p[jj * oSites_ + ss], innerProduct(x, y));
});
}
// ONE sync after all n kernels
accelerator_barrier();
// Copy all per-site results to host
hostVector<inner_t> all_ip_h((uint64_t)n * oSites);
acceleratorCopyFromDevice(all_ip_p, &all_ip_h[0], (uint64_t)n * oSites * sizeof(inner_t));
// Reduce on host: sum over oSites, then collapse SIMD lanes via Reduce(TensorRemove(...))
// TensorRemove strips the iSinglet tensor wrapper to expose the SIMD scalar type.
// Reduce sums all nsimd lanes and returns a plain scalar (RealD or ComplexD).
std::vector<ComplexD> raw(n);
for (int j = 0; j < n; ++j) {
inner_t sum = Zero();
for (uint64_t ss = 0; ss < oSites; ++ss)
sum += all_ip_h[(uint64_t)j * oSites + ss];
raw[j] = ComplexD(Reduce(TensorRemove(sum)));
}
grid->GlobalSumVector(&raw[0], n);
for (int j = 0; j < n; ++j) c[j] = raw[j];
for (int j = 0; j < n; ++j) h_basis_v[j].ViewClose();
}
void batchUpdate(Field &vec,
const std::vector<Field> &basis,
const std::vector<ComplexD> &c)
{
int n = (int)basis.size();
if (n == 0) return;
typedef typename Field::vector_object vobj;
typedef decltype(basis[0].View(AcceleratorRead)) View;
GridBase *grid = vec.Grid();
uint64_t oSites = grid->oSites();
uint64_t nsimd = grid->Nsimd();
// Split complex coefficients into real/imag double arrays on device.
// Using doubles avoids potential ComplexD-device-code compatibility issues.
hostVector<double> h_re(n), h_im(n);
deviceVector<double> d_re(n), d_im(n);
for (int k = 0; k < n; ++k) {
h_re[k] = c[k].real();
h_im[k] = c[k].imag();
}
acceleratorCopyToDevice(&h_re[0], &d_re[0], n * sizeof(double));
acceleratorCopyToDevice(&h_im[0], &d_im[0], n * sizeof(double));
double *re_p = &d_re[0];
double *im_p = &d_im[0];
// Basis views
hostVector<View> h_basis_v(n);
deviceVector<View> d_basis_v(n);
for (int k = 0; k < n; ++k) {
h_basis_v[k] = basis[k].View(AcceleratorRead);
acceleratorPut(d_basis_v[k], h_basis_v[k]);
}
View *basis_vp = &d_basis_v[0];
// Single kernel: vec[ss] -= sum_k (re[k] + i*im[k]) * basis[k][ss]
autoView(vec_v, vec, AcceleratorWrite);
accelerator_for(ss, oSites, nsimd, {
auto v = coalescedRead(vec_v[ss]);
for (int k = 0; k < n; ++k) {
auto b = coalescedRead(basis_vp[k][ss]);
v = v - re_p[k] * b - timesI(im_p[k] * b);
}
coalescedWrite(vec_v[ss], v);
});
for (int k = 0; k < n; ++k) h_basis_v[k].ViewClose();
}
// ------------------------------------------------------------------
// Full reorthogonalization using two-pass Classical Gram-Schmidt (CGS2).
// Each pass calls batchInnerProducts (1 GPU sync) + batchUpdate (1 sync),
// replacing the old 2n GPU syncs per pass from sequential MGS.
// ------------------------------------------------------------------
void reorthogonalize(Field &vec, const std::vector<Field> &basis)
{
if (basis.empty()) return;
std::vector<ComplexD> c;
for (int pass = 0; pass < 2; ++pass) {
batchInnerProducts(vec, basis, c);
batchUpdate(vec, basis, c);
}
}
// ------------------------------------------------------------------
// Implicit restart: given the Nm-step bidiagonalization and its SVD,
// compress to Nk steps via implicit QR shifts applied to B_k.
//
// The "shifts" are the Nm - Nk singular values we want to deflate
// (those NOT in the desired set). We apply them as implicit QR steps
// to the bidiagonal matrix, then update the lattice bases accordingly.
//
// After this call:
// V[0..Nk], U[0..Nk-1], alpha[0..Nk-1], beta[0..Nk-1] are updated.
// betaRestart ← new beta_Nk coupling for the next extension.
// ------------------------------------------------------------------
void implicitRestart(int k, int p,
const Eigen::VectorXd &sigma,
const Eigen::MatrixXd &X,
const Eigen::MatrixXd &Y,
const Eigen::VectorXi &order,
RealD betaK,
RealD &betaRestart)
{
// Thick restart (Baglama & Reichel, Sec. 2.2):
//
// Given B_k = X Sigma Y^T, define the new p-step basis by:
// V^+_i = V_k * y_{order(i)} (right sing. vec. of B_k)
// U^+_i = U_k * x_{order(i)} (left sing. vec. of B_k)
//
// Then A V^+_i = A V_k y_{order(i)} = U_k B_k y_{order(i)}
// = sigma_{order(i)} U_k x_{order(i)} = sigma_{order(i)} U^+_i
//
// So B_p^+ = diag(sigma_{order(0)}, ..., sigma_{order(p-1)}) — DIAGONAL,
// all internal betas are zero.
//
// The residual coupling comes from A^dag U_k = V_k B_k^T + betaK V[k] e_{k-1}^T:
// A^dag U^+_{p-1} - sigma_{order(p-1)} V^+_{p-1}
// = V_k (B_k^T x_{order(p-1)} - sigma_{order(p-1)} y_{order(p-1)})
// + betaK * X(k-1, order(p-1)) * V[k]
// = betaK * X(k-1, order(p-1)) * V[k] (since B_k^T x_j = sigma_j y_j)
//
// Therefore: betaRestart = |betaK * X(k-1, order(p-1))|
// V[p] = sign(X(k-1, order(p-1))) * V[k]
// ---- Build new lattice vectors ----
std::vector<Field> Vnew, Unew;
Vnew.reserve(p + 1);
Unew.reserve(p);
for (int i = 0; i < p; ++i) {
int idx = order(i);
Field vi(Grid); vi = Zero();
for (int j = 0; j < k; ++j)
vi = vi + Y(j, idx) * V[j];
Vnew.push_back(vi);
}
for (int i = 0; i < p; ++i) {
int idx = order(i);
Field ui(Grid); ui = Zero();
for (int j = 0; j < k; ++j)
ui = ui + X(j, idx) * U[j];
Unew.push_back(ui);
}
// New v_{p} (0-indexed: V[p]) = sign * V[k]
// From A^dag U_k = V_k B_k^T + betaK V[k] e_{k-1}^T:
// A^dag U^+_j - sigma_j V^+_j = betaK * X(k-1, order(j)) * V[k]
// The last Ritz pair (j=p-1) defines betaRestart and the sign of V[p].
// All p couplings (j=0..p-1) are stored in fvec so that buildFullB can
// reconstruct the exact column p of U^dag A V after the next extension.
RealD coeff = betaK * X(k - 1, order(p - 1));
betaRestart = std::abs(coeff);
RealD sgn = (coeff >= 0.0) ? 1.0 : -1.0;
fvec.resize(p);
for (int j = 0; j < p; ++j)
fvec[j] = betaK * X(k - 1, order(j)) * sgn;
// fvec[p-1] == betaRestart by construction
restart_col = p;
Field vp(Grid);
if (betaRestart > 1.0e-14) {
vp = sgn * V[k];
} else {
betaRestart = 0.0;
vp = Zero();
}
Vnew.push_back(vp); // V[p]
// ---- New alpha, beta ----
// B_p^+ is diagonal: alpha^+_i = sigma_{order(i)}, all internal beta = 0
std::vector<RealD> alpha_new(p), beta_new(p);
for (int i = 0; i < p; ++i) alpha_new[i] = sigma(order(i));
for (int i = 0; i < p - 1; ++i) beta_new[i] = 0.0;
beta_new[p - 1] = betaRestart;
// ---- Commit new state ----
V = Vnew;
U = Unew;
alpha = alpha_new;
beta = beta_new;
std::cout << GridLogMessage
<< "IRLBA restart: compressed to " << p << " steps,"
<< " new beta_p = " << betaRestart << std::endl;
}
// ------------------------------------------------------------------
// Extract the desired singular triplets into the public output vectors.
// ------------------------------------------------------------------
void extractTriplets(int m,
const Eigen::VectorXd &sigma,
const Eigen::MatrixXd &X,
const Eigen::MatrixXd &Y,
const Eigen::VectorXi &order,
int nout)
{
singularValues.resize(nout);
leftVectors.clear(); leftVectors.reserve(nout);
rightVectors.clear(); rightVectors.reserve(nout);
for (int i = 0; i < nout; ++i) {
int idx = order(i);
singularValues[i] = sigma(idx);
// Left singular vector of A: svec_L = U_m * x_i
Field svL(Grid); svL = Zero();
for (int j = 0; j < m && j < (int)U.size(); ++j)
svL = svL + X(j, idx) * U[j];
leftVectors.push_back(svL);
// Right singular vector of A: svec_R = V_m * y_i
Field svR(Grid); svR = Zero();
for (int j = 0; j < m && j < (int)V.size(); ++j)
svR = svR + Y(j, idx) * V[j];
rightVectors.push_back(svR);
}
}
};
NAMESPACE_END(Grid);
#endif
+931
View File
@@ -0,0 +1,931 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/ImplicitlyRestartedLanczos.h
Copyright (C) 2015
Author: Chulwoo Jung <chulwoo@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.
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 */
#ifndef GRID_LANC_H
#define GRID_LANC_H
#include <string.h> //memset
#ifdef USE_LAPACK
#ifdef USE_MKL
#include<mkl_lapack.h>
#else
void LAPACK_dstegr (char *jobz, char *range, int *n, double *d, double *e,
double *vl, double *vu, int *il, int *iu, double *abstol,
int *m, double *w, double *z, int *ldz, int *isuppz,
double *work, int *lwork, int *iwork, int *liwork,
int *info);
//#include <lapacke/lapacke.h>
#endif
#endif
//#include <Grid/algorithms/densematrix/DenseMatrix.h>
// eliminate temorary vector in calc()
#define MEM_SAVE
namespace Grid
{
struct Bisection
{
#if 0
static void get_eig2 (int row_num, std::vector < RealD > &ALPHA,
std::vector < RealD > &BETA,
std::vector < RealD > &eig)
{
int i, j;
std::vector < RealD > evec1 (row_num + 3);
std::vector < RealD > evec2 (row_num + 3);
RealD eps2;
ALPHA[1] = 0.;
BETHA[1] = 0.;
for (i = 0; i < row_num - 1; i++)
{
ALPHA[i + 1] = A[i * (row_num + 1)].real ();
BETHA[i + 2] = A[i * (row_num + 1) + 1].real ();
}
ALPHA[row_num] = A[(row_num - 1) * (row_num + 1)].real ();
bisec (ALPHA, BETHA, row_num, 1, row_num, 1e-10, 1e-10, evec1, eps2);
bisec (ALPHA, BETHA, row_num, 1, row_num, 1e-16, 1e-16, evec2, eps2);
// Do we really need to sort here?
int begin = 1;
int end = row_num;
int swapped = 1;
while (swapped)
{
swapped = 0;
for (i = begin; i < end; i++)
{
if (mag (evec2[i]) > mag (evec2[i + 1]))
{
swap (evec2 + i, evec2 + i + 1);
swapped = 1;
}
}
end--;
for (i = end - 1; i >= begin; i--)
{
if (mag (evec2[i]) > mag (evec2[i + 1]))
{
swap (evec2 + i, evec2 + i + 1);
swapped = 1;
}
}
begin++;
}
for (i = 0; i < row_num; i++)
{
for (j = 0; j < row_num; j++)
{
if (i == j)
H[i * row_num + j] = evec2[i + 1];
else
H[i * row_num + j] = 0.;
}
}
}
#endif
static void bisec (std::vector < RealD > &c,
std::vector < RealD > &b,
int n,
int m1,
int m2,
RealD eps1,
RealD relfeh, std::vector < RealD > &x, RealD & eps2)
{
std::vector < RealD > wu (n + 2);
RealD h, q, x1, xu, x0, xmin, xmax;
int i, a, k;
b[1] = 0.0;
xmin = c[n] - fabs (b[n]);
xmax = c[n] + fabs (b[n]);
for (i = 1; i < n; i++)
{
h = fabs (b[i]) + fabs (b[i + 1]);
if (c[i] + h > xmax)
xmax = c[i] + h;
if (c[i] - h < xmin)
xmin = c[i] - h;
}
xmax *= 2.;
eps2 = relfeh * ((xmin + xmax) > 0.0 ? xmax : -xmin);
if (eps1 <= 0.0)
eps1 = eps2;
eps2 = 0.5 * eps1 + 7.0 * (eps2);
x0 = xmax;
for (i = m1; i <= m2; i++)
{
x[i] = xmax;
wu[i] = xmin;
}
for (k = m2; k >= m1; k--)
{
xu = xmin;
i = k;
do
{
if (xu < wu[i])
{
xu = wu[i];
i = m1 - 1;
}
i--;
}
while (i >= m1);
if (x0 > x[k])
x0 = x[k];
while ((x0 - xu) > 2 * relfeh * (fabs (xu) + fabs (x0)) + eps1)
{
x1 = (xu + x0) / 2;
a = 0;
q = 1.0;
for (i = 1; i <= n; i++)
{
q =
c[i] - x1 -
((q != 0.0) ? b[i] * b[i] / q : fabs (b[i]) / relfeh);
if (q < 0)
a++;
}
// printf("x1=%0.14e a=%d\n",x1,a);
if (a < k)
{
if (a < m1)
{
xu = x1;
wu[m1] = x1;
}
else
{
xu = x1;
wu[a + 1] = x1;
if (x[a] > x1)
x[a] = x1;
}
}
else
x0 = x1;
}
printf ("x0=%0.14e xu=%0.14e k=%d\n", x0, xu, k);
x[k] = (x0 + xu) / 2;
}
}
};
/////////////////////////////////////////////////////////////
// Implicitly restarted lanczos
/////////////////////////////////////////////////////////////
template < class Field > class SimpleLanczos
{
const RealD small = 1.0e-16;
public:
int lock;
int get;
int Niter;
int converged;
int Nstop; // Number of evecs checked for convergence
int Nk; // Number of converged sought
int Np; // Np -- Number of spare vecs in kryloc space
int Nm; // Nm -- total number of vectors
RealD OrthoTime;
RealD eresid;
// SortEigen < Field > _sort;
LinearFunction < Field > &_Linop;
// OperatorFunction < Field > &_poly;
/////////////////////////
// Constructor
/////////////////////////
void init (void)
{
};
// void Abort (int ff, std::vector < RealD > &evals, DenseVector < Denstd::vector < RealD > >&evecs);
SimpleLanczos (LinearFunction < Field > &Linop, // op
// OperatorFunction < Field > &poly, // polynmial
int _Nstop, // sought vecs
int _Nk, // sought vecs
int _Nm, // spare vecs
RealD _eresid, // resid in lmdue deficit
int _Niter): // Max iterations
_Linop (Linop),
// _poly (poly),
Nstop (_Nstop), Nk (_Nk), Nm (_Nm), eresid (_eresid), Niter (_Niter)
{
Np = Nm - Nk;
assert (Np > 0);
};
/////////////////////////
// Sanity checked this routine (step) against Saad.
/////////////////////////
void RitzMatrix (std::vector < Field > &evec, int k)
{
if (1)
return;
GridBase *grid = evec[0].Grid();
Field w (grid);
std::cout << GridLogMessage << "RitzMatrix " << std::endl;
for (int i = 0; i < k; i++)
{
_Linop(evec[i], w);
// _poly(_Linop,evec[i],w);
std::cout << GridLogMessage << "[" << i << "] ";
for (int j = 0; j < k; j++)
{
ComplexD in = innerProduct (evec[j], w);
if (fabs ((double) i - j) > 1)
{
if (abs (in) > 1.0e-9)
{
std::cout << GridLogMessage << "oops" << std::endl;
abort ();
}
else
std::cout << GridLogMessage << " 0 ";
}
else
{
std::cout << GridLogMessage << " " << in << " ";
}
}
std::cout << GridLogMessage << std::endl;
}
}
void step (std::vector < RealD > &lmd,
std::vector < RealD > &lme,
Field & last, Field & current, Field & next, uint64_t k)
{
if (lmd.size () <= k)
lmd.resize (k + Nm);
if (lme.size () <= k)
lme.resize (k + Nm);
// _poly(_Linop,current,next ); // 3. wk:=Avk−βkv_{k1}
_Linop(current, next); // 3. wk:=Avk−βkv_{k1}
if (k > 0)
{
next -= lme[k - 1] * last;
}
// std::cout<<GridLogMessage << "<last|next>" << innerProduct(last,next) <<std::endl;
ComplexD zalph = innerProduct (current, next); // 4. αk:=(wk,vk)
RealD alph = real (zalph);
next = next - alph * current; // 5. wk:=wk−αkvk
// std::cout<<GridLogMessage << "<current|next>" << innerProduct(current,next) <<std::endl;
RealD beta = normalise (next); // 6. βk+1 := ∥wk∥2. If βk+1 = 0 then Stop
// 7. vk+1 := wk/βk+1
// norm=beta;
int interval = Nm / 100 + 1;
if ((k % interval) == 0)
std::
cout << GridLogMessage << k << " : alpha = " << zalph << " beta " <<
beta << std::endl;
const RealD tiny = 1.0e-20;
if (beta < tiny)
{
std::cout << GridLogMessage << " beta is tiny " << beta << std::
endl;
}
lmd[k] = alph;
lme[k] = beta;
}
void qr_decomp (std::vector < RealD > &lmd,
std::vector < RealD > &lme,
int Nk,
int Nm,
std::vector < RealD > &Qt, RealD Dsh, int kmin, int kmax)
{
int k = kmin - 1;
RealD x;
RealD Fden = 1.0 / hypot (lmd[k] - Dsh, lme[k]);
RealD c = (lmd[k] - Dsh) * Fden;
RealD s = -lme[k] * Fden;
RealD tmpa1 = lmd[k];
RealD tmpa2 = lmd[k + 1];
RealD tmpb = lme[k];
lmd[k] = c * c * tmpa1 + s * s * tmpa2 - 2.0 * c * s * tmpb;
lmd[k + 1] = s * s * tmpa1 + c * c * tmpa2 + 2.0 * c * s * tmpb;
lme[k] = c * s * (tmpa1 - tmpa2) + (c * c - s * s) * tmpb;
x = -s * lme[k + 1];
lme[k + 1] = c * lme[k + 1];
for (int i = 0; i < Nk; ++i)
{
RealD Qtmp1 = Qt[i + Nm * k];
RealD Qtmp2 = Qt[i + Nm * (k + 1)];
Qt[i + Nm * k] = c * Qtmp1 - s * Qtmp2;
Qt[i + Nm * (k + 1)] = s * Qtmp1 + c * Qtmp2;
}
// Givens transformations
for (int k = kmin; k < kmax - 1; ++k)
{
RealD Fden = 1.0 / hypot (x, lme[k - 1]);
RealD c = lme[k - 1] * Fden;
RealD s = -x * Fden;
RealD tmpa1 = lmd[k];
RealD tmpa2 = lmd[k + 1];
RealD tmpb = lme[k];
lmd[k] = c * c * tmpa1 + s * s * tmpa2 - 2.0 * c * s * tmpb;
lmd[k + 1] = s * s * tmpa1 + c * c * tmpa2 + 2.0 * c * s * tmpb;
lme[k] = c * s * (tmpa1 - tmpa2) + (c * c - s * s) * tmpb;
lme[k - 1] = c * lme[k - 1] - s * x;
if (k != kmax - 2)
{
x = -s * lme[k + 1];
lme[k + 1] = c * lme[k + 1];
}
for (int i = 0; i < Nk; ++i)
{
RealD Qtmp1 = Qt[i + Nm * k];
RealD Qtmp2 = Qt[i + Nm * (k + 1)];
Qt[i + Nm * k] = c * Qtmp1 - s * Qtmp2;
Qt[i + Nm * (k + 1)] = s * Qtmp1 + c * Qtmp2;
}
}
}
#if 0
#ifdef USE_LAPACK
#ifdef USE_MKL
#define LAPACK_INT MKL_INT
#else
#define LAPACK_INT long long
#endif
void diagonalize_lapack (std::vector < RealD > &lmd, std::vector < RealD > &lme, int N1, // all
int N2, // get
GridBase * grid)
{
const int size = Nm;
LAPACK_INT NN = N1;
double evals_tmp[NN];
double DD[NN];
double EE[NN];
for (int i = 0; i < NN; i++)
for (int j = i - 1; j <= i + 1; j++)
if (j < NN && j >= 0)
{
if (i == j)
DD[i] = lmd[i];
if (i == j)
evals_tmp[i] = lmd[i];
if (j == (i - 1))
EE[j] = lme[j];
}
LAPACK_INT evals_found;
LAPACK_INT lwork =
((18 * NN) >
(1 + 4 * NN + NN * NN) ? (18 * NN) : (1 + 4 * NN + NN * NN));
LAPACK_INT liwork = 3 + NN * 10;
LAPACK_INT iwork[liwork];
double work[lwork];
LAPACK_INT isuppz[2 * NN];
char jobz = 'N'; // calculate evals only
char range = 'I'; // calculate il-th to iu-th evals
// char range = 'A'; // calculate all evals
char uplo = 'U'; // refer to upper half of original matrix
char compz = 'I'; // Compute eigenvectors of tridiagonal matrix
int ifail[NN];
LAPACK_INT info;
// int total = QMP_get_number_of_nodes();
// int node = QMP_get_node_number();
// GridBase *grid = evec[0]._grid;
int total = grid->_Nprocessors;
int node = grid->_processor;
int interval = (NN / total) + 1;
double vl = 0.0, vu = 0.0;
LAPACK_INT il = interval * node + 1, iu = interval * (node + 1);
if (iu > NN)
iu = NN;
double tol = 0.0;
if (1)
{
memset (evals_tmp, 0, sizeof (double) * NN);
if (il <= NN)
{
printf ("total=%d node=%d il=%d iu=%d\n", total, node, il, iu);
#ifdef USE_MKL
dstegr (&jobz, &range, &NN,
#else
LAPACK_dstegr (&jobz, &range, &NN,
#endif
(double *) DD, (double *) EE, &vl, &vu, &il, &iu, // these four are ignored if second parameteris 'A'
&tol, // tolerance
&evals_found, evals_tmp, (double *) NULL, &NN,
isuppz, work, &lwork, iwork, &liwork, &info);
for (int i = iu - 1; i >= il - 1; i--)
{
printf ("node=%d evals_found=%d evals_tmp[%d] = %g\n", node,
evals_found, i - (il - 1), evals_tmp[i - (il - 1)]);
evals_tmp[i] = evals_tmp[i - (il - 1)];
if (il > 1)
evals_tmp[i - (il - 1)] = 0.;
}
}
{
grid->GlobalSumVector (evals_tmp, NN);
}
}
// cheating a bit. It is better to sort instead of just reversing it, but the document of the routine says evals are sorted in increasing order. qr gives evals in decreasing order.
}
#undef LAPACK_INT
#endif
void diagonalize (std::vector < RealD > &lmd,
std::vector < RealD > &lme,
int N2, int N1, GridBase * grid)
{
#ifdef USE_LAPACK
const int check_lapack = 0; // just use lapack if 0, check against lapack if 1
if (!check_lapack)
return diagonalize_lapack (lmd, lme, N2, N1, grid);
// diagonalize_lapack(lmd2,lme2,Nm2,Nm,Qt,grid);
#endif
}
#endif
static RealD normalise (Field & v)
{
RealD nn = norm2 (v);
nn = sqrt (nn);
v = v * (1.0 / nn);
return nn;
}
void orthogonalize (Field & w, std::vector < Field > &evec, int k)
{
double t0 = -usecond () / 1e6;
typedef typename Field::scalar_type MyComplex;
MyComplex ip;
if (0)
{
for (int j = 0; j < k; ++j)
{
normalise (evec[j]);
for (int i = 0; i < j; i++)
{
ip = innerProduct (evec[i], evec[j]); // are the evecs normalised? ; this assumes so.
evec[j] = evec[j] - ip * evec[i];
}
}
}
for (int j = 0; j < k; ++j)
{
ip = innerProduct (evec[j], w); // are the evecs normalised? ; this assumes so.
w = w - ip * evec[j];
}
normalise (w);
t0 += usecond () / 1e6;
OrthoTime += t0;
}
void setUnit_Qt (int Nm, std::vector < RealD > &Qt)
{
for (int i = 0; i < Qt.size (); ++i)
Qt[i] = 0.0;
for (int k = 0; k < Nm; ++k)
Qt[k + k * Nm] = 1.0;
}
void calc (std::vector < RealD > &eval, const Field & src, int &Nconv)
{
GridBase *grid = src.Grid();
// assert(grid == src._grid);
std::
cout << GridLogMessage << " -- Nk = " << Nk << " Np = " << Np << std::
endl;
std::cout << GridLogMessage << " -- Nm = " << Nm << std::endl;
std::cout << GridLogMessage << " -- size of eval = " << eval.
size () << std::endl;
// assert(c.size() && Nm == eval.size());
std::vector < RealD > lme (Nm);
std::vector < RealD > lmd (Nm);
Field current (grid);
Field last (grid);
Field next (grid);
Nconv = 0;
RealD beta_k;
// Set initial vector
// (uniform vector) Why not src??
// evec[0] = 1.0;
current = src;
std::cout << GridLogMessage << "norm2(src)= " << norm2 (src) << std::
endl;
normalise (current);
std::
cout << GridLogMessage << "norm2(evec[0])= " << norm2 (current) <<
std::endl;
// Initial Nk steps
OrthoTime = 0.;
double t0 = usecond () / 1e6;
RealD norm; // sqrt norm of last vector
uint64_t iter = 0;
bool initted = false;
std::vector < RealD > low (Nstop * 10);
std::vector < RealD > high (Nstop * 10);
RealD cont = 0.;
while (1) {
cont = 0.;
std::vector < RealD > lme2 (Nm);
std::vector < RealD > lmd2 (Nm);
for (uint64_t k = 0; k < Nm; ++k, iter++) {
step (lmd, lme, last, current, next, iter);
last = current;
current = next;
}
double t1 = usecond () / 1e6;
std::cout << GridLogMessage << "IRL::Initial steps: " << t1 -
t0 << "seconds" << std::endl;
t0 = t1;
std::
cout << GridLogMessage << "IRL::Initial steps:OrthoTime " <<
OrthoTime << "seconds" << std::endl;
// getting eigenvalues
lmd2.resize (iter + 2);
lme2.resize (iter + 2);
for (uint64_t k = 0; k < iter; ++k) {
lmd2[k + 1] = lmd[k];
lme2[k + 2] = lme[k];
}
t1 = usecond () / 1e6;
std::cout << GridLogMessage << "IRL:: copy: " << t1 -
t0 << "seconds" << std::endl;
t0 = t1;
{
int total = grid->_Nprocessors;
int node = grid->_processor;
int interval = (Nstop / total) + 1;
int iu = (iter + 1) - (interval * node + 1);
int il = (iter + 1) - (interval * (node + 1));
std::vector < RealD > eval2 (iter + 3);
RealD eps2;
Bisection::bisec (lmd2, lme2, iter, il, iu, 1e-16, 1e-10, eval2,
eps2);
// diagonalize(eval2,lme2,iter,Nk,grid);
RealD diff = 0.;
for (int i = il; i <= iu; i++) {
if (initted)
diff =
fabs (eval2[i] - high[iu-i]) / (fabs (eval2[i]) +
fabs (high[iu-i]));
if (initted && (diff > eresid))
cont = 1.;
if (initted)
printf ("eval[%d]=%0.14e %0.14e, %0.14e\n", i, eval2[i],
high[iu-i], diff);
high[iu-i] = eval2[i];
}
il = (interval * node + 1);
iu = (interval * (node + 1));
Bisection::bisec (lmd2, lme2, iter, il, iu, 1e-16, 1e-10, eval2,
eps2);
for (int i = il; i <= iu; i++) {
if (initted)
diff =
fabs (eval2[i] - low[i]) / (fabs (eval2[i]) +
fabs (low[i]));
if (initted && (diff > eresid))
cont = 1.;
if (initted)
printf ("eval[%d]=%0.14e %0.14e, %0.14e\n", i, eval2[i],
low[i], diff);
low[i] = eval2[i];
}
t1 = usecond () / 1e6;
std::cout << GridLogMessage << "IRL:: diagonalize: " << t1 -
t0 << "seconds" << std::endl;
t0 = t1;
}
for (uint64_t k = 0; k < Nk; ++k) {
// eval[k] = eval2[k];
}
if (initted)
{
grid->GlobalSumVector (&cont, 1);
if (cont < 1.) return;
}
initted = true;
}
}
#if 0
/**
There is some matrix Q such that for any vector y
Q.e_1 = y and Q is unitary.
**/
template < class T >
static T orthQ (DenseMatrix < T > &Q, std::vector < T > y)
{
int N = y.size (); //Matrix Size
Fill (Q, 0.0);
T tau;
for (int i = 0; i < N; i++)
{
Q[i][0] = y[i];
}
T sig = conj (y[0]) * y[0];
T tau0 = fabs (sqrt (sig));
for (int j = 1; j < N; j++)
{
sig += conj (y[j]) * y[j];
tau = abs (sqrt (sig));
if (abs (tau0) > 0.0)
{
T gam = conj ((y[j] / tau) / tau0);
for (int k = 0; k <= j - 1; k++)
{
Q[k][j] = -gam * y[k];
}
Q[j][j] = tau0 / tau;
}
else
{
Q[j - 1][j] = 1.0;
}
tau0 = tau;
}
return tau;
}
/**
There is some matrix Q such that for any vector y
Q.e_k = y and Q is unitary.
**/
template < class T >
static T orthU (DenseMatrix < T > &Q, std::vector < T > y)
{
T tau = orthQ (Q, y);
SL (Q);
return tau;
}
/**
Wind up with a matrix with the first con rows untouched
say con = 2
Q is such that Qdag H Q has {x, x, val, 0, 0, 0, 0, ...} as 1st colum
and the matrix is upper hessenberg
and with f and Q appropriately modidied with Q is the arnoldi factorization
**/
template < class T > static void Lock (DenseMatrix < T > &H, ///Hess mtx
DenseMatrix < T > &Q, ///Lock Transform
T val, ///value to be locked
int con, ///number already locked
RealD small, int dfg, bool herm)
{
//ForceTridiagonal(H);
int M = H.dim;
DenseVector < T > vec;
Resize (vec, M - con);
DenseMatrix < T > AH;
Resize (AH, M - con, M - con);
AH = GetSubMtx (H, con, M, con, M);
DenseMatrix < T > QQ;
Resize (QQ, M - con, M - con);
Unity (Q);
Unity (QQ);
DenseVector < T > evals;
Resize (evals, M - con);
DenseMatrix < T > evecs;
Resize (evecs, M - con, M - con);
Wilkinson < T > (AH, evals, evecs, small);
int k = 0;
RealD cold = abs (val - evals[k]);
for (int i = 1; i < M - con; i++)
{
RealD cnew = abs (val - evals[i]);
if (cnew < cold)
{
k = i;
cold = cnew;
}
}
vec = evecs[k];
ComplexD tau;
orthQ (QQ, vec);
//orthQM(QQ,AH,vec);
AH = Hermitian (QQ) * AH;
AH = AH * QQ;
for (int i = con; i < M; i++)
{
for (int j = con; j < M; j++)
{
Q[i][j] = QQ[i - con][j - con];
H[i][j] = AH[i - con][j - con];
}
}
for (int j = M - 1; j > con + 2; j--)
{
DenseMatrix < T > U;
Resize (U, j - 1 - con, j - 1 - con);
DenseVector < T > z;
Resize (z, j - 1 - con);
T nm = norm (z);
for (int k = con + 0; k < j - 1; k++)
{
z[k - con] = conj (H (j, k + 1));
}
normalise (z);
RealD tmp = 0;
for (int i = 0; i < z.size () - 1; i++)
{
tmp = tmp + abs (z[i]);
}
if (tmp < small / ((RealD) z.size () - 1.0))
{
continue;
}
tau = orthU (U, z);
DenseMatrix < T > Hb;
Resize (Hb, j - 1 - con, M);
for (int a = 0; a < M; a++)
{
for (int b = 0; b < j - 1 - con; b++)
{
T sum = 0;
for (int c = 0; c < j - 1 - con; c++)
{
sum += H[a][con + 1 + c] * U[c][b];
} //sum += H(a,con+1+c)*U(c,b);}
Hb[b][a] = sum;
}
}
for (int k = con + 1; k < j; k++)
{
for (int l = 0; l < M; l++)
{
H[l][k] = Hb[k - 1 - con][l];
}
} //H(Hb[k-1-con][l] , l,k);}}
DenseMatrix < T > Qb;
Resize (Qb, M, M);
for (int a = 0; a < M; a++)
{
for (int b = 0; b < j - 1 - con; b++)
{
T sum = 0;
for (int c = 0; c < j - 1 - con; c++)
{
sum += Q[a][con + 1 + c] * U[c][b];
} //sum += Q(a,con+1+c)*U(c,b);}
Qb[b][a] = sum;
}
}
for (int k = con + 1; k < j; k++)
{
for (int l = 0; l < M; l++)
{
Q[l][k] = Qb[k - 1 - con][l];
}
} //Q(Qb[k-1-con][l] , l,k);}}
DenseMatrix < T > Hc;
Resize (Hc, M, M);
for (int a = 0; a < j - 1 - con; a++)
{
for (int b = 0; b < M; b++)
{
T sum = 0;
for (int c = 0; c < j - 1 - con; c++)
{
sum += conj (U[c][a]) * H[con + 1 + c][b];
} //sum += conj( U(c,a) )*H(con+1+c,b);}
Hc[b][a] = sum;
}
}
for (int k = 0; k < M; k++)
{
for (int l = con + 1; l < j; l++)
{
H[l][k] = Hc[k][l - 1 - con];
}
} //H(Hc[k][l-1-con] , l,k);}}
}
}
#endif
};
}
#endif
@@ -0,0 +1,124 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/SplitGridBlockKrylovSchur.h
Copyright (C) 2015
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: Chulwoo Jung <chulwoo@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.
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 */
#ifndef GRID_SPLIT_BLOCK_KRYLOV_SCHUR_H
#define GRID_SPLIT_BLOCK_KRYLOV_SCHUR_H
NAMESPACE_BEGIN(Grid);
/**
* Block Krylov-Schur eigensolver with Split-Grid batched operator application.
*
* Derives from BlockKrylovSchur<Field> and overrides only the operator
* application: instead of calling Linop.Op() once per vector, mrhs vectors
* are packed onto a smaller split grid with Grid_split, the polynomial-
* filtered operator poly(SLinop) is applied once to the combined field, and
* the results are unpacked with Grid_unsplit. All other algorithmic logic
* (Arnoldi orthogonalisation, Schur restart, convergence test, etc.) is
* inherited unchanged from the base class.
*
* Constructor extras (beyond BlockKrylovSchur)
* -------------------------------------------
* SLinop : split-grid linear operator used inside poly
* poly : polynomial filter OperatorFunction applied on the split grid
* SGrid : split-grid GridBase (mrhs fields packed here)
* mrhs : RHS batched per split-grid call; Nblock must be divisible by mrhs
*
* Notes
* -----
* - The Rayleigh quotient H is built from poly(A), so its Ritz values are
* eigenvalues of poly(A). Convergence is assessed against the full-grid
* Linop (inherited from base), giving true A-eigenvalues.
* - Grid_ (inherited) is the full grid; SGrid is the split grid.
*/
template<class Field>
class SplitGridBlockKrylovSchur : public BlockKrylovSchur<Field> {
using Base = BlockKrylovSchur<Field>;
// Bring protected base members into scope
using Base::Nblock;
using Base::Grid_;
using Base::basis;
// Split-grid extras
LinearOperatorBase<Field>& SLinop;
OperatorFunction<Field>& poly;
GridBase* SGrid;
int mrhs;
public:
SplitGridBlockKrylovSchur(LinearOperatorBase<Field>& _Linop,
LinearOperatorBase<Field>& _SLinop,
OperatorFunction<Field>& _poly,
GridBase* _FGrid,
GridBase* _SGrid,
int _mrhs,
RealD _Tolerance,
RitzFilter _rf = EvalReSmall)
: Base(_Linop, _FGrid, _Tolerance, _rf),
SLinop(_SLinop), poly(_poly), SGrid(_SGrid), mrhs(_mrhs)
{}
protected:
// Validate mrhs divisibility before the Arnoldi loop starts
void preRun() override
{
assert(Nblock % mrhs == 0 && "Nblock must be divisible by mrhs");
}
// Apply poly(A) to basis[kBase .. kBase+Nblock-1] via Grid_split / Grid_unsplit,
// batching mrhs vectors per split-grid call.
void applyBlock(std::vector<Field>& W, int kBase) override
{
std::vector<Field> in(mrhs, Field(Grid_));
Field s_in(SGrid);
Field s_out(SGrid);
int k_start = 0;
while (k_start < Nblock) {
for (int u = 0; u < mrhs; u++)
in[u] = basis[kBase + k_start + u];
Grid_split(in, s_in);
poly(SLinop, s_in, s_out);
Grid_unsplit(in, s_out);
for (int u = 0; u < mrhs; u++)
W[k_start + u] = in[u];
k_start += mrhs;
}
}
};
NAMESPACE_END(Grid);
#endif // GRID_SPLIT_BLOCK_KRYLOV_SCHUR_H
@@ -0,0 +1,366 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/iterative/TrueHarmonicBlockKrylovSchur.h
Copyright (C) 2015
Author: Chulwoo Jung <chulwoo@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.
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 */
#ifndef GRID_TRUE_HARMONIC_BLOCKED_KRYLOV_SCHUR_H
#define GRID_TRUE_HARMONIC_BLOCKED_KRYLOV_SCHUR_H
#include <Grid/algorithms/iterative/HarmonicBlockKrylovSchur.h>
NAMESPACE_BEGIN(Grid);
/**
* True harmonic-Ritz block Krylov-Schur eigensolver.
*
* Difference from the base class
* ------------------------------
* HarmonicBlockKrylovSchur is a *shift-targeted* Krylov-Schur: it Schur-
* decomposes (H - sigma I) and sorts the standard Ritz values by |lambda -
* sigma|. For interior eigenvalues of Hermitian indefinite operators the
* standard Ritz values can be spurious ("ghosts"), so the sort can select
* junk. This variant performs genuine harmonic Ritz extraction instead.
*
* Harmonic extraction
* -------------------
* From the exact block Arnoldi relation
*
* A V = V H + F B^dag (1)
*
* harmonic Ritz pairs (theta, g) w.r.t. shift sigma satisfy the Petrov-
* Galerkin condition (A - sigma)Vg - (theta - sigma)Vg perp (A - sigma)V,
* which reduces (with Hs = H - sigma I) to the small eigenproblem
*
* Hhat g = (theta - sigma) g, Hhat = Hs + Hs^{-H} B B^dag (2)
*
* Exact thick restart
* -------------------
* From (2), H g = theta g - Hs^{-H} B (B^dag g), so the residual of every
* harmonic pair lies in the Nblock-dimensional column space of the single
* block
*
* W = F - V Hs^{-H} B :
* A (V g) - theta (V g) = W (B^dag g) (3)
*
* Hence {V g_1 .. g_Nk} + cols(W) admits an EXACT truncated Krylov-Schur
* relation A Y = Y H_k + What Bc^dag with dense H_k, and block Arnoldi
* resumes cleanly from What. Krylov-Schur (Stewart, SIMAX 23(3), 2001)
* does not require H to be triangular after restart -- only that the
* decomposition holds exactly, which (3) guarantees.
*
* This is the block analogue of the GMRES-DR restart (Morgan, SISC 24(1),
* 2002; block version: Morgan, Appl. Numer. Math. 54, 2005). The exact
* composition "block harmonic Krylov-Schur" is a derivation from these
* ingredients rather than a published algorithm; the relation (1) is
* checked explicitly at every restart when doVerify=true, and identity (3)
* was validated to machine precision in a standalone Eigen prototype.
*
* Caveats
* -------
* - If sigma is (numerically) an eigenvalue of H, Hs^{-H} is singular;
* an assert fires -- perturb the shift slightly.
* - Reported eigenvalues come from the eigensystem of the retained H_k
* (the true projected Rayleigh quotient Y^dag A Y), as in the base
* class, so convergence tests and reporting are inherited unchanged.
*
* Usage: identical to HarmonicBlockKrylovSchur.
*/
template<class Field>
class TrueHarmonicBlockKrylovSchur : public HarmonicBlockKrylovSchur<Field> {
typedef HarmonicBlockKrylovSchur<Field> Base;
typedef typename Base::CMat CMat;
typedef typename Base::CVec CVec;
// Members of the dependent base class need explicit scope.
using Base::Nblock;
using Base::Nm;
using Base::Nk;
using Base::Nstop;
using Base::MaxIter;
using Base::Tolerance;
using Base::shift;
using Base::Linop;
using Base::Grid_;
using Base::ritzFilter;
using Base::basis;
using Base::H;
using Base::F;
using Base::B;
using Base::beta_k;
using Base::rtol;
using Base::evals;
using Base::littleEvecs;
using Base::ritzEstimates;
using Base::expandStartBlock;
using Base::blockArnoldiIteration;
using Base::blockQR;
using Base::computeEigensystem;
using Base::converged;
using Base::approxMaxEval;
public:
using Base::evecs;
using Base::doEvalCheck;
using Base::useParityFlip;
using Base::useGamma5;
using Base::gamma5Func;
using Base::verify;
TrueHarmonicBlockKrylovSchur(LinearOperatorBase<Field>& _Linop, GridBase* _Grid,
RealD _Tolerance, ComplexD _shift = 0.0,
RitzFilter _rf = EvalNormSmall)
: Base(_Linop, _Grid, _Tolerance, _shift, _rf)
{}
//--------------------------------------------------------------------
// Main entry point (same signature and outer structure as the base;
// the Schur-sort restart is replaced by harmonicRestart()).
//--------------------------------------------------------------------
virtual void operator()(const std::vector<Field>& v0, int _maxIter, int _Nm, int _Nk,
int _Nstop, int _Nblock = 1, bool doubleOrthog = true,
bool doVerify = false) override
{
MaxIter = _maxIter;
Nm = _Nm;
Nk = _Nk;
Nstop = _Nstop;
Nblock = _Nblock;
{
int divisor = 1;
if (useParityFlip) divisor *= 2;
if (useGamma5) divisor *= 2;
assert(Nblock % divisor == 0 && (int)v0.size() >= Nblock / divisor);
}
assert(Nm % Nblock == 0);
assert(Nk % Nblock == 0);
assert(Nk < Nm);
if (useGamma5) assert(gamma5Func && "useGamma5: gamma5Func must be set");
int N = Nm;
RealD approxLambdaMax = approxMaxEval(v0[0]);
rtol = Tolerance * approxLambdaMax;
std::cout << GridLogMessage
<< "TrueHarmonicBlockKrylovSchur: approx max eval = " << approxLambdaMax
<< ", rtol = " << rtol
<< ", shift = " << shift << std::endl;
H = CMat::Zero(N, N);
B = CMat::Zero(N, Nblock);
int start = 0;
std::vector<Field> startBlock = expandStartBlock(v0);
for (int iter = 0; iter < MaxIter; iter++) {
std::cout << GridLogMessage
<< "TrueHarmonicBlockKrylovSchur: restart iteration " << iter << std::endl;
// ---- Block Arnoldi: extend from block 'start' to block Nm/Nblock ----
blockArnoldiIteration(startBlock, Nm/Nblock, start, doubleOrthog);
std::cout << GridLogMessage << "blockArnoldiIteration done " << std::endl;
start = Nk/Nblock;
if (doVerify) {
std::string lbl = "iter " + std::to_string(iter) + " after Arnoldi";
verify(lbl);
}
// ---- Harmonic extraction + exact thick restart ----
harmonicRestart();
std::cout << GridLogMessage << "harmonicRestart done " << std::endl;
// Restart from the residual block W (exact by identity (3))
startBlock = F;
if (doVerify) {
std::string lbl = "iter " + std::to_string(iter) + " after harmonic restart";
verify(lbl);
}
// ---- Eigensystem of retained H_k = Y^dag A Y for convergence ----
CMat Hk = H(Eigen::seqN(0, Nk), Eigen::seqN(0, Nk));
computeEigensystem(Hk, Nk);
int Nconv = converged(Nk);
std::cout << GridLogMessage
<< "TrueHarmonicBlockKrylovSchur: converged " << Nconv
<< " / " << Nstop << std::endl;
if (Nconv >= Nstop || iter == MaxIter - 1) {
std::cout << GridLogMessage
<< "TrueHarmonicBlockKrylovSchur: done after " << iter
<< " restarts, " << Nconv << " converged." << std::endl;
std::cout << GridLogMessage << "Eigenvalues: " << evals.transpose() << std::endl;
if (doEvalCheck) {
Field w(Grid_);
for (int k = 0; k < (int)evecs.size(); k++) {
Linop.Op(evecs[k], w);
ComplexD eval_est = toStdCmplx(innerProduct(evecs[k], w));
w -= eval_est * evecs[k];
RealD res = std::sqrt(norm2(w));
std::cout << GridLogMessage << "TrueHarmonicBlockKrylovSchur: evec[" << k << "]"
<< " eval_reported = " << evals[k]
<< " eval_est = " << eval_est
<< " || A v - eval_est * v || = " << res << std::endl;
}
}
return;
}
}
}
protected:
//--------------------------------------------------------------------
// Harmonic thick restart
//--------------------------------------------------------------------
// On entry: A V = V H + F B^dag exact, V = basis (Nm fields), F Nblock
// residual fields, H/B the Nm-sized small matrices.
// On exit: basis = Y (Nk orthonormal fields spanning the harmonic
// Ritz space), F = What (Nblock orthonormal fields, perp Y),
// H top-left Nk x Nk = Y^dag A Y (dense), B top Nk rows the
// new coupling; relation A Y = Y H_k + F B^dag again exact.
void harmonicRestart()
{
int N = Nm;
// ---- Small harmonic eigenproblem: Hhat g = (theta - sigma) g ----
CMat Hs = H - shift * CMat::Identity(N, N);
Eigen::FullPivLU<CMat> lu(Hs.adjoint());
assert(lu.isInvertible() &&
"TrueHarmonicBlockKrylovSchur: H - shift*I singular; perturb the shift");
CMat M = lu.solve(B); // N x Nblock : Hs^{-H} B
CMat Hhat = Hs + M * B.adjoint(); // N x N
Eigen::ComplexEigenSolver<CMat> es(Hhat);
// Sort harmonic values (theta - sigma) by the Ritz filter
// (EvalNormSmall -> closest to the shift).
ComplexComparator cComp(ritzFilter);
std::vector<int> idx(N);
std::iota(idx.begin(), idx.end(), 0);
std::sort(idx.begin(), idx.end(), [&](int a, int b){
return cComp(toStdCmplx(es.eigenvalues()(a)), toStdCmplx(es.eigenvalues()(b)));
});
CMat G(N, Nk);
CVec theta(Nk);
for (int k = 0; k < Nk; k++) {
G.col(k) = es.eigenvectors().col(idx[k]);
theta(k) = es.eigenvalues()(idx[k]) + toStdCmplx(shift);
}
std::cout << GridLogMessage
<< "TrueHarmonicBlockKrylovSchur: harmonic Ritz values nearest shift:" << std::endl;
for (int k = 0; k < Nk; k++)
std::cout << GridLogMessage << " [" << k << "] " << theta(k) << std::endl;
// ---- Orthonormalise the little vectors: G = Gt Rg ----
Eigen::HouseholderQR<CMat> qr(G);
CMat Gt = qr.householderQ() * CMat::Identity(N, Nk);
CMat Rg = qr.matrixQR().topLeftCorner(Nk, Nk).template triangularView<Eigen::Upper>();
for (int k = 0; k < Nk; k++)
assert(std::abs(Rg(k, k)) > 1e-14 &&
"TrueHarmonicBlockKrylovSchur: harmonic vectors linearly dependent");
CMat RgInv = Rg.inverse();
// A (V G) = (V G) diag(theta) + W (B^dag G), W = F - V M
// => A (V Gt) = (V Gt) T + W Ct
CMat T = Rg * theta.asDiagonal() * RgInv; // Nk x Nk
CMat Ct = (B.adjoint() * G) * RgInv; // Nblock x Nk
// ---- Large-field work ----
// Y = V Gt : new orthonormal basis (Nk fields)
std::vector<Field> Y;
constructThin(Y, basis, Gt, Nk);
// W = F - V M : Nblock residual directions containing every harmonic
// residual (identity (3) in the class comment)
std::vector<Field> W = F;
for (int t = 0; t < Nblock; t++)
for (int j = 0; j < N; j++)
W[t] -= basis[j] * M(j, t);
// Orthogonalise W against Y (two passes), recording P = Y^dag W
CMat P = CMat::Zero(Nk, Nblock);
for (int pass = 0; pass < 2; pass++) {
for (int j = 0; j < Nk; j++) {
for (int t = 0; t < Nblock; t++) {
ComplexD c = innerProduct(Y[j], W[t]);
P(j, t) += toStdCmplx(c);
W[t] -= c * Y[j];
}
}
}
// Block QR of the projected residual: W <- What, returns Rw
CMat Rw = blockQR(W);
// ---- Reassemble the exact Krylov-Schur relation ----
// A Y = Y (T + P Ct) + What (Rw Ct)
CMat Hk = T + P * Ct; // Nk x Nk, dense
CMat Bc = Rw * Ct; // Nblock x Nk
H = CMat::Zero(N, N);
H(Eigen::seqN(0, Nk), Eigen::seqN(0, Nk)) = Hk;
B = CMat::Zero(N, Nblock);
for (int j = 0; j < Nk; j++)
for (int t = 0; t < Nblock; t++)
B(j, t) = std::conj(Bc(t, j));
basis = Y;
F = W;
beta_k = Bc.norm();
std::cout << GridLogMessage
<< "TrueHarmonicBlockKrylovSchur: beta_k = " << beta_k << std::endl;
}
//--------------------------------------------------------------------
// Thin basis combination: out[i] = sum_j U[j] * R(j,i), i < ncol
//--------------------------------------------------------------------
void constructThin(std::vector<Field>& out, const std::vector<Field>& U,
const CMat& R, int ncol)
{
out.clear();
Field tmp(Grid_);
for (int i = 0; i < ncol; i++) {
tmp = Zero();
for (int j = 0; j < (int)U.size(); j++)
tmp += U[j] * R(j, i);
out.push_back(tmp);
}
}
};
NAMESPACE_END(Grid);
#endif // GRID_TRUE_HARMONIC_BLOCKED_KRYLOV_SCHUR_H
+9 -6
View File
@@ -97,7 +97,7 @@ public:
RealD scale;
ConjugateGradient<FineField> CG(1.0e-3,400,false);
ConjugateGradient<FineField> CG(1.0e-4,2000,false);
FineField noise(FineGrid);
FineField Mn(FineGrid);
@@ -131,7 +131,10 @@ public:
RealD scale;
TrivialPrecon<FineField> simple_fine;
PrecGeneralisedConjugateResidualNonHermitian<FineField> GCR(0.001,30,DiracOp,simple_fine,12,12);
// PrecGeneralisedConjugateResidualNonHermitian<FineField> GCR(0.001,10,DiracOp,simple_fine,30,30);
// PrecGeneralisedConjugateResidualNonHermitian<FineField> GCR(0.001,10,DiracOp,simple_fine,12,12);
// PrecGeneralisedConjugateResidualNonHermitian<FineField> GCR(0.001,30,DiracOp,simple_fine,12,12);
PrecGeneralisedConjugateResidualNonHermitian<FineField> GCR(0.001,30,DiracOp,simple_fine,10,10);
FineField noise(FineGrid);
FineField src(FineGrid);
FineField guess(FineGrid);
@@ -146,16 +149,16 @@ public:
DiracOp.Op(noise,Mn); std::cout<<GridLogMessage << "noise ["<<b<<"] <n|Op|n> "<<innerProduct(noise,Mn)<<std::endl;
for(int i=0;i<2;i++){
for(int i=0;i<3;i++){
// void operator() (const Field &src, Field &psi){
#if 1
std::cout << GridLogMessage << " inverting on noise "<<std::endl;
if (i==0)std::cout << GridLogMessage << " inverting on noise "<<std::endl;
src = noise;
guess=Zero();
GCR(src,guess);
subspace[b] = guess;
#else
std::cout << GridLogMessage << " inverting on zero "<<std::endl;
if (i==0)std::cout << GridLogMessage << " inverting on zero "<<std::endl;
src=Zero();
guess = noise;
GCR(src,guess);
@@ -167,7 +170,7 @@ public:
}
DiracOp.Op(noise,Mn); std::cout<<GridLogMessage << "filtered["<<b<<"] <f|Op|f> "<<innerProduct(noise,Mn)<<std::endl;
DiracOp.Op(noise,Mn); std::cout<<GridLogMessage << "filtered["<<b<<"] <f|Op|f> "<<innerProduct(noise,Mn)<<" <f|OpDagOp|f>"<<norm2(Mn)<<std::endl;
subspace[b] = noise;
}
+7 -7
View File
@@ -108,7 +108,7 @@ public:
// very VERY rarely (Log, serial RNG) we need world without a grid
////////////////////////////////////////////////////////////////////////////////
static int RankWorld(void) ;
static void BroadcastWorld(int root,void* data, int bytes);
static void BroadcastWorld(int root,void* data, uint64_t bytes);
static void BarrierWorld(void);
////////////////////////////////////////////////////////////
@@ -175,27 +175,27 @@ public:
int dest,
void *recv,
int from,
int bytes,int dir);
uint64_t bytes,int dir);
void SendToRecvFrom(void *xmit,
int xmit_to_rank,
void *recv,
int recv_from_rank,
int bytes);
uint64_t bytes);
int IsOffNode(int rank);
double StencilSendToRecvFrom(void *xmit,
int xmit_to_rank,int do_xmit,
void *recv,
int recv_from_rank,int do_recv,
int bytes,int dir);
uint64_t bytes,int dir);
double StencilSendToRecvFromPrepare(std::vector<CommsRequest_t> &list,
void *xmit,
int xmit_to_rank,int do_xmit,
void *recv,
int recv_from_rank,int do_recv,
int xbytes,int rbytes,int dir);
uint64_t xbytes,uint64_t rbytes,int dir);
// Could do a PollHtoD and have a CommsMerge dependence
void StencilSendToRecvFromPollDtoH (std::vector<CommsRequest_t> &list);
@@ -206,7 +206,7 @@ public:
int xmit_to_rank,int do_xmit,
void *recv,void *recv_comp,
int recv_from_rank,int do_recv,
int xbytes,int rbytes,int dir);
uint64_t xbytes,uint64_t rbytes,int dir);
void StencilSendToRecvFromComplete(std::vector<CommsRequest_t> &waitall,int i);
@@ -220,7 +220,7 @@ public:
////////////////////////////////////////////////////////////
// Broadcast a buffer and composite larger
////////////////////////////////////////////////////////////
void Broadcast(int root,void* data, int bytes);
void Broadcast(int root,void* data, uint64_t bytes);
////////////////////////////////////////////////////////////
// All2All down one dimension
+20 -21
View File
@@ -342,23 +342,22 @@ void CartesianCommunicator::SendToRecvFromBegin(std::vector<MpiCommsRequest_t> &
int dest,
void *recv,
int from,
int bytes,int dir)
uint64_t bytes,int dir)
{
MPI_Request xrq;
MPI_Request rrq;
GRID_ASSERT(dest != _processor);
GRID_ASSERT(from != _processor);
int tag;
tag= dir+from*32;
int ierr=MPI_Irecv(recv, bytes, MPI_CHAR,from,tag,communicator,&rrq);
int ierr=MPI_Irecv(recv,(int)( bytes/sizeof(int32_t)), MPI_INT32_T,from,tag,communicator,&rrq);
GRID_ASSERT(ierr==0);
list.push_back(rrq);
tag= dir+_processor*32;
ierr =MPI_Isend(xmit, bytes, MPI_CHAR,dest,tag,communicator,&xrq);
ierr =MPI_Isend(xmit,(int)(bytes/sizeof(int32_t)), MPI_INT32_T,dest,tag,communicator,&xrq);
GRID_ASSERT(ierr==0);
list.push_back(xrq);
}
@@ -379,7 +378,7 @@ void CartesianCommunicator::SendToRecvFrom(void *xmit,
int dest,
void *recv,
int from,
int bytes)
uint64_t bytes)
{
std::vector<MpiCommsRequest_t> reqs(0);
@@ -392,8 +391,8 @@ void CartesianCommunicator::SendToRecvFrom(void *xmit,
// Give the CPU to MPI immediately; can use threads to overlap optionally
// printf("proc %d SendToRecvFrom %d bytes Sendrecv \n",_processor,bytes);
ierr=MPI_Sendrecv(xmit,bytes,MPI_CHAR,dest,myrank,
recv,bytes,MPI_CHAR,from, from,
ierr=MPI_Sendrecv(xmit,(int)(bytes/sizeof(int32_t)),MPI_INT32_T,dest,myrank,
recv,(int)(bytes/sizeof(int32_t)),MPI_INT32_T,from, from,
communicator,MPI_STATUS_IGNORE);
GRID_ASSERT(ierr==0);
@@ -403,7 +402,7 @@ double CartesianCommunicator::StencilSendToRecvFrom( void *xmit,
int dest, int dox,
void *recv,
int from, int dor,
int bytes,int dir)
uint64_t bytes,int dir)
{
std::vector<CommsRequest_t> list;
double offbytes = StencilSendToRecvFromPrepare(list,xmit,dest,dox,recv,from,dor,bytes,bytes,dir);
@@ -426,7 +425,7 @@ double CartesianCommunicator::StencilSendToRecvFromPrepare(std::vector<CommsRequ
int dest,int dox,
void *recv,
int from,int dor,
int xbytes,int rbytes,int dir)
uint64_t xbytes,uint64_t rbytes,int dir)
{
return 0.0; // Do nothing -- no preparation required
}
@@ -435,7 +434,7 @@ double CartesianCommunicator::StencilSendToRecvFromBegin(std::vector<CommsReques
int dest,int dox,
void *recv,void *recv_comp,
int from,int dor,
int xbytes,int rbytes,int dir)
uint64_t xbytes,uint64_t rbytes,int dir)
{
int ncomm =communicator_halo.size();
int commdir=dir%ncomm;
@@ -458,7 +457,7 @@ double CartesianCommunicator::StencilSendToRecvFromBegin(std::vector<CommsReques
if ( (gfrom ==MPI_UNDEFINED) || Stencil_force_mpi ) {
tag= dir+from*32;
// std::cout << " StencilSendToRecvFrom "<<dir<<" MPI_Irecv "<<std::hex<<recv<<std::dec<<std::endl;
ierr=MPI_Irecv(recv_comp, rbytes, MPI_CHAR,from,tag,communicator_halo[commdir],&rrq);
ierr=MPI_Irecv(recv_comp,(int)(rbytes/sizeof(int32_t)), MPI_INT32_T,from,tag,communicator_halo[commdir],&rrq);
GRID_ASSERT(ierr==0);
list.push_back(rrq);
off_node_bytes+=rbytes;
@@ -476,7 +475,7 @@ double CartesianCommunicator::StencilSendToRecvFromBegin(std::vector<CommsReques
if (dox) {
if ( (gdest == MPI_UNDEFINED) || Stencil_force_mpi ) {
tag= dir+_processor*32;
ierr =MPI_Isend(xmit_comp, xbytes, MPI_CHAR,dest,tag,communicator_halo[commdir],&xrq);
ierr =MPI_Isend(xmit_comp,(int)(xbytes/sizeof(int32_t)), MPI_INT32_T,dest,tag,communicator_halo[commdir],&xrq);
GRID_ASSERT(ierr==0);
list.push_back(xrq);
off_node_bytes+=xbytes;
@@ -543,7 +542,7 @@ double CartesianCommunicator::StencilSendToRecvFromPrepare(std::vector<CommsRequ
int dest,int dox,
void *recv,
int from,int dor,
int xbytes,int rbytes,int dir)
uint64_t xbytes,uint64_t rbytes,int dir)
{
/*
* Bring sequence from Stencil.h down to lower level.
@@ -584,7 +583,7 @@ double CartesianCommunicator::StencilSendToRecvFromPrepare(std::vector<CommsRequ
if ( (gfrom ==MPI_UNDEFINED) || Stencil_force_mpi ) {
tag= dir+from*32;
host_recv = this->HostBufferMalloc(rbytes);
ierr=MPI_Irecv(host_recv, rbytes, MPI_CHAR,from,tag,communicator_halo[commdir],&rrq);
ierr=MPI_Irecv(host_recv,(int)(rbytes/sizeof(int32_t)), MPI_INT32_T,from,tag,communicator_halo[commdir],&rrq);
GRID_ASSERT(ierr==0);
CommsRequest_t srq;
srq.PacketType = InterNodeRecv;
@@ -686,7 +685,7 @@ void CartesianCommunicator::StencilSendToRecvFromPollDtoH(std::vector<CommsReque
if ( acceleratorEventIsComplete(list[idx].ev) ) {
void *host_xmit = list[idx].host_buf;
uint32_t xbytes = list[idx].bytes;
uint64_t xbytes = list[idx].bytes;
int dest = list[idx].dest;
int tag = list[idx].tag;
int commdir = list[idx].commdir;
@@ -697,7 +696,7 @@ void CartesianCommunicator::StencilSendToRecvFromPollDtoH(std::vector<CommsReque
// std::cout << " DtoH is complete for index "<<idx<<" calling MPI_Isend "<<std::endl;
MPI_Request xrq;
int ierr =MPI_Isend(host_xmit, xbytes, MPI_CHAR,dest,tag,communicator_halo[commdir],&xrq);
int ierr =MPI_Isend(host_xmit, (int)(xbytes/sizeof(int32_t)), MPI_INT32_T,dest,tag,communicator_halo[commdir],&xrq);
GRID_ASSERT(ierr==0);
list[idx].req = xrq; // Update the MPI request in the list
@@ -718,7 +717,7 @@ double CartesianCommunicator::StencilSendToRecvFromBegin(std::vector<CommsReques
int dest,int dox,
void *recv,void *recv_comp,
int from,int dor,
int xbytes,int rbytes,int dir)
uint64_t xbytes,uint64_t rbytes,int dir)
{
int ncomm =communicator_halo.size();
int commdir=dir%ncomm;
@@ -884,11 +883,11 @@ void CartesianCommunicator::Barrier(void)
int ierr = MPI_Barrier(communicator);
GRID_ASSERT(ierr==0);
}
void CartesianCommunicator::Broadcast(int root,void* data, int bytes)
void CartesianCommunicator::Broadcast(int root,void* data,uint64_t bytes)
{
FlightRecorder::StepLog("Broadcast");
int ierr=MPI_Bcast(data,
bytes,
(int)bytes,
MPI_BYTE,
root,
communicator);
@@ -904,11 +903,11 @@ void CartesianCommunicator::BarrierWorld(void){
int ierr = MPI_Barrier(communicator_world);
GRID_ASSERT(ierr==0);
}
void CartesianCommunicator::BroadcastWorld(int root,void* data, int bytes)
void CartesianCommunicator::BroadcastWorld(int root,void* data, uint64_t bytes)
{
FlightRecorder::StepLog("BroadcastWorld");
int ierr= MPI_Bcast(data,
bytes,
(int)bytes,
MPI_BYTE,
root,
communicator_world);
+11 -10
View File
@@ -27,6 +27,8 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
/* END LEGAL */
#include <Grid/GridCore.h>
void GridAbort(void) { abort(); }
NAMESPACE_BEGIN(Grid);
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -34,7 +36,6 @@ NAMESPACE_BEGIN(Grid);
///////////////////////////////////////////////////////////////////////////////////////////////////
Grid_MPI_Comm CartesianCommunicator::communicator_world;
void GridAbort(void) { abort(); }
void CartesianCommunicator::Init(int *argc, char *** arv)
{
@@ -89,7 +90,7 @@ void CartesianCommunicator::SendToRecvFrom(void *xmit,
int dest,
void *recv,
int from,
int bytes)
uint64_t bytes)
{
GRID_ASSERT(0);
}
@@ -99,7 +100,7 @@ void CartesianCommunicator::SendToRecvFromBegin(std::vector<CommsRequest_t> &lis
int dest,
void *recv,
int from,
int bytes,int dir)
uint64_t bytes,int dir)
{
GRID_ASSERT(0);
}
@@ -115,8 +116,8 @@ void CartesianCommunicator::AllToAll(void *in,void *out,uint64_t words,uint64_t
int CartesianCommunicator::RankWorld(void){return 0;}
void CartesianCommunicator::Barrier(void){}
void CartesianCommunicator::Broadcast(int root,void* data, int bytes) {}
void CartesianCommunicator::BroadcastWorld(int root,void* data, int bytes) { }
void CartesianCommunicator::Broadcast(int root,void* data, uint64_t bytes) {}
void CartesianCommunicator::BroadcastWorld(int root,void* data, uint64_t bytes) { }
void CartesianCommunicator::BarrierWorld(void) { }
int CartesianCommunicator::RankFromProcessorCoor(Coordinate &coor) { return 0;}
void CartesianCommunicator::ProcessorCoorFromRank(int rank, Coordinate &coor){ coor = _processor_coor; }
@@ -132,7 +133,7 @@ double CartesianCommunicator::StencilSendToRecvFrom( void *xmit,
int xmit_to_rank,int dox,
void *recv,
int recv_from_rank,int dor,
int bytes, int dir)
uint64_t bytes, int dir)
{
return 2.0*bytes;
}
@@ -143,16 +144,16 @@ double CartesianCommunicator::StencilSendToRecvFromPrepare(std::vector<CommsRequ
int xmit_to_rank,int dox,
void *recv,
int recv_from_rank,int dor,
int xbytes,int rbytes, int dir)
uint64_t xbytes,uint64_t rbytes, int dir)
{
return 0.0;
}
double CartesianCommunicator::StencilSendToRecvFromBegin(std::vector<CommsRequest_t> &list,
void *xmit,
void *xmit, void *xmit_comp,
int xmit_to_rank,int dox,
void *recv,
void *recv, void *recv_comp,
int recv_from_rank,int dor,
int xbytes,int rbytes, int dir)
uint64_t xbytes,uint64_t rbytes, int dir)
{
return xbytes+rbytes;
}
+63
View File
@@ -49,6 +49,20 @@ template<class vobj> Lattice<vobj> Cshift(const Lattice<vobj> &rhs,int dimension
// Map to always positive shift modulo global full dimension.
shift = (shift+fd)%fd;
if( shift ==0 ) {
ret = rhs;
return ret;
}
//
// Potential easy fast cases:
// Shift is a multiple of the local lattice extent.
// Then need only to shift whole subvolumes
int L = rhs.Grid()->_ldimensions[dimension];
if ( (shift%L )==0 && !rhs.Grid()->CheckerBoarded(dimension) ) {
Cshift_simple(ret,rhs,dimension,shift);
return ret;
}
ret.Checkerboard() = rhs.Grid()->CheckerBoardDestination(rhs.Checkerboard(),shift,dimension);
// the permute type
@@ -73,6 +87,55 @@ template<class vobj> Lattice<vobj> Cshift(const Lattice<vobj> &rhs,int dimension
return ret;
}
template<class vobj> void Cshift_simple(Lattice<vobj>& ret,const Lattice<vobj> &rhs,int dimension,int shift)
{
GridBase *grid=rhs.Grid();
int comm_proc, xmit_to_rank, recv_from_rank;
int fd = rhs.Grid()->_fdimensions[dimension];
int rd = rhs.Grid()->_rdimensions[dimension];
int ld = rhs.Grid()->_ldimensions[dimension];
int pd = rhs.Grid()->_processors[dimension];
int simd_layout = rhs.Grid()->_simd_layout[dimension];
int comm_dim = rhs.Grid()->_processors[dimension] >1 ;
comm_proc = ((shift)/ld)%pd;
grid->ShiftedRanks(dimension,comm_proc,xmit_to_rank,recv_from_rank);
if(comm_dim) {
int64_t bytes = sizeof(vobj) * grid->oSites();
autoView(rhs_v , rhs, AcceleratorRead);
autoView(ret_v , ret, AcceleratorWrite);
void *send_buf = (void *)&rhs_v[0];
void *recv_buf = (void *)&ret_v[0];
#ifdef ACCELERATOR_AWARE_MPI
grid->SendToRecvFrom(send_buf,
xmit_to_rank,
recv_buf,
recv_from_rank,
bytes);
#else
static hostVector<vobj> hrhs; hrhs.resize(grid->oSites());
static hostVector<vobj> hret; hret.resize(grid->oSites());
void *hsend_buf = (void *)&hrhs[0];
void *hrecv_buf = (void *)&hret[0];
acceleratorCopyFromDevice(send_buf,hsend_buf,bytes);
grid->SendToRecvFrom(hsend_buf,
xmit_to_rank,
hrecv_buf,
recv_from_rank,
bytes);
acceleratorCopyToDevice(hrecv_buf,recv_buf,bytes);
#endif
}
}
template<class vobj> void Cshift_comms(Lattice<vobj>& ret,const Lattice<vobj> &rhs,int dimension,int shift)
{
int sshift[2];
+2 -1
View File
@@ -260,7 +260,8 @@ class GridLimeReader : public BinaryIO {
<< " / field= " << n2ck << " / rdiff= " << GRID_FIELD_NORM_CALC(FieldNormMetaData_,n2ck) << std::endl;
GRID_FIELD_NORM_CHECK(FieldNormMetaData_,n2ck);
}
assert(scidacChecksumVerify(scidacChecksum_,scidac_csuma,scidac_csumb)==1);
// assert(scidacChecksumVerify(scidacChecksum_,scidac_csuma,scidac_csumb)==1);
scidacChecksumVerify(scidacChecksum_,scidac_csuma,scidac_csumb);
// find out if next field is a GridFieldNorm
return;
+1 -1
View File
@@ -122,7 +122,7 @@ public:
field.checksum = std::stoul(header["CHECKSUM"],0,16);
field.ensemble_id = header["ENSEMBLE_ID"];
field.ensemble_label = header["ENSEMBLE_LABEL"];
field.sequence_number = std::stol(header["SEQUENCE_NUMBER"]);
// field.sequence_number = std::stol(header["SEQUENCE_NUMBER"]);
field.creator = header["CREATOR"];
field.creator_hardware = header["CREATOR_HARDWARE"];
field.creation_date = header["CREATION_DATE"];
+4
View File
@@ -60,12 +60,16 @@ inline std::ostream& operator<< (std::ostream & stream, const GridSecs & time)
}
inline std::ostream& operator<< (std::ostream & stream, const GridMillisecs & now)
{
double secs = 1.0*now.count()*1.0e-3;
stream << secs<<" s";
/*
GridSecs second(1);
auto secs = now/second ;
auto subseconds = now%second ;
auto fill = stream.fill();
stream << secs<<"."<<std::setw(3)<<std::setfill('0')<<subseconds.count()<<" s";
stream.fill(fill);
*/
return stream;
}
inline std::ostream& operator<< (std::ostream & stream, const GridUsecs & now)
+18
View File
@@ -596,16 +596,32 @@ template<int Index,class vobj> inline vobj transposeColour(const vobj &lhs){
//////////////////////////////////////////
// Trace lattice and non-lattice
//////////////////////////////////////////
#define GRID_UNOP(name) name
#define GRID_DEF_UNOP(op, name) \
template <typename T1, typename std::enable_if<is_lattice<T1>::value||is_lattice_expr<T1>::value,T1>::type * = nullptr> \
inline auto op(const T1 &arg) ->decltype(LatticeUnaryExpression<GRID_UNOP(name),T1>(GRID_UNOP(name)(), arg)) \
{ \
return LatticeUnaryExpression<GRID_UNOP(name),T1>(GRID_UNOP(name)(), arg); \
}
template<int Index,class vobj>
inline auto traceSpin(const Lattice<vobj> &lhs) -> Lattice<decltype(traceIndex<SpinIndex>(vobj()))>
{
return traceIndex<SpinIndex>(lhs);
}
GridUnopClass(UnaryTraceSpin, traceIndex<SpinIndex>(a));
GRID_DEF_UNOP(traceSpin, UnaryTraceSpin);
template<int Index,class vobj>
inline auto traceColour(const Lattice<vobj> &lhs) -> Lattice<decltype(traceIndex<ColourIndex>(vobj()))>
{
return traceIndex<ColourIndex>(lhs);
}
GridUnopClass(UnaryTraceColour, traceIndex<ColourIndex>(a));
GRID_DEF_UNOP(traceColour, UnaryTraceColour);
template<int Index,class vobj>
inline auto traceSpin(const vobj &lhs) -> Lattice<decltype(traceIndex<SpinIndex>(lhs))>
{
@@ -617,6 +633,8 @@ inline auto traceColour(const vobj &lhs) -> Lattice<decltype(traceIndex<ColourIn
return traceIndex<ColourIndex>(lhs);
}
#undef GRID_UNOP
#undef GRID_DEF_UNOP
//////////////////////////////////////////
// Current types
//////////////////////////////////////////
@@ -411,7 +411,7 @@ void WilsonKernels<Impl>::DhopDirKernel( StencilImpl &st, DoubledGaugeField &U,S
#undef LoopBody
}
#ifdef GRID_SYCL
#if 0
extern "C" {
ulong SYCL_EXTERNAL __attribute__((overloadable)) intel_get_cycle_counter( void );
uint SYCL_EXTERNAL __attribute__((overloadable)) intel_get_active_channel_mask( void );
+8 -3
View File
@@ -138,10 +138,13 @@ public:
//auto start = std::chrono::high_resolution_clock::now();
autoView(U_v,U,AcceleratorWrite);
autoView(P_v,P,AcceleratorRead);
accelerator_for(ss, P.Grid()->oSites(),1,{
typedef typename Field::vector_object vobj;
const int Nsimd = vobj::Nsimd();
accelerator_for(ss, P.Grid()->oSites(),Nsimd,{
for (int mu = 0; mu < Nd; mu++) {
U_v[ss](mu) = Exponentiate(P_v[ss](mu), ep, Nexp) * U_v[ss](mu);
U_v[ss](mu) = Group::ProjectOnGeneralGroup(U_v[ss](mu));
auto tmp = Exponentiate(P_v(ss)(mu), ep, Nexp) * U_v(ss)(mu);
tmp = Group::ProjectOnGeneralGroup(tmp);
coalescedWrite(U_v[ss](mu),tmp);
}
});
//auto end = std::chrono::high_resolution_clock::now();
@@ -176,6 +179,8 @@ public:
Group::ColdConfiguration(pRNG, U);
}
static const int num_colours = Group::Dimension;
};
+12
View File
@@ -103,6 +103,18 @@ class PolyakovMod: public ObservableModule<PolyakovLogger<Impl>, NoParameters>{
PolyakovMod(): ObsBase(NoParameters()){}
};
template < class Impl >
class SpatialPolyakovMod: public ObservableModule<SpatialPolyakovLogger<Impl>, NoParameters>{
typedef ObservableModule<SpatialPolyakovLogger<Impl>, NoParameters> ObsBase;
using ObsBase::ObsBase; // for constructors
// acquire resource
virtual void initialize(){
this->ObservablePtr.reset(new SpatialPolyakovLogger<Impl>());
}
public:
SpatialPolyakovMod(): ObsBase(NoParameters()){}
};
template < class Impl >
class TopologicalChargeMod: public ObservableModule<TopologicalCharge<Impl>, TopologyObsParameters>{
+42 -2
View File
@@ -2,11 +2,12 @@
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/qcd/modules/polyakov_line.h
Source file: ./Grid/qcd/observables/polyakov_loop.h
Copyright (C) 2017
Copyright (C) 2025
Author: David Preti <david.preti@csic.es>
Author: Alexis Verney-Provatas <2414441@swansea.ac.uk>
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
@@ -60,4 +61,43 @@ class PolyakovLogger : public HmcObservable<typename Impl::Field> {
}
};
template <class Impl>
class SpatialPolyakovLogger : public HmcObservable<typename Impl::Field> {
public:
// here forces the Impl to be of gauge fields
// if not the compiler will complain
INHERIT_GIMPL_TYPES(Impl);
// necessary for HmcObservable compatibility
typedef typename Impl::Field Field;
void TrajectoryComplete(int traj,
Field &U,
GridSerialRNG &sRNG,
GridParallelRNG &pRNG) {
// Save current numerical output precision
int def_prec = std::cout.precision();
// Assume that the dimensions are D=3+1
int Ndim = 3;
ComplexD polyakov;
// Iterate over the spatial directions and print the average spatial polyakov loop
// over them
for (int idx=0; idx<Ndim; idx++) {
polyakov = WilsonLoops<Impl>::avgPolyakovLoop(U, idx);
std::cout << GridLogMessage
<< std::setprecision(std::numeric_limits<Real>::digits10 + 1)
<< "Polyakov Loop in the " << idx << " spatial direction : [ " << traj << " ] "<< polyakov << std::endl;
}
// Return to original output precision
std::cout.precision(def_prec);
}
};
NAMESPACE_END(Grid);
+7 -4
View File
@@ -291,8 +291,8 @@ public:
int idx=0;
for(int mu=0;mu<4;mu++){
for(int nu=0;nu<4;nu++){
if ( mu!=nu) GRID_ASSERT(this->StoutSmearing->SmearRho[idx]==rho);
else GRID_ASSERT(this->StoutSmearing->SmearRho[idx]==0.0);
if ( mu!=nu) assert(this->StoutSmearing->SmearRho[idx]==rho);
else assert(this->StoutSmearing->SmearRho[idx]==0.0);
idx++;
}}
//////////////////////////////////////////////////////////////////
@@ -825,6 +825,7 @@ public:
virtual void fill_smearedSet(GaugeField &U)
{
this->ThinLinks = &U; // attach the smearing routine to the field U
std::cout << GridLogMessage << " fill_smearedSet " << WilsonLoops<PeriodicGimplR>::avgPlaquette(U) << std::endl;
// check the pointer is not null
if (this->ThinLinks == NULL)
@@ -846,6 +847,8 @@ public:
ApplyMask(smeared_A,smearLvl);
smeared_B = previous_u;
ApplyMask(smeared_B,smearLvl);
std::cout << GridLogMessage << " smeared_A " << norm2(smeared_A) << std::endl;
std::cout << GridLogMessage << " smeared_B " << norm2(smeared_B) << std::endl;
// Replace only the masked portion
this->SmearedSet[smearLvl] = previous_u-smeared_B + smeared_A;
previous_u = this->SmearedSet[smearLvl];
@@ -934,10 +937,10 @@ public:
SmearedConfigurationMasked(GridCartesian* _UGrid, unsigned int Nsmear, Smear_Stout<Gimpl>& Stout)
: SmearedConfiguration<Gimpl>(_UGrid, Nsmear,Stout)
{
GRID_ASSERT(Nsmear%(2*Nd)==0); // Or multiply by 8??
assert(Nsmear%(2*Nd)==0); // Or multiply by 8??
// was resized in base class
GRID_ASSERT(this->SmearedSet.size()==Nsmear);
assert(this->SmearedSet.size()==Nsmear);
GridRedBlackCartesian * UrbGrid;
UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(_UGrid);
+1 -1
View File
@@ -54,7 +54,7 @@ public:
// Usual cases are not used
//////////////////////////////////
virtual void refresh(const GaugeField &U, GridSerialRNG &sRNG, GridParallelRNG &pRNG){ GRID_ASSERT(0);};
virtual RealD S(const GaugeField &U) { GRID_ASSERT(0); }
virtual RealD S(const GaugeField &U) { GRID_ASSERT(0); return 0; }
virtual void deriv(const GaugeField &U, GaugeField &dSdU) { GRID_ASSERT(0); }
//////////////////////////////////
+17 -3
View File
@@ -51,10 +51,16 @@ protected:
public:
//Define the action used to evolve the plaquettes
//(Lüscher: https://arxiv.org/pdf/1006.4518 eq. 1.4)
//V'(t) = -g^2 * ( d/dVt S[Vt](g) ) * Vt
// = -g^2 * ( d/dVt (1/g^2 * sum_p Re tr{ 1 - Vt(p) } ) ) * Vt
// = - d/dVt ( sum_p ( Nc - Re tr Vt(p) ) * Vt
// = - d/dVt ( Nc * sum_p ( 1 - Re tr Vt(p)/Nc ) ) * Vt
// = - d/dVt SG[Vt](Nc) * Vt
explicit WilsonFlowBase(unsigned int meas_interval =1) {
SG = (ActionBase *) new WilsonGaugeAction<Gimpl>(3.0);
// WilsonGaugeAction with beta 3.0
SG = (ActionBase *) new WilsonGaugeAction<Gimpl>(Gimpl::num_colours);
setDefaultMeasurements(meas_interval);
}
@@ -149,9 +155,17 @@ public:
////////////////////////////////////////////////////////////////////////////////
// Implementations
////////////////////////////////////////////////////////////////////////////////
//Compute t^2 <E(t)> for time from the plaquette form
//(Lüscher: https://arxiv.org/pdf/1006.4518 eq. 3.1)
//E(t) = 2 * sum_p Retr{ 1 - Vt(p) } =
// = 2 * sum_p ( Nc - Retr Vt(p) ) =
// = 2 * Nc * sum_p ( 1 - Retr Vt(p)/Nc )
// = 2 * SG[Vt](Nc)
//We divide by the volume to get an energy density per site, as is convention
template <class Gimpl>
RealD WilsonFlowBase<Gimpl>::energyDensityPlaquette(const RealD t, const GaugeField& U){
static WilsonGaugeAction<Gimpl> SG(3.0);
static WilsonGaugeAction<Gimpl> SG(Gimpl::num_colours);
return 2.0 * t * t * SG.S(U)/U.Grid()->gSites();
}
+3 -3
View File
@@ -254,9 +254,9 @@ static void testGenerators(GroupName::Sp) {
}
}
template <int N>
static Lattice<iScalar<iScalar<iMatrix<vComplexD, N> > > >
ProjectOnGeneralGroup(const Lattice<iScalar<iScalar<iMatrix<vComplexD, N> > > > &Umu, GroupName::Sp) {
template <class vtype, int N>
static Lattice<iScalar<iScalar<iMatrix<vtype, N> > > >
ProjectOnGeneralGroup(const Lattice<iScalar<iScalar<iMatrix<vtype, N> > > > &Umu, GroupName::Sp) {
return ProjectOnSpGroup(Umu);
}
+26 -8
View File
@@ -177,25 +177,43 @@ public:
}
//////////////////////////////////////////////////
// average over all x,y,z the temporal loop
// average Polyakov loop in mu direction over all directions != mu
//////////////////////////////////////////////////
static ComplexD avgPolyakovLoop(const GaugeField &Umu) { //assume Nd=4
GaugeMat Ut(Umu.Grid()), P(Umu.Grid());
static ComplexD avgPolyakovLoop(const GaugeField &Umu, const int mu) { //assume Nd=4
// Protect against bad value of mu [0, 3]
if ((mu < 0 ) || (mu > 3)) {
std::cout << GridLogError << "Index is not an integer inclusively between 0 and 3." << std::endl;
exit(1);
}
// U_loop is U_{mu}
GaugeMat U_loop(Umu.Grid()), P(Umu.Grid());
ComplexD out;
int T = Umu.Grid()->GlobalDimensions()[3];
int X = Umu.Grid()->GlobalDimensions()[0];
int Y = Umu.Grid()->GlobalDimensions()[1];
int Z = Umu.Grid()->GlobalDimensions()[2];
Ut = peekLorentz(Umu,3); //Select temporal direction
P = Ut;
for (int t=1;t<T;t++){
P = Gimpl::CovShiftForward(Ut,3,P);
// Number of sites in mu direction
int N_mu = Umu.Grid()->GlobalDimensions()[mu];
U_loop = peekLorentz(Umu, mu); //Select direction
P = U_loop;
for (int t=1;t<N_mu;t++){
P = Gimpl::CovShiftForward(U_loop,mu,P);
}
RealD norm = 1.0/(Nc*X*Y*Z*T);
out = sum(trace(P))*norm;
return out;
}
}
/////////////////////////////////////////////////
// overload for temporal Polyakov loop
/////////////////////////////////////////////////
static ComplexD avgPolyakovLoop(const GaugeField &Umu) {
return avgPolyakovLoop(Umu, 3);
}
//////////////////////////////////////////////////
// average over traced single links
+6 -4
View File
@@ -751,7 +751,7 @@ public:
obj.xbytes = xbytes;
obj.rbytes = rbytes;
obj.cb = cb;
for(int i=0;i<CachedTransfers.size();i++){
if ( (CachedTransfers[i].direction ==direction)
&&(CachedTransfers[i].OrthogPlane==OrthogPlane)
@@ -763,11 +763,13 @@ public:
){
// FIXME worry about duplicate with partial compression
// Wont happen as DWF has no duplicates, but...
AddCopy(CachedTransfers[i].recv_buf,recv_buf,rbytes);
return 1;
// AddCopy(CachedTransfers[i].recv_buf,recv_buf,rbytes);
// std::cout << "Duplicate dir " <<direction<<" "<<" OrthogPlane "<<OrthogPlane<<" Dest"<<DestProc <<" xbytes " <<xbytes<<" lane "<< lane<<" cb "<<cb<<std::endl;
return 0;
// return 1;
}
}
CachedTransfers.push_back(obj);
return 0;
}
+5
View File
@@ -28,6 +28,11 @@ Author: paboyle <paboyle@ph.ed.ac.uk>
/* END LEGAL */
#pragma once
#ifndef MIN
#define MIN(x,y) ((x)>(y)?(y):(x))
#endif
// Introduce a class to gain deterministic bit reproducible reduction.
// make static; perhaps just a namespace is required.
NAMESPACE_BEGIN(Grid);
+6 -1
View File
@@ -24,7 +24,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
#if Nc == 3
#include <Grid/qcd/smearing/GaugeConfigurationMasked.h>
@@ -230,3 +234,4 @@ int main(int argc, char **argv)
#endif
} // main
#endif
+6 -3
View File
@@ -25,7 +25,11 @@ directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
#if Nc == 3
#include <Grid/qcd/smearing/GaugeConfigurationMasked.h>
@@ -231,5 +235,4 @@ int main(int argc, char **argv)
#endif
} // main
#endif
+6 -3
View File
@@ -24,7 +24,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
#if Nc == 3
#include <Grid/qcd/smearing/GaugeConfigurationMasked.h>
@@ -230,5 +234,4 @@ int main(int argc, char **argv)
#endif
} // main
#endif
+6 -3
View File
@@ -27,7 +27,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
int main(int argc, char **argv) {
using namespace Grid;
@@ -195,5 +199,4 @@ int main(int argc, char **argv) {
Grid_finalize();
} // main
#endif
+6 -3
View File
@@ -28,7 +28,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
#ifdef GRID_DEFAULT_PRECISION_DOUBLE
#define MIXED_PRECISION
@@ -449,5 +453,4 @@ int main(int argc, char **argv) {
Grid_finalize();
} // main
#endif
+6 -3
View File
@@ -28,7 +28,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
#ifdef GRID_DEFAULT_PRECISION_DOUBLE
#define MIXED_PRECISION
@@ -442,5 +446,4 @@ int main(int argc, char **argv) {
Grid_finalize();
} // main
#endif
+7 -1
View File
@@ -28,7 +28,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
using namespace Grid;
@@ -918,3 +922,5 @@ int main(int argc, char **argv) {
return 0;
#endif
} // main
#endif
+7 -1
View File
@@ -28,7 +28,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
using namespace Grid;
@@ -873,3 +877,5 @@ int main(int argc, char **argv) {
return 0;
#endif
} // main
#endif
+6 -3
View File
@@ -27,7 +27,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
int main(int argc, char **argv) {
using namespace Grid;
@@ -193,5 +197,4 @@ int main(int argc, char **argv) {
Grid_finalize();
} // main
#endif
+6 -3
View File
@@ -27,7 +27,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
NAMESPACE_BEGIN(Grid);
@@ -512,5 +516,4 @@ int main(int argc, char **argv) {
Grid_finalize();
} // main
#endif
+6 -3
View File
@@ -27,7 +27,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
int main(int argc, char **argv) {
using namespace Grid;
@@ -345,5 +349,4 @@ int main(int argc, char **argv) {
Grid_finalize();
} // main
#endif
+6 -3
View File
@@ -27,7 +27,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
NAMESPACE_BEGIN(Grid);
@@ -516,5 +520,4 @@ int main(int argc, char **argv) {
Grid_finalize();
} // main
#endif
+6 -3
View File
@@ -27,7 +27,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
NAMESPACE_BEGIN(Grid);
@@ -567,5 +571,4 @@ int main(int argc, char **argv) {
Grid_finalize();
} // main
#endif
+6 -3
View File
@@ -27,7 +27,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
int main(int argc, char **argv) {
using namespace Grid;
@@ -263,5 +267,4 @@ int main(int argc, char **argv) {
Grid_finalize();
} // main
#endif
+6 -3
View File
@@ -27,7 +27,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
int main(int argc, char **argv) {
using namespace Grid;
@@ -417,5 +421,4 @@ int main(int argc, char **argv) {
Grid_finalize();
} // main
#endif
+6 -3
View File
@@ -27,7 +27,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
NAMESPACE_BEGIN(Grid);
@@ -452,5 +456,4 @@ int main(int argc, char **argv) {
Grid_finalize();
} // main
#endif
+6 -3
View File
@@ -27,7 +27,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
NAMESPACE_BEGIN(Grid);
@@ -462,5 +466,4 @@ int main(int argc, char **argv) {
Grid_finalize();
} // main
#endif
+6 -3
View File
@@ -27,7 +27,11 @@ See the full license in the file "LICENSE" in the top level distribution
directory
*************************************************************************************/
/* END LEGAL */
#include <Grid/Grid.h>
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include<Grid/Grid.h>
@@ -264,5 +268,4 @@ int main(int argc, char **argv) {
Grid_finalize();
} // main
#endif
@@ -0,0 +1,16 @@
#include <Grid/Grid.h>
#pragma once
#ifndef ENABLE_FERMION_INSTANTIATIONS
#include <iostream>
int main(void) {
std::cout << "This build of Grid was configured to exclude fermion instantiations, "
<< "which this example relies on. "
<< "Please reconfigure and rebuild Grid with --enable-fermion-instantiations"
<< "to run this example."
<< std::endl;
return 1;
}
#endif
+5
View File
@@ -26,6 +26,9 @@ Author: paboyle <paboyle@ph.ed.ac.uk>
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_benchmarks_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace Grid;
@@ -731,3 +734,5 @@ int main (int argc, char ** argv)
Grid_finalize();
}
#endif
+4
View File
@@ -20,6 +20,9 @@
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_benchmarks_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
#ifdef GRID_CUDA
#define CUDA_PROFILE
@@ -439,3 +442,4 @@ void Benchmark(int Ls, Coordinate Dirichlet,bool sloppy)
GRID_ASSERT(norm2(src_e)<1.0e-4);
GRID_ASSERT(norm2(src_o)<1.0e-4);
}
#endif
+6
View File
@@ -20,6 +20,10 @@
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_benchmarks_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
#ifdef GRID_CUDA
#define CUDA_PROFILE
@@ -439,3 +443,5 @@ void Benchmark(int Ls, Coordinate Dirichlet,bool sloppy)
GRID_ASSERT(norm2(src_e)<1.0e-4);
GRID_ASSERT(norm2(src_o)<1.0e-4);
}
#endif
@@ -20,6 +20,9 @@
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_benchmarks_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
#ifdef GRID_CUDA
#define CUDA_PROFILE
@@ -385,3 +388,5 @@ int main (int argc, char ** argv)
Grid_finalize();
exit(0);
}
#endif
+4 -2
View File
@@ -26,6 +26,9 @@ Author: paboyle <paboyle@ph.ed.ac.uk>
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_benchmarks_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace std;
@@ -238,5 +241,4 @@ void benchDw(std::vector<int> & latt4, int Ls, int threads,int report )
}
}
#endif
+5
View File
@@ -1,3 +1,7 @@
#include "disable_benchmarks_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
#include <sstream>
using namespace std;
@@ -155,3 +159,4 @@ int main (int argc, char ** argv)
Grid_finalize();
}
#endif
+5
View File
@@ -20,6 +20,9 @@
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_benchmarks_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
#ifdef GRID_CUDA
#define CUDA_PROFILE
@@ -129,3 +132,5 @@ int main (int argc, char ** argv)
Grid_finalize();
exit(0);
}
#endif
+5
View File
@@ -26,6 +26,9 @@ Author: paboyle <paboyle@ph.ed.ac.uk>
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_benchmarks_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace std;
@@ -149,3 +152,5 @@ int main (int argc, char ** argv)
Grid_finalize();
}
#endif
+4 -2
View File
@@ -26,6 +26,9 @@ Author: paboyle <paboyle@ph.ed.ac.uk>
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_benchmarks_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace std;
@@ -172,5 +175,4 @@ void benchDw(std::vector<int> & latt4, int Ls)
// Dw.Report();
}
#endif
+5
View File
@@ -26,6 +26,9 @@ Author: paboyle <paboyle@ph.ed.ac.uk>
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_benchmarks_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace std;
@@ -110,3 +113,5 @@ int main (int argc, char ** argv)
Grid_finalize();
}
#endif
+5
View File
@@ -26,6 +26,9 @@ Author: paboyle <paboyle@ph.ed.ac.uk>
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_benchmarks_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace std;
@@ -112,3 +115,5 @@ int main (int argc, char ** argv)
Grid_finalize();
}
#endif
+7 -1
View File
@@ -26,6 +26,10 @@ Author: paboyle <paboyle@ph.ed.ac.uk>
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_benchmarks_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
#include <Grid/algorithms/blas/BatchedBlas.h>
@@ -873,7 +877,7 @@ int main (int argc, char ** argv)
int do_su4=0;
int do_memory=1;
int do_comms =1;
int do_blas =1;
int do_blas =0;
int do_dslash=1;
int sel=4;
@@ -978,3 +982,5 @@ int main (int argc, char ** argv)
Grid_finalize();
fclose(FP);
}
#endif
+5
View File
@@ -26,6 +26,9 @@ Author: paboyle <paboyle@ph.ed.ac.uk>
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_benchmarks_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace std;
@@ -258,3 +261,5 @@ int main (int argc, char ** argv)
Grid_finalize();
}
#endif
+5
View File
@@ -19,6 +19,9 @@ Author: Richard Rollins <rprollins@users.noreply.github.com>
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_benchmarks_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace std;
@@ -161,3 +164,5 @@ void bench_wilson_eo (
double flops = (single_site_flops * volume * ncall)/2.0;
std::cout << flops/(t1-t0) << "\t\t";
}
#endif
@@ -0,0 +1,16 @@
#include <Grid/Grid.h>
#pragma once
#ifndef ENABLE_FERMION_INSTANTIATIONS
#include <iostream>
int main(void) {
std::cout << "This build of Grid was configured to exclude fermion instantiations, "
<< "which this benchmark relies on. "
<< "Please reconfigure and rebuild Grid with --enable-fermion-instantiations"
<< "to run this benchmark."
<< std::endl;
return 1;
}
#endif
+11 -1
View File
@@ -172,6 +172,12 @@ case ${ac_TRACING} in
esac
############### fermions
AC_ARG_ENABLE([fermion-instantiations],
[AS_HELP_STRING([--enable-fermion-instantiations=yes|no],[enable fermion instantiations])],
[ac_FERMION_INSTANTIATIONS=${enable_fermion_instantiations}], [ac_FERMION_INSTANTIATIONS=yes])
AM_CONDITIONAL(BUILD_FERMION_INSTANTIATIONS, [ test "${ac_FERMION_INSTANTIATIONS}X" == "yesX" ])
AC_ARG_ENABLE([fermion-reps],
[AS_HELP_STRING([--enable-fermion-reps=yes|no],[enable extra fermion representation support])],
[ac_FERMION_REPS=${enable_fermion_reps}], [ac_FERMION_REPS=yes])
@@ -194,6 +200,9 @@ AM_CONDITIONAL(BUILD_ZMOBIUS, [ test "${ac_ZMOBIUS}X" == "yesX" ])
case ${ac_FERMION_REPS} in
yes) AC_DEFINE([ENABLE_FERMION_REPS],[1],[non QCD fermion reps]);;
esac
case ${ac_FERMION_INSTANTIATIONS} in
yes) AC_DEFINE([ENABLE_FERMION_INSTANTIATIONS],[1],[enable fermions]);;
esac
case ${ac_GPARITY} in
yes) AC_DEFINE([ENABLE_GPARITY],[1],[fermion actions with GPARITY BCs]);;
esac
@@ -292,13 +301,14 @@ AC_ARG_ENABLE([accelerator],
case ${ac_ACCELERATOR} in
cuda)
echo CUDA acceleration
LIBS="${LIBS} -lcuda -lcublas"
LIBS="${LIBS} -lcuda -lcublas -lcufft"
AC_DEFINE([GRID_CUDA],[1],[Use CUDA offload]);;
sycl)
echo SYCL acceleration
AC_DEFINE([GRID_SYCL],[1],[Use SYCL offload]);;
hip)
echo HIP acceleration
LIBS="${LIBS} -lhipblas -lrocblas -lhipfft"
AC_DEFINE([GRID_HIP],[1],[Use HIP offload]);;
none)
echo NO acceleration ;;
+4 -2
View File
@@ -3,6 +3,9 @@
* without regression / tests being applied
*/
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace std;
@@ -310,5 +313,4 @@ int main (int argc, char ** argv)
Grid_finalize();
}
#endif
+430
View File
@@ -0,0 +1,430 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./tests/Test_padded_cell.cc
Copyright (C) 2023
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
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 */
// copied here from Test_general_coarse_pvdagm.cc
#include <cstdlib>
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidual.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
#include <Grid/algorithms/iterative/BiCGSTAB.h>
using namespace std;
using namespace Grid;
// Hermitize a DWF operator by squaring it
template<class Matrix,class Field>
class SquaredLinearOperator : public LinearOperatorBase<Field> {
public:
Matrix &_Mat;
public:
SquaredLinearOperator(Matrix &Mat): _Mat(Mat) {};
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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
// std::cout << "Op is overloaded as HermOp" << std::endl;
HermOp(in, out);
}
void AdjOp (const Field &in, Field &out){
HermOp(in, out);
}
void _Op (const Field &in, Field &out){
// std::cout << "Op: M "<<std::endl;
_Mat.M(in, out);
}
void _AdjOp (const Field &in, Field &out){
// std::cout << "AdjOp: Mdag "<<std::endl;
_Mat.Mdag(in, out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
// std::cout << "HermOp: Mdag M Mdag M"<<std::endl;
Field tmp(in.Grid());
_Op(in,tmp);
_AdjOp(tmp,out);
}
};
template<class Matrix,class Field>
class PVdagMLinearOperator : public LinearOperatorBase<Field> {
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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(in,tmp);
_Mat.Mdag(tmp,out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
// _Mat.M(in,tmp);
// _PV.Mdag(tmp,out);
// _PV.M(out,tmp);
// _Mat.Mdag(tmp,out);
Op(in,tmp);
AdjOp(tmp,out);
// std::cout << "HermOp done "<<norm2(out)<<std::endl;
}
};
template<class Matrix,class Field>
class ShiftedPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
RealD shift;
public:
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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
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){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
};
template<class Matrix, class Field>
class ShiftedComplexPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
ComplexD shift;
public:
ShiftedComplexPVdagMLinearOperator(ComplexD _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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
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){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
void resetShift(ComplexD newShift) {
shift = newShift;
}
};
template<class Fobj,class CComplex,int nbasis>
class MGPreconditioner : public LinearFunction< Lattice<Fobj> > {
public:
using LinearFunction<Lattice<Fobj> >::operator();
typedef Aggregation<Fobj,CComplex,nbasis> Aggregates;
typedef typename Aggregation<Fobj,CComplex,nbasis>::FineField FineField;
typedef typename Aggregation<Fobj,CComplex,nbasis>::CoarseVector CoarseVector;
typedef typename Aggregation<Fobj,CComplex,nbasis>::CoarseMatrix CoarseMatrix;
typedef LinearOperatorBase<FineField> FineOperator;
typedef LinearFunction <FineField> FineSmoother;
typedef LinearOperatorBase<CoarseVector> CoarseOperator;
typedef LinearFunction <CoarseVector> CoarseSolver;
Aggregates & _Aggregates;
FineOperator & _FineOperator;
FineSmoother & _PreSmoother;
FineSmoother & _PostSmoother;
CoarseOperator & _CoarseOperator;
CoarseSolver & _CoarseSolve;
int level; void Level(int lv) {level = lv; };
MGPreconditioner(Aggregates &Agg,
FineOperator &Fine,
FineSmoother &PreSmoother,
FineSmoother &PostSmoother,
CoarseOperator &CoarseOperator_,
CoarseSolver &CoarseSolve_)
: _Aggregates(Agg),
_FineOperator(Fine),
_PreSmoother(PreSmoother),
_PostSmoother(PostSmoother),
_CoarseOperator(CoarseOperator_),
_CoarseSolve(CoarseSolve_),
level(1) { }
virtual void operator()(const FineField &in, FineField & out)
{
GridBase *CoarseGrid = _Aggregates.CoarseGrid;
// auto CoarseGrid = _CoarseOperator.Grid();
CoarseVector Csrc(CoarseGrid);
CoarseVector Csol(CoarseGrid);
FineField vec1(in.Grid());
FineField vec2(in.Grid());
std::cout<<GridLogMessage << "Calling PreSmoother " <<std::endl;
// std::cout<<GridLogMessage << "Calling PreSmoother input residual "<<norm2(in) <<std::endl;
double t;
// Fine Smoother
// out = in;
out = Zero();
t=-usecond();
_PreSmoother(in,out);
t+=usecond();
std::cout<<GridLogMessage << "PreSmoother took "<< t/1000.0<< "ms" <<std::endl;
// Update the residual
_FineOperator.Op(out,vec1); sub(vec1, in ,vec1);
// std::cout<<GridLogMessage <<"Residual-1 now " <<norm2(vec1)<<std::endl;
// Fine to Coarse
t=-usecond();
_Aggregates.ProjectToSubspace (Csrc,vec1);
t+=usecond();
std::cout<<GridLogMessage << "Project to coarse took "<< t/1000.0<< "ms" <<std::endl;
// Coarse correction
t=-usecond();
Csol = Zero();
_CoarseSolve(Csrc,Csol);
//Csol=Zero();
t+=usecond();
std::cout<<GridLogMessage << "Coarse solve took "<< t/1000.0<< "ms" <<std::endl;
// Coarse to Fine
t=-usecond();
// _CoarseOperator.PromoteFromSubspace(_Aggregates,Csol,vec1);
_Aggregates.PromoteFromSubspace(Csol,vec1);
add(out,out,vec1);
t+=usecond();
std::cout<<GridLogMessage << "Promote to this level took "<< t/1000.0<< "ms" <<std::endl;
// Residual
_FineOperator.Op(out,vec1); sub(vec1 ,in , vec1);
// std::cout<<GridLogMessage <<"Residual-2 now " <<norm2(vec1)<<std::endl;
// Fine Smoother
t=-usecond();
// vec2=vec1;
vec2=Zero();
_PostSmoother(vec1,vec2);
t+=usecond();
std::cout<<GridLogMessage << "PostSmoother took "<< t/1000.0<< "ms" <<std::endl;
add( out,out,vec2);
std::cout<<GridLogMessage << "Done " <<std::endl;
}
};
int main (int argc, char ** argv)
{
Grid_init(&argc,&argv);
const int Ls=16;
// GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(), GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());
std::vector<int> lat_size {16, 16, 16, 32};
std::cout << "Lattice size: " << lat_size << std::endl;
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);
// Construct a coarsened grid
// poare TODO: replace this with the following line?
Coordinate clatt = lat_size;
// Coordinate clatt = GridDefaultLatt(); // [PO] initial line before I edited it
for(int d=0;d<clatt.size();d++){
clatt[d] = clatt[d]/2;
// clatt[d] = clatt[d]/4;
}
GridCartesian *Coarse4d = SpaceTimeGrid::makeFourDimGrid(clatt, GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());;
GridCartesian *Coarse5d = SpaceTimeGrid::makeFiveDimGrid(1,Coarse4d);
std::vector<int> seeds4({1,2,3,4});
std::vector<int> seeds5({5,6,7,8});
std::vector<int> cseeds({5,6,7,8});
GridParallelRNG RNG5(FGrid); RNG5.SeedFixedIntegers(seeds5);
GridParallelRNG RNG4(UGrid); RNG4.SeedFixedIntegers(seeds4);
GridParallelRNG CRNG(Coarse5d);CRNG.SeedFixedIntegers(cseeds);
LatticeFermion src(FGrid); random(RNG5,src);
LatticeFermion result(FGrid); result=Zero();
LatticeFermion ref(FGrid); ref=Zero();
LatticeFermion tmp(FGrid);
LatticeFermion err(FGrid);
LatticeGaugeField Umu(UGrid);
FieldMetaData header;
// std::string file("ckpoint_lat.4000");
std::string file("/Users/patrickoare/libraries/PETSc-Grid/ckpoint_lat.4000");
NerscIO::readConfiguration(Umu,header,file);
RealD mass=0.01;
RealD M5=1.8;
DomainWallFermionD Ddwf(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,mass,M5);
DomainWallFermionD Dpv(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,1.0,M5);
// const int nbasis = 20; // size of approximate basis for low-mode space
const int nbasis = 3; // size of approximate basis for low-mode space
const int cb = 0 ;
LatticeFermion prom(FGrid);
typedef GeneralCoarsenedMatrix<vSpinColourVector,vTComplex,nbasis> LittleDiracOperator;
typedef LittleDiracOperator::CoarseVector CoarseVector;
NextToNearestStencilGeometry5D geom(Coarse5d);
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
typedef PVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> PVdagM_t;
typedef ShiftedPVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> ShiftedPVdagM_t;
typedef ShiftedComplexPVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> ShiftedComplexPVdagM_t;
PVdagM_t PVdagM(Ddwf, Dpv);
ShiftedPVdagM_t ShiftedPVdagM(0.1,Ddwf,Dpv);
SquaredLinearOperator<DomainWallFermionD, LatticeFermionD> Dsq (Ddwf);
NonHermitianLinearOperator<DomainWallFermionD, LatticeFermionD> DLinOp (Ddwf);
// PowerMethod<LatticeFermion> PM; PM(PVdagM, src);
int Nm = 10;
int Nk = 6;
// int Nm = 6; // Nm = 6 case is acting really strangely... with Nm = 6 and Nm = 3 it zeros out the Hessenberg and also makes it imaginary?
// int Nk = 2;
// int Nk = Nm+1; // if just running once
// int maxIter = 5;
// int maxIter = 1;
int maxIter = 3;
// int maxIter = 100;
int Nstop = 6;
Coordinate origin ({0,0,0,0});
auto tmpSrc = peekSite(src, origin);
std::cout << "[DEBUG] Source at origin = " << tmpSrc << std::endl;
LatticeFermion src2 = src;
// Run Lanczos and Arnoldi on a Hermitian matrix
// Arnoldi Arn (Dsq, FGrid, 1e-8, false);
// Arn(src, 1, Nm, -1);
Arnoldi Arn (Dsq, FGrid, 1e-8, EvalNormLarge); // for comparison to Lanczos
// Arn(src, maxIter, Nm, Nk, Nstop);
// auto tmpSrcDup = peekSite(src, origin);
// std::cout << "[DEBUG] Source at origin = " << tmpSrcDup << std::endl;
// auto tmpSrc2Dup = peekSite(src2, origin);
// std::cout << "[DEBUG] Source2 at origin = " << tmpSrc2Dup << std::endl;
Arn(src, maxIter, Nm, Nk, Nstop);
std::cout << "Hessenberg mat for symmetric N = " << Nm << std::endl;
std::cout << Arn.getHessenbergMat() << std::endl;
// ImplicitlyRestartedLanczosHermOpTester<LatticeFermionD> SimpleTester (Dsq);
// ImplicitlyRestartedLanczos<LatticeFermionD> Lanc (Dsq, Dsq, SimpleTester, Nm, Nm, Nm, 1e-8, Nm);
int Nconv;
PlainHermOp DsqHermOp (Dsq);
// std::vector<RealD> levals (Nm+1); std::vector<LatticeFermionD> levecs (Nm+1, src);
// ImplicitlyRestartedLanczos<LatticeFermionD> Lanc (DsqHermOp, DsqHermOp, Nm, Nm, Nm + 1, 1e-8, Nm);
std::vector<RealD> levals (Nm+1); std::vector<LatticeFermionD> levecs (Nm, src);
ImplicitlyRestartedLanczos<LatticeFermionD> Lanc (DsqHermOp, DsqHermOp, Nstop, Nk, Nm, 1e-8, maxIter);
std::cout << GridLogMessage << "Calculating with Lanczos" << std::endl;
// auto tmpSrc1 = peekSite(src, origin);
// std::cout << "[DEBUG] Source at origin = " << tmpSrc1 << std::endl;
// auto tmpSrc2 = peekSite(src2, origin);
// std::cout << "[DEBUG] Source2 at origin = " << tmpSrc2 << std::endl;
// std::cout << "[DEBUG] Source norm2: " << norm2(src) << std::endl;
std::cout << "running Lanczos now" << std::endl;
Lanc.calc(levals, levecs, src2, Nconv);
std::cout<<GridLogMessage << "*******************************************" << std::endl;
std::cout<<GridLogMessage << "***************** RESULTS *****************" << std::endl;
std::cout<<GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << "Arnoldi eigenvalues: " << std::endl << Arn.getEvals() << std::endl;
std::cout << GridLogMessage << "Lanczos eigenvalues: " << std::endl << levals << std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage << "Done "<< std::endl;
Grid_finalize();
return 0;
}
+4 -2
View File
@@ -3,6 +3,9 @@
* without regression / tests being applied
*/
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace std;
@@ -432,5 +435,4 @@ int main (int argc, char ** argv)
Grid_finalize();
}
#endif
+188
View File
@@ -0,0 +1,188 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./examples/Example_gamma5_block_lanczos.cc
Copyright (C) 2026
Author: Chulwoo Jung <chulwoo@bnl.gov>
γ5-Block Lanczos example for the Wilson Dirac operator.
Reads a gauge configuration from "config" (NERSC format) and Lanczos
parameters from "LanParams.xml". Runs Gamma5BlockLanczos to
compute eigenvalues of D_W directly (not H_W = γ5 D_W).
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 */
#include <cstdlib>
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
using namespace std;
using namespace Grid;
namespace Grid {
struct LanczosParameters : Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(LanczosParameters,
RealD, mass,
Integer, Nstop,
Integer, Nk,
Integer, Np,
Integer, maxIter,
Integer, reorthog,
Integer, verify,
Integer, ReadEvec,
RealD, resid)
LanczosParameters()
: mass(-0.5), Nstop(10), Nk(20), maxIter(100),
reorthog(1), verify(0), ReadEvec(0), resid(1e-8)
{}
template<class ReaderClass>
LanczosParameters(Reader<ReaderClass>& r) { initialize(r); }
template<class ReaderClass>
void initialize(Reader<ReaderClass>& r) {
read(r, "LanczosParameters", *this);
}
};
} // namespace Grid
template<class T>
void writeField(T& in, std::string const& fname) {
#ifdef HAVE_LIME
std::cout << GridLogMessage << "Writing to: " << fname << std::endl;
Grid::emptyUserRecord record;
Grid::ScidacWriter WR(in.Grid()->IsBoss());
WR.open(fname);
WR.writeScidacFieldRecord(in, record, 0);
WR.close();
#endif
}
typedef WilsonFermionD WilsonOp;
typedef typename WilsonFermionD::FermionField FermionField;
int main(int argc, char** argv)
{
Grid_init(&argc, &argv);
GridCartesian* UGrid = SpaceTimeGrid::makeFourDimGrid(
GridDefaultLatt(),
GridDefaultSimd(Nd, vComplex::Nsimd()),
GridDefaultMpi());
GridRedBlackCartesian* UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid);
std::vector<int> seeds4({1, 2, 3, 4});
GridParallelRNG RNG4(UGrid);
RNG4.SeedFixedIntegers(seeds4);
// Read gauge configuration
LatticeGaugeField Umu(UGrid);
FieldMetaData header;
std::string configFile("config");
NerscIO::readConfiguration(Umu, header, configFile);
std::cout << GridLogMessage << "Loaded gauge configuration: " << configFile << std::endl;
// Read Lanczos parameters
LanczosParameters Params;
{
XmlReader rd("LanParams.xml");
read(rd, "LanczosParameters", Params);
}
std::cout << GridLogMessage << Params << std::endl;
// Build Wilson Dirac operator and wrap in a non-Hermitian linear operator
std::vector<Complex> boundary = {1, 1, 1, -1};
WilsonOp::ImplParams WilsonParams(boundary);
WilsonOp Dwilson(Umu, *UGrid, *UrbGrid, Params.mass, WilsonParams);
NonHermitianLinearOperator<WilsonOp, FermionField> DLinOp(Dwilson);
// γ5 functor: for 4D Wilson fermions γ5 is Gamma(Gamma5)
Gamma G5(Gamma::Algebra::Gamma5);
auto gamma5 = [&G5](const FermionField& in, FermionField& out) {
out = G5 * in;
};
// Starting vectors: two independent random vectors
FermionField src(UGrid), src2(UGrid);
random(RNG4, src);
random(RNG4, src2);
std::cout << GridLogMessage << "Using two random starting vectors" << std::endl;
std::cout << GridLogMessage << std::endl;
std::cout << GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << " Running γ5-Block Lanczos" << std::endl;
std::cout << GridLogMessage << " mass = " << Params.mass << std::endl;
std::cout << GridLogMessage << " Nk = " << Params.Nk << std::endl;
std::cout << GridLogMessage << " maxIter = " << Params.maxIter << std::endl;
std::cout << GridLogMessage << " Nstop = " << Params.Nstop << std::endl;
std::cout << GridLogMessage << " resid = " << Params.resid << std::endl;
std::cout << GridLogMessage << " reorthog = " << Params.reorthog << std::endl;
std::cout << GridLogMessage << " verify = " << Params.verify << std::endl;
std::cout << GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << std::endl;
Gamma5BlockLanczos<FermionField> G5BL(DLinOp, UGrid, gamma5, Params.resid);
G5BL.doEvalCheck = (Params.verify != 0);
G5BL.doVerify = (Params.verify != 0);
// G5BL(src, src2, Params.maxIter, Params.Nstop, Params.reorthog != 0);
G5BL.restart(src, src2, Params.maxIter, Params.Nk+Params.Np, Params.Nk, Params.Nstop, Params.reorthog != 0, EvalNormSmall);
// G5BL.implicitRestart(src, src2, Params.maxIter, Params.Nk+Params.Np, Params.Nk, Params.Nstop, Params.reorthog != 0, EvalNormSmall);
if (Params.verify ) G5BL.verify("after restart");
// Summary of results
Eigen::VectorXcd evals = G5BL.getEvals();
std::vector<RealD> residuals = G5BL.getResiduals();
std::vector<FermionField> evecs = G5BL.getEvecs();
int Nout = (int)evals.size();
std::cout << GridLogMessage << std::endl;
std::cout << GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << " γ5-Block Lanczos: " << Nout << " Ritz pairs" << std::endl;
std::cout << GridLogMessage << "*******************************************" << std::endl;
for (int i = 0; i < Nout; i++) {
std::cout << GridLogMessage
<< " [" << std::setw(3) << i << "]"
<< " lambda = " << evals(i)
<< " |res| = " << residuals[i] << std::endl;
}
// Write the first Nstop eigenvectors (in SCIDAC format when LIME is available)
int Nwrite = std::min((int)Params.Nstop, Nout);
for (int i = 0; i < Nwrite; i++) {
std::string fname = "./g5bl_evec_m" + std::to_string(Params.mass)
+ "_" + std::to_string(i);
writeField(evecs[i], fname);
}
std::cout << GridLogMessage << std::endl;
std::cout << GridLogMessage << "Done" << std::endl;
Grid_finalize();
return 0;
}
+405
View File
@@ -0,0 +1,405 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./tests/Test_padded_cell.cc
Copyright (C) 2023
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
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 */
// Tests code written to read off the Krylov coefficients
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidual.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
#include <Grid/algorithms/iterative/BiCGSTAB.h>
#include <Grid/algorithms/iterative/ConjugateGradient.h>
using namespace std;
using namespace Grid;
// Hermitize a DWF operator by squaring it
template<class Matrix,class Field>
class SquaredLinearOperator : public LinearOperatorBase<Field> {
public:
Matrix &_Mat;
public:
SquaredLinearOperator(Matrix &Mat): _Mat(Mat) {};
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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
// std::cout << "Op is overloaded as HermOp" << std::endl;
HermOp(in, out);
}
void AdjOp (const Field &in, Field &out){
HermOp(in, out);
}
void _Op (const Field &in, Field &out){
// std::cout << "Op: M "<<std::endl;
_Mat.M(in, out);
}
void _AdjOp (const Field &in, Field &out){
// std::cout << "AdjOp: Mdag "<<std::endl;
_Mat.Mdag(in, out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
// std::cout << "HermOp: Mdag M Mdag M"<<std::endl;
Field tmp(in.Grid());
_Op(in,tmp);
_AdjOp(tmp,out);
}
};
/**
* Computes the coefficients in the Krylov expansion for 1/D ~ \sum_{i=0}^N c_i D^i.
*
* Parameters
* ----------
* std::vector<double> &coeffs
* Polynomial coeffients to return, with indexing order (c_0, c_1, c_2, ..., c_n).
* LinearOperatorBase<FineField> &DiracOp
* Dirac operator D.
* FineField src
* Source field b.
* FineField psiStar
* Output approximation for D^{-1} b coming from a Krylov method.
* int N
* Dimension of the polynomial approximation (Krylov space K_{N-1} = {b, Db, D^2 b, ..., D^{N-1} b}).
*/
void poly_coeffs(std::vector<ComplexD> &coeffs, LinearOperatorBase<LatticeFermion> &DiracOp, LatticeFermion src,
LatticeFermion psiStar, GridCartesian* FGrid, int N, bool use_herm = false)
{
// stdBasis = {b, Db, D^2 b, ..., D^N b}, kryBasis = {k0, k1, ..., kN}
std::vector<LatticeFermion> kryBasis;
Eigen::VectorXcd psiStarCoeffs (N);
// Normalize by 1 / ||src||; does not change the polynomial coefficients
double srcNorm = 1 / std::sqrt(norm2(src));
kryBasis.push_back(srcNorm * src); // normalized source
psiStar = srcNorm * psiStar;
psiStarCoeffs(0) = innerProduct(kryBasis[0], psiStar);
// orthonormalize canonical Krylov basis {b, Db, D^2 b, ..., D^{N-1} b} <--> {k_i} and compute components <k_i | psi*>
LatticeFermion tmp (FGrid);
for (int i = 0; i < N - 1; i++) { // construct ONB for {b, Db, ..., D^{i+1} b}
if (use_herm) {
DiracOp.HermOp(kryBasis.back(), tmp); // tmp \in span{(D^\dag D)^{i+1} b} \oplus span{(D^\dag D)^i b, ..., D^\dag D b, b}
} else {
DiracOp.Op(kryBasis.back(), tmp); // tmp \in span{D^{i+1} b} \oplus span{D^i b, ..., Db, b}
}
for (int j = 0; j < i+1; j++) { // orthogonalize tmp with previous basis vectors
ComplexD coeff = innerProduct(kryBasis[j], tmp); // <k_j | tmp>
tmp -= coeff * kryBasis[j]; // subtract off |k_j><k_j | tmp>; now tmp is perp to |k_j>
}
double tmpNorm = 1 / std::sqrt(norm2(tmp));
kryBasis.push_back(
tmpNorm * tmp
); // normalize |k_i> and add to kryBasis
psiStarCoeffs(i+1) = innerProduct(kryBasis[i+1], psiStar); // compute < k_i | psi* >
}
// To verify the basis is ONB
// for (int i = 0; i < N; i++) {
// for (int j = 0; j < N; j++) {
// std::cout << "<ki|kj> for (i,j) = (" << i << ", " << j << ") = " << innerProduct(kryBasis[i], kryBasis[j]) << std::endl;
// }
// }
// Compute change of basis matrix
LatticeFermion tmp2 (FGrid);
Eigen::MatrixXcd M = Eigen::MatrixXcd::Zero(N, N);
tmp = kryBasis[0]; // current Krylov vector; starts with tmp = src (normalized)
for (int i = 0; i < N; i++) {
for (int j = 0; j < i + 1; j++) { // fill column with components of kryVec. Only need j <= i to get orthonormal components
M(j, i) = innerProduct(kryBasis[j], tmp);
}
if (use_herm) { // tmp --> D^\dag D(tmp)
DiracOp.HermOp(tmp, tmp2);
tmp = tmp2;
} else { // tmp --> D(tmp). Note that DiracOp.Op(tmp, tmp) will cause a bug
DiracOp.Op(tmp, tmp2);
tmp = tmp2;
}
}
// Compute M^{-1} @ psiStarCoeffs and copy to coeffs
Eigen::VectorXcd res (N);
res = M.inverse() * psiStarCoeffs;
for (int i = 0; i < N; i++) {
coeffs[i] = res(i);
}
}
// out file for poly coefficients (should it be complex?)
// class PolynomialFile: Serializable {
// public:
// GRID_SERIALIZABLE_CLASS_MEMBERS(OutputFile, std::vector< Real >, data);
// };
std::complex<double> poly_approx(std::complex<double> x, std::vector<std::complex<double>> coeffs) {
std::complex<double> px;
for (int i = 0; i < coeffs.size(); i++) {
px += coeffs[i] * std::pow(x, i);
}
return px;
}
/**
* Returns the approximation psi = \sum_i c_i D^i b resulting from a Krylov solver.
*
* Parameters
* ----------
* LatticeFermion &psi
* Approximation field, returned psi = \sum_i c_i D^i b.
* LatticeFermion src
* Source b used to generate the Krylov space K_n(D, b).
* LinearOperatorBase<LatticeFermion> &Linop
* Dirac operator used to generate the Krylov space K_n(D, b).
* std::vector<std::complex<double>> coeffs
* Polynomial coefficients returned from the solver.
*/
void krylovApprox(LatticeFermion &psi, LatticeFermion src, LinearOperatorBase<LatticeFermion> &Linop, std::vector<ComplexD> coeffs) {
psi = Zero();
LatticeFermion tmp (psi.Grid());
tmp = src;
LatticeFermion tmp2 (psi.Grid());
for (int i = 0; i < coeffs.size(); i++) {
psi = psi + coeffs[i] * tmp;
Linop.Op(tmp, tmp2); // tmp = D*tmp
tmp = tmp2;
}
}
int main (int argc, char ** argv)
{
Grid_init(&argc, &argv);
const int Ls = 8;
std::vector<int> lat_size {16, 16, 16, 32};
std::cout << "Lattice size: " << lat_size << std::endl;
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);
//////////////////////////////////////////////////////////////////////
// You can manage seeds however you like.
// Recommend SeedUniqueString.
//////////////////////////////////////////////////////////////////////
// std::vector<int> seeds4({1, 2, 3, 4});
// GridParallelRNG RNG4(UGrid);
// RNG4.SeedFixedIntegers(seeds4);
// std::vector<int> seeds5({1, 2, 3, 4, 5});
// GridParallelRNG RNG5(FGrid);
// RNG5.SeedFixedIntegers(seeds5);
// std::string outStrStem = "/Users/patrickoare/Dropbox (MIT)/research/multigrid/grid_out/";
LatticeGaugeField Umu(UGrid);
FieldMetaData header;
std::string file("/Users/patrickoare/libraries/PETSc-Grid/ckpoint_lat.4000");
NerscIO::readConfiguration(Umu, header, file);
RealD mass=0.01;
RealD M5=1.8;
// RealD M5=1.0;
RealD b=1.5;// Scale factor b+c=2, b-c=1
RealD c=0.5;
// load in Dirac operators that we'll use; square it to Hermitize
// Dsq just needs to be a Hermitian operator so we can use CG on it
DomainWallFermionD Ddwf(Umu, *FGrid, *FrbGrid, *UGrid, *UrbGrid, mass, M5);
SquaredLinearOperator<DomainWallFermionD, LatticeFermionD> Dsq (Ddwf);
NonHermitianLinearOperator<DomainWallFermionD, LatticeFermionD> DLinOp (Ddwf);
LatticeFermion src (FGrid); src = 1.0; // Source to use
LatticeFermion psiCG (FGrid); psiCG = Zero(); // Field to solve with for CG
LatticeFermion psiGCR (FGrid); psiGCR = Zero(); // Field to solve with for GCR
std::cout << GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << "********** TESTING CG POLY COEFFS *********" << std::endl;
std::cout << GridLogMessage << "*******************************************" << std::endl << std::endl;
double tol = 1.0e-8;
int N = 5; // max iterations (size of Krylov basis)
// GCR variables
int outer_iters = 1; // num restarts for GCR
TrivialPrecon<LatticeFermionD> prec; // trivial preconditioner
ConjugateGradientPolynomial<LatticeFermion> CGP(tol, N, false);
CGP(Dsq, src, psiCG);
// Compute Krylov coeffs directly and compare
std::vector<ComplexD> cg_coeffs (N);
poly_coeffs(cg_coeffs, Dsq, src, psiCG, FGrid, N, true);
PolynomialFile PF;
// Use GCR solver, also get poly coeffs
std::vector<ComplexD> gcr_sym_coeffs (N); // Can try N --> N + 3 to test to see if the last 3 comps are 0
PGCRPolynomial<LatticeFermionD> GCRPolySym(tol, outer_iters, Dsq, prec, N+1, N, PF); // mmax sets the memory, note the last beta doesn't really matter for updating the polynomial
GCRPolySym(src, psiGCR);
// poly_coeffs(gcr_sym_coeffs, Dsq, src, psi, FGrid, N, true);
poly_coeffs(gcr_sym_coeffs, Dsq, src, psiGCR, FGrid, N, true);
std::cout << GridLogMessage << std::endl << "******** CG POLYNOMIAL COEFFICIENTS *******" << std::endl;
std::cout << GridLogMessage << CGP.polynomial << std::endl << std::endl;
std::cout << GridLogMessage << "****** DIRECT POLYNOMIAL COEFFICIENTS *****" << std::endl;
std::cout << GridLogMessage << cg_coeffs << std::endl << std::endl;
// TODO: try GCR with a Hermitian operator (Dsq)
std::cout << GridLogMessage << "****** GCR COEFFICIENTS *****" << std::endl;
std::cout << GridLogMessage << GCRPolySym.polynomial << std::endl << std::endl;
std::cout << GridLogMessage << "****** DIRECT GCR COEFFICIENTS *****" << std::endl;
std::cout << GridLogMessage << gcr_sym_coeffs << std::endl << std::endl;
// test how good the decomposition is
std::cout << "Testing fidelity of decomposition by computing ||psi* - sum_i c_i D^i b||^2!" << std::endl;
LatticeFermion psiPrime (FGrid);
// for CG
krylovApprox(psiPrime, src, Dsq, cg_coeffs);
std::cout << "CG with Dsq, ||psi - psiPrime||^2 = " << norm2(psiCG - psiPrime) << std::endl;
// for GCR with alpha / beta computation
krylovApprox(psiPrime, src, Dsq, GCRPolySym.polynomial);
std::cout << "GCR with Dsq, ||psi - psiPrime||^2 = " << norm2(psiGCR - psiPrime) << std::endl;
// for GCR with alpha / beta computation
krylovApprox(psiPrime, src, Dsq, gcr_sym_coeffs);
std::cout << "GCR direct with Dsq, ||psi - psiPrime||^2 = " << norm2(psiGCR - psiPrime) << std::endl;
// std::vector<double> real_cg_diff (N);
// for (int i = 0; i < N; i++) { real_cg_diff[i] = std::abs(cg_coeffs[i].real() - CGP.polynomial[i]); }
// std::cout << GridLogMessage << "************* COEFF DIFFERENCE ************" << std::endl;
// std::cout << GridLogMessage << real_cg_diff << std::endl << std::endl;
// GCR polynomial reconstruction with Ddwf!
std::cout << GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << "********* TESTING GCR POLY COEFFS *********" << std::endl;
std::cout << GridLogMessage << "*******************************************" << std::endl << std::endl;
// re-init variables
src = 1.0;
src = (1 / std::sqrt(norm2(src))) * src;
psiGCR = Zero(); psiPrime = Zero();
// test GCR poly
PGCRPolynomial<LatticeFermionD> GCRPoly(tol, outer_iters, DLinOp, prec, N+1, N, PF); // mmax sets the memory, note the last beta doesn't really matter for updating the polynomial
GCRPoly(src, psiGCR);
// Compute Krylov coeffs directly and compare
// N = 1; // compare the N > 1 decomposition with the psi* resulting from N = 1
std::vector<ComplexD> gcr_coeffs (N); // note N --> N + k should just give k coeffs that are 0; this works as intended
poly_coeffs(gcr_coeffs, DLinOp, src, psiGCR, FGrid, N, false);
std::cout << GridLogMessage << "******* GCR POLYNOMIAL COEFFICIENTS *******" << std::endl;
std::cout << GridLogMessage << GCRPoly.polynomial << std::endl << std::endl;
std::cout << GridLogMessage << "****** DIRECT POLYNOMIAL COEFFICIENTS *****" << std::endl;
std::cout << GridLogMessage << gcr_coeffs << std::endl << std::endl;
// test how good the decomposition is
std::cout << "Testing fidelity of decomposition by computing ||psi* - sum_i c_i D^i b||^2!" << std::endl;
// for GCR with alpha / beta computation
krylovApprox(psiPrime, src, DLinOp, GCRPoly.polynomial);
std::cout << "GCR with Dsq, ||psi - psiPrime||^2 = " << norm2(psiGCR - psiPrime) << std::endl;
// for GCR with alpha / beta computation
krylovApprox(psiPrime, src, DLinOp, gcr_coeffs);
std::cout << "GCR direct with Dsq, ||psi - psiPrime||^2 = " << norm2(psiGCR - psiPrime) << std::endl;
// TESTS TO DO THE N = 2 CASE DIRECTLY
/*
std::vector<std::complex<double>> alphas {
std::complex(0.244300601, 0.00013007545),
std::complex(0.285370971, -0.000160704481)
};
std::complex<double> beta00 (-0.184661284, -6.52153945e-05);
LatticeFermion psi2 (FGrid);
LatticeFermion Dsrc (FGrid);
DLinOp.Op(src, Dsrc);
std::complex<double> c1 = alphas[0] + alphas[1] * (1. + beta00);
std::complex<double> c2 = -alphas[0] * alphas[1];
psi2 = c1 * src + c2 * Dsrc;
std::cout << "||b|| = " << norm2(src) << std::endl;
std::cout << "||Db|| = " << norm2(Dsrc) << std::endl;
// fail; so far this is giving something different than what's being computed in krylovApprox (idk how?)
std::cout << "c1 and c2 are: " << c1 << " and " << c2 << std::endl;
std::cout << "GCRPoly polynomial coeffs are (should equal c1 and c2): " << GCRPoly.polynomial << std::endl;
std::cout << "||GCRpsi - psi2||_2^2 = " << norm2(psiGCR - psi2) << std::endl;
// pass
LatticeFermion src2 (FGrid);
src2 = 1.0;
src2 = (1 / std::sqrt(norm2(src2))) * src2;
std::cout << "||ones - src|| (to verify that src is the same throughout, should be 0) = " << norm2(src2 - src) << std::endl;
// pass
krylovApprox(psiPrime, src, DLinOp, GCRPoly.polynomial);
std::cout << "GCR with Dsq, ||psi2 - psiPrime||^2 = " << norm2(psi2 - psiPrime) << std::endl;
std::vector<ComplexD> psi2_coeffs (N); // note N --> N + k should just give k coeffs that are 0; this works as intended
poly_coeffs(psi2_coeffs, DLinOp, src, psi2, FGrid, N, false);
krylovApprox(psiPrime, src, DLinOp, psi2_coeffs);
std::cout << "GCR direct with Dsq, ||psi - psiPrime||^2 = " << norm2(psi2 - psiPrime) << std::endl;
*/
// std::complex z (10.0, 0.0); // z = 10
// std::cout << GridLogMessage << "************* GCR POLY(z = 10) *************" << std::endl;
// std::cout << GridLogMessage << poly_approx(z, GCRPoly.polynomial) << std::endl;
// std::cout << GridLogMessage << "************ DIRECT POLY(z = 10) ***********" << std::endl;
// std::cout << GridLogMessage << poly_approx(z, gcr_coeffs) << std::endl;
// std::vector<std::complex<double>> gcr_diff (N);
// for (int i = 0; i < N; i++) { gcr_diff[i] = gcr_coeffs[i] - GCRPoly.polynomial[i]; }
// std::cout << GridLogMessage << "*********** GCR COEFF DIFFERENCE **********" << std::endl;
// std::cout << GridLogMessage << gcr_diff << std::endl << std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage << "Done "<< std::endl;
Grid_finalize();
return 0;
}
+425
View File
@@ -0,0 +1,425 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./examples/Example_krylov_schur.cc
Copyright (C) 2023
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: Patrick Oare <patrickoare@gmail.com>
Author: Chulwoo Jung <chulwoo@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.
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 */
// copied here from Test_general_coarse_pvdagm.cc
#include <cstdlib>
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidual.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
#include <Grid/algorithms/iterative/BiCGSTAB.h>
using namespace std;
using namespace Grid;
namespace Grid {
struct LanczosParameters: Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(LanczosParameters,
RealD, mass ,
RealD, mstep ,
Integer, Nstop,
Integer, Nk,
Integer, Np,
Integer, ReadEvec,
Integer, maxIter,
Integer, Nblock,
Integer, verify,
RealD, shift ,
RealD, resid,
RealD, ChebyLow,
RealD, ChebyHigh,
Integer, ChebyOrder)
LanczosParameters() {
////////////////////////////// Default values
mass = 0;
/////////////////////////////////
}
template <class ReaderClass >
LanczosParameters(Reader<ReaderClass> & TheReader){
initialize(TheReader);
}
template < class ReaderClass >
void initialize(Reader<ReaderClass> &TheReader){
// std::cout << GridLogMessage << "Reading HMC\n";
read(TheReader, "HMC", *this);
}
void print_parameters() const {
// std::cout << GridLogMessage << "[HMC parameters] Trajectories : " << Trajectories << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Start trajectory : " << StartTrajectory << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Metropolis test (on/off): " << std::boolalpha << MetropolisTest << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Thermalization trajs : " << NoMetropolisUntil << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Starting type : " << StartingType << "\n";
// MD.print_parameters();
}
};
}
template <class T> void writeFile(T& in, std::string const fname){
#if 1
// Ref: https://github.com/paboyle/Grid/blob/feature/scidac-wp1/tests/debug/Test_general_coarse_hdcg_phys48.cc#L111
std::cout << Grid::GridLogMessage << "Writes to: " << fname << std::endl;
Grid::emptyUserRecord record;
Grid::ScidacWriter WR(in.Grid()->IsBoss());
WR.open(fname);
WR.writeScidacFieldRecord(in,record,0);
WR.close();
#endif
// What is the appropriate way to throw error?
}
typedef WilsonFermionD WilsonOp;
typedef typename WilsonFermionD::FermionField FermionField;
template<class Matrix,class Field>
class InvertNonHermitianLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
RealD _stp;
public:
InvertNonHermitianLinearOperator(Matrix &Mat,RealD stp=1e-8): _Mat(Mat),_stp(stp){};
// Support for coarsening to a multigrid
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<Field> &out){
// _Mat.MdirAll(in,out);
assert(0);
};
void Op (const Field &in, Field &out){
Field tmp(in.Grid());
_Mat.Mdag(in,tmp);
MdagMLinearOperator<Matrix,Field> HermOp(_Mat);
ConjugateGradient<Field> CG(_stp,10000);
CG(HermOp,tmp,out);
// out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
_Mat.Mdag(in,out);
// out = out + shift * in;
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){
assert(0);
}
void HermOp(const Field &in, Field &out){
assert(0);
}
};
template<class Field>
void testSchurFromHess(Arnoldi<Field>& Arn, Field& src, int Nlarge, int Nm, int Nk) {
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout << GridLogMessage << "Testing Schur reordering, Nm = " << Nm << ", Nk = " << Nk << std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout << GridLogMessage << "Running Arnoldi for 1 iteration to get a Hessenberg." << std::endl;
Arn(src, 1, Nlarge, Nm, Nlarge);
Eigen::MatrixXcd Hess = Arn.getHessenbergMat();
std::cout << GridLogMessage << "Hessenberg for use: " << std::endl << Hess << std::endl;
ComplexSchurDecomposition schur (Hess, true);
bool isDecomposed = schur.checkDecomposition();
std::cout << "Schur decomp holds? " << isDecomposed << std::endl;
std::cout << GridLogMessage << "S = " << std::endl << schur.getMatrixS() << std::endl;
std::cout << GridLogMessage << "Swapping S(3, 3) with S(4, 4)" << std::endl;
schur.swapEvals(3);
std::cout << GridLogMessage << "S after swap = " << std::endl << schur.getMatrixS() << std::endl;
std::cout << "Schur decomp still holds? " << schur.checkDecomposition() << std::endl;
// Now move last diagonal element all the way to the front.
std::cout << GridLogMessage << "Moving last eval to front. S at start = " << std::endl << schur.getMatrixS() << std::endl;
for (int i = 0; i < Nk - 1; i++) {
int swapIdx = Nk - 2 - i;
schur.swapEvals(swapIdx);
std::cout << GridLogMessage << "S after swap of index " << swapIdx << " = " << std::endl << schur.getMatrixS() << std::endl;
std::cout << "Schur decomp still holds? " << schur.checkDecomposition() << std::endl;
}
std::cout << GridLogMessage << "Testing Schur reorder" << std::endl;
schur.schurReorder(Nk);
std::cout << GridLogMessage << "S after reorder = " << std::endl << schur.getMatrixS() << std::endl;
std::cout << "Schur decomp still holds? " << schur.checkDecomposition() << std::endl;
}
int main (int argc, char ** argv)
{
Grid_init(&argc,&argv);
const int Ls=16;
// GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(), GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());
// std::vector<int> lat_size {32, 32, 32, 32};
// std::cout << "Lattice size: " << lat_size << std::endl;
GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(),
GridDefaultSimd(Nd,vComplex::Nsimd()),
GridDefaultMpi());
GridRedBlackCartesian * UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid);
// GridCartesian * FGrid = SpaceTimeGrid::makeFiveDimGrid(Ls,UGrid);
// GridRedBlackCartesian * FrbGrid = SpaceTimeGrid::makeFiveDimRedBlackGrid(Ls,UGrid);
GridCartesian * FGrid = UGrid;
GridRedBlackCartesian * FrbGrid = UrbGrid;
// Construct a coarsened grid
// poare TODO: replace this with the following line?
Coordinate clatt = GridDefaultLatt();
// Coordinate clatt = GridDefaultLatt(); // [PO] initial line before I edited it
for(int d=0;d<clatt.size();d++){
std::cout << GridLogMessage<< clatt[d] <<std::endl;
clatt[d] = clatt[d]/2;
// clatt[d] = clatt[d]/4;
}
GridCartesian *Coarse4d = SpaceTimeGrid::makeFourDimGrid(clatt, GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());;
GridCartesian *Coarse5d = SpaceTimeGrid::makeFiveDimGrid(1,Coarse4d);
std::vector<int> seeds4({1,2,3,4});
std::vector<int> seeds5({5,6,7,8});
std::vector<int> cseeds({5,6,7,8});
GridParallelRNG RNG5(FGrid); RNG5.SeedFixedIntegers(seeds5);
GridParallelRNG RNG4(UGrid); RNG4.SeedFixedIntegers(seeds4);
GridParallelRNG CRNG(Coarse5d);CRNG.SeedFixedIntegers(cseeds);
LatticeFermion result(FGrid); result=Zero();
LatticeFermion ref(FGrid); ref=Zero();
LatticeFermion tmp(FGrid);
LatticeFermion err(FGrid);
LatticeGaugeField Umu(UGrid);
FieldMetaData header;
std::string file("config");
// std::string file("Users/patrickoare/libraries/PETSc-Grid/ckpoint_lat.4000");
NerscIO::readConfiguration(Umu,header,file);
LanczosParameters LanParams;
{
XmlReader HMCrd("LanParams.xml");
read(HMCrd,"LanczosParameters",LanParams);
}
std::cout << GridLogMessage<< LanParams <<std::endl;
{
XmlWriter HMCwr("LanParams.xml.out");
write(HMCwr,"LanczosParameters",LanParams);
}
RealD mass=0.01;
RealD M5=1.8;
// PowerMethod<LatticeFermion> PM; PM(PVdagM, src);
int Nm = 50;
int Nk = 12;
int Np = 38;
// int Nk = Nm+1; // if just running once
int maxIter = 10000;
int Nstop = 10;
RealD resid = 1.0e-5;
std::vector<Complex> boundary = {1,1,1,-1};
WilsonOp::ImplParams Params(boundary);
// DomainWallFermionD Ddwf(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,mass,M5);
// DomainWallFermionD Dpv(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,1.0,M5);
mass=LanParams.mass;
std::cout << GridLogIRL<< "mass "<<mass<<std::endl;
WilsonOp WilsonOperator(Umu,*UGrid,*UrbGrid,mass,Params);
// const int nbasis = 20; // size of approximate basis for low-mode space
const int nbasis = 3; // size of approximate basis for low-mode space
const int cb = 0 ;
LatticeFermion prom(FGrid);
typedef GeneralCoarsenedMatrix<vSpinColourVector,vTComplex,nbasis> LittleDiracOperator;
typedef LittleDiracOperator::CoarseVector CoarseVector;
NextToNearestStencilGeometry5D geom(Coarse5d);
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
// typedef PVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> PVdagM_t;
// typedef ShiftedPVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> ShiftedPVdagM_t;
// typedef ShiftedComplexPVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> ShiftedComplexPVdagM_t;
// PVdagM_t PVdagM(Ddwf, Dpv);
// ShiftedPVdagM_t ShiftedPVdagM(0.1,Ddwf,Dpv);
// SquaredLinearOperator<DomainWallFermionD, LatticeFermionD> Dsq (Ddwf);
// NonHermitianLinearOperator<DomainWallFermionD, LatticeFermionD> DLinOp (Ddwf);
NonHermitianLinearOperator<WilsonOp,FermionField> Dwilson(WilsonOperator); /// <-----
InvertNonHermitianLinearOperator<WilsonOp,FermionField> Iwilson(WilsonOperator); /// <-----
MdagMLinearOperator<WilsonOp,FermionField> HermOp(WilsonOperator); /// <-----
Gamma5HermitianLinearOperator <WilsonOp,LatticeFermion> HermOp2(WilsonOperator); /// <----
// PowerMethod<LatticeFermion> PM; PM(PVdagM, src);
resid=LanParams.resid;
Nstop=LanParams.Nstop;
Nk=LanParams.Nk;
Np=LanParams.Np;
maxIter=LanParams.maxIter;
Nm = Nk + Np;
int Nu=16;
std::vector<LatticeFermion> src(Nu,FGrid);
for(int i=0;i<Nu;i++){
random(RNG5,src[i]);
#if 0
LatticeFermion src_e(FrbGrid);
pickCheckerboard(Even, src_e, src[i]);
src_e=Zero();
setCheckerboard(src[i],src_e);
std::cout << GridLogMessage << "src["<<i<<"] even sites zeroed " << std::endl;
#endif
}
if(LanParams.ReadEvec) {
std::string evecs_file="evec_in";
std::cout << GridLogIRL<< "Reading evecs from "<<evecs_file<<std::endl;
Grid::emptyUserRecord record;
Grid::ScidacReader RD;
RD.open(evecs_file);
RD.readScidacFieldRecord(src[0],record);
RD.close();
}
Coordinate origin ({0,0,0,0});
auto tmpSrc = peekSite(src[0], origin);
std::cout << "[DEBUG] Source at origin = " << tmpSrc << std::endl;
LatticeFermion src2 = src[0];
// Run KrylovSchur / BlockKrylovSchur / TrueHarmonicBlockKrylovSchur on a Hermitian matrix
RealD shift=LanParams.shift;
// --use-blockKS : off -> plain (non-block) KrylovSchur
// on -> block family (BlockKrylovSchur / TrueHarmonicBlockKrylovSchur)
// --use-harmonic: off -> no shift/harmonic Ritz extraction
// on, without --use-blockKS -> plain KrylovSchur with shifted (harmonic) Schur extraction
// on, with --use-blockKS -> TrueHarmonicBlockKrylovSchur (true harmonic Ritz)
bool useBlockKS = GridCmdOptionExists(argv, argv+argc, std::string("--use-blockKS"));
bool useHarmonic = GridCmdOptionExists(argv, argv+argc, std::string("--use-harmonic"));
std::vector<LatticeFermion> finalEvecs;
if (!useBlockKS) {
KrylovSchur KrySchur (Dwilson, UGrid, resid,EvalImNormSmall);
KrySchur.doEvalCheck=true;
if (useHarmonic) {
std::cout << GridLogMessage << "Running KrylovSchur (shifted/harmonic Ritz)" << std::endl;
KrySchur(src[0], maxIter, Nm, Nk, Nstop,&shift);
} else {
std::cout << GridLogMessage << "Running KrylovSchur" << std::endl;
KrySchur(src[0], maxIter, Nm, Nk, Nstop);
}
std::cout << GridLogMessage << "KrylovSchur evec.size= " << KrySchur.evecs.size()<< std::endl;
finalEvecs = KrySchur.evecs;
} else {
int Nblock=4;
Nblock=LanParams.Nblock;
bool if_verify=false;
if(LanParams.verify) if_verify=true;
auto gamma5Lambda = [](const FermionField& v, FermionField& out) {
Gamma g5(Gamma::Algebra::Gamma5);
out = g5 * v;
};
if (useHarmonic) {
std::cout << GridLogMessage << "Running TrueHarmonicBlockKrylovSchur" << std::endl;
TrueHarmonicBlockKrylovSchur<FermionField> KrySchur (Dwilson, UGrid, resid,shift,EvalImNormSmall);
KrySchur.doEvalCheck=true;
KrySchur.gamma5Func = gamma5Lambda;
KrySchur(src, maxIter, Nm, Nk, Nstop,Nblock,true,if_verify);
std::cout << GridLogMessage << "TrueHarmonicBlockKrylovSchur evec.size= " << KrySchur.evecs.size()<< std::endl;
finalEvecs = KrySchur.evecs;
} else {
std::cout << GridLogMessage << "Running BlockKrylovSchur" << std::endl;
BlockKrylovSchur<FermionField> KrySchur (Dwilson, UGrid, resid,EvalImNormSmall);
KrySchur.doEvalCheck=true;
KrySchur.gamma5Func = gamma5Lambda;
KrySchur(src, maxIter, Nm, Nk, Nstop,Nblock,true,if_verify);
std::cout << GridLogMessage << "BlockKrylovSchur evec.size= " << KrySchur.evecs.size()<< std::endl;
finalEvecs = KrySchur.evecs;
}
}
src[0]=finalEvecs[0];
std::cout << GridLogMessage << "finalEvecs= "<< finalEvecs.size() <<std::endl;
for (int i=1;i<Nk;i++) src[0]+=finalEvecs[i];
for (int i=0;i<Nstop;i++)
{
std::string evfile ("./evec_"+std::to_string(mass)+"_"+std::to_string(i));
auto evdensity = localInnerProduct(finalEvecs[i],finalEvecs[i] );
writeFile(evdensity,evfile);
}
{
std::string evfile ("./evec_"+std::to_string(mass)+"_sum");
// auto evdensity = localInnerProduct(evec[i],evec[i] );
writeFile(src[0],evfile);
}
/*
std::cout << GridLogMessage << "Running Arnoldi" << std::endl;
// Arnoldi Arn (Dsq, FGrid, 1e-8);
Arnoldi Arn (DLinOp, FGrid, 1e-8);
testSchurFromHess<LatticeFermion>(Arn, src, 10, 6, 4);
Arnoldi Arn2 (DLinOp, FGrid, 1e-8);
testSchurFromHess<LatticeFermion>(Arn2, src, 16, 12, 8);
*/
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage << "Done "<< std::endl;
Grid_finalize();
return 0;
}
+498
View File
@@ -0,0 +1,498 @@
/*************************************************************************************
Runs the Krylov-Schur algorithm on a (pre-conditioned) domain-wall fermion operator
to determine part of its spectrum.
Usage :
$ ./Example_spec_kryschur <Nm> <Nk> <maxiter> <Nstop> <inFile> <outDir> <?rf>
Nm = Maximum size of approximation subspace.
Nk = Size of truncation subspace
maxiter = Maximum number of iterations.
Nstop = Stop when Nstop eigenvalues have converged.
inFile = Gauge configuration to read in.
outDir = Directory to write output to.
rf = (Optional) RitzFilter to sort with. Takes in any string in
{EvalNormSmall, EvalNormLarge, EvalReSmall, EvalReLarge, EvalImSmall, EvalImLarge}
Output:
${outDir}/evals.txt = Contains all eigenvalues. Each line is formatted as `$idx $eval $ritz`, where:
- $idx is the index of the eigenvalue.
- $eval is the eigenvalue, formated as "(re,im)".
- $ritz is the Ritz estimate of the eigenvalue (deviation from being a true eigenvalue)
${outDir}/evec${idx} = Eigenvector $idx written out in SCIDAC format (if LIME is enabled).
Grid physics library, www.github.com/paboyle/Grid
Source file: ./tests/Test_padded_cell.cc
Copyright (C) 2023
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: Patrick Oare <poare@bnl.edu>
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 */
#include <cstdlib>
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidual.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
#include <Grid/algorithms/iterative/BiCGSTAB.h>
#include <Grid/parallelIO/IldgIOtypes.h>
#include <Grid/parallelIO/IldgIO.h>
using namespace std;
using namespace Grid;
namespace Grid {
struct LanczosParameters: Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(LanczosParameters,
RealD, mass ,
RealD, mstep ,
Integer, Nstop,
Integer, Nk,
Integer, Np,
Integer, ReadEvec,
RealD, resid,
RealD, ChebyLow,
RealD, ChebyHigh,
Integer, ChebyOrder)
LanczosParameters() {
/////////////////////////////////
}
template <class ReaderClass >
LanczosParameters(Reader<ReaderClass> & TheReader){
initialize(TheReader);
}
template < class ReaderClass >
void initialize(Reader<ReaderClass> &TheReader){
read(TheReader, "HMC", *this);
}
void print_parameters() const {
// std::cout << GridLogMessage << "[HMC parameters] Trajectories : " << Trajectories << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Start trajectory : " << StartTrajectory << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Metropolis test (on/off): " << std::boolalpha << MetropolisTest << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Thermalization trajs : " << NoMetropolisUntil << "\n";
// std::cout << GridLogMessage << "[HMC parameters] Starting type : " << StartingType << "\n";
// MD.print_parameters();
}
};
}
template <class T> void writeFile(T& in, std::string const fname){
#ifdef HAVE_LIME
// Ref: https://github.com/paboyle/Grid/blob/feature/scidac-wp1/tests/debug/Test_general_coarse_hdcg_phys48.cc#L111
std::cout << Grid::GridLogMessage << "Writes to: " << fname << std::endl;
Grid::emptyUserRecord record;
Grid::ScidacWriter WR(in.Grid()->IsBoss());
WR.open(fname);
WR.writeScidacFieldRecord(in,record,0); // Lexico
WR.close();
#endif
}
/**
* Writes the eigensystem of a Krylov Schur object to a directory.
*
* Parameters
* ----------
* std::string path
* Directory to write to.
*/
template <class Field>
void writeEigensystem(KrylovSchur<Field> KS, std::string outDir) {
int Nk = KS.getNk();
std::cout << GridLogMessage << "Writing output to directory: " << outDir << std::endl;
// Write evals
std::string evalPath = outDir + "/evals.txt";
std::ofstream fEval;
fEval.open(evalPath);
Eigen::VectorXcd evals = KS.getEvals();
std::vector<RealD> ritz = KS.getRitzEstimates();
for (int i = 0; i < Nk; i++) {
// write eigenvalues and Ritz estimates
fEval << i << " " << evals(i) << " " << ritz[i];
if (i < Nk - 1) { fEval << "\n"; }
}
fEval.close();
// Write evecs (TODO: very heavy on storage costs! Don't write them all out)
// std::vector<Field> evecs = KS.getEvecs();
// for (int i = 0; i < Nk; i++) {
// std::string fName = outDir + "/evec" + std::to_string(i);
// writeFile(evecs[i], fName); // using method from Grid/HMC/ComputeWilsonFlow.cc
// }
}
// Hermitize a DWF operator by squaring it
template<class Matrix,class Field>
class SquaredLinearOperator : public LinearOperatorBase<Field> {
public:
Matrix &_Mat;
public:
SquaredLinearOperator(Matrix &Mat): _Mat(Mat) {};
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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
// std::cout << "Op is overloaded as HermOp" << std::endl;
HermOp(in, out);
}
void AdjOp (const Field &in, Field &out){
HermOp(in, out);
}
void _Op (const Field &in, Field &out){
// std::cout << "Op: M "<<std::endl;
_Mat.M(in, out);
}
void _AdjOp (const Field &in, Field &out){
// std::cout << "AdjOp: Mdag "<<std::endl;
_Mat.Mdag(in, out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
// std::cout << "HermOp: Mdag M Mdag M"<<std::endl;
Field tmp(in.Grid());
_Op(in,tmp);
_AdjOp(tmp,out);
}
};
template<class Matrix,class Field>
class PVdagMLinearOperator : public LinearOperatorBase<Field> {
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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(in,tmp);
_Mat.Mdag(tmp,out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
// _Mat.M(in,tmp);
// _PV.Mdag(tmp,out);
// _PV.M(out,tmp);
// _Mat.Mdag(tmp,out);
Op(in,tmp);
AdjOp(tmp,out);
// std::cout << "HermOp done "<<norm2(out)<<std::endl;
}
};
template<class Matrix,class Field>
class ShiftedPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
RealD shift;
public:
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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
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){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
};
template<class Matrix, class Field>
class ShiftedComplexPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
ComplexD shift;
public:
ShiftedComplexPVdagMLinearOperator(ComplexD _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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
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){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
void resetShift(ComplexD newShift) {
shift = newShift;
}
};
#if 0
template<class Fobj,class CComplex,int nbasis>
class MGPreconditioner : public LinearFunction< Lattice<Fobj> > {
public:
using LinearFunction<Lattice<Fobj> >::operator();
typedef Aggregation<Fobj,CComplex,nbasis> Aggregates;
typedef typename Aggregation<Fobj,CComplex,nbasis>::FineField FineField;
typedef typename Aggregation<Fobj,CComplex,nbasis>::CoarseVector CoarseVector;
typedef typename Aggregation<Fobj,CComplex,nbasis>::CoarseMatrix CoarseMatrix;
typedef LinearOperatorBase<FineField> FineOperator;
typedef LinearFunction <FineField> FineSmoother;
typedef LinearOperatorBase<CoarseVector> CoarseOperator;
typedef LinearFunction <CoarseVector> CoarseSolver;
Aggregates & _Aggregates;
FineOperator & _FineOperator;
FineSmoother & _PreSmoother;
FineSmoother & _PostSmoother;
CoarseOperator & _CoarseOperator;
CoarseSolver & _CoarseSolve;
int level; void Level(int lv) {level = lv; };
MGPreconditioner(Aggregates &Agg,
FineOperator &Fine,
FineSmoother &PreSmoother,
FineSmoother &PostSmoother,
CoarseOperator &CoarseOperator_,
CoarseSolver &CoarseSolve_)
: _Aggregates(Agg),
_FineOperator(Fine),
_PreSmoother(PreSmoother),
_PostSmoother(PostSmoother),
_CoarseOperator(CoarseOperator_),
_CoarseSolve(CoarseSolve_),
level(1) { }
virtual void operator()(const FineField &in, FineField & out)
{
GridBase *CoarseGrid = _Aggregates.CoarseGrid;
// auto CoarseGrid = _CoarseOperator.Grid();
CoarseVector Csrc(CoarseGrid);
CoarseVector Csol(CoarseGrid);
FineField vec1(in.Grid());
FineField vec2(in.Grid());
std::cout<<GridLogMessage << "Calling PreSmoother " <<std::endl;
// std::cout<<GridLogMessage << "Calling PreSmoother input residual "<<norm2(in) <<std::endl;
double t;
// Fine Smoother
// out = in;
out = Zero();
t=-usecond();
_PreSmoother(in,out);
t+=usecond();
std::cout<<GridLogMessage << "PreSmoother took "<< t/1000.0<< "ms" <<std::endl;
// Update the residual
_FineOperator.Op(out,vec1); sub(vec1, in ,vec1);
// std::cout<<GridLogMessage <<"Residual-1 now " <<norm2(vec1)<<std::endl;
// Fine to Coarse
t=-usecond();
_Aggregates.ProjectToSubspace (Csrc,vec1);
t+=usecond();
std::cout<<GridLogMessage << "Project to coarse took "<< t/1000.0<< "ms" <<std::endl;
// Coarse correction
t=-usecond();
Csol = Zero();
_CoarseSolve(Csrc,Csol);
//Csol=Zero();
t+=usecond();
std::cout<<GridLogMessage << "Coarse solve took "<< t/1000.0<< "ms" <<std::endl;
// Coarse to Fine
t=-usecond();
// _CoarseOperator.PromoteFromSubspace(_Aggregates,Csol,vec1);
_Aggregates.PromoteFromSubspace(Csol,vec1);
add(out,out,vec1);
t+=usecond();
std::cout<<GridLogMessage << "Promote to this level took "<< t/1000.0<< "ms" <<std::endl;
// Residual
_FineOperator.Op(out,vec1); sub(vec1 ,in , vec1);
// std::cout<<GridLogMessage <<"Residual-2 now " <<norm2(vec1)<<std::endl;
// Fine Smoother
t=-usecond();
// vec2=vec1;
vec2=Zero();
_PostSmoother(vec1,vec2);
t+=usecond();
std::cout<<GridLogMessage << "PostSmoother took "<< t/1000.0<< "ms" <<std::endl;
add( out,out,vec2);
std::cout<<GridLogMessage << "Done " <<std::endl;
}
};
#endif
int main (int argc, char ** argv)
{
Grid_init(&argc,&argv);
// Usage : $ ./Example_spec_kryschur <Nm> <Nk> <maaxiter> <Nstop> <inFile> <outDir>
std::string NmStr = argv[1];
std::string NkStr = argv[2];
std::string maxIterStr = argv[3];
std::string NstopStr = argv[4];
std::string file = argv[5];
std::string outDir = argv[6];
RitzFilter RF;
if (argc == 8) {
std::string rf = argv[7];
RF = selectRitzFilter(rf);
} else {
RF = EvalReSmall;
}
std::cout << "Sorting eigenvalues using " << rfToString(RF) << std::endl;
//const int Ls=16;
const int Ls = 8;
// GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(), GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());
//std::vector<int> lat_size {16, 16, 16, 32};
std::vector<int> lat_size {8, 8, 8, 8};
std::cout << "Lattice size: " << lat_size << std::endl;
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);
std::vector<int> seeds4({1,2,3,4});
std::vector<int> seeds5({5,6,7,8});
GridParallelRNG RNG5(FGrid); RNG5.SeedFixedIntegers(seeds5);
GridParallelRNG RNG4(UGrid); RNG4.SeedFixedIntegers(seeds4);
LatticeFermion src(FGrid); random(RNG5,src);
LatticeGaugeField Umu(UGrid);
FieldMetaData header;
NerscIO::readConfiguration(Umu,header,file);
// RealD mass=0.01;
RealD mass=0.001;
RealD M5=1.8;
DomainWallFermionD Ddwf(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,mass,M5);
DomainWallFermionD Dpv(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,1.0,M5);
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
typedef PVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> PVdagM_t;
typedef ShiftedPVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> ShiftedPVdagM_t;
typedef ShiftedComplexPVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> ShiftedComplexPVdagM_t;
PVdagM_t PVdagM(Ddwf, Dpv);
ShiftedPVdagM_t ShiftedPVdagM(0.1,Ddwf,Dpv);
SquaredLinearOperator<DomainWallFermionD, LatticeFermionD> Dsq (Ddwf);
NonHermitianLinearOperator<DomainWallFermionD, LatticeFermionD> DLinOp (Ddwf);
int Nm = std::stoi(NmStr);
int Nk = std::stoi(NkStr);
int maxIter = std::stoi(maxIterStr);
int Nstop = std::stoi(NstopStr);
std::cout << GridLogMessage << "Runnning Krylov Schur. Nm = " << Nm << ", Nk = " << Nk << ", maxIter = " << maxIter
<< ", Nstop = " << Nstop << std::endl;
KrylovSchur KrySchur (PVdagM, FGrid, 1e-8, RF); // use preconditioned PV^\dag D_{dwf}
// KrylovSchur KrySchur (DLinOp, FGrid, 1e-8, RF); // use D_{dwf}
KrySchur(src, maxIter, Nm, Nk, Nstop);
std::cout<<GridLogMessage << "*******************************************" << std::endl;
std::cout<<GridLogMessage << "***************** RESULTS *****************" << std::endl;
std::cout<<GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << "Krylov Schur eigenvalues: " << std::endl << KrySchur.getEvals() << std::endl;
writeEigensystem(KrySchur, outDir);
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage << "Done "<< std::endl;
Grid_finalize();
return 0;
}
+4 -2
View File
@@ -3,6 +3,9 @@
* without regression / tests being applied
*/
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace std;
@@ -535,5 +538,4 @@ int main (int argc, char ** argv)
Grid_finalize();
}
#endif
+4 -2
View File
@@ -3,6 +3,9 @@
* without regression / tests being applied
*/
#include "disable_examples_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace std;
@@ -429,5 +432,4 @@ int main (int argc, char ** argv)
Grid_finalize();
}
#endif
+383
View File
@@ -0,0 +1,383 @@
/*************************************************************************************
Script for studying the Wilson eigenvectors resulting from the Krylov-Schur process.
Usage :
$ ./Example_spec_kryschur <Nm> <Nk> <maxiter> <Nstop> <inFile> <outDir> <?rf>
Nm = Maximum size of approximation subspace.
Nk = Size of truncation subspace
maxiter = Maximum number of iterations.
Nstop = Stop when Nstop eigenvalues have converged.
inFile = Gauge configuration to read in.
outDir = Directory to write output to.
rf = (Optional) RitzFilter to sort with. Takes in any string in
{EvalNormSmall, EvalNormLarge, EvalReSmall, EvalReLarge, EvalImSmall, EvalImLarge}
Output:
${outDir}/evals.txt = Contains all eigenvalues. Each line is formatted as `$idx $eval $ritz`, where:
- $idx is the index of the eigenvalue.
- $eval is the eigenvalue, formated as "(re,im)".
- $ritz is the Ritz estimate of the eigenvalue (deviation from being a true eigenvalue)
${outDir}/evec${idx} = Eigenvector $idx written out in SCIDAC format (if LIME is enabled).
Grid physics library, www.github.com/paboyle/Grid
Source file: ./tests/Test_padded_cell.cc
Copyright (C) 2023
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: Patrick Oare <poare@bnl.edu>
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 */
#include <cstdlib>
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidual.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
#include <Grid/algorithms/iterative/BiCGSTAB.h>
#include <Grid/parallelIO/IldgIOtypes.h>
#include <Grid/parallelIO/IldgIO.h>
using namespace std;
using namespace Grid;
template <class T> void writeFile(T& in, std::string const fname){
#ifdef HAVE_LIME
// Ref: https://github.com/paboyle/Grid/blob/feature/scidac-wp1/tests/debug/Test_general_coarse_hdcg_phys48.cc#L111
std::cout << Grid::GridLogMessage << "Writes to: " << fname << std::endl;
Grid::emptyUserRecord record;
Grid::ScidacWriter WR(in.Grid()->IsBoss());
WR.open(fname);
WR.writeScidacFieldRecord(in,record,0); // Lexico
WR.close();
#endif
}
template <class T> void readFile(T& out, std::string const fname){
#ifdef HAVE_LIME
// Ref: https://github.com/paboyle/Grid/blob/feature/scidac-wp1/tests/debug/Test_general_coarse_hdcg_phys48.cc#L111
std::cout << Grid::GridLogMessage << "Reads at: " << fname << std::endl;
Grid::emptyUserRecord record;
// Grid::ScidacReader SR(out.Grid()->IsBoss());
Grid::ScidacReader SR;
SR.open(fname);
SR.readScidacFieldRecord(out, record);
SR.close();
#endif
}
/**
* Writes the eigensystem of a Krylov Schur object to a directory.
*
* Parameters
* ----------
* std::string path
* Directory to write to.
*/
template <class Field>
void writeEigensystem(KrylovSchur<Field> KS, std::string outDir) {
int Nk = KS.getNk();
std::cout << GridLogMessage << "Writing output to directory: " << outDir << std::endl;
// Write evals
std::string evalPath = outDir + "/evals.txt";
std::ofstream fEval;
fEval.open(evalPath);
Eigen::VectorXcd evals = KS.getEvals();
std::vector<RealD> ritz = KS.getRitzEstimates();
for (int i = 0; i < Nk; i++) {
// write eigenvalues and Ritz estimates
fEval << i << " " << evals(i) << " " << ritz[i];
if (i < Nk - 1) { fEval << "\n"; }
}
fEval.close();
// Write evecs
int Nevecs = Nk; // don't write all of them
std::vector<Field> evecs = KS.getEvecs();
for (int i = 0; i < Nevecs; i++) {
std::string fName = outDir + "/evec" + std::to_string(i);
writeFile(evecs[i], fName); // using method from Grid/HMC/ComputeWilsonFlow.cc
}
}
// Hermitize a DWF operator by squaring it
template<class Matrix,class Field>
class SquaredLinearOperator : public LinearOperatorBase<Field> {
public:
Matrix &_Mat;
public:
SquaredLinearOperator(Matrix &Mat): _Mat(Mat) {};
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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
// std::cout << "Op is overloaded as HermOp" << std::endl;
HermOp(in, out);
}
void AdjOp (const Field &in, Field &out){
HermOp(in, out);
}
void _Op (const Field &in, Field &out){
// std::cout << "Op: M "<<std::endl;
_Mat.M(in, out);
}
void _AdjOp (const Field &in, Field &out){
// std::cout << "AdjOp: Mdag "<<std::endl;
_Mat.Mdag(in, out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
// std::cout << "HermOp: Mdag M Mdag M"<<std::endl;
Field tmp(in.Grid());
_Op(in,tmp);
_AdjOp(tmp,out);
}
};
template<class Matrix,class Field>
class PVdagMLinearOperator : public LinearOperatorBase<Field> {
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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(in,tmp);
_Mat.Mdag(tmp,out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
// _Mat.M(in,tmp);
// _PV.Mdag(tmp,out);
// _PV.M(out,tmp);
// _Mat.Mdag(tmp,out);
Op(in,tmp);
AdjOp(tmp,out);
// std::cout << "HermOp done "<<norm2(out)<<std::endl;
}
};
template<class Matrix,class Field>
class ShiftedPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
RealD shift;
public:
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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
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){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
};
template<class Matrix, class Field>
class ShiftedComplexPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
ComplexD shift;
public:
ShiftedComplexPVdagMLinearOperator(ComplexD _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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
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){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
void resetShift(ComplexD newShift) {
shift = newShift;
}
};
int main (int argc, char ** argv)
{
Grid_init(&argc,&argv);
// Usage : $ ./Example_wilson_evecs ${inFile}
std::string file = argv[1];
const int Ls=16;
// GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(), GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());
//std::vector<int> lat_size {16, 16, 16, 32};
std::vector<int> lat_size {32, 32, 32, 32};
std::cout << "Lattice size: " << lat_size << std::endl;
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);
GridCartesian * FGrid = UGrid;
GridRedBlackCartesian * FrbGrid = UrbGrid;
std::vector<int> seeds4({1,2,3,4});
GridParallelRNG RNG4(UGrid);
RNG4.SeedFixedIntegers(seeds4);
LatticeFermion src(FGrid); random(RNG4, src);
LatticeGaugeField Umu(UGrid);
FieldMetaData header;
NerscIO::readConfiguration(Umu, header, file);
std::cout << GridLogMessage << "Loaded configuration" << std::endl;
// RealD mass = 0.01;
RealD M5 = 1.8;
// Wilson mass
RealD mass = -1.6;
std::cout << GridLogMessage << "masses specified" << std::endl;
std::vector<Complex> boundary = {1,1,1,-1};
WilsonFermionD::ImplParams Params(boundary);
// DomainWallFermionD Ddwf(Umu, *FGrid, *FrbGrid, *UGrid, *UrbGrid, mass, M5);
// NonHermitianLinearOperator<DomainWallFermionD, LatticeFermionD> DLinOp (Ddwf);
// WilsonFermionD Dwilson(Umu, *FGrid, *FrbGrid, mass);
WilsonFermionD Dwilson(Umu, *UGrid, *UrbGrid, mass, Params);
NonHermitianLinearOperator<WilsonFermionD, LatticeFermionD> DLinOp (Dwilson);
std::cout << GridLogMessage << "Dirac operator defined" << std::endl;
std::string eigenPath = "/home/poare/lqcd/multigrid/spectra/32cube-rho0.124-tau4/U_smr_3.000000/Nm72_Nk24_8111835.aurora-pbs-0001.hostmgmt.cm.aurora.alcf.anl.gov/";
std::cout << GridLogMessage << "Loading eigenvalues" << std::endl;
std::ifstream evalFile(eigenPath + "evals.txt");
std::string str;
std::vector<ComplexD> evals;
while (std::getline(evalFile, str)) {
std::cout << GridLogMessage << "Reading line: " << str << std::endl;
int i1 = str.find("(") + 1;
int i2 = str.find(",") + 1;
int i3 = str.find(")");
std::cout << "i1,i2,i3 = " << i1 << "," << i2 << "," << i3 << std::endl;
std::string reStr = str.substr(i1, i2 - i1);
std::string imStr = str.substr(i2, i3 - i2);
std::cout << GridLogMessage << "Parsed re = " << reStr << " and im = " << imStr << std::endl;
// ComplexD z (std::stof(reStr), std::stof(imStr));
ComplexD z (std::stod(reStr), std::stod(imStr));
evals.push_back(z);
}
std::cout << GridLogMessage << "Eigenvalues: " << evals << std::endl;
int Nevecs = 24;
std::vector<LatticeFermion> evecs;
LatticeFermion evec (FGrid);
for (int i = 0; i < Nevecs; i++) {
std::string evecPath = eigenPath + "evec" + std::to_string(i);
readFile(evec, evecPath);
evecs.push_back(evec);
}
std::cout << GridLogMessage << "Evecs loaded" << std::endl;
// Compute < evec | D - \lambda | evec >
std::cout << GridLogMessage << "Testing eigenvectors" << std::endl;
LatticeFermion Devec (FGrid);
ComplexD ritz;
for (int i = 0; i < Nevecs; i++) {
Devec = Zero();
DLinOp.Op(evecs[i], Devec);
ritz = std::sqrt(norm2(Devec - evals[i] * evecs[i]));
std::cout << GridLogMessage << "i = " << i << ", || (D - lambda) |vi> || = " << ritz << std::endl;
}
// Eigen::MatrixXcd Dw_evecs;
// Dw_evecs = Eigen::MatrixXcd::Zero(Nevecs, Nevecs);
// for (int i = 0; i < Nevecs; i++) {
// Linop.Op(evecs[i], Devec);
// for (int j = 0; j < Nevecs; j++) {
// }
// }
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage << "Done "<< std::endl;
Grid_finalize();
return 0;
}
+374
View File
@@ -0,0 +1,374 @@
/*************************************************************************************
Runs the Krylov-Schur algorithm on a Wilson fermion operator to determine part of its spectrum.
TODO rename this file: really is running the topology change jobs on Aurora.
Usage :
$ ./Example_spec_kryschur <Nm> <Nk> <maxiter> <Nstop> <inFile> <outDir> <?rf>
Nm = Maximum size of approximation subspace.
Nk = Size of truncation subspace
maxiter = Maximum number of iterations.
Nstop = Stop when Nstop eigenvalues have converged.
inFile = Gauge configuration to read in.
outDir = Directory to write output to.
rf = (Optional) RitzFilter to sort with. Takes in any string in
{EvalNormSmall, EvalNormLarge, EvalReSmall, EvalReLarge, EvalImSmall, EvalImLarge}
Output:
${outDir}/evals.txt = Contains all eigenvalues. Each line is formatted as `$idx $eval $ritz`, where:
- $idx is the index of the eigenvalue.
- $eval is the eigenvalue, formated as "(re,im)".
- $ritz is the Ritz estimate of the eigenvalue (deviation from being a true eigenvalue)
${outDir}/evec${idx} = Eigenvector $idx written out in SCIDAC format (if LIME is enabled).
Grid physics library, www.github.com/paboyle/Grid
Source file: ./tests/Test_padded_cell.cc
Copyright (C) 2023
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
Author: Patrick Oare <poare@bnl.edu>
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 */
#include <cstdlib>
#include <Grid/Grid.h>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidual.h>
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
#include <Grid/algorithms/iterative/BiCGSTAB.h>
#include <Grid/parallelIO/IldgIOtypes.h>
#include <Grid/parallelIO/IldgIO.h>
using namespace std;
using namespace Grid;
template <class T> void writeFile(T& in, std::string const fname){
#ifdef HAVE_LIME
// Ref: https://github.com/paboyle/Grid/blob/feature/scidac-wp1/tests/debug/Test_general_coarse_hdcg_phys48.cc#L111
std::cout << Grid::GridLogMessage << "Writes to: " << fname << std::endl;
Grid::emptyUserRecord record;
Grid::ScidacWriter WR(in.Grid()->IsBoss());
WR.open(fname);
WR.writeScidacFieldRecord(in,record,0); // Lexico
WR.close();
#endif
}
/**
* Writes the eigensystem of a Krylov Schur object to a directory.
*
* Parameters
* ----------
* std::string path
* Directory to write to.
*/
template <class Field>
void writeEigensystem(KrylovSchur<Field> KS, std::string outDir) {
int Nk = KS.getNk();
std::cout << GridLogMessage << "Writing output to directory: " << outDir << std::endl;
// Write evals
std::string evalPath = outDir + "/evals.txt";
std::ofstream fEval;
fEval.open(evalPath);
Eigen::VectorXcd evals = KS.getEvals();
std::vector<RealD> ritz = KS.getRitzEstimates();
for (int i = 0; i < Nk; i++) {
// write eigenvalues and Ritz estimates
fEval << i << " " << evals(i) << " " << ritz[i];
if (i < Nk - 1) { fEval << "\n"; }
}
fEval.close();
// Write evecs
int Nevecs = Nk; // don't write all of them
std::vector<Field> evecs = KS.getEvecs();
for (int i = 0; i < Nevecs; i++) {
std::string fName = outDir + "/evec" + std::to_string(i);
writeFile(evecs[i], fName); // using method from Grid/HMC/ComputeWilsonFlow.cc
}
}
// Hermitize a DWF operator by squaring it
template<class Matrix,class Field>
class SquaredLinearOperator : public LinearOperatorBase<Field> {
public:
Matrix &_Mat;
public:
SquaredLinearOperator(Matrix &Mat): _Mat(Mat) {};
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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
// std::cout << "Op is overloaded as HermOp" << std::endl;
HermOp(in, out);
}
void AdjOp (const Field &in, Field &out){
HermOp(in, out);
}
void _Op (const Field &in, Field &out){
// std::cout << "Op: M "<<std::endl;
_Mat.M(in, out);
}
void _AdjOp (const Field &in, Field &out){
// std::cout << "AdjOp: Mdag "<<std::endl;
_Mat.Mdag(in, out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
// std::cout << "HermOp: Mdag M Mdag M"<<std::endl;
Field tmp(in.Grid());
_Op(in,tmp);
_AdjOp(tmp,out);
}
};
template<class Matrix,class Field>
class PVdagMLinearOperator : public LinearOperatorBase<Field> {
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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
Field tmp(in.Grid());
_PV.M(in,tmp);
_Mat.Mdag(tmp,out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); }
void HermOp(const Field &in, Field &out){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
// _Mat.M(in,tmp);
// _PV.Mdag(tmp,out);
// _PV.M(out,tmp);
// _Mat.Mdag(tmp,out);
Op(in,tmp);
AdjOp(tmp,out);
// std::cout << "HermOp done "<<norm2(out)<<std::endl;
}
};
template<class Matrix,class Field>
class ShiftedPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
RealD shift;
public:
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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
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){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
};
template<class Matrix, class Field>
class ShiftedComplexPVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
Matrix &_PV;
ComplexD shift;
public:
ShiftedComplexPVdagMLinearOperator(ComplexD _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<Field> &out){ assert(0); };
void Op (const Field &in, Field &out){
std::cout << "Op: PVdag M "<<std::endl;
Field tmp(in.Grid());
_Mat.M(in,tmp);
_PV.Mdag(tmp,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
std::cout << "AdjOp: Mdag PV "<<std::endl;
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){
std::cout << "HermOp: Mdag PV PVdag M"<<std::endl;
Field tmp(in.Grid());
Op(in,tmp);
AdjOp(tmp,out);
}
void resetShift(ComplexD newShift) {
shift = newShift;
}
};
int main (int argc, char ** argv)
{
Grid_init(&argc,&argv);
// Usage : $ ./Example_spec_kryschur <Nm> <Nk> <maaxiter> <Nstop> <inFile> <outDir>
std::string NmStr = argv[1];
std::string NkStr = argv[2];
std::string maxIterStr = argv[3];
std::string NstopStr = argv[4];
std::string file = argv[5];
std::string outDir = argv[6];
// RitzFilter RF;
// if (argc == 8) {
// std::string rf = argv[7];
// RF = selectRitzFilter(rf);
// } else {
// RF = EvalReSmall;
// }
// RitzFilter RF;
std::string rf = argv[7];
RitzFilter RF = selectRitzFilter(rf);
std::cout << "Sorting eigenvalues using " << rfToString(RF) << std::endl;
const int Ls=16;
// GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(), GridDefaultSimd(Nd,vComplex::Nsimd()),GridDefaultMpi());
//std::vector<int> lat_size {16, 16, 16, 32};
std::vector<int> lat_size {32, 32, 32, 32};
std::cout << "Lattice size: " << lat_size << std::endl;
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);
GridCartesian * FGrid = UGrid;
GridRedBlackCartesian * FrbGrid = UrbGrid;
std::vector<int> seeds4({1,2,3,4});
GridParallelRNG RNG4(UGrid);
RNG4.SeedFixedIntegers(seeds4);
LatticeFermion src(FGrid); random(RNG4, src);
LatticeGaugeField Umu(UGrid);
FieldMetaData header;
NerscIO::readConfiguration(Umu, header, file);
std::cout << GridLogMessage << "Loaded configuration" << std::endl;
// RealD mass = 0.01;
RealD M5 = 1.8;
// Wilson mass
RealD mass = -1.6;
std::cout << GridLogMessage << "masses specified" << std::endl;
std::vector<Complex> boundary = {1,1,1,-1};
WilsonFermionD::ImplParams Params(boundary);
// DomainWallFermionD Ddwf(Umu, *FGrid, *FrbGrid, *UGrid, *UrbGrid, mass, M5);
// NonHermitianLinearOperator<DomainWallFermionD, LatticeFermionD> DLinOp (Ddwf);
// WilsonFermionD Dwilson(Umu, *FGrid, *FrbGrid, mass);
WilsonFermionD Dwilson(Umu, *UGrid, *UrbGrid, mass, Params);
NonHermitianLinearOperator<WilsonFermionD, LatticeFermionD> DLinOp (Dwilson);
std::cout << GridLogMessage << "Dirac operator defined" << std::endl;
// Define PV^dag D (if we want)
// DomainWallFermionD Dpv(Umu, *FGrid, *FrbGrid, *UGrid, *UrbGrid, 1.0, M5);
// typedef PVdagMLinearOperator<DomainWallFermionD,LatticeFermionD> PVdagM_t;
// PVdagM_t PVdagM(Ddwf, Dpv);
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
// SquaredLinearOperator<WilsonFermionD, LatticeFermionD> Dsq (DWilson);
// NonHermitianLinearOperator<WilsonFermionD, LatticeFermionD> DLinOp (DWilson);
int Nm = std::stoi(NmStr);
int Nk = std::stoi(NkStr);
int maxIter = std::stoi(maxIterStr);
int Nstop = std::stoi(NstopStr);
std::cout << GridLogMessage << "Runnning Krylov Schur. Nm = " << Nm << ", Nk = " << Nk << ", maxIter = " << maxIter
<< ", Nstop = " << Nstop << std::endl;
// KrylovSchur KrySchur (PVdagM, FGrid, 1e-8, RF); // use PV^\dag M
KrylovSchur KrySchur (DLinOp, FGrid, 1e-8, RF); // use Ddwf
KrySchur(src, maxIter, Nm, Nk, Nstop);
std::cout << GridLogMessage << "Checking eigensystem." << std::endl;
KrySchur.checkRitzEstimate();
std::cout<<GridLogMessage << "*******************************************" << std::endl;
std::cout<<GridLogMessage << "***************** RESULTS *****************" << std::endl;
std::cout<<GridLogMessage << "*******************************************" << std::endl;
std::cout << GridLogMessage << "Krylov Schur eigenvalues: " << std::endl << KrySchur.getEvals() << std::endl;
writeEigensystem(KrySchur, outDir);
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage<<"*******************************************"<<std::endl;
std::cout<<GridLogMessage<<std::endl;
std::cout<<GridLogMessage << "Done "<< std::endl;
Grid_finalize();
return 0;
}
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0"?>
<grid>
<LanczosParameters>
<mass>0</mass>
<mstep>-0.025</mstep>
<M5>1.8</M5>
<Ls>48</Ls>
<Nstop>100</Nstop>
<Nk>100</Nk>
<Np>100</Np>
<ReadEvec>0</ReadEvec>
<maxIter>1000</maxIter>
<reorthog>1</reorthog>
<Nblock>4</Nblock>
<verify>0</verify>
<shift>1.5</shift>
<resid>1e-8</resid>
<ChebyLow>1</ChebyLow>
<ChebyHigh>100</ChebyHigh>
<ChebyOrder>51</ChebyOrder>
</LanczosParameters>
</grid>
@@ -0,0 +1,15 @@
#include <Grid/Grid.h>
#pragma once
#ifndef ENABLE_FERMION_INSTANTIATIONS
#include <iostream>
int main(void) {
std::cout << "This build of Grid was configured to exclude fermion instantiations, "
<< "which this example relies on. "
<< "Please reconfigure and rebuild Grid with --enable-fermion-instantiations"
<< "to run this example."
<< std::endl;
return 1;
}
#endif
+469
View File
@@ -0,0 +1,469 @@
/*************************************************************************************
fft5d.cc Fourier analysis of a time series of 4-D lattice scalar fields.
Designed for force-norm files from FTHMC (one RealD per site, SCIDAC or binary).
Assembles a (4+1)-D structure [trajectory][t][z][y][x] using Grid lattice fields
and performs three FFT analyses:
--fft spatial
4-D spatial FFT using Grid's FFT class (MPI+GPU parallel via Cshift/cufft).
Outputs shell-averaged P(|k|^2) averaged over trajectories, and a
per-mode table P(kt,kz,ky,kx).
--fft traj
1-D trajectory-axis FFT at each lattice site using FFTW3 locally at each
MPI rank. Outputs site-averaged P(f_traj) and autocorrelation C(lag).
Results are combined with GlobalSumVector.
--fft all
Both of the above, plus a 2-D cross spectrum P(f_traj, |k_spatial|^2):
spatial FFT per trajectory, then trajectory FFTW on the momentum-space series.
Normalisations:
Spatial FFT (volume V): P_spatial(k) = |F(k)|^2 / V^2
=> (1/V) * sum_k P(k) = site mean-square per trajectory
Trajectory FFT (Ntraj): P_traj(f) = |F(f)|^2 / Ntraj^2
=> sum_f P(f) = site mean-square over trajectories
Build: add to examples/Make.inc (see bottom of this file), then make.
Usage (follows Grid conventions):
fft5d --grid Lx.Ly.Lz.Lt [--mpi Px.Py.Pz.Pt] \
[--fft spatial|traj|all] [--format scidac|binary] \
[--output PREFIX] file1 file2 ...
*************************************************************************************/
#include <Grid/Grid.h>
// Grid's FFT.h uses cufft on CUDA builds; for the trajectory axis we also need
// CPU-side FFTW3 (already linked via -lfftw3 in GRID_LIBS).
#include <fftw3.h>
#include <algorithm>
#include <cassert>
#include <cmath>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
using namespace Grid;
// ─────────────────────────────────────────────────────────────────────────────
// Global coordinate of a local site (osite, lane) on this MPI rank
// ─────────────────────────────────────────────────────────────────────────────
static void globalCoor(int osite, int lane, GridCartesian* g, Coordinate& gc)
{
Coordinate oc, ic;
Lexicographic::CoorFromIndex(oc, osite, g->_rdimensions);
Lexicographic::CoorFromIndex(ic, lane, g->_simd_layout);
for (int d = 0; d < Nd; d++)
gc[d] = g->_processor_coor[d] * g->_ldimensions[d]
+ oc[d] * g->_simd_layout[d] + ic[d];
}
// ─────────────────────────────────────────────────────────────────────────────
// Shell-average a power LatticeRealD over |k|^2 bins (MPI-aware via GlobalSumVector)
// ─────────────────────────────────────────────────────────────────────────────
static void shellAverage(const LatticeRealD& power, GridCartesian* g,
double norm, std::ostream& ofs)
{
const int Nsimd = vRealD::Nsimd();
Coordinate fdims = g->_fdimensions;
int maxk2 = 0;
for (int d = 0; d < Nd; d++) { int h = fdims[d]/2; maxk2 += h*h; }
std::vector<RealD> psum(maxk2+1, 0.0), cnt(maxk2+1, 0.0);
{
auto pv = power.View(CpuRead);
Coordinate gc(Nd);
for (int os = 0; os < (int)g->oSites(); os++) {
for (int lane = 0; lane < Nsimd; lane++) {
globalCoor(os, lane, g, gc);
int k2 = 0;
for (int d = 0; d < Nd; d++) {
int kd = std::min(gc[d], fdims[d] - gc[d]);
k2 += kd*kd;
}
psum[k2] += (RealD)extractLane(lane, pv[os]);
cnt [k2] += 1.0;
}
}
}
g->GlobalSumVector(psum.data(), (int)psum.size());
g->GlobalSumVector(cnt .data(), (int)cnt .size());
for (int k2 = 0; k2 <= maxk2; k2++)
if (cnt[k2] > 0)
ofs << k2 << " " << std::sqrt((double)k2) << " "
<< (psum[k2] / cnt[k2]) * norm << "\n";
}
// ─────────────────────────────────────────────────────────────────────────────
// Convert LatticeRealD → LatticeComplexD (zero imaginary part)
// ─────────────────────────────────────────────────────────────────────────────
static LatticeComplexD toComplex(const LatticeRealD& r)
{
std::vector<RealD> lr;
unvectorizeToLexOrdArray(lr, r);
std::vector<ComplexD> lc(lr.size());
for (size_t i = 0; i < lr.size(); i++) lc[i] = ComplexD(lr[i], 0.0);
LatticeComplexD c(r.Grid());
vectorizeFromLexOrdArray(lc, c);
return c;
}
// ─────────────────────────────────────────────────────────────────────────────
// File readers
// ─────────────────────────────────────────────────────────────────────────────
static LatticeComplexD readScidac(const std::string& fname, GridCartesian* g)
{
LatticeRealD field(g);
emptyUserRecord rec;
ScidacReader RD;
RD.open(fname);
RD.readScidacFieldRecord(field, rec);
RD.close();
return toComplex(field);
}
static LatticeComplexD readBinary(const std::string& fname, GridCartesian* g)
{
// Raw IEEE doubles in lex order [x][y][z][t] written serially.
// Rank 0 reads the file, broadcasts to all ranks via GlobalSumVector.
Coordinate fdims = g->_fdimensions;
long vol4 = 1; for (int d = 0; d < Nd; d++) vol4 *= fdims[d];
std::vector<RealD> buf(vol4, 0.0);
if (g->IsBoss()) {
std::ifstream f(fname, std::ios::binary);
if (!f) throw std::runtime_error("Cannot open: " + fname);
f.read(reinterpret_cast<char*>(buf.data()), vol4 * sizeof(RealD));
}
g->GlobalSumVector(buf.data(), (int)vol4); // broadcast from rank 0
LatticeRealD field(g);
Coordinate ldims = g->_ldimensions, pcoor = g->_processor_coor;
for (long ls = 0; ls < g->lSites(); ls++) {
Coordinate lc;
g->LocalIndexToLocalCoor(ls, lc);
long glex = 0, stride = 1;
for (int d = 0; d < Nd; d++) {
glex += (pcoor[d]*ldims[d] + lc[d]) * stride;
stride *= fdims[d];
}
pokeLocalSite(buf[glex], field, lc);
}
return toComplex(field);
}
// ─────────────────────────────────────────────────────────────────────────────
// Load all files into a trajectory vector
// ─────────────────────────────────────────────────────────────────────────────
static std::vector<LatticeComplexD>
loadFiles(const std::vector<std::string>& files, GridCartesian* g,
const std::string& fmt)
{
std::vector<LatticeComplexD> traj;
traj.reserve(files.size());
for (int n = 0; n < (int)files.size(); n++) {
std::cout << GridLogMessage << "[" << n << "] reading " << files[n] << "\n";
if (fmt == "scidac") traj.push_back(readScidac(files[n], g));
else traj.push_back(readBinary (files[n], g));
}
return traj;
}
// ─────────────────────────────────────────────────────────────────────────────
// Spatial FFT analysis
// ─────────────────────────────────────────────────────────────────────────────
static void analyzeSpatial(const std::vector<LatticeComplexD>& traj,
GridCartesian* g, const std::string& pfx)
{
int Ntraj = (int)traj.size();
long vol4 = 1; for (int d = 0; d < Nd; d++) vol4 *= g->_fdimensions[d];
FFT theFFT(g);
LatticeRealD pavg(g); pavg = Zero();
for (int n = 0; n < Ntraj; n++) {
LatticeComplexD fk(g);
theFFT.FFT_all_dim(fk, traj[n], FFT::forward);
// Evaluate product before applying real() — real() is not defined for
// unevaluated LatticeBinaryExpression.
LatticeComplexD fk_sq(g); fk_sq = conjugate(fk) * fk;
std::vector<ComplexD> lc; unvectorizeToLexOrdArray(lc, fk_sq);
std::vector<RealD> lr(lc.size());
for (size_t i = 0; i < lc.size(); i++) lr[i] = lc[i].real();
LatticeRealD pk(g); vectorizeFromLexOrdArray(lr, pk);
pavg += pk;
}
pavg *= (1.0 / Ntraj);
// Shell-averaged spectrum
{
std::ofstream fs(pfx + "_spatial_shell.dat");
fs << "# k2 |k| P_shell_avg (P = |F|^2 / V^2 / shell_count)\n";
shellAverage(pavg, g, 1.0 / ((double)vol4 * vol4), fs);
std::cout << GridLogMessage << "Written: " << pfx << "_spatial_shell.dat\n";
}
// Per-mode table (rank 0 only, via peekSite)
if (g->IsBoss()) {
std::ofstream fm(pfx + "_spatial_modes.dat");
fm << "# kt kz ky kx k2 |k| P_traj_avg\n";
Coordinate fdims = g->_fdimensions, site(Nd);
double norm = 1.0 / ((double)vol4 * vol4);
for (site[3]=0; site[3]<fdims[3]; site[3]++) { int kt=std::min(site[3],fdims[3]-site[3]);
for (site[2]=0; site[2]<fdims[2]; site[2]++) { int kz=std::min(site[2],fdims[2]-site[2]);
for (site[1]=0; site[1]<fdims[1]; site[1]++) { int ky=std::min(site[1],fdims[1]-site[1]);
for (site[0]=0; site[0]<fdims[0]; site[0]++) { int kx=std::min(site[0],fdims[0]-site[0]);
RealD p; peekSite(p, pavg, site);
int k2 = kt*kt+kz*kz+ky*ky+kx*kx;
fm << kt<<" "<<kz<<" "<<ky<<" "<<kx<<" "<<k2<<" "
<<std::sqrt((double)k2)<<" "<<p*norm<<"\n";
}}}}
std::cout << GridLogMessage << "Written: " << pfx << "_spatial_modes.dat\n";
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Local FFTW trajectory FFT: each rank FFTs its own local sites independently
// ─────────────────────────────────────────────────────────────────────────────
static void trajFFT(const std::vector<LatticeComplexD>& in,
std::vector<LatticeComplexD>& out)
{
int Ntraj = (int)in.size();
GridBase* g = in[0].Grid();
long lsites = g->lSites();
// fftw_complex is double[2] — std::vector<double[2]> is not supported by nvcc.
// Use std::vector<double> with 2x size and reinterpret_cast.
std::vector<double> ibuf((long)Ntraj * lsites * 2, 0.0);
for (int n = 0; n < Ntraj; n++) {
std::vector<ComplexD> lc;
unvectorizeToLexOrdArray(lc, in[n]);
for (long s = 0; s < lsites; s++) {
ibuf[((long)n*lsites + s)*2 + 0] = lc[s].real();
ibuf[((long)n*lsites + s)*2 + 1] = lc[s].imag();
}
}
// lsites transforms of length Ntraj, stride=lsites, dist=1
std::vector<double> obuf((long)Ntraj * lsites * 2, 0.0);
fftw_complex* iptr = reinterpret_cast<fftw_complex*>(ibuf.data());
fftw_complex* optr = reinterpret_cast<fftw_complex*>(obuf.data());
int n1[1] = {Ntraj};
fftw_plan p = fftw_plan_many_dft(
1, n1, (int)lsites,
iptr, nullptr, (int)lsites, 1,
optr, nullptr, (int)lsites, 1,
FFTW_FORWARD, FFTW_ESTIMATE);
fftw_execute(p);
fftw_destroy_plan(p);
// vector::assign triggers _M_fill_assign which needs a default constructor;
// Lattice has none. Use explicit emplace_back instead.
out.clear(); out.reserve(Ntraj);
for (int k = 0; k < Ntraj; k++) out.emplace_back(g);
for (int k = 0; k < Ntraj; k++) {
std::vector<ComplexD> lc(lsites);
for (long s = 0; s < lsites; s++)
lc[s] = ComplexD(obuf[((long)k*lsites+s)*2], obuf[((long)k*lsites+s)*2+1]);
vectorizeFromLexOrdArray(lc, out[k]);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Trajectory-axis FFT analysis
// ─────────────────────────────────────────────────────────────────────────────
static void analyzeTraj(const std::vector<LatticeComplexD>& traj,
GridCartesian* g, const std::string& pfx)
{
int Ntraj = (int)traj.size();
int Nf = Ntraj / 2 + 1;
long vol4 = 1; for (int d = 0; d < Nd; d++) vol4 *= g->_fdimensions[d];
std::vector<LatticeComplexD> ftraj;
trajFFT(traj, ftraj);
// P(k) = (1/vol4) * sum_sites |F_traj(k)|^2 / Ntraj^2
std::vector<RealD> Pavg(Ntraj, 0.0);
for (int k = 0; k < Ntraj; k++) {
LatticeComplexD tmp(g); tmp = conjugate(ftraj[k]) * ftraj[k];
std::vector<ComplexD> lc; unvectorizeToLexOrdArray(lc, tmp);
std::vector<RealD> lr(lc.size());
for (size_t i = 0; i < lc.size(); i++) lr[i] = lc[i].real();
LatticeRealD pk(g); vectorizeFromLexOrdArray(lr, pk);
RealD s = 0.0;
for (long ls = 0; ls < g->lSites(); ls++) {
Coordinate lc; g->LocalIndexToLocalCoor(ls, lc);
RealD v; peekLocalSite(v, pk, lc);
s += v;
}
g->GlobalSum(s);
Pavg[k] = s / ((double)vol4 * Ntraj * Ntraj);
}
{
std::ofstream f(pfx + "_traj_power.dat");
f << "# k freq P_avg (sum_k P = site mean-square)\n";
for (int k = 0; k < Nf; k++)
f << k << " " << (double)k/Ntraj << " " << Pavg[k] << "\n";
std::cout << GridLogMessage << "Written: " << pfx << "_traj_power.dat\n";
}
// Autocorrelation via IFFT of the power spectrum
std::vector<double> Pc_buf(Nf * 2, 0.0);
for (int k = 0; k < Nf; k++) { Pc_buf[k*2] = Pavg[k]; Pc_buf[k*2+1] = 0.0; }
fftw_complex* Pc = reinterpret_cast<fftw_complex*>(Pc_buf.data());
std::vector<double> acorr(Ntraj, 0.0);
fftw_plan ip = fftw_plan_dft_c2r(1, &Ntraj, Pc, acorr.data(), FFTW_ESTIMATE);
fftw_execute(ip); fftw_destroy_plan(ip);
double c0 = acorr[0] / Ntraj;
{
std::ofstream f(pfx + "_traj_autocorr.dat");
f << "# lag C(lag) C(lag)/C(0)\n";
for (int lag = 0; lag < Ntraj/2; lag++) {
double c = acorr[lag] / Ntraj;
f << lag << " " << c << " " << (c0 > 0 ? c/c0 : 0.0) << "\n";
}
std::cout << GridLogMessage << "Written: " << pfx << "_traj_autocorr.dat\n";
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Full 5-D analysis: spatial FFT → trajectory FFT → 2-D cross spectrum
// ─────────────────────────────────────────────────────────────────────────────
static void analyzeAll5D(const std::vector<LatticeComplexD>& traj,
GridCartesian* g, const std::string& pfx)
{
int Ntraj = (int)traj.size();
int Nf = Ntraj / 2 + 1;
long vol4 = 1; for (int d = 0; d < Nd; d++) vol4 *= g->_fdimensions[d];
Coordinate fdims = g->_fdimensions;
// Step 1: spatial FFT for each trajectory
FFT theFFT(g);
std::vector<LatticeComplexD> sfft(Ntraj, LatticeComplexD(g));
for (int n = 0; n < Ntraj; n++)
theFFT.FFT_all_dim(sfft[n], traj[n], FFT::forward);
// Step 2: trajectory FFTW on the momentum-space series
std::vector<LatticeComplexD> tfft;
trajFFT(sfft, tfft);
// Step 3: P(f_traj, |k_spatial|^2) shell-averaged
int maxk2 = 0;
for (int d = 0; d < Nd; d++) { int h = fdims[d]/2; maxk2 += h*h; }
long nb = (long)Nf * (maxk2+1);
std::vector<RealD> p2d(nb, 0.0), cnt2d(nb, 0.0);
const int Nsimd = vComplexD::Nsimd();
double norm = 1.0 / ((double)Ntraj * Ntraj * vol4 * vol4);
for (int kf = 0; kf < Nf; kf++) {
LatticeComplexD tmp(g); tmp = conjugate(tfft[kf]) * tfft[kf];
std::vector<ComplexD> lc2; unvectorizeToLexOrdArray(lc2, tmp);
std::vector<RealD> lr2(lc2.size());
for (size_t i = 0; i < lc2.size(); i++) lr2[i] = lc2[i].real();
LatticeRealD pk(g); vectorizeFromLexOrdArray(lr2, pk);
auto pv = pk.View(CpuRead);
Coordinate gc(Nd);
for (int os = 0; os < (int)g->oSites(); os++) {
for (int lane = 0; lane < Nsimd; lane++) {
globalCoor(os, lane, g, gc);
int k2 = 0;
for (int d = 0; d < Nd; d++) {
int kd = std::min(gc[d], fdims[d] - gc[d]);
k2 += kd*kd;
}
long b = (long)kf*(maxk2+1) + k2;
p2d [b] += (RealD)extractLane(lane, pv[os]);
cnt2d[b] += 1.0;
}
}
}
g->GlobalSumVector(p2d .data(), (int)nb);
g->GlobalSumVector(cnt2d.data(), (int)nb);
if (g->IsBoss()) {
std::ofstream f(pfx + "_5d_cross.dat");
f << "# k_traj freq_traj k2_spatial |k_spatial| P_avg\n";
for (int kf = 0; kf < Nf; kf++) {
double freq = (double)kf / Ntraj;
for (int k2 = 0; k2 <= maxk2; k2++) {
long b = (long)kf*(maxk2+1) + k2;
if (cnt2d[b] > 0)
f << kf << " " << freq << " " << k2 << " "
<< std::sqrt((double)k2) << " "
<< (p2d[b]/cnt2d[b]) * norm << "\n";
}
f << "\n"; // blank line between kf slices for gnuplot pm3d
}
std::cout << GridLogMessage << "Written: " << pfx << "_5d_cross.dat\n";
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Main
// ─────────────────────────────────────────────────────────────────────────────
static void usage(const char* prog)
{
std::cerr
<< "Usage: " << prog << " --grid Lx.Ly.Lz.Lt [--mpi Px.Py.Pz.Pt]\n"
<< " [--fft spatial|traj|all] [--format scidac|binary]\n"
<< " [--output PREFIX] file1 [file2 ...]\n";
exit(1);
}
int main(int argc, char** argv)
{
Grid_init(&argc, &argv); // consumes --grid, --mpi, etc.
std::string fftMode = "all";
std::string fmt = "scidac";
std::string pfx = "fft5d";
std::vector<std::string> files;
for (int i = 1; i < argc; i++) {
std::string a = argv[i];
if (a == "--fft" && i+1 < argc) fftMode = argv[++i];
else if (a == "--format" && i+1 < argc) fmt = argv[++i];
else if (a == "--output" && i+1 < argc) pfx = argv[++i];
else if (!a.empty() && a[0] != '-') files.push_back(a);
// unknown --flags skipped (may be Grid flags already consumed)
}
if (files.empty()) usage(argv[0]);
GridCartesian* grid = SpaceTimeGrid::makeFourDimGrid(
GridDefaultLatt(),
GridDefaultSimd(Nd, vComplexD::Nsimd()),
GridDefaultMpi());
Coordinate latt = GridDefaultLatt();
std::cout << GridLogMessage << "Lattice : "
<< latt[0]<<"x"<<latt[1]<<"x"<<latt[2]<<"x"<<latt[3] << "\n"
<< GridLogMessage << "Ntraj : " << files.size() << "\n"
<< GridLogMessage << "FFT : " << fftMode << "\n"
<< GridLogMessage << "Format : " << fmt << "\n"
<< GridLogMessage << "Output : " << pfx << "\n";
auto traj = loadFiles(files, grid, fmt);
if (fftMode == "spatial" || fftMode == "all") analyzeSpatial(traj, grid, pfx);
if (fftMode == "traj" || fftMode == "all") analyzeTraj (traj, grid, pfx);
if (fftMode == "all") analyzeAll5D (traj, grid, pfx);
Grid_finalize();
return 0;
}
/*
Add to examples/Make.inc to build:
bin_PROGRAMS += ... fft5d
fft5d_SOURCES = fft5d.cc
fft5d_LDADD = $(top_builddir)/Grid/libGrid.a
*/
+1 -1
View File
@@ -10,7 +10,7 @@ export HDF5=/opt/cray/pe/hdf5/1.12.2.3/gnu/9.1
--disable-gparity \
--disable-fermion-reps \
--enable-shm=nvlink \
--enable-checksum-comms=yes \
--enable-checksum-comms=no \
--enable-log-views=yes \
--enable-accelerator=sycl \
--enable-accelerator-aware-mpi=no \
+1 -2
View File
@@ -10,12 +10,11 @@ CLIME=`spack find --paths c-lime@2-3-9 | grep c-lime| cut -c 15-`
--disable-fermion-reps \
--enable-simd=GPU \
--with-gmp=$OLCF_GMP_ROOT \
--with-fftw=$FFTW_DIR/.. \
--with-mpfr=/opt/cray/pe/gcc/mpfr/3.1.4/ \
--disable-fermion-reps \
CXX=hipcc MPICXX=mpicxx \
CXXFLAGS="-fPIC -I${ROCM_PATH}/include/ -I${MPICH_DIR}/include -L/lib64 " \
LDFLAGS="-L/lib64 -L${ROCM_PATH}/lib -L${MPICH_DIR}/lib -lmpi -L${CRAY_MPICH_ROOTDIR}/gtl/lib -lmpi_gtl_hsa -lhipblas -lrocblas"
LDFLAGS="-L/lib64 -L${ROCM_PATH}/lib -L${MPICH_DIR}/lib -lmpi -L${CRAY_MPICH_ROOTDIR}/gtl/lib -lmpi_gtl_hsa -lhipblas -lrocblas -lhipfft"
+4 -3
View File
@@ -25,6 +25,9 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_tests_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace std;
@@ -273,8 +276,6 @@ void TestWhat(What & Ddwf,
err = phi-chi;
std::cout<<GridLogMessage << "norm diff "<< norm2(err)<< std::endl;
}
#endif
@@ -30,6 +30,9 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
* Reimplement the badly named "multigrid" lanczos as compressed Lanczos using the features
* in Grid that were intended to be used to support blocked Aggregates, from
*/
#include "disable_tests_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
#include <Grid/algorithms/iterative/ImplicitlyRestartedLanczos.h>
#include <Grid/algorithms/iterative/LocalCoherenceLanczos.h>
@@ -256,3 +259,4 @@ int main (int argc, char ** argv) {
Grid_finalize();
}
#endif
+5
View File
@@ -25,6 +25,9 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_tests_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace std;
@@ -237,3 +240,5 @@ int main (int argc, char ** argv)
Grid_finalize();
}
#endif
+5
View File
@@ -25,6 +25,9 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_tests_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace std;
@@ -222,3 +225,5 @@ int main (int argc, char ** argv)
Grid_finalize();
}
#endif
+4
View File
@@ -25,6 +25,9 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
/* END LEGAL */
#include "disable_tests_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
using namespace std;
@@ -118,3 +121,4 @@ int main (int argc, char ** argv)
Grid_finalize();
}
#endif
#endif
+4
View File
@@ -24,6 +24,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
See the full license in the file "LICENSE" in the top level distribution directory
*************************************************************************************/
#include "disable_tests_without_instantiations.h"
#ifdef ENABLE_FERMION_INSTANTIATIONS
#include <Grid/Grid.h>
#include <Grid/qcd/utils/A2Autils.h>
@@ -157,3 +159,5 @@ int main(int argc, char *argv[])
return EXIT_SUCCESS;
}
#endif
+21 -11
View File
@@ -29,7 +29,6 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
#include <Grid/Grid.h>
using namespace Grid;
;
int main (int argc, char ** argv)
{
@@ -116,10 +115,10 @@ int main (int argc, char ** argv)
Stilde=S;
std::cout<<" Benchmarking FFT of LatticeSpinMatrix "<<std::endl;
theFFT.FFT_dim(Stilde,S,0,FFT::forward); std::cout << theFFT.MFlops()<<" mflops "<<std::endl;
theFFT.FFT_dim(Stilde,S,1,FFT::forward); std::cout << theFFT.MFlops()<<" mflops "<<std::endl;
theFFT.FFT_dim(Stilde,S,2,FFT::forward); std::cout << theFFT.MFlops()<<" mflops "<<std::endl;
theFFT.FFT_dim(Stilde,S,3,FFT::forward); std::cout << theFFT.MFlops()<<" mflops "<<std::endl;
theFFT.FFT_dim(Stilde,Stilde,0,FFT::forward); std::cout << theFFT.MFlops()<<" mflops "<<std::endl;
theFFT.FFT_dim(Stilde,Stilde,1,FFT::forward); std::cout << theFFT.MFlops()<<" mflops "<<std::endl;
theFFT.FFT_dim(Stilde,Stilde,2,FFT::forward); std::cout << theFFT.MFlops()<<" mflops "<<std::endl;
theFFT.FFT_dim(Stilde,Stilde,3,FFT::forward); std::cout << theFFT.MFlops()<<" mflops "<<std::endl;
SpinMatrixD Sp;
Sp = Zero(); Sp = Sp+cVol;
@@ -202,11 +201,16 @@ int main (int argc, char ** argv)
FFT theFFT5(FGrid);
theFFT5.FFT_dim(result5,tmp5,1,FFT::forward); tmp5 = result5;
std::cout<<"Fourier xformed Ddwf 1 "<<norm2(result5)<<std::endl;
theFFT5.FFT_dim(result5,tmp5,2,FFT::forward); tmp5 = result5;
std::cout<<"Fourier xformed Ddwf 2 "<<norm2(result5)<<std::endl;
theFFT5.FFT_dim(result5,tmp5,3,FFT::forward); tmp5 = result5;
theFFT5.FFT_dim(result5,tmp5,4,FFT::forward); result5 = result5*ComplexD(::sqrt(1.0/vol),0.0);
std::cout<<"Fourier xformed Ddwf 3 "<<norm2(result5)<<std::endl;
theFFT5.FFT_dim(result5,tmp5,4,FFT::forward);
std::cout<<"Fourier xformed Ddwf 4 "<<norm2(result5)<<std::endl;
result5 = result5*ComplexD(::sqrt(1.0/vol),0.0);
std::cout<<"Fourier xformed Ddwf"<<std::endl;
std::cout<<"Fourier xformed Ddwf "<<norm2(result5)<<std::endl;
tmp5 = src5;
theFFT5.FFT_dim(src5_p,tmp5,1,FFT::forward); tmp5 = src5_p;
@@ -214,7 +218,7 @@ int main (int argc, char ** argv)
theFFT5.FFT_dim(src5_p,tmp5,3,FFT::forward); tmp5 = src5_p;
theFFT5.FFT_dim(src5_p,tmp5,4,FFT::forward); src5_p = src5_p*ComplexD(::sqrt(1.0/vol),0.0);
std::cout<<"Fourier xformed src5"<<std::endl;
std::cout<<"Fourier xformed src5"<< norm2(src5)<<" -> "<<norm2(src5_p)<<std::endl;
/////////////////////////////////////////////////////////////////
// work out the predicted from Fourier
@@ -251,7 +255,8 @@ int main (int argc, char ** argv)
Kinetic = Kinetic + sin(kmu)*ci*(Gamma(Gmu[mu])*src5_p);
}
std::cout << " src5 "<<norm2(src5_p)<<std::endl;
std::cout << " Kinetic "<<norm2(Kinetic)<<std::endl;
// NB implicit sum over mu
//
// 1-1/2 Dw = 1 - 1/2 ( eip+emip)
@@ -260,18 +265,23 @@ int main (int argc, char ** argv)
// = 2 sink/2 ink/2 = sk2
W = one - M5 + sk2;
std::cout << " W "<<norm2(W)<<std::endl;
Kinetic = Kinetic + W * src5_p;
std::cout << " Kinetic "<<norm2(Kinetic)<<std::endl;
LatticeCoordinate(scoor,sdir);
tmp5 = Cshift(src5_p,sdir,+1);
tmp5 = (tmp5 - G5*tmp5)*0.5;
tmp5 = where(scoor==Integer(Ls-1),mass*tmp5,-tmp5);
std::cout << " tmp5 "<<norm2(tmp5)<<std::endl;
Kinetic = Kinetic + tmp5;
tmp5 = Cshift(src5_p,sdir,-1);
tmp5 = (tmp5 + G5*tmp5)*0.5;
tmp5 = where(scoor==Integer(0),mass*tmp5,-tmp5);
std::cout << " tmp5 "<<norm2(tmp5)<<std::endl;
Kinetic = Kinetic + tmp5;
std::cout<<"Momentum space Ddwf "<< norm2(Kinetic)<<std::endl;
@@ -339,7 +349,7 @@ int main (int argc, char ** argv)
Ddwf.Mdag(src5,tmp5);
src5=tmp5;
MdagMLinearOperator<DomainWallFermionD,LatticeFermionD> HermOp(Ddwf);
ConjugateGradient<LatticeFermionD> CG(1.0e-16,10000);
ConjugateGradient<LatticeFermionD> CG(1.0e-8,10000);
CG(HermOp,src5,result5);
////////////////////////////////////////////////////////////////////////
@@ -423,7 +433,7 @@ int main (int argc, char ** argv)
Dov.Mdag(src5,tmp5);
src5=tmp5;
MdagMLinearOperator<OverlapWilsonCayleyTanhFermionD,LatticeFermionD> HermOp(Dov);
ConjugateGradient<LatticeFermionD> CG(1.0e-16,10000);
ConjugateGradient<LatticeFermionD> CG(1.0e-8,10000);
CG(HermOp,src5,result5);
////////////////////////////////////////////////////////////////////////
+101
View File
@@ -0,0 +1,101 @@
/*************************************************************************************
grid` physics library, www.github.com/paboyle/Grid
Source file: ./tests/Test_cshift.cc
Copyright (C) 2015
Author: Azusa Yamaguchi <ayamaguc@staffmail.ed.ac.uk>
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
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 */
#include <Grid/Grid.h>
using namespace Grid;
template<class LatticeObject>
void bench(GridCartesian *grid, std::string name)
{
LatticeComplexD C(grid);
LatticeComplexD coor(grid);
ComplexD ci(0.0,1.0);
Coordinate p({1,2,3,4});
Coordinate latt_size = grid->_fdimensions;
std::cout<<"*************************************************"<<std::endl;
std::cout<<" Benchmarking FFT of "<<name<<" on plane wave "<<std::endl;
std::cout<<"*************************************************"<<std::endl;
C=Zero();
for(int mu=0;mu<4;mu++){
RealD TwoPiL = M_PI * 2.0/ latt_size[mu];
LatticeCoordinate(coor,mu);
C = C + (TwoPiL * p[mu]) * coor;
}
C = exp(C*ci);
LatticeObject S(grid);
LatticeObject Stilde(grid);
S=Zero();
S = S+C;
FFT theFFT(grid);
Stilde=S;
std::cout << " norm2(s) "<<norm2(Stilde)<<std::endl;
double tt= -usecond();
theFFT.FFT_dim(Stilde,Stilde,0,FFT::forward); std::cout << theFFT.MFlops()<<" mflops "<<norm2(Stilde)<<std::endl;
theFFT.FFT_dim(Stilde,Stilde,1,FFT::forward); std::cout << theFFT.MFlops()<<" mflops "<<norm2(Stilde)<<std::endl;
theFFT.FFT_dim(Stilde,Stilde,2,FFT::forward); std::cout << theFFT.MFlops()<<" mflops "<<norm2(Stilde)<<std::endl;
theFFT.FFT_dim(Stilde,Stilde,3,FFT::forward); std::cout << theFFT.MFlops()<<" mflops "<<norm2(Stilde)<<std::endl;
tt+= usecond();
std::cout<<"*************************************************"<<std::endl;
std::cout<<" FFT of "<<latt_size <<" "<<name<<" took "<<tt/1.e6<<" s"<<std::endl;
std::cout<<"*************************************************"<<std::endl;
}
int main (int argc, char ** argv)
{
Grid_init(&argc,&argv);
int threads = GridThread::GetThreads();
std::cout<<GridLogMessage << "Grid is setup to use "<<threads<<" threads"<<std::endl;
Coordinate latt_size = GridDefaultLatt();
Coordinate simd_layout = GridDefaultSimd(Nd,vComplexD::Nsimd());
Coordinate mpi_layout = GridDefaultMpi();
int vol = 1;
for(int d=0;d<latt_size.size();d++){
vol = vol * latt_size[d];
}
GridCartesian GRID(latt_size,simd_layout,mpi_layout);
bench<LatticeComplexD>(&GRID,std::string("LatticeComplexD"));
bench<LatticeColourMatrixD>(&GRID,std::string("LatticeColourMatrixD"));
bench<LatticePropagatorD>(&GRID,std::string("LatticePropagatorD"));
Grid_finalize();
}
+271
View File
@@ -0,0 +1,271 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: tests/core/Test_plaquette_stats.cc
Copyright (C) 2015
Author: Chulwoo Jung <chulwoo@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.
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 */
/**
* Test_plaquette_stats
*
* Measure every plaquette Re Tr[U_mu(x) U_nu(x+mu) U_mu(x+nu) U_nu(x)] / Nc
* across all sites and all (mu,nu) planes and report max, min, and average.
*
* Usage:
* ./Test_plaquette_stats [Grid options] [--file <nersc_config>] [--hot]
* [--threshold <val>]
*
* --file <path> Read gauge field from NERSC-format file
* --hot Use random (hot) SU(3) start (default: cold/unit start)
* --threshold <val> Print coordinates of every plaquette with Re Tr/Nc < val
*
* Grid size defaults to 8^3 x 16; override with --grid (e.g. --grid 4.4.4.8).
*/
#include <Grid/Grid.h>
#include <limits>
using namespace std;
using namespace Grid;
template<class T>
static void writeFile(T& in, const std::string& fname)
{
#ifdef HAVE_LIME
std::cout << GridLogMessage << "Writing: " << fname << std::endl;
Grid::emptyUserRecord record;
Grid::ScidacWriter WR(in.Grid()->IsBoss());
WR.open(fname);
WR.writeScidacFieldRecord(in, record, 0);
WR.close();
#endif
}
// Return the plane label string for output
static std::string planeName(int mu, int nu)
{
const char dirs[] = "xyzt";
std::string s;
s += dirs[mu];
s += dirs[nu];
return s;
}
int main(int argc, char** argv)
{
Grid_init(&argc, &argv);
// ---- Lattice geometry ----
Coordinate latt_size = GridDefaultLatt();
if (latt_size.size() == 0) {
latt_size = Coordinate(std::vector<int>{8, 8, 8, 16});
}
Coordinate simd_layout = GridDefaultSimd(Nd, vComplex::Nsimd());
Coordinate mpi_layout = GridDefaultMpi();
GridCartesian grid(latt_size, simd_layout, mpi_layout);
std::cout << GridLogMessage << "Lattice: ";
for (int d = 0; d < Nd; d++)
std::cout << latt_size[d] << (d < Nd-1 ? "x" : "\n");
// ---- Gauge field ----
LatticeGaugeField Umu(&grid);
// Check for --file argument
std::string config_file = "";
for (int i = 1; i < argc - 1; i++) {
if (std::string(argv[i]) == "--file") {
config_file = argv[i+1];
break;
}
}
bool doHot = false;
for (int i = 1; i < argc; i++) {
if (std::string(argv[i]) == "--hot") { doHot = true; break; }
}
double threshold = std::numeric_limits<double>::quiet_NaN();
bool doThresh = false;
for (int i = 1; i < argc - 1; i++) {
if (std::string(argv[i]) == "--threshold") {
threshold = std::stod(argv[i+1]);
doThresh = true;
break;
}
}
if (!config_file.empty()) {
std::cout << GridLogMessage << "Reading gauge field from " << config_file << std::endl;
FieldMetaData header;
NerscIO::readConfiguration(Umu, header, config_file);
} else {
std::vector<int> seeds({1, 2, 3, 4});
GridParallelRNG pRNG(&grid);
pRNG.SeedFixedIntegers(seeds);
if (doHot) {
std::cout << GridLogMessage << "Generating hot (random SU(3)) start" << std::endl;
SU<Nc>::HotConfiguration(pRNG, Umu);
} else {
std::cout << GridLogMessage << "Using cold (unit) gauge start" << std::endl;
SU<Nc>::ColdConfiguration(pRNG, Umu);
}
}
// ---- Extract link matrices ----
std::vector<LatticeColourMatrix> U(Nd, &grid);
for (int mu = 0; mu < Nd; mu++)
U[mu] = PeekIndex<LorentzIndex>(Umu, mu);
// ---- Per-plane plaquette statistics ----
//
// For each (mu, nu) plane (mu > nu) compute
// P_munu(x) = Re Tr[plaquette] / Nc
// then report max, min, mean over all sites.
//
// WilsonLoops::traceDirPlaquette gives Tr[U_mu U_nu U_mu† U_nu†] (complex).
double vol = grid.gSites();
// Accumulate site-average plaquette (sum over planes / Nplanes / Nc)
LatticeComplex plaq_all(&grid);
plaq_all = Zero();
int Nplanes = 0;
// Per-site running sum over planes (Re Tr/Nc, normalised per plane)
LatticeRealD plaq_sum(&grid);
plaq_sum = Zero();
std::cout << GridLogMessage
<< "======== Per-plane plaquette statistics ========" << std::endl;
std::cout << GridLogMessage
<< std::setw(6) << "plane"
<< std::setw(20) << "max"
<< std::setw(20) << "min"
<< std::setw(20) << "average"
<< std::endl;
for (int mu = 1; mu < Nd; mu++) {
for (int nu = 0; nu < mu; nu++) {
// Per-site trace of plaquette in (mu,nu) plane
LatticeComplex sitePlaq(&grid);
ColourWilsonLoops::traceDirPlaquette(sitePlaq, U, mu, nu);
plaq_all = plaq_all + sitePlaq;
Nplanes++;
// --- global average via sum() ---
TComplex Tsum = sum(sitePlaq);
ComplexD csum = TensorRemove(Tsum);
RealD avg = csum.real() / vol / Nc;
// --- global max and min via unvectorize + GlobalMax/GlobalMin ---
std::vector<TComplex> sv;
unvectorizeToLexOrdArray(sv, sitePlaq);
RealD local_max = -1e38, local_min = 1e38;
for (auto& tc : sv) {
RealD r = TensorRemove(tc).real() / Nc;
if (r > local_max) local_max = r;
if (r < local_min) local_min = r;
}
// Reduce across MPI ranks (no GlobalMin; negate to use GlobalMax)
grid.GlobalMax(local_max);
local_min = -local_min;
grid.GlobalMax(local_min);
local_min = -local_min;
std::cout << GridLogMessage
<< std::setw(6) << planeName(mu, nu)
<< std::setw(20) << std::setprecision(10) << local_max
<< std::setw(20) << std::setprecision(10) << local_min
<< std::setw(20) << std::setprecision(10) << avg
<< std::endl;
if (doThresh) {
std::vector<RealD> vr(sv.size(), 0.0);
for (int s = 0; s < (int)sv.size(); s++) {
RealD val = TensorRemove(sv[s]).real() / Nc;
if (val < threshold) {
Coordinate lc(Nd), gc(Nd);
Lexicographic::CoorFromIndex(lc, s, grid._ldimensions);
for (int d = 0; d < Nd; d++)
gc[d] = grid._processor_coor[d] * grid._ldimensions[d] + lc[d];
std::cerr << "BELOW_THRESHOLD plane=" << planeName(mu, nu)
// << " global=(" << grid._processor_coor[0] << "," << grid._processor_coor[1] << "," << grid._processor_coor[2] << "," << grid._processor_coor[3] << ")"
// << " dim=(" << grid._ldimensions[0] << "," << grid._ldimensions[1] << "," << grid._ldimensions[2] << "," << grid._ldimensions[3] << ")"
<< " site=(" << gc[0] << "," << gc[1] << "," << gc[2] << "," << gc[3] << ")"
<< " P=" << std::setprecision(10) << val << std::endl;
vr[s] = val;
}
}
LatticeRealD plaqRe(&grid);
vectorizeFromLexOrdArray(vr, plaqRe);
plaq_sum = plaq_sum + plaqRe;
}
}
}
// Write per-site sum of below-threshold plaquette values over all planes
if (doThresh)
writeFile(plaq_sum, "./plaq_sum");
// ---- Overall (averaged over all planes) statistics ----
// plaq_all = sum of Tr[...] over all 6 (mu,nu) planes
// Normalise to Re Tr / Nc per plane
plaq_all = plaq_all * (1.0 / Nc / Nplanes);
TComplex Tsum_all = sum(plaq_all);
RealD avg_all = TensorRemove(Tsum_all).real() / vol;
std::vector<TComplex> sv_all;
unvectorizeToLexOrdArray(sv_all, plaq_all);
RealD max_all = -1e38, min_all = 1e38;
for (auto& tc : sv_all) {
RealD r = TensorRemove(tc).real();
if (r > max_all) max_all = r;
if (r < min_all) min_all = r;
}
grid.GlobalMax(max_all);
min_all = -min_all;
grid.GlobalMax(min_all);
min_all = -min_all;
// Cross-check with built-in avgPlaquette
RealD avg_builtin = ColourWilsonLoops::avgPlaquette(Umu);
std::cout << GridLogMessage
<< "======== Overall plaquette statistics (all planes) ========" << std::endl;
std::cout << GridLogMessage << " max = " << max_all << std::endl;
std::cout << GridLogMessage << " min = " << min_all << std::endl;
std::cout << GridLogMessage << " average = " << avg_all << std::endl;
std::cout << GridLogMessage << " avgPlaquette (builtin check) = " << avg_builtin << std::endl;
Grid_finalize();
return 0;
}

Some files were not shown because too many files have changed in this diff Show More