Skip to content

Refactor hsolver: LCAO direct solvers take H(k)/S(k), not the Hamiltonian - #7948

Merged
mohanchen merged 1 commit into
deepmodeling:developfrom
Critsium-xy:refactor/hsolver-lcao-diag-matrixblock
Sep 10, 2026
Merged

Refactor hsolver: LCAO direct solvers take H(k)/S(k), not the Hamiltonian#7948
mohanchen merged 1 commit into
deepmodeling:developfrom
Critsium-xy:refactor/hsolver-lcao-diag-matrixblock

Conversation

@Critsium-xy

Copy link
Copy Markdown
Collaborator

Reminder

  • I have read AGENTS.md and docs/developers_guide/agent_governance.md.
  • I have linked an issue or explained why this PR does not need one.
  • I have added adequate unit tests and/or case tests, or explained why not.
  • I have listed the exact verification commands run and their results.
  • I have described user-visible behavior changes, including INPUT parameter changes.
  • I have explained core-module impact for ESolver, HSolver, ElecState, Hamilt, Operator, Psi, or other source/ changes.
  • I have requested any needed governance exception below.

Linked Issue

No issue. Follow-up to #7920, second step of making source_hsolver a
self-contained numerical module. Behaviour-neutral and self-contained, so it is
sent on its own rather than bundled with the remaining steps.

Unit Tests and/or Case Tests for my changes

Commands run (Linux, gcc, cmake 3.31 + ninja, 15 cores). The base commit
d3722debd and this branch were built and tested in the same build tree, so
the comparison is exact rather than approximate.

Full build. ENABLE_ELPA=ON matters here: without it diago_elpa.cpp and
diago_elpa_native.cpp are not compiled at all, and two of the six changed
classes would go unchecked.

cmake -B build -G Ninja -DBUILD_TESTING=ON -DENABLE_LCAO=ON -DENABLE_MPI=ON \
      -DENABLE_OPENMP=ON -DENABLE_ELPA=ON
cmake --build build -j15 -- -k 0
OMP_NUM_THREADS=1 ctest --test-dir build -E '^(0[1-9]|1[0-9])_'

CUDA paths — the __CUDA branch of hsolver_lcao.cpp and the cusolver test
both changed:

cmake -B build-cuda -G Ninja -DBUILD_TESTING=ON -DENABLE_LCAO=ON -DENABLE_MPI=ON \
      -DENABLE_OPENMP=ON -DENABLE_ELPA=ON -DUSE_CUDA=ON
cmake --build build-cuda -j15 --target hsolver diag_cusolver MODULE_HSOLVER_LCAO_cusolver

PEXSI path — no PEXSI library on this machine, but module_pexsi/pexsi_solver.h
only needs <vector>, so the translation unit is still syntax-checkable:

c++ <flags taken from the ninja build> -D__PEXSI -fsyntax-only \
    source/source_hsolver/diago_pexsi.cpp

Result summary

check base d3722debd this branch
full build, ELPA on 3328/3328, 0 errors 3328/3328, 0 errors
unit tests 91% passed, 29 failed of 339 91% passed, 29 failed of 339
CUDA hsolver + diag_cusolver + MODULE_HSOLVER_LCAO_cusolver builds clean
diago_pexsi.cpp -D__PEXSI -fsyntax-only clean clean
agent_governance_check.py --staged 0 blockers

The two failing sets are identical test-for-testdiff of the sorted
The following tests FAILED lists is empty.

Direct numerical check of the changed solvers. MODULE_HSOLVER_LCAO sits in
that pre-existing failing set, but only because ctest runs it from the build
directory where it cannot find its *.dat fixtures. Run from
source/source_hsolver/test/, where the fixtures live, it does real work and
checks eigenvalues against stored references:

case base this branch
DiagoGammaOnlyTest.LCAO/0 — genelpa, Si64, NLOCAL=832 OK (832 ms) OK (797 ms)
DiagoGammaOnlyTest.LCAO/1 — scalapack_gvx, Si2 OK OK
DiagoGammaOnlyTest.LCAO/2 — lapack, Si2 OK OK
DiagoKPointsTest.LCAO/* SEGV SEGV

So DiagoElpa, DiagoScalapack and DiagoLapack each reproduce their
reference eigenvalues through the new signature. The DiagoKPointsTest suite
segfaults identically before and after this change — on 1 rank and on 4, and
also with the genelpa case filtered out — so it is a pre-existing problem with
that suite in this sandbox, which I have not tried to fix here.

Checks not run, with reason

  • -DENABLE_CUSOLVERMP=ON (diago_cusolvermp.cpp): this machine has no
    cuSOLVERMp / NCCL headers, so kernels/cuda/diag_cusolvermp.cuh cannot even
    be preprocessed (the same limitation as Refactor hsolver: move MatrixBlock to source_base, drop dead hamilt include #7920). The edit there is the same
    signature change as the other five; I checked its declaration and definition
    against each other by hand.
  • Integration cases under tests/: no code path and no arithmetic changes, only
    where the two matrix blocks are fetched. CI covers them.

What's changed?

Six LCAO direct eigensolvers took a hamilt::Hamilt<T>* and used it for exactly
one thing — phm_in->matrix(h_mat, s_mat) on the first line of diag():

DiagoScalapack, DiagoElpa, DiagoElpaNative, DiagoLapack, DiagoPexsi,
DiagoCusolverMP.

They now take the two matrix blocks directly, and the caller does the one
matrix() call. That is already how DiagoCusolver::diag() and every
diag_pool() in this directory work, so this makes the six consistent with the
parts of the module that were decoupled first, rather than inventing a pattern.

Three consequences:

  • HSolverLCAO::hamiltSolvePsiK fetches H(k)/S(k) once at the top
    instead of once inside each of the six solver branches. HamiltLCAO::matrix()
    is a pure accessor — OperatorLCAO::matrixHk only fills the blocks from
    pointers it already holds — and updateHk(ik) still runs before it in
    solve(), so hoisting is behaviour-neutral. Same hoist in the PEXSI branch,
    where matrix() now sits next to the updateHk(ik) it belongs to.

  • Parallel_K2D::distribute_hsk interleaves updateHk() and matrix()
    inside a loop of collective Cpxgemr2d calls, so it cannot simply hoist. It
    now takes an HskFunc callback and HSolverLCAO supplies the closure. The
    loop structure and the sequence of collective calls are untouched — only the
    source of the blocks moved out. Parallel_K2D is now a pure redistribution
    helper with no Hamiltonian dependency.

  • The four LCAO test files each defined a
    HamiltTEST : public hamilt::Hamilt<T> whose entire body was handing back H
    and S. They are now plain matrix suppliers, with the hamilt::Hamilt
    subclass, the empty constructHamilt/updateHk overrides, and the
    source_hamilt dependency all gone.

The matd/matcd typedefs in four .cpp files existed only to declare the
temporaries that are now parameters, and are removed.

before #7920 after #7920 this PR
source_hsolver files including source_hamilt 16 13 6
#include "source_hamilt/..." lines there 17 13 6

What is left is diago_iter_assist.h and the four HSolver* façade files, which
genuinely hold a hamilt::Hamilt* — those are the next two steps, not this one.

Governance Notes

  • INPUT/docs changes: none. No Input_Item, no user-visible behavior, no
    output format is touched.
  • Core module impact: HSolver, and the Hamilt interface as seen from
    HSolver. No hamilt:: class changes at all — the six solvers simply stop
    asking for one. hamilt::Hamilt<T>::matrix() keeps all its other callers in
    source_lcao, source_estate, source_io and source_esolver, untouched.
    No numerical change: the same MatrixBlock values reach the same solver code
    in the same order.
  • Exceptions requested: none. Two expected agent_governance_check.py
    warnings:
    • "Header diff adds an include dependency" on 6 headers. Each header drops
      source_hamilt/hamilt.h and gains source_base/matrix_block.h and/or
      source_psi/psi.h — the declarations it actually needs, which used to
      arrive transitively through hamilt.h. Net direction is narrower, not
      wider.
    • "Source code changed without test path changes" — the test files are
      changed (they had to be, the mock's base class is gone), and the assertions
      they make are identical. This is a signature refactor with no behaviour
      change, so no new test would add coverage.
  • No default arguments were added to keep old call sites alive; every caller
    is updated in this PR (AGENTS.md rule 5).

🤖 Generated with Claude Code

…nian

DiagoScalapack, DiagoElpa, DiagoElpaNative, DiagoLapack, DiagoPexsi and
DiagoCusolverMP each took a hamilt::Hamilt<T>* and used it for exactly one
thing: `phm_in->matrix(h_mat, s_mat)` on the first line of diag(). Hoist
that call into the caller and take the two matrix blocks instead, which is
what DiagoCusolver::diag() and every diag_pool() already did.

Parallel_K2D::distribute_hsk interleaves updateHk() and matrix() inside a
collective loop, so it cannot simply hoist. It now takes an HskFunc
callback and HSolverLCAO supplies the closure; the loop structure and the
sequence of collective calls are unchanged.

HSolverLCAO::hamiltSolvePsiK fetches H(k)/S(k) once instead of once per
solver branch. HamiltLCAO::matrix() is a pure accessor (OperatorLCAO::
matrixHk only fills the blocks from pointers it already holds) and
updateHk(ik) still runs before it, so the hoist is behaviour-neutral.

The four LCAO test files defined a `HamiltTEST : public hamilt::Hamilt<T>`
whose only job was handing back H and S; they are now plain matrix
suppliers with no source_hamilt dependency. The matd/matcd typedefs
existed only for the deleted temporaries and are removed.

source_hsolver -> source_hamilt includes: 13 files / 13 lines ->
6 files / 6 lines. No behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@mohanchen mohanchen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mohanchen mohanchen added Refactor Refactor ABACUS codes The Absolute Zero Reduce the "entropy" of the code to 0 labels Sep 10, 2026
@mohanchen
mohanchen merged commit fe3949f into deepmodeling:develop Sep 10, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Refactor Refactor ABACUS codes The Absolute Zero Reduce the "entropy" of the code to 0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants