Skip to content

Commit

Permalink
spectrum: absorption & transition analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
maki49 committed Nov 29, 2023
1 parent 27af2b2 commit 58909c5
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 66 deletions.
1 change: 1 addition & 0 deletions source/driver_run.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ void Driver::driver_run()
p_esolver_lr = new ModuleESolver::ESolver_LRTD<std::complex<double>, double>(std::move(*dynamic_cast<ModuleESolver::ESolver_KS_LCAO<std::complex<double>, double>*>(p_esolver)), INPUT, GlobalC::ucell);
ModuleESolver::clean_esolver(p_esolver);
p_esolver_lr->Run(0, GlobalC::ucell);
p_esolver_lr->postprocess();

std::cout << "before clean lr" << std::endl;
ModuleESolver::clean_esolver(p_esolver_lr);
Expand Down
4 changes: 2 additions & 2 deletions source/module_beyonddft/esolver_lrtd_lcao.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ namespace ModuleESolver

virtual void init_after_vc(Input& inp, UnitCell& cell) override {};
virtual void Run(int istep, UnitCell& ucell) override;
virtual void postprocess() override {};
virtual void postprocess() override;

virtual double cal_Energy() override { return 0.0; };
virtual void cal_Force(ModuleBase::matrix& force) override {};
virtual void cal_Stress(ModuleBase::matrix& stress) override {};

protected:

const Input& input;
const UnitCell& ucell;

hamilt::Hamilt<T>* p_hamilt = nullptr; //opsd problem first to use base calss
Expand Down
25 changes: 23 additions & 2 deletions source/module_beyonddft/esolver_lrtd_lcao.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "hamilt_casida.hpp"
#include "module_beyonddft/potentials/pot_hxc_lrtd.hpp"
#include "module_beyonddft/hsolver_lrtd.h"
#include "module_beyonddft/lr_spectrum.hpp"
#include <memory>
#include "module_hamilt_lcao/hamilt_lcaodft/hamilt_lcao.h"
#include "module_io/read_wfc_nao.h"
Expand Down Expand Up @@ -60,7 +61,7 @@ inline void redrect_log(const bool& out_alllog)

template<typename T, typename TR>
ModuleESolver::ESolver_LRTD<T, TR>::ESolver_LRTD(ModuleESolver::ESolver_KS_LCAO<T, TR>&& ks_sol,
Input& inp, UnitCell& ucell) : ucell(ucell)
Input& inp, UnitCell& ucell) : input(inp), ucell(ucell)
{
redrect_log(inp.out_alllog);
ModuleBase::TITLE("ESolver_LRTD", "ESolver_LRTD");
Expand Down Expand Up @@ -136,11 +137,12 @@ ModuleESolver::ESolver_LRTD<T, TR>::ESolver_LRTD(ModuleESolver::ESolver_KS_LCAO<
#endif
this->init_A(dynamic_cast<hamilt::HamiltLCAO<T, TR>*>(ks_sol.p_hamilt)->getHR(), inp.lr_thr);
this->lr_solver = inp.lr_solver;
this->pelec = new elecstate::ElecStateLCAO<T>();
}


template<typename T, typename TR>
ModuleESolver::ESolver_LRTD<T, TR>::ESolver_LRTD(Input& inp, UnitCell& ucell) : ucell(ucell)
ModuleESolver::ESolver_LRTD<T, TR>::ESolver_LRTD(Input& inp, UnitCell& ucell) : input(inp), ucell(ucell)
{
redrect_log(inp.out_alllog);
ModuleBase::TITLE("ESolver_LRTD", "ESolver_LRTD");
Expand Down Expand Up @@ -285,6 +287,7 @@ ModuleESolver::ESolver_LRTD<T, TR>::ESolver_LRTD(Input& inp, UnitCell& ucell) :

this->init_A(nullptr, inp.lr_thr);
this->lr_solver = inp.lr_solver;
this->pelec = new elecstate::ElecState();
}
template<typename T, typename TR>
void ModuleESolver::ESolver_LRTD<T, TR>::Run(int istep, UnitCell& cell)
Expand All @@ -294,6 +297,24 @@ void ModuleESolver::ESolver_LRTD<T, TR>::Run(int istep, UnitCell& cell)
return;
}

template<typename T, typename TR>
void ModuleESolver::ESolver_LRTD<T, TR>::postprocess()
{
//cal spectrum
LR_Spectrum<T> spectrum(this->pelec->ekb.c, *this->X, this->nspin, this->nbasis, this->nocc, this->nvirt, this->gint, *this->pw_rho, *this->psi_ks, this->ucell, this->kv, this->paraX_, this->paraC_, this->paraMat_);
spectrum.oscillator_strength();
spectrum.transition_analysis();
std::vector<double> freq(100);
std::vector<double> abs_wavelen_range({ 20, 200 });//default range
if (input.abs_wavelen_range.size() == 2 && std::abs(input.abs_wavelen_range[1] - input.abs_wavelen_range[0]) > 0.02)
abs_wavelen_range = input.abs_wavelen_range;
double lambda_diff = std::abs(abs_wavelen_range[1] - abs_wavelen_range[0]);
double lambda_min = std::min(abs_wavelen_range[1], abs_wavelen_range[0]);
for (int i = 0;i < freq.size();++i)freq[i] = 91.126664 / (lambda_min + 0.01 * static_cast<double>(i + 1) * lambda_diff);
double eta = 0.01;
spectrum.optical_absorption(freq, eta);
}

template<typename T, typename TR>
void ModuleESolver::ESolver_LRTD<T, TR>::init_X(const int& nvirt_input)
{
Expand Down
57 changes: 35 additions & 22 deletions source/module_beyonddft/hsolver_lrtd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

namespace hsolver
{
inline double square(double x) { return x * x; };
inline double square(std::complex<double> x) { return x.real() * x.real() + x.imag() * x.imag(); };
template<typename T, typename Device>
void HSolverLR<T, Device>::solve(hamilt::Hamilt<T, Device>* pHamilt,
psi::Psi<T, Device>& psi,
Expand Down Expand Up @@ -36,37 +38,48 @@ namespace hsolver
std::vector<T> Amat_full = pHamilt->matrix();
eigenvalue.resize(npairs);
LR_Util::diag_lapack(npairs, Amat_full.data(), eigenvalue.data());
std::cout << "eigenvalues:" << std::endl;
for (auto& e : eigenvalue)std::cout << e << " ";
std::cout << std::endl;
return;
psi.fix_kb(0, 0);
// copy eigenvectors
for (int i = 0;i < psi.size();++i) psi.get_pointer()[i] = Amat_full[i];
}
else
throw std::runtime_error("HSolverLR::solve: method not implemented");

// 3. set precondition and diagethr
for (int i = 0;i < psi.get_nk() * psi.get_nbasis();++i)precondition[i] = static_cast<Real>(1.0);
// T ethr = this->set_diagether(1, 1, static_cast<T>(1e-2));
std::cout << "ethr: " << this->diag_ethr << std::endl;
// 4. solve Hamiltonian
this->pdiagh->diag(pHamilt, psi, eigenvalue.data());
if (this->method != "lapack")
{
// 3. set precondition and diagethr
for (int i = 0;i < psi.get_nk() * psi.get_nbasis();++i)precondition[i] = static_cast<Real>(1.0);
// T ethr = this->set_diagether(1, 1, static_cast<T>(1e-2));
std::cout << "ethr: " << this->diag_ethr << std::endl;
// 4. solve Hamiltonian
this->pdiagh->diag(pHamilt, psi, eigenvalue.data());
}

// 5. copy eigenvalue to pes
pes->ekb.create(1, psi.get_nbands());
for (int ist = 0;ist < psi.get_nbands();++ist) pes->ekb(0, ist) = eigenvalue[ist];


// 6. output eigenvalues and eigenvectors
std::cout << "eigenvalues:" << std::endl;
for (auto& e : eigenvalue)std::cout << e << " ";
std::cout << std::endl;

std::cout << "eigenvectors:" << std::endl;
for (int ist = 0;ist < psi.get_nbands();++ist)
{
for (int ik = 0;ik < psi.get_nk();++ik)
{
for (int ib = 0;ib < psi.get_nbasis();++ib)
{
std::cout << psi(ist, ik, ib) << " ";
}
}
std::cout << std::endl;
}
// normalization is already satisfied
// std::cout << "check normalization of eigenvectors:" << std::endl;
// for (int ist = 0;ist < psi.get_nbands();++ist)
// {
// double norm2 = 0;
// for (int ik = 0;ik < psi.get_nk();++ik)
// {
// for (int ib = 0;ib < psi.get_nbasis();++ib)
// {
// norm2 += square(psi(ist, ik, ib));
// std::cout << "norm2_now=" << norm2 << std::endl;
// }
// }
// std::cout << "state " << ist << ", norm2=" << norm2 << std::endl;
// }

// output iters
std::cout << "Average iterative diagonalization steps: " << DiagoIterAssist<T, Device>::avg_iter
Expand Down
185 changes: 185 additions & 0 deletions source/module_beyonddft/lr_spectrum.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#pragma once
#include "module_psi/psi.h"
#include "module_elecstate/module_dm/density_matrix.h"
#include "module_beyonddft/utils/lr_util.h"
#include "module_hamilt_lcao/module_gint/gint_gamma.h"
#include "module_hamilt_lcao/module_gint/gint_k.h"

template <typename T> struct TGint;
template <>
struct TGint<double> {
using type = Gint_Gamma;
};
template <>
struct TGint<std::complex<double>> {
using type = Gint_k;
};

template<typename T>
class LR_Spectrum
{
public:
LR_Spectrum(const double* eig, const psi::Psi<T>& X, const int& nspin, const int& naos, const int& nocc, const int& nvirt,
typename TGint<T>::type* gint, const ModulePW::PW_Basis& rho_basis, psi::Psi<T>& psi_ks,
const UnitCell& ucell, const K_Vectors& kv_in, Parallel_2D& pX_in, Parallel_2D& pc_in, Parallel_Orbitals& pmat_in) :
eig(eig), X(X), nspin(nspin), naos(naos), nocc(nocc), nvirt(nvirt),
gint(gint), rho_basis(rho_basis), psi_ks(psi_ks),
ucell(ucell), kv(kv_in), pX(pX_in), pc(pc_in), pmat(pmat_in),
nsk(std::is_same<T, double>::value ? nspin : kv_in.kvec_d.size()) {};
/// $$2/3\Omega\sum_{ia\sigma} |\braket{\psi_{i}|\mathbf{r}|\psi_{a}} |^2\int \rho_{\alpha\beta}(\mathbf{r}) \mathbf{r} d\mathbf{r}$$
void oscillator_strength();
/// @brief calculate the optical absorption spectrum
void optical_absorption(const std::vector<double>& freq, const double eta);
/// @brief print out the transition dipole moment and the main contributions to the transition amplitude
void transition_analysis();
private:
const int nspin;
const int nsk;
const int naos;
const int nocc;
const int nvirt;
const double* eig;
const psi::Psi<T>& X;
const K_Vectors& kv;
const psi::Psi<T>& psi_ks;
Parallel_2D& pX;
Parallel_2D& pc;
Parallel_Orbitals& pmat;
typename TGint<T>::type* gint = nullptr;
const ModulePW::PW_Basis& rho_basis;
const UnitCell& ucell;

std::vector<ModuleBase::Vector3<double>> transition_dipole_; // \braket{ \psi_{i} | \mathbf{r} | \psi_{a} }
std::vector<double> oscillator_strength_;// 2/3\Omega |\sum_{ia\sigma} \braket{\psi_{i}|\mathbf{r}|\psi_{a}} |^2
};

template<typename T>
void LR_Spectrum<T>::oscillator_strength()
{
ModuleBase::TITLE("LR_Spectrum", "oscillator_strength");
std::vector<double>& osc = this->oscillator_strength_;
osc.resize(X.get_nbands(), 0.0);
// const int nspin0 = (this->nspin == 2) ? 2 : 1; use this in NSPIN=4 implementation
double osc_tot = 0.0;
elecstate::DensityMatrix<T, double> DM_trans(&this->kv, &this->pmat, this->nspin);
DM_trans.init_DMR(&GlobalC::GridD, &this->ucell);
this->transition_dipole_.resize(X.get_nbands(), ModuleBase::Vector3<double>(0.0, 0.0, 0.0));
for (int istate = 0;istate < X.get_nbands();++istate)
{
X.fix_b(istate);

GlobalV::ofs_running << "final X: " << std::endl;
for (int j = 0;j < pX.get_col_size();++j) //nbands
{
for (int i = 0;i < pX.get_row_size();++i) //nlocal
GlobalV::ofs_running << X.get_pointer()[j * pX.get_row_size() + i] << " ";
GlobalV::ofs_running << std::endl;
}

//1. transition density
#ifdef __MPI
std::vector<container::Tensor> dm_trans_2d = hamilt::cal_dm_trans_pblas(X, this->pX, this->psi_ks, this->pc, this->naos, this->nocc, this->nvirt, this->pmat);
// if (this->tdm_sym) for (auto& t : dm_trans_2d) LR_Util::matsym(t.data<T>(), naos, pmat);
#else
std::vector<container::Tensor> dm_trans_2d = hamilt::cal_dm_trans_blas(X, this->psi_ks, this->nocc, this->nvirt);
// if (this->tdm_sym) for (auto& t : dm_trans_2d) LR_Util::matsym(t.data<T>(), naos);
#endif
for (int isk = 0;isk < this->nsk;++isk)DM_trans.set_DMK_pointer(isk, dm_trans_2d[isk].data<T>());
DM_trans.cal_DMR();
this->gint->transfer_DM2DtoGrid(DM_trans.get_DMR_vector());

// 2. transition density
double** rho_trans;
LR_Util::new_p2(rho_trans, nspin, this->rho_basis.nrxx);
for (int is = 0;is < nspin;++is)ModuleBase::GlobalFunc::ZEROS(rho_trans[is], this->rho_basis.nrxx);
Gint_inout inout_rho((double**)nullptr, rho_trans, Gint_Tools::job_type::rho, false);
this->gint->cal_gint(&inout_rho);

// 3. transition dipole moment
for (int ir = 0; ir < rho_basis.nrxx; ++ir)
{
int i = ir / (rho_basis.ny * rho_basis.nplane);
int j = ir / rho_basis.nplane - i * rho_basis.ny;
int k = ir % rho_basis.nplane + rho_basis.startz_current;
ModuleBase::Vector3<double> rd(static_cast<double>(i) / rho_basis.nx, static_cast<double>(j) / rho_basis.ny, static_cast<double>(k) / rho_basis.nz); //+1/2 better?
rd -= ModuleBase::Vector3<double>(0.5, 0.5, 0.5); //shift to the center of the grid (need ?)
ModuleBase::Vector3<double> rc = rd * ucell.latvec * ucell.lat0; // real coordinate
for (int is = 0;is < nspin;++is) transition_dipole_[istate] += rc * rho_trans[is][ir];
}
Parallel_Reduce::reduce_all(transition_dipole_[istate].x);
Parallel_Reduce::reduce_all(transition_dipole_[istate].y);
Parallel_Reduce::reduce_all(transition_dipole_[istate].z);
osc[istate] = transition_dipole_[istate].norm2() * eig[istate] * 2 / 3;
osc_tot += osc[istate];
}


// check sum rule
// if (std::abs(osc_tot - GlobalV::nelec) > 1e-3)
// ModuleBase::WARNING("LR_Spectrum::oscillator_strength",
// "sum rule is not satisfied, try more nstates if needed: total oscillator strength = "
// + std::to_string(osc_tot) + "nelec = " + std::to_string(GlobalV::nelec));
}

template<typename T>
void LR_Spectrum<T>::optical_absorption(const std::vector<double>& freq, const double eta)
{
ModuleBase::TITLE("LR_Spectrum", "optical_absorption");
std::vector<double>& osc = this->oscillator_strength_;
std::ofstream ofs(GlobalV::global_out_dir + "absorption.dat");
if (GlobalV::MY_RANK == 0) ofs << "Frequency (eV) | wave length(nm) | Absorption coefficient (a.u.)" << std::endl;
for (int f = 0;f < freq.size();++f)
{
std::complex<double> f_complex = std::complex<double>(freq[f], eta);
double abs = 0.0;
for (int i = 0;i < osc.size();++i) //nstates
abs += (osc[i] / (f_complex * f_complex - eig[i] * eig[i])).imag();
if (GlobalV::MY_RANK == 0)ofs << freq[f] * ModuleBase::Ry_to_eV << "\t" << 91.126664 / freq[f] << "\t" << std::abs(abs) << std::endl;
}
ofs.close();
}

template<typename T>
void LR_Spectrum<T>::transition_analysis()
{
ModuleBase::TITLE("LR_Spectrum", "transition_analysis");
std::ofstream& ofs = GlobalV::ofs_running;
ofs << "==================================================================== " << std::endl;
ofs << std::setw(8) << "State" << std::setw(30) << "Excitation Energy (Ry, eV)" <<
std::setw(45) << "Transition dipole x, y, z (a.u.)" << std::setw(30) << "Oscillator strength(a.u.)" << std::endl;
ofs << "------------------------------------------------------------------------------------ " << std::endl;
for (int istate = 0;istate < X.get_nbands();++istate)
ofs << std::setw(8) << istate << std::setw(15) << std::setprecision(3) << eig[istate] << std::setw(15) << eig[istate] * ModuleBase::Ry_to_eV
<< std::setw(15) << transition_dipole_[istate].x << std::setw(15) << transition_dipole_[istate].y << std::setw(15) << transition_dipole_[istate].z
<< std::setw(30) << oscillator_strength_[istate] << std::endl;
ofs << "------------------------------------------------------------------------------------ " << std::endl;
ofs << std::setw(8) << "State" << std::setw(20) << "Occupied orbital" <<
std::setw(20) << "Virtual orbital" << std::setw(30) << "Excitation amplitude" << std::endl;
ofs << "------------------------------------------------------------------------------------ " << std::endl;
for (int istate = 0;istate < X.get_nbands();++istate)
{
/// find the main contributions (> 0.5)
X.fix_b(istate);
psi::Psi<T> X_full(1, X.get_nk(), nocc * nvirt, nullptr, false);
X_full.zero_out();
for (int isk = 0;isk < X.get_nk();++isk)
{
X.fix_k(isk);
X_full.fix_k(isk);
LR_Util::gather_2d_to_full(this->pX, X.get_pointer(), X_full.get_pointer(), false, nvirt, nocc);
}
std::map<double, int, std::greater<double>> abs_order;
X_full.fix_k(0);
for (int i = 0;i < X.get_nk() * nocc * nvirt;++i) { double abs = std::abs(X_full.get_pointer()[i]);if (abs > 0.5) abs_order[abs] = i; }
if (abs_order.size() > 0)
for (auto it = abs_order.cbegin();it != abs_order.cend();++it)
{
int ik = it->second % X.get_nk();
int ipair = it->second - ik * nocc * nvirt;
ofs << std::setw(8) << (it == abs_order.cbegin() ? std::to_string(istate) : " ")
<< std::setw(20) << ipair / nvirt + 1 << std::setw(20) << ipair % nvirt + nocc + 1// iocc and ivirt
<< std::setw(30) << X_full(ik, ipair) << std::endl;
}
}
ofs << "==================================================================== " << std::endl;
}
Loading

0 comments on commit 58909c5

Please sign in to comment.