Skip to content

feat(librpa): add Delta-Sternheimer response solver - #6

Closed
AroundPeking wants to merge 1 commit into
abacus-librpa-strict2d-oct17from
abacus-librpa-delta-st-oct31
Closed

AroundPeking wants to merge 1 commit into
abacus-librpa-strict2d-oct17from
abacus-librpa-delta-st-oct31

Conversation

@AroundPeking

Copy link
Copy Markdown
Owner

Problem

After the strict 2D/3D Coulomb work (PR deepmodeling#8176), the LibRPA feature set of the
long-lived development branch is still missing the Delta-Sternheimer response
path. Users cannot request a Sternheimer-level response or the SIAB primitive
export that the RPA chi0 workflow relies on.

Change

  • Add source_lcao/module_rpa/module_delta_st: Sternheimer solvers, interface
    perturbation, FD and FD-nonlocal paths, chi0 MPI distribution, Coulomb
    whitening, SIAB primitive export, grid diagnostics and the solid LCAO chi0
    driver.
  • Register 22 INPUT parameters together with their reader blocks. Every new
    name keeps at most two underscores, so six of them are shortened relative to
    the development branch: sternheimer_siab_vthr, sternheimer_freq_file,
    sternheimer_ao_file, sternheimer_virt_src, sternheimer_virt_max,
    sternheimer_vnorm_tol.
  • Hook the LCAO SCF path so the occupied k-point records are gathered and the
    chi0 response is emitted.
  • Extend Numerical_Basis with the SIAB primitive API. Its inputs are passed
    through an explicit structure instead of reading the global parameter object.
  • Keep the external GreenX minimax adapter behind an ENABLE_GREENX_MINIMAX
    option that is off by default, so a stock build needs neither Fortran nor
    GreenX.

This PR is stacked on deepmodeling#8176 and should be merged after it.

Notes for reviewers

  • New INPUT names satisfy the two-underscore budget. sternheimer_molecular_coulomb
    is unchanged: it already has a single underscore and is used by existing runs.
  • Local verification: default configuration succeeds without Fortran or GreenX,
    all 40 module_delta_st translation units compile, and the governance checker
    reports no error-level finding.
  • The focused unit tests are registered in CTest but could not be executed on
    this macOS host (gtest/libc++ ABI mismatch), so their runtime result is left
    to Linux CI.

Port the Delta-Sternheimer machinery from the long-lived development
branch so the LibRPA feature set reaches parity after the strict 2D/3D
Coulomb work.

- add source_lcao/module_rpa/module_delta_st: the Sternheimer solvers,
  interface-perturbation and FD/FD-nonlocal paths, chi0 MPI distribution,
  Coulomb whitening, SIAB primitive export, grid diagnostics and the
  solid LCAO chi0 driver
- register 22 INPUT parameters and their reader blocks; names keep at
  most two underscores, so six of them are shortened relative to the
  development branch (siab_vthr, freq_file, ao_file, virt_src, virt_max,
  vnorm_tol)
- hook the LCAO SCF path to gather occupied k-point records and emit the
  chi0 response
- extend Numerical_Basis with the SIAB primitive API, taking its inputs
  through an explicit structure instead of reading the global parameter
  object
- keep the external GreenX minimax adapter behind an off-by-default
  ENABLE_GREENX_MINIMAX option so a stock build needs neither Fortran nor
  GreenX

Verified locally: default configuration succeeds without Fortran or
GreenX, all 40 module_delta_st translation units compile, and the
governance checker reports no error-level finding.
AroundPeking pushed a commit that referenced this pull request Nov 1, 2026
…epmodeling#8240)

* 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.<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.

* 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).

* 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).

* fix(dfpt): construct the output directory from the suffix (zero PARAM 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.

* fix(dfpt): remove the global-state mention from the comment

* fix(build): add dfpt_write_dyn.o to Makefile.Objects (legacy Makefile build)

* fix(dfpt): address all review findings on the dyn file writer

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 deepmodeling#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 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.

* 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).

* fix(dfpt): C++11 aggregate: no default member initializer on myrank

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.

* fix(dfpt): get the rank from the guarded CommunicationDomain wrapper

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.

* 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

* 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.

* test(dfpt): cover the Gamma-ordering and dyn-writer branches; rule-14 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant