Skip to content

Commit 9cdcbfd

Browse files
committed
Fix pot_hxc_gs: correct TDRPA@LDA (linear vxc=fxc[gs](T+Z) instead of vxc[T+Z])
1 parent 648cfb4 commit 9cdcbfd

6 files changed

Lines changed: 73 additions & 14 deletions

File tree

source/source_esolver/esolver_lr_lcao_tddft.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,13 @@ void ModuleESolver::ESolver_LR<T, TR>::init_pot(const Charge& chg_gs)
829829
{
830830
this->init_pot_groundstate(chg_gs);
831831
const std::string xc_kernel_gs = LR_Util::tolower(input.dft_functional);
832-
this->pot_hxc_gs = std::make_shared<LR::PotHxcLR>(xc_kernel_gs, *this->pw_rho, *this->ucell_, chg_gs, Pgrid, ST::S1, input.lr_init_xc_kernel);
832+
// `ST::S1` is only correct when nspin=1. `PotHxcLR` builds its `KernelXC` with
833+
// `PARAM.inp.nspin`, so at nspin=2 the kernel arrays carry 3 spin components per grid point
834+
// while the S1 integrand indexes them as if there were 1 -- it does not even read a
835+
// consistent spin combination. Use `ST::S2_gs` there, which is exactly half of S2_singlet,
836+
// matching the `K_Hxc(singlet) = 2 * pot_hxc_gs` convention of the gradient operators.
837+
const ST st_gs = (nspin == 1) ? ST::S1 : (openshell ? ST::S2_updown : ST::S2_gs);
838+
this->pot_hxc_gs = std::make_shared<LR::PotHxcLR>(xc_kernel_gs, *this->pw_rho, *this->ucell_, chg_gs, Pgrid, st_gs, input.lr_init_xc_kernel);
833839
}
834840
}
835841

