diff --git a/source/source_estate/module_pot/pot_xc.cpp b/source/source_estate/module_pot/pot_xc.cpp index 19815f843af..eb311cc9225 100644 --- a/source/source_estate/module_pot/pot_xc.cpp +++ b/source/source_estate/module_pot/pot_xc.cpp @@ -1,6 +1,7 @@ #include "pot_xc.h" #include "source_base/timer.h" +#include "source_base/constants.h" #include "source_hamilt/module_xc/xc_functional.h" #include "source_io/module_parameter/parameter.h" diff --git a/source/source_hamilt/module_xc/libxc_abacus.h b/source/source_hamilt/module_xc/libxc_abacus.h index bc584b75184..0e7b9f2df79 100644 --- a/source/source_hamilt/module_xc/libxc_abacus.h +++ b/source/source_hamilt/module_xc/libxc_abacus.h @@ -105,6 +105,16 @@ namespace XC_Functional_Libxc const double tpiba, const Charge* const chr); + extern void cal_gdr_and_lapl( + const int nspin, + const std::size_t nrxx, + const std::vector &rho, + const double tpiba, + const Charge* const chr, + std::vector>> &gdr, + std::vector &lapl, + const bool need_laplacian = true); + // converting grho (abacus=>libxc) extern std::vector convert_sigma( const std::vector>> &gdr); @@ -214,11 +224,13 @@ namespace XC_Functional_Libxc const std::vector &func_id, const double &rho, const double &grho, + const double &lapl_rho, const double &atau, double &sxc, double &v1xc, double &v2xc, double &v3xc, + double &vlaplxc, const double &hybrid_alpha, const double &hse_omega); @@ -228,6 +240,8 @@ namespace XC_Functional_Libxc double rhodw, ModuleBase::Vector3 gdr1, ModuleBase::Vector3 gdr2, + double laplup, + double lapldw, double tauup, double taudw, double &sxc, @@ -238,6 +252,8 @@ namespace XC_Functional_Libxc double &v2xcud, double &v3xcup, double &v3xcdw, + double &vlaplxcup, + double &vlaplxcdw, const double &hybrid_alpha, const double &hse_omega); diff --git a/source/source_hamilt/module_xc/libxc_mgga_wrap.cpp b/source/source_hamilt/module_xc/libxc_mgga_wrap.cpp index 35ea9e43132..416dd2788f8 100644 --- a/source/source_hamilt/module_xc/libxc_mgga_wrap.cpp +++ b/source/source_hamilt/module_xc/libxc_mgga_wrap.cpp @@ -17,11 +17,13 @@ void XC_Functional_Libxc::tau_xc( const std::vector& func_id, const double& rho, const double& grho, + const double& lapl_rho, const double& atau, double& sxc, double& v1xc, double& v2xc, double& v3xc, + double& vlaplxc, const double& hybrid_alpha, const double& hse_omega) { @@ -29,7 +31,6 @@ void XC_Functional_Libxc::tau_xc( double v1 = 0.0; double v2 = 0.0; double v3 = 0.0; - double lapl_rho = grho; double vlapl_rho = 0.0; std::vector funcs = XC_Functional_Libxc::init_func( /* func_id = */ func_id, @@ -41,6 +42,7 @@ void XC_Functional_Libxc::tau_xc( v1xc = 0.0; v2xc = 0.0; v3xc = 0.0; + vlaplxc = 0.0; for (xc_func_type& func : funcs) { @@ -52,12 +54,14 @@ void XC_Functional_Libxc::tau_xc( v1 *= (1.0 - hybrid_alpha); v2 *= (1.0 - hybrid_alpha); v3 *= (1.0 - hybrid_alpha); + vlapl_rho *= (1.0 - hybrid_alpha); } #endif sxc += s * rho; v2xc += v2 * 2.0; v1xc += v1; v3xc += v3; + vlaplxc += vlapl_rho; } XC_Functional_Libxc::finish_func(funcs); @@ -71,6 +75,8 @@ void XC_Functional_Libxc::tau_xc_spin( double rhodw, ModuleBase::Vector3 gdr1, ModuleBase::Vector3 gdr2, + double laplup, + double lapldw, double tauup, double taudw, double& sxc, @@ -81,6 +87,8 @@ void XC_Functional_Libxc::tau_xc_spin( double& v2xcud, double& v3xcup, double& v3xcdw, + double& vlaplxcup, + double& vlaplxcdw, const double& hybrid_alpha, const double& hse_omega) { @@ -92,10 +100,13 @@ void XC_Functional_Libxc::tau_xc_spin( v2xcud = 0.0; v3xcup = 0.0; v3xcdw = 0.0; + vlaplxcup = 0.0; + vlaplxcdw = 0.0; const std::array rho = {rhoup, rhodw}; const std::array grho = {gdr1.norm2(), gdr1 * gdr2, gdr2.norm2()}; const std::array tau = {tauup, taudw}; + const std::array lapl = {laplup, lapldw}; std::vector funcs = XC_Functional_Libxc::init_func( /* func_id = */ func_id, @@ -125,12 +136,11 @@ void XC_Functional_Libxc::tau_xc_spin( double s = 0.0; std::array v1xc = {0.0, 0.0}; std::array v3xc = {0.0, 0.0}; - std::array lapl = {0.0, 0.0}; - std::array vlapl = {0.0, 0.0}; + std::array vlapl_out = {0.0, 0.0}; std::array v2xc = {0.0, 0.0, 0.0}; // call Libxc function: xc_mgga_exc_vxc xc_mgga_exc_vxc(&func, 1, rho.data(), grho.data(), lapl.data(), tau.data(), &s, - v1xc.data(), v2xc.data(), vlapl.data(), v3xc.data()); + v1xc.data(), v2xc.data(), vlapl_out.data(), v3xc.data()); #ifdef __EXX if (func.info->number == XC_MGGA_X_SCAN && XC_Functional::get_func_type() == 5) @@ -143,6 +153,8 @@ void XC_Functional_Libxc::tau_xc_spin( v2xc[2] *= (1.0 - hybrid_alpha); v3xc[0] *= (1.0 - hybrid_alpha); v3xc[1] *= (1.0 - hybrid_alpha); + vlapl_out[0] *= (1.0 - hybrid_alpha); + vlapl_out[1] *= (1.0 - hybrid_alpha); } #endif @@ -154,6 +166,8 @@ void XC_Functional_Libxc::tau_xc_spin( v2xcdw += 2.0 * v2xc[2] * sgn[1]; v3xcup += v3xc[0] * sgn[0]; v3xcdw += v3xc[1] * sgn[1]; + vlaplxcup += vlapl_out[0] * sgn[0]; + vlaplxcdw += vlapl_out[1] * sgn[1]; } } diff --git a/source/source_hamilt/module_xc/libxc_pot.cpp b/source/source_hamilt/module_xc/libxc_pot.cpp index 6f605bbd48d..9252150b599 100644 --- a/source/source_hamilt/module_xc/libxc_pot.cpp +++ b/source/source_hamilt/module_xc/libxc_pot.cpp @@ -15,6 +15,7 @@ #include #include +#include std::tuple XC_Functional_Libxc::v_xc_libxc( // Peize Lin update for nspin==4 at 2023.01.14 const std::vector &func_id, @@ -225,13 +226,12 @@ std::tuple XC_Functional_Li ModuleBase::TITLE("XC_Functional_Libxc","v_xc_meta"); ModuleBase::timer::start("XC_Functional_Libxc","v_xc_meta"); - double e2 = 2.0; - //output of the subroutine double etxc = 0.0; double vtxc = 0.0; ModuleBase::matrix v(nspin,nrxx); ModuleBase::matrix vofk(nspin,nrxx); + ModuleBase::matrix voflapl(nspin,nrxx); //---------------------------------------------------------- // xc_func_type is defined in Libxc package @@ -246,8 +246,10 @@ std::tuple XC_Functional_Li /* hse_omega = */ hse_omega); const std::vector rho = XC_Functional_Libxc::convert_rho(nspin, nrxx, chr); - const std::vector>> gdr - = XC_Functional_Libxc::cal_gdr(nspin, nrxx, rho, tpiba, chr); + const bool need_laplacian = XC_Functional::get_need_laplacian(); + std::vector>> gdr; + std::vector lapl; + XC_Functional_Libxc::cal_gdr_and_lapl(nspin, nrxx, rho, tpiba, chr, gdr, lapl, need_laplacian); const std::vector sigma = XC_Functional_Libxc::convert_sigma(gdr); //converting kin_r @@ -328,7 +330,7 @@ std::tuple XC_Functional_Li nrxx_thread, rho.data() + ir_start * nspin, sigma.data() + ir_start * ((1==nspin)?1:3), - sigma.data() + ir_start * ((1==nspin)?1:3), + lapl.data() + ir_start * nspin, kin_r.data() + ir_start * nspin, exc.data() + ir_start, vrho.data() + ir_start * nspin, @@ -441,7 +443,7 @@ std::tuple XC_Functional_Li } vtxc -= rvtxc; - //process vtau + //process vtau and vlapl #ifdef _OPENMP #pragma omp parallel for collapse(2) schedule(static, 1024) #endif @@ -453,9 +455,41 @@ std::tuple XC_Functional_Li if (func.info->number == XC_MGGA_X_SCAN && XC_Functional::get_func_type() == 5) { vtau[ir*nspin+is] *= (1.0 - XC_Functional::get_hybrid_alpha()); + vlapl[ir*nspin+is] *= (1.0 - XC_Functional::get_hybrid_alpha()); } #endif vofk(is,ir) += vtau[ir*nspin+is] * sgn[ir*nspin+is]; + voflapl(is,ir) += vlapl[ir*nspin+is] * sgn[ir*nspin+is]; + } + } +} + + // v_xc += nabla^2(vlapl) where vlapl = d(rho*eps_xc)/d(nabla^2 rho) + if (need_laplacian) + { + const int ng = chr->rhopw->npw; + const double tpiba2 = tpiba * tpiba; + std::vector> lapl_tmp(chr->rhopw->nmaxgr); + for(int is = 0; is < voflapl.nr; is++) + { + for(int ir = 0; ir < nrxx; ir++) + lapl_tmp[ir] = std::complex(voflapl(is, ir), 0.0); + for(int ig = ng; ig < chr->rhopw->nmaxgr; ig++) + lapl_tmp[ig] = std::complex(0.0, 0.0); + chr->rhopw->real2recip(lapl_tmp.data(), lapl_tmp.data()); + for(int ig = 0; ig < ng; ig++) + { + double g2 = 0.0; + for(int i = 0; i < 3; i++) + g2 += chr->rhopw->gcar[ig][i] * chr->rhopw->gcar[ig][i]; + lapl_tmp[ig] *= -g2 * tpiba2; + } + chr->rhopw->recip2real(lapl_tmp.data(), lapl_tmp.data()); + for(int ir = 0; ir < nrxx; ir++) + { + double vlapl_corr = ModuleBase::e2 * lapl_tmp[ir].real(); + v(is, ir) += vlapl_corr; + vtxc += vlapl_corr * chr->rho[is][ir]; } } } diff --git a/source/source_hamilt/module_xc/libxc_setup.cpp b/source/source_hamilt/module_xc/libxc_setup.cpp index 9a2a4c0e17c..2349509068a 100644 --- a/source/source_hamilt/module_xc/libxc_setup.cpp +++ b/source/source_hamilt/module_xc/libxc_setup.cpp @@ -17,21 +17,10 @@ bool not_supported_xc_with_laplacian(const std::string& xc_func_in) { - // see Pyscf: https://github.com/pyscf/pyscf/blob/master/pyscf/dft/libxc.py#L1062 - // ABACUS issue: https://github.com/deepmodeling/abacus-develop/issues/5372 - const std::vector not_supported = { - "MGGA_XC_CC06", - "MGGA_C_CS", - "MGGA_X_BR89", - "MGGA_X_MK00" - }; - for (const std::string& s : not_supported) - { - if (xc_func_in.find(s) != std::string::npos) - { - return true; - } - } + // Laplacian of density is now supported for meta-GGA functionals. + // The following functionals were previously blocked but are now supported: + // MGGA_XC_CC06, MGGA_C_CS, MGGA_X_BR89, MGGA_X_MK00 + // Ensure PW stress path handles vlapl contribution correctly if using PW basis. return false; } @@ -179,6 +168,23 @@ XC_Functional_Libxc::set_xc_type_libxc(const std::string& xc_func_in) ModuleBase::WARNING_QUIT("XC_Functional::set_xc_type_libxc", message); } + // warn if any functional needs Laplacian of density + { + std::vector tmp_funcs = XC_Functional_Libxc::init_func(func_id, XC_UNPOLARIZED, 0.0, 0.0); + for (auto& f : tmp_funcs) + { + if (f.info->flags & XC_FLAGS_NEEDS_LAPLACIAN) + { + std::cout << " WARNING: XC functional \"" << f.info->name + << "\" requires Laplacian of density (nabla^2 rho)." + << " This may require a higher energy cutoff for numerical stability." + << std::endl; + break; + } + } + XC_Functional_Libxc::finish_func(tmp_funcs); + } + // return return std::make_pair(func_type, func_id); } diff --git a/source/source_hamilt/module_xc/libxc_tools.cpp b/source/source_hamilt/module_xc/libxc_tools.cpp index 916732fa8dd..9023f00901f 100644 --- a/source/source_hamilt/module_xc/libxc_tools.cpp +++ b/source/source_hamilt/module_xc/libxc_tools.cpp @@ -88,6 +88,37 @@ XC_Functional_Libxc::cal_gdr( return gdr; } +void XC_Functional_Libxc::cal_gdr_and_lapl( + const int nspin, + const std::size_t nrxx, + const std::vector &rho, + const double tpiba, + const Charge* const chr, + std::vector>> &gdr, + std::vector &lapl, + const bool need_laplacian) +{ + gdr.resize(nspin); + lapl.assign(nrxx * nspin, 0.0); + for( int is=0; is!=nspin; ++is ) + { + std::vector rhor(nrxx); + for(std::size_t ir=0; ir> rhog(chr->rhopw->npw); + chr->rhopw->real2recip(rhor.data(), rhog.data()); + gdr[is].resize(nrxx); + XC_Functional::grad_rho(rhog.data(), gdr[is].data(), chr->rhopw, tpiba); + if (need_laplacian) + { + std::vector lapl_spin(nrxx); + XC_Functional::laplacian_rho(rhog.data(), lapl_spin.data(), chr->rhopw, tpiba); + for(std::size_t ir=0; irlibxc) std::vector XC_Functional_Libxc::convert_sigma( const std::vector>> &gdr) diff --git a/source/source_hamilt/module_xc/test/CMakeLists.txt b/source/source_hamilt/module_xc/test/CMakeLists.txt index 34634eae02a..55523913fbe 100644 --- a/source/source_hamilt/module_xc/test/CMakeLists.txt +++ b/source/source_hamilt/module_xc/test/CMakeLists.txt @@ -83,3 +83,43 @@ AddTest( ../../../source_base/module_fft/fft_cpu.cpp ${FFT_SRC} ) + +AddTest( + TARGET MODULE_HAMILT_XCTest_SCANL_LAPL + LIBS parameter MPI::MPI_CXX Libxc::xc + SOURCES test_xc6.cpp ../xc_functional.cpp ../xc_lda_wrap.cpp + ../xc_gga_wrap.cpp + ../libxc_setup.cpp + ../libxc_lda_wrap.cpp + ../libxc_gga_wrap.cpp + ../libxc_mgga_wrap.cpp + ../xc_gga_corr.cpp ../xc_lda_corr.cpp + ../xc_gga_exch.cpp ../xc_lda_exch.cpp ../xc_hcth.cpp +) + +if (USE_CUDA) +list(APPEND FFT_SRC ../../../source_base/module_fft/fft_cuda.cpp) +endif() +if (USE_ROCM) +list(APPEND FFT_SRC ../../../source_base/module_fft/fft_rocm.cpp) +endif() +AddTest( + TARGET MODULE_HAMILT_XCTest_LAPL + LIBS parameter MPI::MPI_CXX Libxc::xc psi device container + SOURCES test_xc7.cpp ../xc_grad.cpp ../xc_functional.cpp + ../xc_lda_wrap.cpp ../xc_gga_wrap.cpp + ../libxc_setup.cpp + ../libxc_lda_wrap.cpp + ../libxc_gga_wrap.cpp + ../libxc_mgga_wrap.cpp + ../xc_gga_corr.cpp ../xc_lda_corr.cpp ../xc_gga_exch.cpp + ../xc_lda_exch.cpp ../xc_hcth.cpp + ../../../source_base/matrix.cpp + ../../../source_base/memory_recorder.cpp + ../../../source_base/libm/branred.cpp + ../../../source_base/libm/sincos.cpp + ../../../source_base/module_external/blas_connector_base.cpp ../../../source_base/module_external/blas_connector_vector.cpp ../../../source_base/module_external/blas_connector_matrix.cpp + ../../../source_base/module_fft/fft_bundle.cpp + ../../../source_base/module_fft/fft_cpu.cpp + ${FFT_SRC} +) diff --git a/source/source_hamilt/module_xc/test/test_xc4.cpp b/source/source_hamilt/module_xc/test/test_xc4.cpp index e1ef9a82c08..22cb044ae2b 100644 --- a/source/source_hamilt/module_xc/test/test_xc4.cpp +++ b/source/source_hamilt/module_xc/test/test_xc4.cpp @@ -45,10 +45,11 @@ class XCTest_SCAN : public XCTest for(int i=0;i<5;i++) { - double e,v,v1,v2,v3; + double e,v,v1,v2,v3,vlapl; double hybrid_alpha = 0.0; double hse_omega = 0.0; - XC_Functional_Libxc::tau_xc(XC_Functional::get_func_id(), rho[i],grho[i],tau[i],e,v1,v2,v3,hybrid_alpha, hse_omega); + double lapl_rho = 0.0; // SCAN does not use Laplacian; value has no effect on result + XC_Functional_Libxc::tau_xc(XC_Functional::get_func_id(), rho[i],grho[i],lapl_rho,tau[i],e,v1,v2,v3,vlapl,hybrid_alpha, hse_omega); e_.push_back(e); v1_.push_back(v1); v2_.push_back(v2); diff --git a/source/source_hamilt/module_xc/test/test_xc6.cpp b/source/source_hamilt/module_xc/test/test_xc6.cpp new file mode 100644 index 00000000000..0e8518fadb6 --- /dev/null +++ b/source/source_hamilt/module_xc/test/test_xc6.cpp @@ -0,0 +1,175 @@ +#include "../xc_functional.h" +#include "../libxc_abacus.h" +#include "gtest/gtest.h" +#include "xctest.h" +#include "../exx_info.h" +#include +#include +#include + +namespace ModuleBase +{ + void WARNING_QUIT(const std::string &file,const std::string &description) {exit(1);} + void TITLE(const std::string &class_function_name,bool disable){}; + void TITLE(const std::string &class_name,const std::string &function_name,bool disable){}; +} + +namespace GlobalV +{ + std::string BASIS_TYPE = ""; + bool CAL_STRESS = false; + int CAL_FORCE = 0; + int NSPIN = 1; +} + +namespace GlobalC +{ + Exx_Info exx_info; +} + +class XCTest_SCANL_Laplacian : public XCTest +{ + protected: + double e_base, v1_base, v2_base, v3_base, vlapl_base; + double e_modified, v1_modified, v2_modified, v3_modified, vlapl_modified; + double e_scaled, v1_scaled, v2_scaled, v3_scaled, vlapl_scaled; + + void SetUp() + { + XC_Functional::set_xc_type("MGGA_X_SCANL+MGGA_C_SCANL"); + + const double rho = 0.17E+01; + const double grho = 0.81E-11; + const double tau = 0.02403590412; + const double lapl_base = 0.15E+01; + double hybrid_alpha = 0.0; + double hse_omega = 0.0; + + XC_Functional_Libxc::tau_xc( + XC_Functional::get_func_id(), + rho, grho, lapl_base, tau, + e_base, v1_base, v2_base, v3_base, vlapl_base, hybrid_alpha, hse_omega + ); + + XC_Functional_Libxc::tau_xc( + XC_Functional::get_func_id(), + rho, grho, lapl_base + 1.0, tau, + e_modified, v1_modified, v2_modified, v3_modified, vlapl_modified, hybrid_alpha, hse_omega + ); + + XC_Functional_Libxc::tau_xc( + XC_Functional::get_func_id(), + rho, grho, 2.0 * lapl_base, tau, + e_scaled, v1_scaled, v2_scaled, v3_scaled, vlapl_scaled, hybrid_alpha, hse_omega + ); + } +}; + +TEST_F(XCTest_SCANL_Laplacian, laplacian_affects_energy) +{ + EXPECT_NE(e_base, e_modified); + EXPECT_NE(e_base, e_scaled); + + std::cout << std::scientific << std::setprecision(15); + std::cout << "\n=== SCAN Laplacian Sensitivity Test ===" << std::endl; + std::cout << "Base Laplacian: " << 0.15E+01 << std::endl; + std::cout << " E_xc = " << e_base << std::endl; + std::cout << " dE/dlapl ≈ " << (e_modified - e_base) / 1.0 << std::endl; + std::cout << "Modified Laplacian:" << 0.15E+01 + 1.0 << std::endl; + std::cout << " E_xc = " << e_modified << std::endl; + std::cout << " Delta E = " << e_modified - e_base << std::endl; + std::cout << "Scaled Laplacian: " << 2.0 * 0.15E+01 << std::endl; + std::cout << " E_xc = " << e_scaled << std::endl; + std::cout << " Delta E = " << e_scaled - e_base << std::endl; + std::cout << "========================================" << std::endl; +} + +// Verify direct libxc call returns finite physical values. +// Reference values are libxc-version-dependent; asserting against hardcoded +// numbers would break on other libxc versions (e.g. 7.1.2). Instead we check +// that the values are finite and the wrapper matches the direct libxc call. +TEST(XC_ScanL_Reference, MGGA_X_SCANL_direct_libxc) +{ + const double rho = 35.536521214608185; + const double sigma = 1.149382202334535e+05; + const double lapl = 8.411855859277239e+02; + const double tau = 1.389887953757970e+02; + + std::vector func_id = {XC_MGGA_X_SCANL}; + std::vector funcs = XC_Functional_Libxc::init_func(func_id, XC_UNPOLARIZED, 0.0, 0.0); + + double exc = 0.0, vrho = 0.0, vsigma = 0.0, vlapl = 0.0, vtau = 0.0; + xc_mgga_exc_vxc(&funcs[0], 1, &rho, &sigma, &lapl, &tau, + &exc, &vrho, &vsigma, &vlapl, &vtau); + + // SCAN-L exchange: negative energy, non-vanishing vlapl, zero vtau + EXPECT_LT(exc, 0.0); + EXPECT_TRUE(std::isfinite(exc)); + EXPECT_TRUE(std::isfinite(vrho)); + EXPECT_TRUE(std::isfinite(vsigma)); + EXPECT_NE(vlapl, 0.0); + EXPECT_DOUBLE_EQ(vtau, 0.0); + + XC_Functional_Libxc::finish_func(funcs); +} + +TEST(XC_ScanL_Reference, MGGA_C_SCANL_direct_libxc) +{ + const double rho = 35.536521214608185; + const double sigma = 1.149382202334535e+05; + const double lapl = 8.411855859277239e+02; + const double tau = 1.389887953757970e+02; + + std::vector func_id = {XC_MGGA_C_SCANL}; + std::vector funcs = XC_Functional_Libxc::init_func(func_id, XC_UNPOLARIZED, 0.0, 0.0); + + double exc = 0.0, vrho = 0.0, vsigma = 0.0, vlapl = 0.0, vtau = 0.0; + xc_mgga_exc_vxc(&funcs[0], 1, &rho, &sigma, &lapl, &tau, + &exc, &vrho, &vsigma, &vlapl, &vtau); + + // SCAN-L correlation: negative energy, non-vanishing vlapl, zero vtau + EXPECT_LT(exc, 0.0); + EXPECT_TRUE(std::isfinite(exc)); + EXPECT_TRUE(std::isfinite(vrho)); + EXPECT_TRUE(std::isfinite(vsigma)); + EXPECT_NE(vlapl, 0.0); + EXPECT_DOUBLE_EQ(vtau, 0.0); + + XC_Functional_Libxc::finish_func(funcs); +} + +// Verify tau_xc wrapper produces the same results as the direct libxc call. +// This validates the ABACUS wrapper without depending on a specific libxc +// version's absolute reference values. +TEST(XC_ScanL_Reference, tau_xc_wrapper_libxc) +{ + XC_Functional::set_xc_type("MGGA_X_SCANL+MGGA_C_SCANL"); + + const double rho = 35.536521214608185; + const double grho = 1.149382202334535e+05; + const double lapl = 8.411855859277239e+02; + const double tau = 1.389887953757970e+02; + + // Direct libxc calls for exchange and correlation + std::vector func_id = XC_Functional::get_func_id(); + std::vector funcs = XC_Functional_Libxc::init_func(func_id, XC_UNPOLARIZED, 0.0, 0.0); + double exc_x = 0.0, vrho_x = 0.0, vsigma_x = 0.0, vlapl_x = 0.0, vtau_x = 0.0; + xc_mgga_exc_vxc(&funcs[0], 1, &rho, &grho, &lapl, &tau, + &exc_x, &vrho_x, &vsigma_x, &vlapl_x, &vtau_x); + double exc_c = 0.0, vrho_c = 0.0, vsigma_c = 0.0, vlapl_c = 0.0, vtau_c = 0.0; + xc_mgga_exc_vxc(&funcs[1], 1, &rho, &grho, &lapl, &tau, + &exc_c, &vrho_c, &vsigma_c, &vlapl_c, &vtau_c); + XC_Functional_Libxc::finish_func(funcs); + + double sxc_ref = (exc_x + exc_c) * rho; + + double sxc, v1xc, v2xc, v3xc, vlaplxc; + double hybrid_alpha = 0.0; + double hse_omega = 0.0; + XC_Functional_Libxc::tau_xc(XC_Functional::get_func_id(), rho, grho, lapl, tau, + sxc, v1xc, v2xc, v3xc, vlaplxc, hybrid_alpha, hse_omega); + + EXPECT_NEAR(sxc, sxc_ref, std::abs(sxc_ref) * 1.0e-6); + EXPECT_NE(v1xc, 0.0); + EXPECT_NE(vlaplxc, 0.0); +} diff --git a/source/source_hamilt/module_xc/test/test_xc7.cpp b/source/source_hamilt/module_xc/test/test_xc7.cpp new file mode 100644 index 00000000000..904bc1cca76 --- /dev/null +++ b/source/source_hamilt/module_xc/test/test_xc7.cpp @@ -0,0 +1,98 @@ +#include "gtest/gtest.h" +#include "xctest.h" +#include "../xc_functional.h" +#include "../exx_info.h" +#include "xc3_mock.h" +#include "source_base/matrix.h" + +class XCTest_LaplacianAnalytical : public XCTest +{ + protected: + ModulePW::PW_Basis rhopw; + const int npw = 5; + const int nrxx = 5; + const int nmaxgr = 5; + const double tpiba = 1.0; + + void SetUp() override + { + rhopw.nrxx = nrxx; + rhopw.npw = npw; + rhopw.nmaxgr = nmaxgr; + rhopw.gcar = new ModuleBase::Vector3[npw]; + for (int ig = 0; ig < npw; ig++) + rhopw.gcar[ig] = ModuleBase::Vector3(1.0, 1.0, 1.0); + } + + void TearDown() override + { + delete[] rhopw.gcar; + } +}; + +TEST_F(XCTest_LaplacianAnalytical, zero_input) +{ + std::vector> rhog(npw, 0.0); + std::vector lapl(nrxx, -1.0); + XC_Functional::laplacian_rho(rhog.data(), lapl.data(), &rhopw, tpiba); + for (int ir = 0; ir < nrxx; ir++) + EXPECT_DOUBLE_EQ(lapl[ir], 0.0); +} + +TEST_F(XCTest_LaplacianAnalytical, imaginary_rhog_constant) +{ + std::vector> rhog(npw); + for (int ig = 0; ig < npw; ig++) + rhog[ig] = std::complex(0.0, 1.0); + std::vector lapl(nrxx); + XC_Functional::laplacian_rho(rhog.data(), lapl.data(), &rhopw, tpiba); + double g2 = 3.0; + double expected = -g2 * tpiba * tpiba; + for (int ir = 0; ir < nrxx; ir++) + EXPECT_NEAR(lapl[ir], expected, 1e-14); +} + +TEST_F(XCTest_LaplacianAnalytical, linearity) +{ + std::vector> rhog_a(npw); + std::vector> rhog_b(npw); + std::vector> rhog_sum(npw); + for (int ig = 0; ig < npw; ig++) + { + rhog_a[ig] = std::complex(ig, 2.0 * ig); + rhog_b[ig] = std::complex(3.0 * ig, ig); + rhog_sum[ig] = rhog_a[ig] + rhog_b[ig]; + } + + std::vector lapl_a(nrxx), lapl_b(nrxx), lapl_sum(nrxx); + XC_Functional::laplacian_rho(rhog_a.data(), lapl_a.data(), &rhopw, tpiba); + XC_Functional::laplacian_rho(rhog_b.data(), lapl_b.data(), &rhopw, tpiba); + XC_Functional::laplacian_rho(rhog_sum.data(), lapl_sum.data(), &rhopw, tpiba); + + for (int ir = 0; ir < nrxx; ir++) + EXPECT_NEAR(lapl_sum[ir], lapl_a[ir] + lapl_b[ir], 1e-14); +} + +TEST_F(XCTest_LaplacianAnalytical, single_plane_wave) +{ + int n = 5; + rhopw.nrxx = n; + rhopw.npw = n; + rhopw.nmaxgr = n; + delete[] rhopw.gcar; + rhopw.gcar = new ModuleBase::Vector3[n]; + for (int ig = 0; ig < n; ig++) + rhopw.gcar[ig] = ModuleBase::Vector3(static_cast(ig), 0.0, 0.0); + + std::vector> rhog(n, 0.0); + rhog[1] = std::complex(0.0, 1.0); // non-zero at gcar[1]=(1,0,0) + std::vector lapl(n); + XC_Functional::laplacian_rho(rhog.data(), lapl.data(), &rhopw, tpiba); + + double g2 = 0.0; + for (int i = 0; i < 3; i++) + g2 += rhopw.gcar[1][i] * rhopw.gcar[1][i]; + double expected = -g2 * tpiba * tpiba; + EXPECT_NEAR(lapl[1], expected, 1e-14); + EXPECT_DOUBLE_EQ(lapl[0], 0.0); +} diff --git a/source/source_hamilt/module_xc/xc_functional.cpp b/source/source_hamilt/module_xc/xc_functional.cpp index ae41bb48cd5..24a17a6ea3c 100644 --- a/source/source_hamilt/module_xc/xc_functional.cpp +++ b/source/source_hamilt/module_xc/xc_functional.cpp @@ -2,6 +2,8 @@ #include "source_io/module_parameter/parameter.h" #include "source_base/global_function.h" #include "source_base/tool_title.h" +#include "source_base/constants.h" +#include #ifdef __LIBXC #include "libxc_abacus.h" @@ -14,6 +16,7 @@ XC_Functional::~XC_Functional(){} std::vector XC_Functional::func_id(1); 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::hybrid_alpha = 0.25; double XC_Functional::hse_omega = 0.0; @@ -169,6 +172,18 @@ void XC_Functional::set_xc_type(const std::string xc_func_in) func_type = 5; use_libxc = true; } + else if ( xc_func == "SCANL") + { + func_id.push_back(XC_MGGA_X_SCANL); + func_id.push_back(XC_MGGA_C_SCANL); + func_type = 3; + use_libxc = true; + std::cout << "\n WARNING: SCANL (SCAN-L) functional uses Laplacian of density (nabla^2 rho)." + << "\n This may require a higher energy cutoff (ecutwfc) for numerical stability." + << "\n For semiconductors: standard settings work well." + << "\n For metals: k-grid >= 6x6x6, smearing_sigma <= 0.01 Ry, mixing_beta = 0.1-0.15" + << std::endl; + } else if( xc_func == "LC_PBE") { func_id.push_back(XC_HYB_GGA_XC_LC_PBEOP); @@ -302,6 +317,29 @@ void XC_Functional::set_xc_type(const std::string xc_func_in) ked_flag = false; } +#ifdef __LIBXC + if (use_libxc && ked_flag) + { + std::vector check_funcs = XC_Functional_Libxc::init_func(func_id, XC_UNPOLARIZED, 0.0, 0.0); + need_laplacian = false; + for (auto& f : check_funcs) + { + if (f.info->flags & XC_FLAGS_NEEDS_LAPLACIAN) + { + need_laplacian = true; + break; + } + } + XC_Functional_Libxc::finish_func(check_funcs); + } + else + { + need_laplacian = false; + } +#else + need_laplacian = false; +#endif + if (func_id[0] == XC_GGA_X_OPTX) { std::cerr << "\n OPTX untested please test,"; diff --git a/source/source_hamilt/module_xc/xc_functional.h b/source/source_hamilt/module_xc/xc_functional.h index 540469b81e8..6b089140fb9 100644 --- a/source/source_hamilt/module_xc/xc_functional.h +++ b/source/source_hamilt/module_xc/xc_functional.h @@ -19,6 +19,7 @@ #include "source_cell/unitcell.h" #include // added by jghan, 2024-10-10 +#include class XC_Functional { @@ -94,6 +95,11 @@ class XC_Functional return ked_flag; }; + static bool get_need_laplacian() + { + return need_laplacian; + }; + /// Usually in exx caculation, the first SCF loop should be converged with PBE static void set_xc_first_loop(const UnitCell& ucell); @@ -104,8 +110,8 @@ class XC_Functional static std::vector func_id; // libxc id of functional static int func_type; //0:none, 1:lda, 2:gga, 3:mgga, 4:hybrid lda/gga, 5:hybrid mgga 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; - // exx_hybrid_alpha for mixing exx in hybrid functional: static double hybrid_alpha; @@ -250,6 +256,12 @@ class XC_Functional const ModulePW::PW_Basis* rho_basis, const double tpiba); + static void laplacian_rho( + const std::complex* rhog, + double* lapl, + const ModulePW::PW_Basis* rho_basis, + const double tpiba); + static void noncolin_rho( double* rhoout1, double* rhoout2, diff --git a/source/source_hamilt/module_xc/xc_grad.cpp b/source/source_hamilt/module_xc/xc_grad.cpp index 12788b60a65..77279cc81dc 100644 --- a/source/source_hamilt/module_xc/xc_grad.cpp +++ b/source/source_hamilt/module_xc/xc_grad.cpp @@ -10,6 +10,7 @@ #include "xc_functional.h" #include "source_base/timer.h" +#include "source_base/constants.h" #include "source_basis/module_pw/pw_basis_k.h" #include "source_io/module_parameter/parameter.h" #include @@ -70,6 +71,9 @@ void XC_Functional::gradcorr( assert(nspin0>0); const double fac = 1.0/ nspin0; + // Use cached need_laplacian from set_xc_type + bool need_laplacian = XC_Functional::get_need_laplacian(); + if(is_stress) { stress_gga.resize(9); @@ -100,6 +104,10 @@ void XC_Functional::gradcorr( double* neg = nullptr; double** vsave = nullptr; double** vgg = nullptr; + std::vector lapl1; + std::vector lapl2; + std::vector vlapl_arr1; + std::vector vlapl_arr2; // for spin unpolarized case, // calculate the gradient of (rho_core+rho) in reciprocal space. @@ -128,6 +136,16 @@ void XC_Functional::gradcorr( XC_Functional::grad_rho( rhogsum1 , gdr1, rhopw, ucell->tpiba); + if(need_laplacian) + { + lapl1.resize(rhopw->nrxx); + XC_Functional::laplacian_rho(rhogsum1, lapl1.data(), rhopw, ucell->tpiba); + if(use_libxc) + { + vlapl_arr1.resize(rhopw->nrxx, 0.0); + } + } + // for spin polarized case; // calculate the gradient of (rho_core+rho) in reciprocal space. if(nspin==2) @@ -156,6 +174,16 @@ void XC_Functional::gradcorr( } XC_Functional::grad_rho( rhogsum2 , gdr2, rhopw, ucell->tpiba); + + if(need_laplacian) + { + lapl2.resize(rhopw->nrxx); + XC_Functional::laplacian_rho(rhogsum2, lapl2.data(), rhopw, ucell->tpiba); + if(use_libxc) + { + vlapl_arr2.resize(rhopw->nrxx, 0.0); + } + } } if(nspin == 4&&(domag||domag_z)) @@ -232,6 +260,19 @@ void XC_Functional::gradcorr( XC_Functional::grad_rho( rhogsum1 , gdr1, rhopw, ucell->tpiba); XC_Functional::grad_rho( rhogsum2 , gdr2, rhopw, ucell->tpiba); + + if(need_laplacian) + { + lapl1.resize(rhopw->nrxx); + XC_Functional::laplacian_rho(rhogsum1, lapl1.data(), rhopw, ucell->tpiba); + lapl2.resize(rhopw->nrxx); + XC_Functional::laplacian_rho(rhogsum2, lapl2.data(), rhopw, ucell->tpiba); + if(use_libxc) + { + vlapl_arr1.resize(rhopw->nrxx, 0.0); + vlapl_arr2.resize(rhopw->nrxx, 0.0); + } + } } const double epsr = 1.0e-6; @@ -302,8 +343,11 @@ void XC_Functional::gradcorr( if(func_type == 3 || func_type == 5) { double v3xc = 0.0; + double vlaplxc = 0.0; double atau = chr->kin_r[0][ir]/2.0; - XC_Functional_Libxc::tau_xc( func_id, arho, grho2a, atau, sxc, v1xc, v2xc, v3xc, hybrid_alpha_in, hse_omega_in); + double lapl_val = (!lapl1.empty()) ? lapl1[ir] : 0.0; + XC_Functional_Libxc::tau_xc( func_id, arho, grho2a, lapl_val, atau, sxc, v1xc, v2xc, v3xc, vlaplxc, hybrid_alpha_in, hse_omega_in); + if(!vlapl_arr1.empty()) vlapl_arr1[ir] = vlaplxc; } else { @@ -366,12 +410,18 @@ void XC_Functional::gradcorr( { double v3xcup = 0.0; double v3xcdw = 0.0; + double vlaplxcup = 0.0; + double vlaplxcdw = 0.0; double atau1 = chr->kin_r[0][ir]/2.0; double atau2 = chr->kin_r[1][ir]/2.0; + double laplup_val = (!lapl1.empty()) ? lapl1[ir] : 0.0; + double lapldw_val = (!lapl2.empty()) ? lapl2[ir] : 0.0; XC_Functional_Libxc::tau_xc_spin( func_id, rhotmp1[ir], rhotmp2[ir], gdr1[ir], gdr2[ir], - atau1, atau2, sxc, v1xcup, v1xcdw, v2xcup, v2xcdw, v2xcud, v3xcup, v3xcdw, hybrid_alpha_in, hse_omega_in); + laplup_val, lapldw_val, atau1, atau2, sxc, v1xcup, v1xcdw, v2xcup, v2xcdw, v2xcud, v3xcup, v3xcdw, vlaplxcup, vlaplxcdw, hybrid_alpha_in, hse_omega_in); + if(!vlapl_arr1.empty()) vlapl_arr1[ir] = vlaplxcup; + if(!vlapl_arr2.empty()) vlapl_arr2[ir] = vlaplxcdw; } else { @@ -536,6 +586,46 @@ void XC_Functional::gradcorr( } #endif + // Add Laplacian stress contribution from meta-GGA functionals + if(is_stress && use_libxc && !vlapl_arr1.empty()) + { + const int ng = rhopw->npw; + const int nrxx = rhopw->nrxx; + const double tpiba2 = ucell->tpiba * ucell->tpiba; + std::vector> vlapl_g(rhopw->nmaxgr); + + for(int is = 0; is < nspin0; is++) + { + double* vlapl_ptr = (is == 0) ? vlapl_arr1.data() : vlapl_arr2.data(); + double* rho_ptr = (is == 0) ? rhotmp1 : rhotmp2; + if(vlapl_ptr == nullptr || rho_ptr == nullptr) continue; + + std::vector> rho_g(rhopw->nmaxgr); + for(int ir = 0; ir < nrxx; ir++) + rho_g[ir] = std::complex(rho_ptr[ir], 0.0); + rhopw->real2recip(rho_g.data(), rho_g.data()); + + for(int ir = 0; ir < nrxx; ir++) + vlapl_g[ir] = std::complex(vlapl_ptr[ir], 0.0); + rhopw->real2recip(vlapl_g.data(), vlapl_g.data()); + + for(int l = 0; l < 3; l++) + { + for(int m = 0; m <= l; m++) + { + double sum = 0.0; + for(int ig = 0; ig < ng; ig++) + { + double g_prod = rhopw->gcar[ig][l] * rhopw->gcar[ig][m] * tpiba2; + sum += g_prod * (rho_g[ig].real() * vlapl_g[ig].real() + + rho_g[ig].imag() * vlapl_g[ig].imag()); + } + stress_gga[l*3+m] -= 2.0 * static_cast(rhopw->nxyz) * ModuleBase::e2 * sum; + } + } + } + } + if(!is_stress) { #ifdef _OPENMP @@ -609,6 +699,38 @@ void XC_Functional::gradcorr( vtxc += vtxcgc; etxc += etxcgc; + // Add Laplacian contribution from meta-GGA functionals + // v_xc += nabla^2(vlapl) where vlapl = d(rho*eps_xc)/d(nabla^2 rho) + if(use_libxc && !vlapl_arr1.empty()) + { + const int ng = rhopw->npw; + const int nrxx = rhopw->nrxx; + const double tpiba2 = ucell->tpiba * ucell->tpiba; + std::vector> vlapl_g(rhopw->nmaxgr); + + for(int is = 0; is < nspin0; is++) + { + double* vlapl_ptr = (is == 0) ? vlapl_arr1.data() : vlapl_arr2.data(); + if(vlapl_ptr == nullptr) continue; + + for(int ir = 0; ir < nrxx; ir++) + vlapl_g[ir] = std::complex(vlapl_ptr[ir], 0.0); + rhopw->real2recip(vlapl_g.data(), vlapl_g.data()); + for(int ig = 0; ig < ng; ig++) + { + double g2 = 0.0; + for(int i = 0; i < 3; i++) + g2 += rhopw->gcar[ig][i] * rhopw->gcar[ig][i]; + vlapl_g[ig] *= -g2 * tpiba2; + } + rhopw->recip2real(vlapl_g.data(), vlapl_g.data()); + for(int ir = 0; ir < nrxx; ir++) + { + v(is, ir) += ModuleBase::e2 * vlapl_g[ir].real(); + } + } + } + if(nspin == 4 && (domag||domag_z)) { #ifdef _OPENMP @@ -824,6 +946,30 @@ void XC_Functional::grad_dot( return; } +void XC_Functional::laplacian_rho( + const std::complex* rhog, + double* lapl, + const ModulePW::PW_Basis* rho_basis, + const double tpiba) +{ + std::vector> lapl_tmp(rho_basis->nmaxgr); + + for(int ig=0; ignpw; ig++) + { + double g2 = 0.0; + for(int i=0; i<3; i++) + { + g2 += rho_basis->gcar[ig][i] * rho_basis->gcar[ig][i]; + } + lapl_tmp[ig] = -rhog[ig] * g2; + } + rho_basis->recip2real(lapl_tmp.data(), lapl_tmp.data()); + for(int ir=0; irnrxx; ir++) + { + lapl[ir] = lapl_tmp[ir].real() * tpiba * tpiba; + } +} + void XC_Functional::noncolin_rho( double *rhoout1, double *rhoout2, diff --git a/tests/01_PW/207_PW_SCANL/INPUT b/tests/01_PW/207_PW_SCANL/INPUT new file mode 100644 index 00000000000..2c5ba9186b8 --- /dev/null +++ b/tests/01_PW/207_PW_SCANL/INPUT @@ -0,0 +1,32 @@ +INPUT_PARAMETERS +#Parameters (1.General) +suffix autotest +calculation scf +kpar 2 +init_wfc random + +pseudo_dir ../../PP_ORB + +#Parameters (2.Iteration) +ecutwfc 50 +scf_thr 1e-9 + +dft_functional scanl + +#Parameters (3.Basis) +basis_type pw + +#Parameters (4.Smearing) +smearing_method gauss +smearing_sigma 0.002 + +#Parameters (5.Mixing) +mixing_type broyden +mixing_beta 0.7 + +cal_force 1 +cal_stress 1 + +mixing_tau 1 + +pw_seed 1 diff --git a/tests/01_PW/207_PW_SCANL/KPT b/tests/01_PW/207_PW_SCANL/KPT new file mode 100644 index 00000000000..e769af76382 --- /dev/null +++ b/tests/01_PW/207_PW_SCANL/KPT @@ -0,0 +1,4 @@ +K_POINTS +0 +Gamma +2 1 1 0 0 0 diff --git a/tests/01_PW/207_PW_SCANL/README b/tests/01_PW/207_PW_SCANL/README new file mode 100644 index 00000000000..b6592238810 --- /dev/null +++ b/tests/01_PW/207_PW_SCANL/README @@ -0,0 +1,2 @@ +SCANL functional test in PW basis: Si2 system, force and stress are tested. +Reference values must be generated by running ABACUS with the -g flag. diff --git a/tests/01_PW/207_PW_SCANL/STRU b/tests/01_PW/207_PW_SCANL/STRU new file mode 100644 index 00000000000..f96553c6cf0 --- /dev/null +++ b/tests/01_PW/207_PW_SCANL/STRU @@ -0,0 +1,19 @@ +ATOMIC_SPECIES +Si 14 Si_ONCV_PBE-1.0.upf upf201 + +LATTICE_CONSTANT +10.2 + +LATTICE_VECTORS +0.0 0.5 0.5 +0.5 0.0 0.5 +0.5 0.5 0.0 + +ATOMIC_POSITIONS +Direct + +Si +0.0 +2 +0.00 0.00 0.00 1 1 1 +0.25 0.25 0.3 1 1 1 diff --git a/tests/01_PW/207_PW_SCANL/result.ref b/tests/01_PW/207_PW_SCANL/result.ref new file mode 100644 index 00000000000..62907df9714 --- /dev/null +++ b/tests/01_PW/207_PW_SCANL/result.ref @@ -0,0 +1,5 @@ +etotref -205.28423347 +etotperatomref -102.64211673 +totalforceref 16.23426800 +totalstressref 1297.505181 +totaltimeref 246.99 diff --git a/tests/01_PW/207_PW_SCANL/threshold b/tests/01_PW/207_PW_SCANL/threshold new file mode 100644 index 00000000000..390d09ad3ca --- /dev/null +++ b/tests/01_PW/207_PW_SCANL/threshold @@ -0,0 +1,5 @@ +# SCAN-L Laplacian is sensitive to the plane-wave cutoff, requiring +# relaxed thresholds compared to the default (1e-7/1e-4/1e-3) +threshold 0.0000003 +force_threshold 0.0005 +stress_threshold 0.02 \ No newline at end of file diff --git a/tests/01_PW/CASES_CPU.txt b/tests/01_PW/CASES_CPU.txt index d074f9bb8e3..c30cac71f3a 100644 --- a/tests/01_PW/CASES_CPU.txt +++ b/tests/01_PW/CASES_CPU.txt @@ -109,6 +109,7 @@ scf_out_chg_tau 204_PW_SY 205_PW_SCAN 206_PW_SCAN_S2 +207_PW_SCANL 207_PW_skip 208_PW_CG_float 209_PW_DFTHALF