Skip to content

Commit a3d481b

Browse files
committed
fix: use real-symmetric fast path in solve_hermitian_gevp
When the Hermitian Gram matrices are essentially real (max |Im| < 1e-14 * max |Re|), solve the generalized eigenvalue problem directly with dsygvd on the real part. This avoids the 2n x 2n block-real reduction for the common case of real wavefunctions / real operators. Only when the matrices carry significant imaginary parts (complex wavefunctions, e.g. k-point DFT) does the code fall back to the full block-real Hermitian solve. Should resolve the SEGFAULT seen in CI, which was likely caused by the block-real path when applied to numerically degenerate real matrices.
1 parent 8dd5650 commit a3d481b

1 file changed

Lines changed: 130 additions & 69 deletions

File tree

source/source_hsolver/diago_ppcg.cpp

Lines changed: 130 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -420,97 +420,158 @@ void DiagoPPCG<T, Device>::solve_hermitian_gevp(
420420
std::vector<T>& k_herm, std::vector<T>& m_herm,
421421
int n, std::vector<Real>& eval, std::vector<T>& evec) const
422422
{
423-
const int n2 = 2 * n;
424-
std::vector<Real> k_blk(n2 * n2, static_cast<Real>(0));
425-
std::vector<Real> m_blk(n2 * n2, static_cast<Real>(0));
423+
// -----------------------------------------------------------------------
424+
// Determine whether the matrices carry a significant imaginary part.
425+
// When wavefunctions are real (or the operator is real and the basis
426+
// has been kept real) the Hermitian Gram matrices are strictly real,
427+
// and we can use the fast real-symmetric dsygvd path directly.
428+
// -----------------------------------------------------------------------
429+
const int n2 = n * n;
430+
Real max_imag = 0, max_abs = 0;
431+
for (int i = 0; i < n2; ++i)
432+
{
433+
max_imag = std::max(max_imag, std::abs(std::imag(k_herm[i])));
434+
max_imag = std::max(max_imag, std::abs(std::imag(m_herm[i])));
435+
max_abs = std::max(max_abs, std::abs(std::real(k_herm[i])));
436+
max_abs = std::max(max_abs, std::abs(std::real(m_herm[i])));
437+
}
438+
const bool is_real = (max_imag <= max_abs * static_cast<Real>(1e-14));
426439

427-
// Build the 2n×2n real block matrices.
428-
for (int j = 0; j < n; ++j)
440+
if (is_real)
429441
{
430-
for (int i = 0; i < n; ++i)
442+
// ===== Fast path: real-symmetric matrices =====
443+
std::vector<Real> k_real(n2), m_real(n2);
444+
for (int i = 0; i < n2; ++i)
431445
{
432-
const T kij = k_herm[i + j * n];
433-
const T mij = m_herm[i + j * n];
434-
const Real kr = std::real(kij);
435-
const Real ki = std::imag(kij);
436-
const Real mr = std::real(mij);
437-
const Real mi = std::imag(mij);
438-
439-
// Upper-left n×n block: Re(K), Re(M)
440-
k_blk[i + j * n2] = kr;
441-
m_blk[i + j * n2] = mr;
442-
// Upper-right n×n block: -Im(K), -Im(M)
443-
k_blk[i + (j+n) * n2] = -ki;
444-
m_blk[i + (j+n) * n2] = -mi;
445-
// Lower-left n×n block: Im(K), Im(M)
446-
k_blk[i + n + j * n2] = ki;
447-
m_blk[i + n + j * n2] = mi;
448-
// Lower-right n×n block: Re(K), Re(M)
449-
k_blk[i + n + (j+n) * n2] = kr;
450-
m_blk[i + n + (j+n) * n2] = mr;
446+
k_real[i] = std::real(k_herm[i]);
447+
m_real[i] = std::real(m_herm[i]);
451448
}
452-
}
453449

454-
// Symmetrise explicitly.
455-
for (int j = 0; j < n2; ++j)
456-
for (int i = 0; i < j; ++i)
450+
const std::vector<Real> m0 = m_real;
451+
const Real shifts[] = {static_cast<Real>(1e-10),
452+
static_cast<Real>(1e-8),
453+
static_cast<Real>(1e-6)};
454+
bool solved = false;
455+
for (int attempt = 0; attempt < 3; ++attempt)
457456
{
458-
k_blk[j + i * n2] = k_blk[i + j * n2];
459-
m_blk[j + i * n2] = m_blk[i + j * n2];
457+
try
458+
{
459+
Lapack<Real>::sygvd(n, k_real.data(), m_real.data(),
460+
eval.data());
461+
solved = true;
462+
break;
463+
}
464+
catch (const std::runtime_error&)
465+
{
466+
m_real = m0;
467+
for (int i = 0; i < n; ++i)
468+
m_real[i + i * n] += shifts[attempt];
469+
}
460470
}
461471

462-
// M-save/restore for retry (same pattern as solve_small_generalized).
463-
const std::vector<Real> m0 = m_blk;
464-
std::vector<Real> eval2(n2, static_cast<Real>(0));
465-
const Real shifts[] = {static_cast<Real>(1e-10),
466-
static_cast<Real>(1e-8),
467-
static_cast<Real>(1e-6)};
468-
bool solved = false;
469-
for (int attempt = 0; attempt < 3; ++attempt)
470-
{
471-
try
472+
if (solved)
472473
{
473-
Lapack<Real>::sygvd(n2, k_blk.data(), m_blk.data(), eval2.data());
474-
solved = true;
475-
break;
474+
// k_real now holds the real eigenvectors column-by-column.
475+
evec.assign(n2, T(0));
476+
for (int j = 0; j < n; ++j)
477+
for (int i = 0; i < n; ++i)
478+
evec[i + j * n] = T(k_real[i + j * n], 0);
476479
}
477-
catch (const std::runtime_error&)
480+
else
478481
{
479-
m_blk = m0;
480-
for (int i = 0; i < n2; ++i)
481-
m_blk[i + i * n2] += shifts[attempt];
482+
evec.assign(n2, T(0));
483+
for (int i = 0; i < n; ++i)
484+
evec[i + i * n] = T(1, 0);
485+
eval.assign(n, static_cast<Real>(0));
482486
}
487+
return;
483488
}
484489

485-
if (solved)
490+
// ===== General path: complex-Hermitian matrices =====
491+
// Convert to a 2n×2n real block system and solve with dsygvd.
486492
{
487-
// Extract the n lowest eigenvalues (each pair gives two nearly-equal
488-
// eigenvalues; we take the first of each pair).
489-
eval.assign(n, static_cast<Real>(0));
490-
for (int i = 0; i < n; ++i)
491-
eval[i] = eval2[2 * i];
493+
const int n2blk = 2 * n;
494+
std::vector<Real> k_blk(n2blk * n2blk, static_cast<Real>(0));
495+
std::vector<Real> m_blk(n2blk * n2blk, static_cast<Real>(0));
492496

493-
// Extract complex eigenvectors from the block eigenvectors.
494-
// k_blk now holds the real eigenvectors column-by-column (n2×n2).
495-
evec.assign(n * n, T(0));
496497
for (int j = 0; j < n; ++j)
497498
{
498-
const int jj = 2 * j; // use the first of each degenerate pair
499499
for (int i = 0; i < n; ++i)
500500
{
501-
const Real vr = k_blk[i + jj * n2];
502-
const Real vi = k_blk[i + n + jj * n2];
503-
evec[i + j * n] = T(vr, vi);
501+
const T kij = k_herm[i + j * n];
502+
const T mij = m_herm[i + j * n];
503+
const Real kr = std::real(kij);
504+
const Real ki = std::imag(kij);
505+
const Real mr = std::real(mij);
506+
const Real mi = std::imag(mij);
507+
508+
k_blk[i + j * n2blk] = kr;
509+
m_blk[i + j * n2blk] = mr;
510+
k_blk[i + (j+n) * n2blk] = -ki;
511+
m_blk[i + (j+n) * n2blk] = -mi;
512+
k_blk[i + n + j * n2blk] = ki;
513+
m_blk[i + n + j * n2blk] = mi;
514+
k_blk[i + n + (j+n) * n2blk] = kr;
515+
m_blk[i + n + (j+n) * n2blk] = mr;
504516
}
505517
}
506-
}
507-
else
508-
{
509-
// All attempts failed — set eigenvectors to identity (no update).
510-
std::fill(evec.begin(), evec.end(), T(0));
511-
for (int i = 0; i < n; ++i)
512-
evec[i + i * n] = T(1, 0);
513-
std::fill(eval.begin(), eval.end(), static_cast<Real>(0));
518+
519+
// Symmetrise.
520+
for (int j = 0; j < n2blk; ++j)
521+
for (int i = 0; i < j; ++i)
522+
{
523+
k_blk[j + i * n2blk] = k_blk[i + j * n2blk];
524+
m_blk[j + i * n2blk] = m_blk[i + j * n2blk];
525+
}
526+
527+
const std::vector<Real> m0 = m_blk;
528+
std::vector<Real> eval2(n2blk, static_cast<Real>(0));
529+
const Real shifts[] = {static_cast<Real>(1e-10),
530+
static_cast<Real>(1e-8),
531+
static_cast<Real>(1e-6)};
532+
bool solved = false;
533+
for (int attempt = 0; attempt < 3; ++attempt)
534+
{
535+
try
536+
{
537+
Lapack<Real>::sygvd(n2blk, k_blk.data(), m_blk.data(),
538+
eval2.data());
539+
solved = true;
540+
break;
541+
}
542+
catch (const std::runtime_error&)
543+
{
544+
m_blk = m0;
545+
for (int i = 0; i < n2blk; ++i)
546+
m_blk[i + i * n2blk] += shifts[attempt];
547+
}
548+
}
549+
550+
if (solved)
551+
{
552+
eval.assign(n, static_cast<Real>(0));
553+
for (int i = 0; i < n; ++i)
554+
eval[i] = eval2[2 * i];
555+
556+
evec.assign(n2, T(0));
557+
for (int j = 0; j < n; ++j)
558+
{
559+
const int jj = 2 * j;
560+
for (int i = 0; i < n; ++i)
561+
{
562+
const Real vr = k_blk[i + jj * n2blk];
563+
const Real vi = k_blk[i + n + jj * n2blk];
564+
evec[i + j * n] = T(vr, vi);
565+
}
566+
}
567+
}
568+
else
569+
{
570+
evec.assign(n2, T(0));
571+
for (int i = 0; i < n; ++i)
572+
evec[i + i * n] = T(1, 0);
573+
eval.assign(n, static_cast<Real>(0));
574+
}
514575
}
515576
}
516577

0 commit comments

Comments
 (0)