19#include <Eigen/Eigenvalues>
33 throw std::invalid_argument(
"[Instanton] pi_planes must not be negative");
38 if (o.
mode !=
"rate") {
39 throw std::invalid_argument(
"[Instanton] pi_planes needs mode = rate");
42 throw std::invalid_argument(
43 "[Instanton] pi_planes must be 0 or at least 2");
46 throw std::invalid_argument(
"[Instanton] pi_beads must be positive");
49 throw std::invalid_argument(
50 "[Instanton] pi_equilibration_steps must not be negative and "
51 "pi_sampling_steps must be at least 20 (ten blocks of two)");
55 throw std::invalid_argument(
"[Instanton] pi_time_step, pi_pile_tau and "
56 "pi_pile_scale must be positive");
59 throw std::invalid_argument(
"[Instanton] pi_seed must not be negative");
62 throw std::invalid_argument(
63 "[Instanton] pi_thermostat must be pile or piglet, not " +
67 throw std::invalid_argument(
68 "[Instanton] pi_thermostat = piglet needs pi_gle_file");
71 throw std::invalid_argument(
72 "[Instanton] pi_direction must be mode or line, not " + o.
pi_direction);
75 throw std::invalid_argument(
76 "[Instanton] pi_reactant_extent must not be negative");
79 throw std::invalid_argument(
80 "[Instanton] pi_recrossing_parents must be 0 or at least 2");
86 throw std::invalid_argument(
"[Instanton] pi_recrossing_children and "
87 "pi_recrossing_spacing must be positive");
90 throw std::invalid_argument(
91 "[Instanton] pi_recrossing_time must be at least four pi_time_step");
100 std::vector<long> freeAtoms;
101 std::vector<double> sqrtMass;
104 explicit Embedding(
const Matter &reactant)
105 : atoms(reactant.numberOfAtoms()) {
106 for (
long i = 0; i < atoms; ++i) {
107 if (!reactant.getFixed(i)) {
108 freeAtoms.push_back(i);
109 sqrtMass.push_back(std::sqrt(reactant.getMass(i)));
113 long dimension()
const {
return 3 *
static_cast<long>(freeAtoms.size()); }
115 VectorXd full(
const VectorXd &q)
const {
116 VectorXd out = VectorXd::Zero(3 * atoms);
117 for (
size_t k = 0; k < freeAtoms.size(); ++k) {
118 for (
int c = 0; c < 3; ++c) {
119 out(3 * freeAtoms[k] + c) = q(
static_cast<long>(3 * k) + c);
125 VectorXd cartesian(
const VectorXd &reference,
const VectorXd &q)
const {
126 VectorXd x = reference;
127 for (
size_t k = 0; k < freeAtoms.size(); ++k) {
128 for (
int c = 0; c < 3; ++c) {
129 x(3 * freeAtoms[k] + c) +=
130 q(
static_cast<long>(3 * k) + c) / sqrtMass[k];
135 VectorXd toQ(
const Matter &reactant,
const Matter &m)
const {
137 reactant.pbc(m.getPositions() - reactant.getPositions());
138 VectorXd q(dimension());
139 for (
size_t k = 0; k < freeAtoms.size(); ++k) {
140 for (
int c = 0; c < 3; ++c) {
141 q(
static_cast<long>(3 * k) + c) = sqrtMass[k] * d(freeAtoms[k], c);
149 VectorXd x(3 * m.rows());
150 for (
long i = 0; i < m.rows(); ++i) {
151 for (
int c = 0; c < 3; ++c) {
152 x(3 * i + c) = m(i, c);
158std::string kelvinTag(
double t) {
159 std::ostringstream name;
160 name << std::defaultfloat << std::setprecision(6) << t <<
"K";
166std::vector<std::string>
169 const MatrixXd &hSaddle,
const std::vector<VectorXd> &pathQ,
170 const std::vector<double> &temperatures,
171 std::vector<std::pair<std::string, double>> &extras) {
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");
181 const VectorXd lineDir = qSaddle / qNorm;
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) {
196 if (v.dot(lineDir) >= 0.5) {
201 "rad with the reactant-saddle line; planes follow "
203 std::acos(v.dot(lineDir)));
207 "negative eigenvalue; planes follow the line");
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");
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));
229 c.
free.assign(
static_cast<size_t>(3 * c.
atoms), 0);
231 for (
long i = 0; i < c.
atoms; ++i) {
233 c.
numbers[
static_cast<size_t>(i)] = z(i);
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;
248 std::vector<double> bandS;
249 for (
const auto &q : pathQ) {
250 bandS.push_back(dir.dot(q));
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;
259 (1.0 - t) * pathQ[k] + t * pathQ[k + 1]);
262 return emb.cartesian(c.
reference, (s / sStar) * qSaddle);
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;
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,
279 const std::string tableFile =
"rate_piqtst.dat";
280 std::ofstream table(tableFile);
282 throw std::runtime_error(
"piqtst: cannot write " + tableFile);
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 "
288 table <<
" kappa kappa_error ln_k_rpmd_s ln_k_rpmd_s_error";
291 table << std::setprecision(10);
292 std::vector<std::string> files{tableFile};
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();
310 so.
ring.
dt = o.pi_time_step / timeUnit;
317 so.
ring.
seed =
static_cast<std::uint64_t
>(o.pi_seed);
319 const std::vector<Plane> planes =
scan(
pot, c, so);
321 const double lnPerSecond = r.
logRate - logSecond;
324 double lnRpmd = std::numeric_limits<double>::quiet_NaN();
325 double lnRpmdError = std::numeric_limits<double>::quiet_NaN();
328 ro.
s = planes.back().s;
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);
338 lnRpmd = lnPerSecond + std::log(kappa.
plateau);
343 "factor {:.4g} +- {:.2g} is not positive; ln k_RPMD "
344 "is undefined (raise pi_recrossing_parents)",
347 EONC_LOG_INFO(
"[Instanton] piqtst {:.4g} K: transmission factor "
348 "kappa = {:.4f} +- {:.4f} from {} trajectories, "
349 "ln(k_RPMD s) = {:.4f} +- {:.4f}",
351 lnRpmd, lnRpmdError);
352 std::vector<std::string> curveFiles;
354 curveFiles.push_back(
"kappa_piqtst.dat");
356 if (sorted.size() > 1) {
357 curveFiles.push_back(
"kappa_piqtst_" + kelvinTag(t) +
".dat");
359 for (
const auto &file : curveFiles) {
360 std::ofstream curve(file);
362 throw std::runtime_error(
"piqtst: cannot write " + file);
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';
368 files.push_back(file);
372 std::vector<std::string> conFiles;
374 conFiles.push_back(
"piqtst_planes.con");
376 if (sorted.size() > 1) {
377 conFiles.push_back(
"piqtst_planes_" + kelvinTag(t) +
".con");
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);
390 << lnRpmd <<
' ' << lnRpmdError;
395 for (
long i = 0; i < c.
atoms; ++i) {
396 for (
int a = 0; a < 3; ++a) {
405 meta.
scalars = {{
"piqtst_temperature_K", t},
411 {
"beads",
static_cast<double>(o.pi_beads)},
412 {
"spread_max", largest}};
413 for (
const auto &file : conFiles) {
415 throw std::runtime_error(
"piqtst: cannot write " + file);
419 for (
const auto &file : conFiles) {
420 files.push_back(file);
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}",
431 "{:.3g} kT above the reactant minimum of F; the "
432 "reactant integral is cut there (raise "
433 "pi_reactant_extent)",
438 "plane is {:.4g} +- {:.2g} eV / (amu^0.5 A); the "
439 "maximum of F is not at s*",
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);
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);
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);
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, eOnStorageOrder > MatrixXd
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
void setPositions(const AtomMatrix &pos)
bool getPeriodic() const noexcept
long int numberOfAtoms() const
double getMass(long int atom) const
double getPotentialEnergy() const
io::IoStatus matter2con(std::string filename, bool append=false, const io::ConFrameMetadata *metadata=nullptr)
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.
std::vector< std::string > runAfterInstanton(const Parameters ¶ms, 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,...
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.
std::string pi_thermostat
long pi_equilibration_steps
long pi_recrossing_children
double pi_reactant_extent
long pi_recrossing_spacing
double pi_recrossing_time
long pi_recrossing_parents
std::string gleFile
Normal-mode GLE matrices.
double pileScale
Scales the critical PILE damping of the internal modes.
double pileTau
Centroid Langevin time, in the same time unit as dt.
std::vector< int > numbers
std::vector< double > masses
double freeEnergy
F(s) - F(s_0) by the trapezoid rule over the planes, eV, and its standard error from the mean-force e...
std::vector< double > spread
Root-mean-square bead displacement from the centroid per Cartesian coordinate, Angstrom,...
VectorXd centroid
Production average of the centroid, Cartesian.
double meanForce
dF/ds = -<n .
double firstPlaneHeight
beta (F(s_0) - F(reactant)): the reactant integral is cut at s_0, so a small value means the first pl...
double logRate
ln k with k in inverse eOn time units (sqrt(amu Angstrom^2 / eV)), and its standard error.
double barrier
F(s*) - F(reactant), eV, and its error.
long parents
Parent configurations, each this many thermostatted steps after the last.
long equilibration
Thermostatted steps on the plane before the first parent.
std::function< VectorXd(double)> seed
Cartesian centroid to start the parent ring at.
long steps
Unconstrained, thermostat-free steps per child of ring.dt.
long children
Momentum draws per parent; each runs forward and reversed.
double s
The dividing plane s*, amu^0.5 Angstrom.
pathintegral::Options ring
The parents' ring and thermostat.
std::vector< double > time
t = step * ring.dt, from 0 to steps * ring.dt, and kappa(t) = <sdot(0) h(s(t) - s*)> / <sdot(0) h(sdo...
double plateau
Mean of kappa(t) over the last quarter of the times, and its jackknife standard error over parents.
std::vector< double > kappa
std::function< VectorXd(double)> seed
Cartesian centroid to start the ring at on the plane at s.
std::vector< double > planes
Plane positions in amu^0.5 Angstrom, ascending.
long blocks
Equal blocks of the production run for the standard error.
pathintegral::Options ring
Beads, temperature, units (kB in eV / K, hbar in eV time units), time step and thermostat of the ring...