Skip to content

Commit d77b595

Browse files
committed
Add TDDFT velocity gauge force implementation and test
- Add cal_force() methods to TDEkinetic and TDNonlocal classes - Create td_ekinetic_force.hpp and td_nonlocal_force.hpp templates - Fix force consistency: use Nonlocal when |A| < 1e-10 - Add test case comparing DFT vs TDDFT(A=0) forces - Results: forces match exactly when A=0 (4.968 eV/A for CO) Test: CO molecule, PBE/DZP, forces verified consistent
1 parent e268057 commit d77b595

24 files changed

Lines changed: 10032 additions & 3 deletions

source/source_lcao/FORCE_STRESS.cpp

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,23 @@
2222
#include "source_lcao/module_operator_lcao/nonlocal.h"
2323
#include "source_lcao/module_operator_lcao/ekinetic.h"
2424
#include "source_lcao/module_operator_lcao/overlap.h"
25+
#include "source_lcao/module_operator_lcao/td_ekinetic_lcao.h"
26+
#include "source_lcao/module_operator_lcao/td_nonlocal_lcao.h"
27+
#include "source_lcao/module_rt/td_info.h"
2528
#include "source_lcao/pulay_fs.h"
26-
29+
void print_force(ModuleBase::matrix& m, std::string name, int nat)
30+
{
31+
for(int i = 0; i<nat; i++)
32+
{
33+
std::cout<< name <<std::endl;
34+
std::cout<<"nat: "<<i<<std::endl;
35+
for(int j = 0; j<3; j++)
36+
{
37+
std::cout<<m(i,j)<<" ";
38+
}
39+
std::cout<<std::endl;
40+
}
41+
}
2742

2843
// mohan add 2025-11-04
2944
template <>
@@ -111,6 +126,7 @@ void Force_Stress_LCAO<T>::getForceStress(UnitCell& ucell,
111126
ModuleBase::matrix fcc;
112127
ModuleBase::matrix fscc;
113128
ModuleBase::matrix fvnl_dalpha; // deepks
129+
ModuleBase::matrix fekinetic_td;
114130

115131
fvl_dphi.create(nat, 3); // must do it now, update it later, noted by zhengdy
116132

@@ -125,6 +141,7 @@ void Force_Stress_LCAO<T>::getForceStress(UnitCell& ucell,
125141
fcc.create(nat, 3); // force due to core correction
126142
fscc.create(nat, 3); // force due to self-consistent field
127143
fvnl_dalpha.create(nat, 3); // deepks
144+
fekinetic_td.create(nat, 3);
128145

129146
// calculate basic terms in Force, same method with PW base
130147
this->calForcePwPart(ucell, fvl_dvl, fewalds, fcc, fscc, pelec->f_en.etxc,
@@ -200,11 +217,31 @@ void Force_Stress_LCAO<T>::getForceStress(UnitCell& ucell,
200217
tmp_overlap.cal_force_stress(isforce, isstress, edmR, foverlap, soverlap);
201218

202219
// Calculate nonlocal force/stress (uses DM)
203-
hamilt::Nonlocal<hamilt::OperatorLCAO<T, double>> tmp_nonlocal(
220+
// When A=0, TDNonlocal should give same result as Nonlocal
221+
// Use Nonlocal when |A| is very small to ensure consistency
222+
const double A_norm = TD_info::cart_At.norm();
223+
if(PARAM.inp.td_stype == 1 && A_norm > 1e-10)
224+
{
225+
hamilt::TDNonlocal<hamilt::OperatorLCAO<std::complex<double>, double>> tmp_nonlocal(
226+
nullptr, kv.kvec_d, nullptr, &ucell, orb, &gd);
227+
tmp_nonlocal.cal_force(isforce, dmR, fvnl_dbeta);
228+
}
229+
else
230+
{
231+
hamilt::Nonlocal<hamilt::OperatorLCAO<T, double>> tmp_nonlocal(
204232
nullptr, kv.kvec_d, nullptr, &ucell, orb.cutoffs(), &gd,
205233
two_center_bundle.overlap_orb_beta.get());
206-
tmp_nonlocal.cal_force_stress(isforce, isstress, dmR, fvnl_dbeta, svnl_dbeta);
234+
tmp_nonlocal.cal_force_stress(isforce, isstress, dmR, fvnl_dbeta, svnl_dbeta);
235+
}
207236

237+
if(PARAM.inp.td_stype == 1)
238+
{
239+
hamilt::TDEkinetic<hamilt::OperatorLCAO<std::complex<double>, double>> tmp_td_ekinetic(
240+
nullptr, nullptr, &kv, &ucell, orb.cutoffs(), &gd,
241+
two_center_bundle.overlap_orb.get());
242+
tmp_td_ekinetic.cal_force(isforce, &pv, psi, pelec, fekinetic_td);
243+
}
244+
208245
// Switch back to spin channel 0
209246
if (PARAM.inp.nspin == 2)
210247
{
@@ -472,6 +509,22 @@ void Force_Stress_LCAO<T>::getForceStress(UnitCell& ucell,
472509
//--------------------------------
473510
if (isforce)
474511
{
512+
if(GlobalV::MY_RANK==0)
513+
{
514+
print_force(foverlap, "foverlap",nat);
515+
print_force(ftvnl_dphi, "ftvnl_dphi",nat);
516+
print_force(fvnl_dbeta, "fvnl_dbeta",nat);
517+
print_force(fvl_dphi, "fvl_dphi",nat);
518+
print_force(fvl_dvl, "fvl_dvl",nat);
519+
print_force(fewalds, "fewalds",nat);
520+
print_force(fcc, "fcc",nat);
521+
print_force(fscc, "fscc",nat);
522+
// if (PARAM.inp.esolver_type == "tddft")
523+
// {
524+
// print_force(fpothybrid, "fpothybrid",nat);
525+
// print_force(fefield_tddft, "fefield_tddft",nat);
526+
// }
527+
}
475528
//---------------------------------
476529
// sum all parts of force!
477530
//---------------------------------
@@ -517,6 +570,10 @@ void Force_Stress_LCAO<T>::getForceStress(UnitCell& ucell,
517570
if (PARAM.inp.esolver_type == "tddft")
518571
{
519572
fcs(iat, i) += fefield_tddft(iat, i);
573+
if(PARAM.inp.td_stype == 1)
574+
{
575+
fcs(iat, i) += fekinetic_td(iat, i);
576+
}
520577
}
521578
// Gate field force
522579
if (PARAM.inp.gate_flag)
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#pragma once
2+
#include "td_ekinetic_lcao.h"
3+
#include "source_base/parallel_reduce.h"
4+
#include "source_base/timer.h"
5+
#include "source_base/libm/libm.h"
6+
#include "source_estate/module_dm/cal_dm_psi.h"
7+
namespace hamilt
8+
{
9+
template <typename TK, typename TR>
10+
void TDEkinetic<OperatorLCAO<TK, TR>>::cal_force(
11+
const bool cal_force,
12+
const Parallel_Orbitals* paraV,
13+
const psi::Psi<std::complex<double>>* psi,
14+
const elecstate::ElecState* pelec,
15+
ModuleBase::matrix& force)
16+
{
17+
const int npol = ucell->get_npol();
18+
ModuleBase::Vector3<double> At = TD_info::cart_At;
19+
if (cal_force)
20+
{
21+
force.zero_out();
22+
}
23+
else return;
24+
// calculate dmr
25+
const int nspin0 = PARAM.inp.nspin;
26+
const int nspin_dm = std::map<int, int>({ {1,1},{2,2},{4,1} })[nspin0];
27+
elecstate::DensityMatrix<std::complex<double>, double> tmp_dm(paraV, nspin_dm, kv->kvec_d, kv->get_nks() / nspin_dm);
28+
elecstate::cal_dm_psi(paraV, pelec->wg, psi[0], tmp_dm);
29+
tmp_dm.init_DMR(Grid, ucell);
30+
tmp_dm.cal_DMR();
31+
if (PARAM.inp.nspin == 2)
32+
{
33+
tmp_dm.switch_dmr(1);
34+
}
35+
// Convert to complex HContainer for TDDFT velocity gauge
36+
hamilt::HContainer<std::complex<double>> dmR_complex(paraV);
37+
tmp_dm.cal_DMR_full(&dmR_complex);
38+
const hamilt::HContainer<std::complex<double>>* dmR = &dmR_complex;
39+
// Loop over all atom pairs and calculate force contributions
40+
#pragma omp parallel
41+
{
42+
ModuleBase::matrix force_local(force.nr, force.nc);
43+
44+
#pragma omp for schedule(dynamic)
45+
for (int iat1 = 0; iat1 < ucell->nat; iat1++)
46+
{
47+
auto tau1 = ucell->get_tau(iat1);
48+
int T1 = 0, I1 = 0;
49+
ucell->iat2iait(iat1, &I1, &T1);
50+
Atom& atom1 = ucell->atoms[T1];
51+
52+
// Find adjacent atoms
53+
AdjacentAtomInfo adjs;
54+
Grid->Find_atom(*ucell, tau1, T1, I1, &adjs);
55+
56+
for (int ad = 0; ad < adjs.adj_num + 1; ++ad)
57+
{
58+
const int T2 = adjs.ntype[ad];
59+
const int I2 = adjs.natom[ad];
60+
const int iat2 = ucell->itia2iat(T2, I2);
61+
const ModuleBase::Vector3<int>& R_index = adjs.box[ad];
62+
63+
// Check cutoff
64+
ModuleBase::Vector3<double> dtau = ucell->cal_dtau(iat1, iat2, R_index);
65+
if (dtau.norm() * ucell->lat0 >= orb_cutoff_[T1] + orb_cutoff_[T2])
66+
{
67+
continue;
68+
}
69+
70+
// Find density matrix for this atom pair
71+
const hamilt::BaseMatrix<std::complex<double>>* dm_matrix = dmR->find_matrix(iat1, iat2, R_index[0], R_index[1], R_index[2]);
72+
if (dm_matrix == nullptr)
73+
{
74+
continue;
75+
}
76+
77+
// Calculate force for this atom pair
78+
double* force_tmp1 = (cal_force) ? &force_local(iat1, 0) : nullptr;
79+
double* force_tmp2 = (cal_force) ? &force_local(iat2, 0) : nullptr;
80+
81+
Atom& atom2 = ucell->atoms[T2];
82+
auto row_indexes = paraV->get_indexes_row(iat1);
83+
auto col_indexes = paraV->get_indexes_col(iat2);
84+
85+
if (row_indexes.size() == 0 || col_indexes.size() == 0)
86+
{
87+
continue;
88+
}
89+
const std::complex<double>* dm_pointer = dm_matrix->get_pointer();
90+
double overlap = 0;
91+
double grad[3] = {0, 0, 0};
92+
double hess[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
93+
94+
// step_trace = 0 for npol=1; ={0, 1, col_size, col_size+1} for npol=2
95+
std::vector<int> step_trace(npol * npol, 0);
96+
if (npol == 2)
97+
{
98+
step_trace[1] = 1;
99+
step_trace[2] = col_indexes.size();
100+
step_trace[3] = col_indexes.size() + 1;
101+
}
102+
103+
// Loop over orbital pairs
104+
for (int iw1l = 0; iw1l < row_indexes.size(); iw1l += npol)
105+
{
106+
const int iw1 = row_indexes[iw1l] / npol;
107+
const int L1 = atom1.iw2l[iw1];
108+
const int N1 = atom1.iw2n[iw1];
109+
const int m1 = atom1.iw2m[iw1];
110+
const int M1 = (m1 % 2 == 0) ? -m1 / 2 : (m1 + 1) / 2;
111+
112+
for (int iw2l = 0; iw2l < col_indexes.size(); iw2l += npol)
113+
{
114+
const int iw2 = col_indexes[iw2l] / npol;
115+
const int L2 = atom2.iw2l[iw2];
116+
const int N2 = atom2.iw2n[iw2];
117+
const int m2 = atom2.iw2m[iw2];
118+
const int M2 = (m2 % 2 == 0) ? -m2 / 2 : (m2 + 1) / 2;
119+
120+
// Calculate integral and its gradient using provided functor
121+
intor_->calculate(T1, L1, N1, M1, T2, L2, N2, M2, dtau * this->ucell->lat0, &overlap, grad, hess);
122+
123+
// Calculate force contribution with compile-time sign
124+
if (cal_force)
125+
{
126+
// Factor of 2 for Hermitian matrix will be applied later
127+
for (int i = 0; i < 3; i++)
128+
{
129+
// force_tmp1[i] += 2 * dm_pointer[0] * grad[i] * At * At;
130+
force_tmp2[i] -= dm_pointer[0].real() * grad[i] * At * At;
131+
for (int j = 0; j < 3; j++)
132+
{
133+
force_tmp2[i] -= 2 * dm_pointer[0].imag() * hess[j * 3 + i] * At[j] * At[j];
134+
}
135+
}
136+
}
137+
dm_pointer += npol;
138+
}
139+
dm_pointer += (npol - 1) * col_indexes.size();
140+
}
141+
}
142+
}
143+
144+
#pragma omp critical
145+
{
146+
if (cal_force)
147+
{
148+
force += force_local;
149+
}
150+
}
151+
}
152+
153+
// Finalize with MPI reduction and post-processing
154+
if (cal_force)
155+
{
156+
#ifdef __MPI
157+
Parallel_Reduce::reduce_all(force.c, force.nr * force.nc);
158+
#endif
159+
}
160+
}
161+
}// namespace hamilt

source/source_lcao/module_operator_lcao/td_ekinetic_lcao.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "source_lcao/center2_orb-orb11.h"
1111
#include "source_lcao/spar_hsr.h"
1212
#include "source_lcao/module_hcontainer/hcontainer_funcs.h"
13+
#include "td_ekinetic_force.hpp"
1314

1415
namespace hamilt
1516
{
@@ -27,6 +28,7 @@ TDEkinetic<OperatorLCAO<TK, TR>>::TDEkinetic(HS_Matrix_K<TK>* hsk_in,
2728
this->cal_type = calculation_type::lcao_tddft_periodic;
2829
this->Grid = GridD_in;
2930
// initialize HR to get adjs info.
31+
if(hR_in!=nullptr)
3032
this->initialize_HR(Grid);
3133
}
3234

source/source_lcao/module_operator_lcao/td_ekinetic_lcao.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "source_basis/module_nao/two_center_integrator.h"
55
#include "source_cell/klist.h"
66
#include "source_cell/module_neighbor/sltk_grid_driver.h"
7+
#include "source_estate/elecstate.h"
78
#include "source_lcao/module_hcontainer/hcontainer.h"
89
#include "source_lcao/module_rt/td_info.h"
910
#include "operator_lcao.h"
@@ -81,6 +82,23 @@ class TDEkinetic<OperatorLCAO<TK,TR>> : public OperatorLCAO<TK, TR>
8182
*/
8283
void calculate_HR();
8384

85+
/**
86+
* @brief calculate force and stress for kinetic operator
87+
* @param cal_force whether to calculate force
88+
* @param dmR density matrix in real space
89+
* @param force output force matrix (nat x 3)
90+
*/
91+
void cal_force(const bool cal_force,
92+
const Parallel_Orbitals* paraV,
93+
const psi::Psi<std::complex<double>>* psi,
94+
const elecstate::ElecState* pelec,
95+
ModuleBase::matrix& force);
96+
void cal_force(const bool cal_force,
97+
const Parallel_Orbitals* paraV,
98+
const psi::Psi<double>* psi,
99+
const elecstate::ElecState* pelec,
100+
ModuleBase::matrix& force){return;}
101+
84102
virtual void set_HR_fixed(void*) override;
85103

86104

0 commit comments

Comments
 (0)