204 {
205 long iteration = 0;
207
208 QUILL_LOG_DEBUG(
log,
"Nudged elastic band calculation started.");
209
210
211 E_ref = std::min(
path[0]->getPotentialEnergy(),
213
215
216 auto objf = std::make_shared<NEBObjectiveFunction>(
this,
params);
217
218 bool switched{false};
221 std::unique_ptr<Optimizer> refine_optim{nullptr};
225 }
226
227
230
232 if (
params.debug_options.write_movies &&
233 (iteration %
params.debug_options.write_movies_interval == 0)) {
234 bool append = (iteration != 0);
237 params.debug_options.estimate_neb_eigenvalues,
238 std::format("neb_path_{:03d}.con", iteration), iteration))) {
239 QUILL_LOG_ERROR(
log,
"Failed to write NEB path movie for iteration {}",
240 iteration);
241 }
242
245 maxTang =
246 path[0]->pbc(
path[1]->getPositions() -
path[0]->getPositions());
250 } else {
252 }
253 maxTang.normalize();
254 auto maxImageMetadata = eonc::io::ConFrameMetadata{};
255 maxImageMetadata.frame_index =
static_cast<uint64_t
>(
maxEnergyImage);
257 maxImageMetadata.neb_bead =
static_cast<uint64_t
>(
maxEnergyImage);
258 maxImageMetadata.neb_band = static_cast<uint64_t>(iteration);
259 maxImageMetadata.scalars.push_back(
261 path[0]->getPotentialEnergy()});
262 maxImageMetadata.scalars.push_back(
263 {"parallel_force",
265 maxImageMetadata.strings.push_back({"movie_kind", "neb_maximage"});
267 "neb_maximage.con", append, &maxImageMetadata))) {
269 }
271 }
272
273 VectorXd pos = objf->getPositions();
275
277
278 if (iteration == 0) {
280 ocineb.initBaseline(convForce);
281
282
283 auto &ci_opt =
params.neb_options.climbing_image;
284 auto &mmf_opt = ci_opt.ocineb;
285 auto fmt_trigger = [](double val) -> std::string {
286 if (val > 1e100)
287 return "INF";
288 return std::format("{:.4f}", val);
289 };
290
291 QUILL_LOG_INFO(
293 "===============================================================");
294 QUILL_LOG_INFO(
log,
" NEB Optimization Configuration");
295 QUILL_LOG_INFO(
297 "===============================================================");
299
300 std::string ci_status = ci_opt.enabled ? "ENABLED" : "DISABLED";
301 QUILL_LOG_INFO(
log,
" {:<25} : {}",
"Climbing Image (CI)", ci_status);
302 if (ci_opt.enabled) {
304 QUILL_LOG_INFO(
log,
" - {:<21} : {} (Factor: {:.2f})",
305 "Relative Trigger", fmt_trigger(ci_rel_val),
306 ci_opt.trigger_factor);
307 QUILL_LOG_INFO(
log,
" - {:<21} : {}",
"Absolute Trigger",
308 fmt_trigger(ci_opt.trigger_force));
309 QUILL_LOG_INFO(
log,
" - {:<21} : {}",
"Converged Only",
310 ci_opt.converged_only);
311 }
312
313 std::string mmf_status =
314 (ci_opt.enabled && mmf_opt.use_mmf) ? "ENABLED" : "DISABLED";
315 QUILL_LOG_INFO(
log,
" {:<25} : {}",
"Hybrid MMF (OCINEB)", mmf_status);
316 if (ci_opt.enabled && mmf_opt.use_mmf) {
317 QUILL_LOG_INFO(
log,
" - {:<21} : {:.4f} (Factor: {:.2f})",
318 "Initial Threshold", ocineb.threshold(),
319 mmf_opt.trigger_factor);
320 QUILL_LOG_INFO(
log,
" - {:<21} : {:.4f}",
"Absolute Floor",
321 mmf_opt.trigger_force);
322 QUILL_LOG_INFO(
log,
" - {:<21} : {:.4f}",
"Angle Tolerance",
323 mmf_opt.angle_tol);
324 }
325 QUILL_LOG_INFO(
327 "---------------------------------------------------------------");
328
329 EONC_LOG_DEBUG(
"{:>10s} {:>12s} {:>14s} {:>11s} {:>12s}",
"iteration",
330 "step size",
331 params.optimizer_options.convergence_metric_label,
332 "max image", "max energy");
333 QUILL_LOG_DEBUG(
335 "---------------------------------------------------------------\n");
336 }
337
338
339 bool ci_active =
340 params.neb_options.climbing_image.enabled &&
342 params.neb_options.climbing_image.trigger_factor ||
343 convForce <
params.neb_options.climbing_image.trigger_force);
344
345 if (iteration) {
346
348 ocineb.stabilityCount())) {
349 auto result = ocineb.run(*this, convForce);
350
351 if (result.convergedAfterMMF) {
353 break;
354 }
355
356
357
358
359 bool didResample = false;
360 if (!result.convergedAfterMMF && result.newForce < convForce) {
362 std::span{
path.data(),
path.size()});
364 didResample = true;
365 }
366
367
368
369 if (result.shouldResetOptimizer || didResample) {
372 }
373 }
374
375 if (iteration >=
params.neb_options.max_iterations) {
377 break;
378 }
379
380
381
383
384 auto &activeOptim =
385 (refine_optim &&
386 convForce <=
params.optimizer_options.refine.threshold)
387 ? refine_optim
388 : optim;
389 if (refine_optim &&
390 convForce <=
params.optimizer_options.refine.threshold && !switched) {
391 switched = true;
393 magic_enum::enum_name<OptType>(
394 params.optimizer_options.refine.method));
395 }
396 activeOptim->step(
params.optimizer_options.max_move);
397
399 }
400
401 iteration++;
402
404 path[0]->getPotentialEnergy();
406 path[0]->pbcV(objf->getPositions() - pos));
407 QUILL_LOG_DEBUG(
log,
"{:>10} {:>12.4e} {:>14.4e} {:>11} {:>12.4}",
409 dE);
410
412 if (objf->isUncertain()) {
413 QUILL_LOG_DEBUG(
log,
"NEB failed due to high uncertainty");
415 break;
416 } else if (objf->isConverged()) {
417 QUILL_LOG_DEBUG(
log,
"NEB converged\n");
419 break;
420 }
421 } else {
422 if (objf->isConverged()) {
423 QUILL_LOG_DEBUG(
log,
"NEB converged\n");
425 break;
426 }
427 }
428 }
430}
double matDot(const AtomMatrix &a, const AtomMatrix &b)
SIMD-optimized dot product for contiguous Eigen matrices.
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
#define EONC_LOG_DEBUG(...)
#define EONC_LOG_WARNING(...)
void printImageData(bool writeToFile=false, size_t idx=0)
std::size_t maxEnergyImage
double convergenceForce(void)
void setCIEnabled(bool enabled)
friend class eonc::neb::OCINEBController
static Config fromParams(const Parameters ¶ms)
void resamplePathInPlace(std::span< std::shared_ptr< Matter > > path)
In-place path reparameterization for NEB shared_ptr paths.
double maxAtomMotionV(const VectorXd v1)
IoStatus matter2con(Matter &m, std::string filename, bool append, const ConFrameMetadata *metadata)
Append a frame to a .con, or truncate and write one frame.
constexpr bool io_ok(IoStatus s) noexcept
eonc::io::IoStatus writePathCon(const std::vector< std::shared_ptr< Matter > > &path, const std::vector< std::shared_ptr< AtomMatrix > > &tangent, const std::vector< std::shared_ptr< EigenmodeStrategy > > &eigenmode_solvers, long numImages, bool estimateEigenvalues, std::string filename, std::optional< size_t > bandIndex)
Write a NEB band as a multi-frame .con via readcon ConFrameBuilder::clone().