72 VectorXd initialDirection = VectorXd::Map(initialDirectionAtomMatrix.data(),
73 3 * matter->numberOfAtoms());
74 tau = initialDirection.array() * matter->getFreeV().array();
77 if (
tau.norm() > 1e-10) {
78 eonc::safemath::safe_normalize_inplace(
tau);
82 tau =
tau.array() * matter->getFreeV().array();
83 eonc::safemath::safe_normalize_inplace(
tau);
87 double bestNegativeCurvature = std::numeric_limits<double>::max();
88 VectorXd bestTau =
tau;
89 VectorXd bestX0Positions;
90 VectorXd bestG0, bestG1;
97 auto x1Pot =
x1->getPotential();
100 x1->setPotential(x1Pot);
102 VectorXd x0_r =
x0->getPositionsV();
103 bestX0Positions = x0_r;
105 double delta =
params.main_options().finiteDifference;
106 x1->setPositionsV(x0_r + delta *
tau);
116 if (
x1->getPotentialEnergy() -
x0->getPotentialEnergy() > 10.0 * delta) {
118 x1->setPositionsV(x0_r + delta *
tau);
120 log,
"[IDimer] Initial tangent flipped due to high energy wall.");
126 AtomMatrix::Map(
tau.data(), matter->numberOfAtoms(), 3),
127 static_cast<quill::Logger *
>(
log))) {
128 C_tau = alt->eigenvalue;
129 tau = VectorXd::Map(alt->eigenvector.data(), 3 * matter->numberOfAtoms());
132 tau =
tau.array() * matter->getFreeV().array();
133 eonc::safemath::safe_normalize_inplace(
tau);
134 x0_r = matter->getPositionsV();
135 x0->setPositionsV(x0_r);
136 x1->setPositionsV(x0_r + delta *
tau);
153 VectorXd x1_rp, x1_r, tau_prime, tau_Old, g1_prime;
156 double phi_prime = 0.0;
157 double phi_min = 0.0;
162 auto x1p = std::make_shared<Matter>(
x1->getPotential(),
params);
165 if (
params.dimer_options().remove_rotation) {
167 AtomMatrix::Map(x0_r.data(),
x0->numberOfAtoms(), 3),
x1);
168 x1_r =
x1->getPositionsV();
169 tau =
x1->pbcV(x1_r - x0_r);
170 eonc::safemath::safe_normalize_inplace(
tau);
171 x1_r = x0_r +
tau * delta;
172 x1->setPositionsV(x1_r);
184 (
pot->needsPerImageInstance() &&
185 x0->getPotential().get() !=
x1->getPotential().get());
186 if (
pot->supportsBatchEvaluation()) {
187 long n =
x0->numberOfAtoms();
188 bool x0dirty =
x0->needsForceUpdate();
189 bool x1dirty =
x1->needsForceUpdate();
191 if (x0dirty && x1dirty) {
192 auto nrs0 =
x0->getAtomicNrs();
193 auto nrs1 =
x1->getAtomicNrs();
194 auto box0 =
x0->getCell();
195 auto box1 =
x1->getCell();
196 const double *posVec[] = {
x0->getPositions().data(),
197 x1->getPositions().data()};
198 const int *nrsVec[] = {nrs0.data(), nrs1.data()};
199 double *frcVec[] = {
x0->forcesData(),
x1->forcesData()};
200 double energies[2], vars[2];
201 const double *boxVec[] = {box0.data(), box1.data()};
202 pot->forceBatch(2, n, posVec, nrsVec, frcVec, energies, vars, boxVec);
203 x0->setComputedPotential(energies[0], vars[0]);
204 x1->setComputedPotential(energies[1], vars[1]);
205 }
else if (x1dirty) {
206 auto nrs =
x1->getAtomicNrs();
207 auto box =
x1->getCell();
208 const double *posVec[] = {
x1->getPositions().data()};
209 const int *nrsVec[] = {nrs.data()};
210 double *frcVec[] = {
x1->forcesData()};
211 double energies[1], vars[1];
212 const double *boxVec[] = {box.data()};
213 pot->forceBatch(1, n, posVec, nrsVec, frcVec, energies, vars, boxVec);
214 x1->setComputedPotential(energies[0], vars[0]);
215 }
else if (x0dirty) {
218 g0 = -
x0->getForcesV();
219 g1 = -
x1->getForcesV();
220 }
else if (
params.main_options().parallel && canParallel) {
225 std::exception_ptr t0Error;
228 g0 = -
x0->getForcesV();
230 t0Error = std::current_exception();
234 g1 = -
x1->getForcesV();
241 std::rethrow_exception(t0Error);
243 g0 = -
x0->getForcesV();
244 g1 = -
x1->getForcesV();
260 F_R = -2.0 * (g1 - g0) + 2.0 * ((g1 - g0).dot(
tau)) *
tau;
265 theta = eonc::safemath::safe_normalized(
F_R);
285 eonc::safemath::safe_normalize_inplace(
theta);
290 VectorXd s0 =
tau - tau_Old;
300 double H0 = 1.0 / 60.0;
301 size_t loopmax =
s.size();
302 std::vector<double> alpha(loopmax);
305 for (
long i =
static_cast<long>(loopmax) - 1; i >= 0; i--) {
306 alpha[i] =
rho[i] *
s[i].dot(q);
307 q -= alpha[i] *
y[i];
310 for (
size_t i = 0; i < loopmax; i++) {
311 double bv =
rho[i] *
y[i].dot(z);
312 z +=
s[i] * (alpha[i] - bv);
315 double vd = std::clamp(-eonc::safemath::safe_normalized(z).dot(
316 eonc::safemath::safe_normalized(
F_R)),
328 theta = -eonc::safemath::safe_normalized(z);
330 eonc::safemath::safe_normalize_inplace(
theta);
341 if (
C_tau < bestNegativeCurvature) {
342 bestNegativeCurvature =
C_tau;
344 bestX0Positions =
x0->getPositionsV();
351 double d_C_tau_d_phi =
354 d_C_tau_d_phi, 2.0 * std::abs(
C_tau), 0.0);
357 double alignment = std::abs(
tau.dot(referenceMode));
359 if (std::abs(phi_prime) > phi_tol) {
360 double b1 = 0.5 * d_C_tau_d_phi;
363 x0_r =
x0->getPositionsV();
364 tau_prime =
tau * std::cos(phi_prime) +
theta * std::sin(phi_prime);
365 tau_prime = eonc::safemath::safe_normalized(tau_prime);
366 x1_rp = x0_r + tau_prime * delta;
369 x1p->setPositionsV(x1_rp);
370 g1_prime = -x1p->getForcesV();
380 C_tau - C_tau_prime + b1 * std::sin(2.0 * phi_prime),
381 1.0 - std::cos(2.0 * phi_prime), 0.0);
382 double a0 = 2.0 * (
C_tau - a1);
385 double C_tau_min = 0.5 * a0 + a1 * std::cos(2.0 * phi_min) +
386 b1 * std::sin(2.0 * phi_min);
389 if (C_tau_min >
C_tau) {
391 C_tau_min = 0.5 * a0 + a1 * std::cos(2.0 * phi_min) +
392 b1 * std::sin(2.0 * phi_min);
402 tau =
tau * std::cos(phi_min) +
theta * std::sin(phi_min);
403 tau = eonc::safemath::safe_normalized(
tau);
404 x1_r = x0_r +
tau * delta;
407 if (
params.dimer_options().remove_rotation) {
408 x1->setPositionsV(x1_r);
410 AtomMatrix::Map(x0_r.data(),
x0->numberOfAtoms(), 3),
x1);
411 x1_r =
x1->getPositionsV();
413 eonc::safemath::safe_normalize_inplace(
tau);
414 x1_r = x0_r +
tau * delta;
417 x1->setPositionsV(x1_r);
421 double sin_pp = std::sin(phi_prime);
425 g0 * (1.0 - std::cos(phi_min) -
426 std::sin(phi_min) * std::tan(phi_prime * 0.5));
432 "[IDimerRot] ----- --------- ---------- ------------------ "
433 "{:9.4f} {:7.3f} {:6.3f} {:4} {:5.3f}",
438 "[IDimerRot] ----- --------- ---------- ------------------ "
439 "{:9.4f} {:7.3f} ------ ---- {:5.3f}",
440 C_tau,
F_R.norm() / delta, alignment);
444 if (alignment <
params.neb_options().climbing_image.ocineb.angle_tol &&
445 params.neb_options().climbing_image.ocineb.use_mmf) {
447 log,
"Terminating dimer due to lost mode (align {:.3f}).", alignment);
450 if (bestNegativeCurvature < 0.0) {
452 C_tau = bestNegativeCurvature;
454 x0->setPositionsV(bestX0Positions);
455 x1->setPositionsV(bestX0Positions + delta * bestTau);
458 log,
"Restored best negative curvature state: C_tau={:.4f}",
C_tau);
462 log,
"Never found negative curvature. Final C_tau: {:.4f}",
C_tau);
467 }
while (std::abs(phi_prime) > std::abs(phi_tol) &&
468 std::abs(phi_min) > std::abs(phi_tol) &&