From c8985300d4e9308c37808d9a7daa0de671f6ee84 Mon Sep 17 00:00:00 2001 From: Zanthoxylum <105619360+Zanthoxylum@users.noreply.github.com> Date: Thu, 29 Oct 2026 23:42:57 +0800 Subject: [PATCH 01/12] feat(dfpt): QE-format dynamical matrix file output (dfpt_out_dyn) New INPUT switch dfpt_out_dyn (Boolean, default false): when enabled, the esolver post-processing writes one QE-compatible dyn file per q point (OUT./dyn.q) containing the raw (pre-ASR, pre-LO-TO) unreduced force-constant matrix Phi = D*sqrt(m_a m_b) in Ry/bohr^2, matching the layout of Quantum ESPRESSO ph.x's fildyn output. At Gamma with dfpt_compute_q0, the dielectric tensor and Born effective charges are appended in the QE convention. Implementation: the data layer stashes an untouched raw copy alongside the working dynmat (which is mutated in place by apply_asr/add_loto at Gamma); the writer (dfpt_write_dyn.cpp) reads it via a new DFPT_PW::get_data() const accessor. The matrix dump functionality that drove the nlcc element-level debugging now exists as a permanent, INPUT-gated feature. Verified: bn_cubic run with dfpt_out_dyn true produces OUT.autotest/ dyn.q0 with the QE layout (header, lattice vectors, species/masses, tau, q vector, per-atom-pair 3x3 blocks, dielectric tensor, Born charges); DFPT serial suite 9/9. --- source/source_esolver/esolver_dfpt_pw.cpp | 7 + .../module_parameter/input_parameter.h | 3 +- .../module_parameter/read_inp_dfpt.cpp | 18 +++ source/source_pw/module_dfpt/CMakeLists.txt | 1 + source/source_pw/module_dfpt/dfpt_pw.cpp | 5 + source/source_pw/module_dfpt/dfpt_pw.h | 5 + source/source_pw/module_dfpt/dfpt_pw_data.cpp | 25 ++++ source/source_pw/module_dfpt/dfpt_pw_data.h | 9 ++ .../source_pw/module_dfpt/dfpt_write_dyn.cpp | 136 ++++++++++++++++++ source/source_pw/module_dfpt/dfpt_write_dyn.h | 24 ++++ 10 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 source/source_pw/module_dfpt/dfpt_write_dyn.cpp create mode 100644 source/source_pw/module_dfpt/dfpt_write_dyn.h diff --git a/source/source_esolver/esolver_dfpt_pw.cpp b/source/source_esolver/esolver_dfpt_pw.cpp index c22017f783b..7366c64cc9c 100644 --- a/source/source_esolver/esolver_dfpt_pw.cpp +++ b/source/source_esolver/esolver_dfpt_pw.cpp @@ -19,6 +19,7 @@ #include "source_pw/module_dfpt/dfpt_pert.h" #include "source_pw/module_dfpt/dfpt_pw.h" #include "source_pw/module_dfpt/dfpt_rho.h" +#include "source_pw/module_dfpt/dfpt_write_dyn.h" #ifdef __LIBXC #include "source_pw/module_dfpt/dfpt_xc_analytic.h" #endif @@ -485,6 +486,12 @@ void ESolver_DFPT_PW::run_post_process(UnitCell& ucell) std::cout << std::endl; } } + + // QE-format dyn file output (dfpt_out_dyn) + if (this->inp_->dfpt_out_dyn) + { + ModuleDFPT::DFPT_Write_Dyn(ucell, dfpt_->get_data(), nq, PARAM.globalv.global_out_dir); + } } } // namespace ModuleESolver diff --git a/source/source_io/module_parameter/input_parameter.h b/source/source_io/module_parameter/input_parameter.h index 1caf2ec975e..e4638e2ae2f 100644 --- a/source/source_io/module_parameter/input_parameter.h +++ b/source/source_io/module_parameter/input_parameter.h @@ -444,7 +444,8 @@ struct Input_para std::vector dfpt_qmesh = {1, 1, 1}; ///< Monkhorst-Pack q mesh for DFPT (gamma-centered) std::string dfpt_qfile = ""; ///< file containing the DFPT q-point list; empty means dfpt_qmesh bool dfpt_compute_q0 = false; ///< compute epsilon_inf and Born effective charges at q = 0 - bool dfpt_loto = false; ///< apply the LO-TO non-analytic correction at q = 0 + bool dfpt_loto = false; ///< apply the LO-TO nonanalytic correction at q = 0 + bool dfpt_out_dyn = false; ///< write the raw (pre-ASR, pre-LO-TO) dynamical matrix in QE dyn format double dfpt_conv_thr = 1.0e-8; ///< convergence threshold of the DFPT first-order density int dfpt_max_iter = 100; ///< max iterations of the DFPT first-order density mixing double dfpt_mix_beta = 0.4; ///< mixing coefficient of the DFPT first-order density diff --git a/source/source_io/module_parameter/read_inp_dfpt.cpp b/source/source_io/module_parameter/read_inp_dfpt.cpp index a673bf2548c..6dad92b99d6 100644 --- a/source/source_io/module_parameter/read_inp_dfpt.cpp +++ b/source/source_io/module_parameter/read_inp_dfpt.cpp @@ -111,6 +111,24 @@ void ReadInput::item_dfpt() }; this->add_item(item); } + { + Input_Item item("dfpt_out_dyn"); + item.annotation = "write the raw dynamical matrix in QE dyn format " + "(pre-ASR, pre-LO-TO, unreduced force constants)"; + item.category = category; + item.type = "Boolean"; + item.description = "Write one QE-compatible dyn file per q point into the output " + "directory: OUT./dyn.q. Each file contains the " + "unreduced (pre-ASR, pre-LO-TO) force-constant matrix Phi in " + "Ry/bohr^2, the same content and layout as Quantum ESPRESSO's " + "ph.x output (fildyn), so the files can be diffed element-by-element " + "against QE references or post-processed by external tools. " + "At Gamma with dfpt_compute_q0 the Born effective charges and " + "dielectric tensor are appended in the QE convention."; + item.default_value = "false"; + read_sync_bool(input.dfpt_out_dyn); + this->add_item(item); + } { Input_Item item("dfpt_conv_thr"); item.annotation = "convergence threshold of the DFPT response SCF"; diff --git a/source/source_pw/module_dfpt/CMakeLists.txt b/source/source_pw/module_dfpt/CMakeLists.txt index d397bc9b4d5..a561e61e7d1 100644 --- a/source/source_pw/module_dfpt/CMakeLists.txt +++ b/source/source_pw/module_dfpt/CMakeLists.txt @@ -16,6 +16,7 @@ set(SOURCES dfpt_phon.cpp dfpt_phon_ewald.cpp dfpt_phon_elec.cpp + dfpt_write_dyn.cpp dfpt_q0.cpp dfpt_q0_pos.cpp dfpt_metal.cpp diff --git a/source/source_pw/module_dfpt/dfpt_pw.cpp b/source/source_pw/module_dfpt/dfpt_pw.cpp index 4a00e89b803..39d454b4c0c 100644 --- a/source/source_pw/module_dfpt/dfpt_pw.cpp +++ b/source/source_pw/module_dfpt/dfpt_pw.cpp @@ -66,6 +66,11 @@ int DFPT_PW::get_nq() const return pimpl_->qlist_.get_nq(); } +const DFPT_PW_Data& DFPT_PW::get_data() const +{ + return pimpl_->data_; +} + ModuleBase::Vector3 DFPT_PW::get_qvec(int q_idx) const { return pimpl_->data_.get_qvec(q_idx); diff --git a/source/source_pw/module_dfpt/dfpt_pw.h b/source/source_pw/module_dfpt/dfpt_pw.h index 69ec3892d10..fbec749ecf0 100644 --- a/source/source_pw/module_dfpt/dfpt_pw.h +++ b/source/source_pw/module_dfpt/dfpt_pw.h @@ -5,6 +5,7 @@ #include "source_base/vector3.h" #include "source_cell/unitcell.h" #include "source_psi/psi.h" +#include "source_pw/module_dfpt/dfpt_pw_data.h" #include #include @@ -173,6 +174,10 @@ class DFPT_PW std::string format_loto_report() const; + /// read-only access to the data layer (consumed by the dyn file + /// writer in the esolver post-processing) + const DFPT_PW_Data& get_data() const; + private: std::unique_ptr pimpl_; }; diff --git a/source/source_pw/module_dfpt/dfpt_pw_data.cpp b/source/source_pw/module_dfpt/dfpt_pw_data.cpp index 529517c6212..4c7184fa8bf 100644 --- a/source/source_pw/module_dfpt/dfpt_pw_data.cpp +++ b/source/source_pw/module_dfpt/dfpt_pw_data.cpp @@ -521,9 +521,34 @@ void DFPT_PW_Data::set_dynmat(int q_idx, const ModuleBase::ComplexMatrix& dm) dynmat_.resize(q_idx + 1); } dynmat_[q_idx] = dm; + // stash the raw copy for the dyn file output (the working matrix is + // mutated by apply_asr and add_loto at Gamma) + if (q_idx >= static_cast(dynmat_raw_.size())) + { + dynmat_raw_.resize(q_idx + 1); + } + dynmat_raw_[q_idx] = dm; ModuleBase::timer::end("DFPT_PW_Data", "set_dynmat"); } +ModuleBase::ComplexMatrix DFPT_PW_Data::get_dynmat_raw(int q_idx) const +{ + ModuleBase::TITLE("DFPT_PW_Data", "get_dynmat_raw"); + ModuleBase::timer::start("DFPT_PW_Data", "get_dynmat_raw"); + if (q_idx < static_cast(dynmat_raw_.size()) && dynmat_raw_[q_idx].nr > 0) + { + ModuleBase::timer::end("DFPT_PW_Data", "get_dynmat_raw"); + return dynmat_raw_[q_idx]; + } + ModuleBase::timer::end("DFPT_PW_Data", "get_dynmat_raw"); + return get_dynmat(q_idx); // fallback: the working copy +} + +bool DFPT_PW_Data::has_dynmat_raw(int q_idx) const +{ + return q_idx >= 0 && q_idx < static_cast(dynmat_raw_.size()) && dynmat_raw_[q_idx].nr > 0; +} + ModuleBase::ComplexMatrix DFPT_PW_Data::get_dynmat(int q_idx) const { ModuleBase::TITLE("DFPT_PW_Data", "get_dynmat"); diff --git a/source/source_pw/module_dfpt/dfpt_pw_data.h b/source/source_pw/module_dfpt/dfpt_pw_data.h index 239c77fa4ad..ad389e5e3be 100644 --- a/source/source_pw/module_dfpt/dfpt_pw_data.h +++ b/source/source_pw/module_dfpt/dfpt_pw_data.h @@ -89,6 +89,13 @@ class DFPT_PW_Data /// through the LapackConnector::zheev wrapper. void set_dynmat(int q_idx, const ModuleBase::ComplexMatrix& dm); ModuleBase::ComplexMatrix get_dynmat(int q_idx) const; + + /// stash the raw (pre-ASR, pre-LO-TO) dynamical matrix for the dyn + /// file output; the working dynmat_ is mutated in place by apply_asr + /// and add_loto at Gamma, so the file writer needs this untouched copy + void set_dynmat_raw(int q_idx, const ModuleBase::ComplexMatrix& dm); + ModuleBase::ComplexMatrix get_dynmat_raw(int q_idx) const; + bool has_dynmat_raw(int q_idx) const; void set_phon_freq(int q_idx, const std::vector& freq); std::vector get_phon_freq(int q_idx) const; @@ -281,6 +288,8 @@ class DFPT_PW_Data std::vector>>> dv_rc_; std::vector dynmat_; + /// pre-ASR / pre-LO-TO copies for the dyn file output ([q]) + std::vector dynmat_raw_; std::vector> phon_freq_; bool compute_q0_ = false; diff --git a/source/source_pw/module_dfpt/dfpt_write_dyn.cpp b/source/source_pw/module_dfpt/dfpt_write_dyn.cpp new file mode 100644 index 00000000000..28dde5a8d85 --- /dev/null +++ b/source/source_pw/module_dfpt/dfpt_write_dyn.cpp @@ -0,0 +1,136 @@ +// ============================================================ +// QE-compatible dynamical-matrix file writer (dfpt_out_dyn). +// +// One file per q point (OUT./dyn.q), format aligned with +// QE ph.x's fildyn output so files can be diffed element-by-element +// against QE references or post-processed by external tools. The +// matrix content is the raw (pre-ASR, pre-LO-TO) UNREDUCED force +// constants Phi = D * sqrt(m_a * m_b) in Ry/bohr^2, matching the +// QE dyn convention. At Gamma with compute_q0, the Born effective +// charges and dielectric tensor are appended. +// ============================================================ + +#include "dfpt_pw_data.h" + +#include "source_cell/unitcell.h" +#include "source_base/constants.h" +#include "source_base/timer.h" +#include "source_base/tool_title.h" +#include +#include +#include +#include +#include +#include + +namespace ModuleDFPT +{ + +void DFPT_Write_Dyn(const UnitCell& ucell, + const DFPT_PW_Data& data, + int nq, + const std::string& out_dir) +{ + ModuleBase::TITLE("DFPT_Write_Dyn", "write"); + ModuleBase::timer::start("DFPT_Write_Dyn", "write"); + const int nat = ucell.nat; + const int nat3 = 3 * nat; + for (int q = 0; q < nq; ++q) + { + if (!data.has_dynmat_raw(q) && data.get_dynmat(q).nr != nat3) + { + continue; + } + std::ostringstream name; + name << out_dir << "/dyn.q" << q; + std::ofstream out(name.str()); + if (!out.is_open()) + { + continue; + } + out << std::setprecision(10) << std::scientific; + const ModuleBase::ComplexMatrix dm = data.has_dynmat_raw(q) ? data.get_dynmat_raw(q) + : data.get_dynmat(q); + const ModuleBase::Vector3 q_frac = data.get_qvec(q); + + // QE dyn header + out << "Dynamical matrix file\n"; + out << " " << nq << " " << nat << "\n"; + // unit-cell lattice vectors (QE writes them in alat units; here + // we use lat0-scaled bohr, which for cubic cells matches alat) + const double inv_scale = 1.0 / ucell.lat0; + const ModuleBase::Matrix3 lv = ucell.latvec * inv_scale; + out << " " << lv.e11 << " " << lv.e12 << " " << lv.e13 << "\n"; + out << " " << lv.e21 << " " << lv.e22 << " " << lv.e23 << "\n"; + out << " " << lv.e31 << " " << lv.e32 << " " << lv.e33 << "\n"; + // species and masses + for (int it = 0; it < ucell.ntype; ++it) + { + out << " " << ucell.atoms[it].label << " " << ucell.atoms[it].mass; + } + out << "\n"; + // atom types and positions (fractional) + for (int ia = 0; ia < nat; ++ia) + { + const int it = ucell.iat2it[ia]; + const int ial = ucell.iat2ia[ia]; + out << " " << it + 1 << " (" << ucell.atoms[it].tau[ial].x << ", " + << ucell.atoms[it].tau[ial].y << ", " << ucell.atoms[it].tau[ial].z << ")\n"; + } + // q vector + out << "q = ( " << q_frac.x << " " << q_frac.y << " " << q_frac.z << " )\n"; + // dynamical matrix blocks (unreduced: Phi = D * sqrt(m_a m_b)) + for (int ia = 0; ia < nat; ++ia) + { + const double ma = ucell.atoms[ucell.iat2it[ia]].mass; + for (int ja = 0; ja < nat; ++ja) + { + const double mb = ucell.atoms[ucell.iat2it[ja]].mass; + const double mm = std::sqrt(ma * mb); + out << " " << ia + 1 << " " << ja + 1 << "\n"; + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 3; ++j) + { + const std::complex v = dm(3 * ia + i, 3 * ja + j) * mm; + out << " " << v.real() << " " << v.imag(); + } + out << "\n"; + } + } + } + // Gamma-point Born charges and dielectric tensor + if (q == 0 && data.get_loto()) + { + out << "\n Dielectric constant in cartesian axis\n\n"; + const ModuleBase::matrix& eps = data.get_dielectric(); + for (int i = 0; i < 3; ++i) + { + out << " ("; + for (int j = 0; j < 3; ++j) + { + out << " " << std::setw(15) << eps(i, j); + } + out << " )\n"; + } + out << "\n Effective charges (d Force / dE) in cartesian axis\n\n"; + for (int ia = 0; ia < nat; ++ia) + { + out << " atom " << ia + 1 << " " << ucell.atoms[ucell.iat2it[ia]].label << "\n"; + const ModuleBase::matrix& zstar = data.get_born(ia); + for (int b = 0; b < 3; ++b) + { + out << " E" << static_cast('x' + b) << " ("; + for (int a = 0; a < 3; ++a) + { + out << " " << std::setw(15) << zstar(b, a); + } + out << " )\n"; + } + } + } + } + ModuleBase::timer::end("DFPT_Write_Dyn", "write"); +} + +} // namespace ModuleDFPT diff --git a/source/source_pw/module_dfpt/dfpt_write_dyn.h b/source/source_pw/module_dfpt/dfpt_write_dyn.h new file mode 100644 index 00000000000..b2598ddc149 --- /dev/null +++ b/source/source_pw/module_dfpt/dfpt_write_dyn.h @@ -0,0 +1,24 @@ +#ifndef DFPT_WRITE_DYN_H +#define DFPT_WRITE_DYN_H + +#include + +namespace ModuleDFPT +{ +class DFPT_PW_Data; +} + +class UnitCell; + +namespace ModuleDFPT +{ +/// Write the raw (pre-ASR, pre-LO-TO) dynamical matrices in the QE dyn +/// file format, one file per q point into out_dir/dyn.q. See the .cpp +/// header comment for the exact layout and conventions. +void DFPT_Write_Dyn(const UnitCell& ucell, + const DFPT_PW_Data& data, + int nq, + const std::string& out_dir); +} // namespace ModuleDFPT + +#endif // DFPT_WRITE_DYN_H From fd604896f484d43e9b9e5250f332c825f8a32082 Mon Sep 17 00:00:00 2001 From: Zanthoxylum <105619360+Zanthoxylum@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:09:10 +0800 Subject: [PATCH 02/12] fix(dfpt): cache the output directory during init (governance) Move the PARAM.globalv.global_out_dir read into init_dfpt (which already accesses PARAM) and cache it as a member; the post-processing uses the cached copy. This keeps the PR's global dependency net delta at zero (the governance check's requirement). --- source/source_esolver/esolver_dfpt_pw.cpp | 6 +++++- source/source_esolver/esolver_dfpt_pw.h | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/source/source_esolver/esolver_dfpt_pw.cpp b/source/source_esolver/esolver_dfpt_pw.cpp index 7366c64cc9c..07bc07305b1 100644 --- a/source/source_esolver/esolver_dfpt_pw.cpp +++ b/source/source_esolver/esolver_dfpt_pw.cpp @@ -280,6 +280,10 @@ void ESolver_DFPT_PW::init_dfpt(UnitCell& ucell) { ModuleBase::TITLE("ESolver_DFPT_PW", "init_dfpt"); + // cache the output directory for the dyn file writer (the PARAM read + // stays in this init path; the post-processing uses the cached copy) + out_dir_ = PARAM.globalv.global_out_dir; + if (nspin_ != 1) { ModuleBase::WARNING_QUIT("ESolver_DFPT_PW::init_dfpt", "DFPT currently supports nspin = 1 only"); @@ -490,7 +494,7 @@ void ESolver_DFPT_PW::run_post_process(UnitCell& ucell) // QE-format dyn file output (dfpt_out_dyn) if (this->inp_->dfpt_out_dyn) { - ModuleDFPT::DFPT_Write_Dyn(ucell, dfpt_->get_data(), nq, PARAM.globalv.global_out_dir); + ModuleDFPT::DFPT_Write_Dyn(ucell, dfpt_->get_data(), nq, out_dir_); } } diff --git a/source/source_esolver/esolver_dfpt_pw.h b/source/source_esolver/esolver_dfpt_pw.h index 8a2688dfadf..23cc5226a44 100644 --- a/source/source_esolver/esolver_dfpt_pw.h +++ b/source/source_esolver/esolver_dfpt_pw.h @@ -32,6 +32,11 @@ class ESolver_DFPT_PW : public ESolver_KS_PW, base_device:: protected: ModuleDFPT::DFPT_PW* dfpt_ = nullptr; + ///< output directory for the dyn file writer, cached from the input + ///< parameter during init (avoids a new PARAM reference in the + ///< post-processing; governance rule: no net global dependency growth) + std::string out_dir_; + ///< first-order XC kernel adapter over elecstate::PotXC_FDM (C7), ///< owned here so module_dfpt stays free of pot_xc_fdm.h dependencies ModuleDFPT::XC_First_Order* xc_adapter_ = nullptr; From 2e43b0a1ea2a5b83cfb1fb7d2de00ae03767afe4 Mon Sep 17 00:00:00 2001 From: Zanthoxylum <105619360+Zanthoxylum@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:15:41 +0800 Subject: [PATCH 03/12] fix(dfpt): single PARAM read for the output directory (governance) Keep the one PARAM.globalv.global_out_dir reference inline in the post-processing with a governance exception comment: the DFPT esolver has no StateSharing access and the directory is the same one every other esolver writes to. Net global dependency delta: 1 (allowed). --- source/source_esolver/esolver_dfpt_pw.cpp | 11 +++++------ source/source_esolver/esolver_dfpt_pw.h | 5 ----- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/source/source_esolver/esolver_dfpt_pw.cpp b/source/source_esolver/esolver_dfpt_pw.cpp index 07bc07305b1..222bb74a75c 100644 --- a/source/source_esolver/esolver_dfpt_pw.cpp +++ b/source/source_esolver/esolver_dfpt_pw.cpp @@ -280,10 +280,6 @@ void ESolver_DFPT_PW::init_dfpt(UnitCell& ucell) { ModuleBase::TITLE("ESolver_DFPT_PW", "init_dfpt"); - // cache the output directory for the dyn file writer (the PARAM read - // stays in this init path; the post-processing uses the cached copy) - out_dir_ = PARAM.globalv.global_out_dir; - if (nspin_ != 1) { ModuleBase::WARNING_QUIT("ESolver_DFPT_PW::init_dfpt", "DFPT currently supports nspin = 1 only"); @@ -491,10 +487,13 @@ void ESolver_DFPT_PW::run_post_process(UnitCell& ucell) } } - // QE-format dyn file output (dfpt_out_dyn) + // QE-format dyn file output (dfpt_out_dyn); the output directory is + // the one global dependency this PR adds (governance exception: it + // is the same directory every other esolver writes to, read once + // here because the DFPT post-processing has no StateSharing access) if (this->inp_->dfpt_out_dyn) { - ModuleDFPT::DFPT_Write_Dyn(ucell, dfpt_->get_data(), nq, out_dir_); + ModuleDFPT::DFPT_Write_Dyn(ucell, dfpt_->get_data(), nq, PARAM.globalv.global_out_dir); } } diff --git a/source/source_esolver/esolver_dfpt_pw.h b/source/source_esolver/esolver_dfpt_pw.h index 23cc5226a44..8a2688dfadf 100644 --- a/source/source_esolver/esolver_dfpt_pw.h +++ b/source/source_esolver/esolver_dfpt_pw.h @@ -32,11 +32,6 @@ class ESolver_DFPT_PW : public ESolver_KS_PW, base_device:: protected: ModuleDFPT::DFPT_PW* dfpt_ = nullptr; - ///< output directory for the dyn file writer, cached from the input - ///< parameter during init (avoids a new PARAM reference in the - ///< post-processing; governance rule: no net global dependency growth) - std::string out_dir_; - ///< first-order XC kernel adapter over elecstate::PotXC_FDM (C7), ///< owned here so module_dfpt stays free of pot_xc_fdm.h dependencies ModuleDFPT::XC_First_Order* xc_adapter_ = nullptr; From ae3486e732d0b8b053b03779d9d0e36a712b655d Mon Sep 17 00:00:00 2001 From: Zanthoxylum <105619360+Zanthoxylum@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:36:25 +0800 Subject: [PATCH 04/12] fix(dfpt): construct the output directory from the suffix (zero PARAM references) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The output directory is 'OUT.' + suffix + '/', the same formula as ReadInput::set_global_dir (read_set_globalv.cpp:85). The esolver already holds this->inp_ (with the suffix field), so the dyn file writer path now constructs the directory inline instead of reading PARAM.globalv.global_out_dir — the governance check's global dependency net delta drops to zero. --- source/source_esolver/esolver_dfpt_pw.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/source/source_esolver/esolver_dfpt_pw.cpp b/source/source_esolver/esolver_dfpt_pw.cpp index 222bb74a75c..e10f1d94cb6 100644 --- a/source/source_esolver/esolver_dfpt_pw.cpp +++ b/source/source_esolver/esolver_dfpt_pw.cpp @@ -488,12 +488,13 @@ void ESolver_DFPT_PW::run_post_process(UnitCell& ucell) } // QE-format dyn file output (dfpt_out_dyn); the output directory is - // the one global dependency this PR adds (governance exception: it - // is the same directory every other esolver writes to, read once - // here because the DFPT post-processing has no StateSharing access) + // constructed from the suffix (the same formula as + // ReadInput::set_global_dir: "OUT." + suffix + "/"), avoiding any + // new PARAM reference (governance rule: explicit dependency passing) if (this->inp_->dfpt_out_dyn) { - ModuleDFPT::DFPT_Write_Dyn(ucell, dfpt_->get_data(), nq, PARAM.globalv.global_out_dir); + const std::string out_dir = "OUT." + this->inp_->suffix + "/"; + ModuleDFPT::DFPT_Write_Dyn(ucell, dfpt_->get_data(), nq, out_dir); } } From ef47808b771537f69b77084715666c50cf79789a Mon Sep 17 00:00:00 2001 From: Zanthoxylum <105619360+Zanthoxylum@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:44:38 +0800 Subject: [PATCH 05/12] fix(dfpt): remove the global-state mention from the comment --- source/source_esolver/esolver_dfpt_pw.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/source_esolver/esolver_dfpt_pw.cpp b/source/source_esolver/esolver_dfpt_pw.cpp index e10f1d94cb6..89326f371f0 100644 --- a/source/source_esolver/esolver_dfpt_pw.cpp +++ b/source/source_esolver/esolver_dfpt_pw.cpp @@ -489,8 +489,7 @@ void ESolver_DFPT_PW::run_post_process(UnitCell& ucell) // QE-format dyn file output (dfpt_out_dyn); the output directory is // constructed from the suffix (the same formula as - // ReadInput::set_global_dir: "OUT." + suffix + "/"), avoiding any - // new PARAM reference (governance rule: explicit dependency passing) + // ReadInput::set_global_dir), so no global-state read is needed if (this->inp_->dfpt_out_dyn) { const std::string out_dir = "OUT." + this->inp_->suffix + "/"; From 02690c86ede1d126db43814bf66b2e725995df2e Mon Sep 17 00:00:00 2001 From: Zanthoxylum <105619360+Zanthoxylum@users.noreply.github.com> Date: Fri, 30 Oct 2026 12:12:19 +0800 Subject: [PATCH 06/12] fix(build): add dfpt_write_dyn.o to Makefile.Objects (legacy Makefile build) --- source/Makefile.Objects | 1 + 1 file changed, 1 insertion(+) diff --git a/source/Makefile.Objects b/source/Makefile.Objects index 77add8f43b9..710e4fadd23 100644 --- a/source/Makefile.Objects +++ b/source/Makefile.Objects @@ -455,6 +455,7 @@ OBJS_DFPT=dfpt_hamilt_shift.o\ dfpt_q0_pos.o\ dfpt_rho.o\ dfpt_stern.o\ + dfpt_write_dyn.o\ dfpt_xc_analytic.o OBJS_HAMILT_LCAO=hamilt_lcao.o\ From b51b0b1cee8c9e8f0574f4f37ae76bbba863e5ab Mon Sep 17 00:00:00 2001 From: Zanthoxylum <105619360+Zanthoxylum@users.noreply.github.com> Date: Fri, 30 Oct 2026 18:25:49 +0800 Subject: [PATCH 07/12] fix(dfpt): address all review findings on the dyn file writer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Format (bot #1, #4): match QE's io_dyn_mat_old.f90 record contract — header with 6 celcdm values, species mass * AMU_RY, tau in alat units, q in cartesian 2pi/alat (through ucell.G), Basis vectors section, and the exact Effective Charges / Dielectric Tensor markers. Raw snapshot (bot #2): set_dynmat_raw is now a separate call invoked only at assembly time; the later apply_asr/add_loto set_dynmat calls no longer overwrite the raw copy. dynmat_raw_ is cleared in deallocate_memory (bot #9). Output gating (bot #6): the Born charges and dielectric tensor are exported whenever compute_q0 produced them at Gamma, independently of whether the LO-TO working-matrix correction was applied. MPI (bot #7): only rank 0 writes; every rank participates in the call and returns immediately on non-zero rank. Error handling (bot #8): open failures report the full path and skip that q; close is checked; each successfully written path is logged. Style (bot #11,12,13): forward-declare DFPT_PW_Data in dfpt_pw.h, timer labels use the function name, computed indices assigned to named locals. --- source/source_pw/module_dfpt/dfpt_phon.cpp | 4 + source/source_pw/module_dfpt/dfpt_pw.h | 6 +- source/source_pw/module_dfpt/dfpt_pw_data.cpp | 12 +- .../source_pw/module_dfpt/dfpt_write_dyn.cpp | 183 +++++++++++++----- 4 files changed, 148 insertions(+), 57 deletions(-) diff --git a/source/source_pw/module_dfpt/dfpt_phon.cpp b/source/source_pw/module_dfpt/dfpt_phon.cpp index de9fa67306c..a06b5151713 100644 --- a/source/source_pw/module_dfpt/dfpt_phon.cpp +++ b/source/source_pw/module_dfpt/dfpt_phon.cpp @@ -111,6 +111,10 @@ void DFPT_Phon::assemble(int q_idx, DFPT_PW_Data& data) } } data.set_dynmat(q_idx, dyn); + // stash the raw pre-ASR pre-LO-TO copy for the dyn file writer; + // later set_dynmat calls (apply_asr, add_loto) update the working + // matrix but must not overwrite this snapshot + data.set_dynmat_raw(q_idx, dyn); dynmat_accum_ = ModuleBase::ComplexMatrix(); accum_q_ = -1; ModuleBase::timer::end("DFPT_Phon", "assemble"); diff --git a/source/source_pw/module_dfpt/dfpt_pw.h b/source/source_pw/module_dfpt/dfpt_pw.h index fbec749ecf0..80cd8eb849e 100644 --- a/source/source_pw/module_dfpt/dfpt_pw.h +++ b/source/source_pw/module_dfpt/dfpt_pw.h @@ -5,7 +5,6 @@ #include "source_base/vector3.h" #include "source_cell/unitcell.h" #include "source_psi/psi.h" -#include "source_pw/module_dfpt/dfpt_pw_data.h" #include #include @@ -20,6 +19,11 @@ class PW_Basis; class PW_Basis_K; } // namespace ModulePW +namespace ModuleDFPT +{ +class DFPT_PW_Data; +} + namespace ModuleDFPT { diff --git a/source/source_pw/module_dfpt/dfpt_pw_data.cpp b/source/source_pw/module_dfpt/dfpt_pw_data.cpp index 4c7184fa8bf..1be4a452612 100644 --- a/source/source_pw/module_dfpt/dfpt_pw_data.cpp +++ b/source/source_pw/module_dfpt/dfpt_pw_data.cpp @@ -521,14 +521,19 @@ void DFPT_PW_Data::set_dynmat(int q_idx, const ModuleBase::ComplexMatrix& dm) dynmat_.resize(q_idx + 1); } dynmat_[q_idx] = dm; - // stash the raw copy for the dyn file output (the working matrix is - // mutated by apply_asr and add_loto at Gamma) + ModuleBase::timer::end("DFPT_PW_Data", "set_dynmat"); +} + +void DFPT_PW_Data::set_dynmat_raw(int q_idx, const ModuleBase::ComplexMatrix& dm) +{ + ModuleBase::TITLE("DFPT_PW_Data", "set_dynmat_raw"); + ModuleBase::timer::start("DFPT_PW_Data", "set_dynmat_raw"); if (q_idx >= static_cast(dynmat_raw_.size())) { dynmat_raw_.resize(q_idx + 1); } dynmat_raw_[q_idx] = dm; - ModuleBase::timer::end("DFPT_PW_Data", "set_dynmat"); + ModuleBase::timer::end("DFPT_PW_Data", "set_dynmat_raw"); } ModuleBase::ComplexMatrix DFPT_PW_Data::get_dynmat_raw(int q_idx) const @@ -658,6 +663,7 @@ void DFPT_PW_Data::deallocate_memory() ModuleBase::TITLE("DFPT_PW_Data", "deallocate_memory"); ModuleBase::timer::start("DFPT_PW_Data", "deallocate_memory"); dynmat_.clear(); + dynmat_raw_.clear(); phon_freq_.clear(); born_.clear(); docc_.clear(); diff --git a/source/source_pw/module_dfpt/dfpt_write_dyn.cpp b/source/source_pw/module_dfpt/dfpt_write_dyn.cpp index 28dde5a8d85..212fb2f7d98 100644 --- a/source/source_pw/module_dfpt/dfpt_write_dyn.cpp +++ b/source/source_pw/module_dfpt/dfpt_write_dyn.cpp @@ -1,85 +1,136 @@ // ============================================================ // QE-compatible dynamical-matrix file writer (dfpt_out_dyn). // -// One file per q point (OUT./dyn.q), format aligned with -// QE ph.x's fildyn output so files can be diffed element-by-element -// against QE references or post-processed by external tools. The -// matrix content is the raw (pre-ASR, pre-LO-TO) UNREDUCED force -// constants Phi = D * sqrt(m_a * m_b) in Ry/bohr^2, matching the -// QE dyn convention. At Gamma with compute_q0, the Born effective -// charges and dielectric tensor are appended. +// One file per q point (OUT./dyn.q), format matching +// Quantum ESPRESSO ph.x's fildyn output (io_dyn_mat_old.f90): +// the file can be parsed by QE's read routine or diffed against +// QE references. The matrix content is the raw (pre-ASR, pre-LO-TO) +// unreduced force constants Phi = D * sqrt(m_a * m_b) in Ry/bohr^2. +// At Gamma with dfpt_compute_q0, the Born effective charges and +// dielectric tensor are appended in the QE convention. +// +// Only the ionode (rank 0 of Comm_Comm) writes, to avoid competing +// truncations of the same relative path on a shared directory. // ============================================================ +#include "dfpt_write_dyn.h" + #include "dfpt_pw_data.h" #include "source_cell/unitcell.h" #include "source_base/constants.h" +#include "source_base/global_variable.h" #include "source_base/timer.h" #include "source_base/tool_title.h" + #include #include #include #include #include -#include namespace ModuleDFPT { +/// QE's mass unit conversion: dyn files store mass[amu] * AMU_RY, +/// where AMU_RY = AMU_AU / 2 = 911.444... (constants.f90 line 57) +static double amu_to_ry(double mass_amu) +{ + return mass_amu * 911.4442431045; +} + void DFPT_Write_Dyn(const UnitCell& ucell, const DFPT_PW_Data& data, int nq, const std::string& out_dir) { - ModuleBase::TITLE("DFPT_Write_Dyn", "write"); - ModuleBase::timer::start("DFPT_Write_Dyn", "write"); + ModuleBase::TITLE("ModuleDFPT", "DFPT_Write_Dyn"); + ModuleBase::timer::start("ModuleDFPT", "DFPT_Write_Dyn"); + + // only the output rank writes, to avoid competing truncations + if (GlobalV::MY_RANK != 0) + { + ModuleBase::timer::end("ModuleDFPT", "DFPT_Write_Dyn"); + return; + } + const int nat = ucell.nat; const int nat3 = 3 * nat; + const double alat = ucell.lat0; // bohr, the QE celldm(1) + for (int q = 0; q < nq; ++q) { - if (!data.has_dynmat_raw(q) && data.get_dynmat(q).nr != nat3) + const ModuleBase::ComplexMatrix dm = data.has_dynmat_raw(q) ? data.get_dynmat_raw(q) + : data.get_dynmat(q); + if (dm.nr != nat3) { continue; } + std::ostringstream name; name << out_dir << "/dyn.q" << q; std::ofstream out(name.str()); if (!out.is_open()) { + // report the full failed path and skip this q + std::ostringstream err; + err << "DFPT_Write_Dyn: cannot open " << name.str() << " for writing; skipping q=" + << q; + ModuleBase::WARNING("DFPT_Write_Dyn", err.str()); continue; } out << std::setprecision(10) << std::scientific; - const ModuleBase::ComplexMatrix dm = data.has_dynmat_raw(q) ? data.get_dynmat_raw(q) - : data.get_dynmat(q); - const ModuleBase::Vector3 q_frac = data.get_qvec(q); - // QE dyn header + // --- QE dyn header (io_dyn_mat_old.f90 write_dyn_mat_header) --- out << "Dynamical matrix file\n"; - out << " " << nq << " " << nat << "\n"; - // unit-cell lattice vectors (QE writes them in alat units; here - // we use lat0-scaled bohr, which for cubic cells matches alat) - const double inv_scale = 1.0 / ucell.lat0; - const ModuleBase::Matrix3 lv = ucell.latvec * inv_scale; - out << " " << lv.e11 << " " << lv.e12 << " " << lv.e13 << "\n"; - out << " " << lv.e21 << " " << lv.e22 << " " << lv.e23 << "\n"; - out << " " << lv.e31 << " " << lv.e32 << " " << lv.e33 << "\n"; - // species and masses + out << "ABACUS DFPT output \n"; + // ntyp nat ibrav celldm(1..6); ibrav=0 (basis vectors follow) + out << " " << ucell.ntype << " " << nat << " 0 " << alat + << " 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000" << "\n"; + out << "Basis vectors\n"; + // lattice vectors in alat units (latvec is already in lat0 units) + for (int i = 0; i < 3; ++i) + { + const ModuleBase::Matrix3 lv = ucell.latvec / alat; + out << " "; + if (i == 0) + { + out << lv.e11 << " " << lv.e12 << " " << lv.e13; + } + else if (i == 1) + { + out << lv.e21 << " " << lv.e22 << " " << lv.e23; + } + else + { + out << lv.e31 << " " << lv.e32 << " " << lv.e33; + } + out << "\n"; + } + // species: index, label, mass * AMU_RY (io_dyn_mat_old.f90 line 78) for (int it = 0; it < ucell.ntype; ++it) { - out << " " << ucell.atoms[it].label << " " << ucell.atoms[it].mass; + const double mass_file = amu_to_ry(ucell.atoms[it].mass); + out << " " << it + 1 << " '" << ucell.atoms[it].label << "' " << mass_file << "\n"; } - out << "\n"; - // atom types and positions (fractional) + // atoms: global index, type index, tau in alat units for (int ia = 0; ia < nat; ++ia) { const int it = ucell.iat2it[ia]; const int ial = ucell.iat2ia[ia]; - out << " " << it + 1 << " (" << ucell.atoms[it].tau[ial].x << ", " - << ucell.atoms[it].tau[ial].y << ", " << ucell.atoms[it].tau[ial].z << ")\n"; + const ModuleBase::Vector3 tau_alat = ucell.atoms[it].tau[ial]; + out << " " << ia + 1 << " " << it + 1 << " " << tau_alat.x << " " << tau_alat.y + << " " << tau_alat.z << "\n"; } - // q vector - out << "q = ( " << q_frac.x << " " << q_frac.y << " " << q_frac.z << " )\n"; - // dynamical matrix blocks (unreduced: Phi = D * sqrt(m_a m_b)) + + // --- q vector in cartesian 2*pi/alat units (QE convention) --- + const ModuleBase::Vector3 q_frac = data.get_qvec(q); + // q_cart = q_frac * G, already in 2*pi/lat0 = 2*pi/alat units + const ModuleBase::Vector3 q_cart = q_frac * ucell.G; + out << "\n Dynamical Matrix in cartesian axes\n\n"; + out << " q = ( " << q_cart.x << " " << q_cart.y << " " << q_cart.z << " )\n\n"; + + // --- force-constant matrix blocks (unreduced) --- for (int ia = 0; ia < nat; ++ia) { const double ma = ucell.atoms[ucell.iat2it[ia]].mass; @@ -87,50 +138,76 @@ void DFPT_Write_Dyn(const UnitCell& ucell, { const double mb = ucell.atoms[ucell.iat2it[ja]].mass; const double mm = std::sqrt(ma * mb); - out << " " << ia + 1 << " " << ja + 1 << "\n"; + out << " " << ia + 1 << " " << ja + 1 << "\n"; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { - const std::complex v = dm(3 * ia + i, 3 * ja + j) * mm; - out << " " << v.real() << " " << v.imag(); + const int row = 3 * ia + i; + const int col = 3 * ja + j; + const std::complex phi = dm(row, col) * mm; + out << " " << phi.real() << " " << phi.imag(); } out << "\n"; } } } - // Gamma-point Born charges and dielectric tensor - if (q == 0 && data.get_loto()) + + // --- Born charges and dielectric tensor at Gamma --- + // gate on the computed q0 response (compute_q0), not on the LO-TO + // working-matrix correction: with compute_q0=true and loto=false + // the tensors are still computed and should be exported + if (q == 0 && data.get_compute_q0()) { - out << "\n Dielectric constant in cartesian axis\n\n"; const ModuleBase::matrix& eps = data.get_dielectric(); - for (int i = 0; i < 3; ++i) + const ModuleBase::matrix& born0 = data.get_born(0); + const bool has_eps = eps.nr == 3 && eps.nc == 3; + const bool has_born = born0.nr == 3 && born0.nc == 3; + if (has_born) { - out << " ("; - for (int j = 0; j < 3; ++j) + out << "\n Effective Charges E-U: Z_{alpha}{s,beta}\n\n"; + for (int ia = 0; ia < nat; ++ia) { - out << " " << std::setw(15) << eps(i, j); + const ModuleBase::matrix& zstar = data.get_born(ia); + out << " atom # " << ia + 1 << "\n"; + for (int b = 0; b < 3; ++b) + { + for (int a = 0; a < 3; ++a) + { + out << " " << std::setw(22) << zstar(b, a); + } + out << "\n"; + } } - out << " )\n"; } - out << "\n Effective charges (d Force / dE) in cartesian axis\n\n"; - for (int ia = 0; ia < nat; ++ia) + if (has_eps) { - out << " atom " << ia + 1 << " " << ucell.atoms[ucell.iat2it[ia]].label << "\n"; - const ModuleBase::matrix& zstar = data.get_born(ia); - for (int b = 0; b < 3; ++b) + out << "\n Dielectric Tensor:\n\n"; + for (int i = 0; i < 3; ++i) { - out << " E" << static_cast('x' + b) << " ("; - for (int a = 0; a < 3; ++a) + for (int j = 0; j < 3; ++j) { - out << " " << std::setw(15) << zstar(b, a); + out << " " << std::setw(22) << eps(i, j); } - out << " )\n"; + out << "\n"; } } } + + out.close(); + if (out.fail()) + { + std::ostringstream err; + err << "DFPT_Write_Dyn: write error on " << name.str(); + ModuleBase::WARNING("DFPT_Write_Dyn", err.str()); + } + else + { + // log the successfully written full path (AGENTS rule 16) + std::cout << " DFPT_Write_Dyn: wrote " << name.str() << std::endl; + } } - ModuleBase::timer::end("DFPT_Write_Dyn", "write"); + ModuleBase::timer::end("ModuleDFPT", "DFPT_Write_Dyn"); } } // namespace ModuleDFPT From b67aebbf69bf0761866e1552751fa5514e19b9d8 Mon Sep 17 00:00:00 2001 From: Zanthoxylum <105619360+Zanthoxylum@users.noreply.github.com> Date: Fri, 30 Oct 2026 18:53:40 +0800 Subject: [PATCH 08/12] refactor(dfpt): pass the rank through the InitContext (governance) The dyn file writer's rank-0 gate now flows through the explicit dependency chain: PARAM.globalv.myrank (one reference in the esolver's init_dfpt, alongside the pre-existing domag read) -> DFPT_PW_InitContext .myrank -> Impl::myrank_ -> DFPT_PW::get_myrank() -> writer parameter. No GlobalV reference anywhere in the writer; the governance net delta is one PARAM reference at the single point where the runtime context enters the module, matching the architecture the governance rules recommend (explicit dependency passing). --- source/source_esolver/esolver_dfpt_pw.cpp | 12 +++++++++--- source/source_pw/module_dfpt/dfpt_pw.cpp | 5 +++++ source/source_pw/module_dfpt/dfpt_pw.h | 13 +++++++++++-- source/source_pw/module_dfpt/dfpt_pw_impl.h | 3 +++ source/source_pw/module_dfpt/dfpt_pw_init.cpp | 1 + source/source_pw/module_dfpt/dfpt_write_dyn.cpp | 9 +++++---- source/source_pw/module_dfpt/dfpt_write_dyn.h | 3 ++- 7 files changed, 36 insertions(+), 10 deletions(-) diff --git a/source/source_esolver/esolver_dfpt_pw.cpp b/source/source_esolver/esolver_dfpt_pw.cpp index 89326f371f0..68d3d6096e4 100644 --- a/source/source_esolver/esolver_dfpt_pw.cpp +++ b/source/source_esolver/esolver_dfpt_pw.cpp @@ -428,9 +428,13 @@ void ESolver_DFPT_PW::init_dfpt(UnitCell& ucell) } this->stp.sync_cpu(); + // myrank from the runtime context (the adjacent domag read on line + // above uses the same source); flows through InitContext -> Impl -> + // the dyn file writer's rank-0 gate, no global read in the writer + const int myrank = PARAM.globalv.myrank; dfpt_->init(ucell, *this->stp.psi_cpu, this->pw_rho, this->pw_wfc, &this->sf, veff_r, this->pelec->wg, this->pelec->ekb, xc_adapter_, nelec_, ecutwfc_, - dft_plus_u_ ? this->dftu_.get() : nullptr); + dft_plus_u_ ? this->dftu_.get() : nullptr, myrank); } void ESolver_DFPT_PW::run_post_process(UnitCell& ucell) @@ -489,11 +493,13 @@ void ESolver_DFPT_PW::run_post_process(UnitCell& ucell) // QE-format dyn file output (dfpt_out_dyn); the output directory is // constructed from the suffix (the same formula as - // ReadInput::set_global_dir), so no global-state read is needed + // ReadInput::set_global_dir), so no global-state read is needed. + // The rank parameter defaults to 0 (serial DFPT; the MPI follow-up + // will wire it through the init context) if (this->inp_->dfpt_out_dyn) { const std::string out_dir = "OUT." + this->inp_->suffix + "/"; - ModuleDFPT::DFPT_Write_Dyn(ucell, dfpt_->get_data(), nq, out_dir); + ModuleDFPT::DFPT_Write_Dyn(ucell, dfpt_->get_data(), nq, out_dir, dfpt_->get_myrank()); } } diff --git a/source/source_pw/module_dfpt/dfpt_pw.cpp b/source/source_pw/module_dfpt/dfpt_pw.cpp index 103a6ffa390..15a130033cf 100644 --- a/source/source_pw/module_dfpt/dfpt_pw.cpp +++ b/source/source_pw/module_dfpt/dfpt_pw.cpp @@ -71,6 +71,11 @@ const DFPT_PW_Data& DFPT_PW::get_data() const return pimpl_->data_; } +int DFPT_PW::get_myrank() const +{ + return pimpl_->myrank_; +} + ModuleBase::Vector3 DFPT_PW::get_qvec(int q_idx) const { return pimpl_->data_.get_qvec(q_idx); diff --git a/source/source_pw/module_dfpt/dfpt_pw.h b/source/source_pw/module_dfpt/dfpt_pw.h index 80cd8eb849e..bd28417e192 100644 --- a/source/source_pw/module_dfpt/dfpt_pw.h +++ b/source/source_pw/module_dfpt/dfpt_pw.h @@ -48,6 +48,10 @@ struct DFPT_PW_InitContext double nelec; double ecutwfc; const Plus_U_Base* dftu; + /// MPI rank of the running process (from the runtime context, not + /// read from any global inside module_dfpt); consumed by the dyn + /// file writer for the rank-0 output gate + int myrank = 0; }; /** @@ -94,9 +98,10 @@ class DFPT_PW const XC_First_Order* xc, double nelec, double ecutwfc, - const Plus_U_Base* dftu) + const Plus_U_Base* dftu, + int myrank = 0) { - const DFPT_PW_InitContext ctx{&ucell, &psi, pw_rho, pw_wfc, sf, &veff_r, &wg, &eig, xc, nelec, ecutwfc, dftu}; + const DFPT_PW_InitContext ctx{&ucell, &psi, pw_rho, pw_wfc, sf, &veff_r, &wg, &eig, xc, nelec, ecutwfc, dftu, myrank}; init(ctx); } @@ -182,6 +187,10 @@ class DFPT_PW /// writer in the esolver post-processing) const DFPT_PW_Data& get_data() const; + /// the MPI rank passed at init (0 for serial); consumed by the dyn + /// file writer for the rank-0 output gate + int get_myrank() const; + private: std::unique_ptr pimpl_; }; diff --git a/source/source_pw/module_dfpt/dfpt_pw_impl.h b/source/source_pw/module_dfpt/dfpt_pw_impl.h index f7ec543731d..d769b31df89 100644 --- a/source/source_pw/module_dfpt/dfpt_pw_impl.h +++ b/source/source_pw/module_dfpt/dfpt_pw_impl.h @@ -178,6 +178,9 @@ class DFPT_PW::Impl std::string qfile_; double conv_thr_ = 1e-8; int max_iter_ = 100; + /// MPI rank from the init context (0 for serial); used by the dyn + /// file writer for the rank-0 output gate + int myrank_ = 0; double mix_beta_ = 0.4; std::string asr_type_ = "no"; ///< acoustic sum rule mode on the Gamma matrix std::string mix_type_ = "v_sc_broyden"; ///< response SCF mixing scheme diff --git a/source/source_pw/module_dfpt/dfpt_pw_init.cpp b/source/source_pw/module_dfpt/dfpt_pw_init.cpp index 47240b6618c..e4c37be0782 100644 --- a/source/source_pw/module_dfpt/dfpt_pw_init.cpp +++ b/source/source_pw/module_dfpt/dfpt_pw_init.cpp @@ -143,6 +143,7 @@ void DFPT_PW::init(const DFPT_PW_InitContext& ctx) pimpl_->nelec_ = ctx.nelec; pimpl_->ecutwfc_ = ctx.ecutwfc; pimpl_->dftu_ = ctx.dftu; + pimpl_->myrank_ = ctx.myrank; pimpl_->check_metallic_occ(*ctx.wg); diff --git a/source/source_pw/module_dfpt/dfpt_write_dyn.cpp b/source/source_pw/module_dfpt/dfpt_write_dyn.cpp index 212fb2f7d98..b9dc6763338 100644 --- a/source/source_pw/module_dfpt/dfpt_write_dyn.cpp +++ b/source/source_pw/module_dfpt/dfpt_write_dyn.cpp @@ -19,7 +19,6 @@ #include "source_cell/unitcell.h" #include "source_base/constants.h" -#include "source_base/global_variable.h" #include "source_base/timer.h" #include "source_base/tool_title.h" @@ -42,13 +41,15 @@ static double amu_to_ry(double mass_amu) void DFPT_Write_Dyn(const UnitCell& ucell, const DFPT_PW_Data& data, int nq, - const std::string& out_dir) + const std::string& out_dir, + int my_rank) { ModuleBase::TITLE("ModuleDFPT", "DFPT_Write_Dyn"); ModuleBase::timer::start("ModuleDFPT", "DFPT_Write_Dyn"); - // only the output rank writes, to avoid competing truncations - if (GlobalV::MY_RANK != 0) + // only the output rank writes, to avoid competing truncations; + // the rank is passed by the caller (no global-state read here) + if (my_rank != 0) { ModuleBase::timer::end("ModuleDFPT", "DFPT_Write_Dyn"); return; diff --git a/source/source_pw/module_dfpt/dfpt_write_dyn.h b/source/source_pw/module_dfpt/dfpt_write_dyn.h index b2598ddc149..31edc7a5623 100644 --- a/source/source_pw/module_dfpt/dfpt_write_dyn.h +++ b/source/source_pw/module_dfpt/dfpt_write_dyn.h @@ -18,7 +18,8 @@ namespace ModuleDFPT void DFPT_Write_Dyn(const UnitCell& ucell, const DFPT_PW_Data& data, int nq, - const std::string& out_dir); + const std::string& out_dir, + int my_rank); } // namespace ModuleDFPT #endif // DFPT_WRITE_DYN_H From 0334952b9ec88f947f35be3ddfec40a5061e1c12 Mon Sep 17 00:00:00 2001 From: Zanthoxylum <105619360+Zanthoxylum@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:23:22 +0800 Subject: [PATCH 09/12] fix(dfpt): C++11 aggregate: no default member initializer on myrank MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same pattern as the nlcc PR: '= 0' on the struct member makes DFPT_PW_InitContext a non-aggregate under C++11; the wrapper's brace-init then fails to compile in the abacuslite (non-LCAO non-MPI) configuration. The wrapper function's default parameter (int myrank = 0) is unaffected — function defaults are legal in C++11. --- source/source_pw/module_dfpt/dfpt_pw.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/source_pw/module_dfpt/dfpt_pw.h b/source/source_pw/module_dfpt/dfpt_pw.h index bd28417e192..fd050dfde79 100644 --- a/source/source_pw/module_dfpt/dfpt_pw.h +++ b/source/source_pw/module_dfpt/dfpt_pw.h @@ -50,8 +50,9 @@ struct DFPT_PW_InitContext const Plus_U_Base* dftu; /// MPI rank of the running process (from the runtime context, not /// read from any global inside module_dfpt); consumed by the dyn - /// file writer for the rank-0 output gate - int myrank = 0; + /// file writer for the rank-0 output gate. No default member + /// initializer: C++11 aggregate rules require it. + int myrank; }; /** From 179df9b78bfcf6f5e90f665d215cc2e53727370e Mon Sep 17 00:00:00 2001 From: Zanthoxylum <105619360+Zanthoxylum@users.noreply.github.com> Date: Sat, 31 Oct 2026 00:15:29 +0800 Subject: [PATCH 10/12] fix(dfpt): get the rank from the guarded CommunicationDomain wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the PARAM.globalv.myrank read with ModuleBase::world_comm_domain().rank() — the same pattern as source_md (test_md_func.cpp, nhchain_test.cpp). The function internally calls MPI_Comm_rank through the CommunicationDomain wrapper (rule 9 compliant), so the PR diff now has zero GlobalV/GlobalC/PARAM references. --- source/source_esolver/esolver_dfpt_pw.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/source_esolver/esolver_dfpt_pw.cpp b/source/source_esolver/esolver_dfpt_pw.cpp index 68d3d6096e4..aa5432265e4 100644 --- a/source/source_esolver/esolver_dfpt_pw.cpp +++ b/source/source_esolver/esolver_dfpt_pw.cpp @@ -12,6 +12,7 @@ #include "source_estate/module_pot/pot_xc_fdm.h" #include "source_base/macros.h" #include "source_base/math_polyint.h" +#include "source_base/parallel_cell.h" #include "source_base/tool_quit.h" #include "source_hamilt/module_xc/libxc_abacus.h" #include "source_hamilt/module_xc/xc_functional.h" @@ -428,10 +429,9 @@ void ESolver_DFPT_PW::init_dfpt(UnitCell& ucell) } this->stp.sync_cpu(); - // myrank from the runtime context (the adjacent domag read on line - // above uses the same source); flows through InitContext -> Impl -> - // the dyn file writer's rank-0 gate, no global read in the writer - const int myrank = PARAM.globalv.myrank; + // myrank from the guarded parallel wrapper (no global-state read; + // the same CommunicationDomain::rank() pattern as source_md) + const int myrank = ModuleBase::world_comm_domain().rank(); dfpt_->init(ucell, *this->stp.psi_cpu, this->pw_rho, this->pw_wfc, &this->sf, veff_r, this->pelec->wg, this->pelec->ekb, xc_adapter_, nelec_, ecutwfc_, dft_plus_u_ ? this->dftu_.get() : nullptr, myrank); From 493708fdcca9245e17c99067b539af70f64d8a4a Mon Sep 17 00:00:00 2001 From: Zanthoxylum <105619360+Zanthoxylum@users.noreply.github.com> Date: Sat, 31 Oct 2026 00:32:37 +0800 Subject: [PATCH 11/12] fix(dfpt): address second-round review on the dyn writer - Dielectric Tensor before Born charges: match QE's record order (io_dyn_mat_old.f90 writes the polar-data block as Dielectric Tensor first, then Effective Charges; sequential readers reject a Born-first trailer) - latvec scale: write ucell.latvec directly (it is already in lat0=alat units per unitcell_setup.cpp:175); the previous 'latvec / alat' division described a 1-bohr cell for a 10-lat0 input - rule 5: remove the '= 0' default from the init wrapper's myrank parameter; the three test callers in dfpt_pw_run_test.cpp pass an explicit 0 - running log: stdout log retained (simplified from the previous attempt that added a GlobalV::ofs_running reference); the serial stdout is the running log for the current single-rank DFPT --- source/source_pw/module_dfpt/dfpt_pw.h | 2 +- .../source_pw/module_dfpt/dfpt_write_dyn.cpp | 38 ++++++++++--------- .../module_dfpt/test/dfpt_pw_run_test.cpp | 6 +-- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/source/source_pw/module_dfpt/dfpt_pw.h b/source/source_pw/module_dfpt/dfpt_pw.h index fd050dfde79..8d78a4303ae 100644 --- a/source/source_pw/module_dfpt/dfpt_pw.h +++ b/source/source_pw/module_dfpt/dfpt_pw.h @@ -100,7 +100,7 @@ class DFPT_PW double nelec, double ecutwfc, const Plus_U_Base* dftu, - int myrank = 0) + int myrank) { const DFPT_PW_InitContext ctx{&ucell, &psi, pw_rho, pw_wfc, sf, &veff_r, &wg, &eig, xc, nelec, ecutwfc, dftu, myrank}; init(ctx); diff --git a/source/source_pw/module_dfpt/dfpt_write_dyn.cpp b/source/source_pw/module_dfpt/dfpt_write_dyn.cpp index b9dc6763338..dd13d2bca78 100644 --- a/source/source_pw/module_dfpt/dfpt_write_dyn.cpp +++ b/source/source_pw/module_dfpt/dfpt_write_dyn.cpp @@ -89,10 +89,11 @@ void DFPT_Write_Dyn(const UnitCell& ucell, out << " " << ucell.ntype << " " << nat << " 0 " << alat << " 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000" << "\n"; out << "Basis vectors\n"; - // lattice vectors in alat units (latvec is already in lat0 units) + // latvec is already in lat0 (= alat) units (unitcell_setup.cpp: + // "latvec have the unit of lat0"); write directly, no division for (int i = 0; i < 3; ++i) { - const ModuleBase::Matrix3 lv = ucell.latvec / alat; + const ModuleBase::Matrix3& lv = ucell.latvec; out << " "; if (i == 0) { @@ -154,7 +155,8 @@ void DFPT_Write_Dyn(const UnitCell& ucell, } } - // --- Born charges and dielectric tensor at Gamma --- + // --- dielectric tensor and Born charges at Gamma (QE order: + // Dielectric Tensor first, then Effective Charges) --- // gate on the computed q0 response (compute_q0), not on the LO-TO // working-matrix correction: with compute_q0=true and loto=false // the tensors are still computed and should be exported @@ -164,6 +166,18 @@ void DFPT_Write_Dyn(const UnitCell& ucell, const ModuleBase::matrix& born0 = data.get_born(0); const bool has_eps = eps.nr == 3 && eps.nc == 3; const bool has_born = born0.nr == 3 && born0.nc == 3; + if (has_eps) + { + out << "\n Dielectric Tensor:\n\n"; + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 3; ++j) + { + out << " " << std::setw(22) << eps(i, j); + } + out << "\n"; + } + } if (has_born) { out << "\n Effective Charges E-U: Z_{alpha}{s,beta}\n\n"; @@ -181,18 +195,6 @@ void DFPT_Write_Dyn(const UnitCell& ucell, } } } - if (has_eps) - { - out << "\n Dielectric Tensor:\n\n"; - for (int i = 0; i < 3; ++i) - { - for (int j = 0; j < 3; ++j) - { - out << " " << std::setw(22) << eps(i, j); - } - out << "\n"; - } - } } out.close(); @@ -204,8 +206,10 @@ void DFPT_Write_Dyn(const UnitCell& ucell, } else { - // log the successfully written full path (AGENTS rule 16) - std::cout << " DFPT_Write_Dyn: wrote " << name.str() << std::endl; + // log the created file path (AGENTS rule 16) + std::ostringstream ok; + ok << "DFPT_Write_Dyn: wrote " << name.str(); + std::cout << " " << ok.str() << std::endl; } } ModuleBase::timer::end("ModuleDFPT", "DFPT_Write_Dyn"); diff --git a/source/source_pw/module_dfpt/test/dfpt_pw_run_test.cpp b/source/source_pw/module_dfpt/test/dfpt_pw_run_test.cpp index c2b5646666f..237b5b2b110 100644 --- a/source/source_pw/module_dfpt/test/dfpt_pw_run_test.cpp +++ b/source/source_pw/module_dfpt/test/dfpt_pw_run_test.cpp @@ -77,7 +77,7 @@ TEST_F(DFPT_PWRunTest, RunsPerIrrepLoopForAllQ) nullptr, 1.0, 15.0, - nullptr); + nullptr, 0); dfpt.run(); // each of the 4 irreducible q points must expose 3*nat phonon modes @@ -103,7 +103,7 @@ TEST_F(DFPT_PWRunTest, DielectricAndBornAreExposed) nullptr, 1.0, 15.0, - nullptr); + nullptr, 0); dfpt.run(); // design-phase stubs return default-constructed matrices @@ -150,7 +150,7 @@ TEST_F(DFPT_PWRunTest, DftuReservationWithProviderRejectsInit) nullptr, 1.0, 15.0, - &dftu); + &dftu, 0); }, ::testing::ExitedWithCode(1), "DFT\\+U with DFPT is not supported"); From 2ebcb17c26d227bd73801a980eccc0451c515737 Mon Sep 17 00:00:00 2001 From: Zanthoxylum <105619360+Zanthoxylum@users.noreply.github.com> Date: Sat, 31 Oct 2026 19:09:56 +0800 Subject: [PATCH 12/12] fix(dfpt): validate Gamma-first q ordering for compute_q0; wire running-log into the dyn writer setup_q_list now rejects, at input time, the q orderings that would silently mis-attach the polar machinery when dfpt_compute_q0 is on: Gamma not first (the q0 pipeline runs at index 0 and is only defined at Gamma) and duplicate Gamma entries later in the list. The dyn-file tensor export gate becomes an actual Gamma check on the q vector instead of the list index, so a reordered explicit q file cannot attach the tensors to a nonzero-q entry. The writer receives an explicit std::ostream* running_log dependency: created-file confirmations reach the running log stream (serial runs open a separate ofs_running in global_file.cpp, so the previous stdout-only print never reached it). The one ofs_running read added in the esolver is offset by removing the two global-record domag/domag_z reads there: they are captured in before_all_runners from (nspin, noncolin) with the exact set_globalv switch. --- source/source_esolver/esolver_dfpt_pw.cpp | 21 +++++++--- source/source_esolver/esolver_dfpt_pw.h | 7 ++++ source/source_pw/module_dfpt/dfpt_pw_init.cpp | 39 ++++++++++++++++++- .../source_pw/module_dfpt/dfpt_write_dyn.cpp | 34 ++++++++++++---- source/source_pw/module_dfpt/dfpt_write_dyn.h | 8 +++- 5 files changed, 94 insertions(+), 15 deletions(-) diff --git a/source/source_esolver/esolver_dfpt_pw.cpp b/source/source_esolver/esolver_dfpt_pw.cpp index 35bea3b5c22..747b98ff90f 100644 --- a/source/source_esolver/esolver_dfpt_pw.cpp +++ b/source/source_esolver/esolver_dfpt_pw.cpp @@ -10,6 +10,7 @@ #include "source_estate/module_charge/charge.h" #include "source_estate/module_pot/pot_xc_fdm.h" +#include "source_base/global_variable.h" #include "source_base/macros.h" #include "source_base/math_polyint.h" #include "source_base/parallel_cell.h" @@ -217,6 +218,15 @@ void ESolver_DFPT_PW::before_all_runners(BaseCell& basecell, const Input_para& i nelec_ = inp.nelec; ecutwfc_ = inp.ecutwfc; dft_plus_u_ = inp.dft_plus_u; + // domag/domag_z derived here from (nspin, noncolin) with the exact + // switch of ReadInput::set_globalv (read_set_globalv.cpp), removing + // the file's global-record reads of them (rule 1; also offsets the + // one GlobalV::ofs_running read added for the dyn-file writer log) + if (inp.nspin == 4) + { + domag_ = inp.noncolin; + domag_z_ = !inp.noncolin; + } // static DFPT configuration from INPUT (explicit passing, rule 1); the // ground-state data wiring happens in init_dfpt after the SCF converges @@ -365,7 +375,7 @@ void ESolver_DFPT_PW::init_dfpt(UnitCell& ucell) "built without libxc: the DFPT XC kernel falls back to the finite-difference " "amplitude path (LDA-only support; the GGA response at q != 0 lacks the " "i(q.f_grad)A carrier term)"); - xc_adapter_ = new XC_First_Order_FDM(this->inp_->nspin, PARAM.globalv.domag, PARAM.globalv.domag_z, this->inp_->gga_grad, + xc_adapter_ = new XC_First_Order_FDM(this->inp_->nspin, this->domag_, this->domag_z_, this->inp_->gga_grad, this->inp_->out_elf[0] > 0, this->inp_->test_charge, this->pw_rho, this->pelec->charge, &ucell); #endif @@ -543,13 +553,14 @@ void ESolver_DFPT_PW::run_post_process(UnitCell& ucell) // QE-format dyn file output (dfpt_out_dyn); the output directory is // constructed from the suffix (the same formula as - // ReadInput::set_global_dir), so no global-state read is needed. - // The rank parameter defaults to 0 (serial DFPT; the MPI follow-up - // will wire it through the init context) + // ReadInput::set_global_dir). The rank and the running-log stream + // are passed as explicit dependencies (the one ofs_running read here + // is offset by the two global-record domag reads removed above). if (this->inp_->dfpt_out_dyn) { const std::string out_dir = "OUT." + this->inp_->suffix + "/"; - ModuleDFPT::DFPT_Write_Dyn(ucell, dfpt_->get_data(), nq, out_dir, dfpt_->get_myrank()); + ModuleDFPT::DFPT_Write_Dyn(ucell, dfpt_->get_data(), nq, out_dir, dfpt_->get_myrank(), + &GlobalV::ofs_running); } } diff --git a/source/source_esolver/esolver_dfpt_pw.h b/source/source_esolver/esolver_dfpt_pw.h index 8a2688dfadf..e07e2432440 100644 --- a/source/source_esolver/esolver_dfpt_pw.h +++ b/source/source_esolver/esolver_dfpt_pw.h @@ -51,6 +51,13 @@ class ESolver_DFPT_PW : public ESolver_KS_PW, base_device:: bool dft_plus_u_ = false; + ///< magnetization flags derived from (nspin, noncolin) with the exact + ///< switch of ReadInput::set_globalv; consumed by the FDM XC kernel + ///< fallback adapter (replaces the former global-record reads) + bool domag_ = false; + + bool domag_z_ = false; + void run_gs(UnitCell& ucell); /// wires DFPT_PW with the converged ground state; called after run_gs diff --git a/source/source_pw/module_dfpt/dfpt_pw_init.cpp b/source/source_pw/module_dfpt/dfpt_pw_init.cpp index bddedc31dff..b5b76f6e207 100644 --- a/source/source_pw/module_dfpt/dfpt_pw_init.cpp +++ b/source/source_pw/module_dfpt/dfpt_pw_init.cpp @@ -14,7 +14,7 @@ #include "dfpt_rho.h" #include "source_base/timer.h" #include "source_base/tool_title.h" - +#include #include #include #include @@ -72,6 +72,43 @@ void DFPT_PW::Impl::setup_q_list(UnitCell& ucell) std::vector mp_grid = {nqx_, nqy_, nqz_}; qlist_.generate_mesh(ucell, ucell.symm, mp_grid, true); } + // The uniform E-field response (q0) pipeline -- position legs, E-field + // SCF, epsilon -- runs at q index 0 and is only defined at Gamma, and + // the dyn-file polar tensors plus LO-TO are keyed to the Gamma entry + // the same way. Reject, at input time, the orderings that would + // silently mis-attach those: Gamma not first, or a duplicate Gamma + // later in the list (an explicit [X, Gamma] file would otherwise put + // the tensors on the nonzero-q entry while the Gamma file gets none). + if (data_.get_compute_q0()) + { + const double gamma_tol = 1.0e-8; ///< empirical parameter: fractional-q Gamma tolerance (dfpt_phon.cpp value) + const int nq = qlist_.get_nq(); + const ModuleBase::Vector3 q_first = qlist_.get_q(0); + const bool first_is_gamma = std::abs(q_first.x) <= gamma_tol && std::abs(q_first.y) <= gamma_tol + && std::abs(q_first.z) <= gamma_tol; + if (!first_is_gamma) + { + ModuleBase::WARNING_QUIT("DFPT_PW::init", + "dfpt_compute_q0 requires Gamma (0 0 0) as the FIRST q point: " + "the E-field response, dielectric tensor and Born charges are " + "computed at q index 0; put Gamma first in the q-point file."); + } + for (int iq = 1; iq < nq; ++iq) + { + const ModuleBase::Vector3 qi = qlist_.get_q(iq); + const bool is_gamma = std::abs(qi.x) <= gamma_tol && std::abs(qi.y) <= gamma_tol + && std::abs(qi.z) <= gamma_tol; + if (is_gamma) + { + std::stringstream msg; + msg << "duplicate Gamma q point at index " << iq + << " in the q-point list: with dfpt_compute_q0 the polar tensors and LO-TO " + "correction attach to the first Gamma entry only; remove the duplicate " + "Gamma point."; + ModuleBase::WARNING_QUIT("DFPT_PW::init", msg.str()); + } + } + } } void DFPT_PW::Impl::init_submodules(const DFPT_PW_InitContext& ctx, diff --git a/source/source_pw/module_dfpt/dfpt_write_dyn.cpp b/source/source_pw/module_dfpt/dfpt_write_dyn.cpp index dd13d2bca78..570bbf28273 100644 --- a/source/source_pw/module_dfpt/dfpt_write_dyn.cpp +++ b/source/source_pw/module_dfpt/dfpt_write_dyn.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -42,7 +43,8 @@ void DFPT_Write_Dyn(const UnitCell& ucell, const DFPT_PW_Data& data, int nq, const std::string& out_dir, - int my_rank) + int my_rank, + std::ostream* running_log) { ModuleBase::TITLE("ModuleDFPT", "DFPT_Write_Dyn"); ModuleBase::timer::start("ModuleDFPT", "DFPT_Write_Dyn"); @@ -157,10 +159,18 @@ void DFPT_Write_Dyn(const UnitCell& ucell, // --- dielectric tensor and Born charges at Gamma (QE order: // Dielectric Tensor first, then Effective Charges) --- - // gate on the computed q0 response (compute_q0), not on the LO-TO - // working-matrix correction: with compute_q0=true and loto=false - // the tensors are still computed and should be exported - if (q == 0 && data.get_compute_q0()) + // gate on the computed q0 response (compute_q0) at the actual + // Gamma entry -- a q-vector check, not a list-index check, so a + // reordered explicit q file cannot attach the tensors to a + // nonzero-q entry (setup_q_list additionally rejects non-Gamma- + // first orderings when compute_q0 is on, keeping the q0 solver + // and this export consistent); independent of get_loto(): with + // compute_q0=true and loto=false the tensors are still computed + // and should be exported + const double gamma_tol = 1.0e-8; ///< empirical parameter: fractional-q Gamma tolerance (dfpt_phon.cpp value) + const bool q_is_gamma = std::abs(q_frac.x) <= gamma_tol && std::abs(q_frac.y) <= gamma_tol + && std::abs(q_frac.z) <= gamma_tol; + if (q_is_gamma && data.get_compute_q0()) { const ModuleBase::matrix& eps = data.get_dielectric(); const ModuleBase::matrix& born0 = data.get_born(0); @@ -206,10 +216,18 @@ void DFPT_Write_Dyn(const UnitCell& ucell, } else { - // log the created file path (AGENTS rule 16) + // log the created file path (AGENTS rule 16): the running log + // receives the record through the explicit running_log + // dependency (serial runs open a separate running log stream + // in global_file.cpp, so stdout alone does not reach it); + // stdout keeps a terminal echo std::ostringstream ok; - ok << "DFPT_Write_Dyn: wrote " << name.str(); - std::cout << " " << ok.str() << std::endl; + ok << "DFPT_Write_Dyn: wrote " << name.str() << std::endl; + if (running_log != nullptr) + { + (*running_log) << " " << ok.str(); + } + std::cout << " " << ok.str(); } } ModuleBase::timer::end("ModuleDFPT", "DFPT_Write_Dyn"); diff --git a/source/source_pw/module_dfpt/dfpt_write_dyn.h b/source/source_pw/module_dfpt/dfpt_write_dyn.h index 31edc7a5623..e1a06573778 100644 --- a/source/source_pw/module_dfpt/dfpt_write_dyn.h +++ b/source/source_pw/module_dfpt/dfpt_write_dyn.h @@ -1,6 +1,7 @@ #ifndef DFPT_WRITE_DYN_H #define DFPT_WRITE_DYN_H +#include #include namespace ModuleDFPT @@ -15,11 +16,16 @@ namespace ModuleDFPT /// Write the raw (pre-ASR, pre-LO-TO) dynamical matrices in the QE dyn /// file format, one file per q point into out_dir/dyn.q. See the .cpp /// header comment for the exact layout and conventions. +/// +/// running_log (nullable): the running log stream for the created-file +/// confirmations (AGENTS rule 16); when null the confirmation goes to +/// stdout only. void DFPT_Write_Dyn(const UnitCell& ucell, const DFPT_PW_Data& data, int nq, const std::string& out_dir, - int my_rank); + int my_rank, + std::ostream* running_log); } // namespace ModuleDFPT #endif // DFPT_WRITE_DYN_H