diff --git a/docs/advanced/input_files/input-main.md b/docs/advanced/input_files/input-main.md index 366c98bbd43..cc3ab348c08 100644 --- a/docs/advanced/input_files/input-main.md +++ b/docs/advanced/input_files/input-main.md @@ -206,6 +206,7 @@ - [rpa](#rpa) - [rpa\_out\_vel](#rpa_out_vel) - [rpa\_outdir](#rpa_outdir) + - [out\_librpa\_ver](#out_librpa_ver) - [out\_pchg](#out_pchg) - [out\_wfc\_norm](#out_wfc_norm) - [out\_wfc\_re\_im](#out_wfc_re_im) @@ -2327,7 +2328,7 @@ - **Type**: Boolean - **Availability**: *[`basis_type`](#basis_type) in [lcao, lcao_in_pw]* -- **Description**: Whether to print the upper triangular part of the exchange-correlation matrices in Kohn-Sham orbital representation: for each k point into files in the directory OUT.i_nao.txt, where {suffix}/vxc_out.dat. If EXX is calculated, the local and EXX part of band energy will also be printed in OUT.{suffix}/vxc_exx_out.dat, respectively. All the vxc_out.dat files contains 3 integers (nk, nspin, nband) followed by nk*nspin*nband lines of energy Hartree and eV. +- **Description**: Write exchange-correlation matrices in the Kohn-Sham orbital basis and their diagonal expectation values. The diagonal energies are saved in OUT.${suffix}/vxc.txt. When EXX is calculated, its local and EXX contributions are also saved as vxc_local.txt and vxc_exx.txt in the same directory. Each energy file begins with nk, nspin, and nbands, followed by nk*nspin*nbands rows of energies in Hartree and eV. > Note: In the 3.10-LTS version, the file name is k-$k-Vxc and so on. - **Default**: False @@ -2456,6 +2457,13 @@ - **Description**: The directory to save files for LibRPA. - **Default**: "OUT.librpa" +### out_librpa_ver + +- **Type**: Integer +- **Availability**: *[`basis_type`](#basis_type)==lcao* +- **Description**: Select the LibRPA file protocol: 0 for legacy text output and 1 for reader-v1 output. Reader-v1 supports symmetry-reduced Monkhorst-Pack grids and requires a uniform Monkhorst-Pack KPT grid. +- **Default**: 0 + ### out_pchg - **Type**: String diff --git a/docs/parameters.yaml b/docs/parameters.yaml index a6d202867be..323336bffe2 100644 --- a/docs/parameters.yaml +++ b/docs/parameters.yaml @@ -3734,7 +3734,7 @@ parameters: category: Output information type: Boolean description: | - Whether to print the upper triangular part of the exchange-correlation matrices in Kohn-Sham orbital representation: for each k point into files in the directory OUT.i_nao.txt, where {suffix}/vxc_out.dat. If EXX is calculated, the local and EXX part of band energy will also be printed in OUT.{suffix}/vxc_exx_out.dat, respectively. All the vxc_out.dat files contains 3 integers (nk, nspin, nband) followed by nk*nspin*nband lines of energy Hartree and eV. + Write exchange-correlation matrices in the Kohn-Sham orbital basis and their diagonal expectation values. The diagonal energies are saved in OUT.${suffix}/vxc.txt. When EXX is calculated, its local and EXX contributions are also saved as vxc_local.txt and vxc_exx.txt in the same directory. Each energy file begins with nk, nspin, and nbands, followed by nk*nspin*nbands rows of energies in Hartree and eV. [NOTE] In the 3.10-LTS version, the file name is k-$k-Vxc and so on. default_value: "False" @@ -3876,6 +3876,14 @@ parameters: default_value: "\"OUT.librpa\"" unit: "" availability: basis_type==lcao + - name: out_librpa_ver + category: Output information + type: Integer + description: | + Select the LibRPA file protocol: 0 for legacy text output and 1 for reader-v1 output. Reader-v1 supports symmetry-reduced Monkhorst-Pack grids and requires a uniform Monkhorst-Pack KPT grid. + default_value: "0" + unit: "" + availability: basis_type==lcao - name: out_pchg category: Output information type: String diff --git a/source/Makefile.Objects b/source/Makefile.Objects index 7514f75060a..794de412d74 100644 --- a/source/Makefile.Objects +++ b/source/Makefile.Objects @@ -858,6 +858,24 @@ OBJS_MODULE_RI=conv_coulomb_pot_k.o\ gaussian_abfs.o\ exx_lri_detail.o\ ri_util.o\ + rpa_lri.o\ + rpa_lri_detail.o\ + rpa_lri_band_io.o\ + rpa_lri_mpi_layout.o\ + rpa_lri_wfc_mpi.o\ + rpa_lri_bz_io.o\ + rpa_lri_coulomb.o\ + rpa_lri_coulomb_io.o\ + rpa_lri_overlap.o\ + rpa_lri_overlap_io.o\ + rpa_lri_output.o\ + rpa_lri_bz.o\ + rpa_lri_sym.o\ + rpa_lri_coeff_io.o\ + rpa_lri_velocity.o\ + rpa_lri_basis.o\ + rpa_lri_eigenvector_v1.o\ + rpa_lri_eigenvector.o\ singular_value.o\ OBJS_BSE=esolver_lr_lcao_bse.o\ diff --git a/source/source_cell/klist.cpp b/source/source_cell/klist.cpp index ef3809238c3..b1a1be33c76 100644 --- a/source/source_cell/klist.cpp +++ b/source/source_cell/klist.cpp @@ -87,6 +87,13 @@ void K_Vectors::set(const UnitCell& ucell, this->kvec_c_full); + // initialize ibz_index + this->ibz_index.resize(this->nkstot_nospin); + for (int ik = 0; ik < this->nkstot_nospin; ik++) + { + this->ibz_index[ik] = ik; + } + // (2) // reduce kpoints to IBZ according to symmetry operations if (use_ibz) @@ -140,13 +147,6 @@ void K_Vectors::set(const UnitCell& ucell, // set the k vectors for the up and down spin this->set_kup_and_kdw(ofs); - // initialize ibz_index - this->ibz_index.resize(this->nkstot_nospin); - for (int ik = 0; ik < this->nkstot_nospin; ik++) - { - this->ibz_index[ik] = ik; - } - // get ik2iktot: map local k indices to global indices in the pool KListIO::build_ik2iktot(this->para_k.my_pool, this->para_k.startk_pool, @@ -203,6 +203,24 @@ void K_Vectors::renew(const int& kpoint_number) return; } +bool K_Vectors::is_uniform_mp() const +{ + if (!this->is_mp || this->nkstot_nospin <= 0) + { + return false; + } + long long full_kpoints = 1; + for (const int size : this->nmp) + { + if (size <= 0 || full_kpoints > this->nkstot_nospin / size) + { + return false; + } + full_kpoints *= size; + } + return full_kpoints == this->nkstot_nospin; +} + // Read the KPT file, which contains K-point coordinates, weights, and grid size information // Generate K-point grid according to different parameters of the KPT file bool K_Vectors::read_kpoints(const UnitCell& ucell, @@ -663,6 +681,9 @@ void K_Vectors::mpi_k(std::ofstream& ofs_running, const int my_rank, const int m Parallel_Common::bcast_int(this->nmp, 3); + // The MP flag is set while rank 0 reads KPT and is consumed on every rank. + Parallel_Common::bcast_bool(this->is_mp); + this->kl_segids.resize(this->nkstot); Parallel_Common::bcast_int(this->kl_segids.data(), this->nkstot); diff --git a/source/source_cell/klist.h b/source/source_cell/klist.h index b5f3c8f68d6..5198e87acba 100644 --- a/source/source_cell/klist.h +++ b/source/source_cell/klist.h @@ -132,6 +132,8 @@ class K_Vectors : public ModuleCell::ReciprocalGrid return is_mp; } + bool is_uniform_mp() const; + std::vector ik2iktot; ///<[nks] map ik to the global index of k points std::vector ibz_index; ///< map k points (before symmetry reduction) to irreducible k-points @@ -457,4 +459,4 @@ class K_Vectors : public ModuleCell::ReciprocalGrid void mpi_k(std::ofstream& ofs_running, const int my_rank, const int my_pool); #endif }; -#endif // KVECT_H \ No newline at end of file +#endif // KVECT_H diff --git a/source/source_cell/klist_io.cpp b/source/source_cell/klist_io.cpp index 9afa9a09eab..7543816787a 100644 --- a/source/source_cell/klist_io.cpp +++ b/source/source_cell/klist_io.cpp @@ -271,6 +271,11 @@ void pack_kpts(const std::vector& isk, kvec_d_aux[3 * ik] = kvec_d[ik].x; kvec_d_aux[3 * ik + 1] = kvec_d[ik].y; kvec_d_aux[3 * ik + 2] = kvec_d[ik].z; + } + // The unreduced mesh is global and can be larger than the SCF list. + const std::size_t full_count = std::min(kvec_c_full.size(), kvec_c_full_aux.size() / 3); + for (std::size_t ik = 0; ik < full_count; ++ik) + { kvec_c_full_aux[3 * ik] = kvec_c_full[ik].x; kvec_c_full_aux[3 * ik + 1] = kvec_c_full[ik].y; kvec_c_full_aux[3 * ik + 2] = kvec_c_full[ik].z; @@ -300,12 +305,16 @@ void unpack_kpts(const std::vector& isk_aux, kvec_d[i].x = kvec_d_aux[k_index * 3]; kvec_d[i].y = kvec_d_aux[k_index * 3 + 1]; kvec_d[i].z = kvec_d_aux[k_index * 3 + 2]; - kvec_c_full[i].x = kvec_c_full_aux[k_index * 3]; - kvec_c_full[i].y = kvec_c_full_aux[k_index * 3 + 1]; - kvec_c_full[i].z = kvec_c_full_aux[k_index * 3 + 2]; wk[i] = wk_aux[k_index]; isk[i] = isk_aux[k_index]; } + kvec_c_full.resize(kvec_c_full_aux.size() / 3); + for (std::size_t ik = 0; ik < kvec_c_full.size(); ++ik) + { + kvec_c_full[ik].x = kvec_c_full_aux[3 * ik]; + kvec_c_full[ik].y = kvec_c_full_aux[3 * ik + 1]; + kvec_c_full[ik].z = kvec_c_full_aux[3 * ik + 2]; + } } void bcast_kstars(std::vector>>& kstars, diff --git a/source/source_cell/test/CMakeLists.txt b/source/source_cell/test/CMakeLists.txt index 3c1c7a4bc51..9e2c9998d6a 100644 --- a/source/source_cell/test/CMakeLists.txt +++ b/source/source_cell/test/CMakeLists.txt @@ -95,7 +95,7 @@ AddTest( AddTest( TARGET MODULE_CELL_klist_test LIBS base device symmetry - SOURCES klist_test.cpp ../klist.cpp ../klist_io.cpp ../parallel_kpoints.cpp ../reciprocal_grid.cpp + SOURCES klist_test.cpp test_klist_io.cpp ../klist.cpp ../klist_io.cpp ../parallel_kpoints.cpp ../reciprocal_grid.cpp ) AddTest( diff --git a/source/source_cell/test/klist_test.cpp b/source/source_cell/test/klist_test.cpp index bdc98c45600..dc8387246ea 100644 --- a/source/source_cell/test/klist_test.cpp +++ b/source/source_cell/test/klist_test.cpp @@ -881,6 +881,7 @@ TEST_F(KlistTest, IbzKpointIsMP) kv->read_kpoints_for_testing(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, ofs_running, ofs_warning, my_rank); EXPECT_EQ(kv->get_nkstot(), 512); EXPECT_TRUE(kv->is_mp); + EXPECT_TRUE(kv->is_uniform_mp()); // calculate ibz_kpoint std::string skpt; ModuleSymmetry::Symmetry::symm_flag = 0; @@ -915,6 +916,7 @@ TEST_F(KlistTest, IbzKpointCustomWeights) kv_test1.read_kpoints_for_testing(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, ofs_running, ofs_warning, my_rank); EXPECT_EQ(kv_test1.get_nkstot(), 5); EXPECT_FALSE(kv_test1.is_mp); // Should be non-MP + EXPECT_FALSE(kv_test1.is_uniform_mp()); // Store original weights before IBZ reduction std::vector original_weights = kv_test1.wk; @@ -944,6 +946,7 @@ TEST_F(KlistTest, IbzKpointCustomWeights) kv_test2.read_kpoints_for_testing(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, ofs_running, ofs_warning, my_rank); EXPECT_EQ(kv_test2.get_nkstot(), 5); EXPECT_FALSE(kv_test2.is_mp); // Should be non-MP + EXPECT_FALSE(kv_test2.is_uniform_mp()); // Verify custom weights were read correctly EXPECT_DOUBLE_EQ(kv_test2.wk[0], 0.1); @@ -1001,6 +1004,7 @@ TEST_F(KlistTest, IbzKpointCustomWeights) kv_test3.read_kpoints_for_testing(ucell, k_file, gamma_only_local, kspacing, kmesh_type, koffset, ofs_running, ofs_warning, my_rank); EXPECT_EQ(kv_test3.get_nkstot(), 512); EXPECT_TRUE(kv_test3.is_mp); // Should be MP + EXPECT_TRUE(kv_test3.is_uniform_mp()); // Apply IBZ reduction std::string skpt; diff --git a/source/source_cell/test/klist_test_para.cpp b/source/source_cell/test/klist_test_para.cpp index 1e20b685970..29f6695dce6 100644 --- a/source/source_cell/test/klist_test_para.cpp +++ b/source/source_cell/test/klist_test_para.cpp @@ -208,6 +208,7 @@ TEST_F(KlistParaTest, Set) const std::string kmesh_type = "gamma"; const double koffset[3] = {0.0, 0.0, 0.0}; kv->set(ucell, symm, k_file, /*nspin_in*/ 1, ucell.G, ucell.latvec, GlobalV::ofs_running, GlobalV::ofs_warning, use_ibz, global_out_dir, gamma_only_local, kspacing, kmesh_type, koffset); + EXPECT_TRUE(kv->get_is_mp()); EXPECT_EQ(kv->get_nkstot(), 35); EXPECT_EQ(kv->get_nkstot_nospin(), 512); EXPECT_GT(kv->get_nkstot_nospin(), kv->get_nkstot()); diff --git a/source/source_cell/test/test_klist_io.cpp b/source/source_cell/test/test_klist_io.cpp new file mode 100644 index 00000000000..04daf3a49e2 --- /dev/null +++ b/source/source_cell/test/test_klist_io.cpp @@ -0,0 +1,39 @@ +#include "../klist_io.h" +#include + +TEST(KListIO, PreserveFullMeshAcrossPools) +{ + const std::vector spins = {0, 0}; + const std::vector weights = {1.0 / 3, 2.0 / 3}; + const std::vector> irreducible = {{0, 0, 0}, {0, 0, 1.0 / 3}}; + const std::vector> full = {{0, 0, 0}, {0, 0, 1.0 / 3}, {0, 0, 2.0 / 3}}; + auto full_with_spin_capacity = full; + full_with_spin_capacity.resize(6); + std::vector spin_buffer(2); + std::vector weight_buffer(2); + std::vector cart_buffer(6); + std::vector frac_buffer(6); + std::vector full_buffer(9, -9.0); + KListIO::pack_kpts(spins, weights, irreducible, irreducible, full_with_spin_capacity, 2, + spin_buffer, weight_buffer, cart_buffer, frac_buffer, full_buffer); + EXPECT_DOUBLE_EQ(full_buffer[8], 2.0 / 3); + + std::vector local_spin(1); + std::vector local_weight(1); + std::vector> local_cart(1); + std::vector> local_frac(1); + std::vector> local_full(1); + // A second pool holds only the second SCF representative, but must retain + // every original full-grid point in its original order. + KListIO::unpack_kpts(spin_buffer, weight_buffer, cart_buffer, frac_buffer, full_buffer, + 1, 1, local_spin, local_weight, local_cart, local_frac, local_full); + EXPECT_DOUBLE_EQ(local_cart[0].z, 1.0 / 3); + EXPECT_DOUBLE_EQ(local_weight[0], 2.0 / 3); + ASSERT_EQ(local_full.size(), full.size()); + for (std::size_t ik = 0; ik < full.size(); ++ik) + { + EXPECT_DOUBLE_EQ(local_full[ik].x, full[ik].x); + EXPECT_DOUBLE_EQ(local_full[ik].y, full[ik].y); + EXPECT_DOUBLE_EQ(local_full[ik].z, full[ik].z); + } +} diff --git a/source/source_esolver/esolver_fp.cpp b/source/source_esolver/esolver_fp.cpp index f6aa0ec8aca..149f50dc8cb 100644 --- a/source/source_esolver/esolver_fp.cpp +++ b/source/source_esolver/esolver_fp.cpp @@ -22,7 +22,6 @@ #include "source_base/module_parallel/para_world.h" #include "source_base/module_parallel/para_tag.h" #include "source_base/module_parallel/para_bridge.h" - namespace ModuleESolver { diff --git a/source/source_esolver/esolver_ks_lcao.cpp b/source/source_esolver/esolver_ks_lcao.cpp index 3fe7ef78212..79150af093f 100644 --- a/source/source_esolver/esolver_ks_lcao.cpp +++ b/source/source_esolver/esolver_ks_lcao.cpp @@ -1,6 +1,8 @@ #include "esolver_ks_lcao.h" #include "source_basis/module_pw/pw_basis_big.h" // use PW_Basis_Big +#include "source_base/tool_quit.h" #include "source_base/module_external/blacs_connector.h" +#include "source_cell/klist.h" #include "source_cell/module_neighbor/sltk_atom_arrange.h" #include "source_estate/elecstate_tools.h" #include "source_lcao/module_deltaspin/spin_constrain.h" @@ -24,6 +26,7 @@ #include "source_estate/module_charge/chg_routine.h" // use charge mixing, mohan add 20251006 #include "source_estate/module_dm/dm_routine.h" // init dm from electronic wave functions #include "source_io/module_restart/restart.h" // GlobalC::restart for load_exx_flag +#include "source_io/module_parameter/input_parameter.h" #include "source_io/module_ctrl/ctrl_runner_lcao.h" // use ctrl_runner_lcao() #include "source_io/module_ctrl/ctrl_iter_lcao.h" // use ctrl_iter_lcao() #include "source_io/module_ctrl/ctrl_scf_lcao.h" // use ctrl_scf_lcao() @@ -36,6 +39,22 @@ namespace ModuleESolver { +#ifdef __EXX +namespace +{ +void validate_librpa_v1_kpoints(const Input_para& input, const K_Vectors& kv) +{ + // Reader-v1 writes the complete Monkhorst-Pack mesh for LibRPA. + if (input.rpa && input.out_librpa_ver == 1 && !kv.is_uniform_mp()) + { + ModuleBase::WARNING_QUIT( + "ESolver_KS_LCAO::before_all_runners", + "out_librpa_ver=1 requires a uniform Monkhorst-Pack k-point grid."); + } +} +} // namespace +#endif + template ESolver_KS_LCAO::ESolver_KS_LCAO() { @@ -65,6 +84,10 @@ void ESolver_KS_LCAO::before_all_runners(BaseCell& basecell, const Input // 1) before_all_runners in ESolver_KS (includes init_general_exx_info) ESolver_KS::before_all_runners(ucell, inp); +#ifdef __EXX + validate_librpa_v1_kpoints(inp, this->kv); +#endif + // 2) init full Exx_Info for LCAO (includes info_ri, info_opt_abfs, info_lip) init_exx_info(this->exx_info_, inp); diff --git a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp index dddf2576c24..b7533f0325d 100644 --- a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp @@ -683,7 +683,14 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, //------------------------------------------------------------------ if (inp.rpa) { - RPA_LRI rpa_lri_double(exx_info.info_ri); + const ModuleRI::RpaLriRuntime rpa_runtime = { + inp, + global_out_dir, + pv.get_global_row_size(), + GlobalV::MY_RANK, + GlobalV::NPROC, + GlobalV::ofs_running}; + RPA_LRI rpa_lri_double(exx_info.info_ri, rpa_runtime); rpa_lri_double.postSCF(ucell, MPI_COMM_WORLD, *dm, pelec, kv, orb, pv, *psi); if (inp.rpa_out_vel) rpa_lri_double.out_velocity(ucell, gd, two_center_bundle, pv, *psi, pelec); diff --git a/source/source_io/module_hs/vxc_op_lip.cpp b/source/source_io/module_hs/vxc_op_lip.cpp index 47cc4ba7189..fcee73b6cd4 100644 --- a/source/source_io/module_hs/vxc_op_lip.cpp +++ b/source/source_io/module_hs/vxc_op_lip.cpp @@ -219,8 +219,8 @@ void write_Vxc_LIP(int nspin, assert(e_orb.size() == kv.get_nks()); const int nk = kv.get_nks() / nspin0; std::ofstream ofs; - const std::string out_name = (label == "") ? "out.dat" : label + "_out.dat"; - ofs.open(global_out_dir + "vxc_" + out_name, + const std::string out_name = (label == "") ? ".txt" : "_" + label + ".txt"; + ofs.open(global_out_dir + "vxc" + out_name, app ? std::ios::app : std::ios::out); ofs << nk << "\n" << nspin0 << "\n" << nbands << "\n"; ofs << std::scientific << std::setprecision(16); diff --git a/source/source_io/module_hs/vxc_op_tools.cpp b/source/source_io/module_hs/vxc_op_tools.cpp index 0d20574d381..0862ae1557d 100644 --- a/source/source_io/module_hs/vxc_op_tools.cpp +++ b/source/source_io/module_hs/vxc_op_tools.cpp @@ -110,8 +110,10 @@ void write_orb_energy(const K_Vectors& kv, assert(e_orb.size() == kv.get_nks()); const int nk = kv.get_nks() / nspin0; std::ofstream ofs; - const std::string out_name = (label == "") ? "out.dat" : label + "_out.dat"; - ofs.open(global_out_dir + term + "_" + out_name, + const std::string out_name = term == "vxc" + ? (label.empty() ? "vxc.txt" : "vxc_" + label + ".txt") + : term + "_" + (label.empty() ? "out.dat" : label + "_out.dat"); + ofs.open(global_out_dir + out_name, app ? std::ios::app : std::ios::out); ofs << nk << "\n" << nspin0 << "\n" << nbands << "\n"; ofs << std::scientific << std::setprecision(16); diff --git a/source/source_io/module_parameter/input_parameter.h b/source/source_io/module_parameter/input_parameter.h index f6465f89293..a8b584632e0 100644 --- a/source/source_io/module_parameter/input_parameter.h +++ b/source/source_io/module_parameter/input_parameter.h @@ -648,6 +648,7 @@ struct Input_para double shrink_LU_inv_thr = 1e-6; ///< threshold to get inverse of overlap matrix by LU decomposition in auxiliary basis representation bool out_ri_cv = false; ///< Whether to output the coefficient tensor C and ABFs-representation Coulomb matrix V bool out_unshrinked_v = false; ///< whether to output the large Vq matrix in unshrinked auxiliary basis + int out_librpa_ver = 0; ///< 0: legacy ABACUS LibRPA text files; 1: LibRPA reader-v1 binary files bool exx_coul_moment = false; ///< whether to use moment method for Coulomb calculation bool exx_rotate_abfs = false; ///< whether to rotate auxiliary basis for Coulomb calculation double exx_multip_moments_threshold = 1e-10; ///< threshold to screen multipole moments in Coulomb calculation diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index 8ac0f3a23f3..e543ebad497 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -1180,7 +1180,7 @@ For binary output, each file uses the same basename as text output with a .dat s item.annotation = "output exchange-correlation matrix in KS-orbital representation"; item.category = "Output information"; item.type = "Boolean"; - item.description = "Whether to print the upper triangular part of the exchange-correlation matrices in Kohn-Sham orbital representation: for each k point into files in the directory OUT.i_nao.txt, where {suffix}/vxc_out.dat. If EXX is calculated, the local and EXX part of band energy will also be printed in OUT.{suffix}/vxc_exx_out.dat, respectively. All the vxc_out.dat files contains 3 integers (nk, nspin, nband) followed by nk*nspin*nband lines of energy Hartree and eV." + item.description = "Write exchange-correlation matrices in the Kohn-Sham orbital basis and their diagonal expectation values. The diagonal energies are saved in OUT.${suffix}/vxc.txt. When EXX is calculated, its local and EXX contributions are also saved as vxc_local.txt and vxc_exx.txt in the same directory. Each energy file begins with nk, nspin, and nbands, followed by nk*nspin*nbands rows of energies in Hartree and eV." "\n\n[NOTE] In the 3.10-LTS version, the file name is k-$k-Vxc and so on."; item.default_value = "False"; item.unit = "Ry"; @@ -1469,6 +1469,25 @@ If EXX(exact exchange) is calculated (i.e. dft_fuctional==hse/hf/pbe0/scan0 or r }; this->add_item(item); } + { + Input_Item item("out_librpa_ver"); + item.annotation = "LibRPA output reader version"; + item.category = "Output information"; + item.type = "Integer"; + item.description = "Select the LibRPA file protocol: 0 for legacy text output and 1 for reader-v1 output. Reader-v1 supports symmetry-reduced Monkhorst-Pack grids and requires a uniform Monkhorst-Pack KPT grid."; + item.default_value = "0"; + item.unit = ""; + item.set_availability("basis_type==lcao"); + read_sync_int(input.out_librpa_ver); + item.check_value = [](const Input_Item&, const Parameter& para) { + const int value = para.input.out_librpa_ver; + if (value != 0 && value != 1) + { + ModuleBase::WARNING_QUIT("INPUT", "out_librpa_ver must be 0 or 1."); + } + }; + this->add_item(item); + } { Input_Item item("out_pchg"); item.annotation = "specify the bands to be calculated for the partial (band-decomposed) charge densities"; diff --git a/source/source_io/test/read_input_ptest.cpp b/source/source_io/test/read_input_ptest.cpp index 4784aa3f643..d49c3f3ac74 100644 --- a/source/source_io/test/read_input_ptest.cpp +++ b/source/source_io/test/read_input_ptest.cpp @@ -307,6 +307,7 @@ TEST_F(InputParaTest, ParaRead) EXPECT_DOUBLE_EQ(param.inp.exx_v_grad_r_threshold, 0); EXPECT_EQ(param.inp.exx_ccp_rmesh_times, "1.5"); EXPECT_DOUBLE_EQ(param.inp.rpa_ccp_rmesh_times, 10.0); + EXPECT_EQ(param.inp.out_librpa_ver, 1); EXPECT_EQ(param.inp.exx_opt_orb_lmax, 0); EXPECT_DOUBLE_EQ(param.inp.exx_opt_orb_ecut, 0.0); EXPECT_DOUBLE_EQ(param.inp.exx_opt_orb_tolerence, 0.0); diff --git a/source/source_io/test/support/INPUT b/source/source_io/test/support/INPUT index 0578da97f4a..8e8d3902c9c 100644 --- a/source/source_io/test/support/INPUT +++ b/source/source_io/test/support/INPUT @@ -289,6 +289,7 @@ exx_v_grad_threshold 0 # exx_c_grad_r_threshold 0 # exx_v_grad_r_threshold 0 # exx_ccp_rmesh_times default # +out_librpa_ver 1 # exx_opt_orb_lmax 0 # exx_opt_orb_ecut 0 # exx_opt_orb_tolerence 0 # diff --git a/source/source_io/test_serial/read_input_item_test.cpp b/source/source_io/test_serial/read_input_item_test.cpp index 4ace2f6f5a1..6107585e733 100644 --- a/source/source_io/test_serial/read_input_item_test.cpp +++ b/source/source_io/test_serial/read_input_item_test.cpp @@ -1866,6 +1866,29 @@ TEST_F(InputTest, Item_test2) output = testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr("NOTICE")); } + { // out_librpa_ver + auto it = find_label("out_librpa_ver", readinput.input_lists); + ASSERT_NE(it, readinput.input_lists.end()); + EXPECT_EQ(TestParameters::input(param).out_librpa_ver, 0); + + it->second.str_values = {"1"}; + it->second.read_value(it->second, param); + EXPECT_EQ(TestParameters::input(param).out_librpa_ver, 1); + + for (const std::string symmetry : {"-1", "0", "1"}) + { + TestParameters::input(param).symmetry = symmetry; + it->second.check_value(it->second, param); + } + TestParameters::input(param).out_librpa_ver = 0; + it->second.check_value(it->second, param); + + TestParameters::input(param).out_librpa_ver = 2; + testing::internal::CaptureStdout(); + EXPECT_EXIT(it->second.check_value(it->second, param), ::testing::ExitedWithCode(1), ""); + output = testing::internal::GetCapturedStdout(); + EXPECT_THAT(output, testing::HasSubstr("NOTICE")); + } { // berry_phase auto it = find_label("berry_phase", readinput.input_lists); TestParameters::input(param).berry_phase = true; diff --git a/source/source_lcao/module_lr/utils/spectrum_mo.hpp b/source/source_lcao/module_lr/utils/spectrum_mo.hpp index ae3818331a5..3b672587ace 100644 --- a/source/source_lcao/module_lr/utils/spectrum_mo.hpp +++ b/source/source_lcao/module_lr/utils/spectrum_mo.hpp @@ -172,8 +172,7 @@ inline void output_spectrum_mo(const std::vector>& out_spec inline void output_spectrum_mo_librpa(const std::vector>& out_spectrum_mo, const std::string& filename, const int nk, const int nspin_tmp, const int nbands, const int nbasis, - const int KS_num,/*= nocc+nvirt, can be different from nbands, */ - const K_Vectors& kv) + const int KS_num/*= nocc+nvirt, can be different from nbands*/) { assert(out_spectrum_mo.size() == nspin_tmp * 3 * nk * KS_num * KS_num); std::ofstream ofs(filename); diff --git a/source/source_lcao/module_lr/utils/test/CMakeLists.txt b/source/source_lcao/module_lr/utils/test/CMakeLists.txt index c5a71d7c3e1..4f921ff4f15 100644 --- a/source/source_lcao/module_lr/utils/test/CMakeLists.txt +++ b/source/source_lcao/module_lr/utils/test/CMakeLists.txt @@ -10,4 +10,10 @@ AddTest( TARGET MODULE_LR_lr_util_algo_test LIBS parameter base device psi container planewave #for FFT SOURCES lr_util_algo_test.cpp ../lr_util.cpp -) \ No newline at end of file +) + +AddTest( + TARGET MODULE_LR_spectrum_mo_output_test + LIBS parameter base device psi container planewave + SOURCES test_spectrum_mo_output.cpp +) diff --git a/source/source_lcao/module_lr/utils/test/test_spectrum_mo_output.cpp b/source/source_lcao/module_lr/utils/test/test_spectrum_mo_output.cpp new file mode 100644 index 00000000000..c7ddc78e540 --- /dev/null +++ b/source/source_lcao/module_lr/utils/test/test_spectrum_mo_output.cpp @@ -0,0 +1,79 @@ +#include + +#include "../spectrum_mo.hpp" + +#include +#include +#include +#include +#include +#include + +TEST(SpectrumMoOutput, WritesLibRpaVelocityMatrixContract) +{ + const std::string filename = "spectrum_mo_librpa_test.txt"; + std::remove(filename.c_str()); + + const int nk = 1; + const int nspin = 1; + const int nbands = 2; + const int nbasis = 3; + const int ks_num = 2; + std::vector> values(nspin * 3 * nk * ks_num * ks_num); + for (std::size_t index = 0; index < values.size(); ++index) + { + values[index] = std::complex(static_cast(index + 1), -static_cast(index + 1) / 2.0); + } + + LR_Util::output_spectrum_mo_librpa(values, filename, nk, nspin, nbands, nbasis, ks_num); + + std::ifstream input(filename.c_str()); + ASSERT_TRUE(input.good()); + std::string line; + int header = 0; + ASSERT_TRUE(static_cast(std::getline(input, line))); + std::istringstream(line) >> header; + EXPECT_EQ(header, nk); + ASSERT_TRUE(static_cast(std::getline(input, line))); + std::istringstream(line) >> header; + EXPECT_EQ(header, nspin); + ASSERT_TRUE(static_cast(std::getline(input, line))); + std::istringstream(line) >> header; + EXPECT_EQ(header, nbands); + ASSERT_TRUE(static_cast(std::getline(input, line))); + std::istringstream(line) >> header; + EXPECT_EQ(header, nbasis); + + const double conversion = ModuleBase::Hartree_to_eV * ModuleBase::BOHR_TO_A; + for (int direction = 0; direction < 3; ++direction) + { + ASSERT_TRUE(static_cast(std::getline(input, line))); + std::istringstream block_header(line); + int actual_direction = 0; + int actual_kpoint = 0; + int actual_spin = 0; + block_header >> actual_direction >> actual_kpoint >> actual_spin; + EXPECT_EQ(actual_direction, direction + 1); + EXPECT_EQ(actual_kpoint, 1); + EXPECT_EQ(actual_spin, 1); + + const std::size_t offset = static_cast(direction * ks_num * ks_num); + for (int row = 0; row < ks_num; ++row) + { + for (int column = 0; column < ks_num; ++column) + { + ASSERT_TRUE(static_cast(std::getline(input, line))); + std::istringstream value_line(line); + double real = 0.0; + double imag = 0.0; + value_line >> real >> imag; + const std::complex& expected = values[offset + row * ks_num + column]; + EXPECT_DOUBLE_EQ(real, expected.real() * conversion); + EXPECT_DOUBLE_EQ(imag, expected.imag() * conversion); + } + } + } + EXPECT_FALSE(static_cast(std::getline(input, line))); + input.close(); + std::remove(filename.c_str()); +} diff --git a/source/source_lcao/module_ri/CMakeLists.txt b/source/source_lcao/module_ri/CMakeLists.txt index cf9f511eb2c..8b0e6e39494 100644 --- a/source/source_lcao/module_ri/CMakeLists.txt +++ b/source/source_lcao/module_ri/CMakeLists.txt @@ -23,6 +23,25 @@ if (ENABLE_LIBRI) singular_value.cpp exx_lri_detail.cpp ri_util.cpp + rpa_lri.cpp + rpa_lri_detail.cpp + rpa_lri_band_io.cpp + rpa_lri_coeff_v1.cpp + rpa_lri_bz_io.cpp + rpa_lri_mpi_layout.cpp + rpa_lri_wfc_mpi.cpp + rpa_lri_coulomb.cpp + rpa_lri_coulomb_io.cpp + rpa_lri_overlap.cpp + rpa_lri_overlap_io.cpp + rpa_lri_output.cpp + rpa_lri_bz.cpp + rpa_lri_sym.cpp + rpa_lri_coeff_io.cpp + rpa_lri_velocity.cpp + rpa_lri_basis.cpp + rpa_lri_eigenvector_v1.cpp + rpa_lri_eigenvector.cpp ) endif() add_library( @@ -33,7 +52,7 @@ if (ENABLE_LIBRI) if(BUILD_TESTING) if(ENABLE_MPI) - add_subdirectory(test) + add_subdirectory(unittests) endif() endif() diff --git a/source/source_lcao/module_ri/rpa_lri.cpp b/source/source_lcao/module_ri/rpa_lri.cpp new file mode 100644 index 00000000000..7c9cde7ba73 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri.cpp @@ -0,0 +1,322 @@ +#include "rpa_lri.h" + +#include "exx_lri.h" +#include "rpa_lri_detail.h" +#include "source_base/global_function.h" +#include "source_basis/module_ao/elem_basis_idx_orb.h" +#include "source_estate/elecstate_lcao.h" +#include "source_io/module_parameter/input_parameter.h" +#include "source_cell/unitcell.h" +#include "source_lcao/module_ri/module_exx_symmetry/symm_rotation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +RPA_LRI::~RPA_LRI() +{ +} + +template +void RPA_LRI::postSCF(const UnitCell& ucell, + const MPI_Comm& mpi_comm_in, + const module_dm::DensityMatrix& dm, + const elecstate::ElecState* pelec, + const K_Vectors& kv, + const LCAO_Orbitals& orb, + const Parallel_Orbitals& parav, + const psi::Psi& psi) +try +{ + ModuleBase::TITLE("RPA_LRI", "postSCF"); + ModuleBase::timer::start("RPA_LRI", "postSCF"); + ModuleBase::GlobalFunc::MAKE_DIR(this->runtime.input.rpa_outdir); + this->ccp_rmesh_times_cut = this->runtime.input.rpa_ccp_rmesh_times; + this->ccp_rmesh_times_ewald = this->info.ccp_rmesh_times; // should be `exx_ccp_rmesh_times` + + this->cal_postSCF_exx(dm, mpi_comm_in, ucell, kv, orb, parav); + this->init(mpi_comm_in, kv, orb.cutoffs()); + this->out_bands(pelec); + this->out_eigen_vector(parav, psi); + this->out_struc(ucell); + if (this->runtime.input.out_librpa_ver == 1) + { + this->out_bz_sampling(ucell); + } + + std::cout << "rpa_pca_threshold: " << this->info.pca_threshold << std::endl; + std::cout << "rpa_ccp_rmesh_times_cut: " << this->ccp_rmesh_times_cut << std::endl; + std::cout << "rpa_ccp_rmesh_times_ewald: " << this->ccp_rmesh_times_ewald << std::endl; + std::cout << "rpa_lcao_exx(Ha): " << std::fixed << std::setprecision(15) << exx_cut_coulomb->Eexx / 2.0 + << std::endl; + + std::cout << "etxc(Ha): " << std::fixed << std::setprecision(15) << pelec->f_en.etxc / 2.0 << std::endl; + std::cout << "etot(Ha): " << std::fixed << std::setprecision(15) << pelec->f_en.etot / 2.0 << std::endl; + std::cout << "Etot_without_rpa(Ha): " << std::fixed << std::setprecision(15) + << (pelec->f_en.etot - pelec->f_en.etxc + exx_cut_coulomb->Eexx) / 2.0 << std::endl; + exx_cut_coulomb.reset(); + RpaLriDetail::trim_malloc_cache(); + + if (this->info.shrink_abfs_pca_thr >= 0.0) + { + cal_large_Cs(ucell, orb, kv); + cal_abfs_overlap(ucell, orb, kv); + RpaLriDetail::trim_malloc_cache(); + } + this->output_ewald_coulomb(ucell, kv, orb); + + ModuleBase::timer::end("RPA_LRI", "postSCF"); +} +catch (const std::exception& error) +{ + RpaLriDetail::abort_output(mpi_comm_in, error.what()); +} +catch (...) +{ + RpaLriDetail::abort_output(mpi_comm_in, "Unknown exception in postSCF"); +} + +template +void RPA_LRI::init(const MPI_Comm& mpi_comm_in, const K_Vectors& kv_in, const std::vector& orb_cutoff) +{ + ModuleBase::TITLE("RPA_LRI", "init"); + ModuleBase::timer::start("RPA_LRI", "init"); + this->mpi_comm = mpi_comm_in; + this->orb_cutoff_ = orb_cutoff; + this->lcaos = exx_cut_coulomb->lcaos; + this->p_kv = &kv_in; + this->MGT = exx_cut_coulomb->MGT; + + if (this->info.shrink_abfs_pca_thr >= 0.0) + { + this->abfs_shrink = exx_cut_coulomb->abfs; + } + else + { + this->abfs = exx_cut_coulomb->abfs; + } + // this->cv = std::move(exx_lri_rpa.cv); + // exx_lri_rpa.cv = exx_lri_rpa.cv; + ModuleBase::timer::end("RPA_LRI", "init"); +} + +template +void RPA_LRI::cal_postSCF_exx(const module_dm::DensityMatrix& dm, + const MPI_Comm& mpi_comm_in, + const UnitCell& ucell, + const K_Vectors& kv, + const LCAO_Orbitals& orb, + const Parallel_Orbitals& parav) +{ + ModuleBase::TITLE("RPA_LRI", "cal_postSCF_exx"); + ModuleBase::timer::start("RPA_LRI", "cal_postSCF_exx"); + + this->mpi_comm = mpi_comm_in; + this->p_kv = &kv; + this->orb_cutoff_ = orb.cutoffs(); + + Mix_DMk_2D mix_DMk_2D; + bool exx_spacegroup_symmetry = (this->runtime.input.nspin < 4 && ModuleSymmetry::Symmetry::symm_flag == 1); + if (exx_spacegroup_symmetry) + { + mix_DMk_2D.set_nks(kv.get_nkstot_nospin() * (this->runtime.input.nspin == 2 ? 2 : 1)); + } + else + { + mix_DMk_2D.set_nks(kv.get_nks()); + } + + mix_DMk_2D.set_mixing_plain(1.0); + + // -------------------------------------------------------------------------------------- + // NOTE: ABFs are constructed here BEFORE symmetry processing to calculate the correct + // abfs_Lmax for symrot.set_abfs_Lmax(). Previously, abfs_Lmax was obtained either from + // exx_cut_coulomb->abfs_Lmax() (which was nullptr at that point) or from this->info.abfs_Lmax + // (which defaults to 0). This caused abfs_Lmax to degrade to 0 in RPA + symmetry path, + // leading to incorrect rotation matrices for ABFs with higher angular momentum. + // -------------------------------------------------------------------------------------- + std::vector>> abfs_for_lmax; + if (this->info.shrink_abfs_pca_thr >= 0.0) + { + this->lcaos = Exx_Abfs::Construct_Orbs::change_orbs(orb, this->info.kmesh_times); + abfs_for_lmax = Exx_Abfs::Construct_Orbs::abfs_same_atom(ucell, + orb, + this->lcaos, + this->info.kmesh_times, + this->info.shrink_abfs_pca_thr); + if (!this->info.files_shrink_abfs.empty()) + { + abfs_for_lmax = Exx_Abfs::IO::construct_abfs(abfs_for_lmax, + orb, + this->info.files_shrink_abfs, + this->info.kmesh_times); + } + } + else + { + this->lcaos = Exx_Abfs::Construct_Orbs::change_orbs(orb, this->info.kmesh_times); + abfs_for_lmax = Exx_Abfs::Construct_Orbs::abfs_same_atom(ucell, + orb, + this->lcaos, + this->info.kmesh_times, + this->info.pca_threshold); + if (!this->info.files_abfs.empty()) + { + abfs_for_lmax + = Exx_Abfs::IO::construct_abfs(abfs_for_lmax, orb, this->info.files_abfs, this->info.kmesh_times); + } + } + + ModuleSymmetry::Symmetry_rotation symrot; + if (exx_spacegroup_symmetry) + { + const std::array period = RI_Util::get_Born_vonKarmen_period(kv); + const auto& Rs = RI_Util::get_Born_von_Karmen_cells(period); + symrot.find_irred_sector(ucell.symm, + ucell.atoms, + ucell.nat, + ucell.ntype, + ucell.iat2it, + ucell.iat2ia, + Rs, + period, + ucell.lat, + this->runtime.global_out_dir); + // set Lmax of the rotation matrices to max(l_ao, l_abf), to support rotation under ABF + // NOTE: Using Exx_Abfs::Construct_Orbs::get_Lmax() to compute Lmax from the actual ABFs + // instead of relying on exx_cut_coulomb->abfs_Lmax() (not yet initialized) or + // this->info.abfs_Lmax (defaults to 0). This ensures correct Lmax for symmetry rotation. + symrot.set_abfs_Lmax(Exx_Abfs::Construct_Orbs::get_Lmax(abfs_for_lmax)); + symrot.cal_Ms(kv, + ucell.symm, + ucell.atoms, + ucell.latvec, + ucell.lmax, + ucell.nat, + ucell.iat2it, + ucell.iat2ia, + parav, + this->runtime.input.nspin); + // output Ts (symrot_R.txt) and Ms (symrot_k.txt) + ModuleSymmetry::print_symrot_info_R(symrot, ucell.symm, ucell.lmax, Rs); + ModuleSymmetry::print_symrot_info_k(symrot, kv, ucell); + mix_DMk_2D.mix(symrot.restore_dm(kv, dm.get_dmk_vec(), parav), true); + } + else + { + mix_DMk_2D.mix(dm.get_dmk_vec(), true); + } + + const std::vector>>> Ds + = RI_2D_Comm::split_m2D_ktoR(ucell, + kv, + mix_DMk_2D.get_DMk_out(), + parav, + this->runtime.input.nspin, + exx_spacegroup_symmetry); + + if (!exx_cut_coulomb) + { + Exx_Info_RI local_info = this->info; + local_info.ccp_rmesh_times = this->ccp_rmesh_times_cut; + Exx_LRI* new_exx = new Exx_LRI(local_info); + exx_cut_coulomb.reset(new_exx); + } + + if (this->info.shrink_abfs_pca_thr >= 0.0) + { + // NOTE: Reuse abfs_for_lmax constructed earlier to avoid redundant ABFs construction. + // This ensures consistency between the ABFs used for Lmax calculation and the ABFs + // used for actual EXX computation. + this->abfs_shrink = abfs_for_lmax; + Exx_Abfs::Construct_Orbs::print_orbs_size(ucell, abfs_shrink, this->runtime.log); + exx_cut_coulomb->init_spencer(mpi_comm_in, ucell, kv, orb, abfs_shrink); + } + else + // NOTE: Reuse abfs_for_lmax constructed earlier to avoid redundant ABFs construction. + exx_cut_coulomb->init_spencer(mpi_comm_in, ucell, kv, orb, abfs_for_lmax); + // cal C and V for exx + Exx_LRI* cut_coulomb = exx_cut_coulomb.get(); + this->output_cut_coulomb_cs(ucell, cut_coulomb); + // cal CVCD + if (exx_spacegroup_symmetry && this->runtime.input.exx_symmetry_realspace) + { + exx_cut_coulomb->cal_exx_elec(Ds, ucell, parav, &symrot); + } + else + { + exx_cut_coulomb->cal_exx_elec(Ds, ucell, parav); + } + // cout<<"postSCF_Eexx: "<::postSCF(const UnitCell& ucell, + const MPI_Comm& mpi_comm_in, + const module_dm::DensityMatrix& dm, + const elecstate::ElecState* pelec, + const K_Vectors& kv, + const LCAO_Orbitals& orb, + const Parallel_Orbitals& parav, + const psi::Psi& psi); +template void RPA_LRI, double>::postSCF( + const UnitCell& ucell, + const MPI_Comm& mpi_comm_in, + const module_dm::DensityMatrix, double>& dm, + const elecstate::ElecState* pelec, + const K_Vectors& kv, + const LCAO_Orbitals& orb, + const Parallel_Orbitals& parav, + const psi::Psi>& psi); +template void RPA_LRI::init(const MPI_Comm& mpi_comm_in, + const K_Vectors& kv_in, + const std::vector& orb_cutoff); +template void RPA_LRI, double>::init(const MPI_Comm& mpi_comm_in, + const K_Vectors& kv_in, + const std::vector& orb_cutoff); +template void RPA_LRI::cal_postSCF_exx(const module_dm::DensityMatrix& dm, + const MPI_Comm& mpi_comm_in, + const UnitCell& ucell, + const K_Vectors& kv, + const LCAO_Orbitals& orb, + const Parallel_Orbitals& parav); +template void RPA_LRI, double>::cal_postSCF_exx( + const module_dm::DensityMatrix, double>& dm, + const MPI_Comm& mpi_comm_in, + const UnitCell& ucell, + const K_Vectors& kv, + const LCAO_Orbitals& orb, + const Parallel_Orbitals& parav); +template RPA_LRI::~RPA_LRI(); +template RPA_LRI, double>::~RPA_LRI(); + +template +void RPA_LRI::out_eigen_vector(const Parallel_Orbitals& parav, const psi::Psi& psi) +{ + ModuleBase::TITLE("DFT_RPA_interface", "out_eigen_vector"); + if (this->runtime.input.out_librpa_ver == 1) + { + this->out_eigen_vector_v1(parav, psi); + } + else + { + this->out_eigen_vector_legacy(parav, psi); + } +} + +template void RPA_LRI::out_eigen_vector(const Parallel_Orbitals& parav, const psi::Psi& psi); +template void RPA_LRI, double>::out_eigen_vector(const Parallel_Orbitals& parav, + const psi::Psi>& psi); diff --git a/source/source_lcao/module_ri/rpa_lri.h b/source/source_lcao/module_ri/rpa_lri.h index 675ed5ac251..5ac44987ca3 100644 --- a/source/source_lcao/module_ri/rpa_lri.h +++ b/source/source_lcao/module_ri/rpa_lri.h @@ -8,6 +8,7 @@ #include "lri_cv.h" #include "source_hamilt/module_xc/exx_info_ri.h" +#include "source_psi/psi.h" // #include "module_xc/exx_info.h" // #include "source_basis/module_ao/orb_atomic_lm.h" #include "source_base/matrix.h" @@ -15,15 +16,49 @@ // #include #include #include +#include +#include #include #include #include +#include #include class Parallel_Orbitals; class K_Vectors; +class Grid_Driver; +class TwoCenterBundle; +class UnitCell; +class LCAO_Orbitals; +struct Input_para; template class Exx_LRI; +namespace elecstate +{ +class ElecState; +} + +namespace module_dm +{ +template class DensityMatrix; +} + +namespace ModuleRI +{ + +// Capture RPA producer state at the SCF boundary to avoid process-global IO. +struct RpaLriRuntime +{ + const Input_para& input; + const std::string& global_out_dir; + int nlocal; + int rank; + int nproc; + std::ofstream& log; +}; + +} // namespace ModuleRI + template class RPA_LRI { private: @@ -37,7 +72,8 @@ template class RPA_LRI using TatomR = std::array; // tmp public: - RPA_LRI(const Exx_Info_RI &info_in) : info(info_in) + RPA_LRI(const Exx_Info_RI& info_in, const ModuleRI::RpaLriRuntime& runtime_in) + : runtime(runtime_in), info(info_in) { } ~RPA_LRI(); @@ -68,8 +104,15 @@ template class RPA_LRI std::string filename, const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs_s, const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs); + void out_abfs_overlap_v1(const UnitCell& ucell, + std::map>>& overlap_abfs_abfs, + std::map>>& overlap_abfs_abf, + std::string filename, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs_s, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs); void out_eigen_vector(const Parallel_Orbitals& parav, const psi::Psi& psi); void out_struc(const UnitCell& ucell); + void out_bz_sampling(const UnitCell& ucell); void out_bands(const elecstate::ElecState *pelec); void out_velocity(const UnitCell& ucell, const Grid_Driver& gd, @@ -79,10 +122,20 @@ template class RPA_LRI const elecstate::ElecState* pelec); void output_cut_coulomb_cs(const UnitCell& ucell, Exx_LRI* exx_lri_rpa); void out_Cs(const UnitCell& ucell, std::map>>& Cs_in, std::string filename); + void out_Cs_v1(const UnitCell& ucell, std::map>>& Cs_in, std::string filename); void out_coulomb_k(const UnitCell& ucell, std::map>>& Vs, std::string filename, Exx_LRI* exx_lri); + void out_coulomb_k_v1(const UnitCell& ucell, + std::map>>& Vs, + std::string filename, + Exx_LRI* exx_lri); + void out_librpa_basis_v1(const UnitCell& ucell, + Exx_LRI* exx_lri, + const std::string& aux_filename, + const std::string& legacy_filename, + bool write_wfc_basis); // void print_matrix(char *desc, const ModuleBase::matrix &mat); // void print_complex_matrix(char *desc, const ModuleBase::ComplexMatrix &mat); // void init(const MPI_Comm &mpi_comm_in); @@ -91,7 +144,12 @@ template class RPA_LRI Tdata Erpa; private: - const std::string& outdir = PARAM.inp.rpa_outdir; + void out_eigen_vector_v1(const Parallel_Orbitals& parav, const psi::Psi& psi); + void out_eigen_vector_legacy(const Parallel_Orbitals& parav, const psi::Psi& psi); + Conv_Coulomb_Pot_K::Coulomb_Method select_coulomb_basis_method_(Exx_LRI* exx_lri) const; + std::vector collect_atom_naux_(const UnitCell& ucell, Exx_LRI* exx_lri) const; + + const ModuleRI::RpaLriRuntime runtime; Exx_Info_RI info; const K_Vectors *p_kv=nullptr; MPI_Comm mpi_comm; @@ -117,6 +175,4 @@ template class RPA_LRI std::unique_ptr> exx_cut_coulomb; std::unique_ptr> exx_full_coulomb; }; -#include "rpa_lri.hpp" - #endif diff --git a/source/source_lcao/module_ri/rpa_lri.hpp b/source/source_lcao/module_ri/rpa_lri.hpp deleted file mode 100644 index e30641d8f44..00000000000 --- a/source/source_lcao/module_ri/rpa_lri.hpp +++ /dev/null @@ -1,1602 +0,0 @@ -//======================= -// AUTHOR : Rong Shi -// DATE : 2022-12-09 -//======================= - -#ifndef RPA_LRI_HPP -#define RPA_LRI_HPP -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "source_lcao/module_ri/module_exx_symmetry/symm_rotation.h" -#include "source_cell/unitcell.h" - -#include "rpa_lri.h" -#include "exx_lri.h" -#include "source_basis/module_ao/elem_basis_idx_orb.h" -#include "source_base/global_function.h" -#include "source_estate/elecstate_lcao.h" -#include "source_io/module_parameter/parameter.h" -#include "source_lcao/module_lr/utils/spectrum_mo.hpp" - -#if defined(__GLIBC__) -#include -#endif - -namespace RpaLriDetail -{ -inline void trim_malloc_cache() -{ -#if defined(__GLIBC__) - malloc_trim(0); -#endif -} -} // namespace RpaLriDetail - -template -RPA_LRI::~RPA_LRI() = default; - -template -void RPA_LRI::postSCF(const UnitCell& ucell, - const MPI_Comm& mpi_comm_in, - const module_dm::DensityMatrix& dm, - const elecstate::ElecState* pelec, - const K_Vectors& kv, - const LCAO_Orbitals& orb, - const Parallel_Orbitals& parav, - const psi::Psi& psi) -{ - ModuleBase::TITLE("RPA_LRI", "postSCF"); - ModuleBase::timer::start("RPA_LRI", "postSCF"); - ModuleBase::GlobalFunc::MAKE_DIR(outdir); - this->ccp_rmesh_times_cut = PARAM.inp.rpa_ccp_rmesh_times; - this->ccp_rmesh_times_ewald = this->info.ccp_rmesh_times; // should be `exx_ccp_rmesh_times` - - this->cal_postSCF_exx(dm, mpi_comm_in, ucell, kv, orb, parav); - this->init(mpi_comm_in, kv, orb.cutoffs()); - this->out_bands(pelec); - this->out_eigen_vector(parav, psi); - this->out_struc(ucell); - - std::cout << "rpa_pca_threshold: " << this->info.pca_threshold << std::endl; - std::cout << "rpa_ccp_rmesh_times_cut: " << this->ccp_rmesh_times_cut << std::endl; - std::cout << "rpa_ccp_rmesh_times_ewald: " << this->ccp_rmesh_times_ewald << std::endl; - std::cout << "rpa_lcao_exx(Ha): " << std::fixed << std::setprecision(15) << exx_cut_coulomb->Eexx / 2.0 << std::endl; - - std::cout << "etxc(Ha): " << std::fixed << std::setprecision(15) << pelec->f_en.etxc / 2.0 << std::endl; - std::cout << "etot(Ha): " << std::fixed << std::setprecision(15) << pelec->f_en.etot / 2.0 << std::endl; - std::cout << "Etot_without_rpa(Ha): " << std::fixed << std::setprecision(15) - << (pelec->f_en.etot - pelec->f_en.etxc + exx_cut_coulomb->Eexx) / 2.0 << std::endl; - exx_cut_coulomb.reset(); - RpaLriDetail::trim_malloc_cache(); - - if (this->info.shrink_abfs_pca_thr >= 0.0) - { - cal_large_Cs(ucell, orb, kv); - cal_abfs_overlap(ucell, orb, kv); - RpaLriDetail::trim_malloc_cache(); - } - this->output_ewald_coulomb(ucell, kv, orb); - - ModuleBase::timer::end("RPA_LRI", "postSCF"); -} - -template -void RPA_LRI::init(const MPI_Comm& mpi_comm_in, const K_Vectors& kv_in, const std::vector& orb_cutoff) -{ - ModuleBase::TITLE("RPA_LRI", "init"); - ModuleBase::timer::start("RPA_LRI", "init"); - this->mpi_comm = mpi_comm_in; - this->orb_cutoff_ = orb_cutoff; - this->lcaos = exx_cut_coulomb->lcaos; - this->p_kv = &kv_in; - this->MGT = exx_cut_coulomb->MGT; - - if (this->info.shrink_abfs_pca_thr >= 0.0) - { - this->abfs_shrink = exx_cut_coulomb->abfs; - } - else - { - this->abfs = exx_cut_coulomb->abfs; - } - // this->cv = std::move(exx_lri_rpa.cv); - // exx_lri_rpa.cv = exx_lri_rpa.cv; - ModuleBase::timer::end("RPA_LRI", "init"); -} - -template -void RPA_LRI::cal_postSCF_exx(const module_dm::DensityMatrix& dm, - const MPI_Comm& mpi_comm_in, - const UnitCell& ucell, - const K_Vectors& kv, - const LCAO_Orbitals& orb, - const Parallel_Orbitals& parav) -{ - ModuleBase::TITLE("RPA_LRI", "cal_postSCF_exx"); - ModuleBase::timer::start("RPA_LRI", "cal_postSCF_exx"); - - this->mpi_comm = mpi_comm_in; - this->p_kv = &kv; - this->orb_cutoff_ = orb.cutoffs(); - - Mix_DMk_2D mix_DMk_2D; - bool exx_spacegroup_symmetry = (PARAM.inp.nspin < 4 && ModuleSymmetry::Symmetry::symm_flag == 1); - if (exx_spacegroup_symmetry) - {mix_DMk_2D.set_nks(kv.get_nkstot_nospin() * (PARAM.inp.nspin == 2 ? 2 : 1));} - else - {mix_DMk_2D.set_nks(kv.get_nks());} - - mix_DMk_2D.set_mixing_plain(1.0); - - // -------------------------------------------------------------------------------------- - // NOTE: ABFs are constructed here BEFORE symmetry processing to calculate the correct - // abfs_Lmax for symrot.set_abfs_Lmax(). Previously, abfs_Lmax was obtained either from - // exx_cut_coulomb->abfs_Lmax() (which was nullptr at that point) or from this->info.abfs_Lmax - // (which defaults to 0). This caused abfs_Lmax to degrade to 0 in RPA + symmetry path, - // leading to incorrect rotation matrices for ABFs with higher angular momentum. - // -------------------------------------------------------------------------------------- - std::vector>> abfs_for_lmax; - if (this->info.shrink_abfs_pca_thr >= 0.0) - { - this->lcaos = Exx_Abfs::Construct_Orbs::change_orbs(orb, this->info.kmesh_times); - abfs_for_lmax = Exx_Abfs::Construct_Orbs::abfs_same_atom(ucell, orb, this->lcaos, this->info.kmesh_times, this->info.shrink_abfs_pca_thr); - if (!this->info.files_shrink_abfs.empty()) - { - abfs_for_lmax = Exx_Abfs::IO::construct_abfs(abfs_for_lmax, orb, this->info.files_shrink_abfs, this->info.kmesh_times); - } - } - else - { - this->lcaos = Exx_Abfs::Construct_Orbs::change_orbs(orb, this->info.kmesh_times); - abfs_for_lmax = Exx_Abfs::Construct_Orbs::abfs_same_atom(ucell, orb, this->lcaos, this->info.kmesh_times, this->info.pca_threshold); - if (!this->info.files_abfs.empty()) - { - abfs_for_lmax = Exx_Abfs::IO::construct_abfs(abfs_for_lmax, orb, this->info.files_abfs, this->info.kmesh_times); - } - } - - ModuleSymmetry::Symmetry_rotation symrot; - if (exx_spacegroup_symmetry) - { - const std::array period = RI_Util::get_Born_vonKarmen_period(kv); - const auto& Rs = RI_Util::get_Born_von_Karmen_cells(period); - symrot.find_irred_sector(ucell.symm, ucell.atoms, - ucell.nat, ucell.ntype, ucell.iat2it, ucell.iat2ia, - Rs, period, ucell.lat, PARAM.globalv.global_out_dir); - // set Lmax of the rotation matrices to max(l_ao, l_abf), to support rotation under ABF - // NOTE: Using Exx_Abfs::Construct_Orbs::get_Lmax() to compute Lmax from the actual ABFs - // instead of relying on exx_cut_coulomb->abfs_Lmax() (not yet initialized) or - // this->info.abfs_Lmax (defaults to 0). This ensures correct Lmax for symmetry rotation. - symrot.set_abfs_Lmax(Exx_Abfs::Construct_Orbs::get_Lmax(abfs_for_lmax)); - symrot.cal_Ms(kv, ucell.symm, ucell.atoms, ucell.latvec, ucell.lmax, - ucell.nat, ucell.iat2it, ucell.iat2ia, parav, PARAM.inp.nspin); - // output Ts (symrot_R.txt) and Ms (symrot_k.txt) - ModuleSymmetry::print_symrot_info_R(symrot, ucell.symm, ucell.lmax, Rs); - ModuleSymmetry::print_symrot_info_k(symrot, kv, ucell); - mix_DMk_2D.mix(symrot.restore_dm(kv, dm.get_dmk_vec(), parav), true); - } - else { mix_DMk_2D.mix(dm.get_dmk_vec(), true); } - - const std::vector>>> - Ds = RI_2D_Comm::split_m2D_ktoR( - ucell, - kv, - mix_DMk_2D.get_DMk_out(), - parav, - PARAM.inp.nspin, - exx_spacegroup_symmetry); - - if (!exx_cut_coulomb) - { - Exx_Info_RI local_info = this->info; - local_info.ccp_rmesh_times = this->ccp_rmesh_times_cut; - Exx_LRI* new_exx = new Exx_LRI(local_info); - exx_cut_coulomb.reset(new_exx); - } - - if (this->info.shrink_abfs_pca_thr >= 0.0) - { - // NOTE: Reuse abfs_for_lmax constructed earlier to avoid redundant ABFs construction. - // This ensures consistency between the ABFs used for Lmax calculation and the ABFs - // used for actual EXX computation. - this->abfs_shrink = abfs_for_lmax; - Exx_Abfs::Construct_Orbs::print_orbs_size(ucell, abfs_shrink, GlobalV::ofs_running); - exx_cut_coulomb->init_spencer(mpi_comm_in, ucell, kv, orb, abfs_shrink); - } - else - // NOTE: Reuse abfs_for_lmax constructed earlier to avoid redundant ABFs construction. - exx_cut_coulomb->init_spencer(mpi_comm_in, ucell, kv, orb, abfs_for_lmax); - // cal C and V for exx - Exx_LRI* cut_coulomb = exx_cut_coulomb.get(); - this->output_cut_coulomb_cs(ucell, cut_coulomb); - // cal CVCD - if (exx_spacegroup_symmetry && PARAM.inp.exx_symmetry_realspace) - { - exx_cut_coulomb->cal_exx_elec(Ds, ucell, parav, &symrot); - } - else - { - exx_cut_coulomb->cal_exx_elec(Ds, ucell, parav); - } - // cout<<"postSCF_Eexx: "< -void RPA_LRI::output_cut_coulomb_cs(const UnitCell& ucell, Exx_LRI* exx_lri_rpa) -{ - ModuleBase::TITLE("RPA_LRI", "output_cut_coulomb_cs"); - ModuleBase::timer::start("RPA_LRI", "output_cut_coulomb_cs"); - - std::map>> Vs_cut_IJR; - std::map>> Cs; - std::map>> tmp; - std::cout << "Use rpa_ccp_rmesh_times=" << this->ccp_rmesh_times_cut << " to calculate cut Coulomb" << std::endl; - // Shrink_ABFS_ORBITAL cannot exceed this angular momentum of MGT - exx_lri_rpa->cal_cut_coulomb_cs(Vs_cut_IJR, Cs, ucell, PARAM.inp.out_ri_cv); - // MPI: {ia0, {ia1, R}} to {ia0, ia1} - std::vector atoms(ucell.nat); - for (int iat = 0; iat < ucell.nat; ++iat) - atoms[iat] = iat; - const std::array period_Vs - = LRI_CV_Tools::cal_latvec_range(1 + this->ccp_rmesh_times_cut, ucell, this->orb_cutoff_); - const std::pair, std::vector>>> list_As_Vs_atoms - = RI::Distribute_Equally::distribute_atoms(this->mpi_comm, atoms, period_Vs, 2, false); - const auto list_A0_pair_R = list_As_Vs_atoms.first; - const auto list_A1_pair_R = list_As_Vs_atoms.second[0]; - std::set atoms00; - std::set atoms01; - for (const auto& I: list_A0_pair_R) - { - atoms00.insert(I); - } - for (const auto& JR: list_A1_pair_R) - { - atoms01.insert(JR.first); - } - std::map>> Vs_cut_IJ - = RI_2D_Comm::comm_map2_first(this->mpi_comm, Vs_cut_IJR, atoms00, atoms01); - Vs_cut_IJR.clear(); - const std::array period = {p_kv->nmp[0], p_kv->nmp[1], p_kv->nmp[2]}; - this->Vs_period = RI::RI_Tools::cal_period(Vs_cut_IJ, period); - this->out_coulomb_k(ucell, this->Vs_period, "coulomb_cut_", exx_lri_rpa); - Vs_period.clear(); - Vs_period.swap(tmp); - - this->Cs_period = RI::RI_Tools::cal_period(Cs, period); - this->Cs_period = exx_lri_rpa->exx_lri.post_2D.set_tensors_map2(this->Cs_period); - - if (this->info.shrink_abfs_pca_thr >= 0.0) - this->out_Cs(ucell, this->Cs_period, "Cs_shrinked_data_"); - else - this->out_Cs(ucell, this->Cs_period, "Cs_data_"); - Cs_period.clear(); - Cs_period.swap(tmp); - - ModuleBase::timer::end("RPA_LRI", "output_cut_coulomb_cs"); -} - -template -void RPA_LRI::output_ewald_coulomb(const UnitCell& ucell, const K_Vectors& kv, const LCAO_Orbitals& orb) -{ - ModuleBase::TITLE("RPA_LRI", "output_ewald_coulomb"); - ModuleBase::timer::start("RPA_LRI", "output_ewald_coulomb"); - - Exx_Info_RI local_info = this->info; - local_info.ccp_rmesh_times = this->ccp_rmesh_times_ewald; - if (!exx_full_coulomb) - { - Exx_LRI* new_exx = new Exx_LRI(local_info); - exx_full_coulomb.reset(new_exx); - } - - if (this->info.shrink_abfs_pca_thr >= 0.0) - exx_full_coulomb->init(mpi_comm, ucell, kv, orb, this->abfs_shrink); - else - exx_full_coulomb->init(mpi_comm, ucell, kv, orb, this->abfs); - std::map>> Vs_full_IJR; - std::map>> Cs; - std::map>> tmp; - exx_full_coulomb->cal_ewald_coulomb(Vs_full_IJR, Cs, ucell, PARAM.inp.out_ri_cv); - // MPI: {ia0, {ia1, R}} to {ia0, ia1} - std::vector atoms(ucell.nat); - for (int iat = 0; iat < ucell.nat; ++iat) - atoms[iat] = iat; - const std::array period_Vs - = LRI_CV_Tools::cal_latvec_range(1 + this->ccp_rmesh_times_ewald, ucell, this->orb_cutoff_); - const std::pair, std::vector>>> list_As_Vs_atoms - = RI::Distribute_Equally::distribute_atoms(mpi_comm, atoms, period_Vs, 2, false); - const auto list_A0_pair_R = list_As_Vs_atoms.first; - const auto list_A1_pair_R = list_As_Vs_atoms.second[0]; - std::set atoms00; - std::set atoms01; - for (const auto& I: list_A0_pair_R) - { - atoms00.insert(I); - } - for (const auto& JR: list_A1_pair_R) - { - atoms01.insert(JR.first); - } - std::map>> Vs_full_IJ - = RI_2D_Comm::comm_map2_first(mpi_comm, Vs_full_IJR, atoms00, atoms01); - Vs_full_IJR.clear(); - - const std::array period = {p_kv->nmp[0], p_kv->nmp[1], p_kv->nmp[2]}; - this->Vs_period = RI::RI_Tools::cal_period(Vs_full_IJ, period); - Exx_LRI* full_coulomb = exx_full_coulomb.get(); - this->out_coulomb_k(ucell, this->Vs_period, "coulomb_mat_", full_coulomb); - Vs_period.clear(); - Vs_period.swap(tmp); - Cs.clear(); - Cs.swap(tmp); - - exx_full_coulomb.reset(); - RpaLriDetail::trim_malloc_cache(); - - ModuleBase::timer::end("RPA_LRI", "output_ewald_coulomb"); -} - -template -void RPA_LRI::cal_large_Cs(const UnitCell& ucell, const LCAO_Orbitals& orb, const K_Vectors& kv) -{ - ModuleBase::TITLE("RPA_LRI", "cal_large_Cs"); - ModuleBase::timer::start("RPA_LRI", "cal_large_Cs"); - if (!exx_cut_coulomb) - { - Exx_Info_RI local_info = this->info; - local_info.ccp_rmesh_times = this->ccp_rmesh_times_cut; - Exx_LRI* new_exx = new Exx_LRI(local_info); - exx_cut_coulomb.reset(new_exx); - } - exx_cut_coulomb->init_spencer(this->mpi_comm, ucell, kv, orb); - ModuleBase::GlobalFunc::DONE(GlobalV::ofs_running, "exx_cut_coulomb->init"); - this->abfs = exx_cut_coulomb->abfs; - this->MGT = exx_cut_coulomb->MGT; - std::vector atoms(ucell.nat); - for (int iat = 0; iat < ucell.nat; ++iat) - { - atoms[iat] = iat; - } - std::map atoms_pos; - for (int iat = 0; iat < ucell.nat; ++iat) - atoms_pos[iat] = RI_Util::Vector3_to_array3(ucell.atoms[ucell.iat2it[iat]].tau[ucell.iat2ia[iat]]); - const std::array latvec = {RI_Util::Vector3_to_array3(ucell.a1), - RI_Util::Vector3_to_array3(ucell.a2), - RI_Util::Vector3_to_array3(ucell.a3)}; - const std::array period = {p_kv->nmp[0], p_kv->nmp[1], p_kv->nmp[2]}; - this->exx_cut_coulomb->exx_lri.set_parallel(this->mpi_comm, atoms_pos, latvec, period); - auto center2_obj_it = this->exx_cut_coulomb->exx_objs.find(Conv_Coulomb_Pot_K::Coulomb_Method::Center2); - if (center2_obj_it == this->exx_cut_coulomb->exx_objs.end()) - { - throw std::invalid_argument("RPA_LRI::cal_large_Cs expected a Center2 cut-Coulomb object after init_spencer."); - } - center2_obj_it->second.cv.set_orbitals(ucell, - orb, - this->lcaos, - exx_cut_coulomb->abfs, - center2_obj_it->second.abfs_ccp, - this->info.kmesh_times, - this->MGT, // get MGT from exx_cut_coulomb and used in `cal_abfs_overlap` - true); - - const std::array period_Vs - = LRI_CV_Tools::cal_latvec_range(1 + this->ccp_rmesh_times_cut, ucell, orb_cutoff_); - std::pair, std::vector>>>> list_As_Vs - = RI::Distribute_Equally::distribute_atoms_periods(this->mpi_comm, atoms, period_Vs, 2, false); - ModuleBase::GlobalFunc::DONE(GlobalV::ofs_running, "cal_large_Vs start"); - std::map>> Vs_cut_IJR - = center2_obj_it->second.cv.cal_Vs(ucell, list_As_Vs.first, list_As_Vs.second[0], {{"writable_Vws", true}}); - ModuleBase::GlobalFunc::DONE(GlobalV::ofs_running, "cal_large_Vs end"); - - const std::array period_Cs = LRI_CV_Tools::cal_latvec_range(2, ucell, orb_cutoff_); - const std::pair, std::vector>>>> list_As_Cs - = RI::Distribute_Equally::distribute_atoms_periods(this->mpi_comm, atoms, period_Cs, 2, false); - std::pair>>, - std::map, 3>>>> - Cs_dCs = center2_obj_it->second.cv.cal_Cs_dCs(ucell, - list_As_Cs.first, - list_As_Cs.second[0], - {{"cal_dC", false}, - {"writable_Cws", true}, - {"writable_dCws", true}, - {"writable_Vws", false}, - {"writable_dVws", false}}); - ModuleBase::GlobalFunc::DONE(GlobalV::ofs_running, "cal_large_Cs"); - - std::map>> tmp; - if (PARAM.inp.out_unshrinked_v) - { - this->Vs_period = RI::RI_Tools::cal_period(Vs_cut_IJR, period); - Vs_cut_IJR.clear(); - ModuleBase::GlobalFunc::DONE(GlobalV::ofs_running, "Vs_period"); - // MPI: {ia0, {ia1, R}} to {ia0, ia1} - const std::pair, std::vector>>> list_As_Vs_atoms - = RI::Distribute_Equally::distribute_atoms(this->mpi_comm, atoms, period_Vs, 2, false); - const auto list_A0_pair_R = list_As_Vs_atoms.first; - const auto list_A1_pair_R = list_As_Vs_atoms.second[0]; - std::set atoms00; - std::set atoms01; - for (const auto& I: list_A0_pair_R) - { - atoms00.insert(I); - } - for (const auto& JR: list_A1_pair_R) - { - atoms01.insert(JR.first); - } - - this->Vs_period = RI_2D_Comm::comm_map2_first(this->mpi_comm, this->Vs_period, atoms00, atoms01); - ModuleBase::GlobalFunc::DONE(GlobalV::ofs_running, "Vs_period_comm"); - - Exx_LRI* cut_coulomb = exx_cut_coulomb.get(); - this->out_coulomb_k(ucell, this->Vs_period, "coulomb_unshrinked_cut_", cut_coulomb); - ModuleBase::GlobalFunc::DONE(GlobalV::ofs_running, "out_large_Vs"); - this->Vs_period.clear(); - this->Vs_period.swap(tmp); - } - - std::map>>& Cs = std::get<0>(Cs_dCs); - this->Cs_period = RI::RI_Tools::cal_period(Cs, period); - this->Cs_period = exx_cut_coulomb->exx_lri.post_2D.set_tensors_map2(this->Cs_period); - this->out_Cs(ucell, this->Cs_period, "Cs_data_"); - ModuleBase::GlobalFunc::DONE(GlobalV::ofs_running, "out_large_Cs"); - this->Cs_period.clear(); - this->Cs_period.swap(tmp); - exx_cut_coulomb.reset(); - RpaLriDetail::trim_malloc_cache(); - - ModuleBase::timer::end("RPA_LRI", "cal_large_Cs"); -} - -template -void RPA_LRI::cal_abfs_overlap(const UnitCell& ucell, const LCAO_Orbitals& orb, const K_Vectors& kv) -{ - ModuleBase::TITLE("DFT_RPA_interface", "cal_abfs_overlap"); - const auto& abfs_s = this->abfs_shrink; - - // - Matrix_Orbs11 m_abfs_abfs; - // - Matrix_Orbs11 m_abfs_abf; - - m_abfs_abf.MGT = this->MGT; - m_abfs_abf.init(abfs_s, this->abfs, ucell, orb, this->info.kmesh_times); - m_abfs_abf.init_radial_table(); - - m_abfs_abfs.MGT = this->MGT; - m_abfs_abfs.init(abfs_s, abfs_s, ucell, orb, this->info.kmesh_times); - m_abfs_abfs.init_radial_table(); - // get Rlist - const std::array period = RI_Util::get_Born_vonKarmen_period(kv); - const auto R_period = RI_Util::get_Born_von_Karmen_cells(period); - std::map>> overlap_abfs_abfs; - std::map>> overlap_abfs_abf; - - // index of smaller abfs - const ModuleBase::Element_Basis_Index::Range range_abfs_s = ModuleBase::Element_Basis_Index::construct_range(abfs_s); - const ModuleBase::Element_Basis_Index::IndexLNM index_abfs_s - = ModuleBase::Element_Basis_Index::construct_index(range_abfs_s); - // index of larger abfs - const ModuleBase::Element_Basis_Index::Range range_abfs = ModuleBase::Element_Basis_Index::construct_range(this->abfs); - const ModuleBase::Element_Basis_Index::IndexLNM index_abfs - = ModuleBase::Element_Basis_Index::construct_index(range_abfs); - - auto orb_cutoff_ = orb.cutoffs(); - const std::array period_Vs = LRI_CV_Tools::cal_latvec_range(2, ucell, orb_cutoff_); - std::vector atoms(ucell.nat); - for (int iat = 0; iat < ucell.nat; ++iat) - atoms[iat] = iat; - const std::pair, std::vector>>>> list_As_Vs - = RI::Distribute_Equally::distribute_atoms_periods(this->mpi_comm, atoms, period_Vs, 2, false); - -// Huanjing Gong debug -// std::stringstream ss; -// ss << "IJR_" << GlobalV::MY_RANK << ".txt"; -// std::ofstream ofs; -// ofs.open(ss.str().c_str(), std::ios::out); -// for (size_t iA = 0; iA < list_As_Vs.first.size(); ++iA) -// { -// const auto& A = list_As_Vs.first[iA]; -// for (const auto& BR: list_As_Vs.second[0]) -// { -// const auto& B = BR.first; -// const auto& R = BR.second; -// ofs << "ABR: " << A << B << "," << R.at(0) << R.at(1) << R.at(2) << std::endl; -// } -// } -// ofs.close(); -#pragma omp parallel - { - using LocalMapType = std::map>, RI::Tensor>; - std::map overlap_abfs_abfs_local; - std::map overlap_abfs_abf_local; - -#pragma omp for schedule(dynamic) nowait - for (size_t iA = 0; iA < list_As_Vs.first.size(); ++iA) - { - const auto& A = list_As_Vs.first[iA]; - for (const auto& BR: list_As_Vs.second[0]) - { - const auto& B = BR.first; - const auto& R = BR.second; - - const size_t TA = ucell.iat2it[A]; - const size_t IA = ucell.iat2ia[A]; - const auto& tauA = ucell.atoms[TA].tau[IA]; - const size_t TB = ucell.iat2it[B]; - const size_t IB = ucell.iat2ia[B]; - const auto& tauB = ucell.atoms[TB].tau[IB]; - - const ModuleBase::Vector3 tauB_shift - = tauB + (RI_Util::array3_to_Vector3(R) * ucell.latvec); - const ModuleBase::Vector3 tau_delta = tauB_shift - tauA; - static const ModuleBase::Vector3 tau0(0.0, 0.0, 0.0); - - auto& local_abfs_abfs = overlap_abfs_abfs_local[A]; - local_abfs_abfs[{B, R}] - = m_abfs_abfs.template cal_overlap_matrix(TA, - TB, - tau0, - tau_delta, - index_abfs_s, - index_abfs_s, - Matrix_Orbs11::Matrix_Order::AB); - - auto& local_abfs_abf = overlap_abfs_abf_local[A]; - local_abfs_abf[{B, R}] - = m_abfs_abf.template cal_overlap_matrix(TA, - TB, - tau0, - tau_delta, - index_abfs_s, - index_abfs, - Matrix_Orbs11::Matrix_Order::AB); - } - } - -#pragma omp critical(RPA_LRI_merge) - { - for (auto& aPair: overlap_abfs_abfs_local) - { - auto& aKey = aPair.first; - auto& aSubMap = aPair.second; - for (auto& subPair: aSubMap) - { - auto& key = subPair.first; - auto& value = subPair.second; - overlap_abfs_abfs[aKey][key] = std::move(value); - } - } - for (auto& aPair: overlap_abfs_abf_local) - { - auto& aKey = aPair.first; - auto& aSubMap = aPair.second; - for (auto& subPair: aSubMap) - { - auto& key = subPair.first; - auto& value = subPair.second; - overlap_abfs_abf[aKey][key] = std::move(value); - } - } - } - } - // MPI: {ia0, {ia1, R}} to {ia0, ia1} - const std::array period_Vs_IJ = LRI_CV_Tools::cal_latvec_range(2, ucell, orb_cutoff_); - const std::pair, std::vector>>> list_As_Vs_atoms - = RI::Distribute_Equally::distribute_atoms(this->mpi_comm, atoms, period_Vs, 2, false); - const auto list_A0_pair_R = list_As_Vs_atoms.first; - const auto list_A1_pair_R = list_As_Vs_atoms.second[0]; - std::set atoms00; - std::set atoms01; - for (const auto& I: list_A0_pair_R) - { - atoms00.insert(I); - } - for (const auto& JR: list_A1_pair_R) - { - atoms01.insert(JR.first); - } - std::map>> overlap_abfs_abfs_IJ - = RI_2D_Comm::comm_map2_first(mpi_comm, overlap_abfs_abfs, atoms00, atoms01); - overlap_abfs_abfs.clear(); - std::map>> overlap_abfs_abf_IJ - = RI_2D_Comm::comm_map2_first(mpi_comm, overlap_abfs_abf, atoms00, atoms01); - overlap_abfs_abf.clear(); - - out_abfs_overlap(ucell, overlap_abfs_abfs_IJ, overlap_abfs_abf_IJ, "shrink_sinvS_", index_abfs_s, index_abfs); -} - -template -void RPA_LRI::out_abfs_overlap(const UnitCell& ucell, - std::map>>& overlap_abfs_abfs, - std::map>>& overlap_abfs_abf, - std::string filename, - const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs_s, - const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs) -{ - ModuleBase::TITLE("RPA_LRI", "out_abfs_overlap"); - ModuleBase::timer::start("RPA_LRI", "out_abfs_overlap"); - const double threshold = 1e-15; - const auto format = std::scientific; - int prec = 15; - - int all_mu_s = 0; - int all_mu = 0; - std::vector mu_s_shift(ucell.nat); - std::vector mu_shift(ucell.nat); - for (int I = 0; I != ucell.nat; I++) - { - mu_s_shift[I] = all_mu_s; - mu_shift[I] = all_mu; - all_mu_s += index_abfs_s[ucell.iat2it[I]].count_size; - all_mu += index_abfs[ucell.iat2it[I]].count_size; - } - const int nks_tot = PARAM.inp.nspin == 2 ? (int)p_kv->get_nks() / 2 : p_kv->get_nks(); - std::stringstream ss; - ss << filename << (GlobalV::MY_RANK + 1) << ".txt"; - - std::ofstream ofs; - ofs.open(outdir + ss.str().c_str(), std::ios::out); - - ofs << nks_tot << std::endl; - - // Fourier of ss(R->k), s(R->k) - std::map>>> olp_q_ss; - std::map>>> olp_q_s; - for (int ik = 0; ik != nks_tot; ik++) - { - for (auto& Ip: overlap_abfs_abfs) - { - auto I = Ip.first; - for (auto& JPp: Ip.second) - { - auto J = JPp.first.first; - auto R = JPp.first.second; - auto q = RI_Util::Vector3_to_array3(p_kv->kvec_c[ik]); - RI::Tensor> tmp_olp_ss - = RI::Global_Func::convert>(JPp.second); - RI::Tensor> tmp_olp_s - = RI::Global_Func::convert>(overlap_abfs_abf[I][{J, R}]); - if (olp_q_ss[I][{J, q}].empty()) - { - olp_q_ss[I][{J, q}] = RI::Tensor>({tmp_olp_ss.shape[0], tmp_olp_ss.shape[1]}); - olp_q_s[I][{J, q}] = RI::Tensor>({tmp_olp_s.shape[0], tmp_olp_s.shape[1]}); - } - const double arg = 1 * (p_kv->kvec_c[ik] * (RI_Util::array3_to_Vector3(R) * ucell.latvec)) - * ModuleBase::TWO_PI; // latvec - const std::complex kphase = std::complex(cos(arg), sin(arg)); - - olp_q_ss[I][{J, q}] = olp_q_ss[I][{J, q}] + tmp_olp_ss * kphase; - olp_q_s[I][{J, q}] = olp_q_s[I][{J, q}] + tmp_olp_s * kphase; - } - } - } - // for multi-mpi - for (int I = 0; I != ucell.nat; I++) - { - for (int J = 0; J != ucell.nat; J++) - { - for (int ik = 0; ik != nks_tot; ik++) - { - auto q = RI_Util::Vector3_to_array3(p_kv->kvec_c[ik]); - if (olp_q_ss[I][{J, q}].empty()) - { - auto mu = index_abfs_s[ucell.iat2it[I]].count_size; - auto nu = index_abfs_s[ucell.iat2it[J]].count_size; - olp_q_ss[I][{J, q}] = RI::Tensor>({mu, nu}); - } - if (olp_q_s[I][{J, q}].empty()) - { - auto mu = index_abfs_s[ucell.iat2it[I]].count_size; - auto nu = index_abfs[ucell.iat2it[J]].count_size; - olp_q_s[I][{J, q}] = RI::Tensor>({mu, nu}); - } - for (int ir = 0; ir < olp_q_ss[I][{J, q}].shape[0]; ir++) - { - for (int ic = 0; ic < olp_q_ss[I][{J, q}].shape[1]; ic++) - { - Parallel_Reduce::reduce_all>(olp_q_ss[I][{J, q}](ir, ic)); - } - for (int ic = 0; ic < olp_q_s[I][{J, q}].shape[1]; ic++) - { - Parallel_Reduce::reduce_all>(olp_q_s[I][{J, q}](ir, ic)); - } - } - } - } - } - - // out_ri_tensor("olp_ss.txt", olp_q_ss, 0.); - // Inverse of overlap(q) - inverse_olp(ucell, olp_q_ss, index_abfs_s); - // out_ri_tensor("olp_ss_inv.txt", olp_q_ss, 0.); - // out_ri_tensor("olp_s.txt", olp_q_s, 0.); - for (auto& Ip: overlap_abfs_abf) - { - auto I = Ip.first; - size_t mu_num_s = index_abfs_s[ucell.iat2it[I]].count_size; - size_t mu_num = index_abfs[ucell.iat2it[I]].count_size; - - for (int ik = 0; ik != nks_tot; ik++) - { - std::map>> sinvS; - for (auto& JPp: Ip.second) - { - auto J = JPp.first.first; - auto R = JPp.first.second; - if (sinvS[J].empty()) - { - sinvS[J] = RI::Tensor>( - {overlap_abfs_abfs[I][{J, R}].shape[0], overlap_abfs_abf[I][{J, R}].shape[1]}); - } - } - for (const auto& pair: sinvS) - { - auto J = pair.first; - auto q = RI_Util::Vector3_to_array3(p_kv->kvec_c[ik]); - for (int K = 0; K != ucell.nat; K++) - { - sinvS[J] += olp_q_ss.at(I).at({K, q}) * olp_q_s.at(K).at({J, q}); - } - } - for (auto& iJU: sinvS) - { - auto iJ = iJU.first; - auto& vq_J = iJU.second; - size_t nu_num = index_abfs[ucell.iat2it[iJ]].count_size; - ofs << all_mu_s << " " << all_mu << " " << mu_s_shift[I] + 1 << " " << mu_s_shift[I] + mu_num_s - << " " << mu_shift[iJ] + 1 << " " << mu_shift[iJ] + nu_num << std::endl; - ofs << ik + 1 << " " << p_kv->wk[ik] / 2.0 * PARAM.inp.nspin << std::endl; - for (int i = 0; i != vq_J.data->size(); i++) - { - // ofs << std::setw(25) << std::fixed << std::setprecision(15) << (*vq_J.data)[i].real() - // << std::setw(25) << std::fixed << std::setprecision(15) << (*vq_J.data)[i].imag() << - // std::endl; - // if (fabs((*vq_J.data)[i].real()) > threshold || fabs((*vq_J.data)[i].imag()) > threshold) - ofs << std::showpoint << format << std::setprecision(prec) << (*vq_J.data)[i].real() << " " - << std::showpoint << format << std::setprecision(prec) << (*vq_J.data)[i].imag() << "\n"; - // else - // ofs << std::showpoint << format << std::setprecision(prec) << 0.0 << " " << std::showpoint - // << format << std::setprecision(prec) << 0.0 << "\n"; - } - } - } - } - ofs.close(); - ModuleBase::timer::end("RPA_LRI", "out_abfs_overlap"); -} - -template -void RPA_LRI::inverse_olp(const UnitCell& ucell, - std::map>>>& overlap_abfs_abfs, - const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs_s) -{ - ModuleBase::TITLE("RPA_LRI", "inverse_olp"); - ModuleBase::timer::start("RPA_LRI", "inverse_olp"); - const int nks_tot = PARAM.inp.nspin == 2 ? (int)p_kv->get_nks() / 2 : p_kv->get_nks(); - size_t all_mu_s = 0; - std::vector mu_s_shift(ucell.nat); - for (int I = 0; I != ucell.nat; I++) - { - mu_s_shift[I] = all_mu_s; - all_mu_s += index_abfs_s[ucell.iat2it[I]].count_size; - } - RI::Tensor> olp_all = RI::Tensor>({all_mu_s, all_mu_s}); - for (int ik = 0; ik < nks_tot; ik++) - { - for (auto& Ip: overlap_abfs_abfs) - { - auto I = Ip.first; - size_t mu_s_I = index_abfs_s[ucell.iat2it[I]].count_size; - for (auto& JPp: Ip.second) - { - auto J = JPp.first.first; - auto q = JPp.first.second; - if (q != RI_Util::Vector3_to_array3(p_kv->kvec_c[ik])) - continue; - // std::cout << "IJ: " << I << "," << J << std::endl; - auto mu_s_J = index_abfs_s[ucell.iat2it[J]].count_size; - for (int ir = 0; ir < mu_s_I; ir++) - { - for (int ic = 0; ic < mu_s_J; ic++) - { - olp_all(mu_s_shift[I] + ir, mu_s_shift[J] + ic) = JPp.second(ir, ic); - } - } - } - } - // for multi-mpi - // for (int ir = 0; ir < all_mu_s; ir++) - // { - // for (int ic = 0; ic < all_mu_s; ic++) - // { - // Parallel_Reduce::reduce_all>(olp_all(ir, ic)); - // } - // } - - // check Hermitian - for (int ir = 0; ir < all_mu_s; ir++) - { - for (int ic = ir; ic < all_mu_s; ic++) - { - auto delta = std::abs(olp_all(ir, ic) - std::conj(olp_all(ic, ir))); - if (delta > 1e-10) - { - std::cout << "Warning: olp_all is not Hermitian!" << std::endl; - std::cout << "ik,ir,ic: " << ik << "," << ir << "," << ic << std::endl; - std::cout << "delta(ir, ic): " << delta << std::endl; - } - } - } - // out_pure_ri_tensor("olp_all.txt", olp_all, 0.); - auto olp_inv = LRI_CV_Tools::cal_I(olp_all, - Inverse_Matrix>::Method::syev, - this->info.shrink_LU_inv_thr); - for (int ir = 0; ir < all_mu_s; ir++) - { - for (int ic = ir; ic < all_mu_s; ic++) - { - olp_inv(ic, ir) = std::conj(olp_inv(ir, ic)); - } - } - // out_pure_ri_tensor("olp_inv.txt", olp_inv, 0.); - for (auto& Ip: overlap_abfs_abfs) - { - auto I = Ip.first; - size_t mu_s_I = index_abfs_s[ucell.iat2it[I]].count_size; - for (auto& JPp: Ip.second) - { - auto q = JPp.first.second; - if (q != RI_Util::Vector3_to_array3(p_kv->kvec_c[ik])) - continue; - auto J = JPp.first.first; - auto mu_s_J = index_abfs_s[ucell.iat2it[J]].count_size; - - for (int ir = 0; ir < mu_s_I; ir++) - { - for (int ic = 0; ic < mu_s_J; ic++) - JPp.second(ir, ic) = olp_inv(mu_s_shift[I] + ir, mu_s_shift[J] + ic); - } - } - } - } - ModuleBase::timer::end("RPA_LRI", "inverse_olp"); -} - -// debug function -// template -// void RPA_LRI::out_pure_ri_tensor(const std::string fn, -// RI::Tensor>& olp, -// const double threshold) -// { -// std::ofstream fs; -// auto format = std::scientific; -// int prec = 15; -// fs.open(fn); -// int nr = olp.shape[0]; -// int nc = olp.shape[1]; -// size_t nnz = nr * nc; -// fs << "%%MatrixMarket matrix coordinate complex general" << std::endl; -// fs << "%" << std::endl; - -// fs << nr << " " << nc << " " << nnz << std::endl; - -// for (int j = 0; j < nc; j++) -// { -// for (int i = 0; i < nr; i++) -// { -// auto v = olp(i, j); -// if (fabs(v.real()) > threshold || fabs(v.imag()) > threshold) -// fs << i + 1 << " " << j + 1 << " " << std::showpoint << format << std::setprecision(prec) << v.real() -// << " " << std::showpoint << format << std::setprecision(prec) << v.imag() << "\n"; -// } -// } - -// fs.close(); -// } - -// template -// void RPA_LRI::out_pure_ri_tensor(const std::string fn, RI::Tensor& olp, const double threshold) -// { -// std::ofstream fs; -// auto format = std::scientific; -// int prec = 15; -// fs.open(fn); -// int nr = olp.shape[0]; -// int nc = olp.shape[1]; -// size_t nnz = nr * nc; -// fs << "%%MatrixMarket matrix coordinate complex general" << std::endl; -// fs << "%" << std::endl; - -// fs << nr << " " << nc << " " << nnz << std::endl; - -// for (int j = 0; j < nc; j++) -// { -// for (int i = 0; i < nr; i++) -// { -// auto v = olp(i, j); -// if (fabs(v) > threshold) -// fs << i + 1 << " " << j + 1 << " " << std::showpoint << format << std::setprecision(prec) << v << "\n"; -// } -// } - -// fs.close(); -// } - -// template -// void RPA_LRI::out_ri_tensor(const std::string fn, -// std::map>>>& olp, -// const double threshold) -// { -// std::ofstream fs; -// auto format = std::scientific; -// int prec = 15; -// fs.open(fn); -// for (auto& IJq: olp) -// { -// int I = IJq.first; -// for (auto& Jq: IJq.second) -// { -// int J = Jq.first.first; -// auto q = Jq.first.second; -// auto mat = Jq.second; -// int nr = mat.shape[0]; -// int nc = mat.shape[1]; -// size_t nnz = nr * nc; -// fs << "%%MatrixMarket matrix coordinate complex general" << std::endl; -// fs << I << " " << J << " " << q.at(0) << " " << q.at(1) << " " << q.at(2) << std::endl; -// fs << "%" << std::endl; - -// fs << nr << " " << nc << " " << nnz << std::endl; - -// for (int j = 0; j < nc; j++) -// { -// for (int i = 0; i < nr; i++) -// { -// auto v = mat(i, j); -// if (fabs(v.real()) > threshold || fabs(v.imag()) > threshold) -// fs << i + 1 << " " << j + 1 << " " << std::showpoint << format << std::setprecision(prec) -// << v.real() << " " << std::showpoint << format << std::setprecision(prec) << v.imag() -// << "\n"; -// } -// } -// } -// } - -// fs.close(); -// } - -template -void RPA_LRI::out_eigen_vector(const Parallel_Orbitals& parav, const psi::Psi& psi) -{ - ModuleBase::TITLE("DFT_RPA_interface", "out_eigen_vector"); - - const int nks_tot = PARAM.inp.nspin == 2 ? p_kv->get_nks() / 2 : p_kv->get_nks(); - const int npsin_tmp = PARAM.inp.nspin == 2 ? 2 : 1; - const int nbands = parav.get_wfc_global_nbands(); - const int nbasis = parav.get_wfc_global_nbasis(); - const std::size_t values_per_iw = static_cast(nbands) * npsin_tmp; - -#ifdef __MPI - const MPI_Comm mpi_comm = parav.comm(); - const int mpi_rank = parav.get_coord_row() * parav.get_dim1() + parav.get_coord_col(); - const int mpi_size = parav.get_dim0() * parav.get_dim1(); - const auto check_mpi = [mpi_comm](const int local_error, const std::string& context) { - const int local_failed = local_error == MPI_SUCCESS ? 0 : 1; - int any_failed = 0; - if (MPI_Allreduce(&local_failed, &any_failed, 1, MPI_INT, MPI_MAX, mpi_comm) != MPI_SUCCESS - || any_failed != 0) - { - throw std::runtime_error(context); - } - }; - // 1. Assign a contiguous basis interval to each rank. Rank order also - // gives the order of the corresponding text blocks in the output file. - std::vector output_basis_counts(mpi_size, nbasis / mpi_size); - for (int ip = 0; ip < nbasis % mpi_size; ++ip) - { - ++output_basis_counts[ip]; - } - std::vector output_basis_offsets(mpi_size + 1, 0); - std::vector output_basis_owner(nbasis); - for (int ip = 0; ip < mpi_size; ++ip) - { - output_basis_offsets[ip + 1] - = output_basis_offsets[ip] + output_basis_counts[ip]; - std::fill(output_basis_owner.begin() + output_basis_offsets[ip], - output_basis_owner.begin() + output_basis_offsets[ip + 1], - ip); - } - const int local_nw = output_basis_counts[mpi_rank]; -#else - const int mpi_rank = 0; - const int local_nw = nbasis; -#endif - const std::size_t local_size = static_cast(local_nw) * values_per_iw; - std::vector> local_wfc(local_size); -#ifdef __MPI - struct WfcPackIndex - { - int spin; - int band; - int basis; - }; - - // 2. Build the 2D block-cyclic to contiguous-basis redistribution map. - // ncol_bands is the number of wavefunction bands stored on this rank. - const int local_band_count = parav.ncol_bands; - const unsigned long long send_size_wide - = static_cast(local_band_count) * psi.get_nbasis() * npsin_tmp; - const unsigned long long recv_size_wide - = static_cast(local_nw) * values_per_iw; - const unsigned long long max_alltoallv_count = std::numeric_limits::max(); - check_mpi(send_size_wide <= max_alltoallv_count && recv_size_wide <= max_alltoallv_count - ? MPI_SUCCESS - : MPI_ERR_COUNT, - "RPA eigenvector buffer exceeds MPI_Alltoallv count range."); - - // Group every locally owned (band, basis, spin) coefficient by destination rank. - std::vector send_counts(mpi_size, 0); - for (int ib = 0; ib < nbands; ++ib) - { - if (parav.global2local_col(ib) < 0) - { - continue; - } - for (int ir = 0; ir < psi.get_nbasis(); ++ir) - { - send_counts[output_basis_owner[parav.local2global_row(ir)]] += npsin_tmp; - } - } - std::vector send_displacements(mpi_size, 0); - std::vector recv_counts(mpi_size); - std::vector recv_displacements(mpi_size, 0); - for (int ip = 1; ip < mpi_size; ++ip) - { - send_displacements[ip] = send_displacements[ip - 1] + send_counts[ip - 1]; - } - check_mpi(MPI_Alltoall(send_counts.data(), 1, MPI_INT, recv_counts.data(), 1, MPI_INT, mpi_comm), - "Failed to exchange RPA eigenvector redistribution counts."); - for (int ip = 1; ip < mpi_size; ++ip) - { - recv_displacements[ip] = recv_displacements[ip - 1] + recv_counts[ip - 1]; - } - const int send_size = static_cast(send_size_wide); - const int recv_size = static_cast(recv_size_wide); - check_mpi(recv_displacements.back() + recv_counts.back() == recv_size - ? MPI_SUCCESS - : MPI_ERR_COUNT, - "RPA eigenvector Alltoallv redistribution map is inconsistent."); - // Store source-local coordinates and destination-local flat indices in the - // same packing order. The map is k-independent, so exchange target indices once. - std::vector pack_indices(send_size); - std::vector send_targets(send_size); - std::vector recv_targets(recv_size); - std::vector send_positions = send_displacements; - for (int ib = 0; ib < nbands; ++ib) - { - const int ib_local = parav.global2local_col(ib); - if (ib_local < 0) - { - continue; - } - for (int ir = 0; ir < psi.get_nbasis(); ++ir) - { - const int iw = parav.local2global_row(ir); - const int destination = output_basis_owner[iw]; - for (int is = 0; is < npsin_tmp; ++is) - { - const int position = send_positions[destination]++; - pack_indices[position] = {is, ib_local, ir}; - send_targets[position] - = static_cast(iw - output_basis_offsets[destination]) - * values_per_iw - + ib * npsin_tmp + is; - } - } - } - std::complex dummy(0.0, 0.0); - unsigned long long dummy_target = 0; - check_mpi(MPI_Alltoallv(send_size > 0 ? send_targets.data() : &dummy_target, - send_counts.data(), - send_displacements.data(), - MPI_UNSIGNED_LONG_LONG, - recv_size > 0 ? recv_targets.data() : &dummy_target, - recv_counts.data(), - recv_displacements.data(), - MPI_UNSIGNED_LONG_LONG, - mpi_comm), - "Failed to exchange RPA eigenvector destination indices."); - std::vector> send_wfc(send_size); - std::vector> recv_wfc(recv_size); -#endif - std::string output_buffer; - constexpr std::size_t output_line_bytes = 61; // required width: 2 * 30 columns plus '\n' - output_buffer.reserve(local_size * output_line_bytes + 32); // 32 bytes for the k-point index and newline - - // 3. Open the unified output file once for all k points. - const std::string filename = outdir + "KS_eigenvector.txt"; -#ifdef __MPI - MPI_File file = MPI_FILE_NULL; - unsigned long long total_file_bytes = 0; // start offset of the current k point - const unsigned long long max_mpi_offset - = static_cast(std::numeric_limits::max()); - const char dummy_buffer = '\0'; - check_mpi(MPI_File_open(mpi_comm, - filename.c_str(), - MPI_MODE_CREATE | MPI_MODE_WRONLY, - MPI_INFO_NULL, - &file), - "Failed to open " + filename + "."); - check_mpi(MPI_File_set_size(file, 0), "Failed to truncate " + filename + "."); -#else - std::ofstream ofs(filename.c_str(), std::ios::out); -#endif - // 4. Process k points in file order; all ranks participate in each iteration. - for (int ik = 0; ik < nks_tot; ik++) - { -#ifdef __MPI - // 4.1 Pack and redistribute all bands and spins for this k point. - for (int index = 0; index < send_size; ++index) - { - const WfcPackIndex& source = pack_indices[index]; - send_wfc[index] = psi(ik + nks_tot * source.spin, source.band, source.basis); - } - check_mpi(MPI_Alltoallv(send_size > 0 ? send_wfc.data() : &dummy, - send_counts.data(), - send_displacements.data(), - MPI_DOUBLE_COMPLEX, - recv_size > 0 ? recv_wfc.data() : &dummy, - recv_counts.data(), - recv_displacements.data(), - MPI_DOUBLE_COMPLEX, - mpi_comm), - "Failed to redistribute RPA eigenvectors with MPI_Alltoallv."); - for (int index = 0; index < recv_size; ++index) - { - local_wfc[static_cast(recv_targets[index])] = recv_wfc[index]; - } -#else - for (int is = 0; is < npsin_tmp; is++) - { - for (int ib = 0; ib < nbands; ++ib) - { - for (int iw = 0; iw < psi.get_nbasis(); ++iw) - { - local_wfc[iw * values_per_iw + ib * npsin_tmp + is] - = psi(ik + nks_tot * is, ib, iw); - } - } - } -#endif - // 4.2 Format this rank's contiguous basis interval in basis-band-spin order. - output_buffer.clear(); - if (mpi_rank == 0) - { - output_buffer += std::to_string(ik + 1); - output_buffer += '\n'; - } - char line_buffer[128]; - for (int iw = 0; iw < local_nw; ++iw) - { - for (int ib = 0; ib < nbands; ++ib) - { - for (int is = 0; is < npsin_tmp; is++) - { - const std::complex& value - = local_wfc[static_cast(iw) * values_per_iw + ib * npsin_tmp + is]; - const int line_length = std::snprintf(line_buffer, - sizeof(line_buffer), - "%30.15f%30.15f\n", - value.real(), - value.imag()); - if (line_length != static_cast(output_line_bytes)) - { - throw std::runtime_error("Failed to format an RPA eigenvector value."); - } - output_buffer.append(line_buffer, static_cast(line_length)); - } - } - } - -#ifdef __MPI - // 4.3 Fixed-width value lines make each rank's file offset deterministic; - // only the k-point header length varies. - const unsigned long long local_bytes - = static_cast(output_buffer.size()); - const unsigned long long header_bytes = std::to_string(ik + 1).size() + 1; - const unsigned long long bytes_per_basis = values_per_iw * output_line_bytes; - if (bytes_per_basis > (max_mpi_offset - header_bytes) / std::max(nbasis, 1) - || header_bytes + static_cast(nbasis) * bytes_per_basis - > max_mpi_offset - total_file_bytes) - { - throw std::runtime_error("RPA eigenvector file layout exceeds MPI_Offset range."); - } - const unsigned long long kpoint_bytes - = header_bytes + static_cast(nbasis) * bytes_per_basis; - const unsigned long long rank_offset_bytes - = mpi_rank == 0 - ? 0 - : header_bytes - + static_cast(output_basis_offsets[mpi_rank]) - * bytes_per_basis; - const unsigned long long file_offset_bytes = total_file_bytes + rank_offset_bytes; - // Rank 0 has the largest basis slice and additionally owns the k-point header. - const unsigned long long max_local_bytes - = header_bytes - + static_cast(output_basis_counts[0]) * bytes_per_basis; - const unsigned long long max_write_count - = static_cast(std::numeric_limits::max()); - // MPI_File_write_at_all is collective, so every rank executes the same - // number of chunks; ranks without data in a chunk pass a zero count. - for (unsigned long long buffer_offset = 0; - buffer_offset < max_local_bytes; - buffer_offset += max_write_count) - { - const unsigned long long remaining - = local_bytes > buffer_offset ? local_bytes - buffer_offset : 0; - const int write_count - = static_cast(std::min(remaining, max_write_count)); - const char* write_buffer - = write_count > 0 - ? output_buffer.data() + static_cast(buffer_offset) - : &dummy_buffer; - const unsigned long long write_offset - = write_count > 0 ? file_offset_bytes + buffer_offset : file_offset_bytes; - check_mpi(MPI_File_write_at_all(file, - static_cast(write_offset), - write_buffer, - write_count, - MPI_CHAR, - MPI_STATUS_IGNORE), - "Failed to write " + filename + "."); - } - total_file_bytes += kpoint_bytes; -#else - ofs << output_buffer; -#endif - } // ik -#ifdef __MPI - check_mpi(MPI_File_sync(file), "Failed to sync " + filename + "."); - check_mpi(MPI_File_close(&file), "Failed to close " + filename + "."); -#endif -} - -template -void RPA_LRI::out_struc(const UnitCell& ucell) -{ - if (GlobalV::MY_RANK != 0) - { - return; - } - ModuleBase::TITLE("DFT_RPA_interface", "out_struc"); - const int nks_tot = PARAM.inp.nspin == 2 ? (int)p_kv->get_nks() / 2 : p_kv->get_nks(); - const ModuleBase::Matrix3 lat = ucell.latvec * ucell.lat0; // in unit of Bohr - const ModuleBase::Matrix3 G_RPA = ucell.G * (ModuleBase::TWO_PI / ucell.lat0); // in unit of 1/Bohr - std::ofstream ofs; - ofs.open(outdir + "stru_out.txt", std::ios::out); - ofs << std::fixed << std::setprecision(9); - ofs << lat.e11 << std::setw(15) << lat.e12 << std::setw(15) << lat.e13 << std::endl; - ofs << lat.e21 << std::setw(15) << lat.e22 << std::setw(15) << lat.e23 << std::endl; - ofs << lat.e31 << std::setw(15) << lat.e32 << std::setw(15) << lat.e33 << std::endl; - - ofs << G_RPA.e11 << std::setw(15) << G_RPA.e12 << std::setw(15) << G_RPA.e13 << std::endl; - ofs << G_RPA.e21 << std::setw(15) << G_RPA.e22 << std::setw(15) << G_RPA.e23 << std::endl; - ofs << G_RPA.e31 << std::setw(15) << G_RPA.e32 << std::setw(15) << G_RPA.e33 << std::endl; - - ofs << ucell.nat << std::endl; - for (int it = 0; it < ucell.ntype; it++) - { - for (int ia = 0; ia < ucell.atoms[it].na; ia++) - { - const ModuleBase::Vector3 position = ucell.atoms[it].tau[ia] * ucell.lat0; // in unit of Bohr - ofs << std::setw(15) << position.x << std::setw(15) << position.y - << std::setw(15) << position.z << std::setw(15) << it + 1 << std::endl; - } - } - - ofs << p_kv->nmp[0] << std::setw(6) << p_kv->nmp[1] << std::setw(6) << p_kv->nmp[2] << std::setw(6) << std::endl; - - for (int ik = 0; ik != nks_tot; ik++) - { - const ModuleBase::Vector3 kpoint = - p_kv->kvec_c[ik] * (ModuleBase::TWO_PI / ucell.lat0); // in unit of 1/Bohr - ofs << std::setw(15) << kpoint.x << std::setw(15) << kpoint.y - << std::setw(15) << kpoint.z << std::endl; - } - // added for BZ to IBZ (actually LibRPA interface only support BZ by 2025/03/30) - if (PARAM.inp.symmetry == "-1") - { - for (int ik = 0; ik != nks_tot; ++ik) - { - ofs << (ik + 1) << std::endl; - } - } - ofs.close(); - return; -} - -template -void RPA_LRI::out_bands(const elecstate::ElecState* pelec) -{ - ModuleBase::TITLE("DFT_RPA_interface", "out_bands"); - if (GlobalV::MY_RANK != 0) - { - return; - } - const int nks_tot = PARAM.inp.nspin == 2 ? (int)p_kv->get_nks() / 2 : p_kv->get_nks(); - const int nspin_tmp = PARAM.inp.nspin == 2 ? 2 : 1; - std::ofstream ofs; - ofs.open(outdir + "band_out.txt", std::ios::out); - ofs << std::fixed << std::setprecision(15); - ofs << nks_tot << std::endl; - ofs << nspin_tmp << std::endl; - ofs << PARAM.inp.nbands << std::endl; - ofs << PARAM.globalv.nlocal << std::endl; - ofs << (pelec->eferm.ef / 2.0) << std::endl; - - for (int ik = 0; ik != nks_tot; ik++) - { - for (int is = 0; is != nspin_tmp; is++) - { - ofs << std::setw(6) << ik + 1 << std::setw(6) << is + 1 << std::endl; - for (int ib = 0; ib != PARAM.inp.nbands; ib++) - { - ofs << std::setw(5) << ib + 1 << " " << std::setw(8) << pelec->wg(ik + is * nks_tot, ib) * nks_tot - << std::setw(25) << pelec->ekb(ik + is * nks_tot, ib) / 2.0 - << std::setw(25) - << pelec->ekb(ik + is * nks_tot, ib) * ModuleBase::Ry_to_eV << std::endl; - } - } - } - ofs.close(); - return; -} - -template -void RPA_LRI::out_Cs(const UnitCell& ucell, std::map>>& Cs_in, std::string filename) -{ - ModuleBase::TITLE("DFT_RPA_interface", "out_Cs"); - ModuleBase::timer::start("RPA_LRI", "out_Cs"); - - std::stringstream ss; - ss << filename << (GlobalV::MY_RANK + 1) << ".txt"; - std::ofstream ofs; - ofs.open(outdir + ss.str().c_str(), std::ios::out); - ofs << ucell.nat << " " << 0 << std::endl; - ofs << std::fixed << std::setprecision(15); - for (auto& Ip: Cs_in) - { - size_t I = Ip.first; - size_t i_num = ucell.atoms[ucell.iat2it[I]].nw; - for (auto& JPp: Ip.second) - { - size_t J = JPp.first.first; - auto R = JPp.first.second; - auto& tmp_Cs = JPp.second; - size_t j_num = ucell.atoms[ucell.iat2it[J]].nw; - - ofs << I + 1 << " " << J + 1 << " " << R[0] << " " << R[1] << " " << R[2] << " " << i_num - << std::endl; - ofs << j_num << " " << tmp_Cs.shape[0] << std::endl; - for (int i = 0; i != i_num; i++) - { - for (int j = 0; j != j_num; j++) - { - for (int mu = 0; mu != tmp_Cs.shape[0]; mu++) - { - ofs << std::setw(30) << tmp_Cs(mu, i, j) << std::endl; - } - } - } - } - } - ofs.close(); - ModuleBase::timer::end("RPA_LRI", "out_Cs"); - return; -} - -template -void RPA_LRI::out_coulomb_k(const UnitCell& ucell, - std::map>>& Vs, - std::string filename, - Exx_LRI* exx_lri) -{ - ModuleBase::TITLE("DFT_RPA_interface", "out_coulomb_k"); - ModuleBase::timer::start("RPA_LRI", "out_coulomb_k"); - - int all_mu = 0; - std::vector mu_shift(ucell.nat); - if (exx_lri->exx_objs.empty()) - { - throw std::invalid_argument(std::string(__FILE__) + " line " + std::to_string(__LINE__)); - } - const auto basis_method = exx_lri->exx_objs.count(Conv_Coulomb_Pot_K::Coulomb_Method::Center2) - ? Conv_Coulomb_Pot_K::Coulomb_Method::Center2 - : exx_lri->exx_objs.begin()->first; - for (int I = 0; I != ucell.nat; I++) - { - mu_shift[I] = all_mu; - all_mu += exx_lri->exx_objs.at(basis_method).cv.get_index_abfs_size(ucell.iat2it[I]); - } - const int nks_tot = PARAM.inp.nspin == 2 ? (int)p_kv->get_nks() / 2 : p_kv->get_nks(); - std::stringstream ss; - ss << filename << (GlobalV::MY_RANK + 1) << ".txt"; - - std::ofstream ofs; - ofs.open(outdir + ss.str().c_str(), std::ios::out); - - ofs << nks_tot << std::endl; - ofs << std::fixed << std::setprecision(15); - for (auto& Ip: Vs) - { - auto I = Ip.first; - size_t mu_num = exx_lri->exx_objs.at(basis_method).cv.get_index_abfs_size(ucell.iat2it[I]); - - for (int ik = 0; ik != nks_tot; ik++) - { - std::map>> Vq_k_IJ; - for (auto& JPp: Ip.second) - { - auto J = JPp.first.first; - - auto R = JPp.first.second; - if (J < I) - { - continue; - } - RI::Tensor> tmp_VR = RI::Global_Func::convert>(JPp.second); - const double arg = 1 * (p_kv->kvec_c[ik] * (RI_Util::array3_to_Vector3(R) * ucell.latvec)) - * ModuleBase::TWO_PI; // latvec - const std::complex kphase = std::complex(cos(arg), sin(arg)); - if (Vq_k_IJ[J].empty()) - { - Vq_k_IJ[J] = RI::Tensor>({tmp_VR.shape[0], tmp_VR.shape[1]}); - } - Vq_k_IJ[J] = Vq_k_IJ[J] + tmp_VR * kphase; - } - for (auto& vq_Jp: Vq_k_IJ) - { - auto iJ = vq_Jp.first; - auto& vq_J = vq_Jp.second; - size_t nu_num = exx_lri->exx_objs.at(basis_method).cv.get_index_abfs_size(ucell.iat2it[iJ]); - ofs << all_mu << " " << mu_shift[I] + 1 << " " << mu_shift[I] + mu_num << " " << mu_shift[iJ] + 1 - << " " << mu_shift[iJ] + nu_num << std::endl; - ofs << ik + 1 << " " << p_kv->wk[ik] / 2.0 * PARAM.inp.nspin << std::endl; - for (int i = 0; i != vq_J.data->size(); i++) - { - ofs << std::setw(25) << (*vq_J.data)[i].real() - << std::setw(25) << (*vq_J.data)[i].imag() << std::endl; - } - } - } - } - ofs.close(); - ModuleBase::timer::end("RPA_LRI", "out_coulomb_k"); -} - -template -void RPA_LRI::out_velocity(const UnitCell &ucell, - const Grid_Driver &gd, - const TwoCenterBundle &two_center_bundle, - const Parallel_Orbitals ¶v,/*nbasis×nbasis*/ - const psi::Psi &psi, - const elecstate::ElecState* pelec) -{ - ModuleBase::TITLE("DFT_RPA_interface", "out_velocity"); - ModuleBase::timer::start("RPA_LRI", "out_velocity"); - - Parallel_2D parac;/*nbasis×nbands*/ - LR_Util::setup_2d_division(parac, parav.get_block_size(), PARAM.globalv.nlocal, PARAM.inp.nbands - #ifdef __MPI - , parav.blacs_ctxt - #endif - ); - - const int nk = PARAM.inp.nspin == 2 ? p_kv->get_nks() / 2 : p_kv->get_nks(); - const int nspin_tmp = PARAM.inp.nspin == 2 ? 2 : 1; - const int nbands = parav.get_wfc_global_nbands(); - const int nbasis = parav.get_wfc_global_nbasis(); - - // nocc and nvirt dosen't matter, their sum is actually used - std::vector nocc(2, nbands); - std::vector nvirt(2, 0); - - std::vector> velocity_mo = LR_Util::cal_velocity_mo(ucell, gd, two_center_bundle, - parav, parac, *this->p_kv, psi, nk, nspin_tmp, PARAM.globalv.nlocal, nocc, nvirt); - if (GlobalV::MY_RANK == 0){ - // for librpa readable - LR_Util::output_spectrum_mo_librpa(velocity_mo, outdir + "velocity_matrix.txt", - nk, nspin_tmp, nbands, nbasis, nbands, *this->p_kv); - // for human readable - // LR_Util::output_spectrum_mo(velocity_mo, PARAM.globalv.global_out_dir + "velocity_matrix_h.txt", - // pelec->ekb.c, nk, nspin_tmp, PARAM.inp.nbands, *this->p_kv); - } - ModuleBase::timer::end("RPA_LRI", "out_velocity"); -} - -// template -// void RPA_LRI::init(const MPI_Comm &mpi_comm_in) -// { -// if(this->info == this->exx.info) -// { -// this->lcaos = this->exx.lcaos; -// this->abfs = this->exx.abfs; -// this->abfs_ccp = this->exx.abfs_ccp; - -// exx_lri_rpa.cv = std::move(this->exx.cv); -// } -// else -// { -// this->lcaos = ... -// this->abfs = ... -// this->abfs_ccp = ... - -// exx_lri_rpa.cv.set_orbitals( -// this->lcaos, this->abfs, this->abfs_ccp, -// this->info.kmesh_times, this->info.ccp_rmesh_times ); -// } - -// } - - - -// template -// void RPA_LRI::cal_rpa_ions() -// { -// // this->rpa_lri.set_parallel(this->mpi_comm, atoms_pos, latvec, period); - -// if(this->info == this->exx.info) -// exx_lri_rpa.cv.Vws = std::move(this->exx.cv.Vws); - -// const std::array period_Vs = -// LRI_CV_Tools::cal_latvec_range(1+this->info.ccp_rmesh_times); const -// std::pair, -// std::vector>>>> list_As_Vs -// = RI::Distribute_Equally::distribute_atoms(this->mpi_comm, atoms, period_Vs, -// 2, false); - -// std::map>> -// Vs = exx_lri_rpa.cv.cal_Vs( -// list_As_Vs.first, list_As_Vs.second[0], -// {{"writable_Vws",true}}); - -// // Vs[iat0][{iat1,cell1}] distributed across processes by (iat0,iat1); each process holds all cell1 -// Vqs = FFT(Vs); -// out_Vs(Vqs); - -// if(this->info == this->exx.info) -// exx_lri_rpa.cv.Cws = std::move(this->exx.cv.Cws); - -// const std::array period_Cs = -// LRI_CV_Tools::cal_latvec_range(2); const std::pair, -// std::vector>>>> list_As_Cs -// = RI::Distribute_Equally::distribute_atoms_periods(this->mpi_comm, atoms, -// period_Cs, 2, false); - -// std::pair>>, -// std::array>>,3>> Cs_dCs = -// exx_lri_rpa.cv.cal_Cs_dCs( list_As_Cs.first, list_As_Cs.second[0], -// {{"cal_dC",false}, -// {"writable_Cws",true}, {"writable_dCws",true}, -// {"writable_Vws",false}, -// {"writable_dVws",false}}); std::map>> &Cs -// = std::get<0>(Cs_dCs); - -// out_Cs(Cs); - -// // rpa_lri.set_Cs(Cs); -// } - -#endif diff --git a/source/source_lcao/module_ri/rpa_lri_band_io.cpp b/source/source_lcao/module_ri/rpa_lri_band_io.cpp new file mode 100644 index 00000000000..a4e651fc3c1 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_band_io.cpp @@ -0,0 +1,67 @@ +#include "rpa_lri_band_io.h" + +#include "source_base/constants.h" + +#include +#include +#include +#include + +namespace RpaLriDetail +{ +void write_band_output(std::ostream& output, const BandOutputData& data) +{ + if (data.nk <= 0 || data.nspin <= 0 || data.nbands <= 0 || data.nbasis <= 0) + { + throw std::runtime_error("LibRPA band output has invalid dimensions."); + } + const unsigned long long expected = static_cast(data.nk) + * static_cast(data.nspin) + * static_cast(data.nbands); + if (expected != static_cast(data.samples.size())) + { + throw std::runtime_error("LibRPA band output has an inconsistent sample count."); + } + if (!std::isfinite(data.fermi_ry)) + { + throw std::runtime_error("LibRPA band output has a non-finite Fermi energy."); + } + for (const BandSample& sample: data.samples) + { + if (!std::isfinite(sample.occupation) || !std::isfinite(sample.energy_ry)) + { + throw std::runtime_error("LibRPA band output has a non-finite sample."); + } + } + if (!output.good()) + { + throw std::runtime_error("LibRPA band output stream is not writable."); + } + + output << std::fixed << std::setprecision(15); + output << data.nk << '\n'; + output << data.nspin << '\n'; + output << data.nbands << '\n'; + output << data.nbasis << '\n'; + output << data.fermi_ry / 2.0 << '\n'; + + std::size_t sample_index = 0; + for (int ik = 0; ik < data.nk; ++ik) + { + for (int is = 0; is < data.nspin; ++is) + { + output << std::setw(6) << ik + 1 << std::setw(6) << is + 1 << '\n'; + for (int ib = 0; ib < data.nbands; ++ib, ++sample_index) + { + const BandSample& sample = data.samples[sample_index]; + output << std::setw(5) << ib + 1 << " " << std::setw(8) << sample.occupation << std::setw(25) + << sample.energy_ry / 2.0 << std::setw(25) << sample.energy_ry * ModuleBase::Ry_to_eV << '\n'; + } + } + } + if (!output.good()) + { + throw std::runtime_error("Failed to write LibRPA band output."); + } +} +} // namespace RpaLriDetail diff --git a/source/source_lcao/module_ri/rpa_lri_band_io.h b/source/source_lcao/module_ri/rpa_lri_band_io.h new file mode 100644 index 00000000000..13a4ff56804 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_band_io.h @@ -0,0 +1,28 @@ +#ifndef RPA_LRI_BAND_IO_H +#define RPA_LRI_BAND_IO_H + +#include +#include + +namespace RpaLriDetail +{ +struct BandSample +{ + double occupation = 0.0; + double energy_ry = 0.0; +}; + +struct BandOutputData +{ + int nk = 0; + int nspin = 0; + int nbands = 0; + int nbasis = 0; + double fermi_ry = 0.0; + std::vector samples; +}; + +void write_band_output(std::ostream& output, const BandOutputData& data); +} + +#endif diff --git a/source/source_lcao/module_ri/rpa_lri_basis.cpp b/source/source_lcao/module_ri/rpa_lri_basis.cpp new file mode 100644 index 00000000000..ff95c653048 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_basis.cpp @@ -0,0 +1,265 @@ +#include "exx_lri.h" +#include "rpa_lri.h" +#include "rpa_lri_detail.h" +#include "source_base/global_function.h" +#include "source_basis/module_ao/elem_basis_idx_orb.h" +#include "source_estate/elecstate_lcao.h" +#include "source_io/module_parameter/input_parameter.h" +#include "source_lcao/module_ri/module_exx_symmetry/symm_rotation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace RpaLriDetail +{ +std::vector> collect_abfs_l_nchi( + const std::vector>>& abfs) +{ + std::vector> abfs_l_nchi; + abfs_l_nchi.reserve(abfs.size()); + for (const auto& abfs_type: abfs) + { + std::vector shell_counts; + shell_counts.reserve(abfs_type.size()); + for (const auto& abfs_l: abfs_type) + { + shell_counts.push_back(static_cast(abfs_l.size())); + } + abfs_l_nchi.push_back(std::move(shell_counts)); + } + return abfs_l_nchi; +} + +std::vector> collect_wfc_l_nchi(const UnitCell& ucell) +{ + std::vector> wfc_l_nchi; + wfc_l_nchi.reserve(static_cast(ucell.ntype)); + for (int itype = 0; itype < ucell.ntype; ++itype) + { + const auto& atom = ucell.atoms[itype]; + if (atom.nwl < 0 || atom.l_nchi.size() < static_cast(atom.nwl + 1)) + { + throw std::runtime_error("LibRPA v1 basis output found inconsistent AO shell counts."); + } + std::vector shell_counts; + shell_counts.reserve(static_cast(atom.nwl + 1)); + for (int l = 0; l <= atom.nwl; ++l) + { + if (atom.l_nchi[static_cast(l)] < 0) + { + throw std::runtime_error("LibRPA v1 basis output found negative AO shell count."); + } + shell_counts.push_back(atom.l_nchi[static_cast(l)]); + } + wfc_l_nchi.push_back(std::move(shell_counts)); + } + return wfc_l_nchi; +} + +int basis_size_from_shell_counts(const std::vector& shell_counts) +{ + int basis_size = 0; + for (std::size_t l = 0; l < shell_counts.size(); ++l) + { + const int count = shell_counts[l]; + if (count < 0) + { + throw std::runtime_error("LibRPA v1 basis output found negative shell count."); + } + const int shell_size = static_cast(2 * l + 1); + if (count > 0 && shell_size > std::numeric_limits::max() / count) + { + throw std::runtime_error("Integer overflow while summing LibRPA v1 shell sizes."); + } + const int increment = count * shell_size; + if (increment > std::numeric_limits::max() - basis_size) + { + throw std::runtime_error("Integer overflow while summing LibRPA v1 shell sizes."); + } + basis_size += increment; + } + return basis_size; +} + +void write_librpa_split_basis_file(const UnitCell& ucell, + const std::vector& type_sizes, + const std::vector>& type_shell_counts, + const std::string& filename) +{ + if (type_sizes.size() != static_cast(ucell.ntype) + || type_shell_counts.size() != static_cast(ucell.ntype)) + { + throw std::runtime_error("LibRPA v1 basis output found inconsistent atom-type count."); + } + + int total_basis = 0; + for (int itype = 0; itype < ucell.ntype; ++itype) + { + const int type_size = type_sizes[static_cast(itype)]; + if (type_size <= 0) + { + throw std::runtime_error("LibRPA v1 basis output found a non-positive per-type basis size."); + } + const int shell_size = basis_size_from_shell_counts(type_shell_counts[static_cast(itype)]); + if (type_size != shell_size) + { + throw std::runtime_error("LibRPA v1 basis output found inconsistent shell layout size."); + } + if (ucell.atoms[itype].na <= 0 || type_size > std::numeric_limits::max() / ucell.atoms[itype].na) + { + throw std::runtime_error("Integer overflow while summing LibRPA v1 basis sizes."); + } + const int increment = type_size * ucell.atoms[itype].na; + if (increment > std::numeric_limits::max() - total_basis) + { + throw std::runtime_error("Integer overflow while summing LibRPA v1 basis sizes."); + } + total_basis += increment; + } + + std::ofstream ofs(filename, std::ios::out | std::ios::trunc); + if (!ofs.good()) + { + throw std::runtime_error("Failed to open " + filename); + } + ofs << std::setw(10) << ucell.ntype << std::setw(10) << total_basis << " abacus" << std::endl; + for (int itype = 0; itype < ucell.ntype; ++itype) + { + ofs << std::setw(10) << itype + 1 << std::setw(10) << type_sizes[static_cast(itype)] << std::endl; + } + for (int itype = 0; itype < ucell.ntype; ++itype) + { + const auto& shell_counts = type_shell_counts[static_cast(itype)]; + int nshell = 0; + for (const int count: shell_counts) + { + nshell += count; + } + ofs << std::setw(10) << itype + 1 << std::setw(10) << nshell << std::endl; + for (std::size_t l = 0; l < shell_counts.size(); ++l) + { + for (int iradial = 0; iradial < shell_counts[l]; ++iradial) + { + ofs << std::setw(10) << l << std::endl; + } + } + } +} +} // namespace RpaLriDetail + +template +void RPA_LRI::out_librpa_basis_v1(const UnitCell& ucell, + Exx_LRI* exx_lri, + const std::string& aux_filename, + const std::string& legacy_filename, + const bool write_wfc_basis) +try +{ + if (this->runtime.rank != 0) + { + return; + } + ModuleBase::TITLE("DFT_RPA_interface", "out_librpa_basis_v1"); + + const auto basis_method = this->select_coulomb_basis_method_(exx_lri); + std::vector type_naux(static_cast(ucell.ntype), 0); + std::vector type_nw(static_cast(ucell.ntype), 0); + int total_wfc = 0; + int total_aux = 0; + for (int it = 0; it != ucell.ntype; ++it) + { + type_naux[static_cast(it)] = exx_lri->exx_objs.at(basis_method).cv.get_index_abfs_size(it); + if (type_naux[static_cast(it)] <= 0) + { + throw std::runtime_error("LibRPA v1 basis output found a non-positive per-type auxiliary size."); + } + type_nw[static_cast(it)] = ucell.atoms[it].nw; + if (type_nw[static_cast(it)] <= 0) + { + throw std::runtime_error("LibRPA v1 basis output found a non-positive per-type wave-function size."); + } + total_wfc += type_nw[static_cast(it)] * ucell.atoms[it].na; + total_aux += type_naux[static_cast(it)] * ucell.atoms[it].na; + } + + const auto wfc_l_nchi = RpaLriDetail::collect_wfc_l_nchi(ucell); + const auto aux_l_nchi = RpaLriDetail::collect_abfs_l_nchi(exx_lri->abfs); + const std::string wfc_filename = this->runtime.input.rpa_outdir + "wfc_basis.txt"; + const std::string aux_output_filename = this->runtime.input.rpa_outdir + aux_filename; + const std::string legacy_output_filename = this->runtime.input.rpa_outdir + legacy_filename; + if (write_wfc_basis) + { + RpaLriDetail::write_librpa_split_basis_file(ucell, type_nw, wfc_l_nchi, wfc_filename); + } + RpaLriDetail::write_librpa_split_basis_file(ucell, type_naux, aux_l_nchi, aux_output_filename); + + if (legacy_filename.empty()) + { + return; + } + std::ofstream ofs(legacy_output_filename, std::ios::out | std::ios::trunc); + if (!ofs.good()) + { + throw std::runtime_error("Failed to open " + legacy_output_filename); + } + ofs << std::setw(10) << ucell.ntype << std::setw(10) << total_wfc << std::setw(10) << total_aux << " fallback" + << std::endl; + for (int it = 0; it != ucell.ntype; ++it) + { + ofs << std::setw(10) << it + 1 << std::setw(10) << type_nw[static_cast(it)] << std::setw(10) + << type_naux[static_cast(it)] << std::endl; + } + const auto write_legacy_shells = [&ofs](const std::vector>& shell_counts_by_type) { + for (std::size_t itype = 0; itype < shell_counts_by_type.size(); ++itype) + { + const auto& shell_counts = shell_counts_by_type[itype]; + int nshell = 0; + for (const int count: shell_counts) + { + nshell += count; + } + ofs << std::setw(10) << itype + 1 << std::setw(10) << nshell << std::endl; + for (std::size_t l = 0; l < shell_counts.size(); ++l) + { + for (int iradial = 0; iradial < shell_counts[l]; ++iradial) + { + ofs << std::setw(10) << l << std::endl; + } + } + } + }; + write_legacy_shells(wfc_l_nchi); + write_legacy_shells(aux_l_nchi); +} +catch (const std::exception& error) +{ + RpaLriDetail::abort_output(this->mpi_comm, error.what()); +} +catch (...) +{ + RpaLriDetail::abort_output(this->mpi_comm, "Unknown exception in out_librpa_basis_v1"); +} + +template void RPA_LRI::out_librpa_basis_v1(const UnitCell& ucell, + Exx_LRI* exx_lri, + const std::string& aux_filename, + const std::string& legacy_filename, + bool write_wfc_basis); +template void RPA_LRI, double>::out_librpa_basis_v1(const UnitCell& ucell, + Exx_LRI* exx_lri, + const std::string& aux_filename, + const std::string& legacy_filename, + bool write_wfc_basis); diff --git a/source/source_lcao/module_ri/rpa_lri_bz.cpp b/source/source_lcao/module_ri/rpa_lri_bz.cpp new file mode 100644 index 00000000000..9b255e19557 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_bz.cpp @@ -0,0 +1,53 @@ +#include "rpa_lri.h" +#include "rpa_lri_bz_io.h" +#include "rpa_lri_detail.h" +#include "source_base/global_function.h" +#include "source_cell/klist.h" +#include "source_cell/unitcell.h" +#include "source_io/module_parameter/input_parameter.h" + +#include +#include +#include + +template +void RPA_LRI::out_bz_sampling(const UnitCell& ucell) +try +{ + if (this->runtime.rank != 0) + { + return; + } + + ModuleBase::TITLE("DFT_RPA_interface", "out_bz_sampling"); + const int nks_tot = this->runtime.input.nspin == 2 ? static_cast(p_kv->get_nks()) / 2 : p_kv->get_nks(); + RpaLriDetail::BzSampleData data; + data.mesh = {{p_kv->nmp[0], p_kv->nmp[1], p_kv->nmp[2]}}; + data.reduced.resize(nks_tot); + for (int ik = 0; ik < nks_tot; ++ik) + { + const ModuleBase::Vector3 kcart = p_kv->kvec_c[ik] * (ModuleBase::TWO_PI / ucell.lat0); + data.reduced[ik].weight = p_kv->wk[ik]; + data.reduced[ik].direct = {{p_kv->kvec_d[ik].x, p_kv->kvec_d[ik].y, p_kv->kvec_d[ik].z}}; + data.reduced[ik].cartesian = {{kcart.x, kcart.y, kcart.z}}; + } + data.full_cartesian.resize(p_kv->kvec_c_full.size()); + for (std::size_t ik = 0; ik < p_kv->kvec_c_full.size(); ++ik) + { + const auto kcart = p_kv->kvec_c_full[ik] * (ModuleBase::TWO_PI / ucell.lat0); + data.full_cartesian[ik] = {{kcart.x, kcart.y, kcart.z}}; + } + data.full_map = p_kv->ibz_index; + RpaLriDetail::write_bz_sample(this->runtime.input.rpa_outdir + "bz_sample.txt", data); +} +catch (const std::exception& error) +{ + RpaLriDetail::abort_output(this->mpi_comm, error.what()); +} +catch (...) +{ + RpaLriDetail::abort_output(this->mpi_comm, "Unknown exception in out_bz_sampling"); +} + +template void RPA_LRI::out_bz_sampling(const UnitCell& ucell); +template void RPA_LRI, double>::out_bz_sampling(const UnitCell& ucell); diff --git a/source/source_lcao/module_ri/rpa_lri_bz_io.cpp b/source/source_lcao/module_ri/rpa_lri_bz_io.cpp new file mode 100644 index 00000000000..935c7231f44 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_bz_io.cpp @@ -0,0 +1,132 @@ +#include "rpa_lri_bz_io.h" + +#include "rpa_lri_detail.h" + +#include +#include +#include +#include +#include + +namespace RpaLriDetail +{ +namespace +{ +bool finite_point(const std::array& point) +{ + return std::isfinite(point[0]) && std::isfinite(point[1]) && std::isfinite(point[2]); +} + +std::size_t full_grid_size(const std::array& mesh) +{ + unsigned long long size = 1; + for (const int value: mesh) + { + if (value <= 0) + { + throw std::runtime_error("bz_sample requires a positive Monkhorst-Pack mesh."); + } + size = checked_mul_u64(size, static_cast(value), "bz_sample grid size"); + } + if (size > static_cast(std::numeric_limits::max())) + { + throw std::runtime_error("bz_sample grid size exceeds size_t range."); + } + return static_cast(size); +} + +void write_point(std::ofstream& output, const BzSamplePoint& point, const int index, const double weight) +{ + output << std::setw(8) << index << std::setw(24) << std::scientific << std::setprecision(15) << weight + << std::setw(24) << std::scientific << std::setprecision(15) << point.direct[0] << std::setw(24) + << std::scientific << std::setprecision(15) << point.direct[1] << std::setw(24) << std::scientific + << std::setprecision(15) << point.direct[2] << std::setw(24) << std::scientific << std::setprecision(15) + << point.cartesian[0] << std::setw(24) << std::scientific << std::setprecision(15) << point.cartesian[1] + << std::setw(24) << std::scientific << std::setprecision(15) << point.cartesian[2] << std::setw(8) << index + << std::setw(8) << index << '\n'; +} +} // namespace + +void write_bz_sample(const std::string& filename, const BzSampleData& data) +{ + const std::size_t full_count = full_grid_size(data.mesh); + const int nk_full = checked_i32_from_size(full_count, "bz_sample full-grid size"); + const int nk_reduced = checked_i32_from_size(data.reduced.size(), "bz_sample reduced-grid size"); + if (data.reduced.empty() || data.reduced.size() > full_count) + { + throw std::runtime_error("bz_sample has an invalid reduced-grid size."); + } + if (data.full_cartesian.size() != full_count || data.full_map.size() != full_count) + { + throw std::runtime_error("bz_sample full-grid metadata has an invalid size."); + } + + double weight_sum = 0.0; + for (const BzSamplePoint& point: data.reduced) + { + if (!std::isfinite(point.weight) || point.weight <= 0.0 || !finite_point(point.direct) + || !finite_point(point.cartesian)) + { + throw std::runtime_error("bz_sample contains a non-finite or non-positive point."); + } + weight_sum += point.weight; + } + if (!std::isfinite(weight_sum) || weight_sum <= 0.0) + { + throw std::runtime_error("bz_sample has a non-positive total k-point weight."); + } + + std::vector multiplicity(data.reduced.size(), 0); + if (full_count > data.reduced.size()) + { + for (std::size_t ik = 0; ik < full_count; ++ik) + { + const int representative = data.full_map[ik]; + if (representative < 0 || representative >= nk_reduced) + { + throw std::runtime_error("bz_sample representative is outside the reduced grid."); + } + if (!finite_point(data.full_cartesian[ik])) + { + throw std::runtime_error("bz_sample contains a non-finite full-grid point."); + } + ++multiplicity[representative]; + } + for (std::size_t ik = 0; ik < data.reduced.size(); ++ik) + { + const double expected = static_cast(multiplicity[ik]) / static_cast(full_count); + if (multiplicity[ik] == 0 || std::abs(data.reduced[ik].weight / weight_sum - expected) > 1e-6) + { + throw std::runtime_error("bz_sample weight disagrees with full-grid multiplicity."); + } + } + } + + std::ofstream output(filename.c_str(), std::ios::out | std::ios::trunc); + if (!output.good()) + { + throw std::runtime_error("Failed to open " + filename); + } + output << data.mesh[0] << std::setw(6) << data.mesh[1] << std::setw(6) << data.mesh[2] << '\n'; + output << nk_reduced << std::setw(8) << nk_reduced << '\n'; + for (std::size_t ik = 0; ik < data.reduced.size(); ++ik) + { + write_point(output, data.reduced[ik], static_cast(ik + 1), data.reduced[ik].weight / weight_sum); + } + if (full_count > data.reduced.size()) + { + output << "full_kmap " << nk_full << '\n'; + for (std::size_t ik = 0; ik < full_count; ++ik) + { + const auto& point = data.full_cartesian[ik]; + output << std::setw(8) << ik + 1 << std::setw(24) << point[0] << std::setw(24) << point[1] << std::setw(24) + << point[2] << std::setw(8) << data.full_map[ik] + 1 << '\n'; + } + } + output.close(); + if (!output.good()) + { + throw std::runtime_error("Failed to write " + filename); + } +} +} // namespace RpaLriDetail diff --git a/source/source_lcao/module_ri/rpa_lri_bz_io.h b/source/source_lcao/module_ri/rpa_lri_bz_io.h new file mode 100644 index 00000000000..cecc115de97 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_bz_io.h @@ -0,0 +1,28 @@ +#ifndef RPA_LRI_BZ_IO_H +#define RPA_LRI_BZ_IO_H + +#include +#include +#include + +namespace RpaLriDetail +{ +struct BzSamplePoint +{ + double weight = 0.0; + std::array direct = {{0.0, 0.0, 0.0}}; + std::array cartesian = {{0.0, 0.0, 0.0}}; +}; + +struct BzSampleData +{ + std::array mesh = {{0, 0, 0}}; + std::vector reduced; + std::vector> full_cartesian; + std::vector full_map; +}; + +void write_bz_sample(const std::string& filename, const BzSampleData& data); +} // namespace RpaLriDetail + +#endif diff --git a/source/source_lcao/module_ri/rpa_lri_coeff_io.cpp b/source/source_lcao/module_ri/rpa_lri_coeff_io.cpp new file mode 100644 index 00000000000..67410442022 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_coeff_io.cpp @@ -0,0 +1,156 @@ +#include "exx_lri.h" +#include "rpa_lri.h" +#include "rpa_lri_coeff_v1.h" +#include "rpa_lri_detail.h" +#include "source_base/global_function.h" +#include "source_basis/module_ao/elem_basis_idx_orb.h" +#include "source_estate/elecstate_lcao.h" +#include "source_io/module_parameter/input_parameter.h" +#include "source_lcao/module_ri/module_exx_symmetry/symm_rotation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +void RPA_LRI::out_Cs(const UnitCell& ucell, + std::map>>& Cs_in, + std::string filename) +{ + ModuleBase::TITLE("DFT_RPA_interface", "out_Cs"); + ModuleBase::timer::start("RPA_LRI", "out_Cs"); + + std::stringstream ss; + ss << filename << (this->runtime.rank + 1) << ".txt"; + std::ofstream ofs; + ofs.open(this->runtime.input.rpa_outdir + ss.str().c_str(), std::ios::out); + ofs << ucell.nat << " " << 0 << std::endl; + ofs << std::fixed << std::setprecision(15); + for (auto& Ip: Cs_in) + { + size_t I = Ip.first; + size_t i_num = ucell.atoms[ucell.iat2it[I]].nw; + for (auto& JPp: Ip.second) + { + size_t J = JPp.first.first; + auto R = JPp.first.second; + auto& tmp_Cs = JPp.second; + size_t j_num = ucell.atoms[ucell.iat2it[J]].nw; + + ofs << I + 1 << " " << J + 1 << " " << R[0] << " " << R[1] << " " << R[2] << " " << i_num + << std::endl; + ofs << j_num << " " << tmp_Cs.shape[0] << std::endl; + for (int i = 0; i != i_num; i++) + { + for (int j = 0; j != j_num; j++) + { + for (int mu = 0; mu != tmp_Cs.shape[0]; mu++) + { + ofs << std::setw(30) << tmp_Cs(mu, i, j) << std::endl; + } + } + } + } + } + ofs.close(); + ModuleBase::timer::end("RPA_LRI", "out_Cs"); + return; +} + +template +void RPA_LRI::out_Cs_v1(const UnitCell& ucell, + std::map>>& Cs_in, + std::string filename) +try +{ + ModuleBase::TITLE("DFT_RPA_interface", "out_Cs_v1"); + ModuleBase::timer::start("RPA_LRI", "out_Cs_v1"); + + std::vector records; + records.reserve(Cs_in.size()); + for (auto& Ip: Cs_in) + { + const int I = static_cast(Ip.first); + const int i_num = ucell.atoms[ucell.iat2it[I]].nw; + for (auto& JPp: Ip.second) + { + const int J = static_cast(JPp.first.first); + auto& tmp_Cs = JPp.second; + if (tmp_Cs.shape.size() != 3 || tmp_Cs.shape[0] <= 0 || tmp_Cs.shape[1] <= 0 || tmp_Cs.shape[2] <= 0) + { + continue; + } + const int j_num = ucell.atoms[ucell.iat2it[J]].nw; + if (static_cast(tmp_Cs.shape[1]) != i_num || static_cast(tmp_Cs.shape[2]) != j_num) + { + throw std::runtime_error("LibRPA v1 Cs output encountered an inconsistent tensor shape."); + } + RpaLriDetail::LriCoefV1Block record; + record.ia1 = I + 1; + record.ia2 = J + 1; + for (int axis = 0; axis < 3; ++axis) + { + record.r[axis] = JPp.first.second[axis]; + } + record.tensor = &tmp_Cs; + record.nw1 = i_num; + record.nw2 = j_num; + record.naux = static_cast(tmp_Cs.shape[0]); + for (int i = 0; i != record.nw1; ++i) + { + for (int j = 0; j != record.nw2; ++j) + { + for (int mu = 0; mu != record.naux; ++mu) + { + record.max_abs + = std::max(record.max_abs, std::abs(RpaLriDetail::real_as_double(tmp_Cs(mu, i, j)))); + } + } + } + records.push_back(record); + } + } + + std::stringstream ss; + ss << filename << this->runtime.rank << ".dat"; + const std::string out_name = this->runtime.input.rpa_outdir + ss.str(); + RpaLriDetail::write_lri_coef_v1(out_name, static_cast(ucell.nat), records); + ModuleBase::timer::end("RPA_LRI", "out_Cs_v1"); +} +catch (const std::exception& error) +{ + RpaLriDetail::abort_output(this->mpi_comm, error.what()); +} +catch (...) +{ + RpaLriDetail::abort_output(this->mpi_comm, "Unknown exception in out_Cs_v1"); +} + +template void RPA_LRI::out_Cs( + const UnitCell& ucell, + std::map>, RI::Tensor>>& Cs_in, + std::string filename); +template void RPA_LRI, double>::out_Cs( + const UnitCell& ucell, + std::map>, RI::Tensor>>& Cs_in, + std::string filename); +template void RPA_LRI::out_Cs_v1( + const UnitCell& ucell, + std::map>, RI::Tensor>>& Cs_in, + std::string filename); +template void RPA_LRI, double>::out_Cs_v1( + const UnitCell& ucell, + std::map>, RI::Tensor>>& Cs_in, + std::string filename); diff --git a/source/source_lcao/module_ri/rpa_lri_coeff_v1.cpp b/source/source_lcao/module_ri/rpa_lri_coeff_v1.cpp new file mode 100644 index 00000000000..8e634e28605 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_coeff_v1.cpp @@ -0,0 +1,97 @@ +#include "rpa_lri_coeff_v1.h" + +#include "rpa_lri_detail.h" + +#include +#include +#include +#include + +namespace RpaLriDetail +{ +void write_lri_coef_v1(const std::string& filename, + const std::int32_t natom, + std::vector& blocks) +{ + if (natom <= 0) + { + throw std::runtime_error("LibRPA v1 coefficient output requires a positive atom count."); + } + + const std::int64_t nblocks + = checked_i64_from_u64(static_cast(blocks.size()), "LibRPA v1 coefficient block count"); + const unsigned long long header_bytes = 3 * sizeof(std::int32_t) + 2 * sizeof(std::int64_t); + const unsigned long long record_bytes + = 5 * sizeof(std::int32_t) + sizeof(double) + sizeof(std::int64_t); + unsigned long long offset = checked_add_u64( + header_bytes, + checked_mul_u64(static_cast(nblocks), record_bytes, "LibRPA v1 coefficient record table"), + "LibRPA v1 coefficient data offset"); + + for (auto& block: blocks) + { + if (block.tensor == nullptr || block.nw1 <= 0 || block.nw2 <= 0 || block.naux <= 0 + || block.tensor->shape.size() != 3 || block.tensor->shape[0] != static_cast(block.naux) + || block.tensor->shape[1] != static_cast(block.nw1) + || block.tensor->shape[2] != static_cast(block.nw2)) + { + throw std::runtime_error("LibRPA v1 coefficient output has an invalid block shape."); + } + block.offset = checked_i64_from_u64(offset, "LibRPA v1 coefficient data offset"); + const unsigned long long values = checked_mul_u64( + checked_mul_u64(static_cast(block.nw1), + static_cast(block.nw2), + "LibRPA v1 coefficient block size"), + static_cast(block.naux), + "LibRPA v1 coefficient block size"); + offset = checked_add_u64( + offset, + checked_mul_u64(values, sizeof(double), "LibRPA v1 coefficient block size"), + "LibRPA v1 coefficient data size"); + } + + std::ofstream ofs(filename.c_str(), std::ios::out | std::ios::binary | std::ios::trunc); + if (!ofs.good()) + { + throw std::runtime_error("Failed to open " + filename); + } + + const std::int32_t marker = LIBRPA_LRICOEF_V1_MARKER; + const std::int32_t ncell = 0; + write_scalar(ofs, marker, filename); + write_scalar(ofs, natom, filename); + write_scalar(ofs, ncell, filename); + write_scalar(ofs, nblocks, filename); + write_scalar(ofs, nblocks, filename); + for (const auto& block: blocks) + { + write_scalar(ofs, block.ia1, filename); + write_scalar(ofs, block.ia2, filename); + for (const std::int32_t coord: block.r) + { + write_scalar(ofs, coord, filename); + } + write_scalar(ofs, block.max_abs, filename); + write_scalar(ofs, block.offset, filename); + } + for (const auto& block: blocks) + { + for (int i = 0; i < block.nw1; ++i) + { + for (int j = 0; j < block.nw2; ++j) + { + for (int mu = 0; mu < block.naux; ++mu) + { + const double value = real_as_double((*block.tensor)(mu, i, j)); + write_scalar(ofs, value, filename); + } + } + } + } + ofs.close(); + if (!ofs.good()) + { + throw std::runtime_error("Failed to finish writing " + filename); + } +} +} // namespace RpaLriDetail diff --git a/source/source_lcao/module_ri/rpa_lri_coeff_v1.h b/source/source_lcao/module_ri/rpa_lri_coeff_v1.h new file mode 100644 index 00000000000..667fb7edadf --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_coeff_v1.h @@ -0,0 +1,34 @@ +#ifndef RPA_LRI_COEFF_V1_H +#define RPA_LRI_COEFF_V1_H + +#include +#include +#include + +namespace RI +{ +template +class Tensor; +} + +namespace RpaLriDetail +{ +struct LriCoefV1Block +{ + std::int32_t ia1 = 0; + std::int32_t ia2 = 0; + std::int32_t r[3] = {0, 0, 0}; + double max_abs = 0.0; + std::int64_t offset = 0; + int nw1 = 0; + int nw2 = 0; + int naux = 0; + const RI::Tensor* tensor = nullptr; +}; + +void write_lri_coef_v1(const std::string& filename, + const std::int32_t natom, + std::vector& blocks); +} // namespace RpaLriDetail + +#endif diff --git a/source/source_lcao/module_ri/rpa_lri_coulomb.cpp b/source/source_lcao/module_ri/rpa_lri_coulomb.cpp new file mode 100644 index 00000000000..00246bbdc13 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_coulomb.cpp @@ -0,0 +1,302 @@ +#include "exx_lri.h" +#include "rpa_lri.h" +#include "rpa_lri_detail.h" +#include "source_base/global_function.h" +#include "source_basis/module_ao/elem_basis_idx_orb.h" +#include "source_estate/elecstate_lcao.h" +#include "source_io/module_parameter/input_parameter.h" +#include "source_lcao/module_ri/module_exx_symmetry/symm_rotation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +void RPA_LRI::output_cut_coulomb_cs(const UnitCell& ucell, Exx_LRI* exx_lri_rpa) +{ + ModuleBase::TITLE("RPA_LRI", "output_cut_coulomb_cs"); + ModuleBase::timer::start("RPA_LRI", "output_cut_coulomb_cs"); + + std::map>> Vs_cut_IJR; + std::map>> Cs; + std::map>> tmp; + std::cout << "Use rpa_ccp_rmesh_times=" << this->ccp_rmesh_times_cut << " to calculate cut Coulomb" << std::endl; + // Shrink_ABFS_ORBITAL cannot exceed this angular momentum of MGT + exx_lri_rpa->cal_cut_coulomb_cs(Vs_cut_IJR, Cs, ucell, this->runtime.input.out_ri_cv); + // MPI: {ia0, {ia1, R}} to {ia0, ia1} + std::vector atoms(ucell.nat); + for (int iat = 0; iat < ucell.nat; ++iat) + atoms[iat] = iat; + const std::array period_Vs + = LRI_CV_Tools::cal_latvec_range(1 + this->ccp_rmesh_times_cut, ucell, this->orb_cutoff_); + const std::pair, std::vector>>> list_As_Vs_atoms + = RI::Distribute_Equally::distribute_atoms(this->mpi_comm, atoms, period_Vs, 2, false); + const auto list_A0_pair_R = list_As_Vs_atoms.first; + const auto list_A1_pair_R = list_As_Vs_atoms.second[0]; + std::set atoms00; + std::set atoms01; + for (const auto& I: list_A0_pair_R) + { + atoms00.insert(I); + } + for (const auto& JR: list_A1_pair_R) + { + atoms01.insert(JR.first); + } + std::map>> Vs_cut_IJ + = RI_2D_Comm::comm_map2_first(this->mpi_comm, Vs_cut_IJR, atoms00, atoms01); + Vs_cut_IJR.clear(); + const std::array period = {p_kv->nmp[0], p_kv->nmp[1], p_kv->nmp[2]}; + this->Vs_period = RI::RI_Tools::cal_period(Vs_cut_IJ, period); + if (this->runtime.input.out_librpa_ver == 1) + { + this->out_coulomb_k_v1(ucell, this->Vs_period, "V_cut_", exx_lri_rpa); + } + else + { + this->out_coulomb_k(ucell, this->Vs_period, "coulomb_cut_", exx_lri_rpa); + } + Vs_period.clear(); + Vs_period.swap(tmp); + + this->Cs_period = RI::RI_Tools::cal_period(Cs, period); + this->Cs_period = exx_lri_rpa->exx_lri.post_2D.set_tensors_map2(this->Cs_period); + + if (this->runtime.input.out_librpa_ver == 1) + { + if (this->info.shrink_abfs_pca_thr >= 0.0) + { + this->out_Cs_v1(ucell, this->Cs_period, "Cs_shrink_"); + } + else + { + this->out_Cs_v1(ucell, this->Cs_period, "Cs_"); + } + } + else + { + if (this->info.shrink_abfs_pca_thr >= 0.0) + this->out_Cs(ucell, this->Cs_period, "Cs_shrinked_data_"); + else + this->out_Cs(ucell, this->Cs_period, "Cs_data_"); + } + Cs_period.clear(); + Cs_period.swap(tmp); + + ModuleBase::timer::end("RPA_LRI", "output_cut_coulomb_cs"); +} + +template +Conv_Coulomb_Pot_K::Coulomb_Method RPA_LRI::select_coulomb_basis_method_(Exx_LRI* exx_lri) const +{ + if (exx_lri == nullptr || exx_lri->exx_objs.empty()) + { + throw std::invalid_argument("Cannot select Coulomb basis method from an empty Exx_LRI object."); + } + return exx_lri->exx_objs.count(Conv_Coulomb_Pot_K::Coulomb_Method::Center2) + ? Conv_Coulomb_Pot_K::Coulomb_Method::Center2 + : exx_lri->exx_objs.begin()->first; +} + +template +std::vector RPA_LRI::collect_atom_naux_(const UnitCell& ucell, Exx_LRI* exx_lri) const +{ + const auto basis_method = this->select_coulomb_basis_method_(exx_lri); + std::vector atom_naux(static_cast(ucell.nat), 0); + for (int I = 0; I != ucell.nat; ++I) + { + atom_naux[static_cast(I)] + = exx_lri->exx_objs.at(basis_method).cv.get_index_abfs_size(ucell.iat2it[I]); + if (atom_naux[static_cast(I)] <= 0) + { + throw std::runtime_error("LibRPA v1 output found a non-positive per-atom auxiliary size."); + } + } + return atom_naux; +} + +template +void RPA_LRI::output_ewald_coulomb(const UnitCell& ucell, const K_Vectors& kv, const LCAO_Orbitals& orb) +{ + ModuleBase::TITLE("RPA_LRI", "output_ewald_coulomb"); + ModuleBase::timer::start("RPA_LRI", "output_ewald_coulomb"); + + Exx_Info_RI local_info = this->info; + local_info.ccp_rmesh_times = this->ccp_rmesh_times_ewald; + if (!exx_full_coulomb) + { + Exx_LRI* new_exx = new Exx_LRI(local_info); + exx_full_coulomb.reset(new_exx); + } + + if (this->info.shrink_abfs_pca_thr >= 0.0) + exx_full_coulomb->init(mpi_comm, ucell, kv, orb, this->abfs_shrink); + else + exx_full_coulomb->init(mpi_comm, ucell, kv, orb, this->abfs); + std::map>> Vs_full_IJR; + std::map>> Cs; + std::map>> tmp; + exx_full_coulomb->cal_ewald_coulomb(Vs_full_IJR, Cs, ucell, this->runtime.input.out_ri_cv); + // MPI: {ia0, {ia1, R}} to {ia0, ia1} + std::vector atoms(ucell.nat); + for (int iat = 0; iat < ucell.nat; ++iat) + atoms[iat] = iat; + const std::array period_Vs + = LRI_CV_Tools::cal_latvec_range(1 + this->ccp_rmesh_times_ewald, ucell, this->orb_cutoff_); + const std::pair, std::vector>>> list_As_Vs_atoms + = RI::Distribute_Equally::distribute_atoms(mpi_comm, atoms, period_Vs, 2, false); + const auto list_A0_pair_R = list_As_Vs_atoms.first; + const auto list_A1_pair_R = list_As_Vs_atoms.second[0]; + std::set atoms00; + std::set atoms01; + for (const auto& I: list_A0_pair_R) + { + atoms00.insert(I); + } + for (const auto& JR: list_A1_pair_R) + { + atoms01.insert(JR.first); + } + std::map>> Vs_full_IJ + = RI_2D_Comm::comm_map2_first(mpi_comm, Vs_full_IJR, atoms00, atoms01); + Vs_full_IJR.clear(); + + const std::array period = {p_kv->nmp[0], p_kv->nmp[1], p_kv->nmp[2]}; + this->Vs_period = RI::RI_Tools::cal_period(Vs_full_IJ, period); + if (this->runtime.input.out_librpa_ver == 1) + { + const bool use_shrink = this->info.shrink_abfs_pca_thr >= 0.0; + const std::string aux_filename = use_shrink ? "aux_basis_s.txt" : "aux_basis.txt"; + const std::string legacy_filename = use_shrink ? "" : "basis_map.txt"; + // With shrink enabled, cal_large_Cs emits wfc_basis.txt once and this + // call emits only the reduced auxiliary basis. + this->out_librpa_basis_v1(ucell, exx_full_coulomb.get(), aux_filename, legacy_filename, !use_shrink); + this->out_coulomb_k_v1(ucell, this->Vs_period, "V_full_", exx_full_coulomb.get()); + } + else + { + this->out_coulomb_k(ucell, this->Vs_period, "coulomb_mat_", exx_full_coulomb.get()); + } + Vs_period.clear(); + Vs_period.swap(tmp); + Cs.clear(); + Cs.swap(tmp); + + exx_full_coulomb.reset(); + RpaLriDetail::trim_malloc_cache(); + + ModuleBase::timer::end("RPA_LRI", "output_ewald_coulomb"); +} + +template +void RPA_LRI::out_coulomb_k(const UnitCell& ucell, + std::map>>& Vs, + std::string filename, + Exx_LRI* exx_lri) +{ + ModuleBase::TITLE("DFT_RPA_interface", "out_coulomb_k"); + ModuleBase::timer::start("RPA_LRI", "out_coulomb_k"); + + int all_mu = 0; + std::vector mu_shift(ucell.nat); + const auto basis_method = this->select_coulomb_basis_method_(exx_lri); + for (int I = 0; I != ucell.nat; I++) + { + mu_shift[I] = all_mu; + all_mu += exx_lri->exx_objs.at(basis_method).cv.get_index_abfs_size(ucell.iat2it[I]); + } + const int nks_tot = this->runtime.input.nspin == 2 ? (int)p_kv->get_nks() / 2 : p_kv->get_nks(); + std::stringstream ss; + ss << filename << (this->runtime.rank + 1) << ".txt"; + + std::ofstream ofs; + ofs.open(this->runtime.input.rpa_outdir + ss.str().c_str(), std::ios::out); + + ofs << nks_tot << std::endl; + ofs << std::fixed << std::setprecision(15); + for (auto& Ip: Vs) + { + auto I = Ip.first; + size_t mu_num = exx_lri->exx_objs.at(basis_method).cv.get_index_abfs_size(ucell.iat2it[I]); + + for (int ik = 0; ik != nks_tot; ik++) + { + std::map>> Vq_k_IJ; + for (auto& JPp: Ip.second) + { + auto J = JPp.first.first; + + auto R = JPp.first.second; + if (J < I) + { + continue; + } + RI::Tensor> tmp_VR = RI::Global_Func::convert>(JPp.second); + const double arg = 1 * (p_kv->kvec_c[ik] * (RI_Util::array3_to_Vector3(R) * ucell.latvec)) + * ModuleBase::TWO_PI; // latvec + const std::complex kphase = std::complex(cos(arg), sin(arg)); + if (Vq_k_IJ[J].empty()) + { + Vq_k_IJ[J] = RI::Tensor>({tmp_VR.shape[0], tmp_VR.shape[1]}); + } + Vq_k_IJ[J] = Vq_k_IJ[J] + tmp_VR * kphase; + } + for (auto& vq_Jp: Vq_k_IJ) + { + auto iJ = vq_Jp.first; + auto& vq_J = vq_Jp.second; + size_t nu_num = exx_lri->exx_objs.at(basis_method).cv.get_index_abfs_size(ucell.iat2it[iJ]); + ofs << all_mu << " " << mu_shift[I] + 1 << " " << mu_shift[I] + mu_num << " " << mu_shift[iJ] + 1 + << " " << mu_shift[iJ] + nu_num << std::endl; + ofs << ik + 1 << " " << p_kv->wk[ik] / 2.0 * this->runtime.input.nspin << std::endl; + for (int i = 0; i != vq_J.data->size(); i++) + { + ofs << std::setw(25) << (*vq_J.data)[i].real() << std::setw(25) << (*vq_J.data)[i].imag() + << std::endl; + } + } + } + } + ofs.close(); + ModuleBase::timer::end("RPA_LRI", "out_coulomb_k"); +} + +template void RPA_LRI::output_cut_coulomb_cs(const UnitCell& ucell, Exx_LRI* exx_lri_rpa); +template void RPA_LRI, double>::output_cut_coulomb_cs(const UnitCell& ucell, + Exx_LRI* exx_lri_rpa); +template Conv_Coulomb_Pot_K::Coulomb_Method RPA_LRI::select_coulomb_basis_method_( + Exx_LRI* exx_lri) const; +template Conv_Coulomb_Pot_K::Coulomb_Method RPA_LRI, double>::select_coulomb_basis_method_( + Exx_LRI* exx_lri) const; +template std::vector RPA_LRI::collect_atom_naux_(const UnitCell& ucell, + Exx_LRI* exx_lri) const; +template std::vector RPA_LRI, double>::collect_atom_naux_(const UnitCell& ucell, + Exx_LRI* exx_lri) const; +template void RPA_LRI::output_ewald_coulomb(const UnitCell& ucell, + const K_Vectors& kv, + const LCAO_Orbitals& orb); +template void RPA_LRI, double>::output_ewald_coulomb(const UnitCell& ucell, + const K_Vectors& kv, + const LCAO_Orbitals& orb); +template void RPA_LRI::out_coulomb_k( + const UnitCell& ucell, + std::map>, RI::Tensor>>& Vs, + std::string filename, + Exx_LRI* exx_lri); +template void RPA_LRI, double>::out_coulomb_k( + const UnitCell& ucell, + std::map>, RI::Tensor>>& Vs, + std::string filename, + Exx_LRI* exx_lri); diff --git a/source/source_lcao/module_ri/rpa_lri_coulomb_io.cpp b/source/source_lcao/module_ri/rpa_lri_coulomb_io.cpp new file mode 100644 index 00000000000..5cea3bb8ab0 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_coulomb_io.cpp @@ -0,0 +1,198 @@ +#include "exx_lri.h" +#include "rpa_lri.h" +#include "rpa_lri_detail.h" +#include "source_base/global_function.h" +#include "source_basis/module_ao/elem_basis_idx_orb.h" +#include "source_estate/elecstate_lcao.h" +#include "source_io/module_parameter/input_parameter.h" +#include "source_lcao/module_ri/module_exx_symmetry/symm_rotation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +void RPA_LRI::out_coulomb_k_v1(const UnitCell& ucell, + std::map>>& Vs, + std::string filename, + Exx_LRI* exx_lri) +try +{ + ModuleBase::TITLE("DFT_RPA_interface", "out_coulomb_k_v1"); + ModuleBase::timer::start("RPA_LRI", "out_coulomb_k_v1"); + + const auto basis_method = this->select_coulomb_basis_method_(exx_lri); + const auto atom_naux = this->collect_atom_naux_(ucell, exx_lri); + const int all_mu = RpaLriDetail::sum_int_vector(atom_naux); + const int nks_tot = this->runtime.input.nspin == 2 ? static_cast(p_kv->get_nks()) / 2 : p_kv->get_nks(); + const std::size_t natoms = static_cast(ucell.nat); + + struct V1Block + { + int pair_index = 0; + int I = 0; + int J = 0; + std::int64_t offset = 0; + RI::Tensor> tensor; + }; + + for (int ik = 0; ik != nks_tot; ++ik) + { + std::vector blocks; + for (auto& Ip: Vs) + { + const int I = static_cast(Ip.first); + const int mu_num = exx_lri->exx_objs.at(basis_method).cv.get_index_abfs_size(ucell.iat2it[I]); + std::map>> Vq_k_IJ; + for (auto& JPp: Ip.second) + { + const int J = static_cast(JPp.first.first); + if (J < I) + { + continue; + } + if (!RpaLriDetail::has_valid_matrix_shape(JPp.second)) + { + continue; + } + RI::Tensor> tmp_VR = RI::Global_Func::convert>(JPp.second); + const auto R = JPp.first.second; + // Fourier transform the real-space pair block with + // exp(i 2*pi k.R), using the lattice-vector convention. + const double arg + = (p_kv->kvec_c[ik] * (RI_Util::array3_to_Vector3(R) * ucell.latvec)) * ModuleBase::TWO_PI; + const std::complex kphase = std::complex(std::cos(arg), std::sin(arg)); + if (Vq_k_IJ[J].empty()) + { + Vq_k_IJ[J] = RI::Tensor>({tmp_VR.shape[0], tmp_VR.shape[1]}); + } + Vq_k_IJ[J] = Vq_k_IJ[J] + tmp_VR * kphase; + } + for (auto& vq_Jp: Vq_k_IJ) + { + const int J = static_cast(vq_Jp.first); + auto& vq_J = vq_Jp.second; + const int nu_num = exx_lri->exx_objs.at(basis_method).cv.get_index_abfs_size(ucell.iat2it[J]); + if (static_cast(vq_J.shape[0]) != mu_num || static_cast(vq_J.shape[1]) != nu_num) + { + throw std::runtime_error("LibRPA v1 Coulomb output encountered an inconsistent tensor shape."); + } + V1Block block; + block.pair_index = static_cast(RpaLriDetail::coulomb_atom_pair_index(static_cast(I), + static_cast(J), + natoms)); + block.I = I; + block.J = J; + block.tensor = std::move(vq_J); + blocks.push_back(std::move(block)); + } + } + + std::sort(blocks.begin(), blocks.end(), [](const V1Block& lhs, const V1Block& rhs) { + return lhs.pair_index < rhs.pair_index; + }); + for (std::size_t ib = 1; ib < blocks.size(); ++ib) + { + if (blocks[ib - 1].pair_index == blocks[ib].pair_index) + { + throw std::runtime_error("LibRPA v1 Coulomb output found duplicate atom-pair blocks on one MPI rank."); + } + } + + const std::int64_t nblocks = static_cast(blocks.size()); + const std::int64_t header_size + = static_cast(6 * sizeof(std::int32_t)) + + static_cast(ucell.nat * sizeof(std::int32_t)) + + nblocks * static_cast(sizeof(std::int32_t) + sizeof(std::int64_t)); + std::int64_t offset = header_size; + for (auto& block: blocks) + { + block.offset = offset; + const unsigned long long values = RpaLriDetail::checked_mul_u64( + static_cast(atom_naux[static_cast(block.I)]), + static_cast(atom_naux[static_cast(block.J)]), + "LibRPA v1 Coulomb block size"); + const unsigned long long bytes + = RpaLriDetail::checked_mul_u64(values, + static_cast(sizeof(std::complex)), + "LibRPA v1 Coulomb block size"); + offset += RpaLriDetail::checked_i64_from_u64(bytes, "LibRPA v1 Coulomb block size"); + } + + std::stringstream ss; + ss << filename << ik + 1 << "_r" << this->runtime.rank << ".dat"; + const std::string out_name = this->runtime.input.rpa_outdir + ss.str(); + std::ofstream ofs(out_name.c_str(), std::ios::out | std::ios::binary | std::ios::trunc); + if (!ofs.good()) + { + throw std::runtime_error("Failed to open " + out_name); + } + + const std::int32_t marker = RpaLriDetail::LIBRPA_COULOMB_V1_MARKER; + const std::int32_t iq = ik + 1; + const std::int32_t naux = all_mu; + const std::int32_t value_flag = RpaLriDetail::LIBRPA_COULOMB_V1_COMPLEX_FLAG; + const std::int32_t natom = ucell.nat; + const std::int32_t nblock_i32 = static_cast(nblocks); + RpaLriDetail::write_scalar(ofs, marker, out_name); + RpaLriDetail::write_scalar(ofs, iq, out_name); + RpaLriDetail::write_scalar(ofs, naux, out_name); + RpaLriDetail::write_scalar(ofs, value_flag, out_name); + RpaLriDetail::write_scalar(ofs, natom, out_name); + RpaLriDetail::write_scalar(ofs, nblock_i32, out_name); + for (const int atom_aux: atom_naux) + { + const std::int32_t atom_aux_i32 = atom_aux; + RpaLriDetail::write_scalar(ofs, atom_aux_i32, out_name); + } + for (const auto& block: blocks) + { + const std::int32_t pair_index = block.pair_index; + RpaLriDetail::write_scalar(ofs, pair_index, out_name); + RpaLriDetail::write_scalar(ofs, block.offset, out_name); + } + for (const auto& block: blocks) + { + const std::size_t nvalues = block.tensor.get_shape_all(); + if (nvalues == 0) + { + throw std::runtime_error("LibRPA v1 Coulomb output encountered an empty tensor payload."); + } + RpaLriDetail::checked_write(ofs, block.tensor.ptr(), nvalues * sizeof(std::complex), out_name); + } + ofs.close(); + } + + ModuleBase::timer::end("RPA_LRI", "out_coulomb_k_v1"); +} +catch (const std::exception& error) +{ + RpaLriDetail::abort_output(this->mpi_comm, error.what()); +} +catch (...) +{ + RpaLriDetail::abort_output(this->mpi_comm, "Unknown exception in out_coulomb_k_v1"); +} + +template void RPA_LRI::out_coulomb_k_v1( + const UnitCell& ucell, + std::map>, RI::Tensor>>& Vs, + std::string filename, + Exx_LRI* exx_lri); +template void RPA_LRI, double>::out_coulomb_k_v1( + const UnitCell& ucell, + std::map>, RI::Tensor>>& Vs, + std::string filename, + Exx_LRI* exx_lri); diff --git a/source/source_lcao/module_ri/rpa_lri_detail.cpp b/source/source_lcao/module_ri/rpa_lri_detail.cpp new file mode 100644 index 00000000000..22db4e57721 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_detail.cpp @@ -0,0 +1,174 @@ +#include "rpa_lri_detail.h" +#include "source_base/tool_quit.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__GLIBC__) +#include +#endif + +namespace RpaLriDetail +{ +void trim_malloc_cache() +{ +#if defined(__GLIBC__) + malloc_trim(0); +#endif +} +bool debug_dump_exx_ao_enabled() +{ + const char* env = std::getenv("ABACUS_DUMP_EXX_AO"); + if (env == nullptr) + { + return false; + } + const std::string value(env); + return !(value.empty() || value == "0" || value == "f" || value == "F" || value == "false" || value == "FALSE"); +} + +std::size_t coulomb_atom_pair_index(const std::size_t I, const std::size_t J, const std::size_t natoms) +{ + if (I > J) + { + throw std::runtime_error("LibRPA v1 Coulomb output expects upper-triangular atom pairs."); + } + // Rows before I contribute I*natoms - I*(I-1)/2 entries; + // J-I then selects the element within row I. + return I * natoms - I * (I - 1) / 2 + (J - I); +} + +void checked_write(std::ofstream& ofs, const void* data, const std::size_t bytes, const std::string& filename) +{ + const char* ptr = reinterpret_cast(data); + std::size_t bytes_left = bytes; + const std::size_t max_chunk = static_cast(std::numeric_limits::max()); + while (bytes_left > 0) + { + const std::size_t chunk = std::min(bytes_left, max_chunk); + ofs.write(ptr, static_cast(chunk)); + if (!ofs.good()) + { + throw std::runtime_error("Failed to write " + filename); + } + ptr += chunk; + bytes_left -= chunk; + } +} + +template +void write_scalar(std::ofstream& ofs, const Value& value, const std::string& filename) +{ + checked_write(ofs, &value, sizeof(Value), filename); +} + +unsigned long long checked_mul_u64(const unsigned long long lhs, + const unsigned long long rhs, + const std::string& context) +{ + if (lhs != 0 && rhs > std::numeric_limits::max() / lhs) + { + throw std::runtime_error(context + " exceeds uint64_t range."); + } + return lhs * rhs; +} + +unsigned long long checked_add_u64(const unsigned long long lhs, + const unsigned long long rhs, + const std::string& context) +{ + if (rhs > std::numeric_limits::max() - lhs) + { + throw std::runtime_error(context + " exceeds uint64_t range."); + } + return lhs + rhs; +} + +std::int64_t checked_i64_from_u64(const unsigned long long value, const std::string& context) +{ + if (value > static_cast(std::numeric_limits::max())) + { + throw std::runtime_error(context + " exceeds int64_t range."); + } + return static_cast(value); +} + +std::int32_t checked_i32_from_size(const std::size_t value, const std::string& context) +{ + if (value > static_cast(std::numeric_limits::max())) + { + throw std::runtime_error(context + " exceeds int32_t range."); + } + return static_cast(value); +} + +std::int32_t checked_i32_from_int(const int value, const std::string& context) +{ + if (value < 0) + { + throw std::runtime_error(context + " is negative."); + } + return checked_i32_from_size(static_cast(value), context); +} +int sum_int_vector(const std::vector& values) +{ + int sum = 0; + for (const int value: values) + { + if (value > std::numeric_limits::max() - sum) + { + throw std::runtime_error("Integer overflow while summing LibRPA v1 basis sizes."); + } + sum += value; + } + return sum; +} +double real_as_double(const std::complex& value) +{ + return value.real(); +} +double real_as_double(double value) +{ + return value; +} + +bool has_valid_matrix_shape(const RI::Tensor& tensor) +{ + return tensor.shape.size() == 2 && tensor.shape[0] > 0 && tensor.shape[1] > 0; +} + +[[noreturn]] void abort_output(MPI_Comm comm, const std::string& message) +{ + std::cerr << "RPA producer output failed: " << message << std::endl; +#ifdef __MPI + // A writer error can be rank-local. WARNING_QUIT finalizes only the + // calling rank and can leave its peers waiting in MPI-IO, so terminate + // the communicator here after the collective checks have reported the + // failure where possible. + if (comm != MPI_COMM_NULL) + { + MPI_Abort(comm, 1); + } + std::abort(); +#else + ModuleBase::WARNING_QUIT("RPA_LRI", message); +#endif +} +template void write_scalar(std::ofstream&, const std::int32_t&, const std::string&); +template void write_scalar(std::ofstream&, const std::int64_t&, const std::string&); +template void write_scalar(std::ofstream&, const double&, const std::string&); +} // namespace RpaLriDetail diff --git a/source/source_lcao/module_ri/rpa_lri_detail.h b/source/source_lcao/module_ri/rpa_lri_detail.h new file mode 100644 index 00000000000..d2b157c0120 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_detail.h @@ -0,0 +1,122 @@ +#ifndef RPA_LRI_DETAIL_H +#define RPA_LRI_DETAIL_H + +#include "source_psi/psi.h" + +#include +#include +#include +#include +#include +#include +#include + +class Parallel_Orbitals; +class UnitCell; +class K_Vectors; +class Numerical_Orbital_Lm; +namespace RI +{ +template +class Tensor; +} + +namespace RpaLriDetail +{ +constexpr int LIBRPA_COULOMB_V1_MARKER = -20129433; +constexpr int LIBRPA_LRICOEF_V1_MARKER = -10267453; +constexpr int LIBRPA_SHRINK_SINVS_V1_MARKER = -30241621; +constexpr int LIBRPA_KS_EIGENVECTOR_V1_MARKER = -12345679; +constexpr int LIBRPA_KS_EIGENVECTOR_V1_KIND_COMPLEX_DOUBLE = 28; +constexpr int LIBRPA_COULOMB_V1_COMPLEX_FLAG = 1; + +static_assert(sizeof(std::complex) == 2 * sizeof(double), + "LibRPA v1 binary output expects complex as two doubles."); + +void trim_malloc_cache(); +void write_stru_sym(std::ostream& output, const UnitCell& cell, const K_Vectors& kv); + +bool debug_dump_exx_ao_enabled(); + +std::size_t coulomb_atom_pair_index(const std::size_t I, const std::size_t J, const std::size_t natoms); + +void checked_write(std::ofstream& ofs, const void* data, const std::size_t bytes, const std::string& filename); + +unsigned long long checked_mul_u64(const unsigned long long lhs, + const unsigned long long rhs, + const std::string& context); + +unsigned long long checked_add_u64(const unsigned long long lhs, + const unsigned long long rhs, + const std::string& context); + +std::int64_t checked_i64_from_u64(const unsigned long long value, const std::string& context); + +std::int32_t checked_i32_from_size(const std::size_t value, const std::string& context); + +std::int32_t checked_i32_from_int(const int value, const std::string& context); + +int sum_int_vector(const std::vector& values); + +double real_as_double(const std::complex& value); + +std::vector> collect_abfs_l_nchi( + const std::vector>>& abfs); + +std::vector> collect_wfc_l_nchi(const UnitCell& ucell); + +int basis_size_from_shell_counts(const std::vector& shell_counts); + +void write_librpa_split_basis_file(const UnitCell& ucell, + const std::vector& type_sizes, + const std::vector>& type_shell_counts, + const std::string& filename); + +template +void write_scalar(std::ofstream& ofs, const Value& value, const std::string& filename); +double real_as_double(double value); +bool has_valid_matrix_shape(const RI::Tensor& tensor); +[[noreturn]] void abort_output(MPI_Comm comm, const std::string& message); + +#ifdef __MPI +struct KSEigenvectorMpiLayout +{ + MPI_Datatype filetype = MPI_C_DOUBLE_COMPLEX; + bool free_filetype = false; + unsigned long long local_count = 0; + unsigned long long max_local_count = 0; +}; + +std::string mpi_error_string(const int error_code); + +void collective_mpi_check(const MPI_Comm mpi_comm, const int local_error, const std::string& context); + +void collective_require(const MPI_Comm mpi_comm, const bool local_condition, const std::string& context); + +MPI_Aint checked_mpi_aint_from_u64(const unsigned long long value, const std::string& context); + +MPI_Offset checked_mpi_offset_from_u64(const unsigned long long value, const std::string& context); + +KSEigenvectorMpiLayout make_ks_eigenvector_mpi_layout(const MPI_Comm mpi_comm, + const Parallel_Orbitals& parav, + const int nbands, + const int nbasis_wfc, + const bool is_soc, + const int spinor_component); + +int free_ks_eigenvector_mpi_layout(KSEigenvectorMpiLayout& layout); + +template +void write_ks_eigenvector_v1_mpi(const MPI_Comm mpi_comm, + const Parallel_Orbitals& parav, + const psi::Psi& psi, + const int nks_tot, + const int nspins, + const int nspin_abacus, + const int nbands, + const int nbasis_wfc, + const std::string& final_name); + +#endif +} // namespace RpaLriDetail +#endif diff --git a/source/source_lcao/module_ri/rpa_lri_eigenvector.cpp b/source/source_lcao/module_ri/rpa_lri_eigenvector.cpp new file mode 100644 index 00000000000..12f419fea03 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_eigenvector.cpp @@ -0,0 +1,292 @@ +#include "exx_lri.h" +#include "rpa_lri.h" +#include "rpa_lri_detail.h" +#include "source_base/global_function.h" +#include "source_basis/module_ao/elem_basis_idx_orb.h" +#include "source_estate/elecstate_lcao.h" +#include "source_io/module_parameter/input_parameter.h" +#include "source_lcao/module_ri/module_exx_symmetry/symm_rotation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +void RPA_LRI::out_eigen_vector_legacy(const Parallel_Orbitals& parav, const psi::Psi& psi) +{ + const int nks_tot = this->runtime.input.nspin == 2 ? p_kv->get_nks() / 2 : p_kv->get_nks(); + const int npsin_tmp = this->runtime.input.nspin == 2 ? 2 : 1; + const int nbands = parav.get_wfc_global_nbands(); + const int nbasis = parav.get_wfc_global_nbasis(); + const std::size_t values_per_iw = static_cast(nbands) * npsin_tmp; + // Preserve the legacy text writer when reader-v1 output is disabled. +#ifdef __MPI + const MPI_Comm mpi_comm = parav.comm(); + const int mpi_rank = parav.get_coord_row() * parav.get_dim1() + parav.get_coord_col(); + const int mpi_size = parav.get_dim0() * parav.get_dim1(); + const auto check_mpi = [mpi_comm](const int local_error, const std::string& context) { + const int local_failed = local_error == MPI_SUCCESS ? 0 : 1; + int any_failed = 0; + if (MPI_Allreduce(&local_failed, &any_failed, 1, MPI_INT, MPI_MAX, mpi_comm) != MPI_SUCCESS || any_failed != 0) + { + throw std::runtime_error(context); + } + }; + std::vector output_basis_counts(mpi_size, nbasis / mpi_size); + for (int ip = 0; ip < nbasis % mpi_size; ++ip) + { + ++output_basis_counts[ip]; + } + std::vector output_basis_offsets(mpi_size + 1, 0); + std::vector output_basis_owner(nbasis); + for (int ip = 0; ip < mpi_size; ++ip) + { + output_basis_offsets[ip + 1] = output_basis_offsets[ip] + output_basis_counts[ip]; + std::fill(output_basis_owner.begin() + output_basis_offsets[ip], + output_basis_owner.begin() + output_basis_offsets[ip + 1], + ip); + } + const int local_nw = output_basis_counts[mpi_rank]; +#else + const int mpi_rank = 0; + const int local_nw = nbasis; +#endif + const std::size_t local_size = static_cast(local_nw) * values_per_iw; + std::vector> local_wfc(local_size); +#ifdef __MPI + struct WfcPackIndex + { + int spin; + int band; + int basis; + }; + + const int local_band_count = parav.ncol_bands; + const unsigned long long send_size_wide + = static_cast(local_band_count) * psi.get_nbasis() * npsin_tmp; + const unsigned long long recv_size_wide = static_cast(local_nw) * values_per_iw; + const unsigned long long max_alltoallv_count = std::numeric_limits::max(); + check_mpi(send_size_wide <= max_alltoallv_count && recv_size_wide <= max_alltoallv_count ? MPI_SUCCESS + : MPI_ERR_COUNT, + "RPA eigenvector buffer exceeds MPI_Alltoallv count range."); + + std::vector send_counts(mpi_size, 0); + for (int ib = 0; ib < nbands; ++ib) + { + if (parav.global2local_col(ib) < 0) + { + continue; + } + for (int ir = 0; ir < psi.get_nbasis(); ++ir) + { + send_counts[output_basis_owner[parav.local2global_row(ir)]] += npsin_tmp; + } + } + std::vector send_displacements(mpi_size, 0); + std::vector recv_counts(mpi_size); + std::vector recv_displacements(mpi_size, 0); + for (int ip = 1; ip < mpi_size; ++ip) + { + send_displacements[ip] = send_displacements[ip - 1] + send_counts[ip - 1]; + } + check_mpi(MPI_Alltoall(send_counts.data(), 1, MPI_INT, recv_counts.data(), 1, MPI_INT, mpi_comm), + "Failed to exchange RPA eigenvector redistribution counts."); + for (int ip = 1; ip < mpi_size; ++ip) + { + recv_displacements[ip] = recv_displacements[ip - 1] + recv_counts[ip - 1]; + } + const int send_size = static_cast(send_size_wide); + const int recv_size = static_cast(recv_size_wide); + check_mpi(recv_displacements.back() + recv_counts.back() == recv_size ? MPI_SUCCESS : MPI_ERR_COUNT, + "RPA eigenvector Alltoallv redistribution map is inconsistent."); + + std::vector pack_indices(send_size); + std::vector send_targets(send_size); + std::vector recv_targets(recv_size); + std::vector send_positions = send_displacements; + for (int ib = 0; ib < nbands; ++ib) + { + const int ib_local = parav.global2local_col(ib); + if (ib_local < 0) + { + continue; + } + for (int ir = 0; ir < psi.get_nbasis(); ++ir) + { + const int iw = parav.local2global_row(ir); + const int destination = output_basis_owner[iw]; + for (int is = 0; is < npsin_tmp; ++is) + { + const int position = send_positions[destination]++; + pack_indices[position] = {is, ib_local, ir}; + send_targets[position] + = static_cast(iw - output_basis_offsets[destination]) * values_per_iw + + ib * npsin_tmp + is; + } + } + } + std::complex dummy(0.0, 0.0); + unsigned long long dummy_target = 0; + check_mpi(MPI_Alltoallv(send_size > 0 ? send_targets.data() : &dummy_target, + send_counts.data(), + send_displacements.data(), + MPI_UNSIGNED_LONG_LONG, + recv_size > 0 ? recv_targets.data() : &dummy_target, + recv_counts.data(), + recv_displacements.data(), + MPI_UNSIGNED_LONG_LONG, + mpi_comm), + "Failed to exchange RPA eigenvector destination indices."); + std::vector> send_wfc(send_size); + std::vector> recv_wfc(recv_size); +#endif + std::string output_buffer; + constexpr std::size_t output_line_bytes = 61; + output_buffer.reserve(local_size * output_line_bytes + 32); + + const std::string filename = this->runtime.input.rpa_outdir + "KS_eigenvector.txt"; +#ifdef __MPI + MPI_File file = MPI_FILE_NULL; + unsigned long long total_file_bytes = 0; + const unsigned long long max_mpi_offset = static_cast(std::numeric_limits::max()); + const char dummy_buffer = '\0'; + check_mpi(MPI_File_open(mpi_comm, filename.c_str(), MPI_MODE_CREATE | MPI_MODE_WRONLY, MPI_INFO_NULL, &file), + "Failed to open " + filename + "."); + check_mpi(MPI_File_set_size(file, 0), "Failed to truncate " + filename + "."); +#else + std::ofstream ofs(filename.c_str(), std::ios::out); +#endif + + for (int ik = 0; ik < nks_tot; ik++) + { +#ifdef __MPI + // 4.1 Pack and redistribute all bands and spins for this k point. + for (int index = 0; index < send_size; ++index) + { + const WfcPackIndex& source = pack_indices[index]; + send_wfc[index] = psi(ik + nks_tot * source.spin, source.band, source.basis); + } + check_mpi(MPI_Alltoallv(send_size > 0 ? send_wfc.data() : &dummy, + send_counts.data(), + send_displacements.data(), + MPI_DOUBLE_COMPLEX, + recv_size > 0 ? recv_wfc.data() : &dummy, + recv_counts.data(), + recv_displacements.data(), + MPI_DOUBLE_COMPLEX, + mpi_comm), + "Failed to redistribute RPA eigenvectors with MPI_Alltoallv."); + for (int index = 0; index < recv_size; ++index) + { + local_wfc[static_cast(recv_targets[index])] = recv_wfc[index]; + } +#else + for (int is = 0; is < npsin_tmp; is++) + { + for (int ib = 0; ib < nbands; ++ib) + { + for (int iw = 0; iw < psi.get_nbasis(); ++iw) + { + local_wfc[iw * values_per_iw + ib * npsin_tmp + is] = psi(ik + nks_tot * is, ib, iw); + } + } + } +#endif + // 4.2 Format this rank's contiguous basis interval in basis-band-spin order. + output_buffer.clear(); + if (mpi_rank == 0) + { + output_buffer += std::to_string(ik + 1); + output_buffer += '\n'; + } + char line_buffer[128]; + for (int iw = 0; iw < local_nw; ++iw) + { + for (int ib = 0; ib < nbands; ++ib) + { + for (int is = 0; is < npsin_tmp; is++) + { + const std::complex& value + = local_wfc[static_cast(iw) * values_per_iw + ib * npsin_tmp + is]; + const int line_length = std::snprintf(line_buffer, + sizeof(line_buffer), + "%30.15f%30.15f\n", + value.real(), + value.imag()); + if (line_length != static_cast(output_line_bytes)) + { + throw std::runtime_error("Failed to format an RPA eigenvector value."); + } + output_buffer.append(line_buffer, static_cast(line_length)); + } + } + } + +#ifdef __MPI + // 4.3 Fixed-width value lines make each rank's file offset deterministic; + // only the k-point header length varies. + const unsigned long long local_bytes = static_cast(output_buffer.size()); + const unsigned long long header_bytes = std::to_string(ik + 1).size() + 1; + const unsigned long long bytes_per_basis = values_per_iw * output_line_bytes; + if (bytes_per_basis > (max_mpi_offset - header_bytes) / std::max(nbasis, 1) + || header_bytes + static_cast(nbasis) * bytes_per_basis + > max_mpi_offset - total_file_bytes) + { + throw std::runtime_error("RPA eigenvector file layout exceeds MPI_Offset range."); + } + const unsigned long long kpoint_bytes + = header_bytes + static_cast(nbasis) * bytes_per_basis; + const unsigned long long rank_offset_bytes + = mpi_rank == 0 + ? 0 + : header_bytes + static_cast(output_basis_offsets[mpi_rank]) * bytes_per_basis; + const unsigned long long file_offset_bytes = total_file_bytes + rank_offset_bytes; + // Rank 0 has the largest basis slice and additionally owns the k-point header. + const unsigned long long max_local_bytes + = header_bytes + static_cast(output_basis_counts[0]) * bytes_per_basis; + const unsigned long long max_write_count = static_cast(std::numeric_limits::max()); + // MPI_File_write_at_all is collective, so every rank executes the same + // number of chunks; ranks without data in a chunk pass a zero count. + for (unsigned long long buffer_offset = 0; buffer_offset < max_local_bytes; buffer_offset += max_write_count) + { + const unsigned long long remaining = local_bytes > buffer_offset ? local_bytes - buffer_offset : 0; + const int write_count = static_cast(std::min(remaining, max_write_count)); + const char* write_buffer + = write_count > 0 ? output_buffer.data() + static_cast(buffer_offset) : &dummy_buffer; + const unsigned long long write_offset + = write_count > 0 ? file_offset_bytes + buffer_offset : file_offset_bytes; + check_mpi(MPI_File_write_at_all(file, + static_cast(write_offset), + write_buffer, + write_count, + MPI_CHAR, + MPI_STATUS_IGNORE), + "Failed to write " + filename + "."); + } + total_file_bytes += kpoint_bytes; +#else + ofs << output_buffer; +#endif + } // ik +#ifdef __MPI + check_mpi(MPI_File_sync(file), "Failed to sync " + filename + "."); + check_mpi(MPI_File_close(&file), "Failed to close " + filename + "."); +#endif +} + +template void RPA_LRI::out_eigen_vector_legacy(const Parallel_Orbitals& parav, + const psi::Psi& psi); +template void RPA_LRI, double>::out_eigen_vector_legacy(const Parallel_Orbitals& parav, + const psi::Psi>& psi); diff --git a/source/source_lcao/module_ri/rpa_lri_eigenvector_v1.cpp b/source/source_lcao/module_ri/rpa_lri_eigenvector_v1.cpp new file mode 100644 index 00000000000..bf7be7b2b67 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_eigenvector_v1.cpp @@ -0,0 +1,46 @@ +#include "exx_lri.h" +#include "rpa_lri.h" +#include "rpa_lri_detail.h" +#include "source_base/global_function.h" +#include "source_basis/module_ao/elem_basis_idx_orb.h" +#include "source_estate/elecstate_lcao.h" +#include "source_io/module_parameter/input_parameter.h" +#include "source_lcao/module_ri/module_exx_symmetry/symm_rotation.h" + +#include + +template +void RPA_LRI::out_eigen_vector_v1(const Parallel_Orbitals& parav, const psi::Psi& psi) +{ +#ifdef __MPI + const int nks_tot = this->runtime.input.nspin == 2 ? p_kv->get_nks() / 2 : p_kv->get_nks(); + const int npsin_tmp = this->runtime.input.nspin == 2 ? 2 : 1; + ModuleBase::timer::start("RPA_LRI", "out_eigen_vector_v1_mpi_io"); + const MPI_Comm io_comm = parav.comm(); + if (io_comm == MPI_COMM_NULL) + { + RpaLriDetail::abort_output(this->mpi_comm, "KS eigenvector MPI-IO writer has no wavefunction communicator."); + } + int communicator_relation = MPI_UNEQUAL; + RpaLriDetail::collective_mpi_check(io_comm, + MPI_Comm_compare(io_comm, this->mpi_comm, &communicator_relation), + "Failed to compare KS eigenvector MPI communicators"); + RpaLriDetail::collective_require(io_comm, + communicator_relation == MPI_IDENT || communicator_relation == MPI_CONGRUENT, + "KS eigenvector wavefunction and RPA communicators are inconsistent"); + RpaLriDetail::write_ks_eigenvector_v1_mpi(io_comm, + parav, + psi, + nks_tot, + npsin_tmp, + this->runtime.input.nspin, + this->runtime.input.nbands, + this->runtime.nlocal, + this->runtime.input.rpa_outdir + "KS_wfc_0.dat"); + ModuleBase::timer::end("RPA_LRI", "out_eigen_vector_v1_mpi_io"); +#endif +} + +template void RPA_LRI::out_eigen_vector_v1(const Parallel_Orbitals& parav, const psi::Psi& psi); +template void RPA_LRI, double>::out_eigen_vector_v1(const Parallel_Orbitals& parav, + const psi::Psi>& psi); diff --git a/source/source_lcao/module_ri/rpa_lri_mpi_layout.cpp b/source/source_lcao/module_ri/rpa_lri_mpi_layout.cpp new file mode 100644 index 00000000000..ec8b8d195aa --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_mpi_layout.cpp @@ -0,0 +1,235 @@ +#include "rpa_lri_detail.h" +#include "source_basis/module_ao/parallel_orbitals.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __MPI +namespace RpaLriDetail +{ +std::string mpi_error_string(const int error_code) +{ + char error_buffer[MPI_MAX_ERROR_STRING] = {}; + int error_length = 0; + if (MPI_Error_string(error_code, error_buffer, &error_length) != MPI_SUCCESS) + { + return "MPI error " + std::to_string(error_code); + } + return std::string(error_buffer, static_cast(error_length)); +} + +void collective_mpi_check(const MPI_Comm mpi_comm, const int local_error, const std::string& context) +{ + int mpi_rank = 0; + MPI_Comm_rank(mpi_comm, &mpi_rank); + const int no_failure = std::numeric_limits::max(); + const int local_failed_rank = local_error == MPI_SUCCESS ? no_failure : mpi_rank; + int first_failed_rank = no_failure; + const int reduce_error = MPI_Allreduce(&local_failed_rank, &first_failed_rank, 1, MPI_INT, MPI_MIN, mpi_comm); + if (reduce_error != MPI_SUCCESS) + { + throw std::runtime_error(context + + ": failed to synchronize MPI error status: " + mpi_error_string(reduce_error)); + } + if (first_failed_rank == no_failure) + { + return; + } + + int shared_error = mpi_rank == first_failed_rank ? local_error : MPI_SUCCESS; + const int broadcast_error = MPI_Bcast(&shared_error, 1, MPI_INT, first_failed_rank, mpi_comm); + if (broadcast_error != MPI_SUCCESS) + { + throw std::runtime_error(context + + ": failed to broadcast MPI error status: " + mpi_error_string(broadcast_error)); + } + throw std::runtime_error(context + ": " + mpi_error_string(shared_error)); +} + +void collective_require(const MPI_Comm mpi_comm, const bool local_condition, const std::string& context) +{ + int mpi_rank = 0; + MPI_Comm_rank(mpi_comm, &mpi_rank); + const int no_failure = std::numeric_limits::max(); + const int local_failed_rank = local_condition ? no_failure : mpi_rank; + int first_failed_rank = no_failure; + const int reduce_error = MPI_Allreduce(&local_failed_rank, &first_failed_rank, 1, MPI_INT, MPI_MIN, mpi_comm); + if (reduce_error != MPI_SUCCESS) + { + throw std::runtime_error(context + + ": failed to synchronize validation status: " + mpi_error_string(reduce_error)); + } + if (first_failed_rank != no_failure) + { + throw std::runtime_error(context + " (first failing MPI rank " + std::to_string(first_failed_rank) + ")."); + } +} + +MPI_Aint checked_mpi_aint_from_u64(const unsigned long long value, const std::string& context) +{ + if (value > static_cast(std::numeric_limits::max())) + { + throw std::runtime_error(context + " exceeds MPI_Aint range."); + } + return static_cast(value); +} + +MPI_Offset checked_mpi_offset_from_u64(const unsigned long long value, const std::string& context) +{ + if (value > static_cast(std::numeric_limits::max())) + { + throw std::runtime_error(context + " exceeds MPI_Offset range."); + } + return static_cast(value); +} +KSEigenvectorMpiLayout make_ks_eigenvector_mpi_layout(const MPI_Comm mpi_comm, + const Parallel_Orbitals& parav, + const int nbands, + const int nbasis_wfc, + const bool is_soc, + const int spinor_component) +{ + KSEigenvectorMpiLayout layout; + std::vector block_lengths; + std::vector block_displacements; + bool local_valid = nbands >= 0 && nbasis_wfc >= 0 && parav.ncol_bands >= 0 + && parav.ncol_bands <= parav.get_col_size() && spinor_component >= 0 + && spinor_component < (is_soc ? 2 : 1); + const int spatial_basis = is_soc ? nbasis_wfc / 2 : nbasis_wfc; + + try + { + bool have_run = false; + unsigned long long run_first_index = 0; + unsigned long long previous_index = 0; + int run_length = 0; + + const auto flush_run = [&]() { + if (!have_run) + { + return; + } + const unsigned long long byte_displacement + = checked_mul_u64(run_first_index, + static_cast(sizeof(std::complex)), + "KS eigenvector MPI file displacement"); + block_lengths.push_back(run_length); + block_displacements.push_back( + checked_mpi_aint_from_u64(byte_displacement, "KS eigenvector MPI file displacement")); + have_run = false; + run_length = 0; + }; + + for (int ib_local = 0; local_valid && ib_local < parav.ncol_bands; ++ib_local) + { + const int ib_global = parav.local2global_col(ib_local); + for (int ir_local = 0; ir_local < parav.get_row_size(); ++ir_local) + { + const int iw_global = parav.local2global_row(ir_local); + if (is_soc && iw_global % 2 != spinor_component) + { + continue; + } + + const int iw_file = is_soc ? iw_global / 2 : iw_global; + const unsigned long long band_offset = checked_mul_u64(static_cast(ib_global), + static_cast(spatial_basis), + "KS eigenvector MPI file index"); + const unsigned long long file_index = checked_add_u64(band_offset, + static_cast(iw_file), + "KS eigenvector MPI file index"); + if (!have_run) + { + have_run = true; + run_first_index = file_index; + previous_index = file_index; + run_length = 1; + } + else if (file_index == previous_index + 1 && run_length < std::numeric_limits::max()) + { + ++run_length; + previous_index = file_index; + } + else + { + flush_run(); + have_run = true; + run_first_index = file_index; + previous_index = file_index; + run_length = 1; + } + layout.local_count = checked_add_u64(layout.local_count, 1, "Local KS eigenvector MPI element count"); + } + } + flush_run(); + } + catch (const std::exception&) + { + local_valid = false; + } + + local_valid = local_valid && block_lengths.size() <= static_cast(std::numeric_limits::max()); + collective_require(mpi_comm, local_valid, "Invalid local KS eigenvector MPI layout"); + + unsigned long long global_count = 0; + collective_mpi_check( + mpi_comm, + MPI_Allreduce(&layout.local_count, &global_count, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, mpi_comm), + "Failed to validate KS eigenvector MPI ownership"); + const unsigned long long expected_count = checked_mul_u64(static_cast(nbands), + static_cast(spatial_basis), + "Global KS eigenvector MPI element count"); + collective_require(mpi_comm, + global_count == expected_count, + "KS eigenvector MPI ownership does not cover the global matrix exactly"); + + collective_mpi_check( + mpi_comm, + MPI_Allreduce(&layout.local_count, &layout.max_local_count, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, mpi_comm), + "Failed to determine the KS eigenvector MPI chunk count"); + + int type_create_error = MPI_SUCCESS; + if (layout.local_count > 0) + { + type_create_error = MPI_Type_create_hindexed(static_cast(block_lengths.size()), + block_lengths.data(), + block_displacements.data(), + MPI_C_DOUBLE_COMPLEX, + &layout.filetype); + } + collective_mpi_check(mpi_comm, type_create_error, "Failed to create KS eigenvector MPI file type"); + + int type_commit_error = MPI_SUCCESS; + if (layout.local_count > 0) + { + type_commit_error = MPI_Type_commit(&layout.filetype); + } + collective_mpi_check(mpi_comm, type_commit_error, "Failed to commit KS eigenvector MPI file type"); + layout.free_filetype = layout.local_count > 0; + return layout; +} + +int free_ks_eigenvector_mpi_layout(KSEigenvectorMpiLayout& layout) +{ + if (!layout.free_filetype) + { + return MPI_SUCCESS; + } + layout.free_filetype = false; + return MPI_Type_free(&layout.filetype); +} +} // namespace RpaLriDetail +#endif diff --git a/source/source_lcao/module_ri/rpa_lri_output.cpp b/source/source_lcao/module_ri/rpa_lri_output.cpp new file mode 100644 index 00000000000..5b3289ba16d --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_output.cpp @@ -0,0 +1,161 @@ +#include "exx_lri.h" +#include "rpa_lri.h" +#include "rpa_lri_band_io.h" +#include "rpa_lri_detail.h" +#include "source_base/global_function.h" +#include "source_basis/module_ao/elem_basis_idx_orb.h" +#include "source_estate/elecstate_lcao.h" +#include "source_io/module_parameter/input_parameter.h" +#include "source_lcao/module_ri/module_exx_symmetry/symm_rotation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +void RPA_LRI::out_struc(const UnitCell& ucell) +try +{ + if (this->runtime.rank != 0) + { + return; + } + ModuleBase::TITLE("DFT_RPA_interface", "out_struc"); + const int nks_tot = this->runtime.input.nspin == 2 ? (int)p_kv->get_nks() / 2 : p_kv->get_nks(); + const ModuleBase::Matrix3 lat = ucell.latvec * ucell.lat0; // in unit of Bohr + const ModuleBase::Matrix3 G_RPA = ucell.G * (ModuleBase::TWO_PI / ucell.lat0); // in unit of 1/Bohr + std::ofstream ofs; + ofs.open(this->runtime.input.rpa_outdir + "stru_out.txt", std::ios::out); + if (!ofs.good()) throw std::runtime_error("Failed to open stru_out.txt"); + ofs << std::fixed << std::setprecision(9); + ofs << lat.e11 << std::setw(15) << lat.e12 << std::setw(15) << lat.e13 << std::endl; + ofs << lat.e21 << std::setw(15) << lat.e22 << std::setw(15) << lat.e23 << std::endl; + ofs << lat.e31 << std::setw(15) << lat.e32 << std::setw(15) << lat.e33 << std::endl; + + ofs << G_RPA.e11 << std::setw(15) << G_RPA.e12 << std::setw(15) << G_RPA.e13 << std::endl; + ofs << G_RPA.e21 << std::setw(15) << G_RPA.e22 << std::setw(15) << G_RPA.e23 << std::endl; + ofs << G_RPA.e31 << std::setw(15) << G_RPA.e32 << std::setw(15) << G_RPA.e33 << std::endl; + + ofs << ucell.nat << std::endl; + for (int it = 0; it < ucell.ntype; it++) + { + for (int ia = 0; ia < ucell.atoms[it].na; ia++) + { + const ModuleBase::Vector3 position = ucell.atoms[it].tau[ia] * ucell.lat0; // in unit of Bohr + ofs << std::setw(15) << position.x << std::setw(15) << position.y << std::setw(15) << position.z + << std::setw(15) << it + 1 << std::endl; + } + } + + // Reader-v1 stores k-point sampling in bz_sample.txt. Keep the legacy + // k-point block in stru_out.txt for reader-v0 consumers. + if (this->runtime.input.out_librpa_ver != 1) + { + ofs << p_kv->nmp[0] << std::setw(6) << p_kv->nmp[1] << std::setw(6) << p_kv->nmp[2] << std::setw(6) + << std::endl; + + for (int ik = 0; ik != nks_tot; ik++) + { + const ModuleBase::Vector3 kpoint + = p_kv->kvec_c[ik] * (ModuleBase::TWO_PI / ucell.lat0); // in unit of 1/Bohr + ofs << std::setw(15) << kpoint.x << std::setw(15) << kpoint.y << std::setw(15) << kpoint.z << std::endl; + } + if (this->runtime.input.symmetry == "-1") + { + for (int ik = 0; ik != nks_tot; ++ik) + { + ofs << (ik + 1) << std::endl; + } + } + } + // LibRPA reconstructs spatial k-stars and auxiliary-basis rotations from + // the operations in the input cell's fractional row-vector convention. + // This block belongs only to the reader-v1 structure contract; v0 keeps + // the historical stru_out layout unchanged. + if (this->runtime.input.out_librpa_ver == 1 && this->runtime.input.symmetry == "1" + && this->runtime.input.nspin != 4) + { + RpaLriDetail::write_stru_sym(ofs, ucell, *p_kv); + } + ofs.close(); + if (!ofs.good()) throw std::runtime_error("Failed to write stru_out.txt"); + return; +} +catch (const std::exception& error) +{ + RpaLriDetail::abort_output(this->mpi_comm, error.what()); +} +catch (...) +{ + RpaLriDetail::abort_output(this->mpi_comm, "Unknown exception in out_struc"); +} + +template +void RPA_LRI::out_bands(const elecstate::ElecState* pelec) +try +{ + ModuleBase::TITLE("DFT_RPA_interface", "out_bands"); + if (this->runtime.rank != 0) + { + return; + } + const int nks_tot = this->runtime.input.nspin == 2 ? (int)p_kv->get_nks() / 2 : p_kv->get_nks(); + const int nspin_tmp = this->runtime.input.nspin == 2 ? 2 : 1; + RpaLriDetail::BandOutputData data; + data.nk = nks_tot; + data.nspin = nspin_tmp; + data.nbands = this->runtime.input.nbands; + data.nbasis = this->runtime.nlocal; + data.fermi_ry = pelec->eferm.ef; + data.samples.reserve(static_cast(nks_tot) * static_cast(nspin_tmp) + * static_cast(this->runtime.input.nbands)); + for (int ik = 0; ik != nks_tot; ik++) + { + for (int is = 0; is != nspin_tmp; is++) + { + for (int ib = 0; ib != this->runtime.input.nbands; ib++) + { + RpaLriDetail::BandSample sample; + sample.occupation = pelec->wg(ik + is * nks_tot, ib) * nks_tot; + sample.energy_ry = pelec->ekb(ik + is * nks_tot, ib); + data.samples.push_back(sample); + } + } + } + std::ofstream ofs(this->runtime.input.rpa_outdir + "band_out.txt", std::ios::out | std::ios::trunc); + if (!ofs.good()) + { + throw std::runtime_error("Failed to open band_out.txt"); + } + RpaLriDetail::write_band_output(ofs, data); + ofs.close(); + if (!ofs.good()) + { + throw std::runtime_error("Failed to write band_out.txt"); + } +} +catch (const std::exception& error) +{ + RpaLriDetail::abort_output(this->mpi_comm, error.what()); +} +catch (...) +{ + RpaLriDetail::abort_output(this->mpi_comm, "Unknown exception in out_bands"); +} + +template void RPA_LRI::out_struc(const UnitCell& ucell); +template void RPA_LRI, double>::out_struc(const UnitCell& ucell); +template void RPA_LRI::out_bands(const elecstate::ElecState* pelec); +template void RPA_LRI, double>::out_bands(const elecstate::ElecState* pelec); diff --git a/source/source_lcao/module_ri/rpa_lri_overlap.cpp b/source/source_lcao/module_ri/rpa_lri_overlap.cpp new file mode 100644 index 00000000000..1bb5e27ad6f --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_overlap.cpp @@ -0,0 +1,430 @@ +#include "exx_lri.h" +#include "rpa_lri.h" +#include "rpa_lri_detail.h" +#include "source_base/global_function.h" +#include "source_basis/module_ao/elem_basis_idx_orb.h" +#include "source_estate/elecstate_lcao.h" +#include "source_io/module_parameter/input_parameter.h" +#include "source_lcao/module_ri/module_exx_symmetry/symm_rotation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +void RPA_LRI::cal_large_Cs(const UnitCell& ucell, const LCAO_Orbitals& orb, const K_Vectors& kv) +{ + ModuleBase::TITLE("RPA_LRI", "cal_large_Cs"); + ModuleBase::timer::start("RPA_LRI", "cal_large_Cs"); + if (!exx_cut_coulomb) + { + Exx_Info_RI local_info = this->info; + local_info.ccp_rmesh_times = this->ccp_rmesh_times_cut; + Exx_LRI* new_exx = new Exx_LRI(local_info); + exx_cut_coulomb.reset(new_exx); + } + exx_cut_coulomb->init_spencer(this->mpi_comm, ucell, kv, orb); + ModuleBase::GlobalFunc::DONE(this->runtime.log, "exx_cut_coulomb->init"); + this->abfs = exx_cut_coulomb->abfs; + this->MGT = exx_cut_coulomb->MGT; + std::vector atoms(ucell.nat); + for (int iat = 0; iat < ucell.nat; ++iat) + { + atoms[iat] = iat; + } + std::map atoms_pos; + for (int iat = 0; iat < ucell.nat; ++iat) + atoms_pos[iat] = RI_Util::Vector3_to_array3(ucell.atoms[ucell.iat2it[iat]].tau[ucell.iat2ia[iat]]); + const std::array latvec = {RI_Util::Vector3_to_array3(ucell.a1), + RI_Util::Vector3_to_array3(ucell.a2), + RI_Util::Vector3_to_array3(ucell.a3)}; + const std::array period = {p_kv->nmp[0], p_kv->nmp[1], p_kv->nmp[2]}; + this->exx_cut_coulomb->exx_lri.set_parallel(this->mpi_comm, atoms_pos, latvec, period); + auto center2_obj_it = this->exx_cut_coulomb->exx_objs.find(Conv_Coulomb_Pot_K::Coulomb_Method::Center2); + if (center2_obj_it == this->exx_cut_coulomb->exx_objs.end()) + { + throw std::invalid_argument("RPA_LRI::cal_large_Cs expected a Center2 cut-Coulomb object after init_spencer."); + } + center2_obj_it->second.cv.set_orbitals(ucell, + orb, + this->lcaos, + exx_cut_coulomb->abfs, + center2_obj_it->second.abfs_ccp, + this->info.kmesh_times, + this->MGT, // get MGT from exx_cut_coulomb and used in `cal_abfs_overlap` + true); + + const std::array period_Vs + = LRI_CV_Tools::cal_latvec_range(1 + this->ccp_rmesh_times_cut, ucell, orb_cutoff_); + std::pair, std::vector>>>> list_As_Vs + = RI::Distribute_Equally::distribute_atoms_periods(this->mpi_comm, atoms, period_Vs, 2, false); + ModuleBase::GlobalFunc::DONE(this->runtime.log, "cal_large_Vs start"); + std::map>> Vs_cut_IJR + = center2_obj_it->second.cv.cal_Vs(ucell, list_As_Vs.first, list_As_Vs.second[0], {{"writable_Vws", true}}); + ModuleBase::GlobalFunc::DONE(this->runtime.log, "cal_large_Vs end"); + + const std::array period_Cs = LRI_CV_Tools::cal_latvec_range(2, ucell, orb_cutoff_); + const std::pair, std::vector>>>> list_As_Cs + = RI::Distribute_Equally::distribute_atoms_periods(this->mpi_comm, atoms, period_Cs, 2, false); + std::pair>>, + std::map, 3>>>> + Cs_dCs = center2_obj_it->second.cv.cal_Cs_dCs(ucell, + list_As_Cs.first, + list_As_Cs.second[0], + {{"cal_dC", false}, + {"writable_Cws", true}, + {"writable_dCws", true}, + {"writable_Vws", false}, + {"writable_dVws", false}}); + ModuleBase::GlobalFunc::DONE(this->runtime.log, "cal_large_Cs"); + + std::map>> tmp; + if (this->runtime.input.out_unshrinked_v) + { + this->Vs_period = RI::RI_Tools::cal_period(Vs_cut_IJR, period); + Vs_cut_IJR.clear(); + ModuleBase::GlobalFunc::DONE(this->runtime.log, "Vs_period"); + // MPI: {ia0, {ia1, R}} to {ia0, ia1} + const std::pair, std::vector>>> list_As_Vs_atoms + = RI::Distribute_Equally::distribute_atoms(this->mpi_comm, atoms, period_Vs, 2, false); + const auto list_A0_pair_R = list_As_Vs_atoms.first; + const auto list_A1_pair_R = list_As_Vs_atoms.second[0]; + std::set atoms00; + std::set atoms01; + for (const auto& I: list_A0_pair_R) + { + atoms00.insert(I); + } + for (const auto& JR: list_A1_pair_R) + { + atoms01.insert(JR.first); + } + + this->Vs_period = RI_2D_Comm::comm_map2_first(this->mpi_comm, this->Vs_period, atoms00, atoms01); + ModuleBase::GlobalFunc::DONE(this->runtime.log, "Vs_period_comm"); + + Exx_LRI* cut_coulomb = exx_cut_coulomb.get(); + this->out_coulomb_k(ucell, this->Vs_period, "coulomb_unshrinked_cut_", cut_coulomb); + ModuleBase::GlobalFunc::DONE(this->runtime.log, "out_large_Vs"); + this->Vs_period.clear(); + this->Vs_period.swap(tmp); + } + + std::map>>& Cs = std::get<0>(Cs_dCs); + this->Cs_period = RI::RI_Tools::cal_period(Cs, period); + this->Cs_period = exx_cut_coulomb->exx_lri.post_2D.set_tensors_map2(this->Cs_period); + if (this->runtime.input.out_librpa_ver == 1) + { + // The large auxiliary basis and the canonical fallback map are emitted + // here; the final Ewald output adds only aux_basis_s.txt. + this->out_librpa_basis_v1(ucell, exx_cut_coulomb.get(), "aux_basis.txt", "basis_map.txt", true); + this->out_Cs_v1(ucell, this->Cs_period, "Cs_"); + } + else + { + this->out_Cs(ucell, this->Cs_period, "Cs_data_"); + } + ModuleBase::GlobalFunc::DONE(this->runtime.log, "out_large_Cs"); + this->Cs_period.clear(); + this->Cs_period.swap(tmp); + exx_cut_coulomb.reset(); + RpaLriDetail::trim_malloc_cache(); + + ModuleBase::timer::end("RPA_LRI", "cal_large_Cs"); +} + +template +void RPA_LRI::cal_abfs_overlap(const UnitCell& ucell, const LCAO_Orbitals& orb, const K_Vectors& kv) +{ + ModuleBase::TITLE("DFT_RPA_interface", "cal_abfs_overlap"); + const auto& abfs_s = this->abfs_shrink; + + // + Matrix_Orbs11 m_abfs_abfs; + // + Matrix_Orbs11 m_abfs_abf; + + m_abfs_abf.MGT = this->MGT; + m_abfs_abf.init(abfs_s, this->abfs, ucell, orb, this->info.kmesh_times); + m_abfs_abf.init_radial_table(); + + m_abfs_abfs.MGT = this->MGT; + m_abfs_abfs.init(abfs_s, abfs_s, ucell, orb, this->info.kmesh_times); + m_abfs_abfs.init_radial_table(); + // get Rlist + const std::array period = RI_Util::get_Born_vonKarmen_period(kv); + const auto R_period = RI_Util::get_Born_von_Karmen_cells(period); + std::map>> overlap_abfs_abfs; + std::map>> overlap_abfs_abf; + + // index of smaller abfs + const ModuleBase::Element_Basis_Index::Range range_abfs_s + = ModuleBase::Element_Basis_Index::construct_range(abfs_s); + const ModuleBase::Element_Basis_Index::IndexLNM index_abfs_s + = ModuleBase::Element_Basis_Index::construct_index(range_abfs_s); + // index of larger abfs + const ModuleBase::Element_Basis_Index::Range range_abfs + = ModuleBase::Element_Basis_Index::construct_range(this->abfs); + const ModuleBase::Element_Basis_Index::IndexLNM index_abfs + = ModuleBase::Element_Basis_Index::construct_index(range_abfs); + + auto orb_cutoff_ = orb.cutoffs(); + const std::array period_Vs = LRI_CV_Tools::cal_latvec_range(2, ucell, orb_cutoff_); + std::vector atoms(ucell.nat); + for (int iat = 0; iat < ucell.nat; ++iat) + atoms[iat] = iat; + const std::pair, std::vector>>>> list_As_Vs + = RI::Distribute_Equally::distribute_atoms_periods(this->mpi_comm, atoms, period_Vs, 2, false); + +// Huanjing Gong debug +// std::stringstream ss; +// ss << "IJR_" << this->runtime.rank << ".txt"; +// std::ofstream ofs; +// ofs.open(ss.str().c_str(), std::ios::out); +// for (size_t iA = 0; iA < list_As_Vs.first.size(); ++iA) +// { +// const auto& A = list_As_Vs.first[iA]; +// for (const auto& BR: list_As_Vs.second[0]) +// { +// const auto& B = BR.first; +// const auto& R = BR.second; +// ofs << "ABR: " << A << B << "," << R.at(0) << R.at(1) << R.at(2) << std::endl; +// } +// } +// ofs.close(); +#pragma omp parallel + { + using LocalMapType = std::map>, RI::Tensor>; + std::map overlap_abfs_abfs_local; + std::map overlap_abfs_abf_local; + +#pragma omp for schedule(dynamic) nowait + for (size_t iA = 0; iA < list_As_Vs.first.size(); ++iA) + { + const auto& A = list_As_Vs.first[iA]; + for (const auto& BR: list_As_Vs.second[0]) + { + const auto& B = BR.first; + const auto& R = BR.second; + + const size_t TA = ucell.iat2it[A]; + const size_t IA = ucell.iat2ia[A]; + const auto& tauA = ucell.atoms[TA].tau[IA]; + const size_t TB = ucell.iat2it[B]; + const size_t IB = ucell.iat2ia[B]; + const auto& tauB = ucell.atoms[TB].tau[IB]; + + const ModuleBase::Vector3 tauB_shift = tauB + (RI_Util::array3_to_Vector3(R) * ucell.latvec); + const ModuleBase::Vector3 tau_delta = tauB_shift - tauA; + static const ModuleBase::Vector3 tau0(0.0, 0.0, 0.0); + + auto& local_abfs_abfs = overlap_abfs_abfs_local[A]; + local_abfs_abfs[{B, R}] + = m_abfs_abfs.template cal_overlap_matrix(TA, + TB, + tau0, + tau_delta, + index_abfs_s, + index_abfs_s, + Matrix_Orbs11::Matrix_Order::AB); + + auto& local_abfs_abf = overlap_abfs_abf_local[A]; + local_abfs_abf[{B, R}] + = m_abfs_abf.template cal_overlap_matrix(TA, + TB, + tau0, + tau_delta, + index_abfs_s, + index_abfs, + Matrix_Orbs11::Matrix_Order::AB); + } + } + +#pragma omp critical(RPA_LRI_merge) + { + for (auto& aPair: overlap_abfs_abfs_local) + { + auto& aKey = aPair.first; + auto& aSubMap = aPair.second; + for (auto& subPair: aSubMap) + { + auto& key = subPair.first; + auto& value = subPair.second; + overlap_abfs_abfs[aKey][key] = std::move(value); + } + } + for (auto& aPair: overlap_abfs_abf_local) + { + auto& aKey = aPair.first; + auto& aSubMap = aPair.second; + for (auto& subPair: aSubMap) + { + auto& key = subPair.first; + auto& value = subPair.second; + overlap_abfs_abf[aKey][key] = std::move(value); + } + } + } + } + // MPI: {ia0, {ia1, R}} to {ia0, ia1} + const std::array period_Vs_IJ = LRI_CV_Tools::cal_latvec_range(2, ucell, orb_cutoff_); + const std::pair, std::vector>>> list_As_Vs_atoms + = RI::Distribute_Equally::distribute_atoms(this->mpi_comm, atoms, period_Vs, 2, false); + const auto list_A0_pair_R = list_As_Vs_atoms.first; + const auto list_A1_pair_R = list_As_Vs_atoms.second[0]; + std::set atoms00; + std::set atoms01; + for (const auto& I: list_A0_pair_R) + { + atoms00.insert(I); + } + for (const auto& JR: list_A1_pair_R) + { + atoms01.insert(JR.first); + } + std::map>> overlap_abfs_abfs_IJ + = RI_2D_Comm::comm_map2_first(mpi_comm, overlap_abfs_abfs, atoms00, atoms01); + overlap_abfs_abfs.clear(); + std::map>> overlap_abfs_abf_IJ + = RI_2D_Comm::comm_map2_first(mpi_comm, overlap_abfs_abf, atoms00, atoms01); + overlap_abfs_abf.clear(); + + if (this->runtime.input.out_librpa_ver == 1) + { + out_abfs_overlap_v1(ucell, overlap_abfs_abfs_IJ, overlap_abfs_abf_IJ, "sinvS_", index_abfs_s, index_abfs); + } + else + { + out_abfs_overlap(ucell, overlap_abfs_abfs_IJ, overlap_abfs_abf_IJ, "shrink_sinvS_", index_abfs_s, index_abfs); + } +} + +template +void RPA_LRI::inverse_olp(const UnitCell& ucell, + std::map>>>& overlap_abfs_abfs, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs_s) +{ + ModuleBase::TITLE("RPA_LRI", "inverse_olp"); + ModuleBase::timer::start("RPA_LRI", "inverse_olp"); + const int nks_tot = this->runtime.input.nspin == 2 ? (int)p_kv->get_nks() / 2 : p_kv->get_nks(); + size_t all_mu_s = 0; + std::vector mu_s_shift(ucell.nat); + for (int I = 0; I != ucell.nat; I++) + { + mu_s_shift[I] = all_mu_s; + all_mu_s += index_abfs_s[ucell.iat2it[I]].count_size; + } + RI::Tensor> olp_all = RI::Tensor>({all_mu_s, all_mu_s}); + for (int ik = 0; ik < nks_tot; ik++) + { + for (auto& Ip: overlap_abfs_abfs) + { + auto I = Ip.first; + size_t mu_s_I = index_abfs_s[ucell.iat2it[I]].count_size; + for (auto& JPp: Ip.second) + { + auto J = JPp.first.first; + auto q = JPp.first.second; + if (q != RI_Util::Vector3_to_array3(p_kv->kvec_c[ik])) + continue; + // std::cout << "IJ: " << I << "," << J << std::endl; + auto mu_s_J = index_abfs_s[ucell.iat2it[J]].count_size; + for (int ir = 0; ir < mu_s_I; ir++) + { + for (int ic = 0; ic < mu_s_J; ic++) + { + olp_all(mu_s_shift[I] + ir, mu_s_shift[J] + ic) = JPp.second(ir, ic); + } + } + } + } + // for multi-mpi + // for (int ir = 0; ir < all_mu_s; ir++) + // { + // for (int ic = 0; ic < all_mu_s; ic++) + // { + // Parallel_Reduce::reduce_all>(olp_all(ir, ic)); + // } + // } + + // check Hermitian + for (int ir = 0; ir < all_mu_s; ir++) + { + for (int ic = ir; ic < all_mu_s; ic++) + { + auto delta = std::abs(olp_all(ir, ic) - std::conj(olp_all(ic, ir))); + if (delta > 1e-10) + { + std::cout << "Warning: olp_all is not Hermitian!" << std::endl; + std::cout << "ik,ir,ic: " << ik << "," << ir << "," << ic << std::endl; + std::cout << "delta(ir, ic): " << delta << std::endl; + } + } + } + // out_pure_ri_tensor("olp_all.txt", olp_all, 0.); + auto olp_inv = LRI_CV_Tools::cal_I(olp_all, + Inverse_Matrix>::Method::syev, + this->info.shrink_LU_inv_thr); + for (int ir = 0; ir < all_mu_s; ir++) + { + for (int ic = ir; ic < all_mu_s; ic++) + { + olp_inv(ic, ir) = std::conj(olp_inv(ir, ic)); + } + } + // out_pure_ri_tensor("olp_inv.txt", olp_inv, 0.); + for (auto& Ip: overlap_abfs_abfs) + { + auto I = Ip.first; + size_t mu_s_I = index_abfs_s[ucell.iat2it[I]].count_size; + for (auto& JPp: Ip.second) + { + auto q = JPp.first.second; + if (q != RI_Util::Vector3_to_array3(p_kv->kvec_c[ik])) + continue; + auto J = JPp.first.first; + auto mu_s_J = index_abfs_s[ucell.iat2it[J]].count_size; + + for (int ir = 0; ir < mu_s_I; ir++) + { + for (int ic = 0; ic < mu_s_J; ic++) + JPp.second(ir, ic) = olp_inv(mu_s_shift[I] + ir, mu_s_shift[J] + ic); + } + } + } + } + ModuleBase::timer::end("RPA_LRI", "inverse_olp"); +} + +template void RPA_LRI::cal_large_Cs(const UnitCell& ucell, + const LCAO_Orbitals& orb, + const K_Vectors& kv); +template void RPA_LRI, double>::cal_large_Cs(const UnitCell& ucell, + const LCAO_Orbitals& orb, + const K_Vectors& kv); +template void RPA_LRI::cal_abfs_overlap(const UnitCell& ucell, + const LCAO_Orbitals& orb, + const K_Vectors& kv); +template void RPA_LRI, double>::cal_abfs_overlap(const UnitCell& ucell, + const LCAO_Orbitals& orb, + const K_Vectors& kv); +template void RPA_LRI::inverse_olp( + const UnitCell& ucell, + std::map>, RI::Tensor>>>& overlap_abfs_abfs, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs_s); +template void RPA_LRI, double>::inverse_olp( + const UnitCell& ucell, + std::map>, RI::Tensor>>>& overlap_abfs_abfs, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs_s); diff --git a/source/source_lcao/module_ri/rpa_lri_overlap_io.cpp b/source/source_lcao/module_ri/rpa_lri_overlap_io.cpp new file mode 100644 index 00000000000..91e8ac6b70f --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_overlap_io.cpp @@ -0,0 +1,439 @@ +#include "exx_lri.h" +#include "rpa_lri.h" +#include "rpa_lri_detail.h" +#include "source_base/global_function.h" +#include "source_basis/module_ao/elem_basis_idx_orb.h" +#include "source_estate/elecstate_lcao.h" +#include "source_io/module_parameter/input_parameter.h" +#include "source_lcao/module_ri/module_exx_symmetry/symm_rotation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +void RPA_LRI::out_abfs_overlap(const UnitCell& ucell, + std::map>>& overlap_abfs_abfs, + std::map>>& overlap_abfs_abf, + std::string filename, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs_s, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs) +{ + ModuleBase::TITLE("RPA_LRI", "out_abfs_overlap"); + ModuleBase::timer::start("RPA_LRI", "out_abfs_overlap"); + const double threshold = 1e-15; + const auto format = std::scientific; + int prec = 15; + + int all_mu_s = 0; + int all_mu = 0; + std::vector mu_s_shift(ucell.nat); + std::vector mu_shift(ucell.nat); + for (int I = 0; I != ucell.nat; I++) + { + mu_s_shift[I] = all_mu_s; + mu_shift[I] = all_mu; + all_mu_s += index_abfs_s[ucell.iat2it[I]].count_size; + all_mu += index_abfs[ucell.iat2it[I]].count_size; + } + const int nks_tot = this->runtime.input.nspin == 2 ? (int)p_kv->get_nks() / 2 : p_kv->get_nks(); + std::stringstream ss; + ss << filename << (this->runtime.rank + 1) << ".txt"; + + std::ofstream ofs; + ofs.open(this->runtime.input.rpa_outdir + ss.str().c_str(), std::ios::out); + + ofs << nks_tot << std::endl; + + // Fourier of ss(R->k), s(R->k) + std::map>>> olp_q_ss; + std::map>>> olp_q_s; + for (int ik = 0; ik != nks_tot; ik++) + { + for (auto& Ip: overlap_abfs_abfs) + { + auto I = Ip.first; + for (auto& JPp: Ip.second) + { + auto J = JPp.first.first; + auto R = JPp.first.second; + auto q = RI_Util::Vector3_to_array3(p_kv->kvec_c[ik]); + RI::Tensor> tmp_olp_ss + = RI::Global_Func::convert>(JPp.second); + RI::Tensor> tmp_olp_s + = RI::Global_Func::convert>(overlap_abfs_abf[I][{J, R}]); + if (olp_q_ss[I][{J, q}].empty()) + { + olp_q_ss[I][{J, q}] = RI::Tensor>({tmp_olp_ss.shape[0], tmp_olp_ss.shape[1]}); + olp_q_s[I][{J, q}] = RI::Tensor>({tmp_olp_s.shape[0], tmp_olp_s.shape[1]}); + } + // Fourier transform the real-space overlap block with + // exp(i 2*pi k.R), using the lattice-vector convention. + const double arg = (p_kv->kvec_c[ik] * (RI_Util::array3_to_Vector3(R) * ucell.latvec)) + * ModuleBase::TWO_PI; + const std::complex kphase = std::complex(cos(arg), sin(arg)); + + olp_q_ss[I][{J, q}] = olp_q_ss[I][{J, q}] + tmp_olp_ss * kphase; + olp_q_s[I][{J, q}] = olp_q_s[I][{J, q}] + tmp_olp_s * kphase; + } + } + } + // for multi-mpi + for (int I = 0; I != ucell.nat; I++) + { + for (int J = 0; J != ucell.nat; J++) + { + for (int ik = 0; ik != nks_tot; ik++) + { + auto q = RI_Util::Vector3_to_array3(p_kv->kvec_c[ik]); + if (olp_q_ss[I][{J, q}].empty()) + { + auto mu = index_abfs_s[ucell.iat2it[I]].count_size; + auto nu = index_abfs_s[ucell.iat2it[J]].count_size; + olp_q_ss[I][{J, q}] = RI::Tensor>({mu, nu}); + } + if (olp_q_s[I][{J, q}].empty()) + { + auto mu = index_abfs_s[ucell.iat2it[I]].count_size; + auto nu = index_abfs[ucell.iat2it[J]].count_size; + olp_q_s[I][{J, q}] = RI::Tensor>({mu, nu}); + } + for (int ir = 0; ir < olp_q_ss[I][{J, q}].shape[0]; ir++) + { + for (int ic = 0; ic < olp_q_ss[I][{J, q}].shape[1]; ic++) + { + Parallel_Reduce::reduce_all>(olp_q_ss[I][{J, q}](ir, ic)); + } + for (int ic = 0; ic < olp_q_s[I][{J, q}].shape[1]; ic++) + { + Parallel_Reduce::reduce_all>(olp_q_s[I][{J, q}](ir, ic)); + } + } + } + } + } + + // out_ri_tensor("olp_ss.txt", olp_q_ss, 0.); + // Inverse of overlap(q) + inverse_olp(ucell, olp_q_ss, index_abfs_s); + // out_ri_tensor("olp_ss_inv.txt", olp_q_ss, 0.); + // out_ri_tensor("olp_s.txt", olp_q_s, 0.); + for (auto& Ip: overlap_abfs_abf) + { + auto I = Ip.first; + size_t mu_num_s = index_abfs_s[ucell.iat2it[I]].count_size; + size_t mu_num = index_abfs[ucell.iat2it[I]].count_size; + + for (int ik = 0; ik != nks_tot; ik++) + { + std::map>> sinvS; + for (auto& JPp: Ip.second) + { + auto J = JPp.first.first; + auto R = JPp.first.second; + if (sinvS[J].empty()) + { + sinvS[J] = RI::Tensor>( + {overlap_abfs_abfs[I][{J, R}].shape[0], overlap_abfs_abf[I][{J, R}].shape[1]}); + } + } + for (const auto& pair: sinvS) + { + auto J = pair.first; + auto q = RI_Util::Vector3_to_array3(p_kv->kvec_c[ik]); + for (int K = 0; K != ucell.nat; K++) + { + // S^{-1}S(I,J;q) = sum_K S_ss(I,K;q) S_s(K,J;q). + sinvS[J] += olp_q_ss.at(I).at({K, q}) * olp_q_s.at(K).at({J, q}); + } + } + for (auto& iJU: sinvS) + { + auto iJ = iJU.first; + auto& vq_J = iJU.second; + size_t nu_num = index_abfs[ucell.iat2it[iJ]].count_size; + ofs << all_mu_s << " " << all_mu << " " << mu_s_shift[I] + 1 << " " << mu_s_shift[I] + mu_num_s + << " " << mu_shift[iJ] + 1 << " " << mu_shift[iJ] + nu_num << std::endl; + ofs << ik + 1 << " " << p_kv->wk[ik] / 2.0 * this->runtime.input.nspin << std::endl; + for (int i = 0; i != vq_J.data->size(); i++) + { + // ofs << std::setw(25) << std::fixed << std::setprecision(15) << (*vq_J.data)[i].real() + // << std::setw(25) << std::fixed << std::setprecision(15) << (*vq_J.data)[i].imag() << + // std::endl; + // if (fabs((*vq_J.data)[i].real()) > threshold || fabs((*vq_J.data)[i].imag()) > threshold) + ofs << std::showpoint << format << std::setprecision(prec) << (*vq_J.data)[i].real() << " " + << std::showpoint << format << std::setprecision(prec) << (*vq_J.data)[i].imag() << "\n"; + // else + // ofs << std::showpoint << format << std::setprecision(prec) << 0.0 << " " << std::showpoint + // << format << std::setprecision(prec) << 0.0 << "\n"; + } + } + } + } + ofs.close(); + ModuleBase::timer::end("RPA_LRI", "out_abfs_overlap"); +} + +template +void RPA_LRI::out_abfs_overlap_v1(const UnitCell& ucell, + std::map>>& overlap_abfs_abfs, + std::map>>& overlap_abfs_abf, + std::string filename, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs_s, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs) +try +{ + ModuleBase::TITLE("RPA_LRI", "out_abfs_overlap_v1"); + ModuleBase::timer::start("RPA_LRI", "out_abfs_overlap_v1"); + + struct SinvSRecord + { + std::int32_t iq = 0; + std::int32_t nrow_total = 0; + std::int32_t ncol_total = 0; + std::int32_t begin_row = 0; + std::int32_t end_row = 0; + std::int32_t begin_col = 0; + std::int32_t end_col = 0; + double q_weight = 0.0; + std::int64_t offset = 0; + std::vector> payload; + }; + + int all_mu_s = 0; + int all_mu = 0; + std::vector mu_s_shift(ucell.nat); + std::vector mu_shift(ucell.nat); + for (int I = 0; I != ucell.nat; I++) + { + mu_s_shift[I] = all_mu_s; + mu_shift[I] = all_mu; + all_mu_s += index_abfs_s[ucell.iat2it[I]].count_size; + all_mu += index_abfs[ucell.iat2it[I]].count_size; + } + const int nks_tot = this->runtime.input.nspin == 2 ? (int)p_kv->get_nks() / 2 : p_kv->get_nks(); + + std::map>>> olp_q_ss; + std::map>>> olp_q_s; + for (int ik = 0; ik != nks_tot; ik++) + { + for (auto& Ip: overlap_abfs_abfs) + { + auto I = Ip.first; + for (auto& JPp: Ip.second) + { + auto J = JPp.first.first; + auto R = JPp.first.second; + auto q = RI_Util::Vector3_to_array3(p_kv->kvec_c[ik]); + RI::Tensor> tmp_olp_ss + = RI::Global_Func::convert>(JPp.second); + RI::Tensor> tmp_olp_s + = RI::Global_Func::convert>(overlap_abfs_abf[I][{J, R}]); + if (olp_q_ss[I][{J, q}].empty()) + { + olp_q_ss[I][{J, q}] = RI::Tensor>({tmp_olp_ss.shape[0], tmp_olp_ss.shape[1]}); + olp_q_s[I][{J, q}] = RI::Tensor>({tmp_olp_s.shape[0], tmp_olp_s.shape[1]}); + } + // Fourier transform the real-space overlap block with + // exp(i 2*pi k.R), using the lattice-vector convention. + const double arg + = (p_kv->kvec_c[ik] * (RI_Util::array3_to_Vector3(R) * ucell.latvec)) * ModuleBase::TWO_PI; + const std::complex kphase = std::complex(cos(arg), sin(arg)); + olp_q_ss[I][{J, q}] = olp_q_ss[I][{J, q}] + tmp_olp_ss * kphase; + olp_q_s[I][{J, q}] = olp_q_s[I][{J, q}] + tmp_olp_s * kphase; + } + } + } + + for (int I = 0; I != ucell.nat; I++) + { + for (int J = 0; J != ucell.nat; J++) + { + for (int ik = 0; ik != nks_tot; ik++) + { + auto q = RI_Util::Vector3_to_array3(p_kv->kvec_c[ik]); + if (olp_q_ss[I][{J, q}].empty()) + { + auto mu = index_abfs_s[ucell.iat2it[I]].count_size; + auto nu = index_abfs_s[ucell.iat2it[J]].count_size; + olp_q_ss[I][{J, q}] = RI::Tensor>({mu, nu}); + } + if (olp_q_s[I][{J, q}].empty()) + { + auto mu = index_abfs_s[ucell.iat2it[I]].count_size; + auto nu = index_abfs[ucell.iat2it[J]].count_size; + olp_q_s[I][{J, q}] = RI::Tensor>({mu, nu}); + } + for (int ir = 0; ir < olp_q_ss[I][{J, q}].shape[0]; ir++) + { + for (int ic = 0; ic < olp_q_ss[I][{J, q}].shape[1]; ic++) + { + Parallel_Reduce::reduce_all>(olp_q_ss[I][{J, q}](ir, ic)); + } + for (int ic = 0; ic < olp_q_s[I][{J, q}].shape[1]; ic++) + { + Parallel_Reduce::reduce_all>(olp_q_s[I][{J, q}](ir, ic)); + } + } + } + } + } + + inverse_olp(ucell, olp_q_ss, index_abfs_s); + + std::vector records; + for (auto& Ip: overlap_abfs_abf) + { + auto I = Ip.first; + size_t mu_num_s = index_abfs_s[ucell.iat2it[I]].count_size; + + for (int ik = 0; ik != nks_tot; ik++) + { + std::map>> sinvS; + for (auto& JPp: Ip.second) + { + auto J = JPp.first.first; + auto R = JPp.first.second; + if (sinvS[J].empty()) + { + sinvS[J] = RI::Tensor>( + {overlap_abfs_abfs[I][{J, R}].shape[0], overlap_abfs_abf[I][{J, R}].shape[1]}); + } + } + for (const auto& pair: sinvS) + { + auto J = pair.first; + auto q = RI_Util::Vector3_to_array3(p_kv->kvec_c[ik]); + for (int K = 0; K != ucell.nat; K++) + { + // S^{-1}S(I,J;q) = sum_K S_ss(I,K;q) S_s(K,J;q). + sinvS[J] += olp_q_ss.at(I).at({K, q}) * olp_q_s.at(K).at({J, q}); + } + } + for (auto& iJU: sinvS) + { + auto iJ = iJU.first; + auto& vq_J = iJU.second; + size_t nu_num = index_abfs[ucell.iat2it[iJ]].count_size; + SinvSRecord record; + record.iq = static_cast(ik + 1); + record.nrow_total = static_cast(all_mu_s); + record.ncol_total = static_cast(all_mu); + record.begin_row = static_cast(mu_s_shift[I] + 1); + record.end_row = static_cast(mu_s_shift[I] + mu_num_s); + record.begin_col = static_cast(mu_shift[iJ] + 1); + record.end_col = static_cast(mu_shift[iJ] + nu_num); + record.q_weight = p_kv->wk[ik] / 2.0 * this->runtime.input.nspin; + record.payload.reserve(static_cast(vq_J.shape[0]) + * static_cast(vq_J.shape[1])); + for (int i = 0; i != vq_J.shape[0]; ++i) + { + for (int j = 0; j != vq_J.shape[1]; ++j) + { + if (i >= static_cast(mu_num_s) || j >= static_cast(nu_num)) + { + throw std::runtime_error("LibRPA v1 shrink_sinvS encountered an inconsistent block shape."); + } + record.payload.push_back(vq_J(i, j)); + } + } + records.push_back(std::move(record)); + } + } + } + + const std::int64_t record_bytes = 7 * static_cast(sizeof(std::int32_t)) + + static_cast(sizeof(double)) + + static_cast(sizeof(std::int64_t)); + std::int64_t offset = 2 * static_cast(sizeof(std::int32_t)) + + static_cast(records.size()) * record_bytes; + for (auto& record: records) + { + record.offset = offset; + offset += static_cast(record.payload.size() * sizeof(std::complex)); + } + + std::stringstream ss; + ss << filename << this->runtime.rank << ".dat"; + const std::string out_name = this->runtime.input.rpa_outdir + ss.str(); + std::ofstream ofs(out_name.c_str(), std::ios::out | std::ios::binary | std::ios::trunc); + if (!ofs.good()) + { + throw std::runtime_error("Failed to open " + out_name); + } + + const std::int32_t marker = RpaLriDetail::LIBRPA_SHRINK_SINVS_V1_MARKER; + const std::int32_t nrecords = static_cast(records.size()); + RpaLriDetail::write_scalar(ofs, marker, out_name); + RpaLriDetail::write_scalar(ofs, nrecords, out_name); + for (const auto& record: records) + { + RpaLriDetail::write_scalar(ofs, record.iq, out_name); + RpaLriDetail::write_scalar(ofs, record.nrow_total, out_name); + RpaLriDetail::write_scalar(ofs, record.ncol_total, out_name); + RpaLriDetail::write_scalar(ofs, record.begin_row, out_name); + RpaLriDetail::write_scalar(ofs, record.end_row, out_name); + RpaLriDetail::write_scalar(ofs, record.begin_col, out_name); + RpaLriDetail::write_scalar(ofs, record.end_col, out_name); + RpaLriDetail::write_scalar(ofs, record.q_weight, out_name); + RpaLriDetail::write_scalar(ofs, record.offset, out_name); + } + for (const auto& record: records) + { + RpaLriDetail::checked_write(ofs, + record.payload.data(), + record.payload.size() * sizeof(std::complex), + out_name); + } + ofs.close(); + ModuleBase::timer::end("RPA_LRI", "out_abfs_overlap_v1"); +} +catch (const std::exception& error) +{ + RpaLriDetail::abort_output(this->mpi_comm, error.what()); +} +catch (...) +{ + RpaLriDetail::abort_output(this->mpi_comm, "Unknown exception in out_abfs_overlap_v1"); +} + +template void RPA_LRI::out_abfs_overlap( + const UnitCell& ucell, + std::map>, RI::Tensor>>& overlap_abfs_abfs, + std::map>, RI::Tensor>>& overlap_abfs_abf, + std::string filename, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs_s, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs); +template void RPA_LRI, double>::out_abfs_overlap( + const UnitCell& ucell, + std::map>, RI::Tensor>>& overlap_abfs_abfs, + std::map>, RI::Tensor>>& overlap_abfs_abf, + std::string filename, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs_s, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs); +template void RPA_LRI::out_abfs_overlap_v1( + const UnitCell& ucell, + std::map>, RI::Tensor>>& overlap_abfs_abfs, + std::map>, RI::Tensor>>& overlap_abfs_abf, + std::string filename, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs_s, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs); +template void RPA_LRI, double>::out_abfs_overlap_v1( + const UnitCell& ucell, + std::map>, RI::Tensor>>& overlap_abfs_abfs, + std::map>, RI::Tensor>>& overlap_abfs_abf, + std::string filename, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs_s, + const ModuleBase::Element_Basis_Index::IndexLNM& index_abfs); diff --git a/source/source_lcao/module_ri/rpa_lri_sym.cpp b/source/source_lcao/module_ri/rpa_lri_sym.cpp new file mode 100644 index 00000000000..fe6694b56ca --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_sym.cpp @@ -0,0 +1,69 @@ +#include "rpa_lri_detail.h" +#include "source_cell/klist.h" +#include "source_cell/unitcell.h" + +#include +#include +#include +#include +#include + +namespace +{ +bool preserves_mesh(const ModuleBase::Matrix3& kg, const K_Vectors& kv, const UnitCell& cell) +{ + const double rows[3][3] = {{kg.e11, kg.e12, kg.e13}, {kg.e21, kg.e22, kg.e23}, {kg.e31, kg.e32, kg.e33}}; + // ABACUS and LibRPA rotate row vectors: k' = k * kg. Therefore the + // coefficient taking source component j on an nmp[j] mesh to target + // component i on an nmp[i] mesh is kg[j][i] * nmp[i] / nmp[j]. + for (int j = 0; j < 3; ++j) + { + if (kv.nmp[j] <= 0) throw std::runtime_error("Spatial symmetry requires a uniform k-grid"); + for (int i = 0; i < 3; ++i) + { + const double step = rows[j][i] * kv.nmp[i] / kv.nmp[j]; + if (std::abs(step - std::round(step)) > 1e-8) return false; + } + } + // Check the actual mesh offset as well as its spacing. + const auto origin = kv.kvec_c_full.at(0) * cell.G.Inverse(); + const auto delta = origin * kg - origin; + const double shifts[3] = {delta.x * kv.nmp[0], delta.y * kv.nmp[1], delta.z * kv.nmp[2]}; + for (const double shift : shifts) + { + if (std::abs(shift - std::round(shift)) > 1e-8) return false; + } + return true; +} +} + +void RpaLriDetail::write_stru_sym(std::ostream& output, const UnitCell& cell, const K_Vectors& kv) +{ + const auto& symm = cell.symm; + std::vector operations; + for (int isym = 0; isym < symm.nrotk; ++isym) + { + // Use the same reciprocal-space matrices that ABACUS used to reduce + // this mesh. Reconstructing them from gmatrix can change the basis + // convention for non-orthogonal primitive cells. + if (preserves_mesh(symm.kgmatrix[isym], kv, cell)) operations.push_back(isym); + } + if (operations.empty()) throw std::runtime_error("Missing mesh-preserving spatial symmetry operations"); + // Use the subgroup that preserves this mesh. The full crystal group may + // rotate an anisotropic mesh outside itself and cannot accelerate its RPA. + output << operations.size() << " row\n"; + for (const int isym : operations) + { + const auto& r = symm.gmatrix[isym]; + const double entries[9] = {r.e11, r.e12, r.e13, r.e21, r.e22, r.e23, r.e31, r.e32, r.e33}; + for (const double entry : entries) + { + if (!std::isfinite(entry) || std::abs(entry - std::round(entry)) > 1e-8) + throw std::runtime_error("Nonintegral fractional symmetry rotation"); + output << std::setw(4) << static_cast(std::round(entry)); + } + const auto& t = symm.gtrans[isym]; + output << std::scientific << std::setprecision(15) << std::setw(24) << t.x + << std::setw(24) << t.y << std::setw(24) << t.z << '\n'; + } +} diff --git a/source/source_lcao/module_ri/rpa_lri_velocity.cpp b/source/source_lcao/module_ri/rpa_lri_velocity.cpp new file mode 100644 index 00000000000..767a2b401be --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_velocity.cpp @@ -0,0 +1,92 @@ +#include "exx_lri.h" +#include "rpa_lri.h" +#include "rpa_lri_detail.h" +#include "source_base/global_function.h" +#include "source_basis/module_ao/elem_basis_idx_orb.h" +#include "source_estate/elecstate_lcao.h" +#include "source_io/module_parameter/input_parameter.h" +#include "source_lcao/module_lr/utils/spectrum_mo.hpp" +#include "source_lcao/module_ri/module_exx_symmetry/symm_rotation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +void RPA_LRI::out_velocity(const UnitCell& ucell, + const Grid_Driver& gd, + const TwoCenterBundle& two_center_bundle, + const Parallel_Orbitals& parav, + const psi::Psi& psi, + const elecstate::ElecState* pelec) +{ + ModuleBase::TITLE("DFT_RPA_interface", "out_velocity"); + ModuleBase::timer::start("RPA_LRI", "out_velocity"); + + Parallel_2D parac; + LR_Util::setup_2d_division(parac, + parav.get_block_size(), + this->runtime.nlocal, + this->runtime.input.nbands +#ifdef __MPI + , + parav.blacs_ctxt +#endif + ); + + const int nk = this->runtime.input.nspin == 2 ? p_kv->get_nks() / 2 : p_kv->get_nks(); + const int nspin_tmp = this->runtime.input.nspin == 2 ? 2 : 1; + const int nbands = parav.get_wfc_global_nbands(); + const int nbasis = parav.get_wfc_global_nbasis(); + + std::vector nocc(2, nbands); + std::vector nvirt(2, 0); + const std::vector> velocity_mo = LR_Util::cal_velocity_mo(ucell, + gd, + two_center_bundle, + parav, + parac, + *this->p_kv, + psi, + nk, + nspin_tmp, + this->runtime.nlocal, + nocc, + nvirt); + if (this->runtime.rank == 0) + { + LR_Util::output_spectrum_mo_librpa(velocity_mo, + this->runtime.input.rpa_outdir + "velocity_matrix.txt", + nk, + nspin_tmp, + nbands, + nbasis, + nbands); + } + ModuleBase::timer::end("RPA_LRI", "out_velocity"); +} + +template void RPA_LRI::out_velocity(const UnitCell& ucell, + const Grid_Driver& gd, + const TwoCenterBundle& two_center_bundle, + const Parallel_Orbitals& parav, + const psi::Psi& psi, + const elecstate::ElecState* pelec); +template void RPA_LRI, double>::out_velocity(const UnitCell& ucell, + const Grid_Driver& gd, + const TwoCenterBundle& two_center_bundle, + const Parallel_Orbitals& parav, + const psi::Psi>& psi, + const elecstate::ElecState* pelec); diff --git a/source/source_lcao/module_ri/rpa_lri_wfc_mpi.cpp b/source/source_lcao/module_ri/rpa_lri_wfc_mpi.cpp new file mode 100644 index 00000000000..c33e8ffd0b5 --- /dev/null +++ b/source/source_lcao/module_ri/rpa_lri_wfc_mpi.cpp @@ -0,0 +1,360 @@ +#include "rpa_lri_detail.h" +#include "source_basis/module_ao/parallel_orbitals.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __MPI +namespace RpaLriDetail +{ +template +void append_binary_scalar(std::vector& bytes, const Value& value) +{ + const std::size_t old_size = bytes.size(); + bytes.resize(old_size + sizeof(Value)); + std::memcpy(bytes.data() + old_size, &value, sizeof(Value)); +} + +struct KSEigenvectorV1Metadata +{ + std::vector bytes; + std::vector payload_offsets; + unsigned long long component_bytes = 0; + unsigned long long total_bytes = 0; +}; + +KSEigenvectorV1Metadata make_ks_eigenvector_v1_metadata(const int nks_tot, + const int nspins, + const int ncomponents, + const int nbands, + const int nbasis_wfc, + const int spatial_basis) +{ + KSEigenvectorV1Metadata metadata; + const unsigned long long header_bytes + = checked_mul_u64(6, static_cast(sizeof(std::int32_t)), "KS eigenvector v1 header size"); + const unsigned long long record_bytes = checked_add_u64(static_cast(sizeof(std::int32_t)), + static_cast(sizeof(std::int64_t)), + "KS eigenvector v1 directory record size"); + const unsigned long long directory_bytes + = checked_mul_u64(static_cast(nks_tot), record_bytes, "KS eigenvector v1 directory size"); + const unsigned long long payload_begin + = checked_add_u64(header_bytes, directory_bytes, "KS eigenvector v1 metadata size"); + const unsigned long long component_values = checked_mul_u64(static_cast(nbands), + static_cast(spatial_basis), + "KS eigenvector v1 component size"); + metadata.component_bytes = checked_mul_u64(component_values, + static_cast(sizeof(std::complex)), + "KS eigenvector v1 component byte size"); + const unsigned long long kpoint_bytes = checked_mul_u64(static_cast(ncomponents), + metadata.component_bytes, + "KS eigenvector v1 k-point byte size"); + + const unsigned long long payload_bytes = checked_mul_u64(static_cast(nks_tot), + kpoint_bytes, + "KS eigenvector v1 payload byte size"); + // The file is [header][one directory record per k][k-point payloads]. + // Each payload is component-major and contains nbands*spatial_basis values. + metadata.total_bytes = checked_add_u64(payload_begin, payload_bytes, "KS eigenvector v1 file size"); + + const std::int32_t marker = LIBRPA_KS_EIGENVECTOR_V1_MARKER; + const std::int32_t kind = LIBRPA_KS_EIGENVECTOR_V1_KIND_COMPLEX_DOUBLE; + const std::int32_t nkpoints_i32 = checked_i32_from_int(nks_tot, "KS eigenvector k-point count"); + const std::int32_t nspins_i32 = checked_i32_from_int(nspins, "KS eigenvector spin count"); + const std::int32_t nstates_i32 = checked_i32_from_int(nbands, "KS eigenvector state count"); + const std::int32_t nbasis_i32 = checked_i32_from_int(nbasis_wfc, "KS eigenvector basis count"); + append_binary_scalar(metadata.bytes, marker); + append_binary_scalar(metadata.bytes, kind); + append_binary_scalar(metadata.bytes, nkpoints_i32); + append_binary_scalar(metadata.bytes, nspins_i32); + append_binary_scalar(metadata.bytes, nstates_i32); + append_binary_scalar(metadata.bytes, nbasis_i32); + + metadata.payload_offsets.reserve(static_cast(nks_tot)); + for (int ik = 0; ik < nks_tot; ++ik) + { + const unsigned long long kpoint_offset + = checked_mul_u64(static_cast(ik), kpoint_bytes, "KS eigenvector v1 k-point offset"); + const unsigned long long payload_offset + = checked_add_u64(payload_begin, kpoint_offset, "KS eigenvector v1 payload offset"); + metadata.payload_offsets.push_back(payload_offset); + const std::int32_t ik_file = checked_i32_from_int(ik + 1, "KS eigenvector k-point index"); + const std::int64_t payload_offset_i64 + = checked_i64_from_u64(payload_offset, "KS eigenvector v1 payload offset"); + append_binary_scalar(metadata.bytes, ik_file); + append_binary_scalar(metadata.bytes, payload_offset_i64); + } + if (metadata.bytes.size() != static_cast(payload_begin)) + { + throw std::runtime_error("KS eigenvector v1 metadata size is inconsistent."); + } + return metadata; +} + +struct KSEigenvectorPackCursor +{ + int band_local = 0; + int row_local = 0; +}; + +template +int pack_ks_eigenvector_chunk(const Parallel_Orbitals& parav, + const psi::Psi& psi, + const int psi_k, + const bool is_soc, + const int spinor_component, + KSEigenvectorPackCursor& cursor, + std::vector>& buffer, + const int requested_count) +{ + int packed = 0; + while (cursor.band_local < parav.ncol_bands && packed < requested_count) + { + while (cursor.row_local < psi.get_nbasis() && packed < requested_count) + { + const int ir_local = cursor.row_local++; + const int iw_global = parav.local2global_row(ir_local); + if (is_soc && iw_global % 2 != spinor_component) + { + continue; + } + buffer[static_cast(packed)] = std::complex(psi(psi_k, cursor.band_local, ir_local)); + ++packed; + } + if (cursor.row_local == psi.get_nbasis()) + { + ++cursor.band_local; + cursor.row_local = 0; + } + } + return packed; +} + +template +void write_ks_eigenvector_v1_mpi(const MPI_Comm mpi_comm, + const Parallel_Orbitals& parav, + const psi::Psi& psi, + const int nks_tot, + const int nspins, + const int nspin_abacus, + const int nbands, + const int nbasis_wfc, + const std::string& final_name) +{ + if (mpi_comm == MPI_COMM_NULL) + { + abort_output(mpi_comm, "KS eigenvector MPI-IO writer received MPI_COMM_NULL."); + } + try + { + int mpi_rank = 0; + collective_mpi_check(mpi_comm, + MPI_Comm_rank(mpi_comm, &mpi_rank), + "Failed to query the KS eigenvector MPI rank"); + + const bool is_soc = nspin_abacus == 4; + const int ncomponents = is_soc ? 2 : nspins; + bool dimensions_valid = nks_tot >= 0 && nbands >= 0 && nbasis_wfc >= 0 && nspins > 0 + && (nspin_abacus == 1 || nspin_abacus == 2 || nspin_abacus == 4) + && nspins == (nspin_abacus == 2 ? 2 : 1) && (!is_soc || nbasis_wfc % 2 == 0) + && psi.get_nbasis() == parav.get_row_size() && psi.get_nbands() == parav.ncol_bands + && parav.get_wfc_global_nbasis() == nbasis_wfc + && parav.get_wfc_global_nbands() == nbands; + if (dimensions_valid) + { + const unsigned long long required_psi_kpoints = checked_mul_u64(static_cast(nks_tot), + static_cast(nspins), + "KS eigenvector MPI source k-point count"); + dimensions_valid = required_psi_kpoints <= static_cast(psi.get_nk()); + } + collective_require(mpi_comm, dimensions_valid, "Invalid KS eigenvector MPI-IO dimensions"); + + const int spatial_basis = is_soc ? nbasis_wfc / 2 : nbasis_wfc; + std::vector layouts; + layouts.reserve(static_cast(is_soc ? 2 : 1)); + for (int component = 0; component < (is_soc ? 2 : 1); ++component) + { + layouts.push_back(make_ks_eigenvector_mpi_layout(mpi_comm, parav, nbands, nbasis_wfc, is_soc, component)); + } + + const unsigned long long chunk_elements = 4ULL * 1024ULL * 1024ULL; + unsigned long long local_buffer_elements = 0; + for (const auto& layout: layouts) + { + local_buffer_elements = std::max(local_buffer_elements, std::min(layout.local_count, chunk_elements)); + } + bool buffer_allocated = true; + std::vector> buffer; + try + { + buffer.resize(static_cast(local_buffer_elements)); + } + catch (const std::exception&) + { + buffer_allocated = false; + } + collective_require(mpi_comm, buffer_allocated, "Failed to allocate the bounded KS eigenvector MPI pack buffer"); + std::complex dummy(0.0, 0.0); + + const KSEigenvectorV1Metadata metadata + = make_ks_eigenvector_v1_metadata(nks_tot, nspins, ncomponents, nbands, nbasis_wfc, spatial_basis); + collective_require(mpi_comm, + metadata.bytes.size() <= static_cast(std::numeric_limits::max()), + "KS eigenvector MPI metadata exceeds the MPI count range"); + + const std::string temporary_name = final_name + ".tmp"; + MPI_File file = MPI_FILE_NULL; + int mpi_error = MPI_File_open(mpi_comm, + const_cast(temporary_name.c_str()), + MPI_MODE_CREATE | MPI_MODE_WRONLY, + MPI_INFO_NULL, + &file); + collective_mpi_check(mpi_comm, mpi_error, "Failed to open " + temporary_name); + collective_mpi_check(mpi_comm, + MPI_File_set_errhandler(file, MPI_ERRORS_RETURN), + "Failed to set the KS eigenvector MPI file error handler"); + collective_mpi_check( + mpi_comm, + MPI_File_set_size(file, checked_mpi_offset_from_u64(metadata.total_bytes, "KS eigenvector MPI file size")), + "Failed to set the KS eigenvector MPI file size"); + + const int metadata_count = mpi_rank == 0 ? static_cast(metadata.bytes.size()) : 0; + MPI_Status metadata_status; + mpi_error + = MPI_File_write_at_all(file, + 0, + metadata_count == 0 ? static_cast(&dummy) + : static_cast(const_cast(metadata.bytes.data())), + metadata_count, + MPI_BYTE, + &metadata_status); + collective_mpi_check(mpi_comm, mpi_error, "Failed to write KS eigenvector MPI metadata"); + + for (int ik = 0; ik < nks_tot; ++ik) + { + for (int component = 0; component < ncomponents; ++component) + { + KSEigenvectorMpiLayout& layout = layouts[is_soc ? component : 0]; + const unsigned long long component_offset = checked_mul_u64(static_cast(component), + metadata.component_bytes, + "KS eigenvector MPI component offset"); + const unsigned long long displacement + = checked_add_u64(metadata.payload_offsets[static_cast(ik)], + component_offset, + "KS eigenvector MPI view displacement"); + collective_mpi_check( + mpi_comm, + MPI_File_set_view(file, + checked_mpi_offset_from_u64(displacement, "KS eigenvector MPI view displacement"), + MPI_C_DOUBLE_COMPLEX, + layout.filetype, + const_cast("native"), + MPI_INFO_NULL), + "Failed to set the KS eigenvector MPI file view"); + + KSEigenvectorPackCursor cursor; + const int psi_k = is_soc ? ik : ik + nks_tot * component; + unsigned long long chunk_begin = 0; + while (chunk_begin < layout.max_local_count) + { + const unsigned long long local_remaining + = chunk_begin < layout.local_count ? layout.local_count - chunk_begin : 0; + const int local_chunk_count = static_cast(std::min(local_remaining, chunk_elements)); + const int packed = pack_ks_eigenvector_chunk(parav, + psi, + psi_k, + is_soc, + component, + cursor, + buffer, + local_chunk_count); + collective_require(mpi_comm, + packed == local_chunk_count, + "Failed to pack the expected KS eigenvector MPI chunk"); + + MPI_Status payload_status; + mpi_error = MPI_File_write_at_all( + file, + checked_mpi_offset_from_u64(chunk_begin, "KS eigenvector MPI chunk offset"), + local_chunk_count == 0 ? static_cast(&dummy) : static_cast(buffer.data()), + local_chunk_count, + MPI_C_DOUBLE_COMPLEX, + &payload_status); + collective_mpi_check(mpi_comm, mpi_error, "Failed to write a KS eigenvector MPI payload chunk"); + if (layout.max_local_count - chunk_begin <= chunk_elements) + { + break; + } + chunk_begin = checked_add_u64(chunk_begin, chunk_elements, "KS eigenvector MPI chunk offset"); + } + } + } + + mpi_error = MPI_File_sync(file); + collective_mpi_check(mpi_comm, mpi_error, "Failed to sync the KS eigenvector MPI file"); + mpi_error = MPI_File_close(&file); + collective_mpi_check(mpi_comm, mpi_error, "Failed to close the KS eigenvector MPI file"); + + int free_error = MPI_SUCCESS; + for (auto& layout: layouts) + { + const int layout_error = free_ks_eigenvector_mpi_layout(layout); + if (free_error == MPI_SUCCESS && layout_error != MPI_SUCCESS) + { + free_error = layout_error; + } + } + collective_mpi_check(mpi_comm, free_error, "Failed to free a KS eigenvector MPI file type"); + + const int rename_error + = mpi_rank == 0 && std::rename(temporary_name.c_str(), final_name.c_str()) != 0 ? MPI_ERR_IO : MPI_SUCCESS; + collective_mpi_check(mpi_comm, rename_error, "Failed to publish the completed KS eigenvector MPI file"); + + if (mpi_rank == 0) + { + std::cout << "KS eigenvector writer: binary v1 MPI-IO, bounded pack buffer, file " << final_name + << std::endl; + } + } + catch (const std::exception& error) + { + abort_output(mpi_comm, error.what()); + } + catch (...) + { + abort_output(mpi_comm, "Unknown exception in KS eigenvector MPI-IO writer"); + } +} +template void write_ks_eigenvector_v1_mpi(MPI_Comm, + const Parallel_Orbitals&, + const psi::Psi&, + int, + int, + int, + int, + int, + const std::string&); +template void write_ks_eigenvector_v1_mpi>(MPI_Comm, + const Parallel_Orbitals&, + const psi::Psi>&, + int, + int, + int, + int, + int, + const std::string&); +} // namespace RpaLriDetail +#endif diff --git a/source/source_lcao/module_ri/test/CMakeLists.txt b/source/source_lcao/module_ri/test/CMakeLists.txt deleted file mode 100644 index ff75006e5b6..00000000000 --- a/source/source_lcao/module_ri/test/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -abacus_disable_feature_definitions(__MLALGO) -abacus_disable_feature_definitions(__CUDA) -abacus_disable_feature_definitions(__ROCM) -AddTest( - TARGET MODULE_RI_dm_mixing_test - LIBS parameter base device - SOURCES dm_mixing_test.cpp ../mix_dmk_2d.cpp -) -AddTest( - TARGET MODULE_RI_ri_cv_io_test - LIBS base device parameter - SOURCES ri_cv_io_test.cpp -) -AddTest( - TARGET MODULE_RI_abfs_vector3_order - LIBS parameter - SOURCES abfs_vec3_order_test.cpp -) -install(DIRECTORY support DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/source/source_lcao/module_ri/unittests/CMakeLists.txt b/source/source_lcao/module_ri/unittests/CMakeLists.txt new file mode 100644 index 00000000000..5635d5d68a1 --- /dev/null +++ b/source/source_lcao/module_ri/unittests/CMakeLists.txt @@ -0,0 +1,30 @@ +abacus_disable_feature_definitions(__MLALGO) +abacus_disable_feature_definitions(__CUDA) +abacus_disable_feature_definitions(__ROCM) +AddTest( + TARGET MODULE_RI_dm_mixing_test + LIBS parameter base device + SOURCES test_mix_dmk_2d.cpp ../mix_dmk_2d.cpp +) +AddTest( + TARGET MODULE_RI_ri_cv_io_test + LIBS base device parameter + SOURCES test_write_ri_cv.cpp +) +AddTest( + TARGET MODULE_RI_abfs_vector3_order + LIBS parameter + SOURCES test_abfs_vector3_order.cpp +) +AddTest( + TARGET MODULE_RI_rpa_lri_detail_test + LIBS parameter base device + SOURCES test_rpa_lri_detail.cpp ../rpa_lri_detail.cpp ../rpa_lri_coeff_v1.cpp ../rpa_lri_bz_io.cpp +) +AddTest( + TARGET MODULE_RI_rpa_lri_band_io_test + LIBS parameter base device + SOURCES test_rpa_lri_band_io.cpp ../rpa_lri_band_io.cpp +) +file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/support" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") diff --git a/source/source_lcao/module_ri/test/support/CMakeLists.txt b/source/source_lcao/module_ri/unittests/support/CMakeLists.txt similarity index 100% rename from source/source_lcao/module_ri/test/support/CMakeLists.txt rename to source/source_lcao/module_ri/unittests/support/CMakeLists.txt diff --git a/source/source_lcao/module_ri/test/support/Cs b/source/source_lcao/module_ri/unittests/support/Cs similarity index 100% rename from source/source_lcao/module_ri/test/support/Cs rename to source/source_lcao/module_ri/unittests/support/Cs diff --git a/source/source_lcao/module_ri/test/support/Vs b/source/source_lcao/module_ri/unittests/support/Vs similarity index 100% rename from source/source_lcao/module_ri/test/support/Vs rename to source/source_lcao/module_ri/unittests/support/Vs diff --git a/source/source_lcao/module_ri/test/abfs_vec3_order_test.cpp b/source/source_lcao/module_ri/unittests/test_abfs_vector3_order.cpp similarity index 100% rename from source/source_lcao/module_ri/test/abfs_vec3_order_test.cpp rename to source/source_lcao/module_ri/unittests/test_abfs_vector3_order.cpp diff --git a/source/source_lcao/module_ri/test/dm_mixing_test.cpp b/source/source_lcao/module_ri/unittests/test_mix_dmk_2d.cpp similarity index 100% rename from source/source_lcao/module_ri/test/dm_mixing_test.cpp rename to source/source_lcao/module_ri/unittests/test_mix_dmk_2d.cpp diff --git a/source/source_lcao/module_ri/unittests/test_rpa_lri_band_io.cpp b/source/source_lcao/module_ri/unittests/test_rpa_lri_band_io.cpp new file mode 100644 index 00000000000..407c99a656f --- /dev/null +++ b/source/source_lcao/module_ri/unittests/test_rpa_lri_band_io.cpp @@ -0,0 +1,101 @@ +#include "../rpa_lri_band_io.h" + +#include "source_base/constants.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +RpaLriDetail::BandOutputData make_band_data() +{ + RpaLriDetail::BandOutputData data; + data.nk = 2; + data.nspin = 2; + data.nbands = 2; + data.nbasis = 5; + data.fermi_ry = 3.0; + for (int ik = 0; ik < data.nk; ++ik) + { + for (int is = 0; is < data.nspin; ++is) + { + for (int ib = 0; ib < data.nbands; ++ib) + { + RpaLriDetail::BandSample sample; + sample.occupation = static_cast((ik + 1) * 10 + (is + 1) * 2 + ib); + sample.energy_ry = static_cast(100 * ik + 10 * is + ib + 1); + data.samples.push_back(sample); + } + } + } + return data; +} +} // namespace + +TEST(RpaLriBandIo, WritesHeaderAndSpinBandRecords) +{ + std::ostringstream output; + const RpaLriDetail::BandOutputData data = make_band_data(); + RpaLriDetail::write_band_output(output, data); + + std::istringstream input(output.str()); + int value = 0; + input >> value; + EXPECT_EQ(value, 2); + input >> value; + EXPECT_EQ(value, 2); + input >> value; + EXPECT_EQ(value, 2); + input >> value; + EXPECT_EQ(value, 5); + double fermi = 0.0; + input >> fermi; + EXPECT_DOUBLE_EQ(fermi, 1.5); + + for (int ik = 0; ik < data.nk; ++ik) + { + for (int is = 0; is < data.nspin; ++is) + { + int kpoint = 0; + int spin = 0; + input >> kpoint >> spin; + EXPECT_EQ(kpoint, ik + 1); + EXPECT_EQ(spin, is + 1); + for (int ib = 0; ib < data.nbands; ++ib) + { + int band = 0; + double occupation = 0.0; + double energy_ha = 0.0; + double energy_ev = 0.0; + input >> band >> occupation >> energy_ha >> energy_ev; + const RpaLriDetail::BandSample& sample = data.samples[(ik * data.nspin + is) * data.nbands + ib]; + EXPECT_EQ(band, ib + 1); + EXPECT_DOUBLE_EQ(occupation, sample.occupation); + EXPECT_DOUBLE_EQ(energy_ha, sample.energy_ry / 2.0); + EXPECT_DOUBLE_EQ(energy_ev, sample.energy_ry * ModuleBase::Ry_to_eV); + } + } + } + std::string trailing; + EXPECT_FALSE(input >> trailing); +} + +TEST(RpaLriBandIo, RejectsInvalidDimensionsAndSamples) +{ + RpaLriDetail::BandOutputData data = make_band_data(); + data.nbands = 0; + EXPECT_THROW(RpaLriDetail::write_band_output(std::cout, data), std::runtime_error); + + data = make_band_data(); + data.samples.pop_back(); + EXPECT_THROW(RpaLriDetail::write_band_output(std::cout, data), std::runtime_error); + + data = make_band_data(); + data.samples[0].energy_ry = std::nan(""); + EXPECT_THROW(RpaLriDetail::write_band_output(std::cout, data), std::runtime_error); +} diff --git a/source/source_lcao/module_ri/unittests/test_rpa_lri_detail.cpp b/source/source_lcao/module_ri/unittests/test_rpa_lri_detail.cpp new file mode 100644 index 00000000000..bb2894c22c1 --- /dev/null +++ b/source/source_lcao/module_ri/unittests/test_rpa_lri_detail.cpp @@ -0,0 +1,376 @@ +#include "../rpa_lri_detail.h" + +#include "../rpa_lri_bz_io.h" +#include "../rpa_lri_coeff_v1.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +using RpaLriDetail::BzSampleData; +using RpaLriDetail::BzSamplePoint; +using RpaLriDetail::checked_add_u64; +using RpaLriDetail::checked_i32_from_int; +using RpaLriDetail::checked_i32_from_size; +using RpaLriDetail::checked_i64_from_u64; +using RpaLriDetail::checked_mul_u64; +using RpaLriDetail::coulomb_atom_pair_index; +using RpaLriDetail::has_valid_matrix_shape; +using RpaLriDetail::LIBRPA_LRICOEF_V1_MARKER; +using RpaLriDetail::LriCoefV1Block; +using RpaLriDetail::real_as_double; +using RpaLriDetail::sum_int_vector; +using RpaLriDetail::write_bz_sample; +using RpaLriDetail::write_lri_coef_v1; +} + +TEST(RpaLriDetail, CoulombAtomPairIndexUsesUpperTriangleOrder) +{ + const std::size_t natoms = 4; + const std::vector expected = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + std::size_t index = 0; + for (std::size_t I = 0; I < natoms; ++I) + { + for (std::size_t J = I; J < natoms; ++J) + { + EXPECT_EQ(coulomb_atom_pair_index(I, J, natoms), expected[index]); + ++index; + } + } + EXPECT_THROW(coulomb_atom_pair_index(2, 1, natoms), std::runtime_error); +} + +TEST(RpaLriDetail, CheckedUnsignedArithmeticRejectsOverflow) +{ + const unsigned long long max_value = std::numeric_limits::max(); + EXPECT_EQ(checked_mul_u64(6, 7, "product"), 42); + EXPECT_EQ(checked_add_u64(6, 7, "sum"), 13); + EXPECT_THROW(checked_mul_u64(max_value, 2, "product"), std::runtime_error); + EXPECT_THROW(checked_add_u64(max_value, 1, "sum"), std::runtime_error); +} + +TEST(RpaLriDetail, CheckedIntegerConversionsRejectOverflowAndNegativeValues) +{ + EXPECT_EQ(checked_i64_from_u64(17, "value"), 17); + EXPECT_EQ(checked_i32_from_size(17, "value"), 17); + EXPECT_EQ(checked_i32_from_int(17, "value"), 17); + EXPECT_THROW(checked_i64_from_u64(std::numeric_limits::max(), "value"), + std::runtime_error); + EXPECT_THROW(checked_i32_from_size(std::numeric_limits::max(), "value"), std::runtime_error); + EXPECT_THROW(checked_i32_from_int(-1, "value"), std::runtime_error); +} + +TEST(RpaLriDetail, SumIntVectorRejectsPositiveOverflow) +{ + EXPECT_EQ(sum_int_vector({1, 2, 3}), 6); + EXPECT_EQ(sum_int_vector({}), 0); + EXPECT_THROW(sum_int_vector({std::numeric_limits::max(), 1}), std::runtime_error); +} + +TEST(RpaLriDetail, RealPartConversionAndMatrixShapeValidation) +{ + EXPECT_DOUBLE_EQ(real_as_double(2.5), 2.5); + EXPECT_DOUBLE_EQ(real_as_double(std::complex(2.5, -4.0)), 2.5); + + RI::Tensor tensor; + EXPECT_FALSE(has_valid_matrix_shape(tensor)); + tensor.shape = {2, 3}; + EXPECT_TRUE(has_valid_matrix_shape(tensor)); + tensor.shape = {2}; + EXPECT_FALSE(has_valid_matrix_shape(tensor)); + tensor.shape = {0, 3}; + EXPECT_FALSE(has_valid_matrix_shape(tensor)); +} + +TEST(RpaLriDetail, CoefficientV1WritesHeaderOffsetsAndPayload) +{ + const std::string filename = "rpa_lri_coeff_v1_test.dat"; + std::remove(filename.c_str()); + + RI::Tensor coeff({2, 1, 2}); + coeff(0, 0, 0) = 1.25; + coeff(1, 0, 0) = -2.0; + coeff(0, 0, 1) = 3.5; + coeff(1, 0, 1) = 4.25; + + LriCoefV1Block block; + block.ia1 = 2; + block.ia2 = 1; + block.r[0] = -1; + block.r[1] = 2; + block.r[2] = 0; + block.max_abs = 4.25; + block.nw1 = 1; + block.nw2 = 2; + block.naux = 2; + block.tensor = &coeff; + RI::Tensor coeff2({1, 1, 1}); + coeff2(0, 0, 0) = -7.0; + LriCoefV1Block block2; + block2.ia1 = 1; + block2.ia2 = 1; + block2.max_abs = 7.0; + block2.nw1 = 1; + block2.nw2 = 1; + block2.naux = 1; + block2.tensor = &coeff2; + std::vector blocks; + blocks.push_back(block); + blocks.push_back(block2); + + write_lri_coef_v1(filename, 2, blocks); + + std::ifstream input(filename.c_str(), std::ios::binary); + ASSERT_TRUE(input.good()); + std::int32_t marker = 0; + std::int32_t natom = 0; + std::int32_t ncell = -1; + std::int64_t nblocks = 0; + std::int64_t nrecords = 0; + input.read(reinterpret_cast(&marker), sizeof(marker)); + input.read(reinterpret_cast(&natom), sizeof(natom)); + input.read(reinterpret_cast(&ncell), sizeof(ncell)); + input.read(reinterpret_cast(&nblocks), sizeof(nblocks)); + input.read(reinterpret_cast(&nrecords), sizeof(nrecords)); + ASSERT_TRUE(input.good()); + EXPECT_EQ(marker, LIBRPA_LRICOEF_V1_MARKER); + EXPECT_EQ(natom, 2); + EXPECT_EQ(ncell, 0); + EXPECT_EQ(nblocks, 2); + EXPECT_EQ(nrecords, 2); + + std::int32_t atom1 = 0; + std::int32_t atom2 = 0; + std::int32_t r[3] = {0, 0, 0}; + double max_abs = 0.0; + std::int64_t offset = 0; + input.read(reinterpret_cast(&atom1), sizeof(atom1)); + input.read(reinterpret_cast(&atom2), sizeof(atom2)); + input.read(reinterpret_cast(r), sizeof(r)); + input.read(reinterpret_cast(&max_abs), sizeof(max_abs)); + input.read(reinterpret_cast(&offset), sizeof(offset)); + ASSERT_TRUE(input.good()); + EXPECT_EQ(atom1, 2); + EXPECT_EQ(atom2, 1); + EXPECT_EQ(r[0], -1); + EXPECT_EQ(r[1], 2); + EXPECT_EQ(r[2], 0); + EXPECT_DOUBLE_EQ(max_abs, 4.25); + EXPECT_EQ(offset, 100); + + std::int32_t atom3 = 0; + std::int32_t atom4 = 0; + std::int32_t r2[3] = {0, 0, 0}; + double max_abs2 = 0.0; + std::int64_t offset2 = 0; + input.read(reinterpret_cast(&atom3), sizeof(atom3)); + input.read(reinterpret_cast(&atom4), sizeof(atom4)); + input.read(reinterpret_cast(r2), sizeof(r2)); + input.read(reinterpret_cast(&max_abs2), sizeof(max_abs2)); + input.read(reinterpret_cast(&offset2), sizeof(offset2)); + ASSERT_TRUE(input.good()); + EXPECT_EQ(atom3, 1); + EXPECT_EQ(atom4, 1); + EXPECT_EQ(r2[0], 0); + EXPECT_EQ(r2[1], 0); + EXPECT_EQ(r2[2], 0); + EXPECT_DOUBLE_EQ(max_abs2, 7.0); + EXPECT_EQ(offset2, 132); + + double values[4] = {}; + input.read(reinterpret_cast(values), sizeof(values)); + ASSERT_TRUE(input.good()); + EXPECT_DOUBLE_EQ(values[0], 1.25); + EXPECT_DOUBLE_EQ(values[1], -2.0); + EXPECT_DOUBLE_EQ(values[2], 3.5); + EXPECT_DOUBLE_EQ(values[3], 4.25); + double value2 = 0.0; + input.read(reinterpret_cast(&value2), sizeof(value2)); + ASSERT_TRUE(input.good()); + EXPECT_DOUBLE_EQ(value2, -7.0); + EXPECT_EQ(input.peek(), std::char_traits::eof()); + input.close(); + std::remove(filename.c_str()); +} + +TEST(RpaLriDetail, CoefficientV1RejectsShapeMismatch) +{ + RI::Tensor coeff({1, 1, 1}); + LriCoefV1Block block; + block.nw1 = 2; + block.nw2 = 1; + block.naux = 1; + block.tensor = &coeff; + std::vector blocks(1, block); + + EXPECT_THROW(write_lri_coef_v1("unused.dat", 1, blocks), std::runtime_error); +} + +namespace +{ +BzSamplePoint make_bz_point(const double weight, + const std::array& direct, + const std::array& cartesian) +{ + BzSamplePoint point; + point.weight = weight; + point.direct = direct; + point.cartesian = cartesian; + return point; +} + +BzSampleData make_reduced_bz_sample() +{ + BzSampleData data; + data.mesh = {{2, 1, 2}}; + data.reduced.push_back(make_bz_point(2.0, {{0.0, 0.0, 0.0}}, {{0.0, 0.0, 0.0}})); + data.reduced.push_back(make_bz_point(2.0, {{0.5, 0.0, 0.5}}, {{1.0, 0.0, 1.0}})); + data.full_cartesian = {{{0.0, 0.0, 0.0}}, {{0.0, 0.0, 1.0}}, {{1.0, 0.0, 0.0}}, {{1.0, 0.0, 1.0}}}; + data.full_map = {0, 0, 1, 1}; + return data; +} +} // namespace + +TEST(RpaLriDetail, BzSampleWritesReducedGridAndFullMap) +{ + const std::string filename = "rpa_lri_bz_sample_test.txt"; + std::remove(filename.c_str()); + write_bz_sample(filename, make_reduced_bz_sample()); + + std::ifstream input(filename.c_str()); + ASSERT_TRUE(input.good()); + std::string line; + ASSERT_TRUE(static_cast(std::getline(input, line))); + EXPECT_EQ(line, "2 1 2"); + ASSERT_TRUE(static_cast(std::getline(input, line))); + EXPECT_EQ(line, "2 2"); + + ASSERT_TRUE(static_cast(std::getline(input, line))); + std::istringstream first_point(line); + int index = 0; + double weight = 0.0; + double direct_x = 0.0; + double direct_y = 0.0; + double direct_z = 0.0; + double cart_x = 0.0; + double cart_y = 0.0; + double cart_z = 0.0; + int scf_index = 0; + int coulomb_index = 0; + first_point >> index >> weight >> direct_x >> direct_y >> direct_z >> cart_x >> cart_y >> cart_z >> scf_index + >> coulomb_index; + EXPECT_EQ(index, 1); + EXPECT_DOUBLE_EQ(weight, 0.5); + EXPECT_DOUBLE_EQ(direct_x, 0.0); + EXPECT_DOUBLE_EQ(direct_y, 0.0); + EXPECT_DOUBLE_EQ(direct_z, 0.0); + EXPECT_DOUBLE_EQ(cart_x, 0.0); + EXPECT_DOUBLE_EQ(cart_y, 0.0); + EXPECT_DOUBLE_EQ(cart_z, 0.0); + EXPECT_EQ(scf_index, 1); + EXPECT_EQ(coulomb_index, 1); + + ASSERT_TRUE(static_cast(std::getline(input, line))); + std::istringstream second_point(line); + second_point >> index >> weight >> direct_x >> direct_y >> direct_z >> cart_x >> cart_y >> cart_z >> scf_index + >> coulomb_index; + EXPECT_EQ(index, 2); + EXPECT_DOUBLE_EQ(weight, 0.5); + EXPECT_DOUBLE_EQ(direct_x, 0.5); + EXPECT_DOUBLE_EQ(direct_y, 0.0); + EXPECT_DOUBLE_EQ(direct_z, 0.5); + EXPECT_DOUBLE_EQ(cart_x, 1.0); + EXPECT_DOUBLE_EQ(cart_y, 0.0); + EXPECT_DOUBLE_EQ(cart_z, 1.0); + EXPECT_EQ(scf_index, 2); + EXPECT_EQ(coulomb_index, 2); + + ASSERT_TRUE(static_cast(std::getline(input, line))); + EXPECT_EQ(line, "full_kmap 4"); + for (int full_index = 1; full_index <= 4; ++full_index) + { + ASSERT_TRUE(static_cast(std::getline(input, line))); + std::istringstream full_point(line); + int point_index = 0; + int representative = 0; + double full_x = 0.0; + double full_y = 0.0; + double full_z = 0.0; + full_point >> point_index; + full_point >> full_x >> full_y >> full_z; + full_point >> representative; + EXPECT_EQ(point_index, full_index); + EXPECT_DOUBLE_EQ(full_x, full_index == 1 || full_index == 2 ? 0.0 : 1.0); + EXPECT_DOUBLE_EQ(full_y, 0.0); + EXPECT_DOUBLE_EQ(full_z, full_index == 1 || full_index == 3 ? 0.0 : 1.0); + EXPECT_EQ(representative, full_index <= 2 ? 1 : 2); + } + EXPECT_FALSE(static_cast(std::getline(input, line))); + input.close(); + std::remove(filename.c_str()); +} + +TEST(RpaLriDetail, BzSampleRejectsInvalidRepresentative) +{ + BzSampleData data = make_reduced_bz_sample(); + data.full_map[3] = 2; + EXPECT_THROW(write_bz_sample("unused.txt", data), std::runtime_error); +} + +TEST(RpaLriDetail, BzSampleRejectsInconsistentFoldedWeight) +{ + BzSampleData data = make_reduced_bz_sample(); + data.reduced[0].weight = 3.0; + data.reduced[1].weight = 1.0; + EXPECT_THROW(write_bz_sample("unused.txt", data), std::runtime_error); +} + +TEST(RpaLriDetail, BzSampleKeepsFullGridFormatWithoutMapTail) +{ + const std::string filename = "rpa_lri_bz_full_test.txt"; + std::remove(filename.c_str()); + BzSampleData data; + data.mesh = {{1, 1, 2}}; + data.reduced.push_back(make_bz_point(0.5, {{0.0, 0.0, 0.0}}, {{0.0, 0.0, 0.0}})); + data.reduced.push_back(make_bz_point(0.5, {{0.0, 0.0, 0.5}}, {{0.0, 0.0, 1.0}})); + data.full_cartesian = {{{0.0, 0.0, 0.0}}, {{0.0, 0.0, 1.0}}}; + data.full_map = {0, 1}; + write_bz_sample(filename, data); + + std::ifstream input(filename.c_str()); + ASSERT_TRUE(input.good()); + std::string line; + int line_count = 0; + while (std::getline(input, line)) + { + ++line_count; + EXPECT_NE(line, "full_kmap 2"); + } + EXPECT_EQ(line_count, 4); + input.close(); + std::remove(filename.c_str()); +} + +TEST(RpaLriDetail, BzSampleRejectsGridSizeOutsideIndexRange) +{ + BzSampleData data; + data.mesh = {{std::numeric_limits::max(), 2, 1}}; + EXPECT_THROW(write_bz_sample("unused.txt", data), std::runtime_error); +} + +TEST(RpaLriDetail, BzSampleRejectsOutputOpenFailure) +{ + EXPECT_THROW(write_bz_sample("missing_bz_sample_directory/bz_sample.txt", make_reduced_bz_sample()), + std::runtime_error); +} diff --git a/source/source_lcao/module_ri/test/ri_cv_io_test.cpp b/source/source_lcao/module_ri/unittests/test_write_ri_cv.cpp similarity index 100% rename from source/source_lcao/module_ri/test/ri_cv_io_test.cpp rename to source/source_lcao/module_ri/unittests/test_write_ri_cv.cpp diff --git a/tests/02_NAO_Gamma/scf_out_hxc/vxc_out.ref b/tests/02_NAO_Gamma/scf_out_hxc/vxc.ref similarity index 100% rename from tests/02_NAO_Gamma/scf_out_hxc/vxc_out.ref rename to tests/02_NAO_Gamma/scf_out_hxc/vxc.ref diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/INPUT b/tests/08_RI/60_LIBRPA_RPA_READER_V1/INPUT new file mode 100644 index 00000000000..55f05aa342e --- /dev/null +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/INPUT @@ -0,0 +1,27 @@ +INPUT_PARAMETERS +suffix autotest +pseudo_dir ../../PP_ORB +orbital_dir ../../PP_ORB +calculation scf +ntype 1 +nbands 2 +ecutwfc 20 +smearing_method gaussian +smearing_sigma 1e-4 +basis_type lcao +ks_solver genelpa +dft_functional pbe +rpa 1 +rpa_out_vel 1 +out_mat_xc 1 +out_wfc_lcao 1 +out_librpa_ver 1 +symmetry -1 +exx_separate_loop 1 +exx_pca_threshold 1e-3 +shrink_abfs_pca_thr 1e-1 +shrink_lu_inv_thr 1e-3 +exx_singularity_correction massidda +exx_ccp_rmesh_times 1 +rpa_ccp_rmesh_times 1 +exx_real_number 1 diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/KPT b/tests/08_RI/60_LIBRPA_RPA_READER_V1/KPT new file mode 100644 index 00000000000..a427751e525 --- /dev/null +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/KPT @@ -0,0 +1,4 @@ +K_POINTS +0 +Gamma +1 1 2 0 0 0 diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/README b/tests/08_RI/60_LIBRPA_RPA_READER_V1/README new file mode 100644 index 00000000000..e629f45d763 --- /dev/null +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/README @@ -0,0 +1,15 @@ +Check reader-v1 RPA producer output, including shrink LRI, Coulomb, and GW +preprocessor input (`KPT.info`, `vxc.txt`, and text LCAO wavefunctions), +for a compact two-atom H `1x1x2` solid. + +For symmetry-reduced grids, reader-v1 appends the actual full-to-SCF mapping +to bz_sample.txt and writes spatial operations in stru_out.txt. These fixed +references use the full grid; temporary variants check symmetry 0 and 1. +The established `velocity_matrix.txt` protocol name is retained +for both versions, including its exception to the new 15-character naming +preference. + +The serial run compares stable producer references. The two-rank test checks +rank-owned output and compares KS binary assembly with independent text +wavefunctions from the same run, avoiding gauge-dependent historical data. +It also injects root-only and non-root file-open failures to check MPI abort. diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/STRU b/tests/08_RI/60_LIBRPA_RPA_READER_V1/STRU new file mode 100644 index 00000000000..6293228374f --- /dev/null +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/STRU @@ -0,0 +1,22 @@ +ATOMIC_SPECIES +H 1.008 H_ONCV_PBE-1.0.upf + +NUMERICAL_ORBITAL +H_gga_6au_60Ry_1s.orb + +LATTICE_CONSTANT +1.8897261254578281 + +LATTICE_VECTORS +0.000000 1.500000 1.500000 +1.500000 0.000000 1.500000 +1.500000 1.500000 0.000000 + +ATOMIC_POSITIONS +Direct + +H +0.0 +2 +0.000000 0.000000 0.000000 0 0 0 +0.500000 0.500000 0.500000 0 0 0 diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/librpa_producer_manifest.json b/tests/08_RI/60_LIBRPA_RPA_READER_V1/librpa_producer_manifest.json new file mode 100644 index 00000000000..e876b34b675 --- /dev/null +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/librpa_producer_manifest.json @@ -0,0 +1,19 @@ +{ + "output_dir": "OUT.librpa", + "reference_dir": "producer_reference", + "required": [ + {"pattern": "stru_out.txt", "kind": "stru", "reference": true, "abs_tol": 1e-7}, + {"pattern": "band_out.txt", "kind": "band", "reference": true, "abs_tol": 1e-7}, + {"pattern": "*basis*.txt", "kind": "text", "reference": true, "abs_tol": 1e-7}, + {"pattern": "bz_sample.txt", "kind": "text", "reference": true, "abs_tol": 1e-7}, + {"pattern": "Cs*_*.dat", "kind": "lri_coeff_v1", "reference": true, "abs_tol": 1e-7}, + {"pattern": "sinvS_*.dat", "kind": "shrink_sinvs_v1", "reference": true, "abs_tol": 1e-7}, + {"pattern": "V_cut_*.dat", "kind": "coulomb_v1", "reference": true, "abs_tol": 1e-7}, + {"pattern": "V_full_*.dat", "kind": "coulomb_v1", "reference": true, "abs_tol": 1e-4}, + {"pattern": "KS_wfc_0.dat", "kind": "ks_wfc_v1", "nao_dir": "OUT.autotest", "abs_tol": 1e-7}, + {"pattern": "velocity_matrix.txt", "kind": "velocity"}, + {"base_dir": ".", "pattern": "OUT.autotest/vxc.txt", "kind": "text", "reference": true, "abs_tol": 1e-7}, + {"base_dir": ".", "pattern": "OUT.autotest/KPT.info", "kind": "text", "reference": true, "abs_tol": 1e-7}, + {"base_dir": ".", "pattern": "OUT.autotest/wfk*_nao.txt", "kind": "wfc_nao", "expected_count": 2} + ] +} diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/Cs_0.dat b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/Cs_0.dat new file mode 100644 index 00000000000..a263c6ae485 Binary files /dev/null and b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/Cs_0.dat differ diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/Cs_shrink_0.dat b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/Cs_shrink_0.dat new file mode 100644 index 00000000000..a263c6ae485 Binary files /dev/null and b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/Cs_shrink_0.dat differ diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/OUT.autotest/KPT.info b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/OUT.autotest/KPT.info new file mode 100644 index 00000000000..976054a09dd --- /dev/null +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/OUT.autotest/KPT.info @@ -0,0 +1,5 @@ + nkstot now = 2 + K-POINTS DIRECT COORDINATES + KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT + 1 0.00000000 0.00000000 0.00000000 0.5000 + 2 0.00000000 0.00000000 0.50000000 0.5000 diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/OUT.autotest/vxc.txt b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/OUT.autotest/vxc.txt new file mode 100644 index 00000000000..ab0596d1cdc --- /dev/null +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/OUT.autotest/vxc.txt @@ -0,0 +1,7 @@ +2 +1 +2 +-4.0271632676450153e-01 -1.0958473443254251e+01 +-5.2768632122235570e-01 -1.4359081450564725e+01 +-4.4995197211831628e-01 -1.2243821294292463e+01 +-4.4995197204557325e-01 -1.2243821292313024e+01 diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/V_cut_1_r0.dat b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/V_cut_1_r0.dat new file mode 100644 index 00000000000..9be55b5de80 Binary files /dev/null and b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/V_cut_1_r0.dat differ diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/V_cut_2_r0.dat b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/V_cut_2_r0.dat new file mode 100644 index 00000000000..60350df41ea Binary files /dev/null and b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/V_cut_2_r0.dat differ diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/V_full_1_r0.dat b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/V_full_1_r0.dat new file mode 100644 index 00000000000..835b705451a Binary files /dev/null and b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/V_full_1_r0.dat differ diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/V_full_2_r0.dat b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/V_full_2_r0.dat new file mode 100644 index 00000000000..d24e80811b2 Binary files /dev/null and b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/V_full_2_r0.dat differ diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/aux_basis.txt b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/aux_basis.txt new file mode 100644 index 00000000000..cbfe2adfdcc --- /dev/null +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/aux_basis.txt @@ -0,0 +1,4 @@ + 1 2 abacus + 1 1 + 1 1 + 0 diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/aux_basis_s.txt b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/aux_basis_s.txt new file mode 100644 index 00000000000..cbfe2adfdcc --- /dev/null +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/aux_basis_s.txt @@ -0,0 +1,4 @@ + 1 2 abacus + 1 1 + 1 1 + 0 diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/band_out.txt b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/band_out.txt new file mode 100644 index 00000000000..eb30716871e --- /dev/null +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/band_out.txt @@ -0,0 +1,11 @@ +2 +1 +2 +2 +0.023041469995946 + 1 1 + 1 2.000000000000000 -0.443929937899325 -12.079953336433942 + 2 0.000000000000000 0.782901065125719 21.303830911957743 + 2 1 + 1 1.000000001846792 0.023041469995864 0.626990564479579 + 2 0.999999998340573 0.023041469996020 0.626990564483806 diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/basis_map.txt b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/basis_map.txt new file mode 100644 index 00000000000..d144ade7bb6 --- /dev/null +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/basis_map.txt @@ -0,0 +1,6 @@ + 1 2 2 fallback + 1 1 1 + 1 1 + 0 + 1 1 + 0 diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/bz_sample.txt b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/bz_sample.txt new file mode 100644 index 00000000000..cd7be84b255 --- /dev/null +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/bz_sample.txt @@ -0,0 +1,4 @@ +1 1 2 +2 2 + 1 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 1 1 + 2 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 5.541530791626701e-01 5.541530791626701e-01 -5.541530791626701e-01 2 2 diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/sinvS_0.dat b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/sinvS_0.dat new file mode 100644 index 00000000000..b61e78b9fd4 Binary files /dev/null and b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/sinvS_0.dat differ diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/stru_out.txt b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/stru_out.txt new file mode 100644 index 00000000000..74dbf76bcad --- /dev/null +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/stru_out.txt @@ -0,0 +1,9 @@ +0.000000000 2.834589188 2.834589188 +2.834589188 0.000000000 2.834589188 +2.834589188 2.834589188 0.000000000 +-1.108306158 1.108306158 1.108306158 +1.108306158 -1.108306158 1.108306158 +1.108306158 1.108306158 -1.108306158 +2 + 0.000000000 0.000000000 0.000000000 1 + 2.834589188 2.834589188 2.834589188 1 diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/wfc_basis.txt b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/wfc_basis.txt new file mode 100644 index 00000000000..cbfe2adfdcc --- /dev/null +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/wfc_basis.txt @@ -0,0 +1,4 @@ + 1 2 abacus + 1 1 + 1 1 + 0 diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/result.ref b/tests/08_RI/60_LIBRPA_RPA_READER_V1/result.ref new file mode 100644 index 00000000000..e7a7822ad46 --- /dev/null +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/result.ref @@ -0,0 +1,4 @@ +etotref -34.21719071548007 +etotperatomref -17.1085953577 +Etot_without_rpa -1.228213829728219 +totaltimeref 0.53 diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/INPUT b/tests/08_RI/64_LIBRPA_NSPIN2/INPUT new file mode 100644 index 00000000000..4f220dd8414 --- /dev/null +++ b/tests/08_RI/64_LIBRPA_NSPIN2/INPUT @@ -0,0 +1,28 @@ +INPUT_PARAMETERS +suffix autotest +pseudo_dir ../../PP_ORB +orbital_dir ../../PP_ORB +calculation scf +ntype 1 +nbands 2 +ecutwfc 20 +smearing_method gaussian +smearing_sigma 1e-4 +basis_type lcao +ks_solver genelpa +dft_functional pbe +rpa 1 +rpa_out_vel 1 +out_mat_xc 1 +out_wfc_lcao 1 +out_librpa_ver 1 +nspin 2 +symmetry -1 +exx_separate_loop 1 +exx_pca_threshold 1e-3 +shrink_abfs_pca_thr 1e-1 +shrink_lu_inv_thr 1e-3 +exx_singularity_correction massidda +exx_ccp_rmesh_times 1 +rpa_ccp_rmesh_times 1 +exx_real_number 1 diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/KPT b/tests/08_RI/64_LIBRPA_NSPIN2/KPT new file mode 100644 index 00000000000..a427751e525 --- /dev/null +++ b/tests/08_RI/64_LIBRPA_NSPIN2/KPT @@ -0,0 +1,4 @@ +K_POINTS +0 +Gamma +1 1 2 0 0 0 diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/README b/tests/08_RI/64_LIBRPA_NSPIN2/README new file mode 100644 index 00000000000..02844696da8 --- /dev/null +++ b/tests/08_RI/64_LIBRPA_NSPIN2/README @@ -0,0 +1,2 @@ +Check reader-v1 RPA producer output and the spin-resolved GW-preprocessor +inputs for an `nspin = 2` compact two-atom H solid. diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/STRU b/tests/08_RI/64_LIBRPA_NSPIN2/STRU new file mode 100644 index 00000000000..6293228374f --- /dev/null +++ b/tests/08_RI/64_LIBRPA_NSPIN2/STRU @@ -0,0 +1,22 @@ +ATOMIC_SPECIES +H 1.008 H_ONCV_PBE-1.0.upf + +NUMERICAL_ORBITAL +H_gga_6au_60Ry_1s.orb + +LATTICE_CONSTANT +1.8897261254578281 + +LATTICE_VECTORS +0.000000 1.500000 1.500000 +1.500000 0.000000 1.500000 +1.500000 1.500000 0.000000 + +ATOMIC_POSITIONS +Direct + +H +0.0 +2 +0.000000 0.000000 0.000000 0 0 0 +0.500000 0.500000 0.500000 0 0 0 diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/librpa_producer_manifest.json b/tests/08_RI/64_LIBRPA_NSPIN2/librpa_producer_manifest.json new file mode 100644 index 00000000000..3983bb8146f --- /dev/null +++ b/tests/08_RI/64_LIBRPA_NSPIN2/librpa_producer_manifest.json @@ -0,0 +1,19 @@ +{ + "output_dir": "OUT.librpa", + "reference_dir": "producer_reference", + "required": [ + {"pattern": "stru_out.txt", "kind": "stru", "reference": true, "abs_tol": 1e-7}, + {"pattern": "band_out.txt", "kind": "band", "reference": true, "abs_tol": 1e-7}, + {"pattern": "*basis*.txt", "kind": "text", "reference": true, "abs_tol": 1e-7}, + {"pattern": "bz_sample.txt", "kind": "text", "reference": true, "abs_tol": 1e-7}, + {"pattern": "Cs*_*.dat", "kind": "lri_coeff_v1", "reference": true, "abs_tol": 1e-7}, + {"pattern": "sinvS_*.dat", "kind": "shrink_sinvs_v1", "reference": true, "abs_tol": 1e-7}, + {"pattern": "V_cut_*.dat", "kind": "coulomb_v1", "reference": true, "abs_tol": 1e-7}, + {"pattern": "V_full_*.dat", "kind": "coulomb_v1", "reference": true, "abs_tol": 1e-4}, + {"pattern": "KS_wfc_0.dat", "kind": "ks_wfc_v1", "nao_dir": "OUT.autotest", "abs_tol": 1e-7}, + {"pattern": "velocity_matrix.txt", "kind": "velocity"}, + {"base_dir": ".", "pattern": "OUT.autotest/vxc.txt", "kind": "text", "reference": true, "abs_tol": 1e-7}, + {"base_dir": ".", "pattern": "OUT.autotest/KPT.info", "kind": "text", "reference": true, "abs_tol": 1e-7}, + {"base_dir": ".", "pattern": "OUT.autotest/wfk*s*_nao.txt", "kind": "wfc_nao", "expected_count": 4} + ] +} diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/Cs_0.dat b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/Cs_0.dat new file mode 100644 index 00000000000..a263c6ae485 Binary files /dev/null and b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/Cs_0.dat differ diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/Cs_shrink_0.dat b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/Cs_shrink_0.dat new file mode 100644 index 00000000000..a263c6ae485 Binary files /dev/null and b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/Cs_shrink_0.dat differ diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/OUT.autotest/KPT.info b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/OUT.autotest/KPT.info new file mode 100644 index 00000000000..976054a09dd --- /dev/null +++ b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/OUT.autotest/KPT.info @@ -0,0 +1,5 @@ + nkstot now = 2 + K-POINTS DIRECT COORDINATES + KPOINTS DIRECT_X DIRECT_Y DIRECT_Z WEIGHT + 1 0.00000000 0.00000000 0.00000000 0.5000 + 2 0.00000000 0.00000000 0.50000000 0.5000 diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/OUT.autotest/vxc.txt b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/OUT.autotest/vxc.txt new file mode 100644 index 00000000000..e3b1e9c2593 --- /dev/null +++ b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/OUT.autotest/vxc.txt @@ -0,0 +1,11 @@ +2 +2 +2 +-4.3702834187294770e-01 -1.1892151273928162e+01 +-5.9164290536875597e-01 -1.6099429388579743e+01 +-3.5427332707853737e-01 -9.6402717953716035e+00 +-4.1800714929822841e-01 -1.1374558070385216e+01 +-4.9624586825711253e-01 -1.3503542834508119e+01 +-4.9624586825711292e-01 -1.3503542834508130e+01 +-3.7639113000047697e-01 -1.0242128089330459e+01 +-3.7639113000047547e-01 -1.0242128089330418e+01 diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/V_cut_1_r0.dat b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/V_cut_1_r0.dat new file mode 100644 index 00000000000..9be55b5de80 Binary files /dev/null and b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/V_cut_1_r0.dat differ diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/V_cut_2_r0.dat b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/V_cut_2_r0.dat new file mode 100644 index 00000000000..60350df41ea Binary files /dev/null and b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/V_cut_2_r0.dat differ diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/V_full_1_r0.dat b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/V_full_1_r0.dat new file mode 100644 index 00000000000..835b705451a Binary files /dev/null and b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/V_full_1_r0.dat differ diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/V_full_2_r0.dat b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/V_full_2_r0.dat new file mode 100644 index 00000000000..d24e80811b2 Binary files /dev/null and b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/V_full_2_r0.dat differ diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/aux_basis.txt b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/aux_basis.txt new file mode 100644 index 00000000000..cbfe2adfdcc --- /dev/null +++ b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/aux_basis.txt @@ -0,0 +1,4 @@ + 1 2 abacus + 1 1 + 1 1 + 0 diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/aux_basis_s.txt b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/aux_basis_s.txt new file mode 100644 index 00000000000..cbfe2adfdcc --- /dev/null +++ b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/aux_basis_s.txt @@ -0,0 +1,4 @@ + 1 2 abacus + 1 1 + 1 1 + 0 diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/band_out.txt b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/band_out.txt new file mode 100644 index 00000000000..2ddadc51194 --- /dev/null +++ b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/band_out.txt @@ -0,0 +1,17 @@ +2 +2 +2 +2 +0.035791368778819 + 1 1 + 1 1.000000000000000 -0.478241953034516 -13.013631167835607 + 2 0.000000000000000 0.718944481211793 19.563482980268653 + 1 2 + 1 1.000000000000000 -0.395486938462553 -10.761751695332153 + 2 0.000000000000000 0.892580238467711 24.288354330719308 + 2 1 + 1 1.000000000000000 -0.023252426114625 -0.632730974965803 + 2 1.000000000000000 -0.023252426114625 -0.632730974965793 + 2 2 + 1 0.000000000000000 0.096602311768029 2.628683760035285 + 2 0.000000000000000 0.096602311768031 2.628683760035354 diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/basis_map.txt b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/basis_map.txt new file mode 100644 index 00000000000..d144ade7bb6 --- /dev/null +++ b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/basis_map.txt @@ -0,0 +1,6 @@ + 1 2 2 fallback + 1 1 1 + 1 1 + 0 + 1 1 + 0 diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/bz_sample.txt b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/bz_sample.txt new file mode 100644 index 00000000000..cd7be84b255 --- /dev/null +++ b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/bz_sample.txt @@ -0,0 +1,4 @@ +1 1 2 +2 2 + 1 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 1 1 + 2 5.000000000000000e-01 0.000000000000000e+00 0.000000000000000e+00 5.000000000000000e-01 5.541530791626701e-01 5.541530791626701e-01 -5.541530791626701e-01 2 2 diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/sinvS_0.dat b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/sinvS_0.dat new file mode 100644 index 00000000000..b61e78b9fd4 Binary files /dev/null and b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/sinvS_0.dat differ diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/stru_out.txt b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/stru_out.txt new file mode 100644 index 00000000000..74dbf76bcad --- /dev/null +++ b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/stru_out.txt @@ -0,0 +1,9 @@ +0.000000000 2.834589188 2.834589188 +2.834589188 0.000000000 2.834589188 +2.834589188 2.834589188 0.000000000 +-1.108306158 1.108306158 1.108306158 +1.108306158 -1.108306158 1.108306158 +1.108306158 1.108306158 -1.108306158 +2 + 0.000000000 0.000000000 0.000000000 1 + 2.834589188 2.834589188 2.834589188 1 diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/wfc_basis.txt b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/wfc_basis.txt new file mode 100644 index 00000000000..cbfe2adfdcc --- /dev/null +++ b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/wfc_basis.txt @@ -0,0 +1,4 @@ + 1 2 abacus + 1 1 + 1 1 + 0 diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/result.ref b/tests/08_RI/64_LIBRPA_NSPIN2/result.ref new file mode 100644 index 00000000000..689fbcc4bf1 --- /dev/null +++ b/tests/08_RI/64_LIBRPA_NSPIN2/result.ref @@ -0,0 +1,4 @@ +etotref -34.9850254725296 +etotperatomref -17.4925127363 +Etot_without_rpa -1.392427047610331 +totaltimeref 0.78 diff --git a/tests/08_RI/CASES_LIBRPA_PRODUCER.txt b/tests/08_RI/CASES_LIBRPA_PRODUCER.txt new file mode 100644 index 00000000000..62735a2d0de --- /dev/null +++ b/tests/08_RI/CASES_LIBRPA_PRODUCER.txt @@ -0,0 +1,2 @@ +60_LIBRPA_RPA_READER_V1 +64_LIBRPA_NSPIN2 diff --git a/tests/08_RI/CMakeLists.txt b/tests/08_RI/CMakeLists.txt index a16fc04f320..d0ace2c3361 100644 --- a/tests/08_RI/CMakeLists.txt +++ b/tests/08_RI/CMakeLists.txt @@ -13,4 +13,43 @@ else() COMMAND ${BASH} ../integrate/Autotest.sh -a ${ABACUS_BIN_PATH} -n 4 -j auto WORKING_DIRECTORY ${ABACUS_TEST_DIR}/08_RI ) + + # These compact cases validate only producer artifacts consumed by LibRPA. + # They leave the established EXX and BSE regression corpus unchanged. + add_test( + NAME 08_RI_librpa_producer + COMMAND ${BASH} ../integrate/validation_tools/run_librpa_producer.sh + ${ABACUS_BIN_PATH} ${ABACUS_TEST_DIR}/08_RI + WORKING_DIRECTORY ${ABACUS_TEST_DIR}/08_RI + ) + # Keep launcher flags in one argv item because they start with a dash and + # the Python helper's argparse would otherwise parse their values. + set(MPI_LAUNCHER_FLAGS) + foreach(flag IN LISTS MPIEXEC_PREFLAGS) + list(APPEND MPI_LAUNCHER_FLAGS "--mpi-preflag=${flag}") + endforeach() + foreach(flag IN LISTS MPIEXEC_POSTFLAGS) + list(APPEND MPI_LAUNCHER_FLAGS "--mpi-postflag=${flag}") + endforeach() + add_test( + NAME 08_RI_librpa_wavefunction_checks + COMMAND ${BASH} ${ABACUS_TEST_DIR}/integrate/validation_tools/run_librpa_wavefunction_checks.sh + ${ABACUS_TEST_DIR}/integrate/validation_tools + ) + add_test( + NAME 08_RI_librpa_producer_mpi + COMMAND ${BASH} ${ABACUS_TEST_DIR}/integrate/validation_tools/run_librpa_mpi.sh + --abacus ${ABACUS_BIN_PATH} --cases ${ABACUS_TEST_DIR}/08_RI + --mpirun ${MPIEXEC_EXECUTABLE} --mpi-np-flag=${MPIEXEC_NUMPROC_FLAG} + ${MPI_LAUNCHER_FLAGS} + ) + set_tests_properties(08_RI_librpa_producer_mpi PROPERTIES PROCESSORS 2 TIMEOUT 1200) + add_test( + NAME 08_RI_librpa_symmetry + COMMAND ${BASH} ${ABACUS_TEST_DIR}/integrate/validation_tools/run_librpa_sym.sh + --abacus ${ABACUS_BIN_PATH} --cases ${ABACUS_TEST_DIR}/08_RI + --mpirun ${MPIEXEC_EXECUTABLE} --mpi-np-flag=${MPIEXEC_NUMPROC_FLAG} + ${MPI_LAUNCHER_FLAGS} + ) + set_tests_properties(08_RI_librpa_symmetry PROPERTIES PROCESSORS 2 TIMEOUT 1800) endif() diff --git a/tests/08_RI/scf_out_xc_multik/vxc_out.ref b/tests/08_RI/scf_out_xc_multik/vxc.ref similarity index 100% rename from tests/08_RI/scf_out_xc_multik/vxc_out.ref rename to tests/08_RI/scf_out_xc_multik/vxc.ref diff --git a/tests/integrate/Autotest.sh b/tests/integrate/Autotest.sh index 27d75dd9a1d..4f49b3ade9b 100755 --- a/tests/integrate/Autotest.sh +++ b/tests/integrate/Autotest.sh @@ -43,6 +43,18 @@ cases_file=CASES_CPU.txt case='^[^#].*_.*$' # enable AddressSanitizer sanitize=false +# Skip property collectors when a dedicated validator owns the output format. +# This is a generic harness option; the harness itself does not know which +# physics module produced the files. +property_check=true +# CMake can provide the MPI launcher while standalone runs retain the default. +mpi_launcher=${ABACUS_MPIEXEC:-mpirun} +mpi_np_flag=${ABACUS_MPIEXEC_NUMPROC_FLAG:--np} +# FindMPI may require launcher-specific flags (for example +# Open MPI's --allow-run-as-root in a CI container). Keep these as arrays so +# every invocation uses the same command assembled by the CMake test setup. +read -r -a mpi_preflags <<< "${ABACUS_MPIEXEC_PREFLAGS:-}" +read -r -a mpi_postflags <<< "${ABACUS_MPIEXEC_POSTFLAGS:-}" threshold_file="threshold" # can specify the threshold for each test case @@ -53,7 +65,7 @@ threshold_file="threshold" # fatal_threshold 1 -while getopts a:n:t:c:s:r:f:go:j: flag +while getopts a:n:t:c:s:r:f:gpo:j: flag do case "${flag}" in a) abacus=${OPTARG};; @@ -64,6 +76,7 @@ do r) case=${OPTARG};; f) cases_file=${OPTARG};; g) g=true;; #generate test reference + p) property_check=false;; #skip property collectors o) nt=${OPTARG}; nt_explicit=true;; # number of OpenMP threads j) njobs=${OPTARG};; # number of concurrent test cases, or "auto" esac @@ -141,6 +154,7 @@ echo "Check accuaracy: $ca" echo "Test cases file: $cases_file" echo "Test cases regex: $case" echo "Generate reference: $g" +echo "Property collectors: $property_check" echo "--------------------------------" echo "" @@ -337,13 +351,11 @@ run_case() # This lets a serial ABACUS (ENABLE_MPI=OFF, e.g. the native # Windows build) reuse this harness unchanged. $abacus > log.txt - elif [ "$case" = "282_NO_RPA" ]; then - mpirun -np 1 $abacus > log.txt elif grep -qE '^[[:space:]]*of_ml_gene_data[[:space:]]+1([[:space:]]|$)' INPUT; then # of_ml_gene_data supports single-rank only. - mpirun -np 1 $abacus > log.txt + "$mpi_launcher" "$mpi_np_flag" 1 "${mpi_preflags[@]}" "$abacus" "${mpi_postflags[@]}" > log.txt else - mpirun -np $np $abacus > log.txt + "$mpi_launcher" "$mpi_np_flag" $np "${mpi_preflags[@]}" "$abacus" "${mpi_postflags[@]}" > log.txt fi # if ABACUS failed, print out the error message @@ -356,7 +368,10 @@ run_case() else # check the output test -d OUT.autotest || (echo "No 'OUT.autotest' dir presented. Some errors may happened in ABACUS." && exit 1) - if test -z $g + if [ "$property_check" == false ]; then + echo "[----------] Property collectors skipped; use the dedicated validator for this output contract." + let ok++ + elif test -z $g then bash -e ../../integrate/validation_tools/catch_properties.sh result.out if [ $? -ne 0 ]; then diff --git a/tests/integrate/CMakeLists.txt b/tests/integrate/CMakeLists.txt index 9f737798889..af107559be9 100644 --- a/tests/integrate/CMakeLists.txt +++ b/tests/integrate/CMakeLists.txt @@ -1,5 +1,4 @@ find_program(BASH bash) -find_package(Python3 REQUIRED) if(ENABLE_ASAN) add_test( @@ -14,4 +13,4 @@ else() COMMAND ${BASH} Autotest.sh -a ${ABACUS_BIN_PATH} -n 4 -j auto WORKING_DIRECTORY ${ABACUS_TEST_DIR}/integrate ) -endif() \ No newline at end of file +endif() diff --git a/tests/integrate/validation_tools/check_librpa_producer.py b/tests/integrate/validation_tools/check_librpa_producer.py new file mode 100644 index 00000000000..aebd439e0bd --- /dev/null +++ b/tests/integrate/validation_tools/check_librpa_producer.py @@ -0,0 +1,489 @@ +#!/usr/bin/env python3 +"""Validate the ABACUS producer-side file contract consumed by LibRPA. + +The checker deliberately separates stable producer data from gauge-dependent +wavefunction data. It validates names, headers, dimensions, finite values, +and optional tolerant references; it does not require KS eigenvector or +velocity-matrix values to be byte-identical. +""" + +import argparse +import glob +import json +import math +import re +import struct +from pathlib import Path + +from librpa_wavefunctions import check_ks_nao, check_velocity, read_ks_wfc + + +class ProducerContractError(ValueError): + """Raised when a producer output violates the declared contract.""" + + +_COULOMB_MARKER = -20129433 +_CHI0_MARKER = -41073291 +_LRICOEF_MARKER = -10267453 +_SHRINK_SINVS_MARKER = -30241621 + + +def _matches(root, pattern): + return sorted(Path(path) for path in glob.glob(str(root / pattern), recursive=True)) + + +def _number(token): + try: + value = float(token) + except (TypeError, ValueError): + return None + if not math.isfinite(value): + raise ProducerContractError("non-finite numeric value: {}".format(token)) + return value + + +def _required_number(path, token, context): + value = _number(token) + if value is None: + raise ProducerContractError("{} has a non-numeric {}: {}".format(path, context, token)) + return value + + +def _numeric_tokens(path): + values = [] + for line_number, line in enumerate(path.read_text().splitlines(), 1): + for token in line.split(): + value = _number(token) + if value is not None: + values.append((line_number, token, value)) + if not values: + raise ProducerContractError("{} contains no numeric values".format(path)) + return values + + +def _compare_text(actual, reference, tolerance): + actual_lines = actual.read_text().splitlines() + reference_lines = reference.read_text().splitlines() + # Some ABACUS text writers leave an extra blank record at EOF. It has no + # parser-visible meaning, so keep the value comparison strict while + # ignoring only trailing empty records. + while actual_lines and not actual_lines[-1].strip(): + actual_lines.pop() + while reference_lines and not reference_lines[-1].strip(): + reference_lines.pop() + if len(actual_lines) != len(reference_lines): + raise ProducerContractError( + "{} has {} lines; reference has {}".format(actual, len(actual_lines), len(reference_lines)) + ) + for line_number, (aline, rline) in enumerate(zip(actual_lines, reference_lines), 1): + atokens = aline.split() + rtokens = rline.split() + if len(atokens) != len(rtokens): + raise ProducerContractError("{} line {} token count differs".format(actual, line_number)) + for column, (atoken, rtoken) in enumerate(zip(atokens, rtokens), 1): + avalue = _number(atoken) + rvalue = _number(rtoken) + if avalue is None or rvalue is None: + if atoken != rtoken: + raise ProducerContractError("{} line {} column {} differs".format(actual, line_number, column)) + elif abs(avalue - rvalue) > tolerance: + raise ProducerContractError( + "{} line {} column {} differs by {} (tol {})".format( + actual, line_number, column, abs(avalue - rvalue), tolerance + ) + ) + + +def _check_stru(path, minimum_symmetry_rows): + lines = path.read_text().splitlines() + if len(lines) < 8: + raise ProducerContractError("{} is too short for stru_out".format(path)) + for line in lines[:6]: + if len(line.split()) != 3: + raise ProducerContractError("{} has an invalid lattice block".format(path)) + for token in line.split(): + _number(token) + try: + natom = int(lines[6].split()[0]) + except (IndexError, ValueError): + raise ProducerContractError("{} has an invalid atom count".format(path)) + if natom <= 0 or len(lines) < 7 + natom: + raise ProducerContractError("{} has an invalid atom block".format(path)) + for line in lines[7 : 7 + natom]: + if len(line.split()) != 4: + raise ProducerContractError("{} has an invalid atom row".format(path)) + for token in line.split()[:3]: + _number(token) + try: + int(line.split()[3]) + except ValueError: + raise ProducerContractError("{} has an invalid atom type".format(path)) + row_match = None + row_index = None + for index, line in enumerate(lines[7 + natom :], 7 + natom): + match = re.match(r"^\s*(\d+)\s+row\s*$", line) + if match: + row_match = int(match.group(1)) + row_index = index + break + if minimum_symmetry_rows is not None: + if row_match is None or row_match < int(minimum_symmetry_rows): + raise ProducerContractError("{} has too few symmetry rows".format(path)) + if row_match is not None: + if len(lines) < row_index + 1 + row_match: + raise ProducerContractError("{} symmetry block is truncated".format(path)) + for line in lines[row_index + 1 : row_index + 1 + row_match]: + tokens = line.split() + if len(tokens) != 12: + raise ProducerContractError("{} has an invalid symmetry row".format(path)) + for token in tokens[:9]: + try: + int(token) + except ValueError: + raise ProducerContractError("{} has a non-integer symmetry rotation".format(path)) + for token in tokens[9:]: + _number(token) +def _check_wfc_nao(path): + """Validate the text LCAO wavefunction format consumed by GW preprocessing.""" + lines = [line.strip() for line in path.read_text().splitlines() if line.strip()] + if len(lines) < 6: + raise ProducerContractError("{} is too short for an LCAO wavefunction".format(path)) + + complex_layout = "(index of k points)" in lines[0] + header_index = 0 + coefficient_width = 1 + if complex_layout: + try: + if int(lines[0].split()[0]) <= 0: + raise ValueError + except (IndexError, ValueError): + raise ProducerContractError("{} has an invalid k-point index".format(path)) + coordinates = lines[1].split() + if len(coordinates) != 3: + raise ProducerContractError("{} has an invalid k-point coordinate".format(path)) + for coordinate in coordinates: + _number(coordinate) + header_index = 2 + coefficient_width = 2 + + try: + nbands = int(lines[header_index].split()[0]) + nlocal = int(lines[header_index + 1].split()[0]) + except (IndexError, ValueError): + raise ProducerContractError("{} has an invalid wavefunction dimension header".format(path)) + if nbands <= 0 or nlocal <= 0: + raise ProducerContractError("{} has non-positive wavefunction dimensions".format(path)) + + first_band_index = header_index + 2 + band_indices = [ + index for index, line in enumerate(lines[first_band_index:], first_band_index) if line.endswith("(band)") + ] + if len(band_indices) != nbands: + raise ProducerContractError("{} has {} bands; expected {}".format(path, len(band_indices), nbands)) + + for iband, band_index in enumerate(band_indices, 1): + try: + band_number = int(lines[band_index].split()[0]) + except (IndexError, ValueError): + raise ProducerContractError("{} has an invalid band index".format(path)) + if band_number != iband: + raise ProducerContractError("{} has a non-contiguous band index".format(path)) + if band_index + 2 >= len(lines): + raise ProducerContractError("{} has an incomplete band record".format(path)) + try: + energy = lines[band_index + 1].split()[0] + occupation = lines[band_index + 2].split()[0] + except IndexError: + raise ProducerContractError("{} has an incomplete band record".format(path)) + _required_number(path, energy, "band energy") + _required_number(path, occupation, "band occupation") + next_band_index = band_indices[iband] if iband < nbands else len(lines) + coefficients = [] + for line in lines[band_index + 3 : next_band_index]: + for token in line.split(): + coefficients.append(_required_number(path, token, "wavefunction coefficient")) + expected_coefficients = coefficient_width * nlocal + if len(coefficients) != expected_coefficients: + raise ProducerContractError( + "{} band {} has {} coefficients; expected {}".format( + path, iband, len(coefficients), expected_coefficients + ) + ) + + +def _upper_pair(pair_index, natom): + current = 0 + for iatom in range(natom): + for jatom in range(iatom, natom): + if current == pair_index: + return iatom, jatom + current += 1 + raise ProducerContractError("invalid upper-triangular atom pair index {}".format(pair_index)) + + +def _check_v1_binary(path, kind): + data = path.read_bytes() + if kind == "coulomb_v1": + if len(data) < 24: + raise ProducerContractError("{} is too short for a Coulomb v1 header".format(path)) + marker, iq, naux, value_flag, natom, nblocks = struct.unpack_from("<6i", data, 0) + header_end = 24 + if marker != _COULOMB_MARKER or iq <= 0 or naux <= 0 or natom <= 0 or nblocks < 0: + raise ProducerContractError("{} has an invalid Coulomb v1 header".format(path)) + else: + if len(data) < 44: + raise ProducerContractError("{} is too short for a chi0 v1 header".format(path)) + marker, iq, ifrequency, naux, value_flag, natom = struct.unpack_from("<6i", data, 0) + omega, weight = struct.unpack_from("<2d", data, 24) + nblocks = struct.unpack_from(" len(data): + raise ProducerContractError("{} has an invalid v1 payload offset".format(path)) + blocks.append((offset, payload_end)) + if blocks and any(right > next_left for (_, right), (next_left, _) in zip(sorted(blocks), sorted(blocks)[1:])): + raise ProducerContractError("{} has overlapping v1 payload blocks".format(path)) + + +def _v1_layout(path, kind): + """Read the stable metadata/table/payload regions of a v1 binary file.""" + data = path.read_bytes() + if kind == "coulomb_v1": + if len(data) < 24: + raise ProducerContractError("{} is too short for a Coulomb v1 header".format(path)) + header = struct.unpack_from("<6i", data, 0) + marker, iq, naux, value_flag, natom, nblocks = header + floats = () + header_end = 24 + else: + if len(data) < 44: + raise ProducerContractError("{} is too short for a chi0 v1 header".format(path)) + header = struct.unpack_from("<6i", data, 0) + marker, iq, ifrequency, naux, value_flag, natom = header + floats = struct.unpack_from("<2d", data, 24) + nblocks = struct.unpack_from(" tolerance: + raise ProducerContractError("{} v1 header value {} differs from reference".format(actual, index)) + if (len(adata) - apayload_start) % 8 != 0 or (len(rdata) - rpayload_start) % 8 != 0: + raise ProducerContractError("{} v1 payload is not a sequence of doubles".format(actual)) + if apayload_start != rpayload_start: + raise ProducerContractError("{} v1 payload layout differs from {}".format(actual, reference)) + for index in range(apayload_start, len(adata), 8): + avalue = struct.unpack_from(" tolerance: + raise ProducerContractError("{} v1 payload value differs at byte {} (tol {})".format(actual, index, tolerance)) + + +def _lri_layout(path, kind): + data = path.read_bytes() + if kind == "lri_coeff_v1": + if len(data) < 28: + raise ProducerContractError("{} is too short for an LRI coefficient v1 header".format(path)) + marker, natom, ncell = struct.unpack_from("<3i", data, 0) + nrecords, nrecords_max = struct.unpack_from("<2q", data, 12) + if marker != _LRICOEF_MARKER or natom <= 0 or ncell < 0 or nrecords < 0 or nrecords > nrecords_max: + raise ProducerContractError("{} has an invalid LRI coefficient v1 header".format(path)) + record_size = 36 + table_start = 28 + table_end = table_start + record_size * nrecords_max + records = [] + for index in range(nrecords_max): + offset = table_start + record_size * index + ia1, ia2, r0, r1, r2 = struct.unpack_from("<5i", data, offset) + max_abs = struct.unpack_from(" tolerance: + raise ProducerContractError("{} v1 record {} differs from reference".format(actual, index)) + else: + for index, (arecord, rrecord) in enumerate(zip(arecords, rrecords)): + if arecord[0] != rrecord[0] or arecord[2] != rrecord[2]: + raise ProducerContractError("{} v1 record {} differs from {}".format(actual, index, reference)) + if abs(arecord[1] - rrecord[1]) > tolerance: + raise ProducerContractError("{} v1 record {} differs from reference".format(actual, index)) + start = atable_end + if start != rtable_end: + raise ProducerContractError("{} v1 payload layout differs from {}".format(actual, reference)) + for index in range(start, len(adata), 8): + avalue = struct.unpack_from(" tolerance: + raise ProducerContractError("{} v1 payload value differs at byte {} (tol {})".format(actual, index, tolerance)) + + +def _check_file(path, entry): + kind = entry.get("kind", "numeric") + if not path.is_file() or path.stat().st_size == 0: + raise ProducerContractError("required producer file is missing or empty: {}".format(path)) + if kind == "gauge" or kind == "presence": + return + if kind == "stru": + _check_stru(path, entry.get("symmetry_rows")) + elif kind == "wfc_nao": + _check_wfc_nao(path) + elif kind == "ks_wfc_v1": + read_ks_wfc(path) + elif kind == "velocity": + check_velocity(path) + elif kind == "band": + lines = path.read_text().splitlines() + if len(lines) < 5: + raise ProducerContractError("{} is too short for band_out".format(path)) + for index in range(4): + try: + if int(lines[index].split()[0]) <= 0: + raise ValueError + except (IndexError, ValueError): + raise ProducerContractError("{} has an invalid band_out header".format(path)) + _number(lines[4].split()[0]) + for line in lines[5:]: + for token in line.split(): + _number(token) + elif kind in ("coulomb_v1", "chi0_v1"): + _check_v1_binary(path, kind) + elif kind in ("lri_coeff_v1", "shrink_sinvs_v1"): + _lri_layout(path, kind) + elif kind in ("numeric", "text"): + _numeric_tokens(path) + else: + raise ProducerContractError("unknown producer contract kind: {}".format(kind)) + + +def check_manifest(root, manifest): + """Return diagnostics (empty on success) or raise on contract failure.""" + root = Path(root) + output_root = root / manifest.get("output_dir", ".") + if not output_root.is_dir(): + raise ProducerContractError("producer output directory is missing: {}".format(output_root)) + reference_root = root / manifest["reference_dir"] if manifest.get("reference_dir") else None + diagnostics = [] + for entry in manifest.get("required", []): + entry_root = root / entry.get("base_dir", manifest.get("output_dir", ".")) + matches = _matches(entry_root, entry["pattern"]) + if not matches: + raise ProducerContractError("no producer files match {}".format(entry["pattern"])) + expected_count = entry.get("expected_count") + if expected_count is not None and len(matches) != int(expected_count): + raise ProducerContractError( + "{} matches {} files; expected {}".format(entry["pattern"], len(matches), expected_count) + ) + for path in matches: + try: + _check_file(path, entry) + if entry.get("kind") in ("ks_wfc_v1", "velocity"): + dims = read_ks_wfc(path)[0] if entry["kind"] == "ks_wfc_v1" else check_velocity(path) + band = output_root / "band_out.txt" + if band.is_file(): + nk, nspin, nbands, nbasis = [int(line.split()[0]) for line in band.read_text().splitlines()[:4]] + if dims != (nk, nspin, nbands, nbasis): + raise ValueError("{} dimensions disagree with band_out.txt".format(path)) + if entry.get("kind") == "ks_wfc_v1" and entry.get("nao_dir"): + check_ks_nao(path, root / entry["nao_dir"], float(entry.get("abs_tol", 1.0e-7))) + except (ValueError, struct.error) as error: + raise ProducerContractError(str(error)) + if entry.get("reference"): + if reference_root is None: + raise ProducerContractError("reference requested without reference_dir") + reference = reference_root / path.relative_to(entry_root) + if not reference.is_file(): + raise ProducerContractError("missing producer reference: {}".format(reference)) + tolerance = float(entry.get("abs_tol", 1.0e-8)) + if entry.get("kind", "numeric") in ("coulomb_v1", "chi0_v1"): + _compare_v1_binary(path, reference, entry["kind"], tolerance) + elif entry.get("kind", "numeric") in ("lri_coeff_v1", "shrink_sinvs_v1"): + _compare_lri_binary(path, reference, entry["kind"], tolerance) + else: + _compare_text(path, reference, tolerance) + for entry in manifest.get("optional", []): + entry_root = root / entry.get("base_dir", manifest.get("output_dir", ".")) + for path in _matches(entry_root, entry["pattern"]): + _check_file(path, entry) + return diagnostics + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", required=True, type=Path) + parser.add_argument("--manifest", required=True, type=Path) + args = parser.parse_args(argv) + manifest = json.loads(args.manifest.read_text()) + try: + check_manifest(args.root, manifest) + except ProducerContractError as error: + parser.error(str(error)) + print("LibRPA producer contract: PASS") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/integrate/validation_tools/librpa_wavefunctions.py b/tests/integrate/validation_tools/librpa_wavefunctions.py new file mode 100644 index 00000000000..b0cdac43f4b --- /dev/null +++ b/tests/integrate/validation_tools/librpa_wavefunctions.py @@ -0,0 +1,81 @@ +"""Structural and same-run checks for gauge-dependent LibRPA producer data.""" + +import math +import struct + + +def read_ks_wfc(path): + data = path.read_bytes() + if len(data) < 24: + raise ValueError("{} has a truncated KS header".format(path)) + marker, kind, nk, nspin, nbands, nbasis = struct.unpack_from("<6i", data) + if marker != -12345679 or kind != 28 or min(nk, nspin, nbands, nbasis) <= 0: + raise ValueError("{} has an invalid KS header".format(path)) + if nspin not in (1, 2): + raise ValueError("{} has an invalid KS spin count".format(path)) + start = 24 + 12 * nk + count = nspin * nbands * nbasis + if len(data) != start + 16 * nk * count: + raise ValueError("{} has an inconsistent KS payload size".format(path)) + records = [] + for ik in range(nk): + index, offset = struct.unpack_from(" tolerance: + raise ValueError("{} KS payload disagrees with {}".format(path, source)) + + +def check_velocity(path): + lines = [line.split() for line in path.read_text().splitlines() if line.strip()] + if len(lines) < 4 or any(len(line) != 1 for line in lines[:4]): + raise ValueError("{} has a truncated velocity header".format(path)) + nk, nspin, nbands, nbasis = [int(line[0]) for line in lines[:4]] + if min(nk, nspin, nbands, nbasis) <= 0 or nspin not in (1, 2): + raise ValueError("{} has invalid velocity dimensions".format(path)) + if len(lines) != 4 + 3 * nk * nspin * (1 + nbands * nbands): + raise ValueError("{} has an inconsistent velocity payload size".format(path)) + cursor = 4 + for spin in range(1, nspin + 1): + for ik in range(1, nk + 1): + for direction in range(1, 4): + if [int(value) for value in lines[cursor]] != [direction, ik, spin]: + raise ValueError("{} has an invalid velocity block index".format(path)) + cursor += 1 + for _ in range(nbands * nbands): + row = lines[cursor] + if len(row) != 2 or not all(math.isfinite(float(value)) for value in row): + raise ValueError("{} has invalid velocity values".format(path)) + cursor += 1 + return nk, nspin, nbands, nbasis diff --git a/tests/integrate/validation_tools/props_cube.sh b/tests/integrate/validation_tools/props_cube.sh index fb048df4f93..1dfd83270ff 100644 --- a/tests/integrate/validation_tools/props_cube.sh +++ b/tests/integrate/validation_tools/props_cube.sh @@ -147,7 +147,7 @@ fi # wave functions in LCAO basis # echo "$has_lowf" # test out_wfc_lcao > 0 #-------------------------------------------- -if ! test -z "$has_lowf" && [ $has_lowf == 1 ]; then +if ! test -z "$has_lowf" && [ "$has_lowf" == 1 ]; then if ! test -z "$gamma_only" && [ $gamma_only == 1 ]; then wfc_cal=OUT.autotest/wf_nao.txt wfc_ref=wf_nao.txt.ref @@ -159,18 +159,18 @@ if ! test -z "$has_lowf" && [ $has_lowf == 1 ]; then wfc_name=wfk2_nao input_file=wfk2_nao fi - awk 'BEGIN {flag=999} - { - if($2 == "(band)") {flag=2;print $0} - else if(flag>0) {flag-=1;print $0} - else if(flag==0) - { - for(i=1;i<=NF/2;i++) - {printf "%.10e ",sqrt( $(2*i)*$(2*i)+$(2*i-1)*$(2*i-1) )}; - printf "\n" - } - else {print $0} - }' OUT.autotest/"$input_file".txt > OUT.autotest/"$wfc_name"_mod.txt + awk 'BEGIN {flag=999} + { + if($2 == "(band)") {flag=2;print $0} + else if(flag>0) {flag-=1;print $0} + else if(flag==0) + { + for(i=1;i<=NF/2;i++) + {printf "%.10e ",sqrt( $(2*i)*$(2*i)+$(2*i-1)*$(2*i-1) )}; + printf "\n" + } + else {print $0} + }' OUT.autotest/"$input_file".txt > OUT.autotest/"$wfc_name"_mod.txt wfc_cal=OUT.autotest/"$wfc_name"_mod.txt wfc_ref="$wfc_name"_mod.txt.ref fi diff --git a/tests/integrate/validation_tools/props_mat.sh b/tests/integrate/validation_tools/props_mat.sh index 1ec823fc41a..3123a8d4fc9 100644 --- a/tests/integrate/validation_tools/props_mat.sh +++ b/tests/integrate/validation_tools/props_mat.sh @@ -204,7 +204,7 @@ fi #-------------------------------- # exchange-correlation potential #-------------------------------- -if ! test -z "$has_xc" && [ $has_xc == 1 ]; then +if ! test -z "$has_xc" && [ "$has_xc" == 1 ]; then if ! test -z "$gamma_only" && [ $gamma_only == 1 ]; then xcref=vxc_nao.txt.ref xccal=OUT.autotest/vxc_nao.txt @@ -212,8 +212,8 @@ if ! test -z "$has_xc" && [ $has_xc == 1 ]; then xcref=vxck2_nao.txt.ref xccal=OUT.autotest/vxck2_nao.txt fi - oeref=vxc_out.ref - oecal=OUT.autotest/vxc_out.dat + oeref=vxc.ref + oecal=OUT.autotest/vxc.txt python3 $COMPARE_SCRIPT $xcref $xccal 4 echo "CompareVXC_pass $?" >>$props_result_file python3 $COMPARE_SCRIPT $oeref $oecal 5 diff --git a/tests/integrate/validation_tools/run_librpa_mpi.py b/tests/integrate/validation_tools/run_librpa_mpi.py new file mode 100644 index 00000000000..3c504cb6357 --- /dev/null +++ b/tests/integrate/validation_tools/run_librpa_mpi.py @@ -0,0 +1,119 @@ +"""Run the producer regression on two MPI ranks in an isolated case tree.""" + +import argparse +import json +import os +from pathlib import Path +import re +import shutil +import subprocess +import tempfile + +from check_librpa_producer import ProducerContractError, check_manifest + + +def ignore_outputs(directory, names): + if Path(directory).name == "producer_reference": + return [] + return [name for name in names if name.startswith("OUT.") or name in ("log.txt", "result.out")] + + +def mpi_command(args, ranks): + return [args.mpirun, args.mpi_np_flag, str(ranks), *args.mpi_preflag, + args.abacus, *args.mpi_postflag] + + +def use_mpi_solver(case): + """Use a distributed solver for the two-rank compact test copies.""" + input_path = Path(case) / "INPUT" + text = input_path.read_text() + text, count = re.subn(r"(?m)^ks_solver\s+.*$", "ks_solver scalapack_gvx", text) + if count != 1: + raise RuntimeError("{} must contain exactly one ks_solver entry".format(input_path)) + input_path.write_text(text) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--abacus", required=True) + parser.add_argument("--cases", required=True, type=Path) + parser.add_argument("--mpirun", default="mpirun") + parser.add_argument("--mpi-np-flag", default="-np") + parser.add_argument("--mpi-preflag", action="append", default=[]) + parser.add_argument("--mpi-postflag", action="append", default=[]) + args = parser.parse_args() + source = args.cases.resolve() + cases = (source / "CASES_LIBRPA_PRODUCER.txt").read_text().split() + with tempfile.TemporaryDirectory(prefix="abacus-librpa-mpi-") as directory: + root = Path(directory) + work = root / "08_RI" + work.mkdir() + (root / "PP_ORB").symlink_to(source.parent / "PP_ORB", target_is_directory=True) + (root / "integrate").symlink_to(source.parent / "integrate", target_is_directory=True) + shutil.copy2(source / "CASES_LIBRPA_PRODUCER.txt", work) + for case in cases: + target = work / case + shutil.copytree(source / case, target, ignore=ignore_outputs) + use_mpi_solver(target) + # Binary rank partitions differ from the single-rank reference. + # Stable text remains compared; KS data use the same-run NAO writer. + manifest_path = target / "librpa_producer_manifest.json" + manifest = json.loads(manifest_path.read_text()) + for entry in manifest["required"]: + if entry["kind"] in ("lri_coeff_v1", "shrink_sinvs_v1", "coulomb_v1"): + entry["reference"] = False + manifest_path.write_text(json.dumps(manifest, indent=2) + "\n") + env = dict(os.environ, + OMP_NUM_THREADS="1", + MKL_NUM_THREADS="1", + ABACUS_MPIEXEC=args.mpirun, + ABACUS_MPIEXEC_NUMPROC_FLAG=args.mpi_np_flag, + ABACUS_MPIEXEC_PREFLAGS=" ".join(args.mpi_preflag), + ABACUS_MPIEXEC_POSTFLAGS=" ".join(args.mpi_postflag)) + subprocess.run(["bash", "../integrate/Autotest.sh", "-a", args.abacus, "-n", "2", "-j", "1", + "-p", "-f", "CASES_LIBRPA_PRODUCER.txt"], cwd=work, env=env, check=True, timeout=600) + for case in cases: + target = work / case + try: + check_manifest(target, json.loads((target / "librpa_producer_manifest.json").read_text())) + except ProducerContractError as error: + raise RuntimeError("{} producer contract failed: {}".format(case, error)) + for case in cases: + out = work / case / "OUT.librpa" + if not (out / "Cs_1.dat").is_file() or not list(out.glob("V_full_*_r1.dat")): + raise RuntimeError("{} did not produce rank-1 output".format(case)) + + invalid_kpt = work / "invalid_kpt_reader_v1" + shutil.copytree(source / cases[0], invalid_kpt, ignore=ignore_outputs) + (invalid_kpt / "KPT").write_text( + "K_POINTS\n2\nDirect\n0.0 0.0 0.0 0.5\n0.5 0.0 0.0 0.5\n" + ) + invalid = subprocess.run(mpi_command(args, 2), cwd=invalid_kpt, env=env, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + universal_newlines=True, timeout=90) + expected_error = "out_librpa_ver=1 requires a uniform Monkhorst-Pack k-point grid." + if invalid.returncode == 0 or expected_error not in invalid.stdout: + raise RuntimeError("reader-v1 accepted an explicit KPT list or failed unclearly:\n{}".format(invalid.stdout)) + print("Reader-v1 KPT validation: PASS (explicit list rejected before SCF)") + + # Inject a rank-0-only and a rank-1-only file-open failure. Both must + # terminate the MPI job promptly, instead of hanging in a collective. + for name in ("bz_sample.txt", "Cs_1.dat"): + case = root / ("failure_" + name.replace(".", "_")) + shutil.copytree(source / cases[0], case, ignore=ignore_outputs) + use_mpi_solver(case) + # These cases are one directory shallower than the positive runs. + inp = case / "INPUT" + inp.write_text(inp.read_text().replace("../../PP_ORB", str(source.parent / "PP_ORB"))) + (case / "OUT.librpa" / name).mkdir(parents=True) + run = subprocess.run(mpi_command(args, 2), cwd=case, env=env, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + universal_newlines=True, timeout=90) + if run.returncode == 0 or "RPA producer output failed:" not in run.stdout: + raise RuntimeError("{} did not report a communicator-wide failure:\n{}".format(name, run.stdout)) + print("MPI output failure check: PASS ({})".format(name)) + print("Two-rank LibRPA producer regression: PASS") + + +if __name__ == "__main__": + main() diff --git a/tests/integrate/validation_tools/run_librpa_mpi.sh b/tests/integrate/validation_tools/run_librpa_mpi.sh new file mode 100755 index 00000000000..8509efb4ee7 --- /dev/null +++ b/tests/integrate/validation_tools/run_librpa_mpi.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -euo pipefail +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +exec python3 "$script_dir/run_librpa_mpi.py" "$@" diff --git a/tests/integrate/validation_tools/run_librpa_producer.sh b/tests/integrate/validation_tools/run_librpa_producer.sh new file mode 100755 index 00000000000..ab41653e004 --- /dev/null +++ b/tests/integrate/validation_tools/run_librpa_producer.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -euo pipefail + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 ABACUS_EXECUTABLE CASES_DIRECTORY" >&2 + exit 2 +fi + +abacus=$1 +cases_dir=$2 +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +cd "$cases_dir" +bash "$script_dir/../Autotest.sh" -a "$abacus" -n 1 -j auto -p -f CASES_LIBRPA_PRODUCER.txt + +while read -r case_name; do + [ -z "$case_name" ] && continue + case_dir="$cases_dir/$case_name" + (cd "$case_dir" && python3 "$script_dir/check_librpa_producer.py" \ + --root . --manifest librpa_producer_manifest.json) +done < CASES_LIBRPA_PRODUCER.txt diff --git a/tests/integrate/validation_tools/run_librpa_sym.py b/tests/integrate/validation_tools/run_librpa_sym.py new file mode 100644 index 00000000000..2d855f6d573 --- /dev/null +++ b/tests/integrate/validation_tools/run_librpa_sym.py @@ -0,0 +1,141 @@ +"""Check real full/irreducible-grid producer handoffs using temporary cases.""" + +import argparse +from collections import Counter +import json +import math +import os +from pathlib import Path +import re +import shutil +import struct +import subprocess +import tempfile + +from librpa_wavefunctions import check_ks_nao, read_ks_wfc +from run_librpa_mpi import ignore_outputs, mpi_command, use_mpi_solver + + +def check_output(case, mesh, symmetry, nspin): + out = case / "OUT.librpa" + rows = [line.split() for line in (out / "bz_sample.txt").read_text().splitlines()] + assert tuple(map(int, rows[0])) == mesh + nk, nq = map(int, rows[1]) + full = mesh[0] * mesh[1] * mesh[2] + assert 0 < nk <= full and nk == nq + weights = [float(row[1]) for row in rows[2:2 + nk]] + assert abs(sum(weights) - 1) < 1e-12 + for ik, row in enumerate(rows[2:2 + nk], 1): + assert len(row) == 10 and int(row[0]) == ik + assert list(map(int, row[-2:])) == [ik, ik] + if nk < full: + tail = rows[2 + nk:] + assert tail[0] == ["full_kmap", str(full)] and len(tail) == full + 1 + reps = Counter() + coordinates = set() + for ik, row in enumerate(tail[1:], 1): + assert len(row) == 5 and int(row[0]) == ik + rep = int(row[4]) + assert 1 <= rep <= nk + point = tuple(float(x) for x in row[1:4]) + assert all(math.isfinite(x) for x in point) and point not in coordinates + coordinates.add(point) + reps[rep] += 1 + for ik, weight in enumerate(weights, 1): + assert abs(weight - reps[ik] / full) < 1e-12 + else: + assert len(rows) == nk + 2 + if symmetry == -1: + assert nk == full + if symmetry == 1 or (symmetry == 0 and mesh == (1, 1, 3)): + assert nk < full, "The test must exercise an actual reduction" + dims, _ = read_ks_wfc(out / "KS_wfc_0.dat") + bands = (out / "band_out.txt").read_text().split() + assert dims[:2] == (nk, nspin) + assert tuple(map(int, bands[:4])) == (nk, nspin, dims[2], dims[3]) + manifest = json.loads((case / "librpa_producer_manifest.json").read_text()) + tolerance = next(entry["abs_tol"] for entry in manifest["required"] if entry["kind"] == "ks_wfc_v1") + check_ks_nao(out / "KS_wfc_0.dat", case / "OUT.autotest", tolerance) + for iq in range(1, nq + 1): + for rank in (0, 1): + data = (out / "V_full_{}_r{}.dat".format(iq, rank)).read_bytes() + header = struct.unpack_from("<6i", data) + assert header[0] == -20129433 and header[1] == iq and header[3] == 1 + assert (out / "Cs_1.dat").is_file() + stru = (out / "stru_out.txt").read_text().splitlines() + if symmetry == 1: + begin = 7 + int(stru[6]) + count, convention = stru[begin].split() + assert int(count) > 1 and convention == "row" + assert len(stru[begin + 1:]) == int(count) + assert all(len(row.split()) == 12 for row in stru[begin + 1:]) + for row in stru[begin + 1:]: + rotation = list(map(int, row.split()[:9])) + for i in range(3): + for j in range(3): + step = rotation[3 * i + j] * mesh[i] / mesh[j] + assert abs(step - round(step)) < 1e-12, "Operation does not preserve the BvK mesh" + return {"case": str(case), "mesh": mesh, "symmetry": symmetry, + "nspin": nspin, "nk_full": full, "nk_scf": nk, "nq": nq} + + +def run(args, root): + source = args.cases.resolve() + work = root / "08_RI" + work.mkdir() + (root / "PP_ORB").symlink_to(source.parent / "PP_ORB", target_is_directory=True) + results = [] + env = dict(os.environ, + OMP_NUM_THREADS="1", + MKL_NUM_THREADS="1", + ABACUS_MPIEXEC=args.mpirun, + ABACUS_MPIEXEC_NUMPROC_FLAG=args.mpi_np_flag, + ABACUS_MPIEXEC_PREFLAGS=" ".join(args.mpi_preflag), + ABACUS_MPIEXEC_POSTFLAGS=" ".join(args.mpi_postflag)) + cases = (source / "CASES_LIBRPA_PRODUCER.txt").read_text().split() + for name, nspin in zip(cases, (1, 2)): + for mesh in ((1, 1, 3), (2, 2, 2)): + for symmetry in (-1, 0, 1): + case = work / "{}_{}_s{}".format(name, "".join(map(str, mesh)), symmetry) + shutil.copytree(source / name, case, ignore=ignore_outputs) + use_mpi_solver(case) + inp = case / "INPUT" + inp.write_text(re.sub(r"(?m)^symmetry\s+.*$", "symmetry {}".format(symmetry), inp.read_text())) + with inp.open("a") as stream: + stream.write("scf_thr 1e-10\n") + if nspin == 2: + # Fix the spin populations in these comparisons. Otherwise + # the metallic full-grid run can spontaneously magnetize. + with inp.open("a") as stream: + stream.write("nupdown 1e-12\n") + (case / "KPT").write_text("K_POINTS\n0\nGamma\n{} {} {} 0 0 0\n".format(*mesh)) + with (case / "producer.log").open("w") as log: + subprocess.run(mpi_command(args, 2), cwd=case, env=env, + stdout=log, stderr=subprocess.STDOUT, check=True, timeout=120) + result = check_output(case, mesh, symmetry, nspin) + results.append(result) + print(json.dumps(result), flush=True) + (root / "summary.json").write_text(json.dumps(results, indent=2) + "\n") + print("Symmetry producer checks: PASS (12 two-rank runs)") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--abacus", required=True) + parser.add_argument("--cases", required=True, type=Path) + parser.add_argument("--mpirun", default="mpirun") + parser.add_argument("--mpi-np-flag", default="-np") + parser.add_argument("--mpi-preflag", action="append", default=[]) + parser.add_argument("--mpi-postflag", action="append", default=[]) + parser.add_argument("--work", type=Path, help="Retain run artifacts in a new directory") + args = parser.parse_args() + if args.work: + args.work.mkdir(parents=True) + run(args, args.work.resolve()) + else: + with tempfile.TemporaryDirectory(prefix="abacus-librpa-sym-") as directory: + run(args, Path(directory)) + + +if __name__ == "__main__": + main() diff --git a/tests/integrate/validation_tools/run_librpa_sym.sh b/tests/integrate/validation_tools/run_librpa_sym.sh new file mode 100755 index 00000000000..7d215de30e8 --- /dev/null +++ b/tests/integrate/validation_tools/run_librpa_sym.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -euo pipefail +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +exec python3 "$script_dir/run_librpa_sym.py" "$@" diff --git a/tests/integrate/validation_tools/run_librpa_wavefunction_checks.sh b/tests/integrate/validation_tools/run_librpa_wavefunction_checks.sh new file mode 100755 index 00000000000..ed0d6316f72 --- /dev/null +++ b/tests/integrate/validation_tools/run_librpa_wavefunction_checks.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -euo pipefail + +if [ "$#" -ne 1 ]; then + echo "Usage: $0 VALIDATION_TOOL_DIRECTORY" >&2 + exit 2 +fi + +python3 -m unittest discover -s "$1" -p test_librpa_wavefunctions.py diff --git a/tests/integrate/validation_tools/test_librpa_wavefunctions.py b/tests/integrate/validation_tools/test_librpa_wavefunctions.py new file mode 100644 index 00000000000..0344400d53b --- /dev/null +++ b/tests/integrate/validation_tools/test_librpa_wavefunctions.py @@ -0,0 +1,98 @@ +"""Reject malformed producer data, including MPI-IO assembly corruption.""" + +from pathlib import Path +import struct +import tempfile +import unittest + +from librpa_wavefunctions import check_ks_nao, check_velocity, read_ks_wfc +from check_librpa_producer import check_manifest, ProducerContractError + + +class WavefunctionContractTests(unittest.TestCase): + def setUp(self): + self.directory = tempfile.TemporaryDirectory() + self.root = Path(self.directory.name) + self.ks = self.root / "KS_wfc_0.dat" + # Two bands with distinct complex coefficients expose reordering. + self.data = struct.pack("<6iiq8d", -12345679, 28, 1, 1, 2, 2, 1, 36, + 1, 0.5, 2, -0.5, 3, 0.25, 4, -0.25) + self.ks.write_bytes(self.data) + (self.root / "wfk1_nao.txt").write_text( + "1 (index of k points)\n0 0 0\n2 (number of bands)\n2 (number of orbitals)\n" + "1 (band)\n0 (Ry)\n1 (occupation)\n1 0.5 2 -0.5\n" + "2 (band)\n1 (Ry)\n0 (occupation)\n3 0.25 4 -0.25\n") + + def tearDown(self): + self.directory.cleanup() + + def test_valid_ks_matches_same_run_text(self): + self.assertEqual(read_ks_wfc(self.ks)[0], (1, 1, 2, 2)) + check_ks_nao(self.ks, self.root, 1e-7) + + def test_band_header_dimension_order(self): + band = self.root / "band_out.txt" + band.write_text("1\n1\n2\n2\n") + manifest = {"required": [{"pattern": self.ks.name, "kind": "ks_wfc_v1"}]} + check_manifest(self.root, manifest) + band.write_text("2\n1\n2\n1\n") + with self.assertRaisesRegex(ProducerContractError, "dimensions disagree"): + check_manifest(self.root, manifest) + + def test_spin_blocks_and_global_text_indices(self): + values = struct.unpack_from("<8d", self.data, 36) + payload = struct.pack("<16d", *(values + tuple(-v for v in values))) + header = bytearray(self.data[:36]) + struct.pack_into("