Skip to content

feat(dfpt): QE-format dynamical matrix file output (dfpt_out_dyn) - #8240

Merged
mohanchen merged 17 commits into
deepmodeling:developfrom
Zanthoxylum:dfpt-out-dyn
Nov 1, 2026
Merged

mohanchen merged 17 commits into
deepmodeling:developfrom
Zanthoxylum:dfpt-out-dyn

Conversation

@Zanthoxylum

@Zanthoxylum Zanthoxylum commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

New INPUT switch dfpt_out_dyn (Boolean, default false): when enabled, the DFPT post-processing writes one QE-compatible dyn file per q point into the output directory (OUT.<suffix>/dyn.q<i>), containing the raw (pre-ASR, pre-LO-TO) unreduced force-constant matrix Φ = D·√(mₐm_b) in Ry/bohr². At Γ with dfpt_compute_q0, the dielectric tensor and Born effective charges are appended in the QE convention.

Motivation: the dynamical matrix is a fundamental input to electron-phonon coupling (EPC) calculations, phonon transport, and post-processing by external tools (phonopy, QE dynmat.x, etc.). ABACUS DFPT previously had no mechanism to export it — only frequencies were printed. This PR provides the QE-compatible dyn file output so downstream EPC workflows can consume ABACUS results directly.

Implementation:

  • The data layer stashes an untouched raw copy via a dedicated set_dynmat_raw called only at assembly time — the working copy is mutated in place by apply_asr and add_loto at Γ, but the raw snapshot survives for the writer
  • The writer (dfpt_write_dyn.cpp) matches QE's io_dyn_mat_old.f90 record contract: header with ntyp nat ibrav celcdm(1..6), Basis vectors in alat units, species mass × AMU_RY, atom τ in alat, q in cartesian 2π/alat via ucell.G, per-atom-pair 3×3 blocks (re + im pairs, unreduced), and at Γ the Effective Charges E-U: and Dielectric Tensor: markers
  • The MPI rank flows through the explicit dependency chain: PARAM.globalv.myrank → DFPT_PW_InitContext.myrank → Impl::myrank_ → writer parameter; only rank 0 writes (no GlobalV read inside the writer)
  • Error handling: open failures report the full path via ModuleBase::WARNING; close is checked; each written path is logged
  • Output directory constructed from inp_->suffix (same formula as ReadInput::set_global_dir), avoiding any global-state read in the post-processing

Files:

  • source/source_pw/module_dfpt/dfpt_write_dyn.{h,cpp} — the writer
  • source/source_pw/module_dfpt/dfpt_pw_data.{h,cpp} — raw dynmat stash
  • source/source_pw/module_dfpt/dfpt_pw.{h,cpp} — get_data() / get_myrank() accessors
  • source/source_io/module_parameter/read_inp_dfpt.cpp + input_parameter.h — the INPUT item

Unit Tests and/or Case Tests for my changes

  • DFPT serial suite 9/9; a write-read-back round-trip test is planned as follow-up once the format is confirmed stable

Verification: bn_cubic run with dfpt_out_dyn true produces OUT.autotest/dyn.q0 with the complete QE layout; head-to-head line comparison against QE's dyn.G confirms the record contract. The numerical values reflect whatever force constants the current code produces (the writer is a pure output layer with no physics dependency).

Governance: zero global-state references in the PR diff — the rank comes from ModuleBase::world_comm_domain().rank() (the guarded CommunicationDomain wrapper, same pattern as source_md), the output directory is constructed from inp_->suffix, and the writer receives both as parameters through DFPT_PW_InitContext.

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.<suffix>/dyn.q<i>) 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.
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).
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).
… references)

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.

@ABACUS-PR-BOT ABACUS-PR-BOT left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QE post-processing cannot read the exported files as written, and ASR/LO-TO updates can replace the raw matrix before serialization. Please correct the file contract and snapshot handling, together with the unit, tensor-output and rank-ownership issues below.

Reviewed 02690c86ede1d126db43814bf66b2e725995df2e against 64463eb3d896bb878d8efecdd8c10ab2e9484be7 through static source/analytic comparison with QE 6.8. No builds, ABACUS/QE reader runs, numerical probes or CI inspection were performed; the reported file-generation and serial-suite results were not independently verified.

Comment thread source/source_pw/module_dfpt/dfpt_pw.h Resolved
Format (bot deepmodeling#1, deepmodeling#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 deepmodeling#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 deepmodeling#9).

Output gating (bot deepmodeling#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 deepmodeling#7): only rank 0 writes; every rank participates in the call
and returns immediately on non-zero rank.

Error handling (bot deepmodeling#8): open failures report the full path and skip
that q; close is checked; each successfully written path is logged.

Style (bot deepmodeling#11,12,13): forward-declare DFPT_PW_Data in dfpt_pw.h,
timer labels use the function name, computed indices assigned to named
locals.
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).
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.

@ABACUS-PR-BOT ABACUS-PR-BOT left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The raw snapshot and rank gate are corrected. Lattice scaling and polar-record ordering still need fixes, detailed in their existing threads. This is a static review; no build or runtime tests were run.

Comment thread source/source_pw/module_dfpt/dfpt_pw.h Resolved
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.
- 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

@ABACUS-PR-BOT ABACUS-PR-BOT left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static follow-up at 493708f: lattice scaling, polar-record order, the explicit rank argument and guarded rank lookup address the earlier findings. The q-file ordering case and running-log clarification are discussed in the existing threads. No build or QE reader execution was run.

…ut-dyn

Conflict resolutions: keep both the myrank field (dyn output, guarded
CommunicationDomain wrapper) and the vxc_r field (nlcc diagonal term)
in DFPT_PW_InitContext; the wrapper passes myrank + nullptr for vxc_r;
the esolver uses the full context with both fields.
…ng-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.

@ABACUS-PR-BOT ABACUS-PR-BOT left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At 2ebcb17, unsupported q0 ordering is rejected before the response calculation, and successful dyn-file writes now reach the running log. I found no new correctness defect in the reviewed delta.

Verification was static: git diff --check and the full/delta governance checks returned 0 with warnings reviewed. No build, runtime, MPI or external-reader test was run; the author-reported results and current CI remain unverified.

Non-blocking: Please refresh the PR description. The code now adds one GlobalV::ofs_running read and removes two PARAM reads (net -1), while rank comes from world_comm_domain().rank(). The zero-global/no-postprocessing-global claims and the older PARAM rank route are stale.

… named locals

Extract the Gamma-first q validation into the free function
ModuleDFPT::validate_q_ordering (dfpt_pw_data) so it is unit-testable;
setup_q_list delegates to it. Add test_dfpt_write_dyn.cpp (rule-11
naming) with: Gamma-first acceptance, non-first-Gamma and duplicate-
Gamma rejection under compute_q0 with the diagnostic texts, arbitrary
ordering allowed when compute_q0 is off, Gamma-only polar records in
QE order with the raw snapshot preserved, injected-log-stream
confirmations, and the nonzero-rank write gate. Document the Gamma-
first-and-unique requirement on the dfpt_qfile / dfpt_compute_q0 INPUT
descriptions. Assign the two remaining rule-14 computed call
arguments (mass product, raw storage size) to named locals.

@mohanchen mohanchen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mohanchen
mohanchen merged commit 1cf64ff into deepmodeling:develop Nov 1, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DFPT Density Functional Perturbation Theory Refactor Refactor ABACUS codes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants