From ae461ddcd69b583a9caf8935d1adcea9ba94e05a Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 16 Oct 2026 14:47:40 +0800 Subject: [PATCH 01/61] refactor(out_app_flag): change out_app_flag from bool to int and add unified output directories - Change out_app_flag from bool to int with three states: 0: no append, no subdirectory (overwrite) 1: append to same file (default, backward compatible) 2: write to unified subdirectories - Add new global directory variables in System_para: global_psi_dir, global_chg_dir, global_pot_dir, global_elf_dir, global_dos_dir, global_dip_dir, global_mat_dir - Initialize directories in set_global_dir(): out_app_flag==2 -> OUT.suffix/{PSI,CHG,POT,ELF,DOS,DIP,MAT}/ otherwise -> all point to global_out_dir (backward compatible) - Map legacy WFC->PSI, matrix->MAT - Update all out_app_flag usages from bool to int semantics - Broadcast new directory variables via Parallel_Common::bcast_string Issue: #8151 --- source/source_base/module_out/filename.cpp | 10 +++--- source/source_base/module_out/filename.h | 4 +-- .../source_io/module_ctrl/ctrl_output_fp.cpp | 2 +- .../source_io/module_ctrl/ctrl_scf_lcao.cpp | 12 +++---- source/source_io/module_dhs/write_dh.cpp | 6 ++-- source/source_io/module_dos/cal_dos.cpp | 4 +-- source/source_io/module_dos/cal_dos.h | 2 +- .../source_io/module_dos/write_dos_lcao.cpp | 6 ++-- source/source_io/module_dos/write_dos_lcao.h | 2 +- source/source_io/module_dos/write_dos_pw.cpp | 2 +- source/source_io/module_dos/write_dos_pw.h | 2 +- .../source_io/module_energy/write_bands.cpp | 4 +-- .../source_io/module_hs/dhs_sparse_writer.cpp | 6 ++-- .../source_io/module_hs/dhs_sparse_writer.h | 2 +- source/source_io/module_hs/hs_sparse_io.cpp | 2 +- source/source_io/module_hs/hs_sparse_io.h | 2 +- source/source_io/module_hs/hsk_writer.cpp | 6 ++-- source/source_io/module_hs/hsk_writer.h | 2 +- source/source_io/module_hs/hsr_legacy.cpp | 8 ++--- source/source_io/module_hs/hsr_legacy.h | 4 +-- source/source_io/module_hs/hterm_writer.cpp | 10 +++--- source/source_io/module_hs/hterm_writer.h | 2 +- .../source_io/module_hs/output_mat_sparse.cpp | 2 +- source/source_io/module_hs/pos_op_mat.cpp | 2 +- source/source_io/module_hs/pos_op_mat.h | 2 +- source/source_io/module_hs/pos_op_writer.cpp | 6 ++-- source/source_io/module_hs/pos_op_writer.h | 2 +- source/source_io/module_hs/vxc_op_lip.cpp | 2 +- source/source_io/module_hs/vxc_op_mat.cpp | 2 +- .../module_parameter/input_parameter.h | 5 +-- .../module_parameter/read_inp_out.cpp | 18 +++++++--- .../module_parameter/read_set_globalv.cpp | 36 +++++++++++++++++++ .../module_parameter/system_parameter.h | 10 +++++- source/source_io/module_wf/read_wf2rho_pw.cpp | 2 +- source/source_io/module_wf/read_wfc_nao.cpp | 2 +- source/source_io/module_wf/write_wfc_nao.cpp | 10 +++--- source/source_io/module_wf/write_wfc_nao.h | 2 +- source/source_io/module_wf/write_wfc_pw.cpp | 2 +- source/source_psi/psi_init_file.cpp | 2 +- 39 files changed, 131 insertions(+), 76 deletions(-) diff --git a/source/source_base/module_out/filename.cpp b/source/source_base/module_out/filename.cpp index 4dd86dc2bea..c7fa58fb9eb 100644 --- a/source/source_base/module_out/filename.cpp +++ b/source/source_base/module_out/filename.cpp @@ -14,7 +14,7 @@ std::string filename_output( const int nspin, const int nkstot, const int out_type, - const bool out_app_flag, + const int out_app_flag, const bool gamma_only, const int istep, const int iter) @@ -97,14 +97,14 @@ std::string filename_output( } std::string istep_block - = (istep >= 0 && (!out_app_flag)) + = (istep >= 0 && (out_app_flag != 1)) ? "g" + std::to_string(istep + 1) // istep starts from 0 - : ""; // only when istep >= 0 and out_app_flag is false will write each wfc to a separate file + : ""; // only when istep >= 0 and out_app_flag is not append mode will write each wfc to a separate file std::string iter_block - = (iter >= 0 && (!out_app_flag)) + = (iter >= 0 && (out_app_flag != 1)) ? "e" + std::to_string(iter) // iter starts from 1 - : ""; // only when istep >= 0 and out_app_flag is false will write each wfc to a separate file + : ""; // only when istep >= 0 and out_app_flag is not append mode will write each wfc to a separate file std::string suffix_block; if (out_type == 1) diff --git a/source/source_base/module_out/filename.h b/source/source_base/module_out/filename.h index d76970c769f..d4c351e3a1e 100644 --- a/source/source_base/module_out/filename.h +++ b/source/source_base/module_out/filename.h @@ -16,7 +16,7 @@ namespace ModuleIO * @param nspin: number of spin channels, 1,2 or 4 * @param nkstot: number of total k-points * @param out_type: two types of output file format, 1 for .txt and 2 for .dat (binary) - * @param out_app_flag: whether to append to existing file. + * @param out_app_flag: 0=no append, 1=append to same file, 2=write to subdirectory. * @param gamma_only: gamma_only algorithm or not. * @param istep: index of the ion step starting from 0. If < 0, the step number is not included in the file name. * @param iter: index of the electronic step starting from 0. If < 0, the step number is not included in the file name. @@ -31,7 +31,7 @@ std::string filename_output( const int nspin, const int nkstot, const int out_type, - const bool out_app_flag, + const int out_app_flag, const bool gamma_only, const int istep=-1, const int iter=-1); diff --git a/source/source_io/module_ctrl/ctrl_output_fp.cpp b/source/source_io/module_ctrl/ctrl_output_fp.cpp index 05bc5e6f6af..c6c08cc9a39 100644 --- a/source/source_io/module_ctrl/ctrl_output_fp.cpp +++ b/source/source_io/module_ctrl/ctrl_output_fp.cpp @@ -32,7 +32,7 @@ void ctrl_output_fp(UnitCell& ucell, ModuleBase::TITLE("ModuleIO", "ctrl_output_fp"); ModuleBase::timer::start("ModuleIO", "ctrl_output_fp"); - const bool out_app_flag = inp.out_app_flag; + const int out_app_flag = inp.out_app_flag; const bool gamma_only = PARAM.globalv.gamma_only_local; const int nspin = inp.nspin; const std::string global_out_dir = PARAM.globalv.global_out_dir; diff --git a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp index b7533f0325d..7bd751b1a88 100644 --- a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp @@ -136,7 +136,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, //***** - const bool out_app_flag = inp.out_app_flag; + const int out_app_flag = inp.out_app_flag; const bool gamma_only = PARAM.globalv.gamma_only_local; const int nspin = inp.nspin; const std::string global_out_dir = PARAM.globalv.global_out_dir; @@ -179,7 +179,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, { const int precision = inp.out_dmr[1]; - ModuleIO::write_dmr(dm->get_dmr_vec(), &ucell, precision, pv, out_app_flag, + ModuleIO::write_dmr(dm->get_dmr_vec(), &ucell, precision, pv, (out_app_flag == 1), ucell.get_iat2iwt(), ucell.nat, istep, ofs_running); } @@ -279,7 +279,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, const hamilt::HContainer* sr = p_hamilt->getSR(); ModuleIO::write_hsr(hr_vec, sr, &ucell, inp.out_hsr[0], precision, pv, - out_app_flag, gamma_only, ucell.get_iat2iwt(), ucell.nat, istep, + (out_app_flag == 1), gamma_only, ucell.get_iat2iwt(), ucell.nat, istep, PARAM.globalv.global_out_dir, pelec->eferm, ofs_running); } @@ -399,7 +399,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, dh_params.nspin = inp.nspin; dh_params.istep = istep; dh_params.gamma_only = gamma_only; - dh_params.append = out_app_flag; + dh_params.append = (out_app_flag == 1); dh_params.ofs_running = &ofs_running; if (PARAM.inp.nspin == 1 || PARAM.inp.nspin == 2) { @@ -442,7 +442,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, h_params.nrxx = pw_rho->nrxx; h_params.nspin = nspin; h_params.istep = istep; - h_params.append = out_app_flag; + h_params.append = (out_app_flag == 1); h_params.iat2iwt = ucell.get_iat2iwt(); h_params.nat = ucell.nat; h_params.nlocal = PARAM.globalv.nlocal; @@ -527,7 +527,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, false, // bit inp.out_mat_tk[1], 1, // true for upper triangle matrix - inp.out_app_flag, + (inp.out_app_flag == 1), t_fn, pv, GlobalV::DRANK, diff --git a/source/source_io/module_dhs/write_dh.cpp b/source/source_io/module_dhs/write_dh.cpp index 9a61cf3dee9..e0d99fde3ee 100644 --- a/source/source_io/module_dhs/write_dh.cpp +++ b/source/source_io/module_dhs/write_dh.cpp @@ -44,9 +44,9 @@ void write_dh_perI(WriteDHParams& params, const int nks = params.kv->get_nks() / nspin_k; const int nlocal = PARAM.globalv.nlocal; const std::string global_out_dir = PARAM.globalv.global_out_dir; - const bool out_app_flag = PARAM.inp.out_app_flag; + const int out_app_flag = PARAM.inp.out_app_flag; const std::string r_dir - = (PARAM.inp.calculation == "md" && !out_app_flag) ? PARAM.globalv.global_matrix_dir : global_out_dir; + = (PARAM.inp.calculation == "md" && (out_app_flag != 1)) ? PARAM.globalv.global_matrix_dir : global_out_dir; #ifdef __MPI Parallel_Orbitals serialV; @@ -129,7 +129,7 @@ void write_dh_perI(WriteDHParams& params, false, 8, false, - out_app_flag, + (out_app_flag == 1), fk, pv, GlobalV::DRANK, diff --git a/source/source_io/module_dos/cal_dos.cpp b/source/source_io/module_dos/cal_dos.cpp index 7975c5e6ded..79f5dd7d65a 100644 --- a/source/source_io/module_dos/cal_dos.cpp +++ b/source/source_io/module_dos/cal_dos.cpp @@ -108,7 +108,7 @@ bool ModuleIO::cal_dos(const int& is, // index for spin const ModuleBase::matrix& ekb, // energy for each k point and each band const ModuleBase::matrix& wg, // weight of k-points and bands const int istep, // ionic step - const bool out_app_flag, + const int out_app_flag, const int bndpar) { ModuleBase::TITLE("ModuleIO", "cal_dos"); @@ -117,7 +117,7 @@ bool ModuleIO::cal_dos(const int& is, // index for spin if (GlobalV::MY_RANK == 0) { - if(out_app_flag==true) + if(out_app_flag == 1) { ofs_dos.open(fn.c_str(), std::ios::app); } diff --git a/source/source_io/module_dos/cal_dos.h b/source/source_io/module_dos/cal_dos.h index d133c4e9423..e8f305b685b 100644 --- a/source/source_io/module_dos/cal_dos.h +++ b/source/source_io/module_dos/cal_dos.h @@ -37,7 +37,7 @@ namespace ModuleIO const ModuleBase::matrix &ekb, //store energy for each k point and each band const ModuleBase::matrix &wg, //weight of (kpoint,bands)) const int istep_in, // ionic_step - const bool out_app_flag, // whether to append output + const int out_app_flag, // 0: no append, 1: append, 2: subdirectory const int bndpar); // number of band parallel groups } diff --git a/source/source_io/module_dos/write_dos_lcao.cpp b/source/source_io/module_dos/write_dos_lcao.cpp index 926d94bd4e5..20ef0799aae 100644 --- a/source/source_io/module_dos/write_dos_lcao.cpp +++ b/source/source_io/module_dos/write_dos_lcao.cpp @@ -20,7 +20,7 @@ void write_dos_lcao( const double& dos_edelta_ev, const double& dos_scale, const double& bcoeff, - const bool out_app_flag, + const int out_app_flag, const int istep, const int nspin, const int nlocal, @@ -125,7 +125,7 @@ template void write_dos_lcao( const double& dos_edelta_ev, const double& dos_scale, const double& bcoeff, - const bool out_app_flag, + const int out_app_flag, const int istep, const int nspin, const int nlocal, @@ -152,7 +152,7 @@ template void write_dos_lcao( const double& dos_edelta_ev, const double& dos_scale, const double& bcoeff, - const bool out_app_flag, + const int out_app_flag, const int istep, const int nspin, const int nlocal, diff --git a/source/source_io/module_dos/write_dos_lcao.h b/source/source_io/module_dos/write_dos_lcao.h index 0e9017149a4..25e8b6d14a6 100644 --- a/source/source_io/module_dos/write_dos_lcao.h +++ b/source/source_io/module_dos/write_dos_lcao.h @@ -28,7 +28,7 @@ namespace ModuleIO const double& dos_edelta_ev, // Delta energy const double& dos_scale, const double& bcoeff, - const bool out_app_flag, + const int out_app_flag, const int istep, const int nspin, const int nlocal, diff --git a/source/source_io/module_dos/write_dos_pw.cpp b/source/source_io/module_dos/write_dos_pw.cpp index 1ce707f59de..3564a78f275 100644 --- a/source/source_io/module_dos/write_dos_pw.cpp +++ b/source/source_io/module_dos/write_dos_pw.cpp @@ -20,7 +20,7 @@ void ModuleIO::write_dos_pw( const bool dos_setemin, const double dos_emin_ev, const bool two_fermi, - const bool out_app_flag, + const int out_app_flag, const int bndpar, const std::string& global_out_dir, std::ofstream& ofs_running) diff --git a/source/source_io/module_dos/write_dos_pw.h b/source/source_io/module_dos/write_dos_pw.h index 4b2417d8f42..2491c7d303a 100644 --- a/source/source_io/module_dos/write_dos_pw.h +++ b/source/source_io/module_dos/write_dos_pw.h @@ -27,7 +27,7 @@ namespace ModuleIO const bool dos_setemin, const double dos_emin_ev, const bool two_fermi, - const bool out_app_flag, + const int out_app_flag, const int bndpar, const std::string& global_out_dir, std::ofstream& ofs_running); diff --git a/source/source_io/module_energy/write_bands.cpp b/source/source_io/module_energy/write_bands.cpp index f796b19f07a..e5d229f00c0 100644 --- a/source/source_io/module_energy/write_bands.cpp +++ b/source/source_io/module_energy/write_bands.cpp @@ -89,7 +89,7 @@ void ModuleIO::nscf_bands(const int& is, const int band_group = Parallel::make_band_world().rank(); if (GlobalV::MY_RANK == 0) { - if (PARAM.inp.out_app_flag == true) + if (PARAM.inp.out_app_flag == 1) { // do nothing } @@ -149,7 +149,7 @@ void ModuleIO::nscf_bands(const int& is, if (GlobalV::MY_RANK == 0) { - if (PARAM.inp.out_app_flag == true) + if (PARAM.inp.out_app_flag == 1) { ofs.open(eig_file.c_str(), std::ios::app); } diff --git a/source/source_io/module_hs/dhs_sparse_writer.cpp b/source/source_io/module_hs/dhs_sparse_writer.cpp index 5c4a049546f..4d69c718a93 100644 --- a/source/source_io/module_hs/dhs_sparse_writer.cpp +++ b/source/source_io/module_hs/dhs_sparse_writer.cpp @@ -24,7 +24,7 @@ void ModuleIO::save_dH_sparse(const int& istep, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nspin, const int nlocal, std::ofstream& ofs_running) { @@ -112,7 +112,7 @@ void ModuleIO::save_dH_sparse(const int& istep, } } - const bool md_no_append = (calculation == "md") && !out_app_flag; + const bool md_no_append = (calculation == "md") && (out_app_flag != 1); for (auto& comp: comps) { for (int ispin = 0; ispin < 2; ++ispin) @@ -134,7 +134,7 @@ void ModuleIO::save_dH_sparse(const int& istep, if (GlobalV::DRANK == 0) { - const bool open_in_append = (calculation == "md") && out_app_flag && istep >= 0; + const bool open_in_append = (calculation == "md") && (out_app_flag == 1) && istep >= 0; for (auto& comp: comps) { const std::string label = std::string("dH") + comp.axis; diff --git a/source/source_io/module_hs/dhs_sparse_writer.h b/source/source_io/module_hs/dhs_sparse_writer.h index 8aab9c8007e..7d6337d577e 100644 --- a/source/source_io/module_hs/dhs_sparse_writer.h +++ b/source/source_io/module_hs/dhs_sparse_writer.h @@ -20,7 +20,7 @@ void save_dH_sparse(const int& istep, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nspin, const int nlocal, std::ofstream& ofs_running); diff --git a/source/source_io/module_hs/hs_sparse_io.cpp b/source/source_io/module_hs/hs_sparse_io.cpp index f645893164e..580903839cc 100644 --- a/source/source_io/module_hs/hs_sparse_io.cpp +++ b/source/source_io/module_hs/hs_sparse_io.cpp @@ -33,7 +33,7 @@ void open_sparse_file(std::ofstream& ofs, const ModuleIO::SparseWriteOptions& op { mode |= std::ios::binary; } - const bool append_on_restart = (options.calculation == "md") && options.out_app_flag && options.istep >= 0; + const bool append_on_restart = (options.calculation == "md") && (options.out_app_flag == 1) && options.istep >= 0; if (append_on_restart) { mode |= std::ios::app; diff --git a/source/source_io/module_hs/hs_sparse_io.h b/source/source_io/module_hs/hs_sparse_io.h index d8bdb089989..3371e78628f 100644 --- a/source/source_io/module_hs/hs_sparse_io.h +++ b/source/source_io/module_hs/hs_sparse_io.h @@ -31,7 +31,7 @@ struct SparseWriteOptions // Runtime flags that decide file-open mode (append on md restart). // Must be provided explicitly by the caller instead of reading PARAM. std::string calculation; - bool out_app_flag = false; + int out_app_flag = 0; }; template diff --git a/source/source_io/module_hs/hsk_writer.cpp b/source/source_io/module_hs/hsk_writer.cpp index 263635b573b..0faa1ba1aa7 100644 --- a/source/source_io/module_hs/hsk_writer.cpp +++ b/source/source_io/module_hs/hsk_writer.cpp @@ -14,7 +14,7 @@ void ModuleIO::write_hsk( hamilt::Hamilt* p_hamilt, const Parallel_Orbitals &pv, const bool gamma_only, - const bool out_app_flag, + const int out_app_flag, const int istep, const int out_type, const int precision, @@ -96,11 +96,11 @@ template void ModuleIO::write_hsk( const std::string&, const int, const int, const int, const std::vector&, const std::vector&, hamilt::Hamilt*, const Parallel_Orbitals&, - const bool, const bool, const int, const int, const int, + const bool, const int, const int, const int, const int, const int, const std::string&, const int, std::ofstream&); template void ModuleIO::write_hsk>( const std::string&, const int, const int, const int, const std::vector&, const std::vector&, hamilt::Hamilt>*, const Parallel_Orbitals&, - const bool, const bool, const int, const int, const int, + const bool, const int, const int, const int, const int, const int, const std::string&, const int, std::ofstream&); diff --git a/source/source_io/module_hs/hsk_writer.h b/source/source_io/module_hs/hsk_writer.h index af2bb6a8540..d80a5eb8839 100644 --- a/source/source_io/module_hs/hsk_writer.h +++ b/source/source_io/module_hs/hsk_writer.h @@ -25,7 +25,7 @@ void write_hsk(const std::string& global_out_dir, hamilt::Hamilt* p_hamilt, const Parallel_Orbitals& pv, const bool gamma_only, - const bool out_app_flag, + const int out_app_flag, const int istep, const int out_type, const int precision, diff --git a/source/source_io/module_hs/hsr_legacy.cpp b/source/source_io/module_hs/hsr_legacy.cpp index 0f1e37daa3e..d3c7d338d85 100644 --- a/source/source_io/module_hs/hsr_legacy.cpp +++ b/source/source_io/module_hs/hsr_legacy.cpp @@ -107,7 +107,7 @@ void ModuleIO::output_SR(Parallel_Orbitals& pv, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nspin, std::ofstream& ofs_running) { @@ -174,7 +174,7 @@ void ModuleIO::output_TR(const int istep, ModuleBase::timer::start("ModuleIO", "output_TR"); std::stringstream sst; - const bool md_no_append = (options.calculation == "md") && !options.out_app_flag; + const bool md_no_append = (options.calculation == "md") && (options.out_app_flag != 1); if (istep >= 0) { // insert the 1-indexed ionic-step suffix right after the "tr" prefix, @@ -220,7 +220,7 @@ template void ModuleIO::output_SR(Parallel_Orbitals& pv, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nspin, std::ofstream& ofs_running); template void ModuleIO::output_SR>(Parallel_Orbitals& pv, @@ -233,6 +233,6 @@ template void ModuleIO::output_SR>(Parallel_Orbitals& pv, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nspin, std::ofstream& ofs_running); diff --git a/source/source_io/module_hs/hsr_legacy.h b/source/source_io/module_hs/hsr_legacy.h index 334374125e1..cddd93aed3f 100644 --- a/source/source_io/module_hs/hsr_legacy.h +++ b/source/source_io/module_hs/hsr_legacy.h @@ -29,7 +29,7 @@ struct MatROutputOptions std::string global_out_dir; std::string global_matrix_dir; std::string calculation; - bool out_app_flag = false; + int out_app_flag = 0; int nspin = 1; }; @@ -82,7 +82,7 @@ void output_SR(Parallel_Orbitals& pv, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nspin, std::ofstream& ofs_running); diff --git a/source/source_io/module_hs/hterm_writer.cpp b/source/source_io/module_hs/hterm_writer.cpp index a4be8399b56..ee6c997c7b3 100644 --- a/source/source_io/module_hs/hterm_writer.cpp +++ b/source/source_io/module_hs/hterm_writer.cpp @@ -79,7 +79,7 @@ static void gather_and_write(const std::string& prefix, const int* iat2iwt, const int nat, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const std::string& global_out_dir, const std::string& global_matrix_dir, std::ofstream* ofs_running) @@ -94,7 +94,7 @@ static void gather_and_write(const std::string& prefix, if (GlobalV::MY_RANK == 0) #endif { - const bool md_no_append = (calculation == "md") && !out_app_flag; + const bool md_no_append = (calculation == "md") && (out_app_flag != 1); const std::string& out_dir = md_no_append ? global_matrix_dir : global_out_dir; const std::string fname = out_dir + hsr_gen_fname(prefix, ispin, append, istep); // H(R) component terms (T, V^NL, V^L, V^H, V^XC, V^EXX) are parts of @@ -127,7 +127,7 @@ static void write_hk_common(hamilt::HContainer& hR, const int nlocal, const bool gamma_only, const std::string& global_out_dir, - const bool out_app_flag, + const int out_app_flag, const std::string& ks_solver, const int drank) { @@ -200,7 +200,7 @@ static void write_h_term(WriteHParams& params, const HTermSpec& spec) const int nlocal = params.nlocal; const bool gamma_only = params.gamma_only_local; const std::string& global_out_dir = params.global_out_dir; - const bool out_app_flag = params.out_app_flag; + const int out_app_flag = params.out_app_flag; const int nspin_out = (nspin == 2 ? 2 : 1); @@ -378,7 +378,7 @@ static void write_h_exx_impl(const UnitCell& ucell, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const std::string& ks_solver, const int drank, const Exx_Info& exx_info, diff --git a/source/source_io/module_hs/hterm_writer.h b/source/source_io/module_hs/hterm_writer.h index 7aa2f721fc6..2cc9599548e 100644 --- a/source/source_io/module_hs/hterm_writer.h +++ b/source/source_io/module_hs/hterm_writer.h @@ -46,7 +46,7 @@ struct WriteHParams bool domag = false; bool domag_z = false; int gga_grad = 0; // used by the XC term - bool out_app_flag = false; + int out_app_flag = 0; std::string calculation; std::string global_out_dir; std::string global_matrix_dir; diff --git a/source/source_io/module_hs/output_mat_sparse.cpp b/source/source_io/module_hs/output_mat_sparse.cpp index 5179bd5477e..59e133574e7 100644 --- a/source/source_io/module_hs/output_mat_sparse.cpp +++ b/source/source_io/module_hs/output_mat_sparse.cpp @@ -25,7 +25,7 @@ void output_mat_sparse(const MatSparseOutputOptions& options, const std::string& global_out_dir = PARAM.globalv.global_out_dir; const std::string& global_matrix_dir = PARAM.globalv.global_matrix_dir; const std::string& calculation = PARAM.inp.calculation; - const bool out_app_flag = PARAM.inp.out_app_flag; + const int out_app_flag = PARAM.inp.out_app_flag; const int nspin = PARAM.inp.nspin; const bool gamma_only_local = PARAM.globalv.gamma_only_local; const int npol = PARAM.globalv.npol; diff --git a/source/source_io/module_hs/pos_op_mat.cpp b/source/source_io/module_hs/pos_op_mat.cpp index 04ec8d45e85..e37c7f1245e 100644 --- a/source/source_io/module_hs/pos_op_mat.cpp +++ b/source/source_io/module_hs/pos_op_mat.cpp @@ -89,7 +89,7 @@ void Position_op::out_rR(const UnitCell& ucell, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nlocal, const int npol, std::ofstream& ofs_running) diff --git a/source/source_io/module_hs/pos_op_mat.h b/source/source_io/module_hs/pos_op_mat.h index 96a6513c23e..d76d71e0c3e 100644 --- a/source/source_io/module_hs/pos_op_mat.h +++ b/source/source_io/module_hs/pos_op_mat.h @@ -76,7 +76,7 @@ class Position_op const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nlocal, const int npol, std::ofstream& ofs_running); diff --git a/source/source_io/module_hs/pos_op_writer.cpp b/source/source_io/module_hs/pos_op_writer.cpp index 15e1fa201ea..f131047041b 100644 --- a/source/source_io/module_hs/pos_op_writer.cpp +++ b/source/source_io/module_hs/pos_op_writer.cpp @@ -22,7 +22,7 @@ void PosOpWriter::out_lat_r(const UnitCell& ucell, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - bool out_app_flag, + int out_app_flag, int nlocal, int npol, double sparse_threshold, @@ -216,7 +216,7 @@ void PosOpWriter::out_lat_r(const UnitCell& ucell, if (GlobalV::DRANK == 0) { std::stringstream ssr; - const bool md_no_append = (calculation == "md") && !out_app_flag; + const bool md_no_append = (calculation == "md") && (out_app_flag != 1); if (step >= 0) { ssr << (md_no_append ? global_matrix_dir : global_out_dir) << "rrg" << (step + 1) << "_nao.txt"; @@ -227,7 +227,7 @@ void PosOpWriter::out_lat_r(const UnitCell& ucell, } ofs_tem1.close(); - const bool open_in_append = (calculation == "md") && out_app_flag && step >= 0; + const bool open_in_append = (calculation == "md") && (out_app_flag == 1) && step >= 0; const int header_step = std::max(step, 0); ModuleIO::detail::assemble_csr(ssr.str(), tem1.str(), diff --git a/source/source_io/module_hs/pos_op_writer.h b/source/source_io/module_hs/pos_op_writer.h index 1fd22bcfc81..d2996e43890 100644 --- a/source/source_io/module_hs/pos_op_writer.h +++ b/source/source_io/module_hs/pos_op_writer.h @@ -35,7 +35,7 @@ class PosOpWriter const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - bool out_app_flag, + int out_app_flag, int nlocal, int npol, double sparse_threshold, diff --git a/source/source_io/module_hs/vxc_op_lip.cpp b/source/source_io/module_hs/vxc_op_lip.cpp index fcee73b6cd4..094015992d4 100644 --- a/source/source_io/module_hs/vxc_op_lip.cpp +++ b/source/source_io/module_hs/vxc_op_lip.cpp @@ -197,7 +197,7 @@ void write_Vxc_LIP(int nspin, // add-up and write const int istep = -1; const int out_label = 1; // 1 means .txt while 2 means .dat - const bool out_app_flag = 0; + const int out_app_flag = 0; std::string vxc_file = ModuleIO::filename_output( global_out_dir, "vxc", "nao", ik, kv.ik2iktot, nspin, kv.get_nkstot(), diff --git a/source/source_io/module_hs/vxc_op_mat.cpp b/source/source_io/module_hs/vxc_op_mat.cpp index c7a0b8fe2b4..b749e110b5a 100644 --- a/source/source_io/module_hs/vxc_op_mat.cpp +++ b/source/source_io/module_hs/vxc_op_mat.cpp @@ -115,7 +115,7 @@ void write_Vxc(const int nspin, // write const int istep = -1; const int out_label = 1; // 1 means .txt while 2 means .dat - const bool out_app_flag = 0; + const int out_app_flag = 0; std::string vxc_file = ModuleIO::filename_output( global_out_dir, "vxc", "nao", ik, kv.ik2iktot, nspin, kv.get_nkstot(), diff --git a/source/source_io/module_parameter/input_parameter.h b/source/source_io/module_parameter/input_parameter.h index d86993b63d9..dde1c933695 100644 --- a/source/source_io/module_parameter/input_parameter.h +++ b/source/source_io/module_parameter/input_parameter.h @@ -483,8 +483,9 @@ struct Input_para bool out_hr_npz = false; ///< output H(R) matrix in npz format bool out_hsr_npz = false; ///< output H(R) and S(R) matrices in npz format bool out_dm_npz = false; ///< output DM(R) matrix in npz format - bool out_app_flag = true; ///< whether output r(R), H(R), S(R), T(R), and dH(R) matrices - ///< in an append manner during MD liuyu 2023-03-20 + int out_app_flag = 1; ///< 0: no append, no subdirectory; 1: append to same file; + ///< 2: write to subdirectory (PSI/, CHG/, POT/, ELF/, DOS/, DIP/, MAT/) + ///< liuyu 2023-03-20, modified 2025-10-16 int out_ndigits = 8; ///< Assuming 8 digits precision is needed for matrices output std::vector out_mat_t = {0, 8}; ///< output T(R) matrix with precision bool out_element_info = false; ///< output information of all elements diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index 80a55319466..dd600da3666 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -1394,12 +1394,22 @@ The circle order of the charge density on real space grids is: x is the outer lo item.annotation = "whether output r(R), H(R), S(R), T(R), and dH(R) " "matrices in an append manner during MD"; item.category = "Output information"; - item.type = "Boolean"; - item.description = "Whether to output r(R), H(R), S(R), T(R), dH(R), dS(R), and wfc matrices in an append manner during molecular dynamics calculations. Check input parameters out_mat_r, out_hsr, out_mat_t, out_mat_dh, out_hsk and out_wfc_lcao for more information."; - item.default_value = "true"; + item.type = "Integer"; + item.description = R"(Controls output file organization during multi-step calculations (MD, relax, RT-TDDFT). +* 0: Write to OUT directory directly, no append (files overwritten each step) +* 1: Append to same file (default, backward compatible) +* 2: Write to subdirectory (PSI/, CHG/, POT/, ELF/, DOS/, DIP/, MAT/) +For more information, refer to the documentation of out_mat_r, out_hsr, out_mat_t, out_mat_dh, out_hsk, out_wfc_lcao, out_chg, out_pot, out_elf, out_dos, out_dipole.)"; + item.default_value = "1"; item.unit = ""; item.set_availability("basis_type==lcao and gamma_only==0"); - read_sync_bool(input.out_app_flag); + read_sync_int(input.out_app_flag); + item.check_value = [](const Input_Item& item, const Parameter& para) { + if (para.input.out_app_flag < 0 || para.input.out_app_flag > 2) + { + ModuleBase::WARNING_QUIT("ReadInput", "out_app_flag should be 0, 1, or 2"); + } + }; this->add_item(item); } { diff --git a/source/source_io/module_parameter/read_set_globalv.cpp b/source/source_io/module_parameter/read_set_globalv.cpp index 3b395e6b8f5..94bfaeb06ac 100644 --- a/source/source_io/module_parameter/read_set_globalv.cpp +++ b/source/source_io/module_parameter/read_set_globalv.cpp @@ -97,6 +97,35 @@ void ReadInput::set_global_dir(const Input_para& inp, System_para& sys) sys.global_wfc_dir = sys.global_out_dir + "WFC/"; sys.global_wfc_dir = to_dir(sys.global_wfc_dir); + /// Set subdirectories based on out_app_flag + /// out_app_flag == 2: use unified uppercase subdirectories + /// otherwise: keep backward-compatible behavior (all point to global_out_dir) + if (inp.out_app_flag == 2) + { + sys.global_psi_dir = sys.global_out_dir + "PSI/"; + sys.global_chg_dir = sys.global_out_dir + "CHG/"; + sys.global_pot_dir = sys.global_out_dir + "POT/"; + sys.global_elf_dir = sys.global_out_dir + "ELF/"; + sys.global_dos_dir = sys.global_out_dir + "DOS/"; + sys.global_dip_dir = sys.global_out_dir + "DIP/"; + sys.global_mat_dir = sys.global_out_dir + "MAT/"; + } + else + { + sys.global_psi_dir = sys.global_out_dir; + sys.global_chg_dir = sys.global_out_dir; + sys.global_pot_dir = sys.global_out_dir; + sys.global_elf_dir = sys.global_out_dir; + sys.global_dos_dir = sys.global_out_dir; + sys.global_dip_dir = sys.global_out_dir; + sys.global_mat_dir = sys.global_out_dir; + } + + /// Map legacy directory names to new unified names + /// WFC -> PSI, matrix -> MAT + sys.global_wfc_dir = sys.global_psi_dir; + sys.global_matrix_dir = sys.global_mat_dir; + /// get the global ML KEDF descriptor directory sys.global_mlkedf_descriptor_dir = sys.global_out_dir + "MLKEDF_Descriptors/"; sys.global_mlkedf_descriptor_dir = to_dir(sys.global_mlkedf_descriptor_dir); @@ -154,6 +183,13 @@ void ReadInput::set_global_dir(const Input_para& inp, System_para& sys) Parallel_Common::bcast_string(sys.global_stru_dir); Parallel_Common::bcast_string(sys.global_matrix_dir); Parallel_Common::bcast_string(sys.global_wfc_dir); + Parallel_Common::bcast_string(sys.global_psi_dir); + Parallel_Common::bcast_string(sys.global_chg_dir); + Parallel_Common::bcast_string(sys.global_pot_dir); + Parallel_Common::bcast_string(sys.global_elf_dir); + Parallel_Common::bcast_string(sys.global_dos_dir); + Parallel_Common::bcast_string(sys.global_dip_dir); + Parallel_Common::bcast_string(sys.global_mat_dir); Parallel_Common::bcast_string(sys.global_in_stru); #endif } diff --git a/source/source_io/module_parameter/system_parameter.h b/source/source_io/module_parameter/system_parameter.h index 438bd32c145..4705c33f4ea 100644 --- a/source/source_io/module_parameter/system_parameter.h +++ b/source/source_io/module_parameter/system_parameter.h @@ -53,8 +53,16 @@ struct System_para std::string global_stru_dir = ""; ///< global structure directory std::string global_matrix_dir = ""; ///< global matrix directory std::string global_wfc_dir = ""; ///< global wavefunction directory + // Subdirectories used when out_app_flag == 2 + std::string global_psi_dir = ""; ///< wavefunction directory (PSI/) + std::string global_chg_dir = ""; ///< charge density directory (CHG/) + std::string global_pot_dir = ""; ///< potential directory (POT/) + std::string global_elf_dir = ""; ///< ELF directory (ELF/) + std::string global_dos_dir = ""; ///< DOS directory (DOS/) + std::string global_dip_dir = ""; ///< dipole directory (DIP/) + std::string global_mat_dir = ""; ///< matrix directory (MAT/) std::string global_mlkedf_descriptor_dir = ""; ///< global ML KEDF descriptor directory - std::string global_deepks_label_elec_dir = ""; ///< global directory for DeePKS labels during electronic steps + std::string global_deepks_label_elec_dir = ""; ///< global directory for DeePKS labels during electronic steps std::string log_file = "log"; ///< log file name bool deepks_setorb = false; ///< true if "deepks" is set diff --git a/source/source_io/module_wf/read_wf2rho_pw.cpp b/source/source_io/module_wf/read_wf2rho_pw.cpp index 50e31e733a3..298334607db 100644 --- a/source/source_io/module_wf/read_wf2rho_pw.cpp +++ b/source/source_io/module_wf/read_wf2rho_pw.cpp @@ -129,7 +129,7 @@ void ModuleIO::read_wf2rho_pw_impl(const ModulePW::PW_Basis_K* pw_wfc, // mohan add 2025-05-17 // .dat file const int out_type = 2; - const bool out_app_flag = false; + const int out_app_flag = 0; const bool gamma_only = false; const int istep = -1; diff --git a/source/source_io/module_wf/read_wfc_nao.cpp b/source/source_io/module_wf/read_wfc_nao.cpp index 3148e2c0e2f..58599769d4c 100644 --- a/source/source_io/module_wf/read_wfc_nao.cpp +++ b/source/source_io/module_wf/read_wfc_nao.cpp @@ -296,7 +296,7 @@ bool ModuleIO::read_wfc_nao( { if (myrank == 0) { - const bool out_app_flag = false; + const int out_app_flag = 0; std::stringstream error_message; std::string readin_dir = global_readin_dir; if(istep >= 0) diff --git a/source/source_io/module_wf/write_wfc_nao.cpp b/source/source_io/module_wf/write_wfc_nao.cpp index 2d28e96e60d..b468cc4c6c1 100644 --- a/source/source_io/module_wf/write_wfc_nao.cpp +++ b/source/source_io/module_wf/write_wfc_nao.cpp @@ -202,7 +202,7 @@ void wfc_nao_write2file_complex(const std::string& name, template void write_wfc_nao(const int out_type, - const bool out_app_flag, + const int out_app_flag, const psi::Psi& psi, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, @@ -270,7 +270,7 @@ void write_wfc_nao(const int out_type, if (myid == 0) { std::string wfc_dir; - if(out_app_flag) + if(out_app_flag == 1) { wfc_dir = PARAM.globalv.global_out_dir; } @@ -281,7 +281,7 @@ void write_wfc_nao(const int out_type, std::string fn = filename_output(wfc_dir,"wf","nao",ik,ik2iktot,nspin,nkstot, out_type,out_app_flag,gamma_only,istep); - bool append_flag = (istep > 0 && out_app_flag); + bool append_flag = (istep > 0 && out_app_flag == 1); if (std::is_same::value) { wfc_nao_write2file(fn, @@ -311,7 +311,7 @@ void write_wfc_nao(const int out_type, } template void write_wfc_nao(const int out_type, - const bool out_app_flag, + const int out_app_flag, const psi::Psi& psi, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, @@ -323,7 +323,7 @@ template void write_wfc_nao(const int out_type, const int istep); template void write_wfc_nao>(const int out_type, - const bool out_app_flag, + const int out_app_flag, const psi::Psi>& psi, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, diff --git a/source/source_io/module_wf/write_wfc_nao.h b/source/source_io/module_wf/write_wfc_nao.h index 152723b5109..70dde0ae6fb 100644 --- a/source/source_io/module_wf/write_wfc_nao.h +++ b/source/source_io/module_wf/write_wfc_nao.h @@ -25,7 +25,7 @@ namespace ModuleIO */ template void write_wfc_nao(const int out_type, - const bool out_app_flag, + const int out_app_flag, const psi::Psi& psi, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, diff --git a/source/source_io/module_wf/write_wfc_pw.cpp b/source/source_io/module_wf/write_wfc_pw.cpp index 450bb596988..d90f7f5d645 100644 --- a/source/source_io/module_wf/write_wfc_pw.cpp +++ b/source/source_io/module_wf/write_wfc_pw.cpp @@ -43,7 +43,7 @@ void ModuleIO::write_wfc_pw( assert(nkstot>0); assert(nks>0); - bool out_app_flag = false; // need to modify later, mohan 2025-05-17 + int out_app_flag = 0; // need to modify later, mohan 2025-05-17 bool gamma_only = false; // need to modify later, mohan 2025-05-17 std::string* wfilename = new std::string[nks]; diff --git a/source/source_psi/psi_init_file.cpp b/source/source_psi/psi_init_file.cpp index 29a8619e30b..cc7086d8c8f 100644 --- a/source/source_psi/psi_init_file.cpp +++ b/source/source_psi/psi_init_file.cpp @@ -52,7 +52,7 @@ void psi_init_file::init_psig(T* psig, const int& ik) // mohan update, this is for plane wave, 2025-05-17 const int out_type = 2; - const bool out_app_flag = false; + const int out_app_flag = 0; const bool gamma_only = false; const int istep = -1; From 2545b1be41ac0887002d51c3087da59fbda1e0a3 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 16 Oct 2026 15:00:14 +0800 Subject: [PATCH 02/61] feat(output): use unified subdirectories when out_app_flag=2 - ctrl_output_fp.cpp: route chg/tau to global_chg_dir, pot/potes to global_pot_dir, ELF to global_elf_dir, dipole to global_dip_dir - ctrl_output_pw.cpp: route wfc to global_psi_dir, dos to global_dos_dir, ldos to global_dos_dir - ctrl_scf_lcao.cpp: route dos to global_dos_dir - ctrl_runner_lcao.cpp: route ldos to global_dos_dir - read_input.cpp: create subdirectories (PSI/CHG/POT/ELF/DOS/DIP/MAT) when out_app_flag==2 - Update docs: out_app_flag semantics from bool to int (0/1/2) Issue: #8151 --- docs/advanced/elec_properties/hs_matrix.md | 8 +++--- .../elec_properties/position_matrix.md | 2 +- docs/advanced/elec_properties/wfc.md | 2 +- docs/advanced/scf/initialization.md | 2 +- .../source_io/module_ctrl/ctrl_output_fp.cpp | 16 ++++++----- .../source_io/module_ctrl/ctrl_output_pw.cpp | 6 ++--- .../module_ctrl/ctrl_runner_lcao.cpp | 2 +- .../source_io/module_ctrl/ctrl_scf_lcao.cpp | 2 +- .../source_io/module_parameter/read_input.cpp | 27 +++++++++++++++++++ 9 files changed, 49 insertions(+), 18 deletions(-) diff --git a/docs/advanced/elec_properties/hs_matrix.md b/docs/advanced/elec_properties/hs_matrix.md index 5adf14e3971..6e4aa48423b 100644 --- a/docs/advanced/elec_properties/hs_matrix.md +++ b/docs/advanced/elec_properties/hs_matrix.md @@ -39,7 +39,7 @@ The output filenames depend on the k-point algorithm and `nspin`: Here `${k}` is the one-based k-point index. For `nspin = 2`, the overlap matrix is spin-independent, so only one overlap file is written for each physical k point. The gamma-only algorithm does not support `nspin = 4`; use the multi-k algorithm with an explicit Gamma-only `KPT` file for a noncollinear calculation at Gamma. -When `out_app_flag` is false, `g${step}` is inserted before `_nao`, where `${step}` is the one-based ionic-step index. For example, the first spin channel at the first k point and first ionic step is written to `hk1s1g1_nao.txt`. +When `out_app_flag` is not 1, `g${step}` is inserted before `_nao`, where `${step}` is the one-based ionic-step index. For example, the first spin channel at the first k point and first ionic step is written to `hk1s1g1_nao.txt`. Each output block starts with a comment header containing the one-based ionic-step index, filename, `gamma only` flag, and matrix dimensions. It is followed by `Row 1`, `Row 2`, and so on. Each row contains the matrix elements from the diagonal through the upper triangle. @@ -57,7 +57,7 @@ For `out_hsk 2`, the filenames in the table above use `.dat` instead of `.txt`. The file therefore contains `sizeof(int) + N(N+1)/2 * sizeof(double)` bytes for a gamma-only record and `sizeof(int) + N(N+1) * sizeof(double)` bytes for a multi-k or spinor record. The format uses the host integer representation and byte order and is intended for readers using a compatible ABI. -When `out_app_flag` is true, the first ionic step truncates the shared file and every later step appends another complete record. When it is false, each ionic step has a separate filename containing `g${step}` before `_nao`. +When `out_app_flag` is 1, the first ionic step truncates the shared file and every later step appends another complete record. When it is not 1, each ionic step has a separate filename containing `g${step}` before `_nao`. ## out_hsr @@ -95,7 +95,7 @@ In gamma-only mode, every generated file reports one Bravais lattice vector and ### Native Binary CSR Format -Set `out_hsr 2` to write the same H(R) and S(R) matrix sets with a `.dat` suffix. For example, an `nspin = 2` calculation writes `hrs1_nao.dat`, `hrs2_nao.dat`, and `sr_nao.dat`. When `out_app_flag` is false, the one-based ionic step is included before `_nao`, for example `hrs1g1_nao.dat` and `srg1_nao.dat`. +Set `out_hsr 2` to write the same H(R) and S(R) matrix sets with a `.dat` suffix. For example, an `nspin = 2` calculation writes `hrs1_nao.dat`, `hrs2_nao.dat`, and `sr_nao.dat`. When `out_app_flag` is not 1, the one-based ionic step is included before `_nao`, for example `hrs1g1_nao.dat` and `srg1_nao.dat`. Each ionic step is a complete record with no padding or self-describing header: @@ -107,7 +107,7 @@ Each ionic step is a complete record with no padding or self-describing header: All R blocks stored by the internal HContainer are present, including blocks with zero nonzero values. The sparse threshold is `1e-10`, matching text CSR output. The format uses the host integer representation and byte order and therefore requires a compatible ABI. It does not contain unit-cell, spin, or matrix-label metadata; those are determined by the calculation input and filename. -When `out_app_flag` is true, the first ionic step truncates the shared file and later ionic steps append complete records. Otherwise every ionic step is written to its own file. +When `out_app_flag` is 1, the first ionic step truncates the shared file and later ionic steps append complete records. Otherwise every ionic step is written to its own file. ### NPZ Format diff --git a/docs/advanced/elec_properties/position_matrix.md b/docs/advanced/elec_properties/position_matrix.md index c504a866b64..38743d5b65b 100644 --- a/docs/advanced/elec_properties/position_matrix.md +++ b/docs/advanced/elec_properties/position_matrix.md @@ -18,7 +18,7 @@ Each file or each section of the appended file starts with "STEP: " followed by Each block here contains the matrix for the corresponding cell. There are three columns in each block, giving the matrix elements in x, y, z directions, respectively. There are altogether nbasis * nbasis lines in each block, which emulates the matrix elements. -In molecular dynamics (MD) calculations, if [out_app_flag](../input_files/input-main.md#out_app_flag) is set to true, then `data-rR-tr` is written in an append manner. Otherwise, output files will be put in a separate directory, `matrix`, and named as `$x`_data-rR-tr, where `$x` is the number of MD step. In addition, the output frequency is controlled by [out_freq_ion](../input_files/input-main.md#out_freq_ion). For example, if we are running a 10-step MD with out_freq_ion = 3, then `$x` will be 0, 3, 6, and 9. +In molecular dynamics (MD) calculations, if [out_app_flag](../input_files/input-main.md#out_app_flag) is set to 1, then `data-rR-tr` is written in an append manner. Otherwise, output files will be put in a separate directory, `matrix`, and named as `$x`_data-rR-tr, where `$x` is the number of MD step. In addition, the output frequency is controlled by [out_freq_ion](../input_files/input-main.md#out_freq_ion). For example, if we are running a 10-step MD with out_freq_ion = 3, then `$x` will be 0, 3, 6, and 9. ## get_s We also offer the option of only calculating the position matrix without running SCF. For that purpose, in `INPUT` file we need to set the keyword [calculation](../input_files/input-main.md#calculation) to `get_s`, and [out_mat_r](../input_files/input-main.md#out_mat_r) to `true`. diff --git a/docs/advanced/elec_properties/wfc.md b/docs/advanced/elec_properties/wfc.md index 354d8844fc4..562b0c4b32b 100644 --- a/docs/advanced/elec_properties/wfc.md +++ b/docs/advanced/elec_properties/wfc.md @@ -18,7 +18,7 @@ The [`init_wfc file binary`](../scf/initialization.md#wavefunction) path reads o For `basis_type=lcao`, [`out_wfc_lcao`](https://abacus-rtd.readthedocs.io/en/latest/advanced/input_files/input-main.html#out-wfc-lcao) controls the output of numerical-atomic-orbital coefficients: `1` writes text files with the `.txt` suffix and `2` writes binary files with the `.dat` suffix. Multi-k calculations generate `wfs{spin}k{k-point}_nao.txt` or `.dat`, while Gamma-only calculations generate `wfs{spin}_nao.txt` or `.dat`. -To reuse LCAO coefficients, point `read_file_dir` to the coefficient files and set `init_wfc file txt` for text input or `init_wfc file binary` for binary input. The selected extension is required; there is no automatic format fallback. Only independent files without geometry-step indices are supported, not files accumulated with `out_app_flag`. +To reuse LCAO coefficients, point `read_file_dir` to the coefficient files and set `init_wfc file txt` for text input or `init_wfc file binary` for binary input. The selected extension is required; there is no automatic format fallback. Only independent files without geometry-step indices are supported, not files accumulated with `out_app_flag=1`. ## Wave Function in Real Space diff --git a/docs/advanced/scf/initialization.md b/docs/advanced/scf/initialization.md index 427146bf223..4517b691b44 100644 --- a/docs/advanced/scf/initialization.md +++ b/docs/advanced/scf/initialization.md @@ -26,7 +26,7 @@ For `basis_type=pw`, the available options are: * `nao+random`: Apply an approximately 5% multiplicative random perturbation to the NAO initialization; any bands not covered by NAO states are first initialized randomly. * `file binary`: Read binary `wf*_pw.dat` files generated with [`out_wfc_pw=2`](../elec_properties/wfc.md) from `read_file_dir`. The files must match the current k points, `nbands`, plane-wave layout, and lattice. The `txt` format is not supported for PW wavefunctions. -For `basis_type=lcao`, use `init_wfc file txt` to read text `wf*_nao.txt` files generated with [`out_wfc_lcao=1`](../elec_properties/wfc.md), or `init_wfc file binary` to read binary `wf*_nao.dat` files generated with `out_wfc_lcao=2`, from `read_file_dir`. LCAO wavefunctions initialize the density matrix and real-space charge density. The selected format is required; ABACUS does not automatically detect or fall back to the other format. The files must use a compatible NAO basis, match the current k-point and spin setup, and contain enough bands. Only independent files without geometry-step indices are supported, not files accumulated with `out_app_flag`. +For `basis_type=lcao`, use `init_wfc file txt` to read text `wf*_nao.txt` files generated with [`out_wfc_lcao=1`](../elec_properties/wfc.md), or `init_wfc file binary` to read binary `wf*_nao.dat` files generated with `out_wfc_lcao=2`, from `read_file_dir`. LCAO wavefunctions initialize the density matrix and real-space charge density. The selected format is required; ABACUS does not automatically detect or fall back to the other format. The files must use a compatible NAO basis, match the current k-point and spin setup, and contain enough bands. Only independent files without geometry-step indices are supported, not files accumulated with `out_app_flag=1`. For `basis_type=lcao_in_pw`, `init_wfc` is automatically set to `nao`. diff --git a/source/source_io/module_ctrl/ctrl_output_fp.cpp b/source/source_io/module_ctrl/ctrl_output_fp.cpp index c6c08cc9a39..7fe1a6e0aad 100644 --- a/source/source_io/module_ctrl/ctrl_output_fp.cpp +++ b/source/source_io/module_ctrl/ctrl_output_fp.cpp @@ -36,6 +36,10 @@ void ctrl_output_fp(UnitCell& ucell, const bool gamma_only = PARAM.globalv.gamma_only_local; const int nspin = inp.nspin; const std::string global_out_dir = PARAM.globalv.global_out_dir; + const std::string global_chg_dir = PARAM.globalv.global_chg_dir; + const std::string global_pot_dir = PARAM.globalv.global_pot_dir; + const std::string global_elf_dir = PARAM.globalv.global_elf_dir; + const std::string global_dip_dir = PARAM.globalv.global_dip_dir; const int output_frequency = inp.esolver_type == "tddft" ? inp.out_freq_td : inp.out_freq_ion; // Print the global electronic step for TDDFT and the ionic step otherwise. @@ -61,7 +65,7 @@ void ctrl_output_fp(UnitCell& ucell, { for (int is = 0; is < nspin; ++is) { - std::string fn = PARAM.globalv.global_out_dir + "chg"; + std::string fn = PARAM.globalv.global_chg_dir + "chg"; std::string spin_block; if (nspin == 2 || nspin == 4) @@ -94,7 +98,7 @@ void ctrl_output_fp(UnitCell& ucell, if (XC_Functional::get_ked_flag()) { - fn = PARAM.globalv.global_out_dir + "tau"; + fn = PARAM.globalv.global_chg_dir + "tau"; fn += spin_block + geom_block + ".cube"; @@ -123,7 +127,7 @@ void ctrl_output_fp(UnitCell& ucell, { for (int is = 0; is < nspin; is++) { - std::string fn = PARAM.globalv.global_out_dir + "pot"; + std::string fn = PARAM.globalv.global_pot_dir + "pot"; std::string spin_block; if (nspin == 2 || nspin == 4) @@ -157,7 +161,7 @@ void ctrl_output_fp(UnitCell& ucell, } else if (inp.out_pot[0] == 2 && should_output) { - std::string fn = PARAM.globalv.global_out_dir + "potes"; + std::string fn = PARAM.globalv.global_pot_dir + "potes"; fn += geom_block + ".cube"; const std::vector* pcc_potential = pelec->pot->pcc_electron_potential(); @@ -189,7 +193,7 @@ void ctrl_output_fp(UnitCell& ucell, module_charge::cal_rhog_symm(is, chr, pw_rhod, ucell.symm); } - std::string out_dir = PARAM.globalv.global_out_dir; + std::string out_dir = PARAM.globalv.global_elf_dir; ModuleIO::write_elf(out_dir, istep, nspin, @@ -226,7 +230,7 @@ void ctrl_output_fp(UnitCell& ucell, for (int is = 0; is < nspin; ++is) { std::stringstream ss_dipole; - ss_dipole << global_out_dir << "dipole_s" << is + 1 << ".txt"; + ss_dipole << global_dip_dir << "dipole_s" << is + 1 << ".txt"; ModuleIO::write_dipole(ucell, chr.rho[is], pw_rhod, istep, ss_dipole.str(), ofs_running); } } diff --git a/source/source_io/module_ctrl/ctrl_output_pw.cpp b/source/source_io/module_ctrl/ctrl_output_pw.cpp index 8294d7cbae6..6856a49458e 100644 --- a/source/source_io/module_ctrl/ctrl_output_pw.cpp +++ b/source/source_io/module_ctrl/ctrl_output_pw.cpp @@ -74,7 +74,7 @@ void ModuleIO::ctrl_iter_pw(const int istep, GlobalV::NPROC_IN_POOL, inp.out_wfc_pw, inp.ecutwfc, - PARAM.globalv.global_out_dir, + PARAM.globalv.global_psi_dir, stp.psi_cpu[0], kv, pw_wfc, @@ -174,7 +174,7 @@ void ModuleIO::ctrl_scf_pw(const int istep, PARAM.globalv.two_fermi, inp.out_app_flag, inp.bndpar, - PARAM.globalv.global_out_dir, + PARAM.globalv.global_dos_dir, ofs_running); } } @@ -294,7 +294,7 @@ void ModuleIO::ctrl_runner_pw(UnitCell& ucell, inp.out_ldos, inp.stm_bias, inp.nspin, - PARAM.globalv.global_out_dir, + PARAM.globalv.global_dos_dir, PARAM.globalv.two_fermi, inp.nbands, inp.dos_edelta_ev, diff --git a/source/source_io/module_ctrl/ctrl_runner_lcao.cpp b/source/source_io/module_ctrl/ctrl_runner_lcao.cpp index 5ceec5f9165..55a21bce7f7 100644 --- a/source/source_io/module_ctrl/ctrl_runner_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_runner_lcao.cpp @@ -54,7 +54,7 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell { ModuleIO::Cal_ldos::cal_ldos_lcao(pelec->eferm, chr, dmat, kv, pelec->ekb, pelec->wg, psi[0], pgrid, pv, gd, ucell, - inp.stm_bias, inp.nspin, PARAM.globalv.global_out_dir, + inp.stm_bias, inp.nspin, PARAM.globalv.global_dos_dir, PARAM.globalv.two_fermi, inp.out_ldos[1], ofs_running); } diff --git a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp index 7bd751b1a88..0be22ccc0dc 100644 --- a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp @@ -168,7 +168,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, inp.dos_emin_ev, PARAM.globalv.two_fermi, inp.bndpar, - PARAM.globalv.global_out_dir, + PARAM.globalv.global_dos_dir, ofs_running); } diff --git a/source/source_io/module_parameter/read_input.cpp b/source/source_io/module_parameter/read_input.cpp index 2de46279d8b..15d6b28c52a 100644 --- a/source/source_io/module_parameter/read_input.cpp +++ b/source/source_io/module_parameter/read_input.cpp @@ -13,6 +13,7 @@ #include #include #include "source_base/formatter.h" +#include "source_base/fs_compat.h" #include "source_base/global_file.h" #include "source_base/global_function.h" #include "source_base/tool_quit.h" @@ -306,6 +307,32 @@ void ReadInput::create_directory(const Parameter& param) param.globalv.log_file, param.input.of_ml_gene_data, param.input.deepks_out_freq_elec > 0); // xiaohui add 2013-09-01 + + // Create unified subdirectories when out_app_flag == 2 + if (param.input.out_app_flag == 2) + { + const std::vector subdirs = { + param.globalv.global_psi_dir, + param.globalv.global_chg_dir, + param.globalv.global_pot_dir, + param.globalv.global_elf_dir, + param.globalv.global_dos_dir, + param.globalv.global_dip_dir, + param.globalv.global_mat_dir + }; + for (const auto& dir : subdirs) + { + if (!dir.empty() && dir != param.globalv.global_out_dir) + { + int ret = ModuleBase::make_directory(dir); + if (ret != 0 && errno != EEXIST) + { + std::cout << " CAN NOT MAKE THE DIR: " << dir << std::endl; + } + } + } + } + //const std::string ss = "test -d " + PARAM.inp.read_file_dir; struct stat st; if (stat(PARAM.inp.read_file_dir.c_str(), &st) != 0 || !S_ISDIR(st.st_mode)) From 1beabad0cd45192b96a76a234dce72e7e2973cdc Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 16 Oct 2026 15:04:38 +0800 Subject: [PATCH 03/61] test(io): update directory expectations for out_app_flag default When out_app_flag defaults to 1 (not 2), all new unified directories (PSI, CHG, POT, ELF, DOS, DIP, MAT) map to global_out_dir for backward compatibility. Update io_sys_var_test.cpp expectations: - global_matrix_dir: "OUT.test/matrix/" -> "OUT.test/" - Add assertions for all 7 new directory variables --- source/source_io/test_serial/io_sys_var_test.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/source/source_io/test_serial/io_sys_var_test.cpp b/source/source_io/test_serial/io_sys_var_test.cpp index 678ee6a81b4..7acb032a557 100644 --- a/source/source_io/test_serial/io_sys_var_test.cpp +++ b/source/source_io/test_serial/io_sys_var_test.cpp @@ -68,7 +68,16 @@ TEST_F(InputTest, Item_test) EXPECT_EQ(sys.global_out_dir, "OUT.test/"); EXPECT_EQ(sys.global_stru_dir, "OUT.test/STRU/"); - EXPECT_EQ(sys.global_matrix_dir, "OUT.test/matrix/"); + // out_app_flag defaults to 1, so matrix dir maps to global out dir + EXPECT_EQ(sys.global_matrix_dir, "OUT.test/"); + EXPECT_EQ(sys.global_wfc_dir, "OUT.test/"); + EXPECT_EQ(sys.global_psi_dir, "OUT.test/"); + EXPECT_EQ(sys.global_chg_dir, "OUT.test/"); + EXPECT_EQ(sys.global_pot_dir, "OUT.test/"); + EXPECT_EQ(sys.global_elf_dir, "OUT.test/"); + EXPECT_EQ(sys.global_dos_dir, "OUT.test/"); + EXPECT_EQ(sys.global_dip_dir, "OUT.test/"); + EXPECT_EQ(sys.global_mat_dir, "OUT.test/"); TestParameters::set_globalv(readinput, input, sys); From a4ce9a3d9704e1dc66b9c018985b42d149c9d88c Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 16 Oct 2026 15:10:31 +0800 Subject: [PATCH 04/61] refactor(io): migrate remaining out_app_flag bool usage to int - read_input.cpp: change !out_app_flag to out_app_flag == 0 for matrix/WFC legacy directory decisions - Update three unit tests to use int out_app_flag (0/1) instead of bool (false/true), matching the new function signatures: test_cal_dos.cpp, test_write_dos_pw.cpp, write_wfc_nao_test.cpp - Update two comments to describe 0/1 semantics --- code_quality_score.txt | 66 +++++++++++++++++++ .../module_dos/unittests/test_cal_dos.cpp | 6 +- .../unittests/test_write_dos_pw.cpp | 4 +- .../source_io/module_parameter/read_input.cpp | 4 +- source/source_io/test/write_wfc_nao_test.cpp | 12 ++-- 5 files changed, 79 insertions(+), 13 deletions(-) create mode 100644 code_quality_score.txt diff --git a/code_quality_score.txt b/code_quality_score.txt new file mode 100644 index 00000000000..9a72ceadb64 --- /dev/null +++ b/code_quality_score.txt @@ -0,0 +1,66 @@ +====================================================================== +Code Quality Score Report +====================================================================== + +Per-module rollup (sorted by average score, worst first): + Module Files Avg Pass +---------------------------------------------------------------------- + source/source_io 4 55.0 1/4 + + Score File +---------------------------------------------------------------------- + 36 source/source_io/module_parameter/read_input.cpp + 54 source/source_io/test/write_wfc_nao_test.cpp + 58 source/source_io/module_dos/unittests/test_cal_dos.cpp + 72 source/source_io/module_dos/unittests/test_write_dos_pw.cpp + +---------------------------------------------------------------------- +File: source/source_io/module_parameter/read_input.cpp (score: 36) +---------------------------------------------------------------------- + [file_too_long] file: file has 755 lines (exceeds 500 by 255) (-12) + [uppercase_constant] file: 2 occurrence(s) (-2) + [global_dependency] file: 3 occurrence(s) (-9) + [auto_keyword] file: 13 occurrence(s) (capped at 10) (-10) + [high_cyclomatic_complexity] line 99: function 'filter_nonascii_and_comment' has cyclomatic complexity 11 (exceeds 10 by 1) (-1) + [high_cyclomatic_complexity] line 270: function 'create_directory' has cyclomatic complexity 17 (exceeds 10 by 7) (-7) + [high_cyclomatic_complexity] line 353: function 'read_txt_input' has cyclomatic complexity 17 (exceeds 10 by 7) (-7) + [high_cyclomatic_complexity] line 555: function 'write_txt_input' has cyclomatic complexity 21 (exceeds 10 by 11) (-11) + [high_cyclomatic_complexity] line 651: function 'check_ntype' has cyclomatic complexity 15 (exceeds 10 by 5) (-5) + +---------------------------------------------------------------------- +File: source/source_io/test/write_wfc_nao_test.cpp (score: 54) +---------------------------------------------------------------------- + [file_too_long] file: file has 586 lines (exceeds 500 by 86) (-4) + [tab_indentation] file: 21 occurrence(s) (capped at 5) (-5) + [line_too_long] file: 1 occurrence(s) (-1) + [uppercase_constant] file: 67 occurrence(s) (capped at 5) (-5) + [global_dependency] file: 13 occurrence(s) (capped at 10) (-30) + [member_function_too_long] line 157: member function in class WriteWfcLcaoTest spans 57 lines (exceeds 50) (-1) + +---------------------------------------------------------------------- +File: source/source_io/module_dos/unittests/test_cal_dos.cpp (score: 58) +---------------------------------------------------------------------- + [tab_indentation] file: 112 occurrence(s) (capped at 5) (-5) + [line_too_long] file: 3 occurrence(s) (-3) + [uppercase_constant] file: 28 occurrence(s) (capped at 5) (-5) + [global_dependency] file: 9 occurrence(s) (-27) + [duplicate_doc_block] file: 2 occurrence(s) (-2) + +---------------------------------------------------------------------- +File: source/source_io/module_dos/unittests/test_write_dos_pw.cpp (score: 72) +---------------------------------------------------------------------- + [tab_indentation] file: 114 occurrence(s) (capped at 5) (-5) + [line_too_long] file: 2 occurrence(s) (-2) + [uppercase_constant] file: 25 occurrence(s) (capped at 5) (-5) + [global_dependency] file: 4 occurrence(s) (-12) + [unpaired_new_delete] file: 1 occurrence(s) (-1) + [raw_new_keyword] file: 1 occurrence(s) (-1) + [duplicate_doc_block] file: 1 occurrence(s) (-1) + [member_local_name_conflict] line 35: local variable 'kv' in class DosPWTest shadows a member variable (-1) + +====================================================================== +Files scanned: 4 +Files shown: 4 +Average score: 55.0 +Passing (>= 60): 1/4 +====================================================================== diff --git a/source/source_io/module_dos/unittests/test_cal_dos.cpp b/source/source_io/module_dos/unittests/test_cal_dos.cpp index abcf942bad4..3fe5d410600 100644 --- a/source/source_io/module_dos/unittests/test_cal_dos.cpp +++ b/source/source_io/module_dos/unittests/test_cal_dos.cpp @@ -37,7 +37,7 @@ TEST_F(DosTest,Dos) EXPECT_EQ(dosp.is,0); const int istep = 1; - const bool out_app_flag = false; + const int out_app_flag = 0; const int bndpar = 1; ModuleIO::cal_dos(dosp.is, dosp.fa, @@ -124,7 +124,7 @@ TEST_F(DosTest,DosW1) GlobalV::ofs_warning.open("warning1.log"); const int istep = 1; - const bool out_app_flag = false; + const int out_app_flag = 0; const int bndpar = 1; EXPECT_NO_THROW(ModuleIO::cal_dos(dosp.is, dosp.fa, @@ -171,7 +171,7 @@ TEST_F(DosTest,DosW2) GlobalV::ofs_warning.open("warning2.log"); const int istep = 1; - const bool out_app_flag = false; + const int out_app_flag = 0; const int bndpar = 1; EXPECT_NO_THROW(ModuleIO::cal_dos(dosp.is, dosp.fa, diff --git a/source/source_io/module_dos/unittests/test_write_dos_pw.cpp b/source/source_io/module_dos/unittests/test_write_dos_pw.cpp index 384af45ac63..b866c0b24d6 100644 --- a/source/source_io/module_dos/unittests/test_write_dos_pw.cpp +++ b/source/source_io/module_dos/unittests/test_write_dos_pw.cpp @@ -67,7 +67,7 @@ TEST_F(DosPWTest,Dos1) const bool dos_setemax = true; const bool dos_setemin = true; const bool two_fermi = false; - const bool out_app_flag = false; + const int out_app_flag = 0; const int bndpar = 1; const std::string global_out_dir = "./"; @@ -144,7 +144,7 @@ TEST_F(DosPWTest,Dos2) const int nspin = 1; const int out_dos = 1; const bool two_fermi = false; - const bool out_app_flag = false; + const int out_app_flag = 0; const int bndpar = 1; const std::string global_out_dir = "./"; const bool dos_setemax = false; diff --git a/source/source_io/module_parameter/read_input.cpp b/source/source_io/module_parameter/read_input.cpp index 15d6b28c52a..1eca7302fec 100644 --- a/source/source_io/module_parameter/read_input.cpp +++ b/source/source_io/module_parameter/read_input.cpp @@ -277,7 +277,7 @@ void ReadInput::create_directory(const Parameter& param) // NAME : Run::make_dir( dir name : OUT.suffix) //---------------------------------------------------------- bool out_dir = false; - if (!param.input.out_app_flag + if (param.input.out_app_flag == 0 && ((param.input.out_hsr[0] == 1 || param.input.out_hsr[0] == 2) || param.input.out_mat_r[0] || param.input.out_mat_t[0] || param.input.out_mat_dh[0] || param.input.out_mat_ds[0])) @@ -285,7 +285,7 @@ void ReadInput::create_directory(const Parameter& param) out_dir = true; } bool out_wfc_dir = false; - if (param.input.out_wfc_lcao && !param.input.out_app_flag) + if (param.input.out_wfc_lcao && param.input.out_app_flag == 0) { out_wfc_dir = true; } diff --git a/source/source_io/test/write_wfc_nao_test.cpp b/source/source_io/test/write_wfc_nao_test.cpp index b5a64f2a336..8d7bc6b6a48 100644 --- a/source/source_io/test/write_wfc_nao_test.cpp +++ b/source/source_io/test/write_wfc_nao_test.cpp @@ -35,9 +35,9 @@ TEST(GenWfcLcaoFnameTest, OutType1GammaOnlyOutAppFlagTrue) const int nspin = 1; const int nkstot = 1; const int out_type = 1; - const bool out_app_flag = true; + const int out_app_flag = 1; const bool gamma_only = true; - // if out_app_flag = true, then the 'g' label will not show up + // if out_app_flag = 1 (append), then the 'g' label will not show up const int istep = 0; std::string result = ModuleIO::filename_output( @@ -60,9 +60,9 @@ TEST(GenWfcLcaoFnameTest, OutType2GammaOnlyOutAppFlagFalse) const int nspin = 2; const int nkstot = 2; const int out_type = 2; - const bool out_app_flag = false; + const int out_app_flag = 0; const bool gamma_only = true; - // if out_app_flag = false, then the 'g' label appears + // if out_app_flag = 0 (no append), then the 'g' label appears const int istep = 2; std::string result = ModuleIO::filename_output( @@ -84,7 +84,7 @@ TEST(GenWfcLcaoFnameTest, OutTypeInvalid) const int nspin = 1; const int nkstot = 3; const int out_type = 3; - const bool out_app_flag = true; + const int out_app_flag = 1; const bool gamma_only = false; const int istep = 3; @@ -151,7 +151,7 @@ class WriteWfcLcaoTest : public testing::Test // mohan add 2025-05-11 std::vector ik2iktot = {0,1}; int nkstot = 2; - bool out_app_flag = true; + int out_app_flag = 1; void SetUp() override { From 5adf04bbef598c1662a80f23ea43c5591a83cb0d Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 16 Oct 2026 15:10:43 +0800 Subject: [PATCH 05/61] chore: remove accidentally committed quality report file --- code_quality_score.txt | 66 ------------------------------------------ 1 file changed, 66 deletions(-) delete mode 100644 code_quality_score.txt diff --git a/code_quality_score.txt b/code_quality_score.txt deleted file mode 100644 index 9a72ceadb64..00000000000 --- a/code_quality_score.txt +++ /dev/null @@ -1,66 +0,0 @@ -====================================================================== -Code Quality Score Report -====================================================================== - -Per-module rollup (sorted by average score, worst first): - Module Files Avg Pass ----------------------------------------------------------------------- - source/source_io 4 55.0 1/4 - - Score File ----------------------------------------------------------------------- - 36 source/source_io/module_parameter/read_input.cpp - 54 source/source_io/test/write_wfc_nao_test.cpp - 58 source/source_io/module_dos/unittests/test_cal_dos.cpp - 72 source/source_io/module_dos/unittests/test_write_dos_pw.cpp - ----------------------------------------------------------------------- -File: source/source_io/module_parameter/read_input.cpp (score: 36) ----------------------------------------------------------------------- - [file_too_long] file: file has 755 lines (exceeds 500 by 255) (-12) - [uppercase_constant] file: 2 occurrence(s) (-2) - [global_dependency] file: 3 occurrence(s) (-9) - [auto_keyword] file: 13 occurrence(s) (capped at 10) (-10) - [high_cyclomatic_complexity] line 99: function 'filter_nonascii_and_comment' has cyclomatic complexity 11 (exceeds 10 by 1) (-1) - [high_cyclomatic_complexity] line 270: function 'create_directory' has cyclomatic complexity 17 (exceeds 10 by 7) (-7) - [high_cyclomatic_complexity] line 353: function 'read_txt_input' has cyclomatic complexity 17 (exceeds 10 by 7) (-7) - [high_cyclomatic_complexity] line 555: function 'write_txt_input' has cyclomatic complexity 21 (exceeds 10 by 11) (-11) - [high_cyclomatic_complexity] line 651: function 'check_ntype' has cyclomatic complexity 15 (exceeds 10 by 5) (-5) - ----------------------------------------------------------------------- -File: source/source_io/test/write_wfc_nao_test.cpp (score: 54) ----------------------------------------------------------------------- - [file_too_long] file: file has 586 lines (exceeds 500 by 86) (-4) - [tab_indentation] file: 21 occurrence(s) (capped at 5) (-5) - [line_too_long] file: 1 occurrence(s) (-1) - [uppercase_constant] file: 67 occurrence(s) (capped at 5) (-5) - [global_dependency] file: 13 occurrence(s) (capped at 10) (-30) - [member_function_too_long] line 157: member function in class WriteWfcLcaoTest spans 57 lines (exceeds 50) (-1) - ----------------------------------------------------------------------- -File: source/source_io/module_dos/unittests/test_cal_dos.cpp (score: 58) ----------------------------------------------------------------------- - [tab_indentation] file: 112 occurrence(s) (capped at 5) (-5) - [line_too_long] file: 3 occurrence(s) (-3) - [uppercase_constant] file: 28 occurrence(s) (capped at 5) (-5) - [global_dependency] file: 9 occurrence(s) (-27) - [duplicate_doc_block] file: 2 occurrence(s) (-2) - ----------------------------------------------------------------------- -File: source/source_io/module_dos/unittests/test_write_dos_pw.cpp (score: 72) ----------------------------------------------------------------------- - [tab_indentation] file: 114 occurrence(s) (capped at 5) (-5) - [line_too_long] file: 2 occurrence(s) (-2) - [uppercase_constant] file: 25 occurrence(s) (capped at 5) (-5) - [global_dependency] file: 4 occurrence(s) (-12) - [unpaired_new_delete] file: 1 occurrence(s) (-1) - [raw_new_keyword] file: 1 occurrence(s) (-1) - [duplicate_doc_block] file: 1 occurrence(s) (-1) - [member_local_name_conflict] line 35: local variable 'kv' in class DosPWTest shadows a member variable (-1) - -====================================================================== -Files scanned: 4 -Files shown: 4 -Average score: 55.0 -Passing (>= 60): 1/4 -====================================================================== From e734403187f88fc0c47defa1adc6c45a1a8659fb Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 16 Oct 2026 15:15:17 +0800 Subject: [PATCH 06/61] fix(io): use local cached directory variables in ctrl_output_fp Replace repeated PARAM.globalv.global_*_dir accesses with the local const variables already declared at function top, eliminating the net +4 PARAM global dependency introduced by the path routing change. --- source/source_io/module_ctrl/ctrl_output_fp.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/source_io/module_ctrl/ctrl_output_fp.cpp b/source/source_io/module_ctrl/ctrl_output_fp.cpp index 7fe1a6e0aad..8e673e4fb4b 100644 --- a/source/source_io/module_ctrl/ctrl_output_fp.cpp +++ b/source/source_io/module_ctrl/ctrl_output_fp.cpp @@ -65,7 +65,7 @@ void ctrl_output_fp(UnitCell& ucell, { for (int is = 0; is < nspin; ++is) { - std::string fn = PARAM.globalv.global_chg_dir + "chg"; + std::string fn = global_chg_dir + "chg"; std::string spin_block; if (nspin == 2 || nspin == 4) @@ -98,7 +98,7 @@ void ctrl_output_fp(UnitCell& ucell, if (XC_Functional::get_ked_flag()) { - fn = PARAM.globalv.global_chg_dir + "tau"; + fn = global_chg_dir + "tau"; fn += spin_block + geom_block + ".cube"; @@ -127,7 +127,7 @@ void ctrl_output_fp(UnitCell& ucell, { for (int is = 0; is < nspin; is++) { - std::string fn = PARAM.globalv.global_pot_dir + "pot"; + std::string fn = global_pot_dir + "pot"; std::string spin_block; if (nspin == 2 || nspin == 4) @@ -161,7 +161,7 @@ void ctrl_output_fp(UnitCell& ucell, } else if (inp.out_pot[0] == 2 && should_output) { - std::string fn = PARAM.globalv.global_pot_dir + "potes"; + std::string fn = global_pot_dir + "potes"; fn += geom_block + ".cube"; const std::vector* pcc_potential = pelec->pot->pcc_electron_potential(); @@ -193,7 +193,7 @@ void ctrl_output_fp(UnitCell& ucell, module_charge::cal_rhog_symm(is, chr, pw_rhod, ucell.symm); } - std::string out_dir = PARAM.globalv.global_elf_dir; + std::string out_dir = global_elf_dir; ModuleIO::write_elf(out_dir, istep, nspin, From 9254e95d8d69dce074e253357d0d60ca49ce2d76 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 16 Oct 2026 15:57:58 +0800 Subject: [PATCH 07/61] test(integrate): fix WFC file path in props_cube.sh for out_app_flag=0 With out_app_flag changed from bool to int, the legacy WFC/ subdirectory is no longer created when out_app_flag=0 (now maps to global_out_dir). Update the multi-k wfc reference path accordingly. --- tests/integrate/validation_tools/props_cube.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrate/validation_tools/props_cube.sh b/tests/integrate/validation_tools/props_cube.sh index 1dfd83270ff..2b1b78526b5 100644 --- a/tests/integrate/validation_tools/props_cube.sh +++ b/tests/integrate/validation_tools/props_cube.sh @@ -154,7 +154,7 @@ if ! test -z "$has_lowf" && [ "$has_lowf" == 1 ]; then else # multi-k point case if ! test -z "$out_app_flag" && [ $out_app_flag == 0 ]; then wfc_name=wfk1g3_nao - input_file=WFC/wfk1g3_nao + input_file=wfk1g3_nao else wfc_name=wfk2_nao input_file=wfk2_nao From 00ece1556d44e42fcdb0b33d61a4ebe7057f62fb Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 16 Oct 2026 16:07:23 +0800 Subject: [PATCH 08/61] feat(io): create subdirs on demand and route dmk/lx/rr/tr to MAT - read_input.cpp: only create PSI/CHG/POT/ELF/DOS/DIP/MAT subdirectories when the corresponding output flag is enabled (out_app_flag=2) - ctrl_scf_lcao.cpp: route write_dmk and angular momentum calculator output to global_mat_dir instead of global_out_dir - hsr_legacy.cpp: route tr_nao.csr to global_matrix_dir - pos_op_writer.cpp: route rr_nao.txt to global_matrix_dir --- .../17_relax/04_relax_with_output_lcao/INPUT | 3 ++- examples/18_md/02_lcao_output/INPUT | 2 ++ .../source_io/module_ctrl/ctrl_scf_lcao.cpp | 5 ++-- source/source_io/module_hs/hsr_legacy.cpp | 2 +- source/source_io/module_hs/pos_op_writer.cpp | 2 +- .../source_io/module_parameter/read_input.cpp | 27 +++++++++++-------- 6 files changed, 25 insertions(+), 16 deletions(-) diff --git a/examples/17_relax/04_relax_with_output_lcao/INPUT b/examples/17_relax/04_relax_with_output_lcao/INPUT index 05125b6fa1e..155b3ecc32c 100644 --- a/examples/17_relax/04_relax_with_output_lcao/INPUT +++ b/examples/17_relax/04_relax_with_output_lcao/INPUT @@ -34,7 +34,6 @@ out_dmr 1 out_dos 1 out_band 1 out_stru 1 -out_app_flag 0 out_mat_l 1 out_mat_r 1 @@ -42,3 +41,5 @@ out_mat_t 1 #out_mat_dh 1 out_freq_ion 2 + +out_app_flag 2 diff --git a/examples/18_md/02_lcao_output/INPUT b/examples/18_md/02_lcao_output/INPUT index 7c29d6e1355..c8333b2f0e8 100644 --- a/examples/18_md/02_lcao_output/INPUT +++ b/examples/18_md/02_lcao_output/INPUT @@ -36,3 +36,5 @@ md_tchain 1 dft_functional hse restart_save 1 + +out_app_flag 0 diff --git a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp index 0be22ccc0dc..fd75ea67da3 100644 --- a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp @@ -140,6 +140,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, const bool gamma_only = PARAM.globalv.gamma_only_local; const int nspin = inp.nspin; const std::string global_out_dir = PARAM.globalv.global_out_dir; + const std::string global_mat_dir = PARAM.globalv.global_mat_dir; //------------------------------------------------------------------ //! 1) print out density of states (DOS) @@ -195,7 +196,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, } const int precision = inp.out_dmk[1]; - ModuleIO::write_dmk(dm->get_dmk_vec(), kv, precision, efermis, &(ucell), pv, global_out_dir, istep, ofs_running); + ModuleIO::write_dmk(dm->get_dmk_vec(), kv, precision, efermis, &(ucell), pv, global_mat_dir, istep, ofs_running); } //------------------------------------------------------------------ @@ -558,7 +559,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, PARAM.globalv.gamma_only_local, &ofs_running, GlobalV::MY_RANK); - mylcalculator.calculate(inp.suffix, global_out_dir, ucell, inp.out_mat_l[1], GlobalV::MY_RANK, istep_in); + mylcalculator.calculate(inp.suffix, global_mat_dir, ucell, inp.out_mat_l[1], GlobalV::MY_RANK, istep_in); } //------------------------------------------------------------------ diff --git a/source/source_io/module_hs/hsr_legacy.cpp b/source/source_io/module_hs/hsr_legacy.cpp index d3c7d338d85..5503bba8155 100644 --- a/source/source_io/module_hs/hsr_legacy.cpp +++ b/source/source_io/module_hs/hsr_legacy.cpp @@ -184,7 +184,7 @@ void ModuleIO::output_TR(const int istep, } else { - sst << options.global_out_dir << TR_filename; + sst << options.global_matrix_dir << TR_filename; } ofs_running << " Write T(R) matrix in NAO basis to file: " << sst.str() << std::endl; sparse_format::cal_TR(ucell, pv, HS_Arrays, grid, two_center_bundle, orb, options.sparse_threshold); diff --git a/source/source_io/module_hs/pos_op_writer.cpp b/source/source_io/module_hs/pos_op_writer.cpp index f131047041b..e1dd5a2a7ef 100644 --- a/source/source_io/module_hs/pos_op_writer.cpp +++ b/source/source_io/module_hs/pos_op_writer.cpp @@ -223,7 +223,7 @@ void PosOpWriter::out_lat_r(const UnitCell& ucell, } else { - ssr << global_out_dir << "rr_nao.txt"; + ssr << global_matrix_dir << "rr_nao.txt"; } ofs_tem1.close(); diff --git a/source/source_io/module_parameter/read_input.cpp b/source/source_io/module_parameter/read_input.cpp index 1eca7302fec..cc9bf5ba577 100644 --- a/source/source_io/module_parameter/read_input.cpp +++ b/source/source_io/module_parameter/read_input.cpp @@ -308,21 +308,26 @@ void ReadInput::create_directory(const Parameter& param) param.input.of_ml_gene_data, param.input.deepks_out_freq_elec > 0); // xiaohui add 2013-09-01 - // Create unified subdirectories when out_app_flag == 2 + // Create unified subdirectories when out_app_flag == 2, only for outputs the user actually requested if (param.input.out_app_flag == 2) { - const std::vector subdirs = { - param.globalv.global_psi_dir, - param.globalv.global_chg_dir, - param.globalv.global_pot_dir, - param.globalv.global_elf_dir, - param.globalv.global_dos_dir, - param.globalv.global_dip_dir, - param.globalv.global_mat_dir + const std::vector> subdir_flags = { + {param.input.out_wfc_lcao > 0 || param.input.out_wfc_pw > 0, param.globalv.global_psi_dir}, + {param.input.out_chg[0] > 0, param.globalv.global_chg_dir}, + {param.input.out_pot[0] > 0 || param.input.out_elf[0] > 0, param.globalv.global_pot_dir}, + {param.input.out_elf[0] > 0, param.globalv.global_elf_dir}, + {param.input.out_dos > 0 || !param.input.out_band.empty() && param.input.out_band[0] > 0, param.globalv.global_dos_dir}, + {param.input.out_dipole, param.globalv.global_dip_dir}, + {param.input.out_dmk[0] > 0 || param.input.out_hsk[0] > 0 || param.input.out_mat_hs2[0] > 0 + || param.input.out_mat_r[0] > 0 || param.input.out_mat_t[0] > 0 || param.input.out_mat_tk[0] > 0 + || param.input.out_mat_xc || param.input.out_mat_xc2[0] > 0 || param.input.out_hr_npz + || param.input.out_dm_npz, param.globalv.global_mat_dir} }; - for (const auto& dir : subdirs) + for (const auto& flag_dir : subdir_flags) { - if (!dir.empty() && dir != param.globalv.global_out_dir) + const bool need_create = flag_dir.first; + const std::string& dir = flag_dir.second; + if (need_create && !dir.empty() && dir != param.globalv.global_out_dir) { int ret = ModuleBase::make_directory(dir); if (ret != 0 && errno != EEXIST) From a77e356a4a48d8fd79b329dc80335418910c8790 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Sun, 18 Oct 2026 08:10:35 +0800 Subject: [PATCH 09/61] feat(io): route dmr/tr/rr/dh/ds/lx outputs to MAT directory - Remove md_no_append ternary in hsr_legacy.cpp, pos_op_writer.cpp, hterm_writer.cpp, dhs_sparse_writer.cpp: use global_matrix_dir unconditionally (it already equals global_out_dir when out_app_flag != 2, so no behavior change for legacy modes) - write_dmr: add dmr_dir parameter, route DM(R) to MAT instead of hardcoded global_out_dir; drop parameter.h include - read_input.cpp: add out_dmr/out_mat_l/out_mat_dh/out_mat_ds to MAT subdirectory creation flags Co-Authored-By: Claude Sonnet 4.6 --- source/source_io/module_ctrl/ctrl_scf_lcao.cpp | 5 +++-- source/source_io/module_dm/write_dmr.cpp | 5 +++-- source/source_io/module_dm/write_dmr.h | 2 ++ source/source_io/module_hs/dhs_sparse_writer.cpp | 6 ++---- source/source_io/module_hs/dhs_sparse_writer.h | 1 - source/source_io/module_hs/hsr_legacy.cpp | 7 +++---- source/source_io/module_hs/hterm_writer.cpp | 13 ++++--------- source/source_io/module_hs/pos_op_writer.cpp | 3 +-- .../module_hs/unittests/test_dhs_sparse_writer.cpp | 6 +++--- source/source_io/module_parameter/read_input.cpp | 6 ++++-- 10 files changed, 25 insertions(+), 29 deletions(-) diff --git a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp index fd75ea67da3..12f9067348f 100644 --- a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp @@ -179,9 +179,10 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, if (inp.out_dmr[0]) { const int precision = inp.out_dmr[1]; + const bool append_dmr = (out_app_flag == 1); - ModuleIO::write_dmr(dm->get_dmr_vec(), &ucell, precision, pv, (out_app_flag == 1), - ucell.get_iat2iwt(), ucell.nat, istep, ofs_running); + ModuleIO::write_dmr(dm->get_dmr_vec(), &ucell, precision, pv, append_dmr, + ucell.get_iat2iwt(), ucell.nat, global_mat_dir, istep, ofs_running); } //------------------------------------------------------------------ diff --git a/source/source_io/module_dm/write_dmr.cpp b/source/source_io/module_dm/write_dmr.cpp index 6b95e726a31..58bdd2ec16a 100644 --- a/source/source_io/module_dm/write_dmr.cpp +++ b/source/source_io/module_dm/write_dmr.cpp @@ -1,7 +1,7 @@ #include "write_dmr.h" #include "source_io/module_output/spin_tag.h" -#include "source_io/module_parameter/parameter.h" +#include "source_base/global_variable.h" #include "source_hamilt/module_hcontainer/hcontainer_funcs.h" #include "source_hamilt/module_hcontainer/output_hcontainer.h" #include "source_cell/ucell_io.h" @@ -85,6 +85,7 @@ void write_dmr(const std::vector*> dmr, const bool append, const int* iat2iwt, const int nat, + const std::string& dmr_dir, const int istep, std::ofstream& ofs_running) { @@ -110,7 +111,7 @@ void write_dmr(const std::vector*> dmr, // out_type = 1, csr format; // out_type = 2, npz format (currently not support) const int out_type = 1; - std::string fname = PARAM.globalv.global_out_dir + dmr_gen_fname(out_type, ispin, append, istep); + std::string fname = dmr_dir + dmr_gen_fname(out_type, ispin, append, istep); write_dmr_csr(fname, ucell, precision, &dm_serial, istep, ispin, nspin); const std::string spin_tag = ModuleIO::make_spin_tag(ispin, nspin); ofs_running << " Write DM(R)" << spin_tag << " matrix in NAO basis to file: " << fname << std::endl; diff --git a/source/source_io/module_dm/write_dmr.h b/source/source_io/module_dm/write_dmr.h index 644e2828b52..957553242e8 100644 --- a/source/source_io/module_dm/write_dmr.h +++ b/source/source_io/module_dm/write_dmr.h @@ -54,6 +54,7 @@ void write_dmr_csr(std::string& fname, * @param ispin The spin index, starting from 0. * @param append Whether to append the data to an existing file or create a new file. The file name is related to this * flag. + * @param dmr_dir The directory the DM(R) files are written into. * @param istep The ION step, starting from 0. */ void write_dmr(const std::vector*> dmr, @@ -63,6 +64,7 @@ void write_dmr(const std::vector*> dmr, const bool append, const int* iat2iwt, const int nat, + const std::string& dmr_dir, const int istep, std::ofstream& ofs_running); } // namespace ModuleIO diff --git a/source/source_io/module_hs/dhs_sparse_writer.cpp b/source/source_io/module_hs/dhs_sparse_writer.cpp index 4d69c718a93..557adfa59e7 100644 --- a/source/source_io/module_hs/dhs_sparse_writer.cpp +++ b/source/source_io/module_hs/dhs_sparse_writer.cpp @@ -21,7 +21,6 @@ void ModuleIO::save_dH_sparse(const int& istep, const bool& binary, const std::string& fileflag, const int precision, - const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, const int out_app_flag, @@ -112,20 +111,19 @@ void ModuleIO::save_dH_sparse(const int& istep, } } - const bool md_no_append = (calculation == "md") && (out_app_flag != 1); for (auto& comp: comps) { for (int ispin = 0; ispin < 2; ++ispin) { if (istep >= 0) { - comp.fname[ispin] << (md_no_append ? global_matrix_dir : global_out_dir) + comp.fname[ispin] << global_matrix_dir << "d" << fileflag << "r" << comp.axis << "s" << (ispin + 1) << "g" << (istep + 1) << "_nao.csr"; } else { - comp.fname[ispin] << global_out_dir + comp.fname[ispin] << global_matrix_dir << "d" << fileflag << "r" << comp.axis << "s" << (ispin + 1) << "_nao.csr"; } diff --git a/source/source_io/module_hs/dhs_sparse_writer.h b/source/source_io/module_hs/dhs_sparse_writer.h index 7d6337d577e..59677d8f6ce 100644 --- a/source/source_io/module_hs/dhs_sparse_writer.h +++ b/source/source_io/module_hs/dhs_sparse_writer.h @@ -17,7 +17,6 @@ void save_dH_sparse(const int& istep, const bool& binary, const std::string& fileflag, const int precision, - const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, const int out_app_flag, diff --git a/source/source_io/module_hs/hsr_legacy.cpp b/source/source_io/module_hs/hsr_legacy.cpp index 5503bba8155..8b3fc5a4dfc 100644 --- a/source/source_io/module_hs/hsr_legacy.cpp +++ b/source/source_io/module_hs/hsr_legacy.cpp @@ -40,7 +40,7 @@ void ModuleIO::output_dSR(const int& istep, // mohan update 2024-04-01 const std::string fileflag_s = "s"; ModuleIO::save_dH_sparse(istep, pv, HS_Arrays, options.sparse_threshold, options.binary, - fileflag_s, options.precision, options.global_out_dir, + fileflag_s, options.precision, options.global_matrix_dir, options.calculation, options.out_app_flag, options.nspin, nlocal, ofs_running); @@ -86,7 +86,7 @@ void ModuleIO::output_dHR(const int& istep, // mohan update 2024-04-01 const std::string fileflag_h = "h"; ModuleIO::save_dH_sparse(istep, pv, HS_Arrays, options.sparse_threshold, options.binary, - fileflag_h, options.precision, options.global_out_dir, + fileflag_h, options.precision, options.global_matrix_dir, options.calculation, options.out_app_flag, options.nspin, nlocal, ofs_running); @@ -174,13 +174,12 @@ void ModuleIO::output_TR(const int istep, ModuleBase::timer::start("ModuleIO", "output_TR"); std::stringstream sst; - const bool md_no_append = (options.calculation == "md") && (options.out_app_flag != 1); if (istep >= 0) { // insert the 1-indexed ionic-step suffix right after the "tr" prefix, // i.e. tr_nao.csr -> trg{step+1}_nao.csr (aligned with lxg/rrg naming) const std::string base = TR_filename.substr(0, TR_filename.size() - std::string("_nao.csr").size()); - sst << (md_no_append ? options.global_matrix_dir : options.global_out_dir) << base << "g" << (istep + 1) << "_nao.csr"; + sst << options.global_matrix_dir << base << "g" << (istep + 1) << "_nao.csr"; } else { diff --git a/source/source_io/module_hs/hterm_writer.cpp b/source/source_io/module_hs/hterm_writer.cpp index ee6c997c7b3..95910677d11 100644 --- a/source/source_io/module_hs/hterm_writer.cpp +++ b/source/source_io/module_hs/hterm_writer.cpp @@ -78,9 +78,6 @@ static void gather_and_write(const std::string& prefix, const bool append, const int* iat2iwt, const int nat, - const std::string& calculation, - const int out_app_flag, - const std::string& global_out_dir, const std::string& global_matrix_dir, std::ofstream* ofs_running) { @@ -94,9 +91,7 @@ static void gather_and_write(const std::string& prefix, if (GlobalV::MY_RANK == 0) #endif { - const bool md_no_append = (calculation == "md") && (out_app_flag != 1); - const std::string& out_dir = md_no_append ? global_matrix_dir : global_out_dir; - const std::string fname = out_dir + hsr_gen_fname(prefix, ispin, append, istep); + const std::string fname = global_matrix_dir + hsr_gen_fname(prefix, ispin, append, istep); // H(R) component terms (T, V^NL, V^L, V^H, V^XC, V^EXX) are parts of // the Hamiltonian, not the full H(R): they do not carry the Fermi annotation const double no_efermi = 0.0; @@ -215,8 +210,8 @@ static void write_h_term(WriteHParams& params, const HTermSpec& spec) if (also_hR) { gather_and_write(spec.r_prefix, spec.r_label, spec.term_name, hR_tmp, ucell, pv, nspin, ispin, - istep, append, iat2iwt, nat, params.calculation, params.out_app_flag, - params.global_out_dir, params.global_matrix_dir, params.ofs_running); + istep, append, iat2iwt, nat, + params.global_matrix_dir, params.ofs_running); } } @@ -402,7 +397,7 @@ static void write_h_exx_impl(const UnitCell& ucell, if (also_hR) { gather_and_write("vexx", "V^EXX", "exx", hR_tmp, ucell, pv, nspin, ispin, istep, append, - iat2iwt, nat, calculation, out_app_flag, global_out_dir, global_matrix_dir, + iat2iwt, nat, global_matrix_dir, ofs_running); } } diff --git a/source/source_io/module_hs/pos_op_writer.cpp b/source/source_io/module_hs/pos_op_writer.cpp index e1dd5a2a7ef..8f75d52ed72 100644 --- a/source/source_io/module_hs/pos_op_writer.cpp +++ b/source/source_io/module_hs/pos_op_writer.cpp @@ -216,10 +216,9 @@ void PosOpWriter::out_lat_r(const UnitCell& ucell, if (GlobalV::DRANK == 0) { std::stringstream ssr; - const bool md_no_append = (calculation == "md") && (out_app_flag != 1); if (step >= 0) { - ssr << (md_no_append ? global_matrix_dir : global_out_dir) << "rrg" << (step + 1) << "_nao.txt"; + ssr << global_matrix_dir << "rrg" << (step + 1) << "_nao.txt"; } else { diff --git a/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp b/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp index 3885c7c9472..c667523e075 100644 --- a/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp +++ b/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp @@ -40,7 +40,7 @@ TEST(DhsSparseWriter, TextCountsOnlyValuesAboveThreshold) arrays.dHRx_sparse[0][r_vector][1][0] = 0.0; arrays.dHRx_sparse[0][r_vector][1][1] = -2.0; - ModuleIO::save_dH_sparse(5, pv, arrays, 1e-10, false, "h", 8, "./", "./", "scf", false, 1, 2, ofs_running); + ModuleIO::save_dH_sparse(5, pv, arrays, 1e-10, false, "h", 8, "./", "scf", false, 1, 2, ofs_running); const std::vector lines = read_lines("dhrxs1g6_nao.csr"); ASSERT_GE(lines.size(), 7); @@ -91,7 +91,7 @@ TEST(DhsSparseWriter, BinaryCountsOnlyValuesAboveThreshold) arrays.dHRx_sparse[0][r_vector][1][0] = 0.0; arrays.dHRx_sparse[0][r_vector][1][1] = -2.0; - ModuleIO::save_dH_sparse(6, pv, arrays, 1e-10, true, "h", 8, "./", "./", "scf", false, 1, 2, ofs_running); + ModuleIO::save_dH_sparse(6, pv, arrays, 1e-10, true, "h", 8, "./", "scf", false, 1, 2, ofs_running); std::ifstream ifs("dhrxs1g7_nao.csr", std::ios::binary); ASSERT_TRUE(ifs.is_open()); @@ -127,7 +127,7 @@ TEST(DhsSparseWriter, SocWritesAllDirections) arrays.dHRy_soc_sparse[r_vector][0][1] = std::complex(2.0, -1.0); arrays.dHRz_soc_sparse[r_vector][1][1] = std::complex(-3.0, 0.5); - ModuleIO::save_dH_sparse(7, pv, arrays, 1e-10, false, "s", 8, "./", "./", "scf", false, 4, 2, ofs_running); + ModuleIO::save_dH_sparse(7, pv, arrays, 1e-10, false, "s", 8, "./", "scf", false, 4, 2, ofs_running); const std::string x_output = read_file("dsrxs1g8_nao.csr"); const std::string y_output = read_file("dsrys1g8_nao.csr"); diff --git a/source/source_io/module_parameter/read_input.cpp b/source/source_io/module_parameter/read_input.cpp index cc9bf5ba577..aec29ce0752 100644 --- a/source/source_io/module_parameter/read_input.cpp +++ b/source/source_io/module_parameter/read_input.cpp @@ -318,8 +318,10 @@ void ReadInput::create_directory(const Parameter& param) {param.input.out_elf[0] > 0, param.globalv.global_elf_dir}, {param.input.out_dos > 0 || !param.input.out_band.empty() && param.input.out_band[0] > 0, param.globalv.global_dos_dir}, {param.input.out_dipole, param.globalv.global_dip_dir}, - {param.input.out_dmk[0] > 0 || param.input.out_hsk[0] > 0 || param.input.out_mat_hs2[0] > 0 - || param.input.out_mat_r[0] > 0 || param.input.out_mat_t[0] > 0 || param.input.out_mat_tk[0] > 0 + {param.input.out_dmk[0] > 0 || param.input.out_dmr[0] > 0 || param.input.out_hsk[0] > 0 + || param.input.out_mat_hs2[0] > 0 || param.input.out_mat_r[0] > 0 || param.input.out_mat_t[0] > 0 + || param.input.out_mat_tk[0] > 0 || param.input.out_mat_l[0] > 0 + || param.input.out_mat_dh[0] > 0 || param.input.out_mat_ds[0] > 0 || param.input.out_mat_xc || param.input.out_mat_xc2[0] > 0 || param.input.out_hr_npz || param.input.out_dm_npz, param.globalv.global_mat_dir} }; From 3a384854cfbe9b506fc7959e9093c162e21051e6 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Sun, 18 Oct 2026 08:50:06 +0800 Subject: [PATCH 10/61] feat(io): unify STRU output directory and naming for relax and MD - global_stru_dir now follows out_app_flag: STRU/ for flag 2, global_out_dir otherwise (consistent with MAT/PSI/CHG pattern) - relax_driver: route STRU_NOW/STRU{step}/STRU_FINAL through global_stru_dir instead of global_out_dir - run_md: drop _MD suffix, use STRU{step} naming consistent with relax - global_file: remove unconditional STRU dir creation from make_dir_out; remove global_stru_dir parameter - read_input: create STRU dir on demand (out_stru>0 or MD) Co-Authored-By: Claude Sonnet 4.6 --- source/source_base/global_file.cpp | 40 ------------------- source/source_base/global_file.h | 1 - source/source_base/test/global_file_test.cpp | 20 +--------- .../source_io/module_parameter/read_input.cpp | 2 +- .../module_parameter/read_set_globalv.cpp | 14 +------ .../source_io/test_serial/io_sys_var_test.cpp | 3 +- .../source_io/test_serial/read_input_test.cpp | 1 - source/source_md/run_md.cpp | 2 +- source/source_relax/relax_driver.cpp | 2 +- 9 files changed, 9 insertions(+), 76 deletions(-) diff --git a/source/source_base/global_file.cpp b/source/source_base/global_file.cpp index 76a16036be7..1889357c2c8 100644 --- a/source/source_base/global_file.cpp +++ b/source/source_base/global_file.cpp @@ -30,7 +30,6 @@ void ModuleBase::Global_File::make_dir_out( const bool &restart, const bool out_alllog, const std::string &global_out_dir, - const std::string &global_stru_dir, const std::string &global_matrix_dir, const std::string &global_wfc_dir, const std::string &global_mlkedf_descriptor_dir, @@ -84,45 +83,6 @@ void ModuleBase::Global_File::make_dir_out( MPI_Barrier(MPI_COMM_WORLD); #endif - if(calculation == "md") - { - int make_dir_stru = 0; - - times = 0; - while(times0) { break; -} - ++times; - } - -#ifdef __MPI - if(make_dir_stru==0) - { - std::cout << " CAN NOT MAKE THE STRU DIR......." << std::endl; - ModuleBase::QUIT(); - } - MPI_Barrier(MPI_COMM_WORLD); -#endif - } - // make dir for HS matrix output in md calculation if((out_dir) && calculation == "md") { diff --git a/source/source_base/global_file.h b/source/source_base/global_file.h index d051b5e81c4..4ce5adb18d3 100644 --- a/source/source_base/global_file.h +++ b/source/source_base/global_file.h @@ -28,7 +28,6 @@ namespace Global_File const bool &restart, const bool out_alllog, const std::string &global_out_dir, - const std::string &global_stru_dir, const std::string &global_matrix_dir, const std::string &global_wfc_dir, const std::string &global_mlkedf_descriptor_dir, diff --git a/source/source_base/test/global_file_test.cpp b/source/source_base/test/global_file_test.cpp index 07184237553..fe7bdac7446 100644 --- a/source/source_base/test/global_file_test.cpp +++ b/source/source_base/test/global_file_test.cpp @@ -25,7 +25,6 @@ class GlobalFile : public testing::Test { protected: std::string global_out_dir; - std::string global_stru_dir; std::string global_matrix_dir; std::string global_wfc_dir; std::string global_mlkedf_descriptor_dir; @@ -34,7 +33,6 @@ class GlobalFile : public testing::Test { const std::string suffix = "Si"; global_out_dir = "OUT." + suffix + "/"; - global_stru_dir = global_out_dir + "STRU/"; global_matrix_dir = global_out_dir + "matrix/"; global_wfc_dir = global_out_dir + "WFC/"; global_mlkedf_descriptor_dir = global_out_dir + "MLKEDF_Descriptors/"; @@ -47,7 +45,7 @@ TEST_F(GlobalFile,mkdirout) std::string output; testing::internal::CaptureStdout(); ModuleBase::Global_File::make_dir_out("Si","m",false,false,0,true,true, - global_out_dir, global_stru_dir, global_matrix_dir, global_wfc_dir, + global_out_dir, global_matrix_dir, global_wfc_dir, global_mlkedf_descriptor_dir, global_deepks_label_elec_dir, "running_m_1.log", false, false); output = testing::internal::GetCapturedStdout(); @@ -58,22 +56,9 @@ TEST_F(GlobalFile,mkdirout) std::string dd = "OUT.Si/running_m_1.log"; remove(dd.c_str()); - testing::internal::CaptureStdout(); - ModuleBase::Global_File::make_dir_out("Si","md",false,false,0,true,false, - global_out_dir, global_stru_dir, global_matrix_dir, global_wfc_dir, - global_mlkedf_descriptor_dir, global_deepks_label_elec_dir, - "running_md.log", false, false); - output = testing::internal::GetCapturedStdout(); - EXPECT_THAT(output,testing::HasSubstr("MAKE THE STRU DIR")); - EXPECT_TRUE(GlobalV::ofs_running.is_open()); - GlobalV::ofs_running.close(); - GlobalV::ofs_warning.close(); - std::string bb = "OUT.Si/running_md.log"; - remove(bb.c_str()); - testing::internal::CaptureStdout(); ModuleBase::Global_File::make_dir_out("Si","md",true,false,0,true,true, - global_out_dir, global_stru_dir, global_matrix_dir, global_wfc_dir, + global_out_dir, global_matrix_dir, global_wfc_dir, global_mlkedf_descriptor_dir, global_deepks_label_elec_dir, "running_md_1.log", false, false); output = testing::internal::GetCapturedStdout(); @@ -84,7 +69,6 @@ TEST_F(GlobalFile,mkdirout) remove(cc.c_str()); std::string aa = "OUT.Si/warning.log"; remove(aa.c_str()); - rmdir(global_stru_dir.c_str()); rmdir(global_matrix_dir.c_str()); rmdir(global_out_dir.c_str()); } diff --git a/source/source_io/module_parameter/read_input.cpp b/source/source_io/module_parameter/read_input.cpp index aec29ce0752..aa89d49dd33 100644 --- a/source/source_io/module_parameter/read_input.cpp +++ b/source/source_io/module_parameter/read_input.cpp @@ -299,7 +299,6 @@ void ReadInput::create_directory(const Parameter& param) param.input.mdp.md_restart, param.input.out_alllog, param.globalv.global_out_dir, - param.globalv.global_stru_dir, param.globalv.global_matrix_dir, param.globalv.global_wfc_dir, param.globalv.global_mlkedf_descriptor_dir, @@ -312,6 +311,7 @@ void ReadInput::create_directory(const Parameter& param) if (param.input.out_app_flag == 2) { const std::vector> subdir_flags = { + {param.input.out_stru > 0 || param.input.calculation == "md", param.globalv.global_stru_dir}, {param.input.out_wfc_lcao > 0 || param.input.out_wfc_pw > 0, param.globalv.global_psi_dir}, {param.input.out_chg[0] > 0, param.globalv.global_chg_dir}, {param.input.out_pot[0] > 0 || param.input.out_elf[0] > 0, param.globalv.global_pot_dir}, diff --git a/source/source_io/module_parameter/read_set_globalv.cpp b/source/source_io/module_parameter/read_set_globalv.cpp index 94bfaeb06ac..3a3ed49d1ef 100644 --- a/source/source_io/module_parameter/read_set_globalv.cpp +++ b/source/source_io/module_parameter/read_set_globalv.cpp @@ -85,23 +85,12 @@ void ReadInput::set_global_dir(const Input_para& inp, System_para& sys) sys.global_out_dir = prefix + inp.suffix + "/"; sys.global_out_dir = to_dir(sys.global_out_dir); - /// get the global output directory - sys.global_stru_dir = sys.global_out_dir + "STRU/"; - sys.global_stru_dir = to_dir(sys.global_stru_dir); - - /// get the global output directory - sys.global_matrix_dir = sys.global_out_dir + "matrix/"; - sys.global_matrix_dir = to_dir(sys.global_matrix_dir); - - /// get the global output directory - sys.global_wfc_dir = sys.global_out_dir + "WFC/"; - sys.global_wfc_dir = to_dir(sys.global_wfc_dir); - /// Set subdirectories based on out_app_flag /// out_app_flag == 2: use unified uppercase subdirectories /// otherwise: keep backward-compatible behavior (all point to global_out_dir) if (inp.out_app_flag == 2) { + sys.global_stru_dir = sys.global_out_dir + "STRU/"; sys.global_psi_dir = sys.global_out_dir + "PSI/"; sys.global_chg_dir = sys.global_out_dir + "CHG/"; sys.global_pot_dir = sys.global_out_dir + "POT/"; @@ -112,6 +101,7 @@ void ReadInput::set_global_dir(const Input_para& inp, System_para& sys) } else { + sys.global_stru_dir = sys.global_out_dir; sys.global_psi_dir = sys.global_out_dir; sys.global_chg_dir = sys.global_out_dir; sys.global_pot_dir = sys.global_out_dir; diff --git a/source/source_io/test_serial/io_sys_var_test.cpp b/source/source_io/test_serial/io_sys_var_test.cpp index 7acb032a557..5156027973b 100644 --- a/source/source_io/test_serial/io_sys_var_test.cpp +++ b/source/source_io/test_serial/io_sys_var_test.cpp @@ -67,7 +67,8 @@ TEST_F(InputTest, Item_test) TestParameters::set_global_dir(readinput, input, sys); EXPECT_EQ(sys.global_out_dir, "OUT.test/"); - EXPECT_EQ(sys.global_stru_dir, "OUT.test/STRU/"); + // out_app_flag defaults to 1, so stru dir maps to global out dir + EXPECT_EQ(sys.global_stru_dir, "OUT.test/"); // out_app_flag defaults to 1, so matrix dir maps to global out dir EXPECT_EQ(sys.global_matrix_dir, "OUT.test/"); EXPECT_EQ(sys.global_wfc_dir, "OUT.test/"); diff --git a/source/source_io/test_serial/read_input_test.cpp b/source/source_io/test_serial/read_input_test.cpp index 236b8e8f87d..b948aa9116c 100644 --- a/source/source_io/test_serial/read_input_test.cpp +++ b/source/source_io/test_serial/read_input_test.cpp @@ -39,7 +39,6 @@ void make_dir_out(const std::string& suffix, const bool& restart, const bool out_alllog, const std::string& global_out_dir, - const std::string& global_stru_dir, const std::string& global_matrix_dir, const std::string& global_wfc_dir, const std::string& global_mlkedf_descriptor_dir, diff --git a/source/source_md/run_md.cpp b/source/source_md/run_md.cpp index 114f9cbb3a5..d87849c8791 100644 --- a/source/source_md/run_md.cpp +++ b/source/source_md/run_md.cpp @@ -288,7 +288,7 @@ void md_line(MDCell& mdcell, mdcell.sync_backing_unitcell(); } std::stringstream file; - file << PARAM.globalv.global_stru_dir << "STRU_MD_" << mdrun->step_ + mdrun->step_rst_; + file << PARAM.globalv.global_stru_dir << "STRU" << mdrun->step_ + mdrun->step_rst_; mdcell::print_stru_file(mdcell, mdcell.stru_meta(), file.str()); mdrun->write_restart(PARAM.globalv.global_out_dir); } diff --git a/source/source_relax/relax_driver.cpp b/source/source_relax/relax_driver.cpp index 7497fc8e1c9..1756d7ebf79 100644 --- a/source/source_relax/relax_driver.cpp +++ b/source/source_relax/relax_driver.cpp @@ -21,7 +21,7 @@ void Relax_Driver::relax_driver( // Cache global configuration once so downstream helpers do not each reach // into PARAM / GlobalV. - out_dir_ = PARAM.globalv.global_out_dir; + out_dir_ = PARAM.globalv.global_stru_dir; deepks_setorb_ = PARAM.globalv.deepks_setorb; my_rank_ = GlobalV::MY_RANK; From aabef5f78bd36b1b1fa79616cdfa48aecb346028 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Sun, 18 Oct 2026 09:08:05 +0800 Subject: [PATCH 11/61] feat(md): replace md_restartfreq with out_freq_ion + out_stru - run_md: use out_freq_ion (with md_restartfreq fallback) as the effective ionic-step output frequency for both STRU and restart - run_md: gate STRU output on out_stru > 0, consistent with relax - read_input: simplify STRU dir creation to out_stru > 0 - read_inp_out: update out_stru description to cover MD - read_input_item_md: mark md_restartfreq as deprecated Co-Authored-By: Claude Sonnet 4.6 --- .../module_parameter/read_inp_out.cpp | 2 +- .../source_io/module_parameter/read_input.cpp | 2 +- .../module_parameter/read_input_item_md.cpp | 4 +++- source/source_md/run_md.cpp | 22 ++++++++++++++----- source/source_relax/relax_driver.cpp | 2 +- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index dd600da3666..5d172868e6d 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -456,7 +456,7 @@ Note: values 2 and 3 are no longer supported. Setting out_dos to 2 or 3 will rai item.annotation = "output the structure files per ion step"; item.category = "Output information"; item.type = "Integer"; - item.description = "Controls the output of structure files per ionic step. The files are written to the OUT.{suffix}/ directory. Each file corresponds to the structure at RELAX STEP ${istep} (for scf/nscf this is the single step), i.e., the structure for which that step's energy was computed (before the relax move), and includes a header comment with the ABACUS version, timestamp, energy, and stress tensor. When out_freq_ion is positive, the numbered files STRU{istep} (or STRU{istep}.cif) are written every out_freq_ion steps during geometry relaxation; when out_freq_ion is 0, no numbered files are output. This parameter is effective for scf/nscf/relax/cell-relax; for scf/nscf only STRU_FINAL (or STRU_FINAL.cif) is written, and structure output is disabled by default unless out_stru is set explicitly. Molecular dynamics structure output is instead controlled by md_restartfreq (STRU_MD_*).\n" + item.description = "Controls the output of structure files per ionic step. The files are written to the OUT.{suffix}/ directory. Each file corresponds to the structure at the current ionic step, and includes a header comment with the ABACUS version, timestamp, energy, and stress tensor. When out_freq_ion is positive, the numbered files STRU{istep} (or STRU{istep}.cif) are written every out_freq_ion steps; when out_freq_ion is 0, no numbered files are output. This parameter is effective for scf/nscf/relax/cell-relax and md. For scf/nscf only STRU_FINAL (or STRU_FINAL.cif) is written. For MD, structure output follows the same out_freq_ion frequency as the restart file.\n" " - 0: No structure files are output.\n" " - 1: ABACUS STRU format files are output. The latest structure is written to STRU_NOW (overwritten each step), the numbered file STRU{istep} (e.g., STRU1, STRU2) is written every out_freq_ion steps (when out_freq_ion is positive), and the final converged structure is written to STRU_FINAL. No CIF files are output.\n" " - 2: CIF format files are output. The latest structure is written to STRU_NOW.cif (overwritten each step), the numbered file STRU{istep}.cif (e.g., STRU1.cif, STRU2.cif) is written every out_freq_ion steps (when out_freq_ion is positive), and the final converged structure is written to STRU_FINAL.cif. No non-CIF files are output.\n" diff --git a/source/source_io/module_parameter/read_input.cpp b/source/source_io/module_parameter/read_input.cpp index aa89d49dd33..56b4b750146 100644 --- a/source/source_io/module_parameter/read_input.cpp +++ b/source/source_io/module_parameter/read_input.cpp @@ -311,7 +311,7 @@ void ReadInput::create_directory(const Parameter& param) if (param.input.out_app_flag == 2) { const std::vector> subdir_flags = { - {param.input.out_stru > 0 || param.input.calculation == "md", param.globalv.global_stru_dir}, + {param.input.out_stru > 0, param.globalv.global_stru_dir}, {param.input.out_wfc_lcao > 0 || param.input.out_wfc_pw > 0, param.globalv.global_psi_dir}, {param.input.out_chg[0] > 0, param.globalv.global_chg_dir}, {param.input.out_pot[0] > 0 || param.input.out_elf[0] > 0, param.globalv.global_pot_dir}, diff --git a/source/source_io/module_parameter/read_input_item_md.cpp b/source/source_io/module_parameter/read_input_item_md.cpp index d5f6dc6bd67..eb6ac3db167 100644 --- a/source/source_io/module_parameter/read_input_item_md.cpp +++ b/source/source_io/module_parameter/read_input_item_md.cpp @@ -169,7 +169,9 @@ Note that md_tlast is only used in NVT/NPT simulations. If md_tlast is unset or item.annotation = "The period to output MD restart information"; item.category = "Molecular dynamics"; item.type = "Integer"; - item.description = "The output frequency of OUT.{suffix}/STRU_MD_*, which are used to restart molecular dynamics calculations, see md_restart in detail. Set to 0 to disable MD restart output."; + item.description = "Deprecated: use out_freq_ion to control MD output frequency instead. " + "This parameter is kept for backward compatibility as a fallback when " + "out_freq_ion is not set (0)."; item.default_value = "5"; item.unit = ""; read_sync_int(input.mdp.md_restartfreq); diff --git a/source/source_md/run_md.cpp b/source/source_md/run_md.cpp index d87849c8791..33a9432797e 100644 --- a/source/source_md/run_md.cpp +++ b/source/source_md/run_md.cpp @@ -188,6 +188,13 @@ void md_line(MDCell& mdcell, apply_plumed_forces(mdcell, plumed_forces); }; + /// Effective ionic-step output frequency. + /// Prefer out_freq_ion; fall back to md_restartfreq for backward + /// compatibility when out_freq_ion is not set. + const int eff_output_freq = (param_in.inp.out_freq_ion > 0) + ? param_in.inp.out_freq_ion + : param_in.mdp.md_restartfreq; + /// md cycle, mohan update 2026-01-04, change '<=' to '<' while ((mdrun->step_ + mdrun->step_rst_) < param_in.mdp.md_nstep && !mdrun->stop) { @@ -280,16 +287,21 @@ void md_line(MDCell& mdcell, mdrun->virial); } - if (param_in.mdp.md_restartfreq > 0 - && (mdrun->step_ + mdrun->step_rst_) % param_in.mdp.md_restartfreq == 0) + const int current_step = mdrun->step_ + mdrun->step_rst_; + const bool do_output_step = eff_output_freq > 0 + && current_step % eff_output_freq == 0; + if (do_output_step) { if (mdcell.has_backing_unitcell()) { mdcell.sync_backing_unitcell(); } - std::stringstream file; - file << PARAM.globalv.global_stru_dir << "STRU" << mdrun->step_ + mdrun->step_rst_; - mdcell::print_stru_file(mdcell, mdcell.stru_meta(), file.str()); + if (param_in.inp.out_stru > 0) + { + std::stringstream file; + file << PARAM.globalv.global_stru_dir << "STRU" << current_step; + mdcell::print_stru_file(mdcell, mdcell.stru_meta(), file.str()); + } mdrun->write_restart(PARAM.globalv.global_out_dir); } diff --git a/source/source_relax/relax_driver.cpp b/source/source_relax/relax_driver.cpp index 1756d7ebf79..3c3d5c9273f 100644 --- a/source/source_relax/relax_driver.cpp +++ b/source/source_relax/relax_driver.cpp @@ -186,7 +186,7 @@ bool Relax_Driver::relax_step(std::vector& steps, void Relax_Driver::stru_out(const int istep, UnitCell& ucell, const Input_para& inp, const double etot, const ModuleBase::matrix& stress, const ModuleBase::matrix& force) { - // out_stru is effective for scf/nscf/relax/cell-relax (md writes STRU_MD_* via md_restartfreq) + // out_stru is effective for scf/nscf/relax/cell-relax; md handles STRU output in run_md.cpp if (inp.calculation != "relax" && inp.calculation != "cell-relax" && inp.calculation != "scf" && inp.calculation != "nscf") { From 443c337ef54f7a1674197965bb878e6c977becb5 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Sun, 18 Oct 2026 09:19:13 +0800 Subject: [PATCH 12/61] fix(md): start MD STRU numbering from 1 (like relax) Co-Authored-By: Claude Sonnet 4.6 --- source/source_md/run_md.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/source_md/run_md.cpp b/source/source_md/run_md.cpp index 33a9432797e..1e92c2e9556 100644 --- a/source/source_md/run_md.cpp +++ b/source/source_md/run_md.cpp @@ -298,8 +298,9 @@ void md_line(MDCell& mdcell, } if (param_in.inp.out_stru > 0) { + const int stru_step = current_step + 1; std::stringstream file; - file << PARAM.globalv.global_stru_dir << "STRU" << current_step; + file << PARAM.globalv.global_stru_dir << "STRU" << stru_step; mdcell::print_stru_file(mdcell, mdcell.stru_meta(), file.str()); } mdrun->write_restart(PARAM.globalv.global_out_dir); From b106783a52d54ce39a385bdbd3811ed5f090bb7c Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Mon, 19 Oct 2026 09:17:36 +0800 Subject: [PATCH 13/61] fix(io): restore MD restart STRU lookup after out_app_flag rework - commit 3a384854c made global_stru_dir follow out_app_flag (default 1 maps it to the output dir), but the MD restart lookup still probed STRU_MD_{istep} there, so restart cases whose fixtures live in OUT.suffix/STRU/ (e.g. tests/04_FF/14_LJ_rescale_v) failed to start - search the current global_stru_dir first, then fall back to the legacy OUT.suffix/STRU/ layout; accept both the new STRU{istep+1} naming (1-based, unified with relax) and the legacy STRU_MD_{istep} - quit with a clear diagnostic when no restart structure file exists - add unit test covering both naming schemes in a user read_file_dir --- .../module_parameter/read_set_globalv.cpp | 56 ++++++++++++++++++- .../source_io/test_serial/io_sys_var_test.cpp | 53 ++++++++++++++++++ 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/source/source_io/module_parameter/read_set_globalv.cpp b/source/source_io/module_parameter/read_set_globalv.cpp index 3a3ed49d1ef..5a74f8b41b8 100644 --- a/source/source_io/module_parameter/read_set_globalv.cpp +++ b/source/source_io/module_parameter/read_set_globalv.cpp @@ -3,6 +3,35 @@ #include "source_base/global_variable.h" #include "source_base/tool_quit.h" #include "source_io/module_parameter/parameter.h" + +namespace +{ +/// @brief Find the structure file for restarting a molecular dynamics run. +/// @param stru_dir directory where the previous run wrote its structure files +/// @param istep number of completed ionic steps recorded in Restart_md.txt +/// @return path of the first existing structure file, empty if none exists +/// @details Two naming schemes are accepted, in order: +/// - "STRU{istep + 1}": written by run_md since the ionic-step numbering +/// was unified with relax (1-based); +/// - "STRU_MD_{istep}": written by older ABACUS versions. +std::string find_md_restart_stru(const std::string& stru_dir, const int istep) +{ + const std::string stru_new = stru_dir + "STRU" + std::to_string(istep + 1); + std::ifstream file_new(stru_new.c_str()); + if (file_new.good()) + { + return stru_new; + } + const std::string stru_old = stru_dir + "STRU_MD_" + std::to_string(istep); + std::ifstream file_old(stru_old.c_str()); + if (file_old.good()) + { + return stru_old; + } + return ""; +} +} // namespace + namespace ModuleIO { /// @note Here para.inp has been synchronized of all ranks. @@ -131,15 +160,36 @@ void ReadInput::set_global_dir(const Input_para& inp, System_para& sys) /// get the stru file for md restart case if (inp.calculation == "md" && inp.mdp.md_restart) { - int istep = current_md_step(sys.global_readin_dir); + const int istep = current_md_step(sys.global_readin_dir); if (inp.read_file_dir == to_dir("OUT." + inp.suffix)) { - sys.global_in_stru = sys.global_stru_dir + "STRU_MD_" + std::to_string(istep); + // Restart from the default output directory: search the current + // STRU output directory, which follows out_app_flag. + const std::string stru_dir = sys.global_stru_dir; + sys.global_in_stru = find_md_restart_stru(stru_dir, istep); + if (sys.global_in_stru.empty()) + { + // Releases before the out_app_flag rework always wrote + // STRU_MD_{istep} into a fixed STRU/ subdirectory of the + // output directory; keep those outputs restartable. + const std::string legacy_dir = sys.global_out_dir + "STRU/"; + sys.global_in_stru = find_md_restart_stru(legacy_dir, istep); + } } else { - sys.global_in_stru = inp.read_file_dir + "STRU_MD_" + std::to_string(istep); + // Restart from a user-provided directory: the previous run may + // have written either naming scheme there. + const std::string stru_dir = inp.read_file_dir; + sys.global_in_stru = find_md_restart_stru(stru_dir, istep); + } + + if (sys.global_in_stru.empty()) + { + const std::string message = "no STRU file found for MD restart in " + sys.global_readin_dir + + "; expected STRU_MD_{step} or STRU{step+1}"; + ModuleBase::WARNING_QUIT("ReadInput::set_global_dir", message); } } else diff --git a/source/source_io/test_serial/io_sys_var_test.cpp b/source/source_io/test_serial/io_sys_var_test.cpp index 5156027973b..21257b96694 100644 --- a/source/source_io/test_serial/io_sys_var_test.cpp +++ b/source/source_io/test_serial/io_sys_var_test.cpp @@ -5,6 +5,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "source_base/fs_compat.h" #include "source_base/tool_quit.h" #include "source_io/module_parameter/input_parameter.h" #define private public @@ -110,3 +111,55 @@ TEST_F(InputTest, Item_test) EXPECT_EQ(sys.npol, 1); } } + +TEST_F(InputTest, SetGlobalDirMdRestart) +{ + readinput.check_ntype_flag = false; + + const std::string case_dir = testing::TempDir() + "/abacus_md_restart_io/"; + ModuleBase::make_directory(case_dir); + + // Restart from a user-provided read_file_dir holding the legacy + // "STRU_MD_{istep}" naming: the structure file must be picked up. + const std::string legacy_dir = case_dir + "legacy/"; + ModuleBase::make_directory(legacy_dir); + const std::string restart_txt = legacy_dir + "Restart_md.txt"; + std::ofstream restart_file(restart_txt.c_str()); + restart_file << 5 << std::endl; + restart_file << 0.03 << std::endl; + restart_file.close(); + const std::string stru_legacy = legacy_dir + "STRU_MD_5"; + std::ofstream stru_file_legacy(stru_legacy.c_str()); + stru_file_legacy << "ATOMIC_SPECIES" << std::endl; + stru_file_legacy.close(); + + input.suffix = "autotest"; + input.calculation = "md"; + input.mdp.md_restart = true; + input.read_file_dir = legacy_dir; + TestParameters::set_global_dir(readinput, input, sys); + EXPECT_EQ(sys.global_in_stru, stru_legacy); + + // Restart from a read_file_dir holding the current "STRU{istep + 1}" + // naming written by run_md (1-based, unified with relax). + const std::string newnaming_dir = case_dir + "newnaming/"; + ModuleBase::make_directory(newnaming_dir); + const std::string restart_txt_new = newnaming_dir + "Restart_md.txt"; + std::ofstream restart_file_new(restart_txt_new.c_str()); + restart_file_new << 5 << std::endl; + restart_file_new << 0.03 << std::endl; + restart_file_new.close(); + const std::string stru_new = newnaming_dir + "STRU6"; + std::ofstream stru_file_new(stru_new.c_str()); + stru_file_new << "ATOMIC_SPECIES" << std::endl; + stru_file_new.close(); + + input.read_file_dir = newnaming_dir; + TestParameters::set_global_dir(readinput, input, sys); + EXPECT_EQ(sys.global_in_stru, stru_new); + + // restore the shared input state so other tests are not affected + input.calculation = "scf"; + input.mdp.md_restart = false; + input.read_file_dir = "OUT.$suffix"; +} From 432649cf479b476036b11282c2a43a27b8765783 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 13:18:07 +0800 Subject: [PATCH 14/61] fix(io): address review comments on out_app_flag mode-2 rework - add PSI/ candidate in read_wfc_nao so RT-TDDFT restart finds mode-2 snapshots - route H(k) term outputs to MAT directory; add out_hsr_npz to MAT dir creation predicate; narrow DOS dir creation predicate to out_dos/out_ldos - pass boolean append to save_mat in write_hk_common (was int out_app_flag) - update out_app_flag/out_wfc_lcao INPUT metadata and position_matrix docs - add mode-2 directory mapping and PSI layout roundtrip unit tests --- .../elec_properties/position_matrix.md | 2 +- source/source_esolver/esolver_ks_lcaopw.cpp | 2 +- .../module_ctrl/ctrl_runner_lcao.cpp | 4 +- .../source_io/module_ctrl/ctrl_scf_lcao.cpp | 16 +-- source/source_io/module_dhs/write_dh.cpp | 3 +- source/source_io/module_hs/hsk_writer.cpp | 7 +- source/source_io/module_hs/hterm_writer.cpp | 2 +- source/source_io/module_hs/vxc_op_mat.cpp | 3 +- .../module_parameter/read_inp_out.cpp | 33 ++++-- .../source_io/module_parameter/read_input.cpp | 8 +- source/source_io/module_wf/read_wfc_nao.cpp | 77 ++++++++++-- source/source_io/test/read_wfc_nao_test.cpp | 112 ++++++++++++++++++ .../source_io/test_serial/io_sys_var_test.cpp | 28 +++++ .../source_io/test_serial/read_input_test.cpp | 40 +++++++ 14 files changed, 303 insertions(+), 34 deletions(-) diff --git a/docs/advanced/elec_properties/position_matrix.md b/docs/advanced/elec_properties/position_matrix.md index 38743d5b65b..ccd71f56dcf 100644 --- a/docs/advanced/elec_properties/position_matrix.md +++ b/docs/advanced/elec_properties/position_matrix.md @@ -18,7 +18,7 @@ Each file or each section of the appended file starts with "STEP: " followed by Each block here contains the matrix for the corresponding cell. There are three columns in each block, giving the matrix elements in x, y, z directions, respectively. There are altogether nbasis * nbasis lines in each block, which emulates the matrix elements. -In molecular dynamics (MD) calculations, if [out_app_flag](../input_files/input-main.md#out_app_flag) is set to 1, then `data-rR-tr` is written in an append manner. Otherwise, output files will be put in a separate directory, `matrix`, and named as `$x`_data-rR-tr, where `$x` is the number of MD step. In addition, the output frequency is controlled by [out_freq_ion](../input_files/input-main.md#out_freq_ion). For example, if we are running a 10-step MD with out_freq_ion = 3, then `$x` will be 0, 3, 6, and 9. +In molecular dynamics (MD) calculations, the output frequency is controlled by [out_freq_ion](../input_files/input-main.md#out_freq_ion). Each ionic step is written to its own file named `rrg${step}_nao.txt`, where `${step}` is the one-based ionic-step index, while the single-shot output of `calculation = get_s` is named `rr_nao.txt`. The files are stored in `OUT.${suffix}` when [out_app_flag](../input_files/input-main.md#out_app_flag) is 0 or 1, and in its `MAT/` subdirectory when out_app_flag is 2. For example, if we are running a 10-step MD with out_freq_ion = 3 and out_app_flag = 2, the files are `MAT/rrg1_nao.txt`, `MAT/rrg4_nao.txt`, `MAT/rrg7_nao.txt`, and `MAT/rrg10_nao.txt`. ## get_s We also offer the option of only calculating the position matrix without running SCF. For that purpose, in `INPUT` file we need to set the keyword [calculation](../input_files/input-main.md#calculation) to `get_s`, and [out_mat_r](../input_files/input-main.md#out_mat_r) to `true`. diff --git a/source/source_esolver/esolver_ks_lcaopw.cpp b/source/source_esolver/esolver_ks_lcaopw.cpp index 5c6ba6e9549..f5ece6c3be7 100644 --- a/source/source_esolver/esolver_ks_lcaopw.cpp +++ b/source/source_esolver/esolver_ks_lcaopw.cpp @@ -299,7 +299,7 @@ namespace ModuleESolver this->kv, this->pelec->wg, PARAM.globalv.gamma_only_local, - PARAM.globalv.global_out_dir, + PARAM.globalv.global_mat_dir, this->inp_->out_ndigits, this->inp_->ks_solver, cal_exx, diff --git a/source/source_io/module_ctrl/ctrl_runner_lcao.cpp b/source/source_io/module_ctrl/ctrl_runner_lcao.cpp index 55a21bce7f7..f2f6079d860 100644 --- a/source/source_io/module_ctrl/ctrl_runner_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_runner_lcao.cpp @@ -84,7 +84,7 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell gd, inp.dft_plus_u, PARAM.globalv.gamma_only_local, - PARAM.globalv.global_out_dir, + PARAM.globalv.global_mat_dir, inp.out_ndigits, inp.ks_solver, cal_exx, @@ -119,7 +119,7 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell kv, orb.cutoffs(), gd, - PARAM.globalv.global_out_dir, + PARAM.globalv.global_mat_dir, cal_exx, hybrid_alpha, real_number diff --git a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp index 12f9067348f..203d1de0695 100644 --- a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp @@ -207,7 +207,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, if (hsk_out_type == 1 || hsk_out_type == 2) { const int precision = inp.out_hsk[1]; - ModuleIO::write_hsk(global_out_dir, + ModuleIO::write_hsk(global_mat_dir, nspin, kv.get_nks(), kv.get_nkstot(), @@ -282,7 +282,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, ModuleIO::write_hsr(hr_vec, sr, &ucell, inp.out_hsr[0], precision, pv, (out_app_flag == 1), gamma_only, ucell.get_iat2iwt(), ucell.nat, istep, - PARAM.globalv.global_out_dir, pelec->eferm, + global_mat_dir, pelec->eferm, ofs_running); } @@ -292,7 +292,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, const bool output_hsr_npz = inp.out_hsr[0] == 3 || inp.out_hsr_npz_compat; if (output_hsr_npz) { - std::string zipname = PARAM.globalv.global_out_dir + "sr_nao.npz"; + std::string zipname = global_mat_dir + "sr_nao.npz"; ModuleIO::output_mat_npz(ucell, zipname, *(p_hamilt->getSR())); } @@ -302,7 +302,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, for (int ispin = 0; ispin < hr_vec.size(); ++ispin) { std::string zipname - = PARAM.globalv.global_out_dir + "hrs" + std::to_string(ispin + 1) + "_nao.npz"; + = global_mat_dir + "hrs" + std::to_string(ispin + 1) + "_nao.npz"; ModuleIO::output_mat_npz(ucell, zipname, *(hr_vec[ispin])); } } @@ -313,7 +313,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, for (int ispin = 0; ispin < dmr_vec.size(); ++ispin) { std::string zipname - = PARAM.globalv.global_out_dir + "output_DM" + std::to_string(ispin) + ".npz"; + = global_mat_dir + "output_DM" + std::to_string(ispin) + ".npz"; ModuleIO::output_mat_npz(ucell, zipname, *(dmr_vec[ispin])); } } @@ -455,7 +455,9 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, h_params.gga_grad = PARAM.inp.gga_grad; h_params.out_app_flag = out_app_flag; h_params.calculation = inp.calculation; - h_params.global_out_dir = global_out_dir; + // H(k) component matrices follow the selected matrix directory so + // they land in MAT/ together with the other matrix outputs in mode 2 + h_params.global_out_dir = global_mat_dir; h_params.global_matrix_dir = PARAM.globalv.global_matrix_dir; h_params.ks_solver = PARAM.inp.ks_solver; h_params.drank = GlobalV::DRANK; @@ -511,7 +513,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, const int out_label = 1; // 1: .txt, 2: .dat - std::string t_fn = ModuleIO::filename_output(global_out_dir, + std::string t_fn = ModuleIO::filename_output(global_mat_dir, "tk", "nao", ik, diff --git a/source/source_io/module_dhs/write_dh.cpp b/source/source_io/module_dhs/write_dh.cpp index e0d99fde3ee..d05ff12716a 100644 --- a/source/source_io/module_dhs/write_dh.cpp +++ b/source/source_io/module_dhs/write_dh.cpp @@ -123,13 +123,14 @@ void write_dh_perI(WriteDHParams& params, fk += "_ik" + std::to_string(params.kv->ik2iktot[ik]); } fk += "_nao.txt"; + const bool append_fk = (out_app_flag == 1); ModuleIO::save_mat(params.istep, hk.data(), nlocal, false, 8, false, - (out_app_flag == 1), + append_fk, fk, pv, GlobalV::DRANK, diff --git a/source/source_io/module_hs/hsk_writer.cpp b/source/source_io/module_hs/hsk_writer.cpp index 0faa1ba1aa7..8b0242cf0b6 100644 --- a/source/source_io/module_hs/hsk_writer.cpp +++ b/source/source_io/module_hs/hsk_writer.cpp @@ -40,6 +40,9 @@ void ModuleIO::write_hsk( { p_hamilt->updateHk(ik); const bool binary = (out_type == 2); + // save_mat takes a Boolean append flag: only mode 1 appends, modes + // 0 and 2 overwrite their per-step files + const bool append_mat = (out_app_flag == 1); hamilt::MatrixBlock h_mat; hamilt::MatrixBlock s_mat; @@ -56,7 +59,7 @@ void ModuleIO::write_hsk( binary, precision, 1, - out_app_flag, + append_mat, h_fn, pv, drank, @@ -83,7 +86,7 @@ void ModuleIO::write_hsk( binary, precision, 1, - out_app_flag, + append_mat, s_fn, pv, drank, diff --git a/source/source_io/module_hs/hterm_writer.cpp b/source/source_io/module_hs/hterm_writer.cpp index 95910677d11..3e3c7a00204 100644 --- a/source/source_io/module_hs/hterm_writer.cpp +++ b/source/source_io/module_hs/hterm_writer.cpp @@ -154,7 +154,7 @@ static void write_hk_common(hamilt::HContainer& hR, false, 8, false, - out_app_flag, + append, fname, pv, drank, diff --git a/source/source_io/module_hs/vxc_op_mat.cpp b/source/source_io/module_hs/vxc_op_mat.cpp index b749e110b5a..a8f95c42d84 100644 --- a/source/source_io/module_hs/vxc_op_mat.cpp +++ b/source/source_io/module_hs/vxc_op_mat.cpp @@ -116,6 +116,7 @@ void write_Vxc(const int nspin, const int istep = -1; const int out_label = 1; // 1 means .txt while 2 means .dat const int out_app_flag = 0; + const bool append_vxc = (out_app_flag == 1); std::string vxc_file = ModuleIO::filename_output( global_out_dir, "vxc", "nao", ik, kv.ik2iktot, nspin, kv.get_nkstot(), @@ -127,7 +128,7 @@ void write_Vxc(const int nspin, false /*binary*/, out_ndigits, true /*triangle*/, - out_app_flag /*append*/, + append_vxc, vxc_file, p2d, drank, diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index 5d172868e6d..904a87123d4 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -297,7 +297,7 @@ With `init_wfc file binary`, ABACUS reads only unindexed binary `wf*_pw.dat` fil item.annotation = "ouput LCAO wave functions, 0, no output 1: text, 2: binary"; item.category = "Output information"; item.type = "Integer"; - item.description = R"(Whether to output the electronic wavefunction coefficients into files and store them in the folder OUT.${suffix}. The files are named as wf{s}{spin index}{k(optional)}{k-point index}{g(optional)}{geometry index1}{_nao} + {".txt"/".dat"}. Here, 's' refers to spin, where s1 means spin up channel while s2 means spin down channel, and 's12' refer to spinor wave functions that contains both spin channels with spin-orbital coupling or noncollinear calculations enabled. In addition, if 'gamma_only' is set to 0, then the optinoal k-point sampling index appears with the k-point index attached to the electronic wave function file names. Finally, if out_app_flag is set to false, the file name contains the optional 'g' index for each ionic step that may have different geometries, and if out_app_flag is set to true, the wave functions accumulate during ionic steps. If the out_app_flag is set to false, a new folder named WFC will be created, and the wave function files will be saved into it. + item.description = R"(Whether to output the electronic wavefunction coefficients into files and store them in the folder OUT.${suffix} (or its PSI/ subdirectory when out_app_flag is 2). The files are named as wf{s}{spin index}{k(optional)}{k-point index}{g(optional)}{geometry index1}{_nao} + {".txt"/".dat"}. Here, 's' refers to spin, where s1 means spin up channel while s2 means spin down channel, and 's12' refer to spinor wave functions that contains both spin channels with spin-orbital coupling or noncollinear calculations enabled. In addition, if 'gamma_only' is set to 0, then the optinoal k-point sampling index appears with the k-point index attached to the electronic wave function file names. Finally, if out_app_flag is not 1, the file name contains the optional 'g' index for each ionic step that may have different geometries, and if out_app_flag is 1, the wave functions accumulate during ionic steps. When out_app_flag is 2, the wave function files are saved into the PSI/ subdirectory of OUT.${suffix}. * 0: no output * 1: (txt format) * gamma-only: wfs1_nao.txt or wfs2_nao.txt, ...; @@ -1391,19 +1391,38 @@ The circle order of the charge density on real space grids is: x is the outer lo } { Input_Item item("out_app_flag"); - item.annotation = "whether output r(R), H(R), S(R), T(R), and dH(R) " - "matrices in an append manner during MD"; + item.annotation = "output directory layout and append behavior " + "during multi-step calculations (0/1/2)"; item.category = "Output information"; item.type = "Integer"; - item.description = R"(Controls output file organization during multi-step calculations (MD, relax, RT-TDDFT). -* 0: Write to OUT directory directly, no append (files overwritten each step) + item.description = R"(Controls output file organization during multi-step calculations (MD, relax, RT-TDDFT). The directory routing applies to both LCAO and PW basis calculations. +* 0: Write to OUT directory directly, no append (each step's file carries a g{step} suffix) * 1: Append to same file (default, backward compatible) -* 2: Write to subdirectory (PSI/, CHG/, POT/, ELF/, DOS/, DIP/, MAT/) +* 2: Write to subdirectory (PSI/, CHG/, POT/, ELF/, DOS/, DIP/, MAT/), files carry a g{step} suffix +The legacy Boolean spellings (true/false, yes/no, on/off, t/f, y/n, and the dotted forms) are still accepted and map to modes 1/0 as before. For more information, refer to the documentation of out_mat_r, out_hsr, out_mat_t, out_mat_dh, out_hsk, out_wfc_lcao, out_chg, out_pot, out_elf, out_dos, out_dipole.)"; item.default_value = "1"; item.unit = ""; item.set_availability("basis_type==lcao and gamma_only==0"); - read_sync_int(input.out_app_flag); + // Keep the previously accepted Boolean spellings working: the legacy + // parser treated out_app_flag as a Boolean, so INPUT files containing + // "true"/"false"/"yes"/"no"/"on"/"off" (case-insensitive) must keep + // parsing instead of reaching std::stoi and throwing. + item.read_value = [](const Input_Item& item, Parameter& para) { + try + { + para.input.out_app_flag = std::stoi(item.str_values[0]); + } + catch (const std::invalid_argument&) + { + para.input.out_app_flag = assume_as_boolean(item.str_values[0]) ? 1 : 0; + } + catch (const std::out_of_range&) + { + para.input.out_app_flag = assume_as_boolean(item.str_values[0]) ? 1 : 0; + } + }; + sync_int(input.out_app_flag); item.check_value = [](const Input_Item& item, const Parameter& para) { if (para.input.out_app_flag < 0 || para.input.out_app_flag > 2) { diff --git a/source/source_io/module_parameter/read_input.cpp b/source/source_io/module_parameter/read_input.cpp index 56b4b750146..366082cf54e 100644 --- a/source/source_io/module_parameter/read_input.cpp +++ b/source/source_io/module_parameter/read_input.cpp @@ -314,16 +314,18 @@ void ReadInput::create_directory(const Parameter& param) {param.input.out_stru > 0, param.globalv.global_stru_dir}, {param.input.out_wfc_lcao > 0 || param.input.out_wfc_pw > 0, param.globalv.global_psi_dir}, {param.input.out_chg[0] > 0, param.globalv.global_chg_dir}, - {param.input.out_pot[0] > 0 || param.input.out_elf[0] > 0, param.globalv.global_pot_dir}, + {param.input.out_pot[0] > 0, param.globalv.global_pot_dir}, {param.input.out_elf[0] > 0, param.globalv.global_elf_dir}, - {param.input.out_dos > 0 || !param.input.out_band.empty() && param.input.out_band[0] > 0, param.globalv.global_dos_dir}, + {param.input.out_dos > 0 + || (!param.input.out_ldos.empty() && param.input.out_ldos[0] > 0), + param.globalv.global_dos_dir}, {param.input.out_dipole, param.globalv.global_dip_dir}, {param.input.out_dmk[0] > 0 || param.input.out_dmr[0] > 0 || param.input.out_hsk[0] > 0 || param.input.out_mat_hs2[0] > 0 || param.input.out_mat_r[0] > 0 || param.input.out_mat_t[0] > 0 || param.input.out_mat_tk[0] > 0 || param.input.out_mat_l[0] > 0 || param.input.out_mat_dh[0] > 0 || param.input.out_mat_ds[0] > 0 || param.input.out_mat_xc || param.input.out_mat_xc2[0] > 0 || param.input.out_hr_npz - || param.input.out_dm_npz, param.globalv.global_mat_dir} + || param.input.out_hsr_npz || param.input.out_dm_npz, param.globalv.global_mat_dir} }; for (const auto& flag_dir : subdir_flags) { diff --git a/source/source_io/module_wf/read_wfc_nao.cpp b/source/source_io/module_wf/read_wfc_nao.cpp index 58599769d4c..d814a1c52bb 100644 --- a/source/source_io/module_wf/read_wfc_nao.cpp +++ b/source/source_io/module_wf/read_wfc_nao.cpp @@ -8,6 +8,7 @@ #include "source_base/global_function.h" // use READ_VALUE #include +#include namespace { @@ -296,18 +297,78 @@ bool ModuleIO::read_wfc_nao( { if (myrank == 0) { - const int out_app_flag = 0; std::stringstream error_message; - std::string readin_dir = global_readin_dir; - if(istep >= 0) + const int read_type = binary ? 2 : 1; + // Candidate file locations, in priority order: + // 1. readin_dir + step-suffixed name: layout written by runs with + // out_app_flag 0 (files in the OUT directory root) + // 2. readin_dir + "PSI/" + step-suffixed name: layout written by + // runs with out_app_flag 2 + // 3. readin_dir + "WFC/" + step-suffixed name: layout written by + // releases before the out_app_flag rework + // 4. readin_dir + name without the step suffix: written in append + // mode (out_app_flag 1) + std::vector candidates; + if (istep >= 0) { - readin_dir = readin_dir + "WFC/"; + const int suffix_app_flag = 0; + candidates.push_back(ModuleIO::filename_output(global_readin_dir, + "wf", + "nao", + ik, + ik2iktot, + nspin, + nkstot, + read_type, + suffix_app_flag, + gamma_only, + istep)); + const std::string psi_dir = global_readin_dir + "PSI/"; + candidates.push_back(ModuleIO::filename_output(psi_dir, + "wf", + "nao", + ik, + ik2iktot, + nspin, + nkstot, + read_type, + suffix_app_flag, + gamma_only, + istep)); + const std::string legacy_dir = global_readin_dir + "WFC/"; + candidates.push_back(ModuleIO::filename_output(legacy_dir, + "wf", + "nao", + ik, + ik2iktot, + nspin, + nkstot, + read_type, + suffix_app_flag, + gamma_only, + istep)); } - const int read_type = binary ? 2 : 1; - std::string ss = ModuleIO::filename_output(readin_dir,"wf","nao", - ik,ik2iktot,nspin,nkstot,read_type,out_app_flag,gamma_only,istep); + const int append_app_flag = 1; + candidates.push_back(ModuleIO::filename_output(global_readin_dir, + "wf", + "nao", + ik, + ik2iktot, + nspin, + nkstot, + read_type, + append_app_flag, + gamma_only, + istep)); - read_success = read_one_file(ss, error_message, ik, ctot); + for (size_t ic = 0; ic < candidates.size(); ++ic) + { + read_success = read_one_file(candidates[ic], error_message, ik, ctot); + if (read_success) + { + break; + } + } errors = error_message.str(); } #ifdef __MPI diff --git a/source/source_io/test/read_wfc_nao_test.cpp b/source/source_io/test/read_wfc_nao_test.cpp index 6107db23036..2d2a9e4fa30 100644 --- a/source/source_io/test/read_wfc_nao_test.cpp +++ b/source/source_io/test/read_wfc_nao_test.cpp @@ -451,6 +451,118 @@ TEST_F(ReadWfcNaoTest, RejectTruncatedBinary) true)); } +TEST_F(ReadWfcNaoTest, ReadPsiDirectoryLayout) +{ + // RT-TDDFT restart with out_app_flag = 2: the snapshot lives in the + // PSI/ subdirectory of the read-in directory and carries the one-based + // g{step} suffix in its name. + const int nbands = 2; + const int nlocal = 3; + Parallel_Orbitals para; + initialize_parallel_orbitals(para, nlocal, nbands); + + const std::string psi_dir = binary_test_dir + "PSI/"; + if (my_rank == 0) + { + ModuleBase::GlobalFunc::MAKE_DIR(psi_dir); + } +#ifdef __MPI + MPI_Barrier(MPI_COMM_WORLD); +#endif + + const std::vector coefficients = {0.1, 0.2, 0.3, 1.1, 1.2, 1.3}; + ModuleBase::matrix ekb_source(1, nbands); + ModuleBase::matrix wg_source(1, nbands); + ekb_source(0, 0) = -0.6; + ekb_source(0, 1) = 0.9; + wg_source(0, 0) = 2.0; + wg_source(0, 1) = 0.0; + + const int istep = 0; + const std::vector ik2iktot = {0}; + const int read_type = 2; + const int suffix_app_flag = 0; + // The real-valued reader builds candidate names through the gamma-only + // branch of filename_output, so the snapshot must use the same naming. + const bool gamma_only_for_name = true; + const std::string snapshot = ModuleIO::filename_output(psi_dir, + "wf", + "nao", + 0, + ik2iktot, + 1, + 1, + read_type, + suffix_app_flag, + gamma_only_for_name, + istep); + if (my_rank == 0) + { + ModuleIO::wfc_nao_write2file(snapshot, + coefficients.data(), + nlocal, + 0, + ekb_source, + wg_source, + true, + false); + } +#ifdef __MPI + MPI_Barrier(MPI_COMM_WORLD); +#endif + + psi::Psi psi_read; + ModuleBase::matrix ekb(1, nbands); + ModuleBase::matrix wg(1, nbands); + EXPECT_TRUE(ModuleIO::read_wfc_nao(binary_test_dir, + para, + psi_read, + ekb, + wg, + ik2iktot, + 1, + 1, + true, + 0, + istep)); + EXPECT_DOUBLE_EQ(ekb(0, 0), ekb_source(0, 0)); + EXPECT_DOUBLE_EQ(ekb(0, 1), ekb_source(0, 1)); + if (my_rank == 0) + { + EXPECT_DOUBLE_EQ(psi_read(0, 0, 0), coefficients[0]); + } + + // Rewriting the snapshot must replace it, not append another record. + if (my_rank == 0) + { + std::ifstream ifs(snapshot.c_str(), std::ios::binary); + ifs.seekg(0, std::ios::end); + const std::streamoff size_after_first_write = ifs.tellg(); + ifs.close(); + + ModuleIO::wfc_nao_write2file(snapshot, + coefficients.data(), + nlocal, + 0, + ekb_source, + wg_source, + true, + false); + + std::ifstream ifs_rewritten(snapshot.c_str(), std::ios::binary); + ifs_rewritten.seekg(0, std::ios::end); + const std::streamoff size_after_second_write = ifs_rewritten.tellg(); + ifs_rewritten.close(); + EXPECT_EQ(size_after_second_write, size_after_first_write); + + std::remove(snapshot.c_str()); + std::remove(psi_dir.c_str()); + } +#ifdef __MPI + MPI_Barrier(MPI_COMM_WORLD); +#endif +} + #ifdef __MPI diff --git a/source/source_io/test_serial/io_sys_var_test.cpp b/source/source_io/test_serial/io_sys_var_test.cpp index 21257b96694..2fe0fa4ba6d 100644 --- a/source/source_io/test_serial/io_sys_var_test.cpp +++ b/source/source_io/test_serial/io_sys_var_test.cpp @@ -163,3 +163,31 @@ TEST_F(InputTest, SetGlobalDirMdRestart) input.mdp.md_restart = false; input.read_file_dir = "OUT.$suffix"; } + +TEST_F(InputTest, SetGlobalDirMode2) +{ + readinput.check_ntype_flag = false; + + // out_app_flag = 2 routes every output class to its unified + // subdirectory below OUT./ + input.suffix = "modetwo"; + input.out_app_flag = 2; + TestParameters::set_global_dir(readinput, input, sys); + + EXPECT_EQ(sys.global_out_dir, "OUT.modetwo/"); + EXPECT_EQ(sys.global_stru_dir, "OUT.modetwo/STRU/"); + EXPECT_EQ(sys.global_psi_dir, "OUT.modetwo/PSI/"); + EXPECT_EQ(sys.global_chg_dir, "OUT.modetwo/CHG/"); + EXPECT_EQ(sys.global_pot_dir, "OUT.modetwo/POT/"); + EXPECT_EQ(sys.global_elf_dir, "OUT.modetwo/ELF/"); + EXPECT_EQ(sys.global_dos_dir, "OUT.modetwo/DOS/"); + EXPECT_EQ(sys.global_dip_dir, "OUT.modetwo/DIP/"); + EXPECT_EQ(sys.global_mat_dir, "OUT.modetwo/MAT/"); + // legacy aliases follow the unified directories + EXPECT_EQ(sys.global_wfc_dir, sys.global_psi_dir); + EXPECT_EQ(sys.global_matrix_dir, sys.global_mat_dir); + + // restore the shared input state so other tests are not affected + input.suffix = "test"; + input.out_app_flag = 1; +} diff --git a/source/source_io/test_serial/read_input_test.cpp b/source/source_io/test_serial/read_input_test.cpp index b948aa9116c..bc84cbd1925 100644 --- a/source/source_io/test_serial/read_input_test.cpp +++ b/source/source_io/test_serial/read_input_test.cpp @@ -396,3 +396,43 @@ TEST_F(InputTest, Check) EXPECT_THAT(output, testing::HasSubstr("INPUT parameters have been successfully checked!")); EXPECT_TRUE(std::remove("./INPUT.ref") == 0); } + +TEST_F(InputTest, OutAppFlagParsing) +{ + // numeric modes 0/1/2 are parsed as integers + { + Parameter param; + read_parameters("app_flag_numeric_INPUT", "basis_type lcao\nout_app_flag 2\n", param); + EXPECT_EQ(param.inp.out_app_flag, 2); + } + { + Parameter param; + read_parameters("app_flag_zero_INPUT", "basis_type lcao\nout_app_flag 0\n", param); + EXPECT_EQ(param.inp.out_app_flag, 0); + } + // the legacy Boolean spellings remain valid: true-like maps to mode 1, + // false-like maps to mode 0 + const std::vector> spellings + = {{"true", 1}, + {"TRUE", 1}, + {"Yes", 1}, + {"ON", 1}, + {".true.", 1}, + {"t", 1}, + {"false", 0}, + {"False", 0}, + {"NO", 0}, + {"off", 0}, + {".false.", 0}, + {"F", 0}}; + for (const auto& spelling : spellings) + { + Parameter param; + read_parameters("app_flag_bool_INPUT", "basis_type lcao\nout_app_flag " + spelling.first + "\n", param); + EXPECT_EQ(param.inp.out_app_flag, spelling.second) << "spelling: " << spelling.first; + } + // values outside 0/1/2 are rejected + expect_invalid_input("app_flag_oob_INPUT", + "basis_type lcao\nout_app_flag 3\n", + "out_app_flag should be 0, 1, or 2"); +} From 7e0ac771366d2325d412c3940f54ef22c0fd114c Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 14:02:44 +0800 Subject: [PATCH 15/61] feat(io): honor out_app_flag=1 for realspace cube outputs In append mode (out_app_flag=1), chg/tau/pot/potes/ELF cube outputs now reuse a fixed filename (e.g. chgs1.cube, elftot.cube) so every step overwrites the previous snapshot instead of accumulating g{N}-suffixed files. Modes 0 and 2 keep the per-step g{N} suffix. --- source/source_io/module_ctrl/ctrl_output_fp.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/source_io/module_ctrl/ctrl_output_fp.cpp b/source/source_io/module_ctrl/ctrl_output_fp.cpp index 8e673e4fb4b..35d17a13eb5 100644 --- a/source/source_io/module_ctrl/ctrl_output_fp.cpp +++ b/source/source_io/module_ctrl/ctrl_output_fp.cpp @@ -56,7 +56,12 @@ void ctrl_output_fp(UnitCell& ucell, bool should_output = (output_frequency == 0); if (istep_in >= 0) { - geom_block = "g" + std::to_string(istep + 1); + // In append mode (out_app_flag == 1) each cube output reuses a + // fixed filename, so every step overwrites the previous snapshot. + if (out_app_flag != 1) + { + geom_block = "g" + std::to_string(istep + 1); + } should_output = true; } From ca73e2e3e55ffd27863b9333e9fb4be3456c522b Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 14:06:38 +0800 Subject: [PATCH 16/61] feat(io): write DM(k) to fixed filename when out_app_flag=1 Add an explicit out_app_flag parameter to ModuleIO::write_dmk. In append mode the geometry-step index is dropped from the filename (istep_file = -1) so every ionic step overwrites the previous snapshot, matching the fixed name that read_dmk already expects; the file body still records the real ionic step. --- source/source_io/module_ctrl/ctrl_scf_lcao.cpp | 2 +- source/source_io/module_dm/test/write_dmk_test.cpp | 4 ++-- source/source_io/module_dm/write_dmk.cpp | 10 +++++++++- source/source_io/module_dm/write_dmk.h | 3 +++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp index 8e4621ee0ed..9ff021bf95f 100644 --- a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp @@ -197,7 +197,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, } const int precision = inp.out_dmk[1]; - ModuleIO::write_dmk(dm->get_dmk_vec(), kv, precision, efermis, &(ucell), pv, global_mat_dir, istep, ofs_running); + ModuleIO::write_dmk(dm->get_dmk_vec(), kv, precision, efermis, &(ucell), pv, global_mat_dir, istep, out_app_flag, ofs_running); } //------------------------------------------------------------------ diff --git a/source/source_io/module_dm/test/write_dmk_test.cpp b/source/source_io/module_dm/test/write_dmk_test.cpp index 4d6a2a8f1bc..6b552788306 100644 --- a/source/source_io/module_dm/test/write_dmk_test.cpp +++ b/source/source_io/module_dm/test/write_dmk_test.cpp @@ -165,8 +165,8 @@ TEST(DMKTest,WriteDMK) { kv.kd_done = true; std::ofstream ofs_log; - ModuleIO::write_dmk(dmk, kv, 3, efs, ucell, pv, out_dir, istep, ofs_log); - ModuleIO::write_dmk(dmk_multik, kv, 3, efs, ucell, pv, out_dir, istep, ofs_log); + ModuleIO::write_dmk(dmk, kv, 3, efs, ucell, pv, out_dir, istep, 0, ofs_log); + ModuleIO::write_dmk(dmk_multik, kv, 3, efs, ucell, pv, out_dir, istep, 0, ofs_log); std::ifstream ifs; diff --git a/source/source_io/module_dm/write_dmk.cpp b/source/source_io/module_dm/write_dmk.cpp index beec822652a..c57b8f196b9 100644 --- a/source/source_io/module_dm/write_dmk.cpp +++ b/source/source_io/module_dm/write_dmk.cpp @@ -188,11 +188,17 @@ void ModuleIO::write_dmk(const std::vector>& dmk, const Parallel_2D& pv, const std::string& dmk_dir, const int istep, + const int out_app_flag, std::ofstream& ofs_running) { ModuleBase::TITLE("ModuleIO", "write_dmk"); ModuleBase::timer::start("ModuleIO", "write_dmk"); + // In append mode (out_app_flag == 1) DM(k) reuses a fixed filename, + // so each ionic step overwrites the previous snapshot; the file body + // still records the real ionic step via istep. + const int istep_file = (out_app_flag == 1) ? -1 : istep; + int my_rank = 0; #ifdef __MPI MPI_Comm_rank(pv.comm(), &my_rank); @@ -239,7 +245,7 @@ void ModuleIO::write_dmk(const std::vector>& dmk, if (my_rank == 0) { std::string fn = dmk_dir - + dmk_gen_fname(gamma_only, ispin, nspin, ik, istep); + + dmk_gen_fname(gamma_only, ispin, nspin, ik, istep_file); std::ofstream ofs(fn.c_str()); @@ -347,6 +353,7 @@ template void ModuleIO::write_dmk(const std::vector> const Parallel_2D& pv, const std::string& dmk_dir, const int istep, + const int out_app_flag, std::ofstream& ofs_running); template void ModuleIO::write_dmk>(const std::vector>>& dmk, @@ -357,5 +364,6 @@ template void ModuleIO::write_dmk>(const std::vector void write_dmk(const std::vector>& dmk, @@ -78,6 +80,7 @@ void write_dmk(const std::vector>& dmk, const Parallel_2D& pv, const std::string& dmk_dir, const int istep, + const int out_app_flag, std::ofstream& ofs_running); } // namespace ModuleIO From 0a8c069f076b9bf5f6173408bad3d0aa01f66a97 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 14:08:39 +0800 Subject: [PATCH 17/61] feat(io): keep fixed T(R)/R(R) filenames when out_app_flag=1 Drop the g{N} suffix from tr_nao.csr and rr_nao.txt in append mode. The filenames were changing every ionic step, which defeated the existing MD append branches in save_sparse and the rr assemble path; with fixed names those branches now accumulate all steps as designed. --- source/source_io/module_hs/hsr_legacy.cpp | 4 +++- source/source_io/module_hs/pos_op_writer.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/source/source_io/module_hs/hsr_legacy.cpp b/source/source_io/module_hs/hsr_legacy.cpp index 8b3fc5a4dfc..d5b814214e9 100644 --- a/source/source_io/module_hs/hsr_legacy.cpp +++ b/source/source_io/module_hs/hsr_legacy.cpp @@ -174,7 +174,9 @@ void ModuleIO::output_TR(const int istep, ModuleBase::timer::start("ModuleIO", "output_TR"); std::stringstream sst; - if (istep >= 0) + // In append mode (out_app_flag == 1) the file keeps the fixed name so + // the MD append logic in save_sparse can accumulate all ionic steps. + if (istep >= 0 && options.out_app_flag != 1) { // insert the 1-indexed ionic-step suffix right after the "tr" prefix, // i.e. tr_nao.csr -> trg{step+1}_nao.csr (aligned with lxg/rrg naming) diff --git a/source/source_io/module_hs/pos_op_writer.cpp b/source/source_io/module_hs/pos_op_writer.cpp index 8f75d52ed72..79ce0355926 100644 --- a/source/source_io/module_hs/pos_op_writer.cpp +++ b/source/source_io/module_hs/pos_op_writer.cpp @@ -216,7 +216,9 @@ void PosOpWriter::out_lat_r(const UnitCell& ucell, if (GlobalV::DRANK == 0) { std::stringstream ssr; - if (step >= 0) + // In append mode (out_app_flag == 1) keep the fixed filename so + // the append branch below can accumulate all ionic steps. + if (step >= 0 && out_app_flag != 1) { ssr << global_matrix_dir << "rrg" << (step + 1) << "_nao.txt"; } From 93425349cc0d6aab2b173b5e8ddd5646a2ed1bc0 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 14:11:12 +0800 Subject: [PATCH 18/61] fix(io): use real out_app_flag for PW wavefunction output Replace the hardcoded out_app_flag=0 in write_wfc_pw (a 2025-05-17 TODO) with an explicit parameter wired from inp.out_app_flag. In the default append mode the PW wavefunction files now use fixed names (wfk1_pw.dat), which finally matches what the restart reader read_wf2rho_pw looks for; modes 0 and 2 keep the g{N} suffix. --- source/source_io/module_ctrl/ctrl_output_pw.cpp | 1 + source/source_io/module_wf/write_wfc_pw.cpp | 2 +- source/source_io/module_wf/write_wfc_pw.h | 1 + source/source_io/test/read_wf2rho_pw_test.cpp | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/source/source_io/module_ctrl/ctrl_output_pw.cpp b/source/source_io/module_ctrl/ctrl_output_pw.cpp index 6856a49458e..5bffea81abe 100644 --- a/source/source_io/module_ctrl/ctrl_output_pw.cpp +++ b/source/source_io/module_ctrl/ctrl_output_pw.cpp @@ -73,6 +73,7 @@ void ModuleIO::ctrl_iter_pw(const int istep, GlobalV::RANK_IN_POOL, GlobalV::NPROC_IN_POOL, inp.out_wfc_pw, + inp.out_app_flag, inp.ecutwfc, PARAM.globalv.global_psi_dir, stp.psi_cpu[0], diff --git a/source/source_io/module_wf/write_wfc_pw.cpp b/source/source_io/module_wf/write_wfc_pw.cpp index d90f7f5d645..4d322c83f54 100644 --- a/source/source_io/module_wf/write_wfc_pw.cpp +++ b/source/source_io/module_wf/write_wfc_pw.cpp @@ -23,6 +23,7 @@ void ModuleIO::write_wfc_pw( const int rank_in_pool, const int nproc_in_pool, const int out_wfc_pw, + const int out_app_flag, const double& ecutwfc, const std::string& global_out_dir, const psi::Psi>& psi, @@ -43,7 +44,6 @@ void ModuleIO::write_wfc_pw( assert(nkstot>0); assert(nks>0); - int out_app_flag = 0; // need to modify later, mohan 2025-05-17 bool gamma_only = false; // need to modify later, mohan 2025-05-17 std::string* wfilename = new std::string[nks]; diff --git a/source/source_io/module_wf/write_wfc_pw.h b/source/source_io/module_wf/write_wfc_pw.h index 7e035bf82cd..6877179eed7 100644 --- a/source/source_io/module_wf/write_wfc_pw.h +++ b/source/source_io/module_wf/write_wfc_pw.h @@ -19,6 +19,7 @@ void write_wfc_pw( const int rank_in_pool, const int nproc_in_pool, const int out_wfc_pw, + const int out_app_flag, const double& ecutwfc, const std::string& global_out_dir, const psi::Psi>& psi, diff --git a/source/source_io/test/read_wf2rho_pw_test.cpp b/source/source_io/test/read_wf2rho_pw_test.cpp index 991a46a8f26..eb43411bc61 100644 --- a/source/source_io/test/read_wf2rho_pw_test.cpp +++ b/source/source_io/test/read_wf2rho_pw_test.cpp @@ -298,6 +298,7 @@ TEST_F(ReadWfcRhoTest, ReadWfcRho) GlobalV::RANK_IN_POOL, GlobalV::NPROC_IN_POOL, TestParameters::input().out_wfc_pw, + 0, ecutwfc, out_dir, *psi, From d0274b95387109f9d94307fcd1319a28568f0e8d Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 14:12:52 +0800 Subject: [PATCH 19/61] feat(io): omit geometry index in init cube names when out_app_flag=1 write_chg_init/write_pot_init now keep the fixed _ini.cube names in append mode so each snapshot overwrites the previous one, instead of accumulating chgs1g{N}_ini.cube / potg{N}_ini.cube files. These initial-density cubes are write-only snapshots; no reader consumes them, so no restart path is affected. --- source/source_estate/write_init.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/source/source_estate/write_init.cpp b/source/source_estate/write_init.cpp index 6b083c1986a..d496e5eecaa 100644 --- a/source/source_estate/write_init.cpp +++ b/source/source_estate/write_init.cpp @@ -6,6 +6,7 @@ // - Frequency is out_freq_td for RT-TDDFT and out_freq_ion otherwise. // - Frequency = 0: chg_ini.cube/pot_ini.cube, chgs1_ini.cube/chgs2_ini.cube // - Frequency > 0: chgg{#}_ini.cube/potg{#}_ini.cube, chgs{#}g{#}_ini.cube +// (out_app_flag == 1 keeps the fixed names without the g{#} index) // - File indices start from 1 (istep + 1); RT-TDDFT uses electronic steps. // // Usage: @@ -82,7 +83,9 @@ void ModuleIO::write_chg_init(const UnitCell& ucell, if (should_output) { - const bool include_geom_step = (output_frequency > 0); + // In append mode (out_app_flag == 1) each snapshot overwrites + // the same fixed file, so the geometry-step index is omitted. + const bool include_geom_step = (output_frequency > 0) && (inp.out_app_flag != 1); for (int is = 0; is < nspin; is++) { @@ -151,7 +154,9 @@ void ModuleIO::write_pot_init(const UnitCell& ucell, if (should_output) { - const bool include_geom_step = (output_frequency > 0); + // In append mode (out_app_flag == 1) each snapshot overwrites + // the same fixed file, so the geometry-step index is omitted. + const bool include_geom_step = (output_frequency > 0) && (inp.out_app_flag != 1); for (int is = 0; is < nspin; is++) { From b8236ecb0f598fb5d1a171797deb035199fe0a3a Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 14:16:10 +0800 Subject: [PATCH 20/61] docs(input): describe fixed-name behavior of out_app_flag=1 Clarify that mode 1 reuses fixed filenames across steps (text records append, cube snapshots overwrite), fix the remaining Boolean wording in out_hsk, and note that out_chg=2/out_pot=3 initial-density cubes omit the geometry-step index in append mode. --- source/source_io/module_parameter/read_inp_out.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index 904a87123d4..37fb09f98ac 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -91,7 +91,7 @@ void ReadInput::item_output() - nspin = 2: `chgs1g{geom_step}_ini.cube` and `chgs2g{geom_step}_ini.cube`; - nspin = 4: `chgs1g{geom_step}_ini.cube`, `chgs2g{geom_step}_ini.cube`, `chgs3g{geom_step}_ini.cube`, and `chgs4g{geom_step}_ini.cube`. - output every out_freq_ion steps - Here, {geom_step} denotes the geometry step index, starting from 1 (geom_step = istep + 1). + Here, {geom_step} denotes the geometry step index, starting from 1 (geom_step = istep + 1). With out_app_flag = 1, the geometry-step index is omitted and each snapshot overwrites the same fixed file. - -1: Disable the charge density auto-back-up file `{suffix}-CHARGE-DENSITY.restart`, useful for large systems. The second integer controls the precision of the charge density output. If not given, `3` is used as default. For restarting from this file and other high-precision calculations, `10` is recommended. @@ -152,7 +152,7 @@ In molecular dynamics simulations, the output frequency is controlled by out_fre * nspin = 2: `pots1g{geom_step}_ini.cube` and `pots2g{geom_step}_ini.cube`; * nspin = 4: `pots1g{geom_step}_ini.cube`, `pots2g{geom_step}_ini.cube`, `pots3g{geom_step}_ini.cube`, and `pots4g{geom_step}_ini.cube`. * output every out_freq_ion steps - Here, {geom_step} denotes the geometry step index, starting from 1 (geom_step = istep + 1). + Here, {geom_step} denotes the geometry step index, starting from 1 (geom_step = istep + 1). With out_app_flag = 1, the geometry-step index is omitted and each snapshot overwrites the same fixed file. The optional second integer controls the output precision. If not provided, the default precision is 8. @@ -562,8 +562,8 @@ The output is also controlled by out_freq_ion and out_app_flag. For more informa * Multi-k, nspin = 2: hk1s1_nao.txt and hk1s2_nao.txt for the two spin channels of the Hamiltonian matrix, and sk1_nao.txt for the overlap matrix at the first k-point. Only one overlap matrix is written because it is identical for both spin channels. * Multi-k, nspin = 4: hk1s4_nao.txt for the spinor Hamiltonian matrix and sk1_nao.txt for the spinor overlap matrix at the first k-point. For binary output, the same names use the .dat suffix. Each native binary record contains the matrix dimension as an int followed by the row-major upper triangle. Gamma-only elements are doubles; multi-k and spinor elements are pairs of doubles containing the real and imaginary parts. Native integer representation and byte order are used. -When out_app_flag is true, the first ionic step truncates the file and later steps append complete records. -When out_app_flag is false, g followed by the one-based ionic-step index is inserted before _nao, for example hk1s1g1_nao.txt. +When out_app_flag is 1, the first ionic step truncates the file and later steps append complete records. +When out_app_flag is 0 or 2, g followed by the one-based ionic-step index is inserted before _nao, for example hk1s1g1_nao.txt. [NOTE] In the 3.10-LTS version, the file names are data-0-H and data-0-S, etc.)"; item.default_value = "0 8"; @@ -1397,7 +1397,7 @@ The circle order of the charge density on real space grids is: x is the outer lo item.type = "Integer"; item.description = R"(Controls output file organization during multi-step calculations (MD, relax, RT-TDDFT). The directory routing applies to both LCAO and PW basis calculations. * 0: Write to OUT directory directly, no append (each step's file carries a g{step} suffix) -* 1: Append to same file (default, backward compatible) +* 1: Reuse fixed filenames across steps (default, backward compatible): text outputs append new records, cube snapshots are overwritten * 2: Write to subdirectory (PSI/, CHG/, POT/, ELF/, DOS/, DIP/, MAT/), files carry a g{step} suffix The legacy Boolean spellings (true/false, yes/no, on/off, t/f, y/n, and the dotted forms) are still accepted and map to modes 1/0 as before. For more information, refer to the documentation of out_mat_r, out_hsr, out_mat_t, out_mat_dh, out_hsk, out_wfc_lcao, out_chg, out_pot, out_elf, out_dos, out_dipole.)"; From 8c9091d6528e126c7fb20c87c1736960ee895e9b Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 14:29:52 +0800 Subject: [PATCH 21/61] fix(io): append DM(k) records in MD when out_app_flag=1 The fixed filename alone still truncated the file every ionic step. Add a calculation parameter and open the stream with ios::app under the same condition as open_sparse_file (calculation=="md" && out_app_flag==1 && istep>=0), so each MD step appends a new record while other calculations start fresh. --- source/source_io/module_ctrl/ctrl_scf_lcao.cpp | 2 +- source/source_io/module_dm/test/write_dmk_test.cpp | 4 ++-- source/source_io/module_dm/write_dmk.cpp | 14 +++++++++++++- source/source_io/module_dm/write_dmk.h | 3 +++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp index 9ff021bf95f..e0dfe6d1667 100644 --- a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp @@ -197,7 +197,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, } const int precision = inp.out_dmk[1]; - ModuleIO::write_dmk(dm->get_dmk_vec(), kv, precision, efermis, &(ucell), pv, global_mat_dir, istep, out_app_flag, ofs_running); + ModuleIO::write_dmk(dm->get_dmk_vec(), kv, precision, efermis, &(ucell), pv, global_mat_dir, istep, out_app_flag, inp.calculation, ofs_running); } //------------------------------------------------------------------ diff --git a/source/source_io/module_dm/test/write_dmk_test.cpp b/source/source_io/module_dm/test/write_dmk_test.cpp index 6b552788306..8990f4f68ed 100644 --- a/source/source_io/module_dm/test/write_dmk_test.cpp +++ b/source/source_io/module_dm/test/write_dmk_test.cpp @@ -165,8 +165,8 @@ TEST(DMKTest,WriteDMK) { kv.kd_done = true; std::ofstream ofs_log; - ModuleIO::write_dmk(dmk, kv, 3, efs, ucell, pv, out_dir, istep, 0, ofs_log); - ModuleIO::write_dmk(dmk_multik, kv, 3, efs, ucell, pv, out_dir, istep, 0, ofs_log); + ModuleIO::write_dmk(dmk, kv, 3, efs, ucell, pv, out_dir, istep, 0, "scf", ofs_log); + ModuleIO::write_dmk(dmk_multik, kv, 3, efs, ucell, pv, out_dir, istep, 0, "scf", ofs_log); std::ifstream ifs; diff --git a/source/source_io/module_dm/write_dmk.cpp b/source/source_io/module_dm/write_dmk.cpp index c57b8f196b9..e3774e213e4 100644 --- a/source/source_io/module_dm/write_dmk.cpp +++ b/source/source_io/module_dm/write_dmk.cpp @@ -189,6 +189,7 @@ void ModuleIO::write_dmk(const std::vector>& dmk, const std::string& dmk_dir, const int istep, const int out_app_flag, + const std::string& calculation, std::ofstream& ofs_running) { ModuleBase::TITLE("ModuleIO", "write_dmk"); @@ -198,6 +199,9 @@ void ModuleIO::write_dmk(const std::vector>& dmk, // so each ionic step overwrites the previous snapshot; the file body // still records the real ionic step via istep. const int istep_file = (out_app_flag == 1) ? -1 : istep; + // Each MD step appends a new record to the fixed file, mirroring the + // open_sparse_file convention; other calculations start fresh. + const bool append_on_restart = (calculation == "md") && (out_app_flag == 1) && (istep >= 0); int my_rank = 0; #ifdef __MPI @@ -247,7 +251,13 @@ void ModuleIO::write_dmk(const std::vector>& dmk, std::string fn = dmk_dir + dmk_gen_fname(gamma_only, ispin, nspin, ik, istep_file); - std::ofstream ofs(fn.c_str()); + std::ofstream ofs; + std::ios_base::openmode mode = std::ios::out; + if (append_on_restart) + { + mode |= std::ios::app; + } + ofs.open(fn.c_str(), mode); ofs << " --- Ionic Step " << istep+1 << " ---" << std::endl; @@ -354,6 +364,7 @@ template void ModuleIO::write_dmk(const std::vector> const std::string& dmk_dir, const int istep, const int out_app_flag, + const std::string& calculation, std::ofstream& ofs_running); template void ModuleIO::write_dmk>(const std::vector>>& dmk, @@ -365,5 +376,6 @@ template void ModuleIO::write_dmk>(const std::vector void write_dmk(const std::vector>& dmk, @@ -81,6 +83,7 @@ void write_dmk(const std::vector>& dmk, const std::string& dmk_dir, const int istep, const int out_app_flag, + const std::string& calculation, std::ofstream& ofs_running); } // namespace ModuleIO From a9cba384829f373b0ec25371f098e16a562c7961 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 14:39:03 +0800 Subject: [PATCH 22/61] docs(governance): require one-based ionic-step indices in outputs Rule 22: user-facing output files print ionic steps starting from 1, matching the g{N} filename suffix convention; zero-based counters such as "STEP: 0" are not allowed. --- AGENTS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 0705adefe89..f235f134264 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -79,6 +79,9 @@ rules. Read the complete governance document before making or reviewing changes: guards and `#ifdef`/`#ifndef` usages of existing macros are exempt. 21. Do not initialize member variables from `PARAM` in `.h`/`.hpp` files; pass the value through the constructor or a setter instead. + 22. Print ionic-step indices one-based (starting from 1) in user-facing + output files, matching the `g{N}` filename suffix convention; do not + write zero-based step counters such as `STEP: 0` into output files. - Use LF line endings for text files. Only `.bat` and `.cmd` files may use CRLF. - Keep source file additions deterministic: update the relevant `CMakeLists.txt` or explain why the file is generated or included indirectly. From a658239dcd1c5f557a69036d787b1cebde958f31 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 15:10:06 +0800 Subject: [PATCH 23/61] feat(io): full out_app_flag=1 semantics for DOS/PDOS/L/matrices Naming (mode 1 fixed name, modes 0/2 keep g{N}): - DOS (LCAO+PW) and PDOS honor out_app_flag for the g{N} suffix - lx/ly/lz matrices: Angmom_op::calculate takes out_app_flag Append (relax now appends like MD; was calculation=="md" only): - open_sparse_file, rr/dH/dS writers, DM(k): condition relaxed to (out_app_flag == 1) && (istep >= 0); SCF passes istep=-1, unaffected - PDOS and lx/ly/lz append per ionic step in mode 1 - DM(k) drops the calculation parameter again (no longer used) Step indexing (governance rule 22, one-based): - STEP: headers of sparse text files and the binary CSR step field now start from 1 (hs_sparse_io, pos_op_writer, dhs_sparse_writer, hsr_writer) Build: make -j 30 verified by developer. --- .../source_io/module_ctrl/ctrl_scf_lcao.cpp | 4 ++-- .../module_dm/test/write_dmk_test.cpp | 4 ++-- source/source_io/module_dm/write_dmk.cpp | 10 ++++------ source/source_io/module_dm/write_dmk.h | 6 ++---- .../source_io/module_dos/cal_pdos_gamma.cpp | 3 ++- source/source_io/module_dos/cal_pdos_gamma.h | 1 + .../source_io/module_dos/cal_pdos_multik.cpp | 3 ++- source/source_io/module_dos/cal_pdos_multik.h | 1 + .../unittests/test_write_pdos_text.cpp | 6 +++--- .../source_io/module_dos/write_dos_lcao.cpp | 5 ++++- source/source_io/module_dos/write_dos_pw.cpp | 4 +++- .../source_io/module_dos/write_pdos_text.cpp | 19 ++++++++++++++---- source/source_io/module_dos/write_pdos_text.h | 5 +++-- source/source_io/module_hs/angmom_op_mat.cpp | 18 ++++++++++++----- source/source_io/module_hs/angmom_op_mat.h | 20 ++++++++++--------- .../source_io/module_hs/dhs_sparse_writer.cpp | 9 ++++++--- source/source_io/module_hs/hs_sparse_io.cpp | 8 ++++++-- source/source_io/module_hs/hsr_writer.cpp | 3 ++- source/source_io/module_hs/pos_op_writer.cpp | 7 +++++-- 19 files changed, 87 insertions(+), 49 deletions(-) diff --git a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp index e0dfe6d1667..0ee884ec369 100644 --- a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp @@ -197,7 +197,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, } const int precision = inp.out_dmk[1]; - ModuleIO::write_dmk(dm->get_dmk_vec(), kv, precision, efermis, &(ucell), pv, global_mat_dir, istep, out_app_flag, inp.calculation, ofs_running); + ModuleIO::write_dmk(dm->get_dmk_vec(), kv, precision, efermis, &(ucell), pv, global_mat_dir, istep, out_app_flag, ofs_running); } //------------------------------------------------------------------ @@ -562,7 +562,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, PARAM.globalv.gamma_only_local, &ofs_running, GlobalV::MY_RANK); - mylcalculator.calculate(inp.suffix, global_mat_dir, ucell, inp.out_mat_l[1], GlobalV::MY_RANK, istep_in); + mylcalculator.calculate(inp.suffix, global_mat_dir, ucell, inp.out_mat_l[1], GlobalV::MY_RANK, istep_in, out_app_flag); } //------------------------------------------------------------------ diff --git a/source/source_io/module_dm/test/write_dmk_test.cpp b/source/source_io/module_dm/test/write_dmk_test.cpp index 8990f4f68ed..6b552788306 100644 --- a/source/source_io/module_dm/test/write_dmk_test.cpp +++ b/source/source_io/module_dm/test/write_dmk_test.cpp @@ -165,8 +165,8 @@ TEST(DMKTest,WriteDMK) { kv.kd_done = true; std::ofstream ofs_log; - ModuleIO::write_dmk(dmk, kv, 3, efs, ucell, pv, out_dir, istep, 0, "scf", ofs_log); - ModuleIO::write_dmk(dmk_multik, kv, 3, efs, ucell, pv, out_dir, istep, 0, "scf", ofs_log); + ModuleIO::write_dmk(dmk, kv, 3, efs, ucell, pv, out_dir, istep, 0, ofs_log); + ModuleIO::write_dmk(dmk_multik, kv, 3, efs, ucell, pv, out_dir, istep, 0, ofs_log); std::ifstream ifs; diff --git a/source/source_io/module_dm/write_dmk.cpp b/source/source_io/module_dm/write_dmk.cpp index e3774e213e4..b75114d3b48 100644 --- a/source/source_io/module_dm/write_dmk.cpp +++ b/source/source_io/module_dm/write_dmk.cpp @@ -189,7 +189,6 @@ void ModuleIO::write_dmk(const std::vector>& dmk, const std::string& dmk_dir, const int istep, const int out_app_flag, - const std::string& calculation, std::ofstream& ofs_running) { ModuleBase::TITLE("ModuleIO", "write_dmk"); @@ -199,9 +198,10 @@ void ModuleIO::write_dmk(const std::vector>& dmk, // so each ionic step overwrites the previous snapshot; the file body // still records the real ionic step via istep. const int istep_file = (out_app_flag == 1) ? -1 : istep; - // Each MD step appends a new record to the fixed file, mirroring the - // open_sparse_file convention; other calculations start fresh. - const bool append_on_restart = (calculation == "md") && (out_app_flag == 1) && (istep >= 0); + // Each multi-step run (MD or relax) appends a new record to the fixed + // file, mirroring the open_sparse_file convention; single-point SCF + // passes istep = -1 and starts a fresh file. + const bool append_on_restart = (out_app_flag == 1) && (istep >= 0); int my_rank = 0; #ifdef __MPI @@ -364,7 +364,6 @@ template void ModuleIO::write_dmk(const std::vector> const std::string& dmk_dir, const int istep, const int out_app_flag, - const std::string& calculation, std::ofstream& ofs_running); template void ModuleIO::write_dmk>(const std::vector>>& dmk, @@ -376,6 +375,5 @@ template void ModuleIO::write_dmk>(const std::vector void write_dmk(const std::vector>& dmk, @@ -83,7 +82,6 @@ void write_dmk(const std::vector>& dmk, const std::string& dmk_dir, const int istep, const int out_app_flag, - const std::string& calculation, std::ofstream& ofs_running); } // namespace ModuleIO diff --git a/source/source_io/module_dos/cal_pdos_gamma.cpp b/source/source_io/module_dos/cal_pdos_gamma.cpp index 3b9ee1b1dd6..d042fb6ede5 100644 --- a/source/source_io/module_dos/cal_pdos_gamma.cpp +++ b/source/source_io/module_dos/cal_pdos_gamma.cpp @@ -23,6 +23,7 @@ void ModuleIO::cal_pdos( const double& dos_edelta_ev, const double& bcoeff, const int istep, + const int out_app_flag, const int nlocal, const int nspin, const std::string& global_out_dir) @@ -148,7 +149,7 @@ void ModuleIO::cal_pdos( if (GlobalV::MY_RANK == 0) { write_pdos_text(ucell, pdos.data(), nspin, nlocal, npoints, - emin, dos_edelta_ev, global_out_dir, istep); + emin, dos_edelta_ev, global_out_dir, istep, out_app_flag); ModuleIO::write_orb_info(&ucell); } } diff --git a/source/source_io/module_dos/cal_pdos_gamma.h b/source/source_io/module_dos/cal_pdos_gamma.h index e5122464262..0af1fca0540 100644 --- a/source/source_io/module_dos/cal_pdos_gamma.h +++ b/source/source_io/module_dos/cal_pdos_gamma.h @@ -25,6 +25,7 @@ namespace ModuleIO const double& dos_edelta_ev, const double& bcoeff, const int istep, + const int out_app_flag, const int nlocal, const int nspin, const std::string& global_out_dir); diff --git a/source/source_io/module_dos/cal_pdos_multik.cpp b/source/source_io/module_dos/cal_pdos_multik.cpp index 95ee8c7ba1c..219b5bf69cb 100644 --- a/source/source_io/module_dos/cal_pdos_multik.cpp +++ b/source/source_io/module_dos/cal_pdos_multik.cpp @@ -22,6 +22,7 @@ void ModuleIO::cal_pdos( const double& dos_edelta_ev, const double& bcoeff, const int istep, + const int out_app_flag, const int nlocal, const int nspin, const std::string& global_out_dir) @@ -188,7 +189,7 @@ void ModuleIO::cal_pdos( if (GlobalV::MY_RANK == 0) { write_pdos_text(ucell, pdos.data(), nspin, nlocal, npoints, - emin, dos_edelta_ev, global_out_dir, istep); + emin, dos_edelta_ev, global_out_dir, istep, out_app_flag); ModuleIO::write_orb_info(&ucell); } } diff --git a/source/source_io/module_dos/cal_pdos_multik.h b/source/source_io/module_dos/cal_pdos_multik.h index 110009becb4..a4b194d967b 100644 --- a/source/source_io/module_dos/cal_pdos_multik.h +++ b/source/source_io/module_dos/cal_pdos_multik.h @@ -26,6 +26,7 @@ namespace ModuleIO const double& dos_edelta_ev, const double& bcoeff, const int istep, + const int out_app_flag, const int nlocal, const int nspin, const std::string& global_out_dir); diff --git a/source/source_io/module_dos/unittests/test_write_pdos_text.cpp b/source/source_io/module_dos/unittests/test_write_pdos_text.cpp index 00681a3cb5f..52571d2888f 100644 --- a/source/source_io/module_dos/unittests/test_write_pdos_text.cpp +++ b/source/source_io/module_dos/unittests/test_write_pdos_text.cpp @@ -93,7 +93,7 @@ TEST_F(WritePdosTextTest, BasicOutput) } ModuleIO::write_pdos_text(*ucell, &pdos, nspin, nlocal, npoints, - emin, dos_edelta_ev, out_dir, istep); + emin, dos_edelta_ev, out_dir, istep, 0); const std::string str = read_file("./pdoss1.txt"); @@ -127,7 +127,7 @@ TEST_F(WritePdosTextTest, IstepFileName) pdos.zero_out(); ModuleIO::write_pdos_text(*ucell, &pdos, nspin, nlocal, npoints, - emin, dos_edelta_ev, "./", 2); + emin, dos_edelta_ev, "./", 2, 0); const std::string str = read_file("./pdoss1g3.txt"); EXPECT_THAT(str, testing::HasSubstr("# istep: 3")); @@ -163,7 +163,7 @@ TEST_F(WritePdosTextTest, SpinPolarized) } ModuleIO::write_pdos_text(*ucell, pdos_arr, nspin, nlocal, npoints, - emin, dos_edelta_ev, "./", -1); + emin, dos_edelta_ev, "./", -1, 0); const std::string s1 = read_file("./pdoss1.txt"); const std::string s2 = read_file("./pdoss2.txt"); diff --git a/source/source_io/module_dos/write_dos_lcao.cpp b/source/source_io/module_dos/write_dos_lcao.cpp index 20ef0799aae..ed47e0c5eaf 100644 --- a/source/source_io/module_dos/write_dos_lcao.cpp +++ b/source/source_io/module_dos/write_dos_lcao.cpp @@ -62,7 +62,9 @@ void write_dos_lcao( ss << global_out_dir << "doss" << is + 1; - if(istep>=0) + // In append mode (out_app_flag == 1) the fixed name is reused and + // each ionic step appends a new DOS block; modes 0 and 2 keep g{N}. + if(istep>=0 && out_app_flag!=1) { ss << "g" << istep+1; } @@ -102,6 +104,7 @@ void write_dos_lcao( dos_edelta_ev, bcoeff, istep, + out_app_flag, nlocal, nspin, global_out_dir); diff --git a/source/source_io/module_dos/write_dos_pw.cpp b/source/source_io/module_dos/write_dos_pw.cpp index 3564a78f275..4026b181b60 100644 --- a/source/source_io/module_dos/write_dos_pw.cpp +++ b/source/source_io/module_dos/write_dos_pw.cpp @@ -54,7 +54,9 @@ void ModuleIO::write_dos_pw( std::stringstream ss; ss << global_out_dir << "doss" << is + 1; - if(istep_in>=0) + // In append mode (out_app_flag == 1) the fixed name is reused and + // each ionic step appends a new DOS block; modes 0 and 2 keep g{N}. + if(istep_in>=0 && out_app_flag!=1) { ss << "g" << istep_in+1; } diff --git a/source/source_io/module_dos/write_pdos_text.cpp b/source/source_io/module_dos/write_pdos_text.cpp index f0b3331dcf1..84cfab41c85 100644 --- a/source/source_io/module_dos/write_pdos_text.cpp +++ b/source/source_io/module_dos/write_pdos_text.cpp @@ -17,24 +17,35 @@ void ModuleIO::write_pdos_text( const double& emin, const double& dos_edelta_ev, const std::string& out_dir, - const int istep) + const int istep, + const int out_app_flag) { ModuleBase::TITLE("ModuleIO", "write_pdos_text"); // number of spin channels to write const int nspin0 = (nspin == 2) ? 2 : 1; + // In append mode (out_app_flag == 1) reuse the fixed filename and append + // each ionic step; single-point SCF passes istep = -1 and truncates. + const int istep_file = (out_app_flag == 1) ? -1 : istep; + const bool append_step = (out_app_flag == 1) && (istep >= 0); + for (int is = 0; is < nspin0; ++is) { std::stringstream ss; ss << out_dir << "pdoss" << is + 1; - if (istep >= 0) + if (istep_file >= 0) { - ss << "g" << istep + 1; + ss << "g" << istep_file + 1; } ss << ".txt"; - std::ofstream ofs(ss.str().c_str()); + std::ios_base::openmode mode = std::ios::out; + if (append_step) + { + mode |= std::ios::app; + } + std::ofstream ofs(ss.str().c_str(), mode); // iw2m stores m as 0..2l, mapping to physical m values: // 0->0, 1->+1, 2->-1, 3->+2, 4->-2, 5->+3, 6->-3 diff --git a/source/source_io/module_dos/write_pdos_text.h b/source/source_io/module_dos/write_pdos_text.h index dcb9a184623..434c6f7ebe9 100644 --- a/source/source_io/module_dos/write_pdos_text.h +++ b/source/source_io/module_dos/write_pdos_text.h @@ -8,7 +8,7 @@ namespace ModuleIO { /// Write PDOS in plain text long-table format. -/// File name: pdoss{spin}g{geom}.txt +/// File name: pdoss{spin}g{geom}.txt (g{geom} omitted when out_app_flag == 1) /// Columns: energy(eV) atom species l z m pdos(1/eV) /// For nspin=4, the two spinor components are summed. void write_pdos_text( @@ -20,7 +20,8 @@ void write_pdos_text( const double& emin, const double& dos_edelta_ev, const std::string& out_dir, - const int istep); + const int istep, + const int out_app_flag); } diff --git a/source/source_io/module_hs/angmom_op_mat.cpp b/source/source_io/module_hs/angmom_op_mat.cpp index 16f8dc7e44d..a0cfa54e57a 100644 --- a/source/source_io/module_hs/angmom_op_mat.cpp +++ b/source/source_io/module_hs/angmom_op_mat.cpp @@ -380,7 +380,8 @@ void ModuleIO::Angmom_op::calculate( const UnitCell& ucell, const int precision, const int rank, - const int istep) + const int istep, + const int out_app_flag) { ModuleBase::TITLE("Angmom_op", "calculate"); ModuleBase::timer::start("Angmom_op", "calculate"); @@ -392,9 +393,11 @@ void ModuleIO::Angmom_op::calculate( } std::ofstream ofout; const std::string dir = "xyz"; - /// @brief Filename suffix: if istep >= 0, append "g{istep+1}" (e.g., "g1") - /// to follow the out_freq_ion convention; otherwise no suffix. - const std::string step_suffix = (istep >= 0) ? "g" + std::to_string(istep + 1) : ""; + /// @brief Filename suffix: in append mode (out_app_flag == 1) the fixed + /// name is reused and each ionic step appends; modes 0 and 2 carry + /// "g{istep+1}" (e.g., "g1") following the out_freq_ion convention. + const std::string step_suffix = (istep >= 0 && out_app_flag != 1) ? "g" + std::to_string(istep + 1) : ""; + const bool append_step = (out_app_flag == 1) && (istep >= 0); const std::string title = "# it ia il iz im iRx iRy iRz jt ja jl jz jm Re[] Im[]\n" "# it: atomtype index of the first atom\n" "# ia: atomic index of the first atom within the atomtype\n" @@ -413,7 +416,12 @@ void ModuleIO::Angmom_op::calculate( for (char d : dir) { std::string fn = outdir + "l" + d + step_suffix + "_nao.txt"; - ofout.open(fn, std::ios::out); + std::ios_base::openmode mode = std::ios::out; + if (append_step) + { + mode |= std::ios::app; + } + ofout.open(fn, mode); ofout << title; this->kernel(&ofout, ucell, d, precision); ofout.close(); diff --git a/source/source_io/module_hs/angmom_op_mat.h b/source/source_io/module_hs/angmom_op_mat.h index c5000d68f32..c79a059ff46 100644 --- a/source/source_io/module_hs/angmom_op_mat.h +++ b/source/source_io/module_hs/angmom_op_mat.h @@ -226,19 +226,21 @@ namespace ModuleIO * filenames are fixed as l{x,y,z}[g{step}]_nao.txt) * @param outdir the output directory * @param ucell the unit cell object - * @param precision the precision of the output, default is 10 - * @param rank the MPI rank, default is 0 - * @param istep the ionic step index. If >= 0, the filename includes - * `g{istep+1}` (e.g., lxg1_nao.txt); if -1, the filename - * has no step suffix (e.g., lx_nao.txt). Default is -1 - * (single-step overwrite mode). + * @param precision the precision of the output + * @param rank the MPI rank + * @param istep the ionic step index; -1 for single-point runs + * @param out_app_flag append mode: with 1 the fixed filename + * l{x,y,z}_nao.txt is reused and each step appends; + * with 0 or 2 the filename carries `g{istep+1}` + * (e.g., lxg1_nao.txt) */ void calculate(const std::string& prefix, const std::string& outdir, const UnitCell& ucell, - const int precision = 10, - const int rank = 0, - const int istep = -1); + const int precision, + const int rank, + const int istep, + const int out_app_flag); private: std::ofstream fallback_ofs_; diff --git a/source/source_io/module_hs/dhs_sparse_writer.cpp b/source/source_io/module_hs/dhs_sparse_writer.cpp index 557adfa59e7..636938c5f14 100644 --- a/source/source_io/module_hs/dhs_sparse_writer.cpp +++ b/source/source_io/module_hs/dhs_sparse_writer.cpp @@ -66,8 +66,9 @@ void ModuleIO::save_dH_sparse(const int& istep, const int total_R_num = static_cast(all_R_coor_ptr.size()); int output_R_number = 0; - // Use istep for filename/append decisions; clamp only the header STEP value. - const int header_step = std::max(istep, 0); + // Use istep for filename/append decisions; clamp only the header STEP + // value, printed one-based per governance rule 22. + const int header_step = std::max(istep, 0) + 1; int spin_loop = 1; if (nspin == 2) { @@ -132,7 +133,9 @@ void ModuleIO::save_dH_sparse(const int& istep, if (GlobalV::DRANK == 0) { - const bool open_in_append = (calculation == "md") && (out_app_flag == 1) && istep >= 0; + // Append for any multi-step run (MD or relax) in append mode; + // single-point SCF passes istep = -1 and starts a fresh file. + const bool open_in_append = (out_app_flag == 1) && istep >= 0; for (auto& comp: comps) { const std::string label = std::string("dH") + comp.axis; diff --git a/source/source_io/module_hs/hs_sparse_io.cpp b/source/source_io/module_hs/hs_sparse_io.cpp index 580903839cc..8f02178944e 100644 --- a/source/source_io/module_hs/hs_sparse_io.cpp +++ b/source/source_io/module_hs/hs_sparse_io.cpp @@ -33,7 +33,9 @@ void open_sparse_file(std::ofstream& ofs, const ModuleIO::SparseWriteOptions& op { mode |= std::ios::binary; } - const bool append_on_restart = (options.calculation == "md") && (options.out_app_flag == 1) && options.istep >= 0; + // Append for any multi-step run (MD or relax) in append mode; + // single-point SCF passes istep = -1 and always starts a fresh file. + const bool append_on_restart = (options.out_app_flag == 1) && (options.istep >= 0); if (append_on_restart) { mode |= std::ios::app; @@ -51,7 +53,9 @@ void write_sparse_header(std::ofstream& ofs, const int nlocal, const int output_R_number) { - const int step = std::max(options.istep, 0); + // ionic step printed one-based per governance rule 22, consistent + // with the g{N} filename suffix (g1 holds the first ionic step) + const int step = std::max(options.istep, 0) + 1; if (options.binary) { ofs.write(reinterpret_cast(&step), sizeof(int)); diff --git a/source/source_io/module_hs/hsr_writer.cpp b/source/source_io/module_hs/hsr_writer.cpp index 10b5218fa69..54975d9d1c3 100644 --- a/source/source_io/module_hs/hsr_writer.cpp +++ b/source/source_io/module_hs/hsr_writer.cpp @@ -219,7 +219,8 @@ void ModuleIO::write_hcontainer_csr_binary(const std::string& fname, } std::sort(R_coordinates.begin(), R_coordinates.end()); - const int step = std::max(istep, 0); + // ionic step written one-based per governance rule 22 + const int step = std::max(istep, 0) + 1; const int nbasis = mat_serial->get_nbasis(); const int nR = static_cast(R_coordinates.size()); write_native_value(ofs, step); diff --git a/source/source_io/module_hs/pos_op_writer.cpp b/source/source_io/module_hs/pos_op_writer.cpp index 79ce0355926..8095abab207 100644 --- a/source/source_io/module_hs/pos_op_writer.cpp +++ b/source/source_io/module_hs/pos_op_writer.cpp @@ -228,8 +228,11 @@ void PosOpWriter::out_lat_r(const UnitCell& ucell, } ofs_tem1.close(); - const bool open_in_append = (calculation == "md") && (out_app_flag == 1) && step >= 0; - const int header_step = std::max(step, 0); + // Append for any multi-step run (MD or relax) in append mode; + // single-point SCF passes step = -1 and starts a fresh file. + const bool open_in_append = (out_app_flag == 1) && step >= 0; + // ionic step printed one-based per governance rule 22 + const int header_step = std::max(step, 0) + 1; ModuleIO::detail::assemble_csr(ssr.str(), tem1.str(), header_step, From 9b2b4ac4db7017a17fae239053d4056ca50cacb6 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 15:43:52 +0800 Subject: [PATCH 24/61] feat(io): single accumulated STRU file when out_app_flag=1 Instead of numbered STRU{N} files, out_app_flag=1 now writes one fixed-name STRU file that accumulates one structure block per written step, for both relax and MD: - unitcell::print_stru_file and relax_stru_io::write_stru take a new append flag; relax writes "# Ionic Step N" headers (one-based, governance rule 22) via the existing build_stru_header - mdcell::print_stru_file takes frame_comment + append; MD frames are prefixed with "# MD step N". The MPI path broadcasts a base offset (lseek SEEK_END) and keeps absolute pwrite offsets; O_APPEND is deliberately avoided since Linux pwrite ignores the offset with it - CIF output keeps single-frame semantics: fixed-name STRU.cif is overwritten each written step - modes 0/2 keep the numbered STRU{N} files unchanged - CLI help of out_stru/out_app_flag documents the mode-1 behavior Build: make -j 30 verified by developer. --- .../test/test_mdcell_reader.cpp | 2 +- source/source_cell/print_cell.cpp | 36 ++++++++++++++----- source/source_cell/print_cell.h | 20 +++++++++-- .../source_cell/test/read_atoms_hlp_test.cpp | 2 +- source/source_cell/test/test_print_cell.cpp | 18 +++++----- .../module_parameter/read_inp_out.cpp | 6 ++-- source/source_md/run_md.cpp | 15 ++++++-- source/source_relax/relax_driver.cpp | 17 ++++++--- source/source_relax/relax_stru_io.cpp | 6 ++-- source/source_relax/relax_stru_io.h | 5 ++- 10 files changed, 94 insertions(+), 33 deletions(-) diff --git a/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp b/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp index 50c907ab4f0..ac191c2d4cf 100644 --- a/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp +++ b/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp @@ -227,7 +227,7 @@ TEST(MDCellReaderTest, RestartStruPreservesAtomRecordsAcrossRanks) StruMeta metadata; metadata.species.resize(2); const std::string output_file = "distributed_mdcell_restart.STRU"; - mdcell::print_stru_file(mdcell, metadata, output_file); + mdcell::print_stru_file(mdcell, metadata, output_file, "", false); DomainDecomposition reader_decomp; MDCell round_trip = MDCellReader::read_stru(output_file, diff --git a/source/source_cell/print_cell.cpp b/source/source_cell/print_cell.cpp index f98b3ab8a99..2697bd1a961 100644 --- a/source/source_cell/print_cell.cpp +++ b/source/source_cell/print_cell.cpp @@ -101,7 +101,8 @@ namespace unitcell const bool& dpks_desc, const int& iproc, const ModuleBase::matrix& force, - const bool& has_force) + const bool& has_force, + const bool append) { ModuleBase::TITLE("UnitCell","print_stru_file"); if (iproc != 0) @@ -235,7 +236,12 @@ namespace unitcell nat_++; } } - std::ofstream ofs(fn.c_str()); + std::ios_base::openmode mode = std::ios::out; + if (append) + { + mode |= std::ios::app; + } + std::ofstream ofs(fn.c_str(), mode); ofs << str; ofs.close(); return; @@ -368,7 +374,8 @@ StruMeta make_stru_meta(const UnitCell& ucell) namespace mdcell { -void print_stru_file(const MDCell& cell, const StruMeta& stru_meta, const std::string& fn) +void print_stru_file(const MDCell& cell, const StruMeta& stru_meta, const std::string& fn, + const std::string& frame_comment, const bool append) { if (stru_meta.species.size() != cell.type_labels().size() || stru_meta.species.size() != cell.type_masses().size() @@ -376,27 +383,35 @@ void print_stru_file(const MDCell& cell, const StruMeta& stru_meta, const std::s { throw std::runtime_error("MDCell STRU metadata does not match the MDCell type data."); } - const std::string header = mdcell_stru_header(cell, stru_meta); + const std::string header = frame_comment + mdcell_stru_header(cell, stru_meta); #ifdef __MPI int rank = 0; const MPI_Comm comm = cell.communicator(); MPI_Comm_rank(comm, &rank); int header_ok = 1; + MPI_Offset base_offset = 0; if (rank == 0) { - const int header_file = open(fn.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0666); + // No O_APPEND: on Linux pwrite() ignores the given offset when the + // file was opened with O_APPEND. Truncate unless appending, then + // seek to the current end to learn the base offset. + const int open_flags = O_CREAT | O_WRONLY | (append ? 0 : O_TRUNC); + const int header_file = open(fn.c_str(), open_flags, 0666); if (header_file < 0) { header_ok = 0; } else { - const bool header_written = write_at(header_file, header, 0); + const off_t end_pos = lseek(header_file, 0, SEEK_END); + base_offset = end_pos >= 0 ? static_cast(end_pos) : 0; + const bool header_written = end_pos >= 0 && write_at(header_file, header, base_offset); const bool header_closed = close(header_file) == 0; header_ok = header_written && header_closed; } } MPI_Bcast(&header_ok, 1, MPI_INT, 0, comm); + MPI_Bcast(&base_offset, 1, MPI_OFFSET, 0, comm); if (header_ok == 0) { throw std::runtime_error("Unable to create MDCell restart STRU file: " + fn + ": " + std::strerror(errno)); @@ -413,7 +428,7 @@ void print_stru_file(const MDCell& cell, const StruMeta& stru_meta, const std::s throw std::runtime_error("Unable to open MDCell restart STRU file: " + fn + ": " + std::strerror(errno)); } - MPI_Offset offset = static_cast(header.size()); + MPI_Offset offset = base_offset + static_cast(header.size()); for (std::size_t it = 0; it < stru_meta.species.size(); ++it) { const std::string type_header = mdcell_type_header(cell, stru_meta, it); @@ -453,7 +468,12 @@ void print_stru_file(const MDCell& cell, const StruMeta& stru_meta, const std::s throw std::runtime_error("Unable to close MDCell restart STRU file: " + fn + ": " + std::strerror(errno)); } #else - std::ofstream output(fn.c_str()); + std::ios_base::openmode mode = std::ios::out; + if (append) + { + mode |= std::ios::app; + } + std::ofstream output(fn.c_str(), mode); output << header; for (std::size_t it = 0; it < stru_meta.species.size(); ++it) output << mdcell_type_header(cell, stru_meta, it) << local_mdcell_atoms(cell, it); diff --git a/source/source_cell/print_cell.h b/source/source_cell/print_cell.h index 1370629b0cd..ab39ddcf0dc 100644 --- a/source/source_cell/print_cell.h +++ b/source/source_cell/print_cell.h @@ -51,6 +51,8 @@ namespace unitcell * @param iproc GlobalV::MY_RANK [in] * @param force atomic forces in Ry/Bohr, used only when has_force is true [in] * @param has_force true to emit per-atom f fields; force matrix must match nat [in] + * @param append true to append the structure block to an existing file + * (out_app_flag == 1 multi-step runs); false truncates [in] */ void print_stru_file(const UnitCell& ucell, const Atom* atoms, @@ -64,7 +66,8 @@ namespace unitcell const bool& dpks_desc, const int& iproc, const ModuleBase::matrix& force, - const bool& has_force); + const bool& has_force, + const bool append); /** * @brief Print basic unitcell information to output stream. @@ -79,9 +82,22 @@ namespace unitcell namespace mdcell { +/** + * @brief Write an MDCell structure file in STRU format. + * + * @param mdcell distributed MD cell data [in] + * @param stru_meta species/orbital metadata used for the header [in] + * @param fn output file path [in] + * @param frame_comment comment block prepended before the structure header + * (e.g. "# MD step N\n\n"); pass an empty string for no marker [in] + * @param append true to append the frame to an existing file + * (out_app_flag == 1 multi-step runs); false truncates [in] + */ void print_stru_file(const MDCell& mdcell, const StruMeta& stru_meta, - const std::string& fn); + const std::string& fn, + const std::string& frame_comment, + const bool append); } #endif diff --git a/source/source_cell/test/read_atoms_hlp_test.cpp b/source/source_cell/test/read_atoms_hlp_test.cpp index c536fa4eabf..194a9f67fc3 100644 --- a/source/source_cell/test/read_atoms_hlp_test.cpp +++ b/source/source_cell/test/read_atoms_hlp_test.cpp @@ -684,7 +684,7 @@ TEST_F(ReadAtomsHelperTest, RoundTripMultiAtomMixedFields) const std::string filename = "test_stru_roundtrip.tmp"; unitcell::print_stru_file(ucell, ucell.atoms, ucell.latvec, filename, "", - 2, false, false, false, false, 0, force, true); + 2, false, false, false, false, 0, force, true, false); std::ifstream ifpos(filename.c_str()); diff --git a/source/source_cell/test/test_print_cell.cpp b/source/source_cell/test/test_print_cell.cpp index b1d131ffcb4..9bcf7123f33 100644 --- a/source/source_cell/test/test_print_cell.cpp +++ b/source/source_cell/test/test_print_cell.cpp @@ -48,7 +48,7 @@ TEST_F(PrintCellTest, PrintSTRU_nspin1) std::string fn = "C1H2_STRU_nspin1"; unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 1, false, false, false, false, 0, ModuleBase::matrix(), false); + fn, "", 1, false, false, false, false, 0, ModuleBase::matrix(), false, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -91,7 +91,7 @@ TEST_F(PrintCellTest, PrintSTRU_zero_force_computed) force.zero_out(); unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 2, false, false, false, false, 0, force, true); + fn, "", 2, false, false, false, false, 0, force, true, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -114,7 +114,7 @@ TEST_F(PrintCellTest, PrintSTRU_force_matrix_without_flag) force(0, 0) = 0.1; force(0, 1) = 0.2; force(0, 2) = 0.3; unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 2, false, false, false, false, 0, force, false); + fn, "", 2, false, false, false, false, 0, force, false, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -131,7 +131,7 @@ TEST_F(PrintCellTest, PrintSTRU_nspin2_no_force) std::string fn = "C1H2_STRU_nspin2"; unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 2, true, false, false, false, 0, ModuleBase::matrix(), false); + fn, "", 2, true, false, false, false, 0, ModuleBase::matrix(), false, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -163,7 +163,7 @@ TEST_F(PrintCellTest, PrintSTRU_nspin2_with_mag) ucell->atoms[1].mag[0] = -0.5; // H1 ucell->atoms[1].mag[1] = 2.0; // H2 unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 2, false, false, false, false, 0, ModuleBase::matrix(), false); + fn, "", 2, false, false, false, false, 0, ModuleBase::matrix(), false, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -192,7 +192,7 @@ TEST_F(PrintCellTest, PrintSTRU_nspin2_mulliken) ucell->orbital_fn[1] = "__unittest_orbital_fn_1__"; ucell->atom_mulliken = {{-1, 0.5}, {-1, 0.4}, {-1, 0.3}}; unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 2, false, true, true, true, 0, ModuleBase::matrix(), false); + fn, "", 2, false, true, true, true, 0, ModuleBase::matrix(), false, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -221,7 +221,7 @@ TEST_F(PrintCellTest, PrintSTRU_nspin4_initial_mag) ucell->atoms[1].m_loc_[0].set(0.0, 1.0, 0.0); // H1 ucell->atoms[1].m_loc_[1].set(0.0, 0.0, 1.0); // H2 unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 4, false, false, false, false, 0, ModuleBase::matrix(), false); + fn, "", 4, false, false, false, false, 0, ModuleBase::matrix(), false, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -247,7 +247,7 @@ TEST_F(PrintCellTest, PrintSTRU_nspin4_mulliken) ucell->atom_mulliken = {{-1, 0.5, 0.1, 0.2}, {-1, 0.4, 0.3, 0.4}, {-1, 0.3, 0.5, 0.6}}; unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 4, false, true, false, false, 0, ModuleBase::matrix(), false); + fn, "", 4, false, true, false, false, 0, ModuleBase::matrix(), false, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -274,7 +274,7 @@ TEST_F(PrintCellTest, PrintSTRU_with_force) force(2, 0) = 0.05; force(2, 1) = 0.15; force(2, 2) = -0.25; // H2 unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 2, false, false, false, false, 0, force, true); + fn, "", 2, false, false, false, false, 0, force, true, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index 37fb09f98ac..211be5716ee 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -458,8 +458,8 @@ Note: values 2 and 3 are no longer supported. Setting out_dos to 2 or 3 will rai item.type = "Integer"; item.description = "Controls the output of structure files per ionic step. The files are written to the OUT.{suffix}/ directory. Each file corresponds to the structure at the current ionic step, and includes a header comment with the ABACUS version, timestamp, energy, and stress tensor. When out_freq_ion is positive, the numbered files STRU{istep} (or STRU{istep}.cif) are written every out_freq_ion steps; when out_freq_ion is 0, no numbered files are output. This parameter is effective for scf/nscf/relax/cell-relax and md. For scf/nscf only STRU_FINAL (or STRU_FINAL.cif) is written. For MD, structure output follows the same out_freq_ion frequency as the restart file.\n" " - 0: No structure files are output.\n" - " - 1: ABACUS STRU format files are output. The latest structure is written to STRU_NOW (overwritten each step), the numbered file STRU{istep} (e.g., STRU1, STRU2) is written every out_freq_ion steps (when out_freq_ion is positive), and the final converged structure is written to STRU_FINAL. No CIF files are output.\n" - " - 2: CIF format files are output. The latest structure is written to STRU_NOW.cif (overwritten each step), the numbered file STRU{istep}.cif (e.g., STRU1.cif, STRU2.cif) is written every out_freq_ion steps (when out_freq_ion is positive), and the final converged structure is written to STRU_FINAL.cif. No non-CIF files are output.\n" + " - 1: ABACUS STRU format files are output. The latest structure is written to STRU_NOW (overwritten each step), the numbered file STRU{istep} (e.g., STRU1, STRU2) is written every out_freq_ion steps (when out_freq_ion is positive), and the final converged structure is written to STRU_FINAL. No CIF files are output. When out_app_flag is 1, the numbered STRU{istep} files are replaced by a single fixed-name STRU file that appends one structure block (with a one-based \"# Ionic Step N\" or \"# MD step N\" comment) per written step.\n" + " - 2: CIF format files are output. The latest structure is written to STRU_NOW.cif (overwritten each step), the numbered file STRU{istep}.cif (e.g., STRU1.cif, STRU2.cif) is written every out_freq_ion steps (when out_freq_ion is positive), and the final converged structure is written to STRU_FINAL.cif. No non-CIF files are output. When out_app_flag is 1, the numbered files are replaced by a fixed-name STRU.cif overwritten each written step.\n" "[NOTE] For backward compatibility, true/false (case insensitive) are accepted and converted to 1/0."; item.default_value = "1"; item.unit = ""; @@ -1400,7 +1400,7 @@ The circle order of the charge density on real space grids is: x is the outer lo * 1: Reuse fixed filenames across steps (default, backward compatible): text outputs append new records, cube snapshots are overwritten * 2: Write to subdirectory (PSI/, CHG/, POT/, ELF/, DOS/, DIP/, MAT/), files carry a g{step} suffix The legacy Boolean spellings (true/false, yes/no, on/off, t/f, y/n, and the dotted forms) are still accepted and map to modes 1/0 as before. -For more information, refer to the documentation of out_mat_r, out_hsr, out_mat_t, out_mat_dh, out_hsk, out_wfc_lcao, out_chg, out_pot, out_elf, out_dos, out_dipole.)"; +For more information, refer to the documentation of out_mat_r, out_hsr, out_mat_t, out_mat_dh, out_hsk, out_wfc_lcao, out_chg, out_pot, out_elf, out_dos, out_dipole, out_stru.)"; item.default_value = "1"; item.unit = ""; item.set_availability("basis_type==lcao and gamma_only==0"); diff --git a/source/source_md/run_md.cpp b/source/source_md/run_md.cpp index 1e92c2e9556..d16ac26135f 100644 --- a/source/source_md/run_md.cpp +++ b/source/source_md/run_md.cpp @@ -299,9 +299,20 @@ void md_line(MDCell& mdcell, if (param_in.inp.out_stru > 0) { const int stru_step = current_step + 1; + // out_app_flag == 1: single fixed-name STRU accumulates one + // frame per step with a one-based "# MD step N" comment; + // modes 0/2 keep the numbered STRU{stru_step} files. + const bool append_stru = (param_in.inp.out_app_flag == 1); + const std::string frame_comment = append_stru + ? "# MD step " + std::to_string(stru_step) + "\n\n" + : ""; std::stringstream file; - file << PARAM.globalv.global_stru_dir << "STRU" << stru_step; - mdcell::print_stru_file(mdcell, mdcell.stru_meta(), file.str()); + file << PARAM.globalv.global_stru_dir << "STRU"; + if (!append_stru) + { + file << stru_step; + } + mdcell::print_stru_file(mdcell, mdcell.stru_meta(), file.str(), frame_comment, append_stru); } mdrun->write_restart(PARAM.globalv.global_out_dir); } diff --git a/source/source_relax/relax_driver.cpp b/source/source_relax/relax_driver.cpp index 865ed78d729..f9afb041374 100644 --- a/source/source_relax/relax_driver.cpp +++ b/source/source_relax/relax_driver.cpp @@ -225,13 +225,20 @@ void Relax_Driver::stru_out(const int istep, need_orb, deepks_setorb_, my_rank_, - inp.cal_force); + inp.cal_force, + false); } // Numbered files per out_freq_ion: only meaningful for relaxation calculations if (is_relax && freq_ok) { - const std::string step_file = out_dir_ + "STRU" + std::to_string(istep + 1) + (inp.out_stru == 1 ? "" : ".cif"); + // out_app_flag == 1: single fixed-name STRU accumulates one block per + // step (CIF format truncates to the latest frame instead); modes 0/2 + // keep the numbered STRU{istep+1} files. + const bool append_stru = (inp.out_app_flag == 1) && (inp.out_stru == 1); + const std::string step_file = (inp.out_app_flag == 1) + ? out_dir_ + (inp.out_stru == 1 ? "STRU" : "STRU.cif") + : out_dir_ + "STRU" + std::to_string(istep + 1) + (inp.out_stru == 1 ? "" : ".cif"); relax_stru_io::write_stru(ucell, inp, step_file, @@ -240,7 +247,8 @@ void Relax_Driver::stru_out(const int istep, need_orb, deepks_setorb_, my_rank_, - inp.cal_force); + inp.cal_force, + append_stru); } } @@ -291,7 +299,8 @@ void Relax_Driver::final_out(const int istep, need_orb, deepks_setorb_, my_rank_, - write_force); + write_force, + false); } // relax_nmax == 0 is a valid dry-run mode: no relaxation step was ever diff --git a/source/source_relax/relax_stru_io.cpp b/source/source_relax/relax_stru_io.cpp index 0f191ac644b..21883283cd2 100644 --- a/source/source_relax/relax_stru_io.cpp +++ b/source/source_relax/relax_stru_io.cpp @@ -117,7 +117,8 @@ void write_stru(UnitCell& ucell, const bool need_orb, const bool deepks_setorb, const int my_rank, - const bool has_force) + const bool has_force, + const bool append) { if (inp.out_stru == 1) { @@ -133,7 +134,8 @@ void write_stru(UnitCell& ucell, deepks_setorb, my_rank, force, - has_force); + has_force, + append); } else if (inp.out_stru == 2) { diff --git a/source/source_relax/relax_stru_io.h b/source/source_relax/relax_stru_io.h index 2830f4b09b7..87e37882b07 100644 --- a/source/source_relax/relax_stru_io.h +++ b/source/source_relax/relax_stru_io.h @@ -64,6 +64,8 @@ bool need_orbital(const Input_para& inp); * @param has_force true to emit per-atom f fields; forwarded to * print_stru_file. Callers must pass false when the forces do not * belong to the geometry being written (e.g. early relax exit). + * @param append true to append the structure block (STRU format only, + * for out_app_flag == 1 multi-step runs); CIF always truncates. */ void write_stru(UnitCell& ucell, const Input_para& inp, @@ -73,7 +75,8 @@ void write_stru(UnitCell& ucell, const bool need_orb, const bool deepks_setorb, const int my_rank, - const bool has_force); + const bool has_force, + const bool append); } // namespace relax_stru_io #endif From 0933cf5bdf6ecde873bb3ab1d5f239f489c2422b Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 15:48:35 +0800 Subject: [PATCH 25/61] fix(io): terminate DM(k) matrix body with a newline The DM(k) writer emitted the row newline only before each group of elements, so the file ended without a trailing newline. In append mode (out_app_flag == 1) the next " --- Ionic Step N ---" record was glued to the last value of the previous step. Add a trailing newline before closing the file and cover it with a regression assertion. --- source/source_io/module_dm/test/write_dmk_test.cpp | 2 ++ source/source_io/module_dm/write_dmk.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/source/source_io/module_dm/test/write_dmk_test.cpp b/source/source_io/module_dm/test/write_dmk_test.cpp index 6b552788306..ab5e5bfda9b 100644 --- a/source/source_io/module_dm/test/write_dmk_test.cpp +++ b/source/source_io/module_dm/test/write_dmk_test.cpp @@ -195,6 +195,8 @@ TEST(DMKTest,WriteDMK) { std::istreambuf_iterator()); EXPECT_THAT(str, testing::HasSubstr("0 # Fermi energy in Ry")); EXPECT_THAT(str, testing::HasSubstr("20 # number of localized basis")); + // the matrix body must end with a newline so appended records are not glued + EXPECT_EQ(str.back(), '\n'); ifs.close(); fn = "dmk2s1_nao.txt"; diff --git a/source/source_io/module_dm/write_dmk.cpp b/source/source_io/module_dm/write_dmk.cpp index b75114d3b48..9a162a8ba78 100644 --- a/source/source_io/module_dm/write_dmk.cpp +++ b/source/source_io/module_dm/write_dmk.cpp @@ -331,6 +331,10 @@ void ModuleIO::write_dmk(const std::vector>& dmk, } } } + // Terminate the matrix body with a newline so that the next + // appended record (out_app_flag == 1) starts on a fresh line + // instead of being glued to the last value of this step. + ofs << std::endl; ofs.close(); } // rank0 } // ik From 65a4a61126c6c94176a76e8924eb5829410d0327 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 15:52:42 +0800 Subject: [PATCH 26/61] feat(io): append PW wavefunction frames when out_app_flag=1 write_wfc_pw now mirrors write_wfc_nao's append_flag: the first written step truncates the fixed-name wf*_pw.{txt,dat} files and later ionic steps append one frame per step (all payload writes already use append mode; only the per-step pre-clear is skipped). Modes 0/2 keep per-step g{N} files unchanged. Also terminate the DM(k) matrix body with a newline so appended records start on a fresh line (developer fix), and document in read_wf2rho_pw/out_wfc_pw help that multi-frame binary files are read from their first frame only. Build: make -j 30 verified by developer. --- .../module_parameter/read_inp_out.cpp | 4 ++-- source/source_io/module_wf/read_wf2rho_pw.cpp | 5 ++++ source/source_io/module_wf/write_wfc_pw.cpp | 24 ++++++++++++------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index 211be5716ee..f869afb8f4f 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -271,9 +271,9 @@ Available values are: The file-name pattern is `wfk{k}[s{spin}][g{geometry step}][e{electronic iteration}]_pw.txt` for `out_wfc_pw=1` and `wfk{k}[s{spin}][g{geometry step}][e{electronic iteration}]_pw.dat` for `out_wfc_pw=2`. All PW output files include a `k*` label, including Gamma-only calculations. Without geometry-step or electronic-iteration indices, representative names are `wfk1_pw.txt` or `wfk1_pw.dat` for `nspin=1`, `wfk1s1_pw.txt` and `wfk1s2_pw.txt` or their `.dat` equivalents for `nspin=2`, and `wfk1s4_pw.txt` or `wfk1s4_pw.dat` for `nspin=4`. -With `out_freq_ion=0`, files are written only when the electronic calculation converges or reaches `scf_nmax`; no `g*` or `e*` index is added. During structural relaxation or molecular dynamics, later ionic steps overwrite the same unindexed files. With `out_freq_ion` > 0, output is restricted to the ionic steps selected by `out_freq_ion` and is written when the electronic iteration is a multiple of `out_freq_elec`, when the calculation converges, or when it reaches `scf_nmax`. Both `g*` and `e*` indices are then added, including for a static `calculation=scf` or `calculation=nscf` run. +With `out_freq_ion=0`, files are written only when the electronic calculation converges or reaches `scf_nmax`; no `g*` or `e*` index is added. During structural relaxation or molecular dynamics with `out_app_flag=1`, the first written step truncates the unindexed files and later ionic steps append one frame per step (mirroring `out_wfc_lcao`); with `out_app_flag=0` the unindexed files are overwritten each step. With `out_freq_ion` > 0, output is restricted to the ionic steps selected by `out_freq_ion` and is written when the electronic iteration is a multiple of `out_freq_elec`, when the calculation converges, or when it reaches `scf_nmax`. Both `g*` and `e*` indices are then added (except that `g*` is omitted when `out_app_flag=1`), including for a static `calculation=scf` or `calculation=nscf` run. -With `init_wfc file binary`, ABACUS reads only unindexed binary `wf*_pw.dat` files from `read_file_dir`. Such directly reusable files are normally generated with `out_wfc_pw=2` and `out_freq_ion=0`. Text `wf*_pw.txt` files and files containing `g*` or `e*` indices are not matched automatically. +With `init_wfc file binary`, ABACUS reads only unindexed binary `wf*_pw.dat` files from `read_file_dir`, starting from the first frame. Such directly reusable files are normally generated with `out_wfc_pw=2` and `out_freq_ion=0`; multi-frame files accumulated under `out_app_flag=1` are trajectories and only their first frame is visible to the reader. Text `wf*_pw.txt` files and files containing `g*` or `e*` indices are not matched automatically. [NOTE] In the 3.10-LTS version, the binary files are named `WAVEFUNC1.dat`, `WAVEFUNC2.dat`, etc.)"; item.default_value = "0"; diff --git a/source/source_io/module_wf/read_wf2rho_pw.cpp b/source/source_io/module_wf/read_wf2rho_pw.cpp index 298334607db..20d0415e874 100644 --- a/source/source_io/module_wf/read_wf2rho_pw.cpp +++ b/source/source_io/module_wf/read_wf2rho_pw.cpp @@ -128,6 +128,11 @@ void ModuleIO::read_wf2rho_pw_impl(const ModulePW::PW_Basis_K* pw_wfc, // mohan add 2025-05-17 // .dat file + // NOTE: reads the fixed-name binary file from its first frame. Files + // accumulated across ionic steps under out_app_flag == 1 hold one + // frame per step (MD trajectories); only the first frame is seen + // here, so use out_app_flag 0/2 (per-step files) when the charge + // density of a specific step is to be reconstructed. const int out_type = 2; const int out_app_flag = 0; const bool gamma_only = false; diff --git a/source/source_io/module_wf/write_wfc_pw.cpp b/source/source_io/module_wf/write_wfc_pw.cpp index 4d322c83f54..f7433de42ef 100644 --- a/source/source_io/module_wf/write_wfc_pw.cpp +++ b/source/source_io/module_wf/write_wfc_pw.cpp @@ -65,15 +65,23 @@ void ModuleIO::write_wfc_pw( if (rank_in_pool == 0) { - if (out_wfc_pw == 1) + // Mirrors write_wfc_nao's append_flag: the first step of a + // run truncates the file, later steps append a new frame + // when out_app_flag == 1, so the file accumulates one frame + // per ionic step instead of being rewritten each step. + const bool append_frame = (istep > 0 && out_app_flag == 1); + if (!append_frame) { - std::ofstream ofs(fn.c_str()); // clear all wavefunc files. - ofs.close(); - } - else if (out_wfc_pw == 2) - { - Binstream wfs(fn, "w"); - wfs.close(); + if (out_wfc_pw == 1) + { + std::ofstream ofs(fn.c_str()); // clear all wavefunc files. + ofs.close(); + } + else if (out_wfc_pw == 2) + { + Binstream wfs(fn, "w"); + wfs.close(); + } } } } From c14f0c81a858c0c5190b51706f90c9e34b309da2 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 16:14:11 +0800 Subject: [PATCH 27/61] feat(io): add per-ionic-step provenance header to band output band.txt/bands1.txt/bands2.txt contained numeric rows only, so the k-point blocks appended across ionic steps of a relax/md run were indistinguishable, and a single-step run carried no version or timestamp. Print the STRU-style header ("# ABACUS version", "# Written at", "# IONIC STEP n") in front of every ionic step instead: write_bands now takes the step index, nscf_bands takes the prepared header, and the step label is one-based with the collinear spin-channel tag when nspin=2. The integration comparison strips "#" lines before comparing band.txt, and the unit test skips comment lines while parsing and asserts the header of each appended block. --- .../output_files/output-specification.md | 17 +++- source/source_esolver/esolver_ks.cpp | 2 +- .../source_io/module_energy/write_bands.cpp | 47 +++++++++- source/source_io/module_energy/write_bands.h | 22 ++++- source/source_io/test/write_bands_test.cpp | 89 ++++++++++++++++--- tests/integrate/validation_tools/props_mat.sh | 14 ++- 6 files changed, 168 insertions(+), 23 deletions(-) diff --git a/docs/advanced/output_files/output-specification.md b/docs/advanced/output_files/output-specification.md index 71e55026865..e3b1b128777 100644 --- a/docs/advanced/output_files/output-specification.md +++ b/docs/advanced/output_files/output-specification.md @@ -98,7 +98,7 @@ All output file naming conventions can be found in the online documentation (wit | `doss1g1.txt` | Density of states | | `eig_occ.txt` | Eigenvalues and occupations | | `mulliken.txt` | Mulliken population analysis | -| `band.txt` | Band structure | +| `band.txt` | Band structure (nspin=1/4); `bands1.txt`/`bands2.txt` when nspin=2 | | `chgs1.cube`, `chgs2.cube` | Charge density (spin 1, spin 2) | | `chg.cube` | Total charge density | | Initial charge density (out_chg=2) | | @@ -121,6 +121,21 @@ All output file naming conventions can be found in the online documentation (wit | `elfs{#}g{#}.cube` | ELF, spin {#}, geometry step {#} (nspin=2) | | `elftotg{#}.cube` | ELF, total, geometry step {#} (nspin=2) | +**Band structure files (`out_band`):** each ionic step of `band.txt`, +`bands1.txt` and `bands2.txt` is preceded by a provenance comment block, so the +k-point blocks appended by a relax/md run stay distinguishable: + +``` +# ABACUS version: v3.11.0-beta11 +# Written at 2026-10-20 15:40:45 +# IONIC STEP 1 (spin up ) + 1 0.00000000 -5.36802595 6.70427084 ... +``` + +`# IONIC STEP` is one-based and carries the collinear spin-channel label only +when `nspin = 2`. Consumers that parse the numeric columns must skip lines +starting with `#`. + ## 3. File Format Standards ### 3.1 Header Section with Comments diff --git a/source/source_esolver/esolver_ks.cpp b/source/source_esolver/esolver_ks.cpp index 9d12dfe07dd..01f08d8d78d 100644 --- a/source/source_esolver/esolver_ks.cpp +++ b/source/source_esolver/esolver_ks.cpp @@ -440,7 +440,7 @@ void ESolver_KS::after_scf(UnitCell& ucell, const int istep, const bool conv_eso istep); // 4) write band information to band.txt - ModuleIO::write_bands(*this->inp_, this->pelec->ekb, this->kv, GlobalV::ofs_running); + ModuleIO::write_bands(*this->inp_, this->pelec->ekb, this->kv, GlobalV::ofs_running, istep); } diff --git a/source/source_io/module_energy/write_bands.cpp b/source/source_io/module_energy/write_bands.cpp index e5d229f00c0..ec92f77dc99 100644 --- a/source/source_io/module_energy/write_bands.cpp +++ b/source/source_io/module_energy/write_bands.cpp @@ -9,13 +9,43 @@ #include "source_base/tool_title.h" #include "source_io/module_output/spin_tag.h" #include "source_io/module_parameter/parameter.h" // mohan add 20250911 +#include "source_main/version.h" #ifdef __MPI #include #endif +#include + +namespace +{ + +/** + * @brief build the STRU-style provenance header that precedes the k-point + * block of one ionic step in a band output file. + * + * @param istep zero-based ionic step index, printed one-based + * @param spin_tag label of the collinear spin channel, empty when nspin != 2 + * @return three comment lines: ABACUS version, local timestamp, ionic step + */ +std::string build_band_header(const int istep, const std::string& spin_tag) +{ + const std::time_t now = std::time(nullptr); + std::tm tm_local; + localtime_r(&now, &tm_local); + char time_buf[64]; + std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", &tm_local); + return FmtCore::format("# ABACUS version: %s\n# Written at %s\n# IONIC STEP %d%s\n", + VERSION, + time_buf, + istep + 1, + spin_tag.c_str()); +} + +} // namespace + void ModuleIO::write_bands(const Input_para& inp, const ModuleBase::matrix& ekb, const K_Vectors& kv, - std::ofstream& ofs_running) + std::ofstream& ofs_running, const int istep) { // write band information to band.txt if (inp.out_band[0]) @@ -44,6 +74,7 @@ void ModuleIO::write_bands(const Input_para& inp, const ModuleBase::matrix& ekb, const std::string spin_tag = ModuleIO::make_spin_tag(is, nspin0); ofs_running << " Write eigenvalues" << spin_tag << " to file: " << eig_file << std::endl; + const std::string header = build_band_header(istep, spin_tag); const double eshift = 0.0; nscf_bands(is, eig_file, @@ -51,7 +82,8 @@ void ModuleIO::write_bands(const Input_para& inp, const ModuleBase::matrix& ekb, eshift, inp.out_band[1], // precision global_ekb, - kv); + kv, + header); } } } @@ -62,7 +94,8 @@ void ModuleIO::nscf_bands(const int& is, const double& fermie, const int& precision, const ModuleBase::matrix& ekb, - const K_Vectors& kv) + const K_Vectors& kv, + const std::string& header) { ModuleBase::TITLE("ModuleIO", "nscf_bands"); ModuleBase::timer::start("ModuleIO", "nscf_bands"); @@ -125,6 +158,10 @@ void ModuleIO::nscf_bands(const int& is, if (kv.para_k.rank_in_pool == 0 && band_group == 0) { std::ofstream ofs(eig_file.c_str(), std::ios::app); + if (ik == 0) + { + ofs << header; + } ofs << FmtCore::format("%4d", ik + 1); int width = precision + 4; std::string fmtstr = " %." + std::to_string(precision) + "f"; @@ -171,6 +208,10 @@ void ModuleIO::nscf_bands(const int& is, const int spin_k = ik + is * nks_np; if (kv.isk[spin_k] == is) { + if (ik == 0) + { + ofs << header; + } ofs << FmtCore::format("%4d", ik + 1); int width = precision + 4; std::string fmtstr = " %." + std::to_string(precision) + "f"; diff --git a/source/source_io/module_energy/write_bands.h b/source/source_io/module_energy/write_bands.h index 09c2135aff3..39b912498e5 100644 --- a/source/source_io/module_energy/write_bands.h +++ b/source/source_io/module_energy/write_bands.h @@ -11,21 +11,36 @@ namespace ModuleIO { +/** + * @brief write the eigenvalues of every spin channel to band.txt (nspin=1/4) + * or to bands1.txt/bands2.txt (nspin=2). + * + * @param inp input parameters, out_band[0] enables the output and out_band[1] + * sets the number of printed decimals + * @param ekb eigenvalues of k points and bands + * @param kv klist + * @param ofs_running running log stream + * @param istep zero-based index of the current ionic step, printed one-based + * in the per-step provenance header + */ void write_bands(const Input_para& inp, const ModuleBase::matrix& ekb, const K_Vectors& kv, - std::ofstream& ofs_running); + std::ofstream& ofs_running, + const int istep); /** * @brief calculate the band structure * * @param is spin index is = 0 or 1 - * @param out_band_dir directory to save the band structure + * @param eig_file file to save the band structure * @param nband number of bands * @param fermie fermi energy * @param precision precision of the output * @param ekb eigenvalues of k points and bands * @param kv klist + * @param header provenance header written once in front of the k-point block; + * pass an empty string to omit it */ void nscf_bands(const int& is, const std::string &eig_file, @@ -33,7 +48,8 @@ void nscf_bands(const int& is, const double& fermie, const int& precision, const ModuleBase::matrix& ekb, - const K_Vectors& kv); + const K_Vectors& kv, + const std::string& header); } #endif diff --git a/source/source_io/test/write_bands_test.cpp b/source/source_io/test/write_bands_test.cpp index 4fdcbdbb7e5..8cf6459a693 100644 --- a/source/source_io/test/write_bands_test.cpp +++ b/source/source_io/test/write_bands_test.cpp @@ -5,6 +5,7 @@ #include "source_base/module_parallel/para_world.h" #include "source_base/parallel_comm.h" #include "source_base/parallel_global.h" +#include "source_main/version.h" #include "gtest/gtest.h" @@ -227,6 +228,61 @@ class BandOutputTest : public testing::TestWithParam input_.out_band = {1, 8}; } + /// Read the next data line of a band file, skipping the provenance header. + static bool read_data_line(std::istream& stream, std::string& line) + { + while (static_cast(std::getline(stream, line))) + { + const std::size_t first = line.find_first_not_of(" \t"); + if (first != std::string::npos && line[first] == '#') + { + continue; + } + return true; + } + return false; + } + + /// Collect the comment lines of a band file in the order they were written. + std::vector comment_lines(const std::string& name) const + { + std::vector comments; + if (world_->rank() != 0) + { + return comments; + } + std::ifstream file(directory_ + name); + EXPECT_TRUE(file.is_open()) << name; + std::string line; + while (static_cast(std::getline(file, line))) + { + const std::size_t first = line.find_first_not_of(" \t"); + if (first != std::string::npos && line[first] == '#') + { + comments.push_back(line); + } + } + return comments; + } + + /// Check the three comment lines preceding k-point block number `block`. + void expect_header(const std::string& name, int block, int spin, int channels) const + { + if (world_->rank() != 0) + { + return; + } + SCOPED_TRACE(name); + const std::vector comments = comment_lines(name); + ASSERT_GE(comments.size(), static_cast(3 * block + 3)); + EXPECT_EQ(comments[3 * block], "# ABACUS version: " + std::string(VERSION)); + const std::string written = "# Written at "; + EXPECT_EQ(comments[3 * block + 1].compare(0, written.size(), written), 0) + << comments[3 * block + 1]; + const std::string tag = (channels == 2) ? (spin == 0 ? " (spin up )" : " (spin down)") : ""; + EXPECT_EQ(comments[3 * block + 2], "# IONIC STEP " + std::to_string(block + 1) + tag); + } + void expect_output(const std::string& name, int spin, int nbands, double fermi, int precision, int copies) const { @@ -244,7 +300,7 @@ class BandOutputTest : public testing::TestWithParam { for (int ik = 0; ik < 5; ++ik) { - ASSERT_TRUE(static_cast(std::getline(file, line))) << "Missing k-point " << ik; + ASSERT_TRUE(read_data_line(file, line)) << "Missing k-point " << ik; std::istringstream row(line); int index = 0; ASSERT_TRUE(static_cast(row >> index)); @@ -271,7 +327,7 @@ class BandOutputTest : public testing::TestWithParam EXPECT_FALSE(static_cast(row >> extra)) << "Unexpected column: " << extra; } } - EXPECT_FALSE(static_cast(std::getline(file, line))) << "Unexpected line: " << line; + EXPECT_FALSE(read_data_line(file, line)) << "Unexpected line: " << line; } std::string read_text(const std::string& name) const @@ -304,18 +360,21 @@ class BandOutputTest : public testing::TestWithParam { SCOPED_TRACE(distributed ? "distributed" : "replicated"); prepare(nspin, 3, distributed); - ModuleIO::write_bands(input_, ekb_, kv_, ofs_running); + ModuleIO::write_bands(input_, ekb_, kv_, ofs_running, 0); if (nspin == 2) { expect_output("bands1.txt", 0, 3, 0.0, 8, 1); expect_output("bands2.txt", 1, 3, 0.0, 8, 1); expect_missing("band.txt"); + expect_header("bands1.txt", 0, 0, 2); + expect_header("bands2.txt", 0, 1, 2); } else { expect_output("band.txt", 0, 3, 0.0, 8, 1); expect_missing("bands1.txt"); expect_missing("bands2.txt"); + expect_header("band.txt", 0, 0, 1); } // Root must finish reading before another collective call overwrites output. Parallel::barrier(*world_); @@ -326,7 +385,7 @@ class BandOutputTest : public testing::TestWithParam TEST_P(BandOutputTest, DirectSingleSpin) { prepare(1, 3, false); - ModuleIO::nscf_bands(0, directory_ + "direct.txt", 3, 0.0, 8, ekb_, kv_); + ModuleIO::nscf_bands(0, directory_ + "direct.txt", 3, 0.0, 8, ekb_, kv_, ""); expect_output("direct.txt", 0, 3, 0.0, 8, 1); if (world_->rank() == 0) { @@ -339,8 +398,8 @@ TEST_P(BandOutputTest, DirectSingleSpin) TEST_P(BandOutputTest, DirectBothSpins) { prepare(2, 3, false); - ModuleIO::nscf_bands(0, directory_ + "up.txt", 3, 0.0, 8, ekb_, kv_); - ModuleIO::nscf_bands(1, directory_ + "down.txt", 3, 0.0, 8, ekb_, kv_); + ModuleIO::nscf_bands(0, directory_ + "up.txt", 3, 0.0, 8, ekb_, kv_, ""); + ModuleIO::nscf_bands(1, directory_ + "down.txt", 3, 0.0, 8, ekb_, kv_, ""); expect_output("up.txt", 0, 3, 0.0, 8, 1); expect_output("down.txt", 1, 3, 0.0, 8, 1); } @@ -350,7 +409,7 @@ TEST_P(BandOutputTest, PrecisionAndFermiShift) prepare(1, 3, false); for (const int precision : {4, 8}) { - ModuleIO::nscf_bands(0, directory_ + "direct.txt", 3, 0.25, precision, ekb_, kv_); + ModuleIO::nscf_bands(0, directory_ + "direct.txt", 3, 0.25, precision, ekb_, kv_, ""); expect_output("direct.txt", 0, 3, 0.25, precision, 1); if (world_->rank() == 0 && precision == 4) { @@ -366,23 +425,25 @@ TEST_P(BandOutputTest, OverwriteAndAppend) prepare(1, 3, true); seed_file("band.txt", "stale output\n"); Parallel::barrier(*world_); - ModuleIO::write_bands(input_, ekb_, kv_, ofs_running); + ModuleIO::write_bands(input_, ekb_, kv_, ofs_running, 0); expect_output("band.txt", 0, 3, 0.0, 8, 1); + expect_header("band.txt", 0, 0, 1); Parallel::barrier(*world_); parameters_->set_output(directory_, true); - ModuleIO::write_bands(input_, ekb_, kv_, ofs_running); + ModuleIO::write_bands(input_, ekb_, kv_, ofs_running, 1); expect_output("band.txt", 0, 3, 0.0, 8, 2); + expect_header("band.txt", 1, 0, 1); } TEST_P(BandOutputTest, OutputDisabled) { prepare(1, 3, true); input_.out_band[0] = 0; - ModuleIO::write_bands(input_, ekb_, kv_, ofs_running); + ModuleIO::write_bands(input_, ekb_, kv_, ofs_running, 0); expect_missing("band.txt"); seed_file("band.txt", "keep this content\n"); Parallel::barrier(*world_); - ModuleIO::write_bands(input_, ekb_, kv_, ofs_running); + ModuleIO::write_bands(input_, ekb_, kv_, ofs_running, 0); if (world_->rank() == 0) { EXPECT_EQ(read_text("band.txt"), "keep this content\n"); @@ -413,9 +474,11 @@ TEST_P(BandOutputTest, EmptyBandShard) { EXPECT_EQ(ekb_.nc, 0); } - ModuleIO::write_bands(input_, ekb_, kv_, ofs_running); + ModuleIO::write_bands(input_, ekb_, kv_, ofs_running, 0); expect_output("bands1.txt", 0, 1, 0.0, 8, 1); expect_output("bands2.txt", 1, 1, 0.0, 8, 1); + expect_header("bands1.txt", 0, 0, 2); + expect_header("bands2.txt", 0, 1, 2); } TEST_P(BandOutputTest, RunningLogNamesSpinChannels) @@ -425,7 +488,7 @@ TEST_P(BandOutputTest, RunningLogNamesSpinChannels) + std::to_string(world_->rank()) + ".log"; std::ofstream log(log_name); EXPECT_TRUE(log.is_open()); - ModuleIO::write_bands(input_, ekb_, kv_, log); + ModuleIO::write_bands(input_, ekb_, kv_, log, 0); log.close(); std::ifstream in(log_name); diff --git a/tests/integrate/validation_tools/props_mat.sh b/tests/integrate/validation_tools/props_mat.sh index 5128c3dd379..bc345fee57c 100644 --- a/tests/integrate/validation_tools/props_mat.sh +++ b/tests/integrate/validation_tools/props_mat.sh @@ -106,8 +106,18 @@ fi if ! test -z "$has_band" && [ $has_band == 1 ]; then bandref=band.txt.ref bandcal=OUT.autotest/band.txt - python3 $COMPARE_SCRIPT $bandref $bandcal 8 - echo "CompareBand_pass $?" >>$props_result_file + # Every ionic step in band.txt is preceded by a provenance comment block + # ("# ABACUS version: ...", "# Written at ...", "# IONIC STEP n"), so the + # reference and the computed file are compared after those lines are + # stripped. A missing file must be reported explicitly, because process + # substitution would hand CompareFile.py an empty stream and pass silently. + if ! test -f "$bandref" || ! test -f "$bandcal"; then + echo "Error: can not find file $bandref or $bandcal" + echo "CompareBand_pass 1" >>$props_result_file + else + python3 $COMPARE_SCRIPT <(sed '/^[[:space:]]*#/d' "$bandref") <(sed '/^[[:space:]]*#/d' "$bandcal") 8 + echo "CompareBand_pass $?" >>$props_result_file + fi fi #-------------------------------- From 747646c334fb4f4d16b3fbc8693098fbd4573f67 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 16:36:16 +0800 Subject: [PATCH 28/61] fix(io): use compact spin label in band provenance header make_spin_tag pads "up" so that both collinear channels share the same width wherever they are printed next to each other in the running log. The band header instead ends up in a per-spin file (bands1.txt/bands2.txt), so the padding only leaves a trailing blank before the closing parenthesis. The header now uses " (spin up)" through a local band_spin_tag helper, while the log line keeps make_spin_tag unchanged. --- .../output_files/output-specification.md | 2 +- .../source_io/module_energy/write_bands.cpp | 27 ++++++++++++++++--- source/source_io/test/write_bands_test.cpp | 2 +- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/docs/advanced/output_files/output-specification.md b/docs/advanced/output_files/output-specification.md index e3b1b128777..99d177824dc 100644 --- a/docs/advanced/output_files/output-specification.md +++ b/docs/advanced/output_files/output-specification.md @@ -128,7 +128,7 @@ k-point blocks appended by a relax/md run stay distinguishable: ``` # ABACUS version: v3.11.0-beta11 # Written at 2026-10-20 15:40:45 -# IONIC STEP 1 (spin up ) +# IONIC STEP 1 (spin up) 1 0.00000000 -5.36802595 6.70427084 ... ``` diff --git a/source/source_io/module_energy/write_bands.cpp b/source/source_io/module_energy/write_bands.cpp index ec92f77dc99..86bdc45ecc3 100644 --- a/source/source_io/module_energy/write_bands.cpp +++ b/source/source_io/module_energy/write_bands.cpp @@ -20,6 +20,27 @@ namespace { +/** + * @brief spin-channel label for the band provenance header. + * + * Unlike ModuleIO::make_spin_tag, which pads "up" so that both channels share + * the same width wherever they are printed next to each other, the band header + * goes into a per-spin file (bands1.txt/bands2.txt), so the compact form + * " (spin up)" is used. + * + * @param is spin channel index + * @param nspin number of spins, padding is dropped unless nspin == 2 + * @return " (spin up)", " (spin down)" or an empty string + */ +std::string band_spin_tag(const int is, const int nspin) +{ + if (nspin != 2 || is < 0) + { + return ""; + } + return (is == 0) ? " (spin up)" : " (spin down)"; +} + /** * @brief build the STRU-style provenance header that precedes the k-point * block of one ionic step in a band output file. @@ -71,10 +92,10 @@ void ModuleIO::write_bands(const Input_para& inp, const ModuleBase::matrix& ekb, ss << ".txt"; const std::string eig_file = ss.str(); - const std::string spin_tag = ModuleIO::make_spin_tag(is, nspin0); - ofs_running << " Write eigenvalues" << spin_tag << " to file: " << eig_file << std::endl; + const std::string log_spin_tag = ModuleIO::make_spin_tag(is, nspin0); + ofs_running << " Write eigenvalues" << log_spin_tag << " to file: " << eig_file << std::endl; - const std::string header = build_band_header(istep, spin_tag); + const std::string header = build_band_header(istep, band_spin_tag(is, nspin0)); const double eshift = 0.0; nscf_bands(is, eig_file, diff --git a/source/source_io/test/write_bands_test.cpp b/source/source_io/test/write_bands_test.cpp index 8cf6459a693..79990fc0118 100644 --- a/source/source_io/test/write_bands_test.cpp +++ b/source/source_io/test/write_bands_test.cpp @@ -279,7 +279,7 @@ class BandOutputTest : public testing::TestWithParam const std::string written = "# Written at "; EXPECT_EQ(comments[3 * block + 1].compare(0, written.size(), written), 0) << comments[3 * block + 1]; - const std::string tag = (channels == 2) ? (spin == 0 ? " (spin up )" : " (spin down)") : ""; + const std::string tag = (channels == 2) ? (spin == 0 ? " (spin up)" : " (spin down)") : ""; EXPECT_EQ(comments[3 * block + 2], "# IONIC STEP " + std::to_string(block + 1) + tag); } From 9876bf48e6c1cb410c3c8fdc509fd0a45697b782 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 17:06:37 +0800 Subject: [PATCH 29/61] test(out_app_flag): three-mode unit test coverage for naming and append semantics Add focused unit tests exercising out_app_flag modes 0/1/2 across the filename_output primitive and all major output writers: - test_filename.cpp (new): filename_output three-mode matrix, iter/gamma/nspin - write_dmk_test.cpp: Mode0PerStepFiles / Mode1FixedNameAppend / Mode2SameNamingAsMode0 - test_write_pdos_text.cpp: Mode1FixedNameAppend / Mode2SameNamingAsMode0 - test_cal_dos.cpp: Mode1Appends (two ionic step headers) / Mode2Overwrites - test_print_cell.cpp: STRUAppend (two ATOMIC_SPECIES blocks accumulated) - test_mdcell_reader.cpp: STRUAppendWithFrameComment (# MD step N markers) - write_wfc_nao_test.cpp: OutAppFlagMode2SameNamingAsMode0 (completes 0/1/2) Also sync out_dos CLI description to document the three-mode semantics. Governance: net_delta=-9 (global dependency budget decreased). --- source/source_base/test/CMakeLists.txt | 6 + source/source_base/test/test_filename.cpp | 120 ++++++++++++++++++ .../test/test_mdcell_reader.cpp | 65 ++++++++++ source/source_cell/test/test_print_cell.cpp | 35 +++++ .../module_dm/test/write_dmk_test.cpp | 120 ++++++++++++++++++ .../module_dos/unittests/test_cal_dos.cpp | 86 +++++++++++++ .../unittests/test_write_pdos_text.cpp | 64 ++++++++++ .../module_parameter/read_inp_out.cpp | 5 +- source/source_io/test/write_wfc_nao_test.cpp | 27 ++++ 9 files changed, 526 insertions(+), 2 deletions(-) create mode 100644 source/source_base/test/test_filename.cpp diff --git a/source/source_base/test/CMakeLists.txt b/source/source_base/test/CMakeLists.txt index 16ba949d640..9964cade867 100644 --- a/source/source_base/test/CMakeLists.txt +++ b/source/source_base/test/CMakeLists.txt @@ -280,3 +280,9 @@ if(ENABLE_GOOGLEBENCH) SOURCES perf_sphbes_test.cpp ../math_sphbes.cpp ../timer.cpp ) endif() + +AddTest( + TARGET MODULE_BASE_filename + LIBS parameter + SOURCES test_filename.cpp +) diff --git a/source/source_base/test/test_filename.cpp b/source/source_base/test/test_filename.cpp new file mode 100644 index 00000000000..5bffa1fba8c --- /dev/null +++ b/source/source_base/test/test_filename.cpp @@ -0,0 +1,120 @@ +#ifndef TEST_FILENAME_CPP +#define TEST_FILENAME_CPP + +#include +#include +#include +#include "source_base/module_out/filename.h" + +// Keep local names short; do not pollute. +using ModuleIO::filename_output; + +// --------------------------------------------------------------------------- +// Fixture: minimal ik2iktot mapping for a 2-k-point pool. +// --------------------------------------------------------------------------- +class FilenameOutputTest : public ::testing::Test +{ +protected: + const std::vector ik2iktot = {0, 1}; + const int ik_local = 0; + const int nkstot = 2; + const int nspin = 2; + const std::string dir = "OUT.test/"; +}; + +// --------------------------------------------------------------------------- +// mode 0 = no append -> per-step g{N} suffix included. +// --------------------------------------------------------------------------- +TEST_F(FilenameOutputTest, Mode0IstepAddsG) +{ + // PW wavefunction, binary, k1, spin up (ik0=0 < half_k=1 -> s1). + const std::string fn = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, nspin, nkstot, + /*out_type=*/2, /*out_app_flag=*/0, /*gamma_only=*/false, + /*istep=*/1, /*iter=*/-1); + EXPECT_EQ(fn, "OUT.test/wfk1s1g2_pw.dat"); +} + +// --------------------------------------------------------------------------- +// mode 1 = append -> fixed name, no g suffix even when istep >= 0. +// --------------------------------------------------------------------------- +TEST_F(FilenameOutputTest, Mode1FixedNameNoG) +{ + const std::string fn = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, nspin, nkstot, + /*out_type=*/2, /*out_app_flag=*/1, /*gamma_only=*/false, + /*istep=*/1, /*iter=*/-1); + EXPECT_EQ(fn, "OUT.test/wfk1s1_pw.dat"); +} + +// --------------------------------------------------------------------------- +// mode 2 = write to subdirectory -> same naming as mode 0 (g suffix kept). +// The directory routing is handled by the caller (set_global_dir), so +// filename_output itself treats modes 0 and 2 identically. +// --------------------------------------------------------------------------- +TEST_F(FilenameOutputTest, Mode2SameNamingAsMode0) +{ + const std::string fn = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, nspin, nkstot, + /*out_type=*/2, /*out_app_flag=*/2, /*gamma_only=*/false, + /*istep=*/1, /*iter=*/-1); + EXPECT_EQ(fn, "OUT.test/wfk1s1g2_pw.dat"); +} + +// --------------------------------------------------------------------------- +// istep < 0 (SCF single-point) -> no g suffix in any mode. +// --------------------------------------------------------------------------- +TEST_F(FilenameOutputTest, SinglePointNoG) +{ + const std::string fn = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, nspin, nkstot, + /*out_type=*/2, /*out_app_flag=*/0, /*gamma_only=*/false, + /*istep=*/-1, /*iter=*/-1); + EXPECT_EQ(fn, "OUT.test/wfk1s1_pw.dat"); +} + +// --------------------------------------------------------------------------- +// iter >= 0 adds e{N} in modes 0/2, but is suppressed in mode 1 too. +// --------------------------------------------------------------------------- +TEST_F(FilenameOutputTest, IterIndexRespectsAppendFlag) +{ + // mode 0: both g and e present. + const std::string fn_mode0 = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, nspin, nkstot, + /*out_type=*/1, /*out_app_flag=*/0, /*gamma_only=*/false, + /*istep=*/1, /*iter=*/3); + EXPECT_EQ(fn_mode0, "OUT.test/wfk1s1g2e3_pw.txt"); + + // mode 1: neither g nor e. + const std::string fn_mode1 = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, nspin, nkstot, + /*out_type=*/1, /*out_app_flag=*/1, /*gamma_only=*/false, + /*istep=*/1, /*iter=*/3); + EXPECT_EQ(fn_mode1, "OUT.test/wfk1s1_pw.txt"); +} + +// --------------------------------------------------------------------------- +// gamma_only suppresses the k-point label for non-hk/sk/tk properties. +// --------------------------------------------------------------------------- +TEST_F(FilenameOutputTest, GammaOnlyNoKLabel) +{ + const std::string fn = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, nspin, nkstot, + /*out_type=*/2, /*out_app_flag=*/1, /*gamma_only=*/true, + /*istep=*/-1, /*iter=*/-1); + EXPECT_EQ(fn, "OUT.test/wfs1_pw.dat"); +} + +// --------------------------------------------------------------------------- +// nspin=4 (noncollinear SOC) -> spin block is s4, no half-k split. +// --------------------------------------------------------------------------- +TEST_F(FilenameOutputTest, NoncollinearSpin4) +{ + const std::string fn = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, /*nspin=*/4, nkstot, + /*out_type=*/2, /*out_app_flag=*/2, /*gamma_only=*/false, + /*istep=*/0, /*iter=*/-1); + EXPECT_EQ(fn, "OUT.test/wfk1s4g1_pw.dat"); +} + +#endif // TEST_FILENAME_CPP diff --git a/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp b/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp index ac191c2d4cf..6a2906838b9 100644 --- a/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp +++ b/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp @@ -319,6 +319,71 @@ TEST(MDCellReaderTest, RestartStruPreservesAtomRecordsAcrossRanks) if (rank == 0) std::remove(output_file.c_str()); } +// --------------------------------------------------------------------------- +// mdcell::print_stru_file append + frame_comment. +// --------------------------------------------------------------------------- +TEST(MDCellReaderTest, STRUAppendWithFrameComment) +{ + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + std::vector owned_atoms; + owned_atoms.push_back(LocalAtom(ModuleBase::Vector3(0.0, 0.0, 0.0), + ModuleBase::Vector3(0.0, 0.0, 0.0), + ModuleBase::Vector3(0.0, 0.0, 0.0), + ModuleBase::Vector3(0.0, 0.0, 0.0), + ModuleBase::Vector3(1, 0, 1), + 1.0, + 0, + 0, + rank)); + + ModuleBase::Matrix3 lattice = make_lattice(); + MDCell mdcell; + mdcell.initialize_from_owned_atoms(lattice, + lattice.Inverse(), + 1.0, + 1.0, + 1, + owned_atoms, + std::vector{"A"}, + std::vector{1.0}, + std::vector{1}, + 0.0, + ModuleBase::world_comm_domain()); + StruMeta metadata; + metadata.species.resize(1); + const std::string output_file = "mdcell_append_test.STRU"; + + // First frame: append=false, frame comment set. + mdcell::print_stru_file(mdcell, metadata, output_file, + "# MD step 1\n\n", /*append=*/false); + // Second frame: append=true with a different comment. + mdcell::print_stru_file(mdcell, metadata, output_file, + "# MD step 2\n\n", /*append=*/true); + + if (rank == 0) + { + std::ifstream ifs(output_file.c_str()); + std::string str((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + // Both frame markers must be present. + EXPECT_THAT(str, testing::HasSubstr("# MD step 1")); + EXPECT_THAT(str, testing::HasSubstr("# MD step 2")); + // Two STRU blocks -> two ATOMIC_SPECIES markers. + int occurrences = 0; + size_t pos = 0; + while ((pos = str.find("ATOMIC_SPECIES", pos)) != std::string::npos) + { + ++occurrences; + pos += 14; + } + EXPECT_EQ(occurrences, 2); + std::remove(output_file.c_str()); + } +} + int main(int argc, char** argv) { MPI_Init(&argc, &argv); diff --git a/source/source_cell/test/test_print_cell.cpp b/source/source_cell/test/test_print_cell.cpp index 9bcf7123f33..c4ff6e08ae7 100644 --- a/source/source_cell/test/test_print_cell.cpp +++ b/source/source_cell/test/test_print_cell.cpp @@ -390,3 +390,38 @@ TEST_F(PrintCellTest, PrintTauCartesian) // remove the file remove("print_tau_Cartesian"); } + +// --------------------------------------------------------------------------- +// STRU append flag: append=true accumulates successive structures in one file. +// --------------------------------------------------------------------------- +TEST_F(PrintCellTest, STRUAppend) +{ + UcellTestPrepare utp = UcellTestLib["C1H2-Index"]; + ucell = utp.SetUcellInfo(); + const std::string fn = "STRU_append_test"; + + // First call: append=false -> create/truncate. + unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, + fn, "", 1, false, false, false, false, 0, ModuleBase::matrix(), false, + /*append=*/false); + // Second call: append=true -> accumulate into same file. + unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, + fn, "", 1, false, false, false, false, 0, ModuleBase::matrix(), false, + /*append=*/true); + + std::ifstream ifs(fn); + std::string str((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + // Two STRU blocks -> two ATOMIC_SPECIES markers. + int occurrences = 0; + size_t pos = 0; + while ((pos = str.find("ATOMIC_SPECIES", pos)) != std::string::npos) + { + ++occurrences; + pos += 14; + } + EXPECT_EQ(occurrences, 2); + + remove(fn.c_str()); +} diff --git a/source/source_io/module_dm/test/write_dmk_test.cpp b/source/source_io/module_dm/test/write_dmk_test.cpp index ab5e5bfda9b..d2d1dea45bb 100644 --- a/source/source_io/module_dm/test/write_dmk_test.cpp +++ b/source/source_io/module_dm/test/write_dmk_test.cpp @@ -235,6 +235,126 @@ TEST(DMKTest,WriteDMK) { }; +// --------------------------------------------------------------------------- +// out_app_flag = 0: per-step files with g suffix, overwrite each call. +// --------------------------------------------------------------------------- +TEST(DMKTest, Mode0PerStepFiles) { + UnitCell* ucell; + UcellTestPrepare utp = UcellTestLib["Si"]; + ucell = utp.SetUcellInfo(); + + int nspin = 2; + int nk = 1; + int nlocal = 20; + std::vector> dmk; + std::vector efs; + Parallel_2D pv; + init_pv(nlocal, pv); + gen_dmk(dmk, efs, nspin, nk, nlocal, pv); + + const std::string out_dir = "./"; + const int out_app_flag = 0; + std::ofstream ofs_log; + + // Two different isteps -> two differently-named files. + ModuleIO::write_dmk(dmk, 1, efs, ucell, pv, out_dir, /*istep=*/0, out_app_flag, ofs_log); + ModuleIO::write_dmk(dmk, 1, efs, ucell, pv, out_dir, /*istep=*/1, out_app_flag, ofs_log); + + if (GlobalV::MY_RANK == 0) + { + // First call: g1 suffix (istep 0 -> istep+1). + std::ifstream ifs("dms1g1_nao.txt"); + EXPECT_TRUE(ifs.good()); + ifs.close(); + // Second call: g2 suffix. + ifs.open("dms1g2_nao.txt"); + EXPECT_TRUE(ifs.good()); + ifs.close(); + // Fixed-name file must NOT exist. + ifs.open("dms1_nao.txt"); + EXPECT_FALSE(ifs.good()); + remove("dms1g1_nao.txt"); + remove("dms1g2_nao.txt"); + } + delete ucell; +} + +// --------------------------------------------------------------------------- +// out_app_flag = 1: fixed filename, multiple calls append successive records. +// --------------------------------------------------------------------------- +TEST(DMKTest, Mode1FixedNameAppend) { + UnitCell* ucell; + UcellTestPrepare utp = UcellTestLib["Si"]; + ucell = utp.SetUcellInfo(); + + int nspin = 2; + int nk = 1; + int nlocal = 20; + std::vector> dmk; + std::vector efs; + Parallel_2D pv; + init_pv(nlocal, pv); + gen_dmk(dmk, efs, nspin, nk, nlocal, pv); + + const std::string out_dir = "./"; + const int out_app_flag = 1; + std::ofstream ofs_log; + + // Two ionic steps -> both write to dms1_nao.txt. + ModuleIO::write_dmk(dmk, 1, efs, ucell, pv, out_dir, /*istep=*/0, out_app_flag, ofs_log); + ModuleIO::write_dmk(dmk, 1, efs, ucell, pv, out_dir, /*istep=*/1, out_app_flag, ofs_log); + + if (GlobalV::MY_RANK == 0) + { + std::ifstream ifs("dms1_nao.txt"); + std::string str((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + // Two "Ionic Step" headers present -> two appended records. + EXPECT_THAT(str, testing::HasSubstr("Ionic Step: 1")); + EXPECT_THAT(str, testing::HasSubstr("Ionic Step: 2")); + // g-suffixed file must NOT exist. + ifs.open("dms1g1_nao.txt"); + EXPECT_FALSE(ifs.good()); + remove("dms1_nao.txt"); + } + delete ucell; +} + +// --------------------------------------------------------------------------- +// out_app_flag = 2: same naming as mode 0 (g suffix kept); directory routing +// is handled by set_global_dir and is covered by io_sys_var_test. +// --------------------------------------------------------------------------- +TEST(DMKTest, Mode2SameNamingAsMode0) { + UnitCell* ucell; + UcellTestPrepare utp = UcellTestLib["Si"]; + ucell = utp.SetUcellInfo(); + + int nspin = 2; + int nk = 1; + int nlocal = 20; + std::vector> dmk; + std::vector efs; + Parallel_2D pv; + init_pv(nlocal, pv); + gen_dmk(dmk, efs, nspin, nk, nlocal, pv); + + const std::string out_dir = "./"; + const int out_app_flag = 2; + std::ofstream ofs_log; + + ModuleIO::write_dmk(dmk, 1, efs, ucell, pv, out_dir, /*istep=*/2, out_app_flag, ofs_log); + + if (GlobalV::MY_RANK == 0) + { + std::ifstream ifs("dms1g3_nao.txt"); + EXPECT_TRUE(ifs.good()); + ifs.close(); + remove("dms1g3_nao.txt"); + } + delete ucell; +} + /* // no function in the main code calls read_dmk??? mohan note 2025-05-25 TEST(DMKTest, ReadDMK) { diff --git a/source/source_io/module_dos/unittests/test_cal_dos.cpp b/source/source_io/module_dos/unittests/test_cal_dos.cpp index 3fe5d410600..82c99219112 100644 --- a/source/source_io/module_dos/unittests/test_cal_dos.cpp +++ b/source/source_io/module_dos/unittests/test_cal_dos.cpp @@ -207,6 +207,92 @@ TEST_F(DosTest,DosW2) #endif } +// --------------------------------------------------------------------------- +// out_app_flag = 1: cal_dos appends successive records to the same file. +// --------------------------------------------------------------------------- +TEST_F(DosTest, Mode1Appends) +{ + const std::string fname = "doss1_mode1.txt"; + DosPrepare dosp(0, fname, 0.005, 18, -6, 0.07, + dos_test_data::nks, dos_test_data::nkstot, dos_test_data::nbands); + dosp.set_isk(); + dosp.set_wk(); + dosp.set_istate_info(); + + const int out_app_flag = 1; + const int bndpar = 1; + + // First call: istep=0 -> "1 # ionic step". + ModuleIO::cal_dos(dosp.is, dosp.fa, dosp.de_ev, dosp.emax_ev, dosp.emin_ev, + dosp.bcoeff, dosp.nks, dosp.nkstot, dosp.wk, dosp.isk, + dosp.nbands, dosp.ekb, dosp.wg, + /*istep=*/0, out_app_flag, bndpar); + // Second call: istep=1 -> "2 # ionic step", appended. + ModuleIO::cal_dos(dosp.is, dosp.fa, dosp.de_ev, dosp.emax_ev, dosp.emin_ev, + dosp.bcoeff, dosp.nks, dosp.nkstot, dosp.wk, dosp.isk, + dosp.nbands, dosp.ekb, dosp.wg, + /*istep=*/1, out_app_flag, bndpar); + +#ifdef __MPI + if (GlobalV::MY_RANK == 0) +#endif + { + std::ifstream ifs(fname); + std::string str((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + // Two ionic step headers present. + EXPECT_THAT(str, testing::HasSubstr("1 # ionic step")); + EXPECT_THAT(str, testing::HasSubstr("2 # ionic step")); + remove(fname.c_str()); + } +} + +// --------------------------------------------------------------------------- +// out_app_flag = 2: same overwrite behaviour as mode 0 (file is truncated each +// call; naming is handled by the caller write_dos_* which adds g{N}). +// --------------------------------------------------------------------------- +TEST_F(DosTest, Mode2Overwrites) +{ + const std::string fname = "doss1_mode2.txt"; + DosPrepare dosp(0, fname, 0.005, 18, -6, 0.07, + dos_test_data::nks, dos_test_data::nkstot, dos_test_data::nbands); + dosp.set_isk(); + dosp.set_wk(); + dosp.set_istate_info(); + + const int out_app_flag = 2; + const int bndpar = 1; + + // First call writes istep 1, second overwrites with istep 2. + ModuleIO::cal_dos(dosp.is, dosp.fa, dosp.de_ev, dosp.emax_ev, dosp.emin_ev, + dosp.bcoeff, dosp.nks, dosp.nkstot, dosp.wk, dosp.isk, + dosp.nbands, dosp.ekb, dosp.wg, + /*istep=*/0, out_app_flag, bndpar); + ModuleIO::cal_dos(dosp.is, dosp.fa, dosp.de_ev, dosp.emax_ev, dosp.emin_ev, + dosp.bcoeff, dosp.nks, dosp.nkstot, dosp.wk, dosp.isk, + dosp.nbands, dosp.ekb, dosp.wg, + /*istep=*/1, out_app_flag, bndpar); + +#ifdef __MPI + if (GlobalV::MY_RANK == 0) +#endif + { + std::ifstream ifs(fname); + std::string str((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + // Only the last ionic step header must be present. + EXPECT_THAT(str, testing::HasSubstr("2 # ionic step")); + // A simple count check: "1 # ionic step" must appear only inside + // the DOS grid lines as a value, not as a header. Check that the + // header for step 1 is absent by verifying it is NOT found as a + // standalone header line. + EXPECT_THAT(str, testing::Not(testing::HasSubstr("\n1 # ionic step"))); + remove(fname.c_str()); + } +} + #ifdef __MPI int main(int argc, char **argv) { diff --git a/source/source_io/module_dos/unittests/test_write_pdos_text.cpp b/source/source_io/module_dos/unittests/test_write_pdos_text.cpp index 52571d2888f..69a93c8b92b 100644 --- a/source/source_io/module_dos/unittests/test_write_pdos_text.cpp +++ b/source/source_io/module_dos/unittests/test_write_pdos_text.cpp @@ -177,3 +177,67 @@ TEST_F(WritePdosTextTest, SpinPolarized) GlobalV::ofs_running.close(); remove("test_write_pdos_text.log"); } + +// --------------------------------------------------------------------------- +// out_app_flag = 1: fixed-name files, successive calls append records. +// --------------------------------------------------------------------------- +TEST_F(WritePdosTextTest, Mode1FixedNameAppend) +{ + GlobalV::ofs_running.open("test_write_pdos_text.log"); + + const int nspin = 1; + const int nlocal = 4; + const int npoints = 2; + const double emin = 0.0; + const double dos_edelta_ev = 1.0; + const int out_app_flag = 1; + + ModuleBase::matrix pdos(nlocal, npoints); + pdos.zero_out(); + + // First call: istep=0, mode 1 -> fixed name pdoss1.txt. + ModuleIO::write_pdos_text(*ucell, &pdos, nspin, nlocal, npoints, + emin, dos_edelta_ev, "./", /*istep=*/0, out_app_flag); + // Second call: istep=1, mode 1 -> append to same file. + ModuleIO::write_pdos_text(*ucell, &pdos, nspin, nlocal, npoints, + emin, dos_edelta_ev, "./", /*istep=*/1, out_app_flag); + + const std::string str = read_file("./pdoss1.txt"); + // Two istep headers present -> two appended records. + EXPECT_THAT(str, testing::HasSubstr("# istep: 1")); + EXPECT_THAT(str, testing::HasSubstr("# istep: 2")); + // g-suffixed file must NOT exist. + std::ifstream ifs("./pdoss1g1.txt"); + EXPECT_FALSE(ifs.good()); + + remove("./pdoss1.txt"); + GlobalV::ofs_running.close(); + remove("test_write_pdos_text.log"); +} + +// --------------------------------------------------------------------------- +// out_app_flag = 2: same naming as mode 0 (g suffix kept). +// --------------------------------------------------------------------------- +TEST_F(WritePdosTextTest, Mode2SameNamingAsMode0) +{ + GlobalV::ofs_running.open("test_write_pdos_text.log"); + + const int nspin = 1; + const int nlocal = 4; + const int npoints = 2; + const double emin = 0.0; + const double dos_edelta_ev = 1.0; + const int out_app_flag = 2; + ModuleBase::matrix pdos(nlocal, npoints); + pdos.zero_out(); + + ModuleIO::write_pdos_text(*ucell, &pdos, nspin, nlocal, npoints, + emin, dos_edelta_ev, "./", 3, out_app_flag); + + const std::string str = read_file("./pdoss1g4.txt"); + EXPECT_THAT(str, testing::HasSubstr("# istep: 4")); + + remove("./pdoss1g4.txt"); + GlobalV::ofs_running.close(); + remove("test_write_pdos_text.log"); +} diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index f869afb8f4f..c05e113dcc6 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -341,8 +341,9 @@ Also controled by out_freq_ion and out_app_flag. item.description = R"(Whether to output the density of states (DOS) and projected density of states (PDOS). For more information, refer to the dos.md. * 0: no output * 1: output the density of states (DOS) and projected density of states (PDOS, LCAO only) - * nspin=1 or 4: doss1g{geom}.txt and pdoss1g{geom}.txt (PDOS is LCAO only), where geom is the geometry index when cell changes or ions move. PW and LCAO calculations use the same naming. - * nspin=2: doss1/doss2 and pdoss1/pdoss2 files for two spin channels. + * With out_app_flag = 0 or 2: per-step files are named `doss{s}g{geom}.txt` and `pdoss{s}g{geom}.txt` where `{geom}` is the one-based geometry index (e.g. `doss1g3.txt` for the third ionic step). + * With out_app_flag = 1: fixed-name files (`doss{s}.txt`, `pdoss{s}.txt`) are reused; the first written step truncates them and later steps append one record per step (prefixed with a `# ionic step N` header, one-based). + * nspin=1 or 4: one spin channel (`s1`). nspin=2: two channels (`s1`, `s2`). PW and LCAO use the same naming. Note: values 2 and 3 are no longer supported. Setting out_dos to 2 or 3 will raise an error.)"; item.default_value = "0"; item.unit = ""; diff --git a/source/source_io/test/write_wfc_nao_test.cpp b/source/source_io/test/write_wfc_nao_test.cpp index 8d7bc6b6a48..e383ab9e1c8 100644 --- a/source/source_io/test/write_wfc_nao_test.cpp +++ b/source/source_io/test/write_wfc_nao_test.cpp @@ -102,6 +102,33 @@ TEST(GenWfcLcaoFnameTest, OutTypeInvalid) EXPECT_EQ(result, expected_output); } +// --------------------------------------------------------------------------- +// out_app_flag = 2 (write to subdirectory) keeps the g{N} suffix, identical +// to mode 0 at the filename level; directory routing is handled by the caller. +// --------------------------------------------------------------------------- +TEST(GenWfcLcaoFnameTest, OutAppFlagMode2SameNamingAsMode0) +{ + const std::string directory = ""; + const std::string property = "wf"; + const std::string basis = "nao"; + const int ik = 1; + const std::vector ik2iktot = {0, 1}; + const int nspin = 2; + const int nkstot = 2; + const int out_type = 2; + const int out_app_flag = 2; + const bool gamma_only = true; + const int istep = 2; + + std::string result = ModuleIO::filename_output( + directory, property, basis, ik, ik2iktot, nspin, + nkstot, out_type, out_app_flag, gamma_only, istep); + + // Same g-suffixed naming as mode 0. + std::string expected_output = "wfs2g3_nao.dat"; + EXPECT_EQ(result, expected_output); +} + template void read_bin(const std::string& name, std::vector& data) { From 40c8bcdfaffacb6cd3ce94867f35f305c866b66b Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Tue, 20 Oct 2026 17:28:17 +0800 Subject: [PATCH 30/61] fix(test): add missing link libs and K_Vectors/precision args - CMakeLists: MODULE_BASE_filename needs both `base` (filename.cpp) and `device` (kernel ops referenced by base) in its LIBS - test_mdcell_reader: include gmock/gmock.h for HasSubstr matcher - write_dmk_test: new three-mode tests were missing the K_Vectors construction and the precision parameter (third positional arg); sync with the WriteDMK precedent in the same file Build verified by developer (make -j 30). --- source/source_base/test/CMakeLists.txt | 2 +- .../test/test_mdcell_reader.cpp | 1 + .../module_dm/test/write_dmk_test.cpp | 52 +++++++++++++++++-- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/source/source_base/test/CMakeLists.txt b/source/source_base/test/CMakeLists.txt index 9964cade867..7a48043bef6 100644 --- a/source/source_base/test/CMakeLists.txt +++ b/source/source_base/test/CMakeLists.txt @@ -283,6 +283,6 @@ endif() AddTest( TARGET MODULE_BASE_filename - LIBS parameter + LIBS parameter base device SOURCES test_filename.cpp ) diff --git a/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp b/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp index 6a2906838b9..e23707c7aab 100644 --- a/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp +++ b/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp @@ -1,4 +1,5 @@ #include "source_cell/domain_decomposition.h" +#include #include #include "source_cell/mdcell_reader.h" diff --git a/source/source_io/module_dm/test/write_dmk_test.cpp b/source/source_io/module_dm/test/write_dmk_test.cpp index d2d1dea45bb..15dce489046 100644 --- a/source/source_io/module_dm/test/write_dmk_test.cpp +++ b/source/source_io/module_dm/test/write_dmk_test.cpp @@ -252,13 +252,27 @@ TEST(DMKTest, Mode0PerStepFiles) { init_pv(nlocal, pv); gen_dmk(dmk, efs, nspin, nk, nlocal, pv); + K_Vectors kv; + kv.set_nkstot(1); + kv.set_nkstot_nospin(1); + kv.set_nks(1); + kv.set_spin_mult(2); + kv.kvec_c.resize(1); + kv.kvec_c[0].x = 0.0; + kv.kvec_c[0].y = 0.0; + kv.kvec_c[0].z = 0.0; + kv.wk.resize(1); + kv.wk[0] = 1.0; + kv.isk.resize(1); + kv.isk[0] = 0; + const std::string out_dir = "./"; const int out_app_flag = 0; std::ofstream ofs_log; // Two different isteps -> two differently-named files. - ModuleIO::write_dmk(dmk, 1, efs, ucell, pv, out_dir, /*istep=*/0, out_app_flag, ofs_log); - ModuleIO::write_dmk(dmk, 1, efs, ucell, pv, out_dir, /*istep=*/1, out_app_flag, ofs_log); + ModuleIO::write_dmk(dmk, kv, /*precision=*/3, efs, ucell, pv, out_dir, /*istep=*/0, out_app_flag, ofs_log); + ModuleIO::write_dmk(dmk, kv, /*precision=*/3, efs, ucell, pv, out_dir, /*istep=*/1, out_app_flag, ofs_log); if (GlobalV::MY_RANK == 0) { @@ -296,13 +310,27 @@ TEST(DMKTest, Mode1FixedNameAppend) { init_pv(nlocal, pv); gen_dmk(dmk, efs, nspin, nk, nlocal, pv); + K_Vectors kv; + kv.set_nkstot(1); + kv.set_nkstot_nospin(1); + kv.set_nks(1); + kv.set_spin_mult(2); + kv.kvec_c.resize(1); + kv.kvec_c[0].x = 0.0; + kv.kvec_c[0].y = 0.0; + kv.kvec_c[0].z = 0.0; + kv.wk.resize(1); + kv.wk[0] = 1.0; + kv.isk.resize(1); + kv.isk[0] = 0; + const std::string out_dir = "./"; const int out_app_flag = 1; std::ofstream ofs_log; // Two ionic steps -> both write to dms1_nao.txt. - ModuleIO::write_dmk(dmk, 1, efs, ucell, pv, out_dir, /*istep=*/0, out_app_flag, ofs_log); - ModuleIO::write_dmk(dmk, 1, efs, ucell, pv, out_dir, /*istep=*/1, out_app_flag, ofs_log); + ModuleIO::write_dmk(dmk, kv, /*precision=*/3, efs, ucell, pv, out_dir, /*istep=*/0, out_app_flag, ofs_log); + ModuleIO::write_dmk(dmk, kv, /*precision=*/3, efs, ucell, pv, out_dir, /*istep=*/1, out_app_flag, ofs_log); if (GlobalV::MY_RANK == 0) { @@ -339,11 +367,25 @@ TEST(DMKTest, Mode2SameNamingAsMode0) { init_pv(nlocal, pv); gen_dmk(dmk, efs, nspin, nk, nlocal, pv); + K_Vectors kv; + kv.set_nkstot(1); + kv.set_nkstot_nospin(1); + kv.set_nks(1); + kv.set_spin_mult(2); + kv.kvec_c.resize(1); + kv.kvec_c[0].x = 0.0; + kv.kvec_c[0].y = 0.0; + kv.kvec_c[0].z = 0.0; + kv.wk.resize(1); + kv.wk[0] = 1.0; + kv.isk.resize(1); + kv.isk[0] = 0; + const std::string out_dir = "./"; const int out_app_flag = 2; std::ofstream ofs_log; - ModuleIO::write_dmk(dmk, 1, efs, ucell, pv, out_dir, /*istep=*/2, out_app_flag, ofs_log); + ModuleIO::write_dmk(dmk, kv, /*precision=*/3, efs, ucell, pv, out_dir, /*istep=*/2, out_app_flag, ofs_log); if (GlobalV::MY_RANK == 0) { From d5781b1f0622e5e6b77a5413d6f9ec98eb89ecd2 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 21 Oct 2026 15:14:22 +0800 Subject: [PATCH 31/61] test(io): align step assertions and fix DM(k) test crashes Follow-up to a658239dc which moved the sparse/CSR STEP field to the one-based convention (governance rule 22) without updating the tests: - hs_sparse_io / dhs_sparse_writer / hsr_writer: expect istep+1 in the text STEP header and in the native binary step field - write_dmk modes 0/1/2: resize kvec_d before calling write_dmk, which dereferences kv.kvec_d[ik] and segfaulted on the empty vector - write_dmk mode 1: match the actual " --- Ionic Step N ---" header Verified: the four test targets pass, including mpirun -np 2. --- .../source_io/module_dm/test/write_dmk_test.cpp | 16 ++++++++++++++-- .../unittests/test_dhs_sparse_writer.cpp | 6 ++++-- .../module_hs/unittests/test_hs_sparse_io.cpp | 12 ++++++++---- .../module_hs/unittests/test_hsr_writer.cpp | 11 +++++++---- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/source/source_io/module_dm/test/write_dmk_test.cpp b/source/source_io/module_dm/test/write_dmk_test.cpp index 15dce489046..97a440add57 100644 --- a/source/source_io/module_dm/test/write_dmk_test.cpp +++ b/source/source_io/module_dm/test/write_dmk_test.cpp @@ -261,6 +261,10 @@ TEST(DMKTest, Mode0PerStepFiles) { kv.kvec_c[0].x = 0.0; kv.kvec_c[0].y = 0.0; kv.kvec_c[0].z = 0.0; + kv.kvec_d.resize(1); + kv.kvec_d[0].x = 0.0; + kv.kvec_d[0].y = 0.0; + kv.kvec_d[0].z = 0.0; kv.wk.resize(1); kv.wk[0] = 1.0; kv.isk.resize(1); @@ -319,6 +323,10 @@ TEST(DMKTest, Mode1FixedNameAppend) { kv.kvec_c[0].x = 0.0; kv.kvec_c[0].y = 0.0; kv.kvec_c[0].z = 0.0; + kv.kvec_d.resize(1); + kv.kvec_d[0].x = 0.0; + kv.kvec_d[0].y = 0.0; + kv.kvec_d[0].z = 0.0; kv.wk.resize(1); kv.wk[0] = 1.0; kv.isk.resize(1); @@ -339,8 +347,8 @@ TEST(DMKTest, Mode1FixedNameAppend) { std::istreambuf_iterator()); ifs.close(); // Two "Ionic Step" headers present -> two appended records. - EXPECT_THAT(str, testing::HasSubstr("Ionic Step: 1")); - EXPECT_THAT(str, testing::HasSubstr("Ionic Step: 2")); + EXPECT_THAT(str, testing::HasSubstr(" --- Ionic Step 1 ---")); + EXPECT_THAT(str, testing::HasSubstr(" --- Ionic Step 2 ---")); // g-suffixed file must NOT exist. ifs.open("dms1g1_nao.txt"); EXPECT_FALSE(ifs.good()); @@ -376,6 +384,10 @@ TEST(DMKTest, Mode2SameNamingAsMode0) { kv.kvec_c[0].x = 0.0; kv.kvec_c[0].y = 0.0; kv.kvec_c[0].z = 0.0; + kv.kvec_d.resize(1); + kv.kvec_d[0].x = 0.0; + kv.kvec_d[0].y = 0.0; + kv.kvec_d[0].z = 0.0; kv.wk.resize(1); kv.wk[0] = 1.0; kv.isk.resize(1); diff --git a/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp b/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp index c667523e075..085f92a25c3 100644 --- a/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp +++ b/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp @@ -44,7 +44,8 @@ TEST(DhsSparseWriter, TextCountsOnlyValuesAboveThreshold) const std::vector lines = read_lines("dhrxs1g6_nao.csr"); ASSERT_GE(lines.size(), 7); - EXPECT_EQ(lines[0], "STEP: 5"); + // istep = 5 is the sixth ionic step, printed one-based as 6 + EXPECT_EQ(lines[0], "STEP: 6"); EXPECT_EQ(lines[1], "Matrix Dimension of dHx(R): 2"); EXPECT_EQ(lines[2], "Matrix number of dHx(R): 1"); EXPECT_EQ(lines[3], "0 0 0 2"); @@ -95,7 +96,8 @@ TEST(DhsSparseWriter, BinaryCountsOnlyValuesAboveThreshold) std::ifstream ifs("dhrxs1g7_nao.csr", std::ios::binary); ASSERT_TRUE(ifs.is_open()); - EXPECT_EQ(read_binary_value(ifs), 6); + // istep = 6 -> one-based step 7 in the binary header + EXPECT_EQ(read_binary_value(ifs), 7); EXPECT_EQ(read_binary_value(ifs), 2); EXPECT_EQ(read_binary_value(ifs), 1); EXPECT_EQ(read_binary_value(ifs), 0); diff --git a/source/source_io/module_hs/unittests/test_hs_sparse_io.cpp b/source/source_io/module_hs/unittests/test_hs_sparse_io.cpp index d3bd9fb8495..41544d286d2 100644 --- a/source/source_io/module_hs/unittests/test_hs_sparse_io.cpp +++ b/source/source_io/module_hs/unittests/test_hs_sparse_io.cpp @@ -41,7 +41,8 @@ TEST(HsSparseIo, SparseHeaderKeepsStepStyle) ModuleIO::save_sparse(sparse_matrix, all_R_coor, pv, options); const std::string output = read_file(filename); - EXPECT_TRUE(starts_with(output, "STEP: 0\n")); + // istep = 0 is the first ionic step, printed one-based as 1 + EXPECT_TRUE(starts_with(output, "STEP: 1\n")); EXPECT_THAT(output, testing::HasSubstr("Matrix Dimension of S(R): 2\n")); EXPECT_THAT(output, testing::HasSubstr("Matrix number of S(R): 1\n")); EXPECT_THAT(output, testing::HasSubstr("0 0 0 2\n")); @@ -77,7 +78,8 @@ TEST(HsSparseIo, SparseTextCountsOnlyValuesAboveThreshold) const std::vector lines = read_lines(filename); ASSERT_GE(lines.size(), 6); - EXPECT_EQ(lines[0], "STEP: 2"); + // istep = 2 is the third ionic step, printed one-based as 3 + EXPECT_EQ(lines[0], "STEP: 3"); EXPECT_EQ(lines[1], "Matrix Dimension of S(R): 2"); EXPECT_EQ(lines[2], "Matrix number of S(R): 1"); EXPECT_EQ(lines[3], "0 0 0 2"); @@ -137,8 +139,9 @@ TEST(HsSparseIo, SparseBinaryHeaderWritesConcreteStep) options.reduce = false; ModuleIO::save_sparse(sparse_matrix, all_R_coor, pv, options); + // istep = 3 -> one-based step 4 in the binary header const std::vector header_and_r = read_binary_ints(filename, 7); - EXPECT_THAT(header_and_r, testing::ElementsAre(3, 2, 1, 0, 0, 0, 2)); + EXPECT_THAT(header_and_r, testing::ElementsAre(4, 2, 1, 0, 0, 0, 2)); std::remove(filename.c_str()); } @@ -171,7 +174,8 @@ TEST(HsSparseIo, SparseBinaryCountsOnlyValuesAboveThreshold) std::ifstream ifs(filename.c_str(), std::ios::binary); ASSERT_TRUE(ifs.is_open()); - EXPECT_EQ(read_binary_value(ifs), 4); + // istep = 4 -> one-based step 5 in the binary header + EXPECT_EQ(read_binary_value(ifs), 5); EXPECT_EQ(read_binary_value(ifs), 2); EXPECT_EQ(read_binary_value(ifs), 1); EXPECT_EQ(read_binary_value(ifs), 0); diff --git a/source/source_io/module_hs/unittests/test_hsr_writer.cpp b/source/source_io/module_hs/unittests/test_hsr_writer.cpp index a5f86e7c74e..d660e852f04 100644 --- a/source/source_io/module_hs/unittests/test_hsr_writer.cpp +++ b/source/source_io/module_hs/unittests/test_hsr_writer.cpp @@ -300,7 +300,8 @@ TEST(HsrWriterIo, HContainerBinaryWritesSortedAndEmptyRBlocks) std::ifstream ifs(filename.c_str(), std::ios::binary); ASSERT_TRUE(ifs.is_open()); const NativeDoubleRecord record = read_native_double_record(ifs); - EXPECT_EQ(record.step, 0); + // istep = -1 (single-point SCF) maps to one-based step 1 + EXPECT_EQ(record.step, 1); EXPECT_EQ(record.nbasis, 2); ASSERT_EQ(record.blocks.size(), 2); EXPECT_EQ(record.blocks[0].rx, -1); @@ -340,7 +341,8 @@ TEST(HsrWriterIo, HContainerBinaryWritesComplexValuesAsDoublePairs) std::ifstream ifs(filename.c_str(), std::ios::binary); ASSERT_TRUE(ifs.is_open()); - EXPECT_EQ(read_binary_value(ifs), 4); + // istep = 4 -> one-based step 5 in the binary header + EXPECT_EQ(read_binary_value(ifs), 5); EXPECT_EQ(read_binary_value(ifs), 2); EXPECT_EQ(read_binary_value(ifs), 1); EXPECT_EQ(read_binary_value(ifs), 0); @@ -377,15 +379,16 @@ TEST(HsrWriterIo, HContainerBinaryAppendsAndCanOverwrite) std::ifstream appended(filename.c_str(), std::ios::binary); ASSERT_TRUE(appended.is_open()); - EXPECT_EQ(read_native_double_record(appended).step, 0); + // istep 0/1/2 -> one-based steps 1/2/3 EXPECT_EQ(read_native_double_record(appended).step, 1); + EXPECT_EQ(read_native_double_record(appended).step, 2); EXPECT_EQ(appended.peek(), std::ifstream::traits_type::eof()); appended.close(); ModuleIO::write_hcontainer_csr_binary(filename, &matrix, 2, false); std::ifstream replaced(filename.c_str(), std::ios::binary); ASSERT_TRUE(replaced.is_open()); - EXPECT_EQ(read_native_double_record(replaced).step, 2); + EXPECT_EQ(read_native_double_record(replaced).step, 3); EXPECT_EQ(replaced.peek(), std::ifstream::traits_type::eof()); std::remove(filename.c_str()); From 5ac2adf795cd80e53403b3cc82c8a92e25ab0cba Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 21 Oct 2026 15:32:03 +0800 Subject: [PATCH 32/61] test(io): update STEP header in sparse-matrix references to one-based The sparse CSR writers emit std::max(istep, 0) + 1 in the STEP field since a658239dc (governance rule 22), but these integration references still expected the zero-based value. Only the header line changes; all matrix elements are byte-identical. Affected cases: 02_NAO_Gamma/scf_out_hxc, 03_NAO_multik/scf_out_dh_t, 03_NAO_multik/get_s, 03_NAO_multik/nscf_out_hsr_tr_rr, 03_NAO_multik/scf_out_hsr_spin4. Verified with Autotest.sh on a clean OUT.autotest: 36 test cases pass. --- tests/02_NAO_Gamma/scf_out_hxc/Vxc_R_spin0.ref | 2 +- tests/03_NAO_multik/get_s/dsrxs1.csr.ref | 2 +- tests/03_NAO_multik/get_s/dsrys1.csr.ref | 2 +- tests/03_NAO_multik/get_s/dsrzs1.csr.ref | 2 +- tests/03_NAO_multik/get_s/sr_nao.csr.ref | 2 +- tests/03_NAO_multik/nscf_out_hsr_tr_rr/rr_nao.txt.ref | 2 +- tests/03_NAO_multik/nscf_out_hsr_tr_rr/tr_nao.csr.ref | 2 +- tests/03_NAO_multik/scf_out_dh_t/dhrxs1_nao.csr.ref | 2 +- tests/03_NAO_multik/scf_out_dh_t/dhrys1_nao.csr.ref | 2 +- tests/03_NAO_multik/scf_out_dh_t/dhrzs1_nao.csr.ref | 2 +- tests/03_NAO_multik/scf_out_dh_t/tr_nao.csr.ref | 2 +- tests/03_NAO_multik/scf_out_hsr_spin4/rr_nao.txt.ref | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/02_NAO_Gamma/scf_out_hxc/Vxc_R_spin0.ref b/tests/02_NAO_Gamma/scf_out_hxc/Vxc_R_spin0.ref index 87ee81fb2e7..e359703fc9e 100644 --- a/tests/02_NAO_Gamma/scf_out_hxc/Vxc_R_spin0.ref +++ b/tests/02_NAO_Gamma/scf_out_hxc/Vxc_R_spin0.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of Vxc_R_spin0(R): 26 Matrix number of Vxc_R_spin0(R): 1 0 0 0 132 diff --git a/tests/03_NAO_multik/get_s/dsrxs1.csr.ref b/tests/03_NAO_multik/get_s/dsrxs1.csr.ref index 304c2fc81a8..aaec88d6f49 100644 --- a/tests/03_NAO_multik/get_s/dsrxs1.csr.ref +++ b/tests/03_NAO_multik/get_s/dsrxs1.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of dHx(R): 112 Matrix number of dHx(R): 3 -1 0 0 268 diff --git a/tests/03_NAO_multik/get_s/dsrys1.csr.ref b/tests/03_NAO_multik/get_s/dsrys1.csr.ref index e596e253c0a..dfdfb209dce 100644 --- a/tests/03_NAO_multik/get_s/dsrys1.csr.ref +++ b/tests/03_NAO_multik/get_s/dsrys1.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of dHy(R): 112 Matrix number of dHy(R): 3 -1 0 0 192 diff --git a/tests/03_NAO_multik/get_s/dsrzs1.csr.ref b/tests/03_NAO_multik/get_s/dsrzs1.csr.ref index 8b4a094f573..16c5b952134 100644 --- a/tests/03_NAO_multik/get_s/dsrzs1.csr.ref +++ b/tests/03_NAO_multik/get_s/dsrzs1.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of dHz(R): 112 Matrix number of dHz(R): 3 -1 0 0 192 diff --git a/tests/03_NAO_multik/get_s/sr_nao.csr.ref b/tests/03_NAO_multik/get_s/sr_nao.csr.ref index 00c283dd399..3c6902b76a6 100644 --- a/tests/03_NAO_multik/get_s/sr_nao.csr.ref +++ b/tests/03_NAO_multik/get_s/sr_nao.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of S(R): 112 Matrix number of S(R): 3 -1 0 0 268 diff --git a/tests/03_NAO_multik/nscf_out_hsr_tr_rr/rr_nao.txt.ref b/tests/03_NAO_multik/nscf_out_hsr_tr_rr/rr_nao.txt.ref index db8fe0b5461..42b80ea265e 100644 --- a/tests/03_NAO_multik/nscf_out_hsr_tr_rr/rr_nao.txt.ref +++ b/tests/03_NAO_multik/nscf_out_hsr_tr_rr/rr_nao.txt.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of r(R): 6 Matrix number of r(R): 9 -1 0 0 diff --git a/tests/03_NAO_multik/nscf_out_hsr_tr_rr/tr_nao.csr.ref b/tests/03_NAO_multik/nscf_out_hsr_tr_rr/tr_nao.csr.ref index 00e95fe978b..bb0639f3d07 100644 --- a/tests/03_NAO_multik/nscf_out_hsr_tr_rr/tr_nao.csr.ref +++ b/tests/03_NAO_multik/nscf_out_hsr_tr_rr/tr_nao.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of T(R): 6 Matrix number of T(R): 9 -1 0 0 15 diff --git a/tests/03_NAO_multik/scf_out_dh_t/dhrxs1_nao.csr.ref b/tests/03_NAO_multik/scf_out_dh_t/dhrxs1_nao.csr.ref index 1a624c49e90..90eb4f45d82 100644 --- a/tests/03_NAO_multik/scf_out_dh_t/dhrxs1_nao.csr.ref +++ b/tests/03_NAO_multik/scf_out_dh_t/dhrxs1_nao.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of dHx(R): 13 Matrix number of dHx(R): 1 0 0 0 24 diff --git a/tests/03_NAO_multik/scf_out_dh_t/dhrys1_nao.csr.ref b/tests/03_NAO_multik/scf_out_dh_t/dhrys1_nao.csr.ref index 1dc059da783..a5286bde5f3 100644 --- a/tests/03_NAO_multik/scf_out_dh_t/dhrys1_nao.csr.ref +++ b/tests/03_NAO_multik/scf_out_dh_t/dhrys1_nao.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of dHy(R): 13 Matrix number of dHy(R): 1 0 0 0 24 diff --git a/tests/03_NAO_multik/scf_out_dh_t/dhrzs1_nao.csr.ref b/tests/03_NAO_multik/scf_out_dh_t/dhrzs1_nao.csr.ref index ccfe6e99635..8c1fa290e1e 100644 --- a/tests/03_NAO_multik/scf_out_dh_t/dhrzs1_nao.csr.ref +++ b/tests/03_NAO_multik/scf_out_dh_t/dhrzs1_nao.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of dHz(R): 13 Matrix number of dHz(R): 1 0 0 0 20 diff --git a/tests/03_NAO_multik/scf_out_dh_t/tr_nao.csr.ref b/tests/03_NAO_multik/scf_out_dh_t/tr_nao.csr.ref index 1c241cae74c..52a639bfda5 100644 --- a/tests/03_NAO_multik/scf_out_dh_t/tr_nao.csr.ref +++ b/tests/03_NAO_multik/scf_out_dh_t/tr_nao.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of T(R): 13 Matrix number of T(R): 1 0 0 0 21 diff --git a/tests/03_NAO_multik/scf_out_hsr_spin4/rr_nao.txt.ref b/tests/03_NAO_multik/scf_out_hsr_spin4/rr_nao.txt.ref index e735fc09448..26635300247 100644 --- a/tests/03_NAO_multik/scf_out_hsr_spin4/rr_nao.txt.ref +++ b/tests/03_NAO_multik/scf_out_hsr_spin4/rr_nao.txt.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of r(R): 26 Matrix number of r(R): 7 -1 0 0 From a61b91a19f8557cd3b9220777e93380f2f48c16e Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 21 Oct 2026 15:44:35 +0800 Subject: [PATCH 33/61] fix(io): align binary H(R)/S(R) step check and docs with one-based header compare_hsr_binary.py still rejected any non-zero step in the native binary record, so scf_out_hsr_binary_spin2 aborted after the sparse writers moved to the one-based convention (a658239dc, rule 22). Accept step 1 and describe the merged field as one-based in the out_hsr help text and in the binary CSR format documentation. Verified: scf_out_hsr_binary_spin2 passes; full 02_NAO_Gamma (35 cases) and 03_NAO_multik (69 cases) rerun clean, with only the two DFT-D4 cases failing because this build lacks -DENABLE_DFTD4=ON. --- docs/advanced/elec_properties/hs_matrix.md | 2 +- source/source_io/module_parameter/read_inp_out.cpp | 2 +- tests/integrate/validation_tools/compare_hsr_binary.py | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/advanced/elec_properties/hs_matrix.md b/docs/advanced/elec_properties/hs_matrix.md index 6e4aa48423b..8e13d2457ad 100644 --- a/docs/advanced/elec_properties/hs_matrix.md +++ b/docs/advanced/elec_properties/hs_matrix.md @@ -99,7 +99,7 @@ Set `out_hsr 2` to write the same H(R) and S(R) matrix sets with a `.dat` suffix Each ionic step is a complete record with no padding or self-describing header: -1. Native `int`: zero-based ionic step, matrix dimension, number of R blocks. +1. Native `int`: one-based ionic step, matrix dimension, number of R blocks. 2. For every R block in lexicographic `(Rx, Ry, Rz)` order, four native `int` values: `Rx`, `Ry`, `Rz`, and `nnz`. 3. `nnz` matrix values in CSR order. Real matrices use one native `double`; complex matrices use consecutive real and imaginary `double` values. 4. `nnz` native `int` column indices. diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index c05e113dcc6..da0937928a7 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -637,7 +637,7 @@ When out_app_flag is 0 or 2, g followed by the one-based ionic-step index is ins For multi-k calculations, the output contains the individual real-space blocks stored for the Bravais lattice vectors R. For gamma-only calculations, the internal real-space contributions are folded into a single R = (0, 0, 0) block. This folded result cannot recover the original R-resolved contributions or interpolate arbitrary k points. Terms added only while constructing H(k) are not guaranteed to be present. -For binary output, each file uses the same basename as text output with a .dat suffix. Every native record contains the zero-based ionic step, matrix dimension, and number of R blocks as ints. Each R block contains three int coordinates, an int nonzero count, native double values (real/imaginary double pairs for complex matrices), int column indices, and long long row pointers. Native integer representation and byte order are used. When out_app_flag is true, the first ionic step truncates the file and later steps append complete records. +For binary output, each file uses the same basename as text output with a .dat suffix. Every native record contains the one-based ionic step, matrix dimension, and number of R blocks as ints. Each R block contains three int coordinates, an int nonzero count, native double values (real/imaginary double pairs for complex matrices), int column indices, and long long row pointers. Native integer representation and byte order are used. When out_app_flag is true, the first ionic step truncates the file and later steps append complete records. [NOTE] In the 3.10-LTS version, the file names are data-HR-sparse_SPIN0.csr and data-SR-sparse_SPIN0.csr, etc.)"; item.default_value = "0 8"; diff --git a/tests/integrate/validation_tools/compare_hsr_binary.py b/tests/integrate/validation_tools/compare_hsr_binary.py index 2e3e295ed6e..b306a5f1569 100644 --- a/tests/integrate/validation_tools/compare_hsr_binary.py +++ b/tests/integrate/validation_tools/compare_hsr_binary.py @@ -20,8 +20,10 @@ def read_native(stream, fmt, description): def read_binary(filename, value_type): with open(filename, "rb") as stream: step, nbasis, nr = read_native(stream, "@iii", "record header") - if step != 0: - raise ValueError("expected zero-based ionic step 0, found {}".format(step)) + # The binary step field follows governance rule 22: the first ionic + # step is printed one-based as 1. + if step != 1: + raise ValueError("expected one-based ionic step 1, found {}".format(step)) if nbasis <= 0 or nr < 0: raise ValueError("invalid matrix dimensions nbasis={} nr={}".format(nbasis, nr)) From 613d6e854e4fdd9eae186bcec5df0ffb76c39837 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 21 Oct 2026 15:55:01 +0800 Subject: [PATCH 34/61] fix(io): route per-atom dH component outputs through global_mat_dir write_dh_perI had two relic caller-side branches that duplicated the directory selection already performed by ReadInput::set_global_dir: 1. r_dir used a ternary keyed on calculation=="md" && out_app_flag!=1 2. the dense k-space dH(k) filename was hardcoded against global_out_dir instead of the r_dir variable Both are replaced with a single mat_dir = PARAM.globalv.global_mat_dir, so relax + out_app_flag=2 now lands CSR dH/dR and dense dH/dk in OUT.suffix/MAT/ together with H(R), S(R), DM(k) and the H(k) term matrices already routed in the fix commit. Modes 0 and 1 keep the exact same output path because global_mat_dir aliases global_out_dir there. --- source/source_io/module_dhs/write_dh.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/source/source_io/module_dhs/write_dh.cpp b/source/source_io/module_dhs/write_dh.cpp index d05ff12716a..4f82a30c9de 100644 --- a/source/source_io/module_dhs/write_dh.cpp +++ b/source/source_io/module_dhs/write_dh.cpp @@ -43,10 +43,12 @@ void write_dh_perI(WriteDHParams& params, const int nspin_k = (nspin == 2 ? 2 : 1); const int nks = params.kv->get_nks() / nspin_k; const int nlocal = PARAM.globalv.nlocal; - const std::string global_out_dir = PARAM.globalv.global_out_dir; + // All dH component outputs (CSR dH/dR, dense dH/dk) belong to the unified + // matrix directory: global_mat_dir is set to OUT.root in modes 0/1 and to + // OUT.root/MAT/ in mode 2 by ReadInput::set_global_dir, so no caller-side + // calculation or out_app_flag branching is needed here. + const std::string mat_dir = PARAM.globalv.global_mat_dir; const int out_app_flag = PARAM.inp.out_app_flag; - const std::string r_dir - = (PARAM.inp.calculation == "md" && (out_app_flag != 1)) ? PARAM.globalv.global_matrix_dir : global_out_dir; #ifdef __MPI Parallel_Orbitals serialV; @@ -79,7 +81,7 @@ void write_dh_perI(WriteDHParams& params, if (GlobalV::MY_RANK == 0) #endif { - std::string fr = r_dir + ModuleIO::dhr_gen_fname(rprefix + tag, ispin, params.append, params.istep); + std::string fr = mat_dir + ModuleIO::dhr_gen_fname(rprefix + tag, ispin, params.append, params.istep); // dH/dR is not a Hamiltonian: no Fermi energy annotation in the header const double no_efermi = 0.0; #ifdef __MPI @@ -117,7 +119,7 @@ void write_dh_perI(WriteDHParams& params, #else hamilt::folding_HR(*hR, hk.data(), params.kv->kvec_d[ik], nlocal, 0); #endif - std::string fk = global_out_dir + kprefix + tag; + std::string fk = mat_dir + kprefix + tag; if (nks > 1) { fk += "_ik" + std::to_string(params.kv->ik2iktot[ik]); From 2f33b8a6c93daaeb9ed6fd41f7e4a37a8efda3ca Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 21 Oct 2026 16:01:37 +0800 Subject: [PATCH 35/61] fix(io): include H/dH component flags in MAT dir creation predicate The mode-2 subdir_flags OR chain for MAT/ covered the aggregate switches (out_mat_hs2, out_mat_dh) and a handful of legacy flags (out_dmk, out_hsk, out_mat_tk, ...) but none of the per-component switches that actually write into MAT/: H(R) components: out_mat_h_t/vnl/vl/vh/vxc/exx dH components: out_mat_dh_t/vl/vnl/vh/vxc With only one of those per-component flags enabled in mode 2, MAT/ would not be created and the H(R)/dH component writers would fail to open their output files. Append all 11 component switches to the OR chain so MAT/ is created whenever any matrix output is requested. --- source/source_io/module_parameter/read_input.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/source_io/module_parameter/read_input.cpp b/source/source_io/module_parameter/read_input.cpp index 366082cf54e..f87002d843a 100644 --- a/source/source_io/module_parameter/read_input.cpp +++ b/source/source_io/module_parameter/read_input.cpp @@ -325,7 +325,12 @@ void ReadInput::create_directory(const Parameter& param) || param.input.out_mat_tk[0] > 0 || param.input.out_mat_l[0] > 0 || param.input.out_mat_dh[0] > 0 || param.input.out_mat_ds[0] > 0 || param.input.out_mat_xc || param.input.out_mat_xc2[0] > 0 || param.input.out_hr_npz - || param.input.out_hsr_npz || param.input.out_dm_npz, param.globalv.global_mat_dir} + || param.input.out_hsr_npz || param.input.out_dm_npz + || param.input.out_mat_h_t[0] > 0 || param.input.out_mat_h_vnl[0] > 0 || param.input.out_mat_h_vl[0] > 0 + || param.input.out_mat_h_vh[0] > 0 || param.input.out_mat_h_vxc[0] > 0 || param.input.out_mat_h_exx[0] > 0 + || param.input.out_mat_dh_t[0] > 0 || param.input.out_mat_dh_vl[0] > 0 || param.input.out_mat_dh_vnl[0] > 0 + || param.input.out_mat_dh_vh[0] > 0 || param.input.out_mat_dh_vxc[0] > 0, + param.globalv.global_mat_dir} }; for (const auto& flag_dir : subdir_flags) { From 8c4ebb049b4393411a4750b6556aaa2297d9b2c7 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 21 Oct 2026 16:10:43 +0800 Subject: [PATCH 36/61] fix(io): route remaining PW outputs and drop LCAO-only availability for out_app_flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related fixes for the out_app_flag rework: 1. ctrl_output_pw.cpp — the last two PW-specific outputs that still wrote into OUT.root are now routed into their purpose-built mode-2 subdirectories: out_pchg -> global_chg_dir (CHG/) out_wfc_norm / _re_im -> global_psi_dir (PSI/) Every PW writer (wfc, DOS, chg, pot, ELF, dipole, pchg, wfc_norm) now honors mode-2 routing — no more OUT.root leakage from PW. 2. read_inp_out.cpp — out_app_flag's availability was "basis_type==lcao and gamma_only==0" even though the description said the routing applies to both LCAO and PW. Empty the availability expression so the flag is visible to every run type. The lcao restriction was never enforced in the code paths and PW users were only being hidden from the parameter — the PW writers above now back that description up. --- source/source_io/module_ctrl/ctrl_output_pw.cpp | 4 ++-- source/source_io/module_parameter/read_inp_out.cpp | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/source/source_io/module_ctrl/ctrl_output_pw.cpp b/source/source_io/module_ctrl/ctrl_output_pw.cpp index 5bffea81abe..7b8baff6f86 100644 --- a/source/source_io/module_ctrl/ctrl_output_pw.cpp +++ b/source/source_io/module_ctrl/ctrl_output_pw.cpp @@ -189,14 +189,14 @@ void ModuleIO::ctrl_scf_pw(const int istep, // Use the solver's native wavefunction precision for FFT and projector overlaps. ModuleIO::Get_pchg_pw output(*stp.template get_psi_t(), *pw_wfc, *pw_rho, *pw_rhod, ppcell, inp.nspin, inp.nbands); - output.begin(&ucell, para_grid, kv, inp.out_pchg, PARAM.globalv.global_out_dir, inp.if_separate_k, inp.noncolin, ofs_running); + output.begin(&ucell, para_grid, kv, inp.out_pchg, PARAM.globalv.global_chg_dir, inp.if_separate_k, inp.noncolin, ofs_running); } if ((inp.out_wfc_norm.size() > 0 || inp.out_wfc_re_im.size() > 0) && output_td_state) { // Use the solver's native wavefunction precision for FFT. ModuleIO::Get_wf_pw output(*stp.template get_psi_t(), *pw_wfc, *pw_rho, *pw_rhod, inp.nspin, inp.nbands); - output.begin(ucell, para_grid, kv, inp.out_wfc_norm, inp.out_wfc_re_im, PARAM.globalv.global_out_dir, ofs_running); + output.begin(ucell, para_grid, kv, inp.out_wfc_norm, inp.out_wfc_re_im, PARAM.globalv.global_psi_dir, ofs_running); } //------------------------------------------------------------------ diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index da0937928a7..23d402ef921 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -1404,7 +1404,10 @@ The legacy Boolean spellings (true/false, yes/no, on/off, t/f, y/n, and the dott For more information, refer to the documentation of out_mat_r, out_hsr, out_mat_t, out_mat_dh, out_hsk, out_wfc_lcao, out_chg, out_pot, out_elf, out_dos, out_dipole, out_stru.)"; item.default_value = "1"; item.unit = ""; - item.set_availability("basis_type==lcao and gamma_only==0"); + // out_app_flag applies to both LCAO and PW bases, and gamma-only runs + // follow the same directory routing rules as multi-k runs. An empty + // availability expression means "always available". + item.set_availability(""); // Keep the previously accepted Boolean spellings working: the legacy // parser treated out_app_flag as a Boolean, so INPUT files containing // "true"/"false"/"yes"/"no"/"on"/"off" (case-insensitive) must keep From 4ca63db43338f039c69f74da34e307e486e30829 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 21 Oct 2026 16:24:49 +0800 Subject: [PATCH 37/61] test(io): cover save_mat overwrite and write_hsk mode-2 rerun semantics Two regression guards for the review feedback asking for focused mode-2 coverage: test_hs_dense_io.cpp :: AppendFalseOverwritesSameIstep save_mat must truncate on append=false even when the same istep is rewritten. The older BinaryAppend test cross-stepped istep 0 -> 1 -> 2 and never caught the same-step overwrite case. This pairs append=true then append=false on the same file for istep=0 and asserts exactly one record remains. test_hsk_writer.cpp :: Mode2OverwritesSameIstep write_hsk was passing the integer out_app_flag directly into save_mat's boolean append parameter before the fix, so out_app_flag=2 would append instead of truncating. The existing tests only exercised append=false implicit via k_out_app=0 (mode 0 behaviour). This test drives out_app_flag=2 for istep=0, swaps the FakeHamilt payload, writes again, and verifies the second payload replaced the first. Governance: one new test file matches one source file, test_hsk_writer.cpp was already in place so a new case joins the existing fixture. --- .../module_hs/unittests/test_hs_dense_io.cpp | 38 ++++++++ .../module_hs/unittests/test_hsk_writer.cpp | 87 +++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/source/source_io/module_hs/unittests/test_hs_dense_io.cpp b/source/source_io/module_hs/unittests/test_hs_dense_io.cpp index 31410679def..0a54bcdf2f3 100644 --- a/source/source_io/module_hs/unittests/test_hs_dense_io.cpp +++ b/source/source_io/module_hs/unittests/test_hs_dense_io.cpp @@ -378,6 +378,44 @@ TEST(HsDenseIoSaveMat, BinaryAppendCompleteRecordsAndOverwrite) remove_test_file(filename); } +TEST(HsDenseIoSaveMat, AppendFalseOverwritesSameIstep) +{ + /// Protects against regression where an int-mode value (out_app_flag) is + /// passed to a bool append parameter. The older BinaryAppend test covers + /// cross-stepping; this one exercises the same-istep overwrite path. + const int dim = 2; + const std::string filename = "save_mat_overwrite_same_step.dat"; + remove_test_file(filename); + + Parallel_Orbitals pv; + initialize_distribution(pv, dim); + const std::vector first = {1.0, 2.0, 3.0, 4.0}; + const std::vector replacement = {9.0, 10.0, 11.0, 12.0}; + const std::vector first_local = distribute_matrix(pv, first, dim); + const std::vector replacement_local + = distribute_matrix(pv, replacement, dim); + + // Step 1: append=true writes the full record for istep=0. + ModuleIO::save_mat(0, first_local.data(), dim, true, 8, true, true, + filename, pv, test_rank, "cg"); + + // Step 2: append=false on the same istep must truncate the file, not + // append a second copy. + ModuleIO::save_mat(0, replacement_local.data(), dim, true, 8, true, false, + filename, pv, test_rank, "cg"); + + if (test_rank == 0) + { + std::ifstream ifs(filename.c_str(), std::ios::binary); + ASSERT_TRUE(ifs.is_open()); + // Exactly one record, containing the replacement values. + EXPECT_EQ(read_record(ifs, dim), upper_triangle(replacement, dim)); + EXPECT_EQ(ifs.peek(), std::ifstream::traits_type::eof()); + } + + remove_test_file(filename); +} + // --------------------------------------------------------------------------- // Main: initialize MPI before running tests (required by save_mat's MPI path) // --------------------------------------------------------------------------- diff --git a/source/source_io/module_hs/unittests/test_hsk_writer.cpp b/source/source_io/module_hs/unittests/test_hsk_writer.cpp index a812efb77f4..2cfea1b46f4 100644 --- a/source/source_io/module_hs/unittests/test_hsk_writer.cpp +++ b/source/source_io/module_hs/unittests/test_hsk_writer.cpp @@ -436,6 +436,93 @@ TEST(WriteHsk, WritesComplexKMatrix) remove_test_file("write_hsk_running_" + std::to_string(test_rank) + ".log"); } +// --------------------------------------------------------------------------- +// Regression guard: out_app_flag = 2 must write non-append records. +// The old code passed the integer out_app_flag directly to save_mat's +// boolean append argument, so a rerun in mode 2 would silently grow the +// file instead of replacing the snapshot. This test sets out_app_flag=2, +// writes one snapshot, swaps the FakeHamilt buffers, writes the same step +// again, and verifies the file contains exactly one record holding the +// second payload. +// --------------------------------------------------------------------------- + +TEST(WriteHsk, Mode2OverwritesSameIstep) +{ + const int dim = 2; + const int nspin = 1; + const int nks = 1; + const int nkstot = 1; + const std::vector ik2iktot = {0}; + const std::vector isk = {0}; + const bool gamma_only = false; + const int istep = 0; + const int out_app_flag = 2; // mode 2 => overwrite semantics + const int out_type = 2; // binary + const int precision = 8; + + Parallel_Orbitals pv; + initialize_distribution(pv, dim); + + FakeHamilt fake; + fake.dim = dim; + + // Round 1 payload: H = all 1.0, S = all -1.0 + std::vector hk_first(dim * dim, 1.0); + std::vector sk_first(dim * dim, -1.0); + fake.set_k_matrix(0, + distribute_matrix(pv, hk_first, dim), + distribute_matrix(pv, sk_first, dim)); + + // filename_output with out_app_flag=2 + istep=0 yields hk{ik}g{step+1}_nao.dat + const std::string h_fn = ModuleIO::filename_output( + "", "hk", "nao", 0, ik2iktot, nspin, nkstot, out_type, + out_app_flag, gamma_only, istep); + const std::string s_fn = ModuleIO::filename_output( + "", "sk", "nao", 0, ik2iktot, nspin, nkstot, out_type, + out_app_flag, gamma_only, istep); + remove_test_file(h_fn); + remove_test_file(s_fn); + + std::ofstream ofs = open_running_log(); + ModuleIO::write_hsk("", nspin, nks, nkstot, ik2iktot, isk, &fake, pv, + gamma_only, out_app_flag, istep, out_type, precision, dim, + "cg", test_rank, ofs); + + // Round 2 payload: different data, same out_app_flag and istep. + std::vector hk_second(dim * dim, 9.0); + std::vector sk_second(dim * dim, -9.0); + fake.set_k_matrix(0, + distribute_matrix(pv, hk_second, dim), + distribute_matrix(pv, sk_second, dim)); + +#ifdef __MPI + MPI_Barrier(DIAG_WORLD); +#endif + ModuleIO::write_hsk("", nspin, nks, nkstot, ik2iktot, isk, &fake, pv, + gamma_only, out_app_flag, istep, out_type, precision, dim, + "cg", test_rank, ofs); + + if (test_rank == 0) + { + std::ifstream hfs(h_fn.c_str(), std::ios::binary); + ASSERT_TRUE(hfs.is_open()) << h_fn; + // Exactly one record — second payload — not two records stacked. + EXPECT_EQ(read_record(hfs, dim), upper_triangle(hk_second, dim)); + EXPECT_EQ(hfs.peek(), std::ifstream::traits_type::eof()) + << "hk file should contain one overwrite record, not two"; + + std::ifstream sfs(s_fn.c_str(), std::ios::binary); + ASSERT_TRUE(sfs.is_open()) << s_fn; + EXPECT_EQ(read_record(sfs, dim), upper_triangle(sk_second, dim)); + EXPECT_EQ(sfs.peek(), std::ifstream::traits_type::eof()); + } + + remove_test_file(h_fn); + remove_test_file(s_fn); + ofs.close(); + remove_test_file("write_hsk_running_" + std::to_string(test_rank) + ".log"); +} + int main(int argc, char** argv) { #ifdef __MPI From 7cc8eb050cd2c21c82c02bf88eec861394e38cc2 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 21 Oct 2026 16:24:55 +0800 Subject: [PATCH 38/61] fix(md): keep legacy STRU directory layout when writing MD structures out_app_flag == 1 writes one fixed-name "STRU" file, but a previous release kept per-step STRU_MD_{step} files inside a STRU/ subdirectory of the output directory. Restarting an MD from such output (14_LJ_- rescale_v ships that layout) made open() hit the directory and abort with "Unable to create MDCell restart STRU file: ... Is a directory". Resolve the STRU output prefix once before the MD loop: when mode 1 is requested and /STRU already exists as a directory, keep the legacy numbered files inside that directory and skip accumulation. Modes 0 and 2, and mode 1 without a legacy directory, are unchanged. Also refresh the Mulliken output sample in the documentation: the file now starts with " --- Ionic Step 1 ---" and uses the zeta/spin table with sum m / sum mz / sum lmz labels. Verified: make -j 30; 14_LJ_rescale_v 4/4; full 04_FF 78 checks; 02_NAO_Gamma md_*/relax_* (9 cases) 53 checks. --- docs/advanced/elec_properties/Mulliken.md | 83 ++++++++++++----------- source/source_md/run_md.cpp | 24 ++++++- 2 files changed, 66 insertions(+), 41 deletions(-) diff --git a/docs/advanced/elec_properties/Mulliken.md b/docs/advanced/elec_properties/Mulliken.md index c768e83c7ff..0b5163281f8 100644 --- a/docs/advanced/elec_properties/Mulliken.md +++ b/docs/advanced/elec_properties/Mulliken.md @@ -1,62 +1,67 @@ # Mulliken Charge Analysis From version 2.1.0, ABACUS has the function of Mulliken population analysis. The example can be found in [examples/mulliken](https://github.com/deepmodeling/abacus-develop/tree/develop/examples/14_mulliken). \ -To use this function, set [out_mul](../input_files/input-main.md#out_mul) to `1` in the INPUT file. After calculation, there will be an output file named `mulliken.txt` in the output directory. In MD calculations, the output interval is controlled by the keyword [out_freq_ion](../input_files/input-main.md#out_freq_ion). In the file, there are contents like (`nspin 1`): +To use this function, set [out_mul](../input_files/input-main.md#out_mul) to `1` in the INPUT file. After calculation, there will be an output file named `mulliken.txt` in the output directory. In MD calculations, the output interval is controlled by the keyword [out_freq_ion](../input_files/input-main.md#out_freq_ion). In the file, there are contents like (`nspin 1`, H2): ``` -STEP: 0 -CALCULATE THE MULLIkEN ANALYSIS FOR EACH ATOM - Total charge of spin 1: 8 - Total charge: 8 -Decomposed Mulliken populations -0 Zeta of Si Spin 1 -s 0 1.2553358 - sum over m 1.2553358 -s 1 -0.030782972 - sum over m -0.030782972 - sum over m+zeta 1.2245529 -pz 0 0.85945806 -px 0 0.85945806 -py 0 0.85945806 - sum over m 2.5783742 -pz 1 0.0065801228 -px 1 0.0065801228 -py 1 0.0065801228 - sum over m 0.019740368 - sum over m+zeta 2.5981145 -dz^2 0 0.0189287 -dxz 0 0.046491729 -dyz 0 0.046491729 -dx^2-y^2 0 0.0189287 -dxy 0 0.046491729 - sum over m 0.17733259 - sum over m+zeta 0.17733259 -Total Charge on atom: Si 4 + --- Ionic Step 1 --- + Total charge 2 + Decomposed Mulliken population analysis for each atom + l and m from Ylm, z stands for zeta orbital + + ------------------ + Atom 1 is H + ------------------ + zeta spin1 + s 1 0.9980 + s 2 -0.0052 + sum mz 0.9928 + + pz 1 0.0072 + px 1 0.0000 + py 1 0.0000 + sum m 0.0072 + sum mz 0.0072 + + sum lmz 1.0000 + + total charge on atom 1 1.0000 ... ``` -The file gives Mulliken charge in turn according to the order of atoms in the system. For example, the following block is for the first atom in system (`nspin 2`), +The ionic step index starts from 1. The file gives Mulliken charge in turn according to the order of atoms in the system. For example, the following block is for the first atom in system (`nspin 2`), ``` -0 Zeta of Si Spin 1 Spin 2 Sum Diff + ------------------ + Atom 1 is Fe1 + ------------------ + zeta spin1 spin2 sum diff + s 1 0.9437 0.8381 1.7818 0.1056 ... -Total Charge on atom: Si 4 -Total Magnetism on atom: Si -1.2739809e-14 + sum lmz ... ``` And the next block is for the second atom in system, and so on. ``` -1 Zeta of Si Spin 1 Spin 2 Sum Diff + ------------------ + Atom 2 is Fe2 + ------------------ + zeta spin1 spin2 sum diff ... ``` For each atom, the file gives detailed Mulliken population analysis at different levels, -- magnetic quantum number level: such as lines beigin with ‘s,px,py,pz,...’ -- azimuthal quantum number level: such as lines begin with ‘sum over m’. -- principal quantum number level: such as lines begin with ‘sum over m+zeta’. Here ‘zeta’ - equals ‘zeta’ in the file, which means how many radial atomic orbitals there are for a given orbital angular momentum. -- atomic level: such as lines begin with ‘Total Charge on atom’. +- magnetic quantum number level: such as lines begin with 's, px, py, pz, ...'. The columns + are the radial index (`zeta`, one-based) followed by the spin channels. +- azimuthal quantum number level: such as lines begin with 'sum m', the sum over the + magnetic quantum number m for one radial orbital. +- principal quantum number level: such as lines begin with 'sum mz', the sum over both the + magnetic quantum number m and the radial index zeta for a given orbital angular momentum. + Here 'z' equals 'zeta' in the file, which means how many radial atomic orbitals there are + for a given orbital angular momentum. +- atomic level: such as lines begin with 'sum lmz' for the atom total and + 'total charge on atom' for the charge assigned to the atom. More orbital information can be found in 'Orbital' file output with 'mulliken.txt' when `out_mul 1` diff --git a/source/source_md/run_md.cpp b/source/source_md/run_md.cpp index d16ac26135f..f7d49433d2c 100644 --- a/source/source_md/run_md.cpp +++ b/source/source_md/run_md.cpp @@ -25,6 +25,7 @@ #include "source_md/module_plumed/plumed_interface.h" #include +#include #ifdef __MPI #include #endif @@ -195,6 +196,25 @@ void md_line(MDCell& mdcell, ? param_in.inp.out_freq_ion : param_in.mdp.md_restartfreq; + /// out_app_flag == 1 writes one fixed-name "STRU" file. A previous + /// release kept per-step STRU_MD_{step} files inside a STRU/ + /// subdirectory of the output directory, so an MD restarted from those + /// files still sees that directory. Writing the fixed name on top of a + /// directory aborts the run, therefore the legacy numbered layout is + /// kept whenever such a directory is present. + const bool append_stru_allowed = (param_in.inp.out_app_flag == 1); + bool has_legacy_stru_dir = false; + std::string stru_out_dir = PARAM.globalv.global_stru_dir; + if (append_stru_allowed) + { + struct stat stru_path; + if (stat((stru_out_dir + "STRU").c_str(), &stru_path) == 0 && S_ISDIR(stru_path.st_mode)) + { + has_legacy_stru_dir = true; + stru_out_dir += "STRU/"; + } + } + /// md cycle, mohan update 2026-01-04, change '<=' to '<' while ((mdrun->step_ + mdrun->step_rst_) < param_in.mdp.md_nstep && !mdrun->stop) { @@ -302,12 +322,12 @@ void md_line(MDCell& mdcell, // out_app_flag == 1: single fixed-name STRU accumulates one // frame per step with a one-based "# MD step N" comment; // modes 0/2 keep the numbered STRU{stru_step} files. - const bool append_stru = (param_in.inp.out_app_flag == 1); + const bool append_stru = append_stru_allowed && !has_legacy_stru_dir; const std::string frame_comment = append_stru ? "# MD step " + std::to_string(stru_step) + "\n\n" : ""; std::stringstream file; - file << PARAM.globalv.global_stru_dir << "STRU"; + file << stru_out_dir << "STRU"; if (!append_stru) { file << stru_step; From 13b21fbd55d08ddbacaebff7368f59a506505545 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 21 Oct 2026 16:31:18 +0800 Subject: [PATCH 39/61] fix(io): resolve MPI crashes in write_dipole when out_dipole=1 Two bugs in source/source_io/module_dipole/write_dipole.cpp caused out_dipole=1 to abort under MPI: 1. Validation check rejected rhopw->nplane == 0, but nplane is a per-rank local count (number of z-planes owned by the current process). Some ranks legitimately hold zero z-planes under z-axis MPI decomposition; hitting WARNING_QUIT there made those ranks exit with code 1 while the others waited forever. 2. The dipole data line was written to 'ofs' on every rank, but the file is only opened on rank 0. Non-root ranks wrote to an unopened stream, which is undefined behaviour and corrupts output when it doesn't crash outright. Fixes: - Remove rhopw->nplane == 0 from the guard; keep only global dimensions nx/ny/nz/nxyz, which truly must be positive. - Wrap the ofs << ... write inside an if (GlobalV::MY_RANK == 0) block, consistent with the existing file-open and file-close guards. --- .../source_io/module_dipole/write_dipole.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/source/source_io/module_dipole/write_dipole.cpp b/source/source_io/module_dipole/write_dipole.cpp index 74020c0492e..35cd8667fbf 100644 --- a/source/source_io/module_dipole/write_dipole.cpp +++ b/source/source_io/module_dipole/write_dipole.cpp @@ -67,10 +67,13 @@ void write_dipole(const UnitCell& ucell, ofs_running << " Write dipole data to file: " << fn << std::endl; - // Validate grid dimensions to prevent division by zero - if (rhopw->nx == 0 || rhopw->ny == 0 || rhopw->nz == 0 || rhopw->nxyz == 0 || rhopw->nplane == 0) + // Validate global grid dimensions to prevent division by zero. + // Do NOT reject nplane == 0: some MPI ranks legitimately hold zero + // z-planes (empty partition), in which case nrxx is also 0 and the + // accumulation loop below is a harmless no-op. + if (rhopw->nx == 0 || rhopw->ny == 0 || rhopw->nz == 0 || rhopw->nxyz == 0) { - ModuleBase::WARNING_QUIT("ModuleIO::write_dipole", "Invalid grid parameters: nx, ny, nz, nxyz, or nplane is zero"); + ModuleBase::WARNING_QUIT("ModuleIO::write_dipole", "Invalid grid parameters: nx, ny, nz, or nxyz is zero"); } // Accumulate the electronic first moment in fractional coordinates. @@ -117,9 +120,12 @@ void write_dipole(const UnitCell& ucell, // Output electronic dipole moment print_dipole_moment(ofs_running, "Electronic dipole moment", dipole_elec[0], dipole_elec[1], dipole_elec[2]); - // Write to file: step index and three dipole components - ofs << std::setprecision(precision) << istep + 1 << " " << dipole_elec[0] << " " << dipole_elec[1] << " " << dipole_elec[2] - << std::endl; + // Write to file on master process only; ofs is opened only there. + if (GlobalV::MY_RANK == 0) + { + ofs << std::setprecision(precision) << istep + 1 << " " << dipole_elec[0] << " " << dipole_elec[1] << " " + << dipole_elec[2] << std::endl; + } // Calculate ionic dipole moment // Accumulate Z_v * taud, then transform to Cartesian coordinates. From 43c09cda9ee556646db0eecacb712cf7b4c2f978 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 21 Oct 2026 19:51:55 +0800 Subject: [PATCH 40/61] fix(elf): keep all ranks on the FFT path in write_elf A rank can own zero real-space grid points (nrxx == 0) when the FFT grid is decomposed across more processes than it has z-slabs. The asserts in write_elf rejected that legitimate empty partition. Dropping the asserts alone is not enough if the FFT body is skipped: real2recip/recip2real go through PW_Basis::gatherp_scatters, which performs an MPI_Alltoallv that requires every rank in POOL_WORLD to participate, so a rank that returns early deadlocks the grid-owning ranks inside Alltoallv. Run the same FFT path unconditionally on every rank (the per-grid-point loops simply iterate zero times when nrxx == 0) and keep every rank in the write_vdata_palgrid output section for the collective MPI_Gatherv in Parallel_Grid::reduce. Add regression tests for the zero-length container and zero-iteration invariants. Verified with examples/17_relax/04_relax_with_output_lcao (out_elf 1): OMP_NUM_THREADS=4 mpirun -np 4 abacus_max_para finishes in 4 s and writes elfs1.cube, elfs2.cube and elftot.cube; MODULE_IO_write_elf_logic_test passes 14/14. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com> --- source/source_io/module_elf/write_elf.cpp | 17 +++- .../source_io/test/write_elf_logic_test.cpp | 95 +++++++++++++++++++ 2 files changed, 109 insertions(+), 3 deletions(-) diff --git a/source/source_io/module_elf/write_elf.cpp b/source/source_io/module_elf/write_elf.cpp index fe6164c0107..986de94fd4d 100644 --- a/source/source_io/module_elf/write_elf.cpp +++ b/source/source_io/module_elf/write_elf.cpp @@ -35,8 +35,14 @@ void write_elf( const int nrxx = rho_basis->nrxx; const int npw = rho_basis->npw; - assert(nrxx>0); - assert(npw>0); + // A rank may own zero real-space grid points (nrxx == 0) when the FFT + // grid is decomposed across more processes than it has z-slabs. Such + // empty partitions are legitimate: the per-grid-point loops below simply + // do not execute, but every rank must still call real2recip / recip2real + // because PW_Basis::gatherp_scatters performs an MPI_Alltoallv that + // requires all ranks in POOL_WORLD to participate. Skipping the FFT on + // empty ranks would deadlock the grid-owning ranks inside Alltoallv. + // The asserts are removed to allow nrxx == 0 / npw == 0 through. std::vector> elf(nspin_eff, std::vector(nrxx, 0.)); // 1) calculate the kinetic energy density of vW KEDF @@ -154,7 +160,12 @@ void write_elf( } } - // 4) output the ELF = 1 / (1 + F^2) to cube file + // 4) output the ELF = 1 / (1 + F^2) to cube file. + // Every rank, including empty-partition ranks with nrxx == 0, must + // enter this block so write_vdata_palgrid (which calls + // Parallel_Grid::reduce / MPI_Gatherv) sees a collective call from all + // ranks. Empty partitions pass a null pointer which Parallel_Grid::reduce + // accepts only when nrxx == 0 (sendcount 0 ignores the buffer). const double ef_tmp = 0.0; const int out_fermi = 0; diff --git a/source/source_io/test/write_elf_logic_test.cpp b/source/source_io/test/write_elf_logic_test.cpp index 25269172f73..6bbf65c5f5a 100644 --- a/source/source_io/test/write_elf_logic_test.cpp +++ b/source/source_io/test/write_elf_logic_test.cpp @@ -170,6 +170,101 @@ TEST_F(ElfLogicTest, Nspin4MixedSigns) } } +// --------------------------------------------------------------------------- +// Zero-grid partition regression tests +// +// When the real-space FFT grid is decomposed across more MPI processes than +// it has z-slabs, some ranks receive nrxx == 0, which is a legitimate empty +// partition (see Parallel_Grid::reduce assert contract). write_elf does NOT +// skip the FFT path on those ranks: PW_Basis::real2recip / recip2real call +// gatherp_scatters, which performs an MPI_Alltoallv requiring every rank in +// POOL_WORLD to participate. Only the per-grid-point loops execute zero +// iterations; the FFT itself must still run so the Alltoallv matches across +// ranks. Every rank also enters write_vdata_palgrid so Parallel_Grid::reduce +// (MPI_Gatherv) sees a collective call. These tests document the zero-length +// container and zero-iteration invariants that keep empty partitions safe. +// --------------------------------------------------------------------------- + +TEST_F(ElfLogicTest, ZeroGridHasLocalGrid) +{ + // A rank holds local grid data only when both nrxx and npw are positive. + // write_elf no longer branches on this (every rank runs the FFT path), + // but the predicate is kept here to document the empty-partition + // condition that motivates the invariant checks below. + auto has_local_grid = [](int nrxx, int npw) { + return nrxx > 0 && npw > 0; + }; + + EXPECT_FALSE(has_local_grid(0, 0)); + EXPECT_FALSE(has_local_grid(0, 100)); + EXPECT_FALSE(has_local_grid(100, 0)); + EXPECT_TRUE(has_local_grid(100, 50)); + EXPECT_TRUE(has_local_grid(1, 1)); +} +TEST_F(ElfLogicTest, ZeroLengthVectorIsLegal) +{ + // std::vector(0) is well-defined and .data() is non-null only + // when size() > 0. Empty partitions pass a null pointer into the MPI + // reduce call, which Parallel_Grid::reduce explicitly tolerates when + // nrxx == 0 (sendcount 0 ignores the buffer). + std::vector empty(0, 0.0); + EXPECT_EQ(empty.size(), 0u); + EXPECT_EQ(empty.data(), nullptr); + + // All-zero non-empty vector for comparison: .data() is non-null. + std::vector zeros(5, 0.0); + EXPECT_EQ(zeros.size(), 5u); + EXPECT_NE(zeros.data(), nullptr); +} + +TEST_F(ElfLogicTest, ZeroGridLoopSkipsAllIterations) +{ + // Every for-loop in write_elf is bounded by `ir < nrxx` or `ip < npw`; + // with nrxx == 0 the loop body must execute zero times. + int iterations = 0; + const int nrxx = 0; + for (int ir = 0; ir < nrxx; ++ir) + { + ++iterations; + } + EXPECT_EQ(iterations, 0); + + iterations = 0; + const int npw = 0; + for (int ip = 0; ip < npw; ++ip) + { + ++iterations; + } + EXPECT_EQ(iterations, 0); +} + +TEST_F(ElfLogicTest, ZeroGridElfValuesRemainInitialised) +{ + // On an empty partition, `elf` and `tau_vw` are all-zero initialised by + // the std::vector constructor and never updated, so every element stays + // at the supplied default. + const int nrxx = 0; + const int nspin_eff = 1; + + std::vector> elf(nspin_eff, std::vector(nrxx, 0.0)); + + EXPECT_EQ(elf.size(), 1u); + EXPECT_EQ(elf[0].size(), 0u); + EXPECT_EQ(elf[0].data(), nullptr); + + // For a non-empty partition the vector still initialises to zero, then + // the computation fills it, documenting the contract contrast. + const int nrxx_nonempty = 8; + std::vector> elf_nonempty(nspin_eff, + std::vector(nrxx_nonempty, 0.0)); + EXPECT_EQ(elf_nonempty[0].size(), 8u); + EXPECT_NE(elf_nonempty[0].data(), nullptr); + for (int i = 0; i < nrxx_nonempty; ++i) + { + EXPECT_DOUBLE_EQ(elf_nonempty[0][i], 0.0); + } +} + int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); From e141cc2f4b3e72e30af6d244195769938d2673f0 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 21 Oct 2026 20:01:43 +0800 Subject: [PATCH 41/61] refactor(io): rename orbital info output from Orbital to nao.txt The 'Orbital' file only describes the NAO basis orbitals of the cell (index, species, l, m, zeta, symmetry name) and is consumed by the projected-band and Mulliken outputs. Rename it to 'nao.txt' so the name states the basis type and does not collide conceptually with plane-wave outputs. Update both writers, the affected unit tests, the pband integration comparison script and its golden reference, and the Mulliken docs. --- docs/advanced/elec_properties/Mulliken.md | 2 +- source/source_cell/cell_index.cpp | 2 +- source/source_cell/test/cell_index_test.cpp | 4 ++-- source/source_io/module_output/write_orb_info.cpp | 2 +- source/source_io/test/output_mulliken_test.cpp | 4 ++-- source/source_io/test/write_orb_info_test.cpp | 4 ++-- .../nscf_out_band_pband/{refOrbital => refNaoTxt} | 0 tests/integrate/validation_tools/props_mat.sh | 4 ++-- 8 files changed, 11 insertions(+), 11 deletions(-) rename tests/03_NAO_multik/nscf_out_band_pband/{refOrbital => refNaoTxt} (100%) diff --git a/docs/advanced/elec_properties/Mulliken.md b/docs/advanced/elec_properties/Mulliken.md index 0b5163281f8..afb585d0e29 100644 --- a/docs/advanced/elec_properties/Mulliken.md +++ b/docs/advanced/elec_properties/Mulliken.md @@ -64,4 +64,4 @@ For each atom, the file gives detailed Mulliken population analysis at different - atomic level: such as lines begin with 'sum lmz' for the atom total and 'total charge on atom' for the charge assigned to the atom. -More orbital information can be found in 'Orbital' file output with 'mulliken.txt' when `out_mul 1` +More orbital information can be found in 'nao.txt' file output with 'mulliken.txt' when `out_mul 1` diff --git a/source/source_cell/cell_index.cpp b/source/source_cell/cell_index.cpp index c147afa936f..f6b9d3d8362 100644 --- a/source/source_cell/cell_index.cpp +++ b/source/source_cell/cell_index.cpp @@ -264,7 +264,7 @@ void CellIndex::cal_orbitalCounts() void CellIndex::write_orb_info(const std::string& out_dir) const { std::stringstream os; - os << out_dir << "Orbital"; + os << out_dir << "nao.txt"; std::ofstream out(os.str().c_str()); out << std::setw(5) << "#io" << std::setw(8) << "spec" << std::setw(5) << "l" << std::setw(5) << "m" << std::setw(5) << "z" << std::setw(5) << "sym" << std::endl; diff --git a/source/source_cell/test/cell_index_test.cpp b/source/source_cell/test/cell_index_test.cpp index e0767ad6aaf..06a932c92d7 100644 --- a/source/source_cell/test/cell_index_test.cpp +++ b/source/source_cell/test/cell_index_test.cpp @@ -56,11 +56,11 @@ TEST_F(CellIndexTest, Index) TEST_F(CellIndexTest, WriteOrbInfo) { cell_index.write_orb_info("./"); - std::ifstream ifs("./Orbital"); + std::ifstream ifs("./nao.txt"); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); EXPECT_THAT(str, testing::HasSubstr("#io spec l m z sym")); EXPECT_THAT(str, testing::HasSubstr("0 C 2 4 1 dxy")); EXPECT_THAT(str, testing::HasSubstr("1 H 2 4 1 dxy")); EXPECT_THAT(str, testing::HasSubstr("2 H 2 4 1 dxy")); - remove("./Orbital"); + remove("./nao.txt"); } \ No newline at end of file diff --git a/source/source_io/module_output/write_orb_info.cpp b/source/source_io/module_output/write_orb_info.cpp index 665e12e9f86..8f6117a6a9c 100644 --- a/source/source_io/module_output/write_orb_info.cpp +++ b/source/source_io/module_output/write_orb_info.cpp @@ -7,7 +7,7 @@ void ModuleIO::write_orb_info(const UnitCell* ucell) { std::stringstream os; - os << PARAM.globalv.global_out_dir << "Orbital"; + os << PARAM.globalv.global_out_dir << "nao.txt"; std::ofstream out(os.str().c_str()); out << std::setw(5) << "#io" << std::setw(8) << "spec" diff --git a/source/source_io/test/output_mulliken_test.cpp b/source/source_io/test/output_mulliken_test.cpp index 2d4b0800d02..6e7ab943a19 100644 --- a/source/source_io/test/output_mulliken_test.cpp +++ b/source/source_io/test/output_mulliken_test.cpp @@ -27,11 +27,11 @@ TYPED_TEST(OutputMullikenTest, OrbInfo) { CellIndex cell_index = CellIndex(this->atomLabels, this->atomCounts, this->lnchiCounts, 1); cell_index.write_orb_info("./"); - std::ifstream ifs("./Orbital"); + std::ifstream ifs("./nao.txt"); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); EXPECT_THAT(str, testing::HasSubstr("#io spec l m z sym")); EXPECT_THAT(str, testing::HasSubstr("0 Si 2 3 1 dx^2-y^2")); - remove("./Orbital"); + remove("./nao.txt"); } #ifdef __MPI diff --git a/source/source_io/test/write_orb_info_test.cpp b/source/source_io/test/write_orb_info_test.cpp index 6795439f743..2f8d4876dc6 100644 --- a/source/source_io/test/write_orb_info_test.cpp +++ b/source/source_io/test/write_orb_info_test.cpp @@ -50,7 +50,7 @@ TEST(OrbInfo,WriteOrbInfo) unitcell::cal_nwfc(ofs,*ucell,ucell->atoms, nspin, nlocal, npol, basis_type, esolver_type, init_wfc, nbands); ModuleIO::write_orb_info(ucell); ofs.close(); - std::ifstream ifs("Orbital"); + std::ifstream ifs("nao.txt"); std::string str((std::istreambuf_iterator(ifs)),std::istreambuf_iterator()); EXPECT_THAT(str, testing::HasSubstr("#io spec l m z sym")); EXPECT_THAT(str, testing::HasSubstr("0 Si 0 0 1 s")); @@ -58,7 +58,7 @@ TEST(OrbInfo,WriteOrbInfo) EXPECT_THAT(str, testing::HasSubstr("#sym =Symmetry name of real orbital")); ifs.close(); delete ucell; - std::remove("Orbital"); + std::remove("nao.txt"); std::remove("running.log"); } diff --git a/tests/03_NAO_multik/nscf_out_band_pband/refOrbital b/tests/03_NAO_multik/nscf_out_band_pband/refNaoTxt similarity index 100% rename from tests/03_NAO_multik/nscf_out_band_pband/refOrbital rename to tests/03_NAO_multik/nscf_out_band_pband/refNaoTxt diff --git a/tests/integrate/validation_tools/props_mat.sh b/tests/integrate/validation_tools/props_mat.sh index bc345fee57c..e9affd4093f 100644 --- a/tests/integrate/validation_tools/props_mat.sh +++ b/tests/integrate/validation_tools/props_mat.sh @@ -69,8 +69,8 @@ fi # echo $out_pband #------------------------------- if ! test -z "$out_pband" && [ $out_pband == 1 ]; then - orbref=refOrbital - orbcal=OUT.autotest/Orbital + orbref=refNaoTxt + orbcal=OUT.autotest/nao.txt python3 $COMPARE_SCRIPT $orbref $orbcal 8 echo "CompareOrb_pass $?" >>$props_result_file fi From 5f944eaeb6e184d9d41c6ba99928e0abcaea1f22 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 21 Oct 2026 20:40:47 +0800 Subject: [PATCH 42/61] feat(io): add provenance header to text LCAO wavefunction output wfk*_nao.txt now carries the same three-line header as bands1.txt (version, timestamp, ionic step with spin tag) before the data block, with one header per frame in append mode. The reader skips leading '#' comment lines on the ASCII path only; the binary record layout is untouched. Downstream Python consumers of the text format filter '#' lines so they tolerate the new header. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com --- interfaces/Multiwfn_interface/molden.py | 4 + source/source_io/module_wf/read_wfc_nao.cpp | 40 +++++ source/source_io/module_wf/write_wfc_nao.cpp | 113 +++++++++++++- source/source_io/module_wf/write_wfc_nao.h | 38 ++++- source/source_io/test/read_wfc_nao_test.cpp | 85 +++++++++-- source/source_io/test/write_wfc_nao_test.cpp | 141 +++++++++++++++++- .../validation_tools/check_librpa_producer.py | 8 +- .../validation_tools/librpa_wavefunctions.py | 3 +- tools/01_NAO_generation/qo/postprocess.py | 3 +- 9 files changed, 407 insertions(+), 28 deletions(-) diff --git a/interfaces/Multiwfn_interface/molden.py b/interfaces/Multiwfn_interface/molden.py index 1935267a2c9..856cf55aad9 100644 --- a/interfaces/Multiwfn_interface/molden.py +++ b/interfaces/Multiwfn_interface/molden.py @@ -533,6 +533,10 @@ def read_abacus_lowf(flowf, pat=r'^(WFC_NAO_(K\d+|GAMMA\d+)(_ION\d+)?|wf((k\d+)? with open(flowf, 'r') as file: lines = file.readlines() + # ignore provenance comment lines (version/timestamp/ionic step) that + # ABACUS writes before the data block + lines = [line for line in lines if not line.lstrip().startswith('#')] + if lines[0].endswith('(index of k points)\n'): # discard the first two lines lines = lines[2:] diff --git a/source/source_io/module_wf/read_wfc_nao.cpp b/source/source_io/module_wf/read_wfc_nao.cpp index 2459e9de560..d0fe553bd32 100644 --- a/source/source_io/module_wf/read_wfc_nao.cpp +++ b/source/source_io/module_wf/read_wfc_nao.cpp @@ -7,6 +7,7 @@ #include "source_base/tool_title.h" // use title #include "source_base/global_function.h" // use READ_VALUE +#include #include #include @@ -20,6 +21,36 @@ bool read_binary_value(std::ifstream& ifs, T& value) return static_cast(ifs); } +/** + * @brief skip leading comment lines starting with '#' in a text stream. + * + * Text wavefunction files may carry a provenance header (ABACUS version, + * timestamp, ionic step) before the data block. The stream is left positioned + * at the first non-comment character. Must not be used on binary streams, + * where '#' is legitimate record data. + * + * @param ifs text input stream positioned at the start of the file + */ +void skip_comment_lines(std::ifstream& ifs) +{ + std::string line; + while (ifs.peek() != std::char_traits::eof()) + { + const std::streampos pos = ifs.tellg(); + ifs >> std::ws; + if (ifs.peek() == '#') + { + std::getline(ifs, line); + } + else + { + ifs.seekg(pos); + ifs.clear(); + break; + } + } +} + template bool read_record_value(std::ifstream& ifs, T& value, const bool binary) { @@ -102,6 +133,10 @@ bool ModuleIO::read_wfc_nao_spin_populations(const std::string& readin_dir, ik, ik2iktot, nspin, nkstot, read_type, false, gamma_only, -1); const std::ios_base::openmode mode = binary ? std::ios::in | std::ios::binary : std::ios::in; std::ifstream ifs(file.c_str(), mode); + if (!binary) + { + skip_comment_lines(ifs); + } if (!gamma_only) { int ik_file = 0; @@ -262,6 +297,11 @@ bool ModuleIO::read_wfc_nao( std::cout << " Read NAO wave functions from " << ss << std::endl; } + if (!binary) + { + skip_comment_lines(ifs); + } + const auto incomplete_file = [&](const std::string& field) { error_message << "The wave function file is incomplete or corrupted while reading " << field << ": " << ss << std::endl; diff --git a/source/source_io/module_wf/write_wfc_nao.cpp b/source/source_io/module_wf/write_wfc_nao.cpp index b468cc4c6c1..5e98bc0b2be 100644 --- a/source/source_io/module_wf/write_wfc_nao.cpp +++ b/source/source_io/module_wf/write_wfc_nao.cpp @@ -10,6 +10,89 @@ #include "source_base/global_function.h" #include "source_base/module_out/binstream.h" #include "source_base/module_out/filename.h" +#include "source_base/formatter.h" +#include "source_main/version.h" + +#include + +namespace +{ + +/** + * @brief compact spin-channel label for the per-file provenance header. + * + * The header goes into a per-spin file (e.g. wfk1s1_nao.txt), so the compact + * form " (spin up)" is used, matching the bands1.txt/bands2.txt header. + * + * @param is spin channel index, -1 when undefined + * @param nspin number of spins; the label is dropped unless nspin == 2 + * @return " (spin up)", " (spin down)" or an empty string + */ +std::string wfc_spin_tag(const int is, const int nspin) +{ + if (nspin != 2 || is < 0) + { + return ""; + } + return (is == 0) ? " (spin up)" : " (spin down)"; +} + +/** + * @brief spin channel of a global k-point index, consistent with the s{N} + * block of filename_output. + * + * @param ik0 global k-point index (the ik2iktot value) + * @param nspin number of spins + * @param nkstot total number of k-points including the spin multiplicity + * @return 0 for spin up, 1 for spin down, -1 when nspin != 2 + */ +int wfc_spin_index(const int ik0, const int nspin, const int nkstot) +{ + if (nspin != 2) + { + return -1; + } + const int half_k = nkstot / 2; + if (ik0 >= half_k) + { + return 1; + } + return 0; +} + +/** + * @brief build the provenance header that precedes the wavefunction data + * block, mirroring the bands1.txt/bands2.txt header style. + * + * In append mode (out_app_flag == 1) every accumulated frame carries its own + * header, so readers can tell the ionic steps apart. + * + * @param istep zero-based ionic step index, printed one-based; the ionic-step + * line is omitted when istep < 0 (no step context in the run) + * @param spin_tag label of the collinear spin channel, empty when nspin != 2 + * @return two or three comment lines terminated by newlines + */ +std::string build_wfc_nao_header(const int istep, const std::string& spin_tag) +{ + const std::time_t now = std::time(nullptr); + std::tm tm_local; + localtime_r(&now, &tm_local); + char time_buf[64]; + std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", &tm_local); + if (istep < 0) + { + return FmtCore::format("# ABACUS version: %s\n# Written at %s\n", + VERSION, + time_buf); + } + return FmtCore::format("# ABACUS version: %s\n# Written at %s\n# IONIC STEP %d%s\n", + VERSION, + time_buf, + istep + 1, + spin_tag.c_str()); +} + +} // namespace namespace ModuleIO { @@ -21,7 +104,8 @@ void wfc_nao_write2file(const std::string& name, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, const bool& writeBinary, - const bool& append_flag) + const bool& append_flag, + const std::string& header) { ModuleBase::TITLE("ModuleIO", "wfc_nao_write2file"); ModuleBase::timer::start("ModuleIO", "wfc_nao_write2file"); @@ -75,6 +159,12 @@ void wfc_nao_write2file(const std::string& name, { ModuleBase::WARNING_QUIT("ModuleIO::wfc_nao_write2file", "Can't write local orbital wave functions."); } + // provenance header precedes the data block; the binary branch above + // keeps its fixed record layout and ignores the header + if (!header.empty()) + { + ofs << header; + } ofs << nbands << " (number of bands)" << std::endl; ofs << nlocal << " (number of orbitals)"; ofs << std::setprecision(8); @@ -112,7 +202,8 @@ void wfc_nao_write2file_complex(const std::string& name, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, const bool& writeBinary, - const bool& append_flag) + const bool& append_flag, + const std::string& header) { ModuleBase::TITLE("ModuleIO","wfc_nao_write2file_complex"); ModuleBase::timer::start("ModuleIO","wfc_nao_write2file_complex"); @@ -169,6 +260,12 @@ void wfc_nao_write2file_complex(const std::string& name, { ModuleBase::WARNING_QUIT("ModuleIO::wfc_nao_write2file_complex", "Can't write local orbital wave functions."); } + // provenance header precedes the k-point record; the binary branch + // above keeps its fixed record layout and ignores the header + if (!header.empty()) + { + ofs << header; + } ofs << std::setprecision(8); ofs << ik + 1 << " (index of k points)" << std::endl; ofs << kvec_c.x << " " << kvec_c.y << " " << kvec_c.z << std::endl; @@ -282,6 +379,12 @@ void write_wfc_nao(const int out_type, out_type,out_app_flag,gamma_only,istep); bool append_flag = (istep > 0 && out_app_flag == 1); + // spin channel of the current k-point and the per-frame + // provenance header (version, timestamp, ionic step, spin tag) + const int ik0 = ik2iktot[ik]; + const int is = wfc_spin_index(ik0, nspin, nkstot); + const std::string spin_tag = wfc_spin_tag(is, nspin); + const std::string header = build_wfc_nao_header(istep, spin_tag); if (std::is_same::value) { wfc_nao_write2file(fn, @@ -291,7 +394,8 @@ void write_wfc_nao(const int out_type, ekb, wg, writeBinary, - append_flag); + append_flag, + header); } else { @@ -303,7 +407,8 @@ void write_wfc_nao(const int out_type, ekb, wg, writeBinary, - append_flag); + append_flag, + header); } } } diff --git a/source/source_io/module_wf/write_wfc_nao.h b/source/source_io/module_wf/write_wfc_nao.h index 70dde0ae6fb..a1421404b6d 100644 --- a/source/source_io/module_wf/write_wfc_nao.h +++ b/source/source_io/module_wf/write_wfc_nao.h @@ -4,6 +4,7 @@ #include "source_basis/module_ao/parallel_orbitals.h" #include "source_psi/psi.h" #include "source_base/vector3.h" +#include #include namespace ModuleIO @@ -36,6 +37,20 @@ void write_wfc_nao(const int out_type, const int nspin, const int istep=-1) ; +/** + * @brief write real-valued Gamma-only wavefunction coefficients to a file. + * + * @param name output file name + * @param ctot global wavefunction coefficient array, size nbands*nlocal + * @param nlocal number of orbitals (basis functions) + * @param ik local k-point index, used to index ekb/wg rows + * @param ekb eigenvalue matrix + * @param wg occupation matrix + * @param writeBinary true for the binary format, false for text + * @param append_flag true to append to an existing file, false to truncate + * @param header provenance comment block written before the data in text + * mode; ignored in binary mode + */ void wfc_nao_write2file(const std::string& name, const double* ctot, const int nlocal, @@ -43,8 +58,24 @@ void wfc_nao_write2file(const std::string& name, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, const bool& writeBinary, - const bool& append_flag = false); + const bool& append_flag, + const std::string& header); +/** + * @brief write complex wavefunction coefficients to a file. + * + * @param name output file name + * @param ctot global wavefunction coefficient array, size nbands*nlocal + * @param nlocal number of orbitals (basis functions) + * @param ik local k-point index, used to index ekb/wg rows + * @param kvec_c Cartesian k-vector of the k-point + * @param ekb eigenvalue matrix + * @param wg occupation matrix + * @param writeBinary true for the binary format, false for text + * @param append_flag true to append to an existing file, false to truncate + * @param header provenance comment block written before the data in text + * mode; ignored in binary mode + */ void wfc_nao_write2file_complex(const std::string& name, const std::complex* ctot, const int nlocal, @@ -52,7 +83,8 @@ void wfc_nao_write2file_complex(const std::string& name, const ModuleBase::Vector3& kvec_c, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, - const bool& writeBinary = false, - const bool& append_flag = false); + const bool& writeBinary, + const bool& append_flag, + const std::string& header); }// namespace ModuleIO #endif diff --git a/source/source_io/test/read_wfc_nao_test.cpp b/source/source_io/test/read_wfc_nao_test.cpp index a087d37c415..8c0b9bbb18a 100644 --- a/source/source_io/test/read_wfc_nao_test.cpp +++ b/source/source_io/test/read_wfc_nao_test.cpp @@ -186,7 +186,8 @@ TEST_F(ReadWfcNaoTest, ReadBinaryGamma) ekb_source, wg_source, true, - false); + false, + ""); } #ifdef __MPI MPI_Barrier(MPI_COMM_WORLD); @@ -215,6 +216,64 @@ TEST_F(ReadWfcNaoTest, ReadBinaryGamma) } } +TEST_F(ReadWfcNaoTest, ReadTextWithCommentHeader) +{ + // Text files carrying the provenance header (version/timestamp/step) + // must stay readable: the reader skips leading '#' comment lines. + const int nbands = 2; + const int nlocal = 3; + Parallel_Orbitals para; + initialize_parallel_orbitals(para, nlocal, nbands); + + const std::vector coefficients = {0.1, 0.2, 0.3, 1.1, 1.2, 1.3}; + ModuleBase::matrix ekb_source(1, nbands); + ModuleBase::matrix wg_source(1, nbands); + ekb_source(0, 0) = -0.5; + ekb_source(0, 1) = 0.7; + wg_source(0, 0) = 2.0; + wg_source(0, 1) = 0.0; + if (my_rank == 0) + { + const std::string header = "# ABACUS version: test\n" + "# Written at 2026-01-01 00:00:00\n" + "# IONIC STEP 1 (spin up)\n"; + ModuleIO::wfc_nao_write2file(binary_test_dir + "wf_nao.txt", + coefficients.data(), + nlocal, + 0, + ekb_source, + wg_source, + false, + false, + header); + } +#ifdef __MPI + MPI_Barrier(MPI_COMM_WORLD); +#endif + + psi::Psi psi_read; + ModuleBase::matrix ekb(1, nbands); + ModuleBase::matrix wg(1, nbands); + const std::vector ik2iktot = {0}; + EXPECT_TRUE(ModuleIO::read_wfc_nao(binary_test_dir, + para, + psi_read, + ekb, + wg, + ik2iktot, + 1, + 1, + false)); + EXPECT_DOUBLE_EQ(ekb(0, 0), ekb_source(0, 0)); + EXPECT_DOUBLE_EQ(ekb(0, 1), ekb_source(0, 1)); + EXPECT_DOUBLE_EQ(wg(0, 0), wg_source(0, 0)); + EXPECT_DOUBLE_EQ(wg(0, 1), wg_source(0, 1)); + if (my_rank == 0) + { + EXPECT_DOUBLE_EQ(psi_read(0, 0, 0), coefficients[0]); + } +} + TEST_F(ReadWfcNaoTest, ReadBinaryGammaFloat) { const int nbands = 2; @@ -238,7 +297,8 @@ TEST_F(ReadWfcNaoTest, ReadBinaryGammaFloat) ekb_source, wg_source, true, - false); + false, + ""); } #ifdef __MPI MPI_Barrier(MPI_COMM_WORLD); @@ -292,7 +352,8 @@ TEST_F(ReadWfcNaoTest, ReadBinaryComplex) ekb_source, wg_source, true, - false); + false, + ""); } #ifdef __MPI MPI_Barrier(MPI_COMM_WORLD); @@ -349,7 +410,8 @@ TEST_F(ReadWfcNaoTest, ReadBinaryPart) ekb_source, wg_source, true, - false); + false, + ""); } #ifdef __MPI MPI_Barrier(MPI_COMM_WORLD); @@ -398,7 +460,8 @@ TEST_F(ReadWfcNaoTest, BinaryDoesNotFallBackToText) ekb_source, wg_source, false, - false); + false, + ""); } #ifdef __MPI MPI_Barrier(MPI_COMM_WORLD); @@ -505,7 +568,8 @@ TEST_F(ReadWfcNaoTest, ReadPsiDirectoryLayout) ekb_source, wg_source, true, - false); + false, + ""); } #ifdef __MPI MPI_Barrier(MPI_COMM_WORLD); @@ -547,7 +611,8 @@ TEST_F(ReadWfcNaoTest, ReadPsiDirectoryLayout) ekb_source, wg_source, true, - false); + false, + ""); std::ifstream ifs_rewritten(snapshot.c_str(), std::ios::binary); ifs_rewritten.seekg(0, std::ios::end); @@ -596,13 +661,13 @@ TEST_F(ReadWfcNaoTest, CompleteSpinPopulations) if (gamma_only) { ModuleIO::wfc_nao_write2file(file, real_coefficients.data(), nlocal, - ik, energies, occupations, binary, false); + ik, energies, occupations, binary, false, ""); } else { const ModuleBase::Vector3 kpoint(0.25, 0.0, 0.0); ModuleIO::wfc_nao_write2file_complex(file, complex_coefficients.data(), nlocal, - ik, kpoint, energies, occupations, binary, false); + ik, kpoint, energies, occupations, binary, false, ""); } } } @@ -645,7 +710,7 @@ TEST_F(ReadWfcNaoTest, CompleteGlobalKPointPopulations) { const ModuleBase::Vector3 kpoint(0.25 * ik, 0.0, 0.0); ModuleIO::wfc_nao_write2file_complex(file, coefficients.data(), nlocal, - ik, kpoint, energies, occupations, false, false); + ik, kpoint, energies, occupations, false, false, ""); } } std::vector populations; diff --git a/source/source_io/test/write_wfc_nao_test.cpp b/source/source_io/test/write_wfc_nao_test.cpp index e383ab9e1c8..73790bf442d 100644 --- a/source/source_io/test/write_wfc_nao_test.cpp +++ b/source/source_io/test/write_wfc_nao_test.cpp @@ -326,6 +326,51 @@ TEST_F(WriteWfcLcaoTest, WriteWfcLcaoComplex) } } +TEST_F(WriteWfcLcaoTest, WriteWfcLcaoTextHeader) +{ + const std::string out_dir = testing::TempDir(); + TestParameters::sys().global_out_dir = out_dir; + + const std::string property = "wf"; + const std::string basis = "nao"; + const int nspin = 2; + const int out_type = 1; + const int out_app_flag = 1; + const bool gamma_only = true; + const int istep = 0; + + psi::Psi my_psi(psi_local_double.data(), nk, nbands_local, nbasis_local, nbasis_local, true); + + ModuleIO::write_wfc_nao(out_type, out_app_flag, my_psi, ekb, wg, kvec_c, + ik2iktot, nkstot, pv, nspin, istep); + + if (GlobalV::MY_RANK == 0) + { + // ik2iktot[0] == 0 belongs to the first spin half of nkstot == 2, + // so the header must carry the one-based step and the spin tag + const std::string fname = ModuleIO::filename_output( + out_dir, property, basis, 0, ik2iktot, nspin, + nkstot, out_type, out_app_flag, gamma_only, istep); + + std::ifstream file1(fname); + ASSERT_TRUE(file1.good()); + std::string first_line; + std::getline(file1, first_line); + EXPECT_THAT(first_line, testing::HasSubstr("# ABACUS version:")); + std::string rest((std::istreambuf_iterator(file1)), std::istreambuf_iterator()); + EXPECT_THAT(rest, testing::HasSubstr("# IONIC STEP 1 (spin up)")); + EXPECT_THAT(rest, testing::HasSubstr("(number of bands)")); + file1.close(); + + std::remove(fname.c_str()); + // the second k-point belongs to the spin-down half; clean it up too + const std::string fname_down = ModuleIO::filename_output( + out_dir, property, basis, 1, ik2iktot, nspin, + nkstot, out_type, out_app_flag, gamma_only, istep); + std::remove(fname_down.c_str()); + } +} + TEST(ModuleIOTest, WriteWfcNao) { if (GlobalV::MY_RANK == 0) @@ -350,7 +395,7 @@ TEST(ModuleIOTest, WriteWfcNao) wg(1, 1) = 1.2; // Call the function to be tested - ModuleIO::wfc_nao_write2file(filename, ctot.data(), TestParameters::sys().nlocal, 0, ekb, wg, false); + ModuleIO::wfc_nao_write2file(filename, ctot.data(), TestParameters::sys().nlocal, 0, ekb, wg, false, false, ""); // Check the output file std::ifstream ifs(filename); @@ -372,6 +417,50 @@ TEST(ModuleIOTest, WriteWfcNao) } } +TEST(ModuleIOTest, WriteWfcNaoHeader) +{ + if (GlobalV::MY_RANK == 0) + { + // Set up GlobalV + GlobalV::DRANK = 0; + TestParameters::input().nbands = 2; + TestParameters::sys().nlocal = 2; + + const std::string filename = std::string(testing::TempDir()) + "test_wfc_nao_header.txt"; + std::vector ctot = {0.1, 0.2, 0.3, 0.4}; + ModuleBase::matrix ekb(2, 2); + ekb(0, 0) = 0.5; + ekb(1, 0) = 0.6; + ekb(0, 1) = 0.7; + ekb(1, 1) = 0.8; + ModuleBase::matrix wg(2, 2); + wg(0, 0) = 0.9; + wg(1, 0) = 1.0; + wg(0, 1) = 1.1; + wg(1, 1) = 1.2; + + const std::string header = "# ABACUS version: test\n" + "# Written at 2026-01-01 00:00:00\n" + "# IONIC STEP 1 (spin up)\n"; + + // Call the function to be tested + ModuleIO::wfc_nao_write2file(filename, ctot.data(), TestParameters::sys().nlocal, 0, ekb, wg, false, false, header); + + // Check the output file: the provenance header precedes the data block + std::ifstream ifs(filename); + std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + EXPECT_THAT(str, testing::HasSubstr("# ABACUS version: test\n")); + EXPECT_THAT(str, testing::HasSubstr("# Written at 2026-01-01 00:00:00\n")); + EXPECT_THAT(str, testing::HasSubstr("# IONIC STEP 1 (spin up)\n")); + EXPECT_THAT(str, testing::HasSubstr("2 (number of bands)")); + EXPECT_THAT(str, testing::HasSubstr("1.00000000e-01 2.00000000e-01")); + ifs.close(); + + // clean up + std::remove(filename.c_str()); + } +} + TEST(ModuleIOTest, WriteWfcNaoBinary) { if (GlobalV::MY_RANK == 0) @@ -396,7 +485,7 @@ TEST(ModuleIOTest, WriteWfcNaoBinary) wg(1, 1) = 1.2; // Call the function to be tested - ModuleIO::wfc_nao_write2file(filename, ctot.data(), TestParameters::sys().nlocal, 0, ekb, wg, true); + ModuleIO::wfc_nao_write2file(filename, ctot.data(), TestParameters::sys().nlocal, 0, ekb, wg, true, false, ""); // Check the output file Binstream wfc(filename, "r"); @@ -429,6 +518,46 @@ TEST(ModuleIOTest, WriteWfcNaoBinary) } } +TEST(ModuleIOTest, WriteWfcNaoBinaryIgnoresHeader) +{ + if (GlobalV::MY_RANK == 0) + { + // Set up GlobalV + GlobalV::DRANK = 0; + TestParameters::input().nbands = 2; + TestParameters::sys().nlocal = 2; + + const std::string filename = std::string(testing::TempDir()) + "test_wfc_nao_header.dat"; + std::vector ctot = {0.1, 0.2, 0.3, 0.4}; + ModuleBase::matrix ekb(2, 2); + ekb(0, 0) = 0.5; + ekb(1, 0) = 0.6; + ekb(0, 1) = 0.7; + ekb(1, 1) = 0.8; + ModuleBase::matrix wg(2, 2); + wg(0, 0) = 0.9; + wg(1, 0) = 1.0; + wg(0, 1) = 1.1; + wg(1, 1) = 1.2; + + const std::string header = "# ABACUS version: test\n"; + ModuleIO::wfc_nao_write2file(filename, ctot.data(), TestParameters::sys().nlocal, 0, ekb, wg, true, false, header); + + // the binary record must start with the band count, not the header + Binstream wfc(filename, "r"); + int nbands = 0; + int nlocal = 0; + wfc >> nbands; + wfc >> nlocal; + EXPECT_EQ(nbands, 2); + EXPECT_EQ(nlocal, 2); + wfc.close(); + + // clean up + std::remove(filename.c_str()); + } +} + TEST(ModuleIOTest, WriteWfcNaoComplex) { if (GlobalV::MY_RANK == 0) @@ -454,7 +583,7 @@ TEST(ModuleIOTest, WriteWfcNaoComplex) std::complex(0.0, 3.0)}; // Call the function - ModuleIO::wfc_nao_write2file_complex(name, ctot.data(), TestParameters::sys().nlocal, ik, kvec_c, ekb, wg); + ModuleIO::wfc_nao_write2file_complex(name, ctot.data(), TestParameters::sys().nlocal, ik, kvec_c, ekb, wg, false, false, ""); // Check the output file std::ifstream ifs(name); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -497,7 +626,7 @@ TEST(ModuleIOTest, WriteWfcNaoComplexBinary) std::complex(6.0, 8.0)}; // Call the function - ModuleIO::wfc_nao_write2file_complex(name, ctot.data(), TestParameters::sys().nlocal, ik, kvec_c, ekb, wg, true); + ModuleIO::wfc_nao_write2file_complex(name, ctot.data(), TestParameters::sys().nlocal, ik, kvec_c, ekb, wg, true, false, ""); // Check the output file Binstream wfc(name, "r"); @@ -556,7 +685,7 @@ TEST(ModuleIOTest, WriteWfcNaoBinaryOpenFail) ModuleBase::matrix wg(2, 2); testing::internal::CaptureStdout(); - EXPECT_EXIT(ModuleIO::wfc_nao_write2file(filename, ctot.data(), nlocal, 0, ekb, wg, true), + EXPECT_EXIT(ModuleIO::wfc_nao_write2file(filename, ctot.data(), nlocal, 0, ekb, wg, true, false, ""), ::testing::ExitedWithCode(1), ""); std::string output = testing::internal::GetCapturedStdout(); @@ -587,7 +716,7 @@ TEST(ModuleIOTest, WriteWfcNaoComplexBinaryOpenFail) std::complex(0.0, 3.0)}; testing::internal::CaptureStdout(); - EXPECT_EXIT(ModuleIO::wfc_nao_write2file_complex(name, ctot.data(), nlocal, ik, kvec_c, ekb, wg, true), + EXPECT_EXIT(ModuleIO::wfc_nao_write2file_complex(name, ctot.data(), nlocal, ik, kvec_c, ekb, wg, true, false, ""), ::testing::ExitedWithCode(1), ""); std::string output = testing::internal::GetCapturedStdout(); diff --git a/tests/integrate/validation_tools/check_librpa_producer.py b/tests/integrate/validation_tools/check_librpa_producer.py index ee8a47e6e98..3396870cdf9 100644 --- a/tests/integrate/validation_tools/check_librpa_producer.py +++ b/tests/integrate/validation_tools/check_librpa_producer.py @@ -63,8 +63,9 @@ def _numeric_tokens(path): def _compare_text(actual, reference, tolerance): - actual_lines = actual.read_text().splitlines() - reference_lines = reference.read_text().splitlines() + # ignore provenance comment lines starting with '#' on both sides + actual_lines = [line for line in actual.read_text().splitlines() if not line.lstrip().startswith("#")] + reference_lines = [line for line in reference.read_text().splitlines() if not line.lstrip().startswith("#")] # 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. @@ -153,7 +154,8 @@ def _check_stru(path, minimum_symmetry_rows, spin_symmetry_rows=None): raise ProducerContractError(str(error)) 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()] + # ignore provenance comment lines starting with '#' + lines = [line.strip() for line in path.read_text().splitlines() if line.strip() and not line.strip().startswith("#")] if len(lines) < 6: raise ProducerContractError("{} is too short for an LCAO wavefunction".format(path)) diff --git a/tests/integrate/validation_tools/librpa_wavefunctions.py b/tests/integrate/validation_tools/librpa_wavefunctions.py index 15303a35ef3..f643b0d647d 100644 --- a/tests/integrate/validation_tools/librpa_wavefunctions.py +++ b/tests/integrate/validation_tools/librpa_wavefunctions.py @@ -41,7 +41,8 @@ def check_ks_nao(path, nao_dir, tolerance, spinor=False): else: name = "wfk{}_nao.txt".format(ik) if nspin == 1 else "wfk{}s{}_nao.txt".format(ik, spin) source = nao_dir / name - lines = [line.strip() for line in source.read_text().splitlines() if line.strip()] + # ignore provenance comment lines starting with '#' + lines = [line.strip() for line in source.read_text().splitlines() if line.strip() and not line.strip().startswith("#")] if len(lines) < 4 or "(index of k points)" not in lines[0]: raise ValueError("{} has an invalid text KS header".format(source)) text_index = ik if spinor else ik + (spin - 1) * nk diff --git a/tools/01_NAO_generation/qo/postprocess.py b/tools/01_NAO_generation/qo/postprocess.py index d4b7aeae615..f90a2d7238d 100644 --- a/tools/01_NAO_generation/qo/postprocess.py +++ b/tools/01_NAO_generation/qo/postprocess.py @@ -72,7 +72,8 @@ def read_lowf(flowf): np.ndarray: k-vector """ with open(flowf, 'r') as f: - lines = [line.strip() for line in f.readlines()] + # ignore provenance comment lines starting with '#' + lines = [line.strip() for line in f.readlines() if line.strip() and not line.strip().startswith('#')] for line in lines: if line.endswith("(number of bands)"): nband = int(line.split()[0]) From fe2b62f8aa2159d2a19c32222f9ee92b4eff1875 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 21 Oct 2026 21:04:18 +0800 Subject: [PATCH 43/61] test(lr): expect one-based STEP header in sparse DMR writer test Align test_lr_util.cpp with governance rule 22: hs_sparse_io writes STEP: max(istep,0)+1, and LR_Util::save_HR passes istep = 0, so the header is "STEP: 1". Fixes MODULE_LR_dm_regressions and MODULE_LR_sparse_rank_keys. Co-Authored-By: Claude Sonnet 4.6 --- source/source_lcao/module_lr/test/test_lr_util.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/source_lcao/module_lr/test/test_lr_util.cpp b/source/source_lcao/module_lr/test/test_lr_util.cpp index 7aaae902029..928fd4f3414 100644 --- a/source/source_lcao/module_lr/test/test_lr_util.cpp +++ b/source/source_lcao/module_lr/test/test_lr_util.cpp @@ -237,7 +237,8 @@ TEST_F(LRDMTest, SparseWriterKeepsSpinFilesAndDisjointRankKeys) ASSERT_TRUE(input.is_open()); std::string line; std::getline(input, line); - EXPECT_EQ(line, "STEP: 0"); + // Header step is one-based per governance rule 22 (writer receives istep = 0). + EXPECT_EQ(line, "STEP: 1"); std::getline(input, line); EXPECT_EQ(line, "Matrix Dimension of EDM(R): " + std::to_string(dimension)); std::getline(input, line); From 603824f7dcbf44b4cd5a8348ace2a3bc86d77466 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 21 Oct 2026 21:47:31 +0800 Subject: [PATCH 44/61] feat(io): append cube frames per ionic step under out_app_flag 1 With out_app_flag = 1 the multi-step writers reuse a fixed filename, so write_cube truncated the previous snapshot and only the last written step survived in chg/tau/pot/elf cube files. Text outputs already append records under the same flag, and out_wfc_lcao already truncates on the first step and appends afterwards, so cube output was the remaining inconsistency. Pass an explicit append flag down the writer chain instead of reading PARAM inside the writers: - write_cube / write_vdata_palgrid take a new append parameter and open the file with std::ios::app when it is set, otherwise truncate; - write_elf and write_elecstat_pot forward it, and write_chg_init / write_pot_init compute it from inp.out_app_flag; - ctrl_output_fp uses append = (out_app_flag == 1 && istep > 0), mirroring the out_wfc_lcao rule: the first written ionic step truncates, later written steps append one complete frame whose first comment line is Ionic_Step {#}, one-based. Single-point SCF/NSCF stays at istep == 0, so it keeps overwriting rather than stacking on a stale file from a rerun. - every other call site (LDOS, LCAO/PW wavefunction cubes, partial charge, LR-TDDFT density, exciton density, DM2rho) passes false explicitly to keep its existing truncate behaviour. Update the out_app_flag and out_elf help text, the output specification, and add CubeIOTest.WriteCubeAppendFrames covering truncate-then-append-then- truncate frame counting and the trailing newline contract. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com> --- .../output_files/output-specification.md | 6 ++ source/source_esolver/esolver_dm2rho.cpp | 1 + source/source_estate/write_elecstat_pot.cpp | 2 + source/source_estate/write_elecstat_pot.h | 4 +- source/source_estate/write_init.cpp | 14 +++- .../source_io/module_chgpot/get_pchg_lcao.cpp | 6 +- .../source_io/module_chgpot/get_pchg_pw.cpp | 2 +- .../source_io/module_ctrl/ctrl_output_fp.cpp | 18 ++++- source/source_io/module_dos/cal_ldos.cpp | 3 +- source/source_io/module_elf/write_elf.cpp | 4 + source/source_io/module_elf/write_elf.h | 1 + source/source_io/module_output/cube_io.h | 7 ++ source/source_io/module_output/write_cube.cpp | 16 +++- .../module_parameter/read_inp_out.cpp | 4 +- source/source_io/module_wf/get_wf_lcao.cpp | 7 +- source/source_io/module_wf/get_wf_pw.cpp | 2 +- source/source_io/test_serial/rho_io_test.cpp | 78 ++++++++++++++++++- source/source_lcao/module_lr/lr_density.cpp | 2 +- .../module_lr/utils/exciton_plotter.cpp | 1 + 19 files changed, 158 insertions(+), 20 deletions(-) diff --git a/docs/advanced/output_files/output-specification.md b/docs/advanced/output_files/output-specification.md index 99d177824dc..55a2b6babb5 100644 --- a/docs/advanced/output_files/output-specification.md +++ b/docs/advanced/output_files/output-specification.md @@ -55,6 +55,12 @@ All output file naming conventions can be found in the online documentation (wit - All index numbers start from 1 (not 0) - For Gamma-only algorithm in LCAO, no `k` index is included - Overlap matrix `s` does not distinguish spin, so only one matrix is output +- `out_app_flag = 1` omits the `g#` suffix for cube files (chg/tau/pot/elf): the + first written ionic step truncates the fixed-name file and every later + written step appends one complete cube frame whose first comment line is + `Ionic_Step {#}` (one-based). `out_app_flag = 0` writes one suffixed file per + step, and `out_app_flag = 2` writes those suffixed files into the `CHG/`, + `POT/` and `ELF/` subdirectories - For initial charge density output (`out_chg = 2`): - `out_freq_ion = 0`: `chg_ini.cube` (nspin=1) or `chgs{#}_ini.cube` (nspin=2/4) - output at every step (overwrite same file) - `out_freq_ion > 0`: `chgg{#}_ini.cube` (nspin=1) or `chgs{#}g{#}_ini.cube` (nspin=2/4) - output every out_freq_ion steps diff --git a/source/source_esolver/esolver_dm2rho.cpp b/source/source_esolver/esolver_dm2rho.cpp index 77b7825cc34..6c5dc31302a 100644 --- a/source/source_esolver/esolver_dm2rho.cpp +++ b/source/source_esolver/esolver_dm2rho.cpp @@ -99,6 +99,7 @@ void ESolver_DM2rho::runner(BaseCell& basecell, const int istep) 1, PARAM.globalv.two_fermi, false, + false, GlobalV::ofs_running, desc_chg); } diff --git a/source/source_estate/write_elecstat_pot.cpp b/source/source_estate/write_elecstat_pot.cpp index 5f0d0d68c37..77cba62621b 100644 --- a/source/source_estate/write_elecstat_pot.cpp +++ b/source/source_estate/write_elecstat_pot.cpp @@ -24,6 +24,7 @@ void write_elecstat_pot( const surchem& solvent, const std::vector* pcc_potential, const int precision, + const bool append, std::ofstream& ofs_running) { ModuleBase::TITLE("ModuleIO", "write_elecstat_pot"); @@ -112,6 +113,7 @@ void write_elecstat_pot( out_fermi, PARAM.globalv.two_fermi, false, + append, ofs_running, "electrostatic potential"); diff --git a/source/source_estate/write_elecstat_pot.h b/source/source_estate/write_elecstat_pot.h index 79eb72cd698..d785b0ef4fe 100644 --- a/source/source_estate/write_elecstat_pot.h +++ b/source/source_estate/write_elecstat_pot.h @@ -21,7 +21,8 @@ namespace ModuleIO /// @param ucell_ /// @param v_eff_fixed /// @param solvent: for solvation model -/// #param precision: output precision +/// @param precision: output precision +/// @param append: append a frame to an existing cube file (out_app_flag == 1) void write_elecstat_pot( #ifdef __MPI const int& bz, @@ -36,6 +37,7 @@ void write_elecstat_pot( const surchem& solvent, const std::vector* pcc_potential, const int precision, + const bool append, std::ofstream& ofs_running); } // namespace ModuleIO diff --git a/source/source_estate/write_init.cpp b/source/source_estate/write_init.cpp index d496e5eecaa..a6defbe0f72 100644 --- a/source/source_estate/write_init.cpp +++ b/source/source_estate/write_init.cpp @@ -83,9 +83,11 @@ void ModuleIO::write_chg_init(const UnitCell& ucell, if (should_output) { - // In append mode (out_app_flag == 1) each snapshot overwrites - // the same fixed file, so the geometry-step index is omitted. + // In append mode (out_app_flag == 1) each written step reuses the + // same fixed filename and contributes one complete cube frame, so + // the geometry-step index is omitted and frames accumulate. const bool include_geom_step = (output_frequency > 0) && (inp.out_app_flag != 1); + const bool append = (inp.out_app_flag == 1) && (istep > 0); for (int is = 0; is < nspin; is++) { @@ -122,6 +124,7 @@ void ModuleIO::write_chg_init(const UnitCell& ucell, 1, two_fermi, false, + append, ofs_running, desc_chg); } @@ -154,9 +157,11 @@ void ModuleIO::write_pot_init(const UnitCell& ucell, if (should_output) { - // In append mode (out_app_flag == 1) each snapshot overwrites - // the same fixed file, so the geometry-step index is omitted. + // In append mode (out_app_flag == 1) each written step reuses the + // same fixed filename and contributes one complete cube frame, so + // the geometry-step index is omitted and frames accumulate. const bool include_geom_step = (output_frequency > 0) && (inp.out_app_flag != 1); + const bool append = (inp.out_app_flag == 1) && (istep > 0); for (int is = 0; is < nspin; is++) { @@ -176,6 +181,7 @@ void ModuleIO::write_pot_init(const UnitCell& ucell, 0, two_fermi, false, + append, ofs_running, desc_pot); } diff --git a/source/source_io/module_chgpot/get_pchg_lcao.cpp b/source/source_io/module_chgpot/get_pchg_lcao.cpp index 3ececff4b1f..9add37b9758 100644 --- a/source/source_io/module_chgpot/get_pchg_lcao.cpp +++ b/source/source_io/module_chgpot/get_pchg_lcao.cpp @@ -80,7 +80,7 @@ void Get_pchg_lcao::begin_gamma(const UnitCell& ucell, const std::string desc_pchg = ModuleIO::make_data_desc("partial charge", "partial magnetization m", is, nspin_); - ModuleIO::write_vdata_palgrid(pgrid, rho[is].data(), is, nspin_, 0, ssc.str(), 0.0, &ucell, precision, 0, false, false, ofs_running, desc_pchg); + ModuleIO::write_vdata_palgrid(pgrid, rho[is].data(), is, nspin_, 0, ssc.str(), 0.0, &ucell, precision, 0, false, false, false, ofs_running, desc_pchg); } } } @@ -172,7 +172,7 @@ void Get_pchg_lcao::begin_k(const ModulePW::PW_Basis& rho_pw, const std::string desc_pchg = ModuleIO::make_data_desc("partial charge", "partial magnetization m", is, nspin_); - ModuleIO::write_vdata_palgrid(pgrid, rho[is].data(), is, nspin_, 0, ssc.str(), 0.0, &ucell, precision, 0, false, false, ofs_running, desc_pchg); + ModuleIO::write_vdata_palgrid(pgrid, rho[is].data(), is, nspin_, 0, ssc.str(), 0.0, &ucell, precision, 0, false, false, false, ofs_running, desc_pchg); } } } @@ -212,7 +212,7 @@ void Get_pchg_lcao::begin_k(const ModulePW::PW_Basis& rho_pw, const std::string desc_pchg = ModuleIO::make_data_desc("partial charge", "partial magnetization m", is, nspin_); - ModuleIO::write_vdata_palgrid(pgrid, rho[is].data(), is, nspin_, 0, ssc.str(), 0.0, &ucell, precision, 0, false, false, ofs_running, desc_pchg); + ModuleIO::write_vdata_palgrid(pgrid, rho[is].data(), is, nspin_, 0, ssc.str(), 0.0, &ucell, precision, 0, false, false, false, ofs_running, desc_pchg); } } } diff --git a/source/source_io/module_chgpot/get_pchg_pw.cpp b/source/source_io/module_chgpot/get_pchg_pw.cpp index 10561069885..554ef4caf8e 100644 --- a/source/source_io/module_chgpot/get_pchg_pw.cpp +++ b/source/source_io/module_chgpot/get_pchg_pw.cpp @@ -474,7 +474,7 @@ void Get_pchg_pw::write_cube(const int band, } filename << ".cube"; const std::string desc_pchg = ModuleIO::make_data_desc("partial charge", "partial magnetization m", component, nspin_); - ModuleIO::write_vdata_palgrid(pgrid, values.data(), component, nspin_, 0, filename.str(), 0.0, &ucell, 11, 0, false, separate_k, ofs_running, desc_pchg); + ModuleIO::write_vdata_palgrid(pgrid, values.data(), component, nspin_, 0, filename.str(), 0.0, &ucell, 11, 0, false, separate_k, false, ofs_running, desc_pchg); } // Explicit instantiation emits both precisions for each supported device from this .cpp file. diff --git a/source/source_io/module_ctrl/ctrl_output_fp.cpp b/source/source_io/module_ctrl/ctrl_output_fp.cpp index 35d17a13eb5..9cf1c811d8f 100644 --- a/source/source_io/module_ctrl/ctrl_output_fp.cpp +++ b/source/source_io/module_ctrl/ctrl_output_fp.cpp @@ -56,8 +56,10 @@ void ctrl_output_fp(UnitCell& ucell, bool should_output = (output_frequency == 0); if (istep_in >= 0) { - // In append mode (out_app_flag == 1) each cube output reuses a - // fixed filename, so every step overwrites the previous snapshot. + // In append mode (out_app_flag == 1) each cube output reuses a fixed + // filename, so every written step appends one complete frame to that + // file. Modes 0 and 2 keep a per-step g{index} suffix, which makes + // every file hold exactly one frame. if (out_app_flag != 1) { geom_block = "g" + std::to_string(istep + 1); @@ -65,6 +67,13 @@ void ctrl_output_fp(UnitCell& ucell, should_output = true; } + // Mirror the out_wfc_lcao convention: in append mode the first ionic step + // (istep == 0, always written because every out_freq_ion divides it) + // truncates the fixed-name cube files and later steps append one complete + // frame each. Single-point SCF/NSCF runs stop at istep == 0, so they keep + // overwriting instead of stacking on a stale file from an earlier run. + const bool append_cube = (out_app_flag == 1) && (istep > 0); + // 4) write charge density if (inp.out_chg[0] > 0 && should_output) { @@ -98,6 +107,7 @@ void ctrl_output_fp(UnitCell& ucell, 1, PARAM.globalv.two_fermi, false, + append_cube, ofs_running, desc_chg); @@ -121,6 +131,7 @@ void ctrl_output_fp(UnitCell& ucell, 1, // default out_fermi PARAM.globalv.two_fermi, false, + append_cube, ofs_running, desc_tau); } @@ -160,6 +171,7 @@ void ctrl_output_fp(UnitCell& ucell, 0, // out_fermi PARAM.globalv.two_fermi, false, + append_cube, ofs_running, desc_pot); } @@ -184,6 +196,7 @@ void ctrl_output_fp(UnitCell& ucell, solvent, pcc_potential, inp.out_pot[1], + append_cube, ofs_running); } @@ -210,6 +223,7 @@ void ctrl_output_fp(UnitCell& ucell, inp.out_elf[1], geom_block, PARAM.globalv.two_fermi, + append_cube, ofs_running); } diff --git a/source/source_io/module_dos/cal_ldos.cpp b/source/source_io/module_dos/cal_ldos.cpp index 63f0e2c3f5b..5e8e14de0d9 100644 --- a/source/source_io/module_dos/cal_ldos.cpp +++ b/source/source_io/module_dos/cal_ldos.cpp @@ -102,6 +102,7 @@ void Cal_ldos::cal_ldos_lcao( 0, two_fermi, false, + false, ofs_running, "local DOS"); @@ -230,7 +231,7 @@ void stm_mode_pw(const elecstate::ElecStatePW>* pelec, << ".cube"; const int precision = out_ldos_precision; - ModuleIO::write_vdata_palgrid(pgrid, ldos.data(), 0, nspin, 0, fn.str(), 0, &ucell, precision, 0, two_fermi, false, ofs_running, "local DOS"); + ModuleIO::write_vdata_palgrid(pgrid, ldos.data(), 0, nspin, 0, fn.str(), 0, &ucell, precision, 0, two_fermi, false, false, ofs_running, "local DOS"); } } diff --git a/source/source_io/module_elf/write_elf.cpp b/source/source_io/module_elf/write_elf.cpp index 986de94fd4d..911c6cb47a1 100644 --- a/source/source_io/module_elf/write_elf.cpp +++ b/source/source_io/module_elf/write_elf.cpp @@ -18,6 +18,7 @@ void write_elf( const int& precision, const std::string& geom_block, const bool two_fermi, + const bool append, std::ofstream& ofs_running) { ModuleBase::timer::start("ModuleIO", "write_elf"); @@ -186,6 +187,7 @@ void write_elf( out_fermi, two_fermi, false, + append, ofs_running, "electron localization function"); } @@ -211,6 +213,7 @@ void write_elf( out_fermi, two_fermi, false, + append, ofs_running, desc_elf); } @@ -249,6 +252,7 @@ void write_elf( out_fermi, two_fermi, false, + append, ofs_running, "electron localization function"); } diff --git a/source/source_io/module_elf/write_elf.h b/source/source_io/module_elf/write_elf.h index 970843e6526..e0396824d22 100644 --- a/source/source_io/module_elf/write_elf.h +++ b/source/source_io/module_elf/write_elf.h @@ -21,6 +21,7 @@ void write_elf( const int& precision, const std::string& geom_block, const bool two_fermi, + const bool append, std::ofstream& ofs_running); } diff --git a/source/source_io/module_output/cube_io.h b/source/source_io/module_output/cube_io.h index 6f554fef949..26496b8407c 100644 --- a/source/source_io/module_output/cube_io.h +++ b/source/source_io/module_output/cube_io.h @@ -52,6 +52,9 @@ bool read_vdata_palgrid(const Parallel_Grid& pgrid, const int nat); /// write volumetric data on the parallized grid into a .cube file +/// @param append append a complete frame to an existing cube file instead of +/// truncating it. Used by out_app_flag == 1, where successive ionic steps +/// reuse a fixed filename and each written step contributes one frame. void write_vdata_palgrid(const Parallel_Grid& pgrid, const double* const data, const int is, @@ -64,6 +67,7 @@ void write_vdata_palgrid(const Parallel_Grid& pgrid, const int out_fermi, const bool two_fermi, const bool reduce_all_pool, + const bool append, std::ofstream& ofs_running, const std::string& data_desc); @@ -84,6 +88,8 @@ bool read_cube(const std::string& file, std::vector& data); /// write a cube file +/// @param append append a complete frame to an existing file instead of +/// truncating it (out_app_flag == 1 multi-step trajectories). void write_cube(const std::string& file, const std::vector& comment, const int& natom, @@ -99,6 +105,7 @@ void write_cube(const std::string& file, const std::vector>& atom_pos, const std::vector& data, const int precision, + const bool append, const int ndata_line = 6); /** diff --git a/source/source_io/module_output/write_cube.cpp b/source/source_io/module_output/write_cube.cpp index 5c3d8e9af54..6fc55dc7954 100644 --- a/source/source_io/module_output/write_cube.cpp +++ b/source/source_io/module_output/write_cube.cpp @@ -23,6 +23,7 @@ void ModuleIO::write_vdata_palgrid(const Parallel_Grid& pgrid, const int out_fermi, const bool two_fermi, const bool reduce_all_pool, + const bool append, std::ofstream& ofs_running, const std::string& data_desc) { @@ -173,7 +174,8 @@ void ModuleIO::write_vdata_palgrid(const Parallel_Grid& pgrid, atom_charge, atom_pos, data_xyz_full, - precision); + precision, + append); ofs_running << " Write " << data_desc << " to file: " << fn << std::endl; @@ -199,6 +201,7 @@ void ModuleIO::write_cube(const std::string& file, const std::vector>& atom_pos, const std::vector& data, const int precision, + const bool append, const int ndata_line) { assert(comment.size() >= 2); @@ -222,7 +225,16 @@ void ModuleIO::write_cube(const std::string& file, assert(data.size() >= nx * ny * nz); - std::ofstream ofs(file); + // In append mode (out_app_flag == 1) each written ionic step contributes + // one complete cube frame; the previous frame always ends with a newline, + // so the appended frame starts on a fresh line. Otherwise the file is + // truncated and holds a single frame. + std::ios_base::openmode open_mode = std::ios::out | std::ios::trunc; + if (append) + { + open_mode = std::ios::out | std::ios::app; + } + std::ofstream ofs(file, open_mode); for (int i = 0; i < 2; ++i) { diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index 23d402ef921..52bb93776b8 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -1398,7 +1398,7 @@ The circle order of the charge density on real space grids is: x is the outer lo item.type = "Integer"; item.description = R"(Controls output file organization during multi-step calculations (MD, relax, RT-TDDFT). The directory routing applies to both LCAO and PW basis calculations. * 0: Write to OUT directory directly, no append (each step's file carries a g{step} suffix) -* 1: Reuse fixed filenames across steps (default, backward compatible): text outputs append new records, cube snapshots are overwritten +* 1: Reuse fixed filenames across steps (default, backward compatible): text outputs append new records, cube outputs accumulate one frame per written step * 2: Write to subdirectory (PSI/, CHG/, POT/, ELF/, DOS/, DIP/, MAT/), files carry a g{step} suffix The legacy Boolean spellings (true/false, yes/no, on/off, t/f, y/n, and the dotted forms) are still accepted and map to modes 1/0 as before. For more information, refer to the documentation of out_mat_r, out_hsr, out_mat_t, out_mat_dh, out_hsk, out_wfc_lcao, out_chg, out_pot, out_elf, out_dos, out_dipole, out_stru.)"; @@ -1665,6 +1665,8 @@ Here $\tilde{\psi}$ is the pseudo-wavefunction, $\beta_i^I$ are the atomic proje When `out_freq_ion > 0`, a geometry step suffix `g{#}` is appended to the file names (e.g., `elftotg1.cube`, `elfs1g1.cube`). +With `out_app_flag=1` the geometry step suffix is omitted and the same ELF files accumulate one complete cube frame per written ionic step (`Ionic_Step {#}` as the first comment line, one-based); the first written step truncates them. With `out_app_flag=0` every step writes its own suffixed file, and with `out_app_flag=2` the frames are routed to the `ELF/` subdirectory. + The second integer controls the precision of the kinetic energy density output, if not given, will use 3 as default. For purpose restarting from this file and other high-precision involved calculation, recommend to use 10. In molecular dynamics calculations, the output frequency is controlled by out_freq_ion.)"; diff --git a/source/source_io/module_wf/get_wf_lcao.cpp b/source/source_io/module_wf/get_wf_lcao.cpp index 70b1e46784f..f982738b54b 100644 --- a/source/source_io/module_wf/get_wf_lcao.cpp +++ b/source/source_io/module_wf/get_wf_lcao.cpp @@ -76,7 +76,7 @@ void Get_wf_lcao::begin_gamma(const UnitCell& ucell, std::stringstream ss_out; ss_out << global_out_dir << ss_file.str(); - ModuleIO::write_vdata_palgrid(pgrid, wfc_norm.data(), is, nspin_, 0, ss_out.str(), 0.0, &ucell, precision, 0, false, false, ofs_running, desc_wfc_norm); + ModuleIO::write_vdata_palgrid(pgrid, wfc_norm.data(), is, nspin_, 0, ss_out.str(), 0.0, &ucell, precision, 0, false, false, false, ofs_running, desc_wfc_norm); } if (re_im_bands_picked[ib]) @@ -95,6 +95,7 @@ void Get_wf_lcao::begin_gamma(const UnitCell& ucell, 0, false, false, + false, ofs_running, desc_wfc_real); @@ -112,6 +113,7 @@ void Get_wf_lcao::begin_gamma(const UnitCell& ucell, 0, false, false, + false, ofs_running, desc_wfc_imag); } @@ -199,6 +201,7 @@ void Get_wf_lcao::begin_k(const UnitCell& ucell, 0, false, false, + false, ofs_running, desc_wfc_norm); } @@ -235,6 +238,7 @@ void Get_wf_lcao::begin_k(const UnitCell& ucell, 0, false, false, + false, ofs_running, desc_wfc_real); @@ -252,6 +256,7 @@ void Get_wf_lcao::begin_k(const UnitCell& ucell, 0, false, false, + false, ofs_running, desc_wfc_imag); } diff --git a/source/source_io/module_wf/get_wf_pw.cpp b/source/source_io/module_wf/get_wf_pw.cpp index c3d3401c2d9..39b17bc18f0 100644 --- a/source/source_io/module_wf/get_wf_pw.cpp +++ b/source/source_io/module_wf/get_wf_pw.cpp @@ -338,7 +338,7 @@ void Get_wf_pw::write_cube(const int band, } // collinear spin channel (nspin=2); spinor components (nspin=4) are not spin channels data_desc += ModuleIO::make_spin_tag(component, nspin_); - ModuleIO::write_vdata_palgrid(pgrid, values.data(), component, nspin_, 0, filename.str(), 0.0, &ucell, 11, 0, false, true, ofs_running, data_desc); + ModuleIO::write_vdata_palgrid(pgrid, values.data(), component, nspin_, 0, filename.str(), 0.0, &ucell, 11, 0, false, true, false, ofs_running, data_desc); } // Explicit instantiation emits both precisions for each supported device from this .cpp file. diff --git a/source/source_io/test_serial/rho_io_test.cpp b/source/source_io/test_serial/rho_io_test.cpp index 38b226fc994..5a63f4fd485 100644 --- a/source/source_io/test_serial/rho_io_test.cpp +++ b/source/source_io/test_serial/rho_io_test.cpp @@ -7,6 +7,10 @@ #include "prepare_unitcell.h" #include "source_base/parallel_grid.h" +#include +#include +#include + Magnetism::Magnetism() { this->tot_mag = 0.0; @@ -97,7 +101,7 @@ TEST_F(RhoIOTest, Write) ucell->atoms[0].ncpp.zv = 4; Parallel_Grid pgrid(nx, ny, nz, nz, nrxx, nz, 1); ModuleIO::read_vdata_palgrid(pgrid, my_rank, ofs_running, "support/chg.cube", rho[0], ucell->nat); - ModuleIO::write_vdata_palgrid(pgrid, rho[0], 0, nspin, 0, "test_write_vdata_palgrid.cube", 0.461002, ucell, 11, 1, false, false, ofs_running, "charge density"); + ModuleIO::write_vdata_palgrid(pgrid, rho[0], 0, nspin, 0, "test_write_vdata_palgrid.cube", 0.461002, ucell, 11, 1, false, false, false, ofs_running, "charge density"); std::ifstream ifs1("test_write_vdata_palgrid.cube", std::ifstream::binary | std::ifstream::ate); std::ifstream ifs2("support/chg.cube", std::ifstream::binary | std::ifstream::ate); EXPECT_EQ(ifs1.tellg(), ifs2.tellg()); @@ -217,10 +221,80 @@ TEST_F(CubeIOTest, WriteCube) comment, natom, origin, nx_read, ny_read, nz_read, dx, dy, dz, atom_type, - atom_charge, atom_pos, data_read, 11); + atom_charge, atom_pos, data_read, 11, false); std::ifstream ifs1("test_write.cube", std::ifstream::binary | std::ifstream::ate); std::ifstream ifs2("./support/chg.cube", std::ifstream::binary | std::ifstream::ate); EXPECT_EQ(ifs1.tellg(), ifs2.tellg()); ifs1.close(); ifs2.close(); } + +/** + * - Tested Functions: + * - write_cube() + * - append == false truncates the file, leaving exactly one frame + * - append == true accumulates one complete frame per call, which is how + * out_app_flag == 1 writes successive ionic steps into a fixed filename + */ +TEST_F(CubeIOTest, WriteCubeAppendFrames) +{ + ModuleIO::read_cube(fn, comment, natom, origin, + nx_read, ny_read, nz_read, + dx, dy, dz, + atom_type, atom_charge, atom_pos, data_read); + + const std::string fname = std::string(testing::TempDir()) + "abacus_cube_append.cube"; + + auto count_frames = [&fname, this]() { + std::ifstream ifs(fname.c_str()); + const std::string content((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + std::size_t frames = 0; + std::size_t pos = content.find(comment[0]); + while (pos != std::string::npos) + { + ++frames; + pos = content.find(comment[0], pos + comment[0].size()); + } + return frames; + }; + + // The first frame of a run truncates stale content from any previous run. + ModuleIO::write_cube(fname, + comment, natom, origin, + nx_read, ny_read, nz_read, + dx, dy, dz, atom_type, + atom_charge, atom_pos, data_read, 11, false); + EXPECT_EQ(count_frames(), 1u); + + // Two appended frames stack a second and a third complete cube block. + for (int iframe = 0; iframe < 2; ++iframe) + { + ModuleIO::write_cube(fname, + comment, natom, origin, + nx_read, ny_read, nz_read, + dx, dy, dz, atom_type, + atom_charge, atom_pos, data_read, 11, true); + } + EXPECT_EQ(count_frames(), 3u); + + // Every frame ends with a newline, so the next frame starts on a fresh line. + { + std::ifstream ifs(fname.c_str()); + const std::string content((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + EXPECT_EQ(content.back(), '\n'); + } + + // Overwriting once more drops the two accumulated frames. + ModuleIO::write_cube(fname, + comment, natom, origin, + nx_read, ny_read, nz_read, + dx, dy, dz, atom_type, + atom_charge, atom_pos, data_read, 11, false); + EXPECT_EQ(count_frames(), 1u); + + std::remove(fname.c_str()); +} diff --git a/source/source_lcao/module_lr/lr_density.cpp b/source/source_lcao/module_lr/lr_density.cpp index f2186ed806f..ecf931298bb 100644 --- a/source/source_lcao/module_lr/lr_density.cpp +++ b/source/source_lcao/module_lr/lr_density.cpp @@ -87,7 +87,7 @@ void LR_Density::write_density_single_state(const double* const* const densit { const std::string data_desc = "LR-TDDFT electron-hole density difference (electrons/Bohr^3)"; ModuleIO::write_vdata_palgrid(pgrid_, density[0], 0, 1, 0, filepath, 0.0, &ucell_, - this->out_chg_precision_, 0, false, true, ofs_running, data_desc); + this->out_chg_precision_, 0, false, true, false, ofs_running, data_desc); } template diff --git a/source/source_lcao/module_lr/utils/exciton_plotter.cpp b/source/source_lcao/module_lr/utils/exciton_plotter.cpp index 037ba0586d7..5bfe7091a76 100644 --- a/source/source_lcao/module_lr/utils/exciton_plotter.cpp +++ b/source/source_lcao/module_lr/utils/exciton_plotter.cpp @@ -510,6 +510,7 @@ void ExcitonPlotter::plot_average_density(const int istate, const std::string 0, false, /*two_fermi*/ false, + false, GlobalV::ofs_running, "exciton density"); } From d73fe29a27bc744415d2891a1906d99d203d6b85 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 21 Oct 2026 21:48:49 +0800 Subject: [PATCH 45/61] feat(io): rename band-energy-term outputs and add provenance headers Rename vpp_local_out.dat, vpp_nonlocal_out.dat, vhartree_out.dat and kinetic_out.dat to eband_vl.txt, eband_vnl.txt, eband_hart.txt and eband_kin.txt, and prefix each file with version, timestamp, ionic step and total energy, expectation-value formula, and unit comments. Write the files on rank 0 only to avoid concurrent writes in MPI runs. --- source/source_esolver/esolver_ks_lcao.cpp | 2 + source/source_esolver/esolver_ks_lcao.h | 5 ++ .../source_esolver/esolver_ks_lcao_tddft.cpp | 3 + .../module_ctrl/ctrl_runner_lcao.cpp | 8 ++ .../source_io/module_ctrl/ctrl_runner_lcao.h | 1 + .../module_energy/write_eband_terms.cpp | 80 +++++++++++++++++-- .../module_energy/write_eband_terms.h | 7 ++ source/source_io/module_hs/vxc_op_mat.cpp | 6 +- source/source_io/module_hs/vxc_op_tools.cpp | 12 +-- source/source_io/module_hs/vxc_op_tools.h | 9 ++- .../module_parameter/read_inp_out.cpp | 2 +- .../{vhartree_out.ref => eband_hart.ref} | 0 .../{kinetic_out.ref => eband_kin.ref} | 0 .../{vpp_local_out.ref => eband_vl.ref} | 0 .../{vpp_nonlocal_out.ref => eband_vnl.ref} | 0 .../{vhartree_out.ref => eband_hart.ref} | 0 .../{kinetic_out.ref => eband_kin.ref} | 0 .../{vpp_local_out.ref => eband_vl.ref} | 0 .../{vpp_nonlocal_out.ref => eband_vnl.ref} | 0 tests/integrate/validation_tools/props_mat.sh | 29 ++++--- 20 files changed, 132 insertions(+), 32 deletions(-) rename tests/02_NAO_Gamma/scf_out_hxc/{vhartree_out.ref => eband_hart.ref} (100%) rename tests/02_NAO_Gamma/scf_out_hxc/{kinetic_out.ref => eband_kin.ref} (100%) rename tests/02_NAO_Gamma/scf_out_hxc/{vpp_local_out.ref => eband_vl.ref} (100%) rename tests/02_NAO_Gamma/scf_out_hxc/{vpp_nonlocal_out.ref => eband_vnl.ref} (100%) rename tests/08_RI/scf_out_xc_multik/{vhartree_out.ref => eband_hart.ref} (100%) rename tests/08_RI/scf_out_xc_multik/{kinetic_out.ref => eband_kin.ref} (100%) rename tests/08_RI/scf_out_xc_multik/{vpp_local_out.ref => eband_vl.ref} (100%) rename tests/08_RI/scf_out_xc_multik/{vpp_nonlocal_out.ref => eband_vnl.ref} (100%) diff --git a/source/source_esolver/esolver_ks_lcao.cpp b/source/source_esolver/esolver_ks_lcao.cpp index e6c047a40c2..ad1cc50f974 100644 --- a/source/source_esolver/esolver_ks_lcao.cpp +++ b/source/source_esolver/esolver_ks_lcao.cpp @@ -165,6 +165,7 @@ void ESolver_KS_LCAO::before_all_runners(BaseCell& basecell, const Input template void ESolver_KS_LCAO::before_scf(UnitCell& ucell, const int istep) { + this->istep_ = istep; this->before_scf(ucell, istep, istep); } @@ -442,6 +443,7 @@ void ESolver_KS_LCAO::after_all_runners(BaseCell& basecell) ModuleIO::ctrl_runner_lcao(ucell, *this->inp_, + this->istep_, this->kv, this->pelec, this->dmat, diff --git a/source/source_esolver/esolver_ks_lcao.h b/source/source_esolver/esolver_ks_lcao.h index 6fca9c417ec..093625c3210 100644 --- a/source/source_esolver/esolver_ks_lcao.h +++ b/source/source_esolver/esolver_ks_lcao.h @@ -60,6 +60,11 @@ class ESolver_KS_LCAO : public ESolver_KS //! Prepare an ionic step with a separate index for initial charge and potential output. void before_scf(UnitCell& ucell, const int istep, const int output_step); + /// Ionic step index of the most recent ionic step, recorded in before_scf(). + /// Read by after_all_runners() when labelling provenance headers of files + /// written once at the end of a run. + int istep_ = 0; + //! Prepare charge mixing and Hamiltonian updates for an SCF iteration. virtual void iter_init(UnitCell& ucell, const int istep, const int iter) override; diff --git a/source/source_esolver/esolver_ks_lcao_tddft.cpp b/source/source_esolver/esolver_ks_lcao_tddft.cpp index ff2b1e06e40..5ba39719c03 100644 --- a/source/source_esolver/esolver_ks_lcao_tddft.cpp +++ b/source/source_esolver/esolver_ks_lcao_tddft.cpp @@ -144,6 +144,9 @@ void ESolver_KS_LCAO_TDDFT::runner(BaseCell& basecell, const int ist ModuleBase::TITLE("ESolver_KS_LCAO_TDDFT", "runner"); ModuleBase::timer::start(this->classname, "runner"); + // Record the ionic step for provenance headers written in after_all_runners. + this->istep_ = istep; + //---------------------------------------------------------------- // 1) before_scf (preparation before the electronic-step loop) //---------------------------------------------------------------- diff --git a/source/source_io/module_ctrl/ctrl_runner_lcao.cpp b/source/source_io/module_ctrl/ctrl_runner_lcao.cpp index f2f6079d860..aac9aa1f898 100644 --- a/source/source_io/module_ctrl/ctrl_runner_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_runner_lcao.cpp @@ -20,6 +20,7 @@ namespace ModuleIO template void ctrl_runner_lcao(UnitCell& ucell, // unitcell const Input_para &inp, // input + const int istep, // ionic step index, zero-based K_Vectors &kv, // k-point elecstate::ElecState* pelec,// electronic info const module_dm::Setup_DM &dmat, // mohan add 2025-11-02 @@ -138,7 +139,11 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell // write eband terms if (inp.out_eband_terms) { + const double etot = pelec->f_en.etot; ModuleIO::write_eband_terms(inp.nspin, + istep, + etot, + GlobalV::MY_RANK, PARAM.globalv.nlocal, GlobalV::DRANK, &pv, @@ -173,6 +178,7 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell // TK: double TR: double template void ctrl_runner_lcao(UnitCell& ucell, // unitcell const Input_para &inp, // input + const int istep, // ionic step index, zero-based K_Vectors &kv, // k-point elecstate::ElecState* pelec,// electronic info const module_dm::Setup_DM &dmat, // mohan add 2025-11-02 @@ -196,6 +202,7 @@ template void ctrl_runner_lcao(UnitCell& ucell, // unitcell // TK: complex TR: double template void ctrl_runner_lcao, double>(UnitCell& ucell, // unitcell const Input_para &inp, // input + const int istep, // ionic step index, zero-based K_Vectors &kv, // k-point elecstate::ElecState* pelec,// electronic info const module_dm::Setup_DM> &dmat, // mohan add 2025-11-02 @@ -219,6 +226,7 @@ template void ctrl_runner_lcao, double>(UnitCell& ucell, // TK: complex TR: complex template void ctrl_runner_lcao, std::complex>(UnitCell& ucell, // unitcell const Input_para &inp, // input + const int istep, // ionic step index, zero-based K_Vectors &kv, // k-point elecstate::ElecState* pelec,// electronic info const module_dm::Setup_DM> &dmat, // mohan add 2025-11-02 diff --git a/source/source_io/module_ctrl/ctrl_runner_lcao.h b/source/source_io/module_ctrl/ctrl_runner_lcao.h index 96434852343..a9f36154f48 100644 --- a/source/source_io/module_ctrl/ctrl_runner_lcao.h +++ b/source/source_io/module_ctrl/ctrl_runner_lcao.h @@ -18,6 +18,7 @@ namespace ModuleIO template void ctrl_runner_lcao(UnitCell& ucell, // unitcell const Input_para &inp, // input + const int istep, // ionic step index, zero-based K_Vectors &kv, // k-point elecstate::ElecState* pelec,// electronic info const module_dm::Setup_DM &dmat, // mohan add 2025-11-02 diff --git a/source/source_io/module_energy/write_eband_terms.cpp b/source/source_io/module_energy/write_eband_terms.cpp index b7474b68023..4afa0946913 100644 --- a/source/source_io/module_energy/write_eband_terms.cpp +++ b/source/source_io/module_energy/write_eband_terms.cpp @@ -6,6 +6,9 @@ #include "source_lcao/module_operator_lcao/nonlocal.h" #include "source_cell/module_neighbor/sltk_grid_driver.h" +#include "source_base/constants.h" +#include "source_base/formatter.h" +#include "source_main/version.h" #include "source_psi/psi.h" #include "source_cell/unitcell.h" #include "source_cell/klist.h" @@ -22,10 +25,57 @@ #include "source_lcao/module_operator_lcao/op_exx_lcao.h" #endif +#include +#include + +namespace +{ +/// @brief Format the current local time for output file headers. +/// @return Timestamp in "YYYY-MM-DD HH:MM:SS" format. +std::string local_time_stamp() +{ + const std::time_t now = std::time(nullptr); + std::tm tm_now; + localtime_r(&now, &tm_now); + char buffer[32]; + std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm_now); + return std::string(buffer); +} + +/// @brief Build the five comment lines preceding band-decomposed energy data. +/// @param istep Zero-based ionic step index, printed one-based. +/// @param etot_eV Total energy of the written frame in eV. +/// @param term_symbol Operator symbol used in the expectation value expression. +/// @param time_stamp Timestamp shared by all files of the same run. +/// @return Comment block terminated by a newline. +std::string build_eband_header(const int istep, + const double etot_eV, + const std::string& term_symbol, + const std::string& time_stamp) +{ + const int istep_one_based = istep + 1; + const std::string header = FmtCore::format( + "# ABACUS version: %s\n" + "# Written at %s\n" + "# RELAX STEP %d, Energy: %.8f eV\n" + "# e_{ikn} = in the KS orbital basis\n" + "# units: columns are e/2 in Hartree and e in eV; data rows follow nk, nspin0, nbands\n", + VERSION, + time_stamp.c_str(), + istep_one_based, + etot_eV, + term_symbol.c_str()); + return header; +} +} // namespace + namespace ModuleIO { template void write_eband_terms(const int nspin, + const int istep, + const double etot, + const int my_rank, const int nbasis, const int drank, const Parallel_Orbitals* pv, @@ -53,6 +103,8 @@ void write_eband_terms(const int nspin, // 0. prepare const int& nbands = wg.nc; const int nspin0 = (nspin == 2) ? 2 : 1; + const double etot_eV = etot * ModuleBase::Ry_to_eV; + const std::string time_stamp = local_time_stamp(); double etxc = 0.0; double vtxc = 0.0; @@ -68,6 +120,20 @@ void write_eband_terms(const int nspin, } }; + // Only rank 0 writes; the orbital energies above are reduced on all ranks. + auto write_term = [&](const std::vector>& e_orb, + const std::string& out_name, + const std::string& term_symbol) + { + if (my_rank != 0) + { + return; + } + const std::string header = build_eband_header(istep, etot_eV, term_symbol, time_stamp); + const std::string& out_dir = PARAM.globalv.global_out_dir; + write_orb_energy(kv, nspin0, nbands, e_orb, out_name, header, out_dir); + }; + auto all_band_energy = [&wg](const int ik, const std::vector& e_orb)->double { double e = 0; @@ -107,7 +173,7 @@ void write_eband_terms(const int nspin, cVc(kinetic_k_ao.get_hk(), &psi(ik, 0, 0), nbasis, nbands, *pv, p2d), p2d)); } - write_orb_energy(kv, nspin0, nbands, e_orb_kinetic, "kinetic", "", PARAM.globalv.global_out_dir); + write_term(e_orb_kinetic, "eband_kin.txt", "T"); } // 2. pp: local @@ -139,7 +205,7 @@ void write_eband_terms(const int nspin, e_orb_pp_local.emplace_back(orbital_energy(ik, nbands, cVc(v_pp_local_k_ao.get_hk(), &psi(ik, 0, 0), nbasis, nbands, *pv, p2d), p2d)); } - write_orb_energy(kv, nspin0, nbands, e_orb_pp_local, "vpp_local", "", PARAM.globalv.global_out_dir); + write_term(e_orb_pp_local, "eband_vl.txt", "V_loc"); } // 3. pp: nonlocal @@ -159,7 +225,7 @@ void write_eband_terms(const int nspin, e_orb_pp_nonlocal.emplace_back(orbital_energy(ik, nbands, cVc(v_pp_nonlocal_k_ao.get_hk(), &psi(ik, 0, 0), nbasis, nbands, *pv, p2d), p2d)); } - write_orb_energy(kv, nspin0, nbands, e_orb_pp_nonlocal, "vpp_nonlocal", "", PARAM.globalv.global_out_dir); + write_term(e_orb_pp_nonlocal, "eband_vnl.txt", "V_nonlocal"); } // 4. hartree @@ -192,7 +258,7 @@ void write_eband_terms(const int nspin, cVc(v_hartree_k_ao.get_hk(), &psi(ik, 0, 0), nbasis, nbands, *pv, p2d), p2d)); } for (auto& op : v_hartree_op) { delete op; } - write_orb_energy(kv, nspin0, nbands, e_orb_hartree, "vhartree", "", PARAM.globalv.global_out_dir); + write_term(e_orb_hartree, "eband_hart.txt", "V_H"); } // 5. xc (including exx) @@ -233,7 +299,7 @@ void write_eband_terms(const int nspin, // Explicit template instantiations template void write_eband_terms( - const int, const int, const int, const Parallel_Orbitals*, const psi::Psi&, + const int, const int, const double, const int, const int, const int, const Parallel_Orbitals*, const psi::Psi&, const UnitCell&, Structure_Factor&, surchem&, const ModulePW::PW_Basis&, const ModulePW::PW_Basis&, const ModuleBase::matrix&, const Charge&, const K_Vectors&, const ModuleBase::matrix&, Grid_Driver&, const std::vector&, const TwoCenterBundle&, const Exx_Info& @@ -244,7 +310,7 @@ template void write_eband_terms( ); template void write_eband_terms, double>( - const int, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, + const int, const int, const double, const int, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, const UnitCell&, Structure_Factor&, surchem&, const ModulePW::PW_Basis&, const ModulePW::PW_Basis&, const ModuleBase::matrix&, const Charge&, const K_Vectors&, const ModuleBase::matrix&, Grid_Driver&, const std::vector&, const TwoCenterBundle&, const Exx_Info& @@ -255,7 +321,7 @@ template void write_eband_terms, double>( ); template void write_eband_terms, std::complex>( - const int, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, + const int, const int, const double, const int, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, const UnitCell&, Structure_Factor&, surchem&, const ModulePW::PW_Basis&, const ModulePW::PW_Basis&, const ModuleBase::matrix&, const Charge&, const K_Vectors&, const ModuleBase::matrix&, Grid_Driver&, const std::vector&, const TwoCenterBundle&, const Exx_Info& diff --git a/source/source_io/module_energy/write_eband_terms.h b/source/source_io/module_energy/write_eband_terms.h index 54d980a8701..bd78c40a39a 100644 --- a/source/source_io/module_energy/write_eband_terms.h +++ b/source/source_io/module_energy/write_eband_terms.h @@ -42,10 +42,17 @@ namespace ModuleIO /// @brief Write the band-decomposed energy of each Hamiltonian term /// (kinetic, local/nonlocal pp, Hartree, XC) in KS orbital representation. +/// @param nspin Number of spin components (1, 2 or 4) +/// @param istep Zero-based ionic step index; written one-based in the header +/// @param etot Total energy of the current frame in Ry +/// @param my_rank World rank; only rank 0 writes the files /// @tparam TK K-point data type (double or std::complex) /// @tparam TR Real-space data type (double or std::complex) template void write_eband_terms(const int nspin, + const int istep, + const double etot, + const int my_rank, const int nbasis, const int drank, const Parallel_Orbitals* pv, diff --git a/source/source_io/module_hs/vxc_op_mat.cpp b/source/source_io/module_hs/vxc_op_mat.cpp index a8f95c42d84..a94ad96112c 100644 --- a/source/source_io/module_hs/vxc_op_mat.cpp +++ b/source/source_io/module_hs/vxc_op_mat.cpp @@ -137,12 +137,12 @@ void write_Vxc(const int nspin, if (GlobalV::MY_RANK == 0) { - write_orb_energy(kv, nspin0, nbands, e_orb_tot, "vxc", "", global_out_dir); + write_orb_energy(kv, nspin0, nbands, e_orb_tot, "vxc.txt", "", global_out_dir); #ifdef __EXX if (cal_exx) { - write_orb_energy(kv, nspin0, nbands, e_orb_locxc, "vxc", "local", global_out_dir); - write_orb_energy(kv, nspin0, nbands, e_orb_exx, "vxc", "exx", global_out_dir); + write_orb_energy(kv, nspin0, nbands, e_orb_locxc, "vxc_local.txt", "", global_out_dir); + write_orb_energy(kv, nspin0, nbands, e_orb_exx, "vxc_exx.txt", "", global_out_dir); } #endif } diff --git a/source/source_io/module_hs/vxc_op_tools.cpp b/source/source_io/module_hs/vxc_op_tools.cpp index 0862ae1557d..2b33eda76a3 100644 --- a/source/source_io/module_hs/vxc_op_tools.cpp +++ b/source/source_io/module_hs/vxc_op_tools.cpp @@ -102,19 +102,21 @@ void write_orb_energy(const K_Vectors& kv, const int nspin0, const int nbands, const std::vector>& e_orb, - const std::string& term, - const std::string& label, + const std::string& out_name, + const std::string& header, const std::string& global_out_dir, const bool app) { assert(e_orb.size() == kv.get_nks()); + assert(!out_name.empty()); const int nk = kv.get_nks() / nspin0; std::ofstream ofs; - 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); + if (!header.empty()) + { + ofs << header; + } ofs << nk << "\n" << nspin0 << "\n" << nbands << "\n"; ofs << std::scientific << std::setprecision(16); for (int ik = 0; ik < nk; ++ik) diff --git a/source/source_io/module_hs/vxc_op_tools.h b/source/source_io/module_hs/vxc_op_tools.h index 4e66d632fb0..0aa66dceabc 100644 --- a/source/source_io/module_hs/vxc_op_tools.h +++ b/source/source_io/module_hs/vxc_op_tools.h @@ -80,16 +80,17 @@ double all_band_energy(const int ik, /// @param nspin0 Number of independent spin channels (1 or 2) /// @param nbands Number of bands /// @param e_orb Orbital energies per k-point -/// @param term Term label (e.g., "vxc", "kinetic") -/// @param label Additional label (e.g., "local", "exx") +/// @param out_name Output file name relative to the output directory +/// (e.g., "vxc.txt", "eband_vl.txt") +/// @param header Comment block written before the numeric data; empty to omit /// @param global_out_dir Output directory /// @param app Append mode flag void write_orb_energy(const K_Vectors& kv, const int nspin0, const int nbands, const std::vector>& e_orb, - const std::string& term, - const std::string& label, + const std::string& out_name, + const std::string& header, const std::string& global_out_dir, const bool app = false); diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index 52bb93776b8..d57d95074a6 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -1297,7 +1297,7 @@ The circle order of the charge density on real space grids is: x is the outer lo item.annotation = "output the band energy terms separately"; item.category = "Output information"; item.type = "Boolean"; - item.description = "Whether to print the band energy terms separately in the file OUT.{term}_out.dat. The terms include the kinetic, pseudopotential (local + nonlocal), Hartree and exchange-correlation (including exact exchange if calculated)."; + item.description = "Whether to print the band-decomposed energy of each Hamiltonian term in the files eband_kin.txt, eband_vl.txt, eband_vnl.txt, eband_hart.txt and vxc.txt under the output directory. Each file starts with comment lines giving the ABACUS version, the timestamp, the ionic step and total energy, the expectation-value formula, and the units. The terms include the kinetic, pseudopotential (local + nonlocal), Hartree and exchange-correlation (including exact exchange if calculated)."; item.default_value = "False"; item.unit = ""; item.set_availability("basis_type==lcao"); diff --git a/tests/02_NAO_Gamma/scf_out_hxc/vhartree_out.ref b/tests/02_NAO_Gamma/scf_out_hxc/eband_hart.ref similarity index 100% rename from tests/02_NAO_Gamma/scf_out_hxc/vhartree_out.ref rename to tests/02_NAO_Gamma/scf_out_hxc/eband_hart.ref diff --git a/tests/02_NAO_Gamma/scf_out_hxc/kinetic_out.ref b/tests/02_NAO_Gamma/scf_out_hxc/eband_kin.ref similarity index 100% rename from tests/02_NAO_Gamma/scf_out_hxc/kinetic_out.ref rename to tests/02_NAO_Gamma/scf_out_hxc/eband_kin.ref diff --git a/tests/02_NAO_Gamma/scf_out_hxc/vpp_local_out.ref b/tests/02_NAO_Gamma/scf_out_hxc/eband_vl.ref similarity index 100% rename from tests/02_NAO_Gamma/scf_out_hxc/vpp_local_out.ref rename to tests/02_NAO_Gamma/scf_out_hxc/eband_vl.ref diff --git a/tests/02_NAO_Gamma/scf_out_hxc/vpp_nonlocal_out.ref b/tests/02_NAO_Gamma/scf_out_hxc/eband_vnl.ref similarity index 100% rename from tests/02_NAO_Gamma/scf_out_hxc/vpp_nonlocal_out.ref rename to tests/02_NAO_Gamma/scf_out_hxc/eband_vnl.ref diff --git a/tests/08_RI/scf_out_xc_multik/vhartree_out.ref b/tests/08_RI/scf_out_xc_multik/eband_hart.ref similarity index 100% rename from tests/08_RI/scf_out_xc_multik/vhartree_out.ref rename to tests/08_RI/scf_out_xc_multik/eband_hart.ref diff --git a/tests/08_RI/scf_out_xc_multik/kinetic_out.ref b/tests/08_RI/scf_out_xc_multik/eband_kin.ref similarity index 100% rename from tests/08_RI/scf_out_xc_multik/kinetic_out.ref rename to tests/08_RI/scf_out_xc_multik/eband_kin.ref diff --git a/tests/08_RI/scf_out_xc_multik/vpp_local_out.ref b/tests/08_RI/scf_out_xc_multik/eband_vl.ref similarity index 100% rename from tests/08_RI/scf_out_xc_multik/vpp_local_out.ref rename to tests/08_RI/scf_out_xc_multik/eband_vl.ref diff --git a/tests/08_RI/scf_out_xc_multik/vpp_nonlocal_out.ref b/tests/08_RI/scf_out_xc_multik/eband_vnl.ref similarity index 100% rename from tests/08_RI/scf_out_xc_multik/vpp_nonlocal_out.ref rename to tests/08_RI/scf_out_xc_multik/eband_vnl.ref diff --git a/tests/integrate/validation_tools/props_mat.sh b/tests/integrate/validation_tools/props_mat.sh index e9affd4093f..0765baaa6b0 100644 --- a/tests/integrate/validation_tools/props_mat.sh +++ b/tests/integrate/validation_tools/props_mat.sh @@ -37,6 +37,19 @@ compare_hr_csr(){ python3 $COMPARE_SCRIPT <(sed -n "$pick" "$ref") <(sed -n "$pick" "$cal") 3 } +# Band-energy-term files start with comment lines (version, timestamp, ionic +# step, formula, units) that legitimately change between runs, so the header +# is stripped and only the numeric body is compared. +compare_eband_energy(){ + local ref=$1 + local cal=$2 + if ! test -f "$ref" || ! test -f "$cal"; then + echo "Error: can not find file $ref or $cal" + return 1 + fi + python3 $COMPARE_SCRIPT <(sed '/^[[:space:]]*#/d' "$ref") <(sed '/^[[:space:]]*#/d' "$cal") 4 +} + run_mat_dm1_props(){ #------------------------------- @@ -246,21 +259,13 @@ fi # separate terms in band enegy #-------------------------------- if ! test -z "$has_eband_separate" && [ $has_eband_separate == 1 ]; then - ekref=kinetic_out.ref - ekcal=OUT.autotest/kinetic_out.dat - python3 $COMPARE_SCRIPT $ekref $ekcal 4 + compare_eband_energy eband_kin.ref OUT.autotest/eband_kin.txt echo "CompareOrbKinetic_pass $?" >>$props_result_file - vlref=vpp_local_out.ref - vlcal=OUT.autotest/vpp_local_out.dat - python3 $COMPARE_SCRIPT $vlref $vlcal 4 + compare_eband_energy eband_vl.ref OUT.autotest/eband_vl.txt echo "CompareOrbVL_pass $?" >>$props_result_file - vnlref=vpp_nonlocal_out.ref - vnlcal=OUT.autotest/vpp_nonlocal_out.dat - python3 $COMPARE_SCRIPT $vnlref $vnlcal 4 + compare_eband_energy eband_vnl.ref OUT.autotest/eband_vnl.txt echo "CompareOrbVNL_pass $?" >>$props_result_file - vhref=vhartree_out.ref - vhcal=OUT.autotest/vhartree_out.dat - python3 $COMPARE_SCRIPT $vhref $vhcal 4 + compare_eband_energy eband_hart.ref OUT.autotest/eband_hart.txt echo "CompareOrbVHartree_pass $?" >>$props_result_file fi From 0475b924442051d87e68488197d08130bb97eceb Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Thu, 22 Oct 2026 09:00:42 +0800 Subject: [PATCH 46/61] fix(io): reuse fixed dH/dS CSR filename in append mode and log every write In out_app_flag=1 the previous code still generated a g{istep+1} suffixed filename per ionic step, so the supposed "append" mode actually wrote a new file each step and never accumulated frames. - Generate the fixed dhrxs1_nao.csr / dsrxs1_nao.csr filename when out_app_flag == 1, matching the established one-based naming used by other NAO matrix writers. - Open the file in truncate mode on the first written step (istep == 0) and append on subsequent steps. - Always print the "Write ... to file:" line to the running log, including appended steps and binary output, so every written file is traceable. - Add DhsSparseWriter.AppendModeReusesFixedFilename unit test covering modes 1 and 2. Verification: ctest -V -R dhs_sparse_writer_test (4/4 pass), make -j 30 abacus_max_para links cleanly. --- .../source_io/module_hs/dhs_sparse_writer.cpp | 29 +++++++++------ .../unittests/test_dhs_sparse_writer.cpp | 37 +++++++++++++++++++ 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/source/source_io/module_hs/dhs_sparse_writer.cpp b/source/source_io/module_hs/dhs_sparse_writer.cpp index 636938c5f14..6eff42e1b6b 100644 --- a/source/source_io/module_hs/dhs_sparse_writer.cpp +++ b/source/source_io/module_hs/dhs_sparse_writer.cpp @@ -112,11 +112,16 @@ void ModuleIO::save_dH_sparse(const int& istep, } } + // In append mode (out_app_flag == 1) the fixed filename is reused so that + // every written ionic step accumulates into one file. Modes 0 and 2 embed + // the one-based ionic step before "_nao" and never append. + const bool include_step = (istep >= 0) && (out_app_flag != 1); + for (auto& comp: comps) { for (int ispin = 0; ispin < 2; ++ispin) { - if (istep >= 0) + if (include_step) { comp.fname[ispin] << global_matrix_dir << "d" << fileflag << "r" << comp.axis @@ -133,9 +138,10 @@ void ModuleIO::save_dH_sparse(const int& istep, if (GlobalV::DRANK == 0) { - // Append for any multi-step run (MD or relax) in append mode; - // single-point SCF passes istep = -1 and starts a fresh file. - const bool open_in_append = (out_app_flag == 1) && istep >= 0; + // The first written ionic step (istep == 0) truncates the shared file; + // later steps append one complete record each. Single-point runs pass + // istep <= 0 and therefore always start a fresh file. + const bool open_in_append = (out_app_flag == 1) && istep > 0; for (auto& comp: comps) { const std::string label = std::string("dH") + comp.axis; @@ -150,13 +156,14 @@ void ModuleIO::save_dH_sparse(const int& istep, { mode |= std::ios::app; } - else if (!binary) - { - const char* matrix_name = (fileflag == "s") ? "dS/dR" : "dH/dR"; - const std::string spin_tag = ModuleIO::make_spin_tag(ispin, nspin); - ofs_running << " Write " << matrix_name << " (" << comp.axis << " component)" << spin_tag - << " matrix in NAO basis to file: " << comp.fname[ispin].str() << std::endl; - } + + // Announce every written file, including appended multi-step + // records and binary output, so the running log tracks each write. + const char* matrix_name = (fileflag == "s") ? "dS/dR" : "dH/dR"; + const std::string spin_tag = ModuleIO::make_spin_tag(ispin, nspin); + ofs_running << " Write " << matrix_name << " (" << comp.axis << " component)" << spin_tag + << " matrix in NAO basis to file: " << comp.fname[ispin].str() << std::endl; + comp.ofs[ispin].open(comp.fname[ispin].str().c_str(), mode); detail::check_output_file_open(comp.ofs[ispin], comp.fname[ispin].str(), "ModuleIO::save_dH_sparse"); diff --git a/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp b/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp index 085f92a25c3..cc1be494877 100644 --- a/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp +++ b/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp @@ -144,6 +144,43 @@ TEST(DhsSparseWriter, SocWritesAllDirections) remove_derivative_files("s", 7); } +TEST(DhsSparseWriter, AppendModeReusesFixedFilename) +{ + remove_derivative_files("s"); + init_sparse_output_globals(); + + Parallel_Orbitals pv; + init_serial_orbitals(pv); + LCAO_HS_Arrays arrays; + const Abfs::Vector3_Order r_vector(0, 0, 0); + arrays.all_R_coor.insert(r_vector); + arrays.dHRx_sparse[0][r_vector][0][0] = 1.0; + + // out_app_flag = 1 reuses the fixed filename without a g{N} suffix; the + // first written ionic step (istep == 0) truncates it. + ModuleIO::save_dH_sparse(0, pv, arrays, 1e-10, false, "s", 8, "./", "md", 1, 1, 2, ofs_running); + const std::string first_frame = read_file("dsrxs1_nao.csr"); + ASSERT_FALSE(first_frame.empty()); + EXPECT_EQ(count_substr(first_frame, "STEP: "), 1); + EXPECT_EQ(count_substr(first_frame, "STEP: 1"), 1); + + // The next ionic step appends a second frame to the same file. + ModuleIO::save_dH_sparse(1, pv, arrays, 1e-10, false, "s", 8, "./", "md", 1, 1, 2, ofs_running); + const std::string two_frames = read_file("dsrxs1_nao.csr"); + EXPECT_EQ(count_substr(two_frames, "STEP: "), 2); + EXPECT_EQ(count_substr(two_frames, "STEP: 1"), 1); + EXPECT_EQ(count_substr(two_frames, "STEP: 2"), 1); + + // Modes 0 and 2 keep one suffixed file per ionic step and never append. + ModuleIO::save_dH_sparse(0, pv, arrays, 1e-10, false, "s", 8, "./", "md", 2, 1, 2, ofs_running); + const std::vector mode2_lines = read_lines("dsrxs1g1_nao.csr"); + ASSERT_FALSE(mode2_lines.empty()); + EXPECT_EQ(mode2_lines[0], "STEP: 1"); + + remove_derivative_files("s"); + remove_derivative_files("s", 0); +} + int main(int argc, char** argv) { #ifdef __MPI From 1eb4483e540f764794762c7645f8a6ebd6bd63e5 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 23 Oct 2026 08:30:46 +0800 Subject: [PATCH 47/61] fix(test): skip provenance comment header when comparing LCAO wfc output Since v3.11 the text LCAO wavefunction writer prepends a provenance header ("# ABACUS version:", "# Written at ...", "# IONIC STEP ..."), and the multi-k awk pass forwards those comment lines into *_mod.txt. The reference files predate the header and the "# Written at" timestamp is run-dependent, so the wfc comparison always failed. Strip comment lines from both the calculated and reference files before invoking CompareFile.py, mirroring the existing header filtering in props_mat.sh. Reference files are left unchanged. Verified by rerunning scf_out_wf (gamma), 03_NAO_multik/scf_out_wf, 03_NAO_multik/md_out_wf and 05_rtTDDFT/02_NO_CH_OW_TDDFT with the current build: all wfc comparisons pass (exit 0) after filtering. --- tests/integrate/validation_tools/props_cube.sh | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/integrate/validation_tools/props_cube.sh b/tests/integrate/validation_tools/props_cube.sh index 2b1b78526b5..a8af0ad856e 100644 --- a/tests/integrate/validation_tools/props_cube.sh +++ b/tests/integrate/validation_tools/props_cube.sh @@ -175,8 +175,21 @@ if ! test -z "$has_lowf" && [ "$has_lowf" == 1 ]; then wfc_ref="$wfc_name"_mod.txt.ref fi - python3 $COMPARE_SCRIPT $wfc_cal $wfc_ref 8 -abs 1 - echo "Compare_wfc_lcao_pass $?" >>$props_result_file + # wfc text output carries a provenance header ("# ABACUS version:", + # "# Written at ...", "# IONIC STEP ...") since v3.11; the multi-k awk + # pass also forwards those comment lines into the *_mod.txt file. + # Strip comment lines from both sides so the (run-dependent) header and + # its timestamp never affect the comparison. + local wfc_cal_nohdr + local wfc_ref_nohdr + wfc_cal_nohdr=$(mktemp) + wfc_ref_nohdr=$(mktemp) + sed '/^[[:space:]]*#/d' "$wfc_cal" >"$wfc_cal_nohdr" + sed '/^[[:space:]]*#/d' "$wfc_ref" >"$wfc_ref_nohdr" + python3 $COMPARE_SCRIPT "$wfc_cal_nohdr" "$wfc_ref_nohdr" 8 -abs 1 + local wfc_pass=$? + rm -f "$wfc_cal_nohdr" "$wfc_ref_nohdr" + echo "Compare_wfc_lcao_pass $wfc_pass" >>$props_result_file fi } From f26d91104022fb75d68f696926d77c8bb02067ef Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 23 Oct 2026 09:43:46 +0800 Subject: [PATCH 48/61] refactor(io): rename band-decomposed XC output and relocate LIBRPA reference files Rename the band-decomposed XC energy output from vxc.txt to eband_vxc.txt for consistency with the other eband_* term files (eband_kin/vl/vnl/hart), and rename the INPUT parameter out_eband_terms to out_eband. - write_Vxc now always emits eband_vxc.txt (with provenance header) for either out_mat_xc or out_eband; the MO-basis vxc_nao.txt matrices remain gated by out_mat_xc alone. - Pass ionic step and total energy into write_Vxc for the eband header. - write_eband_terms no longer writes the XC term (handled by write_Vxc) and drops the now-unused EXX/DRANK arguments. - Update integration cases (scf_out_hxc, scf_out_xc_multik) and LIBRPA producer manifests; reference files renamed via git mv (vxc.ref -> eband_vxc.ref, vxc.txt -> eband_vxc.txt). - Move LIBRPA producer reference files out of the .gitignore'd producer_reference/OUT.autotest/ directory into producer_reference/ (flattened), so new reference snapshots no longer need git add -f. - compare scripts filter comment/header lines before diffing eband files. Verification: - Grouped and applied via patch-based hunk split (backup in /tmp/commit_split_backup). Not recompiled here; eband output paths and reference resolution verified by inspection of props_mat.sh, check_librpa_producer.py and the updated manifests. --- source/source_esolver/esolver_ks_lcaopw.cpp | 5 + .../module_ctrl/ctrl_runner_lcao.cpp | 23 ++-- .../module_energy/write_eband_terms.cpp | 123 ++---------------- .../module_energy/write_eband_terms.h | 29 +---- source/source_io/module_hs/vxc_op_lip.cpp | 31 +++-- source/source_io/module_hs/vxc_op_lip.h | 8 +- source/source_io/module_hs/vxc_op_mat.cpp | 79 ++++++----- source/source_io/module_hs/vxc_op_mat.h | 12 +- source/source_io/module_hs/vxc_op_tools.cpp | 33 +++++ source/source_io/module_hs/vxc_op_tools.h | 17 ++- .../module_parameter/input_parameter.h | 2 +- .../module_parameter/read_inp_out.cpp | 6 +- source/source_io/test/read_input_ptest.cpp | 2 +- tests/02_NAO_Gamma/scf_out_hxc/INPUT | 2 +- .../scf_out_hxc/{vxc.ref => eband_vxc.ref} | 0 tests/08_RI/60_LIBRPA_RPA_READER_V1/README | 2 +- .../librpa_producer_manifest.json | 4 +- .../{OUT.autotest => }/KPT.info | 0 .../{OUT.autotest/vxc.txt => eband_vxc.txt} | 0 .../librpa_producer_manifest.json | 4 +- .../{OUT.autotest => }/KPT.info | 0 .../{OUT.autotest/vxc.txt => eband_vxc.txt} | 0 tests/08_RI/scf_out_xc_multik/INPUT | 2 +- tests/08_RI/scf_out_xc_multik/README | 2 +- .../{vxc.ref => eband_vxc.ref} | 0 .../validation_tools/props_common.sh | 2 +- tests/integrate/validation_tools/props_mat.sh | 6 +- 27 files changed, 184 insertions(+), 210 deletions(-) rename tests/02_NAO_Gamma/scf_out_hxc/{vxc.ref => eband_vxc.ref} (100%) rename tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/{OUT.autotest => }/KPT.info (100%) rename tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/{OUT.autotest/vxc.txt => eband_vxc.txt} (100%) rename tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/{OUT.autotest => }/KPT.info (100%) rename tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/{OUT.autotest/vxc.txt => eband_vxc.txt} (100%) rename tests/08_RI/scf_out_xc_multik/{vxc.ref => eband_vxc.ref} (100%) diff --git a/source/source_esolver/esolver_ks_lcaopw.cpp b/source/source_esolver/esolver_ks_lcaopw.cpp index f5ece6c3be7..b4147245715 100644 --- a/source/source_esolver/esolver_ks_lcaopw.cpp +++ b/source/source_esolver/esolver_ks_lcaopw.cpp @@ -284,7 +284,12 @@ namespace ModuleESolver bool cal_exx = false; double hybrid_alpha = 0.0; #endif + // one-shot output after the whole run; the single frame is step 1 + const int istep_xc = 0; + const double etot_xc = this->pelec->f_en.etot; ModuleIO::write_Vxc_LIP(this->inp_->nspin, + istep_xc, + etot_xc, PARAM.globalv.nlocal, GlobalV::DRANK, *this->stp.template get_psi_t(), diff --git a/source/source_io/module_ctrl/ctrl_runner_lcao.cpp b/source/source_io/module_ctrl/ctrl_runner_lcao.cpp index aac9aa1f898..7441f419572 100644 --- a/source/source_io/module_ctrl/ctrl_runner_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_runner_lcao.cpp @@ -59,15 +59,21 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell PARAM.globalv.two_fermi, inp.out_ldos[1], ofs_running); } - // 3) print out exchange-correlation potential - if (inp.out_mat_xc) + // 3) print out exchange-correlation potential: the band-decomposed orbital + // energies (eband_vxc.txt etc.) are produced for either switch, while + // the full MO-basis matrices (vxc_nao.txt etc.) are only for out_mat_xc. + const bool out_vxc_orb = inp.out_mat_xc || inp.out_eband; + if (out_vxc_orb) { bool cal_exx = exx_info.info_global.cal_exx; + const double etot_xc = pelec->f_en.etot; #ifdef __EXX auto* hexxd_ptr = exx_nao.exd ? &exx_nao.exd->get_Hexxs() : nullptr; auto* hexxc_ptr = exx_nao.exc ? &exx_nao.exc->get_Hexxs() : nullptr; #endif ModuleIO::write_Vxc(inp.nspin, + istep, + etot_xc, PARAM.globalv.nlocal, GlobalV::DRANK, &pv, @@ -85,7 +91,9 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell gd, inp.dft_plus_u, PARAM.globalv.gamma_only_local, + inp.out_mat_xc, PARAM.globalv.global_mat_dir, + PARAM.globalv.global_out_dir, inp.out_ndigits, inp.ks_solver, cal_exx, @@ -137,7 +145,7 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell // write eband terms - if (inp.out_eband_terms) + if (inp.out_eband) { const double etot = pelec->f_en.etot; ModuleIO::write_eband_terms(inp.nspin, @@ -145,7 +153,6 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell etot, GlobalV::MY_RANK, PARAM.globalv.nlocal, - GlobalV::DRANK, &pv, *psi, ucell, @@ -159,13 +166,7 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell pelec->wg, gd, orb.cutoffs(), - two_center_bundle, - exx_info -#ifdef __EXX - , - exx_nao.exd ? &exx_nao.exd->get_Hexxs() : nullptr, - exx_nao.exc ? &exx_nao.exc->get_Hexxs() : nullptr -#endif + two_center_bundle ); } diff --git a/source/source_io/module_energy/write_eband_terms.cpp b/source/source_io/module_energy/write_eband_terms.cpp index 4afa0946913..153795d3907 100644 --- a/source/source_io/module_energy/write_eband_terms.cpp +++ b/source/source_io/module_energy/write_eband_terms.cpp @@ -1,19 +1,17 @@ #include "write_eband_terms.h" -#include "source_io/module_hs/vxc_op_mat.h" +#include "source_io/module_hs/vxc_op_tools.h" #include "source_io/module_parameter/parameter.h" #include "source_lcao/module_operator_lcao/ekinetic.h" #include "source_lcao/module_operator_lcao/nonlocal.h" +#include "source_lcao/module_operator_lcao/veff_lcao.h" #include "source_cell/module_neighbor/sltk_grid_driver.h" #include "source_base/constants.h" -#include "source_base/formatter.h" -#include "source_main/version.h" #include "source_psi/psi.h" #include "source_cell/unitcell.h" #include "source_cell/klist.h" #include "source_estate/module_pot/potential_new.h" -#include "source_hamilt/module_xc/exx_info.h" #include "source_basis/module_nao/two_center_bundle.h" #include "source_basis/module_ao/parallel_orbitals.h" #include "source_estate/module_charge/charge.h" @@ -21,54 +19,8 @@ #include "source_hamilt/module_surchem/surchem.h" #include "source_basis/module_pw/pw_basis.h" -#ifdef __EXX -#include "source_lcao/module_operator_lcao/op_exx_lcao.h" -#endif - -#include #include -namespace -{ -/// @brief Format the current local time for output file headers. -/// @return Timestamp in "YYYY-MM-DD HH:MM:SS" format. -std::string local_time_stamp() -{ - const std::time_t now = std::time(nullptr); - std::tm tm_now; - localtime_r(&now, &tm_now); - char buffer[32]; - std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm_now); - return std::string(buffer); -} - -/// @brief Build the five comment lines preceding band-decomposed energy data. -/// @param istep Zero-based ionic step index, printed one-based. -/// @param etot_eV Total energy of the written frame in eV. -/// @param term_symbol Operator symbol used in the expectation value expression. -/// @param time_stamp Timestamp shared by all files of the same run. -/// @return Comment block terminated by a newline. -std::string build_eband_header(const int istep, - const double etot_eV, - const std::string& term_symbol, - const std::string& time_stamp) -{ - const int istep_one_based = istep + 1; - const std::string header = FmtCore::format( - "# ABACUS version: %s\n" - "# Written at %s\n" - "# RELAX STEP %d, Energy: %.8f eV\n" - "# e_{ikn} = in the KS orbital basis\n" - "# units: columns are e/2 in Hartree and e in eV; data rows follow nk, nspin0, nbands\n", - VERSION, - time_stamp.c_str(), - istep_one_based, - etot_eV, - term_symbol.c_str()); - return header; -} -} // namespace - namespace ModuleIO { template @@ -77,7 +29,6 @@ void write_eband_terms(const int nspin, const double etot, const int my_rank, const int nbasis, - const int drank, const Parallel_Orbitals* pv, const psi::Psi& psi, const UnitCell& ucell, @@ -91,13 +42,7 @@ void write_eband_terms(const int nspin, const ModuleBase::matrix& wg, Grid_Driver& gd, const std::vector& orb_cutoff, - const TwoCenterBundle& two_center_bundle, - const Exx_Info& exx_info -#ifdef __EXX - , - std::vector>>>* Hexxd, - std::vector>>>>* Hexxc -#endif + const TwoCenterBundle& two_center_bundle ) { // 0. prepare @@ -260,75 +205,31 @@ void write_eband_terms(const int nspin, for (auto& op : v_hartree_op) { delete op; } write_term(e_orb_hartree, "eband_hart.txt", "V_H"); } - - // 5. xc (including exx) - if (!PARAM.inp.out_mat_xc) // avoid duplicate output - { - bool cal_exx = exx_info.info_global.cal_exx; - write_Vxc(nspin, - nbasis, - drank, - pv, - psi, - ucell, - sf, - solvent, - rho_basis, - rhod_basis, - vloc, - chg, - kv, - orb_cutoff, - wg, - gd, - PARAM.inp.dft_plus_u, - PARAM.globalv.gamma_only_local, - PARAM.globalv.global_out_dir, - PARAM.inp.out_ndigits, - PARAM.inp.ks_solver, - cal_exx, - exx_info -#ifdef __EXX - , - Hexxd, - Hexxc -#endif - ); - } + // NOTE: the XC term (eband_vxc.txt etc.) is written by write_Vxc from + // ctrl_runner_lcao.cpp for both out_mat_xc and out_eband, + // so it is intentionally not handled here. } // Explicit template instantiations template void write_eband_terms( - const int, const int, const double, const int, const int, const int, const Parallel_Orbitals*, const psi::Psi&, + const int, const int, const double, const int, const int, const Parallel_Orbitals*, const psi::Psi&, const UnitCell&, Structure_Factor&, surchem&, const ModulePW::PW_Basis&, const ModulePW::PW_Basis&, const ModuleBase::matrix&, const Charge&, const K_Vectors&, const ModuleBase::matrix&, Grid_Driver&, - const std::vector&, const TwoCenterBundle&, const Exx_Info& -#ifdef __EXX - , std::vector>>>*, - std::vector>>>>* -#endif + const std::vector&, const TwoCenterBundle& ); template void write_eband_terms, double>( - const int, const int, const double, const int, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, + const int, const int, const double, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, const UnitCell&, Structure_Factor&, surchem&, const ModulePW::PW_Basis&, const ModulePW::PW_Basis&, const ModuleBase::matrix&, const Charge&, const K_Vectors&, const ModuleBase::matrix&, Grid_Driver&, - const std::vector&, const TwoCenterBundle&, const Exx_Info& -#ifdef __EXX - , std::vector>>>*, - std::vector>>>>* -#endif + const std::vector&, const TwoCenterBundle& ); template void write_eband_terms, std::complex>( - const int, const int, const double, const int, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, + const int, const int, const double, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, const UnitCell&, Structure_Factor&, surchem&, const ModulePW::PW_Basis&, const ModulePW::PW_Basis&, const ModuleBase::matrix&, const Charge&, const K_Vectors&, const ModuleBase::matrix&, Grid_Driver&, - const std::vector&, const TwoCenterBundle&, const Exx_Info& -#ifdef __EXX - , std::vector>>>*, - std::vector>>>>* -#endif + const std::vector&, const TwoCenterBundle& ); } // namespace ModuleIO diff --git a/source/source_io/module_energy/write_eband_terms.h b/source/source_io/module_energy/write_eband_terms.h index bd78c40a39a..067b2966b1f 100644 --- a/source/source_io/module_energy/write_eband_terms.h +++ b/source/source_io/module_energy/write_eband_terms.h @@ -4,10 +4,6 @@ #include "source_base/matrix.h" #include "source_psi/psi.h" -#include -#include -#include -#include #include class Grid_Driver; @@ -18,30 +14,18 @@ class surchem; class Charge; class K_Vectors; class TwoCenterBundle; -struct Exx_Info; namespace ModulePW { class PW_Basis; } -#ifdef __EXX -namespace hamilt -{ -using TAC = std::pair>; -} -namespace RI -{ -template -class Tensor; -} -#endif - namespace ModuleIO { /// @brief Write the band-decomposed energy of each Hamiltonian term -/// (kinetic, local/nonlocal pp, Hartree, XC) in KS orbital representation. +/// (kinetic, local/nonlocal pp, Hartree) in KS orbital representation. +/// The XC term is handled by write_Vxc in ctrl_runner_lcao.cpp instead. /// @param nspin Number of spin components (1, 2 or 4) /// @param istep Zero-based ionic step index; written one-based in the header /// @param etot Total energy of the current frame in Ry @@ -54,7 +38,6 @@ void write_eband_terms(const int nspin, const double etot, const int my_rank, const int nbasis, - const int drank, const Parallel_Orbitals* pv, const psi::Psi& psi, const UnitCell& ucell, @@ -68,13 +51,7 @@ void write_eband_terms(const int nspin, const ModuleBase::matrix& wg, Grid_Driver& gd, const std::vector& orb_cutoff, - const TwoCenterBundle& two_center_bundle, - const Exx_Info& exx_info -#ifdef __EXX - , - std::vector>>>* Hexxd, - std::vector>>>>* Hexxc -#endif + const TwoCenterBundle& two_center_bundle ); } // namespace ModuleIO diff --git a/source/source_io/module_hs/vxc_op_lip.cpp b/source/source_io/module_hs/vxc_op_lip.cpp index 094015992d4..27ddcd4bb48 100644 --- a/source/source_io/module_hs/vxc_op_lip.cpp +++ b/source/source_io/module_hs/vxc_op_lip.cpp @@ -4,6 +4,7 @@ #include "source_base/parallel_reduce.h" #include "source_base/module_container/base/third_party/blas.h" #include "source_io/module_hs/hs_dense_io.h" +#include "source_io/module_hs/vxc_op_tools.h" #include #include @@ -104,6 +105,8 @@ FPTYPE all_band_energy_lip(const int ik, const std::vector& orbital_ener template void write_Vxc_LIP(int nspin, + const int istep, + const double etot, int naos, int drank, const psi::Psi>& psi_pw, @@ -195,15 +198,15 @@ void write_Vxc_LIP(int nspin, #endif // add-up and write - const int istep = -1; + const int istep_mat = -1; const int out_label = 1; // 1 means .txt while 2 means .dat const int out_app_flag = 0; std::string vxc_file = ModuleIO::filename_output( global_out_dir, "vxc", "nao", ik, kv.ik2iktot, nspin, kv.get_nkstot(), - out_label, out_app_flag, gamma_only, istep); + out_label, out_app_flag, gamma_only, istep_mat); - ModuleIO::save_mat(istep, vxc_tot_k_mo.data(), nbands, + ModuleIO::save_mat(istep_mat, vxc_tot_k_mo.data(), nbands, false, out_ndigits, true, out_app_flag, vxc_file, p2d_serial, drank, ks_solver, false); @@ -215,13 +218,18 @@ void write_Vxc_LIP(int nspin, const int nspin0 = (nspin == 2) ? 2 : 1; auto write_orb_energy_lip = [&kv, &nspin0, &nbands, &global_out_dir](const std::vector>& e_orb, const std::string& label, + const std::string& header, const bool app = false) { assert(e_orb.size() == kv.get_nks()); const int nk = kv.get_nks() / nspin0; std::ofstream ofs; const std::string out_name = (label == "") ? ".txt" : "_" + label + ".txt"; - ofs.open(global_out_dir + "vxc" + out_name, + ofs.open(global_out_dir + "eband_vxc" + out_name, app ? std::ios::app : std::ios::out); + if (!header.empty()) + { + ofs << header; + } ofs << nk << "\n" << nspin0 << "\n" << nbands << "\n"; ofs << std::scientific << std::setprecision(16); for (int ik = 0; ik < nk; ++ik) @@ -238,12 +246,17 @@ void write_Vxc_LIP(int nspin, if (GlobalV::MY_RANK == 0) { - write_orb_energy_lip(e_orb_tot, ""); + const double etot_eV = etot * ModuleBase::Ry_to_eV; + const std::string time_stamp = local_time_stamp(); + const std::string header_tot = build_eband_header(istep, etot_eV, "V_xc", time_stamp); + write_orb_energy_lip(e_orb_tot, "", header_tot); #if((defined __LCAO)&&(defined __EXX) && !(defined __CUDA)&& !(defined __ROCM)) if (cal_exx) { - write_orb_energy_lip(e_orb_locxc, "local"); - write_orb_energy_lip(e_orb_exx, "exx"); + const std::string header_loc = build_eband_header(istep, etot_eV, "V_xc_local", time_stamp); + const std::string header_exx = build_eband_header(istep, etot_eV, "V_EXX", time_stamp); + write_orb_energy_lip(e_orb_locxc, "local", header_loc); + write_orb_energy_lip(e_orb_exx, "exx", header_exx); } #endif } @@ -251,7 +264,7 @@ void write_Vxc_LIP(int nspin, // Explicit template instantiations template void write_Vxc_LIP( - int, int, int, const psi::Psi>&, const UnitCell&, Structure_Factor&, surchem&, + int, const int, const double, int, int, const psi::Psi>&, const UnitCell&, Structure_Factor&, surchem&, const ModulePW::PW_Basis_K&, const ModulePW::PW_Basis&, const ModulePW::PW_Basis&, const ModuleBase::matrix&, const Charge&, const K_Vectors&, const ModuleBase::matrix&, const bool, const std::string&, const int, const std::string&, bool, double @@ -261,7 +274,7 @@ template void write_Vxc_LIP( ); template void write_Vxc_LIP( - int, int, int, const psi::Psi>&, const UnitCell&, Structure_Factor&, surchem&, + int, const int, const double, int, int, const psi::Psi>&, const UnitCell&, Structure_Factor&, surchem&, const ModulePW::PW_Basis_K&, const ModulePW::PW_Basis&, const ModulePW::PW_Basis&, const ModuleBase::matrix&, const Charge&, const K_Vectors&, const ModuleBase::matrix&, const bool, const std::string&, const int, const std::string&, bool, double diff --git a/source/source_io/module_hs/vxc_op_lip.h b/source/source_io/module_hs/vxc_op_lip.h index a613077f6dd..232edb286a5 100644 --- a/source/source_io/module_hs/vxc_op_lip.h +++ b/source/source_io/module_hs/vxc_op_lip.h @@ -19,10 +19,16 @@ namespace ModuleIO { /// @brief Write the Vxc matrix in KS orbital representation for LIP (LCAO-in-PW), useful for GW calculation -/// including terms: local/semi-local XC and EXX +/// including terms: local/semi-local XC and EXX. +/// The band-decomposed diagonal energies (eband_vxc.txt and, with EXX, +/// eband_vxc_local.txt and eband_vxc_exx.txt) are written to global_out_dir. +/// @param istep Zero-based ionic step index; written one-based in the header +/// @param etot Total energy of the current frame in Ry /// @tparam FPTYPE Floating point type (float or double) template void write_Vxc_LIP(int nspin, + const int istep, + const double etot, int naos, int drank, const psi::Psi>& psi_pw, diff --git a/source/source_io/module_hs/vxc_op_mat.cpp b/source/source_io/module_hs/vxc_op_mat.cpp index a94ad96112c..42ef13c2233 100644 --- a/source/source_io/module_hs/vxc_op_mat.cpp +++ b/source/source_io/module_hs/vxc_op_mat.cpp @@ -1,5 +1,6 @@ #include "vxc_op_mat.h" +#include "source_base/constants.h" #include "source_base/module_out/filename.h" #include "source_base/parallel_reduce.h" #include "source_io/module_hs/hs_dense_io.h" @@ -9,6 +10,8 @@ namespace ModuleIO template void write_Vxc(const int nspin, + const int istep, + const double etot, const int nbasis, const int drank, const Parallel_Orbitals* pv, @@ -26,6 +29,8 @@ void write_Vxc(const int nspin, Grid_Driver& gd, const bool dft_plus_u, const bool gamma_only, + const bool out_mat, + const std::string& mat_dir, const std::string& global_out_dir, const int out_ndigits, const std::string& ks_solver, @@ -112,37 +117,45 @@ void write_Vxc(const int nspin, const std::vector& vxc_tot_k_mo = cVc(vxc_k_ao.get_hk(), &psi(ik, 0, 0), nbasis, nbands, *pv, p2d); e_orb_tot.emplace_back(orbital_energy(ik, nbands, vxc_tot_k_mo, p2d)); - // write - const int istep = -1; - const int out_label = 1; // 1 means .txt while 2 means .dat - const int out_app_flag = 0; - const bool append_vxc = (out_app_flag == 1); - std::string vxc_file = ModuleIO::filename_output( - global_out_dir, - "vxc", "nao", ik, kv.ik2iktot, nspin, kv.get_nkstot(), - out_label, out_app_flag, gamma_only, istep); - - ModuleIO::save_mat(istep, - vxc_tot_k_mo.data(), - nbands, - false /*binary*/, - out_ndigits, - true /*triangle*/, - append_vxc, - vxc_file, - p2d, - drank, - ks_solver); + // write the full MO-basis matrix only when requested + if (out_mat) + { + const int istep = -1; + const int out_label = 1; // 1 means .txt while 2 means .dat + const int out_app_flag = 0; + const bool append_vxc = (out_app_flag == 1); + std::string vxc_file = ModuleIO::filename_output( + mat_dir, + "vxc", "nao", ik, kv.ik2iktot, nspin, kv.get_nkstot(), + out_label, out_app_flag, gamma_only, istep); + + ModuleIO::save_mat(istep, + vxc_tot_k_mo.data(), + nbands, + false /*binary*/, + out_ndigits, + true /*triangle*/, + append_vxc, + vxc_file, + p2d, + drank, + ks_solver); + } } if (GlobalV::MY_RANK == 0) { - write_orb_energy(kv, nspin0, nbands, e_orb_tot, "vxc.txt", "", global_out_dir); + const double etot_eV = etot * ModuleBase::Ry_to_eV; + const std::string time_stamp = local_time_stamp(); + const std::string header_tot = build_eband_header(istep, etot_eV, "V_xc", time_stamp); + write_orb_energy(kv, nspin0, nbands, e_orb_tot, "eband_vxc.txt", header_tot, global_out_dir); #ifdef __EXX if (cal_exx) { - write_orb_energy(kv, nspin0, nbands, e_orb_locxc, "vxc_local.txt", "", global_out_dir); - write_orb_energy(kv, nspin0, nbands, e_orb_exx, "vxc_exx.txt", "", global_out_dir); + const std::string header_loc = build_eband_header(istep, etot_eV, "V_xc_local", time_stamp); + const std::string header_exx = build_eband_header(istep, etot_eV, "V_EXX", time_stamp); + write_orb_energy(kv, nspin0, nbands, e_orb_locxc, "eband_vxc_local.txt", header_loc, global_out_dir); + write_orb_energy(kv, nspin0, nbands, e_orb_exx, "eband_vxc_exx.txt", header_exx, global_out_dir); } #endif } @@ -150,11 +163,11 @@ void write_Vxc(const int nspin, // Explicit template instantiations template void write_Vxc( - const int, const int, const int, const Parallel_Orbitals*, const psi::Psi&, + const int, const int, const double, const int, const int, const Parallel_Orbitals*, const psi::Psi&, const UnitCell&, Structure_Factor&, surchem&, const ModulePW::PW_Basis&, const ModulePW::PW_Basis&, const ModuleBase::matrix&, const Charge&, const K_Vectors&, const std::vector&, - const ModuleBase::matrix&, Grid_Driver&, const bool, const bool, const std::string&, - const int, const std::string&, bool, const Exx_Info& + const ModuleBase::matrix&, Grid_Driver&, const bool, const bool, const bool, const std::string&, + const std::string&, const int, const std::string&, bool, const Exx_Info& #ifdef __EXX , std::vector>>>*, std::vector>>>>* @@ -162,11 +175,11 @@ template void write_Vxc( ); template void write_Vxc, double>( - const int, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, + const int, const int, const double, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, const UnitCell&, Structure_Factor&, surchem&, const ModulePW::PW_Basis&, const ModulePW::PW_Basis&, const ModuleBase::matrix&, const Charge&, const K_Vectors&, const std::vector&, - const ModuleBase::matrix&, Grid_Driver&, const bool, const bool, const std::string&, - const int, const std::string&, bool, const Exx_Info& + const ModuleBase::matrix&, Grid_Driver&, const bool, const bool, const bool, const std::string&, + const std::string&, const int, const std::string&, bool, const Exx_Info& #ifdef __EXX , std::vector>>>*, std::vector>>>>* @@ -174,11 +187,11 @@ template void write_Vxc, double>( ); template void write_Vxc, std::complex>( - const int, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, + const int, const int, const double, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, const UnitCell&, Structure_Factor&, surchem&, const ModulePW::PW_Basis&, const ModulePW::PW_Basis&, const ModuleBase::matrix&, const Charge&, const K_Vectors&, const std::vector&, - const ModuleBase::matrix&, Grid_Driver&, const bool, const bool, const std::string&, - const int, const std::string&, bool, const Exx_Info& + const ModuleBase::matrix&, Grid_Driver&, const bool, const bool, const bool, const std::string&, + const std::string&, const int, const std::string&, bool, const Exx_Info& #ifdef __EXX , std::vector>>>*, std::vector>>>>* diff --git a/source/source_io/module_hs/vxc_op_mat.h b/source/source_io/module_hs/vxc_op_mat.h index 7e17a17cfb2..44860b52ca9 100644 --- a/source/source_io/module_hs/vxc_op_mat.h +++ b/source/source_io/module_hs/vxc_op_mat.h @@ -29,11 +29,19 @@ namespace ModuleIO { /// @brief Write the Vxc matrix in KS orbital representation, useful for GW calculation -/// including terms: local/semi-local XC, EXX, DFTU +/// including terms: local/semi-local XC, EXX, DFTU. +/// The band-decomposed diagonal energies (eband_vxc.txt and, with EXX, +/// eband_vxc_local.txt and eband_vxc_exx.txt) are always written to +/// global_out_dir; the full MO-basis matrices (vxc_nao.txt / vxck*_nao.txt) +/// are written to mat_dir only when out_mat is true. +/// @param istep Zero-based ionic step index; written one-based in the header +/// @param etot Total energy of the current frame in Ry /// @tparam TK K-point data type (double or std::complex) /// @tparam TR Real-space data type (double or std::complex) template void write_Vxc(const int nspin, + const int istep, + const double etot, const int nbasis, const int drank, const Parallel_Orbitals* pv, @@ -51,6 +59,8 @@ void write_Vxc(const int nspin, Grid_Driver& gd, const bool dft_plus_u, const bool gamma_only, + const bool out_mat, + const std::string& mat_dir, const std::string& global_out_dir, const int out_ndigits, const std::string& ks_solver, diff --git a/source/source_io/module_hs/vxc_op_tools.cpp b/source/source_io/module_hs/vxc_op_tools.cpp index 2b33eda76a3..5ab83ba2d00 100644 --- a/source/source_io/module_hs/vxc_op_tools.cpp +++ b/source/source_io/module_hs/vxc_op_tools.cpp @@ -1,8 +1,11 @@ #include "vxc_op_tools.h" +#include "source_base/formatter.h" #include "source_base/module_external/scalapack_connector.h" #include "source_base/module_out/filename.h" +#include "source_main/version.h" +#include #include #include @@ -131,6 +134,36 @@ void write_orb_energy(const K_Vectors& kv, } } +std::string local_time_stamp() +{ + const std::time_t now = std::time(nullptr); + std::tm tm_now; + localtime_r(&now, &tm_now); + char buffer[32]; + std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm_now); + return std::string(buffer); +} + +std::string build_eband_header(const int istep, + const double etot_eV, + const std::string& term_symbol, + const std::string& time_stamp) +{ + const int istep_one_based = istep + 1; + const std::string header = FmtCore::format( + "# ABACUS version: %s\n" + "# Written at %s\n" + "# RELAX STEP %d, Energy: %.8f eV\n" + "# e_{ikn} = in the KS orbital basis\n" + "# units: columns are e/2 in Hartree and e in eV; data rows follow nk, nspin0, nbands\n", + VERSION, + time_stamp.c_str(), + istep_one_based, + etot_eV, + term_symbol.c_str()); + return header; +} + // Explicit template instantiations template std::vector cVc(const double*, const double*, const int, const int, const Parallel_Orbitals&, const Parallel_2D&); template std::vector> cVc>(const std::complex*, const std::complex*, const int, const int, const Parallel_Orbitals&, const Parallel_2D&); diff --git a/source/source_io/module_hs/vxc_op_tools.h b/source/source_io/module_hs/vxc_op_tools.h index 0aa66dceabc..78db7c0f703 100644 --- a/source/source_io/module_hs/vxc_op_tools.h +++ b/source/source_io/module_hs/vxc_op_tools.h @@ -81,7 +81,7 @@ double all_band_energy(const int ik, /// @param nbands Number of bands /// @param e_orb Orbital energies per k-point /// @param out_name Output file name relative to the output directory -/// (e.g., "vxc.txt", "eband_vl.txt") +/// (e.g., "eband_vxc.txt", "eband_vl.txt") /// @param header Comment block written before the numeric data; empty to omit /// @param global_out_dir Output directory /// @param app Append mode flag @@ -94,6 +94,21 @@ void write_orb_energy(const K_Vectors& kv, const std::string& global_out_dir, const bool app = false); +/// @brief Format the current local time for output file headers. +/// @return Timestamp in "YYYY-MM-DD HH:MM:SS" format. +std::string local_time_stamp(); + +/// @brief Build the five comment lines preceding band-decomposed energy data. +/// @param istep Zero-based ionic step index, printed one-based. +/// @param etot_eV Total energy of the written frame in eV. +/// @param term_symbol Operator symbol used in the expectation value expression. +/// @param time_stamp Timestamp shared by all files of the same run. +/// @return Comment block terminated by a newline. +std::string build_eband_header(const int istep, + const double etot_eV, + const std::string& term_symbol, + const std::string& time_stamp); + } // namespace ModuleIO #endif // VXC_OP_TOOLS_H diff --git a/source/source_io/module_parameter/input_parameter.h b/source/source_io/module_parameter/input_parameter.h index 6ee46bc9f5a..207fb89952c 100644 --- a/source/source_io/module_parameter/input_parameter.h +++ b/source/source_io/module_parameter/input_parameter.h @@ -486,7 +486,7 @@ struct Input_para bool out_mat_xc = false; ///< output exchange-correlation matrix in ///< KS-orbital representation. std::vector out_mat_xc2 = {0, 8}; ///< output Vxc(R) matrix with precision - bool out_eband_terms = false; ///< output the band energy terms separately + bool out_eband = false; ///< output the band energy terms separately bool out_hr_npz = false; ///< output H(R) matrix in npz format bool out_hsr_npz = false; ///< output H(R) and S(R) matrices in npz format bool out_dm_npz = false; ///< output DM(R) matrix in npz format diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index d57d95074a6..96090750bbe 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -1293,15 +1293,15 @@ The circle order of the charge density on real space grids is: x is the outer lo this->add_item(item); } { - Input_Item item("out_eband_terms"); + Input_Item item("out_eband"); item.annotation = "output the band energy terms separately"; item.category = "Output information"; item.type = "Boolean"; - item.description = "Whether to print the band-decomposed energy of each Hamiltonian term in the files eband_kin.txt, eband_vl.txt, eband_vnl.txt, eband_hart.txt and vxc.txt under the output directory. Each file starts with comment lines giving the ABACUS version, the timestamp, the ionic step and total energy, the expectation-value formula, and the units. The terms include the kinetic, pseudopotential (local + nonlocal), Hartree and exchange-correlation (including exact exchange if calculated)."; + item.description = "Whether to print the band-decomposed energy of each Hamiltonian term in the files eband_kin.txt, eband_vl.txt, eband_vnl.txt, eband_hart.txt and eband_vxc.txt under the output directory. Each file starts with comment lines giving the ABACUS version, the timestamp, the ionic step and total energy, the expectation-value formula, and the units. The terms include the kinetic, pseudopotential (local + nonlocal), Hartree and exchange-correlation (including exact exchange if calculated)."; item.default_value = "False"; item.unit = ""; item.set_availability("basis_type==lcao"); - read_sync_bool(input.out_eband_terms); + read_sync_bool(input.out_eband); this->add_item(item); } { diff --git a/source/source_io/test/read_input_ptest.cpp b/source/source_io/test/read_input_ptest.cpp index 90b92f50eb2..a2d7995d834 100644 --- a/source/source_io/test/read_input_ptest.cpp +++ b/source/source_io/test/read_input_ptest.cpp @@ -218,7 +218,7 @@ TEST_F(InputParaTest, ParaRead) EXPECT_FALSE(param.inp.out_mat_xc); EXPECT_EQ(param.inp.out_mat_xc2[0], 0); EXPECT_EQ(param.inp.out_mat_xc2[1], 8); - EXPECT_FALSE(param.inp.out_eband_terms); + EXPECT_FALSE(param.inp.out_eband); EXPECT_EQ(param.inp.out_app_flag, 0); EXPECT_EQ(param.inp.out_mat_r[0], 0); EXPECT_EQ(param.inp.out_mat_r[1], 8); diff --git a/tests/02_NAO_Gamma/scf_out_hxc/INPUT b/tests/02_NAO_Gamma/scf_out_hxc/INPUT index b53704a5dd3..5fc20dc3944 100644 --- a/tests/02_NAO_Gamma/scf_out_hxc/INPUT +++ b/tests/02_NAO_Gamma/scf_out_hxc/INPUT @@ -24,7 +24,7 @@ smearing_sigma 0.002 out_mat_xc 1 out_mat_xc2 1 -out_eband_terms 1 +out_eband 1 #Parameters (5.Mixing) mixing_type broyden diff --git a/tests/02_NAO_Gamma/scf_out_hxc/vxc.ref b/tests/02_NAO_Gamma/scf_out_hxc/eband_vxc.ref similarity index 100% rename from tests/02_NAO_Gamma/scf_out_hxc/vxc.ref rename to tests/02_NAO_Gamma/scf_out_hxc/eband_vxc.ref diff --git a/tests/08_RI/60_LIBRPA_RPA_READER_V1/README b/tests/08_RI/60_LIBRPA_RPA_READER_V1/README index e629f45d763..ed7c290b7f0 100644 --- a/tests/08_RI/60_LIBRPA_RPA_READER_V1/README +++ b/tests/08_RI/60_LIBRPA_RPA_READER_V1/README @@ -1,5 +1,5 @@ Check reader-v1 RPA producer output, including shrink LRI, Coulomb, and GW -preprocessor input (`KPT.info`, `vxc.txt`, and text LCAO wavefunctions), +preprocessor input (`KPT.info`, `eband_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 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 index e876b34b675..064f73d5cdf 100644 --- 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 @@ -12,8 +12,8 @@ {"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": "OUT.autotest", "pattern": "eband_vxc.txt", "kind": "text", "reference": true, "abs_tol": 1e-7}, + {"base_dir": "OUT.autotest", "pattern": "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/OUT.autotest/KPT.info b/tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/KPT.info similarity index 100% rename from tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/OUT.autotest/KPT.info rename to tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/KPT.info 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/eband_vxc.txt similarity index 100% rename from tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/OUT.autotest/vxc.txt rename to tests/08_RI/60_LIBRPA_RPA_READER_V1/producer_reference/eband_vxc.txt diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/librpa_producer_manifest.json b/tests/08_RI/64_LIBRPA_NSPIN2/librpa_producer_manifest.json index 3983bb8146f..eb2c1a366e7 100644 --- a/tests/08_RI/64_LIBRPA_NSPIN2/librpa_producer_manifest.json +++ b/tests/08_RI/64_LIBRPA_NSPIN2/librpa_producer_manifest.json @@ -12,8 +12,8 @@ {"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": "OUT.autotest", "pattern": "eband_vxc.txt", "kind": "text", "reference": true, "abs_tol": 1e-7}, + {"base_dir": "OUT.autotest", "pattern": "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/OUT.autotest/KPT.info b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/KPT.info similarity index 100% rename from tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/OUT.autotest/KPT.info rename to tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/KPT.info diff --git a/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/OUT.autotest/vxc.txt b/tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/eband_vxc.txt similarity index 100% rename from tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/OUT.autotest/vxc.txt rename to tests/08_RI/64_LIBRPA_NSPIN2/producer_reference/eband_vxc.txt diff --git a/tests/08_RI/scf_out_xc_multik/INPUT b/tests/08_RI/scf_out_xc_multik/INPUT index 90aec1200f6..e4a05ccaa73 100644 --- a/tests/08_RI/scf_out_xc_multik/INPUT +++ b/tests/08_RI/scf_out_xc_multik/INPUT @@ -26,7 +26,7 @@ mixing_type broyden mixing_beta 0.7 out_mat_xc 1 -out_eband_terms 1 +out_eband 1 ks_solver scalapack_gvx dft_functional hse diff --git a/tests/08_RI/scf_out_xc_multik/README b/tests/08_RI/scf_out_xc_multik/README index f11eb37f242..2810c86200c 100644 --- a/tests/08_RI/scf_out_xc_multik/README +++ b/tests/08_RI/scf_out_xc_multik/README @@ -1 +1 @@ -scf HSE on H2O, multik, out_mat_xc=1, out_eband_terms=1. +scf HSE on H2O, multik, out_mat_xc=1, out_eband=1. diff --git a/tests/08_RI/scf_out_xc_multik/vxc.ref b/tests/08_RI/scf_out_xc_multik/eband_vxc.ref similarity index 100% rename from tests/08_RI/scf_out_xc_multik/vxc.ref rename to tests/08_RI/scf_out_xc_multik/eband_vxc.ref diff --git a/tests/integrate/validation_tools/props_common.sh b/tests/integrate/validation_tools/props_common.sh index bca1717e6e6..0fa8462f42a 100644 --- a/tests/integrate/validation_tools/props_common.sh +++ b/tests/integrate/validation_tools/props_common.sh @@ -118,7 +118,7 @@ props_init(){ fi has_xc=$(get_input_key_value "out_mat_xc" "INPUT") has_xc2=$(get_input_key_value "out_mat_xc2" "INPUT") - has_eband_separate=$(get_input_key_value "out_eband_terms" "INPUT") + has_eband_separate=$(get_input_key_value "out_eband" "INPUT") has_lowf=$(get_input_key_value "out_wfc_lcao" "INPUT") out_app_flag=$(get_input_key_value "out_app_flag" "INPUT") has_wfc_r=$(get_input_key_value "out_wfc_r" "INPUT") diff --git a/tests/integrate/validation_tools/props_mat.sh b/tests/integrate/validation_tools/props_mat.sh index 0765baaa6b0..561694af4fe 100644 --- a/tests/integrate/validation_tools/props_mat.sh +++ b/tests/integrate/validation_tools/props_mat.sh @@ -237,11 +237,11 @@ if ! test -z "$has_xc" && [ "$has_xc" == 1 ]; then xcref=vxck2_nao.txt.ref xccal=OUT.autotest/vxck2_nao.txt fi - oeref=vxc.ref - oecal=OUT.autotest/vxc.txt + oeref=eband_vxc.ref + oecal=OUT.autotest/eband_vxc.txt python3 $COMPARE_SCRIPT $xcref $xccal 4 echo "CompareVXC_pass $?" >>$props_result_file - python3 $COMPARE_SCRIPT $oeref $oecal 5 + compare_eband_energy $oeref $oecal echo "CompareOrbXC_pass $?" >>$props_result_file fi From 964455d6bac9a365a55714ed8e42b60546d01307 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 23 Oct 2026 09:45:25 +0800 Subject: [PATCH 49/61] refactor(io): rename LCAO k-space matrix output params and fix misleading docs The out_mat_h_* family of INPUT parameters printed k-space matrices in NAO text format, but their docs falsely claimed R-space CSR output (which is gated by WriteHParams::also_hR, always false upstream). Fix the docs to describe the actual k-space output, and rename the parameters to match the established *k suffix convention. - Doc fix: out_mat_h_t/vnl/vl/vh/vxc/exx descriptions now state k-space NAO text output instead of the bogus "R in CSR format". - out_mat_h_t is a legacy alias of out_mat_tk (the dedicated write_h_t path duplicated the T(k) output and is removed). - Rename: out_mat_h_vnl->out_mat_vnlk, out_mat_h_vl->out_mat_vlk, out_mat_h_vh->out_mat_vhk, out_mat_h_vxc->out_mat_vxck, out_mat_h_exx->out_mat_exxk. Old names remain as legacy aliases resolved during input normalization (new name wins; legacy value maps over). - Update MAT/ directory creation condition and ctrl_scf_lcao to the new canonical names. - Add HkComponentAliases serial test covering alias mapping and new-name-wins priority. Verification: compiled with make -j 30; HkComponentAliases, HsOutputAliases and the full MODULE_IO ctest label (54 tests) pass; code_quality_score is unchanged from the pre-change baseline (remaining debt is pre-existing). --- .../source_io/module_ctrl/ctrl_scf_lcao.cpp | 21 +- source/source_io/module_hs/hterm_writer.cpp | 28 +-- source/source_io/module_hs/hterm_writer.h | 6 +- .../module_parameter/input_parameter.h | 17 +- .../module_parameter/read_inp_out.cpp | 204 ++++++++++++++---- .../source_io/module_parameter/read_input.cpp | 35 ++- .../test_serial/read_input_item_test.cpp | 60 ++++++ 7 files changed, 284 insertions(+), 87 deletions(-) diff --git a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp index 0ee884ec369..132fd428433 100644 --- a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp @@ -53,8 +53,8 @@ void setup_exx_h_params(ModuleIO::WriteHParams& h_params, Exx_NAO& exx_nao, // EXX-H output is unsupported. Reject it explicitly here so the request cannot be silently // dropped (the WARNING_QUIT inside write_h_exx is unreachable at multi-k). ModuleBase::WARNING_QUIT("setup_exx_h_params", - "out_mat_h_exx is only supported for gamma-only: the V^EXX(R) " - "output is not available at multi-k. Use gamma_only."); + "out_mat_exxk (legacy out_mat_h_exx) is only supported for gamma-only: " + "the V^EXX(R) output is not available at multi-k. Use gamma_only."); } template <> @@ -462,28 +462,27 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, h_params.ks_solver = PARAM.inp.ks_solver; h_params.drank = GlobalV::DRANK; h_params.ofs_running = &ofs_running; - if (inp.out_mat_h_t[0]) - { - ModuleIO::write_h_t(h_params); - } - if (inp.out_mat_h_vnl[0]) + // out_mat_h_t is a legacy alias for out_mat_tk (resolved at input + // normalization); the dedicated write_h_t path is removed because it + // duplicated the T(k) output of the out_mat_tk block below. + if (inp.out_mat_vnlk[0]) { ModuleIO::write_h_vnl(h_params); } - if (inp.out_mat_h_vl[0]) + if (inp.out_mat_vlk[0]) { ModuleIO::write_h_vl(h_params); } - if (inp.out_mat_h_vh[0]) + if (inp.out_mat_vhk[0]) { ModuleIO::write_h_vh(h_params); } - if (inp.out_mat_h_vxc[0]) + if (inp.out_mat_vxck[0]) { ModuleIO::write_h_vxc(h_params); } #ifdef __EXX - if (inp.out_mat_h_exx[0] && exx_info.info_global.cal_exx) + if (inp.out_mat_exxk[0] && exx_info.info_global.cal_exx) { // V^EXX(R) output is wired for the gamma (TK==double) exx interfaces. setup_exx_h_params(h_params, exx_nao, exx_info); diff --git a/source/source_io/module_hs/hterm_writer.cpp b/source/source_io/module_hs/hterm_writer.cpp index 3e3c7a00204..5fa656f656b 100644 --- a/source/source_io/module_hs/hterm_writer.cpp +++ b/source/source_io/module_hs/hterm_writer.cpp @@ -12,7 +12,6 @@ #include "source_hamilt/module_gint/gint_interface.h" #include "source_hamilt/module_hcontainer/hcontainer_funcs.h" #include "source_hamilt/module_hcontainer/output_hcontainer.h" -#include "source_lcao/module_operator_lcao/ekinetic.h" #include "source_lcao/module_operator_lcao/nonlocal.h" #include "source_lcao/module_operator_lcao/operator_fs_utils.h" #ifdef __EXX @@ -218,31 +217,6 @@ static void write_h_term(WriteHParams& params, const HTermSpec& spec) ModuleBase::timer::end("ModuleIO", spec.timer_label); } -void write_h_t(WriteHParams& params) -{ - const UnitCell& ucell = *params.ucell; - const Grid_Driver& gd = *params.gd; - const Parallel_Orbitals& pv = *params.pv; - const TwoCenterBundle& two_center_bundle = *params.two_center_bundle; - const LCAO_Orbitals& orb = *params.orb; - const K_Vectors& kv = *params.kv; - const std::vector& orb_cutoff = orb.cutoffs(); - - HTermSpec spec; - spec.timer_label = "write_h_t"; - spec.k_prefix = "tk"; - spec.r_prefix = "t"; - spec.r_label = "T"; - spec.term_name = "kinetic"; - spec.build = [&](hamilt::HContainer& hR_tmp, int /*ispin*/) { - hamilt::EKinetic> - tmp_ekinetic(nullptr, kv.kvec_d, &hR_tmp, &ucell, orb_cutoff, &gd, - two_center_bundle.kinetic_orb.get()); - tmp_ekinetic.contributeHR(); - }; - write_h_term(params, spec); -} - void write_h_vnl(WriteHParams& params) { const UnitCell& ucell = *params.ucell; @@ -408,7 +382,7 @@ void write_h_exx(WriteHParams& params, const Exx_Info& exx_info) ModuleBase::TITLE("ModuleIO", "write_h_exx"); ModuleBase::timer::start("ModuleIO", "write_h_exx"); - // Multi-k out_mat_h_exx is rejected upstream at the call site (setup_exx_h_params in + // Multi-k out_mat_exxk is rejected upstream at the call site (setup_exx_h_params in // ctrl_scf_lcao.cpp); this function is only reached on the gamma-only path. const UnitCell& ucell = *params.ucell; const Parallel_Orbitals& pv = *params.pv; diff --git a/source/source_io/module_hs/hterm_writer.h b/source/source_io/module_hs/hterm_writer.h index 2cc9599548e..0e9f236a98c 100644 --- a/source/source_io/module_hs/hterm_writer.h +++ b/source/source_io/module_hs/hterm_writer.h @@ -65,11 +65,11 @@ struct WriteHParams #endif }; -// Individual Hamiltonian-term writers. write_h_t/vnl/vl/vh/vxc share a common +// Individual Hamiltonian-term writers. write_h_vnl/vl/vh/vxc share a common // build -> write H(k) -> optionally write H(R) skeleton (see hterm_writer.cpp); // the term-specific part is only how hR_tmp is filled. -void write_h_t(WriteHParams& params); - +// The kinetic T(k) output is handled by the out_mat_tk path in ctrl_scf_lcao, +// so there is no write_h_t here. void write_h_vnl(WriteHParams& params); void write_h_vl(WriteHParams& params); diff --git a/source/source_io/module_parameter/input_parameter.h b/source/source_io/module_parameter/input_parameter.h index 207fb89952c..8723fafa7fc 100644 --- a/source/source_io/module_parameter/input_parameter.h +++ b/source/source_io/module_parameter/input_parameter.h @@ -470,12 +470,17 @@ struct Input_para std::vector out_mat_tk = {0, 8}; ///< output T(k) matrix in local basis. std::vector out_mat_l = {0, 8}; ///< output L matrix in local basis. std::vector out_mat_hs2 = {0, 8}; ///< legacy alias for text H(R) and S(R) output - std::vector out_mat_h_t = {0, 8}; ///< output kinetic energy T(R) matrix - std::vector out_mat_h_vnl = {0, 8}; ///< output nonlocal pseudopotential Vnl(R) matrix - std::vector out_mat_h_vl = {0, 8}; ///< output local pseudopotential Vl(R) matrix - std::vector out_mat_h_vh = {0, 8}; ///< output Hartree Vh(R) matrix - std::vector out_mat_h_vxc = {0, 8}; ///< output XC Vxc(R) matrix - std::vector out_mat_h_exx = {0, 8}; ///< output exact-exchange Vexx(R) matrix + std::vector out_mat_h_t = {0, 8}; ///< legacy alias for out_mat_tk (kinetic T(k) matrix) + std::vector out_mat_vnlk = {0, 8}; ///< output nonlocal pseudopotential Vnl(k) matrix + std::vector out_mat_vlk = {0, 8}; ///< output local pseudopotential Vl(k) matrix + std::vector out_mat_vhk = {0, 8}; ///< output Hartree Vh(k) matrix + std::vector out_mat_vxck = {0, 8}; ///< output XC Vxc(k) matrix + std::vector out_mat_exxk = {0, 8}; ///< output exact-exchange Vexx(k) matrix + std::vector out_mat_h_vnl = {0, 8}; ///< legacy alias for out_mat_vnlk + std::vector out_mat_h_vl = {0, 8}; ///< legacy alias for out_mat_vlk + std::vector out_mat_h_vh = {0, 8}; ///< legacy alias for out_mat_vhk + std::vector out_mat_h_vxc = {0, 8}; ///< legacy alias for out_mat_vxck + std::vector out_mat_h_exx = {0, 8}; ///< legacy alias for out_mat_exxk std::vector out_mat_dh = {0, 8}; ///< output dH/dR matrices with precision std::vector out_mat_dh_t = {0, 8}; ///< output kinetic dH/dR (dT/dR) matrices std::vector out_mat_dh_vl = {0, 8}; ///< output local pseudopotential dH/dR (dV^L/dR) matrices diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index 96090750bbe..a8b7b2ce6ae 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -1008,11 +1008,10 @@ For binary output, each file uses the same basename as text output with a .dat s } { Input_Item item("out_mat_h_t"); - item.annotation = "output kinetic energy T(R) matrix"; + item.annotation = "legacy alias for out_mat_tk"; item.category = "Output information"; item.type = "Integer"; - item.description = "Whether to print files containing the kinetic energy matrix T(R) in CSR format." - "\n\nSee out_hsr for format details."; + item.description = "Legacy alias for out_mat_tk, which outputs the kinetic energy matrix T(k) for each k-point in the NAO basis. The optional second integer controls text precision. If both out_mat_tk and out_mat_h_t are present, out_mat_tk takes precedence."; item.default_value = "0 8"; item.unit = "Ry"; item.read_value = [](const Input_Item& item, Parameter& para) { @@ -1023,21 +1022,47 @@ For binary output, each file uses the same basename as text output with a .dat s catch (const std::invalid_argument& e) { ModuleBase::WARNING("Input", "out_mat_h_t enable flag must be 0/1, using default 0"); } + if (count >= 2) try { para.input.out_mat_h_t[1] = std::stoi(item.str_values[1]); } + catch (const std::invalid_argument&) { /* do nothing */ } + catch (const std::out_of_range&) { /* do nothing */ } + }; + sync_intvec(input.out_mat_h_t, 2, 0); + this->add_item(item); + } + { + Input_Item item("out_mat_vnlk"); + item.annotation = "output nonlocal pseudopotential Vnl(k) matrix"; + item.category = "Output information"; + item.type = "Integer"; + item.description = "Whether to print the nonlocal pseudopotential matrix Vnl(k) for each k-point in the NAO basis, into text files OUT.${suffix}/vnlk*_nao.txt." + "\n\nThe optional second integer controls text precision (default 8)."; + item.default_value = "0 8"; + item.unit = "Ry"; + item.read_value = [](const Input_Item& item, Parameter& para) { + const size_t count = item.get_size(); + try { + para.input.out_mat_vnlk[0] = assume_as_boolean(item.str_values[0]); + } + catch (const std::invalid_argument& e) { + ModuleBase::WARNING("Input", "out_mat_vnlk enable flag must be 0/1, using default 0"); + } + if (count >= 2) try { para.input.out_mat_vnlk[1] = std::stoi(item.str_values[1]); } + catch (const std::invalid_argument&) { /* do nothing */ } + catch (const std::out_of_range&) { /* do nothing */ } }; item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.out_mat_h_t[0] && para.input.nspin == 4) - ModuleBase::WARNING_QUIT("ReadInput", "out_mat_h_t is not available for nspin = 4"); + if (para.input.out_mat_vnlk[0] && para.input.nspin == 4) + ModuleBase::WARNING_QUIT("ReadInput", "out_mat_vnlk is not available for nspin = 4"); }; - sync_intvec(input.out_mat_h_t, 2, 0); + sync_intvec(input.out_mat_vnlk, 2, 0); this->add_item(item); } { Input_Item item("out_mat_h_vnl"); - item.annotation = "output nonlocal pseudopotential Vnl(R) matrix"; + item.annotation = "legacy alias for out_mat_vnlk"; item.category = "Output information"; item.type = "Integer"; - item.description = "Whether to print files containing the nonlocal pseudopotential matrix Vnl(R) in CSR format." - "\n\nSee out_hsr for format details."; + item.description = "Legacy alias for out_mat_vnlk, which outputs the nonlocal pseudopotential matrix Vnl(k) for each k-point in the NAO basis. The optional second integer controls text precision. If both out_mat_vnlk and out_mat_h_vnl are present, out_mat_vnlk takes precedence."; item.default_value = "0 8"; item.unit = "Ry"; item.read_value = [](const Input_Item& item, Parameter& para) { @@ -1048,21 +1073,47 @@ For binary output, each file uses the same basename as text output with a .dat s catch (const std::invalid_argument& e) { ModuleBase::WARNING("Input", "out_mat_h_vnl enable flag must be 0/1, using default 0"); } + if (count >= 2) try { para.input.out_mat_h_vnl[1] = std::stoi(item.str_values[1]); } + catch (const std::invalid_argument&) { /* do nothing */ } + catch (const std::out_of_range&) { /* do nothing */ } + }; + sync_intvec(input.out_mat_h_vnl, 2, 0); + this->add_item(item); + } + { + Input_Item item("out_mat_vlk"); + item.annotation = "output local pseudopotential Vl(k) matrix"; + item.category = "Output information"; + item.type = "Integer"; + item.description = "Whether to print the local pseudopotential matrix Vl(k) for each k-point in the NAO basis, into text files OUT.${suffix}/vlk*_nao.txt." + "\n\nThe optional second integer controls text precision (default 8)."; + item.default_value = "0 8"; + item.unit = "Ry"; + item.read_value = [](const Input_Item& item, Parameter& para) { + const size_t count = item.get_size(); + try { + para.input.out_mat_vlk[0] = assume_as_boolean(item.str_values[0]); + } + catch (const std::invalid_argument& e) { + ModuleBase::WARNING("Input", "out_mat_vlk enable flag must be 0/1, using default 0"); + } + if (count >= 2) try { para.input.out_mat_vlk[1] = std::stoi(item.str_values[1]); } + catch (const std::invalid_argument&) { /* do nothing */ } + catch (const std::out_of_range&) { /* do nothing */ } }; item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.out_mat_h_vnl[0] && para.input.nspin == 4) - ModuleBase::WARNING_QUIT("ReadInput", "out_mat_h_vnl is not available for nspin = 4"); + if (para.input.out_mat_vlk[0] && para.input.nspin == 4) + ModuleBase::WARNING_QUIT("ReadInput", "out_mat_vlk is not available for nspin = 4"); }; - sync_intvec(input.out_mat_h_vnl, 2, 0); + sync_intvec(input.out_mat_vlk, 2, 0); this->add_item(item); } { Input_Item item("out_mat_h_vl"); - item.annotation = "output local pseudopotential Vl(R) matrix"; + item.annotation = "legacy alias for out_mat_vlk"; item.category = "Output information"; item.type = "Integer"; - item.description = "Whether to print files containing the local pseudopotential matrix Vl(R) in CSR format." - "\n\nSee out_hsr for format details."; + item.description = "Legacy alias for out_mat_vlk, which outputs the local pseudopotential matrix Vl(k) for each k-point in the NAO basis. The optional second integer controls text precision. If both out_mat_vlk and out_mat_h_vl are present, out_mat_vlk takes precedence."; item.default_value = "0 8"; item.unit = "Ry"; item.read_value = [](const Input_Item& item, Parameter& para) { @@ -1073,21 +1124,47 @@ For binary output, each file uses the same basename as text output with a .dat s catch (const std::invalid_argument& e) { ModuleBase::WARNING("Input", "out_mat_h_vl enable flag must be 0/1, using default 0"); } + if (count >= 2) try { para.input.out_mat_h_vl[1] = std::stoi(item.str_values[1]); } + catch (const std::invalid_argument&) { /* do nothing */ } + catch (const std::out_of_range&) { /* do nothing */ } + }; + sync_intvec(input.out_mat_h_vl, 2, 0); + this->add_item(item); + } + { + Input_Item item("out_mat_vhk"); + item.annotation = "output Hartree Vh(k) matrix"; + item.category = "Output information"; + item.type = "Integer"; + item.description = "Whether to print the Hartree matrix Vh(k) for each k-point in the NAO basis, into text files OUT.${suffix}/vhk*_nao.txt." + "\n\nThe optional second integer controls text precision (default 8)."; + item.default_value = "0 8"; + item.unit = "Ry"; + item.read_value = [](const Input_Item& item, Parameter& para) { + const size_t count = item.get_size(); + try { + para.input.out_mat_vhk[0] = assume_as_boolean(item.str_values[0]); + } + catch (const std::invalid_argument& e) { + ModuleBase::WARNING("Input", "out_mat_vhk enable flag must be 0/1, using default 0"); + } + if (count >= 2) try { para.input.out_mat_vhk[1] = std::stoi(item.str_values[1]); } + catch (const std::invalid_argument&) { /* do nothing */ } + catch (const std::out_of_range&) { /* do nothing */ } }; item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.out_mat_h_vl[0] && para.input.nspin == 4) - ModuleBase::WARNING_QUIT("ReadInput", "out_mat_h_vl is not available for nspin = 4"); + if (para.input.out_mat_vhk[0] && para.input.nspin == 4) + ModuleBase::WARNING_QUIT("ReadInput", "out_mat_vhk is not available for nspin = 4"); }; - sync_intvec(input.out_mat_h_vl, 2, 0); + sync_intvec(input.out_mat_vhk, 2, 0); this->add_item(item); } { Input_Item item("out_mat_h_vh"); - item.annotation = "output Hartree Vh(R) matrix"; + item.annotation = "legacy alias for out_mat_vhk"; item.category = "Output information"; item.type = "Integer"; - item.description = "Whether to print files containing the Hartree matrix Vh(R) in CSR format." - "\n\nSee out_hsr for format details."; + item.description = "Legacy alias for out_mat_vhk, which outputs the Hartree matrix Vh(k) for each k-point in the NAO basis. The optional second integer controls text precision. If both out_mat_vhk and out_mat_h_vh are present, out_mat_vhk takes precedence."; item.default_value = "0 8"; item.unit = "Ry"; item.read_value = [](const Input_Item& item, Parameter& para) { @@ -1098,21 +1175,47 @@ For binary output, each file uses the same basename as text output with a .dat s catch (const std::invalid_argument& e) { ModuleBase::WARNING("Input", "out_mat_h_vh enable flag must be 0/1, using default 0"); } + if (count >= 2) try { para.input.out_mat_h_vh[1] = std::stoi(item.str_values[1]); } + catch (const std::invalid_argument&) { /* do nothing */ } + catch (const std::out_of_range&) { /* do nothing */ } + }; + sync_intvec(input.out_mat_h_vh, 2, 0); + this->add_item(item); + } + { + Input_Item item("out_mat_vxck"); + item.annotation = "output exchange-correlation Vxc(k) matrix"; + item.category = "Output information"; + item.type = "Integer"; + item.description = "Whether to print the exchange-correlation matrix Vxc(k) for each k-point in the NAO basis, into text files OUT.${suffix}/vxck*_nao.txt." + "\n\nThe optional second integer controls text precision (default 8)."; + item.default_value = "0 8"; + item.unit = "Ry"; + item.read_value = [](const Input_Item& item, Parameter& para) { + const size_t count = item.get_size(); + try { + para.input.out_mat_vxck[0] = assume_as_boolean(item.str_values[0]); + } + catch (const std::invalid_argument& e) { + ModuleBase::WARNING("Input", "out_mat_vxck enable flag must be 0/1, using default 0"); + } + if (count >= 2) try { para.input.out_mat_vxck[1] = std::stoi(item.str_values[1]); } + catch (const std::invalid_argument&) { /* do nothing */ } + catch (const std::out_of_range&) { /* do nothing */ } }; item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.out_mat_h_vh[0] && para.input.nspin == 4) - ModuleBase::WARNING_QUIT("ReadInput", "out_mat_h_vh is not available for nspin = 4"); + if (para.input.out_mat_vxck[0] && para.input.nspin == 4) + ModuleBase::WARNING_QUIT("ReadInput", "out_mat_vxck is not available for nspin = 4"); }; - sync_intvec(input.out_mat_h_vh, 2, 0); + sync_intvec(input.out_mat_vxck, 2, 0); this->add_item(item); } { Input_Item item("out_mat_h_vxc"); - item.annotation = "output exchange-correlation Vxc(R) matrix"; + item.annotation = "legacy alias for out_mat_vxck"; item.category = "Output information"; item.type = "Integer"; - item.description = "Whether to print files containing the XC matrix Vxc(R) in CSR format." - "\n\nSee out_hsr for format details."; + item.description = "Legacy alias for out_mat_vxck, which outputs the exchange-correlation matrix Vxc(k) for each k-point in the NAO basis. The optional second integer controls text precision. If both out_mat_vxck and out_mat_h_vxc are present, out_mat_vxck takes precedence."; item.default_value = "0 8"; item.unit = "Ry"; item.read_value = [](const Input_Item& item, Parameter& para) { @@ -1123,21 +1226,47 @@ For binary output, each file uses the same basename as text output with a .dat s catch (const std::invalid_argument& e) { ModuleBase::WARNING("Input", "out_mat_h_vxc enable flag must be 0/1, using default 0"); } + if (count >= 2) try { para.input.out_mat_h_vxc[1] = std::stoi(item.str_values[1]); } + catch (const std::invalid_argument&) { /* do nothing */ } + catch (const std::out_of_range&) { /* do nothing */ } + }; + sync_intvec(input.out_mat_h_vxc, 2, 0); + this->add_item(item); + } + { + Input_Item item("out_mat_exxk"); + item.annotation = "output exact-exchange Vexx(k) matrix"; + item.category = "Output information"; + item.type = "Integer"; + item.description = "Whether to print the exact-exchange matrix Vexx(k) for each k-point in the NAO basis, into text files OUT.${suffix}/vexxk*_nao.txt." + "\n\nThe optional second integer controls text precision (default 8). Only available for gamma-only calculations with exact exchange enabled."; + item.default_value = "0 8"; + item.unit = "Ry"; + item.read_value = [](const Input_Item& item, Parameter& para) { + const size_t count = item.get_size(); + try { + para.input.out_mat_exxk[0] = assume_as_boolean(item.str_values[0]); + } + catch (const std::invalid_argument& e) { + ModuleBase::WARNING("Input", "out_mat_exxk enable flag must be 0/1, using default 0"); + } + if (count >= 2) try { para.input.out_mat_exxk[1] = std::stoi(item.str_values[1]); } + catch (const std::invalid_argument&) { /* do nothing */ } + catch (const std::out_of_range&) { /* do nothing */ } }; item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.out_mat_h_vxc[0] && para.input.nspin == 4) - ModuleBase::WARNING_QUIT("ReadInput", "out_mat_h_vxc is not available for nspin = 4"); + if (para.input.out_mat_exxk[0] && para.input.nspin == 4) + ModuleBase::WARNING_QUIT("ReadInput", "out_mat_exxk is not available for nspin = 4"); }; - sync_intvec(input.out_mat_h_vxc, 2, 0); + sync_intvec(input.out_mat_exxk, 2, 0); this->add_item(item); } { Input_Item item("out_mat_h_exx"); - item.annotation = "output exact-exchange Vexx(R) matrix"; + item.annotation = "legacy alias for out_mat_exxk"; item.category = "Output information"; item.type = "Integer"; - item.description = "Whether to print files containing the exact-exchange matrix Vexx(R) in CSR format." - "\n\nSee out_hsr for format details."; + item.description = "Legacy alias for out_mat_exxk, which outputs the exact-exchange matrix Vexx(k) for each k-point in the NAO basis. The optional second integer controls text precision. If both out_mat_exxk and out_mat_h_exx are present, out_mat_exxk takes precedence."; item.default_value = "0 8"; item.unit = "Ry"; item.read_value = [](const Input_Item& item, Parameter& para) { @@ -1148,10 +1277,9 @@ For binary output, each file uses the same basename as text output with a .dat s catch (const std::invalid_argument& e) { ModuleBase::WARNING("Input", "out_mat_h_exx enable flag must be 0/1, using default 0"); } - }; - item.check_value = [](const Input_Item& item, const Parameter& para) { - if (para.input.out_mat_h_exx[0] && para.input.nspin == 4) - ModuleBase::WARNING_QUIT("ReadInput", "out_mat_h_exx is not available for nspin = 4"); + if (count >= 2) try { para.input.out_mat_h_exx[1] = std::stoi(item.str_values[1]); } + catch (const std::invalid_argument&) { /* do nothing */ } + catch (const std::out_of_range&) { /* do nothing */ } }; sync_intvec(input.out_mat_h_exx, 2, 0); this->add_item(item); @@ -1195,7 +1323,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 = "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." + 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}/eband_vxc.txt. When EXX is calculated, its local and EXX contributions are also saved as eband_vxc_local.txt and eband_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"; diff --git a/source/source_io/module_parameter/read_input.cpp b/source/source_io/module_parameter/read_input.cpp index f87002d843a..457d053901a 100644 --- a/source/source_io/module_parameter/read_input.cpp +++ b/source/source_io/module_parameter/read_input.cpp @@ -326,8 +326,8 @@ void ReadInput::create_directory(const Parameter& param) || param.input.out_mat_dh[0] > 0 || param.input.out_mat_ds[0] > 0 || param.input.out_mat_xc || param.input.out_mat_xc2[0] > 0 || param.input.out_hr_npz || param.input.out_hsr_npz || param.input.out_dm_npz - || param.input.out_mat_h_t[0] > 0 || param.input.out_mat_h_vnl[0] > 0 || param.input.out_mat_h_vl[0] > 0 - || param.input.out_mat_h_vh[0] > 0 || param.input.out_mat_h_vxc[0] > 0 || param.input.out_mat_h_exx[0] > 0 + || param.input.out_mat_vnlk[0] > 0 || param.input.out_mat_vlk[0] > 0 + || param.input.out_mat_vhk[0] > 0 || param.input.out_mat_vxck[0] > 0 || param.input.out_mat_exxk[0] > 0 || param.input.out_mat_dh_t[0] > 0 || param.input.out_mat_dh_vl[0] > 0 || param.input.out_mat_dh_vnl[0] > 0 || param.input.out_mat_dh_vh[0] > 0 || param.input.out_mat_dh_vxc[0] > 0, param.globalv.global_mat_dir} @@ -564,6 +564,37 @@ void ReadInput::normalize_hs_output_options(Parameter& param) { param.input.out_hsk[0] = 1; } + + // Merge the legacy out_mat_h_* aliases into their canonical k-space names. + // Each alias is accepted for backward compatibility; when both names are + // present the canonical new name takes precedence. out_mat_h_t is an alias + // for out_mat_tk because both produce the T(k) NAO text matrices (the + // former via the redundant write_h_t path, now removed). + const auto merge_alias = [&](const char* new_label, const char* old_label, + std::vector& new_val, const std::vector& old_val) { + const bool new_is_read = item_is_read(new_label); + const bool old_is_read = item_is_read(old_label); + if (new_is_read) + { + if (old_is_read) + { + ModuleBase::WARNING("ReadInput", + std::string("both ") + new_label + " and " + old_label + + " are set; " + new_label + " takes precedence"); + } + } + else if (old_is_read) + { + new_val = old_val; + } + }; + + merge_alias("out_mat_tk", "out_mat_h_t", param.input.out_mat_tk, param.input.out_mat_h_t); + merge_alias("out_mat_vnlk", "out_mat_h_vnl", param.input.out_mat_vnlk, param.input.out_mat_h_vnl); + merge_alias("out_mat_vlk", "out_mat_h_vl", param.input.out_mat_vlk, param.input.out_mat_h_vl); + merge_alias("out_mat_vhk", "out_mat_h_vh", param.input.out_mat_vhk, param.input.out_mat_h_vh); + merge_alias("out_mat_vxck", "out_mat_h_vxc", param.input.out_mat_vxck, param.input.out_mat_h_vxc); + merge_alias("out_mat_exxk", "out_mat_h_exx", param.input.out_mat_exxk, param.input.out_mat_h_exx); } void ReadInput::write_txt_input(const Parameter& param, const std::string& filename) 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 57cdc38e873..81bdbefcd70 100644 --- a/source/source_io/test_serial/read_input_item_test.cpp +++ b/source/source_io/test_serial/read_input_item_test.cpp @@ -1421,6 +1421,66 @@ TEST_F(InputTest, HsOutputAliases) EXPECT_EQ(TestParameters::input(param).out_hsk[0], 1); } } + +// The out_mat_h_* keywords are legacy aliases for the canonical k-space names +// (out_mat_tk and out_mat_{vnl,vl,vh,vxc,exx}k). Verify that each alias feeds +// the canonical value and that the canonical name wins when both are present. +TEST_F(InputTest, HkComponentAliases) +{ + // alias alone populates the canonical name + { + ModuleIO::ReadInput readinput(0); + Parameter param; + auto legacy = find_label("out_mat_h_t", readinput.input_lists); + legacy->second.str_values = {"1", "5"}; + legacy->second.read_value(legacy->second, param); + readinput.normalize_hs_output_options(param); + EXPECT_EQ(TestParameters::input(param).out_mat_tk[0], 1); + EXPECT_EQ(TestParameters::input(param).out_mat_tk[1], 5); + } + // canonical name takes precedence over the alias + { + ModuleIO::ReadInput readinput(0); + Parameter param; + auto legacy = find_label("out_mat_h_t", readinput.input_lists); + auto primary = find_label("out_mat_tk", readinput.input_lists); + legacy->second.str_values = {"1", "5"}; + primary->second.str_values = {"1", "3"}; + legacy->second.read_value(legacy->second, param); + primary->second.read_value(primary->second, param); + readinput.normalize_hs_output_options(param); + EXPECT_EQ(TestParameters::input(param).out_mat_tk[0], 1); + EXPECT_EQ(TestParameters::input(param).out_mat_tk[1], 3); + } + // the five renamed terms: legacy alias -> canonical k-space name + const std::vector> aliases = { + {"out_mat_h_vnl", "out_mat_vnlk"}, + {"out_mat_h_vl", "out_mat_vlk"}, + {"out_mat_h_vh", "out_mat_vhk"}, + {"out_mat_h_vxc", "out_mat_vxck"}, + {"out_mat_h_exx", "out_mat_exxk"}, + }; + for (const auto& pair : aliases) + { + SCOPED_TRACE(pair.first); + ModuleIO::ReadInput readinput(0); + Parameter param; + auto legacy = find_label(pair.first, readinput.input_lists); + auto primary = find_label(pair.second, readinput.input_lists); + legacy->second.str_values = {"1", "6"}; + legacy->second.read_value(legacy->second, param); + readinput.normalize_hs_output_options(param); + const auto& canon = TestParameters::input(param); + const std::vector* value = nullptr; + if (pair.second == "out_mat_vnlk") { value = &canon.out_mat_vnlk; } + else if (pair.second == "out_mat_vlk") { value = &canon.out_mat_vlk; } + else if (pair.second == "out_mat_vhk") { value = &canon.out_mat_vhk; } + else if (pair.second == "out_mat_vxck") { value = &canon.out_mat_vxck; } + else { value = &canon.out_mat_exxk; } + EXPECT_EQ((*value)[0], 1); + EXPECT_EQ((*value)[1], 6); + } +} TEST_F(InputTest, PreserveExplicitMagneticMixing) { const std::string filename = testing::TempDir() + "abacus_magnetic_mixing_INPUT"; From dfbcbbbc1ef41a2bc83a1d1522b0f9ce6d208085 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 23 Oct 2026 15:21:31 +0800 Subject: [PATCH 50/61] refactor(io): rename mode-2 structure output dir STRU/ -> STRUC/ The out_app_flag=2 structure subdirectory shared the name of the STRU input file, which is confusing in docs, scripts, and on case-insensitive filesystems. Rename it to STRUC/ and update the unit test, INPUT help text, and comments accordingly. MD restart still recognizes structure files written into the legacy STRU/ subdirectory by previous releases. --- source/source_io/module_parameter/read_inp_out.cpp | 4 ++-- source/source_io/module_parameter/read_input_item_md.cpp | 2 +- source/source_io/module_parameter/read_set_globalv.cpp | 7 +++++-- source/source_io/test_serial/io_sys_var_test.cpp | 2 +- source/source_md/run_md.cpp | 3 ++- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index a8b7b2ce6ae..8c77c3a341b 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -457,7 +457,7 @@ Note: values 2 and 3 are no longer supported. Setting out_dos to 2 or 3 will rai item.annotation = "output the structure files per ion step"; item.category = "Output information"; item.type = "Integer"; - item.description = "Controls the output of structure files per ionic step. The files are written to the OUT.{suffix}/ directory. Each file corresponds to the structure at the current ionic step, and includes a header comment with the ABACUS version, timestamp, energy, and stress tensor. When out_freq_ion is positive, the numbered files STRU{istep} (or STRU{istep}.cif) are written every out_freq_ion steps; when out_freq_ion is 0, no numbered files are output. This parameter is effective for scf/nscf/relax/cell-relax and md. For scf/nscf only STRU_FINAL (or STRU_FINAL.cif) is written. For MD, structure output follows the same out_freq_ion frequency as the restart file.\n" + item.description = "Controls the output of structure files per ionic step. The files are written to the OUT.{suffix}/ directory (or the OUT.{suffix}/STRUC/ subdirectory when out_app_flag is 2). Each file corresponds to the structure at the current ionic step, and includes a header comment with the ABACUS version, timestamp, energy, and stress tensor. When out_freq_ion is positive, the numbered files STRU{istep} (or STRU{istep}.cif) are written every out_freq_ion steps; when out_freq_ion is 0, no numbered files are output. This parameter is effective for scf/nscf/relax/cell-relax and md. For scf/nscf only STRU_FINAL (or STRU_FINAL.cif) is written. For MD, structure output follows the same out_freq_ion frequency as the restart file.\n" " - 0: No structure files are output.\n" " - 1: ABACUS STRU format files are output. The latest structure is written to STRU_NOW (overwritten each step), the numbered file STRU{istep} (e.g., STRU1, STRU2) is written every out_freq_ion steps (when out_freq_ion is positive), and the final converged structure is written to STRU_FINAL. No CIF files are output. When out_app_flag is 1, the numbered STRU{istep} files are replaced by a single fixed-name STRU file that appends one structure block (with a one-based \"# Ionic Step N\" or \"# MD step N\" comment) per written step.\n" " - 2: CIF format files are output. The latest structure is written to STRU_NOW.cif (overwritten each step), the numbered file STRU{istep}.cif (e.g., STRU1.cif, STRU2.cif) is written every out_freq_ion steps (when out_freq_ion is positive), and the final converged structure is written to STRU_FINAL.cif. No non-CIF files are output. When out_app_flag is 1, the numbered files are replaced by a fixed-name STRU.cif overwritten each written step.\n" @@ -1527,7 +1527,7 @@ The circle order of the charge density on real space grids is: x is the outer lo item.description = R"(Controls output file organization during multi-step calculations (MD, relax, RT-TDDFT). The directory routing applies to both LCAO and PW basis calculations. * 0: Write to OUT directory directly, no append (each step's file carries a g{step} suffix) * 1: Reuse fixed filenames across steps (default, backward compatible): text outputs append new records, cube outputs accumulate one frame per written step -* 2: Write to subdirectory (PSI/, CHG/, POT/, ELF/, DOS/, DIP/, MAT/), files carry a g{step} suffix +* 2: Write to subdirectory (STRUC/ for structure files, PSI/, CHG/, POT/, ELF/, DOS/, DIP/, MAT/), files carry a g{step} suffix The legacy Boolean spellings (true/false, yes/no, on/off, t/f, y/n, and the dotted forms) are still accepted and map to modes 1/0 as before. For more information, refer to the documentation of out_mat_r, out_hsr, out_mat_t, out_mat_dh, out_hsk, out_wfc_lcao, out_chg, out_pot, out_elf, out_dos, out_dipole, out_stru.)"; item.default_value = "1"; diff --git a/source/source_io/module_parameter/read_input_item_md.cpp b/source/source_io/module_parameter/read_input_item_md.cpp index eb6ac3db167..77bf505b475 100644 --- a/source/source_io/module_parameter/read_input_item_md.cpp +++ b/source/source_io/module_parameter/read_input_item_md.cpp @@ -157,7 +157,7 @@ Note that md_tlast is only used in NVT/NPT simulations. If md_tlast is unset or item.category = "Molecular dynamics"; item.type = "Boolean"; item.description = R"(Control whether to restart molecular dynamics calculations and time-dependent density functional theory calculations. -* True: ABACUS will read in {md_step}, then read in the corresponding STRU_MD_suffix/STRU/ automatically. For tddft, ABACUS will also read in WFC_NAO_K${kpoint} of the last step (You need to set out_wfc_lcao=1 and out_app_flag=0 to obtain this file). +* True: ABACUS will read in {md_step}, then read in the corresponding structure file automatically (STRU{istep} from the STRUC/ output directory when out_app_flag=2; older releases wrote STRU_MD_{istep} into a STRU/ subdirectory, which is still recognized). For tddft, ABACUS will also read in WFC_NAO_K${kpoint} of the last step (You need to set out_wfc_lcao=1 and out_app_flag=0 to obtain this file). * False: ABACUS will start molecular dynamics calculations normally from the first step.)"; item.default_value = "False"; item.unit = ""; diff --git a/source/source_io/module_parameter/read_set_globalv.cpp b/source/source_io/module_parameter/read_set_globalv.cpp index 5a74f8b41b8..f7e85586ddb 100644 --- a/source/source_io/module_parameter/read_set_globalv.cpp +++ b/source/source_io/module_parameter/read_set_globalv.cpp @@ -117,9 +117,11 @@ void ReadInput::set_global_dir(const Input_para& inp, System_para& sys) /// Set subdirectories based on out_app_flag /// out_app_flag == 2: use unified uppercase subdirectories /// otherwise: keep backward-compatible behavior (all point to global_out_dir) + /// The structure subdirectory is named STRUC (not STRU) so it cannot be + /// confused with the STRU input file. if (inp.out_app_flag == 2) { - sys.global_stru_dir = sys.global_out_dir + "STRU/"; + sys.global_stru_dir = sys.global_out_dir + "STRUC/"; sys.global_psi_dir = sys.global_out_dir + "PSI/"; sys.global_chg_dir = sys.global_out_dir + "CHG/"; sys.global_pot_dir = sys.global_out_dir + "POT/"; @@ -172,7 +174,8 @@ void ReadInput::set_global_dir(const Input_para& inp, System_para& sys) { // Releases before the out_app_flag rework always wrote // STRU_MD_{istep} into a fixed STRU/ subdirectory of the - // output directory; keep those outputs restartable. + // output directory (the mode-2 directory has since been + // renamed to STRUC/); keep those outputs restartable. const std::string legacy_dir = sys.global_out_dir + "STRU/"; sys.global_in_stru = find_md_restart_stru(legacy_dir, istep); } diff --git a/source/source_io/test_serial/io_sys_var_test.cpp b/source/source_io/test_serial/io_sys_var_test.cpp index 2fe0fa4ba6d..48ab0ec0a8e 100644 --- a/source/source_io/test_serial/io_sys_var_test.cpp +++ b/source/source_io/test_serial/io_sys_var_test.cpp @@ -175,7 +175,7 @@ TEST_F(InputTest, SetGlobalDirMode2) TestParameters::set_global_dir(readinput, input, sys); EXPECT_EQ(sys.global_out_dir, "OUT.modetwo/"); - EXPECT_EQ(sys.global_stru_dir, "OUT.modetwo/STRU/"); + EXPECT_EQ(sys.global_stru_dir, "OUT.modetwo/STRUC/"); EXPECT_EQ(sys.global_psi_dir, "OUT.modetwo/PSI/"); EXPECT_EQ(sys.global_chg_dir, "OUT.modetwo/CHG/"); EXPECT_EQ(sys.global_pot_dir, "OUT.modetwo/POT/"); diff --git a/source/source_md/run_md.cpp b/source/source_md/run_md.cpp index f7d49433d2c..3315cdbea3d 100644 --- a/source/source_md/run_md.cpp +++ b/source/source_md/run_md.cpp @@ -198,7 +198,8 @@ void md_line(MDCell& mdcell, /// out_app_flag == 1 writes one fixed-name "STRU" file. A previous /// release kept per-step STRU_MD_{step} files inside a STRU/ - /// subdirectory of the output directory, so an MD restarted from those + /// subdirectory of the output directory (the mode-2 directory has + /// since been renamed to STRUC/), so an MD restarted from those /// files still sees that directory. Writing the fixed name on top of a /// directory aborts the run, therefore the legacy numbered layout is /// kept whenever such a directory is present. From 0d295c4ad6b51da74bdb30d8be1896f93005c90a Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 23 Oct 2026 17:07:11 +0800 Subject: [PATCH 51/61] fix(lcao): skip Hartree gint on grid-parallel ranks with no local grid Under grid-parallel MPI distribution a rank may own no real-space grid points (rho_basis->nrxx == 0). write_h_vh then builds an empty matrix v_h(nspin, 0) and unconditionally dereferences &v_h(ispin, 0), tripping the matrix.h `ic < nc` assertion and aborting the run when out_mat_h_vh / out_mat_h 2 / out_mat_vhks_k is enabled. The SCF path itself is unaffected because it never indexes the matrix this way. Keep setup_veff_hcontainer so the HContainer sparsity stays consistent for the subsequent folding_HR/save_mat, and only skip the gint potential integral when there is no local potential to integrate. --- source/source_io/module_hs/hterm_writer.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/source/source_io/module_hs/hterm_writer.cpp b/source/source_io/module_hs/hterm_writer.cpp index 5fa656f656b..55df8bc2066 100644 --- a/source/source_io/module_hs/hterm_writer.cpp +++ b/source/source_io/module_hs/hterm_writer.cpp @@ -286,7 +286,13 @@ void write_h_vh(WriteHParams& params) spec.term_name = "hartree"; spec.build = [&](hamilt::HContainer& hR_tmp, int ispin) { setup_veff_hcontainer(hR_tmp, ucell, gd, pv, orb_cutoff); - ModuleGint::cal_gint_vl(&v_h(ispin, 0), &hR_tmp); + // Under grid-parallel distribution a rank may own no real-space grid + // points (nrxx == 0), in which case v_h is an empty matrix and there + // is no local potential to integrate; skip the gint evaluation there. + if (v_h.c != nullptr) + { + ModuleGint::cal_gint_vl(&v_h(ispin, 0), &hR_tmp); + } }; write_h_term(params, spec); } From a6650e3fbf704b19ef8d752fc9353a0325d42063 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 23 Oct 2026 17:51:08 +0800 Subject: [PATCH 52/61] fix(lcao): guard local/XC gint on grid-parallel ranks with no local grid Same class of bug as the preceding Hartree fix: when a grid-parallel rank owns no real-space grid points (nrxx == 0), write_h_vl receives a null v_local from pot->get_fixed_v() and write_h_vxc gets an empty v_xc matrix from XC_Functional::v_xc; both then index the potential unconditionally. Keep setup_veff_hcontainer for a consistent HContainer sparsity and only skip the gint integral when there is no local potential on this rank. --- source/source_io/module_hs/hterm_writer.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/source/source_io/module_hs/hterm_writer.cpp b/source/source_io/module_hs/hterm_writer.cpp index 55df8bc2066..0699544e642 100644 --- a/source/source_io/module_hs/hterm_writer.cpp +++ b/source/source_io/module_hs/hterm_writer.cpp @@ -260,7 +260,12 @@ void write_h_vl(WriteHParams& params) spec.term_name = "local"; spec.build = [&](hamilt::HContainer& hR_tmp, int /*ispin*/) { setup_veff_hcontainer(hR_tmp, ucell, gd, pv, orb_cutoff); - ModuleGint::cal_gint_vl(v_local, &hR_tmp); + // A grid-parallel rank may own no real-space grid points (nrxx == 0), + // leaving v_local null; there is no local potential to integrate there. + if (v_local != nullptr) + { + ModuleGint::cal_gint_vl(v_local, &hR_tmp); + } }; write_h_term(params, spec); } @@ -329,7 +334,12 @@ void write_h_vxc(WriteHParams& params) spec.term_name = "xc"; spec.build = [&](hamilt::HContainer& hR_tmp, int ispin) { setup_veff_hcontainer(hR_tmp, ucell, gd, pv, orb_cutoff); - ModuleGint::cal_gint_vl(&v_xc(ispin, 0), &hR_tmp); + // A grid-parallel rank may own no real-space grid points (nrxx == 0), + // leaving v_xc an empty matrix; skip the gint evaluation there. + if (v_xc.c != nullptr) + { + ModuleGint::cal_gint_vl(&v_xc(ispin, 0), &hR_tmp); + } }; write_h_term(params, spec); } From ec9ebf96abdc45cf3647698994f9c2ec5257e877 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 23 Oct 2026 19:28:03 +0800 Subject: [PATCH 53/61] fix(lcao): keep gint collective matched on empty-grid ranks The preceding two commits wrapped the cal_gint_vl call in a pointer guard, so a grid-parallel rank with nrxx == 0 skipped it entirely. cal_gint_vl reaches an MPI_Alltoall in transferSerials2Parallels (via hr_gint_to_hR); with rank 3 absent, ranks 0-2 blocked in the Alltoall while rank 3 raced ahead into save_mat's Allreduce, deadlocking the run. SCF never hangs here because every rank calls cal_gint_vl. Re-enter cal_gint_vl on all ranks to keep the collective matched, and only avoid the original matrix::operator() ic < nc assertion by passing nullptr (never dereferenced, since such a rank owns no local biggrid) instead of &v(ispin, 0) for the Hartree and XC terms. The local term passes v_local directly (already a plain pointer, null on empty ranks) and needed no change, so it is restored to the unconditional call. Verified: examples/17_relax/04_relax_with_output_lcao with out_mat_h_vh=1 under mpirun -np 4 now completes 5 relax steps in ~2s (no assertion, no deadlock; previously it hung). --- source/source_io/module_hs/hterm_writer.cpp | 39 +++++++++++---------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/source/source_io/module_hs/hterm_writer.cpp b/source/source_io/module_hs/hterm_writer.cpp index 0699544e642..6f2b3473e0e 100644 --- a/source/source_io/module_hs/hterm_writer.cpp +++ b/source/source_io/module_hs/hterm_writer.cpp @@ -260,12 +260,11 @@ void write_h_vl(WriteHParams& params) spec.term_name = "local"; spec.build = [&](hamilt::HContainer& hR_tmp, int /*ispin*/) { setup_veff_hcontainer(hR_tmp, ucell, gd, pv, orb_cutoff); - // A grid-parallel rank may own no real-space grid points (nrxx == 0), - // leaving v_local null; there is no local potential to integrate there. - if (v_local != nullptr) - { - ModuleGint::cal_gint_vl(v_local, &hR_tmp); - } + // All ranks must enter cal_gint_vl so the Alltoall inside + // hr_gint_to_hR stays matched. A grid-parallel rank with no local + // real-space grid points (nrxx == 0) holds v_local == nullptr; + // cal_gint_vl then has no local biggrid and never dereferences it. + ModuleGint::cal_gint_vl(v_local, &hR_tmp); }; write_h_term(params, spec); } @@ -291,13 +290,14 @@ void write_h_vh(WriteHParams& params) spec.term_name = "hartree"; spec.build = [&](hamilt::HContainer& hR_tmp, int ispin) { setup_veff_hcontainer(hR_tmp, ucell, gd, pv, orb_cutoff); - // Under grid-parallel distribution a rank may own no real-space grid - // points (nrxx == 0), in which case v_h is an empty matrix and there - // is no local potential to integrate; skip the gint evaluation there. - if (v_h.c != nullptr) - { - ModuleGint::cal_gint_vl(&v_h(ispin, 0), &hR_tmp); - } + // All ranks must enter cal_gint_vl so the Alltoall inside + // hr_gint_to_hR stays matched. Under grid-parallel distribution a + // rank may own no real-space grid points (nrxx == 0), leaving v_h an + // empty matrix; pass nullptr there (cal_gint_vl then has no local + // biggrid and never dereferences it) instead of &v_h(ispin, 0), whose + // matrix::operator() would trip the ic < nc assertion. + const double* vh_ptr = (v_h.c != nullptr) ? &v_h(ispin, 0) : nullptr; + ModuleGint::cal_gint_vl(vh_ptr, &hR_tmp); }; write_h_term(params, spec); } @@ -334,12 +334,13 @@ void write_h_vxc(WriteHParams& params) spec.term_name = "xc"; spec.build = [&](hamilt::HContainer& hR_tmp, int ispin) { setup_veff_hcontainer(hR_tmp, ucell, gd, pv, orb_cutoff); - // A grid-parallel rank may own no real-space grid points (nrxx == 0), - // leaving v_xc an empty matrix; skip the gint evaluation there. - if (v_xc.c != nullptr) - { - ModuleGint::cal_gint_vl(&v_xc(ispin, 0), &hR_tmp); - } + // All ranks must enter cal_gint_vl so the Alltoall inside + // hr_gint_to_hR stays matched. A grid-parallel rank with no local + // real-space grid points (nrxx == 0) holds an empty v_xc matrix; pass + // nullptr there (never dereferenced) instead of &v_xc(ispin, 0), + // whose matrix::operator() would trip the ic < nc assertion. + const double* vxc_ptr = (v_xc.c != nullptr) ? &v_xc(ispin, 0) : nullptr; + ModuleGint::cal_gint_vl(vxc_ptr, &hR_tmp); }; write_h_term(params, spec); } From 3ed18db36b4385d96d98ef55807dc7e00b17286b Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 23 Oct 2026 19:52:15 +0800 Subject: [PATCH 54/61] fix(lcao): drop doubled 'k' in potential matrix filenames filename_output only omitted the extra 'k' k-point prefix for hk, sk and tk. The potential terms written by hterm_writer use k-suffixed property names (vhk, vlk, vnlk, vxck, vexxk) that already denote k-space quantities, so they fell into the branch that prepends another 'k', yielding doubled names like vhkk1s1_nao.txt. The INPUT help for out_mat_h_vexx documents the intended pattern as vexxk*_nao.txt (property directly followed by the k-point index), so generalize the special case: any property whose name ends in 'k' takes the bare index, everything else keeps the 'k' prefix. wf, vxc and other non-k properties are unchanged. Verified: examples/17_relax/04_relax_with_output_lcao with out_mat_h_t/vnl/vl/vh/vxc enabled under mpirun -np 4 now writes vhk1s1_nao.txt etc. (no doubled k), and ctest -R MODULE_BASE_filename passes. --- source/source_base/module_out/filename.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/source_base/module_out/filename.cpp b/source/source_base/module_out/filename.cpp index c7fa58fb9eb..17b9f4c3167 100644 --- a/source/source_base/module_out/filename.cpp +++ b/source/source_base/module_out/filename.cpp @@ -86,7 +86,11 @@ std::string filename_output( else { // mohan add 20250921 - if(property=="hk" || property=="sk" || property=="tk") + // A property whose name already ends in 'k' denotes a k-space + // quantity (hk, sk, tk, vhk, vlk, vnlk, vxck, vexxk, ...); the + // k-point index then needs no extra 'k' prefix, otherwise the + // filename would contain a doubled 'k' (e.g. vhkk1). + if (!property.empty() && property.back() == 'k') { kpoint_block = std::to_string(ik0+1); } From a6c3a17821863ca48513601f85407fec25f27e62 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 23 Oct 2026 20:46:52 +0800 Subject: [PATCH 55/61] fix(md): restore MD restart from mode-1 cumulative STRU file find_md_restart_stru only recognized the per-step "STRU{istep+1}" and legacy "STRU_MD_{istep}" names, so a default MD restart could not find a checkpoint written in out_app_flag == 1 mode, where run_md appends one "# MD step N" frame per output step to a single fixed-name STRU file. Add stru_frame_io::extract_last_frame() in source_cell to split the last frame of a cumulative STRU file into a single-frame side file, and let find_md_restart_stru fall back to that file so the restart readers see a plain single-frame structure. The side file STRU_restart_frame is rewritten on every restart to stay current with the latest frame. Cover the frame extraction with a dedicated source_cell unit test (MODULE_CELL_stru_frame_io) and extend the MD-restart routing case in MODULE_IO_system_variable_test for the cumulative-STRU layout. Wire the new source through the cell OBJECT library and Makefile.Objects. Not yet compiled or run; build and test verification pending. --- source/Makefile.Objects | 1 + source/source_cell/CMakeLists.txt | 1 + source/source_cell/stru_frame_io.cpp | 59 ++++++++++++ source/source_cell/stru_frame_io.h | 31 +++++++ source/source_cell/test/CMakeLists.txt | 9 ++ .../source_cell/test/test_stru_frame_io.cpp | 89 +++++++++++++++++++ .../module_parameter/read_set_globalv.cpp | 22 ++++- source/source_io/test_serial/CMakeLists.txt | 1 + .../source_io/test_serial/io_sys_var_test.cpp | 26 ++++++ 9 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 source/source_cell/stru_frame_io.cpp create mode 100644 source/source_cell/stru_frame_io.h create mode 100644 source/source_cell/test/test_stru_frame_io.cpp diff --git a/source/Makefile.Objects b/source/Makefile.Objects index f393a5eb316..8c3a9f1bf74 100644 --- a/source/Makefile.Objects +++ b/source/Makefile.Objects @@ -234,6 +234,7 @@ OBJS_CELL=atom_pseudo.o\ update_cell.o\ bcast_cell.o\ read_stru.o\ + stru_frame_io.o\ read_atom_species.o\ sep.o\ sep_cell.o\ diff --git a/source/source_cell/CMakeLists.txt b/source/source_cell/CMakeLists.txt index e35cb1025dc..2ec5c1541a2 100644 --- a/source/source_cell/CMakeLists.txt +++ b/source/source_cell/CMakeLists.txt @@ -38,6 +38,7 @@ add_library( magnetism.cpp bcast_cell.cpp read_stru.cpp + stru_frame_io.cpp print_cell.cpp read_atom_species.cpp sep.cpp diff --git a/source/source_cell/stru_frame_io.cpp b/source/source_cell/stru_frame_io.cpp new file mode 100644 index 00000000000..250175cc96d --- /dev/null +++ b/source/source_cell/stru_frame_io.cpp @@ -0,0 +1,59 @@ +/** + * @file stru_frame_io.cpp + * @brief Implementation of the cumulative STRU frame extraction helpers. + */ +#include "stru_frame_io.h" + +#include +#include + +namespace stru_frame_io +{ +bool extract_last_frame(const std::string& cumulative_path, const std::string& frame_path) +{ + std::ifstream ifs(cumulative_path.c_str()); + if (!ifs) + { + return false; + } + const std::string frame_marker = "# MD step "; + std::string line; + std::string last_frame; + std::string current_frame; + bool in_frame = false; + while (std::getline(ifs, line)) + { + const bool is_marker = line.compare(0, frame_marker.size(), frame_marker) == 0; + if (is_marker) + { + if (in_frame) + { + last_frame = current_frame; + } + current_frame.clear(); + in_frame = true; + continue; + } + if (in_frame) + { + current_frame += line; + current_frame += '\n'; + } + } + if (in_frame) + { + last_frame = current_frame; + } + if (last_frame.empty()) + { + return false; + } + std::ofstream ofs(frame_path.c_str(), std::ios::out | std::ios::trunc); + if (!ofs) + { + return false; + } + ofs << last_frame; + return ofs.good(); +} +} // namespace stru_frame_io diff --git a/source/source_cell/stru_frame_io.h b/source/source_cell/stru_frame_io.h new file mode 100644 index 00000000000..8f2456d7683 --- /dev/null +++ b/source/source_cell/stru_frame_io.h @@ -0,0 +1,31 @@ +#ifndef STRU_FRAME_IO_H +#define STRU_FRAME_IO_H + +#include + +/** + * @file stru_frame_io.h + * @brief Helpers to read frames from fixed-name cumulative STRU files. + * + * In out_app_flag == 1 mode run_md appends one structure frame per output + * step to a single "STRU" file, each frame introduced by a one-based + * "# MD step N" comment line. Restart readers such as MDCellReader::read_stru + * expect a single-frame STRU file, so the frames written this way must be + * split before they can be consumed. This header owns that frame-splitting + * knowledge so callers outside source_cell do not parse STRU text themselves. + */ +namespace stru_frame_io +{ +/// @brief Extract the last MD frame from a fixed-name cumulative STRU file. +/// @param cumulative_path path of the mode-1 "STRU" file holding one frame per +/// output step, each introduced by a "# MD step N" comment line +/// @param frame_path path of the single-frame file to (over)write with the +/// last frame; the caller passes this path to the structure readers +/// @return true when at least one frame was found and written +/// @details The last frame is copied verbatim, without its "# MD step N" +/// comment line, into frame_path. The side file is rewritten on every call so +/// repeated restarts always see the most recent frame. +bool extract_last_frame(const std::string& cumulative_path, const std::string& frame_path); +} // namespace stru_frame_io + +#endif // STRU_FRAME_IO_H diff --git a/source/source_cell/test/CMakeLists.txt b/source/source_cell/test/CMakeLists.txt index 2f554913891..de399db58fc 100644 --- a/source/source_cell/test/CMakeLists.txt +++ b/source/source_cell/test/CMakeLists.txt @@ -72,6 +72,15 @@ addtest( test_cell_geometry.cpp ../cell_geometry.cpp) +addtest( + TARGET + MODULE_CELL_stru_frame_io + LIBS + base + SOURCES + test_stru_frame_io.cpp + ../stru_frame_io.cpp) + if(NOT ENABLE_MPI) return() endif() diff --git a/source/source_cell/test/test_stru_frame_io.cpp b/source/source_cell/test/test_stru_frame_io.cpp new file mode 100644 index 00000000000..e674834775e --- /dev/null +++ b/source/source_cell/test/test_stru_frame_io.cpp @@ -0,0 +1,89 @@ +/** + * @file test_stru_frame_io.cpp + * @brief Unit test for stru_frame_io: last-frame extraction from a fixed-name + * cumulative STRU file written in out_app_flag == 1 mode. + */ +#include "source_cell/stru_frame_io.h" + +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "source_base/fs_compat.h" + +class StruFrameIoTest : public testing::Test +{ + protected: + std::string case_dir; + + void SetUp() override + { + case_dir = testing::TempDir() + "/abacus_stru_frame_io/"; + ModuleBase::make_directory(case_dir); + } + + std::string read_all(const std::string& path) + { + std::ifstream ifs(path.c_str()); + std::stringstream content; + content << ifs.rdbuf(); + return content.str(); + } +}; + +TEST_F(StruFrameIoTest, ExtractLastFramePicksNewest) +{ + const std::string cumulative = case_dir + "STRU"; + std::ofstream ofs(cumulative.c_str()); + ofs << "# MD step 1\n\nATOMIC_SPECIES\nSi 28.0855\n"; + ofs << "# MD step 2\n\nATOMIC_SPECIES\nSi 28.0855\n\nLATTICE_CONSTANT\n10.2\n"; + ofs.close(); + + const std::string frame = case_dir + "STRU_restart_frame"; + EXPECT_TRUE(stru_frame_io::extract_last_frame(cumulative, frame)); + + const std::string content = read_all(frame); + // the extracted file must hold only the last frame, not the earlier one + EXPECT_EQ(content.find("# MD step"), std::string::npos); + EXPECT_NE(content.find("LATTICE_CONSTANT"), std::string::npos); +} + +TEST_F(StruFrameIoTest, ExtractLastFrameRewritesOnSecondCall) +{ + const std::string cumulative = case_dir + "STRU"; + std::ofstream ofs(cumulative.c_str()); + ofs << "# MD step 1\n\nATOMIC_SPECIES\nSi 28.0855\n"; + ofs.close(); + + const std::string frame = case_dir + "STRU_restart_frame"; + EXPECT_TRUE(stru_frame_io::extract_last_frame(cumulative, frame)); + + // simulate a later restart: append another frame and re-extract + std::ofstream app(cumulative.c_str(), std::ios::app); + app << "# MD step 2\n\nLATTICE_CONSTANT\n10.9\n"; + app.close(); + EXPECT_TRUE(stru_frame_io::extract_last_frame(cumulative, frame)); + + const std::string content = read_all(frame); + EXPECT_NE(content.find("10.9"), std::string::npos); +} + +TEST_F(StruFrameIoTest, ExtractLastFrameFailsOnMissingFile) +{ + const std::string missing = case_dir + "NO_SUCH_STRU"; + const std::string frame = case_dir + "STRU_restart_frame"; + EXPECT_FALSE(stru_frame_io::extract_last_frame(missing, frame)); +} + +TEST_F(StruFrameIoTest, ExtractLastFrameFailsWithoutMarker) +{ + const std::string cumulative = case_dir + "STRU"; + std::ofstream ofs(cumulative.c_str()); + ofs << "ATOMIC_SPECIES\nSi 28.0855\n"; + ofs.close(); + + const std::string frame = case_dir + "STRU_restart_frame"; + EXPECT_FALSE(stru_frame_io::extract_last_frame(cumulative, frame)); +} diff --git a/source/source_io/module_parameter/read_set_globalv.cpp b/source/source_io/module_parameter/read_set_globalv.cpp index f7e85586ddb..ed503fcd841 100644 --- a/source/source_io/module_parameter/read_set_globalv.cpp +++ b/source/source_io/module_parameter/read_set_globalv.cpp @@ -2,18 +2,24 @@ #include "read_input_tool.h" #include "source_base/global_variable.h" #include "source_base/tool_quit.h" +#include "source_cell/stru_frame_io.h" #include "source_io/module_parameter/parameter.h" +#include + namespace { /// @brief Find the structure file for restarting a molecular dynamics run. /// @param stru_dir directory where the previous run wrote its structure files /// @param istep number of completed ionic steps recorded in Restart_md.txt /// @return path of the first existing structure file, empty if none exists -/// @details Two naming schemes are accepted, in order: +/// @details Three naming schemes are accepted, in order: /// - "STRU{istep + 1}": written by run_md since the ionic-step numbering /// was unified with relax (1-based); -/// - "STRU_MD_{istep}": written by older ABACUS versions. +/// - "STRU_MD_{istep}": written by older ABACUS versions; +/// - "STRU": fixed-name cumulative file written in out_app_flag == 1 mode; +/// the last frame is extracted to "STRU_restart_frame" and that path is +/// returned so the restart readers see a single-frame file. std::string find_md_restart_stru(const std::string& stru_dir, const int istep) { const std::string stru_new = stru_dir + "STRU" + std::to_string(istep + 1); @@ -28,6 +34,16 @@ std::string find_md_restart_stru(const std::string& stru_dir, const int istep) { return stru_old; } + const std::string stru_cumulative = stru_dir + "STRU"; + std::ifstream file_cumulative(stru_cumulative.c_str()); + if (file_cumulative.good()) + { + const std::string frame_path = stru_dir + "STRU_restart_frame"; + if (stru_frame_io::extract_last_frame(stru_cumulative, frame_path)) + { + return frame_path; + } + } return ""; } } // namespace @@ -191,7 +207,7 @@ void ReadInput::set_global_dir(const Input_para& inp, System_para& sys) if (sys.global_in_stru.empty()) { const std::string message = "no STRU file found for MD restart in " + sys.global_readin_dir - + "; expected STRU_MD_{step} or STRU{step+1}"; + + "; expected STRU_MD_{step}, STRU{step+1} or cumulative STRU"; ModuleBase::WARNING_QUIT("ReadInput::set_global_dir", message); } } diff --git a/source/source_io/test_serial/CMakeLists.txt b/source/source_io/test_serial/CMakeLists.txt index 0201fb14e01..9a30acbfb82 100644 --- a/source/source_io/test_serial/CMakeLists.txt +++ b/source/source_io/test_serial/CMakeLists.txt @@ -65,4 +65,5 @@ AddTest( TARGET MODULE_IO_system_variable_test LIBS parameter base device io_input_serial SOURCES io_sys_var_test.cpp + ../../source_cell/stru_frame_io.cpp ) diff --git a/source/source_io/test_serial/io_sys_var_test.cpp b/source/source_io/test_serial/io_sys_var_test.cpp index 48ab0ec0a8e..c8c9351498c 100644 --- a/source/source_io/test_serial/io_sys_var_test.cpp +++ b/source/source_io/test_serial/io_sys_var_test.cpp @@ -158,6 +158,32 @@ TEST_F(InputTest, SetGlobalDirMdRestart) TestParameters::set_global_dir(readinput, input, sys); EXPECT_EQ(sys.global_in_stru, stru_new); + // Restart from a read_file_dir holding only the fixed-name cumulative + // "STRU" written in out_app_flag == 1 mode: the last "# MD step N" frame + // must be extracted to a single-frame file and picked up. + const std::string cumulative_dir = case_dir + "cumulative/"; + ModuleBase::make_directory(cumulative_dir); + const std::string restart_txt_cum = cumulative_dir + "Restart_md.txt"; + std::ofstream restart_file_cum(restart_txt_cum.c_str()); + restart_file_cum << 2 << std::endl; + restart_file_cum << 0.03 << std::endl; + restart_file_cum.close(); + const std::string stru_cumulative = cumulative_dir + "STRU"; + std::ofstream stru_file_cum(stru_cumulative.c_str()); + stru_file_cum << "# MD step 1\n\nATOMIC_SPECIES\nSi 28.0855\n" << std::endl; + stru_file_cum << "# MD step 2\n\nATOMIC_SPECIES\nSi 28.0855\n\nLATTICE_CONSTANT\n10.2\n" << std::endl; + stru_file_cum.close(); + + input.read_file_dir = cumulative_dir; + TestParameters::set_global_dir(readinput, input, sys); + // the router picks the extracted single-frame side file next to STRU; the + // frame contents themselves are covered by the stru_frame_io unit test + const std::string stru_frame = cumulative_dir + "STRU_restart_frame"; + EXPECT_EQ(sys.global_in_stru, stru_frame); + std::ifstream frame_ifs(stru_frame.c_str()); + EXPECT_TRUE(frame_ifs.good()); + frame_ifs.close(); + // restore the shared input state so other tests are not affected input.calculation = "scf"; input.mdp.md_restart = false; From de91422708c579dfb6ebb327d4fe8cf837fe26e2 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 23 Oct 2026 21:03:01 +0800 Subject: [PATCH 56/61] fix(tddft): reject cumulative WFC file on positive restart step The mode-1 fixed-name cumulative file was appended to the read candidates unconditionally. Since that file is always read from its first frame, a positive restart index (istep >= 0) would silently load step-0 wave functions whenever the step-suffixed file was missing, corrupting the TDDFT restart without any error. Restrict the cumulative-file candidate to istep < 0 (first read) so a positive restart that cannot find its step-suffixed snapshot now fails loudly through the existing error path instead of reading the wrong frame. Add a unit test (ReadWfcNaoTest.PositiveStepRejectsCumulativeFile) that writes only the cumulative file and asserts that a positive-step read fails. Not yet compiled or run; build and test verification pending. --- source/source_io/module_wf/read_wfc_nao.cpp | 31 +++++---- source/source_io/test/read_wfc_nao_test.cpp | 75 +++++++++++++++++++++ 2 files changed, 93 insertions(+), 13 deletions(-) diff --git a/source/source_io/module_wf/read_wfc_nao.cpp b/source/source_io/module_wf/read_wfc_nao.cpp index d0fe553bd32..310a9e7b3d0 100644 --- a/source/source_io/module_wf/read_wfc_nao.cpp +++ b/source/source_io/module_wf/read_wfc_nao.cpp @@ -435,7 +435,9 @@ bool ModuleIO::read_wfc_nao( // 3. readin_dir + "WFC/" + step-suffixed name: layout written by // releases before the out_app_flag rework // 4. readin_dir + name without the step suffix: written in append - // mode (out_app_flag 1) + // mode (out_app_flag 1); only valid for istep < 0, because the + // cumulative file is always read from its first frame and would + // silently return step-0 data for a positive restart index std::vector candidates; if (istep >= 0) { @@ -476,18 +478,21 @@ bool ModuleIO::read_wfc_nao( gamma_only, istep)); } - const int append_app_flag = 1; - candidates.push_back(ModuleIO::filename_output(global_readin_dir, - "wf", - "nao", - ik, - ik2iktot, - nspin, - nkstot, - read_type, - append_app_flag, - gamma_only, - istep)); + else + { + const int append_app_flag = 1; + candidates.push_back(ModuleIO::filename_output(global_readin_dir, + "wf", + "nao", + ik, + ik2iktot, + nspin, + nkstot, + read_type, + append_app_flag, + gamma_only, + istep)); + } for (size_t ic = 0; ic < candidates.size(); ++ic) { diff --git a/source/source_io/test/read_wfc_nao_test.cpp b/source/source_io/test/read_wfc_nao_test.cpp index 8c0b9bbb18a..5e730352baf 100644 --- a/source/source_io/test/read_wfc_nao_test.cpp +++ b/source/source_io/test/read_wfc_nao_test.cpp @@ -729,6 +729,81 @@ TEST_F(ReadWfcNaoTest, CompleteGlobalKPointPopulations) } } +TEST_F(ReadWfcNaoTest, PositiveStepRejectsCumulativeFile) +{ + // A positive restart step must not fall back to the fixed-name cumulative + // file (out_app_flag == 1): that file is always read from its first frame, + // so accepting it would silently return step-0 data for a later step. Only + // the step-suffixed candidates are valid for a positive istep, so reading + // a directory that holds ONLY the cumulative file must fail. + const int nbands = 2; + const int nlocal = 3; + Parallel_Orbitals para; + initialize_parallel_orbitals(para, nlocal, nbands); + + const std::vector coefficients = {0.1, 0.2, 0.3, 1.1, 1.2, 1.3}; + ModuleBase::matrix ekb_source(1, nbands); + ModuleBase::matrix wg_source(1, nbands); + ekb_source(0, 0) = -0.5; + ekb_source(0, 1) = 0.7; + wg_source(0, 0) = 2.0; + wg_source(0, 1) = 0.0; + + const std::vector ik2iktot = {0}; + const int read_type = 2; + const int append_app_flag = 1; // fixed name without the step suffix + const bool gamma_only_for_name = true; + const std::string cumulative = ModuleIO::filename_output(binary_test_dir, + "wf", + "nao", + 0, + ik2iktot, + 1, + 1, + read_type, + append_app_flag, + gamma_only_for_name, + -1); + if (my_rank == 0) + { + ModuleIO::wfc_nao_write2file(cumulative, + coefficients.data(), + nlocal, + 0, + ekb_source, + wg_source, + true, + false, + ""); + } +#ifdef __MPI + MPI_Barrier(MPI_COMM_WORLD); +#endif + + psi::Psi psi_read; + ModuleBase::matrix ekb(1, nbands); + ModuleBase::matrix wg(1, nbands); + const int restart_istep = 5; // positive restart step: no step-suffixed file exists + EXPECT_FALSE(ModuleIO::read_wfc_nao(binary_test_dir, + para, + psi_read, + ekb, + wg, + ik2iktot, + 1, + 1, + true, + 0, + restart_istep)); + if (my_rank == 0) + { + std::remove(cumulative.c_str()); + } +#ifdef __MPI + MPI_Barrier(MPI_COMM_WORLD); +#endif +} + #ifdef __MPI int main(int argc, char** argv) { From 7076eafb9c60a163dfad2c31cd6a2298bfebf076 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 23 Oct 2026 21:11:49 +0800 Subject: [PATCH 57/61] build(cell): link device lib into MODULE_CELL_stru_frame_io test The new stru_frame_io unit test failed to link: the base library pulls in base_device::memory::* symbols that live in the device target. Add device to the test's link libraries, matching the sibling cell_geometry test. --- source/source_cell/test/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/source/source_cell/test/CMakeLists.txt b/source/source_cell/test/CMakeLists.txt index de399db58fc..0db120faeab 100644 --- a/source/source_cell/test/CMakeLists.txt +++ b/source/source_cell/test/CMakeLists.txt @@ -77,6 +77,7 @@ addtest( MODULE_CELL_stru_frame_io LIBS base + device SOURCES test_stru_frame_io.cpp ../stru_frame_io.cpp) From 5c766991113d1232dfb5680406718f8914cd7ace Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 23 Oct 2026 22:35:42 +0800 Subject: [PATCH 58/61] build(io): link stru_frame_io into io_input test targets P1-a made read_set_globalv.cpp call stru_frame_io::extract_last_frame, which lives in the cell library. Test targets that compile read_set_globalv.cpp via the io_input / io_input_serial OBJECT libraries but do not link cell then fail to link with an undefined reference. Add stru_frame_io.cpp to io_input_serial (covers all its consumers) and to the three io_input-based tests that do not link cell. The main io_input library is left untouched: the abacus binary already links cell, so adding the source there would duplicate the symbol. --- source/source_io/test/CMakeLists.txt | 6 ++++++ source/source_io/test_serial/CMakeLists.txt | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/source/source_io/test/CMakeLists.txt b/source/source_io/test/CMakeLists.txt index 6cfbecf5089..d7dd1f1ef37 100644 --- a/source/source_io/test/CMakeLists.txt +++ b/source/source_io/test/CMakeLists.txt @@ -34,6 +34,10 @@ AddTest( TARGET MODULE_IO_input_test_para LIBS parameter base device io_input SOURCES read_input_ptest.cpp + # io_input (via read_set_globalv.cpp) references + # stru_frame_io::extract_last_frame, but this test does not link + # the cell library, so provide the translation unit directly. + ../../source_cell/stru_frame_io.cpp ) add_test(NAME MODULE_IO_input_test_para_4 @@ -110,12 +114,14 @@ AddTest( TARGET MODULE_IO_parse_args LIBS parameter base device io_input SOURCES parse_args_test.cpp ../parse_args.cpp ../input_help.cpp + ../../source_cell/stru_frame_io.cpp ) AddTest( TARGET MODULE_IO_input_help_test LIBS parameter base device io_input SOURCES input_help_test.cpp ../input_help.cpp + ../../source_cell/stru_frame_io.cpp ) AddTest( diff --git a/source/source_io/test_serial/CMakeLists.txt b/source/source_io/test_serial/CMakeLists.txt index 9a30acbfb82..028f460aa3f 100644 --- a/source/source_io/test_serial/CMakeLists.txt +++ b/source/source_io/test_serial/CMakeLists.txt @@ -26,6 +26,9 @@ add_library( ../module_parameter/read_set_globalv.cpp ../module_parameter/availability.cpp ../module_parameter/availability_validator.cpp + # read_set_globalv.cpp references stru_frame_io::extract_last_frame, so + # every target linking io_input_serial needs this translation unit. + ../../source_cell/stru_frame_io.cpp ) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/support DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) @@ -65,5 +68,4 @@ AddTest( TARGET MODULE_IO_system_variable_test LIBS parameter base device io_input_serial SOURCES io_sys_var_test.cpp - ../../source_cell/stru_frame_io.cpp ) From e146c47ff387e467a6451aadd7d4e7c4b5abee4b Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 23 Oct 2026 22:35:56 +0800 Subject: [PATCH 59/61] fix(output): truncate fixed-name files on first step of a fresh run In append mode (out_app_flag == 1) the cumulative PDOS, DM(k) and MD-STRU files used "istep >= 0" / unconditional append, so rerunning in an existing OUT directory kept stale records from the previous run mixed with the new data. Now the first output step of a fresh (non-restart) run truncates the fixed-name file; later steps and restarted runs keep appending. - write_pdos_text.cpp / write_dmk.cpp: append only when istep > 0 - run_md.cpp: do not append on the very first step of a fresh run (step_ == 0 && step_rst_ == 0) - read_inp_out.cpp: document the mode-1 first-step-truncation contract in the out_app_flag help text --- source/source_io/module_dm/write_dmk.cpp | 6 ++++-- source/source_io/module_dos/write_pdos_text.cpp | 4 +++- source/source_io/module_parameter/read_inp_out.cpp | 1 + source/source_md/run_md.cpp | 7 ++++++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/source/source_io/module_dm/write_dmk.cpp b/source/source_io/module_dm/write_dmk.cpp index 9a162a8ba78..287ebd9c893 100644 --- a/source/source_io/module_dm/write_dmk.cpp +++ b/source/source_io/module_dm/write_dmk.cpp @@ -200,8 +200,10 @@ void ModuleIO::write_dmk(const std::vector>& dmk, const int istep_file = (out_app_flag == 1) ? -1 : istep; // Each multi-step run (MD or relax) appends a new record to the fixed // file, mirroring the open_sparse_file convention; single-point SCF - // passes istep = -1 and starts a fresh file. - const bool append_on_restart = (out_app_flag == 1) && (istep >= 0); + // passes istep = -1 and starts a fresh file. The first written step + // (istep == 0) truncates so a fresh run drops records left by a previous + // run; only later steps (istep > 0) append. + const bool append_on_restart = (out_app_flag == 1) && (istep > 0); int my_rank = 0; #ifdef __MPI diff --git a/source/source_io/module_dos/write_pdos_text.cpp b/source/source_io/module_dos/write_pdos_text.cpp index 84cfab41c85..a61d6f191ea 100644 --- a/source/source_io/module_dos/write_pdos_text.cpp +++ b/source/source_io/module_dos/write_pdos_text.cpp @@ -27,8 +27,10 @@ void ModuleIO::write_pdos_text( // In append mode (out_app_flag == 1) reuse the fixed filename and append // each ionic step; single-point SCF passes istep = -1 and truncates. + // The first written step (istep == 0) truncates so a fresh run does not + // keep records from a previous run; only later steps (istep > 0) append. const int istep_file = (out_app_flag == 1) ? -1 : istep; - const bool append_step = (out_app_flag == 1) && (istep >= 0); + const bool append_step = (out_app_flag == 1) && (istep > 0); for (int is = 0; is < nspin0; ++is) { diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index 8c77c3a341b..ae7f8f0aced 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -1527,6 +1527,7 @@ The circle order of the charge density on real space grids is: x is the outer lo item.description = R"(Controls output file organization during multi-step calculations (MD, relax, RT-TDDFT). The directory routing applies to both LCAO and PW basis calculations. * 0: Write to OUT directory directly, no append (each step's file carries a g{step} suffix) * 1: Reuse fixed filenames across steps (default, backward compatible): text outputs append new records, cube outputs accumulate one frame per written step + In mode 1 the first output step of a fresh (non-restart) run truncates the fixed-name file so a rerun does not keep stale records from a previous run; later steps and restarted runs keep appending. * 2: Write to subdirectory (STRUC/ for structure files, PSI/, CHG/, POT/, ELF/, DOS/, DIP/, MAT/), files carry a g{step} suffix The legacy Boolean spellings (true/false, yes/no, on/off, t/f, y/n, and the dotted forms) are still accepted and map to modes 1/0 as before. For more information, refer to the documentation of out_mat_r, out_hsr, out_mat_t, out_mat_dh, out_hsk, out_wfc_lcao, out_chg, out_pot, out_elf, out_dos, out_dipole, out_stru.)"; diff --git a/source/source_md/run_md.cpp b/source/source_md/run_md.cpp index 3315cdbea3d..898d2b12f96 100644 --- a/source/source_md/run_md.cpp +++ b/source/source_md/run_md.cpp @@ -323,7 +323,12 @@ void md_line(MDCell& mdcell, // out_app_flag == 1: single fixed-name STRU accumulates one // frame per step with a one-based "# MD step N" comment; // modes 0/2 keep the numbered STRU{stru_step} files. - const bool append_stru = append_stru_allowed && !has_legacy_stru_dir; + // The very first step of a fresh (non-restart) run truncates + // the cumulative file so it does not keep frames from a + // previous run; later steps and restarted runs append. + const bool is_first_step_of_fresh_run = (mdrun->step_ == 0) && (mdrun->step_rst_ == 0); + const bool append_stru = append_stru_allowed && !has_legacy_stru_dir + && !is_first_step_of_fresh_run; const std::string frame_comment = append_stru ? "# MD step " + std::to_string(stru_step) + "\n\n" : ""; From 1ce77a9a8bb6ff0fecac0b1837c73ace76a2cf6c Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 23 Oct 2026 23:18:26 +0800 Subject: [PATCH 60/61] fix(output): decouple PW wfc append decision from the filename sentinel write_wfc_pw used a single istep both to decide whether the ionic step appears in the file name and whether to append a new frame. When out_freq_ion == 0 the caller passes istep = -1 (fixed filename), which made append_frame always false, so every converged ionic step truncated the file instead of accumulating one frame per step as documented. Add an append_step parameter carrying the real output step: append_frame now depends on append_step while istep remains only the filename sentinel. The caller passes the real ionic step; single-point writes pass -1. Add a WriteWfcAppendMode test that truncates on the first step, appends on the second (file grows), and truncates again on a fresh run (size restored). Verified: single-process and mpirun -np 4 both pass (2/2). --- .../source_io/module_ctrl/ctrl_output_pw.cpp | 5 + source/source_io/module_wf/write_wfc_pw.cpp | 8 +- source/source_io/module_wf/write_wfc_pw.h | 5 + source/source_io/test/read_wf2rho_pw_test.cpp | 131 ++++++++++++++++++ 4 files changed, 147 insertions(+), 2 deletions(-) diff --git a/source/source_io/module_ctrl/ctrl_output_pw.cpp b/source/source_io/module_ctrl/ctrl_output_pw.cpp index 7b8baff6f86..7b6206e0bd8 100644 --- a/source/source_io/module_ctrl/ctrl_output_pw.cpp +++ b/source/source_io/module_ctrl/ctrl_output_pw.cpp @@ -62,8 +62,13 @@ void ModuleIO::ctrl_iter_pw(const int istep, if (out_wfc_flag && (inp.out_wfc_pw == 1 || inp.out_wfc_pw == 2)) { stp.sync_cpu(); + // istep_in is only the filename sentinel (-1 omits the step suffix); + // pass the real ionic step separately so append mode keeps + // accumulating frames even when the filename carries no step. + const int append_step = istep; ModuleIO::write_wfc_pw(istep_in, iter_in, + append_step, GlobalV::KPAR, GlobalV::MY_POOL, GlobalV::MY_RANK, diff --git a/source/source_io/module_wf/write_wfc_pw.cpp b/source/source_io/module_wf/write_wfc_pw.cpp index f7433de42ef..50cfcb117a7 100644 --- a/source/source_io/module_wf/write_wfc_pw.cpp +++ b/source/source_io/module_wf/write_wfc_pw.cpp @@ -14,6 +14,7 @@ void ModuleIO::write_wfc_pw( const int istep, const int iter, + const int append_step, const int kpar, const int my_pool, const int my_rank, @@ -68,8 +69,11 @@ void ModuleIO::write_wfc_pw( // Mirrors write_wfc_nao's append_flag: the first step of a // run truncates the file, later steps append a new frame // when out_app_flag == 1, so the file accumulates one frame - // per ionic step instead of being rewritten each step. - const bool append_frame = (istep > 0 && out_app_flag == 1); + // per ionic step instead of being rewritten each step. The + // decision uses append_step (the real output step), not istep + // (the filename sentinel), so an out_freq_ion == 0 run that + // writes a fixed-name file still appends after its first step. + const bool append_frame = (append_step > 0 && out_app_flag == 1); if (!append_frame) { if (out_wfc_pw == 1) diff --git a/source/source_io/module_wf/write_wfc_pw.h b/source/source_io/module_wf/write_wfc_pw.h index 6877179eed7..fbe6c387416 100644 --- a/source/source_io/module_wf/write_wfc_pw.h +++ b/source/source_io/module_wf/write_wfc_pw.h @@ -7,9 +7,14 @@ namespace ModuleIO { +// istep is the filename sentinel: -1 keeps the ionic step out of the file +// name. append_step is the real output step that decides whether the fixed +// file is appended (a new frame per step) or truncated; pass -1 for a +// single-point write that always truncates. void write_wfc_pw( const int istep, const int iter, + const int append_step, const int kpar, const int my_pool, const int my_rank, diff --git a/source/source_io/test/read_wf2rho_pw_test.cpp b/source/source_io/test/read_wf2rho_pw_test.cpp index eb43411bc61..de4dac1edc5 100644 --- a/source/source_io/test/read_wf2rho_pw_test.cpp +++ b/source/source_io/test/read_wf2rho_pw_test.cpp @@ -286,9 +286,11 @@ TEST_F(ReadWfcRhoTest, ReadWfcRho) const int istep = -1; // -1 means ionic iteration number will not appear in file name const int iter = -1; // -1 means electronic iteration number will not appear in file name + const int append_step = -1; // single-point write: truncate, do not append ModuleIO::write_wfc_pw(istep, iter, + append_step, kpar, my_pool, my_rank, @@ -367,6 +369,135 @@ TEST_F(ReadWfcRhoTest, ReadWfcRho) } } +// Size of a file in bytes; returns -1 when the file cannot be opened. +static long file_size_bytes(const std::string& path) +{ + std::ifstream ifs(path.c_str(), std::ios::binary | std::ios::ate); + if (!ifs.is_open()) + { + return -1; + } + return static_cast(ifs.tellg()); +} + +// In append mode (out_app_flag == 1) with a fixed filename (istep == -1), the +// first output step of a run must truncate the file while a later step must +// append a new frame. This is driven by append_step, independent of the +// filename sentinel istep. Before the fix, istep == -1 always truncated, so a +// second write never grew the file. +TEST_F(ReadWfcRhoTest, WriteWfcAppendMode) +{ + const int kpar = GlobalV::KPAR; + const int nspin = 1; + const int nbands = 4; + const int my_pool = GlobalV::MY_POOL; + const int my_rank = GlobalV::MY_RANK; + const int nks = 2; + const int nkstot = GlobalV::KPAR * nks; + + // K points and PW basis + kv->set_nkstot(nkstot); + kv->set_nks(nks); + kv->isk = {0, 0}; + const double shift = my_pool * 0.1; + kv->kvec_d = {ModuleBase::Vector3(shift, shift, shift), ModuleBase::Vector3(0.5 + shift, 0.5 + shift, 0.5 + shift)}; + kv->ik2iktot.resize(nks); + cal_ik2iktot(kv->ik2iktot, nks, nkstot); + +#ifdef __MPI + wfcpw->initmpi(GlobalV::NPROC_IN_POOL, GlobalV::RANK_IN_POOL, POOL_WORLD); +#endif + wfcpw->initgrids(5.3233, ModuleBase::Matrix3(-0.5, 0.0, 0.5, 0.0, 0.5, 0.5, -0.5, 0.5, 0.0), 80); + wfcpw->initparameters(false, 20, nks, kv->kvec_d.data()); + wfcpw->setuptransform(); + wfcpw->collect_local_pw(); + + kv->kvec_c.clear(); + for (int ik = 0; ik < nks; ++ik) + { + kv->kvec_c.push_back(wfcpw->kvec_c[ik]); + } + kv->ngk = {wfcpw->npwk[0], wfcpw->npwk[1]}; + kv->wk = {1.0, 1.0}; + + psi = new psi::Psi>(nks, nbands, wfcpw->npwk_max, kv->ngk, true); + std::complex* ptr = psi->get_pointer(); + for (int i = 0; i < nks * nbands * wfcpw->npwk_max; i++) + { + ptr[i] = std::complex((i + my_pool * 100) / 100.0, (i + my_pool) / 100.0); + } + + const std::string out_dir = "./"; + std::ofstream running_log("running_log_app0.txt"); + const double ecutwfc = 20; + const int npol = 1; + const int istep = -1; // filename sentinel: no step suffix + const int iter = -1; + const int out_app_flag = 1; // append mode + + auto write_once = [&](const int append_step) { + ModuleIO::write_wfc_pw(istep, + iter, + append_step, + kpar, + my_pool, + my_rank, + nbands, + nspin, + npol, + GlobalV::RANK_IN_POOL, + GlobalV::NPROC_IN_POOL, + TestParameters::input().out_wfc_pw, + out_app_flag, + ecutwfc, + out_dir, + *psi, + *kv, + wfcpw, + running_log); + }; + + write_once(0); // fresh first step: truncate +#ifdef __MPI + MPI_Barrier(MPI_COMM_WORLD); +#endif + const long size_first = file_size_bytes("wfk1_pw.dat"); + + write_once(1); // later step: append a frame +#ifdef __MPI + MPI_Barrier(MPI_COMM_WORLD); +#endif + const long size_second = file_size_bytes("wfk1_pw.dat"); + + write_once(0); // fresh run again: truncate back to a single frame +#ifdef __MPI + MPI_Barrier(MPI_COMM_WORLD); +#endif + const long size_third = file_size_bytes("wfk1_pw.dat"); + + if (GlobalV::MY_RANK == 0) + { + EXPECT_GT(size_first, 0); + EXPECT_GT(size_second, size_first); + EXPECT_EQ(size_third, size_first); + } + + running_log.close(); + delete psi; + + if (GlobalV::MY_RANK == 0) + { + remove("running_log_app0.txt"); + remove("wfk1_pw.dat"); + remove("wfk2_pw.dat"); + if (GlobalV::KPAR == 2) + { + remove("wfk3_pw.dat"); + remove("wfk4_pw.dat"); + } + } +} + int main(int argc, char** argv) { #ifdef __MPI From 720f83015cc39b16967a3ae4e18558cb68a541b3 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Fri, 23 Oct 2026 23:38:57 +0800 Subject: [PATCH 61/61] fix(input): create mode-2 subdirectories for out_hsr/out_pchg/out_wfc_norm The mode-2 (out_app_flag==2) subdirectory predicates in create_directory were missing three output classes, so enabling them alone wrote files into directories that were never created: - out_hsr (formats 1 and 2, CSR text) writes to global_mat_dir but only out_hsr_npz was in the MAT/ predicate; add out_hsr[0]==1 || ==2. - out_pchg writes to global_chg_dir but only out_chg was in the CHG/ predicate; add !out_pchg.empty(). - out_wfc_norm / out_wfc_re_im write to global_psi_dir but only out_wfc_lcao / out_wfc_pw were in the PSI/ predicate; add !empty() for both. Verified: io_input_serial compiles (make -j 30). The create_directory path cannot be exercised in the serial unit test because make_dir_out calls MPI_Barrier, so predicate coverage is deferred. --- source/source_io/module_parameter/read_input.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/source_io/module_parameter/read_input.cpp b/source/source_io/module_parameter/read_input.cpp index 457d053901a..da7f8c34ae9 100644 --- a/source/source_io/module_parameter/read_input.cpp +++ b/source/source_io/module_parameter/read_input.cpp @@ -312,8 +312,10 @@ void ReadInput::create_directory(const Parameter& param) { const std::vector> subdir_flags = { {param.input.out_stru > 0, param.globalv.global_stru_dir}, - {param.input.out_wfc_lcao > 0 || param.input.out_wfc_pw > 0, param.globalv.global_psi_dir}, - {param.input.out_chg[0] > 0, param.globalv.global_chg_dir}, + {param.input.out_wfc_lcao > 0 || param.input.out_wfc_pw > 0 + || !param.input.out_wfc_norm.empty() || !param.input.out_wfc_re_im.empty(), + param.globalv.global_psi_dir}, + {param.input.out_chg[0] > 0 || !param.input.out_pchg.empty(), param.globalv.global_chg_dir}, {param.input.out_pot[0] > 0, param.globalv.global_pot_dir}, {param.input.out_elf[0] > 0, param.globalv.global_elf_dir}, {param.input.out_dos > 0 @@ -329,7 +331,8 @@ void ReadInput::create_directory(const Parameter& param) || param.input.out_mat_vnlk[0] > 0 || param.input.out_mat_vlk[0] > 0 || param.input.out_mat_vhk[0] > 0 || param.input.out_mat_vxck[0] > 0 || param.input.out_mat_exxk[0] > 0 || param.input.out_mat_dh_t[0] > 0 || param.input.out_mat_dh_vl[0] > 0 || param.input.out_mat_dh_vnl[0] > 0 - || param.input.out_mat_dh_vh[0] > 0 || param.input.out_mat_dh_vxc[0] > 0, + || param.input.out_mat_dh_vh[0] > 0 || param.input.out_mat_dh_vxc[0] > 0 + || param.input.out_hsr[0] == 1 || param.input.out_hsr[0] == 2, param.globalv.global_mat_dir} }; for (const auto& flag_dir : subdir_flags)