From a110fc515f0460c89b2ee8baeb74a32f80aefd3b Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 03:16:16 +0800 Subject: [PATCH 01/16] feat(xc): add libvdwxc-backed rVV10 adapter --- .github/workflows/test.yml | 5 + CMakeLists.txt | 52 +++++ cmake/CollectBuildInfoVars.cmake | 10 + docs/advanced/input_files/input-main.md | 25 +++ docs/advanced/install.md | 21 ++ docs/developers_guide/index.rst | 1 + docs/developers_guide/nonlocal_xc.md | 121 +++++++++++ docs/parameters.yaml | 24 +++ source/CMakeLists.txt | 15 +- source/Makefile.Objects | 2 + source/source_estate/CMakeLists.txt | 1 + .../module_pot/pot_nonlocal_xc.cpp | 68 +++++++ .../module_pot/pot_nonlocal_xc.h | 34 ++++ .../module_pot/potential_new.cpp | 30 ++- .../source_estate/module_pot/potential_new.h | 25 +++ .../module_pot/potential_types.cpp | 24 +++ source/source_estate/setup_estate_pw.cpp | 3 +- .../source_hamilt/module_vdw/CMakeLists.txt | 17 +- .../source_hamilt/module_vdw/nonlocal_xc.cpp | 33 +++ source/source_hamilt/module_vdw/nonlocal_xc.h | 46 +++++ .../module_vdw/nonlocal_xc_libvdwxc.cpp | 190 ++++++++++++++++++ .../module_vdw/nonlocal_xc_libvdwxc.h | 50 +++++ .../module_vdw/test/CMakeLists.txt | 28 ++- .../test/test_nonlocal_xc_libvdwxc.cpp | 108 ++++++++++ source/source_io/build_info.h.in | 1 + .../module_parameter/input_parameter.h | 3 + .../module_parameter/read_inp_estruc.cpp | 117 +++++++++++ source/source_io/parse_args.cpp | 1 + source/source_pw/module_pwdft/Makefile | 2 +- source/source_pw/module_pwdft/hamilt_pw.cpp | 5 + source/source_pw/module_pwdft/hamilt_pw.h | 1 + tests/CMakeLists.txt | 1 + tests/rvv10/CMakeLists.txt | 8 + tests/rvv10/README.md | 38 ++++ tests/rvv10/test_libvdwxc_contract.py | 71 +++++++ 35 files changed, 1166 insertions(+), 15 deletions(-) create mode 100644 docs/developers_guide/nonlocal_xc.md create mode 100644 source/source_estate/module_pot/pot_nonlocal_xc.cpp create mode 100644 source/source_estate/module_pot/pot_nonlocal_xc.h create mode 100644 source/source_hamilt/module_vdw/nonlocal_xc.cpp create mode 100644 source/source_hamilt/module_vdw/nonlocal_xc.h create mode 100644 source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp create mode 100644 source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h create mode 100644 source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp create mode 100644 tests/rvv10/CMakeLists.txt create mode 100644 tests/rvv10/README.md create mode 100644 tests/rvv10/test_libvdwxc_contract.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5a594489ffe..fbc48ceb1fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -115,6 +115,11 @@ jobs: cmake --build build -j8 cmake --install build + - name: rVV10 host contract + run: | + source toolchain/install/setup + ctest --test-dir build --output-on-failure --no-tests=error -R RVV10_LIBVDWXC_CONTRACT + - name: Report compiler cache if: always() run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 62843068f6d..b4f757017cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ option(USE_SW "Enable SW Architecture" OFF) option(ENABLE_ABACUS_LIBM "Build libmath from source to speed up" OFF) option(ENABLE_LIBXC "Enable using the LibXC package" OFF) +option(ENABLE_LIBVDWXC "Enable libvdwxc-backed nonlocal XC" OFF) option(ENABLE_FLOAT_FFTW "Enable using single-precision FFTW library." OFF) option(ENABLE_LCAO "Enable LCAO algorithm" ON) @@ -618,6 +619,57 @@ if(ENABLE_LIBXC) endif() endif() +if(ENABLE_LIBVDWXC) + if(NOT ENABLE_LIBXC) + message(FATAL_ERROR + "ENABLE_LIBVDWXC currently requires ENABLE_LIBXC=ON because the first " + "rVV10 interface uses Libxc for the semilocal RPW86+PBE part.") + endif() + # libvdwxc's rVV10 API is currently available from its development branch; + # keep discovery explicit so a normal build never acquires this dependency. + # The package target carries FFTW/PFFT and math transitive dependencies for + # static builds; do not replace it with a bare -lvdwxc lookup. + find_package(libvdwxc CONFIG REQUIRED) + if(NOT TARGET libvdwxc::vdwxc) + message(FATAL_ERROR + "ENABLE_LIBVDWXC requires the libvdwxc::vdwxc CMake target. " + "Use the libvdwxc development build that includes the rVV10 API.") + endif() + get_target_property(LIBVDWXC_INCLUDE_DIRS libvdwxc::vdwxc INTERFACE_INCLUDE_DIRECTORIES) + if(NOT LIBVDWXC_INCLUDE_DIRS) + message(FATAL_ERROR "libvdwxc::vdwxc does not expose its include directory") + endif() + # Installed packages expose a concrete include path, while build-tree + # targets may expose generator expressions. Strip those expressions before + # probing so configuration gives a useful error instead of a later include + # failure. + set(_libvdwxc_include_hints) + foreach(_include_dir IN LISTS LIBVDWXC_INCLUDE_DIRS) + string(GENEX_STRIP "${_include_dir}" _include_dir_stripped) + if(IS_DIRECTORY "${_include_dir_stripped}") + list(APPEND _libvdwxc_include_hints "${_include_dir_stripped}") + endif() + endforeach() + unset(LIBVDWXC_HEADER_DIR CACHE) + find_path(LIBVDWXC_HEADER_DIR NAMES vdwxc.h HINTS ${_libvdwxc_include_hints} + NO_DEFAULT_PATH) + if(NOT LIBVDWXC_HEADER_DIR) + message(FATAL_ERROR "libvdwxc::vdwxc does not provide vdwxc.h") + endif() + file(READ "${LIBVDWXC_HEADER_DIR}/vdwxc.h" LIBVDWXC_HEADER_TEXT) + if(NOT LIBVDWXC_HEADER_TEXT MATCHES "VDWXC_RVV10" + OR NOT LIBVDWXC_HEADER_TEXT MATCHES "vdwxc_new" + OR NOT LIBVDWXC_HEADER_TEXT MATCHES "vdwxc_set_rvv10_parameters" + OR NOT LIBVDWXC_HEADER_TEXT MATCHES "vdwxc_set_unit_cell" + OR NOT LIBVDWXC_HEADER_TEXT MATCHES "vdwxc_init_serial" + OR NOT LIBVDWXC_HEADER_TEXT MATCHES "vdwxc_calculate" + OR NOT LIBVDWXC_HEADER_TEXT MATCHES "vdwxc_finalize") + message(FATAL_ERROR + "The selected libvdwxc headers do not provide the complete serial rVV10 API; " + "use the pinned development build containing MR !32.") + endif() +endif() + if(DEFINED DeePMD_DIR) find_package(DeePMD REQUIRED) endif() diff --git a/cmake/CollectBuildInfoVars.cmake b/cmake/CollectBuildInfoVars.cmake index 740c50ca4b0..e60ec22f90d 100644 --- a/cmake/CollectBuildInfoVars.cmake +++ b/cmake/CollectBuildInfoVars.cmake @@ -181,6 +181,14 @@ else() set(ABACUS_DFTD4_VERSION "no") endif() +if(ENABLE_LIBVDWXC AND libvdwxc_VERSION) + set(ABACUS_LIBVDWXC_VERSION "yes (v${libvdwxc_VERSION})") +elseif(ENABLE_LIBVDWXC) + set(ABACUS_LIBVDWXC_VERSION "yes (version unknown)") +else() + set(ABACUS_LIBVDWXC_VERSION "no") +endif() + # Accelerators if(USE_CUDA AND CUDAToolkit_VERSION) set(ABACUS_CUDA_VERSION "yes (v${CUDAToolkit_VERSION})") @@ -382,6 +390,7 @@ list(APPEND CMAKE_OPTIONS_LIST " USE_ROCM=${USE_ROCM}") list(APPEND CMAKE_OPTIONS_LIST " ENABLE_LCAO=${ENABLE_LCAO}") list(APPEND CMAKE_OPTIONS_LIST " ENABLE_ELPA=${ENABLE_ELPA}") list(APPEND CMAKE_OPTIONS_LIST " ENABLE_LIBXC=${ENABLE_LIBXC}") +list(APPEND CMAKE_OPTIONS_LIST " ENABLE_LIBVDWXC=${ENABLE_LIBVDWXC}") list(APPEND CMAKE_OPTIONS_LIST " ENABLE_MLALGO=${ENABLE_MLALGO}") list(APPEND CMAKE_OPTIONS_LIST " ENABLE_ASAN=${ENABLE_ASAN}") list(APPEND CMAKE_OPTIONS_LIST " BUILD_TESTING=${BUILD_TESTING}") @@ -396,6 +405,7 @@ list(APPEND CMAKE_FIND_PACKAGES_LIST " CUDA Found=${CUDA_FOUND}") list(APPEND CMAKE_FIND_PACKAGES_LIST " ROCm Found=${ROCM_FOUND}") list(APPEND CMAKE_FIND_PACKAGES_LIST " ELPA Found=${ELPA_FOUND}") list(APPEND CMAKE_FIND_PACKAGES_LIST " Libxc Found=${Libxc_FOUND}") +list(APPEND CMAKE_FIND_PACKAGES_LIST " libvdwxc Found=${libvdwxc_FOUND}") list(APPEND CMAKE_FIND_PACKAGES_LIST " PEXSI Found=${PEXSI_FOUND}") list(APPEND CMAKE_FIND_PACKAGES_LIST " LibRI Found=${LIBRI_FOUND}") foreach(package_line ${CMAKE_FIND_PACKAGES_LIST}) diff --git a/docs/advanced/input_files/input-main.md b/docs/advanced/input_files/input-main.md index cc3ab348c08..a46323df9e9 100644 --- a/docs/advanced/input_files/input-main.md +++ b/docs/advanced/input_files/input-main.md @@ -90,6 +90,9 @@ - [nelec\_delta](#nelec_delta) - [nupdown](#nupdown) - [dft\_functional](#dft_functional) + - [xc\_nonlocal](#xc_nonlocal) + - [rvv10\_b](#rvv10_b) + - [rvv10\_c](#rvv10_c) - [xc\_temperature](#xc_temperature) - [xc\_exch\_ext](#xc_exch_ext) - [xc\_corr\_ext](#xc_corr_ext) @@ -1336,6 +1339,28 @@ The other way is only available when compiling with LIBXC, and it allows for supplying exchange-correlation functionals as combinations of LIBXC keywords for functional components, joined by a plus sign, for example, dft_functional='LDA_X_1D_EXPONENTIAL+LDA_C_1D_CSC'. - **Default**: Used the same as DFT functional as specified in the pseudopotential files. +### xc_nonlocal + +- **Type**: String +- **Description**: Select an optional libvdwxc-backed nonlocal exchange-correlation correction. `none` disables the correction. The current rVV10 interface requires a CMake build with `ENABLE_LIBXC=ON` and `ENABLE_LIBVDWXC=ON`, `dft_functional=GGA_X_RPW86+GGA_C_PBE`, fixed b=6.3 and C=0.0093, and a single-process, CPU double-precision, plane-wave, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, LCAO, GPU, reduced-gamma layouts, ultrasoft/PAW pseudopotentials, spin-polarized calculations and pairwise vdW corrections are not part of this interface. +- **Default**: none + +### rvv10_b + +- **Type**: Real +- **Availability**: *[`xc_nonlocal`](#xc_nonlocal)==rvv10* +- **Description**: Fixed damping parameter b=6.3 for the current libvdwxc-backed `GGA_X_RPW86+GGA_C_PBE` rVV10 interface. +- **Default**: 6.3 +- **Unit**: dimensionless + +### rvv10_c + +- **Type**: Real +- **Availability**: *[`xc_nonlocal`](#xc_nonlocal)==rvv10* +- **Description**: Fixed parameter C=0.0093 for the current libvdwxc-backed `GGA_X_RPW86+GGA_C_PBE` rVV10 interface. +- **Default**: 0.0093 +- **Unit**: dimensionless + ### xc_temperature - **Type**: Real diff --git a/docs/advanced/install.md b/docs/advanced/install.md index ab1939181be..c1271f2212b 100644 --- a/docs/advanced/install.md +++ b/docs/advanced/install.md @@ -99,6 +99,27 @@ vdw_d4_model d4 # or d4s for the smooth D4S model If `vdw_d4_xc` is set to `default`, ABACUS will infer the functional name from `dft_functional` or pseudopotential metadata and pass it to the DFT-D4 library. The `vdw_d4_model` keyword selects the dispersion model inside the DFT-D4 library; the default is `d4`, while `d4s` enables the smooth D4S model. +## Build with libvdwxc-backed rVV10 support + +The first rVV10 integration uses the external [libvdwxc](https://gitlab.com/libvdwxc/libvdwxc) library for the nonlocal kernel. The current rVV10 API is under review in Draft MR !32, so this option is intended for development and reproducible integration testing rather than a stable release build. + +Build and install the pinned libvdwxc development branch first (MR !32, +`rvv10-cmake`, commit +`91ef4820f3270e22b35fddc2ab4cab8efa4b6d79`), then pass its CMake prefix to +ABACUS together with Libxc: + +```bash +cmake -B build-libvdwxc -DBUILD_TESTING=ON \ + -DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON \ + -DCMAKE_PREFIX_PATH=/path/to/libvdwxc-prefix +``` + +The first host adapter is limited to CPU, double-precision, full-complex PW, +one process, `nspin=1`, norm-conserving pseudopotentials and SCF energy/potential +evaluation. It is not available through the legacy Makefile. See +`docs/developers_guide/nonlocal_xc.md` for the exact dependency commit and +runtime restrictions. + ## Build with JSON support ABACUS uses [nlohmann-json](https://github.com/nlohmann/json) for optional JSON output. To build ABACUS with JSON support, pass `-DENABLE_JSON=ON` to CMake. diff --git a/docs/developers_guide/index.rst b/docs/developers_guide/index.rst index 80224240428..9cba6711c10 100644 --- a/docs/developers_guide/index.rst +++ b/docs/developers_guide/index.rst @@ -12,3 +12,4 @@ This section provides guidelines and resources for developers working on the ABA basic_types_class.md input_availability.md agent_governance.md + nonlocal_xc.md diff --git a/docs/developers_guide/nonlocal_xc.md b/docs/developers_guide/nonlocal_xc.md new file mode 100644 index 00000000000..7d079112e59 --- /dev/null +++ b/docs/developers_guide/nonlocal_xc.md @@ -0,0 +1,121 @@ +# Density-grid nonlocal exchange-correlation + +ABACUS treats a density-grid nonlocal correction as an additive component next +to the ordinary semilocal XC term. The first implementation uses the rVV10 +backend from libvdwxc; ABACUS does not carry a second rVV10 kernel. + +## Input and declared scope + +The input is deliberately separate from the semilocal functional: + +```text +dft_functional GGA_X_RPW86+GGA_C_PBE +xc_nonlocal rvv10 +rvv10_b 6.3 +rvv10_c 0.0093 +``` + +The first adapter requires both Libxc and libvdwxc at build time. It accepts a +CPU, double-precision, full-complex plane-wave SCF calculation with one MPI +rank, `nspin=1`, and norm-conserving pseudopotentials. It returns the nonlocal +energy, the local potential, and the valence `vtxc` contraction. The parser +rejects forces, stress, relaxation, molecular dynamics, LCAO, GPU, gamma-only +layouts, spin-polarized inputs, ultrasoft/PAW pseudopotentials, MPI runs with +more than one rank, and pairwise D2/D3/D4 corrections. These are follow-up +interfaces, not silently approximated results. + +`xc_nonlocal=rvv10` must not be encoded as `dft_functional=RVV10`, and it must +not be combined with a semilocal functional that already contains a nonlocal +vdW term. The existing pairwise `vdw_method` path remains independent and is +mutually rejected by the input check. + +## Host/backend boundary + +`Potential` registers the existing semilocal `xc` component first. If +`xc_nonlocal` is enabled, it then registers the generic `xc_nonlocal` +component. `PotNonlocalXC` only adds the backend's energy, potential and +`vtxc`; it never replaces or recomputes the semilocal term. + +The host-side interface lives in +`source/source_hamilt/module_vdw/nonlocal_xc.h`. The only rVV10-specific +implementation in ABACUS is +`nonlocal_xc_libvdwxc.{h,cpp}`. The adapter reuses +`PW_Basis::real2recip`, `XC_Functional::grad_rho` and +`XC_Functional::grad_dot`; libvdwxc owns the kernel table, interpolation and +nonlocal convolution. + +For one SCF update the data path is: + +```text +rho_total = rho_valence + rho_core + -> grad(rho_total), sigma = |grad(rho_total)|^2 + -> libvdwxc rVV10 energy, dE/dn and dE/dsigma + -> ABACUS grad_dot for div(2*dE/dsigma * grad(rho_total)) + -> v_nl in Ry, etxc/vtxc accumulation, and addition to every spin channel +``` + +The library already includes the rVV10 beta compensation. ABACUS therefore +does not add beta a second time. The total density is used by the backend; +`vtxc` is contracted with the valence density, matching the host XC energy +bookkeeping. + +## CMake and dependency contract + +Enable the optional backend with a CMake package that exports +`libvdwxc::vdwxc`: + +```bash +cmake -B build-libvdwxc -DBUILD_TESTING=ON \ + -DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON \ + -DCMAKE_PREFIX_PATH=/path/to/libvdwxc-prefix +``` + +The current upstream MR is still Draft. The integration was checked against +the `rvv10-cmake` branch at commit +`91ef4820f3270e22b35fddc2ab4cab8efa4b6d79`; a PR must keep this pin (or a +newer reviewed commit), record the dependency's CTest result, and never call +the Draft API a stable release. Configuration verifies the imported target, +the header, and every serial API symbol used by the adapter. Legacy Makefile +builds do not provide this optional dependency; `xc_nonlocal=rvv10` fails with +a clear input diagnostic there. + +The feature is included in the CMake build summary as `libvdwxc`. The adapter +is attached to `source_hamilt/module_vdw`, not to the semilocal XC evaluator or +to a special rVV10 branch in the generic SCF solver. + +## Numerical and layout contract + +The serial API receives an ABACUS full real-space grid with z as the fastest +index. The unit-cell matrix is passed column by column, and the adapter checks +the grid dimensions, cell identity, precision, device, gamma flag, rank count, +density sizes and finite values before calling the library. It forms +`sigma=|grad(n_total)|^2`, converts the library Hartree outputs to ABACUS Ry, +and uses the same reciprocal-space projection for the gradient and divergence. + +The adapter is intentionally not thread-safe through a shared evaluator object; +one evaluator belongs to one potential instance. MPI/PFFT ownership, spin +interfaces, force/stress derivatives and GPU/LCAO data layouts need separate +API and numerical tests before being enabled. + +## Verification + +The host-side Python contract test checks the CMake switch, imported target, +required API symbols and the absence of a duplicate in-tree rVV10 kernel. With +the pinned library installed, the GoogleTest +`MODULE_HAMILT_vdwTest_LIBVDWXC` compares the adapter with a direct serial +libvdwxc call for a nonuniform density and an oblique cell. It checks the +energy, the host-assembled `dedn - div(2*dedsigma*grad(n))` potential, and the +valence `vtxc` contraction. + +Before converting a Draft PR to Ready, run both libraries' tests and add an +ABACUS SCF reference case with the exact executable, pseudopotential hashes, +input, dependency commit, compiler, FFT provider, and numerical tolerances. +The current local environment cannot provide a complete CMake/libvdwxc build; +therefore this change remains a Draft until that evidence is attached. + +## Follow-up work + +The next PRs can add a libvdwxc MPI/PFFT adapter, ordinary spin, GPU/LCAO +paths, forces and stress, and functional-specific parameter tables. Each +extension should add its own layout, unit, finite-difference and reference +tests before loosening the input checks. diff --git a/docs/parameters.yaml b/docs/parameters.yaml index 323336bffe2..2e59feba5e3 100644 --- a/docs/parameters.yaml +++ b/docs/parameters.yaml @@ -668,6 +668,30 @@ parameters: default_value: Used the same as DFT functional as specified in the pseudopotential files. unit: "" availability: "" + - name: xc_nonlocal + category: Electronic structure + type: String + description: | + Select an optional libvdwxc-backed nonlocal exchange-correlation correction. `none` disables the correction. The current rVV10 interface requires a CMake build with `ENABLE_LIBXC=ON` and `ENABLE_LIBVDWXC=ON`, `dft_functional=GGA_X_RPW86+GGA_C_PBE`, fixed b=6.3 and C=0.0093, and a single-process, CPU double-precision, plane-wave, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, LCAO, GPU, reduced-gamma layouts, ultrasoft/PAW pseudopotentials, spin-polarized calculations and pairwise vdW corrections are not part of this interface. + default_value: none + unit: "" + availability: "" + - name: rvv10_b + category: Electronic structure + type: Real + description: | + Fixed damping parameter b=6.3 for the current libvdwxc-backed `GGA_X_RPW86+GGA_C_PBE` rVV10 interface. + default_value: "6.3" + unit: dimensionless + availability: xc_nonlocal==rvv10 + - name: rvv10_c + category: Electronic structure + type: Real + description: | + Fixed parameter C=0.0093 for the current libvdwxc-backed `GGA_X_RPW86+GGA_C_PBE` rVV10 interface. + default_value: "0.0093" + unit: dimensionless + availability: xc_nonlocal==rvv10 - name: xc_temperature category: Electronic structure type: Real diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 05ee4118ee5..d98323f4065 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -66,6 +66,7 @@ set(_abacus_feature_definitions $<$:TEST_EXX_LCAO=0> $<$:TEST_EXX_RADIAL=1> $<$:__LIBXC> + $<$:__LIBVDWXC> $<$:__DFTD4> $<$:GATHER_INFO>) @@ -270,10 +271,15 @@ set(_abacus_feature_libs $<$:ZLIB::ZLIB> $<$:cereal::cereal> $<$:Libxc::xc> - $<$:PLUMED::plumed>) + $<$:PLUMED::plumed> + $<$:libvdwxc::vdwxc>) set(_abacus_feature_include_dirs) set(_abacus_feature_compile_options) +if(ENABLE_LIBVDWXC) + list(APPEND _abacus_feature_include_dirs ${LIBVDWXC_HEADER_DIR}) +endif() + if(ENABLE_ELPA) list(APPEND _abacus_feature_include_dirs ${ELPA_INCLUDE_DIR}) endif() @@ -392,6 +398,7 @@ foreach(_abacus_feature_target IN ITEMS cereal::cereal ELPA::ELPA Libxc::xc + libvdwxc::vdwxc dftd4::dftd4 DeePMD::deepmd_c DeePMD::deepmd_cc @@ -556,6 +563,11 @@ add_subdirectory(source_psi) add_subdirectory(source_estate) add_subdirectory(source_hamilt) add_subdirectory(source_pw) +if(BUILD_TESTING) + # Register the nonlocal XC tests once; CMake resolves their PW dependencies + # after all source subdirectories have been processed. + add_subdirectory(source_hamilt/module_vdw/test) +endif() add_subdirectory(source_lcao) add_subdirectory(source_hsolver) add_subdirectory(source_basis/module_ao) @@ -737,6 +749,7 @@ target_link_libraries( module_stodft module_dfpt xc_ + nonlocal_xc vdw relax io_advanced diff --git a/source/Makefile.Objects b/source/Makefile.Objects index 794de412d74..2607ce4ddc4 100644 --- a/source/Makefile.Objects +++ b/source/Makefile.Objects @@ -290,6 +290,7 @@ OBJS_ELECSTAT=elecstate.o\ h_hartree_pw.o\ h_tddft_pw.o\ pot_xc.o\ + pot_nonlocal_xc.o\ pot_ml_exx.o\ pot_ml_exx_label.o\ pot_xc_fdm.o\ @@ -1013,6 +1014,7 @@ OBJS_SRCPW=h_ewald_pw.o\ vsep_pw.o OBJS_VDW=vdw.o\ + nonlocal_xc.o\ vdwd2_parameters.o\ vdwd3_parameters.o\ vdwd2.o\ diff --git a/source/source_estate/CMakeLists.txt b/source/source_estate/CMakeLists.txt index 15c09ac3ce1..dc2a7a9977b 100644 --- a/source/source_estate/CMakeLists.txt +++ b/source/source_estate/CMakeLists.txt @@ -19,6 +19,7 @@ list(APPEND objects module_pot/h_hartree_pw.cpp module_pot/h_tddft_pw.cpp module_pot/pot_xc.cpp + module_pot/pot_nonlocal_xc.cpp module_pot/pot_local.cpp module_pot/pot_pcc.cpp module_pot/potential_new.cpp diff --git a/source/source_estate/module_pot/pot_nonlocal_xc.cpp b/source/source_estate/module_pot/pot_nonlocal_xc.cpp new file mode 100644 index 00000000000..5d3c593b3b9 --- /dev/null +++ b/source/source_estate/module_pot/pot_nonlocal_xc.cpp @@ -0,0 +1,68 @@ +#include "pot_nonlocal_xc.h" + +#include "source_hamilt/module_vdw/nonlocal_xc.h" +#include "source_base/timer.h" +#include "source_base/tool_quit.h" + +#include +#include + +namespace elecstate +{ +PotNonlocalXC::PotNonlocalXC(const ModulePW::PW_Basis* density_basis, + double* etxc, + double* vtxc, + const std::string& method, + double b, + double c) + : etxc_(etxc), vtxc_(vtxc) +{ + backend_ = vdw::make_nonlocal_xc(density_basis, method, b, c); + rho_basis_ = density_basis; + dynamic_mode = true; + fixed_mode = false; +} + +PotNonlocalXC::~PotNonlocalXC() = default; + +void PotNonlocalXC::cal_v_eff(const Charge* chg, const UnitCell* cell, ModuleBase::matrix& v_eff) +{ + ModuleBase::TITLE("PotNonlocalXC", "cal_veff"); + ModuleBase::timer::start("PotNonlocalXC", "cal_veff"); + if (!chg || !cell || !rho_basis_ || chg->rhopw != rho_basis_ || (chg->nspin != 1 && chg->nspin != 2) + || chg->nrxx != rho_basis_->nrxx || !chg->rho || !chg->rho_core || v_eff.nr != chg->nspin + || v_eff.nc != chg->nrxx) + { + ModuleBase::WARNING_QUIT("PotNonlocalXC", "nonlocal XC requires a consistent nspin=1 or nspin=2 density basis"); + } + std::vector valence(chg->nrxx, 0.0); + for (int is = 0; is < chg->nspin; ++is) + for (int r = 0; r < chg->nrxx; ++r) + valence[r] += chg->rho[is][r]; + std::vector total = valence; + for (int r = 0; r < chg->nrxx; ++r) + total[r] += chg->rho_core[r]; + vdw::NonlocalXCResult nl = {0.0, 0.0, std::vector()}; + try + { + nl = backend_->evaluate(*rho_basis_, total, valence); + } + catch (const std::exception& error) + { + ModuleBase::WARNING_QUIT("PotNonlocalXC", error.what()); + } + if (nl.potential.size() != static_cast(chg->nrxx)) + { + ModuleBase::WARNING_QUIT("PotNonlocalXC", "nonlocal XC backend returned an invalid potential size"); + } + + // PotXC is registered before this component and owns the semilocal + // contribution. This component only adds the selected nonlocal correction. + *etxc_ += nl.energy; + *vtxc_ += nl.vtxc; + for (int is = 0; is < chg->nspin; ++is) + for (int r = 0; r < chg->nrxx; ++r) + v_eff(is, r) += nl.potential[r]; + ModuleBase::timer::end("PotNonlocalXC", "cal_veff"); +} +} // namespace elecstate diff --git a/source/source_estate/module_pot/pot_nonlocal_xc.h b/source/source_estate/module_pot/pot_nonlocal_xc.h new file mode 100644 index 00000000000..feafc824bd5 --- /dev/null +++ b/source/source_estate/module_pot/pot_nonlocal_xc.h @@ -0,0 +1,34 @@ +#ifndef ABACUS_POT_NONLOCAL_XC_H +#define ABACUS_POT_NONLOCAL_XC_H +#include "pot_base.h" + +#include +#include + +namespace vdw +{ +class NonlocalXC; +} + +namespace elecstate +{ +// Independent rVV10 nonlocal correction for the CPU/PW SCF path. +class PotNonlocalXC final : public PotBase +{ + public: + PotNonlocalXC(const ModulePW::PW_Basis* density_basis, + double* etxc, + double* vtxc, + const std::string& method, + double b, + double c); + ~PotNonlocalXC() override; + void cal_v_eff(const Charge* chg, const UnitCell* cell, ModuleBase::matrix& v_eff) override; + + private: + double* etxc_; + double* vtxc_; + std::unique_ptr backend_; +}; +} // namespace elecstate +#endif // ABACUS_POT_NONLOCAL_XC_H diff --git a/source/source_estate/module_pot/potential_new.cpp b/source/source_estate/module_pot/potential_new.cpp index b23b8710bb5..73cb7f242cc 100644 --- a/source/source_estate/module_pot/potential_new.cpp +++ b/source/source_estate/module_pot/potential_new.cpp @@ -24,8 +24,36 @@ Potential::Potential(const ModulePW::PW_Basis* rho_basis_in, double* etxc_in, double* vtxc_in, VSep* vsep_cell_in) + : Potential(rho_basis_in, + rho_basis_smooth_in, + ucell_in, + vloc_in, + structure_factors_in, + solvent_in, + etxc_in, + vtxc_in, + vsep_cell_in, + 6.3, + 0.0093, + "none") +{ +} + +Potential::Potential(const ModulePW::PW_Basis* rho_basis_in, + const ModulePW::PW_Basis* rho_basis_smooth_in, + const UnitCell* ucell_in, + const ModuleBase::matrix* vloc_in, + Structure_Factor* structure_factors_in, + surchem* solvent_in, + double* etxc_in, + double* vtxc_in, + VSep* vsep_cell_in, + double nonlocal_xc_b_in, + double nonlocal_xc_c_in, + const std::string& nonlocal_xc_in) : ucell_(ucell_in), vloc_(vloc_in), structure_factors_(structure_factors_in), solvent_(solvent_in), vsep_cell(vsep_cell_in), - etxc_(etxc_in), vtxc_(vtxc_in) + etxc_(etxc_in), vtxc_(vtxc_in), nonlocal_xc_b_(nonlocal_xc_b_in), nonlocal_xc_c_(nonlocal_xc_c_in), + nonlocal_xc_(nonlocal_xc_in) { this->rho_basis_ = rho_basis_in; this->rho_basis_smooth_ = rho_basis_smooth_in; diff --git a/source/source_estate/module_pot/potential_new.h b/source/source_estate/module_pot/potential_new.h index e1af8269816..177365c8e1a 100644 --- a/source/source_estate/module_pot/potential_new.h +++ b/source/source_estate/module_pot/potential_new.h @@ -8,6 +8,7 @@ #include "source_pw/module_pwdft/vsep_pw.h" #include +#include #include namespace elecstate @@ -67,6 +68,21 @@ class Potential : public PotBase double* etxc_in, double* vtxc_in, VSep* vsep_cell_in = nullptr); + // Constructor used when a density-based nonlocal XC correction is + // selected. Keeping the method in the immutable Potential configuration + // avoids a mutable workflow switch after construction. + Potential(const ModulePW::PW_Basis* rho_basis_in, + const ModulePW::PW_Basis* rho_basis_smooth_in, + const UnitCell* ucell_in, + const ModuleBase::matrix* vloc_in, + Structure_Factor* structure_factors_in, + surchem* solvent_in, + double* etxc_in, + double* vtxc_in, + VSep* vsep_cell_in, + double nonlocal_xc_b_in, + double nonlocal_xc_c_in, + const std::string& nonlocal_xc_in); ~Potential(); // initialize potential when SCF begin @@ -189,6 +205,12 @@ class Potential : public PotBase { return this->rho_basis_; } + + bool has_nonlocal_xc() const + { + return !nonlocal_xc_.empty() && nonlocal_xc_ != "none"; + } + const UnitCell* get_ucell() const { return this->ucell_; @@ -259,6 +281,9 @@ class Potential : public PotBase Structure_Factor* structure_factors_ = nullptr; surchem* solvent_ = nullptr; VSep* vsep_cell = nullptr; + double nonlocal_xc_b_ = 6.3; + double nonlocal_xc_c_ = 0.0093; + std::string nonlocal_xc_ = "none"; bool use_gpu_ = false; std::shared_ptr td_field_manager_; }; diff --git a/source/source_estate/module_pot/potential_types.cpp b/source/source_estate/module_pot/potential_types.cpp index 4280bb8210f..f154767ce57 100644 --- a/source/source_estate/module_pot/potential_types.cpp +++ b/source/source_estate/module_pot/potential_types.cpp @@ -3,6 +3,7 @@ #include "gatefield.h" #include "pot_local.h" #include "pot_pcc.h" +#include "pot_nonlocal_xc.h" #include "pot_sep.h" #include "pot_surchem.h" #include "pot_xc.h" @@ -14,6 +15,7 @@ #include "source_base/tool_title.h" #include "source_io/module_parameter/parameter.h" #include "h_tddft_pw.h" + #ifdef __MLALGO #include "pot_ml_exx.h" #endif @@ -47,6 +49,28 @@ PotBase* Potential::get_pot_type(const std::string& pot_type) const int open_axis = input.pcc_2d_axis; return new PotPcc(this->rho_basis_, PotPcc::Dimension::slab, open_axis, electron_count); } + else if (pot_type == "xc_nonlocal") + { + // USPP can use a single density grid, so pointer equality does not + // determine the pseudopotential type. + for (int it = 0; it < this->ucell_->ntype; ++it) + { + if (this->ucell_->atoms[it].ncpp.tvanp) + { + ModuleBase::WARNING_QUIT("Potential", "nonlocal XC currently supports norm-conserving pseudopotentials only"); + } + } + if (this->rho_basis_ != this->rho_basis_smooth_) + { + ModuleBase::WARNING_QUIT("Potential", "nonlocal XC currently requires the same smooth and dense density basis"); + } + return new PotNonlocalXC(this->rho_basis_, + this->etxc_, + this->vtxc_, + this->nonlocal_xc_, + this->nonlocal_xc_b_, + this->nonlocal_xc_c_); + } else if (pot_type == "surchem") { return new PotSurChem(this->rho_basis_, this->structure_factors_, this->v_eff_fixed.data(), this->solvent_); diff --git a/source/source_estate/setup_estate_pw.cpp b/source/source_estate/setup_estate_pw.cpp index 9e045377cf2..a134715f882 100644 --- a/source/source_estate/setup_estate_pw.cpp +++ b/source/source_estate/setup_estate_pw.cpp @@ -103,7 +103,8 @@ void setup_estate_pw_impl( { pelec->pot = new elecstate::Potential(pw_rhod, pw_rho, &ucell, &locpp.vloc, &sf, - &solvent, &(pelec->f_en.etxc), &(pelec->f_en.vtxc), vsep_cell); + &solvent, &(pelec->f_en.etxc), &(pelec->f_en.vtxc), vsep_cell, + inp.rvv10_b, inp.rvv10_c, inp.xc_nonlocal); } locpp.init_vloc(ucell, pw_rhod); diff --git a/source/source_hamilt/module_vdw/CMakeLists.txt b/source/source_hamilt/module_vdw/CMakeLists.txt index e51299da04d..dc5f1286832 100644 --- a/source/source_hamilt/module_vdw/CMakeLists.txt +++ b/source/source_hamilt/module_vdw/CMakeLists.txt @@ -15,6 +15,17 @@ endif() add_library(vdw OBJECT ${vdw_sources}) +# Density-grid nonlocal XC has a different dependency and data model from the +# atom-pair D2/D3/D4 correction. Keep it in a separate object target so the +# existing pairwise-vdW unit tests do not acquire PW/XC link dependencies. +set(nonlocal_xc_sources nonlocal_xc.cpp) + +if(ENABLE_LIBVDWXC) + list(APPEND nonlocal_xc_sources nonlocal_xc_libvdwxc.cpp) +endif() + +add_library(nonlocal_xc OBJECT ${nonlocal_xc_sources}) + if(ENABLE_DFTD4) target_link_libraries(vdw PUBLIC dftd4::dftd4) endif() @@ -22,9 +33,3 @@ endif() if(ENABLE_COVERAGE) add_coverage(vdw) endif() - -if(BUILD_TESTING) - if(ENABLE_MPI) - add_subdirectory(test) - endif() -endif() diff --git a/source/source_hamilt/module_vdw/nonlocal_xc.cpp b/source/source_hamilt/module_vdw/nonlocal_xc.cpp new file mode 100644 index 00000000000..8ae9f544478 --- /dev/null +++ b/source/source_hamilt/module_vdw/nonlocal_xc.cpp @@ -0,0 +1,33 @@ +#include "nonlocal_xc.h" +#ifdef __LIBVDWXC +#include "nonlocal_xc_libvdwxc.h" +#endif + +#include "source_base/tool_quit.h" + +namespace vdw +{ + +std::unique_ptr make_nonlocal_xc(const ModulePW::PW_Basis* pw, + const std::string& method, + double b, + double c) +{ + if (method == "rvv10") + { +#ifdef __LIBVDWXC + if (pw == nullptr) + { + ModuleBase::WARNING_QUIT("NonlocalXC", "libvdwxc rVV10 requires a density PW basis"); + } + return std::unique_ptr(new LibvdwxcNonlocalXC(*pw, b, c)); +#else + ModuleBase::WARNING_QUIT("NonlocalXC", "xc_nonlocal=rvv10 requires a CMake build with ENABLE_LIBVDWXC=ON"); +#endif + } + + ModuleBase::WARNING_QUIT("NonlocalXC", "Unsupported nonlocal XC method: " + method); + return nullptr; +} + +} // namespace vdw diff --git a/source/source_hamilt/module_vdw/nonlocal_xc.h b/source/source_hamilt/module_vdw/nonlocal_xc.h new file mode 100644 index 00000000000..4941edfbc15 --- /dev/null +++ b/source/source_hamilt/module_vdw/nonlocal_xc.h @@ -0,0 +1,46 @@ +#ifndef ABACUS_NONLOCAL_XC_H +#define ABACUS_NONLOCAL_XC_H + +#include +#include +#include + +namespace ModulePW +{ +class PW_Basis; +} + +namespace vdw +{ + +struct NonlocalXCResult +{ + double energy; + double vtxc; + std::vector potential; +}; + +/** + * @brief Density-grid interface for a nonlocal exchange-correlation backend. + * + * The host potential code only knows this interface. Concrete methods such as + * rVV10, and future libvdwxc-backed methods, remain in the vdW module. + */ +class NonlocalXC +{ + public: + virtual ~NonlocalXC() = default; + + virtual NonlocalXCResult evaluate(const ModulePW::PW_Basis& pw, + const std::vector& total_density, + const std::vector& valence_density) const = 0; +}; + +std::unique_ptr make_nonlocal_xc(const ModulePW::PW_Basis* pw, + const std::string& method, + double b, + double c); + +} // namespace vdw + +#endif // ABACUS_NONLOCAL_XC_H diff --git a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp new file mode 100644 index 00000000000..7aeb7fe3162 --- /dev/null +++ b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp @@ -0,0 +1,190 @@ +#include "nonlocal_xc_libvdwxc.h" + +#ifdef __LIBVDWXC + +#include "source_basis/module_pw/pw_basis.h" +#include "source_hamilt/module_xc/xc_functional.h" + +#include +#include +#include +#include +#include + +namespace vdw +{ +namespace +{ +void check_serial_basis(const ModulePW::PW_Basis& pw) +{ + if (pw.gamma_only || pw.get_device() != "cpu" || pw.get_precision() != "double" || pw.poolnproc != 1 + || pw.nrxx != pw.nxyz || pw.nx <= 0 || pw.ny <= 0 || pw.nz <= 0 || pw.nxyz <= 0 || pw.npw <= 0 + || !std::isfinite(pw.omega) || pw.omega <= 0.0 || !std::isfinite(pw.tpiba) || pw.tpiba <= 0.0) + { + throw std::invalid_argument("libvdwxc rVV10 requires one-process CPU/double full-complex PW density grid"); + } +} + +std::array cell_columns(const ModulePW::PW_Basis& pw) +{ + const auto& a = pw.latvec; + return {a.e11 * pw.lat0, + a.e21 * pw.lat0, + a.e31 * pw.lat0, + a.e12 * pw.lat0, + a.e22 * pw.lat0, + a.e32 * pw.lat0, + a.e13 * pw.lat0, + a.e23 * pw.lat0, + a.e33 * pw.lat0}; +} + +bool same_cell(const std::array& lhs, const std::array& rhs) +{ + for (std::size_t i = 0; i < lhs.size(); ++i) + { + const double scale = std::max({1.0, std::abs(lhs[i]), std::abs(rhs[i])}); + if (std::abs(lhs[i] - rhs[i]) > 1.0e-12 * scale) + { + return false; + } + } + return true; +} +} // namespace + +LibvdwxcNonlocalXC::LibvdwxcNonlocalXC(const ModulePW::PW_Basis& pw, double b, double c) +{ + check_serial_basis(pw); + data_ = vdwxc_new(VDWXC_RVV10); + if (data_ == nullptr) + { + throw std::runtime_error("libvdwxc failed to create an rVV10 evaluator"); + } + if (vdwxc_set_rvv10_parameters(data_, b, c) != 0) + { + vdwxc_finalize(&data_); + throw std::invalid_argument("libvdwxc rejected the rVV10 b/C parameters"); + } + + cell_ = cell_columns(pw); + // libvdwxc names the arguments by matrix columns (C00,C10,C20,...). + vdwxc_set_unit_cell(data_, + pw.nx, + pw.ny, + pw.nz, + cell_[0], + cell_[1], + cell_[2], + cell_[3], + cell_[4], + cell_[5], + cell_[6], + cell_[7], + cell_[8]); + vdwxc_init_serial(data_); + nx_ = pw.nx; + ny_ = pw.ny; + nz_ = pw.nz; + omega_ = pw.omega; +} + +LibvdwxcNonlocalXC::~LibvdwxcNonlocalXC() +{ + if (data_ != nullptr) + { + vdwxc_finalize(&data_); + } +} + +NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, + const std::vector& total_density, + const std::vector& valence_density) const +{ + check_serial_basis(pw); + if (pw.nx != nx_ || pw.ny != ny_ || pw.nz != nz_ || !same_cell(cell_, cell_columns(pw)) + || std::abs(pw.omega - omega_) > 1.0e-12 * std::max({1.0, std::abs(pw.omega), std::abs(omega_)}) + || total_density.size() != static_cast(pw.nrxx) + || valence_density.size() != total_density.size()) + { + throw std::invalid_argument("libvdwxc rVV10 received a density grid different from its initialized cell"); + } + + using Complex = std::complex; + std::vector density_g(pw.npw); + std::vector> gradient(pw.nrxx); + std::vector sigma(pw.nrxx, 0.0); + std::vector dedn(pw.nrxx, 0.0); + std::vector dedsigma(pw.nrxx, 0.0); + std::vector> field(pw.nrxx); + std::vector divergence(pw.nrxx, 0.0); + + for (double value: total_density) + { + if (!std::isfinite(value)) + { + throw std::invalid_argument("libvdwxc rVV10 received a nonfinite total density"); + } + } + for (double value: valence_density) + { + if (!std::isfinite(value)) + { + throw std::invalid_argument("libvdwxc rVV10 received a nonfinite valence density"); + } + } + + pw.real2recip(total_density.data(), density_g.data()); + XC_Functional::grad_rho(density_g.data(), gradient.data(), &pw, pw.tpiba); + for (int r = 0; r < pw.nrxx; ++r) + { + sigma[r] = gradient[r].x * gradient[r].x + gradient[r].y * gradient[r].y + gradient[r].z * gradient[r].z; + field[r] = ModuleBase::Vector3(0.0, 0.0, 0.0); + } + + std::vector density = total_density; + const double energy_ha = vdwxc_calculate(data_, + density.data(), + sigma.data(), + dedn.data(), + dedsigma.data()); + if (!std::isfinite(energy_ha)) + { + throw std::runtime_error("libvdwxc returned a nonfinite rVV10 energy"); + } + + for (int r = 0; r < pw.nrxx; ++r) + { + if (!std::isfinite(dedn[r]) || !std::isfinite(dedsigma[r])) + { + throw std::runtime_error("libvdwxc returned a nonfinite rVV10 derivative"); + } + const double coefficient = 2.0 * dedsigma[r]; + field[r].x = coefficient * gradient[r].x; + field[r].y = coefficient * gradient[r].y; + field[r].z = coefficient * gradient[r].z; + } + XC_Functional::grad_dot(field.data(), divergence.data(), &pw, pw.tpiba); + + NonlocalXCResult result{2.0 * energy_ha, 0.0, std::vector(pw.nrxx, 0.0)}; + const double dv = pw.omega / pw.nxyz; + for (int r = 0; r < pw.nrxx; ++r) + { + // libvdwxc returns Hartree derivatives; ABACUS stores XC quantities in Ry. + result.potential[r] = 2.0 * (dedn[r] - divergence[r]); + if (!std::isfinite(divergence[r]) || !std::isfinite(result.potential[r])) + { + throw std::runtime_error("libvdwxc produced a nonfinite rVV10 potential"); + } + result.vtxc += dv * valence_density[r] * result.potential[r]; + } + if (!std::isfinite(result.vtxc)) + { + throw std::runtime_error("libvdwxc produced a nonfinite rVV10 vtxc"); + } + return result; +} + +} // namespace vdw + +#endif // __LIBVDWXC diff --git a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h new file mode 100644 index 00000000000..7185e892b74 --- /dev/null +++ b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h @@ -0,0 +1,50 @@ +#ifndef ABACUS_NONLOCAL_XC_LIBVDWXC_H +#define ABACUS_NONLOCAL_XC_LIBVDWXC_H + +#include "nonlocal_xc.h" + +#include + +#ifdef __LIBVDWXC +// libvdwxc's public C header includes inside an extern "C" +// block. Pre-include the standard header here so libc++ does not parse its +// C++ templates with C linkage. +#include +#include +#endif + +namespace vdw +{ + +#ifdef __LIBVDWXC +/** + * @brief Thin serial/PW adapter for libvdwxc's rVV10 evaluator. + * + * libvdwxc owns the nonlocal kernel, interpolation and convolution. ABACUS + * owns density construction, FFT gradients/divergence, units and XC + * bookkeeping. The first adapter intentionally rejects distributed layouts; + * the libvdwxc MPI grid/communicator contract must be validated separately. + */ +class LibvdwxcNonlocalXC final : public NonlocalXC +{ + public: + LibvdwxcNonlocalXC(const ModulePW::PW_Basis& pw, double b, double c); + ~LibvdwxcNonlocalXC() override; + + NonlocalXCResult evaluate(const ModulePW::PW_Basis& pw, + const std::vector& total_density, + const std::vector& valence_density) const override; + + private: + vdwxc_data data_ = nullptr; + int nx_ = 0; + int ny_ = 0; + int nz_ = 0; + double omega_ = 0.0; + std::array cell_{}; +}; +#endif + +} // namespace vdw + +#endif // ABACUS_NONLOCAL_XC_LIBVDWXC_H diff --git a/source/source_hamilt/module_vdw/test/CMakeLists.txt b/source/source_hamilt/module_vdw/test/CMakeLists.txt index 195a5312ce8..c1d2d56517b 100644 --- a/source/source_hamilt/module_vdw/test/CMakeLists.txt +++ b/source/source_hamilt/module_vdw/test/CMakeLists.txt @@ -6,11 +6,13 @@ install(FILES c6.txt DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) install(FILES r0.txt DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) install(FILES r0_zero.txt DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) -AddTest( - TARGET MODULE_HAMILT_vdwTest - LIBS parameter base device vdw - SOURCES vdw_test.cpp vdwd3_evaluator_test.cpp -) +if(ENABLE_MPI) + AddTest( + TARGET MODULE_HAMILT_vdwTest + LIBS parameter base device vdw + SOURCES vdw_test.cpp vdwd3_evaluator_test.cpp + ) +endif() if(ENABLE_DFTD4 AND ENABLE_MPI) add_test( @@ -21,3 +23,19 @@ if(ENABLE_DFTD4 AND ENABLE_MPI) WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) endif() +if(ENABLE_LIBVDWXC AND NOT ENABLE_MPI AND NOT USE_CUDA AND NOT USE_ROCM) + AddTest( + TARGET MODULE_HAMILT_vdwTest_LIBVDWXC + LIBS xc_ planewave base parameter device nonlocal_xc + SOURCES test_nonlocal_xc_libvdwxc.cpp + ) + if(ENABLE_LIBXC) + target_link_libraries(MODULE_HAMILT_vdwTest_LIBVDWXC PRIVATE Libxc::xc) + endif() + target_link_libraries(MODULE_HAMILT_vdwTest_LIBVDWXC PRIVATE libvdwxc::vdwxc) + set_tests_properties(MODULE_HAMILT_vdwTest_LIBVDWXC PROPERTIES ENVIRONMENT "OMP_NUM_THREADS=1") +endif() + +if(ENABLE_DFTD4 AND ENABLE_MPI) + target_link_libraries(MODULE_HAMILT_vdwTest PRIVATE dftd4::dftd4) +endif() diff --git a/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp b/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp new file mode 100644 index 00000000000..b0acc26c82d --- /dev/null +++ b/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp @@ -0,0 +1,108 @@ +#include "../nonlocal_xc_libvdwxc.h" + +#include "source_basis/module_pw/pw_basis.h" +#include "source_hamilt/module_xc/xc_functional.h" + +#include +#include + +#include +#include +#include +#include + +namespace +{ +const double pi = std::acos(-1.0); + +class LibvdwxcAdapterTest : public testing::Test +{ + protected: + ModulePW::PW_Basis pw{"cpu", "double"}; + + void SetUp() override + { + // The oblique cell catches row/column-major cell mapping mistakes. + const ModuleBase::Matrix3 lattice(1.0, 0.0, 0.0, -0.5, std::sqrt(0.75), 0.0, 0.0, 0.0, 2.0); + pw.initgrids(10.0, lattice, 6, 8, 12); + pw.initparameters(false, 4.0, 1, true); + pw.setuptransform(); + pw.collect_local_pw(); + } + + std::vector density(double offset, double amplitude) const + { + std::vector values(pw.nrxx); + for (int x = 0; x < pw.nx; ++x) + for (int y = 0; y < pw.ny; ++y) + for (int z = 0; z < pw.nz; ++z) + { + const int index = (x * pw.ny + y) * pw.nz + z; + const double ax = 2.0 * pi * x / pw.nx; + const double ay = 2.0 * pi * y / pw.ny; + const double az = 2.0 * pi * z / pw.nz; + values[index] = offset + amplitude * (1.0 + 0.25 * std::sin(ax) + 0.15 * std::cos(ay + az)); + } + return values; + } +}; + +#ifdef __LIBVDWXC +TEST_F(LibvdwxcAdapterTest, MatchesDirectSerialApiForNonuniformDensity) +{ + const double b = 6.3; + const double c = 0.0093; + const std::vector total = density(0.004, 0.006); + const std::vector valence = density(0.002, 0.003); + vdw::LibvdwxcNonlocalXC adapter(pw, b, c); + const vdw::NonlocalXCResult adapted = adapter.evaluate(pw, total, valence); + + vdwxc_data direct = vdwxc_new(VDWXC_RVV10); + ASSERT_NE(direct, nullptr); + ASSERT_EQ(vdwxc_set_rvv10_parameters(direct, b, c), 0); + const auto& a = pw.latvec; + vdwxc_set_unit_cell(direct, pw.nx, pw.ny, pw.nz, + a.e11 * pw.lat0, a.e21 * pw.lat0, a.e31 * pw.lat0, + a.e12 * pw.lat0, a.e22 * pw.lat0, a.e32 * pw.lat0, + a.e13 * pw.lat0, a.e23 * pw.lat0, a.e33 * pw.lat0); + vdwxc_init_serial(direct); + + std::vector> total_g(pw.npw); + std::vector> gradient(pw.nrxx); + std::vector sigma(pw.nrxx, 0.0); + std::vector dedn(pw.nrxx, 0.0); + std::vector dedsigma(pw.nrxx, 0.0); + std::vector> field(pw.nrxx); + std::vector divergence(pw.nrxx, 0.0); + pw.real2recip(total.data(), total_g.data()); + XC_Functional::grad_rho(total_g.data(), gradient.data(), &pw, pw.tpiba); + for (int r = 0; r < pw.nrxx; ++r) + { + sigma[r] = gradient[r].x * gradient[r].x + gradient[r].y * gradient[r].y + gradient[r].z * gradient[r].z; + } + std::vector direct_density = total; + const double direct_energy = vdwxc_calculate(direct, direct_density.data(), sigma.data(), dedn.data(), dedsigma.data()); + for (int r = 0; r < pw.nrxx; ++r) + { + const double coefficient = 2.0 * dedsigma[r]; + field[r].x = coefficient * gradient[r].x; + field[r].y = coefficient * gradient[r].y; + field[r].z = coefficient * gradient[r].z; + } + XC_Functional::grad_dot(field.data(), divergence.data(), &pw, pw.tpiba); + + const double dv = pw.omega / pw.nxyz; + double expected_vtxc = 0.0; + EXPECT_NEAR(adapted.energy, 2.0 * direct_energy, 2.0e-12); + for (int r = 0; r < pw.nrxx; ++r) + { + const double expected_potential = 2.0 * (dedn[r] - divergence[r]); + expected_vtxc += dv * valence[r] * expected_potential; + EXPECT_NEAR(adapted.potential[r], expected_potential, 2.0e-12); + } + EXPECT_NEAR(adapted.vtxc, expected_vtxc, 2.0e-12); + vdwxc_finalize(&direct); +} +#endif + +} // namespace diff --git a/source/source_io/build_info.h.in b/source/source_io/build_info.h.in index 543ef3e1b41..aa9ba4c1f79 100644 --- a/source/source_io/build_info.h.in +++ b/source/source_io/build_info.h.in @@ -31,6 +31,7 @@ #define ABACUS_ELPA_VERSION "@ABACUS_ELPA_VERSION@" #define ABACUS_MKL_SUPPORT "@ABACUS_MKL_SUPPORT@" #define ABACUS_LIBXC_VERSION "@ABACUS_LIBXC_VERSION@" +#define ABACUS_LIBVDWXC_VERSION "@ABACUS_LIBVDWXC_VERSION@" #define ABACUS_FFTW_VERSION "@ABACUS_FFTW_VERSION@" // --- Accelerators & Specific Hardware --- diff --git a/source/source_io/module_parameter/input_parameter.h b/source/source_io/module_parameter/input_parameter.h index a8b584632e0..4bc7f2fcd61 100644 --- a/source/source_io/module_parameter/input_parameter.h +++ b/source/source_io/module_parameter/input_parameter.h @@ -88,6 +88,9 @@ struct Input_para double nupdown = 0.0; std::string dft_functional = "default"; ///< input DFT functional. int gga_grad = 0; ///< Noncollinear GGA: 0 original, 1 local axis, 2 regularized projected LCA. + std::string xc_nonlocal = "none"; ///< nonlocal XC correction (none; rvv10) + double rvv10_b = 6.3; ///< rVV10 damping parameter b + double rvv10_c = 0.0093; ///< rVV10 parameter C double xc_temperature = 0.0; ///< only relevant if finite temperature functional is used double pseudo_rcut = 15.0; ///< cut-off radius for calculating msh bool pseudo_mesh = false; ///< 0: use msh to normalize radial wave functions; 1: diff --git a/source/source_io/module_parameter/read_inp_estruc.cpp b/source/source_io/module_parameter/read_inp_estruc.cpp index 5da535085ca..6cd705addd6 100644 --- a/source/source_io/module_parameter/read_inp_estruc.cpp +++ b/source/source_io/module_parameter/read_inp_estruc.cpp @@ -3,6 +3,10 @@ #include "read_input.h" #include "read_input_tool.h" +#include +#include +#include + namespace ModuleIO { void ReadInput::item_elec_stru() @@ -333,6 +337,107 @@ The other way is only available when compiling with LIBXC, and it allows for sup item.default_value = "Used the same as DFT functional as specified in the pseudopotential files."; item.unit = ""; read_sync_string(input.dft_functional); + item.check_value = [](const Input_Item&, const Parameter& para) { + std::string name = para.input.dft_functional; + std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { + return static_cast(std::toupper(c)); + }); + if (name == "RVV10") + ModuleBase::WARNING_QUIT( + "ReadInput", "Use xc_nonlocal=rvv10 with an explicit semilocal dft_functional"); + }; + this->add_item(item); + } + { + Input_Item item("xc_nonlocal"); + item.annotation = "none; rvv10"; + item.category = "Electronic structure"; + item.type = "String"; + item.description = R"(Select an optional libvdwxc-backed nonlocal exchange-correlation correction. `none` disables the correction. The current rVV10 interface requires a CMake build with `ENABLE_LIBXC=ON` and `ENABLE_LIBVDWXC=ON`, `dft_functional=GGA_X_RPW86+GGA_C_PBE`, fixed b=6.3 and C=0.0093, and a single-process, CPU double-precision, plane-wave, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, LCAO, GPU, reduced-gamma layouts, ultrasoft/PAW pseudopotentials, spin-polarized calculations and pairwise vdW corrections are not part of this interface.)"; + item.default_value = "none"; + item.unit = ""; + read_sync_string(input.xc_nonlocal); + item.reset_value = [](const Input_Item&, Parameter& para) { + std::transform(para.input.xc_nonlocal.begin(), para.input.xc_nonlocal.end(), para.input.xc_nonlocal.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + }; + item.check_value = [](const Input_Item&, const Parameter& para) { + std::string nonlocal = para.input.xc_nonlocal; + std::transform(nonlocal.begin(), nonlocal.end(), nonlocal.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + if (nonlocal != "none" && nonlocal != "rvv10") + ModuleBase::WARNING_QUIT("ReadInput", "xc_nonlocal must be either none or rvv10"); + if (nonlocal != "rvv10") + return; +#if !defined(__LIBXC) || !defined(__LIBVDWXC) + ModuleBase::WARNING_QUIT("ReadInput", "xc_nonlocal=rvv10 requires Libxc and libvdwxc-enabled CMake builds"); +#endif + if (!std::isfinite(para.input.rvv10_b) || para.input.rvv10_b <= 0.0 || !std::isfinite(para.input.rvv10_c) + || para.input.rvv10_c < 0.0) + { + ModuleBase::WARNING_QUIT( + "ReadInput", "rvv10_b must be finite and positive, and rvv10_c must be finite and non-negative"); + } + std::string functional = para.input.dft_functional; + std::transform(functional.begin(), functional.end(), functional.begin(), [](unsigned char c) { + return static_cast(std::toupper(c)); + }); + functional.erase(std::remove_if(functional.begin(), functional.end(), [](unsigned char c) { + return std::isspace(c); + }), functional.end()); + if (functional.empty() || functional == "DEFAULT") + ModuleBase::WARNING_QUIT( + "ReadInput", "xc_nonlocal=rvv10 requires an explicit semilocal dft_functional"); + if (functional != "GGA_X_RPW86+GGA_C_PBE") + ModuleBase::WARNING_QUIT( + "ReadInput", + "xc_nonlocal=rvv10 requires dft_functional=GGA_X_RPW86+GGA_C_PBE for the libvdwxc backend"); + if (std::abs(para.input.rvv10_b - 6.3) > 1.0e-12 || std::abs(para.input.rvv10_c - 0.0093) > 1.0e-12) + ModuleBase::WARNING_QUIT( + "ReadInput", "xc_nonlocal=rvv10 uses fixed parameters rvv10_b=6.3 and rvv10_c=0.0093"); + const auto& p = para.input; + if (para.globalv.nproc != 1 || p.calculation != "scf" || p.esolver_type != "ksdft" || p.basis_type != "pw" || p.device != "cpu" + || p.precision != "double" || p.nspin != 1 || p.cal_force || p.cal_stress + || p.gamma_only || p.vdw_method != "none" || !p.vl_in_h) + ModuleBase::WARNING_QUIT("ReadInput", + "xc_nonlocal=rvv10 supports only one MPI rank with CPU double PW nspin=1 " + "serial SCF without forces, stress, gamma_only or other vdW corrections"); + }; + this->add_item(item); + } + { + Input_Item item("rvv10_b"); + item.annotation = "positive real"; + item.category = "Electronic structure"; + item.type = "Real"; + item.description = "Fixed damping parameter b=6.3 for the current libvdwxc-backed `GGA_X_RPW86+GGA_C_PBE` rVV10 interface."; + item.default_value = "6.3"; + item.unit = "dimensionless"; + item.set_availability("xc_nonlocal==rvv10"); + read_sync_double(input.rvv10_b); + item.check_value = [](const Input_Item&, const Parameter& para) { + if (para.input.xc_nonlocal == "rvv10" + && (!std::isfinite(para.input.rvv10_b) || para.input.rvv10_b <= 0.0)) + ModuleBase::WARNING_QUIT("ReadInput", "rvv10_b must be finite and positive"); + }; + this->add_item(item); + } + { + Input_Item item("rvv10_c"); + item.annotation = "non-negative real"; + item.category = "Electronic structure"; + item.type = "Real"; + item.description = "Fixed parameter C=0.0093 for the current libvdwxc-backed `GGA_X_RPW86+GGA_C_PBE` rVV10 interface."; + item.default_value = "0.0093"; + item.unit = "dimensionless"; + item.set_availability("xc_nonlocal==rvv10"); + read_sync_double(input.rvv10_c); + item.check_value = [](const Input_Item&, const Parameter& para) { + if (para.input.xc_nonlocal == "rvv10" + && (!std::isfinite(para.input.rvv10_c) || para.input.rvv10_c < 0.0)) + ModuleBase::WARNING_QUIT("ReadInput", "rvv10_c must be finite and non-negative"); + }; this->add_item(item); } { @@ -805,6 +910,18 @@ Note: If gamma_only is set to 1, the KPT file will be overwritten. So make sure item.reset_value = [](const Input_Item& item, Parameter& para) { if (para.input.basis_type == "pw" && para.input.gamma_only) { + // Resets precede validation. Reject before the PW fallback + // overwrites KPT; xc_nonlocal may not yet be lowercased. + std::string nonlocal = para.input.xc_nonlocal; + std::transform(nonlocal.begin(), nonlocal.end(), nonlocal.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + if (nonlocal == "rvv10") + { + ModuleBase::WARNING_QUIT("ReadInput", + "xc_nonlocal=rvv10 supports only one MPI rank with CPU double PW nspin=1 " + "serial SCF without forces, stress, gamma_only or other vdW corrections"); + } para.input.gamma_only = false; GlobalV::ofs_warning << " WARNING : gamma_only has not been implemented for pw yet" << std::endl; GlobalV::ofs_warning << "gamma_only is not supported in the pw model" << std::endl; diff --git a/source/source_io/parse_args.cpp b/source/source_io/parse_args.cpp index 046bc527028..41cf5054c6a 100644 --- a/source/source_io/parse_args.cpp +++ b/source/source_io/parse_args.cpp @@ -140,6 +140,7 @@ void print_build_info() print_info("ELPA Support", ABACUS_ELPA_VERSION); print_info("MKL Support", ABACUS_MKL_SUPPORT); print_info("LibXC Support", ABACUS_LIBXC_VERSION); + print_info("libvdwxc Support", ABACUS_LIBVDWXC_VERSION); print_info("FFTW Support", ABACUS_FFTW_VERSION); // --- 8. Accelerators & Specific Hardware --- diff --git a/source/source_pw/module_pwdft/Makefile b/source/source_pw/module_pwdft/Makefile index ad634ae49e9..8091a706708 100644 --- a/source/source_pw/module_pwdft/Makefile +++ b/source/source_pw/module_pwdft/Makefile @@ -139,4 +139,4 @@ ${OBJ_DIR}/%.o:%.cu .PHONY:clean clean: @ if [ -d $(OBJ_DIR) ]; then rm -rf $(OBJ_DIR); fi - @ if [ -e ${VERSION}-PW.x ]; then rm -f ${VERSION}-PW.x; fi \ No newline at end of file + @ if [ -e ${VERSION}-PW.x ]; then rm -f ${VERSION}-PW.x; fi diff --git a/source/source_pw/module_pwdft/hamilt_pw.cpp b/source/source_pw/module_pwdft/hamilt_pw.cpp index 13977cd9f6e..fdc189c464b 100644 --- a/source/source_pw/module_pwdft/hamilt_pw.cpp +++ b/source/source_pw/module_pwdft/hamilt_pw.cpp @@ -83,6 +83,11 @@ HamiltPW::HamiltPW(elecstate::Potential* pot_in, } // no variable can choose xc, maybe it is necessary pot_register_in.push_back("xc"); + // Keep the selected nonlocal XC backend after the canonical semilocal XC. + if (pot_in->has_nonlocal_xc()) + { + pot_register_in.push_back("xc_nonlocal"); + } if (input.imp_sol) { pot_register_in.push_back("surchem"); diff --git a/source/source_pw/module_pwdft/hamilt_pw.h b/source/source_pw/module_pwdft/hamilt_pw.h index 72158eadaf4..c4117810b3c 100644 --- a/source/source_pw/module_pwdft/hamilt_pw.h +++ b/source/source_pw/module_pwdft/hamilt_pw.h @@ -27,6 +27,7 @@ class HamiltPW : public Hamilt using syncmem_complex_h2d_op = base_device::memory::synchronize_memory_op; public: + // Explicit component selection keeps nonlocal XC out of global state. HamiltPW(elecstate::Potential* pot_in, ModulePW::PW_Basis_K* wfc_basis, K_Vectors* p_kv, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bcbc620a1a3..b0976b7eea0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(integrate) +add_subdirectory(rvv10) add_subdirectory(01_PW) add_subdirectory(02_NAO_Gamma) add_subdirectory(03_NAO_multik) diff --git a/tests/rvv10/CMakeLists.txt b/tests/rvv10/CMakeLists.txt new file mode 100644 index 00000000000..19bd8c4b0db --- /dev/null +++ b/tests/rvv10/CMakeLists.txt @@ -0,0 +1,8 @@ +find_package(Python3 REQUIRED COMPONENTS Interpreter) + +add_test(NAME RVV10_LIBVDWXC_CONTRACT + COMMAND ${Python3_EXECUTABLE} -m unittest discover + -s ${CMAKE_CURRENT_SOURCE_DIR} -p test_libvdwxc_contract.py) + +set_tests_properties(RVV10_LIBVDWXC_CONTRACT PROPERTIES + ENVIRONMENT "OMP_NUM_THREADS=1") diff --git a/tests/rvv10/README.md b/tests/rvv10/README.md new file mode 100644 index 00000000000..467def4ad3c --- /dev/null +++ b/tests/rvv10/README.md @@ -0,0 +1,38 @@ +# libvdwxc-backed rVV10 checks + +This directory checks the host-side contract of the first ABACUS integration. +The numerical rVV10 kernel is provided by the pinned libvdwxc development +branch; ABACUS does not carry a second kernel implementation. + +The host adapter is intentionally limited to the current API contract: + +* CPU, double precision, full-complex plane-wave density grid; +* one process and `nspin=1` SCF; +* Libxc semilocal `GGA_X_RPW86+GGA_C_PBE` plus `xc_nonlocal=rvv10`; +* norm-conserving pseudopotentials, energy, potential and `vtxc` only. + +MPI, spin-polarized calculations, GPU/LCAO, gamma-only layouts, ultrasoft/PAW +pseudopotentials, forces and stress are rejected by the input contract and are +follow-up work. The current libvdwxc MR is still Draft, so a Ready PR must pin +commit `91ef4820f3270e22b35fddc2ab4cab8efa4b6d79` and show both the library's `ctest -R rvv10` output and the +ABACUS adapter test output. + +Configure the host-side contract test with: + +```bash +cmake -B build -DBUILD_TESTING=ON +ctest --test-dir build -R RVV10_LIBVDWXC_CONTRACT --output-on-failure +``` + +With the pinned libvdwxc installation available, enable the real adapter test: + +```bash +cmake -B build-libvdwxc -DBUILD_TESTING=ON -DENABLE_LIBXC=ON \ + -DENABLE_LIBVDWXC=ON -DCMAKE_PREFIX_PATH=/path/to/libvdwxc +cmake --build build-libvdwxc --target MODULE_HAMILT_vdwTest_LIBVDWXC +ctest --test-dir build-libvdwxc -R LIBVDWXC --output-on-failure +``` + +The adapter test uses a nonuniform density and an oblique cell. It compares +ABACUS energy, the host-computed `dedn - div(2*dedsigma*grad(n))` potential, +and the valence `vtxc` contraction against the direct serial libvdwxc API. diff --git a/tests/rvv10/test_libvdwxc_contract.py b/tests/rvv10/test_libvdwxc_contract.py new file mode 100644 index 00000000000..710710155f2 --- /dev/null +++ b/tests/rvv10/test_libvdwxc_contract.py @@ -0,0 +1,71 @@ +"""Guard the optional libvdwxc integration contract.""" + +from pathlib import Path +import unittest + + +ROOT = Path(__file__).resolve().parents[2] + + +class LibvdwxcContract(unittest.TestCase): + def test_optional_backend_has_a_separate_build_switch_and_adapter(self): + cmake = (ROOT / "CMakeLists.txt").read_text() + module_cmake = (ROOT / "source/source_hamilt/module_vdw/CMakeLists.txt").read_text() + adapter = ROOT / "source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp" + self.assertIn("ENABLE_LIBVDWXC", cmake) + self.assertIn("find_package(libvdwxc CONFIG REQUIRED)", cmake) + self.assertIn("libvdwxc::vdwxc", cmake) + self.assertIn("ENABLE_LIBVDWXC", module_cmake) + self.assertIn("nonlocal_xc_libvdwxc.cpp", module_cmake) + self.assertTrue(adapter.is_file()) + text = adapter.read_text() + self.assertIn("vdwxc_calculate", text) + self.assertIn("vdwxc_set_unit_cell", text) + self.assertIn("vdwxc_init_serial", text) + self.assertIn("vdwxc_finalize", text) + adapter_header = (ROOT / "source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h").read_text() + self.assertLess(adapter_header.index("#include "), adapter_header.index("#include ")) + + def test_first_pr_does_not_bundle_a_second_rvv10_kernel(self): + module_cmake = (ROOT / "source/source_hamilt/module_vdw/CMakeLists.txt").read_text() + self.assertNotIn("nonlocal_xc_rvv10.cpp", module_cmake) + self.assertNotIn("nonlocal_xc_rvv10_pw.cpp", module_cmake) + for name in ( + "nonlocal_xc_rvv10.cpp", + "nonlocal_xc_rvv10.h", + "nonlocal_xc_rvv10_pw.cpp", + "nonlocal_xc_rvv10_pw.h", + ): + self.assertFalse((ROOT / "source/source_hamilt/module_vdw" / name).exists(), name) + + def test_configuration_checks_the_complete_serial_rvv10_api(self): + cmake = (ROOT / "CMakeLists.txt").read_text() + for symbol in ("VDWXC_RVV10", "vdwxc_new", "vdwxc_set_rvv10_parameters", "vdwxc_set_unit_cell", + "vdwxc_init_serial", "vdwxc_calculate", "vdwxc_finalize"): + self.assertIn(symbol, cmake) + self.assertIn("NO_DEFAULT_PATH", cmake) + + def test_input_path_requires_both_semilocal_and_nonlocal_dependencies(self): + source = (ROOT / "source/source_io/module_parameter/read_inp_estruc.cpp").read_text() + cmake = (ROOT / "CMakeLists.txt").read_text() + self.assertIn("#if !defined(__LIBXC) || !defined(__LIBVDWXC)", source) + self.assertIn("ENABLE_LIBVDWXC currently requires ENABLE_LIBXC=ON", cmake) + + def test_docs_do_not_describe_a_bundled_native_kernel(self): + docs = (ROOT / "docs/developers_guide/nonlocal_xc.md").read_text() + self.assertNotIn("existing native evaluator remains available", docs) + self.assertIn("does not carry a second rVV10 kernel", docs) + + def test_build_info_reports_the_optional_backend(self): + build_info = (ROOT / "source/source_io/build_info.h.in").read_text() + parse_args = (ROOT / "source/source_io/parse_args.cpp").read_text() + collect_build_info = (ROOT / "cmake/CollectBuildInfoVars.cmake").read_text() + make_build_info = (ROOT / "generate_build_info.sh").read_text() + self.assertIn("ABACUS_LIBVDWXC_VERSION", build_info) + self.assertIn('print_info("libvdwxc Support", ABACUS_LIBVDWXC_VERSION)', parse_args) + self.assertIn('libvdwxc = ${ABACUS_LIBVDWXC_VERSION}', collect_build_info) + self.assertIn('@ABACUS_LIBVDWXC_VERSION@', make_build_info) + + +if __name__ == "__main__": + unittest.main() From a8ab61b7c2dd8f77ef9467364bf3c19c325a4988 Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 03:26:01 +0800 Subject: [PATCH 02/16] fix(build-info): report libvdwxc in Makefile builds --- generate_build_info.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/generate_build_info.sh b/generate_build_info.sh index f50f98ac727..9bb953909a5 100755 --- a/generate_build_info.sh +++ b/generate_build_info.sh @@ -165,6 +165,22 @@ if [ "${USE_CUDA}" == "ON" ]; then fi fi +# 6b. libvdwxc +# +# The Makefile build does not currently compile the libvdwxc adapter, but the +# generated build-info header must still define every placeholder used by the +# CMake build. Keep the value explicit (rather than leaving the literal +# @ABACUS_LIBVDWXC_VERSION@ marker in the header) and report a detected version +# when a future Makefile configuration provides the dependency. +LIBVDWXC_VERSION="no" +if [ "${ENABLE_LIBVDWXC}" == "ON" ]; then + LIBVDWXC_VERSION="yes (version unknown)" + if command -v pkg-config > /dev/null; then + version=$(pkg-config --modversion libvdwxc 2>/dev/null) + if [ -n "$version" ]; then LIBVDWXC_VERSION="yes (v$version)"; fi + fi +fi + # 7. ROCm/HIP ROCM_VERSION="no" if [ "${USE_ROCM}" == "ON" ]; then @@ -279,6 +295,7 @@ sed \ -e "s#@ABACUS_OPENMP_VERSION@#$OPENMP_VERSION#g" \ -e "s#@ABACUS_MKL_SUPPORT@#$MKL_SUPPORT#g" \ -e "s#@ABACUS_LIBXC_VERSION@#$LIBXC_VERSION#g" \ + -e "s#@ABACUS_LIBVDWXC_VERSION@#$LIBVDWXC_VERSION#g" \ -e "s#@ABACUS_FFTW_VERSION@#$FFTW3_VERSION#g" \ -e "s#@ABACUS_CUDA_VERSION@#$CUDA_VERSION#g" \ -e "s#@ABACUS_ROCM_VERSION@#$ROCM_VERSION#g" \ From 52d9da673dc01a389a63f7f022a15b7a6ef52b9a Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 03:34:11 +0800 Subject: [PATCH 03/16] fix(xc): avoid libvdwxc header macro collision --- cmake/CollectBuildInfoVars.cmake | 1 + source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h | 5 +++++ .../module_vdw/test/test_nonlocal_xc_libvdwxc.cpp | 3 --- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cmake/CollectBuildInfoVars.cmake b/cmake/CollectBuildInfoVars.cmake index e60ec22f90d..15ec9e43e83 100644 --- a/cmake/CollectBuildInfoVars.cmake +++ b/cmake/CollectBuildInfoVars.cmake @@ -449,6 +449,7 @@ message(" Torch = ${ABACUS_LIBTORCH_VERSION}") message(" DeePMD = ${ABACUS_DEEPMD_VERSION}") message(" NEP = ${ABACUS_NEP_VERSION}") message(" Features : Libxc = ${ABACUS_LIBXC_VERSION}") +message(" libvdwxc = ${ABACUS_LIBVDWXC_VERSION}") message(" LibRI = ${ABACUS_LIBRI_VERSION}") message(" DFT-D4 = ${ABACUS_DFTD4_VERSION}") message(" JSON = ${ABACUS_JSON_VERSION}") diff --git a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h index 7185e892b74..b5fb5088d98 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h +++ b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h @@ -11,6 +11,11 @@ // C++ templates with C linkage. #include #include +// complex.h defines I as a macro; it collides with template identifiers in +// GoogleTest and other C++ headers, but is not needed after vdwxc.h parsing. +#ifdef I +#undef I +#endif #endif namespace vdw diff --git a/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp b/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp index b0acc26c82d..fe644fd0c50 100644 --- a/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp +++ b/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp @@ -3,9 +3,6 @@ #include "source_basis/module_pw/pw_basis.h" #include "source_hamilt/module_xc/xc_functional.h" -#include -#include - #include #include #include From 48bc03fbc066647a515f1600ff5298bfa7586f6e Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 03:39:24 +0800 Subject: [PATCH 04/16] test(xc): cover nonlocal potential derivative --- source/CMakeLists.txt | 10 ++--- .../test/test_nonlocal_xc_libvdwxc.cpp | 44 +++++++++++++++++++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index d98323f4065..ea5f2d0ff15 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -563,17 +563,17 @@ add_subdirectory(source_psi) add_subdirectory(source_estate) add_subdirectory(source_hamilt) add_subdirectory(source_pw) -if(BUILD_TESTING) - # Register the nonlocal XC tests once; CMake resolves their PW dependencies - # after all source subdirectories have been processed. - add_subdirectory(source_hamilt/module_vdw/test) -endif() add_subdirectory(source_lcao) add_subdirectory(source_hsolver) add_subdirectory(source_basis/module_ao) add_subdirectory(source_basis/module_nao) add_subdirectory(source_md) add_subdirectory(source_basis/module_pw) +if(BUILD_TESTING) + # Register after the PW basis target exists; the optional adapter test links + # the existing planewave object library. + add_subdirectory(source_hamilt/module_vdw/test) +endif() add_subdirectory(source_esolver) add_subdirectory(source_io) add_subdirectory(source_relax) diff --git a/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp b/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp index fe644fd0c50..6f856cf88f9 100644 --- a/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp +++ b/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp @@ -42,6 +42,21 @@ class LibvdwxcAdapterTest : public testing::Test } return values; } + + std::vector perturbation() const + { + std::vector values(pw.nrxx, 0.0); + for (int x = 0; x < pw.nx; ++x) + for (int y = 0; y < pw.ny; ++y) + for (int z = 0; z < pw.nz; ++z) + { + const int index = (x * pw.ny + y) * pw.nz + z; + const double ax = 2.0 * pi * x / pw.nx; + const double ay = 2.0 * pi * y / pw.ny; + values[index] = std::sin(ax + ay) + 0.3 * std::cos(2.0 * ay); + } + return values; + } }; #ifdef __LIBVDWXC @@ -100,6 +115,35 @@ TEST_F(LibvdwxcAdapterTest, MatchesDirectSerialApiForNonuniformDensity) EXPECT_NEAR(adapted.vtxc, expected_vtxc, 2.0e-12); vdwxc_finalize(&direct); } + +TEST_F(LibvdwxcAdapterTest, PotentialMatchesFiniteDifferenceOfEnergy) +{ + const double b = 6.3; + const double c = 0.0093; + const double step = 1.0e-5; + const std::vector total = density(0.004, 0.006); + const std::vector valence = total; + const std::vector direction = perturbation(); + vdw::LibvdwxcNonlocalXC adapter(pw, b, c); + const vdw::NonlocalXCResult reference = adapter.evaluate(pw, total, valence); + std::vector plus = total; + std::vector minus = total; + for (int r = 0; r < pw.nrxx; ++r) + { + plus[r] += step * direction[r]; + minus[r] -= step * direction[r]; + } + const vdw::NonlocalXCResult forward = adapter.evaluate(pw, plus, valence); + const vdw::NonlocalXCResult backward = adapter.evaluate(pw, minus, valence); + const double finite_difference = (forward.energy - backward.energy) / (2.0 * step); + const double dv = pw.omega / pw.nxyz; + double potential_contraction = 0.0; + for (int r = 0; r < pw.nrxx; ++r) + { + potential_contraction += dv * reference.potential[r] * direction[r]; + } + EXPECT_NEAR(finite_difference, potential_contraction, 2.0e-6); +} #endif } // namespace From e6097919d320a617db245a3d9efc50a71fd2d6df Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 03:40:52 +0800 Subject: [PATCH 05/16] docs(xc): state external validation gate clearly --- docs/developers_guide/nonlocal_xc.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/developers_guide/nonlocal_xc.md b/docs/developers_guide/nonlocal_xc.md index 7d079112e59..e803145e504 100644 --- a/docs/developers_guide/nonlocal_xc.md +++ b/docs/developers_guide/nonlocal_xc.md @@ -110,8 +110,8 @@ valence `vtxc` contraction. Before converting a Draft PR to Ready, run both libraries' tests and add an ABACUS SCF reference case with the exact executable, pseudopotential hashes, input, dependency commit, compiler, FFT provider, and numerical tolerances. -The current local environment cannot provide a complete CMake/libvdwxc build; -therefore this change remains a Draft until that evidence is attached. +The pull request must attach this evidence explicitly; the default CI contract +test does not build the optional external dependency. ## Follow-up work From 3b589157b923813212b00ce09660f586c110c7f1 Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 03:41:50 +0800 Subject: [PATCH 06/16] build(tests): register adapter after its dependencies --- source/CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index ea5f2d0ff15..6ac3e017d27 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -569,16 +569,16 @@ add_subdirectory(source_basis/module_ao) add_subdirectory(source_basis/module_nao) add_subdirectory(source_md) add_subdirectory(source_basis/module_pw) -if(BUILD_TESTING) - # Register after the PW basis target exists; the optional adapter test links - # the existing planewave object library. - add_subdirectory(source_hamilt/module_vdw/test) -endif() add_subdirectory(source_esolver) add_subdirectory(source_io) add_subdirectory(source_relax) add_subdirectory(source_lcao/module_ri) add_subdirectory(source_io/module_parameter) +if(BUILD_TESTING) + # Register after PW, XC and INPUT targets exist; the optional adapter test + # links those existing object libraries instead of creating wrappers. + add_subdirectory(source_hamilt/module_vdw/test) +endif() add_subdirectory(source_lcao/module_lr) add_subdirectory(source_lcao/module_rdmft) # add by jghan From 4ad8c370f4e996f9a000f16ad5685ec4b4ea4c44 Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 03:43:13 +0800 Subject: [PATCH 07/16] build(tests): register after device target --- source/CMakeLists.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 6ac3e017d27..6b97d66960e 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -574,11 +574,6 @@ add_subdirectory(source_io) add_subdirectory(source_relax) add_subdirectory(source_lcao/module_ri) add_subdirectory(source_io/module_parameter) -if(BUILD_TESTING) - # Register after PW, XC and INPUT targets exist; the optional adapter test - # links those existing object libraries instead of creating wrappers. - add_subdirectory(source_hamilt/module_vdw/test) -endif() add_subdirectory(source_lcao/module_lr) add_subdirectory(source_lcao/module_rdmft) # add by jghan @@ -726,6 +721,12 @@ endif() # base uses symbols from device (memory_op and math_ylm_op). target_link_libraries(base PUBLIC device) +if(BUILD_TESTING) + # Register after all object-library dependencies exist; the optional adapter + # test links the existing PW, INPUT and device targets. + add_subdirectory(source_hamilt/module_vdw/test) +endif() + if(ENABLE_COVERAGE) add_coverage(driver) endif() From e4763982fbe7df89c595988baf8b0286f736610d Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 03:59:27 +0800 Subject: [PATCH 08/16] docs(xc): make serial libvdwxc test command reproducible --- docs/advanced/install.md | 1 + docs/developers_guide/nonlocal_xc.md | 1 + tests/rvv10/README.md | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/advanced/install.md b/docs/advanced/install.md index c1271f2212b..70537429c04 100644 --- a/docs/advanced/install.md +++ b/docs/advanced/install.md @@ -110,6 +110,7 @@ ABACUS together with Libxc: ```bash cmake -B build-libvdwxc -DBUILD_TESTING=ON \ + -DENABLE_MPI=OFF \ -DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON \ -DCMAKE_PREFIX_PATH=/path/to/libvdwxc-prefix ``` diff --git a/docs/developers_guide/nonlocal_xc.md b/docs/developers_guide/nonlocal_xc.md index e803145e504..4f4262f954f 100644 --- a/docs/developers_guide/nonlocal_xc.md +++ b/docs/developers_guide/nonlocal_xc.md @@ -66,6 +66,7 @@ Enable the optional backend with a CMake package that exports ```bash cmake -B build-libvdwxc -DBUILD_TESTING=ON \ + -DENABLE_MPI=OFF \ -DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON \ -DCMAKE_PREFIX_PATH=/path/to/libvdwxc-prefix ``` diff --git a/tests/rvv10/README.md b/tests/rvv10/README.md index 467def4ad3c..bbc7183c195 100644 --- a/tests/rvv10/README.md +++ b/tests/rvv10/README.md @@ -27,7 +27,7 @@ ctest --test-dir build -R RVV10_LIBVDWXC_CONTRACT --output-on-failure With the pinned libvdwxc installation available, enable the real adapter test: ```bash -cmake -B build-libvdwxc -DBUILD_TESTING=ON -DENABLE_LIBXC=ON \ +cmake -B build-libvdwxc -DBUILD_TESTING=ON -DENABLE_MPI=OFF -DENABLE_LIBXC=ON \ -DENABLE_LIBVDWXC=ON -DCMAKE_PREFIX_PATH=/path/to/libvdwxc cmake --build build-libvdwxc --target MODULE_HAMILT_vdwTest_LIBVDWXC ctest --test-dir build-libvdwxc -R LIBVDWXC --output-on-failure From 67e6c66360ee4e6a59a488b585334569daee11a7 Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 04:01:23 +0800 Subject: [PATCH 09/16] docs(xc): add rVV10 citation guidance --- docs/CITATIONS.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/CITATIONS.md b/docs/CITATIONS.md index 43176829a1d..da0e68dadc4 100644 --- a/docs/CITATIONS.md +++ b/docs/CITATIONS.md @@ -55,3 +55,11 @@ The following references are required to be cited when using ABACUS. Specificall Eike Caldeweyher, Jan-Michael Mewes, Sebastian Ehlert, and Stefan Grimme. "Extension and evaluation of the D4 London-dispersion model for periodic systems." Physical Chemistry Chemical Physics 22, 8499-8512 (2020). DOI: 10.1039/D0CP00502A. Nikolay V. Tkachenko, Linus B. Dittmer, Rebecca Tomann, and Martin Head-Gordon. "Smooth D4S model." The Journal of Physical Chemistry Letters 15, 10629-10637 (2024). DOI: 10.1021/acs.jpclett.4c02653. + +- **If the libvdwxc-backed rVV10 nonlocal correction is used:** + + O. A. Vydrov and T. Van Voorhis. "Nonlocal van der Waals density functional: The simpler the better." The Journal of Chemical Physics 133, 244103 (2010). DOI: 10.1063/1.3521275. + + P. Sabatini, T. Gorni, and S. de Gironcoli. "Nonlocal van der Waals density functional made simple and efficient." Physical Review B 87, 041108(R) (2013). DOI: 10.1103/PhysRevB.87.041108. + + Cite the exact [libvdwxc](https://gitlab.com/libvdwxc/libvdwxc) revision used for the calculation, because the rVV10 CMake interface is version-dependent during development. From f3d41ad55ed53abb18ec8953d64df805d926dedd Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 04:12:56 +0800 Subject: [PATCH 10/16] refactor(xc): make adapter validation explicit --- .../module_vdw/nonlocal_xc_libvdwxc.cpp | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp index 7aeb7fe3162..0420c228ccd 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp +++ b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp @@ -5,7 +5,6 @@ #include "source_basis/module_pw/pw_basis.h" #include "source_hamilt/module_xc/xc_functional.h" -#include #include #include #include @@ -43,7 +42,17 @@ bool same_cell(const std::array& lhs, const std::array& rh { for (std::size_t i = 0; i < lhs.size(); ++i) { - const double scale = std::max({1.0, std::abs(lhs[i]), std::abs(rhs[i])}); + const double lhs_abs = std::abs(lhs[i]); + const double rhs_abs = std::abs(rhs[i]); + double scale = 1.0; + if (lhs_abs > scale) + { + scale = lhs_abs; + } + if (rhs_abs > scale) + { + scale = rhs_abs; + } if (std::abs(lhs[i] - rhs[i]) > 1.0e-12 * scale) { return false; @@ -102,8 +111,21 @@ NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, const std::vector& valence_density) const { check_serial_basis(pw); - if (pw.nx != nx_ || pw.ny != ny_ || pw.nz != nz_ || !same_cell(cell_, cell_columns(pw)) - || std::abs(pw.omega - omega_) > 1.0e-12 * std::max({1.0, std::abs(pw.omega), std::abs(omega_)}) + const std::array current_cell = cell_columns(pw); + const double omega_abs = std::abs(pw.omega); + const double initialized_omega_abs = std::abs(omega_); + double volume_scale = 1.0; + if (omega_abs > volume_scale) + { + volume_scale = omega_abs; + } + if (initialized_omega_abs > volume_scale) + { + volume_scale = initialized_omega_abs; + } + const bool same_grid = pw.nx == nx_ && pw.ny == ny_ && pw.nz == nz_; + const bool same_volume = std::abs(pw.omega - omega_) <= 1.0e-12 * volume_scale; + if (!same_grid || !same_cell(cell_, current_cell) || !same_volume || total_density.size() != static_cast(pw.nrxx) || valence_density.size() != total_density.size()) { @@ -166,7 +188,9 @@ NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, } XC_Functional::grad_dot(field.data(), divergence.data(), &pw, pw.tpiba); - NonlocalXCResult result{2.0 * energy_ha, 0.0, std::vector(pw.nrxx, 0.0)}; + const double energy_ry = 2.0 * energy_ha; + std::vector potential(pw.nrxx, 0.0); + NonlocalXCResult result{energy_ry, 0.0, potential}; const double dv = pw.omega / pw.nxyz; for (int r = 0; r < pw.nrxx; ++r) { From 32e12de74c07d2307a90c695dac6f8e452f592e5 Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 12:27:46 +0800 Subject: [PATCH 11/16] fix(xc): reject unsupported rVV10 density grids and spin --- docs/advanced/input_files/input-main.md | 2 +- docs/developers_guide/nonlocal_xc.md | 14 ++++++++------ docs/parameters.yaml | 2 +- .../source_estate/module_pot/pot_nonlocal_xc.cpp | 4 ++-- .../source_io/module_parameter/read_inp_estruc.cpp | 6 +++--- tests/rvv10/test_libvdwxc_contract.py | 9 +++++++++ 6 files changed, 24 insertions(+), 13 deletions(-) diff --git a/docs/advanced/input_files/input-main.md b/docs/advanced/input_files/input-main.md index a46323df9e9..ac552e268f2 100644 --- a/docs/advanced/input_files/input-main.md +++ b/docs/advanced/input_files/input-main.md @@ -1342,7 +1342,7 @@ ### xc_nonlocal - **Type**: String -- **Description**: Select an optional libvdwxc-backed nonlocal exchange-correlation correction. `none` disables the correction. The current rVV10 interface requires a CMake build with `ENABLE_LIBXC=ON` and `ENABLE_LIBVDWXC=ON`, `dft_functional=GGA_X_RPW86+GGA_C_PBE`, fixed b=6.3 and C=0.0093, and a single-process, CPU double-precision, plane-wave, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, LCAO, GPU, reduced-gamma layouts, ultrasoft/PAW pseudopotentials, spin-polarized calculations and pairwise vdW corrections are not part of this interface. +- **Description**: Select an optional libvdwxc-backed nonlocal exchange-correlation correction. `none` disables the correction. The current rVV10 interface requires a CMake build with `ENABLE_LIBXC=ON` and `ENABLE_LIBVDWXC=ON`, `dft_functional=GGA_X_RPW86+GGA_C_PBE`, fixed b=6.3 and C=0.0093, and a single-process, CPU double-precision, plane-wave, single-density-grid, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, LCAO, GPU, reduced-gamma layouts, ultrasoft/PAW pseudopotentials, spin-polarized calculations and pairwise vdW corrections are not part of this interface. - **Default**: none ### rvv10_b diff --git a/docs/developers_guide/nonlocal_xc.md b/docs/developers_guide/nonlocal_xc.md index 4f4262f954f..ef4f49a66a0 100644 --- a/docs/developers_guide/nonlocal_xc.md +++ b/docs/developers_guide/nonlocal_xc.md @@ -17,12 +17,14 @@ rvv10_c 0.0093 The first adapter requires both Libxc and libvdwxc at build time. It accepts a CPU, double-precision, full-complex plane-wave SCF calculation with one MPI -rank, `nspin=1`, and norm-conserving pseudopotentials. It returns the nonlocal -energy, the local potential, and the valence `vtxc` contraction. The parser -rejects forces, stress, relaxation, molecular dynamics, LCAO, GPU, gamma-only -layouts, spin-polarized inputs, ultrasoft/PAW pseudopotentials, MPI runs with -more than one rank, and pairwise D2/D3/D4 corrections. These are follow-up -interfaces, not silently approximated results. +rank, `nspin=1`, a single density grid, and norm-conserving pseudopotentials. +It returns the nonlocal energy, the local potential, and the valence `vtxc` +contraction. The parser rejects explicit double-grid inputs (for example, +`ecutrho` or `ndx/ndy/ndz` settings that create a separate smooth grid), +forces, stress, relaxation, molecular dynamics, LCAO, GPU, gamma-only layouts, +spin-polarized inputs, ultrasoft/PAW pseudopotentials, MPI runs with more than +one rank, and pairwise D2/D3/D4 corrections. These are follow-up interfaces, +not silently approximated results. `xc_nonlocal=rvv10` must not be encoded as `dft_functional=RVV10`, and it must not be combined with a semilocal functional that already contains a nonlocal diff --git a/docs/parameters.yaml b/docs/parameters.yaml index 2e59feba5e3..1a0c48bd87f 100644 --- a/docs/parameters.yaml +++ b/docs/parameters.yaml @@ -672,7 +672,7 @@ parameters: category: Electronic structure type: String description: | - Select an optional libvdwxc-backed nonlocal exchange-correlation correction. `none` disables the correction. The current rVV10 interface requires a CMake build with `ENABLE_LIBXC=ON` and `ENABLE_LIBVDWXC=ON`, `dft_functional=GGA_X_RPW86+GGA_C_PBE`, fixed b=6.3 and C=0.0093, and a single-process, CPU double-precision, plane-wave, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, LCAO, GPU, reduced-gamma layouts, ultrasoft/PAW pseudopotentials, spin-polarized calculations and pairwise vdW corrections are not part of this interface. + Select an optional libvdwxc-backed nonlocal exchange-correlation correction. `none` disables the correction. The current rVV10 interface requires a CMake build with `ENABLE_LIBXC=ON` and `ENABLE_LIBVDWXC=ON`, `dft_functional=GGA_X_RPW86+GGA_C_PBE`, fixed b=6.3 and C=0.0093, and a single-process, CPU double-precision, plane-wave, single-density-grid, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, LCAO, GPU, reduced-gamma layouts, ultrasoft/PAW pseudopotentials, spin-polarized calculations and pairwise vdW corrections are not part of this interface. default_value: none unit: "" availability: "" diff --git a/source/source_estate/module_pot/pot_nonlocal_xc.cpp b/source/source_estate/module_pot/pot_nonlocal_xc.cpp index 5d3c593b3b9..202fa6988ce 100644 --- a/source/source_estate/module_pot/pot_nonlocal_xc.cpp +++ b/source/source_estate/module_pot/pot_nonlocal_xc.cpp @@ -29,11 +29,11 @@ void PotNonlocalXC::cal_v_eff(const Charge* chg, const UnitCell* cell, ModuleBas { ModuleBase::TITLE("PotNonlocalXC", "cal_veff"); ModuleBase::timer::start("PotNonlocalXC", "cal_veff"); - if (!chg || !cell || !rho_basis_ || chg->rhopw != rho_basis_ || (chg->nspin != 1 && chg->nspin != 2) + if (!chg || !cell || !rho_basis_ || chg->rhopw != rho_basis_ || chg->nspin != 1 || chg->nrxx != rho_basis_->nrxx || !chg->rho || !chg->rho_core || v_eff.nr != chg->nspin || v_eff.nc != chg->nrxx) { - ModuleBase::WARNING_QUIT("PotNonlocalXC", "nonlocal XC requires a consistent nspin=1 or nspin=2 density basis"); + ModuleBase::WARNING_QUIT("PotNonlocalXC", "the first rVV10 interface requires a consistent nspin=1 density basis"); } std::vector valence(chg->nrxx, 0.0); for (int is = 0; is < chg->nspin; ++is) diff --git a/source/source_io/module_parameter/read_inp_estruc.cpp b/source/source_io/module_parameter/read_inp_estruc.cpp index 6cd705addd6..d4c1c1fde95 100644 --- a/source/source_io/module_parameter/read_inp_estruc.cpp +++ b/source/source_io/module_parameter/read_inp_estruc.cpp @@ -353,7 +353,7 @@ The other way is only available when compiling with LIBXC, and it allows for sup item.annotation = "none; rvv10"; item.category = "Electronic structure"; item.type = "String"; - item.description = R"(Select an optional libvdwxc-backed nonlocal exchange-correlation correction. `none` disables the correction. The current rVV10 interface requires a CMake build with `ENABLE_LIBXC=ON` and `ENABLE_LIBVDWXC=ON`, `dft_functional=GGA_X_RPW86+GGA_C_PBE`, fixed b=6.3 and C=0.0093, and a single-process, CPU double-precision, plane-wave, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, LCAO, GPU, reduced-gamma layouts, ultrasoft/PAW pseudopotentials, spin-polarized calculations and pairwise vdW corrections are not part of this interface.)"; + item.description = R"(Select an optional libvdwxc-backed nonlocal exchange-correlation correction. `none` disables the correction. The current rVV10 interface requires a CMake build with `ENABLE_LIBXC=ON` and `ENABLE_LIBVDWXC=ON`, `dft_functional=GGA_X_RPW86+GGA_C_PBE`, fixed b=6.3 and C=0.0093, and a single-process, CPU double-precision, plane-wave, single-density-grid, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, LCAO, GPU, reduced-gamma layouts, ultrasoft/PAW pseudopotentials, spin-polarized calculations and pairwise vdW corrections are not part of this interface.)"; item.default_value = "none"; item.unit = ""; read_sync_string(input.xc_nonlocal); @@ -398,11 +398,11 @@ The other way is only available when compiling with LIBXC, and it allows for sup "ReadInput", "xc_nonlocal=rvv10 uses fixed parameters rvv10_b=6.3 and rvv10_c=0.0093"); const auto& p = para.input; if (para.globalv.nproc != 1 || p.calculation != "scf" || p.esolver_type != "ksdft" || p.basis_type != "pw" || p.device != "cpu" - || p.precision != "double" || p.nspin != 1 || p.cal_force || p.cal_stress + || p.precision != "double" || p.nspin != 1 || p.cal_force || p.cal_stress || para.sys.double_grid || p.gamma_only || p.vdw_method != "none" || !p.vl_in_h) ModuleBase::WARNING_QUIT("ReadInput", "xc_nonlocal=rvv10 supports only one MPI rank with CPU double PW nspin=1 " - "serial SCF without forces, stress, gamma_only or other vdW corrections"); + "serial SCF with a single density grid, without forces, stress, gamma_only or other vdW corrections"); }; this->add_item(item); } diff --git a/tests/rvv10/test_libvdwxc_contract.py b/tests/rvv10/test_libvdwxc_contract.py index 710710155f2..5e3a7237d20 100644 --- a/tests/rvv10/test_libvdwxc_contract.py +++ b/tests/rvv10/test_libvdwxc_contract.py @@ -50,6 +50,15 @@ def test_input_path_requires_both_semilocal_and_nonlocal_dependencies(self): cmake = (ROOT / "CMakeLists.txt").read_text() self.assertIn("#if !defined(__LIBXC) || !defined(__LIBVDWXC)", source) self.assertIn("ENABLE_LIBVDWXC currently requires ENABLE_LIBXC=ON", cmake) + self.assertIn("para.sys.double_grid", source) + self.assertIn("single density grid", source) + parameters = (ROOT / "docs/parameters.yaml").read_text() + self.assertIn("single-density-grid", parameters) + + def test_host_component_matches_the_declared_first_pr_spin_scope(self): + source = (ROOT / "source/source_estate/module_pot/pot_nonlocal_xc.cpp").read_text() + self.assertIn("chg->nspin != 1", source) + self.assertIn("first rVV10 interface requires a consistent nspin=1 density basis", source) def test_docs_do_not_describe_a_bundled_native_kernel(self): docs = (ROOT / "docs/developers_guide/nonlocal_xc.md").read_text() From e65e07cb1da78f4c0160a87ccfdc3ace10dead9e Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 15:13:39 +0800 Subject: [PATCH 12/16] fix(xc): preserve cell orientation in libvdwxc adapter --- docs/advanced/input_files/input-main.md | 2 +- docs/developers_guide/nonlocal_xc.md | 8 ++++--- docs/parameters.yaml | 2 +- .../module_pot/pot_nonlocal_xc.cpp | 4 ++-- .../module_vdw/nonlocal_xc_libvdwxc.cpp | 23 +++++++++++-------- .../test/test_nonlocal_xc_libvdwxc.cpp | 20 +++++++++++++--- .../module_parameter/read_inp_estruc.cpp | 2 +- tests/rvv10/test_libvdwxc_contract.py | 9 ++++++++ 8 files changed, 50 insertions(+), 20 deletions(-) diff --git a/docs/advanced/input_files/input-main.md b/docs/advanced/input_files/input-main.md index ac552e268f2..e0e29b3e0d8 100644 --- a/docs/advanced/input_files/input-main.md +++ b/docs/advanced/input_files/input-main.md @@ -1342,7 +1342,7 @@ ### xc_nonlocal - **Type**: String -- **Description**: Select an optional libvdwxc-backed nonlocal exchange-correlation correction. `none` disables the correction. The current rVV10 interface requires a CMake build with `ENABLE_LIBXC=ON` and `ENABLE_LIBVDWXC=ON`, `dft_functional=GGA_X_RPW86+GGA_C_PBE`, fixed b=6.3 and C=0.0093, and a single-process, CPU double-precision, plane-wave, single-density-grid, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, LCAO, GPU, reduced-gamma layouts, ultrasoft/PAW pseudopotentials, spin-polarized calculations and pairwise vdW corrections are not part of this interface. +- **Description**: Select an optional libvdwxc-backed nonlocal exchange-correlation correction. `none` disables the correction. The current rVV10 interface requires a CMake build with `ENABLE_LIBXC=ON` and `ENABLE_LIBVDWXC=ON`, `dft_functional=GGA_X_RPW86+GGA_C_PBE`, fixed b=6.3 and C=0.0093, and a single-process, CPU double-precision, plane-wave, single-density-grid, right-handed-cell, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, LCAO, GPU, reduced-gamma layouts, ultrasoft/PAW pseudopotentials, spin-polarized calculations and pairwise vdW corrections are not part of this interface. - **Default**: none ### rvv10_b diff --git a/docs/developers_guide/nonlocal_xc.md b/docs/developers_guide/nonlocal_xc.md index ef4f49a66a0..a0d97051182 100644 --- a/docs/developers_guide/nonlocal_xc.md +++ b/docs/developers_guide/nonlocal_xc.md @@ -89,9 +89,11 @@ to a special rVV10 branch in the generic SCF solver. ## Numerical and layout contract The serial API receives an ABACUS full real-space grid with z as the fastest -index. The unit-cell matrix is passed column by column, and the adapter checks -the grid dimensions, cell identity, precision, device, gamma flag, rank count, -density sizes and finite values before calling the library. It forms +index. ABACUS stores lattice vectors as rows, while the libvdwxc C API groups +`C00,C10,C20` as one vector; the adapter therefore passes the three ABACUS rows +in those groups and rejects left-handed cells. It also checks the grid +dimensions, cell identity, precision, device, gamma flag, rank count, density +sizes and finite values before calling the library. It forms `sigma=|grad(n_total)|^2`, converts the library Hartree outputs to ABACUS Ry, and uses the same reciprocal-space projection for the gradient and divergence. diff --git a/docs/parameters.yaml b/docs/parameters.yaml index 1a0c48bd87f..c7966f1b853 100644 --- a/docs/parameters.yaml +++ b/docs/parameters.yaml @@ -672,7 +672,7 @@ parameters: category: Electronic structure type: String description: | - Select an optional libvdwxc-backed nonlocal exchange-correlation correction. `none` disables the correction. The current rVV10 interface requires a CMake build with `ENABLE_LIBXC=ON` and `ENABLE_LIBVDWXC=ON`, `dft_functional=GGA_X_RPW86+GGA_C_PBE`, fixed b=6.3 and C=0.0093, and a single-process, CPU double-precision, plane-wave, single-density-grid, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, LCAO, GPU, reduced-gamma layouts, ultrasoft/PAW pseudopotentials, spin-polarized calculations and pairwise vdW corrections are not part of this interface. + Select an optional libvdwxc-backed nonlocal exchange-correlation correction. `none` disables the correction. The current rVV10 interface requires a CMake build with `ENABLE_LIBXC=ON` and `ENABLE_LIBVDWXC=ON`, `dft_functional=GGA_X_RPW86+GGA_C_PBE`, fixed b=6.3 and C=0.0093, and a single-process, CPU double-precision, plane-wave, single-density-grid, right-handed-cell, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, LCAO, GPU, reduced-gamma layouts, ultrasoft/PAW pseudopotentials, spin-polarized calculations and pairwise vdW corrections are not part of this interface. default_value: none unit: "" availability: "" diff --git a/source/source_estate/module_pot/pot_nonlocal_xc.cpp b/source/source_estate/module_pot/pot_nonlocal_xc.cpp index 202fa6988ce..b7443f627a9 100644 --- a/source/source_estate/module_pot/pot_nonlocal_xc.cpp +++ b/source/source_estate/module_pot/pot_nonlocal_xc.cpp @@ -28,7 +28,7 @@ PotNonlocalXC::~PotNonlocalXC() = default; void PotNonlocalXC::cal_v_eff(const Charge* chg, const UnitCell* cell, ModuleBase::matrix& v_eff) { ModuleBase::TITLE("PotNonlocalXC", "cal_veff"); - ModuleBase::timer::start("PotNonlocalXC", "cal_veff"); + ModuleBase::timer::start("PotNonlocalXC", "cal_v_eff"); if (!chg || !cell || !rho_basis_ || chg->rhopw != rho_basis_ || chg->nspin != 1 || chg->nrxx != rho_basis_->nrxx || !chg->rho || !chg->rho_core || v_eff.nr != chg->nspin || v_eff.nc != chg->nrxx) @@ -63,6 +63,6 @@ void PotNonlocalXC::cal_v_eff(const Charge* chg, const UnitCell* cell, ModuleBas for (int is = 0; is < chg->nspin; ++is) for (int r = 0; r < chg->nrxx; ++r) v_eff(is, r) += nl.potential[r]; - ModuleBase::timer::end("PotNonlocalXC", "cal_veff"); + ModuleBase::timer::end("PotNonlocalXC", "cal_v_eff"); } } // namespace elecstate diff --git a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp index 0420c228ccd..09e6b037a4e 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp +++ b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp @@ -16,25 +16,30 @@ namespace { void check_serial_basis(const ModulePW::PW_Basis& pw) { + const double determinant = pw.latvec.Det(); if (pw.gamma_only || pw.get_device() != "cpu" || pw.get_precision() != "double" || pw.poolnproc != 1 || pw.nrxx != pw.nxyz || pw.nx <= 0 || pw.ny <= 0 || pw.nz <= 0 || pw.nxyz <= 0 || pw.npw <= 0 - || !std::isfinite(pw.omega) || pw.omega <= 0.0 || !std::isfinite(pw.tpiba) || pw.tpiba <= 0.0) + || !std::isfinite(pw.omega) || pw.omega <= 0.0 || !std::isfinite(determinant) || determinant <= 0.0 + || !std::isfinite(pw.tpiba) || pw.tpiba <= 0.0) { - throw std::invalid_argument("libvdwxc rVV10 requires one-process CPU/double full-complex PW density grid"); + throw std::invalid_argument( + "libvdwxc rVV10 requires one-process CPU/double full-complex PW density grid and a right-handed cell"); } } -std::array cell_columns(const ModulePW::PW_Basis& pw) +std::array cell_vectors(const ModulePW::PW_Basis& pw) { const auto& a = pw.latvec; + // ABACUS stores a1, a2, a3 as rows. The C API groups C00,C10,C20 as + // one vector, so pass the ABACUS rows consecutively in those groups. return {a.e11 * pw.lat0, - a.e21 * pw.lat0, - a.e31 * pw.lat0, a.e12 * pw.lat0, - a.e22 * pw.lat0, - a.e32 * pw.lat0, a.e13 * pw.lat0, + a.e21 * pw.lat0, + a.e22 * pw.lat0, a.e23 * pw.lat0, + a.e31 * pw.lat0, + a.e32 * pw.lat0, a.e33 * pw.lat0}; } @@ -76,7 +81,7 @@ LibvdwxcNonlocalXC::LibvdwxcNonlocalXC(const ModulePW::PW_Basis& pw, double b, d throw std::invalid_argument("libvdwxc rejected the rVV10 b/C parameters"); } - cell_ = cell_columns(pw); + cell_ = cell_vectors(pw); // libvdwxc names the arguments by matrix columns (C00,C10,C20,...). vdwxc_set_unit_cell(data_, pw.nx, @@ -111,7 +116,7 @@ NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, const std::vector& valence_density) const { check_serial_basis(pw); - const std::array current_cell = cell_columns(pw); + const std::array current_cell = cell_vectors(pw); const double omega_abs = std::abs(pw.omega); const double initialized_omega_abs = std::abs(omega_); double volume_scale = 1.0; diff --git a/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp b/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp index 6f856cf88f9..0b0c3cd9532 100644 --- a/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp +++ b/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include namespace @@ -74,9 +75,11 @@ TEST_F(LibvdwxcAdapterTest, MatchesDirectSerialApiForNonuniformDensity) ASSERT_EQ(vdwxc_set_rvv10_parameters(direct, b, c), 0); const auto& a = pw.latvec; vdwxc_set_unit_cell(direct, pw.nx, pw.ny, pw.nz, - a.e11 * pw.lat0, a.e21 * pw.lat0, a.e31 * pw.lat0, - a.e12 * pw.lat0, a.e22 * pw.lat0, a.e32 * pw.lat0, - a.e13 * pw.lat0, a.e23 * pw.lat0, a.e33 * pw.lat0); + // libvdwxc stores each C00,C10,C20 group as one + // lattice vector; ABACUS stores lattice vectors in rows. + a.e11 * pw.lat0, a.e12 * pw.lat0, a.e13 * pw.lat0, + a.e21 * pw.lat0, a.e22 * pw.lat0, a.e23 * pw.lat0, + a.e31 * pw.lat0, a.e32 * pw.lat0, a.e33 * pw.lat0); vdwxc_init_serial(direct); std::vector> total_g(pw.npw); @@ -116,6 +119,17 @@ TEST_F(LibvdwxcAdapterTest, MatchesDirectSerialApiForNonuniformDensity) vdwxc_finalize(&direct); } +TEST_F(LibvdwxcAdapterTest, RejectsLeftHandedCell) +{ + ModulePW::PW_Basis reflected("cpu", "double"); + const ModuleBase::Matrix3 lattice(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, -2.0); + reflected.initgrids(10.0, lattice, 6, 8, 12); + reflected.initparameters(false, 4.0, 1, true); + reflected.setuptransform(); + reflected.collect_local_pw(); + EXPECT_THROW((vdw::LibvdwxcNonlocalXC(reflected, 6.3, 0.0093)), std::invalid_argument); +} + TEST_F(LibvdwxcAdapterTest, PotentialMatchesFiniteDifferenceOfEnergy) { const double b = 6.3; diff --git a/source/source_io/module_parameter/read_inp_estruc.cpp b/source/source_io/module_parameter/read_inp_estruc.cpp index d4c1c1fde95..98a2a116c7e 100644 --- a/source/source_io/module_parameter/read_inp_estruc.cpp +++ b/source/source_io/module_parameter/read_inp_estruc.cpp @@ -353,7 +353,7 @@ The other way is only available when compiling with LIBXC, and it allows for sup item.annotation = "none; rvv10"; item.category = "Electronic structure"; item.type = "String"; - item.description = R"(Select an optional libvdwxc-backed nonlocal exchange-correlation correction. `none` disables the correction. The current rVV10 interface requires a CMake build with `ENABLE_LIBXC=ON` and `ENABLE_LIBVDWXC=ON`, `dft_functional=GGA_X_RPW86+GGA_C_PBE`, fixed b=6.3 and C=0.0093, and a single-process, CPU double-precision, plane-wave, single-density-grid, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, LCAO, GPU, reduced-gamma layouts, ultrasoft/PAW pseudopotentials, spin-polarized calculations and pairwise vdW corrections are not part of this interface.)"; + item.description = R"(Select an optional libvdwxc-backed nonlocal exchange-correlation correction. `none` disables the correction. The current rVV10 interface requires a CMake build with `ENABLE_LIBXC=ON` and `ENABLE_LIBVDWXC=ON`, `dft_functional=GGA_X_RPW86+GGA_C_PBE`, fixed b=6.3 and C=0.0093, and a single-process, CPU double-precision, plane-wave, single-density-grid, right-handed-cell, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, LCAO, GPU, reduced-gamma layouts, ultrasoft/PAW pseudopotentials, spin-polarized calculations and pairwise vdW corrections are not part of this interface.)"; item.default_value = "none"; item.unit = ""; read_sync_string(input.xc_nonlocal); diff --git a/tests/rvv10/test_libvdwxc_contract.py b/tests/rvv10/test_libvdwxc_contract.py index 5e3a7237d20..c26e4f4e7ac 100644 --- a/tests/rvv10/test_libvdwxc_contract.py +++ b/tests/rvv10/test_libvdwxc_contract.py @@ -59,6 +59,15 @@ def test_host_component_matches_the_declared_first_pr_spin_scope(self): source = (ROOT / "source/source_estate/module_pot/pot_nonlocal_xc.cpp").read_text() self.assertIn("chg->nspin != 1", source) self.assertIn("first rVV10 interface requires a consistent nspin=1 density basis", source) + self.assertIn('timer::start("PotNonlocalXC", "cal_v_eff")', source) + self.assertIn('timer::end("PotNonlocalXC", "cal_v_eff")', source) + + def test_adapter_uses_row_vectors_and_rejects_left_handed_cells(self): + source = (ROOT / "source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp").read_text() + self.assertIn("const double determinant = pw.latvec.Det();", source) + self.assertIn("determinant <= 0.0", source) + self.assertIn("a.e11 * pw.lat0,\n a.e12 * pw.lat0,\n a.e13 * pw.lat0", source) + self.assertIn("right-handed cell", source) def test_docs_do_not_describe_a_bundled_native_kernel(self): docs = (ROOT / "docs/developers_guide/nonlocal_xc.md").read_text() From c23f000cbfed0438edd64c642b6ff54a541a6b5d Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 18:03:49 +0800 Subject: [PATCH 13/16] refactor(xc): simplify libvdwxc adapter after review --- CMakeLists.txt | 44 +----- docs/advanced/install.md | 30 +++-- docs/developers_guide/index.rst | 1 - docs/developers_guide/nonlocal_xc.md | 126 ------------------ generate_build_info.sh | 14 +- source/CMakeLists.txt | 4 - .../module_pot/pot_nonlocal_xc.cpp | 13 +- .../module_vdw/nonlocal_xc_libvdwxc.cpp | 23 ++-- .../test/test_nonlocal_xc_libvdwxc.cpp | 74 +++++++++- tests/rvv10/CMakeLists.txt | 14 +- tests/rvv10/README.md | 34 +++-- tests/rvv10/test_libvdwxc_contract.py | 90 ++----------- 12 files changed, 156 insertions(+), 311 deletions(-) delete mode 100644 docs/developers_guide/nonlocal_xc.md diff --git a/CMakeLists.txt b/CMakeLists.txt index b4f757017cb..dd8b64261d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -625,49 +625,9 @@ if(ENABLE_LIBVDWXC) "ENABLE_LIBVDWXC currently requires ENABLE_LIBXC=ON because the first " "rVV10 interface uses Libxc for the semilocal RPW86+PBE part.") endif() - # libvdwxc's rVV10 API is currently available from its development branch; - # keep discovery explicit so a normal build never acquires this dependency. - # The package target carries FFTW/PFFT and math transitive dependencies for - # static builds; do not replace it with a bare -lvdwxc lookup. + # The config target supplies headers and transitive FFT dependencies. + # rVV10 currently requires the development revision documented in install.md. find_package(libvdwxc CONFIG REQUIRED) - if(NOT TARGET libvdwxc::vdwxc) - message(FATAL_ERROR - "ENABLE_LIBVDWXC requires the libvdwxc::vdwxc CMake target. " - "Use the libvdwxc development build that includes the rVV10 API.") - endif() - get_target_property(LIBVDWXC_INCLUDE_DIRS libvdwxc::vdwxc INTERFACE_INCLUDE_DIRECTORIES) - if(NOT LIBVDWXC_INCLUDE_DIRS) - message(FATAL_ERROR "libvdwxc::vdwxc does not expose its include directory") - endif() - # Installed packages expose a concrete include path, while build-tree - # targets may expose generator expressions. Strip those expressions before - # probing so configuration gives a useful error instead of a later include - # failure. - set(_libvdwxc_include_hints) - foreach(_include_dir IN LISTS LIBVDWXC_INCLUDE_DIRS) - string(GENEX_STRIP "${_include_dir}" _include_dir_stripped) - if(IS_DIRECTORY "${_include_dir_stripped}") - list(APPEND _libvdwxc_include_hints "${_include_dir_stripped}") - endif() - endforeach() - unset(LIBVDWXC_HEADER_DIR CACHE) - find_path(LIBVDWXC_HEADER_DIR NAMES vdwxc.h HINTS ${_libvdwxc_include_hints} - NO_DEFAULT_PATH) - if(NOT LIBVDWXC_HEADER_DIR) - message(FATAL_ERROR "libvdwxc::vdwxc does not provide vdwxc.h") - endif() - file(READ "${LIBVDWXC_HEADER_DIR}/vdwxc.h" LIBVDWXC_HEADER_TEXT) - if(NOT LIBVDWXC_HEADER_TEXT MATCHES "VDWXC_RVV10" - OR NOT LIBVDWXC_HEADER_TEXT MATCHES "vdwxc_new" - OR NOT LIBVDWXC_HEADER_TEXT MATCHES "vdwxc_set_rvv10_parameters" - OR NOT LIBVDWXC_HEADER_TEXT MATCHES "vdwxc_set_unit_cell" - OR NOT LIBVDWXC_HEADER_TEXT MATCHES "vdwxc_init_serial" - OR NOT LIBVDWXC_HEADER_TEXT MATCHES "vdwxc_calculate" - OR NOT LIBVDWXC_HEADER_TEXT MATCHES "vdwxc_finalize") - message(FATAL_ERROR - "The selected libvdwxc headers do not provide the complete serial rVV10 API; " - "use the pinned development build containing MR !32.") - endif() endif() if(DEFINED DeePMD_DIR) diff --git a/docs/advanced/install.md b/docs/advanced/install.md index 70537429c04..6a255778943 100644 --- a/docs/advanced/install.md +++ b/docs/advanced/install.md @@ -106,20 +106,34 @@ The first rVV10 integration uses the external [libvdwxc](https://gitlab.com/libv Build and install the pinned libvdwxc development branch first (MR !32, `rvv10-cmake`, commit `91ef4820f3270e22b35fddc2ab4cab8efa4b6d79`), then pass its CMake prefix to -ABACUS together with Libxc: +ABACUS together with Libxc. Use standalone FFTW for both programs: with GCC +11.2.1 the pinned library passes all eight tests using FFTW 3.3.11, whereas its +MKL 2022.2.1 compatibility backend fails 69 finite-difference comparisons. +The rVV10 source is identical in those builds. To use the validated serial +configuration, leave `MKLROOT`/`MKL_ROOT` unset and select OpenBLAS/LAPACK plus +FFTW through ABACUS's existing dependency options: ```bash cmake -B build-libvdwxc -DBUILD_TESTING=ON \ - -DENABLE_MPI=OFF \ + -DENABLE_MPI=OFF -DENABLE_LCAO=OFF \ -DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON \ - -DCMAKE_PREFIX_PATH=/path/to/libvdwxc-prefix + -DCMAKE_PREFIX_PATH="/path/to/libvdwxc-prefix;/path/to/libxc-prefix" \ + -DFFTW3_DIR=/path/to/fftw-prefix \ + -DBLA_VENDOR=OpenBLAS \ + -DBLAS_DIR=/path/to/openblas-prefix \ + -DLAPACK_DIR=/path/to/openblas-prefix ``` -The first host adapter is limited to CPU, double-precision, full-complex PW, -one process, `nspin=1`, norm-conserving pseudopotentials and SCF energy/potential -evaluation. It is not available through the legacy Makefile. See -`docs/developers_guide/nonlocal_xc.md` for the exact dependency commit and -runtime restrictions. +Enable the correction with `xc_nonlocal rvv10` and explicitly select +`dft_functional GGA_X_RPW86+GGA_C_PBE`; this first parameterization uses +`rvv10_b 6.3` and `rvv10_c 0.0093`. + +The first adapter supports CPU double-precision, full-complex PW, one process, +`nspin=1`, a single density grid, a right-handed cell and norm-conserving +pseudopotentials for SCF energy/potential evaluation. Other spin/layout modes, +forces, stress, relaxation and pairwise D2/D3/D4 combinations are rejected. +The legacy Makefile does not build this backend. See `tests/rvv10/README.md` +for the numerical tests and host/backend accounting. ## Build with JSON support diff --git a/docs/developers_guide/index.rst b/docs/developers_guide/index.rst index 9cba6711c10..80224240428 100644 --- a/docs/developers_guide/index.rst +++ b/docs/developers_guide/index.rst @@ -12,4 +12,3 @@ This section provides guidelines and resources for developers working on the ABA basic_types_class.md input_availability.md agent_governance.md - nonlocal_xc.md diff --git a/docs/developers_guide/nonlocal_xc.md b/docs/developers_guide/nonlocal_xc.md deleted file mode 100644 index a0d97051182..00000000000 --- a/docs/developers_guide/nonlocal_xc.md +++ /dev/null @@ -1,126 +0,0 @@ -# Density-grid nonlocal exchange-correlation - -ABACUS treats a density-grid nonlocal correction as an additive component next -to the ordinary semilocal XC term. The first implementation uses the rVV10 -backend from libvdwxc; ABACUS does not carry a second rVV10 kernel. - -## Input and declared scope - -The input is deliberately separate from the semilocal functional: - -```text -dft_functional GGA_X_RPW86+GGA_C_PBE -xc_nonlocal rvv10 -rvv10_b 6.3 -rvv10_c 0.0093 -``` - -The first adapter requires both Libxc and libvdwxc at build time. It accepts a -CPU, double-precision, full-complex plane-wave SCF calculation with one MPI -rank, `nspin=1`, a single density grid, and norm-conserving pseudopotentials. -It returns the nonlocal energy, the local potential, and the valence `vtxc` -contraction. The parser rejects explicit double-grid inputs (for example, -`ecutrho` or `ndx/ndy/ndz` settings that create a separate smooth grid), -forces, stress, relaxation, molecular dynamics, LCAO, GPU, gamma-only layouts, -spin-polarized inputs, ultrasoft/PAW pseudopotentials, MPI runs with more than -one rank, and pairwise D2/D3/D4 corrections. These are follow-up interfaces, -not silently approximated results. - -`xc_nonlocal=rvv10` must not be encoded as `dft_functional=RVV10`, and it must -not be combined with a semilocal functional that already contains a nonlocal -vdW term. The existing pairwise `vdw_method` path remains independent and is -mutually rejected by the input check. - -## Host/backend boundary - -`Potential` registers the existing semilocal `xc` component first. If -`xc_nonlocal` is enabled, it then registers the generic `xc_nonlocal` -component. `PotNonlocalXC` only adds the backend's energy, potential and -`vtxc`; it never replaces or recomputes the semilocal term. - -The host-side interface lives in -`source/source_hamilt/module_vdw/nonlocal_xc.h`. The only rVV10-specific -implementation in ABACUS is -`nonlocal_xc_libvdwxc.{h,cpp}`. The adapter reuses -`PW_Basis::real2recip`, `XC_Functional::grad_rho` and -`XC_Functional::grad_dot`; libvdwxc owns the kernel table, interpolation and -nonlocal convolution. - -For one SCF update the data path is: - -```text -rho_total = rho_valence + rho_core - -> grad(rho_total), sigma = |grad(rho_total)|^2 - -> libvdwxc rVV10 energy, dE/dn and dE/dsigma - -> ABACUS grad_dot for div(2*dE/dsigma * grad(rho_total)) - -> v_nl in Ry, etxc/vtxc accumulation, and addition to every spin channel -``` - -The library already includes the rVV10 beta compensation. ABACUS therefore -does not add beta a second time. The total density is used by the backend; -`vtxc` is contracted with the valence density, matching the host XC energy -bookkeeping. - -## CMake and dependency contract - -Enable the optional backend with a CMake package that exports -`libvdwxc::vdwxc`: - -```bash -cmake -B build-libvdwxc -DBUILD_TESTING=ON \ - -DENABLE_MPI=OFF \ - -DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON \ - -DCMAKE_PREFIX_PATH=/path/to/libvdwxc-prefix -``` - -The current upstream MR is still Draft. The integration was checked against -the `rvv10-cmake` branch at commit -`91ef4820f3270e22b35fddc2ab4cab8efa4b6d79`; a PR must keep this pin (or a -newer reviewed commit), record the dependency's CTest result, and never call -the Draft API a stable release. Configuration verifies the imported target, -the header, and every serial API symbol used by the adapter. Legacy Makefile -builds do not provide this optional dependency; `xc_nonlocal=rvv10` fails with -a clear input diagnostic there. - -The feature is included in the CMake build summary as `libvdwxc`. The adapter -is attached to `source_hamilt/module_vdw`, not to the semilocal XC evaluator or -to a special rVV10 branch in the generic SCF solver. - -## Numerical and layout contract - -The serial API receives an ABACUS full real-space grid with z as the fastest -index. ABACUS stores lattice vectors as rows, while the libvdwxc C API groups -`C00,C10,C20` as one vector; the adapter therefore passes the three ABACUS rows -in those groups and rejects left-handed cells. It also checks the grid -dimensions, cell identity, precision, device, gamma flag, rank count, density -sizes and finite values before calling the library. It forms -`sigma=|grad(n_total)|^2`, converts the library Hartree outputs to ABACUS Ry, -and uses the same reciprocal-space projection for the gradient and divergence. - -The adapter is intentionally not thread-safe through a shared evaluator object; -one evaluator belongs to one potential instance. MPI/PFFT ownership, spin -interfaces, force/stress derivatives and GPU/LCAO data layouts need separate -API and numerical tests before being enabled. - -## Verification - -The host-side Python contract test checks the CMake switch, imported target, -required API symbols and the absence of a duplicate in-tree rVV10 kernel. With -the pinned library installed, the GoogleTest -`MODULE_HAMILT_vdwTest_LIBVDWXC` compares the adapter with a direct serial -libvdwxc call for a nonuniform density and an oblique cell. It checks the -energy, the host-assembled `dedn - div(2*dedsigma*grad(n))` potential, and the -valence `vtxc` contraction. - -Before converting a Draft PR to Ready, run both libraries' tests and add an -ABACUS SCF reference case with the exact executable, pseudopotential hashes, -input, dependency commit, compiler, FFT provider, and numerical tolerances. -The pull request must attach this evidence explicitly; the default CI contract -test does not build the optional external dependency. - -## Follow-up work - -The next PRs can add a libvdwxc MPI/PFFT adapter, ordinary spin, GPU/LCAO -paths, forces and stress, and functional-specific parameter tables. Each -extension should add its own layout, unit, finite-difference and reference -tests before loosening the input checks. diff --git a/generate_build_info.sh b/generate_build_info.sh index 9bb953909a5..48f46416f62 100755 --- a/generate_build_info.sh +++ b/generate_build_info.sh @@ -167,19 +167,9 @@ fi # 6b. libvdwxc # -# The Makefile build does not currently compile the libvdwxc adapter, but the -# generated build-info header must still define every placeholder used by the -# CMake build. Keep the value explicit (rather than leaving the literal -# @ABACUS_LIBVDWXC_VERSION@ marker in the header) and report a detected version -# when a future Makefile configuration provides the dependency. +# The legacy Makefile does not compile the adapter, even if an environment +# variable requests it. Report the compiled feature, not a discovered library. LIBVDWXC_VERSION="no" -if [ "${ENABLE_LIBVDWXC}" == "ON" ]; then - LIBVDWXC_VERSION="yes (version unknown)" - if command -v pkg-config > /dev/null; then - version=$(pkg-config --modversion libvdwxc 2>/dev/null) - if [ -n "$version" ]; then LIBVDWXC_VERSION="yes (v$version)"; fi - fi -fi # 7. ROCm/HIP ROCM_VERSION="no" diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 60661b80c40..ecc4f3908b7 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -276,10 +276,6 @@ set(_abacus_feature_libs set(_abacus_feature_include_dirs) set(_abacus_feature_compile_options) -if(ENABLE_LIBVDWXC) - list(APPEND _abacus_feature_include_dirs ${LIBVDWXC_HEADER_DIR}) -endif() - if(ENABLE_ELPA) list(APPEND _abacus_feature_include_dirs ${ELPA_INCLUDE_DIR}) endif() diff --git a/source/source_estate/module_pot/pot_nonlocal_xc.cpp b/source/source_estate/module_pot/pot_nonlocal_xc.cpp index b7443f627a9..c41f033a01b 100644 --- a/source/source_estate/module_pot/pot_nonlocal_xc.cpp +++ b/source/source_estate/module_pot/pot_nonlocal_xc.cpp @@ -4,7 +4,6 @@ #include "source_base/timer.h" #include "source_base/tool_quit.h" -#include #include namespace elecstate @@ -27,7 +26,7 @@ PotNonlocalXC::~PotNonlocalXC() = default; void PotNonlocalXC::cal_v_eff(const Charge* chg, const UnitCell* cell, ModuleBase::matrix& v_eff) { - ModuleBase::TITLE("PotNonlocalXC", "cal_veff"); + ModuleBase::TITLE("PotNonlocalXC", "cal_v_eff"); ModuleBase::timer::start("PotNonlocalXC", "cal_v_eff"); if (!chg || !cell || !rho_basis_ || chg->rhopw != rho_basis_ || chg->nspin != 1 || chg->nrxx != rho_basis_->nrxx || !chg->rho || !chg->rho_core || v_eff.nr != chg->nspin @@ -42,15 +41,7 @@ void PotNonlocalXC::cal_v_eff(const Charge* chg, const UnitCell* cell, ModuleBas std::vector total = valence; for (int r = 0; r < chg->nrxx; ++r) total[r] += chg->rho_core[r]; - vdw::NonlocalXCResult nl = {0.0, 0.0, std::vector()}; - try - { - nl = backend_->evaluate(*rho_basis_, total, valence); - } - catch (const std::exception& error) - { - ModuleBase::WARNING_QUIT("PotNonlocalXC", error.what()); - } + const vdw::NonlocalXCResult nl = backend_->evaluate(*rho_basis_, total, valence); if (nl.potential.size() != static_cast(chg->nrxx)) { ModuleBase::WARNING_QUIT("PotNonlocalXC", "nonlocal XC backend returned an invalid potential size"); diff --git a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp index 09e6b037a4e..f7529c07334 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp +++ b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp @@ -3,11 +3,11 @@ #ifdef __LIBVDWXC #include "source_basis/module_pw/pw_basis.h" +#include "source_base/tool_quit.h" #include "source_hamilt/module_xc/xc_functional.h" #include #include -#include #include namespace vdw @@ -22,7 +22,7 @@ void check_serial_basis(const ModulePW::PW_Basis& pw) || !std::isfinite(pw.omega) || pw.omega <= 0.0 || !std::isfinite(determinant) || determinant <= 0.0 || !std::isfinite(pw.tpiba) || pw.tpiba <= 0.0) { - throw std::invalid_argument( + ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc rVV10 requires one-process CPU/double full-complex PW density grid and a right-handed cell"); } } @@ -73,12 +73,12 @@ LibvdwxcNonlocalXC::LibvdwxcNonlocalXC(const ModulePW::PW_Basis& pw, double b, d data_ = vdwxc_new(VDWXC_RVV10); if (data_ == nullptr) { - throw std::runtime_error("libvdwxc failed to create an rVV10 evaluator"); + ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc failed to create an rVV10 evaluator"); } if (vdwxc_set_rvv10_parameters(data_, b, c) != 0) { vdwxc_finalize(&data_); - throw std::invalid_argument("libvdwxc rejected the rVV10 b/C parameters"); + ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc rejected the rVV10 b/C parameters"); } cell_ = cell_vectors(pw); @@ -134,7 +134,8 @@ NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, || total_density.size() != static_cast(pw.nrxx) || valence_density.size() != total_density.size()) { - throw std::invalid_argument("libvdwxc rVV10 received a density grid different from its initialized cell"); + ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", + "libvdwxc rVV10 received a density grid different from its initialized cell"); } using Complex = std::complex; @@ -150,14 +151,14 @@ NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, { if (!std::isfinite(value)) { - throw std::invalid_argument("libvdwxc rVV10 received a nonfinite total density"); + ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc rVV10 received a nonfinite total density"); } } for (double value: valence_density) { if (!std::isfinite(value)) { - throw std::invalid_argument("libvdwxc rVV10 received a nonfinite valence density"); + ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc rVV10 received a nonfinite valence density"); } } @@ -177,14 +178,14 @@ NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, dedsigma.data()); if (!std::isfinite(energy_ha)) { - throw std::runtime_error("libvdwxc returned a nonfinite rVV10 energy"); + ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc returned a nonfinite rVV10 energy"); } for (int r = 0; r < pw.nrxx; ++r) { if (!std::isfinite(dedn[r]) || !std::isfinite(dedsigma[r])) { - throw std::runtime_error("libvdwxc returned a nonfinite rVV10 derivative"); + ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc returned a nonfinite rVV10 derivative"); } const double coefficient = 2.0 * dedsigma[r]; field[r].x = coefficient * gradient[r].x; @@ -203,13 +204,13 @@ NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, result.potential[r] = 2.0 * (dedn[r] - divergence[r]); if (!std::isfinite(divergence[r]) || !std::isfinite(result.potential[r])) { - throw std::runtime_error("libvdwxc produced a nonfinite rVV10 potential"); + ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc produced a nonfinite rVV10 potential"); } result.vtxc += dv * valence_density[r] * result.potential[r]; } if (!std::isfinite(result.vtxc)) { - throw std::runtime_error("libvdwxc produced a nonfinite rVV10 vtxc"); + ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc produced a nonfinite rVV10 vtxc"); } return result; } diff --git a/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp b/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp index 0b0c3cd9532..b35868b22ed 100644 --- a/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp +++ b/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include namespace @@ -127,7 +127,77 @@ TEST_F(LibvdwxcAdapterTest, RejectsLeftHandedCell) reflected.initparameters(false, 4.0, 1, true); reflected.setuptransform(); reflected.collect_local_pw(); - EXPECT_THROW((vdw::LibvdwxcNonlocalXC(reflected, 6.3, 0.0093)), std::invalid_argument); + EXPECT_EXIT(vdw::LibvdwxcNonlocalXC(reflected, 6.3, 0.0093), testing::ExitedWithCode(1), ""); +} + +TEST_F(LibvdwxcAdapterTest, RejectsInvalidParametersWithStandardDiagnostic) +{ + EXPECT_EXIT(vdw::LibvdwxcNonlocalXC(pw, 0.0, 0.0093), testing::ExitedWithCode(1), ""); +} + +TEST_F(LibvdwxcAdapterTest, RejectsInvalidDensityWithStandardDiagnostic) +{ + vdw::LibvdwxcNonlocalXC adapter(pw, 6.3, 0.0093); + const std::vector total = density(0.004, 0.006); + std::vector invalid = total; + invalid.pop_back(); + EXPECT_EXIT(adapter.evaluate(pw, invalid, total), testing::ExitedWithCode(1), ""); + invalid = total; + invalid[0] = std::numeric_limits::quiet_NaN(); + EXPECT_EXIT(adapter.evaluate(pw, invalid, total), testing::ExitedWithCode(1), ""); +} + +TEST_F(LibvdwxcAdapterTest, CellMappingPreservesIndependentReciprocalGeometry) +{ + // Both cells have volume sqrt(3)*lat0^3 and |G100|=4*pi/(sqrt(3)*lat0). + // A density depending only on fractional x must therefore give identical + // isotropic nonlocal energy; transposing the oblique cell breaks this equality. + ModulePW::PW_Basis orthogonal("cpu", "double"); + const ModuleBase::Matrix3 lattice(std::sqrt(0.75), 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 2.0); + orthogonal.initgrids(10.0, lattice, pw.nx, pw.ny, pw.nz); + orthogonal.initparameters(false, 4.0, 1, true); + orthogonal.setuptransform(); + orthogonal.collect_local_pw(); + + const double amplitude = 0.003; + std::vector total(pw.nrxx); + for (int x = 0; x < pw.nx; ++x) + for (int y = 0; y < pw.ny; ++y) + for (int z = 0; z < pw.nz; ++z) + { + const int index = (x * pw.ny + y) * pw.nz + z; + const double phase = 2.0 * pi * x / pw.nx; + total[index] = 0.01 + amplitude * std::cos(phase); + } + + // The hand-derived gradient of cos(2*pi*s1) uses b1=(1,1/sqrt(3),0). + std::vector> density_g(pw.npw); + std::vector> gradient(pw.nrxx); + pw.real2recip(total.data(), density_g.data()); + XC_Functional::grad_rho(density_g.data(), gradient.data(), &pw, pw.tpiba); + for (int x = 0; x < pw.nx; ++x) + for (int y = 0; y < pw.ny; ++y) + for (int z = 0; z < pw.nz; ++z) + { + const int index = (x * pw.ny + y) * pw.nz + z; + const double phase = 2.0 * pi * x / pw.nx; + const double derivative = -amplitude * (2.0 * pi / pw.lat0) * std::sin(phase); + EXPECT_NEAR(gradient[index].x, derivative, 2.0e-12); + EXPECT_NEAR(gradient[index].y, derivative / std::sqrt(3.0), 2.0e-12); + EXPECT_NEAR(gradient[index].z, 0.0, 2.0e-12); + } + + vdw::LibvdwxcNonlocalXC oblique_adapter(pw, 6.3, 0.0093); + vdw::LibvdwxcNonlocalXC orthogonal_adapter(orthogonal, 6.3, 0.0093); + const vdw::NonlocalXCResult oblique_result = oblique_adapter.evaluate(pw, total, total); + const vdw::NonlocalXCResult orthogonal_result = orthogonal_adapter.evaluate(orthogonal, total, total); + EXPECT_NEAR(pw.omega, orthogonal.omega, 2.0e-12); + ASSERT_NEAR(oblique_result.energy, orthogonal_result.energy, 2.0e-12); + EXPECT_NEAR(oblique_result.vtxc, orthogonal_result.vtxc, 2.0e-12); + for (int r = 0; r < pw.nrxx; ++r) + { + EXPECT_NEAR(oblique_result.potential[r], orthogonal_result.potential[r], 2.0e-12); + } } TEST_F(LibvdwxcAdapterTest, PotentialMatchesFiniteDifferenceOfEnergy) diff --git a/tests/rvv10/CMakeLists.txt b/tests/rvv10/CMakeLists.txt index 19bd8c4b0db..a9ee1775d36 100644 --- a/tests/rvv10/CMakeLists.txt +++ b/tests/rvv10/CMakeLists.txt @@ -1,8 +1,10 @@ -find_package(Python3 REQUIRED COMPONENTS Interpreter) +find_package(Python3 QUIET COMPONENTS Interpreter) -add_test(NAME RVV10_LIBVDWXC_CONTRACT - COMMAND ${Python3_EXECUTABLE} -m unittest discover - -s ${CMAKE_CURRENT_SOURCE_DIR} -p test_libvdwxc_contract.py) +if(Python3_Interpreter_FOUND) + add_test(NAME RVV10_LIBVDWXC_CONTRACT + COMMAND ${Python3_EXECUTABLE} -m unittest discover + -s ${CMAKE_CURRENT_SOURCE_DIR} -p test_libvdwxc_contract.py) -set_tests_properties(RVV10_LIBVDWXC_CONTRACT PROPERTIES - ENVIRONMENT "OMP_NUM_THREADS=1") + set_tests_properties(RVV10_LIBVDWXC_CONTRACT PROPERTIES + ENVIRONMENT "OMP_NUM_THREADS=1") +endif() diff --git a/tests/rvv10/README.md b/tests/rvv10/README.md index bbc7183c195..b06dc71ea2b 100644 --- a/tests/rvv10/README.md +++ b/tests/rvv10/README.md @@ -1,8 +1,11 @@ # libvdwxc-backed rVV10 checks -This directory checks the host-side contract of the first ABACUS integration. -The numerical rVV10 kernel is provided by the pinned libvdwxc development -branch; ABACUS does not carry a second kernel implementation. +The numerical kernel is provided by libvdwxc MR !32, revision +`91ef4820f3270e22b35fddc2ab4cab8efa4b6d79`. This unreleased API is tested +with standalone FFTW 3.3.11. The same revision's MKL 2022.2.1 compatibility +backend has known finite-difference failures; do not mix both FFT providers +in one ABACUS process. Installation and supported inputs are documented in +`docs/advanced/install.md`. The host adapter is intentionally limited to the current API contract: @@ -13,11 +16,11 @@ The host adapter is intentionally limited to the current API contract: MPI, spin-polarized calculations, GPU/LCAO, gamma-only layouts, ultrasoft/PAW pseudopotentials, forces and stress are rejected by the input contract and are -follow-up work. The current libvdwxc MR is still Draft, so a Ready PR must pin -commit `91ef4820f3270e22b35fddc2ab4cab8efa4b6d79` and show both the library's `ctest -R rvv10` output and the -ABACUS adapter test output. +follow-up work. The initial integration remains Draft pending independent QE +reference data and maintainer agreement on this staged scope. -Configure the host-side contract test with: +The dependency-free test executes the Makefile build-info generator and +checks that an installed library cannot be mistaken for a compiled feature: ```bash cmake -B build -DBUILD_TESTING=ON @@ -27,12 +30,19 @@ ctest --test-dir build -R RVV10_LIBVDWXC_CONTRACT --output-on-failure With the pinned libvdwxc installation available, enable the real adapter test: ```bash -cmake -B build-libvdwxc -DBUILD_TESTING=ON -DENABLE_MPI=OFF -DENABLE_LIBXC=ON \ - -DENABLE_LIBVDWXC=ON -DCMAKE_PREFIX_PATH=/path/to/libvdwxc +# Configure build-libvdwxc with the standalone-FFTW command in install.md. cmake --build build-libvdwxc --target MODULE_HAMILT_vdwTest_LIBVDWXC ctest --test-dir build-libvdwxc -R LIBVDWXC --output-on-failure ``` -The adapter test uses a nonuniform density and an oblique cell. It compares -ABACUS energy, the host-computed `dedn - div(2*dedsigma*grad(n))` potential, -and the valence `vtxc` contraction against the direct serial libvdwxc API. +The C++ tests check energy and potential against the serial API, the potential +by a finite difference of energy, and geometry using equivalent oblique and +orthogonal cells with an analytically matched reciprocal vector. Negative +tests exercise the standard ABACUS error diagnostics. + +The host reuses `PW_Basis`, `grad_rho` and `grad_dot` to supply +`n = n_valence + n_core` and `sigma = |grad(n)|^2` to libvdwxc. The library +includes the beta compensation. The returned derivatives form +`v = 2*(dedn - div(2*dedsigma*grad(n)))` in Ry; `vtxc` contracts this potential +with the valence density. The generic host component adds these quantities +after `PotXC`, while the numerical backend stays in `module_vdw`. diff --git a/tests/rvv10/test_libvdwxc_contract.py b/tests/rvv10/test_libvdwxc_contract.py index c26e4f4e7ac..77612a50c41 100644 --- a/tests/rvv10/test_libvdwxc_contract.py +++ b/tests/rvv10/test_libvdwxc_contract.py @@ -1,6 +1,9 @@ -"""Guard the optional libvdwxc integration contract.""" +"""Exercise the optional-backend build report without external dependencies.""" +import os from pathlib import Path +import subprocess +import tempfile import unittest @@ -8,81 +11,16 @@ class LibvdwxcContract(unittest.TestCase): - def test_optional_backend_has_a_separate_build_switch_and_adapter(self): - cmake = (ROOT / "CMakeLists.txt").read_text() - module_cmake = (ROOT / "source/source_hamilt/module_vdw/CMakeLists.txt").read_text() - adapter = ROOT / "source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp" - self.assertIn("ENABLE_LIBVDWXC", cmake) - self.assertIn("find_package(libvdwxc CONFIG REQUIRED)", cmake) - self.assertIn("libvdwxc::vdwxc", cmake) - self.assertIn("ENABLE_LIBVDWXC", module_cmake) - self.assertIn("nonlocal_xc_libvdwxc.cpp", module_cmake) - self.assertTrue(adapter.is_file()) - text = adapter.read_text() - self.assertIn("vdwxc_calculate", text) - self.assertIn("vdwxc_set_unit_cell", text) - self.assertIn("vdwxc_init_serial", text) - self.assertIn("vdwxc_finalize", text) - adapter_header = (ROOT / "source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h").read_text() - self.assertLess(adapter_header.index("#include "), adapter_header.index("#include ")) - - def test_first_pr_does_not_bundle_a_second_rvv10_kernel(self): - module_cmake = (ROOT / "source/source_hamilt/module_vdw/CMakeLists.txt").read_text() - self.assertNotIn("nonlocal_xc_rvv10.cpp", module_cmake) - self.assertNotIn("nonlocal_xc_rvv10_pw.cpp", module_cmake) - for name in ( - "nonlocal_xc_rvv10.cpp", - "nonlocal_xc_rvv10.h", - "nonlocal_xc_rvv10_pw.cpp", - "nonlocal_xc_rvv10_pw.h", - ): - self.assertFalse((ROOT / "source/source_hamilt/module_vdw" / name).exists(), name) - - def test_configuration_checks_the_complete_serial_rvv10_api(self): - cmake = (ROOT / "CMakeLists.txt").read_text() - for symbol in ("VDWXC_RVV10", "vdwxc_new", "vdwxc_set_rvv10_parameters", "vdwxc_set_unit_cell", - "vdwxc_init_serial", "vdwxc_calculate", "vdwxc_finalize"): - self.assertIn(symbol, cmake) - self.assertIn("NO_DEFAULT_PATH", cmake) - - def test_input_path_requires_both_semilocal_and_nonlocal_dependencies(self): - source = (ROOT / "source/source_io/module_parameter/read_inp_estruc.cpp").read_text() - cmake = (ROOT / "CMakeLists.txt").read_text() - self.assertIn("#if !defined(__LIBXC) || !defined(__LIBVDWXC)", source) - self.assertIn("ENABLE_LIBVDWXC currently requires ENABLE_LIBXC=ON", cmake) - self.assertIn("para.sys.double_grid", source) - self.assertIn("single density grid", source) - parameters = (ROOT / "docs/parameters.yaml").read_text() - self.assertIn("single-density-grid", parameters) - - def test_host_component_matches_the_declared_first_pr_spin_scope(self): - source = (ROOT / "source/source_estate/module_pot/pot_nonlocal_xc.cpp").read_text() - self.assertIn("chg->nspin != 1", source) - self.assertIn("first rVV10 interface requires a consistent nspin=1 density basis", source) - self.assertIn('timer::start("PotNonlocalXC", "cal_v_eff")', source) - self.assertIn('timer::end("PotNonlocalXC", "cal_v_eff")', source) - - def test_adapter_uses_row_vectors_and_rejects_left_handed_cells(self): - source = (ROOT / "source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp").read_text() - self.assertIn("const double determinant = pw.latvec.Det();", source) - self.assertIn("determinant <= 0.0", source) - self.assertIn("a.e11 * pw.lat0,\n a.e12 * pw.lat0,\n a.e13 * pw.lat0", source) - self.assertIn("right-handed cell", source) - - def test_docs_do_not_describe_a_bundled_native_kernel(self): - docs = (ROOT / "docs/developers_guide/nonlocal_xc.md").read_text() - self.assertNotIn("existing native evaluator remains available", docs) - self.assertIn("does not carry a second rVV10 kernel", docs) - - def test_build_info_reports_the_optional_backend(self): - build_info = (ROOT / "source/source_io/build_info.h.in").read_text() - parse_args = (ROOT / "source/source_io/parse_args.cpp").read_text() - collect_build_info = (ROOT / "cmake/CollectBuildInfoVars.cmake").read_text() - make_build_info = (ROOT / "generate_build_info.sh").read_text() - self.assertIn("ABACUS_LIBVDWXC_VERSION", build_info) - self.assertIn('print_info("libvdwxc Support", ABACUS_LIBVDWXC_VERSION)', parse_args) - self.assertIn('libvdwxc = ${ABACUS_LIBVDWXC_VERSION}', collect_build_info) - self.assertIn('@ABACUS_LIBVDWXC_VERSION@', make_build_info) + def test_makefile_reports_only_compiled_libvdwxc_support(self): + environment = os.environ.copy() + environment["ENABLE_LIBVDWXC"] = "ON" + with tempfile.TemporaryDirectory() as directory: + header = Path(directory) / "build_info.h" + subprocess.run( + ["bash", str(ROOT / "generate_build_info.sh"), str(header)], + env=environment, check=True, capture_output=True, text=True, + ) + self.assertIn('#define ABACUS_LIBVDWXC_VERSION "no"', header.read_text()) if __name__ == "__main__": From 9da9d847b8bb6911c70ab30b904db33aaa56441f Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 22:56:57 +0800 Subject: [PATCH 14/16] fix(xc): prohibit copying owned libvdwxc handles --- source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h | 2 ++ .../module_vdw/test/test_nonlocal_xc_libvdwxc.cpp | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h index b5fb5088d98..4b460c4765d 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h +++ b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h @@ -35,6 +35,8 @@ class LibvdwxcNonlocalXC final : public NonlocalXC public: LibvdwxcNonlocalXC(const ModulePW::PW_Basis& pw, double b, double c); ~LibvdwxcNonlocalXC() override; + LibvdwxcNonlocalXC(const LibvdwxcNonlocalXC&) = delete; + LibvdwxcNonlocalXC& operator=(const LibvdwxcNonlocalXC&) = delete; NonlocalXCResult evaluate(const ModulePW::PW_Basis& pw, const std::vector& total_density, diff --git a/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp b/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp index b35868b22ed..ea27dbc1322 100644 --- a/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp +++ b/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include namespace @@ -61,6 +62,12 @@ class LibvdwxcAdapterTest : public testing::Test }; #ifdef __LIBVDWXC +TEST(LibvdwxcOwnershipTest, BackendHandleCannotBeCopied) +{ + EXPECT_FALSE(std::is_copy_constructible::value); + EXPECT_FALSE(std::is_copy_assignable::value); +} + TEST_F(LibvdwxcAdapterTest, MatchesDirectSerialApiForNonuniformDensity) { const double b = 6.3; From c3dedfb7c920e9481984216c4c6c63e404f57fdc Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 23:07:28 +0800 Subject: [PATCH 15/16] test(xc): verify additive nonlocal host bookkeeping --- source/CMakeLists.txt | 22 ++++++ .../test/test_pot_nonlocal_xc.cpp | 70 +++++++++++++++++++ tests/rvv10/README.md | 6 +- 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 source/source_estate/test/test_pot_nonlocal_xc.cpp diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index ecc4f3908b7..588f39c3176 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -721,6 +721,28 @@ if(BUILD_TESTING) # Register after all object-library dependencies exist; the optional adapter # test links the existing PW, INPUT and device targets. add_subdirectory(source_hamilt/module_vdw/test) + if(ENABLE_LIBVDWXC AND NOT ENABLE_MPI AND NOT USE_CUDA AND NOT USE_ROCM) + # Test the additive host with real Charge and UnitCell lifecycles after + # its dependencies exist, without linking the full estate/cell libraries. + AddTest( + TARGET MODULE_ESTATE_pot_nonlocal_xc_LIBVDWXC + LIBS xc_ planewave base parameter device nonlocal_xc + SOURCES + source_estate/test/test_pot_nonlocal_xc.cpp + source_estate/module_pot/pot_nonlocal_xc.cpp + source_estate/module_charge/charge.cpp + source_estate/module_charge/chg_tools.cpp + source_cell/unitcell.cpp + source_cell/atom_spec.cpp + source_cell/atom_pseudo.cpp + source_cell/pseudo.cpp + source_cell/magnetism.cpp + source_cell/sep.cpp + source_cell/sep_cell.cpp + ) + target_link_libraries(MODULE_ESTATE_pot_nonlocal_xc_LIBVDWXC PRIVATE Libxc::xc libvdwxc::vdwxc) + set_tests_properties(MODULE_ESTATE_pot_nonlocal_xc_LIBVDWXC PROPERTIES ENVIRONMENT "OMP_NUM_THREADS=1") + endif() endif() if(ENABLE_COVERAGE) diff --git a/source/source_estate/test/test_pot_nonlocal_xc.cpp b/source/source_estate/test/test_pot_nonlocal_xc.cpp new file mode 100644 index 00000000000..2df6f7ba140 --- /dev/null +++ b/source/source_estate/test/test_pot_nonlocal_xc.cpp @@ -0,0 +1,70 @@ +#include "source_estate/module_pot/pot_nonlocal_xc.h" +#include "source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h" + +#include +#include +#include + +TEST(PotNonlocalXCTest, AddsCorrectionFromValenceAndCoreWithoutReplacingExistingXc) +{ + ModulePW::PW_Basis pw("cpu", "double"); + const ModuleBase::Matrix3 lattice(1.0, 0.0, 0.0, -0.5, std::sqrt(0.75), 0.0, 0.0, 0.0, 2.0); + pw.initgrids(10.0, lattice, 6, 8, 12); + pw.initparameters(false, 4.0, 1, true); + pw.setuptransform(); + pw.collect_local_pw(); + + UnitCell cell; + cell.lat0 = pw.lat0; + cell.latvec = lattice; + cell.omega = pw.omega; + Charge charge; + charge.set_rhopw(&pw); + charge.allocate(1, false, false, 0); + + const double pi = std::acos(-1.0); + std::vector valence(pw.nrxx); + std::vector core(pw.nrxx); + std::vector total(pw.nrxx); + ModuleBase::matrix potential(1, pw.nrxx); + std::vector initial_potential(pw.nrxx); + for (int x = 0; x < pw.nx; ++x) + for (int y = 0; y < pw.ny; ++y) + for (int z = 0; z < pw.nz; ++z) + { + const int r = (x * pw.ny + y) * pw.nz + z; + const double ax = 2.0 * pi * x / pw.nx; + const double ay = 2.0 * pi * y / pw.ny; + const double az = 2.0 * pi * z / pw.nz; + valence[r] = 0.007 + 0.001 * std::cos(ax) + 0.0005 * std::sin(ay + az); + core[r] = 0.003 + 0.0008 * std::cos(ay); + total[r] = valence[r] + core[r]; + charge.rho[0][r] = valence[r]; + charge.rho_core[r] = core[r]; + initial_potential[r] = 0.25 + 0.0001 * r; + potential(0, r) = initial_potential[r]; + } + + // The separately tested adapter supplies the correction; this test checks + // the production host's density construction and additive XC bookkeeping. + vdw::LibvdwxcNonlocalXC reference(pw, 6.3, 0.0093); + const vdw::NonlocalXCResult correction = reference.evaluate(pw, total, valence); + const double initial_energy = -1.25; + const double initial_vtxc = 0.375; + double energy = initial_energy; + double vtxc = initial_vtxc; + elecstate::PotNonlocalXC host(&pw, &energy, &vtxc, "rvv10", 6.3, 0.0093); + host.cal_v_eff(&charge, &cell, potential); + + const double expected_energy = initial_energy + correction.energy; + const double expected_vtxc = initial_vtxc + correction.vtxc; + EXPECT_NEAR(energy, expected_energy, 2.0e-12); + EXPECT_NEAR(vtxc, expected_vtxc, 2.0e-12); + for (int r = 0; r < pw.nrxx; ++r) + { + const double expected_potential = initial_potential[r] + correction.potential[r]; + EXPECT_NEAR(potential(0, r), expected_potential, 2.0e-12); + EXPECT_DOUBLE_EQ(charge.rho[0][r], valence[r]); + EXPECT_DOUBLE_EQ(charge.rho_core[r], core[r]); + } +} diff --git a/tests/rvv10/README.md b/tests/rvv10/README.md index b06dc71ea2b..2ba99173c5e 100644 --- a/tests/rvv10/README.md +++ b/tests/rvv10/README.md @@ -31,7 +31,8 @@ With the pinned libvdwxc installation available, enable the real adapter test: ```bash # Configure build-libvdwxc with the standalone-FFTW command in install.md. -cmake --build build-libvdwxc --target MODULE_HAMILT_vdwTest_LIBVDWXC +cmake --build build-libvdwxc --target MODULE_HAMILT_vdwTest_LIBVDWXC \ + MODULE_ESTATE_pot_nonlocal_xc_LIBVDWXC ctest --test-dir build-libvdwxc -R LIBVDWXC --output-on-failure ``` @@ -39,6 +40,9 @@ The C++ tests check energy and potential against the serial API, the potential by a finite difference of energy, and geometry using equivalent oblique and orthogonal cells with an analytically matched reciprocal vector. Negative tests exercise the standard ABACUS error diagnostics. +The adapter cannot be copied because it owns its library handle. A separate +host test uses real `Charge` and `UnitCell` objects to check valence/core density +construction, additive energy/potential bookkeeping and unchanged input data. The host reuses `PW_Basis`, `grad_rho` and `grad_dot` to supply `n = n_valence + n_core` and `sigma = |grad(n)|^2` to libvdwxc. The library From 95528a1b79d012f8afbfcc67df2aaebb19ba6a30 Mon Sep 17 00:00:00 2001 From: 1 Date: Thu, 15 Oct 2026 00:12:25 +0800 Subject: [PATCH 16/16] docs(xc): pin validated libvdwxc FFT-boundary fix --- docs/advanced/install.md | 12 +++++++----- tests/rvv10/README.md | 10 ++++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/advanced/install.md b/docs/advanced/install.md index 6a255778943..6c2606ab9ab 100644 --- a/docs/advanced/install.md +++ b/docs/advanced/install.md @@ -105,11 +105,13 @@ The first rVV10 integration uses the external [libvdwxc](https://gitlab.com/libv Build and install the pinned libvdwxc development branch first (MR !32, `rvv10-cmake`, commit -`91ef4820f3270e22b35fddc2ab4cab8efa4b6d79`), then pass its CMake prefix to -ABACUS together with Libxc. Use standalone FFTW for both programs: with GCC -11.2.1 the pinned library passes all eight tests using FFTW 3.3.11, whereas its -MKL 2022.2.1 compatibility backend fails 69 finite-difference comparisons. -The rVV10 source is identical in those builds. To use the validated serial +`94410ebdf3dc9615a40f40c3a5b59dbe20a433d2`), then pass its CMake prefix to +ABACUS together with Libxc. This revision fixes Hermitian symmetry on stored +FFT boundary planes in oblique cells. With GCC 11.2.1, its eight library tests +pass using either FFTW 3.3.11 or the MKL 2022.2.1 compatibility backend. +The ABACUS adapter is tested with standalone FFTW for both programs; the +library's MKL tests alone do not validate an MKL-based ABACUS build. Do not +mix the two FFT providers in one process. To use the validated serial configuration, leave `MKLROOT`/`MKL_ROOT` unset and select OpenBLAS/LAPACK plus FFTW through ABACUS's existing dependency options: diff --git a/tests/rvv10/README.md b/tests/rvv10/README.md index 2ba99173c5e..826dff73c6f 100644 --- a/tests/rvv10/README.md +++ b/tests/rvv10/README.md @@ -1,10 +1,12 @@ # libvdwxc-backed rVV10 checks The numerical kernel is provided by libvdwxc MR !32, revision -`91ef4820f3270e22b35fddc2ab4cab8efa4b6d79`. This unreleased API is tested -with standalone FFTW 3.3.11. The same revision's MKL 2022.2.1 compatibility -backend has known finite-difference failures; do not mix both FFT providers -in one ABACUS process. Installation and supported inputs are documented in +`94410ebdf3dc9615a40f40c3a5b59dbe20a433d2`. This revision fixes FFT boundary +symmetry in oblique cells. Its eight library tests pass with both standalone +FFTW 3.3.11 and the MKL 2022.2.1 compatibility backend (GCC 11.2.1). +The ABACUS adapter and host tests are verified with standalone FFTW, not an +MKL-based ABACUS build. Do not mix both FFT providers in one process. +Installation and supported inputs are documented in `docs/advanced/install.md`. The host adapter is intentionally limited to the current API contract: