Loading...
Searching...
No Matches
eonc::piqtst Namespace Reference

Classes

struct  Coordinate
 The coordinate s = n . More...
struct  Plane
struct  Rate
struct  Recrossing
struct  RecrossingOptions
struct  ScanOptions

Functions

void integrate (std::vector< Plane > &planes)
 Trapezoid integral of the mean forces, F(s_0) = 0, with errors from independent planes.
std::vector< Plane > scan (Potential &pot, const Coordinate &coordinate, const ScanOptions &options)
 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, the mass-weighted coordinate carrying unit mass, beta in 1 / eV.
Recrossing recrossing (Potential &pot, const Coordinate &coordinate, const RecrossingOptions &options)
 Bennett-Chandler transmission at s*: parents sampled with the centroid held on the plane, children with Maxwell-Boltzmann ring momenta at beta / P and the plane released, propagated by RPMD.
void validateOptions (const instanton_options_t &o)
 Throws std::invalid_argument on an inconsistent [Instanton] pi_* key.
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, and with pi_recrossing_parents > 0 the transmission factor on the top plane and k_RPMD = kappa k_PI-QTST.

Function Documentation

◆ integrate()

void eonc::piqtst::integrate ( std::vector< Plane > & planes)

Trapezoid integral of the mean forces, F(s_0) = 0, with errors from independent planes.

Fills freeEnergy and freeEnergyError.

Definition at line 86 of file PIQTST.cpp.

86 {
87 if (planes.empty()) {
88 return;
89 }
90 const MatrixXd w = trapezoidWeights(planes);
91 VectorXd f(static_cast<long>(planes.size()));
92 for (size_t i = 0; i < planes.size(); ++i) {
93 f(static_cast<long>(i)) = planes[i].meanForce;
94 }
95 const VectorXd errors = forceErrors(planes);
96 const VectorXd values = w * f;
97 for (size_t j = 0; j < planes.size(); ++j) {
98 planes[j].freeEnergy = values(static_cast<long>(j));
99 planes[j].freeEnergyError =
100 propagated(w.row(static_cast<long>(j)).transpose(), errors);
101 }
102}
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, eOnStorageOrder > MatrixXd
Definition Eigen.h:33

◆ rate()

Rate eonc::piqtst::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, the mass-weighted coordinate carrying unit mass, beta in 1 / eV.

The integral is the trapezoid rule over the planes; the last is s*.

Definition at line 213 of file PIQTST.cpp.

213 {
214 const long n = static_cast<long>(planes.size());
215 if (n < 2) {
216 throw std::invalid_argument("piqtst: the rate needs two planes");
217 }
218 if (!(beta > 0.0)) {
219 throw std::invalid_argument("piqtst: the rate needs a positive beta");
220 }
221 const MatrixXd w = trapezoidWeights(planes);
222 const VectorXd errors = forceErrors(planes);
223 Rate r;
224 double fMin = std::numeric_limits<double>::infinity();
225 for (long j = 0; j < n; ++j) {
226 const double f = planes[static_cast<size_t>(j)].freeEnergy;
227 if (f < fMin) {
228 fMin = f;
229 r.reactant = j;
230 }
231 }
232 const long top = n - 1;
233 const double fTop = planes[static_cast<size_t>(top)].freeEnergy;
234 r.barrier = fTop - fMin;
235 r.barrierError =
236 propagated((w.row(top) - w.row(r.reactant)).transpose(), errors);
237 r.firstPlaneHeight = beta * (planes.front().freeEnergy - fMin);
238
239 // Z = int exp(-beta F) ds by the trapezoid rule, referenced to fMin.
240 VectorXd weight(n);
241 double z = 0.0;
242 for (long j = 0; j < n; ++j) {
243 const double left = j > 0 ? planes[static_cast<size_t>(j)].s -
244 planes[static_cast<size_t>(j - 1)].s
245 : 0.0;
246 const double right = j + 1 < n ? planes[static_cast<size_t>(j + 1)].s -
247 planes[static_cast<size_t>(j)].s
248 : 0.0;
249 weight(j) =
250 0.5 * (left + right) *
251 std::exp(-beta * (planes[static_cast<size_t>(j)].freeEnergy - fMin));
252 z += weight(j);
253 }
254 weight /= z;
255 r.logRate = std::log(0.5 * std::sqrt(2.0 / (std::numbers::pi * beta))) -
256 beta * (fTop - fMin) - std::log(z);
257 const VectorXd gradient =
258 -beta * w.row(top).transpose() + beta * (w.transpose() * weight);
259 r.logRateError = propagated(gradient, errors);
260 return r;
261}

