Loading...
Searching...
No Matches
Dimer.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/Dimer.h"
14#include "eon/HelperFunctions.h"
15#include "eon/SafeMath.h"
16
17#include <cassert>
18#include <cmath>
19#include <stdexcept>
20#include <thread>
21
22namespace eonc {
23
24namespace {
25
26// A NaN batch force makes torque NaN. Every rotation-exit comparison then
27// fails, including rotations_max, so the loop never returns.
28bool batchForcesFinite(long nAtoms, const double *forces, double energy) {
29 if (forces == nullptr || !std::isfinite(energy)) {
30 return false;
31 }
32 return Eigen::Map<const AtomMatrix>(forces, nAtoms, 3).allFinite();
33}
34
35void rejectNonFiniteBatch(long nAtoms, const double *forces, double energy) {
36 if (!batchForcesFinite(nAtoms, forces, energy)) {
37 throw std::runtime_error("Dimer::calcRotationalForceReturnCurvature: "
38 "non-finite batch forces");
39 }
40}
41
42} // namespace
43
44Dimer::Dimer(std::shared_ptr<Matter> matter, const Parameters &params,
45 std::shared_ptr<Potential> pot)
47 // Give matterDimer its own potential for parallel force evaluation
48 auto dimerPot =
49 (pot->needsPerImageInstance() && params.main_options().parallel)
51 : pot;
52 matterCenter = std::make_shared<Matter>(pot, params);
53 matterDimer = std::make_shared<Matter>(dimerPot, params);
54 *matterCenter = *matter;
55 *matterDimer = *matter;
56 nAtoms = matter->numberOfAtoms();
57
58 direction.resize(nAtoms, 3);
59 rotationalPlane.resize(nAtoms, 3);
60 direction.setZero();
61 rotationalPlane.setZero();
63}
64
65void Dimer::compute(std::shared_ptr<Matter> matter,
66 AtomMatrix initialDirection) {
67 *matterCenter = *matter;
68
69 eonc::safemath::safe_normalize_inplace(initialDirection);
70 direction = initialDirection;
71
72 // Optional: LOR / Lanczos / Davidson (enum dispatch; classical falls
73 // through).
74 if (auto alt = runAlternativeRotation(params.dimer_options().rotation_backend,
75 matter, params, pot, direction,
76 static_cast<quill::Logger *>(log))) {
77 eigenvalue = alt->eigenvalue;
78 direction = alt->eigenvector;
79 totalForceCalls += alt->forceCalls;
80 statsRotations = alt->rotations;
81 eonc::safemath::safe_normalize_inplace(direction);
82 for (long i = 0; i < nAtoms; ++i) {
83 if (matterCenter->getFixed(i)) {
84 direction.row(i).setZero();
85 }
86 }
87 *matterCenter = *matter;
88 return;
89 }
90
91 long rotations = 0;
92 long forceCallsCenter = matterCenter->getForceCalls();
93 long forceCallsDimer = matterDimer->getForceCalls();
94 double curvature = 0.0;
95 double rotationAngle = 0.0;
96 double torque = 0.0;
97
98 AtomMatrix rotationalForce(nAtoms, 3);
99 AtomMatrix rotationalForceOld(nAtoms, 3);
100 AtomMatrix rotationalPlaneOld(nAtoms, 3);
101 rotationalForce.setZero();
102 rotationalForceOld.setZero();
103 rotationalPlaneOld.setZero();
104
105 statsAngle = 0;
106 double lengthRotationalForceOld = 0.0;
107
108 // Two force calls per rotation iteration
109 bool doneRotating = false;
110 while (!doneRotating) {
111 curvature = calcRotationalForceReturnCurvature(rotationalForce);
112
113 determineRotationalPlane(rotationalForce, rotationalForceOld,
114 rotationalPlaneOld, lengthRotationalForceOld);
115
116 torque = rotationalForce.norm();
117 // torque == 0 is a valid aligned dimer; isnormal(0) is false.
118 assert(std::isfinite(torque));
119
120 // Convergence: stop if torque is below threshold or max rotations reached
121 if ((torque > params.dimer_options().torque_max &&
122 rotations >= params.dimer_options().rotations_max) ||
123 (torque < params.dimer_options().torque_max &&
124 torque >= params.dimer_options().torque_min &&
125 rotations >= params.dimer_options().rotations_min) ||
126 (torque < params.dimer_options().torque_min)) {
127 doneRotating = true;
128 }
129
130 // A probe after acceptance would return rotation_angle off the
131 // direction whose curvature was just measured.
132 if (!doneRotating) {
133 double rotForce1 = matDot(rotationalForce, rotationalPlane);
134 rotate(params.dimer_options().rotation_angle);
135
136 curvature = calcRotationalForceReturnCurvature(rotationalForce);
137 double rotForce2 = matDot(rotationalForce, rotationalPlane);
138
139 double rotForceChange =
140 (rotForce1 - rotForce2) / params.dimer_options().rotation_angle;
141 double forceDimer = (rotForce1 + rotForce2) / 2.0;
142
143 rotationAngle = eonc::safemath::safe_atan_ratio(2.0 * forceDimer,
144 rotForceChange, 0.0) /
145 2.0 -
146 params.dimer_options().rotation_angle / 2.0;
147
148 if (rotForceChange < 0) {
149 rotationAngle += eonc::helpers::pi / 2.0;
150 }
151
152 rotate(rotationAngle);
153 rotationalPlaneOld = rotationalPlane;
154 rotations++;
155 }
156 QUILL_LOG_DEBUG(log,
157 "[DimerRot] ----- --------- ---------------- "
158 "--------- {:9.3e} {:9.3e} {:9.3e} ---------\n",
159 curvature, torque,
160 rotationAngle * (180.0 / eonc::helpers::pi));
161 }
162
163 statsTorque = torque;
164 statsCurvature = curvature;
165 eonc::safemath::safe_normalize_inplace(direction);
166 for (long i = 0; i < nAtoms; ++i) {
167 if (matterCenter->getFixed(i)) {
168 direction.row(i).setZero();
169 }
170 }
172 statsAngle *= (180.0 / eonc::helpers::pi);
173 statsRotations = rotations;
174 eigenvalue = curvature;
175
176 forceCallsCenter = matterCenter->getForceCalls() - forceCallsCenter;
177 forceCallsDimer = matterDimer->getForceCalls() - forceCallsDimer;
178 totalForceCalls += forceCallsCenter + forceCallsDimer;
179}
180
182
184
186 AtomMatrix posCenter = matterCenter->getPositions();
187
188 // Displace to get dimer configuration A
189 AtomMatrix posDimer =
190 posCenter + direction * params.main_options().finiteDifference;
191
192 // Optional rotation removal (Melander, Laasonen, Jonsson, JCTC 2015)
193 if (params.dimer_options().remove_rotation) {
194 matterDimer->setPositions(posDimer);
196 posDimer = matterDimer->getPositions();
197 direction = matterCenter->pbc(posDimer - posCenter);
198 eonc::safemath::safe_normalize_inplace(direction);
199 posDimer = posCenter + direction * params.main_options().finiteDifference;
200 }
201
202 // Obtain forces for dimer and center
203 matterDimer->setPositions(posDimer);
204 AtomMatrix forceA, forceCenter;
205 if (pot->supportsBatchEvaluation()) {
206 // Only batch systems that actually need recomputation.
207 bool centerDirty = matterCenter->needsForceUpdate();
208 bool dimerDirty = matterDimer->needsForceUpdate();
209
210 if (centerDirty && dimerDirty) {
211 // Both need eval -- batch together
212 auto nrs0 = matterCenter->getAtomicNrs();
213 auto nrs1 = matterDimer->getAtomicNrs();
214 auto box0 = matterCenter->getCell();
215 auto box1 = matterDimer->getCell();
216 const double *posVec[] = {matterCenter->getPositions().data(),
217 posDimer.data()};
218 const int *nrsVec[] = {nrs0.data(), nrs1.data()};
219 double *frcVec[] = {matterCenter->forcesData(),
220 matterDimer->forcesData()};
221 double energies[2], vars[2];
222 const double *boxVec[] = {box0.data(), box1.data()};
223 pot->forceBatch(2, nAtoms, posVec, nrsVec, frcVec, energies, vars,
224 boxVec);
225 rejectNonFiniteBatch(nAtoms, frcVec[0], energies[0]);
226 rejectNonFiniteBatch(nAtoms, frcVec[1], energies[1]);
227 matterCenter->setComputedPotential(energies[0], vars[0]);
228 matterDimer->setComputedPotential(energies[1], vars[1]);
229 } else if (dimerDirty) {
230 // Only dimer moved -- eval just dimer, center is cached
231 auto nrs = matterDimer->getAtomicNrs();
232 auto box = matterDimer->getCell();
233 const double *posVec[] = {posDimer.data()};
234 const int *nrsVec[] = {nrs.data()};
235 double *frcVec[] = {matterDimer->forcesData()};
236 double energies[1], vars[1];
237 const double *boxVec[] = {box.data()};
238 pot->forceBatch(1, nAtoms, posVec, nrsVec, frcVec, energies, vars,
239 boxVec);
240 rejectNonFiniteBatch(nAtoms, frcVec[0], energies[0]);
241 matterDimer->setComputedPotential(energies[0], vars[0]);
242 } else if (centerDirty) {
243 // Only center moved (rare)
244 matterCenter->getForces(); // through computePotential
245 }
246 // else: both cached, nothing to do
247 forceCenter = matterCenter->getForces();
248 forceA = matterDimer->getForces();
249 } else {
250 forceA = matterDimer->getForces();
251 forceCenter = matterCenter->getForces();
252 }
253 AtomMatrix forceB = 2.0 * forceCenter - forceA;
254
255 double projA = matDot(direction, forceA);
256 double projB = matDot(direction, forceB);
257
258 // Remove force component parallel to dimer
259 forceA = helpers::makeOrthogonal(forceA, direction);
260 forceB = helpers::makeOrthogonal(forceB, direction);
261
262 // Rotational force = orthogonal force difference
263 rotationalForce =
264 (forceA - forceB) / (2.0 * params.main_options().finiteDifference);
265
266 // Curvature along the dimer
267 return (projB - projA) / (2.0 * params.main_options().finiteDifference);
268}
269
270void Dimer::determineRotationalPlane(const AtomMatrix &rotationalForce,
271 AtomMatrix &rotationalForceOld,
272 const AtomMatrix &rotationalPlaneOld,
273 double &lengthRotationalForceOld) {
274 double gamma = 0.0;
275 double a = std::abs(matDot(rotationalForce, rotationalForceOld));
276 double b = rotationalForceOld.squaredNorm();
277 if (a < 0.5 * b) {
278 // Polak-Ribiere conjugate gradient direction
279 gamma = (rotationalForce.array() *
280 (rotationalForce - rotationalForceOld).array())
281 .sum() /
282 b;
283 }
284
285 // New rotational plane from current force and previous plane
287 rotationalForce + rotationalPlaneOld * lengthRotationalForceOld * gamma;
288
289 // Orthogonalize to dimer direction and normalize
290 lengthRotationalForceOld = rotationalPlane.norm();
292 eonc::safemath::safe_normalize_inplace(rotationalPlane);
293
294 rotationalForceOld = rotationalForce;
295}
296
297void Dimer::rotate(double rotationAngle) {
298 statsAngle += rotationAngle;
299
300 double cosA = std::cos(rotationAngle);
301 double sinA = std::sin(rotationAngle);
302
303 AtomMatrix newDirection = direction * cosA + rotationalPlane * sinA;
304 AtomMatrix newPlane = rotationalPlane * cosA - direction * sinA;
305 direction = std::move(newDirection);
306 rotationalPlane = std::move(newPlane);
307
308 eonc::safemath::safe_normalize_inplace(direction);
309 eonc::safemath::safe_normalize_inplace(rotationalPlane);
310
311 // Remove component from rotationalPlane parallel to direction
313 eonc::safemath::safe_normalize_inplace(rotationalPlane);
314}
315
316} // namespace eonc
double matDot(const AtomMatrix &a, const AtomMatrix &b)
SIMD-optimized dot product for contiguous Eigen matrices.
Definition Eigen.h:50
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
AtomMatrix direction
Definition Dimer.h:37
AtomMatrix rotationalPlane
Definition Dimer.h:38
std::shared_ptr< Matter > matterDimer
Definition Dimer.h:36
long nAtoms
Definition Dimer.h:40
double getEigenvalue() override
Definition Dimer.cpp:181
AtomMatrix getEigenvector() override
Definition Dimer.cpp:183
std::shared_ptr< Matter > matterCenter
Definition Dimer.h:35
void determineRotationalPlane(const AtomMatrix &rotationalForce, AtomMatrix &rotationalForceOld, const AtomMatrix &rotationalPlaneOld, double &lengthRotationalForceOld)
Determine rotational plane via conjugate gradient.
Definition Dimer.cpp:270
double eigenvalue
Definition Dimer.h:39
double calcRotationalForceReturnCurvature(AtomMatrix &rotationalForce)
Compute rotational force and return curvature along the dimer.
Definition Dimer.cpp:185
void rotate(double rotationAngle)
Rotate the dimer by the given angle (radians).
Definition Dimer.cpp:297
eonc::log::FileScoped log
Definition Dimer.h:34
void compute(std::shared_ptr< Matter > matter, AtomMatrix initialDirection) override
Definition Dimer.cpp:65
Dimer(std::shared_ptr< Matter > matter, const Parameters &params, std::shared_ptr< Potential > pot)
Definition Dimer.cpp:44
const Parameters & params
std::shared_ptr< Potential > pot
LowestEigenmode(std::shared_ptr< Potential > potPassed, const Parameters &parameters)
void rotationRemove(const AtomMatrix r1, std::shared_ptr< Matter > m2)
constexpr double pi
std::shared_ptr< Potential > makePotential(const Parameters &params)
AtomMatrix makeOrthogonal(const AtomMatrix v1, const AtomMatrix v2)
double safe_acos(double x)
Definition SafeMath.h:34
double safe_atan_ratio(double num, double denom, double fallback=0.0)
Definition SafeMath.h:42
RAII resource manager for the ARTn C library with global synchronization.
std::optional< DimerRotationResult > runAlternativeRotation(DimerRotationBackend backend, const std::shared_ptr< Matter > &matter, const Parameters &params, const std::shared_ptr< Potential > &pot, const AtomMatrix &initialDirection, quill::Logger *log=nullptr)
Run Lanczos, Davidson, or LOR.