Skip to content

Feat: add PPCG solver for PW diagonalization (reopened) - #8042

Open
cheerly-pku wants to merge 179 commits into
deepmodeling:developfrom
cheerly-pku:ppcg-v2-reopen
Open

cheerly-pku wants to merge 179 commits into
deepmodeling:developfrom
cheerly-pku:ppcg-v2-reopen

Conversation

@cheerly-pku

@cheerly-pku cheerly-pku commented Sep 28, 2026 •

Copy link
Copy Markdown

What's changed?

Adds ks_solver=ppcg to PW diagonalization, with block updates in
[X,W,P], local overlap-rank truncation, lowest-root startup correction, and
final residual validation. CPU execution is the main implementation; GPU
execution uses a host/device bridge with device H/S operators and host-side
PPCG linear algebra. This continues the implementation from #7848.

Review fixes pass block size and Ritz interval explicitly through HSolverPW,
reuse projected H/S images, perform full Rayleigh-Ritz solves at the requested
cadence, add function-level timers, initialize MPI in test harnesses, reject
invalid input collectively, and aggregate nonconvergence diagnostics. PPCG
now follows the shared NSCF iteration-limit setup.

Between full Ritz steps, Cholesky S-orthonormalization and the block gradient
maintain the trial space. Full Ritz extraction also occurs at initialization,
after startup correction, at tentative convergence, on orthogonalization
failure, and on the last update. The startup reclassification prevents a new
approximate low root from inheriting an old locked-prefix position.

INPUT behavior and numerical accuracy

  • pw_diag_ndim selects PPCG's local block size.
  • pw_diag_rr_step is a positive full-Ritz interval, with conservative default
    1. Larger intervals genuinely reduce full Ritz solves, but can require a
      larger iteration budget and alter the outer SCF trajectory. H/S cache
      refresh is independent of the Ritz interval: every 16 updates, selectively
      after strong P-projection cancellation, and before constructing W when the
      residual approaches a machine-precision-dependent scale. The last trigger
      is a cache-reliability heuristic, not an error bound or a changed convergence
      tolerance. Tentative convergence is checked again with freshly applied H/S;
      the extra validation/startup Ritz steps above remain mandatory.
  • pw_diag_thr has solver-dependent meaning. CG/Davidson compare eigenvalue
    changes; PPCG checks ||H psi_i - epsilon_i S psi_i|| against
    max(ethr_band[i], ethr, 1e-14). Equal input numbers do not imply equal
    accuracy or work across solvers.

We tested the reviewer's square-root residual-threshold suggestion, but
rejected that change after native force/stress comparisons failed. In case
817, a force-component discrepancy of approximately 0.04 eV/Angstrom
remained despite a total-energy difference of approximately 2.7e-6 eV.
The final code preserves the direct residual criterion. No numerical test
tolerances or reference values were relaxed to accept that experiment.
The C++ parameter descriptions and generated YAML/Markdown document this
difference explicitly.

Scope and memory accounting

There is no remaining diff under tests/integrate against this PR's base.
General comparison/reference-generation fixes were moved to
#8138.
The Wannier90 invariant-comparison proposal remains on an unpublished local
branch and is not activated by either PR.

Both ABACUS Davidson and this PPCG implementation have bounded workspace for
fixed dimensions and parameters. Davidson caps its basis at
david_ndim * nband and restarts; it does not grow without bound with
iterations. PPCG owns eight persistent ld_psi * nband CPU work arrays, plus
projected matrices, active-column copies, local-subspace/LAPACK temporaries,
and the caller's wavefunctions. The GPU bridge additionally allocates one full
host wavefunction copy and two full device transfer/operator buffers. The
algorithm's three blocks are not its total memory footprint, and counting the
eight persistent arrays alone does not establish peak memory use. No general
speed or memory advantage over Davidson is claimed.

Linked Issue

No separate issue. Follow-up to #7848, with review discussion in this PR.

Unit Tests and/or Case Tests for my changes

Current merge head: b5d5fdb3682b93a14642f833039f588cbcc4093e (2026-10-14),
merging deepmodeling/develop at 98c2e2257 into published head a26541419.
Resolved only the generated INPUT-document deletions and CPU case-list
conflicts; no PPCG numerical code, reference values, or tolerances changed.
The two unpublished local solver follow-ups and the local thesis are excluded.

Fresh merge verification: the main PW binary and ten focused test targets
build; 11/11 CTests pass, including double/float PPCG, MPI ranks 2/3/6 and
two pools, HSolverPW, DiagoParams, and INPUT/help tests. A direct LAPACK
comparison passes (PPCG max eigenvalue error 2.87e-13). CLI input checking,
parameter YAML/Markdown generation, governance (zero blockers, seven review
warnings), and the PR whitespace check pass. Exact commands, configuration,
corrected setup failures, and scope are recorded in the
merge verification update.
The full 130-case runtime suite, LCAO, CUDA, and no-MPI configurations were
not rerun for this merge. GitHub confirms no merge conflicts; checks for this
new head are pending as of this update.

The detailed measurements below belong to the prior published numerical
revision, not a rerun on the merge head:
a26541419075073caf9e201f13c15b382a55759a (2026-10-13).
Focused local verification: GNU 12.3, OpenMPI 5.0.10, Release, ELPA/libxc,
ENABLE_LCAO=OFF, OMP_NUM_THREADS=1, OPENBLAS_NUM_THREADS=1.
The rebuilt executable reports ABACUS v3.11.0-beta10; runtime logs identify
commit a26541419. CUDA verification uses CUDA 13.1 and an RTX 3090.

Focused coverage Test inventory Final result
MODULE_HSOLVER_ppcg 48 enabled; 1 disabled benchmark 48/48 pass
MODULE_HSOLVER_ppcg_float 4 4/4 pass
MODULE_HSOLVER_pw 6 6/6 pass
MODULE_HSOLVER_diago_params 2 2/2 pass
MPI PPCG 2/3/6 ranks; two pools; asymmetric invalid input/trace state pass
Native cases 817-820 24 existing property checks, np4 24/24 pass; no PPCG nonconvergence warnings
INPUT metadata/CLI serial7; MPI np1/np4; -h and --check-input all pass
Non-MPI PPCG build double48/float4 2/2 CTests pass
Independent LAPACK comparisons 5 default matrices, direct API all pass; max PPCG eigenvalue error 1.44e-12

The periodic-Ritz regression runs ten updates with intervals 1/4/16 and
checks full-Ritz call counts 11/5/3 and 13 H-operator calls in each case,
including one cancellation-triggered P refresh.
An 18-update variant checks the independent periodic refresh (23 H calls).
It independently checks the returned residuals, ordering, and orthogonality.
Other regressions cover a complex generalized problem with clustered roots
and a locked prefix, startup displacement of an old locked prefix, complex
phase preservation, failure classification, selective HP/SP refresh after
projection cancellation, well-separated cache reuse, zero-direction cleanup,
and near-precision residual refresh before constructing a new direction.

The native 820 cutoff case exposed a cache-cancellation failure during review.
Keeping a fixed refresh interval alone was insufficient. The corrected
reliability-before-W trial reached a fresh residual of 9.33e-13 in 49 updates
without changing its 1e-12 tolerance or 50-update budget. Final committed
revision repeat: 817-820's 24 existing checks pass, including 820's energy
and band reference checks, with no PPCG nonconvergence warnings.

Exact final commands and observed outcomes:

export OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1
# MPI unit build and 7/7 CTests PASS
cmake --build /tmp/abacus_ppcg_compare_mpi --target MODULE_HSOLVER_ppcg MODULE_HSOLVER_ppcg_float MODULE_HSOLVER_ppcg_parallel MODULE_HSOLVER_compare MODULE_HSOLVER_pw MODULE_HSOLVER_diago_params MODULE_IO_read_item_serial MODULE_ESTATE_elecstate_print -j6
ctest --test-dir /tmp/abacus_ppcg_compare_mpi -V -R '^(MODULE_HSOLVER_(ppcg|ppcg_float|ppcg_parallel_test|pw|diago_params)|MODULE_IO_read_item_serial|MODULE_ESTATE_elecstate_print)$'
# Non-MPI build: 2/2 CTests PASS
cmake --build /tmp/abacus_ppcg_trace_build --target MODULE_HSOLVER_ppcg MODULE_HSOLVER_ppcg_float -j4
ctest --test-dir /tmp/abacus_ppcg_trace_build -V -R '^MODULE_HSOLVER_ppcg(_float)?$'
# Independent dense-reference accuracy check: all five matrices PASS
/tmp/abacus_ppcg_compare_mpi/source/source_hsolver/test/MODULE_HSOLVER_compare
# Production builds PASS (existing configured toolchains)
cmake --build /tmp/abacus_ppcg_01pw_pw_build_elpa --target abacus_pw_para -j6
LIBRARY_PATH=/tmp/abacus_ppcg_fortran_lib cmake --build build_cuda --target abacus_pw_gpu -j6
# From tests/01_PW; case list contains only 817,818,819,820: 24/24 PASS
bash ../integrate/Autotest.sh -a /tmp/abacus_ppcg_01pw_pw_build_elpa/abacus_pw_para -n4 -o1 -j1 -f /tmp/ppcg_native_cases_trace_fix.txt
# Production CLI: all exit 0; --check-input from a valid copy of 817
abacus_pw_para --version
abacus_pw_para -h pw_diag_thr
abacus_pw_para -h pw_diag_rr_step
abacus_pw_para -h pw_diag_nmax
abacus_pw_para --check-input

Performance: three interleaved repetitions, np4, single-threaded BLAS/OpenMP,
same machine and copied inputs; only ks_solver differs for the Davidson
comparison. Old PPCG and dav_subspace use 8509c406d; new PPCG uses
a26541419. Timings below are median HSolverPW::solve_psik seconds, not
whole-job wall time. H-call counts and energy differences were stable across
the repetitions. The old and final PPCG residual tolerance semantics are the
same; the rejected square-root experiment is excluded.

Case Old PPCG s New PPCG s dav_subspace s H calls old → new abs ΔE new−old (eV) abs ΔE new−dav_subspace (eV)
817_PW_PPCG 1.48 1.06 0.29 402 → 239 1.82e-12 1.49e-6
038_PW_NC 5.40 3.91 0.72 1191 → 671 7.89e-10 7.24e-7
100_PW_DJ_SO 4.52 3.24 1.31 977 → 574 3.18e-11 1.06e-3
scf_deltaspin2 5.77 4.19 1.04 1536 → 911 1.20e-10 5.35e-6

This is a 27–28% reduction versus the reviewed PPCG revision, but PPCG remains
slower than dav_subspace in all four cases. The comparison is not evidence of
equal stopping accuracy across solvers. All four SCF runs reached the density
stopping criterion; 038_PW_NC still emitted five per-SCF PPCG nonconvergence
summaries and scf_deltaspin2 one. Old warnings were per k point, so their
counts are not a like-for-like diagnostic comparison. These warnings were
not hidden or turned into successful inner solves.

GPU: rebuilt build_cuda/abacus_pw_gpu, ran a copied
tests/11_PW_GPU/scf_ppcg input with mpirun -np 1 and compared with the CPU
build using the same input except device=cpu. Both SCF runs converged with
no PPCG nonconvergence warning. Absolute energy difference: 4.55e-12 eV;
force components match at the log's 10 decimal places; max printed stress
component difference: 6.01e-10 kbar. Both logs identify a26541419.

Remote CI for a26541419 is fully green (verified 2026-10-13): all 17
recorded GitHub check runs succeeded (including governance reruns),
and both ReadTheDocs status checks succeeded. The
build matrix,
CPU tests,
and CUDA tests
all passed. These CI results do not imply that every solver substitution
converges; the broader frozen-head comparison below has nonpassing results.
Not run locally: the complete LCAO test matrix, Intel/ROCm builds, or
multi-GPU. The substitution
sweep below uses a GNU LCAO/LibRI-enabled build; that is not the entire LCAO
test matrix. Local CUDA exercises the host/device bridge, not native GPU PPCG.

Fresh full PW substitution sweep, same frozen revision (2026-10-13):
Detailed validation update and bounded diagnostics.

130 active cases, comprising 121 CG/Davidson/default-CG substitutions,
four native PPCG cases and five unchanged BPCG controls. Both case trees
were extracted from the same commit; a 746-file/link byte audit confirmed
exactly 121 INPUT differences, solely at ks_solver. Source, references,
thresholds, seeds and iteration budgets were unchanged.

Original Autotest case outcome (mutually exclusive) Original solvers PPCG-substitution tree
Reference pass 128 53
Reference warning only 0 67
Fatal reference/collector outcome 1 9
Runtime failure before solving 1 1

Both full-suite commands exit 1. The native four and unchanged BPCG five
pass on both sides; among the 121 actual substitutions, 44 pass the original
reference pipeline. The additional completeness audit flags two original
harness passes: a malformed trailing reference line in 036_PW_AF and a
missing STRU_ION1_D_pass result in 061_PW_RE_NEW. The harness's footer
property counts and overlapping warning/fatal lists are not unique-case totals.

The full build lacks MLALGO descriptor output for 104 on both sides, and its
FLOAT_FFTW-disabled configuration prevents 208 from starting. A separate
same-head FLOAT_FFTW-enabled PW-build repeat of 208 passes five original
checks on each side, with converged SCF and no PPCG residual warning:
abs energy difference 1.28707368e-5 eV < its existing 2e-5 eV threshold.
This supplement does not rewrite the main build's outcome; 208 requests
neither forces nor stress.

Among the 121 main substitution pairs, full-precision energy and maximum
force/stress component differences are within all requested existing
threshold values for 55, exceed at least one for 65, and are unavailable for
208. Component comparisons are a supplementary diagnostic, not the original
aggregate force/stress reference predicate. A finite energy comparison for
104 does not validate its missing descriptors. MD/relaxation endpoints can
have different trajectories. Thirty substituted cases emit PPCG residual
warnings: 23 still in the final electronic step, seven only earlier. Small
observable differences or SCF convergence do not waive these inner tolerances.

Important main-run pair abs delta E (eV) max delta force (eV/Angstrom) max delta stress (kbar)
006_PW_UPF201_Eu 139.1375263 not requested not requested
078_PW_S2_elec_add 8.30699e-4 0.250409 52.8096
100_PW_DJ_SO 1.05935e-3 0.277390 4.29415

In a separate Eu diagnostic, dav_subspace converges within 5.90e-10 eV
of the PPCG endpoint, not CG. This is evidence for further root/SCF-state
investigation, not a fixed-Hamiltonian proof that either endpoint is correct.
Tightening both solvers' SCF thresholds to 1e-10 does not resolve 078's
force/stress spread; both tightened 100 runs exhaust their original 50-step
SCF budget without convergence. These isolated variants do not replace main
results. No blanket energy-accuracy or "all substitutions passed" claim is made.

Full-sweep environment: GNU 12.3, OpenMPI 5.0.10, ELPA 2026.02.001,
Libxc 7.0.0, Release, LCAO/LibRI/JSON/CNPY enabled. All completed running
logs identify a26541419; full executable SHA256:
bc8e5ea105b776f4ad75ea02f574931715bb76ceb39e2e930f274f157bb5d894.
From each independent tests/01_PW tree, with the unchanged Autotest script:

export OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1
export PRTE_MCA_mapby=slot PRTE_MCA_bindto=none
bash ../integrate/Autotest.sh -a "$VALIDATION_BINARY" -n4 -o1 -j4 -f "$ACTIVE_CASE_LIST"

The list contains all 130 active cases. Suites execute concurrently, so
recorded timings are descriptive, not a controlled performance comparison.
The earlier reviewer 17-case and historical substitution runs remain separate
from these fresh results. Larger performance work is deferred while the
accuracy and convergence gaps above are investigated.

After removal of the solver-specific NSCF override, NSCF follows the shared
legacy iteration-limit setup. The final default-interval nscf_out_pot
substitution exited normally but did not fully converge: one of three
k points hit the shared 30-iteration limit, with two active bands and final
max residual 1.432121e-6. It is not counted as a numerical pass. Fixing
the common NSCF limit semantics for every solver requires a separate change,
as requested in review. An additional final 078_PW_S2_elec_add substitution
reached SCF convergence but retained an inner PPCG warning; it also does not
extend the native 24/24 claim.

Maintainability is still a limitation. The optional four-file quality score
is 0/50/86/92 for diago_ppcg.cpp, hsolver_pw.cpp, hsolver_pw.h, and
diago_ppcg.h, respectively; both implementation files are below the tool's
60-point line. Its main findings are file length, control-flow complexity,
and long interfaces (including the 14-argument GPU bridge helper). These are
not resolved by passing numerical tests. The deterministic governance checker
has no blockers but retains the explained header/global warnings. A separately
reviewed, incrementally built decomposition is follow-up work, not included
or claimed complete in this numerical-correctness revision.

Historical benchmark and GPU observations from earlier commits are not
presented here as measurements of the final revision. Multi-GPU and native
distributed-GPU PPCG remain outside the validated scope.

Implementation history and diagnostics

Merge ff7edeca4 included behavior changes beyond conflict resolution:
startup [X,W_active] instead of [X,W_all], removal of the local-row-zero
real projection, reuse of work buffers, and spinor-aware active-rank counting.
Those changes are documented in the review reply with focused test anchors.
The published merge history and review references are preserved; these
historical changes have not been moved into rewritten commits.

The new RAII timers start on entry to the named function and stop at scope
exit, including early returns and exceptions. They expose projection,
operator application, local subspace solves, orthonormalization, and full
Ritz costs. Nested timer totals overlap; they are not additive cost partitions
or evidence of a speedup by themselves. In residual CSV
traces, block_sweep values are Rayleigh quotients, not ordered Ritz
eigenvalues; Ritz-error plots must use the actual Ritz/final stages.

Governance Notes

  • INPUT/docs: C++ Input_Item parameter registrations are authoritative.
    Following upstream Docs: Generate input reference in CI #7976, docs/parameters.yaml and
    docs/advanced/input_files/input-main.md are generated build artifacts,
    not committed sources. Fresh YAML/Markdown generation passes on the merge
    head; all five PPCG-related parameter records match the previous PR YAML.
  • Core impact: HSolver's PPCG implementation and PW dispatch; explicit
    configuration, diagnostics, tests, and PPCG case registrations. No changes
    to the existing CG/Davidson numerical kernels.
  • Header dependencies: diago_ppcg.h directly uses std::complex, the
    std::conditional/std::is_same traits, owned std::vector and
    std::function members, and the lightweight device type declarations from
    module_device/types.h. It does not include implementation .hpp files.
  • Global dependency rationale: the checker at a26541419 reports two added
    versus twenty removed textual references (net -18). The two DeltaSpin PW
    call sites retain their existing responsibility for obtaining INPUT state;
    a local const Input_para& input = PARAM.inp consolidates those reads and
    forwards the Ritz interval explicitly to HSolverPW. This is not a claim
    that the higher-level call sites have become global-free. PPCG's numerical
    implementation does not read PARAM, GlobalV, or GlobalC.
  • Exceptions: none requested. Final command:
    python3 tools/03_code_analysis/agent_governance_check.py --base cd2c7ce5b2c8b24893d3426922a20fb7d4b7fd3c --head HEAD --format text:
    exit 0, no blocking findings; header/global warnings explained above.
    git diff --check also passes.

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 final verification commands run and their results.
  • I have described user-visible behavior changes, including INPUT parameter changes.
  • I have explained core-module impact.
  • I have requested any needed governance exception above.

cheerly-pku and others added 30 commits March 28, 2026 13:19
Consider the previous contributions made by classmates, I'm only capable to make small difference without disrupting the entire program ---- like such a small "static".
…w_Small-Changes

2025PKUCourseHW5: Case: 1 - Change rank_seed_offset to static const
…ent)

Add PPCG iterative diagonalization with two strategies:
- CONJUGATE_GRADIENT: band-by-band Polak-Ribiere CG (verified working)
- BLOCK_SUBSPACE: block subspace diagonalization

Includes potrf retry fix: save/restore original matrix before applying
diagonal shift, preventing accumulated shifts from corrupting the matrix.

Test: 1D particle-in-a-box (n_dim=10), CG strategy matches exact
eigenvalues with error 4.3e-12.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ormalization

Three fixes for numerical stability:

1. potrf: save/restore original matrix before diagonal shift retries,
   preventing accumulated shifts from corrupting the Cholesky factor.

2. sygvd/syevd: skip workspace query (lwork=-1) and allocate directly.
   The LAPACK replacement ignores workspace queries, causing the second
   call to operate on already-transformed data, corrupting eigenvalues.

3. Block subspace: add chol_qr + hpsi/spi recomputation after
   update_one_block and every rayleigh_ritz, keeping wavefunctions
   S-orthonormal and preventing numerical drift of H|psi> and S|psi>.

Results (1D particle-in-a-box, S=I):
- CG nband=1: error 4.3e-12 (unchanged, already working)
- BLOCK_SUBSPACE nband=1: no longer NaN, converges (to wrong eigenvalue
  due to algorithmic limitation with S=I)
… RR steps

1. solve_small_generalized: save/restore M matrix before retry with shifts
   (prevents accumulation of shifts on sygvd-corrupted M)

2. BLOCK_SUBSPACE: add Krylov fallback for near-collinear p/w vectors
   When p is nearly parallel to w (cos^2 > 0.99), replace p with H·w
   to keep the 3-vector subspace [psi, w, p] full rank.
   This fixes NaN eigenvalues for nband>1 with S=I.

3. LAPACK: use standard workspace query (lwork=-1) pattern for syevd/sygvd
   More robust with real LAPACK implementations.

4. CG: add periodic Rayleigh-Ritz subspace rotation every rr_step iterations
   Corrects band ordering and eigenvalue estimates after band-by-band
   line minimization. Resets PR state after rotation.
The gamma_dot function returns only the real part of inner products,
which is correct for Hermitian forms like <psi|H|psi> but wrong for
projection coefficients where the imaginary part matters.

In orth_gradient and project_against, the projection coefficient
<psi_i | v> must use the full complex inner product to correctly
remove the overlap. Using only Re(<psi_i | v>) leaves an imaginary
component that corrupts the search direction, causing excited-state
bands to converge to wrong eigenvalues.

This fixes the CG strategy bands 1 and 2 converging to the highest
eigenvalue (3.919) instead of the first excited states.
…PACE

The chol_qr_active call after update_one_block re-orthonormalized psi
but left the p vector in the old basis, creating an inconsistency.
The p vector is constructed in update_one_block using the same subspace
rotation as psi, so they start consistent. Adding chol_qr_active before
the p vector is updated breaks this consistency.
This change was unrelated to the PPCG integration and should not
have been included.
H and S are real symmetric operators whose eigenvectors are real.
The previous complex random initialization produced complex
off-diagonal elements in the H-gram matrix (max |Im| ~ 0.5 for
nband=3), causing Re(<psi_i|H|psi_j>) != <psi_i|H|psi_j>.  The
gamma_dot function only returns the real part, so all subspace
Gram matrices (built via gram()) computed wrong off-diagonals,
leading to incorrect eigenvalues from sygvd.

With real-only psi all inner products are real and gamma_dot is
exact, so both BLOCK_SUBSPACE and CONJUGATE_GRADIENT strategies
should now converge to the correct eigenvalues.
…tioning

The 3-block subspace method builds a generalized eigenvalue problem
with basis V = [psi, w, p] where p is constructed from the previous
subspace eigenvectors (p_new += w_l * cw in update_one_block).  This
makes p a linear combination of the w vectors, causing the [w, p]
block of the S-gram matrix M to become nearly rank-deficient.

With nband=3 and sbsize=4 the 9x9 M matrix has condition number
large enough that dsygvd produces negative eigenvalues for the
positive-definite problem (observed: -0.26 at iter=2), and the
eigenvalues diverge exponentially thereafter.

Setting use_p=false reduces the subspace to [psi, w] (2-block),
which is a preconditioned Davidson-like method.  It converges
robustly: the BLOCK_SUBSPACE test now passes in 57 ms with all
3 eigenvalues within 1e-8 of the exact values.

The 3-block code path is preserved for future re-enablement once
a more robust p-vector construction is implemented.
With rr_step=4, the non-RR iterations use Cholesky orthonormalization
which mixes bands through the upper-triangular U^{-1}, causing high-energy
bands to contaminate low-energy ones.  This drives CG eigenvalues to the
spectrum maximum [3.31, 3.68, 3.92] instead of the correct lowest values
[0.081, 0.317, 0.690].

Using rr_step=1 forces Rayleigh-Ritz every iteration, which correctly
diagonalizes the subspace and preserves band ordering.
The orth_cholesky call before rayleigh_ritz mixes bands through the
upper-triangular U^{-1} factor, contaminating low-energy bands with
high-energy components.  This drives CG eigenvalues to the spectrum
maximum instead of the minimum.

rayleigh_ritz solves the generalized eigenvalue problem K v = λ M v
via dsygvd, which correctly handles non-S-orthogonal bases.  The
orth_cholesky is not needed and is actively harmful.

This makes the CG RR path consistent with BLOCK_SUBSPACE, which
calls rayleigh_ritz without prior orth_cholesky.
BLOCK_SUBSPACE starts with rayleigh_ritz (line 1085) which finds correct
eigenvalues and rotates psi before the iteration loop.  CG was using
diagonal Rayleigh quotients instead — these are poor approximations for
random initial guesses, producing wrong gradients that drive the band-by-band
line_minimize toward high-energy eigenstates.

With rr_step=1 (every-iteration RR), the CG loop itself is now correct,
but without an initial RR the first line_minimize step already pushes
psi in the wrong direction, and subsequent RR steps cannot fully recover.
Backup preserved at diago_ppcg_test.cpp.bak
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).
…e 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.
The 3-block [psi, w, p] subspace generalized eigenproblem becomes
ill-conditioned when residuals are small (near convergence).  The
[w, p] Gram block shrinks, the M matrix approaches singularity, and
dsygvd produces garbage eigenvectors that drive eigenvalues to
catastrophic values (e.g., -137775 instead of 0.081).

The p-bad H·w Krylov fallback fixes p~w collinearity but does not
address the small-residual ill-conditioning, which is fundamental to
the 3-block construction.  Keep use_p=false for robust convergence.
The [w,p] block of the Gram matrix M shrinks as residuals converge,
making M nearly singular and causing sygvd to produce garbage
eigenvectors.  Scaling w and p to unit S-norm keeps M well-conditioned
(diagonal ~1) without changing the subspace — Ritz values are identical
and Ritz vector coefficients cancel in update_one_block.

This enables the full 3-block [psi,w,p] subspace (use_p=true) by
addressing the fundamental ill-conditioning that the p-bad Krylov
fallback alone could not handle.
The Krylov fallback (replace p with Hw when p~w) was flawed:
when w is approximately an eigenvector (Hw ≈ λw), the replacement
does not fix collinearity.  After S-norm scaling, p ≈ w still,
M_wp ≈ [1,1;1,1] is rank-1, and dsygvd fails.

Instead, simply skip p for this iteration (use_p_now=false).
update_one_block still produces a valid p for the next iteration
from the w Ritz-vector contribution.
Add tests for:
- 2x2 matrix (smallest non-trivial case)
- Degenerate eigenvalues (H = I + J, multiplicity-3 degeneracy)
- Larger 20x20 tridiagonal with 5 bands
- Dense 8x8 matrix via Givens rotations (addresses full-matrix coverage)

All use CONJUGATE_GRADIENT strategy which has sygvd fallback.
BLOCK_SUBSPACE tests deferred due to dsygvd instability with some LAPACK builds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Covers diagonal, tridiagonal, dense, pentadiagonal, degenerate,
Neumann, S≠I, gamma_g0, single-band, all-band, many-band,
bad preconditioner, tight threshold, scaled, gapped spectrum,
rr_step=1, 1x1, and eigenvector quality checks.

Adds QuickBenchmark (CI-friendly) and DISABLED_FullBenchmark.
@Critsium-xy

Copy link
Copy Markdown
Collaborator

I think you need to fix the conflicts first and then inspect whether CICD can pass.

Resolve integration validation-tool conflicts and retain PPCG correctness hardening.
@cheerly-pku

Copy link
Copy Markdown
Author

Conflict resolution and fresh local verification for ff7edec

Merged the current deepmodeling/develop tip cd2c7ce without rebasing. Resolved the integration validation-tool migration conflicts while retaining the PPCG changes. The index has no unmerged entries, no stale tests/integrate/tools references remain, and git diff --check deepmodeling/develop...HEAD passes. GitHub now reports the PR as MERGEABLE; the current BLOCKED state is from checks still running, not a merge conflict.

Fresh verification performed with OMP_NUM_THREADS=1:

  • Built MPI targets: MODULE_HSOLVER_ppcg, MODULE_HSOLVER_ppcg_float, MODULE_HSOLVER_diago_params, MODULE_HSOLVER_compare, MODULE_HSOLVER_pw, MODULE_HSOLVER_ppcg_parallel, MODULE_PW_Sto_HSolver_UTs, and abacus_pw_para.
  • Focused CTest: 11/11 enabled tests passed.
  • Explicit PPCG comparison benchmark: exit 0; tested n=50, 100, 200, and 500 cases.
  • PPCG MPI regression: ranks 1, 2, 3, and 6 all passed.
  • Integration cases 817-820: 24/24 property checks passed.
  • No-MPI PPCG double/float tests: 2/2 passed.
  • Validation tools: 19/19 Python tests passed; catch_properties shell regression passed.
  • abacus_pw_para --version, -h pw_diag_rr_step, and --check-input all completed successfully.
  • agent_governance_check.py --base deepmodeling/develop --head HEAD --format text completed with only the existing non-blocking header-dependency review warnings.

The local report, convergence image, and research notes remain untracked and were not included in this push. CI for this new head is currently pending.

Reject non-positive Rayleigh-Ritz intervals, verify the quick benchmark reaches convergence, and keep generated parameter documentation synchronized.
Preserve strict checks for missing data and interior layout while accepting numerically irrelevant blank lines at end of generated outputs.
@cheerly-pku

Copy link
Copy Markdown
Author

Follow-up CI fix pushed in 65c6050.

The previous head had one failure in 02_NAO_Gamma/scf_out_hk_spin2 (237/238 cases passed). This was not a PPCG numerical failure: the generated sr_nao.csr contained one trailing blank line after its 37 data/layout lines, while the reference did not. The stricter CompareFile.py length check treated that formatting-only line as missing reference data.

The comparator now discards only trailing blank lines before comparison. It remains strict for missing data, extra nonblank lines/comments, interior blank lines, and non-finite numbers. Fresh verification:

  • CompareFile unit tests: 7/7 passed.
  • All validation-tool Python tests: 20/20 passed.
  • CTest compare_file_test, compare_wannier90_test, catch_properties_test: 3/3 passed.
  • Staged governance check: no findings.

A new CI run is now pending for this head.

@cheerly-pku

Copy link
Copy Markdown
Author

Final verification for 8509c406d186ba6eb239a22ab11c529b3cd6236b

The final audit found and fixed an MPI-only issue in the optional PPCG residual trace: rank-local file-open state could previously split a pool collective, and multiple ranks could truncate/write the same CSV. The trace now has one world-root writer, a pool-consistent enable state, and diagnostic LAPACK failures cannot alter solver control flow.

Fresh local verification with OMP_NUM_THREADS=1:

  • MPI Hsolver CTest: 4/4 passed (MODULE_HSOLVER_pw, PPCG double, PPCG float, and the PPCG parallel regression).
  • Parallel regression passed with 2, 3, and 6 ranks, plus a 4-rank/two-pool same-path trace case.
  • No-MPI PPCG double/float CTest: 2/2 passed.
  • MODULE_BASE_ParaReduce: 11/11 tests passed on each of 4 MPI ranks.
  • Native PPCG integration cases 817-820: 24/24 property checks passed with the rebuilt abacus_pw_para.
  • git diff --check passed and the governance checker reported no blocker.

CI for this exact head is fully green: 7/7 workflows, 15/15 GitHub check runs, and 2/2 ReadTheDocs statuses (17/17 total). GitHub reports the PR as MERGEABLE with no conflicts.

No documentation update is needed for this last commit: it only makes the existing internal ABACUS_PPCG_RESIDUAL_TRACE debug path MPI-safe and does not change INPUT parameters, public APIs, numerical behavior, or the CSV schema.

@Critsium-xy the conflicts are resolved and the latest head is fully green. This is ready for review.

@Critsium-xy Critsium-xy 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.

Review of head 8509c406d.

Local verification (Release, OpenMPI 4.1.2, OMP_NUM_THREADS=1, LCAO+ELPA+libxc):

  • Unit tests: MODULE_HSOLVER_ppcg 39/39 (1 disabled), ppcg_float 4/4, ppcg_parallel_test (np 2/3/6), diago_params, pw, IO_read_item_serial, elecstate_print: all pass.
  • Autotest.sh -n 4 on 817–820: 24/24 checks pass.
  • Solver substitution (dav_subspace/dav/cg/ppcg) on 17 existing 01_PW cases: PPCG never crashes and energies agree with dav/dav_subspace to ~1e-6 eV. 006_PW_UPF201_Eu: PPCG and dav_subspace give -2364.17 eV, cg and dav give -2225.02 eV, which suggests the existing reference is a wrong-root state (worth a separate issue).

Performance (solve_psik time):

case dav_subspace ppcg ppcg, lock thr = sqrt(ethr) (local experiment)
817 GaAs 0.28 s 1.39 s 0.55 s
038_PW_NC 0.97 s 7.43 s (4 nmax warnings) 2.59 s (0)
100_PW_DJ_SO 1.61 s 7.10 s 1.93 s
scf_deltaspin2 2.23 s 12.64 s (2) 5.04 s (0)
Si64, 150 bands, np 16 16.2 s 94.1 s (4) 51.1 s (2)

On Si64, PPCG spends 53.5 s in hPsi against 7.8 s for dav_subspace, and about 41 s outside hPsi against about 8 s. See the inline comments for the causes.

Other points

  • Merge commit ff7edeca4 contains +189/-172 lines of solver behavior changes in diago_ppcg.{h,cpp} and hsolver_pw.cpp: the startup subspace changes from 2*nband to nband+nact, gamma_g0_real is removed, and workspaces are merged. Please move these into a regular commit and describe them.
  • PR description: the claim that Davidson memory grows without bound is inaccurate, because DiagoDavid allocates a fixed nbase_x = david_ndim * nband (diago_david.cpp:21). The claimed PPCG footprint of ~3*nband*npw also does not match the 8+ persistent ld_psi x nband buffers. The "Current verification" section still refers to 74cd30b95.
  • The integration-tool changes (compare_wannier90.py, CompareFile.py, props_*.sh) are unrelated to PPCG and change pass/fail rules for existing cases. Please split them into a separate PR.
  • DiagoPPCG has no ModuleBase::timer instrumentation, so its cost cannot be broken down in running_*.log.

for (int j = 0; j < n_band_; ++j)
{
const Real rnrm = std::sqrt(std::max(Real(nrm2_all[j]), Real(0)));
const Real thr = std::max(Real(ethr_band[j]), diag_thr_);

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.

Convergence semantics differ from the other PW solvers. dav, dav_subspace and cg test |Δε| < ethr_band (diago_david.cpp:224, diago_dav_subspace.cpp:196, diago_cg.cpp:450). Here the test is ||Hψ-εSψ|| < ethr_band. The SCF loop tunes ethr for eigenvalue change, and the eigenvalue error is about ||r||^2/gap, so this is roughly quadratically stricter. In practice the threshold reaches 8.3e-12 in nscf_out_pot, and pw_diag_nmax is hit in 038_PW_NC, scf_deltaspin2, 078 and Si64. Changing only this line to sqrt(max(ethr, diag_thr)) cuts solve_psik time 2–4x (see review body) with energies unchanged within cross-solver spread. Please align the criterion with the other solvers, or document that pw_diag_thr means something different for PPCG.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agreed that the threshold semantics differ. We tested the square-root residual threshold, but it failed the native force/stress checks: case 817 retained an approximately 0.04 eV/Angstrom force-component discrepancy despite a total-energy difference of about 2.7e-6 eV. We therefore retained the direct residual tolerance and explicitly documented PPCG's criterion in the C++ parameter description and regenerated YAML/Markdown. No comparison tolerance or reference was relaxed. The HSolverPW regression now checks that the requested residual norm is preserved.

Verified at a26541419: HSolverPW 6/6, including PpcgPreservesRequestedResidualNorm; native cases 817-820 passed all 24 existing property checks without PPCG nonconvergence warnings. The five direct-API LAPACK comparisons also passed with maximum PPCG eigenvalue error 1.44e-12 at residual tolerance 1e-6; this is not an assertion that identical PW thresholds have identical meanings. See the updated PR description's final comparison table for performance and case-specific errors.

Comment thread source/source_hsolver/diago_ppcg.cpp Outdated
h_active_vector.assign(ld_psi_ * nact, T(0));
s_active_vector.assign(ld_psi_ * nact, T(0));
scatter_cols(p_.data(), active_cols, active_vector);
apply_h(hpsi_func, active_vector.data(), h_active_vector.data(), nact);

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.

H and S are re-applied to p on every iteration after projecting it against psi. The projection is linear, so Hp and Sp can be updated with the same coefficients (Hp -= Hψ·C, Sp -= Sψ·C), as standard LOBPCG does. The current code doubles the H applications per active band. The second S application on w (L1559) is also redundant, because project_against (L1551) already updated sw_. The same applies to p here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The projection now updates P, SP, and HP with the same coefficients, and the redundant second S application to W has been removed. Operator applications are instrumented, and a focused regression checks reuse against independently recomputed final residuals.

The native 820 cutoff case exposed a numerical caveat: at one projection, P's norm fell to about 5.3e-12 of its original norm. Normalizing this nearly annihilated vector amplified the roundoff in HP-HX*C to about 5e-5, which then contaminated later HX updates. This was cancellation amplification, not a mismatched transformation of the three arrays; a fixed refresh interval alone did not prevent it.

The guard refreshes H/S for P columns whose squared norm falls below half its pre-projection value; pool-reduced norms make every rank select the same columns. Zero directions and their images are cleared. Near the numerical cancellation scale, fresh images and a recomputed residual are used before constructing W; this trigger does not loosen convergence tolerances. Periodic refresh remains, and tentative convergence is confirmed with fresh H/S. The corrected trial reached 9.33e-13 in 49 updates within the unchanged 1e-12/50-update criteria.

The cache fix is in 60730342f. At final a26541419, double 48/48 and float 4/4 passed in both MPI-linked and non-MPI builds; native 817-820 passed 24/24 checks without PPCG nonconvergence warnings. The bounded 10/18-sweep regression uses 13/23 H applications, including one selected-P refresh. The near-precision regression checks 6 versus 4 H applications with independently recomputed residuals. See the updated PR description's final comparison table for timings.

Comment thread source/source_hsolver/diago_ppcg.cpp Outdated
// synchronized with the updated active vectors. The block update
// can otherwise drift into an ill-conditioned basis before the next
// Ritz rotation.
last_rr_succeeded = rayleigh_ritz(

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.

A full nband x nband Rayleigh–Ritz (two Gram builds, hegvd, three ld_psi x nband x nband GEMMs) runs on every iteration. Avoiding this every step is the main point of PPCG. pw_diag_rr_step controls only when H/S are recomputed (L1681), not the RR frequency, so the INPUT annotation "Rayleigh-Ritz re-application interval" is misleading.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Corrected: pw_diag_rr_step now controls full projected Ritz diagonalizations. Intermediate sweeps use S-orthonormalization and the block gradient. Initialization, startup reclassification, tentative convergence, failed orthogonalization, and the final update also force a Ritz solve.

The cadence regression checks 11/5/3 full Ritz calls for ten updates at intervals 1/4/16, plus sorted returned values, independently recomputed residuals, and orthogonality. The conservative default is now 1; larger intervals are opt-in because they can change convergence and the SCF trajectory. Operator-cache refresh is documented separately.

The cadence fix is in df9bdab4c; the default and cache safeguards were finalized in the later commits. At a26541419, all 48 double tests, including cadence and clustered generalized-spectrum tests, passed in both MPI-linked and non-MPI builds; float 4/4 and native 24/24 property checks also passed. See the updated PR description's final comparison table for timings rather than inferring a speedup from fewer Ritz calls alone.

Comment thread source/source_hsolver/diago_ppcg.cpp Outdated
{
if (!hpsi_func)
{
throw std::invalid_argument("PPCG: H operator is empty.");

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.

std::invalid_argument here, and std::out_of_range in the GPU bridge (hsolver_pw.cpp:142,151), are not caught by HSolverPW. Under MPI this ends in std::terminate with no ABACUS diagnostics, possibly on one rank only. Please use ModuleBase::WARNING_QUIT as other solvers do.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The HSolverPW boundary now catches PPCG exceptions and reports them through ModuleBase::WARNING_QUIT. Solver input validation first synchronizes failure across the MPI pool, so a bad input on one rank is rejected by every pool rank before operator/Gram collectives. The GPU bridge also validates sizes and bounds before buffer operations.

Coverage includes PpcgFailureUsesApplicationDiagnostic and asymmetric MPI invalid input that checks no rank enters the H operator. At a26541419, HSolverPW passed 6/6 tests; the MPI script passed with 2, 3, and 6 ranks and with four ranks split into two pools. This addresses the known input/bounds failures; it does not promise collective recovery from arbitrary callback exceptions or out-of-memory failures.

public:
static Real PW_DIAG_THR;
static int PW_DIAG_NMAX;
static int PW_DIAG_NDIM;

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.

New mutable static state duplicates existing explicit state: HSolverPW already receives pw_diag_ndim through its constructor (this->pw_diag_ndim). Any caller that builds HSolverPW without first running setup_diago_params_pw (unit tests, for example) silently gets 4/16 here, whatever pw_diag_ndim the constructor received. Please use this->pw_diag_ndim and pass pw_diag_rr_step through the constructor (governance rules 1/2).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed the PPCG PW_DIAG_NDIM/PW_DIAG_RR_STEP static copies. HSolverPW now uses its existing pw_diag_ndim member and receives pw_diag_rr_step explicitly through its constructor; callers were updated without adding a default argument. The independent-constructor-configuration regression builds differently configured instances without setup_diago_params_pw and checks their dispatch against directly configured PPCG instances.

At a26541419, MODULE_HSOLVER_pw passed 6/6. The independent-configuration, residual-threshold, and warning-aggregation tests also passed on each rank in a four-rank/four-pool run.

Comment thread source/source_hsolver/diago_params.cpp Outdated
if (inp.calculation != "nscf")
// Preserve the established NSCF behavior of existing solvers while allowing
// PPCG to honor its configured iteration limit.
if (inp.calculation != "nscf" || inp.ks_solver == "ppcg")

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.

Solver-name branching in the shared parameter setup gives pw_diag_nmax different NSCF semantics for PPCG and for every other solver. If the NSCF behavior is a bug, fix it for all solvers in a separate PR. Otherwise, keep PPCG consistent with the existing behavior.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed the PPCG-only NSCF branch. setup_diago_params_pw now preserves the existing shared NSCF iteration-limit behavior for every solver. PreservesSharedNmaxForNscf checks cg, dav, dav_subspace, bpcg, and ppcg; the SCF test separately checks that pw_diag_nmax is applied there.

At a26541419, MODULE_HSOLVER_diago_params passed 2/2 tests. This verifies the shared setup behavior, not a blanket claim that every NSCF calculation converges within the inherited iteration budget; runtime limits and case-specific outcomes are listed in the updated PR description.

In particular, the final default-interval nscf_out_pot substitution still hit the common 30-iteration limit at one of three k points, with two active bands and residual 1.432121e-6. I have explicitly recorded this as not fully converged, rather than restoring a PPCG-only override. A common NSCF-limit fix should be reviewed separately for all solvers.

Comment thread source/source_hsolver/hsolver_pw.cpp Outdated
<< ppcg_result.max_residual << '.';
}
const std::string warning = warning_stream.str();
ModuleBase::WARNING("HSolverPW::hamiltSolvePsiK", warning);

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.

This warning fires per k-point per SCF iteration. With the current residual criterion it is common (for example, 4 occurrences in 038_PW_NC), so it adds noise to warning.log without user action.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Replaced the per-k-point warning with one summary per HSolverPW solve/electronic iteration. Each pool contributes its k points once; the summary reports the number of unconverged k points, iteration-limit hits, active bands, and maximum final residual. It retains actionable nonconvergence information while avoiding repeated per-k-point messages. PpcgConvergenceWarningSummarizesKpoints covers the aggregation.

At a26541419, HSolverPW passed 6/6, and the aggregation test also passed on each rank with four independent pools. Native cases 817-820 passed all 24 existing property checks without PPCG nonconvergence warnings. Deliberately nonconverged unit-test inputs still exercise the summary rather than suppressing the warning entirely.

Comment thread source/source_base/parallel_comm.cpp Outdated

MPI_Comm POOL_WORLD; //groups for different plane waves. In this group, only plane waves are different. K-points and bands are the same.
// Plane-wave pool: only the plane waves differ; k-points and bands are shared.
MPI_Comm POOL_WORLD = MPI_COMM_NULL;

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.

This changes the initial value of a global communicator in source_base, and Parallel_Reduce::pool_ready() / is_world_root() add runtime MPI-state checks to the base layer. Both exist only to let PPCG run in tests without MPI_Init. Please initialize MPI and POOL_WORLD in the test harness instead, as the other hsolver tests do, or justify the base-layer change in the PR.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The MPI-linked PPCG unit/float/benchmark harnesses now initialize MPI and the pool communicator. POOL_WORLD's initializer and pool_ready()/the reduction bypass were removed; solver collectives no longer silently become local operations.

We retained is_world_root() for a production reason: simultaneous k-point pools may request the same residual-trace filename, and only world rank zero may open it. Its initialized/finalized guards protect that read-only rank query, not MPI-less solver execution. The successful-open flag is synchronized within the writer's pool before trace collectives. The float reduce_pool instantiation supports the single-precision path.

At a26541419, the MPI script passed with 2, 3, and 6 ranks and four ranks/two pools, including the trace and asymmetric-input checks. Double 48/48 and float 4/4 also passed in both MPI-linked and non-MPI builds. The non-MPI builds are separate configurations, not MPI-linked tests relying on uninitialized communicators.



EIG_DEGENERATE_WINDOW_EV = 3.0e-6
EIG_CLUSTER_SHAPE_TOLERANCE_EV = 1.0e-6

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.

This replaces the check for the existing case 101_PW_W90, which does not use PPCG. Eigenvalues inside a 3 µeV cluster may now differ by up to 1e-6 eV, compared with 1e-8 per entry before. The reference diamond.eig contains a 4.3e-7 split that this rule absorbs. AMN is compared through A^H A, so band mixing passes. This is unrelated to PPCG; please move it to a separate PR.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed this comparator and its collector wiring from the solver PR in 32723ea66. tests/integrate now matches the PR base, so 101_PW_W90 keeps its existing comparison policy. The invariant comparator is preserved only as an unpublished, unwired proposal with the changed cluster/shape tolerances explicitly identified for separate review. It is not included in #8138 either.

ExitError(i,sline1[j],sline2[j],j)
elif sline1[j] != sline2[j]: ExitError(i,sline1[j],sline2[j],j)

if len(lines1) != len(lines2):

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.

The line-count check and the non-finite check change pass/fail behavior for every case that uses CompareFile.py. They are reasonable, but they do not belong in a solver PR.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agreed. These shared comparison changes and their focused tests were removed from this PR in 32723ea66 and extracted into #8138. The solver PR now has no tests/integrate diff against its base. #8138 preserves existing numerical tolerances and does not activate the Wannier90 invariant-comparison policy.

@cheerly-pku

Copy link
Copy Markdown
Author

Thank you for independently building the PR, running the focused tests and
native cases, and measuring the substituted solvers. Your review identified
real performance, API, scope, and documentation problems.

At a26541419, the revision reuses HP/SP under projection, makes
pw_diag_rr_step control full Ritz frequency, passes PPCG settings explicitly,
uses initialized MPI harnesses, handles invalid input collectively, and
aggregates nonconvergence warnings. Function-level timers now expose
apply_h, apply_s, project_against, build_small_subspace,
solve_small_generalized, update_one_block, orthonormalize,
update_block_gradient, rayleigh_ritz, and diag.

We also tested the proposed square-root tolerance. It reduced work, but failed
the native force/stress checks: case 817 retained roughly 0.04 eV/Angstrom
force-component error even though its total energy differed by only about
2.7e-6 eV. That experiment is not the final behavior. We restored the direct
residual criterion, left comparison tolerances/references unchanged, and
documented the different PPCG meaning of pw_diag_thr. Final accuracy and
timing results are in the updated PR description: native cases 817-820 pass
24/24 checks; three interleaved measurements on your four small cases show
27–28% less solve_psik time and 41–44% fewer H calls than 8509c406d.
PPCG remains slower than dav_subspace, and the description explicitly
records the remaining inner-iteration warnings and actual energy spreads.

The default full-Ritz interval is now conservatively 1. During validation,
an intermediate periodic-Ritz revision needed 93 updates on the fixed-H SCAN
cutoff case with interval 16, exceeding the existing 50-update budget.
Larger intervals remain functional but require validation of the intended
calculation and budget. Cases 817/818 also showed SCF-path sensitivity
at their existing stopping tolerances. We retained their inputs, references,
and comparison tolerances. H/S cache refresh is independent of full-Ritz
frequency, so the conservative Ritz default does not by itself require fresh
operator applications at every Ritz step.

The native 820 check also exposed why periodic refresh alone was insufficient:
one P projection reduced its norm by roughly 5.3e-12, and normalization
amplified cancellation error in its cached HP to roughly 5e-5. That error
then propagated into HX. The revised guard uses pool-reduced before/after
norms to refresh only collapsed P images. Near the numerical cancellation
scale, it additionally recomputes images and the residual before constructing
W; the scale is a refresh heuristic, not a looser convergence tolerance.
Tentative convergence is checked with fresh H/S applications, and periodic
refresh remains as a separate safeguard. The corrected trial reached
9.33e-13 in 49 updates, within the unchanged 1e-12/50-update criteria.
Final committed-revision evidence: double48/48, float4/4, all seven related
MPI CTests, np2/3/6 and two-pool regressions, and the native 24/24 checks pass.
The same double/float suites also pass in an MPI-disabled build.

The description now correctly states that Davidson's basis is capped at
david_ndim * nband with restart, and that PPCG has eight persistent full
CPU work arrays plus temporary storage and additional host/device bridge
buffers; eight arrays are not a peak-memory estimate. The stale current-verification
section and unsupported general performance claims have been replaced.

The solver PR now has no diff under tests/integrate. General validation
hardening is in #8138.
The Wannier90 policy was isolated as an unpublished, unwired proposal; its
changed tolerances are not active in either PR.

You are also right about ff7edeca4: its first-parent solver diff is
+189/-172, not just conflict resolution. Its behavior changes were:

  1. The startup solve retains all X columns but only active correction columns
    (nband+nact). StartupCorrectionKeepsConvergedPrefixInactive checks roots
    1-6 with two already-converged states and the active iteration count 1+4/6.
    The new StartupReclassifiesDisplacedLockedPrefix additionally checks that
    a newly found low root cannot inherit an old frozen-prefix position.
  2. gamma_g0_real was removed. The PW INPUT path resets gamma_only to false;
    a Gamma k-point is not half-G storage. The old helper zeroed the imaginary
    part of local row zero without the actual ig_gge0 mapping.
    ExactImaginaryLeadingCoefficientNeedsNoProjection now checks a valid
    imaginary-leading exact eigenstate's eigenvalue, norm, and residual without
    prescribing its final phase. The complex-Hermitian tests remain.
  3. Ritz/active-column work buffers were reused after their previous contents
    were consumed; X/HX/SX retain matching coefficient transformations.
  4. The shared rank check counts both spinor components rather than using a
    padded storage dimension.

These should originally have been separate descriptive commits. We preserved
the published history and review anchors; we have not claimed to move them out
of the merge or force-pushed rewritten history. Their rationale and focused
regressions are now explicit. The review fixes themselves are ordinary,
separately verified commits.

The updated PR description lists exact build/test commands, the four-case
performance/energy table, and CPU/GPU agreement (4.55e-12 eV on the same
GaAs input). Five direct-API LAPACK comparisons pass with PPCG maximum
eigenvalue error 1.44e-12; they are not an equal-threshold PW speed test.

One remaining limitation deserves emphasis: after removing the PPCG-only
NSCF override as requested, a final nscf_out_pot substitution still reaches
the common 30-iteration limit at one k point (residual 1.432121e-6). We do
not count that as a numerical pass or claim all PW substitutions converge.
The common NSCF behavior and large-file/interface maintainability remain
follow-up work. The full substitution matrix and your Si64 benchmark have
not been repeated on this head. Remote CI is newly running, not yet declared
fully green. I have replied to each inline point; please re-review the fixes.

@cheerly-pku

Copy link
Copy Markdown
Author

Fresh full 01_PW solver-substitution results on frozen head
a26541419075073caf9e201f13c15b382a55759a (2026-10-13).

CI remains green: 17 recorded check runs, including governance reruns, and
both ReadTheDocs statuses succeeded. However, this broader local experiment
does not support claiming that every existing PW input can be switched
to PPCG and pass its original reference checks.
Green CI is not merge approval or a statement that all validation gaps are resolved.

Scope and unchanged-input results

The current list has 130 cases: 121 original CG/Davidson/default-CG inputs
were switched only at ks_solver, four native PPCG cases were retained, and
five BPCG/band-parallel cases were left unchanged. Both sides came from
git archive at the same SHA and used the same frozen executable. A pre-run
byte audit checked 746 files/links: exactly 121 INPUT files differed, solely
in ks_solver. No source, references, tolerances, seeds or iteration budgets
were changed for this main comparison.

Original Autotest outcome, mutually exclusive case counts Original solvers PPCG-substitution tree
Reference checks pass 128 53
Reference warning, no fatal error 0 67
Fatal reference/collector outcome 1 9
Runtime failure before solving 1 1
Total 130 130

All four native PPCG cases and five unchanged BPCG controls pass on both
sides. Within the 121 actual substitutions, 44 pass the original reference
pipeline. The script's footer counts property checks, and its warning/fatal
case lists overlap; adding the footer numbers is not a valid failed-case count.
The extra completeness audit also flags two original-solver artifacts despite
their raw harness pass: a malformed trailing reference line in 036_PW_AF,
and a missing STRU_ION1_D_pass result in 061_PW_RE_NEW. These raw passes
are not silently promoted to complete independent validation.

Build limitations are recorded rather than counted as PPCG numerical defects:
104_PW_Gene_Descriptors lacks descriptor output with MLALGO disabled on both
sides. 208_PW_CG_float cannot start with FLOAT_FFTW disabled in the full build.
A separate same-head FLOAT_FFTW-enabled PW-build repeat of 208 passes all
five original checks on each side, with SCF convergence and no PPCG warning;
absolute energy difference is 1.28707368e-5 eV, below its existing 2e-5 eV
threshold. Force/stress were not requested in 208.

Numerical differences and convergence are separate

For the 121 main substitutions, full-precision final energy and complete
force/stress components give 55 pairs within all requested existing threshold
values, 65 exceeding at least one, and one unavailable pair (208)
. Applying
the force/stress threshold numbers to maximum component differences is an
additional diagnostic, not the original harness's sum-of-absolute-components
comparison. The MLALGO-disabled case's finite energy remains comparable, but
this does not validate its missing descriptors. MD/relaxation differences are
trajectory-dependent, not fixed-geometry eigensolver error bounds.

There are 30 substituted cases with PPCG residual warnings: 23 still report
warnings in the final electronic step and seven only in earlier steps.
Reference agreement, process exit zero and outer SCF convergence do not waive
those requested inner residual tolerances.

Important unresolved main-run pair abs energy difference (eV) max force-component difference (eV/Angstrom) max stress-component difference (kbar)
006_PW_UPF201_Eu 139.1375263 not requested not requested
078_PW_S2_elec_add 8.30699e-4 0.250409 52.8096
100_PW_DJ_SO 1.05935e-3 0.277390 4.29415

These are not all phase-sensitive outputs or harmless rounding. In a separate
diagnostic, Eu with dav_subspace converges within 5.90e-10 eV of PPCG, not
the original CG endpoint. This supports the need to distinguish root selection
from different SCF solutions; lower energy and cross-solver endpoint agreement
alone are not a fixed-Hamiltonian proof. For 078, tightening both solvers'
SCF threshold to 1e-10 does not resolve the force/stress spread. For 100, both
tightened runs exhaust their existing 50-step SCF budgets without convergence.
None of these diagnostic INPUT variants replaces a failed main-run result.

Bounded iteration diagnostics

Separate copied inputs, using the separately identified same-head PW-only
build (abacus_pw_a265, SHA256 6aa3774a86d64aa5fae0ec4213eaddec233321074f5f5929fbd61dbd9c1dec1b),
confirm that raising the diagonalization budget from
50 to 200 removes the warnings in 038_PW_NC and scf_deltaspin2; the latter's
two difficult traces converge at updates 54/55 with identical first-50-update
histories. This is not a global budget recommendation or changed default.

For nscf_out_pot, setting INPUT pw_diag_nmax=200 leaves the complete trace
byte-identical: the shared NSCF setup still supplies a 30-update cap. The final
k-point retains two active bands and fresh residual 1.432121e-6. This common
NSCF behavior needs its own interface/behavior change and cross-solver tests;
no solver-specific override was reintroduced here.

Reproduction and next review

GNU 12.3 / OpenMPI 5.0.10, Release, ELPA 2026.02.001, Libxc 7.0.0, LCAO/LibRI,
JSON/CNPY enabled; OpenMP, OpenBLAS and MKL each use one thread. The full binary
reports v3.11.0-beta10, and all completed logs identify a26541419.
SHA256: bc8e5ea105b776f4ad75ea02f574931715bb76ceb39e2e930f274f157bb5d894.

From each independent case tree, using the unchanged test script and its
130-line active case list:

export OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1
export PRTE_MCA_mapby=slot PRTE_MCA_bindto=none
bash ../integrate/Autotest.sh -a "$VALIDATION_BINARY" -n4 -o1 -j4 -f "$ACTIVE_CASE_LIST"

Both main invocations exit 1; their nonpassing results above are preserved.
The suites run concurrently, so their timers are not controlled speedup
measurements. Larger performance benchmarking is deferred while these
correctness/validation gaps are being characterized.

@Critsium-xy, could you re-review the green-head implementation fixes and this
more complete validation scope? I have kept the source head frozen and have
not resolved your review threads on your behalf. I am not presenting this
full substitution sweep as passed or requesting that its discrepancies be
waived; fixed-Hamiltonian/root-accuracy checks and shared NSCF budget semantics
are the concrete next investigation items.

Synchronize with deepmodeling/develop at 98c2e22 without rebasing. Preserve upstream DFT+U case renames and all four PPCG entries in CASES_CPU.txt. Accept removal of generated INPUT reference files; PPCG Input_Item metadata remains the source of truth.

Focused MPI build succeeds; six PPCG/PW/parameter CTests pass, including np2/3/6 and two-pool regression. Explicit LAPACK comparison (200 10 5 4 4) passes. No PPCG numerical implementation, references, or tolerances changed by conflict resolution. Does not include the two unpublished local solver follow-up commits.
@cheerly-pku

Copy link
Copy Markdown
Author

Conflict resolution: b5d5fdb36 (2026-10-14)

Merged deepmodeling/develop at 98c2e2257 into the published PPCG head
a26541419, without rebasing or force-pushing.

  • Retained upstream's scf_dftu_spin2/4 to relax_dftu_spin2/4 case renames
    and all four PPCG cases (817–820). The active CPU list contains 130 unique
    entries; their INPUT paths exist. No references or tolerances were changed.
  • Accepted upstream Docs: Generate input reference in CI #7976's removal of the generated docs/parameters.yaml
    and docs/advanced/input_files/input-main.md. The C++ Input_Item
    registrations remain authoritative. A freshly built program exports valid
    YAML and Markdown; all five PPCG-related parameter records exactly match
    the previous PR's generated YAML.
  • Conflict resolution does not change PPCG's numerical implementation. The
    previously unpublished local follow-up commits b24bf4689 and ad39c4070
    are not included. The local thesis/report and figures are not included.

Fresh verification on the merge commit used GNU 12.3, OpenMPI 4.1.2,
OpenBLAS, Release, MPI/OpenMP/ELPA enabled, and LCAO/LibXC/CUDA/JSON/CNPY
disabled. Runtime and math-library threads were fixed to one.

export OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1
cmake --build "$BUILD" --parallel 6 --target \
  MODULE_HSOLVER_ppcg MODULE_HSOLVER_ppcg_float MODULE_HSOLVER_pw \
  MODULE_HSOLVER_diago_params MODULE_HSOLVER_ppcg_parallel \
  MODULE_HSOLVER_compare MODULE_IO_input_test_para \
  MODULE_IO_read_input_serial MODULE_IO_read_item_serial MODULE_IO_input_help_test
cmake --build "$BUILD" --parallel 6 --target abacus_pw_para
ctest --test-dir "$BUILD" -V \
  -R '^(MODULE_HSOLVER_(ppcg|ppcg_float|pw|diago_params|ppcg_parallel|ppcg_parallel_test)|MODULE_IO_(input_test_para(_4)?|read_input_serial|read_item_serial|input_help_test))$'
"$BUILD/source/source_hsolver/test/MODULE_HSOLVER_compare" 200 10 5 4 4
"$BUILD/abacus_pw_para" --version
"$BUILD/abacus_pw_para" -h pw_diag_rr_step
"$BUILD/abacus_pw_para" --generate-parameters-yaml > "$DOC_CHECK/parameters.yaml"
/usr/bin/python3 docs/generate_input_main.py "$DOC_CHECK/parameters.yaml" \
  --output "$DOC_CHECK/input-main.md"
# From an independent copy of tests/01_PW/817_PW_PPCG:
"$BUILD/abacus_pw_para" --check-input
python3 tools/03_code_analysis/agent_governance_check.py \
  --base 98c2e2257 --head HEAD --format text
git diff --check 98c2e2257...HEAD

Here BUILD=/tmp/ppcg-pr8042-conflicts.WoW5Nd/build and
DOC_CHECK=/tmp/ppcg-pr8042-conflicts.WoW5Nd/generated-docs are isolated
local directories; no generated documentation was committed.

Results: main binary and all ten test targets build; 11/11 CTests pass.
These include PPCG double 48/48, float 4/4, HSolverPW 6/6, DiagoParams 2/2,
MPI ranks 2/3/6 and a four-rank two-pool test, and input/help regressions.
The default-disabled large benchmark was not enabled. The explicit comparison
exits 0 (PPCG maximum eigenvalue error 2.87e-13). All CLI/documentation
checks pass; --check-input identifies commit b5d5fdb36. The case-list
audit and PR whitespace check pass. Governance exits 0 with the same seven
header/global review warnings: the existing global-reference budget is
non-increasing (+2/-20), and the header owns the types requiring its includes.

Initial setup attempts used ELPA=OFF (the fixed genelpa input test requires
ELPA) and the nonexistent target name abacus. Those attempts failed and
were retained in local logs; the final results above use ELPA=ON and the actual
abacus_pw_para target, without modifying tests. MPI-linked CLI/tests also
emit local hwloc/display environment messages; these logs were retained.

The full 130-case runtime suite, LCAO, CUDA, and no-MPI configurations were
not rerun for this conflict-resolution update. This does not change the
previously reported broader substitution-test limitations. CI for this new
head must complete separately; local passes are not a claim that remote CI
has already passed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Diago Issues related to diagonalizaiton methods Features Needed The features are indeed needed, and developers should have sophisticated knowledge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants