[Instanton] mode rate with pi_planes > 0: the planes and rate at each temperature, and with pi_recrossing_parents > 0 the transmission factor on the top plane and k_RPMD = kappa k_PI-QTST.
hSaddle and the band in pathQ are in the instanton's mass-weighted coordinates over the free atoms, measured from the reactant; saddle is aligned to the reactant. Appends results.dat keys for the last (lowest) temperature to extras and returns the files written.
171 {
175 const Embedding emb(reactant);
176 const VectorXd qSaddle = emb.toQ(reactant, saddle);
177 const double qNorm = qSaddle.norm();
178 if (!(qNorm > 0.0)) {
179 throw std::runtime_error("piqtst: the saddle sits on the reactant");
180 }
181 const VectorXd lineDir = qSaddle / qNorm;
182
183
184
185
186 VectorXd dir = lineDir;
187 std::string chosen = "line";
188 if (o.pi_direction == "mode") {
189 const Eigen::SelfAdjointEigenSolver<MatrixXd> es(
190 0.5 * (hSaddle + hSaddle.transpose()));
191 if (es.info() == Eigen::Success && es.eigenvalues()(0) < 0.0) {
192 VectorXd v = es.eigenvectors().col(0);
193 if (v.dot(lineDir) < 0.0) {
194 v = -v;
195 }
196 if (v.dot(lineDir) >= 0.5) {
197 dir = v;
198 chosen = "mode";
199 } else {
201 "rad with the reactant-saddle line; planes follow "
202 "the line",
203 std::acos(v.dot(lineDir)));
204 }
205 } else {
207 "negative eigenvalue; planes follow the line");
208 }
209 }
210 if (emb.freeAtoms.size() ==
static_cast<size_t>(reactant.
numberOfAtoms()) &&
213 "fixed in the reactant's frame and a rotation of the "
214 "cluster moves the centroid between them");
215 }
216 const double sStar = dir.dot(qSaddle);
217 const long nPlanes = o.pi_planes;
218 const double s0 = -o.pi_reactant_extent * sStar;
219 std::vector<double> sPlanes;
220 for (long j = 0; j < nPlanes; ++j) {
221 sPlanes.push_back(s0 + (sStar - s0) * static_cast<double>(j) /
222 static_cast<double>(nPlanes - 1));
223 }
224
227 c.masses.resize(static_cast<size_t>(c.atoms));
228 c.numbers.resize(static_cast<size_t>(c.atoms));
229 c.free.assign(static_cast<size_t>(3 * c.atoms), 0);
231 for (long i = 0; i < c.atoms; ++i) {
232 c.masses[
static_cast<size_t>(i)] = reactant.
getMass(i);
233 c.numbers[static_cast<size_t>(i)] = z(i);
234 }
235 for (const long i : emb.freeAtoms) {
236 for (int a = 0; a < 3; ++a) {
237 c.free[static_cast<size_t>(3 * i + a)] = 1;
238 }
239 }
241 c.direction = emb.full(dir);
244 c.box = cell.data();
245
246
247
248 std::vector<double> bandS;
249 for (const auto &q : pathQ) {
250 bandS.push_back(dir.dot(q));
251 }
252 auto seed = [&](double s) -> VectorXd {
253 for (size_t k = 0; k + 1 < pathQ.size(); ++k) {
254 const double lo = bandS[k];
255 const double hi = bandS[k + 1];
256 if ((lo <= s && s <= hi) || (hi <= s && s <= lo)) {
257 const double t = hi != lo ? (s - lo) / (hi - lo) : 0.0;
258 return emb.cartesian(c.reference,
259 (1.0 - t) * pathQ[k] + t * pathQ[k + 1]);
260 }
261 }
262 return emb.cartesian(c.reference, (s / sStar) * qSaddle);
263 };
264
265 const double vReactant = Matter(reactant).getPotentialEnergy();
266 const double vSaddle = Matter(saddle).getPotentialEnergy();
267 double effective = std::numeric_limits<double>::quiet_NaN();
268 for (const auto &kv : extras) {
269 if (kv.first == "barrier_effective_instanton") {
270 effective = kv.second;
271 }
272 }
273 EONC_LOG_INFO(
"[Instanton] piqtst: {} planes from s = {:.4f} to s* = {:.4f} "
274 "amu^0.5 A along the {}, {} beads, {} + {} steps of {:.4g} fs",
275 nPlanes, s0, sStar, chosen == "mode" ? "unstable mode" : "line",
276 o.pi_beads, o.pi_equilibration_steps, o.pi_sampling_steps,
277 o.pi_time_step);
278
279 const std::string tableFile = "rate_piqtst.dat";
280 std::ofstream table(tableFile);
281 if (!table) {
282 throw std::runtime_error("piqtst: cannot write " + tableFile);
283 }
284 const bool kappaOn = o.pi_recrossing_parents > 0;
285 table << "# T_K s_amu05A dF_ds_eV_per_amu05A dF_ds_error F_eV F_error_eV "
286 "spread_max_A";
287 if (kappaOn) {
288 table << " kappa kappa_error ln_k_rpmd_s ln_k_rpmd_s_error";
289 }
290 table << '\n';
291 table << std::setprecision(10);
292 std::vector<std::string> files{tableFile};
293
294 std::vector<double> sorted = temperatures;
295 std::sort(sorted.begin(), sorted.end(), std::greater<>());
297 for (size_t ti = 0; ti < sorted.size(); ++ti) {
298 const double t = sorted[ti];
299 const bool last = ti + 1 == sorted.size();
303 so.equilibration = o.pi_equilibration_steps;
304 so.production = o.pi_sampling_steps;
305 so.blocks = 10;
306 so.ring.beads = o.pi_beads;
307 so.ring.temperature = t;
310 so.ring.dt = o.pi_time_step / timeUnit;
311 so.ring.thermostat = o.pi_thermostat == "piglet"
314 so.ring.pileTau = o.pi_pile_tau / timeUnit;
315 so.ring.pileScale = o.pi_pile_scale;
316 so.ring.gleFile = o.pi_gle_file;
317 so.ring.seed = static_cast<std::uint64_t>(o.pi_seed);
318 so.seed = seed;
319 const std::vector<Plane> planes =
scan(pot, c, so);
321 const double lnPerSecond = r.logRate - logSecond;
322
324 double lnRpmd = std::numeric_limits<double>::quiet_NaN();
325 double lnRpmdError = std::numeric_limits<double>::quiet_NaN();
326 if (kappaOn) {
328 ro.
s = planes.back().s;
329 ro.equilibration = o.pi_equilibration_steps;
330 ro.parents = o.pi_recrossing_parents;
331 ro.spacing = o.pi_recrossing_spacing;
332 ro.children = o.pi_recrossing_children;
333 ro.steps = std::lround(o.pi_recrossing_time / o.pi_time_step);
334 ro.ring = so.ring;
335 ro.seed = seed;
337 if (kappa.plateau > 0.0) {
338 lnRpmd = lnPerSecond + std::log(kappa.plateau);
339 lnRpmdError =
340 std::hypot(r.logRateError, kappa.plateauError / kappa.plateau);
341 } else {
343 "factor {:.4g} +- {:.2g} is not positive; ln k_RPMD "
344 "is undefined (raise pi_recrossing_parents)",
345 t, kappa.plateau, kappa.plateauError);
346 }
347 EONC_LOG_INFO(
"[Instanton] piqtst {:.4g} K: transmission factor "
348 "kappa = {:.4f} +- {:.4f} from {} trajectories, "
349 "ln(k_RPMD s) = {:.4f} +- {:.4f}",
350 t, kappa.plateau, kappa.plateauError, kappa.trajectories,
351 lnRpmd, lnRpmdError);
352 std::vector<std::string> curveFiles;
353 if (last) {
354 curveFiles.push_back("kappa_piqtst.dat");
355 }
356 if (sorted.size() > 1) {
357 curveFiles.push_back("kappa_piqtst_" + kelvinTag(t) + ".dat");
358 }
359 for (const auto &file : curveFiles) {
360 std::ofstream curve(file);
361 if (!curve) {
362 throw std::runtime_error("piqtst: cannot write " + file);
363 }
364 curve << "# t_fs kappa\n" << std::setprecision(10);
365 for (size_t i = 0; i < kappa.time.size(); ++i) {
366 curve << kappa.time[i] * timeUnit << ' ' << kappa.kappa[i] << '\n';
367 }
368 files.push_back(file);
369 }
370 }
371
372 std::vector<std::string> conFiles;
373 if (last) {
374 conFiles.push_back("piqtst_planes.con");
375 }
376 if (sorted.size() > 1) {
377 conFiles.push_back("piqtst_planes_" + kelvinTag(t) + ".con");
378 }
379 for (size_t j = 0; j < planes.size(); ++j) {
380 const Plane &p = planes[j];
381 double largest = 0.0;
382 for (const double sp : p.spread) {
383 largest = std::max(largest, sp);
384 }
385 table << t << ' ' << p.s << ' ' << p.meanForce << ' ' << p.meanForceError
386 << ' ' << p.freeEnergy << ' ' << p.freeEnergyError << ' '
387 << largest;
388 if (kappaOn) {
389 table << ' ' << kappa.plateau << ' ' << kappa.plateauError << ' '
390 << lnRpmd << ' ' << lnRpmdError;
391 }
392 table << '\n';
393 Matter frame(reactant);
395 for (long i = 0; i < c.atoms; ++i) {
396 for (int a = 0; a < 3; ++a) {
397 pos(i, a) = p.centroid(3 * i + a);
398 }
399 }
400 frame.setPositions(pos);
401 io::ConFrameMetadata meta;
402 meta.frame_index = static_cast<uint64_t>(j);
403 meta.write_con_forces = false;
404 meta.spreads = p.spread;
405 meta.scalars = {{"piqtst_temperature_K", t},
406 {"piqtst_s", p.s},
407 {"piqtst_dF_ds", p.meanForce},
408 {"piqtst_dF_ds_error", p.meanForceError},
409 {"piqtst_F", p.freeEnergy},
410 {"piqtst_F_error", p.freeEnergyError},
411 {"beads", static_cast<double>(o.pi_beads)},
412 {"spread_max", largest}};
413 for (const auto &file : conFiles) {
414 if (!
io::io_ok(frame.matter2con(file, j > 0, &meta))) {
415 throw std::runtime_error("piqtst: cannot write " + file);
416 }
417 }
418 }
419 for (const auto &file : conFiles) {
420 files.push_back(file);
421 }
422
423 const Plane &top = planes.back();
424 EONC_LOG_INFO(
"[Instanton] piqtst {:.4g} K: free-energy barrier {:.5f} "
425 "+- {:.5f} eV (classical {:.5f} eV, instanton effective "
426 "{:.5f} eV), ln(k s) = {:.4f} +- {:.4f}",
427 t, r.barrier, r.barrierError, vSaddle - vReactant, effective,
428 lnPerSecond, r.logRateError);
429 if (r.firstPlaneHeight < 5.0) {
431 "{:.3g} kT above the reactant minimum of F; the "
432 "reactant integral is cut there (raise "
433 "pi_reactant_extent)",
434 t, r.firstPlaneHeight);
435 }
436 if (std::abs(top.meanForce) > 3.0 * top.meanForceError) {
438 "plane is {:.4g} +- {:.2g} eV / (amu^0.5 A); the "
439 "maximum of F is not at s*",
440 t, top.meanForce, top.meanForceError);
441 }
442 if (!last) {
443 continue;
444 }
445 extras.emplace_back("piqtst_temperature_K", t);
446 extras.emplace_back("piqtst_planes", static_cast<double>(nPlanes));
447 extras.emplace_back("piqtst_beads", static_cast<double>(o.pi_beads));
448 extras.emplace_back("piqtst_s_star", sStar);
449 extras.emplace_back("piqtst_dF_ds_star", top.meanForce);
450 extras.emplace_back("piqtst_dF_ds_star_error", top.meanForceError);
451 extras.emplace_back("piqtst_first_plane_kT", r.firstPlaneHeight);
452 extras.emplace_back("barrier_piqtst", r.barrier);
453 extras.emplace_back("barrier_piqtst_error", r.barrierError);
454 extras.emplace_back("rate_piqtst", std::exp(lnPerSecond));
455 extras.emplace_back("rate_piqtst_log", lnPerSecond);
456 extras.emplace_back("rate_piqtst_log_error", r.logRateError);
457 if (kappaOn) {
458 extras.emplace_back("piqtst_kappa", kappa.plateau);
459 extras.emplace_back("piqtst_kappa_error", kappa.plateauError);
460 extras.emplace_back("rate_rpmd", std::exp(lnRpmd));
461 extras.emplace_back("rate_rpmd_log", lnRpmd);
462 extras.emplace_back("rate_rpmd_log_error", lnRpmdError);
463 }
464 }
465 return files;
466}
Eigen::Matrix< double, 3, 3, eOnStorageOrder > Matrix3d
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
#define EONC_LOG_WARNING(...)
#define EONC_LOG_INFO(...)
VectorXi getAtomicNrs() const
const AtomMatrix & getPositions() const
bool getPeriodic() const noexcept
long int numberOfAtoms() const
double getMass(long int atom) const
const instanton_options_t & instanton_options() const
const constants_t & constants() const
constexpr bool io_ok(IoStatus s) noexcept
Recrossing recrossing(Potential &pot, const Coordinate &c, const RecrossingOptions &o)
Bennett-Chandler transmission at s*: parents sampled with the centroid held on the plane,...
std::vector< Plane > scan(Potential &pot, const Coordinate &c, const ScanOptions &o)
Samples one ring per plane and integrates the mean force.
Rate rate(const std::vector< Plane > &planes, double beta)
k = (1/2) sqrt(2 / (pi beta)) exp(-beta F(s*)) / int_{s_0}^{s*} exp(-beta F(s)) ds,...
void validateOptions(const instanton_options_t &o)
Throws std::invalid_argument on an inconsistent [Instanton] pi_* key.
constexpr double kTimeUnitSeconds
One unit of time, sqrt(amu Angstrom^2 / eV), in seconds.
constexpr double kHbar
hbar in eV^0.5 amu^0.5 Angstrom, correctly rounded from the exact SI values (h = 6....
constexpr double kBoltzmann
Boltzmann constant in eV / K, correctly rounded from the exact 1.380649e-23 J / K.
double s
The dividing plane s*, amu^0.5 Angstrom.
std::vector< double > planes
Plane positions in amu^0.5 Angstrom, ascending.