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
76 tau =
tau.array() * matter->getFreeV().array();
77 eonc::safemath::safe_normalize_inplace(
tau);
78 }
79
80
81 double bestNegativeCurvature = std::numeric_limits<double>::max();
82 VectorXd bestTau =
tau;
83 VectorXd bestX0Positions;
84 VectorXd bestG0, bestG1;
85
86
88
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
98 if (
x1->getPotentialEnergy() -
x0->getPotentialEnergy() > 10.0 * delta) {
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
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());
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);
122 return;
123 }
124
130 }
131
132 VectorXd x1_rp, x1_r, tau_prime, tau_Old, g1_prime;
133 double phi_tol =
135 double phi_prime = 0.0;
136 double phi_min = 0.0;
137
139
140
141 auto x1p = std::make_shared<Matter>(
x1->getPotential(),
params);
142
143
144 if (
params.dimer_options.remove_rotation) {
146 x1_r =
x1->getPositionsV();
148 eonc::safemath::safe_normalize_inplace(
tau);
149 x1_r = x0_r +
tau * delta;
150 }
151
152
153
154
155
156
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) {
191 }
192 g0 = -
x0->getForcesV();
193 g1 = -
x1->getForcesV();
194 }
else if (
params.main_options.parallel && canParallel) {
195
196
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
220
221 do {
222
223
224 F_R = -2.0 * (g1 - g0) + 2.0 * ((g1 - g0).dot(
tau)) *
tau;
226
227
229 theta = eonc::safemath::safe_normalized(
F_R);
230
235 } else {
240 : 0.0;
241 }
242
248 }
249 eonc::safemath::safe_normalize_inplace(
theta);
251
254 VectorXd s0 =
tau - tau_Old;
256 VectorXd y0 =
260 } else {
262 }
263
264 double H0 = 1.0 / 60.0;
265 size_t loopmax =
s.size();
266 std::vector<double> alpha(loopmax);
267
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) {
290 }
291
292 theta = -eonc::safemath::safe_normalized(z);
294 eonc::safemath::safe_normalize_inplace(
theta);
295
299 }
300
301
303
304
305 if (
C_tau < bestNegativeCurvature) {
306 bestNegativeCurvature =
C_tau;
308 bestX0Positions =
x0->getPositionsV();
309 bestG0 = g0;
310 bestG1 = g1;
312 }
313
314
315 double d_C_tau_d_phi =
318 d_C_tau_d_phi, 2.0 * std::abs(
C_tau), 0.0);
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
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
333 x1p->setPositionsV(x1_rp);
334 g1_prime = -x1p->getForcesV();
335
338
339 double C_tau_prime =
341
342
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);
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
353 if (C_tau_min >
C_tau) {
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
362 }
364
365
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
371 if (
params.dimer_options.remove_rotation) {
372 x1->setPositionsV(x1_r);
375 x1_r =
x1->getPositionsV();
377 eonc::safemath::safe_normalize_inplace(
tau);
378 x1_r = x0_r +
tau * delta;
379 }
380
381 x1->setPositionsV(x1_r);
383
384
385 double sin_pp = std::sin(phi_prime);
387 0.0) +
389 g0 * (1.0 - std::cos(phi_min) -
390 std::sin(phi_min) * std::tan(phi_prime * 0.5));
391
394 QUILL_LOG_INFO(
396 "[IDimerRot] ----- --------- ---------- ------------------ "
397 "{:9.4f} {:7.3f} {:6.3f} {:4} {:5.3f}",
399 } else {
400 QUILL_LOG_INFO(
402 "[IDimerRot] ----- --------- ---------- ------------------ "
403 "{:9.4f} {:7.3f} ------ ---- {:5.3f}",
404 C_tau,
F_R.norm() / delta, alignment);
405 }
406
407
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);
413
414 if (bestNegativeCurvature < 0.0) {
415
416 C_tau = bestNegativeCurvature;
418 x0->setPositionsV(bestX0Positions);
419 x1->setPositionsV(bestX0Positions + delta * bestTau);
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) &&
434}
bool foundNegativeCurvature
std::vector< VectorXd > s
VectorXd fixedReferenceMode
std::vector< VectorXd > positions
static const char OPT_LBFGS[]
static const char OPT_SD[]
std::vector< VectorXd > gradients
std::vector< VectorXd > y
std::vector< double > rho
void rotationRemove(const AtomMatrix r1, std::shared_ptr< Matter > m2)
constexpr double safe_div(double num, double denom, double fallback=0.0)
double safe_acos(double x)
constexpr double safe_recip(double x, double fallback=0.0)
double safe_atan_ratio(double num, double denom, double fallback=0.0)
std::optional< DimerRotationResult > runAlternativeRotation(DimerRotationBackend backend, const std::shared_ptr< Matter > &matter, const Parameters ¶ms, const std::shared_ptr< Potential > &pot, const AtomMatrix &initialDirection, quill::Logger *log=nullptr)
Run Lanczos, Davidson, or LOR.