Loading...
Searching...
No Matches
InstantonJob.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/InstantonJob.h"
13#include "eon/ConFileIO.h"
14#include "eon/EonLogger.h"
15#include "eon/Hessian.h"
16#include "eon/JobResult.h"
17#include "eon/Matter.h"
18#include "eon/PIQTST.h"
19#include "eon/PathIntegral.h"
20#include "eon/PotRegistry.h"
21#include "eon/Potential.h"
22#include "eon/Tunneling.h"
23
24#include <Eigen/QR>
25#include <Eigen/SVD>
26
27#include <algorithm>
28#include <array>
29#include <cmath>
30#include <filesystem>
31#include <fstream>
32#include <functional>
33#include <iomanip>
34#include <limits>
35#include <map>
36#include <memory>
37#include <sstream>
38#include <stdexcept>
39#include <string>
40#include <vector>
41
42namespace eonc {
43
44namespace {
45
47constexpr double kTimeUnitFs = 10.180505717871193;
48
52constexpr double kRotationZero = 1e-2;
53
56class MassWeighted {
57public:
58 explicit MassWeighted(const Matter &reference)
59 : ref_(reference) {
60 for (long i = 0; i < reference.numberOfAtoms(); ++i) {
61 if (reference.getFixed(i)) {
62 continue;
63 }
64 const double m = reference.getMass(i);
65 if (!(m > 0.0)) {
66 throw std::invalid_argument("instanton: a free atom without a mass");
67 }
68 free_.push_back(i);
69 sqrtMass_.push_back(std::sqrt(m));
70 }
71 if (free_.empty()) {
72 throw std::invalid_argument("instanton: every atom is fixed");
73 }
74 }
75 long dimension() const { return 3 * static_cast<long>(free_.size()); }
76 VectorXi freeAtoms() const {
77 VectorXi out(static_cast<long>(free_.size()));
78 for (size_t k = 0; k < free_.size(); ++k) {
79 out(static_cast<long>(k)) = static_cast<int>(free_[k]);
80 }
81 return out;
82 }
86 std::vector<double> spreadAbout(const std::vector<VectorXd> &beads,
87 VectorXd &centroid, long atoms) const {
88 centroid = VectorXd::Zero(dimension());
89 for (const auto &q : beads) {
90 centroid += q;
91 }
92 centroid /= static_cast<double>(std::max<size_t>(1, beads.size()));
93 std::vector<double> out(static_cast<size_t>(3 * atoms), 0.0);
94 for (size_t k = 0; k < free_.size(); ++k) {
95 for (int c = 0; c < 3; ++c) {
96 const long i = static_cast<long>(3 * k) + c;
97 double ss = 0.0;
98 for (const auto &q : beads) {
99 const double d = q(i) - centroid(i);
100 ss += d * d;
101 }
102 out[static_cast<size_t>(3 * free_[k] + c)] =
103 std::sqrt(ss /
104 static_cast<double>(std::max<size_t>(1, beads.size()))) /
105 sqrtMass_[k];
106 }
107 }
108 return out;
109 }
110 VectorXd toQ(const Matter &m) const {
111 const AtomMatrix d = ref_.pbc(m.getPositions() - ref_.getPositions());
112 VectorXd q(dimension());
113 for (size_t k = 0; k < free_.size(); ++k) {
114 for (int c = 0; c < 3; ++c) {
115 q(static_cast<long>(3 * k) + c) = sqrtMass_[k] * d(free_[k], c);
116 }
117 }
118 return q;
119 }
120 void place(const VectorXd &q, Matter &m) const {
121 AtomMatrix r = ref_.getPositions();
122 for (size_t k = 0; k < free_.size(); ++k) {
123 for (int c = 0; c < 3; ++c) {
124 r(free_[k], c) += q(static_cast<long>(3 * k) + c) / sqrtMass_[k];
125 }
126 }
127 m.setPositions(r);
128 }
131 MatrixXd rigidGenerators(const Matter &m) const {
132 const long n = dimension();
133 MatrixXd b = MatrixXd::Zero(n, 6);
134 const AtomMatrix r = m.getPositions();
135 Eigen::RowVector3d com = Eigen::RowVector3d::Zero();
136 double total = 0.0;
137 for (size_t k = 0; k < free_.size(); ++k) {
138 const double w = sqrtMass_[k] * sqrtMass_[k];
139 com += w * r.row(free_[k]);
140 total += w;
141 }
142 com /= total;
143 for (size_t k = 0; k < free_.size(); ++k) {
144 const long i = static_cast<long>(3 * k);
145 const Eigen::Vector3d x = (r.row(free_[k]) - com).transpose();
146 for (int c = 0; c < 3; ++c) {
147 b(i + c, c) = sqrtMass_[k];
148 Eigen::Vector3d e = Eigen::Vector3d::Zero();
149 e(c) = 1.0;
150 b.block(i, 3 + c, 3, 1) = sqrtMass_[k] * e.cross(x);
151 }
152 }
153 return b;
154 }
158 void markRotationZeroModes(const MatrixXd &hess, const MatrixXd &generators,
159 std::array<bool, 3> &keep,
160 std::array<double, 3> &residual) const {
161 const MatrixXd h = 0.5 * (hess + hess.transpose());
162 const double hn = h.norm();
163 for (int c = 0; c < 3; ++c) {
164 const VectorXd r = generators.col(3 + c);
165 const double rn = r.norm();
166 if (!(rn > 0.0)) {
167 keep[static_cast<size_t>(c)] = false;
168 residual[static_cast<size_t>(c)] = 0.0;
169 continue;
170 }
171 const double rel = hn > 0.0 ? (h * r).norm() / (hn * rn) : 0.0;
172 residual[static_cast<size_t>(c)] = rel;
173 keep[static_cast<size_t>(c)] = rel <= kRotationZero;
174 }
175 }
180 MatrixXd rigidBasis(const Matter &m,
181 const std::array<bool, 3> &rotations) const {
182 if (static_cast<long>(free_.size()) != m.numberOfAtoms()) {
183 return {};
184 }
185 const MatrixXd g = rigidGenerators(m);
186 const long n = g.rows();
187 std::vector<int> cols{0, 1, 2};
188 for (int c = 0; c < 3; ++c) {
189 if (rotations[static_cast<size_t>(c)]) {
190 cols.push_back(3 + c);
191 }
192 }
193 MatrixXd b(n, static_cast<long>(cols.size()));
194 for (size_t k = 0; k < cols.size(); ++k) {
195 b.col(static_cast<long>(k)) = g.col(cols[k]);
196 }
197 const Eigen::ColPivHouseholderQR<MatrixXd> qr(b);
198 const long rank = qr.rank();
199 if (rank <= 0) {
200 return {};
201 }
202 return qr.householderQ() * MatrixXd::Identity(n, rank);
203 }
204 const std::vector<double> &sqrtMasses() const { return sqrtMass_; }
206 VectorXd referenceFree() const {
207 const AtomMatrix r = ref_.getPositions();
208 VectorXd out(dimension());
209 for (size_t k = 0; k < free_.size(); ++k) {
210 for (int c = 0; c < 3; ++c) {
211 out(static_cast<long>(3 * k) + c) = r(free_[k], c);
212 }
213 }
214 return out;
215 }
216 VectorXd gradient(const AtomMatrix &forces) const {
217 VectorXd g(dimension());
218 for (size_t k = 0; k < free_.size(); ++k) {
219 for (int c = 0; c < 3; ++c) {
220 g(static_cast<long>(3 * k) + c) = -forces(free_[k], c) / sqrtMass_[k];
221 }
222 }
223 return g;
224 }
225
226private:
227 const Matter &ref_;
228 std::vector<long> free_;
229 std::vector<double> sqrtMass_;
230};
231
236void alignRigid(const Matter &ref, Matter &m) {
237 const long n = ref.numberOfAtoms();
238 for (long i = 0; i < n; ++i) {
239 if (ref.getFixed(i)) {
240 return;
241 }
242 }
243 AtomMatrix d = ref.pbc(m.getPositions() - ref.getPositions());
244 VectorXd w(n);
245 for (long i = 0; i < n; ++i) {
246 w(i) = ref.getMass(i);
247 }
248 const double total = w.sum();
249 const Eigen::RowVector3d shift = (w.transpose() * d) / total;
250 d.rowwise() -= shift;
251 if (!ref.getPeriodic()) {
252 const Eigen::RowVector3d com = (w.transpose() * ref.getPositions()) / total;
253 AtomMatrix x = ref.getPositions();
254 x.rowwise() -= com;
255 const AtomMatrix y = x + d;
256 const Eigen::Matrix3d h = y.transpose() * w.asDiagonal() * x;
257 Eigen::JacobiSVD<Eigen::Matrix3d> svd(h, Eigen::ComputeFullU |
258 Eigen::ComputeFullV);
259 Eigen::Matrix3d fix = Eigen::Matrix3d::Identity();
260 fix(2, 2) = (svd.matrixV() * svd.matrixU().transpose()).determinant() < 0.0
261 ? -1.0
262 : 1.0;
263 const Eigen::Matrix3d rot = svd.matrixV() * fix * svd.matrixU().transpose();
264 d = (y * rot.transpose()) - x;
265 }
266 m.setPositions(ref.getPositions() + d);
267}
268
272void writeCentroid(const std::string &file, const std::vector<VectorXd> &beads,
273 const MassWeighted &mw, const Matter &reactant,
274 std::vector<io::ConMetadataValue> scalars) {
275 VectorXd centroid;
276 Matter frame(reactant);
278 meta.spreads = mw.spreadAbout(beads, centroid, reactant.numberOfAtoms());
279 mw.place(centroid, frame);
280 meta.frame_index = 0;
281 meta.write_con_forces = false;
282 double largest = 0.0;
283 for (const double s : meta.spreads) {
284 largest = std::max(largest, s);
285 }
286 scalars.push_back({"beads", static_cast<double>(beads.size())});
287 scalars.push_back({"spread_max", largest});
288 meta.scalars = std::move(scalars);
289 if (!io::io_ok(frame.matter2con(file, false, &meta))) {
290 throw std::runtime_error("instanton: cannot write " + file);
291 }
292}
293
295std::vector<VectorXd> doubledRing(const std::vector<VectorXd> &ring) {
296 const size_t n = ring.size();
297 std::vector<VectorXd> fine(2 * n);
298 for (size_t j = 0; j < n; ++j) {
299 fine[2 * j] = ring[j];
300 fine[2 * j + 1] = 0.5 * (ring[j] + ring[(j + 1) % n]);
301 }
302 return fine;
303}
304
305} // namespace
306
307namespace {
308
316void steepestDescentPath(const VectorXd &qSaddle, double vSaddle,
317 const MatrixXd &hSaddle,
318 const std::vector<double> &sqrtMass,
319 const tunneling::BatchPotential &evaluate,
320 std::vector<VectorXd> &pathQ,
321 std::vector<double> &pathV) {
322 constexpr double cartStep = 0.01;
323 constexpr double gradTol = 1e-3;
324 constexpr long maxSteps = 4000;
325 const long n = qSaddle.size();
326 const Eigen::SelfAdjointEigenSolver<MatrixXd> es(
327 0.5 * (hSaddle + hSaddle.transpose()));
328 const VectorXd mode = es.eigenvectors().col(0);
329 const double stiff = es.eigenvalues().cwiseAbs().maxCoeff();
330 // The largest Cartesian move of a mass-weighted displacement.
331 auto cartesian = [&](const VectorXd &dq) {
332 double big = 0.0;
333 for (long i = 0; i < n; ++i) {
334 const size_t a = static_cast<size_t>(i / 3);
335 const double m = a < sqrtMass.size() ? sqrtMass[a] : 1.0;
336 big = std::max(big, std::abs(dq(i)) / m);
337 }
338 return big;
339 };
340 auto capped = [&](VectorXd dq) {
341 const double big = cartesian(dq);
342 if (big > cartStep) {
343 dq *= cartStep / big;
344 }
345 return dq;
346 };
347 auto at = [&](const VectorXd &q, double &v, VectorXd &g) {
348 std::vector<VectorXd> one{q};
349 std::vector<double> vs;
350 std::vector<VectorXd> gs;
351 evaluate(one, vs, gs);
352 if (vs.empty() || gs.empty() || !std::isfinite(vs[0]) ||
353 !gs[0].array().isFinite().all()) {
354 return false;
355 }
356 v = vs[0];
357 g = gs[0];
358 return true;
359 };
360 std::vector<std::vector<VectorXd>> sideQ(2);
361 std::vector<std::vector<double>> sideV(2);
362 for (int side = 0; side < 2; ++side) {
363 const double big0 = cartesian(mode);
364 VectorXd q = qSaddle + (side == 0 ? -1.0 : 1.0) * (cartStep / big0) * mode;
365 double v = 0.0;
366 VectorXd g;
367 if (!at(q, v, g) || !(v < vSaddle)) {
368 continue;
369 }
370 sideQ[side].push_back(q);
371 sideV[side].push_back(v);
372 double alpha = stiff > 0.0 ? 1.0 / stiff : 1.0;
373 for (long k = 0; k < maxSteps && g.norm() > gradTol; ++k) {
374 bool lowered = false;
375 for (int halving = 0; halving < 30 && !lowered; ++halving) {
376 const VectorXd trial = q + capped(-alpha * g);
377 double vt = 0.0;
378 VectorXd gt;
379 if (at(trial, vt, gt) && vt < v) {
380 q = trial;
381 v = vt;
382 g = gt;
383 lowered = true;
384 alpha *= 1.2;
385 } else {
386 alpha *= 0.5;
387 }
388 }
389 if (!lowered) {
390 break;
391 }
392 sideQ[side].push_back(q);
393 sideV[side].push_back(v);
394 }
395 }
396 pathQ.assign(sideQ[0].rbegin(), sideQ[0].rend());
397 pathV.assign(sideV[0].rbegin(), sideV[0].rend());
398 pathQ.push_back(qSaddle);
399 pathV.push_back(vSaddle);
400 pathQ.insert(pathQ.end(), sideQ[1].begin(), sideQ[1].end());
401 pathV.insert(pathV.end(), sideV[1].begin(), sideV[1].end());
402 // The reactant sits at q = 0, and a path starts at the reactant end.
403 if (pathQ.back().norm() < pathQ.front().norm()) {
404 std::reverse(pathQ.begin(), pathQ.end());
405 std::reverse(pathV.begin(), pathV.end());
406 }
407}
408
412bool straddlesSaddle(const std::vector<VectorXd> &beads,
413 const VectorXd &qSaddle, const MatrixXd &hSaddle) {
414 const Eigen::SelfAdjointEigenSolver<MatrixXd> es(
415 0.5 * (hSaddle + hSaddle.transpose()));
416 const VectorXd mode = es.eigenvectors().col(0);
417 double lo = std::numeric_limits<double>::infinity();
418 double hi = -lo;
419 for (const auto &b : beads) {
420 const double s = (b - qSaddle).dot(mode);
421 lo = std::min(lo, s);
422 hi = std::max(hi, s);
423 }
424 return lo < 0.0 && hi > 0.0;
425}
426
429std::vector<std::string>
430runRate(const Parameters &params, const std::shared_ptr<Potential> &pot,
431 const Matter &reactant, const MassWeighted &mw,
432 const tunneling::BatchPotential &evaluate,
433 const std::function<MatrixXd(const VectorXd &)> &hessianAt,
434 const std::array<bool, 3> &rotationZero,
435 const std::array<double, 3> &rotationResidual) {
436 const auto &o = params.instanton_options();
437 const std::string resultsFile = "results.dat";
438 const std::string pathFile = "instanton.con";
439 std::vector<std::string> returnFiles{resultsFile};
440
441 Matter saddle(pot, params);
442 if (!io::io_ok(saddle.con2matter(o.saddle_filename))) {
443 throw std::runtime_error("instanton: cannot read " + o.saddle_filename);
444 }
445 if (saddle.numberOfAtoms() != reactant.numberOfAtoms()) {
446 throw std::runtime_error(
447 "instanton: the saddle and the reactant differ in atom count");
448 }
449 if (o.hessian_final != "recomputed") {
450 throw std::invalid_argument("instanton: hessian_final must be recomputed");
451 }
452 std::vector<double> temperatures = o.temperatures;
453 if (temperatures.empty()) {
454 temperatures.push_back(o.temperature);
455 }
456 for (const double t : temperatures) {
457 if (!(t > 0.0)) {
458 throw std::invalid_argument(
459 "instanton: mode rate needs [Instanton] temperature or "
460 "temperatures in K");
461 }
462 }
463 // Highest first, so each ring can start the colder one.
464 std::sort(temperatures.begin(), temperatures.end(), std::greater<>());
465 alignRigid(reactant, saddle);
466 const long n = mw.dimension();
467 const VectorXd qSaddle = mw.toQ(saddle);
468 const double vReactant = Matter(reactant).getPotentialEnergy();
469 const double vSaddle = saddle.getPotentialEnergy();
470 const MatrixXd hReactant = hessianAt(VectorXd::Zero(n));
471 const MatrixXd hSaddle = hessianAt(qSaddle);
472 const double tc = tunneling::crossoverTemperature(hSaddle);
473 const long rigidModes = mw.rigidBasis(reactant, rotationZero).cols();
474 if (temperatures.size() == 1) {
475 EONC_LOG_INFO("[Instanton] rate: {} beads at {:.4g} K, crossover {:.4g} K, "
476 "barrier {:.6f} eV, {} degrees of freedom, {} rigid modes",
477 o.beads, temperatures.front(), tc, vSaddle - vReactant, n,
478 rigidModes);
479 } else {
480 EONC_LOG_INFO("[Instanton] rate: {} beads at {} temperatures from {:.4g} K "
481 "down to {:.4g} K, crossover {:.4g} K, barrier {:.6f} eV, "
482 "{} degrees of freedom, {} rigid modes",
483 o.beads, temperatures.size(), temperatures.front(),
484 temperatures.back(), tc, vSaddle - vReactant, n, rigidModes);
485 }
486 EONC_LOG_INFO("[Instanton] rotation residuals {:.3g}, {:.3g}, {:.3g}",
487 rotationResidual[0], rotationResidual[1], rotationResidual[2]);
488
489 std::vector<std::pair<std::string, double>> extras{
490 {"instanton_crossover_K", tc},
491 {"barrier_classical", vSaddle - vReactant}};
492 auto write = [&](RunStatus status) {
494 status, params.potential_options().potential,
495 PotRegistry::get().total_force_calls(), false, 0.0);
496 env.job_type = "instanton";
497 env.extras.emplace_back("force_calls",
498 static_cast<double>(env.force_calls));
499 for (const auto &kv : extras) {
500 env.extras.push_back(kv);
501 }
502 env.writeResultsDat(resultsFile);
503 };
504
505 // A band over the barrier seeds the ring and carries the
506 // one-dimensional WKB rate. An empty path leaves the saddle-mode seed.
507 std::vector<VectorXd> pathQ;
508 std::vector<double> pathV;
509 std::unique_ptr<tunneling::Profile> profile;
510 double hwPath = 0.0;
511 if (!o.initial_path.empty()) {
512 const auto frames = readcon::read_all_frames(o.initial_path);
513 bool haveEnergies = true;
514 for (const auto &frame : frames) {
515 Matter image(reactant);
516 if (!io::io_ok(io::con2matter(image, frame))) {
517 throw std::runtime_error("instanton: cannot read " + o.initial_path);
518 }
519 if (image.numberOfAtoms() != reactant.numberOfAtoms()) {
520 throw std::runtime_error("instanton: " + o.initial_path +
521 " differs in atom count");
522 }
523 alignRigid(reactant, image);
524 pathQ.push_back(mw.toQ(image));
525 const auto energy = frame.energy_opt();
526 haveEnergies = haveEnergies && energy.has_value();
527 pathV.push_back(energy.value_or(0.0));
528 }
529 if (pathQ.size() < 3) {
530 throw std::runtime_error("instanton: " + o.initial_path +
531 " holds fewer than three frames");
532 }
533 if (!haveEnergies) {
534 std::vector<VectorXd> grads;
535 evaluate(pathQ, pathV, grads);
536 }
537 std::vector<double> arc(pathQ.size(), 0.0);
538 for (size_t k = 1; k < pathQ.size(); ++k) {
539 arc[k] = arc[k - 1] + (pathQ[k] - pathQ[k - 1]).norm();
540 }
541 try {
542 profile = std::make_unique<tunneling::Profile>(std::move(arc), pathV);
543 } catch (const std::invalid_argument &ex) {
544 EONC_LOG_WARNING("[Instanton] {}; seeding from the saddle mode",
545 ex.what());
546 }
547 if (profile) {
548 try {
549 hwPath = tunneling::hbarOmega(tunneling::wellCurvature(*profile, true));
550 } catch (const std::exception &ex) {
551 EONC_LOG_WARNING("[Instanton] no reactant frequency along the path: {}",
552 ex.what());
553 }
554 }
555 }
556 // No band: the steepest-descent path out of the saddle carries the ring
557 // seed by the period condition. Cooling a cosine from the crossover finds
558 // the ring only where it grows continuously out of the saddle; where it
559 // does not, the search walks to a neighbouring saddle.
560 if (!profile) {
561 pathQ.clear();
562 pathV.clear();
563 const long before = PotRegistry::get().total_force_calls();
564 steepestDescentPath(qSaddle, vSaddle, hSaddle, mw.sqrtMasses(), evaluate,
565 pathQ, pathV);
566 std::vector<double> arc(pathQ.size(), 0.0);
567 for (size_t k = 1; k < pathQ.size(); ++k) {
568 arc[k] = arc[k - 1] + (pathQ[k] - pathQ[k - 1]).norm();
569 }
570 try {
571 profile = std::make_unique<tunneling::Profile>(std::move(arc), pathV);
572 hwPath = tunneling::hbarOmega(tunneling::wellCurvature(*profile, true));
573 EONC_LOG_INFO("[Instanton] steepest-descent path of {} points, {} "
574 "force calls",
575 pathQ.size(),
576 PotRegistry::get().total_force_calls() - before);
577 } catch (const std::exception &ex) {
578 EONC_LOG_WARNING("[Instanton] steepest-descent path unusable: {}; "
579 "seeding from the saddle mode",
580 ex.what());
581 if (!profile) {
582 pathQ.clear();
583 pathV.clear();
584 }
585 }
586 }
587
588 const std::string tableFile = "rate_instanton.dat";
589 std::ofstream table(tableFile);
590 if (!table) {
591 throw std::runtime_error("instanton: cannot write " + tableFile);
592 }
593 table << "# T_K T_c_K beads converged iterations U_N_eV negative_modes "
594 "ln_k_per_s k_per_s ln_k_htst_per_s barrier_effective_eV "
595 "ln_k_wkb_path_per_s ln_k_parabolic_per_s parabolic_factor\n";
596 table << std::setprecision(10);
597 returnFiles.push_back(tableFile);
598 const double logSecond = std::log(tunneling::kTimeUnitSeconds);
599 const double nan = std::numeric_limits<double>::quiet_NaN();
600 auto wkbAt = [&](double beta) {
601 if (!(profile && hwPath > 0.0)) {
602 return nan;
603 }
604 try {
605 return tunneling::wkbLogRateAlongPath(*profile, beta, hwPath) - logSecond;
606 } catch (const std::exception &ex) {
607 EONC_LOG_WARNING("[Instanton] no WKB rate along the path: {}", ex.what());
608 return nan;
609 }
610 };
611
612 std::vector<VectorXd> ring;
613 RunStatus status = RunStatus::GOOD;
614 bool rateFailed = false;
615 for (size_t ti = 0; ti < temperatures.size(); ++ti) {
616 const double temperature = temperatures[ti];
617 const double beta = 1.0 / (tunneling::kBoltzmann * temperature);
618 const bool last = ti + 1 == temperatures.size();
619 const double wkbLog = wkbAt(beta);
620 if (!(temperature < tc)) {
621 // The ring collapses onto the saddle. Above T_c the rate is the
622 // parabolic factor times quantum harmonic TST, the N -> infinity ring
623 // at the saddle, so it joins the instanton rate at T_c, where the
624 // factor diverges.
625 bool wrote = false;
626 if (temperature > tc) {
627 try {
628 const double factor = tunneling::parabolicFactor(temperature, tc);
629 const double logHtst = tunneling::harmonicTstLogRate(
630 hReactant, hSaddle, beta, vSaddle - vReactant, rigidModes);
631 const double logQhtst = tunneling::quantumHarmonicTstLogRate(
632 hReactant, hSaddle, beta, vSaddle - vReactant, rigidModes);
633 const double logPar = logQhtst + std::log(factor);
634 const double kPar = std::exp(logPar) / tunneling::kTimeUnitSeconds;
635 const double kHtst = std::exp(logHtst) / tunneling::kTimeUnitSeconds;
636 EONC_LOG_INFO("[Instanton] {:.4g} K is above the crossover {:.4g} "
637 "K; parabolic factor {:.6g}, ln(k s) = {:.4f}",
638 temperature, tc, factor, logPar - logSecond);
639 if (factor > 10.0) {
641 "[Instanton] parabolic factor {:.6g} is large; this close "
642 "to the crossover a uniform theory is the finite rate",
643 factor);
644 }
645 table << temperature << ' ' << tc << ' ' << o.beads
646 << " 0 0 nan 0 nan nan " << (logHtst - logSecond) << " nan "
647 << wkbLog << ' ' << (logPar - logSecond) << ' ' << factor
648 << '\n';
649 if (last) {
650 extras.emplace_back("instanton_temperature_K", temperature);
651 extras.emplace_back("parabolic_factor", factor);
652 extras.emplace_back("rate_parabolic", kPar);
653 extras.emplace_back("rate_parabolic_log", logPar - logSecond);
654 extras.emplace_back("rate_htst", kHtst);
655 extras.emplace_back("rate_htst_log", logHtst - logSecond);
656 if (std::isfinite(wkbLog)) {
657 extras.emplace_back("rate_wkb_path_log", wkbLog);
658 }
659 }
660 wrote = true;
661 } catch (const std::exception &ex) {
662 EONC_LOG_ERROR("[Instanton] {}", ex.what());
663 }
664 }
665 if (!wrote) {
666 if (!(temperature > tc)) {
667 EONC_LOG_ERROR("[Instanton] {:.4g} K is at the crossover temperature "
668 "{:.4g} K; the parabolic factor diverges there",
669 temperature, tc);
670 }
671 table << temperature << ' ' << tc << ' ' << o.beads
672 << " 0 0 nan 0 nan nan nan nan " << wkbLog << " nan nan\n";
673 rateFailed = true;
675 if (last) {
676 extras.emplace_back("instanton_temperature_K", temperature);
677 if (std::isfinite(wkbLog)) {
678 extras.emplace_back("rate_wkb_path_log", wkbLog);
679 }
680 }
681 }
682 continue;
683 }
684
686 ro.beads = o.beads;
687 ro.maxIterations = o.max_iterations;
688 ro.forceTolerance = o.force_tolerance;
689 ro.halfRing = o.half_ring;
690 ro.initialHessians = o.initial_hessians;
691 ro.energyShift = o.energy_shift;
692 if (static_cast<long>(mw.sqrtMasses().size()) == reactant.numberOfAtoms()) {
693 ro.rigidSqrtMasses = mw.sqrtMasses();
694 ro.rigidReference = mw.referenceFree();
695 ro.rigidRotations = rotationZero;
696 }
697 std::vector<VectorXd> guess = ring;
698 if (guess.empty() && profile) {
699 try {
700 guess = tunneling::ringFromPath(pathQ, pathV, beta * tunneling::kHbar,
701 o.beads);
702 EONC_LOG_INFO("[Instanton] ring seeded from {} by the period condition",
703 o.initial_path.empty() ? "the steepest-descent path"
704 : o.initial_path);
705 } catch (const std::invalid_argument &ex) {
706 EONC_LOG_WARNING("[Instanton] {}; seeding from the saddle mode instead",
707 ex.what());
708 guess.clear();
709 }
710 }
711 long ladderIterations = 0;
712 if (guess.empty() && o.bead_ladder && o.beads >= 16) {
713 long coarse = o.beads / 4;
714 if (coarse % 2 != 0) {
715 ++coarse;
716 }
717 if (coarse < 4) {
718 coarse = 4;
719 }
720 std::vector<VectorXd> rung;
721 for (long nb = coarse; nb < o.beads; nb *= 2) {
723 step.beads = nb;
724 const tunneling::RateInstanton rungInst =
725 tunneling::optimizeRateInstanton(qSaddle, hSaddle, beta, rung,
726 evaluate, step);
727 ladderIterations += rungInst.iterations;
728 EONC_LOG_INFO("[Instanton] ladder rung {} beads: U_N {:.6f} eV after "
729 "{} iterations{}",
730 nb, rungInst.ringPotential, rungInst.iterations,
731 rungInst.converged ? "" : " (not converged)");
732 rung = rungInst.beads;
733 if (static_cast<long>(rung.size()) != nb) {
734 rung.clear();
735 break;
736 }
737 rung = doubledRing(rung);
738 if (static_cast<long>(rung.size()) > o.beads) {
739 rung.clear();
740 break;
741 }
742 }
743 if (static_cast<long>(rung.size()) == o.beads) {
744 guess = std::move(rung);
745 }
746 }
748 qSaddle, hSaddle, beta, guess, evaluate, ro);
749 inst.iterations += ladderIterations;
750 EONC_LOG_INFO("[Instanton] {:.4g} K: ring U_N {:.6f} eV after {} "
751 "iterations{}",
752 temperature, inst.ringPotential, inst.iterations,
753 inst.converged ? "" : " (not converged)");
754
755 if (inst.converged && !straddlesSaddle(inst.beads, qSaddle, hSaddle)) {
756 EONC_LOG_ERROR("[Instanton] {:.4g} K: the ring converged off the "
757 "saddle's dividing plane, onto another saddle; no rate",
758 temperature);
759 inst.converged = false;
760 }
761 bool rateOk = false;
762 if (inst.converged) {
763 // Bead Hessians on every stride-th bead of the ring, linear in
764 // between, wrapping from the last anchor back to bead 0.
765 const long stride = std::max<long>(1, o.hessian_stride);
766 const long nBeads = o.beads;
767 std::map<long, MatrixXd> anchors;
768 // Bead N - j mirrors bead j on an out-and-back ring and shares its
769 // Hessian.
770 auto anchor = [&](long j) -> const MatrixXd & {
771 const long n = static_cast<long>(inst.beads.size());
772 if (j > n / 2 && (inst.beads[static_cast<size_t>(j)] -
773 inst.beads[static_cast<size_t>(n - j)])
774 .norm() <= 1e-10) {
775 j = n - j;
776 }
777 auto it = anchors.find(j);
778 if (it == anchors.end()) {
779 it = anchors.emplace(j, hessianAt(inst.beads[static_cast<size_t>(j)]))
780 .first;
781 }
782 return it->second;
783 };
784 auto beadHessian = [&](long j, const VectorXd &) -> MatrixXd {
785 const long lo = (j / stride) * stride;
786 if (j == lo) {
787 return anchor(lo);
788 }
789 const long hi = lo + stride < nBeads ? lo + stride : 0;
790 const long span = (hi == 0 ? nBeads : hi) - lo;
791 const double t =
792 static_cast<double>(j - lo) / static_cast<double>(span);
793 return (1.0 - t) * anchor(lo) + t * anchor(hi);
794 };
795 try {
796 tunneling::instantonRate(inst, beadHessian, hReactant,
797 vReactant - o.energy_shift, hSaddle,
798 vSaddle - o.energy_shift, rigidModes);
799 rateOk = std::isfinite(inst.logRate) && inst.negativeModes == 1;
800 if (inst.negativeModes != 1) {
801 EONC_LOG_ERROR("[Instanton] the ring Hessian has {} negative modes, "
802 "not one: the ring is not a first-order saddle of U_N",
803 inst.negativeModes);
804 }
805 } catch (const std::runtime_error &ex) {
806 EONC_LOG_ERROR("[Instanton] {}", ex.what());
807 }
808 }
809 if (rateOk) {
810 EONC_LOG_INFO("[Instanton] {:.4g} K: ln(k s) = {:.4f}, harmonic TST "
811 "{:.4f}, effective barrier {:.4f} eV",
812 temperature, inst.logRate - logSecond,
813 inst.classicalLogRate - logSecond, inst.effectiveBarrier);
814 }
815 table << temperature << ' ' << tc << ' ' << o.beads << ' '
816 << (inst.converged ? 1 : 0) << ' ' << inst.iterations << ' '
817 << inst.ringPotential << ' ' << inst.negativeModes << ' ';
818 if (rateOk) {
819 table << inst.logRate - logSecond << ' ' << inst.rate << ' '
820 << inst.classicalLogRate - logSecond << ' '
821 << inst.effectiveBarrier;
822 } else {
823 table << "nan nan nan nan";
824 }
825 table << ' ' << wkbLog << " nan nan\n";
826
827 std::vector<std::string> files;
828 if (last) {
829 files.push_back(pathFile);
830 }
831 if (temperatures.size() > 1) {
832 std::ostringstream name;
833 name << std::defaultfloat << std::setprecision(6) << "instanton_"
834 << temperature << "K.con";
835 files.push_back(name.str());
836 }
837 if (!inst.beads.empty()) {
838 Matter frame(reactant);
839 for (const auto &file : files) {
840 for (size_t j = 0; j < inst.beads.size(); ++j) {
841 mw.place(inst.beads[j], frame);
843 meta.frame_index = static_cast<uint64_t>(j);
844 meta.energy = inst.energies[j];
845 meta.write_con_forces = false;
846 meta.scalars = {
847 {"imaginary_time_fs", static_cast<double>(j) * inst.betaN *
848 tunneling::kHbar * kTimeUnitFs}};
849 if (j == 0) {
850 meta.scalars.push_back({"instanton_temperature_K", temperature});
851 meta.scalars.push_back({"instanton_crossover_K", tc});
852 meta.scalars.push_back(
853 {"instanton_converged", inst.converged ? 1.0 : 0.0});
854 if (rateOk) {
855 meta.scalars.push_back(
856 {"rate_instanton_log", inst.logRate - logSecond});
857 meta.scalars.push_back(
858 {"barrier_effective_instanton", inst.effectiveBarrier});
859 }
860 }
861 if (!io::io_ok(frame.matter2con(file, j > 0, &meta))) {
862 throw std::runtime_error("instanton: cannot write " + file);
863 }
864 }
865 returnFiles.push_back(file);
866 }
867 }
868 {
869 const std::string centroidFile =
870 last ? "instanton_centroid.con"
871 : "instanton_centroid_" +
872 files.back().substr(std::string("instanton_").size());
873 writeCentroid(centroidFile, inst.beads, mw, reactant,
874 {{"instanton_temperature_K", temperature},
875 {"instanton_crossover_K", tc},
876 {"instanton_converged", inst.converged ? 1.0 : 0.0}});
877 returnFiles.push_back(centroidFile);
878 }
879
880 ring = inst.beads;
881 if (!last) {
882 continue;
883 }
884 extras.emplace_back("instanton_temperature_K", temperature);
885 extras.emplace_back("instanton_iterations",
886 static_cast<double>(inst.iterations));
887 extras.emplace_back("instanton_ring_potential", inst.ringPotential);
888 extras.emplace_back("instanton_bN", inst.bN);
889 if (std::isfinite(wkbLog)) {
890 extras.emplace_back("rate_wkb_path_log", wkbLog);
891 }
892 if (rateOk) {
893 extras.emplace_back("rate_instanton", inst.rate);
894 extras.emplace_back("rate_instanton_log", inst.logRate - logSecond);
895 extras.emplace_back("rate_htst", inst.classicalRate);
896 extras.emplace_back("rate_htst_log", inst.classicalLogRate - logSecond);
897 extras.emplace_back("barrier_effective_instanton", inst.effectiveBarrier);
898 extras.emplace_back("instanton_negative_modes",
899 static_cast<double>(inst.negativeModes));
900 extras.emplace_back("instanton_zero_mode", inst.zeroEigenvalue);
901 } else {
902 rateFailed = true;
903 status = inst.converged ? RunStatus::FAIL_POTENTIAL_FAILED
905 }
906 }
907 if (!rateFailed) {
908 status = RunStatus::GOOD;
909 }
910 if (o.pi_planes > 0) {
911 const auto planeFiles = piqtst::runAfterInstanton(
912 params, *pot, reactant, saddle, hSaddle, pathQ, temperatures, extras);
913 returnFiles.insert(returnFiles.end(), planeFiles.begin(), planeFiles.end());
914 }
915 write(status);
916 return returnFiles;
917}
918
919} // namespace
920
921std::vector<std::string> InstantonJob::run(void) {
922 const auto &o = params.instanton_options();
923 pathintegral::requireTrotterSprings(o.springs, "instanton");
924 std::vector<std::string> returnFiles;
925 const std::string resultsFile = "results.dat";
926 const std::string pathFile = "instanton.con";
927 returnFiles.push_back(resultsFile);
928
929 auto reactant = std::make_unique<Matter>(pot, params);
930 if (!io::io_ok(reactant->con2matter(o.reactant_filename))) {
931 throw std::runtime_error("instanton: cannot read " + o.reactant_filename);
932 }
933 const MassWeighted mw(*reactant);
934 const long n = mw.dimension();
935 const VectorXd qStart = VectorXd::Zero(n);
936
937 // Bead evaluations: one batch per call, through forceBatch when the
938 // potential spreads a batch over calculators.
939 std::vector<std::unique_ptr<Matter>> pool;
940 auto evaluate = [&](const std::vector<VectorXd> &q, std::vector<double> &v,
941 std::vector<VectorXd> &grad) {
942 while (pool.size() < q.size()) {
943 pool.push_back(std::make_unique<Matter>(*reactant));
944 }
945 for (size_t j = 0; j < q.size(); ++j) {
946 mw.place(q[j], *pool[j]);
947 }
948 v.resize(q.size());
949 grad.resize(q.size());
950 if (pot->supportsBatchEvaluation() && q.size() > 1) {
951 const long atoms = reactant->numberOfAtoms();
952 std::vector<VectorXi> nrs(q.size());
953 std::vector<Matrix3d> boxes(q.size());
954 std::vector<const double *> posPtr, boxPtr;
955 std::vector<const int *> nrsPtr;
956 std::vector<double *> frcPtr;
957 for (size_t j = 0; j < q.size(); ++j) {
958 nrs[j] = pool[j]->getAtomicNrs();
959 boxes[j] = pool[j]->getPeriodic() ? pool[j]->getCell()
960 : Matrix3d::Zero().eval();
961 }
962 for (size_t j = 0; j < q.size(); ++j) {
963 posPtr.push_back(pool[j]->getPositions().data());
964 nrsPtr.push_back(nrs[j].data());
965 frcPtr.push_back(pool[j]->forcesData());
966 boxPtr.push_back(boxes[j].data());
967 }
968 std::vector<double> energies(q.size()), variances(q.size());
969 pot->forceBatch(static_cast<long>(q.size()), atoms, posPtr.data(),
970 nrsPtr.data(), frcPtr.data(), energies.data(),
971 variances.data(), boxPtr.data());
972 for (size_t j = 0; j < q.size(); ++j) {
973 pool[j]->setComputedPotential(energies[j], variances[j]);
974 }
975 }
976 for (size_t j = 0; j < q.size(); ++j) {
977 v[j] = pool[j]->getPotentialEnergy();
978 grad[j] = mw.gradient(pool[j]->getForces());
979 }
980 };
981
982 // The first call is the reactant. Its Hessian decides which rotations
983 // are zero modes; later beads reuse that decision.
984 std::array<bool, 3> rotationZero{{false, false, false}};
985 std::array<double, 3> rotationResidual{{0.0, 0.0, 0.0}};
986 bool rotationsKnown = false;
987 auto hessianAt = [&](const VectorXd &q) {
988 Matter m(*reactant);
989 mw.place(q, m);
990 Hessian h(params, &m);
991 h.writeHessianFile(false);
992 MatrixXd out = h.getHessian(&m, mw.freeAtoms());
993 if (out.rows() != n) {
994 throw std::runtime_error("instanton: a bead Hessian failed");
995 }
996 if (!rotationsKnown) {
997 mw.markRotationZeroModes(out, mw.rigidGenerators(m), rotationZero,
998 rotationResidual);
999 rotationsKnown = true;
1000 }
1001 // A finite-difference Hessian of a free structure has small nonzero
1002 // rigid eigenvalues of either sign; project them to zero.
1003 const MatrixXd rigid = mw.rigidBasis(m, rotationZero);
1004 if (rigid.cols() > 0) {
1005 const MatrixXd p = MatrixXd::Identity(n, n) - rigid * rigid.transpose();
1006 out = p * out * p;
1007 }
1008 return out;
1009 };
1010
1011 if (o.mode == "rate") {
1012 return runRate(params, pot, *reactant, mw, evaluate, hessianAt,
1013 rotationZero, rotationResidual);
1014 }
1015
1016 auto product = std::make_unique<Matter>(pot, params);
1017 if (!io::io_ok(product->con2matter(o.product_filename))) {
1018 throw std::runtime_error("instanton: cannot read " + o.product_filename);
1019 }
1020 if (reactant->numberOfAtoms() != product->numberOfAtoms()) {
1021 throw std::runtime_error("instanton: the minima differ in atom count");
1022 }
1023 alignRigid(*reactant, *product);
1024 const VectorXd qEnd = mw.toQ(*product);
1025
1026 // Starting path: a band from file, else the straight line.
1027 std::vector<VectorXd> guess;
1028 if (!o.initial_path.empty()) {
1029 const auto frames = readcon::read_all_frames(o.initial_path);
1030 for (const auto &frame : frames) {
1031 Matter m(*reactant);
1032 if (!io::io_ok(io::con2matter(m, frame))) {
1033 throw std::runtime_error("instanton: cannot read " + o.initial_path);
1034 }
1035 alignRigid(*reactant, m);
1036 guess.push_back(mw.toQ(m));
1037 }
1038 if (guess.size() >= 2) {
1039 guess.front() = qStart;
1040 guess.back() = qEnd;
1041 }
1042 }
1043
1044 const MatrixXd hStart = hessianAt(qStart);
1045 const MatrixXd hEnd = hessianAt(qEnd);
1046 const double omega = tunneling::pathOmega(hStart, hEnd, qStart, qEnd);
1047 const double betaHbar = o.beta_hbar_omega / omega;
1049 opt.beads = o.beads;
1050 opt.betaHbarOmega = o.beta_hbar_omega;
1051 opt.maxIterations = o.max_iterations;
1052 opt.forceTolerance = o.force_tolerance;
1053 EONC_LOG_INFO("[Instanton] {} beads over beta hbar = {:.4f} fs, {} degrees "
1054 "of freedom",
1055 o.beads, betaHbar * kTimeUnitFs, n);
1056
1058 qStart, qEnd, betaHbar, guess, evaluate, opt);
1059 EONC_LOG_INFO("[Instanton] action {:.6f} after {} iterations{}", inst.action,
1060 inst.iterations, inst.converged ? "" : " (not converged)");
1061
1062 bool splitOk = false;
1063 std::string failure;
1064 // beta |delta|: the propagator ratio reads delta0 only when the wells
1065 // lie within a small fraction of kB T of each other.
1066 const double betaAsymmetry =
1067 std::abs(inst.asymmetry) * betaHbar / tunneling::kHbar;
1068 if (inst.converged && !inst.symmetricEnough) {
1069 EONC_LOG_WARNING("[Instanton] beta |delta| = {:.3g}: the wells differ by "
1070 "{:.4g} eV, too far for the splitting; the path and "
1071 "action are written, the splitting is not",
1072 betaAsymmetry, inst.asymmetry);
1073 }
1074 if (inst.converged && inst.symmetricEnough) {
1075 const long stride = std::max<long>(1, o.hessian_stride);
1076 const long P = o.beads;
1077 std::map<long, MatrixXd> anchors;
1078 auto anchor = [&](long j) -> const MatrixXd & {
1079 auto it = anchors.find(j);
1080 if (it == anchors.end()) {
1081 it = anchors.emplace(j, hessianAt(inst.path[static_cast<size_t>(j)]))
1082 .first;
1083 }
1084 return it->second;
1085 };
1086 auto beadHessian = [&](long j, const VectorXd &) -> MatrixXd {
1087 if (stride == 1) {
1088 return anchor(j);
1089 }
1090 const long lo = 1 + ((j - 1) / stride) * stride;
1091 const long hi = std::min(lo + stride, P - 1);
1092 if (j == lo || hi == lo) {
1093 return anchor(lo);
1094 }
1095 const double t =
1096 static_cast<double>(j - lo) / static_cast<double>(hi - lo);
1097 return (1.0 - t) * anchor(lo) + t * anchor(hi);
1098 };
1099 try {
1100 tunneling::instantonSplitting(inst, beadHessian, hStart, hEnd);
1101 splitOk = std::isfinite(inst.delta0);
1102 } catch (const std::runtime_error &ex) {
1103 failure = ex.what();
1104 EONC_LOG_ERROR("[Instanton] {}", failure);
1105 }
1106 }
1107
1108 // The path, one frame per bead; the splitting on the first frame.
1109 const double kelvin = tunneling::kHbar / (tunneling::kBoltzmann * betaHbar);
1110 Matter frame(*reactant);
1111 for (size_t j = 0; j < inst.path.size(); ++j) {
1112 mw.place(inst.path[j], frame);
1114 meta.frame_index = static_cast<uint64_t>(j);
1115 meta.energy = inst.energies[j];
1116 meta.write_con_forces = false;
1117 meta.scalars = {{"imaginary_time_fs",
1118 static_cast<double>(j) * inst.dtau * kTimeUnitFs}};
1119 if (j == 0) {
1120 meta.scalars.push_back({"instanton_action", inst.action});
1121 meta.scalars.push_back(
1122 {"instanton_beta_hbar_fs", betaHbar * kTimeUnitFs});
1123 meta.scalars.push_back({"instanton_temperature_K", kelvin});
1124 meta.scalars.push_back(
1125 {"instanton_converged", inst.converged ? 1.0 : 0.0});
1126 meta.scalars.push_back({"tunnel_asymmetry", inst.asymmetry});
1127 if (splitOk) {
1128 meta.scalars.push_back({"tunnel_splitting_instanton", inst.delta0});
1129 meta.scalars.push_back(
1130 {"tls_energy_instanton", std::hypot(inst.asymmetry, inst.delta0)});
1131 meta.scalars.push_back({"instanton_zero_mode", inst.zeroMode});
1132 meta.scalars.push_back(
1133 {"instanton_mode_separation", inst.modeSeparation});
1134 meta.scalars.push_back(
1135 {"instanton_symmetric", inst.symmetricEnough ? 1.0 : 0.0});
1136 }
1137 }
1138 if (!io::io_ok(frame.matter2con(pathFile, j > 0, &meta))) {
1139 throw std::runtime_error("instanton: cannot write " + pathFile);
1140 }
1141 }
1142 returnFiles.push_back(pathFile);
1143 writeCentroid("instanton_centroid.con", inst.path, mw, *reactant,
1144 {{"instanton_temperature_K", kelvin},
1145 {"instanton_converged", inst.converged ? 1.0 : 0.0}});
1146 returnFiles.push_back("instanton_centroid.con");
1147
1148 // A converged path between wells too far apart is a result, not a
1149 // failure: the flags say why no splitting was written.
1150 const bool good = splitOk || (inst.converged && !inst.symmetricEnough);
1151 const auto status = good ? RunStatus::GOOD
1152 : (inst.converged ? RunStatus::FAIL_POTENTIAL_FAILED
1155 status, params.potential_options().potential,
1156 PotRegistry::get().total_force_calls(), false, 0.0);
1157 env.job_type = "instanton";
1158 env.extras.emplace_back("force_calls", static_cast<double>(env.force_calls));
1159 env.extras.emplace_back("instanton_iterations",
1160 static_cast<double>(inst.iterations));
1161 env.extras.emplace_back("instanton_action", inst.action);
1162 env.extras.emplace_back("instanton_temperature_K", kelvin);
1163 env.extras.emplace_back("tunnel_asymmetry", inst.asymmetry);
1164 env.extras.emplace_back("instanton_beta_asymmetry", betaAsymmetry);
1165 env.extras.emplace_back("instanton_symmetric",
1166 inst.symmetricEnough ? 1.0 : 0.0);
1167 if (splitOk) {
1168 env.extras.emplace_back("tunnel_splitting_instanton", inst.delta0);
1169 env.extras.emplace_back("instanton_mode_separation", inst.modeSeparation);
1170 }
1171 env.writeResultsDat(resultsFile);
1172 return returnFiles;
1173}
1174
1175} // namespace eonc
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
#define EONC_LOG_ERROR(...)
Definition EonLogger.h:261
#define EONC_LOG_WARNING(...)
Definition EonLogger.h:255
#define EONC_LOG_INFO(...)
Definition EonLogger.h:249
void writeHessianFile(bool on) noexcept
Whether a finished Hessian goes to hessian.dat (on by default).
Definition Hessian.h:61
MatrixXd getHessian(Matter *matterIn, const VectorXi &atomsIn)
Definition Hessian.cpp:88
std::vector< std::string > run(void) override
Virtual run; used solely for dynamic dispatch.
std::shared_ptr< Potential > pot
Definition Job.h:63
Parameters params
Definition Job.h:58
double getPotentialEnergy() const
Definition Matter.cpp:554
io::IoStatus matter2con(std::string filename, bool append=false, const io::ConFrameMetadata *metadata=nullptr)
Definition Matter.h:268
static PotRegistry & get() noexcept
Process-lifetime singleton.
size_t total_force_calls() const noexcept
IoStatus con2matter(Matter &m, std::string filename)
constexpr bool io_ok(IoStatus s) noexcept
Definition ConFileIO.h:38
void requireTrotterSprings(const std::string &springs, const char *use)
Economised springs and a normal-mode GLE are refused.
std::vector< std::string > runAfterInstanton(const Parameters &params, Potential &pot, const Matter &reactant, const Matter &saddle, const MatrixXd &hSaddle, const std::vector< VectorXd > &pathQ, const std::vector< double > &temperatures, std::vector< std::pair< std::string, double > > &extras)
[Instanton] mode rate with pi_planes > 0: the planes and rate at each temperature,...
double parabolicFactor(double temperature, double crossover)
(pi T_c / T) / sin(pi T_c / T).
void instantonSplitting(Instanton &inst, const BeadHessian &hessian, const MatrixXd &hessStart, const MatrixXd &hessEnd)
Fills delta0, zeroMode and modeSeparation from the bead Hessians and the Hessians of the two minima.
constexpr double kTimeUnitSeconds
One unit of time, sqrt(amu Angstrom^2 / eV), in seconds.
Definition Tunneling.h:224
void instantonRate(RateInstanton &inst, const RingBeadHessian &hessian, const MatrixXd &hessReactant, double vReactant, const MatrixXd &hessSaddle, double vSaddle, long rigidModes, long denseLimit)
Fills the rate from the bead Hessians, the reactant minimum's Hessian and energy, and optionally the ...
double pathOmega(const MatrixXd &hessStart, const MatrixXd &hessEnd, const VectorXd &start, const VectorXd &end)
omega along the straight line between the minima from the curvature of each well there,...
std::vector< VectorXd > ringFromPath(const std::vector< VectorXd > &path, const std::vector< double > &energies, double betaHbar, long beads)
Closed ring of beads samples of path whose imaginary-time period is betaHbar.
RateInstanton optimizeRateInstanton(const VectorXd &saddle, const MatrixXd &hessSaddle, double beta, std::vector< VectorXd > guess, const BatchPotential &potential, const RateInstantonOptions &options)
Finds the rate instanton at inverse temperature beta (1 / eV).
constexpr double kHbar
hbar in eV^0.5 amu^0.5 Angstrom, correctly rounded from the exact SI values (h = 6....
Definition Tunneling.h:37
double harmonicTstLogRate(const MatrixXd &hessReactant, const MatrixXd &hessSaddle, double beta, double barrier, long rigidModes)
ln(k) for classical harmonic transition-state theory, k in 1/time.
double wellCurvature(const Profile &p, bool leftEnd)
d2V/ds2 at one end of the path, in eV / (amu Angstrom^2), from a least squares fit of a s^2 + b s^3 t...
std::function< void(const std::vector< VectorXd > &q, std::vector< double > &v, std::vector< VectorXd > &grad)> BatchPotential
V (eV) and dV/dq (eV / (amu^0.5 Angstrom)) at every point of q, all in one call so a potential can sp...
Definition Tunneling.h:152
double quantumHarmonicTstLogRate(const MatrixXd &hessReactant, const MatrixXd &hessSaddle, double beta, double barrier, long rigidModes)
ln(k) for quantum harmonic transition-state theory, k in 1/time: (1 / (2 pi beta hbar)) prod_r 2 sinh...
double crossoverTemperature(const MatrixXd &hessSaddle)
T_c = hbar omega_b / (2 pi kB) from the mass-weighted Hessian at the saddle, in K; throws when the He...
double wkbLogRateAlongPath(const Profile &profile, double beta, double hwReactant)
ln(k), k in 1/time, for the one-dimensional thermal rate along profile.
double hbarOmega(double curvature)
hbar omega in eV for a mass-weighted curvature.
Instanton optimizeInstanton(const VectorXd &start, const VectorXd &end, double betaHbar, std::vector< VectorXd > guess, const BatchPotential &potential, const InstantonOptions &options)
Minimises the action from guess (P + 1 beads, ends at the minima, or empty for a tanh kink along the ...
constexpr double kBoltzmann
Boltzmann constant in eV / K, correctly rounded from the exact 1.380649e-23 J / K.
Definition Tunneling.h:41
RAII resource manager for the ARTn C library with global synchronization.
static JobResultEnvelope fromMinimization(RunStatus status, PotType pot, std::uint64_t fcalls, bool hasE, double energy)
Definition JobResult.h:101
std::optional< uint64_t > frame_index
Definition ConFileIO.h:72
std::vector< double > spreads
Per-atom root-mean-square spread of the position distribution about the written coordinates,...
Definition ConFileIO.h:90
std::vector< ConMetadataValue > scalars
Definition ConFileIO.h:79
std::optional< double > energy
Definition ConFileIO.h:73
std::optional< bool > write_con_forces
When set, this write includes or omits force sections regardless of Parameters.main_options()....
Definition ConFileIO.h:93
long maxIterations
L-BFGS iterations.
Definition Tunneling.h:144
double forceTolerance
largest per-bead |dS/dq| / dtau, eV / (amu^0.5 Angstrom)
Definition Tunneling.h:145
long beads
P: segments from one minimum to the other.
Definition Tunneling.h:141
double betaHbarOmega
beta hbar omega of the stiffer end along the path; sets the imaginary time
Definition Tunneling.h:142
double asymmetry
V(end) - V(start), eV.
Definition Tunneling.h:168
std::vector< double > energies
V at every bead, eV.
Definition Tunneling.h:161
double zeroMode
the eigenvalue det' leaves out
Definition Tunneling.h:166
double action
(S - S_well) / hbar
Definition Tunneling.h:164
bool symmetricEnough
beta |asymmetry| < 0.1: the propagator ratio reads delta0 only when the wells lie within a small frac...
Definition Tunneling.h:173
double modeSeparation
The second smallest eigenvalue of J over the zero mode's: small means the kink is not isolated in ima...
Definition Tunneling.h:176
double dtau
betaHbar / P
Definition Tunneling.h:163
double delta0
tunnelling splitting, eV
Definition Tunneling.h:167
std::vector< VectorXd > path
P + 1 beads, ends at the minima.
Definition Tunneling.h:160
long beads
N, beads on the ring.
Definition Tunneling.h:255