Loading...
Searching...
No Matches
PathIntegral.cpp
Go to the documentation of this file.
1/*
2** This file is part of eOn.
3**
4** i-PI Copyright (C) 2014-2015 i-PI developers
5**
6** Permission is hereby granted, free of charge, to any person obtaining
7** a copy of this software and associated documentation files (the
8** "Software"), to deal in the Software without restriction, including
9** without limitation the rights to use, copy, modify, merge, publish,
10** distribute, sublicense, and/or sell copies of the Software, and to
11** permit persons to whom the Software is furnished to do so, subject to
12** the following conditions:
13**
14** The above copyright notice and this permission notice shall be
15** included in all copies or substantial portions of the Software.
16**
17** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
21** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22** ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24** SOFTWARE.
25**
26** SPDX-License-Identifier: MIT
27*/
28
29#include "eon/PathIntegral.h"
30
31#include <unsupported/Eigen/MatrixFunctions>
32
33#include <algorithm>
34#include <cctype>
35#include <cmath>
36#include <fstream>
37#include <numbers>
38#include <sstream>
39#include <stdexcept>
40#include <utility>
41
43namespace {
44
45constexpr double kPi = std::numbers::pi;
46
47std::string lower(std::string s) {
48 for (char &c : s) {
49 c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
50 }
51 return s;
52}
53
54double ecoResponse(double x) {
55 const double z = 0.5 * x;
56 if (z < 0.25) {
57 const double z2 = z * z;
58 const double denom =
59 1.0 / 3.0 - z2 / 45.0 + 2.0 * z2 * z2 / 945.0 - z2 * z2 * z2 / 4725.0;
60 return 4.0 / denom;
61 }
62 return (x * x) / (z / std::tanh(z) - 1.0);
63}
64
65VectorXd ecoFit(long nBeads, double xmax) {
66 const long nfree = nBeads / 2;
67 VectorXd mult(nfree);
68 mult.setConstant(2.0);
69 if (nBeads % 2 == 0) {
70 mult[nfree - 1] = 1.0;
71 }
72 const long m = std::max(std::lround(10.0 * xmax), 100L);
73 VectorXd x(m);
74 VectorXd f(m);
75 for (long i = 0; i < m; ++i) {
76 x[i] = (static_cast<double>(i) + 0.5) * (xmax / static_cast<double>(m));
77 f[i] = ecoResponse(x[i]);
78 }
79
80 auto objective = [&](const VectorXd &y, double &s, VectorXd &g, MatrixXd &h) {
81 MatrixXd d(m, nfree);
82 MatrixXd e(m, nfree);
83 MatrixXd dg(m, nfree);
84 MatrixXd d2(m, nfree);
85 VectorXd r(m);
86 for (long i = 0; i < m; ++i) {
87 double row = 0.0;
88 for (long k = 0; k < nfree; ++k) {
89 d(i, k) = 1.0 / (y[k] * y[k] + x[i] * x[i]);
90 e(i, k) = f[i] * mult[k] * d(i, k);
91 row += e(i, k);
92 dg(i, k) = -2.0 * d(i, k) * e(i, k) * y[k];
93 d2(i, k) = 2.0 * d(i, k) * d(i, k) * e(i, k) *
94 (3.0 * y[k] * y[k] - x[i] * x[i]);
95 }
96 r[i] = row - 1.0;
97 }
98 s = 0.5 * r.squaredNorm() / static_cast<double>(m);
99 g = VectorXd::Zero(nfree);
100 for (long i = 0; i < m; ++i) {
101 for (long k = 0; k < nfree; ++k) {
102 g[k] += r[i] * dg(i, k);
103 }
104 }
105 g /= static_cast<double>(m);
106 h = dg.transpose() * dg;
107 h += (d2.transpose() * r).asDiagonal();
108 h /= static_cast<double>(m);
109 };
110
111 VectorXd y(nfree);
112 for (long k = 0; k < nfree; ++k) {
113 y[k] = 2.0 * kPi * static_cast<double>(k + 1);
114 }
115 double s = 0.0;
116 VectorXd g(nfree);
117 MatrixXd h(nfree, nfree);
118 objective(y, s, g, h);
119 bool exhausted = true;
120 // The near-degenerate high-frequency group leaves a valley whose Hessian
121 // eigenvalues span about twelve decades; the shifted Newton step crosses
122 // it in several hundred iterations, so the cap follows the paper's 10000.
123 for (int iter = 0; iter < 10000; ++iter) {
124 Eigen::SelfAdjointEigenSolver<MatrixXd> es(h);
125 if (es.info() != Eigen::Success) {
126 throw std::runtime_error("economised spring fit: Hessian eigensolve");
127 }
128 const VectorXd eva = es.eigenvalues();
129 const MatrixXd vec = es.eigenvectors();
130 const double delta = std::max(1e-16 * eva[nfree - 1], -2.0 * eva[0]);
131 const VectorXd rhs = vec.transpose() * g;
132 VectorXd scaled(nfree);
133 for (long k = 0; k < nfree; ++k) {
134 scaled[k] = -rhs[k] / (eva[k] + delta);
135 }
136 const VectorXd dy = vec * scaled;
137 const double previous = s;
138 double c = 1.0;
139 bool accepted = false;
140 for (int cut = 0; cut < 60; ++cut) {
141 const VectorXd z = y + c * dy;
142 bool ordered = z[0] >= 0.0;
143 for (long k = 1; ordered && k < nfree; ++k) {
144 ordered = z[k] >= z[k - 1];
145 }
146 if (ordered) {
147 double sn = 0.0;
148 VectorXd gn(nfree);
149 MatrixXd hn(nfree, nfree);
150 objective(z, sn, gn, hn);
151 if (sn <= s) {
152 y = z;
153 s = sn;
154 g = std::move(gn);
155 h = std::move(hn);
156 accepted = true;
157 break;
158 }
159 }
160 c *= 0.5;
161 }
162 if (!accepted) {
163 exhausted = false;
164 break;
165 }
166 if (previous - s <= 1e-12 * previous) {
167 exhausted = false;
168 break;
169 }
170 }
171 if (exhausted) {
172 throw std::runtime_error("economised spring fit did not converge");
173 }
174 return y;
175}
176
177MatrixXd factorCovariance(const MatrixXd &cov) {
178 const MatrixXd sym = 0.5 * (cov + cov.transpose());
179 Eigen::SelfAdjointEigenSolver<MatrixXd> es(sym);
180 if (es.info() != Eigen::Success) {
181 throw std::runtime_error("GLE covariance factorisation failed");
182 }
183 return es.eigenvectors() *
184 es.eigenvalues().cwiseMax(0.0).cwiseSqrt().asDiagonal();
185}
186
187struct GleFile {
188 long nModes{0};
189 long dim{0};
190 std::vector<MatrixXd> drift;
191 std::vector<MatrixXd> covariance;
192};
193
194GleFile readGle(const std::string &path) {
195 std::ifstream in(path);
196 if (!in) {
197 throw std::runtime_error("cannot read GLE matrices from " + path);
198 }
199 std::vector<double> nums;
200 std::string line;
201 while (std::getline(in, line)) {
202 const auto hash = line.find('#');
203 if (hash != std::string::npos) {
204 line.resize(hash);
205 }
206 std::istringstream ss(line);
207 double v = 0.0;
208 while (ss >> v) {
209 nums.push_back(v);
210 }
211 }
212 if (nums.size() < 2) {
213 throw std::runtime_error("GLE matrix file " + path + " is empty");
214 }
215 GleFile out;
216 out.nModes = std::lround(nums[0]);
217 out.dim = std::lround(nums[1]);
218 if (out.nModes < 1 || out.dim < 1) {
219 throw std::runtime_error("GLE matrix file " + path +
220 " has no modes or no matrix");
221 }
222 const long block = out.dim * out.dim;
223 const long need = 2 + out.nModes * 2 * block;
224 if (static_cast<long>(nums.size()) != need) {
225 throw std::runtime_error("GLE matrix file " + path +
226 " does not match its header");
227 }
228 long cursor = 2;
229 out.drift.resize(static_cast<size_t>(out.nModes));
230 out.covariance.resize(static_cast<size_t>(out.nModes));
231 for (long mode = 0; mode < out.nModes; ++mode) {
232 MatrixXd a(out.dim, out.dim);
233 MatrixXd c(out.dim, out.dim);
234 for (long i = 0; i < out.dim; ++i) {
235 for (long j = 0; j < out.dim; ++j) {
236 a(i, j) = nums[static_cast<size_t>(cursor++)];
237 }
238 }
239 for (long i = 0; i < out.dim; ++i) {
240 for (long j = 0; j < out.dim; ++j) {
241 c(i, j) = nums[static_cast<size_t>(cursor++)];
242 }
243 }
244 out.drift[static_cast<size_t>(mode)] = std::move(a);
245 out.covariance[static_cast<size_t>(mode)] = std::move(c);
246 }
247 return out;
248}
249
250} // namespace
251
252void requireTrotterSprings(const std::string &springs, const char *use) {
253 const std::string s = lower(springs);
254 if (s == "eco" || s == "economised") {
255 throw std::invalid_argument(
256 std::string(use) +
257 " requires Trotter springs; economised springs are refused");
258 }
259}
260
261VectorXd trotterEigenvalues(long nBeads) {
262 if (nBeads < 1) {
263 throw std::invalid_argument("bead count must be positive");
264 }
265 VectorXd eva(nBeads);
266 for (long k = 0; k < nBeads; ++k) {
267 eva[k] = 2.0 * std::sin(kPi * static_cast<double>(k) /
268 static_cast<double>(nBeads));
269 }
270 return eva;
271}
272
273VectorXd ecoEigenvalues(long nBeads, double xmax) {
274 if (nBeads < 1) {
275 throw std::invalid_argument("bead count must be positive");
276 }
277 VectorXd eva = VectorXd::Zero(nBeads);
278 if (nBeads == 1) {
279 return eva;
280 }
281 if (!(xmax > 0.0)) {
282 throw std::invalid_argument(
283 "economised springs need a positive maximum frequency");
284 }
285 const VectorXd y = ecoFit(nBeads, xmax);
286 for (long k = 1; k < nBeads; ++k) {
287 const long pair = std::min(k, nBeads - k) - 1;
288 eva[k] = y[pair] / static_cast<double>(nBeads);
289 }
290 return eva;
291}
292
294 if (nBeads < 1) {
295 throw std::invalid_argument("bead count must be positive");
296 }
297 MatrixXd b = MatrixXd::Zero(nBeads, nBeads);
298 const double n = static_cast<double>(nBeads);
299 for (long j = 0; j < nBeads; ++j) {
300 b(0, j) = 1.0;
301 for (long i = 1; i <= nBeads / 2; ++i) {
302 b(i, j) = std::sqrt(2.0) * std::cos(2.0 * kPi * static_cast<double>(j) *
303 static_cast<double>(i) / n);
304 }
305 for (long i = nBeads / 2 + 1; i < nBeads; ++i) {
306 b(i, j) = std::sqrt(2.0) * std::sin(2.0 * kPi * static_cast<double>(j) *
307 static_cast<double>(i) / n);
308 }
309 }
310 if (nBeads % 2 == 0) {
311 const long mid = nBeads / 2;
312 for (long j = 0; j < nBeads; ++j) {
313 b(mid, j) = (j % 2 == 0) ? 1.0 : -1.0;
314 }
315 }
316 b /= std::sqrt(n);
317 return b;
318}
319
320RingPolymer::RingPolymer(long nAtoms, std::vector<double> masses,
321 std::vector<int> atomicNumbers, std::vector<char> free,
322 Options opt)
323 : opt_(std::move(opt)),
324 nAtoms_(nAtoms),
325 nDof_(3 * nAtoms),
327 atomicNumbers_(std::move(atomicNumbers)),
328 free_(std::move(free)),
329 rng_(opt_.seed == 0 ? 1 : opt_.seed) {
330 if (nAtoms_ < 1 || nBeads_ < 1) {
331 throw std::invalid_argument("path integral needs atoms and beads");
332 }
333 if (static_cast<long>(masses.size()) != nAtoms_ ||
334 static_cast<long>(atomicNumbers_.size()) != nAtoms_ ||
335 static_cast<long>(free_.size()) != nDof_) {
336 throw std::invalid_argument("path integral mass, number or mask size");
337 }
338 if (!(opt_.temperature > 0.0) || !(opt_.kB > 0.0) || !(opt_.hbar > 0.0) ||
339 !(opt_.dt > 0.0) || !(opt_.pileTau > 0.0) || !(opt_.pileScale > 0.0)) {
340 throw std::invalid_argument(
341 "path integral temperature, timestep and damping must be positive");
342 }
343 if (opt_.springs == Springs::Eco && opt_.thermostat == Thermostat::Piglet) {
344 throw std::invalid_argument(
345 "economised springs cannot be combined with a normal-mode GLE");
346 }
347 mass_.assign(static_cast<size_t>(nDof_), 0.0);
348 nFree_ = 0;
349 for (long i = 0; i < nAtoms_; ++i) {
350 if (!(masses[static_cast<size_t>(i)] > 0.0)) {
351 throw std::invalid_argument("path integral atom has no mass");
352 }
353 for (int axis = 0; axis < 3; ++axis) {
354 const long a = 3 * i + axis;
355 mass_[static_cast<size_t>(a)] = masses[static_cast<size_t>(i)];
356 if (free_[static_cast<size_t>(a)]) {
357 freeIndex_.push_back(a);
358 ++nFree_;
359 }
360 }
361 }
362 if (nFree_ < 1) {
363 throw std::invalid_argument("path integral has no free coordinate");
364 }
366 const double omegan =
367 static_cast<double>(nBeads_) * opt_.kB * opt_.temperature / opt_.hbar;
368 if (opt_.springs == Springs::Eco) {
369 const double xmax =
370 opt_.ecoOmegaMax * opt_.hbar / (opt_.kB * opt_.temperature);
371 omegaK_ = omegan * ecoEigenvalues(nBeads_, xmax);
372 } else {
374 }
375 q_.assign(static_cast<size_t>(nBeads_), VectorXd::Zero(nDof_));
376 p_.assign(static_cast<size_t>(nBeads_), VectorXd::Zero(nDof_));
377 f_.assign(static_cast<size_t>(nBeads_), VectorXd::Zero(nDof_));
378 qnm_.assign(static_cast<size_t>(nBeads_), VectorXd::Zero(nDof_));
379 pnm_.assign(static_cast<size_t>(nBeads_), VectorXd::Zero(nDof_));
380 if (opt_.thermostat == Thermostat::Piglet) {
381 initGle();
382 }
384}
385
387 if (nBeads_ == 1) {
388 return;
389 }
390 if (opt_.gleFile.empty()) {
391 throw std::invalid_argument("normal-mode GLE needs a matrix file");
392 }
393 const GleFile file = readGle(opt_.gleFile);
394 long first = 0;
395 if (file.nModes == nBeads_) {
396 first = 1;
397 } else if (file.nModes != nBeads_ - 1) {
398 throw std::invalid_argument(
399 "GLE matrix count must be the bead count or one less");
400 }
401 const double h = 0.5 * opt_.dt;
402 gle_.resize(static_cast<size_t>(nBeads_ - 1));
403 for (long k = 1; k < nBeads_; ++k) {
404 const long src = first + (k - 1);
405 ModeGle mode;
406 mode.drift = file.drift[static_cast<size_t>(src)];
407 mode.covariance = file.covariance[static_cast<size_t>(src)];
408 mode.propagate = (-mode.drift * h).exp();
409 const MatrixXd cov =
410 opt_.kB * (mode.covariance - mode.propagate * mode.covariance *
411 mode.propagate.transpose());
412 mode.noise = factorCovariance(cov);
413 mode.extended = MatrixXd::Zero(file.dim, nFree_);
414 gle_[static_cast<size_t>(k - 1)] = std::move(mode);
415 }
416}
417
418void RingPolymer::setAllBeads(const double *q) {
419 if (q == nullptr) {
420 throw std::invalid_argument("path integral positions are missing");
421 }
422 for (long bead = 0; bead < nBeads_; ++bead) {
423 for (long a = 0; a < nDof_; ++a) {
424 q_[static_cast<size_t>(bead)][a] = q[a];
425 }
426 }
427 if (constrain_) {
429 }
430 haveForces_ = false;
431}
432
433void RingPolymer::setBeads(const std::vector<VectorXd> &beads) {
434 if (static_cast<long>(beads.size()) != nBeads_) {
435 throw std::invalid_argument("path integral bead count mismatch");
436 }
437 for (long bead = 0; bead < nBeads_; ++bead) {
438 if (beads[static_cast<size_t>(bead)].size() != nDof_) {
439 throw std::invalid_argument("path integral bead has the wrong length");
440 }
441 q_[static_cast<size_t>(bead)] = beads[static_cast<size_t>(bead)];
442 }
443 if (constrain_) {
445 }
446 haveForces_ = false;
447}
448
449void RingPolymer::setMomenta(const std::vector<VectorXd> &momenta) {
450 if (static_cast<long>(momenta.size()) != nBeads_) {
451 throw std::invalid_argument("path integral bead count mismatch");
452 }
453 for (long bead = 0; bead < nBeads_; ++bead) {
454 if (momenta[static_cast<size_t>(bead)].size() != nDof_) {
455 throw std::invalid_argument(
456 "path integral momentum has the wrong length");
457 }
458 for (long a = 0; a < nDof_; ++a) {
459 p_[static_cast<size_t>(bead)][a] =
460 free_[static_cast<size_t>(a)] ? momenta[static_cast<size_t>(bead)][a]
461 : 0.0;
462 }
463 }
464}
465
466void RingPolymer::setHyperplane(const VectorXd &normal,
467 const VectorXd &origin) {
468 if (normal.size() != nDof_ || origin.size() != nDof_) {
469 throw std::invalid_argument("hyperplane vectors have the wrong length");
470 }
471 planeNormal_ = VectorXd::Zero(nDof_);
472 planeOrigin_ = origin;
473 double norm = 0.0;
474 for (long a : freeIndex_) {
475 planeNormal_[a] = normal[a];
476 norm += normal[a] * normal[a];
477 }
478 if (!(norm > 0.0)) {
479 throw std::invalid_argument("hyperplane normal has no free component");
480 }
481 planeNormal_ /= std::sqrt(norm);
482 constrain_ = true;
485}
486
488 rng_ = rng_ * 6364136223846793005ULL + 1ULL;
489 const double u1 =
490 std::max((rng_ >> 11) * (1.0 / 9007199254740992.0), 1.0e-16);
491 rng_ = rng_ * 6364136223846793005ULL + 1ULL;
492 const double u2 = (rng_ >> 11) * (1.0 / 9007199254740992.0);
493 return std::sqrt(-2.0 * std::log(u1)) * std::cos(2.0 * kPi * u2);
494}
495
497 for (long bead = 0; bead < nBeads_; ++bead) {
498 p_[static_cast<size_t>(bead)].setZero();
499 }
500 toNormal(p_, pnm_);
501 const double tSim = static_cast<double>(nBeads_) * opt_.kB * opt_.temperature;
502 for (long k = 0; k < nBeads_; ++k) {
503 const bool gleMode = opt_.thermostat == Thermostat::Piglet && k > 0;
504 if (gleMode) {
505 continue;
506 }
507 for (long a : freeIndex_) {
508 pnm_[static_cast<size_t>(k)][a] =
509 gauss() * std::sqrt(mass_[static_cast<size_t>(a)] * tSim);
510 }
511 }
512 if (opt_.thermostat == Thermostat::Piglet) {
513 for (long k = 1; k < nBeads_; ++k) {
514 ModeGle &mode = gle_[static_cast<size_t>(k - 1)];
515 const MatrixXd factor = factorCovariance(opt_.kB * mode.covariance);
516 MatrixXd noise(mode.extended.rows(), mode.extended.cols());
517 for (long r = 0; r < noise.rows(); ++r) {
518 for (long c = 0; c < noise.cols(); ++c) {
519 noise(r, c) = gauss();
520 }
521 }
522 mode.extended = factor * noise;
523 for (long col = 0; col < nFree_; ++col) {
524 const long a = freeIndex_[static_cast<size_t>(col)];
525 pnm_[static_cast<size_t>(k)][a] =
526 mode.extended(0, col) * std::sqrt(mass_[static_cast<size_t>(a)]);
527 }
528 }
529 }
531 haveForces_ = false;
532}
533
534void RingPolymer::toNormal(const std::vector<VectorXd> &src,
535 std::vector<VectorXd> &dst) const {
536 MatrixXd packed(nDof_, nBeads_);
537 for (long j = 0; j < nBeads_; ++j) {
538 packed.col(j) = src[static_cast<size_t>(j)];
539 }
540 const MatrixXd out = packed * modes_.transpose();
541 for (long k = 0; k < nBeads_; ++k) {
542 dst[static_cast<size_t>(k)] = out.col(k);
543 }
544}
545
546void RingPolymer::fromNormal(const std::vector<VectorXd> &src,
547 std::vector<VectorXd> &dst) const {
548 MatrixXd packed(nDof_, nBeads_);
549 for (long k = 0; k < nBeads_; ++k) {
550 packed.col(k) = src[static_cast<size_t>(k)];
551 }
552 const MatrixXd out = packed * modes_;
553 for (long j = 0; j < nBeads_; ++j) {
554 dst[static_cast<size_t>(j)] = out.col(j);
555 }
556}
557
558void RingPolymer::forces(Potential &pot, const double *box) {
559 double zeroBox[9] = {};
560 const double *cell = box != nullptr ? box : zeroBox;
561 std::vector<const double *> pos(static_cast<size_t>(nBeads_));
562 std::vector<const int *> nrs(static_cast<size_t>(nBeads_));
563 std::vector<double *> frc(static_cast<size_t>(nBeads_));
564 std::vector<const double *> boxes(static_cast<size_t>(nBeads_), cell);
565 for (long bead = 0; bead < nBeads_; ++bead) {
566 pos[static_cast<size_t>(bead)] = q_[static_cast<size_t>(bead)].data();
567 nrs[static_cast<size_t>(bead)] = atomicNumbers_.data();
568 frc[static_cast<size_t>(bead)] = f_[static_cast<size_t>(bead)].data();
569 }
570 std::vector<double> energies(static_cast<size_t>(nBeads_), 0.0);
571 std::vector<double> variances(static_cast<size_t>(nBeads_), 0.0);
572 pot.forceBatch(nBeads_, nAtoms_, pos.data(), nrs.data(), frc.data(),
573 energies.data(), variances.data(), boxes.data());
574 ++batches_;
575 for (long bead = 0; bead < nBeads_; ++bead) {
576 for (long a = 0; a < nDof_; ++a) {
577 if (!free_[static_cast<size_t>(a)]) {
578 f_[static_cast<size_t>(bead)][a] = 0.0;
579 }
580 }
581 }
582 haveForces_ = true;
583}
584
585VectorXd RingPolymer::centroid() const {
586 VectorXd c = VectorXd::Zero(nDof_);
587 for (long bead = 0; bead < nBeads_; ++bead) {
588 c += q_[static_cast<size_t>(bead)];
589 }
590 c /= static_cast<double>(nBeads_);
591 return c;
592}
593
595 std::vector<VectorXd> pnm(static_cast<size_t>(nBeads_),
596 VectorXd::Zero(nDof_));
597 toNormal(p_, pnm);
598 VectorXd v = VectorXd::Zero(nDof_);
599 const double scale = std::sqrt(static_cast<double>(nBeads_));
600 for (long a = 0; a < nDof_; ++a) {
601 const double m = mass_[static_cast<size_t>(a)];
602 if (m > 0.0) {
603 v[a] = pnm[0][a] / (scale * m);
604 }
605 }
606 return v;
607}
608
610 double k = 0.5 * static_cast<double>(nFree_) * opt_.kB * opt_.temperature;
611 if (!haveForces_) {
612 return k;
613 }
614 const VectorXd c = centroid();
615 double virial = 0.0;
616 for (long bead = 0; bead < nBeads_; ++bead) {
617 for (long a : freeIndex_) {
618 virial += (q_[static_cast<size_t>(bead)][a] - c[a]) *
619 f_[static_cast<size_t>(bead)][a];
620 }
621 }
622 k += -0.5 / static_cast<double>(nBeads_) * virial;
623 return k;
624}
625
627 if (!constrain_ || recorded_ < 1) {
628 return 0.0;
629 }
630 return forceSum_ / static_cast<double>(recorded_);
631}
632
634 recorded_ = 0;
635 kineticSum_ = 0.0;
636 forceSum_ = 0.0;
637}
638
640 toNormal(p_, pnm_);
641 const double tSim = static_cast<double>(nBeads_) * opt_.kB * opt_.temperature;
642 auto langevin = [&](long k, double tau) {
643 const double damp = std::exp(-h / tau);
644 const double noise = std::sqrt(tSim * (1.0 - damp * damp));
645 for (long a : freeIndex_) {
646 const double sm = std::sqrt(mass_[static_cast<size_t>(a)]);
647 double pms = pnm_[static_cast<size_t>(k)][a] / sm;
648 pms = damp * pms + noise * gauss();
649 pnm_[static_cast<size_t>(k)][a] = pms * sm;
650 }
651 };
652 langevin(0, opt_.pileTau);
653 for (long k = 1; k < nBeads_; ++k) {
654 if (opt_.thermostat == Thermostat::Piglet) {
655 ModeGle &mode = gle_[static_cast<size_t>(k - 1)];
656 for (long col = 0; col < nFree_; ++col) {
657 const long a = freeIndex_[static_cast<size_t>(col)];
658 mode.extended(0, col) = pnm_[static_cast<size_t>(k)][a] /
659 std::sqrt(mass_[static_cast<size_t>(a)]);
660 }
661 MatrixXd noise(mode.extended.rows(), mode.extended.cols());
662 for (long r = 0; r < noise.rows(); ++r) {
663 for (long c = 0; c < noise.cols(); ++c) {
664 noise(r, c) = gauss();
665 }
666 }
667 mode.extended = mode.propagate * mode.extended + mode.noise * noise;
668 for (long col = 0; col < nFree_; ++col) {
669 const long a = freeIndex_[static_cast<size_t>(col)];
670 pnm_[static_cast<size_t>(k)][a] =
671 mode.extended(0, col) * std::sqrt(mass_[static_cast<size_t>(a)]);
672 }
673 } else {
674 const double tau = 1.0 / (2.0 * opt_.pileScale * omegaK_[k]);
675 langevin(k, tau);
676 }
677 }
679}
680
681void RingPolymer::kick(double h, bool dropParallel) {
682 VectorXd removal = VectorXd::Zero(nDof_);
683 if (dropParallel && constrain_) {
684 VectorXd fc = VectorXd::Zero(nDof_);
685 for (long bead = 0; bead < nBeads_; ++bead) {
686 fc += f_[static_cast<size_t>(bead)];
687 }
688 fc /= static_cast<double>(nBeads_);
689 double along = 0.0;
690 for (long a : freeIndex_) {
691 along += planeNormal_[a] * fc[a];
692 }
693 removal = along * planeNormal_;
694 }
695 for (long bead = 0; bead < nBeads_; ++bead) {
696 for (long a : freeIndex_) {
697 p_[static_cast<size_t>(bead)][a] +=
698 (f_[static_cast<size_t>(bead)][a] - removal[a]) * h;
699 }
700 }
701}
702
704 toNormal(q_, qnm_);
705 toNormal(p_, pnm_);
706 for (long a : freeIndex_) {
707 const double m = mass_[static_cast<size_t>(a)];
708 qnm_[0][a] += pnm_[0][a] / m * h;
709 for (long k = 1; k < nBeads_; ++k) {
710 const double omega = omegaK_[k];
711 const double c = std::cos(omega * h);
712 const double s = std::sin(omega * h);
713 const double pk = pnm_[static_cast<size_t>(k)][a];
714 const double qk = qnm_[static_cast<size_t>(k)][a];
715 pnm_[static_cast<size_t>(k)][a] = c * pk - m * omega * s * qk;
716 qnm_[static_cast<size_t>(k)][a] = c * qk + s * pk / (omega * m);
717 }
718 }
721}
722
723// The centroid mode is the bead sum over sqrt(P), so a change d of that
724// mode moves every bead by d / sqrt(P) and leaves the other modes alone.
726 if (!constrain_) {
727 return;
728 }
729 const VectorXd c = centroid();
730 double sigma = 0.0;
731 for (long a : freeIndex_) {
732 sigma += planeNormal_[a] * (c[a] - planeOrigin_[a]);
733 }
734 for (long bead = 0; bead < nBeads_; ++bead) {
735 for (long a : freeIndex_) {
736 q_[static_cast<size_t>(bead)][a] -= sigma * planeNormal_[a];
737 }
738 }
739}
740
742 if (!constrain_) {
743 return;
744 }
745 VectorXd sum = VectorXd::Zero(nDof_);
746 for (long bead = 0; bead < nBeads_; ++bead) {
747 sum += p_[static_cast<size_t>(bead)];
748 }
749 // p_0 = sum / sqrt(P); remove lambda n from p_0.
750 const double scale = std::sqrt(static_cast<double>(nBeads_));
751 double num = 0.0;
752 double den = 0.0;
753 for (long a : freeIndex_) {
754 const double m = mass_[static_cast<size_t>(a)];
755 num += planeNormal_[a] * (sum[a] / scale) / m;
756 den += planeNormal_[a] * planeNormal_[a] / m;
757 }
758 if (den > 0.0) {
759 const double shift = num / den / scale;
760 for (long bead = 0; bead < nBeads_; ++bead) {
761 for (long a : freeIndex_) {
762 p_[static_cast<size_t>(bead)][a] -= shift * planeNormal_[a];
763 }
764 }
765 }
766}
767
768void RingPolymer::step(Potential &pot, const double *box, bool record) {
769 const double half = 0.5 * opt_.dt;
770 thermostat(half);
772 forces(pot, box);
773 kick(half, true);
775 propagate(half);
776 propagate(half);
778 forces(pot, box);
779 if (record) {
781 if (constrain_) {
782 VectorXd fc = VectorXd::Zero(nDof_);
783 for (long bead = 0; bead < nBeads_; ++bead) {
784 fc += f_[static_cast<size_t>(bead)];
785 }
786 fc /= static_cast<double>(nBeads_);
787 double along = 0.0;
788 for (long a : freeIndex_) {
789 along += planeNormal_[a] * fc[a];
790 }
791 forceSum_ += along;
792 }
793 ++recorded_;
794 }
795 kick(half, true);
797 thermostat(half);
799}
800
801void RingPolymer::nveStep(Potential &pot, const double *box) {
802 if (constrain_) {
803 throw std::logic_error("path integral NVE step with a hyperplane set");
804 }
805 const double half = 0.5 * opt_.dt;
806 if (!haveForces_) {
807 forces(pot, box);
808 }
809 kick(half, false);
810 propagate(opt_.dt);
811 forces(pot, box);
812 kick(half, false);
813}
814
816 long equilibration, long production) {
817 if (equilibration < 0 || production < 1) {
818 throw std::invalid_argument("path integral sample length");
819 }
820 for (long step = 0; step < equilibration; ++step) {
821 this->step(pot, box, false);
822 }
824 const long batchesBefore = batches_;
825 for (long step = 0; step < production; ++step) {
826 this->step(pot, box, true);
827 }
828 Sample out;
829 out.kineticCv = kineticSum_ / static_cast<double>(recorded_);
830 out.meanForce = meanForce();
831 out.batches = batches_ - batchesBefore;
832 return out;
833}
834
835} // namespace eonc::pathintegral
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, eOnStorageOrder > MatrixXd
Definition Eigen.h:33
std::vector< int > atomicNumbers_
void setAllBeads(const double *q)
std::vector< long > freeIndex_
std::vector< VectorXd > pnm_
std::vector< ModeGle > gle_
std::vector< double > mass_
std::vector< VectorXd > f_
void setMomenta(const std::vector< VectorXd > &momenta)
One momentum vector of length 3 * nAtoms per bead; fixed coordinates are zeroed.
void nveStep(Potential &pot, const double *box)
Thermostat-free RPMD step: velocity Verlet with the free ring propagated exactly in normal modes.
RingPolymer(long nAtoms, std::vector< double > masses, std::vector< int > atomicNumbers, std::vector< char > free, Options opt)
Sample sample(Potential &pot, const double *box, long equilibration, long production)
void toNormal(const std::vector< VectorXd > &src, std::vector< VectorXd > &dst) const
void setBeads(const std::vector< VectorXd > &beads)
One position vector of length 3 * nAtoms per bead.
std::vector< VectorXd > p_
void forces(Potential &pot, const double *box)
void setHyperplane(const VectorXd &normal, const VectorXd &origin)
Hold n · (q_centroid - origin) = 0.
std::vector< VectorXd > q_
void kick(double h, bool dropParallel)
void step(Potential &pot, const double *box, bool record)
const std::vector< VectorXd > & momenta() const
void fromNormal(const std::vector< VectorXd > &src, std::vector< VectorXd > &dst) const
const std::vector< VectorXd > & beads() const
std::vector< VectorXd > qnm_
MatrixXd normalModeMatrix(long nBeads)
Orthogonal bead-to-normal-mode matrix. Row k is mode k.
VectorXd trotterEigenvalues(long nBeads)
Dimensionless free-ring eigenvalues, mode 0 equal to 0.
void requireTrotterSprings(const std::string &springs, const char *use)
Economised springs and a normal-mode GLE are refused.
VectorXd ecoEigenvalues(long nBeads, double xmax)
double meanForce
Average of n · f_centroid.