Skip to content

Fix #6286: avoid redundant libxc init_func calls in per-point evaluation paths - #8082

Merged
mohanchen merged 10 commits into
deepmodeling:developfrom
mohanchen:2026-10-06-c
Oct 10, 2026
Merged

mohanchen merged 10 commits into
deepmodeling:developfrom
mohanchen:2026-10-06-c

Conversation

@mohanchen

@mohanchen mohanchen commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Fix #6286
Description:

Issue #6286 reported that after SCF convergence, XC_Functional_Libxc::init_func was called over 3000 times while running the 08_EXX/01_GO_S1_HSE test.

Root cause: the per-point libxc wrappers (xc_spin_libxc, gcxc_libxc, gcxc_spin_libxc, tau_xc, tau_xc_spin) called init_func -> evaluate on a single grid point -> finish_func inside the grid-point OpenMP loop. init_func only depends on the functional id, polarization, and runtime parameters (hybrid_alpha, hse_omega), none of which vary across grid points, so it needs to run only once per thread rather than once per point.

Changes:

  1. Added overloads of all five per-point wrappers that accept an already-initialized std::vector<xc_func_type>. The original func_id overloads are kept as thin wrappers that init, delegate, and finish, so existing callers and tests are unaffected.
  2. In gradcorr_xc_kernel, initialize the functional once per OpenMP thread (inside #pragma omp parallel, before the for loop) and reuse it for all grid points handled by that thread.
  3. In v_xc's nspin==4 branch, split #pragma omp parallel for into a parallel block that initializes the functional once per thread followed by the for loop.
  4. Added a std::atomic counter in init_func with get_and_reset_init_count(), exposed via libxc_abacus.h, so unit tests can assert that per-point paths do not re-initialize the functional.
  5. Added unit tests verifying that the new funcs overloads produce bit-identical results to the func_id overloads for LDA, GGA, and mGGA, and that the init count is exactly 1 for the old overload and 0 for the new one.
    Result: init_func calls on the per-point paths drop from O(nrxx * SCF steps) to O(nthreads * SCF steps), eliminating the 3000+ calls observed in the issue. Physical results are unchanged because the functional evaluation itself is identical.

Verification:

  • Built with ENABLE_LIBXC=ON (libxc 7.0.0): make -j 30 exits with 0.
  • All 10 XC test executables pass: VXC (18), GRADCORR (3), PBE (23), PZ_SPN (14), SCANL_LAPL (5), HSE (9), SCAN (1), LAPL (4), NCGGA_RADIAL (9), NCGGA_DISCRETE_FD (40).
  • code_quality_score: 8/8 changed files pass (average 80.6, minimum 61, pass line 60).
    Note: this PR addresses only the per-point (A-class) redundant init_func calls. The remaining per-SCF-step init_func calls in v_xc_libxc and related whole-grid functions (B-class) are left for a follow-up, where a cached RAII functional handle can reduce them to once per calculation.

…ppers

issue deepmodeling#6286 reported that init_func was called thousands of times after
SCF convergence. The root cause is that the per-point libxc wrappers
(xc_spin_libxc, gcxc_libxc, gcxc_spin_libxc, tau_xc, tau_xc_spin)
called init_func -> evaluate(1 point) -> finish_func inside every grid
point of the OpenMP loops.

Since init_func only depends on the functional id, polarization, and
runtime parameters (alpha, omega) -- none of which vary across grid
points -- it only needs to run once per thread.

Changes:
- Add funcs-taking overloads to all five per-point wrappers; the old
  func_id overloads become thin wrappers that init/evaluate/finish.
- In gradcorr_xc_kernel, init the functional once per OpenMP thread
  (inside #pragma omp parallel, before the for) and reuse it for all
  points that thread handles.
- In v_xc's nspin==4 branch, split #pragma omp parallel for into
  parallel { init; for } so init runs once per thread.
- Add a std::atomic<int> init counter with get_and_reset_init_count()
  for tests to assert per-point paths do not re-initialize.
- Add unit tests: funcs-overload vs func_id-overload bit-equality for
  LDA/GGA/mGGA, and an init-count assertion (old=1, new=0).

init_func call count on the per-point paths drops from O(nrxx * SCF)
to O(nthreads * SCF). No change to physical results.

Verification: make -j 30 (ENABLE_LIBXC=ON, libxc 7.0.0) exit 0; all
10 XC test executables pass (VXC 18, GRADCORR 3, PBE 23, PZ_SPN 14,
SCANL_LAPL 5, HSE 9, SCAN 1, LAPL 4, NCGGA_RADIAL 9, NCGGA_DISCRETE_FD 40);
code_quality_score 8/8 files pass (avg 80.6, min 61).
@mohanchen
mohanchen requested a review from Critsium-xy October 6, 2026 03:29
@mohanchen mohanchen added Refactor Refactor ABACUS codes The Absolute Zero Reduce the "entropy" of the code to 0 labels Oct 6, 2026
@mohanchen mohanchen changed the title fix(module_xc): avoid per-grid-point libxc init_func in per-point wra… Fix #6286: avoid redundant libxc init_func calls in per-point evaluation paths Oct 6, 2026
@mohanchen mohanchen added the Bugs Bugs that only solvable with sufficient knowledge of DFT label Oct 6, 2026
Comment thread source/source_hamilt/module_xc/libxc_mgga_wrap.cpp Outdated
Comment thread source/source_hamilt/module_xc/test/test_xc2.cpp Outdated
Comment thread source/source_hamilt/module_xc/xc_pot.cpp Outdated
Comment thread source/source_hamilt/module_xc/xc_grad_kernel.cpp
Comment thread source/source_hamilt/module_xc/libxc_setup.cpp Outdated
Comment thread source/source_hamilt/module_xc/xc_grad_kernel.cpp Outdated
Comment thread source/source_hamilt/module_xc/libxc_gga_wrap.cpp
Comment thread source/source_hamilt/module_xc/libxc_gga_wrap.cpp Outdated
mohanchen and others added 9 commits October 9, 2026 17:16
…#8082)

- tau_xc/tau_xc_spin funcs overloads take hybrid_alpha explicitly
  instead of reading the XC_Functional global for SCAN0 scaling
- revert the unreachable use_libxc branch change in v_xc (nspin==4) and
  the now-unused xc_spin_libxc funcs overload
- drop the init_func call counter from the production path
- only init libxc in gradcorr when a libxc branch runs
  (nspin0 != 1 || is_stress)
- gcxc_libxc func_id overload returns before init_func for
  negligible rho/grho, as before
- remove const_cast: Libxc >= 5.1.7 takes const xc_func_type*
- replace self-comparison tests with a libxc gradcorr regression test
  (references from the per-point implementation) and a SCAN0 test
  checking the hybrid_alpha argument is used

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mohanchen
mohanchen merged commit 4bc459b into deepmodeling:develop Oct 10, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bugs Bugs that only solvable with sufficient knowledge of DFT Refactor Refactor ABACUS codes The Absolute Zero Reduce the "entropy" of the code to 0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Question: why there are more than 3000 calls of XC_Functional_Libxc::init_func after the SCF is converged?

2 participants