mirror of
https://github.com/paboyle/Grid.git
synced 2026-07-17 15:43:27 +01:00
Finally Block Harmonic(?) KS working
This commit is contained in:
@@ -34,65 +34,44 @@ See the full license in the file "LICENSE" in the top level distribution directo
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
/**
|
||||
* Block harmonic restarted Krylov-Schur eigensolver.
|
||||
* Block shift-targeted Krylov-Schur eigensolver.
|
||||
*
|
||||
* Harmonic Ritz values
|
||||
* --------------------
|
||||
* Standard Ritz values of A in a Krylov space K_m minimise the residual
|
||||
* in a Galerkin sense; they are good approximations to eigenvalues at the
|
||||
* *exterior* of the spectrum. For eigenvalues *near* a target shift σ
|
||||
* (e.g. the smallest eigenvalues when σ=0) harmonic Ritz values are
|
||||
* better-suited: they are obtained by a Petrov-Galerkin condition that
|
||||
* requires the residual to be orthogonal to (A-σI)K_m instead of K_m.
|
||||
*
|
||||
* Given the block Arnoldi factorisation
|
||||
* Algorithm
|
||||
* ---------
|
||||
* Uses a block Arnoldi factorisation:
|
||||
*
|
||||
* A V = V H + F B^dag (1)
|
||||
*
|
||||
* with V orthonormal (Nm columns), H the Nm² block
|
||||
* upper-Hessenberg Rayleigh quotient, F the Nblock residual vectors and B
|
||||
* the Nm×Nblock coupling matrix, the harmonic Rayleigh quotient
|
||||
* relative to shift σ is
|
||||
*
|
||||
* Hhat = H + (H - σI)^{-H} B B^H (2)
|
||||
*
|
||||
* Derivation: the harmonic Ritz condition (A-σI)Vy ⊥ (A-σI)V leads to
|
||||
*
|
||||
* [ (H-σI)^H (H-σI) + B B^H ] y = μ (H-σI)^H y
|
||||
*
|
||||
* Left-multiplying by (H-σI)^{-H} and setting θ = μ + σ gives the
|
||||
* standard eigenvalue problem Hhat y = θ y with Hhat as in (2).
|
||||
*
|
||||
* The harmonic Ritz values θ_j are eigenvalues of Hhat; among these,
|
||||
* the ones closest to σ (smallest |θ_j - σ|) are the best approximations
|
||||
* to the eigenvalues of A near σ.
|
||||
* 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
|
||||
* -------------
|
||||
* The Schur decomposition Hhat = Q^dag S Q is computed and the
|
||||
* leading Nk*Nblock Schur values (sorted by the RitzFilter) are kept.
|
||||
* The same unitary rotation Q is applied to both the Krylov basis and
|
||||
* to the *original* Rayleigh quotient H (not Hhat) for the restart:
|
||||
* To target eigenvalues near shift σ, the Schur decomposition is computed
|
||||
* for the shifted Rayleigh quotient:
|
||||
*
|
||||
* V_new = V Q^dag [first Nk*Nblock columns]
|
||||
* H_new = Q H Q^dag [truncated Nk*Nblock × Nk*Nblock]
|
||||
* B_new = Q B [truncated Nk*Nblock × Nblock]
|
||||
* (H - σI) = Q^dag S Q (2)
|
||||
*
|
||||
* Block Arnoldi then resumes from block Nk, restoring H to full size
|
||||
* as new columns are appended.
|
||||
* 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
|
||||
* -----------
|
||||
* For a harmonic Ritz pair (θ, y) the true Ritz residual bound is
|
||||
*
|
||||
* || (A - θI) V y || ≤ || B^H y ||
|
||||
*
|
||||
* (same as for standard Ritz, because B captures the full coupling).
|
||||
* Convergence is declared when || B^H y || < Tolerance * approxLambdaMax.
|
||||
* 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)
|
||||
* 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)
|
||||
@@ -205,70 +184,38 @@ public:
|
||||
verify(lbl);
|
||||
}
|
||||
|
||||
// ---- Form harmonic Rayleigh quotient ----
|
||||
// Hhat = H + (H - σI)^{-H} * B * B^H
|
||||
CMat Hhat = harmonicRayleigh(H, B, N);
|
||||
|
||||
// ---- Schur decompose Hhat ----
|
||||
ComplexSchurDecomposition schur(Hhat, false, ritzFilter);
|
||||
// ---- 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: harmonic Ritz values (first Nk):" << std::endl;
|
||||
<< "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) << std::endl;
|
||||
std::cout << GridLogMessage << " [" << i << "] " << S(i, i) + shift << std::endl;
|
||||
|
||||
CMat Q = schur.getMatrixQ();
|
||||
CMat Qt = Q.adjoint();
|
||||
|
||||
// ---- Rotate Krylov basis using Q from Hhat ----
|
||||
// ---- Rotate Krylov basis ----
|
||||
std::vector<Field> basis2;
|
||||
constructUR(basis2, basis, Qt, N);
|
||||
basis = basis2;
|
||||
|
||||
// ---- Update H and B (rotate H, not Hhat) ----
|
||||
H = Q * H * Qt;
|
||||
// ---- 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 ----
|
||||
// ---- Truncate to Nk (exact: H upper triangular) ----
|
||||
int Nkeep = Nk;
|
||||
|
||||
// ---- Option B: corrected restart starting block -------------------------
|
||||
//
|
||||
// In standard Krylov-Schur, Q diagonalises H itself, so Q H Q^H = S is
|
||||
// upper triangular and the off-diagonal coupling block H_dk = S[Nkeep:,:Nkeep] = 0.
|
||||
// Truncation is therefore exact.
|
||||
//
|
||||
// Here Q diagonalises Hhat (not H), so H_new = Q H Q^H is generally dense.
|
||||
// The off-diagonal block
|
||||
//
|
||||
// H_dk = H_new[Nkeep:, :Nkeep] (size (N-Nkeep) x Nkeep)
|
||||
//
|
||||
// is non-zero, and the true KS relation after truncation is:
|
||||
//
|
||||
// A V_k = V_k H_k + V_disc H_dk + F B_k^H
|
||||
//
|
||||
// If we restart from F only, the V_disc H_dk term is lost.
|
||||
//
|
||||
// Fix: include the V_disc coupling in the new starting block:
|
||||
//
|
||||
// G[t] = F[t] + sum_{s=Nkeep}^{N-1} basis[s] * H(s, t), t = 0..Nblock-1
|
||||
//
|
||||
// G[:,t] is the t-th column of (V_disc H_dk + F B_k^H) restricted to the
|
||||
// first Nblock columns of H_dk. Since F ⊥ V_k and V_disc ⊥ V_k, G is
|
||||
// automatically orthogonal to the retained subspace.
|
||||
//
|
||||
// This must be computed BEFORE basis[Nkeep:] and H[Nkeep:, :] are discarded.
|
||||
|
||||
std::vector<Field> G(Nblock, Field(Grid_));
|
||||
for (int t = 0; t < Nblock; t++) {
|
||||
G[t] = F[t];
|
||||
for (int s = Nkeep; s < N; s++)
|
||||
G[t] += basis[s] * H(s, t);
|
||||
}
|
||||
blockQR(G); // orthonormalise within G (G is already ⊥ to basis[0:Nkeep])
|
||||
|
||||
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;
|
||||
@@ -284,10 +231,8 @@ public:
|
||||
std::cout << GridLogMessage
|
||||
<< "HarmonicBlockKrylovSchur: beta_k = " << beta_k << std::endl;
|
||||
|
||||
// Use corrected starting block G (not bare F).
|
||||
// G encodes the coupling from the discarded basis vectors V_disc through
|
||||
// H_dk[:,0:Nblock], restoring the exact KS relation to first order.
|
||||
startBlock = G;
|
||||
// Restart from F (exact: no discarded-basis correction needed)
|
||||
startBlock = F;
|
||||
|
||||
if (doVerify) {
|
||||
std::string lbl = "iter " + std::to_string(iter) + " after restart+truncation";
|
||||
@@ -327,8 +272,7 @@ public:
|
||||
* A V = V H + F B^dag (KS)
|
||||
*
|
||||
* by explicit operator applications. H here is the standard Rayleigh
|
||||
* quotient (not Hhat), so the KS relation is the same as for
|
||||
* BlockKrylovSchur.
|
||||
* quotient, so the KS relation is the same as for BlockKrylovSchur.
|
||||
*
|
||||
* Prints:
|
||||
* - H (current Rayleigh quotient, nBasis × nBasis)
|
||||
@@ -445,32 +389,6 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Harmonic Rayleigh quotient
|
||||
//--------------------------------------------------------------------
|
||||
/**
|
||||
* Forms the harmonic Rayleigh quotient relative to shift σ:
|
||||
*
|
||||
* Hhat = H + (H - σI)^{-H} * B * B^H
|
||||
*
|
||||
* where H is the N×N block-Hessenberg, B is the N×Nblock coupling matrix.
|
||||
*
|
||||
* The N×N solve (H - σI)^H X = B B^H is done via Eigen's LU
|
||||
* factorisation. If H - σI is (nearly) singular the result is
|
||||
* ill-conditioned; in that case σ should be perturbed slightly.
|
||||
*/
|
||||
CMat harmonicRayleigh(const CMat& H_, const CMat& B_, int N)
|
||||
{
|
||||
CMat K = H_ - shift * CMat::Identity(N, N);
|
||||
CMat KH = K.adjoint(); // (H - σI)^H
|
||||
|
||||
// Solve KH * X = B B^H → X = KH^{-1} B B^H = (H-σI)^{-H} B B^H
|
||||
CMat BBH = B_ * B_.adjoint(); // N × N
|
||||
CMat X = KH.lu().solve(BBH); // N × N
|
||||
|
||||
return H_ + X;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Block Arnoldi iteration
|
||||
//--------------------------------------------------------------------
|
||||
@@ -491,24 +409,15 @@ private:
|
||||
} else {
|
||||
// Append the new starting block to the retained basis.
|
||||
//
|
||||
// Standard KS (startBlock = F):
|
||||
// The exact truncated relation is A V_k = V_k H_k + F B_k^dag,
|
||||
// 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]).
|
||||
//
|
||||
// Harmonic KS Option B (startBlock = G, where G = F + V_disc H_dk[:,0:Nblock]):
|
||||
// The exact coupling rows are H[Nkeep+t, j] = <G[t], A V_k[:,j]>,
|
||||
// which differs from conj(B_k[j,t]) because G ≠ F.
|
||||
// For a Hermitian operator these preset rows are overwritten by the
|
||||
// Hermitian symmetry fill inside blockArnoldiStep (via explicit inner
|
||||
// products), so the approximate preset below does no harm.
|
||||
// For a non-Hermitian operator the preset is approximate; a more
|
||||
// expensive fix would compute <G[t], A V_k[:,j]> explicitly here.
|
||||
// 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 for standard KS; approximate
|
||||
// for harmonic KS with Option-B starting block, but overwritten by
|
||||
// Hermitian symmetry fill for Hermitian operators).
|
||||
// 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));
|
||||
@@ -560,10 +469,6 @@ private:
|
||||
for (int s = 0; s < Nblock; s++)
|
||||
B(kBase + t, s) = std::conj(R(s, t)); // B_block = R^H
|
||||
beta_k = R.norm();
|
||||
// Hermitian symmetry fill for last block (same as non-last path below)
|
||||
for (int t = 0; t < Nblock; t++)
|
||||
for (int j = 0; j < kBase; j++)
|
||||
H(kBase + t, j) = std::conj(H(j, kBase + t));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -578,23 +483,6 @@ private:
|
||||
for (int t = 0; t < Nblock; t++)
|
||||
basis.push_back(F[t]);
|
||||
|
||||
// Hermitian symmetry fill: H[kBase+t, j] = conj(H[j, kBase+t]) for j < kBase.
|
||||
//
|
||||
// In a fresh block Arnoldi the Krylov structure forces H[kBase+t, j] = 0 for
|
||||
// j < kBase-Nblock (sub-subdiagonal), so this is a no-op.
|
||||
//
|
||||
// After a non-Schur restart (e.g. harmonic restart where H_new = Q H Q^dag is
|
||||
// a full matrix), A v_k_j for j < Nkeep has components in ALL new extended
|
||||
// vectors, making these elements non-zero. The Arnoldi step fills column
|
||||
// kBase+t (H[j, kBase+t] for j < prevN) via inner products, but never fills
|
||||
// the corresponding row. For a Hermitian operator the two are related by
|
||||
// H[kBase+t, j] = <basis[kBase+t] | A basis[j]>
|
||||
// = conj(<basis[j] | A basis[kBase+t]>) = conj(H[j, kBase+t])
|
||||
// Filling these ensures H = H^dag and fixes the M != H discrepancy that
|
||||
// corrupts subsequent Arnoldi steps after a harmonic restart.
|
||||
for (int t = 0; t < Nblock; t++)
|
||||
for (int j = 0; j < kBase; j++)
|
||||
H(kBase + t, j) = std::conj(H(j, kBase + t));
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
@@ -56,6 +56,7 @@ struct LanczosParameters: Serializable {
|
||||
Integer, maxIter,
|
||||
Integer, Nblock,
|
||||
Integer, verify,
|
||||
RealD, shift ,
|
||||
RealD, resid,
|
||||
RealD, ChebyLow,
|
||||
RealD, ChebyHigh,
|
||||
@@ -117,12 +118,9 @@ 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) {
|
||||
// _Mat.Mdiag(in,out);
|
||||
// out = out + shift*in;
|
||||
assert(0);
|
||||
}
|
||||
void OpDir (const Field &in, Field &out,int dir,int disp) {
|
||||
// _Mat.Mdir(in,out,dir,disp);
|
||||
assert(0);
|
||||
}
|
||||
void OpDirAll (const Field &in, std::vector<Field> &out){
|
||||
@@ -131,11 +129,6 @@ public:
|
||||
};
|
||||
void Op (const Field &in, Field &out){
|
||||
Field tmp(in.Grid());
|
||||
// _Mat.M(in,out);
|
||||
// RealD mass=-shift;
|
||||
// WilsonCloverFermionD Dw(Umu, Grid, RBGrid, mass, csw_r, csw_t);
|
||||
// NonHermitianLinearOperator<Matrix,Field> HermOp(_Mat);
|
||||
// BiCGSTAB<Field> CG(_stp,10000);
|
||||
_Mat.Mdag(in,tmp);
|
||||
MdagMLinearOperator<Matrix,Field> HermOp(_Mat);
|
||||
ConjugateGradient<Field> CG(_stp,10000);
|
||||
@@ -341,12 +334,7 @@ int main (int argc, char ** argv)
|
||||
|
||||
// Run KrylovSchur and Arnoldi on a Hermitian matrix
|
||||
std::cout << GridLogMessage << "Running Krylov Schur" << std::endl;
|
||||
// KrylovSchur KrySchur (Dsq, FGrid, 1e-8, EvalNormLarge);
|
||||
// KrylovSchur KrySchur (Dsq, FGrid, 1e-8,EvalImNormSmall);
|
||||
// KrySchur(src, maxIter, Nm, Nk, Nstop);
|
||||
// KrylovSchur KrySchur (HermOp2, UGrid, resid,EvalNormSmall);
|
||||
// Hacked, really EvalImagSmall
|
||||
RealD shift=1.5;
|
||||
RealD shift=LanParams.shift;
|
||||
#if 0
|
||||
KrylovSchur KrySchur (Dwilson, UGrid, resid,EvalImNormSmall);
|
||||
// KrySchur(src[0], maxIter, Nm, Nk, Nstop);
|
||||
@@ -357,10 +345,8 @@ int main (int argc, char ** argv)
|
||||
Nblock=LanParams.Nblock;
|
||||
bool if_verify=false;
|
||||
if(LanParams.verify) if_verify=true;
|
||||
// KrylovSchur KrySchur (Dwilson, UGrid, resid,EvalImNormSmall);
|
||||
// KrySchur(src, maxIter, Nm, Nk, Nstop,true,if_verify);
|
||||
BlockKrylovSchur KrySchur (Dwilson, UGrid, resid,EvalImNormSmall);
|
||||
// HarmonicBlockKrylovSchur KrySchur (Dwilson, UGrid, resid,shift,EvalImNormSmall);
|
||||
// BlockKrylovSchur KrySchur (Dwilson, UGrid, resid,EvalImNormSmall);
|
||||
HarmonicBlockKrylovSchur KrySchur (Dwilson, UGrid, resid,shift,EvalNormSmall);
|
||||
KrySchur(src, maxIter, Nm, Nk, Nstop,Nblock,true,if_verify);
|
||||
std::cout << GridLogMessage << "BlockKrylovSchur evec.size= " << KrySchur.evecs.size()<< std::endl;
|
||||
#endif
|
||||
|
||||
@@ -61,11 +61,11 @@ int main(int argc, char** argv)
|
||||
//----------------------------------------------------------------------
|
||||
// Parameters (kept small so output is readable)
|
||||
//----------------------------------------------------------------------
|
||||
const int Nblock = 2;
|
||||
const int Nm = 12; // total vectors (= 6 blocks * Nblock=2)
|
||||
const int Nk = 6; // total kept (= 3 blocks * Nblock=2)
|
||||
const int Nstop = 2;
|
||||
const int maxIter = 4;
|
||||
const int Nblock = 4;
|
||||
const int Nm = 20; // total vectors (= 5 blocks * Nblock=4)
|
||||
const int Nk = 8; // total kept (= 2 blocks * Nblock=4)
|
||||
const int Nstop = 4;
|
||||
const int maxIter = 8;
|
||||
const RealD tol = 1e-6;
|
||||
|
||||
// Two identical starting blocks
|
||||
@@ -73,7 +73,7 @@ int main(int argc, char** argv)
|
||||
std::vector<Field> v0b(Nblock, Field(grid));
|
||||
for (int t = 0; t < Nblock; t++) {
|
||||
random(RNG, v0[t]);
|
||||
v0b[t] = v0[t];
|
||||
v0b[t] = v0[t]; // identical start for fair comparison
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
@@ -87,7 +87,7 @@ int main(int argc, char** argv)
|
||||
std::cout << GridLogMessage
|
||||
<< "========================================\n" << std::endl;
|
||||
|
||||
BlockKrylovSchur<Field> bks(op, grid, tol, EvalReSmall);
|
||||
BlockKrylovSchur<Field> bks(op, grid, tol, EvalImNormSmall);
|
||||
bks(v0, maxIter, Nm, Nk, Nstop, Nblock,
|
||||
/*doubleOrthog=*/true, /*doVerify=*/true);
|
||||
|
||||
@@ -108,7 +108,7 @@ int main(int argc, char** argv)
|
||||
std::cout << GridLogMessage
|
||||
<< "========================================\n" << std::endl;
|
||||
|
||||
HarmonicBlockKrylovSchur<Field> hbks(op, grid, tol, 0.0, EvalNormSmall);
|
||||
HarmonicBlockKrylovSchur<Field> hbks(op, grid, tol, 0.0, EvalImNormSmall);
|
||||
hbks(v0b, maxIter, Nm, Nk, Nstop, Nblock,
|
||||
/*doubleOrthog=*/true, /*doVerify=*/true);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user