Skip to content

Commit f7a1ea0

Browse files
committed
fix: use exact quadratic root in line_minimize for CG strategy
The linear approximation α = -C/B drops the α² term from the Rayleigh quotient derivative dR/dα = 0. This picks one of the two stationary points (minimum or maximum) arbitrarily. For bands far from convergence it can select the MAXIMUM, driving ψ toward high-energy states instead of the desired lowest eigenvalues. Solve the full quadratic Aα² + Bα + C = 0, evaluate R(α) for both roots (and the linear guess), and pick the one with the lowest R. Also restore the CG unit test (rr_step=1, initial rayleigh_ritz).
1 parent c7bba69 commit f7a1ea0

2 files changed

Lines changed: 102 additions & 5 deletions

File tree

source/source_hsolver/diago_ppcg.cpp

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -919,8 +919,19 @@ void DiagoPPCG<T, Device>::update_polak_ribiere(
919919
// For each band j: find optimal step α by minimizing the Rayleigh quotient
920920
// in the 2D subspace spanned by |psi_j> and |p_j>.
921921
//
922-
// The optimal α satisfies:
923-
// α = (h_ii * s_ip - h_ip * s_ii) / (h_pp * s_ii - h_ii * s_pp)
922+
// The Rayleigh quotient:
923+
// R(α) = (h_ii + 2α h_ip + α² h_pp) / (s_ii + 2α s_ip + α² s_pp)
924+
//
925+
// Setting dR/dα = 0 gives a QUADRATIC equation A α² + B α + C = 0 with:
926+
// A = s_ip * h_pp - h_ip * s_pp
927+
// B = s_ii * h_pp - h_ii * s_pp
928+
// C = s_ii * h_ip - h_ii * s_ip
929+
//
930+
// The linear approximation α = -C / B (dropping the α² term) picks one of
931+
// the two stationary points more-or-less arbitrarily. For bands far from
932+
// convergence this can select the MAXIMUM, driving ψ toward high-energy
933+
// states. We solve the full quadratic and explicitly pick the root with
934+
// the lower Rayleigh quotient.
924935
//
925936
// Update: |psi> += α |p>
926937
// H|psi> += α H|p>
@@ -949,10 +960,54 @@ void DiagoPPCG<T, Device>::line_minimize(
949960
Real h_pp = gamma_dot(pp, hpp);
950961
Real s_pp = gamma_dot(pp, spp);
951962

963+
// Coefficients of A α² + B α + C = 0
964+
const Real A = s_ip * h_pp - h_ip * s_pp;
965+
const Real B = s_ii * h_pp - h_ii * s_pp;
966+
const Real C = s_ii * h_ip - h_ii * s_ip;
967+
968+
// Helper: evaluate R(α)
969+
auto ray_quot = [&](Real a) -> Real {
970+
return (h_ii + static_cast<Real>(2) * a * h_ip + a * a * h_pp)
971+
/ std::max(s_ii + static_cast<Real>(2) * a * s_ip + a * a * s_pp,
972+
static_cast<Real>(1e-30));
973+
};
974+
952975
Real alpha = 0;
953-
Real denom = h_pp * s_ii - h_ii * s_pp;
954-
if (std::abs(denom) > static_cast<Real>(1.0e-12))
955-
alpha = (h_ii * s_ip - h_ip * s_ii) / denom;
976+
Real alpha_linear = (std::abs(B) > static_cast<Real>(1e-30))
977+
? -C / B : static_cast<Real>(0);
978+
979+
// Use full quadratic when the α² term is significant.
980+
const Real tol = std::numeric_limits<Real>::epsilon() * static_cast<Real>(100);
981+
if (std::abs(A) > tol * std::max(static_cast<Real>(1), std::abs(B)))
982+
{
983+
const Real disc = B * B - static_cast<Real>(4) * A * C;
984+
if (disc >= static_cast<Real>(0))
985+
{
986+
const Real sqrt_disc = std::sqrt(disc);
987+
const Real a1 = (-B + sqrt_disc) / (static_cast<Real>(2) * A);
988+
const Real a2 = (-B - sqrt_disc) / (static_cast<Real>(2) * A);
989+
990+
const Real r1 = ray_quot(a1);
991+
const Real r2 = ray_quot(a2);
992+
const Real r_lin = ray_quot(alpha_linear);
993+
994+
// Pick the root with the lowest Rayleigh quotient.
995+
if (r1 < r2 && r1 < r_lin)
996+
alpha = a1;
997+
else if (r2 < r1 && r2 < r_lin)
998+
alpha = a2;
999+
else
1000+
alpha = alpha_linear;
1001+
}
1002+
else
1003+
{
1004+
alpha = alpha_linear;
1005+
}
1006+
}
1007+
else
1008+
{
1009+
alpha = alpha_linear;
1010+
}
9561011

9571012
for (int ig = 0; ig < n_dim_; ++ig)
9581013
{

source/source_hsolver/test/diago_ppcg_test.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,48 @@ TEST_F(DiagoPPCGTest, BlockSubspaceStrategy)
152152
<< "BLOCK_SUBSPACE: too many iterations";
153153
}
154154

155+
// -----------------------------------------------------------------------------
156+
// Test CONJUGATE_GRADIENT strategy
157+
// -----------------------------------------------------------------------------
158+
TEST_F(DiagoPPCGTest, ConjugateGradientStrategy)
159+
{
160+
std::vector<T> psi_run = psi;
161+
std::vector<Real> eval(nband, 0.0);
162+
163+
hsolver::DiagoPPCG<T, hsolver::base_device::DEVICE_CPU> solver(
164+
/* diag_thr = */ 1e-12,
165+
/* max_iter = */ 100,
166+
/* sbsize = */ 4,
167+
/* rr_step = */ 1,
168+
/* gamma_g0 = */ false,
169+
hsolver::PpcgStrategy::CONJUGATE_GRADIENT
170+
);
171+
172+
auto h_op = [this](T* in, T* out, int ld_in, int ncol) {
173+
dense_h_multiply(H_mat.data(), n_dim, in, out, ld_in, ncol);
174+
};
175+
176+
double avg_iter = solver.diag(
177+
h_op,
178+
/* spsi_func = */ nullptr, // S = I
179+
ld, nband, n_dim,
180+
psi_run.data(),
181+
eval.data(),
182+
ethr,
183+
prec.data()
184+
);
185+
186+
// Check eigenvalues against exact solution
187+
for (int i = 0; i < nband; ++i) {
188+
EXPECT_NEAR(eval[i], exact[i], 1e-8)
189+
<< "CONJUGATE_GRADIENT: eigenvalue[" << i << "] mismatch";
190+
}
191+
192+
// Should converge within reasonable iterations
193+
EXPECT_LE(avg_iter, static_cast<double>(100))
194+
<< "CONJUGATE_GRADIENT: too many iterations";
195+
}
196+
155197
int main(int argc, char** argv)
156198
{
157199
testing::InitGoogleTest(&argc, argv);

0 commit comments

Comments
 (0)