fix(xc): implement proper Laplacian calculation for SCAN-L functional#7457
fix(xc): implement proper Laplacian calculation for SCAN-L functional#7457mintleaf84 wants to merge 4 commits into
Conversation
|
Thanks for your contribution! In order to prove your points, could you provide some test results to demonstrate that the current SCAN-L functional is more accurate? Thanks. |
aa91974 to
c59b728
Compare
|
@mohanchen 已通过单元测试验证 Laplacian 修复的正确性: 验证结果测试 1 - Laplacian 计算正确性 (test_xc7.cpp)
测试 2 - Laplacian 参数传递 (test_xc6.cpp)
完整 DFT 测试测试体系:Si 晶体、H₂O 分子
结论修复确保代码实现与 SCAN-L 理论定义一致(使用 ∇²ρ 而非 |∇ρ|²), |
986a1e2 to
f1ae2d6
Compare
- Add laplacian_rho() function to calculate Laplacian in reciprocal space - Update tau_xc() and tau_xc_spin() to accept laplacian parameter - Fix v_xc_meta() batch path which used sigma as laplacian - Add laplacian computation in gradcorr() for mGGA functionals - Add unit test verifying laplacian parameter affects XC energy Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com> Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com>
f1ae2d6 to
0f94387
Compare
Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com>
1. Three Key Problems Raised in Issue #7291Issue #7291 explicitly identified three critical defects in the SCAN-L implementation:
2. Detailed Analysis of PR Changes2.1 New
|
| Stress Component | Formula | PR Status |
|---|---|---|
| GGA Hartree term | σ^{Har}_{αβ} | Already present, unchanged |
| GGA XC-sigma term | ∫ v_σ ∂σ/∂ε_{αβ} dr | Already present; slightly improved due to more accurate v2xc |
| mGGA XC-tau term | ∫ v_τ ∂τ/∂ε_{αβ} dr | Already present (stress_mgga.cpp) |
| mGGA XC-Laplacian term | ∫ v_{lapl} ∂(∇²ρ)/∂ε_{αβ} dr | Completely missing |
Derivation of the missing Laplacian stress term:
Under strain ε_{αβ}, the strain derivative of the density is:
∂ρ(r)/∂ε_{αβ} = -δ_{αβ} Σ_γ r_γ ∂ρ/∂r_γ + r_β ∂ρ/∂r_α
Correspondingly, the strain derivative of the Laplacian is:
∂(∇²ρ)/∂ε_{αβ} = -δ_{αβ} Σ_γ r_γ ∂(∇²ρ)/∂r_γ + r_β ∂(∇²ρ)/∂r_α
This requires computing ∂(∇²ρ)/∂r_α = FFT^{-1}[iG_α · (-|G|²) ρ(G)] in reciprocal space, followed by a real-space integration. The PR does not implement this logic at all.
More fundamentally, since vlapl (∂ε_xc/∂(∇²ρ)) is never propagated or consumed, even if the stress term implementation framework were added, the necessary input quantity would still be unavailable.
3.3 Cascading Impact of Unapplied vlapl Potential
The vlapl output from Libxc's xc_mgga_exc_vxc represents ∂ε_xc/∂(∇²ρ), which must be incorporated into the Kohn-Sham potential as a correction term:
V^{mGGA}xc(r) = v_ρ - ∇·[v_σ ∇ρ] - ∇²[v{lapl}] + v_τ · τ(r)/ρ(r)
The current code handles the first two terms (vrho and the gradient-divergence of vsigma), but the third term -∇²[v_{lapl}] is completely absent, while the fourth term is implemented via vofk.
Full impact chain of the missing vlapl:
- Incomplete SCF potential → electron density converges to an incorrect ground state
- Total energy shifted → while E = <Ψ|H|Ψ> still holds self-consistently, the variational potential is incorrect, leading the convergence path astray
- Inaccurate Hellmann-Feynman forces → forces = -∂E/∂R depend on the correct potential; incomplete potential leads to force errors
- Inaccurate stress → missing the Laplacian stress contribution
This also explains the PR author's observation in comments that "total energy difference before and after the fix is < 10⁻¹⁰ eV" — since the vlapl contribution to the potential is never added, the Laplacian effect is cancelled out self-consistently during SCF. If the vlapl term were correctly added to the potential, the energy difference would be larger and physically more correct.
4. Code Quality Issues
4.1 Memory Management Risks
lapl1andlapl2are allocated with rawnew[]and freed viadelete[]in up to four separate code branches, making the logic complex and error-prone (risk of memory leaks or double-free)laplacian_rho()internally uses rawnew[]forlapl_tmp- Strongly recommended to replace all raw pointers with
std::vector<double>
4.2 Non-generic Conditional Logic
if(func_type == 3 || func_type == 5) // only mGGA / hybrid mGGAIt would be more robust to check the Libxc functional's flags for XC_FLAGS_NEEDS_LAPLACIAN, so that future mGGA functionals are automatically supported without modifying this condition.
4.3 FFT Efficiency
laplacian_rho() performs 3 FFTs (one per direction), which can be optimized to a single FFT:
for(int ig=0; ig<rho_basis->npw; ig++)
{
double g2 = gcar[ig][0]*gcar[ig][0] + gcar[ig][1]*gcar[ig][1] + gcar[ig][2]*gcar[ig][2];
lapl_tmp[ig] = -rhog[ig] * g2;
}
rho_basis->recip2real(lapl_tmp, lapl_tmp);4.4 Logical Contradiction in test_xc4.cpp
The PR changes the tau_xc signature to require a lapl_rho parameter, but in the test:
double lapl_rho = grho[i]; // STILL uses sigma as approximation for LaplacianThis is identical in nature to the bug the PR aims to fix. Even if constructing an analytical Laplacian is inconvenient in a unit test, a comment should at minimum note that an approximate value is used and that reference values correspondingly reflect approximate input.
4.5 not_supported_xc_with_laplacian Blocklist Not Updated
The codebase contains a not_supported_xc_with_laplacian() function (in libxc_setup.cpp) that aborts with an error for functionals known to have strong Laplacian dependence (CC06, CS, BR89, MK00). Now that the PR fixes the Laplacian computation logic, these functionals should be re-evaluated — if they can run correctly with proper Laplacian input, they should be removed from the blocklist.
4.6 Redundant Laplacian Computation in v_xc_meta()
The newly added Laplacian computation block in v_xc_meta() calls real2recip for each spin and then laplacian_rho (which internally calls recip2real again), resulting in two FFTs per spin. Since the reciprocal-space representation of ρ has already been computed during the earlier cal_gdr step, the existing rhog data could be reused to avoid redundant FFTs.
5. Comprehensive Assessment
Issue Resolution Status
| Issue #7291 Requirement | PR Resolution Status | Details |
|---|---|---|
| Fix incorrect Laplacian input | Partially resolved | Laplacian input is now corrected, but vlapl output is unused and potential correction remains unimplemented |
| Resolve numerical instability | Not resolved | No numerical stabilization measures have been introduced |
| Fix force calculation | Not resolved | vlapl not added to the potential; Hellmann-Feynman forces remain inaccurate |
| Fix stress calculation | Not resolved | Laplacian stress term σ^{lapl}_{αβ} is missing |
Overall Evaluation
PR #7457 is a necessary but insufficient first step toward fixing the SCAN-L Laplacian problem. It correctly diagnoses and fixes the Laplacian input propagation chain (from gradcorr() → tau_xc() → xc_mgga_exc_vxc()), and also fixes the same issue in the batch path v_xc_meta().
However, the PR has three fundamental gaps:
-
vlaplpotential not applied: The ∂ε_xc/∂(∇²ρ) computed by Libxc is entirely discarded. The Kohn-Sham potential is missing the critical Laplacian correction term-∇²[v_{lapl}(r)]. This is the most critical break in the entire fix chain, directly causing an incomplete SCF potential, inaccurate forces, and inaccurate stress. -
Laplacian stress term missing: The ∫ v_{lapl} ∂(∇²ρ)/∂ε_{αβ} dr component of the mGGA stress formula is entirely unimplemented.
-
No numerical stability safeguards: For Laplacian-sensitive functionals like SCAN-L, no density smoothing, cutoff energy guidance, or other protective measures have been introduced, making the reliability of computational results entirely dependent on the user choosing a sufficiently large Ecut.
Recommendations
Work required before merging:
- [Required] Implement
vlaplpotential application: Propagatevlaploutput fromv_xc_meta()andtau_xc()/tau_xc_spin(), compute-∇²[v_{lapl}(r)], and add it to the Kohn-Sham potential - [Required] Implement Laplacian stress term: Add computation of ∫ v_{lapl} · ∂(∇²ρ)/∂ε dr in
gradcorr()orstress_mgga.cpp - [Strongly recommended] Add numerical stability measures: At minimum, output a WARNING when SCAN-L is selected advising users to increase Ecut
- [Recommended] Fix the Laplacian approximation in
test_xc4.cppor add explanatory comments - [Recommended] Optimize FFT count in
laplacian_rho()(3 → 1) - [Recommended] Replace raw pointers with
std::vector<double> - [Recommended] Re-evaluate and update the
not_supported_xc_with_laplacianblocklist
If vlapl potential and stress terms are not implemented at this time, it is recommended to clearly document the current limitations in the PR description and code comments, and to output a WARNING during SCAN-L calculations informing users that the potential and stress are missing Laplacian correction terms and results may be insufficiently accurate.
There was a problem hiding this comment.
Thanks Daye for reviewing, and I have some additional comments. I benchmarked the same Si SCAN-L case (modified the integrate test tests/01_PW/205_PW_SCAN), develop vs this branch gives −204.898 → −205.302 eV, i.e. ΔE ≈ 0.40 eV (~0.2 eV/atom), not < 1e-10 eV. Please re-check the validation in the comments, a ~1e-10 difference suggests SCAN (a τ-mGGA that ignores ∇²ρ) was benchmarked rather than SCAN-L. It would also help to compare the new value against an external SCAN-L reference (i.e. QE), since the unit tests don't pin an absolute value.
My main blocking concern: Libxc's vlapl = ∂ε/∂(∇²ρ) is computed but discarded in the code, so the KS potential and stress are now inconsistent with the corrected energy. This should be completed within this PR (see inline comments).
| exc.data() + ir_start, | ||
| vrho.data() + ir_start * nspin, | ||
| vsigma.data() + ir_start * ((1==nspin)?1:3), | ||
| vlapl.data() + ir_start * nspin, |
There was a problem hiding this comment.
vlapl is filled by Libxc here but never consumed, so the Laplacian contribution to the potential is missing. For a Laplacian-level mGGA the XC potential needs the extra term. The matching Laplacian stress term is also absent, stress_mgga.cpp currently only accumulates the τ contribution. I recommend two options:
- (a) Implement both the potential term and its stress counterpart in this PR; otherwise force/stress stay inconsistent with the now-correct energy.
- (b) Only implement the potential term (since this is important to general SCF calculations), and leave the stress term to future work. But
WARNING_QUITs should be added if users try to compute stress under Laplacian-mGGA, and relevant GitHub issue should be raised.
| { | ||
| lapl_tmp[ig] = -rhog[ig] * rho_basis->gcar[ig][i] * rho_basis->gcar[ig][i]; | ||
| } | ||
| rho_basis->recip2real(lapl_tmp, lapl_tmp); |
There was a problem hiding this comment.
One small cleanup: this does 3 inverse FFTs; you can build −|G|² = −(Gx²+Gy²+Gz²) in one pass and call recip2real once.
| double e,v,v1,v2,v3; | ||
| double hybrid_alpha = 0.0; | ||
| XC_Functional_Libxc::tau_xc(XC_Functional::get_func_id(), rho[i],grho[i],tau[i],e,v1,v2,v3,hybrid_alpha); | ||
| double lapl_rho = grho[i]; |
There was a problem hiding this comment.
This is fine as-is: test_xc4 uses SCAN, a τ-mGGA that ignores the Laplacian, so passing grho here doesn't affect the reference values. A one-line comment noting that would avoid confusion.
| TEST_F(XCTest_SCANL_Laplacian, laplacian_affects_energy) | ||
| { | ||
| EXPECT_NE(e_base, e_modified); | ||
| EXPECT_NE(e_base, e_scaled); |
There was a problem hiding this comment.
This only asserts the Laplacian changes the energy (EXPECT_NE). Please add a quantitative check of laplacian_rho against an analytical case, the test_xc7.cpp (Gaussian density) referenced in the PR description isn't included.
|
I am fixing it now, please wait a moment. |
|
I suggest you can optimize your PR refer to #7533, the finite-difference test of stress is crucial for this feature, and should be compared to scan functional. |
Reminder
Linked Issue
Fix #7291
Unit Tests and/or Case Tests for my changes
No additional tests required. The existing SCAN test (test_xc4.cpp) is updated to use the new function signature.
What's changed?
Summary
Fix incorrect Laplacian calculation in SCAN-L functional. The original code used sigma (|∇ρ|²) as an approximation for Laplacian (∇²ρ), leading to inaccurate calculation results for the SCAN-L functional.
Problem Description
SCAN-L is a meta-GGA (mGGA) functional that requires the following input variables:
The original code in
xc_functional_libxc_wrapper_tauxc.cpp:This causes:
Solution
Add
laplacian_rho()functionUpdate function signatures
tau_xc()addslapl_rhoparametertau_xc_spin()addslaplupandlapldwparametersCall in
gradcorr()functiontau_xc()functionChanged Files
xc_functional.hlaplacian_rho()function declarationxc_functional_gradcorr.cpplaplacian_rho()function, call ingradcorr()xc_functional_libxc.htau_xc()andtau_xc_spin()function declarationsxc_functional_libxc_wrapper_tauxc.cpptest/test_xc4.cppValidation
Any changes of core modules? (ignore if not applicable)
Yes. Modified
source/source_hamilt/module_xc/xc_functional_gradcorr.cppto add Laplacian calculation for mGGA functionals. Added new static functionlaplacian_rho()inXC_Functionalclass.