Loading...
Searching...
No Matches
ImprovedDimer Class Reference

#include <ImprovedDimer.h>

Inheritance diagram for ImprovedDimer:

Public Member Functions

 ImprovedDimer (std::shared_ptr< Matter > matter, const Parameters &params, std::shared_ptr< Potential > pot)
 ~ImprovedDimer ()=default
void compute (std::shared_ptr< Matter > matter, AtomMatrix initialDirection)
double getEigenvalue ()
AtomMatrix getEigenvector ()
void setReferenceMode (const VectorXd &ref)
void clearReferenceMode ()
Public Member Functions inherited from eonc::LowestEigenmode
 LowestEigenmode (std::shared_ptr< Potential > potPassed, const Parameters &parameters)
 ~LowestEigenmode ()=default

Public Attributes

std::shared_ptr< Matterx0
std::shared_ptr< Matterx1
VectorXd tau
VectorXd theta
VectorXd F_R
double C_tau {0.0}
bool rotationDidConverge {false}
bool foundNegativeCurvature {false}
VectorXd F_R_Old
VectorXd thetaOld
double a {0.0}
double b {0.0}
double gamma {0.0}
bool init_cg {false}
std::vector< VectorXd > s
std::vector< VectorXd > y
std::vector< double > rho
bool init_lbfgs {false}
VectorXd rPrev
std::vector< VectorXd > gradients
std::vector< VectorXd > positions
Public Attributes inherited from eonc::LowestEigenmode
long totalForceCalls {0}
double statsTorque {0.0}
double statsCurvature {0.0}
double statsAngle {0.0}
long statsRotations {0}
long totalIterations {0}

Static Public Attributes

static const char OPT_SD [] = "sd"
static const char OPT_CG [] = "cg"
static const char OPT_LBFGS [] = "lbfgs"
Static Public Attributes inherited from eonc::LowestEigenmode
static const char MINMODE_DIMER [] = "dimer"
static const char MINMODE_GPRDIMER [] = "gprdimer"
static const char MINMODE_LANCZOS [] = "lanczos"
static const char MINMODE_DAVIDSON [] = "davidson"

Private Attributes

eonc::log::Scoped log
VectorXd fixedReferenceMode
bool hasFixedReference = false

Additional Inherited Members

Protected Attributes inherited from eonc::LowestEigenmode
std::shared_ptr< Potentialpot
const Parametersparams

Detailed Description

Definition at line 23 of file ImprovedDimer.h.

Constructor & Destructor Documentation

◆ ImprovedDimer()

ImprovedDimer::ImprovedDimer ( std::shared_ptr< Matter > matter,
const Parameters & params,
std::shared_ptr< Potential > pot )

Definition at line 32 of file ImprovedDimer.cpp.

36 // Each dimer image gets its own potential for lock-free parallel evaluation
37 auto x1Pot = (pot->needsPerImageInstance() && params.main_options.parallel)
39 : pot;
40 x0 = std::make_shared<Matter>(pot, params);
41 x1 = std::make_shared<Matter>(x1Pot, params);
42 *x0 = *matter;
43 *x1 = *matter;
44 tau.resize(3 * matter->numberOfAtoms());
45 tau.setZero();
47
48 if (params.dimer_options.opt_method == OPT_CG) {
49 init_cg = true;
50 }
51}
std::shared_ptr< Matter > x1
static const char OPT_CG[]
std::shared_ptr< Matter > x0
const Parameters & params
std::shared_ptr< Potential > pot
LowestEigenmode(std::shared_ptr< Potential > potPassed, const Parameters &parameters)
std::shared_ptr< Potential > makePotential(const Parameters &params)

◆ ~ImprovedDimer()

Member Function Documentation

◆ clearReferenceMode()

Definition at line 61 of file ImprovedDimer.cpp.

61{ hasFixedReference = false; }

◆ compute()

void ImprovedDimer::compute ( std::shared_ptr< Matter > matter,
AtomMatrix initialDirection )

Definition at line 63 of file ImprovedDimer.cpp.

64 {
65
66 VectorXd initialDirection = VectorXd::Map(initialDirectionAtomMatrix.data(),
67 3 * matter->numberOfAtoms());
68 tau = initialDirection.array() * matter->getFreeV().array();
71 if (tau.norm() > 1e-10) {
72 eonc::safemath::safe_normalize_inplace(tau);
73 } else {
74 // Fallback if the tangent was zero on free atoms (unlikely but safe)
75 tau.setRandom();
76 tau = tau.array() * matter->getFreeV().array();
77 eonc::safemath::safe_normalize_inplace(tau);
78 }
79
80 // Track the best (most negative) curvature and corresponding mode
81 double bestNegativeCurvature = std::numeric_limits<double>::max();
82 VectorXd bestTau = tau;
83 VectorXd bestX0Positions;
84 VectorXd bestG0, bestG1;
85
86 // Reference mode tracking for OCINEB mode-switching prevention
87 VectorXd referenceMode = hasFixedReference ? fixedReferenceMode : tau;
88
89 *x0 = *matter;
90 *x1 = *matter;
91 VectorXd x0_r = x0->getPositionsV();
92 bestX0Positions = x0_r;
93
94 double delta = params.main_options.finiteDifference;
95 x1->setPositionsV(x0_r + delta * tau);
96
97 // If we stepped into a high-energy wall, flip the tangent immediately
98 if (x1->getPotentialEnergy() - x0->getPotentialEnergy() > 10.0 * delta) {
99 tau = -tau;
100 x1->setPositionsV(x0_r + delta * tau);
101 QUILL_LOG_DEBUG(
102 log, "[IDimer] Initial tangent flipped due to high energy wall.");
103 }
104
105 // Optional: LOR / Lanczos / Davidson rotation backends (enum dispatch).
106 if (auto alt = runAlternativeRotation(
107 params.dimer_options.rotation_backend, matter, params, pot,
108 AtomMatrix::Map(tau.data(), matter->numberOfAtoms(), 3),
109 static_cast<quill::Logger *>(log))) {
110 C_tau = alt->eigenvalue;
111 tau = VectorXd::Map(alt->eigenvector.data(), 3 * matter->numberOfAtoms());
112 totalForceCalls += alt->forceCalls;
113 statsRotations = alt->rotations;
114 tau = tau.array() * matter->getFreeV().array();
115 eonc::safemath::safe_normalize_inplace(tau);
116 x0_r = matter->getPositionsV();
117 x0->setPositionsV(x0_r);
118 x1->setPositionsV(x0_r + delta * tau);
119 *matter = *x0;
120 rotationDidConverge = alt->converged;
122 return;
123 }
124
125 if (params.dimer_options.opt_method == OPT_LBFGS) {
126 s.clear();
127 y.clear();
128 rho.clear();
129 init_lbfgs = true;
130 }
131
132 VectorXd x1_rp, x1_r, tau_prime, tau_Old, g1_prime;
133 double phi_tol =
134 eonc::helpers::pi * (params.dimer_options.converged_angle / 180.0);
135 double phi_prime = 0.0;
136 double phi_min = 0.0;
137
138 statsRotations = 0;
139
140 // Use x1's potential for the trial rotation image (consistent with per-image)
141 auto x1p = std::make_shared<Matter>(x1->getPotential(), params);
142
143 // Melander, Laasonen, Jonsson, JCTC 11(3), 1055-1062, 2015
144 if (params.dimer_options.remove_rotation) {
145 rotationRemove(AtomMatrix::Map(x0_r.data(), x0->numberOfAtoms(), 3), x1);
146 x1_r = x1->getPositionsV();
147 tau = x1_r - x0_r;
148 eonc::safemath::safe_normalize_inplace(tau);
149 x1_r = x0_r + tau * delta;
150 }
151
152 // Calculate gradients on x0 and x1.
153 // Prefer batched evaluation when the potential supports it (single
154 // model.forward() call for both replicas, e.g. MetatomicPotential on GPU).
155 // Else fall back to thread-parallel when the potential is thread-safe or
156 // wants per-image instances. Otherwise sequential.
157 VectorXd g0, g1;
158 bool canParallel =
159 pot->isSharedInstanceThreadSafe() || pot->needsPerImageInstance();
160 if (pot->supportsBatchEvaluation()) {
161 long n = x0->numberOfAtoms();
162 bool x0dirty = x0->needsForceUpdate();
163 bool x1dirty = x1->needsForceUpdate();
164
165 if (x0dirty && x1dirty) {
166 auto nrs0 = x0->getAtomicNrs();
167 auto nrs1 = x1->getAtomicNrs();
168 auto box0 = x0->getCell();
169 auto box1 = x1->getCell();
170 const double *posVec[] = {x0->getPositions().data(),
171 x1->getPositions().data()};
172 const int *nrsVec[] = {nrs0.data(), nrs1.data()};
173 double *frcVec[] = {x0->forcesData(), x1->forcesData()};
174 double energies[2], vars[2];
175 const double *boxVec[] = {box0.data(), box1.data()};
176 pot->forceBatch(2, n, posVec, nrsVec, frcVec, energies, vars, boxVec);
177 x0->setComputedPotential(energies[0], vars[0]);
178 x1->setComputedPotential(energies[1], vars[1]);
179 } else if (x1dirty) {
180 auto nrs = x1->getAtomicNrs();
181 auto box = x1->getCell();
182 const double *posVec[] = {x1->getPositions().data()};
183 const int *nrsVec[] = {nrs.data()};
184 double *frcVec[] = {x1->forcesData()};
185 double energies[1], vars[1];
186 const double *boxVec[] = {box.data()};
187 pot->forceBatch(1, n, posVec, nrsVec, frcVec, energies, vars, boxVec);
188 x1->setComputedPotential(energies[0], vars[0]);
189 } else if (x0dirty) {
190 x0->getForcesRaw(); // through computePotential
191 }
192 g0 = -x0->getForcesV();
193 g1 = -x1->getForcesV();
194 } else if (params.main_options.parallel && canParallel) {
195 // std::thread instead of std::jthread (Apple Clang libc++). Guard so an
196 // exception from the foreground call still joins t0 before rethrow.
197 std::thread t0([&] { g0 = -x0->getForcesV(); });
198 try {
199 g1 = -x1->getForcesV();
200 } catch (...) {
201 if (t0.joinable())
202 t0.join();
203 throw;
204 }
205 t0.join();
206 } else {
207 g0 = -x0->getForcesV();
208 g1 = -x1->getForcesV();
209 }
210
211 bestG0 = g0;
212 bestG1 = g1;
213
214 positions.clear();
215 gradients.clear();
216 positions.push_back(x0->getPositionsV());
217 positions.push_back(x1->getPositionsV());
218 gradients.push_back(g0);
219 gradients.push_back(g1);
220
221 do { // Rotation loop: converge phi or hit max rotations
222
223 // Rotational force, F_R
224 F_R = -2.0 * (g1 - g0) + 2.0 * ((g1 - g0).dot(tau)) * tau;
225 statsTorque = eonc::safemath::safe_div(F_R.norm(), delta * 2.0, 0.0);
226
227 // Determine step direction theta via selected optimizer
228 if (params.dimer_options.opt_method == OPT_SD) {
229 theta = eonc::safemath::safe_normalized(F_R);
230
231 } else if (params.dimer_options.opt_method == OPT_CG) {
232 if (init_cg) {
233 init_cg = false;
234 gamma = 0.0;
235 } else {
236 a = std::abs(F_R.dot(F_R_Old));
237 b = F_R_Old.squaredNorm();
238 gamma = (a < 0.5 * b)
240 : 0.0;
241 }
242
243 theta = (gamma == 0.0) ? F_R : F_R + thetaOld * gamma;
244 theta -= theta.dot(tau) * tau;
245 thetaOld = theta;
246 if (theta.norm() < eonc::safemath::eps) {
247 theta = F_R - F_R.dot(tau) * tau;
248 }
249 eonc::safemath::safe_normalize_inplace(theta);
250 F_R_Old = F_R;
251
252 } else if (params.dimer_options.opt_method == OPT_LBFGS) {
253 if (!init_lbfgs) {
254 VectorXd s0 = tau - tau_Old;
255 s.push_back(s0);
256 VectorXd y0 =
257 eonc::safemath::safe_div(1.0, delta, 0.0) * (F_R_Old - F_R);
258 y.push_back(y0);
259 rho.push_back(eonc::safemath::safe_recip(s0.dot(y0), 0.0));
260 } else {
261 init_lbfgs = false;
262 }
263
264 double H0 = 1.0 / 60.0;
265 size_t loopmax = s.size();
266 std::vector<double> alpha(loopmax);
267
268 VectorXd q = -F_R;
269 for (long i = static_cast<long>(loopmax) - 1; i >= 0; i--) {
270 alpha[i] = rho[i] * s[i].dot(q);
271 q -= alpha[i] * y[i];
272 }
273 VectorXd z = H0 * q;
274 for (size_t i = 0; i < loopmax; i++) {
275 double bv = rho[i] * y[i].dot(z);
276 z += s[i] * (alpha[i] - bv);
277 }
278
279 double vd = std::clamp(-eonc::safemath::safe_normalized(z).dot(
280 eonc::safemath::safe_normalized(F_R)),
281 -1.0, 1.0);
282 double angle =
284
285 if (angle > 87.0) {
286 s.clear();
287 y.clear();
288 rho.clear();
289 z = -F_R;
290 }
291
292 theta = -eonc::safemath::safe_normalized(z);
293 theta -= theta.dot(tau) * tau;
294 eonc::safemath::safe_normalize_inplace(theta);
295
296 thetaOld = theta;
297 F_R_Old = F_R;
298 tau_Old = tau;
299 }
300
301 // Curvature along tau
302 C_tau = eonc::safemath::safe_div((g1 - g0).dot(tau), delta, 0.0);
303
304 // Track best negative curvature for mode restoration
305 if (C_tau < bestNegativeCurvature) {
306 bestNegativeCurvature = C_tau;
307 bestTau = tau;
308 bestX0Positions = x0->getPositionsV();
309 bestG0 = g0;
310 bestG1 = g1;
312 }
313
314 // Estimate optimum rotation angle
315 double d_C_tau_d_phi =
316 2.0 * eonc::safemath::safe_div((g1 - g0).dot(theta), delta, 0.0);
317 phi_prime = -0.5 * eonc::safemath::safe_atan_ratio(
318 d_C_tau_d_phi, 2.0 * std::abs(C_tau), 0.0);
319 statsAngle = phi_prime * (180.0 / eonc::helpers::pi);
320
321 double alignment = std::abs(tau.dot(referenceMode));
322
323 if (std::abs(phi_prime) > phi_tol) {
324 double b1 = 0.5 * d_C_tau_d_phi;
325
326 // Trial rotation to phi_prime
327 x0_r = x0->getPositionsV();
328 tau_prime = tau * std::cos(phi_prime) + theta * std::sin(phi_prime);
329 tau_prime = eonc::safemath::safe_normalized(tau_prime);
330 x1_rp = x0_r + tau_prime * delta;
331
332 *x1p = *x1;
333 x1p->setPositionsV(x1_rp);
334 g1_prime = -x1p->getForcesV();
335
336 positions.push_back(x1_rp);
337 gradients.push_back(g1_prime);
338
339 double C_tau_prime =
340 eonc::safemath::safe_div((g1_prime - g0).dot(tau_prime), delta, 0.0);
341
342 // Optimal rotation angle via Fourier interpolation
343 double a1 = eonc::safemath::safe_div(
344 C_tau - C_tau_prime + b1 * std::sin(2.0 * phi_prime),
345 1.0 - std::cos(2.0 * phi_prime), 0.0);
346 double a0 = 2.0 * (C_tau - a1);
347 phi_min = 0.5 * eonc::safemath::safe_atan_ratio(b1, a1, 0.0);
348
349 double C_tau_min = 0.5 * a0 + a1 * std::cos(2.0 * phi_min) +
350 b1 * std::sin(2.0 * phi_min);
351
352 // If curvature is being maximized, push over pi/2
353 if (C_tau_min > C_tau) {
354 phi_min += eonc::helpers::pi * 0.5;
355 C_tau_min = 0.5 * a0 + a1 * std::cos(2.0 * phi_min) +
356 b1 * std::sin(2.0 * phi_min);
357 }
358
359 // Keep phi_min in [-pi/2, pi/2] for accurate LBFGS
360 if (phi_min > eonc::helpers::pi * 0.5) {
361 phi_min -= eonc::helpers::pi;
362 }
363 statsAngle = phi_min * (180.0 / eonc::helpers::pi);
364
365 // Apply optimal rotation
366 tau = tau * std::cos(phi_min) + theta * std::sin(phi_min);
367 tau = eonc::safemath::safe_normalized(tau);
368 x1_r = x0_r + tau * delta;
369
370 // Melander, Laasonen, Jonsson, JCTC 11(3), 1055-1062, 2015
371 if (params.dimer_options.remove_rotation) {
372 x1->setPositionsV(x1_r);
373 rotationRemove(AtomMatrix::Map(x0_r.data(), x0->numberOfAtoms(), 3),
374 x1);
375 x1_r = x1->getPositionsV();
376 tau = x1_r - x0_r;
377 eonc::safemath::safe_normalize_inplace(tau);
378 x1_r = x0_r + tau * delta;
379 }
380
381 x1->setPositionsV(x1_r);
382 C_tau = C_tau_min;
383
384 // Interpolate g1 at phi_min from g1 and g1_prime (saves one force call)
385 double sin_pp = std::sin(phi_prime);
386 g1 = g1 * eonc::safemath::safe_div(std::sin(phi_prime - phi_min), sin_pp,
387 0.0) +
388 g1_prime * eonc::safemath::safe_div(std::sin(phi_min), sin_pp, 0.0) +
389 g0 * (1.0 - std::cos(phi_min) -
390 std::sin(phi_min) * std::tan(phi_prime * 0.5));
391
392 statsTorque = eonc::safemath::safe_div(F_R.norm(), 2.0 * delta, 0.0);
393 statsRotations += 1;
394 QUILL_LOG_INFO(
395 log,
396 "[IDimerRot] ----- --------- ---------- ------------------ "
397 "{:9.4f} {:7.3f} {:6.3f} {:4} {:5.3f}",
399 } else {
400 QUILL_LOG_INFO(
401 log,
402 "[IDimerRot] ----- --------- ---------- ------------------ "
403 "{:9.4f} {:7.3f} ------ ---- {:5.3f}",
404 C_tau, F_R.norm() / delta, alignment);
405 }
406
407 // Check for mode loss (OCINEB dimer refinement)
408 if (alignment < params.neb_options.climbing_image.ocineb.angle_tol &&
409 params.neb_options.climbing_image.ocineb.use_mmf) {
410 QUILL_LOG_WARNING(
411 log, "Terminating dimer due to lost mode (align {:.3f}).", alignment);
412 rotationDidConverge = false;
413
414 if (bestNegativeCurvature < 0.0) {
415 // Restore the best negative curvature state
416 C_tau = bestNegativeCurvature;
417 tau = bestTau;
418 x0->setPositionsV(bestX0Positions);
419 x1->setPositionsV(bestX0Positions + delta * bestTau);
420 *matter = *x0;
421 QUILL_LOG_DEBUG(
422 log, "Restored best negative curvature state: C_tau={:.4f}", C_tau);
423 throw eonc::DimerModeRestoredException();
424 } else {
425 QUILL_LOG_WARNING(
426 log, "Never found negative curvature. Final C_tau: {:.4f}", C_tau);
427 throw eonc::DimerModeLostException();
428 }
429 }
430
431 } while (std::abs(phi_prime) > std::abs(phi_tol) &&
432 std::abs(phi_min) > std::abs(phi_tol) &&
433 statsRotations < params.dimer_options.rotations_max);
434}
bool foundNegativeCurvature
std::vector< VectorXd > s
VectorXd fixedReferenceMode
std::vector< VectorXd > positions
static const char OPT_LBFGS[]
static const char OPT_SD[]
bool rotationDidConverge
VectorXd F_R_Old
std::vector< VectorXd > gradients
std::vector< VectorXd > y
eonc::log::Scoped log
VectorXd thetaOld
std::vector< double > rho
constexpr double pi
void rotationRemove(const AtomMatrix r1, std::shared_ptr< Matter > m2)
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
constexpr double safe_recip(double x, double fallback=0.0)
Definition SafeMath.h:29
double safe_atan_ratio(double num, double denom, double fallback=0.0)
Definition SafeMath.h:42
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.

◆ getEigenvalue()

Definition at line 436 of file ImprovedDimer.cpp.

436{ return C_tau; }

◆ getEigenvector()

Definition at line 438 of file ImprovedDimer.cpp.

438 {
439 return AtomMatrix::Map(tau.data(), x0->numberOfAtoms(), 3);
440}

◆ setReferenceMode()

void ImprovedDimer::setReferenceMode ( const VectorXd & ref)

Definition at line 53 of file ImprovedDimer.cpp.

53 {
55 if (fixedReferenceMode.norm() > 1e-10) {
56 eonc::safemath::safe_normalize_inplace(fixedReferenceMode);
57 }
58 hasFixedReference = true;
59}

Member Data Documentation

◆ a

double eonc::ImprovedDimer::a {0.0}

Definition at line 62 of file ImprovedDimer.h.

62{0.0}, b{0.0}, gamma{0.0};

◆ b

double eonc::ImprovedDimer::b {0.0}

Definition at line 62 of file ImprovedDimer.h.

62{0.0}, b{0.0}, gamma{0.0};

◆ C_tau

Definition at line 53 of file ImprovedDimer.h.

53{0.0}; // Curvature along tau

◆ F_R

Definition at line 52 of file ImprovedDimer.h.

◆ F_R_Old

Definition at line 60 of file ImprovedDimer.h.

◆ fixedReferenceMode

Definition at line 28 of file ImprovedDimer.h.

◆ foundNegativeCurvature

Definition at line 58 of file ImprovedDimer.h.

58{false};

◆ gamma

Definition at line 62 of file ImprovedDimer.h.

62{0.0}, b{0.0}, gamma{0.0};

◆ gradients

std::vector<VectorXd> eonc::ImprovedDimer::gradients

Definition at line 71 of file ImprovedDimer.h.

◆ hasFixedReference

Definition at line 29 of file ImprovedDimer.h.

◆ init_cg

Definition at line 63 of file ImprovedDimer.h.

63{false};

◆ init_lbfgs

Definition at line 68 of file ImprovedDimer.h.

68{false};

◆ log

Definition at line 26 of file ImprovedDimer.h.

◆ OPT_CG

const char ImprovedDimer::OPT_CG = "cg"
static

Definition at line 37 of file ImprovedDimer.h.

◆ OPT_LBFGS

const char ImprovedDimer::OPT_LBFGS = "lbfgs"
static

Definition at line 38 of file ImprovedDimer.h.

◆ OPT_SD

const char ImprovedDimer::OPT_SD = "sd"
static

Definition at line 36 of file ImprovedDimer.h.

◆ positions

std::vector<VectorXd> eonc::ImprovedDimer::positions

Definition at line 72 of file ImprovedDimer.h.

◆ rho

std::vector<double> eonc::ImprovedDimer::rho

Definition at line 67 of file ImprovedDimer.h.

◆ rotationDidConverge

Definition at line 57 of file ImprovedDimer.h.

57{false};

◆ rPrev

Definition at line 69 of file ImprovedDimer.h.

◆ s

std::vector<VectorXd> eonc::ImprovedDimer::s

Definition at line 66 of file ImprovedDimer.h.

◆ tau

Definition at line 50 of file ImprovedDimer.h.

◆ theta

Definition at line 51 of file ImprovedDimer.h.

◆ thetaOld

Definition at line 61 of file ImprovedDimer.h.

◆ x0

std::shared_ptr<Matter> eonc::ImprovedDimer::x0

Definition at line 48 of file ImprovedDimer.h.

◆ x1

std::shared_ptr<Matter> eonc::ImprovedDimer::x1

Definition at line 49 of file ImprovedDimer.h.

◆ y

std::vector<VectorXd> eonc::ImprovedDimer::y

Definition at line 66 of file ImprovedDimer.h.


The documentation for this class was generated from the following files: