Skip to content

Commit 6cefa36

Browse files
author
dyzheng
committed
Fix DMK->DMR inverse Fourier sign to e^{-ikR} and freeze DM conventions
- cal_DMR/cal_DMR_td/cal_DMR_full and LR dmr_complex: use e^{-ikR} as the inverse transform of folding_HR's e^{+ikR}, fixing the R pairing in the nspin=4 SOC nonlocal force - density_matrix.h: document DMK transposed convention (DMK = (C f C+)^T), embedded k weights (no 1/Nk), and closed-trace protection conditions - operator_fs_utils: replace stale 'factor of 2' comments; full R-set iteration uses factor 1.0 - output_mulliken: correct the M_y inline-comment algebra (code unchanged) - add docs/nao_lcao_force_stress_derivation.md (Sec. 3 derivation referenced by density_matrix.cpp comments) - unit tests: T1 Fourier round-trip (sign sentinel), T2 DMR Hermiticity, T3 closed-trace equivalence incl. non-symmetric-in-R operator, T8 full-direction pairing storage guard Verification: ctest -R 'ESTATE_dm|LCAO_operator' passes (13/14; the parallel_operator_tests.sh failure is a pre-existing build-dir artifact). Sensitivity check: flipping -sinp back to +sinp turns T1 red. Note: integrate-case reference results affected by the sign fix (e.g. SOC nonlocal force) are NOT regenerated in this commit and will follow.
1 parent d88b719 commit 6cefa36

12 files changed

Lines changed: 930 additions & 17 deletions

