Loading...
Searching...
No Matches
Davidson.cpp
Go to the documentation of this file.
1/*
2** This file is part of eOn.
3**
4** SPDX-License-Identifier: BSD-3-Clause
5**
6** Copyright (c) 2010--present, eOn Development Team
7** All rights reserved.
8**
9** Repo:
10** https://github.com/TheochemUI/eOn
11*/
12// Minimum-mode via Davidson subspace iteration with finite-difference
13// Hessian-vector products (same H*v as Lanczos). Replaces dimer rotation
14// constrained minimization when min_mode_method = davidson.
15//
16// Krylov / Ritz space is the PHVA mobile set (see Lanczos.cpp): default
17// phva_atoms=All keeps all free atoms; an explicit list bounds the space
18// to 3 N_mobile without changing free/fixed.
19
20#include "eon/Davidson.h"
21#include "eon/EonLogger.h"
22#include "eon/HelperFunctions.h"
23#include "eon/MobileAtoms.h"
24#include "eon/Potential.h"
25#include "eon/SafeMath.h"
26
27#include <cmath>
28#include <memory>
29#include <vector>
30
31Davidson::Davidson(std::shared_ptr<Matter> matter, const Parameters &params,
32 std::shared_ptr<Potential> pot)
34 lowestEv.resize(matter->numberOfAtoms(), 3);
35 lowestEv.setZero();
36 lowestEw = 0.0;
37}
38
39void Davidson::compute(std::shared_ptr<Matter> matter, AtomMatrix direction) {
40 const VectorXi mobile =
41 resolveMobileAtoms(matter.get(), params.davidson_options.phva_atoms);
42 compute(std::move(matter), std::move(direction), mobile);
43}
44
45void Davidson::compute(std::shared_ptr<Matter> matter, AtomMatrix direction,
46 const VectorXi &mobileIn) {
49 lowestEv.resize(matter->numberOfAtoms(), 3);
50 lowestEv.setZero();
51
52 const VectorXi mobile = resolveMobileAtoms(matter.get(), mobileIn);
53 const int size = 3 * static_cast<int>(mobile.size());
54 if (size == 0) {
55 lowestEw = 0.0;
56 return;
57 }
58
59 const long maxIter = params.davidson_options.max_iterations;
60 const double tol = params.davidson_options.tolerance;
61 const double dr = params.main_options.finiteDifference;
62 const bool useDiagPrec = params.davidson_options.diagonal_preconditioner;
63
64 MatrixXd V(size, maxIter);
65 MatrixXd HV(size, maxIter);
66 V.setZero();
67 HV.setZero();
68
69 VectorXd r = packMobileRows(direction, mobile);
70 double beta = r.norm();
71 if (beta < eonc::safemath::eps) {
72 lowestEw = 0.0;
73 return;
74 }
75 r /= beta;
76
77 auto tmpMatter = std::make_unique<Matter>(*matter);
78 const long forceCallsStart = tmpMatter->getForceCalls();
79 const AtomMatrix pos0 = matter->getPositions();
80 const VectorXd force0 = mobileForces(tmpMatter.get(), mobile);
81
82 auto applyH = [&](const VectorXd &v) -> VectorXd {
83 AtomMatrix pos = pos0;
84 unpackMobileRows(packMobileRows(pos0, mobile) + dr * v, mobile, pos);
85 tmpMatter->setPositions(pos);
86 return -(mobileForces(tmpMatter.get(), mobile) - force0) / dr;
87 };
88
89 VectorXd diagH = VectorXd::Ones(size);
90
91 V.col(0) = r;
92 HV.col(0) = applyH(V.col(0));
93 if (useDiagPrec) {
94 for (int k = 0; k < size; ++k) {
95 const double vk = V(k, 0);
96 if (std::fabs(vk) > 1e-8) {
97 diagH(k) = std::max(std::fabs(HV(k, 0) / vk), 1e-3);
98 }
99 }
100 }
101
102 double ew = 0.0, ewOld = 0.0;
103 VectorXd evEst = V.col(0);
104 VectorXd evOldEst = evEst;
105 int subspace = 1;
106
107 for (int iter = 0; iter < maxIter; ++iter) {
108 statsRotations = iter;
109 statsAngle = 0.0;
110
111 MatrixXd G = V.leftCols(subspace).transpose() * HV.leftCols(subspace);
112 G = 0.5 * (G + G.transpose());
113
114 Eigen::SelfAdjointEigenSolver<MatrixXd> es(G);
115 ew = es.eigenvalues()(0);
116 VectorXd y = es.eigenvectors().col(0);
117 evEst = V.leftCols(subspace) * y;
118 evEst.normalize();
119
120 VectorXd Hx = HV.leftCols(subspace) * y;
121 VectorXd resid = Hx - ew * evEst;
122 const double residNorm = resid.norm();
123 const double ewAbsRelErr =
124 (iter == 0) ? 1.0
125 : eonc::safemath::safe_div(std::fabs(ew - ewOld),
126 std::fabs(ewOld), 1.0);
127 ewOld = ew;
128 statsTorque = std::max(ewAbsRelErr, residNorm / (std::fabs(ew) + 1e-12));
129 statsAngle = eonc::safemath::safe_acos(std::fabs(evEst.dot(evOldEst))) *
130 (180 / eonc::helpers::pi);
131 evOldEst = evEst;
132
133 QUILL_LOG_INFO(log,
134 "[Davidson] ew={:10.6f} rel_err={:10.6f} |r|={:10.6f} "
135 "angle={:7.3f} dim={:3d} iter={:3d} n_mobile={}",
136 ew, ewAbsRelErr, residNorm, statsAngle, subspace, iter,
137 mobile.size());
138
139 if (ewAbsRelErr < tol && residNorm < tol * (std::fabs(ew) + 1.0)) {
140 QUILL_LOG_INFO(log, "[Davidson] Tolerance reached: {}", tol);
141 break;
142 }
143 if (subspace >= maxIter) {
144 QUILL_LOG_ERROR(log, "[Davidson] Max subspace dimension");
145 break;
146 }
147
148 VectorXd t(size);
149 if (useDiagPrec) {
150 for (int k = 0; k < size; ++k) {
151 const double denom = diagH(k) - ew;
152 t(k) = resid(k) / (std::fabs(denom) > 1e-12 ? denom : 1e-12);
153 }
154 } else {
155 t = resid;
156 }
157
158 for (int c = 0; c < subspace; ++c) {
159 t -= V.col(c).dot(t) * V.col(c);
160 }
161 const double tnorm = t.norm();
162 if (tnorm < 1e-14) {
163 QUILL_LOG_ERROR(log, "[Davidson] Linear dependence in residual");
164 break;
165 }
166 t /= tnorm;
167
168 V.col(subspace) = t;
169 HV.col(subspace) = applyH(t);
170 if (useDiagPrec) {
171 for (int k = 0; k < size; ++k) {
172 const double vk = t(k);
173 if (std::fabs(vk) > 1e-8) {
174 const double d = std::fabs(HV(k, subspace) / vk);
175 diagH(k) = std::max(diagH(k), std::max(d, 1e-3));
176 }
177 }
178 }
179 ++subspace;
180
181 if (iter >= maxIter - 1) {
182 QUILL_LOG_ERROR(log, "[Davidson] Max iterations");
183 break;
184 }
185 }
186
187 lowestEw = ew;
188 totalForceCalls = tmpMatter->getForceCalls() - forceCallsStart;
189 lowestEv.setZero();
190 if (evEst.size() == size) {
191 unpackMobileRows(evEst, mobile, lowestEv);
192 }
193}
194
196
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, eOnStorageOrder > MatrixXd
Definition Eigen.h:33
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
Davidson(std::shared_ptr< Matter > matter, const Parameters &params, std::shared_ptr< Potential > pot)
Definition Davidson.cpp:31
double lowestEw
Definition Davidson.h:45
AtomMatrix getEigenvector()
Definition Davidson.cpp:197
AtomMatrix lowestEv
Definition Davidson.h:44
eonc::log::Scoped log
Definition Davidson.h:46
double getEigenvalue()
Definition Davidson.cpp:195
void compute(std::shared_ptr< Matter > matter, AtomMatrix initialDirection)
Definition Davidson.cpp:39
const Parameters & params
std::shared_ptr< Potential > pot
LowestEigenmode(std::shared_ptr< Potential > potPassed, const Parameters &parameters)
constexpr double pi
constexpr double safe_div(double num, double denom, double fallback=0.0)
Definition SafeMath.h:21
double safe_acos(double x)
Definition SafeMath.h:34
constexpr double eps
Definition SafeMath.h:19
VectorXi resolveMobileAtoms(const Matter *matter, const std::string &atomList)
PHVA-class mobile set for FD Hessian and matrix-free Krylov (Lanczos / Davidson).
void unpackMobileRows(const VectorXd &packed, const VectorXi &mobile, AtomMatrix &full)
Write a 3*n_mobile vector into full AtomMatrix rows (other rows unchanged).
VectorXd packMobileRows(const AtomMatrix &full, const VectorXi &mobile)
Pack full (n_atoms,3) rows of mobile atoms into a 3*n_mobile vector.
VectorXd mobileForces(Matter *matter, const VectorXi &mobile)
Force components on mobile atoms after Matter has a valid force cache (calls getForces under the hood...