diff --git a/source/Makefile.Objects b/source/Makefile.Objects index d800d4f365a..7104d8a62e6 100644 --- a/source/Makefile.Objects +++ b/source/Makefile.Objects @@ -211,7 +211,6 @@ OBJS_CELL=atom_pseudo.o\ print_cell.o\ setup_nonlocal.o\ klist.o\ - k_vector_utils.o\ reciprocal_grid.o\ cell_index.o\ cell_tools.o\ @@ -375,14 +374,23 @@ OBJS_HAMILT_OF=kedf_tf.o\ kedf_manager.o\ evolve_ofdft.o\ -OBJS_DFPT=dfpt_metal.o\ - dfpt_hamilt_shift.o\ +OBJS_DFPT=dfpt_hamilt_shift.o\ dfpt_kq_basis.o\ + dfpt_metal.o\ dfpt_pert.o\ + dfpt_pert_nl.o\ + dfpt_pert_vkb.o\ dfpt_phon.o\ + dfpt_phon_elec.o\ + dfpt_phon_ewald.o\ dfpt_pw.o\ dfpt_pw_data.o\ + dfpt_pw_init.o\ + dfpt_pw_q0.o\ + dfpt_pw_run.o\ + dfpt_pw_solve.o\ dfpt_q0.o\ + dfpt_q0_pos.o\ dfpt_rho.o\ dfpt_stern.o diff --git a/source/source_cell/CMakeLists.txt b/source/source_cell/CMakeLists.txt index 9792a2279ed..3c74e1cd4fa 100644 --- a/source/source_cell/CMakeLists.txt +++ b/source/source_cell/CMakeLists.txt @@ -33,7 +33,6 @@ add_library( read_stru.cpp print_cell.cpp read_atom_species.cpp - k_vector_utils.cpp sep.cpp sep_cell.cpp qlist.cpp diff --git a/source/source_cell/k_vector_utils.cpp b/source/source_cell/k_vector_utils.cpp deleted file mode 100644 index bc1366209f8..00000000000 --- a/source/source_cell/k_vector_utils.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/** - * @file k_vector_utils.cpp - * @brief Implementation of k-vector utility functions. - * @author rhx (created on 25-6-3) - * - * @note Since 2026-08-14 these free functions are thin wrappers around the - * spin-free members of ModuleCell::ReciprocalGrid / the K_Vectors - * IBZ orchestration, so that existing call sites (esolver_fp.cpp, - * klist.cpp, tests) keep working unchanged. - */ -#include "k_vector_utils.h" - -#include "klist.h" -#include "source_base/global_variable.h" -#include "source_base/matrix3.h" - -#include "source_base/formatter.h" -#include "source_base/parallel_common.h" -#include "source_base/parallel_reduce.h" - -namespace KVectorUtils -{ -void kvec_d2c(K_Vectors& kv, const ModuleBase::Matrix3& reciprocal_vec) -{ - kv.kvec_d2c(reciprocal_vec); -} -void kvec_c2d(K_Vectors& kv, const ModuleBase::Matrix3& latvec) -{ - kv.kvec_c2d(latvec); -} - -void set_both_kvec(K_Vectors& kv, const ModuleBase::Matrix3& G, const ModuleBase::Matrix3& R, std::string& skpt) -{ - kv.set_both_kvec(G, R, skpt); -} - -void set_after_vc(K_Vectors& kv, const int& nspin_in, const ModuleBase::Matrix3& reciprocal_vec) -{ - GlobalV::ofs_running << "\n SETUP K-POINTS" << std::endl; - kv.set_nspin(nspin_in); - ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running, "nspin", kv.get_nspin()); - - // set cartesian k vectors. - kv.kvec_d2c(reciprocal_vec); - - std::string table; - table += "K-POINTS DIRECT COORDINATES\n"; - table += FmtCore::format("%8s%12s%12s%12s%8s\n", "KPOINTS", "DIRECT_X", "DIRECT_Y", "DIRECT_Z", "WEIGHT"); - for (int i = 0; i < kv.get_nks(); i++) - { - table += FmtCore::format("%8d%12.8f%12.8f%12.8f%8.4f\n", - i + 1, - kv.kvec_d[i].x, - kv.kvec_d[i].y, - kv.kvec_d[i].z, - kv.wk[i]); - } - GlobalV::ofs_running << table << std::endl; - - kv.kd_done = true; - kv.kc_done = true; - - print_klists(kv, GlobalV::ofs_running); -} - -void print_klists(const K_Vectors& kv, std::ofstream& ofs) -{ - kv.print_klists(ofs); -} - -#ifdef __MPI -void kvec_mpi_k(K_Vectors& kv) -{ - ModuleBase::TITLE("KVectorUtils", "kvec_mpi_k"); - - Parallel_Common::bcast_bool(kv.kc_done); - - Parallel_Common::bcast_bool(kv.kd_done); - - Parallel_Common::bcast_int(kv.nspin); - - Parallel_Common::bcast_int(kv.nkstot); - - Parallel_Common::bcast_int(kv.nkstot_full); - - Parallel_Common::bcast_int(kv.nmp, 3); - - kv.kl_segids.resize(kv.nkstot); - Parallel_Common::bcast_int(kv.kl_segids.data(), kv.nkstot); - - Parallel_Common::bcast_double(kv.koffset, 3); - - kv.nks = kv.para_k.nks_pool[GlobalV::MY_POOL]; - - GlobalV::ofs_running << std::endl; - ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running, "Number of k-points in this process", kv.nks); - int nks_minimum = kv.nks; - - Parallel_Reduce::reduce_min(nks_minimum); - - if (nks_minimum == 0) - { - ModuleBase::WARNING_QUIT("K_Vectors::mpi_k()", " nks == 0, some processor have no k points!"); - } - else - { - ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running, "Minimum distributed k-point number", nks_minimum); - } - - std::vector isk_aux(kv.nkstot); - std::vector wk_aux(kv.nkstot); - std::vector kvec_c_aux(kv.nkstot * 3); - std::vector kvec_d_aux(kv.nkstot * 3); - std::vector kvec_c_full_aux(kv.nkstot_full * 3); - - // collect and process in rank 0 - if (GlobalV::MY_RANK == 0) - { - for (int ik = 0; ik < kv.nkstot; ik++) - { - isk_aux[ik] = kv.isk[ik]; - wk_aux[ik] = kv.wk[ik]; - kvec_c_aux[3 * ik] = kv.kvec_c[ik].x; - kvec_c_aux[3 * ik + 1] = kv.kvec_c[ik].y; - kvec_c_aux[3 * ik + 2] = kv.kvec_c[ik].z; - kvec_d_aux[3 * ik] = kv.kvec_d[ik].x; - kvec_d_aux[3 * ik + 1] = kv.kvec_d[ik].y; - kvec_d_aux[3 * ik + 2] = kv.kvec_d[ik].z; - kvec_c_full_aux[3 * ik] = kv.kvec_c_full[ik].x; - kvec_c_full_aux[3 * ik + 1] = kv.kvec_c_full[ik].y; - kvec_c_full_aux[3 * ik + 2] = kv.kvec_c_full[ik].z; - } - } - - // broadcast k point data to all processors - Parallel_Common::bcast_int(isk_aux.data(), kv.nkstot); - - Parallel_Common::bcast_double(wk_aux.data(), kv.nkstot); - Parallel_Common::bcast_double(kvec_c_aux.data(), kv.nkstot * 3); - Parallel_Common::bcast_double(kvec_d_aux.data(), kv.nkstot * 3); - Parallel_Common::bcast_double(kvec_c_full_aux.data(), kv.nkstot_full * 3); - - // process k point data in each processor - kv.renew(kv.nks * kv.nspin); - - // distribute - int k_index = 0; - - for (int i = 0; i < kv.nks; i++) - { - // 3 is because each k point has three value:kx, ky, kz - k_index = i + kv.para_k.startk_pool[GlobalV::MY_POOL]; - kv.kvec_c[i].x = kvec_c_aux[k_index * 3]; - kv.kvec_c[i].y = kvec_c_aux[k_index * 3 + 1]; - kv.kvec_c[i].z = kvec_c_aux[k_index * 3 + 2]; - kv.kvec_d[i].x = kvec_d_aux[k_index * 3]; - kv.kvec_d[i].y = kvec_d_aux[k_index * 3 + 1]; - kv.kvec_d[i].z = kvec_d_aux[k_index * 3 + 2]; - kv.kvec_c_full[i].x = kvec_c_full_aux[k_index * 3]; - kv.kvec_c_full[i].y = kvec_c_full_aux[k_index * 3 + 1]; - kv.kvec_c_full[i].z = kvec_c_full_aux[k_index * 3 + 2]; - kv.wk[i] = wk_aux[k_index]; - kv.isk[i] = isk_aux[k_index]; - } - -#ifdef __EXX - if (ModuleSymmetry::Symmetry::symm_flag == 1) - { // bcast kstars - kv.kstars.resize(kv.nkstot); - for (int ikibz = 0; ikibz < kv.nkstot; ++ikibz) - { - int starsize = kv.kstars[ikibz].size(); - Parallel_Common::bcast_int(starsize); - auto ks = kv.kstars[ikibz].begin(); - for (int ik = 0; ik < starsize; ++ik) - { - int isym = 0; - ModuleBase::Vector3 ks_vec(0, 0, 0); - if (GlobalV::MY_RANK == 0) - { - isym = ks->first; - ks_vec = ks->second; - ++ks; - } - Parallel_Common::bcast_int(isym); - Parallel_Common::bcast_double(ks_vec.x); - Parallel_Common::bcast_double(ks_vec.y); - Parallel_Common::bcast_double(ks_vec.z); - if (GlobalV::MY_RANK != 0) - { - kv.kstars[ikibz].insert(std::make_pair(isym, ks_vec)); - } - } - } - } -#endif -} // END SUBROUTINE -#endif - -void kvec_ibz_kpoint(K_Vectors& kv, - const ModuleSymmetry::Symmetry& symm, - bool use_symm, - std::string& skpt, - const UnitCell& ucell, - bool& match) -{ - kv.reduce_by_symmetry(ucell, symm, use_symm, skpt, match); -} -} // namespace KVectorUtils diff --git a/source/source_cell/k_vector_utils.h b/source/source_cell/k_vector_utils.h deleted file mode 100644 index 124ecf2ed2e..00000000000 --- a/source/source_cell/k_vector_utils.h +++ /dev/null @@ -1,140 +0,0 @@ -/** - * @file k_vector_utils.h - * @brief Utility functions for k-vector operations. - * @author rhx (created on 25-6-3) - */ -#ifndef K_VECTOR_UTILS_H -#define K_VECTOR_UTILS_H - -#include "source_base/matrix3.h" -#include "source_cell/unitcell.h" - -class K_Vectors; - -namespace KVectorUtils -{ -/** - * @brief Convert k-vectors from direct to Cartesian coordinates. - * - * @param kv K_Vectors object [in/out] - * @param reciprocal_vec reciprocal lattice vectors [in] - */ -void kvec_d2c(K_Vectors& kv, const ModuleBase::Matrix3& reciprocal_vec); - -/** - * @brief Convert k-vectors from Cartesian to direct coordinates. - * - * @param kv K_Vectors object [in/out] - * @param latvec lattice vectors [in] - */ -void kvec_c2d(K_Vectors& kv, const ModuleBase::Matrix3& latvec); - -/** - * @brief Sets both the direct and Cartesian k-vectors. - * - * This function sets both the direct and Cartesian k-vectors based on the input parameters. - * It also checks the k-point type and sets the corresponding flags. - * - * @param kv The K_Vectors object containing the k-point information. - * @param G The reciprocal lattice matrix. - * @param R The real space lattice matrix. - * @param skpt A string to store the k-point table. - * - * @return void - * - * @note If the k-point type is neither "Cartesian" nor "Direct", an error message will be printed. - * @note The function sets the flags kd_done and kc_done to indicate whether the direct and Cartesian k-vectors have - * been set, respectively. - * @note The function also prints a table of the direct k-vectors and their weights. - * @note If the function is called by the master process (MY_RANK == 0), the k-point table is also stored in the - * string skpt. - */ -void set_both_kvec(K_Vectors& kv, const ModuleBase::Matrix3& G, const ModuleBase::Matrix3& R, std::string& skpt); - -/** - * @brief Sets up the k-points after a volume change. - * - * This function sets up the k-points after a volume change in the system. - * It sets the Cartesian and direct k-vectors based on the new reciprocal and real space lattice vectors. - * - * @param kv The K_Vectors object containing the k-point information. - * @param nspin_in The number of spins. 1 for non-spin-polarized calculations and 2 for spin-polarized calculations. - * @param reciprocal_vec The new reciprocal lattice matrix. - * - * @return void - * - * @note The function first sets the number of spins (nspin) to the input value. - * @note The direct k-vectors have been set (kd_done = true) but the Cartesian k-vectors have not (kc_done = - * false) after a volume change. The function calculates the Cartesian k-vectors by multiplying the direct k-vectors - * with the reciprocal lattice matrix. - * @note The function also prints a table of the direct k-vectors and their weights. - * @note The function calls the print_klists function to print the k-points in both Cartesian and direct - * coordinates. - */ -void set_after_vc(K_Vectors& kv, const int& nspin, const ModuleBase::Matrix3& G); - -/** - * @brief Prints the k-points in both Cartesian and direct coordinates. - * - * This function prints the k-points in both Cartesian and direct coordinates to the output file stream. - * The output includes the index, x, y, and z coordinates, and the weight of each k-point. - * - * @param ofs The output file stream to which the k-points are printed. - * - * @return void - * - * @note The function first checks if the total number of k-points (nkstot) is less than the number of k-points for - * the current spin (nks). If so, it prints an error message and quits. - * @note The function prints the k-points in a table format, with separate tables for Cartesian and direct - * coordinates. - * @note The function uses the FmtCore::format function to format the output. - */ -void print_klists(const K_Vectors& kv, std::ofstream& ofs); - -// step 3 : mpi kpoints information. - -/** - * @brief Distributes k-points among MPI processes. - * - * This function distributes the k-points among the MPI processes. Each process gets a subset of the k-points to - * work on. The function also broadcasts various variables related to the k-points to all processes. - * - * @param kv The K_Vectors object containing the k-point information. - * - * @return void - * - * @note This function is only compiled and used if MPI is enabled. - * @note The function assumes that the number of k-points (nkstot) is greater than 0. - * @note The function broadcasts the flags kc_done and kd_done, the number of spins (nspin), the total number of - * k-points (nkstot), the full number of k-points (nkstot_full), the Monkhorst-Pack grid (nmp), the k-point offsets - * (koffset), and the segment IDs of the k-points (kl_segids). - * @note The function also broadcasts the indices of the k-points (isk), their weights (wk), and their Cartesian and - * direct coordinates (kvec_c and kvec_d). - * @note If a process has no k-points to work on, the function will quit with an error message. - */ -#ifdef __MPI -void kvec_mpi_k(K_Vectors& kv); -#endif // __MPI - -/** - * @brief Generates irreducible k-points in the Brillouin zone considering symmetry operations. - * - * This function calculates the irreducible k-points (IBZ) from the given k-points, taking into - * account the symmetry of the unit cell. It updates the symmetry-matched k-points and generates - * the corresponding weight for each k-point. - * - * @param symm The symmetry information of the system. - * @param use_symm A flag indicating whether to use symmetry operations. - * @param skpt A string to store the formatted k-points information. - * @param ucell The unit cell of the crystal. - * @param match A boolean flag that indicates if the results matches the real condition. - */ -void kvec_ibz_kpoint(K_Vectors& kv, - const ModuleSymmetry::Symmetry& symm, - bool use_symm, - std::string& skpt, - const UnitCell& ucell, - bool& match); -} // namespace KVectorUtils - -#endif // K_VECTOR_UTILS_H diff --git a/source/source_cell/klist.cpp b/source/source_cell/klist.cpp index 917ddd75ded..1cbfd4d56bd 100644 --- a/source/source_cell/klist.cpp +++ b/source/source_cell/klist.cpp @@ -4,7 +4,6 @@ */ #include "klist.h" -#include "k_vector_utils.h" #include "source_base/formatter.h" #include "source_base/parallel_common.h" #include "source_base/parallel_global.h" @@ -84,7 +83,7 @@ void K_Vectors::set(const UnitCell& ucell, this->nspin = (this->nspin == 4) ? 1 : this->nspin; - bool read_succesfully = this->read_kpoints(ucell, k_file_name, gamma_only_local_, kspacing, kmesh_type_, koffset); + bool read_succesfully = this->read_kpoints(ucell, k_file_name, gamma_only_local_, kspacing, kmesh_type_, koffset, ofs); #ifdef __MPI Parallel_Common::bcast_bool(read_succesfully); #endif @@ -115,7 +114,7 @@ void K_Vectors::set(const UnitCell& ucell, { bool match = true; // calculate kpoints in IBZ and reduce kpoints according to symmetry - KVectorUtils::kvec_ibz_kpoint(*this, symm, ModuleSymmetry::Symmetry::symm_flag, skpt1, ucell, match); + this->reduce_by_symmetry(ucell, symm, ModuleSymmetry::Symmetry::symm_flag, skpt1, match); #ifdef __MPI Parallel_Common::bcast_bool(match); #endif @@ -130,7 +129,7 @@ void K_Vectors::set(const UnitCell& ucell, std::cout << "Automatically set symmetry to 0 and continue ..." << std::endl; ModuleSymmetry::Symmetry::symm_flag = 0; match = true; - KVectorUtils::kvec_ibz_kpoint(*this, symm, ModuleSymmetry::Symmetry::symm_flag, skpt1, ucell, match); + this->reduce_by_symmetry(ucell, symm, ModuleSymmetry::Symmetry::symm_flag, skpt1, match); } else { ModuleBase::WARNING_QUIT("K_Vectors::ibz_kpoint", "Possible solutions: \n \ @@ -146,8 +145,7 @@ void K_Vectors::set(const UnitCell& ucell, // Improve k point information // Complement the coordinates of k point -// this->set_both_kvec(reciprocal_vec, latvec, skpt2); - KVectorUtils::set_both_kvec(*this, reciprocal_vec, latvec, skpt2); + this->set_both_kvec(reciprocal_vec, latvec, skpt2, ofs); if (GlobalV::MY_RANK == 0) { @@ -174,11 +172,11 @@ void K_Vectors::set(const UnitCell& ucell, nspin_in); // assign k points to several process pools #ifdef __MPI // distribute K point data to the corresponding process - KVectorUtils::kvec_mpi_k(*this); + this->mpi_k(ofs); #endif // set the k vectors for the up and down spin - this->set_kup_and_kdw(); + this->set_kup_and_kdw(ofs); // initialize ibz_index this->ibz_index.resize(this->nkstot_full); @@ -190,7 +188,7 @@ void K_Vectors::set(const UnitCell& ucell, // get ik2iktot this->cal_ik_global(); - KVectorUtils::print_klists(*this, ofs); + this->print_klists(ofs); // std::cout << " NUMBER OF K-POINTS : " << nkstot << std::endl; @@ -214,7 +212,8 @@ bool K_Vectors::read_kpoints(const UnitCell& ucell, const bool gamma_only_local, const double kspacing[3], const std::string& kmesh_type, - const double koffset[3]) + const double koffset[3], + std::ofstream& ofs_running) { ModuleBase::TITLE("K_Vectors", "read_kpoints"); if (GlobalV::MY_RANK != 0) @@ -222,14 +221,22 @@ bool K_Vectors::read_kpoints(const UnitCell& ucell, return true; } - const bool gamma_only_local_ = gamma_only_local; - const double kspacing_[3] = {kspacing[0], kspacing[1], kspacing[2]}; - const std::string kmesh_type_ = kmesh_type; - const double koffset_[3] = {koffset[0], koffset[1], koffset[2]}; - // 1. Overwrite the KPT file and default K-point information if needed // mohan add 2010-09-04 - if (gamma_only_local_) + this->generate_kfile(ucell, fn, gamma_only_local, kspacing, kmesh_type, koffset); + + // 2. Read the KPT file and build the k-point list + return this->parse_kfile(fn, ofs_running); +} + +void K_Vectors::generate_kfile(const UnitCell& ucell, + const std::string& fn, + const bool gamma_only_local, + const double kspacing[3], + const std::string& kmesh_type, + const double koffset[3]) +{ + if (gamma_only_local) { GlobalV::ofs_warning << " Auto generating k-points file: " << fn << std::endl; std::ofstream ofs(fn.c_str()); @@ -239,9 +246,9 @@ bool K_Vectors::read_kpoints(const UnitCell& ucell, ofs << "1 1 1 0 0 0" << std::endl; ofs.close(); } - else if (kspacing_[0] > 0.0) + else if (kspacing[0] > 0.0) { - if (kspacing_[1] <= 0 || kspacing_[2] <= 0) + if (kspacing[1] <= 0 || kspacing[2] <= 0) { ModuleBase::WARNING_QUIT("K_Vectors", "kspacing should > 0"); }; @@ -250,18 +257,15 @@ bool K_Vectors::read_kpoints(const UnitCell& ucell, double b1 = sqrt(btmp.e11 * btmp.e11 + btmp.e12 * btmp.e12 + btmp.e13 * btmp.e13); double b2 = sqrt(btmp.e21 * btmp.e21 + btmp.e22 * btmp.e22 + btmp.e23 * btmp.e23); double b3 = sqrt(btmp.e31 * btmp.e31 + btmp.e32 * btmp.e32 + btmp.e33 * btmp.e33); - int nk1 - = std::max(1, static_cast(b1 * ModuleBase::TWO_PI / kspacing_[0] / ucell.lat0 + 1)); - int nk2 - = std::max(1, static_cast(b2 * ModuleBase::TWO_PI / kspacing_[1] / ucell.lat0 + 1)); - int nk3 - = std::max(1, static_cast(b3 * ModuleBase::TWO_PI / kspacing_[2] / ucell.lat0 + 1)); + int nk1 = std::max(1, static_cast(b1 * ModuleBase::TWO_PI / kspacing[0] / ucell.lat0 + 1)); + int nk2 = std::max(1, static_cast(b2 * ModuleBase::TWO_PI / kspacing[1] / ucell.lat0 + 1)); + int nk3 = std::max(1, static_cast(b3 * ModuleBase::TWO_PI / kspacing[2] / ucell.lat0 + 1)); GlobalV::ofs_warning << " Generate k-points file according to KSPACING: " << fn << std::endl; std::ofstream ofs(fn.c_str()); ofs << "K_POINTS" << std::endl; ofs << "0" << std::endl; - if (kmesh_type_ == "mp") + if (kmesh_type == "mp") { ofs << "Monkhorst-Pack" << std::endl; } @@ -269,12 +273,15 @@ bool K_Vectors::read_kpoints(const UnitCell& ucell, { ofs << "Gamma" << std::endl; } - ofs << nk1 << " " << nk2 << " " << nk3 << " " << koffset_[0] << " " << koffset_[1] << " " - << koffset_[2] << std::endl; + ofs << nk1 << " " << nk2 << " " << nk3 << " " << koffset[0] << " " << koffset[1] << " " + << koffset[2] << std::endl; ofs.close(); } +} - // 2. Generate the K-point grid automatically according to the KPT file +// 2. Generate the K-point grid automatically according to the KPT file +bool K_Vectors::parse_kfile(const std::string& fn, std::ofstream& ofs_running) +{ // 2.1 read the KPT file std::ifstream ifk(fn.c_str()); if (!ifk) @@ -340,13 +347,13 @@ bool K_Vectors::read_kpoints(const UnitCell& ucell, { is_mp = true; k_type = 0; - ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running, "Input type of k points", "Monkhorst-Pack(Gamma)"); + ModuleBase::GlobalFunc::OUT(ofs_running, "Input type of k points", "Monkhorst-Pack(Gamma)"); } else if (kword == "Monkhorst-Pack" || kword == "MP" || kword == "mp") { is_mp = true; k_type = 1; - ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running, "Input type of k points", "Monkhorst-Pack"); + ModuleBase::GlobalFunc::OUT(ofs_running, "Input type of k points", "Monkhorst-Pack"); } else { @@ -430,7 +437,7 @@ bool K_Vectors::read_kpoints(const UnitCell& ucell, this->nkstot_full = this->nks = this->nkstot; - ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running, "nkstot", nkstot); + ModuleBase::GlobalFunc::OUT(ofs_running, "nkstot", nkstot); return true; } // END SUBROUTINE @@ -509,7 +516,8 @@ void K_Vectors::interpolate_k_between(std::ifstream& ifk, std::vector>& kvec_d_ibz, - const std::vector& wk_ibz) + const std::vector& wk_ibz, + std::ofstream& ofs_running) { if (GlobalV::MY_RANK != 0) { return; @@ -520,7 +528,7 @@ void K_Vectors::update_use_ibz(const int& nkstot_ibz, // update nkstot this->nks = this->nkstot = nkstot_ibz; - ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running, "nkstot now", nkstot); + ModuleBase::GlobalFunc::OUT(ofs_running, "nkstot now", nkstot); this->kvec_d.resize(this->nkstot * nspin); // qianrui fix a bug 2021-7-13 for nspin=2 in set_kup_and_kdw() @@ -541,7 +549,7 @@ void K_Vectors::update_use_ibz(const int& nkstot_ibz, // This routine sets the k vectors for the up and down spin //---------------------------------------------------------- // from set_kup_and_kdw.f90 -void K_Vectors::set_kup_and_kdw() +void K_Vectors::set_kup_and_kdw(std::ofstream& ofs_running) { ModuleBase::TITLE("K_Vectors", "setup_kup_and_kdw"); @@ -549,6 +557,8 @@ void K_Vectors::set_kup_and_kdw() // on output: the number of points is doubled and xk and wk in the // first (nks/2) positions correspond to up spin // those in the second (nks/2) ones correspond to down spin + // nspin can only be 1 or 2 here: K_Vectors::set() maps nspin=4 + // (non-collinear) to 1 before the k-list is built. //========================================================================= switch (nspin) { @@ -575,16 +585,8 @@ void K_Vectors::set_kup_and_kdw() this->nks *= 2; this->nkstot *= 2; - ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running, "nks(nspin=2)", nks); - ModuleBase::GlobalFunc::OUT(GlobalV::ofs_running, "nkstot(nspin=2)", nkstot); - break; - case 4: - - for (int ik = 0; ik < nks; ik++) - { - this->isk[ik] = 0; - } - + ModuleBase::GlobalFunc::OUT(ofs_running, "nks(nspin=2)", nks); + ModuleBase::GlobalFunc::OUT(ofs_running, "nkstot(nspin=2)", nkstot); break; } @@ -659,10 +661,10 @@ void K_Vectors::reduce_by_symmetry(const UnitCell& ucell, } // convert kgmatrix to k-lattice - ModuleBase::Matrix3* kkmatrix = new ModuleBase::Matrix3[nrotkm]; + std::vector kkmatrix(nrotkm); if (this->get_is_mp()) { - symm.gmatrix_convert(kgmatrix.data(), kkmatrix, nrotkm, ucell.G, k_vec); + symm.gmatrix_convert(kgmatrix.data(), kkmatrix.data(), nrotkm, ucell.G, k_vec); } // use operation : kgmatrix to find @@ -670,31 +672,9 @@ void K_Vectors::reduce_by_symmetry(const UnitCell& ucell, std::vector> kvec_d_ibz; std::vector wk_ibz; std::vector ibz2bz; - this->reduce_ibz(kgmatrix.data(), nrotkm, ucell.G, k_vec, kkmatrix, symm.epsilon, kvec_d_ibz, wk_ibz, this->ibz_index, ibz2bz); + this->reduce_ibz(kgmatrix.data(), nrotkm, ucell.G, k_vec, kkmatrix.data(), symm.epsilon, kvec_d_ibz, wk_ibz, this->ibz_index, ibz2bz); const int nkstot_ibz = kvec_d_ibz.size(); - delete[] kkmatrix; - - auto restrict_kpt = [&symm](ModuleBase::Vector3& kvec) { - // in (-0.5, 0.5] - kvec.x = fmod(kvec.x + 100.5 - 0.5 * symm.epsilon, 1) - 0.5 + 0.5 * symm.epsilon; - kvec.y = fmod(kvec.y + 100.5 - 0.5 * symm.epsilon, 1) - 0.5 + 0.5 * symm.epsilon; - kvec.z = fmod(kvec.z + 100.5 - 0.5 * symm.epsilon, 1) - 0.5 + 0.5 * symm.epsilon; - if (std::abs(kvec.x) < symm.epsilon) - { - kvec.x = 0.0; - } - if (std::abs(kvec.y) < symm.epsilon) - { - kvec.y = 0.0; - } - if (std::abs(kvec.z) < symm.epsilon) - { - kvec.z = 0.0; - } - return; - }; - #ifdef __EXX // setup kstars according to the final (max-norm) kvec_d_ibz this->kstars.resize(nkstot_ibz); @@ -708,7 +688,7 @@ void K_Vectors::reduce_by_symmetry(const UnitCell& ucell, for (int j = 0; j < nrotkm; ++j) { kvec_rot = this->kvec_d[i] * kgmatrix[j]; - restrict_kpt(kvec_rot); + ModuleCell::restrict_kpt(kvec_rot, symm.epsilon); for (int k = 0; k < nkstot_ibz; ++k) { if (symm.equal(kvec_rot.x, kvec_d_ibz[k].x) && symm.equal(kvec_rot.y, kvec_d_ibz[k].y) @@ -778,8 +758,166 @@ void K_Vectors::reduce_by_symmetry(const UnitCell& ucell, // resize the kpoint container according to nkstot_ibz if (use_symm || this->get_is_mp()) { - this->update_use_ibz(nkstot_ibz, kvec_d_ibz, wk_ibz); + this->update_use_ibz(nkstot_ibz, kvec_d_ibz, wk_ibz, GlobalV::ofs_running); } return; } + +void K_Vectors::set_after_vc(const int& nspin_in, const ModuleBase::Matrix3& G, std::ofstream& ofs_running) +{ + ofs_running << "\n SETUP K-POINTS" << std::endl; + this->set_nspin(nspin_in); + ModuleBase::GlobalFunc::OUT(ofs_running, "nspin", this->get_nspin()); + + // set cartesian k vectors. + this->kvec_d2c(G); + + std::string table; + table += "K-POINTS DIRECT COORDINATES\n"; + table += FmtCore::format("%8s%12s%12s%12s%8s\n", "KPOINTS", "DIRECT_X", "DIRECT_Y", "DIRECT_Z", "WEIGHT"); + for (int i = 0; i < this->nks; i++) + { + table += FmtCore::format("%8d%12.8f%12.8f%12.8f%8.4f\n", + i + 1, + this->kvec_d[i].x, + this->kvec_d[i].y, + this->kvec_d[i].z, + this->wk[i]); + } + ofs_running << table << std::endl; + + this->kd_done = true; + this->kc_done = true; + + this->print_klists(ofs_running); +} + +#ifdef __MPI +void K_Vectors::mpi_k(std::ofstream& ofs_running) +{ + ModuleBase::TITLE("K_Vectors", "mpi_k"); + + Parallel_Common::bcast_bool(this->kc_done); + + Parallel_Common::bcast_bool(this->kd_done); + + Parallel_Common::bcast_int(this->nspin); + + Parallel_Common::bcast_int(this->nkstot); + + Parallel_Common::bcast_int(this->nkstot_full); + + Parallel_Common::bcast_int(this->nmp, 3); + + this->kl_segids.resize(this->nkstot); + Parallel_Common::bcast_int(this->kl_segids.data(), this->nkstot); + + Parallel_Common::bcast_double(this->koffset, 3); + + this->nks = this->para_k.nks_pool[GlobalV::MY_POOL]; + + ofs_running << std::endl; + ModuleBase::GlobalFunc::OUT(ofs_running, "Number of k-points in this process", this->nks); + int nks_minimum = this->nks; + + Parallel_Reduce::reduce_min(nks_minimum); + + if (nks_minimum == 0) + { + ModuleBase::WARNING_QUIT("K_Vectors::mpi_k()", " nks == 0, some processor have no k points!"); + } + else + { + ModuleBase::GlobalFunc::OUT(ofs_running, "Minimum distributed k-point number", nks_minimum); + } + + std::vector isk_aux(this->nkstot); + std::vector wk_aux(this->nkstot); + std::vector kvec_c_aux(this->nkstot * 3); + std::vector kvec_d_aux(this->nkstot * 3); + std::vector kvec_c_full_aux(this->nkstot_full * 3); + + // collect and process in rank 0 + if (GlobalV::MY_RANK == 0) + { + for (int ik = 0; ik < this->nkstot; ik++) + { + isk_aux[ik] = this->isk[ik]; + wk_aux[ik] = this->wk[ik]; + kvec_c_aux[3 * ik] = this->kvec_c[ik].x; + kvec_c_aux[3 * ik + 1] = this->kvec_c[ik].y; + kvec_c_aux[3 * ik + 2] = this->kvec_c[ik].z; + kvec_d_aux[3 * ik] = this->kvec_d[ik].x; + kvec_d_aux[3 * ik + 1] = this->kvec_d[ik].y; + kvec_d_aux[3 * ik + 2] = this->kvec_d[ik].z; + kvec_c_full_aux[3 * ik] = this->kvec_c_full[ik].x; + kvec_c_full_aux[3 * ik + 1] = this->kvec_c_full[ik].y; + kvec_c_full_aux[3 * ik + 2] = this->kvec_c_full[ik].z; + } + } + + // broadcast k point data to all processors + Parallel_Common::bcast_int(isk_aux.data(), this->nkstot); + + Parallel_Common::bcast_double(wk_aux.data(), this->nkstot); + Parallel_Common::bcast_double(kvec_c_aux.data(), this->nkstot * 3); + Parallel_Common::bcast_double(kvec_d_aux.data(), this->nkstot * 3); + Parallel_Common::bcast_double(kvec_c_full_aux.data(), this->nkstot_full * 3); + + // process k point data in each processor + this->renew(this->nks * this->nspin); + + // distribute + int k_index = 0; + + for (int i = 0; i < this->nks; i++) + { + // 3 is because each k point has three value:kx, ky, kz + k_index = i + this->para_k.startk_pool[GlobalV::MY_POOL]; + this->kvec_c[i].x = kvec_c_aux[k_index * 3]; + this->kvec_c[i].y = kvec_c_aux[k_index * 3 + 1]; + this->kvec_c[i].z = kvec_c_aux[k_index * 3 + 2]; + this->kvec_d[i].x = kvec_d_aux[k_index * 3]; + this->kvec_d[i].y = kvec_d_aux[k_index * 3 + 1]; + this->kvec_d[i].z = kvec_d_aux[k_index * 3 + 2]; + this->kvec_c_full[i].x = kvec_c_full_aux[k_index * 3]; + this->kvec_c_full[i].y = kvec_c_full_aux[k_index * 3 + 1]; + this->kvec_c_full[i].z = kvec_c_full_aux[k_index * 3 + 2]; + this->wk[i] = wk_aux[k_index]; + this->isk[i] = isk_aux[k_index]; + } + +#ifdef __EXX + if (ModuleSymmetry::Symmetry::symm_flag == 1) + { // bcast kstars + this->kstars.resize(this->nkstot); + for (int ikibz = 0; ikibz < this->nkstot; ++ikibz) + { + int starsize = this->kstars[ikibz].size(); + Parallel_Common::bcast_int(starsize); + auto ks = this->kstars[ikibz].begin(); + for (int ik = 0; ik < starsize; ++ik) + { + int isym = 0; + ModuleBase::Vector3 ks_vec(0, 0, 0); + if (GlobalV::MY_RANK == 0) + { + isym = ks->first; + ks_vec = ks->second; + ++ks; + } + Parallel_Common::bcast_int(isym); + Parallel_Common::bcast_double(ks_vec.x); + Parallel_Common::bcast_double(ks_vec.y); + Parallel_Common::bcast_double(ks_vec.z); + if (GlobalV::MY_RANK != 0) + { + this->kstars[ikibz].insert(std::make_pair(isym, ks_vec)); + } + } + } + } +#endif +} // END SUBROUTINE mpi_k +#endif diff --git a/source/source_cell/klist.h b/source/source_cell/klist.h index 706fbb2bf53..2e212629f06 100644 --- a/source/source_cell/klist.h +++ b/source/source_cell/klist.h @@ -5,7 +5,6 @@ #include "source_base/matrix3.h" #include "source_cell/unitcell.h" #include "parallel_kpoints.h" -#include "k_vector_utils.h" #include "reciprocal_grid.h" #include @@ -150,7 +149,22 @@ class K_Vectors : public ModuleCell::ReciprocalGrid */ void update_use_ibz(const int& nkstot_ibz, const std::vector>& kvec_d_ibz, - const std::vector& wk_ibz); + const std::vector& wk_ibz, + std::ofstream& ofs_running); + + /** + * @brief Sets up the k-points after a volume change. + * + * Sets the number of spins, converts the direct coordinates (which are + * kept across the volume change) to the new Cartesian coordinates using + * the new reciprocal lattice, prints the resulting table, and marks both + * coordinate sets as up to date. + * + * @param nspin_in The number of spins. 1 for non-spin-polarized + * calculations and 2 for spin-polarized calculations. + * @param G The new reciprocal lattice matrix. + */ + void set_after_vc(const int& nspin_in, const ModuleBase::Matrix3& G, std::ofstream& ofs_running); private: int nspin = 0; ///< number of spin states @@ -221,7 +235,43 @@ class K_Vectors : public ModuleCell::ReciprocalGrid const bool gamma_only_local, const double kspacing[3], const std::string& kmesh_type, - const double koffset[3]); // return 0: something wrong. + const double koffset[3], + std::ofstream& ofs_running); // return 0: something wrong. + + /** + * @brief Overwrite the KPT file with an auto-generated mesh when requested. + * + * Writes a Gamma-mesh KPT file if gamma_only_local is set, or a + * KSPACING-derived Gamma/Monkhorst-Pack mesh if kspacing is positive. + * Does nothing when neither condition holds. + * + * @param ucell unit cell (reciprocal lattice and lat0 for the mesh size) + * @param fn KPT filename to (over)write + * @param gamma_only_local whether to force a single Gamma point + * @param kspacing target k-point spacing in 1/bohr (three components) + * @param kmesh_type "mp" for Monkhorst-Pack, anything else for Gamma + * @param koffset mesh offsets (three components) + */ + void generate_kfile(const UnitCell& ucell, + const std::string& fn, + const bool gamma_only_local, + const double kspacing[3], + const std::string& kmesh_type, + const double koffset[3]); + + /** + * @brief Read the KPT file and build the k-point list from it. + * + * Locates the "K_POINTS" header, reads the point count and type keyword, + * then dispatches to the Monkhorst-Pack mesh, the explicit Cartesian/ + * Direct list, or the Line-mode interpolation accordingly. + * + * @param fn KPT filename to read + * + * @return bool Returns true if the k-points are successfully read, + * false otherwise. + */ + bool parse_kfile(const std::string& fn, std::ofstream& ofs_running); /** * @brief Adds k-points linearly between special points. @@ -267,21 +317,26 @@ class K_Vectors : public ModuleCell::ReciprocalGrid * @note The function also doubles the total number of k-points (nks and nkstot) for spin-polarized calculations. * @note The function prints the total number of k-points for spin-polarized calculations. */ - void set_kup_and_kdw(); + void set_kup_and_kdw(std::ofstream& ofs_running); /** * @brief Gets the global index of a k-point. * @return this->ik2iktot[ik] */ void cal_ik_global(); - friend void KVectorUtils::kvec_ibz_kpoint(K_Vectors& kv, - const ModuleSymmetry::Symmetry& symm, - bool use_symm, - std::string& skpt, - const UnitCell& ucell, - bool& match); + #ifdef __MPI - friend void KVectorUtils::kvec_mpi_k(K_Vectors& kvec); + /** + * @brief Distributes k-points among MPI processes. + * + * Broadcasts the k-point metadata (flags, counts, mesh, segment IDs) + * from rank 0 and distributes the per-pool k-point slice (indices, + * weights, coordinates) to every process. Only compiled with MPI. + * + * @note Assumes nkstot > 0 and quits if some process ends up with + * no k-points. + */ + void mpi_k(std::ofstream& ofs_running); #endif }; #endif // KVECT_H \ No newline at end of file diff --git a/source/source_cell/module_symmetry/symm_magnetic.cpp b/source/source_cell/module_symmetry/symm_magnetic.cpp index 26aac71950a..c7439684de4 100644 --- a/source/source_cell/module_symmetry/symm_magnetic.cpp +++ b/source/source_cell/module_symmetry/symm_magnetic.cpp @@ -215,7 +215,7 @@ int Symmetry::density_sym_ops(std::vector& kgmat, std::vector& trs_inv) const { // The density must be symmetrized with the SAME group that was used to fold the k-points - // (see KVectorUtils::ibz_kpoint): otherwise the density accumulated over the IBZ is not + // (see K_Vectors::reduce_by_symmetry): otherwise the density accumulated over the IBZ is not // restored to the full BZ result. For nspin=4 with a non-zero moment that group is the // Shubnikov group H + Theta*A, so the antiunitary elements' spatial parts are appended here. // Theta leaves the charge invariant and reverses the magnetization, which is what `trs_inv` diff --git a/source/source_cell/qlist.cpp b/source/source_cell/qlist.cpp index cd13bf1c6ef..128ca8a6279 100644 --- a/source/source_cell/qlist.cpp +++ b/source/source_cell/qlist.cpp @@ -4,10 +4,12 @@ #include "qlist.h" +#include "module_symmetry/symmetry.h" #include "source_base/global_function.h" #include "source_base/global_variable.h" #include "source_base/formatter.h" #include "source_base/tool_quit.h" +#include "unitcell.h" #include #include #include @@ -111,9 +113,9 @@ void QList::read_from_file(const std::string& filename, UnitCell& ucell) { this->k_kword = qword; const int max_qpoints = 100000; - if (this->nkstot > max_qpoints) + if (this->nkstot < 0 || this->nkstot > max_qpoints) { - ModuleBase::WARNING("QList::read_from_file", "nkstot > MAX_QPOINTS"); + ModuleBase::WARNING("QList::read_from_file", "nkstot is negative or greater than MAX_QPOINTS."); this->nkstot = this->nks = 0; return; } @@ -233,7 +235,11 @@ void QList::interpolate_q_between(std::ifstream& ifq, std::vector> qs[iqs].y; ifq >> qs[iqs].z; ModuleBase::GlobalFunc::READ_VALUE(ifq, nql[iqs]); - assert(nql[iqs] >= 0); + if (nql[iqs] <= 0) + { + ModuleBase::WARNING_QUIT("QList::interpolate_q_between", + "Line-mode interpolation counts must be positive."); + } this->nkstot += nql[iqs]; if ((nql[iqs] == 1) && (iqs != (nqs_special - 1))) { @@ -241,7 +247,11 @@ void QList::interpolate_q_between(std::ifstream& ifq, std::vectorkl_segids.push_back(qpt_segid); } - assert(nql[nqs_special - 1] == 1); + if (nql[nqs_special - 1] != 1) + { + ModuleBase::WARNING_QUIT("QList::interpolate_q_between", + "The final line-mode q-point must have an interpolation count of 1."); + } this->renew(this->nkstot); @@ -358,16 +368,14 @@ void QList::reduce_by_symmetry(const UnitCell& ucell, nrotkm *= 2; } - ModuleBase::Matrix3* kkmatrix = new ModuleBase::Matrix3[nrotkm]; - symm.gmatrix_convert(kgmatrix.data(), kkmatrix, nrotkm, ucell.G, q_vec); + std::vector kkmatrix(nrotkm); + symm.gmatrix_convert(kgmatrix.data(), kkmatrix.data(), nrotkm, ucell.G, q_vec); std::vector> qvec_ibz; std::vector wk_ibz; std::vector ibz_index; std::vector ibz2bz; - this->reduce_ibz(kgmatrix.data(), nrotkm, ucell.G, q_vec, kkmatrix, symm.epsilon, qvec_ibz, wk_ibz, ibz_index, ibz2bz); - - delete[] kkmatrix; + this->reduce_ibz(kgmatrix.data(), nrotkm, ucell.G, q_vec, kkmatrix.data(), symm.epsilon, qvec_ibz, wk_ibz, ibz_index, ibz2bz); // update the reduced q-point list (no spin expansion) const int nq_ibz = qvec_ibz.size(); diff --git a/source/source_cell/qlist.h b/source/source_cell/qlist.h index 7a60e42f22b..cfe5642b810 100644 --- a/source/source_cell/qlist.h +++ b/source/source_cell/qlist.h @@ -11,11 +11,15 @@ #include "source_base/vector3.h" #include "module_symmetry/little_group.h" -#include "module_symmetry/symmetry.h" -#include "unitcell.h" #include "reciprocal_grid.h" #include +class UnitCell; +namespace ModuleSymmetry +{ +class Symmetry; +} + namespace ModuleCell { /** @@ -80,9 +84,16 @@ class QList : public ModuleCell::ReciprocalGrid { /** * @brief Get q-point at given index. * @param idx q-point index - * @return q-point vector (direct coordinates) + * @return q-point vector (direct coordinates); zero vector if idx is out of range */ - ModuleBase::Vector3 get_q(int idx) const { return this->kvec_d[idx]; } + ModuleBase::Vector3 get_q(int idx) const + { + if (idx < 0 || idx >= static_cast(this->kvec_d.size())) + { + return ModuleBase::Vector3(); + } + return this->kvec_d[idx]; + } /** * @brief Get the number of irreps at given q-point. diff --git a/source/source_cell/reciprocal_grid.cpp b/source/source_cell/reciprocal_grid.cpp index c35c4981bb6..6f94a8769cc 100644 --- a/source/source_cell/reciprocal_grid.cpp +++ b/source/source_cell/reciprocal_grid.cpp @@ -1,8 +1,8 @@ /** * @file reciprocal_grid.cpp * @brief Implementation of the ModuleCell::ReciprocalGrid base class. - * @note Spin-free logic migrated from K_Vectors (klist.cpp) and - * KVectorUtils (k_vector_utils.cpp) on 2026-08-14. + * @note Spin-free logic migrated from K_Vectors (klist.cpp) on 2026-08-14; + * the intermediate KVectorUtils shim was removed on 2026-09-02. */ #include "reciprocal_grid.h" @@ -18,6 +18,27 @@ namespace ModuleCell { +void restrict_kpt(ModuleBase::Vector3& kvec, double epsilon) +{ + // fold into (-0.5, 0.5]; the epsilon shift keeps points sitting on the + // boundary consistent with the epsilon-based equivalence checks + kvec.x = fmod(kvec.x + 100.5 - 0.5 * epsilon, 1) - 0.5 + 0.5 * epsilon; + kvec.y = fmod(kvec.y + 100.5 - 0.5 * epsilon, 1) - 0.5 + 0.5 * epsilon; + kvec.z = fmod(kvec.z + 100.5 - 0.5 * epsilon, 1) - 0.5 + 0.5 * epsilon; + if (std::abs(kvec.x) < epsilon) + { + kvec.x = 0.0; + } + if (std::abs(kvec.y) < epsilon) + { + kvec.y = 0.0; + } + if (std::abs(kvec.z) < epsilon) + { + kvec.z = 0.0; + } +} + void ReciprocalGrid::renew(const int& kpoint_number) { kvec_c.resize(kpoint_number); @@ -146,31 +167,34 @@ void ReciprocalGrid::kvec_c2d(const ModuleBase::Matrix3& latvec) } } -void ReciprocalGrid::set_both_kvec(const ModuleBase::Matrix3& G, const ModuleBase::Matrix3& R, std::string& skpt) +void ReciprocalGrid::set_both_kvec(const ModuleBase::Matrix3& G, + const ModuleBase::Matrix3& R, + std::string& skpt, + std::ofstream& ofs_running) { - if (true) // once-per-run gate (the FINAL_SCF hole is irrelevant here) + // Re-derive the "which representation was read from file" flags. + // For auto-generated meshes (k_nkstot == 0) the direct coordinates + // are always available. + if (this->k_nkstot == 0) + { + this->kd_done = true; + this->kc_done = false; + } + else { - if (this->k_nkstot == 0) + if (this->k_kword == "Cartesian" || this->k_kword == "C") + { + this->kc_done = true; + this->kd_done = false; + } + else if (this->k_kword == "Direct" || this->k_kword == "D") { this->kd_done = true; this->kc_done = false; } else { - if (this->k_kword == "Cartesian" || this->k_kword == "C") - { - this->kc_done = true; - this->kd_done = false; - } - else if (this->k_kword == "Direct" || this->k_kword == "D") - { - this->kd_done = true; - this->kc_done = false; - } - else - { - GlobalV::ofs_warning << " Error : neither Cartesian nor Direct kpoint." << std::endl; - } + GlobalV::ofs_warning << " Error : neither Cartesian nor Direct kpoint." << std::endl; } } @@ -199,7 +223,7 @@ void ReciprocalGrid::set_both_kvec(const ModuleBase::Matrix3& G, const ModuleBas this->kvec_d[i].z, this->wk[i]); } - GlobalV::ofs_running << table << std::endl; + ofs_running << table << std::endl; if (GlobalV::MY_RANK == 0) { std::stringstream ss; @@ -303,25 +327,6 @@ void ReciprocalGrid::reduce_ibz(const ModuleBase::Matrix3* rot_ops, std::vector& ibz2bz) { auto equal = [epsilon](double m, double n) { return fabs(m - n) < epsilon; }; - // restrict a vector to (-0.5, 0.5] - auto restrict_kpt = [epsilon](ModuleBase::Vector3& kvec) { - kvec.x = fmod(kvec.x + 100.5 - 0.5 * epsilon, 1) - 0.5 + 0.5 * epsilon; - kvec.y = fmod(kvec.y + 100.5 - 0.5 * epsilon, 1) - 0.5 + 0.5 * epsilon; - kvec.z = fmod(kvec.z + 100.5 - 0.5 * epsilon, 1) - 0.5 + 0.5 * epsilon; - if (std::abs(kvec.x) < epsilon) - { - kvec.x = 0.0; - } - if (std::abs(kvec.y) < epsilon) - { - kvec.y = 0.0; - } - if (std::abs(kvec.z) < epsilon) - { - kvec.z = 0.0; - } - return; - }; // direct coordinates of points in the k-lattice std::vector> kvec_d_k(this->nkstot); @@ -335,7 +340,10 @@ void ReciprocalGrid::reduce_ibz(const ModuleBase::Matrix3* rot_ops, int nkstot_ibz = 0; - assert(this->nkstot > 0); + if (this->nkstot <= 0) + { + ModuleBase::WARNING_QUIT("ReciprocalGrid::reduce_ibz", "no points to reduce (nkstot <= 0)."); + } std::vector> kvec_d_ibz(this->nkstot); std::vector wk_ibz_tmp(this->nkstot); // ibz point weight ibz2bz.resize(this->nkstot); @@ -354,7 +362,7 @@ void ReciprocalGrid::reduce_ibz(const ModuleBase::Matrix3* rot_ops, if (!this->is_mp) { weight = this->wk[i]; } // use the input weight, instead of 1/nkstot // restrict to (-0.5, 0.5] - restrict_kpt(this->kvec_d[i]); + restrict_kpt(this->kvec_d[i], epsilon); bool already_exist = false; int exist_number = -1; @@ -364,12 +372,12 @@ void ReciprocalGrid::reduce_ibz(const ModuleBase::Matrix3* rot_ops, if (!already_exist) { kvec_rot = this->kvec_d[i] * rot_ops[j]; // wrong for total energy, but correct for nonlocal force. - restrict_kpt(kvec_rot); + restrict_kpt(kvec_rot, epsilon); if (this->is_mp) { kvec_rot_k = kvec_d_k[i] * kkmatrix[j]; // k-lattice rotation kvec_rot_k = kvec_rot_k * k_lattice * G.Inverse(); // convert to recip lattice - restrict_kpt(kvec_rot_k); + restrict_kpt(kvec_rot_k, epsilon); assert(equal(kvec_rot.x, kvec_rot_k.x)); assert(equal(kvec_rot.y, kvec_rot_k.y)); @@ -524,7 +532,10 @@ bool ReciprocalGrid::build_star_ops(const UnitCell& ucell, << std::endl; GlobalV::ofs_running << "ibrav of real space lattice: " << symm.ilattname << std::endl; GlobalV::ofs_running << "ibrav of reciprocal lattice: " << recip_brav_name << std::endl; - GlobalV::ofs_running << "(which should be " << ibrav_a2b[symm.real_brav - 1] << ")." << std::endl; + if (symm.real_brav >= 1 && symm.real_brav <= 14) + { + GlobalV::ofs_running << "(which should be " << ibrav_a2b[symm.real_brav - 1] << ")." << std::endl; + } return false; } @@ -552,7 +563,12 @@ bool ReciprocalGrid::build_star_ops(const UnitCell& ucell, // point-group analysis of reciprocal lattice ModuleBase::Matrix3 bsymop[48]; int bnop = 0; - // search again + // Search again on the vectors possibly replaced in place by the + // first lattice_type call (it swaps in the shortest basis and may + // swap in higher-symmetry optimized vectors). This second pass + // re-derives the (Bravais type, standard-orientation vectors) pair + // consistently for the final vectors, which the setgroup + + // gmatrix_convert calls below rely on. Do not remove this call. symm.lattice_type(recip_vec1, recip_vec2, recip_vec3, diff --git a/source/source_cell/reciprocal_grid.h b/source/source_cell/reciprocal_grid.h index fffabbb720b..020ca79e73c 100644 --- a/source/source_cell/reciprocal_grid.h +++ b/source/source_cell/reciprocal_grid.h @@ -1,8 +1,9 @@ /** * @file reciprocal_grid.h * @brief Abstract base class for reciprocal-space point grids. - * @note Extracted from K_Vectors / KVectorUtils (2026-08-14) so that both - * k-points (K_Vectors) and q-points (QList) share the common + * @note Extracted from K_Vectors (2026-08-14; the intermediate KVectorUtils + * shim has since been folded back into the member functions) so that + * both k-points (K_Vectors) and q-points (QList) share the common * spin-free functionality: mesh generation, coordinate conversion, * weight normalization, printing and star (IBZ) reduction. */ @@ -24,6 +25,17 @@ class Symmetry; namespace ModuleCell { +/** + * @brief Fold a point into (-0.5, 0.5] in direct coordinates. + * + * Uses the epsilon-shifted fmod convention shared with the symmetry + * checker, and zeroes components below the epsilon tolerance. + * + * @param kvec point to fold in place + * @param epsilon symmetry tolerance + */ +void restrict_kpt(ModuleBase::Vector3& kvec, double epsilon); + /** * @brief Abstract base class shared by K_Vectors (electrons) and QList (phonons). * @@ -91,8 +103,12 @@ class ReciprocalGrid * @param G reciprocal lattice matrix * @param R real space lattice matrix * @param skpt output string holding the point table + * @param ofs_running running-log stream */ - void set_both_kvec(const ModuleBase::Matrix3& G, const ModuleBase::Matrix3& R, std::string& skpt); + void set_both_kvec(const ModuleBase::Matrix3& G, + const ModuleBase::Matrix3& R, + std::string& skpt, + std::ofstream& ofs_running); /// @brief Normalize the weights so that they sum to the spin degeneracy. void normalize_wk(const int& degspin); diff --git a/source/source_cell/test/CMakeLists.txt b/source/source_cell/test/CMakeLists.txt index ad2bb093012..caab63da4ad 100644 --- a/source/source_cell/test/CMakeLists.txt +++ b/source/source_cell/test/CMakeLists.txt @@ -86,7 +86,7 @@ AddTest( AddTest( TARGET MODULE_CELL_klist_test LIBS base device symmetry - SOURCES klist_test.cpp ../klist.cpp ../parallel_kpoints.cpp ../k_vector_utils.cpp ../reciprocal_grid.cpp + SOURCES klist_test.cpp ../klist.cpp ../parallel_kpoints.cpp ../reciprocal_grid.cpp ) AddTest( @@ -110,7 +110,7 @@ AddTest( AddTest( TARGET MODULE_CELL_klist_test_para1 LIBS base device symmetry - SOURCES klist_test_para.cpp ../klist.cpp ../parallel_kpoints.cpp ../k_vector_utils.cpp ../reciprocal_grid.cpp + SOURCES klist_test_para.cpp ../klist.cpp ../parallel_kpoints.cpp ../reciprocal_grid.cpp ) add_test(NAME MODULE_CELL_klist_test_para4 diff --git a/source/source_cell/test/klist_test.cpp b/source/source_cell/test/klist_test.cpp index 287dda2f86c..74ea3d12ec4 100644 --- a/source/source_cell/test/klist_test.cpp +++ b/source/source_cell/test/klist_test.cpp @@ -63,7 +63,7 @@ Magnetism::~Magnetism() * - K_Vectors() * - basic parameters (nks,nkstot,nkstot_ibz) are set * - read_kpoints() - * - ReadKpointsGammaOnlyLocal: PARAM.sys.gamma_only_local = 1 + * - ReadKpointsGammaOnlyLocal: gamma_only_local = true * - ReadKpointsKspacing: generate KPT from kspacing parameter * - ReadKpointsGamma: "Gamma" mode of `KPT` file * - ReadKpointsMP: "MP" mode of `KPT` file @@ -83,7 +83,8 @@ Magnetism::~Magnetism() * according to different spin case * - set_both_kvec() * - SetBothKvec: set kvec_c (cartesian coor.) and kvec_d (direct coor.) - * - SetBothKvecFinalSCF: same as above, with PARAM.input.final_scf=1 + * - SetBothKvecFlagsFromFile: flags are re-derived from the k_nkstot / + * k_kword file record (Cartesian/Direct/unknown) * - print_klists() * - PrintKlists: print kpoints coordinates * - PrintKlistsWarningQuit: for nkstot < nks error @@ -266,7 +267,7 @@ TEST_F(KlistTest, ReadKpointsGammaOnlyLocal) const double koffset[3] = {0.0, 0.0, 0.0}; std::string k_file = "KPT_GO"; kv->nspin = 1; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); ifs.open("KPT_GO"); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); EXPECT_THAT(str, testing::HasSubstr("Gamma")); @@ -284,7 +285,7 @@ TEST_F(KlistTest, ReadKpointsKspacing) const double koffset[3] = {0.0, 0.0, 0.0}; setucell(); std::string k_file = "./support/KPT3"; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv->get_nkstot(), 343); } @@ -297,7 +298,7 @@ TEST_F(KlistTest, ReadKpointsKspacing3values) const double koffset[3] = {0.0, 0.0, 0.0}; setucell(); std::string k_file = "./support/KPT3"; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv->get_nkstot(), 210); } @@ -310,7 +311,7 @@ TEST_F(KlistTest, ReadKpointsInvalidKspacing3values) const double koffset[3] = {0.0, 0.0, 0.0}; std::string k_file = "./support/KPT3"; testing::internal::CaptureStdout(); - EXPECT_EXIT(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); } @@ -324,7 +325,7 @@ TEST_F(KlistTest, ReadKpointsKspacingShiftedGamma) setucell(); std::string k_file = "./support/KPT3"; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv->get_nkstot(), 343); EXPECT_EQ(kv->get_k_kword(), "Gamma"); @@ -346,7 +347,7 @@ TEST_F(KlistTest, ReadKpointsKspacingShiftedMP) setucell(); std::string k_file = "./support/KPT3"; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv->get_nkstot(), 343); EXPECT_EQ(kv->get_k_kword(), "Monkhorst-Pack"); @@ -366,7 +367,7 @@ TEST_F(KlistTest, ReadKpointsGamma) const double koffset[3] = {0.0, 0.0, 0.0}; std::string k_file = "./support/KPT"; kv->nspin = 1; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv->get_nkstot(), 512); } @@ -378,7 +379,7 @@ TEST_F(KlistTest, ReadKpointsMP) const double koffset[3] = {0.0, 0.0, 0.0}; std::string k_file = "./support/KPT1"; kv->nspin = 1; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv->get_nkstot(), 512); } @@ -391,7 +392,7 @@ TEST_F(KlistTest, ReadKpointsLine) const double koffset[3] = {0.0, 0.0, 0.0}; std::string k_file = "./support/KPT2"; kv->nspin = 1; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv->get_nkstot(), 122); } @@ -405,7 +406,7 @@ TEST_F(KlistTest, ReadKpointsLineRejectsZeroInterpolationCount) const std::string k_file = "./support/KPT_ZERO_LINE_COUNT"; kv->nspin = 1; - EXPECT_EXIT(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset), + EXPECT_EXIT(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running), ::testing::ExitedWithCode(1), ""); } @@ -419,11 +420,11 @@ TEST_F(KlistTest, ReadKpointsCartesian) std::string k_file = "./support/KPT4"; // Cartesian: non-spin case nspin=1 kv->nspin = 1; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv->kvec_c.size(), 5); // spin case nspin=2 kv->nspin = 2; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv->kvec_c.size(), 10); } @@ -436,13 +437,13 @@ TEST_F(KlistTest, ReadKpointsLineCartesian) std::string k_file = "./support/KPT5"; // Line Cartesian: non-spin case nspin=1 kv->nspin = 1; - kv->set_kup_and_kdw(); - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->set_kup_and_kdw(GlobalV::ofs_running); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv->get_nkstot(), 51); EXPECT_EQ(kv->kvec_c.size(), 51); // Line Cartesian: spin case nspin=2 kv->nspin = 2; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv->get_nkstot(), 51); EXPECT_EQ(kv->kvec_c.size(), 102); } @@ -455,8 +456,8 @@ TEST_F(KlistTest, ReadKpointsDirect) const double koffset[3] = {0.0, 0.0, 0.0}; std::string k_file = "./support/KPT6"; kv->nspin = 1; - kv->set_kup_and_kdw(); - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->set_kup_and_kdw(GlobalV::ofs_running); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv->get_nkstot(), 6); EXPECT_TRUE(kv->kd_done); } @@ -470,7 +471,7 @@ TEST_F(KlistTest, ReadKpointsWarning1) std::string k_file = "arbitrary_1"; kv->nspin = 1; GlobalV::ofs_warning.open("klist_tmp_warning_1"); - EXPECT_NO_THROW(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset)); + EXPECT_NO_THROW(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running)); GlobalV::ofs_warning.close(); ifs.open("klist_tmp_warning_1"); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -491,7 +492,7 @@ TEST_F(KlistTest, ReadKpointsWarning2) ofs.close(); kv->nspin = 1; GlobalV::ofs_warning.open("klist_tmp_warning_2"); - EXPECT_NO_THROW(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset)); + EXPECT_NO_THROW(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running)); GlobalV::ofs_warning.close(); ifs.open("klist_tmp_warning_2"); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -514,7 +515,7 @@ TEST_F(KlistTest, ReadKpointsWarning3) ofs.close(); kv->nspin = 1; GlobalV::ofs_warning.open("klist_tmp_warning_3"); - EXPECT_NO_THROW(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset)); + EXPECT_NO_THROW(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running)); GlobalV::ofs_warning.close(); ifs.open("klist_tmp_warning_3"); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -538,7 +539,7 @@ TEST_F(KlistTest, ReadKpointsWarning4) ofs.close(); kv->nspin = 1; GlobalV::ofs_warning.open("klist_tmp_warning_4"); - EXPECT_NO_THROW(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset)); + EXPECT_NO_THROW(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running)); GlobalV::ofs_warning.close(); ifs.open("klist_tmp_warning_4"); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -562,7 +563,7 @@ TEST_F(KlistTest, ReadKpointsWarning5) ofs.close(); kv->nspin = 1; GlobalV::ofs_warning.open("klist_tmp_warning_5"); - EXPECT_NO_THROW(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset)); + EXPECT_NO_THROW(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running)); GlobalV::ofs_warning.close(); ifs.open("klist_tmp_warning_5"); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -587,7 +588,7 @@ TEST_F(KlistTest, ReadKpointsWarning6) kv->nspin = 1; ModuleSymmetry::Symmetry::symm_flag = 1; GlobalV::ofs_warning.open("klist_tmp_warning_6"); - EXPECT_NO_THROW(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset)); + EXPECT_NO_THROW(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running)); GlobalV::ofs_warning.close(); ifs.open("klist_tmp_warning_6"); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -613,7 +614,7 @@ TEST_F(KlistTest, ReadKpointsWarning7) kv->nspin = 1; ModuleSymmetry::Symmetry::symm_flag = 1; GlobalV::ofs_warning.open("klist_tmp_warning_7"); - EXPECT_NO_THROW(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset)); + EXPECT_NO_THROW(kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running)); GlobalV::ofs_warning.close(); ifs.open("klist_tmp_warning_7"); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -632,15 +633,15 @@ TEST_F(KlistTest, SetKupKdown) const double koffset[3] = {0.0, 0.0, 0.0}; std::string k_file = "./support/KPT4"; kv->nspin = 1; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); - kv->set_kup_and_kdw(); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); + kv->set_kup_and_kdw(GlobalV::ofs_running); for (int ik = 0; ik < 5; ik++) { EXPECT_EQ(kv->isk[ik], 0); } kv->nspin = 4; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); - kv->set_kup_and_kdw(); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); + kv->set_kup_and_kdw(GlobalV::ofs_running); for (int ik = 0; ik < 5; ik++) { EXPECT_EQ(kv->isk[ik], 0); @@ -649,8 +650,8 @@ TEST_F(KlistTest, SetKupKdown) EXPECT_EQ(kv->isk[ik + 15], 0); } kv->nspin = 2; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); - kv->set_kup_and_kdw(); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); + kv->set_kup_and_kdw(GlobalV::ofs_running); for (int ik = 0; ik < 5; ik++) { EXPECT_EQ(kv->isk[ik], 0); @@ -667,8 +668,7 @@ TEST_F(KlistTest, SetAfterVC) kv->kvec_c[0].x = 0; kv->kvec_c[0].y = 0; kv->kvec_c[0].z = 0; -// kv->set_after_vc(PARAM.input.nspin, ucell.G, ucell.latvec); - KVectorUtils::set_after_vc(*kv, kv->nspin, ucell.G); + kv->set_after_vc(kv->nspin, ucell.G, GlobalV::ofs_running); EXPECT_TRUE(kv->kd_done); EXPECT_TRUE(kv->kc_done); @@ -689,10 +689,9 @@ TEST_F(KlistTest, PrintKlists) kv->kvec_c[0].x = 0; kv->kvec_c[0].y = 0; kv->kvec_c[0].z = 0; -// kv->set_after_vc(PARAM.input.nspin, ucell.G, ucell.latvec); - KVectorUtils::set_after_vc(*kv, kv->nspin, ucell.G); + kv->set_after_vc(kv->nspin, ucell.G, GlobalV::ofs_running); EXPECT_TRUE(kv->kd_done); - KVectorUtils::print_klists(*kv, GlobalV::ofs_running); + kv->print_klists(GlobalV::ofs_running); GlobalV::ofs_running.close(); remove("tmp_klist_2"); } @@ -707,12 +706,12 @@ TEST_F(KlistTest, PrintKlistsWarnigQuit) kv->kvec_c[0].y = 0; kv->kvec_c[0].z = 0; testing::internal::CaptureStdout(); - EXPECT_EXIT(KVectorUtils::print_klists(*kv, GlobalV::ofs_running), ::testing::ExitedWithCode(1), ""); + EXPECT_EXIT(kv->print_klists(GlobalV::ofs_running), ::testing::ExitedWithCode(1), ""); output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("nkstot < nks")); } -TEST_F(KlistTest, SetBothKvecFinalSCF) +TEST_F(KlistTest, SetBothKvecFlagsFromFile) { kv->nspin = 1; kv->set_nkstot(1); @@ -725,33 +724,32 @@ TEST_F(KlistTest, SetBothKvecFinalSCF) kv->kvec_c[0].y = 0.0; kv->kvec_c[0].z = 0.0; std::string skpt; -// PARAM.input.final_scf = true; kv->kd_done = false; kv->kc_done = false; // case 1 kv->k_nkstot = 0; -// kv->set_both_kvec(ucell.G, ucell.latvec, skpt); - KVectorUtils::set_both_kvec(*kv, ucell.G, ucell.latvec, skpt); +// kv->set_both_kvec(ucell.G, ucell.latvec, skpt, GlobalV::ofs_running); + kv->set_both_kvec(ucell.G, ucell.latvec, skpt, GlobalV::ofs_running); EXPECT_TRUE(kv->kd_done); EXPECT_TRUE(kv->kc_done); // case 2 kv->k_nkstot = 1; kv->k_kword = "D"; -// kv->set_both_kvec(ucell.G, ucell.latvec, skpt); - KVectorUtils::set_both_kvec(*kv, ucell.G, ucell.latvec, skpt); +// kv->set_both_kvec(ucell.G, ucell.latvec, skpt, GlobalV::ofs_running); + kv->set_both_kvec(ucell.G, ucell.latvec, skpt, GlobalV::ofs_running); EXPECT_TRUE(kv->kd_done); EXPECT_TRUE(kv->kc_done); // case 3 kv->k_kword = "C"; -// kv->set_both_kvec(ucell.G, ucell.latvec, skpt); - KVectorUtils::set_both_kvec(*kv, ucell.G, ucell.latvec, skpt); +// kv->set_both_kvec(ucell.G, ucell.latvec, skpt, GlobalV::ofs_running); + kv->set_both_kvec(ucell.G, ucell.latvec, skpt, GlobalV::ofs_running); EXPECT_TRUE(kv->kc_done); EXPECT_TRUE(kv->kd_done); // case 4 GlobalV::ofs_warning.open("klist_tmp_warning_8"); kv->k_kword = "arbitrary"; -// kv->set_both_kvec(ucell.G, ucell.latvec, skpt); - KVectorUtils::set_both_kvec(*kv, ucell.G, ucell.latvec, skpt); +// kv->set_both_kvec(ucell.G, ucell.latvec, skpt, GlobalV::ofs_running); + kv->set_both_kvec(ucell.G, ucell.latvec, skpt, GlobalV::ofs_running); GlobalV::ofs_warning.close(); ifs.open("klist_tmp_warning_8"); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -772,14 +770,13 @@ TEST_F(KlistTest, SetBothKvec) kv->kc_done = false; kv->kd_done = true; std::string skpt; -// PARAM.input.final_scf = false; -// kv->set_both_kvec(ucell.G, ucell.latvec, skpt); - KVectorUtils::set_both_kvec(*kv, ucell.G, ucell.latvec, skpt); +// kv->set_both_kvec(ucell.G, ucell.latvec, skpt, GlobalV::ofs_running); + kv->set_both_kvec(ucell.G, ucell.latvec, skpt, GlobalV::ofs_running); EXPECT_TRUE(kv->kc_done); kv->kc_done = true; kv->kd_done = false; -// kv->set_both_kvec(ucell.G, ucell.latvec, skpt); - KVectorUtils::set_both_kvec(*kv, ucell.G, ucell.latvec, skpt); +// kv->set_both_kvec(ucell.G, ucell.latvec, skpt, GlobalV::ofs_running); + kv->set_both_kvec(ucell.G, ucell.latvec, skpt, GlobalV::ofs_running); EXPECT_TRUE(kv->kd_done); } @@ -829,7 +826,7 @@ TEST_F(KlistTest, UpdateUseIBZ) kv->set_nkstot(3); kv->set_nks(3); kv->renew(kv->get_nkstot()); - kv->update_use_ibz(2, std::vector>(2, {0, 0, 0}), std::vector(2, 0.0)); + kv->update_use_ibz(2, std::vector>(2, {0, 0, 0}), std::vector(2, 0.0), GlobalV::ofs_running); EXPECT_EQ(kv->get_nkstot(), 2); EXPECT_EQ(kv->kvec_d.size(), 2); EXPECT_TRUE(kv->kd_done); @@ -849,13 +846,13 @@ TEST_F(KlistTest, IbzKpoint) symm.analy_sys(ucell.lat, ucell.st, ucell.atoms, GlobalV::ofs_running, 1e-6, 1, "scf", cal_symm_repr); std::string k_file = "./support/KPT1"; kv->nspin = 1; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv->get_nkstot(), 512); // calculate ibz_kpoint std::string skpt; ModuleSymmetry::Symmetry::symm_flag = 1; bool match = true; - KVectorUtils::kvec_ibz_kpoint(*kv, symm, ModuleSymmetry::Symmetry::symm_flag, skpt, ucell, match); + kv->reduce_by_symmetry(ucell, symm, ModuleSymmetry::Symmetry::symm_flag, skpt, match); EXPECT_EQ(kv->get_nkstot(), 35); GlobalV::ofs_running << skpt << std::endl; GlobalV::ofs_running.close(); @@ -876,14 +873,14 @@ TEST_F(KlistTest, IbzKpointIsMP) symm.analy_sys(ucell.lat, ucell.st, ucell.atoms, GlobalV::ofs_running, 1e-6, 1, "scf", cal_symm_repr); std::string k_file = "./support/KPT1"; kv->nspin = 1; - kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv->get_nkstot(), 512); EXPECT_TRUE(kv->is_mp); // calculate ibz_kpoint std::string skpt; ModuleSymmetry::Symmetry::symm_flag = 0; bool match = true; - KVectorUtils::kvec_ibz_kpoint(*kv, symm, ModuleSymmetry::Symmetry::symm_flag, skpt, ucell, match); + kv->reduce_by_symmetry(ucell, symm, ModuleSymmetry::Symmetry::symm_flag, skpt, match); EXPECT_EQ(kv->get_nks(), 260); GlobalV::ofs_running << skpt << std::endl; GlobalV::ofs_running.close(); @@ -908,7 +905,7 @@ TEST_F(KlistTest, IbzKpointCustomWeights) K_Vectors kv_test1; std::string k_file = "./support/KPT4"; kv_test1.nspin = 1; - kv_test1.read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv_test1.read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv_test1.get_nkstot(), 5); EXPECT_FALSE(kv_test1.is_mp); // Should be non-MP @@ -919,7 +916,7 @@ TEST_F(KlistTest, IbzKpointCustomWeights) std::string skpt; ModuleSymmetry::Symmetry::symm_flag = 1; bool match = true; - KVectorUtils::kvec_ibz_kpoint(kv_test1, symm, ModuleSymmetry::Symmetry::symm_flag, skpt, ucell, match); + kv_test1.reduce_by_symmetry(ucell, symm, ModuleSymmetry::Symmetry::symm_flag, skpt, match); // Verify that weights are preserved (not overwritten with 1/nkstot) // After IBZ reduction, weights should still reflect the input weights @@ -937,7 +934,7 @@ TEST_F(KlistTest, IbzKpointCustomWeights) K_Vectors kv_test2; std::string k_file = "./support/KPT_custom_weights"; kv_test2.nspin = 1; - kv_test2.read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv_test2.read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv_test2.get_nkstot(), 5); EXPECT_FALSE(kv_test2.is_mp); // Should be non-MP @@ -960,7 +957,7 @@ TEST_F(KlistTest, IbzKpointCustomWeights) std::string skpt; ModuleSymmetry::Symmetry::symm_flag = 1; bool match = true; - KVectorUtils::kvec_ibz_kpoint(kv_test2, symm, ModuleSymmetry::Symmetry::symm_flag, skpt, ucell, match); + kv_test2.reduce_by_symmetry(ucell, symm, ModuleSymmetry::Symmetry::symm_flag, skpt, match); // After IBZ reduction, the weights should be based on the custom input weights, // not uniform 1/nkstot weights. The total weight should be preserved. @@ -994,7 +991,7 @@ TEST_F(KlistTest, IbzKpointCustomWeights) K_Vectors kv_test3; std::string k_file = "./support/KPT1"; kv_test3.nspin = 1; - kv_test3.read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv_test3.read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv_test3.get_nkstot(), 512); EXPECT_TRUE(kv_test3.is_mp); // Should be MP @@ -1002,7 +999,7 @@ TEST_F(KlistTest, IbzKpointCustomWeights) std::string skpt; ModuleSymmetry::Symmetry::symm_flag = 1; bool match = true; - KVectorUtils::kvec_ibz_kpoint(kv_test3, symm, ModuleSymmetry::Symmetry::symm_flag, skpt, ucell, match); + kv_test3.reduce_by_symmetry(ucell, symm, ModuleSymmetry::Symmetry::symm_flag, skpt, match); // For MP grids, all weights should be uniform after IBZ reduction EXPECT_EQ(kv_test3.get_nkstot(), 35); // Known result from existing test @@ -1021,13 +1018,13 @@ TEST_F(KlistTest, IbzKpointCustomWeights) K_Vectors kv_test4; std::string k_file = "./support/KPT_custom_weights"; kv_test4.nspin = 1; - kv_test4.read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv_test4.read_kpoints(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); // Apply IBZ reduction std::string skpt; ModuleSymmetry::Symmetry::symm_flag = 1; bool match = true; - KVectorUtils::kvec_ibz_kpoint(kv_test4, symm, ModuleSymmetry::Symmetry::symm_flag, skpt, ucell, match); + kv_test4.reduce_by_symmetry(ucell, symm, ModuleSymmetry::Symmetry::symm_flag, skpt, match); // Normalize weights int degspin = (kv_test4.nspin == 2) ? 1 : 2; diff --git a/source/source_cell/test/klist_test_para.cpp b/source/source_cell/test/klist_test_para.cpp index 37c991dc490..17a39ed576e 100644 --- a/source/source_cell/test/klist_test_para.cpp +++ b/source/source_cell/test/klist_test_para.cpp @@ -352,8 +352,7 @@ TEST_F(KlistParaTest, SetAfterVC) } // call set_after_vc here kv->kc_done = false; -// kv->set_after_vc(kv->nspin, ucell.G, ucell.latvec); - KVectorUtils::set_after_vc(*kv, kv->nspin, ucell.G); + kv->set_after_vc(kv->nspin, ucell.G, GlobalV::ofs_running); EXPECT_TRUE(kv->kc_done); EXPECT_TRUE(kv->kd_done); // clear diff --git a/source/source_cell/test/qlist_test.cpp b/source/source_cell/test/qlist_test.cpp index 647928dcfbe..8e828356a2e 100644 --- a/source/source_cell/test/qlist_test.cpp +++ b/source/source_cell/test/qlist_test.cpp @@ -66,7 +66,13 @@ Sep_Cell::~Sep_Cell() noexcept {} * - get_nirr() / get_irrep_modes() * - placeholder irrep data (one fully-symmetric irrep per q-point) * - read_from_file() - * - placeholder interface, must not crash + * - ReadFromFileDirect: explicit Direct list with weights + * - ReadFromFileCartesian: explicit Cartesian list with weights + * - ReadFromFileMonkhorstPack: auto mesh (nkstot == 0) + * - ReadFromFileLinePath / ReadFromFileLineCartesian: line interpolation + * - ReadFromFileNegativeNkstot: negative count is rejected cleanly + * - ReadFromFileLineRejectsZeroCount: non-positive line count quits + * - ReadFromFileMissing / ReadFromFileBadHeader: must not crash */ // abbreviated from module_symmetry/test/symm_test.cpp and klist_test.cpp @@ -423,11 +429,86 @@ TEST_F(QListTest, ReadFromFileLinePath) ClearUcell(); } +TEST_F(QListTest, ReadFromFileCartesian) +{ + construct_ucell(stru_lib[0]); + + const char* fname = "tmp_qpoints_cart"; + std::ofstream ofs(fname); + ofs << "Q_POINTS\n2\nCartesian\n0.0 0.0 0.0 1.0\n0.5 0.0 0.0 1.0\n"; + ofs.close(); + + qlist.read_from_file(fname, ucell); + EXPECT_EQ(qlist.get_nq(), 2); + EXPECT_TRUE(qlist.kc_done); + EXPECT_TRUE(qlist.kd_done); + EXPECT_DOUBLE_EQ(qlist.kvec_c[1].x, 0.5); + // direct coordinates complemented from the Cartesian ones (G = I here) + EXPECT_DOUBLE_EQ(qlist.get_q(1).x, 0.5); + // weights normalized to sum 1 + EXPECT_NEAR(qlist.wk[0] + qlist.wk[1], 1.0, 1e-10); + + remove(fname); + ClearUcell(); +} + +TEST_F(QListTest, ReadFromFileLineCartesian) +{ + construct_ucell(stru_lib[0]); + + const char* fname = "tmp_qpoints_line_cart"; + std::ofstream ofs(fname); + // G -> X segment with 4 points plus the final special point (5 total) + ofs << "Q_POINTS\n2\nLine_Cartesian\n0.0 0.0 0.0 4\n0.5 0.0 0.0 1\n"; + ofs.close(); + + qlist.read_from_file(fname, ucell); + EXPECT_EQ(qlist.get_nq(), 5); + EXPECT_TRUE(qlist.kc_done); + EXPECT_TRUE(qlist.kd_done); + EXPECT_DOUBLE_EQ(qlist.get_q(1).x, 0.5 / 4.0); + EXPECT_DOUBLE_EQ(qlist.get_q(4).x, 0.5); + // line weights are not normalized + EXPECT_DOUBLE_EQ(qlist.wk[0], 1.0); + + remove(fname); + ClearUcell(); +} + +TEST_F(QListTest, ReadFromFileNegativeNkstot) +{ + // a negative count must be rejected cleanly instead of crashing in renew() + const char* fname = "tmp_qpoints_negative"; + std::ofstream ofs(fname); + ofs << "Q_POINTS\n-3\nDirect\n0.0 0.0 0.0 1.0\n"; + ofs.close(); + + qlist.read_from_file(fname, ucell); + EXPECT_EQ(qlist.get_nq(), 0); + + remove(fname); +} + +TEST_F(QListTest, ReadFromFileLineRejectsZeroCount) +{ + const char* fname = "tmp_qpoints_zero_count"; + std::ofstream ofs(fname); + ofs << "Q_POINTS\n2\nLine_Direct\n0.0 0.0 0.0 0\n0.5 0.0 0.0 1\n"; + ofs.close(); + + EXPECT_EXIT(qlist.read_from_file(fname, ucell), ::testing::ExitedWithCode(1), ""); + + remove(fname); +} + TEST_F(QListTest, ReadFromFileMissing) { // a nonexistent file yields an empty q-point list, not a crash qlist.read_from_file("nonexistent_qpoints", ucell); EXPECT_EQ(qlist.get_nq(), 0); + // out-of-range access returns the zero vector instead of crashing + const ModuleBase::Vector3 q0 = qlist.get_q(0); + EXPECT_DOUBLE_EQ(q0.x, 0.0); } TEST_F(QListTest, ReadFromFileBadHeader) diff --git a/source/source_cell/test/reciprocal_grid_test.cpp b/source/source_cell/test/reciprocal_grid_test.cpp index e3d0be2232e..c369bf5ff4c 100644 --- a/source/source_cell/test/reciprocal_grid_test.cpp +++ b/source/source_cell/test/reciprocal_grid_test.cpp @@ -265,3 +265,55 @@ TEST_F(ReciprocalGridTest, ReduceIbzKeepsDistinctPoints) EXPECT_EQ(ibz_index[0], 0); EXPECT_EQ(ibz_index[1], 1); } + +TEST_F(ReciprocalGridTest, ReduceIbzMpKLattice) +{ + // Monkhorst-Pack path: the {0, 0.5}^3 gamma-centered mesh folded by the + // closed group {I, C3, C3^2} (order-3 rotations about (1,1,1)) yields + // Gamma + 3 X + 3 M + R, with the k-lattice consistency asserts active. + const ModuleBase::Matrix3 G(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0); + const ModuleBase::Matrix3 ind(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0); + // row-vector convention: (a,b,c) * c3 = (c,a,b); * c3sq = (b,c,a) + const ModuleBase::Matrix3 c3(0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0); + const ModuleBase::Matrix3 c3sq(0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0); + + const int nmp[3] = {2, 2, 2}; + const double offset[3] = {0.0, 0.0, 0.0}; + grid.Monkhorst_Pack(nmp, offset, 0); // sets nkstot=8, wk=1/8, kd_done + grid.is_mp = true; + grid.nkstot_full = grid.nkstot; + + // k-lattice basis of the 2x2x2 mesh: G/2 along each reciprocal axis. + // In this diagonal frame the k-lattice rotations equal the reciprocal ones. + const ModuleBase::Matrix3 k_lattice(0.5, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.5); + const ModuleBase::Matrix3 ops[3] = {ind, c3, c3sq}; + const std::vector kkmatrix(ops, ops + 3); + + std::vector> vec_ibz; + std::vector wk_ibz; + std::vector ibz_index; + std::vector ibz2bz; + grid.reduce_ibz(ops, 3, G, k_lattice, kkmatrix.data(), 1e-6, vec_ibz, wk_ibz, ibz_index, ibz2bz); + + ASSERT_EQ(vec_ibz.size(), 4); + // every mesh point is mapped to an irreducible point + for (int i = 0; i < grid.nkstot; ++i) + { + EXPECT_GE(ibz_index[i], 0); + } + // Gamma first, then representatives of the X, M, R stars + EXPECT_DOUBLE_EQ(vec_ibz[0].x, 0.0); + EXPECT_DOUBLE_EQ(vec_ibz[0].y, 0.0); + EXPECT_DOUBLE_EQ(vec_ibz[0].z, 0.0); + // stars: Gamma(1) + X(3) + M(3) + R(1) -> weights 1/8, 3/8, 3/8, 1/8 + EXPECT_DOUBLE_EQ(wk_ibz[0], 0.125); + EXPECT_DOUBLE_EQ(wk_ibz[1], 0.375); + EXPECT_DOUBLE_EQ(wk_ibz[2], 0.375); + EXPECT_DOUBLE_EQ(wk_ibz[3], 0.125); + double sum = 0.0; + for (size_t i = 0; i < wk_ibz.size(); ++i) + { + sum += wk_ibz[i]; + } + EXPECT_NEAR(sum, 1.0, 1e-12); +} diff --git a/source/source_esolver/esolver_factory.cpp b/source/source_esolver/esolver_factory.cpp index 03e0a9dc739..322a0d7a1bc 100644 --- a/source/source_esolver/esolver_factory.cpp +++ b/source/source_esolver/esolver_factory.cpp @@ -52,7 +52,7 @@ std::string determine_type(const Input_para& inp) { esolver_type = "ksdft_pw"; } - else if (PARAM.inp.esolver_type == "dfpt") + else if (inp.esolver_type == "dfpt") { esolver_type = "dfpt_pw"; } @@ -137,6 +137,7 @@ ESolver* init_esolver(const Input_para& inp) { // determine type of esolver based on INPUT information const std::string esolver_type = determine_type(inp); + const bool gamma_only = PARAM.globalv.gamma_only_local; // initialize the corresponding Esolver child class if (esolver_type == "ksdft_pw") @@ -207,7 +208,7 @@ ESolver* init_esolver(const Input_para& inp) { if (inp.calculation == "get_s") { - if (PARAM.globalv.gamma_only_local) + if (gamma_only) { ModuleBase::WARNING_QUIT("ESolver", "get_s is not implemented for gamma_only"); } @@ -218,7 +219,7 @@ ESolver* init_esolver(const Input_para& inp) } else if (inp.deepks_out_base != "none") { - if (PARAM.globalv.gamma_only_local) + if (gamma_only) { return new ESolver_DoubleXC(); } @@ -233,7 +234,7 @@ ESolver* init_esolver(const Input_para& inp) } else if (inp.dm_to_rho) { - if (PARAM.globalv.gamma_only_local) + if (gamma_only) { ModuleBase::WARNING_QUIT("ESolver", "dm_to_rho is not implemented for gamma_only"); } @@ -248,7 +249,7 @@ ESolver* init_esolver(const Input_para& inp) } else { - if (PARAM.globalv.gamma_only_local) + if (gamma_only) { return new ESolver_KS_LCAO(); } @@ -297,7 +298,7 @@ ESolver* init_esolver(const Input_para& inp) const std::string& out_dir = PARAM.globalv.global_out_dir; if (inp.xc_kernel != "bse") { - if (PARAM.globalv.gamma_only_local) + if (gamma_only) { return new ModuleESolver::ESolver_LR(inp, in_dir, out_dir); } @@ -309,7 +310,7 @@ ESolver* init_esolver(const Input_para& inp) else { #ifdef __EXX - if (PARAM.globalv.gamma_only_local) + if (gamma_only) { return new ModuleESolver::ESolver_BSE(inp, in_dir, out_dir); } @@ -327,7 +328,7 @@ ESolver* init_esolver(const Input_para& inp) { const std::string& in_dir = PARAM.globalv.global_readin_dir; const std::string& out_dir = PARAM.globalv.global_out_dir; - if (PARAM.globalv.gamma_only_local) + if (gamma_only) { return new ModuleESolver::ESolver_LR(inp, in_dir, out_dir); } diff --git a/source/source_esolver/esolver_fp.cpp b/source/source_esolver/esolver_fp.cpp index 74737ce903e..b0114402c67 100644 --- a/source/source_esolver/esolver_fp.cpp +++ b/source/source_esolver/esolver_fp.cpp @@ -182,7 +182,7 @@ void ESolver_FP::before_scf(UnitCell& ucell, const int istep) } // reset k-points - KVectorUtils::set_after_vc(kv, this->inp_->nspin, ucell.G); + kv.set_after_vc(this->inp_->nspin, ucell.G, GlobalV::ofs_running); ModuleBase::GlobalFunc::DONE(GlobalV::ofs_running, "INIT K-POINTS"); } diff --git a/source/source_estate/module_dm/test/CMakeLists.txt b/source/source_estate/module_dm/test/CMakeLists.txt index 37a8a5020c3..1d96186fbec 100644 --- a/source/source_estate/module_dm/test/CMakeLists.txt +++ b/source/source_estate/module_dm/test/CMakeLists.txt @@ -18,7 +18,6 @@ AddTest( ${ABACUS_SOURCE_DIR}/source_basis/module_ao/parallel_orbitals.cpp ${ABACUS_SOURCE_DIR}/source_cell/klist.cpp ${ABACUS_SOURCE_DIR}/source_cell/parallel_kpoints.cpp - ${ABACUS_SOURCE_DIR}/source_cell/k_vector_utils.cpp ${ABACUS_SOURCE_DIR}/source_cell/reciprocal_grid.cpp ) @@ -33,7 +32,6 @@ AddTest( ${ABACUS_SOURCE_DIR}/source_basis/module_ao/parallel_orbitals.cpp ${ABACUS_SOURCE_DIR}/source_cell/klist.cpp ${ABACUS_SOURCE_DIR}/source_cell/parallel_kpoints.cpp - ${ABACUS_SOURCE_DIR}/source_cell/k_vector_utils.cpp ${ABACUS_SOURCE_DIR}/source_cell/reciprocal_grid.cpp ) @@ -47,7 +45,6 @@ AddTest( ${ABACUS_SOURCE_DIR}/source_basis/module_ao/parallel_orbitals.cpp ${ABACUS_SOURCE_DIR}/source_cell/klist.cpp ${ABACUS_SOURCE_DIR}/source_cell/parallel_kpoints.cpp - ${ABACUS_SOURCE_DIR}/source_cell/k_vector_utils.cpp ${ABACUS_SOURCE_DIR}/source_cell/reciprocal_grid.cpp ) @@ -61,7 +58,6 @@ AddTest( ${ABACUS_SOURCE_DIR}/source_basis/module_ao/parallel_orbitals.cpp ${ABACUS_SOURCE_DIR}/source_cell/klist.cpp ${ABACUS_SOURCE_DIR}/source_cell/parallel_kpoints.cpp - ${ABACUS_SOURCE_DIR}/source_cell/k_vector_utils.cpp ${ABACUS_SOURCE_DIR}/source_cell/reciprocal_grid.cpp ) diff --git a/source/source_estate/test/CMakeLists.txt b/source/source_estate/test/CMakeLists.txt index 8cdeafe3e0c..1eaaa0c6bce 100644 --- a/source/source_estate/test/CMakeLists.txt +++ b/source/source_estate/test/CMakeLists.txt @@ -33,7 +33,7 @@ AddTest( TARGET MODULE_ESTATE_elecstate_print LIBS parameter base device symmetry SOURCES elecstate_print_test.cpp ../elecstate_print.cpp ../occupy.cpp - ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/k_vector_utils.cpp ../../source_cell/reciprocal_grid.cpp + ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/reciprocal_grid.cpp ) AddTest( @@ -41,7 +41,7 @@ AddTest( LIBS parameter base device symmetry SOURCES elecstate_base_test.cpp ../elecstate.cpp ../elecstate_tools.cpp ../occupy.cpp ../../source_psi/psi.cpp ../../source_base/module_fft/fft_bundle.cpp ../../source_base/module_fft/fft_cpu.cpp - ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/k_vector_utils.cpp ../../source_cell/reciprocal_grid.cpp + ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/reciprocal_grid.cpp ) AddTest( @@ -57,7 +57,7 @@ AddTest( ../../source_lcao/module_deltaspin/deltaspin_state.cpp ../../source_psi/psi.cpp ../../source_base/module_device/memory_op.cpp - ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/k_vector_utils.cpp ../../source_cell/reciprocal_grid.cpp + ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/reciprocal_grid.cpp ) AddTest( @@ -68,7 +68,7 @@ AddTest( ../fp_energy.cpp ../makov_payne.cpp ../module_pot/h_hartree_pw.cpp - ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/k_vector_utils.cpp ../../source_cell/reciprocal_grid.cpp + ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/reciprocal_grid.cpp ) AddTest( diff --git a/source/source_hsolver/test/CMakeLists.txt b/source/source_hsolver/test/CMakeLists.txt index 96625325041..72a2de07b12 100644 --- a/source/source_hsolver/test/CMakeLists.txt +++ b/source/source_hsolver/test/CMakeLists.txt @@ -78,7 +78,7 @@ if (ENABLE_MPI) LIBS parameter psi device base container symmetry SOURCES test_hsolver_pw.cpp ../hsolver_pw.cpp ../hsolver_lcaopw.cpp ../diago_bpcg.cpp ../diago_dav_subspace.cpp ../diag_const_nums.cpp ../diago_iter_assist.cpp ../para_lin_tf.cpp ../../source_estate/elecstate_tools.cpp ../../source_estate/occupy.cpp ../../source_base/module_fft/fft_bundle.cpp ../../source_base/module_fft/fft_cpu.cpp - ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/k_vector_utils.cpp ../../source_cell/reciprocal_grid.cpp + ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/reciprocal_grid.cpp ) AddTest( diff --git a/source/source_io/test/CMakeLists.txt b/source/source_io/test/CMakeLists.txt index e6a4eb35373..4385b04dabc 100644 --- a/source/source_io/test/CMakeLists.txt +++ b/source/source_io/test/CMakeLists.txt @@ -63,7 +63,7 @@ add_test( AddTest( TARGET MODULE_IO_write_eig_occ_test LIBS parameter base device symmetry - SOURCES write_eig_occ_test.cpp ../module_output/band_parallel_output.cpp ../module_energy/write_eig_occ.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/klist.cpp ../../source_cell/k_vector_utils.cpp + SOURCES write_eig_occ_test.cpp ../module_output/band_parallel_output.cpp ../module_energy/write_eig_occ.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/klist.cpp ../../source_cell/cif_io.cpp ../../source_cell/reciprocal_grid.cpp ) @@ -76,13 +76,13 @@ AddTest( AddTest( TARGET MODULE_IO_write_dos_pw LIBS parameter base device symmetry - SOURCES write_dos_pw_test.cpp ../module_dos/cal_dos.cpp ../module_dos/write_dos_pw.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/klist.cpp ../module_energy/nscf_fermi_surf.cpp ../../source_cell/k_vector_utils.cpp ../../source_cell/reciprocal_grid.cpp + SOURCES write_dos_pw_test.cpp ../module_dos/cal_dos.cpp ../module_dos/write_dos_pw.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/klist.cpp ../module_energy/nscf_fermi_surf.cpp ../../source_cell/reciprocal_grid.cpp ) AddTest( TARGET MODULE_IO_print_info LIBS parameter base device symmetry cell_info - SOURCES print_info_test.cpp ../module_output/print_info.cpp ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/k_vector_utils.cpp ../../source_cell/reciprocal_grid.cpp + SOURCES print_info_test.cpp ../module_output/print_info.cpp ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/reciprocal_grid.cpp ) AddTest( @@ -177,7 +177,7 @@ AddTest( TARGET MODULE_IO_read_wf2rho_pw_test LIBS parameter base device planewave psi symmetry SOURCES read_wf2rho_pw_test.cpp ../module_wf/read_wfc_pw.cpp ../module_wf/read_wf2rho_pw.cpp ../../source_basis/module_pw/test/test_tool.cpp ../../source_estate/module_charge/charge_mpi.cpp ../module_wf/write_wfc_pw.cpp - ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/k_vector_utils.cpp ../../source_cell/reciprocal_grid.cpp + ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/reciprocal_grid.cpp ) add_test(NAME MODULE_IO_read_wf2rho_pw_parallel @@ -232,7 +232,7 @@ AddTest( TARGET MODULE_IO_write_dmk LIBS parameter base device cell_info symmetry SOURCES ../module_dm/test/write_dmk_test.cpp ../module_dm/write_dmk.cpp ../../source_cell/ucell_io.cpp - ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/k_vector_utils.cpp ../../source_cell/reciprocal_grid.cpp + ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/reciprocal_grid.cpp ) add_test( diff --git a/source/source_io/test/print_info_test.cpp b/source/source_io/test/print_info_test.cpp index b02ff66acdf..44d500b98fd 100644 --- a/source/source_io/test/print_info_test.cpp +++ b/source/source_io/test/print_info_test.cpp @@ -53,7 +53,7 @@ TEST_F(PrintInfoTest, SetupParameters) const double kspacing[3] = {0.0, 0.0, 0.0}; const std::string kmesh_type = "gamma"; const double koffset[3] = {0.0, 0.0, 0.0}; - kv->read_kpoints(*ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset); + kv->read_kpoints(*ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, GlobalV::ofs_running); EXPECT_EQ(kv->get_nkstot(),512); std::vector cal_type = {"scf","relax","cell-relax","md"}; std::vector md_types = {"fire","nve","nvt","npt","langevin","msst"}; diff --git a/source/source_io/test_serial/CMakeLists.txt b/source/source_io/test_serial/CMakeLists.txt index 309fc0c542e..aa7fec69f4c 100644 --- a/source/source_io/test_serial/CMakeLists.txt +++ b/source/source_io/test_serial/CMakeLists.txt @@ -58,7 +58,7 @@ AddTest( TARGET MODULE_IO_write_bands LIBS parameter base device symmetry SOURCES write_bands_test.cpp ../module_output/band_parallel_output.cpp ../module_energy/write_bands.cpp - ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/k_vector_utils.cpp ../../source_cell/reciprocal_grid.cpp + ../../source_cell/klist.cpp ../../source_cell/parallel_kpoints.cpp ../../source_cell/reciprocal_grid.cpp ) AddTest( diff --git a/source/source_lcao/module_deepks/test/CMakeLists.txt b/source/source_lcao/module_deepks/test/CMakeLists.txt index 06ffbd03a97..df699495f90 100644 --- a/source/source_lcao/module_deepks/test/CMakeLists.txt +++ b/source/source_lcao/module_deepks/test/CMakeLists.txt @@ -27,7 +27,6 @@ set(DEEPKS_UNIT_COMMON_SOURCES ../../../source_cell/read_atom_species.cpp ../../../source_cell/klist.cpp ../../../source_cell/parallel_kpoints.cpp - ../../../source_cell/k_vector_utils.cpp ../../../source_cell/reciprocal_grid.cpp ../../setup_nonlocal.cpp ../../../source_cell/pseudo.cpp diff --git a/source/source_lcao/module_deltaspin/test/CMakeLists.txt b/source/source_lcao/module_deltaspin/test/CMakeLists.txt index 6a5745ac01a..a3bac8f9ee5 100644 --- a/source/source_lcao/module_deltaspin/test/CMakeLists.txt +++ b/source/source_lcao/module_deltaspin/test/CMakeLists.txt @@ -18,7 +18,6 @@ AddTest( ../../../source_basis/module_ao/parallel_orbitals.cpp ../../../source_cell/klist.cpp ../../../source_cell/parallel_kpoints.cpp - ../../../source_cell/k_vector_utils.cpp ../../../source_cell/reciprocal_grid.cpp ) @@ -33,7 +32,6 @@ AddTest( ../basic_funcs.cpp ../../../source_cell/klist.cpp ../../../source_cell/parallel_kpoints.cpp - ../../../source_cell/k_vector_utils.cpp ../../../source_cell/reciprocal_grid.cpp ) diff --git a/source/source_lcao/module_ri/module_exx_symmetry/symm_rotation.cpp b/source/source_lcao/module_ri/module_exx_symmetry/symm_rotation.cpp index 902d9a45eca..4a88554624d 100644 --- a/source/source_lcao/module_ri/module_exx_symmetry/symm_rotation.cpp +++ b/source/source_lcao/module_ri/module_exx_symmetry/symm_rotation.cpp @@ -129,7 +129,7 @@ namespace ModuleSymmetry // for nspin<4 (Theta=K) the original TRS_conj path already gives the conjugate. // // Which spatial operation the index denotes depends on the regime, matching - // how the k-reduction filled kgmatrix[] (see KVectorUtils::ibz_kpoint): + // how the k-reduction filled kgmatrix[] (see K_Vectors::reduce_by_symmetry): // - nspin=4 magnetic (Shubnikov): index j+nsym_ is the antiunitary element // Theta*gmatrix_anti[j]; its Ms is stored under the RAW key j+nsym_. // - otherwise (grey group / nspin<4): index i+nsym_ is Theta*gmatrix[i], diff --git a/source/source_lcao/test/CMakeLists.txt b/source/source_lcao/test/CMakeLists.txt index df31e44ab9b..1971ea0b056 100644 --- a/source/source_lcao/test/CMakeLists.txt +++ b/source/source_lcao/test/CMakeLists.txt @@ -23,7 +23,6 @@ AddTest( ${ABACUS_SOURCE_DIR}/source_hamilt/module_hcontainer/output_hcontainer.cpp ${ABACUS_SOURCE_DIR}/source_cell/klist.cpp ${ABACUS_SOURCE_DIR}/source_cell/parallel_kpoints.cpp - ${ABACUS_SOURCE_DIR}/source_cell/k_vector_utils.cpp ${ABACUS_SOURCE_DIR}/source_cell/reciprocal_grid.cpp ) diff --git a/source/source_pw/module_dfpt/CMakeLists.txt b/source/source_pw/module_dfpt/CMakeLists.txt index 55ef7853867..4c9398d746f 100644 --- a/source/source_pw/module_dfpt/CMakeLists.txt +++ b/source/source_pw/module_dfpt/CMakeLists.txt @@ -2,13 +2,22 @@ set(MODULE_NAME module_dfpt) set(SOURCES dfpt_pw.cpp + dfpt_pw_init.cpp + dfpt_pw_run.cpp + dfpt_pw_solve.cpp + dfpt_pw_q0.cpp dfpt_pw_data.cpp dfpt_kq_basis.cpp dfpt_pert.cpp + dfpt_pert_vkb.cpp + dfpt_pert_nl.cpp dfpt_stern.cpp dfpt_rho.cpp dfpt_phon.cpp + dfpt_phon_ewald.cpp + dfpt_phon_elec.cpp dfpt_q0.cpp + dfpt_q0_pos.cpp dfpt_metal.cpp dfpt_hamilt_shift.cpp ) diff --git a/source/source_pw/module_dfpt/dfpt_hamilt_shift.cpp b/source/source_pw/module_dfpt/dfpt_hamilt_shift.cpp index 3321ee541e7..fb46af3c87b 100644 --- a/source/source_pw/module_dfpt/dfpt_hamilt_shift.cpp +++ b/source/source_pw/module_dfpt/dfpt_hamilt_shift.cpp @@ -1,16 +1,10 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #include "dfpt_hamilt_shift.h" #include "dfpt_pert.h" #include "source_base/constants.h" #include "source_base/global_function.h" +#include "source_base/timer.h" +#include "source_base/tool_title.h" #include "source_basis/module_pw/pw_basis.h" #include "source_basis/module_pw/pw_basis_k.h" #include "source_cell/unitcell.h" @@ -19,23 +13,22 @@ #include #include -namespace ModuleDFPT { +namespace ModuleDFPT +{ DFPT_HamiltShift::DFPT_HamiltShift(const UnitCell& ucell, ModulePW::PW_Basis* pw_rho, ModulePW::PW_Basis_K* pw_wfc, const std::vector& veff_r, const DFPT_Pert* pert) - : ucell_(&ucell), - pw_rho_(pw_rho), - pw_wfc_(pw_wfc), - pert_(pert), - veff_r_(veff_r), - tpiba2_(ucell.tpiba2), - nrxx_(pw_rho != nullptr ? pw_rho->nrxx : 0) { - for (int it = 0; it < ucell_->ntype; ++it) { + : ucell_(&ucell), pw_rho_(pw_rho), pw_wfc_(pw_wfc), pert_(pert), veff_r_(veff_r), tpiba2_(ucell.tpiba2), + nrxx_(pw_rho != nullptr ? pw_rho->nrxx : 0) +{ + for (int it = 0; it < ucell_->ntype; ++it) + { const pseudo& ncpp = ucell_->atoms[it].ncpp; - if (ncpp.tvanp || ncpp.has_so) { + if (ncpp.tvanp || ncpp.has_so) + { ModuleBase::WARNING_QUIT("DFPT_HamiltShift", "the shifted Sternheimer operator is implemented for " "normal-conserving separable pseudopotentials only."); @@ -45,10 +38,13 @@ DFPT_HamiltShift::DFPT_HamiltShift(const UnitCell& ucell, std::vector ib; std::vector m; int mu = 0; - for (int ibeta = 0; ibeta < ncpp.nbeta; ++ibeta) { + for (int ibeta = 0; ibeta < ncpp.nbeta; ++ibeta) + { const int l = ncpp.lll[ibeta]; - for (int im = 0; im < 2 * l + 1; ++im) { - if (mu < ncpp.nh) { + for (int im = 0; im < 2 * l + 1; ++im) + { + if (mu < ncpp.nh) + { ib.push_back(ibeta); m.push_back(im); } @@ -60,29 +56,38 @@ DFPT_HamiltShift::DFPT_HamiltShift(const UnitCell& ucell, } } -DFPT_HamiltShift::~DFPT_HamiltShift() {} +DFPT_HamiltShift::~DFPT_HamiltShift() +{ +} -void DFPT_HamiltShift::set_context(const ModuleBase::Vector3& q_cart, int k_idx) { +void DFPT_HamiltShift::set_context(const ModuleBase::Vector3& q_cart, int k_idx) +{ + ModuleBase::TITLE("DFPT_HamiltShift", "set_context"); + ModuleBase::timer::start("DFPT_HamiltShift", "set_context"); kq_.init(pw_wfc_, pw_rho_, q_cart, k_idx); ik_cache_ = k_idx; const int npw = kq_.get_npwk(); // k+q G index -> charge-grid G index (both bases share the FFT cell) kq2rho_.assign(npw, -1); - for (int igl = 0; igl < npw; ++igl) { + for (int igl = 0; igl < npw; ++igl) + { kq2rho_[igl] = kq_.get_ig_rho(igl); } // cache the beta projectors of every atom on the k+q list std::vector> gk(npw); - for (int igl = 0; igl < npw; ++igl) { + for (int igl = 0; igl < npw; ++igl) + { gk[igl] = kq_.get_gpluskq(igl); } vkb_cache_.assign(ucell_->nat, std::vector>>()); - for (int iat = 0; iat < ucell_->nat; ++iat) { + for (int iat = 0; iat < ucell_->nat; ++iat) + { const int it = ucell_->iat2it[iat]; const int ia = ucell_->iat2ia[iat]; - if (ucell_->atoms[it].ncpp.nh == 0) { + if (ucell_->atoms[it].ncpp.nh == 0) + { continue; } pert_->build_vkb(it, ia, gk, vkb_cache_[iat]); @@ -90,124 +95,173 @@ void DFPT_HamiltShift::set_context(const ModuleBase::Vector3& q_cart, in x_recip_.assign(pw_rho_->npw, std::complex(0.0, 0.0)); x_r_.assign(nrxx_, std::complex(0.0, 0.0)); + ModuleBase::timer::end("DFPT_HamiltShift", "set_context"); } -void DFPT_HamiltShift::set_shift(double shift) { +void DFPT_HamiltShift::set_shift(double shift) +{ + ModuleBase::TITLE("DFPT_HamiltShift", "set_shift"); + ModuleBase::timer::start("DFPT_HamiltShift", "set_shift"); shift_ = shift; + ModuleBase::timer::end("DFPT_HamiltShift", "set_shift"); } -int DFPT_HamiltShift::dimension() const { +int DFPT_HamiltShift::dimension() const +{ + ModuleBase::TITLE("DFPT_HamiltShift", "dimension"); + ModuleBase::timer::start("DFPT_HamiltShift", "dimension"); + ModuleBase::timer::end("DFPT_HamiltShift", "dimension"); return kq_.get_npwk(); } -void DFPT_HamiltShift::apply(const std::complex* x, std::complex* y) const { +void DFPT_HamiltShift::apply(const std::complex* x, std::complex* y) const +{ + ModuleBase::TITLE("DFPT_HamiltShift", "apply"); + ModuleBase::timer::start("DFPT_HamiltShift", "apply"); const int npw = kq_.get_npwk(); - if (npw <= 0 || x == nullptr || y == nullptr) { + if (npw <= 0 || x == nullptr || y == nullptr) + { + ModuleBase::timer::end("DFPT_HamiltShift", "apply"); return; } // kinetic part minus the eigenvalue shift - for (int igl = 0; igl < npw; ++igl) { + for (int igl = 0; igl < npw; ++igl) + { y[igl] = (tpiba2_ * kq_.get_gk2(igl) - shift_) * x[igl]; } // local effective potential: phase-free FFT convolution on the shared // grid (the k+q Bloch phases cancel in the product, real_space_dv conv.) std::fill(x_recip_.begin(), x_recip_.end(), std::complex(0.0, 0.0)); - for (int igl = 0; igl < npw; ++igl) { - if (kq2rho_[igl] >= 0) { + for (int igl = 0; igl < npw; ++igl) + { + if (kq2rho_[igl] >= 0) + { x_recip_[kq2rho_[igl]] = x[igl]; } } pw_rho_->recip2real(x_recip_.data(), x_r_.data()); - for (int ir = 0; ir < nrxx_; ++ir) { + for (int ir = 0; ir < nrxx_; ++ir) + { x_r_[ir] *= veff_r_[ir]; } pw_rho_->real2recip(x_r_.data(), x_recip_.data()); - for (int igl = 0; igl < npw; ++igl) { - if (kq2rho_[igl] >= 0) { + for (int igl = 0; igl < npw; ++igl) + { + if (kq2rho_[igl] >= 0) + { y[igl] += x_recip_[kq2rho_[igl]]; } } // nonlocal part with the cached k+q projectors - for (int iat = 0; iat < ucell_->nat; ++iat) { + for (int iat = 0; iat < ucell_->nat; ++iat) + { const int it = ucell_->iat2it[iat]; const int nh = ucell_->atoms[it].ncpp.nh; - if (nh == 0) { + if (nh == 0) + { continue; } const std::vector>>& vkb = vkb_cache_[iat]; becp_.assign(nh, std::complex(0.0, 0.0)); - for (int mu = 0; mu < nh; ++mu) { - for (int igl = 0; igl < npw; ++igl) { + for (int mu = 0; mu < nh; ++mu) + { + for (int igl = 0; igl < npw; ++igl) + { becp_[mu] += std::conj(vkb[mu][igl]) * x[igl]; } } dbecp_.assign(nh, std::complex(0.0, 0.0)); - for (int mu = 0; mu < nh; ++mu) { - for (int nu = 0; nu < nh; ++nu) { - if (mu_m_[it][mu] != mu_m_[it][nu]) { + for (int mu = 0; mu < nh; ++mu) + { + for (int nu = 0; nu < nh; ++nu) + { + if (mu_m_[it][mu] != mu_m_[it][nu]) + { continue; } dbecp_[mu] += ucell_->atoms[it].ncpp.dion(mu_ib_[it][mu], mu_ib_[it][nu]) * becp_[nu]; } } - for (int mu = 0; mu < nh; ++mu) { - for (int igl = 0; igl < npw; ++igl) { + for (int mu = 0; mu < nh; ++mu) + { + for (int igl = 0; igl < npw; ++igl) + { y[igl] += vkb[mu][igl] * dbecp_[mu]; } } } + ModuleBase::timer::end("DFPT_HamiltShift", "apply"); } -double DFPT_HamiltShift::debug_t_vnl(const std::vector>& x) const { +double DFPT_HamiltShift::debug_t_vnl(const std::vector>& x) const +{ + ModuleBase::TITLE("DFPT_HamiltShift", "debug_t_vnl"); + ModuleBase::timer::start("DFPT_HamiltShift", "debug_t_vnl"); const int npw = kq_.get_npwk(); double ekin = 0.0; - for (int igl = 0; igl < npw; ++igl) { + for (int igl = 0; igl < npw; ++igl) + { ekin += tpiba2_ * kq_.get_gk2(igl) * std::norm(x[igl]); } double vnl = 0.0; - for (int iat = 0; iat < ucell_->nat; ++iat) { + for (int iat = 0; iat < ucell_->nat; ++iat) + { const int it = ucell_->iat2it[iat]; const int nh = ucell_->atoms[it].ncpp.nh; - if (nh == 0) { + if (nh == 0) + { continue; } const std::vector>>& vkb = vkb_cache_[iat]; becp_.assign(nh, std::complex(0.0, 0.0)); - for (int mu = 0; mu < nh; ++mu) { - for (int igl = 0; igl < npw; ++igl) { + for (int mu = 0; mu < nh; ++mu) + { + for (int igl = 0; igl < npw; ++igl) + { becp_[mu] += std::conj(vkb[mu][igl]) * x[igl]; } } dbecp_.assign(nh, std::complex(0.0, 0.0)); - for (int mu = 0; mu < nh; ++mu) { - for (int nu = 0; nu < nh; ++nu) { - if (mu_m_[it][mu] != mu_m_[it][nu]) { + for (int mu = 0; mu < nh; ++mu) + { + for (int nu = 0; nu < nh; ++nu) + { + if (mu_m_[it][mu] != mu_m_[it][nu]) + { continue; } dbecp_[mu] += ucell_->atoms[it].ncpp.dion(mu_ib_[it][mu], mu_ib_[it][nu]) * becp_[nu]; } } - for (int mu = 0; mu < nh; ++mu) { + for (int mu = 0; mu < nh; ++mu) + { vnl += std::real(std::conj(becp_[mu]) * dbecp_[mu]); } } + ModuleBase::timer::end("DFPT_HamiltShift", "debug_t_vnl"); return ekin + vnl; } -double DFPT_HamiltShift::debug_v_wfc(const std::vector>& x) const { +double DFPT_HamiltShift::debug_v_wfc(const std::vector>& x) const +{ + ModuleBase::TITLE("DFPT_HamiltShift", "debug_v_wfc"); + ModuleBase::timer::start("DFPT_HamiltShift", "debug_v_wfc"); const int npw = kq_.get_npwk(); std::vector> ur(nrxx_, std::complex(0.0, 0.0)); pw_wfc_->recip2real(x.data(), ur.data(), ik_cache_); - for (int ir = 0; ir < nrxx_; ++ir) { + for (int ir = 0; ir < nrxx_; ++ir) + { ur[ir] *= veff_r_[ir]; } std::vector> xg(pw_wfc_->npwk[ik_cache_], std::complex(0.0, 0.0)); pw_wfc_->real2recip(ur.data(), xg.data(), ik_cache_); std::complex dot(0.0, 0.0); const int n = std::min(static_cast(xg.size()), npw); - for (int igl = 0; igl < n; ++igl) { + for (int igl = 0; igl < n; ++igl) + { dot += std::conj(x[igl]) * xg[igl]; } + ModuleBase::timer::end("DFPT_HamiltShift", "debug_v_wfc"); return dot.real(); } diff --git a/source/source_pw/module_dfpt/dfpt_hamilt_shift.h b/source/source_pw/module_dfpt/dfpt_hamilt_shift.h index bd5aaa35ea5..848e8a4d71e 100644 --- a/source/source_pw/module_dfpt/dfpt_hamilt_shift.h +++ b/source/source_pw/module_dfpt/dfpt_hamilt_shift.h @@ -1,28 +1,23 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #ifndef DFPT_HAMILT_SHIFT_H #define DFPT_HAMILT_SHIFT_H #include "dfpt_kq_basis.h" #include "dfpt_stern.h" #include "source_base/vector3.h" + #include #include -namespace ModulePW { +namespace ModulePW +{ class PW_Basis; class PW_Basis_K; -} +} // namespace ModulePW class UnitCell; -namespace ModuleDFPT { +namespace ModuleDFPT +{ class DFPT_Pert; @@ -44,8 +39,9 @@ class DFPT_Pert; * goes through the (ix,iy,iz) FFT-cell reverse map (C1 finding: the rho * and wfc stick encodings are not interchangeable). */ -class DFPT_HamiltShift : public DFPT_Stern::LinearOperator { -public: +class DFPT_HamiltShift : public DFPT_Stern::LinearOperator +{ + public: DFPT_HamiltShift(const UnitCell& ucell, ModulePW::PW_Basis* pw_rho, ModulePW::PW_Basis_K* pw_wfc, @@ -72,7 +68,7 @@ class DFPT_HamiltShift : public DFPT_Stern::LinearOperator { /// path (validation of the rho-grid scatter/gather convolution) double debug_v_wfc(const std::vector>& x) const; -private: + private: const UnitCell* ucell_ = nullptr; ModulePW::PW_Basis* pw_rho_ = nullptr; ModulePW::PW_Basis_K* pw_wfc_ = nullptr; diff --git a/source/source_pw/module_dfpt/dfpt_kq_basis.cpp b/source/source_pw/module_dfpt/dfpt_kq_basis.cpp index 143c813afc9..7dde521fb0b 100644 --- a/source/source_pw/module_dfpt/dfpt_kq_basis.cpp +++ b/source/source_pw/module_dfpt/dfpt_kq_basis.cpp @@ -1,32 +1,31 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in design phase and has not been -// put into production yet. -// It may change in the future. -// Please use this code with caution. -// Only developers who know -// what they are doing should use this code. -// ============================================================ - #include "dfpt_kq_basis.h" #include "source_base/global_function.h" +#include "source_base/timer.h" +#include "source_base/tool_title.h" #include "source_basis/module_pw/pw_basis.h" #include "source_basis/module_pw/pw_basis_k.h" #include -namespace ModuleDFPT { +namespace ModuleDFPT +{ -DFPT_KQ_Basis::DFPT_KQ_Basis() {} +DFPT_KQ_Basis::DFPT_KQ_Basis() +{ +} -DFPT_KQ_Basis::~DFPT_KQ_Basis() {} +DFPT_KQ_Basis::~DFPT_KQ_Basis() +{ +} void DFPT_KQ_Basis::init(const ModulePW::PW_Basis_K* pw_wfc, const ModulePW::PW_Basis* pw_rho, const ModuleBase::Vector3& q_cart, int ik) { + ModuleBase::TITLE("DFPT_KQ_Basis", "init"); + ModuleBase::timer::start("DFPT_KQ_Basis", "init"); pw_wfc_ = pw_wfc; npwk_ = 0; ig_rho_.clear(); @@ -35,6 +34,7 @@ void DFPT_KQ_Basis::init(const ModulePW::PW_Basis_K* pw_wfc, if (pw_wfc_ == nullptr || pw_rho == nullptr) { + ModuleBase::timer::end("DFPT_KQ_Basis", "init"); return; } @@ -49,8 +49,7 @@ void DFPT_KQ_Basis::init(const ModulePW::PW_Basis_K* pw_wfc, } // the two bases exchange G vectors through the shared FFT cell position - if (pw_wfc_->nx != pw_rho->nx || pw_wfc_->ny != pw_rho->ny - || pw_wfc_->nz != pw_rho->nz) + if (pw_wfc_->nx != pw_rho->nx || pw_wfc_->ny != pw_rho->ny || pw_wfc_->nz != pw_rho->nz) { ModuleBase::WARNING_QUIT("DFPT_KQ_Basis", "DFPT requires the wavefunction and charge FFT grids to share " @@ -75,7 +74,9 @@ void DFPT_KQ_Basis::init(const ModulePW::PW_Basis_K* pw_wfc, } std::set taken; - auto try_push = [&](const int ix_in, const int iy_in, const int iz_in, + auto try_push = [&](const int ix_in, + const int iy_in, + const int iz_in, const ModuleBase::Matrix3& gbase, const int ig_rho_hint) { int ix = ix_in; @@ -93,8 +94,7 @@ void DFPT_KQ_Basis::init(const ModulePW::PW_Basis_K* pw_wfc, { iz -= pw_wfc_->nz; } - const ModuleBase::Vector3 gcar - = ModuleBase::Vector3(ix, iy, iz) * gbase; + const ModuleBase::Vector3 gcar = ModuleBase::Vector3(ix, iy, iz) * gbase; const ModuleBase::Vector3 gpluskq = gcar + kplusq_c_; const double gk2 = gpluskq * gpluskq; if (gk2 > pw_wfc_->gk_ecut) @@ -137,16 +137,20 @@ void DFPT_KQ_Basis::init(const ModulePW::PW_Basis_K* pw_wfc, try_push(ix, iy, iz, pw_rho->G, ig); } npwk_ = static_cast(gcar_.size()); + ModuleBase::timer::end("DFPT_KQ_Basis", "init"); } void DFPT_KQ_Basis::clear() { + ModuleBase::TITLE("DFPT_KQ_Basis", "clear"); + ModuleBase::timer::start("DFPT_KQ_Basis", "clear"); pw_wfc_ = nullptr; kplusq_c_ = ModuleBase::Vector3(); npwk_ = 0; ig_rho_.clear(); gk2_.clear(); gcar_.clear(); + ModuleBase::timer::end("DFPT_KQ_Basis", "clear"); } } // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_kq_basis.h b/source/source_pw/module_dfpt/dfpt_kq_basis.h index 7e26307c742..e2f4273ba1f 100644 --- a/source/source_pw/module_dfpt/dfpt_kq_basis.h +++ b/source/source_pw/module_dfpt/dfpt_kq_basis.h @@ -1,25 +1,18 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in design phase and has not been -// put into production yet. -// It may change in the future. -// Please use this code with caution. -// Only developers who know -// what they are doing should use this code. -// ============================================================ - #ifndef DFPT_KQ_BASIS_H #define DFPT_KQ_BASIS_H #include "source_base/vector3.h" + #include -namespace ModulePW { +namespace ModulePW +{ class PW_Basis; class PW_Basis_K; -} +} // namespace ModulePW -namespace ModuleDFPT { +namespace ModuleDFPT +{ /** * @brief Plane-wave basis at the perturbation wavevector k+q. @@ -44,8 +37,9 @@ namespace ModuleDFPT { * - Both bases must share the same FFT grid dimensions (the k+q G vectors * are exchanged between them through the shared FFT cell position). */ -class DFPT_KQ_Basis { -public: +class DFPT_KQ_Basis +{ + public: DFPT_KQ_Basis(); ~DFPT_KQ_Basis(); @@ -63,30 +57,57 @@ class DFPT_KQ_Basis { void clear(); - bool is_valid() const { return pw_wfc_ != nullptr; } + bool is_valid() const + { + return pw_wfc_ != nullptr; + } ///< number of k+q plane waves on this processor - int get_npwk() const { return npwk_; } + int get_npwk() const + { + return npwk_; + } ///< index of the G vector in the charge-density basis (-1 if the shared ///< FFT cell position carries no local rho-grid G) - int get_ig_rho(int igl) const { return ig_rho_[igl]; } + int get_ig_rho(int igl) const + { + return ig_rho_[igl]; + } ///< G in Cartesian coordinates - ModuleBase::Vector3 get_gcar(int igl) const { return gcar_[igl]; } + ModuleBase::Vector3 get_gcar(int igl) const + { + return gcar_[igl]; + } ///< G + (k+q) in Cartesian coordinates - ModuleBase::Vector3 get_gpluskq(int igl) const { return gcar_[igl] + kplusq_c_; } + ModuleBase::Vector3 get_gpluskq(int igl) const + { + return gcar_[igl] + kplusq_c_; + } ///< |G + (k+q)|^2 in units of 1/lat0^2 - double get_gk2(int igl) const { return gk2_[igl]; } + double get_gk2(int igl) const + { + return gk2_[igl]; + } ///< k+q wavevector in Cartesian coordinates - ModuleBase::Vector3 get_kplusq() const { return kplusq_c_; } - const std::vector& get_gk2_all() const { return gk2_; } - const std::vector>& get_gcar_all() const { return gcar_; } + ModuleBase::Vector3 get_kplusq() const + { + return kplusq_c_; + } + const std::vector& get_gk2_all() const + { + return gk2_; + } + const std::vector>& get_gcar_all() const + { + return gcar_; + } -private: - const ModulePW::PW_Basis_K* pw_wfc_ = nullptr; ///< ground-state k-basis - ModuleBase::Vector3 kplusq_c_; ///< k+q in Cartesian coordinates - int npwk_ = 0; ///< number of k+q plane waves - std::vector ig_rho_; ///< k+q index -> charge-grid G index - std::vector gk2_; ///< |G + (k+q)|^2 - std::vector> gcar_;///< G in Cartesian coordinates + private: + const ModulePW::PW_Basis_K* pw_wfc_ = nullptr; ///< ground-state k-basis + ModuleBase::Vector3 kplusq_c_; ///< k+q in Cartesian coordinates + int npwk_ = 0; ///< number of k+q plane waves + std::vector ig_rho_; ///< k+q index -> charge-grid G index + std::vector gk2_; ///< |G + (k+q)|^2 + std::vector> gcar_; ///< G in Cartesian coordinates }; } // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_metal.cpp b/source/source_pw/module_dfpt/dfpt_metal.cpp index 73b6652b5aa..95614d916ee 100644 --- a/source/source_pw/module_dfpt/dfpt_metal.cpp +++ b/source/source_pw/module_dfpt/dfpt_metal.cpp @@ -1,27 +1,33 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in the design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #include "dfpt_metal.h" + +#include "source_base/timer.h" #include "source_base/tool_quit.h" +#include "source_base/tool_title.h" -namespace ModuleDFPT { +namespace ModuleDFPT +{ -DFPT_Metal::DFPT_Metal() {} +DFPT_Metal::DFPT_Metal() +{ +} -DFPT_Metal::~DFPT_Metal() {} +DFPT_Metal::~DFPT_Metal() +{ +} -void DFPT_Metal::init(double sigma, const std::string& smearing_type) { +void DFPT_Metal::init(double sigma, const std::string& smearing_type) +{ + ModuleBase::TITLE("DFPT_Metal", "init"); + ModuleBase::timer::start("DFPT_Metal", "init"); sigma_ = sigma; smearing_type_ = smearing_type; + ModuleBase::timer::end("DFPT_Metal", "init"); } -void DFPT_Metal::dfdeps(const ModuleBase::matrix& eig, double efermi, - ModuleBase::matrix& dfdeps) { +void DFPT_Metal::dfdeps(const ModuleBase::matrix& eig, double efermi, ModuleBase::matrix& dfdeps) +{ + ModuleBase::TITLE("DFPT_Metal", "dfdeps"); + ModuleBase::timer::start("DFPT_Metal", "dfdeps"); // C4 interface reservation: the metallic DFPT branch (smearing // derivatives, Fermi-level shift dmu and the occupation-response part of // the first-order density) is intentionally NOT implemented in this @@ -31,44 +37,60 @@ void DFPT_Metal::dfdeps(const ModuleBase::matrix& eig, double efermi, (void)eig; (void)efermi; (void)dfdeps; - ModuleBase::WARNING_QUIT("DFPT_Metal", - "metallic DFPT (dfdeps) is not supported in the design phase"); + ModuleBase::WARNING_QUIT("DFPT_Metal", "metallic DFPT (dfdeps) is not supported in the design phase"); } -void DFPT_Metal::compute_dmu(int q_idx, const psi::Psi>& psi, - const ModuleBase::matrix& wg, const ModuleBase::matrix& dfdeps, - DFPT_PW_Data& data) { +void DFPT_Metal::compute_dmu(int q_idx, + const psi::Psi>& psi, + const ModuleBase::matrix& wg, + const ModuleBase::matrix& dfdeps, + DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Metal", "compute_dmu"); + ModuleBase::timer::start("DFPT_Metal", "compute_dmu"); (void)q_idx; (void)psi; (void)wg; (void)dfdeps; (void)data; - ModuleBase::WARNING_QUIT("DFPT_Metal", - "metallic DFPT (compute_dmu) is not supported in the design phase"); + ModuleBase::WARNING_QUIT("DFPT_Metal", "metallic DFPT (compute_dmu) is not supported in the design phase"); } -void DFPT_Metal::compute_drho_metal(int q_idx, const psi::Psi>& psi, - const ModuleBase::matrix& wg, const ModuleBase::matrix& dfdeps, - double dmu, DFPT_PW_Data& data) { +void DFPT_Metal::compute_drho_metal(int q_idx, + const psi::Psi>& psi, + const ModuleBase::matrix& wg, + const ModuleBase::matrix& dfdeps, + double dmu, + DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Metal", "compute_drho_metal"); + ModuleBase::timer::start("DFPT_Metal", "compute_drho_metal"); (void)q_idx; (void)psi; (void)wg; (void)dfdeps; (void)dmu; (void)data; - ModuleBase::WARNING_QUIT("DFPT_Metal", - "metallic DFPT (compute_drho_metal) is not supported in the design phase"); + ModuleBase::WARNING_QUIT("DFPT_Metal", "metallic DFPT (compute_drho_metal) is not supported in the design phase"); } -double DFPT_Metal::fd_dfdeps(double e, double efermi) { +double DFPT_Metal::fd_dfdeps(double e, double efermi) +{ + ModuleBase::TITLE("DFPT_Metal", "fd_dfdeps"); + ModuleBase::timer::start("DFPT_Metal", "fd_dfdeps"); (void)e; (void)efermi; + ModuleBase::timer::end("DFPT_Metal", "fd_dfdeps"); return 0.0; } -double DFPT_Metal::gauss_dfdeps(double e, double efermi) { +double DFPT_Metal::gauss_dfdeps(double e, double efermi) +{ + ModuleBase::TITLE("DFPT_Metal", "gauss_dfdeps"); + ModuleBase::timer::start("DFPT_Metal", "gauss_dfdeps"); (void)e; (void)efermi; + ModuleBase::timer::end("DFPT_Metal", "gauss_dfdeps"); return 0.0; } diff --git a/source/source_pw/module_dfpt/dfpt_metal.h b/source/source_pw/module_dfpt/dfpt_metal.h index fa5469d49fa..dc8f0a55bae 100644 --- a/source/source_pw/module_dfpt/dfpt_metal.h +++ b/source/source_pw/module_dfpt/dfpt_metal.h @@ -1,46 +1,44 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in the design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #ifndef DFPT_METAL_H #define DFPT_METAL_H #include "dfpt_pw_data.h" #include "source_psi/psi.h" -namespace ModuleDFPT { +namespace ModuleDFPT +{ -class DFPT_Metal { -public: +class DFPT_Metal +{ + public: DFPT_Metal(); ~DFPT_Metal(); - + void init(double sigma, const std::string& smearing_type); - - void dfdeps(const ModuleBase::matrix& eig, double efermi, - ModuleBase::matrix& dfdeps); - - void compute_dmu(int q_idx, const psi::Psi>& psi, - const ModuleBase::matrix& wg, const ModuleBase::matrix& dfdeps, + + void dfdeps(const ModuleBase::matrix& eig, double efermi, ModuleBase::matrix& dfdeps); + + void compute_dmu(int q_idx, + const psi::Psi>& psi, + const ModuleBase::matrix& wg, + const ModuleBase::matrix& dfdeps, DFPT_PW_Data& data); - - void compute_drho_metal(int q_idx, const psi::Psi>& psi, - const ModuleBase::matrix& wg, const ModuleBase::matrix& dfdeps, - double dmu, DFPT_PW_Data& data); -private: + void compute_drho_metal(int q_idx, + const psi::Psi>& psi, + const ModuleBase::matrix& wg, + const ModuleBase::matrix& dfdeps, + double dmu, + DFPT_PW_Data& data); + + private: double sigma_ = 0.0; std::string smearing_type_ = "gaussian"; - + double fd_dfdeps(double e, double efermi); - + double gauss_dfdeps(double e, double efermi); }; } // namespace ModuleDFPT -#endif // DFPT_METAL_H \ No newline at end of file +#endif // DFPT_METAL_H diff --git a/source/source_pw/module_dfpt/dfpt_pert.cpp b/source/source_pw/module_dfpt/dfpt_pert.cpp index dbd58c8491c..ad3a2628ba6 100644 --- a/source/source_pw/module_dfpt/dfpt_pert.cpp +++ b/source/source_pw/module_dfpt/dfpt_pert.cpp @@ -1,47 +1,62 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in the design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ +// The KB-projector construction of DFPT_Pert (radial_vq, real_ylm, +// grad_real_ylm, build_vkb, build_vkb_dk) lives in dfpt_pert_vkb.cpp +// and the nonlocal first/second-order potentials (dVnl_dtau, +// apply_d2vnl) in dfpt_pert_nl.cpp. #include "dfpt_pert.h" #include "source_base/constants.h" #include "source_base/global_function.h" #include "source_base/math_integral.h" -#include "source_base/math_sphbes.h" #include "source_base/truncated_func.h" #include "source_cell/atom_pseudo.h" #include "source_cell/atom_spec.h" #include "source_pw/module_pwdft/stru_fac.h" #include +#include #include #include #include +#include -namespace ModuleDFPT { +#include "source_base/timer.h" +#include "source_base/tool_title.h" -DFPT_Pert::DFPT_Pert() {} +namespace ModuleDFPT +{ -DFPT_Pert::~DFPT_Pert() {} +DFPT_Pert::DFPT_Pert() +{ +} + +DFPT_Pert::~DFPT_Pert() +{ +} -void DFPT_Pert::init(UnitCell& ucell, ModulePW::PW_Basis* pw_rho, - ModulePW::PW_Basis_K* pw_wfc, Structure_Factor& sf) { +void DFPT_Pert::init(UnitCell& ucell, ModulePW::PW_Basis* pw_rho, ModulePW::PW_Basis_K* pw_wfc, Structure_Factor& sf) +{ + ModuleBase::TITLE("DFPT_Pert", "init"); + ModuleBase::timer::start("DFPT_Pert", "init"); ucell_ = &ucell; pw_rho_ = pw_rho; pw_wfc_ = pw_wfc; sf_ = &sf; + ModuleBase::timer::end("DFPT_Pert", "init"); } -void DFPT_Pert::atom_index(int atom_idx, int& it, int& ia) const { +void DFPT_Pert::atom_index(int atom_idx, int& it, int& ia) const +{ + ModuleBase::TITLE("DFPT_Pert", "atom_index"); + ModuleBase::timer::start("DFPT_Pert", "atom_index"); it = 0; ia = atom_idx; - for (int it_type = 0; it_type < ucell_->ntype; ++it_type) { - if (ia < ucell_->atoms[it_type].na) { + for (int it_type = 0; it_type < ucell_->ntype; ++it_type) + { + if (ia < ucell_->atoms[it_type].na) + { it = it_type; + ModuleBase::timer::end("DFPT_Pert", "atom_index"); return; } ia -= ucell_->atoms[it_type].na; @@ -49,27 +64,47 @@ void DFPT_Pert::atom_index(int atom_idx, int& it, int& ia) const { // out of range: leave it/ia at the last type / last picture and let the // caller guard; dV requests with invalid indices simply produce nothing. ia = -1; + ModuleBase::timer::end("DFPT_Pert", "atom_index"); } -void DFPT_Pert::rho_gvec(int ig, ModuleBase::Vector3& gcar) const { +void DFPT_Pert::rho_gvec(int ig, ModuleBase::Vector3& gcar) const +{ + ModuleBase::TITLE("DFPT_Pert", "rho_gvec"); + ModuleBase::timer::start("DFPT_Pert", "rho_gvec"); const int isz = pw_rho_->ig2isz[ig]; int iz = isz % pw_rho_->nz; const int is = isz / pw_rho_->nz; const int ixy = pw_rho_->is2fftixy[is]; int ix = ixy / pw_rho_->fftny; int iy = ixy % pw_rho_->fftny; - if (ix >= int(pw_rho_->nx / 2) + 1) { ix -= pw_rho_->nx; } - if (iy >= int(pw_rho_->ny / 2) + 1) { iy -= pw_rho_->ny; } - if (iz >= int(pw_rho_->nz / 2) + 1) { iz -= pw_rho_->nz; } + if (ix >= int(pw_rho_->nx / 2) + 1) + { + ix -= pw_rho_->nx; + } + if (iy >= int(pw_rho_->ny / 2) + 1) + { + iy -= pw_rho_->ny; + } + if (iz >= int(pw_rho_->nz / 2) + 1) + { + iz -= pw_rho_->nz; + } gcar = ModuleBase::Vector3(ix, iy, iz) * ucell_->G; + ModuleBase::timer::end("DFPT_Pert", "rho_gvec"); } -double DFPT_Pert::vloc_at_g(int it, double g2) const { +double DFPT_Pert::vloc_at_g(int it, double g2) const +{ + ModuleBase::TITLE("DFPT_Pert", "vloc_at_g"); + ModuleBase::timer::start("DFPT_Pert", "vloc_at_g"); // g2 is the squared magnitude in bohr^-2 units. + const double g_zero_tol = 1.0e-8; ///< empirical parameter: |G| floor (bohr^-1) for the G=0 radial integral const Atom* atom = &ucell_->atoms[it]; const double zv = atom->ncpp.zv; - if (atom->coulomb_potential) { + if (atom->coulomb_potential) + { // analytic Coulomb local potential (vl_pw.cpp::vloc_coulomb) + ModuleBase::timer::end("DFPT_Pert", "vloc_at_g"); return -zv * ModuleBase::e2 * ModuleBase::FOUR_PI / ucell_->omega / g2; } // numeric pseudopotential: mirror vl_pw.cpp::vloc_of_g at the requested @@ -80,15 +115,19 @@ double DFPT_Pert::vloc_at_g(int it, double g2) const { const double fac = zv * ModuleBase::e2; std::vector aux(msh); const double g = std::sqrt(g2); - if (g < 1.0e-8) { + if (g < g_zero_tol) + { double v0 = 0.0; - for (int ir = 0; ir < msh; ++ir) { + for (int ir = 0; ir < msh; ++ir) + { aux[ir] = atom->ncpp.r[ir] * (atom->ncpp.r[ir] * atom->ncpp.vloc_at[ir] + fac); } ModuleBase::Integral::Simpson_Integral(msh, aux.data(), atom->ncpp.rab.data(), v0); + ModuleBase::timer::end("DFPT_Pert", "vloc_at_g"); return v0 * ModuleBase::FOUR_PI / ucell_->omega; } - for (int ir = 0; ir < msh; ++ir) { + for (int ir = 0; ir < msh; ++ir) + { aux[ir] = (atom->ncpp.r[ir] * atom->ncpp.vloc_at[ir] + fac * std::erf(atom->ncpp.r[ir])) * std::sin(g * atom->ncpp.r[ir]) / g; } @@ -96,34 +135,45 @@ double DFPT_Pert::vloc_at_g(int it, double g2) const { ModuleBase::Integral::Simpson_Integral(msh, aux.data(), atom->ncpp.rab.data(), v); // erf(r)-compensating gaussian subtraction (same as vloc_of_g) v -= fac * ModuleBase::truncated_exp(-g2 * 0.25) / g2; + ModuleBase::timer::end("DFPT_Pert", "vloc_at_g"); return v * ModuleBase::FOUR_PI / ucell_->omega; } -void DFPT_Pert::dVloc_dtau(int atom_idx, int dir, +void DFPT_Pert::dVloc_dtau(int atom_idx, + int dir, const ModuleBase::Vector3& q, - std::vector>& dv) { - if (pw_rho_ == nullptr || pw_rho_->gamma_only) { + std::vector>& dv) +{ + ModuleBase::TITLE("DFPT_Pert", "dVloc_dtau"); + ModuleBase::timer::start("DFPT_Pert", "dVloc_dtau"); + if (pw_rho_ == nullptr || pw_rho_->gamma_only) + { ModuleBase::WARNING_QUIT("DFPT_Pert::dVloc_dtau", "DFPT requires a complex (gamma_only=false) real-space basis."); } int it = 0; int ia = 0; atom_index(atom_idx, it, ia); - if (ia < 0) { + if (ia < 0) + { + ModuleBase::timer::end("DFPT_Pert", "dVloc_dtau"); return; } const ModuleBase::Vector3& tau = ucell_->atoms[it].tau[ia]; const int npw = pw_rho_->npw; dv.assign(npw, std::complex(0.0, 0.0)); ModuleBase::Vector3 gcar; - for (int ig = 0; ig < npw; ++ig) { + const double w2_floor = 1.0e-12; ///< empirical parameter: |Delta+q|^2 zero-shell guard (2*pi/lat0 units) + for (int ig = 0; ig < npw; ++ig) + { rho_gvec(ig, gcar); const ModuleBase::Vector3 w = gcar + q; // Delta + q, 2*pi/lat0 units const double w2 = w * w; // the Delta == -q component carries no displacement gradient (constant // potential shift) and is dropped, consistently with the G=0 handling // of the ground-state local potential. - if (w2 < 1.0e-12) { + if (w2 < w2_floor) + { continue; } const double g_bohr2 = w2 * ucell_->tpiba2; @@ -135,17 +185,22 @@ void DFPT_Pert::dVloc_dtau(int atom_idx, int dir, const double arg = -ModuleBase::TWO_PI * (w * tau); const std::complex phase(std::cos(arg), std::sin(arg)); // dV_loc / d tau_direction = -i g_dir * Vloc * exp(-i (Delta+q).tau) - const std::complex iw_dir = - std::complex(0.0, -1.0) * (ucell_->tpiba * w[dir]); + const std::complex iw_dir = std::complex(0.0, -1.0) * (ucell_->tpiba * w[dir]); dv[ig] = iw_dir * vloc * phase; } + ModuleBase::timer::end("DFPT_Pert", "dVloc_dtau"); } -void DFPT_Pert::build_dv(int q_idx, int atom_idx, int dir, DFPT_PW_Data& data) { +void DFPT_Pert::build_dv(int q_idx, int atom_idx, int dir, DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Pert", "build_dv"); + ModuleBase::timer::start("DFPT_Pert", "build_dv"); // the local first-order potential is assembled on the rho grid in reciprocal // space (reciprocal coefficients indexed by the rho-basis ig), then brought // to the shared real-space grid where apply_dv performs the convolution. - if (pw_rho_ == nullptr) { + if (pw_rho_ == nullptr) + { + ModuleBase::timer::end("DFPT_Pert", "build_dv"); return; } const ModuleBase::Vector3 q_cart = data.get_qvec(q_idx) * ucell_->G; @@ -162,69 +217,94 @@ void DFPT_Pert::build_dv(int q_idx, int atom_idx, int dir, DFPT_PW_Data& data) { // DFT+U perturbation reservation (U0): append the first-order Hubbard // potential dV_U when a DFT+U provider is wired. Physical implementation // lands in C1 (frozen projector term) and C3 (occupation response). - if (data.with_u()) { + if (data.with_u()) + { build_dv_u(q_idx, atom_idx, dir, data); } + ModuleBase::timer::end("DFPT_Pert", "build_dv"); } -void DFPT_Pert::real_space_dv(int q_idx, int k_idx, +void DFPT_Pert::real_space_dv(int q_idx, + int k_idx, const psi::Psi>& psi, DFPT_PW_Data& data, const DFPT_KQ_Basis& kq, - std::vector>>& dv_psi) const { + std::vector>>& dv_psi) const +{ + ModuleBase::TITLE("DFPT_Pert", "real_space_dv"); + ModuleBase::timer::start("DFPT_Pert", "real_space_dv"); const std::vector> dv_rc = data.get_dv_rc(q_idx, 0); - if (dv_rc.empty() || dv_rc.size() != static_cast(pw_rho_->nrxx)) { + if (dv_rc.empty() || dv_rc.size() != static_cast(pw_rho_->nrxx)) + { + ModuleBase::timer::end("DFPT_Pert", "real_space_dv"); return; } apply_vr_core(k_idx, dv_rc, psi, kq, dv_psi); + ModuleBase::timer::end("DFPT_Pert", "real_space_dv"); } -void DFPT_Pert::apply_vr(int q_idx, int k_idx, +void DFPT_Pert::apply_vr(int q_idx, + int k_idx, const std::vector>& v_rc, const psi::Psi>& psi, const ModuleBase::Vector3& q_cart, - std::vector>>& dv_psi) const { + std::vector>>& dv_psi) const +{ + ModuleBase::TITLE("DFPT_Pert", "apply_vr"); + ModuleBase::timer::start("DFPT_Pert", "apply_vr"); (void)q_idx; - if (pw_rho_ == nullptr || pw_wfc_ == nullptr - || v_rc.size() != static_cast(pw_rho_->nrxx)) { + if (pw_rho_ == nullptr || pw_wfc_ == nullptr || v_rc.size() != static_cast(pw_rho_->nrxx)) + { dv_psi.clear(); + ModuleBase::timer::end("DFPT_Pert", "apply_vr"); return; } DFPT_KQ_Basis kq; kq.init(pw_wfc_, pw_rho_, q_cart, k_idx); apply_vr_core(k_idx, v_rc, psi, kq, dv_psi); + ModuleBase::timer::end("DFPT_Pert", "apply_vr"); } void DFPT_Pert::apply_vr_core(int k_idx, const std::vector>& v_rc, const psi::Psi>& psi, const DFPT_KQ_Basis& kq, - std::vector>>& dv_psi) const { + std::vector>>& dv_psi) const +{ + ModuleBase::TITLE("DFPT_Pert", "apply_vr_core"); + ModuleBase::timer::start("DFPT_Pert", "apply_vr_core"); const int nbands = psi.get_nbands(); const int npwk_kq = kq.get_npwk(); std::vector> u_r(pw_rho_->nrxx); std::vector> d_r(pw_rho_->nrxx); std::vector> d_recip(pw_rho_->npw); dv_psi.assign(nbands, std::vector>(npwk_kq, std::complex(0.0, 0.0))); - for (int iband = 0; iband < nbands; ++iband) { + for (int iband = 0; iband < nbands; ++iband) + { pw_wfc_->recip2real(&psi(k_idx, iband, 0), u_r.data(), k_idx); - for (int ir = 0; ir < pw_rho_->nrxx; ++ir) { + for (int ir = 0; ir < pw_rho_->nrxx; ++ir) + { d_r[ir] = u_r[ir] * v_rc[ir]; } pw_rho_->real2recip(d_r.data(), d_recip.data()); std::vector> dpsi(npwk_kq, std::complex(0.0, 0.0)); - for (int igl = 0; igl < npwk_kq; ++igl) { + for (int igl = 0; igl < npwk_kq; ++igl) + { const int ig_rho = kq.get_ig_rho(igl); - if (ig_rho >= 0) { + if (ig_rho >= 0) + { dpsi[igl] = d_recip[ig_rho]; } } - dv_psi[iband] = dpsi; - } + dv_psi[iband] = dpsi; + } + ModuleBase::timer::end("DFPT_Pert", "apply_vr_core"); } -void DFPT_Pert::apply_dv(int q_idx, int k_idx, const psi::Psi>& psi, - DFPT_PW_Data& data) { +void DFPT_Pert::apply_dv(int q_idx, int k_idx, const psi::Psi>& psi, DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Pert", "apply_dv"); + ModuleBase::timer::start("DFPT_Pert", "apply_dv"); const int atom_idx = data.get_pert_atom(); const int dir = data.get_pert_dir(); const ModuleBase::Vector3 q_cart = data.get_qvec(q_idx) * ucell_->G; @@ -241,366 +321,32 @@ void DFPT_Pert::apply_dv(int q_idx, int k_idx, const psi::Psi (per displaced atom) std::vector>> dv_psi_nl; dVnl_dtau(atom_idx, dir, q_cart, psi, k_idx, dv_psi_nl); - if (dv_psi_nl.size() == static_cast(nbands)) { - for (int iband = 0; iband < nbands; ++iband) { - if (dv_psi[iband].size() != dv_psi_nl[iband].size()) { + if (dv_psi_nl.size() == static_cast(nbands)) + { + for (int iband = 0; iband < nbands; ++iband) + { + if (dv_psi[iband].size() != dv_psi_nl[iband].size()) + { continue; } - for (size_t i = 0; i < dv_psi[iband].size(); ++i) { + for (size_t i = 0; i < dv_psi[iband].size(); ++i) + { dv_psi[iband][i] += dv_psi_nl[iband][i]; } } } - for (int iband = 0; iband < nbands; ++iband) { + for (int iband = 0; iband < nbands; ++iband) + { data.set_dpsi(q_idx, k_idx, iband, dv_psi[iband]); } + ModuleBase::timer::end("DFPT_Pert", "apply_dv"); } -// --------------------------------------------------------------------------- -// nonlocal first-order potential (normal-conserving separable case) -// --------------------------------------------------------------------------- - -double DFPT_Pert::radial_vq(int it, int ib, double g) const { - const pseudo& ncpp = ucell_->atoms[it].ncpp; - const int l = ncpp.lll[ib]; - int kkbeta = ncpp.kkbeta; - if (kkbeta > 0 && (kkbeta % 2 == 0)) { - --kkbeta; - } - std::vector jl(kkbeta); - std::vector aux(kkbeta); - ModuleBase::Sphbes::Spherical_Bessel(kkbeta, ncpp.r.data(), g, l, jl.data()); - for (int ir = 0; ir < kkbeta; ++ir) { - aux[ir] = ncpp.betar(ib, ir) * jl[ir] * ncpp.r[ir]; - } - double v = 0.0; - ModuleBase::Integral::Simpson_Integral(kkbeta, aux.data(), ncpp.rab.data(), v); - // tab convention from vnl_pw.cpp: (4pi/sqrt(Omega)) * integral - return v * ModuleBase::FOUR_PI / std::sqrt(ucell_->omega); -} - -double DFPT_Pert::real_ylm(int l, int m, const ModuleBase::Vector3& ghat) const { - // orthonormal real spherical harmonics Y_{l,m} for l <= 2 with the - // standard convention, m in [-l, l]: - // Y_{l,0} = sqrt((2l+1)/4pi) P_l^0(cos0) - // Y_{l,m>0} = sqrt(2 (2l+1)/4pi (l-m)!/(l+m)!) P_l^m(cos0) cos(m phi) - // Y_{l,m<0} = sqrt(2 (2l+1)/4pi (l-|m|)!/(l+|m|)!) P_l^{|m|}(cos0) sin(|m| phi) - // with the associated Legendre convention P_1^1 = -sin0, P_2^1 = -3 sin0 cos0, - // P_2^2 = 3 sin^2 0. The ABACUS GS vkb applies an additional (-1)^|m| phase - // for the m>0 channels; exact GS parity is reconciled in the diamond - // end-to-end test (C7), while the C1 identity test is convention-independent. - const double x = ghat.x; - const double y = ghat.y; - const double z = ghat.z; - const double r = std::sqrt(x * x + y * y + z * z); - if (r < 1.0e-12) { - return (l == 0) ? 0.5 * std::sqrt(1.0 / ModuleBase::PI) : 0.0; - } - const double nx = x / r; - const double ny = y / r; - const double nz = z / r; - switch (l) { - case 0: { - return 0.5 * std::sqrt(1.0 / ModuleBase::PI); - } - case 1: { - switch (m) { - case -1: return -0.5 * std::sqrt(3.0 / ModuleBase::PI) * ny; - case 0: return 0.5 * std::sqrt(3.0 / ModuleBase::PI) * nz; - case 1: return -0.5 * std::sqrt(3.0 / ModuleBase::PI) * nx; - } - break; - } - case 2: { - switch (m) { - case -2: return 0.5 * std::sqrt(15.0 / ModuleBase::PI) * nx * ny; - case -1: return -0.5 * std::sqrt(15.0 / ModuleBase::PI) * nz * ny; - case 0: return 0.25 * std::sqrt(5.0 / ModuleBase::PI) * (3.0 * nz * nz - 1.0); - case 1: return -0.5 * std::sqrt(15.0 / ModuleBase::PI) * nz * nx; - case 2: return 0.25 * std::sqrt(15.0 / ModuleBase::PI) * (nx * nx - ny * ny); - } - break; - } - default: { - ModuleBase::WARNING_QUIT("DFPT_Pert::real_ylm", - "real_ylm implemented for l<=2 only (DFPT NC path)."); - } - } - return 0.0; -} - -void DFPT_Pert::build_vkb(int it, int ia, - const std::vector>& gk, - std::vector>>& vkb) const { - // per-type projector bookkeeping mirrors the ground-state vnl_pw.cpp layout: - // every radial beta (nbeta) with angular momentum l spins out (2l+1) - // projectors with combined index lm = l^2 + m, m in 0..2l (i.e. the real - // harmonic m channels -l..l walked as m' = (-1)^(m+1) ceil... ABACUS ylm - // block: m=0, +1, -1, +2, -2, ...). We use the signed m' directly. - const pseudo& ncpp = ucell_->atoms[it].ncpp; - const int nh = ncpp.nh; - const int ngk = static_cast(gk.size()); - const ModuleBase::Vector3& tau = ucell_->atoms[it].tau[ia]; - vkb.assign(nh, std::vector>(ngk, std::complex(0.0, 0.0))); - if (nh == 0) { - return; - } - int mu = 0; - for (int ib = 0; ib < ncpp.nbeta; ++ib) { - const int l = ncpp.lll[ib]; - if (l > 2) { - ModuleBase::WARNING_QUIT("DFPT_Pert::build_vkb", - "DFPT NC projector path implemented for l<=2 only."); - } - const std::complex pref = - std::pow(std::complex(0.0, -1.0), l); // (-i)^l - for (int m = 0; m < 2 * l + 1; ++m) { - // ABACUS real-harmonic walk over the m channels of this radial beta: - // m=0 -> m'=0; m=1 -> m'=+1; m=2 -> m'=-1; m=3 -> m'=+2; m=4 -> m'=-2 - const int mr = (m == 0) ? 0 : ((m % 2 == 1) ? (m + 1) / 2 : -(m / 2)); - for (int ig = 0; ig < ngk; ++ig) { - const ModuleBase::Vector3& G = gk[ig]; // k(+q)+G, 2*pi/lat0 - const double gnorm = std::sqrt(G * G) * ucell_->tpiba; // bohr^-1 - // real_ylm handles the |G|=0 point itself (Y_00 is - // direction-independent; l>0 channels vanish there together - // with vq), so the raw vector is passed directly. - const double ylm = real_ylm(l, mr, G); - const double vq = radial_vq(it, ib, gnorm); - // GS structure-factor convention (stru_fac.cpp get_sk / - // eigts, ci_tpi = -2pi i): exp(-i 2pi (gk.tau)) - const double arg = -ModuleBase::TWO_PI * (G * tau); - const std::complex phase(std::cos(arg), std::sin(arg)); - vkb[mu][ig] = pref * ylm * vq * phase; - } - ++mu; - } - } -} - -void DFPT_Pert::grad_real_ylm(int l, int m, const ModuleBase::Vector3& ghat, - double grad[3]) const { - // analytic gradients of the real_ylm polynomials (l <= 2), consistent - // with the conventions documented above real_ylm - const double x = ghat.x; - const double y = ghat.y; - const double z = ghat.z; - const double c1 = 0.5 * std::sqrt(3.0 / ModuleBase::PI); - const double c2 = 0.5 * std::sqrt(15.0 / ModuleBase::PI); - const double c20 = 0.25 * std::sqrt(5.0 / ModuleBase::PI); - grad[0] = grad[1] = grad[2] = 0.0; - switch (l) { - case 0: - return; - case 1: - switch (m) { - case -1: grad[1] = -c1; return; - case 0: grad[2] = c1; return; - case 1: grad[0] = -c1; return; - } - break; - case 2: - switch (m) { - case -2: grad[0] = c2 * y; grad[1] = c2 * x; return; - case -1: grad[1] = -c2 * z; grad[2] = -c2 * y; return; - case 0: grad[2] = 6.0 * c20 * z; return; - case 1: grad[0] = -c2 * z; grad[2] = -c2 * x; return; - case 2: grad[0] = 2.0 * c20 * x; grad[1] = -2.0 * c20 * y; return; - } - break; - default: - ModuleBase::WARNING_QUIT("DFPT_Pert::grad_real_ylm", - "grad_real_ylm implemented for l<=2 only (DFPT NC path)."); - } -} - -void DFPT_Pert::build_vkb_dk(int it, int ia, int dir, - const std::vector>& gk, - std::vector>>& vkb, - std::vector>>& dvkb) const { - const pseudo& ncpp = ucell_->atoms[it].ncpp; - const int nh = ncpp.nh; - const int ngk = static_cast(gk.size()); - const ModuleBase::Vector3& tau = ucell_->atoms[it].tau[ia]; - if (static_cast(vkb.size()) != nh - || static_cast(vkb[0].size()) != ngk) { - ModuleBase::WARNING_QUIT("DFPT_Pert::build_vkb_dk", - "vkb must be built on the same gk list first."); - } - dvkb.assign(nh, std::vector>(ngk, std::complex(0.0, 0.0))); - if (nh == 0) { - return; - } - const double dg = 1.0e-4; // bohr^-1, radial central-difference step - int mu = 0; - for (int ib = 0; ib < ncpp.nbeta; ++ib) { - const int l = ncpp.lll[ib]; - const std::complex pref = - std::pow(std::complex(0.0, -1.0), l); // (-i)^l - for (int m = 0; m < 2 * l + 1; ++m) { - const int mr = (m == 0) ? 0 : ((m % 2 == 1) ? (m + 1) / 2 : -(m / 2)); - for (int ig = 0; ig < ngk; ++ig) { - const ModuleBase::Vector3& G = gk[ig]; - const double gmag = std::sqrt(G * G); // 2*pi/lat0 units - const double gnorm = gmag * ucell_->tpiba; // bohr^-1 - const double vq0 = radial_vq(it, ib, gnorm); - const double dvq = (radial_vq(it, ib, gnorm + dg) - - radial_vq(it, ib, std::max(0.0, gnorm - dg))) - / (dg * (gnorm > dg ? 2.0 : 1.0)); - const double arg = -ModuleBase::TWO_PI * (G * tau); - const std::complex phase(std::cos(arg), std::sin(arg)); - const std::complex dphase = - std::complex(0.0, -ModuleBase::TWO_PI * tau[dir]) * phase; - double dy[3] = {0.0, 0.0, 0.0}; - double ylm = 0.0; - if (gmag > 1.0e-10) { - const ModuleBase::Vector3 ghat = G * (1.0 / gmag); - ylm = real_ylm(l, mr, ghat); - grad_real_ylm(l, mr, ghat, dy); - const double gdir[3] = {ghat.x, ghat.y, ghat.z}; - // chain rule dghat/dk_dir = (e_dir - ghat*ghat_dir)/|G| - double dylm_dir = 0.0; - for (int c = 0; c < 3; ++c) { - dylm_dir += dy[c] * ((c == dir ? 1.0 : 0.0) - gdir[c] * gdir[dir]); - } - dylm_dir /= gmag; - // radial chain: dg/dk_dir = tpiba * ghat_dir - const double dradial = dvq * ucell_->tpiba * gdir[dir]; - dvkb[mu][ig] = pref * phase * (dylm_dir * vq0 + ylm * dradial) - + pref * ylm * vq0 * dphase; - } else { - // degenerate |G| = 0: only the l = 0 channel survives - // (real_ylm convention); keep only the phase term - ylm = (l == 0) ? 0.5 * std::sqrt(1.0 / ModuleBase::PI) : 0.0; - dvkb[mu][ig] = pref * ylm * vq0 * dphase; - } - } - ++mu; - } - } -} - -void DFPT_Pert::dVnl_dtau(int atom_idx, int dir, - const ModuleBase::Vector3& q_cart, - const psi::Psi>& psi, int k_idx, - std::vector>>& dv_psi) { - int it = 0; - int ia = 0; - atom_index(atom_idx, it, ia); - if (ia < 0) { - return; - } - const pseudo& ncpp = ucell_->atoms[it].ncpp; - if (ncpp.tvanp || ncpp.has_so) { - // the separable NC path documented in C1; ultrasoft and spin-orbit - // projectors are deferred (their D and augmentation have |k+q| shifts - // that need the USPP machinery). - ModuleBase::WARNING_QUIT("DFPT_Pert::dVnl_dtau", - "DFPT nonlocal first-order potential is implemented " - "for normal-conserving separable pseudopotentials only."); - } - const int nh = ncpp.nh; - - // projector -> (radial beta index, m channel) table, matching build_vkb. - std::vector mu_ib(nh, 0); - std::vector mu_m(nh, 0); - int mu_idx = 0; - for (int ib = 0; ib < ncpp.nbeta; ++ib) { - const int l = ncpp.lll[ib]; - for (int m = 0; m < 2 * l + 1; ++m) { - if (mu_idx < nh) { - mu_ib[mu_idx] = ib; - mu_m[mu_idx] = m; - } - ++mu_idx; - } - } - - // incoming k basis: G = k + G' (pw_wfc k-basis index) - const int npwk = pw_wfc_->npwk[k_idx]; - std::vector> gk_in(npwk); - for (int ig = 0; ig < npwk; ++ig) { - gk_in[ig] = pw_wfc_->getgpluskcar(k_idx, ig); - } - std::vector>> vkb_in; - build_vkb(it, ia, gk_in, vkb_in); - - // outgoing k+q basis - DFPT_KQ_Basis kq; - kq.init(pw_wfc_, pw_rho_, q_cart, k_idx); - const int npwk_kq = kq.get_npwk(); - std::vector> gk_out(npwk_kq); - for (int igl = 0; igl < npwk_kq; ++igl) { - gk_out[igl] = kq.get_gpluskq(igl); - } - std::vector>> vkb_out; - build_vkb(it, ia, gk_out, vkb_out); - - const int nbands = psi.get_nbands(); - dv_psi.assign(nbands, std::vector>(npwk_kq, std::complex(0.0, 0.0))); - - for (int iband = 0; iband < nbands; ++iband) { - // becp_nu(k) = sum_G' conj(vkb_in[nu][G']) psi(G') - std::vector> becp(nh, std::complex(0.0, 0.0)); - for (int nu = 0; nu < nh; ++nu) { - for (int ig = 0; ig < npwk; ++ig) { - becp[nu] += std::conj(vkb_in[nu][ig]) * psi(k_idx, iband, ig); - } - } - // dcbecp = D * becp with D_{mu,nu} = dion(ib_mu, ib_nu) delta_{m_mu, m_nu} - std::vector> dcbecp(nh, std::complex(0.0, 0.0)); - for (int mu = 0; mu < nh; ++mu) { - for (int nu = 0; nu < nh; ++nu) { - if (mu_m[mu] != mu_m[nu]) { - continue; - } - dcbecp[mu] += ncpp.dion(mu_ib[mu], mu_ib[nu]) * becp[nu]; - } - } - // term A: i (k+q+G'')_dir * (Vnl |psi>) on the k+q basis - std::vector> term_a(npwk_kq, std::complex(0.0, 0.0)); - for (int igl = 0; igl < npwk_kq; ++igl) { - std::complex vnlpsi(0.0, 0.0); - for (int mu = 0; mu < nh; ++mu) { - vnlpsi += vkb_out[mu][igl] * dcbecp[mu]; - } - term_a[igl] = std::complex(0.0, 1.0) * (ucell_->tpiba * gk_out[igl][dir]) * vnlpsi; - } - // term B: Vnl [i (k+G')_dir |psi>] - std::vector> becp_dpsi(nh, std::complex(0.0, 0.0)); - for (int nu = 0; nu < nh; ++nu) { - for (int ig = 0; ig < npwk; ++ig) { - const std::complex dpsi_ig = - std::complex(0.0, 1.0) * (ucell_->tpiba * gk_in[ig][dir]) * psi(k_idx, iband, ig); - becp_dpsi[nu] += std::conj(vkb_in[nu][ig]) * dpsi_ig; - } - } - std::vector> dcbecp_dpsi(nh, std::complex(0.0, 0.0)); - for (int mu = 0; mu < nh; ++mu) { - for (int nu = 0; nu < nh; ++nu) { - if (mu_m[mu] != mu_m[nu]) { - continue; - } - dcbecp_dpsi[mu] += ncpp.dion(mu_ib[mu], mu_ib[nu]) * becp_dpsi[nu]; - } - } - std::vector> term_b(npwk_kq, std::complex(0.0, 0.0)); - for (int igl = 0; igl < npwk_kq; ++igl) { - std::complex vnl_dpsi(0.0, 0.0); - for (int mu = 0; mu < nh; ++mu) { - vnl_dpsi += vkb_out[mu][igl] * dcbecp_dpsi[mu]; - } - term_b[igl] = vnl_dpsi; - } - for (int igl = 0; igl < npwk_kq; ++igl) { - // GS exp(-2pi gk.tau) projector convention: dVnl/dtau_dir - // |psi> = -i (k+q+G'')_dir (Vnl|psi>) + Vnl[i (k+G')_dir |psi>] - dv_psi[iband][igl] = term_b[igl] - term_a[igl]; - } - } -} - -void DFPT_Pert::build_dv_u(int q_idx, int atom_idx, int dir, DFPT_PW_Data& data) { +void DFPT_Pert::build_dv_u(int q_idx, int atom_idx, int dir, DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Pert", "build_dv_u"); + ModuleBase::timer::start("DFPT_Pert", "build_dv_u"); // C1 frozen term of the first-order Hubbard potential: // |dphi(k+q)/dtau> V_eff + adjoint // The provider is only usable when its occupation matrices are @@ -609,7 +355,8 @@ void DFPT_Pert::build_dv_u(int q_idx, int atom_idx, int dir, DFPT_PW_Data& data) // guard is defense in depth; the diamond DFT+U test (C7) will exercise // this path once OnsiteProjector integration on the DFPT k+q basis is // finalized. - if (!data.u_active()) { + if (!data.u_active()) + { return; } (void)q_idx; @@ -619,23 +366,31 @@ void DFPT_Pert::build_dv_u(int q_idx, int atom_idx, int dir, DFPT_PW_Data& data) // (|phi(k+q)> U(diag*delta - docc) ) lands in C3 after docc. } -void DFPT_Pert::d2vloc_r(int atom_idx, int da, int db, - std::vector>& dv2_r) const { - if (pw_rho_ == nullptr) { +void DFPT_Pert::d2vloc_r(int atom_idx, int da, int db, std::vector>& dv2_r) const +{ + ModuleBase::TITLE("DFPT_Pert", "d2vloc_r"); + ModuleBase::timer::start("DFPT_Pert", "d2vloc_r"); + if (pw_rho_ == nullptr) + { + ModuleBase::timer::end("DFPT_Pert", "d2vloc_r"); return; } int it = 0; int ia = 0; atom_index(atom_idx, it, ia); - if (ia < 0) { + if (ia < 0) + { dv2_r.clear(); + ModuleBase::timer::end("DFPT_Pert", "d2vloc_r"); return; } const ModuleBase::Vector3& tau = ucell_->atoms[it].tau[ia]; const int npw = pw_rho_->npw; std::vector> dv2_recip(npw, std::complex(0.0, 0.0)); ModuleBase::Vector3 gcar; - for (int ig = 0; ig < npw; ++ig) { + const double w2_floor = 1.0e-12; ///< empirical parameter: |G|^2 zero-shell guard (2*pi/lat0 units) + for (int ig = 0; ig < npw; ++ig) + { rho_gvec(ig, gcar); // QE ground truth (dynmat_us.f90): the mixed (+q,-q) second-order // local potential is the integer-G, q-independent kernel @@ -644,7 +399,8 @@ void DFPT_Pert::d2vloc_r(int atom_idx, int da, int db, // reciprocal. const ModuleBase::Vector3 w = gcar; const double w2 = w * w; - if (w2 < 1.0e-12) { + if (w2 < w2_floor) + { continue; } const double vloc = vloc_at_g(it, w2 * ucell_->tpiba2); @@ -655,127 +411,23 @@ void DFPT_Pert::d2vloc_r(int atom_idx, int da, int db, } dv2_r.assign(pw_rho_->nrxx, std::complex(0.0, 0.0)); pw_rho_->recip2real(dv2_recip.data(), dv2_r.data()); + ModuleBase::timer::end("DFPT_Pert", "d2vloc_r"); } -void DFPT_Pert::apply_d2vnl(int atom_idx, int da, int db, - const ModuleBase::Vector3& q_eff, - bool include_middle, - const psi::Psi>& psi, int k_idx, - std::vector>>& d2v_psi) const { - int it = 0; - int ia = 0; - atom_index(atom_idx, it, ia); - if (ia < 0) { - return; - } - const pseudo& ncpp = ucell_->atoms[it].ncpp; - if (ncpp.tvanp || ncpp.has_so) { - ModuleBase::WARNING_QUIT("DFPT_Pert::apply_d2vnl", - "DFPT second-order nonlocal potential is implemented " - "for normal-conserving separable pseudopotentials only."); - } - const int nh = ncpp.nh; - const int nbands = psi.get_nbands(); - - // projector -> (radial index, m channel) table, matching build_vkb - std::vector mu_ib(nh, 0); - std::vector mu_m(nh, 0); - int mu_idx = 0; - for (int ib = 0; ib < ncpp.nbeta; ++ib) { - const int l = ncpp.lll[ib]; - for (int m = 0; m < 2 * l + 1; ++m) { - if (mu_idx < nh) { - mu_ib[mu_idx] = ib; - mu_m[mu_idx] = m; - } - ++mu_idx; - } - } - - // incoming k basis and outgoing k+q basis projectors (same atom) - const int npwk = pw_wfc_->npwk[k_idx]; - std::vector> gk_in(npwk); - for (int ig = 0; ig < npwk; ++ig) { - gk_in[ig] = pw_wfc_->getgpluskcar(k_idx, ig); - } - std::vector>> vkb_in; - build_vkb(it, ia, gk_in, vkb_in); - DFPT_KQ_Basis kq; - kq.init(pw_wfc_, pw_rho_, q_eff, k_idx); - const int npwk_kq = kq.get_npwk(); - std::vector> gk_out(npwk_kq); - for (int igl = 0; igl < npwk_kq; ++igl) { - gk_out[igl] = kq.get_gpluskq(igl); - } - std::vector>> vkb_out; - build_vkb(it, ia, gk_out, vkb_out); - - d2v_psi.assign(nbands, std::vector>(npwk_kq, std::complex(0.0, 0.0))); - for (int iband = 0; iband < nbands; ++iband) { - // becp and its (k+G')-weighted variants: becp_x = sum x(G') |beta>> becp(nh, std::complex(0.0, 0.0)); - std::vector> becp_a(nh, std::complex(0.0, 0.0)); - std::vector> becp_b(nh, std::complex(0.0, 0.0)); - std::vector> becp_ab(nh, std::complex(0.0, 0.0)); - for (int nu = 0; nu < nh; ++nu) { - for (int ig = 0; ig < npwk; ++ig) { - const std::complex vc = std::conj(vkb_in[nu][ig]) * psi(k_idx, iband, ig); - const double kp_da = ucell_->tpiba * gk_in[ig][da]; - const double kp_db = ucell_->tpiba * gk_in[ig][db]; - becp[nu] += vc; - becp_a[nu] += kp_da * vc; - becp_b[nu] += kp_db * vc; - becp_ab[nu] += kp_da * kp_db * vc; - } - } - // D contraction with the same-m selection rule as dVnl_dtau - std::vector> d0(nh, std::complex(0.0, 0.0)); - std::vector> da_(nh, std::complex(0.0, 0.0)); - std::vector> db_(nh, std::complex(0.0, 0.0)); - std::vector> dab(nh, std::complex(0.0, 0.0)); - for (int mu = 0; mu < nh; ++mu) { - for (int nu = 0; nu < nh; ++nu) { - if (mu_m[mu] != mu_m[nu]) { - continue; - } - const double dij = ncpp.dion(mu_ib[mu], mu_ib[nu]); - d0[mu] += dij * becp[nu]; - da_[mu] += dij * becp_a[nu]; - db_[mu] += dij * becp_b[nu]; - dab[mu] += dij * becp_ab[nu]; - } - } - // chi(G'') = sum_mu vkb_out,mu [ -kq_da kq_db d0 - dab - // + (include_middle ? kq_da db_ + kq_db da_ : 0) ]_mu - // QE ground truth (dynmat_us.f90 + phq_init.f90): the KB second-order - // term pairs gammap (integer-G (k+G)_da(k+G)_db derivative of beta) - // with becp1 = and the same-atom alphap_a* alphap_b - // middle product; everything is built at k with integer-G momentum - // factors, so the caller passes q_eff = 0 and the kernel is - // q-independent for every q. - for (int igl = 0; igl < npwk_kq; ++igl) { - const double kq_da = ucell_->tpiba * gk_out[igl][da]; - const double kq_db = ucell_->tpiba * gk_out[igl][db]; - std::complex chi(0.0, 0.0); - for (int mu = 0; mu < nh; ++mu) { - chi += vkb_out[mu][igl] * (-kq_da * kq_db * d0[mu] - dab[mu]); - if (include_middle) { - chi += vkb_out[mu][igl] * (kq_da * db_[mu] + kq_db * da_[mu]); - } - } - d2v_psi[iband][igl] = chi; - } - } -} - -void DFPT_Pert::build_efield(const ModuleBase::Vector3& field, DFPT_PW_Data& data) { +void DFPT_Pert::build_efield(const ModuleBase::Vector3& field, DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Pert", "build_efield"); + ModuleBase::timer::start("DFPT_Pert", "build_efield"); // first-order electric-field potential: delta V(r) = - r . E (q=0 limit, // position operator in the periodic cell). Computed directly on the shared // real-space grid. Only relevant for the Q0 dielectric response (C6). - if (pw_rho_ == nullptr) { + if (pw_rho_ == nullptr) + { + ModuleBase::timer::end("DFPT_Pert", "build_efield"); return; } - if (pw_rho_->gamma_only) { + if (pw_rho_->gamma_only) + { ModuleBase::WARNING_QUIT("DFPT_Pert::build_efield", "DFPT requires a complex (gamma_only=false) real-space basis."); } @@ -784,7 +436,8 @@ void DFPT_Pert::build_efield(const ModuleBase::Vector3& field, DFPT_PW_D const double lat0 = ucell_->lat0; // shared real-space grid layout (serial pool): ir = (ix*ny + iy)*nz + iz, // i.e. z runs fastest (verified against the impulse response of the FFT). - for (int ir = 0; ir < pw_rho_->nrxx; ++ir) { + for (int ir = 0; ir < pw_rho_->nrxx; ++ir) + { const int iz = ir % pw_rho_->nz; const int rem = ir / pw_rho_->nz; const int iy = rem % pw_rho_->ny; @@ -799,6 +452,7 @@ void DFPT_Pert::build_efield(const ModuleBase::Vector3& field, DFPT_PW_D dv_real[ir] = -(field * r); // -e r.E (e absorbed in field convention) } data.set_dv_rc(0, 0, dv_real); + ModuleBase::timer::end("DFPT_Pert", "build_efield"); } -} // namespace ModuleDFPT \ No newline at end of file +} // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_pert.h b/source/source_pw/module_dfpt/dfpt_pert.h index 86124f53a64..8da6662696c 100644 --- a/source/source_pw/module_dfpt/dfpt_pert.h +++ b/source/source_pw/module_dfpt/dfpt_pert.h @@ -1,43 +1,41 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in the design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #ifndef DFPT_PERT_H #define DFPT_PERT_H #include "dfpt_kq_basis.h" #include "dfpt_pw_data.h" -#include "source_cell/unitcell.h" -#include "source_psi/psi.h" #include "source_basis/module_pw/pw_basis.h" #include "source_basis/module_pw/pw_basis_k.h" +#include "source_cell/unitcell.h" +#include "source_psi/psi.h" class Structure_Factor; -namespace ModuleDFPT { +namespace ModuleDFPT +{ -class DFPT_Pert { -public: +class DFPT_Pert +{ + public: DFPT_Pert(); ~DFPT_Pert(); - - void init(UnitCell& ucell, ModulePW::PW_Basis* pw_rho, - ModulePW::PW_Basis_K* pw_wfc, Structure_Factor& sf); + + void init(UnitCell& ucell, ModulePW::PW_Basis* pw_rho, ModulePW::PW_Basis_K* pw_wfc, Structure_Factor& sf); /// C5: read access to the ground-state wfc basis for the dynamical-matrix /// contractions in DFPT_Phon::accumulate_electron. - ModulePW::PW_Basis_K* get_pw_wfc() const { return pw_wfc_; } - ModulePW::PW_Basis* get_pw_rho() const { return pw_rho_; } - + ModulePW::PW_Basis_K* get_pw_wfc() const + { + return pw_wfc_; + } + ModulePW::PW_Basis* get_pw_rho() const + { + return pw_rho_; + } + void build_dv(int q_idx, int atom_idx, int dir, DFPT_PW_Data& data); - - void apply_dv(int q_idx, int k_idx, const psi::Psi>& psi, - DFPT_PW_Data& data); - + + void apply_dv(int q_idx, int k_idx, const psi::Psi>& psi, DFPT_PW_Data& data); + void build_efield(const ModuleBase::Vector3& field, DFPT_PW_Data& data); /// C5: real-space kernel of the same-atom second-order LOCAL potential @@ -50,23 +48,22 @@ class DFPT_Pert { /// and skips otherwise. Returned on the shared real-space grid; its /// expectation value with |u(r)|^2 enters the electronic dynamical /// matrix (anharmonic term). - void d2vloc_r(int atom_idx, int da, int db, - std::vector>& dv2_r) const; + void d2vloc_r(int atom_idx, int da, int db, std::vector>& dv2_r) const; /// C5: same-atom second-order NONLOCAL potential acting on psi, /// chi_n(G'') = (d^2 Vnl / d tau_{da} d tau_{db}) |psi_n> on the /// q_eff-shifted basis (q_eff = q when q is itself a reciprocal vector, - /// otherwise 2q: the second-order potential carries wavevector 2q, and - /// the |d beta>& q_eff, - bool include_middle, - const psi::Psi>& psi, int k_idx, + const psi::Psi>& psi, + int k_idx, std::vector>>& d2v_psi) const; /// Build the beta-projector array (in the ABACUS vkb convention) for a @@ -77,7 +74,8 @@ class DFPT_Pert { /// Usable for both the incoming k basis (G = k+G') and the outgoing DFPT /// k+q basis (G = k+q+G''), so the atomic phase is correct on either side. /// Public since C6: DFPT_Q0 reuses it for the velocity operator. - void build_vkb(int it, int ia, + void build_vkb(int it, + int ia, const std::vector>& gk, std::vector>>& vkb) const; @@ -88,7 +86,9 @@ class DFPT_Pert { /// and the real-harmonic direction derivative (grad_real_ylm chain /// (e_dir - ghat ghat_dir)/|G|). Feeds the dV_nl/dk part of the /// velocity operator in DFPT_Q0::pos_matrix. - void build_vkb_dk(int it, int ia, int dir, + void build_vkb_dk(int it, + int ia, + int dir, const std::vector>& gk, std::vector>>& vkb, std::vector>>& dvkb) const; @@ -101,38 +101,28 @@ class DFPT_Pert { /// The potential is the q-shifted complex periodic amplitude (the same /// convention as dv_rc); the DFPT self-consistent loop uses it for the /// screened response potential (Hartree + XC) of the mixed density. - void apply_vr(int q_idx, int k_idx, + void apply_vr(int q_idx, + int k_idx, const std::vector>& v_rc, const psi::Psi>& psi, const ModuleBase::Vector3& q_cart, std::vector>>& dv_psi) const; -private: - UnitCell* ucell_ = nullptr; - ModulePW::PW_Basis* pw_rho_ = nullptr; - ModulePW::PW_Basis_K* pw_wfc_ = nullptr; - Structure_Factor* sf_ = nullptr; - - /// C1: first-order LOCAL potential dVloc_dtau (per displaced atom). /// Grid helper: reconstruct the cartesian reciprocal vector (in 2*pi/lat0 /// units) of rho-grid index ig from the shared FFT-grid (ix,iy,iz) mapping. + /// A stateless building block exposed publicly so the serial analytic + /// tests can validate the rho-grid G layout directly. void rho_gvec(int ig, ModuleBase::Vector3& gcar) const; - /// The local pseudopotential Vloc(g^2) at an arbitrary magnitude: - /// Coulomb atoms use the analytic form, numeric pseudopotentials reuse the - /// radial-mesh Fourier transform of vl_pw.cpp::vloc_of_g at |g| themselves. - double vloc_at_g(int it, double g2) const; - /// linear atom index -> (type, picture) of ucell_. - void atom_index(int atom_idx, int& it, int& ia) const; /// First-order asymmetric-part local potential on the rho grid: /// dVloc_dtau(Delta) = -i (Delta+q).direction * Vloc(|Delta+q|) /// * exp(-i (Delta+q).tau_atom) * ... /// (GS structure-factor convention exp(-2pi g.tau); the sign/coefficient /// is the exact derivative of the local potential with respect to the - /// atomic displacement). - void dVloc_dtau(int atom_idx, int dir, const ModuleBase::Vector3& q, - std::vector>& dv); - + /// atomic displacement). Stateless building block validated by the serial + /// analytic tests against a finite difference of the displaced potential. + void dVloc_dtau(int atom_idx, int dir, const ModuleBase::Vector3& q, std::vector>& dv); + /// C1: first-order NONLOCAL potential acting on psi (normal-conserving /// separable case), for one displaced atom in direction dir. /// Uses the identity (GS exp(-2pi gk.tau) projector convention) @@ -142,21 +132,40 @@ class DFPT_Pert { /// DFPT k+q outgoing basis are needed (dsVnl contribution per pair is /// i (q+G''-G')_a times the zero-order matrix element). /// USPP/ultrasoft and spin-orbit projectors are rejected for now. - void dVnl_dtau(int atom_idx, int dir, const ModuleBase::Vector3& q, - const psi::Psi>& psi, int k_idx, - std::vector>>& dv_psi); + /// Stateless building block validated by the serial analytic tests + /// against an operator finite difference. + void dVnl_dtau(int atom_idx, + int dir, + const ModuleBase::Vector3& q, + const psi::Psi>& psi, + int k_idx, + std::vector>>& dv_psi); + + private: + UnitCell* ucell_ = nullptr; + ModulePW::PW_Basis* pw_rho_ = nullptr; + ModulePW::PW_Basis_K* pw_wfc_ = nullptr; + Structure_Factor* sf_ = nullptr; + + /// C1: first-order LOCAL potential dVloc_dtau (per displaced atom). + /// The local pseudopotential Vloc(g^2) at an arbitrary magnitude: + /// Coulomb atoms use the analytic form, numeric pseudopotentials reuse the + /// radial-mesh Fourier transform of vl_pw.cpp::vloc_of_g at |g| themselves. + double vloc_at_g(int it, double g2) const; + /// linear atom index -> (type, picture) of ucell_. + void atom_index(int atom_idx, int& it, int& ia) const; /// real spherical harmonic Y_{l,m}(g_hat), orthonormal convention, l<=2. double real_ylm(int l, int m, const ModuleBase::Vector3& ghat) const; /// gradient of real_ylm with respect to the unit vector ghat, l<=2 /// (dY/dghat returned per cartesian component). - void grad_real_ylm(int l, int m, const ModuleBase::Vector3& ghat, - double grad[3]) const; + void grad_real_ylm(int l, int m, const ModuleBase::Vector3& ghat, double grad[3]) const; /// General (nonlocal and local) part of apply_dv for the compartments that /// live in real space (local potential); the |psi> product requires the /// shared real-space grid of pw_rho_/pw_wfc_. - void real_space_dv(int q_idx, int k_idx, + void real_space_dv(int q_idx, + int k_idx, const psi::Psi>& psi, DFPT_PW_Data& data, const DFPT_KQ_Basis& kq, @@ -177,4 +186,4 @@ class DFPT_Pert { } // namespace ModuleDFPT -#endif // DFPT_PERT_H \ No newline at end of file +#endif // DFPT_PERT_H diff --git a/source/source_pw/module_dfpt/dfpt_pert_nl.cpp b/source/source_pw/module_dfpt/dfpt_pert_nl.cpp new file mode 100644 index 00000000000..210c85d118f --- /dev/null +++ b/source/source_pw/module_dfpt/dfpt_pert_nl.cpp @@ -0,0 +1,346 @@ +// Nonlocal first- and second-order potentials of DFPT_Pert (normal- +// conserving separable case), split out of dfpt_pert.cpp: dVnl_dtau +// and apply_d2vnl with the shared projector table, D contraction and +// projector-sum helpers. All formulas are moved verbatim from the +// original bodies; the always-true include_middle knob of +// apply_d2vnl is dropped (its q-independence is established QE ground +// truth, see the comment inside apply_d2vnl). + +#include "dfpt_pert.h" + +#include "source_base/constants.h" +#include "source_base/tool_quit.h" +#include "source_cell/atom_pseudo.h" + +#include +#include +#include + +#include "source_base/timer.h" +#include "source_base/tool_title.h" + +namespace ModuleDFPT +{ + +namespace +{ + +/// projector -> (radial beta index, m channel) table matching build_vkb +void nl_projector_table(const pseudo& ncpp, int nh, std::vector& mu_ib, std::vector& mu_m) +{ + mu_ib.assign(nh, 0); + mu_m.assign(nh, 0); + int mu_idx = 0; + for (int ib = 0; ib < ncpp.nbeta; ++ib) + { + const int l = ncpp.lll[ib]; + for (int m = 0; m < 2 * l + 1; ++m) + { + if (mu_idx < nh) + { + mu_ib[mu_idx] = ib; + mu_m[mu_idx] = m; + } + ++mu_idx; + } + } +} + +/// cartesian list of the k (+q) plane waves of one k point (2*pi/lat0 units) +std::vector> nl_gk_list(const ModulePW::PW_Basis_K& pw_wfc, int k_idx, int npwk) +{ + std::vector> gk(npwk); + for (int ig = 0; ig < npwk; ++ig) + { + gk[ig] = pw_wfc.getgpluskcar(k_idx, ig); + } + return gk; +} + +/// becp_nu(k) = sum_G' conj(vkb_in[nu][G']) psi(G') for one band +void nl_becp(int npwk, + const std::vector>>& vkb_in, + const std::complex* psi_in, + std::vector>& becp) +{ + const int nh = static_cast(vkb_in.size()); + becp.assign(nh, std::complex(0.0, 0.0)); + for (int nu = 0; nu < nh; ++nu) + { + for (int ig = 0; ig < npwk; ++ig) + { + becp[nu] += std::conj(vkb_in[nu][ig]) * psi_in[ig]; + } + } +} + +/// becp of the momentum-weighted band i (k+G')_dir |psi> (term B carrier) +void nl_becp_dpsi(int npwk, + int dir, + double tpiba, + const std::vector>& gk_in, + const std::vector>>& vkb_in, + const std::complex* psi_in, + std::vector>& becp_dpsi) +{ + const int nh = static_cast(vkb_in.size()); + becp_dpsi.assign(nh, std::complex(0.0, 0.0)); + for (int nu = 0; nu < nh; ++nu) + { + for (int ig = 0; ig < npwk; ++ig) + { + const std::complex dpsi_ig + = std::complex(0.0, 1.0) * (tpiba * gk_in[ig][dir]) * psi_in[ig]; + becp_dpsi[nu] += std::conj(vkb_in[nu][ig]) * dpsi_ig; + } + } +} + +/// dcbecp = D * becp with D_{mu,nu} = dion(ib_mu, ib_nu) delta_{m_mu, m_nu} +void nl_d_contract(const pseudo& ncpp, + const std::vector& mu_ib, + const std::vector& mu_m, + const std::vector>& becp, + std::vector>& dcbecp) +{ + const int nh = static_cast(becp.size()); + dcbecp.assign(nh, std::complex(0.0, 0.0)); + for (int mu = 0; mu < nh; ++mu) + { + for (int nu = 0; nu < nh; ++nu) + { + if (mu_m[mu] != mu_m[nu]) + { + continue; + } + dcbecp[mu] += ncpp.dion(mu_ib[mu], mu_ib[nu]) * becp[nu]; + } + } +} + +/// D contraction of the four becp variants with the same-m selection rule +/// shared with dVnl_dtau; dx rows are 0 = d0 (plain), 1 = da_ (k+G')_da, +/// 2 = db_ (k+G')_db, 3 = dab (k+G')_da (k+G')_db +void nl_d_contract_x(const pseudo& ncpp, + const std::vector& mu_ib, + const std::vector& mu_m, + const std::vector>>& becp_x, + std::vector>>& dx) +{ + const int nh = static_cast(becp_x[0].size()); + dx.assign(4, std::vector>(nh, std::complex(0.0, 0.0))); + for (int mu = 0; mu < nh; ++mu) + { + for (int nu = 0; nu < nh; ++nu) + { + if (mu_m[mu] != mu_m[nu]) + { + continue; + } + const double dij = ncpp.dion(mu_ib[mu], mu_ib[nu]); + for (int iw = 0; iw < 4; ++iw) + { + dx[iw][mu] += dij * becp_x[iw][nu]; + } + } + } +} + +/// (Vnl |carrier>)_igl = sum_mu vkb_out[mu][igl] coeff[mu] on the k+q basis +std::complex nl_sum_projectors(const std::vector>>& vkb_out, + const std::vector>& coeff, + int nh, + int igl) +{ + std::complex vnlpsi(0.0, 0.0); + for (int mu = 0; mu < nh; ++mu) + { + vnlpsi += vkb_out[mu][igl] * coeff[mu]; + } + return vnlpsi; +} + +} // namespace + +void DFPT_Pert::dVnl_dtau(int atom_idx, + int dir, + const ModuleBase::Vector3& q_cart, + const psi::Psi>& psi, + int k_idx, + std::vector>>& dv_psi) +{ + ModuleBase::TITLE("DFPT_Pert", "dVnl_dtau"); + ModuleBase::timer::start("DFPT_Pert", "dVnl_dtau"); + int it = 0; + int ia = 0; + atom_index(atom_idx, it, ia); + if (ia < 0) + { + ModuleBase::timer::end("DFPT_Pert", "dVnl_dtau"); + return; + } + const pseudo& ncpp = ucell_->atoms[it].ncpp; + if (ncpp.tvanp || ncpp.has_so) + { + // the separable NC path documented in C1; ultrasoft and spin-orbit + // projectors are deferred (their D and augmentation have |k+q| shifts + // that need the USPP machinery). + ModuleBase::WARNING_QUIT("DFPT_Pert::dVnl_dtau", + "DFPT nonlocal first-order potential is implemented " + "for normal-conserving separable pseudopotentials only."); + } + const int nh = ncpp.nh; + + // projector -> (radial beta index, m channel) table, matching build_vkb. + std::vector mu_ib; + std::vector mu_m; + nl_projector_table(ncpp, nh, mu_ib, mu_m); + + // incoming k basis: G = k + G' (pw_wfc k-basis index) + const int npwk = pw_wfc_->npwk[k_idx]; + const std::vector> gk_in = nl_gk_list(*pw_wfc_, k_idx, npwk); + std::vector>> vkb_in; + build_vkb(it, ia, gk_in, vkb_in); + + // outgoing k+q basis + DFPT_KQ_Basis kq; + kq.init(pw_wfc_, pw_rho_, q_cart, k_idx); + const int npwk_kq = kq.get_npwk(); + std::vector> gk_out(npwk_kq); + for (int igl = 0; igl < npwk_kq; ++igl) + { + gk_out[igl] = kq.get_gpluskq(igl); + } + std::vector>> vkb_out; + build_vkb(it, ia, gk_out, vkb_out); + + const int nbands = psi.get_nbands(); + dv_psi.assign(nbands, std::vector>(npwk_kq, std::complex(0.0, 0.0))); + + for (int iband = 0; iband < nbands; ++iband) + { + // becp_nu(k) = sum_G' conj(vkb_in[nu][G']) psi(G') + std::vector> becp; + nl_becp(npwk, vkb_in, &psi(k_idx, iband, 0), becp); + // dcbecp = D * becp with D_{mu,nu} = dion(ib_mu, ib_nu) delta_{m_mu, m_nu} + std::vector> dcbecp; + nl_d_contract(ncpp, mu_ib, mu_m, becp, dcbecp); + // term A: i (k+q+G'')_dir * (Vnl |psi>) on the k+q basis + std::vector> term_a(npwk_kq, std::complex(0.0, 0.0)); + for (int igl = 0; igl < npwk_kq; ++igl) + { + term_a[igl] = std::complex(0.0, 1.0) * (ucell_->tpiba * gk_out[igl][dir]) + * nl_sum_projectors(vkb_out, dcbecp, nh, igl); + } + // term B: Vnl [i (k+G')_dir |psi>] + std::vector> becp_dpsi; + nl_becp_dpsi(npwk, dir, ucell_->tpiba, gk_in, vkb_in, &psi(k_idx, iband, 0), becp_dpsi); + std::vector> dcbecp_dpsi; + nl_d_contract(ncpp, mu_ib, mu_m, becp_dpsi, dcbecp_dpsi); + for (int igl = 0; igl < npwk_kq; ++igl) + { + // GS exp(-2pi gk.tau) projector convention: dVnl/dtau_dir + // |psi> = -i (k+q+G'')_dir (Vnl|psi>) + Vnl[i (k+G')_dir |psi>] + dv_psi[iband][igl] = nl_sum_projectors(vkb_out, dcbecp_dpsi, nh, igl) - term_a[igl]; + } + } + ModuleBase::timer::end("DFPT_Pert", "dVnl_dtau"); +} + +void DFPT_Pert::apply_d2vnl(int atom_idx, + int da, + int db, + const ModuleBase::Vector3& q_eff, + const psi::Psi>& psi, + int k_idx, + std::vector>>& d2v_psi) const +{ + ModuleBase::TITLE("DFPT_Pert", "apply_d2vnl"); + ModuleBase::timer::start("DFPT_Pert", "apply_d2vnl"); + int it = 0; + int ia = 0; + atom_index(atom_idx, it, ia); + if (ia < 0) + { + ModuleBase::timer::end("DFPT_Pert", "apply_d2vnl"); + return; + } + const pseudo& ncpp = ucell_->atoms[it].ncpp; + if (ncpp.tvanp || ncpp.has_so) + { + ModuleBase::WARNING_QUIT("DFPT_Pert::apply_d2vnl", + "DFPT second-order nonlocal potential is implemented " + "for normal-conserving separable pseudopotentials only."); + } + const int nh = ncpp.nh; + const int nbands = psi.get_nbands(); + + // projector -> (radial index, m channel) table, matching build_vkb + std::vector mu_ib; + std::vector mu_m; + nl_projector_table(ncpp, nh, mu_ib, mu_m); + + // incoming k basis and outgoing k+q basis projectors (same atom) + const int npwk = pw_wfc_->npwk[k_idx]; + const std::vector> gk_in = nl_gk_list(*pw_wfc_, k_idx, npwk); + std::vector>> vkb_in; + build_vkb(it, ia, gk_in, vkb_in); + DFPT_KQ_Basis kq; + kq.init(pw_wfc_, pw_rho_, q_eff, k_idx); + const int npwk_kq = kq.get_npwk(); + std::vector> gk_out(npwk_kq); + for (int igl = 0; igl < npwk_kq; ++igl) + { + gk_out[igl] = kq.get_gpluskq(igl); + } + std::vector>> vkb_out; + build_vkb(it, ia, gk_out, vkb_out); + + d2v_psi.assign(nbands, std::vector>(npwk_kq, std::complex(0.0, 0.0))); + for (int iband = 0; iband < nbands; ++iband) + { + // becp and its (k+G')-weighted variants: becp_x = sum x(G') |beta>>> becp_x( + 4, std::vector>(nh, std::complex(0.0, 0.0))); + for (int nu = 0; nu < nh; ++nu) + { + for (int ig = 0; ig < npwk; ++ig) + { + const std::complex vc = std::conj(vkb_in[nu][ig]) * psi(k_idx, iband, ig); + const double kp_da = ucell_->tpiba * gk_in[ig][da]; + const double kp_db = ucell_->tpiba * gk_in[ig][db]; + becp_x[0][nu] += vc; + becp_x[1][nu] += kp_da * vc; + becp_x[2][nu] += kp_db * vc; + becp_x[3][nu] += kp_da * kp_db * vc; + } + } + // D contraction with the same-m selection rule as dVnl_dtau + std::vector>> dx; + nl_d_contract_x(ncpp, mu_ib, mu_m, becp_x, dx); + // chi(G'') = sum_mu vkb_out,mu [ -kq_da kq_db d0 - dab + // + kq_da db_ + kq_db da_ ]_mu + // QE ground truth (dynmat_us.f90 + phq_init.f90): the KB second-order + // term pairs gammap (integer-G (k+G)_da(k+G)_db derivative of beta) + // with becp1 = and the same-atom alphap_a* alphap_b + // middle product; everything is built at k with integer-G momentum + // factors, so the caller passes q_eff = 0 and the kernel is + // q-independent for every q. + for (int igl = 0; igl < npwk_kq; ++igl) + { + const double kq_da = ucell_->tpiba * gk_out[igl][da]; + const double kq_db = ucell_->tpiba * gk_out[igl][db]; + std::complex chi(0.0, 0.0); + for (int mu = 0; mu < nh; ++mu) + { + chi += vkb_out[mu][igl] * (-kq_da * kq_db * dx[0][mu] - dx[3][mu]); + chi += vkb_out[mu][igl] * (kq_da * dx[2][mu] + kq_db * dx[1][mu]); + } + d2v_psi[iband][igl] = chi; + } + } + ModuleBase::timer::end("DFPT_Pert", "apply_d2vnl"); +} + +} // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_pert_vkb.cpp b/source/source_pw/module_dfpt/dfpt_pert_vkb.cpp new file mode 100644 index 00000000000..0684dd7bca4 --- /dev/null +++ b/source/source_pw/module_dfpt/dfpt_pert_vkb.cpp @@ -0,0 +1,342 @@ +// KB-projector construction of DFPT_Pert, split out of dfpt_pert.cpp: +// the radial vq integral, the real spherical harmonics (l <= 2) with +// their gradients, and the vkb / dvkb builders on the (k+q) basis. All +// formulas are moved verbatim from the original body; the per-l +// spherical-harmonic channels are factored into file-local helpers. + +#include "dfpt_pert.h" + +#include "source_base/constants.h" +#include "source_base/math_integral.h" +#include "source_base/math_sphbes.h" +#include "source_base/tool_quit.h" +#include "source_cell/atom_pseudo.h" + +#include +#include +#include +#include + +#include "source_base/timer.h" +#include "source_base/tool_title.h" + +namespace ModuleDFPT +{ + +namespace +{ + +/// Y_{1,m} channel of the orthonormal real spherical harmonics (m in [-1, 1]) +double ylm_l1(int m, double nx, double ny, double nz) +{ + switch (m) + { + case -1: + return -0.5 * std::sqrt(3.0 / ModuleBase::PI) * ny; + case 0: + return 0.5 * std::sqrt(3.0 / ModuleBase::PI) * nz; + case 1: + return -0.5 * std::sqrt(3.0 / ModuleBase::PI) * nx; + } + return 0.0; +} + +/// Y_{2,m} channel of the orthonormal real spherical harmonics (m in [-2, 2]) +double ylm_l2(int m, double nx, double ny, double nz) +{ + switch (m) + { + case -2: + return 0.5 * std::sqrt(15.0 / ModuleBase::PI) * nx * ny; + case -1: + return -0.5 * std::sqrt(15.0 / ModuleBase::PI) * nz * ny; + case 0: + return 0.25 * std::sqrt(5.0 / ModuleBase::PI) * (3.0 * nz * nz - 1.0); + case 1: + return -0.5 * std::sqrt(15.0 / ModuleBase::PI) * nz * nx; + case 2: + return 0.25 * std::sqrt(15.0 / ModuleBase::PI) * (nx * nx - ny * ny); + } + return 0.0; +} + +/// gradient of the Y_{1,m} channel (m in [-1, 1]) +void grad_l1(int m, double c1, double* grad) +{ + switch (m) + { + case -1: + grad[1] = -c1; + return; + case 0: + grad[2] = c1; + return; + case 1: + grad[0] = -c1; + return; + } +} + +/// gradient of the Y_{2,m} channel (m in [-2, 2]) +void grad_l2(int m, double c2, double c20, double x, double y, double z, double* grad) +{ + switch (m) + { + case -2: + grad[0] = c2 * y; + grad[1] = c2 * x; + return; + case -1: + grad[1] = -c2 * z; + grad[2] = -c2 * y; + return; + case 0: + grad[2] = 6.0 * c20 * z; + return; + case 1: + grad[0] = -c2 * z; + grad[2] = -c2 * x; + return; + case 2: + grad[0] = 2.0 * c20 * x; + grad[1] = -2.0 * c20 * y; + return; + } +} + +} // namespace + +double DFPT_Pert::radial_vq(int it, int ib, double g) const +{ + ModuleBase::TITLE("DFPT_Pert", "radial_vq"); + ModuleBase::timer::start("DFPT_Pert", "radial_vq"); + const pseudo& ncpp = ucell_->atoms[it].ncpp; + const int l = ncpp.lll[ib]; + int kkbeta = ncpp.kkbeta; + if (kkbeta > 0 && (kkbeta % 2 == 0)) + { + --kkbeta; + } + std::vector jl(kkbeta); + std::vector aux(kkbeta); + ModuleBase::Sphbes::Spherical_Bessel(kkbeta, ncpp.r.data(), g, l, jl.data()); + for (int ir = 0; ir < kkbeta; ++ir) + { + aux[ir] = ncpp.betar(ib, ir) * jl[ir] * ncpp.r[ir]; + } + double v = 0.0; + ModuleBase::Integral::Simpson_Integral(kkbeta, aux.data(), ncpp.rab.data(), v); + // tab convention from vnl_pw.cpp: (4pi/sqrt(Omega)) * integral + ModuleBase::timer::end("DFPT_Pert", "radial_vq"); + return v * ModuleBase::FOUR_PI / std::sqrt(ucell_->omega); +} + +double DFPT_Pert::real_ylm(int l, int m, const ModuleBase::Vector3& ghat) const +{ + ModuleBase::TITLE("DFPT_Pert", "real_ylm"); + ModuleBase::timer::start("DFPT_Pert", "real_ylm"); + // orthonormal real spherical harmonics Y_{l,m} for l <= 2 with the + // standard convention, m in [-l, l]: + // Y_{l,0} = sqrt((2l+1)/4pi) P_l^0(cos0) + // Y_{l,m>0} = sqrt(2 (2l+1)/4pi (l-m)!/(l+m)!) P_l^m(cos0) cos(m phi) + // Y_{l,m<0} = sqrt(2 (2l+1)/4pi (l-|m|)!/(l+|m|)!) P_l^{|m|}(cos0) sin(|m| phi) + // with the associated Legendre convention P_1^1 = -sin0, P_2^1 = -3 sin0 cos0, + // P_2^2 = 3 sin^2 0. The ABACUS GS vkb applies an additional (-1)^|m| phase + // for the m>0 channels; exact GS parity is reconciled in the diamond + // end-to-end test (C7), while the C1 identity test is convention-independent. + const double ghat_zero_tol = 1.0e-12; ///< empirical parameter: |ghat| floor for the zero-direction limit + const double x = ghat.x; + const double y = ghat.y; + const double z = ghat.z; + const double r = std::sqrt(x * x + y * y + z * z); + if (r < ghat_zero_tol) + { + ModuleBase::timer::end("DFPT_Pert", "real_ylm"); + return (l == 0) ? 0.5 * std::sqrt(1.0 / ModuleBase::PI) : 0.0; + } + const double nx = x / r; + const double ny = y / r; + const double nz = z / r; + double ylm = 0.0; + switch (l) + { + case 0: + ylm = 0.5 * std::sqrt(1.0 / ModuleBase::PI); + break; + case 1: + ylm = ylm_l1(m, nx, ny, nz); + break; + case 2: + ylm = ylm_l2(m, nx, ny, nz); + break; + default: + ModuleBase::WARNING_QUIT("DFPT_Pert::real_ylm", "real_ylm implemented for l<=2 only (DFPT NC path)."); + } + ModuleBase::timer::end("DFPT_Pert", "real_ylm"); + return ylm; +} + +void DFPT_Pert::build_vkb(int it, + int ia, + const std::vector>& gk, + std::vector>>& vkb) const +{ + ModuleBase::TITLE("DFPT_Pert", "build_vkb"); + ModuleBase::timer::start("DFPT_Pert", "build_vkb"); + // per-type projector bookkeeping mirrors the ground-state vnl_pw.cpp layout: + // every radial beta (nbeta) with angular momentum l spins out (2l+1) + // projectors with combined index lm = l^2 + m, m in 0..2l (i.e. the real + // harmonic m channels -l..l walked as m' = (-1)^(m+1) ceil... ABACUS ylm + // block: m=0, +1, -1, +2, -2, ...). We use the signed m' directly. + const pseudo& ncpp = ucell_->atoms[it].ncpp; + const int nh = ncpp.nh; + const int ngk = static_cast(gk.size()); + const ModuleBase::Vector3& tau = ucell_->atoms[it].tau[ia]; + vkb.assign(nh, std::vector>(ngk, std::complex(0.0, 0.0))); + if (nh == 0) + { + ModuleBase::timer::end("DFPT_Pert", "build_vkb"); + return; + } + int mu = 0; + for (int ib = 0; ib < ncpp.nbeta; ++ib) + { + const int l = ncpp.lll[ib]; + if (l > 2) + { + ModuleBase::WARNING_QUIT("DFPT_Pert::build_vkb", "DFPT NC projector path implemented for l<=2 only."); + } + const std::complex pref = std::pow(std::complex(0.0, -1.0), l); // (-i)^l + for (int m = 0; m < 2 * l + 1; ++m) + { + // ABACUS real-harmonic walk over the m channels of this radial beta: + // m=0 -> m'=0; m=1 -> m'=+1; m=2 -> m'=-1; m=3 -> m'=+2; m=4 -> m'=-2 + const int mr = (m == 0) ? 0 : ((m % 2 == 1) ? (m + 1) / 2 : -(m / 2)); + for (int ig = 0; ig < ngk; ++ig) + { + const ModuleBase::Vector3& G = gk[ig]; // k(+q)+G, 2*pi/lat0 + const double gnorm = std::sqrt(G * G) * ucell_->tpiba; // bohr^-1 + // real_ylm handles the |G|=0 point itself (Y_00 is + // direction-independent; l>0 channels vanish there together + // with vq), so the raw vector is passed directly. + const double ylm = real_ylm(l, mr, G); + const double vq = radial_vq(it, ib, gnorm); + // GS structure-factor convention (stru_fac.cpp get_sk / + // eigts, ci_tpi = -2pi i): exp(-i 2pi (gk.tau)) + const double arg = -ModuleBase::TWO_PI * (G * tau); + const std::complex phase(std::cos(arg), std::sin(arg)); + vkb[mu][ig] = pref * ylm * vq * phase; + } + ++mu; + } + } + ModuleBase::timer::end("DFPT_Pert", "build_vkb"); +} + +void DFPT_Pert::grad_real_ylm(int l, int m, const ModuleBase::Vector3& ghat, double grad[3]) const +{ + ModuleBase::TITLE("DFPT_Pert", "grad_real_ylm"); + ModuleBase::timer::start("DFPT_Pert", "grad_real_ylm"); + // analytic gradients of the real_ylm polynomials (l <= 2), consistent + // with the conventions documented above real_ylm + const double x = ghat.x; + const double y = ghat.y; + const double z = ghat.z; + const double c1 = 0.5 * std::sqrt(3.0 / ModuleBase::PI); + const double c2 = 0.5 * std::sqrt(15.0 / ModuleBase::PI); + const double c20 = 0.25 * std::sqrt(5.0 / ModuleBase::PI); + grad[0] = grad[1] = grad[2] = 0.0; + switch (l) + { + case 0: + break; + case 1: + grad_l1(m, c1, grad); + break; + case 2: + grad_l2(m, c2, c20, x, y, z, grad); + break; + default: + ModuleBase::WARNING_QUIT("DFPT_Pert::grad_real_ylm", "grad_real_ylm implemented for l<=2 only (DFPT NC path)."); + } + ModuleBase::timer::end("DFPT_Pert", "grad_real_ylm"); +} + +void DFPT_Pert::build_vkb_dk(int it, + int ia, + int dir, + const std::vector>& gk, + std::vector>>& vkb, + std::vector>>& dvkb) const +{ + ModuleBase::TITLE("DFPT_Pert", "build_vkb_dk"); + ModuleBase::timer::start("DFPT_Pert", "build_vkb_dk"); + const pseudo& ncpp = ucell_->atoms[it].ncpp; + const int nh = ncpp.nh; + const int ngk = static_cast(gk.size()); + const ModuleBase::Vector3& tau = ucell_->atoms[it].tau[ia]; + if (static_cast(vkb.size()) != nh || static_cast(vkb[0].size()) != ngk) + { + ModuleBase::WARNING_QUIT("DFPT_Pert::build_vkb_dk", "vkb must be built on the same gk list first."); + } + dvkb.assign(nh, std::vector>(ngk, std::complex(0.0, 0.0))); + if (nh == 0) + { + ModuleBase::timer::end("DFPT_Pert", "build_vkb_dk"); + return; + } + const double dg = 1.0e-4; // bohr^-1, radial central-difference step + const double gmag_zero_tol = 1.0e-10; ///< empirical parameter: |G| floor (2*pi/lat0) for the angular terms + int mu = 0; + for (int ib = 0; ib < ncpp.nbeta; ++ib) + { + const int l = ncpp.lll[ib]; + const std::complex pref = std::pow(std::complex(0.0, -1.0), l); // (-i)^l + for (int m = 0; m < 2 * l + 1; ++m) + { + const int mr = (m == 0) ? 0 : ((m % 2 == 1) ? (m + 1) / 2 : -(m / 2)); + for (int ig = 0; ig < ngk; ++ig) + { + const ModuleBase::Vector3& G = gk[ig]; + const double gmag = std::sqrt(G * G); // 2*pi/lat0 units + const double gnorm = gmag * ucell_->tpiba; // bohr^-1 + const double vq0 = radial_vq(it, ib, gnorm); + const double dvq = (radial_vq(it, ib, gnorm + dg) - radial_vq(it, ib, std::max(0.0, gnorm - dg))) + / (dg * (gnorm > dg ? 2.0 : 1.0)); + const double arg = -ModuleBase::TWO_PI * (G * tau); + const std::complex phase(std::cos(arg), std::sin(arg)); + const std::complex dphase = std::complex(0.0, -ModuleBase::TWO_PI * tau[dir]) * phase; + double dy[3] = {0.0, 0.0, 0.0}; + double ylm = 0.0; + if (gmag > gmag_zero_tol) + { + const ModuleBase::Vector3 ghat = G * (1.0 / gmag); + ylm = real_ylm(l, mr, ghat); + grad_real_ylm(l, mr, ghat, dy); + const double gdir[3] = {ghat.x, ghat.y, ghat.z}; + // chain rule dghat/dk_dir = (e_dir - ghat*ghat_dir)/|G| + double dylm_dir = 0.0; + for (int c = 0; c < 3; ++c) + { + dylm_dir += dy[c] * ((c == dir ? 1.0 : 0.0) - gdir[c] * gdir[dir]); + } + dylm_dir /= gmag; + // radial chain: dg/dk_dir = tpiba * ghat_dir + const double dradial = dvq * ucell_->tpiba * gdir[dir]; + dvkb[mu][ig] = pref * phase * (dylm_dir * vq0 + ylm * dradial) + pref * ylm * vq0 * dphase; + } + else + { + // degenerate |G| = 0: only the l = 0 channel survives + // (real_ylm convention); keep only the phase term + ylm = (l == 0) ? 0.5 * std::sqrt(1.0 / ModuleBase::PI) : 0.0; + dvkb[mu][ig] = pref * ylm * vq0 * dphase; + } + } + ++mu; + } + } + ModuleBase::timer::end("DFPT_Pert", "build_vkb_dk"); +} + +} // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_phon.cpp b/source/source_pw/module_dfpt/dfpt_phon.cpp index 9807307113b..678f5071a5a 100644 --- a/source/source_pw/module_dfpt/dfpt_phon.cpp +++ b/source/source_pw/module_dfpt/dfpt_phon.cpp @@ -1,21 +1,12 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in the design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ +// The Ewald ion-ion part of ion_ion lives in dfpt_phon_ewald.cpp and +// the electronic (2n+1) part of accumulate_electron in +// dfpt_phon_elec.cpp. #include "dfpt_phon.h" -#include "dfpt_kq_basis.h" -#include "dfpt_pert.h" #include "source_base/constants.h" #include "source_base/global_function.h" #include "source_base/module_external/lapack_connector.h" -#include "source_base/tool_quit.h" -#include "source_base/truncated_func.h" -#include "source_basis/module_pw/pw_basis.h" #include #include @@ -25,499 +16,96 @@ #include #include -namespace ModuleDFPT { +#include "source_base/timer.h" +#include "source_base/tool_title.h" -DFPT_Phon::DFPT_Phon() {} +namespace ModuleDFPT +{ -DFPT_Phon::~DFPT_Phon() {} +DFPT_Phon::DFPT_Phon() +{ +} + +DFPT_Phon::~DFPT_Phon() +{ +} -namespace { +namespace +{ // signed frequencies: omega = sgn(e) sqrt(|e|), converted to cm^-1 // sqrt(Ry/(bohr^2 amu)) in cm^-1 = sqrt(RYDBERG_SI/amu_kg)/(bohr*2pi*c) -std::vector signed_freqs_cm1(const std::vector& eigs) { +std::vector signed_freqs_cm1(const std::vector& eigs) +{ + ModuleBase::TITLE("DFPT_Phon", "signed_freqs_cm1"); + ModuleBase::timer::start("DFPT_Phon", "signed_freqs_cm1"); const double amu_kg = 1.0e-3 / ModuleBase::NA; + const double light_speed_cgs = 2.99792458e10; // cm/s, exact SI value const double ry_bohr2_amu_to_cm1 = std::sqrt(ModuleBase::RYDBERG_SI / amu_kg) - / (ModuleBase::BOHR_RADIUS_SI * ModuleBase::TWO_PI - * 2.99792458e10); + / (ModuleBase::BOHR_RADIUS_SI * ModuleBase::TWO_PI * light_speed_cgs); std::vector freq(eigs.size(), 0.0); - for (size_t i = 0; i < eigs.size(); ++i) { - freq[i] = ((eigs[i] >= 0.0) ? 1.0 : -1.0) * std::sqrt(std::abs(eigs[i])) - * ry_bohr2_amu_to_cm1; + for (size_t i = 0; i < eigs.size(); ++i) + { + freq[i] = ((eigs[i] >= 0.0) ? 1.0 : -1.0) * std::sqrt(std::abs(eigs[i])) * ry_bohr2_amu_to_cm1; } + ModuleBase::timer::end("DFPT_Phon", "signed_freqs_cm1"); return freq; } } // namespace -void DFPT_Phon::init(UnitCell& ucell, ModulePW::PW_Basis* pw_rho, DFPT_Pert* pert) { +void DFPT_Phon::init(UnitCell& ucell, ModulePW::PW_Basis* pw_rho, DFPT_Pert* pert) +{ + ModuleBase::TITLE("DFPT_Phon", "init"); + ModuleBase::timer::start("DFPT_Phon", "init"); ucell_ = &ucell; pw_rho_ = pw_rho; pert_ = pert; -} - -// --------------------------------------------------------------------------- -// Ewald ion-ion force constants (C5) -// --------------------------------------------------------------------------- - -void DFPT_Phon::ion_ion(const ModuleBase::Vector3& q_frac, - ModuleBase::ComplexMatrix& dyn) { - const int nat = ucell_->nat; - const int nat3 = 3 * nat; - const double lat0 = ucell_->lat0; - const ModuleBase::Matrix3& latvec = ucell_->latvec; - - // total ionic charge - double charge = 0.0; - for (int it = 0; it < ucell_->ntype; ++it) { - charge += ucell_->atoms[it].na * ucell_->atoms[it].ncpp.zv; - } - - // choose the screening alpha so that the G-sum tail is converged inside - // the rho grid (the erfc envelope bounds the exp(-G^2/4alpha) tail); - // ggecut counts |G_max|^2 in 1/lat0^2 units (pw_basis.h), so the bohr^2 - // cutoff is ggecut * tpiba2 - double alpha = 1.1; - double upperbound = 0.0; - do { - alpha *= 0.9; - if (alpha < 1.0e-4) { - ModuleBase::WARNING_QUIT("DFPT_Phon::ion_ion", - "Can't find optimal Ewald alpha."); - } - upperbound = 2.0 * charge * charge - * std::sqrt(2.0 * alpha / ModuleBase::TWO_PI) - * ModuleBase::truncated_erfc( - std::sqrt(pw_rho_->ggecut * ucell_->tpiba2 / 4.0 / alpha)); - } while (upperbound > 1.0e-6); - ewald_alpha_ = alpha; - // erfc(alpha R) < 1e-16 well inside 6/sqrt(alpha) - ewald_rcut_ = 6.0 / std::sqrt(alpha); - - const ModuleBase::Vector3 q_cart = q_frac * ucell_->G; - - // ---------------- reciprocal-space part ---------------- - // Poisson pair identity (validated against direct sums): - // sum_L h(R) e^{i2pi q.L} = sum_L h_erfc(R) e^{i2pi q.L} - // + (4pi/Omega) sum_{|G+q|>0} (G+q)_a (G+q)_b / |G+q|^2 - // exp(-|G+q|^2/4a) e^{i2pi (G+q).(tau_a-tau_b)} - // so the G part enters D with the + sign while the erfc part carries -. - // The on-site diagonal (both second derivatives act on tau_a in cell 0) - // is phase-free: it is accumulated from Gamma-phase (G-only) pair terms - // as -sqrt(Mb/Ma) times the pair element. sq/s0 accumulate the self-image - // phase difference of the same-atom images (validated element-wise - // against finite differences of the erfc-split Ewald energy in a - // q-commensurate supercell): - // D_ii(q) - D_ii(0) = (Za^2 e2 / Ma) [ sum_{L!=0} h(L)(1 - cos(2pi q.L)) - // + (4pi/Omega)(sq - s0) ], - // where sq sums (G+q)(G+q)/|G+q|^2 exp(-|G+q|^2/4a) over all grid G - // (the G = 0 member contributes through w = q) and s0 the same kernel - // at q = 0. The alpha independence of this combination was verified - // numerically; at q = 0 both differences vanish and the acoustic sum - // rule holds exactly by construction. - double sq[3][3] = {{0.0}}; - double s0[3][3] = {{0.0}}; - for (int ig = 0; ig < pw_rho_->npw; ++ig) { - const ModuleBase::Vector3& gcart = pw_rho_->gcar[ig]; - const ModuleBase::Vector3 w = gcart + q_cart; - const double w2 = w * w; - const double g2 = gcart * gcart; - if (w2 < 1.0e-12) { - // G + q = 0 (only possible at q = 0 with G = 0): excluded, as in - // the q = 0 G part below; its isotropic delta/3 limit belongs to - // the direction-averaged q -> 0 behavior, not the exact q = 0 - // matrix - continue; - } - const double w2_bohr = w2 * ucell_->tpiba2; - const double gauss = ModuleBase::truncated_exp(-w2_bohr / (4.0 * alpha)); - for (int da = 0; da < 3; ++da) { - for (int db = 0; db < 3; ++db) { - sq[da][db] += w[da] * w[db] / w2 * gauss; - } - } - double gauss_g = 0.0; - if (g2 > 1.0e-12) { - gauss_g = ModuleBase::truncated_exp(-g2 * ucell_->tpiba2 / (4.0 * alpha)); - for (int da = 0; da < 3; ++da) { - for (int db = 0; db < 3; ++db) { - s0[da][db] += gcart[da] * gcart[db] / g2 * gauss_g; - } - } - } - for (int ia = 0; ia < nat; ++ia) { - const int ita = ucell_->iat2it[ia]; - const int iia = ucell_->iat2ia[ia]; - const double za = ucell_->atoms[ita].ncpp.zv; - const double ma = ucell_->atoms[ita].mass; - const ModuleBase::Vector3& ta = ucell_->atoms[ita].tau[iia]; - for (int ib = 0; ib < nat; ++ib) { - if (ib == ia) { - continue; - } - const int itb = ucell_->iat2it[ib]; - const int iib = ucell_->iat2ia[ib]; - const double zb = ucell_->atoms[itb].ncpp.zv; - const double mb = ucell_->atoms[itb].mass; - const ModuleBase::Vector3& tb = ucell_->atoms[itb].tau[iib]; - const double arg = ModuleBase::TWO_PI * (w * (ta - tb)); - const std::complex phase(std::cos(arg), std::sin(arg)); - const double pref = ModuleBase::FOUR_PI / ucell_->omega - * za * zb * ModuleBase::e2 * gauss - / (std::sqrt(ma * mb) * w2); - // Gamma-phase on-site piece (G-only kernel, G != 0) - std::complex phase0(1.0, 0.0); - double pref0 = 0.0; - if (g2 > 1.0e-12) { - const double arg0 = ModuleBase::TWO_PI * (gcart * (ta - tb)); - phase0 = std::complex(std::cos(arg0), std::sin(arg0)); - pref0 = ModuleBase::FOUR_PI / ucell_->omega - * za * zb * ModuleBase::e2 * gauss_g - / (std::sqrt(ma * mb) * g2); - } - for (int da = 0; da < 3; ++da) { - for (int db = 0; db < 3; ++db) { - const std::complex elem = pref * w[da] * w[db] * phase; - dyn(3 * ia + da, 3 * ib + db) += elem; - // on-site diagonal: phase-free (Gamma) accumulation, - // Phi_ii = -Phi_ij => -sqrt(Mb/Ma) on the pair term - dyn(3 * ia + da, 3 * ia + db) - -= pref0 * gcart[da] * gcart[db] * phase0 - * std::sqrt(mb / ma); - } - } - } - } - } - // self-image G-space phase difference on the diagonal - for (int ia = 0; ia < nat; ++ia) { - const double za = ucell_->atoms[ucell_->iat2it[ia]].ncpp.zv; - const double ma = ucell_->atoms[ucell_->iat2it[ia]].mass; - const double f2 = za * za * ModuleBase::e2 / ma; - for (int da = 0; da < 3; ++da) { - for (int db = 0; db < 3; ++db) { - dyn(3 * ia + da, 3 * ia + db) - += f2 * ModuleBase::FOUR_PI / ucell_->omega - * (sq[da][db] - s0[da][db]); - } - } - } - - // ---------------- real-space part ---------------- - // h_ab(R) = d^2/dR_a dR_b [ erfc(sqrt(alpha) R) / R ] - // = erfc(sqrt(alpha) R) (3 Ra Rb - delta R^2)/R^5 - // + (2 sqrt(alpha)/sqrt(pi)) e^{-alpha R^2} - // [ 2 alpha Ra Rb/R^2 + 3 Ra Rb/R^4 - delta/R^2 ] - // D^R_ab = -(1/sqrt(MaMb)) ZaZb e2 h(R = tau_b + l - tau_a) e^{i2pi q.l} - // ranges of the lattice-vector shells (rows of latvec are the lattice - // translations in lat0 units) - const double row_e[3][3] = {{latvec.e11, latvec.e12, latvec.e13}, - {latvec.e21, latvec.e22, latvec.e23}, - {latvec.e31, latvec.e32, latvec.e33}}; - int nmax[3] = {0, 0, 0}; - for (int d = 0; d < 3; ++d) { - const ModuleBase::Vector3 a1(row_e[d][0], row_e[d][1], row_e[d][2]); - const double len = std::sqrt(a1 * a1) * lat0; // bohr - nmax[d] = static_cast(std::ceil(ewald_rcut_ / len)) + 1; - } - for (int ia = 0; ia < nat; ++ia) { - const int ita = ucell_->iat2it[ia]; - const int iia = ucell_->iat2ia[ia]; - const double za = ucell_->atoms[ita].ncpp.zv; - const double ma = ucell_->atoms[ita].mass; - for (int ib = 0; ib < nat; ++ib) { - const int itb = ucell_->iat2it[ib]; - const int iib = ucell_->iat2ia[ib]; - const double zb = ucell_->atoms[itb].ncpp.zv; - const double mb = ucell_->atoms[itb].mass; - const ModuleBase::Vector3 dt = - ucell_->atoms[itb].tau[iib] - ucell_->atoms[ita].tau[iia]; - if (ib == ia) { - // self-image phase difference: the on-site i-i energy is - // L-independent while the cross-cell i-i force constants carry - // e^{i2pi q.L}, so D_ii receives - // -(Za^2 e2/Ma) sum_{L!=0} h_erfc(L) (e^{i2pi q.L} - 1); - // the imaginary part cancels over the +-L symmetric sphere - // (h is even) and L = 0 carries e^{i2pi q.0} - 1 = 0 - for (int n1 = -nmax[0]; n1 <= nmax[0]; ++n1) { - for (int n2 = -nmax[1]; n2 <= nmax[1]; ++n2) { - for (int n3 = -nmax[2]; n3 <= nmax[2]; ++n3) { - if (n1 == 0 && n2 == 0 && n3 == 0) { - continue; - } - const ModuleBase::Vector3 lvec( - n1 * latvec.e11 + n2 * latvec.e21 + n3 * latvec.e31, - n1 * latvec.e12 + n2 * latvec.e22 + n3 * latvec.e32, - n1 * latvec.e13 + n2 * latvec.e23 + n3 * latvec.e33); - const ModuleBase::Vector3 r = lvec * lat0; - const double r2 = r * r; - if (r2 > ewald_rcut_ * ewald_rcut_) { - continue; - } - const double rlen = std::sqrt(r2); - const double sar = std::sqrt(alpha); - const double e2a = ModuleBase::truncated_exp(-alpha * r2); - const double f = 2.0 * sar / std::sqrt(ModuleBase::PI) * e2a; - const double er = ModuleBase::truncated_erfc(sar * rlen); - const double ph_arg = ModuleBase::TWO_PI - * (q_frac.x * n1 + q_frac.y * n2 - + q_frac.z * n3); - const double wcos = std::cos(ph_arg) - 1.0; - const double f2 = za * za * ModuleBase::e2 / ma; - for (int da = 0; da < 3; ++da) { - for (int db = 0; db < 3; ++db) { - const double delta = (da == db) ? 1.0 : 0.0; - const double h = er * (3.0 * r[da] * r[db] - delta * r2) - / (rlen * r2 * r2) - + f * (2.0 * alpha * r[da] * r[db] / r2 - + 3.0 * r[da] * r[db] / (r2 * r2) - - delta / r2); - dyn(3 * ia + da, 3 * ia + db) -= f2 * h * wcos; - } - } - } - } - } - continue; - } - for (int n1 = -nmax[0]; n1 <= nmax[0]; ++n1) { - for (int n2 = -nmax[1]; n2 <= nmax[1]; ++n2) { - for (int n3 = -nmax[2]; n3 <= nmax[2]; ++n3) { - const ModuleBase::Vector3 lvec( - n1 * latvec.e11 + n2 * latvec.e21 + n3 * latvec.e31, - n1 * latvec.e12 + n2 * latvec.e22 + n3 * latvec.e32, - n1 * latvec.e13 + n2 * latvec.e23 + n3 * latvec.e33); - ModuleBase::Vector3 r = (lvec + dt) * lat0; // bohr - const double r2 = r * r; - if (r2 > ewald_rcut_ * ewald_rcut_) { - continue; - } - const double rlen = std::sqrt(r2); - const double r3 = r2 * rlen; - const double sar = std::sqrt(alpha); - const double e2a = ModuleBase::truncated_exp(-alpha * r2); - const double f = 2.0 * sar / std::sqrt(ModuleBase::PI) * e2a; - const double er = ModuleBase::truncated_erfc(sar * rlen); - const double ph_arg = ModuleBase::TWO_PI - * (q_frac.x * n1 + q_frac.y * n2 + q_frac.z * n3); - const std::complex phase(std::cos(ph_arg), std::sin(ph_arg)); - const double zab2 = za * zb * ModuleBase::e2 / std::sqrt(ma * mb); - for (int da = 0; da < 3; ++da) { - for (int db = 0; db < 3; ++db) { - const double delta = (da == db) ? 1.0 : 0.0; - // d^2/dR_a dR_b [erfc(sqrt(alpha) R)/R], - // validated against central finite differences - const double h = er * (3.0 * r[da] * r[db] - delta * r2) / (r3 * r2) - + f * (2.0 * alpha * r[da] * r[db] / r2 - + 3.0 * r[da] * r[db] / (r2 * r2) - - delta / r2); - dyn(3 * ia + da, 3 * ib + db) -= zab2 * h * phase; - // on-site diagonal Phi_ii^R = sum_{j != i} - // Z_iZ_j sum_L h(r_ij + L): phase-free (both - // derivatives act on tau_a in cell 0), i.e. - // -sqrt(Mb/Ma) times the pair term - dyn(3 * ia + da, 3 * ia + db) - += zab2 * std::sqrt(mb / ma) * h; - } - } - } - } - } - } - } - - // The Gaussian self constant -Z^2 sqrt(2 alpha/pi) and the h_erf contact - // -4 alpha^{3/2}/(3 sqrt(pi)) delta_ab are tau-independent and cancel in - // the (e^{i2pi q.L} - 1) differences; the diagonal is carried by the - // phase-free cross-atom accumulation plus the self-image phase terms - // (both G and R pieces above). At q = 0 all phase differences vanish and - // the acoustic sum rule holds exactly by construction. -} - -// --------------------------------------------------------------------------- -// electronic contribution (2n+1 theorem) -// --------------------------------------------------------------------------- - -void DFPT_Phon::accumulate_electron(int q_idx, int atom_idx, int dir, - const psi::Psi>& psi, - const ModuleBase::matrix& wg, DFPT_PW_Data& data) { - if (pert_ == nullptr || pw_rho_ == nullptr || ucell_ == nullptr) { - return; - } - const int nat = ucell_->nat; - const int nat3 = 3 * nat; - if (accum_q_ != q_idx || dynmat_accum_.nr != nat3) { - dynmat_accum_ = ModuleBase::ComplexMatrix(nat3, nat3, true); - accum_q_ = q_idx; - } - const int rowb = 3 * atom_idx + dir; - const int nk = psi.get_nk(); - const int nbands = psi.get_nbands(); - - // stash the converged dpsi of this displacement (apply_dv reuses the slot): - // prefer the per-displacement store of the two-pass flow; fall back to - // the working slots for the legacy interleaved call order - std::vector>>> dpsib - = data.get_dpsi_disp(atom_idx, dir); - if (dpsib.empty() || static_cast(dpsib.size()) < nk - || (nk > 0 && static_cast(dpsib[0].size()) < nbands)) { - dpsib.assign(nk, std::vector>>(nbands)); - for (int ik = 0; ik < nk; ++ik) { - for (int ib = 0; ib < nbands; ++ib) { - dpsib[ik][ib] = data.get_dpsi(q_idx, ik, ib); - } - } - } - - for (int iat = 0; iat < nat; ++iat) { - for (int idir = 0; idir < 3; ++idir) { - const int cola = 3 * iat + idir; - // ---- term 2 over all k,n ---- - // Hermitian (2n+1) accumulation: the row element gets X_ba and - // the transposed element gets conj(X_ba); the self-consistent - // response of dpsi^b already contains the screening, and the - // Hartree-xc kernel quadratic term cancels the - // cross terms by the variational identity, so only the bare - // external perturbation appears here - pert_->build_dv(q_idx, iat, idir, data); - std::complex cross(0.0, 0.0); - for (int ik = 0; ik < nk; ++ik) { - pert_->apply_dv(q_idx, ik, psi, data); - for (int ib = 0; ib < nbands; ++ib) { - if (!dfpt_band_occupied(wg, ik, ib)) { - continue; - } - const std::vector> rhs = data.get_dpsi(q_idx, ik, ib); - const std::vector>& sol = dpsib[ik][ib]; - if (rhs.size() != sol.size() || sol.empty()) { - continue; - } - std::complex dot(0.0, 0.0); - for (size_t i = 0; i < sol.size(); ++i) { - dot += std::conj(sol[i]) * rhs[i]; - } - cross += wg(ik, ib) * dot; - } - } - const double mass_norm - = std::sqrt(ucell_->atoms[ucell_->iat2it[atom_idx]].mass - * ucell_->atoms[ucell_->iat2it[iat]].mass); - dynmat_accum_(rowb, cola) += cross / mass_norm; - dynmat_accum_(cola, rowb) += std::conj(cross) / mass_norm; - - // ---- same-atom anharmonic term ---- - // QE ground truth (dynmat_us.f90 + phq_init.f90): the mixed - // (+q, -q) second-order potential of the local part is - // -Omega tpiba^2 G_a G_b vloc(|G|) Re[rho(G) e^{-iG tau_s}] - // (integer G, no q), and the KB nonlocal part is the same-atom - // block deff[gammap*becp1 + becp1*gammap + alphap_a*alphap_b + - // alphap_b*alphap_a] with becp1/alphap/gammap all built from - // vkb_k and (k+G) factors (integer G, no q). The (+q,-q) - // dressings collapse to an integer-G carrier for every q, so - // this term is q-independent and must never be gated on 2q - // commensurability (the old gate silently dropped it for - // 2q not reciprocal, e.g. q=(0.25,0,0), and produced - // imaginary phonon branches). - const ModuleBase::Vector3 q_eff_cart(0.0, 0.0, 0.0); - // the same-atom d2 middle term is always included (its - // q-independence is established QE ground truth; the old - // 2q-commensurability gate and the D2MID A/B knob are gone) - const bool include_middle = true; - if (iat == atom_idx && cola >= rowb) { - std::vector> dv2_r; - pert_->d2vloc_r(atom_idx, idir, dir, dv2_r); - if (static_cast(dv2_r.size()) != pw_rho_->nrxx) { - dv2_r.assign(pw_rho_->nrxx, std::complex(0.0, 0.0)); - } - std::vector>> chi; - std::complex d2sum(0.0, 0.0); - std::vector> u_r(pw_rho_->nrxx); - std::vector> x_r(pw_rho_->nrxx); - std::vector> x_recip(pw_rho_->npw, std::complex(0.0, 0.0)); - for (int ik = 0; ik < nk; ++ik) { - pert_->apply_d2vnl(atom_idx, idir, dir, q_eff_cart, include_middle, psi, ik, chi); - // k+q_eff scatter map for this k (must match apply_d2vnl) - DFPT_KQ_Basis kq; - kq.init(pert_->get_pw_wfc(), pert_->get_pw_rho(), q_eff_cart, ik); - const int npwk_kq = kq.get_npwk(); - for (int ib = 0; ib < nbands; ++ib) { - if (!dfpt_band_occupied(wg, ik, ib)) { - continue; - } - pert_->get_pw_wfc()->recip2real(&psi(ik, ib, 0), u_r.data(), ik); - if (static_cast(chi.size()) == nbands - && static_cast(chi[ib].size()) == npwk_kq) { - std::fill(x_recip.begin(), x_recip.end(), std::complex(0.0, 0.0)); - for (int igl = 0; igl < npwk_kq; ++igl) { - const int ig_rho = kq.get_ig_rho(igl); - if (ig_rho >= 0) { - x_recip[ig_rho] = chi[ib][igl]; - } - } - pw_rho_->recip2real(x_recip.data(), x_r.data()); - } - else { - std::fill(x_r.begin(), x_r.end(), std::complex(0.0, 0.0)); - } - std::complex expect(0.0, 0.0); - for (int ir = 0; ir < pw_rho_->nrxx; ++ir) { - expect += std::conj(u_r[ir]) * u_r[ir] * dv2_r[ir] - + std::conj(u_r[ir]) * x_r[ir]; - } - d2sum += wg(ik, ib) * expect / static_cast(pw_rho_->nxyz); - } - } - const double inv_m - = 1.0 / ucell_->atoms[ucell_->iat2it[atom_idx]].mass; - dynmat_accum_(rowb, cola) += d2sum * inv_m; - if (cola != rowb) { - dynmat_accum_(cola, rowb) += std::conj(d2sum) * inv_m; - } - } - } - } - - // restore the converged dpsi of this displacement - for (int ik = 0; ik < nk; ++ik) { - for (int ib = 0; ib < nbands; ++ib) { - if (!dpsib[ik][ib].empty()) { - data.set_dpsi(q_idx, ik, ib, dpsib[ik][ib]); - } - } - } + ModuleBase::timer::end("DFPT_Phon", "init"); } // --------------------------------------------------------------------------- // assemble / diagonalize / LO-TO / sum rule // --------------------------------------------------------------------------- -void DFPT_Phon::assemble(int q_idx, DFPT_PW_Data& data) { - if (ucell_ == nullptr) { +void DFPT_Phon::assemble(int q_idx, DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Phon", "assemble"); + ModuleBase::timer::start("DFPT_Phon", "assemble"); + if (ucell_ == nullptr) + { + ModuleBase::timer::end("DFPT_Phon", "assemble"); return; } const int nat = ucell_->nat; const int nat3 = 3 * nat; ModuleBase::ComplexMatrix dyn(nat3, nat3, true); - if (pw_rho_ != nullptr) { + if (pw_rho_ != nullptr) + { ion_ion(data.get_qvec(q_idx), dyn); } - if (accum_q_ == q_idx && dynmat_accum_.nr == nat3) { - for (int i = 0; i < nat3; ++i) { - for (int j = 0; j < nat3; ++j) { + if (accum_q_ == q_idx && dynmat_accum_.nr == nat3) + { + for (int i = 0; i < nat3; ++i) + { + for (int j = 0; j < nat3; ++j) + { dyn(i, j) += dynmat_accum_(i, j); } } } // DFT+U dynamical-matrix term (U0 reservation, implemented with the C7/U1 // Plus_U wiring): sum_nk w_nk [ + frozen second-order term]. - if (data.with_u()) { + if (data.with_u()) + { dftu_onsite(q_idx, data); } // Hermitian symmetrization (rows filled by independent solves) - for (int i = 0; i < nat3; ++i) { - for (int j = i + 1; j < nat3; ++j) { - const std::complex avg - = 0.5 * (dyn(i, j) + std::conj(dyn(j, i))); + for (int i = 0; i < nat3; ++i) + { + for (int j = i + 1; j < nat3; ++j) + { + const std::complex avg = 0.5 * (dyn(i, j) + std::conj(dyn(j, i))); dyn(i, j) = avg; dyn(j, i) = std::conj(avg); } @@ -525,13 +113,19 @@ void DFPT_Phon::assemble(int q_idx, DFPT_PW_Data& data) { data.set_dynmat(q_idx, dyn); dynmat_accum_ = ModuleBase::ComplexMatrix(); accum_q_ = -1; + ModuleBase::timer::end("DFPT_Phon", "assemble"); } -void DFPT_Phon::diagonalize(int q_idx, DFPT_PW_Data& data) { +void DFPT_Phon::diagonalize(int q_idx, DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Phon", "diagonalize"); + ModuleBase::timer::start("DFPT_Phon", "diagonalize"); const int nat = ucell_->nat; const int nat3 = 3 * nat; ModuleBase::ComplexMatrix dyn = data.get_dynmat(q_idx); - if (dyn.nr != nat3) { + if (dyn.nr != nat3) + { + ModuleBase::timer::end("DFPT_Phon", "diagonalize"); return; } @@ -540,57 +134,81 @@ void DFPT_Phon::diagonalize(int q_idx, DFPT_PW_Data& data) { std::vector rwork(std::max(1, 3 * nat3 - 2), 0.0); std::vector> work(1); int info = 0; - LapackConnector::zheev('N', 'U', nat3, dyn, nat3, w.data(), work.data(), -1, - rwork.data(), &info); + LapackConnector::zheev('N', 'U', nat3, dyn, nat3, w.data(), work.data(), -1, rwork.data(), &info); work.resize(std::max(1, static_cast(work[0].real()))); - LapackConnector::zheev('N', 'U', nat3, dyn, nat3, w.data(), work.data(), - static_cast(work.size()), rwork.data(), &info); + LapackConnector::zheev('N', + 'U', + nat3, + dyn, + nat3, + w.data(), + work.data(), + static_cast(work.size()), + rwork.data(), + &info); // signed frequencies: omega = sgn(e) sqrt(|e|), converted to cm^-1 // sqrt(Ry/(bohr^2 amu)) in cm^-1 = sqrt(RYDBERG_SI/amu_kg)/(bohr*2pi*c) const double amu_kg = 1.0e-3 / ModuleBase::NA; + const double light_speed_cgs = 2.99792458e10; // cm/s, exact SI value const double ry_bohr2_amu_to_cm1 = std::sqrt(ModuleBase::RYDBERG_SI / amu_kg) - / (ModuleBase::BOHR_RADIUS_SI * ModuleBase::TWO_PI - * 2.99792458e10); + / (ModuleBase::BOHR_RADIUS_SI * ModuleBase::TWO_PI * light_speed_cgs); std::vector freq(nat3, 0.0); - for (int i = 0; i < nat3; ++i) { + for (int i = 0; i < nat3; ++i) + { const double e = w[i]; freq[i] = ((e >= 0.0) ? 1.0 : -1.0) * std::sqrt(std::abs(e)) * ry_bohr2_amu_to_cm1; } data.set_phon_freq(q_idx, freq); + ModuleBase::timer::end("DFPT_Phon", "diagonalize"); } -void DFPT_Phon::add_loto(const ModuleBase::Vector3& qhat, DFPT_PW_Data& data) { +void DFPT_Phon::add_loto(const ModuleBase::Vector3& qhat, DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Phon", "add_loto"); + ModuleBase::timer::start("DFPT_Phon", "add_loto"); const int nat = ucell_->nat; const int nat3 = 3 * nat; ModuleBase::ComplexMatrix dyn = data.get_dynmat(0); - if (dyn.nr != nat3) { + const double qeq_tol = 1.0e-10; ///< empirical parameter: |q eps q| floor for the non-polar-direction skip + if (dyn.nr != nat3) + { + ModuleBase::timer::end("DFPT_Phon", "add_loto"); return; } const ModuleBase::matrix eps = data.get_dielectric(); - if (eps.nr != 3 || eps.nc != 3) { + if (eps.nr != 3 || eps.nc != 3) + { + ModuleBase::timer::end("DFPT_Phon", "add_loto"); return; // no dielectric tensor stored yet (C6 not run) } const double qeq = qhat.x * (qhat.x * eps(0, 0) + qhat.y * eps(1, 0) + qhat.z * eps(2, 0)) + qhat.y * (qhat.x * eps(0, 1) + qhat.y * eps(1, 1) + qhat.z * eps(2, 1)) + qhat.z * (qhat.x * eps(0, 2) + qhat.y * eps(1, 2) + qhat.z * eps(2, 2)); - if (std::abs(qeq) < 1.0e-10) { + if (std::abs(qeq) < qeq_tol) + { + ModuleBase::timer::end("DFPT_Phon", "add_loto"); return; } const double pref = ModuleBase::FOUR_PI * ModuleBase::e2 / ucell_->omega / qeq; - for (int ia = 0; ia < nat; ++ia) { + for (int ia = 0; ia < nat; ++ia) + { const double ma = ucell_->atoms[ucell_->iat2it[ia]].mass; const ModuleBase::matrix za = data.get_born(ia); - if (za.nr != 3 || za.nc != 3) { + if (za.nr != 3 || za.nc != 3) + { continue; } - for (int ib = 0; ib < nat; ++ib) { + for (int ib = 0; ib < nat; ++ib) + { const double mb = ucell_->atoms[ucell_->iat2it[ib]].mass; const ModuleBase::matrix zb = data.get_born(ib); - for (int da = 0; da < 3; ++da) { + for (int da = 0; da < 3; ++da) + { // (qhat Z*_a)_da = sum_gamma qhat_gamma Z_a(da,gamma) const double qza = qhat.x * za(da, 0) + qhat.y * za(da, 1) + qhat.z * za(da, 2); - for (int db = 0; db < 3; ++db) { + for (int db = 0; db < 3; ++db) + { const double qzb = qhat.x * zb(db, 0) + qhat.y * zb(db, 1) + qhat.z * zb(db, 2); dyn(3 * ia + da, 3 * ib + db) += pref * qza * qzb / std::sqrt(ma * mb); } @@ -598,95 +216,138 @@ void DFPT_Phon::add_loto(const ModuleBase::Vector3& qhat, DFPT_PW_Data& } } data.set_dynmat(0, dyn); + ModuleBase::timer::end("DFPT_Phon", "add_loto"); } -void DFPT_Phon::diagonalize_loto(DFPT_PW_Data& data) { +void DFPT_Phon::diagonalize_loto(DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Phon", "diagonalize_loto"); + ModuleBase::timer::start("DFPT_Phon", "diagonalize_loto"); const int nat3 = 3 * ucell_->nat; // the stored Gamma matrix already carries the non-analytic term added // by add_loto; the copy below is destroyed by the solver, the stored // one stays available for the plain report ModuleBase::ComplexMatrix dyn = data.get_dynmat(0); - if (dyn.nr != nat3) { + if (dyn.nr != nat3) + { + ModuleBase::timer::end("DFPT_Phon", "diagonalize_loto"); return; } std::vector w(nat3, 0.0); std::vector rwork(std::max(1, 3 * nat3 - 2), 0.0); std::vector> work(1); int info = 0; - LapackConnector::zheev('N', 'U', nat3, dyn, nat3, w.data(), work.data(), -1, - rwork.data(), &info); + LapackConnector::zheev('N', 'U', nat3, dyn, nat3, w.data(), work.data(), -1, rwork.data(), &info); work.resize(std::max(1, static_cast(work[0].real()))); - LapackConnector::zheev('N', 'U', nat3, dyn, nat3, w.data(), work.data(), - static_cast(work.size()), rwork.data(), &info); + LapackConnector::zheev('N', + 'U', + nat3, + dyn, + nat3, + w.data(), + work.data(), + static_cast(work.size()), + rwork.data(), + &info); data.set_phon_freq_loto(signed_freqs_cm1(w)); + ModuleBase::timer::end("DFPT_Phon", "diagonalize_loto"); } -std::string DFPT_Phon::format_q_report(int q_idx, const DFPT_PW_Data& data) const { +std::string DFPT_Phon::format_q_report(int q_idx, const DFPT_PW_Data& data) const +{ + ModuleBase::TITLE("DFPT_Phon", "format_q_report"); + ModuleBase::timer::start("DFPT_Phon", "format_q_report"); const ModuleBase::Vector3 qd = data.get_qvec(q_idx); const std::vector freq = data.get_phon_freq(q_idx); std::ostringstream os; - os << " DFPT phonon frequencies at q #" << q_idx << " = (" - << std::fixed << std::setprecision(6) - << qd.x << " " << qd.y << " " << qd.z - << ") (direct) in cm^-1:" << "\n"; - for (size_t im = 0; im < freq.size(); ++im) { - os << " mode " << std::setw(3) << im << " : " - << std::fixed << std::setprecision(6) << freq[im] << " cm^-1" << "\n"; + os << " DFPT phonon frequencies at q #" << q_idx << " = (" << std::fixed << std::setprecision(6) << qd.x << " " + << qd.y << " " << qd.z << ") (direct) in cm^-1:" << "\n"; + for (size_t im = 0; im < freq.size(); ++im) + { + os << " mode " << std::setw(3) << im << " : " << std::fixed << std::setprecision(6) << freq[im] << " cm^-1" + << "\n"; } + ModuleBase::timer::end("DFPT_Phon", "format_q_report"); return os.str(); } -std::string DFPT_Phon::format_loto_report(const DFPT_PW_Data& data) const { +std::string DFPT_Phon::format_loto_report(const DFPT_PW_Data& data) const +{ + ModuleBase::TITLE("DFPT_Phon", "format_loto_report"); + ModuleBase::timer::start("DFPT_Phon", "format_loto_report"); const std::vector freq = data.get_phon_freq_loto(); - if (freq.empty()) { + if (freq.empty()) + { + ModuleBase::timer::end("DFPT_Phon", "format_loto_report"); return std::string(); } const ModuleBase::Vector3 dir = data.get_loto_dir(); std::ostringstream os; - os << " DFPT LO-TO corrected frequencies at q #0 along q->0 direction (" - << std::fixed << std::setprecision(6) - << dir.x << " " << dir.y << " " << dir.z - << ") in cm^-1:" << "\n"; - for (size_t im = 0; im < freq.size(); ++im) { - os << " mode " << std::setw(3) << im << " : " - << std::fixed << std::setprecision(6) << freq[im] << " cm^-1" << "\n"; + os << " DFPT LO-TO corrected frequencies at q #0 along q->0 direction (" << std::fixed << std::setprecision(6) + << dir.x << " " << dir.y << " " << dir.z << ") in cm^-1:" << "\n"; + for (size_t im = 0; im < freq.size(); ++im) + { + os << " mode " << std::setw(3) << im << " : " << std::fixed << std::setprecision(6) << freq[im] << " cm^-1" + << "\n"; } + ModuleBase::timer::end("DFPT_Phon", "format_loto_report"); return os.str(); } -bool DFPT_Phon::check_sum_rule(int q_idx, DFPT_PW_Data& data) const { +bool DFPT_Phon::check_sum_rule(int q_idx, DFPT_PW_Data& data) const +{ + ModuleBase::TITLE("DFPT_Phon", "check_sum_rule"); + ModuleBase::timer::start("DFPT_Phon", "check_sum_rule"); + const double gamma_tol = 1.0e-8; ///< empirical parameter: fractional-q Gamma tolerance + const double dyn_zero_floor = 1.0e-12; ///< empirical parameter: dynamical matrix treated as zero + const double asr_rel_tol = 1.0e-6; ///< empirical parameter: tolerated relative column-sum error const ModuleBase::Vector3 q_frac = data.get_qvec(q_idx); - if (std::abs(q_frac.x) > 1.0e-8 || std::abs(q_frac.y) > 1.0e-8 - || std::abs(q_frac.z) > 1.0e-8) { + if (std::abs(q_frac.x) > gamma_tol || std::abs(q_frac.y) > gamma_tol || std::abs(q_frac.z) > gamma_tol) + { + ModuleBase::timer::end("DFPT_Phon", "check_sum_rule"); return true; // only applies at Gamma } const int nat3 = 3 * ucell_->nat; ModuleBase::ComplexMatrix dyn = data.get_dynmat(q_idx); - if (dyn.nr != nat3) { + if (dyn.nr != nat3) + { + ModuleBase::timer::end("DFPT_Phon", "check_sum_rule"); return false; } double max_elem = 0.0; - for (int i = 0; i < nat3; ++i) { - for (int j = 0; j < nat3; ++j) { + for (int i = 0; i < nat3; ++i) + { + for (int j = 0; j < nat3; ++j) + { max_elem = std::max(max_elem, std::abs(dyn(i, j))); } } - if (max_elem < 1.0e-12) { + if (max_elem < dyn_zero_floor) + { + ModuleBase::timer::end("DFPT_Phon", "check_sum_rule"); return true; } - for (int i = 0; i < nat3; ++i) { + for (int i = 0; i < nat3; ++i) + { std::complex colsum(0.0, 0.0); - for (int j = 0; j < nat3; ++j) { + for (int j = 0; j < nat3; ++j) + { colsum += dyn(i, j); } - if (std::abs(colsum) > 1.0e-6 * max_elem) { + if (std::abs(colsum) > asr_rel_tol * max_elem) + { + ModuleBase::timer::end("DFPT_Phon", "check_sum_rule"); return false; } } + ModuleBase::timer::end("DFPT_Phon", "check_sum_rule"); return true; } -void DFPT_Phon::dftu_onsite(int q_idx, DFPT_PW_Data& data) { +void DFPT_Phon::dftu_onsite(int q_idx, DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Phon", "dftu_onsite"); + ModuleBase::timer::start("DFPT_Phon", "dftu_onsite"); // Reserved DFT+U contribution to the dynamical matrix (U0). // The physical implementation lands with the Plus_U production wiring: // sum_nk w_nk [ + frozen second-order U term diff --git a/source/source_pw/module_dfpt/dfpt_phon.h b/source/source_pw/module_dfpt/dfpt_phon.h index 25c99a355e2..9e8c18cdceb 100644 --- a/source/source_pw/module_dfpt/dfpt_phon.h +++ b/source/source_pw/module_dfpt/dfpt_phon.h @@ -1,11 +1,3 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in the design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #ifndef DFPT_PHON_H #define DFPT_PHON_H @@ -15,11 +7,13 @@ #include -namespace ModulePW { +namespace ModulePW +{ class PW_Basis; } -namespace ModuleDFPT { +namespace ModuleDFPT +{ class DFPT_Pert; @@ -42,23 +36,27 @@ class DFPT_Pert; * storage never needs a direction dimension (data-layer refactor reserved * for phase B). */ -class DFPT_Phon { -public: +class DFPT_Phon +{ + public: DFPT_Phon(); ~DFPT_Phon(); - + void init(UnitCell& ucell, ModulePW::PW_Basis* pw_rho, DFPT_Pert* pert); - + void assemble(int q_idx, DFPT_PW_Data& data); /// Fill the D[b][*] row of the electronic dynamical-matrix contribution /// for the converged displacement (atom_idx, dir); psi/wg are the /// ground-state wavefunctions and occupations. Requires a wired /// DFPT_Pert (init); a null pert leaves the row untouched. - void accumulate_electron(int q_idx, int atom_idx, int dir, + void accumulate_electron(int q_idx, + int atom_idx, + int dir, const psi::Psi>& psi, - const ModuleBase::matrix& wg, DFPT_PW_Data& data); - + const ModuleBase::matrix& wg, + DFPT_PW_Data& data); + void diagonalize(int q_idx, DFPT_PW_Data& data); /// Diagonalize the LO-TO corrected Gamma dynamical matrix (after @@ -77,30 +75,50 @@ class DFPT_Phon { /// data.loto_dir(); returns an empty string unless the corrected /// frequencies have been computed (add_loto + diagonalize_loto). std::string format_loto_report(const DFPT_PW_Data& data) const; - + /// Non-analytic (LO-TO) term along the q->0 direction qhat (unit vector, /// Cartesian): D_NAC = (4 pi e^2/Omega) (qhat Z*_a)(qhat Z*_b) / /// (qhat eps_inf qhat) / sqrt(M_a M_b). Uses the dielectric tensor and /// Born charges stored in data (set by DFPT_Q0, C6). void add_loto(const ModuleBase::Vector3& qhat, DFPT_PW_Data& data); - + /// Acoustic sum rule check at q=Gamma: max_a |sum_b D_ab| relative to /// the largest matrix element; returns true when below 1e-6 (or away /// from Gamma, where the rule does not apply). bool check_sum_rule(int q_idx, DFPT_PW_Data& data) const; -private: + /// Ewald ion-ion force constants C^ewald_ab(q) (G-space + real-space + + /// Gaussian self term), mass-reduced by 1/sqrt(M_a M_b). A stateless + /// building block exposed publicly so the serial analytic tests can + /// validate it directly (no internal state is touched). + void ion_ion(const ModuleBase::Vector3& q_frac, ModuleBase::ComplexMatrix& dyn); + + /// read-only view of the accumulated electronic dynamical-matrix rows + /// (see dynmat_accum_); consumed by the serial analytic tests. + const ModuleBase::ComplexMatrix& dynmat_accum() const + { + return dynmat_accum_; + } + + private: UnitCell* ucell_ = nullptr; ModulePW::PW_Basis* pw_rho_ = nullptr; DFPT_Pert* pert_ = nullptr; - + double ewald_alpha_ = 0.0; double ewald_rcut_ = 0.0; - - /// Ewald ion-ion force constants C^ewald_ab(q) (G-space + real-space + - /// Gaussian self term), mass-reduced by 1/sqrt(M_a M_b). - void ion_ion(const ModuleBase::Vector3& q_frac, ModuleBase::ComplexMatrix& dyn); - + + /// same-atom anharmonic term accumulated into + /// the (rowb, cola) entries of dynmat_accum_ (upper triangle only; the + /// Hermitian partner is added here from conj(d2sum)) + void accum_d2_same_atom(int atom_idx, + int dir, + int iat, + int idir, + int rowb, + const psi::Psi>& psi, + const ModuleBase::matrix& wg); + /// DFT+U contribution to the dynamical matrix (U0 reservation). void dftu_onsite(int q_idx, DFPT_PW_Data& data); diff --git a/source/source_pw/module_dfpt/dfpt_phon_elec.cpp b/source/source_pw/module_dfpt/dfpt_phon_elec.cpp new file mode 100644 index 00000000000..2cbef586ec7 --- /dev/null +++ b/source/source_pw/module_dfpt/dfpt_phon_elec.cpp @@ -0,0 +1,265 @@ +// Electronic (2n+1) contribution of DFPT_Phon::accumulate_electron, +// split out of dfpt_phon.cpp: the converged-dpsi stash/restore, the +// bare-potential cross sum and the same-atom second-order term. All +// formulas are moved verbatim from the original body. + +#include "dfpt_phon.h" + +#include "dfpt_kq_basis.h" +#include "dfpt_pert.h" +#include "dfpt_pw_data.h" + +#include +#include +#include +#include + +#include "source_base/timer.h" +#include "source_base/tool_title.h" + +namespace ModuleDFPT +{ + +namespace +{ + +/// dpsi layout of one displacement: [k][band][G] +using DpsiDisp = std::vector>>>; + +/// stash the converged dpsi of one displacement: prefer the per-displacement +/// store of the two-pass flow; fall back to the working slots for the legacy +/// interleaved call order +DpsiDisp stash_dpsi(DFPT_PW_Data& data, int q_idx, int atom_idx, int dir, int nk, int nbands) +{ + DpsiDisp dpsib = data.get_dpsi_disp(atom_idx, dir); + if (dpsib.empty() || static_cast(dpsib.size()) < nk + || (nk > 0 && static_cast(dpsib[0].size()) < nbands)) + { + dpsib.assign(nk, std::vector>>(nbands)); + for (int ik = 0; ik < nk; ++ik) + { + for (int ib = 0; ib < nbands; ++ib) + { + dpsib[ik][ib] = data.get_dpsi(q_idx, ik, ib); + } + } + } + return dpsib; +} + +/// restore the stashed converged dpsi after the accumulation touched the +/// working slots (apply_dv reuses the slot of every k/band) +void restore_dpsi(DFPT_PW_Data& data, int q_idx, int nk, int nbands, const DpsiDisp& dpsib) +{ + for (int ik = 0; ik < nk; ++ik) + { + for (int ib = 0; ib < nbands; ++ib) + { + if (!dpsib[ik][ib].empty()) + { + data.set_dpsi(q_idx, ik, ib, dpsib[ik][ib]); + } + } + } +} + +/// bare-potential cross sum X_ba = sum_kn wg ; +/// dV^a_ext must have been built (build_dv) before this call +std::complex cross_sum(DFPT_Pert& pert, + DFPT_PW_Data& data, + int q_idx, + const DpsiDisp& dpsib, + const psi::Psi>& psi, + const ModuleBase::matrix& wg) +{ + const int nk = psi.get_nk(); + const int nbands = psi.get_nbands(); + std::complex cross(0.0, 0.0); + for (int ik = 0; ik < nk; ++ik) + { + pert.apply_dv(q_idx, ik, psi, data); + for (int ib = 0; ib < nbands; ++ib) + { + if (!dfpt_band_occupied(wg, ik, ib)) + { + continue; + } + const std::vector> rhs = data.get_dpsi(q_idx, ik, ib); + const std::vector>& sol = dpsib[ik][ib]; + if (rhs.size() != sol.size() || sol.empty()) + { + continue; + } + std::complex dot(0.0, 0.0); + for (size_t i = 0; i < sol.size(); ++i) + { + dot += std::conj(sol[i]) * rhs[i]; + } + cross += wg(ik, ib) * dot; + } + } + return cross; +} + +/// scatter the band-ib q-carrier chi from the k+q_eff G-shell onto the rho +/// grid and transform to real space; a carrier of the wrong shape (chi not +/// stored for this k/band) leaves x_r zero, matching apply_d2vnl +void scatter_chi_to_r(const ModulePW::PW_Basis& pw_rho, + const std::vector>>& chi, + int nbands, + int ib, + int npwk_kq, + const DFPT_KQ_Basis& kq, + std::vector>& x_r) +{ + std::vector> x_recip(pw_rho.npw, std::complex(0.0, 0.0)); + if (static_cast(chi.size()) == nbands && static_cast(chi[ib].size()) == npwk_kq) + { + for (int igl = 0; igl < npwk_kq; ++igl) + { + const int ig_rho = kq.get_ig_rho(igl); + if (ig_rho >= 0) + { + x_recip[ig_rho] = chi[ib][igl]; + } + } + pw_rho.recip2real(x_recip.data(), x_r.data()); + } + else + { + std::fill(x_r.begin(), x_r.end(), std::complex(0.0, 0.0)); + } +} + +} // namespace + +void DFPT_Phon::accumulate_electron(int q_idx, + int atom_idx, + int dir, + const psi::Psi>& psi, + const ModuleBase::matrix& wg, + DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Phon", "accumulate_electron"); + ModuleBase::timer::start("DFPT_Phon", "accumulate_electron"); + if (pert_ == nullptr || pw_rho_ == nullptr || ucell_ == nullptr) + { + ModuleBase::timer::end("DFPT_Phon", "accumulate_electron"); + return; + } + const int nat = ucell_->nat; + const int nat3 = 3 * nat; + if (accum_q_ != q_idx || dynmat_accum_.nr != nat3) + { + dynmat_accum_ = ModuleBase::ComplexMatrix(nat3, nat3, true); + accum_q_ = q_idx; + } + const int rowb = 3 * atom_idx + dir; + + // stash the converged dpsi of this displacement (apply_dv reuses the slot) + const DpsiDisp dpsib = stash_dpsi(data, q_idx, atom_idx, dir, psi.get_nk(), psi.get_nbands()); + + for (int iat = 0; iat < nat; ++iat) + { + for (int idir = 0; idir < 3; ++idir) + { + const int cola = 3 * iat + idir; + // ---- term 2 over all k,n ---- + // Hermitian (2n+1) accumulation: the row element gets X_ba and + // the transposed element gets conj(X_ba); the self-consistent + // response of dpsi^b already contains the screening, and the + // Hartree-xc kernel quadratic term cancels the + // cross terms by the variational identity, so only the bare + // external perturbation appears here + pert_->build_dv(q_idx, iat, idir, data); + const std::complex cross = cross_sum(*pert_, data, q_idx, dpsib, psi, wg); + const double mass_norm + = std::sqrt(ucell_->atoms[ucell_->iat2it[atom_idx]].mass * ucell_->atoms[ucell_->iat2it[iat]].mass); + dynmat_accum_(rowb, cola) += cross / mass_norm; + dynmat_accum_(cola, rowb) += std::conj(cross) / mass_norm; + + // ---- same-atom anharmonic term ---- + if (iat == atom_idx) + { + accum_d2_same_atom(atom_idx, dir, iat, idir, rowb, psi, wg); + } + } + } + + // restore the converged dpsi of this displacement + restore_dpsi(data, q_idx, psi.get_nk(), psi.get_nbands(), dpsib); + ModuleBase::timer::end("DFPT_Phon", "accumulate_electron"); +} + +void DFPT_Phon::accum_d2_same_atom(int atom_idx, + int dir, + int iat, + int idir, + int rowb, + const psi::Psi>& psi, + const ModuleBase::matrix& wg) +{ + const int cola = 3 * iat + idir; + if (cola < rowb) + { + // only the upper triangle of the same-atom block is accumulated + // here; the Hermitian partner is added below from conj(d2sum) + return; + } + const int nk = psi.get_nk(); + const int nbands = psi.get_nbands(); + // QE ground truth (dynmat_us.f90 + phq_init.f90): the mixed + // (+q, -q) second-order potential of the local part is + // -Omega tpiba^2 G_a G_b vloc(|G|) Re[rho(G) e^{-iG tau_s}] + // (integer G, no q), and the KB nonlocal part is the same-atom + // block deff[gammap*becp1 + becp1*gammap + alphap_a*alphap_b + + // alphap_b*alphap_a] with becp1/alphap/gammap all built from + // vkb_k and (k+G) factors (integer G, no q). The (+q,-q) + // dressings collapse to an integer-G carrier for every q, so + // this term is q-independent and must never be gated on 2q + // commensurability (the old gate silently dropped it for + // 2q not reciprocal, e.g. q=(0.25,0,0), and produced + // imaginary phonon branches). + const ModuleBase::Vector3 q_eff_cart(0.0, 0.0, 0.0); + std::vector> dv2_r; + pert_->d2vloc_r(atom_idx, idir, dir, dv2_r); + if (static_cast(dv2_r.size()) != pw_rho_->nrxx) + { + dv2_r.assign(pw_rho_->nrxx, std::complex(0.0, 0.0)); + } + std::vector>> chi; + std::complex d2sum(0.0, 0.0); + std::vector> u_r(pw_rho_->nrxx); + std::vector> x_r(pw_rho_->nrxx); + for (int ik = 0; ik < nk; ++ik) + { + pert_->apply_d2vnl(atom_idx, idir, dir, q_eff_cart, psi, ik, chi); + // k+q_eff scatter map for this k (must match apply_d2vnl) + DFPT_KQ_Basis kq; + kq.init(pert_->get_pw_wfc(), pert_->get_pw_rho(), q_eff_cart, ik); + const int npwk_kq = kq.get_npwk(); + for (int ib = 0; ib < nbands; ++ib) + { + if (!dfpt_band_occupied(wg, ik, ib)) + { + continue; + } + pert_->get_pw_wfc()->recip2real(&psi(ik, ib, 0), u_r.data(), ik); + scatter_chi_to_r(*pw_rho_, chi, nbands, ib, npwk_kq, kq, x_r); + std::complex expect(0.0, 0.0); + for (int ir = 0; ir < pw_rho_->nrxx; ++ir) + { + expect += std::conj(u_r[ir]) * u_r[ir] * dv2_r[ir] + std::conj(u_r[ir]) * x_r[ir]; + } + d2sum += wg(ik, ib) * expect / static_cast(pw_rho_->nxyz); + } + } + const double inv_m = 1.0 / ucell_->atoms[ucell_->iat2it[atom_idx]].mass; + dynmat_accum_(rowb, cola) += d2sum * inv_m; + if (cola != rowb) + { + dynmat_accum_(cola, rowb) += std::conj(d2sum) * inv_m; + } +} + +} // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_phon_ewald.cpp b/source/source_pw/module_dfpt/dfpt_phon_ewald.cpp new file mode 100644 index 00000000000..d0c6c10d751 --- /dev/null +++ b/source/source_pw/module_dfpt/dfpt_phon_ewald.cpp @@ -0,0 +1,425 @@ +// Ewald ion-ion part of DFPT_Phon::ion_ion, split out of +// dfpt_phon.cpp: the screening-alpha search, the reciprocal-space +// Poisson sums and the real-space erfc force constants. All formulas +// are moved verbatim from the original ion_ion body. + +#include "dfpt_phon.h" + +#include "source_base/constants.h" +#include "source_base/tool_quit.h" +#include "source_base/truncated_func.h" +#include "source_basis/module_pw/pw_basis.h" +#include "source_cell/unitcell.h" + +#include +#include +#include + +#include "source_base/timer.h" +#include "source_base/tool_title.h" + +namespace ModuleDFPT +{ + +namespace +{ + +/// converged screening alpha of the Ewald split, searched so that the +/// G-sum tail is bounded inside the rho grid (returns the alpha; the +/// caller stores it and derives the real-space cutoff) +double ewald_alpha_search(double charge, double ggecut, double tpiba2) +{ + const double alpha_init = 1.1; ///< empirical parameter: initial Ewald screening-alpha guess + const double alpha_shrink = 0.9; ///< empirical parameter: per-iteration alpha shrink factor + const double alpha_min = 1.0e-4; ///< empirical parameter: alpha floor of the search + const double ewald_tail_tol = 1.0e-6; ///< empirical parameter: accepted G-sum tail bound + // ggecut counts |G_max|^2 in 1/lat0^2 units (pw_basis.h), so the bohr^2 + // cutoff is ggecut * tpiba2 + double alpha = alpha_init; + double upperbound = 0.0; + do + { + alpha *= alpha_shrink; + if (alpha < alpha_min) + { + ModuleBase::WARNING_QUIT("DFPT_Phon::ion_ion", "Can't find optimal Ewald alpha."); + } + upperbound = 2.0 * charge * charge * std::sqrt(2.0 * alpha / ModuleBase::TWO_PI) + * ModuleBase::truncated_erfc(std::sqrt(ggecut * tpiba2 / 4.0 / alpha)); + } while (upperbound > ewald_tail_tol); + return alpha; +} + +/// sq/s0 kernels of the self-image phase difference: sq sums +/// (G+q)(G+q)/|G+q|^2 exp(-|G+q|^2/4a) over all grid G (the G = 0 member +/// contributes through w = q) and s0 the same kernel at q = 0 +void g_self_accum(const UnitCell& ucell, + const ModulePW::PW_Basis& pw_rho, + const ModuleBase::Vector3& q_cart, + double alpha, + double (&sq)[3][3], + double (&s0)[3][3]) +{ + const double w2_floor = 1.0e-12; ///< empirical parameter: |G+q|^2 zero-shell guard (1/lat0^2) + const double g2_floor = 1.0e-12; ///< empirical parameter: |G|^2 zero-shell guard (1/lat0^2) + for (int ig = 0; ig < pw_rho.npw; ++ig) + { + const ModuleBase::Vector3& gcart = pw_rho.gcar[ig]; + const ModuleBase::Vector3 w = gcart + q_cart; + const double w2 = w * w; + const double g2 = gcart * gcart; + if (w2 < w2_floor) + { + // G + q = 0 (only possible at q = 0 with G = 0): excluded, as in + // the q = 0 G part below; its isotropic delta/3 limit belongs to + // the direction-averaged q -> 0 behavior, not the exact q = 0 + // matrix + continue; + } + const double w2_bohr = w2 * ucell.tpiba2; + const double gauss = ModuleBase::truncated_exp(-w2_bohr / (4.0 * alpha)); + for (int da = 0; da < 3; ++da) + { + for (int db = 0; db < 3; ++db) + { + sq[da][db] += w[da] * w[db] / w2 * gauss; + } + } + if (g2 > g2_floor) + { + const double gauss_g = ModuleBase::truncated_exp(-g2 * ucell.tpiba2 / (4.0 * alpha)); + for (int da = 0; da < 3; ++da) + { + for (int db = 0; db < 3; ++db) + { + s0[da][db] += gcart[da] * gcart[db] / g2 * gauss_g; + } + } + } + } +} + +/// reciprocal-space Poisson pair sum over atom pairs (ia != ib) with the +/// phase-free Gamma on-site diagonal of the same-atom block +void g_pair_accum(const UnitCell& ucell, + const ModulePW::PW_Basis& pw_rho, + const ModuleBase::Vector3& q_cart, + double alpha, + ModuleBase::ComplexMatrix& dyn) +{ + const double w2_floor = 1.0e-12; ///< empirical parameter: |G+q|^2 zero-shell guard (1/lat0^2) + const double g2_floor = 1.0e-12; ///< empirical parameter: |G|^2 zero-shell guard (1/lat0^2) + const int nat = ucell.nat; + for (int ig = 0; ig < pw_rho.npw; ++ig) + { + const ModuleBase::Vector3& gcart = pw_rho.gcar[ig]; + const ModuleBase::Vector3 w = gcart + q_cart; + const double w2 = w * w; + const double g2 = gcart * gcart; + if (w2 < w2_floor) + { + // G + q = 0 (only possible at q = 0 with G = 0): excluded + continue; + } + const double w2_bohr = w2 * ucell.tpiba2; + const double gauss = ModuleBase::truncated_exp(-w2_bohr / (4.0 * alpha)); + double gauss_g = 0.0; + if (g2 > g2_floor) + { + gauss_g = ModuleBase::truncated_exp(-g2 * ucell.tpiba2 / (4.0 * alpha)); + } + for (int ia = 0; ia < nat; ++ia) + { + const int ita = ucell.iat2it[ia]; + const int iia = ucell.iat2ia[ia]; + const double za = ucell.atoms[ita].ncpp.zv; + const double ma = ucell.atoms[ita].mass; + const ModuleBase::Vector3& ta = ucell.atoms[ita].tau[iia]; + for (int ib = 0; ib < nat; ++ib) + { + if (ib == ia) + { + continue; + } + const int itb = ucell.iat2it[ib]; + const int iib = ucell.iat2ia[ib]; + const double zb = ucell.atoms[itb].ncpp.zv; + const double mb = ucell.atoms[itb].mass; + const ModuleBase::Vector3& tb = ucell.atoms[itb].tau[iib]; + const double arg = ModuleBase::TWO_PI * (w * (ta - tb)); + const std::complex phase(std::cos(arg), std::sin(arg)); + const double pref = ModuleBase::FOUR_PI / ucell.omega * za * zb * ModuleBase::e2 * gauss + / (std::sqrt(ma * mb) * w2); + // Gamma-phase on-site piece (G-only kernel, G != 0) + std::complex phase0(1.0, 0.0); + double pref0 = 0.0; + if (g2 > g2_floor) + { + const double arg0 = ModuleBase::TWO_PI * (gcart * (ta - tb)); + phase0 = std::complex(std::cos(arg0), std::sin(arg0)); + pref0 = ModuleBase::FOUR_PI / ucell.omega * za * zb * ModuleBase::e2 * gauss_g + / (std::sqrt(ma * mb) * g2); + } + for (int da = 0; da < 3; ++da) + { + for (int db = 0; db < 3; ++db) + { + const std::complex elem = pref * w[da] * w[db] * phase; + dyn(3 * ia + da, 3 * ib + db) += elem; + // on-site diagonal: phase-free (Gamma) accumulation, + // Phi_ii = -Phi_ij => -sqrt(Mb/Ma) on the pair term + dyn(3 * ia + da, 3 * ia + db) -= pref0 * gcart[da] * gcart[db] * phase0 * std::sqrt(mb / ma); + } + } + } + } + } +} + +/// d^2/dR_a dR_b [ erfc(sqrt(alpha) R) / R ] tensor (bohr^-3), shared by +/// the self-image and the pair real-space sums; validated against central +/// finite differences of the erfc-split Ewald energy +void ewald_h_ab(const ModuleBase::Vector3& r, double alpha, double (&h)[3][3]) +{ + const double r2 = r * r; + const double rlen = std::sqrt(r2); + const double sar = std::sqrt(alpha); + const double e2a = ModuleBase::truncated_exp(-alpha * r2); + const double f = 2.0 * sar / std::sqrt(ModuleBase::PI) * e2a; + const double er = ModuleBase::truncated_erfc(sar * rlen); + for (int da = 0; da < 3; ++da) + { + for (int db = 0; db < 3; ++db) + { + const double delta = (da == db) ? 1.0 : 0.0; + h[da][db] = er * (3.0 * r[da] * r[db] - delta * r2) / (rlen * r2 * r2) + + f + * (2.0 * alpha * r[da] * r[db] / r2 + + 3.0 * r[da] * r[db] / (r2 * r2) - delta / r2); + } + } +} + +/// ranges of the lattice-vector shells (rows of latvec are the lattice +/// translations in lat0 units) covering the real-space cutoff +void real_shell_ranges(const ModuleBase::Matrix3& latvec, double lat0, double rcut, int (&nmax)[3]) +{ + const double row_e[3][3] = {{latvec.e11, latvec.e12, latvec.e13}, + {latvec.e21, latvec.e22, latvec.e23}, + {latvec.e31, latvec.e32, latvec.e33}}; + for (int d = 0; d < 3; ++d) + { + const ModuleBase::Vector3 a1(row_e[d][0], row_e[d][1], row_e[d][2]); + const double len = std::sqrt(a1 * a1) * lat0; // bohr + nmax[d] = static_cast(std::ceil(rcut / len)) + 1; + } +} + +/// real-space self-image phase difference of the on-site i-i block: the +/// on-site i-i energy is L-independent while the cross-cell i-i force +/// constants carry e^{i2pi q.L}, so D_ii receives +/// -(Za^2 e2/Ma) sum_{L!=0} h_erfc(L) (e^{i2pi q.L} - 1); +/// the imaginary part cancels over the +-L symmetric sphere (h is even) +/// and L = 0 carries e^{i2pi q.0} - 1 = 0 +void real_self_images(const UnitCell& ucell, + const ModuleBase::Vector3& q_frac, + double lat0, + double alpha, + double rcut, + const int (&nmax)[3], + ModuleBase::ComplexMatrix& dyn) +{ + const ModuleBase::Matrix3& latvec = ucell.latvec; + for (int ia = 0; ia < ucell.nat; ++ia) + { + const int ita = ucell.iat2it[ia]; + const int iia = ucell.iat2ia[ia]; + const double za = ucell.atoms[ita].ncpp.zv; + const double ma = ucell.atoms[ita].mass; + const double f2 = za * za * ModuleBase::e2 / ma; + for (int n1 = -nmax[0]; n1 <= nmax[0]; ++n1) + { + for (int n2 = -nmax[1]; n2 <= nmax[1]; ++n2) + { + for (int n3 = -nmax[2]; n3 <= nmax[2]; ++n3) + { + if (n1 == 0 && n2 == 0 && n3 == 0) + { + continue; + } + const ModuleBase::Vector3 lvec(n1 * latvec.e11 + n2 * latvec.e21 + n3 * latvec.e31, + n1 * latvec.e12 + n2 * latvec.e22 + n3 * latvec.e32, + n1 * latvec.e13 + n2 * latvec.e23 + n3 * latvec.e33); + const ModuleBase::Vector3 r = lvec * lat0; + const double r2 = r * r; + if (r2 > rcut * rcut) + { + continue; + } + const double ph_arg = ModuleBase::TWO_PI * (q_frac.x * n1 + q_frac.y * n2 + q_frac.z * n3); + const double wcos = std::cos(ph_arg) - 1.0; + double h[3][3]; + ewald_h_ab(r, alpha, h); + for (int da = 0; da < 3; ++da) + { + for (int db = 0; db < 3; ++db) + { + dyn(3 * ia + da, 3 * ia + db) -= f2 * h[da][db] * wcos; + } + } + } + } + } + } +} + +/// real-space pair sum over lattice shells for ia != ib with the phase-free +/// on-site diagonal Phi_ii^R = -sqrt(Mb/Ma) times the pair term +void real_pair_sum(const UnitCell& ucell, + const ModuleBase::Vector3& q_frac, + double lat0, + double alpha, + double rcut, + const int (&nmax)[3], + ModuleBase::ComplexMatrix& dyn) +{ + const ModuleBase::Matrix3& latvec = ucell.latvec; + for (int ia = 0; ia < ucell.nat; ++ia) + { + const int ita = ucell.iat2it[ia]; + const int iia = ucell.iat2ia[ia]; + const double za = ucell.atoms[ita].ncpp.zv; + const double ma = ucell.atoms[ita].mass; + for (int ib = 0; ib < ucell.nat; ++ib) + { + if (ib == ia) + { + continue; + } + const int itb = ucell.iat2it[ib]; + const int iib = ucell.iat2ia[ib]; + const double zb = ucell.atoms[itb].ncpp.zv; + const double mb = ucell.atoms[itb].mass; + const ModuleBase::Vector3 dt = ucell.atoms[itb].tau[iib] - ucell.atoms[ita].tau[iia]; + const double zab2 = za * zb * ModuleBase::e2 / std::sqrt(ma * mb); + for (int n1 = -nmax[0]; n1 <= nmax[0]; ++n1) + { + for (int n2 = -nmax[1]; n2 <= nmax[1]; ++n2) + { + for (int n3 = -nmax[2]; n3 <= nmax[2]; ++n3) + { + const ModuleBase::Vector3 lvec(n1 * latvec.e11 + n2 * latvec.e21 + n3 * latvec.e31, + n1 * latvec.e12 + n2 * latvec.e22 + n3 * latvec.e32, + n1 * latvec.e13 + n2 * latvec.e23 + n3 * latvec.e33); + ModuleBase::Vector3 r = (lvec + dt) * lat0; // bohr + const double r2 = r * r; + if (r2 > rcut * rcut) + { + continue; + } + const double ph_arg = ModuleBase::TWO_PI * (q_frac.x * n1 + q_frac.y * n2 + q_frac.z * n3); + const std::complex phase(std::cos(ph_arg), std::sin(ph_arg)); + double h[3][3]; + ewald_h_ab(r, alpha, h); + for (int da = 0; da < 3; ++da) + { + for (int db = 0; db < 3; ++db) + { + dyn(3 * ia + da, 3 * ib + db) -= zab2 * h[da][db] * phase; + // on-site diagonal Phi_ii^R = sum_{j != i} + // Z_iZ_j sum_L h(r_ij + L): phase-free (both + // derivatives act on tau_a in cell 0), i.e. + // -sqrt(Mb/Ma) times the pair term + dyn(3 * ia + da, 3 * ia + db) += zab2 * std::sqrt(mb / ma) * h[da][db]; + } + } + } + } + } + } + } +} + +} // namespace + +void DFPT_Phon::ion_ion(const ModuleBase::Vector3& q_frac, ModuleBase::ComplexMatrix& dyn) +{ + ModuleBase::TITLE("DFPT_Phon", "ion_ion"); + ModuleBase::timer::start("DFPT_Phon", "ion_ion"); + const double lat0 = ucell_->lat0; + + // total ionic charge + double charge = 0.0; + for (int it = 0; it < ucell_->ntype; ++it) + { + charge += ucell_->atoms[it].na * ucell_->atoms[it].ncpp.zv; + } + + // choose the screening alpha so that the G-sum tail is converged inside + // the rho grid (the erfc envelope bounds the exp(-G^2/4alpha) tail) + ewald_alpha_ = ewald_alpha_search(charge, pw_rho_->ggecut, ucell_->tpiba2); + const double ewald_rcut_factor = 6.0; ///< empirical parameter: real-space cutoff in 1/sqrt(alpha) + // erfc(alpha R) < 1e-16 well inside 6/sqrt(alpha) + ewald_rcut_ = ewald_rcut_factor / std::sqrt(ewald_alpha_); + + const ModuleBase::Vector3 q_cart = q_frac * ucell_->G; + + // ---------------- reciprocal-space part ---------------- + // Poisson pair identity (validated against direct sums): + // sum_L h(R) e^{i2pi q.L} = sum_L h_erfc(R) e^{i2pi q.L} + // + (4pi/Omega) sum_{|G+q|>0} (G+q)_a (G+q)_b / |G+q|^2 + // exp(-|G+q|^2/4a) e^{i2pi (G+q).(tau_a-tau_b)} + // so the G part enters D with the + sign while the erfc part carries -. + // The on-site diagonal (both second derivatives act on tau_a in cell 0) + // is phase-free: it is accumulated from Gamma-phase (G-only) pair terms + // as -sqrt(Mb/Ma) times the pair element. sq/s0 accumulate the self-image + // phase difference of the same-atom images (validated element-wise + // against finite differences of the erfc-split Ewald energy in a + // q-commensurate supercell): + // D_ii(q) - D_ii(0) = (Za^2 e2 / Ma) [ sum_{L!=0} h(L)(1 - cos(2pi q.L)) + // + (4pi/Omega)(sq - s0) ], + // where sq/s0 are the kernels collected by g_self_accum. The alpha + // independence of this combination was verified numerically; at q = 0 + // both differences vanish and the acoustic sum rule holds exactly by + // construction. + double sq[3][3] = {{0.0}}; + double s0[3][3] = {{0.0}}; + g_self_accum(*ucell_, *pw_rho_, q_cart, ewald_alpha_, sq, s0); + g_pair_accum(*ucell_, *pw_rho_, q_cart, ewald_alpha_, dyn); + // self-image G-space phase difference on the diagonal + for (int ia = 0; ia < ucell_->nat; ++ia) + { + const int ita = ucell_->iat2it[ia]; + const double za = ucell_->atoms[ita].ncpp.zv; + const double ma = ucell_->atoms[ita].mass; + const double f2 = za * za * ModuleBase::e2 / ma; + for (int da = 0; da < 3; ++da) + { + for (int db = 0; db < 3; ++db) + { + dyn(3 * ia + da, 3 * ia + db) += f2 * ModuleBase::FOUR_PI / ucell_->omega * (sq[da][db] - s0[da][db]); + } + } + } + + // ---------------- real-space part ---------------- + // h_ab(R) = d^2/dR_a dR_b [ erfc(sqrt(alpha) R) / R ] + // = erfc(sqrt(alpha) R) (3 Ra Rb - delta R^2)/R^5 + // + (2 sqrt(alpha)/sqrt(pi)) e^{-alpha R^2} + // [ 2 alpha Ra Rb/R^2 + 3 Ra Rb/R^4 - delta/R^2 ] + // D^R_ab = -(1/sqrt(MaMb)) ZaZb e2 h(R = tau_b + l - tau_a) e^{i2pi q.l} + int nmax[3] = {0, 0, 0}; + real_shell_ranges(ucell_->latvec, lat0, ewald_rcut_, nmax); + real_self_images(*ucell_, q_frac, lat0, ewald_alpha_, ewald_rcut_, nmax, dyn); + real_pair_sum(*ucell_, q_frac, lat0, ewald_alpha_, ewald_rcut_, nmax, dyn); + + // The Gaussian self constant -Z^2 sqrt(2 alpha/pi) and the h_erf contact + // -4 alpha^{3/2}/(3 sqrt(pi)) delta_ab are tau-independent and cancel in + // the (e^{i2pi q.L} - 1) differences; the diagonal is carried by the + // phase-free cross-atom accumulation plus the self-image phase terms + // (both G and R pieces above). At q = 0 all phase differences vanish and + // the acoustic sum rule holds exactly by construction. + ModuleBase::timer::end("DFPT_Phon", "ion_ion"); +} + +} // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_pw.cpp b/source/source_pw/module_dfpt/dfpt_pw.cpp index ecc68474686..2d325c90059 100644 --- a/source/source_pw/module_dfpt/dfpt_pw.cpp +++ b/source/source_pw/module_dfpt/dfpt_pw.cpp @@ -1,945 +1,155 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #include "dfpt_pw.h" -#include "dfpt_pw_data.h" +#include "dfpt_pw_impl.h" + +#include "dfpt_hamilt_shift.h" +#include "dfpt_kq_basis.h" +#include "dfpt_metal.h" #include "dfpt_pert.h" -#include "dfpt_stern.h" -#include "dfpt_rho.h" #include "dfpt_phon.h" +#include "dfpt_pw_data.h" #include "dfpt_q0.h" -#include "dfpt_metal.h" -#include "dfpt_hamilt_shift.h" -#include "dfpt_kq_basis.h" +#include "dfpt_rho.h" +#include "dfpt_stern.h" #include "source_base/constants.h" #include "source_base/global_function.h" -#include +#include "source_base/timer.h" +#include "source_base/tool_title.h" #include "source_cell/qlist.h" #include "source_pw/module_pwdft/stru_fac.h" #include #include +#include #include #include #include -#include +#include +#include #include -namespace ModuleDFPT { - -class DFPT_PW::Impl { -public: - Impl() {} - ~Impl() - { - delete hamilt_; - } - - DFPT_PW_Data data_; - DFPT_Pert pert_; - DFPT_Stern stern_; - DFPT_Rho rho_; - DFPT_Phon phon_; - DFPT_Q0 q0_; - DFPT_Metal metal_; - ModuleCell::QList qlist_; - DFPT_HamiltShift* hamilt_ = nullptr; - - psi::Psi> gs_psi_; - UnitCell* ucell_ = nullptr; - ModulePW::PW_Basis* pw_rho_ = nullptr; - ModulePW::PW_Basis_K* pw_wfc_ = nullptr; - Structure_Factor* sf_ = nullptr; - std::vector veff_r_; - ModuleBase::matrix wg_; - ModuleBase::matrix eig_; - const XC_First_Order* xc_ = nullptr; - double nelec_ = 0.0; - double ecutwfc_ = 0.0; - const Plus_U_Base* dftu_ = nullptr; - - ///< occupied states at k+q on the k+q G list, [ik][occ m][igl]; - /// rebuilt per q (they depend on q and k only) - std::vector>>> occ_kq_; - ///< remembers the (q_idx, ik) the shifted operator was last cached at - int last_q_ = -1; - int last_ik_ = -1; - std::vector ikq_of_k_; - - int nqx_ = 1, nqy_ = 1, nqz_ = 1; - std::string qfile_; - double conv_thr_ = 1e-8; - int max_iter_ = 100; - double mix_beta_ = 0.4; - - bool wired() const { return pw_rho_ != nullptr && pw_wfc_ != nullptr; } - - /// occupied-state projector set at k+q for every k of this q (commensurate - /// q: kvec_d[ik] + q must be a k point of the ground-state list mod lattice) - void build_occ_kq(int q_idx); - - /// one self-consistent Sternheimer cycle for the displacement (iat, idir) - /// at q; returns the achieved density residual (zero when unwired) - double solve_displacement(int q_idx, int iat, int idir); - - /// position legs Y^a_{k,v} = P_c x_a|psi_{k,v}> of the q = 0 mesh - /// (velocity-rhs Sternheimer solves, one per direction; stashed through - /// data as the exact position leg of the screened Born charges) - void solve_pos_resp(int q_idx); - - /// E-field SCF response dpsi^E,a of the q = 0 mesh (QE solve_e + - /// dfpt_kernel form: fixed point on the rhs -(Y^a + dV_sc^E,a|psi>) - /// with the screening assembly of solve_displacement) - void solve_efield_resp(int q_idx); -}; - -DFPT_PW::DFPT_PW() : pimpl_(new Impl()) {} - -DFPT_PW::~DFPT_PW() { - delete pimpl_; -} - -void DFPT_PW::init(UnitCell& ucell, const psi::Psi>& psi, - ModulePW::PW_Basis* pw_rho, ModulePW::PW_Basis_K* pw_wfc, - Structure_Factor* sf, const std::vector& veff_r, - const ModuleBase::matrix& wg, const ModuleBase::matrix& eig, - const XC_First_Order* xc, - double nelec, double ecutwfc, const Plus_U_Base* dftu) { - pimpl_->ucell_ = &ucell; - pimpl_->gs_psi_ = psi; - pimpl_->pw_rho_ = pw_rho; - pimpl_->pw_wfc_ = pw_wfc; - pimpl_->sf_ = sf; - pimpl_->veff_r_ = veff_r; - pimpl_->wg_ = wg; - pimpl_->eig_ = eig; - - // Metallic-sampling guard: the Sternheimer/projector flow treats every - // band as either fully occupied or empty and carries no d(mu)/dtau - // response, so a sampling whose smearing Fermi level cuts a band (wg - // strictly between 0 and the full reference) yields force constants - // wrong at the 100% level while still converging cleanly. Reject it - // explicitly (C4 defers metallic DFPT); negligible gauss tails - // (relative weight < 1e-3) are tolerated as the insulator limit. - for (int ik = 0; ik < wg.nr; ++ik) { - const double wref = wg(ik, 0); - if (wref <= 0.0) { - continue; - } - for (int ib = 0; ib < wg.nc; ++ib) { - const double rel = wg(ik, ib) / wref; - if (rel > 1.0e-3 && rel < 1.0 - 1.0e-3) { - std::stringstream msg; - msg << "fractional band occupation at (ik=" << ik - << ", ib=" << ib << ", wg=" << wg(ik, ib) - << "): metallic DFPT (smearing occupations crossing the" - " Fermi level) is not supported; reduce smearing sigma" - " or use an insulating k sampling."; - ModuleBase::WARNING_QUIT("DFPT_PW::init", msg.str()); - } - } - } - pimpl_->xc_ = xc; - pimpl_->nelec_ = nelec; - pimpl_->ecutwfc_ = ecutwfc; - pimpl_->dftu_ = dftu; - - // DFT+U guard: the ground state now supports PW-basis DFT+U and wires a - // provider when dft_plus_u is enabled, but every DFPT U hook - // (DFPT_Rho::cal_docc, DFPT_Pert::build_dv_u, DFPT_Q0 born/docc - // contractions, DFPT_Phon::dftu_onsite) is a no-op reservation (U0). - // Running anyway would converge cleanly while silently dropping the - // whole first-order U response, so reject explicitly until U1 lands - // (same fail-loud pattern as the metallic-sampling guard above). - if (dftu != nullptr) { - ModuleBase::WARNING_QUIT("DFPT_PW::init", - "DFT+U with DFPT is not supported yet: the " - "first-order U response is not implemented " - "(U0 reservation); rerun with dft_plus_u 0."); - } - - // q points: an explicit q list file overrides the Monkhorst-Pack mesh - if (!pimpl_->qfile_.empty()) { - pimpl_->qlist_.read_from_file(pimpl_->qfile_, ucell); - if (pimpl_->qlist_.get_nq() == 0) { - ModuleBase::WARNING_QUIT("DFPT_PW::init", - "failed to read the DFPT q-point file: " + pimpl_->qfile_); - } - } else { - std::vector mp_grid = {pimpl_->nqx_, pimpl_->nqy_, pimpl_->nqz_}; - pimpl_->qlist_.generate_mesh(ucell, ucell.symm, mp_grid, true); - } - - int nq = pimpl_->qlist_.get_nq(); - int nk = psi.get_nk(); - int nbands = psi.get_nbands(); - int npw_max = psi.get_current_ngk(); - int nrxx = (pw_rho != nullptr) ? pw_rho->nrxx : 0; - int nspin = 1; - int nat = ucell.nat; +namespace ModuleDFPT +{ - if (pw_rho != nullptr && pw_wfc != nullptr && sf != nullptr) { - pimpl_->pert_.init(ucell, pw_rho, pw_wfc, *sf); - // plain-mixing coefficient: the response Jacobian has strongly - // negative eigenvalues concentrated on the smallest-G shells (the - // Coulomb stiffness 4pi/G^2; measured lambda ~ -2.2 on {111}/{200} - // for the diamond smoke case), so the coefficient must stay below - // 2 / (1 + |lambda_min|); the INPUT default 0.4 keeps margin up to - // |lambda| ~ 3; the alternative is mix_type = "kerker", the screen - // f_g = |G+q|^2 / (|G+q|^2 + a^2) in 1/lat0^2 units (a^2 via - // DFPT_KERKER_A2), which stabilizes those shells at beta up to 1; - // the env knobs are design-phase calibration aids - double mix_beta = pimpl_->mix_beta_; - if (const char* env_beta = getenv("DFPT_MIX_BETA")) { - const double parsed = atof(env_beta); - if (parsed > 0.0 && parsed <= 1.0) { - mix_beta = parsed; - } - } - std::string mix_type = "plain"; - if (const char* env_type = getenv("DFPT_MIX_TYPE")) { - const std::string parsed = env_type; - if (parsed == "plain" || parsed == "kerker") { - mix_type = parsed; - } - } - double kerker_a2 = 1.0; - if (const char* env_a2 = getenv("DFPT_KERKER_A2")) { - const double parsed = atof(env_a2); - if (parsed > 0.0) { - kerker_a2 = parsed; - } - } - pimpl_->rho_.init(nspin, nrxx, pw_rho, pw_wfc, ucell.G, mix_type, mix_beta, kerker_a2); - pimpl_->phon_.init(ucell, pw_rho, &pimpl_->pert_); - pimpl_->q0_.init(ucell, pw_rho, pw_wfc, &pimpl_->pert_); - delete pimpl_->hamilt_; - pimpl_->hamilt_ = new DFPT_HamiltShift(ucell, pw_rho, pw_wfc, veff_r, &pimpl_->pert_); - } else { - pimpl_->phon_.init(ucell, nullptr, nullptr); - } - pimpl_->data_.init(&pimpl_->qlist_, nk, nbands, npw_max, nrxx, nspin, nat, dftu); +DFPT_PW::Impl::Impl() +{ } -bool DFPT_PW::get_with_u() const { - return pimpl_->data_.with_u(); +DFPT_PW::Impl::~Impl() +{ } -bool DFPT_PW::get_u_active() const { - return pimpl_->data_.u_active(); +bool DFPT_PW::Impl::wired() const +{ + return pw_rho_ != nullptr && pw_wfc_ != nullptr; } -void DFPT_PW::Impl::build_occ_kq(int q_idx) { - const int nk = pw_wfc_->nks; - occ_kq_.assign(nk, std::vector>>()); - ikq_of_k_.assign(nk, -1); - const ModuleBase::Vector3 q_frac = data_.get_qvec(q_idx); - const ModuleBase::Vector3 q_cart = q_frac * ucell_->G; - for (int ik = 0; ik < nk; ++ik) { - // k+q folded into [0,1) direct coordinates must be a ground-state k - // point (DFPT q meshes are commensurate with the k mesh) - const ModuleBase::Vector3 target = pw_wfc_->kvec_d[ik] + q_frac; - int ikq = -1; - for (int j = 0; j < nk; ++j) { - const ModuleBase::Vector3& kj = pw_wfc_->kvec_d[j]; - const double rx = std::round(kj.x - target.x); - const double ry = std::round(kj.y - target.y); - const double rz = std::round(kj.z - target.z); - if (std::abs(kj.x - target.x - rx) < 1.0e-6 - && std::abs(kj.y - target.y - ry) < 1.0e-6 - && std::abs(kj.z - target.z - rz) < 1.0e-6) { - ikq = j; - break; - } - } - if (ikq < 0) { - std::ostringstream oss; - oss << "k+q is not a point of the ground-state k list: the DFPT " - "q mesh must be commensurate with the k mesh (and inside " - "the first Brillouin zone). ik=" << ik - << " k_d=(" << pw_wfc_->kvec_d[ik].x << "," << pw_wfc_->kvec_d[ik].y - << "," << pw_wfc_->kvec_d[ik].z << ") q_d=(" << q_frac.x << "," - << q_frac.y << "," << q_frac.z << ") k+q=(" << target.x << "," - << target.y << "," << target.z << ") nk=" << nk; - ModuleBase::WARNING_QUIT("DFPT_PW::build_occ_kq", oss.str()); - } - ikq_of_k_[ik] = ikq; - - DFPT_KQ_Basis kq; - kq.init(pw_wfc_, pw_rho_, q_cart, ik); - const int npw_kq = kq.get_npwk(); - - // The congruence match above may fold k+q onto a *different label* - // of the same physical point (e.g. k lists holding both (1/2,0,0) - // and (-1/2,0,0), which differ by a reciprocal lattice vector). - // The two balls then enumerate different G labels: a state of the - // ikq ball with vector G' coincides physically with the k+q-ball - // vector G when G' + k(ijq) == G + k(ik) + q, i.e. - // G' = G + dn with dn = k_d(ik) + q - k_d(ikq) integer in - // reciprocal-basis coordinates. Coincident FFT cells identify the - // same G only for dn = 0, so match through the G vectors instead. - const ModuleBase::Vector3 dn = pw_wfc_->kvec_d[ik] + q_frac - - pw_wfc_->kvec_d[ikq]; - const double dnr[3] = {std::round(dn.x), std::round(dn.y), std::round(dn.z)}; - if (std::abs(dn.x - dnr[0]) > 1.0e-6 || std::abs(dn.y - dnr[1]) > 1.0e-6 - || std::abs(dn.z - dnr[2]) > 1.0e-6) { - ModuleBase::WARNING_QUIT("DFPT_PW::build_occ_kq", - "k+q folds onto a k-list entry with a " - "non-integer reciprocal offset."); - } - const int dn_i[3] = {static_cast(dnr[0]), - static_cast(dnr[1]), - static_cast(dnr[2])}; - const ModuleBase::Matrix3 ginv = pw_wfc_->G.Inverse(); - // reciprocal-basis integer triple -> per-k index of the ikq ball - // (pw_wfc_ is a PW_Basis_K whose gcar holds a per-k ball layout, - // not the parent-class global-ig layout: read it through getgcar) - std::map, int> jgl_of_n; - for (int jgl = 0; jgl < pw_wfc_->npwk[ikq]; ++jgl) { - const ModuleBase::Vector3 gf - = pw_wfc_->getgcar(ikq, jgl) * ginv; - const std::vector key = {static_cast(std::round(gf.x)), - static_cast(std::round(gf.y)), - static_cast(std::round(gf.z))}; - jgl_of_n[key] = jgl; - } - - const int nbands = gs_psi_.get_nbands(); - for (int m = 0; m < nbands; ++m) { - if (!dfpt_band_occupied(wg_, ikq, m)) { - continue; // empty at k+q: outside the P_c projector - } - std::vector> state(npw_kq, std::complex(0.0, 0.0)); - for (int igl = 0; igl < npw_kq; ++igl) { - const ModuleBase::Vector3 gf = kq.get_gcar(igl) * ginv; - const std::vector key - = {static_cast(std::round(gf.x)) + dn_i[0], - static_cast(std::round(gf.y)) + dn_i[1], - static_cast(std::round(gf.z)) + dn_i[2]}; - const auto it = jgl_of_n.find(key); - if (it != jgl_of_n.end()) { - state[igl] = gs_psi_(ikq, m, it->second); - } - } - occ_kq_[ik].push_back(std::move(state)); - } - } - last_q_ = q_idx; - last_ik_ = -1; -} - -double DFPT_PW::Impl::solve_displacement(int q_idx, int iat, int idir) { - if (!wired() || hamilt_ == nullptr) { - return 0.0; - } - const ModuleBase::Vector3 q_frac = data_.get_qvec(q_idx); - const ModuleBase::Vector3 q_cart = q_frac * ucell_->G; - const int nrxx = pw_rho_->nrxx; - const int nk = gs_psi_.get_nk(); - const int nbands = gs_psi_.get_nbands(); - - pert_.build_dv(q_idx, iat, idir, data_); - rho_.reset_mixing(q_idx); - // the previous perturbation's stored response must not leak into the - // first iteration of this one - data_.set_drho_g(q_idx, 0, - std::vector>(pw_rho_->npw, - std::complex(0.0, 0.0))); - - const int lin_max = data_.get_max_iter(); - const double lin_thr = data_.get_conv_thr(); - - bool converged = false; - double residual = 0.0; - const bool dbg = (getenv("DFPT_DEBUG") != nullptr); - // last screened response potential (hoisted out of the loop: the 2n+1 - // accumulation below needs the converged v_sc of this displacement) - std::vector> v_sc_r_last; - for (int iter = 0; iter < max_iter_ && !converged; ++iter) { - // the per-displacement SCF state (iter / residual / converged) is - // local to this solve: the DFPT_PW_Data ledger is the per-(q,irrep) - // outer-pass record kept by run(), and the final residual is - // returned to the caller for that aggregation (B4) - - // ---- 1. screened response potential from the mixed input density: - // q-shifted complex periodic amplitude on the shared grid, i.e. the - // same convention as dv_rc (v_hartree_q acts on the q-shifted - // coefficients; the XC kernel responds to Re/Im of the amplitude) - std::vector> v_sc_r(nrxx, std::complex(0.0, 0.0)); - const std::vector> drho_in_g = data_.get_drho_g(q_idx, 0); - if (!drho_in_g.empty() && static_cast(drho_in_g.size()) == pw_rho_->npw) { - std::vector> dv_ha_g; - rho_.v_hartree_q(q_cart, drho_in_g, dv_ha_g); - std::vector> vh_r(nrxx); - pw_rho_->recip2real(dv_ha_g.data(), vh_r.data()); - for (int ir = 0; ir < nrxx; ++ir) { - v_sc_r[ir] = vh_r[ir]; - } - if (xc_ != nullptr) { - std::vector> a_r(nrxx); - pw_rho_->recip2real(drho_in_g.data(), a_r.data()); - std::vector> b_r; - xc_->apply(a_r, b_r); - if (static_cast(b_r.size()) == nrxx) { - for (int ir = 0; ir < nrxx; ++ir) { - v_sc_r[ir] += b_r[ir]; - } - } - } - if (dbg) { - double dh = 0.0; - double dv = 0.0; - for (int ig = 0; ig < pw_rho_->npw; ++ig) { - dh += std::norm(drho_in_g[ig]); - } - for (int ir = 0; ir < nrxx; ++ir) { - dv += std::norm(v_sc_r[ir]); - } - std::cout << "DBG iter=" << iter << " |drho_in_g|=" << std::sqrt(dh) - << " |v_sc_r|=" << std::sqrt(dv) << std::endl; - } - } - v_sc_r_last = v_sc_r; - - // ---- 2. Sternheimer solve of every occupied (k, band) - for (int ik = 0; ik < nk; ++ik) { - if (static_cast(occ_kq_.size()) <= ik || occ_kq_[ik].empty()) { - if (dbg) { std::cout << "DBG skip ik=" << ik << " no occ_kq" << std::endl; } - continue; // no occupied states at k+q: nothing to solve - } - // dV_ext |psi_n> for all bands (dVloc convolution + dVnl_dtau) - pert_.apply_dv(q_idx, ik, gs_psi_, data_); - // screened response part |v_sc psi_n> - std::vector>> dv_sc; - pert_.apply_vr(q_idx, ik, v_sc_r, gs_psi_, q_cart, dv_sc); - if (ik != last_ik_ || last_q_ != q_idx) { - hamilt_->set_context(q_cart, ik); - last_ik_ = ik; - if (dbg) { - std::cout << "DBG occ_kq nstates=" << occ_kq_[ik].size() << std::endl; - for (size_t m = 0; m < occ_kq_[ik].size(); ++m) { - double nrm = 0.0; - for (size_t i = 0; i < occ_kq_[ik][m].size(); ++i) { - nrm += std::norm(occ_kq_[ik][m][i]); - } - std::cout << "DBG occ[" << m << "] |psi|^2=" << nrm << std::endl; - } - // kernel consistency: must equal - // eig(ikq, m); the eigenvalue used by set_shift below is - // the k-side one (equal only when H is assembled right) - for (size_t m = 0; m < occ_kq_[ik].size(); ++m) { - hamilt_->set_shift(0.0); - std::vector> hp(occ_kq_[ik][m].size()); - hamilt_->apply(occ_kq_[ik][m].data(), hp.data()); - std::complex dot(0.0, 0.0); - for (size_t i = 0; i < hp.size(); ++i) { - dot += std::conj(occ_kq_[ik][m][i]) * hp[i]; - } - std::cout << "DBG = " - << dot.real() << " + i " << dot.imag() - << " (GS eig " << eig_(ikq_of_k_[ik], static_cast(m)) << ")" << std::endl; - std::cout << "DBG = " - << hamilt_->debug_t_vnl(occ_kq_[ik][m]) << std::endl; - std::cout << "DBG = " - << hamilt_->debug_v_wfc(occ_kq_[ik][m]) << std::endl; - } - } - } - for (int ib = 0; ib < nbands; ++ib) { - if (!dfpt_band_occupied(wg_, ik, ib)) { - continue; // unoccupied: no Sternheimer equation - } - std::vector> rhs = data_.get_dpsi(q_idx, ik, ib); - if (rhs.empty() || static_cast(dv_sc.size()) != nbands - || rhs.size() != dv_sc[ib].size()) { - if (dbg) { - std::cout << "DBG skip solve ik=" << ik << " ib=" << ib - << " rhs.size=" << rhs.size() - << " dv_sc.size=" << dv_sc.size() - << " dv_sc[ib].size=" << (dv_sc.size() > static_cast(ib) ? dv_sc[ib].size() : 999999) - << std::endl; - } - continue; - } - // b = -(dV_ext + dV_sc)|psi_n> - for (size_t i = 0; i < rhs.size(); ++i) { - rhs[i] = -(rhs[i] + dv_sc[ib][i]); - } - hamilt_->set_shift(eig_(ik, ib)); - std::vector> dpsi_out; - double res = 0.0; - stern_.solve(*hamilt_, occ_kq_[ik], rhs, lin_max, lin_thr, dpsi_out, res); - if (dbg) { - double nr = 0.0, nb2 = 0.0; - for (size_t i = 0; i < dpsi_out.size(); ++i) { - nr += std::norm(dpsi_out[i]); - nb2 += std::norm(rhs[i]); - } - std::cout << "DBG solve ik=" << ik << " ib=" << ib - << " eps=" << eig_(ik, ib) - << " res=" << res << " |dpsi|=" << std::sqrt(nr) - << " |rhs|=" << std::sqrt(nb2) - << " finite=" << (std::isfinite(std::sqrt(nr)) ? 1 : 0) - << std::endl; - } - data_.set_dpsi(q_idx, ik, ib, dpsi_out); - } - } - - // ---- 3. first-order density and mixing - rho_.compute_drho(gs_psi_, wg_, q_idx, data_); - rho_.mix_drho(q_idx, data_); - residual = rho_.get_residual(q_idx, data_); - if (dbg) { - std::cout << "DBG iter=" << iter << " residual=" << residual - << " conv_thr=" << conv_thr_ << std::endl; - } - converged = (residual < conv_thr_); - } - // stash the converged screened potential and dpsi of this displacement - // for the two-pass 2n+1 accumulation (term2 cross section needs - // dV_ext^b + dV_sc^b and dpsi^b of every displacement) - data_.set_vsc_r(iat, idir, v_sc_r_last); - { - std::vector>>> disp( - nk, std::vector>>(nbands)); - for (int ik = 0; ik < nk; ++ik) { - for (int ib = 0; ib < nbands; ++ib) { - disp[ik][ib] = data_.get_dpsi(q_idx, ik, ib); - } - } - data_.set_dpsi_disp(iat, idir, disp); - } - - return residual; +DFPT_PW::DFPT_PW() : pimpl_(std::unique_ptr(new Impl())) +{ } -void DFPT_PW::Impl::solve_pos_resp(int q_idx) { - // Y^a_{k,v} = P_c x_a|psi_{k,v}> through the Sternheimer equation - // (H(k) - eps_v) Y^a_v = P_c [H, x_a]|psi_v>, - // [H, x_a]|psi> = -(i/tpiba) dH/dk_a|psi> (velocity form), - // exactly the linear solve of QE dvpsi_e (whose rhs negation restores - // P_c[H,x]psi from commutator_Hx_psi's [x,H] convention). dH/dk_a is the - // pos_matrix velocity operator: the diagonal kinetic 2 tpiba^2 (k+G)_a - // plus the separable projector derivative (build_vkb/build_vkb_dk). The - // solved vector carries the complete conduction-space position response - // and replaces the empty-eigenvector-truncated r-matrix contraction. - if (!wired() || hamilt_ == nullptr) { - return; - } - const ModuleBase::Vector3 q_cart = data_.get_qvec(q_idx) * ucell_->G; - const int nk = gs_psi_.get_nk(); - const int nbands = gs_psi_.get_nbands(); - const double tpiba = ucell_->tpiba; - const double tpiba2 = tpiba * tpiba; - const int lin_max = data_.get_max_iter(); - const double lin_thr = data_.get_conv_thr(); - const bool dbg = (getenv("DFPT_DEBUG") != nullptr); - - for (int a = 0; a < 3; ++a) { - std::vector>>> yvec( - nk, std::vector>>(nbands)); - for (int ik = 0; ik < nk; ++ik) { - if (occ_kq_[ik].empty()) { - continue; // matches the displacement solve guard - } - if (last_q_ != q_idx || last_ik_ != ik) { - hamilt_->set_context(q_cart, ik); - last_q_ = q_idx; - last_ik_ = ik; - } - const int npwk = pw_wfc_->npwk[ik]; - std::vector> gk(npwk); - for (int ig = 0; ig < npwk; ++ig) { - gk[ig] = pw_wfc_->getgpluskcar(ik, ig); - } - // dH/dk_a|psi_b> for every band: diagonal kinetic part - std::vector>> vel( - nbands, - std::vector>(npwk, std::complex(0.0, 0.0))); - for (int ib = 0; ib < nbands; ++ib) { - for (int ig = 0; ig < npwk; ++ig) { - vel[ib][ig] = 2.0 * tpiba2 * gk[ig][a] * gs_psi_(ik, ib, ig); - } - } - // nonlocal derivative part (pos_matrix velocity form; NCPP - // separable projectors only) - for (int it = 0; it < ucell_->ntype; ++it) { - const pseudo& ncpp = ucell_->atoms[it].ncpp; - const int nh = ncpp.nh; - if (nh == 0) { - continue; - } - // projector -> (radial beta index, m channel) table - std::vector mu_ib(nh, 0); - std::vector mu_m(nh, 0); - int mu_idx = 0; - for (int ib = 0; ib < ncpp.nbeta; ++ib) { - const int l = ncpp.lll[ib]; - for (int m = 0; m < 2 * l + 1; ++m) { - if (mu_idx < nh) { - mu_ib[mu_idx] = ib; - mu_m[mu_idx] = m; - } - ++mu_idx; - } - } - for (int ia = 0; ia < ucell_->atoms[it].na; ++ia) { - std::vector>> vkb; - pert_.build_vkb(it, ia, gk, vkb); - // becp_b[mu] = - std::vector>> becp(nbands); - for (int b = 0; b < nbands; ++b) { - becp[b].assign(nh, std::complex(0.0, 0.0)); - for (int mu = 0; mu < nh; ++mu) { - for (int ig = 0; ig < npwk; ++ig) { - becp[b][mu] += std::conj(vkb[mu][ig]) * gs_psi_(ik, b, ig); - } - } - } - std::vector>> dvkb; - pert_.build_vkb_dk(it, ia, a, gk, vkb, dvkb); - // dbecp_b[mu] = - std::vector>> dbecp(nbands); - for (int b = 0; b < nbands; ++b) { - dbecp[b].assign(nh, std::complex(0.0, 0.0)); - for (int mu = 0; mu < nh; ++mu) { - for (int ig = 0; ig < npwk; ++ig) { - dbecp[b][mu] += std::conj(dvkb[mu][ig]) * gs_psi_(ik, b, ig); - } - } - } - // dV_nl/dk_a|psi_b> = sum_mu |dvkb_mu> (D becp_b)_mu - // + |vkb_mu> (D dbecp_b)_mu - for (int b = 0; b < nbands; ++b) { - for (int mu = 0; mu < nh; ++mu) { - std::complex out_b(0.0, 0.0); - std::complex in_b(0.0, 0.0); - for (int nu = 0; nu < nh; ++nu) { - if (mu_m[mu] != mu_m[nu]) { - continue; - } - const double dij = ncpp.dion(mu_ib[mu], mu_ib[nu]); - out_b += dij * becp[b][nu]; - in_b += dij * dbecp[b][nu]; - } - for (int ig = 0; ig < npwk; ++ig) { - vel[b][ig] += dvkb[mu][ig] * out_b + vkb[mu][ig] * in_b; - } - } - } - } - } - // solve (H - eps_v) Y = -(i/tpiba) vel for every occupied band - for (int ib = 0; ib < nbands; ++ib) { - if (!dfpt_band_occupied(wg_, ik, ib)) { - continue; - } - std::vector> rhs( - npwk, std::complex(0.0, 0.0)); - const std::complex fac(0.0, -1.0 / tpiba); - for (int ig = 0; ig < npwk; ++ig) { - rhs[ig] = fac * vel[ib][ig]; - } - hamilt_->set_shift(eig_(ik, ib)); - double res = 0.0; - stern_.solve(*hamilt_, occ_kq_[ik], rhs, lin_max, lin_thr, - yvec[ik][ib], res); - if (dbg) { - std::cout << "DBG posresp a=" << a << " ik=" << ik - << " ib=" << ib << " eps=" << eig_(ik, ib) - << " res=" << res << std::endl; - } - } - } - data_.set_pos_resp(a, yvec); - } +DFPT_PW::~DFPT_PW() +{ } -void DFPT_PW::Impl::solve_efield_resp(int q_idx) { - // E-field SCF response (QE solve_e + dfpt_kernel form): the bare legs - // Y^a stashed by solve_pos_resp are the field rhs base and the fixed - // point adds the screened response potential of the mixed drho^E - // exactly like solve_displacement. The converged dpsi^E,a feeds the - // SCF dielectric tensor (DFPT_Q0::compute_eps) and the zstar_eu - // cross-check probe (DFPT_ALEG). - if (!wired() || hamilt_ == nullptr) { - return; - } - const ModuleBase::Vector3 q_cart = data_.get_qvec(q_idx) * ucell_->G; - const int nrxx = pw_rho_->nrxx; - const int nk = gs_psi_.get_nk(); - const int nbands = gs_psi_.get_nbands(); - const int lin_max = data_.get_max_iter(); - const double lin_thr = data_.get_conv_thr(); - - for (int a = 0; a < 3; ++a) { - const std::vector>>> yr - = data_.get_pos_resp(a); - if (static_cast(yr.size()) != nk) { - continue; // bare legs not solved: no E response either - } - rho_.reset_mixing(q_idx); - data_.set_drho_g(q_idx, 0, - std::vector>(pw_rho_->npw, - std::complex(0.0, 0.0))); - bool converged = false; - for (int iter = 0; iter < max_iter_ && !converged; ++iter) { - // screened response potential of the mixed input density - // (identical assembly to solve_displacement) - std::vector> v_sc_r(nrxx, std::complex(0.0, 0.0)); - const std::vector> drho_in_g = data_.get_drho_g(q_idx, 0); - if (!drho_in_g.empty() && static_cast(drho_in_g.size()) == pw_rho_->npw) { - std::vector> dv_ha_g; - rho_.v_hartree_q(q_cart, drho_in_g, dv_ha_g); - pw_rho_->recip2real(dv_ha_g.data(), v_sc_r.data()); - if (xc_ != nullptr) { - std::vector> a_r(nrxx); - pw_rho_->recip2real(drho_in_g.data(), a_r.data()); - std::vector> b_r; - xc_->apply(a_r, b_r); - if (static_cast(b_r.size()) == nrxx) { - for (int ir = 0; ir < nrxx; ++ir) { - v_sc_r[ir] += b_r[ir]; - } - } - } - } - for (int ik = 0; ik < nk; ++ik) { - if (static_cast(occ_kq_.size()) <= ik || occ_kq_[ik].empty()) { - continue; - } - std::vector>> dv_sc; - pert_.apply_vr(q_idx, ik, v_sc_r, gs_psi_, q_cart, dv_sc); - if (last_q_ != q_idx || last_ik_ != ik) { - hamilt_->set_context(q_cart, ik); - last_q_ = q_idx; - last_ik_ = ik; - } - for (int ib = 0; ib < nbands; ++ib) { - if (!dfpt_band_occupied(wg_, ik, ib)) { - continue; - } - if (static_cast(yr[ik][ib].size()) == 0 - || static_cast(dv_sc.size()) != nbands - || yr[ik][ib].size() != dv_sc[ib].size()) { - continue; - } - std::vector> rhs(yr[ik][ib].size()); - for (size_t i = 0; i < rhs.size(); ++i) { - rhs[i] = -(yr[ik][ib][i] + dv_sc[ib][i]); - } - hamilt_->set_shift(eig_(ik, ib)); - std::vector> dpsi_out; - double res = 0.0; - stern_.solve(*hamilt_, occ_kq_[ik], rhs, lin_max, lin_thr, - dpsi_out, res); - data_.set_dpsi(q_idx, ik, ib, dpsi_out); - } - } - rho_.compute_drho(gs_psi_, wg_, q_idx, data_); - rho_.mix_drho(q_idx, data_); - const double residual = rho_.get_residual(q_idx, data_); - converged = (residual < conv_thr_); - if (converged) { - std::cout << "DFPT efield dir=" << a - << " converged, residual=" << residual - << " (iter=" << iter << ")" << std::endl; - } - } - // stash dpsi^E,a before any later solve reuses the slots - std::vector>>> de( - nk, std::vector>>(nbands)); - for (int ik = 0; ik < nk; ++ik) { - for (int ib = 0; ib < nbands; ++ib) { - de[ik][ib] = data_.get_dpsi(q_idx, ik, ib); - } - } - data_.set_dpsi_efield(a, de); - } +bool DFPT_PW::get_with_u() const +{ + return pimpl_->data_.with_u(); } -void DFPT_PW::run() { - const int nq = pimpl_->qlist_.get_nq(); - for (int q_idx = 0; q_idx < nq; ++q_idx) { - // Special handling for q=0 (uniform electric field responses): - // The standard position operator r is ill-defined in periodic systems. - // Developers should NOT pass a conventional position matrix. Instead, - // matrix elements should be computed using the well-defined periodic - // commutator [Ĥ_SCF, r̂]. This is implemented in DFPT_Q0 module. - if (q_idx == 0 && pimpl_->data_.get_compute_q0()) { - pimpl_->q0_.compute_q0_response(pimpl_->data_); - } - - // occupied states at k+q for every k of this q (projector of P_c); - // also invalidates the shifted-operator context cache - if (pimpl_->wired()) { - pimpl_->build_occ_kq(q_idx); - } - - // position legs of the screened Born charges: the q = 0 Y solves - // need the projector just built and must land before the two-pass - // displacement solves below reuse the shifted-operator context - if (q_idx == 0 && pimpl_->data_.get_compute_q0() && pimpl_->wired()) { - pimpl_->solve_pos_resp(q_idx); - // SCF E-field responses of the dielectric tensor: after the - // bare Y legs they consume, before the displacement solves - // reuse the slots; the epsilon contraction runs straight after - // (QE solve_e -> dielec.f90 order) - pimpl_->solve_efield_resp(q_idx); - pimpl_->q0_.compute_eps(pimpl_->wg_, pimpl_->data_); - } - - // Per-irrep self-consistent loop: the little-group irrep - // decomposition is a placeholder until stage A, so the single - // available irrep falls back to the full 3N displacement basis. - // Ledger semantics (B4): one outer pass solves every displacement - // to its own convergence (solve_displacement restarts each from a - // zero input density), and the pass residual is the worst final - // displacement residual; the pass converges when that worst is - // below conv_thr. An unconverged pass therefore re-runs the full - // solve, bounded by max_iter_ outer passes, and the residual - // history keeps an honest record instead of the former - // unconditional single-pass convergence. - const int nirr = pimpl_->data_.get_nirr(q_idx); - for (int irrep = 0; irrep < nirr; ++irrep) { - pimpl_->data_.set_converged(q_idx, irrep, false); - pimpl_->data_.set_current_iter(q_idx, irrep, 0); - while (!pimpl_->data_.get_converged(q_idx, irrep) - && pimpl_->data_.get_current_iter(q_idx, irrep) < pimpl_->max_iter_) { - if (pimpl_->wired()) { - const int nat = pimpl_->ucell_->nat; - // two passes over the 3N displacement basis: first solve - // every displacement to convergence (the 2n+1 accumulation - // of displacement b needs the converged dpsi AND screened - // potential of every column displacement a), then run the - // 2n+1 accumulation for each - double worst = 0.0; - for (int iat = 0; iat < nat; ++iat) { - for (int idir = 0; idir < 3; ++idir) { - const double residual = pimpl_->solve_displacement(q_idx, iat, idir); - worst = std::max(worst, residual); - } - } - for (int iat = 0; iat < nat; ++iat) { - for (int idir = 0; idir < 3; ++idir) { - // 2n+1 accumulation of this converged displacement - pimpl_->phon_.accumulate_electron(q_idx, iat, idir, - pimpl_->gs_psi_, - pimpl_->wg_, - pimpl_->data_); - } - } - pimpl_->data_.add_residual(q_idx, irrep, worst); - pimpl_->data_.set_converged(q_idx, irrep, - worst < pimpl_->data_.get_conv_thr()); - } else { - // design-phase skeleton: no bases wired, converge at once - pimpl_->data_.add_residual(q_idx, irrep, 0.0); - pimpl_->data_.set_converged(q_idx, irrep, true); - } - pimpl_->data_.set_current_iter( - q_idx, irrep, pimpl_->data_.get_current_iter(q_idx, irrep) + 1); - } - } - - // screened Born charges: the Gonze-Lee 2n+1 form consumes the - // converged (screened) dpsi of every q = 0 displacement stashed by - // solve_displacement, so it must run after the two-pass solves - // above and before the LO-TO term below consumes it - if (q_idx == 0 && pimpl_->data_.get_compute_q0() && pimpl_->wired()) { - pimpl_->q0_.compute_born(pimpl_->gs_psi_, pimpl_->wg_, - pimpl_->eig_, pimpl_->data_); - } - - pimpl_->phon_.assemble(q_idx, pimpl_->data_); - pimpl_->phon_.diagonalize(q_idx, pimpl_->data_); - if (q_idx == 0 && pimpl_->data_.get_loto()) { - // non-analytic LO-TO correction along the data-layer direction - // (default isotropic (1,1,1)/sqrt(3) for cubic crystals; - // set_loto_dir overrides, e.g. per irrep direction in stage A) - pimpl_->phon_.add_loto(pimpl_->data_.get_loto_dir(), pimpl_->data_); - pimpl_->phon_.diagonalize_loto(pimpl_->data_); - } - } +bool DFPT_PW::get_u_active() const +{ + return pimpl_->data_.u_active(); } -int DFPT_PW::get_nq() const { +int DFPT_PW::get_nq() const +{ return pimpl_->qlist_.get_nq(); } -ModuleBase::Vector3 DFPT_PW::get_qvec(int q_idx) const { +ModuleBase::Vector3 DFPT_PW::get_qvec(int q_idx) const +{ return pimpl_->data_.get_qvec(q_idx); } -std::vector DFPT_PW::get_phonon_freq(int q_idx) const { +std::vector DFPT_PW::get_phonon_freq(int q_idx) const +{ return pimpl_->data_.get_phon_freq(q_idx); } -std::vector DFPT_PW::get_phon_freq_loto() const { +std::vector DFPT_PW::get_phon_freq_loto() const +{ return pimpl_->data_.get_phon_freq_loto(); } -ModuleBase::Vector3 DFPT_PW::get_loto_dir() const { +ModuleBase::Vector3 DFPT_PW::get_loto_dir() const +{ return pimpl_->data_.get_loto_dir(); } -std::string DFPT_PW::format_q_report(int q_idx) const { +std::string DFPT_PW::format_q_report(int q_idx) const +{ return pimpl_->phon_.format_q_report(q_idx, pimpl_->data_); } -std::string DFPT_PW::format_loto_report() const { +std::string DFPT_PW::format_loto_report() const +{ return pimpl_->phon_.format_loto_report(pimpl_->data_); } -ModuleBase::matrix DFPT_PW::get_dielectric_tensor() const { +ModuleBase::matrix DFPT_PW::get_dielectric_tensor() const +{ return pimpl_->data_.get_dielectric(); } -ModuleBase::matrix DFPT_PW::get_born_charges(int atom_idx) const { +ModuleBase::matrix DFPT_PW::get_born_charges(int atom_idx) const +{ return pimpl_->data_.get_born(atom_idx); } -void DFPT_PW::set_qfile(const std::string& filename) { +void DFPT_PW::set_qfile(const std::string& filename) +{ pimpl_->qfile_ = filename; } -void DFPT_PW::set_qmesh(int nqx, int nqy, int nqz) { +void DFPT_PW::set_qmesh(int nqx, int nqy, int nqz) +{ pimpl_->nqx_ = nqx; pimpl_->nqy_ = nqy; pimpl_->nqz_ = nqz; } -void DFPT_PW::set_conv_thr(double thr) { +void DFPT_PW::set_conv_thr(double thr) +{ pimpl_->conv_thr_ = thr; pimpl_->data_.set_conv_thr(thr); } -void DFPT_PW::set_max_iter(int max_iter) { +void DFPT_PW::set_max_iter(int max_iter) +{ pimpl_->max_iter_ = max_iter; pimpl_->data_.set_max_iter(max_iter); } -void DFPT_PW::set_mix_beta(double beta) { - if (beta > 0.0 && beta <= 1.0) { +void DFPT_PW::set_mix_beta(double beta) +{ + if (beta > 0.0 && beta <= 1.0) + { pimpl_->mix_beta_ = beta; } } -void DFPT_PW::set_compute_q0(bool flag) { +void DFPT_PW::set_compute_q0(bool flag) +{ pimpl_->data_.set_compute_q0(flag); } -void DFPT_PW::set_loto(bool flag) { +void DFPT_PW::set_loto(bool flag) +{ pimpl_->data_.set_loto(flag); } -void DFPT_PW::set_loto_dir(const ModuleBase::Vector3& dir) { +void DFPT_PW::set_loto_dir(const ModuleBase::Vector3& dir) +{ pimpl_->data_.set_loto_dir(dir); } diff --git a/source/source_pw/module_dfpt/dfpt_pw.h b/source/source_pw/module_dfpt/dfpt_pw.h index 59a2dbb1fdb..83560769a19 100644 --- a/source/source_pw/module_dfpt/dfpt_pw.h +++ b/source/source_pw/module_dfpt/dfpt_pw.h @@ -1,11 +1,3 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #ifndef DFPT_PW_H #define DFPT_PW_H @@ -14,21 +6,45 @@ #include "source_cell/unitcell.h" #include "source_psi/psi.h" +#include #include #include class Plus_U_Base; class Structure_Factor; -namespace ModulePW { +namespace ModulePW +{ class PW_Basis; class PW_Basis_K; -} +} // namespace ModulePW -namespace ModuleDFPT { +namespace ModuleDFPT +{ class XC_First_Order; +/// Bundled initialisation context for DFPT_PW (see dfpt_pw_impl.h for the +/// full field-level doxygen). Forward-declared here so callers can build a +/// struct aggregate without pulling the heavy impl header; the detailed +/// field docs live alongside the private impl header that actually uses +/// each field. +struct DFPT_PW_InitContext +{ + UnitCell* ucell; + const psi::Psi>* psi; + ModulePW::PW_Basis* pw_rho; + ModulePW::PW_Basis_K* pw_wfc; + Structure_Factor* sf; + const std::vector* veff_r; + const ModuleBase::matrix* wg; + const ModuleBase::matrix* eig; + const XC_First_Order* xc; + double nelec; + double ecutwfc; + const Plus_U_Base* dftu; +}; + /** * @brief Density-functional perturbation theory driver (plane waves). * @@ -45,17 +61,44 @@ class XC_First_Order; * With null bases (design-phase skeleton) run() keeps the documented * first-iteration-converged fallback of the irrep bookkeeping loop. */ -class DFPT_PW { -public: +class DFPT_PW +{ + public: + class Impl; // pimpl forward declaration (kept in public section so the + // private nested class can be named as DFPT_PW::Impl from + // outside translation units that include the impl header). + DFPT_PW(); ~DFPT_PW(); - void init(UnitCell& ucell, const psi::Psi>& psi, - ModulePW::PW_Basis* pw_rho, ModulePW::PW_Basis_K* pw_wfc, - Structure_Factor* sf, const std::vector& veff_r, - const ModuleBase::matrix& wg, const ModuleBase::matrix& eig, + /// Package-and-forward convenience wrapper: the former 12-argument + /// signature is retained for backward compatibility with the small + /// number of call sites (esolver_dfpt_pw.cpp + three test fixtures), + /// and the actual validation/submodule wiring happens in the + /// InitContext overload below. Keeping the thin wrapper inline avoids + /// a separate TU and gives the call-site aggregate initialization the + /// same performance as a direct call. + void init(UnitCell& ucell, + const psi::Psi>& psi, + ModulePW::PW_Basis* pw_rho, + ModulePW::PW_Basis_K* pw_wfc, + Structure_Factor* sf, + const std::vector& veff_r, + const ModuleBase::matrix& wg, + const ModuleBase::matrix& eig, const XC_First_Order* xc, - double nelec, double ecutwfc, const Plus_U_Base* dftu); + double nelec, + double ecutwfc, + const Plus_U_Base* dftu) + { + const DFPT_PW_InitContext ctx{&ucell, &psi, pw_rho, pw_wfc, sf, &veff_r, &wg, &eig, xc, nelec, ecutwfc, dftu}; + init(ctx); + } + + /// Single-context init carrying the twelve parameters as named fields + /// so the function body stays under the coding-rule parameter-count + /// budget. Semantics are identical to the overload above. + void init(const DFPT_PW_InitContext& ctx); void run(); @@ -115,9 +158,8 @@ class DFPT_PW { std::string format_loto_report() const; -private: - class Impl; - Impl* pimpl_; + private: + std::unique_ptr pimpl_; }; } // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_pw_data.cpp b/source/source_pw/module_dfpt/dfpt_pw_data.cpp index fc8bea0d747..feede10aa11 100644 --- a/source/source_pw/module_dfpt/dfpt_pw_data.cpp +++ b/source/source_pw/module_dfpt/dfpt_pw_data.cpp @@ -1,26 +1,32 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in the design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #include "dfpt_pw_data.h" +#include "source_base/timer.h" +#include "source_base/tool_title.h" #include "source_pw/module_pwdft/dftu_base.h" - #include -namespace ModuleDFPT { +namespace ModuleDFPT +{ -DFPT_PW_Data::DFPT_PW_Data() {} +DFPT_PW_Data::DFPT_PW_Data() +{ +} -DFPT_PW_Data::~DFPT_PW_Data() { +DFPT_PW_Data::~DFPT_PW_Data() +{ clean(); } -void DFPT_PW_Data::init(ModuleCell::QList* qlist, int nk, int nbands, int npw_max, - int nrxx, int nspin, int nat, const Plus_U_Base* dftu) { +void DFPT_PW_Data::init(ModuleCell::QList* qlist, + int nk, + int nbands, + int npw_max, + int nrxx, + int nspin, + int nat, + const Plus_U_Base* dftu) +{ + ModuleBase::TITLE("DFPT_PW_Data", "init"); + ModuleBase::timer::start("DFPT_PW_Data", "init"); qlist_ = qlist; nk_ = nk; nbands_ = nbands; @@ -29,367 +35,603 @@ void DFPT_PW_Data::init(ModuleCell::QList* qlist, int nk, int nbands, int npw_ma nspin_ = nspin; nat_ = nat; dftu_ = dftu; - + allocate_memory(); is_initialized_ = true; + ModuleBase::timer::end("DFPT_PW_Data", "init"); } -void DFPT_PW_Data::clean() { +void DFPT_PW_Data::clean() +{ + ModuleBase::TITLE("DFPT_PW_Data", "clean"); + ModuleBase::timer::start("DFPT_PW_Data", "clean"); deallocate_memory(); is_initialized_ = false; + ModuleBase::timer::end("DFPT_PW_Data", "clean"); } -bool DFPT_PW_Data::u_active() const { +bool DFPT_PW_Data::u_active() const +{ + ModuleBase::TITLE("DFPT_PW_Data", "u_active"); + ModuleBase::timer::start("DFPT_PW_Data", "u_active"); // a usable provider has its occupation matrices initialized (the ground // state does this when DFT+U actually runs); a wired provider without // them (e.g. a default-constructed reservation) stays inactive. + ModuleBase::timer::end("DFPT_PW_Data", "u_active"); return with_u() && dftu_->is_occ_mat_initialized(); } -void DFPT_PW_Data::set_docc(int q_idx, const std::vector>& occ) { - if (q_idx < 0) { +void DFPT_PW_Data::set_docc(int q_idx, const std::vector>& occ) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_docc"); + ModuleBase::timer::start("DFPT_PW_Data", "set_docc"); + if (q_idx < 0) + { + ModuleBase::timer::end("DFPT_PW_Data", "set_docc"); return; } - if (q_idx >= static_cast(docc_.size())) { + if (q_idx >= static_cast(docc_.size())) + { docc_.resize(q_idx + 1); } docc_[q_idx] = occ; + ModuleBase::timer::end("DFPT_PW_Data", "set_docc"); } -void DFPT_PW_Data::set_vsc_r(int atom_idx, int dir, - const std::vector>& v) { - if (atom_idx < 0 || dir < 0 || dir >= 3) { +void DFPT_PW_Data::set_vsc_r(int atom_idx, int dir, const std::vector>& v) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_vsc_r"); + ModuleBase::timer::start("DFPT_PW_Data", "set_vsc_r"); + if (atom_idx < 0 || dir < 0 || dir >= 3) + { + ModuleBase::timer::end("DFPT_PW_Data", "set_vsc_r"); return; } const size_t slot = static_cast(3 * atom_idx + dir); - if (slot >= vsc_r_.size()) { + if (slot >= vsc_r_.size()) + { vsc_r_.resize(slot + 1); } vsc_r_[slot] = v; + ModuleBase::timer::end("DFPT_PW_Data", "set_vsc_r"); } -std::vector> DFPT_PW_Data::get_vsc_r(int atom_idx, int dir) const { - if (atom_idx < 0 || dir < 0 || dir >= 3) { +std::vector> DFPT_PW_Data::get_vsc_r(int atom_idx, int dir) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_vsc_r"); + ModuleBase::timer::start("DFPT_PW_Data", "get_vsc_r"); + if (atom_idx < 0 || dir < 0 || dir >= 3) + { + ModuleBase::timer::end("DFPT_PW_Data", "get_vsc_r"); return std::vector>(); } const size_t slot = static_cast(3 * atom_idx + dir); - if (slot < vsc_r_.size()) { + if (slot < vsc_r_.size()) + { + ModuleBase::timer::end("DFPT_PW_Data", "get_vsc_r"); return vsc_r_[slot]; } + ModuleBase::timer::end("DFPT_PW_Data", "get_vsc_r"); return std::vector>(); } -void DFPT_PW_Data::set_dpsi_disp( - int atom_idx, int dir, - const std::vector>>>& d) { - if (atom_idx < 0 || dir < 0 || dir >= 3) { +void DFPT_PW_Data::set_dpsi_disp(int atom_idx, + int dir, + const std::vector>>>& d) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_dpsi_disp"); + ModuleBase::timer::start("DFPT_PW_Data", "set_dpsi_disp"); + if (atom_idx < 0 || dir < 0 || dir >= 3) + { + ModuleBase::timer::end("DFPT_PW_Data", "set_dpsi_disp"); return; } const size_t slot = static_cast(3 * atom_idx + dir); - if (slot >= dpsi_disp_.size()) { + if (slot >= dpsi_disp_.size()) + { dpsi_disp_.resize(slot + 1); } dpsi_disp_[slot] = d; + ModuleBase::timer::end("DFPT_PW_Data", "set_dpsi_disp"); } -std::vector>>> -DFPT_PW_Data::get_dpsi_disp(int atom_idx, int dir) const { - if (atom_idx < 0 || dir < 0 || dir >= 3) { +std::vector>>> DFPT_PW_Data::get_dpsi_disp(int atom_idx, int dir) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_dpsi_disp"); + ModuleBase::timer::start("DFPT_PW_Data", "get_dpsi_disp"); + if (atom_idx < 0 || dir < 0 || dir >= 3) + { + ModuleBase::timer::end("DFPT_PW_Data", "get_dpsi_disp"); return std::vector>>>(); } const size_t slot = static_cast(3 * atom_idx + dir); - if (slot < dpsi_disp_.size()) { + if (slot < dpsi_disp_.size()) + { + ModuleBase::timer::end("DFPT_PW_Data", "get_dpsi_disp"); return dpsi_disp_[slot]; } + ModuleBase::timer::end("DFPT_PW_Data", "get_dpsi_disp"); return std::vector>>>(); } -void DFPT_PW_Data::set_pos_resp( - int dir, const std::vector>>>& y) { - if (dir < 0 || dir >= 3) { +void DFPT_PW_Data::set_pos_resp(int dir, const std::vector>>>& y) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_pos_resp"); + ModuleBase::timer::start("DFPT_PW_Data", "set_pos_resp"); + if (dir < 0 || dir >= 3) + { + ModuleBase::timer::end("DFPT_PW_Data", "set_pos_resp"); return; } - if (pos_resp_.size() < 3) { + if (pos_resp_.size() < 3) + { pos_resp_.resize(3); } pos_resp_[dir] = y; + ModuleBase::timer::end("DFPT_PW_Data", "set_pos_resp"); } -std::vector>>> -DFPT_PW_Data::get_pos_resp(int dir) const { - if (dir < 0 || dir >= 3 || dir >= static_cast(pos_resp_.size())) { +std::vector>>> DFPT_PW_Data::get_pos_resp(int dir) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_pos_resp"); + ModuleBase::timer::start("DFPT_PW_Data", "get_pos_resp"); + if (dir < 0 || dir >= 3 || dir >= static_cast(pos_resp_.size())) + { + ModuleBase::timer::end("DFPT_PW_Data", "get_pos_resp"); return std::vector>>>(); } + ModuleBase::timer::end("DFPT_PW_Data", "get_pos_resp"); return pos_resp_[dir]; } -void DFPT_PW_Data::set_dpsi_efield( - int dir, const std::vector>>>& d) { - if (dir < 0 || dir >= 3) { +void DFPT_PW_Data::set_dpsi_efield(int dir, const std::vector>>>& d) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_dpsi_efield"); + ModuleBase::timer::start("DFPT_PW_Data", "set_dpsi_efield"); + if (dir < 0 || dir >= 3) + { + ModuleBase::timer::end("DFPT_PW_Data", "set_dpsi_efield"); return; } - if (dpsi_efield_.size() < 3) { + if (dpsi_efield_.size() < 3) + { dpsi_efield_.resize(3); } dpsi_efield_[dir] = d; + ModuleBase::timer::end("DFPT_PW_Data", "set_dpsi_efield"); } -std::vector>>> -DFPT_PW_Data::get_dpsi_efield(int dir) const { - if (dir < 0 || dir >= 3 || dir >= static_cast(dpsi_efield_.size())) { +std::vector>>> DFPT_PW_Data::get_dpsi_efield(int dir) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_dpsi_efield"); + ModuleBase::timer::start("DFPT_PW_Data", "get_dpsi_efield"); + if (dir < 0 || dir >= 3 || dir >= static_cast(dpsi_efield_.size())) + { + ModuleBase::timer::end("DFPT_PW_Data", "get_dpsi_efield"); return std::vector>>>(); } + ModuleBase::timer::end("DFPT_PW_Data", "get_dpsi_efield"); return dpsi_efield_[dir]; } -std::vector> DFPT_PW_Data::get_docc(int q_idx) const { - if (q_idx >= 0 && q_idx < static_cast(docc_.size())) { +std::vector> DFPT_PW_Data::get_docc(int q_idx) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_docc"); + ModuleBase::timer::start("DFPT_PW_Data", "get_docc"); + if (q_idx >= 0 && q_idx < static_cast(docc_.size())) + { + ModuleBase::timer::end("DFPT_PW_Data", "get_docc"); return docc_[q_idx]; } + ModuleBase::timer::end("DFPT_PW_Data", "get_docc"); return std::vector>(); } -int DFPT_PW_Data::get_nq() const { +int DFPT_PW_Data::get_nq() const +{ return qlist_->get_nq(); } -ModuleBase::Vector3 DFPT_PW_Data::get_qvec(int q_idx) const { +ModuleBase::Vector3 DFPT_PW_Data::get_qvec(int q_idx) const +{ return qlist_->get_q(q_idx); } -int DFPT_PW_Data::get_nirr(int q_idx) const { +int DFPT_PW_Data::get_nirr(int q_idx) const +{ return qlist_->get_nirr(q_idx); } -std::vector DFPT_PW_Data::get_irrep_modes(int q_idx, int irrep) const { +std::vector DFPT_PW_Data::get_irrep_modes(int q_idx, int irrep) const +{ return qlist_->get_irrep_modes(q_idx, irrep); } -void DFPT_PW_Data::set_dpsi(int q_idx, int k_idx, int band_idx, - const std::vector>& psi) { - if (q_idx < 0 || k_idx < 0 || band_idx < 0) { +void DFPT_PW_Data::set_dpsi(int q_idx, int k_idx, int band_idx, const std::vector>& psi) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_dpsi"); + ModuleBase::timer::start("DFPT_PW_Data", "set_dpsi"); + if (q_idx < 0 || k_idx < 0 || band_idx < 0) + { + ModuleBase::timer::end("DFPT_PW_Data", "set_dpsi"); return; } - if (q_idx >= static_cast(dpsi_.size())) { + if (q_idx >= static_cast(dpsi_.size())) + { dpsi_.resize(q_idx + 1); } - if (k_idx >= static_cast(dpsi_[q_idx].size())) { + if (k_idx >= static_cast(dpsi_[q_idx].size())) + { dpsi_[q_idx].resize(k_idx + 1); } - if (band_idx >= static_cast(dpsi_[q_idx][k_idx].size())) { + if (band_idx >= static_cast(dpsi_[q_idx][k_idx].size())) + { dpsi_[q_idx][k_idx].resize(band_idx + 1); } dpsi_[q_idx][k_idx][band_idx] = psi; + ModuleBase::timer::end("DFPT_PW_Data", "set_dpsi"); } -std::vector> DFPT_PW_Data::get_dpsi(int q_idx, int k_idx, int band_idx) const { - if (q_idx >= 0 && k_idx >= 0 && band_idx >= 0 && - q_idx < static_cast(dpsi_.size()) && - k_idx < static_cast(dpsi_[q_idx].size()) && - band_idx < static_cast(dpsi_[q_idx][k_idx].size())) +std::vector> DFPT_PW_Data::get_dpsi(int q_idx, int k_idx, int band_idx) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_dpsi"); + ModuleBase::timer::start("DFPT_PW_Data", "get_dpsi"); + if (q_idx >= 0 && k_idx >= 0 && band_idx >= 0 && q_idx < static_cast(dpsi_.size()) + && k_idx < static_cast(dpsi_[q_idx].size()) && band_idx < static_cast(dpsi_[q_idx][k_idx].size())) { + ModuleBase::timer::end("DFPT_PW_Data", "get_dpsi"); return dpsi_[q_idx][k_idx][band_idx]; } + ModuleBase::timer::end("DFPT_PW_Data", "get_dpsi"); return std::vector>(); } -void DFPT_PW_Data::set_converged(int q_idx, int irrep, bool flag) { +void DFPT_PW_Data::set_converged(int q_idx, int irrep, bool flag) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_converged"); + ModuleBase::timer::start("DFPT_PW_Data", "set_converged"); converged_[std::make_pair(q_idx, irrep)] = flag; + ModuleBase::timer::end("DFPT_PW_Data", "set_converged"); } -bool DFPT_PW_Data::get_converged(int q_idx, int irrep) const { +bool DFPT_PW_Data::get_converged(int q_idx, int irrep) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_converged"); + ModuleBase::timer::start("DFPT_PW_Data", "get_converged"); const auto it = converged_.find(std::make_pair(q_idx, irrep)); + ModuleBase::timer::end("DFPT_PW_Data", "get_converged"); return it != converged_.end() ? it->second : false; } -void DFPT_PW_Data::add_residual(int q_idx, int irrep, double r) { +void DFPT_PW_Data::add_residual(int q_idx, int irrep, double r) +{ + ModuleBase::TITLE("DFPT_PW_Data", "add_residual"); + ModuleBase::timer::start("DFPT_PW_Data", "add_residual"); residuals_[std::make_pair(q_idx, irrep)].push_back(r); + ModuleBase::timer::end("DFPT_PW_Data", "add_residual"); } -std::vector DFPT_PW_Data::get_residuals(int q_idx, int irrep) const { +std::vector DFPT_PW_Data::get_residuals(int q_idx, int irrep) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_residuals"); + ModuleBase::timer::start("DFPT_PW_Data", "get_residuals"); const auto it = residuals_.find(std::make_pair(q_idx, irrep)); + ModuleBase::timer::end("DFPT_PW_Data", "get_residuals"); return it != residuals_.end() ? it->second : std::vector(); } -void DFPT_PW_Data::set_current_iter(int q_idx, int irrep, int iter) { +void DFPT_PW_Data::set_current_iter(int q_idx, int irrep, int iter) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_current_iter"); + ModuleBase::timer::start("DFPT_PW_Data", "set_current_iter"); current_iter_[std::make_pair(q_idx, irrep)] = iter; + ModuleBase::timer::end("DFPT_PW_Data", "set_current_iter"); } -int DFPT_PW_Data::get_current_iter(int q_idx, int irrep) const { +int DFPT_PW_Data::get_current_iter(int q_idx, int irrep) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_current_iter"); + ModuleBase::timer::start("DFPT_PW_Data", "get_current_iter"); const auto it = current_iter_.find(std::make_pair(q_idx, irrep)); + ModuleBase::timer::end("DFPT_PW_Data", "get_current_iter"); return it != current_iter_.end() ? it->second : 0; } -void DFPT_PW_Data::set_drho_r(int q_idx, int spin, const std::vector& rho) { - if (q_idx < 0 || spin < 0) { return; } - if (q_idx >= static_cast(drho_r_.size())) { +void DFPT_PW_Data::set_drho_r(int q_idx, int spin, const std::vector& rho) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_drho_r"); + ModuleBase::timer::start("DFPT_PW_Data", "set_drho_r"); + if (q_idx < 0 || spin < 0) + { + ModuleBase::timer::end("DFPT_PW_Data", "set_drho_r"); + return; + } + if (q_idx >= static_cast(drho_r_.size())) + { drho_r_.resize(q_idx + 1); } - if (spin >= static_cast(drho_r_[q_idx].size())) { + if (spin >= static_cast(drho_r_[q_idx].size())) + { drho_r_[q_idx].resize(spin + 1); } drho_r_[q_idx][spin] = rho; + ModuleBase::timer::end("DFPT_PW_Data", "set_drho_r"); } -std::vector DFPT_PW_Data::get_drho_r(int q_idx, int spin) const { - if (q_idx >= 0 && spin >= 0 && - q_idx < static_cast(drho_r_.size()) && - spin < static_cast(drho_r_[q_idx].size())) +std::vector DFPT_PW_Data::get_drho_r(int q_idx, int spin) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_drho_r"); + ModuleBase::timer::start("DFPT_PW_Data", "get_drho_r"); + if (q_idx >= 0 && spin >= 0 && q_idx < static_cast(drho_r_.size()) + && spin < static_cast(drho_r_[q_idx].size())) { + ModuleBase::timer::end("DFPT_PW_Data", "get_drho_r"); return drho_r_[q_idx][spin]; } + ModuleBase::timer::end("DFPT_PW_Data", "get_drho_r"); return std::vector(); } -void DFPT_PW_Data::set_drho_g(int q_idx, int spin, const std::vector>& rho) { - if (q_idx < 0 || spin < 0) { return; } - if (q_idx >= static_cast(drho_g_.size())) { +void DFPT_PW_Data::set_drho_g(int q_idx, int spin, const std::vector>& rho) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_drho_g"); + ModuleBase::timer::start("DFPT_PW_Data", "set_drho_g"); + if (q_idx < 0 || spin < 0) + { + ModuleBase::timer::end("DFPT_PW_Data", "set_drho_g"); + return; + } + if (q_idx >= static_cast(drho_g_.size())) + { drho_g_.resize(q_idx + 1); } - if (spin >= static_cast(drho_g_[q_idx].size())) { + if (spin >= static_cast(drho_g_[q_idx].size())) + { drho_g_[q_idx].resize(spin + 1); } drho_g_[q_idx][spin] = rho; + ModuleBase::timer::end("DFPT_PW_Data", "set_drho_g"); } -std::vector> DFPT_PW_Data::get_drho_g(int q_idx, int spin) const { - if (q_idx >= 0 && spin >= 0 && - q_idx < static_cast(drho_g_.size()) && - spin < static_cast(drho_g_[q_idx].size())) +std::vector> DFPT_PW_Data::get_drho_g(int q_idx, int spin) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_drho_g"); + ModuleBase::timer::start("DFPT_PW_Data", "get_drho_g"); + if (q_idx >= 0 && spin >= 0 && q_idx < static_cast(drho_g_.size()) + && spin < static_cast(drho_g_[q_idx].size())) { + ModuleBase::timer::end("DFPT_PW_Data", "get_drho_g"); return drho_g_[q_idx][spin]; } + ModuleBase::timer::end("DFPT_PW_Data", "get_drho_g"); return std::vector>(); } -void DFPT_PW_Data::set_dv_r(int q_idx, int spin, const std::vector& v) { - if (q_idx < 0 || spin < 0) { return; } - if (q_idx >= static_cast(dv_r_.size())) { +void DFPT_PW_Data::set_dv_r(int q_idx, int spin, const std::vector& v) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_dv_r"); + ModuleBase::timer::start("DFPT_PW_Data", "set_dv_r"); + if (q_idx < 0 || spin < 0) + { + ModuleBase::timer::end("DFPT_PW_Data", "set_dv_r"); + return; + } + if (q_idx >= static_cast(dv_r_.size())) + { dv_r_.resize(q_idx + 1); } - if (spin >= static_cast(dv_r_[q_idx].size())) { + if (spin >= static_cast(dv_r_[q_idx].size())) + { dv_r_[q_idx].resize(spin + 1); } dv_r_[q_idx][spin] = v; + ModuleBase::timer::end("DFPT_PW_Data", "set_dv_r"); } -std::vector DFPT_PW_Data::get_dv_r(int q_idx, int spin) const { - if (q_idx >= 0 && spin >= 0 && - q_idx < static_cast(dv_r_.size()) && - spin < static_cast(dv_r_[q_idx].size())) +std::vector DFPT_PW_Data::get_dv_r(int q_idx, int spin) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_dv_r"); + ModuleBase::timer::start("DFPT_PW_Data", "get_dv_r"); + if (q_idx >= 0 && spin >= 0 && q_idx < static_cast(dv_r_.size()) + && spin < static_cast(dv_r_[q_idx].size())) { + ModuleBase::timer::end("DFPT_PW_Data", "get_dv_r"); return dv_r_[q_idx][spin]; } + ModuleBase::timer::end("DFPT_PW_Data", "get_dv_r"); return std::vector(); } -void DFPT_PW_Data::set_dv_recip_c(int q_idx, int spin, const std::vector>& v) { - if (q_idx < 0 || spin < 0) { return; } - if (q_idx >= static_cast(dv_recip_c_.size())) { +void DFPT_PW_Data::set_dv_recip_c(int q_idx, int spin, const std::vector>& v) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_dv_recip_c"); + ModuleBase::timer::start("DFPT_PW_Data", "set_dv_recip_c"); + if (q_idx < 0 || spin < 0) + { + ModuleBase::timer::end("DFPT_PW_Data", "set_dv_recip_c"); + return; + } + if (q_idx >= static_cast(dv_recip_c_.size())) + { dv_recip_c_.resize(q_idx + 1); } - if (spin >= static_cast(dv_recip_c_[q_idx].size())) { + if (spin >= static_cast(dv_recip_c_[q_idx].size())) + { dv_recip_c_[q_idx].resize(spin + 1); } dv_recip_c_[q_idx][spin] = v; + ModuleBase::timer::end("DFPT_PW_Data", "set_dv_recip_c"); } -std::vector> DFPT_PW_Data::get_dv_recip_c(int q_idx, int spin) const { - if (q_idx >= 0 && spin >= 0 && - q_idx < static_cast(dv_recip_c_.size()) && - spin < static_cast(dv_recip_c_[q_idx].size())) +std::vector> DFPT_PW_Data::get_dv_recip_c(int q_idx, int spin) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_dv_recip_c"); + ModuleBase::timer::start("DFPT_PW_Data", "get_dv_recip_c"); + if (q_idx >= 0 && spin >= 0 && q_idx < static_cast(dv_recip_c_.size()) + && spin < static_cast(dv_recip_c_[q_idx].size())) { + ModuleBase::timer::end("DFPT_PW_Data", "get_dv_recip_c"); return dv_recip_c_[q_idx][spin]; } + ModuleBase::timer::end("DFPT_PW_Data", "get_dv_recip_c"); return std::vector>(); } -void DFPT_PW_Data::set_dv_rc(int q_idx, int spin, const std::vector>& v) { - if (q_idx < 0 || spin < 0) { return; } - if (q_idx >= static_cast(dv_rc_.size())) { +void DFPT_PW_Data::set_dv_rc(int q_idx, int spin, const std::vector>& v) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_dv_rc"); + ModuleBase::timer::start("DFPT_PW_Data", "set_dv_rc"); + if (q_idx < 0 || spin < 0) + { + ModuleBase::timer::end("DFPT_PW_Data", "set_dv_rc"); + return; + } + if (q_idx >= static_cast(dv_rc_.size())) + { dv_rc_.resize(q_idx + 1); } - if (spin >= static_cast(dv_rc_[q_idx].size())) { + if (spin >= static_cast(dv_rc_[q_idx].size())) + { dv_rc_[q_idx].resize(spin + 1); } dv_rc_[q_idx][spin] = v; + ModuleBase::timer::end("DFPT_PW_Data", "set_dv_rc"); } -std::vector> DFPT_PW_Data::get_dv_rc(int q_idx, int spin) const { - if (q_idx >= 0 && spin >= 0 && - q_idx < static_cast(dv_rc_.size()) && - spin < static_cast(dv_rc_[q_idx].size())) +std::vector> DFPT_PW_Data::get_dv_rc(int q_idx, int spin) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_dv_rc"); + ModuleBase::timer::start("DFPT_PW_Data", "get_dv_rc"); + if (q_idx >= 0 && spin >= 0 && q_idx < static_cast(dv_rc_.size()) + && spin < static_cast(dv_rc_[q_idx].size())) { + ModuleBase::timer::end("DFPT_PW_Data", "get_dv_rc"); return dv_rc_[q_idx][spin]; } + ModuleBase::timer::end("DFPT_PW_Data", "get_dv_rc"); return std::vector>(); } -void DFPT_PW_Data::set_dynmat(int q_idx, const ModuleBase::ComplexMatrix& dm) { - if (q_idx >= static_cast(dynmat_.size())) { +void DFPT_PW_Data::set_dynmat(int q_idx, const ModuleBase::ComplexMatrix& dm) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_dynmat"); + ModuleBase::timer::start("DFPT_PW_Data", "set_dynmat"); + if (q_idx >= static_cast(dynmat_.size())) + { dynmat_.resize(q_idx + 1); } dynmat_[q_idx] = dm; + ModuleBase::timer::end("DFPT_PW_Data", "set_dynmat"); } -ModuleBase::ComplexMatrix DFPT_PW_Data::get_dynmat(int q_idx) const { - if (q_idx < static_cast(dynmat_.size())) { +ModuleBase::ComplexMatrix DFPT_PW_Data::get_dynmat(int q_idx) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_dynmat"); + ModuleBase::timer::start("DFPT_PW_Data", "get_dynmat"); + if (q_idx < static_cast(dynmat_.size())) + { + ModuleBase::timer::end("DFPT_PW_Data", "get_dynmat"); return dynmat_[q_idx]; } + ModuleBase::timer::end("DFPT_PW_Data", "get_dynmat"); return ModuleBase::ComplexMatrix(); } -void DFPT_PW_Data::set_phon_freq(int q_idx, const std::vector& freq) { - if (q_idx >= static_cast(phon_freq_.size())) { +void DFPT_PW_Data::set_phon_freq(int q_idx, const std::vector& freq) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_phon_freq"); + ModuleBase::timer::start("DFPT_PW_Data", "set_phon_freq"); + if (q_idx >= static_cast(phon_freq_.size())) + { phon_freq_.resize(q_idx + 1); } phon_freq_[q_idx] = freq; + ModuleBase::timer::end("DFPT_PW_Data", "set_phon_freq"); } -std::vector DFPT_PW_Data::get_phon_freq(int q_idx) const { - if (q_idx < static_cast(phon_freq_.size())) { +std::vector DFPT_PW_Data::get_phon_freq(int q_idx) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_phon_freq"); + ModuleBase::timer::start("DFPT_PW_Data", "get_phon_freq"); + if (q_idx < static_cast(phon_freq_.size())) + { + ModuleBase::timer::end("DFPT_PW_Data", "get_phon_freq"); return phon_freq_[q_idx]; } + ModuleBase::timer::end("DFPT_PW_Data", "get_phon_freq"); return std::vector(); } -void DFPT_PW_Data::set_loto_dir(const ModuleBase::Vector3& dir) { +void DFPT_PW_Data::set_loto_dir(const ModuleBase::Vector3& dir) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_loto_dir"); + ModuleBase::timer::start("DFPT_PW_Data", "set_loto_dir"); const double norm = std::sqrt(dir * dir); - if (norm < 1.0e-10) { + const double null_norm_tol = 1.0e-10; ///< empirical parameter: norm below which the direction input is null + if (norm < null_norm_tol) + { + ModuleBase::timer::end("DFPT_PW_Data", "set_loto_dir"); return; // keep the current direction on a null input } loto_dir_ = dir / norm; + ModuleBase::timer::end("DFPT_PW_Data", "set_loto_dir"); } -void DFPT_PW_Data::set_dielectric(const ModuleBase::matrix& eps) { +void DFPT_PW_Data::set_dielectric(const ModuleBase::matrix& eps) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_dielectric"); + ModuleBase::timer::start("DFPT_PW_Data", "set_dielectric"); dielectric_ = eps; + ModuleBase::timer::end("DFPT_PW_Data", "set_dielectric"); } -ModuleBase::matrix DFPT_PW_Data::get_dielectric() const { +ModuleBase::matrix DFPT_PW_Data::get_dielectric() const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_dielectric"); + ModuleBase::timer::start("DFPT_PW_Data", "get_dielectric"); + ModuleBase::timer::end("DFPT_PW_Data", "get_dielectric"); return dielectric_; } -void DFPT_PW_Data::set_born(int atom_idx, const ModuleBase::matrix& z) { - if (atom_idx >= static_cast(born_.size())) { +void DFPT_PW_Data::set_born(int atom_idx, const ModuleBase::matrix& z) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_born"); + ModuleBase::timer::start("DFPT_PW_Data", "set_born"); + if (atom_idx >= static_cast(born_.size())) + { born_.resize(atom_idx + 1); } born_[atom_idx] = z; + ModuleBase::timer::end("DFPT_PW_Data", "set_born"); } -ModuleBase::matrix DFPT_PW_Data::get_born(int atom_idx) const { - if (atom_idx < static_cast(born_.size())) { +ModuleBase::matrix DFPT_PW_Data::get_born(int atom_idx) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_born"); + ModuleBase::timer::start("DFPT_PW_Data", "get_born"); + if (atom_idx < static_cast(born_.size())) + { + ModuleBase::timer::end("DFPT_PW_Data", "get_born"); return born_[atom_idx]; } + ModuleBase::timer::end("DFPT_PW_Data", "get_born"); return ModuleBase::matrix(); } -void DFPT_PW_Data::allocate_memory() { +void DFPT_PW_Data::allocate_memory() +{ + ModuleBase::TITLE("DFPT_PW_Data", "allocate_memory"); + ModuleBase::timer::start("DFPT_PW_Data", "allocate_memory"); dynmat_.resize(get_nq()); phon_freq_.resize(get_nq()); born_.resize(nat_); + ModuleBase::timer::end("DFPT_PW_Data", "allocate_memory"); } -void DFPT_PW_Data::deallocate_memory() { +void DFPT_PW_Data::deallocate_memory() +{ + ModuleBase::TITLE("DFPT_PW_Data", "deallocate_memory"); + ModuleBase::timer::start("DFPT_PW_Data", "deallocate_memory"); dynmat_.clear(); phon_freq_.clear(); born_.clear(); @@ -401,6 +643,7 @@ void DFPT_PW_Data::deallocate_memory() { converged_.clear(); residuals_.clear(); current_iter_.clear(); + ModuleBase::timer::end("DFPT_PW_Data", "deallocate_memory"); } -} // namespace ModuleDFPT \ No newline at end of file +} // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_pw_data.h b/source/source_pw/module_dfpt/dfpt_pw_data.h index 6ad7656ec18..d1ea8dd6f1f 100644 --- a/source/source_pw/module_dfpt/dfpt_pw_data.h +++ b/source/source_pw/module_dfpt/dfpt_pw_data.h @@ -1,27 +1,21 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in the design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #ifndef DFPT_PW_DATA_H #define DFPT_PW_DATA_H -#include "source_base/matrix.h" #include "source_base/complexmatrix.h" +#include "source_base/matrix.h" #include "source_base/vector3.h" -#include "source_psi/psi.h" #include "source_cell/qlist.h" +#include "source_psi/psi.h" + +#include #include #include #include -#include class Plus_U_Base; -namespace ModuleDFPT { +namespace ModuleDFPT +{ /// Occupied-band classifier shared by the projector build, the Sternheimer /// driver, the response-density accumulation and the 2n+1 assembly. A band @@ -40,33 +34,39 @@ inline bool dfpt_band_occupied(const ModuleBase::matrix& wg, int ik, int ib) return wg(ik, ib) > 0.5 * wg(ik, 0); } -class DFPT_PW_Data { -public: +class DFPT_PW_Data +{ + public: DFPT_PW_Data(); ~DFPT_PW_Data(); - - void init(ModuleCell::QList* qlist, int nk, int nbands, int npw_max, - int nrxx, int nspin, int nat, const Plus_U_Base* dftu); - + + void init(ModuleCell::QList* qlist, + int nk, + int nbands, + int npw_max, + int nrxx, + int nspin, + int nat, + const Plus_U_Base* dftu); + void clean(); - + int get_nq() const; ModuleBase::Vector3 get_qvec(int q_idx) const; int get_nirr(int q_idx) const; std::vector get_irrep_modes(int q_idx, int irrep) const; - - void set_dpsi(int q_idx, int k_idx, int band_idx, - const std::vector>& psi); + + void set_dpsi(int q_idx, int k_idx, int band_idx, const std::vector>& psi); std::vector> get_dpsi(int q_idx, int k_idx, int band_idx) const; - + void set_drho_r(int q_idx, int spin, const std::vector& rho); std::vector get_drho_r(int q_idx, int spin) const; void set_drho_g(int q_idx, int spin, const std::vector>& rho); std::vector> get_drho_g(int q_idx, int spin) const; - + void set_dv_r(int q_idx, int spin, const std::vector& v); std::vector get_dv_r(int q_idx, int spin) const; - + /// First-order perturbation potential dV stored as complex plane-wave /// coefficients (indexed by the rho-grid ig) and as the corresponding /// complex real-space array on the shared FFT grid (C1). @@ -78,7 +78,7 @@ class DFPT_PW_Data { std::vector> get_dv_recip_c(int q_idx, int spin) const; void set_dv_rc(int q_idx, int spin, const std::vector>& v); std::vector> get_dv_rc(int q_idx, int spin) const; - + /// The dynamical matrix at a generic q is complex Hermitian; stored as a /// ModuleBase::ComplexMatrix (C5), consumed by DFPT_Phon::diagonalize /// through the LapackConnector::zheev wrapper. @@ -86,48 +86,105 @@ class DFPT_PW_Data { ModuleBase::ComplexMatrix get_dynmat(int q_idx) const; void set_phon_freq(int q_idx, const std::vector& freq); std::vector get_phon_freq(int q_idx) const; - + void set_dielectric(const ModuleBase::matrix& eps); ModuleBase::matrix get_dielectric() const; void set_born(int atom_idx, const ModuleBase::matrix& z); ModuleBase::matrix get_born(int atom_idx) const; - - void set_compute_q0(bool flag) { compute_q0_ = flag; } - bool get_compute_q0() const { return compute_q0_; } - void set_loto(bool flag) { loto_ = flag; } - bool get_loto() const { return loto_; } + + void set_compute_q0(bool flag) + { + compute_q0_ = flag; + } + bool get_compute_q0() const + { + return compute_q0_; + } + void set_loto(bool flag) + { + loto_ = flag; + } + bool get_loto() const + { + return loto_; + } /// q->0 direction of the non-analytic (LO-TO) term, as a unit vector. /// The setter normalizes; a null vector falls back to the isotropic /// default (1,1,1)/sqrt(3) (documented cubic-crystal default; a general /// direction control arrives with the irrep machinery of stage A). void set_loto_dir(const ModuleBase::Vector3& dir); - ModuleBase::Vector3 get_loto_dir() const { return loto_dir_; } + ModuleBase::Vector3 get_loto_dir() const + { + return loto_dir_; + } /// signed Gamma frequencies (cm^-1) after the non-analytic LO-TO term /// along loto_dir_; empty until add_loto + diagonalize_loto have run - void set_phon_freq_loto(const std::vector& freq) { phon_freq_loto_ = freq; } - std::vector get_phon_freq_loto() const { return phon_freq_loto_; } + void set_phon_freq_loto(const std::vector& freq) + { + phon_freq_loto_ = freq; + } + std::vector get_phon_freq_loto() const + { + return phon_freq_loto_; + } /// The perturbation currently being solved: displacement of which linear /// atom index (over all atoms) and along which cartesian direction. /// Set by DFPT_Pert::build_dv and consumed by DFPT_Pert::apply_dv so the /// Stern solver can keep applying the same perturbation per irrep without /// re-passing (atom,dir) on every matrix-vector product. - void set_pert_atom(int atom_idx) { pert_atom_ = atom_idx; } - int get_pert_atom() const { return pert_atom_; } - void set_pert_dir(int dir) { pert_dir_ = dir; } - int get_pert_dir() const { return pert_dir_; } - - void set_is_metal(bool flag) { is_metal_ = flag; } - bool get_is_metal() const { return is_metal_; } - void set_dmu(double dmu) { dmu_ = dmu; } - double get_dmu() const { return dmu_; } - - void set_max_iter(int iter) { max_iter_ = iter; } - int get_max_iter() const { return max_iter_; } - void set_conv_thr(double thr) { conv_thr_ = thr; } - double get_conv_thr() const { return conv_thr_; } + void set_pert_atom(int atom_idx) + { + pert_atom_ = atom_idx; + } + int get_pert_atom() const + { + return pert_atom_; + } + void set_pert_dir(int dir) + { + pert_dir_ = dir; + } + int get_pert_dir() const + { + return pert_dir_; + } + + void set_is_metal(bool flag) + { + is_metal_ = flag; + } + bool get_is_metal() const + { + return is_metal_; + } + void set_dmu(double dmu) + { + dmu_ = dmu; + } + double get_dmu() const + { + return dmu_; + } + + void set_max_iter(int iter) + { + max_iter_ = iter; + } + int get_max_iter() const + { + return max_iter_; + } + void set_conv_thr(double thr) + { + conv_thr_ = thr; + } + double get_conv_thr() const + { + return conv_thr_; + } /// Per-(q, irrep) SCF convergence ledger (B4: sunk from the retired /// DFPT_IrrepData adapter). The irrep dimension is a stage-A slot: @@ -141,7 +198,7 @@ class DFPT_PW_Data { std::vector get_residuals(int q_idx, int irrep) const; void set_current_iter(int q_idx, int irrep, int iter); int get_current_iter(int q_idx, int irrep) const; - + /// DFT+U interface reservation (U0): /// the DFPT modules never read global input state directly; the esolver /// layer decides whether DFT+U is active and passes a non-null @@ -150,10 +207,16 @@ class DFPT_PW_Data { /// u_active(): the provider is additionally usable (occupation matrices /// initialized, which the ground state does when DFT+U /// actually runs; a provider without them stays inactive). - bool with_u() const { return dftu_ != nullptr; } + bool with_u() const + { + return dftu_ != nullptr; + } bool u_active() const; - const Plus_U_Base* get_dftu() const { return dftu_; } - + const Plus_U_Base* get_dftu() const + { + return dftu_; + } + /// first-order occupation matrix (docc) storage, indexed by q. /// lazy allocation: unset / out-of-range reads return an empty vector. void set_docc(int q_idx, const std::vector>& occ); @@ -164,77 +227,67 @@ class DFPT_PW_Data { /// Sternheimer iteration of that displacement. The 2n+1 accumulation /// needs it to complete the term2 cross section /// 2 (screening channel). - void set_vsc_r(int atom_idx, int dir, - const std::vector>& v); + void set_vsc_r(int atom_idx, int dir, const std::vector>& v); std::vector> get_vsc_r(int atom_idx, int dir) const; /// converged dpsi of displacement (atom, dir), indexed [k][band]; the /// two-pass 2n+1 accumulation reads it back after all displacements of /// the basis have been solved (the working dpsi slots get overwritten by /// later solves). - void set_dpsi_disp(int atom_idx, int dir, - const std::vector>>>& d); - std::vector>>> - get_dpsi_disp(int atom_idx, int dir) const; + void set_dpsi_disp(int atom_idx, int dir, const std::vector>>>& d); + std::vector>>> get_dpsi_disp(int atom_idx, int dir) const; /// conduction-projected position operator P_c r_dir |u_(k,band)> of the /// q = 0 mesh, solved exactly as a linear response ((H - eps_band) Y = /// -(i/tpiba) dH/dk_dir |u>), indexed [dir][k][band]; the screened Born /// charge contraction avoids the empty-eigenvector /// truncation of the explicit r-matrix sum - void set_pos_resp(int dir, - const std::vector>>>& y); - std::vector>>> - get_pos_resp(int dir) const; + void set_pos_resp(int dir, const std::vector>>>& y); + std::vector>>> get_pos_resp(int dir) const; /// converged screened E-field response dpsi^E(dir) of the q = 0 mesh /// (QE solve_e + dfpt_kernel fixed point on the rhs /// -(Y^dir + dV_sc^E|psi>)), indexed [dir][k][band] - void set_dpsi_efield( - int dir, - const std::vector>>>& d); - std::vector>>> - get_dpsi_efield(int dir) const; + void set_dpsi_efield(int dir, const std::vector>>>& d); + std::vector>>> get_dpsi_efield(int dir) const; -private: + private: ModuleCell::QList* qlist_ = nullptr; - + int nk_ = 0; int nbands_ = 0; int npw_max_ = 0; int nrxx_ = 0; int nspin_ = 1; int nat_ = 0; - + /// first-order wavefunction response, indexed [q][k][band]; each entry is /// the dpsi on the k+q basis for that band (a vector of complex coefficients). std::vector>>>> dpsi_; - + std::vector>> drho_r_; std::vector>>> drho_g_; - + std::vector>> dv_r_; - + std::vector>>> dv_recip_c_; std::vector>>> dv_rc_; - + std::vector dynmat_; std::vector> phon_freq_; - + bool compute_q0_ = false; bool loto_ = false; - ModuleBase::Vector3 loto_dir_{1.0 / std::sqrt(3.0), - 1.0 / std::sqrt(3.0), - 1.0 / std::sqrt(3.0)}; + ModuleBase::Vector3 loto_dir_{1.0 / std::sqrt(3.0), 1.0 / std::sqrt(3.0), 1.0 / std::sqrt(3.0)}; std::vector phon_freq_loto_; int pert_atom_ = -1; int pert_dir_ = -1; ModuleBase::matrix dielectric_; std::vector born_; - + bool is_metal_ = false; double dmu_ = 0.0; - + /// DFT+U reservation state (U0) const Plus_U_Base* dftu_ = nullptr; std::vector>> docc_; @@ -250,9 +303,8 @@ class DFPT_PW_Data { std::vector>>>> pos_resp_; /// converged E-field response dpsi^E per direction: [3][k][band] - std::vector>>>> - dpsi_efield_; - + std::vector>>>> dpsi_efield_; + int max_iter_ = 100; double conv_thr_ = 1e-8; @@ -260,13 +312,13 @@ class DFPT_PW_Data { std::map, bool> converged_; std::map, std::vector> residuals_; std::map, int> current_iter_; - + bool is_initialized_ = false; - + void allocate_memory(); void deallocate_memory(); }; } // namespace ModuleDFPT -#endif // DFPT_PW_DATA_H \ No newline at end of file +#endif // DFPT_PW_DATA_H diff --git a/source/source_pw/module_dfpt/dfpt_pw_impl.h b/source/source_pw/module_dfpt/dfpt_pw_impl.h new file mode 100644 index 00000000000..7448034d703 --- /dev/null +++ b/source/source_pw/module_dfpt/dfpt_pw_impl.h @@ -0,0 +1,184 @@ +#ifndef DFPT_PW_IMPL_H +#define DFPT_PW_IMPL_H + +#include "dfpt_hamilt_shift.h" +#include "dfpt_kq_basis.h" +#include "dfpt_metal.h" +#include "dfpt_pert.h" +#include "dfpt_phon.h" +#include "dfpt_pw.h" +#include "dfpt_pw_data.h" +#include "dfpt_q0.h" +#include "dfpt_rho.h" +#include "dfpt_stern.h" +#include "source_base/matrix.h" +#include "source_base/vector3.h" +#include "source_cell/qlist.h" +#include "source_cell/unitcell.h" +#include "source_psi/psi.h" + +#include +#include +#include + +namespace ModulePW +{ +class PW_Basis; +class PW_Basis_K; +} // namespace ModulePW + +class Plus_U_Base; +class Structure_Factor; + +namespace ModuleDFPT +{ + +class XC_First_Order; + +/// Private implementation body of DFPT_PW (opaque in the public header). +/// +/// Only the constructors / destructor and the outward-facing per-solve +/// hooks are public; all data members and per-solve helpers are private. +class DFPT_PW::Impl +{ + public: + Impl(); + ~Impl(); + + /// occupied-state projector set at k+q for every k of this q + /// (commensurate q: kvec_d[ik] + q must be a k point of the + /// ground-state list mod lattice) + void build_occ_kq(int q_idx); + + /// one self-consistent Sternheimer cycle for the displacement + /// (iat, idir) at q; returns the achieved density residual (zero + /// when unwired) + double solve_displacement(int q_idx, int iat, int idir); + + /// position legs Y^a_{k,v} = P_c x_a|psi_{k,v}> of the q = 0 mesh + void solve_pos_resp(int q_idx); + + /// E-field SCF response dpsi^E,a of the q = 0 mesh + void solve_efield_resp(int q_idx); + + /// per-iteration assembly of the screened response potential + /// v_sc^q = v_Hartree(drho_in) + xc(drho_in) on the shared grid; + /// shared by solve_displacement and solve_efield_resp so the + /// real-space Hartree+XC add loop is not duplicated + void assemble_v_sc(const ModuleBase::Vector3& q_cart, + const std::vector>& drho_in_g, + std::vector>& v_sc_r) const; + + private: + friend class DFPT_PW; + + // ----- initialisation helpers (dfpt_pw_init.cpp) ----- + void check_metallic_occ(const ModuleBase::matrix& wg) const; + void setup_q_list(UnitCell& ucell); + void init_submodules(const DFPT_PW_InitContext& ctx, + int nk, + int nbands, + int npw_max, + int nrxx, + int nspin, + int nat); + + // ----- build_occ_kq helpers (dfpt_pw_init.cpp) ----- + int match_commensurate_kq(int ik, + const ModuleBase::Vector3& q_frac, + double tol, + ModuleBase::Vector3& dn_out) const; + void copy_occ_state_ball(int ik, + int ikq, + const ModuleBase::Vector3& dn, + const DFPT_KQ_Basis& kq, + const ModuleBase::Matrix3& ginv, + std::vector>>& occ_ik) const; + + // ----- run() dispatch helpers (dfpt_pw_run.cpp) ----- + void run_q0_pre(int q_idx); + double run_displacement_irrep_pass(int q_idx, int irrep); + void run_q0_post(int q_idx); + void run_assemble(int q_idx); + + // ----- solve_displacement helpers (dfpt_pw_solve.cpp) ----- + double sternheimer_per_band(int ik, + int ib, + const std::vector>>& dv_sc, + int nbands, + int lin_max, + double lin_thr); + void stash_converged_disp_response(int q_idx, + int iat, + int idir, + const std::vector>& v_sc_r_last, + int nk, + int nbands); + + // ----- q=0 solve helpers (dfpt_pw_q0.cpp) ----- + void vel_diag_part(int ik, int a, int nbands, std::vector>>& vel) const; + void vel_nl_per_atom(int ik, + int a, + int it, + int ia, + int nbands, + const std::vector>& gk, + std::vector>>& vel); + void pos_per_band_solve(int ik, + int a, + int nbands, + int lin_max, + double lin_thr, + std::vector>>>& yvec); + void efield_per_band_solve(int ik, + int a, + int nbands, + int lin_max, + double lin_thr, + const std::vector>>>& yr, + const std::vector>>& dv_sc); + void stash_dpsi_efield(int q_idx, int a, int nk, int nbands); + + bool wired() const; + + DFPT_PW_Data data_; + DFPT_Pert pert_; + DFPT_Stern stern_; + DFPT_Rho rho_; + DFPT_Phon phon_; + DFPT_Q0 q0_; + DFPT_Metal metal_; + ModuleCell::QList qlist_; + std::unique_ptr hamilt_; + + psi::Psi> gs_psi_; + UnitCell* ucell_ = nullptr; + ModulePW::PW_Basis* pw_rho_ = nullptr; + ModulePW::PW_Basis_K* pw_wfc_ = nullptr; + Structure_Factor* sf_ = nullptr; + std::vector veff_r_; + ModuleBase::matrix wg_; + ModuleBase::matrix eig_; + const XC_First_Order* xc_ = nullptr; + double nelec_ = 0.0; + double ecutwfc_ = 0.0; + const Plus_U_Base* dftu_ = nullptr; + + ///< occupied states at k+q on the k+q G list, [ik][occ m][igl]; + ///< rebuilt per q (they depend on q and k only) + std::vector>>> occ_kq_; + ///< remembers the (q_idx, ik) the shifted operator was last cached at + int last_q_ = -1; + int last_ik_ = -1; + std::vector ikq_of_k_; + + int nqx_ = 1, nqy_ = 1, nqz_ = 1; + std::string qfile_; + double conv_thr_ = 1e-8; + int max_iter_ = 100; + double mix_beta_ = 0.4; +}; + +} // namespace ModuleDFPT + +#endif // DFPT_PW_IMPL_H diff --git a/source/source_pw/module_dfpt/dfpt_pw_init.cpp b/source/source_pw/module_dfpt/dfpt_pw_init.cpp new file mode 100644 index 00000000000..0b175c0075a --- /dev/null +++ b/source/source_pw/module_dfpt/dfpt_pw_init.cpp @@ -0,0 +1,299 @@ +// ============================================================ +// DFPT_PW::init + DFPT_PW::Impl::build_occ_kq implementation +// with helper extraction, moved from dfpt_pw.cpp so the driver +// translation unit stays below the coding-rule 500-line budget. +// ============================================================ + +#include "dfpt_pw_impl.h" + +#include "dfpt_kq_basis.h" +#include "dfpt_pert.h" +#include "dfpt_phon.h" +#include "dfpt_pw_data.h" +#include "dfpt_q0.h" +#include "dfpt_rho.h" +#include "source_base/timer.h" +#include "source_base/tool_title.h" + +#include +#include +#include +#include +#include + +namespace ModuleDFPT +{ + +void DFPT_PW::Impl::check_metallic_occ(const ModuleBase::matrix& wg) const +{ + // Metallic-sampling guard: the Sternheimer/projector flow treats every + // band as either fully occupied or empty and carries no d(mu)/dtau + // response, so a sampling whose smearing Fermi level cuts a band (wg + // strictly between 0 and the full reference) yields force constants + // wrong at the 100% level while still converging cleanly. Reject it + // explicitly (C4 defers metallic DFPT); negligible gauss tails + // (relative weight < 1e-3) are tolerated as the insulator limit. + const double frac_weight_tol = 1.0e-3; ///< empirical parameter: relative band weight treated as metallic + for (int ik = 0; ik < wg.nr; ++ik) + { + const double wref = wg(ik, 0); + if (wref <= 0.0) + { + continue; + } + for (int ib = 0; ib < wg.nc; ++ib) + { + const double rel = wg(ik, ib) / wref; + if (rel > frac_weight_tol && rel < 1.0 - frac_weight_tol) + { + std::stringstream msg; + msg << "fractional band occupation at (ik=" << ik << ", ib=" << ib << ", wg=" << wg(ik, ib) + << "): metallic DFPT (smearing occupations crossing the" + " Fermi level) is not supported; reduce smearing sigma" + " or use an insulating k sampling."; + ModuleBase::WARNING_QUIT("DFPT_PW::init", msg.str()); + } + } + } +} + +void DFPT_PW::Impl::setup_q_list(UnitCell& ucell) +{ + if (!qfile_.empty()) + { + qlist_.read_from_file(qfile_, ucell); + if (qlist_.get_nq() == 0) + { + ModuleBase::WARNING_QUIT("DFPT_PW::init", "failed to read the DFPT q-point file: " + qfile_); + } + } + else + { + std::vector mp_grid = {nqx_, nqy_, nqz_}; + qlist_.generate_mesh(ucell, ucell.symm, mp_grid, true); + } +} + +void DFPT_PW::Impl::init_submodules(const DFPT_PW_InitContext& ctx, + int nk, + int nbands, + int npw_max, + int nrxx, + int nspin, + int nat) +{ + // plain-mixing coefficient: the response Jacobian has strongly + // negative eigenvalues concentrated on the smallest-G shells (the + // Coulomb stiffness 4pi/G^2; measured lambda ~ -2.2 on {111}/{200} + // for the diamond smoke case), so the coefficient must stay below + // 2 / (1 + |lambda_min|); the INPUT default 0.4 keeps margin up to + // |lambda| ~ 3; the alternative is mix_type = "kerker", the screen + // f_g = |G+q|^2 / (|G+q|^2 + a^2) in 1/lat0^2 units (a^2 via + // DFPT_KERKER_A2), which stabilizes those shells at beta up to 1; + // the env knobs are design-phase calibration aids + double mix_beta = mix_beta_; + if (const char* env_beta = getenv("DFPT_MIX_BETA")) + { + const double parsed = atof(env_beta); + if (parsed > 0.0 && parsed <= 1.0) + { + mix_beta = parsed; + } + } + std::string mix_type = "plain"; + if (const char* env_type = getenv("DFPT_MIX_TYPE")) + { + const std::string parsed = env_type; + if (parsed == "plain" || parsed == "kerker") + { + mix_type = parsed; + } + } + double kerker_a2 = 1.0; + if (const char* env_a2 = getenv("DFPT_KERKER_A2")) + { + const double parsed = atof(env_a2); + if (parsed > 0.0) + { + kerker_a2 = parsed; + } + } + rho_.init({nspin, nrxx, ctx.pw_rho, ctx.pw_wfc, ctx.ucell->G, mix_type, mix_beta, kerker_a2}); + phon_.init(*ctx.ucell, ctx.pw_rho, &pert_); + q0_.init(*ctx.ucell, ctx.pw_rho, ctx.pw_wfc, &pert_); + hamilt_.reset(new DFPT_HamiltShift(*ctx.ucell, ctx.pw_rho, ctx.pw_wfc, *ctx.veff_r, &pert_)); + data_.init(&qlist_, nk, nbands, npw_max, nrxx, nspin, nat, ctx.dftu); +} + +void DFPT_PW::init(const DFPT_PW_InitContext& ctx) +{ + ModuleBase::TITLE("DFPT_PW", "init"); + ModuleBase::timer::start("DFPT_PW", "init"); + // ctx.{psi,wg,veff_r,eig} are valid non-null pointers even in skeleton + // mode: the empty Psi / matrix objects still carry the queryable nk / + // nbands / nrxx shape fields consumed by the init helpers; ucell is + // always a non-null per the public wrapper (it takes a reference). + UnitCell* const ucell = ctx.ucell; + const psi::Psi>* const psi = ctx.psi; + ModulePW::PW_Basis* const pw_rho = ctx.pw_rho; + ModulePW::PW_Basis_K* const pw_wfc = ctx.pw_wfc; + Structure_Factor* const sf = ctx.sf; + + pimpl_->ucell_ = ucell; + pimpl_->gs_psi_ = *psi; + pimpl_->pw_rho_ = pw_rho; + pimpl_->pw_wfc_ = pw_wfc; + pimpl_->sf_ = sf; + pimpl_->veff_r_ = *ctx.veff_r; + pimpl_->wg_ = *ctx.wg; + pimpl_->eig_ = *ctx.eig; + pimpl_->xc_ = ctx.xc; + pimpl_->nelec_ = ctx.nelec; + pimpl_->ecutwfc_ = ctx.ecutwfc; + pimpl_->dftu_ = ctx.dftu; + + pimpl_->check_metallic_occ(*ctx.wg); + + // DFT+U guard: the ground state now supports PW-basis DFT+U and wires a + // provider when dft_plus_u is enabled, but every DFPT U hook + // (DFPT_Rho::cal_docc, DFPT_Pert::build_dv_u, DFPT_Q0 born/docc + // contractions, DFPT_Phon::dftu_onsite) is a no-op reservation (U0). + // Running anyway would converge cleanly while silently dropping the + // whole first-order U response, so reject explicitly until U1 lands + // (same fail-loud pattern as the metallic-sampling guard above). + if (ctx.dftu != nullptr) + { + ModuleBase::WARNING_QUIT("DFPT_PW::init", + "DFT+U with DFPT is not supported yet: the " + "first-order U response is not implemented " + "(U0 reservation); rerun with dft_plus_u 0."); + } + + pimpl_->setup_q_list(*ucell); + + const int nk = psi->get_nk(); + const int nbands = psi->get_nbands(); + const int npw_max = psi->get_current_ngk(); + const int nrxx = (pw_rho != nullptr) ? pw_rho->nrxx : 0; + const int nspin = 1; + const int nat = ucell->nat; + + if (pw_rho != nullptr && pw_wfc != nullptr && sf != nullptr) + { + pimpl_->pert_.init(*ucell, pw_rho, pw_wfc, *sf); + pimpl_->init_submodules(ctx, nk, nbands, npw_max, nrxx, nspin, nat); + } + else + { + pimpl_->phon_.init(*ucell, nullptr, nullptr); + pimpl_->data_.init(&pimpl_->qlist_, nk, nbands, npw_max, nrxx, nspin, nat, ctx.dftu); + } + ModuleBase::timer::end("DFPT_PW", "init"); +} + +int DFPT_PW::Impl::match_commensurate_kq(int ik, + const ModuleBase::Vector3& q_frac, + double tol, + ModuleBase::Vector3& dn_out) const +{ + // k+q folded into [0,1) direct coordinates must be a ground-state k + // point (DFPT q meshes are commensurate with the k mesh) + const ModuleBase::Vector3 target = pw_wfc_->kvec_d[ik] + q_frac; + const int nk = pw_wfc_->nks; + for (int j = 0; j < nk; ++j) + { + const ModuleBase::Vector3& kj = pw_wfc_->kvec_d[j]; + const double rx = std::round(kj.x - target.x); + const double ry = std::round(kj.y - target.y); + const double rz = std::round(kj.z - target.z); + if (std::abs(kj.x - target.x - rx) < tol && std::abs(kj.y - target.y - ry) < tol + && std::abs(kj.z - target.z - rz) < tol) + { + dn_out.x = static_cast(rx); + dn_out.y = static_cast(ry); + dn_out.z = static_cast(rz); + return j; + } + } + std::ostringstream oss; + oss << "k+q is not a point of the ground-state k list: the DFPT " + "q mesh must be commensurate with the k mesh (and inside " + "the first Brillouin zone). ik=" + << ik << " k_d=(" << pw_wfc_->kvec_d[ik].x << "," << pw_wfc_->kvec_d[ik].y << "," + << pw_wfc_->kvec_d[ik].z << ") q_d=(" << q_frac.x << "," << q_frac.y << "," << q_frac.z << ") k+q=(" + << target.x << "," << target.y << "," << target.z << ") nk=" << nk; + ModuleBase::WARNING_QUIT("DFPT_PW::build_occ_kq", oss.str()); +} + +void DFPT_PW::Impl::copy_occ_state_ball(int ik, + int ikq, + const ModuleBase::Vector3& dn, + const DFPT_KQ_Basis& kq, + const ModuleBase::Matrix3& ginv, + std::vector>>& occ_ik) const +{ + // reciprocal-basis integer triple -> per-k index of the ikq ball + // (pw_wfc_ is a PW_Basis_K whose gcar holds a per-k ball layout, + // not the parent-class global-ig layout: read it through getgcar) + std::map, int> jgl_of_n; + for (int jgl = 0; jgl < pw_wfc_->npwk[ikq]; ++jgl) + { + const ModuleBase::Vector3 gf = pw_wfc_->getgcar(ikq, jgl) * ginv; + const std::vector key = {static_cast(std::round(gf.x)), + static_cast(std::round(gf.y)), + static_cast(std::round(gf.z))}; + jgl_of_n[key] = jgl; + } + + const int npw_kq = kq.get_npwk(); + const int nbands = gs_psi_.get_nbands(); + for (int m = 0; m < nbands; ++m) + { + if (!dfpt_band_occupied(wg_, ikq, m)) + { + continue; // empty at k+q: outside the P_c projector + } + std::vector> state(npw_kq, std::complex(0.0, 0.0)); + for (int igl = 0; igl < npw_kq; ++igl) + { + const ModuleBase::Vector3 gf = kq.get_gcar(igl) * ginv; + const std::vector key = {static_cast(std::round(gf.x)) + dn.x, + static_cast(std::round(gf.y)) + dn.y, + static_cast(std::round(gf.z)) + dn.z}; + const auto it = jgl_of_n.find(key); + if (it != jgl_of_n.end()) + { + state[igl] = gs_psi_(ikq, m, it->second); + } + } + occ_ik.push_back(std::move(state)); + } +} + +void DFPT_PW::Impl::build_occ_kq(int q_idx) +{ + ModuleBase::TITLE("DFPT_PW", "build_occ_kq"); + ModuleBase::timer::start("DFPT_PW", "build_occ_kq"); + const int nk = pw_wfc_->nks; + occ_kq_.assign(nk, std::vector>>()); + ikq_of_k_.assign(nk, -1); + const ModuleBase::Vector3 q_frac = data_.get_qvec(q_idx); + const ModuleBase::Vector3 q_cart = q_frac * ucell_->G; + const double kmatch_tol = 1.0e-6; ///< empirical parameter: folded fractional k-list match tolerance + for (int ik = 0; ik < nk; ++ik) + { + ModuleBase::Vector3 dn(0, 0, 0); + const int ikq = match_commensurate_kq(ik, q_frac, kmatch_tol, dn); + ikq_of_k_[ik] = ikq; + + DFPT_KQ_Basis kq; + kq.init(pw_wfc_, pw_rho_, q_cart, ik); + const ModuleBase::Matrix3 ginv = pw_wfc_->G.Inverse(); + copy_occ_state_ball(ik, ikq, dn, kq, ginv, occ_kq_[ik]); + } + last_q_ = q_idx; + last_ik_ = -1; + ModuleBase::timer::end("DFPT_PW", "build_occ_kq"); +} + +} // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_pw_q0.cpp b/source/source_pw/module_dfpt/dfpt_pw_q0.cpp new file mode 100644 index 00000000000..39dba1d3f8d --- /dev/null +++ b/source/source_pw/module_dfpt/dfpt_pw_q0.cpp @@ -0,0 +1,353 @@ +// ============================================================ +// DFPT_PW::Impl::solve_pos_resp and solve_efield_resp +// implementations (q=0 legs). The position response Y^a solves the +// velocity-form commutator Sternheimer equation (Giannozzi et al. +// 1991 QE dvpsi_e), and the e-field SCF response reuses the shared +// Impl::assemble_v_sc from dfpt_pw_solve.cpp. Both large drivers +// are split into small private helpers so the new translation unit +// stays well under the coding-rule 500-line budget and each helper +// function remains under cyclomatic complexity 10. +// ============================================================ + +#include "dfpt_pw_impl.h" + +#include "dfpt_pw_data.h" +#include "source_base/timer.h" +#include "source_base/tool_title.h" + +#include +#include +#include +#include +#include + +namespace ModuleDFPT +{ + +void DFPT_PW::Impl::vel_diag_part(int ik, + int a, + int nbands, + std::vector>>& vel) const +{ + // dH/dk_a |psi> diagonal kinetic part: + // - d/dk_a ( (hbar^2/2m) (k+G)^2 ) = hbar^2 (k+G)_a, + // re-expressed in ABACUS Rydberg units via tpiba^2. + const double tpiba2 = ucell_->tpiba * ucell_->tpiba; + const int npwk = pw_wfc_->npwk[ik]; + vel.assign(nbands, std::vector>(npwk, std::complex(0.0, 0.0))); + for (int ib = 0; ib < nbands; ++ib) + { + for (int ig = 0; ig < npwk; ++ig) + { + const ModuleBase::Vector3 gk = pw_wfc_->getgpluskcar(ik, ig); + vel[ib][ig] = 2.0 * tpiba2 * gk[a] * gs_psi_(ik, ib, ig); + } + } +} + +void DFPT_PW::Impl::vel_nl_per_atom(int ik, + int a, + int it, + int ia, + int nbands, + const std::vector>& gk, + std::vector>>& vel) +{ + const pseudo& ncpp = ucell_->atoms[it].ncpp; + const int nh = ncpp.nh; + if (nh == 0) + { + return; + } + const int npwk = pw_wfc_->npwk[ik]; + // projector -> (radial beta index, m channel) table + std::vector mu_ib(nh, 0); + std::vector mu_m(nh, 0); + int mu_idx = 0; + for (int ib = 0; ib < ncpp.nbeta; ++ib) + { + const int l = ncpp.lll[ib]; + for (int m = 0; m < 2 * l + 1; ++m) + { + if (mu_idx < nh) + { + mu_ib[mu_idx] = ib; + mu_m[mu_idx] = m; + } + ++mu_idx; + } + } + std::vector>> vkb; + pert_.build_vkb(it, ia, gk, vkb); + // becp_b[mu] = + std::vector>> becp(nbands); + for (int b = 0; b < nbands; ++b) + { + becp[b].assign(nh, std::complex(0.0, 0.0)); + for (int mu = 0; mu < nh; ++mu) + { + for (int ig = 0; ig < npwk; ++ig) + { + becp[b][mu] += std::conj(vkb[mu][ig]) * gs_psi_(ik, b, ig); + } + } + } + std::vector>> dvkb; + pert_.build_vkb_dk(it, ia, a, gk, vkb, dvkb); + // dbecp_b[mu] = + std::vector>> dbecp(nbands); + for (int b = 0; b < nbands; ++b) + { + dbecp[b].assign(nh, std::complex(0.0, 0.0)); + for (int mu = 0; mu < nh; ++mu) + { + for (int ig = 0; ig < npwk; ++ig) + { + dbecp[b][mu] += std::conj(dvkb[mu][ig]) * gs_psi_(ik, b, ig); + } + } + } + // dV_nl/dk_a|psi_b> = sum_mu |dvkb_mu> (D becp_b)_mu + // + |vkb_mu> (D dbecp_b)_mu + for (int b = 0; b < nbands; ++b) + { + for (int mu = 0; mu < nh; ++mu) + { + std::complex out_b(0.0, 0.0); + std::complex in_b(0.0, 0.0); + for (int nu = 0; nu < nh; ++nu) + { + if (mu_m[mu] != mu_m[nu]) + { + continue; + } + const double dij = ncpp.dion(mu_ib[mu], mu_ib[nu]); + out_b += dij * becp[b][nu]; + in_b += dij * dbecp[b][nu]; + } + for (int ig = 0; ig < npwk; ++ig) + { + vel[b][ig] += dvkb[mu][ig] * out_b + vkb[mu][ig] * in_b; + } + } + } +} + +void DFPT_PW::Impl::pos_per_band_solve(int ik, + int a, + int nbands, + int lin_max, + double lin_thr, + std::vector>>>& yvec) +{ + const bool dbg = (getenv("DFPT_DEBUG") != nullptr); + const double tpiba = ucell_->tpiba; + const int npwk = pw_wfc_->npwk[ik]; + std::vector>> vel(nbands); + vel_diag_part(ik, a, nbands, vel); + for (int it = 0; it < ucell_->ntype; ++it) + { + for (int ia = 0; ia < ucell_->atoms[it].na; ++ia) + { + std::vector> gk(npwk); + for (int ig = 0; ig < npwk; ++ig) + { + gk[ig] = pw_wfc_->getgpluskcar(ik, ig); + } + vel_nl_per_atom(ik, a, it, ia, nbands, gk, vel); + } + } + // solve (H - eps_v) Y = -(i/tpiba) vel for every occupied band + for (int ib = 0; ib < nbands; ++ib) + { + if (!dfpt_band_occupied(wg_, ik, ib)) + { + continue; + } + std::vector> rhs(npwk, std::complex(0.0, 0.0)); + const std::complex fac(0.0, -1.0 / tpiba); + for (int ig = 0; ig < npwk; ++ig) + { + rhs[ig] = fac * vel[ib][ig]; + } + hamilt_->set_shift(eig_(ik, ib)); + double res = 0.0; + stern_.solve(*hamilt_, occ_kq_[ik], rhs, lin_max, lin_thr, yvec[ik][ib], res); + if (dbg) + { + std::cout << "DBG posresp a=" << a << " ik=" << ik << " ib=" << ib << " eps=" << eig_(ik, ib) + << " res=" << res << std::endl; + } + } +} + +void DFPT_PW::Impl::efield_per_band_solve(int ik, + int a, + int nbands, + int lin_max, + double lin_thr, + const std::vector>>>& yr, + const std::vector>>& dv_sc) +{ + (void)a; // used only by caller-index semantics, kept future-proof + for (int ib = 0; ib < nbands; ++ib) + { + if (!dfpt_band_occupied(wg_, ik, ib)) + { + continue; + } + if (yr[ik][ib].empty() || static_cast(dv_sc.size()) != nbands + || yr[ik][ib].size() != dv_sc[ib].size()) + { + continue; + } + std::vector> rhs(yr[ik][ib].size()); + for (size_t i = 0; i < rhs.size(); ++i) + { + rhs[i] = -(yr[ik][ib][i] + dv_sc[ib][i]); + } + hamilt_->set_shift(eig_(ik, ib)); + std::vector> dpsi_out; + double res = 0.0; + stern_.solve(*hamilt_, occ_kq_[ik], rhs, lin_max, lin_thr, dpsi_out, res); + data_.set_dpsi(last_q_, ik, ib, dpsi_out); + (void)res; // convergence is aggregated via drho residual, not per-band stern_ + } +} + +void DFPT_PW::Impl::stash_dpsi_efield(int q_idx, int a, int nk, int nbands) +{ + // stash dpsi^E,a before any later solve reuses the slots + std::vector>>> de( + nk, + std::vector>>(nbands)); + for (int ik = 0; ik < nk; ++ik) + { + for (int ib = 0; ib < nbands; ++ib) + { + de[ik][ib] = data_.get_dpsi(q_idx, ik, ib); + } + } + data_.set_dpsi_efield(a, de); +} + +void DFPT_PW::Impl::solve_pos_resp(int q_idx) +{ + ModuleBase::TITLE("DFPT_PW", "solve_pos_resp"); + ModuleBase::timer::start("DFPT_PW", "solve_pos_resp"); + // Y^a_{k,v} = P_c x_a|psi_{k,v}> through the Sternheimer equation + // (H(k) - eps_v) Y^a_v = P_c [H, x_a]|psi_v>, + // [H, x_a]|psi> = -(i/tpiba) dH/dk_a|psi> (velocity form), + // exactly the linear solve of QE dvpsi_e (whose rhs negation restores + // P_c[H,x]psi from commutator_Hx_psi's [x,H] convention). dH/dk_a is + // the pos_matrix velocity operator: the diagonal kinetic 2 tpiba^2 + // (k+G)_a plus the separable projector derivative + // (build_vkb/build_vkb_dk). The solved vector carries the complete + // conduction-space position response and replaces the + // empty-eigenvector-truncated r-matrix contraction. + if (!wired() || hamilt_ == nullptr) + { + ModuleBase::timer::end("DFPT_PW", "solve_pos_resp"); + return; + } + const ModuleBase::Vector3 q_cart = data_.get_qvec(q_idx) * ucell_->G; + const int nk = gs_psi_.get_nk(); + const int nbands = gs_psi_.get_nbands(); + const int lin_max = data_.get_max_iter(); + const double lin_thr = data_.get_conv_thr(); + + for (int a = 0; a < 3; ++a) + { + std::vector>>> yvec( + nk, + std::vector>>(nbands)); + for (int ik = 0; ik < nk; ++ik) + { + if (occ_kq_[ik].empty()) + { + continue; // matches the displacement solve guard + } + if (last_q_ != q_idx || last_ik_ != ik) + { + hamilt_->set_context(q_cart, ik); + last_q_ = q_idx; + last_ik_ = ik; + } + pos_per_band_solve(ik, a, nbands, lin_max, lin_thr, yvec); + } + data_.set_pos_resp(a, yvec); + } + ModuleBase::timer::end("DFPT_PW", "solve_pos_resp"); +} + +void DFPT_PW::Impl::solve_efield_resp(int q_idx) +{ + ModuleBase::TITLE("DFPT_PW", "solve_efield_resp"); + ModuleBase::timer::start("DFPT_PW", "solve_efield_resp"); + // E-field SCF response (QE solve_e + dfpt_kernel form): the bare legs + // Y^a stashed by solve_pos_resp are the field rhs base and the fixed + // point adds the screened response potential of the mixed drho^E + // exactly like solve_displacement. The converged dpsi^E,a feeds the + // SCF dielectric tensor (DFPT_Q0::compute_eps) and the zstar_eu + // cross-check probe (DFPT_ALEG). + if (!wired() || hamilt_ == nullptr) + { + ModuleBase::timer::end("DFPT_PW", "solve_efield_resp"); + return; + } + const ModuleBase::Vector3 q_cart = data_.get_qvec(q_idx) * ucell_->G; + const int npw = pw_rho_->npw; + const int nk = gs_psi_.get_nk(); + const int nbands = gs_psi_.get_nbands(); + const int lin_max = data_.get_max_iter(); + const double lin_thr = data_.get_conv_thr(); + + for (int a = 0; a < 3; ++a) + { + const std::vector>>> yr = data_.get_pos_resp(a); + if (static_cast(yr.size()) != nk) + { + continue; // bare legs not solved: no E response either + } + rho_.reset_mixing(q_idx); + data_.set_drho_g(q_idx, 0, std::vector>(npw, std::complex(0.0, 0.0))); + bool converged = false; + for (int iter = 0; iter < max_iter_ && !converged; ++iter) + { + // screened response potential of the mixed input density + // (shared Impl::assemble_v_sc from dfpt_pw_solve.cpp) + std::vector> v_sc_r; + const std::vector> drho_in_g = data_.get_drho_g(q_idx, 0); + assemble_v_sc(q_cart, drho_in_g, v_sc_r); + for (int ik = 0; ik < nk; ++ik) + { + if (static_cast(occ_kq_.size()) <= ik || occ_kq_[ik].empty()) + { + continue; + } + std::vector>> dv_sc; + pert_.apply_vr(q_idx, ik, v_sc_r, gs_psi_, q_cart, dv_sc); + if (last_q_ != q_idx || last_ik_ != ik) + { + hamilt_->set_context(q_cart, ik); + last_q_ = q_idx; + last_ik_ = ik; + } + efield_per_band_solve(ik, a, nbands, lin_max, lin_thr, yr, dv_sc); + } + rho_.compute_drho(gs_psi_, wg_, q_idx, data_); + rho_.mix_drho(q_idx, data_); + const double residual = rho_.get_residual(q_idx, data_); + converged = (residual < conv_thr_); + if (converged) + { + std::cout << "DFPT efield dir=" << a << " converged, residual=" << residual << " (iter=" << iter << ")" + << std::endl; + } + } + stash_dpsi_efield(q_idx, a, nk, nbands); + } + ModuleBase::timer::end("DFPT_PW", "solve_efield_resp"); +} + +} // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_pw_run.cpp b/source/source_pw/module_dfpt/dfpt_pw_run.cpp new file mode 100644 index 00000000000..16aec15df2f --- /dev/null +++ b/source/source_pw/module_dfpt/dfpt_pw_run.cpp @@ -0,0 +1,151 @@ +// ============================================================ +// DFPT_PW::run driver dispatcher implementation with helper +// extraction, moved from dfpt_pw.cpp so the driver TU stays below +// the coding-rule 500-line budget. The helpers mirror Gonze-Lee +// (1992, 1997) order: q0 response -> occ projector -> position legs +// -> e-field SCF -> displacement solves -> 2n+1 assembly -> Born +// charges -> phonon matrix diag + LO-TO. +// ============================================================ + +#include "dfpt_pw_impl.h" + +#include "source_base/timer.h" +#include "source_base/tool_title.h" + +#include + +namespace ModuleDFPT +{ + +void DFPT_PW::Impl::run_q0_pre(int q_idx) +{ + // C7 note: the uniform E-field / position-operator responses of the + // periodic crystal are ill-defined as bare matrix elements, so they + // are obtained instead as the well-defined periodic commutator + // [H_SCf, r] (QE dfpt_kernel / dfpt_tetra / dvpsi_e layout). The + // compute_q0_response kernel stashes the irrep info consumed by the + // per-direction solves below. + if (data_.get_compute_q0()) + { + q0_.compute_q0_response(data_); + } + if (wired()) + { + build_occ_kq(q_idx); + } + if (q_idx != 0 || !data_.get_compute_q0() || !wired()) + { + return; + } + // position legs of the screened Born charges: the q = 0 Y solves + // need the projector just built and must land before the two-pass + // displacement solves below reuse the shifted-operator context + solve_pos_resp(q_idx); + // SCF E-field responses of the dielectric tensor: after the + // bare Y legs they consume, before the displacement solves + // reuse the slots; the epsilon contraction runs straight after + // (QE solve_e -> dielec.f90 order) + solve_efield_resp(q_idx); + q0_.compute_eps(wg_, data_); +} + +double DFPT_PW::Impl::run_displacement_irrep_pass(int q_idx, int irrep) +{ + // Per-irrep self-consistent outer pass. Ledger semantics (B4): one + // outer pass solves every displacement to its own convergence + // (solve_displacement restarts each from a zero input density), and + // the pass residual is the worst final displacement residual. An + // unconverged pass therefore re-runs the full solve, bounded by + // max_iter_ outer passes. + if (!wired()) + { + // design-phase skeleton: no bases wired, converge at once + data_.add_residual(q_idx, irrep, 0.0); + data_.set_converged(q_idx, irrep, true); + return 0.0; + } + const int nat = ucell_->nat; + // two passes over the 3N displacement basis: first solve every + // displacement to convergence (the 2n+1 accumulation of + // displacement b needs the converged dpsi AND screened potential + // of every column displacement a), then run the 2n+1 accumulation + // for each + double worst = 0.0; + for (int iat = 0; iat < nat; ++iat) + { + for (int idir = 0; idir < 3; ++idir) + { + const double residual = solve_displacement(q_idx, iat, idir); + worst = std::max(worst, residual); + } + } + for (int iat = 0; iat < nat; ++iat) + { + for (int idir = 0; idir < 3; ++idir) + { + // 2n+1 accumulation of this converged displacement + phon_.accumulate_electron(q_idx, iat, idir, gs_psi_, wg_, data_); + } + } + data_.add_residual(q_idx, irrep, worst); + data_.set_converged(q_idx, irrep, worst < data_.get_conv_thr()); + return worst; +} + +void DFPT_PW::Impl::run_q0_post(int q_idx) +{ + // Screened Born charges: the Gonze-Lee 2n+1 form consumes the + // converged (screened) dpsi of every q = 0 displacement stashed by + // solve_displacement, so it must run after the two-pass solves + // above and before the LO-TO term below consumes it. + if (q_idx != 0 || !data_.get_compute_q0() || !wired()) + { + return; + } + q0_.compute_born(gs_psi_, wg_, eig_, data_); +} + +void DFPT_PW::Impl::run_assemble(int q_idx) +{ + phon_.assemble(q_idx, data_); + phon_.diagonalize(q_idx, data_); + if (q_idx != 0 || !data_.get_loto()) + { + return; + } + // non-analytic LO-TO correction along the data-layer direction + // (default isotropic (1,1,1)/sqrt(3) for cubic crystals; + // set_loto_dir overrides, e.g. per irrep direction in stage A) + phon_.add_loto(data_.get_loto_dir(), data_); + phon_.diagonalize_loto(data_); +} + +void DFPT_PW::run() +{ + ModuleBase::TITLE("DFPT_PW", "run"); + ModuleBase::timer::start("DFPT_PW", "run"); + const int nq = pimpl_->qlist_.get_nq(); + for (int q_idx = 0; q_idx < nq; ++q_idx) + { + pimpl_->run_q0_pre(q_idx); + + const int nirr = pimpl_->data_.get_nirr(q_idx); + for (int irrep = 0; irrep < nirr; ++irrep) + { + pimpl_->data_.set_converged(q_idx, irrep, false); + pimpl_->data_.set_current_iter(q_idx, irrep, 0); + while (!pimpl_->data_.get_converged(q_idx, irrep) + && pimpl_->data_.get_current_iter(q_idx, irrep) < pimpl_->max_iter_) + { + pimpl_->run_displacement_irrep_pass(q_idx, irrep); + pimpl_->data_.set_current_iter(q_idx, irrep, pimpl_->data_.get_current_iter(q_idx, irrep) + 1); + } + } + + pimpl_->run_q0_post(q_idx); + pimpl_->run_assemble(q_idx); + } + ModuleBase::timer::end("DFPT_PW", "run"); +} + +} // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_pw_solve.cpp b/source/source_pw/module_dfpt/dfpt_pw_solve.cpp new file mode 100644 index 00000000000..0355722dc5c --- /dev/null +++ b/source/source_pw/module_dfpt/dfpt_pw_solve.cpp @@ -0,0 +1,266 @@ +// ============================================================ +// DFPT_PW::Impl::solve_displacement and the shared assemble_v_sc +// implementation, with helper extraction. +// +// The two assembly routines (displacement SCF, efield SCF) both need +// the same screened response potential v^{SCF}(drho), so that piece +// is exposed as a separate Impl member reused by both translation +// units via dfpt_pw_impl.h. +// ============================================================ + +#include "dfpt_pw_impl.h" + +#include "dfpt_pw_data.h" +#include "source_base/timer.h" +#include "source_base/tool_title.h" + +#include +#include +#include +#include +#include + +namespace ModuleDFPT +{ + +void DFPT_PW::Impl::assemble_v_sc(const ModuleBase::Vector3& q_cart, + const std::vector>& drho_in_g, + std::vector>& v_sc_r) const +{ + const int nrxx = pw_rho_->nrxx; + v_sc_r.assign(nrxx, std::complex(0.0, 0.0)); + if (drho_in_g.empty() || static_cast(drho_in_g.size()) != pw_rho_->npw) + { + return; + } + // Hartree q-shifted response: V_H(q,G) = 4pi / |G+q|^2 * drho(G) + std::vector> dv_ha_g; + rho_.v_hartree_q(q_cart, drho_in_g, dv_ha_g); + std::vector> vh_r(nrxx); + pw_rho_->recip2real(dv_ha_g.data(), vh_r.data()); + for (int ir = 0; ir < nrxx; ++ir) + { + v_sc_r[ir] = vh_r[ir]; + } + // XC kernel response: v_xc(r) = f_xc(r,rho0) * drho(r); the + // XC_First_Order provider returns zero-size output for any + // unsupported / pure-LDA-kernel path, so the size check is the + // correct existence guard. + if (xc_ == nullptr) + { + return; + } + std::vector> a_r(nrxx); + pw_rho_->recip2real(drho_in_g.data(), a_r.data()); + std::vector> b_r; + xc_->apply(a_r, b_r); + if (static_cast(b_r.size()) != nrxx) + { + return; + } + for (int ir = 0; ir < nrxx; ++ir) + { + v_sc_r[ir] += b_r[ir]; + } +} + +double DFPT_PW::Impl::sternheimer_per_band(int ik, + int ib, + const std::vector>>& dv_sc, + int nbands, + int lin_max, + double lin_thr) +{ + if (!dfpt_band_occupied(wg_, ik, ib)) + { + return 0.0; + } + std::vector> rhs = data_.get_dpsi(last_q_, ik, ib); + const bool dbg = (getenv("DFPT_DEBUG") != nullptr); + if (rhs.empty() || static_cast(dv_sc.size()) != nbands || rhs.size() != dv_sc[ib].size()) + { + if (dbg) + { + std::cout << "DBG skip solve ik=" << ik << " ib=" << ib << " rhs.size=" << rhs.size() + << " dv_sc.size=" << dv_sc.size() << " dv_sc[ib].size=" + << (dv_sc.size() > static_cast(ib) ? dv_sc[ib].size() : 999999) << std::endl; + } + return 0.0; + } + for (size_t i = 0; i < rhs.size(); ++i) + { + rhs[i] = -(rhs[i] + dv_sc[ib][i]); + } + hamilt_->set_shift(eig_(ik, ib)); + std::vector> dpsi_out; + double res = 0.0; + stern_.solve(*hamilt_, occ_kq_[ik], rhs, lin_max, lin_thr, dpsi_out, res); + if (dbg) + { + double nr = 0.0; + double nb2 = 0.0; + for (size_t i = 0; i < dpsi_out.size(); ++i) + { + nr += std::norm(dpsi_out[i]); + nb2 += std::norm(rhs[i]); + } + std::cout << "DBG solve ik=" << ik << " ib=" << ib << " eps=" << eig_(ik, ib) << " res=" << res + << " |dpsi|=" << std::sqrt(nr) << " |rhs|=" << std::sqrt(nb2) + << " finite=" << (std::isfinite(std::sqrt(nr)) ? 1 : 0) << std::endl; + } + data_.set_dpsi(last_q_, ik, ib, dpsi_out); + return res; +} + +void DFPT_PW::Impl::stash_converged_disp_response(int q_idx, + int iat, + int idir, + const std::vector>& v_sc_r_last, + int nk, + int nbands) +{ + data_.set_vsc_r(iat, idir, v_sc_r_last); + std::vector>>> disp( + nk, + std::vector>>(nbands)); + for (int ik = 0; ik < nk; ++ik) + { + for (int ib = 0; ib < nbands; ++ib) + { + disp[ik][ib] = data_.get_dpsi(q_idx, ik, ib); + } + } + data_.set_dpsi_disp(iat, idir, disp); +} + +namespace +{ + +void debug_iter_snapshot(int iter, + const std::vector>& drho_in_g, + const std::vector>& v_sc_r, + int npw, + int nrxx) +{ + double dh = 0.0; + double dv = 0.0; + for (int ig = 0; ig < npw; ++ig) + { + dh += std::norm(drho_in_g[ig]); + } + for (int ir = 0; ir < nrxx; ++ir) + { + dv += std::norm(v_sc_r[ir]); + } + std::cout << "DBG iter=" << iter << " |drho_in_g|=" << std::sqrt(dh) << " |v_sc_r|=" << std::sqrt(dv) + << std::endl; +} + +void debug_h_consistency(DFPT_HamiltShift& h, + const std::vector>>& occ_k, + const ModuleBase::matrix& eig, + int ikq) +{ + std::cout << "DBG occ_kq nstates=" << occ_k.size() << std::endl; + for (size_t m = 0; m < occ_k.size(); ++m) + { + h.set_shift(0.0); + std::vector> hp(occ_k[m].size()); + h.apply(occ_k[m].data(), hp.data()); + std::complex dot(0.0, 0.0); + for (size_t i = 0; i < hp.size(); ++i) + { + dot += std::conj(occ_k[m][i]) * hp[i]; + } + std::cout << "DBG = " << dot.real() << " + i " << dot.imag() + << " (GS eig " << eig(ikq, static_cast(m)) << ")" << std::endl; + std::cout << "DBG = " << h.debug_t_vnl(occ_k[m]) << std::endl; + std::cout << "DBG = " << h.debug_v_wfc(occ_k[m]) << std::endl; + } +} + +} // namespace + +double DFPT_PW::Impl::solve_displacement(int q_idx, int iat, int idir) +{ + ModuleBase::TITLE("DFPT_PW", "solve_displacement"); + ModuleBase::timer::start("DFPT_PW", "solve_displacement"); + if (!wired() || hamilt_ == nullptr) + { + ModuleBase::timer::end("DFPT_PW", "solve_displacement"); + return 0.0; + } + const ModuleBase::Vector3 q_frac = data_.get_qvec(q_idx); + const ModuleBase::Vector3 q_cart = q_frac * ucell_->G; + const int nrxx = pw_rho_->nrxx; + const int npw = pw_rho_->npw; + const int nk = gs_psi_.get_nk(); + const int nbands = gs_psi_.get_nbands(); + const bool dbg = (getenv("DFPT_DEBUG") != nullptr); + + pert_.build_dv(q_idx, iat, idir, data_); + rho_.reset_mixing(q_idx); + data_.set_drho_g(q_idx, 0, std::vector>(npw, std::complex(0.0, 0.0))); + + const int lin_max = data_.get_max_iter(); + const double lin_thr = data_.get_conv_thr(); + + bool converged = false; + double residual = 0.0; + std::vector> v_sc_r_last; + for (int iter = 0; iter < max_iter_ && !converged; ++iter) + { + std::vector> v_sc_r; + const std::vector> drho_in_g = data_.get_drho_g(q_idx, 0); + assemble_v_sc(q_cart, drho_in_g, v_sc_r); + if (dbg) + { + debug_iter_snapshot(iter, drho_in_g, v_sc_r, npw, nrxx); + } + v_sc_r_last = v_sc_r; + + for (int ik = 0; ik < nk; ++ik) + { + if (static_cast(occ_kq_.size()) <= ik || occ_kq_[ik].empty()) + { + if (dbg) + { + std::cout << "DBG skip ik=" << ik << " no occ_kq" << std::endl; + } + continue; + } + pert_.apply_dv(q_idx, ik, gs_psi_, data_); + std::vector>> dv_sc; + pert_.apply_vr(q_idx, ik, v_sc_r, gs_psi_, q_cart, dv_sc); + if (ik != last_ik_ || last_q_ != q_idx) + { + hamilt_->set_context(q_cart, ik); + last_ik_ = ik; + last_q_ = q_idx; + if (dbg) + { + debug_h_consistency(*hamilt_, occ_kq_[ik], eig_, ikq_of_k_[ik]); + } + } + for (int ib = 0; ib < nbands; ++ib) + { + sternheimer_per_band(ik, ib, dv_sc, nbands, lin_max, lin_thr); + } + } + + rho_.compute_drho(gs_psi_, wg_, q_idx, data_); + rho_.mix_drho(q_idx, data_); + residual = rho_.get_residual(q_idx, data_); + if (dbg) + { + std::cout << "DBG iter=" << iter << " residual=" << residual << " conv_thr=" << conv_thr_ << std::endl; + } + converged = (residual < conv_thr_); + } + stash_converged_disp_response(q_idx, iat, idir, v_sc_r_last, nk, nbands); + + ModuleBase::timer::end("DFPT_PW", "solve_displacement"); + return residual; +} + +} // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_q0.cpp b/source/source_pw/module_dfpt/dfpt_q0.cpp index 25212f50f67..283b6c430e5 100644 --- a/source/source_pw/module_dfpt/dfpt_q0.cpp +++ b/source/source_pw/module_dfpt/dfpt_q0.cpp @@ -1,77 +1,308 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #include "dfpt_q0.h" -#include "dfpt_pert.h" -#include "source_base/constants.h" -#include "source_base/global_function.h" +#include "source_base/timer.h" +#include "source_base/tool_title.h" #include #include -#include -#include #include -namespace ModuleDFPT { +namespace ModuleDFPT +{ -DFPT_Q0::DFPT_Q0() {} +namespace +{ -DFPT_Q0::~DFPT_Q0() {} +/// element accessor for ModuleBase::Matrix3 (row i, column j); the public +/// interface only exposes the named e11..e33 members +inline double me(const ModuleBase::Matrix3& m, int i, int j) +{ + switch (3 * i + j) + { + case 0: + return m.e11; + case 1: + return m.e12; + case 2: + return m.e13; + case 3: + return m.e21; + case 4: + return m.e22; + case 5: + return m.e23; + case 6: + return m.e31; + case 7: + return m.e32; + default: + return m.e33; + } +} -void DFPT_Q0::init(UnitCell& ucell, ModulePW::PW_Basis* pw_rho, - ModulePW::PW_Basis_K* pw_wfc, DFPT_Pert* pert) { - ucell_ = &ucell; - pw_rho_ = pw_rho; - pw_wfc_ = pw_wfc; - pert_ = pert; - stars_.clear(); +/// folded fractional equality with the lattice periodicity absorbed +inline bool folded_equal(double a, double b, double tol) +{ + const double d = std::abs(a - b); + return d < tol || std::abs(d - 1.0) < tol; } -namespace { -// element accessor for ModuleBase::Matrix3 (row i, column j); the public -// interface only exposes the named e11..e33 members -inline double me(const ModuleBase::Matrix3& m, int i, int j) { - switch (3 * i + j) { - case 0: return m.e11; - case 1: return m.e12; - case 2: return m.e13; - case 3: return m.e21; - case 4: return m.e22; - case 5: return m.e23; - case 6: return m.e31; - case 7: return m.e32; - default: return m.e33; +/// true if kp differs from every already-folded star member +bool is_new_member(const std::vector>& kfolds, const ModuleBase::Vector3& kp) +{ + const double kfold_tol = 1.0e-5; ///< empirical parameter: folded fractional-coordinate match tolerance + for (size_t im = 0; im < kfolds.size(); ++im) + { + if (folded_equal(kp.x, kfolds[im].x, kfold_tol) && folded_equal(kp.y, kfolds[im].y, kfold_tol) + && folded_equal(kp.z, kfolds[im].z, kfold_tol)) + { + return false; + } } + return true; } -// folded fractional equality with the lattice periodicity absorbed -inline bool folded_equal(double a, double b, double tol) { - const double d = std::abs(a - b); - return d < tol || std::abs(d - 1.0) < tol; +/// atom image map iat -> image atom of the j-th symmetry operation +/// (direct-space gmatrix/gtrans pair; species map onto themselves). +/// Returns false when some atom has no same-species image, i.e. the +/// operation set is inconsistent with the structure. +bool make_atom_map(const UnitCell& ucell, const ModuleSymmetry::Symmetry& symm, int j, std::vector& atom_map) +{ + const int nat = ucell.nat; + const double tau_match_tol = 1.0e-4; ///< empirical parameter: direct-space atom-image match tolerance + atom_map.assign(nat, -1); + for (int iat = 0; iat < nat; ++iat) + { + const int it = ucell.iat2it[iat]; + const int ia = ucell.iat2ia[iat]; + ModuleBase::Vector3 tp = ucell.atoms[it].taud[ia] * symm.gmatrix[j] + symm.gtrans[j]; + tp.x -= std::floor(tp.x); + tp.y -= std::floor(tp.y); + tp.z -= std::floor(tp.z); + for (int jat = 0; jat < nat; ++jat) + { + if (ucell.iat2it[jat] != it) + { + continue; // a species maps onto itself + } + const int ja = ucell.iat2ia[jat]; + const ModuleBase::Vector3& tq = ucell.atoms[it].taud[ja]; + if (folded_equal(tp.x, tq.x, tau_match_tol) && folded_equal(tp.y, tq.y, tau_match_tol) + && folded_equal(tp.z, tq.z, tau_match_tol)) + { + atom_map[iat] = jat; + break; + } + } + if (atom_map[iat] < 0) + { + return false; + } + } + return true; +} + +/// [ik][v][ig] wave-function / response coefficients over the k mesh +typedef std::vector>>> KMeshCoeffs; + +/// wg-weighted partial chi_k[ik](a, b) = sum_occ Re at +/// every stored k (QE dielec.f90: eps -= 4*(4pi/Omega)*wk*Re) +void accumulate_chi_eps(const ModuleBase::matrix& wg, + const std::vector& yr, + const std::vector& de, + int nk, + std::vector& chi_k) +{ + const int nbands = wg.nc; + for (int ik = 0; ik < nk; ++ik) + { + for (int v = 0; v < nbands; ++v) + { + if (!dfpt_band_occupied(wg, ik, v)) + { + continue; // empty + } + for (int a = 0; a < 3; ++a) + { + const int npw = static_cast(yr[a][ik][v].size()); + if (npw <= 0) + { + continue; + } + for (int b = 0; b < 3; ++b) + { + if (static_cast(de[b][ik][v].size()) != npw) + { + continue; + } + std::complex dot(0.0, 0.0); + for (int ig = 0; ig < npw; ++ig) + { + dot += std::conj(yr[a][ik][v][ig]) * de[b][ik][v][ig]; + } + chi_k[ik](a, b) += wg(ik, v) * dot.real(); + } + } + } + } +} + +/// star average of the partial tensors: eps += (1/n_star) R chi(k) R^T +void star_average_eps(const std::vector>& stars, + const std::vector& chi_k, + ModuleBase::matrix& eps) +{ + const int nk = static_cast(stars.size()); + for (int ik = 0; ik < nk; ++ik) + { + const double inv_nstar = 1.0 / static_cast(stars[ik].size()); + for (size_t im = 0; im < stars[ik].size(); ++im) + { + double rot[9]; + DFPT_Q0::rotate_tensor(stars[ik][im].cart, chi_k[ik], rot); + for (int a = 0; a < 3; ++a) + { + for (int b = 0; b < 3; ++b) + { + eps(a, b) += inv_nstar * rot[3 * a + b]; + } + } + } + } } + +/// wg-weighted partial chi_k[ik](a, idir) of one atom displacement +/// direction, paired with the position legs Y^a of the q = 0 mesh +void accumulate_disp_chi(const ModuleBase::matrix& wg, + const std::vector& yr, + const KMeshCoeffs& disp, + int idir, + int nbasis, + std::vector& chi_k) +{ + const int nk = static_cast(disp.size()); + const int nbands = wg.nc; + for (int ik = 0; ik < nk; ++ik) + { + for (int v = 0; v < nbands; ++v) + { + if (!dfpt_band_occupied(wg, ik, v)) + { + continue; // empty + } + const int npw = static_cast(disp[ik][v].size()); + if (npw <= 0 || npw > nbasis) + { + continue; // unsolved slot or inconsistent basis + } + // per field direction + for (int a = 0; a < 3; ++a) + { + if (static_cast(yr[a][ik][v].size()) != npw) + { + continue; + } + std::complex dot(0.0, 0.0); + for (int ig = 0; ig < npw; ++ig) + { + dot += std::conj(disp[ik][v][ig]) * yr[a][ik][v][ig]; + } + chi_k[ik](a, idir) += wg(ik, v) * dot.real(); + } + } + } +} + +/// star-credit the atom-resolved partials: the partial at member Rk is +/// R chi(k) R^T and is credited to the image atom R(iat) +void credit_star_born(const std::vector>& stars, + const std::vector& chi_k, + int iat, + std::vector& zacc) +{ + const int nk = static_cast(stars.size()); + for (int ik = 0; ik < nk; ++ik) + { + const double inv_nstar = 1.0 / static_cast(stars[ik].size()); + for (size_t im = 0; im < stars[ik].size(); ++im) + { + const DFPT_Q0::StarMember& mem = stars[ik][im]; + const int jat = (mem.atom_map.empty()) ? iat : mem.atom_map[iat]; + double rot[9]; + DFPT_Q0::rotate_tensor(mem.cart, chi_k[ik], rot); + for (int a = 0; a < 3; ++a) + { + for (int d = 0; d < 3; ++d) + { + zacc[jat](a, d) += inv_nstar * rot[3 * a + d]; + } + } + } + } +} + +/// assemble Z*_iat = -2 Zacc + Z_ion on the diagonal and stash per atom +void set_born_charges(const UnitCell& ucell, const std::vector& zacc, DFPT_PW_Data& data) +{ + const int nat = ucell.nat; + for (int iat = 0; iat < nat; ++iat) + { + ModuleBase::matrix zstar(3, 3, true); + for (int a = 0; a < 3; ++a) + { + for (int d = 0; d < 3; ++d) + { + zstar(a, d) = -2.0 * zacc[iat](a, d); + } + } + // ionic rigid-ion charge on the diagonal (a == b directions) + const int it = ucell.iat2it[iat]; + const double zion = ucell.atoms[it].ncpp.zv; + for (int d = 0; d < 3; ++d) + { + zstar(d, d) += zion; + } + data.set_born(iat, zstar); + } +} + } // namespace -void DFPT_Q0::build_stars(int nk) { +void DFPT_Q0::init(UnitCell& ucell, ModulePW::PW_Basis* pw_rho, ModulePW::PW_Basis_K* pw_wfc, DFPT_Pert* pert) +{ + ModuleBase::TITLE("DFPT_Q0", "init"); + ModuleBase::timer::start("DFPT_Q0", "init"); + ucell_ = &ucell; + pw_rho_ = pw_rho; + pw_wfc_ = pw_wfc; + pert_ = pert; + stars_.clear(); + ModuleBase::timer::end("DFPT_Q0", "init"); +} + +void DFPT_Q0::build_stars(int nk) +{ + ModuleBase::TITLE("DFPT_Q0", "build_stars"); + ModuleBase::timer::start("DFPT_Q0", "build_stars"); // every k starts with the identity member (also the permanent fallback) stars_.assign(nk, std::vector(1, StarMember())); - if (ucell_ == nullptr || pw_wfc_ == nullptr || pw_wfc_->kvec_d == nullptr) { + if (ucell_ == nullptr || pw_wfc_ == nullptr || pw_wfc_->kvec_d == nullptr) + { + ModuleBase::timer::end("DFPT_Q0", "build_stars"); return; } const ModuleSymmetry::Symmetry& symm = ucell_->symm; - if (symm.nrotk <= 0) { + if (symm.nrotk <= 0) + { // no point-group analysis (symmetry off / unreduced mesh): the // stored list is already the full mesh, identity members only + ModuleBase::timer::end("DFPT_Q0", "build_stars"); return; } const int nat = ucell_->nat; std::vector> kfolds; - for (int ik = 0; ik < nk; ++ik) { + for (int ik = 0; ik < nk; ++ik) + { kfolds.clear(); // the pre-filled identity member owns the folded k itself ModuleBase::Vector3 k0 = pw_wfc_->kvec_d[ik]; @@ -79,23 +310,16 @@ void DFPT_Q0::build_stars(int nk) { k0.y -= std::round(k0.y); k0.z -= std::round(k0.z); kfolds.push_back(k0); - for (int j = 0; j < symm.nrotk; ++j) { + for (int j = 0; j < symm.nrotk; ++j) + { ModuleBase::Vector3 kp = pw_wfc_->kvec_d[ik] * symm.kgmatrix[j]; // fold to [-0.5, 0.5): star members are grid points, the // folded coordinates identify the distinct mesh points kp.x -= std::round(kp.x); kp.y -= std::round(kp.y); kp.z -= std::round(kp.z); - bool dup = false; - for (size_t im = 0; im < kfolds.size(); ++im) { - if (folded_equal(kp.x, kfolds[im].x, 1.0e-5) - && folded_equal(kp.y, kfolds[im].y, 1.0e-5) - && folded_equal(kp.z, kfolds[im].z, 1.0e-5)) { - dup = true; - break; - } - } - if (dup) { + if (!is_new_member(kfolds, kp)) + { continue; } kfolds.push_back(kp); @@ -104,309 +328,129 @@ void DFPT_Q0::build_stars(int nk) { // k_cart = k_frac * G, hence k_cart' = k_cart * (G^-1 K G). That // product is the row-convention operator; rotate_tensor applies // the column form chi' = R chi R^T, so store the transpose - const ModuleBase::Matrix3 krow - = ucell_->G.Inverse() * symm.kgmatrix[j] * ucell_->G; - mem.cart = ModuleBase::Matrix3(krow.e11, krow.e21, krow.e31, - krow.e12, krow.e22, krow.e32, - krow.e13, krow.e23, krow.e33); - // atom image under the paired direct-space operation - mem.atom_map.assign(nat, -1); - bool ok = true; - for (int iat = 0; iat < nat && ok; ++iat) { - const int it = ucell_->iat2it[iat]; - const int ia = ucell_->iat2ia[iat]; - ModuleBase::Vector3 tp - = ucell_->atoms[it].taud[ia] * symm.gmatrix[j] + symm.gtrans[j]; - tp.x -= std::floor(tp.x); - tp.y -= std::floor(tp.y); - tp.z -= std::floor(tp.z); - for (int jat = 0; jat < nat; ++jat) { - if (ucell_->iat2it[jat] != it) { - continue; // a species maps onto itself - } - const int ja = ucell_->iat2ia[jat]; - const ModuleBase::Vector3& tq - = ucell_->atoms[it].taud[ja]; - if (folded_equal(tp.x, tq.x, 1.0e-4) - && folded_equal(tp.y, tq.y, 1.0e-4) - && folded_equal(tp.z, tq.z, 1.0e-4)) { - mem.atom_map[iat] = jat; - break; - } - } - if (mem.atom_map[iat] < 0) { - ok = false; - } - } - if (!ok) { + const ModuleBase::Matrix3 krow = ucell_->G.Inverse() * symm.kgmatrix[j] * ucell_->G; + mem.cart = ModuleBase::Matrix3(krow.e11, + krow.e21, + krow.e31, + krow.e12, + krow.e22, + krow.e32, + krow.e13, + krow.e23, + krow.e33); + if (!make_atom_map(*ucell_, symm, j, mem.atom_map)) + { // inconsistent operation set: fall back to identity-only // stars for every k (the unreduced-sum behavior) stars_.assign(nk, std::vector(1, StarMember())); + ModuleBase::timer::end("DFPT_Q0", "build_stars"); return; } stars_[ik].push_back(mem); } } + ModuleBase::timer::end("DFPT_Q0", "build_stars"); } -void DFPT_Q0::rotate_tensor(const ModuleBase::Matrix3& r, - const ModuleBase::matrix& chi, - double (&chi_rot)[9]) { - for (int a = 0; a < 3; ++a) { - for (int b = 0; b < 3; ++b) { +void DFPT_Q0::rotate_tensor(const ModuleBase::Matrix3& r, const ModuleBase::matrix& chi, double (&chi_rot)[9]) +{ + ModuleBase::TITLE("DFPT_Q0", "rotate_tensor"); + ModuleBase::timer::start("DFPT_Q0", "rotate_tensor"); + for (int a = 0; a < 3; ++a) + { + for (int b = 0; b < 3; ++b) + { double s = 0.0; - for (int ap = 0; ap < 3; ++ap) { - for (int bp = 0; bp < 3; ++bp) { + for (int ap = 0; ap < 3; ++ap) + { + for (int bp = 0; bp < 3; ++bp) + { s += me(r, a, ap) * me(r, b, bp) * chi(ap, bp); } } chi_rot[3 * a + b] = s; } } + ModuleBase::timer::end("DFPT_Q0", "rotate_tensor"); } -void DFPT_Q0::pos_matrix(const psi::Psi>& psi, - const ModuleBase::matrix& eig, - std::vector>>>>& r_mat) { - const int nk = psi.get_nk(); - const int nbands = psi.get_nbands(); - r_mat.assign(nk, - std::vector>>>( - nbands, - std::vector>>( - nbands, ModuleBase::Vector3>(0.0, 0.0, 0.0)))); - if (pw_wfc_ == nullptr || ucell_ == nullptr || pert_ == nullptr) { - return; - } - const double tpiba = ucell_->tpiba; - const double tpiba2 = tpiba * tpiba; - for (int ik = 0; ik < nk; ++ik) { - const int npwk = pw_wfc_->npwk[ik]; - std::vector> gk(npwk); - for (int ig = 0; ig < npwk; ++ig) { - gk[ig] = pw_wfc_->getgpluskcar(ik, ig); - } - // velocity operator dH/dk matrix elements, with the k derivative in - // the same dimensionless 2*pi/lat0 units build_vkb_dk uses: - // p^d_{mn} = - // V_loc is k-independent; the DFT+U commutator is the U0 reservation. - std::vector>>> p_mat( - nbands, - std::vector>>( - nbands, ModuleBase::Vector3>(0.0, 0.0, 0.0))); - // diagonal kinetic part: T = tpiba^2 |k+G|^2 (Ry a.u.) - for (int m = 0; m < nbands; ++m) { - for (int n = 0; n < nbands; ++n) { - std::complex dot[3] = {std::complex(0.0, 0.0), - std::complex(0.0, 0.0), - std::complex(0.0, 0.0)}; - for (int ig = 0; ig < npwk; ++ig) { - const std::complex cc = - std::conj(psi(ik, m, ig)) * psi(ik, n, ig); - for (int d = 0; d < 3; ++d) { - dot[d] += 2.0 * tpiba2 * gk[ig][d] * cc; - } - } - for (int d = 0; d < 3; ++d) { - p_mat[m][n][d] = dot[d]; - } - } - } - // nonlocal derivative part: dV_nl/dk_d = sum_{mu,nu} (|dvkb_mu> D_{mu,nu} D_{mu,nu} ntype; ++it) { - const pseudo& ncpp = ucell_->atoms[it].ncpp; - const int nh = ncpp.nh; - if (nh == 0) { - continue; - } - if (ncpp.tvanp || ncpp.has_so) { - ModuleBase::WARNING_QUIT("DFPT_Q0::pos_matrix", - "DFPT velocity operator is implemented for " - "normal-conserving separable pseudopotentials only."); - } - // projector -> (radial beta index, m channel) table, matching build_vkb - std::vector mu_ib(nh, 0); - std::vector mu_m(nh, 0); - int mu_idx = 0; - for (int ib = 0; ib < ncpp.nbeta; ++ib) { - const int l = ncpp.lll[ib]; - for (int m = 0; m < 2 * l + 1; ++m) { - if (mu_idx < nh) { - mu_ib[mu_idx] = ib; - mu_m[mu_idx] = m; - } - ++mu_idx; - } - } - for (int ia = 0; ia < ucell_->atoms[it].na; ++ia) { - std::vector>> vkb; - pert_->build_vkb(it, ia, gk, vkb); - // becp_b[mu] = for all bands - std::vector>> becp(nbands); - for (int b = 0; b < nbands; ++b) { - becp[b].assign(nh, std::complex(0.0, 0.0)); - for (int mu = 0; mu < nh; ++mu) { - for (int ig = 0; ig < npwk; ++ig) { - becp[b][mu] += std::conj(vkb[mu][ig]) * psi(ik, b, ig); - } - } - } - for (int d = 0; d < 3; ++d) { - std::vector>> dvkb; - pert_->build_vkb_dk(it, ia, d, gk, vkb, dvkb); - // dbecp_b[mu] = - std::vector>> dbecp(nbands); - for (int b = 0; b < nbands; ++b) { - dbecp[b].assign(nh, std::complex(0.0, 0.0)); - for (int mu = 0; mu < nh; ++mu) { - for (int ig = 0; ig < npwk; ++ig) { - dbecp[b][mu] += std::conj(dvkb[mu][ig]) * psi(ik, b, ig); - } - } - } - // accumulate the two Hermitian-conjugate projector terms - for (int m = 0; m < nbands; ++m) { - for (int n = 0; n < nbands; ++n) { - std::complex term(0.0, 0.0); - for (int mu = 0; mu < nh; ++mu) { - std::complex out_m(0.0, 0.0); - std::complex in_n(0.0, 0.0); - for (int nu = 0; nu < nh; ++nu) { - if (mu_m[mu] != mu_m[nu]) { - continue; - } - const double dij = ncpp.dion(mu_ib[mu], mu_ib[nu]); - out_m += dij * becp[n][nu]; - in_n += dij * dbecp[n][nu]; - } - // D + D - term += std::conj(dbecp[m][mu]) * out_m - + std::conj(becp[m][mu]) * in_n; - } - p_mat[m][n][d] += term; - } - } - } - } - } - // velocity -> position: r = -i v / (tpiba (eps_m - eps_n)), r in bohr - // (from [H, r] = -i dH/dk in Ry a.u.); degenerate pairs are skipped, - // their gauge-dependent matrix elements carry no unique value. - for (int m = 0; m < nbands; ++m) { - for (int n = 0; n < nbands; ++n) { - if (m == n) { - continue; - } - const double de = eig(ik, m) - eig(ik, n); - if (std::abs(de) < 1.0e-8) { - continue; - } - for (int d = 0; d < 3; ++d) { - r_mat[ik][m][n][d] = std::complex(0.0, -1.0) * p_mat[m][n][d] - / (tpiba * de); - } - } - } - } -} - -void DFPT_Q0::compute_eps(const ModuleBase::matrix& wg, DFPT_PW_Data& data) { - if (ucell_ == nullptr) { +void DFPT_Q0::compute_eps(const ModuleBase::matrix& wg, DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Q0", "compute_eps"); + ModuleBase::timer::start("DFPT_Q0", "compute_eps"); + if (ucell_ == nullptr) + { + ModuleBase::timer::end("DFPT_Q0", "compute_eps"); return; } const int nk = wg.nr; - const int nbands = wg.nc; // bare position legs Y^a and converged E-field responses dpsi^E,b of // the q = 0 mesh (DFPT_PW::solve_pos_resp / solve_efield_resp) - std::vector>>>> yr(3); - std::vector>>>> de(3); - for (int a = 0; a < 3; ++a) { + std::vector yr(3); + std::vector de(3); + for (int a = 0; a < 3; ++a) + { yr[a] = data.get_pos_resp(a); de[a] = data.get_dpsi_efield(a); - if (static_cast(yr[a].size()) != nk - || static_cast(de[a].size()) != nk) { + if (static_cast(yr[a].size()) != nk || static_cast(de[a].size()) != nk) + { + ModuleBase::timer::end("DFPT_Q0", "compute_eps"); return; // responses not solved: nothing to accumulate } } build_stars(nk); - // wg-weighted partial chi_k[ik](a, b) = sum_occ Re at - // every stored k (QE dielec.f90: eps -= 4*(4pi/Omega)*wk*Re) std::vector chi_k(nk, ModuleBase::matrix(3, 3, true)); - for (int ik = 0; ik < nk; ++ik) { - for (int v = 0; v < nbands; ++v) { - if (!dfpt_band_occupied(wg, ik, v)) { - continue; // empty - } - for (int a = 0; a < 3; ++a) { - const int npw = static_cast(yr[a][ik][v].size()); - if (npw <= 0) { - continue; - } - for (int b = 0; b < 3; ++b) { - if (static_cast(de[b][ik][v].size()) != npw) { - continue; - } - std::complex dot(0.0, 0.0); - for (int ig = 0; ig < npw; ++ig) { - dot += std::conj(yr[a][ik][v][ig]) * de[b][ik][v][ig]; - } - chi_k[ik](a, b) += wg(ik, v) * dot.real(); - } - } - } - } + accumulate_chi_eps(wg, yr, de, nk, chi_k); ModuleBase::matrix eps(3, 3, true); // wg carries the full k weight (star size included) times the spin // factor 2, so the star-averaged partials sum to the complete // Brillouin-zone average: no extra 1/nk normalization - for (int ik = 0; ik < nk; ++ik) { - const double inv_nstar = 1.0 / static_cast(stars_[ik].size()); - for (size_t im = 0; im < stars_[ik].size(); ++im) { - double rot[9]; - rotate_tensor(stars_[ik][im].cart, chi_k[ik], rot); - for (int a = 0; a < 3; ++a) { - for (int b = 0; b < 3; ++b) { - eps(a, b) += inv_nstar * rot[3 * a + b]; - } - } - } - } - for (int a = 0; a < 3; ++a) { - for (int b = 0; b < 3; ++b) { + star_average_eps(stars_, chi_k, eps); + for (int a = 0; a < 3; ++a) + { + for (int b = 0; b < 3; ++b) + { // 16 pi / Omega: QE dielec.f90 form eps = 1 - 4*(4pi/Omega)*wk* // Re (validated against QE 7.2 Si to 0.06%: // 23.6825 here vs 23.6685 QE) eps(a, b) *= -16.0 * ModuleBase::PI / ucell_->omega; - if (a == b) { + if (a == b) + { eps(a, b) += 1.0; } } } data.set_dielectric(eps); + ModuleBase::timer::end("DFPT_Q0", "compute_eps"); } void DFPT_Q0::compute_born(const psi::Psi>& psi, const ModuleBase::matrix& wg, - const ModuleBase::matrix& eig, DFPT_PW_Data& data) { - if (ucell_ == nullptr) { + const ModuleBase::matrix& eig, + DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Q0", "compute_born"); + ModuleBase::timer::start("DFPT_Q0", "compute_born"); + if (ucell_ == nullptr) + { + ModuleBase::timer::end("DFPT_Q0", "compute_born"); return; } const int nk = psi.get_nk(); - const int nbands = psi.get_nbands(); - const int nat = ucell_->nat; const int nbasis = psi.get_nbasis(); (void)eig; // solved position legs Y^a_{k,v} = P_c x_a|psi_v> of the q = 0 mesh // (DFPT_PW::solve_pos_resp stashes them per direction) - std::vector>>>> yr(3); - for (int a = 0; a < 3; ++a) { + std::vector yr(3); + for (int a = 0; a < 3; ++a) + { yr[a] = data.get_pos_resp(a); - if (static_cast(yr[a].size()) != nk) { + if (static_cast(yr[a].size()) != nk) + { + ModuleBase::timer::end("DFPT_Q0", "compute_born"); return; // position responses not solved: nothing to accumulate } } @@ -414,86 +458,42 @@ void DFPT_Q0::compute_born(const psi::Psi>& psi, build_stars(nk); // star-rotated electronic partials, credited to the image atom under // each star member: zacc[kappa](a, idir) + const int nat = ucell_->nat; std::vector zacc(nat, ModuleBase::matrix(3, 3, true)); - for (int iat = 0; iat < nat; ++iat) { + for (int iat = 0; iat < nat; ++iat) + { // wg-weighted partial chi_k[ik](a, idir) of THIS atom at every k std::vector chi_k(nk, ModuleBase::matrix(3, 3, true)); - for (int idir = 0; idir < 3; ++idir) { + for (int idir = 0; idir < 3; ++idir) + { // converged screened displacement response dpsi(scf)/du of this // mode, stashed by solve_displacement before compute_born runs - const std::vector>>> disp - = data.get_dpsi_disp(iat, idir); - if (static_cast(disp.size()) != nk) { + const KMeshCoeffs disp = data.get_dpsi_disp(iat, idir); + if (static_cast(disp.size()) != nk) + { continue; } - for (int ik = 0; ik < nk; ++ik) { - for (int v = 0; v < nbands; ++v) { - if (!dfpt_band_occupied(wg, ik, v)) { - continue; // empty - } - const int npw = static_cast(disp[ik][v].size()); - if (npw <= 0 || npw > nbasis) { - continue; // unsolved slot or inconsistent basis - } - // per field direction - for (int a = 0; a < 3; ++a) { - if (static_cast(yr[a][ik][v].size()) != npw) { - continue; - } - std::complex dot(0.0, 0.0); - for (int ig = 0; ig < npw; ++ig) { - dot += std::conj(disp[ik][v][ig]) * yr[a][ik][v][ig]; - } - chi_k[ik](a, idir) += wg(ik, v) * dot.real(); - } - } - } - } - // star average: the partial at member Rk is R chi(k) R^T and is - // credited to the image atom R(iat); wg already carries the star - // size, so each member contributes with 1/n_star - for (int ik = 0; ik < nk; ++ik) { - const double inv_nstar - = 1.0 / static_cast(stars_[ik].size()); - for (size_t im = 0; im < stars_[ik].size(); ++im) { - const StarMember& mem = stars_[ik][im]; - const int jat = (mem.atom_map.empty()) ? iat : mem.atom_map[iat]; - double rot[9]; - rotate_tensor(mem.cart, chi_k[ik], rot); - for (int a = 0; a < 3; ++a) { - for (int d = 0; d < 3; ++d) { - zacc[jat](a, d) += inv_nstar * rot[3 * a + d]; - } - } - } + accumulate_disp_chi(wg, yr, disp, idir, nbasis, chi_k); } + credit_star_born(stars_, chi_k, iat, zacc); } - for (int iat = 0; iat < nat; ++iat) { - ModuleBase::matrix zstar(3, 3, true); - for (int a = 0; a < 3; ++a) { - for (int d = 0; d < 3; ++d) { - zstar(a, d) = -2.0 * zacc[iat](a, d); - } - } - // ionic rigid-ion charge on the diagonal (a == b directions) - const int it = ucell_->iat2it[iat]; - const double zion = ucell_->atoms[it].ncpp.zv; - for (int d = 0; d < 3; ++d) { - zstar(d, d) += zion; - } - data.set_born(iat, zstar); - } + set_born_charges(*ucell_, zacc, data); + ModuleBase::timer::end("DFPT_Q0", "compute_born"); } -void DFPT_Q0::compute_q0_response(DFPT_PW_Data& data) { +void DFPT_Q0::compute_q0_response(DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Q0", "compute_q0_response"); + ModuleBase::timer::start("DFPT_Q0", "compute_q0_response"); // DFT+U reservation (U0): V_U is nonlocal (onsite projector), so the // position operator does NOT commute with the DFT+U potential. The // [r, V_U] commutator term must be handled separately in addition to // the occupation-matrix response (docc) when u_active() runs; this is // the hardest DFT+U piece and is deferred with the Plus_U wiring. (void)data; + ModuleBase::timer::end("DFPT_Q0", "compute_q0_response"); } } // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_q0.h b/source/source_pw/module_dfpt/dfpt_q0.h index 3b78db34f25..9d366030b4f 100644 --- a/source/source_pw/module_dfpt/dfpt_q0.h +++ b/source/source_pw/module_dfpt/dfpt_q0.h @@ -1,21 +1,14 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in the design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #ifndef DFPT_Q0_H #define DFPT_Q0_H #include "dfpt_pw_data.h" -#include "source_cell/unitcell.h" -#include "source_psi/psi.h" #include "source_basis/module_pw/pw_basis.h" #include "source_basis/module_pw/pw_basis_k.h" +#include "source_cell/unitcell.h" +#include "source_psi/psi.h" -namespace ModuleDFPT { +namespace ModuleDFPT +{ class DFPT_Pert; @@ -42,23 +35,23 @@ class DFPT_Pert; * SOLVED conduction-projected position response (QE zstar_eu/add_zstar_ue * anchoring; Gonze-Lee screened form). The position leg * Y^a_{k,v} = P_c x_a|psi_{k,v}>, (H(k)-eps_v) Y = P_c [H,x_a]|psi_v>, - * with the commutator rhs [H,x_a]|psi> = -(i/tpiba) dH/dk_a|psi> (the - * same velocity operator as above), is solved exactly by Sternheimer - * solves in DFPT_PW (solve_pos_resp, stashed per direction in the shared - * data) and therefore carries the complete conduction-space response; the - * eigenvector-truncated r-matrix contraction of the du form is only its - * nbands-cut approximation. With dpsi^kappa(scf) the converged q = 0 - * Sternheimer displacement responses: - * Z*_k,ab = Z_k delta_ab - 2 sum_{k,v occ} wg - * * Re - * (wg carries the spin degeneracy, so the prefactor is the -2*wk of - * add_zstar_ue). By the symmetry of the mixed second derivative of the - * total energy this equals the transposed leg - * -2*sum wg*Re that QE's zstar_eu - * computes with the electric-field responses; only one leg is needed. - * The dpsi^kappa Sternheimer gauge ( = 0) drops the - * occupied-occupied block of x exactly. The diamond C7 target - * (Z* -> 0 by inversion + ASR) requires the screened dpsi. + * with the commutator rhs [H,x_a]|psi> = -(i/tpiba) dH/dk_a|psi> (the + * same velocity operator as above), is solved exactly by Sternheimer + * solves in DFPT_PW (solve_pos_resp, stashed per direction in the shared + * data) and therefore carries the complete conduction-space response; the + * eigenvector-truncated r-matrix contraction of the du form is only its + * nbands-cut approximation. With dpsi^kappa(scf) the converged q = 0 + * Sternheimer displacement responses: + * Z*_k,ab = Z_k delta_ab - 2 sum_{k,v occ} wg + * * Re + * (wg carries the spin degeneracy, so the prefactor is the -2*wk of + * add_zstar_ue). By the symmetry of the mixed second derivative of the + * total energy this equals the transposed leg + * -2*sum wg*Re that QE's zstar_eu + * computes with the electric-field responses; only one leg is needed. + * The dpsi^kappa Sternheimer gauge ( = 0) drops the + * occupied-occupied block of x exactly. The diamond C7 target + * (Z* -> 0 by inversion + ASR) requires the screened dpsi. * With a symmetry-reduced k list both sums run over the irreducible k and * each partial tensor chi(k) is star-averaged: the physical partial at a * rotated star member Rk is R chi(k) R^T, and atom-resolved (Born) partials @@ -67,13 +60,13 @@ class DFPT_Pert; * The absolute calibration of both expressions is pinned by the diamond * end-to-end test in C7 (structure/symmetry by the C6 tests). */ -class DFPT_Q0 { -public: - DFPT_Q0(); - ~DFPT_Q0(); +class DFPT_Q0 +{ + public: + DFPT_Q0() = default; + ~DFPT_Q0() = default; - void init(UnitCell& ucell, ModulePW::PW_Basis* pw_rho, - ModulePW::PW_Basis_K* pw_wfc, DFPT_Pert* pert); + void init(UnitCell& ucell, ModulePW::PW_Basis* pw_rho, ModulePW::PW_Basis_K* pw_wfc, DFPT_Pert* pert); /// SCF dielectric tensor (QE dielec.f90 form): /// eps = 1 - (16 pi / Omega) sum_k wg sum_v Re @@ -84,7 +77,8 @@ class DFPT_Q0 { void compute_born(const psi::Psi>& psi, const ModuleBase::matrix& wg, - const ModuleBase::matrix& eig, DFPT_PW_Data& data); + const ModuleBase::matrix& eig, + DFPT_PW_Data& data); void compute_q0_response(DFPT_PW_Data& data); @@ -104,11 +98,12 @@ class DFPT_Q0 { // applies chi' = R chi R^T directly) plus the atom map iat -> image // atom under the same operation (built from the direct space // gmatrix/gtrans pair; species map to themselves). - struct StarMember { - ModuleBase::Matrix3 cart; ///< defaults to the identity - std::vector atom_map; ///< empty means the identity map + struct StarMember + { + ModuleBase::Matrix3 cart; ///< defaults to the identity + std::vector atom_map; ///< empty means the identity map }; - std::vector> stars_; ///< [ik] -> star members + std::vector> stars_; ///< [ik] -> star members /// rebuild stars_ for the stored k list (nk points); falls back to a /// single identity member per k when the point group is unavailable @@ -117,11 +112,9 @@ class DFPT_Q0 { /// chi_rot(a,b) = sum_{a'b'} R(a,a') R(b,b') chi(a',b') of a 3x3 /// partial tensor under a cartesian rotation - static void rotate_tensor(const ModuleBase::Matrix3& r, - const ModuleBase::matrix& chi, - double (&chi_rot)[9]); + static void rotate_tensor(const ModuleBase::Matrix3& r, const ModuleBase::matrix& chi, double (&chi_rot)[9]); -private: + private: UnitCell* ucell_ = nullptr; ModulePW::PW_Basis* pw_rho_ = nullptr; ModulePW::PW_Basis_K* pw_wfc_ = nullptr; diff --git a/source/source_pw/module_dfpt/dfpt_q0_pos.cpp b/source/source_pw/module_dfpt/dfpt_q0_pos.cpp new file mode 100644 index 00000000000..cf70e9b11f9 --- /dev/null +++ b/source/source_pw/module_dfpt/dfpt_q0_pos.cpp @@ -0,0 +1,273 @@ +#include "dfpt_q0.h" + +#include "dfpt_pert.h" +#include "source_base/global_function.h" +#include "source_base/timer.h" +#include "source_base/tool_title.h" + +#include +#include +#include + +namespace ModuleDFPT +{ + +namespace +{ + +/// [m][n] velocity matrix elements of one k point +typedef std::vector>>> VelocityMat; + +/// diagonal kinetic velocity, p^d_{mn} = , +/// with the k derivative in the same dimensionless 2*pi/lat0 units +/// build_vkb_dk uses (T = tpiba^2 |k+G|^2 in Ry a.u.) +void kinetic_velocity(int nbands, + int npwk, + double tpiba2, + const std::vector>& gk, + const psi::Psi>& psi, + int ik, + VelocityMat& p_mat) +{ + for (int m = 0; m < nbands; ++m) + { + for (int n = 0; n < nbands; ++n) + { + std::complex dot[3] + = {std::complex(0.0, 0.0), std::complex(0.0, 0.0), std::complex(0.0, 0.0)}; + for (int ig = 0; ig < npwk; ++ig) + { + const std::complex cc = std::conj(psi(ik, m, ig)) * psi(ik, n, ig); + for (int d = 0; d < 3; ++d) + { + dot[d] += 2.0 * tpiba2 * gk[ig][d] * cc; + } + } + for (int d = 0; d < 3; ++d) + { + p_mat[m][n][d] = dot[d]; + } + } + } +} + +/// projector -> (radial beta index, m channel) table, matching build_vkb +void build_projector_table(const pseudo& ncpp, std::vector& mu_ib, std::vector& mu_m) +{ + const int nh = ncpp.nh; + mu_ib.assign(nh, 0); + mu_m.assign(nh, 0); + int mu_idx = 0; + for (int ib = 0; ib < ncpp.nbeta; ++ib) + { + const int l = ncpp.lll[ib]; + for (int m = 0; m < 2 * l + 1; ++m) + { + if (mu_idx < nh) + { + mu_ib[mu_idx] = ib; + mu_m[mu_idx] = m; + } + ++mu_idx; + } + } +} + +/// becp_b[mu] = for all bands (wfc = vkb or dvkb) +void project_bands(int nbands, + int nh, + int npwk, + const std::vector>>& wfc, + const psi::Psi>& psi, + int ik, + std::vector>>& becp) +{ + const std::complex zero(0.0, 0.0); + becp.assign(nbands, std::vector>(nh, zero)); + for (int b = 0; b < nbands; ++b) + { + for (int mu = 0; mu < nh; ++mu) + { + for (int ig = 0; ig < npwk; ++ig) + { + becp[b][mu] += std::conj(wfc[mu][ig]) * psi(ik, b, ig); + } + } + } +} + +/// accumulate the two Hermitian-conjugate projector terms of one k +/// derivative direction into p_dir[m][n]: +/// D + D +/// with D_{mu,nu} = dion(ib_mu, ib_nu) delta_{m_mu, m_nu} (dVnl_dtau layout) +void add_nonlocal_dk(int nh, + const pseudo& ncpp, + const std::vector& mu_ib, + const std::vector& mu_m, + const std::vector>>& becp, + const std::vector>>& dbecp, + std::vector>>& p_dir) +{ + const int nbands = static_cast(p_dir.size()); + for (int m = 0; m < nbands; ++m) + { + for (int n = 0; n < static_cast(p_dir[m].size()); ++n) + { + std::complex term(0.0, 0.0); + for (int mu = 0; mu < nh; ++mu) + { + std::complex out_m(0.0, 0.0); + std::complex in_n(0.0, 0.0); + for (int nu = 0; nu < nh; ++nu) + { + if (mu_m[mu] != mu_m[nu]) + { + continue; // a radial m channel maps onto itself + } + const double dij = ncpp.dion(mu_ib[mu], mu_ib[nu]); + out_m += dij * becp[n][nu]; + in_n += dij * dbecp[n][nu]; + } + term += std::conj(dbecp[m][mu]) * out_m + std::conj(becp[m][mu]) * in_n; + } + p_dir[m][n] += term; + } + } +} + +/// nonlocal derivative part of the velocity operator: +/// dV_nl/dk_d = sum_{mu,nu} (|dvkb_mu> D_{mu,nu} D_{mu,nu} >& gk, + const psi::Psi>& psi, + int ik, + int npwk, + VelocityMat& p_mat) +{ + const int nbands = psi.get_nbands(); + for (int it = 0; it < ucell.ntype; ++it) + { + const pseudo& ncpp = ucell.atoms[it].ncpp; + const int nh = ncpp.nh; + if (nh == 0) + { + continue; + } + if (ncpp.tvanp || ncpp.has_so) + { + ModuleBase::WARNING_QUIT("DFPT_Q0::pos_matrix", + "DFPT velocity operator is implemented for " + "normal-conserving separable pseudopotentials only."); + } + std::vector mu_ib; + std::vector mu_m; + build_projector_table(ncpp, mu_ib, mu_m); + for (int ia = 0; ia < ucell.atoms[it].na; ++ia) + { + std::vector>> vkb; + pert.build_vkb(it, ia, gk, vkb); + // becp_b[mu] = for all bands + std::vector>> becp; + project_bands(nbands, nh, npwk, vkb, psi, ik, becp); + for (int d = 0; d < 3; ++d) + { + std::vector>> dvkb; + pert.build_vkb_dk(it, ia, d, gk, vkb, dvkb); + // dbecp_b[mu] = + std::vector>> dbecp; + project_bands(nbands, nh, npwk, dvkb, psi, ik, dbecp); + const std::complex zero(0.0, 0.0); + std::vector>> p_dir( + nbands, std::vector>(nbands, zero)); + add_nonlocal_dk(nh, ncpp, mu_ib, mu_m, becp, dbecp, p_dir); + for (int m = 0; m < nbands; ++m) + { + for (int n = 0; n < nbands; ++n) + { + p_mat[m][n][d] += p_dir[m][n]; + } + } + } + } + } +} + +/// velocity -> position: r = -i v / (tpiba (eps_m - eps_n)), r in bohr +/// (from [H, r] = -i dH/dk in Ry a.u.); degenerate pairs are skipped, +/// their gauge-dependent matrix elements carry no unique value. +void velocity_to_position(int nbands, + double tpiba, + const ModuleBase::matrix& eig, + int ik, + const VelocityMat& p_mat, + VelocityMat& r_mat_ik) +{ + const double degen_tol = 1.0e-8; ///< empirical parameter: eigenvalue gap (Ry) for the degenerate-pair skip + for (int m = 0; m < nbands; ++m) + { + for (int n = 0; n < nbands; ++n) + { + if (m == n) + { + continue; + } + const double de = eig(ik, m) - eig(ik, n); + if (std::abs(de) < degen_tol) + { + continue; + } + for (int d = 0; d < 3; ++d) + { + r_mat_ik[m][n][d] = std::complex(0.0, -1.0) * p_mat[m][n][d] / (tpiba * de); + } + } + } +} + +} // namespace + +void DFPT_Q0::pos_matrix(const psi::Psi>& psi, + const ModuleBase::matrix& eig, + std::vector>>>>& r_mat) +{ + ModuleBase::TITLE("DFPT_Q0", "pos_matrix"); + ModuleBase::timer::start("DFPT_Q0", "pos_matrix"); + const int nk = psi.get_nk(); + const int nbands = psi.get_nbands(); + r_mat.assign(nk, + std::vector>>>( + nbands, + std::vector>>( + nbands, + ModuleBase::Vector3>(0.0, 0.0, 0.0)))); + if (pw_wfc_ == nullptr || ucell_ == nullptr || pert_ == nullptr) + { + ModuleBase::timer::end("DFPT_Q0", "pos_matrix"); + return; + } + const double tpiba = ucell_->tpiba; + const double tpiba2 = tpiba * tpiba; + for (int ik = 0; ik < nk; ++ik) + { + const int npwk = pw_wfc_->npwk[ik]; + std::vector> gk(npwk); + for (int ig = 0; ig < npwk; ++ig) + { + gk[ig] = pw_wfc_->getgpluskcar(ik, ig); + } + // velocity operator dH/dk matrix elements (kinetic + nonlocal parts) + VelocityMat p_mat(nbands, + std::vector>>( + nbands, + ModuleBase::Vector3>(0.0, 0.0, 0.0))); + kinetic_velocity(nbands, npwk, tpiba2, gk, psi, ik, p_mat); + nonlocal_velocity(*ucell_, *pert_, gk, psi, ik, npwk, p_mat); + velocity_to_position(nbands, tpiba, eig, ik, p_mat, r_mat[ik]); + } + ModuleBase::timer::end("DFPT_Q0", "pos_matrix"); +} + +} // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_rho.cpp b/source/source_pw/module_dfpt/dfpt_rho.cpp index 8cb12ae5359..2a271c97168 100644 --- a/source/source_pw/module_dfpt/dfpt_rho.cpp +++ b/source/source_pw/module_dfpt/dfpt_rho.cpp @@ -1,66 +1,185 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in the design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #include "dfpt_rho.h" #include "dfpt_kq_basis.h" #include "source_base/constants.h" #include "source_base/global_function.h" +#include "source_base/timer.h" +#include "source_base/tool_title.h" #include "source_base/module_mixing/plain_mixing.h" + #include #include #include #include #include -namespace ModuleDFPT { +namespace ModuleDFPT +{ + +DFPT_Rho::DFPT_Rho() +{ +} + +// Defined here (after plain_mixing.h is included) so the unique_ptr deleter +// sees the complete Plain_Mixing type. The body is implicitly generated. +DFPT_Rho::~DFPT_Rho() = default; + +void DFPT_Rho::init(const Config& cfg) +{ + ModuleBase::TITLE("DFPT_Rho", "init"); + ModuleBase::timer::start("DFPT_Rho", "init"); + nspin_ = cfg.nspin; + nrxx_ = cfg.nrxx; + pw_rho_ = cfg.pw_rho; + pw_wfc_ = cfg.pw_wfc; + recip_matrix_ = cfg.recip_matrix; + mix_beta_ = cfg.mix_beta; + mix_type_ = cfg.mix_type; + kerker_a2_ = cfg.kerker_a2; + if (cfg.mix_type != "plain" && cfg.mix_type != "kerker") + { + ModuleBase::WARNING_QUIT("DFPT_Rho", "unsupported mix_type, expected plain or kerker"); + } + // make_unique is C++14; C++11 uses new directly through reset() + mixer_.reset(new Base_Mixing::Plain_Mixing(mix_beta_)); + ModuleBase::timer::end("DFPT_Rho", "init"); +} + +bool DFPT_Rho::is_gamma_q_(const ModuleBase::Vector3& q) +{ + const double gamma_tol = 1.0e-10; ///< empirical parameter: fractional-q tolerance for the Gamma test + return std::abs(q.x) < gamma_tol && std::abs(q.y) < gamma_tol && std::abs(q.z) < gamma_tol; +} -DFPT_Rho::DFPT_Rho() {} +void DFPT_Rho::add_band_(int ik, + int ib, + double w, + const std::complex* c_ptr, + const std::vector>& dpsi, + const DFPT_KQ_Basis& kq, + std::vector>& a_r) +{ + const int npw_kq = kq.get_npwk(); + // k+q G index -> rho-grid ig (both bases share the FFT cell) + std::vector kq2rho(npw_kq, -1); + for (int igl = 0; igl < npw_kq; ++igl) + { + kq2rho[igl] = kq.get_ig_rho(igl); + } + // periodic part u_nk(r) on the shared grid (phase-free FFT) + std::vector> u_r(pw_rho_->nrxx); + pw_wfc_->recip2real(c_ptr, u_r.data(), ik); + // periodic part du_nk(r): scatter the k+q coefficients onto the + // rho grid and transform (same convention, so the product is + // consistent with the u transform) + std::vector> d_recip(pw_rho_->npw, std::complex(0.0, 0.0)); + const int nd = std::min(npw_kq, static_cast(dpsi.size())); + for (int igl = 0; igl < nd; ++igl) + { + if (kq2rho[igl] >= 0) + { + d_recip[kq2rho[igl]] = dpsi[igl]; + } + } + std::vector> d_r(pw_rho_->nrxx); + pw_rho_->recip2real(d_recip.data(), d_r.data()); + // same normalization as the GS density accumulation + // (elecstate_pw.cpp rhoBandK: w1 = wg / omega), including the + // spin factor 2: QE incdrhoscf uses wgt = 2 * weight / omega + // at every q (the factor 2 is the spin degeneracy, not a + // Hermitian completion) + const double w1 = 2.0 * w / pw_rho_->omega; + for (int ir = 0; ir < pw_rho_->nrxx; ++ir) + { + a_r[ir] += w1 * std::conj(u_r[ir]) * d_r[ir]; + } +} -DFPT_Rho::~DFPT_Rho() { - if (mixer_ != nullptr) { - delete mixer_; - mixer_ = nullptr; +void DFPT_Rho::make_drho_r_(const std::vector>& drho_g, + const ModuleBase::Vector3& q_frac, + std::vector& drho_r) const +{ + // real-space manifest density: at q = 0 the completed coefficients are + // already the full (real) response; away from q = 0 the manifest is the + // real combination 2 Re[e^{i q r} A(r)] of the one-sided amplitude + std::vector> a_clean(pw_rho_->nrxx); + pw_rho_->recip2real(drho_g.data(), a_clean.data()); + drho_r.resize(pw_rho_->nrxx); + if (is_gamma_q_(q_frac)) + { + for (int ir = 0; ir < pw_rho_->nrxx; ++ir) + { + drho_r[ir] = a_clean[ir].real(); + } + } + else + { + for (int ix = 0; ix < pw_rho_->nx; ++ix) + { + for (int iy = 0; iy < pw_rho_->ny; ++iy) + { + for (int iz = 0; iz < pw_rho_->nz; ++iz) + { + const int ir = (ix * pw_rho_->ny + iy) * pw_rho_->nz + iz; + const double theta + = ModuleBase::TWO_PI + * (q_frac.x * ix / pw_rho_->nx + q_frac.y * iy / pw_rho_->ny + q_frac.z * iz / pw_rho_->nz); + drho_r[ir] = 2.0 * (a_clean[ir].real() * std::cos(theta) - a_clean[ir].imag() * std::sin(theta)); + } + } + } } } -void DFPT_Rho::init(int nspin, int nrxx, ModulePW::PW_Basis* pw_rho, - ModulePW::PW_Basis_K* pw_wfc, - const ModuleBase::Matrix3& recip_matrix, - const std::string& mix_type, double mix_beta, - double kerker_a2) { - nspin_ = nspin; - nrxx_ = nrxx; - pw_rho_ = pw_rho; - pw_wfc_ = pw_wfc; - recip_matrix_ = recip_matrix; - mix_beta_ = mix_beta; - mix_type_ = mix_type; - kerker_a2_ = kerker_a2; - if (mix_type != "plain" && mix_type != "kerker") +void DFPT_Rho::zero_neg_q_(const ModuleBase::Vector3& q_cart, + std::vector>& drho_g) const +{ + // charge conservation: the Delta = -q harmonic (G+q = 0 component of the + // response density) must vanish whenever -q falls on a reciprocal + // lattice vector; for a generic q inside the cell this never triggers + const ModuleBase::Vector3 mq_cart(-q_cart.x, -q_cart.y, -q_cart.z); + const ModuleBase::Vector3 mfrac = mq_cart * recip_matrix_.Inverse(); + const double mr[3] = {std::round(mfrac.x), std::round(mfrac.y), std::round(mfrac.z)}; + const double gvec_match_tol = 1.0e-6; ///< empirical parameter: folded -q reciprocal-vector match tolerance + if (std::abs(mfrac.x - mr[0]) < gvec_match_tol && std::abs(mfrac.y - mr[1]) < gvec_match_tol + && std::abs(mfrac.z - mr[2]) < gvec_match_tol) { - ModuleBase::WARNING_QUIT("DFPT_Rho", - "unsupported mix_type, expected plain or kerker"); + // locate the rho-grid G equal to -q through its FFT cell + const int cix = (static_cast(mr[0]) % pw_rho_->nx + pw_rho_->nx) % pw_rho_->nx; + const int ciy = (static_cast(mr[1]) % pw_rho_->ny + pw_rho_->ny) % pw_rho_->ny; + const int ciz = (static_cast(mr[2]) % pw_rho_->nz + pw_rho_->nz) % pw_rho_->nz; + for (int ig = 0; ig < pw_rho_->npw; ++ig) + { + const int isz = pw_rho_->ig2isz[ig]; + const int iz = isz % pw_rho_->nz; + const int is = isz / pw_rho_->nz; + const int ixy = pw_rho_->is2fftixy[is]; + const int ix = ixy / pw_rho_->fftny; + const int iy = ixy % pw_rho_->fftny; + if (ix == cix && iy == ciy && iz == ciz) + { + drho_g[ig] = std::complex(0.0, 0.0); + break; + } + } } - delete mixer_; - mixer_ = new Base_Mixing::Plain_Mixing(mix_beta_); } void DFPT_Rho::compute_drho(const psi::Psi>& psi, - const ModuleBase::matrix& wg, int q_idx, - DFPT_PW_Data& data) { - if (pw_rho_ == nullptr || pw_wfc_ == nullptr) { + const ModuleBase::matrix& wg, + int q_idx, + DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Rho", "compute_drho"); + ModuleBase::timer::start("DFPT_Rho", "compute_drho"); + if (pw_rho_ == nullptr || pw_wfc_ == nullptr) + { + ModuleBase::timer::end("DFPT_Rho", "compute_drho"); return; } if (nspin_ != 1) { - ModuleBase::WARNING_QUIT("DFPT_Rho", - "only nspin = 1 is supported in the design phase"); + ModuleBase::WARNING_QUIT("DFPT_Rho", "only nspin = 1 is supported in the design phase"); } const int nk = psi.get_nk(); const int nbands = psi.get_nbands(); @@ -68,46 +187,19 @@ void DFPT_Rho::compute_drho(const psi::Psi>& psi, const ModuleBase::Vector3 q_cart = q_frac * recip_matrix_; std::vector> a_r(pw_rho_->nrxx, std::complex(0.0, 0.0)); - std::vector> u_r(pw_rho_->nrxx); - std::vector> d_r(pw_rho_->nrxx); - std::vector> d_recip(pw_rho_->npw, std::complex(0.0, 0.0)); DFPT_KQ_Basis kq; - for (int ik = 0; ik < nk; ++ik) { + for (int ik = 0; ik < nk; ++ik) + { kq.init(pw_wfc_, pw_rho_, q_cart, ik); - const int npw_kq = kq.get_npwk(); - // k+q G index -> rho-grid ig (both bases share the FFT cell) - std::vector kq2rho(npw_kq, -1); - for (int igl = 0; igl < npw_kq; ++igl) { - kq2rho[igl] = kq.get_ig_rho(igl); - } - for (int ib = 0; ib < nbands; ++ib) { - const double w = wg(ik, ib); - if (!dfpt_band_occupied(wg, ik, ib)) { + for (int ib = 0; ib < nbands; ++ib) + { + if (!dfpt_band_occupied(wg, ik, ib)) + { continue; // unoccupied band: no contribution to the density } - // periodic part u_nk(r) on the shared grid (phase-free FFT) - pw_wfc_->recip2real(&psi(ik, ib, 0), u_r.data(), ik); - // periodic part du_nk(r): scatter the k+q coefficients onto the - // rho grid and transform (same convention, so the product is - // consistent with the u transform) - std::fill(d_recip.begin(), d_recip.end(), std::complex(0.0, 0.0)); + const double w = wg(ik, ib); const std::vector> dpsi = data.get_dpsi(q_idx, ik, ib); - const int nd = std::min(npw_kq, static_cast(dpsi.size())); - for (int igl = 0; igl < nd; ++igl) { - if (kq2rho[igl] >= 0) { - d_recip[kq2rho[igl]] = dpsi[igl]; - } - } - pw_rho_->recip2real(d_recip.data(), d_r.data()); - // same normalization as the GS density accumulation - // (elecstate_pw.cpp rhoBandK: w1 = wg / omega), including the - // spin factor 2: QE incdrhoscf uses wgt = 2 * weight / omega - // at every q (the factor 2 is the spin degeneracy, not a - // Hermitian completion) - const double w1 = 2.0 * w / pw_rho_->omega; - for (int ir = 0; ir < pw_rho_->nrxx; ++ir) { - a_r[ir] += w1 * std::conj(u_r[ir]) * d_r[ir]; - } + add_band_(ik, ib, w, &psi(ik, ib, 0), dpsi, kq, a_r); } } @@ -120,11 +212,10 @@ void DFPT_Rho::compute_drho(const psi::Psi>& psi, // one-sided sticks whose -G falls outside it. Away from q = 0 the +q // harmonic of the response is exactly the one-sided object and no // completion applies. - const bool q_is_zero = (std::abs(q_frac.x) < 1.0e-10 - && std::abs(q_frac.y) < 1.0e-10 - && std::abs(q_frac.z) < 1.0e-10); - if (q_is_zero) { - for (int ir = 0; ir < pw_rho_->nrxx; ++ir) { + if (is_gamma_q_(q_frac)) + { + for (int ir = 0; ir < pw_rho_->nrxx; ++ir) + { a_r[ir] = std::complex(a_r[ir].real(), 0.0); } } @@ -133,78 +224,31 @@ void DFPT_Rho::compute_drho(const psi::Psi>& psi, std::vector> drho_g(pw_rho_->npw); pw_rho_->real2recip(a_r.data(), drho_g.data()); - // charge conservation: the Delta = -q harmonic (G+q = 0 component of the - // response density) must vanish whenever -q falls on a reciprocal - // lattice vector; for a generic q inside the cell this never triggers - { - const ModuleBase::Vector3 mq_cart(-q_cart.x, -q_cart.y, -q_cart.z); - const ModuleBase::Vector3 mfrac = mq_cart * recip_matrix_.Inverse(); - const double mr[3] = {std::round(mfrac.x), std::round(mfrac.y), std::round(mfrac.z)}; - if (std::abs(mfrac.x - mr[0]) < 1.0e-6 && - std::abs(mfrac.y - mr[1]) < 1.0e-6 && - std::abs(mfrac.z - mr[2]) < 1.0e-6) - { - // locate the rho-grid G equal to -q through its FFT cell - const int cix = (static_cast(mr[0]) % pw_rho_->nx + pw_rho_->nx) % pw_rho_->nx; - const int ciy = (static_cast(mr[1]) % pw_rho_->ny + pw_rho_->ny) % pw_rho_->ny; - const int ciz = (static_cast(mr[2]) % pw_rho_->nz + pw_rho_->nz) % pw_rho_->nz; - int ig0 = -1; - for (int ig = 0; ig < pw_rho_->npw; ++ig) { - const int isz = pw_rho_->ig2isz[ig]; - const int iz = isz % pw_rho_->nz; - const int is = isz / pw_rho_->nz; - const int ixy = pw_rho_->is2fftixy[is]; - const int ix = ixy / pw_rho_->fftny; - const int iy = ixy % pw_rho_->fftny; - if (ix == cix && iy == ciy && iz == ciz) { - ig0 = ig; - break; - } - } - if (ig0 >= 0) { - drho_g[ig0] = std::complex(0.0, 0.0); - } - } - } + // charge conservation: zero the Delta = -q harmonic + zero_neg_q_(q_cart, drho_g); data.set_drho_g(q_idx, 0, drho_g); - // real-space manifest density: at q = 0 the completed coefficients are - // already the full (real) response; away from q = 0 the manifest is the - // real combination 2 Re[e^{i q r} A(r)] of the one-sided amplitude - std::vector> a_clean(pw_rho_->nrxx); - pw_rho_->recip2real(drho_g.data(), a_clean.data()); - std::vector drho_r(pw_rho_->nrxx); - if (q_is_zero) { - for (int ir = 0; ir < pw_rho_->nrxx; ++ir) { - drho_r[ir] = a_clean[ir].real(); - } - } else { - for (int ix = 0; ix < pw_rho_->nx; ++ix) { - for (int iy = 0; iy < pw_rho_->ny; ++iy) { - for (int iz = 0; iz < pw_rho_->nz; ++iz) { - const int ir = (ix * pw_rho_->ny + iy) * pw_rho_->nz + iz; - const double theta = ModuleBase::TWO_PI * - (q_frac.x * ix / pw_rho_->nx + - q_frac.y * iy / pw_rho_->ny + - q_frac.z * iz / pw_rho_->nz); - drho_r[ir] = 2.0 * (a_clean[ir].real() * std::cos(theta) - - a_clean[ir].imag() * std::sin(theta)); - } - } - } - } + // real-space manifest density + std::vector drho_r; + make_drho_r_(drho_g, q_frac, drho_r); data.set_drho_r(q_idx, 0, drho_r); // remember the freshly computed output for the mixing step - if (q_idx >= static_cast(drho_out_.size())) { + if (q_idx >= static_cast(drho_out_.size())) + { drho_out_.resize(q_idx + 1); } drho_out_[q_idx].assign(1, drho_g); + ModuleBase::timer::end("DFPT_Rho", "compute_drho"); } void DFPT_Rho::cal_docc(const psi::Psi>& psi, - const ModuleBase::matrix& wg, int q_idx, - DFPT_PW_Data& data) { + const ModuleBase::matrix& wg, + int q_idx, + DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Rho", "cal_docc"); + ModuleBase::timer::start("DFPT_Rho", "cal_docc"); // Reserved first-order occupation matrix (docc) for DFT+U (U0). // The physical cross terms need the beta projectors at both k and k+q // (a PW-side adapter of the build_vkb machinery); they land together @@ -213,61 +257,84 @@ void DFPT_Rho::cal_docc(const psi::Psi>& psi, // and never reach this accumulation: // cross term: Re(becp(k+q, dpsi) * becp(k, psi)) (response) // frozen term: becp(k, psi) * dbecp_f(k, psi) (GS k) - if (!data.with_u()) { + if (!data.with_u()) + { + ModuleBase::timer::end("DFPT_Rho", "cal_docc"); return; } (void)psi; (void)wg; (void)q_idx; (void)data; + ModuleBase::timer::end("DFPT_Rho", "cal_docc"); } -void DFPT_Rho::reset_mixing(int q_idx) { - if (q_idx < 0) { +void DFPT_Rho::reset_mixing(int q_idx) +{ + ModuleBase::TITLE("DFPT_Rho", "reset_mixing"); + ModuleBase::timer::start("DFPT_Rho", "reset_mixing"); + if (q_idx < 0) + { + ModuleBase::timer::end("DFPT_Rho", "reset_mixing"); return; } - if (q_idx < static_cast(drho_in_.size())) { + if (q_idx < static_cast(drho_in_.size())) + { drho_in_[q_idx].clear(); } - if (q_idx < static_cast(residual_.size())) { + if (q_idx < static_cast(residual_.size())) + { residual_[q_idx] = 0.0; } + ModuleBase::timer::end("DFPT_Rho", "reset_mixing"); } -void DFPT_Rho::mix_drho(int q_idx, DFPT_PW_Data& data) { - if (mixer_ == nullptr || pw_rho_ == nullptr) { +void DFPT_Rho::mix_drho(int q_idx, DFPT_PW_Data& data) +{ + ModuleBase::TITLE("DFPT_Rho", "mix_drho"); + ModuleBase::timer::start("DFPT_Rho", "mix_drho"); + if (mixer_ == nullptr || pw_rho_ == nullptr) + { + ModuleBase::timer::end("DFPT_Rho", "mix_drho"); return; } const std::vector> out = data.get_drho_g(q_idx, 0); - if (out.empty() || static_cast(out.size()) != pw_rho_->npw) { + if (out.empty() || static_cast(out.size()) != pw_rho_->npw) + { + ModuleBase::timer::end("DFPT_Rho", "mix_drho"); return; } const int npw = pw_rho_->npw; - if (q_idx >= static_cast(drho_in_.size())) { + if (q_idx >= static_cast(drho_in_.size())) + { drho_in_.resize(q_idx + 1); residual_.resize(q_idx + 1, 0.0); } // first iteration starts from a zero input density - if (drho_in_[q_idx].empty()) { + if (drho_in_[q_idx].empty()) + { drho_in_[q_idx].assign(1, std::vector>(npw, std::complex(0.0, 0.0))); } const std::vector>& rin = drho_in_[q_idx][0]; std::vector> mixed(npw); + const double w2_floor = 1.0e-12; ///< empirical parameter: |G+q|^2 zero-shell guard (1/lat0^2, Kerker freeze) // the fractional q is needed both by the Kerker screen and by the // real-space manifest below; the q-shifted |G+q| convention matches // v_hartree_q (gcar + q_frac * recip, 1/lat0^2 units) const ModuleBase::Vector3 q_frac = data.get_qvec(q_idx); - if (mix_type_ == "kerker") { + if (mix_type_ == "kerker") + { const ModuleBase::Vector3 q_cart = q_frac * recip_matrix_; std::vector> rin_s(npw); std::vector> out_s(npw); std::vector> mixed_s(npw); - for (int ig = 0; ig < npw; ++ig) { + for (int ig = 0; ig < npw; ++ig) + { const ModuleBase::Vector3 w = pw_rho_->gcar[ig] + q_cart; const double w2 = w * w; // |G+q| = 0 harmonic: f = 0, frozen at rin (that harmonic is // dropped by compute_drho, so both inputs are zero there) - const double f = (w2 < 1.0e-12) ? 0.0 : w2 / (w2 + kerker_a2_); + const double f = (w2 < w2_floor) ? 0.0 : w2 / (w2 + kerker_a2_); rin_s[ig] = f * rin[ig]; out_s[ig] = f * out[ig]; } @@ -279,20 +346,20 @@ void DFPT_Rho::mix_drho(int q_idx, DFPT_PW_Data& data) { // add back the screened-out part: mixed = rin + beta f (out - rin), // i.e. a plain mix with the per-shell coefficient beta f_g while // the stored density stays physical (not screen-scaled) - for (int ig = 0; ig < npw; ++ig) { + for (int ig = 0; ig < npw; ++ig) + { mixed[ig] = rin[ig] + (mixed_s[ig] - rin_s[ig]); } - } else { - mixer_->plain_mix(mixed.data(), - rin.data(), - out.data(), - npw, - std::function*)>()); + } + else + { + mixer_->plain_mix(mixed.data(), rin.data(), out.data(), npw, std::function*)>()); } // relative residual ||out - in|| / ||out|| double dn2 = 0.0; double o2 = 0.0; - for (int ig = 0; ig < npw; ++ig) { + for (int ig = 0; ig < npw; ++ig) + { dn2 += std::norm(out[ig] - rin[ig]); o2 += std::norm(out[ig]); } @@ -303,67 +370,61 @@ void DFPT_Rho::mix_drho(int q_idx, DFPT_PW_Data& data) { // rebuild the real-space manifest from the mixed coefficients (q = 0: // completed coefficients are the full real response; otherwise the // one-sided 2 Re[e^{i q r} A(r)] manifest) - const bool q_is_zero = (std::abs(q_frac.x) < 1.0e-10 - && std::abs(q_frac.y) < 1.0e-10 - && std::abs(q_frac.z) < 1.0e-10); - std::vector> a_clean(pw_rho_->nrxx); - pw_rho_->recip2real(mixed.data(), a_clean.data()); - std::vector drho_r(pw_rho_->nrxx); - if (q_is_zero) { - for (int ir = 0; ir < pw_rho_->nrxx; ++ir) { - drho_r[ir] = a_clean[ir].real(); - } - } else { - for (int ix = 0; ix < pw_rho_->nx; ++ix) { - for (int iy = 0; iy < pw_rho_->ny; ++iy) { - for (int iz = 0; iz < pw_rho_->nz; ++iz) { - const int ir = (ix * pw_rho_->ny + iy) * pw_rho_->nz + iz; - const double theta = ModuleBase::TWO_PI * - (q_frac.x * ix / pw_rho_->nx + - q_frac.y * iy / pw_rho_->ny + - q_frac.z * iz / pw_rho_->nz); - drho_r[ir] = 2.0 * (a_clean[ir].real() * std::cos(theta) - - a_clean[ir].imag() * std::sin(theta)); - } - } - } - } + std::vector drho_r; + make_drho_r_(mixed, q_frac, drho_r); data.set_drho_r(q_idx, 0, drho_r); + ModuleBase::timer::end("DFPT_Rho", "mix_drho"); } -double DFPT_Rho::get_residual(int q_idx, DFPT_PW_Data& data) const { +double DFPT_Rho::get_residual(int q_idx, DFPT_PW_Data& data) const +{ + ModuleBase::TITLE("DFPT_Rho", "get_residual"); + ModuleBase::timer::start("DFPT_Rho", "get_residual"); (void)data; - if (q_idx < 0 || q_idx >= static_cast(residual_.size())) { + if (q_idx < 0 || q_idx >= static_cast(residual_.size())) + { + ModuleBase::timer::end("DFPT_Rho", "get_residual"); return 0.0; } + ModuleBase::timer::end("DFPT_Rho", "get_residual"); return residual_[q_idx]; } void DFPT_Rho::v_hartree_q(const ModuleBase::Vector3& q_cart, const std::vector>& drho_g, - std::vector>& dv_ha_g) const { - if (pw_rho_ == nullptr) { + std::vector>& dv_ha_g) const +{ + ModuleBase::TITLE("DFPT_Rho", "v_hartree_q"); + ModuleBase::timer::start("DFPT_Rho", "v_hartree_q"); + if (pw_rho_ == nullptr) + { dv_ha_g.clear(); + ModuleBase::timer::end("DFPT_Rho", "v_hartree_q"); return; } const int npw = pw_rho_->npw; - if (static_cast(drho_g.size()) != npw) { + if (static_cast(drho_g.size()) != npw) + { dv_ha_g.clear(); + ModuleBase::timer::end("DFPT_Rho", "v_hartree_q"); return; } dv_ha_g.assign(npw, std::complex(0.0, 0.0)); - for (int ig = 0; ig < npw; ++ig) { + const double w2_floor = 1.0e-12; ///< empirical parameter: |G+q|^2 zero-shell guard (1/lat0^2, Coulomb skip) + for (int ig = 0; ig < npw; ++ig) + { const ModuleBase::Vector3 w = pw_rho_->gcar[ig] + q_cart; const double w2_lat0 = w * w; // 1/lat0^2 units, like pw_rho_->gg // skip |G+q| = 0 (ig = -q): the q-shifted G=0 harmonic of the // Hartree kernel (v_hartree skips ig_gge0 the same way) - if (w2_lat0 < 1.0e-12) { + if (w2_lat0 < w2_floor) + { continue; } - const double fac = ModuleBase::e2 * ModuleBase::FOUR_PI - / (pw_rho_->tpiba2 * w2_lat0); + const double fac = ModuleBase::e2 * ModuleBase::FOUR_PI / (pw_rho_->tpiba2 * w2_lat0); dv_ha_g[ig] = fac * drho_g[ig]; } + ModuleBase::timer::end("DFPT_Rho", "v_hartree_q"); } } // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_rho.h b/source/source_pw/module_dfpt/dfpt_rho.h index f1b696d88d8..a21c26686a8 100644 --- a/source/source_pw/module_dfpt/dfpt_rho.h +++ b/source/source_pw/module_dfpt/dfpt_rho.h @@ -1,19 +1,13 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in the design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #ifndef DFPT_RHO_H #define DFPT_RHO_H #include "dfpt_pw_data.h" #include "source_base/matrix3.h" -#include "source_psi/psi.h" #include "source_basis/module_pw/pw_basis.h" #include "source_basis/module_pw/pw_basis_k.h" +#include "source_psi/psi.h" + +#include #include #include @@ -22,7 +16,10 @@ namespace Base_Mixing class Plain_Mixing; } -namespace ModuleDFPT { +namespace ModuleDFPT +{ + +class DFPT_KQ_Basis; /** * @brief First-order exchange-correlation kernel contract (C6). @@ -35,16 +32,17 @@ namespace ModuleDFPT { * includes pot_xc_fdm.h (minimal header dependencies), mirroring the * DFPT_Stern::LinearOperator injection convention. */ -class XC_First_Order { -public: +class XC_First_Order +{ + public: virtual ~XC_First_Order() = default; /// dvxc_r(r) = delta V_xc[drho_r](r), complex q-shifted amplitude on /// the shared real-space grid. Implementations must not resize or /// alias drho_r; dvxc_r is resized to drho_r.size() and fully /// overwritten. - virtual void apply(const std::vector>& drho_r, - std::vector>& dvxc_r) const = 0; + virtual void apply(const std::vector>& drho_r, std::vector>& dvxc_r) const + = 0; }; /** @@ -78,26 +76,38 @@ class XC_First_Order { * rin + beta_g (out - in) (physical, not screen-scaled); the |G+q| = 0 * harmonic (f = 0) is frozen, consistent with its drop in compute_drho. */ -class DFPT_Rho { -public: +class DFPT_Rho +{ + public: + /// aggregate config for init (no defaults: every field must be set) + struct Config + { + int nspin; + int nrxx; + ModulePW::PW_Basis* pw_rho; + ModulePW::PW_Basis_K* pw_wfc; + ModuleBase::Matrix3 recip_matrix; + std::string mix_type; // "plain" or "kerker" + double mix_beta; + double kerker_a2; // Kerker screen a^2, 1/lat0^2 (kerker only) + }; + DFPT_Rho(); ~DFPT_Rho(); - - void init(int nspin, int nrxx, ModulePW::PW_Basis* pw_rho, - ModulePW::PW_Basis_K* pw_wfc, - const ModuleBase::Matrix3& recip_matrix, - const std::string& mix_type, double mix_beta, - double kerker_a2); - - void compute_drho(const psi::Psi>& psi, - const ModuleBase::matrix& wg, int q_idx, + + void init(const Config& cfg); + + void compute_drho(const psi::Psi>& psi, + const ModuleBase::matrix& wg, + int q_idx, DFPT_PW_Data& data); - + /// first-order occupation matrix (docc) for DFT+U (U0 reservation). - void cal_docc(const psi::Psi>& psi, - const ModuleBase::matrix& wg, int q_idx, + void cal_docc(const psi::Psi>& psi, + const ModuleBase::matrix& wg, + int q_idx, DFPT_PW_Data& data); - + void mix_drho(int q_idx, DFPT_PW_Data& data); /// C7: drop the mixing state of q_idx so the next perturbation at the @@ -105,7 +115,7 @@ class DFPT_Rho { /// indexed by q only, while every (atom, direction) needs its own /// self-consistent cycle). void reset_mixing(int q_idx); - + /// C6: q-shifted first-order Hartree potential in reciprocal space, /// dV_H(G) = 4 pi e^2 / |G+q|^2 * drho_g, /// with the convention aligned with elecstate::H_Hartree_pw::v_hartree @@ -115,10 +125,31 @@ class DFPT_Rho { void v_hartree_q(const ModuleBase::Vector3& q_cart, const std::vector>& drho_g, std::vector>& dv_ha_g) const; - + double get_residual(int q_idx, DFPT_PW_Data& data) const; -private: + private: + /// accumulate one (ik, ib) band contribution to the real-space amplitude + void add_band_(int ik, + int ib, + double w, + const std::complex* c_ptr, + const std::vector>& dpsi, + const DFPT_KQ_Basis& kq, + std::vector>& a_r); + + /// rebuild the real-space manifest drho_r from G-space coefficients + void make_drho_r_(const std::vector>& drho_g, + const ModuleBase::Vector3& q_frac, + std::vector& drho_r) const; + + /// charge conservation: zero the Delta = -q harmonic when -q is a G vector + void zero_neg_q_(const ModuleBase::Vector3& q_cart, + std::vector>& drho_g) const; + + /// true if q is Gamma within 1e-10 + static bool is_gamma_q_(const ModuleBase::Vector3& q); + int nspin_ = 1; int nrxx_ = 0; ModulePW::PW_Basis* pw_rho_ = nullptr; @@ -130,9 +161,9 @@ class DFPT_Rho { std::string mix_type_; ///< Kerker screening parameter a^2 in 1/lat0^2 (same units as |G+q|^2) double kerker_a2_ = 0.0; - - Base_Mixing::Plain_Mixing* mixer_ = nullptr; - + + std::unique_ptr mixer_; + /// mixing state, q-shifted coefficients on the rho grid, [q][spin] std::vector>>> drho_in_; std::vector>>> drho_out_; diff --git a/source/source_pw/module_dfpt/dfpt_stern.cpp b/source/source_pw/module_dfpt/dfpt_stern.cpp index fe379c2f39b..06bc7e5b12d 100644 --- a/source/source_pw/module_dfpt/dfpt_stern.cpp +++ b/source/source_pw/module_dfpt/dfpt_stern.cpp @@ -1,26 +1,27 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in the design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #include "dfpt_stern.h" +#include "source_base/timer.h" +#include "source_base/tool_title.h" #include -namespace ModuleDFPT { +namespace ModuleDFPT +{ -DFPT_Stern::DFPT_Stern() {} +DFPT_Stern::DFPT_Stern() +{ +} -DFPT_Stern::~DFPT_Stern() {} +DFPT_Stern::~DFPT_Stern() +{ +} -namespace { +namespace +{ -double real_vdot(const std::vector>& a, - const std::vector>& b) +double real_vdot(const std::vector>& a, const std::vector>& b) { + ModuleBase::TITLE("DFPT_Stern", "real_vdot"); + ModuleBase::timer::start("DFPT_Stern", "real_vdot"); // Re = Re sum_i conj(a_i) b_i (the CG scalar products of a // Hermitian operator are real up to roundoff) double s = 0.0; @@ -28,6 +29,7 @@ double real_vdot(const std::vector>& a, { s += a[i].real() * b[i].real() + a[i].imag() * b[i].imag(); } + ModuleBase::timer::end("DFPT_Stern", "real_vdot"); return s; } @@ -37,6 +39,8 @@ void DFPT_Stern::apply_pv(const std::vector>>& const std::vector>& x, std::vector>& px) const { + ModuleBase::TITLE("DFPT_Stern", "apply_pv"); + ModuleBase::timer::start("DFPT_Stern", "apply_pv"); px = x; // two modified Gram-Schmidt sweeps keep the complement exact enough for // long CG chains even when the occupied set is only machine-orthonormal; @@ -58,6 +62,7 @@ void DFPT_Stern::apply_pv(const std::vector>>& } } } + ModuleBase::timer::end("DFPT_Stern", "apply_pv"); } int DFPT_Stern::solve(const LinearOperator& aop, @@ -68,11 +73,14 @@ int DFPT_Stern::solve(const LinearOperator& aop, std::vector>& dpsi, double& residual) const { + ModuleBase::TITLE("DFPT_Stern", "solve"); + ModuleBase::timer::start("DFPT_Stern", "solve"); const int n = aop.dimension(); dpsi.assign(n, std::complex(0.0, 0.0)); if (n == 0 || static_cast(b.size()) != n || max_iter <= 0) { residual = 0.0; + ModuleBase::timer::end("DFPT_Stern", "solve"); return 0; } for (size_t m = 0; m < occ_kq.size(); ++m) @@ -80,6 +88,7 @@ int DFPT_Stern::solve(const LinearOperator& aop, if (static_cast(occ_kq[m].size()) != n) { residual = 0.0; + ModuleBase::timer::end("DFPT_Stern", "solve"); return 0; } } @@ -87,11 +96,13 @@ int DFPT_Stern::solve(const LinearOperator& aop, std::vector> pb(n); apply_pv(occ_kq, b, pb); const double bnorm = std::sqrt(real_vdot(pb, pb)); - if (bnorm < 1.0e-300) + const double homog_bnorm_floor = 1.0e-300; ///< empirical parameter: rhs norm floor for the homogeneous case + if (bnorm < homog_bnorm_floor) { // the right-hand side lies inside the occupied subspace: the // projected system is homogeneous and dpsi = 0 solves it exactly residual = 0.0; + ModuleBase::timer::end("DFPT_Stern", "solve"); return 0; } @@ -146,6 +157,7 @@ int DFPT_Stern::solve(const LinearOperator& aop, apply_pv(occ_kq, dpsi, tmp); dpsi.swap(tmp); residual = std::sqrt(rnorm2) / bnorm; + ModuleBase::timer::end("DFPT_Stern", "solve"); return used; } diff --git a/source/source_pw/module_dfpt/dfpt_stern.h b/source/source_pw/module_dfpt/dfpt_stern.h index 762697ae22b..325e6583f07 100644 --- a/source/source_pw/module_dfpt/dfpt_stern.h +++ b/source/source_pw/module_dfpt/dfpt_stern.h @@ -1,18 +1,11 @@ -// ============================================================ -// This code is added by Mohan Chen on 2026-05-18. -// This code is currently in the design phase and has not been -// put into production yet. It may change in the future. -// Please use this code with caution. Only developers who know -// what they are doing should use this code. -// ============================================================ - #ifndef DFPT_STERN_H #define DFPT_STERN_H #include #include -namespace ModuleDFPT { +namespace ModuleDFPT +{ /** * @brief Projected conjugate-gradient solver of the Sternheimer equation (C2). @@ -28,15 +21,17 @@ namespace ModuleDFPT { * production adapter reuses hamilt::Hamilt::ops->hPsi at the k+q point * (wired in C7), while unit tests supply analytic operators. */ -class DFPT_Stern { -public: +class DFPT_Stern +{ + public: DFPT_Stern(); ~DFPT_Stern(); /// Hermitian linear action y = (H(k+q) - eps) x on the k+q basis; the /// eigenvalue shift is carried inside the implementation. - class LinearOperator { - public: + class LinearOperator + { + public: virtual ~LinearOperator() = default; virtual int dimension() const = 0; virtual void apply(const std::complex* x, std::complex* y) const = 0; @@ -62,7 +57,7 @@ class DFPT_Stern { std::vector>& dpsi, double& residual) const; -private: + private: /// P_c x by modified Gram-Schmidt against the occupied states; safe for /// px to alias x (projection coefficients are collected before subtracting) void apply_pv(const std::vector>>& occ_kq, diff --git a/source/source_pw/module_dfpt/test/CMakeLists.txt b/source/source_pw/module_dfpt/test/CMakeLists.txt index b14ec5ab375..95cc5932e5c 100644 --- a/source/source_pw/module_dfpt/test/CMakeLists.txt +++ b/source/source_pw/module_dfpt/test/CMakeLists.txt @@ -38,13 +38,22 @@ AddTest( LIBS parameter base device symmetry planewave SOURCES dfpt_pw_run_test.cpp ../dfpt_pw.cpp + ../dfpt_pw_init.cpp + ../dfpt_pw_run.cpp + ../dfpt_pw_solve.cpp + ../dfpt_pw_q0.cpp ../dfpt_pw_data.cpp ../dfpt_pert.cpp + ../dfpt_pert_vkb.cpp + ../dfpt_pert_nl.cpp ../dfpt_kq_basis.cpp ../dfpt_stern.cpp ../dfpt_rho.cpp ../dfpt_phon.cpp + ../dfpt_phon_ewald.cpp + ../dfpt_phon_elec.cpp ../dfpt_q0.cpp + ../dfpt_q0_pos.cpp ../dfpt_metal.cpp ../dfpt_hamilt_shift.cpp ../../../source_cell/qlist.cpp diff --git a/source/source_pw/module_dfpt/test/dfpt_kq_basis_test.cpp b/source/source_pw/module_dfpt/test/dfpt_kq_basis_test.cpp index 42a22003d12..22766c4a1dd 100644 --- a/source/source_pw/module_dfpt/test/dfpt_kq_basis_test.cpp +++ b/source/source_pw/module_dfpt/test/dfpt_kq_basis_test.cpp @@ -1,14 +1,16 @@ -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include -#include -#include +#include "source_pw/module_dfpt/dfpt_kq_basis.h" + #include "source_base/constants.h" #include "source_base/matrix3.h" #include "source_base/vector3.h" #include "source_basis/module_pw/pw_basis.h" #include "source_basis/module_pw/pw_basis_k.h" -#include "source_pw/module_dfpt/dfpt_kq_basis.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +#include +#include /************************************************ * unit test of DFPT_KQ_Basis (C0) @@ -31,7 +33,8 @@ * enumeration fixes. */ -namespace { +namespace +{ bool VecLess(const ModuleBase::Vector3& a, const ModuleBase::Vector3& b) { @@ -246,8 +249,7 @@ TEST_F(DFPTKQBasisTest, GammaQ0ReproducesWfcGrid) EXPECT_EQ(kq.get_npwk(), pw_.npw); // every selected vector lies inside the cutoff and on the brute-force set - const std::vector> ref = ReferenceSelection( - ModuleBase::Vector3(0.0, 0.0, 0.0)); + const std::vector> ref = ReferenceSelection(ModuleBase::Vector3(0.0, 0.0, 0.0)); EXPECT_EQ(static_cast(ref.size()), pw_.npw); const std::vector> sel = KqSet(kq); EXPECT_EQ(sel.size(), ref.size()); @@ -278,8 +280,7 @@ TEST_F(DFPTKQBasisTest, ShiftedCenterSelectsAsymmetricSphere) { // k = (0,0,0.5b): the |G+k|^2 cut keeps an asymmetric shell const double b = ModuleBase::TWO_PI / lat0_; - BuildBase({ModuleBase::Vector3(0.0, 0.0, 0.0), - ModuleBase::Vector3(0.0, 0.0, 0.5 * b)}); + BuildBase({ModuleBase::Vector3(0.0, 0.0, 0.0), ModuleBase::Vector3(0.0, 0.0, 0.5 * b)}); ModuleDFPT::DFPT_KQ_Basis kq; kq.init(&pw_, &prho_, ModuleBase::Vector3(0.0, 0.0, 0.0), 1); diff --git a/source/source_pw/module_dfpt/test/dfpt_pw_data_test.cpp b/source/source_pw/module_dfpt/test/dfpt_pw_data_test.cpp index 9d50330f2b4..843036257de 100644 --- a/source/source_pw/module_dfpt/test/dfpt_pw_data_test.cpp +++ b/source/source_pw/module_dfpt/test/dfpt_pw_data_test.cpp @@ -2,19 +2,17 @@ #include "gtest/gtest.h" #include #include -#define private public #include "source_cell/atom_pseudo.h" #include "source_cell/atom_spec.h" +#include "source_cell/magnetism.h" #include "source_cell/pseudo.h" #include "source_cell/qlist.h" #include "source_cell/unitcell.h" -#include "source_cell/magnetism.h" -#undef private -#include "source_base/parallel_global.h" +#include "dfpt_stru_fixture.h" #include "source_base/global_variable.h" +#include "source_base/parallel_global.h" #include "source_pw/module_dfpt/dfpt_pw_data.h" #include "source_pw/module_pwdft/dftu_base.h" -#include "dfpt_stru_fixture.h" // ctor/dtor stubs for the cell/spepot link closures live in the shared // dfpt_test_mocks.cpp compiled into every DFPT test binary. diff --git a/source/source_pw/module_dfpt/test/dfpt_pw_run_test.cpp b/source/source_pw/module_dfpt/test/dfpt_pw_run_test.cpp index 444e70aba04..73ed2219c4e 100644 --- a/source/source_pw/module_dfpt/test/dfpt_pw_run_test.cpp +++ b/source/source_pw/module_dfpt/test/dfpt_pw_run_test.cpp @@ -2,20 +2,18 @@ #include "gtest/gtest.h" #include #include -#define private public #include "source_cell/atom_pseudo.h" #include "source_cell/atom_spec.h" +#include "source_cell/magnetism.h" #include "source_cell/pseudo.h" #include "source_cell/qlist.h" #include "source_cell/unitcell.h" -#include "source_cell/magnetism.h" -#undef private -#include "source_base/parallel_global.h" +#include "dfpt_stru_fixture.h" #include "source_base/global_variable.h" +#include "source_base/parallel_global.h" #include "source_estate/module_charge/charge_mixing.h" -#include "source_pw/module_pwdft/dftu_base.h" #include "source_pw/module_dfpt/dfpt_pw.h" -#include "dfpt_stru_fixture.h" +#include "source_pw/module_pwdft/dftu_base.h" // ctor/dtor stubs for the cell/spepot/charge link closures live in the // shared dfpt_test_mocks.cpp compiled into every DFPT test binary. @@ -66,8 +64,18 @@ TEST_F(DFPT_PWRunTest, RunsPerIrrepLoopForAllQ) dfpt.set_max_iter(10); psi::Psi> psi; // skeleton mode: no bases wired (design-phase fallback of the irrep loop) - dfpt.init(ucell, psi, nullptr, nullptr, nullptr, std::vector(), - ModuleBase::matrix(), ModuleBase::matrix(), nullptr, 1.0, 15.0, nullptr); + dfpt.init(ucell, + psi, + nullptr, + nullptr, + nullptr, + std::vector(), + ModuleBase::matrix(), + ModuleBase::matrix(), + nullptr, + 1.0, + 15.0, + nullptr); dfpt.run(); // each of the 4 irreducible q points must expose 3*nat phonon modes @@ -82,8 +90,18 @@ TEST_F(DFPT_PWRunTest, DielectricAndBornAreExposed) { dfpt.set_qmesh(1, 1, 1); // Gamma-only q mesh psi::Psi> psi; - dfpt.init(ucell, psi, nullptr, nullptr, nullptr, std::vector(), - ModuleBase::matrix(), ModuleBase::matrix(), nullptr, 1.0, 15.0, nullptr); + dfpt.init(ucell, + psi, + nullptr, + nullptr, + nullptr, + std::vector(), + ModuleBase::matrix(), + ModuleBase::matrix(), + nullptr, + 1.0, + 15.0, + nullptr); dfpt.run(); // design-phase stubs return default-constructed matrices @@ -116,9 +134,22 @@ TEST_F(DFPT_PWRunTest, DftuReservationWithProviderRejectsInit) psi::Psi> psi; // death tests match the child's stderr, while WARNING_QUIT writes the // NOTICE block to std::cout; bridge the two inside the statement - EXPECT_EXIT({ - std::cout.rdbuf(std::cerr.rdbuf()); - dfpt.init(ucell, psi, nullptr, nullptr, nullptr, std::vector(), - ModuleBase::matrix(), ModuleBase::matrix(), nullptr, 1.0, 15.0, &dftu); - }, ::testing::ExitedWithCode(1), "DFT\\+U with DFPT is not supported"); + EXPECT_EXIT( + { + std::cout.rdbuf(std::cerr.rdbuf()); + dfpt.init(ucell, + psi, + nullptr, + nullptr, + nullptr, + std::vector(), + ModuleBase::matrix(), + ModuleBase::matrix(), + nullptr, + 1.0, + 15.0, + &dftu); + }, + ::testing::ExitedWithCode(1), + "DFT\\+U with DFPT is not supported"); } diff --git a/source/source_pw/module_dfpt/test/dfpt_stern_test.cpp b/source/source_pw/module_dfpt/test/dfpt_stern_test.cpp index 8742ec5d093..f44a15cdfe7 100644 --- a/source/source_pw/module_dfpt/test/dfpt_stern_test.cpp +++ b/source/source_pw/module_dfpt/test/dfpt_stern_test.cpp @@ -1,8 +1,9 @@ +#include "source_pw/module_dfpt/dfpt_stern.h" + #include "gtest/gtest.h" #include #include #include -#include "source_pw/module_dfpt/dfpt_stern.h" /************************************************ * unit test of DFPT_Stern (C2) @@ -24,7 +25,8 @@ * 3. projection properties and degenerate right-hand sides. */ -namespace { +namespace +{ unsigned g_seed = 20260814u; double test_rand() diff --git a/source/source_pw/module_dfpt/test/dfpt_stru_fixture.h b/source/source_pw/module_dfpt/test/dfpt_stru_fixture.h index ab31094e92e..36b1fa97fd4 100644 --- a/source/source_pw/module_dfpt/test/dfpt_stru_fixture.h +++ b/source/source_pw/module_dfpt/test/dfpt_stru_fixture.h @@ -1,10 +1,11 @@ #ifndef DFPT_STRU_FIXTURE_H #define DFPT_STRU_FIXTURE_H -#include -#include #include "source_cell/unitcell.h" + #include "gtest/gtest.h" +#include +#include // Shared gtest fixture for building a minimal cubic UnitCell from a // hand-written structure table (abbreviated from @@ -12,11 +13,9 @@ // MPI-side DFPT tests that drive the QList / DFPT_PW wiring // (dfpt_pw_data_test.cpp, dfpt_pw_run_test.cpp). // -// NOTE ON INCLUDE ORDER: every test that needs UnitCell private members -// includes the cell headers with `#define private public` BEFORE this -// header; the include guards then keep the fixture header's own includes -// inert. The fixture implementation (dfpt_stru_fixture.cpp) only touches -// public members, so it compiles without the define. +// All members touched by construct_ucell (UnitCell geometry fields and +// the Atom label/na/tau/taud vectors) are public, so tests include the +// cell headers normally. struct atomtype_ { diff --git a/source/source_pw/module_dfpt/test_serial/CMakeLists.txt b/source/source_pw/module_dfpt/test_serial/CMakeLists.txt index c82aebdf193..b5cfc3ef700 100644 --- a/source/source_pw/module_dfpt/test_serial/CMakeLists.txt +++ b/source/source_pw/module_dfpt/test_serial/CMakeLists.txt @@ -33,6 +33,8 @@ AddTest( LIBS parameter dfpt_planewave_serial device base symmetry SOURCES dfpt_pert_serial_test.cpp ../dfpt_pert.cpp + ../dfpt_pert_vkb.cpp + ../dfpt_pert_nl.cpp ../dfpt_pw_data.cpp ../dfpt_kq_basis.cpp ../../../source_cell/qlist.cpp @@ -67,7 +69,11 @@ AddTest( LIBS parameter dfpt_planewave_serial device base symmetry SOURCES dfpt_phon_serial_test.cpp ../dfpt_phon.cpp + ../dfpt_phon_ewald.cpp + ../dfpt_phon_elec.cpp ../dfpt_pert.cpp + ../dfpt_pert_vkb.cpp + ../dfpt_pert_nl.cpp ../dfpt_pw_data.cpp ../dfpt_kq_basis.cpp ../../../source_cell/qlist.cpp @@ -85,7 +91,10 @@ AddTest( LIBS parameter dfpt_planewave_serial device base symmetry SOURCES dfpt_q0_serial_test.cpp ../dfpt_q0.cpp + ../dfpt_q0_pos.cpp ../dfpt_pert.cpp + ../dfpt_pert_vkb.cpp + ../dfpt_pert_nl.cpp ../dfpt_pw_data.cpp ../dfpt_kq_basis.cpp ../../../source_cell/qlist.cpp diff --git a/source/source_pw/module_dfpt/test_serial/dfpt_pert_serial_test.cpp b/source/source_pw/module_dfpt/test_serial/dfpt_pert_serial_test.cpp index b080c97cde9..cff890dbb96 100644 --- a/source/source_pw/module_dfpt/test_serial/dfpt_pert_serial_test.cpp +++ b/source/source_pw/module_dfpt/test_serial/dfpt_pert_serial_test.cpp @@ -11,23 +11,18 @@ // the real serial initgrids/initparameters/setuptransform path on a shared // FFT grid, exactly like the production setup_pwrho/setup_pwwfc sequence. -#define private public +#include "dfpt_serial_fixture.h" +#include "source_base/constants.h" +#include "source_base/matrix3.h" +#include "source_base/vector3.h" #include "source_cell/atom_pseudo.h" #include "source_cell/atom_spec.h" -#include "source_cell/pseudo.h" #include "source_cell/qlist.h" #include "source_cell/unitcell.h" -#include "source_cell/magnetism.h" -#include "source_pw/module_pwdft/stru_fac.h" +#include "source_psi/psi.h" #include "source_pw/module_dfpt/dfpt_pert.h" -#undef private - -#include "source_base/constants.h" -#include "source_base/matrix3.h" -#include "source_base/vector3.h" +#include "source_pw/module_pwdft/stru_fac.h" #include "source_pw/module_pwdft/dftu_base.h" -#include "source_psi/psi.h" -#include "dfpt_serial_fixture.h" // ctor/dtor stubs for the cell/spepot/stru_fac link closures live in the // shared test/dfpt_test_mocks.cpp compiled into every DFPT test binary. @@ -106,8 +101,7 @@ TEST_F(DFPTPertSerialTest, DVlocDtauMatchesFiniteDifference) const double ap = -ModuleBase::TWO_PI * (w * (tau_ + eps * d)); const double am = -ModuleBase::TWO_PI * (w * (tau_ - eps * d)); const std::complex fd = VlocCoulomb((w * w) * ucell_.tpiba2) - * (std::polar(1.0, ap) - std::polar(1.0, am)) - / (2.0 * eps * lat0_); + * (std::polar(1.0, ap) - std::polar(1.0, am)) / (2.0 * eps * lat0_); EXPECT_NEAR(dv[ig].real(), fd.real(), 1.0e-9); EXPECT_NEAR(dv[ig].imag(), fd.imag(), 1.0e-9); } @@ -176,7 +170,7 @@ TEST_F(DFPTPertSerialTest, ApplyDvConvolutionMatchesAnalyticMatrixElement) const ModuleBase::Vector3 gpp = kq.get_gcar(igl); const std::complex e0 = AnalyticDVloc(0, gpp + q_cart_); const std::complex e1 = 0.7 * AnalyticDVloc(0, gpp - g1 + q_cart_) - + std::complex(0.3, 0.2) * AnalyticDVloc(0, gpp - g2 + q_cart_); + + std::complex(0.3, 0.2) * AnalyticDVloc(0, gpp - g2 + q_cart_); EXPECT_NEAR(d0[igl].real(), e0.real(), 1.0e-8); EXPECT_NEAR(d0[igl].imag(), e0.imag(), 1.0e-8); EXPECT_NEAR(d1[igl].real(), e1.real(), 1.0e-8); @@ -260,8 +254,7 @@ TEST_F(DFPTPertSerialTest, BuildVkbL0MatchesIndependentSimpson) const pseudo& p = ucell_.atoms[0].ncpp; const double dx = p.rab[0]; const double pref = ModuleBase::FOUR_PI / std::sqrt(ucell_.omega); - auto simpson = [&](const std::function& f, int n) - { + auto simpson = [&](const std::function& f, int n) { double s = f(0) + f(n - 1); for (int i = 1; i < n - 1; ++i) { @@ -274,16 +267,15 @@ TEST_F(DFPTPertSerialTest, BuildVkbL0MatchesIndependentSimpson) { const double g = std::sqrt(gk[ig] * gk[ig]) * ucell_.tpiba; // bohr^-1 // independent j0 and Simpson transform (no ModuleBase Sphbes/Integral) - auto f0 = [&](int i) - { + auto f0 = [&](int i) { const double gr = g * p.r[i]; const double j0 = (gr < 1.0e-12) ? 1.0 : std::sin(gr) / gr; return p.betar(0, i) * j0 * p.r[i]; }; const double vq = pref * simpson(f0, p.msh); const double arg = -ModuleBase::TWO_PI * (gk[ig] * tau_); - const std::complex expect = 0.5 * std::sqrt(1.0 / ModuleBase::PI) * vq - * std::complex(std::cos(arg), std::sin(arg)); + const std::complex expect + = 0.5 * std::sqrt(1.0 / ModuleBase::PI) * vq * std::complex(std::cos(arg), std::sin(arg)); EXPECT_NEAR(vkb[0][ig].real(), expect.real(), 1.0e-9 * std::max(1.0, std::abs(expect))); EXPECT_NEAR(vkb[0][ig].imag(), expect.imag(), 1.0e-9 * std::max(1.0, std::abs(expect))); } @@ -331,8 +323,7 @@ TEST_F(DFPTPertSerialTest, DVnlDtauMatchesOperatorFiniteDifference) // deterministic pseudo-random wavefunctions, normalized per band psi::Psi> psi(1, 2, npwk, npwk, true); unsigned seed = 20260814u; - auto rnd = [&]() - { + auto rnd = [&]() { seed = seed * 1664525u + 1013904223u; return ((seed >> 8) & 0xffffff) / 16777216.0 * 2.0 - 1.0; }; diff --git a/source/source_pw/module_dfpt/test_serial/dfpt_phon_serial_test.cpp b/source/source_pw/module_dfpt/test_serial/dfpt_phon_serial_test.cpp index 78838183099..2511020066a 100644 --- a/source/source_pw/module_dfpt/test_serial/dfpt_phon_serial_test.cpp +++ b/source/source_pw/module_dfpt/test_serial/dfpt_phon_serial_test.cpp @@ -10,24 +10,20 @@ // eigensolver and the LO-TO term. Runs without __MPI on the shared FFT grid // like the other DFPT serial tests. -#define private public +#include "dfpt_serial_fixture.h" +#include "source_base/complexmatrix.h" +#include "source_base/constants.h" +#include "source_base/matrix3.h" +#include "source_base/vector3.h" #include "source_cell/atom_pseudo.h" #include "source_cell/atom_spec.h" -#include "source_cell/pseudo.h" #include "source_cell/qlist.h" #include "source_cell/unitcell.h" -#include "source_cell/magnetism.h" -#include "source_pw/module_pwdft/stru_fac.h" +#include "source_psi/psi.h" +#include "source_pw/module_dfpt/dfpt_kq_basis.h" #include "source_pw/module_dfpt/dfpt_pert.h" #include "source_pw/module_dfpt/dfpt_phon.h" -#undef private - -#include "source_base/complexmatrix.h" -#include "source_base/constants.h" -#include "source_base/matrix3.h" -#include "source_base/vector3.h" -#include "source_psi/psi.h" -#include "dfpt_serial_fixture.h" +#include "source_pw/module_pwdft/stru_fac.h" // ctor/dtor stubs for the cell/spepot/stru_fac link closures live in the // shared test/dfpt_test_mocks.cpp compiled into every DFPT test binary. @@ -71,8 +67,7 @@ class DFPTPhonSerialTest : public DFPTSerialBase // (re)initialize the bases and the pert/phon wiring for a given (k, q) // pair; SetUp uses the default fixture values - void SetupPhon(const ModuleBase::Vector3& k_d, - const ModuleBase::Vector3& q_d) + void SetupPhon(const ModuleBase::Vector3& k_d, const ModuleBase::Vector3& q_d) { SetupBases(k_d, q_d, 2); pert_.init(ucell_, &pw_rho_, &pw_wfc_, sf_); @@ -83,8 +78,7 @@ class DFPTPhonSerialTest : public DFPTSerialBase double RyBohr2AmuToCm1() const { const double amu_kg = 1.66053906660e-27; // CODATA amu in kg - return std::sqrt(ModuleBase::RYDBERG_SI / amu_kg) - / (0.529177210903e-10 * 2.0 * ModuleBase::PI * 2.99792458e10); + return std::sqrt(ModuleBase::RYDBERG_SI / amu_kg) / (0.529177210903e-10 * 2.0 * ModuleBase::PI * 2.99792458e10); } // common setup of the isotropic loto closed-form tests: zero 6x6 @@ -159,8 +153,7 @@ class DFPTPhonSerialTest : public DFPTSerialBase for (size_t ic = 0; ic < psi_coef.size(); ++ic) { // AnalyticDVloc returns 0 at w = 0 (dVloc drop) - cross += psi_coef[ic] * std::conj(dpsi_inj[igl]) - * AnalyticDVloc(adir, gpp - psi_gcart[ic] + q_cart_); + cross += psi_coef[ic] * std::conj(dpsi_inj[igl]) * AnalyticDVloc(adir, gpp - psi_gcart[ic] + q_cart_); } } return cross; @@ -199,17 +192,14 @@ TEST_F(DFPTPhonSerialTest, IonIonAcousticSumRuleGamma) { rowsum += sqrtm[j / 3] * dyn(i, j); } - EXPECT_LT(std::abs(rowsum), 1.0e-6 * max_elem) - << "row " << i << " sum " << std::abs(rowsum); + EXPECT_LT(std::abs(rowsum), 1.0e-6 * max_elem) << "row " << i << " sum " << std::abs(rowsum); } // Hermitian for (int i = 0; i < 6; ++i) { for (int j = i + 1; j < 6; ++j) { - EXPECT_NEAR(std::abs(dyn(i, j) - std::conj(dyn(j, i))), - 0.0, - 1.0e-10 * max_elem); + EXPECT_NEAR(std::abs(dyn(i, j) - std::conj(dyn(j, i))), 0.0, 1.0e-10 * max_elem); } } } @@ -218,9 +208,9 @@ TEST_F(DFPTPhonSerialTest, IonIonGammaAcousticZeroModes) { // same two-atom cell: three acoustic eigenvalues vanish at Gamma MakeTwoAtomCell(); - data_.set_dynmat(0, ModuleBase::ComplexMatrix(6, 6, true)); - ModuleBase::ComplexMatrix& dyn = data_.dynmat_[0]; + ModuleBase::ComplexMatrix dyn(6, 6, true); phon_.ion_ion(ModuleBase::Vector3(0.0, 0.0, 0.0), dyn); + data_.set_dynmat(0, dyn); phon_.diagonalize(0, data_); const std::vector freq = data_.get_phon_freq(0); ASSERT_EQ(freq.size(), 6u); @@ -274,8 +264,7 @@ TEST_F(DFPTPhonSerialTest, IonIonGenericQVsDirectSum) for (int ib = 0; ib < 2; ++ib) { const bool self = (ib == ia); - const ModuleBase::Vector3 dt = - (ib == 0 ? tau1 : tau2) - (ia == 0 ? tau1 : tau2); + const ModuleBase::Vector3 dt = (ib == 0 ? tau1 : tau2) - (ia == 0 ? tau1 : tau2); for (int n1 = -nshell; n1 <= nshell; ++n1) { for (int n2 = -nshell; n2 <= nshell; ++n2) @@ -286,14 +275,12 @@ TEST_F(DFPTPhonSerialTest, IonIonGenericQVsDirectSum) { continue; } - const ModuleBase::Vector3 r( - (n1 * a_ + dt.x) * lat0_, - (n2 * a_ + dt.y) * lat0_, - (n3 * a_ + dt.z) * lat0_); + const ModuleBase::Vector3 r((n1 * a_ + dt.x) * lat0_, + (n2 * a_ + dt.y) * lat0_, + (n3 * a_ + dt.z) * lat0_); const double r2 = r * r; const double r5 = r2 * r2 * std::sqrt(r2); - const double ph = ModuleBase::TWO_PI - * (q_d_.x * n1 + q_d_.y * n2 + q_d_.z * n3); + const double ph = ModuleBase::TWO_PI * (q_d_.x * n1 + q_d_.y * n2 + q_d_.z * n3); const std::complex phase(std::cos(ph), std::sin(ph)); const double pref = -z[ia] * z[ib] * ModuleBase::e2 / std::sqrt(m[ia] * m[ib]); for (int da = 0; da < 3; ++da) @@ -305,14 +292,12 @@ TEST_F(DFPTPhonSerialTest, IonIonGenericQVsDirectSum) if (self) { ref(3 * ia + da, 3 * ia + db) - += z[ia] * z[ia] * ModuleBase::e2 / m[ia] * h0 - * (1.0 - phase); + += z[ia] * z[ia] * ModuleBase::e2 / m[ia] * h0 * (1.0 - phase); } else { ref(3 * ia + da, 3 * ib + db) += pref * h0 * phase; - ref(3 * ia + da, 3 * ia + db) - -= pref * std::sqrt(m[ib] / m[ia]) * h0; + ref(3 * ia + da, 3 * ia + db) -= pref * std::sqrt(m[ib] / m[ia]) * h0; } } } @@ -375,21 +360,15 @@ TEST_F(DFPTPhonSerialTest, AccumulateElectronAnalyticContraction) const std::vector> g0(1, ModuleBase::Vector3(0.0, 0.0, 0.0)); for (int adir = 0; adir < 3; ++adir) { - std::complex expect = wg(0, 0) * AnalyticCrossTerm(kq, - {std::complex(1.0, 0.0)}, - g0, - dpsi_inj, - adir); + std::complex expect + = wg(0, 0) * AnalyticCrossTerm(kq, {std::complex(1.0, 0.0)}, g0, dpsi_inj, adir); if (adir == 1) { expect = 2.0 * expect.real(); } expect /= ucell_.atoms[0].mass; - EXPECT_NEAR(std::abs(phon_.dynmat_accum_(1, adir) - expect), - 0.0, - 1.0e-7 * (1.0 + std::abs(expect))) - << "adir " << adir << " got " << phon_.dynmat_accum_(1, adir) - << " expect " << expect; + EXPECT_NEAR(std::abs(phon_.dynmat_accum()(1, adir) - expect), 0.0, 1.0e-7 * (1.0 + std::abs(expect))) + << "adir " << adir << " got " << phon_.dynmat_accum()(1, adir) << " expect " << expect; } // the dpsi slot must be restored to the injected solution @@ -413,8 +392,7 @@ TEST_F(DFPTPhonSerialTest, AccumulateElectronD2GateOffGenericQ) ModuleDFPT::DFPT_KQ_Basis kq; kq.init(&pw_wfc_, &pw_rho_, q_cart_, 0); - std::vector> dpsi_inj(kq.get_npwk(), - std::complex(0.0, 0.0)); + std::vector> dpsi_inj(kq.get_npwk(), std::complex(0.0, 0.0)); dpsi_inj[0] = std::complex(0.25, -0.15); if (kq.get_npwk() > 2) { @@ -427,21 +405,15 @@ TEST_F(DFPTPhonSerialTest, AccumulateElectronD2GateOffGenericQ) const std::vector> g0(1, ModuleBase::Vector3(0.0, 0.0, 0.0)); for (int adir = 0; adir < 3; ++adir) { - std::complex expect = wg(0, 0) * AnalyticCrossTerm(kq, - {std::complex(1.0, 0.0)}, - g0, - dpsi_inj, - adir); + std::complex expect + = wg(0, 0) * AnalyticCrossTerm(kq, {std::complex(1.0, 0.0)}, g0, dpsi_inj, adir); if (adir == 0) { expect = 2.0 * expect.real(); } expect /= ucell_.atoms[0].mass; - EXPECT_NEAR(std::abs(phon_.dynmat_accum_(0, adir) - expect), - 0.0, - 1.0e-7 * (1.0 + std::abs(expect))) - << "adir " << adir << " got " << phon_.dynmat_accum_(0, adir) - << " expect " << expect; + EXPECT_NEAR(std::abs(phon_.dynmat_accum()(0, adir) - expect), 0.0, 1.0e-7 * (1.0 + std::abs(expect))) + << "adir " << adir << " got " << phon_.dynmat_accum()(0, adir) << " expect " << expect; } } @@ -465,14 +437,11 @@ TEST_F(DFPTPhonSerialTest, AccumulateElectronD2CommensurateQ) // pairwise differences of |psi|^2 (the G=0 diagonal difference hits the // w=0 skip of the kernel); the (0,-1,1) difference makes the mixed // component K_{2,1} nonzero as well - const std::vector> gfrac - = {ModuleBase::Vector3(0.0, 0.0, 0.0), - ModuleBase::Vector3(0.0, 1.0, 0.0), - ModuleBase::Vector3(0.0, 0.0, 1.0)}; + const std::vector> gfrac = {ModuleBase::Vector3(0.0, 0.0, 0.0), + ModuleBase::Vector3(0.0, 1.0, 0.0), + ModuleBase::Vector3(0.0, 0.0, 1.0)}; const std::vector> ccoef - = {std::complex(1.0, 0.0), - std::complex(0.6, -0.3), - std::complex(-0.4, 0.25)}; + = {std::complex(1.0, 0.0), std::complex(0.6, -0.3), std::complex(-0.4, 0.25)}; const size_t ncomp = gfrac.size(); std::vector> gcart(ncomp); std::vector ig_of(ncomp, -1); @@ -482,12 +451,10 @@ TEST_F(DFPTPhonSerialTest, AccumulateElectronD2CommensurateQ) } for (int ig = 0; ig < npwk; ++ig) { - const ModuleBase::Vector3 gprim - = pw_wfc_.getgpluskcar(0, ig) - k_cart; + const ModuleBase::Vector3 gprim = pw_wfc_.getgpluskcar(0, ig) - k_cart; for (size_t ic = 0; ic < ncomp; ++ic) { - if (std::abs(gprim.x - gcart[ic].x) < 1e-10 - && std::abs(gprim.y - gcart[ic].y) < 1e-10 + if (std::abs(gprim.x - gcart[ic].x) < 1e-10 && std::abs(gprim.y - gcart[ic].y) < 1e-10 && std::abs(gprim.z - gcart[ic].z) < 1e-10) { ig_of[ic] = ig; @@ -541,21 +508,17 @@ TEST_F(DFPTPhonSerialTest, AccumulateElectronD2CommensurateQ) continue; } const double arg = -ModuleBase::TWO_PI * (g * tau_); - const std::complex kterm - = -(ucell_.tpiba * g[adir]) * (ucell_.tpiba * g[1]) - * VlocCoulomb(g2 * ucell_.tpiba2) - * std::complex(std::cos(arg), std::sin(arg)); + const std::complex kterm = -(ucell_.tpiba * g[adir]) * (ucell_.tpiba * g[1]) + * VlocCoulomb(g2 * ucell_.tpiba2) + * std::complex(std::cos(arg), std::sin(arg)); d2elem += std::conj(ccoef[i]) * ccoef[j] * kterm; } } expect += wg(0, 0) * d2elem; } expect /= ucell_.atoms[0].mass; - EXPECT_NEAR(std::abs(phon_.dynmat_accum_(1, adir) - expect), - 0.0, - 1.0e-7 * (1.0 + std::abs(expect))) - << "adir " << adir << " got " << phon_.dynmat_accum_(1, adir) - << " expect " << expect; + EXPECT_NEAR(std::abs(phon_.dynmat_accum()(1, adir) - expect), 0.0, 1.0e-7 * (1.0 + std::abs(expect))) + << "adir " << adir << " got " << phon_.dynmat_accum()(1, adir) << " expect " << expect; } } @@ -584,8 +547,7 @@ TEST_F(DFPTPhonSerialTest, DiagonalizeKnownMatrix) const std::vector freq = data_.get_phon_freq(0); ASSERT_EQ(freq.size(), 6u); std::vector expect; - auto block = [&expect](double a, double b, std::complex c) - { + auto block = [&expect](double a, double b, std::complex c) { const double mid = 0.5 * (a + b); const double rad = std::sqrt(std::pow(0.5 * (a - b), 2) + std::norm(c)); expect.push_back(mid + rad); @@ -593,9 +555,9 @@ TEST_F(DFPTPhonSerialTest, DiagonalizeKnownMatrix) }; block(lam[0], lam[1], dyn(0, 1)); // coupled pair block(lam[2], lam[3], dyn(2, 3)); // coupled pair - expect.push_back(lam[4]); // untouched diagonal + expect.push_back(lam[4]); // untouched diagonal expect.push_back(lam[5]); - for (double& e : expect) + for (double& e: expect) { const double s = (e >= 0.0) ? 1.0 : -1.0; e = s * std::sqrt(std::abs(e)) * RyBohr2AmuToCm1(); @@ -622,8 +584,7 @@ TEST_F(DFPTPhonSerialTest, AddLotoIsotropicClosedForm) phon_.add_loto(qhat, data_); // closed form: D_NAC(0x,1x) = 4pi e2/Omega * 1*2/(3) / sqrt(12*4) - const double expect = ModuleBase::FOUR_PI * ModuleBase::e2 / ucell_.omega / 3.0 - * 2.0 / std::sqrt(48.0); + const double expect = ModuleBase::FOUR_PI * ModuleBase::e2 / ucell_.omega / 3.0 * 2.0 / std::sqrt(48.0); const ModuleBase::ComplexMatrix dyn = data_.get_dynmat(0); EXPECT_NEAR(std::abs(dyn(0, 3) - std::complex(expect, 0.0)), 0.0, 1.0e-12); EXPECT_NEAR(std::abs(dyn(3, 0) - std::complex(expect, 0.0)), 0.0, 1.0e-12); @@ -711,12 +672,11 @@ TEST_F(DFPTPhonSerialTest, FormatReportsRegression) // fixture q = (0.13, 0, 0.07) direct; three crafted frequencies data_.set_phon_freq(0, std::vector{-7.32457, 517.491, 0.0}); const std::string qrep = phon_.format_q_report(0, data_); - const std::string expect_q - = " DFPT phonon frequencies at q #0 = (0.130000 0.000000 0.070000) " - "(direct) in cm^-1:\n" - " mode 0 : -7.324570 cm^-1\n" - " mode 1 : 517.491000 cm^-1\n" - " mode 2 : 0.000000 cm^-1\n"; + const std::string expect_q = " DFPT phonon frequencies at q #0 = (0.130000 0.000000 0.070000) " + "(direct) in cm^-1:\n" + " mode 0 : -7.324570 cm^-1\n" + " mode 1 : 517.491000 cm^-1\n" + " mode 2 : 0.000000 cm^-1\n"; EXPECT_EQ(qrep, expect_q); // LO-TO report: empty before the corrected frequencies exist @@ -724,11 +684,10 @@ TEST_F(DFPTPhonSerialTest, FormatReportsRegression) data_.set_loto_dir(ModuleBase::Vector3(0.0, 3.0, 0.0)); data_.set_phon_freq_loto(std::vector{0.0, 520.123456, 520.123457}); const std::string lrep = phon_.format_loto_report(data_); - const std::string expect_l - = " DFPT LO-TO corrected frequencies at q #0 along q->0 direction " - "(0.000000 1.000000 0.000000) in cm^-1:\n" - " mode 0 : 0.000000 cm^-1\n" - " mode 1 : 520.123456 cm^-1\n" - " mode 2 : 520.123457 cm^-1\n"; + const std::string expect_l = " DFPT LO-TO corrected frequencies at q #0 along q->0 direction " + "(0.000000 1.000000 0.000000) in cm^-1:\n" + " mode 0 : 0.000000 cm^-1\n" + " mode 1 : 520.123456 cm^-1\n" + " mode 2 : 520.123457 cm^-1\n"; EXPECT_EQ(lrep, expect_l); } diff --git a/source/source_pw/module_dfpt/test_serial/dfpt_q0_serial_test.cpp b/source/source_pw/module_dfpt/test_serial/dfpt_q0_serial_test.cpp index 82182541595..d7f12199275 100644 --- a/source/source_pw/module_dfpt/test_serial/dfpt_q0_serial_test.cpp +++ b/source/source_pw/module_dfpt/test_serial/dfpt_q0_serial_test.cpp @@ -10,24 +10,19 @@ // serial tests; all references are closed-form or operator finite // differences, no ground-state solver is involved. -#define private public +#include "dfpt_serial_fixture.h" +#include "source_base/constants.h" +#include "source_base/matrix.h" +#include "source_base/matrix3.h" +#include "source_base/vector3.h" #include "source_cell/atom_pseudo.h" #include "source_cell/atom_spec.h" -#include "source_cell/pseudo.h" #include "source_cell/qlist.h" #include "source_cell/unitcell.h" -#include "source_cell/magnetism.h" -#include "source_pw/module_pwdft/stru_fac.h" +#include "source_psi/psi.h" #include "source_pw/module_dfpt/dfpt_pert.h" #include "source_pw/module_dfpt/dfpt_q0.h" -#undef private - -#include "source_base/constants.h" -#include "source_base/matrix.h" -#include "source_base/matrix3.h" -#include "source_base/vector3.h" -#include "source_psi/psi.h" -#include "dfpt_serial_fixture.h" +#include "source_pw/module_pwdft/stru_fac.h" // ctor/dtor stubs for the cell/spepot/stru_fac link closures live in the // shared test/dfpt_test_mocks.cpp compiled into every DFPT test binary. @@ -82,8 +77,7 @@ class DFPTQ0SerialTest : public DFPTSerialBase for (int ig = 0; ig < npwk; ++ig) { const ModuleBase::Vector3 g = pw_wfc_.getgpluskcar(0, ig); - if (std::llround(g.x * a_) == ix && std::llround(g.y * a_) == iy - && std::llround(g.z * a_) == iz) + if (std::llround(g.x * a_) == ix && std::llround(g.y * a_) == iy && std::llround(g.z * a_) == iz) { return ig; } @@ -141,10 +135,8 @@ TEST_F(DFPTQ0SerialTest, BuildVkbDkMatchesFiniteDifference) { const std::complex fd = (vkb_p[mu][i] - vkb_m[mu][i]) / (2.0 * eps); const double scale = std::max(1.0, std::abs(fd)); - EXPECT_NEAR(dvkb[mu][i].real(), fd.real(), 1.0e-5 * scale) - << "mu=" << mu << " i=" << i << " d=" << d; - EXPECT_NEAR(dvkb[mu][i].imag(), fd.imag(), 1.0e-5 * scale) - << "mu=" << mu << " i=" << i << " d=" << d; + EXPECT_NEAR(dvkb[mu][i].real(), fd.real(), 1.0e-5 * scale) << "mu=" << mu << " i=" << i << " d=" << d; + EXPECT_NEAR(dvkb[mu][i].imag(), fd.imag(), 1.0e-5 * scale) << "mu=" << mu << " i=" << i << " d=" << d; } } } @@ -229,8 +221,7 @@ TEST_F(DFPTQ0SerialTest, PosMatrixNonlocalMatchesOperatorFiniteDifference) psi::Psi> psi(1, nb, npwk, npwk, true); psi.zero_out(); unsigned seed = 20260817u; - auto rnd = [&]() - { + auto rnd = [&]() { seed = seed * 1664525u + 1013904223u; return ((seed >> 8) & 0xffffff) / 16777216.0 * 2.0 - 1.0; }; @@ -239,8 +230,7 @@ TEST_F(DFPTQ0SerialTest, PosMatrixNonlocalMatchesOperatorFiniteDifference) { for (int ig = 0; ig < npwk; ++ig) { - c[b][ig] = (ig == ig0) ? std::complex(0.0, 0.0) - : std::complex(rnd(), rnd()); + c[b][ig] = (ig == ig0) ? std::complex(0.0, 0.0) : std::complex(rnd(), rnd()); } } // Gram-Schmidt, skipping the zero column keeps the norm from column 1 on @@ -304,8 +294,7 @@ TEST_F(DFPTQ0SerialTest, PosMatrixNonlocalMatchesOperatorFiniteDifference) // becp with the |G| = 0 column dropped on a shifted list auto vnl_matrix = [&](const std::vector>& glist, - std::vector>>& mmat) - { + std::vector>>& mmat) { std::vector>> vkb; pert_.build_vkb(0, 0, glist, vkb); std::vector>> becp(nb); @@ -369,21 +358,17 @@ TEST_F(DFPTQ0SerialTest, PosMatrixNonlocalMatchesOperatorFiniteDifference) } const double de = eig(0, m) - eig(0, n); // recover p from r: r = -i p / (tpiba de) - const std::complex p_r - = std::complex(0.0, 1.0) * ucell_.tpiba * de * r_mat[0][m][n][d]; + const std::complex p_r = std::complex(0.0, 1.0) * ucell_.tpiba * de * r_mat[0][m][n][d]; // analytic kinetic + finite-difference nonlocal std::complex p_kin(0.0, 0.0); for (int ig = 0; ig < npwk; ++ig) { - p_kin += 2.0 * ucell_.tpiba2 * gk[ig][d] * std::conj(psi(0, m, ig)) - * psi(0, n, ig); + p_kin += 2.0 * ucell_.tpiba2 * gk[ig][d] * std::conj(psi(0, m, ig)) * psi(0, n, ig); } const std::complex p_nl = (mm_p[m][n] - mm_m[m][n]) / (2.0 * eps); const double scale = std::max(1.0, std::abs(p_kin) + std::abs(p_nl)); - EXPECT_NEAR(p_r.real(), (p_kin + p_nl).real(), 1.0e-6 * scale) - << "m=" << m << " n=" << n << " d=" << d; - EXPECT_NEAR(p_r.imag(), (p_kin + p_nl).imag(), 1.0e-6 * scale) - << "m=" << m << " n=" << n << " d=" << d; + EXPECT_NEAR(p_r.real(), (p_kin + p_nl).real(), 1.0e-6 * scale) << "m=" << m << " n=" << n << " d=" << d; + EXPECT_NEAR(p_r.imag(), (p_kin + p_nl).imag(), 1.0e-6 * scale) << "m=" << m << " n=" << n << " d=" << d; } } } @@ -409,16 +394,15 @@ TEST_F(DFPTQ0SerialTest, ComputeEpsScfSyntheticStash) wg(0, 1) = 0.0; // synthetic bare position legs Y^a_{0,0} = P_c x_a|psi_0> - const std::complex gam[3] = {std::complex(0.15, -0.3), - std::complex(0.4, 0.05), - std::complex(-0.35, 0.2)}; - const std::complex del[3] = {std::complex(-0.25, 0.45), - std::complex(0.1, -0.1), - std::complex(0.3, 0.25)}; + const std::complex gam[3] + = {std::complex(0.15, -0.3), std::complex(0.4, 0.05), std::complex(-0.35, 0.2)}; + const std::complex del[3] + = {std::complex(-0.25, 0.45), std::complex(0.1, -0.1), std::complex(0.3, 0.25)}; for (int a = 0; a < 3; ++a) { std::vector>>> y( - 1, std::vector>>(2)); + 1, + std::vector>>(2)); y[0][0].assign(npwk, std::complex(0.0, 0.0)); y[0][0][ig0] = gam[a]; y[0][0][igx] = del[a]; @@ -426,16 +410,15 @@ TEST_F(DFPTQ0SerialTest, ComputeEpsScfSyntheticStash) } // synthetic converged E-field responses dpsi^E,b_{0,0} - const std::complex mue[3] = {std::complex(0.3, 0.2), - std::complex(-0.1, 0.4), - std::complex(0.25, -0.15)}; - const std::complex nue[3] = {std::complex(0.2, -0.35), - std::complex(0.45, 0.1), - std::complex(-0.2, -0.05)}; + const std::complex mue[3] + = {std::complex(0.3, 0.2), std::complex(-0.1, 0.4), std::complex(0.25, -0.15)}; + const std::complex nue[3] + = {std::complex(0.2, -0.35), std::complex(0.45, 0.1), std::complex(-0.2, -0.05)}; for (int b = 0; b < 3; ++b) { std::vector>>> e( - 1, std::vector>>(2)); + 1, + std::vector>>(2)); e[0][0].assign(npwk, std::complex(0.0, 0.0)); e[0][0][ig0] = mue[b]; e[0][0][igx] = nue[b]; @@ -451,11 +434,8 @@ TEST_F(DFPTQ0SerialTest, ComputeEpsScfSyntheticStash) { // = conj(gam_a) mue_b + conj(del_a) nue_b over the // shared G support, wg-weighted with the 16 pi/Omega prefactor - const std::complex dot = std::conj(gam[a]) * mue[b] - + std::conj(del[a]) * nue[b]; - const double expect = ((a == b) ? 1.0 : 0.0) - - 16.0 * ModuleBase::PI / ucell_.omega - * wg(0, 0) * dot.real(); + const std::complex dot = std::conj(gam[a]) * mue[b] + std::conj(del[a]) * nue[b]; + const double expect = ((a == b) ? 1.0 : 0.0) - 16.0 * ModuleBase::PI / ucell_.omega * wg(0, 0) * dot.real(); EXPECT_NEAR(eps(a, b), expect, 1.0e-12) << "a=" << a << " b=" << b; } } @@ -497,16 +477,15 @@ TEST_F(DFPTQ0SerialTest, ComputeBornTwoLevelAnalytic) // synthetic converged displacement responses dpsi(scf)/du_{0,idir} for // the occupied band (G0/Gx components, distinct complexes per idir // catch transposed indices); the empty-band row stays unsolved - const std::complex alpha[3] = {std::complex(0.3, 0.2), - std::complex(-0.1, 0.4), - std::complex(0.25, -0.15)}; - const std::complex beta[3] = {std::complex(0.2, -0.35), - std::complex(0.45, 0.1), - std::complex(-0.2, -0.05)}; + const std::complex alpha[3] + = {std::complex(0.3, 0.2), std::complex(-0.1, 0.4), std::complex(0.25, -0.15)}; + const std::complex beta[3] + = {std::complex(0.2, -0.35), std::complex(0.45, 0.1), std::complex(-0.2, -0.05)}; for (int idir = 0; idir < 3; ++idir) { std::vector>>> disp( - 1, std::vector>>(2)); + 1, + std::vector>>(2)); disp[0][0].assign(npwk, std::complex(0.0, 0.0)); disp[0][0][ig0] = alpha[idir]; disp[0][0][igx] = beta[idir]; @@ -514,16 +493,15 @@ TEST_F(DFPTQ0SerialTest, ComputeBornTwoLevelAnalytic) } // synthetic solved position legs Y^a_{0,0} = P_c x_a|psi_0> - const std::complex gam[3] = {std::complex(0.15, -0.3), - std::complex(0.4, 0.05), - std::complex(-0.35, 0.2)}; - const std::complex del[3] = {std::complex(-0.25, 0.45), - std::complex(0.1, -0.1), - std::complex(0.3, 0.25)}; + const std::complex gam[3] + = {std::complex(0.15, -0.3), std::complex(0.4, 0.05), std::complex(-0.35, 0.2)}; + const std::complex del[3] + = {std::complex(-0.25, 0.45), std::complex(0.1, -0.1), std::complex(0.3, 0.25)}; for (int a = 0; a < 3; ++a) { std::vector>>> y( - 1, std::vector>>(2)); + 1, + std::vector>>(2)); y[0][0].assign(npwk, std::complex(0.0, 0.0)); y[0][0][ig0] = gam[a]; y[0][0][igx] = del[a]; @@ -540,10 +518,8 @@ TEST_F(DFPTQ0SerialTest, ComputeBornTwoLevelAnalytic) { // = conj(alpha)gam + conj(beta)del over the // shared G support, wg-weighted with the -2 spin prefactor - const std::complex dot = std::conj(alpha[idir]) * gam[a] - + std::conj(beta[idir]) * del[a]; - const double expect = ((a == idir) ? zion : 0.0) - - 2.0 * wg(0, 0) * dot.real(); + const std::complex dot = std::conj(alpha[idir]) * gam[a] + std::conj(beta[idir]) * del[a]; + const double expect = ((a == idir) ? zion : 0.0) - 2.0 * wg(0, 0) * dot.real(); EXPECT_NEAR(zstar(a, idir), expect, 1.0e-12) << "a=" << a << " idir=" << idir; } } @@ -607,8 +583,7 @@ TEST_F(DFPTQ0SerialTest, StarRotationCyclicGroup) // single reduced k point (1/4,0,0) on its own wfc basis ModulePW::PW_Basis_K kwfc; - const ModuleBase::Vector3 klist[1] - = {ModuleBase::Vector3(0.25, 0.0, 0.0)}; + const ModuleBase::Vector3 klist[1] = {ModuleBase::Vector3(0.25, 0.0, 0.0)}; kwfc.initgrids(lat0_, latvec_, pw_rho_.nx, pw_rho_.ny, pw_rho_.nz); kwfc.initparameters(false, ecutwfc_, 1, klist); kwfc.fft_bundle.initfftmode(0); diff --git a/source/source_pw/module_dfpt/test_serial/dfpt_rho_serial_test.cpp b/source/source_pw/module_dfpt/test_serial/dfpt_rho_serial_test.cpp index 937af5c65eb..4e4a328f149 100644 --- a/source/source_pw/module_dfpt/test_serial/dfpt_rho_serial_test.cpp +++ b/source/source_pw/module_dfpt/test_serial/dfpt_rho_serial_test.cpp @@ -9,10 +9,7 @@ // the real serial initgrids/initparameters/setuptransform path on a shared // FFT grid, exactly like the production setup_pwrho/setup_pwwfc sequence. -#define private public -#include "source_cell/qlist.h" -#undef private - +#include "dfpt_serial_fixture.h" #include "source_base/constants.h" #include "source_base/matrix.h" #include "source_base/matrix3.h" @@ -23,7 +20,6 @@ #include "source_pw/module_dfpt/dfpt_kq_basis.h" #include "source_pw/module_dfpt/dfpt_pw_data.h" #include "source_pw/module_dfpt/dfpt_rho.h" -#include "dfpt_serial_fixture.h" /************************************************ * serial unit test of DFPT_Rho (C3) @@ -42,7 +38,8 @@ * - occupation gate: bands with wg < 1e-8 do not contribute. */ -namespace { +namespace +{ unsigned g_seed = 20260815u; double test_rand() @@ -68,7 +65,7 @@ class DFPTRhoSerialTest : public DFPTSerialBase void SetUp() override { DFPTSerialBase::SetUp(); - rho_.init(1, pw_rho_.nrxx, &pw_rho_, &pw_wfc_, G_, "plain", 0.4, 0.0); + rho_.init({1, pw_rho_.nrxx, &pw_rho_, &pw_wfc_, G_, std::string("plain"), 0.4, 0.0}); } void FillRandomStates(psi::Psi>& psi, @@ -135,8 +132,7 @@ TEST_F(DFPTRhoSerialTest, ComputeDrhoMatchesBruteForceGSpace) const int mx = (ix <= pw_rho_.nx / 2) ? ix : ix - pw_rho_.nx; const int my = (iy <= pw_rho_.ny / 2) ? iy : iy - pw_rho_.ny; const int mz = (iz <= pw_rho_.nz / 2) ? iz : iz - pw_rho_.nz; - const ModuleBase::Vector3 delta = - ModuleBase::Vector3(mx, my, mz) * G_; + const ModuleBase::Vector3 delta = ModuleBase::Vector3(mx, my, mz) * G_; // A_Delta = (2 w / omega) * sum_G c*_G d_{G+Delta}: the spin factor // 2 sits in the band weight w1 = 2 w / omega (the QE incdrhoscf // convention, a915352cd), brute-forced over the lists @@ -146,9 +142,8 @@ TEST_F(DFPTRhoSerialTest, ComputeDrhoMatchesBruteForceGSpace) const ModuleBase::Vector3 gq = kq.get_gcar(jgl); for (size_t j = 0; j < glist.size(); ++j) { - if (std::abs(glist[j].x - (gq.x - delta.x)) < 1.0e-6 && - std::abs(glist[j].y - (gq.y - delta.y)) < 1.0e-6 && - std::abs(glist[j].z - (gq.z - delta.z)) < 1.0e-6) + if (std::abs(glist[j].x - (gq.x - delta.x)) < 1.0e-6 && std::abs(glist[j].y - (gq.y - delta.y)) < 1.0e-6 + && std::abs(glist[j].z - (gq.z - delta.z)) < 1.0e-6) { aref += std::conj(clist[j]) * dvec[jgl]; break; @@ -205,8 +200,7 @@ TEST_F(DFPTRhoSerialTest, ComputeDrhoRealSpaceMatchesDirectSum) const double fx = static_cast(ix) / pw_rho_.nx; const double fy = static_cast(iy) / pw_rho_.ny; const double fz = static_cast(iz) / pw_rho_.nz; - const ModuleBase::Vector3 r_cart = - ModuleBase::Vector3(fx, fy, fz) * latvec_; + const ModuleBase::Vector3 r_cart = ModuleBase::Vector3(fx, fy, fz) * latvec_; std::complex u(0.0, 0.0); for (size_t j = 0; j < glist.size(); ++j) { @@ -248,7 +242,7 @@ TEST_F(DFPTRhoSerialTest, ChargeConservationAtGamma) ModuleDFPT::DFPT_PW_Data data0; data0.init(&qlist0, 1, nbands_, pw_wfc0.npwk_max, pw_rho_.nrxx, 1, 1, nullptr); ModuleDFPT::DFPT_Rho rho0; - rho0.init(1, pw_rho_.nrxx, &pw_rho_, &pw_wfc0, G_, "plain", 0.4, 0.0); + rho0.init({1, pw_rho_.nrxx, &pw_rho_, &pw_wfc0, G_, std::string("plain"), 0.4, 0.0}); psi::Psi> psi(1, nbands_, pw_wfc0.npwk_max, pw_wfc0.npwk[0], true); ModuleDFPT::DFPT_KQ_Basis kq0; @@ -353,8 +347,7 @@ TEST_F(DFPTRhoSerialTest, MixDrhoSecondStepCombinesCorrectly) TEST_F(DFPTRhoSerialTest, VHartreeQClosedFormAndZeroMode) { // single-G amplitude: dv_ha_g[ig] = e2 4 pi / (tpiba2 |G+q|^2) drho_g[ig] - const int ig_star = [this]() - { + const int ig_star = [this]() { for (int ig = 0; ig < pw_rho_.npw; ++ig) { if ((pw_rho_.gcar[ig] + q_cart_) * (pw_rho_.gcar[ig] + q_cart_) > 1.0e-4) @@ -373,8 +366,7 @@ TEST_F(DFPTRhoSerialTest, VHartreeQClosedFormAndZeroMode) ASSERT_EQ(dv.size(), static_cast(pw_rho_.npw)); const ModuleBase::Vector3 w = pw_rho_.gcar[ig_star] + q_cart_; const std::complex expect - = ModuleBase::e2 * ModuleBase::FOUR_PI / (pw_rho_.tpiba2 * (w * w)) - * drho_g[ig_star]; + = ModuleBase::e2 * ModuleBase::FOUR_PI / (pw_rho_.tpiba2 * (w * w)) * drho_g[ig_star]; for (int ig = 0; ig < pw_rho_.npw; ++ig) { if (ig == ig_star) @@ -418,7 +410,7 @@ TEST_F(DFPTRhoSerialTest, MixDrhoKerkerFirstStepIsPreconditionedScaledOutput) } ASSERT_GT(w2_min, 0.0); const double a2 = 4.0 * w2_min; - rho_k.init(1, pw_rho_.nrxx, &pw_rho_, &pw_wfc_, G_, "kerker", 0.7, a2); + rho_k.init({1, pw_rho_.nrxx, &pw_rho_, &pw_wfc_, G_, std::string("kerker"), 0.7, a2}); std::vector> out(pw_rho_.npw); int n_small = 0; @@ -497,8 +489,7 @@ TEST_F(DFPTRhoSerialTest, MixDrhoKerkerStabilizesStiffModelProblem) target[ig] = 0.01 * std::complex(std::cos(0.3 * ig), std::sin(0.9 * ig)); } - auto model_out = [&](const std::vector>& in) - { + auto model_out = [&](const std::vector>& in) { std::vector> o(npw); for (int ig = 0; ig < npw; ++ig) { @@ -509,7 +500,7 @@ TEST_F(DFPTRhoSerialTest, MixDrhoKerkerStabilizesStiffModelProblem) // plain beta = 0.7 on the stiff model diverges ModuleDFPT::DFPT_Rho rho_p; - rho_p.init(1, pw_rho_.nrxx, &pw_rho_, &pw_wfc_, G_, "plain", 0.7, 0.0); + rho_p.init({1, pw_rho_.nrxx, &pw_rho_, &pw_wfc_, G_, std::string("plain"), 0.7, 0.0}); data_.set_drho_g(0, 0, std::vector>(npw, std::complex(0.0, 0.0))); for (int it = 0; it < 40; ++it) { @@ -522,7 +513,7 @@ TEST_F(DFPTRhoSerialTest, MixDrhoKerkerStabilizesStiffModelProblem) // kerker beta = 0.7 with a^2 = 9 w2_min (f ~ 0.1 on the stiff shell) // converges to the target ModuleDFPT::DFPT_Rho rho_k; - rho_k.init(1, pw_rho_.nrxx, &pw_rho_, &pw_wfc_, G_, "kerker", 0.7, 9.0 * w2_min); + rho_k.init({1, pw_rho_.nrxx, &pw_rho_, &pw_wfc_, G_, std::string("kerker"), 0.7, 9.0 * w2_min}); data_.set_drho_g(0, 0, std::vector>(npw, std::complex(0.0, 0.0))); for (int it = 0; it < 300; ++it) { diff --git a/source/source_pw/module_dfpt/test_serial/dfpt_serial_fixture.cpp b/source/source_pw/module_dfpt/test_serial/dfpt_serial_fixture.cpp index 9c99a1ae6e9..13d843f72ed 100644 --- a/source/source_pw/module_dfpt/test_serial/dfpt_serial_fixture.cpp +++ b/source/source_pw/module_dfpt/test_serial/dfpt_serial_fixture.cpp @@ -1,22 +1,4 @@ -// Pull the whole standard-library closure in before the private->public -// define below: the cell/qlist headers drag in and friends whose -// internals break when compiled with `private` redefined (same pattern as -// the test translation units themselves). -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define private public #include "dfpt_serial_fixture.h" -#undef private #include "source_base/constants.h" diff --git a/source/source_pw/module_dfpt/test_serial/dfpt_serial_fixture.h b/source/source_pw/module_dfpt/test_serial/dfpt_serial_fixture.h index 138a8c5013f..77285d8c7ce 100644 --- a/source/source_pw/module_dfpt/test_serial/dfpt_serial_fixture.h +++ b/source/source_pw/module_dfpt/test_serial/dfpt_serial_fixture.h @@ -1,8 +1,6 @@ #ifndef DFPT_SERIAL_FIXTURE_H #define DFPT_SERIAL_FIXTURE_H -#include -#include "gtest/gtest.h" #include "source_base/matrix3.h" #include "source_base/vector3.h" #include "source_basis/module_pw/pw_basis.h" @@ -11,17 +9,19 @@ #include "source_cell/unitcell.h" #include "source_pw/module_dfpt/dfpt_pw_data.h" +#include "gtest/gtest.h" +#include + // Shared serial-side gtest fixture for the DFPT unit tests // (dfpt_pert/rho/phon/q0_serial_test.cpp). Everything runs without // __MPI: the plane-wave bases are built through the real serial // initgrids/initparameters/setuptransform path on a shared FFT grid, // exactly like the production setup_pwrho/setup_pwwfc sequence. // -// NOTE ON INCLUDE ORDER: the tests that touch private members include -// the cell/qlist/dfpt headers with `#define private public` BEFORE this -// header; the include guards then keep this header's own includes inert. -// The fixture implementation (dfpt_serial_fixture.cpp) needs the same -// define for QList, so it wraps its include accordingly. +// All members the fixture touches (UnitCell geometry fields, the +// Atom/pseudo public data, QList::nkstot / kvec_d and the +// DFPT_PW_Data::init entry) are public, so the tests include the +// cell/qlist/dfpt headers normally. class DFPTSerialBase : public testing::Test { @@ -58,9 +58,7 @@ class DFPTSerialBase : public testing::Test // (re)initialize the bases and the shared data wiring for a given // (k, q) pair and band count; SetUp uses the default fixture values - void SetupBases(const ModuleBase::Vector3& k_d, - const ModuleBase::Vector3& q_d, - int nbands); + void SetupBases(const ModuleBase::Vector3& k_d, const ModuleBase::Vector3& q_d, int nbands); void MakeCoulombAtom(); void MakeNCAtom(); diff --git a/source/source_pw/module_ofdft/kedf_manager.cpp b/source/source_pw/module_ofdft/kedf_manager.cpp index 337158703b2..96a8be470e6 100644 --- a/source/source_pw/module_ofdft/kedf_manager.cpp +++ b/source/source_pw/module_ofdft/kedf_manager.cpp @@ -187,7 +187,7 @@ void KEDF_Manager::get_potential( } if (this->of_kinetic_ == "xwm") { - this->xwm_->xwm_potential(prho, pw_rho, rpot); + this->xwm_->xwm_potential(prho, pw_rho, rpot, PARAM.inp.nspin); } if (this->of_kinetic_ == "lkt") { @@ -353,7 +353,7 @@ void KEDF_Manager::get_energy_density( } if (this->of_kinetic_ == "xwm") { - this->xwm_->tau_xwm(prho, pw_rho, rtau[0]); + this->xwm_->tau_xwm(prho, pw_rho, rtau[0], PARAM.inp.nspin); } if (this->of_kinetic_ == "lkt") { diff --git a/source/source_pw/module_ofdft/kedf_vw.cpp b/source/source_pw/module_ofdft/kedf_vw.cpp index 3f272bf1e15..05744f39dfc 100644 --- a/source/source_pw/module_ofdft/kedf_vw.cpp +++ b/source/source_pw/module_ofdft/kedf_vw.cpp @@ -21,9 +21,10 @@ void KEDF_vW::set_para(double dV, double vw_weight) */ double KEDF_vW::get_energy(double** pphi, ModulePW::PW_Basis* pw_rho) { + const int nspin = PARAM.inp.nspin; // since pphi may contain minus element, we define tempPhi = std::abs(phi), which is true sqrt(rho) - double** tempPhi = new double*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) + double** tempPhi = new double*[nspin]; + for (int is = 0; is < nspin; ++is) { tempPhi[is] = new double[pw_rho->nrxx]; for (int ir = 0; ir < pw_rho->nrxx; ++ir) @@ -32,14 +33,14 @@ double KEDF_vW::get_energy(double** pphi, ModulePW::PW_Basis* pw_rho) } } - double** LapPhi = new double*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) { + double** LapPhi = new double*[nspin]; + for (int is = 0; is < nspin; ++is) { LapPhi[is] = new double[pw_rho->nrxx]; } this->laplacian_phi(tempPhi, LapPhi, pw_rho); double energy = 0.; // in Ry - if (PARAM.inp.nspin == 1) + if (nspin == 1) { for (int ir = 0; ir < pw_rho->nrxx; ++ir) { @@ -47,9 +48,9 @@ double KEDF_vW::get_energy(double** pphi, ModulePW::PW_Basis* pw_rho) } energy *= this->dV_ * 0.5 * this->vw_weight_ * 2.; // vw_weight * 2 to convert Hartree to Ry } - else if (PARAM.inp.nspin == 2) + else if (nspin == 2) { - for (int is = 0; is < PARAM.inp.nspin; ++is) + for (int is = 0; is < nspin; ++is) { for (int ir = 0; ir < pw_rho->nrxx; ++ir) { @@ -61,7 +62,7 @@ double KEDF_vW::get_energy(double** pphi, ModulePW::PW_Basis* pw_rho) this->vw_energy = energy; Parallel_Reduce::reduce_all(this->vw_energy); - for (int is = 0; is < PARAM.inp.nspin; ++is) + for (int is = 0; is < nspin; ++is) { delete[] tempPhi[is]; delete[] LapPhi[is]; @@ -84,9 +85,10 @@ double KEDF_vW::get_energy(double** pphi, ModulePW::PW_Basis* pw_rho) */ double KEDF_vW::get_energy_density(double** pphi, int is, int ir, ModulePW::PW_Basis* pw_rho) { + const int nspin = PARAM.inp.nspin; // since pphi may contain minus element, we define tempPhi = std::abs(phi), which is true sqrt(rho) - double** tempPhi = new double*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) + double** tempPhi = new double*[nspin]; + for (int is = 0; is < nspin; ++is) { tempPhi[is] = new double[pw_rho->nrxx]; for (int ir = 0; ir < pw_rho->nrxx; ++ir) @@ -95,8 +97,8 @@ double KEDF_vW::get_energy_density(double** pphi, int is, int ir, ModulePW::PW_B } } - double** LapPhi = new double*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) { + double** LapPhi = new double*[nspin]; + for (int is = 0; is < nspin; ++is) { LapPhi[is] = new double[pw_rho->nrxx]; } this->laplacian_phi(tempPhi, LapPhi, pw_rho); @@ -105,7 +107,7 @@ double KEDF_vW::get_energy_density(double** pphi, int is, int ir, ModulePW::PW_B energyDen = 0.5 * tempPhi[is][ir] * LapPhi[is][ir] * this->vw_weight_ * 2.; // vw_weight * 2 to convert Hartree to Ry - for (int is = 0; is < PARAM.inp.nspin; ++is) + for (int is = 0; is < nspin; ++is) { delete[] tempPhi[is]; delete[] LapPhi[is]; @@ -174,9 +176,10 @@ void KEDF_vW::vw_potential(const double* const* pphi, ModulePW::PW_Basis* pw_rho ModuleBase::TITLE("KEDF_vW", "vw_potential"); ModuleBase::timer::start("KEDF_vW", "vw_potential"); + const int nspin = PARAM.inp.nspin; // since pphi may contain minus element, we define tempPhi = std::abs(phi), which is true sqrt(rho) - double** tempPhi = new double*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) + double** tempPhi = new double*[nspin]; + for (int is = 0; is < nspin; ++is) { tempPhi[is] = new double[pw_rho->nrxx]; for (int ir = 0; ir < pw_rho->nrxx; ++ir) @@ -186,14 +189,14 @@ void KEDF_vW::vw_potential(const double* const* pphi, ModulePW::PW_Basis* pw_rho } // calculate the minus \nabla^2 sqrt(rho) - double** LapPhi = new double*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) { + double** LapPhi = new double*[nspin]; + for (int is = 0; is < nspin; ++is) { LapPhi[is] = new double[pw_rho->nrxx]; } this->laplacian_phi(tempPhi, LapPhi, pw_rho); // calculate potential - for (int is = 0; is < PARAM.inp.nspin; ++is) + for (int is = 0; is < nspin; ++is) { for (int ir = 0; ir < pw_rho->nrxx; ++ir) { @@ -210,7 +213,7 @@ void KEDF_vW::vw_potential(const double* const* pphi, ModulePW::PW_Basis* pw_rho // calculate energy double energy = 0.; // in Ry - if (PARAM.inp.nspin == 1) + if (nspin == 1) { for (int ir = 0; ir < pw_rho->nrxx; ++ir) { @@ -218,9 +221,9 @@ void KEDF_vW::vw_potential(const double* const* pphi, ModulePW::PW_Basis* pw_rho } energy *= this->dV_ * 0.5 * this->vw_weight_ * 2.; // vw_weight * 2 to convert Hartree to Ry } - else if (PARAM.inp.nspin == 2) + else if (nspin == 2) { - for (int is = 0; is < PARAM.inp.nspin; ++is) + for (int is = 0; is < nspin; ++is) { for (int ir = 0; ir < pw_rho->nrxx; ++ir) { @@ -232,7 +235,7 @@ void KEDF_vW::vw_potential(const double* const* pphi, ModulePW::PW_Basis* pw_rho this->vw_energy = energy; Parallel_Reduce::reduce_all(this->vw_energy); - for (int is = 0; is < PARAM.inp.nspin; ++is) + for (int is = 0; is < nspin; ++is) { delete[] tempPhi[is]; delete[] LapPhi[is]; @@ -251,9 +254,10 @@ void KEDF_vW::vw_potential(const double* const* pphi, ModulePW::PW_Basis* pw_rho */ void KEDF_vW::get_stress(const double* const* pphi, ModulePW::PW_Basis* pw_rho) { + const int nspin = PARAM.inp.nspin; // since pphi may contain minus element, we define tempPhi = std::abs(phi), which is true sqrt(rho) - double** tempPhi = new double*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) + double** tempPhi = new double*[nspin]; + for (int is = 0; is < nspin; ++is) { tempPhi[is] = new double[pw_rho->nrxx]; for (int ir = 0; ir < pw_rho->nrxx; ++ir) @@ -262,9 +266,9 @@ void KEDF_vW::get_stress(const double* const* pphi, ModulePW::PW_Basis* pw_rho) } } - std::complex** recipPhi = new std::complex*[PARAM.inp.nspin]; - std::complex** ggrecipPhi = new std::complex*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) + std::complex** recipPhi = new std::complex*[nspin]; + std::complex** ggrecipPhi = new std::complex*[nspin]; + for (int is = 0; is < nspin; ++is) { recipPhi[is] = new std::complex[pw_rho->npw]; ggrecipPhi[is] = new std::complex[pw_rho->npw]; @@ -279,7 +283,7 @@ void KEDF_vW::get_stress(const double* const* pphi, ModulePW::PW_Basis* pw_rho) for (int beta = alpha; beta < 3; ++beta) { this->stress(alpha, beta) = 0; - for (int is = 0; is < PARAM.inp.nspin; ++is) + for (int is = 0; is < nspin; ++is) { for (int ik = 0; ik < pw_rho->npw; ++ik) { @@ -304,7 +308,7 @@ void KEDF_vW::get_stress(const double* const* pphi, ModulePW::PW_Basis* pw_rho) this->stress(alpha, beta) = this->stress(beta, alpha); } } - for (int is = 0; is < PARAM.inp.nspin; ++is) + for (int is = 0; is < nspin; ++is) { delete[] tempPhi[is]; delete[] recipPhi[is]; @@ -325,8 +329,9 @@ void KEDF_vW::get_stress(const double* const* pphi, ModulePW::PW_Basis* pw_rho) */ void KEDF_vW::laplacian_phi(const double* const* pphi, double** rLapPhi, ModulePW::PW_Basis* pw_rho) { - std::complex** recipPhi = new std::complex*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) + const int nspin = PARAM.inp.nspin; + std::complex** recipPhi = new std::complex*[nspin]; + for (int is = 0; is < nspin; ++is) { recipPhi[is] = new std::complex[pw_rho->npw]; @@ -338,7 +343,7 @@ void KEDF_vW::laplacian_phi(const double* const* pphi, double** rLapPhi, ModuleP pw_rho->recip2real(recipPhi[is], rLapPhi[is]); } - for (int is = 0; is < PARAM.inp.nspin; ++is) + for (int is = 0; is < nspin; ++is) { delete[] recipPhi[is]; } diff --git a/source/source_pw/module_ofdft/kedf_xwm.cpp b/source/source_pw/module_ofdft/kedf_xwm.cpp index 8883ba5fc7b..0f26ce23743 100644 --- a/source/source_pw/module_ofdft/kedf_xwm.cpp +++ b/source/source_pw/module_ofdft/kedf_xwm.cpp @@ -1,6 +1,5 @@ #include "./kedf_xwm.h" -#include "source_io/module_parameter/parameter.h" #include "source_base/parallel_reduce.h" #include "source_base/tool_quit.h" @@ -60,24 +59,24 @@ void KEDF_XWM::set_para(double dV, * * @param prho charge density * @param pw_rho pw basis + * @param nspin number of spin channels * @return the energy of XWM KEDF */ -double KEDF_XWM::get_energy(const double* const* prho, ModulePW::PW_Basis* pw_rho) +double KEDF_XWM::get_energy(const double* const* prho, ModulePW::PW_Basis* pw_rho, int nspin) { - const int nspin = PARAM.inp.nspin; double** w1Rho5_6 = new double*[nspin]; for (int is = 0; is < nspin; ++is) { w1Rho5_6[is] = new double[pw_rho->nrxx]; } - this->multi_kernel(prho, this->kernel1_.data(), w1Rho5_6, this->kappa_5_6, pw_rho); + this->multi_kernel(prho, this->kernel1_.data(), w1Rho5_6, this->kappa_5_6, pw_rho, nspin); double** w2Rho5_6 = new double*[nspin]; for (int is = 0; is < nspin; ++is) { w2Rho5_6[is] = new double[pw_rho->nrxx]; } - this->multi_kernel(prho, this->kernel2_.data(), w2Rho5_6, this->kappa_5_6, pw_rho); + this->multi_kernel(prho, this->kernel2_.data(), w2Rho5_6, this->kappa_5_6, pw_rho, nspin); double energy = 0.; // in Ry if (nspin == 1) @@ -114,30 +113,31 @@ double KEDF_XWM::get_energy(const double* const* prho, ModulePW::PW_Basis* pw_rh * @param is spin index * @param ir grid index * @param pw_rho pw basis + * @param nspin number of spin channels * @return the energy density of XWM KEDF */ -double KEDF_XWM::get_energy_density(const double* const* prho, int is, int ir, ModulePW::PW_Basis* pw_rho) +double KEDF_XWM::get_energy_density(const double* const* prho, int is, int ir, ModulePW::PW_Basis* pw_rho, int nspin) { - double** w1Rho5_6 = new double*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) + double** w1Rho5_6 = new double*[nspin]; + for (int is = 0; is < nspin; ++is) { w1Rho5_6[is] = new double[pw_rho->nrxx]; } - this->multi_kernel(prho, this->kernel1_.data(), w1Rho5_6, this->kappa_5_6, pw_rho); + this->multi_kernel(prho, this->kernel1_.data(), w1Rho5_6, this->kappa_5_6, pw_rho, nspin); - double** w2Rho5_6 = new double*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) + double** w2Rho5_6 = new double*[nspin]; + for (int is = 0; is < nspin; ++is) { w2Rho5_6[is] = new double[pw_rho->nrxx]; } - this->multi_kernel(prho, this->kernel2_.data(), w2Rho5_6, this->kappa_5_6, pw_rho); + this->multi_kernel(prho, this->kernel2_.data(), w2Rho5_6, this->kappa_5_6, pw_rho, nspin); double result = std::pow(prho[is][ir], this->kappa_5_6) * w1Rho5_6[is][ir] + std::pow(prho[is][ir], this->kappa_11_6) * w2Rho5_6[is][ir]; result *= this->dV_; - for (int is = 0; is < PARAM.inp.nspin; ++is) + for (int is = 0; is < nspin; ++is) { delete[] w1Rho5_6[is]; delete[] w2Rho5_6[is]; @@ -154,24 +154,25 @@ double KEDF_XWM::get_energy_density(const double* const* prho, int is, int ir, M * @param prho charge density * @param pw_rho pw basis * @param rtau_xwm rtau_xwm => rtau_xwm + tau_xwm + * @param nspin number of spin channels */ -void KEDF_XWM::tau_xwm(const double* const* prho, ModulePW::PW_Basis* pw_rho, double* rtau_xwm) +void KEDF_XWM::tau_xwm(const double* const* prho, ModulePW::PW_Basis* pw_rho, double* rtau_xwm, int nspin) { - double** w1Rho5_6 = new double*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) + double** w1Rho5_6 = new double*[nspin]; + for (int is = 0; is < nspin; ++is) { w1Rho5_6[is] = new double[pw_rho->nrxx]; } - this->multi_kernel(prho, this->kernel1_.data(), w1Rho5_6, this->kappa_5_6, pw_rho); + this->multi_kernel(prho, this->kernel1_.data(), w1Rho5_6, this->kappa_5_6, pw_rho, nspin); - double** w2Rho5_6 = new double*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) + double** w2Rho5_6 = new double*[nspin]; + for (int is = 0; is < nspin; ++is) { w2Rho5_6[is] = new double[pw_rho->nrxx]; } - this->multi_kernel(prho, this->kernel2_.data(), w2Rho5_6, this->kappa_5_6, pw_rho); + this->multi_kernel(prho, this->kernel2_.data(), w2Rho5_6, this->kappa_5_6, pw_rho, nspin); - if (PARAM.inp.nspin == 1) + if (nspin == 1) { for (int ir = 0; ir < pw_rho->nrxx; ++ir) { @@ -179,12 +180,12 @@ void KEDF_XWM::tau_xwm(const double* const* prho, ModulePW::PW_Basis* pw_rho, do + std::pow(prho[0][ir], this->kappa_11_6) * w2Rho5_6[0][ir]; } } - else if (PARAM.inp.nspin == 2) + else if (nspin == 2) { // TODO: spin polarized } - for (int is = 0; is < PARAM.inp.nspin; ++is) + for (int is = 0; is < nspin; ++is) { delete[] w1Rho5_6[is]; delete[] w2Rho5_6[is]; @@ -200,34 +201,35 @@ void KEDF_XWM::tau_xwm(const double* const* prho, ModulePW::PW_Basis* pw_rho, do * @param prho charge density * @param pw_rho pw basis * @param rpotential rpotential => rpotential + V_{XWM} + * @param nspin number of spin channels */ -void KEDF_XWM::xwm_potential(const double* const* prho, ModulePW::PW_Basis* pw_rho, ModuleBase::matrix& rpotential) +void KEDF_XWM::xwm_potential(const double* const* prho, ModulePW::PW_Basis* pw_rho, ModuleBase::matrix& rpotential, int nspin) { ModuleBase::TITLE("KEDF_XWM", "xwm_potential"); ModuleBase::timer::start("KEDF_XWM", "xwm_potential"); - double** w1Rho5_6 = new double*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) + double** w1Rho5_6 = new double*[nspin]; + for (int is = 0; is < nspin; ++is) { w1Rho5_6[is] = new double[pw_rho->nrxx]; } - this->multi_kernel(prho, this->kernel1_.data(), w1Rho5_6, this->kappa_5_6, pw_rho); + this->multi_kernel(prho, this->kernel1_.data(), w1Rho5_6, this->kappa_5_6, pw_rho, nspin); - double** w2Rho11_6 = new double*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) + double** w2Rho11_6 = new double*[nspin]; + for (int is = 0; is < nspin; ++is) { w2Rho11_6[is] = new double[pw_rho->nrxx]; } - this->multi_kernel(prho, this->kernel2_.data(), w2Rho11_6, this->kappa_11_6, pw_rho); + this->multi_kernel(prho, this->kernel2_.data(), w2Rho11_6, this->kappa_11_6, pw_rho, nspin); - double** w2Rho5_6 = new double*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) + double** w2Rho5_6 = new double*[nspin]; + for (int is = 0; is < nspin; ++is) { w2Rho5_6[is] = new double[pw_rho->nrxx]; } - this->multi_kernel(prho, this->kernel2_.data(), w2Rho5_6, this->kappa_5_6, pw_rho); + this->multi_kernel(prho, this->kernel2_.data(), w2Rho5_6, this->kappa_5_6, pw_rho, nspin); double energy = 0.; - for (int is = 0; is < PARAM.inp.nspin; ++is) + for (int is = 0; is < nspin; ++is) { for (int ir = 0; ir < pw_rho->nrxx; ++ir) { @@ -246,7 +248,7 @@ void KEDF_XWM::xwm_potential(const double* const* prho, ModulePW::PW_Basis* pw_r this->xwm_energy = energy; Parallel_Reduce::reduce_all(this->xwm_energy); - for (int is = 0; is < PARAM.inp.nspin; ++is) + for (int is = 0; is < nspin; ++is) { delete[] w1Rho5_6[is]; delete[] w2Rho11_6[is]; @@ -278,11 +280,12 @@ void KEDF_XWM::get_stress(const double* const* prho, ModulePW::PW_Basis* pw_rho, * @param [out] rkernel_rho \int{W(r-r')rho^{exponent}(r') dr'} * @param [in] exponent the exponent of rho * @param [in] pw_rho pw_basis + * @param [in] nspin number of spin channels */ -void KEDF_XWM::multi_kernel(const double* const* prho, const double* kernel, double** rkernel_rho, double exponent, ModulePW::PW_Basis* pw_rho) +void KEDF_XWM::multi_kernel(const double* const* prho, const double* kernel, double** rkernel_rho, double exponent, ModulePW::PW_Basis* pw_rho, int nspin) { - std::complex** recipkernelRho = new std::complex*[PARAM.inp.nspin]; - for (int is = 0; is < PARAM.inp.nspin; ++is) + std::complex** recipkernelRho = new std::complex*[nspin]; + for (int is = 0; is < nspin; ++is) { recipkernelRho[is] = new std::complex[pw_rho->npw]; for (int ir = 0; ir < pw_rho->nrxx; ++ir) @@ -297,7 +300,7 @@ void KEDF_XWM::multi_kernel(const double* const* prho, const double* kernel, dou pw_rho->recip2real(recipkernelRho[is], rkernel_rho[is]); } - for (int is = 0; is < PARAM.inp.nspin; ++is) + for (int is = 0; is < nspin; ++is) { delete[] recipkernelRho[is]; } diff --git a/source/source_pw/module_ofdft/kedf_xwm.h b/source/source_pw/module_ofdft/kedf_xwm.h index cc4ef2dbffd..daf783138dc 100644 --- a/source/source_pw/module_ofdft/kedf_xwm.h +++ b/source/source_pw/module_ofdft/kedf_xwm.h @@ -28,16 +28,16 @@ class KEDF_XWM ModulePW::PW_Basis* pw_rho); - double get_energy(const double* const* prho, ModulePW::PW_Basis* pw_rho); - double get_energy_density(const double* const* prho, int is, int ir, ModulePW::PW_Basis* pw_rho); - void tau_xwm(const double* const* prho, ModulePW::PW_Basis* pw_rho, double* rtau_xwm); - void xwm_potential(const double* const* prho, ModulePW::PW_Basis* pw_rho, ModuleBase::matrix& rpotential); + double get_energy(const double* const* prho, ModulePW::PW_Basis* pw_rho, int nspin); + double get_energy_density(const double* const* prho, int is, int ir, ModulePW::PW_Basis* pw_rho, int nspin); + void tau_xwm(const double* const* prho, ModulePW::PW_Basis* pw_rho, double* rtau_xwm, int nspin); + void xwm_potential(const double* const* prho, ModulePW::PW_Basis* pw_rho, ModuleBase::matrix& rpotential, int nspin); void get_stress(const double* const* prho, ModulePW::PW_Basis* pw_rho, double vw_weight); double xwm_energy = 0.; ModuleBase::matrix stress; private: - void multi_kernel(const double* const* prho, const double* kernel, double** rkernel_rho, double exponent, ModulePW::PW_Basis* pw_rho); + void multi_kernel(const double* const* prho, const double* kernel, double** rkernel_rho, double exponent, ModulePW::PW_Basis* pw_rho, int nspin); void fill_kernel(double tf_weight, double vw_weight, ModulePW::PW_Basis* pw_rho); double dV_ = 0.; diff --git a/source/source_pw/module_stodft/test/CMakeLists.txt b/source/source_pw/module_stodft/test/CMakeLists.txt index 15c5c93d507..c025b789558 100644 --- a/source/source_pw/module_stodft/test/CMakeLists.txt +++ b/source/source_pw/module_stodft/test/CMakeLists.txt @@ -10,5 +10,5 @@ AddTest( TARGET MODULE_PW_Sto_Hamilt_UTs LIBS parameter psi base device planewave_serial symmetry SOURCES ../hamilt_sdft_pw.cpp test_hamilt_sto.cpp ../../../source_hamilt/operator.cpp - ../../../source_cell/klist.cpp ../../../source_cell/parallel_kpoints.cpp ../../../source_cell/k_vector_utils.cpp ../../../source_cell/reciprocal_grid.cpp + ../../../source_cell/klist.cpp ../../../source_cell/parallel_kpoints.cpp ../../../source_cell/reciprocal_grid.cpp ) \ No newline at end of file