Skip to content

Commit 187c1f0

Browse files
committed
fix: use subspace diagonalization for CG non-RR eigenvalues; re-enable use_p
Three changes to make both PPCG strategies correctly converge with rr_step=4: 1. CG non-RR path: After orth_cholesky, solve the nband x nband subspace generalized eigenvalue problem instead of using diagonal Rayleigh quotients. The upper-triangular U^{-1} from Cholesky mixes high-energy components into low-energy bands, making diagonal RQs overestimate the eigenvalues. The subspace solve gives correct Ritz values without rotating the states, preserving Polak-Ribiere conjugate-direction accumulators. 2. BLOCK_SUBSPACE: Re-enable use_p=true (3-block [psi, w, p] subspace). The Krylov fallback (replace p with H·w when p ~ w) was already in place but dead because use_p was hardcoded to false. Now it activates on the first iteration (p is zero-initialized) and whenever p becomes collinear with w after update_one_block. 3. CG test: Change rr_step from 1 back to 4 so the non-RR Cholesky path is exercised, validating the true Polak-Ribiere CG mechanism.
1 parent f7a1ea0 commit 187c1f0

2 files changed

Lines changed: 51 additions & 15 deletions

File tree

source/source_hsolver/diago_ppcg.cpp

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,14 +1168,13 @@ double DiagoPPCG<T, Device>::diag(const HPsiFunc& hpsi_func,
11681168

11691169
avg_iter += static_cast<double>(nact) / static_cast<double>(ncol);
11701170

1171-
// Use only the [psi, w] 2-block subspace.
1172-
// The 3-block [psi, w, p] subspace can become ill-conditioned
1173-
// when p is constructed from the previous subspace eigenvectors
1174-
// (p ~ w), leading to near-singular M matrices and catastrophic
1175-
// eigenvalue blow-up. Without p the method reduces to a
1176-
// preconditioned Davidson-like iteration that converges robustly,
1177-
// albeit with slightly more iterations for hard problems.
1178-
const bool use_p = false;
1171+
// Use the 3-block [psi, w, p] subspace for faster convergence.
1172+
// When p is nearly collinear with w (p ~ w), the subspace Gram
1173+
// matrix becomes nearly singular, causing the generalized
1174+
// eigenvalue solver to fail. The p-bad detection below catches
1175+
// this and replaces p with H·w (a genuinely independent Krylov
1176+
// direction), keeping the subspace full-rank.
1177+
const bool use_p = true;
11791178
if (use_p)
11801179
{
11811180
apply_s_current(p_.data(), sp_.data(), ncol);
@@ -1384,12 +1383,49 @@ double DiagoPPCG<T, Device>::diag(const HPsiFunc& hpsi_func,
13841383
// Cholesky orthonormalization.
13851384
orth_cholesky(psi_in, hpsi_.data(), spsi_.data(), ncol);
13861385

1387-
// Update eigenvalues.
1388-
for (int i = 0; i < ncol; ++i)
1389-
eigenvalue_in[i] = gamma_dot(psi_in + i * ld_psi_,
1390-
hpsi_.data() + i * ld_psi_)
1391-
/ gamma_dot(psi_in + i * ld_psi_,
1392-
spsi_.data() + i * ld_psi_);
1386+
// After Cholesky the bands are S-orthonormal, but the
1387+
// upper-triangular U^{-1} transformation mixes high-energy
1388+
// components into the low-energy bands. Diagonal Rayleigh
1389+
// quotients then overestimate the low eigenvalues and
1390+
// produce wrong gradients that drive the CG search toward
1391+
// high-energy states.
1392+
//
1393+
// Solve the subspace generalized eigenvalue problem to get
1394+
// correct Ritz values. We do NOT rotate the states — that
1395+
// would invalidate the Polak-Ribiere conjugate-direction
1396+
// accumulators. The Cholesky basis spans the same subspace,
1397+
// so the Ritz values are exact for this subspace.
1398+
std::vector<Real> h_sub(ncol * ncol, static_cast<Real>(0));
1399+
std::vector<Real> s_sub(ncol * ncol, static_cast<Real>(0));
1400+
for (int jj = 0; jj < ncol; ++jj)
1401+
{
1402+
for (int ii = 0; ii < ncol; ++ii)
1403+
{
1404+
h_sub[ii + jj * ncol]
1405+
= gamma_dot(psi_in + ii * ld_psi_,
1406+
hpsi_.data() + jj * ld_psi_);
1407+
s_sub[ii + jj * ncol]
1408+
= gamma_dot(psi_in + ii * ld_psi_,
1409+
spsi_.data() + jj * ld_psi_);
1410+
}
1411+
}
1412+
1413+
std::vector<Real> eval_cg(ncol, static_cast<Real>(0));
1414+
try
1415+
{
1416+
Lapack<Real>::sygvd(ncol, h_sub.data(), s_sub.data(),
1417+
eval_cg.data());
1418+
}
1419+
catch (const std::runtime_error&)
1420+
{
1421+
// Fallback: diagonal Rayleigh quotients.
1422+
for (int ii = 0; ii < ncol; ++ii)
1423+
eval_cg[ii] = h_sub[ii + ii * ncol]
1424+
/ std::max(s_sub[ii + ii * ncol],
1425+
static_cast<Real>(1e-30));
1426+
}
1427+
for (int ii = 0; ii < ncol; ++ii)
1428+
eigenvalue_in[ii] = eval_cg[ii];
13931429
}
13941430

13951431
// Compute new gradient.

source/source_hsolver/test/diago_ppcg_test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ TEST_F(DiagoPPCGTest, ConjugateGradientStrategy)
164164
/* diag_thr = */ 1e-12,
165165
/* max_iter = */ 100,
166166
/* sbsize = */ 4,
167-
/* rr_step = */ 1,
167+
/* rr_step = */ 4,
168168
/* gamma_g0 = */ false,
169169
hsolver::PpcgStrategy::CONJUGATE_GRADIENT
170170
);

0 commit comments

Comments
 (0)