45constexpr double kPi = std::numbers::pi;
47std::string lower(std::string s) {
49 c =
static_cast<char>(std::tolower(
static_cast<unsigned char>(c)));
54double ecoResponse(
double x) {
55 const double z = 0.5 * x;
57 const double z2 = z * z;
59 1.0 / 3.0 - z2 / 45.0 + 2.0 * z2 * z2 / 945.0 - z2 * z2 * z2 / 4725.0;
62 return (x * x) / (z / std::tanh(z) - 1.0);
65VectorXd ecoFit(
long nBeads,
double xmax) {
66 const long nfree = nBeads / 2;
68 mult.setConstant(2.0);
69 if (nBeads % 2 == 0) {
70 mult[nfree - 1] = 1.0;
72 const long m = std::max(std::lround(10.0 * xmax), 100L);
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]);
80 auto objective = [&](
const VectorXd &y,
double &s, VectorXd &g,
MatrixXd &h) {
86 for (
long i = 0; i < m; ++i) {
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);
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]);
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);
105 g /=
static_cast<double>(m);
106 h = dg.transpose() * dg;
107 h += (d2.transpose() * r).asDiagonal();
108 h /=
static_cast<double>(m);
112 for (
long k = 0; k < nfree; ++k) {
113 y[k] = 2.0 * kPi *
static_cast<double>(k + 1);
118 objective(y, s, g, h);
119 bool exhausted =
true;
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");
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);
136 const VectorXd dy = vec * scaled;
137 const double previous = s;
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];
150 objective(z, sn, gn, hn);
166 if (previous - s <= 1e-12 * previous) {
172 throw std::runtime_error(
"economised spring fit did not converge");
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");
183 return es.eigenvectors() *
184 es.eigenvalues().cwiseMax(0.0).cwiseSqrt().asDiagonal();
190 std::vector<MatrixXd> drift;
191 std::vector<MatrixXd> covariance;
194GleFile readGle(
const std::string &path) {
195 std::ifstream in(path);
197 throw std::runtime_error(
"cannot read GLE matrices from " + path);
199 std::vector<double> nums;
201 while (std::getline(in, line)) {
202 const auto hash = line.find(
'#');
203 if (hash != std::string::npos) {
206 std::istringstream ss(line);
212 if (nums.size() < 2) {
213 throw std::runtime_error(
"GLE matrix file " + path +
" is empty");
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");
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");
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) {
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++)];
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++)];
244 out.drift[
static_cast<size_t>(mode)] = std::move(a);
245 out.covariance[
static_cast<size_t>(mode)] = std::move(c);
253 const std::string s = lower(springs);
254 if (s ==
"eco" || s ==
"economised") {
255 throw std::invalid_argument(
257 " requires Trotter springs; economised springs are refused");
263 throw std::invalid_argument(
"bead count must be positive");
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));
275 throw std::invalid_argument(
"bead count must be positive");
277 VectorXd eva = VectorXd::Zero(nBeads);
282 throw std::invalid_argument(
283 "economised springs need a positive maximum frequency");
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);
295 throw std::invalid_argument(
"bead count must be positive");
297 MatrixXd b = MatrixXd::Zero(nBeads, nBeads);
298 const double n =
static_cast<double>(nBeads);
299 for (
long j = 0; j < nBeads; ++j) {
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);
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);
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;
321 std::vector<int> atomicNumbers, std::vector<char> free,
323 :
opt_(std::move(opt)),
328 free_(std::move(free)),
331 throw std::invalid_argument(
"path integral needs atoms and beads");
333 if (
static_cast<long>(masses.size()) !=
nAtoms_ ||
336 throw std::invalid_argument(
"path integral mass, number or mask size");
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");
344 throw std::invalid_argument(
345 "economised springs cannot be combined with a normal-mode GLE");
347 mass_.assign(
static_cast<size_t>(
nDof_), 0.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");
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)]) {
363 throw std::invalid_argument(
"path integral has no free coordinate");
366 const double omegan =
390 if (
opt_.gleFile.empty()) {
391 throw std::invalid_argument(
"normal-mode GLE needs a matrix file");
393 const GleFile file = readGle(
opt_.gleFile);
397 }
else if (file.nModes !=
nBeads_ - 1) {
398 throw std::invalid_argument(
399 "GLE matrix count must be the bead count or one less");
401 const double h = 0.5 *
opt_.dt;
403 for (
long k = 1; k <
nBeads_; ++k) {
404 const long src = first + (k - 1);
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();
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);
420 throw std::invalid_argument(
"path integral positions are missing");
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];
435 throw std::invalid_argument(
"path integral bead count mismatch");
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");
441 q_[
static_cast<size_t>(bead)] =
beads[
static_cast<size_t>(bead)];
451 throw std::invalid_argument(
"path integral bead count mismatch");
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");
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]
467 const VectorXd &origin) {
468 if (normal.size() !=
nDof_ || origin.size() !=
nDof_) {
469 throw std::invalid_argument(
"hyperplane vectors have the wrong length");
476 norm += normal[a] * normal[a];
479 throw std::invalid_argument(
"hyperplane normal has no free component");
488 rng_ =
rng_ * 6364136223846793005ULL + 1ULL;
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);
497 for (
long bead = 0; bead <
nBeads_; ++bead) {
498 p_[
static_cast<size_t>(bead)].setZero();
501 const double tSim =
static_cast<double>(
nBeads_) *
opt_.kB *
opt_.temperature;
502 for (
long k = 0; k <
nBeads_; ++k) {
508 pnm_[
static_cast<size_t>(k)][a] =
509 gauss() * std::sqrt(
mass_[
static_cast<size_t>(a)] * tSim);
513 for (
long k = 1; k <
nBeads_; ++k) {
517 for (
long r = 0; r < noise.rows(); ++r) {
518 for (
long c = 0; c < noise.cols(); ++c) {
519 noise(r, c) =
gauss();
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)]);
535 std::vector<VectorXd> &dst)
const {
537 for (
long j = 0; j <
nBeads_; ++j) {
538 packed.col(j) = src[
static_cast<size_t>(j)];
541 for (
long k = 0; k <
nBeads_; ++k) {
542 dst[
static_cast<size_t>(k)] = out.col(k);
547 std::vector<VectorXd> &dst)
const {
549 for (
long k = 0; k <
nBeads_; ++k) {
550 packed.col(k) = src[
static_cast<size_t>(k)];
553 for (
long j = 0; j <
nBeads_; ++j) {
554 dst[
static_cast<size_t>(j)] = out.col(j);
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();
568 frc[
static_cast<size_t>(bead)] =
f_[
static_cast<size_t>(bead)].data();
570 std::vector<double> energies(
static_cast<size_t>(
nBeads_), 0.0);
571 std::vector<double> variances(
static_cast<size_t>(
nBeads_), 0.0);
573 energies.data(), variances.data(), boxes.data());
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;
586 VectorXd c = VectorXd::Zero(
nDof_);
587 for (
long bead = 0; bead <
nBeads_; ++bead) {
588 c +=
q_[
static_cast<size_t>(bead)];
590 c /=
static_cast<double>(
nBeads_);
595 std::vector<VectorXd> pnm(
static_cast<size_t>(
nBeads_),
596 VectorXd::Zero(
nDof_));
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)];
603 v[a] = pnm[0][a] / (scale * m);
610 double k = 0.5 *
static_cast<double>(
nFree_) *
opt_.kB *
opt_.temperature;
616 for (
long bead = 0; bead <
nBeads_; ++bead) {
618 virial += (
q_[
static_cast<size_t>(bead)][a] - c[a]) *
619 f_[
static_cast<size_t>(bead)][a];
622 k += -0.5 /
static_cast<double>(
nBeads_) * virial;
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));
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;
652 langevin(0,
opt_.pileTau);
653 for (
long k = 1; k <
nBeads_; ++k) {
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)]);
662 for (
long r = 0; r < noise.rows(); ++r) {
663 for (
long c = 0; c < noise.cols(); ++c) {
664 noise(r, c) =
gauss();
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)]);
674 const double tau = 1.0 / (2.0 *
opt_.pileScale *
omegaK_[k]);
682 VectorXd removal = VectorXd::Zero(
nDof_);
684 VectorXd fc = VectorXd::Zero(
nDof_);
685 for (
long bead = 0; bead <
nBeads_; ++bead) {
686 fc +=
f_[
static_cast<size_t>(bead)];
688 fc /=
static_cast<double>(
nBeads_);
695 for (
long bead = 0; bead <
nBeads_; ++bead) {
697 p_[
static_cast<size_t>(bead)][a] +=
698 (
f_[
static_cast<size_t>(bead)][a] - removal[a]) * h;
707 const double m =
mass_[
static_cast<size_t>(a)];
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);
734 for (
long bead = 0; bead <
nBeads_; ++bead) {
745 VectorXd sum = VectorXd::Zero(
nDof_);
746 for (
long bead = 0; bead <
nBeads_; ++bead) {
747 sum +=
p_[
static_cast<size_t>(bead)];
750 const double scale = std::sqrt(
static_cast<double>(
nBeads_));
754 const double m =
mass_[
static_cast<size_t>(a)];
759 const double shift = num / den / scale;
760 for (
long bead = 0; bead <
nBeads_; ++bead) {
769 const double half = 0.5 *
opt_.dt;
782 VectorXd fc = VectorXd::Zero(
nDof_);
783 for (
long bead = 0; bead <
nBeads_; ++bead) {
784 fc +=
f_[
static_cast<size_t>(bead)];
786 fc /=
static_cast<double>(
nBeads_);
803 throw std::logic_error(
"path integral NVE step with a hyperplane set");
805 const double half = 0.5 *
opt_.dt;
816 long equilibration,
long production) {
817 if (equilibration < 0 || production < 1) {
818 throw std::invalid_argument(
"path integral sample length");
821 this->
step(pot, box,
false);
824 const long batchesBefore =
batches_;
826 this->
step(pot, box,
true);