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;
184}
185
186// Solve the generalized Roothaan-Hall problem
187//
188// F C = S C eps
189//
190// by symmetric orthogonalization: diagonalize X^T F X with X = S^{-1/2} and
191// back-transform the eigenvectors as C = X C' .
193 const Eigen::MatrixXd& H) const {
194 // transform to orthogonal for
195 Eigen::MatrixXd H_ortho = Sminusahalf.transpose() * H * Sminusahalf;
196 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(H_ortho);
197
198 if (es.info() != Eigen::ComputationInfo::Success) {
199 throw std::runtime_error("Matrix Diagonalisation failed. DiagInfo" +
200 std::to_string(es.info()));
201 }
202
203 tools::EigenSystem result;
204 result.eigenvalues() = es.eigenvalues();
205
206 result.eigenvectors() = Sminusahalf * es.eigenvectors();
207 return result;
208}
209
210// Add a virtual-space level shift
211//
212// F <- F + S C_virt Delta C_virt^T S,
213//
214// implemented by placing the scalar shift on the virtual diagonal in the MO
215// basis built from the previous iteration. This leaves occupied orbitals
216// unchanged while opening the HOMO-LUMO gap during difficult SCF phases.
217void ConvergenceAcc::Levelshift(Eigen::MatrixXd& H,
218 const Eigen::MatrixXd& MOs_old) const {
219 if (opt_.levelshift < 1e-9) {
220 return;
221 }
222 Eigen::VectorXd virt = Eigen::VectorXd::Zero(H.rows());
223 for (Index i = nocclevels_; i < H.rows(); i++) {
224 virt(i) = opt_.levelshift;
225 }
226
228 << TimeStamp() << " Using levelshift:" << opt_.levelshift << " Hartree"
229 << std::flush;
230 Eigen::MatrixXd vir = S_->Matrix() * MOs_old * virt.asDiagonal() *
231 MOs_old.transpose() * S_->Matrix();
232 H += vir;
233 return;
234}
235
236/*
237Eigen::MatrixXd ConvergenceAcc::DensityMatrix(
238 const tools::EigenSystem& MOs) const {
239 Eigen::MatrixXd result;
240 if (opt_.mode == KSmode::closed) {
241 result = DensityMatrixGroundState(MOs.eigenvectors());
242 } else if (opt_.mode == KSmode::open) {
243 result = DensityMatrixGroundState_unres(MOs.eigenvectors());
244 } else if (opt_.mode == KSmode::fractional) {
245 result = DensityMatrixGroundState_frac(MOs);
246 }
247 return result;
248} */
249
250// Closed-shell AO density matrix
251//
252// P = 2 C_occ C_occ^T,
253//
254// where each occupied spatial orbital contributes one alpha and one beta
255// electron.
257 const Eigen::MatrixXd& MOs) const {
258 const Eigen::MatrixXd occstates = MOs.leftCols(nocclevels_);
259 Eigen::MatrixXd dmatGS = 2.0 * occstates * occstates.transpose();
260 return dmatGS;
261}
262
263// Spin-resolved unrestricted AO density matrix for one spin channel,
264//
265// P^sigma = C_occ^sigma (C_occ^sigma)^T,
266//
267// with no factor of two because a single spin channel is represented.
269 const Eigen::MatrixXd& MOs) const {
270 if (nocclevels_ == 0) {
271 return Eigen::MatrixXd::Zero(MOs.rows(), MOs.rows());
272 }
273 Eigen::MatrixXd occstates = MOs.leftCols(nocclevels_);
274 Eigen::MatrixXd dmatGS = occstates * occstates.transpose();
275 return dmatGS;
276}
277
278// Fractionally occupied AO density matrix
279//
280// P = C f C^T,
281//
282// where f is a diagonal matrix of orbital occupations assembled from the
283// configured electron count.
284// Fractional-occupation AO density matrix
285//
286// P = C n C^T,
287//
288// where n is the diagonal matrix of orbital occupations provided in the
289// EigenSystem container.
291 const tools::EigenSystem& MOs) const {
292 if (opt_.numberofelectrons == 0) {
293 return Eigen::MatrixXd::Zero(MOs.eigenvectors().rows(),
294 MOs.eigenvectors().rows());
295 }
296
297 Eigen::VectorXd occupation = Eigen::VectorXd::Zero(MOs.eigenvalues().size());
298 std::vector<std::vector<Index> > degeneracies;
299 double buffer = 1e-4;
300 degeneracies.push_back(std::vector<Index>{0});
301 for (Index i = 1; i < occupation.size(); i++) {
302 if (MOs.eigenvalues()(i) <
303 MOs.eigenvalues()(degeneracies[degeneracies.size() - 1][0]) + buffer) {
304 degeneracies[degeneracies.size() - 1].push_back(i);
305 } else {
306 degeneracies.push_back(std::vector<Index>{i});
307 }
308 }
309 Index numofelec = opt_.numberofelectrons;
310 for (const std::vector<Index>& deglevel : degeneracies) {
311 Index numofpossibleelectrons = 2 * Index(deglevel.size());
312 if (numofpossibleelectrons <= numofelec) {
313 for (Index i : deglevel) {
314 occupation(i) = 2;
315 }
316 numofelec -= numofpossibleelectrons;
317 } else {
318 double occ = double(numofelec) / double(deglevel.size());
319 for (Index i : deglevel) {
320 occupation(i) = occ;
321 }
322 break;
323 }
324 }
325 Eigen::MatrixXd dmatGS = MOs.eigenvectors() * occupation.asDiagonal() *
326 MOs.eigenvectors().transpose();
327 return dmatGS;
328}
329
330/*******************************************************
331 * EXTENSION FOR SPIN-KS-DFT
332 *******************************************************/
333
336 const Eigen::MatrixXd& MOs) const {
337
338 const Index n_docc =
339 std::min(opt_.number_alpha_electrons, opt_.number_beta_electrons);
340 const Index n_socc_alpha = opt_.number_alpha_electrons - n_docc;
341
342 SpinDensity result;
343 result.alpha = Eigen::MatrixXd::Zero(MOs.rows(), MOs.rows());
344 result.beta = Eigen::MatrixXd::Zero(MOs.rows(), MOs.rows());
345
346 if (n_docc > 0) {
347 const Eigen::MatrixXd docc = MOs.leftCols(n_docc);
348 const Eigen::MatrixXd d_docc = docc * docc.transpose();
349 result.alpha += d_docc;
350 result.beta += d_docc;
351 }
352
353 if (n_socc_alpha > 0) {
354 const Eigen::MatrixXd socc = MOs.middleCols(n_docc, n_socc_alpha);
355 result.alpha += socc * socc.transpose();
356 }
357
358 return result;
359}
360
361// Construct spin-resolved densities according to the configured occupation
362// model. For restricted open-shell cases the same spatial orbitals are split
363// into doubly and singly occupied subsets using the stored alpha/beta counts.
365 const tools::EigenSystem& MOs) const {
366
367 if (opt_.mode == KSmode::restricted_open) {
369 } else if (opt_.mode == KSmode::closed) {
370 Eigen::MatrixXd d = DensityMatrixGroundState(MOs.eigenvectors());
371 return {0.5 * d, 0.5 * d};
372 } else if (opt_.mode == KSmode::open) {
373 Eigen::MatrixXd d = DensityMatrixGroundState_unres(MOs.eigenvectors());
374 return {d, Eigen::MatrixXd::Zero(d.rows(), d.cols())};
375 } else {
376 Eigen::MatrixXd d = DensityMatrixGroundState_frac(MOs);
377 return {0.5 * d, 0.5 * d};
378 }
379}
380
382 const tools::EigenSystem& MOs) const {
383 SpinDensity spin_dmat = DensityMatrixSpinResolved(MOs);
384 return spin_dmat.total();
385}
386
387} // namespace xtp
388} // 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.