votca 2026-dev
Loading...
Searching...
No Matches
convergenceacc.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 * 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#include <algorithm>
21
22// Local VOTCA includes
23#include "votca/xtp/citations.h"
26
27namespace votca {
28namespace xtp {
29
38
39// Build the symmetric orthogonalization matrix X = S^{-1/2}. All Fock-like
40// matrices are diagonalized in the orthogonal AO basis X^T F X.
42 S_ = &S;
43 // Own eigendecomposition rather than AOOverlap::Pseudo_InvSqrt: the
44 // removed directions are needed as well, see removed_projector_.
45 const SymmetricEigenSystem es = SymmetricEigen(S.Matrix());
46 const Eigen::VectorXd& s_eig = es.values;
47 Eigen::VectorXd inv_sqrt = Eigen::VectorXd::Zero(s_eig.size());
48 Index removed = 0;
49 for (Index i = 0; i < s_eig.size(); ++i) {
50 if (s_eig(i) < etol) {
51 ++removed;
52 } else {
53 inv_sqrt(i) = 1.0 / std::sqrt(s_eig(i));
54 }
55 }
56 Sminusahalf = es.vectors * inv_sqrt.asDiagonal() * es.vectors.transpose();
57 removed_projector_.resize(0, 0);
58 if (removed > 0) {
59 // In SolveFockmatrix, H_ortho = X^T H X vanishes on these directions,
60 // so they would come out as eigenvalue-0 "orbitals" in the middle of
61 // the spectrum. Shifting them far up keeps them out of the occupied
62 // and low virtual space.
63 const Eigen::MatrixXd U = es.vectors.leftCols(removed);
64 removed_projector_ = U * U.transpose();
65 }
67 << TimeStamp() << " Smallest value of AOOverlap matrix is " << s_eig(0)
68 << std::flush;
70 << TimeStamp() << " Removed " << removed
71 << " basisfunction from inverse overlap matrix (threshold " << etol << ")"
72 << std::flush;
73 if (removed > 0) {
75 << TimeStamp() << " The " << removed
76 << " removed direction(s) appear as zero orbitals at +" << kRemovedShift
77 << " Hartree." << std::flush;
78 } else if (s_eig(0) < 1e3 * etol) {
80 << TimeStamp()
81 << " WARNING: the overlap matrix is nearly singular; if the SCF is "
82 "unstable, raise xtpdft.overlap_tolerance above "
83 << s_eig(0) << "." << std::flush;
84 }
85 return;
86}
87
88// Perform one SCF acceleration step.
89//
90// The commutator residual
91//
92// R = X^T (F P S - S P F) X
93//
94// vanishes at self-consistency in a non-orthogonal AO basis. Its maximum
95// element is used as the DIIS error metric, while the history of Fock and
96// density matrices is passed to either DIIS or ADIIS to construct the next
97// extrapolated Fock matrix. When the extrapolation is deemed unsafe, linear
98// density mixing is used instead.
99Eigen::MatrixXd ConvergenceAcc::Iterate(const Eigen::MatrixXd& dmat,
100 Eigen::MatrixXd& H,
101 tools::EigenSystem& MOs, double totE) {
102 // Check the second-order step returned by the previous call, now that
103 // the energy of its density (totE) is known: Fletcher's trust-radius
104 // update. A rejected step is undone and, while second-order steps
105 // continue, repeated at once with the smaller radius from the stored
106 // Fock matrix and Davidson subspace: no Fock build, no SCF iteration and
107 // usually no Hessian products.
108 last_rejected_ = false;
109 if (trah_pending_) {
110 trah_pending_ = false;
111 const double actual = totE - trah_pre_energy_;
112 const bool accepted =
115 << TimeStamp() << " Second-order step check: actual dE=" << actual
116 << ", predicted dE=" << trah_predicted_ << ", r=" << trust_.LastRatio()
117 << (accepted ? " (accepted)" : " (rejected)")
118 << ", new trust radius=" << trust_.Radius() << std::flush;
119 if (!accepted) {
120 if (trust_.Radius() < kMinTrustRadius) {
121 trah_active_ = false;
122 trah_disabled_ = true;
124 << TimeStamp() << " Trust radius below " << kMinTrustRadius
125 << " without an accepted step; leaving second-order steps for "
126 "this SCF"
127 << std::flush;
128 }
129 MOs = trah_pre_MOs_;
130 totE_.push_back(trah_pre_energy_);
131 last_rejected_ = true;
132 usedmixing_ = false;
133 if (trah_active_) {
134 const Eigen::MatrixXd H_pre = trah_pre_H_;
135 return SecondOrderStep(DensityMatrix(MOs), H_pre, MOs,
137 }
138 return DensityMatrix(MOs);
139 }
140 }
141
142 totE_.push_back(totE);
144 // The MOs of the previous step, for the level shift.
145 const Eigen::MatrixXd MOs_old = MOs.eigenvectors();
146 const Eigen::VectorXd MOs_old_energies = MOs.eigenvalues();
147
148 const Eigen::MatrixXd& S = S_->Matrix();
149 const Eigen::MatrixXd errormatrix =
150 Sminusahalf.transpose() * (H * dmat * S - S * dmat * H) * Sminusahalf;
151 diiserror_ = errormatrix.cwiseAbs().maxCoeff();
152
154 << TimeStamp() << " DIIs error " << getDIIsError() << std::flush;
156 << TimeStamp() << " Delta Etot " << getDeltaE() << std::flush;
157
158 // Energy-rise reset. An extrapolated Fock matrix that sends the energy
159 // far above anything seen before is not a step to build on: the history
160 // that produced it is discarded and the SCF continues, damped, from the
161 // lowest-energy density so far.
163 if (opt_.energy_reset > 0.0 && have_best_ && !trah_active_ &&
164 totE > best_energy_ + opt_.energy_reset &&
170 << TimeStamp() << " WARNING: energy rose by " << totE - best_energy_
171 << " Ha above the lowest so far (" << best_energy_
172 << " Ha); discarding the (A)DIIS history and restarting from that "
173 "density (reset "
174 << energy_resets_ << ")" << std::flush;
175 mathist_.clear();
176 dmatHist_.clear();
177 errhist_.clear();
178 diis_.Clear();
179 Eigen::MatrixXd H_restart = best_H_;
180 if (opt_.levelshift > 0.0) {
181 Levelshift(H_restart, MOs_old);
182 }
183 MOs = SolveFockmatrix(H_restart);
184 usedmixing_ = true;
185 return opt_.mixingparameter * best_dmat_ +
186 (1.0 - opt_.mixingparameter) * DensityMatrix(MOs);
187 }
188 if (!have_best_ || totE < best_energy_) {
189 have_best_ = true;
190 best_energy_ = totE;
191 best_dmat_ = dmat;
192 best_H_ = H;
193 }
194
195 // History, trimmed at one index for all of mathist_, dmatHist_, errhist_
196 // and DIIS's own error history: the oldest entry, or with DIIS_maxout the
197 // one with the largest error. All four are always the same length, so
198 // DIIS::Update trims at the same moment and the same index.
199 Index drop = 0;
200 if (Index(mathist_.size()) == opt_.histlength) {
201 if (opt_.maxout) {
202 drop = Index(std::max_element(errhist_.begin(), errhist_.end()) -
203 errhist_.begin());
204 }
205 mathist_.erase(mathist_.begin() + drop);
206 dmatHist_.erase(dmatHist_.begin() + drop);
207 errhist_.erase(errhist_.begin() + drop);
208 }
209 // The UNSHIFTED Fock matrix goes into the history: the level shift is a
210 // device for the next diagonalization only, and would otherwise enter
211 // the DIIS errors and the ADIIS energy model.
212 mathist_.push_back(H);
213 dmatHist_.push_back(dmat);
214 errhist_.push_back(diiserror_);
215 diis_.Update(drop, errormatrix);
216 error_history_.push_back(diiserror_);
217 if (Index(error_history_.size()) > kTrailingWindow) {
218 error_history_.erase(error_history_.begin());
219 }
220
221 // Second-order (TRAH) steps: from the start with second_order "always";
222 // with "auto" once (A)DIIS has failed repeatedly or the DIIS error has
223 // stalled. Once started they continue for the rest of this SCF, unless
224 // the trust radius collapses.
226 (opt_.second_order == "always" || SecondOrderStalled())) {
228 << TimeStamp() << " Switching to second-order (TRAH) steps"
229 << (opt_.second_order == "always"
230 ? ""
231 : " ((A)DIIS failed repeatedly or the DIIS error stalled)")
232 << std::flush;
233 trah_active_ = true;
234 }
235 if (trah_active_) {
236 return SecondOrderStep(dmat, H, MOs, totE);
237 }
238
239 bool diis_error = false;
240 Eigen::MatrixXd H_guess = H;
241 // Below DIIS_start the density is already close to self-consistency (as
242 // after a warm start from the previous QM/MM iteration): no damping, a
243 // plain step from the current Fock matrix first and DIIS from two
244 // history entries on. Far from it (a cold start), the first steps are
245 // damped until the history holds three entries, as before.
246 const bool near_convergence = diiserror_ < opt_.diis_start;
247 const std::size_t min_history = near_convergence ? 1 : 2;
248 if ((diiserror_ < opt_.adiis_start || diiserror_ < opt_.diis_start) &&
249 opt_.usediis && mathist_.size() > min_history) {
250 Eigen::VectorXd coeffs;
251 // ADIIS above DIIS_start, and also below it whenever the energy went
252 // up: plain DIIS does not minimize the energy and can run away from a
253 // bad step.
254 const bool energy_rose = getDeltaE() > kEnergyRiseForADIIS;
255 if (diiserror_ > opt_.diis_start || energy_rose) {
256 coeffs = adiis_.CalcCoeff(dmatHist_, mathist_);
257 diis_error = !adiis_.Info();
259 << TimeStamp() << " Using ADIIS for next guess" << std::flush;
260 } else {
261 coeffs = diis_.CalcCoeff();
262 diis_error = !diis_.Info();
264 << TimeStamp() << " Using DIIS for next guess" << std::flush;
265 }
266 if (diis_error) {
269 << TimeStamp() << " (A)DIIS failed using mixing instead"
270 << std::flush;
271 } else {
273 H_guess.setZero();
274 for (Index i = 0; i < coeffs.size(); i++) {
275 if (std::abs(coeffs(i)) < 1e-8) {
276 continue;
277 }
278 H_guess += coeffs(i) * mathist_[i];
279 }
280 }
281 }
282
283 if (opt_.mode != KSmode::fractional && nocclevels_ > 0 &&
284 nocclevels_ < MOs_old_energies.size()) {
285 const double gap =
286 MOs_old_energies(nocclevels_) - MOs_old_energies(nocclevels_ - 1);
287 if ((diiserror_ > opt_.levelshiftend && opt_.levelshift > 0.0) ||
288 gap < 1e-6) {
289 Levelshift(H_guess, MOs_old);
290 }
291 }
292
293 MOs = SolveFockmatrix(H_guess);
294
295 Eigen::MatrixXd dmatout = DensityMatrix(MOs);
296 // mixing_end, not ADIIS_start, decides on damping (as in the UKS path):
297 // the two are separate options.
298 if (diiserror_ > opt_.mixingend || !opt_.usediis || diis_error ||
299 (mathist_.size() <= 2 && !near_convergence)) {
300 usedmixing_ = true;
301 dmatout =
302 opt_.mixingparameter * dmat + (1.0 - opt_.mixingparameter) * dmatout;
304 << TimeStamp() << " Using Mixing with alpha=" << opt_.mixingparameter
305 << std::flush;
306 } else {
307 usedmixing_ = false;
308 }
309 return dmatout;
310}
311
313 const std::vector<Eigen::MatrixXd>& errors = diis_.ErrorHistory();
314 if (errors.size() != mathist_.size() || dmatHist_.size() != mathist_.size() ||
315 errhist_.size() != mathist_.size()) {
316 return false;
317 }
318 const Eigen::MatrixXd& S = S_->Matrix();
319 for (std::size_t k = 0; k < mathist_.size(); ++k) {
320 const Eigen::MatrixXd e =
321 Sminusahalf.transpose() *
322 (mathist_[k] * dmatHist_[k] * S - S * dmatHist_[k] * mathist_[k]) *
324 if ((e - errors[k]).cwiseAbs().maxCoeff() > 1e-12 ||
325 std::abs(e.cwiseAbs().maxCoeff() - errhist_[k]) > 1e-12) {
326 return false;
327 }
328 }
329 return true;
330}
331
334 return true;
335 }
338 return false;
339 }
340 double mean_ratio = 0.0;
341 Index count = 0;
342 for (std::size_t i = 1; i < error_history_.size(); ++i) {
343 if (error_history_[i - 1] > 1e-12) {
344 mean_ratio += error_history_[i] / error_history_[i - 1];
345 ++count;
346 }
347 }
348 return count > 0 && mean_ratio / double(count) > 1.0 / kMeanRatioTolerance;
349}
350
351// One TRAH step from the current orbitals. The gradient (from H) and the
352// Fletcher check (against totE) belong to dmat; if dmat is not the density
353// of MOs (a guess or damped density), the orbital density is returned first
354// and the step follows on the next call.
355Eigen::MatrixXd ConvergenceAcc::SecondOrderStep(const Eigen::MatrixXd& dmat,
356 const Eigen::MatrixXd& H,
358 double totE) {
359 citations::Cite("Helmich-Paris:2021");
360 const Eigen::MatrixXd dmat_mos = DensityMatrix(MOs);
361 const double mismatch = (dmat_mos - dmat).cwiseAbs().maxCoeff();
362 if (mismatch > kDensityMismatchTolerance) {
364 << TimeStamp()
365 << " Density differs from that of the current orbitals (max "
366 "deviation "
367 << mismatch << "); returning the orbital density first" << std::flush;
368 usedmixing_ = false;
369 return dmat_mos;
370 }
371
372 const Eigen::MatrixXd& C = MOs.eigenvectors();
373 const Eigen::MatrixXd F_MO = C.transpose() * H * C;
374 const Index nocc = nocclevels_;
375 const Index nvirt = C.cols() - nocc;
376 const Eigen::VectorXd g = EnergyGradientScale * trah::OccVirt(F_MO, nocc);
377 // diagonal of the energy Hessian, 4 (F_aa - F_ii), for preconditioning
378 Eigen::VectorXd diag(nocc * nvirt);
379 constexpr double kMinGap = 1e-3;
380 for (Index i = 0; i < nocc; ++i) {
381 for (Index a = 0; a < nvirt; ++a) {
382 diag(i * nvirt + a) =
383 4.0 *
384 std::max(std::abs(F_MO(nocc + a, nocc + a) - F_MO(i, i)), kMinGap);
385 }
386 }
387 const tools::EigenSystem MOs_now = MOs;
388 trah::HessianProduct hessian = [&](const Eigen::MatrixXd& X) {
389 return orbital_hessian_(MOs_now, H, X);
390 };
391 trah::StepResult step;
392 try {
393 step = trah::Step(g, diag, hessian, trust_.Radius(), trah_options_,
395 } catch (const std::runtime_error& error) {
397 << TimeStamp()
398 << " Second-order step not possible, continuing without: "
399 << error.what() << std::flush;
400 trah_active_ = false;
401 trah_disabled_ = true;
402 usedmixing_ = false;
403 return dmat_mos;
404 }
406 << TimeStamp() << " TRAH step: |g|=" << g.norm()
407 << ", |x|=" << step.x.norm() << ", trust radius=" << trust_.Radius()
408 << ", alpha=" << step.alpha << ", micro-iterations=" << step.iterations
409 << ", Hessian products=" << step.products
410 << (step.reused ? " (subspace reused)" : "")
411 << ", residual=" << step.residual
412 << (step.converged ? "" : " (not converged)")
413 << ", predicted dE=" << step.predicted << std::flush;
414
415 trah_pre_energy_ = totE;
416 trah_pre_MOs_ = MOs;
417 trah_pre_H_ = H;
419 trah_linear_ = step.linear;
420 trah_step_norm_ = step.x.norm();
421 trah_pending_ = true;
422
423 const Eigen::MatrixXd C_new = trah::RotateOrbitals(C, step.x, nocc);
424 MOs.eigenvectors() = C_new;
425 // diagonal of the old Fock matrix in the rotated orbitals, for the level
426 // shift gap check and the preconditioner until the next diagonalisation
427 MOs.eigenvalues() = (C_new.transpose() * H * C_new).diagonal();
428 usedmixing_ = false;
429 return DensityMatrix(MOs);
430}
431
434 << TimeStamp() << " Convergence Options:" << std::flush;
436 << "\t\t Delta E [Ha]: " << opt_.Econverged << std::flush;
438 << "\t\t DIIS max error: " << opt_.error_converged << std::flush;
439 if (opt_.usediis) {
441 << "\t\t DIIS histlength: " << opt_.histlength << std::flush;
443 << "\t\t ADIIS start: " << opt_.adiis_start << std::flush;
445 << "\t\t DIIS start: " << opt_.diis_start << std::flush;
446 std::string del = "oldest";
447 if (opt_.maxout) {
448 del = "largest";
449 }
451 << "\t\t Deleting " << del << " element from DIIS hist" << std::flush;
452 }
454 << "\t\t Levelshift[Ha]: " << opt_.levelshift << std::flush;
456 << "\t\t Levelshift end: " << opt_.levelshiftend << std::flush;
458 << "\t\t Mixing Parameter alpha: " << opt_.mixingparameter << std::flush;
460 << "\t\t Mixing end: " << opt_.mixingend << std::flush;
462 << "\t\t Second-order (TRAH) steps: " << opt_.second_order << std::flush;
464 << "\t\t Energy reset [Ha]: " << opt_.energy_reset
465 << (opt_.energy_reset > 0.0 ? "" : " (off)") << std::flush;
466}
467
468// Solve the generalized Roothaan-Hall problem
469//
470// F C = S C eps
471//
472// by symmetric orthogonalization: diagonalize X^T F X with X = S^{-1/2} and
473// back-transform the eigenvectors as C = X C' .
475 const Eigen::MatrixXd& H) const {
476 // transform to orthogonal for
477 Eigen::MatrixXd H_ortho = Sminusahalf.transpose() * H * Sminusahalf;
478 if (removed_projector_.size() > 0) {
480 }
481 const SymmetricEigenSystem es = SymmetricEigen(H_ortho);
482 if (!es.values.allFinite()) {
483 throw std::runtime_error("Matrix Diagonalisation failed");
484 }
485
486 tools::EigenSystem result;
487 result.eigenvalues() = es.values;
488 result.eigenvectors() = Sminusahalf * es.vectors;
489 return result;
490}
491
492// Add a virtual-space level shift
493//
494// F <- F + S C_virt Delta C_virt^T S,
495//
496// implemented by placing the scalar shift on the virtual diagonal in the MO
497// basis built from the previous iteration. This leaves occupied orbitals
498// unchanged while opening the HOMO-LUMO gap during difficult SCF phases.
499void ConvergenceAcc::Levelshift(Eigen::MatrixXd& H,
500 const Eigen::MatrixXd& MOs_old) const {
501 if (opt_.levelshift < 1e-9) {
502 return;
503 }
504 Eigen::VectorXd virt = Eigen::VectorXd::Zero(H.rows());
505 for (Index i = nocclevels_; i < H.rows(); i++) {
506 virt(i) = opt_.levelshift;
507 }
508
510 << TimeStamp() << " Using levelshift:" << opt_.levelshift << " Hartree"
511 << std::flush;
512 Eigen::MatrixXd vir = S_->Matrix() * MOs_old * virt.asDiagonal() *
513 MOs_old.transpose() * S_->Matrix();
514 H += vir;
515 return;
516}
517
518/*
519Eigen::MatrixXd ConvergenceAcc::DensityMatrix(
520 const tools::EigenSystem& MOs) const {
521 Eigen::MatrixXd result;
522 if (opt_.mode == KSmode::closed) {
523 result = DensityMatrixGroundState(MOs.eigenvectors());
524 } else if (opt_.mode == KSmode::open) {
525 result = DensityMatrixGroundState_unres(MOs.eigenvectors());
526 } else if (opt_.mode == KSmode::fractional) {
527 result = DensityMatrixGroundState_frac(MOs);
528 }
529 return result;
530} */
531
532// Closed-shell AO density matrix
533//
534// P = 2 C_occ C_occ^T,
535//
536// where each occupied spatial orbital contributes one alpha and one beta
537// electron.
539 const Eigen::MatrixXd& MOs) const {
540 const Eigen::MatrixXd occstates = MOs.leftCols(nocclevels_);
541 Eigen::MatrixXd dmatGS = 2.0 * occstates * occstates.transpose();
542 return dmatGS;
543}
544
545// Spin-resolved unrestricted AO density matrix for one spin channel,
546//
547// P^sigma = C_occ^sigma (C_occ^sigma)^T,
548//
549// with no factor of two because a single spin channel is represented.
551 const Eigen::MatrixXd& MOs) const {
552 if (nocclevels_ == 0) {
553 return Eigen::MatrixXd::Zero(MOs.rows(), MOs.rows());
554 }
555 Eigen::MatrixXd occstates = MOs.leftCols(nocclevels_);
556 Eigen::MatrixXd dmatGS = occstates * occstates.transpose();
557 return dmatGS;
558}
559
560// Fractionally occupied AO density matrix
561//
562// P = C f C^T,
563//
564// where f is a diagonal matrix of orbital occupations assembled from the
565// configured electron count.
566// Fractional-occupation AO density matrix
567//
568// P = C n C^T,
569//
570// where n is the diagonal matrix of orbital occupations provided in the
571// EigenSystem container.
573 const tools::EigenSystem& MOs) const {
574 if (opt_.numberofelectrons == 0) {
575 return Eigen::MatrixXd::Zero(MOs.eigenvectors().rows(),
576 MOs.eigenvectors().rows());
577 }
578
579 Eigen::VectorXd occupation = Eigen::VectorXd::Zero(MOs.eigenvalues().size());
580 std::vector<std::vector<Index> > degeneracies;
581 double buffer = 1e-4;
582 degeneracies.push_back(std::vector<Index>{0});
583 for (Index i = 1; i < occupation.size(); i++) {
584 if (MOs.eigenvalues()(i) <
585 MOs.eigenvalues()(degeneracies[degeneracies.size() - 1][0]) + buffer) {
586 degeneracies[degeneracies.size() - 1].push_back(i);
587 } else {
588 degeneracies.push_back(std::vector<Index>{i});
589 }
590 }
591 Index numofelec = opt_.numberofelectrons;
592 for (const std::vector<Index>& deglevel : degeneracies) {
593 Index numofpossibleelectrons = 2 * Index(deglevel.size());
594 if (numofpossibleelectrons <= numofelec) {
595 for (Index i : deglevel) {
596 occupation(i) = 2;
597 }
598 numofelec -= numofpossibleelectrons;
599 } else {
600 double occ = double(numofelec) / double(deglevel.size());
601 for (Index i : deglevel) {
602 occupation(i) = occ;
603 }
604 break;
605 }
606 }
607 Eigen::MatrixXd dmatGS = MOs.eigenvectors() * occupation.asDiagonal() *
608 MOs.eigenvectors().transpose();
609 return dmatGS;
610}
611
612/*******************************************************
613 * EXTENSION FOR SPIN-KS-DFT
614 *******************************************************/
615
618 const Eigen::MatrixXd& MOs) const {
619
620 const Index n_docc =
621 std::min(opt_.number_alpha_electrons, opt_.number_beta_electrons);
622 const Index n_socc_alpha = opt_.number_alpha_electrons - n_docc;
623
624 SpinDensity result;
625 result.alpha = Eigen::MatrixXd::Zero(MOs.rows(), MOs.rows());
626 result.beta = Eigen::MatrixXd::Zero(MOs.rows(), MOs.rows());
627
628 if (n_docc > 0) {
629 const Eigen::MatrixXd docc = MOs.leftCols(n_docc);
630 const Eigen::MatrixXd d_docc = docc * docc.transpose();
631 result.alpha += d_docc;
632 result.beta += d_docc;
633 }
634
635 if (n_socc_alpha > 0) {
636 const Eigen::MatrixXd socc = MOs.middleCols(n_docc, n_socc_alpha);
637 result.alpha += socc * socc.transpose();
638 }
639
640 return result;
641}
642
643// Construct spin-resolved densities according to the configured occupation
644// model. For restricted open-shell cases the same spatial orbitals are split
645// into doubly and singly occupied subsets using the stored alpha/beta counts.
647 const tools::EigenSystem& MOs) const {
648
649 if (opt_.mode == KSmode::restricted_open) {
651 } else if (opt_.mode == KSmode::closed) {
652 Eigen::MatrixXd d = DensityMatrixGroundState(MOs.eigenvectors());
653 return {0.5 * d, 0.5 * d};
654 } else if (opt_.mode == KSmode::open) {
655 Eigen::MatrixXd d = DensityMatrixGroundState_unres(MOs.eigenvectors());
656 return {d, Eigen::MatrixXd::Zero(d.rows(), d.cols())};
657 } else {
658 Eigen::MatrixXd d = DensityMatrixGroundState_frac(MOs);
659 return {0.5 * d, 0.5 * d};
660 }
661}
662
664 const tools::EigenSystem& MOs) const {
665 SpinDensity spin_dmat = DensityMatrixSpinResolved(MOs);
666 return spin_dmat.total();
667}
668
669} // namespace xtp
670} // namespace votca
const Eigen::VectorXd & eigenvalues() const
Definition eigensystem.h:30
const Eigen::MatrixXd & eigenvectors() const
Definition eigensystem.h:33
Eigen::MatrixXd DensityMatrix(const tools::EigenSystem &MOs) const
static constexpr double kMeanRatioTolerance
trah::StepOptions trah_options_
tools::EigenSystem trah_pre_MOs_
static constexpr Index kTrailingWindow
static constexpr double kRemovedShift
Eigen::MatrixXd DensityMatrixGroundState_unres(const Eigen::MatrixXd &MOs) const
std::vector< double > error_history_
std::vector< Eigen::MatrixXd > mathist_
Eigen::MatrixXd Iterate(const Eigen::MatrixXd &dmat, Eigen::MatrixXd &H, tools::EigenSystem &MOs, double totE)
double getDIIsError() const
Return the DIIS commutator norm from the latest iteration.
static constexpr double kDensityMismatchTolerance
static constexpr Index kAutoStartIteration
tools::EigenSystem SolveFockmatrix(const Eigen::MatrixXd &H) const
Solve the generalized eigenvalue problem for the current Fock matrix.
static constexpr double kMinTrustRadius
std::vector< double > errhist_
void PrintConfigOptions() const
Print the active convergence-acceleration settings to the logger.
void Levelshift(Eigen::MatrixXd &H, const Eigen::MatrixXd &MOs_old) const
Apply a virtual-space level shift in the molecular-orbital basis.
static constexpr double EnergyGradientScale
static constexpr Index kResetCooldown
double getDeltaE() const
Return the total-energy change between the two most recent SCF iterations.
static constexpr Index kMaxEnergyResets
Eigen::MatrixXd DensityMatrixGroundState_frac(const tools::EigenSystem &MOs) const
Construct a fractional-occupation density matrix from orbital occupations.
std::vector< double > totE_
static constexpr Index kMaxConsecutiveFailures
Eigen::MatrixXd SecondOrderStep(const Eigen::MatrixXd &dmat, const Eigen::MatrixXd &H, tools::EigenSystem &MOs, double totE)
void setOverlap(AOOverlap &S, double etol)
Precompute overlap-dependent quantities used when solving the Fock matrix.
Eigen::MatrixXd removed_projector_
Eigen::MatrixXd DensityMatrixGroundState(const Eigen::MatrixXd &MOs) const
std::vector< Eigen::MatrixXd > dmatHist_
static constexpr double kEnergyRiseForADIIS
SpinDensity DensityMatrixSpinResolved(const tools::EigenSystem &MOs) const
SpinDensity DensityMatrixGroundState_restricted_open(const Eigen::MatrixXd &MOs) const
Timestamp returns the current time as a string Example: cout << TimeStamp().
Definition logger.h:224
#define XTP_LOG(level, log)
Definition logger.h:40
void Cite(const std::string &key)
marks a reference as used (thread safe; unknown keys are printed as such)
Definition citations.cc:289
std::function< Eigen::MatrixXd(const Eigen::MatrixXd &)> HessianProduct
Hessian applied to the columns of V (n x k), returning n x k.
Definition trah.h:85
StepResult Step(const Eigen::VectorXd &g, const Eigen::VectorXd &diag, const HessianProduct &hessian, double trust_radius, const StepOptions &opt=StepOptions(), Subspace *subspace=nullptr)
Definition trah.cc:116
Eigen::MatrixXd RotateOrbitals(const Eigen::MatrixXd &C, const Eigen::VectorXd &x, Index nocc)
Definition trah.cc:275
Eigen::VectorXd OccVirt(const Eigen::MatrixXd &M, Index nocc)
Definition trah.cc:318
Charge transport classes.
Definition ERIs.h:28
SymmetricEigenSystem SymmetricEigen(const Eigen::MatrixXd &A, bool vectors=true)
With vectors = false only the eigenvalues are computed.
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26
Spin-resolved density matrices returned for open-shell SCF updates.
Eigen::MatrixXd total() const
Return the total density P = P^alpha + P^beta.
Eigen::VectorXd x
Definition trah.h:70