38Eigen::VectorXd
ExpValue(
const Eigen::MatrixXd& state1, OP OPxstate2) {
39 return state1.cwiseProduct(OPxstate2.eval()).colwise().sum().transpose();
42Eigen::VectorXd
ExpValue(
const Eigen::MatrixXd& state1,
43 const Eigen::MatrixXd& OPxstate2) {
44 return state1.cwiseProduct(OPxstate2).colwise().sum().transpose();
53double DavidsonToleranceValue(
const std::string& tol) {
56 }
else if (tol ==
"normal") {
58 }
else if (tol ==
"strict") {
60 }
else if (tol ==
"lapack") {
63 throw std::runtime_error(tol +
" is not a valid Davidson tolerance");
69 const Eigen::VectorXd& RPAInputEnergiesAlpha,
70 const Eigen::VectorXd& RPAInputEnergiesBeta,
71 const Eigen::MatrixXd& Hqp_alpha_in,
const Eigen::MatrixXd& Hqp_beta_in,
72 const Eigen::VectorXd& epsilon_0_inv,
73 const Eigen::MatrixXd& epsilon_eigenvectors) {
87 if (
opt_.use_Hqp_offdiag) {
102 Mmn_.alpha.MultiplyRightWithAuxMatrix(epsilon_eigenvectors);
103 Mmn_.beta.MultiplyRightWithAuxMatrix(epsilon_eigenvectors);
109 const Eigen::VectorXd& RPAInputEnergies,
112 Index bse_ctotal =
opt_.cmax - (homo + 1) + 1;
113 Index hqp_size = bse_vtotal + bse_ctotal;
116 Eigen::MatrixXd Hqp_BSE = Eigen::MatrixXd::Zero(hqp_size, hqp_size);
121 Hqp_BSE = Hqp.block(start, start, hqp_size, hqp_size);
123 Index virtoffset = gwsize - start;
124 Hqp_BSE.topLeftCorner(virtoffset, virtoffset) =
125 Hqp.block(start, start, virtoffset, virtoffset);
128 Hqp_BSE.diagonal().tail(virt_extra) =
129 RPAInputEnergies.segment(RPAoffset + virtoffset, virt_extra);
135 Hqp_BSE.diagonal().head(occ_extra) =
136 RPAInputEnergies.segment(RPAoffset, occ_extra);
138 Hqp_BSE.block(occ_extra, occ_extra, gwsize, gwsize) = Hqp;
141 Index virtoffset = occ_extra + gwsize;
143 Hqp_BSE.diagonal().tail(virt_extra) =
144 RPAInputEnergies.segment(RPAoffset + virtoffset, virt_extra);
152 const Eigen::VectorXd& RPAInputEnergiesAlpha,
153 const Eigen::VectorXd& RPAInputEnergiesBeta,
double energy) {
174template <
typename BSE_OPERATOR>
186template <
typename BSE_OPERATOR>
204 expectation_values.
cross_term = Eigen::VectorXd::Zero(0);
207 return expectation_values;
210template <
typename BSE_OPERATOR>
218 const Eigen::MatrixXd Xstate = BSECoefs.
eigenvectors().col(state);
219 const Eigen::MatrixXd temp =
H * Xstate;
224 const Eigen::MatrixXd Ystate = BSECoefs.
eigenvectors2().col(state);
228 expectation_values.
cross_term = Eigen::VectorXd::Zero(0);
231 return expectation_values;
246 <<
TimeStamp() <<
" Setup combined UKS TDA Hamiltonian " << flush;
323 <<
TimeStamp() <<
" Setup combined UKS full BSE exciton hamiltonian "
329 constexpr Index dense_threshold = 128;
331 if (A.
rows() <= dense_threshold) {
334 <<
" Using dense full UKS-BSE solve for small system (dim=" << A.
rows()
336 std::chrono::time_point<std::chrono::system_clock> start =
337 std::chrono::system_clock::now();
341 const Index n = Ad.rows();
343 Eigen::MatrixXd Hfull = Eigen::MatrixXd::Zero(2 * n, 2 * n);
344 Hfull.topLeftCorner(n, n) = Ad;
345 Hfull.topRightCorner(n, n) = Bd;
346 Hfull.bottomLeftCorner(n, n) = -Bd;
347 Hfull.bottomRightCorner(n, n) = -Ad;
349 Eigen::EigenSolver<Eigen::MatrixXd> es(Hfull,
true);
350 if (es.info() != Eigen::Success) {
351 throw std::runtime_error(
"Dense full UKS-BSE diagonalization failed.");
355 std::tuple<double, Index, double>;
356 std::vector<RootInfo> positive_roots;
357 positive_roots.reserve(2 * n);
359 for (
Index i = 0; i < 2 * n; ++i) {
360 const std::complex<double> ev = es.eigenvalues()(i);
361 if (std::abs(ev.imag()) < 1
e-8 && ev.real() > 0.0) {
362 positive_roots.emplace_back(ev.real(), i, std::abs(ev.imag()));
366 std::sort(positive_roots.begin(), positive_roots.end(),
367 [](
const RootInfo& a,
const RootInfo& b) {
368 return std::get<0>(a) < std::get<0>(b);
371 if (positive_roots.empty()) {
372 throw std::runtime_error(
373 "Dense full UKS-BSE diagonalization produced no positive real "
378 std::min<Index>(
opt_.nmax,
static_cast<Index>(positive_roots.size()));
381 result.
eigenvalues() = Eigen::VectorXd::Zero(nroots);
382 result.
eigenvectors() = Eigen::MatrixXd::Zero(n, nroots);
385 for (
Index iroot = 0; iroot < nroots; ++iroot) {
386 const Index col = std::get<1>(positive_roots[iroot]);
387 Eigen::VectorXd vec = es.eigenvectors().col(col).real();
389 Eigen::VectorXd X = vec.head(n);
390 Eigen::VectorXd Y = vec.tail(n);
393 Eigen::Index imax = 0;
394 X.cwiseAbs().maxCoeff(&imax);
400 const double norm = X.squaredNorm() - Y.squaredNorm();
401 if (std::abs(norm) < 1
e-12) {
402 throw std::runtime_error(
403 "Dense full UKS-BSE eigenvector has near-zero (X^2-Y^2) norm.");
406 const double invnorm = 1.0 / std::sqrt(std::abs(norm));
408 result.
eigenvalues()(iroot) = std::get<0>(positive_roots[iroot]);
413 std::chrono::time_point<std::chrono::system_clock> end =
414 std::chrono::system_clock::now();
415 std::chrono::duration<double> elapsed_time = end - start;
418 <<
TimeStamp() <<
" Dense full UKS-BSE diagonalization done in "
419 << elapsed_time.count() <<
" secs" << flush;
428 fopt.
tolerance = DavidsonToleranceValue(
opt_.davidson_tolerance);
433 }
catch (
const std::runtime_error& error) {
436 <<
"; using the non-Hermitian Davidson solver" << flush;
439 const Eigen::VectorXd adiag = A.
diagonal();
440 const Eigen::VectorXd bdiag = B.
diagonal();
441 Eigen::MatrixXd initial_guess =
465 <<
" BSE root(s) without positive X^2 - Y^2 norm (not converged?)"
484template <
typename BSE_OPERATOR>
486 std::chrono::time_point<std::chrono::system_clock> start =
487 std::chrono::system_clock::now();
502 std::chrono::time_point<std::chrono::system_clock> end =
503 std::chrono::system_clock::now();
504 std::chrono::duration<double> elapsed_time = end - start;
507 << elapsed_time.count() <<
" secs" << flush;
512template <
typename BSE_OPERATOR_A,
typename BSE_OPERATOR_B>
514 BSE_OPERATOR_A& Aop, BSE_OPERATOR_B& Bop)
const {
515 std::chrono::time_point<std::chrono::system_clock> start =
516 std::chrono::system_clock::now();
532 Eigen::MatrixXd tmpX = DS.
eigenvectors().topRows(Aop.rows());
533 Eigen::MatrixXd tmpY = DS.
eigenvectors().bottomRows(Bop.rows());
538 <<
" BSE root(s) without positive X^2 - Y^2 norm (not converged?)"
544 std::chrono::time_point<std::chrono::system_clock> end =
545 std::chrono::system_clock::now();
546 std::chrono::duration<double> elapsed_time = end - start;
549 << elapsed_time.count() <<
" secs" << flush;
555 struct Contribution {
562 constexpr Index max_print = 8;
564 double alpha_weight = weights.head(
alpha_size_).sum();
565 double beta_weight = weights.tail(
beta_size_).sum();
569 " alpha-sector: %1$+6.2f%% beta-sector: %2$+6.2f%%") %
570 (100.0 * alpha_weight) % (100.0 * beta_weight)
573 std::vector<Contribution> contributions;
577 if (std::abs(weights(i)) >
opt_.min_print_weight) {
578 contributions.push_back(
585 if (std::abs(w) >
opt_.min_print_weight) {
590 std::sort(contributions.begin(), contributions.end(),
591 [](
const Contribution& a,
const Contribution& b) {
592 return std::abs(a.weight) > std::abs(b.weight);
596 std::min<Index>(max_print,
static_cast<Index>(contributions.size()));
598 for (
Index i = 0; i < nprint; ++i) {
599 const Contribution& c = contributions[i];
604 " [alpha] HOMO-%1$-3d -> LUMO+%2$-3d : "
613 " [beta ] HOMO-%1$-3d -> LUMO+%2$-3d : "
622 if (
static_cast<Index>(contributions.size()) > nprint) {
625 " ... %1$d more contributions above threshold") %
626 (
static_cast<Index>(contributions.size()) - nprint)
637 const Eigen::VectorXd oscs =
642 <<
" ====== combined UKS TDA exciton energies (eV) ====== " << flush;
645 <<
" ====== combined UKS full-BSE exciton energies (eV) ====== "
656 const double osc = (i < oscs.size()) ? oscs(i) : 0.0;
660 " TrDipole length gauge[e*bohr] dx = %1$+1.4f dy = "
661 "%2$+1.4f dz = %3$+1.4f |d|^2 = %4$+1.4f f = %5$+1.4f") %
662 trdip[0] % trdip[1] % trdip[2] % (trdip.squaredNorm()) % osc
665 Eigen::VectorXd weights = es.
eigenvectors().col(i).cwiseAbs2();
677 const Eigen::VectorXd& BSEenergies = BSECoefs.
eigenvalues();
691 Eigen::VectorXd Hd_static_contribution = expectation_values.
direct_term;
697 Hd_static_contribution += expectation_values.
cross_term;
700 Eigen::VectorXd BSEenergies_dynamic = BSEenergies;
702 for (
Index i_exc = 0; i_exc < BSEenergies.size(); ++i_exc) {
704 <<
"Dynamical Screening UKS BSE, Excitation " << i_exc <<
" static "
705 << BSEenergies(i_exc) << flush;
707 for (
Index iter = 0; iter <
opt_.max_dyn_iter; ++iter) {
708 const double old_energy = BSEenergies_dynamic(i_exc);
711 RPAInputEnergiesBeta, old_energy);
717 Eigen::VectorXd Hd_dynamic_contribution = expectation_values.
direct_term;
724 Hd_dynamic_contribution += expectation_values.
cross_term;
727 BSEenergies_dynamic(i_exc) = BSEenergies(i_exc) +
728 Hd_static_contribution(i_exc) -
729 Hd_dynamic_contribution(0);
732 << i_exc <<
" iteration " << iter <<
" dynamic "
733 << BSEenergies_dynamic(i_exc) << flush;
735 if (std::abs(BSEenergies_dynamic(i_exc) - old_energy) <
736 opt_.dyn_tolerance) {
747 Eigen::VectorXd oscs = Eigen::VectorXd::Zero(0);
754 <<
" ====== combined UKS TDA exciton energies with perturbative "
755 "dynamical screening (eV) ====== "
759 <<
" ====== combined UKS full-BSE exciton energies with perturbative "
760 "dynamical screening (eV) ====== "
764 const Index nprint = std::min<Index>(
opt_.nmax, BSEenergies_dynamic.size());
766 for (
Index i = 0; i < nprint; ++i) {
768 if (has_dipoles && i < oscs.size()) {
769 osc = oscs(i) * BSEenergies_dynamic(i) / BSEenergies(i);
774 " XU(dynamic) = %1$4d Omega = %2$+1.12f eV lambda = %3$+3.2f "
777 (i + 1) % (hrt2ev * BSEenergies_dynamic(i)) %
778 (1240.0 / (hrt2ev * BSEenergies_dynamic(i))) % osc
Eigen::VectorXd diagonal() const override
void configure(BSEOperatorUKS_Options opt)
Eigen::MatrixXd dense_matrix() const
const TCMatrix_gwbse_spin & Mmn_raw_
ExpectationValues ExpectationValue_Operator_State(Index state, const Orbitals &orb, const BSE_OPERATOR &H) const
tools::EigenSystem solve_hermitian(BSE_OPERATOR &h) const
void configure_with_precomputed_screening(const options &opt, Index homo_alpha, Index homo_beta, const Eigen::VectorXd &RPAInputEnergiesAlpha, const Eigen::VectorXd &RPAInputEnergiesBeta, const Eigen::MatrixXd &Hqp_alpha_in, const Eigen::MatrixXd &Hqp_beta_in, const Eigen::VectorXd &epsilon_0_inv, const Eigen::MatrixXd &epsilon_eigenvectors)
void configureBSEOperator(BSE_OPERATOR &H) const
void PrintWeightsUKS(const Eigen::VectorXd &coeffs) const
tools::EigenSystem Solve_nonhermitian_Davidson(BSE_OPERATOR_A &Aop, BSE_OPERATOR_B &Bop) const
Eigen::MatrixXd Hqp_beta_
Eigen::VectorXd epsilon_0_inv_
void Perturbative_DynamicalScreening(Orbitals &orb)
ExpectationValues ExpectationValue_Operator(const Orbitals &orb, const BSE_OPERATOR &H) const
Eigen::MatrixXd AdjustHqpSize(const Eigen::MatrixXd &Hqp_in, const Eigen::VectorXd &RPAInputEnergies, Index homo) const
tools::EigenSystem Solve_excitons_uks_BTDA() const
void SetupDirectInteractionOperator(const Eigen::VectorXd &RPAInputEnergiesAlpha, const Eigen::VectorXd &RPAInputEnergiesBeta, double energy)
void Solve_excitons_uks(Orbitals &orb) const
Eigen::MatrixXd Hqp_alpha_
void Analyze_excitons_uks(std::vector< QMFragment< BSE_Population > > fragments, const Orbitals &orb) const
tools::EigenSystem Solve_excitons_uks_TDA() const
Use Davidson algorithm to solve A*V=E*V.
void set_max_search_space(Index N)
Eigen::VectorXd eigenvalues() const
void solve(const MatrixReplacement &A, Index neigen, Index size_initial_guess=0)
void set_iter_max(Index N)
void set_matrix_type(std::string mt)
void set_correction(std::string method)
void set_size_update(std::string update_size)
void set_tolerance(std::string tol)
Eigen::MatrixXd eigenvectors() const
Lowest roots of the full (non-TDA) BSE.
tools::EigenSystem Solve(const OpA &A, const OpB &B, Index nroots)
Container for molecular orbitals and derived one-particle data.
const Eigen::VectorXd & BSEUKS_dynamic() const
void CalcCoupledTransition_Dipoles()
bool getTDAApprox() const
Return whether the Tamm-Dancoff approximation is enabled.
const tools::EigenSystem & BSEUKS() const
Eigen::VectorXd Oscillatorstrengths() const
bool hasTransitionDipoles() const
Report whether transition dipole moments have been computed.
const std::vector< Eigen::Vector3d > & TransitionDipoles() const
Return the stored transition dipole moments.
const Eigen::VectorXd & RPAInputEnergiesBeta() const
void setTDAApprox(bool usedTDA)
Enable or disable the Tamm-Dancoff approximation flag.
const Eigen::VectorXd & RPAInputEnergiesAlpha() const
Unrestricted RPA helper for spin-resolved GW screening.
void setRPAInputEnergies(const Eigen::VectorXd &rpaenergies_alpha, const Eigen::VectorXd &rpaenergies_beta)
Set alpha and beta RPA input energies directly.
void configure(Index homo_alpha, Index homo_beta, Index rpamin, Index rpamax)
Eigen::MatrixXd calculate_epsilon_r(double frequency) const
Dielectric matrix on the real frequency axis.
Timestamp returns the current time as a string Example: cout << TimeStamp().
#define XTP_LOG(level, log)
Charge transport classes.
BSE_OPERATOR_UKS< 0, 1, 0, 1 > ExcitonUKSOperator_BTDA_B
Index NormalizeExcitationVectors(Eigen::MatrixXd &X, Eigen::MatrixXd &Y)
BSE_OPERATOR_UKS< 1, 1, 1, 0 > ExcitonUKSOperator_TDA
Eigen::VectorXd ExpValue(const Eigen::MatrixXd &state1, OP OPxstate2)
SymmetricEigenSystem SymmetricEigen(const Eigen::MatrixXd &A, bool vectors=true)
With vectors = false only the eigenvalues are computed.
BSE_OPERATOR_UKS< 0, 0, 0, 1 > Hd2UKSOperator
Eigen::MatrixXd BuildFullBSEXRankedInitialGuess(const Eigen::VectorXd &adiag, const Eigen::VectorXd &bdiag, Index nroots)
Build an initial guess for full-BSE Davidson diagonalization.
BSE_OPERATOR_UKS< 0, 0, 1, 0 > HdUKSOperator
Provides a means for comparing floating point numbers.
Eigen::VectorXd cross_term
Eigen::VectorXd direct_term