26 const Eigen::MatrixXd& vxc_alpha,
27 const Eigen::MatrixXd& vxc_beta,
28 const Eigen::VectorXd& dft_energies_alpha,
29 const Eigen::VectorXd& dft_energies_beta)
49 throw std::runtime_error(
50 "GW_UKS currently supports only implemented unrestricted sigma "
51 "evaluators. At present this is 'ppm', 'cda', and 'exact'.");
73 auto print_spin_ranges = [&](
Spin spin,
Index homo) {
74 const Index lumo = homo + 1;
76 const Index n_occ_rpa =
77 (
opt_.rpamin <= homo) ? (homo -
opt_.rpamin + 1) : 0;
78 const Index n_virt_rpa =
79 (lumo <=
opt_.rpamax) ? (
opt_.rpamax - lumo + 1) : 0;
82 const Index qp_occ_max = std::min(
opt_.qpmax, homo);
83 const Index n_occ_qp =
84 (qp_occ_min <= qp_occ_max) ? (qp_occ_max - qp_occ_min + 1) : 0;
86 const Index qp_virt_min = std::max(
opt_.qpmin, lumo);
88 const Index n_virt_qp =
89 (qp_virt_min <= qp_virt_max) ? (qp_virt_max - qp_virt_min + 1) : 0;
93 <<
" effective ranges: HOMO=" << homo <<
" LUMO=" << lumo
94 <<
" | RPA occ[" <<
opt_.rpamin <<
":" << homo <<
"]"
95 <<
" virt[" << lumo <<
":" <<
opt_.rpamax <<
"]"
96 <<
" (#occ=" << n_occ_rpa <<
", #virt=" << n_virt_rpa <<
")"
97 <<
" | GW occ[" << qp_occ_min <<
":" << qp_occ_max <<
"]"
98 <<
" virt[" << qp_virt_min <<
":" << qp_virt_max <<
"]"
99 <<
" (#occ=" << n_occ_qp <<
", #virt=" << n_virt_qp <<
")"
106 if (
opt_.sigma_integration ==
"ppm") {
110 if (ppm_a ==
nullptr || ppm_b ==
nullptr) {
111 throw std::runtime_error(
112 "GW_UKS: expected Sigma_PPM_UKS evaluators for "
113 "sigma_integration=ppm");
117 ppm_b->SetSharedPPM(
ppm_);
153 if (level ==
Homo(spin)) {
155 }
else if (level ==
Homo(spin) + 1) {
163 return (level <=
Homo(spin)) ?
"occ" :
"virt";
167 const Eigen::VectorXd& dft_energies,
Index homo)
const {
168 Eigen::VectorXd shifted_energies = dft_energies;
169 shifted_energies.segment(homo + 1, dft_energies.size() - homo - 1).array() +=
171 return shifted_energies;
178 const double DFTgap = dft(homo + 1) - dft(homo);
180 frequencies(homo + 1 -
opt_.qpmin) - frequencies(homo -
opt_.qpmin);
181 return QPgap - DFTgap;
189 <<
" Calculated spin-resolved Hartree exchange contribution"
192 Eigen::VectorXd dft_shifted_alpha =
194 Eigen::VectorXd dft_shifted_beta =
199 <<
" Scissor shifting alpha/beta DFT energies by: " <<
opt_.shift
200 <<
" Hrt" << std::flush;
202 rpa_.setRPAInputEnergies(
203 dft_shifted_alpha.segment(
opt_.rpamin,
opt_.rpamax -
opt_.rpamin + 1),
204 dft_shifted_beta.segment(
opt_.rpamin,
opt_.rpamax -
opt_.rpamin + 1));
206 Eigen::VectorXd frequencies_alpha =
208 Eigen::VectorXd frequencies_beta =
216 for (
Index i_gw = 0; i_gw <
opt_.gw_sc_max_iterations; ++i_gw) {
217 if (i_gw %
opt_.reset_3c == 0 && i_gw != 0) {
218 Mmn_.alpha.Rebuild();
221 <<
TimeStamp() <<
" Rebuilding alpha/beta 3c integrals" << std::flush;
224 if (
opt_.sigma_integration ==
"ppm") {
225 ppm_.PPM_construct_parameters(
rpa_);
234 <<
TimeStamp() <<
" Calculated unrestricted screening via RPA"
237 if (
opt_.gw_mixing_order > 0 && i_gw > 0) {
245 if (
opt_.gw_sc_max_iterations > 1) {
246 Eigen::VectorXd rpa_alpha_old =
rpa_.getRPAInputEnergiesAlpha();
247 Eigen::VectorXd rpa_beta_old =
rpa_.getRPAInputEnergiesBeta();
249 if (
opt_.gw_mixing_order > 0 && i_gw > 0) {
252 frequencies_alpha = mixing_alpha.
MixHistory();
257 frequencies_alpha, frequencies_beta,
261 <<
TimeStamp() <<
" GW_Iteration:" << i_gw <<
" Shift_alpha[Hrt]:"
263 <<
" Shift_beta[Hrt]:"
266 const bool converged_alpha =
Converged(
rpa_.getRPAInputEnergiesAlpha(),
267 rpa_alpha_old,
opt_.gw_sc_limit);
268 const bool converged_beta =
Converged(
rpa_.getRPAInputEnergiesBeta(),
269 rpa_beta_old,
opt_.gw_sc_limit);
270 if (converged_alpha && converged_beta) {
272 <<
TimeStamp() <<
" Converged after " << i_gw + 1
273 <<
" unrestricted GW iterations." << std::flush;
275 }
else if (i_gw ==
opt_.gw_sc_max_iterations - 1) {
278 <<
" WARNING! UKS GW-self-consistency cycle not converged after "
279 <<
opt_.gw_sc_max_iterations <<
" iterations." << std::flush;
303 return rpa_.getRPAInputEnergiesAlpha();
306 return rpa_.getRPAInputEnergiesBeta();
310 const Eigen::VectorXd& frequencies)
const {
311 const Eigen::VectorXd intercepts =
313 SigmaX(spin).diagonal() -
Vxc(spin).diagonal();
314 Eigen::VectorXd frequencies_new = frequencies;
315 Eigen::Array<bool, Eigen::Dynamic, 1> converged =
316 Eigen::Array<bool, Eigen::Dynamic, 1>::Zero(
qptotal_);
322#pragma omp parallel for schedule(dynamic) num_threads(use_threads)
324 const double initial_f = frequencies[gw_level];
325 const double intercept = intercepts[gw_level];
326 boost::optional<double> newf;
327 if (
opt_.qp_solver ==
"fixedpoint") {
331 frequencies_new[gw_level] = newf.value();
332 converged[gw_level] =
true;
334 newf =
SolveQP_Grid(spin, intercept, initial_f, gw_level);
336 frequencies_new[gw_level] = newf.value();
337 converged[gw_level] =
true;
341 frequencies_new[gw_level] = newf.value();
346 return frequencies_new;
352 Index gw_level)
const {
353 boost::optional<double> newf = boost::none;
355 const double sig = sigma.CalcCorrelationDiagElement(gw_level, frequency0);
356 const double dsigma_domega =
357 sigma.CalcCorrelationDiagElementDerivative(gw_level, frequency0);
358 const double Z = 1.0 - dsigma_domega;
359 if (std::abs(Z) > 1
e-9) {
360 newf = frequency0 + (intercept0 - frequency0 + sig) / Z;
367 Index gw_level)
const {
369 opt_.qp_grid_spacing * double(
opt_.qp_grid_steps - 1) / 2.0;
370 boost::optional<double> newf = boost::none;
371 double freq_prev = frequency0 - range;
373 double targ_prev = fqp.
value(freq_prev);
374 double qp_energy = 0.0;
375 double gradient_max = std::numeric_limits<double>::max();
376 bool pole_found =
false;
377 for (
Index i_node = 1; i_node <
opt_.qp_grid_steps; ++i_node) {
378 double freq = freq_prev +
opt_.qp_grid_spacing;
379 double targ = fqp.
value(freq);
380 if (targ_prev * targ < 0.0) {
382 double gradient = fqp.
deriv(f);
383 if (std::abs(gradient) < gradient_max) {
384 gradient_max = std::abs(gradient);
400 Index gw_level)
const {
401 boost::optional<double> newf = boost::none;
404 opt_.g_sc_max_iterations,
opt_.g_sc_limit,
opt_.qp_solver_alpha);
405 double freq_new = newton.
FindRoot(f, frequency0);
413 double upperbound,
double f_upperbound,
415 if (f_lowerbound * f_upperbound > 0) {
416 throw std::runtime_error(
417 "Bisection needs a positive and negative function value");
420 const double c = 0.5 * (lowerbound + upperbound);
421 if (std::abs(upperbound - lowerbound) <
opt_.g_sc_limit) {
424 const double y_c = f.
value(c);
425 if (std::abs(y_c) <
opt_.g_sc_limit) {
428 if (y_c * f_lowerbound > 0) {
439 double epsilon)
const {
441 const double diff_max = (e1 - e2).cwiseAbs().maxCoeff(&state);
443 <<
" StateNo:" << state << std::flush;
444 return diff_max <= epsilon;
467Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd>
469 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> qpdiag(
getHQPAlpha());
474Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd>
476 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> qpdiag(
getHQPBeta());
482 const Eigen::VectorXd gwa =
489 << (boost::format(
" ====== Perturbative %1% quasiparticle energies "
490 "(Hartree) ======") %
495 if (
opt_.qpmin <= homo && homo + 1 <=
opt_.qpmax) {
496 const double dft_gap = dft(homo + 1) - dft(homo);
497 const double qp_gap = gwa(homo + 1 -
opt_.qpmin) - gwa(homo -
opt_.qpmin);
500 << (boost::format(
" %1% DFT gap = %2$+1.6f Hartree %1% GWA gap = "
501 "%3$+1.6f Hartree Delta = %4$+1.6f Hartree") %
502 SpinName(spin) % dft_gap % qp_gap % (qp_gap - dft_gap))
511 << (boost::format(
" = %1$4d (%2%) %3% DFT = %4$+1.4f VXC = %5$+1.4f "
512 "S-X = %6$+1.4f S-C = %7$+1.4f GWA = %8$+1.4f") %
515 SigmaC(spin)(i, i) % gwa(i))
522 const Eigen::VectorXd& qp_diag_energies)
const {
523 const Eigen::VectorXd gwa =
530 <<
" quasiparticle Hamiltonian" << std::flush;
532 if (
opt_.qpmin <= homo && homo + 1 <=
opt_.qpmax) {
533 const double dft_gap = dft(homo + 1) - dft(homo);
534 const double pqp_gap = gwa(homo + 1 -
opt_.qpmin) - gwa(homo -
opt_.qpmin);
535 const double dqp_gap = qp_diag_energies(homo + 1 -
opt_.qpmin) -
536 qp_diag_energies(homo -
opt_.qpmin);
539 << (boost::format(
" %1% DFT gap = %2$+1.6f Hartree %1% PQP gap = "
540 "%3$+1.6f Hartree %1% DQP gap = %4$+1.6f Hartree") %
541 SpinName(spin) % dft_gap % pqp_gap % dqp_gap)
550 << (boost::format(
" = %1$4d (%2%) %3% PQP = %4$+1.6f DQP = %5$+1.6f") %
Anderson mixing as convergence acceleration in SCF/fixed point problems.
void UpdateInput(const Eigen::VectorXd &newInput)
const Eigen::VectorXd MixHistory()
void UpdateOutput(const Eigen::VectorXd &newOutput)
void Configure(const Index order, const double alpha)
double deriv(double frequency) const
double value(double frequency) const
boost::optional< double > SolveQP_FixedPoint(Spin spin, double intercept0, double frequency0, Index gw_level) const
TCMatrix_gwbse_spin & Mmn_
Eigen::SelfAdjointEigenSolver< Eigen::MatrixXd > DiagonalizeQPHamiltonianAlpha() const
bool Converged(const Eigen::VectorXd &e1, const Eigen::VectorXd &e2, double epsilon) const
void PrintQP_Energies(Spin spin, const Eigen::VectorXd &qp_diag_energies) const
Eigen::MatrixXd & SigmaC(Spin spin)
const Eigen::VectorXd & DftEnergies(Spin spin) const
std::unique_ptr< Sigma_base_UKS > sigma_alpha_
std::string LevelLabel(Spin spin, Index level) const
Eigen::MatrixXd Sigma_x_alpha_
std::unique_ptr< Sigma_base_UKS > sigma_beta_
Eigen::MatrixXd Sigma_c_beta_
Eigen::MatrixXd & SigmaX(Spin spin)
Eigen::VectorXd getGWAResultsAlpha() const
const Eigen::MatrixXd & vxc_alpha_
Eigen::VectorXd ScissorShift_DFTlevel(const Eigen::VectorXd &dft_energies, Index homo) const
const Eigen::VectorXd & RPAInputEnergiesAlpha() const
const char * OccupationTag(Spin spin, Index level) const
Sigma_base_UKS & SigmaEvaluator(Spin spin)
Eigen::VectorXd SolveQP(Spin spin, const Eigen::VectorXd &frequencies) const
boost::optional< double > SolveQP_Linearisation(Spin spin, double intercept0, double frequency0, Index gw_level) const
const Eigen::VectorXd & RPAInputEnergiesBeta() const
Eigen::MatrixXd getHQPBeta() const
boost::optional< double > SolveQP_Grid(Spin spin, double intercept0, double frequency0, Index gw_level) const
void PrintGWA_Energies(Spin spin) const
Eigen::VectorXd getGWAResultsBeta() const
Eigen::MatrixXd getHQPAlpha() const
Eigen::MatrixXd Sigma_x_beta_
const char * SpinName(Spin spin) const
GW_UKS(Logger &log, TCMatrix_gwbse_spin &Mmn, const Eigen::MatrixXd &vxc_alpha, const Eigen::MatrixXd &vxc_beta, const Eigen::VectorXd &dft_energies_alpha, const Eigen::VectorXd &dft_energies_beta)
const Eigen::VectorXd & dft_energies_alpha_
double CalcSpinHomoLumoShift(const Eigen::VectorXd &frequencies, Spin spin) const
const Eigen::MatrixXd & vxc_beta_
const Eigen::VectorXd & dft_energies_beta_
Eigen::SelfAdjointEigenSolver< Eigen::MatrixXd > DiagonalizeQPHamiltonianBeta() const
Eigen::MatrixXd Sigma_c_alpha_
const Eigen::MatrixXd & Vxc(Spin spin) const
void configure(const options &opt)
void CalculateGWPerturbation()
Index Homo(Spin spin) const
double SolveQP_Bisection(double lowerbound, double f_lowerbound, double upperbound, double f_upperbound, const QPFunc &f) const
Logger is used for thread-safe output of messages.
Newton Rapson rootfinder for 1d functions.
double FindRoot(const Func &f, double x0)
void SetSharedPPM(const PPM &ppm)
Timestamp returns the current time as a string Example: cout << TimeStamp()
#define XTP_LOG(level, log)
Charge transport classes.
Provides a means for comparing floating point numbers.
std::string quadrature_scheme