26MatrixXd trapezoidWeights(
const std::vector<Plane> &planes) {
27 const long n =
static_cast<long>(planes.size());
29 for (
long j = 1; j < n; ++j) {
30 w.row(j) = w.row(j - 1);
32 planes[
static_cast<size_t>(j)].s - planes[
static_cast<size_t>(j - 1)].s;
33 w(j, j - 1) += 0.5 * h;
39VectorXd forceErrors(
const std::vector<Plane> &planes) {
40 VectorXd e(
static_cast<long>(planes.size()));
41 for (
size_t i = 0; i < planes.size(); ++i) {
42 e(
static_cast<long>(i)) = planes[i].meanForceError;
47double propagated(
const VectorXd &gradient,
const VectorXd &errors) {
48 return std::sqrt(gradient.cwiseProduct(errors).squaredNorm());
60 const long dof = 3 * c.
atoms;
62 static_cast<long>(c.
free.size()) != dof || c.
reference.size() != dof ||
64 throw std::invalid_argument(
"piqtst: coordinate sizes do not match");
66 Axes out{VectorXd::Zero(dof), VectorXd::Zero(dof)};
68 for (
long i = 0; i < dof; ++i) {
69 if (!c.
free[
static_cast<size_t>(i)]) {
72 const double sm = std::sqrt(c.
masses[
static_cast<size_t>(i / 3)]);
77 if (std::abs(nn - 1.0) > 1e-8) {
78 throw std::invalid_argument(
79 "piqtst: the direction must be a unit vector on the free coordinates");
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;
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);
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");
113 for (
size_t j = 1; j < o.
planes.size(); ++j) {
115 throw std::invalid_argument(
"piqtst: plane positions must ascend");
120 throw std::invalid_argument(
121 "piqtst: sampling needs at least as many steps as blocks, and two "
124 const double aNorm = a.norm();
125 auto seedAt = [&](
double s) -> VectorXd {
127 VectorXd x = o.
seed(s);
128 if (x.size() != dof) {
129 throw std::invalid_argument(
"piqtst: a seed has the wrong length");
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);
148 const VectorXd shift = target - ring.
centroid();
149 std::vector<VectorXd> moved = ring.
beads();
150 for (
auto &q : moved) {
157 const long batches0 = ring.
batches();
163 plane.
centroid = VectorXd::Zero(dof);
164 VectorXd spread2 = VectorXd::Zero(dof);
166 double blockSum = 0.0;
167 std::vector<double> blockMeans;
168 for (
long step = 0; step < o.
production; ++step) {
174 if ((step + 1) % blockSize == 0 &&
175 static_cast<long>(blockMeans.size()) < o.
blocks) {
176 blockMeans.push_back(blockSum /
static_cast<double>(blockSize));
179 const VectorXd centroid = ring.
centroid();
181 for (
const auto &q : ring.
beads()) {
182 spread2 += (q - centroid).cwiseAbs2();
185 const double steps =
static_cast<double>(o.
production);
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));
192 const double mean = sum / steps;
193 double blockMean = 0.0;
194 for (
const double m : blockMeans) {
197 const double nb =
static_cast<double>(blockMeans.size());
200 for (
const double m : blockMeans) {
201 var += (m - blockMean) * (m - blockMean);
203 var /= nb * (nb - 1.0);
207 out.push_back(std::move(plane));
213Rate rate(
const std::vector<Plane> &planes,
double beta) {
214 const long n =
static_cast<long>(planes.size());
216 throw std::invalid_argument(
"piqtst: the rate needs two planes");
219 throw std::invalid_argument(
"piqtst: the rate needs a positive beta");
221 const MatrixXd w = trapezoidWeights(planes);
222 const VectorXd errors = forceErrors(planes);
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;
232 const long top = n - 1;
233 const double fTop = planes[
static_cast<size_t>(top)].freeEnergy;
236 propagated((w.row(top) - w.row(r.
reactant)).transpose(), errors);
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
246 const double right = j + 1 < n ? planes[
static_cast<size_t>(j + 1)].s -
247 planes[
static_cast<size_t>(j)].s
250 0.5 * (left + right) *
251 std::exp(-beta * (planes[
static_cast<size_t>(j)].freeEnergy - fMin));
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);
265 const long dof = 3 * c.
atoms;
266 const Axes ax = axes(c);
269 throw std::invalid_argument(
270 "piqtst: recrossing needs two parents, one child, a positive "
271 "spacing and four steps");
273 const VectorXd origin = c.
reference + o.
s * ax.b;
274 VectorXd start = origin;
277 if (start.size() != dof) {
278 throw std::invalid_argument(
"piqtst: a seed has the wrong length");
295 childOptions.
seed = o.
ring.
seed + 0x9E3779B97F4A7C15ULL;
299 const long n = o.
steps + 1;
302 std::vector<VectorXd> numerator(
static_cast<size_t>(o.
parents),
304 std::vector<double> denominator(
static_cast<size_t>(o.
parents), 0.0);
306 for (
long p = 0; p < o.
parents; ++p) {
307 for (
long step = 0; step < o.
spacing; ++step) {
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) {
316 std::vector<VectorXd> reversed = child.
momenta();
318 for (
int sign = 0; sign < 2; ++sign) {
320 for (
auto &v : reversed) {
326 const double sdot = sign == 0 ? forward : -forward;
327 const double flux = sdot > 0.0 ? sdot : 0.0;
330 for (
long i = 1; i < n; ++i) {
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)];
348 if (!(totalDen > 0.0)) {
349 throw std::runtime_error(
"piqtst: no child left the plane forward");
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;
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);
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)];
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)];
381 for (
const double v : leaveOut) {
382 var += (v - meanLeave) * (v - meanLeave);
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, eOnStorageOrder > MatrixXd
VectorXd centroidVelocity() const
void setAllBeads(const double *q)
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.
void setBeads(const std::vector< VectorXd > &beads)
One position vector of length 3 * nAtoms per bead.
VectorXd centroid() const
void setHyperplane(const VectorXd &normal, const VectorXd &origin)
Hold n · (q_centroid - origin) = 0.
void step(Potential &pot, const double *box, bool record)
const std::vector< VectorXd > & momenta() const
const std::vector< VectorXd > & beads() const
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.
void integrate(std::vector< Plane > &planes)
Trapezoid integral of the mean forces, F(s_0) = 0, with errors from independent planes.
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,...
std::string gleFile
Normal-mode GLE matrices.
std::vector< int > numbers
std::vector< double > masses
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 .
long reactant
Index of the plane with the lowest F, the reactant.
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...