votca 2026-dev
Loading...
Searching...
No Matches
uks_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
23#include "votca/xtp/trah.h"
24
25#include <algorithm>
26
27#include <tuple>
28
29namespace votca {
30namespace xtp {
31
33 const options& opt_beta) {
34 opt_alpha_ = opt_alpha;
35 opt_beta_ = opt_beta;
36
37 nocclevels_alpha_ = opt_alpha_.numberofelectrons;
38 nocclevels_beta_ = opt_beta_.numberofelectrons;
39
40 // one shared DIIS/ADIIS history length
41 diis_.setHistLength(opt_alpha_.histlength);
42 trah_options_.max_iterations = opt_alpha_.davidson_max_iter;
43 // adiis_.setHistLength(opt_alpha_.histlength);
44}
45
47
49 // As ConvergenceAcc::setOverlap: removed directions are kept out of the
50 // occupied and low virtual space via removed_projector_.
51 S_ = &S;
52 const SymmetricEigenSystem es = SymmetricEigen(S.Matrix());
53 const Eigen::VectorXd& s_eig = es.values;
54 Eigen::VectorXd inv_sqrt = Eigen::VectorXd::Zero(s_eig.size());
55 Index removed = 0;
56 for (Index i = 0; i < s_eig.size(); ++i) {
57 if (s_eig(i) < etol) {
58 ++removed;
59 } else {
60 inv_sqrt(i) = 1.0 / std::sqrt(s_eig(i));
61 }
62 }
63 Sminusahalf = es.vectors * inv_sqrt.asDiagonal() * es.vectors.transpose();
64 removed_projector_.resize(0, 0);
65 if (removed > 0) {
66 const Eigen::MatrixXd U = es.vectors.leftCols(removed);
67 removed_projector_ = U * U.transpose();
68 }
70 << TimeStamp() << " Smallest value of AOOverlap matrix is " << s_eig(0)
71 << std::flush;
73 << TimeStamp() << " Removed " << removed
74 << " basisfunction from inverse overlap matrix (threshold " << etol << ")"
75 << std::flush;
76 if (removed == 0 && s_eig(0) < 1e3 * etol) {
78 << TimeStamp()
79 << " WARNING: the overlap matrix is nearly singular; if the SCF is "
80 "unstable, raise xtpdft.overlap_tolerance above "
81 << s_eig(0) << "." << std::flush;
82 }
83}
84
86 const Eigen::MatrixXd& H) const {
87 Eigen::MatrixXd H_ortho = Sminusahalf.transpose() * H * Sminusahalf;
88 if (removed_projector_.size() > 0) {
90 }
91 const SymmetricEigenSystem es = SymmetricEigen(H_ortho);
92 if (!es.values.allFinite()) {
93 throw std::runtime_error("Matrix Diagonalisation failed");
94 }
95
96 tools::EigenSystem result;
97 result.eigenvalues() = es.values;
98 result.eigenvectors() = Sminusahalf * es.vectors;
99 return result;
100}
101
103 const Eigen::VectorXd& v_ov, Index nao, Index nocclevels) const {
104 Index nvirt = nao - nocclevels;
105 Eigen::MatrixXd kappa = Eigen::MatrixXd::Zero(nao, nao);
106 for (Index i = 0; i < nocclevels; ++i) {
107 for (Index a = 0; a < nvirt; ++a) {
108 double val = v_ov(i * nvirt + a);
109 kappa(i, nocclevels + a) = val;
110 kappa(nocclevels + a, i) = -val;
111 }
112 }
113 return kappa;
114}
115
116std::pair<Eigen::MatrixXd, Eigen::MatrixXd>
118 Index nao_alpha,
119 Index nocclevels_alpha,
120 Index nao_beta,
121 Index nocclevels_beta) const {
122 Index n_ov_alpha = nocclevels_alpha * (nao_alpha - nocclevels_alpha);
123 Eigen::VectorXd v_alpha = v.head(n_ov_alpha);
124 Eigen::VectorXd v_beta = v.tail(v.size() - n_ov_alpha);
125 Eigen::MatrixXd kappa_alpha =
126 UnflattenRotation(v_alpha, nao_alpha, nocclevels_alpha);
127 Eigen::MatrixXd kappa_beta =
128 UnflattenRotation(v_beta, nao_beta, nocclevels_beta);
129 return {kappa_alpha, kappa_beta};
130}
131
133 const Eigen::VectorXd& v, const Eigen::MatrixXd& C_alpha,
134 Index nocclevels_alpha, const Eigen::MatrixXd& C_beta,
135 Index nocclevels_beta, const CoupledFockBuilder& coupled_fock_builder,
136 double finite_diff_step) const {
137 Index nao_alpha = C_alpha.rows();
138 Index nvirt_alpha = nao_alpha - nocclevels_alpha;
139 Index nao_beta = C_beta.rows();
140 Index nvirt_beta = nao_beta - nocclevels_beta;
141 Index n_ov_alpha = nocclevels_alpha * nvirt_alpha;
142 Index n_ov_beta = nocclevels_beta * nvirt_beta;
143
144 auto [kappa_alpha_trial, kappa_beta_trial] = UnflattenCoupledRotation(
145 v, nao_alpha, nocclevels_alpha, nao_beta, nocclevels_beta);
146 kappa_alpha_trial *= finite_diff_step;
147 kappa_beta_trial *= finite_diff_step;
148
149 // Linearized exponential + Lowdin reorthonormalization as in
150 // BuildSigmaVector, but both spins are rotated together and both Fock
151 // matrices built in one coupled_fock_builder call, which gives the
152 // alpha-beta coupling (shared Coulomb, cross-spin XC kernel).
153 auto EvaluateBothGradientsAt = [&](const Eigen::MatrixXd& kappa_alpha,
154 const Eigen::MatrixXd& kappa_beta)
155 -> std::pair<Eigen::MatrixXd, Eigen::MatrixXd> {
156 Eigen::MatrixXd C_alpha_rot =
157 C_alpha *
158 (Eigen::MatrixXd::Identity(nao_alpha, nao_alpha) + kappa_alpha);
159 Eigen::MatrixXd nonortho_alpha =
160 C_alpha_rot.transpose() * S_->Matrix() * C_alpha_rot;
161 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es_alpha(nonortho_alpha);
162 C_alpha_rot = C_alpha_rot * es_alpha.operatorInverseSqrt();
163
164 Eigen::MatrixXd C_beta_rot =
165 C_beta * (Eigen::MatrixXd::Identity(nao_beta, nao_beta) + kappa_beta);
166 Eigen::MatrixXd nonortho_beta =
167 C_beta_rot.transpose() * S_->Matrix() * C_beta_rot;
168 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es_beta(nonortho_beta);
169 C_beta_rot = C_beta_rot * es_beta.operatorInverseSqrt();
170
171 Eigen::MatrixXd C_alpha_occ_rot = C_alpha_rot.leftCols(nocclevels_alpha);
172 Eigen::MatrixXd D_alpha_rot = C_alpha_occ_rot * C_alpha_occ_rot.transpose();
173 Eigen::MatrixXd C_beta_occ_rot = C_beta_rot.leftCols(nocclevels_beta);
174 Eigen::MatrixXd D_beta_rot = C_beta_occ_rot * C_beta_occ_rot.transpose();
175
176 SpinFock H_rot = coupled_fock_builder(D_alpha_rot, D_beta_rot);
177 Eigen::MatrixXd F_MO_alpha_rot =
178 C_alpha_rot.transpose() * H_rot.alpha * C_alpha_rot;
179 Eigen::MatrixXd F_MO_beta_rot =
180 C_beta_rot.transpose() * H_rot.beta * C_beta_rot;
181 return {F_MO_alpha_rot, F_MO_beta_rot};
182 };
183
184 auto [F_MO_alpha_plus, F_MO_beta_plus] =
185 EvaluateBothGradientsAt(kappa_alpha_trial, kappa_beta_trial);
186 auto [F_MO_alpha_minus, F_MO_beta_minus] =
187 EvaluateBothGradientsAt(-kappa_alpha_trial, -kappa_beta_trial);
188
189 // The energy gradient is dE/dkappa_ia = -2 F_ia (see
190 // EnergyGradientScale), so the Hessian-vector product is -2 times the
191 // central difference of F_ia along v.
192 const double scale = EnergyGradientScale / (2.0 * finite_diff_step);
193 Eigen::VectorXd sigma(n_ov_alpha + n_ov_beta);
194 for (Index i = 0; i < nocclevels_alpha; ++i) {
195 for (Index a = 0; a < nvirt_alpha; ++a) {
196 double g_plus_ia = F_MO_alpha_plus(i, nocclevels_alpha + a);
197 double g_minus_ia = F_MO_alpha_minus(i, nocclevels_alpha + a);
198 sigma(i * nvirt_alpha + a) = scale * (g_plus_ia - g_minus_ia);
199 }
200 }
201 for (Index i = 0; i < nocclevels_beta; ++i) {
202 for (Index a = 0; a < nvirt_beta; ++a) {
203 double g_plus_ia = F_MO_beta_plus(i, nocclevels_beta + a);
204 double g_minus_ia = F_MO_beta_minus(i, nocclevels_beta + a);
205 sigma(n_ov_alpha + i * nvirt_beta + a) = scale * (g_plus_ia - g_minus_ia);
206 }
207 }
208 return sigma;
209}
210
212 const Eigen::MatrixXd& H_AO, const tools::EigenSystem& MOs,
213 Index nocclevels, double& predicted_energy_change) const {
214 Index nao = MOs.eigenvectors().rows();
215 const Eigen::MatrixXd& C = MOs.eigenvectors();
216 const Eigen::VectorXd& eps = MOs.eigenvalues();
217
218 // Orbital gradient: the occ-virt block of the MO-basis Fock matrix,
219 // which vanishes exactly at self-consistency (F_MO would be block-
220 // diagonal, occ-occ and virt-virt only).
221 Eigen::MatrixXd F_MO = C.transpose() * H_AO * C;
222
223 // Antisymmetric rotation generator: nonzero only in the occ-virt (and
224 // virt-occ, by antisymmetry) blocks -- occ-occ/virt-virt rotations
225 // would leave the density matrix (and therefore the energy) entirely
226 // unchanged, so there is nothing to gain from including them.
227 Eigen::MatrixXd kappa = Eigen::MatrixXd::Zero(nao, nao);
228 // g_h_ratio(i,a) below stores kappa_ia's own g_ia/h_ia BEFORE the
229 // whole-matrix trust-radius scaling further down -- needed again
230 // afterward to compute the quadratic model's predicted energy change
231 // using the FINAL (per-element- and trust-radius-clamped) kappa,
232 // since g_ia and h_ia themselves do not change under that later,
233 // uniform rescaling, only kappa does.
234 Eigen::MatrixXd h_matrix = Eigen::MatrixXd::Zero(nao, nao);
235 // Diagonal Hessian h_ia = max(|eps_a - eps_i|, kMinGap), kept positive
236 // definite. Occupations follow the column index, not the energy order,
237 // so gaps can be negative (occupied/virtual swaps) or near zero; without
238 // abs() a negative gap would become kMinGap and give a huge step.
239 constexpr double kMinGap = 1e-3;
240 // Per-pair step cap in addition to the trust radius below, so that a
241 // single near-degenerate pair cannot dominate the step direction.
242 constexpr double kMaxKappaElement = 0.1;
243 for (Index i = 0; i < nocclevels; ++i) {
244 for (Index a = nocclevels; a < nao; ++a) {
245 double gap = std::max(std::abs(eps(a) - eps(i)), kMinGap);
246 // Energy gradient g_ia = -2 F_ia (EnergyGradientScale) and the
247 // diagonal of the energy Hessian, 2 (eps_a - eps_i): the leading
248 // term of d^2E/dkappa_ia^2 for one spin channel.
249 double g_ia = EnergyGradientScale * F_MO(i, a);
250 double h_ia = 2.0 * gap;
251 double kappa_ia = -g_ia / h_ia;
252 kappa_ia = std::clamp(kappa_ia, -kMaxKappaElement, kMaxKappaElement);
253 kappa(i, a) = kappa_ia;
254 kappa(a, i) = -kappa_ia;
255 h_matrix(i, a) = h_ia;
256 }
257 }
258
259 double knorm = kappa.norm();
260 if (knorm > trust_.Radius()) {
261 kappa *= (trust_.Radius() / knorm);
262 }
263
264 // Quadratic model's predicted energy change, Sum_ia[g_ia*kappa_ia
265 // + 0.5*h_ia*kappa_ia^2], for the kappa actually applied (after the
266 // per-element clamp and the trust-radius scaling), as needed by
267 // Iterate's Fletcher accept/reject check.
268 predicted_energy_change = 0.0;
269 for (Index i = 0; i < nocclevels; ++i) {
270 for (Index a = nocclevels; a < nao; ++a) {
271 double kappa_ia = kappa(i, a);
272 predicted_energy_change += EnergyGradientScale * F_MO(i, a) * kappa_ia +
273 0.5 * h_matrix(i, a) * kappa_ia * kappa_ia;
274 }
275 }
276
277 // exp(kappa) ~= I + kappa is valid specifically because the trust-
278 // radius bound above keeps kappa small -- re-orthonormalize
279 // afterward since I+kappa is only approximately unitary, using the
280 // same Lowdin (symmetric orthogonalization) approach as
281 // DFTEngine::OrthogonalizeGuess.
282 Eigen::MatrixXd C_new = C * (Eigen::MatrixXd::Identity(nao, nao) + kappa);
283 Eigen::MatrixXd nonortho = C_new.transpose() * S_->Matrix() * C_new;
284 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es_ortho(nonortho);
285 return C_new * es_ortho.operatorInverseSqrt();
286}
287
288std::pair<Eigen::MatrixXd, Eigen::MatrixXd>
290 const Eigen::MatrixXd& H_AO_alpha, const tools::EigenSystem& MOs_alpha,
291 Index nocclevels_alpha, const Eigen::MatrixXd& H_AO_beta,
292 const tools::EigenSystem& MOs_beta, Index nocclevels_beta,
293 const CoupledFockBuilder& coupled_fock_builder, double trust_radius,
294 double& predicted_energy_change) const {
295 Index nao_alpha = MOs_alpha.eigenvectors().rows();
296 Index nvirt_alpha = nao_alpha - nocclevels_alpha;
297 Index n_ov_alpha = nocclevels_alpha * nvirt_alpha;
298 const Eigen::MatrixXd& C_alpha = MOs_alpha.eigenvectors();
299 const Eigen::VectorXd& eps_alpha = MOs_alpha.eigenvalues();
300
301 Index nao_beta = MOs_beta.eigenvectors().rows();
302 Index nvirt_beta = nao_beta - nocclevels_beta;
303 Index n_ov_beta = nocclevels_beta * nvirt_beta;
304 const Eigen::MatrixXd& C_beta = MOs_beta.eigenvectors();
305 const Eigen::VectorXd& eps_beta = MOs_beta.eigenvalues();
306
307 Index n_ov = n_ov_alpha + n_ov_beta;
308
309 Eigen::MatrixXd F_MO_alpha = C_alpha.transpose() * H_AO_alpha * C_alpha;
310 Eigen::MatrixXd F_MO_beta = C_beta.transpose() * H_AO_beta * C_beta;
311
312 // Energy gradient g = -2 F_ia (EnergyGradientScale) and the diagonal
313 // preconditioner 2 (eps_a - eps_i), the leading diagonal term of the
314 // energy Hessian that BuildCoupledSigmaVector applies; alpha block
315 // first, beta block after it, as in UnflattenCoupledRotation. The
316 // preconditioner has no alpha-beta cross terms; only the Davidson
317 // convergence depends on it, not the step.
318 Eigen::VectorXd g(n_ov);
319 Eigen::VectorXd diag_h(n_ov);
320 constexpr double kMinGap = 1e-3;
321 for (Index i = 0; i < nocclevels_alpha; ++i) {
322 for (Index a = 0; a < nvirt_alpha; ++a) {
323 g(i * nvirt_alpha + a) =
324 EnergyGradientScale * F_MO_alpha(i, nocclevels_alpha + a);
325 double gap = std::max(
326 std::abs(eps_alpha(nocclevels_alpha + a) - eps_alpha(i)), kMinGap);
327 diag_h(i * nvirt_alpha + a) = 2.0 * gap;
328 }
329 }
330 for (Index i = 0; i < nocclevels_beta; ++i) {
331 for (Index a = 0; a < nvirt_beta; ++a) {
332 g(n_ov_alpha + i * nvirt_beta + a) =
333 EnergyGradientScale * F_MO_beta(i, nocclevels_beta + a);
334 double gap = std::max(
335 std::abs(eps_beta(nocclevels_beta + a) - eps_beta(i)), kMinGap);
336 diag_h(n_ov_alpha + i * nvirt_beta + a) = 2.0 * gap;
337 }
338 }
339
340 // Hessian products by finite differences of the coupled Fock matrices,
341 // one vector at a time (two coupled Fock builds each).
342 bool analytic = bool(orbital_hessian_) && !analytic_hessian_failed_;
343 trah::HessianProduct hessian = [&](const Eigen::MatrixXd& V) {
344 if (analytic) {
345 try {
346 return orbital_hessian_(MOs_alpha, MOs_beta, H_AO_alpha, H_AO_beta, V);
347 } catch (const std::runtime_error& error) {
349 << TimeStamp() << " Analytic UKS Hessian not available ("
350 << error.what() << "); using finite differences" << std::flush;
351 analytic = false;
353 }
354 }
355 Eigen::MatrixXd HV(V.rows(), V.cols());
356 for (Index k = 0; k < V.cols(); ++k) {
357 HV.col(k) =
358 BuildCoupledSigmaVector(V.col(k), C_alpha, nocclevels_alpha, C_beta,
359 nocclevels_beta, coupled_fock_builder);
360 }
361 return HV;
362 };
363 const trah::StepResult step = trah::Step(g, diag_h, hessian, trust_radius,
365 predicted_energy_change = step.predicted;
367 last_step_norm_ = step.x.norm();
368
370 << TimeStamp() << " TRAH step: |g|=" << g.norm()
371 << ", |x|=" << step.x.norm() << ", trust radius=" << trust_radius
372 << ", alpha=" << step.alpha << ", micro-iterations=" << step.iterations
373 << ", Hessian products=" << step.products
374 << (analytic ? " (analytic)" : " (finite differences)")
375 << (step.reused ? " (subspace reused)" : "")
376 << ", residual=" << step.residual
377 << (step.converged ? "" : " (not converged)")
378 << ", predicted dE=" << predicted_energy_change << std::flush;
379
380 return {
381 trah::RotateOrbitals(C_alpha, step.x.head(n_ov_alpha), nocclevels_alpha),
382 trah::RotateOrbitals(C_beta, step.x.tail(n_ov_beta), nocclevels_beta)};
383}
384
386 const Eigen::MatrixXd& MOs, Index nocclevels) const {
387 if (nocclevels == 0) {
388 return Eigen::MatrixXd::Zero(MOs.rows(), MOs.rows());
389 }
390 Eigen::MatrixXd occstates = MOs.leftCols(nocclevels);
391 return occstates * occstates.transpose();
392}
393
395 const tools::EigenSystem& MOs_alpha,
396 const tools::EigenSystem& MOs_beta) const {
397 SpinDensity result;
400 result.beta =
402 return result;
403}
404
405void UKSConvergenceAcc::Levelshift(Eigen::MatrixXd& H,
406 const Eigen::MatrixXd& MOs_old,
407 const options& opt, Index nocclevels) const {
408 if (opt.levelshift < 1e-9) {
409 return;
410 }
411 Eigen::VectorXd virt = Eigen::VectorXd::Zero(H.rows());
412 for (Index i = nocclevels; i < H.rows(); ++i) {
413 virt(i) = opt.levelshift;
414 }
415
417 << TimeStamp() << " Using levelshift:" << opt.levelshift << " Hartree"
418 << std::flush;
419
420 Eigen::MatrixXd vir = S_->Matrix() * MOs_old * virt.asDiagonal() *
421 MOs_old.transpose() * S_->Matrix();
422 H += vir;
423}
424
426 const Eigen::MatrixXd& dmat, const Eigen::MatrixXd& H) const {
427 const Eigen::MatrixXd& S = S_->Matrix();
428 return Sminusahalf.transpose() * (H * dmat * S - S * dmat * H) * Sminusahalf;
429}
430
431double UKSConvergenceAcc::CombinedError(const Eigen::MatrixXd& err_alpha,
432 const Eigen::MatrixXd& err_beta) const {
433 return std::max(err_alpha.cwiseAbs().maxCoeff(),
434 err_beta.cwiseAbs().maxCoeff());
435}
436
438 const SpinDensity& dmat, SpinFock& H, tools::EigenSystem& MOs_alpha,
439 tools::EigenSystem& MOs_beta, double totE) {
440
441 // Check the second-order step returned by the previous call, now that
442 // the energy of its density (totE) is known: Fletcher's trust-radius
443 // update. A rejected step is undone and, while second-order steps
444 // continue, repeated at once with the smaller radius from the stored
445 // Fock matrices and Davidson subspace (see ConvergenceAcc::Iterate).
446 last_rejected_ = false;
448 direct_min_pending_ = false;
449 const double actual_change = totE - direct_min_pre_energy_;
450 const bool accepted =
451 trust_.Update(direct_min_predicted_change_, actual_change,
454 << TimeStamp()
455 << " Second-order step check: actual dE=" << actual_change
456 << ", predicted dE=" << direct_min_predicted_change_
457 << ", r=" << trust_.LastRatio()
458 << (accepted ? " (accepted)" : " (rejected)")
459 << ", new trust radius=" << trust_.Radius() << std::flush;
460 if (!accepted) {
461 if (trust_.Radius() < kMinTrustRadius) {
463 trah_active_ = false;
465 << TimeStamp() << " Trust radius below " << kMinTrustRadius
466 << " without an accepted step; leaving second-order steps for "
467 "this SCF"
468 << std::flush;
469 }
474 totE_.push_back(direct_min_pre_energy_);
475 last_rejected_ = true;
476 usedmixing_ = false;
477 if (trah_active_) {
478 const SpinFock H_pre = direct_min_pre_H_;
479 return SecondOrderStep(DensityMatrix(MOs_alpha, MOs_beta), H_pre,
480 MOs_alpha, MOs_beta, direct_min_pre_energy_);
481 }
482 return DensityMatrix(MOs_alpha, MOs_beta);
483 }
484 }
485
486 // History, trimmed at ONE index for the Fock, density and error
487 // histories and for DIIS's own error history: the oldest entry, or with
488 // DIIS_maxout the entry with the largest error. Previously the index was
489 // a stale position (never re-based after an erase, maxerror_ never
490 // reset), and DIIS::Update could trim at a different index than the
491 // Fock history, pairing coefficients with the wrong matrices. totE_ is
492 // not trimmed: it is the chronological energy list getDeltaE reads.
493 Index drop = 0;
494 if (int(mathist_alpha_.size()) == opt_alpha_.histlength) {
495 if (opt_alpha_.maxout) {
496 drop = Index(std::max_element(errhist_.begin(), errhist_.end()) -
497 errhist_.begin());
498 }
499 mathist_alpha_.erase(mathist_alpha_.begin() + drop);
500 mathist_beta_.erase(mathist_beta_.begin() + drop);
501 dmatHist_alpha_.erase(dmatHist_alpha_.begin() + drop);
502 dmatHist_beta_.erase(dmatHist_beta_.begin() + drop);
503 errhist_.erase(errhist_.begin() + drop);
504 }
505
506 totE_.push_back(totE);
507
508 if (nocclevels_alpha_ > 0 &&
509 nocclevels_alpha_ < MOs_alpha.eigenvalues().size()) {
510 double gap_alpha = MOs_alpha.eigenvalues()(nocclevels_alpha_) -
511 MOs_alpha.eigenvalues()(nocclevels_alpha_ - 1);
512 // No level shift once direct minimization takes over: Levelshift()
513 // modifies H in place, inflating the virtual orbital energies from
514 // which the diagonal preconditioner of
515 // CoupledAugmentedHessianStep/DirectMinimizationRotation is built,
516 // which makes the Davidson solve diverge. ADIIS/DIIS are unaffected.
517 if ((diiserror_ > opt_alpha_.levelshiftend &&
518 opt_alpha_.levelshift > 0.0) ||
519 gap_alpha < 1e-6) {
520 // "- 1": consecutive_adiis_failures_ is incremented later in this
521 // call, so this also covers the iteration on which direct
522 // minimization starts.
524 !trah_active_) {
525 Levelshift(H.alpha, MOs_alpha.eigenvectors(), opt_alpha_,
527 }
528 }
529 }
530
531 if (nocclevels_beta_ > 0 &&
532 nocclevels_beta_ < MOs_beta.eigenvalues().size()) {
533 double gap_beta = MOs_beta.eigenvalues()(nocclevels_beta_) -
534 MOs_beta.eigenvalues()(nocclevels_beta_ - 1);
535 if ((diiserror_ > opt_beta_.levelshiftend && opt_beta_.levelshift > 0.0) ||
536 gap_beta < 1e-6) {
538 !trah_active_) {
539 Levelshift(H.beta, MOs_beta.eigenvectors(), opt_beta_,
541 }
542 }
543 }
544
545 Eigen::MatrixXd err_alpha = BuildErrorMatrix(dmat.alpha, H.alpha);
546 Eigen::MatrixXd err_beta = BuildErrorMatrix(dmat.beta, H.beta);
547
548 diiserror_ = CombinedError(err_alpha, err_beta);
549
550 // Trailing-average trigger bookkeeping (see diiserror_history_),
551 // recorded every iteration whatever method is used.
556 }
557
558 mathist_alpha_.push_back(H.alpha);
559 mathist_beta_.push_back(H.beta);
560 dmatHist_alpha_.push_back(dmat.alpha);
561 dmatHist_beta_.push_back(dmat.beta);
562
563 errhist_.push_back(diiserror_);
564
565 // crucial: one shared error matrix = alpha + beta contribution
566 diis_.Update(drop, err_alpha, err_beta);
567
568 bool diis_error = false;
570 << TimeStamp() << " DIIs error " << diiserror_ << std::flush;
572 << TimeStamp() << " Delta Etot " << getDeltaE() << std::flush;
573
574 // Once second-order steps have started (second_order "auto" after the
575 // trigger below, or "always" from the start) they continue until the SCF
576 // has converged or the trust radius has collapsed.
578 opt_alpha_.second_order != "off") {
579 const bool failures =
581 const bool stalled = TrailingAverageStalled();
582 if (opt_alpha_.second_order == "always" || failures || stalled) {
584 << TimeStamp() << " Switching to second-order (TRAH) steps"
585 << (failures ? " ((A)DIIS failed repeatedly)" : "")
586 << (stalled ? " (DIIS error stalled)" : "") << std::flush;
587 trah_active_ = true;
588 }
589 }
590 if (trah_active_) {
591 return SecondOrderStep(dmat, H, MOs_alpha, MOs_beta, totE);
592 }
593
594 Eigen::MatrixXd H_guess_alpha = H.alpha;
595 Eigen::MatrixXd H_guess_beta = H.beta;
596
597 // As in ConvergenceAcc::Iterate: below DIIS_start (e.g. after a warm
598 // start) no damping, a plain first step and DIIS from two entries on.
599 const bool near_convergence = diiserror_ < opt_alpha_.diis_start;
600 const std::size_t min_history = near_convergence ? 1 : 2;
601 if ((diiserror_ < opt_alpha_.adiis_start ||
602 diiserror_ < opt_alpha_.diis_start) &&
603 opt_alpha_.usediis && mathist_alpha_.size() > min_history) {
604
605 Eigen::VectorXd coeffs;
606
607 if (diiserror_ > opt_alpha_.diis_start ||
608 totE_.back() - totE_[totE_.size() - 2] > kEnergyRiseForADIIS) {
611 diis_error = !adiis_.Info() || coeffs.size() == 0;
613 << TimeStamp() << " Using ADIIS for next UKS guess" << std::flush;
614 } else {
615 coeffs = diis_.CalcCoeff();
616 diis_error = !diis_.Info() || coeffs.size() == 0;
618 << TimeStamp() << " Using DIIS for next UKS guess" << std::flush;
619 }
620
621 if (diis_error) {
624 << TimeStamp() << " (A)DIIS failed using mixing instead"
625 << std::flush;
626 H_guess_alpha = H.alpha;
627 H_guess_beta = H.beta;
628 } else {
630 H_guess_alpha.setZero();
631 H_guess_beta.setZero();
632 for (Index i = 0; i < coeffs.size(); ++i) {
633 if (std::abs(coeffs(i)) < 1e-8) {
634 continue;
635 }
636 H_guess_alpha += coeffs(i) * mathist_alpha_[i];
637 H_guess_beta += coeffs(i) * mathist_beta_[i];
638 }
639 }
640 }
641
642 MOs_alpha = SolveFockmatrix(H_guess_alpha);
643 MOs_beta = SolveFockmatrix(H_guess_beta);
644
645 SpinDensity dmatout = DensityMatrix(MOs_alpha, MOs_beta);
646
647 if (diiserror_ > opt_alpha_.mixingend || !opt_alpha_.usediis || diis_error ||
648 (mathist_alpha_.size() <= 2 && !near_convergence)) {
649 // mixingend is independent of adiis_start (like ORCA's DampErr and
650 // DIISStart), so damping can stay on after DIIS starts; see
651 // convergenceacc.h.
652 usedmixing_ = true;
653 // Adaptive damping (cf. ORCA DampFac/DampMax): ramp linearly from
654 // mixingparameter to mixingmax as consecutive_adiis_failures_ goes to
655 // kMaxConsecutiveADIISFailures. The counter resets after a successful
656 // ADIIS/DIIS step, so the damping relaxes again.
657 double ramp_fraction =
658 std::min(1.0, double(consecutive_adiis_failures_) /
660 double mixingparameter_alpha_current =
661 opt_alpha_.mixingparameter +
662 ramp_fraction * (opt_alpha_.mixingmax - opt_alpha_.mixingparameter);
663 double mixingparameter_beta_current =
664 opt_beta_.mixingparameter +
665 ramp_fraction * (opt_beta_.mixingmax - opt_beta_.mixingparameter);
666 dmatout.alpha = mixingparameter_alpha_current * dmat.alpha +
667 (1.0 - mixingparameter_alpha_current) * dmatout.alpha;
668 dmatout.beta = mixingparameter_beta_current * dmat.beta +
669 (1.0 - mixingparameter_beta_current) * dmatout.beta;
671 << TimeStamp() << " Using coupled UKS mixing with adaptive alpha="
672 << mixingparameter_alpha_current
673 << " (base=" << opt_alpha_.mixingparameter
674 << ", ceiling=" << opt_alpha_.mixingmax
675 << ", ramp fraction=" << ramp_fraction << ")" << std::flush;
676 } else {
677 usedmixing_ = false;
678 }
679
680 return dmatout;
681}
682
683// After kAutoStartIteration iterations: true if the DIIS error has, on
684// average over the last kTrailingWindowSize iterations, shrunk by less than
685// a factor kMeanRatioTolerance per iteration (ORCA's AutoTRAH criterion:
686// "Auto Start mean grad. ratio tolernc. 1.125"). Evaluated every iteration,
687// also when the error stays above ADIIS_start and (A)DIIS is never tried.
691 return false;
692 }
693 double mean_ratio = 0.0;
694 Index ratio_count = 0;
695 for (Index i = 1; i < Index(diiserror_history_.size()); ++i) {
696 if (diiserror_history_[i - 1] > 1e-12) {
697 mean_ratio += diiserror_history_[i] / diiserror_history_[i - 1];
698 ++ratio_count;
699 }
700 }
701 return ratio_count > 0 &&
702 mean_ratio / double(ratio_count) > 1.0 / kMeanRatioTolerance;
703}
704
706 const SpinDensity& dmat, const SpinFock& H, tools::EigenSystem& MOs_alpha,
707 tools::EigenSystem& MOs_beta, double totE) {
708 // The step rotates MOs_alpha/MOs_beta, and its gradient and the Fletcher
709 // check are taken relative to H and totE, which belong to dmat. If dmat
710 // is not the density of these orbitals (after a damped iteration, or a
711 // guess density), return their density first; the step follows on the
712 // next call, from a consistent starting point.
713 SpinDensity dmat_mos = DensityMatrix(MOs_alpha, MOs_beta);
714 const double mismatch =
715 std::max((dmat_mos.alpha - dmat.alpha).cwiseAbs().maxCoeff(),
716 (dmat_mos.beta - dmat.beta).cwiseAbs().maxCoeff());
717 if (mismatch > kDensityMismatchTolerance) {
719 << TimeStamp()
720 << " Density differs from that of the current orbitals (max "
721 "deviation "
722 << mismatch << "); returning the orbital density first" << std::flush;
723 usedmixing_ = false;
724 return dmat_mos;
725 }
726
733
734 Eigen::MatrixXd C_new_alpha;
735 Eigen::MatrixXd C_new_beta;
736 // without the linear term (diagonal path) a rejection shrinks by 0.7
737 direct_min_linear_ = 0.0;
740 std::tie(C_new_alpha, C_new_beta) = CoupledAugmentedHessianStep(
741 H.alpha, MOs_alpha, nocclevels_alpha_, H.beta, MOs_beta,
746 } else {
747 // no Fock builder (e.g. the atomic UKS guess): diagonal Hessian, one
748 // channel at a time
749 double predicted_alpha = 0.0;
750 double predicted_beta = 0.0;
751 C_new_alpha = DirectMinimizationRotation(
752 H.alpha, MOs_alpha, nocclevels_alpha_, predicted_alpha);
753 C_new_beta = DirectMinimizationRotation(H.beta, MOs_beta, nocclevels_beta_,
754 predicted_beta);
755 direct_min_predicted_change_ = predicted_alpha + predicted_beta;
756 }
757 direct_min_pending_ = true;
758
759 MOs_alpha.eigenvectors() = C_new_alpha;
760 MOs_beta.eigenvectors() = C_new_beta;
761 // diagonal of the old Fock matrix in the rotated orbitals: for the
762 // preconditioner and the gap checks until the next diagonalisation
763 MOs_alpha.eigenvalues() =
764 (C_new_alpha.transpose() * H.alpha * C_new_alpha).diagonal();
765 MOs_beta.eigenvalues() =
766 (C_new_beta.transpose() * H.beta * C_new_beta).diagonal();
767 usedmixing_ = false;
768 return DensityMatrix(MOs_alpha, MOs_beta);
769}
770
772 if (totE_.size() < 2) {
773 return 100.0;
774 }
775 return std::abs(totE_.back() - totE_[totE_.size() - 2]);
776}
777
779 // not on the call that undid a rejected second-order step (see
780 // ConvergenceAcc::isConverged)
781 return (!last_rejected_ && getDeltaE() < opt_alpha_.Econverged &&
782 diiserror_ < opt_alpha_.error_converged);
783}
784
785} // namespace xtp
786} // namespace votca
const Eigen::VectorXd & eigenvalues() const
Definition eigensystem.h:30
const Eigen::MatrixXd & eigenvectors() const
Definition eigensystem.h:33
Logger is used for thread-safe output of messages.
Definition logger.h:164
Timestamp returns the current time as a string Example: cout << TimeStamp().
Definition logger.h:224
double CombinedError(const Eigen::MatrixXd &err_alpha, const Eigen::MatrixXd &err_beta) const
tools::EigenSystem SolveFockmatrix(const Eigen::MatrixXd &H) const
std::pair< Eigen::MatrixXd, Eigen::MatrixXd > UnflattenCoupledRotation(const Eigen::VectorXd &v, Index nao_alpha, Index nocclevels_alpha, Index nao_beta, Index nocclevels_beta) const
static constexpr Index kAutoStartIteration
SpinDensity DensityMatrix(const tools::EigenSystem &MOs_alpha, const tools::EigenSystem &MOs_beta) const
void Levelshift(Eigen::MatrixXd &H, const Eigen::MatrixXd &MOs_old, const options &opt, Index nocclevels) const
std::vector< Eigen::MatrixXd > mathist_beta_
static constexpr double kMinTrustRadius
static constexpr Index kMaxConsecutiveADIISFailures
void setOverlap(AOOverlap &S, double etol)
void Configure(const options &opt_alpha, const options &opt_beta)
Eigen::VectorXd BuildCoupledSigmaVector(const Eigen::VectorXd &v, const Eigen::MatrixXd &C_alpha, Index nocclevels_alpha, const Eigen::MatrixXd &C_beta, Index nocclevels_beta, const CoupledFockBuilder &coupled_fock_builder, double finite_diff_step=1e-3) const
static constexpr double kEnergyRiseForADIIS
Eigen::MatrixXd BuildErrorMatrix(const Eigen::MatrixXd &dmat, const Eigen::MatrixXd &H) const
ConvergenceAcc::options options
SpinDensity Iterate(const SpinDensity &dmat, SpinFock &H, tools::EigenSystem &MOs_alpha, tools::EigenSystem &MOs_beta, double totE)
std::vector< Eigen::MatrixXd > dmatHist_alpha_
Eigen::MatrixXd DirectMinimizationRotation(const Eigen::MatrixXd &H_AO, const tools::EigenSystem &MOs, Index nocclevels, double &predicted_energy_change) const
Eigen::MatrixXd UnflattenRotation(const Eigen::VectorXd &v_ov, Index nao, Index nocclevels) const
Eigen::VectorXd direct_min_pre_MOs_beta_energies_
static constexpr double EnergyGradientScale
std::vector< double > diiserror_history_
std::vector< Eigen::MatrixXd > dmatHist_beta_
CoupledFockBuilder coupled_fock_builder_
Eigen::VectorXd direct_min_pre_MOs_alpha_energies_
static constexpr double kDensityMismatchTolerance
Eigen::MatrixXd DensityMatrixGroundState_unres(const Eigen::MatrixXd &MOs, Index nocclevels) const
static constexpr Index kTrailingWindowSize
static constexpr double kMeanRatioTolerance
std::function< SpinFock(const Eigen::MatrixXd &, const Eigen::MatrixXd &)> CoupledFockBuilder
SpinDensity SecondOrderStep(const SpinDensity &dmat, const SpinFock &H, tools::EigenSystem &MOs_alpha, tools::EigenSystem &MOs_beta, double totE)
std::vector< Eigen::MatrixXd > mathist_alpha_
static constexpr double kRemovedShift
std::pair< Eigen::MatrixXd, Eigen::MatrixXd > CoupledAugmentedHessianStep(const Eigen::MatrixXd &H_AO_alpha, const tools::EigenSystem &MOs_alpha, Index nocclevels_alpha, const Eigen::MatrixXd &H_AO_beta, const tools::EigenSystem &MOs_beta, Index nocclevels_beta, const CoupledFockBuilder &coupled_fock_builder, double trust_radius, double &predicted_energy_change) const
#define XTP_LOG(level, log)
Definition logger.h:40
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
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
Eigen::VectorXd x
Definition trah.h:70