votca 2026-dev
Loading...
Searching...
No Matches
gw_uks.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2026 The VOTCA Development Team
3 * (http://www.votca.org)
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License")
6 */
7
8#include <fstream>
9#include <iostream>
10#include <limits>
11
14#include "votca/xtp/gw_uks.h"
17#include "votca/xtp/rpa_uks.h"
19
21
22namespace votca {
23namespace xtp {
24
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)
30 : log_(log),
31 Mmn_(Mmn),
32 vxc_alpha_(vxc_alpha),
33 vxc_beta_(vxc_beta),
34 dft_energies_alpha_(dft_energies_alpha),
35 dft_energies_beta_(dft_energies_beta),
36 rpa_(log, Mmn) {}
37
38void GW_UKS::configure(const options& opt) {
39 opt_ = opt;
40
41 // Normalize legacy and new grid-search settings once at configuration time.
42 // This keeps the UKS path numerically aligned with the RKS path.
44 qptotal_ = opt_.qpmax - opt_.qpmin + 1;
45 rpa_.configure(opt_.homo_alpha, opt_.homo_beta, opt_.rpamin, opt_.rpamax);
46
47 sigma_alpha_ = SigmaFactory_UKS().Create(opt_.sigma_integration, Mmn_, rpa_,
49 sigma_beta_ = SigmaFactory_UKS().Create(opt_.sigma_integration, Mmn_, rpa_,
51
52 if (!sigma_alpha_ || !sigma_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'.");
56 }
57
59 sigma_opt.homo = opt_.homo_alpha;
60 sigma_opt.qpmax = opt_.qpmax;
61 sigma_opt.qpmin = opt_.qpmin;
62 sigma_opt.rpamin = opt_.rpamin;
63 sigma_opt.rpamax = opt_.rpamax;
64 sigma_opt.eta = opt_.eta;
65 sigma_opt.alpha = opt_.alpha;
66 sigma_opt.quadrature_scheme = opt_.quadrature_scheme;
67 sigma_opt.order = opt_.order;
68 sigma_alpha_->configure(sigma_opt);
69 sigma_opt.homo = opt_.homo_beta;
70 sigma_beta_->configure(sigma_opt);
71
72 Sigma_x_alpha_ = Eigen::MatrixXd::Zero(qptotal_, qptotal_);
73 Sigma_x_beta_ = Eigen::MatrixXd::Zero(qptotal_, qptotal_);
74 Sigma_c_alpha_ = Eigen::MatrixXd::Zero(qptotal_, qptotal_);
75 Sigma_c_beta_ = Eigen::MatrixXd::Zero(qptotal_, qptotal_);
76
77 auto print_spin_ranges = [&](Spin spin, Index homo) {
78 const Index lumo = homo + 1;
79
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;
84
85 const Index qp_occ_min = opt_.qpmin;
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;
89
90 const Index qp_virt_min = std::max(opt_.qpmin, lumo);
91 const Index qp_virt_max = opt_.qpmax;
92 const Index n_virt_qp =
93 (qp_virt_min <= qp_virt_max) ? (qp_virt_max - qp_virt_min + 1) : 0;
94
96 << TimeStamp() << " UKS " << SpinName(spin)
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 << ")"
104 << std::flush;
105 };
106
107 print_spin_ranges(Spin::Alpha, opt_.homo_alpha);
108 print_spin_ranges(Spin::Beta, opt_.homo_beta);
109
110 if (opt_.sigma_integration == "ppm") {
111 auto* ppm_a = dynamic_cast<Sigma_PPM_UKS*>(sigma_alpha_.get());
112 auto* ppm_b = dynamic_cast<Sigma_PPM_UKS*>(sigma_beta_.get());
113
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");
118 }
119
120 ppm_a->SetSharedPPM(ppm_);
121 ppm_b->SetSharedPPM(ppm_);
122 }
123}
124
125const Eigen::VectorXd& GW_UKS::DftEnergies(Spin spin) const {
127}
128const Eigen::MatrixXd& GW_UKS::Vxc(Spin spin) const {
129 return (spin == Spin::Alpha) ? vxc_alpha_ : vxc_beta_;
130}
131Eigen::MatrixXd& GW_UKS::SigmaX(Spin spin) {
132 return (spin == Spin::Alpha) ? Sigma_x_alpha_ : Sigma_x_beta_;
133}
134Eigen::MatrixXd& GW_UKS::SigmaC(Spin spin) {
135 return (spin == Spin::Alpha) ? Sigma_c_alpha_ : Sigma_c_beta_;
136}
137const Eigen::MatrixXd& GW_UKS::SigmaX(Spin spin) const {
138 return (spin == Spin::Alpha) ? Sigma_x_alpha_ : Sigma_x_beta_;
139}
140const Eigen::MatrixXd& GW_UKS::SigmaC(Spin spin) const {
141 return (spin == Spin::Alpha) ? Sigma_c_alpha_ : Sigma_c_beta_;
142}
147 return (spin == Spin::Alpha) ? *sigma_alpha_ : *sigma_beta_;
148}
150 return (spin == Spin::Alpha) ? opt_.homo_alpha : opt_.homo_beta;
151}
152const char* GW_UKS::SpinName(Spin spin) const {
153 return (spin == Spin::Alpha) ? "alpha" : "beta";
154}
155
156std::string GW_UKS::LevelLabel(Spin spin, Index level) const {
157 if (level == Homo(spin)) {
158 return " HOMO ";
159 } else if (level == Homo(spin) + 1) {
160 return " LUMO ";
161 } else {
162 return " Level";
163 }
164}
165
166const char* GW_UKS::OccupationTag(Spin spin, Index level) const {
167 return (level <= Homo(spin)) ? "occ" : "virt";
168}
169
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() +=
174 opt_.shift;
175 return shifted_energies;
176}
177
178double GW_UKS::CalcSpinHomoLumoShift(const Eigen::VectorXd& frequencies,
179 Spin spin) const {
180 const Index homo = Homo(spin);
181 const Eigen::VectorXd& dft = DftEnergies(spin);
182 const double DFTgap = dft(homo + 1) - dft(homo);
183 const double QPgap =
184 frequencies(homo + 1 - opt_.qpmin) - frequencies(homo - opt_.qpmin);
185 return QPgap - DFTgap;
186}
187
189 Sigma_x_alpha_ = (1 - opt_.ScaHFX) * sigma_alpha_->CalcExchangeMatrix();
190 Sigma_x_beta_ = (1 - opt_.ScaHFX) * sigma_beta_->CalcExchangeMatrix();
192 << TimeStamp()
193 << " Calculated spin-resolved Hartree exchange contribution"
194 << std::flush;
195
196 Eigen::VectorXd dft_shifted_alpha =
198 Eigen::VectorXd dft_shifted_beta =
200
202 << TimeStamp()
203 << " Scissor shifting alpha/beta DFT energies by: " << opt_.shift
204 << " Hrt" << std::flush;
205
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));
209
210 Eigen::VectorXd frequencies_alpha =
211 dft_shifted_alpha.segment(opt_.qpmin, qptotal_);
212 Eigen::VectorXd frequencies_beta =
213 dft_shifted_beta.segment(opt_.qpmin, qptotal_);
214
215 Anderson mixing_alpha;
216 Anderson mixing_beta;
217 mixing_alpha.Configure(opt_.gw_mixing_order, opt_.gw_mixing_alpha);
218 mixing_beta.Configure(opt_.gw_mixing_order, opt_.gw_mixing_alpha);
219
220 for (Index i_gw = 0; i_gw < opt_.gw_sc_max_iterations; ++i_gw) {
221 gw_sc_iteration_ = i_gw;
222 if (i_gw % opt_.reset_3c == 0 && i_gw != 0) {
223 Mmn_.alpha.Rebuild();
224 Mmn_.beta.Rebuild();
226 << TimeStamp() << " Rebuilding alpha/beta 3c integrals" << std::flush;
227 }
228
229 if (opt_.sigma_integration == "ppm") {
230 ppm_.PPM_construct_parameters(rpa_);
231 sigma_alpha_->PrepareScreening();
232 sigma_beta_->PrepareScreening();
233 } else {
234 sigma_alpha_->PrepareScreening();
235 sigma_beta_->PrepareScreening();
236 }
237
239 << TimeStamp() << " Calculated unrestricted screening via RPA"
240 << std::flush;
241
242 if (opt_.gw_mixing_order > 0 && i_gw > 0) {
243 mixing_alpha.UpdateInput(frequencies_alpha);
244 mixing_beta.UpdateInput(frequencies_beta);
245 }
246
247 frequencies_alpha = SolveQP(Spin::Alpha, frequencies_alpha);
248 frequencies_beta = SolveQP(Spin::Beta, frequencies_beta);
249
250 if (opt_.gw_sc_max_iterations > 1) {
251 Eigen::VectorXd rpa_alpha_old = rpa_.getRPAInputEnergiesAlpha();
252 Eigen::VectorXd rpa_beta_old = rpa_.getRPAInputEnergiesBeta();
253
254 if (opt_.gw_mixing_order > 0 && i_gw > 0) {
255 mixing_alpha.UpdateOutput(frequencies_alpha);
256 mixing_beta.UpdateOutput(frequencies_beta);
257 frequencies_alpha = mixing_alpha.MixHistory();
258 frequencies_beta = mixing_beta.MixHistory();
259 }
260
261 rpa_.UpdateRPAInputEnergies(dft_energies_alpha_, dft_energies_beta_,
262 frequencies_alpha, frequencies_beta,
263 opt_.qpmin);
264
266 << TimeStamp() << " GW_Iteration:" << i_gw << " Shift_alpha[Hrt]:"
267 << CalcSpinHomoLumoShift(frequencies_alpha, Spin::Alpha)
268 << " Shift_beta[Hrt]:"
269 << CalcSpinHomoLumoShift(frequencies_beta, Spin::Beta) << std::flush;
270
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;
279 break;
280 } else if (i_gw == opt_.gw_sc_max_iterations - 1) {
282 << TimeStamp()
283 << " WARNING! UKS GW-self-consistency cycle not converged after "
284 << opt_.gw_sc_max_iterations << " iterations." << std::flush;
285 break;
286 }
287 }
288 }
289
290 Sigma_c_alpha_.diagonal() =
291 sigma_alpha_->CalcCorrelationDiag(frequencies_alpha);
292 Sigma_c_beta_.diagonal() = sigma_beta_->CalcCorrelationDiag(frequencies_beta);
295}
296
297Eigen::VectorXd GW_UKS::getGWAResultsAlpha() const {
298 return Sigma_x_alpha_.diagonal() + Sigma_c_alpha_.diagonal() -
299 vxc_alpha_.diagonal() +
300 dft_energies_alpha_.segment(opt_.qpmin, qptotal_);
301}
302Eigen::VectorXd GW_UKS::getGWAResultsBeta() const {
303 return Sigma_x_beta_.diagonal() + Sigma_c_beta_.diagonal() -
304 vxc_beta_.diagonal() +
305 dft_energies_beta_.segment(opt_.qpmin, qptotal_);
306}
307const Eigen::VectorXd& GW_UKS::RPAInputEnergiesAlpha() const {
308 return rpa_.getRPAInputEnergiesAlpha();
309}
310const Eigen::VectorXd& GW_UKS::RPAInputEnergiesBeta() const {
311 return rpa_.getRPAInputEnergiesBeta();
312}
313
314Eigen::VectorXd GW_UKS::SolveQP(Spin spin,
315 const Eigen::VectorXd& frequencies) const {
316 const Eigen::VectorXd intercepts =
317 DftEnergies(spin).segment(opt_.qpmin, qptotal_) +
318 SigmaX(spin).diagonal() - Vxc(spin).diagonal();
319
320 Eigen::VectorXd frequencies_new = frequencies;
321
322 Index use_threads = qptotal_;
323#ifdef _OPENMP
324 use_threads =
326#endif
327
328#pragma omp parallel for schedule(dynamic) num_threads(use_threads)
329 for (Index gw_level = 0; gw_level < qptotal_; ++gw_level) {
330 const double initial_f = frequencies[gw_level];
331 const double intercept = intercepts[gw_level];
332
333 boost::optional<double> newf;
334 QPStats fixed_stats;
335 QPStats grid_stats;
336 QPStats lin_stats;
337
338 if (opt_.qp_solver == "fixedpoint") {
339 newf = SolveQP_FixedPoint(spin, intercept, initial_f, gw_level,
340 &fixed_stats);
341 }
342
343 if (newf) {
344 frequencies_new[gw_level] = newf.value();
345 } else {
346 newf = SolveQP_Grid(spin, intercept, initial_f, gw_level, &grid_stats);
347 if (newf) {
348 frequencies_new[gw_level] = newf.value();
349 } else {
350 newf = SolveQP_Linearisation(spin, intercept, initial_f, gw_level,
351 &lin_stats);
352 if (newf) {
353 frequencies_new[gw_level] = newf.value();
354 }
355 }
356 }
357
359#pragma omp critical
360 {
361 QPStats total_stats;
362 total_stats.Add(fixed_stats);
363 total_stats.Add(grid_stats);
364 total_stats.Add(lin_stats);
365
366 const Index mo_level = opt_.qpmin + gw_level;
367
369 << " QP stats " << LevelLabel(spin, mo_level) << " mo=" << mo_level
370 << " spin=" << SpinName(spin)
371 << " sigma_calls=" << total_stats.TotalSigmaCalls()
372 << " scan=" << total_stats.sigma_scan_calls
373 << " refine=" << total_stats.sigma_refine_calls
374 << " other=" << total_stats.sigma_other_calls
375 << " deriv_calls=" << total_stats.deriv_calls
376 << " unique_omega=" << total_stats.sigma_unique_frequencies
377 << " repeat_omega=" << total_stats.sigma_repeat_calls << std::flush;
378 }
379 }
380 }
381
382 return frequencies_new;
383}
384
385boost::optional<double> GW_UKS::SolveQP_Linearisation(Spin spin,
386 double intercept0,
387 double frequency0,
388 Index gw_level,
389 QPStats* stats) const {
390 boost::optional<double> newf = boost::none;
391
392 QPFunc fqp(gw_level, SigmaEvaluator(spin), intercept0);
393
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;
397
398 if (std::abs(Z) > 1e-9) {
399 newf = frequency0 + (intercept0 - frequency0 + sigma) / Z;
400 }
401
402 if (stats != nullptr) {
403 *stats = fqp.GetStats();
404 }
405
406 return newf;
407}
408
409boost::optional<double> GW_UKS::SolveQP_Grid(Spin spin, double intercept0,
410 double frequency0, Index gw_level,
411 QPStats* stats) const {
412 // The full QP search window is now controlled explicitly and no longer
413 // derived from the dense-grid spacing.
414 const double range = opt_.qp_full_window_half_width;
415
416 const double full_left_limit = frequency0 - range;
417 const double full_right_limit = frequency0 + range;
418
419 double restricted_left_limit = full_left_limit;
420 double restricted_right_limit = full_right_limit;
421
422 bool use_restricted_window = false;
423
424 if (opt_.qp_restrict_search) {
425 const Index mo_level = gw_level + opt_.qpmin;
426 const bool is_occupied = (mo_level <= Homo(spin));
427
428 if (is_occupied) {
429 restricted_right_limit = std::min(full_right_limit, -opt_.qp_zero_margin);
430 } else {
431 restricted_left_limit =
432 std::max(full_left_limit, opt_.qp_virtual_min_energy);
433 }
434
435 const double tol = 1e-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);
439 }
440
441 if (use_restricted_window && restricted_left_limit < restricted_right_limit) {
442 auto restricted =
443 SolveQP_Grid_Windowed(spin, intercept0, frequency0, gw_level,
444 restricted_left_limit, restricted_right_limit,
445 false, // accepted roots only
446 stats);
447
448 if (restricted) {
449 return restricted;
450 }
451
453#pragma omp critical
454 {
456 << " Restricted QP search failed for "
457 << LevelLabel(spin, opt_.qpmin + gw_level) << " in window ["
458 << restricted_left_limit << ", " << restricted_right_limit
459 << "], forcing full dense window [" << full_left_limit << ", "
460 << full_right_limit << "]" << std::flush;
461 }
462 }
463
464 return SolveQP_Grid_Windowed_Dense(spin, intercept0, frequency0, gw_level,
465 full_left_limit, full_right_limit, true,
466 stats);
467 }
468
469 return SolveQP_Grid_Windowed(spin, intercept0, frequency0, gw_level,
470 full_left_limit, full_right_limit, true, stats);
471}
472
474 Spin spin, double intercept0, double frequency0, Index gw_level,
475 double left_limit, double right_limit, bool allow_rejected_return,
476 QPStats* stats) const {
477
478 QPFunc fqp(gw_level, SigmaEvaluator(spin), intercept0);
479
480 qp_solver::SolverOptions solver_opt;
481
482 // Pass only canonical search controls into the shared grid-search utility.
483 // UKS uses the same adaptive/dense logic as RKS; only the spin-resolved
484 // sigma evaluation changes.
485 solver_opt.g_sc_limit = opt_.g_sc_limit;
486 solver_opt.qp_bisection_max_iter = opt_.g_sc_max_iterations;
487 solver_opt.qp_full_window_half_width = opt_.qp_full_window_half_width;
488 solver_opt.qp_dense_spacing = opt_.qp_dense_spacing;
489 solver_opt.qp_adaptive_shell_width = opt_.qp_adaptive_shell_width;
490 solver_opt.qp_adaptive_shell_count = opt_.qp_adaptive_shell_count;
492 std::vector<QPRootCandidate> accepted_roots;
493 std::vector<QPRootCandidate> rejected_roots;
494
495 bool use_brent = false;
496 if (opt_.qp_root_finder == "brent") {
497 use_brent = true;
498 }
499
501 fqp, frequency0, left_limit, right_limit, gw_sc_iteration_, solver_opt,
502 &wdiag, &accepted_roots, &rejected_roots, use_brent);
503
505#pragma omp critical
506 {
507 if (accepted_roots.empty() && rejected_roots.empty()) {
509 << " No roots found for " << LevelLabel(spin, opt_.qpmin + gw_level)
510 << " (center="
511 << std::max(left_limit, std::min(right_limit, frequency0))
512 << ", shells=" << wdiag.shells_explored << ")" << std::flush;
513 } else {
515 << " Adaptive scan " << LevelLabel(spin, opt_.qpmin + gw_level)
516 << " shells=" << wdiag.shells_explored
517 << " first_interval_shell=" << wdiag.first_interval_shell
518 << " first_accepted_shell=" << wdiag.first_accepted_shell
519 << " intervals=" << wdiag.intervals_found << std::flush;
520 }
521 }
522 }
523
524 if (stats != nullptr) {
525 *stats = fqp.GetStats();
526 }
527
528 if (!accepted_roots.empty()) {
529 return result;
530 }
531
532 if (!rejected_roots.empty() && !allow_rejected_return) {
534#pragma omp critical
535 {
537 << " Adaptive scan " << LevelLabel(spin, opt_.qpmin + gw_level)
538 << " produced only rejected roots in [" << left_limit << ", "
539 << right_limit << "], forcing wider retry" << std::flush;
540 }
541 }
542 return boost::none;
543 }
544
545 return result;
546}
547
549 Spin spin, double intercept0, double frequency0, Index gw_level,
550 double left_limit, double right_limit, bool allow_rejected_return,
551 QPStats* stats) const {
552
553 QPFunc fqp(gw_level, SigmaEvaluator(spin), intercept0);
554
555 qp_solver::SolverOptions solver_opt;
556
557 // The dense path performs a uniform sign-change scan over the requested
558 // interval. Its mesh is controlled by qp_dense_spacing and is independent
559 // from the adaptive shell spacing.
560 solver_opt.g_sc_limit = opt_.g_sc_limit;
561 solver_opt.qp_bisection_max_iter = opt_.g_sc_max_iterations;
562 solver_opt.qp_full_window_half_width = opt_.qp_full_window_half_width;
563 solver_opt.qp_dense_spacing = opt_.qp_dense_spacing;
564 solver_opt.qp_adaptive_shell_width = opt_.qp_adaptive_shell_width;
565 solver_opt.qp_adaptive_shell_count = opt_.qp_adaptive_shell_count;
566
567 const bool use_brent = (opt_.qp_root_finder == "brent");
568
569 std::vector<QPRootCandidate> accepted_roots;
570 std::vector<QPRootCandidate> rejected_roots;
571 std::vector<std::pair<double, double>> roots; // omega : Z
572
573 if (left_limit < right_limit) {
574 double freq_prev = left_limit;
575 double targ_prev = fqp.value(freq_prev, EvalStage::Scan);
576
577 // Dense scanning is the robustness path: it uniformly samples the entire
578 // interval to avoid missing sign changes that a shell-based scan may not
579 // hit directly.
580 const Index n_steps = std::max<Index>(
581 2, static_cast<Index>(
582 std::ceil((right_limit - left_limit) / opt_.qp_dense_spacing)) +
583 1);
584
585 for (Index i_node = 1; i_node < n_steps; ++i_node) {
586 const double freq =
587 (i_node == n_steps - 1)
588 ? right_limit
589 : std::min(right_limit, left_limit + static_cast<double>(i_node) *
590 opt_.qp_dense_spacing);
591
592 const double targ = fqp.value(freq, EvalStage::Scan);
593
594 if (targ_prev * targ < 0.0) {
595 auto cand =
596 qp_solver::RefineQPInterval(freq_prev, targ_prev, freq, targ, fqp,
597 frequency0, solver_opt, use_brent);
598 if (cand) {
599 roots.emplace_back(cand->omega, cand->Z);
600 if (cand->accepted) {
601 accepted_roots.push_back(*cand);
602 } else {
603 rejected_roots.push_back(*cand);
604 }
605 }
606 }
607
608 freq_prev = freq;
609 targ_prev = targ;
610 }
611 }
612
614#pragma omp critical
615 {
616 if (accepted_roots.empty() && rejected_roots.empty()) {
618 << " Dense scan " << LevelLabel(spin, opt_.qpmin + gw_level)
619 << " found no roots in [" << left_limit << ", " << right_limit
620 << "]" << std::flush;
621 } else {
623 << " Dense scan " << LevelLabel(spin, opt_.qpmin + gw_level)
624 << " roots (omega:Z)\n\t\t";
625 for (const auto& root : roots) {
626 XTP_LOG(Log::info, log_) << std::setprecision(5) << root.first << ":"
627 << root.second << " ";
628 }
629 XTP_LOG(Log::info, log_) << std::flush;
630 }
631 }
632 }
633
634 if (stats != nullptr) {
635 *stats = fqp.GetStats();
636 }
637
638 if (!accepted_roots.empty()) {
639 auto best = std::max_element(
640 accepted_roots.begin(), accepted_roots.end(),
641 [](const QPRootCandidate& a, const QPRootCandidate& b) {
642 return qp_solver::ScoreRoot(a) < qp_solver::ScoreRoot(b);
643 });
644 return best->omega;
645 }
646
647 if (!rejected_roots.empty()) {
648 if (!allow_rejected_return) {
650#pragma omp critical
651 {
653 << " Dense scan " << LevelLabel(spin, opt_.qpmin + gw_level)
654 << " produced only rejected roots in [" << left_limit << ", "
655 << right_limit << "], forcing wider retry" << std::flush;
656 }
657 }
658 return boost::none;
659 }
660 auto least_bad = std::max_element(
661 rejected_roots.begin(), rejected_roots.end(),
662 [](const QPRootCandidate& a, const QPRootCandidate& b) {
663 return qp_solver::ScoreRoot(a) < qp_solver::ScoreRoot(b);
664 });
665 return least_bad->omega;
666 }
667
668 return boost::none;
669}
670
671boost::optional<double> GW_UKS::SolveQP_Grid_Windowed(
672 Spin spin, double intercept0, double frequency0, Index gw_level,
673 double left_limit, double right_limit, bool allow_rejected_return,
674 QPStats* stats) const {
675
676 if (opt_.qp_grid_search_mode == "adaptive") {
677 return SolveQP_Grid_Windowed_Adaptive(spin, intercept0, frequency0,
678 gw_level, left_limit, right_limit,
679 allow_rejected_return, stats);
680 }
681
682 if (opt_.qp_grid_search_mode == "dense") {
683 return SolveQP_Grid_Windowed_Dense(spin, intercept0, frequency0, gw_level,
684 left_limit, right_limit,
685 allow_rejected_return, stats);
686 }
687
688 if (opt_.qp_grid_search_mode == "adaptive_with_dense_fallback") {
689 QPStats total_stats;
690 auto adaptive = SolveQP_Grid_Windowed_Adaptive(
691 spin, intercept0, frequency0, gw_level, left_limit, right_limit,
692 allow_rejected_return, &total_stats);
693 if (adaptive) {
694 if (stats != nullptr) {
695 *stats = total_stats;
696 }
697 return adaptive;
698 }
699
700 QPStats dense_stats;
701 auto dense = SolveQP_Grid_Windowed_Dense(
702 spin, intercept0, frequency0, gw_level, left_limit, right_limit,
703 allow_rejected_return, &dense_stats);
704 total_stats.Add(dense_stats);
705
707#pragma omp critical
708 {
710 << " Adaptive QP scan failed for "
711 << LevelLabel(spin, opt_.qpmin + gw_level)
712 << ", retrying dense grid scan in [" << left_limit << ", "
713 << right_limit << "]" << std::flush;
714 }
715 }
716
717 if (stats != nullptr) {
718 *stats = total_stats;
719 }
720 return dense;
721 }
722
723 throw std::runtime_error("Unknown gw.qp_grid_search_mode '" +
724 opt_.qp_grid_search_mode + "'");
725}
726
727boost::optional<double> GW_UKS::SolveQP_FixedPoint(Spin spin, double intercept0,
728 double frequency0,
729 Index gw_level,
730 QPStats* stats) const {
731 boost::optional<double> newf = boost::none;
732
733 QPFunc f(gw_level, SigmaEvaluator(spin), intercept0);
735 opt_.g_sc_max_iterations, opt_.g_sc_limit, opt_.qp_solver_alpha);
736
737 const double freq_new = newton.FindRoot(f, frequency0);
738 if (newton.getInfo() == NewtonRapson<QPFunc>::success) {
739 newf = freq_new;
740 }
741
742 if (stats != nullptr) {
743 *stats = f.GetStats();
744 }
745
746 return newf;
747}
748
749bool GW_UKS::Converged(const Eigen::VectorXd& e1, const Eigen::VectorXd& e2,
750 double epsilon) const {
751 Index state = 0;
752 const double diff_max = (e1 - e2).cwiseAbs().maxCoeff(&state);
753 XTP_LOG(Log::info, log_) << TimeStamp() << " E_diff max=" << diff_max
754 << " StateNo:" << state << std::flush;
755 return diff_max <= epsilon;
756}
757
759 Eigen::VectorXd diag_backup_alpha = Sigma_c_alpha_.diagonal();
760 Eigen::VectorXd diag_backup_beta = Sigma_c_beta_.diagonal();
761 Sigma_c_alpha_ = sigma_alpha_->CalcCorrelationOffDiag(getGWAResultsAlpha());
762 Sigma_c_beta_ = sigma_beta_->CalcCorrelationOffDiag(getGWAResultsBeta());
763 Sigma_c_alpha_.diagonal() = diag_backup_alpha;
764 Sigma_c_beta_.diagonal() = diag_backup_beta;
765}
766
767Eigen::MatrixXd GW_UKS::getHQPAlpha() const {
769 Eigen::MatrixXd(
770 dft_energies_alpha_.segment(opt_.qpmin, qptotal_).asDiagonal());
771}
772Eigen::MatrixXd GW_UKS::getHQPBeta() const {
774 Eigen::MatrixXd(
775 dft_energies_beta_.segment(opt_.qpmin, qptotal_).asDiagonal());
776}
777
778Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd>
780 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> qpdiag(getHQPAlpha());
781 PrintQP_Energies(Spin::Alpha, qpdiag.eigenvalues());
782 return qpdiag;
783}
784
785Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd>
787 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> qpdiag(getHQPBeta());
788 PrintQP_Energies(Spin::Beta, qpdiag.eigenvalues());
789 return qpdiag;
790}
791
793 const Eigen::VectorXd gwa =
795
796 const Index homo = Homo(spin);
797 const Eigen::VectorXd& dft = DftEnergies(spin);
798
800 << (boost::format(" ====== Perturbative %1% quasiparticle energies "
801 "(Hartree) ======") %
802 SpinName(spin))
803 .str()
804 << std::flush;
805
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);
809
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))
814 .str()
815 << std::flush;
816 }
817
818 for (Index i = 0; i < qptotal_; i++) {
819 const Index level = i + opt_.qpmin;
821 << LevelLabel(spin, level)
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") %
824 level % OccupationTag(spin, level) % SpinName(spin) %
825 DftEnergies(spin)(level) % Vxc(spin)(i, i) % SigmaX(spin)(i, i) %
826 SigmaC(spin)(i, i) % gwa(i))
827 .str()
828 << std::flush;
829 }
830}
831
833 const Eigen::VectorXd& qp_diag_energies) const {
834 const Eigen::VectorXd gwa =
836
837 const Index homo = Homo(spin);
838 const Eigen::VectorXd& dft = DftEnergies(spin);
839
840 XTP_LOG(Log::error, log_) << TimeStamp() << " Full " << SpinName(spin)
841 << " quasiparticle Hamiltonian" << std::flush;
842
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);
848
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)
853 .str()
854 << std::flush;
855 }
856
857 for (Index i = 0; i < qptotal_; i++) {
858 const Index level = i + opt_.qpmin;
860 << LevelLabel(spin, level)
861 << (boost::format(" = %1$4d (%2%) %3% PQP = %4$+1.6f DQP = %5$+1.6f") %
862 level % OccupationTag(spin, level) % SpinName(spin) % gwa(i) %
863 qp_diag_energies(i))
864 .str()
865 << std::flush;
866 }
867}
868
869} // namespace xtp
870} // namespace votca
virtual std::unique_ptr< T > Create(const key_t &key, args_t &&...arguments)
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
Definition gw_uks.h:116
const QPStats & GetStats() const
Definition gw_uks.h:141
double deriv(double frequency) const
Definition gw_uks.h:134
double value(double frequency, EvalStage stage=EvalStage::Other) const
Definition gw_uks.h:130
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
Definition gw_uks.cc:671
TCMatrix_gwbse_spin & Mmn_
Definition gw_uks.h:241
Eigen::SelfAdjointEigenSolver< Eigen::MatrixXd > DiagonalizeQPHamiltonianAlpha() const
Definition gw_uks.cc:779
bool Converged(const Eigen::VectorXd &e1, const Eigen::VectorXd &e2, double epsilon) const
Definition gw_uks.cc:749
void PrintQP_Energies(Spin spin, const Eigen::VectorXd &qp_diag_energies) const
Definition gw_uks.cc:832
Eigen::MatrixXd & SigmaC(Spin spin)
Definition gw_uks.cc:134
const Eigen::VectorXd & DftEnergies(Spin spin) const
Definition gw_uks.cc:125
std::unique_ptr< Sigma_base_UKS > sigma_alpha_
Definition gw_uks.h:247
boost::optional< double > SolveQP_Linearisation(Spin spin, double intercept0, double frequency0, Index gw_level, QPStats *stats=nullptr) const
Definition gw_uks.cc:385
std::string LevelLabel(Spin spin, Index level) const
Definition gw_uks.cc:156
Eigen::MatrixXd Sigma_x_alpha_
Definition gw_uks.h:249
std::unique_ptr< Sigma_base_UKS > sigma_beta_
Definition gw_uks.h:248
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
Definition gw_uks.cc:473
boost::optional< double > SolveQP_Grid(Spin spin, double intercept0, double frequency0, Index gw_level, QPStats *stats=nullptr) const
Definition gw_uks.cc:409
Eigen::MatrixXd Sigma_c_beta_
Definition gw_uks.h:252
Eigen::MatrixXd & SigmaX(Spin spin)
Definition gw_uks.cc:131
Eigen::VectorXd getGWAResultsAlpha() const
Definition gw_uks.cc:297
const Eigen::MatrixXd & vxc_alpha_
Definition gw_uks.h:242
Eigen::VectorXd ScissorShift_DFTlevel(const Eigen::VectorXd &dft_energies, Index homo) const
Definition gw_uks.cc:170
void CalculateHQP()
Definition gw_uks.cc:758
const Eigen::VectorXd & RPAInputEnergiesAlpha() const
Definition gw_uks.cc:307
qp_solver::WindowDiagnostics QPWindowDiagnostics
Definition gw_uks.h:30
const char * OccupationTag(Spin spin, Index level) const
Definition gw_uks.cc:166
qp_solver::Stats QPStats
Definition gw_uks.h:28
Sigma_base_UKS & SigmaEvaluator(Spin spin)
Definition gw_uks.cc:143
Eigen::VectorXd SolveQP(Spin spin, const Eigen::VectorXd &frequencies) const
Definition gw_uks.cc:314
const Eigen::VectorXd & RPAInputEnergiesBeta() const
Definition gw_uks.cc:310
Eigen::MatrixXd getHQPBeta() const
Definition gw_uks.cc:772
void PrintGWA_Energies(Spin spin) const
Definition gw_uks.cc:792
Eigen::VectorXd getGWAResultsBeta() const
Definition gw_uks.cc:302
Eigen::MatrixXd getHQPAlpha() const
Definition gw_uks.cc:767
Eigen::MatrixXd Sigma_x_beta_
Definition gw_uks.h:250
Index gw_sc_iteration_
Definition gw_uks.h:240
const char * SpinName(Spin spin) const
Definition gw_uks.cc:152
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)
Definition gw_uks.cc:25
const Eigen::VectorXd & dft_energies_alpha_
Definition gw_uks.h:244
double CalcSpinHomoLumoShift(const Eigen::VectorXd &frequencies, Spin spin) const
Definition gw_uks.cc:178
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
Definition gw_uks.cc:548
const Eigen::MatrixXd & vxc_beta_
Definition gw_uks.h:243
const Eigen::VectorXd & dft_energies_beta_
Definition gw_uks.h:245
Logger & log_
Definition gw_uks.h:239
qp_solver::RootCandidate QPRootCandidate
Definition gw_uks.h:29
Eigen::SelfAdjointEigenSolver< Eigen::MatrixXd > DiagonalizeQPHamiltonianBeta() const
Definition gw_uks.cc:786
Eigen::MatrixXd Sigma_c_alpha_
Definition gw_uks.h:251
const Eigen::MatrixXd & Vxc(Spin spin) const
Definition gw_uks.cc:128
void configure(const options &opt)
Definition gw_uks.cc:38
void CalculateGWPerturbation()
Definition gw_uks.cc:188
boost::optional< double > SolveQP_FixedPoint(Spin spin, double intercept0, double frequency0, Index gw_level, QPStats *stats=nullptr) const
Definition gw_uks.cc:727
Index Homo(Spin spin) const
Definition gw_uks.cc:149
Logger is used for thread-safe output of messages.
Definition logger.h:164
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()
Definition logger.h:224
#define XTP_LOG(level, log)
Definition logger.h:40
Index getMaxThreads()
Definition eigen.h:128
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.
Definition ERIs.h:28
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26
static Level current_level
Definition globals.h:30
void Add(const Stats &other)
std::size_t TotalSigmaCalls() const