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)
53 throw std::runtime_error(
54 "GW_UKS currently supports only implemented unrestricted sigma "
55 "evaluators. At present this is 'ppm', 'cda', and 'exact'.");
77 auto print_spin_ranges = [&](
Spin spin,
Index homo) {
78 const Index lumo = homo + 1;
80 const Index n_occ_rpa =
81 (
opt_.rpamin <= homo) ? (homo -
opt_.rpamin + 1) : 0;
82 const Index n_virt_rpa =
83 (lumo <=
opt_.rpamax) ? (
opt_.rpamax - lumo + 1) : 0;
86 const Index qp_occ_max = std::min(
opt_.qpmax, homo);
87 const Index n_occ_qp =
88 (qp_occ_min <= qp_occ_max) ? (qp_occ_max - qp_occ_min + 1) : 0;
90 const Index qp_virt_min = std::max(
opt_.qpmin, lumo);
92 const Index n_virt_qp =
93 (qp_virt_min <= qp_virt_max) ? (qp_virt_max - qp_virt_min + 1) : 0;
97 <<
" effective ranges: HOMO=" << homo <<
" LUMO=" << lumo
98 <<
" | RPA occ[" <<
opt_.rpamin <<
":" << homo <<
"]"
99 <<
" virt[" << lumo <<
":" <<
opt_.rpamax <<
"]"
100 <<
" (#occ=" << n_occ_rpa <<
", #virt=" << n_virt_rpa <<
")"
101 <<
" | GW occ[" << qp_occ_min <<
":" << qp_occ_max <<
"]"
102 <<
" virt[" << qp_virt_min <<
":" << qp_virt_max <<
"]"
103 <<
" (#occ=" << n_occ_qp <<
", #virt=" << n_virt_qp <<
")"
110 if (
opt_.sigma_integration ==
"ppm") {
114 if (ppm_a ==
nullptr || ppm_b ==
nullptr) {
115 throw std::runtime_error(
116 "GW_UKS: expected Sigma_PPM_UKS evaluators for "
117 "sigma_integration=ppm");
121 ppm_b->SetSharedPPM(
ppm_);
157 if (level ==
Homo(spin)) {
159 }
else if (level ==
Homo(spin) + 1) {
167 return (level <=
Homo(spin)) ?
"occ" :
"virt";
171 const Eigen::VectorXd& dft_energies,
Index homo)
const {
172 Eigen::VectorXd shifted_energies = dft_energies;
173 shifted_energies.segment(homo + 1, dft_energies.size() - homo - 1).array() +=
175 return shifted_energies;
182 const double DFTgap = dft(homo + 1) - dft(homo);
184 frequencies(homo + 1 -
opt_.qpmin) - frequencies(homo -
opt_.qpmin);
185 return QPgap - DFTgap;
193 <<
" Calculated spin-resolved Hartree exchange contribution"
196 Eigen::VectorXd dft_shifted_alpha =
198 Eigen::VectorXd dft_shifted_beta =
203 <<
" Scissor shifting alpha/beta DFT energies by: " <<
opt_.shift
204 <<
" Hrt" << std::flush;
206 rpa_.setRPAInputEnergies(
207 dft_shifted_alpha.segment(
opt_.rpamin,
opt_.rpamax -
opt_.rpamin + 1),
208 dft_shifted_beta.segment(
opt_.rpamin,
opt_.rpamax -
opt_.rpamin + 1));
210 Eigen::VectorXd frequencies_alpha =
212 Eigen::VectorXd frequencies_beta =
220 for (
Index i_gw = 0; i_gw <
opt_.gw_sc_max_iterations; ++i_gw) {
222 if (i_gw %
opt_.reset_3c == 0 && i_gw != 0) {
223 Mmn_.alpha.Rebuild();
226 <<
TimeStamp() <<
" Rebuilding alpha/beta 3c integrals" << std::flush;
229 if (
opt_.sigma_integration ==
"ppm") {
230 ppm_.PPM_construct_parameters(
rpa_);
239 <<
TimeStamp() <<
" Calculated unrestricted screening via RPA"
242 if (
opt_.gw_mixing_order > 0 && i_gw > 0) {
250 if (
opt_.gw_sc_max_iterations > 1) {
251 Eigen::VectorXd rpa_alpha_old =
rpa_.getRPAInputEnergiesAlpha();
252 Eigen::VectorXd rpa_beta_old =
rpa_.getRPAInputEnergiesBeta();
254 if (
opt_.gw_mixing_order > 0 && i_gw > 0) {
257 frequencies_alpha = mixing_alpha.
MixHistory();
262 frequencies_alpha, frequencies_beta,
266 <<
TimeStamp() <<
" GW_Iteration:" << i_gw <<
" Shift_alpha[Hrt]:"
268 <<
" Shift_beta[Hrt]:"
271 const bool converged_alpha =
Converged(
rpa_.getRPAInputEnergiesAlpha(),
272 rpa_alpha_old,
opt_.gw_sc_limit);
273 const bool converged_beta =
Converged(
rpa_.getRPAInputEnergiesBeta(),
274 rpa_beta_old,
opt_.gw_sc_limit);
275 if (converged_alpha && converged_beta) {
277 <<
TimeStamp() <<
" Converged after " << i_gw + 1
278 <<
" unrestricted GW iterations." << std::flush;
280 }
else if (i_gw ==
opt_.gw_sc_max_iterations - 1) {
283 <<
" WARNING! UKS GW-self-consistency cycle not converged after "
284 <<
opt_.gw_sc_max_iterations <<
" iterations." << std::flush;
308 return rpa_.getRPAInputEnergiesAlpha();
311 return rpa_.getRPAInputEnergiesBeta();
315 const Eigen::VectorXd& frequencies)
const {
316 const Eigen::VectorXd intercepts =
318 SigmaX(spin).diagonal() -
Vxc(spin).diagonal();
320 Eigen::VectorXd frequencies_new = frequencies;
328#pragma omp parallel for schedule(dynamic) num_threads(use_threads)
330 const double initial_f = frequencies[gw_level];
331 const double intercept = intercepts[gw_level];
333 boost::optional<double> newf;
338 if (
opt_.qp_solver ==
"fixedpoint") {
344 frequencies_new[gw_level] = newf.value();
346 newf =
SolveQP_Grid(spin, intercept, initial_f, gw_level, &grid_stats);
348 frequencies_new[gw_level] = newf.value();
353 frequencies_new[gw_level] = newf.value();
362 total_stats.
Add(fixed_stats);
363 total_stats.
Add(grid_stats);
364 total_stats.
Add(lin_stats);
366 const Index mo_level =
opt_.qpmin + gw_level;
369 <<
" QP stats " <<
LevelLabel(spin, mo_level) <<
" mo=" << mo_level
382 return frequencies_new;
390 boost::optional<double> newf = boost::none;
394 const double sigma = fqp.
sigma(frequency0, EvalStage::Other);
395 const double dsigma_domega = fqp.
deriv(frequency0);
396 const double Z = 1.0 - dsigma_domega;
398 if (std::abs(Z) > 1
e-9) {
399 newf = frequency0 + (intercept0 - frequency0 + sigma) / Z;
402 if (stats !=
nullptr) {
410 double frequency0,
Index gw_level,
414 const double range =
opt_.qp_full_window_half_width;
416 const double full_left_limit = frequency0 - range;
417 const double full_right_limit = frequency0 + range;
419 double restricted_left_limit = full_left_limit;
420 double restricted_right_limit = full_right_limit;
422 bool use_restricted_window =
false;
424 if (
opt_.qp_restrict_search) {
425 const Index mo_level = gw_level +
opt_.qpmin;
426 const bool is_occupied = (mo_level <=
Homo(spin));
429 restricted_right_limit = std::min(full_right_limit, -
opt_.qp_zero_margin);
431 restricted_left_limit =
432 std::max(full_left_limit,
opt_.qp_virtual_min_energy);
435 const double tol = 1
e-12;
436 use_restricted_window =
437 (std::abs(restricted_left_limit - full_left_limit) > tol) ||
438 (std::abs(restricted_right_limit - full_right_limit) > tol);
441 if (use_restricted_window && restricted_left_limit < restricted_right_limit) {
444 restricted_left_limit, restricted_right_limit,
456 <<
" Restricted QP search failed for "
458 << restricted_left_limit <<
", " << restricted_right_limit
459 <<
"], forcing full dense window [" << full_left_limit <<
", "
460 << full_right_limit <<
"]" << std::flush;
465 full_left_limit, full_right_limit,
true,
470 full_left_limit, full_right_limit,
true, stats);
474 Spin spin,
double intercept0,
double frequency0,
Index gw_level,
475 double left_limit,
double right_limit,
bool allow_rejected_return,
492 std::vector<QPRootCandidate> accepted_roots;
493 std::vector<QPRootCandidate> rejected_roots;
495 bool use_brent =
false;
496 if (
opt_.qp_root_finder ==
"brent") {
502 &wdiag, &accepted_roots, &rejected_roots, use_brent);
507 if (accepted_roots.empty() && rejected_roots.empty()) {
509 <<
" No roots found for " <<
LevelLabel(spin,
opt_.qpmin + gw_level)
511 << std::max(left_limit, std::min(right_limit, frequency0))
524 if (stats !=
nullptr) {
528 if (!accepted_roots.empty()) {
532 if (!rejected_roots.empty() && !allow_rejected_return) {
538 <<
" produced only rejected roots in [" << left_limit <<
", "
539 << right_limit <<
"], forcing wider retry" << std::flush;
549 Spin spin,
double intercept0,
double frequency0,
Index gw_level,
550 double left_limit,
double right_limit,
bool allow_rejected_return,
567 const bool use_brent = (
opt_.qp_root_finder ==
"brent");
569 std::vector<QPRootCandidate> accepted_roots;
570 std::vector<QPRootCandidate> rejected_roots;
571 std::vector<std::pair<double, double>> roots;
573 if (left_limit < right_limit) {
574 double freq_prev = left_limit;
575 double targ_prev = fqp.
value(freq_prev, EvalStage::Scan);
580 const Index n_steps = std::max<Index>(
581 2,
static_cast<Index>(
582 std::ceil((right_limit - left_limit) /
opt_.qp_dense_spacing)) +
585 for (
Index i_node = 1; i_node < n_steps; ++i_node) {
587 (i_node == n_steps - 1)
589 : std::min(right_limit, left_limit +
static_cast<double>(i_node) *
590 opt_.qp_dense_spacing);
592 const double targ = fqp.
value(freq, EvalStage::Scan);
594 if (targ_prev * targ < 0.0) {
597 frequency0, solver_opt, use_brent);
599 roots.emplace_back(cand->omega, cand->Z);
600 if (cand->accepted) {
601 accepted_roots.push_back(*cand);
603 rejected_roots.push_back(*cand);
616 if (accepted_roots.empty() && rejected_roots.empty()) {
619 <<
" found no roots in [" << left_limit <<
", " << right_limit
620 <<
"]" << std::flush;
624 <<
" roots (omega:Z)\n\t\t";
625 for (
const auto& root : roots) {
627 << root.second <<
" ";
634 if (stats !=
nullptr) {
638 if (!accepted_roots.empty()) {
639 auto best = std::max_element(
640 accepted_roots.begin(), accepted_roots.end(),
642 return qp_solver::ScoreRoot(a) < qp_solver::ScoreRoot(b);
647 if (!rejected_roots.empty()) {
648 if (!allow_rejected_return) {
654 <<
" produced only rejected roots in [" << left_limit <<
", "
655 << right_limit <<
"], forcing wider retry" << std::flush;
660 auto least_bad = std::max_element(
661 rejected_roots.begin(), rejected_roots.end(),
663 return qp_solver::ScoreRoot(a) < qp_solver::ScoreRoot(b);
665 return least_bad->omega;
672 Spin spin,
double intercept0,
double frequency0,
Index gw_level,
673 double left_limit,
double right_limit,
bool allow_rejected_return,
676 if (
opt_.qp_grid_search_mode ==
"adaptive") {
678 gw_level, left_limit, right_limit,
679 allow_rejected_return, stats);
682 if (
opt_.qp_grid_search_mode ==
"dense") {
684 left_limit, right_limit,
685 allow_rejected_return, stats);
688 if (
opt_.qp_grid_search_mode ==
"adaptive_with_dense_fallback") {
691 spin, intercept0, frequency0, gw_level, left_limit, right_limit,
692 allow_rejected_return, &total_stats);
694 if (stats !=
nullptr) {
695 *stats = total_stats;
702 spin, intercept0, frequency0, gw_level, left_limit, right_limit,
703 allow_rejected_return, &dense_stats);
704 total_stats.
Add(dense_stats);
710 <<
" Adaptive QP scan failed for "
712 <<
", retrying dense grid scan in [" << left_limit <<
", "
713 << right_limit <<
"]" << std::flush;
717 if (stats !=
nullptr) {
718 *stats = total_stats;
723 throw std::runtime_error(
"Unknown gw.qp_grid_search_mode '" +
724 opt_.qp_grid_search_mode +
"'");
731 boost::optional<double> newf = boost::none;
735 opt_.g_sc_max_iterations,
opt_.g_sc_limit,
opt_.qp_solver_alpha);
737 const double freq_new = newton.
FindRoot(f, frequency0);
742 if (stats !=
nullptr) {
750 double epsilon)
const {
752 const double diff_max = (e1 - e2).cwiseAbs().maxCoeff(&state);
754 <<
" StateNo:" << state << std::flush;
755 return diff_max <= epsilon;
778Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd>
780 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> qpdiag(
getHQPAlpha());
785Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd>
787 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> qpdiag(
getHQPBeta());
793 const Eigen::VectorXd gwa =
800 << (boost::format(
" ====== Perturbative %1% quasiparticle energies "
801 "(Hartree) ======") %
806 if (
opt_.qpmin <= homo && homo + 1 <=
opt_.qpmax) {
807 const double dft_gap = dft(homo + 1) - dft(homo);
808 const double qp_gap = gwa(homo + 1 -
opt_.qpmin) - gwa(homo -
opt_.qpmin);
811 << (boost::format(
" %1% DFT gap = %2$+1.6f Hartree %1% GWA gap = "
812 "%3$+1.6f Hartree Delta = %4$+1.6f Hartree") %
813 SpinName(spin) % dft_gap % qp_gap % (qp_gap - dft_gap))
822 << (boost::format(
" = %1$4d (%2%) %3% DFT = %4$+1.4f VXC = %5$+1.4f "
823 "S-X = %6$+1.4f S-C = %7$+1.4f GWA = %8$+1.4f") %
826 SigmaC(spin)(i, i) % gwa(i))
833 const Eigen::VectorXd& qp_diag_energies)
const {
834 const Eigen::VectorXd gwa =
841 <<
" quasiparticle Hamiltonian" << std::flush;
843 if (
opt_.qpmin <= homo && homo + 1 <=
opt_.qpmax) {
844 const double dft_gap = dft(homo + 1) - dft(homo);
845 const double pqp_gap = gwa(homo + 1 -
opt_.qpmin) - gwa(homo -
opt_.qpmin);
846 const double dqp_gap = qp_diag_energies(homo + 1 -
opt_.qpmin) -
847 qp_diag_energies(homo -
opt_.qpmin);
850 << (boost::format(
" %1% DFT gap = %2$+1.6f Hartree %1% PQP gap = "
851 "%3$+1.6f Hartree %1% DQP gap = %4$+1.6f Hartree") %
852 SpinName(spin) % dft_gap % pqp_gap % dqp_gap)
861 << (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 sigma(double frequency, EvalStage stage=EvalStage::Other) const
const QPStats & GetStats() const
double deriv(double frequency) const
double value(double frequency, EvalStage stage=EvalStage::Other) const
boost::optional< double > SolveQP_Grid_Windowed(Spin spin, double intercept0, double frequency0, Index gw_level, double left_limit, double right_limit, bool allow_rejected_return=true, QPStats *stats=nullptr) 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_
boost::optional< double > SolveQP_Linearisation(Spin spin, double intercept0, double frequency0, Index gw_level, QPStats *stats=nullptr) const
std::string LevelLabel(Spin spin, Index level) const
Eigen::MatrixXd Sigma_x_alpha_
std::unique_ptr< Sigma_base_UKS > sigma_beta_
boost::optional< double > SolveQP_Grid_Windowed_Adaptive(Spin spin, double intercept0, double frequency0, Index gw_level, double left_limit, double right_limit, bool allow_rejected_return=true, QPStats *stats=nullptr) const
boost::optional< double > SolveQP_Grid(Spin spin, double intercept0, double frequency0, Index gw_level, QPStats *stats=nullptr) const
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
qp_solver::WindowDiagnostics QPWindowDiagnostics
const char * OccupationTag(Spin spin, Index level) const
Sigma_base_UKS & SigmaEvaluator(Spin spin)
Eigen::VectorXd SolveQP(Spin spin, const Eigen::VectorXd &frequencies) const
const Eigen::VectorXd & RPAInputEnergiesBeta() const
Eigen::MatrixXd getHQPBeta() 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
boost::optional< double > SolveQP_Grid_Windowed_Dense(Spin spin, double intercept0, double frequency0, Index gw_level, double left_limit, double right_limit, bool allow_rejected_return=true, QPStats *stats=nullptr) const
const Eigen::MatrixXd & vxc_beta_
const Eigen::VectorXd & dft_energies_beta_
qp_solver::RootCandidate QPRootCandidate
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()
boost::optional< double > SolveQP_FixedPoint(Spin spin, double intercept0, double frequency0, Index gw_level, QPStats *stats=nullptr) const
Index Homo(Spin spin) 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)
boost::optional< double > SolveQP_Grid_Windowed(QPFunc &fqp, double frequency0, double left_limit, double right_limit, Index gw_sc_iteration, const SolverOptions &opt, WindowDiagnostics *wdiag=nullptr, std::vector< RootCandidate > *accepted_roots_out=nullptr, std::vector< RootCandidate > *rejected_roots_out=nullptr, bool use_brent=false)
void NormalizeGridSearchOptions(Opt &opt)
boost::optional< RootCandidate > RefineQPInterval(double lowerbound, double f_lowerbound, double upperbound, double f_upperbound, const QPFunc &f, double reference, const SolverOptions &opt, bool use_brent)
Charge transport classes.
Provides a means for comparing floating point numbers.
static Level current_level
std::string quadrature_scheme
double qp_adaptive_shell_width
Index qp_adaptive_shell_count
double qp_full_window_half_width
Index qp_bisection_max_iter
std::size_t sigma_repeat_calls
std::size_t sigma_scan_calls
void Add(const Stats &other)
std::size_t TotalSigmaCalls() const
std::size_t sigma_refine_calls
std::size_t sigma_other_calls
std::size_t sigma_unique_frequencies
Index first_interval_shell
Index first_accepted_shell