source/source_lcao/module_lr/Grad/esolver_lr_grad.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ std::vector<ModuleBase::matrix> ModuleESolver::ESolver_LR<T, TR>::cal_force(cons
250250
ModuleIO::print_force(GlobalV::ofs_running, (*this->ucell_), "HXC DMTRANS FORCE (eV/Angstrom)", force_hxc_dmtrans, false);
251251

252252
const elecstate::DensityMatrix<T, double>& dm_gs = this->cal_dm_gs();
253-
ModuleBase::matrix force_hamiltgs_relaxed_diff = lr_force.cal_force_hamilt_gs_dm_relaxed_diff(relaxed_diff_dm_real, dm_gs);
253+
ModuleBase::matrix force_hamiltgs_relaxed_diff = lr_force.cal_force_hamilt_gs_dm_relaxed_diff(relaxed_diff_dm_real, dm_gs, false, this->pot_hxc_gs.get());
254254
if (PARAM.inp.test_force)
255255
ModuleIO::print_force(GlobalV::ofs_running, (*this->ucell_), "H_GS-(T+Z) FORCE (without EXX) (eV/Angstrom)", force_hamiltgs_relaxed_diff, false);
256256

source/source_lcao/module_lr/Grad/force/lr_force.cpp

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ namespace LR
5151
template<typename TK>
5252
ModuleBase::matrix LR_Force<TK>::cal_force_hamilt_gs_dm_relaxed_diff(const elecstate::DensityMatrix<TK, double>& relax_diff_dm,
5353
const elecstate::DensityMatrix<TK, double>& dm_gs,
54-
const bool reproduce_gs)
54+
const bool reproduce_gs,
55+
const PotHxcLR* pot_hxc_gs)
5556
{
5657
const bool with_ewald = reproduce_gs;
5758
const Charge chr_diff_relaxed = dm_to_charge(relax_diff_dm);
@@ -94,10 +95,39 @@ namespace LR
9495

9596
// 3.3 Hartree + xc (Hellmann-Feynman)
9697
ModuleBase::matrix fhxc_dvhxc(this->ucell_.nat, 3);
97-
elecstate::Potential pot_hxc_relaxed_diff = this->dm_to_hxc_potential(relax_diff_dm);
98-
//`cal_pulay_fs` calculates only one spin channel because `relax_diff_dm` has only one.
99-
PulayForceStress::cal_pulay_fs(1/*nspin*/, fhxc_dvhxc, stress_tmp,
100-
dm_gs, this->ucell_, &pot_hxc_relaxed_diff, true, false);
98+
// The potential here must be the *linear response* of $V^\text{Hxc}$ to the difference
99+
// density, i.e. $v_H[\rho^{T+Z}] + f_{xc}[\rho^\text{gs}]\,\rho^{T+Z}$ -- NOT
100+
// $v_\text{Hxc}[\rho^{T+Z}]$. This term is
101+
// $\sum_{\kappa\lambda}(T{+}D^Z)_{\kappa\lambda}\int\phi_\kappa\phi_\lambda\,
102+
// f_{xc}\sum_{\alpha\beta}D^\text{gs}_{\alpha\beta}(\phi_\alpha\phi_\beta)^x$,
103+
// the half of $\partial_x V^\text{Hxc}$ whose basis derivative falls on the *ground-state*
104+
// pair. Hartree is linear in the density so feeding it $\rho^{T+Z}$ happens to be right;
105+
// xc is not -- $\rho^{T+Z}$ is not even positive everywhere, while LDA has
106+
// $v_{xc}\propto-\rho^{1/3}$.
107+
//
108+
// `pot_hxc_gs` supplies exactly this object (Hartree weight 1, xc = $(f_{uu}+f_{ud})/2$ at
109+
// nspin=2), with no extra factor. Verified on H2/SZ TDRPA@LDA, where $K^T\equiv0$ makes the
110+
// triplet gradient identical to $d(\varepsilon_a-\varepsilon_i)/dx$: analytic 28.5982 vs the
111+
// KS-gap finite difference 28.598156. It used to be off by -3.5 eV/Ang.
112+
//
113+
// `reproduce_gs` is the exception: there `relax_diff_dm` *is* the ground-state density
114+
// matrix and the term being checked is the true ground-state force, for which
115+
// $v_\text{Hxc}[\rho^\text{gs}]$ is the correct potential.
116+
if (reproduce_gs || pot_hxc_gs == nullptr)
117+
{
118+
elecstate::Potential pot_hxc_relaxed_diff = this->dm_to_hxc_potential(relax_diff_dm);
119+
//`cal_pulay_fs` calculates only one spin channel because `relax_diff_dm` has only one.
120+
PulayForceStress::cal_pulay_fs(1/*nspin*/, fhxc_dvhxc, stress_tmp,
121+
dm_gs, this->ucell_, &pot_hxc_relaxed_diff, true, false);
122+
}
123+
else
124+
{
125+
ModuleBase::matrix v_lin(1, this->rhopw_.nrxx); // zero-initialized
126+
double* rho_in[1] = { const_cast<double*>(chr_diff_relaxed.rho[0]) };
127+
pot_hxc_gs->cal_v_eff(rho_in, this->ucell_, v_lin);
128+
std::vector<const double*> vr_eff = { v_lin.c };
129+
ModuleGint::cal_gint_fvl(1, vr_eff, dm_gs.get_DMR_vector(), true, false, &fhxc_dvhxc, &stress_tmp);
130+
}
101131
if(!reproduce_gs) {fhxc_dvhxc *= 2;} // for the two channels of the ground-state dm.
102132

103133
// 4. kinetic (Pulay)

source/source_lcao/module_lr/Grad/force/lr_force.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ namespace LR
3838

3939
/// 1. $Tr[H_{GS}^x * (T+D^Z)]$, where GS=groud state and $(T+D^Z)$ is the relaxed difference density matrix
4040
ModuleBase::matrix cal_force_hamilt_gs_dm_relaxed_diff(const elecstate::DensityMatrix<TK, double>& relaxed_diff_dm,
41-
const elecstate::DensityMatrix<TK, double>& dm_gs, const bool reproduce_gs = false);
41+
const elecstate::DensityMatrix<TK, double>& dm_gs, const bool reproduce_gs = false,
42+
const PotHxcLR* pot_hxc_gs = nullptr);
4243

4344
/// 2. $Tr[S^x * (EDM)]
4445
ModuleBase::matrix cal_force_overlap_edm(const elecstate::DensityMatrix<TK, double>& edm);

source/source_lcao/module_lr/potentials/pot_hxc_lrtd.cpp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ namespace LR
3131
// Hartree
3232
switch (this->spin_type_)
3333
{
34-
case SpinType::S1: case SpinType::S2_updown:
34+
case SpinType::S1: case SpinType::S2_updown: case SpinType::S2_gs:
3535
v_eff += elecstate::H_Hartree_pw::v_hartree(ucell, const_cast<ModulePW::PW_Basis*>(&this->rho_basis_), 1, rho);
3636
break;
3737
case SpinType::S2_singlet:
@@ -68,16 +68,21 @@ namespace LR
6868
};
6969
break;
7070
case SpinType::S2_singlet:
71-
funcs[s] = [this, &fxc](FXC_PARA_TYPE)->void
71+
case SpinType::S2_gs:
72+
{
73+
// S2_gs is exactly half of S2_singlet (see the SpinType doc in the header).
74+
const double prefac = (s == SpinType::S2_gs) ? 0.5 : 1.0;
75+
funcs[s] = [this, &fxc, prefac](FXC_PARA_TYPE)->void
7276
{
7377
for (int ir = 0;ir < nrxx;++ir)
7478
{
7579
const int irs0 = 3 * ir;
7680
const int irs1 = irs0 + 1;
77-
v_eff(0, ir) += ModuleBase::e2 * (fxc.v2rho2.at(irs0) + fxc.v2rho2.at(irs1)) * rho[ir];
81+
v_eff(0, ir) += ModuleBase::e2 * prefac * (fxc.v2rho2.at(irs0) + fxc.v2rho2.at(irs1)) * rho[ir];
7882
}
7983
};
8084
break;
85+
}
8186
case SpinType::S2_triplet:
8287
funcs[s] = [this, &fxc](FXC_PARA_TYPE)->void
8388
{
@@ -147,7 +152,12 @@ namespace LR
147152
};
148153
break;
149154
case SpinType::S2_singlet:
150-
funcs[s] = [this, &fxc](FXC_PARA_TYPE)-> void
155+
case SpinType::S2_gs:
156+
{
157+
// S2_gs is exactly half of S2_singlet; the whole expression is linear in the
158+
// kernel, so scaling the final axpy is enough.
159+
const double prefac = (s == SpinType::S2_gs) ? 0.5 : 1.0;
160+
funcs[s] = [this, &fxc, prefac](FXC_PARA_TYPE)-> void
151161
{
152162
std::vector<ModuleBase::Vector3<double>> drho(nrxx); // transition density gradient
153163
LR_Util::grad(rho, drho.data(), this->rho_basis_, this->tpiba_);
@@ -171,9 +181,10 @@ namespace LR
171181
vxc_tmp[ir] += rho[ir] * (fxc.v2rho2.at(ir * 3) + fxc.v2rho2.at(ir * 3 + 1))
172182
+ drho.at(ir) * fxc.v2rhosigma_drho_singlet.at(ir);
173183
}
174-
BlasConnector::axpy(nrxx, ModuleBase::e2, vxc_tmp.data(), 1, v_eff.c, 1);
184+
BlasConnector::axpy(nrxx, ModuleBase::e2 * prefac, vxc_tmp.data(), 1, v_eff.c, 1);
175185
};
176186
break;
187+
}
177188
case SpinType::S2_triplet:
178189
funcs[s] = [this, &fxc](FXC_PARA_TYPE)->void
179190
{

source/source_lcao/module_lr/potentials/pot_hxc_lrtd.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,18 @@ namespace LR
1414
/// S2_singlet: 2*K^Hartree + K^xc_{upup} + K^xc_{updown}
1515
/// S2_triplet: K^xc_{upup} - K^xc_{updown}
1616
/// S2_updown: K^Hartree + (K^xc_{upup}, K^xc_{updown}, K^xc_{downup} or K^xc_{downdown}), according to `ispin_op` (for spin-polarized systems)
17-
enum SpinType { S1 = 0, S2_singlet = 1, S2_triplet = 2, S2_updown = 3 };
17+
/// S2_gs: the nspin=2 counterpart of S1, i.e. the *ground-state* Hxc kernel
18+
/// K^Hartree + (K^xc_{upup} + K^xc_{updown})/2 = S2_singlet / 2.
19+
/// Used for `pot_hxc_gs` in LR gradients, where the convention is
20+
/// `K_Hxc(singlet) = 2 * pot_hxc_gs` (see `cal_multiplier_w_from_z.h`).
21+
/// The 1/2 on the xc part is not a convention but the chain rule: the derivative is
22+
/// taken w.r.t. the *total* density matrix, and $\partial v_u/\partial\rho =
23+
/// (f_{uu}+f_{ud})/2$ because $\rho_u=\rho_d=\rho/2$. The Hartree part needs no
24+
/// halving, which is exactly why S1 and S2_gs share the same Hartree weight.
25+
/// Do NOT use S1 here when nspin=2: `KernelXC` is built with `PARAM.inp.nspin`, so the
26+
/// kernel arrays carry 3 spin components per grid point while the S1 integrand indexes
27+
/// them as if there were 1.
28+
enum SpinType { S1 = 0, S2_singlet = 1, S2_triplet = 2, S2_updown = 3, S2_gs = 4 };
1829
/// XCType here is to determin the method of integration from kernel to potential, not the way calculating the kernel
1930
enum XCType { None = 0, LDA = 1, GGA = 2, HYB_GGA = 4 };
2031
/// constructor for exchange-correlation kernel

0 commit comments

Comments
 (0)