Skip to content

feat(dfpt): acoustic sum rule enforcement (dfpt_asr = no | simple | crystal) - #8182

Merged
mohanchen merged 6 commits into
deepmodeling:developfrom
Zanthoxylum:dfpt-asr
Oct 21, 2026
Merged

mohanchen merged 6 commits into
deepmodeling:developfrom
Zanthoxylum:dfpt-asr

Conversation

@Zanthoxylum

Copy link
Copy Markdown
Collaborator

Summary

Unconstrained linear-response self-consistency violates the translational
acoustic sum rule on the Gamma dynamical matrix, and the Born effective
charges violate sum_a Z*_a = 0 similarly. This PR adds the standard
post-processing remedy as a new INPUT parameter dfpt_asr (default no),
applied to the RAW Gamma matrix before the LO-TO nonanalytic term, together
with the Z* sum rule, so the LO-TO term consumes the corrected charges (the
QE dynmat set_asr order). Two modes:

  • simple — the legacy fix translated to our mass-reduced storage:
    D(a,a) = -sum_{b!=a} sqrt(m_b/m_a) D(a,b).
  • crystal — the optimized minimal-norm fix: the symmetric projection
    D -> (1-P) D (1-P) with the orthonormal translation vectors
    w_i(3a+alpha) = sqrt(m_a) delta(alpha,i)/sqrt(sum m); zeroes every
    physical row AND column sum exactly, preserves Hermiticity and keeps
    symmetry-equivalent atoms equivalent.

Diagnostics: per-stage audit lines (raw / asr / loto) print per-atom
PHYSICAL row sums (sum_jb sqrt(m_jb) D) with the worst relative to the
matrix scale, plus an explicit correction report (worst physical rowsum before -> after | max |sum_a Z*_a| -> 0).

Tests (MODULE_DFPT_phon_serial 12 -> 17): simple and crystal zero the
physical row sums; crystal keeps Hermiticity, maps the acoustic triplet to
machine zero, and keeps symmetry-equivalent atoms equivalent (mass-equal
species); Born charges shift by the per-component mean; regression: unequal
masses + violating matrix + eps and unbalanced Z* -> after crystal + LO-TO
the acoustic triplet stays at zero.

Print the per-atom row-sum norm of the Gamma dynamical matrix right
after the raw diagonalization and, when the LO-TO correction runs,
again on the corrected matrix (the nonanalytic term carries its own
Z* sum rule, so the two violations are audited separately). This is
the diagnostics feeder for the acoustic-branch investigation: the
existing check_sum_rule helper only returns a bool against a 1e-6
relative tolerance and is not wired anywhere, while the observed
Gamma acoustic modes are off by tens to hundreds of cm^-1 across all
ten validation systems.
New INPUT parameter dfpt_asr = no (default) | simple | crystal, applied to
the RAW Gamma dynamical matrix before the LO-TO nonanalytic term, together
with the Z* sum rule sum_a Z*_a = 0 on the Born effective charges (the
LO-TO term then consumes the corrected charges, QE dynmat set_asr order).

Unconstrained linear-response self-consistency violates both sum rules at
the percent to tens-of-percent level -- measured on ten validation
systems (raw worst rowsum rel 1.8e-3 .. 8.3e-1, invariant under a 100x
tighter convergence threshold), and QE ph.x raw output shows the same
behavior (0.09% .. 34%); the reference workflow post-processes with
dynmat asr='crystal', so this option aligns the treatment.

- simple: the legacy fix, each row's total is subtracted from its
  diagonal element, with a Hermitian re-symmetrization pass
- crystal: the optimized minimal-norm fix -- the symmetric projection
  D -> (1-P) D (1-P) with the orthonormal translations
  u_i(3a+alpha) = delta(alpha,i)/sqrt(nat); zeroes every row AND column
  sum exactly, preserves Hermiticity and keeps symmetry-equivalent atoms
  equivalent; the three acoustic eigenvalues vanish to machine precision
- an explicit report line prints the worst rowsum before -> after (and
  the |sum Z*| correction); run_assemble audits raw / asr / loto stages
  separately

Tests (MODULE_DFPT_phon_serial 12 -> 16): simple and crystal zero the
row sums; crystal keeps Hermiticity, maps the acoustic triplet to zero
(sqrt-scaled residual ~1e-5 cm^-1 from a ~1e-17 matrix residual), keeps
equivalent atoms equivalent, and shifts each Born charge by the
per-component mean.
The first crystal/simple implementation enforced UNWEIGHTED row sums on
the stored mass-REDUCED matrix (D = Phi/sqrt(m_a m_b)), which projects
out the wrong direction (uniform mass-coordinate displacement instead
of uniform physical displacement). Consequence caught by the first
ten-system validation run: the pre-LO-TO acoustic modes zero out along
the spurious direction, but the LO-TO nonanalytic term -- which decouples
from the PHYSICAL acoustic subspace only through sum_a Z*_a = 0 -- mixes
back in and lifts one acoustic mode (bn_cubic Gamma: +36 cm^-1 along
(1,1,1) while QE stays at ~1e-5).

QE dynmat operates on the UNreduced matrix (dyndiag divides by the
sqrt(m_a m_b) only at diagonalization), so its unweighted rows are the
physical rule; the correct translation into our reduced storage carries
the sqrt(m) weights:

- crystal: project with the orthonormal physical translations
  w_i(3a+alpha) = sqrt(m_a) delta(alpha,i)/sqrt(sum m), i.e.
  D' = D - (sqrt(m_a)/M) S - (sqrt(m_b)/M) T + (sqrt(m_a m_b)/M^2) U
  with S/T/U the sqrt(m)-weighted single and double atom sums
- simple: D(a,a) = -sum_{b!=a} sqrt(m_b/m_a) D(a,b)
- the audit and the report line now print the PHYSICAL row sums
  sum_jb sqrt(m_jb) D(3a+da, jb), the quantity QE's rows sum to

Tests (phon serial 16 -> 17): row-sum assertions move to the physical
sums; the equivalent-atoms case runs with mass-equal species; new
regression AsrCrystalPlusLotoKeepsAcousticExactlyZero (masses 12/4,
violating matrix, eps 3I and unbalanced Z*): after crystal + LO-TO the
acoustic triplet stays at zero to 1e-4 cm^-1.
@Zanthoxylum
Zanthoxylum requested a review from mohanchen October 19, 2026 07:45
@Zanthoxylum Zanthoxylum added the DFPT Density Functional Perturbation Theory label Oct 19, 2026
…tru fixture

The CI MODULE_DFPT_pw_run_test (GCC 11 container) segfaulted inside the
first run() of the ASR branch while the same binary passed everywhere
locally (GCC 13): DFPTStruTestFixture::construct_ucell never populated
ucell.iat2it, so the audit's cosmetic species-label lookup
atoms[iat2it[ia]].label read an EMPTY vector -- undefined behavior that
GCC 13 layouts happened to survive and GCC 11 did not.

- audit_asr: print the atom index only; production code no longer
  touches iat2it (apply_asr's mass lookup still does, legitimately)
- the stru fixture now fills iat2it/iat2ia like any real cell
@mohanchen mohanchen added the Refactor Refactor ABACUS codes label Oct 20, 2026

@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

@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 review of f7c9bd39e2cd0166fd55a7664321fc0f6154d775 with independent verification: two correctness issues and two nonblocking suggestions inline. git diff --check passed; the trusted governance checker reported documentation warnings only. No build or runtime tests were run; CI diagnostics were unavailable.

Non-blocking: Please add the exact commands, configuration, and observed results behind the test claims (or explicitly mark them not run), plus a short note that the INPUT pages are generated from the updated C++ metadata by the existing documentation pipeline.

Comment thread source/source_pw/module_dfpt/dfpt_phon.cpp
Comment thread source/source_pw/module_dfpt/dfpt_phon.cpp Outdated
Comment thread source/source_pw/module_dfpt/dfpt_phon.h Outdated
Comment thread source/source_pw/module_dfpt/dfpt_pw_run.cpp
[P2: directional cancellation in the audit metric] The audit and the
apply_asr report summed over ALL columns (atoms AND Cartesian
directions) before taking the norm, so opposite x/y residuals could
cancel: the block [[1,-1,0],[-1,1,0],[0,0,0]] reported zero although
D*t_x != 0. Both the audit and the report now compute per-direction
residuals R(a,alpha,beta) = sum_b sqrt(m_b) D(3a+alpha, 3b+beta) at a
FIXED translation direction beta, then combine the norms. New
regression: the cancellation pattern must be detected.

[P2: Hermitian re-symmetrization breaks the simple correction] The
simple mode dumped the residual onto the onsite 3x3 blocks, which can
write complex values on the DIAGONAL (the j>i symmetrization loop
leaves the diagonal untouched), producing a non-Hermitian input to the
crystal projection -- whose result is then non-Hermitian, and the
shared re-symmetrization halves the correction back (the review's
+/-delta/2 example). Fix: (1) take the real part of the diagonal after
the onsite dump, making the input genuinely Hermitian before the
projection; (2) finish with the crystal projection, which now gives an
exact Hermitian matrix satisfying all three translation constraints.
New regression: cross-Cartesian coupling (atom0-x <-> atom1-y) with
unequal masses, checking translations exact + Hermiticity + real
diagonal.

Also addresses the two non-blocking notes:
- header doc: correct translation vectors to sqrt(m_a) delta/sqrt(M),
  describe simple as onsite-block correction + projection finish
- run_assemble: single eigensolve after any applicable correction
  (the raw audit does not consume frequencies)
@Zanthoxylum

Copy link
Copy Markdown
Collaborator Author

Both P2 findings are fixed in 64fcadf:
P2: directional cancellation — The audit and the apply_asr report now compute per-direction residuals R(a,α,β) = Σ_b √m_b D(3a+α, 3b+β) at a fixed translation direction β, then combine norms. The cancellation pattern [[1,-1,0],[-1,1,0]] is detected by the new AsrDirectionalCancellationDetected regression.
P2: Hermitian re-symmetrization breaks simple — Root cause: the onsite dump can write complex values on the diagonal (the j > i symmetrization loop leaves the diagonal untouched), producing a non-Hermitian input to the crystal projection whose result is then non-Hermitian; the shared re-symmetrization halves the correction back (the review's ±δ/2 example). Fix: (1) take the real part of the diagonal after the onsite dump, making the input genuinely Hermitian; (2) finish with the crystal projection, which now gives an exact Hermitian matrix satisfying all three translation constraints. New regression AsrSimpleCrossCartesianRegression checks translations exact + Hermiticity + real diagonal with cross-Cartesian coupling and unequal masses.
The two non-blocking notes are also addressed: the header doc now states the translation vectors as √m_a·δ(α,i)/√(Σm) and describes simple as an onsite-block correction with a projection finish; run_assemble performs a single eigensolve after any applicable correction instead of diagonalizing non-Gamma matrices twice.

… loop

When a pseudopotential with nonlinear core correction (nlcc) displaces,
its core charge density moves with the atom. The first-order density
drho must include this contribution in the XC channel (the core
Coulomb interaction is already absorbed in the ionic pseudopotential,
so no Hartree term). Previously compute_drho carried only the valence
u*du cross terms, leaving nlcc systems' response incomplete: bn_hex
Gamma soft-mode pair was 8.7x too imaginary versus QE (-259.7 vs
-29.8 cm^-1) while non-nlcc systems were unaffected.

New DFPT_Rho::compute_drhoc computes the q-shifted G-space amplitude

  d rho_core / d tau_dir (G+q)
    = -i * tpiba * (G+q)_dir * rho_core~(|G+q|) * exp(i (G+q) tau_iat)

where rho_core~ is the radial spherical-Bessel Fourier transform of
the core charge (evaluated per-|G+q| from the pseudopotential's
rho_atc radial data, the same integral the ground state's
non_linear_core_correction performs). Non-nlcc atoms give zero.

assemble_v_sc injects the core derivative on the XC input side only:
a_r += drhoc_r before xc_->apply; the Hartree above keeps the pure
valence density. solve_displacement computes drhoc once per
displacement (it is iteration-independent) alongside build_dv.

Verified: all existing DFPT serial suites pass (rho/pert/phon/q0/
xc_analytic 8/7/19/6/6); nlcc validation runs in flight.
@mohanchen
mohanchen merged commit 8918917 into deepmodeling:develop Oct 21, 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