From a110fc515f0460c89b2ee8baeb74a32f80aefd3b Mon Sep 17 00:00:00 2001 From: 1 Date: Wed, 14 Oct 2026 03:16:16 +0800 Subject: [PATCH 01/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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: From 1b09868931e800929ebc7ff7a3a9f6bccc877fd9 Mon Sep 17 00:00:00 2001 From: 1 Date: Sat, 17 Oct 2026 20:22:09 +0800 Subject: [PATCH 17/28] fix(io): report the first unsupported rVV10 input setting Replace the single combined scope check with per-setting diagnostics that name the parameter to change, drop the rvv10_b/rvv10_c checks already covered by the xc_nonlocal check, give the gamma_only reset its own message, and note the nscf restriction in the parameter description. The reader tests now create mkstemp scratch files under testing::TempDir() so concurrent runs do not share INPUT/KPT names. Co-Authored-By: Claude Fable 5.1 --- .../module_parameter/read_inp_estruc.cpp | 50 +++--- .../source_io/test_serial/read_input_test.cpp | 151 ++++++++++++++++++ 2 files changed, 182 insertions(+), 19 deletions(-) diff --git a/source/source_io/module_parameter/read_inp_estruc.cpp b/source/source_io/module_parameter/read_inp_estruc.cpp index 2762b3e02ad..4956d91da6d 100644 --- a/source/source_io/module_parameter/read_inp_estruc.cpp +++ b/source/source_io/module_parameter/read_inp_estruc.cpp @@ -6,6 +6,9 @@ #include #include #include +#include +#include +#include namespace ModuleIO { @@ -353,7 +356,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, 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.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, non-SCF calculations such as `nscf`, 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); @@ -396,13 +399,32 @@ The other way is only available when compiling with LIBXC, and it allows for sup 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"); + // Report the first unsupported setting so the user knows what to change. 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 || 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 with a single density grid, without forces, stress, gamma_only or other vdW corrections"); + const std::vector> unsupported = { + {para.globalv.nproc != 1, "one MPI rank"}, + {p.calculation != "scf", "calculation=scf"}, + {p.esolver_type != "ksdft", "esolver_type=ksdft"}, + {p.basis_type != "pw", "basis_type=pw"}, + {p.device != "cpu", "device=cpu"}, + {p.precision != "double", "precision=double"}, + {p.nspin != 1, "nspin=1"}, + {p.cal_force, "cal_force=0"}, + {p.cal_stress, "cal_stress=0"}, + {para.sys.double_grid, "a single density grid"}, + {p.gamma_only, "gamma_only=0"}, + {p.vdw_method != "none", "vdw_method=none"}, + {!p.vl_in_h, "vl_in_h=1"}, + }; + for (const auto& entry: unsupported) + { + if (entry.first) + { + const std::string message = std::string("xc_nonlocal=rvv10 supports only ") + entry.second + + " in the current libvdwxc interface"; + ModuleBase::WARNING_QUIT("ReadInput", message); + } + } }; this->add_item(item); } @@ -416,11 +438,6 @@ The other way is only available when compiling with LIBXC, and it allows for sup 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); } { @@ -433,11 +450,6 @@ The other way is only available when compiling with LIBXC, and it allows for sup 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); } { @@ -919,8 +931,8 @@ Note: If gamma_only is set to 1, the KPT file will be overwritten. So make sure 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"); + "xc_nonlocal=rvv10 supports only full k-point PW layouts; " + "gamma_only is not implemented"); } para.input.gamma_only = false; GlobalV::ofs_warning << " WARNING : gamma_only has not been implemented for pw yet" << std::endl; diff --git a/source/source_io/test_serial/read_input_test.cpp b/source/source_io/test_serial/read_input_test.cpp index 236b8e8f87d..9934845cd01 100644 --- a/source/source_io/test_serial/read_input_test.cpp +++ b/source/source_io/test_serial/read_input_test.cpp @@ -6,8 +6,11 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include +#include #include +#include #include +#include // mock namespace GlobalV @@ -78,6 +81,18 @@ class InputTest : public testing::Test GlobalV::NPROC = nproc; } + std::string temporary_file(const std::string& name) + { + std::string filename = testing::TempDir() + name + ".XXXXXX"; + const int fd = mkstemp(&filename[0]); + if (fd == -1) + { + throw std::runtime_error("Cannot create temporary INPUT test file"); + } + EXPECT_EQ(close(fd), 0); + return filename; + } + void write_input(const std::string& filename, const std::string& parameters) { std::ofstream input(filename.c_str()); @@ -212,6 +227,142 @@ TEST_F(InputTest, RejectAutoDevice) EXPECT_TRUE(std::remove("./auto_device_INPUT") == 0); } +TEST_F(InputTest, NonlocalXcDefaultsToNone) +{ + Parameter param; + const std::string filename = temporary_file("abacus_nonlocal_default.INPUT"); + EXPECT_NO_THROW(read_parameters(filename, "", param)); + EXPECT_EQ(param.inp.xc_nonlocal, "none"); +} + +TEST_F(InputTest, NonlocalXcDisabledPreservesSemilocalInput) +{ + Parameter param; + const std::string filename = temporary_file("abacus_nonlocal_disabled.INPUT"); + EXPECT_NO_THROW(read_parameters(filename, "xc_nonlocal NoNe\ndft_functional PBE\nnspin 2\ncal_force 1\n", param)); + EXPECT_EQ(param.inp.xc_nonlocal, "none"); + EXPECT_EQ(param.inp.dft_functional, "PBE"); + EXPECT_EQ(param.inp.nspin, 2); + EXPECT_TRUE(param.inp.cal_force); +} + +TEST_F(InputTest, NonlocalXcRejectsUnknownMethod) +{ + const std::string filename = temporary_file("abacus_nonlocal_unknown.INPUT"); + expect_invalid_input(filename, "xc_nonlocal Unknown\n", "xc_nonlocal must be either none or rvv10"); +} + +TEST_F(InputTest, NonlocalXcRejectsCombinedFunctionalAlias) +{ + const std::string filename = temporary_file("abacus_nonlocal_alias.INPUT"); + expect_invalid_input(filename, "dft_functional rVv10\n", "Use xc_nonlocal=rvv10"); +} + +#if defined(__LIBXC) && defined(__LIBVDWXC) +TEST_F(InputTest, NonlocalXcAcceptsSupportedMixedCaseInput) +{ + Parameter param; + const std::string filename = temporary_file("abacus_nonlocal_enabled.INPUT"); + EXPECT_NO_THROW(read_parameters(filename, "xc_nonlocal rVv10\ndft_functional gga_x_rpw86+gga_c_pbe\n", param)); + EXPECT_EQ(param.inp.xc_nonlocal, "rvv10"); +} + +TEST_F(InputTest, NonlocalXcRejectsIncorrectFunctional) +{ + const std::string filename = temporary_file("abacus_nonlocal_functional.INPUT"); + expect_invalid_input(filename, "xc_nonlocal rvv10\n", "requires an explicit semilocal dft_functional"); + expect_invalid_input(filename, + "xc_nonlocal rvv10\ndft_functional PBE\n", + "requires dft_functional=GGA_X_RPW86+GGA_C_PBE"); +} + +TEST_F(InputTest, NonlocalXcRejectsInvalidOrUnsupportedParameters) +{ + const std::string filename = temporary_file("abacus_nonlocal_parameters.INPUT"); + const std::string base = "xc_nonlocal rvv10\ndft_functional GGA_X_RPW86+GGA_C_PBE\n"; + const std::vector> cases = { + {"rvv10_b 0\n", "must be finite and positive"}, + {"rvv10_b nan\n", "must be finite and positive"}, + {"rvv10_c -0.1\n", "must be finite and non-negative"}, + {"rvv10_c inf\n", "must be finite and non-negative"}, + {"rvv10_b 7.0\n", "uses fixed parameters"}, + {"rvv10_c 0.01\n", "uses fixed parameters"}, + }; + for (const auto& entry: cases) + { + SCOPED_TRACE(entry.first); + const std::string parameters = base + entry.first; + expect_invalid_input(filename, parameters, entry.second); + } +} + +TEST_F(InputTest, NonlocalXcRejectsUnsupportedInputCombinations) +{ + const std::string filename = temporary_file("abacus_nonlocal_scope.INPUT"); + const std::string base = "xc_nonlocal rvv10\ndft_functional GGA_X_RPW86+GGA_C_PBE\n"; + const std::vector cases = { + "nspin 2\n", + "cal_force 1\n", + "cal_stress 1\n", + "basis_type lcao\nks_solver lapack\n", + "calculation nscf\n", + "esolver_type sdft\n", + "ecutwfc 50\necutrho 240\n", + "vdw_method d2\n", + "vl_in_h 0\n", + }; + for (const auto& entry: cases) + { + SCOPED_TRACE(entry); + const std::string parameters = base + entry; + expect_invalid_input(filename, parameters, "xc_nonlocal=rvv10 supports only"); + } +} + +TEST_F(InputTest, NonlocalXcRejectsMultipleProcesses) +{ + set_nproc(2); + Parameter param; + param.set_pal_param(0, 2, 1); + const std::string filename = temporary_file("abacus_nonlocal_parallel.INPUT"); + testing::internal::CaptureStdout(); + EXPECT_THROW(read_parameters(filename, "xc_nonlocal rvv10\ndft_functional GGA_X_RPW86+GGA_C_PBE\n", param), + std::runtime_error); + const std::string output = testing::internal::GetCapturedStdout(); + EXPECT_THAT(output, testing::HasSubstr("xc_nonlocal=rvv10 supports only one MPI rank")); +} +#else +TEST_F(InputTest, NonlocalXcRequiresEnabledBackend) +{ + const std::string filename = temporary_file("abacus_nonlocal_backend.INPUT"); + expect_invalid_input(filename, + "xc_nonlocal rVv10\ndft_functional GGA_X_RPW86+GGA_C_PBE\n", + "requires Libxc and libvdwxc-enabled CMake builds"); +} +#endif + +TEST_F(InputTest, NonlocalXcRejectsGammaBeforeOverwritingKpoints) +{ + const std::string filename = temporary_file("abacus_nonlocal_gamma.INPUT"); + const std::string kpoint_file = temporary_file("abacus_nonlocal_gamma.KPT"); + const std::string original = "K_POINTS\n2\nDirect\n0.0 0.0 0.0 0.5\n0.25 0.0 0.0 0.5\n"; + { + std::ofstream kpoints(kpoint_file.c_str(), std::ios::binary); + ASSERT_TRUE(kpoints.is_open()); + kpoints << original; + kpoints.close(); + ASSERT_FALSE(kpoints.fail()); + } + const std::string parameters = "xc_nonlocal rVv10\ngamma_only 1\nkpoint_file " + kpoint_file + "\n"; + expect_invalid_input(filename, parameters, "gamma_only"); + std::ifstream kpoints(kpoint_file.c_str(), std::ios::binary); + ASSERT_TRUE(kpoints.is_open()); + const std::string actual((std::istreambuf_iterator(kpoints)), std::istreambuf_iterator()); + EXPECT_EQ(actual, original); + kpoints.close(); + EXPECT_EQ(std::remove(kpoint_file.c_str()), 0); +} + TEST_F(InputTest, ValidateRelaxMethodVariants) { Parameter cg_param; From 00ae9bb4e2bc844745824e9a7a993ea933f39b67 Mon Sep 17 00:00:00 2001 From: 1 Date: Sat, 17 Oct 2026 20:22:09 +0800 Subject: [PATCH 18/28] refactor(xc): keep the libvdwxc C header out of the adapter interface Forward-declare the opaque vdwxc_data_obj handle so only the implementation and the API-parity test include . Register the module_vdw tests from their own CMakeLists again and the serial libvdwxc host test from source_estate, remove the redundant dftd4 link of the pairwise vdW test, read the single nspin=1 density channel in PotNonlocalXC, and stop duplicating the rVV10 defaults in Potential. Co-Authored-By: Claude Fable 5.1 --- source/CMakeLists.txt | 28 ------------------- source/source_estate/CMakeLists.txt | 22 +++++++++++++++ .../module_pot/pot_nonlocal_xc.cpp | 21 +++++++------- .../module_pot/pot_nonlocal_xc.h | 3 +- .../module_pot/potential_new.cpp | 4 +-- .../source_estate/module_pot/potential_new.h | 5 ++-- .../source_hamilt/module_vdw/CMakeLists.txt | 4 +++ .../module_vdw/nonlocal_xc_libvdwxc.cpp | 13 ++++++++- .../module_vdw/nonlocal_xc_libvdwxc.h | 17 +++-------- .../module_vdw/test/CMakeLists.txt | 9 +----- .../test/test_nonlocal_xc_libvdwxc.cpp | 10 +++++++ 11 files changed, 71 insertions(+), 65 deletions(-) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 588f39c3176..c3fd55ed6f8 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -717,34 +717,6 @@ 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) - 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) add_coverage(driver) endif() diff --git a/source/source_estate/CMakeLists.txt b/source/source_estate/CMakeLists.txt index dc2a7a9977b..59e832aa36a 100644 --- a/source/source_estate/CMakeLists.txt +++ b/source/source_estate/CMakeLists.txt @@ -98,6 +98,28 @@ if(BUILD_TESTING) add_subdirectory(test) add_subdirectory(module_charge/unittests) endif() + if(ENABLE_LIBVDWXC AND NOT ENABLE_MPI AND NOT USE_CUDA AND NOT USE_ROCM) + # The serial libvdwxc host test lives outside the MPI-only test directory. + # It drives PotNonlocalXC with real Charge and UnitCell objects. + AddTest( + TARGET MODULE_ESTATE_pot_nonlocal_xc_LIBVDWXC + LIBS xc_ planewave base parameter device nonlocal_xc + SOURCES + test/test_pot_nonlocal_xc.cpp + module_pot/pot_nonlocal_xc.cpp + module_charge/charge.cpp + 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_LCAO) diff --git a/source/source_estate/module_pot/pot_nonlocal_xc.cpp b/source/source_estate/module_pot/pot_nonlocal_xc.cpp index c41f033a01b..c772d310526 100644 --- a/source/source_estate/module_pot/pot_nonlocal_xc.cpp +++ b/source/source_estate/module_pot/pot_nonlocal_xc.cpp @@ -32,15 +32,15 @@ void PotNonlocalXC::cal_v_eff(const Charge* chg, const UnitCell* cell, ModuleBas || chg->nrxx != rho_basis_->nrxx || !chg->rho || !chg->rho_core || v_eff.nr != chg->nspin || v_eff.nc != chg->nrxx) { - ModuleBase::WARNING_QUIT("PotNonlocalXC", "the first rVV10 interface requires a consistent nspin=1 density basis"); + ModuleBase::WARNING_QUIT("PotNonlocalXC", "nonlocal XC requires a consistent nspin=1 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; + // nspin=1 is enforced above, so the valence density is the single channel. + const std::vector valence(chg->rho[0], chg->rho[0] + chg->nrxx); + std::vector total(chg->nrxx, 0.0); for (int r = 0; r < chg->nrxx; ++r) - total[r] += chg->rho_core[r]; + { + total[r] = valence[r] + chg->rho_core[r]; + } const vdw::NonlocalXCResult nl = backend_->evaluate(*rho_basis_, total, valence); if (nl.potential.size() != static_cast(chg->nrxx)) { @@ -51,9 +51,10 @@ void PotNonlocalXC::cal_v_eff(const Charge* chg, const UnitCell* cell, ModuleBas // 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]; + for (int r = 0; r < chg->nrxx; ++r) + { + v_eff(0, r) += nl.potential[r]; + } ModuleBase::timer::end("PotNonlocalXC", "cal_v_eff"); } } // namespace elecstate diff --git a/source/source_estate/module_pot/pot_nonlocal_xc.h b/source/source_estate/module_pot/pot_nonlocal_xc.h index feafc824bd5..1d6189746be 100644 --- a/source/source_estate/module_pot/pot_nonlocal_xc.h +++ b/source/source_estate/module_pot/pot_nonlocal_xc.h @@ -12,7 +12,8 @@ class NonlocalXC; namespace elecstate { -// Independent rVV10 nonlocal correction for the CPU/PW SCF path. +// Additive density-grid nonlocal XC correction, registered after PotXC on the +// CPU/PW SCF path. The numerical method lives behind vdw::NonlocalXC. class PotNonlocalXC final : public PotBase { public: diff --git a/source/source_estate/module_pot/potential_new.cpp b/source/source_estate/module_pot/potential_new.cpp index 73cb7f242cc..7f1cb6d286a 100644 --- a/source/source_estate/module_pot/potential_new.cpp +++ b/source/source_estate/module_pot/potential_new.cpp @@ -33,8 +33,8 @@ Potential::Potential(const ModulePW::PW_Basis* rho_basis_in, etxc_in, vtxc_in, vsep_cell_in, - 6.3, - 0.0093, + 0.0, + 0.0, "none") { } diff --git a/source/source_estate/module_pot/potential_new.h b/source/source_estate/module_pot/potential_new.h index 177365c8e1a..9e78fdbbfdf 100644 --- a/source/source_estate/module_pot/potential_new.h +++ b/source/source_estate/module_pot/potential_new.h @@ -281,8 +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; + // Nonlocal XC selection; the parameters are only read when a method is set. + double nonlocal_xc_b_ = 0.0; + double nonlocal_xc_c_ = 0.0; std::string nonlocal_xc_ = "none"; bool use_gpu_ = false; std::shared_ptr td_field_manager_; diff --git a/source/source_hamilt/module_vdw/CMakeLists.txt b/source/source_hamilt/module_vdw/CMakeLists.txt index dc5f1286832..d14505cfb86 100644 --- a/source/source_hamilt/module_vdw/CMakeLists.txt +++ b/source/source_hamilt/module_vdw/CMakeLists.txt @@ -33,3 +33,7 @@ endif() if(ENABLE_COVERAGE) add_coverage(vdw) endif() + +if(BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp index f7529c07334..b7f76b58a39 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp +++ b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp @@ -2,6 +2,15 @@ #ifdef __LIBVDWXC +// libvdwxc's public C header includes inside an extern "C" +// block. Pre-include the standard header so libc++ does not parse its C++ +// templates with C linkage, then drop the I macro that complex.h defines. +#include +#include +#ifdef I +#undef I +#endif + #include "source_basis/module_pw/pw_basis.h" #include "source_base/tool_quit.h" #include "source_hamilt/module_xc/xc_functional.h" @@ -167,7 +176,6 @@ NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, 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; @@ -194,6 +202,9 @@ NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, } XC_Functional::grad_dot(field.data(), divergence.data(), &pw, pw.tpiba); + // v = 2 * (dE/dn - div(2 * dE/dsigma * grad n)) converts the Hartree + // derivatives to Ry; vtxc contracts v with the valence density only, matching + // the PotXC bookkeeping. The library already includes the beta term. const double energy_ry = 2.0 * energy_ha; std::vector potential(pw.nrxx, 0.0); NonlocalXCResult result{energy_ry, 0.0, potential}; diff --git a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h index 4b460c4765d..dfe45a58d6f 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h +++ b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h @@ -5,18 +5,9 @@ #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 -// 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 +// Opaque libvdwxc handle; vdwxc_data is a pointer to this struct. The C header +// is included only by the implementation file. +struct vdwxc_data_obj; namespace vdw { @@ -43,7 +34,7 @@ class LibvdwxcNonlocalXC final : public NonlocalXC const std::vector& valence_density) const override; private: - vdwxc_data data_ = nullptr; + vdwxc_data_obj* data_ = nullptr; int nx_ = 0; int ny_ = 0; int nz_ = 0; diff --git a/source/source_hamilt/module_vdw/test/CMakeLists.txt b/source/source_hamilt/module_vdw/test/CMakeLists.txt index c1d2d56517b..17af83fd194 100644 --- a/source/source_hamilt/module_vdw/test/CMakeLists.txt +++ b/source/source_hamilt/module_vdw/test/CMakeLists.txt @@ -29,13 +29,6 @@ if(ENABLE_LIBVDWXC AND NOT ENABLE_MPI AND NOT USE_CUDA AND NOT USE_ROCM) 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) + target_link_libraries(MODULE_HAMILT_vdwTest_LIBVDWXC PRIVATE Libxc::xc 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 index ea27dbc1322..02b561d269d 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 @@ -10,6 +10,16 @@ #include #include +#ifdef __LIBVDWXC +// Direct library calls provide the API-parity reference. Include the C header +// after the C++ headers and drop the I macro that complex.h defines. +#include +#include +#ifdef I +#undef I +#endif +#endif + namespace { const double pi = std::acos(-1.0); From a6c3ed46060c6704715c07c3fb213c88a1595484 Mon Sep 17 00:00:00 2001 From: 1 Date: Sat, 17 Oct 2026 20:22:09 +0800 Subject: [PATCH 19/28] docs(xc): trim the rVV10 test notes and drop the duplicate CI step The build-report contract test already runs in the "Other Unittests" step, so the workflow is now unchanged from develop. The test README keeps only how to run the tests, install.md documents the nscf restriction, and CITATIONS.md fixes the Sabatini initial. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/test.yml | 5 --- docs/CITATIONS.md | 2 +- docs/advanced/install.md | 5 +-- tests/rvv10/README.md | 64 ++++++++++++++------------------------ 4 files changed, 27 insertions(+), 49 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5af84d07116..87bf922499b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -115,11 +115,6 @@ 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/docs/CITATIONS.md b/docs/CITATIONS.md index da0e68dadc4..c428c330c3c 100644 --- a/docs/CITATIONS.md +++ b/docs/CITATIONS.md @@ -60,6 +60,6 @@ The following references are required to be cited when using ABACUS. Specificall 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. + R. 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. diff --git a/docs/advanced/install.md b/docs/advanced/install.md index 6c2606ab9ab..a1457275e40 100644 --- a/docs/advanced/install.md +++ b/docs/advanced/install.md @@ -133,9 +133,10 @@ Enable the correction with `xc_nonlocal rvv10` and explicitly select 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. +forces, stress, relaxation, non-SCF runs such as `nscf` band structures 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. +for how to run the dependency-free and backend tests. ## Build with JSON support diff --git a/tests/rvv10/README.md b/tests/rvv10/README.md index 826dff73c6f..33d65c0b88c 100644 --- a/tests/rvv10/README.md +++ b/tests/rvv10/README.md @@ -1,54 +1,36 @@ # libvdwxc-backed rVV10 checks -The numerical kernel is provided by libvdwxc MR !32, revision -`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: - -* 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 initial integration remains Draft pending independent QE -reference data and maintainer agreement on this staged scope. - -The dependency-free test executes the Makefile build-info generator and -checks that an installed library cannot be mistaken for a compiled feature: +The nonlocal kernel comes from libvdwxc MR !32, revision +`94410ebdf3dc9615a40f40c3a5b59dbe20a433d2`. Installation, the FFT-provider +constraints and the supported input scope are documented in +`docs/advanced/install.md`; `abacus -h xc_nonlocal` prints the same contract. + +## Dependency-free build-report test + +`RVV10_LIBVDWXC_CONTRACT` runs the legacy Makefile build-info generator and +checks that an installed library is never reported as a compiled feature: ```bash cmake -B build -DBUILD_TESTING=ON -ctest --test-dir build -R RVV10_LIBVDWXC_CONTRACT --output-on-failure +ctest --test-dir build -R RVV10_LIBVDWXC_CONTRACT --output-on-failure --no-tests=error ``` -With the pinned libvdwxc installation available, enable the real adapter test: +## Backend tests + +With the pinned libvdwxc installation, configure `build-libvdwxc` with the +standalone-FFTW command in `install.md`, then: ```bash -# Configure build-libvdwxc with the standalone-FFTW command in install.md. 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 +ctest --test-dir build-libvdwxc -R LIBVDWXC --output-on-failure --no-tests=error ``` -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 -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`. +`MODULE_HAMILT_vdwTest_LIBVDWXC` checks the adapter against the serial library +API, the potential against a finite difference of the energy, the cell mapping +with equivalent oblique and orthogonal cells, handle ownership and the standard +error diagnostics. `MODULE_ESTATE_pot_nonlocal_xc_LIBVDWXC` drives the host +component with real `Charge` and `UnitCell` objects and checks valence/core +density construction, additive energy/potential/`vtxc` bookkeeping and +unchanged input arrays. The Ry conversion and `vtxc` convention are documented +next to the implementation in `nonlocal_xc_libvdwxc.cpp`. From 9828e9ba2236637f4ecfb8dfc6201a2c45e3a77a Mon Sep 17 00:00:00 2001 From: 1 Date: Sun, 25 Oct 2026 15:19:34 +0000 Subject: [PATCH 20/28] refactor(xc): select the nonlocal XC component from Input_para and fix the rVV10 parameters Read xc_nonlocal in Potential::get_pot_type from the Input_para it now receives, the same way the pcc, efield and sccs components do, and drop the second Potential constructor, the three stored members and the has_nonlocal_xc() query that HamiltPW no longer needs since the merge of develop switched it to input.xc_nonlocal. The current interface pairs GGA_X_RPW86+GGA_C_PBE with the published rVV10 parameters b=6.3 and C=0.0093, so they become named constants in nonlocal_xc.h instead of two INPUT parameters that accepted only one value. The reader keeps the functional and scope checks in a small helper that names the first unsupported setting; braces on every if, no auto, a named spin channel, and a unit-cell guard in the factory. Co-Authored-By: Claude Fable 5.1 --- .../module_pot/pot_nonlocal_xc.cpp | 18 +- .../module_pot/pot_nonlocal_xc.h | 15 +- .../module_pot/potential_new.cpp | 30 +-- .../source_estate/module_pot/potential_new.h | 26 --- .../module_pot/potential_types.cpp | 20 +- source/source_estate/setup_estate_pw.cpp | 3 +- .../test/test_pot_nonlocal_xc.cpp | 24 ++- .../source_hamilt/module_vdw/nonlocal_xc.cpp | 10 +- source/source_hamilt/module_vdw/nonlocal_xc.h | 20 +- .../module_parameter/input_parameter.h | 2 - .../module_parameter/read_inp_estruc.cpp | 171 ++++++++---------- .../source_io/test_serial/read_input_test.cpp | 22 +-- 12 files changed, 140 insertions(+), 221 deletions(-) diff --git a/source/source_estate/module_pot/pot_nonlocal_xc.cpp b/source/source_estate/module_pot/pot_nonlocal_xc.cpp index c772d310526..e59b38b3505 100644 --- a/source/source_estate/module_pot/pot_nonlocal_xc.cpp +++ b/source/source_estate/module_pot/pot_nonlocal_xc.cpp @@ -1,8 +1,8 @@ #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 "source_hamilt/module_vdw/nonlocal_xc.h" #include @@ -11,12 +11,10 @@ namespace elecstate PotNonlocalXC::PotNonlocalXC(const ModulePW::PW_Basis* density_basis, double* etxc, double* vtxc, - const std::string& method, - double b, - double c) + const std::string& method) : etxc_(etxc), vtxc_(vtxc) { - backend_ = vdw::make_nonlocal_xc(density_basis, method, b, c); + backend_ = vdw::make_nonlocal_xc(density_basis, method); rho_basis_ = density_basis; dynamic_mode = true; fixed_mode = false; @@ -28,14 +26,14 @@ void PotNonlocalXC::cal_v_eff(const Charge* chg, const UnitCell* cell, ModuleBas { 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 - || v_eff.nc != chg->nrxx) + 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 density basis"); } // nspin=1 is enforced above, so the valence density is the single channel. - const std::vector valence(chg->rho[0], chg->rho[0] + chg->nrxx); + const int spin_channel = 0; + const std::vector valence(chg->rho[spin_channel], chg->rho[spin_channel] + chg->nrxx); std::vector total(chg->nrxx, 0.0); for (int r = 0; r < chg->nrxx; ++r) { @@ -53,7 +51,7 @@ void PotNonlocalXC::cal_v_eff(const Charge* chg, const UnitCell* cell, ModuleBas *vtxc_ += nl.vtxc; for (int r = 0; r < chg->nrxx; ++r) { - v_eff(0, r) += nl.potential[r]; + v_eff(spin_channel, r) += nl.potential[r]; } ModuleBase::timer::end("PotNonlocalXC", "cal_v_eff"); } diff --git a/source/source_estate/module_pot/pot_nonlocal_xc.h b/source/source_estate/module_pot/pot_nonlocal_xc.h index 1d6189746be..01ad10bef43 100644 --- a/source/source_estate/module_pot/pot_nonlocal_xc.h +++ b/source/source_estate/module_pot/pot_nonlocal_xc.h @@ -12,17 +12,16 @@ class NonlocalXC; namespace elecstate { -// Additive density-grid nonlocal XC correction, registered after PotXC on the -// CPU/PW SCF path. The numerical method lives behind vdw::NonlocalXC. +/** + * @brief Additive density-grid nonlocal XC correction for the CPU/PW SCF path. + * + * Registered after PotXC; the numerical method lives behind vdw::NonlocalXC. + * etxc and vtxc point at the energy bookkeeping that PotXC already fills. + */ 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(const ModulePW::PW_Basis* density_basis, double* etxc, double* vtxc, const std::string& method); ~PotNonlocalXC() override; void cal_v_eff(const Charge* chg, const UnitCell* cell, ModuleBase::matrix& v_eff) override; diff --git a/source/source_estate/module_pot/potential_new.cpp b/source/source_estate/module_pot/potential_new.cpp index e0208a3ce54..aecf60006ed 100644 --- a/source/source_estate/module_pot/potential_new.cpp +++ b/source/source_estate/module_pot/potential_new.cpp @@ -24,36 +24,8 @@ 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, - 0.0, - 0.0, - "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), nonlocal_xc_b_(nonlocal_xc_b_in), nonlocal_xc_c_(nonlocal_xc_c_in), - nonlocal_xc_(nonlocal_xc_in) + etxc_(etxc_in), vtxc_(vtxc_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 9ce759d5ecb..fdfaa190f08 100644 --- a/source/source_estate/module_pot/potential_new.h +++ b/source/source_estate/module_pot/potential_new.h @@ -8,7 +8,6 @@ #include "source_pw/module_pwdft/vsep_pw.h" #include -#include #include struct Input_para; @@ -70,21 +69,6 @@ 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 @@ -214,12 +198,6 @@ 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_; @@ -291,10 +269,6 @@ class Potential : public PotBase Structure_Factor* structure_factors_ = nullptr; surchem* solvent_ = nullptr; VSep* vsep_cell = nullptr; - // Nonlocal XC selection; the parameters are only read when a method is set. - double nonlocal_xc_b_ = 0.0; - double nonlocal_xc_c_ = 0.0; - 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 fc46291f60f..1202c148585 100644 --- a/source/source_estate/module_pot/potential_types.cpp +++ b/source/source_estate/module_pot/potential_types.cpp @@ -1,9 +1,10 @@ -#include "h_hartree_pw.h" #include "efield.h" #include "gatefield.h" +#include "h_hartree_pw.h" +#include "h_tddft_pw.h" #include "pot_local.h" -#include "pot_pcc.h" #include "pot_nonlocal_xc.h" +#include "pot_pcc.h" #include "pot_sccs.h" #include "pot_sep.h" #include "pot_surchem.h" @@ -15,8 +16,6 @@ #include "source_base/tool_quit.h" #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 @@ -64,6 +63,10 @@ PotBase* Potential::get_pot_type(const std::string& pot_type, const Input_para& } else if (pot_type == "xc_nonlocal") { + if (this->ucell_ == nullptr) + { + ModuleBase::WARNING_QUIT("Potential", "nonlocal XC requires the unit cell"); + } // 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) @@ -77,12 +80,9 @@ PotBase* Potential::get_pot_type(const std::string& pot_type, const Input_para& { 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_); + const std::string method = input.xc_nonlocal; + return new PotNonlocalXC(this->rho_basis_, this->etxc_, this->vtxc_, method); + } else if (pot_type == "sccs") { return this->make_sccs_pot(input); diff --git a/source/source_estate/setup_estate_pw.cpp b/source/source_estate/setup_estate_pw.cpp index a134715f882..9e045377cf2 100644 --- a/source/source_estate/setup_estate_pw.cpp +++ b/source/source_estate/setup_estate_pw.cpp @@ -103,8 +103,7 @@ 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, - inp.rvv10_b, inp.rvv10_c, inp.xc_nonlocal); + &solvent, &(pelec->f_en.etxc), &(pelec->f_en.vtxc), vsep_cell); } locpp.init_vloc(ucell, pw_rhod); diff --git a/source/source_estate/test/test_pot_nonlocal_xc.cpp b/source/source_estate/test/test_pot_nonlocal_xc.cpp index 2df6f7ba140..3802e90c007 100644 --- a/source/source_estate/test/test_pot_nonlocal_xc.cpp +++ b/source/source_estate/test/test_pot_nonlocal_xc.cpp @@ -1,3 +1,4 @@ +#include "source_base/constants.h" #include "source_estate/module_pot/pot_nonlocal_xc.h" #include "source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h" @@ -22,20 +23,23 @@ TEST(PotNonlocalXCTest, AddsCorrectionFromValenceAndCoreWithoutReplacingExisting charge.set_rhopw(&pw); charge.allocate(1, false, false, 0); - const double pi = std::acos(-1.0); + // Host and adapter add the same numbers, so they agree to rounding. + const double rounding_tolerance = 2.0e-12; 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; + const double ax = ModuleBase::TWO_PI * x / pw.nx; + const double ay = ModuleBase::TWO_PI * y / pw.ny; + const double az = ModuleBase::TWO_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]; @@ -44,26 +48,28 @@ TEST(PotNonlocalXCTest, AddsCorrectionFromValenceAndCoreWithoutReplacingExisting 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); + vdw::LibvdwxcNonlocalXC reference(pw, vdw::RVV10_B_RPW86_PBE, vdw::RVV10_C_RPW86_PBE); 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); + elecstate::PotNonlocalXC host(&pw, &energy, &vtxc, "rvv10"); 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); + EXPECT_NEAR(energy, expected_energy, rounding_tolerance); + EXPECT_NEAR(vtxc, expected_vtxc, rounding_tolerance); 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_NEAR(potential(0, r), expected_potential, rounding_tolerance); EXPECT_DOUBLE_EQ(charge.rho[0][r], valence[r]); EXPECT_DOUBLE_EQ(charge.rho_core[r], core[r]); } diff --git a/source/source_hamilt/module_vdw/nonlocal_xc.cpp b/source/source_hamilt/module_vdw/nonlocal_xc.cpp index 8ae9f544478..9f51a8ee783 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc.cpp +++ b/source/source_hamilt/module_vdw/nonlocal_xc.cpp @@ -8,10 +8,7 @@ namespace vdw { -std::unique_ptr make_nonlocal_xc(const ModulePW::PW_Basis* pw, - const std::string& method, - double b, - double c) +std::unique_ptr make_nonlocal_xc(const ModulePW::PW_Basis* pw, const std::string& method) { if (method == "rvv10") { @@ -20,13 +17,14 @@ std::unique_ptr make_nonlocal_xc(const ModulePW::PW_Basis* pw, { ModuleBase::WARNING_QUIT("NonlocalXC", "libvdwxc rVV10 requires a density PW basis"); } - return std::unique_ptr(new LibvdwxcNonlocalXC(*pw, b, c)); + return std::unique_ptr(new LibvdwxcNonlocalXC(*pw, RVV10_B_RPW86_PBE, RVV10_C_RPW86_PBE)); #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); + const std::string message = "Unsupported nonlocal XC method: " + method; + ModuleBase::WARNING_QUIT("NonlocalXC", message); return nullptr; } diff --git a/source/source_hamilt/module_vdw/nonlocal_xc.h b/source/source_hamilt/module_vdw/nonlocal_xc.h index 4941edfbc15..60da74138a1 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc.h +++ b/source/source_hamilt/module_vdw/nonlocal_xc.h @@ -13,6 +13,13 @@ class PW_Basis; namespace vdw { +/// rVV10 parameters published for the GGA_X_RPW86+GGA_C_PBE pairing +/// (R. Sabatini, T. Gorni and S. de Gironcoli, Phys. Rev. B 87, 041108(R) (2013)). +/// The current interface accepts only this pairing, so the values are fixed +/// here instead of being INPUT parameters. +const double RVV10_B_RPW86_PBE = 6.3; +const double RVV10_C_RPW86_PBE = 0.0093; + struct NonlocalXCResult { double energy; @@ -31,15 +38,20 @@ class NonlocalXC public: virtual ~NonlocalXC() = default; + /** + * @param pw density plane-wave basis the densities are defined on + * @param total_density valence plus core density on the real-space grid + * @param valence_density valence density used for the vtxc contraction + * @return nonlocal energy, vtxc and potential, all in Ry + */ 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); +/// Create the backend for `method` ("rvv10"). Quits for unknown methods and +/// for builds without the required library. +std::unique_ptr make_nonlocal_xc(const ModulePW::PW_Basis* pw, const std::string& method); } // namespace vdw diff --git a/source/source_io/module_parameter/input_parameter.h b/source/source_io/module_parameter/input_parameter.h index d80ec9b5907..7959fc969ca 100644 --- a/source/source_io/module_parameter/input_parameter.h +++ b/source/source_io/module_parameter/input_parameter.h @@ -89,8 +89,6 @@ struct Input_para std::string dft_functional = "default"; ///< input DFT functional. int gga_grad = 0; ///< Noncollinear GGA: 0 original, 1 local axis, 2 regularized mapped-density 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 1b923db93b4..2526879c7b4 100644 --- a/source/source_io/module_parameter/read_inp_estruc.cpp +++ b/source/source_io/module_parameter/read_inp_estruc.cpp @@ -12,6 +12,70 @@ namespace ModuleIO { +namespace +{ +// xc_nonlocal=rvv10 is a staged interface. Reject everything outside it while +// INPUT is read, before any expensive setup, and name the first unsupported +// setting so the user knows what to change. +void check_xc_nonlocal(const Input_para& p, const bool double_grid, const int nproc) +{ + const std::string nonlocal = p.xc_nonlocal; + 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 + std::string functional = p.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"); + } + // Report the first unsupported setting so the user knows what to change. + // gamma_only is rejected in its own reset, before the KPT file is rewritten. + const std::vector> unsupported = { + {nproc != 1, "one MPI rank"}, + {p.calculation != "scf", "calculation=scf"}, + {p.esolver_type != "ksdft", "esolver_type=ksdft"}, + {p.basis_type != "pw", "basis_type=pw"}, + {p.device != "cpu", "device=cpu"}, + {p.precision != "double", "precision=double"}, + {p.nspin != 1, "nspin=1"}, + {p.cal_force, "cal_force=0"}, + {p.cal_stress, "cal_stress=0"}, + {double_grid, "a single density grid"}, + {p.vdw_method != "none", "vdw_method=none"}, + {!p.vl_in_h, "vl_in_h=1"}, + }; + for (const std::pair& entry: unsupported) + { + if (entry.first) + { + const std::string message + = std::string("xc_nonlocal=rvv10 supports only ") + entry.second + " in the current libvdwxc interface"; + ModuleBase::WARNING_QUIT("ReadInput", message); + } + } +} +} // namespace + void ReadInput::item_elec_stru() { // NOTE: The order of add_item() calls below determines the parameter order @@ -303,8 +367,10 @@ The other way is only available when compiling with LIBXC, and it allows for sup return static_cast(std::toupper(c)); }); if (name == "RVV10") - ModuleBase::WARNING_QUIT( - "ReadInput", "Use xc_nonlocal=rvv10 with an explicit semilocal dft_functional"); + { + ModuleBase::WARNING_QUIT("ReadInput", + "Use xc_nonlocal=rvv10 with an explicit semilocal dft_functional"); + } }; this->add_item(item); } @@ -313,7 +379,8 @@ 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, right-handed-cell, norm-conserving, spin-unpolarized SCF calculation. Forces, stress, geometry optimization, molecular dynamics, non-SCF calculations such as `nscf`, 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` (the published rVV10 parameters b=6.3 and C=0.0093 of this pairing are built in), 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, non-SCF calculations such as `nscf`, LCAO, GPU, reduced-gamma layouts, ultrasoft 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); @@ -322,93 +389,12 @@ The other way is only available when compiling with LIBXC, and it allows for sup [](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"); - // Report the first unsupported setting so the user knows what to change. - const auto& p = para.input; - const std::vector> unsupported = { - {para.globalv.nproc != 1, "one MPI rank"}, - {p.calculation != "scf", "calculation=scf"}, - {p.esolver_type != "ksdft", "esolver_type=ksdft"}, - {p.basis_type != "pw", "basis_type=pw"}, - {p.device != "cpu", "device=cpu"}, - {p.precision != "double", "precision=double"}, - {p.nspin != 1, "nspin=1"}, - {p.cal_force, "cal_force=0"}, - {p.cal_stress, "cal_stress=0"}, - {para.sys.double_grid, "a single density grid"}, - {p.gamma_only, "gamma_only=0"}, - {p.vdw_method != "none", "vdw_method=none"}, - {!p.vl_in_h, "vl_in_h=1"}, - }; - for (const auto& entry: unsupported) - { - if (entry.first) - { - const std::string message = std::string("xc_nonlocal=rvv10 supports only ") + entry.second - + " in the current libvdwxc interface"; - ModuleBase::WARNING_QUIT("ReadInput", message); - } - } + const bool double_grid = para.sys.double_grid; + const int nproc = para.globalv.nproc; + check_xc_nonlocal(para.input, double_grid, nproc); }; 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); - 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); - this->add_item(item); - } { Input_Item item("xc_temperature"); item.annotation = "temperature for finite temperature functionals"; @@ -869,13 +855,10 @@ 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") + // Resets run before check_value, so reject rVV10 here, before the + // PW fallback overwrites KPT. xc_nonlocal is registered earlier in + // this function and its reset has already lowercased the value. + if (para.input.xc_nonlocal == "rvv10") { ModuleBase::WARNING_QUIT("ReadInput", "xc_nonlocal=rvv10 supports only full k-point PW layouts; " diff --git a/source/source_io/test_serial/read_input_test.cpp b/source/source_io/test_serial/read_input_test.cpp index bda704a4476..ac5e90d69bd 100644 --- a/source/source_io/test_serial/read_input_test.cpp +++ b/source/source_io/test_serial/read_input_test.cpp @@ -276,26 +276,6 @@ TEST_F(InputTest, NonlocalXcRejectsIncorrectFunctional) "requires dft_functional=GGA_X_RPW86+GGA_C_PBE"); } -TEST_F(InputTest, NonlocalXcRejectsInvalidOrUnsupportedParameters) -{ - const std::string filename = temporary_file("abacus_nonlocal_parameters.INPUT"); - const std::string base = "xc_nonlocal rvv10\ndft_functional GGA_X_RPW86+GGA_C_PBE\n"; - const std::vector> cases = { - {"rvv10_b 0\n", "must be finite and positive"}, - {"rvv10_b nan\n", "must be finite and positive"}, - {"rvv10_c -0.1\n", "must be finite and non-negative"}, - {"rvv10_c inf\n", "must be finite and non-negative"}, - {"rvv10_b 7.0\n", "uses fixed parameters"}, - {"rvv10_c 0.01\n", "uses fixed parameters"}, - }; - for (const auto& entry: cases) - { - SCOPED_TRACE(entry.first); - const std::string parameters = base + entry.first; - expect_invalid_input(filename, parameters, entry.second); - } -} - TEST_F(InputTest, NonlocalXcRejectsUnsupportedInputCombinations) { const std::string filename = temporary_file("abacus_nonlocal_scope.INPUT"); @@ -311,7 +291,7 @@ TEST_F(InputTest, NonlocalXcRejectsUnsupportedInputCombinations) "vdw_method d2\n", "vl_in_h 0\n", }; - for (const auto& entry: cases) + for (const std::string& entry: cases) { SCOPED_TRACE(entry); const std::string parameters = base + entry; From 76e9c1cded379720e43864101e6cdd780952eb42 Mon Sep 17 00:00:00 2001 From: 1 Date: Sun, 25 Oct 2026 15:19:34 +0000 Subject: [PATCH 21/28] test(xc): drop the Python build-report test and register the adapter test from module_vdw The tests/rvv10 CTest only asserted a hard-coded constant in generate_build_info.sh through a Python interpreter found at configure time; backend coverage stays with the two GoogleTest targets. The serial adapter test is now registered from module_vdw/CMakeLists.txt, leaving the MPI-only test directory as in develop, and nonlocal_xc joins the coverage targets. install.md keeps the build recipe, the libvdwxc merge request and the INPUT lines and points to `abacus -h xc_nonlocal` for the supported scope; CITATIONS.md cites libvdwxc itself. Co-Authored-By: Claude Fable 5.1 --- docs/CITATIONS.md | 2 +- docs/advanced/install.md | 36 ++++--------------- .../source_hamilt/module_vdw/CMakeLists.txt | 15 +++++++- .../module_vdw/test/CMakeLists.txt | 21 +++-------- tests/CMakeLists.txt | 1 - tests/rvv10/CMakeLists.txt | 10 ------ tests/rvv10/README.md | 36 ------------------- tests/rvv10/test_libvdwxc_contract.py | 27 -------------- 8 files changed, 26 insertions(+), 122 deletions(-) delete mode 100644 tests/rvv10/CMakeLists.txt delete mode 100644 tests/rvv10/README.md delete mode 100644 tests/rvv10/test_libvdwxc_contract.py diff --git a/docs/CITATIONS.md b/docs/CITATIONS.md index c428c330c3c..839f4bad4d3 100644 --- a/docs/CITATIONS.md +++ b/docs/CITATIONS.md @@ -62,4 +62,4 @@ The following references are required to be cited when using ABACUS. Specificall R. 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. + A. H. Larsen, M. Kuisma, J. Löfgren, Y. Pouillon, P. Erhart, and P. Hyldgaard. "libvdwxc: a library for exchange-correlation functionals in the vdW-DF family." Modelling and Simulation in Materials Science and Engineering 25, 065004 (2017). DOI: 10.1088/1361-651X/aa7320. diff --git a/docs/advanced/install.md b/docs/advanced/install.md index 8ea8523825d..4a2cadc0202 100644 --- a/docs/advanced/install.md +++ b/docs/advanced/install.md @@ -101,42 +101,18 @@ If `vdw_d4_xc` is set to `default`, ABACUS will infer the functional name from ` ## 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 -`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: +`xc_nonlocal rvv10` uses the external [libvdwxc](https://gitlab.com/libvdwxc/libvdwxc) library for the rVV10 nonlocal kernel. Until a tagged libvdwxc release includes the rVV10 API, build and install the rVV10 branch from [libvdwxc merge request !32](https://gitlab.com/libvdwxc/libvdwxc/-/merge_requests/32) (commit `94410eb` at the time of writing) against the same FFTW that ABACUS will use, then pass its prefix together with Libxc. `ENABLE_LIBVDWXC` requires `ENABLE_LIBXC`. A serial configuration: ```bash -cmake -B build-libvdwxc -DBUILD_TESTING=ON \ - -DENABLE_MPI=OFF -DENABLE_LCAO=OFF \ +cmake -B build -DENABLE_MPI=OFF -DENABLE_LCAO=OFF \ -DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON \ -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 + -DFFTW3_DIR=/path/to/fftw-prefix ``` -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, non-SCF runs such as `nscf` band structures and -pairwise D2/D3/D4 combinations are rejected. -The legacy Makefile does not build this backend. See `tests/rvv10/README.md` -for how to run the dependency-free and backend tests. +Keep one FFT provider per process: do not combine a libvdwxc built against FFTW with an ABACUS build that uses the MKL FFT interface. The legacy Makefile does not build this backend. + +Enable the correction with `xc_nonlocal rvv10` and `dft_functional GGA_X_RPW86+GGA_C_PBE`; the rVV10 parameters b=6.3 and C=0.0093 of this pairing are built in. `abacus -h xc_nonlocal` lists the supported calculation settings; unsupported ones are rejected when INPUT is read. ## Build with JSON support diff --git a/source/source_hamilt/module_vdw/CMakeLists.txt b/source/source_hamilt/module_vdw/CMakeLists.txt index d14505cfb86..41e8313a118 100644 --- a/source/source_hamilt/module_vdw/CMakeLists.txt +++ b/source/source_hamilt/module_vdw/CMakeLists.txt @@ -32,8 +32,21 @@ endif() if(ENABLE_COVERAGE) add_coverage(vdw) + add_coverage(nonlocal_xc) endif() if(BUILD_TESTING) - add_subdirectory(test) + if(ENABLE_MPI) + add_subdirectory(test) + endif() + if(ENABLE_LIBVDWXC AND NOT ENABLE_MPI AND NOT USE_CUDA AND NOT USE_ROCM) + # The serial adapter test is registered here because test/ is MPI-only. + AddTest( + TARGET MODULE_HAMILT_vdwTest_LIBVDWXC + LIBS xc_ planewave base parameter device nonlocal_xc + SOURCES test/test_nonlocal_xc_libvdwxc.cpp + ) + target_link_libraries(MODULE_HAMILT_vdwTest_LIBVDWXC PRIVATE Libxc::xc libvdwxc::vdwxc) + set_tests_properties(MODULE_HAMILT_vdwTest_LIBVDWXC PROPERTIES ENVIRONMENT "OMP_NUM_THREADS=1") + endif() endif() diff --git a/source/source_hamilt/module_vdw/test/CMakeLists.txt b/source/source_hamilt/module_vdw/test/CMakeLists.txt index 58bdee37a93..fddd34b6816 100644 --- a/source/source_hamilt/module_vdw/test/CMakeLists.txt +++ b/source/source_hamilt/module_vdw/test/CMakeLists.txt @@ -7,13 +7,11 @@ abacus_copy_test_files( r0.txt r0_zero.txt) -if(ENABLE_MPI) - AddTest( - TARGET MODULE_HAMILT_vdwTest - LIBS parameter base device vdw - SOURCES vdw_test.cpp vdwd3_evaluator_test.cpp - ) -endif() +AddTest( + TARGET MODULE_HAMILT_vdwTest + LIBS parameter base device vdw + SOURCES vdw_test.cpp vdwd3_evaluator_test.cpp +) if(ENABLE_DFTD4 AND ENABLE_MPI) add_test( @@ -24,12 +22,3 @@ 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 - ) - target_link_libraries(MODULE_HAMILT_vdwTest_LIBVDWXC PRIVATE Libxc::xc libvdwxc::vdwxc) - set_tests_properties(MODULE_HAMILT_vdwTest_LIBVDWXC PROPERTIES ENVIRONMENT "OMP_NUM_THREADS=1") -endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8bbd99679c1..147daf67b94 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,4 @@ 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 deleted file mode 100644 index a9ee1775d36..00000000000 --- a/tests/rvv10/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -find_package(Python3 QUIET COMPONENTS Interpreter) - -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") -endif() diff --git a/tests/rvv10/README.md b/tests/rvv10/README.md deleted file mode 100644 index 33d65c0b88c..00000000000 --- a/tests/rvv10/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# libvdwxc-backed rVV10 checks - -The nonlocal kernel comes from libvdwxc MR !32, revision -`94410ebdf3dc9615a40f40c3a5b59dbe20a433d2`. Installation, the FFT-provider -constraints and the supported input scope are documented in -`docs/advanced/install.md`; `abacus -h xc_nonlocal` prints the same contract. - -## Dependency-free build-report test - -`RVV10_LIBVDWXC_CONTRACT` runs the legacy Makefile build-info generator and -checks that an installed library is never reported as a compiled feature: - -```bash -cmake -B build -DBUILD_TESTING=ON -ctest --test-dir build -R RVV10_LIBVDWXC_CONTRACT --output-on-failure --no-tests=error -``` - -## Backend tests - -With the pinned libvdwxc installation, configure `build-libvdwxc` with the -standalone-FFTW command in `install.md`, then: - -```bash -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 --no-tests=error -``` - -`MODULE_HAMILT_vdwTest_LIBVDWXC` checks the adapter against the serial library -API, the potential against a finite difference of the energy, the cell mapping -with equivalent oblique and orthogonal cells, handle ownership and the standard -error diagnostics. `MODULE_ESTATE_pot_nonlocal_xc_LIBVDWXC` drives the host -component with real `Charge` and `UnitCell` objects and checks valence/core -density construction, additive energy/potential/`vtxc` bookkeeping and -unchanged input arrays. The Ry conversion and `vtxc` convention are documented -next to the implementation in `nonlocal_xc_libvdwxc.cpp`. diff --git a/tests/rvv10/test_libvdwxc_contract.py b/tests/rvv10/test_libvdwxc_contract.py deleted file mode 100644 index 77612a50c41..00000000000 --- a/tests/rvv10/test_libvdwxc_contract.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Exercise the optional-backend build report without external dependencies.""" - -import os -from pathlib import Path -import subprocess -import tempfile -import unittest - - -ROOT = Path(__file__).resolve().parents[2] - - -class LibvdwxcContract(unittest.TestCase): - 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__": - unittest.main() From 815deaed70a6cc0d6feccffdae6e656efc23fde0 Mon Sep 17 00:00:00 2001 From: 1 Date: Sun, 25 Oct 2026 15:19:34 +0000 Subject: [PATCH 22/28] style(xc): name tolerances, reuse constants.h and format the new files ModuleBase::e2 for the Hartree-to-Rydberg conversion and ModuleBase::TWO_PI in the tests, named cell and finite-difference tolerances with their origin, braces on nested loops, explicit types instead of auto, one allocation for the returned potential, 2-space CMake blocks matching the Prettify series, a configure-time warning when ENABLE_LIBVDWXC meets the MKL FFTW interface, and clang-format on the new files and touched hunks. The unrelated Makefile newline and the hamilt_pw.h comment are gone. Co-Authored-By: Claude Fable 5.1 --- CMakeLists.txt | 16 ++- cmake/CollectBuildInfoVars.cmake | 6 +- .../module_vdw/nonlocal_xc_libvdwxc.cpp | 45 ++++---- .../test/test_nonlocal_xc_libvdwxc.cpp | 109 ++++++++++++------ source/source_pw/module_pwdft/Makefile | 2 +- source/source_pw/module_pwdft/hamilt_pw.h | 1 - 6 files changed, 110 insertions(+), 69 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 70cd15488a2..79294deedee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -646,13 +646,19 @@ 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.") + message( + FATAL_ERROR + "ENABLE_LIBVDWXC requires ENABLE_LIBXC=ON: the rVV10 interface uses Libxc for the semilocal GGA_X_RPW86+GGA_C_PBE part." + ) endif() - # The config target supplies headers and transitive FFT dependencies. - # rVV10 currently requires the development revision documented in install.md. + # The exported target supplies headers and transitive FFT dependencies. find_package(libvdwxc CONFIG REQUIRED) + if(MKL_FOUND) + message( + WARNING + "ENABLE_LIBVDWXC with MKL: libvdwxc links its own FFTW3 while ABACUS uses the MKL FFTW3 interface, so two FFT providers would coexist in one process. The libvdwxc backend has only been validated with a standalone FFTW3 build of both ABACUS and libvdwxc." + ) + endif() endif() if(DEFINED DeePMD_DIR) diff --git a/cmake/CollectBuildInfoVars.cmake b/cmake/CollectBuildInfoVars.cmake index a13b57e2be3..ca2cc538f2e 100644 --- a/cmake/CollectBuildInfoVars.cmake +++ b/cmake/CollectBuildInfoVars.cmake @@ -193,11 +193,11 @@ else() endif() if(ENABLE_LIBVDWXC AND libvdwxc_VERSION) - set(ABACUS_LIBVDWXC_VERSION "yes (v${libvdwxc_VERSION})") + set(ABACUS_LIBVDWXC_VERSION "yes (v${libvdwxc_VERSION})") elseif(ENABLE_LIBVDWXC) - set(ABACUS_LIBVDWXC_VERSION "yes (version unknown)") + set(ABACUS_LIBVDWXC_VERSION "yes (version unknown)") else() - set(ABACUS_LIBVDWXC_VERSION "no") + set(ABACUS_LIBVDWXC_VERSION "no") endif() # Accelerators diff --git a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp index b7f76b58a39..7d9bd1499b2 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp +++ b/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp @@ -11,8 +11,9 @@ #undef I #endif -#include "source_basis/module_pw/pw_basis.h" +#include "source_base/constants.h" #include "source_base/tool_quit.h" +#include "source_basis/module_pw/pw_basis.h" #include "source_hamilt/module_xc/xc_functional.h" #include @@ -23,6 +24,10 @@ namespace vdw { namespace { +// The cell handed to libvdwxc at construction and the cell seen at each +// evaluation come from the same PW_Basis, so only rounding may differ. +const double CELL_RELATIVE_TOLERANCE = 1.0e-12; + void check_serial_basis(const ModulePW::PW_Basis& pw) { const double determinant = pw.latvec.Det(); @@ -32,13 +37,14 @@ void check_serial_basis(const ModulePW::PW_Basis& pw) || !std::isfinite(pw.tpiba) || pw.tpiba <= 0.0) { ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", - "libvdwxc rVV10 requires one-process CPU/double full-complex PW density grid and a right-handed cell"); + "libvdwxc rVV10 requires a one-process CPU double-precision full-complex PW density " + "grid and a right-handed cell"); } } std::array cell_vectors(const ModulePW::PW_Basis& pw) { - const auto& a = pw.latvec; + const ModuleBase::Matrix3& 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, @@ -67,7 +73,7 @@ bool same_cell(const std::array& lhs, const std::array& rh { scale = rhs_abs; } - if (std::abs(lhs[i] - rhs[i]) > 1.0e-12 * scale) + if (std::abs(lhs[i] - rhs[i]) > CELL_RELATIVE_TOLERANCE * scale) { return false; } @@ -138,13 +144,12 @@ NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, 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; + const bool same_volume = std::abs(pw.omega - omega_) <= CELL_RELATIVE_TOLERANCE * 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()) + || total_density.size() != static_cast(pw.nrxx) || valence_density.size() != total_density.size()) { ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", - "libvdwxc rVV10 received a density grid different from its initialized cell"); + "libvdwxc rVV10 received a density grid different from its initialized cell"); } using Complex = std::complex; @@ -178,12 +183,9 @@ NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, sigma[r] = gradient[r].x * gradient[r].x + gradient[r].y * gradient[r].y + gradient[r].z * gradient[r].z; } + // vdwxc_calculate takes a non-const density pointer; keep the caller's array const. std::vector density = total_density; - const double energy_ha = vdwxc_calculate(data_, - density.data(), - sigma.data(), - dedn.data(), - dedsigma.data()); + const double energy_ha = vdwxc_calculate(data_, density.data(), sigma.data(), dedn.data(), dedsigma.data()); if (!std::isfinite(energy_ha)) { ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc returned a nonfinite rVV10 energy"); @@ -195,6 +197,7 @@ NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, { ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc returned a nonfinite rVV10 derivative"); } + // Chain rule for sigma = |grad n|^2: dE/d(grad n) = 2 * dE/dsigma * grad n. const double coefficient = 2.0 * dedsigma[r]; field[r].x = coefficient * gradient[r].x; field[r].y = coefficient * gradient[r].y; @@ -202,17 +205,17 @@ NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, } XC_Functional::grad_dot(field.data(), divergence.data(), &pw, pw.tpiba); - // v = 2 * (dE/dn - div(2 * dE/dsigma * grad n)) converts the Hartree - // derivatives to Ry; vtxc contracts v with the valence density only, matching - // the PotXC bookkeeping. The library already includes the beta term. - const double energy_ry = 2.0 * energy_ha; - std::vector potential(pw.nrxx, 0.0); - NonlocalXCResult result{energy_ry, 0.0, potential}; + // libvdwxc returns Hartree quantities; e2 converts them to Ry as in PotXC. + // v = dE/dn - div(dE/d(grad n)); vtxc contracts v with the valence density + // only, matching the PotXC bookkeeping. The library includes the beta term. + NonlocalXCResult result; + result.energy = ModuleBase::e2 * energy_ha; + result.vtxc = 0.0; + result.potential.assign(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]); + result.potential[r] = ModuleBase::e2 * (dedn[r] - divergence[r]); if (!std::isfinite(divergence[r]) || !std::isfinite(result.potential[r])) { ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc produced a nonfinite rVV10 potential"); 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 02b561d269d..cce1c5cfeab 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 @@ -1,5 +1,5 @@ #include "../nonlocal_xc_libvdwxc.h" - +#include "source_base/constants.h" #include "source_basis/module_pw/pw_basis.h" #include "source_hamilt/module_xc/xc_functional.h" @@ -22,7 +22,11 @@ namespace { -const double pi = std::acos(-1.0); +// Adapter and direct API run the same FFT path, so results agree to rounding. +const double ROUNDING_TOLERANCE = 2.0e-12; +// Central difference on a smooth density: truncation error is of order step^2. +const double FINITE_DIFFERENCE_STEP = 1.0e-5; +const double FINITE_DIFFERENCE_TOLERANCE = 2.0e-6; class LibvdwxcAdapterTest : public testing::Test { @@ -43,15 +47,19 @@ class LibvdwxcAdapterTest : public testing::Test { 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; + const double ax = ModuleBase::TWO_PI * x / pw.nx; + const double ay = ModuleBase::TWO_PI * y / pw.ny; + const double az = ModuleBase::TWO_PI * z / pw.nz; values[index] = offset + amplitude * (1.0 + 0.25 * std::sin(ax) + 0.15 * std::cos(ay + az)); } + } + } return values; } @@ -59,14 +67,18 @@ class LibvdwxcAdapterTest : public testing::Test { 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; + const double ax = ModuleBase::TWO_PI * x / pw.nx; + const double ay = ModuleBase::TWO_PI * y / pw.ny; values[index] = std::sin(ax + ay) + 0.3 * std::cos(2.0 * ay); } + } + } return values; } }; @@ -80,8 +92,8 @@ TEST(LibvdwxcOwnershipTest, BackendHandleCannotBeCopied) TEST_F(LibvdwxcAdapterTest, MatchesDirectSerialApiForNonuniformDensity) { - const double b = 6.3; - const double c = 0.0093; + const double b = vdw::RVV10_B_RPW86_PBE; + const double c = vdw::RVV10_C_RPW86_PBE; const std::vector total = density(0.004, 0.006); const std::vector valence = density(0.002, 0.003); vdw::LibvdwxcNonlocalXC adapter(pw, b, c); @@ -90,13 +102,22 @@ TEST_F(LibvdwxcAdapterTest, MatchesDirectSerialApiForNonuniformDensity) 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, + const ModuleBase::Matrix3& a = pw.latvec; + vdwxc_set_unit_cell(direct, + pw.nx, + pw.ny, + pw.nz, // 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); + 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); @@ -113,7 +134,8 @@ TEST_F(LibvdwxcAdapterTest, MatchesDirectSerialApiForNonuniformDensity) 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()); + 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]; @@ -125,14 +147,15 @@ TEST_F(LibvdwxcAdapterTest, MatchesDirectSerialApiForNonuniformDensity) const double dv = pw.omega / pw.nxyz; double expected_vtxc = 0.0; - EXPECT_NEAR(adapted.energy, 2.0 * direct_energy, 2.0e-12); + const double expected_energy = ModuleBase::e2 * direct_energy; + EXPECT_NEAR(adapted.energy, expected_energy, ROUNDING_TOLERANCE); for (int r = 0; r < pw.nrxx; ++r) { - const double expected_potential = 2.0 * (dedn[r] - divergence[r]); + const double expected_potential = ModuleBase::e2 * (dedn[r] - divergence[r]); expected_vtxc += dv * valence[r] * expected_potential; - EXPECT_NEAR(adapted.potential[r], expected_potential, 2.0e-12); + EXPECT_NEAR(adapted.potential[r], expected_potential, ROUNDING_TOLERANCE); } - EXPECT_NEAR(adapted.vtxc, expected_vtxc, 2.0e-12); + EXPECT_NEAR(adapted.vtxc, expected_vtxc, ROUNDING_TOLERANCE); vdwxc_finalize(&direct); } @@ -144,17 +167,19 @@ TEST_F(LibvdwxcAdapterTest, RejectsLeftHandedCell) reflected.initparameters(false, 4.0, 1, true); reflected.setuptransform(); reflected.collect_local_pw(); - EXPECT_EXIT(vdw::LibvdwxcNonlocalXC(reflected, 6.3, 0.0093), testing::ExitedWithCode(1), ""); + EXPECT_EXIT(vdw::LibvdwxcNonlocalXC(reflected, vdw::RVV10_B_RPW86_PBE, vdw::RVV10_C_RPW86_PBE), + testing::ExitedWithCode(1), + ""); } TEST_F(LibvdwxcAdapterTest, RejectsInvalidParametersWithStandardDiagnostic) { - EXPECT_EXIT(vdw::LibvdwxcNonlocalXC(pw, 0.0, 0.0093), testing::ExitedWithCode(1), ""); + EXPECT_EXIT(vdw::LibvdwxcNonlocalXC(pw, 0.0, vdw::RVV10_C_RPW86_PBE), testing::ExitedWithCode(1), ""); } TEST_F(LibvdwxcAdapterTest, RejectsInvalidDensityWithStandardDiagnostic) { - vdw::LibvdwxcNonlocalXC adapter(pw, 6.3, 0.0093); + vdw::LibvdwxcNonlocalXC adapter(pw, vdw::RVV10_B_RPW86_PBE, vdw::RVV10_C_RPW86_PBE); const std::vector total = density(0.004, 0.006); std::vector invalid = total; invalid.pop_back(); @@ -179,13 +204,17 @@ TEST_F(LibvdwxcAdapterTest, CellMappingPreservesIndependentReciprocalGeometry) 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; + const double phase = ModuleBase::TWO_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); @@ -193,35 +222,39 @@ TEST_F(LibvdwxcAdapterTest, CellMappingPreservesIndependentReciprocalGeometry) 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); + const double phase = ModuleBase::TWO_PI * x / pw.nx; + const double derivative = -amplitude * (ModuleBase::TWO_PI / pw.lat0) * std::sin(phase); + EXPECT_NEAR(gradient[index].x, derivative, ROUNDING_TOLERANCE); + EXPECT_NEAR(gradient[index].y, derivative / std::sqrt(3.0), ROUNDING_TOLERANCE); + EXPECT_NEAR(gradient[index].z, 0.0, ROUNDING_TOLERANCE); } + } + } - vdw::LibvdwxcNonlocalXC oblique_adapter(pw, 6.3, 0.0093); - vdw::LibvdwxcNonlocalXC orthogonal_adapter(orthogonal, 6.3, 0.0093); + vdw::LibvdwxcNonlocalXC oblique_adapter(pw, vdw::RVV10_B_RPW86_PBE, vdw::RVV10_C_RPW86_PBE); + vdw::LibvdwxcNonlocalXC orthogonal_adapter(orthogonal, vdw::RVV10_B_RPW86_PBE, vdw::RVV10_C_RPW86_PBE); 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); + EXPECT_NEAR(pw.omega, orthogonal.omega, ROUNDING_TOLERANCE); + ASSERT_NEAR(oblique_result.energy, orthogonal_result.energy, ROUNDING_TOLERANCE); + EXPECT_NEAR(oblique_result.vtxc, orthogonal_result.vtxc, ROUNDING_TOLERANCE); for (int r = 0; r < pw.nrxx; ++r) { - EXPECT_NEAR(oblique_result.potential[r], orthogonal_result.potential[r], 2.0e-12); + EXPECT_NEAR(oblique_result.potential[r], orthogonal_result.potential[r], ROUNDING_TOLERANCE); } } TEST_F(LibvdwxcAdapterTest, PotentialMatchesFiniteDifferenceOfEnergy) { - const double b = 6.3; - const double c = 0.0093; - const double step = 1.0e-5; + const double b = vdw::RVV10_B_RPW86_PBE; + const double c = vdw::RVV10_C_RPW86_PBE; + const double step = FINITE_DIFFERENCE_STEP; const std::vector total = density(0.004, 0.006); const std::vector valence = total; const std::vector direction = perturbation(); @@ -243,7 +276,7 @@ TEST_F(LibvdwxcAdapterTest, PotentialMatchesFiniteDifferenceOfEnergy) { potential_contraction += dv * reference.potential[r] * direction[r]; } - EXPECT_NEAR(finite_difference, potential_contraction, 2.0e-6); + EXPECT_NEAR(finite_difference, potential_contraction, FINITE_DIFFERENCE_TOLERANCE); } #endif diff --git a/source/source_pw/module_pwdft/Makefile b/source/source_pw/module_pwdft/Makefile index 8091a706708..ad634ae49e9 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 + @ if [ -e ${VERSION}-PW.x ]; then rm -f ${VERSION}-PW.x; fi \ No newline at end of file diff --git a/source/source_pw/module_pwdft/hamilt_pw.h b/source/source_pw/module_pwdft/hamilt_pw.h index c4117810b3c..72158eadaf4 100644 --- a/source/source_pw/module_pwdft/hamilt_pw.h +++ b/source/source_pw/module_pwdft/hamilt_pw.h @@ -27,7 +27,6 @@ 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, From 0df2a42e91a23a785fc462836f331b2039b470aa Mon Sep 17 00:00:00 2001 From: 1 Date: Mon, 26 Oct 2026 02:35:29 +0000 Subject: [PATCH 23/28] refactor(xc): shorten the adapter file names and split its evaluation nonlocal_xc_libvdwxc.{h,cpp} become nonlocal_vdwxc.{h,cpp} (and the test test_nonlocal_vdwxc.cpp) to keep file names within the 15-character preference. LibvdwxcNonlocalXC::evaluate now delegates to small helpers (serial grid and right-handed cell predicates, finite-value check, initialized-cell check, host gradient/sigma, Ry assembly of energy, potential and vtxc) so each unit stays readable; the numerical path is unchanged. Co-Authored-By: Claude Fable 5.1 --- .../test/test_pot_nonlocal_xc.cpp | 2 +- .../source_hamilt/module_vdw/CMakeLists.txt | 4 +- ...cal_xc_libvdwxc.cpp => nonlocal_vdwxc.cpp} | 224 ++++++++++-------- ...onlocal_xc_libvdwxc.h => nonlocal_vdwxc.h} | 9 +- .../source_hamilt/module_vdw/nonlocal_xc.cpp | 2 +- ...c_libvdwxc.cpp => test_nonlocal_vdwxc.cpp} | 2 +- 6 files changed, 136 insertions(+), 107 deletions(-) rename source/source_hamilt/module_vdw/{nonlocal_xc_libvdwxc.cpp => nonlocal_vdwxc.cpp} (67%) rename source/source_hamilt/module_vdw/{nonlocal_xc_libvdwxc.h => nonlocal_vdwxc.h} (83%) rename source/source_hamilt/module_vdw/test/{test_nonlocal_xc_libvdwxc.cpp => test_nonlocal_vdwxc.cpp} (99%) diff --git a/source/source_estate/test/test_pot_nonlocal_xc.cpp b/source/source_estate/test/test_pot_nonlocal_xc.cpp index 3802e90c007..46d8b9331e9 100644 --- a/source/source_estate/test/test_pot_nonlocal_xc.cpp +++ b/source/source_estate/test/test_pot_nonlocal_xc.cpp @@ -1,6 +1,6 @@ #include "source_base/constants.h" #include "source_estate/module_pot/pot_nonlocal_xc.h" -#include "source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h" +#include "source_hamilt/module_vdw/nonlocal_vdwxc.h" #include #include diff --git a/source/source_hamilt/module_vdw/CMakeLists.txt b/source/source_hamilt/module_vdw/CMakeLists.txt index 41e8313a118..06082c00e44 100644 --- a/source/source_hamilt/module_vdw/CMakeLists.txt +++ b/source/source_hamilt/module_vdw/CMakeLists.txt @@ -21,7 +21,7 @@ add_library(vdw OBJECT ${vdw_sources}) set(nonlocal_xc_sources nonlocal_xc.cpp) if(ENABLE_LIBVDWXC) - list(APPEND nonlocal_xc_sources nonlocal_xc_libvdwxc.cpp) + list(APPEND nonlocal_xc_sources nonlocal_vdwxc.cpp) endif() add_library(nonlocal_xc OBJECT ${nonlocal_xc_sources}) @@ -44,7 +44,7 @@ if(BUILD_TESTING) AddTest( TARGET MODULE_HAMILT_vdwTest_LIBVDWXC LIBS xc_ planewave base parameter device nonlocal_xc - SOURCES test/test_nonlocal_xc_libvdwxc.cpp + SOURCES test/test_nonlocal_vdwxc.cpp ) target_link_libraries(MODULE_HAMILT_vdwTest_LIBVDWXC PRIVATE Libxc::xc libvdwxc::vdwxc) set_tests_properties(MODULE_HAMILT_vdwTest_LIBVDWXC PROPERTIES ENVIRONMENT "OMP_NUM_THREADS=1") diff --git a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp b/source/source_hamilt/module_vdw/nonlocal_vdwxc.cpp similarity index 67% rename from source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp rename to source/source_hamilt/module_vdw/nonlocal_vdwxc.cpp index 7d9bd1499b2..af76ae03941 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.cpp +++ b/source/source_hamilt/module_vdw/nonlocal_vdwxc.cpp @@ -1,4 +1,4 @@ -#include "nonlocal_xc_libvdwxc.h" +#include "nonlocal_vdwxc.h" #ifdef __LIBVDWXC @@ -18,6 +18,7 @@ #include #include +#include #include namespace vdw @@ -28,13 +29,22 @@ namespace // evaluation come from the same PW_Basis, so only rounding may differ. const double CELL_RELATIVE_TOLERANCE = 1.0e-12; -void check_serial_basis(const ModulePW::PW_Basis& pw) +bool serial_cpu_grid(const ModulePW::PW_Basis& pw) +{ + return !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; +} + +bool right_handed_cell(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(determinant) || determinant <= 0.0 - || !std::isfinite(pw.tpiba) || pw.tpiba <= 0.0) + return std::isfinite(pw.omega) && pw.omega > 0.0 && std::isfinite(determinant) && determinant > 0.0 + && std::isfinite(pw.tpiba) && pw.tpiba > 0.0; +} + +void check_serial_basis(const ModulePW::PW_Basis& pw) +{ + if (!serial_cpu_grid(pw) || !right_handed_cell(pw)) { ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc rVV10 requires a one-process CPU double-precision full-complex PW density " @@ -42,6 +52,18 @@ void check_serial_basis(const ModulePW::PW_Basis& pw) } } +void check_finite(const std::vector& values, const char* what) +{ + for (double value: values) + { + if (!std::isfinite(value)) + { + const std::string message = std::string("libvdwxc rVV10 received a nonfinite ") + what; + ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", message); + } + } +} + std::array cell_vectors(const ModulePW::PW_Basis& pw) { const ModuleBase::Matrix3& a = pw.latvec; @@ -58,27 +80,93 @@ std::array cell_vectors(const ModulePW::PW_Basis& pw) a.e33 * pw.lat0}; } +bool same_within_rounding(const double lhs, const double rhs) +{ + double scale = 1.0; + if (std::abs(lhs) > scale) + { + scale = std::abs(lhs); + } + if (std::abs(rhs) > scale) + { + scale = std::abs(rhs); + } + return std::abs(lhs - rhs) <= CELL_RELATIVE_TOLERANCE * scale; +} + bool same_cell(const std::array& lhs, const std::array& rhs) { for (std::size_t i = 0; i < lhs.size(); ++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) + if (!same_within_rounding(lhs[i], rhs[i])) { - scale = lhs_abs; + return false; } - if (rhs_abs > scale) + } + return true; +} + +/// Host FFT gradient of the density and sigma = |grad n|^2 on the real-space grid. +void density_gradient(const ModulePW::PW_Basis& pw, + const std::vector& density, + std::vector>& gradient, + std::vector& sigma) +{ + std::vector> density_g(pw.npw); + pw.real2recip(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; + } +} + +/// Energy, potential and vtxc in Ry from the library's Hartree energy and derivatives. +NonlocalXCResult assemble_result(const ModulePW::PW_Basis& pw, + const double energy_ha, + const std::vector& dedn, + const std::vector& dedsigma, + const std::vector>& gradient, + const std::vector& valence_density) +{ + std::vector> field(pw.nrxx); + for (int r = 0; r < pw.nrxx; ++r) + { + if (!std::isfinite(dedn[r]) || !std::isfinite(dedsigma[r])) { - scale = rhs_abs; + ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc returned a nonfinite rVV10 derivative"); } - if (std::abs(lhs[i] - rhs[i]) > CELL_RELATIVE_TOLERANCE * scale) + // Chain rule for sigma = |grad n|^2: dE/d(grad n) = 2 * dE/dsigma * grad n. + 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; + } + std::vector divergence(pw.nrxx, 0.0); + XC_Functional::grad_dot(field.data(), divergence.data(), &pw, pw.tpiba); + + // libvdwxc returns Hartree quantities; e2 converts them to Ry as in PotXC. + // v = dE/dn - div(dE/d(grad n)); vtxc contracts v with the valence density + // only, matching the PotXC bookkeeping. The library includes the beta term. + NonlocalXCResult result; + result.energy = ModuleBase::e2 * energy_ha; + result.vtxc = 0.0; + result.potential.assign(pw.nrxx, 0.0); + const double dv = pw.omega / pw.nxyz; + for (int r = 0; r < pw.nrxx; ++r) + { + result.potential[r] = ModuleBase::e2 * (dedn[r] - divergence[r]); + if (!std::isfinite(result.potential[r])) { - return false; + ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc produced a nonfinite rVV10 potential"); } + result.vtxc += dv * valence_density[r] * result.potential[r]; } - return true; + if (!std::isfinite(result.vtxc)) + { + ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc produced a nonfinite rVV10 vtxc"); + } + return result; } } // namespace @@ -126,107 +214,45 @@ LibvdwxcNonlocalXC::~LibvdwxcNonlocalXC() } } -NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, - const std::vector& total_density, - const std::vector& valence_density) const +void LibvdwxcNonlocalXC::check_matches_initialized(const ModulePW::PW_Basis& pw) const { - check_serial_basis(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; - 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_) <= CELL_RELATIVE_TOLERANCE * 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()) + const bool same_volume = same_within_rounding(pw.omega, omega_); + if (!same_grid || !same_volume || !same_cell(cell_, cell_vectors(pw))) { ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "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)) - { - ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc rVV10 received a nonfinite total density"); - } - } - for (double value: valence_density) +NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, + const std::vector& total_density, + const std::vector& valence_density) const +{ + check_serial_basis(pw); + check_matches_initialized(pw); + const std::size_t grid_size = static_cast(pw.nrxx); + if (total_density.size() != grid_size || valence_density.size() != grid_size) { - if (!std::isfinite(value)) - { - ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc rVV10 received a nonfinite valence density"); - } + ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc rVV10 received densities of the wrong size"); } + check_finite(total_density, "total density"); + check_finite(valence_density, "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; - } + std::vector> gradient(pw.nrxx); + std::vector sigma(pw.nrxx, 0.0); + density_gradient(pw, total_density, gradient, sigma); // vdwxc_calculate takes a non-const density pointer; keep the caller's array const. std::vector density = total_density; + std::vector dedn(pw.nrxx, 0.0); + std::vector dedsigma(pw.nrxx, 0.0); const double energy_ha = vdwxc_calculate(data_, density.data(), sigma.data(), dedn.data(), dedsigma.data()); if (!std::isfinite(energy_ha)) { 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])) - { - ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc returned a nonfinite rVV10 derivative"); - } - // Chain rule for sigma = |grad n|^2: dE/d(grad n) = 2 * dE/dsigma * grad n. - 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); - - // libvdwxc returns Hartree quantities; e2 converts them to Ry as in PotXC. - // v = dE/dn - div(dE/d(grad n)); vtxc contracts v with the valence density - // only, matching the PotXC bookkeeping. The library includes the beta term. - NonlocalXCResult result; - result.energy = ModuleBase::e2 * energy_ha; - result.vtxc = 0.0; - result.potential.assign(pw.nrxx, 0.0); - const double dv = pw.omega / pw.nxyz; - for (int r = 0; r < pw.nrxx; ++r) - { - result.potential[r] = ModuleBase::e2 * (dedn[r] - divergence[r]); - if (!std::isfinite(divergence[r]) || !std::isfinite(result.potential[r])) - { - ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc produced a nonfinite rVV10 potential"); - } - result.vtxc += dv * valence_density[r] * result.potential[r]; - } - if (!std::isfinite(result.vtxc)) - { - ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc produced a nonfinite rVV10 vtxc"); - } - return result; + return assemble_result(pw, energy_ha, dedn, dedsigma, gradient, valence_density); } } // namespace vdw diff --git a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h b/source/source_hamilt/module_vdw/nonlocal_vdwxc.h similarity index 83% rename from source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h rename to source/source_hamilt/module_vdw/nonlocal_vdwxc.h index dfe45a58d6f..5636f85f01a 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc_libvdwxc.h +++ b/source/source_hamilt/module_vdw/nonlocal_vdwxc.h @@ -1,5 +1,5 @@ -#ifndef ABACUS_NONLOCAL_XC_LIBVDWXC_H -#define ABACUS_NONLOCAL_XC_LIBVDWXC_H +#ifndef ABACUS_NONLOCAL_VDWXC_H +#define ABACUS_NONLOCAL_VDWXC_H #include "nonlocal_xc.h" @@ -34,6 +34,9 @@ class LibvdwxcNonlocalXC final : public NonlocalXC const std::vector& valence_density) const override; private: + /// Quit unless pw has the grid, cell and volume this adapter was initialized with. + void check_matches_initialized(const ModulePW::PW_Basis& pw) const; + vdwxc_data_obj* data_ = nullptr; int nx_ = 0; int ny_ = 0; @@ -45,4 +48,4 @@ class LibvdwxcNonlocalXC final : public NonlocalXC } // namespace vdw -#endif // ABACUS_NONLOCAL_XC_LIBVDWXC_H +#endif // ABACUS_NONLOCAL_VDWXC_H diff --git a/source/source_hamilt/module_vdw/nonlocal_xc.cpp b/source/source_hamilt/module_vdw/nonlocal_xc.cpp index 9f51a8ee783..2282107aaab 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc.cpp +++ b/source/source_hamilt/module_vdw/nonlocal_xc.cpp @@ -1,6 +1,6 @@ #include "nonlocal_xc.h" #ifdef __LIBVDWXC -#include "nonlocal_xc_libvdwxc.h" +#include "nonlocal_vdwxc.h" #endif #include "source_base/tool_quit.h" diff --git a/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp b/source/source_hamilt/module_vdw/test/test_nonlocal_vdwxc.cpp similarity index 99% rename from source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp rename to source/source_hamilt/module_vdw/test/test_nonlocal_vdwxc.cpp index cce1c5cfeab..2d9e2b0449f 100644 --- a/source/source_hamilt/module_vdw/test/test_nonlocal_xc_libvdwxc.cpp +++ b/source/source_hamilt/module_vdw/test/test_nonlocal_vdwxc.cpp @@ -1,4 +1,4 @@ -#include "../nonlocal_xc_libvdwxc.h" +#include "../nonlocal_vdwxc.h" #include "source_base/constants.h" #include "source_basis/module_pw/pw_basis.h" #include "source_hamilt/module_xc/xc_functional.h" From a682b5659735fb75b0d2552288b521f3f0a3dfe2 Mon Sep 17 00:00:00 2001 From: 1 Date: Mon, 26 Oct 2026 13:25:12 +0800 Subject: [PATCH 24/28] style(xc): format the new hunks with the pre-commit clang-format and cmake-format The pre-commit job formats the whole tree, so its exit code reflects upstream formatting debt (2257 C++ and 67 CMake files at develop 8e20175); this commit only brings the lines this PR adds in line with clang-format 23.1.3 and cmake-format 0.6.13: two wrapped WARNING_QUIT calls, the xc_nonlocal field comment, the std::transform iterator arguments, and the two new test-registration blocks. No functional change; tokens verified equal before and after. Co-Authored-By: Claude Fable 5.1 --- source/source_estate/CMakeLists.txt | 47 ++++++++++++------- .../module_pot/potential_types.cpp | 6 ++- .../source_hamilt/module_vdw/CMakeLists.txt | 28 +++++++---- .../module_parameter/input_parameter.h | 2 +- .../module_parameter/read_inp_estruc.cpp | 4 +- 5 files changed, 57 insertions(+), 30 deletions(-) diff --git a/source/source_estate/CMakeLists.txt b/source/source_estate/CMakeLists.txt index 2832ee33f4c..7a222ad13f1 100644 --- a/source/source_estate/CMakeLists.txt +++ b/source/source_estate/CMakeLists.txt @@ -101,27 +101,38 @@ if(BUILD_TESTING) add_subdirectory(test) add_subdirectory(module_charge/unittests) endif() - if(ENABLE_LIBVDWXC AND NOT ENABLE_MPI AND NOT USE_CUDA AND NOT USE_ROCM) + if(ENABLE_LIBVDWXC + AND NOT ENABLE_MPI + AND NOT USE_CUDA + AND NOT USE_ROCM) # The serial libvdwxc host test lives outside the MPI-only test directory. # It drives PotNonlocalXC with real Charge and UnitCell objects. - AddTest( - TARGET MODULE_ESTATE_pot_nonlocal_xc_LIBVDWXC - LIBS xc_ planewave base parameter device nonlocal_xc + addtest( + TARGET + MODULE_ESTATE_pot_nonlocal_xc_LIBVDWXC + LIBS + xc_ + planewave + base + parameter + device + nonlocal_xc SOURCES - test/test_pot_nonlocal_xc.cpp - module_pot/pot_nonlocal_xc.cpp - module_charge/charge.cpp - 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") + test/test_pot_nonlocal_xc.cpp + module_pot/pot_nonlocal_xc.cpp + module_charge/charge.cpp + 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() diff --git a/source/source_estate/module_pot/potential_types.cpp b/source/source_estate/module_pot/potential_types.cpp index 1202c148585..2408863ae1a 100644 --- a/source/source_estate/module_pot/potential_types.cpp +++ b/source/source_estate/module_pot/potential_types.cpp @@ -73,12 +73,14 @@ PotBase* Potential::get_pot_type(const std::string& pot_type, const Input_para& { if (this->ucell_->atoms[it].ncpp.tvanp) { - ModuleBase::WARNING_QUIT("Potential", "nonlocal XC currently supports norm-conserving pseudopotentials only"); + 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"); + ModuleBase::WARNING_QUIT("Potential", + "nonlocal XC currently requires the same smooth and dense density basis"); } const std::string method = input.xc_nonlocal; return new PotNonlocalXC(this->rho_basis_, this->etxc_, this->vtxc_, method); diff --git a/source/source_hamilt/module_vdw/CMakeLists.txt b/source/source_hamilt/module_vdw/CMakeLists.txt index 06082c00e44..c7af1963297 100644 --- a/source/source_hamilt/module_vdw/CMakeLists.txt +++ b/source/source_hamilt/module_vdw/CMakeLists.txt @@ -39,14 +39,26 @@ if(BUILD_TESTING) if(ENABLE_MPI) add_subdirectory(test) endif() - if(ENABLE_LIBVDWXC AND NOT ENABLE_MPI AND NOT USE_CUDA AND NOT USE_ROCM) + if(ENABLE_LIBVDWXC + AND NOT ENABLE_MPI + AND NOT USE_CUDA + AND NOT USE_ROCM) # The serial adapter test is registered here because test/ is MPI-only. - AddTest( - TARGET MODULE_HAMILT_vdwTest_LIBVDWXC - LIBS xc_ planewave base parameter device nonlocal_xc - SOURCES test/test_nonlocal_vdwxc.cpp - ) - target_link_libraries(MODULE_HAMILT_vdwTest_LIBVDWXC PRIVATE Libxc::xc libvdwxc::vdwxc) - set_tests_properties(MODULE_HAMILT_vdwTest_LIBVDWXC PROPERTIES ENVIRONMENT "OMP_NUM_THREADS=1") + addtest( + TARGET + MODULE_HAMILT_vdwTest_LIBVDWXC + LIBS + xc_ + planewave + base + parameter + device + nonlocal_xc + SOURCES + test/test_nonlocal_vdwxc.cpp) + target_link_libraries(MODULE_HAMILT_vdwTest_LIBVDWXC + PRIVATE Libxc::xc libvdwxc::vdwxc) + set_tests_properties(MODULE_HAMILT_vdwTest_LIBVDWXC + PROPERTIES ENVIRONMENT "OMP_NUM_THREADS=1") endif() endif() diff --git a/source/source_io/module_parameter/input_parameter.h b/source/source_io/module_parameter/input_parameter.h index 7959fc969ca..449744a63ff 100644 --- a/source/source_io/module_parameter/input_parameter.h +++ b/source/source_io/module_parameter/input_parameter.h @@ -88,7 +88,7 @@ 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 mapped-density LCA. - std::string xc_nonlocal = "none"; ///< nonlocal XC correction (none; rvv10) + std::string xc_nonlocal = "none"; ///< nonlocal XC correction (none; rvv10) 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 2526879c7b4..9b33a398408 100644 --- a/source/source_io/module_parameter/read_inp_estruc.cpp +++ b/source/source_io/module_parameter/read_inp_estruc.cpp @@ -385,7 +385,9 @@ The other way is only available when compiling with LIBXC, and it allows for sup 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(), + 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) { From 256d2162625485748162cd4f2a43138cb86b060a Mon Sep 17 00:00:00 2001 From: 1 Date: Tue, 27 Oct 2026 00:17:40 +0000 Subject: [PATCH 25/28] refactor(xc): select rVV10 through dft_functional and add it in XC_Functional::v_xc Replace the xc_nonlocal INPUT item and the PotXC hook by the dft_functional short name RVV10 (rPW86 exchange, PBE correlation, rVV10 with b = 6.3 and C = 0.0093), stored next to func_id like the other short names. The nonlocal term is added in the libxc branch of XC_Functional::v_xc, so the SCF potential, the NLCC force and stress terms, out_mat_h_vxc, PotXC_FDM, RDMFT and the DeePKS base functional all use the same XC potential, and no force-specific restriction is needed. The libvdwxc adapter becomes one stateless function: it creates the handle per call, as the libxc wrappers do, and collects the grid on the pool root with the Parallel_Common wrappers. It is compiled into the XC library only when ENABLE_LIBVDWXC is on, following the DFT-D4 build pattern. Stress and DFPT are rejected where those quantities are built. Co-Authored-By: Claude Opus 5.5 --- CMakeLists.txt | 16 +- cmake/CollectBuildInfoVars.cmake | 8 +- docs/CITATIONS.md | 2 +- docs/advanced/install.md | 14 +- generate_build_info.sh | 8 +- source/CMakeLists.txt | 8 +- source/Makefile.Objects | 2 - source/source_esolver/esolver_dfpt_pw.cpp | 5 + source/source_estate/CMakeLists.txt | 34 -- .../module_pot/pot_nonlocal_xc.cpp | 58 --- .../module_pot/pot_nonlocal_xc.h | 34 -- .../module_pot/potential_types.cpp | 29 +- .../test/test_pot_nonlocal_xc.cpp | 76 --- .../source_hamilt/module_vdw/CMakeLists.txt | 34 -- .../module_vdw/nonlocal_vdwxc.cpp | 260 ----------- .../source_hamilt/module_vdw/nonlocal_vdwxc.h | 51 -- .../source_hamilt/module_vdw/nonlocal_xc.cpp | 196 +++++++- source/source_hamilt/module_vdw/nonlocal_xc.h | 60 +-- .../module_vdw/test/CMakeLists.txt | 22 + .../module_vdw/test/test_nonlocal_vdwxc.cpp | 283 ----------- .../module_vdw/test/test_nonlocal_xc.cpp | 440 ++++++++++++++++++ source/source_hamilt/module_xc/CMakeLists.txt | 6 + .../module_xc/test/CMakeLists.txt | 3 + .../source_hamilt/module_xc/xc_functional.cpp | 31 +- .../source_hamilt/module_xc/xc_functional.h | 11 + source/source_hamilt/module_xc/xc_pot.cpp | 36 +- source/source_io/build_info.h.in | 2 +- .../module_parameter/input_parameter.h | 1 - .../module_parameter/read_inp_estruc.cpp | 119 +---- source/source_io/parse_args.cpp | 2 +- .../source_io/test_serial/read_input_test.cpp | 133 +----- source/source_pw/module_pwdft/hamilt_pw.cpp | 4 - source/source_pw/module_pwdft/stress_gga.cpp | 6 + 33 files changed, 771 insertions(+), 1223 deletions(-) delete mode 100644 source/source_estate/module_pot/pot_nonlocal_xc.cpp delete mode 100644 source/source_estate/module_pot/pot_nonlocal_xc.h delete mode 100644 source/source_estate/test/test_pot_nonlocal_xc.cpp delete mode 100644 source/source_hamilt/module_vdw/nonlocal_vdwxc.cpp delete mode 100644 source/source_hamilt/module_vdw/nonlocal_vdwxc.h delete mode 100644 source/source_hamilt/module_vdw/test/test_nonlocal_vdwxc.cpp create mode 100644 source/source_hamilt/module_vdw/test/test_nonlocal_xc.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 79294deedee..7b6657c531e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,7 +49,8 @@ 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) +cmake_dependent_option(ENABLE_LIBVDWXC "Enable using the libvdwxc package" OFF + "ENABLE_LIBXC" OFF) option(ENABLE_FLOAT_FFTW "Enable using single-precision FFTW library." OFF) option(ENABLE_LCAO "Enable LCAO algorithm" ON) @@ -645,20 +646,7 @@ if(ENABLE_LIBXC) endif() if(ENABLE_LIBVDWXC) - if(NOT ENABLE_LIBXC) - message( - FATAL_ERROR - "ENABLE_LIBVDWXC requires ENABLE_LIBXC=ON: the rVV10 interface uses Libxc for the semilocal GGA_X_RPW86+GGA_C_PBE part." - ) - endif() - # The exported target supplies headers and transitive FFT dependencies. find_package(libvdwxc CONFIG REQUIRED) - if(MKL_FOUND) - message( - WARNING - "ENABLE_LIBVDWXC with MKL: libvdwxc links its own FFTW3 while ABACUS uses the MKL FFTW3 interface, so two FFT providers would coexist in one process. The libvdwxc backend has only been validated with a standalone FFTW3 build of both ABACUS and libvdwxc." - ) - endif() endif() if(DEFINED DeePMD_DIR) diff --git a/cmake/CollectBuildInfoVars.cmake b/cmake/CollectBuildInfoVars.cmake index ca2cc538f2e..0894c06f902 100644 --- a/cmake/CollectBuildInfoVars.cmake +++ b/cmake/CollectBuildInfoVars.cmake @@ -192,10 +192,8 @@ else() set(ABACUS_DFTD4_VERSION "no") endif() -if(ENABLE_LIBVDWXC AND libvdwxc_VERSION) +if(ENABLE_LIBVDWXC) 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() @@ -426,7 +424,6 @@ 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}") @@ -441,7 +438,6 @@ 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}) @@ -488,9 +484,9 @@ 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(" libvdwxc = ${ABACUS_LIBVDWXC_VERSION}") message(" JSON = ${ABACUS_JSON_VERSION}") message(" PEXSI = ${ABACUS_PEXSI_VERSION}") message(" CNPY = ${ABACUS_CNPY_VERSION}") diff --git a/docs/CITATIONS.md b/docs/CITATIONS.md index 839f4bad4d3..46e59414be0 100644 --- a/docs/CITATIONS.md +++ b/docs/CITATIONS.md @@ -56,7 +56,7 @@ The following references are required to be cited when using ABACUS. Specificall 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:** +- **If the rVV10 nonlocal correlation (`dft_functional rvv10`) 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. diff --git a/docs/advanced/install.md b/docs/advanced/install.md index 4a2cadc0202..ddbeb3d191e 100644 --- a/docs/advanced/install.md +++ b/docs/advanced/install.md @@ -99,20 +99,16 @@ 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 +## Build with libvdwxc support -`xc_nonlocal rvv10` uses the external [libvdwxc](https://gitlab.com/libvdwxc/libvdwxc) library for the rVV10 nonlocal kernel. Until a tagged libvdwxc release includes the rVV10 API, build and install the rVV10 branch from [libvdwxc merge request !32](https://gitlab.com/libvdwxc/libvdwxc/-/merge_requests/32) (commit `94410eb` at the time of writing) against the same FFTW that ABACUS will use, then pass its prefix together with Libxc. `ENABLE_LIBVDWXC` requires `ENABLE_LIBXC`. A serial configuration: +The rVV10 nonlocal correlation (`dft_functional rvv10`) is evaluated by the external [libvdwxc](https://gitlab.com/libvdwxc/libvdwxc) library. Until a tagged libvdwxc release contains the rVV10 interface, build the branch of [libvdwxc merge request !32](https://gitlab.com/libvdwxc/libvdwxc/-/merge_requests/32) (commit `94410eb` at the time of writing) against the FFTW that ABACUS uses, with libvdwxc's CMake build so that ABACUS can find the exported package; a static libvdwxc also needs `pkg-config` and FFTW's `fftw3.pc`: ```bash -cmake -B build -DENABLE_MPI=OFF -DENABLE_LCAO=OFF \ - -DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON \ - -DCMAKE_PREFIX_PATH="/path/to/libvdwxc-prefix;/path/to/libxc-prefix" \ - -DFFTW3_DIR=/path/to/fftw-prefix +cmake -S libvdwxc -B build-libvdwxc -DCMAKE_INSTALL_PREFIX=/path/to/libvdwxc +cmake --build build-libvdwxc && cmake --install build-libvdwxc ``` -Keep one FFT provider per process: do not combine a libvdwxc built against FFTW with an ABACUS build that uses the MKL FFT interface. The legacy Makefile does not build this backend. - -Enable the correction with `xc_nonlocal rvv10` and `dft_functional GGA_X_RPW86+GGA_C_PBE`; the rVV10 parameters b=6.3 and C=0.0093 of this pairing are built in. `abacus -h xc_nonlocal` lists the supported calculation settings; unsupported ones are rejected when INPUT is read. +Then pass `-DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON` to CMake for ABACUS and add `/path/to/libvdwxc` to `CMAKE_PREFIX_PATH`. A serial libvdwxc build is sufficient: in MPI runs, ABACUS collects the density of each k-point pool on its first process for the nonlocal term. Stress, DFPT and LR-TDDFT forces are not supported with this functional, and the legacy Makefile does not build it. ## Build with JSON support diff --git a/generate_build_info.sh b/generate_build_info.sh index 48f46416f62..afe6325ca56 100755 --- a/generate_build_info.sh +++ b/generate_build_info.sh @@ -165,12 +165,6 @@ if [ "${USE_CUDA}" == "ON" ]; then fi fi -# 6b. libvdwxc -# -# 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" - # 7. ROCm/HIP ROCM_VERSION="no" if [ "${USE_ROCM}" == "ON" ]; then @@ -285,7 +279,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_LIBVDWXC_VERSION@#no#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" \ diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 2f8766b5e02..287206ef031 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -66,8 +66,8 @@ set(_abacus_feature_definitions $<$:TEST_EXX_LCAO=0> $<$:TEST_EXX_RADIAL=1> $<$:__LIBXC> - $<$:__LIBVDWXC> $<$:__DFTD4> + $<$:__LIBVDWXC> $<$:GATHER_INFO>) if(ABACUS_COMMIT_INFO_INCLUDE_DIR) @@ -282,8 +282,7 @@ set(_abacus_feature_libs $<$:ZLIB::ZLIB> $<$:cereal::cereal> $<$:Libxc::xc> - $<$:PLUMED::plumed> - $<$:libvdwxc::vdwxc>) + $<$:PLUMED::plumed>) set(_abacus_feature_include_dirs) set(_abacus_feature_compile_options) @@ -408,8 +407,8 @@ foreach( cereal::cereal ELPA::ELPA Libxc::xc - libvdwxc::vdwxc dftd4::dftd4 + libvdwxc::vdwxc DeePMD::deepmd_c DeePMD::deepmd_cc NEP::nep @@ -769,7 +768,6 @@ 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 36a52c5359d..ced2b16b823 100644 --- a/source/Makefile.Objects +++ b/source/Makefile.Objects @@ -297,7 +297,6 @@ 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\ @@ -1049,7 +1048,6 @@ 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_esolver/esolver_dfpt_pw.cpp b/source/source_esolver/esolver_dfpt_pw.cpp index 777a678d4a2..0e01e52aeb5 100644 --- a/source/source_esolver/esolver_dfpt_pw.cpp +++ b/source/source_esolver/esolver_dfpt_pw.cpp @@ -317,6 +317,11 @@ void ESolver_DFPT_PW::init_dfpt(UnitCell& ucell) << ") needs response channels that are not implemented"; ModuleBase::WARNING_QUIT("ESolver_DFPT_PW::init_dfpt", msg.str()); } + if (XC_Functional::get_nlc_b() > 0.0) + { + ModuleBase::WARNING_QUIT("ESolver_DFPT_PW::init_dfpt", + "the response of the rVV10 nonlocal correlation is not implemented"); + } // first-order XC kernel around the converged ground-state density: // with libxc use the analytic kernel (libxc second derivatives in the diff --git a/source/source_estate/CMakeLists.txt b/source/source_estate/CMakeLists.txt index 7a222ad13f1..b78f8422663 100644 --- a/source/source_estate/CMakeLists.txt +++ b/source/source_estate/CMakeLists.txt @@ -19,7 +19,6 @@ 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/pot_sccs.cpp @@ -101,39 +100,6 @@ if(BUILD_TESTING) add_subdirectory(test) add_subdirectory(module_charge/unittests) endif() - if(ENABLE_LIBVDWXC - AND NOT ENABLE_MPI - AND NOT USE_CUDA - AND NOT USE_ROCM) - # The serial libvdwxc host test lives outside the MPI-only test directory. - # It drives PotNonlocalXC with real Charge and UnitCell objects. - addtest( - TARGET - MODULE_ESTATE_pot_nonlocal_xc_LIBVDWXC - LIBS - xc_ - planewave - base - parameter - device - nonlocal_xc - SOURCES - test/test_pot_nonlocal_xc.cpp - module_pot/pot_nonlocal_xc.cpp - module_charge/charge.cpp - 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_LCAO) diff --git a/source/source_estate/module_pot/pot_nonlocal_xc.cpp b/source/source_estate/module_pot/pot_nonlocal_xc.cpp deleted file mode 100644 index e59b38b3505..00000000000 --- a/source/source_estate/module_pot/pot_nonlocal_xc.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "pot_nonlocal_xc.h" - -#include "source_base/timer.h" -#include "source_base/tool_quit.h" -#include "source_hamilt/module_vdw/nonlocal_xc.h" - -#include - -namespace elecstate -{ -PotNonlocalXC::PotNonlocalXC(const ModulePW::PW_Basis* density_basis, - double* etxc, - double* vtxc, - const std::string& method) - : etxc_(etxc), vtxc_(vtxc) -{ - backend_ = vdw::make_nonlocal_xc(density_basis, method); - 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_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 || v_eff.nc != chg->nrxx) - { - ModuleBase::WARNING_QUIT("PotNonlocalXC", "nonlocal XC requires a consistent nspin=1 density basis"); - } - // nspin=1 is enforced above, so the valence density is the single channel. - const int spin_channel = 0; - const std::vector valence(chg->rho[spin_channel], chg->rho[spin_channel] + chg->nrxx); - std::vector total(chg->nrxx, 0.0); - for (int r = 0; r < chg->nrxx; ++r) - { - total[r] = valence[r] + chg->rho_core[r]; - } - 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"); - } - - // 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 r = 0; r < chg->nrxx; ++r) - { - v_eff(spin_channel, r) += nl.potential[r]; - } - ModuleBase::timer::end("PotNonlocalXC", "cal_v_eff"); -} -} // namespace elecstate diff --git a/source/source_estate/module_pot/pot_nonlocal_xc.h b/source/source_estate/module_pot/pot_nonlocal_xc.h deleted file mode 100644 index 01ad10bef43..00000000000 --- a/source/source_estate/module_pot/pot_nonlocal_xc.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef ABACUS_POT_NONLOCAL_XC_H -#define ABACUS_POT_NONLOCAL_XC_H -#include "pot_base.h" - -#include -#include - -namespace vdw -{ -class NonlocalXC; -} - -namespace elecstate -{ -/** - * @brief Additive density-grid nonlocal XC correction for the CPU/PW SCF path. - * - * Registered after PotXC; the numerical method lives behind vdw::NonlocalXC. - * etxc and vtxc point at the energy bookkeeping that PotXC already fills. - */ -class PotNonlocalXC final : public PotBase -{ - public: - PotNonlocalXC(const ModulePW::PW_Basis* density_basis, double* etxc, double* vtxc, const std::string& method); - ~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_types.cpp b/source/source_estate/module_pot/potential_types.cpp index 2408863ae1a..0cd6048e667 100644 --- a/source/source_estate/module_pot/potential_types.cpp +++ b/source/source_estate/module_pot/potential_types.cpp @@ -1,9 +1,7 @@ +#include "h_hartree_pw.h" #include "efield.h" #include "gatefield.h" -#include "h_hartree_pw.h" -#include "h_tddft_pw.h" #include "pot_local.h" -#include "pot_nonlocal_xc.h" #include "pot_pcc.h" #include "pot_sccs.h" #include "pot_sep.h" @@ -16,6 +14,7 @@ #include "source_base/tool_quit.h" #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 @@ -61,30 +60,6 @@ PotBase* Potential::get_pot_type(const std::string& pot_type, const Input_para& 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") - { - if (this->ucell_ == nullptr) - { - ModuleBase::WARNING_QUIT("Potential", "nonlocal XC requires the unit cell"); - } - // 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"); - } - const std::string method = input.xc_nonlocal; - return new PotNonlocalXC(this->rho_basis_, this->etxc_, this->vtxc_, method); - } else if (pot_type == "sccs") { return this->make_sccs_pot(input); diff --git a/source/source_estate/test/test_pot_nonlocal_xc.cpp b/source/source_estate/test/test_pot_nonlocal_xc.cpp deleted file mode 100644 index 46d8b9331e9..00000000000 --- a/source/source_estate/test/test_pot_nonlocal_xc.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include "source_base/constants.h" -#include "source_estate/module_pot/pot_nonlocal_xc.h" -#include "source_hamilt/module_vdw/nonlocal_vdwxc.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); - - // Host and adapter add the same numbers, so they agree to rounding. - const double rounding_tolerance = 2.0e-12; - 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 = ModuleBase::TWO_PI * x / pw.nx; - const double ay = ModuleBase::TWO_PI * y / pw.ny; - const double az = ModuleBase::TWO_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, vdw::RVV10_B_RPW86_PBE, vdw::RVV10_C_RPW86_PBE); - 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"); - 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, rounding_tolerance); - EXPECT_NEAR(vtxc, expected_vtxc, rounding_tolerance); - 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, rounding_tolerance); - EXPECT_DOUBLE_EQ(charge.rho[0][r], valence[r]); - EXPECT_DOUBLE_EQ(charge.rho_core[r], core[r]); - } -} diff --git a/source/source_hamilt/module_vdw/CMakeLists.txt b/source/source_hamilt/module_vdw/CMakeLists.txt index c7af1963297..e51299da04d 100644 --- a/source/source_hamilt/module_vdw/CMakeLists.txt +++ b/source/source_hamilt/module_vdw/CMakeLists.txt @@ -15,50 +15,16 @@ 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_vdwxc.cpp) -endif() - -add_library(nonlocal_xc OBJECT ${nonlocal_xc_sources}) - if(ENABLE_DFTD4) target_link_libraries(vdw PUBLIC dftd4::dftd4) endif() if(ENABLE_COVERAGE) add_coverage(vdw) - add_coverage(nonlocal_xc) endif() if(BUILD_TESTING) if(ENABLE_MPI) add_subdirectory(test) endif() - if(ENABLE_LIBVDWXC - AND NOT ENABLE_MPI - AND NOT USE_CUDA - AND NOT USE_ROCM) - # The serial adapter test is registered here because test/ is MPI-only. - addtest( - TARGET - MODULE_HAMILT_vdwTest_LIBVDWXC - LIBS - xc_ - planewave - base - parameter - device - nonlocal_xc - SOURCES - test/test_nonlocal_vdwxc.cpp) - target_link_libraries(MODULE_HAMILT_vdwTest_LIBVDWXC - PRIVATE Libxc::xc libvdwxc::vdwxc) - set_tests_properties(MODULE_HAMILT_vdwTest_LIBVDWXC - PROPERTIES ENVIRONMENT "OMP_NUM_THREADS=1") - endif() endif() diff --git a/source/source_hamilt/module_vdw/nonlocal_vdwxc.cpp b/source/source_hamilt/module_vdw/nonlocal_vdwxc.cpp deleted file mode 100644 index af76ae03941..00000000000 --- a/source/source_hamilt/module_vdw/nonlocal_vdwxc.cpp +++ /dev/null @@ -1,260 +0,0 @@ -#include "nonlocal_vdwxc.h" - -#ifdef __LIBVDWXC - -// libvdwxc's public C header includes inside an extern "C" -// block. Pre-include the standard header so libc++ does not parse its C++ -// templates with C linkage, then drop the I macro that complex.h defines. -#include -#include -#ifdef I -#undef I -#endif - -#include "source_base/constants.h" -#include "source_base/tool_quit.h" -#include "source_basis/module_pw/pw_basis.h" -#include "source_hamilt/module_xc/xc_functional.h" - -#include -#include -#include -#include - -namespace vdw -{ -namespace -{ -// The cell handed to libvdwxc at construction and the cell seen at each -// evaluation come from the same PW_Basis, so only rounding may differ. -const double CELL_RELATIVE_TOLERANCE = 1.0e-12; - -bool serial_cpu_grid(const ModulePW::PW_Basis& pw) -{ - return !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; -} - -bool right_handed_cell(const ModulePW::PW_Basis& pw) -{ - const double determinant = pw.latvec.Det(); - return std::isfinite(pw.omega) && pw.omega > 0.0 && std::isfinite(determinant) && determinant > 0.0 - && std::isfinite(pw.tpiba) && pw.tpiba > 0.0; -} - -void check_serial_basis(const ModulePW::PW_Basis& pw) -{ - if (!serial_cpu_grid(pw) || !right_handed_cell(pw)) - { - ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", - "libvdwxc rVV10 requires a one-process CPU double-precision full-complex PW density " - "grid and a right-handed cell"); - } -} - -void check_finite(const std::vector& values, const char* what) -{ - for (double value: values) - { - if (!std::isfinite(value)) - { - const std::string message = std::string("libvdwxc rVV10 received a nonfinite ") + what; - ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", message); - } - } -} - -std::array cell_vectors(const ModulePW::PW_Basis& pw) -{ - const ModuleBase::Matrix3& 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.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}; -} - -bool same_within_rounding(const double lhs, const double rhs) -{ - double scale = 1.0; - if (std::abs(lhs) > scale) - { - scale = std::abs(lhs); - } - if (std::abs(rhs) > scale) - { - scale = std::abs(rhs); - } - return std::abs(lhs - rhs) <= CELL_RELATIVE_TOLERANCE * scale; -} - -bool same_cell(const std::array& lhs, const std::array& rhs) -{ - for (std::size_t i = 0; i < lhs.size(); ++i) - { - if (!same_within_rounding(lhs[i], rhs[i])) - { - return false; - } - } - return true; -} - -/// Host FFT gradient of the density and sigma = |grad n|^2 on the real-space grid. -void density_gradient(const ModulePW::PW_Basis& pw, - const std::vector& density, - std::vector>& gradient, - std::vector& sigma) -{ - std::vector> density_g(pw.npw); - pw.real2recip(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; - } -} - -/// Energy, potential and vtxc in Ry from the library's Hartree energy and derivatives. -NonlocalXCResult assemble_result(const ModulePW::PW_Basis& pw, - const double energy_ha, - const std::vector& dedn, - const std::vector& dedsigma, - const std::vector>& gradient, - const std::vector& valence_density) -{ - std::vector> field(pw.nrxx); - for (int r = 0; r < pw.nrxx; ++r) - { - if (!std::isfinite(dedn[r]) || !std::isfinite(dedsigma[r])) - { - ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc returned a nonfinite rVV10 derivative"); - } - // Chain rule for sigma = |grad n|^2: dE/d(grad n) = 2 * dE/dsigma * grad n. - 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; - } - std::vector divergence(pw.nrxx, 0.0); - XC_Functional::grad_dot(field.data(), divergence.data(), &pw, pw.tpiba); - - // libvdwxc returns Hartree quantities; e2 converts them to Ry as in PotXC. - // v = dE/dn - div(dE/d(grad n)); vtxc contracts v with the valence density - // only, matching the PotXC bookkeeping. The library includes the beta term. - NonlocalXCResult result; - result.energy = ModuleBase::e2 * energy_ha; - result.vtxc = 0.0; - result.potential.assign(pw.nrxx, 0.0); - const double dv = pw.omega / pw.nxyz; - for (int r = 0; r < pw.nrxx; ++r) - { - result.potential[r] = ModuleBase::e2 * (dedn[r] - divergence[r]); - if (!std::isfinite(result.potential[r])) - { - ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc produced a nonfinite rVV10 potential"); - } - result.vtxc += dv * valence_density[r] * result.potential[r]; - } - if (!std::isfinite(result.vtxc)) - { - ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc produced a nonfinite rVV10 vtxc"); - } - return result; -} -} // namespace - -LibvdwxcNonlocalXC::LibvdwxcNonlocalXC(const ModulePW::PW_Basis& pw, double b, double c) -{ - check_serial_basis(pw); - data_ = vdwxc_new(VDWXC_RVV10); - if (data_ == nullptr) - { - ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc failed to create an rVV10 evaluator"); - } - if (vdwxc_set_rvv10_parameters(data_, b, c) != 0) - { - vdwxc_finalize(&data_); - ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc rejected the rVV10 b/C parameters"); - } - - cell_ = cell_vectors(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_); - } -} - -void LibvdwxcNonlocalXC::check_matches_initialized(const ModulePW::PW_Basis& pw) const -{ - const bool same_grid = pw.nx == nx_ && pw.ny == ny_ && pw.nz == nz_; - const bool same_volume = same_within_rounding(pw.omega, omega_); - if (!same_grid || !same_volume || !same_cell(cell_, cell_vectors(pw))) - { - ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", - "libvdwxc rVV10 received a density grid different from its initialized cell"); - } -} - -NonlocalXCResult LibvdwxcNonlocalXC::evaluate(const ModulePW::PW_Basis& pw, - const std::vector& total_density, - const std::vector& valence_density) const -{ - check_serial_basis(pw); - check_matches_initialized(pw); - const std::size_t grid_size = static_cast(pw.nrxx); - if (total_density.size() != grid_size || valence_density.size() != grid_size) - { - ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc rVV10 received densities of the wrong size"); - } - check_finite(total_density, "total density"); - check_finite(valence_density, "valence density"); - - std::vector> gradient(pw.nrxx); - std::vector sigma(pw.nrxx, 0.0); - density_gradient(pw, total_density, gradient, sigma); - - // vdwxc_calculate takes a non-const density pointer; keep the caller's array const. - std::vector density = total_density; - std::vector dedn(pw.nrxx, 0.0); - std::vector dedsigma(pw.nrxx, 0.0); - const double energy_ha = vdwxc_calculate(data_, density.data(), sigma.data(), dedn.data(), dedsigma.data()); - if (!std::isfinite(energy_ha)) - { - ModuleBase::WARNING_QUIT("LibvdwxcNonlocalXC", "libvdwxc returned a nonfinite rVV10 energy"); - } - return assemble_result(pw, energy_ha, dedn, dedsigma, gradient, valence_density); -} - -} // namespace vdw - -#endif // __LIBVDWXC diff --git a/source/source_hamilt/module_vdw/nonlocal_vdwxc.h b/source/source_hamilt/module_vdw/nonlocal_vdwxc.h deleted file mode 100644 index 5636f85f01a..00000000000 --- a/source/source_hamilt/module_vdw/nonlocal_vdwxc.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef ABACUS_NONLOCAL_VDWXC_H -#define ABACUS_NONLOCAL_VDWXC_H - -#include "nonlocal_xc.h" - -#include - -// Opaque libvdwxc handle; vdwxc_data is a pointer to this struct. The C header -// is included only by the implementation file. -struct vdwxc_data_obj; - -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; - LibvdwxcNonlocalXC(const LibvdwxcNonlocalXC&) = delete; - LibvdwxcNonlocalXC& operator=(const LibvdwxcNonlocalXC&) = delete; - - NonlocalXCResult evaluate(const ModulePW::PW_Basis& pw, - const std::vector& total_density, - const std::vector& valence_density) const override; - - private: - /// Quit unless pw has the grid, cell and volume this adapter was initialized with. - void check_matches_initialized(const ModulePW::PW_Basis& pw) const; - - vdwxc_data_obj* data_ = nullptr; - int nx_ = 0; - int ny_ = 0; - int nz_ = 0; - double omega_ = 0.0; - std::array cell_{}; -}; -#endif - -} // namespace vdw - -#endif // ABACUS_NONLOCAL_VDWXC_H diff --git a/source/source_hamilt/module_vdw/nonlocal_xc.cpp b/source/source_hamilt/module_vdw/nonlocal_xc.cpp index 2282107aaab..e122a77901b 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc.cpp +++ b/source/source_hamilt/module_vdw/nonlocal_xc.cpp @@ -1,31 +1,197 @@ #include "nonlocal_xc.h" -#ifdef __LIBVDWXC -#include "nonlocal_vdwxc.h" -#endif -#include "source_base/tool_quit.h" +#include "source_base/constants.h" +#include "source_base/matrix.h" +#include "source_base/parallel_device.h" +#include "source_base/timer.h" +#include "source_base/tool_title.h" +#include "source_basis/module_pw/pw_basis.h" +#include "source_estate/module_charge/charge.h" +#include "source_hamilt/module_xc/xc_functional.h" + +// vdwxc.h includes inside an extern "C" block. Include the C++ header first so that +// libc++ does not parse its templates with C linkage, then drop the I macro. +#include +#include +#include +#undef I + +#include namespace vdw { +namespace +{ +/// Copy the z planes [startz, startz + nplane) of a block stored as [ixy][iz] into the complete grid [ixy][z]. +void put_planes(const ModulePW::PW_Basis& pw, + const int nplane, + const int startz, + const double* block, + std::vector& full) +{ + for (int ixy = 0; ixy < pw.nxy; ++ixy) + { + for (int iz = 0; iz < nplane; ++iz) + { + full[ixy * pw.nz + startz + iz] = block[ixy * nplane + iz]; + } + } +} -std::unique_ptr make_nonlocal_xc(const ModulePW::PW_Basis* pw, const std::string& method) +/// Copy the z planes [startz, startz + nplane) of the complete grid into a block stored as [ixy][iz]. +void get_planes(const ModulePW::PW_Basis& pw, + const int nplane, + const int startz, + const std::vector& full, + double* block) { - if (method == "rvv10") + for (int ixy = 0; ixy < pw.nxy; ++ixy) { -#ifdef __LIBVDWXC - if (pw == nullptr) + for (int iz = 0; iz < nplane; ++iz) { - ModuleBase::WARNING_QUIT("NonlocalXC", "libvdwxc rVV10 requires a density PW basis"); + block[ixy * nplane + iz] = full[ixy * pw.nz + startz + iz]; } - return std::unique_ptr(new LibvdwxcNonlocalXC(*pw, RVV10_B_RPW86_PBE, RVV10_C_RPW86_PBE)); -#else - ModuleBase::WARNING_QUIT("NonlocalXC", "xc_nonlocal=rvv10 requires a CMake build with ENABLE_LIBVDWXC=ON"); + } +} + +/// The complete grid of pw in libvdwxc's [x][y][z] order on the pool root; empty on the other processes. +std::vector gather_grid(const ModulePW::PW_Basis& pw, const double* local) +{ + std::vector full; +#ifdef __MPI + MPI_Comm comm = pw.pool_world; + if (pw.poolrank != 0) + { + Parallel_Common::send_data(local, pw.nrxx, 0, 0, comm); + return full; + } #endif + full.resize(pw.nxyz); + put_planes(pw, pw.nplane, pw.startz_current, local, full); +#ifdef __MPI + std::vector block; + for (int ip = 1; ip < pw.poolnproc; ++ip) + { + const int count = pw.numz[ip] * pw.nxy; + block.resize(count); + Parallel_Common::recv_data(block.data(), count, ip, 0, comm, MPI_STATUS_IGNORE); + put_planes(pw, pw.numz[ip], pw.startz[ip], block.data(), full); } +#endif + return full; +} - const std::string message = "Unsupported nonlocal XC method: " + method; - ModuleBase::WARNING_QUIT("NonlocalXC", message); - return nullptr; +/// The local z planes of pw from the complete grid held by the pool root. +void scatter_grid(const ModulePW::PW_Basis& pw, const std::vector& full, double* local) +{ +#ifdef __MPI + MPI_Comm comm = pw.pool_world; + if (pw.poolrank != 0) + { + Parallel_Common::recv_data(local, pw.nrxx, 0, 0, comm, MPI_STATUS_IGNORE); + return; + } + std::vector block; + for (int ip = 1; ip < pw.poolnproc; ++ip) + { + const int count = pw.numz[ip] * pw.nxy; + block.resize(count); + get_planes(pw, pw.numz[ip], pw.startz[ip], full, block.data()); + Parallel_Common::send_data(block.data(), count, ip, 0, comm); + } +#endif + get_planes(pw, pw.nplane, pw.startz_current, full, local); +} +} // namespace + +void add_rvv10(const double b, + const double c, + const Charge& chr, + std::tuple& etxc_vtxc_v) +{ + ModuleBase::TITLE("vdw", "add_rvv10"); + ModuleBase::timer::start("vdw", "add_rvv10"); + const ModulePW::PW_Basis& pw = *chr.rhopw; + const int nrxx = chr.nrxx; + // rVV10 depends on the total density only; for nspin=4, rho[0] is the total valence density. + const int nchannel = (chr.nspin == 2) ? 2 : 1; + const double* core_begin = chr.rho_core; + const double* core_end = core_begin + nrxx; + std::vector rho(core_begin, core_end); + for (int is = 0; is < nchannel; ++is) + { + for (int ir = 0; ir < nrxx; ++ir) + { + rho[ir] += chr.rho[is][ir]; + } + } + + // sigma = |grad n|^2 from the plane-wave gradient + std::vector> rhog(pw.npw); + pw.real2recip(rho.data(), rhog.data()); + std::vector> grad(nrxx); + XC_Functional::grad_rho(rhog.data(), grad.data(), &pw, pw.tpiba); + std::vector sigma(nrxx); + for (int ir = 0; ir < nrxx; ++ir) + { + sigma[ir] = grad[ir].norm2(); + } + + // libvdwxc runs serially on the pool root; energy and derivatives are in Hartree units + std::vector rho_full = gather_grid(pw, rho.data()); + std::vector sigma_full = gather_grid(pw, sigma.data()); + std::vector dedn_full(rho_full.size()); + std::vector dedsigma_full(rho_full.size()); + double energy = 0.0; + if (pw.poolrank == 0) + { + // ABACUS stores the lattice vectors as the rows of latvec; libvdwxc takes them as three + // consecutive groups of Cartesian components in Bohr and integrates with the signed cell + // volume, so a left-handed cell is inverted, which leaves rVV10 unchanged. + ModuleBase::Matrix3 a = pw.latvec * pw.lat0; + if (a.Det() < 0.0) + { + a = a * -1.0; + } + vdwxc_data data = vdwxc_new(VDWXC_RVV10); + vdwxc_set_rvv10_parameters(data, b, c); + vdwxc_set_unit_cell(data, pw.nx, pw.ny, pw.nz, a.e11, a.e12, a.e13, a.e21, a.e22, a.e23, a.e31, a.e32, a.e33); + vdwxc_init_serial(data); + energy = vdwxc_calculate(data, rho_full.data(), sigma_full.data(), dedn_full.data(), dedsigma_full.data()); + vdwxc_finalize(&data); + } +#ifdef __MPI + Parallel_Common::bcast_data(&energy, 1, pw.pool_world); +#endif + std::vector dedn(nrxx); + std::vector dedsigma(nrxx); + scatter_grid(pw, dedn_full, dedn.data()); + scatter_grid(pw, dedsigma_full, dedsigma.data()); + + // v = dE/dn - div(2 dE/dsigma grad n), converted from Hartree to Ry by e2 + for (int ir = 0; ir < nrxx; ++ir) + { + grad[ir] *= 2.0 * dedsigma[ir]; + } + std::vector divergence(nrxx); + XC_Functional::grad_dot(grad.data(), divergence.data(), &pw, pw.tpiba); + ModuleBase::matrix& v = std::get<2>(etxc_vtxc_v); + double vtxc = 0.0; + for (int ir = 0; ir < nrxx; ++ir) + { + const double v_nl = ModuleBase::e2 * (dedn[ir] - divergence[ir]); + for (int is = 0; is < nchannel; ++is) + { + v(is, ir) += v_nl; + vtxc += v_nl * chr.rho[is][ir]; + } + } +#ifdef __MPI + Parallel_Common::reduce_data(&vtxc, 1, pw.pool_world); +#endif + std::get<0>(etxc_vtxc_v) += ModuleBase::e2 * energy; + std::get<1>(etxc_vtxc_v) += vtxc * pw.omega / pw.nxyz; + ModuleBase::timer::end("vdw", "add_rvv10"); } } // namespace vdw diff --git a/source/source_hamilt/module_vdw/nonlocal_xc.h b/source/source_hamilt/module_vdw/nonlocal_xc.h index 60da74138a1..ba9e9666345 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc.h +++ b/source/source_hamilt/module_vdw/nonlocal_xc.h @@ -1,58 +1,30 @@ -#ifndef ABACUS_NONLOCAL_XC_H -#define ABACUS_NONLOCAL_XC_H +#ifndef NONLOCAL_XC_H +#define NONLOCAL_XC_H -#include -#include -#include +#include -namespace ModulePW +class Charge; +namespace ModuleBase { -class PW_Basis; +class matrix; } namespace vdw { -/// rVV10 parameters published for the GGA_X_RPW86+GGA_C_PBE pairing -/// (R. Sabatini, T. Gorni and S. de Gironcoli, Phys. Rev. B 87, 041108(R) (2013)). -/// The current interface accepts only this pairing, so the values are fixed -/// here instead of being INPUT parameters. -const double RVV10_B_RPW86_PBE = 6.3; -const double RVV10_C_RPW86_PBE = 0.0093; - -struct NonlocalXCResult -{ - double energy; - double vtxc; - std::vector potential; -}; - /** - * @brief Density-grid interface for a nonlocal exchange-correlation backend. + * @brief Add the rVV10 nonlocal correlation, evaluated by libvdwxc, to the results of XC_Functional::v_xc. * - * The host potential code only knows this interface. Concrete methods such as - * rVV10, and future libvdwxc-backed methods, remain in the vdW module. + * The energy and the potential are those of the total density of chr, valence plus core. The + * potential is added to every spin channel that carries density, and vtxc counts the valence + * density only, as in v_xc. b and c are the rVV10 parameters; all results are in Ry. The complete + * grid is collected on the root of the k-point pool, where libvdwxc runs serially. */ -class NonlocalXC -{ - public: - virtual ~NonlocalXC() = default; - - /** - * @param pw density plane-wave basis the densities are defined on - * @param total_density valence plus core density on the real-space grid - * @param valence_density valence density used for the vtxc contraction - * @return nonlocal energy, vtxc and potential, all in Ry - */ - virtual NonlocalXCResult evaluate(const ModulePW::PW_Basis& pw, - const std::vector& total_density, - const std::vector& valence_density) const = 0; -}; - -/// Create the backend for `method` ("rvv10"). Quits for unknown methods and -/// for builds without the required library. -std::unique_ptr make_nonlocal_xc(const ModulePW::PW_Basis* pw, const std::string& method); +void add_rvv10(const double b, + const double c, + const Charge& chr, + std::tuple& etxc_vtxc_v); } // namespace vdw -#endif // ABACUS_NONLOCAL_XC_H +#endif diff --git a/source/source_hamilt/module_vdw/test/CMakeLists.txt b/source/source_hamilt/module_vdw/test/CMakeLists.txt index fddd34b6816..33f208cfde0 100644 --- a/source/source_hamilt/module_vdw/test/CMakeLists.txt +++ b/source/source_hamilt/module_vdw/test/CMakeLists.txt @@ -22,3 +22,25 @@ if(ENABLE_DFTD4 AND ENABLE_MPI) WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) endif() + +if(ENABLE_LIBVDWXC) + addtest( + TARGET MODULE_HAMILT_nonlocal_xcTest + LIBS xc_ + planewave + parameter + MPI::MPI_CXX + Libxc::xc + psi + device + container + base + SOURCES test_nonlocal_xc.cpp) + add_test( + NAME MODULE_HAMILT_nonlocal_xcTest_2np + COMMAND ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} 2 ${MPIEXEC_PREFLAGS} + $ ${MPIEXEC_POSTFLAGS} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + set_tests_properties(MODULE_HAMILT_nonlocal_xcTest_2np + PROPERTIES ENVIRONMENT "OMP_NUM_THREADS=1" PROCESSORS 2) +endif() diff --git a/source/source_hamilt/module_vdw/test/test_nonlocal_vdwxc.cpp b/source/source_hamilt/module_vdw/test/test_nonlocal_vdwxc.cpp deleted file mode 100644 index 2d9e2b0449f..00000000000 --- a/source/source_hamilt/module_vdw/test/test_nonlocal_vdwxc.cpp +++ /dev/null @@ -1,283 +0,0 @@ -#include "../nonlocal_vdwxc.h" -#include "source_base/constants.h" -#include "source_basis/module_pw/pw_basis.h" -#include "source_hamilt/module_xc/xc_functional.h" - -#include -#include -#include -#include -#include -#include - -#ifdef __LIBVDWXC -// Direct library calls provide the API-parity reference. Include the C header -// after the C++ headers and drop the I macro that complex.h defines. -#include -#include -#ifdef I -#undef I -#endif -#endif - -namespace -{ -// Adapter and direct API run the same FFT path, so results agree to rounding. -const double ROUNDING_TOLERANCE = 2.0e-12; -// Central difference on a smooth density: truncation error is of order step^2. -const double FINITE_DIFFERENCE_STEP = 1.0e-5; -const double FINITE_DIFFERENCE_TOLERANCE = 2.0e-6; - -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 = ModuleBase::TWO_PI * x / pw.nx; - const double ay = ModuleBase::TWO_PI * y / pw.ny; - const double az = ModuleBase::TWO_PI * z / pw.nz; - values[index] = offset + amplitude * (1.0 + 0.25 * std::sin(ax) + 0.15 * std::cos(ay + az)); - } - } - } - 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 = ModuleBase::TWO_PI * x / pw.nx; - const double ay = ModuleBase::TWO_PI * y / pw.ny; - values[index] = std::sin(ax + ay) + 0.3 * std::cos(2.0 * ay); - } - } - } - return values; - } -}; - -#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 = vdw::RVV10_B_RPW86_PBE; - const double c = vdw::RVV10_C_RPW86_PBE; - 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 ModuleBase::Matrix3& a = pw.latvec; - vdwxc_set_unit_cell(direct, - pw.nx, - pw.ny, - pw.nz, - // 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); - 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; - const double expected_energy = ModuleBase::e2 * direct_energy; - EXPECT_NEAR(adapted.energy, expected_energy, ROUNDING_TOLERANCE); - for (int r = 0; r < pw.nrxx; ++r) - { - const double expected_potential = ModuleBase::e2 * (dedn[r] - divergence[r]); - expected_vtxc += dv * valence[r] * expected_potential; - EXPECT_NEAR(adapted.potential[r], expected_potential, ROUNDING_TOLERANCE); - } - EXPECT_NEAR(adapted.vtxc, expected_vtxc, ROUNDING_TOLERANCE); - 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_EXIT(vdw::LibvdwxcNonlocalXC(reflected, vdw::RVV10_B_RPW86_PBE, vdw::RVV10_C_RPW86_PBE), - testing::ExitedWithCode(1), - ""); -} - -TEST_F(LibvdwxcAdapterTest, RejectsInvalidParametersWithStandardDiagnostic) -{ - EXPECT_EXIT(vdw::LibvdwxcNonlocalXC(pw, 0.0, vdw::RVV10_C_RPW86_PBE), testing::ExitedWithCode(1), ""); -} - -TEST_F(LibvdwxcAdapterTest, RejectsInvalidDensityWithStandardDiagnostic) -{ - vdw::LibvdwxcNonlocalXC adapter(pw, vdw::RVV10_B_RPW86_PBE, vdw::RVV10_C_RPW86_PBE); - 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 = ModuleBase::TWO_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 = ModuleBase::TWO_PI * x / pw.nx; - const double derivative = -amplitude * (ModuleBase::TWO_PI / pw.lat0) * std::sin(phase); - EXPECT_NEAR(gradient[index].x, derivative, ROUNDING_TOLERANCE); - EXPECT_NEAR(gradient[index].y, derivative / std::sqrt(3.0), ROUNDING_TOLERANCE); - EXPECT_NEAR(gradient[index].z, 0.0, ROUNDING_TOLERANCE); - } - } - } - - vdw::LibvdwxcNonlocalXC oblique_adapter(pw, vdw::RVV10_B_RPW86_PBE, vdw::RVV10_C_RPW86_PBE); - vdw::LibvdwxcNonlocalXC orthogonal_adapter(orthogonal, vdw::RVV10_B_RPW86_PBE, vdw::RVV10_C_RPW86_PBE); - 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, ROUNDING_TOLERANCE); - ASSERT_NEAR(oblique_result.energy, orthogonal_result.energy, ROUNDING_TOLERANCE); - EXPECT_NEAR(oblique_result.vtxc, orthogonal_result.vtxc, ROUNDING_TOLERANCE); - for (int r = 0; r < pw.nrxx; ++r) - { - EXPECT_NEAR(oblique_result.potential[r], orthogonal_result.potential[r], ROUNDING_TOLERANCE); - } -} - -TEST_F(LibvdwxcAdapterTest, PotentialMatchesFiniteDifferenceOfEnergy) -{ - const double b = vdw::RVV10_B_RPW86_PBE; - const double c = vdw::RVV10_C_RPW86_PBE; - const double step = FINITE_DIFFERENCE_STEP; - 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, FINITE_DIFFERENCE_TOLERANCE); -} -#endif - -} // namespace diff --git a/source/source_hamilt/module_vdw/test/test_nonlocal_xc.cpp b/source/source_hamilt/module_vdw/test/test_nonlocal_xc.cpp new file mode 100644 index 00000000000..33466e3a6a3 --- /dev/null +++ b/source/source_hamilt/module_vdw/test/test_nonlocal_xc.cpp @@ -0,0 +1,440 @@ +#include "../nonlocal_xc.h" +#include "source_base/constants.h" +#include "source_base/parallel_comm.h" +#include "source_base/parallel_device.h" +#include "source_base/parallel_global.h" +#include "source_basis/module_pw/pw_basis.h" +#include "source_cell/unitcell.h" +#include "source_estate/module_charge/charge.h" +#include "source_hamilt/module_xc/xc_functional.h" + +#include +#include +#include +#include + +// The reference calls libvdwxc directly. Include its C header after the C++ headers. +#include +#include +#include +#undef I + +// The target links PW/XC objects only; the tests own the charge storage. +Charge::Charge() +{ +} +Charge::~Charge() +{ +} +UnitCell::UnitCell() +{ +} +UnitCell::~UnitCell() +{ +} +Magnetism::Magnetism() +{ +} +Magnetism::~Magnetism() +{ +} +SepPot::SepPot() +{ +} +SepPot::~SepPot() +{ +} +Sep_Cell::Sep_Cell() noexcept +{ +} +Sep_Cell::~Sep_Cell() noexcept +{ +} + +namespace +{ +int test_rank = 0; +int test_size = 1; +typedef std::tuple VxcResult; +// rVV10 parameters that set_xc_type assigns to RVV10 +const double RVV10_B = 6.3; +const double RVV10_C = 0.0093; +// The adapter and the reference share the plane-wave gradient, so they agree to rounding. +const double ROUNDING_TOLERANCE = 2.0e-12; +// Central difference on a smooth density: the truncation error is of order step^2. +const double FINITE_DIFFERENCE_STEP = 1.0e-5; +const double FINITE_DIFFERENCE_TOLERANCE = 2.0e-6; + +typedef double (*GridFunction)(const ModulePW::PW_Basis&, const int, const int, const int); + +double valence_density(const ModulePW::PW_Basis& pw, const int ix, const int iy, const int iz) +{ + const double ax = ModuleBase::TWO_PI * ix / pw.nx; + const double ay = ModuleBase::TWO_PI * iy / pw.ny; + const double az = ModuleBase::TWO_PI * iz / pw.nz; + return 0.004 + 0.006 * (1.0 + 0.25 * std::sin(ax) + 0.15 * std::cos(ay + az)); +} + +double core_density(const ModulePW::PW_Basis& pw, const int ix, const int iy, const int iz) +{ + const double az = ModuleBase::TWO_PI * iz / pw.nz; + return 0.002 * (1.0 + std::cos(az)); +} + +double perturbation(const ModulePW::PW_Basis& pw, const int ix, const int iy, const int iz) +{ + const double ax = ModuleBase::TWO_PI * ix / pw.nx; + const double ay = ModuleBase::TWO_PI * iy / pw.ny; + return std::sin(ax + ay) + 0.3 * std::cos(2.0 * ay); +} + +double x_only_density(const ModulePW::PW_Basis& pw, const int ix, const int iy, const int iz) +{ + return 0.01 + 0.003 * std::cos(ModuleBase::TWO_PI * ix / pw.nx); +} + +double zero_density(const ModulePW::PW_Basis& pw, const int ix, const int iy, const int iz) +{ + return 0.0; +} + +/// Values of f on the z planes of this process, in the ABACUS layout [ixy * nplane + iz]. +std::vector local_grid(const ModulePW::PW_Basis& pw, GridFunction f) +{ + std::vector values(pw.nrxx); + for (int ix = 0; ix < pw.nx; ++ix) + { + for (int iy = 0; iy < pw.ny; ++iy) + { + for (int iz = 0; iz < pw.nplane; ++iz) + { + values[(ix * pw.ny + iy) * pw.nplane + iz] = f(pw, ix, iy, pw.startz_current + iz); + } + } + } + return values; +} + +/// A Charge on pw whose spin channels and core density point into the given vectors. +void attach(Charge& chr, + ModulePW::PW_Basis& pw, + std::vector>& channels, + std::vector& rows, + std::vector& core) +{ + rows.clear(); + for (std::vector& channel: channels) + { + rows.push_back(channel.data()); + } + chr.rhopw = &pw; + chr.nrxx = pw.nrxx; + chr.nspin = static_cast(channels.size()); + chr.rho = rows.data(); + chr.rho_core = core.data(); +} + +/// rVV10 contribution for the given valence channels and core density on pw. +VxcResult rvv10_result(ModulePW::PW_Basis& pw, std::vector> channels, std::vector core) +{ + Charge chr; + std::vector rows; + attach(chr, pw, channels, rows, core); + VxcResult result(0.0, 0.0, ModuleBase::matrix(chr.nspin, pw.nrxx)); + vdw::add_rvv10(RVV10_B, RVV10_C, chr, result); + chr.rho = nullptr; + chr.rho_core = nullptr; + return result; +} + +/// Oblique lattice; z_sign = -1 gives its left-handed mirror image. +ModuleBase::Matrix3 oblique_lattice(const double z_sign) +{ + const double c3 = 2.0 * z_sign; + return ModuleBase::Matrix3(1.0, 0.0, 0.0, -0.5, std::sqrt(0.75), 0.0, 0.0, 0.0, c3); +} + +class NonlocalXCTest : public testing::Test +{ + protected: + /// 6x8x9 grid on an oblique cell, distributed over the processes of the test (5 + 4 planes on two) + ModulePW::PW_Basis pw{"cpu", "double"}; + /// the same grid, complete on every process + ModulePW::PW_Basis serial{"cpu", "double"}; + + void SetUp() override + { + // The oblique cell catches row/column mistakes in the cell handed to libvdwxc. + const ModuleBase::Matrix3 lattice = oblique_lattice(1.0); + pw.initmpi(test_size, test_rank, MPI_COMM_WORLD); + serial.initmpi(1, 0, MPI_COMM_SELF); + setup(pw, lattice); + setup(serial, lattice); + } + + static void setup(ModulePW::PW_Basis& basis, const ModuleBase::Matrix3& lattice) + { + basis.initgrids(10.0, lattice, 6, 8, 9); + basis.initparameters(false, 4.0, 1, true); + basis.setuptransform(); + basis.collect_local_pw(); + } +}; + +TEST(NonlocalXCSelectionTest, ShortNameSelectsRvv10) +{ + XC_Functional::set_xc_type("rVV10"); + EXPECT_EQ(XC_Functional::get_func_type(), 2); + const std::vector expected_id = {XC_GGA_X_RPW86, XC_GGA_C_PBE}; + EXPECT_EQ(XC_Functional::get_func_id(), expected_id); + EXPECT_DOUBLE_EQ(XC_Functional::get_nlc_b(), RVV10_B); + { + XC_Functional::ScopedType semilocal("PBE"); + EXPECT_EQ(XC_Functional::get_nlc_b(), 0.0); + } + EXPECT_DOUBLE_EQ(XC_Functional::get_nlc_b(), RVV10_B); + XC_Functional::set_xc_type("PBE"); + EXPECT_EQ(XC_Functional::get_nlc_b(), 0.0); +} + +TEST_F(NonlocalXCTest, MatchesDirectLibvdwxc) +{ + const std::vector valence = local_grid(serial, valence_density); + const std::vector core = local_grid(serial, core_density); + const VxcResult result = rvv10_result(serial, {valence}, core); + + // reference: libvdwxc on the total density with the plane-wave gradient, in Hartree units + std::vector total(serial.nrxx); + for (int ir = 0; ir < serial.nrxx; ++ir) + { + total[ir] = valence[ir] + core[ir]; + } + std::vector> rhog(serial.npw); + serial.real2recip(total.data(), rhog.data()); + std::vector> grad(serial.nrxx); + XC_Functional::grad_rho(rhog.data(), grad.data(), &serial, serial.tpiba); + std::vector sigma(serial.nrxx); + std::vector dedn(serial.nrxx); + std::vector dedsigma(serial.nrxx); + for (int ir = 0; ir < serial.nrxx; ++ir) + { + sigma[ir] = grad[ir].norm2(); + } + vdwxc_data direct = vdwxc_new(VDWXC_RVV10); + ASSERT_NE(direct, nullptr); + ASSERT_EQ(vdwxc_set_rvv10_parameters(direct, RVV10_B, RVV10_C), 0); + const ModuleBase::Matrix3 a = serial.latvec * serial.lat0; + vdwxc_set_unit_cell(direct, + serial.nx, + serial.ny, + serial.nz, + a.e11, + a.e12, + a.e13, + a.e21, + a.e22, + a.e23, + a.e31, + a.e32, + a.e33); + vdwxc_init_serial(direct); + const double energy_ha = vdwxc_calculate(direct, total.data(), sigma.data(), dedn.data(), dedsigma.data()); + vdwxc_finalize(&direct); + for (int ir = 0; ir < serial.nrxx; ++ir) + { + grad[ir] *= 2.0 * dedsigma[ir]; + } + std::vector divergence(serial.nrxx); + XC_Functional::grad_dot(grad.data(), divergence.data(), &serial, serial.tpiba); + + const double expected_energy = ModuleBase::e2 * energy_ha; + EXPECT_NEAR(std::get<0>(result), expected_energy, ROUNDING_TOLERANCE); + double vtxc = 0.0; + for (int ir = 0; ir < serial.nrxx; ++ir) + { + const double potential = ModuleBase::e2 * (dedn[ir] - divergence[ir]); + EXPECT_NEAR(std::get<2>(result)(0, ir), potential, ROUNDING_TOLERANCE); + vtxc += potential * valence[ir]; + } + const double expected_vtxc = vtxc * serial.omega / serial.nxyz; + EXPECT_NEAR(std::get<1>(result), expected_vtxc, ROUNDING_TOLERANCE); +} + +TEST_F(NonlocalXCTest, SpinChannelsShareTheTotalDensity) +{ + const std::vector valence = local_grid(serial, valence_density); + const std::vector core = local_grid(serial, core_density); + std::vector up(serial.nrxx); + std::vector down(serial.nrxx); + for (int ir = 0; ir < serial.nrxx; ++ir) + { + up[ir] = 0.7 * valence[ir]; + down[ir] = valence[ir] - up[ir]; + } + const VxcResult unpolarized = rvv10_result(serial, {valence}, core); + const VxcResult polarized = rvv10_result(serial, {up, down}, core); + EXPECT_NEAR(std::get<0>(polarized), std::get<0>(unpolarized), ROUNDING_TOLERANCE); + EXPECT_NEAR(std::get<1>(polarized), std::get<1>(unpolarized), ROUNDING_TOLERANCE); + for (int ir = 0; ir < serial.nrxx; ++ir) + { + EXPECT_NEAR(std::get<2>(polarized)(0, ir), std::get<2>(unpolarized)(0, ir), ROUNDING_TOLERANCE); + EXPECT_NEAR(std::get<2>(polarized)(1, ir), std::get<2>(unpolarized)(0, ir), ROUNDING_TOLERANCE); + } +} + +TEST_F(NonlocalXCTest, DistributedGridMatchesCompleteGrid) +{ + const std::vector valence = local_grid(pw, valence_density); + const std::vector core = local_grid(pw, core_density); + const std::vector valence_complete = local_grid(serial, valence_density); + const std::vector core_complete = local_grid(serial, core_density); + const VxcResult distributed = rvv10_result(pw, {valence}, core); + const VxcResult complete = rvv10_result(serial, {valence_complete}, core_complete); + EXPECT_NEAR(std::get<0>(distributed), std::get<0>(complete), ROUNDING_TOLERANCE); + EXPECT_NEAR(std::get<1>(distributed), std::get<1>(complete), ROUNDING_TOLERANCE); + for (int ixy = 0; ixy < pw.nxy; ++ixy) + { + for (int iz = 0; iz < pw.nplane; ++iz) + { + const int local = ixy * pw.nplane + iz; + const int global = ixy * pw.nz + pw.startz_current + iz; + EXPECT_NEAR(std::get<2>(distributed)(0, local), std::get<2>(complete)(0, global), ROUNDING_TOLERANCE); + } + } +} + +TEST_F(NonlocalXCTest, PotentialIsTheDerivativeOfTheEnergy) +{ + const std::vector valence = local_grid(pw, valence_density); + const std::vector core = local_grid(pw, core_density); + const std::vector direction = local_grid(pw, perturbation); + std::vector plus = valence; + std::vector minus = valence; + for (int ir = 0; ir < pw.nrxx; ++ir) + { + plus[ir] += FINITE_DIFFERENCE_STEP * direction[ir]; + minus[ir] -= FINITE_DIFFERENCE_STEP * direction[ir]; + } + const VxcResult reference = rvv10_result(pw, {valence}, core); + const VxcResult result_plus = rvv10_result(pw, {plus}, core); + const VxcResult result_minus = rvv10_result(pw, {minus}, core); + const double energy_difference = std::get<0>(result_plus) - std::get<0>(result_minus); + const double finite_difference = energy_difference / (2.0 * FINITE_DIFFERENCE_STEP); + + double contraction = 0.0; + for (int ir = 0; ir < pw.nrxx; ++ir) + { + contraction += std::get<2>(reference)(0, ir) * direction[ir]; + } + Parallel_Common::reduce_data(&contraction, 1, pw.pool_world); + contraction *= pw.omega / pw.nxyz; + EXPECT_NEAR(finite_difference, contraction, FINITE_DIFFERENCE_TOLERANCE); +} + +TEST_F(NonlocalXCTest, ObliqueCellMatchesEquivalentOrthogonalCell) +{ + // Both cells have the volume sqrt(3) lat0^3 and the plane spacing sqrt(3)/2 lat0 along the + // first lattice direction, so a density that depends only on the first fractional coordinate + // has the same nonlocal energy and potential in both. A transposed cell matrix breaks this. + ModulePW::PW_Basis orthogonal{"cpu", "double"}; + orthogonal.initmpi(1, 0, MPI_COMM_SELF); + const ModuleBase::Matrix3 rectangle(std::sqrt(0.75), 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 2.0); + setup(orthogonal, rectangle); + ASSERT_NEAR(serial.omega, orthogonal.omega, ROUNDING_TOLERANCE); + + const std::vector density = local_grid(serial, x_only_density); + const std::vector no_core = local_grid(serial, zero_density); + const VxcResult oblique = rvv10_result(serial, {density}, no_core); + const VxcResult rectangular = rvv10_result(orthogonal, {density}, no_core); + EXPECT_NEAR(std::get<0>(oblique), std::get<0>(rectangular), ROUNDING_TOLERANCE); + for (int ir = 0; ir < serial.nrxx; ++ir) + { + EXPECT_NEAR(std::get<2>(oblique)(0, ir), std::get<2>(rectangular)(0, ir), ROUNDING_TOLERANCE); + } +} + +TEST_F(NonlocalXCTest, LeftHandedCellGivesTheMirrorImageResult) +{ + // A mirror image has the same nonlocal energy and potential; libvdwxc's signed cell volume would + // otherwise flip the sign of the energy. + ModulePW::PW_Basis mirror{"cpu", "double"}; + mirror.initmpi(1, 0, MPI_COMM_SELF); + setup(mirror, oblique_lattice(-1.0)); + ASSERT_LT(mirror.latvec.Det(), 0.0); + + const std::vector valence = local_grid(serial, valence_density); + const std::vector core = local_grid(serial, core_density); + const VxcResult right_handed = rvv10_result(serial, {valence}, core); + const VxcResult left_handed = rvv10_result(mirror, {valence}, core); + EXPECT_NEAR(std::get<0>(left_handed), std::get<0>(right_handed), ROUNDING_TOLERANCE); + EXPECT_NEAR(std::get<1>(left_handed), std::get<1>(right_handed), ROUNDING_TOLERANCE); + for (int ir = 0; ir < serial.nrxx; ++ir) + { + EXPECT_NEAR(std::get<2>(left_handed)(0, ir), std::get<2>(right_handed)(0, ir), ROUNDING_TOLERANCE); + } +} + +TEST_F(NonlocalXCTest, VxcOfRvv10AddsTheNonlocalCorrelation) +{ + // XC_Functional::v_xc for dft_functional RVV10 equals the semilocal rPW86+PBE result plus add_rvv10. + const std::vector valence = local_grid(pw, valence_density); + std::vector core = local_grid(pw, core_density); + std::vector> channels(2, std::vector(pw.nrxx)); + for (int ir = 0; ir < pw.nrxx; ++ir) + { + channels[0][ir] = 0.7 * valence[ir]; + channels[1][ir] = valence[ir] - channels[0][ir]; + } + Charge chr; + std::vector rows; + attach(chr, pw, channels, rows, core); + UnitCell ucell; + ucell.omega = pw.omega; + ucell.tpiba = pw.tpiba; + const int nspin = 2; + const bool domag = false; + const int gga_grad = 0; + const double hybrid_alpha = 0.0; + const double hse_omega = 0.0; + + XC_Functional::set_xc_type("RVV10"); + const VxcResult total + = XC_Functional::v_xc(pw.nrxx, &chr, &ucell, nspin, domag, domag, gga_grad, hybrid_alpha, hse_omega); + VxcResult expected; + { + XC_Functional::ScopedType semilocal("GGA_X_RPW86+GGA_C_PBE"); + expected = XC_Functional::v_xc(pw.nrxx, &chr, &ucell, nspin, domag, domag, gga_grad, hybrid_alpha, hse_omega); + } + vdw::add_rvv10(RVV10_B, RVV10_C, chr, expected); + XC_Functional::set_xc_type("PBE"); + chr.rho = nullptr; + chr.rho_core = nullptr; + + EXPECT_NEAR(std::get<0>(total), std::get<0>(expected), ROUNDING_TOLERANCE); + EXPECT_NEAR(std::get<1>(total), std::get<1>(expected), ROUNDING_TOLERANCE); + for (int is = 0; is < nspin; ++is) + { + for (int ir = 0; ir < pw.nrxx; ++ir) + { + EXPECT_NEAR(std::get<2>(total)(is, ir), std::get<2>(expected)(is, ir), ROUNDING_TOLERANCE); + } + } +} + +} // namespace + +int main(int argc, char** argv) +{ + int threads = 1; + Parallel_Global::read_pal_param(argc, argv, test_size, threads, test_rank); + POOL_WORLD = MPI_COMM_WORLD; + KP_WORLD = MPI_COMM_NULL; + INT_BGROUP = MPI_COMM_NULL; + BP_WORLD = MPI_COMM_NULL; + GRID_WORLD = MPI_COMM_NULL; + DIAG_WORLD = MPI_COMM_NULL; + testing::InitGoogleTest(&argc, argv); + const int result = RUN_ALL_TESTS(); + Parallel_Global::finalize_mpi(); + return result; +} diff --git a/source/source_hamilt/module_xc/CMakeLists.txt b/source/source_hamilt/module_xc/CMakeLists.txt index 495c5ae1997..fc1132fc494 100644 --- a/source/source_hamilt/module_xc/CMakeLists.txt +++ b/source/source_hamilt/module_xc/CMakeLists.txt @@ -31,6 +31,12 @@ if(ENABLE_COVERAGE) add_coverage(xc_) endif() +if(ENABLE_LIBVDWXC) + # XC_Functional::v_xc adds the rVV10 nonlocal correlation through this adapter. + target_sources(xc_ PRIVATE ../module_vdw/nonlocal_xc.cpp) + target_link_libraries(xc_ PUBLIC libvdwxc::vdwxc) +endif() + if(BUILD_TESTING) if(ENABLE_MPI) if(ENABLE_LIBXC) diff --git a/source/source_hamilt/module_xc/test/CMakeLists.txt b/source/source_hamilt/module_xc/test/CMakeLists.txt index 82e8188923c..606dbb883ea 100644 --- a/source/source_hamilt/module_xc/test/CMakeLists.txt +++ b/source/source_hamilt/module_xc/test/CMakeLists.txt @@ -164,3 +164,6 @@ foreach(xc_gradient_test MODULE_HAMILT_XCTest_GRADCORR MODULE_HAMILT_XCTest_LAPL ../xc_functional_ncgga_sf.cpp ../xc_ncgga_radial.cpp ../libxc_pot.cpp ../libxc_tools.cpp ../../../source_base/timer.cpp) endforeach() + +# MODULE_HAMILT_XCTest_VXC compiles xc_pot.cpp against a mocked PW basis, without the libvdwxc adapter. +abacus_disable_feature_definitions(__LIBVDWXC) diff --git a/source/source_hamilt/module_xc/xc_functional.cpp b/source/source_hamilt/module_xc/xc_functional.cpp index afb4aff039c..e857d4a6474 100644 --- a/source/source_hamilt/module_xc/xc_functional.cpp +++ b/source/source_hamilt/module_xc/xc_functional.cpp @@ -17,6 +17,8 @@ int XC_Functional::func_type = 0; bool XC_Functional::ked_flag = false; bool XC_Functional::need_laplacian = false; bool XC_Functional::use_libxc = true; +double XC_Functional::nlc_b = 0.0; +double XC_Functional::nlc_c = 0.0; double XC_Functional::hybrid_alpha = 0.25; double XC_Functional::hse_omega = 0.0; XCFunctionalParameters XC_Functional::runtime_parameters; @@ -27,7 +29,8 @@ XC_Functional::ScopedType::ScopedType(const std::string& functional) ked_flag_(XC_Functional::ked_flag), need_laplacian_(XC_Functional::need_laplacian), use_libxc_(XC_Functional::use_libxc), hybrid_alpha_(XC_Functional::hybrid_alpha), hse_omega_(XC_Functional::hse_omega), runtime_parameters_(XC_Functional::runtime_parameters), - scaling_factor_xc_(XC_Functional::scaling_factor_xc) + scaling_factor_xc_(XC_Functional::scaling_factor_xc), + nlc_b_(XC_Functional::nlc_b), nlc_c_(XC_Functional::nlc_c) { try { @@ -60,6 +63,8 @@ void XC_Functional::ScopedType::restore() noexcept XC_Functional::runtime_parameters.xc_exch_ext.swap(runtime_parameters_.xc_exch_ext); XC_Functional::runtime_parameters.xc_corr_ext.swap(runtime_parameters_.xc_corr_ext); XC_Functional::scaling_factor_xc.swap(scaling_factor_xc_); + XC_Functional::nlc_b = nlc_b_; + XC_Functional::nlc_c = nlc_c_; } void XC_Functional::set_hybrid_alpha(const double alpha_in) @@ -123,6 +128,8 @@ void XC_Functional::set_xc_type(const std::string xc_func_in) func_id.clear(); scaling_factor_xc.clear(); // added by jghan, 2024-07-07 + nlc_b = 0.0; + nlc_c = 0.0; std::string xc_func = xc_func_in; std::transform(xc_func.begin(), xc_func.end(), xc_func.begin(), (::toupper)); if( xc_func == "LDA" || xc_func == "PZ" || xc_func == "SLAPZNOGXNOGC") //SLA+PZ @@ -335,6 +342,20 @@ void XC_Functional::set_xc_type(const std::string xc_func_in) func_type = 4; use_libxc = true; } + else if (xc_func == "RVV10") + { + // rPW86 exchange, PBE correlation and the rVV10 nonlocal correlation evaluated by libvdwxc, + // R. Sabatini, T. Gorni and S. de Gironcoli, Phys. Rev. B 87, 041108(R) (2013) +#ifndef __LIBVDWXC + ModuleBase::WARNING_QUIT("set_xc_type", "to use RVV10, compile with ENABLE_LIBVDWXC=ON"); +#endif + func_id.push_back(XC_GGA_X_RPW86); + func_id.push_back(XC_GGA_C_PBE); + func_type = 2; + use_libxc = true; + nlc_b = 6.3; + nlc_c = 0.0093; + } #endif else { @@ -407,9 +428,9 @@ void XC_Functional::set_xc_type(const std::string xc_func_in) if(xc_func == "SCAN" || xc_func == "HSE" || xc_func == "SCAN0" || xc_func == "MULLER" || xc_func == "POWER" || xc_func == "WP22" || xc_func == "CWP22" || xc_func == "LC_PBE" || xc_func == "LC_WPBE" || xc_func == "LRC_WPBE" || - xc_func == "LRC_PBEH" || xc_func == "CAM_PBEH") + xc_func == "LRC_PBEH" || xc_func == "CAM_PBEH" || xc_func == "RVV10") { - ModuleBase::WARNING_QUIT("set_xc_type","to use SCAN, SCAN0, HSE, long-range corrected (LC_PBE, LC_WPBE...) or CAM_PBEH LIBXC is required"); + ModuleBase::WARNING_QUIT("set_xc_type","to use SCAN, SCAN0, HSE, long-range corrected (LC_PBE, LC_WPBE...), CAM_PBEH or RVV10 LIBXC is required"); } use_libxc = false; #endif @@ -443,6 +464,10 @@ std::string XC_Functional::output_info() } } XC_Functional_Libxc::finish_func(funcs); + if (nlc_b > 0.0) + { + ss << " Nonlocal correlation: rVV10 (libvdwxc), b = " << nlc_b << ", C = " << nlc_c << std::endl; + } return ss.str(); } else diff --git a/source/source_hamilt/module_xc/xc_functional.h b/source/source_hamilt/module_xc/xc_functional.h index a8b57f00465..8ed66157327 100644 --- a/source/source_hamilt/module_xc/xc_functional.h +++ b/source/source_hamilt/module_xc/xc_functional.h @@ -126,6 +126,8 @@ class XC_Functional double hse_omega_; XCFunctionalParameters runtime_parameters_; std::map scaling_factor_xc_; + double nlc_b_; + double nlc_c_; }; // For hybrid functional @@ -166,6 +168,12 @@ class XC_Functional return need_laplacian; }; + /// rVV10 parameter b of the nonlocal correlation (libxc's nlc_b); 0 if the functional has none + static double get_nlc_b() + { + return nlc_b; + }; + /// Usually in exx caculation, the first SCF loop should be converged with PBE static void set_xc_first_loop(const UnitCell& ucell); @@ -178,6 +186,9 @@ class XC_Functional static bool ked_flag; // whether the functional has kinetic energy density static bool need_laplacian; // whether any functional needs Laplacian of density static bool use_libxc; + // rVV10 nonlocal correlation parameters b and C; nlc_b = 0 means no nonlocal correlation + static double nlc_b; + static double nlc_c; // exx_hybrid_alpha for mixing exx in hybrid functional: static double hybrid_alpha; diff --git a/source/source_hamilt/module_xc/xc_pot.cpp b/source/source_hamilt/module_xc/xc_pot.cpp index 278de827546..e397533f8f2 100644 --- a/source/source_hamilt/module_xc/xc_pot.cpp +++ b/source/source_hamilt/module_xc/xc_pot.cpp @@ -16,6 +16,9 @@ #include "source_hamilt/module_xc/exx_info.h" #endif #endif +#ifdef __LIBVDWXC +#include "source_hamilt/module_vdw/nonlocal_xc.h" +#endif // [etxc, vtxc, v] = XC_Functional::v_xc(...) @@ -36,18 +39,27 @@ std::tuple XC_Functional::v_xc( if (use_libxc) { #ifdef __LIBXC - return XC_Functional_Libxc::v_xc_libxc(XC_Functional::get_func_id(), - nrxx, - ucell->omega, - ucell->tpiba, - chr, - nspin, - domag, - domag_z, - gga_grad, - &(scaling_factor_xc), - hybrid_alpha, - hse_omega); + std::tuple etxc_vtxc_v + = XC_Functional_Libxc::v_xc_libxc(XC_Functional::get_func_id(), + nrxx, + ucell->omega, + ucell->tpiba, + chr, + nspin, + domag, + domag_z, + gga_grad, + &(scaling_factor_xc), + hybrid_alpha, + hse_omega); +#ifdef __LIBVDWXC + // the nonlocal correlation of the functional; RVV10 always takes this libxc branch + if (nlc_b > 0.0) + { + vdw::add_rvv10(nlc_b, nlc_c, *chr, etxc_vtxc_v); + } +#endif + return etxc_vtxc_v; #else ModuleBase::WARNING_QUIT("v_xc", "compile with LIBXC"); #endif diff --git a/source/source_io/build_info.h.in b/source/source_io/build_info.h.in index aa9ba4c1f79..a7eb5e89950 100644 --- a/source/source_io/build_info.h.in +++ b/source/source_io/build_info.h.in @@ -31,7 +31,6 @@ #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 --- @@ -53,6 +52,7 @@ // --- Other Libraries --- #define ABACUS_DFTD4_VERSION "@ABACUS_DFTD4_VERSION@" +#define ABACUS_LIBVDWXC_VERSION "@ABACUS_LIBVDWXC_VERSION@" #define ABACUS_JSON_VERSION "@ABACUS_JSON_VERSION@" #define ABACUS_PEXSI_VERSION "@ABACUS_PEXSI_VERSION@" #define ABACUS_CNPY_VERSION "@ABACUS_CNPY_VERSION@" diff --git a/source/source_io/module_parameter/input_parameter.h b/source/source_io/module_parameter/input_parameter.h index 449744a63ff..1c624aa5a02 100644 --- a/source/source_io/module_parameter/input_parameter.h +++ b/source/source_io/module_parameter/input_parameter.h @@ -88,7 +88,6 @@ 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 mapped-density LCA. - std::string xc_nonlocal = "none"; ///< nonlocal XC correction (none; rvv10) 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 9b33a398408..f89ce099ccc 100644 --- a/source/source_io/module_parameter/read_inp_estruc.cpp +++ b/source/source_io/module_parameter/read_inp_estruc.cpp @@ -3,79 +3,8 @@ #include "read_input.h" #include "read_input_tool.h" -#include -#include -#include -#include -#include -#include - namespace ModuleIO { -namespace -{ -// xc_nonlocal=rvv10 is a staged interface. Reject everything outside it while -// INPUT is read, before any expensive setup, and name the first unsupported -// setting so the user knows what to change. -void check_xc_nonlocal(const Input_para& p, const bool double_grid, const int nproc) -{ - const std::string nonlocal = p.xc_nonlocal; - 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 - std::string functional = p.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"); - } - // Report the first unsupported setting so the user knows what to change. - // gamma_only is rejected in its own reset, before the KPT file is rewritten. - const std::vector> unsupported = { - {nproc != 1, "one MPI rank"}, - {p.calculation != "scf", "calculation=scf"}, - {p.esolver_type != "ksdft", "esolver_type=ksdft"}, - {p.basis_type != "pw", "basis_type=pw"}, - {p.device != "cpu", "device=cpu"}, - {p.precision != "double", "precision=double"}, - {p.nspin != 1, "nspin=1"}, - {p.cal_force, "cal_force=0"}, - {p.cal_stress, "cal_stress=0"}, - {double_grid, "a single density grid"}, - {p.vdw_method != "none", "vdw_method=none"}, - {!p.vl_in_h, "vl_in_h=1"}, - }; - for (const std::pair& entry: unsupported) - { - if (entry.first) - { - const std::string message - = std::string("xc_nonlocal=rvv10 supports only ") + entry.second + " in the current libvdwxc interface"; - ModuleBase::WARNING_QUIT("ReadInput", message); - } - } -} -} // namespace - void ReadInput::item_elec_stru() { // NOTE: The order of add_item() calls below determines the parameter order @@ -356,45 +285,16 @@ Then the user has to correct the input file and restart the calculation.)"; * If LIBXC is available, additional short-hand names of hybrid functionals are supported: HSE(referred to HSE06), B3LYP, LC_PBE, LC_WPBE, LRC_WPBE, LRC_WPBEH, CAM_PBEH, WP22, CWP22, MULLER (equivalent with POWER) * Hybrid meta-GGA functionals * SCAN0 (require LIBXC) +* Nonlocal correlation functionals + * RVV10: rPW86 exchange, PBE correlation and rVV10 nonlocal correlation (require LIBXC and LIBVDWXC; stress, DFPT and LR-TDDFT forces are not supported) 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'.)"; 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` (the published rVV10 parameters b=6.3 and C=0.0093 of this pairing are built in), 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, non-SCF calculations such as `nscf`, LCAO, GPU, reduced-gamma layouts, ultrasoft 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) { - const bool double_grid = para.sys.double_grid; - const int nproc = para.globalv.nproc; - check_xc_nonlocal(para.input, double_grid, nproc); - }; + item.domain.add_requirement("dft_functional in [rvv10, RVV10, rVV10]", + "cal_stress==false and esolver_type!=dfpt and (cal_force==false or " + "(esolver_type!=lr and esolver_type!=ks-lr))"); this->add_item(item); } { @@ -857,15 +757,6 @@ 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 run before check_value, so reject rVV10 here, before the - // PW fallback overwrites KPT. xc_nonlocal is registered earlier in - // this function and its reset has already lowercased the value. - if (para.input.xc_nonlocal == "rvv10") - { - ModuleBase::WARNING_QUIT("ReadInput", - "xc_nonlocal=rvv10 supports only full k-point PW layouts; " - "gamma_only is not implemented"); - } 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 41cf5054c6a..2115f20d215 100644 --- a/source/source_io/parse_args.cpp +++ b/source/source_io/parse_args.cpp @@ -140,7 +140,6 @@ 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 --- @@ -166,6 +165,7 @@ void print_build_info() // --- 11. Other Libraries --- print_section("Other Libraries"); print_info("DFT-D4 Support", ABACUS_DFTD4_VERSION); + print_info("libvdwxc Support", ABACUS_LIBVDWXC_VERSION); print_info("JSON Support", ABACUS_JSON_VERSION); print_info("PEXSI Support", ABACUS_PEXSI_VERSION); print_info("cnpy Support", ABACUS_CNPY_VERSION); diff --git a/source/source_io/test_serial/read_input_test.cpp b/source/source_io/test_serial/read_input_test.cpp index ac5e90d69bd..9e49f442f4d 100644 --- a/source/source_io/test_serial/read_input_test.cpp +++ b/source/source_io/test_serial/read_input_test.cpp @@ -6,11 +6,8 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include -#include #include -#include #include -#include // mock namespace GlobalV @@ -81,18 +78,6 @@ class InputTest : public testing::Test GlobalV::NPROC = nproc; } - std::string temporary_file(const std::string& name) - { - std::string filename = testing::TempDir() + name + ".XXXXXX"; - const int fd = mkstemp(&filename[0]); - if (fd == -1) - { - throw std::runtime_error("Cannot create temporary INPUT test file"); - } - EXPECT_EQ(close(fd), 0); - return filename; - } - void write_input(const std::string& filename, const std::string& parameters) { std::ofstream input(filename.c_str()); @@ -227,120 +212,14 @@ TEST_F(InputTest, RejectAutoDevice) EXPECT_TRUE(std::remove("./auto_device_INPUT") == 0); } -TEST_F(InputTest, NonlocalXcDefaultsToNone) +TEST_F(InputTest, Rvv10RejectsStressDfptAndLrForces) { Parameter param; - const std::string filename = temporary_file("abacus_nonlocal_default.INPUT"); - EXPECT_NO_THROW(read_parameters(filename, "", param)); - EXPECT_EQ(param.inp.xc_nonlocal, "none"); -} - -TEST_F(InputTest, NonlocalXcDisabledPreservesSemilocalInput) -{ - Parameter param; - const std::string filename = temporary_file("abacus_nonlocal_disabled.INPUT"); - EXPECT_NO_THROW(read_parameters(filename, "xc_nonlocal NoNe\ndft_functional PBE\nnspin 2\ncal_force 1\n", param)); - EXPECT_EQ(param.inp.xc_nonlocal, "none"); - EXPECT_EQ(param.inp.dft_functional, "PBE"); - EXPECT_EQ(param.inp.nspin, 2); - EXPECT_TRUE(param.inp.cal_force); -} - -TEST_F(InputTest, NonlocalXcRejectsUnknownMethod) -{ - const std::string filename = temporary_file("abacus_nonlocal_unknown.INPUT"); - expect_invalid_input(filename, "xc_nonlocal Unknown\n", "xc_nonlocal must be either none or rvv10"); -} - -TEST_F(InputTest, NonlocalXcRejectsCombinedFunctionalAlias) -{ - const std::string filename = temporary_file("abacus_nonlocal_alias.INPUT"); - expect_invalid_input(filename, "dft_functional rVv10\n", "Use xc_nonlocal=rvv10"); -} - -#if defined(__LIBXC) && defined(__LIBVDWXC) -TEST_F(InputTest, NonlocalXcAcceptsSupportedMixedCaseInput) -{ - Parameter param; - const std::string filename = temporary_file("abacus_nonlocal_enabled.INPUT"); - EXPECT_NO_THROW(read_parameters(filename, "xc_nonlocal rVv10\ndft_functional gga_x_rpw86+gga_c_pbe\n", param)); - EXPECT_EQ(param.inp.xc_nonlocal, "rvv10"); -} - -TEST_F(InputTest, NonlocalXcRejectsIncorrectFunctional) -{ - const std::string filename = temporary_file("abacus_nonlocal_functional.INPUT"); - expect_invalid_input(filename, "xc_nonlocal rvv10\n", "requires an explicit semilocal dft_functional"); - expect_invalid_input(filename, - "xc_nonlocal rvv10\ndft_functional PBE\n", - "requires dft_functional=GGA_X_RPW86+GGA_C_PBE"); -} - -TEST_F(InputTest, NonlocalXcRejectsUnsupportedInputCombinations) -{ - const std::string filename = temporary_file("abacus_nonlocal_scope.INPUT"); - const std::string base = "xc_nonlocal rvv10\ndft_functional GGA_X_RPW86+GGA_C_PBE\n"; - const std::vector cases = { - "nspin 2\n", - "cal_force 1\n", - "cal_stress 1\n", - "basis_type lcao\nks_solver lapack\n", - "calculation nscf\n", - "esolver_type sdft\n", - "ecutwfc 50\necutrho 240\n", - "vdw_method d2\n", - "vl_in_h 0\n", - }; - for (const std::string& entry: cases) - { - SCOPED_TRACE(entry); - const std::string parameters = base + entry; - expect_invalid_input(filename, parameters, "xc_nonlocal=rvv10 supports only"); - } -} - -TEST_F(InputTest, NonlocalXcRejectsMultipleProcesses) -{ - set_nproc(2); - Parameter param; - param.set_pal_param(0, 2, 1); - const std::string filename = temporary_file("abacus_nonlocal_parallel.INPUT"); - testing::internal::CaptureStdout(); - EXPECT_THROW(read_parameters(filename, "xc_nonlocal rvv10\ndft_functional GGA_X_RPW86+GGA_C_PBE\n", param), - std::runtime_error); - const std::string output = testing::internal::GetCapturedStdout(); - EXPECT_THAT(output, testing::HasSubstr("xc_nonlocal=rvv10 supports only one MPI rank")); -} -#else -TEST_F(InputTest, NonlocalXcRequiresEnabledBackend) -{ - const std::string filename = temporary_file("abacus_nonlocal_backend.INPUT"); - expect_invalid_input(filename, - "xc_nonlocal rVv10\ndft_functional GGA_X_RPW86+GGA_C_PBE\n", - "requires Libxc and libvdwxc-enabled CMake builds"); -} -#endif - -TEST_F(InputTest, NonlocalXcRejectsGammaBeforeOverwritingKpoints) -{ - const std::string filename = temporary_file("abacus_nonlocal_gamma.INPUT"); - const std::string kpoint_file = temporary_file("abacus_nonlocal_gamma.KPT"); - const std::string original = "K_POINTS\n2\nDirect\n0.0 0.0 0.0 0.5\n0.25 0.0 0.0 0.5\n"; - { - std::ofstream kpoints(kpoint_file.c_str(), std::ios::binary); - ASSERT_TRUE(kpoints.is_open()); - kpoints << original; - kpoints.close(); - ASSERT_FALSE(kpoints.fail()); - } - const std::string parameters = "xc_nonlocal rVv10\ngamma_only 1\nkpoint_file " + kpoint_file + "\n"; - expect_invalid_input(filename, parameters, "gamma_only"); - std::ifstream kpoints(kpoint_file.c_str(), std::ios::binary); - ASSERT_TRUE(kpoints.is_open()); - const std::string actual((std::istreambuf_iterator(kpoints)), std::istreambuf_iterator()); - EXPECT_EQ(actual, original); - kpoints.close(); - EXPECT_EQ(std::remove(kpoint_file.c_str()), 0); + EXPECT_NO_THROW(read_parameters("rvv10_INPUT", "dft_functional rvv10\n", param)); + const std::string reason = "Requires cal_stress==false and esolver_type!=dfpt"; + expect_invalid_input("rvv10_INPUT", "dft_functional rVV10\ncalculation cell-relax\n", reason); + expect_invalid_input("rvv10_INPUT", "dft_functional RVV10\nesolver_type dfpt\n", reason); + expect_invalid_input("rvv10_INPUT", "dft_functional rvv10\nesolver_type lr\ncal_force 1\n", reason); } TEST_F(InputTest, ValidateRelaxMethodScalar) diff --git a/source/source_pw/module_pwdft/hamilt_pw.cpp b/source/source_pw/module_pwdft/hamilt_pw.cpp index 0fac19fb293..d3d284643f5 100644 --- a/source/source_pw/module_pwdft/hamilt_pw.cpp +++ b/source/source_pw/module_pwdft/hamilt_pw.cpp @@ -83,10 +83,6 @@ HamiltPW::HamiltPW(elecstate::Potential* pot_in, } // no variable can choose xc, maybe it is necessary pot_register_in.push_back("xc"); - if (input.xc_nonlocal != "none") - { - pot_register_in.push_back("xc_nonlocal"); - } if (input.imp_sol == 1) { pot_register_in.push_back("surchem"); diff --git a/source/source_pw/module_pwdft/stress_gga.cpp b/source/source_pw/module_pwdft/stress_gga.cpp index 1716bd093db..ec1bf7799b2 100644 --- a/source/source_pw/module_pwdft/stress_gga.cpp +++ b/source/source_pw/module_pwdft/stress_gga.cpp @@ -16,6 +16,12 @@ void Stress_Func::stress_gga(const UnitCell& ucell, ModuleBase::TITLE("Stress","stress_gga"); ModuleBase::timer::start("Stress","stress_gga"); + if (XC_Functional::get_nlc_b() > 0.0) + { + ModuleBase::WARNING_QUIT("Stress_Func::stress_gga", + "stress of the rVV10 nonlocal correlation is not implemented"); + } + int func_type = XC_Functional::get_func_type(); if (func_type == 0 || func_type == 1) { From a1b108ab35aab25c607ccf0efdeb7638210a0288 Mon Sep 17 00:00:00 2001 From: 1 Date: Tue, 27 Oct 2026 07:05:29 +0000 Subject: [PATCH 26/28] refactor(xc): select rVV10 as vdw_method rvv10 and use libvdwxc's distributed interface rVV10 becomes vdw_method rvv10 with the parameters vdw_rvv10_b and vdw_rvv10_c, independent of dft_functional. The parameters reach the XC layer through XCFunctionalParameters, and the nonlocal term is added in all three return paths of XC_Functional::v_xc and in XC_Functional_Libxc::v_xc_meta, so it can be combined with native, libxc and meta-GGA functionals. The libvdwxc object, its FFT plans and buffers are kept between calls and rebuilt only when the grid or the pool changes. With several processes per pool, vdwxc_init_mpi is used and the fields are redistributed between the ABACUS z-plane layout and libvdwxc's FFTW-MPI slabs instead of collecting the grid on one process. Stress is computed with vdwxc_stress plus the gradient term on the ABACUS grid. DFPT and LR-TDDFT forces are rejected when INPUT is read and at run time. Co-Authored-By: Claude Opus 5.5 --- CMakeLists.txt | 15 + docs/CITATIONS.md | 4 +- docs/advanced/install.md | 14 +- docs/advanced/scf/advanced.md | 1 + source/source_base/parallel_device.cpp | 10 + source/source_base/parallel_device.h | 7 + source/source_esolver/esolver_dfpt_pw.cpp | 7 +- source/source_esolver/esolver_fp.cpp | 5 + source/source_esolver/esolver_lr_init.cpp | 2 + .../source_hamilt/module_vdw/nonlocal_xc.cpp | 418 ++++++++++++---- source/source_hamilt/module_vdw/nonlocal_xc.h | 24 +- .../module_vdw/test/CMakeLists.txt | 10 + .../module_vdw/test/test_nonlocal_xc.cpp | 467 ++++++++++++++---- .../module_vdw/test/vdw_test.cpp | 12 + source/source_hamilt/module_vdw/vdw.cpp | 15 + source/source_hamilt/module_xc/libxc_pot.cpp | 6 + .../module_xc/test/CMakeLists.txt | 3 +- .../source_hamilt/module_xc/xc_functional.cpp | 33 +- .../source_hamilt/module_xc/xc_functional.h | 14 +- source/source_hamilt/module_xc/xc_pot.cpp | 22 +- .../module_parameter/input_parameter.h | 4 +- .../module_parameter/read_inp_estruc.cpp | 5 - .../module_parameter/read_inp_model.cpp | 40 +- .../source_io/test_serial/read_input_test.cpp | 20 +- .../source_lcao/module_lr/utils/lr_util.cpp | 11 + source/source_lcao/module_lr/utils/lr_util.h | 3 + .../module_lr/utils/test/test_lr_util.cpp | 10 + source/source_pw/module_pwdft/stress_gga.cpp | 12 +- 28 files changed, 908 insertions(+), 286 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b6657c531e..884349601d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -646,7 +646,22 @@ if(ENABLE_LIBXC) endif() if(ENABLE_LIBVDWXC) + # The libvdwxc package configuration looks up MPI for C when libvdwxc was built with MPI. + enable_language(C) find_package(libvdwxc CONFIG REQUIRED) + # A configuration that does not report VDWXC_ENABLE_MPI has no vdwxc_init_mpi either. + if(ENABLE_MPI AND NOT VDWXC_ENABLE_MPI) + message( + FATAL_ERROR + "ENABLE_LIBVDWXC with ENABLE_MPI requires libvdwxc configured with -DVDWXC_ENABLE_MPI=ON" + ) + endif() + if(MKL_FOUND AND VDWXC_ENABLE_MPI) + message( + FATAL_ERROR + "libvdwxc with MPI links FFTW3 and FFTW3-MPI, which cannot be mixed with the MKL FFT interface; build ABACUS with the same FFTW3" + ) + endif() endif() if(DEFINED DeePMD_DIR) diff --git a/docs/CITATIONS.md b/docs/CITATIONS.md index 46e59414be0..88a32d7fccd 100644 --- a/docs/CITATIONS.md +++ b/docs/CITATIONS.md @@ -56,10 +56,12 @@ The following references are required to be cited when using ABACUS. Specificall 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 rVV10 nonlocal correlation (`dft_functional rvv10`) is used:** +- **If the rVV10 nonlocal correlation (`vdw_method rvv10`) 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. R. 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. A. H. Larsen, M. Kuisma, J. Löfgren, Y. Pouillon, P. Erhart, and P. Hyldgaard. "libvdwxc: a library for exchange-correlation functionals in the vdW-DF family." Modelling and Simulation in Materials Science and Engineering 25, 065004 (2017). DOI: 10.1088/1361-651X/aa7320. + + For SCAN+rVV10 and r2SCAN+rVV10, also cite the parametrization: H. Peng, Z.-H. Yang, J. P. Perdew, and J. Sun. "Versatile van der Waals density functional based on a meta-generalized gradient approximation." Physical Review X 6, 041005 (2016). DOI: 10.1103/PhysRevX.6.041005; J. Ning, M. Kothakonda, J. W. Furness, A. D. Kaplan, S. Ehlert, J. G. Brandenburg, J. P. Perdew, and J. Sun. "Workhorse minimally empirical dispersion-corrected density functional with tests for weakly bound systems: r2SCAN+rVV10." Physical Review B 106, 075422 (2022). DOI: 10.1103/PhysRevB.106.075422. diff --git a/docs/advanced/install.md b/docs/advanced/install.md index ddbeb3d191e..45988de12e4 100644 --- a/docs/advanced/install.md +++ b/docs/advanced/install.md @@ -101,14 +101,22 @@ If `vdw_d4_xc` is set to `default`, ABACUS will infer the functional name from ` ## Build with libvdwxc support -The rVV10 nonlocal correlation (`dft_functional rvv10`) is evaluated by the external [libvdwxc](https://gitlab.com/libvdwxc/libvdwxc) library. Until a tagged libvdwxc release contains the rVV10 interface, build the branch of [libvdwxc merge request !32](https://gitlab.com/libvdwxc/libvdwxc/-/merge_requests/32) (commit `94410eb` at the time of writing) against the FFTW that ABACUS uses, with libvdwxc's CMake build so that ABACUS can find the exported package; a static libvdwxc also needs `pkg-config` and FFTW's `fftw3.pc`: +`vdw_method rvv10` evaluates the rVV10 nonlocal correlation with the external [libvdwxc](https://gitlab.com/libvdwxc/libvdwxc) library. Until a tagged libvdwxc release contains the rVV10 interface, build the branch of [libvdwxc merge request !32](https://gitlab.com/libvdwxc/libvdwxc/-/merge_requests/32) (tested at commit `da2d55f`) with libvdwxc's CMake build, which installs the package that ABACUS looks up, and against the FFTW that ABACUS uses. For an MPI build of ABACUS, configure libvdwxc with MPI, which needs FFTW with its MPI library; a static libvdwxc also needs `pkg-config` and FFTW's `fftw3.pc`: ```bash -cmake -S libvdwxc -B build-libvdwxc -DCMAKE_INSTALL_PREFIX=/path/to/libvdwxc +cmake -S libvdwxc -B build-libvdwxc -DVDWXC_ENABLE_MPI=ON -DCMAKE_INSTALL_PREFIX=/path/to/libvdwxc cmake --build build-libvdwxc && cmake --install build-libvdwxc ``` -Then pass `-DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON` to CMake for ABACUS and add `/path/to/libvdwxc` to `CMAKE_PREFIX_PATH`. A serial libvdwxc build is sufficient: in MPI runs, ABACUS collects the density of each k-point pool on its first process for the nonlocal term. Stress, DFPT and LR-TDDFT forces are not supported with this functional, and the legacy Makefile does not build it. +Then pass `-DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON` to CMake for ABACUS and add `/path/to/libvdwxc` to `CMAKE_PREFIX_PATH`. ABACUS hands each k-point pool's density to libvdwxc's distributed interface, so the nonlocal convolution and its memory are shared by the processes of the pool. libvdwxc's MPI build uses FFTW and FFTW-MPI, so it cannot be combined with the MKL FFT interface. The legacy Makefile does not build this feature. + +In the input file: + +```text +vdw_method rvv10 +dft_functional scan +vdw_rvv10_b 15.7 # 6.3 (default) for GGA_X_RPW86+GGA_C_PBE, 15.7 for SCAN, 11.95 for r2SCAN +``` ## Build with JSON support diff --git a/docs/advanced/scf/advanced.md b/docs/advanced/scf/advanced.md index a01280344bb..c10102b4c65 100644 --- a/docs/advanced/scf/advanced.md +++ b/docs/advanced/scf/advanced.md @@ -96,6 +96,7 @@ To use VdW-correction, users need to supply value to the `vdw_method` keyword in - d2: DFT-D2 method - d3_0: DFT-D3(0) method - d3_bj: DFT-D3(BJ) method + - rvv10: rVV10 nonlocal correlation (requires libvdwxc). Unlike the DFT-D methods, it depends on the electron density: it is added self-consistently to the exchange-correlation energy and potential of `dft_functional`, and its energy is reported in the exchange-correlation term. Its parameters `vdw_rvv10_b` and `vdw_rvv10_c` should match the functional, e.g. b = 6.3 for `GGA_X_RPW86+GGA_C_PBE`, 15.7 for SCAN and 11.95 for r2SCAN. Furthermore, ABACUS also provides a [list of keywords](../input_files/input-main.md#vdw-correction) to control relevant parmeters used in calculating the VdW correction, such as the scale factor (s6) term. Recommended values of such parameters can be found on the [webpage](https://www.chemie.uni-bonn.de/pctc/mulliken-center/software/dft-d3). The default values of the parameters in ABACUS are set to be the recommended values for PBE. diff --git a/source/source_base/parallel_device.cpp b/source/source_base/parallel_device.cpp index 8ba8c3ae8ed..a66837d2b45 100644 --- a/source/source_base/parallel_device.cpp +++ b/source/source_base/parallel_device.cpp @@ -350,6 +350,16 @@ void gatherv_data(const std::complex* sendbuf, int sendcount, std::comple { MPI_Allgatherv(sendbuf, sendcount, MPI_COMPLEX, recvbuf, recvcounts, displs, MPI_COMPLEX, comm); } +void alltoallv_data(const double* sendbuf, + const int* sendcounts, + const int* sdispls, + double* recvbuf, + const int* recvcounts, + const int* rdispls, + const MPI_Comm& comm) +{ + MPI_Alltoallv(sendbuf, sendcounts, sdispls, MPI_DOUBLE, recvbuf, recvcounts, rdispls, MPI_DOUBLE, comm); +} #ifndef __CUDA_MPI template diff --git a/source/source_base/parallel_device.h b/source/source_base/parallel_device.h index 7826e4b4653..f4d35e4e993 100644 --- a/source/source_base/parallel_device.h +++ b/source/source_base/parallel_device.h @@ -31,6 +31,13 @@ void gatherv_data(const double* sendbuf, int sendcount, double* recvbuf, const i void gatherv_data(const std::complex* sendbuf, int sendcount, std::complex* recvbuf, const int* recvcounts, const int* displs, MPI_Comm& comm); void gatherv_data(const float* sendbuf, int sendcount, float* recvbuf, const int* recvcounts, const int* displs, MPI_Comm& comm); void gatherv_data(const std::complex* sendbuf, int sendcount, std::complex* recvbuf, const int* recvcounts, const int* displs, MPI_Comm& comm); +void alltoallv_data(const double* sendbuf, + const int* sendcounts, + const int* sdispls, + double* recvbuf, + const int* recvcounts, + const int* rdispls, + const MPI_Comm& comm); #if defined(__NCCL_PARALLEL_DEVICE) void nccl_bcast_data(double* object, const int& n, MPI_Comm& comm, int root = 0); diff --git a/source/source_esolver/esolver_dfpt_pw.cpp b/source/source_esolver/esolver_dfpt_pw.cpp index 0e01e52aeb5..e7529260484 100644 --- a/source/source_esolver/esolver_dfpt_pw.cpp +++ b/source/source_esolver/esolver_dfpt_pw.cpp @@ -317,10 +317,11 @@ void ESolver_DFPT_PW::init_dfpt(UnitCell& ucell) << ") needs response channels that are not implemented"; ModuleBase::WARNING_QUIT("ESolver_DFPT_PW::init_dfpt", msg.str()); } - if (XC_Functional::get_nlc_b() > 0.0) + if (XC_Functional::get_runtime_parameters().rvv10_b > 0.0) { - ModuleBase::WARNING_QUIT("ESolver_DFPT_PW::init_dfpt", - "the response of the rVV10 nonlocal correlation is not implemented"); + ModuleBase::WARNING_QUIT( + "ESolver_DFPT_PW::init_dfpt", + "the response of the rVV10 nonlocal correlation (vdw_method rvv10) is not implemented"); } // first-order XC kernel around the converged ground-state density: diff --git a/source/source_esolver/esolver_fp.cpp b/source/source_esolver/esolver_fp.cpp index 149f50dc8cb..b0c26cbd762 100644 --- a/source/source_esolver/esolver_fp.cpp +++ b/source/source_esolver/esolver_fp.cpp @@ -60,6 +60,11 @@ void ESolver_FP::before_all_runners(BaseCell& basecell, const Input_para& inp) xc_parameters.exx_erfc_alpha = inp.exx_erfc_alpha; xc_parameters.xc_exch_ext = inp.xc_exch_ext; xc_parameters.xc_corr_ext = inp.xc_corr_ext; + if (inp.vdw_method == "rvv10") + { + xc_parameters.rvv10_b = inp.vdw_rvv10_b; + xc_parameters.rvv10_c = inp.vdw_rvv10_c; + } XC_Functional::set_runtime_parameters(xc_parameters); ModuleBase::TITLE("ESolver_FP", "before_all_runners"); diff --git a/source/source_esolver/esolver_lr_init.cpp b/source/source_esolver/esolver_lr_init.cpp index a33589d4955..1170e5b0c50 100644 --- a/source/source_esolver/esolver_lr_init.cpp +++ b/source/source_esolver/esolver_lr_init.cpp @@ -98,6 +98,7 @@ void ModuleESolver::ESolver_LR::before_all_runners(BaseCell& basecell, co this->ks_ = LR_Util::make_unique>(); this->ks_->before_all_runners(basecell, inp); LR_Util::check_force_pp(ucell, inp.cal_force, inp.calculation); + LR_Util::check_force_vdw(inp.vdw_method, inp.cal_force, inp.calculation); } else { @@ -339,6 +340,7 @@ void ModuleESolver::ESolver_LR::initialize_from_unitcell_(UnitCell& ucell // necessary steps in ESolver_FP ESolver_FP::before_all_runners(ucell, inp); LR_Util::check_force_pp(ucell, inp.cal_force, inp.calculation); + LR_Util::check_force_vdw(inp.vdw_method, inp.cal_force, inp.calculation); this->pelec = new elecstate::ElecStateLCAO(); // necessary steps in ESolver_KS::before_all_runners : symmetry and k-points diff --git a/source/source_hamilt/module_vdw/nonlocal_xc.cpp b/source/source_hamilt/module_vdw/nonlocal_xc.cpp index e122a77901b..3e5647df2bc 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc.cpp +++ b/source/source_hamilt/module_vdw/nonlocal_xc.cpp @@ -2,171 +2,338 @@ #include "source_base/constants.h" #include "source_base/matrix.h" +#include "source_base/matrix3.h" #include "source_base/parallel_device.h" #include "source_base/timer.h" +#include "source_base/tool_quit.h" #include "source_base/tool_title.h" #include "source_basis/module_pw/pw_basis.h" #include "source_estate/module_charge/charge.h" #include "source_hamilt/module_xc/xc_functional.h" +#ifdef __MPI +// vdwxc_mpi.h includes inside an extern "C" block, so include it first here. +#include +#endif // vdwxc.h includes inside an extern "C" block. Include the C++ header first so that // libc++ does not parse its templates with C linkage, then drop the I macro. #include #include #include +#ifdef __MPI +#include +#endif #undef I +#include #include namespace vdw { namespace { -/// Copy the z planes [startz, startz + nplane) of a block stored as [ixy][iz] into the complete grid [ixy][z]. -void put_planes(const ModulePW::PW_Basis& pw, - const int nplane, - const int startz, - const double* block, - std::vector& full) +/** + * libvdwxc object for one grid and pool, kept between calls. + * + * libvdwxc distributes the first axis of its grid in blocks of ceil(n/P) planes, the FFTW-MPI default. + * It is given the lattice vectors in the cyclic order (a3, a1, a2), so that this axis is the z axis + * that ABACUS distributes and the slab of a process is stored as [z][x][y], the transpose of the + * ABACUS layout [x][y][z]. Whole z planes are moved between the two distributions with all-to-all exchanges. + */ +class Rvv10Workspace { - for (int ixy = 0; ixy < pw.nxy; ++ixy) + public: + /// libvdwxc energy (Ha) and derivatives of the ABACUS-distributed fields rho and sigma; with + /// stress != nullptr also the explicit strain derivative (Ha, Cartesian, not divided by the volume). + double evaluate(const ModulePW::PW_Basis& pw, + const double b, + const double c, + const std::vector& rho, + const std::vector& sigma, + std::vector& dedn, + std::vector& dedsigma, + double* stress); + + private: + void prepare(const ModulePW::PW_Basis& pw, const double b, const double c); + void plan_exchange(const ModulePW::PW_Basis& pw); + void to_libvdwxc(const ModulePW::PW_Basis& pw, const std::vector& field, std::vector& slab); + void from_libvdwxc(const ModulePW::PW_Basis& pw, const std::vector& slab, std::vector& field); + + vdwxc_data data_ = nullptr; + int nx_ = 0; + int ny_ = 0; + int nz_ = 0; + int nproc_ = 0; +#ifdef __MPI + MPI_Comm comm_ = MPI_COMM_NULL; +#endif + // planes moved to (send) and from (recv) each process, in doubles + std::vector send_count_; + std::vector send_offset_; + std::vector recv_count_; + std::vector recv_offset_; + // the ABACUS planes of this process as [z][xy], and the libvdwxc slab + std::vector planes_; + std::vector rho_; + std::vector sigma_; + std::vector dedn_; + std::vector dedsigma_; +}; + +void Rvv10Workspace::prepare(const ModulePW::PW_Basis& pw, const double b, const double c) +{ + bool same_grid = data_ != nullptr && pw.nx == nx_ && pw.ny == ny_ && pw.nz == nz_ && pw.poolnproc == nproc_; +#ifdef __MPI + same_grid = same_grid && (pw.poolnproc == 1 || pw.pool_world == comm_); +#endif + if (!same_grid) + { + if (data_ != nullptr) + { + vdwxc_finalize(&data_); + } + data_ = vdwxc_new(VDWXC_RVV10); + nx_ = pw.nx; + ny_ = pw.ny; + nz_ = pw.nz; + nproc_ = pw.poolnproc; + } + // The cell is set on every call: a cell relaxation keeps the FFT grid but changes the cell. libvdwxc + // integrates with the signed cell volume, and inverting a left-handed cell leaves rVV10 unchanged. + ModuleBase::Matrix3 a = pw.latvec * pw.lat0; + if (a.Det() < 0.0) + { + a = a * -1.0; + } + vdwxc_set_unit_cell(data_, pw.nz, pw.nx, pw.ny, a.e31, a.e32, a.e33, a.e11, a.e12, a.e13, a.e21, a.e22, a.e23); + if (!same_grid) { - for (int iz = 0; iz < nplane; ++iz) +#ifdef __MPI + comm_ = pw.pool_world; + if (pw.poolnproc > 1) + { + vdwxc_init_mpi(data_, comm_); + } + else { - full[ixy * pw.nz + startz + iz] = block[ixy * nplane + iz]; + vdwxc_init_serial(data_); } +#else + vdwxc_init_serial(data_); +#endif + } + // The libvdwxc slabs depend only on nz and the number of processes, but the ABACUS planes depend on + // the basis, so the exchange is planned on every call. + this->plan_exchange(pw); + if (vdwxc_set_rvv10_parameters(data_, b, c) != 0) + { + ModuleBase::WARNING_QUIT("vdw::add_rvv10", "libvdwxc rejected vdw_rvv10_b or vdw_rvv10_c"); } } -/// Copy the z planes [startz, startz + nplane) of the complete grid into a block stored as [ixy][iz]. -void get_planes(const ModulePW::PW_Basis& pw, - const int nplane, - const int startz, - const std::vector& full, - double* block) +void Rvv10Workspace::plan_exchange(const ModulePW::PW_Basis& pw) { - for (int ixy = 0; ixy < pw.nxy; ++ixy) + const int nproc = pw.poolnproc; + const int block = (pw.nz + nproc - 1) / nproc; + const int first_slab_plane = pw.poolrank * block; + const int slab_start = std::min(first_slab_plane, pw.nz); + const int planes_after_start = pw.nz - slab_start; + const int nslab = std::min(block, planes_after_start); + const int slab_end = slab_start + nslab; + const int plane_end = pw.startz_current + pw.nplane; + send_count_.assign(nproc, 0); + send_offset_.assign(nproc, 0); + recv_count_.assign(nproc, 0); + recv_offset_.assign(nproc, 0); + for (int ip = 0; nproc > 1 && ip < nproc; ++ip) { - for (int iz = 0; iz < nplane; ++iz) + // ABACUS planes of this process that lie in the slab of ip + const int first_plane_of_ip = ip * block; + const int begin = std::min(first_plane_of_ip, pw.nz); + const int block_end = begin + block; + const int end = std::min(block_end, pw.nz); + int low = std::max(pw.startz_current, begin); + int high = std::min(plane_end, end); + if (high > low) + { + send_count_[ip] = (high - low) * pw.nxy; + send_offset_[ip] = (low - pw.startz_current) * pw.nxy; + } + // ABACUS planes of ip that lie in the slab of this process + const int plane_end_of_ip = pw.startz[ip] + pw.numz[ip]; + low = std::max(pw.startz[ip], slab_start); + high = std::min(plane_end_of_ip, slab_end); + if (high > low) { - block[ixy * nplane + iz] = full[ixy * pw.nz + startz + iz]; + recv_count_[ip] = (high - low) * pw.nxy; + recv_offset_[ip] = (low - slab_start) * pw.nxy; } } + // libvdwxc expects valid pointers also for an empty slab + const int slab_points = nslab * pw.nxy; + const int slab_size = std::max(1, slab_points); + // the transposed ABACUS planes are only needed for the exchange between processes + const int planes_size = (nproc > 1) ? std::max(1, pw.nrxx) : 0; + planes_.resize(planes_size); + rho_.resize(slab_size); + sigma_.resize(slab_size); + dedn_.resize(slab_size); + dedsigma_.resize(slab_size); } -/// The complete grid of pw in libvdwxc's [x][y][z] order on the pool root; empty on the other processes. -std::vector gather_grid(const ModulePW::PW_Basis& pw, const double* local) +void Rvv10Workspace::to_libvdwxc(const ModulePW::PW_Basis& pw, + const std::vector& field, + std::vector& slab) { - std::vector full; + std::vector& planes = (pw.poolnproc > 1) ? planes_ : slab; + for (int ixy = 0; ixy < pw.nxy; ++ixy) + { + for (int iz = 0; iz < pw.nplane; ++iz) + { + planes[iz * pw.nxy + ixy] = field[ixy * pw.nplane + iz]; + } + } #ifdef __MPI - MPI_Comm comm = pw.pool_world; - if (pw.poolrank != 0) + if (pw.poolnproc > 1) { - Parallel_Common::send_data(local, pw.nrxx, 0, 0, comm); - return full; + Parallel_Common::alltoallv_data(planes_.data(), + send_count_.data(), + send_offset_.data(), + slab.data(), + recv_count_.data(), + recv_offset_.data(), + pw.pool_world); } #endif - full.resize(pw.nxyz); - put_planes(pw, pw.nplane, pw.startz_current, local, full); +} + +void Rvv10Workspace::from_libvdwxc(const ModulePW::PW_Basis& pw, + const std::vector& slab, + std::vector& field) +{ #ifdef __MPI - std::vector block; - for (int ip = 1; ip < pw.poolnproc; ++ip) + if (pw.poolnproc > 1) { - const int count = pw.numz[ip] * pw.nxy; - block.resize(count); - Parallel_Common::recv_data(block.data(), count, ip, 0, comm, MPI_STATUS_IGNORE); - put_planes(pw, pw.numz[ip], pw.startz[ip], block.data(), full); + Parallel_Common::alltoallv_data(slab.data(), + recv_count_.data(), + recv_offset_.data(), + planes_.data(), + send_count_.data(), + send_offset_.data(), + pw.pool_world); } #endif - return full; + const std::vector& planes = (pw.poolnproc > 1) ? planes_ : slab; + for (int ixy = 0; ixy < pw.nxy; ++ixy) + { + for (int iz = 0; iz < pw.nplane; ++iz) + { + field[ixy * pw.nplane + iz] = planes[iz * pw.nxy + ixy]; + } + } } -/// The local z planes of pw from the complete grid held by the pool root. -void scatter_grid(const ModulePW::PW_Basis& pw, const std::vector& full, double* local) +double Rvv10Workspace::evaluate(const ModulePW::PW_Basis& pw, + const double b, + const double c, + const std::vector& rho, + const std::vector& sigma, + std::vector& dedn, + std::vector& dedsigma, + double* stress) { -#ifdef __MPI - MPI_Comm comm = pw.pool_world; - if (pw.poolrank != 0) + this->prepare(pw, b, c); + this->to_libvdwxc(pw, rho, rho_); + this->to_libvdwxc(pw, sigma, sigma_); + double energy = 0.0; + if (stress == nullptr) { - Parallel_Common::recv_data(local, pw.nrxx, 0, 0, comm, MPI_STATUS_IGNORE); - return; + energy = vdwxc_calculate(data_, rho_.data(), sigma_.data(), dedn_.data(), dedsigma_.data()); } - std::vector block; - for (int ip = 1; ip < pw.poolnproc; ++ip) + else { - const int count = pw.numz[ip] * pw.nxy; - block.resize(count); - get_planes(pw, pw.numz[ip], pw.startz[ip], full, block.data()); - Parallel_Common::send_data(block.data(), count, ip, 0, comm); + energy = vdwxc_stress(data_, stress, rho_.data(), sigma_.data(), dedn_.data(), dedsigma_.data()); + } + this->from_libvdwxc(pw, dedn_, dedn); + this->from_libvdwxc(pw, dedsigma_, dedsigma); +#ifdef __MPI + // the distributed backend returns the contribution of this process + if (pw.poolnproc > 1) + { + Parallel_Common::reduce_data(&energy, 1, pw.pool_world); + if (stress != nullptr) + { + Parallel_Common::reduce_data(stress, 9, pw.pool_world); + } } #endif - get_planes(pw, pw.nplane, pw.startz_current, full, local); + return energy; } -} // namespace -void add_rvv10(const double b, - const double c, - const Charge& chr, - std::tuple& etxc_vtxc_v) +/// The workspace is never destroyed: a static object would be destroyed after MPI_Finalize and +/// fftw_cleanup_threads, which libvdwxc's FFT plans must not outlive. +Rvv10Workspace& workspace() +{ + static Rvv10Workspace* const instance = new Rvv10Workspace; + return *instance; +} + +/// Spin channels of chr that carry density; for nspin=4, rho[0] is the total valence density. +int density_channels(const Charge& chr) +{ + return (chr.nspin == 2) ? 2 : 1; +} + +/// Total density of chr (valence plus core), its plane-wave gradient and sigma = |grad n|^2. +void total_density(const Charge& chr, + std::vector& rho, + std::vector>& grad, + std::vector& sigma) { - ModuleBase::TITLE("vdw", "add_rvv10"); - ModuleBase::timer::start("vdw", "add_rvv10"); const ModulePW::PW_Basis& pw = *chr.rhopw; const int nrxx = chr.nrxx; - // rVV10 depends on the total density only; for nspin=4, rho[0] is the total valence density. - const int nchannel = (chr.nspin == 2) ? 2 : 1; const double* core_begin = chr.rho_core; const double* core_end = core_begin + nrxx; - std::vector rho(core_begin, core_end); - for (int is = 0; is < nchannel; ++is) + rho.assign(core_begin, core_end); + for (int is = 0; is < density_channels(chr); ++is) { for (int ir = 0; ir < nrxx; ++ir) { rho[ir] += chr.rho[is][ir]; } } - - // sigma = |grad n|^2 from the plane-wave gradient std::vector> rhog(pw.npw); pw.real2recip(rho.data(), rhog.data()); - std::vector> grad(nrxx); + grad.resize(nrxx); XC_Functional::grad_rho(rhog.data(), grad.data(), &pw, pw.tpiba); - std::vector sigma(nrxx); + sigma.resize(nrxx); for (int ir = 0; ir < nrxx; ++ir) { sigma[ir] = grad[ir].norm2(); } +} +} // namespace - // libvdwxc runs serially on the pool root; energy and derivatives are in Hartree units - std::vector rho_full = gather_grid(pw, rho.data()); - std::vector sigma_full = gather_grid(pw, sigma.data()); - std::vector dedn_full(rho_full.size()); - std::vector dedsigma_full(rho_full.size()); - double energy = 0.0; - if (pw.poolrank == 0) +void add_rvv10(const Charge& chr, double& etxc, double& vtxc, ModuleBase::matrix& v) +{ + const XCFunctionalParameters& parameters = XC_Functional::get_runtime_parameters(); + if (parameters.rvv10_b <= 0.0) { - // ABACUS stores the lattice vectors as the rows of latvec; libvdwxc takes them as three - // consecutive groups of Cartesian components in Bohr and integrates with the signed cell - // volume, so a left-handed cell is inverted, which leaves rVV10 unchanged. - ModuleBase::Matrix3 a = pw.latvec * pw.lat0; - if (a.Det() < 0.0) - { - a = a * -1.0; - } - vdwxc_data data = vdwxc_new(VDWXC_RVV10); - vdwxc_set_rvv10_parameters(data, b, c); - vdwxc_set_unit_cell(data, pw.nx, pw.ny, pw.nz, a.e11, a.e12, a.e13, a.e21, a.e22, a.e23, a.e31, a.e32, a.e33); - vdwxc_init_serial(data); - energy = vdwxc_calculate(data, rho_full.data(), sigma_full.data(), dedn_full.data(), dedsigma_full.data()); - vdwxc_finalize(&data); + return; } -#ifdef __MPI - Parallel_Common::bcast_data(&energy, 1, pw.pool_world); -#endif + ModuleBase::TITLE("vdw", "add_rvv10"); + ModuleBase::timer::start("vdw", "add_rvv10"); + const ModulePW::PW_Basis& pw = *chr.rhopw; + const int nrxx = chr.nrxx; + std::vector rho; + std::vector> grad; + std::vector sigma; + total_density(chr, rho, grad, sigma); std::vector dedn(nrxx); std::vector dedsigma(nrxx); - scatter_grid(pw, dedn_full, dedn.data()); - scatter_grid(pw, dedsigma_full, dedsigma.data()); + const double energy + = workspace().evaluate(pw, parameters.rvv10_b, parameters.rvv10_c, rho, sigma, dedn, dedsigma, nullptr); // v = dE/dn - div(2 dE/dsigma grad n), converted from Hartree to Ry by e2 for (int ir = 0; ir < nrxx; ++ir) @@ -175,23 +342,82 @@ void add_rvv10(const double b, } std::vector divergence(nrxx); XC_Functional::grad_dot(grad.data(), divergence.data(), &pw, pw.tpiba); - ModuleBase::matrix& v = std::get<2>(etxc_vtxc_v); - double vtxc = 0.0; + double vtxc_nl = 0.0; for (int ir = 0; ir < nrxx; ++ir) { const double v_nl = ModuleBase::e2 * (dedn[ir] - divergence[ir]); - for (int is = 0; is < nchannel; ++is) + for (int is = 0; is < density_channels(chr); ++is) { v(is, ir) += v_nl; - vtxc += v_nl * chr.rho[is][ir]; + vtxc_nl += v_nl * chr.rho[is][ir]; } } #ifdef __MPI - Parallel_Common::reduce_data(&vtxc, 1, pw.pool_world); + Parallel_Common::reduce_data(&vtxc_nl, 1, pw.pool_world); #endif - std::get<0>(etxc_vtxc_v) += ModuleBase::e2 * energy; - std::get<1>(etxc_vtxc_v) += vtxc * pw.omega / pw.nxyz; + etxc += ModuleBase::e2 * energy; + vtxc += vtxc_nl * pw.omega / pw.nxyz; ModuleBase::timer::end("vdw", "add_rvv10"); } +void add_rvv10_stress(const Charge& chr, ModuleBase::matrix& sigma) +{ + const XCFunctionalParameters& parameters = XC_Functional::get_runtime_parameters(); + if (parameters.rvv10_b <= 0.0) + { + return; + } + ModuleBase::TITLE("vdw", "add_rvv10_stress"); + ModuleBase::timer::start("vdw", "add_rvv10_stress"); + const ModulePW::PW_Basis& pw = *chr.rhopw; + const int nrxx = chr.nrxx; + std::vector rho; + std::vector> grad; + std::vector density_sigma; + total_density(chr, rho, grad, density_sigma); + std::vector dedn(nrxx); + std::vector dedsigma(nrxx); + double explicit_stress[9] = {0.0}; + const double energy = workspace().evaluate(pw, + parameters.rvv10_b, + parameters.rvv10_c, + rho, + density_sigma, + dedn, + dedsigma, + explicit_stress); + + // integral of dE/dsigma d_a n d_b n, in units of the volume element + double gradient_term[9] = {0.0}; + for (int ir = 0; ir < nrxx; ++ir) + { + const double g[3] = {grad[ir].x, grad[ir].y, grad[ir].z}; + for (int a = 0; a < 3; ++a) + { + for (int b = 0; b < 3; ++b) + { + gradient_term[3 * a + b] += dedsigma[ir] * g[a] * g[b]; + } + } + } +#ifdef __MPI + Parallel_Common::reduce_data(gradient_term, 9, pw.pool_world); +#endif + + // The full strain derivative is T - delta_ab Int(n dE/dn + 2 sigma dE/dsigma) - 2 Int dE/dsigma d_a n d_b n, + // with T the explicit part from libvdwxc. The XC diagonal -(etxc - vtxc)/omega in PW (in LCAO, -etxc/omega + // and the Pulay term of the effective potential) already holds -e2 (E - Int(n dE/dn + 2 sigma dE/dsigma)) / omega, + // which leaves the terms below (ABACUS sign, Ry). + for (int a = 0; a < 3; ++a) + { + for (int b = 0; b < 3; ++b) + { + const double diagonal = (a == b) ? energy : 0.0; + sigma(a, b) += -ModuleBase::e2 / pw.omega * (explicit_stress[3 * a + b] - diagonal) + + 2.0 * ModuleBase::e2 * gradient_term[3 * a + b] / pw.nxyz; + } + } + ModuleBase::timer::end("vdw", "add_rvv10_stress"); +} + } // namespace vdw diff --git a/source/source_hamilt/module_vdw/nonlocal_xc.h b/source/source_hamilt/module_vdw/nonlocal_xc.h index ba9e9666345..be040e0cf73 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc.h +++ b/source/source_hamilt/module_vdw/nonlocal_xc.h @@ -1,8 +1,6 @@ #ifndef NONLOCAL_XC_H #define NONLOCAL_XC_H -#include - class Charge; namespace ModuleBase { @@ -12,18 +10,16 @@ class matrix; namespace vdw { -/** - * @brief Add the rVV10 nonlocal correlation, evaluated by libvdwxc, to the results of XC_Functional::v_xc. - * - * The energy and the potential are those of the total density of chr, valence plus core. The - * potential is added to every spin channel that carries density, and vtxc counts the valence - * density only, as in v_xc. b and c are the rVV10 parameters; all results are in Ry. The complete - * grid is collected on the root of the k-point pool, where libvdwxc runs serially. - */ -void add_rvv10(const double b, - const double c, - const Charge& chr, - std::tuple& etxc_vtxc_v); +// rVV10 nonlocal correlation of vdw_method rvv10, evaluated by libvdwxc. Both functions do nothing +// unless the XC runtime parameters carry rvv10_b > 0. They use the total density of chr (valence plus +// core) and are collective over the pool communicator of chr.rhopw. The libvdwxc object, its FFT plans +// and its buffers are kept between calls. + +/// Add the rVV10 energy, potential and vtxc (Ry) to the pool-reduced results of an XC evaluation. +void add_rvv10(const Charge& chr, double& etxc, double& vtxc, ModuleBase::matrix& v); + +/// Add the rVV10 stress (Ry/Bohr^3) that the XC diagonal and potential terms do not contain. +void add_rvv10_stress(const Charge& chr, ModuleBase::matrix& sigma); } // namespace vdw diff --git a/source/source_hamilt/module_vdw/test/CMakeLists.txt b/source/source_hamilt/module_vdw/test/CMakeLists.txt index 33f208cfde0..1759b19dd21 100644 --- a/source/source_hamilt/module_vdw/test/CMakeLists.txt +++ b/source/source_hamilt/module_vdw/test/CMakeLists.txt @@ -36,6 +36,8 @@ if(ENABLE_LIBVDWXC) container base SOURCES test_nonlocal_xc.cpp) + set_tests_properties(MODULE_HAMILT_nonlocal_xcTest + PROPERTIES ENVIRONMENT "OMP_NUM_THREADS=1") add_test( NAME MODULE_HAMILT_nonlocal_xcTest_2np COMMAND ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} 2 ${MPIEXEC_PREFLAGS} @@ -43,4 +45,12 @@ if(ENABLE_LIBVDWXC) WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) set_tests_properties(MODULE_HAMILT_nonlocal_xcTest_2np PROPERTIES ENVIRONMENT "OMP_NUM_THREADS=1" PROCESSORS 2) + # four processes give an uneven z distribution and an empty libvdwxc slab + add_test( + NAME MODULE_HAMILT_nonlocal_xcTest_4np + COMMAND ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} 4 ${MPIEXEC_PREFLAGS} + $ ${MPIEXEC_POSTFLAGS} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + set_tests_properties(MODULE_HAMILT_nonlocal_xcTest_4np + PROPERTIES ENVIRONMENT "OMP_NUM_THREADS=1" PROCESSORS 4) endif() diff --git a/source/source_hamilt/module_vdw/test/test_nonlocal_xc.cpp b/source/source_hamilt/module_vdw/test/test_nonlocal_xc.cpp index 33466e3a6a3..21e46a2d840 100644 --- a/source/source_hamilt/module_vdw/test/test_nonlocal_xc.cpp +++ b/source/source_hamilt/module_vdw/test/test_nonlocal_xc.cpp @@ -1,11 +1,13 @@ #include "../nonlocal_xc.h" #include "source_base/constants.h" +#include "source_base/matrix.h" #include "source_base/parallel_comm.h" #include "source_base/parallel_device.h" #include "source_base/parallel_global.h" #include "source_basis/module_pw/pw_basis.h" #include "source_cell/unitcell.h" #include "source_estate/module_charge/charge.h" +#include "source_hamilt/module_xc/libxc_abacus.h" #include "source_hamilt/module_xc/xc_functional.h" #include @@ -56,14 +58,20 @@ namespace int test_rank = 0; int test_size = 1; typedef std::tuple VxcResult; -// rVV10 parameters that set_xc_type assigns to RVV10 +// rVV10 parameters of the rPW86-PBE pairing const double RVV10_B = 6.3; const double RVV10_C = 0.0093; -// The adapter and the reference share the plane-wave gradient, so they agree to rounding. +// Results that share the plane-wave gradient agree to rounding. const double ROUNDING_TOLERANCE = 2.0e-12; -// Central difference on a smooth density: the truncation error is of order step^2. +// Central differences on smooth densities: the truncation error is of order step^2. const double FINITE_DIFFERENCE_STEP = 1.0e-5; const double FINITE_DIFFERENCE_TOLERANCE = 2.0e-6; +const double STRAIN_STEP = 1.0e-4; +const double STRAIN_TOLERANCE = 1.0e-5; +const double BASIS_CUTOFF = 4.0; +// PW_Basis lowers a larger cutoff to the largest G sphere inside the FFT box. The test densities have +// only first-shell G components, well inside that sphere, so a small strain changes none of them. +const double LARGEST_SPHERE_CUTOFF = 1.0e6; typedef double (*GridFunction)(const ModulePW::PW_Basis&, const int, const int, const int); @@ -72,7 +80,8 @@ double valence_density(const ModulePW::PW_Basis& pw, const int ix, const int iy, const double ax = ModuleBase::TWO_PI * ix / pw.nx; const double ay = ModuleBase::TWO_PI * iy / pw.ny; const double az = ModuleBase::TWO_PI * iz / pw.nz; - return 0.004 + 0.006 * (1.0 + 0.25 * std::sin(ax) + 0.15 * std::cos(ay + az)); + const double ayz = ay + az; + return 0.004 + 0.006 * (1.0 + 0.25 * std::sin(ax) + 0.15 * std::cos(ayz)); } double core_density(const ModulePW::PW_Basis& pw, const int ix, const int iy, const int iz) @@ -81,16 +90,25 @@ double core_density(const ModulePW::PW_Basis& pw, const int ix, const int iy, co return 0.002 * (1.0 + std::cos(az)); } +double kinetic_density(const ModulePW::PW_Basis& pw, const int ix, const int iy, const int iz) +{ + const double ay = ModuleBase::TWO_PI * iy / pw.ny; + return 0.003 * (1.0 + 0.2 * std::cos(ay)); +} + double perturbation(const ModulePW::PW_Basis& pw, const int ix, const int iy, const int iz) { const double ax = ModuleBase::TWO_PI * ix / pw.nx; const double ay = ModuleBase::TWO_PI * iy / pw.ny; - return std::sin(ax + ay) + 0.3 * std::cos(2.0 * ay); + const double axy = ax + ay; + const double ay2 = 2.0 * ay; + return std::sin(axy) + 0.3 * std::cos(ay2); } double x_only_density(const ModulePW::PW_Basis& pw, const int ix, const int iy, const int iz) { - return 0.01 + 0.003 * std::cos(ModuleBase::TWO_PI * ix / pw.nx); + const double ax = ModuleBase::TWO_PI * ix / pw.nx; + return 0.01 + 0.003 * std::cos(ax); } double zero_density(const ModulePW::PW_Basis& pw, const int ix, const int iy, const int iz) @@ -115,49 +133,174 @@ std::vector local_grid(const ModulePW::PW_Basis& pw, GridFunction f) return values; } -/// A Charge on pw whose spin channels and core density point into the given vectors. -void attach(Charge& chr, - ModulePW::PW_Basis& pw, - std::vector>& channels, - std::vector& rows, - std::vector& core) +/// Oblique lattice; z_sign = -1 gives its left-handed mirror image. +ModuleBase::Matrix3 oblique_lattice(const double z_sign) { - rows.clear(); - for (std::vector& channel: channels) + const double c3 = 2.0 * z_sign; + return ModuleBase::Matrix3(1.0, 0.0, 0.0, -0.5, std::sqrt(0.75), 0.0, 0.0, 0.0, c3); +} + +/// The lattice after the strain eps_ab = h: every lattice vector gets a_i[a] += h * a_i[b]. +ModuleBase::Matrix3 strained_lattice(const ModuleBase::Matrix3& lattice, const int a, const int b, const double h) +{ + double m[3][3] = {{lattice.e11, lattice.e12, lattice.e13}, + {lattice.e21, lattice.e22, lattice.e23}, + {lattice.e31, lattice.e32, lattice.e33}}; + for (int i = 0; i < 3; ++i) { - rows.push_back(channel.data()); + m[i][a] += h * m[i][b]; } - chr.rhopw = &pw; - chr.nrxx = pw.nrxx; - chr.nspin = static_cast(channels.size()); - chr.rho = rows.data(); - chr.rho_core = core.data(); + return ModuleBase::Matrix3(m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2], m[2][0], m[2][1], m[2][2]); } -/// rVV10 contribution for the given valence channels and core density on pw. -VxcResult rvv10_result(ModulePW::PW_Basis& pw, std::vector> channels, std::vector core) +/// Basis on the nx x ny x nz FFT grid of the given cell. +void setup_grid(ModulePW::PW_Basis& basis, + const ModuleBase::Matrix3& lattice, + const double cutoff, + const int nx, + const int ny, + const int nz) { + basis.initgrids(10.0, lattice, nx, ny, nz); + basis.initparameters(false, cutoff, 1, true); + basis.setuptransform(); + basis.collect_local_pw(); +} + +/// 5x7x9 grid: on two processes the z planes split 5 + 4, on four processes 3 + 2 + 2 + 2 against +/// libvdwxc's 3 + 3 + 3 + 0, which exercises the redistribution and an empty slab. Odd nx and ny leave +/// no Nyquist plane, so libvdwxc's result does not depend on the order in which it gets the axes. +void setup(ModulePW::PW_Basis& basis, const ModuleBase::Matrix3& lattice, const double cutoff) +{ + setup_grid(basis, lattice, cutoff, 5, 7, 9); +} + +/// Sets the rVV10 parameters of the XC runtime parameters for the lifetime of the object. +class Rvv10Parameters +{ + public: + Rvv10Parameters(const double b, const double c) + { + saved_ = XC_Functional::get_runtime_parameters(); + XCFunctionalParameters parameters = saved_; + parameters.rvv10_b = b; + parameters.rvv10_c = c; + XC_Functional::set_runtime_parameters(parameters); + } + ~Rvv10Parameters() + { + XC_Functional::set_runtime_parameters(saved_); + } + + private: + XCFunctionalParameters saved_; +}; + +/// A Charge on pw that owns its density components, core density and the G-space buffers of gradcorr. +class ChargeOnGrid +{ + public: + ChargeOnGrid(ModulePW::PW_Basis& pw, + const std::vector>& channels, + const std::vector& core) + { + channels_ = channels; + core_ = core; + const std::size_t ncomponents = channels.size(); + const std::vector> reciprocal(pw.npw); + rhog_.assign(ncomponents, reciprocal); + core_g_ = reciprocal; + for (std::size_t is = 0; is < ncomponents; ++is) + { + rows_.push_back(channels_[is].data()); + rhog_rows_.push_back(rhog_[is].data()); + } + chr.rhopw = &pw; + chr.nrxx = pw.nrxx; + chr.nspin = static_cast(ncomponents); + chr.rho = rows_.data(); + chr.rhog = rhog_rows_.data(); + chr.rho_core = core_.data(); + chr.rhog_core = core_g_.data(); + } + ~ChargeOnGrid() + { + chr.rho = nullptr; + chr.rhog = nullptr; + chr.rho_core = nullptr; + chr.rhog_core = nullptr; + chr.kin_r = nullptr; + } + ChargeOnGrid(const ChargeOnGrid&) = delete; + ChargeOnGrid& operator=(const ChargeOnGrid&) = delete; + Charge chr; - std::vector rows; - attach(chr, pw, channels, rows, core); - VxcResult result(0.0, 0.0, ModuleBase::matrix(chr.nspin, pw.nrxx)); - vdw::add_rvv10(RVV10_B, RVV10_C, chr, result); - chr.rho = nullptr; - chr.rho_core = nullptr; - return result; + + private: + std::vector> channels_; + std::vector rows_; + std::vector core_; + std::vector>> rhog_; + std::vector*> rhog_rows_; + std::vector> core_g_; +}; + +/// rVV10 contribution for the given valence channels and core density on pw. +VxcResult rvv10_result(ModulePW::PW_Basis& pw, + const std::vector>& channels, + const std::vector& core) +{ + const ChargeOnGrid charge(pw, channels, core); + double etxc = 0.0; + double vtxc = 0.0; + ModuleBase::matrix v(charge.chr.nspin, pw.nrxx); + vdw::add_rvv10(charge.chr, etxc, vtxc, v); + return VxcResult(etxc, vtxc, v); } -/// Oblique lattice; z_sign = -1 gives its left-handed mirror image. -ModuleBase::Matrix3 oblique_lattice(const double z_sign) +/// rVV10 stress beyond the XC diagonal term for the given density on pw. +ModuleBase::matrix rvv10_stress(ModulePW::PW_Basis& pw, + const std::vector>& channels, + const std::vector& core) { - const double c3 = 2.0 * z_sign; - return ModuleBase::Matrix3(1.0, 0.0, 0.0, -0.5, std::sqrt(0.75), 0.0, 0.0, 0.0, c3); + const ChargeOnGrid charge(pw, channels, core); + ModuleBase::matrix sigma(3, 3); + vdw::add_rvv10_stress(charge.chr, sigma); + return sigma; +} + +void expect_same_result(const VxcResult& actual, const VxcResult& expected, const double tolerance) +{ + EXPECT_NEAR(std::get<0>(actual), std::get<0>(expected), tolerance); + EXPECT_NEAR(std::get<1>(actual), std::get<1>(expected), tolerance); + const ModuleBase::matrix& v = std::get<2>(actual); + const ModuleBase::matrix& w = std::get<2>(expected); + ASSERT_EQ(v.nr, w.nr); + ASSERT_EQ(v.nc, w.nc); + for (int is = 0; is < v.nr; ++is) + { + for (int ir = 0; ir < v.nc; ++ir) + { + EXPECT_NEAR(v(is, ir), w(is, ir), tolerance); + } + } +} + +/// A functional evaluated with rVV10 equals the evaluation without it plus add_rvv10. +void expect_rvv10_added(const VxcResult& total, VxcResult semilocal, const Charge& chr) +{ + double& etxc = std::get<0>(semilocal); + double& vtxc = std::get<1>(semilocal); + ModuleBase::matrix& v = std::get<2>(semilocal); + vdw::add_rvv10(chr, etxc, vtxc, v); + expect_same_result(total, semilocal, ROUNDING_TOLERANCE); } class NonlocalXCTest : public testing::Test { protected: - /// 6x8x9 grid on an oblique cell, distributed over the processes of the test (5 + 4 planes on two) + Rvv10Parameters rvv10{RVV10_B, RVV10_C}; + /// grid on an oblique cell, distributed over the processes of the test ModulePW::PW_Basis pw{"cpu", "double"}; /// the same grid, complete on every process ModulePW::PW_Basis serial{"cpu", "double"}; @@ -168,42 +311,18 @@ class NonlocalXCTest : public testing::Test const ModuleBase::Matrix3 lattice = oblique_lattice(1.0); pw.initmpi(test_size, test_rank, MPI_COMM_WORLD); serial.initmpi(1, 0, MPI_COMM_SELF); - setup(pw, lattice); - setup(serial, lattice); - } - - static void setup(ModulePW::PW_Basis& basis, const ModuleBase::Matrix3& lattice) - { - basis.initgrids(10.0, lattice, 6, 8, 9); - basis.initparameters(false, 4.0, 1, true); - basis.setuptransform(); - basis.collect_local_pw(); + setup(pw, lattice, BASIS_CUTOFF); + setup(serial, lattice, BASIS_CUTOFF); } }; -TEST(NonlocalXCSelectionTest, ShortNameSelectsRvv10) -{ - XC_Functional::set_xc_type("rVV10"); - EXPECT_EQ(XC_Functional::get_func_type(), 2); - const std::vector expected_id = {XC_GGA_X_RPW86, XC_GGA_C_PBE}; - EXPECT_EQ(XC_Functional::get_func_id(), expected_id); - EXPECT_DOUBLE_EQ(XC_Functional::get_nlc_b(), RVV10_B); - { - XC_Functional::ScopedType semilocal("PBE"); - EXPECT_EQ(XC_Functional::get_nlc_b(), 0.0); - } - EXPECT_DOUBLE_EQ(XC_Functional::get_nlc_b(), RVV10_B); - XC_Functional::set_xc_type("PBE"); - EXPECT_EQ(XC_Functional::get_nlc_b(), 0.0); -} - TEST_F(NonlocalXCTest, MatchesDirectLibvdwxc) { const std::vector valence = local_grid(serial, valence_density); const std::vector core = local_grid(serial, core_density); const VxcResult result = rvv10_result(serial, {valence}, core); - // reference: libvdwxc on the total density with the plane-wave gradient, in Hartree units + // reference: libvdwxc on the total density in the ABACUS axis order, Hartree units std::vector total(serial.nrxx); for (int ir = 0; ir < serial.nrxx; ++ir) { @@ -247,17 +366,18 @@ TEST_F(NonlocalXCTest, MatchesDirectLibvdwxc) std::vector divergence(serial.nrxx); XC_Functional::grad_dot(grad.data(), divergence.data(), &serial, serial.tpiba); - const double expected_energy = ModuleBase::e2 * energy_ha; - EXPECT_NEAR(std::get<0>(result), expected_energy, ROUNDING_TOLERANCE); + ModuleBase::matrix v(1, serial.nrxx); double vtxc = 0.0; for (int ir = 0; ir < serial.nrxx; ++ir) { const double potential = ModuleBase::e2 * (dedn[ir] - divergence[ir]); - EXPECT_NEAR(std::get<2>(result)(0, ir), potential, ROUNDING_TOLERANCE); + v(0, ir) = potential; vtxc += potential * valence[ir]; } - const double expected_vtxc = vtxc * serial.omega / serial.nxyz; - EXPECT_NEAR(std::get<1>(result), expected_vtxc, ROUNDING_TOLERANCE); + const double etxc = ModuleBase::e2 * energy_ha; + vtxc *= serial.omega / serial.nxyz; + const VxcResult expected(etxc, vtxc, v); + expect_same_result(result, expected, ROUNDING_TOLERANCE); } TEST_F(NonlocalXCTest, SpinChannelsShareTheTotalDensity) @@ -339,7 +459,7 @@ TEST_F(NonlocalXCTest, ObliqueCellMatchesEquivalentOrthogonalCell) ModulePW::PW_Basis orthogonal{"cpu", "double"}; orthogonal.initmpi(1, 0, MPI_COMM_SELF); const ModuleBase::Matrix3 rectangle(std::sqrt(0.75), 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 2.0); - setup(orthogonal, rectangle); + setup(orthogonal, rectangle, BASIS_CUTOFF); ASSERT_NEAR(serial.omega, orthogonal.omega, ROUNDING_TOLERANCE); const std::vector density = local_grid(serial, x_only_density); @@ -359,66 +479,209 @@ TEST_F(NonlocalXCTest, LeftHandedCellGivesTheMirrorImageResult) // otherwise flip the sign of the energy. ModulePW::PW_Basis mirror{"cpu", "double"}; mirror.initmpi(1, 0, MPI_COMM_SELF); - setup(mirror, oblique_lattice(-1.0)); + const ModuleBase::Matrix3 mirrored = oblique_lattice(-1.0); + setup(mirror, mirrored, BASIS_CUTOFF); ASSERT_LT(mirror.latvec.Det(), 0.0); const std::vector valence = local_grid(serial, valence_density); const std::vector core = local_grid(serial, core_density); const VxcResult right_handed = rvv10_result(serial, {valence}, core); const VxcResult left_handed = rvv10_result(mirror, {valence}, core); - EXPECT_NEAR(std::get<0>(left_handed), std::get<0>(right_handed), ROUNDING_TOLERANCE); - EXPECT_NEAR(std::get<1>(left_handed), std::get<1>(right_handed), ROUNDING_TOLERANCE); - for (int ir = 0; ir < serial.nrxx; ++ir) + expect_same_result(left_handed, right_handed, ROUNDING_TOLERANCE); +} + +TEST_F(NonlocalXCTest, WorkspaceFollowsGridCellAndProcessChanges) +{ + // The libvdwxc object is reused between calls; switching the process layout, the cell, the grid and + // back must reproduce the earlier results. + ModulePW::PW_Basis orthogonal{"cpu", "double"}; + orthogonal.initmpi(1, 0, MPI_COMM_SELF); + const ModuleBase::Matrix3 rectangle(std::sqrt(0.75), 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 2.0); + setup(orthogonal, rectangle, BASIS_CUTOFF); + ModulePW::PW_Basis finer{"cpu", "double"}; + finer.initmpi(1, 0, MPI_COMM_SELF); + const ModuleBase::Matrix3 lattice = oblique_lattice(1.0); + setup_grid(finer, lattice, BASIS_CUTOFF, 5, 7, 11); + const std::vector valence_finer = local_grid(finer, valence_density); + const std::vector core_finer = local_grid(finer, core_density); + const std::vector valence = local_grid(serial, valence_density); + const std::vector core = local_grid(serial, core_density); + const std::vector valence_local = local_grid(pw, valence_density); + const std::vector core_local = local_grid(pw, core_density); + const VxcResult first = rvv10_result(serial, {valence}, core); + const VxcResult repeated = rvv10_result(serial, {valence}, core); + const VxcResult distributed = rvv10_result(pw, {valence_local}, core_local); + const VxcResult other_cell = rvv10_result(orthogonal, {valence}, core); + const VxcResult other_grid = rvv10_result(finer, {valence_finer}, core_finer); + const VxcResult again = rvv10_result(serial, {valence}, core); + const VxcResult other_grid_again = rvv10_result(finer, {valence_finer}, core_finer); + expect_same_result(repeated, first, 0.0); + expect_same_result(again, first, ROUNDING_TOLERANCE); + expect_same_result(other_grid_again, other_grid, ROUNDING_TOLERANCE); + EXPECT_NEAR(std::get<0>(distributed), std::get<0>(first), ROUNDING_TOLERANCE); + EXPECT_GT(std::abs(std::get<0>(other_cell) - std::get<0>(first)), 1.0e-8); +} + +TEST_F(NonlocalXCTest, DistributedStressMatchesCompleteGrid) +{ + const std::vector valence = local_grid(pw, valence_density); + const std::vector core = local_grid(pw, core_density); + const std::vector valence_complete = local_grid(serial, valence_density); + const std::vector core_complete = local_grid(serial, core_density); + const ModuleBase::matrix distributed = rvv10_stress(pw, {valence}, core); + const ModuleBase::matrix complete = rvv10_stress(serial, {valence_complete}, core_complete); + EXPECT_GT(std::abs(complete(0, 0)), 1.0e-10); + for (int a = 0; a < 3; ++a) + { + for (int b = 0; b < 3; ++b) + { + EXPECT_NEAR(distributed(a, b), complete(a, b), ROUNDING_TOLERANCE); + } + } +} + +TEST_F(NonlocalXCTest, StressIsTheStrainDerivativeOfTheEnergy) +{ + // A strain keeps the grid and every G component of the density, so the stress is the exact derivative + // of the discrete energy: sigma_ab = -(1/omega) dE/d(eps_ab) at a fixed electron number. + ModulePW::PW_Basis box{"cpu", "double"}; + box.initmpi(1, 0, MPI_COMM_SELF); + const ModuleBase::Matrix3 lattice = oblique_lattice(1.0); + setup(box, lattice, LARGEST_SPHERE_CUTOFF); + const std::vector density = local_grid(box, valence_density); + const std::vector no_core = local_grid(box, zero_density); + const VxcResult reference = rvv10_result(box, {density}, no_core); + const ModuleBase::matrix extra = rvv10_stress(box, {density}, no_core); + // the XC diagonal term that ABACUS adds from etxc and vtxc + const double diagonal = -(std::get<0>(reference) - std::get<1>(reference)) / box.omega; + + const int components[4][2] = {{0, 0}, {2, 2}, {0, 1}, {1, 2}}; + for (const int* ab: components) { - EXPECT_NEAR(std::get<2>(left_handed)(0, ir), std::get<2>(right_handed)(0, ir), ROUNDING_TOLERANCE); + const int a = ab[0]; + const int b = ab[1]; + double energy[2] = {0.0, 0.0}; + for (int side = 0; side < 2; ++side) + { + const double h = (side == 0) ? STRAIN_STEP : -STRAIN_STEP; + ModulePW::PW_Basis strained{"cpu", "double"}; + strained.initmpi(1, 0, MPI_COMM_SELF); + const ModuleBase::Matrix3 strained_cell = strained_lattice(lattice, a, b, h); + setup(strained, strained_cell, LARGEST_SPHERE_CUTOFF); + // keep the electron number: the density scales with the inverse volume ratio + const double volume_ratio = strained.omega / box.omega; + std::vector scaled = density; + for (double& value: scaled) + { + value /= volume_ratio; + } + const VxcResult strained_result = rvv10_result(strained, {scaled}, no_core); + energy[side] = std::get<0>(strained_result); + } + const double finite_difference = -(energy[0] - energy[1]) / (2.0 * STRAIN_STEP * box.omega); + const double analytic = extra(a, b) + ((a == b) ? diagonal : 0.0); + const double tolerance = STRAIN_TOLERANCE * std::abs(analytic) + 1.0e-12; + SCOPED_TRACE(testing::Message() << "component " << a << b); + EXPECT_NEAR(analytic, finite_difference, tolerance); } } -TEST_F(NonlocalXCTest, VxcOfRvv10AddsTheNonlocalCorrelation) +TEST_F(NonlocalXCTest, VxcAddsRvv10ToEveryFunctionalPath) { - // XC_Functional::v_xc for dft_functional RVV10 equals the semilocal rPW86+PBE result plus add_rvv10. + // v_xc and v_xc_meta with vdw_method rvv10 equal the semilocal result plus add_rvv10, for native PBE + // (final return), a libxc GGA (libxc branch), noncollinear PBE (NCGGA exit) and SCAN (v_xc_meta). const std::vector valence = local_grid(pw, valence_density); - std::vector core = local_grid(pw, core_density); - std::vector> channels(2, std::vector(pw.nrxx)); + const std::vector core = local_grid(pw, core_density); + const std::vector zeros(pw.nrxx); + std::vector> collinear(2, zeros); + std::vector> noncollinear(4, zeros); for (int ir = 0; ir < pw.nrxx; ++ir) { - channels[0][ir] = 0.7 * valence[ir]; - channels[1][ir] = valence[ir] - channels[0][ir]; + collinear[0][ir] = 0.7 * valence[ir]; + collinear[1][ir] = valence[ir] - collinear[0][ir]; + noncollinear[0][ir] = valence[ir]; + noncollinear[1][ir] = 0.1 * valence[ir]; + noncollinear[2][ir] = -0.05 * valence[ir]; + noncollinear[3][ir] = 0.2 * valence[ir]; } - Charge chr; - std::vector rows; - attach(chr, pw, channels, rows, core); UnitCell ucell; ucell.omega = pw.omega; ucell.tpiba = pw.tpiba; - const int nspin = 2; - const bool domag = false; - const int gga_grad = 0; + ucell.magnet.lsign_ = false; + const bool domag_z = false; const double hybrid_alpha = 0.0; const double hse_omega = 0.0; - XC_Functional::set_xc_type("RVV10"); - const VxcResult total - = XC_Functional::v_xc(pw.nrxx, &chr, &ucell, nspin, domag, domag, gga_grad, hybrid_alpha, hse_omega); - VxcResult expected; - { - XC_Functional::ScopedType semilocal("GGA_X_RPW86+GGA_C_PBE"); - expected = XC_Functional::v_xc(pw.nrxx, &chr, &ucell, nspin, domag, domag, gga_grad, hybrid_alpha, hse_omega); - } - vdw::add_rvv10(RVV10_B, RVV10_C, chr, expected); - XC_Functional::set_xc_type("PBE"); - chr.rho = nullptr; - chr.rho_core = nullptr; - - EXPECT_NEAR(std::get<0>(total), std::get<0>(expected), ROUNDING_TOLERANCE); - EXPECT_NEAR(std::get<1>(total), std::get<1>(expected), ROUNDING_TOLERANCE); - for (int is = 0; is < nspin; ++is) + const int ncases = 3; + const char* functionals[ncases] = {"PBE", "GGA_X_RPW86+GGA_C_PBE", "PBE"}; + const std::vector>* densities[ncases] = {&collinear, &collinear, &noncollinear}; + const bool domags[ncases] = {false, false, true}; + const int gga_grads[ncases] = {0, 0, 2}; + for (int icase = 0; icase < ncases; ++icase) { - for (int ir = 0; ir < pw.nrxx; ++ir) + SCOPED_TRACE(testing::Message() << functionals[icase] << " with nspin " << densities[icase]->size()); + XC_Functional::ScopedType scope(functionals[icase]); + const ChargeOnGrid charge(pw, *densities[icase], core); + const int nspin = charge.chr.nspin; + const bool domag = domags[icase]; + const int gga_grad = gga_grads[icase]; + const VxcResult total = XC_Functional::v_xc(pw.nrxx, + &charge.chr, + &ucell, + nspin, + domag, + domag_z, + gga_grad, + hybrid_alpha, + hse_omega); + VxcResult semilocal; { - EXPECT_NEAR(std::get<2>(total)(is, ir), std::get<2>(expected)(is, ir), ROUNDING_TOLERANCE); + Rvv10Parameters off(0.0, 0.0); + semilocal = XC_Functional::v_xc(pw.nrxx, + &charge.chr, + &ucell, + nspin, + domag, + domag_z, + gga_grad, + hybrid_alpha, + hse_omega); } + expect_rvv10_added(total, semilocal, charge.chr); + } + + XC_Functional::ScopedType scan("SCAN"); + const std::vector> unpolarized(1, valence); + std::vector kinetic = local_grid(pw, kinetic_density); + std::vector kinetic_rows(1, kinetic.data()); + ChargeOnGrid charge(pw, unpolarized, core); + charge.chr.kin_r = kinetic_rows.data(); + const std::vector func_id = XC_Functional::get_func_id(); + const int nspin = 1; + typedef std::tuple MetaResult; + const MetaResult total = XC_Functional_Libxc::v_xc_meta(func_id, + pw.nrxx, + pw.omega, + pw.tpiba, + &charge.chr, + nspin, + hybrid_alpha, + hse_omega); + MetaResult semilocal; + { + Rvv10Parameters off(0.0, 0.0); + semilocal = XC_Functional_Libxc::v_xc_meta(func_id, + pw.nrxx, + pw.omega, + pw.tpiba, + &charge.chr, + nspin, + hybrid_alpha, + hse_omega); } + const VxcResult total_xc(std::get<0>(total), std::get<1>(total), std::get<2>(total)); + const VxcResult semilocal_xc(std::get<0>(semilocal), std::get<1>(semilocal), std::get<2>(semilocal)); + expect_rvv10_added(total_xc, semilocal_xc, charge.chr); } } // namespace diff --git a/source/source_hamilt/module_vdw/test/vdw_test.cpp b/source/source_hamilt/module_vdw/test/vdw_test.cpp index 71e3682ce62..5e0105e1983 100644 --- a/source/source_hamilt/module_vdw/test/vdw_test.cpp +++ b/source/source_hamilt/module_vdw/test/vdw_test.cpp @@ -264,6 +264,18 @@ TEST_F(vdwd2Test, WrongVdwType) std::string output = testing::internal::GetCapturedStdout(); } +TEST_F(vdwd2Test, Rvv10HasNoPairwiseTerm) +{ + input.vdw_method = "rvv10"; +#ifdef __LIBVDWXC + EXPECT_TRUE(vdw::make_vdw(ucell, input) == nullptr); +#else + testing::internal::CaptureStdout(); + EXPECT_EXIT(vdw::make_vdw(ucell, input);, ::testing::ExitedWithCode(1), ""); + std::string output = testing::internal::GetCapturedStdout(); +#endif +} + TEST_F(vdwd2Test, D2ReadFile) { input.vdw_C6_file = "c6.txt"; diff --git a/source/source_hamilt/module_vdw/vdw.cpp b/source/source_hamilt/module_vdw/vdw.cpp index 2bcad9de68d..ba1f380d441 100644 --- a/source/source_hamilt/module_vdw/vdw.cpp +++ b/source/source_hamilt/module_vdw/vdw.cpp @@ -94,6 +94,21 @@ std::unique_ptr make_vdw(const UnitCell& ucell, const Input_para& input, st "Rebuild ABACUS with -DENABLE_DFTD4=ON."); #endif } + else if (input.vdw_method == "rvv10") + { +#ifndef __LIBVDWXC + ModuleBase::WARNING_QUIT("ModuleHamiltGeneral::ModuleVDW::make_vdw", + "rVV10 support was not enabled at build time. " + "Rebuild ABACUS with -DENABLE_LIBVDWXC=ON."); +#endif + // rVV10 is evaluated inside XC_Functional, so there is no pairwise term here + if (plog != nullptr) + { + *plog << "\nrVV10 nonlocal correlation (libvdwxc): b=" << input.vdw_rvv10_b << " C=" << input.vdw_rvv10_c + << "\nincluded in the exchange-correlation energy, potential, forces and stress" << std::endl; + } + return nullptr; + } else if (input.vdw_method != "none") { ModuleBase::WARNING_QUIT("ModuleHamiltGeneral::ModuleVDW::make_vdw", diff --git a/source/source_hamilt/module_xc/libxc_pot.cpp b/source/source_hamilt/module_xc/libxc_pot.cpp index eb4275cf5e3..53f04ad5937 100644 --- a/source/source_hamilt/module_xc/libxc_pot.cpp +++ b/source/source_hamilt/module_xc/libxc_pot.cpp @@ -9,6 +9,9 @@ #ifdef __EXX #include "source_hamilt/module_xc/exx_info.h" #endif +#ifdef __LIBVDWXC +#include "source_hamilt/module_vdw/nonlocal_xc.h" +#endif #include @@ -768,6 +771,9 @@ std::tuple XC_Functional_Li etxc *= omega / chr->rhopw->nxyz; vtxc *= omega / chr->rhopw->nxyz; +#ifdef __LIBVDWXC + vdw::add_rvv10(*chr, etxc, vtxc, v); +#endif XC_Functional_Libxc::finish_func(funcs); diff --git a/source/source_hamilt/module_xc/test/CMakeLists.txt b/source/source_hamilt/module_xc/test/CMakeLists.txt index 606dbb883ea..1b639bac296 100644 --- a/source/source_hamilt/module_xc/test/CMakeLists.txt +++ b/source/source_hamilt/module_xc/test/CMakeLists.txt @@ -165,5 +165,6 @@ foreach(xc_gradient_test MODULE_HAMILT_XCTest_GRADCORR MODULE_HAMILT_XCTest_LAPL ../libxc_pot.cpp ../libxc_tools.cpp ../../../source_base/timer.cpp) endforeach() -# MODULE_HAMILT_XCTest_VXC compiles xc_pot.cpp against a mocked PW basis, without the libvdwxc adapter. +# The targets in this directory compile xc_pot.cpp or libxc_pot.cpp against a mocked PW basis, without the +# libvdwxc adapter. abacus_disable_feature_definitions(__LIBVDWXC) diff --git a/source/source_hamilt/module_xc/xc_functional.cpp b/source/source_hamilt/module_xc/xc_functional.cpp index e857d4a6474..72504150b49 100644 --- a/source/source_hamilt/module_xc/xc_functional.cpp +++ b/source/source_hamilt/module_xc/xc_functional.cpp @@ -17,8 +17,6 @@ int XC_Functional::func_type = 0; bool XC_Functional::ked_flag = false; bool XC_Functional::need_laplacian = false; bool XC_Functional::use_libxc = true; -double XC_Functional::nlc_b = 0.0; -double XC_Functional::nlc_c = 0.0; double XC_Functional::hybrid_alpha = 0.25; double XC_Functional::hse_omega = 0.0; XCFunctionalParameters XC_Functional::runtime_parameters; @@ -29,8 +27,7 @@ XC_Functional::ScopedType::ScopedType(const std::string& functional) ked_flag_(XC_Functional::ked_flag), need_laplacian_(XC_Functional::need_laplacian), use_libxc_(XC_Functional::use_libxc), hybrid_alpha_(XC_Functional::hybrid_alpha), hse_omega_(XC_Functional::hse_omega), runtime_parameters_(XC_Functional::runtime_parameters), - scaling_factor_xc_(XC_Functional::scaling_factor_xc), - nlc_b_(XC_Functional::nlc_b), nlc_c_(XC_Functional::nlc_c) + scaling_factor_xc_(XC_Functional::scaling_factor_xc) { try { @@ -62,9 +59,9 @@ void XC_Functional::ScopedType::restore() noexcept XC_Functional::runtime_parameters.exx_erfc_alpha.swap(runtime_parameters_.exx_erfc_alpha); XC_Functional::runtime_parameters.xc_exch_ext.swap(runtime_parameters_.xc_exch_ext); XC_Functional::runtime_parameters.xc_corr_ext.swap(runtime_parameters_.xc_corr_ext); + XC_Functional::runtime_parameters.rvv10_b = runtime_parameters_.rvv10_b; + XC_Functional::runtime_parameters.rvv10_c = runtime_parameters_.rvv10_c; XC_Functional::scaling_factor_xc.swap(scaling_factor_xc_); - XC_Functional::nlc_b = nlc_b_; - XC_Functional::nlc_c = nlc_c_; } void XC_Functional::set_hybrid_alpha(const double alpha_in) @@ -128,8 +125,6 @@ void XC_Functional::set_xc_type(const std::string xc_func_in) func_id.clear(); scaling_factor_xc.clear(); // added by jghan, 2024-07-07 - nlc_b = 0.0; - nlc_c = 0.0; std::string xc_func = xc_func_in; std::transform(xc_func.begin(), xc_func.end(), xc_func.begin(), (::toupper)); if( xc_func == "LDA" || xc_func == "PZ" || xc_func == "SLAPZNOGXNOGC") //SLA+PZ @@ -342,20 +337,6 @@ void XC_Functional::set_xc_type(const std::string xc_func_in) func_type = 4; use_libxc = true; } - else if (xc_func == "RVV10") - { - // rPW86 exchange, PBE correlation and the rVV10 nonlocal correlation evaluated by libvdwxc, - // R. Sabatini, T. Gorni and S. de Gironcoli, Phys. Rev. B 87, 041108(R) (2013) -#ifndef __LIBVDWXC - ModuleBase::WARNING_QUIT("set_xc_type", "to use RVV10, compile with ENABLE_LIBVDWXC=ON"); -#endif - func_id.push_back(XC_GGA_X_RPW86); - func_id.push_back(XC_GGA_C_PBE); - func_type = 2; - use_libxc = true; - nlc_b = 6.3; - nlc_c = 0.0093; - } #endif else { @@ -428,9 +409,9 @@ void XC_Functional::set_xc_type(const std::string xc_func_in) if(xc_func == "SCAN" || xc_func == "HSE" || xc_func == "SCAN0" || xc_func == "MULLER" || xc_func == "POWER" || xc_func == "WP22" || xc_func == "CWP22" || xc_func == "LC_PBE" || xc_func == "LC_WPBE" || xc_func == "LRC_WPBE" || - xc_func == "LRC_PBEH" || xc_func == "CAM_PBEH" || xc_func == "RVV10") + xc_func == "LRC_PBEH" || xc_func == "CAM_PBEH") { - ModuleBase::WARNING_QUIT("set_xc_type","to use SCAN, SCAN0, HSE, long-range corrected (LC_PBE, LC_WPBE...), CAM_PBEH or RVV10 LIBXC is required"); + ModuleBase::WARNING_QUIT("set_xc_type","to use SCAN, SCAN0, HSE, long-range corrected (LC_PBE, LC_WPBE...) or CAM_PBEH LIBXC is required"); } use_libxc = false; #endif @@ -464,10 +445,6 @@ std::string XC_Functional::output_info() } } XC_Functional_Libxc::finish_func(funcs); - if (nlc_b > 0.0) - { - ss << " Nonlocal correlation: rVV10 (libvdwxc), b = " << nlc_b << ", C = " << nlc_c << std::endl; - } return ss.str(); } else diff --git a/source/source_hamilt/module_xc/xc_functional.h b/source/source_hamilt/module_xc/xc_functional.h index 8ed66157327..30f72736839 100644 --- a/source/source_hamilt/module_xc/xc_functional.h +++ b/source/source_hamilt/module_xc/xc_functional.h @@ -39,6 +39,9 @@ struct XCFunctionalParameters std::vector exx_erfc_alpha = {"default"}; std::vector xc_exch_ext; std::vector xc_corr_ext; + /// rVV10 nonlocal correlation of vdw_method rvv10, added to any functional; rvv10_b = 0 means off + double rvv10_b = 0.0; + double rvv10_c = 0.0; }; class XC_Functional @@ -126,8 +129,6 @@ class XC_Functional double hse_omega_; XCFunctionalParameters runtime_parameters_; std::map scaling_factor_xc_; - double nlc_b_; - double nlc_c_; }; // For hybrid functional @@ -168,12 +169,6 @@ class XC_Functional return need_laplacian; }; - /// rVV10 parameter b of the nonlocal correlation (libxc's nlc_b); 0 if the functional has none - static double get_nlc_b() - { - return nlc_b; - }; - /// Usually in exx caculation, the first SCF loop should be converged with PBE static void set_xc_first_loop(const UnitCell& ucell); @@ -186,9 +181,6 @@ class XC_Functional static bool ked_flag; // whether the functional has kinetic energy density static bool need_laplacian; // whether any functional needs Laplacian of density static bool use_libxc; - // rVV10 nonlocal correlation parameters b and C; nlc_b = 0 means no nonlocal correlation - static double nlc_b; - static double nlc_c; // exx_hybrid_alpha for mixing exx in hybrid functional: static double hybrid_alpha; diff --git a/source/source_hamilt/module_xc/xc_pot.cpp b/source/source_hamilt/module_xc/xc_pot.cpp index e397533f8f2..2680289a85a 100644 --- a/source/source_hamilt/module_xc/xc_pot.cpp +++ b/source/source_hamilt/module_xc/xc_pot.cpp @@ -53,11 +53,10 @@ std::tuple XC_Functional::v_xc( hybrid_alpha, hse_omega); #ifdef __LIBVDWXC - // the nonlocal correlation of the functional; RVV10 always takes this libxc branch - if (nlc_b > 0.0) - { - vdw::add_rvv10(nlc_b, nlc_c, *chr, etxc_vtxc_v); - } + double& etxc = std::get<0>(etxc_vtxc_v); + double& vtxc = std::get<1>(etxc_vtxc_v); + ModuleBase::matrix& v = std::get<2>(etxc_vtxc_v); + vdw::add_rvv10(*chr, etxc, vtxc, v); #endif return etxc_vtxc_v; #else @@ -68,7 +67,15 @@ std::tuple XC_Functional::v_xc( // Evaluate the regularized mapped-density local-collinear graph. if (nspin == 4 && (domag || domag_z) && gga_grad == 2) { - return ModuleXC::NCGGA_SF_Builtin::v_xc_ncgga_sf_builtin(nrxx, ucell->omega, ucell->tpiba, chr); + std::tuple etxc_vtxc_v + = ModuleXC::NCGGA_SF_Builtin::v_xc_ncgga_sf_builtin(nrxx, ucell->omega, ucell->tpiba, chr); +#ifdef __LIBVDWXC + double& etxc = std::get<0>(etxc_vtxc_v); + double& vtxc = std::get<1>(etxc_vtxc_v); + ModuleBase::matrix& v = std::get<2>(etxc_vtxc_v); + vdw::add_rvv10(*chr, etxc, vtxc, v); +#endif + return etxc_vtxc_v; } ModuleBase::timer::start("XC_Functional", "v_xc"); @@ -217,6 +224,9 @@ std::tuple XC_Functional::v_xc( #endif etxc *= ucell->omega / chr->rhopw->nxyz; vtxc *= ucell->omega / chr->rhopw->nxyz; +#ifdef __LIBVDWXC + vdw::add_rvv10(*chr, etxc, vtxc, v); +#endif ModuleBase::timer::end("XC_Functional", "v_xc"); return std::make_tuple(etxc, vtxc, std::move(v)); diff --git a/source/source_io/module_parameter/input_parameter.h b/source/source_io/module_parameter/input_parameter.h index 1c624aa5a02..c9582d6eb7d 100644 --- a/source/source_io/module_parameter/input_parameter.h +++ b/source/source_io/module_parameter/input_parameter.h @@ -604,7 +604,7 @@ struct Input_para // vdw // Peize Lin add 2014-03-31, jiyy update 2019-08-01 // ========================================================== - std::string vdw_method = "none"; ///< the method of calculating vdw (none; d2; d3_0; d3_bj; d4) + std::string vdw_method = "none"; ///< the method of calculating vdw (none; d2; d3_0; d3_bj; d4; rvv10) std::string vdw_s6 = "default"; ///< scale parameter of d2/d3_0/d3_bj std::string vdw_s8 = "default"; ///< scale parameter of d3_0/d3_bj std::string vdw_a1 = "default"; ///< damping parameter of d3_0/d3_bj @@ -625,6 +625,8 @@ struct Input_para std::string vdw_cn_thr_unit = "Bohr"; ///< unit of cn_thr, Bohr or Angstrom std::string vdw_d4_xc = "default"; ///< functional name passed to DFT-D4 std::string vdw_d4_model = "d4"; ///< DFT-D4 dispersion model (d4 or d4s) + double vdw_rvv10_b = 6.3; ///< rVV10 parameter b + double vdw_rvv10_c = 0.0093; ///< rVV10 parameter C ModuleBase::Vector3 vdw_cutoff_period = {3, 3, 3}; ///< periods of periodic structure // ============== #Parameters (15.exx) ==================== diff --git a/source/source_io/module_parameter/read_inp_estruc.cpp b/source/source_io/module_parameter/read_inp_estruc.cpp index f89ce099ccc..8867fb9e268 100644 --- a/source/source_io/module_parameter/read_inp_estruc.cpp +++ b/source/source_io/module_parameter/read_inp_estruc.cpp @@ -285,16 +285,11 @@ Then the user has to correct the input file and restart the calculation.)"; * If LIBXC is available, additional short-hand names of hybrid functionals are supported: HSE(referred to HSE06), B3LYP, LC_PBE, LC_WPBE, LRC_WPBE, LRC_WPBEH, CAM_PBEH, WP22, CWP22, MULLER (equivalent with POWER) * Hybrid meta-GGA functionals * SCAN0 (require LIBXC) -* Nonlocal correlation functionals - * RVV10: rPW86 exchange, PBE correlation and rVV10 nonlocal correlation (require LIBXC and LIBVDWXC; stress, DFPT and LR-TDDFT forces are not supported) 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'.)"; item.default_value = "Used the same as DFT functional as specified in the pseudopotential files."; item.unit = ""; read_sync_string(input.dft_functional); - item.domain.add_requirement("dft_functional in [rvv10, RVV10, rVV10]", - "cal_stress==false and esolver_type!=dfpt and (cal_force==false or " - "(esolver_type!=lr and esolver_type!=ks-lr))"); this->add_item(item); } { diff --git a/source/source_io/module_parameter/read_inp_model.cpp b/source/source_io/module_parameter/read_inp_model.cpp index 961002946cf..85ab1fb4c73 100644 --- a/source/source_io/module_parameter/read_inp_model.cpp +++ b/source/source_io/module_parameter/read_inp_model.cpp @@ -263,8 +263,12 @@ void ReadInput::item_model() // vdW Correction { Input_Item item("vdw_method"); - item.domain.set_choices({"none", "d2", "d3_0", "d3_bj", "d4"}); - item.annotation = "the method of calculating vdw (none ; d2 ; d3_0 ; d3_bj ; d4)"; + item.domain.set_choices({"none", "d2", "d3_0", "d3_bj", "d4", "rvv10"}); + // The rVV10 response kernel needed by DFPT and LR-TDDFT forces is not implemented. + item.domain.add_requirement( + "vdw_method==rvv10", + "esolver_type!=dfpt and (cal_force==false or (esolver_type!=lr and esolver_type!=ks-lr))"); + item.annotation = "the method of calculating vdw (none ; d2 ; d3_0 ; d3_bj ; d4 ; rvv10)"; item.category = "vdW correction"; item.type = "String"; item.description = R"(Specifies the method used for Van der Waals (VdW) correction. Available options are: @@ -272,6 +276,7 @@ void ReadInput::item_model() * d3_0: Grimme's DFT-D3(0) dispersion correction method (zero-damping) * d3_bj: Grimme's DFTD3(BJ) dispersion correction method (BJ-damping) * d4: Grimme's DFT-D4 dispersion correction method using the external DFT-D4 library +* rvv10: rVV10 nonlocal correlation evaluated self-consistently with the external libvdwxc library and added to the exchange-correlation energy, potential, forces and stress of dft_functional; see vdw_rvv10_b and vdw_rvv10_c. DFPT and LR-TDDFT forces are not supported, and LR-TDDFT spectra use the kernel selected by xc_kernel without the rVV10 response * none: no vdW correction [NOTE] ABACUS automatically loads DFT-D3 parameters for supported functionals according to dft_functional setting. Individual user values overwrite the corresponding tabulated values. Setting all four of vdw_s6, vdw_s8, vdw_a1 and vdw_a2 defines a fully custom set and bypasses functional lookup.)"; @@ -309,6 +314,34 @@ Available options are: read_sync_string(input.vdw_d4_model); this->add_item(item); } + { + Input_Item item("vdw_rvv10_b"); + item.domain.set_rule("value>0"); + item.annotation = "rVV10 parameter b"; + item.category = "vdW correction"; + item.type = "Real"; + item.description + = R"(Parameter b of rVV10, which sets the short-range damping and depends on dft_functional. Published values are 6.3 for rPW86 exchange with PBE correlation (dft_functional GGA_X_RPW86+GGA_C_PBE, the original rVV10), 15.7 for SCAN and 11.95 for r2SCAN.)"; + item.default_value = "6.3"; + item.unit = ""; + item.set_availability("vdw_method==rvv10"); + read_sync_double(input.vdw_rvv10_b); + this->add_item(item); + } + { + Input_Item item("vdw_rvv10_c"); + item.domain.set_rule("value>=0"); + item.annotation = "rVV10 parameter C"; + item.category = "vdW correction"; + item.type = "Real"; + item.description = "Parameter C of rVV10, which sets the gradient dependence of the local polarizability; " + "0.0093 in the original rVV10 and in the SCAN and r2SCAN parametrizations."; + item.default_value = "0.0093"; + item.unit = ""; + item.set_availability("vdw_method==rvv10"); + read_sync_double(input.vdw_rvv10_c); + this->add_item(item); + } { Input_Item item("vdw_s6"); item.annotation = "scale parameter of d2/d3_0/d3_bj"; @@ -547,7 +580,8 @@ Namely, each line contains the element name and the corresponding parameter.)"; } }; item.check_value = [](const Input_Item& item, const Parameter& para) { - if (std::stod(para.input.vdw_cutoff_radius) <= 0 && para.input.vdw_method != "none") + if (std::stod(para.input.vdw_cutoff_radius) <= 0 && para.input.vdw_method != "none" + && para.input.vdw_method != "rvv10") { ModuleBase::WARNING_QUIT("ReadInput", "vdw_cutoff_radius <= 0 is not allowd"); } diff --git a/source/source_io/test_serial/read_input_test.cpp b/source/source_io/test_serial/read_input_test.cpp index 9e49f442f4d..bfa2cc68ec1 100644 --- a/source/source_io/test_serial/read_input_test.cpp +++ b/source/source_io/test_serial/read_input_test.cpp @@ -212,14 +212,22 @@ TEST_F(InputTest, RejectAutoDevice) EXPECT_TRUE(std::remove("./auto_device_INPUT") == 0); } -TEST_F(InputTest, Rvv10RejectsStressDfptAndLrForces) +TEST_F(InputTest, VdwMethodRvv10) { + const std::string input_file = testing::TempDir() + "rvv10_INPUT"; Parameter param; - EXPECT_NO_THROW(read_parameters("rvv10_INPUT", "dft_functional rvv10\n", param)); - const std::string reason = "Requires cal_stress==false and esolver_type!=dfpt"; - expect_invalid_input("rvv10_INPUT", "dft_functional rVV10\ncalculation cell-relax\n", reason); - expect_invalid_input("rvv10_INPUT", "dft_functional RVV10\nesolver_type dfpt\n", reason); - expect_invalid_input("rvv10_INPUT", "dft_functional rvv10\nesolver_type lr\ncal_force 1\n", reason); + EXPECT_NO_THROW(read_parameters(input_file, + "vdw_method rvv10\ndft_functional scan\nvdw_rvv10_b 15.7\ncalculation cell-relax\n", + param)); + EXPECT_EQ(param.inp.vdw_method, "rvv10"); + EXPECT_DOUBLE_EQ(param.inp.vdw_rvv10_b, 15.7); + EXPECT_DOUBLE_EQ(param.inp.vdw_rvv10_c, 0.0093); + const std::string reason = "Requires esolver_type!=dfpt"; + expect_invalid_input(input_file, "vdw_method rvv10\nesolver_type dfpt\n", reason); + expect_invalid_input(input_file, "vdw_method rvv10\nesolver_type lr\ncalculation nscf\ncal_force 1\n", reason); + expect_invalid_input(input_file, "vdw_method rvv10\nvdw_rvv10_b 0\n", "vdw_rvv10_b"); + expect_invalid_input(input_file, "vdw_method rvv10\nvdw_rvv10_c -0.1\n", "vdw_rvv10_c"); + expect_invalid_input(input_file, "vdw_method RVV10\n", "vdw_method"); } TEST_F(InputTest, ValidateRelaxMethodScalar) diff --git a/source/source_lcao/module_lr/utils/lr_util.cpp b/source/source_lcao/module_lr/utils/lr_util.cpp index 80e24184bde..d9f1a709f1f 100644 --- a/source/source_lcao/module_lr/utils/lr_util.cpp +++ b/source/source_lcao/module_lr/utils/lr_util.cpp @@ -107,6 +107,17 @@ namespace LR_Util } } + void check_force_vdw(const std::string& vdw_method, const bool cal_force, const std::string& calculation) + { + const bool relaxation = calculation == "relax" || calculation == "cell-relax"; + if (vdw_method == "rvv10" && (cal_force || relaxation)) + { + throw std::invalid_argument("LR excited-state forces and relaxation do not support vdw_method rvv10:" + " the response of the rVV10 nonlocal correlation is not implemented." + " LR spectra remain available with cal_force=0."); + } + } + const std::vector& prepare_xc_sigma(const std::vector& sigma, const bool is_hse06, std::vector& hse_buffer) diff --git a/source/source_lcao/module_lr/utils/lr_util.h b/source/source_lcao/module_lr/utils/lr_util.h index 1982274a790..a0e01e8a4ae 100644 --- a/source/source_lcao/module_lr/utils/lr_util.h +++ b/source/source_lcao/module_lr/utils/lr_util.h @@ -35,6 +35,9 @@ namespace LR_Util /// Reject unsupported NLCC force requests after pseudopotentials have been loaded. void check_force_pp(const UnitCell& cell, bool cal_force, const std::string& calculation); + /// Reject force requests with vdw_method rvv10, whose response kernel is not implemented. + void check_force_vdw(const std::string& vdw_method, bool cal_force, const std::string& calculation); + /// Preserve signed sigma invariants except for the existing HSE06 stabilization. const std::vector& prepare_xc_sigma(const std::vector& sigma, bool is_hse06, diff --git a/source/source_lcao/module_lr/utils/test/test_lr_util.cpp b/source/source_lcao/module_lr/utils/test/test_lr_util.cpp index d1461c4c937..a138e92ef8a 100644 --- a/source/source_lcao/module_lr/utils/test/test_lr_util.cpp +++ b/source/source_lcao/module_lr/utils/test/test_lr_util.cpp @@ -1,6 +1,8 @@ #include "../lr_util.h" #include "source_cell/unitcell.h" + #include +#include #include TEST(LRForcePP, AllowsForcesAndRelaxationWithoutNLCC) @@ -223,3 +225,11 @@ TEST(LRLinearSolver, ReportsSingularComplexFactorization) const int info = LR_Util::lapack_linear_solver(matrix, result, rhs, 2, 1); EXPECT_GT(info, 0); } + +TEST(LRForceVdw, RejectsForcesAndRelaxationWithRvv10) +{ + EXPECT_NO_THROW(LR_Util::check_force_vdw("rvv10", false, "scf")); + EXPECT_NO_THROW(LR_Util::check_force_vdw("d3_bj", true, "relax")); + EXPECT_THROW(LR_Util::check_force_vdw("rvv10", true, "scf"), std::invalid_argument); + EXPECT_THROW(LR_Util::check_force_vdw("rvv10", false, "relax"), std::invalid_argument); +} diff --git a/source/source_pw/module_pwdft/stress_gga.cpp b/source/source_pw/module_pwdft/stress_gga.cpp index ec1bf7799b2..340de6f0e8c 100644 --- a/source/source_pw/module_pwdft/stress_gga.cpp +++ b/source/source_pw/module_pwdft/stress_gga.cpp @@ -1,6 +1,9 @@ #include "stress_func.h" #include "source_base/parallel_reduce.h" #include "source_hamilt/module_xc/xc_functional.h" +#ifdef __LIBVDWXC +#include "source_hamilt/module_vdw/nonlocal_xc.h" +#endif //calculate the GGA stress correction in PW and LCAO template @@ -15,12 +18,9 @@ void Stress_Func::stress_gga(const UnitCell& ucell, { ModuleBase::TITLE("Stress","stress_gga"); ModuleBase::timer::start("Stress","stress_gga"); - - if (XC_Functional::get_nlc_b() > 0.0) - { - ModuleBase::WARNING_QUIT("Stress_Func::stress_gga", - "stress of the rVV10 nonlocal correlation is not implemented"); - } +#ifdef __LIBVDWXC + vdw::add_rvv10_stress(*chr, sigma); +#endif int func_type = XC_Functional::get_func_type(); if (func_type == 0 || func_type == 1) From 7e9425c868474586281a05016368cfd0de7281e0 Mon Sep 17 00:00:00 2001 From: 1 Date: Tue, 27 Oct 2026 11:57:28 +0000 Subject: [PATCH 27/28] refactor(xc): keep the rVV10 hooks in their owning modules The DFPT runtime guard and LR_Util::check_force_vdw repeated the INPUT requirement on vdw_method, which is checked on the effective values before any calculation; both are removed, so no esolver or LR file changes. ESolver_FP copies the rVV10 settings like the other XC runtime parameters, with an explicit rvv10 switch instead of rvv10_b > 0, and the XC hooks pass these parameters to vdw::add_rvv10 instead of the adapter reading global state. The libxc hook moves to the single exit of v_xc_libxc, next to the one in v_xc_meta, so the existing return of v_xc is unchanged. The adapter test drops cases that other cases already cover and merges the distributed stress check into the distributed grid test; the new Parallel_Common::alltoallv_data gets a case in the existing 4-rank parallel_device test, which runs without libvdwxc. CMake enables C in the shared language block and warns, instead of stopping, when MKL meets an MPI libvdwxc. INPUT and docs keep the parameter values in one place. Co-Authored-By: Claude Opus 5.5 --- CMakeLists.txt | 12 +- docs/advanced/install.md | 4 +- docs/advanced/scf/advanced.md | 2 +- .../test_parallel/parallel_device_test.cpp | 43 +++ source/source_esolver/esolver_dfpt_pw.cpp | 6 - source/source_esolver/esolver_fp.cpp | 8 +- source/source_esolver/esolver_lr_init.cpp | 2 - .../source_hamilt/module_vdw/nonlocal_xc.cpp | 42 +-- source/source_hamilt/module_vdw/nonlocal_xc.h | 15 +- .../module_vdw/test/CMakeLists.txt | 2 +- .../module_vdw/test/test_nonlocal_xc.cpp | 254 +++++++----------- source/source_hamilt/module_xc/CMakeLists.txt | 2 +- source/source_hamilt/module_xc/libxc_pot.cpp | 7 +- .../source_hamilt/module_xc/xc_functional.cpp | 1 + .../source_hamilt/module_xc/xc_functional.h | 3 +- source/source_hamilt/module_xc/xc_pot.cpp | 36 +-- .../module_parameter/read_inp_model.cpp | 6 +- .../source_io/test_serial/read_input_test.cpp | 2 + .../source_lcao/module_lr/utils/lr_util.cpp | 11 - source/source_lcao/module_lr/utils/lr_util.h | 3 - .../module_lr/utils/test/test_lr_util.cpp | 10 - source/source_pw/module_pwdft/stress_gga.cpp | 4 +- 22 files changed, 215 insertions(+), 260 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 884349601d6..31f382cd78c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -287,6 +287,10 @@ if(ENABLE_DFTD4 OR ENABLE_PEXSI) enable_language(C) enable_language(Fortran) endif() +# The libvdwxc package configuration looks up MPI for C when libvdwxc was built with MPI. +if(ENABLE_LIBVDWXC) + enable_language(C) +endif() macro(set_if_higher VARIABLE VALUE) if(${VARIABLE} LESS ${VALUE}) @@ -646,10 +650,8 @@ if(ENABLE_LIBXC) endif() if(ENABLE_LIBVDWXC) - # The libvdwxc package configuration looks up MPI for C when libvdwxc was built with MPI. - enable_language(C) find_package(libvdwxc CONFIG REQUIRED) - # A configuration that does not report VDWXC_ENABLE_MPI has no vdwxc_init_mpi either. + # An MPI build calls vdwxc_init_mpi, which only an MPI-enabled libvdwxc provides. if(ENABLE_MPI AND NOT VDWXC_ENABLE_MPI) message( FATAL_ERROR @@ -658,8 +660,8 @@ if(ENABLE_LIBVDWXC) endif() if(MKL_FOUND AND VDWXC_ENABLE_MPI) message( - FATAL_ERROR - "libvdwxc with MPI links FFTW3 and FFTW3-MPI, which cannot be mixed with the MKL FFT interface; build ABACUS with the same FFTW3" + WARNING + "libvdwxc with MPI links FFTW3 and FFTW3-MPI, while ABACUS uses the FFTW interface of MKL when MKL is found; this combination is untested. Configure ABACUS without MKL to use the same FFTW3." ) endif() endif() diff --git a/docs/advanced/install.md b/docs/advanced/install.md index 45988de12e4..7758a9c373c 100644 --- a/docs/advanced/install.md +++ b/docs/advanced/install.md @@ -108,14 +108,14 @@ cmake -S libvdwxc -B build-libvdwxc -DVDWXC_ENABLE_MPI=ON -DCMAKE_INSTALL_PREFIX cmake --build build-libvdwxc && cmake --install build-libvdwxc ``` -Then pass `-DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON` to CMake for ABACUS and add `/path/to/libvdwxc` to `CMAKE_PREFIX_PATH`. ABACUS hands each k-point pool's density to libvdwxc's distributed interface, so the nonlocal convolution and its memory are shared by the processes of the pool. libvdwxc's MPI build uses FFTW and FFTW-MPI, so it cannot be combined with the MKL FFT interface. The legacy Makefile does not build this feature. +Then pass `-DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON` to CMake for ABACUS and add `/path/to/libvdwxc` to `CMAKE_PREFIX_PATH`. ABACUS hands each k-point pool's density to libvdwxc's distributed interface, so the nonlocal convolution and its memory are shared by the processes of the pool. libvdwxc's MPI build uses FFTW and FFTW-MPI, while ABACUS uses the FFT interface of MKL whenever it finds MKL; that combination is untested, so configure ABACUS without MKL and against the same FFTW (CMake warns otherwise). The legacy Makefile does not build this feature. In the input file: ```text vdw_method rvv10 dft_functional scan -vdw_rvv10_b 15.7 # 6.3 (default) for GGA_X_RPW86+GGA_C_PBE, 15.7 for SCAN, 11.95 for r2SCAN +vdw_rvv10_b 15.7 # b of SCAN+rVV10; see vdw_rvv10_b for other functionals ``` ## Build with JSON support diff --git a/docs/advanced/scf/advanced.md b/docs/advanced/scf/advanced.md index c10102b4c65..afbd4dade7b 100644 --- a/docs/advanced/scf/advanced.md +++ b/docs/advanced/scf/advanced.md @@ -96,7 +96,7 @@ To use VdW-correction, users need to supply value to the `vdw_method` keyword in - d2: DFT-D2 method - d3_0: DFT-D3(0) method - d3_bj: DFT-D3(BJ) method - - rvv10: rVV10 nonlocal correlation (requires libvdwxc). Unlike the DFT-D methods, it depends on the electron density: it is added self-consistently to the exchange-correlation energy and potential of `dft_functional`, and its energy is reported in the exchange-correlation term. Its parameters `vdw_rvv10_b` and `vdw_rvv10_c` should match the functional, e.g. b = 6.3 for `GGA_X_RPW86+GGA_C_PBE`, 15.7 for SCAN and 11.95 for r2SCAN. + - rvv10: rVV10 nonlocal correlation (requires libvdwxc). Unlike the DFT-D methods, it depends on the electron density: it is added self-consistently to the exchange-correlation energy and potential of `dft_functional`, and its energy is reported in the exchange-correlation term. Set `vdw_rvv10_b` for the functional; its description lists the published values. Furthermore, ABACUS also provides a [list of keywords](../input_files/input-main.md#vdw-correction) to control relevant parmeters used in calculating the VdW correction, such as the scale factor (s6) term. Recommended values of such parameters can be found on the [webpage](https://www.chemie.uni-bonn.de/pctc/mulliken-center/software/dft-d3). The default values of the parameters in ABACUS are set to be the recommended values for PBE. diff --git a/source/source_base/test_parallel/parallel_device_test.cpp b/source/source_base/test_parallel/parallel_device_test.cpp index ba4a4d90b86..118d73f183b 100644 --- a/source/source_base/test_parallel/parallel_device_test.cpp +++ b/source/source_base/test_parallel/parallel_device_test.cpp @@ -131,5 +131,48 @@ TEST(ParallelDevice, CoversMpiTypeOverloads) exercise_mpi_wrappers>(domain); } +TEST(ParallelDevice, AlltoallvExchangesUnevenBlocks) +{ + const ModuleBase::CommunicationDomain domain = ModuleBase::world_comm_domain(); + MPI_Comm communicator = domain.communicator(); + const int rank = domain.rank(); + MPICommGroup world_group(communicator); + const int size = world_group.gsize; + // rank r sends (r + p) % 2 + 1 copies of 100 r + p to rank p + std::vector send_counts(size); + std::vector send_offsets(size); + std::vector recv_counts(size); + std::vector recv_offsets(size); + std::vector sent; + int received_total = 0; + for (int peer = 0; peer < size; ++peer) + { + const double value = 100.0 * rank + peer; + send_counts[peer] = (rank + peer) % 2 + 1; + send_offsets[peer] = static_cast(sent.size()); + sent.insert(sent.end(), send_counts[peer], value); + recv_counts[peer] = send_counts[peer]; + recv_offsets[peer] = received_total; + received_total += recv_counts[peer]; + } + std::vector received(received_total); + Parallel_Common::alltoallv_data(sent.data(), + send_counts.data(), + send_offsets.data(), + received.data(), + recv_counts.data(), + recv_offsets.data(), + communicator); + for (int peer = 0; peer < size; ++peer) + { + const double expected = 100.0 * peer + rank; + for (int index = 0; index < recv_counts[peer]; ++index) + { + const int position = recv_offsets[peer] + index; + EXPECT_EQ(received[position], expected); + } + } +} + } // namespace #endif diff --git a/source/source_esolver/esolver_dfpt_pw.cpp b/source/source_esolver/esolver_dfpt_pw.cpp index e7529260484..777a678d4a2 100644 --- a/source/source_esolver/esolver_dfpt_pw.cpp +++ b/source/source_esolver/esolver_dfpt_pw.cpp @@ -317,12 +317,6 @@ void ESolver_DFPT_PW::init_dfpt(UnitCell& ucell) << ") needs response channels that are not implemented"; ModuleBase::WARNING_QUIT("ESolver_DFPT_PW::init_dfpt", msg.str()); } - if (XC_Functional::get_runtime_parameters().rvv10_b > 0.0) - { - ModuleBase::WARNING_QUIT( - "ESolver_DFPT_PW::init_dfpt", - "the response of the rVV10 nonlocal correlation (vdw_method rvv10) is not implemented"); - } // first-order XC kernel around the converged ground-state density: // with libxc use the analytic kernel (libxc second derivatives in the diff --git a/source/source_esolver/esolver_fp.cpp b/source/source_esolver/esolver_fp.cpp index b0c26cbd762..97badb8360d 100644 --- a/source/source_esolver/esolver_fp.cpp +++ b/source/source_esolver/esolver_fp.cpp @@ -60,11 +60,9 @@ void ESolver_FP::before_all_runners(BaseCell& basecell, const Input_para& inp) xc_parameters.exx_erfc_alpha = inp.exx_erfc_alpha; xc_parameters.xc_exch_ext = inp.xc_exch_ext; xc_parameters.xc_corr_ext = inp.xc_corr_ext; - if (inp.vdw_method == "rvv10") - { - xc_parameters.rvv10_b = inp.vdw_rvv10_b; - xc_parameters.rvv10_c = inp.vdw_rvv10_c; - } + xc_parameters.rvv10 = inp.vdw_method == "rvv10"; + xc_parameters.rvv10_b = inp.vdw_rvv10_b; + xc_parameters.rvv10_c = inp.vdw_rvv10_c; XC_Functional::set_runtime_parameters(xc_parameters); ModuleBase::TITLE("ESolver_FP", "before_all_runners"); diff --git a/source/source_esolver/esolver_lr_init.cpp b/source/source_esolver/esolver_lr_init.cpp index 1170e5b0c50..a33589d4955 100644 --- a/source/source_esolver/esolver_lr_init.cpp +++ b/source/source_esolver/esolver_lr_init.cpp @@ -98,7 +98,6 @@ void ModuleESolver::ESolver_LR::before_all_runners(BaseCell& basecell, co this->ks_ = LR_Util::make_unique>(); this->ks_->before_all_runners(basecell, inp); LR_Util::check_force_pp(ucell, inp.cal_force, inp.calculation); - LR_Util::check_force_vdw(inp.vdw_method, inp.cal_force, inp.calculation); } else { @@ -340,7 +339,6 @@ void ModuleESolver::ESolver_LR::initialize_from_unitcell_(UnitCell& ucell // necessary steps in ESolver_FP ESolver_FP::before_all_runners(ucell, inp); LR_Util::check_force_pp(ucell, inp.cal_force, inp.calculation); - LR_Util::check_force_vdw(inp.vdw_method, inp.cal_force, inp.calculation); this->pelec = new elecstate::ElecStateLCAO(); // necessary steps in ESolver_KS::before_all_runners : symmetry and k-points diff --git a/source/source_hamilt/module_vdw/nonlocal_xc.cpp b/source/source_hamilt/module_vdw/nonlocal_xc.cpp index 3e5647df2bc..483012b792f 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc.cpp +++ b/source/source_hamilt/module_vdw/nonlocal_xc.cpp @@ -32,10 +32,14 @@ namespace vdw { namespace { +/// Cartesian components of the stress tensor returned by vdwxc_stress, stored as [3 * a + b] +const int STRESS_COMPONENTS = 9; + /** * libvdwxc object for one grid and pool, kept between calls. * - * libvdwxc distributes the first axis of its grid in blocks of ceil(n/P) planes, the FFTW-MPI default. + * libvdwxc distributes the first axis of its grid in blocks of ceil(n/P) planes, the FFTW-MPI default + * (vdwxc_init_mpi does not report its local slab, so plan_exchange recomputes it). * It is given the lattice vectors in the cyclic order (a3, a1, a2), so that this axis is the z axis * that ABACUS distributes and the slab of a process is stored as [z][x][y], the transpose of the * ABACUS layout [x][y][z]. Whole z planes are moved between the two distributions with all-to-all exchanges. @@ -264,7 +268,7 @@ double Rvv10Workspace::evaluate(const ModulePW::PW_Basis& pw, Parallel_Common::reduce_data(&energy, 1, pw.pool_world); if (stress != nullptr) { - Parallel_Common::reduce_data(stress, 9, pw.pool_world); + Parallel_Common::reduce_data(stress, STRESS_COMPONENTS, pw.pool_world); } } #endif @@ -279,12 +283,6 @@ Rvv10Workspace& workspace() return *instance; } -/// Spin channels of chr that carry density; for nspin=4, rho[0] is the total valence density. -int density_channels(const Charge& chr) -{ - return (chr.nspin == 2) ? 2 : 1; -} - /// Total density of chr (valence plus core), its plane-wave gradient and sigma = |grad n|^2. void total_density(const Charge& chr, std::vector& rho, @@ -295,8 +293,10 @@ void total_density(const Charge& chr, const int nrxx = chr.nrxx; const double* core_begin = chr.rho_core; const double* core_end = core_begin + nrxx; + // for nspin = 4, rho[0] is the total valence density + const int nspin0 = (chr.nspin == 2) ? 2 : 1; rho.assign(core_begin, core_end); - for (int is = 0; is < density_channels(chr); ++is) + for (int is = 0; is < nspin0; ++is) { for (int ir = 0; ir < nrxx; ++ir) { @@ -315,10 +315,13 @@ void total_density(const Charge& chr, } } // namespace -void add_rvv10(const Charge& chr, double& etxc, double& vtxc, ModuleBase::matrix& v) +void add_rvv10(const XCFunctionalParameters& parameters, + const Charge& chr, + double& etxc, + double& vtxc, + ModuleBase::matrix& v) { - const XCFunctionalParameters& parameters = XC_Functional::get_runtime_parameters(); - if (parameters.rvv10_b <= 0.0) + if (!parameters.rvv10) { return; } @@ -342,11 +345,13 @@ void add_rvv10(const Charge& chr, double& etxc, double& vtxc, ModuleBase::matrix } std::vector divergence(nrxx); XC_Functional::grad_dot(grad.data(), divergence.data(), &pw, pw.tpiba); + // for nspin = 4 the potential of the total density goes to v(0) only + const int nspin0 = (chr.nspin == 2) ? 2 : 1; double vtxc_nl = 0.0; for (int ir = 0; ir < nrxx; ++ir) { const double v_nl = ModuleBase::e2 * (dedn[ir] - divergence[ir]); - for (int is = 0; is < density_channels(chr); ++is) + for (int is = 0; is < nspin0; ++is) { v(is, ir) += v_nl; vtxc_nl += v_nl * chr.rho[is][ir]; @@ -360,10 +365,9 @@ void add_rvv10(const Charge& chr, double& etxc, double& vtxc, ModuleBase::matrix ModuleBase::timer::end("vdw", "add_rvv10"); } -void add_rvv10_stress(const Charge& chr, ModuleBase::matrix& sigma) +void add_rvv10_stress(const XCFunctionalParameters& parameters, const Charge& chr, ModuleBase::matrix& sigma) { - const XCFunctionalParameters& parameters = XC_Functional::get_runtime_parameters(); - if (parameters.rvv10_b <= 0.0) + if (!parameters.rvv10) { return; } @@ -377,7 +381,7 @@ void add_rvv10_stress(const Charge& chr, ModuleBase::matrix& sigma) total_density(chr, rho, grad, density_sigma); std::vector dedn(nrxx); std::vector dedsigma(nrxx); - double explicit_stress[9] = {0.0}; + double explicit_stress[STRESS_COMPONENTS] = {0.0}; const double energy = workspace().evaluate(pw, parameters.rvv10_b, parameters.rvv10_c, @@ -388,7 +392,7 @@ void add_rvv10_stress(const Charge& chr, ModuleBase::matrix& sigma) explicit_stress); // integral of dE/dsigma d_a n d_b n, in units of the volume element - double gradient_term[9] = {0.0}; + double gradient_term[STRESS_COMPONENTS] = {0.0}; for (int ir = 0; ir < nrxx; ++ir) { const double g[3] = {grad[ir].x, grad[ir].y, grad[ir].z}; @@ -401,7 +405,7 @@ void add_rvv10_stress(const Charge& chr, ModuleBase::matrix& sigma) } } #ifdef __MPI - Parallel_Common::reduce_data(gradient_term, 9, pw.pool_world); + Parallel_Common::reduce_data(gradient_term, STRESS_COMPONENTS, pw.pool_world); #endif // The full strain derivative is T - delta_ab Int(n dE/dn + 2 sigma dE/dsigma) - 2 Int dE/dsigma d_a n d_b n, diff --git a/source/source_hamilt/module_vdw/nonlocal_xc.h b/source/source_hamilt/module_vdw/nonlocal_xc.h index be040e0cf73..22eb4a56b31 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc.h +++ b/source/source_hamilt/module_vdw/nonlocal_xc.h @@ -2,6 +2,7 @@ #define NONLOCAL_XC_H class Charge; +struct XCFunctionalParameters; namespace ModuleBase { class matrix; @@ -11,15 +12,19 @@ namespace vdw { // rVV10 nonlocal correlation of vdw_method rvv10, evaluated by libvdwxc. Both functions do nothing -// unless the XC runtime parameters carry rvv10_b > 0. They use the total density of chr (valence plus -// core) and are collective over the pool communicator of chr.rhopw. The libvdwxc object, its FFT plans -// and its buffers are kept between calls. +// unless parameters.rvv10 is set. They use the total density of chr (valence plus core) and are +// collective over the pool communicator of chr.rhopw. The libvdwxc object, its FFT plans and its +// buffers are kept between calls. /// Add the rVV10 energy, potential and vtxc (Ry) to the pool-reduced results of an XC evaluation. -void add_rvv10(const Charge& chr, double& etxc, double& vtxc, ModuleBase::matrix& v); +void add_rvv10(const XCFunctionalParameters& parameters, + const Charge& chr, + double& etxc, + double& vtxc, + ModuleBase::matrix& v); /// Add the rVV10 stress (Ry/Bohr^3) that the XC diagonal and potential terms do not contain. -void add_rvv10_stress(const Charge& chr, ModuleBase::matrix& sigma); +void add_rvv10_stress(const XCFunctionalParameters& parameters, const Charge& chr, ModuleBase::matrix& sigma); } // namespace vdw diff --git a/source/source_hamilt/module_vdw/test/CMakeLists.txt b/source/source_hamilt/module_vdw/test/CMakeLists.txt index 1759b19dd21..2f056a69990 100644 --- a/source/source_hamilt/module_vdw/test/CMakeLists.txt +++ b/source/source_hamilt/module_vdw/test/CMakeLists.txt @@ -23,7 +23,7 @@ if(ENABLE_DFTD4 AND ENABLE_MPI) ) endif() -if(ENABLE_LIBVDWXC) +if(ENABLE_LIBVDWXC AND ENABLE_MPI) addtest( TARGET MODULE_HAMILT_nonlocal_xcTest LIBS xc_ diff --git a/source/source_hamilt/module_vdw/test/test_nonlocal_xc.cpp b/source/source_hamilt/module_vdw/test/test_nonlocal_xc.cpp index 21e46a2d840..59689d16931 100644 --- a/source/source_hamilt/module_vdw/test/test_nonlocal_xc.cpp +++ b/source/source_hamilt/module_vdw/test/test_nonlocal_xc.cpp @@ -58,6 +58,7 @@ namespace int test_rank = 0; int test_size = 1; typedef std::tuple VxcResult; +typedef std::tuple MetaResult; // rVV10 parameters of the rPW86-PBE pairing const double RVV10_B = 6.3; const double RVV10_C = 0.0093; @@ -105,17 +106,6 @@ double perturbation(const ModulePW::PW_Basis& pw, const int ix, const int iy, co return std::sin(axy) + 0.3 * std::cos(ay2); } -double x_only_density(const ModulePW::PW_Basis& pw, const int ix, const int iy, const int iz) -{ - const double ax = ModuleBase::TWO_PI * ix / pw.nx; - return 0.01 + 0.003 * std::cos(ax); -} - -double zero_density(const ModulePW::PW_Basis& pw, const int ix, const int iy, const int iz) -{ - return 0.0; -} - /// Values of f on the z planes of this process, in the ABACUS layout [ixy * nplane + iz]. std::vector local_grid(const ModulePW::PW_Basis& pw, GridFunction f) { @@ -175,19 +165,27 @@ void setup(ModulePW::PW_Basis& basis, const ModuleBase::Matrix3& lattice, const setup_grid(basis, lattice, cutoff, 5, 7, 9); } -/// Sets the rVV10 parameters of the XC runtime parameters for the lifetime of the object. -class Rvv10Parameters +/// XC runtime parameters with the rVV10 switch set as given and the parameters of the rPW86-PBE pairing. +XCFunctionalParameters rvv10_parameters(const bool rvv10) +{ + XCFunctionalParameters parameters = XC_Functional::get_runtime_parameters(); + parameters.rvv10 = rvv10; + parameters.rvv10_b = RVV10_B; + parameters.rvv10_c = RVV10_C; + return parameters; +} + +/// Sets the XC runtime parameters for the lifetime of the object, as ESolver_FP does. +class RuntimeParameters { public: - Rvv10Parameters(const double b, const double c) + explicit RuntimeParameters(const bool rvv10) { saved_ = XC_Functional::get_runtime_parameters(); - XCFunctionalParameters parameters = saved_; - parameters.rvv10_b = b; - parameters.rvv10_c = c; + const XCFunctionalParameters parameters = rvv10_parameters(rvv10); XC_Functional::set_runtime_parameters(parameters); } - ~Rvv10Parameters() + ~RuntimeParameters() { XC_Functional::set_runtime_parameters(saved_); } @@ -223,14 +221,6 @@ class ChargeOnGrid chr.rho_core = core_.data(); chr.rhog_core = core_g_.data(); } - ~ChargeOnGrid() - { - chr.rho = nullptr; - chr.rhog = nullptr; - chr.rho_core = nullptr; - chr.rhog_core = nullptr; - chr.kin_r = nullptr; - } ChargeOnGrid(const ChargeOnGrid&) = delete; ChargeOnGrid& operator=(const ChargeOnGrid&) = delete; @@ -251,10 +241,11 @@ VxcResult rvv10_result(ModulePW::PW_Basis& pw, const std::vector& core) { const ChargeOnGrid charge(pw, channels, core); + const XCFunctionalParameters parameters = rvv10_parameters(true); double etxc = 0.0; double vtxc = 0.0; ModuleBase::matrix v(charge.chr.nspin, pw.nrxx); - vdw::add_rvv10(charge.chr, etxc, vtxc, v); + vdw::add_rvv10(parameters, charge.chr, etxc, vtxc, v); return VxcResult(etxc, vtxc, v); } @@ -264,8 +255,9 @@ ModuleBase::matrix rvv10_stress(ModulePW::PW_Basis& pw, const std::vector& core) { const ChargeOnGrid charge(pw, channels, core); + const XCFunctionalParameters parameters = rvv10_parameters(true); ModuleBase::matrix sigma(3, 3); - vdw::add_rvv10_stress(charge.chr, sigma); + vdw::add_rvv10_stress(parameters, charge.chr, sigma); return sigma; } @@ -289,17 +281,42 @@ void expect_same_result(const VxcResult& actual, const VxcResult& expected, cons /// A functional evaluated with rVV10 equals the evaluation without it plus add_rvv10. void expect_rvv10_added(const VxcResult& total, VxcResult semilocal, const Charge& chr) { + const XCFunctionalParameters parameters = rvv10_parameters(true); double& etxc = std::get<0>(semilocal); double& vtxc = std::get<1>(semilocal); ModuleBase::matrix& v = std::get<2>(semilocal); - vdw::add_rvv10(chr, etxc, vtxc, v); + vdw::add_rvv10(parameters, chr, etxc, vtxc, v); expect_same_result(total, semilocal, ROUNDING_TOLERANCE); } +/// XC_Functional::v_xc for the given functional and density with and without rVV10. +void expect_vxc_adds_rvv10(const char* functional, + const Charge& chr, + const UnitCell& ucell, + const bool domag, + const int gga_grad) +{ + XC_Functional::ScopedType scope(functional); + const int nrxx = chr.nrxx; + const int nspin = chr.nspin; + const bool domag_z = false; + const double hybrid_alpha = 0.0; + const double hse_omega = 0.0; + VxcResult total; + VxcResult semilocal; + for (int with_rvv10 = 0; with_rvv10 < 2; ++with_rvv10) + { + const bool rvv10 = with_rvv10 == 1; + RuntimeParameters parameters(rvv10); + VxcResult& result = rvv10 ? total : semilocal; + result = XC_Functional::v_xc(nrxx, &chr, &ucell, nspin, domag, domag_z, gga_grad, hybrid_alpha, hse_omega); + } + expect_rvv10_added(total, semilocal, chr); +} + class NonlocalXCTest : public testing::Test { protected: - Rvv10Parameters rvv10{RVV10_B, RVV10_C}; /// grid on an oblique cell, distributed over the processes of the test ModulePW::PW_Basis pw{"cpu", "double"}; /// the same grid, complete on every process @@ -318,6 +335,7 @@ class NonlocalXCTest : public testing::Test TEST_F(NonlocalXCTest, MatchesDirectLibvdwxc) { + // unpolarized and spin-polarized layouts against libvdwxc called directly in the ABACUS axis order const std::vector valence = local_grid(serial, valence_density); const std::vector core = local_grid(serial, core_density); const VxcResult result = rvv10_result(serial, {valence}, core); @@ -343,19 +361,10 @@ TEST_F(NonlocalXCTest, MatchesDirectLibvdwxc) ASSERT_NE(direct, nullptr); ASSERT_EQ(vdwxc_set_rvv10_parameters(direct, RVV10_B, RVV10_C), 0); const ModuleBase::Matrix3 a = serial.latvec * serial.lat0; - vdwxc_set_unit_cell(direct, - serial.nx, - serial.ny, - serial.nz, - a.e11, - a.e12, - a.e13, - a.e21, - a.e22, - a.e23, - a.e31, - a.e32, - a.e33); + const int nx = serial.nx; + const int ny = serial.ny; + const int nz = serial.nz; + vdwxc_set_unit_cell(direct, nx, ny, nz, a.e11, a.e12, a.e13, a.e21, a.e22, a.e23, a.e31, a.e32, a.e33); vdwxc_init_serial(direct); const double energy_ha = vdwxc_calculate(direct, total.data(), sigma.data(), dedn.data(), dedsigma.data()); vdwxc_finalize(&direct); @@ -378,12 +387,8 @@ TEST_F(NonlocalXCTest, MatchesDirectLibvdwxc) vtxc *= serial.omega / serial.nxyz; const VxcResult expected(etxc, vtxc, v); expect_same_result(result, expected, ROUNDING_TOLERANCE); -} -TEST_F(NonlocalXCTest, SpinChannelsShareTheTotalDensity) -{ - const std::vector valence = local_grid(serial, valence_density); - const std::vector core = local_grid(serial, core_density); + // spin channels share the total density: same energy and vtxc, and each channel gets the potential std::vector up(serial.nrxx); std::vector down(serial.nrxx); for (int ir = 0; ir < serial.nrxx; ++ir) @@ -391,14 +396,13 @@ TEST_F(NonlocalXCTest, SpinChannelsShareTheTotalDensity) up[ir] = 0.7 * valence[ir]; down[ir] = valence[ir] - up[ir]; } - const VxcResult unpolarized = rvv10_result(serial, {valence}, core); const VxcResult polarized = rvv10_result(serial, {up, down}, core); - EXPECT_NEAR(std::get<0>(polarized), std::get<0>(unpolarized), ROUNDING_TOLERANCE); - EXPECT_NEAR(std::get<1>(polarized), std::get<1>(unpolarized), ROUNDING_TOLERANCE); + EXPECT_NEAR(std::get<0>(polarized), etxc, ROUNDING_TOLERANCE); + EXPECT_NEAR(std::get<1>(polarized), vtxc, ROUNDING_TOLERANCE); for (int ir = 0; ir < serial.nrxx; ++ir) { - EXPECT_NEAR(std::get<2>(polarized)(0, ir), std::get<2>(unpolarized)(0, ir), ROUNDING_TOLERANCE); - EXPECT_NEAR(std::get<2>(polarized)(1, ir), std::get<2>(unpolarized)(0, ir), ROUNDING_TOLERANCE); + EXPECT_NEAR(std::get<2>(polarized)(0, ir), v(0, ir), ROUNDING_TOLERANCE); + EXPECT_NEAR(std::get<2>(polarized)(1, ir), v(0, ir), ROUNDING_TOLERANCE); } } @@ -421,6 +425,16 @@ TEST_F(NonlocalXCTest, DistributedGridMatchesCompleteGrid) EXPECT_NEAR(std::get<2>(distributed)(0, local), std::get<2>(complete)(0, global), ROUNDING_TOLERANCE); } } + const ModuleBase::matrix stress = rvv10_stress(pw, {valence}, core); + const ModuleBase::matrix stress_complete = rvv10_stress(serial, {valence_complete}, core_complete); + EXPECT_GT(std::abs(stress_complete(0, 0)), 1.0e-10); + for (int a = 0; a < 3; ++a) + { + for (int b = 0; b < 3; ++b) + { + EXPECT_NEAR(stress(a, b), stress_complete(a, b), ROUNDING_TOLERANCE); + } + } } TEST_F(NonlocalXCTest, PotentialIsTheDerivativeOfTheEnergy) @@ -451,38 +465,14 @@ TEST_F(NonlocalXCTest, PotentialIsTheDerivativeOfTheEnergy) EXPECT_NEAR(finite_difference, contraction, FINITE_DIFFERENCE_TOLERANCE); } -TEST_F(NonlocalXCTest, ObliqueCellMatchesEquivalentOrthogonalCell) -{ - // Both cells have the volume sqrt(3) lat0^3 and the plane spacing sqrt(3)/2 lat0 along the - // first lattice direction, so a density that depends only on the first fractional coordinate - // has the same nonlocal energy and potential in both. A transposed cell matrix breaks this. - ModulePW::PW_Basis orthogonal{"cpu", "double"}; - orthogonal.initmpi(1, 0, MPI_COMM_SELF); - const ModuleBase::Matrix3 rectangle(std::sqrt(0.75), 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 2.0); - setup(orthogonal, rectangle, BASIS_CUTOFF); - ASSERT_NEAR(serial.omega, orthogonal.omega, ROUNDING_TOLERANCE); - - const std::vector density = local_grid(serial, x_only_density); - const std::vector no_core = local_grid(serial, zero_density); - const VxcResult oblique = rvv10_result(serial, {density}, no_core); - const VxcResult rectangular = rvv10_result(orthogonal, {density}, no_core); - EXPECT_NEAR(std::get<0>(oblique), std::get<0>(rectangular), ROUNDING_TOLERANCE); - for (int ir = 0; ir < serial.nrxx; ++ir) - { - EXPECT_NEAR(std::get<2>(oblique)(0, ir), std::get<2>(rectangular)(0, ir), ROUNDING_TOLERANCE); - } -} - TEST_F(NonlocalXCTest, LeftHandedCellGivesTheMirrorImageResult) { - // A mirror image has the same nonlocal energy and potential; libvdwxc's signed cell volume would - // otherwise flip the sign of the energy. + // A mirror image has the same result; libvdwxc's signed cell volume would flip the sign of the energy. ModulePW::PW_Basis mirror{"cpu", "double"}; mirror.initmpi(1, 0, MPI_COMM_SELF); const ModuleBase::Matrix3 mirrored = oblique_lattice(-1.0); setup(mirror, mirrored, BASIS_CUTOFF); ASSERT_LT(mirror.latvec.Det(), 0.0); - const std::vector valence = local_grid(serial, valence_density); const std::vector core = local_grid(serial, core_density); const VxcResult right_handed = rvv10_result(serial, {valence}, core); @@ -522,24 +512,6 @@ TEST_F(NonlocalXCTest, WorkspaceFollowsGridCellAndProcessChanges) EXPECT_GT(std::abs(std::get<0>(other_cell) - std::get<0>(first)), 1.0e-8); } -TEST_F(NonlocalXCTest, DistributedStressMatchesCompleteGrid) -{ - const std::vector valence = local_grid(pw, valence_density); - const std::vector core = local_grid(pw, core_density); - const std::vector valence_complete = local_grid(serial, valence_density); - const std::vector core_complete = local_grid(serial, core_density); - const ModuleBase::matrix distributed = rvv10_stress(pw, {valence}, core); - const ModuleBase::matrix complete = rvv10_stress(serial, {valence_complete}, core_complete); - EXPECT_GT(std::abs(complete(0, 0)), 1.0e-10); - for (int a = 0; a < 3; ++a) - { - for (int b = 0; b < 3; ++b) - { - EXPECT_NEAR(distributed(a, b), complete(a, b), ROUNDING_TOLERANCE); - } - } -} - TEST_F(NonlocalXCTest, StressIsTheStrainDerivativeOfTheEnergy) { // A strain keeps the grid and every G component of the density, so the stress is the exact derivative @@ -549,7 +521,7 @@ TEST_F(NonlocalXCTest, StressIsTheStrainDerivativeOfTheEnergy) const ModuleBase::Matrix3 lattice = oblique_lattice(1.0); setup(box, lattice, LARGEST_SPHERE_CUTOFF); const std::vector density = local_grid(box, valence_density); - const std::vector no_core = local_grid(box, zero_density); + const std::vector no_core(box.nrxx, 0.0); const VxcResult reference = rvv10_result(box, {density}, no_core); const ModuleBase::matrix extra = rvv10_stress(box, {density}, no_core); // the XC diagonal term that ABACUS adds from etxc and vtxc @@ -588,8 +560,8 @@ TEST_F(NonlocalXCTest, StressIsTheStrainDerivativeOfTheEnergy) TEST_F(NonlocalXCTest, VxcAddsRvv10ToEveryFunctionalPath) { - // v_xc and v_xc_meta with vdw_method rvv10 equal the semilocal result plus add_rvv10, for native PBE - // (final return), a libxc GGA (libxc branch), noncollinear PBE (NCGGA exit) and SCAN (v_xc_meta). + // v_xc and v_xc_meta with rVV10 equal the semilocal result plus add_rvv10, for native PBE (final + // return), a libxc GGA (v_xc_libxc), noncollinear PBE (NCGGA return) and SCAN (v_xc_meta). const std::vector valence = local_grid(pw, valence_density); const std::vector core = local_grid(pw, core_density); const std::vector zeros(pw.nrxx); @@ -608,47 +580,11 @@ TEST_F(NonlocalXCTest, VxcAddsRvv10ToEveryFunctionalPath) ucell.omega = pw.omega; ucell.tpiba = pw.tpiba; ucell.magnet.lsign_ = false; - const bool domag_z = false; - const double hybrid_alpha = 0.0; - const double hse_omega = 0.0; - - const int ncases = 3; - const char* functionals[ncases] = {"PBE", "GGA_X_RPW86+GGA_C_PBE", "PBE"}; - const std::vector>* densities[ncases] = {&collinear, &collinear, &noncollinear}; - const bool domags[ncases] = {false, false, true}; - const int gga_grads[ncases] = {0, 0, 2}; - for (int icase = 0; icase < ncases; ++icase) - { - SCOPED_TRACE(testing::Message() << functionals[icase] << " with nspin " << densities[icase]->size()); - XC_Functional::ScopedType scope(functionals[icase]); - const ChargeOnGrid charge(pw, *densities[icase], core); - const int nspin = charge.chr.nspin; - const bool domag = domags[icase]; - const int gga_grad = gga_grads[icase]; - const VxcResult total = XC_Functional::v_xc(pw.nrxx, - &charge.chr, - &ucell, - nspin, - domag, - domag_z, - gga_grad, - hybrid_alpha, - hse_omega); - VxcResult semilocal; - { - Rvv10Parameters off(0.0, 0.0); - semilocal = XC_Functional::v_xc(pw.nrxx, - &charge.chr, - &ucell, - nspin, - domag, - domag_z, - gga_grad, - hybrid_alpha, - hse_omega); - } - expect_rvv10_added(total, semilocal, charge.chr); - } + const ChargeOnGrid polarized(pw, collinear, core); + const ChargeOnGrid magnetized(pw, noncollinear, core); + expect_vxc_adds_rvv10("PBE", polarized.chr, ucell, false, 0); + expect_vxc_adds_rvv10("GGA_X_RPW86+GGA_C_PBE", polarized.chr, ucell, false, 0); + expect_vxc_adds_rvv10("PBE", magnetized.chr, ucell, true, 2); XC_Functional::ScopedType scan("SCAN"); const std::vector> unpolarized(1, valence); @@ -658,30 +594,24 @@ TEST_F(NonlocalXCTest, VxcAddsRvv10ToEveryFunctionalPath) charge.chr.kin_r = kinetic_rows.data(); const std::vector func_id = XC_Functional::get_func_id(); const int nspin = 1; - typedef std::tuple MetaResult; - const MetaResult total = XC_Functional_Libxc::v_xc_meta(func_id, - pw.nrxx, - pw.omega, - pw.tpiba, - &charge.chr, - nspin, - hybrid_alpha, - hse_omega); - MetaResult semilocal; + const double hybrid_alpha = 0.0; + const double hse_omega = 0.0; + VxcResult results[2]; + for (int with_rvv10 = 0; with_rvv10 < 2; ++with_rvv10) { - Rvv10Parameters off(0.0, 0.0); - semilocal = XC_Functional_Libxc::v_xc_meta(func_id, - pw.nrxx, - pw.omega, - pw.tpiba, - &charge.chr, - nspin, - hybrid_alpha, - hse_omega); + const bool rvv10 = with_rvv10 == 1; + RuntimeParameters parameters(rvv10); + const MetaResult meta = XC_Functional_Libxc::v_xc_meta(func_id, + pw.nrxx, + pw.omega, + pw.tpiba, + &charge.chr, + nspin, + hybrid_alpha, + hse_omega); + results[with_rvv10] = VxcResult(std::get<0>(meta), std::get<1>(meta), std::get<2>(meta)); } - const VxcResult total_xc(std::get<0>(total), std::get<1>(total), std::get<2>(total)); - const VxcResult semilocal_xc(std::get<0>(semilocal), std::get<1>(semilocal), std::get<2>(semilocal)); - expect_rvv10_added(total_xc, semilocal_xc, charge.chr); + expect_rvv10_added(results[1], results[0], charge.chr); } } // namespace diff --git a/source/source_hamilt/module_xc/CMakeLists.txt b/source/source_hamilt/module_xc/CMakeLists.txt index fc1132fc494..8496738f86a 100644 --- a/source/source_hamilt/module_xc/CMakeLists.txt +++ b/source/source_hamilt/module_xc/CMakeLists.txt @@ -32,7 +32,7 @@ if(ENABLE_COVERAGE) endif() if(ENABLE_LIBVDWXC) - # XC_Functional::v_xc adds the rVV10 nonlocal correlation through this adapter. + # The XC evaluators (v_xc, v_xc_libxc, v_xc_meta) and Stress_Func::stress_gga add rVV10 through this adapter. target_sources(xc_ PRIVATE ../module_vdw/nonlocal_xc.cpp) target_link_libraries(xc_ PUBLIC libvdwxc::vdwxc) endif() diff --git a/source/source_hamilt/module_xc/libxc_pot.cpp b/source/source_hamilt/module_xc/libxc_pot.cpp index 53f04ad5937..6ef84c60997 100644 --- a/source/source_hamilt/module_xc/libxc_pot.cpp +++ b/source/source_hamilt/module_xc/libxc_pot.cpp @@ -452,6 +452,10 @@ std::tuple XC_Functional_Libxc::v_xc_libxc( etxc *= omega / chr->rhopw->nxyz; vtxc *= omega / chr->rhopw->nxyz; +#ifdef __LIBVDWXC + const XCFunctionalParameters& xc_parameters = XC_Functional::get_runtime_parameters(); + vdw::add_rvv10(xc_parameters, *chr, etxc, vtxc, v); +#endif XC_Functional_Libxc::finish_func(funcs); @@ -772,7 +776,8 @@ std::tuple XC_Functional_Li etxc *= omega / chr->rhopw->nxyz; vtxc *= omega / chr->rhopw->nxyz; #ifdef __LIBVDWXC - vdw::add_rvv10(*chr, etxc, vtxc, v); + const XCFunctionalParameters& xc_parameters = XC_Functional::get_runtime_parameters(); + vdw::add_rvv10(xc_parameters, *chr, etxc, vtxc, v); #endif XC_Functional_Libxc::finish_func(funcs); diff --git a/source/source_hamilt/module_xc/xc_functional.cpp b/source/source_hamilt/module_xc/xc_functional.cpp index 72504150b49..79a7ecfdb63 100644 --- a/source/source_hamilt/module_xc/xc_functional.cpp +++ b/source/source_hamilt/module_xc/xc_functional.cpp @@ -59,6 +59,7 @@ void XC_Functional::ScopedType::restore() noexcept XC_Functional::runtime_parameters.exx_erfc_alpha.swap(runtime_parameters_.exx_erfc_alpha); XC_Functional::runtime_parameters.xc_exch_ext.swap(runtime_parameters_.xc_exch_ext); XC_Functional::runtime_parameters.xc_corr_ext.swap(runtime_parameters_.xc_corr_ext); + XC_Functional::runtime_parameters.rvv10 = runtime_parameters_.rvv10; XC_Functional::runtime_parameters.rvv10_b = runtime_parameters_.rvv10_b; XC_Functional::runtime_parameters.rvv10_c = runtime_parameters_.rvv10_c; XC_Functional::scaling_factor_xc.swap(scaling_factor_xc_); diff --git a/source/source_hamilt/module_xc/xc_functional.h b/source/source_hamilt/module_xc/xc_functional.h index 30f72736839..cce26f3c806 100644 --- a/source/source_hamilt/module_xc/xc_functional.h +++ b/source/source_hamilt/module_xc/xc_functional.h @@ -39,7 +39,8 @@ struct XCFunctionalParameters std::vector exx_erfc_alpha = {"default"}; std::vector xc_exch_ext; std::vector xc_corr_ext; - /// rVV10 nonlocal correlation of vdw_method rvv10, added to any functional; rvv10_b = 0 means off + /// rVV10 nonlocal correlation (vdw_method rvv10) added to any functional, and its parameters b and C + bool rvv10 = false; double rvv10_b = 0.0; double rvv10_c = 0.0; }; diff --git a/source/source_hamilt/module_xc/xc_pot.cpp b/source/source_hamilt/module_xc/xc_pot.cpp index 2680289a85a..21475732dab 100644 --- a/source/source_hamilt/module_xc/xc_pot.cpp +++ b/source/source_hamilt/module_xc/xc_pot.cpp @@ -39,26 +39,18 @@ std::tuple XC_Functional::v_xc( if (use_libxc) { #ifdef __LIBXC - std::tuple etxc_vtxc_v - = XC_Functional_Libxc::v_xc_libxc(XC_Functional::get_func_id(), - nrxx, - ucell->omega, - ucell->tpiba, - chr, - nspin, - domag, - domag_z, - gga_grad, - &(scaling_factor_xc), - hybrid_alpha, - hse_omega); -#ifdef __LIBVDWXC - double& etxc = std::get<0>(etxc_vtxc_v); - double& vtxc = std::get<1>(etxc_vtxc_v); - ModuleBase::matrix& v = std::get<2>(etxc_vtxc_v); - vdw::add_rvv10(*chr, etxc, vtxc, v); -#endif - return etxc_vtxc_v; + return XC_Functional_Libxc::v_xc_libxc(XC_Functional::get_func_id(), + nrxx, + ucell->omega, + ucell->tpiba, + chr, + nspin, + domag, + domag_z, + gga_grad, + &(scaling_factor_xc), + hybrid_alpha, + hse_omega); #else ModuleBase::WARNING_QUIT("v_xc", "compile with LIBXC"); #endif @@ -73,7 +65,7 @@ std::tuple XC_Functional::v_xc( double& etxc = std::get<0>(etxc_vtxc_v); double& vtxc = std::get<1>(etxc_vtxc_v); ModuleBase::matrix& v = std::get<2>(etxc_vtxc_v); - vdw::add_rvv10(*chr, etxc, vtxc, v); + vdw::add_rvv10(runtime_parameters, *chr, etxc, vtxc, v); #endif return etxc_vtxc_v; } @@ -225,7 +217,7 @@ std::tuple XC_Functional::v_xc( etxc *= ucell->omega / chr->rhopw->nxyz; vtxc *= ucell->omega / chr->rhopw->nxyz; #ifdef __LIBVDWXC - vdw::add_rvv10(*chr, etxc, vtxc, v); + vdw::add_rvv10(runtime_parameters, *chr, etxc, vtxc, v); #endif ModuleBase::timer::end("XC_Functional", "v_xc"); diff --git a/source/source_io/module_parameter/read_inp_model.cpp b/source/source_io/module_parameter/read_inp_model.cpp index 85ab1fb4c73..6d68178184e 100644 --- a/source/source_io/module_parameter/read_inp_model.cpp +++ b/source/source_io/module_parameter/read_inp_model.cpp @@ -276,10 +276,12 @@ void ReadInput::item_model() * d3_0: Grimme's DFT-D3(0) dispersion correction method (zero-damping) * d3_bj: Grimme's DFTD3(BJ) dispersion correction method (BJ-damping) * d4: Grimme's DFT-D4 dispersion correction method using the external DFT-D4 library -* rvv10: rVV10 nonlocal correlation evaluated self-consistently with the external libvdwxc library and added to the exchange-correlation energy, potential, forces and stress of dft_functional; see vdw_rvv10_b and vdw_rvv10_c. DFPT and LR-TDDFT forces are not supported, and LR-TDDFT spectra use the kernel selected by xc_kernel without the rVV10 response +* rvv10: rVV10 nonlocal correlation evaluated self-consistently with the external libvdwxc library and added to the exchange-correlation energy, potential, forces and stress of dft_functional; see vdw_rvv10_b and vdw_rvv10_c * none: no vdW correction -[NOTE] ABACUS automatically loads DFT-D3 parameters for supported functionals according to dft_functional setting. Individual user values overwrite the corresponding tabulated values. Setting all four of vdw_s6, vdw_s8, vdw_a1 and vdw_a2 defines a fully custom set and bypasses functional lookup.)"; +[NOTE] ABACUS automatically loads DFT-D3 parameters for supported functionals according to dft_functional setting. Individual user values overwrite the corresponding tabulated values. Setting all four of vdw_s6, vdw_s8, vdw_a1 and vdw_a2 defines a fully custom set and bypasses functional lookup. + +[NOTE] vdw_method rvv10 does not support DFPT or LR-TDDFT forces. LR-TDDFT spectra use the kernel selected by xc_kernel, without the rVV10 response.)"; item.default_value = "none"; item.unit = ""; read_sync_string(input.vdw_method); diff --git a/source/source_io/test_serial/read_input_test.cpp b/source/source_io/test_serial/read_input_test.cpp index bfa2cc68ec1..badce053776 100644 --- a/source/source_io/test_serial/read_input_test.cpp +++ b/source/source_io/test_serial/read_input_test.cpp @@ -225,6 +225,8 @@ TEST_F(InputTest, VdwMethodRvv10) const std::string reason = "Requires esolver_type!=dfpt"; expect_invalid_input(input_file, "vdw_method rvv10\nesolver_type dfpt\n", reason); expect_invalid_input(input_file, "vdw_method rvv10\nesolver_type lr\ncalculation nscf\ncal_force 1\n", reason); + // relax resets cal_force to true, so LR-TDDFT relaxation is rejected without an explicit cal_force + expect_invalid_input(input_file, "vdw_method rvv10\nesolver_type ks-lr\ncalculation relax\ngamma_only 1\n", reason); expect_invalid_input(input_file, "vdw_method rvv10\nvdw_rvv10_b 0\n", "vdw_rvv10_b"); expect_invalid_input(input_file, "vdw_method rvv10\nvdw_rvv10_c -0.1\n", "vdw_rvv10_c"); expect_invalid_input(input_file, "vdw_method RVV10\n", "vdw_method"); diff --git a/source/source_lcao/module_lr/utils/lr_util.cpp b/source/source_lcao/module_lr/utils/lr_util.cpp index d9f1a709f1f..80e24184bde 100644 --- a/source/source_lcao/module_lr/utils/lr_util.cpp +++ b/source/source_lcao/module_lr/utils/lr_util.cpp @@ -107,17 +107,6 @@ namespace LR_Util } } - void check_force_vdw(const std::string& vdw_method, const bool cal_force, const std::string& calculation) - { - const bool relaxation = calculation == "relax" || calculation == "cell-relax"; - if (vdw_method == "rvv10" && (cal_force || relaxation)) - { - throw std::invalid_argument("LR excited-state forces and relaxation do not support vdw_method rvv10:" - " the response of the rVV10 nonlocal correlation is not implemented." - " LR spectra remain available with cal_force=0."); - } - } - const std::vector& prepare_xc_sigma(const std::vector& sigma, const bool is_hse06, std::vector& hse_buffer) diff --git a/source/source_lcao/module_lr/utils/lr_util.h b/source/source_lcao/module_lr/utils/lr_util.h index a0e01e8a4ae..1982274a790 100644 --- a/source/source_lcao/module_lr/utils/lr_util.h +++ b/source/source_lcao/module_lr/utils/lr_util.h @@ -35,9 +35,6 @@ namespace LR_Util /// Reject unsupported NLCC force requests after pseudopotentials have been loaded. void check_force_pp(const UnitCell& cell, bool cal_force, const std::string& calculation); - /// Reject force requests with vdw_method rvv10, whose response kernel is not implemented. - void check_force_vdw(const std::string& vdw_method, bool cal_force, const std::string& calculation); - /// Preserve signed sigma invariants except for the existing HSE06 stabilization. const std::vector& prepare_xc_sigma(const std::vector& sigma, bool is_hse06, diff --git a/source/source_lcao/module_lr/utils/test/test_lr_util.cpp b/source/source_lcao/module_lr/utils/test/test_lr_util.cpp index a138e92ef8a..d1461c4c937 100644 --- a/source/source_lcao/module_lr/utils/test/test_lr_util.cpp +++ b/source/source_lcao/module_lr/utils/test/test_lr_util.cpp @@ -1,8 +1,6 @@ #include "../lr_util.h" #include "source_cell/unitcell.h" - #include -#include #include TEST(LRForcePP, AllowsForcesAndRelaxationWithoutNLCC) @@ -225,11 +223,3 @@ TEST(LRLinearSolver, ReportsSingularComplexFactorization) const int info = LR_Util::lapack_linear_solver(matrix, result, rhs, 2, 1); EXPECT_GT(info, 0); } - -TEST(LRForceVdw, RejectsForcesAndRelaxationWithRvv10) -{ - EXPECT_NO_THROW(LR_Util::check_force_vdw("rvv10", false, "scf")); - EXPECT_NO_THROW(LR_Util::check_force_vdw("d3_bj", true, "relax")); - EXPECT_THROW(LR_Util::check_force_vdw("rvv10", true, "scf"), std::invalid_argument); - EXPECT_THROW(LR_Util::check_force_vdw("rvv10", false, "relax"), std::invalid_argument); -} diff --git a/source/source_pw/module_pwdft/stress_gga.cpp b/source/source_pw/module_pwdft/stress_gga.cpp index 340de6f0e8c..7a8a78a4502 100644 --- a/source/source_pw/module_pwdft/stress_gga.cpp +++ b/source/source_pw/module_pwdft/stress_gga.cpp @@ -19,7 +19,9 @@ void Stress_Func::stress_gga(const UnitCell& ucell, ModuleBase::TITLE("Stress","stress_gga"); ModuleBase::timer::start("Stress","stress_gga"); #ifdef __LIBVDWXC - vdw::add_rvv10_stress(*chr, sigma); + // rVV10 is added before the LDA return below; stress_gga is the XC stress hook of PW, LCAO, SDFT and OFDFT + const XCFunctionalParameters& xc_parameters = XC_Functional::get_runtime_parameters(); + vdw::add_rvv10_stress(xc_parameters, *chr, sigma); #endif int func_type = XC_Functional::get_func_type(); From b378fb261be66de59b0629f592730d7a2d671806 Mon Sep 17 00:00:00 2001 From: 1 Date: Tue, 27 Oct 2026 14:21:06 +0000 Subject: [PATCH 28/28] build(xc): stop for MKL or KML with an MPI libvdwxc With an MPI build, vdwxc_init_mpi creates FFTW-MPI plans and libvdwxc runs them through the public fftw_execute_* symbols. When ABACUS links the FFTW interface of MKL or KML, these symbols resolve to that interface, which cannot execute FFTW-MPI plans, so CMake now stops instead of warning. The install guide builds libvdwxc as a shared library, the form that was validated, and the rebuild hint names both required options. The workspace comment is reworded, and the vdw_method comment in Input_para keeps its develop text. Co-Authored-By: Claude Opus 5.5 --- CMakeLists.txt | 7 ++++--- docs/advanced/install.md | 6 +++--- source/source_hamilt/module_vdw/nonlocal_xc.cpp | 12 ++++++------ source/source_hamilt/module_vdw/vdw.cpp | 2 +- source/source_io/module_parameter/input_parameter.h | 2 +- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 31f382cd78c..671f2736d5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -658,10 +658,11 @@ if(ENABLE_LIBVDWXC) "ENABLE_LIBVDWXC with ENABLE_MPI requires libvdwxc configured with -DVDWXC_ENABLE_MPI=ON" ) endif() - if(MKL_FOUND AND VDWXC_ENABLE_MPI) + # vdwxc_init_mpi creates FFTW-MPI plans, which the FFTW interface of MKL or KML cannot execute. + if(ENABLE_MPI AND (MKL_FOUND OR USE_KML)) message( - WARNING - "libvdwxc with MPI links FFTW3 and FFTW3-MPI, while ABACUS uses the FFTW interface of MKL when MKL is found; this combination is untested. Configure ABACUS without MKL to use the same FFTW3." + FATAL_ERROR + "ENABLE_LIBVDWXC with ENABLE_MPI requires ABACUS built with FFTW3, not with the FFTW interface of MKL or KML" ) endif() endif() diff --git a/docs/advanced/install.md b/docs/advanced/install.md index 7758a9c373c..ceb4fcf9ad7 100644 --- a/docs/advanced/install.md +++ b/docs/advanced/install.md @@ -101,14 +101,14 @@ If `vdw_d4_xc` is set to `default`, ABACUS will infer the functional name from ` ## Build with libvdwxc support -`vdw_method rvv10` evaluates the rVV10 nonlocal correlation with the external [libvdwxc](https://gitlab.com/libvdwxc/libvdwxc) library. Until a tagged libvdwxc release contains the rVV10 interface, build the branch of [libvdwxc merge request !32](https://gitlab.com/libvdwxc/libvdwxc/-/merge_requests/32) (tested at commit `da2d55f`) with libvdwxc's CMake build, which installs the package that ABACUS looks up, and against the FFTW that ABACUS uses. For an MPI build of ABACUS, configure libvdwxc with MPI, which needs FFTW with its MPI library; a static libvdwxc also needs `pkg-config` and FFTW's `fftw3.pc`: +`vdw_method rvv10` evaluates the rVV10 nonlocal correlation with the external [libvdwxc](https://gitlab.com/libvdwxc/libvdwxc) library. Until a tagged libvdwxc release contains the rVV10 interface, build the branch of [libvdwxc merge request !32](https://gitlab.com/libvdwxc/libvdwxc/-/merge_requests/32) (tested at commit `da2d55f`) with libvdwxc's CMake build, which installs the package that ABACUS looks up, and against the FFTW that ABACUS uses. For an MPI build of ABACUS, configure libvdwxc with MPI, which needs FFTW with its MPI library: ```bash -cmake -S libvdwxc -B build-libvdwxc -DVDWXC_ENABLE_MPI=ON -DCMAKE_INSTALL_PREFIX=/path/to/libvdwxc +cmake -S libvdwxc -B build-libvdwxc -DBUILD_SHARED_LIBS=ON -DVDWXC_ENABLE_MPI=ON -DCMAKE_INSTALL_PREFIX=/path/to/libvdwxc cmake --build build-libvdwxc && cmake --install build-libvdwxc ``` -Then pass `-DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON` to CMake for ABACUS and add `/path/to/libvdwxc` to `CMAKE_PREFIX_PATH`. ABACUS hands each k-point pool's density to libvdwxc's distributed interface, so the nonlocal convolution and its memory are shared by the processes of the pool. libvdwxc's MPI build uses FFTW and FFTW-MPI, while ABACUS uses the FFT interface of MKL whenever it finds MKL; that combination is untested, so configure ABACUS without MKL and against the same FFTW (CMake warns otherwise). The legacy Makefile does not build this feature. +Then pass `-DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON` to CMake for ABACUS and add `/path/to/libvdwxc` to `CMAKE_PREFIX_PATH`. ABACUS hands each k-point pool's density to libvdwxc's distributed interface, so the nonlocal convolution and its memory are shared by the processes of the pool. libvdwxc's MPI build plans its FFTs with FFTW-MPI, so an MPI build of ABACUS with libvdwxc must use FFTW3 rather than the FFTW interface of MKL or KML; CMake stops otherwise. The legacy Makefile does not build this feature. In the input file: diff --git a/source/source_hamilt/module_vdw/nonlocal_xc.cpp b/source/source_hamilt/module_vdw/nonlocal_xc.cpp index 483012b792f..4e582307ab2 100644 --- a/source/source_hamilt/module_vdw/nonlocal_xc.cpp +++ b/source/source_hamilt/module_vdw/nonlocal_xc.cpp @@ -36,13 +36,13 @@ namespace const int STRESS_COMPONENTS = 9; /** - * libvdwxc object for one grid and pool, kept between calls. + * libvdwxc object for one FFT grid and pool, kept between calls and rebuilt when either changes. * - * libvdwxc distributes the first axis of its grid in blocks of ceil(n/P) planes, the FFTW-MPI default - * (vdwxc_init_mpi does not report its local slab, so plan_exchange recomputes it). - * It is given the lattice vectors in the cyclic order (a3, a1, a2), so that this axis is the z axis - * that ABACUS distributes and the slab of a process is stored as [z][x][y], the transpose of the - * ABACUS layout [x][y][z]. Whole z planes are moved between the two distributions with all-to-all exchanges. + * libvdwxc gets the lattice vectors in the cyclic order (a3, a1, a2), so that the first axis of its grid + * is the z axis that ABACUS distributes. It splits that axis into blocks of ceil(nz/P) planes, the FFTW-MPI + * default; vdwxc_init_mpi does not report the local slab, so plan_exchange computes it the same way. The + * slab of a process is stored as [z][x][y], the transpose of the ABACUS layout [x][y][z], and whole z + * planes are moved between the two distributions by all-to-all exchanges. */ class Rvv10Workspace { diff --git a/source/source_hamilt/module_vdw/vdw.cpp b/source/source_hamilt/module_vdw/vdw.cpp index ba1f380d441..ac8f6441a4d 100644 --- a/source/source_hamilt/module_vdw/vdw.cpp +++ b/source/source_hamilt/module_vdw/vdw.cpp @@ -99,7 +99,7 @@ std::unique_ptr make_vdw(const UnitCell& ucell, const Input_para& input, st #ifndef __LIBVDWXC ModuleBase::WARNING_QUIT("ModuleHamiltGeneral::ModuleVDW::make_vdw", "rVV10 support was not enabled at build time. " - "Rebuild ABACUS with -DENABLE_LIBVDWXC=ON."); + "Rebuild ABACUS with -DENABLE_LIBXC=ON -DENABLE_LIBVDWXC=ON."); #endif // rVV10 is evaluated inside XC_Functional, so there is no pairwise term here if (plog != nullptr) diff --git a/source/source_io/module_parameter/input_parameter.h b/source/source_io/module_parameter/input_parameter.h index c9582d6eb7d..155415eb033 100644 --- a/source/source_io/module_parameter/input_parameter.h +++ b/source/source_io/module_parameter/input_parameter.h @@ -604,7 +604,7 @@ struct Input_para // vdw // Peize Lin add 2014-03-31, jiyy update 2019-08-01 // ========================================================== - std::string vdw_method = "none"; ///< the method of calculating vdw (none; d2; d3_0; d3_bj; d4; rvv10) + std::string vdw_method = "none"; ///< the method of calculating vdw (none; d2; d3_0; d3_bj; d4) std::string vdw_s6 = "default"; ///< scale parameter of d2/d3_0/d3_bj std::string vdw_s8 = "default"; ///< scale parameter of d3_0/d3_bj std::string vdw_a1 = "default"; ///< damping parameter of d3_0/d3_bj