Add ELPA backend for FullDiag: Solver keyword, distributed Hamiltonian generation, state-parallel observables (ExpecMode 1) and trace kernels (ExpecMode 2)#276
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Four review rounds (Codex: technical, Antigravity: user/workflow) driven to zero must-fix findings, cross-checked against the official ELPA manual (2026.02.001). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
The non-_MAGMA fallback in lapack_diag.c printed "Warning: MAGMA is not used in this calculation." at diagonalization time; ResolveSolver() now prints the same warning at input-parse time, so the legacy NGPU>0 path emitted it twice. Keep the single parse-time warning. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Wires cmake/FindELPA.cmake (pkg-config + ELPA_ROOT search, rejects the threaded elpa_openmp variant, detects elpa_setup_gpu) into the root CMakeLists.txt and src/CMakeLists.txt, gated by a new USE_ELPA option. USE_ELPA=ON force-enables USE_SCALAPACK and defines _ELPA / _ELPA_GPU. Adds config/elpa.cmake as a usage example. Verified: default build still configures/builds, and USE_ELPA=ON fails at configure time with a clear "ELPA was not found" message since no ELPA is installed here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Move the USE_ELPA ScaLAPACK-force logic before the LAPACK block so that block emits exactly one of -D_lapack / -D_SCALAPACK. Previously the LAPACK block ran first with USE_SCALAPACK still OFF (adding -D_lapack) and the later ELPA block added -D_SCALAPACK, leaving both defined on systems with LAPACK. Also detect an explicit -DUSE_SCALAPACK=OFF and FATAL_ERROR rather than silently overriding, and reject threaded elpa_openmp on the manual ELPA_LIBRARY override path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Use MPI_MIN instead of MPI_MAX so any rank reporting -1 makes all ranks see the failure. With the 0/-1 encoding, MPI_MAX masked failures on a subset of ranks (MPI_MAX(0,-1)=0), silently returning success with a broken handle. MPI_MIN(0,-1)=-1 propagates correctly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
GetEigenVectorBlock() gathers one distributed eigenvector column of Z to rank 0 with a single pzgemr2d_ block transfer, replacing the element-wise pzelget_ loop of GetEigenVector. The destination 1x1 BLACS grid is lazily created and cached (InitEigenVectorGatherContext) and released via FreeEigenVectorGatherContext() after the phys loop. New pzgemr2d_/blacs_gridmap_ extern declarations match the existing file convention: long int for matrix dims/indices, int for BLACS context/control arguments (verified by cross-referencing every existing call site and by a syntax-only compile with _SCALAPACK defined, which produced zero type-mismatch warnings). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Replace the iNGPU-based branch in lapack_diag.c with a Solver-keyword switch (SCALAPACK/MAGMA/ELPA/LAPACK), add the ELPA helper that fills a 2D block-cyclic matrix from the replicated Ham and calls diag_elpa_cmp, and switch phys.c's eigenvector recovery to GetEigenVectorBlock on the ELPA path (freeing the gather context afterward). Adds the fulldiag_elpa_hubbard_chain integration test, registered only for USE_ELPA builds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
…-OFF regression for feature/elpa-fulldiag
Whole-branch review follow-up:
- ResolveSolver() now derives iFlgScaLAPACK from iSolver (Solver 1/3) after
legacy-conflict normalization, so the pre-existing HPhiMain.c/check.c/
CheckMPI.c multi-process FullDiag gates key on Solver 1/3 correctly
instead of aborting or defaulting to site-separated Hilbert space.
- fulldiag_elpa_hubbard_chain.sh now launches with ${MPIRUN} (matching what
run_with_mpi_precheck.sh validates) instead of the always-empty
${MPIRUNFC}, so the min:2 precheck passing actually guarantees a
multi-rank run.
- readdef.c rejects FullDiag CalcSpec runs with Solver 3 (ELPA, any nproc)
or Solver 1 (ScaLAPACK, nproc>1) at startup: CalcSpectrumByFullDiag()
reads L_vec, which those paths never fill (eigenvectors stay distributed
in Z_vec).
- CMakeLists.txt distinguishes a command-line -DUSE_SCALAPACK=OFF from a
cached OFF left over from a prior configure (via the cache entry's
HELPSTRING), fixing a false "explicit OFF" FATAL_ERROR when re-running
cmake -DUSE_ELPA=ON in an existing build dir.
- elpa_eigen_check wrapped in the run_with_mpi_precheck.sh/SKIP_RETURN_CODE
77 convention; removed unused rank variable in lapack_diag_elpa(); fixed
NGPU default docs (ja/en) to reflect the Solver-dependent default; and
strengthened fulldiag_solver_keyword.sh so a wrongly-successful Solver 3
run on a non-ELPA build fails the test outright.
Verified: build_noMPI ctest -R fulldiag 16/16 (and full suite) green,
default build builds cleanly, ctest -N has no elpa tests in the default
build, and all four CMake explicit-OFF scenarios from the review produce
the expected FATAL_ERROR/auto-enable behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
…che marker Re-review found a regression in the HELPSTRING-based explicit-OFF detection: option() rewrites USE_SCALAPACK's HELPSTRING to its own docstring after the first configure, so run 1 `cmake -DUSE_SCALAPACK=OFF` followed by run 2 `cmake -DUSE_ELPA=ON` in the same build dir lost the command-line marker and silently auto-enabled ScaLAPACK over the user's explicit OFF instead of erroring. Record the command-line origin in a sticky INTERNAL cache entry (HPHI_USE_SCALAPACK_EXPLICIT_OFF) the first time it is observed, clear it when the user passes USE_SCALAPACK=ON, and test that marker in the USE_ELPA conflict check. Verified all six review scenarios: fresh USE_ELPA=ON auto-enables then fails on ELPA-not-found; fresh combined explicit OFF errors; OFF-then-ELPA across two configures now errors (regression case); explicit re-enable clears the marker; cached default OFF still does not false-positive; plain default configure+build clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
…AGMA NGPU=0 guard - test/fulldiag_solver_keyword.sh + test/CMakeLists.txt: cases (3)/(3b) asserted Solver 3 (ELPA) and Solver 1 (ScaLAPACK) always fail, which is wrong on USE_ELPA/USE_SCALAPACK builds where those solvers are valid. Gate the assertions on new HPHI_HAS_ELPA/HPHI_HAS_SCALAPACK test environment flags (set from USE_ELPA/USE_SCALAPACK) so capability builds assert success instead, while capability-less builds keep the original must-fail checks unchanged. - CMakeLists.txt: USE_ELPA=ON with ENABLE_MPI=OFF previously configured a broken build (ELPA/matrixlapack_elpa.c are MPI-only). Fail fast with a clear message instead. - src/readdef.c (ResolveSolver): explicit Solver 2 (MAGMA) with explicit NGPU 0 passed validation and only failed later at runtime inside diag_magma_cmp. Reject it at startup, reusing the existing cErrCUDA message. Only reachable on _MAGMA builds; untestable on this machine. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Add ELPA (and missing ScaLAPACK/MAGMA entries in en) to the prerequisite and installation guides (ja/en), point README at the optional backends, and make FindELPA's elpa_setup_gpu check link against MPI and LAPACK so a static ELPA library does not falsely report the GPU API as missing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Two defects found during on-hardware validation (clavius, ELPA 2025.06.001): 1. ELPA rejects grids where a process row/column owns no block (ELPA_ERROR_SETUP, 'blacsgrid is not ok'). Cap the block size via ElpaBlockSize() so ceil(N/nblk) >= max(nprow, npcol), and error out clearly when N < max(nprow, npcol). diag_elpa_cmp now takes nblk as a parameter so descriptors and ELPA always agree. 2. ELPA's 2stage solver returned deterministically inaccurate eigenvectors (residual ~1e-6) with capped block sizes (nblk=24 on a 4x2 grid). Fall back to 1stage whenever nblk was capped; capping only occurs for matrices small relative to the grid, where 1stage is fast enough. Verified by elpa_eigen_check at np=1..16. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
…age) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Update the manual checklist to the validated 8-site (N=4900) smoke case (12/14-site chains are impractical for dense FullDiag), and add a ja/en note that Solver 3 requires the Hilbert-space dimension to be at least the largest process-grid dimension. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Solver 3 (ELPA) with nproc>1 will generate the Hamiltonian as a distributed block-cyclic panel in a later phase, which is incompatible with OutputHam/InputHam (they require the full replicated matrix). Reject this combination at startup with a clear error message instead of failing later or silently reading garbage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Convert every Ham[][] write in makeHam.c, nbody_interall.c, and anomalous_pair.c to AddHamElem/HAM_OWNED_COL (hamstore.h), and restrict the enclosing state loops to the owned column range (hs_jb/hs_je) so distributed-panel generation costs O(N^2/P). The diagonal loop keeps its full range (v0/v1 side effects) and only guards the Ham write. In replicated mode (iHamPanelActive == 0) all restricted bounds evaluate to the original [1, i_max], so the replicated path stays bit-identical (verified: 16/16 fulldiag + 128/128 broader suite). Also harden HAM_OWNED_COL into a static inline helper to guarantee single evaluation of its argument, and fix a genuine evaluation-order hazard at one write site (makeHam.c, SpinGC transverse field) where an out-parameter was read for the row index and written by the same RHS call in one statement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
tmisawa
left a comment
There was a problem hiding this comment.
Thank you for the extensive implementation and validation work. I reviewed the current head 47e6564d, including the Phase-3c additions. The CI checks are green, and the local no-MPI, ScaLAPACK, sanitizer, and current-develop merge tests passed. However, I cannot approve the current head because Phase 3c has one correctness issue and two important documentation/scope issues.
-
The energy trace kernel incorrectly admits SpinlessFermion(GC).
CalcByFullDiag()callsmakeHam()for every non-InputHammodel. Although themakeHam()model switch has no SpinlessFermion branch, it also has no rejectingdefault, so the call returns after the common diagonal processing.TraceBuildPlan()nevertheless assumes that SpinlessFermion(GC) cannot reach this point and enables the energy kernel for every non-InputHammodel.trace_model_n_diag()then maps an unlisted model ton_diag == 0, andTraceEnergyEvalState()treats that as canonical Spin, assigningnum = NsiteMPIandSz = Total2SzMPI / 2.This differs from the existing evaluator: canonical SpinlessFermion requires
num = NeMPI,Sz = 0, while SpinlessFermionGC computes the particle number from the wavefunction.I also confirmed with a ScaLAPACK np=2, L=4, N=2 SpinlessFermion input that the observable phase is reached and reports:
INFO: ExpecMode 2: the energy/fluctuation family uses the trace kernel.Please add an explicit positive supported-model predicate and demote SpinlessFermion(GC) to the Mode-1 path, or implement the correct canonical/GC coefficients. Please add corresponding Mode-0/1/2 regression tests and correct the Phase-3c design statement that these models are unreachable.
-
The documented CSR memory guidance contradicts the implementation.
In trace-collect mode,
HS_JB()/HS_JE()enumerate the full column range on every MPI rank. The Phase-3c design also states that the CSR is replicated per rank.However, the English and Japanese CalcMod documentation says that increasing the MPI rank count makes each rank's CSR smaller. That is not true for this implementation: increasing the rank count reduces the state-panel ownership, but not the per-rank energy CSR or its memory-gate result.
Please correct both user manuals and the
TraceHamCollect()API comment to state that the full CSR is replicated on every rank, and remove the suggestion that adding ranks reduces this buffer. -
The PR description has not been updated for Phase 3c.
The current description still says that the scope ends at Phase 3b, describes ExpecMode 2 as not yet implemented, and says that the energy family including
varalways uses Mode 1. These statements contradict both each other and the current head.Please update the PR description to define the actual Phase-3c scope, implementation, fallback behavior, tests, and benchmark, and remove the obsolete Phase-3b statements.
Because Phase 3c was added after the earlier final review, my preference is to split it into a follow-up PR so that the Phase-1--3b scope can remain fixed. If Phase 3c remains in this PR, the three points above need to be resolved before re-review.
Separately, the Intel MPI build issue in #291 is inherited from develop, not introduced by this PR. The intended Intel/ELPA environment should be revalidated after that issue is fixed.
…ionale The Task-7 troubleshooting note wrongly implied that adding MPI ranks shrinks the per-rank CSR Hamiltonian. It does not: the state-panel layout gives each rank complete eigenvectors, so computing y=H*x needs the full H locally and the CSR is built for the full column range on every rank (a distributed CSR would reintroduce the per-state collectives ExpecMode 1/2 remove). Fix the CalcMod troubleshooting sentence (ja/en), add a 'Why the CSR is replicated on every rank' paragraph to the parallel-FullDiag appendix (ja/en) explaining the design and why it is acceptable (nnz=O(N), ~32 MB at N=63504, negligible beside the O(N^2/P) eigenvector panel), and record the rationale + the correction in the phase-3c migration note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
The rationale paragraphs said the per-rank CSR buffer is ~24*N bytes; it is ~24*nnz bytes, and nnz is not O(N) with a fixed factor but ~T*N where T (terms per column) grows slowly with size -- the 32 MB example at N=63504 comes from nnz~21*N. Also qualify the panel comparison: the CSR is P-independent while the O(N^2/P) panel shrinks with P, so the CSR's fixed per-rank cost becomes relatively more significant as P grows and can dominate at large P, not only for operator-dense inputs. Appendix (ja/en) and migration note corrected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
… nnz/P Codex round-2: the buffer capacity and the memory gate are sized on the raw entries makeHam emits, not the post-merge count (rowptr[N], which can be smaller after summing duplicates) -- state that consistently in CalcMod (ja/en), the appendix (ja/en), and the migration note. Also correct 'nnz/P rows' to 'N/P rows (about nnz/P nonzeros)' for a row-distributed matrix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
… Phase-2 review) A reader skimming only the appendix Guidelines could scale up the process count for the O(N^2/P) memory win without realizing ExpecMode 2's energy CSR is P-independent. Add a one-line caveat + back-reference in the observable-guideline bullet (ja/en). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
|
@tmisawa Thanks — you are right, and confirmed against the code: in This is by design, and I've documented the rationale rather than change the layout:
Also: an earlier troubleshooting note of mine wrongly implied adding MPI ranks shrinks the CSR — that was incorrect and is now fixed. Documented across CalcMod (ja/en), the parallel-FullDiag appendix (ja/en, a new 'Why the CSR Hamiltonian is replicated on every rank' paragraph + a Guidelines caveat), and the phase-3c migration note (commits f0b7721..87c5d3d). The wording went through the ai-review-cycle: Codex verified the memory claims against the implementation (corrected 24·N→24·nnz, nnz≈T·N, raw-vs-merged entry counts, N/P-vs-nnz/P) to convergence, plus a docs-clarity/parity pass. A genuinely distributed CSR (per-rank memory scaling as O(nnz/P)) is possible but would need a distributed SpMV with communication and is a larger, separate change — happy to open a follow-up issue for it if you think the large-P memory floor is worth pursuing. |
|
Thank you. I confirm that the main replicated-CSR documentation issue has been addressed. To avoid the remaining blockers being lost among documentation details, I want to state the two required fixes explicitly. Required before re-review1. SpinlessFermion(GC) correctness bug — primary blockerThis was item 1 of my previous review and remains completely unaddressed at the current head The root cause is that This produces the wrong fluctuation semantics:
I reproduced the current head with ScaLAPACK, np=2, and an accepted canonical SpinlessFermion input. It reports:
For this PR, the minimal and safest fix is to introduce an explicit positive supported-model predicate and demote SpinlessFermion, SpinlessFermionGC, and unknown models to the Mode-1 path. Please add canonical and GC Mode-0/1/2 regression tests. Full Spinless trace-kernel support can be implemented separately if desired. 2. The np=3 CI failure comes from a degenerate-subspace-unsafe test oracleThe push-side The failing comparison reruns diagonalization for each The reported failure is exactly of this form: states with the same energy have different This is not evidence of a Phase-3c numerical-kernel error. The failing case 7 uses Please do not resolve this by rerunning CI. The test should instead do one of the following:
The other fixtures used by this np=3 test should also be checked for degeneracy. Secondary documentation cleanupAfter the two blockers above, please also align the remaining documentation and comments with the implementation: the |
TraceBuildPlan() enabled the energy/fluctuation trace kernel for every non-InputHam model. SpinlessFermion (CalcModel 7) and SpinlessFermionGC (8) map (via trace_model_n_diag's bare default:) to n_diag==0, the same as canonical Spin, so the kernel activated and wrote canonical-Spin CONSTANT fluctuation fields (num=NsiteMPI, Sz=0.5*Total2SzMPI, doublon=0) -- wrong for spinless models. n_diag cannot distinguish supported from unsupported (both Spin and spinless/unknown return 0), so a separate positive predicate is added. - Add TraceModelEnergySupported() (positive whitelist of the 11 models the energy kernel implements: Hubbard/Kondo/tJ family + N-conserved, SpinGC, canonical Spin). Declared in expec_trace_ham.h; defined in expec_trace.c next to TraceBuildPlan so the minimal expec_trace_map_check test resolves it without pulling in expec_trace_ham.c's makeHam/expec_energy_flct closure. - TraceExecutionPlan gains demoted_unsupported_model[]; TraceBuildPlan() demotes unsupported models (after the InputHam check); TraceReportPlan() prints the new verbatim "(unsupported model)" INFO line. - Unit test: predicate (11 supported / spinless+unknown rejected) plus a plan build + INFO-report check (Hubbard keeps the kernel; spinless is demoted and does NOT print the trace-kernel line -- non-vacuousness). - Regression: two new expec-equivalence cases (canonical SpinlessFermion and SpinlessFermionGC, expert-mode def sets) asserting the demotion INFO line and Mode 0/1/2 equivalence. output.c: add the missing SpinlessFermion/SpinlessFermionGC FullDiag phys-output filename cases (formerly left sdt uninitialized -> the whole run failed on a garbage filename); turn the silent default into a loud error. This is the prerequisite that lets spinless FullDiag complete at all. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
The test runs each ExpecMode (0/1/2) as a separate HPhi FullDiag launch and compared per-state observables by eigenstate index. For a degenerate eigenvalue the eigensolver may return any orthonormal rotation within the degenerate subspace: energies match but basis-dependent per-state quantities (S2, doublon, per-eigen Green functions) need not. A single deterministic ELPA run reproduces the same basis across launches (cases 1-6 pass), but ScaLAPACK's degenerate-subspace basis is not reproducible launch-to-launch -- case 7 (Solver 1) fails at np=3 with S2 differences up to ~0.70, and the spinless cases 8/9 run Solver 1 under the fulldiag_expecmode_scalapack_np2 registration. Fix (test-only): - compare_phys() now groups zvo_phys rows into degenerate energy levels (etol 1e-6, grouping re-derived and verified identical in both files) and compares the per-level SUM of each column -- Tr(P.A.P), which is basis-invariant -- instead of per-state values. Size-1 levels reduce to the exact per-state check. Tolerance scaled to tol*max(1,level_size). Existing non-vacuousness guards kept. - New compare_output_presence(): the degeneracy-safe subset of compare_output_trees (file-set equality + _eigen.dat aggregate presence, no per-state value diff). - Cases 8/9 switch their observable comparison from compare_output_trees to compare_output_presence + degeneracy-aware compare_phys; the demotion INFO-line assertions are unchanged. - Case 7 already used compare_phys, so inherits the fix. Cases 1-6 (deterministic ELPA) untouched. No source or INFO-line change. Self-checked the awk standalone: non-degenerate identical PASS, degenerate-with-equal-sums PASS, divergent-sum FAIL, spectral-boundary mismatch FAIL, O(1) per-state N regression FAIL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
…fallback reasons Doc/comment cleanup for the PR review (code BLOCKERS already fixed in 9d92e89/a466466c). Two facts propagated everywhere: - Energy/fluctuation trace kernel supports an explicit whitelist only (Hubbard/HubbardGC, tJ/tJGC, Kondo/KondoGC, Spin/SpinGC incl. general spin); SpinlessFermion(GC) and unknown models fall back to ExpecMode-1. - The energy family now has THREE ordered fallback reasons: InputHam, unsupported model, memory gate; new byte-exact INFO line for the unsupported-model case. Changes: - expec_trace_ham.h: TraceHamCollect comment now says the CSR is built for the FULL column range on every rank (replicated), not "this rank's range". - Rename finalize API param nnz_raw -> nnz_merged (it IS ham_csr.nnz = rowptr[n], the merged count) in expec_trace.h, expec_trace_finalize.c, phys_distributed.c; behavior-preserving. Left the genuinely-raw pass-1 nnz_raw in TraceHamGatedBytes/expec_trace_ham.c as-is. - CalcMod en/ja + parallel_fulldiag en/ja + migration note: whitelist coverage, three fallback reasons, fourth INFO catalogue line, and a clarification that HPHI_TRACE_BUF_MAX_MB is a per-quantity threshold (not a budget on the sum of concurrently-live trace buffers). Full suite green (143 tests, 0 failed); no new Sphinx warnings from the touched files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
compare_phys() (must-fix): restore two guards weakened by the degeneracy-aware rewrite while keeping the validated per-level basis-invariant SUM comparison. (a) Header/any non-data line is now compared byte-exactly (awk-extract + diff) so a changed header fails. (b) Before summing a data field, BOTH paired values must match the strict numeric regex; a nonnumeric field (nan/inf/bad) where a number is expected is a hard fail, never coerced to 0. expec_trace.h: update the stale demoted_input_ham comment -- document both static energy demotions (InputHam, unsupported-model) and the check order (InputHam, then unsupported-model, then finalize-time memory gate). parallel_fulldiag (en/ja): rephrase the energy family's fallback reasons -- three overall (InputHam, unsupported model, memory gate); for a supported model only InputHam and the memory gate can still apply (the unsupported-model reason is what excludes a model from support). CalcMod (en/ja), parallel_fulldiag (en/ja), migration note: note the supported Hubbard/tJ/Kondo families include their N-conserved canonical variants (HubbardNConserved/tJNConserved/KondoNConserved). Tests: tighten the energy-family INFO greps (shell + unit Part 6) to match the full "INFO: ExpecMode 2: ...." line via grep -F / prefixed substring; strings unchanged (byte-frozen, shared with docs). Verified: bash -n clean; compare_phys 5-scenario self-check OK; sphinx en+ja 0 new warnings from touched files; ctest build_noMPI 100% (143). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
… precision The per-level basis-invariant SUM was compared at 1e-8*level_size, but zvo_phys is written with %10lf (6 decimals): each value carries ~5e-7 rounding, so a level-size sum compared between two independent ScaLAPACK diagonalizations can differ by ~level_size*1e-6. That caused an intermittent np=3 failure (sumA=4 vs 3.999999). Use ptol=2e-6 per state (output-precision-aware); a real regression shifts a column by O(0.1-1)/state >> 2e-6*level_size, so detection is unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Round-2 Codex review fixes for the degeneracy-aware compare_phys(). MUST-FIX (nan/inf edge case): the header/data split classified a row as "header" whenever its first field failed the numeric regex, so a corrupted DATA row starting with nan/inf/text was misfiled as a header; if BOTH files shared that identical corrupted row it passed the byte-exact header diff and was then skipped by the numeric comparison, hiding a shared NaN/Inf in the energy column. Now classify by POSITION: line 1 is the header (compared byte-exactly via head -1), and ALL remaining rows (tail -n +2) are DATA -- every field on both sides must pass the strict numeric regex AND not be nan/inf (case-insensitive), a hard fail even if byte-identical in both files. The regex header heuristic is removed entirely. The per-level degeneracy-aware SUM comparison and the ptol=2e-6 tolerance are unchanged. SHOULD-FIX (grep tightening): the three energy-family INFO greps (assert_energy_kernel, assert_energy_and_gf_unsupported, case-7 InputHam) switch from grep -Fq (substring) to grep -Fxq (whole line), including the two leading spaces TraceReportPlan() emits (src/expec_trace.c). INFO string text unchanged. The GF/always-fallback substring greps are intentionally left as substrings. Verified: bash -n clean; compare_phys 8-scenario self-check OK (incl. shared nan/inf in identical rows now caught); ctest build_noMPI 100% (143/143). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
|
@tmisawa Thank you for the precise review — both blockers and all secondary items are addressed (pushed Blocker 1 — SpinlessFermion(GC) energy-kernel correctness (
|
|
Thank you for the fixes. I confirmed that the positive energy-kernel whitelist, unsupported-model demotion, INFO messages, and CSR documentation/API issues have been addressed. However, after reviewing and rerunning the current head 1. The degeneracy-safe oracle fix is incomplete
However, cases 1–6 still use The response says:
But the same script is also run with ScaLAPACK by setting HPHI_FULLDIAG_SOLVER=1 MPIRUN='mpiexec -n 3' \
../../test/fulldiag_expecmode_equiv.shAll six runs failed. The failures were mainly in case 2's For example, a fourfold-degenerate level at energy The current CMake registration includes ScaLAPACK
Please make all state-indexed comparisons degeneracy-safe, use non-degenerate fixtures, or reuse the same eigenvectors between modes. Please also register a ScaLAPACK 2. The new Spinless FullDiag cases successfully publish incorrect physicsThe new First, as already recorded in #293, This is the diagonal Second, the particle-number output is also wrong:
Consequently, canonical case 8 has The new tests compare Mode 0/1/2 only against each other. Since all three modes use the same broken Hamiltonian and the same incorrect output fields, they agree and the test passes. The test comment claiming that an incorrect dispatch would cause an O(1) If full Spinless FullDiag support remains outside this PR, the safest resolution is to reject SpinlessFermion(GC)+FullDiag explicitly and keep the unsupported-model demotion check at the plan/unit-test level. Alternatively, the FullDiag Hamiltonian construction and particle-number output must be fixed, followed by an absolute-value oracle for a small exactly known system. These two points remain blockers, so I cannot approve the current head yet. |
SpinlessFermion/SpinlessFermionGC + FullDiag currently publishes incorrect physics: makeHam has no spinless branch (the Trans hopping is silently dropped from the FullDiag Hamiltonian, issue #293) and the FullDiag phys output never populates the particle number for these models. Full support is out of scope for this PR, so reject the combination at startup instead of running the broken path. - readdef.c: new gate (iCalcType==FullDiag && SpinlessFermion(GC)) -> error + return -1, next to the existing FullDiag/ExpecMode gates; new cErrSpinlessFullDiag message in ErrorMessage.c/.h. - test/fulldiag_expecmode_equiv.sh: remove the broken canonical spinless cases 8/9 and their now-orphaned helpers; cases 1-7 unchanged. - test/fulldiag_solver_keyword.sh: add a startup-rejection case asserting HPhi aborts for CalcModel 7 and 8 + FullDiag with the new message (np=1). - test/nbody_spinless_validation.sh: the bad_fulldiag case is now caught by the broader startup gate first; update its expected-failure pattern accordingly. Full build_noMPI ctest suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
…np=3 CI compare_output_trees() compared the per-eigenstate aggregate Green files (*_eigen.dat) row-by-row by eigenstate index. Under a degenerate eigenvalue, ScaLAPACK (Solver 1) returns an arbitrary orthonormal rotation within the degenerate subspace across separate HPhi launches, so per-state basis-dependent values (S2, per-eigenstate Green functions) differ state-by-state while the physics is identical -- causing intermittent failures at ScaLAPACK np=3 that CI never exercised (only np=2 was registered). Part 1 (test/fulldiag_expecmode_equiv.sh): route every *_eigen.dat file through a new degeneracy-aware comparison. build_eigen_level_map() derives the eigenstate-index -> degenerate-level map from Eigenvalue.dat (energies grouped within etol=1e-6; the two dirs' spectra must agree per index or fail). compare_eigen_degenerate() classifies each row's columns by regex (leading eigenstate index; integer operator-key columns; %.10lf value columns), accumulates the per-(operator,level) SUM of each value column for A and B, and requires identical key sets and each summed value within ptol*level_size (ptol=2e-6; size-1 levels reduce to the exact per-state check). The per-level sum is Tr(P.A.P), basis-invariant. Value fields are hard-validated non-nan/inf. Non-*_eigen.dat files (Eigenvalue.dat, CHECK_*, ...) keep the exact per-line diff. compare_phys (case 7) is unchanged; no source code touched. Part 2 (test/CMakeLists.txt): register fulldiag_expecmode_scalapack_np3 (exact:3, HPHI_FULLDIAG_SOLVER=1, SKIP_RETURN_CODE 77) beside the np2 case, so CI covers ELPA np2/np3 AND ScaLAPACK np2/np3. Verified: 6-scenario standalone self-check of the comparison (identical, degenerate-rotation-equal, sum mismatch, missing key, nan, spectral mismatch) all correct; bash -n/sh -n clean; ctest --test-dir build_noMPI 100% (143/143). ScaLAPACK np=3 repeatability to be validated on clavius. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Addresses 5 robustness gaps in the degeneracy-safe FullDiag ExpecMode
comparison. The per-level SUM math and ptol=2e-6 are unchanged (validated).
test/fulldiag_expecmode_equiv.sh:
- Route zvo_phys*.dat through the degeneracy-aware compare_phys() (grouped
per-level sums) instead of the exact 1e-8 per-line loop: zvo_phys has
basis-dependent columns (S2, doublon) that flake under degenerate ScaLAPACK
rotations exactly like the *_eigen files. compare_output_trees() calls
compare_phys once for the pair when a zvo_phys*.dat exists and skips those
files in the exact loop (still counted in the file-set equality check).
- build_eigen_level_map(): derive degenerate-level boundaries INDEPENDENTLY
from A and from B and fail if the level count or any per-index level
assignment differs -- a near-threshold gap (0.9e-6 in A vs 1.1e-6 in B) could
group differently while every per-index energy check still passes.
- build_eigen_level_map(): hard-validate each Eigenvalue.dat data row on both
sides -- exactly 2 fields, integer index, finite numeric energy (reject
nan/inf/non-numeric even if identical), non-decreasing energy.
- compare_eigen_degenerate(): also compare, per (operator,level) and per side,
the row COUNT and value-column count; a dropped/added row (even zero-valued or
sum-compensated) now fails where the value SUM alone would not notice.
test/fulldiag_solver_keyword.sh:
- Tighten the spinless-FullDiag rejection assert to grep the model-identifying
part of cErrSpinlessFullDiag ("SpinlessFermion / SpinlessFermionGC are not
currently supported for FullDiag") rather than the bare unsupported phrase.
Verified: 10-scenario standalone self-check (a-j) all correct, incl. dropped-row
multiplicity, near-1e-6 boundary split, malformed/nan Eigenvalue.dat, and a
basis-rotated-equal vs genuine-regression zvo_phys pair via compare_phys;
bash -n clean; ctest --test-dir build_noMPI 100% (143/143). clavius ScaLAPACK
np=3 x20 + ELPA np2/np3 re-run owned by the coordinator.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
Four robustness fixes in test/fulldiag_expecmode_equiv.sh. compare_phys is
unchanged.
- Tolerance for *_eigen.dat was too loose. The aggregate Green files are written
with %.10lf (10 decimals; GREEN_*_ROW_FORMAT, write_nbodyg_line), not the
6-decimal %10lf that zvo_phys uses, so ptol=2e-6 was a ~200x over-relaxation
that could hide a genuine Green-function regression. compare_eigen_degenerate()
now uses etoleig = ${tol} = 1e-8 scaled by level size (the same 1e-8*level_size
the old per-line loop used), which stays far below a physically-significant GF
perturbation (~1e-6). ptol=2e-6 remains only for zvo_phys via compare_phys.
- Row multiplicity was tracked only per (operator,level). A row dropped for one
eigenstate and duplicated for another in the SAME level preserves that count
and can preserve the sum. compare_eigen_degenerate() now also requires every
(operator-key, eigenstate-index) row to appear exactly once per side (and
matching value-column count). Numeric comparison stays per-level sums only.
- build_eigen_level_map() now rejects any strict energy decrease (e[k] < e[k-1])
on both sides; etol is used only for degeneracy grouping.
- build_eigen_level_map() now validates, independently per side, that eigenstate
indices are the contiguous 0-based sequence 0..n-1 before emitting the map
(a duplicate/skipped/decreasing index shared by both files would otherwise pass
the A==B check yet corrupt lev[]/levsize[]).
Verified: 14-scenario standalone self-check (a-n) all correct, incl. (k) a 1e-6
non-degenerate perturbation rejected at tol 1e-8, (l) drop-one/duplicate-another
within a level, (m) sub-etol energy decrease, (n) duplicated/skipped index;
bash -n clean; ctest --test-dir build_noMPI 100% (143/143). Coordinator will
re-validate ScaLAPACK np=3 x20 with the tightened tolerance.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
|
@tmisawa You were right on both counts — my previous fixes were incomplete. Both are now fixed and validated on clavius on the exact configurations you reproduced (pushed 1. Degeneracy-safe oracle — now covers all state-indexed comparisons, and ScaLAPACK np=3 is in CIMy first fix only made
2. SpinlessFermion(GC) FullDiag — no longer publishes incorrect physicsPer your endorsed resolution, since full spinless FullDiag support is out of scope (the The wording/logic went through the ai-review-cycle (Codex, 3 rounds to zero must-fix — it caught the too-loose tolerance and the per-eigenstate multiplicity gap, both now fixed). The PR description migration notes are updated to record the SpinlessFermion+FullDiag rejection. |
Audit fixes for the ExpecMode-2 test coverage (test + CI files only): - expec_trace_ham_check.c: parts 1-3 (dense-vs-CSR identity for 10 models, coefficient arrays, gate boundary + alloc injection, TraceEnergyEvalState streaming) were compiled out of every MPI build by #ifndef MPI, so the kernel's core correctness evidence never ran in CI or on clavius. The actual blocker was only that StartTimer/StopTimer (reached via expec_energy_flct) dereference the Timer/TimerStart globals that InitTimer() allocates in MPI builds. Call InitTimer() after MPI_Init and replace the compile-time guard with a runtime nproc==1 gate: parts 1-3 now run in noMPI builds AND in MPI builds invoked as a singleton (the ctest serial registration), and print an explicit skip note at nproc>=2 (the mpiexec finalize registration, where part 4 still runs). - Alloc-injection now covers ALL of TraceHamCollect's gallocs: extend fail_alloc_at from 0..5 to 0..5+n_diag (Hubbard fixture: 0..8), testing the previously uncovered diag[] coefficient-array allocations, with an assert that the fixture really reaches them (n_diag>0). - Strengthen the tautological kernel-sentinel assertion var >= 0 into a 1e-12 comparison of energy/var against the independent dense reference (dense_energy_var) over the same state. - CI: add fulldiag_expecmode_scalapack_np3 (the ScaLAPACK np=3 degeneracy regression) to the ELPA three-rank ctest regex; it is registered in that build (USE_ELPA=ON forces USE_SCALAPACK=ON) but never ran in CI. Verified: build_mpi serial run executes parts 1-3 (marker line + ALL PASS, ctest #122 Passed), np=2 finalize test skips parts 1-3 on both ranks and passes (#123), and the full noMPI suite is green (143/143, 1 MPI-only skip). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
…izing Finding 1: add three end-to-end ExpecMode 0/1/2 equivalence cases to test/fulldiag_expecmode_equiv.sh for the activated model families that had no distributed coverage: HubbardGC (case8, exercises trace_fill_diag gc==TRUE / EnergyFlctCoeff_HubbardGC, GF-kernel-supported), canonical Kondo (case9, energy-kernel-eligible but GF-unsupported), and general-spin SpinGC S=1 (case10, exercises EnergyFlctCoeff_GeneralSpinGC, GF-unsupported). KondoGC/ tJGC left to unit+helper coverage (noted in a comment). Also correct a stale "cases 8/9 spinless" comment. Finding 2: force LC_ALL=C in the oracle so awk float parsing is locale-safe. Finding 3: trace_fill_sink (src/expec_trace_ham.c) now O(1) bound-checks the irow range and the per-row cursor against the row's segment end, raising the existing g_overflow flag on a pass-1/pass-2 divergence instead of writing out of bounds; TraceHamCollect checks the flag after pass 2 and also verifies every cursor reached its expected end, demoting on either over- or under-fill. Allocation-free. Finding 4: TraceHamGatedBytes models the merge workspace as sizeof(TraceEnt) (matching the real allocation) instead of sizeof(long int)+sizeof(double complex), which undercounts on ABIs that pad TraceEnt. Finding 5: document in TraceModelEnergySupported (src/expec_trace.c) that the NConserved models rely on sz() remapping them to their base enum before output(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
…stall) Parts 1-3 hung on the 64-core validation host when the MPI-build binary was executed directly: the main thread sat in sz()'s GOMP team barrier with num_threads=64 while OpenBLAS parked 128 worker threads (gdb: main in gomp_team_barrier_wait_end inside calculate_jb_Hubbard_Hacker). The fixtures are <= 4 sites; one OpenMP thread is ample and keeps all OpenMP code paths exercised serially. Production HPhi (mpiexec-launched) is unaffected -- this is test-binary-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
…hould-fix) Keeps the CI OMP_NUM_THREADS=3 legs exercising parallel code paths while still defusing the 64-thread singleton stall. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
…arness limitation) The 4-thread cap crashed on the 64-core host (SIGSEGV in __libc_free during the repeated fixture pipeline) -- the ten-in-one-process fixture re-initialization is not thread-stable, a pattern production code never executes. Production OpenMP paths are covered at full thread counts by the mpiexec integration tests; this harness pins itself to one thread and records the >1-thread crash as a test-infrastructure follow-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H
tmisawa
left a comment
There was a problem hiding this comment.
Re-review result for head f915080e67db01bf8f02aebab78dd05f1f5a8371: Approve — technically ready to merge.
The two correctness blockers from my previous review are resolved:
- All state-indexed equivalence outputs, including Green-function and NBody files, are now compared with a degeneracy-safe, basis-invariant oracle. The ScaLAPACK
np=3variant is covered by CI; I also repeated thenp=3equivalence test with independent output directories and obtained 10/10 passes. SpinlessFermion/SpinlessFermionGCFullDiag now fail clearly at startup instead of completing with incorrect physics. Canonical and GC rejection paths are covered, while non-FullDiag spinless methods remain available.
Additional validation:
- GitHub Actions: 16/16 checks passed across the push and pull-request runs.
- no-MPI Debug CTest: 143/143 passed (one MPI-only test skipped as designed).
- ScaLAPACK Debug: targeted
np=2/np=3equivalence, trace-map/trace-Hamiltonian, MPI finalization, and partial-merge tests passed. - ASan+UBSan: 12/12 representative tests passed.
- The merge tree against the current
developis conflict-free.
I found no remaining must-fix issue in the reviewed scope. The open Intel MPI/Komega build follow-up (#291/#292) predates this PR and is not a PR #276 merge blocker, although Intel/ELPA operational validation should follow that fix before production use.
Summary
Adds an ELPA backend to FullDiag, enabling multi-node (and multi-GPU) full diagonalization, selected via a new
Solverkeyword incalcmod.def. This PR now contains phases 1, 2, 3a, 3b, and 3c of the design indocs/superpowers/specs/:Phase 1 — ELPA solver backend: the diagonalization step runs on ELPA (CPU or GPU, multi-node).
Phase 2 — distributed Hamiltonian generation: with
Solver 3and more than one MPI process, the Hamiltonian is generated and stored directly in distributed form, so peak memory per rank scales as O(N²/P) instead of O(N²) — larger Hilbert spaces become feasible by adding ranks.Phase 3a — state-task-parallel observables (
ExpecMode): after diagonalization, each MPI rank evaluates the FullDiag observables (energy, N, Sz, S², doublon, Green functions) for its own contiguous block of eigenstates independently, removing the per-state collective bottleneck. Benchmark (N=4900, one-body+two-body Green functions for all states, 4 ranks): the observable-evaluation section speeds up 4.04× (≈P); total wall time 48.7 s → 22.4 s.Phase 3b — trace kernels (
ExpecMode 2): per-operator basis mappings are extracted once (reusing the exact code paths of the existing element functions via private probe adapters) and all owned eigenstates are streamed densely, accelerating one-body and two-body Green functions on supported models. Benchmark (same N=4900, 4 ranks): total wall 64.2 s (ExpecMode 0) → 32.3 s (ExpecMode 1) → 26.0 s (ExpecMode 2).Phase 3c — energy-family trace kernel (
ExpecMode 2): the energy/fluctuation family (energy, N, Sz, doublon, and thevarcolumn) now also runs through a CSR sparsey = H·xkernel (the Hamiltonian is collected once per rank from the auditedmakeHamenumeration and every owned eigenstate is streamed through it), instead of the per-stateexpec_energy_flctfallback. It supports a broader model set than the Green-function kernels —Hubbard/HubbardGC(incl. the particle-number-conserved variant),tJ/tJGC,Kondo/KondoGC, andSpin/SpinGC(incl. general spin) — with three fallback reasons:InputHam, unsupported model (SpinlessFermion/SpinlessFermionGCand unknown models), and theHPHI_TRACE_BUF_MAX_MBmemory gate (which now also bounds the energy family's per-rank CSR buffer).S2,NBodyG, andAnomalousGremain on theExpecMode 1path.New user interface
Solverzheev(serial, default)pzheev-DUSE_SCALAPACK=ON-DUSE_ELPA=ONMigration notes
ScaLAPACKkeyword incalcmod.defis deprecated (still works, prints a warning): useSolver 1instead.Solverkeyword behave exactly as before (legacy resolution preserves current defaults, includingNGPU=2on MAGMA builds).NGPU 0runs on CPU andNGPU >= 1on GPU (ELPA assigns 1 rank per GPU round-robin). GPU execution requires ELPA >= 2023.11.001; CPU requires >= 2021.11. Only the non-threaded ELPA is supported.Solver 3requires the Hilbert-space dimension to be at least the largest process-grid dimension; smaller runs abort at startup with a message asking to reduce the rank count (phase 1, since 8732e24).Solver 3and nproc > 1,OutputHam/InputHamare rejected at startup (the Hamiltonian never exists in replicated form). Use 1 process or anotherSolverfor Hamiltonian I/O. All other inputs are unaffected — results are identical to the replicated path.ExpecMode(CalcMod, default0). Selects the evaluation kernel for FullDiag observables:0: conventional evaluation (existing behavior, unchanged).1: state-task-parallel evaluation — each MPI rank evaluates observables for its own contiguous block of eigenstates independently, with no communication during the per-state loop. Aggregate Green-function output is written as rank-local partial files and merged into the final aggregate files by rank 0 once every rank's manifest reports success.2: reserved for a trace-kernel evaluation mode (phase 3b). Not yet implemented; currently runs asExpecMode 1and prints anINFOline saying so.ExpecModerequiresCalcType= FullDiag withSolver1 (ScaLAPACK) or 3 (ELPA); any other combination is rejected at startup. With exactly one MPI process,ExpecModeis automatically reverted to0(anINFOline is printed) since results are identical for a single process either way.ExpecModechanges only evaluation speed —0,1, and2give identical physics up to floating-point rounding (summation order differs between kernels, so results are not bit-identical).ExpecMode 2is now real. Previously (phase 3a)ExpecMode 2printed an INFO line and ran asExpecMode 1. It now selects trace kernels for the one-body and two-body Green functions on supported models —Hubbard/HubbardGCand half-integerSpin/SpinGC(general-spin,tJ/tJGC,Kondo/KondoGC, and spinless models always fall back). Everything else — the energy family including thevarcolumn (computed exactly as before; now trace-kernelized in phase 3c, see below), S²,NBodyG,AnomalousG— always runs theExpecMode 1path in this version. Per-quantity runtime fallbacks are reported by rank-0INFOlines, evaluated in a fixed, mutually exclusive order: unsupported model / shared evaluator (the two-body GF falls back wheneverThreeBodyG/FourBodyG/SixBodyGare requested, since they share one evaluator — this also guarantees single-writer output) / no operators defined / memory gate. The memory gate is controlled by a new environment variableHPHI_TRACE_BUF_MAX_MB(integer MiB, default 1024): it is a per-quantity threshold (checked independently against each quantity's buffer, not a cap on the sum of concurrently-live buffers) that bounds (does not pre-allocate) each kernel quantity's per-rank result buffer (n_ops x ceil(n_states/P) x 16bytes); exceeding it demotes that quantity to theExpecMode 1path. The guarantee is unchanged:ExpecModeaffects speed only — 0/1/2 agree within floating-point rounding (not bit-identical). Purely additive; no keyword or file-format changes.ExpecMode 2. This supersedes the phase-3b statement above that the energy family "always runs theExpecMode 1path": as of phase 3c, for the supported models (Hubbard/HubbardGCincl. the N-conserved variant,tJ/tJGC,Kondo/KondoGC,Spin/SpinGCincl. general spin) the energy family runs a CSRy = H·xkernel.SpinlessFermion/SpinlessFermionGCand any unknown model fall back (a positive supported-model whitelist demotes them, with a verbatimINFO: ExpecMode 2: the energy/fluctuation family uses the ExpecMode-1 fallback (unsupported model).line);InputHamand the memory gate are the other two fallback reasons.varis unchanged (Phys.varstill stores ⟨H²⟩; downstream subtracts ⟨H⟩²). The per-rank CSR is replicated in full on every rank by design (the state-panel layout gives each rank complete eigenvectors, soy = H·xneeds the fullHlocally; distributing it would reintroduce the per-state collectivesExpecMode 1/2remove) — it is sparse (~24·nnz bytes, ~32 MB at N=63504) and bounded byHPHI_TRACE_BUF_MAX_MB. Purely additive; no keyword or file-format changes; the output is identical toExpecMode 0/1within floating-point rounding.SpinlessFermion/SpinlessFermionGC+ FullDiag is now rejected at startup. These models are not correctly supported for FullDiag in the current code —makeHam.chas no spinless branch, so the hopping (Trans) is silently dropped, and the FullDiag phys output printsnum_up + num_down, which spinless never populates (so<N>prints as 0). Rather than let such runs complete and publish incorrect physics,SpinlessFermion(GC)+CalcType=2(FullDiag) now aborts at startup with a clear message. Non-FullDiag spinless methods (Lanczos/TPQ/LOBCG) are unaffected. Full spinless FullDiag support is tracked as an upstream follow-up (the Hamiltonian construction and particle-number output must be fixed, then verified against a small exactly-known system).S2/Szare no longer zero-filled. Before this phase, FullDiag runs withSolver1 (ScaLAPACK) or 3 (ELPA) and more than one MPI process skipped the S²/Sz calculation entirely —S2andSzwere reported as0, and the stdout progress line used a shortened format that omitted theS2column. As of phase 3a, these distributed runs computeS2/Szon rank 0 (same as the serial path), and the stdout progress line always uses the single-process (serial) format, with theS2column present, for everySolver/ExpecModecombination. This is an intentional correctness fix, independent ofExpecMode's value — it also applies to the (unchanged, default)ExpecMode 0. Anyone diffing FullDiag stdout/zvo_phys*output against a pre-phase-3a distributed run should expectS2/Szto change from0to their correct values. Scripts that parse the stdout progress lines of distributed FullDiag runs by column index must be updated: inserting theS2column shifts every subsequent field (e.g. the doublon column) one position to the right relative to the old shortened format.ExpecModeis purely additive and defaults to the pre-existing behavior (0) except for theS2/Szfix above, which applies unconditionally to distributed FullDiag runs.Implementation
Phase 1 (solver backend)
src/matrixlapack_elpa.c:diag_elpa_cmp()following the ELPA C API contract (mandatory params beforeelpa_setup, tunables after,elpa_setup_gpu), with collective error synchronization (MPI_MINreduction) before every collective and no silent GPU→CPU fallback.GetEigenVectorBlock()(matrixscalapack.c): per-state eigenvector gather to rank 0 via a singlepzgemr2d_block transfer (rank-0-only destination grid), replacing element-wisepzelget_on the ELPA path.Solver/legacy keyword resolution and full startup validation inreaddef.c; the pre-existing multi-process FullDiag gates (HPhiMain.c/check.c/CheckMPI.c) are driven from the resolved solver.USE_ELPAoption +cmake/FindELPA.cmake(pkg-config, versioned paths, manual overrides, threaded-variant rejection, GPU-API detection).ElpaBlockSize) so every process row/column owns a block, and a 1stage-solver fallback for capped block sizes (both defects were found during on-hardware validation; see below).Phase 2 (distributed generation)
src/include/hamstore.h: storage abstraction (AddHamElem, owned-column iteration) that writes either the replicatedHamor a rank-local 1D column panel; all 29 Hamiltonian write sites inmakeHam.c/nbody_interall.c/anomalous_pair.crouted through it, with the state loops restricted to owned columns.src/xsetmem.c: in distributed mode allocates the O(N²/P) panel instead of the replicatedHam/L_vec(zero-owner ranks get a 1-element dummy with a valid descriptor so they can join the collectives).RedistPanelToBlockCyclic()(matrixscalapack.c): onepzgemr2d_redistribution from the generation panel to ELPA's 2D block-cyclic layout, with an ownership guard that survives Release (NDEBUG) builds and collective malloc/error synchronization.Phase 3a (state-parallel observables)
src/phys_distributed.c(MPI orchestration: state-panel allocation, redistribution, single rendezvousAllreduce,Gathervof the per-state observables, manifest merge) andsrc/phys_distributed_local.c(the MPI-free per-rank state loop — enforced by a CI guard script that scans it for raw MPI usage).RedistBlockCyclicToStatePanel()(matrixscalapack.c): mirror of the phase-2 redistribution (block-cyclic → 1D state-column panel), with synchronized ownership/descinit verdicts so no rank enterspzgemr2d_alone.SumMPI_*become pass-throughs andfopenMPIopens rank-local files inside the per-state loop; unreachable collectives assert; the raw-MPI_Sendrecvbranches innbody_correlation.c/anomalous_pair.cget defensive guards (sticky error, no abort). A frozen call inventory (docs/superpowers/specs/2026-07-11-expec-call-inventory.md) + guard ctest (check_expec_local_calls) keep this contract enforced.green_output.c): rank-local partial files with a per-(rank, kind) manifest ({attempted, opened, open_error, bytes, closed_ok, paths}); rank 0 validates the full lifecycle of every record, cross-checks on-disk sizes against the manifest, concatenates parts in rank order into<final>.tmp_merge, andrename()s all kinds only after every one succeeded; the verdict is broadcast so every rank returns the same rc; parts are deleted only on global success.src/phys.c(see migration notes).Phase 3b (trace kernels)
src/expec_trace.c(MPI-free, scanned by the call guard): a single immutableTraceExecutionPlanper run (static capability table ∧ the runtime demotions above; the buffer cap is parsed on rank 0 and broadcast so every rank's plan is identical) is consumed by the INFO reporting, the kernel, and the fallback loop — no consumer decides independently. Mapping extraction reuses the element functions' own code paths via_TraceProbeadapters function-extracted insidemltplyHubbardCore.c/mltplySpinCore.c(3-way split: shared static_mapcore + unchanged original + probe; the full reference-anchored suites pass in both builds — noMPI 131/131, MPI 175/175 — anchoring the originals' pre-refactor behavior across all methods). Row formats are shared with the legacy evaluators throughgreen_row_format.h, so kernel and fallback output cannot drift byte-wise. Per-quantity phase timers (map/stream/output) print on rank 0 for benchmarking.Testing
Solver 0/1/2behavior byte-identical, verified against a pre-phase-2 build).fulldiag_solver_keyword(keyword/back-compat matrix incl. ExpecMode validation/demotion cases),fulldiag_elpa_hubbard_chain(multi-rank integration incl. startup HamIO rejection, 5-column reference check),test/unit/elpa_eigen_check,test/unit/elpa_redist_check,test/unit/elpa_statepanel_check(+ dedicated zero-owner-rank case, and a descinit failure-propagation phase),test/unit/green_partial_merge_check(in-process merge failure injection: vanished part, opened-but-never-closed part, truncated-after-close part — all must fail on every rank without publishing),fulldiag_expecmode_equiv_np2/_np3(ExpecMode 0/1/2 equivalence: energies and all aggregate Green files within 1e-8),fulldiag_spingc_gamma.test/manual/elpa_gpu_check.md):Solver 0— max eigenvalue diff 0.0 at printed precision (Hubbard N=400/4900, SpinGC with transverse field); per-rank MaxRSS 1.93 GB → 0.63 GB going np=1 → np=4 at N=4900; GPU distributed generation exact.zvo_physMode 0 vs 1 maxdiff 0.0 over 4900 states; S²/Sz unification exact vs serial on all energy-isolated states;Solver 1+ExpecMode 1exact; zero-owner ranks (6 ranks, 4 states) exact; assert-enabled Debug build clean.zvo_physmaxdiff 0.0, per-eigen Green file sets identical. All three runtime fallback reasons exercised end-to-end, including a forced memory-gate demotion whose fallback two-body output is byte-identical toExpecMode 0. New unit testexpec_trace_map_check(runs in every build): mapping validity vs the original element functions at 1e-13, purity (double extraction bit-identical, no state mutation), buffer-gate boundaries.Review
Design and code went through multi-round AI review at every phase (design docs: multiple rounds to zero must-fix, cross-checked against the official ELPA manual; each implementation task individually reviewed; whole-branch reviews + two-phase Codex/Antigravity cycles to zero must-fix), with all findings resolved or their deferrals justified in the design documents. Phase 3b went through the same per-task review + whole-branch review + two-phase Codex/Antigravity cycle (all must-fix zero at convergence). Phase-3a hardware validation additionally surfaced (and the branch fixes) an out-of-range site index in a new test's def file; the underlying pre-existing robustness gap — HPhi does not validate 3/4/6-body Green-function site indices against Nsite and crashes with an integer divide-by-zero — is documented in
test/manual/elpa_gpu_check.mdas an upstream follow-up.🤖 Generated with Claude Code
https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H