File tree

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# LCAO 力/应力中 DMR 配对约定推导(DM/DMK/DMR 链路)
2+
3+
- 日期:2026-08-09
4+
- 相关代码:`source/source_estate/module_dm/`(DMK/DMR)、
5+
`source/source_hamilt/module_hcontainer/func_folding.cpp`(folding_HR)、
6+
`source/source_lcao/module_operator_lcao/`(overlap/ekinetic/nonlocal 力与应力)
7+
- 配套文档:`docs/dm_dmk_dmr_investigation.md`(排查)、
8+
`docs/dm_dmk_dmr_action_plan.md`(整改与测试方案)
9+
10+
本文档锁定 DMK→DMR 的傅里叶符号约定,以及力/应力计算中实空间密度矩阵
11+
与两中心积分(含 nonlocal 的 R2−R1 配对)的收缩对应关系,作为代码注释
12+
`density_matrix.cpp` 中 "See Sec. 3 ...")所指的推导依据。
13+
14+
## 1. 指标与 R 格矢约定
15+
16+
- 波函数系数 `C_{μn}(k)`:μ = 轨道(行),n = 能带(列),存储为
17+
`psi::Psi``wfc(ib, iw)`
18+
- 密度矩阵元素 `D_{μν}`:μ = 行(bra)轨道,ν = 列(ket)轨道。
19+
- 格矢 R:`HContainer::AtomPair(iat1, iat2, R)` 的矩阵块为
20+
`O_{μν}(R)`,μ∈iat1(0 胞)、ν∈iat2(R 胞);格矢满足
21+
`dtau = τ(iat2) + R·L − τ(iat1)``UnitCell::cal_dtau`)。
22+
- k、R 均为**直接坐标**,相位 `k·R` 前乘 ``
23+
24+
## 2. k 空间与实空间傅里叶对
25+
26+
- 正变换(算符,`folding_HR``func_folding.cpp`):
27+
28+
```
29+
O(k) = Σ_R e^{+ikR} O(R)
30+
```
31+
32+
- 逆变换(密度矩阵,`DensityMatrix_Tools::cal_DMR*``density_matrix.cpp`):
33+
34+
```
35+
D(R) = Σ_k e^{-ikR} DMK(k)
36+
```
37+
38+
- 归一化:k 点权重 `w_k` 已内嵌在 `DMK``wg = w_k·occ`
39+
`occupy.cpp`),因此 `cal_DMR` ****再乘 `1/Nk`
40+
- `DMK(μ,ν;k) = Σ_n f_nk C*_{μn}(k) C_{νn}(k) = (C f C†)^T = D_std^T`
41+
`cal_dm_psi.cpp``zgemm('N','T')` + 先取共轭)。
42+
43+
## 3. 正逆变换互为逆(Sec. 3 约定)
44+
45+
对同一套 R 定义,`e^{+ikR}`(folding_HR)与 `e^{-ikR}`(cal_DMR)构成
46+
自洽的正逆傅里叶对。若把 `density_matrix.cpp` 中的 `-sinp` 改回 `+sinp`
47+
`D(R)` 实际存的是正变换 `Σ_k e^{+ikR} DMK(k)`,与 `folding_HR` 不再
48+
互为逆变换,非对角/非对称消费方(如 nspin=4 nonlocal 力)即出错。
49+
该符号由单元测试 `T1_fourier_round_trip` 锁定(见
50+
`docs/dm_dmk_dmr_action_plan.md` §T1)。
51+
52+
## 4. 力/应力的一般形式
53+
54+
Feynman–Hellmann 型受力(overlap/ekinetic/nonlocal 等):
55+
56+
```
57+
F = Σ_k Tr[ D(k) · ∂O(k)/∂τ ]
58+
= Σ_k Σ_{μν} D_{μν}(k) ∂O_{νμ}(k)/∂τ
59+
```
60+
61+
代入 `∂O(k) = Σ_R e^{+ikR} ∂O(R)`
62+
63+
```
64+
F = Σ_R Σ_{μν} ∂O_{νμ}(R) D_{μν}(−R) (*)
65+
其中 D(−R) := Σ_k e^{+ikR} DMK(k)
66+
```
67+
68+
代码存储的是 `D(R) = Σ_k e^{-ikR} DMK(k)`,两者通过 §5 的闭合迹论证等价。
69+
70+
## 5. 闭合迹共轭保护(Sec. 5 配对)
71+
72+
代码在两中心积分路径中,对每个 `(iat1,iat2,R)` 同指标收缩:
73+
74+
```
75+
W_code = Σ_{μνR} D(μ,ν;R) ∂O(μ,ν;R)
76+
```
77+
78+
`D(R) = Σ_k e^{-ikR} DMK(k)` 代入:
79+
80+
```
81+
W_code = Σ_k Σ_{μν} DMK(μ,ν;k) [Σ_R e^{-ikR} ∂O(μ,ν;R)]
82+
= Σ_k Tr( DMK(k) · ∂O'(−k) )
83+
```
84+
85+
其中 `∂O'(−k) := Σ_R e^{-ikR} ∂O(R)` 是 folding 在 −k 处的取值。
86+
`∂O` 在 HContainer 中**全方向成对**(对每个 `(iat1,iat2,R)` 存在
87+
`(iat2,iat1,−R)`,且 `∂O(iat2,iat1,−R) = ∂O(iat1,iat2,R)†`),则
88+
`∂O'(k) = Σ_R e^{+ikR} ∂O(R)` 逐 k 厄米,`∂O'(−k) = ∂O'(k)†`。于是
89+
90+
```
91+
W_code = Σ_k w_k Tr( DMK(k) · ∂O'(k)† )
92+
= Σ_k w_k conj( Tr( DMK(k) ∂O'(k) ) )
93+
= Σ_k w_k Tr( DMK(k) ∂O'(k) ) [厄米矩阵对的迹为实数]
94+
= Σ_k w_k Re Tr( DMK(k) ∂O'(k) )
95+
```
96+
97+
**总和受共轭保护**,而非"逐项相等"。保护成立的三条件:
98+
99+
1. 收缩是**全轨道闭合 Frobenius 迹**(不按元素取用);
100+
2. 配对算符**逐 k 厄米**(HContainer 全方向成对存储);
101+
3. 只取**实部**
102+
103+
破坏任一条件的消费方即暴露于 O(1) 配对误差。
104+
105+
### 5.1 overlap / ekinetic(两中心积分路径)
106+
107+
`operator_fs_utils.hpp``cal_force_stress_2center`
108+
`(iat1,iat2,R)` 直接取 `D(μ,ν;R) ∂O(μ,ν;R)` 同指标收缩,且对
109+
`(iat2,iat1,−R)` 同样各计一次(全 R 集迭代),故
110+
`finalize_force_stress(force_factor = 1.0)`(无需因子 2)。
111+
112+
### 5.2 nonlocal(β 投影,R2−R1 配对)
113+
114+
`nonlocal_fs.cpp::cal_force_stress` 以中心原子 `iat0` 为基准枚举两个
115+
邻原子:`iat1`(胞 `R1`)、`iat2`(胞 `R2`),取用的 DMR 块是
116+
117+
```
118+
dmR->find_matrix(iat1, iat2, R2 − R1)
119+
```
120+
121+
即配对结构为 `R_vector = R2 − R1``nonlocal_fs.cpp`
122+
`R_index2 − R_index1`),不是简单同 R 闭合迹。该路径的受力公式为
123+
124+
```
125+
F(iat0) = Σ_{iat1,iat2} <∂β_{iat1,R1}/∂τ_iat0 | D_{iat1,iat2}(R2−R1) | β_{iat2,R2}>
126+
```
127+
128+
逐项按 `(iat1,iat2,R2−R1)` 取用复数 DMR 块(nspin=4 时由
129+
`cal_DMR_full` 提供,不丢虚部)。该路径**不满足** §5 的同 R 闭合迹
130+
保护条件,其正确性依赖:
131+
1. `cal_DMR_full` 采用 `e^{-ikR}`(与 `folding_HR``e^{+ikR}` 互逆);
132+
2. DMR 块在 `(iat1,iat2,R2−R1)` 上的取值与 β 投影的指标顺序一致;
133+
3. 数值上由 SOC 有限差分力测试锁定(见 `docs/dm_dmk_dmr_action_plan.md` §T4)。
134+
135+
若未来引入半集存储优化(只存 `(iat1,iat2,R)` 不存 `(iat2,iat1,−R)`),
136+
§5 的保护论证与 `force_factor=1.0` 约定同时失效,须恢复 factor=2 并
137+
重跑 T2/T3/T8 守卫。

source/source_estate/module_dm/cal_dm_psi.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ void cal_dm_psi(const Parallel_Orbitals* ParaV,
2424

2525
// dm = wfc.T * wg * wfc.conj()
2626
// dm[is](iw1,iw2) = \sum_{ib} wfc[is](ib,iw1).T * wg(is,ib) * wfc[is](ib,iw2).conj()
27+
// i.e. DMK(μ,ν) = \sum_ib wg_ib * C*_{μ,ib} * C_{ν,ib} = (C f C†)^T = D_std^T.
28+
// Consumers that read DMK elements explicitly by (μ,ν) must follow this
29+
// transposed convention, not treat DMK as C f C† itself.
2730

2831
for (int ik = 0; ik < wfc.get_nk(); ++ik)
2932
{

source/source_estate/module_dm/density_matrix.cpp

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,14 @@ void DensityMatrix_Tools::cal_DMR(
110110
for(int ik = 0; ik < dm._nk; ++ik)
111111
{
112112
if(ik_in >= 0 && ik_in != ik) { continue; }
113-
// cal k_phase
114-
// if TK==std::complex<double>, kphase is e^{ikR}
113+
// Inverse Fourier transform: D(R) = sum_k D(k) * exp(-i*k*R)
114+
// k-point weights are embedded in DMK, so there is no 1/Nk
115+
// prefactor. See Sec. 3 of nao_lcao_force_stress_derivation.md
115116
const ModuleBase::Vector3<double> dR(R_index[0], R_index[1], R_index[2]);
116117
const double arg = (dm._kvec_d[ik] * dR) * ModuleBase::TWO_PI;
117118
double sinp, cosp;
118119
ModuleBase::libm::sincos(arg, &sinp, &cosp);
119-
kphase_vec[ik][iR] = TK(cosp, sinp);
120+
kphase_vec[ik][iR] = TK(cosp, -sinp);
120121
}
121122
}
122123

@@ -265,13 +266,14 @@ void DensityMatrix_Tools::cal_DMR_td(
265266
for(int ik = 0; ik < dm._nk; ++ik)
266267
{
267268
if(ik_in >= 0 && ik_in != ik) { continue; }
268-
// cal k_phase
269-
// if TK==std::complex<double>, kphase is e^{ikR}
269+
// Inverse Fourier transform: D(R) = sum_k D(k) * exp(-i*k*R)
270+
// k-point weights are embedded in DMK, so there is no 1/Nk
271+
// prefactor. See Sec. 3 of nao_lcao_force_stress_derivation.md
270272
const ModuleBase::Vector3<double> dR(R_index[0], R_index[1], R_index[2]);
271273
const double arg = (dm._kvec_d[ik] * dR) * ModuleBase::TWO_PI;
272274
double sinp, cosp;
273275
ModuleBase::libm::sincos(arg, &sinp, &cosp);
274-
kphase_vec[ik][iR] = TK(cosp, sinp);
276+
kphase_vec[ik][iR] = TK(cosp, -sinp);
275277
if(PARAM.inp.td_stype==2)
276278
{
277279
//phase for hybrid gauge tddft
@@ -422,13 +424,14 @@ void DensityMatrix_Tools::cal_DMR_full(
422424
for(int ik = 0; ik < dm._nk; ++ik)
423425
{
424426
if(ik_in >= 0 && ik_in != ik) { continue; }
425-
// cal k_phase
426-
// if TK==std::complex<double>, kphase is e^{ikR}
427+
// Inverse Fourier transform: D(R) = sum_k D(k) * exp(-i*k*R)
428+
// Phase factor: exp(-i*k*R) = cos(k·R) - i*sin(k·R)
429+
// k-point weights are embedded in DMK, so there is no 1/Nk prefactor.
427430
const ModuleBase::Vector3<double> dR(R_index[0], R_index[1], R_index[2]);
428431
const double arg = (dm._kvec_d[ik] * dR) * ModuleBase::TWO_PI;
429432
double sinp, cosp;
430433
ModuleBase::libm::sincos(arg, &sinp, &cosp);
431-
kphase_vec[ik][iR] = TK(cosp, sinp);
434+
kphase_vec[ik][iR] = TK(cosp, -sinp);
432435
}
433436
}
434437

source/source_estate/module_dm/density_matrix.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,28 @@
99

1010
namespace elecstate
1111
{
12+
// ---------------------------------------------------------------------------
13+
// Density-matrix conventions (DMK/DMR), frozen by the DM/DMK/DMR chain.
14+
// See docs/dm_dmk_dmr_investigation.md and
15+
// docs/nao_lcao_force_stress_derivation.md for the full derivation.
16+
//
17+
// 1. DMK(μ,ν;k) = Σ_n f_nk C*_{μn}(k) C_{νn}(k) = (C f C†)^T = D_std^T.
18+
// μ = row (bra) orbital, ν = column (ket) orbital. Stored as the transpose
19+
// of the textbook D_std (cal_dm_psi.cpp); for Hermitian D this equals the
20+
// conjugate. Consumers that read DMK elements explicitly by (μ,ν) must
21+
// follow this convention.
22+
// 2. D(R) = Σ_k e^{-ikR} DMK(k), k·R in direct coordinates with 2π.
23+
// k-point weights w_k are embedded in DMK (wg = w_k*occ), so cal_DMR must
24+
// NOT multiply by 1/Nk again. The forward pair used by operators is
25+
// O(k) = Σ_R e^{+ikR} O(R) (folding_HR).
26+
// 3. Closed-trace protection: a contraction Σ_{μνR} D(μ,ν;R)·O(μ,ν;R) equals
27+
// Σ_k w_k·Re Tr(DMK(k)·O(k)) iff
28+
// (a) the contraction is a closed Frobenius trace over all orbitals,
29+
// (b) the paired operator is Hermitian per k (the HContainer stores
30+
// (iat1,iat2,R) and (iat2,iat1,-R) with O(-R) = O(R)†),
31+
// (c) only the real part is kept.
32+
// ---------------------------------------------------------------------------
33+
1234
/**
1335
* @brief DensityMatrix Class
1436
* <TK,TR> = <double,double> for Gamma-only calculation

source/source_estate/module_dm/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,6 @@ AddTest(
4646
${ABACUS_SOURCE_DIR}/source_hamilt/module_hcontainer/base_matrix.cpp
4747
${ABACUS_SOURCE_DIR}/source_hamilt/module_hcontainer/hcontainer.cpp
4848
${ABACUS_SOURCE_DIR}/source_hamilt/module_hcontainer/atom_pair.cpp
49+
${ABACUS_SOURCE_DIR}/source_hamilt/module_hcontainer/func_folding.cpp
4950
${ABACUS_SOURCE_DIR}/source_basis/module_ao/parallel_orbitals.cpp
5051
)

0 commit comments

Comments
 (0)