55 sigma_->configure(sigma_opt);
62 double QPgap = frequencies(
opt_.homo + 1 -
opt_.qpmin) -
64 return QPgap - DFTgap;
75 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> qpdiag(
getHQP());
86 " ====== Perturbative quasiparticle energies (Hartree) ====== "))
90 << (boost::format(
" DeltaHLGap = %1$+1.6f Hartree") % shift).str()
94 std::string level =
" Level";
97 }
else if ((i +
opt_.qpmin) ==
opt_.homo + 1) {
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") %
116 <<
TimeStamp() <<
" Full quasiparticle Hamiltonian " << std::flush;
119 " ====== Diagonalized quasiparticle energies (Hartree) "
124 std::string level =
" Level";
125 if ((i +
opt_.qpmin) ==
opt_.homo) {
127 }
else if ((i +
opt_.qpmin) ==
opt_.homo + 1) {
132 << (boost::format(
" = %1$4d PQP = %2$+1.6f DQP = %3$+1.6f ") %
133 (i +
opt_.qpmin) % gwa_energies(i) % qp_diag_energies(i))
141 const Eigen::VectorXd& dft_energies)
const {
142 Eigen::VectorXd shifted_energies = dft_energies;
143 shifted_energies.segment(
opt_.homo + 1, dft_energies.size() -
opt_.homo - 1)
144 .array() +=
opt_.shift;
145 return shifted_energies;
152 <<
TimeStamp() <<
" Calculated Hartree exchange contribution"
159 <<
TimeStamp() <<
" Scissor shifting DFT energies by: " <<
opt_.shift
160 <<
" Hrt" << std::flush;
162 rpa_.setRPAInputEnergies(
163 dft_shifted_energies.segment(
opt_.rpamin,
opt_.rpamax -
opt_.rpamin + 1));
164 Eigen::VectorXd frequencies =
170 for (
Index i_gw = 0; i_gw <
opt_.gw_sc_max_iterations; ++i_gw) {
172 if (i_gw %
opt_.reset_3c == 0 && i_gw != 0) {
175 <<
TimeStamp() <<
" Rebuilding 3c integrals" << std::flush;
177 sigma_->PrepareScreening();
179 <<
TimeStamp() <<
" Calculated screening via RPA" << std::flush;
181 <<
TimeStamp() <<
" Solving QP equations " << std::flush;
182 if (
opt_.gw_mixing_order > 0 && i_gw > 0) {
186 frequencies =
SolveQP(frequencies);
188 if (
opt_.gw_sc_max_iterations > 1) {
189 Eigen::VectorXd rpa_energies_old =
rpa_.getRPAInputEnergies();
190 if (
opt_.gw_mixing_order > 0 && i_gw > 0) {
191 if (
opt_.gw_mixing_order == 1) {
193 <<
"GWSC using linear mixing with alpha: " <<
opt_.gw_mixing_alpha
197 <<
"GWSC using Anderson mixing with history "
198 <<
opt_.gw_mixing_order <<
", alpha: " <<
opt_.gw_mixing_alpha
202 Eigen::VectorXd mixed_frequencies = mixing_.
MixHistory();
205 frequencies = mixed_frequencies;
212 for (
int i = 0; i < frequencies.size(); i++) {
214 <<
"... GWSC iter " << i_gw <<
" state " << i <<
" "
215 << std::setprecision(9) << frequencies(i) << std::flush;
219 <<
TimeStamp() <<
" GW_Iteration:" << i_gw
224 << i_gw + 1 <<
" GW iterations." << std::flush;
226 }
else if (i_gw ==
opt_.gw_sc_max_iterations - 1) {
229 <<
" WARNING! GW-self-consistency cycle not converged after "
230 <<
opt_.gw_sc_max_iterations <<
" iterations." << std::flush;
232 <<
TimeStamp() <<
" Run continues. Inspect results carefully!"
247Eigen::VectorXd
GW::SolveQP(
const Eigen::VectorXd& frequencies)
const {
248 sigma_->ResetDiagEvalCounter();
249 Eigen::VectorXd env = Eigen::VectorXd::Zero(
qptotal_);
251 const Eigen::VectorXd intercepts =
255 Eigen::VectorXd frequencies_new = frequencies;
256 Eigen::Array<bool, Eigen::Dynamic, 1> converged =
257 Eigen::Array<bool, Eigen::Dynamic, 1>::Zero(
qptotal_);
265 Index use_threads = 1;
268#pragma omp parallel for schedule(dynamic) num_threads(use_threads)
271 double initial_f = frequencies[gw_level];
272 double intercept = intercepts[gw_level];
273 boost::optional<double> newf;
276 if (
opt_.qp_solver ==
"fixedpoint") {
280 frequencies_new[gw_level] = newf.value();
281 converged[gw_level] =
true;
283 newf =
SolveQP_Grid(intercept, initial_f, gw_level, &local_stats);
285 frequencies_new[gw_level] = newf.value();
286 converged[gw_level] =
true;
291 frequencies_new[gw_level] = newf.value();
298 total_stats.
Add(local_stats);
302 if (!converged.all()) {
303 std::vector<Index> states;
304 for (
Index s = 0; s < converged.size(); s++) {
313 <<
TimeStamp() <<
" Increase the grid search interval" << std::flush;
317 <<
" Sigma diagonal evaluations in SolveQP: "
318 <<
sigma_->GetDiagEvalCounter() << std::flush;
333 return frequencies_new;
340 boost::optional<double> newf = boost::none;
344 double sigma = fqp.
sigma(frequency0, EvalStage::Other);
345 double dsigma_domega = fqp.
deriv(frequency0);
346 double Z = 1.0 - dsigma_domega;
347 if (std::abs(Z) > 1
e-9) {
348 newf = frequency0 + (intercept0 - frequency0 + sigma) / Z;
351 if (stats !=
nullptr) {
358 double intercept0,
double frequency0,
Index gw_level,
double left_limit,
359 double right_limit,
bool allow_rejected_return,
QPStats* stats)
const {
376 std::vector<QPRootCandidate> accepted_roots;
377 std::vector<QPRootCandidate> rejected_roots;
379 bool use_brent =
false;
380 if (
opt_.qp_root_finder ==
"brent") {
386 &wdiag, &accepted_roots, &rejected_roots, use_brent);
391 if (!accepted_roots.empty() || !rejected_roots.empty()) {
393 <<
" Adaptive scan qplevel:" << gw_level <<
" center="
394 << std::max(left_limit, std::min(right_limit, frequency0))
403 if (stats !=
nullptr) {
407 if (!accepted_roots.empty()) {
411 if (!rejected_roots.empty() && !allow_rejected_return) {
416 <<
" Adaptive scan qplevel:" << gw_level
417 <<
" produced only rejected roots in [" << left_limit <<
", "
418 << right_limit <<
"], forcing wider retry" << std::flush;
428 double intercept0,
double frequency0,
Index gw_level,
double left_limit,
429 double right_limit,
bool allow_rejected_return,
QPStats* stats)
const {
444 const bool use_brent = (
opt_.qp_root_finder ==
"brent");
446 std::vector<QPRootCandidate> accepted_roots;
447 std::vector<QPRootCandidate> rejected_roots;
448 std::vector<std::pair<double, double>> roots;
450 if (left_limit < right_limit) {
451 double freq_prev = left_limit;
452 double targ_prev = fqp.
value(freq_prev, EvalStage::Scan);
457 const Index n_steps = std::max<Index>(
458 2,
static_cast<Index>(
459 std::ceil((right_limit - left_limit) /
opt_.qp_dense_spacing)) +
462 for (
Index i_node = 1; i_node < n_steps; ++i_node) {
464 (i_node == n_steps - 1)
466 : std::min(right_limit, left_limit +
static_cast<double>(i_node) *
467 opt_.qp_dense_spacing);
469 const double targ = fqp.
value(freq, EvalStage::Scan);
471 if (targ_prev * targ < 0.0) {
474 frequency0, solver_opt, use_brent);
476 roots.emplace_back(cand->omega, cand->Z);
477 if (cand->accepted) {
478 accepted_roots.push_back(*cand);
480 rejected_roots.push_back(*cand);
493 if (accepted_roots.empty() && rejected_roots.empty()) {
495 <<
" Dense scan qplevel:" << gw_level <<
" found no roots in ["
496 << left_limit <<
", " << right_limit <<
"]" << std::flush;
499 <<
" Dense scan qplevel:" << gw_level <<
" roots (omega:Z)\n\t\t";
500 for (
const auto& root : roots) {
502 << root.second <<
" ";
509 if (stats !=
nullptr) {
513 if (!accepted_roots.empty()) {
514 auto best = std::max_element(
515 accepted_roots.begin(), accepted_roots.end(),
517 return qp_solver::ScoreRoot(a) < qp_solver::ScoreRoot(b);
522 if (!rejected_roots.empty()) {
523 if (!allow_rejected_return) {
528 <<
" Dense scan qplevel:" << gw_level
529 <<
" produced only rejected roots in [" << left_limit <<
", "
530 << right_limit <<
"], forcing wider retry" << std::flush;
536 auto least_bad = std::max_element(
537 rejected_roots.begin(), rejected_roots.end(),
539 return qp_solver::ScoreRoot(a) < qp_solver::ScoreRoot(b);
541 return least_bad->omega;
548 double intercept0,
double frequency0,
Index gw_level,
double left_limit,
549 double right_limit,
bool allow_rejected_return,
QPStats* stats)
const {
551 if (
opt_.qp_grid_search_mode ==
"adaptive") {
553 left_limit, right_limit,
554 allow_rejected_return, stats);
557 if (
opt_.qp_grid_search_mode ==
"dense") {
559 left_limit, right_limit,
560 allow_rejected_return, stats);
563 if (
opt_.qp_grid_search_mode ==
"adaptive_with_dense_fallback") {
566 intercept0, frequency0, gw_level, left_limit, right_limit,
567 allow_rejected_return, &total_stats);
569 if (stats !=
nullptr) {
570 *stats = total_stats;
577 intercept0, frequency0, gw_level, left_limit, right_limit,
578 allow_rejected_return, &dense_stats);
579 total_stats.
Add(dense_stats);
585 <<
" Adaptive QP scan failed for qplevel:" << gw_level
586 <<
", retrying dense grid scan in [" << left_limit <<
", "
587 << right_limit <<
"]" << std::flush;
591 if (stats !=
nullptr) {
592 *stats = total_stats;
597 throw std::runtime_error(
"Unknown gw.qp_grid_search_mode '" +
598 opt_.qp_grid_search_mode +
"'");
605 const double range =
opt_.qp_full_window_half_width;
607 const double full_left_limit = frequency0 - range;
608 const double full_right_limit = frequency0 + range;
610 double restricted_left_limit = full_left_limit;
611 double restricted_right_limit = full_right_limit;
613 bool use_restricted_window =
false;
615 if (
opt_.qp_restrict_search) {
616 const Index mo_level = gw_level +
opt_.qpmin;
617 const bool is_occupied = (mo_level <=
opt_.homo);
620 restricted_right_limit = std::min(full_right_limit, -
opt_.qp_zero_margin);
622 restricted_left_limit =
623 std::max(full_left_limit,
opt_.qp_virtual_min_energy);
626 const double tol = 1
e-12;
627 use_restricted_window =
628 (std::abs(restricted_left_limit - full_left_limit) > tol) ||
629 (std::abs(restricted_right_limit - full_right_limit) > tol);
632 if (use_restricted_window && restricted_left_limit < restricted_right_limit) {
635 restricted_left_limit, restricted_right_limit,
646 <<
" Restricted QP search failed for qplevel:" << gw_level
647 <<
" in window [" << restricted_left_limit <<
", "
648 << restricted_right_limit <<
"], forcing full dense window ["
649 << full_left_limit <<
", " << full_right_limit <<
"]" << std::flush;
657 full_left_limit, full_right_limit,
true,
662 full_left_limit, full_right_limit,
true, stats);
669 boost::optional<double> newf = boost::none;
672 opt_.g_sc_max_iterations,
opt_.g_sc_limit,
opt_.qp_solver_alpha);
673 double freq_new = newton.
FindRoot(f, frequency0);
677 if (stats !=
nullptr) {
684 double epsilon)
const {
686 bool energies_converged =
true;
687 double diff_max = (e1 - e2).cwiseAbs().maxCoeff(&state);
688 if (diff_max > epsilon) {
689 energies_converged =
false;
692 <<
" StateNo:" << state << std::flush;
693 return energies_converged;
697 Eigen::VectorXd diag_backup =
Sigma_c_.diagonal();
703 std::string states)
const {
705 Eigen::VectorXd frequencies =
708 std::vector<Index> state_inds;
711 for (
Index gw_level : parsed_states) {
712 if (gw_level >=
opt_.qpmin && gw_level <=
opt_.qpmax) {
713 state_inds.push_back(gw_level);
717 <<
TimeStamp() <<
" PQP(omega) written to '" << filename
720 const Index num_states = state_inds.size();
722 const Eigen::VectorXd intercept =
725 Eigen::MatrixXd mat = Eigen::MatrixXd::Zero(steps, 2 * num_states);
726#pragma omp parallel for schedule(dynamic)
727 for (
Index grid_point = 0; grid_point < steps; grid_point++) {
728 const double offset =
729 ((double)grid_point - ((double)(steps - 1) / 2.0)) * spacing;
730 for (
Index i = 0; i < num_states; i++) {
731 const Index gw_level = state_inds[i];
732 const double omega = frequencies(gw_level) + offset;
733 double sigma =
sigma_->CalcCorrelationDiagElement(gw_level, omega);
734 mat(grid_point, 2 * i) = omega;
735 mat(grid_point, 2 * i + 1) = sigma + intercept[gw_level];
741 for (
Index i = 0; i < num_states; i++) {
742 const Index gw_level = state_inds[i];
743 out << boost::format(
"#%1$somega_%2$d\tE_QP(omega)_%2$d") %
744 (i == 0 ?
"" :
"\t") % gw_level;
747 boost::format numFormat(
"%+1.6f");
748 Eigen::IOFormat matFormat(Eigen::StreamPrecision, 0,
"\t",
"\n");
749 out << numFormat % mat.format(matFormat) << std::endl;
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
double sigma(double frequency, EvalStage stage=EvalStage::Other) const
const QPStats & GetStats() const
double deriv(double frequency) const
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
void PlotSigma(std::string filename, Index steps, double spacing, std::string states) const
Eigen::SelfAdjointEigenSolver< Eigen::MatrixXd > DiagonalizeQPHamiltonian() const
void PrintGWA_Energies() const
bool Converged(const Eigen::VectorXd &e1, const Eigen::VectorXd &e2, double epsilon) const
void configure(const options &opt)
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
Eigen::MatrixXd getHQP() const
Eigen::VectorXd SolveQP(const Eigen::VectorXd &frequencies) const
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
qp_solver::RootCandidate QPRootCandidate
const Eigen::VectorXd & dft_energies_
std::unique_ptr< Sigma_base > sigma_
Eigen::VectorXd getGWAResults() const
qp_solver::WindowDiagnostics QPWindowDiagnostics
double CalcHomoLumoShift(Eigen::VectorXd frequencies) const
boost::optional< double > SolveQP_Grid(double intercept0, double frequency0, Index gw_level, QPStats *stats=nullptr) const
const Eigen::MatrixXd & vxc_
void PrintQP_Energies(const Eigen::VectorXd &qp_diag_energies) const
boost::optional< double > SolveQP_FixedPoint(double intercept0, double frequency0, Index gw_level, QPStats *stats=nullptr) const
Eigen::VectorXd ScissorShift_DFTlevel(const Eigen::VectorXd &dft_energies) const
boost::optional< double > SolveQP_Linearisation(double intercept0, double frequency0, Index gw_level, QPStats *stats=nullptr) const
void CalculateGWPerturbation()
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()
#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
std::size_t sigma_derivative_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