Loading...
Searching...
No Matches
eonc::DynamicsSaddleSearch Class Reference

#include <DynamicsSaddleSearch.h>

Inheritance diagram for eonc::DynamicsSaddleSearch:

Public Member Functions

 DynamicsSaddleSearch (std::shared_ptr< Matter > matterPassed, const Parameters &parametersPassed)
 ~DynamicsSaddleSearch ()=default
int run ()
double getEigenvalue ()
AtomMatrix getEigenvector ()
std::string_view describeStatus (int status) const override
int getStatus () const override
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.
Public Member Functions inherited from eonc::SaddleSearchMethod
 SaddleSearchMethod (std::shared_ptr< Potential > potPassed, const Parameters &paramsPassed)
virtual ~SaddleSearchMethod ()
virtual int getIterationCount () const
virtual int getForceCalls () const

Public Attributes

double eigenvalue {0.0}
AtomMatrix eigenvector
double time {0.0}
std::shared_ptr< Matter > product
std::shared_ptr< Matter > reactant
std::shared_ptr< Matter > saddle
int status {0}

Private Attributes

eonc::log::Scoped log

Additional Inherited Members

Protected Attributes inherited from eonc::SaddleSearchMethod
std::shared_ptr< Potential > pot
const Parameters & params

Detailed Description

Definition at line 22 of file DynamicsSaddleSearch.h.

Constructor & Destructor Documentation

◆ DynamicsSaddleSearch()

eonc::DynamicsSaddleSearch::DynamicsSaddleSearch ( std::shared_ptr< Matter > matterPassed,
const Parameters & parametersPassed )
inline

Definition at line 24 of file DynamicsSaddleSearch.h.

26 : SaddleSearchMethod(nullptr, parametersPassed),
27 product{std::make_shared<Matter>(*matterPassed)},
28 reactant{std::make_shared<Matter>(*matterPassed)},
29 saddle{matterPassed} {
30 this->pot = matterPassed->getPotential();
31 eigenvector.resize(reactant->numberOfAtoms(), 3);
32 eigenvector.setZero();
33 };
std::shared_ptr< Matter > saddle
std::shared_ptr< Matter > reactant
std::shared_ptr< Matter > product
std::shared_ptr< Potential > pot
SaddleSearchMethod(std::shared_ptr< Potential > potPassed, const Parameters &paramsPassed)

◆ ~DynamicsSaddleSearch()

eonc::DynamicsSaddleSearch::~DynamicsSaddleSearch ( )
default

Member Function Documentation

◆ describeStatus()

std::string_view eonc::DynamicsSaddleSearch::describeStatus ( int status) const
inlineoverridevirtual

Implements eonc::SaddleSearchMethod.

Definition at line 39 of file DynamicsSaddleSearch.h.

39 {
41 }
static constexpr std::string_view statusMessage(int status)
Human-readable message for a status code.

◆ getEigenvalue()

double eonc::DynamicsSaddleSearch::getEigenvalue ( )
virtual

Implements eonc::SaddleSearchMethod.

Definition at line 411 of file DynamicsSaddleSearch.cpp.

◆ getEigenvector()

AtomMatrix eonc::DynamicsSaddleSearch::getEigenvector ( )
virtual

Implements eonc::SaddleSearchMethod.

Definition at line 413 of file DynamicsSaddleSearch.cpp.

413{ return eigenvector; }

◆ getStatus()

int eonc::DynamicsSaddleSearch::getStatus ( ) const
inlineoverridevirtual

Reimplemented from eonc::SaddleSearchMethod.

Definition at line 42 of file DynamicsSaddleSearch.h.

42{ return status; }

◆ refineTransition()

int eonc::DynamicsSaddleSearch::refineTransition ( const std::vector< std::shared_ptr< Matter > > & snapshots,
std::shared_ptr< Matter > product )

Binary search through MD snapshots to find the transition point.

Definition at line 374 of file DynamicsSaddleSearch.cpp.

376 {
377 int lo = 0;
378 int hi = static_cast<int>(snapshots.size()) - 1;
379 // One frame is index 0. Zero frames yield -1, which is not a subscript.
380 if (hi <= 0) {
381 return hi;
382 }
383
384 QUILL_LOG_DEBUG(log, "refining transition time");
385
386 while ((hi - lo) > 1) {
387 int mid = lo + (hi - lo) / 2;
388 QUILL_LOG_DEBUG(log, "minimizing image {}", mid);
389 Matter snap(pot, params);
390 snap = *snapshots[mid];
391 snap.relax(false);
392
393 if (snap.compare(*reactant)) {
394 QUILL_LOG_DEBUG(log, "image {} minimizes to reactant", mid);
395 lo = mid;
396 } else {
397 QUILL_LOG_DEBUG(log, "image {} minimizes to product", mid);
398 *prod = snap;
399 hi = mid;
400 }
401 }
402
403 // Adjacent brackets make (lo + hi) / 2 equal lo, the snapshot that
404 // still minimizes to the reactant. The transition is the higher index.
405 if (hi < 0) {
406 return 0;
407 }
408 return hi;
409}

◆ run()

int eonc::DynamicsSaddleSearch::run ( void )
virtual

Implements eonc::SaddleSearchMethod.

