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// Local VOTCA includes
22
23namespace votca {
24namespace xtp {
25
34
35// Build the symmetric orthogonalization matrix X = S^{-1/2}. All Fock-like
36// matrices are diagonalized in the orthogonal AO basis X^T F X.
38 S_ = &S;
39 Sminusahalf = S.Pseudo_InvSqrt(etol);
41 << TimeStamp() << " Smallest value of AOOverlap matrix is "
42 << S_->SmallestEigenValue() << std::flush;
44 << TimeStamp() << " Removed " << S_->Removedfunctions()
45 << " basisfunction from inverse overlap matrix" << std::flush;
46 return;
47}
48
49// Perform one SCF acceleration step.
50//
51// The commutator residual
52//
53// R = X^T (F P S - S P F) X
54//
55// vanishes at self-consistency in a non-orthogonal AO basis. Its maximum
56// element is used as the DIIS error metric, while the history of Fock and
57// density matrices is passed to either DIIS or ADIIS to construct the next
58// extrapolated Fock matrix. When the extrapolation is deemed unsafe, linear
59// density mixing is used instead.
60Eigen::MatrixXd ConvergenceAcc::Iterate(const Eigen::MatrixXd& dmat,
61 Eigen::MatrixXd& H,
62 tools::EigenSystem& MOs, double totE) {
63 Eigen::MatrixXd H_guess = Eigen::MatrixXd::Zero(H.rows(), H.cols());
64
65 if (int(mathist_.size()) == opt_.histlength) {
66 totE_.erase(totE_.begin() + maxerrorindex_);
67 mathist_.erase(mathist_.begin() + maxerrorindex_);
68 dmatHist_.erase(dmatHist_.begin() + maxerrorindex_);
69 }
70
71 totE_.push_back(totE);
72 if (opt_.mode != KSmode::fractional && nocclevels_ > 0 &&
73 nocclevels_ < MOs.eigenvalues().size()) {
74 double gap =
76 if ((diiserror_ > opt_.levelshiftend && opt_.levelshift > 0.0) ||
77 gap < 1e-6) {
79 }
80 }
81 const Eigen::MatrixXd& S = S_->Matrix();
82 Eigen::MatrixXd errormatrix =
83 Sminusahalf.transpose() * (H * dmat * S - S * dmat * H) * Sminusahalf;
84 diiserror_ = errormatrix.cwiseAbs().maxCoeff();
85
86 mathist_.push_back(H);
87 dmatHist_.push_back(dmat);
88
89 if (opt_.maxout) {
90 if (diiserror_ > maxerror_) {
92 maxerrorindex_ = mathist_.size() - 1;
93 }
94 }
95
96 diis_.Update(maxerrorindex_, errormatrix);
97 bool diis_error = false;
99 << TimeStamp() << " DIIs error " << getDIIsError() << std::flush;
100
102 << TimeStamp() << " Delta Etot " << getDeltaE() << std::flush;
103
104 if ((diiserror_ < opt_.adiis_start || diiserror_ < opt_.diis_start) &&
105 opt_.usediis && mathist_.size() > 2) {
106 Eigen::VectorXd coeffs;
107 // use ADIIs if energy has risen a lot in current iteration
108
109 if (diiserror_ > opt_.diis_start ||
110 totE_.back() > 0.9 * totE_[totE_.size() - 2]) {
111 coeffs = adiis_.CalcCoeff(dmatHist_, mathist_);
112 diis_error = !adiis_.Info();
114 << TimeStamp() << " Using ADIIS for next guess" << std::flush;
115
116 } else {
117 coeffs = diis_.CalcCoeff();
118 diis_error = !diis_.Info();
120 << TimeStamp() << " Using DIIS for next guess" << std::flush;
121 }
122 if (diis_error) {
124 << TimeStamp() << " (A)DIIS failed using mixing instead"
125 << std::flush;
126 H_guess = H;
127 } else {
128 for (Index i = 0; i < coeffs.size(); i++) {
129 if (std::abs(coeffs(i)) < 1e-8) {
130 continue;
131 }
132 H_guess += coeffs(i) * mathist_[i];
133 }
134 }
135
136 } else {
137 H_guess = H;
138 }
139
140 MOs = SolveFockmatrix(H_guess);
141
142 Eigen::MatrixXd dmatout = DensityMatrix(MOs);
143 if (diiserror_ > opt_.adiis_start || !opt_.usediis || diis_error ||
144 mathist_.size() <= 2) {
145 usedmixing_ = true;
146 dmatout =
147 opt_.mixingparameter * dmat + (1.0 - opt_.mixingparameter) * dmatout;
149 << TimeStamp() << " Using Mixing with alpha=" << opt_.mixingparameter
150 << std::flush;
151 } else {
152 usedmixing_ = false;
153 }
154 return dmatout;
155}
156
159 << TimeStamp() << " Convergence Options:" << std::flush;
161 << "\t\t Delta E [Ha]: " << opt_.Econverged << std::flush;
163 << "\t\t DIIS max error: " << opt_.error_converged << std::flush;
164 if (opt_.usediis) {
166 << "\t\t DIIS histlength: " << opt_.histlength << std::flush;
168 << "\t\t ADIIS start: " << opt_.adiis_start << std::flush;
170 << "\t\t DIIS start: " << opt_.diis_start << std::flush;
171 std::string del = "oldest";
172 if (opt_.maxout) {
173 del = "largest";
174 }
176 << "\t\t Deleting " << del << " element from DIIS hist" << std::flush;
177 }
179 << "\t\t Levelshift[Ha]: " << opt_.levelshift << std::flush;
181 << "\t\t Levelshift end: " << opt_.levelshiftend << std::flush;
183 << "\t\t Mixing Parameter alpha: " << opt_.mixingparameter << std::flush;
185 << "\t\t Mixing end: " << opt_.mixingend << std::flush;
186}
187
188// Solve the generalized Roothaan-Hall problem
189//
190// F C = S C eps
191//
192// by symmetric orthogonalization: diagonalize X^T F X with X = S^{-1/2} and
193// back-transform the eigenvectors as C = X C' .
195 const Eigen::MatrixXd& H) const {
196 // transform to orthogonal for
197 Eigen::MatrixXd H_ortho = Sminusahalf.transpose() * H * Sminusahalf;
198 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(H_ortho);
199
200 if (es.info() != Eigen::ComputationInfo::Success) {
201 throw std::runtime_error("Matrix Diagonalisation failed. DiagInfo" +
202 std::to_string(es.info()));
203 }
204
205 tools::EigenSystem result;
206 result.eigenvalues() = es.eigenvalues();
207
208 result.eigenvectors() = Sminusahalf * es.eigenvectors();
209 return result;
210}
211
212// Add a virtual-space level shift
213//
214// F <- F + S C_virt Delta C_virt^T S,
215//
216// implemented by placing the scalar shift on the virtual diagonal in the MO
217// basis built from the previous iteration. This leaves occupied orbitals
218// unchanged while opening the HOMO-LUMO gap during difficult SCF phases.
219void ConvergenceAcc::Levelshift(Eigen::MatrixXd& H,
220 const Eigen::MatrixXd& MOs_old) const {
221 if (opt_.levelshift < 1e-9) {
222 return;
223 }
224 Eigen::VectorXd virt = Eigen::VectorXd::Zero(H.rows());
225 for (Index i = nocclevels_; i < H.rows(); i++) {
226 virt(i) = opt_.levelshift;
227 }
228
230 << TimeStamp() << " Using levelshift:" << opt_.levelshift << " Hartree"
231 << std::flush;
232 Eigen::MatrixXd vir = S_->Matrix() * MOs_old * virt.asDiagonal() *
233 MOs_old.transpose() * S_->Matrix();
234 H += vir;
235 return;
236}
237
238/*
239Eigen::MatrixXd ConvergenceAcc::DensityMatrix(
240 const tools::EigenSystem& MOs) const {
241 Eigen::MatrixXd result;
242 if (opt_.mode == KSmode::closed) {
243 result = DensityMatrixGroundState(MOs.eigenvectors());
244 } else if (opt_.mode == KSmode::open) {
245 result = DensityMatrixGroundState_unres(MOs.eigenvectors());
246 } else if (opt_.mode == KSmode::fractional) {
247 result = DensityMatrixGroundState_frac(MOs);
248 }
249 return result;
250} */
251
252// Closed-shell AO density matrix
253//
254// P = 2 C_occ C_occ^T,
255//
256// where each occupied spatial orbital contributes one alpha and one beta
257// electron.
259 const Eigen::MatrixXd& MOs) const {
260 const Eigen::MatrixXd occstates = MOs.leftCols(nocclevels_);
261 Eigen::MatrixXd dmatGS = 2.0 * occstates * occstates.transpose();
262 return dmatGS;
263}
264
265// Spin-resolved unrestricted AO density matrix for one spin channel,
266//
267// P^sigma = C_occ^sigma (C_occ^sigma)^T,
268//
269// with no factor of two because a single spin channel is represented.
271 const Eigen::MatrixXd& MOs) const {
272 if (nocclevels_ == 0) {
273 return Eigen::MatrixXd::Zero(MOs.rows(), MOs.rows());
274 }
275 Eigen::MatrixXd occstates = MOs.leftCols(nocclevels_);
276 Eigen::MatrixXd dmatGS = occstates * occstates.transpose();
277 return dmatGS;
278}
279
280// Fractionally occupied AO density matrix
281//
282// P = C f C^T,
283//
284// where f is a diagonal matrix of orbital occupations assembled from the
285// configured electron count.
286// Fractional-occupation AO density matrix
287//
288// P = C n C^T,
289//
290// where n is the diagonal matrix of orbital occupations provided in the
291// EigenSystem container.
293 const tools::EigenSystem& MOs) const {
294 if (opt_.numberofelectrons == 0) {
295 return Eigen::MatrixXd::Zero(MOs.eigenvectors().rows(),
296 MOs.eigenvectors().rows());
297 }
298
299 Eigen::VectorXd occupation = Eigen::VectorXd::Zero(MOs.eigenvalues().size());
300 std::vector<std::vector<Index> > degeneracies;
301 double buffer = 1e-4;
302 degeneracies.push_back(std::vector<Index>{0});
303 for (Index i = 1; i < occupation.size(); i++) {
304 if (MOs.eigenvalues()(i) <
305 MOs.eigenvalues()(degeneracies[degeneracies.size() - 1][0]) + buffer) {
306 degeneracies[degeneracies.size() - 1].push_back(i);
307 } else {
308 degeneracies.push_back(std::vector<Index>{i});
309 }
310 }
311 Index numofelec = opt_.numberofelectrons;
312 for (const std::vector<Index>& deglevel : degeneracies) {
313 Index numofpossibleelectrons = 2 * Index(deglevel.size());
314 if (numofpossibleelectrons <= numofelec) {
315 for (Index i : deglevel) {
316 occupation(i) = 2;
317 }
318 numofelec -= numofpossibleelectrons;
319 } else {
320 double occ = double(numofelec) / double(deglevel.size());
321 for (Index i : deglevel) {
322 occupation(i) = occ;
323 }
324 break;
325 }
326 }
327 Eigen::MatrixXd dmatGS = MOs.eigenvectors() * occupation.asDiagonal() *
328 MOs.eigenvectors().transpose();
329 return dmatGS;
330}
331
332/*******************************************************
333 * EXTENSION FOR SPIN-KS-DFT
334 *******************************************************/
335
338 const Eigen::MatrixXd& MOs) const {
339
340 const Index n_docc =
341 std::min(opt_.number_alpha_electrons, opt_.number_beta_electrons);
342 const Index n_socc_alpha = opt_.number_alpha_electrons - n_docc;
343
344 SpinDensity result;
345 result.alpha = Eigen::MatrixXd::Zero(MOs.rows(), MOs.rows());
346 result.beta = Eigen::MatrixXd::Zero(MOs.rows(), MOs.rows());
347
348 if (n_docc > 0) {
349 const Eigen::MatrixXd docc = MOs.leftCols(n_docc);
350 const Eigen::MatrixXd d_docc = docc * docc.transpose();
351 result.alpha += d_docc;
352 result.beta += d_docc;
353 }
354
355 if (n_socc_alpha > 0) {
356 const Eigen::MatrixXd socc = MOs.middleCols(n_docc, n_socc_alpha);
357 result.alpha += socc * socc.transpose();
358 }
359
360 return result;
361}
362
363// Construct spin-resolved densities according to the configured occupation
364// model. For restricted open-shell cases the same spatial orbitals are split
365// into doubly and singly occupied subsets using the stored alpha/beta counts.
367 const tools::EigenSystem& MOs) const {
368
369 if (opt_.mode == KSmode::restricted_open) {
371 } else if (opt_.mode == KSmode::closed) {
372 Eigen::MatrixXd d = DensityMatrixGroundState(MOs.eigenvectors());
373 return {0.5 * d, 0.5 * d};
374 } else if (opt_.mode == KSmode::open) {
375 Eigen::MatrixXd d = DensityMatrixGroundState_unres(MOs.eigenvectors());
376 return {d, Eigen::MatrixXd::Zero(d.rows(), d.cols())};
377 } else {
378 Eigen::MatrixXd d = DensityMatrixGroundState_frac(MOs);
379 return {0.5 * d, 0.5 * d};
380 }
381}
382
384 const tools::EigenSystem& MOs) const {
385 SpinDensity spin_dmat = DensityMatrixSpinResolved(MOs);
386 return spin_dmat.total();
387}
388
389} // namespace xtp
390} // 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
Eigen::MatrixXd DensityMatrixGroundState_unres(const Eigen::MatrixXd &MOs) const
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.
tools::EigenSystem SolveFockmatrix(const Eigen::MatrixXd &H) const
Solve the generalized eigenvalue problem for the current Fock matrix.
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.
double getDeltaE() const
Return the total-energy change between the two most recent SCF iterations.
Eigen::MatrixXd DensityMatrixGroundState_frac(const tools::EigenSystem &MOs) const
Construct a fractional-occupation density matrix from orbital occupations.
std::vector< double > totE_
void setOverlap(AOOverlap &S, double etol)
Precompute overlap-dependent quantities used when solving the Fock matrix.
Eigen::MatrixXd DensityMatrixGroundState(const Eigen::MatrixXd &MOs) const
std::vector< Eigen::MatrixXd > dmatHist_
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
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
Spin-resolved density matrices returned for open-shell SCF updates.
Eigen::MatrixXd total() const
Return the total density P = P^alpha + P^beta.