Loading...
Searching...
No Matches
Hessian.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#include "eon/Hessian.h"
13#include "eon/EonLogger.h"
14#include "eon/HelperFunctions.h"
15#include "eon/SafeMath.h"
16
17#include <cmath>
18#include <fstream>
19#include <sstream>
20#include <string>
21
22namespace {
23
24// phva_atoms entries are *mobile / displaced* atoms for FD (hybrid/PHVA-class
25// active set). Intersect with non-fixed atoms in HessianJob.
26
27bool isCentralScheme(const std::string &scheme) {
28 return scheme == "central" || scheme == "CENTRAL" || scheme == "Central";
29}
30
31// Checkpoint: first line "eon_hess_ckpt <size> <next_col>", then size*size
32// doubles in row-major order matching MatrixXd storage.
33bool loadColumnCheckpoint(const std::string &path, int size, int &nextCol,
34 MatrixXd &H) {
35 std::ifstream in(path);
36 if (!in) {
37 return false;
38 }
39 std::string tag;
40 int fileSize = 0;
41 in >> tag >> fileSize >> nextCol;
42 if (!in || tag != "eon_hess_ckpt" || fileSize != size || nextCol < 0 ||
43 nextCol > size) {
44 return false;
45 }
46 H.resize(size, size);
47 for (int i = 0; i < size; ++i) {
48 for (int j = 0; j < size; ++j) {
49 double v = 0.0;
50 in >> v;
51 if (!in) {
52 return false;
53 }
54 H(i, j) = v;
55 }
56 }
57 return true;
58}
59
60bool saveColumnCheckpoint(const std::string &path, int size, int nextCol,
61 const MatrixXd &H) {
62 std::ofstream out(path);
63 if (!out) {
64 return false;
65 }
66 out << "eon_hess_ckpt " << size << " " << nextCol << "\n";
67 out.precision(17);
68 for (int i = 0; i < size; ++i) {
69 for (int j = 0; j < size; ++j) {
70 out << H(i, j) << (j + 1 == size ? '\n' : ' ');
71 }
72 }
73 return static_cast<bool>(out);
74}
75
76} // namespace
77
79 : matter{matter},
80 parameters{params} {
81 hessian.resize(0, 0);
82 freqs.resize(0);
83}
84
85MatrixXd Hessian::getHessian(Matter *matterIn, const VectorXi &atomsIn) {
86 if ((matter != matterIn) || (atoms.size() != atomsIn.size()) ||
87 (atoms != atomsIn) || (hessian.rows() == 0)) {
88 hessian.resize(0, 0);
89 matter = matterIn;
90 atoms = atomsIn;
91
92 if (!calculate()) {
93 hessian.resize(0, 0);
94 }
95 }
96 return hessian;
97}
98
99VectorXd Hessian::getFreqs(Matter *matterIn, const VectorXi &atomsIn) {
100 if ((matter != matterIn) || (atoms.size() != atomsIn.size()) ||
101 (atoms != atomsIn) || (hessian.rows() == 0)) {
102 hessian.resize(0, 0);
103 matter = matterIn;
104 atoms = atomsIn;
105
106 if (!calculate()) {
107 freqs.resize(0);
108 }
109 }
110 return freqs;
111}
112
114 int nAtoms = matter->numberOfAtoms();
115
116 int size = static_cast<int>(atoms.rows()) * 3;
117 QUILL_LOG_DEBUG(log, "[Hessian] Hessian size: {}\n", size);
118 if (size == 0) {
119 return false;
120 }
121
122 // Mobile-atom polarity: indices in `atoms` are FD-displaced DOF owners.
123 for (int a = 0; a < atoms.rows(); ++a) {
124 const long idx = atoms(a);
125 if (idx < 0 || idx >= nAtoms) {
126 QUILL_LOG_ERROR(log,
127 "[Hessian] atom index {} out of range [0, {}) at list "
128 "entry {}; aborting FD Hessian",
129 idx, nAtoms, a);
130 return false;
131 }
132 }
133
134 Matter matterTemp(*matter);
135 double dr = parameters.main_options.finiteDifference;
136 if (!(dr > 0.0) || !std::isfinite(dr)) {
137 QUILL_LOG_ERROR(log, "[Hessian] invalid finiteDifference dr={}\n", dr);
138 return false;
139 }
140
141 const bool useCentral = isCentralScheme(parameters.hessian_options.fd_scheme);
142 const std::string &ckptPath = parameters.hessian_options.checkpoint_path;
143 const bool wantResume =
144 parameters.hessian_options.resume && !ckptPath.empty();
145
146 AtomMatrix pos = matter->getPositions();
147 AtomMatrix posDisplace(nAtoms, 3);
148 AtomMatrix posTemp(nAtoms, 3);
149 AtomMatrix force0(nAtoms, 3);
150 AtomMatrix forcePlus(nAtoms, 3);
151 AtomMatrix forceMinus(nAtoms, 3);
152
153 hessian.resize(size, size);
154 hessian.setZero();
155
156 int startCol = 0;
157 if (wantResume && loadColumnCheckpoint(ckptPath, size, startCol, hessian)) {
158 QUILL_LOG_DEBUG(log, "[Hessian] resume from column {} / {}\n", startCol,
159 size);
160 } else {
161 startCol = 0;
162 hessian.setZero();
163 }
164
165 force0 = matterTemp.getForces();
166 if (!force0.allFinite()) {
167 QUILL_LOG_ERROR(log, "[Hessian] non-finite forces at undisplaced geometry; "
168 "aborting FD Hessian");
169 return false;
170 }
171
172 for (int i = startCol; i < size; i++) {
173 posDisplace.setZero();
174 posDisplace(atoms(i / 3), i % 3) = dr;
175
176 posTemp = pos + posDisplace;
177 matterTemp.setPositions(posTemp);
178 forcePlus = matterTemp.getForces();
179 if (!forcePlus.allFinite()) {
180 QUILL_LOG_ERROR(log,
181 "[Hessian] non-finite forces for FD column {} (+); "
182 "aborting FD Hessian",
183 i);
184 return false;
185 }
186
187 if (useCentral) {
188 posTemp = pos - posDisplace;
189 matterTemp.setPositions(posTemp);
190 forceMinus = matterTemp.getForces();
191 if (!forceMinus.allFinite()) {
192 QUILL_LOG_ERROR(log,
193 "[Hessian] non-finite forces for FD column {} (-); "
194 "aborting FD Hessian",
195 i);
196 return false;
197 }
198 // Central: H_ij ≈ -(F+(xj) - F-(xj)) / (2 dr), mass-weighted
199 for (int j = 0; j < size; j++) {
200 const double dF =
201 forcePlus(atoms(j / 3), j % 3) - forceMinus(atoms(j / 3), j % 3);
202 hessian(i, j) = -dF / (2.0 * dr);
203 const double effMass = std::sqrt(matter->getMass(atoms(j / 3)) *
204 matter->getMass(atoms(i / 3)));
205 hessian(i, j) = eonc::safemath::safe_div(hessian(i, j), effMass, 0.0);
206 }
207 } else {
208 // One-sided (forward): H_ij ≈ -(F+(xj) - F0(xj)) / dr [default; cheaper]
209 for (int j = 0; j < size; j++) {
210 const double dF =
211 forcePlus(atoms(j / 3), j % 3) - force0(atoms(j / 3), j % 3);
212 hessian(i, j) = -dF / dr;
213 const double effMass = std::sqrt(matter->getMass(atoms(j / 3)) *
214 matter->getMass(atoms(i / 3)));
215 hessian(i, j) = eonc::safemath::safe_div(hessian(i, j), effMass, 0.0);
216 }
217 }
218
219 if (!ckptPath.empty()) {
220 // next column to compute after a clean interrupt
221 saveColumnCheckpoint(ckptPath, size, i + 1, hessian);
222 }
223 }
224
225 // Symmetrize (FD noise breaks H=H^T; required for vib analysis)
226 for (int i = 0; i < size; i++) {
227 for (int j = 0; j < i; j++) {
228 hessian(i, j) = (hessian(i, j) + hessian(j, i)) / 2;
229 hessian(j, i) = hessian(i, j);
230 }
231 }
232
233 if (!hessian.allFinite()) {
234 QUILL_LOG_ERROR(log, "[Hessian] non-finite entries after FD assembly; "
235 "aborting eigen solve");
236 return false;
237 }
238
239 if (!parameters.main_options.quiet) {
240 QUILL_LOG_DEBUG(log, "[Hessian] writing hessian\n");
241 std::ofstream hessfile;
242 hessfile.open("hessian.dat");
243 hessfile << hessian;
244 hessfile.close();
245 }
246
247 // Completed run: remove checkpoint so a later job does not resume stale cols
248 if (!ckptPath.empty()) {
249 std::remove(ckptPath.c_str());
250 }
251
252 double t0, t1;
253 eonc::helpers::getTime(&t0, nullptr, nullptr);
254 QUILL_LOG_DEBUG(log, "[Hessian] calculating eigen values of the hessian\n");
255 // ColMajor copy for SelfAdjointEigenSolver (eOn MatrixXd is RowMajor)
256 using ColMajorXd =
257 Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>;
258 ColMajorXd hessianCol = hessian;
259 Eigen::SelfAdjointEigenSolver<ColMajorXd> es(hessianCol,
260 Eigen::EigenvaluesOnly);
261 eonc::helpers::getTime(&t1, nullptr, nullptr);
262 QUILL_LOG_DEBUG(log, "[Hessian] eigenvalue problem took {:.4e} seconds\n",
263 t1 - t0);
264 if (es.info() != Eigen::Success) {
265 QUILL_LOG_ERROR(log,
266 "[Hessian] SelfAdjointEigenSolver failed (info={}); "
267 "aborting",
268 static_cast<int>(es.info()));
269 return false;
270 }
271 freqs = es.eigenvalues();
272 if (!freqs.allFinite()) {
273 QUILL_LOG_ERROR(log, "[Hessian] non-finite eigenvalues; aborting");
274 return false;
275 }
276
277 return true;
278}
279
280VectorXd Hessian::removeZeroFreqs(const VectorXd &freqs) {
281 QUILL_LOG_DEBUG(log, "[Hessian] removing zero frequency modes");
282 int size = freqs.size();
283 if (size != 3 * matter->numberOfAtoms()) {
284 return freqs;
285 }
286 VectorXd newfreqs;
287 newfreqs.resize(size);
288 int nremoved = 0;
289 for (int i = 0; i < size; i++) {
290 if (std::abs(freqs(i)) > parameters.hessian_options.zero_freq_value) {
291 newfreqs(i - nremoved) = freqs(i);
292 } else {
293 nremoved++;
294 }
295 }
296
297 if (nremoved != 6) {
298 QUILL_LOG_ERROR(
299 log, "[Hessian] [error] Found {} trivial eigenmodes instead of 6",
300 nremoved);
301 }
302 return newfreqs.head(size - nremoved);
303}
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
Hessian(const Parameters &params, Matter *matter)
Definition Hessian.cpp:78
bool calculate()
Definition Hessian.cpp:113
eonc::log::Scoped log
Definition Hessian.h:40
Matter * matter
Definition Hessian.h:32
VectorXd freqs
Definition Hessian.h:36
VectorXd removeZeroFreqs(const VectorXd &freqs)
Definition Hessian.cpp:280
VectorXi atoms
Definition Hessian.h:38
MatrixXd getHessian(Matter *matterIn, const VectorXi &atomsIn)
Definition Hessian.cpp:85
VectorXd getFreqs(Matter *matterIn, const VectorXi &atomsIn)
Definition Hessian.cpp:99
const Parameters & parameters
Definition Hessian.h:33
MatrixXd hessian
Definition Hessian.h:35
const AtomMatrix & getForces() const
Definition Matter.cpp:324
void setPositions(const AtomMatrix &pos)
Definition Matter.cpp:273
void getTime(double *real, double *user, double *sys)
constexpr double safe_div(double num, double denom, double fallback=0.0)
Definition SafeMath.h:21