votca 2026-dev
Loading...
Searching...
No Matches
gw.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2023 The VOTCA Development Team
3 * (http://www.votca.org)
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License")
6 *
7 * You may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19
20// Standard includes
21#include <fstream>
22#include <iostream>
23
24// Local VOTCA includes
27#include "votca/xtp/gw.h"
29#include "votca/xtp/rpa.h"
31
32namespace votca {
33namespace xtp {
34
35void GW::configure(const options& opt) {
36 opt_ = opt;
37
38 // Normalize legacy and new grid-search settings once at configuration time.
39 // From this point on, the solver operates only on the decoupled canonical
40 // controls, independent of how the user specified them in XML.
42 qptotal_ = opt_.qpmax - opt_.qpmin + 1;
43 rpa_.configure(opt_.homo, opt_.rpamin, opt_.rpamax);
44 sigma_ = SigmaFactory().Create(opt_.sigma_integration, Mmn_, rpa_);
45 Sigma_base::options sigma_opt;
46 sigma_opt.homo = opt_.homo;
47 sigma_opt.qpmax = opt_.qpmax;
48 sigma_opt.qpmin = opt_.qpmin;
49 sigma_opt.rpamin = opt_.rpamin;
50 sigma_opt.rpamax = opt_.rpamax;
51 sigma_opt.eta = opt_.eta;
52 sigma_opt.alpha = opt_.alpha;
53 sigma_opt.quadrature_scheme = opt_.quadrature_scheme;
54 sigma_opt.order = opt_.order;
55 sigma_->configure(sigma_opt);
56 Sigma_x_ = Eigen::MatrixXd::Zero(qptotal_, qptotal_);
57 Sigma_c_ = Eigen::MatrixXd::Zero(qptotal_, qptotal_);
58}
59
60double GW::CalcHomoLumoShift(Eigen::VectorXd frequencies) const {
61 double DFTgap = dft_energies_(opt_.homo + 1) - dft_energies_(opt_.homo);
62 double QPgap = frequencies(opt_.homo + 1 - opt_.qpmin) -
63 frequencies(opt_.homo - opt_.qpmin);
64 return QPgap - DFTgap;
65}
66
67Eigen::MatrixXd GW::getHQP() const {
68 return Sigma_x_ + Sigma_c_ - vxc_ +
69 Eigen::MatrixXd(
70 dft_energies_.segment(opt_.qpmin, qptotal_).asDiagonal());
71}
72
73Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> GW::DiagonalizeQPHamiltonian()
74 const {
75 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> qpdiag(getHQP());
76 PrintQP_Energies(qpdiag.eigenvalues());
77 return qpdiag;
78}
79
81 Eigen::VectorXd gwa_energies = getGWAResults();
82 double shift = CalcHomoLumoShift(gwa_energies);
83
85 << (boost::format(
86 " ====== Perturbative quasiparticle energies (Hartree) ====== "))
87 .str()
88 << std::flush;
90 << (boost::format(" DeltaHLGap = %1$+1.6f Hartree") % shift).str()
91 << std::flush;
92
93 for (Index i = 0; i < qptotal_; i++) {
94 std::string level = " Level";
95 if ((i + opt_.qpmin) == opt_.homo) {
96 level = " HOMO ";
97 } else if ((i + opt_.qpmin) == opt_.homo + 1) {
98 level = " LUMO ";
99 }
100
102 << level
103 << (boost::format(" = %1$4d DFT = %2$+1.4f VXC = %3$+1.4f S-X = "
104 "%4$+1.4f S-C = %5$+1.4f GWA = %6$+1.4f") %
105 (i + opt_.qpmin) % dft_energies_(i + opt_.qpmin) % vxc_(i, i) %
106 Sigma_x_(i, i) % Sigma_c_(i, i) % gwa_energies(i))
107 .str()
108 << std::flush;
109 }
110 return;
111}
112
113void GW::PrintQSGW_Composition(double threshold) const {
115 << TimeStamp()
116 << " ====== QSGW QP state composition (dominant KS contributions) ======"
117 << std::flush;
118
119 for (Index n = 0; n < qptotal_; n++) {
120 const Index abs_n = n + opt_.qpmin;
121 std::string level = " Level";
122 if (abs_n == opt_.homo)
123 level = " HOMO ";
124 else if (abs_n == opt_.homo + 1)
125 level = " LUMO ";
126
127 // Collect contributions above threshold, sorted by weight descending
128 Eigen::VectorXd weights = qsgw_rotation_.col(n).cwiseAbs2();
129 std::vector<std::pair<double, Index>> contribs;
130 for (Index m = 0; m < qptotal_; m++) {
131 if (weights(m) >= threshold) {
132 contribs.push_back({weights(m), m + opt_.qpmin});
133 }
134 }
135 std::sort(contribs.begin(), contribs.end(),
136 [](const auto& a, const auto& b) { return a.first > b.first; });
137
138 std::string line;
139 for (const auto& [w, ks] : contribs) {
140 std::string ks_label;
141 if (ks == opt_.homo)
142 ks_label = "(HOMO)";
143 else if (ks == opt_.homo + 1)
144 ks_label = "(LUMO)";
145
146 line += (boost::format(" %1$5.1f%% KS_%2$d%3$s") % (100.0 * w) % ks %
147 ks_label)
148 .str();
149 }
151 << level << (boost::format(" = %1$4d:") % abs_n).str() << line
152 << std::flush;
153 }
154}
155
156void GW::PrintQSGW_Energies(const std::string& seed_label,
157 const Eigen::VectorXd& seed_energies,
158 const Eigen::VectorXd& qsgw_energies) const {
160 << TimeStamp() << " QSGW quasiparticle energies" << std::flush;
162 << (boost::format(
163 " ====== QSGW quasiparticle energies (Hartree) ====== "))
164 .str()
165 << std::flush;
166
167 for (Index i = 0; i < qptotal_; i++) {
168 std::string level = " Level";
169 if ((i + opt_.qpmin) == opt_.homo) {
170 level = " HOMO ";
171 } else if ((i + opt_.qpmin) == opt_.homo + 1) {
172 level = " LUMO ";
173 }
175 << level
176 << (boost::format(" = %1$4d %2$s = %3$+1.6f QSGW = %4$+1.6f") %
177 (i + opt_.qpmin) % seed_label % seed_energies(i) % qsgw_energies(i))
178 .str()
179 << std::flush;
180 }
181}
182
183void GW::PrintQP_Energies(const Eigen::VectorXd& qp_diag_energies) const {
184 Eigen::VectorXd gwa_energies = getGWAResults();
186 << TimeStamp() << " Full quasiparticle Hamiltonian " << std::flush;
188 << (boost::format(
189 " ====== Diagonalized quasiparticle energies (Hartree) "
190 "====== "))
191 .str()
192 << std::flush;
193 for (Index i = 0; i < qptotal_; i++) {
194 std::string level = " Level";
195 if ((i + opt_.qpmin) == opt_.homo) {
196 level = " HOMO ";
197 } else if ((i + opt_.qpmin) == opt_.homo + 1) {
198 level = " LUMO ";
199 }
201 << level
202 << (boost::format(" = %1$4d PQP = %2$+1.6f DQP = %3$+1.6f ") %
203 (i + opt_.qpmin) % gwa_energies(i) % qp_diag_energies(i))
204 .str()
205 << std::flush;
206 }
207 return;
208}
209
211 const Eigen::VectorXd& dft_energies) const {
212 Eigen::VectorXd shifted_energies = dft_energies;
213 shifted_energies.segment(opt_.homo + 1, dft_energies.size() - opt_.homo - 1)
214 .array() += opt_.shift;
215 return shifted_energies;
216}
217
219
220 Sigma_x_ = (1 - opt_.ScaHFX) * sigma_->CalcExchangeMatrix();
222 << TimeStamp() << " Calculated Hartree exchange contribution"
223 << std::flush;
224 // dftenergies has size aobasissize
225 // rpaenergies/Mmn have size rpatotal
226 // gwaenergies/frequencies have size qptotal
227 // homo index is relative to dft_energies
229 << TimeStamp() << " Scissor shifting DFT energies by: " << opt_.shift
230 << " Hrt" << std::flush;
231 Eigen::VectorXd dft_shifted_energies = ScissorShift_DFTlevel(dft_energies_);
232 rpa_.setRPAInputEnergies(
233 dft_shifted_energies.segment(opt_.rpamin, opt_.rpamax - opt_.rpamin + 1));
234 Eigen::VectorXd frequencies =
235 dft_shifted_energies.segment(opt_.qpmin, qptotal_);
236
237 Anderson mixing_;
238 mixing_.Configure(opt_.gw_mixing_order, opt_.gw_mixing_alpha);
239
240 for (Index i_gw = 0; i_gw < opt_.gw_sc_max_iterations; ++i_gw) {
241 gw_sc_iteration_ = i_gw;
242 if (i_gw % opt_.reset_3c == 0 && i_gw != 0) {
243 Mmn_.Rebuild();
245 << TimeStamp() << " Rebuilding 3c integrals" << std::flush;
246 }
247 sigma_->PrepareScreening();
249 << TimeStamp() << " Calculated screening via RPA" << std::flush;
251 << TimeStamp() << " Solving QP equations " << std::flush;
252 if (opt_.gw_mixing_order > 0 && i_gw > 0) {
253 mixing_.UpdateInput(frequencies);
254 }
255
256 frequencies = SolveQP(frequencies);
257
258 if (opt_.gw_sc_max_iterations > 1) {
259 Eigen::VectorXd rpa_energies_old = rpa_.getRPAInputEnergies();
260 if (opt_.gw_mixing_order > 0 && i_gw > 0) {
261 if (opt_.gw_mixing_order == 1) {
263 << "GWSC using linear mixing with alpha: " << opt_.gw_mixing_alpha
264 << std::flush;
265 } else {
267 << "GWSC using Anderson mixing with history "
268 << opt_.gw_mixing_order << ", alpha: " << opt_.gw_mixing_alpha
269 << std::flush;
270 }
271 mixing_.UpdateOutput(frequencies);
272 Eigen::VectorXd mixed_frequencies = mixing_.MixHistory();
273 rpa_.UpdateRPAInputEnergies(dft_energies_, mixed_frequencies,
274 opt_.qpmin);
275 frequencies = mixed_frequencies;
276
277 } else {
278 XTP_LOG(Log::debug, log_) << "GWSC using plain update " << std::flush;
279 rpa_.UpdateRPAInputEnergies(dft_energies_, frequencies, opt_.qpmin);
280 }
281
282 for (int i = 0; i < frequencies.size(); i++) {
284 << "... GWSC iter " << i_gw << " state " << i << " "
285 << std::setprecision(9) << frequencies(i) << std::flush;
286 }
287
289 << TimeStamp() << " GW_Iteration:" << i_gw
290 << " Shift[Hrt]:" << CalcHomoLumoShift(frequencies) << std::flush;
291 if (Converged(rpa_.getRPAInputEnergies(), rpa_energies_old,
292 opt_.gw_sc_limit)) {
293 XTP_LOG(Log::info, log_) << TimeStamp() << " Converged after "
294 << i_gw + 1 << " GW iterations." << std::flush;
295 break;
296 } else if (i_gw == opt_.gw_sc_max_iterations - 1) {
298 << TimeStamp()
299 << " WARNING! GW-self-consistency cycle not converged after "
300 << opt_.gw_sc_max_iterations << " iterations." << std::flush;
302 << TimeStamp() << " Run continues. Inspect results carefully!"
303 << std::flush;
304 break;
305 }
306 }
307 }
308 Sigma_c_.diagonal() = sigma_->CalcCorrelationDiag(frequencies);
310}
311
312Eigen::VectorXd GW::getGWAResults() const {
313 // When QSGW with virtual trimming has run, return the pre-computed merged
314 // energy vector (QSGW for the trimmed window, seed for excluded virtuals).
315 // This avoids recomputing from sigma matrices which were zeroed post-loop.
316 if (qsgw_final_energies_.size() > 0) {
318 }
319 return Sigma_x_.diagonal() + Sigma_c_.diagonal() - vxc_.diagonal() +
320 dft_energies_.segment(opt_.qpmin, qptotal_);
321}
322
323Eigen::VectorXd GW::SolveQP(const Eigen::VectorXd& frequencies) const {
324 sigma_->ResetDiagEvalCounter();
325 Eigen::VectorXd env = Eigen::VectorXd::Zero(qptotal_);
326
327 const Eigen::VectorXd intercepts =
328 dft_energies_.segment(opt_.qpmin, qptotal_) + Sigma_x_.diagonal() -
329 vxc_.diagonal();
330
331 Eigen::VectorXd frequencies_new = frequencies;
332 Eigen::Array<bool, Eigen::Dynamic, 1> converged =
333 Eigen::Array<bool, Eigen::Dynamic, 1>::Zero(qptotal_);
334
335 QPStats total_stats;
336
337#ifdef _OPENMP
338 Index use_threads =
340#else
341 Index use_threads = 1;
342#endif
343
344#pragma omp parallel for schedule(dynamic) num_threads(use_threads)
345 for (Index gw_level = 0; gw_level < qptotal_; ++gw_level) {
346
347 double initial_f = frequencies[gw_level];
348 double intercept = intercepts[gw_level];
349 boost::optional<double> newf;
350 QPStats local_stats;
351
352 if (opt_.qp_solver == "fixedpoint") {
353 newf = SolveQP_FixedPoint(intercept, initial_f, gw_level, &local_stats);
354 }
355 if (newf) {
356 frequencies_new[gw_level] = newf.value();
357 converged[gw_level] = true;
358 } else {
359 newf = SolveQP_Grid(intercept, initial_f, gw_level, &local_stats);
360 if (newf) {
361 frequencies_new[gw_level] = newf.value();
362 converged[gw_level] = true;
363 } else {
364 newf =
365 SolveQP_Linearisation(intercept, initial_f, gw_level, &local_stats);
366 if (newf) {
367 frequencies_new[gw_level] = newf.value();
368 }
369 }
370 }
371
372#pragma omp critical
373 {
374 total_stats.Add(local_stats);
375 }
376 }
377
378 if (!converged.all()) {
379 std::vector<Index> states;
380 for (Index s = 0; s < converged.size(); s++) {
381 if (!converged[s]) {
382 states.push_back(s);
383 }
384 }
385 IndexParser rp;
386 XTP_LOG(Log::error, log_) << TimeStamp() << " Not converged PQP states are:"
387 << rp.CreateIndexString(states) << std::flush;
389 << TimeStamp() << " Increase the grid search interval" << std::flush;
390 }
391
393 << " Sigma diagonal evaluations in SolveQP: "
394 << sigma_->GetDiagEvalCounter() << std::flush;
395
396 XTP_LOG(Log::info, log_) << TimeStamp() << " QP diagnostics: "
397 << "scan=" << total_stats.sigma_scan_calls
398 << " refine=" << total_stats.sigma_refine_calls
399 << " deriv_sigma="
400 << total_stats.sigma_derivative_calls
401 << " other=" << total_stats.sigma_other_calls
402 << " total_sigma=" << total_stats.TotalSigmaCalls()
403 << " unique_omega="
404 << total_stats.sigma_unique_frequencies
405 << " repeat_sigma=" << total_stats.sigma_repeat_calls
406 << " deriv_calls=" << total_stats.deriv_calls
407 << std::flush;
408
409 return frequencies_new;
410}
411
412boost::optional<double> GW::SolveQP_Linearisation(double intercept0,
413 double frequency0,
414 Index gw_level,
415 QPStats* stats) const {
416 boost::optional<double> newf = boost::none;
417
418 QPFunc fqp(gw_level, *sigma_.get(), intercept0);
419
420 double sigma = fqp.sigma(frequency0, EvalStage::Other);
421 double dsigma_domega = fqp.deriv(frequency0);
422 double Z = 1.0 - dsigma_domega;
423 if (std::abs(Z) > 1e-9) {
424 newf = frequency0 + (intercept0 - frequency0 + sigma) / Z;
425 }
426
427 if (stats != nullptr) {
428 *stats = fqp.GetStats();
429 }
430 return newf;
431}
432
434 double intercept0, double frequency0, Index gw_level, double left_limit,
435 double right_limit, bool allow_rejected_return, QPStats* stats) const {
436
437 QPFunc fqp(gw_level, *sigma_.get(), intercept0);
438
439 qp_solver::SolverOptions solver_opt;
440
441 // Pass only canonical search controls into the shared grid-search utility.
442 // RKS and UKS intentionally use the same search semantics; only the sigma
443 // evaluator differs between the two implementations.
444 solver_opt.g_sc_limit = opt_.g_sc_limit;
445 solver_opt.qp_bisection_max_iter = opt_.g_sc_max_iterations;
446 solver_opt.qp_full_window_half_width = opt_.qp_full_window_half_width;
447 solver_opt.qp_dense_spacing = opt_.qp_dense_spacing;
448 solver_opt.qp_adaptive_shell_width = opt_.qp_adaptive_shell_width;
449 solver_opt.qp_adaptive_shell_count = opt_.qp_adaptive_shell_count;
450
452 std::vector<QPRootCandidate> accepted_roots;
453 std::vector<QPRootCandidate> rejected_roots;
454
455 bool use_brent = false;
456 if (opt_.qp_root_finder == "brent") {
457 use_brent = true;
458 }
459
461 fqp, frequency0, left_limit, right_limit, gw_sc_iteration_, solver_opt,
462 &wdiag, &accepted_roots, &rejected_roots, use_brent);
463
465#pragma omp critical
466 {
467 if (!accepted_roots.empty() || !rejected_roots.empty()) {
469 << " Adaptive scan qplevel:" << gw_level << " center="
470 << std::max(left_limit, std::min(right_limit, frequency0))
471 << " shells=" << wdiag.shells_explored
472 << " first_interval_shell=" << wdiag.first_interval_shell
473 << " first_accepted_shell=" << wdiag.first_accepted_shell
474 << " intervals=" << wdiag.intervals_found << std::flush;
475 }
476 }
477 }
478
479 if (stats != nullptr) {
480 *stats = fqp.GetStats();
481 }
482
483 if (!accepted_roots.empty()) {
484 return result;
485 }
486
487 if (!rejected_roots.empty() && !allow_rejected_return) {
489#pragma omp critical
490 {
492 << " Adaptive scan qplevel:" << gw_level
493 << " produced only rejected roots in [" << left_limit << ", "
494 << right_limit << "], forcing wider retry" << std::flush;
495 }
496 }
497 return boost::none;
498 }
499
500 return result;
501}
502
503boost::optional<double> GW::SolveQP_Grid_Windowed_Dense(
504 double intercept0, double frequency0, Index gw_level, double left_limit,
505 double right_limit, bool allow_rejected_return, QPStats* stats) const {
506
507 QPFunc fqp(gw_level, *sigma_.get(), intercept0);
508
509 qp_solver::SolverOptions solver_opt;
510
511 // The dense path performs a uniform sign-change scan over the requested
512 // interval. Its spacing is qp_dense_spacing, not the adaptive shell width.
513 solver_opt.g_sc_limit = opt_.g_sc_limit;
514 solver_opt.qp_bisection_max_iter = opt_.g_sc_max_iterations;
515 solver_opt.qp_full_window_half_width = opt_.qp_full_window_half_width;
516 solver_opt.qp_dense_spacing = opt_.qp_dense_spacing;
517 solver_opt.qp_adaptive_shell_width = opt_.qp_adaptive_shell_width;
518 solver_opt.qp_adaptive_shell_count = opt_.qp_adaptive_shell_count;
519
520 const bool use_brent = (opt_.qp_root_finder == "brent");
521
522 std::vector<QPRootCandidate> accepted_roots;
523 std::vector<QPRootCandidate> rejected_roots;
524 std::vector<std::pair<double, double>> roots; // omega : Z
525
526 if (left_limit < right_limit) {
527 double freq_prev = left_limit;
528 double targ_prev = fqp.value(freq_prev, EvalStage::Scan);
529
530 // Dense scanning is the robustness path: it uniformly samples the entire
531 // interval to avoid missing sign changes that a coarser adaptive shell
532 // search might step over.
533 const Index n_steps = std::max<Index>(
534 2, static_cast<Index>(
535 std::ceil((right_limit - left_limit) / opt_.qp_dense_spacing)) +
536 1);
537
538 for (Index i_node = 1; i_node < n_steps; ++i_node) {
539 const double freq =
540 (i_node == n_steps - 1)
541 ? right_limit
542 : std::min(right_limit, left_limit + static_cast<double>(i_node) *
543 opt_.qp_dense_spacing);
544
545 const double targ = fqp.value(freq, EvalStage::Scan);
546
547 if (targ_prev * targ < 0.0) {
548 auto cand =
549 qp_solver::RefineQPInterval(freq_prev, targ_prev, freq, targ, fqp,
550 frequency0, solver_opt, use_brent);
551 if (cand) {
552 roots.emplace_back(cand->omega, cand->Z);
553 if (cand->accepted) {
554 accepted_roots.push_back(*cand);
555 } else {
556 rejected_roots.push_back(*cand);
557 }
558 }
559 }
560
561 freq_prev = freq;
562 targ_prev = targ;
563 }
564 }
565
567#pragma omp critical
568 {
569 if (accepted_roots.empty() && rejected_roots.empty()) {
571 << " Dense scan qplevel:" << gw_level << " found no roots in ["
572 << left_limit << ", " << right_limit << "]" << std::flush;
573 } else {
575 << " Dense scan qplevel:" << gw_level << " roots (omega:Z)\n\t\t";
576 for (const auto& root : roots) {
577 XTP_LOG(Log::info, log_) << std::setprecision(5) << root.first << ":"
578 << root.second << " ";
579 }
580 XTP_LOG(Log::info, log_) << std::flush;
581 }
582 }
583 }
584
585 if (stats != nullptr) {
586 *stats = fqp.GetStats();
587 }
588
589 if (!accepted_roots.empty()) {
590 auto best = std::max_element(
591 accepted_roots.begin(), accepted_roots.end(),
592 [](const QPRootCandidate& a, const QPRootCandidate& b) {
593 return qp_solver::ScoreRoot(a) < qp_solver::ScoreRoot(b);
594 });
595 return best->omega;
596 }
597
598 if (!rejected_roots.empty()) {
599 if (!allow_rejected_return) {
601#pragma omp critical
602 {
604 << " Dense scan qplevel:" << gw_level
605 << " produced only rejected roots in [" << left_limit << ", "
606 << right_limit << "], forcing wider retry" << std::flush;
607 }
608 }
609 return boost::none;
610 }
611
612 auto least_bad = std::max_element(
613 rejected_roots.begin(), rejected_roots.end(),
614 [](const QPRootCandidate& a, const QPRootCandidate& b) {
615 return qp_solver::ScoreRoot(a) < qp_solver::ScoreRoot(b);
616 });
617 return least_bad->omega;
618 }
619
620 return boost::none;
621}
622
623boost::optional<double> GW::SolveQP_Grid_Windowed(
624 double intercept0, double frequency0, Index gw_level, double left_limit,
625 double right_limit, bool allow_rejected_return, QPStats* stats) const {
626
627 if (opt_.qp_grid_search_mode == "adaptive") {
628 return SolveQP_Grid_Windowed_Adaptive(intercept0, frequency0, gw_level,
629 left_limit, right_limit,
630 allow_rejected_return, stats);
631 }
632
633 if (opt_.qp_grid_search_mode == "dense") {
634 return SolveQP_Grid_Windowed_Dense(intercept0, frequency0, gw_level,
635 left_limit, right_limit,
636 allow_rejected_return, stats);
637 }
638
639 if (opt_.qp_grid_search_mode == "adaptive_with_dense_fallback") {
640 QPStats total_stats;
641 auto adaptive = SolveQP_Grid_Windowed_Adaptive(
642 intercept0, frequency0, gw_level, left_limit, right_limit,
643 allow_rejected_return, &total_stats);
644 if (adaptive) {
645 if (stats != nullptr) {
646 *stats = total_stats;
647 }
648 return adaptive;
649 }
650
651 QPStats dense_stats;
652 auto dense = SolveQP_Grid_Windowed_Dense(
653 intercept0, frequency0, gw_level, left_limit, right_limit,
654 allow_rejected_return, &dense_stats);
655 total_stats.Add(dense_stats);
656
658#pragma omp critical
659 {
661 << " Adaptive QP scan failed for qplevel:" << gw_level
662 << ", retrying dense grid scan in [" << left_limit << ", "
663 << right_limit << "]" << std::flush;
664 }
665 }
666
667 if (stats != nullptr) {
668 *stats = total_stats;
669 }
670 return dense;
671 }
672
673 throw std::runtime_error("Unknown gw.qp_grid_search_mode '" +
674 opt_.qp_grid_search_mode + "'");
675}
676
677boost::optional<double> GW::SolveQP_Grid(double intercept0, double frequency0,
678 Index gw_level, QPStats* stats) const {
679 // The full QP search window is now controlled explicitly and no longer
680 // inferred from the dense-grid spacing.
681 const double range = opt_.qp_full_window_half_width;
682
683 const double full_left_limit = frequency0 - range;
684 const double full_right_limit = frequency0 + range;
685
686 double restricted_left_limit = full_left_limit;
687 double restricted_right_limit = full_right_limit;
688
689 bool use_restricted_window = false;
690
691 if (opt_.qp_restrict_search) {
692 const Index mo_level = gw_level + opt_.qpmin;
693 const bool is_occupied = (mo_level <= opt_.homo);
694
695 if (is_occupied) {
696 restricted_right_limit = std::min(full_right_limit, -opt_.qp_zero_margin);
697 } else {
698 restricted_left_limit =
699 std::max(full_left_limit, opt_.qp_virtual_min_energy);
700 }
701
702 const double tol = 1e-12;
703 use_restricted_window =
704 (std::abs(restricted_left_limit - full_left_limit) > tol) ||
705 (std::abs(restricted_right_limit - full_right_limit) > tol);
706 }
707
708 if (use_restricted_window && restricted_left_limit < restricted_right_limit) {
709 auto restricted =
710 SolveQP_Grid_Windowed(intercept0, frequency0, gw_level,
711 restricted_left_limit, restricted_right_limit,
712 false, // accepted roots only
713 stats);
714 if (restricted) {
715 return restricted;
716 }
717
719#pragma omp critical
720 {
722 << " Restricted QP search failed for qplevel:" << gw_level
723 << " in window [" << restricted_left_limit << ", "
724 << restricted_right_limit << "], forcing full dense window ["
725 << full_left_limit << ", " << full_right_limit << "]" << std::flush;
726 }
727 }
728
729 // Important: do NOT go back to adaptive here.
730 // The restricted window already failed to produce an accepted root.
731 // Force the old robust full-window dense search.
732 return SolveQP_Grid_Windowed_Dense(intercept0, frequency0, gw_level,
733 full_left_limit, full_right_limit, true,
734 stats);
735 }
736
737 return SolveQP_Grid_Windowed(intercept0, frequency0, gw_level,
738 full_left_limit, full_right_limit, true, stats);
739}
740
741boost::optional<double> GW::SolveQP_FixedPoint(double intercept0,
742 double frequency0,
743 Index gw_level,
744 QPStats* stats) const {
745 boost::optional<double> newf = boost::none;
746 QPFunc f(gw_level, *sigma_.get(), intercept0);
748 opt_.g_sc_max_iterations, opt_.g_sc_limit, opt_.qp_solver_alpha);
749 double freq_new = newton.FindRoot(f, frequency0);
750 if (newton.getInfo() == NewtonRapson<QPFunc>::success) {
751 newf = freq_new;
752 }
753 if (stats != nullptr) {
754 *stats = f.GetStats();
755 }
756 return newf;
757}
758
759bool GW::Converged(const Eigen::VectorXd& e1, const Eigen::VectorXd& e2,
760 double epsilon) const {
761 Index state = 0;
762 bool energies_converged = true;
763 double diff_max = (e1 - e2).cwiseAbs().maxCoeff(&state);
764 if (diff_max > epsilon) {
765 energies_converged = false;
766 }
767 XTP_LOG(Log::info, log_) << TimeStamp() << " E_diff max=" << diff_max
768 << " StateNo:" << state << std::flush;
769 return energies_converged;
770}
771
773 Eigen::VectorXd diag_backup = Sigma_c_.diagonal();
774 Sigma_c_ = sigma_->CalcCorrelationOffDiag(getGWAResults());
775 Sigma_c_.diagonal() = diag_backup;
776}
777
778// =============================================================================
779// GW::CalculateQSGW
780//
781// Quasiparticle self-consistent GW: iterates the rotation of the
782// three-centre integrals until the QP energies are converged.
783//
784// Index conventions (same as evGW throughout):
785// gw_level : 0-based index within the QP window [0, qptotal_)
786// MO level : absolute MO index = gw_level + opt_.qpmin
787//
788// H_QSGW[m,n] = (e_DFT[m] - v_xc[m]) * delta_{mn} + tilde_Sigma[m,n]
789//
790// where tilde_Sigma = 0.5 * Re( Sigma(e_m) + Sigma(e_n) )
791// = 0.5 * (Sigma_x + Sigma_x^T) <- exchange (already sym)
792// + 0.5 * (Sigma_c(e_m,e_n) + Sigma_c(e_n,e_m)^T) <- corr
793//
794// Note: CalcCorrelationOffDiag(freqs) evaluates element (m,n) at
795// frequency freqs[m] for the row index. To form the symmetrised
796// average we call it twice with swapped frequency vectors.
797// =============================================================================
800 << TimeStamp() << " Starting QSGW self-consistency loop " << std::flush;
801
803 << " QSGW start: qptotal_=" << qptotal_
804 << " vxc rows=" << vxc_.rows() << " cols=" << vxc_.cols()
805 << " Sigma_x rows=" << Sigma_x_.rows()
806 << std::flush;
807
808 // Initialise: start from evGW/G0W0 seed energies.
809 const Eigen::VectorXd e_qp_full = getGWAResults(); // full [qpmin,qpmax]
810 qsgw_seed_energies_ = e_qp_full;
811
812 // ── Virtual-level threshold: auto-trim qpmax ─────────────────────────────
813 // Scan virtual levels from LUMO upward. Exclude the first virtual whose
814 // perturbative correction |e_QP - e_DFT| exceeds qsgw_max_virt_correction.
815 // Occupied states are never excluded — large core corrections are physical.
816 Index qsgw_qpmax = opt_.qpmax;
817 {
818 const Index lumo_local = opt_.homo - opt_.qpmin + 1;
819 bool trimmed = false;
820 for (Index n = lumo_local; n < qptotal_; n++) {
821 const double corr =
822 std::abs(e_qp_full(n) - dft_energies_(opt_.qpmin + n));
823 if (corr > opt_.qsgw_max_virt_correction) {
824 qsgw_qpmax = opt_.qpmin + n - 1;
825 trimmed = true;
827 << TimeStamp() << " QSGW virtual threshold: level "
828 << (opt_.qpmin + n)
829 << " correction = " << corr * votca::tools::conv::hrt2ev
830 << " eV exceeds "
831 << opt_.qsgw_max_virt_correction * votca::tools::conv::hrt2ev
832 << " eV. Trimming QSGW window to [" << opt_.qpmin << ","
833 << qsgw_qpmax << "]." << std::flush;
834 break;
835 }
836 }
837 if (!trimmed) {
839 << TimeStamp() << " QSGW virtual threshold: all corrections within "
840 << opt_.qsgw_max_virt_correction * votca::tools::conv::hrt2ev
841 << " eV. Using full window [" << opt_.qpmin << "," << qsgw_qpmax
842 << "]." << std::flush;
843 }
844 }
845
846 const Index qsgw_qptotal = qsgw_qpmax - opt_.qpmin + 1;
847 const bool window_trimmed = (qsgw_qpmax < opt_.qpmax);
848
849 // If trimmed, reconfigure sigma for the reduced window
850 if (window_trimmed) {
851 Sigma_base::options sigma_opt;
852 sigma_opt.homo = opt_.homo;
853 sigma_opt.qpmin = opt_.qpmin;
854 sigma_opt.qpmax = qsgw_qpmax;
855 sigma_opt.rpamin = opt_.rpamin;
856 sigma_opt.rpamax = opt_.rpamax;
857 sigma_opt.eta = opt_.eta;
858 sigma_opt.quadrature_scheme = opt_.quadrature_scheme;
859 sigma_opt.order = opt_.order;
860 sigma_opt.alpha = opt_.alpha;
861 sigma_->configure(sigma_opt);
862 Sigma_x_ = Eigen::MatrixXd::Zero(qsgw_qptotal, qsgw_qptotal);
863 Sigma_c_ = Eigen::MatrixXd::Zero(qsgw_qptotal, qsgw_qptotal);
864 }
865
866 Eigen::VectorXd e_qp = e_qp_full.head(qsgw_qptotal);
867 qsgw_rotation_ = Eigen::MatrixXd::Identity(qsgw_qptotal, qsgw_qptotal);
868
869 // QSGW requires a local (GGA/LDA) DFT starting point.
870 // With a hybrid functional, the HF exchange embedded in the DFT eigenvalues
871 // introduces a starting-point dependence that is not removed by the QSGW
872 // self-consistency loop. See Faleev, van Schilfgaarde & Kotani PRL 2004
873 // and Betzinger, Friedrich & Blugel PRB 2010 for discussion.
874 if (opt_.ScaHFX > 0.0) {
875 throw std::runtime_error(
876 "GW::CalculateQSGW: QSGW is not compatible with hybrid DFT starting "
877 "points (ScaHFX = " +
878 std::to_string(opt_.ScaHFX) +
879 "). Use a pure GGA or LDA functional as the DFT starting point.");
880 }
881
882 if (opt_.sigma_integration == "cda") {
883 throw std::runtime_error(
884 "GW::CalculateQSGW: QSGW is not supported with the CDA sigma "
885 "integration method. The Gaussian quadrature grid used by CDA becomes "
886 "ill-conditioned as the QP wavefunctions rotate away from the DFT-MO "
887 "basis, causing numerical divergence. Use sigma_integration=ppm or "
888 "sigma_integration=exact instead.");
889 }
890
891 // H0 = diag(e_DFT - v_xc): diagonal approximation for the non-interacting
892 // part of H_QSGW. The off-diagonal V_xc elements are neglected here.
893 // Their significance is printed above for diagnostics.
894 const Eigen::VectorXd e_dft_minus_vxc =
895 dft_energies_.segment(opt_.qpmin, qsgw_qptotal) -
896 vxc_.diagonal().head(qsgw_qptotal);
897
898 Eigen::MatrixXd H0 = -vxc_.topLeftCorner(qsgw_qptotal, qsgw_qptotal);
899 H0.diagonal() += dft_energies_.segment(opt_.qpmin, qsgw_qptotal);
900
901 // Store a copy of the original (unrotated) Mmn_ QP-window slices.
902 // At each iteration we restore Mmn_ to the DFT-MO state and apply the full
903 // rotation qsgw_rotation_ in one shot.
904 // This is necessary because dU (eigenvectors of H_QSGW, always in DFT-MO
905 // basis) gives the total rotation from DFT-MOs directly, not an incremental
906 // rotation relative to the previous QP basis.
907 // Store ALL m-slices in the full RPA window, not just the QP window.
908 // PrepareScreening (PPM, CDA) calls MultiplyRightWithAuxMatrix which
909 // overwrites all slices including those outside the QP window (e.g. the
910 // core level when qpmin > rpamin). If we only restore QP-window slices,
911 // out-of-window slices accumulate PPM-basis corruption across iterations,
912 // causing the persistent oscillation seen when qpmin > rpamin.
913 const Index mtotal = Mmn_.msize();
914 std::vector<Eigen::MatrixXd> Mmn_orig(mtotal);
915 for (Index m = 0; m < mtotal; m++) {
916 Mmn_orig[m] = Mmn_[m];
917 }
918
919 // Anderson/DIIS mixer for tilde_Sigma.
920 // Reuses gw_mixing_order and gw_mixing_alpha options.
921 Anderson qsgw_mixer;
922 qsgw_mixer.Configure(opt_.gw_mixing_order, opt_.gw_mixing_alpha);
924 << TimeStamp() << " QSGW mixer: order=" << opt_.gw_mixing_order
925 << " alpha=" << opt_.gw_mixing_alpha << std::flush;
926
927 // Register the QSGW rotation with sigma and rpa so that PrepareScreening
928 // and the RPA functions apply the m-rotation to QP-window hole slices.
929 // At iteration 0 qsgw_rotation_ is identity so no actual rotation is done
930 // (the pointer is set; the rotation only matters when iter > 0).
931 sigma_->setQSGWRotation(&qsgw_rotation_, opt_.qpmin, opt_.homo);
932 rpa_.setQSGWRotation(&qsgw_rotation_, opt_.qpmin, opt_.homo);
933
934 double diff_max_prev = std::numeric_limits<double>::max();
935
936 for (Index iter = 0; iter < opt_.qsgw_max_iterations; iter++) {
937
938 // ── Step 1: restore Mmn_ to DFT-MO basis and apply full rotation ─────────
939 for (Index m = 0; m < mtotal; m++) {
940 Mmn_[m] = Mmn_orig[m];
941 }
942 if (iter > 0) {
943 Mmn_.Rotate(qsgw_rotation_, opt_.qpmin, qsgw_qpmax);
944 }
945
946 // Recompute screening W and Sigma in current QP basis.
947 sigma_->PrepareScreening();
948
949 // Exchange: symmetric, frequency-independent.
950 Sigma_x_ = sigma_->CalcExchangeMatrix();
951
952 // Symmetrised static self-energy (full matrix including diagonal)
953 Eigen::MatrixXd Sc_row = sigma_->CalcCorrelationOffDiag(e_qp);
954 Eigen::MatrixXd Sc_col = Sc_row.transpose();
955 Eigen::MatrixXd tilde_Sigma = Sigma_x_ + 0.5 * (Sc_row + Sc_col);
956 tilde_Sigma.diagonal() += sigma_->CalcCorrelationDiag(e_qp);
957
958 // ── Step 2: mix tilde_Sigma with Anderson/DIIS ───────────────────────────
959 // Follows the evGW mixer pattern:
960 // UpdateInput is called at the END of each iteration with the raw
961 // tilde_Sigma (what "went in" to that step).
962 // UpdateOutput is called at the START of the next iteration with the
963 // new raw tilde_Sigma (what "came out").
964 // MixHistory then returns the Anderson-mixed tilde_Sigma.
965 // This ensures output_.size() == input_.size() at all times, giving the
966 // mixer a consistent residual (output - input) to minimise.
967 Eigen::VectorXd S_flat = Eigen::Map<const Eigen::VectorXd>(
968 tilde_Sigma.data(), qsgw_qptotal * qsgw_qptotal);
969 if (iter > 0) {
970 qsgw_mixer.UpdateOutput(S_flat);
971 S_flat = qsgw_mixer.MixHistory();
972 }
973
974 // ── Step 3: diagonalise H_QSGW ───────────────────────────────────────────
975 // Two diagonalisations:
976 //
977 // (a) Mixed H_QSGW -> dU for rotating Mmn_ next iteration.
978 // S_flat is the Anderson-mixed tilde_Sigma — this is where the damping
979 // actually takes effect. Different alpha must give different dU here.
980 //
981 // (b) Unmixed H_QSGW -> e_new for convergence check.
982 // The convergence criterion must track the true fixed point, not the
983 // damped approximation, so we use the raw tilde_Sigma for eigenvalues.
984
985 // (a) mixed: rotation
986 Eigen::MatrixXd tilde_Sigma_mixed = Eigen::Map<const Eigen::MatrixXd>(
987 S_flat.data(), qsgw_qptotal, qsgw_qptotal);
988 Eigen::MatrixXd H_qsgw_mixed = H0 + tilde_Sigma_mixed;
989 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es_mixed(H_qsgw_mixed);
990 if (es_mixed.info() != Eigen::ComputationInfo::Success) {
991 throw std::runtime_error(
992 "GW::CalculateQSGW: diagonalisation of mixed H_QSGW failed at iter " +
993 std::to_string(iter));
994 }
995 Eigen::MatrixXd dU = es_mixed.eigenvectors();
996
997 // (b) unmixed: convergence check
998 Eigen::MatrixXd H_qsgw_new = H0 + tilde_Sigma;
999 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es_new(H_qsgw_new);
1000 if (es_new.info() != Eigen::ComputationInfo::Success) {
1001 throw std::runtime_error(
1002 "GW::CalculateQSGW: diagonalisation of H_QSGW failed at iter " +
1003 std::to_string(iter));
1004 }
1005 Eigen::VectorXd e_new = es_new.eigenvalues();
1006
1007 double diff_max = (e_new - e_qp).cwiseAbs().maxCoeff();
1009 << TimeStamp() << " QSGW iter " << iter
1010 << " max|dE_QP| = " << diff_max * votca::tools::conv::hrt2ev << " eV"
1011 << std::flush;
1012
1013 // Reset Anderson history when residual increases significantly.
1014 // This prevents accumulation of bad history when the mixer overshoots.
1015 // Anderson::Configure() does not clear the history vectors, so we
1016 // reconstruct the mixer object entirely to get a true reset.
1017 if (iter > 1 && diff_max > 2.0 * diff_max_prev) {
1018 qsgw_mixer = Anderson();
1019 qsgw_mixer.Configure(opt_.gw_mixing_order, opt_.gw_mixing_alpha);
1020 }
1021 diff_max_prev = diff_max;
1022
1023 if (diff_max < opt_.qsgw_sc_limit) {
1024 XTP_LOG(Log::error, log_) << TimeStamp() << " QSGW converged in "
1025 << iter + 1 << " iterations." << std::flush;
1026 e_qp = e_new;
1027 qsgw_rotation_ = dU;
1028 // Update RPA with the final converged e_qp before breaking so that
1029 // RPAInputEnergies() reflects the converged state, not the previous iter.
1030 rpa_.UpdateRPAInputEnergies(dft_energies_, e_qp, opt_.qpmin);
1031 break;
1032 }
1033
1034 if (iter == opt_.qsgw_max_iterations - 1) {
1036 << TimeStamp() << " WARNING: QSGW did not converge in "
1037 << opt_.qsgw_max_iterations
1038 << " iterations. Inspect results carefully." << std::flush;
1039 }
1040
1041 // ── Step 5: update QP energies and total rotation
1042 // ─────────────────────────
1043 e_qp = e_new;
1044 qsgw_rotation_ = dU;
1045
1046 // Store the raw (unmixed) tilde_Sigma as Anderson input for the next
1047 // iteration. Must be the unmixed version so that MixHistory computes
1048 // the correct residual: new_tilde_Sigma (output) - old_tilde_Sigma (input).
1049 qsgw_mixer.UpdateInput(Eigen::Map<const Eigen::VectorXd>(
1050 tilde_Sigma.data(), qsgw_qptotal * qsgw_qptotal));
1051
1052 // Update the RPA input energies to the new QP energies.
1053 rpa_.UpdateRPAInputEnergies(dft_energies_, e_qp, opt_.qpmin);
1054 }
1055
1056 // Store converged results in the standard output fields.
1057 // Sigma_c_ diagonal is set to the converged on-diagonal correlation
1058 // (needed by getGWAResults / PrintGWA_Energies).
1059 Sigma_c_.diagonal() = sigma_->CalcCorrelationDiag(e_qp);
1060
1061 // Store QP energies and eigenvectors (= accumulated rotation from DFT MOs)
1062 // in Sigma_c_ off-diagonal is left as the last iteration's off-diagonal.
1063 // The calling code in gwbse.cc reads QPdiag from DiagonalizeQPHamiltonian,
1064 // but for QSGW the "Hamiltonian" IS already diagonal in the converged basis.
1065 // We set Sigma_c_ so that getHQP() returns the correct H_QSGW:
1066 // H_QSGW = diag(e_DFT - v_xc) + tilde_Sigma
1067 // which is what Sigma_x_ + Sigma_c_ - vxc_.diagonal() + e_DFT gives
1068 // when Sigma_c_ is set appropriately. The cleanest approach is to
1069 // store the full off-diagonal tilde_Sigma in Sigma_c_ and set Sigma_x_
1070 // to zero for the final iteration -- but that would break getGWAResults.
1071 // Instead we leave Sigma_x_ and Sigma_c_ as-is from the last iteration
1072 // and note that DiagonalizeQPHamiltonian() called by gwbse.cc will
1073 // produce the correct converged eigensystem from getHQP().
1074
1075 // Clear QSGW rotation from sigma and rpa so subsequent G0W0/evGW/BSE calls
1076 // (if any) operate in the standard DFT-MO basis.
1077 sigma_->setQSGWRotation(nullptr, 0, 0);
1078 rpa_.setQSGWRotation(nullptr, 0, 0);
1079
1080 // If the virtual window was trimmed, merge converged QSGW energies for
1081 // [qpmin, qsgw_qpmax] with perturbative seed energies for (qsgw_qpmax,
1082 // qpmax]. Pad rotation matrix with identity for excluded virtuals so that
1083 // getQSGWRotation() returns a qptotal_ x qptotal_ matrix for BSE hookup.
1084 if (window_trimmed) {
1085 const Index n_excluded = qptotal_ - qsgw_qptotal;
1087 << TimeStamp() << " QSGW: merging converged energies [" << opt_.qpmin
1088 << "," << qsgw_qpmax << "] with perturbative seed energies for "
1089 << n_excluded << " excluded virtual(s) [" << qsgw_qpmax + 1 << ","
1090 << opt_.qpmax << "]" << std::flush;
1091
1092 // Pad rotation with identity for excluded virtual levels
1093 Eigen::MatrixXd U_full = Eigen::MatrixXd::Identity(qptotal_, qptotal_);
1094 U_full.topLeftCorner(qsgw_qptotal, qsgw_qptotal) = qsgw_rotation_;
1095 qsgw_rotation_ = U_full;
1096
1097 // Build merged energy vector: QSGW for trimmed window, seed for rest.
1098 // Store in qsgw_final_energies_ so getGWAResults() returns the correct
1099 // full-window result without needing to recompute from sigma matrices.
1100 Eigen::VectorXd e_merged = e_qp_full;
1101 e_merged.head(qsgw_qptotal) = e_qp;
1102 qsgw_final_energies_ = e_merged;
1103
1104 // Update RPA with merged energies so RPAInputEnergies() is consistent.
1105 rpa_.UpdateRPAInputEnergies(dft_energies_, e_merged, opt_.qpmin);
1106
1107 // Restore Sigma_x_ and Sigma_c_ to full qptotal_ x qptotal_ size so that
1108 // any downstream code doesn't encounter a size mismatch with vxc_.diagonal()
1109 // (which has qptotal_ elements). getGWAResults() bypasses these matrices
1110 // via qsgw_final_energies_, so zeroing them is safe.
1111 Sigma_x_ = Eigen::MatrixXd::Zero(qptotal_, qptotal_);
1112 Sigma_c_ = Eigen::MatrixXd::Zero(qptotal_, qptotal_);
1113
1114 // Restore full-size sigma configuration so CalcCorrelationDiag (called
1115 // after the loop for diagonal Sigma_c_ output) works correctly.
1116 Sigma_base::options sigma_opt_full;
1117 sigma_opt_full.homo = opt_.homo;
1118 sigma_opt_full.qpmin = opt_.qpmin;
1119 sigma_opt_full.qpmax = opt_.qpmax;
1120 sigma_opt_full.rpamin = opt_.rpamin;
1121 sigma_opt_full.rpamax = opt_.rpamax;
1122 sigma_opt_full.eta = opt_.eta;
1123 sigma_opt_full.quadrature_scheme = opt_.quadrature_scheme;
1124 sigma_opt_full.order = opt_.order;
1125 sigma_opt_full.alpha = opt_.alpha;
1126 sigma_->configure(sigma_opt_full);
1127 }
1128
1130 << TimeStamp() << " QSGW loop complete." << std::flush;
1131}
1132
1133void GW::PlotSigma(std::string filename, Index steps, double spacing,
1134 std::string states) const {
1135
1136 Eigen::VectorXd frequencies =
1137 rpa_.getRPAInputEnergies().segment(opt_.qpmin - opt_.rpamin, qptotal_);
1138
1139 std::vector<Index> state_inds;
1140 IndexParser rp;
1141 std::vector<Index> parsed_states = rp.CreateIndexVector(states);
1142 for (Index gw_level : parsed_states) {
1143 if (gw_level >= opt_.qpmin && gw_level <= opt_.qpmax) {
1144 state_inds.push_back(gw_level);
1145 }
1146 }
1148 << TimeStamp() << " PQP(omega) written to '" << filename
1149 << "' for states " << rp.CreateIndexString(state_inds) << std::flush;
1150
1151 const Index num_states = state_inds.size();
1152
1153 const Eigen::VectorXd intercept =
1154 dft_energies_.segment(opt_.qpmin, qptotal_) + Sigma_x_.diagonal() -
1155 vxc_.diagonal();
1156 Eigen::MatrixXd mat = Eigen::MatrixXd::Zero(steps, 2 * num_states);
1157#pragma omp parallel for schedule(dynamic)
1158 for (Index grid_point = 0; grid_point < steps; grid_point++) {
1159 const double offset =
1160 ((double)grid_point - ((double)(steps - 1) / 2.0)) * spacing;
1161 for (Index i = 0; i < num_states; i++) {
1162 const Index gw_level = state_inds[i];
1163 const double omega = frequencies(gw_level) + offset;
1164 double sigma = sigma_->CalcCorrelationDiagElement(gw_level, omega);
1165 mat(grid_point, 2 * i) = omega;
1166 mat(grid_point, 2 * i + 1) = sigma + intercept[gw_level];
1167 }
1168 }
1169
1170 std::ofstream out;
1171 out.open(filename);
1172 for (Index i = 0; i < num_states; i++) {
1173 const Index gw_level = state_inds[i];
1174 out << boost::format("#%1$somega_%2$d\tE_QP(omega)_%2$d") %
1175 (i == 0 ? "" : "\t") % gw_level;
1176 }
1177 out << std::endl;
1178 boost::format numFormat("%+1.6f");
1179 Eigen::IOFormat matFormat(Eigen::StreamPrecision, 0, "\t", "\n");
1180 out << numFormat % mat.format(matFormat) << std::endl;
1181 out.close();
1182}
1183
1184} // namespace xtp
1185} // 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 value(double frequency, EvalStage stage=EvalStage::Other) const
Definition gw.h:247
double sigma(double frequency, EvalStage stage=EvalStage::Other) const
Definition gw.h:233
const QPStats & GetStats() const
Definition gw.h:258
double deriv(double frequency) const
Definition gw.h:251
boost::optional< double > SolveQP_Grid_Windowed(double intercept0, double frequency0, Index gw_level, double left_limit, double right_limit, bool allow_rejected_return=true, QPStats *stats=nullptr) const
Definition gw.cc:623
void PlotSigma(std::string filename, Index steps, double spacing, std::string states) const
Definition gw.cc:1133
Index qptotal_
Definition gw.h:194
Eigen::SelfAdjointEigenSolver< Eigen::MatrixXd > DiagonalizeQPHamiltonian() const
Definition gw.cc:73
void PrintGWA_Energies() const
Definition gw.cc:80
RPA rpa_
Definition gw.h:217
void PrintQSGW_Energies(const std::string &seed_label, const Eigen::VectorXd &seed_energies, const Eigen::VectorXd &qsgw_energies) const
Print a two-column comparison of seed (G0W0 or evGW) vs converged QSGW quasiparticle energies.
Definition gw.cc:156
void CalculateQSGW()
Run quasiparticle self-consistent GW (QSGW).
Definition gw.cc:798
bool Converged(const Eigen::VectorXd &e1, const Eigen::VectorXd &e2, double epsilon) const
Definition gw.cc:759
void configure(const options &opt)
Definition gw.cc:35
boost::optional< double > SolveQP_Grid_Windowed_Adaptive(double intercept0, double frequency0, Index gw_level, double left_limit, double right_limit, bool allow_rejected_return=true, QPStats *stats=nullptr) const
Definition gw.cc:433
options opt_
Definition gw.h:207
Eigen::MatrixXd getHQP() const
Definition gw.cc:67
Eigen::VectorXd SolveQP(const Eigen::VectorXd &frequencies) const
Definition gw.cc:323
TCMatrix_gwbse & Mmn_
Definition gw.h:211
Index gw_sc_iteration_
Definition gw.h:215
qp_solver::Stats QPStats
Definition gw.h:40
boost::optional< double > SolveQP_Grid_Windowed_Dense(double intercept0, double frequency0, Index gw_level, double left_limit, double right_limit, bool allow_rejected_return=true, QPStats *stats=nullptr) const
Definition gw.cc:503
Eigen::MatrixXd Sigma_c_
Definition gw.h:197
qp_solver::RootCandidate QPRootCandidate
Definition gw.h:41
void PrintQSGW_Composition(double threshold=0.01) const
Print the dominant DFT-KS orbital contributions to each converged QSGW quasiparticle state.
Definition gw.cc:113
Eigen::VectorXd qsgw_seed_energies_
Definition gw.h:200
const Eigen::VectorXd & dft_energies_
Definition gw.h:213
std::unique_ptr< Sigma_base > sigma_
Definition gw.h:209
void CalculateHQP()
Definition gw.cc:772
Eigen::VectorXd qsgw_final_energies_
Definition gw.h:205
Logger & log_
Definition gw.h:210
Eigen::VectorXd getGWAResults() const
Definition gw.cc:312
qp_solver::WindowDiagnostics QPWindowDiagnostics
Definition gw.h:42
double CalcHomoLumoShift(Eigen::VectorXd frequencies) const
Definition gw.cc:60
boost::optional< double > SolveQP_Grid(double intercept0, double frequency0, Index gw_level, QPStats *stats=nullptr) const
Definition gw.cc:677
const Eigen::MatrixXd & vxc_
Definition gw.h:212
void PrintQP_Energies(const Eigen::VectorXd &qp_diag_energies) const
Definition gw.cc:183
boost::optional< double > SolveQP_FixedPoint(double intercept0, double frequency0, Index gw_level, QPStats *stats=nullptr) const
Definition gw.cc:741
Eigen::VectorXd ScissorShift_DFTlevel(const Eigen::VectorXd &dft_energies) const
Definition gw.cc:210
Eigen::MatrixXd Sigma_x_
Definition gw.h:196
boost::optional< double > SolveQP_Linearisation(double intercept0, double frequency0, Index gw_level, QPStats *stats=nullptr) const
Definition gw.cc:412
void CalculateGWPerturbation()
Definition gw.cc:218
Eigen::MatrixXd qsgw_rotation_
Definition gw.h:198
std::vector< Index > CreateIndexVector(const std::string &Ids) const
std::string CreateIndexString(const std::vector< Index > &indeces) const
Newton Rapson rootfinder for 1d functions.
double FindRoot(const Func &f, double x0)
Timestamp returns the current time as a string Example: cout << TimeStamp().
Definition logger.h:224
#define XTP_LOG(level, log)
Definition logger.h:40
const double hrt2ev
Definition constants.h:53
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