Skip to content

Commit a2174a7

Browse files
committed
DeltaP PW: per-atom gamma via Wilson loop eigenvalue decomposition
Replace the physically-wrong becp-weight partition with the correct Wilson loop eigenvalue decomposition (same algorithm as LCAO wannier): compute_per_atom_gamma_wilson(): 1. Build M_{nm} = <u_n(k0)|e^{iG·r}|u_m(k0)> (G-phase overlap) 2. Diagonalize via LAPACK zgeev → eigenvalues e^{iθ_n}, eigenvectors V 3. Project becp at k0 onto V: proj[α,n] = Σ_m V_{mn}* × becp(α,m,k0) 4. Weights: w[I,n] = Σ_{α∈I} |proj[α,n]|² 5. gamma[I] = Σ_n w[I,n] × θ_n / Σ_J w[J,n] This gives the band-resolved atomic polarization decomposition, consistent with the LCAO Wilson loop approach. Test (H2O Gamma-only): Old (becp-weight): γ/atom = (-0.146, -0.029, -0.028) sum=-0.20 New (Wilson): γ/atom = (4.311, 0.722, 1.047) sum=6.08 → -0.20 mod 2π O dominates (70.9%) as expected from projector count. BN LCAO regression passes.
1 parent d89800f commit a2174a7

2 files changed

Lines changed: 139 additions & 4 deletions

File tree

source/source_pw/module_pwdft/deltap_pw.cpp

Lines changed: 109 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
#include "source_cell/klist.h"
77
#include "source_cell/unitcell.h"
88
#include "source_pw/module_pwdft/onsite_proj.h"
9+
#include "source_io/module_unk/unk_overlap_pw.h"
910
#include "source_base/constants.h"
11+
#include "source_base/module_external/lapack_connector.h"
1012
#include <iomanip>
1113
#include <iostream>
1214

@@ -149,9 +151,9 @@ void deltap_iter_finish(
149151
if (gamma_total == 0.0)
150152
return;
151153

152-
// Compute per-atom gamma at lambda=0 (baseline)
154+
// Compute per-atom gamma via Wilson loop decomposition
153155
std::vector<double> gamma_baseline(nat, 0.0);
154-
compute_per_atom_gamma_from_becp(ucell, nocc, gamma_total, gamma_baseline);
156+
compute_per_atom_gamma_wilson(ucell, nocc, psi_cpu, wfcpw, rhopw, gdir, gamma_baseline);
155157

156158
double step = inp.deltap_lambda_step;
157159
double mixing = inp.deltap_lambda_mixing;
@@ -182,7 +184,7 @@ void deltap_iter_finish(
182184
for (int inner = 0; inner < inner_nmax; inner++)
183185
{
184186
std::vector<double> gamma_trial(nat, 0.0);
185-
compute_per_atom_gamma_from_becp(ucell, nocc, gamma_total, gamma_trial);
187+
compute_per_atom_gamma_wilson(ucell, nocc, psi_cpu, wfcpw, rhopw, gdir, gamma_trial);
186188

187189
double max_res_inner = 0.0;
188190
for (int iat = 0; iat < nat; iat++)
@@ -219,7 +221,7 @@ void deltap_iter_finish(
219221

220222
// Compute final max_res and per-atom gamma for output
221223
std::vector<double> gamma_final(nat, 0.0);
222-
compute_per_atom_gamma_from_becp(ucell, nocc, gamma_total, gamma_final);
224+
compute_per_atom_gamma_wilson(ucell, nocc, psi_cpu, wfcpw, rhopw, gdir, gamma_final);
223225
double max_res = 0.0;
224226
for (int iat = 0; iat < nat; iat++)
225227
{
@@ -307,4 +309,107 @@ void compute_per_atom_gamma_from_becp(
307309
gamma_per_atom[iat] = gamma_total * w[iat] / w_total;
308310
}
309311

312+
void compute_per_atom_gamma_wilson(
313+
const UnitCell& ucell,
314+
int nocc,
315+
const psi::Psi<std::complex<double>>* psi_cpu,
316+
const ModulePW::PW_Basis_K* wfcpw,
317+
const ModulePW::PW_Basis* rhopw,
318+
int gdir,
319+
std::vector<double>& gamma_per_atom)
320+
{
321+
int nat = ucell.nat;
322+
gamma_per_atom.assign(nat, 0.0);
323+
if (nocc < 1 || psi_cpu == nullptr || wfcpw == nullptr || rhopw == nullptr) return;
324+
325+
int nk = psi_cpu->get_nk();
326+
int npol = psi_cpu->get_npol();
327+
328+
// Build Wilson loop matrix M_{nm} = <u_n(k0)|e^{iG·r}|u_m(k0)>
329+
// For Gamma-only: single k-point, G-phase overlap gives the Berry phase matrix
330+
int m_dim = nocc;
331+
std::vector<std::complex<double>> M(m_dim * m_dim);
332+
unkOverlap_pw uw;
333+
334+
ModuleBase::Vector3<double> G(0.0, 0.0, 0.0);
335+
if (gdir == 1) G = ModuleBase::Vector3<double>(1.0, 0.0, 0.0);
336+
else if (gdir == 2) G = ModuleBase::Vector3<double>(0.0, 1.0, 0.0);
337+
else G = ModuleBase::Vector3<double>(0.0, 0.0, 1.0);
338+
339+
for (int nb = 0; nb < m_dim; nb++)
340+
for (int mb = 0; mb < m_dim; mb++)
341+
M[nb * m_dim + mb] = uw.unkdotp_G0(rhopw, wfcpw, 0, 0, nb, mb, psi_cpu, G);
342+
343+
// Diagonalize: M = V · diag(lambda) · V^{-1}
344+
// LAPACK zgeev: column-major layout (Fortran order)
345+
// M is n×n column-major: M(nb, mb) = M[nb * n + mb]
346+
// For right eigenvectors: M * V = V * lambda
347+
int info = 0;
348+
std::vector<std::complex<double>> eigenvalues(m_dim);
349+
std::vector<std::complex<double>> VR(m_dim * m_dim);
350+
std::vector<std::complex<double>> work(4 * m_dim);
351+
std::vector<double> rwork(2 * m_dim);
352+
353+
char jobvl = 'N';
354+
char jobvr = 'V';
355+
int lwork = 4 * m_dim;
356+
zgeev_(&jobvl, &jobvr, &m_dim, M.data(), &m_dim, eigenvalues.data(),
357+
nullptr, &m_dim, VR.data(), &m_dim,
358+
work.data(), &lwork, rwork.data(), &info);
359+
360+
if (info != 0) return;
361+
362+
std::vector<double> theta(m_dim);
363+
for (int n = 0; n < m_dim; n++)
364+
theta[n] = atan2(eigenvalues[n].imag(), eigenvalues[n].real());
365+
366+
// Get becp at k0 from OnsiteProjector
367+
auto* onsite_p = projectors::OnsiteProjector<double, base_device::DEVICE_CPU>::get_instance();
368+
if (onsite_p == nullptr) return;
369+
int tot_nproj = onsite_p->get_tot_nproj();
370+
if (tot_nproj == 0) return;
371+
const std::complex<double>* becp = onsite_p->get_becp();
372+
if (becp == nullptr) return;
373+
374+
// Project becp onto Wilson loop eigenvectors: proj[α, n] = Σ_m VR[m,n] × becp[α,m]
375+
// VR is column-major: VR(m, n) = VR[n * m_dim + m]
376+
std::vector<std::vector<double>> w_atom_band(nat, std::vector<double>(m_dim, 0.0));
377+
378+
int iproj = 0;
379+
for (int iat = 0; iat < nat; iat++)
380+
{
381+
int nh = onsite_p->get_nh(iat);
382+
for (int ip = 0; ip < nh; ip++)
383+
{
384+
for (int n = 0; n < m_dim; n++)
385+
{
386+
std::complex<double> proj_val(0.0, 0.0);
387+
for (int m = 0; m < m_dim; m++)
388+
{
389+
std::complex<double> vr_mn = VR[n * m_dim + m];
390+
std::complex<double> becp_val = becp[m * tot_nproj + iproj];
391+
proj_val += vr_mn * std::conj(becp_val);
392+
}
393+
double weight = proj_val.real() * proj_val.real() + proj_val.imag() * proj_val.imag();
394+
w_atom_band[iat][n] += weight;
395+
}
396+
iproj++;
397+
}
398+
}
399+
400+
// Normalize per-band weights and compute per-atom gamma
401+
double gamma_sum_check = 0.0;
402+
for (int n = 0; n < m_dim; n++)
403+
{
404+
double w_tot = 0.0;
405+
for (int iat = 0; iat < nat; iat++) w_tot += w_atom_band[iat][n];
406+
if (w_tot < 1e-30) continue;
407+
for (int iat = 0; iat < nat; iat++)
408+
gamma_per_atom[iat] += w_atom_band[iat][n] * theta[n] / w_tot;
409+
}
410+
for (int iat = 0; iat < nat; iat++) gamma_sum_check += gamma_per_atom[iat];
411+
double th_sum = 0; for (int n=0; n<m_dim; n++) th_sum += theta[n];
412+
std::cout << " [Wilson] Σγ=" << gamma_sum_check << " Σθ=" << th_sum << std::endl;
413+
}
414+
310415
} // namespace pw_deltap

source/source_pw/module_pwdft/deltap_pw.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,36 @@ void compute_per_atom_gamma_from_becp(
100100
double gamma_total,
101101
std::vector<double>& gamma_per_atom);
102102

103+
/**
104+
* @brief Compute per-atom gamma via Wilson loop eigenvalue decomposition.
105+
*
106+
* Builds the Wilson loop matrix M_{nm} = <u_n(k0)|e^{iG·r}|u_m(k0)>
107+
* (G-phase overlap at Gamma point), diagonalizes to get eigenvalues
108+
* e^{iθ_n} and eigenvectors V, then projects becp at k0 onto V:
109+
*
110+
* proj[α, n] = Σ_m V_{nm}^* × becp(α, m, k₀)
111+
* w[I] = Σ_{α∈I} |proj[α,n]|²
112+
* γ_I = Σ_n w[I,n] × θ_n / Σ_J w[J,n]
113+
*
114+
* This replaces the physically-wrong becp-weight partition.
115+
*
116+
* @param ucell Unit cell (for atom/projector mapping)
117+
* @param nocc Number of occupied bands
118+
* @param psi_cpu Wavefunctions
119+
* @param wfcpw PW basis for wavefunctions
120+
* @param rhopw PW basis for charge density (G-phase FFT)
121+
* @param gdir Direction (1=x,2=y,3=z)
122+
* @param gamma_per_atom Output: per-atom gamma [nat]
123+
*/
124+
void compute_per_atom_gamma_wilson(
125+
const UnitCell& ucell,
126+
int nocc,
127+
const psi::Psi<std::complex<double>>* psi_cpu,
128+
const ModulePW::PW_Basis_K* wfcpw,
129+
const ModulePW::PW_Basis* rhopw,
130+
int gdir,
131+
std::vector<double>& gamma_per_atom);
132+
103133
} // namespace pw_deltap
104134

105135
#endif // DELTAP_PW_H

0 commit comments

Comments
 (0)