Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion source/source_esolver/esolver_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "esolver_ks_pw.h"
#include "esolver_sdft_pw.h"
#include "source_base/module_device/device.h"
#include "source_hamilt/module_xc/general_exx_info.h"
#include "source_io/module_parameter/parameter.h"
#ifdef __LCAO
#include "esolver_dm2rho.h"
Expand Down Expand Up @@ -254,7 +255,13 @@ ESolver* init_esolver(const Input_para& inp)
}
else if (esolver_type == "ksdft_lcao_tddft")
{
if (inp.nspin < 4)
// Hybrid RT-TDDFT stores the complete Hamiltonian in complex H(R),
// even for collinear spin. The final operator-chain fold then applies
// the same TD gauge phase to local, non-local, and EXX terms.
General_Exx_Info exx_info;
init_general_exx_info(exx_info, inp);
const bool use_complex_hr = inp.nspin >= 4 || exx_info.cal_exx;
if (!use_complex_hr)
{
#if ((defined __CUDA) /* || (defined __ROCM) */)
if (inp.device == "gpu")
Expand Down
63 changes: 2 additions & 61 deletions source/source_io/module_current/td_current_io_comm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,37 +198,8 @@ void ModuleIO::sum_HR(const UnitCell& ucell,

// init complex full_hR
init_from_hR(hR, full_hR);
#ifdef __EXX
const bool use_cell_nearest = (ModuleBase::Vector3<double>(std::fmod(kv.get_koffset(0), 1.0),
std::fmod(kv.get_koffset(1), 1.0),
std::fmod(kv.get_koffset(2), 1.0))
.norm()
< 1e-10);
RI::Cell_Nearest<int, int, 3, double, 3> cell_nearest;
// reallocate full_hR for BvK used in EXX
if (exx_info.info_global.cal_exx)
{
const std::array<int, 3> Rs_period = {kv.nmp[0], kv.nmp[1], kv.nmp[2]};
if (use_cell_nearest)
{
// set cell_nearest
std::map<int, std::array<double, 3>> atoms_pos;
for (int iat = 0; iat < ucell.nat; ++iat)
{
atoms_pos[iat] = RI_Util::Vector3_to_array3(ucell.atoms[ucell.iat2it[iat]].tau[ucell.iat2ia[iat]]);
}
const std::array<std::array<double, 3>, 3> latvec
= {RI_Util::Vector3_to_array3(ucell.a1), RI_Util::Vector3_to_array3(ucell.a2), RI_Util::Vector3_to_array3(ucell.a3)};
cell_nearest.init(atoms_pos, latvec, Rs_period);
hamilt::reallocate_hcontainer(ucell.nat, full_hR, Rs_period, &cell_nearest);
}
else
{
hamilt::reallocate_hcontainer(ucell.nat, full_hR, Rs_period);
}
}
#endif
// add other hR
// The complete H(R) already contains exact exchange. Copy it once into
// full_hR; rebuilding BvK cells and adding HexxR here would double count.
add_HR(hR, full_hR);
// add velocity complex hR
if (PARAM.inp.td_stype == 1)
Expand All @@ -240,36 +211,6 @@ void ModuleIO::sum_HR(const UnitCell& ucell,
const hamilt::HContainer<std::complex<double>>* velocity_hR = TD_info::td_vel_op->get_velocity_HR_pointer();
add_HR(velocity_hR, full_hR);
}
#ifdef __EXX
// add HexxR to complex full_hR
if (exx_info.info_global.cal_exx)
{
for (size_t is = 0; is != PARAM.inp.nspin; ++is)
{
if (use_cell_nearest)
{
RI_2D_Comm::add_HexxR(is,
exx_info.info_global.hybrid_alpha,
exx_nao.exc->get_Hexxs(),
pv,
PARAM.globalv.npol,
*full_hR,
&cell_nearest);
}
else
{
RI_2D_Comm::add_HexxR(is,
exx_info.info_global.hybrid_alpha,
exx_nao.exc->get_Hexxs(),
pv,
PARAM.globalv.npol,
*full_hR,
nullptr);
}
}
}
#endif

ModuleBase::timer::end("ModuleIO", "sum_HR");
}

Expand Down
43 changes: 14 additions & 29 deletions source/source_lcao/hamilt_lcao.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -422,35 +422,20 @@ HamiltLCAO<TK, TR>::HamiltLCAO(const UnitCell& ucell,
// Peize Lin add 2016-12-03
// set xc type before the first cal of xc in pelec->init_scf
// and calculate Cs, Vs
Operator<TK>* exx;
if (PARAM.inp.esolver_type == "tddft")
{
exx = new OperatorEXX<OperatorLCAO<TK, TR>>(this->hsk,
this->hR,
ucell,
*this->kv,
exx_nao.exd.get(),
exx_nao.exc.get(),
exx_info,
Add_Hexx_Type::k,
istep,
!GlobalC::restart.info_load.restart_exx
&& GlobalC::restart.info_load.load_H);
}
else
{
exx = new OperatorEXX<OperatorLCAO<TK, TR>>(this->hsk,
this->hR,
ucell,
*kv,
exx_nao.exd.get(),
exx_nao.exc.get(),
exx_info,
Add_Hexx_Type::R,
istep,
!GlobalC::restart.info_load.restart_exx
&& GlobalC::restart.info_load.load_H);
}
// Keep exact exchange in H(R) for every workflow. For RT-TDDFT the
// factory selects complex H(R) when EXX is active, so the operator
// chain folds the complete Hamiltonian with one common TD phase.
Operator<TK>* exx = new OperatorEXX<OperatorLCAO<TK, TR>>(this->hsk,
this->hR,
ucell,
*this->kv,
exx_nao.exd.get(),
exx_nao.exc.get(),
exx_info,
Add_Hexx_Type::R,
istep,
!GlobalC::restart.info_load.restart_exx
&& GlobalC::restart.info_load.load_H);
this->getOperator()->add(exx);
}
#endif
Expand Down
113 changes: 39 additions & 74 deletions source/source_lcao/module_operator_lcao/op_exx_lcao.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#include "source_hamilt/module_hcontainer/read_hcontainer.h"
#include "source_lcao/module_ri/exx_lri_interface.h"
#include "source_lcao/module_ri/ri_2d_comm.h"
#include "source_lcao/module_rt/td_info.h"

namespace hamilt
{
Expand Down Expand Up @@ -460,14 +459,23 @@ void OperatorEXX<OperatorLCAO<TK, TR>>::contributeHR()
const int two_level_step
= exx_info_ptr->info_ri.real_number ? this->exd->get_two_level_step() : this->exc->get_two_level_step();

// Remember that the initial GGA-only stage has completed. In
// RT-TDDFT (and in subsequent ionic steps) LibRI may reset
// two_level_step to zero; without this persistent state the HR path
// would incorrectly skip EXX again.
if (two_level_step > 0)
{
this->initial_gga_done = true;
}

// Check if we are in the pre-convergence stage of the two-level SCF (i.e., the pure GGA loop)
bool in_gga_pre_loop = (two_level_step == 0);

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

// If in the pre-convergence loop and lacking a good initial guess, skip adding the EXX contribution
if (in_gga_pre_loop && lacks_good_guess)
if (in_gga_pre_loop && lacks_good_guess && !this->initial_gga_done)
{
return; // In the non-EXX loop, skip adding EXX contribution
}
Expand Down Expand Up @@ -513,52 +521,25 @@ template <typename TK, typename TR>
void OperatorEXX<OperatorLCAO<TK, TR>>::contributeHk(int ik)
{
ModuleBase::TITLE("OperatorEXX", "constributeHk");
const bool has_workflow = exx_info_ptr->info_ri.real_number ? (this->exd != nullptr) : (this->exc != nullptr);
int two_level_step = 0;
if (has_workflow)
{
two_level_step
= exx_info_ptr->info_ri.real_number ? this->exd->get_two_level_step() : this->exc->get_two_level_step();
}

// Peize Lin add 2016-12-03

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

// Check if we are in the pre-convergence stage of the two-level SCF (i.e., the pure GGA loop)
bool in_gga_pre_loop = (two_level_step == 0);

// Check if a high-quality initial guess is missing
bool lacks_good_guess = (!this->restart);

// If in the pre-convergence loop and lacking a good initial guess, skip adding the EXX contribution
// Taoni Bao add 2026-05-18, only skip EXX if initial GGA loop is not done
// Fix RT-TDDFT EXX missing problem in the evolution
if (in_gga_pre_loop && lacks_good_guess && !this->initial_gga_done)
{
return; // In the non-EXX loop, skip adding EXX contribution
}
// The restart path may still use a full EXX workflow. For one-shot
// operators exd/exc are null and the value remains zero.
int two_level_step = 0;
if (exx_info_ptr->info_ri.real_number && this->exd != nullptr)
{
two_level_step = this->exd->get_two_level_step();
}
// 3. For subsequent ionic steps (istep > 0), add EXX normally

if (this->add_hexx_type == Add_Hexx_Type::R)
else if (!exx_info_ptr->info_ri.real_number && this->exc != nullptr)
{
OperatorLCAO<TK, TR>::contributeHk(ik);
two_level_step = this->exc->get_two_level_step();
}

if (XC_Functional::get_func_type() == 4 || XC_Functional::get_func_type() == 5)
Expand All @@ -584,41 +565,25 @@ void OperatorEXX<OperatorLCAO<TK, TR>>::contributeHk(int ik)
}
}
}
// cal H(k) from H(R) normally
if (PARAM.inp.esolver_type == "tddft" && PARAM.inp.td_stype == 2)
if (exx_info_ptr->info_ri.real_number)
{
RI_2D_Comm::add_Hexx_td(ucell,
this->kv,
ik,
exx_info_ptr->info_global.hybrid_alpha,
*this->Hexxc,
*this->hR->get_paraV(),
TD_info::td_vel_op->cart_At,
TD_info::td_vel_op->get_phase_hybrid(),
this->hsk->get_hk());
RI_2D_Comm::add_Hexx(ucell,
this->kv,
ik,
exx_info_ptr->info_global.hybrid_alpha,
*this->Hexxd,
*this->hR->get_paraV(),
this->hsk->get_hk());
}
else
{
if (exx_info_ptr->info_ri.real_number)
{
RI_2D_Comm::add_Hexx(ucell,
this->kv,
ik,
exx_info_ptr->info_global.hybrid_alpha,
*this->Hexxd,
*this->hR->get_paraV(),
this->hsk->get_hk());
}
else
{
RI_2D_Comm::add_Hexx(ucell,
this->kv,
ik,
exx_info_ptr->info_global.hybrid_alpha,
*this->Hexxc,
*this->hR->get_paraV(),
this->hsk->get_hk());
}
RI_2D_Comm::add_Hexx(ucell,
this->kv,
ik,
exx_info_ptr->info_global.hybrid_alpha,
*this->Hexxc,
*this->hR->get_paraV(),
this->hsk->get_hk());
}
}
}
Expand Down Expand Up @@ -748,4 +713,4 @@ template void reallocate_hcontainer<std::complex<double>>(
} // namespace hamilt

// End content migrated from op_exx_lcao.hpp
#endif
#endif
2 changes: 2 additions & 0 deletions source/source_lcao/module_operator_lcao/op_exx_lcao.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ class OperatorEXX<OperatorLCAO<TK, TR>> : public OperatorLCAO<TK, TR>
const Exx_Info* exx_info,
Add_Hexx_Type add_hexx_type_in);

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

Expand Down
13 changes: 3 additions & 10 deletions source/source_lcao/module_operator_lcao/operator_lcao.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,19 +178,12 @@ void OperatorLCAO<TK, TR>::init(const int ik_in) {
}
case calculation_type::lcao_exx:
{
//update HR first
if (!this->hr_done && PARAM.inp.esolver_type != "tddft")
// EXX is accumulated in H(R); the last operator-chain node folds
// the complete H(R) into H(k), including the TD gauge phase.
if (!this->hr_done)
{
this->contributeHR();
}
else if(PARAM.inp.esolver_type == "tddft")
{
this->contributeHk(ik_in);
}

//update HK next
//in cal_type=lcao_exx, HK only need to update from one node
// this->contributeHk(ik_in);

break;
}
Expand Down
Loading
Loading