29 std::vector<std::shared_ptr<Matter>> mdSnapshots;
30 std::vector<double> mdTimes;
31 QUILL_LOG_DEBUG(
log,
"Starting dynamics NEB saddle search");
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");
40 QUILL_LOG_DEBUG(
log,
"No mass weights file found");
45 log,
"Initializing velocities from Maxwell-Boltzmann distribution");
49 const double dt =
params.dynamics_options().time_step;
51 throw std::invalid_argument(
52 "DynamicsSaddleSearch: time_step must be positive");
54 int dephaseSteps =
static_cast<int>(
55 std::floor(
params.parallel_replica_options().dephase_time / dt + 0.5));
59 QUILL_LOG_DEBUG(
log,
"Dephasing: {} steps", dephaseSteps);
65 for (
int step = 1; step <= dephaseSteps; step++) {
75 QUILL_LOG_DEBUG(
log,
"Dephasing successful");
78 QUILL_LOG_DEBUG(
log,
"Transition occured during dephasing; Restarting");
91 if (matter ==
nullptr) {
98 if (
params.hyperdynamics_options().bias_potential ==
100 QUILL_LOG_DEBUG(
log,
"Initializing Bond Boost");
102 saddle->setBiasPotential(&bondBoost);
103 biasGuard.matter =
saddle.get();
106 int checkInterval =
static_cast<int>(
107 params.saddle_search_options().dynamics.state_check_interval /
108 params.dynamics_options().time_step +
112 if (checkInterval < 1) {
116 static_cast<int>(
params.saddle_search_options().dynamics.record_interval /
117 params.dynamics_options().time_step +
120 if (
params.debug_options().write_movies) {
122 QUILL_LOG_WARNING(
log,
"Failed to write dynamics movie header");
126 for (
int step = 1; step <=
params.dynamics_options().steps; step++) {
127 if (
params.hyperdynamics_options().bias_potential ==
135 if (recordInterval != 0 && step % recordInterval == 0) {
136 QUILL_LOG_DEBUG(
log,
"recording configuration at step {} time {:.3f}",
138 step *
params.dynamics_options().time_step *
139 params.constants().timeUnit);
141 auto snapshot = std::make_shared<Matter>(*
saddle);
142 mdSnapshots.push_back(snapshot);
143 mdTimes.push_back(step *
params.dynamics_options().time_step);
146 if (
params.debug_options().write_movies) {
148 QUILL_LOG_WARNING(
log,
"Failed to append dynamics movie frame");
152 if (step % checkInterval == 0) {
153 QUILL_LOG_DEBUG(
log,
"Minimizing trajectory, step {}", step);
159 QUILL_LOG_DEBUG(
log,
"Found new state");
163 if (!mdSnapshots.empty()) {
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;
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);
179 time = mdTimes[
static_cast<size_t>(image)] -
180 params.saddle_search_options().dynamics.record_interval / 2.0;
183 if (image > 0 &&
time < mdTimes[
static_cast<size_t>(image - 1)]) {
184 time = mdTimes[
static_cast<size_t>(image - 1)];
187 QUILL_LOG_DEBUG(
log,
"Transition time {:.2f} fs",
192 if (!
params.saddle_search_options().dynamics.linear_interpolation) {
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");
204 int mid =
neb.numImages / 2 + 1;
205 for (
int img = 1; img <=
neb.numImages; img++) {
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);
216 neb.path[img]->setPositions(
saddle->getPositions());
219 neb.path[img]->matter2con(
"neb_initial_band.con",
true))) {
220 QUILL_LOG_WARNING(
log,
"Failed to append neb_initial_band frame");
224 "neb_initial_band.con",
true))) {
225 QUILL_LOG_WARNING(
log,
226 "Failed to append neb_initial_band endpoint");
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");
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");
244 if (
params.neb_options().max_iterations > 0) {
249 neb.printImageData(
true);
250 int extremumImage = -1;
252 for (jExt = 0; jExt <
neb.numExtrema; jExt++) {
253 if (
neb.extremumCurvature[jExt] <
254 params.saddle_search_options().dynamics.max_init_curvature) {
256 static_cast<int>(std::floor(
neb.extremumPosition[jExt]));
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}",
275 log,
"chose image {} (extrema #{}) as extremum image",
276 extremumImage, jExt + 1);
284 if (extremumImage != -1) {
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);
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();
305 mode =
saddle->pbc(
neb.path[img + 1]->getPositions() -
307 eonc::safemath::safe_normalize_inplace(mode);
310 if (maxEnergy <= reactant->getPotentialEnergy()) {
311 QUILL_LOG_DEBUG(
log,
"warning: no barrier found");
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) {
329 mode = AtomMatrix::Zero(
saddle->numberOfAtoms(), 3);
330 if (mode.rows() > 0) {
337 log,
"Initial saddle guess saved to saddle_initial_guess.con");
339 QUILL_LOG_WARNING(
log,
"Failed to write saddle_initial_guess.con");
343 int minModeStatus = search.
run();
346 QUILL_LOG_DEBUG(
log,
"error in min mode saddle search");
347 return minModeStatus;
356 QUILL_LOG_DEBUG(
log,
"found barrier of {:.3f}", barrier);
361 QUILL_LOG_DEBUG(
log,
"Still in original state");
369 time =
params.dynamics_options().steps *
params.dynamics_options().time_step;