Skip to content

Add ELPA backend for FullDiag: Solver keyword, distributed Hamiltonian generation, state-parallel observables (ExpecMode 1) and trace kernels (ExpecMode 2)#276

Merged
k-yoshimi merged 140 commits into
developfrom
feature/elpa-fulldiag
Jul 26, 2026
Merged

Add ELPA backend for FullDiag: Solver keyword, distributed Hamiltonian generation, state-parallel observables (ExpecMode 1) and trace kernels (ExpecMode 2)#276
k-yoshimi merged 140 commits into
developfrom
feature/elpa-fulldiag

Conversation

@k-yoshimi

@k-yoshimi k-yoshimi commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an ELPA backend to FullDiag, enabling multi-node (and multi-GPU) full diagonalization, selected via a new Solver keyword in calcmod.def. This PR now contains phases 1, 2, 3a, 3b, and 3c of the design in docs/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 3 and 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 the var column) now also runs through a CSR sparse y = H·x kernel (the Hamiltonian is collected once per rank from the audited makeHam enumeration and every owned eigenstate is streamed through it), instead of the per-state expec_energy_flct fallback. It supports a broader model set than the Green-function kernels — Hubbard/HubbardGC (incl. the particle-number-conserved variant), tJ/tJGC, Kondo/KondoGC, and Spin/SpinGC (incl. general spin) — with three fallback reasons: InputHam, unsupported model (SpinlessFermion/SpinlessFermionGC and unknown models), and the HPHI_TRACE_BUF_MAX_MB memory gate (which now also bounds the energy family's per-rank CSR buffer). S2, NBodyG, and AnomalousG remain on the ExpecMode 1 path.

New user interface

Solver Backend Requires
0 LAPACK zheev (serial, default)
1 ScaLAPACK pzheev -DUSE_SCALAPACK=ON
2 MAGMA (single-node multi-GPU) MAGMA build
3 ELPA (multi-node CPU/GPU, new) -DUSE_ELPA=ON

Migration notes

  • The ScaLAPACK keyword in calcmod.def is deprecated (still works, prints a warning): use Solver 1 instead.
  • Inputs without a Solver keyword behave exactly as before (legacy resolution preserves current defaults, including NGPU=2 on MAGMA builds).
  • For ELPA, NGPU 0 runs on CPU and NGPU >= 1 on 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 3 requires 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).
  • Phase 2: with Solver 3 and nproc > 1, OutputHam/InputHam are rejected at startup (the Hamiltonian never exists in replicated form). Use 1 process or another Solver for Hamiltonian I/O. All other inputs are unaffected — results are identical to the replicated path.
  • Phase 3a — new keyword ExpecMode (CalcMod, default 0). 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 as ExpecMode 1 and prints an INFO line saying so.
    • Eligibility: nonzero ExpecMode requires CalcType = FullDiag with Solver 1 (ScaLAPACK) or 3 (ELPA); any other combination is rejected at startup. With exactly one MPI process, ExpecMode is automatically reverted to 0 (an INFO line is printed) since results are identical for a single process either way.
    • Guarantee: ExpecMode changes only evaluation speed0, 1, and 2 give identical physics up to floating-point rounding (summation order differs between kernels, so results are not bit-identical).
  • Phase 3b — ExpecMode 2 is now real. Previously (phase 3a) ExpecMode 2 printed an INFO line and ran as ExpecMode 1. It now selects trace kernels for the one-body and two-body Green functions on supported models — Hubbard/HubbardGC and half-integer Spin/SpinGC (general-spin, tJ/tJGC, Kondo/KondoGC, and spinless models always fall back). Everything else — the energy family including the var column (computed exactly as before; now trace-kernelized in phase 3c, see below), S², NBodyG, AnomalousG — always runs the ExpecMode 1 path in this version. Per-quantity runtime fallbacks are reported by rank-0 INFO lines, evaluated in a fixed, mutually exclusive order: unsupported model / shared evaluator (the two-body GF falls back whenever ThreeBodyG/FourBodyG/SixBodyG are 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 variable HPHI_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 16 bytes); exceeding it demotes that quantity to the ExpecMode 1 path. The guarantee is unchanged: ExpecMode affects speed only — 0/1/2 agree within floating-point rounding (not bit-identical). Purely additive; no keyword or file-format changes.
  • Phase 3c — the energy/fluctuation family is now trace-kernelized under ExpecMode 2. This supersedes the phase-3b statement above that the energy family "always runs the ExpecMode 1 path": as of phase 3c, for the supported models (Hubbard/HubbardGC incl. the N-conserved variant, tJ/tJGC, Kondo/KondoGC, Spin/SpinGC incl. general spin) the energy family runs a CSR y = H·x kernel. SpinlessFermion/SpinlessFermionGC and any unknown model fall back (a positive supported-model whitelist demotes them, with a verbatim INFO: ExpecMode 2: the energy/fluctuation family uses the ExpecMode-1 fallback (unsupported model). line); InputHam and the memory gate are the other two fallback reasons. var is unchanged (Phys.var still 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, so y = H·x needs the full H locally; distributing it would reintroduce the per-state collectives ExpecMode 1/2 remove) — it is sparse (~24·nnz bytes, ~32 MB at N=63504) and bounded by HPHI_TRACE_BUF_MAX_MB. Purely additive; no keyword or file-format changes; the output is identical to ExpecMode 0/1 within floating-point rounding.
  • Phase 3c — behavior change: SpinlessFermion/SpinlessFermionGC + FullDiag is now rejected at startup. These models are not correctly supported for FullDiag in the current code — makeHam.c has no spinless branch, so the hopping (Trans) is silently dropped, and the FullDiag phys output prints num_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).
  • Phase 3a — behavior fix: distributed FullDiag S2/Sz are no longer zero-filled. Before this phase, FullDiag runs with Solver 1 (ScaLAPACK) or 3 (ELPA) and more than one MPI process skipped the S²/Sz calculation entirely — S2 and Sz were reported as 0, and the stdout progress line used a shortened format that omitted the S2 column. As of phase 3a, these distributed runs compute S2/Sz on rank 0 (same as the serial path), and the stdout progress line always uses the single-process (serial) format, with the S2 column present, for every Solver/ExpecMode combination. This is an intentional correctness fix, independent of ExpecMode'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 expect S2/Sz to change from 0 to their correct values. Scripts that parse the stdout progress lines of distributed FullDiag runs by column index must be updated: inserting the S2 column shifts every subsequent field (e.g. the doublon column) one position to the right relative to the old shortened format.
  • No existing keyword is deprecated or removed in phase 3a. ExpecMode is purely additive and defaults to the pre-existing behavior (0) except for the S2/Sz fix 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 before elpa_setup, tunables after, elpa_setup_gpu), with collective error synchronization (MPI_MIN reduction) before every collective and no silent GPU→CPU fallback.
  • GetEigenVectorBlock() (matrixscalapack.c): per-state eigenvector gather to rank 0 via a single pzgemr2d_ block transfer (rank-0-only destination grid), replacing element-wise pzelget_ on the ELPA path.
  • Solver/legacy keyword resolution and full startup validation in readdef.c; the pre-existing multi-process FullDiag gates (HPhiMain.c/check.c/CheckMPI.c) are driven from the resolved solver.
  • CMake: USE_ELPA option + cmake/FindELPA.cmake (pkg-config, versioned paths, manual overrides, threaded-variant rejection, GPU-API detection).
  • Automatic ELPA block-size cap (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 replicated Ham or a rank-local 1D column panel; all 29 Hamiltonian write sites in makeHam.c/nbody_interall.c/anomalous_pair.c routed 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 replicated Ham/L_vec (zero-owner ranks get a 1-element dummy with a valid descriptor so they can join the collectives).
  • RedistPanelToBlockCyclic() (matrixscalapack.c): one pzgemr2d_ 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 rendezvous Allreduce, Gatherv of the per-state observables, manifest merge) and src/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 enters pzgemr2d_ alone.
  • ExpecLocal mode (wrapperMPI.c): SumMPI_* become pass-throughs and fopenMPI opens rank-local files inside the per-state loop; unreachable collectives assert; the raw-MPI_Sendrecv branches in nbody_correlation.c/anomalous_pair.c get 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.
  • Aggregate Green output in Mode 1 (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, and rename()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.
  • Distributed Mode 0 S²/Sz unification in src/phys.c (see migration notes).

Phase 3b (trace kernels)

  • src/expec_trace.c (MPI-free, scanned by the call guard): a single immutable TraceExecutionPlan per 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 _TraceProbe adapters function-extracted inside mltplyHubbardCore.c/mltplySpinCore.c (3-way split: shared static _map core + 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 through green_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

  • Serial regression: all existing FullDiag tests pass unchanged (18/18 incl. the new guard test; Solver 0/1/2 behavior byte-identical, verified against a pre-phase-2 build).
  • New tests: 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.
  • On-hardware validation (dual RTX 6000 Ada workstation, ELPA 2025.06.001; full log in test/manual/elpa_gpu_check.md):
    • Phase 1: eigenpair checks pass at np = 1..16 (CPU); GPU runs (1×1 and 2×2 GPUs, N=4900) match LAPACK eigenvalues exactly; failure paths abort with clear messages.
    • Phase 2: distributed-generation physics vs 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.
    • Phase 3a: equivalence tests pass at np=2 and np=3 (4 cases incl. 3/4/6-body Green functions); benchmark N=4900 np=4: observable section 3.96 s → 0.98 s (4.04×), zvo_phys Mode 0 vs 1 maxdiff 0.0 over 4900 states; S²/Sz unification exact vs serial on all energy-isolated states; Solver 1 + ExpecMode 1 exact; zero-owner ranks (6 ranks, 4 states) exact; assert-enabled Debug build clean.
    • Phase 3b: production-path equivalence np=2/3 across 5 cases (incl. a dedicated canonical-Hubbard golden case covering all one-/two-body operator families, and a SpinGC honeycomb multibody case proving the shared-evaluator demotion — that case caught a real bug during development: skipping the shared evaluator silently dropped the 3/4/6-body output files, fixed by the plan-level demotion). Benchmark N=4900 np=4: walls 64.2 / 32.3 / 26.0 s (Mode 0/1/2) with the kernel spending only ~1.1 s (map 0.001 s, stream 1.0 s, output 0.09 s); zvo_phys maxdiff 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 to ExpecMode 0. New unit test expec_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.
  • Benchmark (N=4900, 2 ranks, diagonalization step): pzheev 149.8 s → ELPA CPU 25.8 s (5.8×) → ELPA GPU 4.2 s (35×).

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.md as an upstream follow-up.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WCwnWyfZJx43ry3SU1yV2H

Kazuyoshi Yoshimi and others added 30 commits July 10, 2026 07:16
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
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
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
…-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
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
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
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
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 tmisawa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

  1. The energy trace kernel incorrectly admits SpinlessFermion(GC).

    CalcByFullDiag() calls makeHam() for every non-InputHam model. Although the makeHam() model switch has no SpinlessFermion branch, it also has no rejecting default, 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-InputHam model. trace_model_n_diag() then maps an unlisted model to n_diag == 0, and TraceEnergyEvalState() treats that as canonical Spin, assigning num = NsiteMPI and Sz = 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.

  2. 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.

  3. 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 var always 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.

Kazuyoshi Yoshimi and others added 4 commits July 21, 2026 22:12
…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
@k-yoshimi

Copy link
Copy Markdown
Contributor Author

@tmisawa Thanks — you are right, and confirmed against the code: in ExpecMode 2 the energy family's CSR Hamiltonian is built for the full column range on every rank, so the per-rank CSR does not shrink as the MPI-rank count grows.

This is by design, and I've documented the rationale rather than change the layout:

  • Why replicated: the state-panel layout (ExpecMode 1/2) gives each rank complete eigenvectors and evaluates them with no per-state communication — that is the whole point of the state-task parallelism. Computing y = H·x for a locally-complete x needs every row of H on that rank. Distributing the CSR (N/P rows per rank) would force per-state collectives to apply the distributed operator to a local full vector, reintroducing exactly the communication ExpecMode 1/2 removes.
  • Why it's acceptable in practice: the CSR is sparse — ~24·nnz bytes with nnz ≈ T·N (T = terms/column, e.g. ~21 for Hubbard L=10) ≈ 32 MB at N=63504 — much smaller than the O(N²/P) eigenvector state panel at moderate P. But because the CSR is P-independent while the panel shrinks with P, its fixed per-rank cost becomes relatively more significant as P grows and can dominate at large P or for operator-dense (InterAll/NBodyInterAll) inputs. In that case the per-rank HPHI_TRACE_BUF_MAX_MB gate falls the energy family back to ExpecMode 1 and reports it.

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.

@tmisawa

tmisawa commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

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-review

1. SpinlessFermion(GC) correctness bug — primary blocker

This was item 1 of my previous review and remains completely unaddressed at the current head 87c5d3df. The commits since that review contain documentation changes only; there are no source or regression-test changes.

The root cause is that TraceBuildPlan() enables the energy trace kernel for every non-InputHam model, based on the incorrect assumption that SpinlessFermion(GC) cannot reach the FullDiag observable phase. In practice it does reach this phase. trace_model_n_diag() then maps the unlisted model to n_diag == 0, and TraceEnergyEvalState() treats it as canonical Spin.

This produces the wrong fluctuation semantics:

  • canonical SpinlessFermion requires num = NeMPI and Sz = 0;
  • SpinlessFermionGC must calculate the particle number from the wavefunction;
  • the trace kernel instead uses the canonical-Spin constants.

I reproduced the current head with ScaLAPACK, np=2, and an accepted canonical SpinlessFermion input. It reports:

INFO: ExpecMode 2: the energy/fluctuation family uses the trace kernel.

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 oracle

The push-side elpa job currently fails in fulldiag_expecmode_equiv_np3, while the pull-request-side job for the identical head passes.

The failing comparison reruns diagonalization for each ExpecMode and compares per-state observables by eigenstate index. For a degenerate eigenvalue, the eigensolver may return any orthonormal rotation within the degenerate subspace. Energies remain identical, but basis-dependent quantities such as S2 and local Green functions need not agree state by state.

The reported failure is exactly of this form: states with the same energy have different S2 values, with differences up to approximately 0.70. I also reproduced this locally with ScaLAPACK np=3.

This is not evidence of a Phase-3c numerical-kernel error. The failing case 7 uses InputHam, so the energy family is demoted to the Mode-1 fallback; despite the CI job name, that case explicitly uses Solver 1 (ScaLAPACK).

Please do not resolve this by rerunning CI. The test should instead do one of the following:

  • use non-degenerate fixtures for state-by-state comparisons;
  • reuse the same eigenvectors for all ExpecMode evaluations; or
  • compare basis-invariant quantities grouped by degenerate eigenspace.

The other fixtures used by this np=3 test should also be checked for degeneracy.

Secondary documentation cleanup

After the two blockers above, please also align the remaining documentation and comments with the implementation: the TraceHamCollect() comment still describes rank-local columns although the full CSR is collected on every rank; the Phase-3c design and finalize API disagree about raw versus merged nnz; HPHI_TRACE_BUF_MAX_MB is a per-quantity threshold rather than a cap on the sum of concurrently live buffers; and the PR description still contains the pre-Phase-3c ExpecMode description.

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
Kazuyoshi Yoshimi and others added 5 commits July 22, 2026 06:43
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
@k-yoshimi

Copy link
Copy Markdown
Contributor Author

@tmisawa Thank you for the precise review — both blockers and all secondary items are addressed (pushed 87c5d3df..5cd629d6), validated on clavius (ScaLAPACK) where you reproduced them, and the wording went through the ai-review-cycle (Codex, 3 rounds to convergence).

Blocker 1 — SpinlessFermion(GC) energy-kernel correctness (9d92e898)

Fixed exactly as you specified: added an explicit positive supported-model predicate TraceModelEnergySupported() (the 11 models with implemented fluctuation semantics: Hubbard/HubbardGC and their N-conserved variant, Kondo/KondoGC/KondoNConserved, tJ/tJGC/tJNConserved, Spin, SpinGC), so SpinlessFermion, SpinlessFermionGC, and any unknown model now demote to the ExpecMode-1 path with a new byte-exact INFO line ... the energy/fluctuation family uses the ExpecMode-1 fallback (unsupported model).. Added the canonical + GC Mode-0/1/2 regression cases (script cases 8/9) and a unit test of the predicate. Clavius np=2/np=3: the mode-2 log now shows the demotion, and Mode-0/1/2 agree.

Blocker 2 — degeneracy-unsafe np=3 oracle (a466466c, 5b106a1a, 5cd629d6)

Not resolved by rerunning CI — the oracle itself is fixed. compare_phys now groups states into degenerate energy levels and compares the basis-invariant per-level SUM Tr(P·A·P) instead of per-state values (per-state S2/doublon are basis-dependent within a degenerate subspace; per-level sums are not). Two follow-on corrections during review: the per-level tolerance was too tight (1e-8 vs the %10lf 6-decimal zvo_phys output → set to 2e-6·level_size, which fixed an intermittent np=3 failure — sumA=4 vs 3.999999); and the header/data split is now position-based with strict numeric validation so NaN/Inf hard-fails. Clavius ScaLAPACK np=3 is now 8/8 green (was flaking); np=2, scalapack_np2, and the ELPA suite all pass. Cases 1-6 (Solver 3, deterministic) were left unchanged.

Secondary documentation/comment cleanup (5f94f396, 4214d92d)

All four done: the TraceHamCollect() comment now says the CSR is the full column range on every rank; the finalize parameter was renamed nnz_rawnnz_merged (it receives ham_csr.nnz = rowptr[N]; the genuinely-raw nnz_raw in TraceHamGatedBytes is unchanged); the docs now state HPHI_TRACE_BUF_MAX_MB is a per-quantity threshold, not a cap on the sum of concurrently-live buffers; and the model coverage everywhere is the explicit whitelist with three fallback reasons (InputHam → unsupported-model → memory gate). The PR description is updated to the current phase-3c ExpecMode text.

@tmisawa

tmisawa commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

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 5cd629d6, I found two remaining correctness blockers.

1. The degeneracy-safe oracle fix is incomplete

compare_phys() is now degeneracy-aware, which correctly fixes the zvo_phys comparison used by cases 7–9.

However, cases 1–6 still use compare_output_trees(), which compares state-indexed Green/NBody output line by line. These quantities are not invariant under an allowed basis rotation within a degenerate eigenspace.

The response says:

Cases 1-6 (Solver 3, deterministic) were left unchanged.

But the same script is also run with ScaLAPACK by setting HPHI_FULLDIAG_SOLVER=1. With ScaLAPACK and np=3, I ran the current script six times from clean output directories:

HPHI_FULLDIAG_SOLVER=1 MPIRUN='mpiexec -n 3' \
  ../../test/fulldiag_expecmode_equiv.sh

All six runs failed. The failures were mainly in case 2's zvo_cisajs_eigen.dat, with one run also failing in zvo_NBodyG_eigen.dat.

For example, a fourfold-degenerate level at energy -1.5 had different state-by-state S2 values, while the sum over the four states was 10.0 in both modes. This is an allowed degenerate-subspace basis rotation, not a physics difference.

The current CMake registration includes ScaLAPACK np=2 only, so CI does not exercise this case:

  • ELPA: np=2, np=3
  • ScaLAPACK: np=2 only

Please make all state-indexed comparisons degeneracy-safe, use non-degenerate fixtures, or reuse the same eigenvectors between modes. Please also register a ScaLAPACK np=3 variant and verify it repeatedly. Passing 8/8 on one solver/library environment does not make a basis-dependent oracle portable.

2. The new Spinless FullDiag cases successfully publish incorrect physics

The new output.c filename cases allow SpinlessFermion and SpinlessFermionGC FullDiag runs to complete, but the underlying FullDiag path is not correct.

First, as already recorded in #293, makeHam.c has no SpinlessFermion/SpinlessFermionGC branch. Therefore, the t=1 hopping in cases 8/9 is ignored. For canonical case 8, the resulting spectrum is:

0, 0, 2, 2, 2, 2

This is the diagonal V=2 spectrum, with no hopping contribution.

Second, the particle-number output is also wrong:

  • expec_energy_flct() stores the Spinless particle number in X->Phys.num;
  • the FullDiag record/output path uses num_up + num_down;
  • canonical Spinless does not set num_up/down;
  • Spinless GC explicitly sets both fields to zero.

Consequently, canonical case 8 has Ncond=2 but prints <N>=0 for every state. Case 9 also prints <N>=0 for all states.

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) <N> difference is not valid because the output does not use Phys.num.

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.

Kazuyoshi Yoshimi and others added 4 commits July 23, 2026 05:33
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
@k-yoshimi

Copy link
Copy Markdown
Contributor Author

@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 5cd629d6..c84cc7fd).