◆ recrossing()

Recrossing eonc::piqtst::recrossing ( Potential & pot,
const Coordinate & coordinate,
const RecrossingOptions & options )

Bennett-Chandler transmission at s*: parents sampled with the centroid held on the plane, children with Maxwell-Boltzmann ring momenta at beta / P and the plane released, propagated by RPMD.

The centroid velocity along s is sdot = n . M^(1/2) v_centroid.

Definition at line 263 of file PIQTST.cpp.

264 {
265 const long dof = 3 * c.atoms;
266 const Axes ax = axes(c);
267 if (o.parents < 2 || o.children < 1 || o.spacing < 1 || o.steps < 4 ||
268 o.equilibration < 0) {
269 throw std::invalid_argument(
270 "piqtst: recrossing needs two parents, one child, a positive "
271 "spacing and four steps");
272 }
273 const VectorXd origin = c.reference + o.s * ax.b;
274 VectorXd start = origin;
275 if (o.seed) {
276 start = o.seed(o.s);
277 if (start.size() != dof) {
278 throw std::invalid_argument("piqtst: a seed has the wrong length");
279 }
280 }
281 pathintegral::RingPolymer parent(c.atoms, c.masses, c.numbers, c.free,
282 o.ring);
283 parent.setAllBeads(start.data());
284 parent.setHyperplane(ax.a, origin);
285 for (long step = 0; step < o.equilibration; ++step) {
286 parent.step(pot, c.box, false);
287 }
288
289 // The children draw every normal mode from the free-ring Boltzmann
290 // distribution at beta / P, which PILE's initial momenta are, and carry
291 // their own stream so parent and child noise stay independent.
292 pathintegral::Options childOptions = o.ring;
293 childOptions.thermostat = pathintegral::Thermostat::Pile;
294 childOptions.gleFile.clear();
295 childOptions.seed = o.ring.seed + 0x9E3779B97F4A7C15ULL;
296 pathintegral::RingPolymer child(c.atoms, c.masses, c.numbers, c.free,
297 childOptions);
298
299 const long n = o.steps + 1;
300 // Per parent: sum over children of sdot(0) h(s(t) - s*) at each time,
301 // and of sdot(0) h(sdot(0)).
302 std::vector<VectorXd> numerator(static_cast<size_t>(o.parents),
303 VectorXd::Zero(n));
304 std::vector<double> denominator(static_cast<size_t>(o.parents), 0.0);
305 Recrossing out;
306 for (long p = 0; p < o.parents; ++p) {
307 for (long step = 0; step < o.spacing; ++step) {
308 parent.step(pot, c.box, false);
309 }
310 const std::vector<VectorXd> beads = parent.beads();
311 VectorXd &num = numerator[static_cast<size_t>(p)];
312 double &den = denominator[static_cast<size_t>(p)];
313 for (long k = 0; k < o.children; ++k) {
314 child.setBeads(beads);
315 child.thermalMomenta();
316 std::vector<VectorXd> reversed = child.momenta();
317 const double forward = ax.a.dot(child.centroidVelocity());
318 for (int sign = 0; sign < 2; ++sign) {
319 if (sign == 1) {
320 for (auto &v : reversed) {
321 v = -v;
322 }
323 child.setBeads(beads);
324 child.setMomenta(reversed);
325 }
326 const double sdot = sign == 0 ? forward : -forward;
327 const double flux = sdot > 0.0 ? sdot : 0.0;
328 den += flux;
329 num(0) += flux;
330 for (long i = 1; i < n; ++i) {
331 child.nveStep(pot, c.box);
332 const double s = ax.a.dot(child.centroid() - c.reference);
333 if (s > o.s) {
334 num(i) += sdot;
335 }
336 }
337 ++out.trajectories;
338 }
339 }
340 }
341
342 VectorXd total = VectorXd::Zero(n);
343 double totalDen = 0.0;
344 for (long p = 0; p < o.parents; ++p) {
345 total += numerator[static_cast<size_t>(p)];
346 totalDen += denominator[static_cast<size_t>(p)];
347 }
348 if (!(totalDen > 0.0)) {
349 throw std::runtime_error("piqtst: no child left the plane forward");
350 }
351 out.time.resize(static_cast<size_t>(n));
352 out.kappa.resize(static_cast<size_t>(n));
353 for (long i = 0; i < n; ++i) {
354 out.time[static_cast<size_t>(i)] = static_cast<double>(i) * o.ring.dt;
355 out.kappa[static_cast<size_t>(i)] = total(i) / totalDen;
356 }
357
358 // Plateau numerators per parent, averaged over the last quarter, and the
359 // leave-one-parent-out ratios.
360 const long first = n - 1 - o.steps / 4;
361 const double span = static_cast<double>(n - first);
362 std::vector<double> plateauNum(static_cast<size_t>(o.parents), 0.0);
363 double sumNum = 0.0;
364 for (long p = 0; p < o.parents; ++p) {
365 plateauNum[static_cast<size_t>(p)] =
366 numerator[static_cast<size_t>(p)].tail(n - first).sum() / span;
367 sumNum += plateauNum[static_cast<size_t>(p)];
368 }
369 out.plateau = sumNum / totalDen;
370 const double np = static_cast<double>(o.parents);
371 std::vector<double> leaveOut(static_cast<size_t>(o.parents), 0.0);
372 double meanLeave = 0.0;
373 for (long p = 0; p < o.parents; ++p) {
374 leaveOut[static_cast<size_t>(p)] =
375 (sumNum - plateauNum[static_cast<size_t>(p)]) /
376 (totalDen - denominator[static_cast<size_t>(p)]);
377 meanLeave += leaveOut[static_cast<size_t>(p)];
378 }
379 meanLeave /= np;
380 double var = 0.0;
381 for (const double v : leaveOut) {
382 var += (v - meanLeave) * (v - meanLeave);
383 }
384 out.plateauError = std::sqrt((np - 1.0) / np * var);
385 out.batches = parent.batches() + child.batches();
386 return out;
387}

◆ runAfterInstanton()

std::vector< std::string > eonc::piqtst::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, 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.

Definition at line 167 of file PIQTSTJob.cpp.

171 {
172 const auto &o = params.instanton_options();
174 const double timeUnit = params.constants().timeUnit;
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 // The plane normal. The saddle's unstable mode is the dividing surface
184 // the rate is evaluated on; one normal for every plane keeps s a linear
185 // coordinate, so the mean force integrates to its free energy.
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 {
200 EONC_LOG_WARNING("[Instanton] piqtst: the unstable mode makes {:.3f} "
201 "rad with the reactant-saddle line; planes follow "
202 "the line",
203 std::acos(v.dot(lineDir)));
204 }
205 } else {
206 EONC_LOG_WARNING("[Instanton] piqtst: the saddle Hessian has no "
207 "negative eigenvalue; planes follow the line");
208 }
209 }
210 if (emb.freeAtoms.size() == static_cast<size_t>(reactant.numberOfAtoms()) &&
211 !reactant.getPeriodic()) {
212 EONC_LOG_WARNING("[Instanton] piqtst: no atom is fixed; the planes are "
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
225 Coordinate c;
226 c.atoms = reactant.numberOfAtoms();
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);
230 const VectorXi z = reactant.getAtomicNrs();
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 }
240 c.reference = flatten(reactant.getPositions());
241 c.direction = emb.full(dir);
242 const Matrix3d cell =
243 reactant.getPeriodic() ? reactant.getCell() : Matrix3d::Zero().eval();
244 c.box = cell.data();
245
246 // Seeds: the band where it crosses the plane, else the straight line
247 // through the reactant and the saddle.
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<>());
296 const double logSecond = std::log(tunneling::kTimeUnitSeconds);
297 for (size_t ti = 0; ti < sorted.size(); ++ti) {
298 const double t = sorted[ti];
299 const bool last = ti + 1 == sorted.size();
300 const double beta = 1.0 / (tunneling::kBoltzmann * t);
301 ScanOptions so;
302 so.planes = sPlanes;
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;
308 so.ring.kB = tunneling::kBoltzmann;
309 so.ring.hbar = tunneling::kHbar;
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);
320 const Rate r = rate(planes, beta);
321 const double lnPerSecond = r.logRate - logSecond;
322
323 Recrossing kappa;
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;
336 kappa = recrossing(pot, c, ro);
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 {
342 EONC_LOG_WARNING("[Instanton] piqtst {:.4g} K: the transmission "
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);
394 AtomMatrix pos = frame.getPositions();
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) {
430 EONC_LOG_WARNING("[Instanton] piqtst {:.4g} K: the first plane is "
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) {
437 EONC_LOG_WARNING("[Instanton] piqtst {:.4g} K: dF/ds at the saddle "
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
Definition Eigen.h:35
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
#define EONC_LOG_WARNING(...)
Definition EonLogger.h:255
#define EONC_LOG_INFO(...)
Definition EonLogger.h:249
VectorXi getAtomicNrs() const
Definition Matter.cpp:691
const AtomMatrix & getPositions() const
Definition Matter.cpp:308
bool getPeriodic() const noexcept
Definition Matter.h:313
Matrix3d getCell() const
Definition Matter.cpp:275
long int numberOfAtoms() const
Definition Matter.cpp:273
double getMass(long int atom) const
Definition Matter.cpp:478
const instanton_options_t & instanton_options() const
const constants_t & constants() const
constexpr bool io_ok(IoStatus s) noexcept
Definition ConFileIO.h:38
Recrossing recrossing(Potential &pot, const Coordinate &c, const RecrossingOptions &o)
Bennett-Chandler transmission at s*: parents sampled with the centroid held on the plane,...
Definition PIQTST.cpp:263
std::vector< Plane > scan(Potential &pot, const Coordinate &c, const ScanOptions &o)
Samples one ring per plane and integrates the mean force.
Definition PIQTST.cpp:104
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,...
Definition PIQTST.cpp:213
void validateOptions(const instanton_options_t &o)
Throws std::invalid_argument on an inconsistent [Instanton] pi_* key.
Definition PIQTSTJob.cpp:31
constexpr double kTimeUnitSeconds
One unit of time, sqrt(amu Angstrom^2 / eV), in seconds.
Definition Tunneling.h:224
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
constexpr double kBoltzmann
Boltzmann constant in eV / K, correctly rounded from the exact 1.380649e-23 J / K.
Definition Tunneling.h:41
The coordinate s = n .
Definition PIQTST.h:42
double s
The dividing plane s*, amu^0.5 Angstrom.
Definition PIQTST.h:119
std::vector< double > planes
Plane positions in amu^0.5 Angstrom, ascending.
Definition PIQTST.h:55

◆ scan()

std::vector< Plane > eonc::piqtst::scan ( Potential & pot,
const Coordinate & coordinate,
const ScanOptions & options )

Samples one ring per plane and integrates the mean force.

One ring is carried from plane to plane: its centroid moves to the next seed and the internal modes keep their state.

Definition at line 104 of file PIQTST.cpp.

105 {
106 const long dof = 3 * c.atoms;
107 const Axes ax = axes(c);
108 const VectorXd &a = ax.a;
109 const VectorXd &b = ax.b;
110 if (o.planes.size() < 2) {
111 throw std::invalid_argument("piqtst: at least two planes are needed");
112 }
113 for (size_t j = 1; j < o.planes.size(); ++j) {
114 if (!(o.planes[j] > o.planes[j - 1])) {
115 throw std::invalid_argument("piqtst: plane positions must ascend");
116 }
117 }
118 if (o.equilibration < 0 || o.production < 2 || o.blocks < 2 ||
119 o.production < o.blocks) {
120 throw std::invalid_argument(
121 "piqtst: sampling needs at least as many steps as blocks, and two "
122 "blocks");
123 }
124 const double aNorm = a.norm();
125 auto seedAt = [&](double s) -> VectorXd {
126 if (o.seed) {
127 VectorXd x = o.seed(s);
128 if (x.size() != dof) {
129 throw std::invalid_argument("piqtst: a seed has the wrong length");
130 }
131 return x;
132 }
133 return c.reference + s * b;
134 };
135
136 pathintegral::RingPolymer ring(c.atoms, c.masses, c.numbers, c.free, o.ring);
137 const long beads = o.ring.beads;
138 const long blockSize = o.production / o.blocks;
139 std::vector<Plane> out;
140 out.reserve(o.planes.size());
141 for (size_t j = 0; j < o.planes.size(); ++j) {
142 const double s = o.planes[j];
143 const VectorXd origin = c.reference + s * b;
144 const VectorXd target = seedAt(s);
145 if (j == 0) {
146 ring.setAllBeads(target.data());
147 } else {
148 const VectorXd shift = target - ring.centroid();
149 std::vector<VectorXd> moved = ring.beads();
150 for (auto &q : moved) {
151 q += shift;
152 }
153 ring.setBeads(moved);
154 }
155 ring.setHyperplane(a, origin);
156
157 const long batches0 = ring.batches();
158 for (long step = 0; step < o.equilibration; ++step) {
159 ring.step(pot, c.box, false);
160 }
161 Plane plane;
162 plane.s = s;
163 plane.centroid = VectorXd::Zero(dof);
164 VectorXd spread2 = VectorXd::Zero(dof);
165 double sum = 0.0;
166 double blockSum = 0.0;
167 std::vector<double> blockMeans;
168 for (long step = 0; step < o.production; ++step) {
169 ring.resetAverages();
170 ring.step(pot, c.box, true);
171 const double fn = ring.meanForce();
172 sum += fn;
173 blockSum += fn;
174 if ((step + 1) % blockSize == 0 &&
175 static_cast<long>(blockMeans.size()) < o.blocks) {
176 blockMeans.push_back(blockSum / static_cast<double>(blockSize));
177 blockSum = 0.0;
178 }
179 const VectorXd centroid = ring.centroid();
180 plane.centroid += centroid;
181 for (const auto &q : ring.beads()) {
182 spread2 += (q - centroid).cwiseAbs2();
183 }
184 }
185 const double steps = static_cast<double>(o.production);
186 plane.centroid /= steps;
187 spread2 /= steps * static_cast<double>(beads);
188 plane.spread.resize(static_cast<size_t>(dof));
189 for (long i = 0; i < dof; ++i) {
190 plane.spread[static_cast<size_t>(i)] = std::sqrt(spread2(i));
191 }
192 const double mean = sum / steps;
193 double blockMean = 0.0;
194 for (const double m : blockMeans) {
195 blockMean += m;
196 }
197 const double nb = static_cast<double>(blockMeans.size());
198 blockMean /= nb;
199 double var = 0.0;
200 for (const double m : blockMeans) {
201 var += (m - blockMean) * (m - blockMean);
202 }
203 var /= nb * (nb - 1.0);
204 plane.meanForce = -mean / aNorm;
205 plane.meanForceError = std::sqrt(var) / aNorm;
206 plane.batches = ring.batches() - batches0;
207 out.push_back(std::move(plane));
208 }
209 integrate(out);
210 return out;
211}
void integrate(std::vector< Plane > &planes)
Trapezoid integral of the mean forces, F(s_0) = 0, with errors from independent planes.
Definition PIQTST.cpp:86

◆ validateOptions()

void eonc::piqtst::validateOptions ( const instanton_options_t & o)

Throws std::invalid_argument on an inconsistent [Instanton] pi_* key.

Definition at line 31 of file PIQTSTJob.cpp.

31 {
32 if (o.pi_planes < 0) {
33 throw std::invalid_argument("[Instanton] pi_planes must not be negative");
34 }
35 if (o.pi_planes == 0) {
36 return;
37 }
38 if (o.mode != "rate") {
39 throw std::invalid_argument("[Instanton] pi_planes needs mode = rate");
40 }
41 if (o.pi_planes < 2) {
42 throw std::invalid_argument(
43 "[Instanton] pi_planes must be 0 or at least 2");
44 }
45 if (o.pi_beads < 1) {
46 throw std::invalid_argument("[Instanton] pi_beads must be positive");
47 }
48 if (o.pi_equilibration_steps < 0 || o.pi_sampling_steps < 20) {
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)");
52 }
53 if (!(o.pi_time_step > 0.0) || !(o.pi_pile_tau > 0.0) ||
54 !(o.pi_pile_scale > 0.0)) {
55 throw std::invalid_argument("[Instanton] pi_time_step, pi_pile_tau and "
56 "pi_pile_scale must be positive");
57 }
58 if (o.pi_seed < 0) {
59 throw std::invalid_argument("[Instanton] pi_seed must not be negative");
60 }
61 if (o.pi_thermostat != "pile" && o.pi_thermostat != "piglet") {
62 throw std::invalid_argument(
63 "[Instanton] pi_thermostat must be pile or piglet, not " +
65 }
66 if (o.pi_thermostat == "piglet" && o.pi_beads > 1 && o.pi_gle_file.empty()) {
67 throw std::invalid_argument(
68 "[Instanton] pi_thermostat = piglet needs pi_gle_file");
69 }
70 if (o.pi_direction != "mode" && o.pi_direction != "line") {
71 throw std::invalid_argument(
72 "[Instanton] pi_direction must be mode or line, not " + o.pi_direction);
73 }
74 if (!(o.pi_reactant_extent >= 0.0)) {
75 throw std::invalid_argument(
76 "[Instanton] pi_reactant_extent must not be negative");
77 }
78 if (o.pi_recrossing_parents < 0 || o.pi_recrossing_parents == 1) {
79 throw std::invalid_argument(
80 "[Instanton] pi_recrossing_parents must be 0 or at least 2");
81 }
82 if (o.pi_recrossing_parents == 0) {
83 return;
84 }
86 throw std::invalid_argument("[Instanton] pi_recrossing_children and "
87 "pi_recrossing_spacing must be positive");
88 }
89 if (!(o.pi_recrossing_time >= 4.0 * o.pi_time_step)) {
90 throw std::invalid_argument(
91 "[Instanton] pi_recrossing_time must be at least four pi_time_step");
92 }
93}