Definition at line 28 of file DynamicsSaddleSearch.cpp.

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 =
36 eonc::helpers::loadMasses("masses.dat", saddle->numberOfAtoms());
37 saddle->setMasses(masses);
38 QUILL_LOG_DEBUG(log, "Applied mass weights");
39 } else {
40 QUILL_LOG_DEBUG(log, "No mass weights file found");
41 }
42
43 Dynamics dyn(saddle.get(), params);
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 // always start from the initial configuration
61 *saddle = *reactant;
62 dyn.setThermalVelocity();
63
64 // Dephase MD trajectory
65 for (int step = 1; step <= dephaseSteps; step++) {
66 dyn.oneStep(step);
67 }
68
69 // Check to see if a transition occured
70 Matter min(pot, params);
71 min = *saddle;
72 min.relax();
73
74 if (min.compare(*reactant)) {
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
85 BondBoost bondBoost(saddle.get(), params);
86 // setBiasPotential does not own this object. Clear it before bondBoost
87 // leaves the stack, including on the early returns below.
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 // A zero or sub-step interval floors to 0. step % 0 is undefined, and a
111 // state check shorter than one dynamics step still has to run.
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) {
121 if (!eonc::io::io_ok(saddle->matter2con("dynamics", false))) {
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 // oneStep() calls getAccelerations(), and therefore boost(), more
130 // than once. Advance the rmd_time counter once per MD step.
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 // BUG FIX: was sharing ownership with saddle instead of copying
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) {
147 if (!eonc::io::io_ok(saddle->matter2con("dynamics", true))) {
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
155 product = std::make_shared<Matter>(*saddle);
156 product->relax(false, false);
157
158 if (!product->compare(*reactant)) {
159 QUILL_LOG_DEBUG(log, "Found new state");
160 // A record interval that rounds to 0 stores nothing. refineTransition
161 // then has no frame, and subscript 0 is outside the vector.
162 int image = -1;
163 if (!mdSnapshots.empty()) {
164 image = refineTransition(mdSnapshots, product);
165 }
166 if (image < 0 || static_cast<size_t>(image) >= mdSnapshots.size() ||
167 static_cast<size_t>(image) >= mdTimes.size()) {
168 QUILL_LOG_DEBUG(log,
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 // Subtract half the record interval to avoid systematic bias
179 time = mdTimes[static_cast<size_t>(image)] -
180 params.saddle_search_options().dynamics.record_interval / 2.0;
181 // Half a record interval before the leaving frame is the unbiased
182 // crossing, and it must not fall before the preceding reactant frame.
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",
188 time * params.constants().timeUnit);
189
190 NudgedElasticBand neb(reactant, product, params, pot);
191
192 if (!params.saddle_search_options().dynamics.linear_interpolation) {
193 QUILL_LOG_DEBUG(
194 log, "Interpolating initial band through MD transition state");
195 AtomMatrix reactantToSaddle =
196 saddle->pbc(saddle->getPositions() - reactant->getPositions());
197 AtomMatrix saddleToProduct =
198 saddle->pbc(product->getPositions() - saddle->getPositions());
199 QUILL_LOG_DEBUG(log, "Initial band saved to neb_initial_band.con");
200 if (!eonc::io::io_ok(
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 }
218 if (!eonc::io::io_ok(
219 neb.path[img]->matter2con("neb_initial_band.con", true))) {
220 QUILL_LOG_WARNING(log, "Failed to append neb_initial_band frame");
221 }
222 }
223 if (!eonc::io::io_ok(neb.path[neb.numImages + 1]->matter2con(
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");
231 if (!eonc::io::io_ok(
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++) {
236 if (!eonc::io::io_ok(
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
243 AtomMatrix mode;
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);
260 AtomMatrix bandDir =
261 saddle->pbc(neb.path[extremumImage + 1]->getPositions() -
262 neb.path[extremumImage]->getPositions());
263 saddle->setPositions(interpDist * bandDir +
264 saddle->getPositions());
265 mode = saddle->pbc(neb.path[extremumImage + 1]->getPositions() -
266 saddle->getPositions());
267 eonc::safemath::safe_normalize_inplace(mode);
268 eonc::eigenmodeCompute(*minModeMethod, saddle, mode);
269 double ev = eonc::eigenmodeGetEigenvalue(*minModeMethod);
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);
289 AtomMatrix bandDir =
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() -
294 saddle->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;
304 *saddle = *neb.path[img];
305 mode = saddle->pbc(neb.path[img + 1]->getPositions() -
306 saddle->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 // No NEB iterations: the middle image of the initial band is the
317 // guess, and the dimer still needs an n-atom tangent. An empty
318 // mode replaces the dimer direction and then indexes every atom.
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");
338 if (!eonc::io::io_ok(saddle->matter2con("saddle_initial_guess.con"))) {
339 QUILL_LOG_WARNING(log, "Failed to write saddle_initial_guess.con");
340 }
341 MinModeSaddleSearch search = MinModeSaddleSearch(
342 saddle, mode, reactant->getPotentialEnergy(), params, pot);
343 int minModeStatus = search.run();
344
345 if (minModeStatus != MinModeSaddleSearch::STATUS_GOOD) {
346 QUILL_LOG_DEBUG(log, "error in min mode saddle search");
347 return minModeStatus;
348 }
349
350 eigenvalue = search.getEigenvalue();
351 eigenvector = search.getEigenvector();
352 QUILL_LOG_DEBUG(log, "eigenvalue: {:.3f}", eigenvalue);
353
354 double barrier =
355 saddle->getPotentialEnergy() - reactant->getPotentialEnergy();
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
Definition Eigen.h:37
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[]
Definition BondBoost.h:75
VectorXd loadMasses(std::string filename, int nAtoms)
constexpr bool io_ok(IoStatus s) noexcept
Definition ConFileIO.h:38
void eigenmodeCompute(LowestEigenmode &s, std::shared_ptr< Matter > matter, AtomMatrix direction)
std::shared_ptr< LowestEigenmode > buildEigenmodeStrategy(std::shared_ptr< Matter > matter, const Parameters &params, std::shared_ptr< Potential > pot)
double eigenmodeGetEigenvalue(LowestEigenmode &s)

Member Data Documentation

◆ eigenvalue

double eonc::DynamicsSaddleSearch::eigenvalue {0.0}

Definition at line 47 of file DynamicsSaddleSearch.h.

47{0.0};

◆ eigenvector

AtomMatrix eonc::DynamicsSaddleSearch::eigenvector

Definition at line 48 of file DynamicsSaddleSearch.h.

◆ log

eonc::log::Scoped eonc::DynamicsSaddleSearch::log
private

Definition at line 59 of file DynamicsSaddleSearch.h.

◆ product

std::shared_ptr<Matter> eonc::DynamicsSaddleSearch::product

Definition at line 52 of file DynamicsSaddleSearch.h.

◆ reactant

std::shared_ptr<Matter> eonc::DynamicsSaddleSearch::reactant

Definition at line 53 of file DynamicsSaddleSearch.h.

◆ saddle

std::shared_ptr<Matter> eonc::DynamicsSaddleSearch::saddle

Definition at line 54 of file DynamicsSaddleSearch.h.

◆ status

int eonc::DynamicsSaddleSearch::status {0}

Definition at line 56 of file DynamicsSaddleSearch.h.

56{0};

◆ time

double eonc::DynamicsSaddleSearch::time {0.0}

Definition at line 50 of file DynamicsSaddleSearch.h.

50{0.0};

The documentation for this class was generated from the following files: