Skip to content

Commit e329296

Browse files
authored
Unify RT-TDDFT EXX with the general LCAO H(R) path (#7864)
* Refactor LCAO EXX accumulation into H(R) * Fix value in current_tot.txt.ref Updated the third row's last value in current_tot.txt.ref. * Update current_tot.txt.ref * Update current_tot.txt.ref * Fix reference values in current_tot.txt.ref * Update current_tot.txt.ref * Update current_tot.txt.ref * Update current_tot.txt.ref * Fix RT-TDDFT EXX cell mapping
1 parent 39564a1 commit e329296

9 files changed

Lines changed: 153 additions & 267 deletions

File tree

source/source_esolver/esolver_factory.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "esolver_ks_pw.h"
55
#include "esolver_sdft_pw.h"
66
#include "source_base/module_device/device.h"
7+
#include "source_hamilt/module_xc/general_exx_info.h"
78
#include "source_io/module_parameter/parameter.h"
89
#ifdef __LCAO
910
#include "esolver_dm2rho.h"
@@ -254,7 +255,13 @@ ESolver* init_esolver(const Input_para& inp)
254255
}
255256
else if (esolver_type == "ksdft_lcao_tddft")
256257
{
257-
if (inp.nspin < 4)
258+
// Hybrid RT-TDDFT stores the complete Hamiltonian in complex H(R),
259+
// even for collinear spin. The final operator-chain fold then applies
260+
// the same TD gauge phase to local, non-local, and EXX terms.
261+
General_Exx_Info exx_info;
262+
init_general_exx_info(exx_info, inp);
263+
const bool use_complex_hr = inp.nspin >= 4 || exx_info.cal_exx;
264+
if (!use_complex_hr)
258265
{
259266
#if ((defined __CUDA) /* || (defined __ROCM) */)
260267
if (inp.device == "gpu")

source/source_io/module_current/td_current_io_comm.cpp

Lines changed: 2 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -198,37 +198,8 @@ void ModuleIO::sum_HR(const UnitCell& ucell,
198198

199199
// init complex full_hR
200200
init_from_hR(hR, full_hR);
201-
#ifdef __EXX
202-
const bool use_cell_nearest = (ModuleBase::Vector3<double>(std::fmod(kv.get_koffset(0), 1.0),
203-
std::fmod(kv.get_koffset(1), 1.0),
204-
std::fmod(kv.get_koffset(2), 1.0))
205-
.norm()
206-
< 1e-10);
207-
RI::Cell_Nearest<int, int, 3, double, 3> cell_nearest;
208-
// reallocate full_hR for BvK used in EXX
209-
if (exx_info.info_global.cal_exx)
210-
{
211-
const std::array<int, 3> Rs_period = {kv.nmp[0], kv.nmp[1], kv.nmp[2]};
212-
if (use_cell_nearest)
213-
{
214-
// set cell_nearest
215-
std::map<int, std::array<double, 3>> atoms_pos;
216-
for (int iat = 0; iat < ucell.nat; ++iat)
217-
{
218-
atoms_pos[iat] = RI_Util::Vector3_to_array3(ucell.atoms[ucell.iat2it[iat]].tau[ucell.iat2ia[iat]]);
219-
}
220-
const std::array<std::array<double, 3>, 3> latvec
221-
= {RI_Util::Vector3_to_array3(ucell.a1), RI_Util::Vector3_to_array3(ucell.a2), RI_Util::Vector3_to_array3(ucell.a3)};
222-
cell_nearest.init(atoms_pos, latvec, Rs_period);
223-
hamilt::reallocate_hcontainer(ucell.nat, full_hR, Rs_period, &cell_nearest);
224-
}
225-
else
226-
{
227-
hamilt::reallocate_hcontainer(ucell.nat, full_hR, Rs_period);
228-
}
229-
}
230-
#endif
231-
// add other hR
201+
// The complete H(R) already contains exact exchange. Copy it once into
202+
// full_hR; rebuilding BvK cells and adding HexxR here would double count.
232203
add_HR(hR, full_hR);
233204
// add velocity complex hR
234205
if (PARAM.inp.td_stype == 1)
@@ -240,36 +211,6 @@ void ModuleIO::sum_HR(const UnitCell& ucell,
240211
const hamilt::HContainer<std::complex<double>>* velocity_hR = TD_info::td_vel_op->get_velocity_HR_pointer();
241212
add_HR(velocity_hR, full_hR);
242213
}
243-
#ifdef __EXX
244-
// add HexxR to complex full_hR
245-
if (exx_info.info_global.cal_exx)
246-
{
247-
for (size_t is = 0; is != PARAM.inp.nspin; ++is)
248-
{
249-
if (use_cell_nearest)
250-
{
251-
RI_2D_Comm::add_HexxR(is,
252-
exx_info.info_global.hybrid_alpha,
253-
exx_nao.exc->get_Hexxs(),
254-
pv,
255-
PARAM.globalv.npol,
256-
*full_hR,
257-
&cell_nearest);
258-
}
259-
else
260-
{
261-
RI_2D_Comm::add_HexxR(is,
262-
exx_info.info_global.hybrid_alpha,
263-
exx_nao.exc->get_Hexxs(),
264-
pv,
265-
PARAM.globalv.npol,
266-
*full_hR,
267-
nullptr);
268-
}
269-
}
270-
}
271-
#endif
272-
273214
ModuleBase::timer::end("ModuleIO", "sum_HR");
274215
}
275216

source/source_lcao/hamilt_lcao.cpp

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -422,35 +422,20 @@ HamiltLCAO<TK, TR>::HamiltLCAO(const UnitCell& ucell,
422422
// Peize Lin add 2016-12-03
423423
// set xc type before the first cal of xc in pelec->init_scf
424424
// and calculate Cs, Vs
425-
Operator<TK>* exx;
426-
if (PARAM.inp.esolver_type == "tddft")
427-
{
428-
exx = new OperatorEXX<OperatorLCAO<TK, TR>>(this->hsk,
429-
this->hR,
430-
ucell,
431-
*this->kv,
432-
exx_nao.exd.get(),
433-
exx_nao.exc.get(),
434-
exx_info,
435-
Add_Hexx_Type::k,
436-
istep,
437-
!GlobalC::restart.info_load.restart_exx
438-
&& GlobalC::restart.info_load.load_H);
439-
}
440-
else
441-
{
442-
exx = new OperatorEXX<OperatorLCAO<TK, TR>>(this->hsk,
443-
this->hR,
444-
ucell,
445-
*kv,
446-
exx_nao.exd.get(),
447-
exx_nao.exc.get(),
448-
exx_info,
449-
Add_Hexx_Type::R,
450-
istep,
451-
!GlobalC::restart.info_load.restart_exx
452-
&& GlobalC::restart.info_load.load_H);
453-
}
425+
// Keep exact exchange in H(R) for every workflow. For RT-TDDFT the
426+
// factory selects complex H(R) when EXX is active, so the operator
427+
// chain folds the complete Hamiltonian with one common TD phase.
428+
Operator<TK>* exx = new OperatorEXX<OperatorLCAO<TK, TR>>(this->hsk,
429+
this->hR,
430+
ucell,
431+
*this->kv,
432+
exx_nao.exd.get(),
433+
exx_nao.exc.get(),
434+
exx_info,
435+
Add_Hexx_Type::R,
436+
istep,
437+
!GlobalC::restart.info_load.restart_exx
438+
&& GlobalC::restart.info_load.load_H);
454439
this->getOperator()->add(exx);
455440
}
456441
#endif

source/source_lcao/module_operator_lcao/op_exx_lcao.cpp

Lines changed: 48 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
#include "source_hamilt/module_hcontainer/read_hcontainer.h"
1010
#include "source_lcao/module_ri/exx_lri_interface.h"
1111
#include "source_lcao/module_ri/ri_2d_comm.h"
12-
#include "source_lcao/module_rt/td_info.h"
1312

1413
namespace hamilt
1514
{
@@ -312,11 +311,15 @@ OperatorEXX<OperatorLCAO<TK, TR>>::OperatorEXX(HS_Matrix_K<TK>* hsk_in,
312311
if (this->add_hexx_type == Add_Hexx_Type::R)
313312
{
314313
// if k points has no shift, use cell_nearest to reduce the memory cost
315-
this->use_cell_nearest = (ModuleBase::Vector3<double>(std::fmod(this->kv.get_koffset(0), 1.0),
316-
std::fmod(this->kv.get_koffset(1), 1.0),
317-
std::fmod(this->kv.get_koffset(2), 1.0))
318-
.norm()
319-
< 1e-10);
314+
// In the hybrid gauge, BvK-equivalent cells can carry different
315+
// finite-field phases, so preserve the original cell indices.
316+
const bool hybrid_gauge_rt = PARAM.inp.esolver_type == "tddft" && PARAM.inp.td_stype == 2;
317+
this->use_cell_nearest = !hybrid_gauge_rt
318+
&& (ModuleBase::Vector3<double>(std::fmod(this->kv.get_koffset(0), 1.0),
319+
std::fmod(this->kv.get_koffset(1), 1.0),
320+
std::fmod(this->kv.get_koffset(2), 1.0))
321+
.norm()
322+
< 1e-10);
320323

321324
const std::array<int, 3> Rs_period = {this->kv.nmp[0], this->kv.nmp[1], this->kv.nmp[2]};
322325
if (this->use_cell_nearest)
@@ -460,14 +463,23 @@ void OperatorEXX<OperatorLCAO<TK, TR>>::contributeHR()
460463
const int two_level_step
461464
= exx_info_ptr->info_ri.real_number ? this->exd->get_two_level_step() : this->exc->get_two_level_step();
462465

466+
// Remember that the initial GGA-only stage has completed. In
467+
// RT-TDDFT (and in subsequent ionic steps) LibRI may reset
468+
// two_level_step to zero; without this persistent state the HR path
469+
// would incorrectly skip EXX again.
470+
if (two_level_step > 0)
471+
{
472+
this->initial_gga_done = true;
473+
}
474+
463475
// Check if we are in the pre-convergence stage of the two-level SCF (i.e., the pure GGA loop)
464476
bool in_gga_pre_loop = (two_level_step == 0);
465477

466478
// Check if a high-quality initial guess is missing (neither reading wavefunctions from a file nor restarting)
467479
bool lacks_good_guess = (PARAM.inp.init_wfc != "file" && !this->restart);
468480

469481
// If in the pre-convergence loop and lacking a good initial guess, skip adding the EXX contribution
470-
if (in_gga_pre_loop && lacks_good_guess)
482+
if (in_gga_pre_loop && lacks_good_guess && !this->initial_gga_done)
471483
{
472484
return; // In the non-EXX loop, skip adding EXX contribution
473485
}
@@ -513,52 +525,25 @@ template <typename TK, typename TR>
513525
void OperatorEXX<OperatorLCAO<TK, TR>>::contributeHk(int ik)
514526
{
515527
ModuleBase::TITLE("OperatorEXX", "constributeHk");
516-
const bool has_workflow = exx_info_ptr->info_ri.real_number ? (this->exd != nullptr) : (this->exc != nullptr);
517-
int two_level_step = 0;
518-
if (has_workflow)
519-
{
520-
two_level_step
521-
= exx_info_ptr->info_ri.real_number ? this->exd->get_two_level_step() : this->exc->get_two_level_step();
522-
}
523-
524-
// Peize Lin add 2016-12-03
525528

526-
// Taoni Bao add 2026-05-15
527-
// In RT-TDDFT, contributeHk is used, but two_level_step is reset to 0 at each ionic step.
528-
// In order to add EXX correctly in for istep > 0, this->istep == 0 is needed to avoid skipping EXX calculation.
529-
// 1. For NSCF
530-
if (PARAM.inp.calculation == "nscf" || !has_workflow)
529+
// The main LCAO path stores EXX in H(R) and lets the final base operator
530+
// fold H(R) into H(k). Keep this override only for one-shot k-space EXX
531+
// operators used by write_Vxc and RDMFT.
532+
if (this->add_hexx_type == Add_Hexx_Type::R)
531533
{
532-
// Do nothing here, allow the code to proceed and calculate EXX.
534+
return;
533535
}
534-
// 2. For the first ionic step:
535-
else if (this->istep == 0)
536-
{
537-
// If EXX is once turned on (two_level_step > 0), let OperatorEXX remember this
538-
if (two_level_step > 0)
539-
{
540-
this->initial_gga_done = true;
541-
}
542536

543-
// Check if we are in the pre-convergence stage of the two-level SCF (i.e., the pure GGA loop)
544-
bool in_gga_pre_loop = (two_level_step == 0);
545-
546-
// Check if a high-quality initial guess is missing
547-
bool lacks_good_guess = (!this->restart);
548-
549-
// If in the pre-convergence loop and lacking a good initial guess, skip adding the EXX contribution
550-
// Taoni Bao add 2026-05-18, only skip EXX if initial GGA loop is not done
551-
// Fix RT-TDDFT EXX missing problem in the evolution
552-
if (in_gga_pre_loop && lacks_good_guess && !this->initial_gga_done)
553-
{
554-
return; // In the non-EXX loop, skip adding EXX contribution
555-
}
537+
// The restart path may still use a full EXX workflow. For one-shot
538+
// operators exd/exc are null and the value remains zero.
539+
int two_level_step = 0;
540+
if (exx_info_ptr->info_ri.real_number && this->exd != nullptr)
541+
{
542+
two_level_step = this->exd->get_two_level_step();
556543
}
557-
// 3. For subsequent ionic steps (istep > 0), add EXX normally
558-
559-
if (this->add_hexx_type == Add_Hexx_Type::R)
544+
else if (!exx_info_ptr->info_ri.real_number && this->exc != nullptr)
560545
{
561-
OperatorLCAO<TK, TR>::contributeHk(ik);
546+
two_level_step = this->exc->get_two_level_step();
562547
}
563548

564549
if (XC_Functional::get_func_type() == 4 || XC_Functional::get_func_type() == 5)
@@ -584,41 +569,25 @@ void OperatorEXX<OperatorLCAO<TK, TR>>::contributeHk(int ik)
584569
}
585570
}
586571
}
587-
// cal H(k) from H(R) normally
588-
if (PARAM.inp.esolver_type == "tddft" && PARAM.inp.td_stype == 2)
572+
if (exx_info_ptr->info_ri.real_number)
589573
{
590-
RI_2D_Comm::add_Hexx_td(ucell,
591-
this->kv,
592-
ik,
593-
exx_info_ptr->info_global.hybrid_alpha,
594-
*this->Hexxc,
595-
*this->hR->get_paraV(),
596-
TD_info::td_vel_op->cart_At,
597-
TD_info::td_vel_op->get_phase_hybrid(),
598-
this->hsk->get_hk());
574+
RI_2D_Comm::add_Hexx(ucell,
575+
this->kv,
576+
ik,
577+
exx_info_ptr->info_global.hybrid_alpha,
578+
*this->Hexxd,
579+
*this->hR->get_paraV(),
580+
this->hsk->get_hk());
599581
}
600582
else
601583
{
602-
if (exx_info_ptr->info_ri.real_number)
603-
{
604-
RI_2D_Comm::add_Hexx(ucell,
605-
this->kv,
606-
ik,
607-
exx_info_ptr->info_global.hybrid_alpha,
608-
*this->Hexxd,
609-
*this->hR->get_paraV(),
610-
this->hsk->get_hk());
611-
}
612-
else
613-
{
614-
RI_2D_Comm::add_Hexx(ucell,
615-
this->kv,
616-
ik,
617-
exx_info_ptr->info_global.hybrid_alpha,
618-
*this->Hexxc,
619-
*this->hR->get_paraV(),
620-
this->hsk->get_hk());
621-
}
584+
RI_2D_Comm::add_Hexx(ucell,
585+
this->kv,
586+
ik,
587+
exx_info_ptr->info_global.hybrid_alpha,
588+
*this->Hexxc,
589+
*this->hR->get_paraV(),
590+
this->hsk->get_hk());
622591
}
623592
}
624593
}
@@ -748,4 +717,4 @@ template void reallocate_hcontainer<std::complex<double>>(
748717
} // namespace hamilt
749718

750719
// End content migrated from op_exx_lcao.hpp
751-
#endif
720+
#endif

source/source_lcao/module_operator_lcao/op_exx_lcao.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ class OperatorEXX<OperatorLCAO<TK, TR>> : public OperatorLCAO<TK, TR>
6262
const Exx_Info* exx_info,
6363
Add_Hexx_Type add_hexx_type_in);
6464

65+
// Retained for the one-shot Add_Hexx_Type::k path used by write_Vxc/RDMFT;
66+
// the main LCAO path uses contributeHR().
6567
virtual void contributeHk(int ik) override;
6668
virtual void contributeHR() override;
6769

source/source_lcao/module_operator_lcao/operator_lcao.cpp

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -178,19 +178,12 @@ void OperatorLCAO<TK, TR>::init(const int ik_in) {
178178
}
179179
case calculation_type::lcao_exx:
180180
{
181-
//update HR first
182-
if (!this->hr_done && PARAM.inp.esolver_type != "tddft")
181+
// EXX is accumulated in H(R); the last operator-chain node folds
182+
// the complete H(R) into H(k), including the TD gauge phase.
183+
if (!this->hr_done)
183184
{
184185
this->contributeHR();
185186
}
186-
else if(PARAM.inp.esolver_type == "tddft")
187-
{
188-
this->contributeHk(ik_in);
189-
}
190-
191-
//update HK next
192-
//in cal_type=lcao_exx, HK only need to update from one node
193-
// this->contributeHk(ik_in);
194187

195188
break;
196189
}

0 commit comments

Comments
 (0)