Fix numerical NAC factor-2 and unify energy-gap sign#160
Conversation
…e change) Investigation handoff for the overlap-based numerical NAC bug: - NACME.nacme divides (S-S^T) by dt instead of HST 2*dt -> dc = 2*d_ij - nacme and numerical_nac use opposite energy-gap signs - d antisymmetric, h=(E_j-E_i)d symmetric (private analytic branch had both backwards) - LiH MRSF run confirms factor 2.0000 -> 1.0000 after restoring 1/2 - NAMD propagator is external (PyRAI2MD); no internal 2x compensation No production code modified; fix gated on convention sign-off + PyRAI2MD contract. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restore the Hammes-Schiffer-Tully 1/2 at the source and standardize the derivative-coupling / interstate-coupling conventions across both overlap-NAC code paths. - NACME.nacme: divide the antisymmetrized state overlap by (2*dt) instead of dt. The Fortran get_dcv returns the raw (S - S^T) ~ +/- 2*dt*d_ij and leaves the denominator to the caller; applying only /dt produced a derivative coupling 2x too large. This corrects both the FD oracle and the per-frame time-derivative coupling. - NAC.numerical_nac: unify the energy gap to gap[i,j] = E_j - E_i, matching NACME.nacme. The two paths previously used opposite gap signs, so numerical_nac reported h with a flipped sign (-2*h_std vs +2*h_std). - Treat d_ij = <Psi_i|grad Psi_j> (antisymmetric) as the canonical stored quantity and derive h_ij = (E_j - E_i)*d_ij (symmetric) from it; add a convention guard (verify_nac_conventions) at both storage sites to catch future gap-sign / symmetry regressions. After the fix both paths yield dcv = d (antisymmetric) and nacv = h (symmetric, standard sign), agreeing element-wise. Note: the source fix changes the value OpenQP exports (DCME/NACME files and the back_door API) to the external PyRAI2MD NAMD driver, halving the exported time-derivative coupling. PyRAI2MD's expected denominator must be confirmed / de-compensated in lockstep before this affects production dynamics. Verification: fd_nac_logic_check.py exercises the exact assembly math on synthetic overlaps built from a known d (no Fortran backend required), confirming factor-2 removal (old ratio 2.0 -> new 1.0), d antisymmetry, h symmetry, h == (E_j-E_i)*d, and cross-path sign/magnitude agreement. https://claude.ai/code/session_0159ZCLgerRZ6JRj1gByyfdN
Add __pycache__/ and *.py[cod] to .gitignore so running the pyoqp code or the verification scripts does not leave untracked bytecode artifacts. https://claude.ai/code/session_0159ZCLgerRZ6JRj1gByyfdN
Add fd_nac_benchmark.py (H2O MRSF/BHHLYP/6-31G displacement scan through the compiled Fortran backend) and BENCHMARK_RESULTS.md documenting exact numbers. Results confirm the fix on real MRSF state overlaps: - current/corrected = 2.000000 exactly for every element at every displacement (the factor-2 the fix removes); - corrected equals the antisymmetric part of the central-difference overlap (the HST derivative coupling) exactly: corr/h_anti = 1.0000, cur/h_anti = 2.0000 for all clean non-degenerate pairs and all step sizes; - corrected is exactly antisymmetric (||M+M^T||/||M|| = 0) at every step; - the raw overlap's symmetric component (basis-following/phase) is correctly discarded by antisymmetrization, as the HST convention requires. Combined with the backend-free exact proof in fd_nac_logic_check.py, this validates the factor-2 / gap-sign correction. https://claude.ai/code/session_0159ZCLgerRZ6JRj1gByyfdN
|
@claude Please review this draft PR as a non-blocking code review. Focus on correctness of the numerical NAC factor/sign fix, tests, and OpenQP portability. Do not modify files; leave concise actionable findings or say no blocking issues found. |
|
Claude finished @karmachoi's task in 3m 50s —— View job Code Review — Numerical NAC Factor/Sign FixTasks:
SummaryThe core math is correct and well-verified. No blocking issues. Four minor actionable items below. Factor-2 fix (
|
Summary
OpenQP's overlap-based numerical NAC had two confirmed bugs in the Python assembly layer (
pyoqp/oqp/library/single_point.py):NACME.nacmedivided the antisymmetrized state overlap bydtinstead of2·dt. SinceS − Sᵀ ≈ ±2·dt·d_ij, the Hammes–Schiffer–Tully convention requires2·dt; the missing ½ made the derivative coupling 2× too large. (The Fortranget_dcvreturns the rawS − Sᵀand explicitly leaves the2adenominator to the caller — so the backend was correct; only the Python layer was wrong.)nacmeusedgap[i,j] = E_j − E_iwhilenumerical_nacusedE_i − E_j, so the two paths reportedhwith opposite sign (+2·h_stdvs−2·h_std).Changes
NACME.nacme:/(self.dt)→/(2.0 * self.dt)— restores the HST ½ at the source (corrects the FD oracle, the per-frame time-derivative coupling, and the export).NAC.numerical_nac: unify the energy gap togap[i,j] = E_j − E_i, matchingnacme.d_ij = ⟨Ψ_i|∇Ψ_j⟩(antisymmetric) as the canonical stored quantity and deriveh_ij = (E_j − E_i)·d_ij(symmetric) from it; add averify_nac_conventionsguard at both storage sites to catch future gap-sign / symmetry regressions.After the fix both paths yield
dcv = d(antisymmetric) andnacv = h(symmetric, standard sign), agreeing element-wise.Benchmark results
Full details in
BENCHMARK_RESULTS.md. Two independent levels:1. Exact convention proof —
fd_nac_logic_check.py(no backend)Synthetic overlaps from a known antisymmetric
dexercise the exact assembly logic:2. Live MRSF backend —
fd_nac_benchmark.pyH₂O (C₂ᵥ, no spatial degeneracy), MRSF/BHHLYP/6-31G, O displaced along z, Δ-scan, single-threaded for bit-reproducibility:
The factor-2 (every element, every Δ):
current/corrected = 2.000000(min = max).correctedvs the true HSTd(= antisymmetric part of the FD), clean pairs (Δ=0.0025 Å):correctedis exactly antisymmetric (‖M+Mᵀ‖/‖M‖ = 0.00) at every Δ — a valid derivative coupling.This fix halves the exported DCME/NACME (and
back_doorAPI) consumed by the external PyRAI2MD NAMD driver. OpenQP has no internal NAMD propagator, so the internal NAC is unambiguously corrected — but PyRAI2MD's expected denominator must be confirmed / de-compensated in lockstep before this affects production dynamics. Kept as draft until that is resolved.Not validated here
An absolute cross-check of
h_ijagainst an independent analytic NAC isn't possible in OpenQP today (analytic MRSF NAC is not implemented) — recommended as the next validation once that backend exists.🤖 Generated with Claude Code
Generated by Claude Code
Source fork PR: karmachoi#3
Phase/root stability follow-up
Latest commit
3289ffdadds a gauge-stabilization layer before overlap-derived NACME is exported:d_ij;mol.data;h_ij, so Cartesian NAC vectors obeyd_ij = -d_jiandh_ij = h_ji;Additional local validation after this commit:
CI was re-triggered on the PR head and is currently queued.