1. Degeneracy-safe oracle — now covers all state-indexed comparisons, and ScaLAPACK np=3 is in CI

My first fix only made compare_phys (case 7's zvo_phys) degeneracy-aware; I missed that cases 1-6 compare the per-eigenstate *_eigen.dat Green files via compare_output_trees, and that CI never ran ScaLAPACK np=3. Fixed:

  • compare_output_trees now routes *_eigen.dat through a degeneracy-safe comparison: it builds the eigenstate→energy-level map from Eigenvalue.dat (derived independently from both runs and required to agree, so a near-threshold grouping difference fails), and compares the basis-invariant per-(operator, level) sum Tr(P·A·P) — plus a structural check of per-(operator, eigenstate-index) row multiplicity so a dropped/duplicated row can't hide behind an equal sum. zvo_phys*.dat (which also carries basis-dependent S2/doublon) is now routed through the degeneracy-aware compare_phys too; only genuinely basis-invariant files stay on the exact 1e-8 diff.
  • Tolerances match the output precision: *_eigen.dat is %.10lf1e-8·level_size; zvo_phys is %10lf (6 decimals) → 2e-6·level_size. A self-test confirms a 1e-6 non-degenerate perturbation is rejected, so this detects real regressions rather than masking them.
  • Registered fulldiag_expecmode_scalapack_np3 in CI (registrations are now ELPA np2/np3 and ScaLAPACK np2/np3).
  • Validation on clavius: ScaLAPACK np=3 passed 20/20 with the tightened tolerance (the case that failed 6/6 for you), plus ELPA np2/np3 and ScaLAPACK np2; a 14-scenario standalone self-check (degenerate rotation passes, real regression / dropped row / boundary split / malformed Eigenvalue.dat all fail).

2. SpinlessFermion(GC) FullDiag — no longer publishes incorrect physics

Per your endorsed resolution, since full spinless FullDiag support is out of scope (the makeHam hopping drop and the num_up+num_down output gap are #293), SpinlessFermion/SpinlessFermionGC + FullDiag is now rejected at startup (a new cErrSpinlessFullDiag message citing the unbuilt hopping and unpopulated particle number). The broken Mode-0/1/2 regression cases (8/9) are removed; the unsupported-model demotion is kept at the unit-test level (TraceModelEnergySupported returns 0 for spinless), and a startup-rejection test asserts the abort. Non-FullDiag spinless methods (Lanczos/TPQ/LOBCG) are unaffected.

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.

Kazuyoshi Yoshimi and others added 5 commits July 24, 2026 06:52
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 tmisawa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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=3 variant is covered by CI; I also repeated the np=3 equivalence test with independent output directories and obtained 10/10 passes.
  • SpinlessFermion / SpinlessFermionGC FullDiag 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=3 equivalence, trace-map/trace-Hamiltonian, MPI finalization, and partial-merge tests passed.
  • ASan+UBSan: 12/12 representative tests passed.
  • The merge tree against the current develop is 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.

@k-yoshimi
k-yoshimi merged commit c8205c1 into develop Jul 26, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants