28 {
29 std::vector<std::shared_ptr<Matter>> mdSnapshots;
30 std::vector<double> mdTimes;
31 QUILL_LOG_DEBUG(
log,
"Starting dynamics NEB saddle search");
32
33 if (std::filesystem::exists("masses.dat")) {
34 QUILL_LOG_DEBUG(
log,
"Found mass weights file");
35 Eigen::VectorXd masses =
38 QUILL_LOG_DEBUG(
log,
"Applied mass weights");
39 } else {
40 QUILL_LOG_DEBUG(
log,
"No mass weights file found");
41 }
42
44 QUILL_LOG_DEBUG(
45 log,
"Initializing velocities from Maxwell-Boltzmann distribution");
46 dyn.setTemperature(
params.saddle_search_options().dynamics.temperature);
47 dyn.setThermalVelocity();
48
49 const double dt =
params.dynamics_options().time_step;
50 if (!(dt > 0.0)) {
51 throw std::invalid_argument(
52 "DynamicsSaddleSearch: time_step must be positive");
53 }
54 int dephaseSteps = static_cast<int>(
55 std::floor(
params.parallel_replica_options().dephase_time / dt + 0.5));
56
57 while (true) {
58
59 QUILL_LOG_DEBUG(
log,
"Dephasing: {} steps", dephaseSteps);
60
62 dyn.setThermalVelocity();
63
64
65 for (int step = 1; step <= dephaseSteps; step++) {
66 dyn.oneStep(step);
67 }
68
69
72 min.relax();
73
75 QUILL_LOG_DEBUG(
log,
"Dephasing successful");
76 break;
77 } else {
78 QUILL_LOG_DEBUG(
log,
"Transition occured during dephasing; Restarting");
79 dephaseSteps /= 2;
80 if (dephaseSteps < 1)
81 dephaseSteps = 1;
82 }
83 }
84
86
87
88 struct BiasGuard {
89 Matter *matter{nullptr};
90 ~BiasGuard() {
91 if (matter == nullptr) {
92 return;
93 }
94 matter->setBiasPotential(nullptr);
95 matter->setBiasForces(AtomMatrix::Zero(matter->numberOfAtoms(), 3));
96 }
97 } biasGuard;
98 if (
params.hyperdynamics_options().bias_potential ==
100 QUILL_LOG_DEBUG(
log,
"Initializing Bond Boost");
101 bondBoost.initialize();
102 saddle->setBiasPotential(&bondBoost);
103 biasGuard.matter =
saddle.get();
104 }
105
106 int checkInterval = static_cast<int>(
107 params.saddle_search_options().dynamics.state_check_interval /
108 params.dynamics_options().time_step +
109 0.5);
110
111
112 if (checkInterval < 1) {
113 checkInterval = 1;
114 }
115 int recordInterval =
116 static_cast<int>(
params.saddle_search_options().dynamics.record_interval /
117 params.dynamics_options().time_step +
118 0.5);
119
120 if (
params.debug_options().write_movies) {
122 QUILL_LOG_WARNING(
log,
"Failed to write dynamics movie header");
123 }
124 }
125
126 for (
int step = 1; step <=
params.dynamics_options().steps; step++) {
127 if (
params.hyperdynamics_options().bias_potential ==
129
130
131 bondBoost.advance();
132 }
133 dyn.oneStep(step);
134
135 if (recordInterval != 0 && step % recordInterval == 0) {
136 QUILL_LOG_DEBUG(
log,
"recording configuration at step {} time {:.3f}",
137 step,
138 step *
params.dynamics_options().time_step *
139 params.constants().timeUnit);
140
141 auto snapshot = std::make_shared<Matter>(*
saddle);
142 mdSnapshots.push_back(snapshot);
143 mdTimes.push_back(step *
params.dynamics_options().time_step);
144 }
145
146 if (
params.debug_options().write_movies) {
148 QUILL_LOG_WARNING(
log,
"Failed to append dynamics movie frame");
149 }
150 }
151
152 if (step % checkInterval == 0) {
153 QUILL_LOG_DEBUG(
log,
"Minimizing trajectory, step {}", step);
154
157
159 QUILL_LOG_DEBUG(
log,
"Found new state");
160
161
162 int image = -1;
163 if (!mdSnapshots.empty()) {
165 }
166 if (image < 0 || static_cast<size_t>(image) >= mdSnapshots.size() ||
167 static_cast<size_t>(image) >= mdTimes.size()) {
169 "No MD snapshots; using the detecting configuration");
170 time = step *
params.dynamics_options().time_step;
171 } else {
172 *
saddle = *mdSnapshots[
static_cast<size_t>(image)];
173 QUILL_LOG_DEBUG(
log,
"Found transition at snapshot image {}", image);
174 for (int ii = 0; ii < static_cast<int>(mdTimes.size()); ii++) {
175 QUILL_LOG_DEBUG(
log,
"MDTimes[{}] = {:.3f}", ii,
176 mdTimes[ii] *
params.constants().timeUnit);
177 }
178
179 time = mdTimes[
static_cast<size_t>(image)] -
180 params.saddle_search_options().dynamics.record_interval / 2.0;
181
182
183 if (image > 0 &&
time < mdTimes[
static_cast<size_t>(image - 1)]) {
184 time = mdTimes[
static_cast<size_t>(image - 1)];
185 }
186 }
187 QUILL_LOG_DEBUG(
log,
"Transition time {:.2f} fs",
189
191
192 if (!
params.saddle_search_options().dynamics.linear_interpolation) {
193 QUILL_LOG_DEBUG(
194 log,
"Interpolating initial band through MD transition state");
199 QUILL_LOG_DEBUG(
log,
"Initial band saved to neb_initial_band.con");
201 neb.path[0]->matter2con("neb_initial_band.con", false))) {
202 QUILL_LOG_WARNING(
log,
"Failed to write neb_initial_band.con");
203 }
204 int mid = neb.numImages / 2 + 1;
205 for (int img = 1; img <= neb.numImages; img++) {
206 if (img < mid) {
207 double frac = static_cast<double>(img) / static_cast<double>(mid);
208 neb.path[img]->setPositions(
reactant->getPositions() +
209 frac * reactantToSaddle);
210 } else if (img > mid) {
211 double frac = static_cast<double>(img - mid) /
212 static_cast<double>(neb.numImages - mid + 1);
213 neb.path[img]->setPositions(
saddle->getPositions() +
214 frac * saddleToProduct);
215 } else {
216 neb.path[img]->setPositions(
saddle->getPositions());
217 }
219 neb.path[img]->matter2con("neb_initial_band.con", true))) {
220 QUILL_LOG_WARNING(
log,
"Failed to append neb_initial_band frame");
221 }
222 }
224 "neb_initial_band.con", true))) {
225 QUILL_LOG_WARNING(
log,
226 "Failed to append neb_initial_band endpoint");
227 }
228 } else {
229 QUILL_LOG_DEBUG(
230 log,
"Linear interpolation between minima used for initial band");
232 neb.path[0]->matter2con("neb_initial_band.con", false))) {
233 QUILL_LOG_WARNING(
log,
"Failed to write neb_initial_band.con");
234 }
235 for (int j = 1; j <= neb.numImages + 1; j++) {
237 neb.path[j]->matter2con("neb_initial_band.con", true))) {
238 QUILL_LOG_WARNING(
log,
"Failed to append neb_initial_band frame");
239 }
240 }
241 }
242
244 if (
params.neb_options().max_iterations > 0) {
245 auto minModeMethod =
247
248 neb.compute();
249 neb.printImageData(true);
250 int extremumImage = -1;
251 int jExt = 0;
252 for (jExt = 0; jExt < neb.numExtrema; jExt++) {
253 if (neb.extremumCurvature[jExt] <
254 params.saddle_search_options().dynamics.max_init_curvature) {
255 extremumImage =
256 static_cast<int>(std::floor(neb.extremumPosition[jExt]));
257 *
saddle = *neb.path[extremumImage];
258 double interpDist = neb.extremumPosition[jExt] -
259 static_cast<double>(extremumImage);
261 saddle->pbc(neb.path[extremumImage + 1]->getPositions() -
262 neb.path[extremumImage]->getPositions());
263 saddle->setPositions(interpDist * bandDir +
265 mode =
saddle->pbc(neb.path[extremumImage + 1]->getPositions() -
267 eonc::safemath::safe_normalize_inplace(mode);
270 QUILL_LOG_DEBUG(
log,
"extrema #{} has eigenvalue {:.8f}",
271 jExt + 1, ev);
272
273 if (ev < 0) {
274 QUILL_LOG_DEBUG(
275 log,
"chose image {} (extrema #{}) as extremum image",
276 extremumImage, jExt + 1);
277 break;
278 } else {
279 extremumImage = -1;
280 }
281 }
282 }
283
284 if (extremumImage != -1) {
285 *
saddle = *neb.path[extremumImage];
286 double interpDist =
287 neb.extremumPosition[jExt] - static_cast<double>(extremumImage);
288 QUILL_LOG_DEBUG(
log,
"interpDistance {}", interpDist);
290 saddle->pbc(neb.path[extremumImage + 1]->getPositions() -
291 neb.path[extremumImage]->getPositions());
292 saddle->setPositions(interpDist * bandDir +
saddle->getPositions());
293 mode =
saddle->pbc(neb.path[extremumImage + 1]->getPositions() -
295 eonc::safemath::safe_normalize_inplace(mode);
296 } else {
297 QUILL_LOG_DEBUG(
298 log,
"no maxima found, using max energy non-endpoint image");
299 double maxEnergy = -std::numeric_limits<double>::infinity();
300 for (int img = 1; img <= neb.numImages; img++) {
301 double U = neb.path[img]->getPotentialEnergy();
302 if (U > maxEnergy) {
303 maxEnergy = U;
305 mode =
saddle->pbc(neb.path[img + 1]->getPositions() -
307 eonc::safemath::safe_normalize_inplace(mode);
308 }
309 }
310 if (maxEnergy <= reactant->getPotentialEnergy()) {
311 QUILL_LOG_DEBUG(
log,
"warning: no barrier found");
313 }
314 }
315 } else {
316
317
318
319 neb.maxEnergyImage = neb.numImages / 2 + 1;
320 const int img = static_cast<int>(neb.maxEnergyImage);
321 const int last = static_cast<int>(neb.path.size()) - 1;
322 const int from = std::clamp(img, 0, last);
323 const int to = std::clamp(img + 1, 0, last);
324 mode =
saddle->pbc(neb.path[to]->getPositions() -
325 neb.path[from]->getPositions());
326 if (mode.norm() > 0.0) {
327 mode.normalize();
328 } else {
329 mode = AtomMatrix::Zero(
saddle->numberOfAtoms(), 3);
330 if (mode.rows() > 0) {
331 mode(0, 0) = 1.0;
332 }
333 }
334 }
335
336 QUILL_LOG_DEBUG(
337 log,
"Initial saddle guess saved to saddle_initial_guess.con");
339 QUILL_LOG_WARNING(
log,
"Failed to write saddle_initial_guess.con");
340 }
341 MinModeSaddleSearch search = MinModeSaddleSearch(
343 int minModeStatus = search.run();
344
346 QUILL_LOG_DEBUG(
log,
"error in min mode saddle search");
347 return minModeStatus;
348 }
349
353
354 double barrier =
356 QUILL_LOG_DEBUG(
log,
"found barrier of {:.3f}", barrier);
357 mdSnapshots.clear();
358 mdTimes.clear();
360 } else {
361 QUILL_LOG_DEBUG(
log,
"Still in original state");
362 mdTimes.clear();
363 mdSnapshots.clear();
364 }
365 }
366 }
367
368 mdSnapshots.clear();
369 time =
params.dynamics_options().steps *
params.dynamics_options().time_step;
371}
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
int refineTransition(const std::vector< std::shared_ptr< Matter > > &snapshots, std::shared_ptr< Matter > product)
Binary search through MD snapshots to find the transition point.
static const char BOND_BOOST[]
@ STATUS_BAD_MD_TRAJECTORY_TOO_SHORT
VectorXd loadMasses(std::string filename, int nAtoms)
constexpr bool io_ok(IoStatus s) noexcept
void eigenmodeCompute(LowestEigenmode &s, std::shared_ptr< Matter > matter, AtomMatrix direction)
std::shared_ptr< LowestEigenmode > buildEigenmodeStrategy(std::shared_ptr< Matter > matter, const Parameters ¶ms, std::shared_ptr< Potential > pot)
double eigenmodeGetEigenvalue(LowestEigenmode &s)