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

#include <NudgedElasticBand.h>

Public Types

enum class  NEBStatus {
  GOOD = 0 , INIT = 1 , BAD_MAX_ITERATIONS = 2 , RUNNING ,
  MAX_UNCERTAINTY
}

Public Member Functions

 NudgedElasticBand (std::shared_ptr< Matter > initialPassed, std::shared_ptr< Matter > finalPassed, const Parameters &parametersPassed, std::shared_ptr< Potential > potPassed)
 NudgedElasticBand (std::vector< Matter > initPath, const Parameters &parametersPassed, std::shared_ptr< Potential > potPassed)
 ~NudgedElasticBand ()=default
NudgedElasticBand::NEBStatus compute (void)
NudgedElasticBand::NEBStatus getStatus ()
bool solidState () const noexcept
double solidJacobian () const noexcept
const Matrix3d & cellForce (long image) const
void updateForces (bool ci_active)
void updateForces (void)
void setCIEnabled (bool enabled)
double convergenceForce (void)
void findExtrema (void)
void printImageData (bool writeToFile=false, size_t idx=0)
std::vector< readcon::ConFrame > pathFrames (std::optional< size_t > bandIndex=std::nullopt)
 In-memory ConFrames with the same NEB stamps as writePathCon / neb.con.

Public Attributes

std::vector< std::shared_ptr< EigenmodeStrategy > > eigenmode_solvers
int atoms {0}
long numImages {0}
long climbingImage {0}
long numExtrema {0}
double reactantEnergy {0.0}
std::vector< std::shared_ptr< Matter > > path
std::vector< std::shared_ptr< AtomMatrix > > tangent
std::vector< std::shared_ptr< AtomMatrix > > projectedForce
std::vector< double > extremumEnergy
std::vector< double > extremumPosition
std::vector< double > extremumCurvature
std::size_t maxEnergyImage {0}
bool movedAfterForceCall {false}
bool perImagePotentials_
 Whether per-image potential instances exist.
double ksp {0.0}
double k_u {0.0}
double k_l {0.0}
double E_ref

Private Member Functions

void prepareSolidState ()
void projectSolidState (bool ci_active)

Private Attributes

bool ci_enabled_ {false}
double baseline_force {-1.0}
Parameters params
std::shared_ptr< Potential > pot
NEBStatus status
eonc::log::Scoped log
neb::TangentStrategy tangentStrat_
neb::ProjectionStrategy projectionStrat_
bool solidState_ {false}
double solidJacobian_ {1.0}
std::vector< Matrix3d > projectedCellForce

Friends

class eonc::neb::OCINEBController

Detailed Description

Definition at line 38 of file NudgedElasticBand.h.

Member Enumeration Documentation

◆ NEBStatus

Enumerator
GOOD 
INIT 
BAD_MAX_ITERATIONS 
RUNNING 
MAX_UNCERTAINTY 

Definition at line 42 of file NudgedElasticBand.h.

42 {
43 GOOD = 0,
44 INIT = 1,
45 BAD_MAX_ITERATIONS = 2,
46 RUNNING,
47 MAX_UNCERTAINTY
48 };

Constructor & Destructor Documentation

◆ NudgedElasticBand() [1/2]

eonc::NudgedElasticBand::NudgedElasticBand ( std::shared_ptr< Matter > initialPassed,
std::shared_ptr< Matter > finalPassed,
const Parameters & parametersPassed,
std::shared_ptr< Potential > potPassed )

Definition at line 50 of file NudgedElasticBand.cpp.

55 [&]() {
56 auto &init_opt = parametersPassed.neb_options().initialization;
57 const size_t base_count =
58 parametersPassed.neb_options().image_count;
59 if (parametersPassed.neb_options().solid_state.enabled &&
60 init_opt.method != NEBInit::LINEAR &&
61 init_opt.method != NEBInit::FILE) {
62 throw std::invalid_argument(
63 "solid_state accepts initializer linear or file");
64 }
65 if (parametersPassed.neb_options().match_endpoints) {
67 *initialPassed, *finalPassed, 1.0);
68 auto *log = eonc::log::get();
69 if (aligned.error != 0) {
70 QUILL_LOG_WARNING(
71 log,
72 "match_endpoints: IRA align failed (error {}), "
73 "interpolating the input order",
74 aligned.error);
75 } else {
76 QUILL_LOG_INFO(log,
77 "match_endpoints: Hausdorff {:.4f} A after IRA "
78 "permute+rotate of the reactant",
79 aligned.hausdorffDistance);
80 }
81 }
82
83 // Apply oversampling factor if flag exists
84 const size_t relax_count =
85 init_opt.oversampling
86 ? base_count * init_opt.oversampling_factor
87 : base_count;
88
89 std::vector<Matter> path;
90 switch (init_opt.method) {
91 case NEBInit::FILE: {
92 std::vector<fs::path> file_paths =
93 eonc::helpers::neb_paths::readFilePaths(init_opt.input_path);
95 file_paths, *initialPassed, base_count);
96 // filePathInit reloads every frame, including ends the caller
97 // may already have minimized.
98 path.front() = Matter(*initialPassed);
99 path.back() = Matter(*finalPassed);
100 break;
101 }
102 case NEBInit::IDPP:
104 *initialPassed, *finalPassed, relax_count, parametersPassed);
105 break;
108 *initialPassed, *finalPassed, relax_count, parametersPassed);
109 break;
110 case NEBInit::SIDPP:
113 *initialPassed, *finalPassed, relax_count, parametersPassed,
114 (init_opt.method == NEBInit::SIDPP_ZBL));
115 break;
116 case NEBInit::LINEAR:
117 default:
119 *initialPassed, *finalPassed, base_count);
120 break;
121 }
122
123 // Decimate path back to the target count using the cubic spline
124 if (init_opt.oversampling && path.size() > (base_count + 2)) {
125 auto *log = eonc::log::get();
126 QUILL_LOG_INFO(log,
127 "Decimating oversampled path ({} images) to "
128 "{} images via cubic spline.",
129 path.size() - 2, base_count);
130
131 // Perform the Spline Resampling
133
134 // POST-DECIMATION RE-RELAXATION
135 // The spline might have placed atoms in high-energy positions.
136 QUILL_LOG_INFO(
137 log, "Relaxing decimated path to restore IDPP surface...");
138
139 // Collective IDPP objective for the new reduced path
140 std::shared_ptr<ObjectiveFunction> post_decim_objf =
141 std::make_shared<CollectiveIDPPObjectiveFunction>(
142 path, parametersPassed);
143
144 // Wrap with ZBL if the original method used ZBL options
145 bool use_zbl = (init_opt.method == NEBInit::SIDPP_ZBL);
146 if (use_zbl) {
148 post_decim_objf = std::make_shared<ZBLRepulsiveIDPPObjective>(
149 post_decim_objf, zbl_pot, path, parametersPassed, 1.0);
150 }
151
153 post_decim_objf, parametersPassed.neb_options().opt_method,
154 parametersPassed);
155
156 // Run a short optimization
157 optim->run(
158 parametersPassed.neb_options().initialization.max_iterations,
159 parametersPassed.neb_options().initialization.max_move);
160 }
161 if (parametersPassed.neb_options().solid_state.enabled &&
162 init_opt.method == NEBInit::LINEAR) {
163 if (init_opt.oversampling) {
164 throw std::invalid_argument(
165 "solid_state NEB does not oversample the initial path");
166 }
167 for (Matter &image : path) {
169 }
171 } else if (parametersPassed.neb_options().solid_state.enabled &&
172 init_opt.method == NEBInit::FILE) {
173 for (Matter &image : path) {
175 }
176 }
177 return path;
178 }(),
179 parametersPassed, potPassed) {}
static MatchResult alignReactantToProduct(Matter &reactant, const Matter &product, double distThreshold)
Rigid-align + permute reactant onto product.
NudgedElasticBand(std::shared_ptr< Matter > initialPassed, std::shared_ptr< Matter > finalPassed, const Parameters &parametersPassed, std::shared_ptr< Potential > potPassed)
std::vector< std::shared_ptr< Matter > > path
std::unique_ptr< Optimizer > mkOptim(std::shared_ptr< ObjectiveFunction > a_objf, OptType a_otype, const Parameters &a_params)
Definition Optimizer.cpp:24
std::vector< Matter > resamplePath(const std::vector< Matter > &densePath, size_t targetCount)
std::vector< Matter > sidppPath(const Matter &initImg, const Matter &finalImg, size_t target_nimgs, const Parameters &params, bool use_zbl)
std::shared_ptr< Potential > createZBLPotential()
std::vector< Matter > linearPath(const Matter &initImg, const Matter &finalImg, const size_t nimgs)
std::vector< Matter > filePathInit(const std::vector< fs::path > &fsrcs, const Matter &refImg, const size_t nimgs)
std::vector< Matter > idppPath(const Matter &initImg, const Matter &finalImg, const size_t nimgs, const Parameters &params, bool use_zbl)
std::vector< Matter > idppCollectivePath(const Matter &initImg, const Matter &finalImg, size_t nimgs, const Parameters &params, bool use_zbl)
std::vector< fs::path > readFilePaths(const std::string &listFilePath)
Reads a file where each line contains a path to another file.
quill::Logger * get() noexcept
Get or create the default "combi" logger.
Definition EonLogger.h:44
void interpolateSolidStateLinear(std::vector< Matter > &images)
Replace interior images by a fractional linear interpolation of the oriented endpoints.
void orientSolidStateMatter(Matter &image)

◆ NudgedElasticBand() [2/2]

eonc::NudgedElasticBand::NudgedElasticBand ( std::vector< Matter > initPath,
const Parameters & parametersPassed,
std::shared_ptr< Potential > potPassed )

Definition at line 182 of file NudgedElasticBand.cpp.

185 : ci_enabled_{parametersPassed.neb_options().climbing_image.enabled},
186 params{parametersPassed},
187 pot{potPassed},
188 E_ref{0.0} {
189
192 params.optimizer_options().convergence_metric, "[Nudged Elastic Band]");
193 this->status = NEBStatus::INIT;
194 numImages = params.neb_options().image_count;
195 if (initPath.empty()) {
196 throw std::invalid_argument("NEB: initPath is empty");
197 }
198 if (initPath.size() != static_cast<size_t>(numImages + 2)) {
199 throw std::invalid_argument("NEB: initPath.size() must be image_count + 2");
200 }
201 atoms = initPath.front().numberOfAtoms();
202 for (size_t i = 1; i < initPath.size(); ++i) {
204 initPath[i], "path images");
205 }
206
207 // Common initialization logic
208 path.resize(numImages + 2);
209 tangent.resize(numImages + 2);
210 projectedForce.resize(numImages + 2);
211 extremumPosition.resize(2 * (numImages + 1));
212 extremumEnergy.resize(2 * (numImages + 1));
213 extremumCurvature.resize(2 * (numImages + 1));
214 numExtrema = 0;
215
216 // Create per-image potentials if needed for true parallel force evaluation
218 pot->needsPerImageInstance() && params.main_options().parallel;
220 QUILL_LOG_INFO(log,
221 "NEB: Creating per-image potential instances for "
222 "parallel force evaluation ({} images)",
223 numImages + 2);
224 }
225
226 for (long i = 0; i <= numImages + 1; i++) {
227 path[i] = std::make_shared<Matter>(std::move(initPath[i]));
228
229 // Give each INTERMEDIATE image its own potential for true parallelism.
230 // Endpoints (i=0, i=numImages+1) keep the shared pot -- they are only
231 // evaluated once during initialization and never in parallel.
232 if (perImagePotentials_ && i > 0 && i <= numImages) {
233 auto cloned = pot->clonePotential();
234 path[i]->setPotential(cloned ? cloned
236 }
237
238 tangent[i] = std::make_shared<AtomMatrix>();
239 tangent[i]->resize(atoms, 3);
240 tangent[i]->setZero();
241 projectedForce[i] = std::make_shared<AtomMatrix>();
242 projectedForce[i]->resize(atoms, 3);
243 projectedForce[i]->setZero();
244 }
245
246 // Common final setup
247 movedAfterForceCall = true;
249 // Both endpoints in one call, so two calculator groups take one each.
250 {
251 Matter *const ends[] = {path[0].get(), path[numImages + 1].get()};
253 }
254 reactantEnergy = path[0]->getPotentialEnergy();
255 climbingImage = 0;
256
257 // Setup springs
258 k_u = params.neb_options().spring.weighting.k_max;
259 k_l = params.neb_options().spring.weighting.k_min;
260 if (params.neb_options().spring.weighting.enabled) {
261 ksp = k_l;
262 } else {
263 ksp = params.neb_options().spring.constant;
264 }
265
266 // Cache strategies that are constant across iterations
269
270 // Optional debugging setup
271 if (params.debug_options().estimate_neb_eigenvalues) {
272 eigenmode_solvers.resize(numImages + 2);
273 for (long i = 0; i <= numImages + 1; i++) {
275 eonc::buildEigenmodeStrategy(path[i], parametersPassed, pot);
276 }
277 }
278}
std::vector< double > extremumEnergy
std::vector< std::shared_ptr< AtomMatrix > > tangent
bool perImagePotentials_
Whether per-image potential instances exist.
std::vector< double > extremumPosition
std::vector< std::shared_ptr< EigenmodeStrategy > > eigenmode_solvers
neb::TangentStrategy tangentStrat_
std::vector< std::shared_ptr< AtomMatrix > > projectedForce
std::shared_ptr< Potential > pot
neb::ProjectionStrategy projectionStrat_
std::vector< double > extremumCurvature
void requireSameAtomCount(const Matter &a, const Matter &b, std::string_view what)
Abort before Eigen subtracts two position matrices of different size.
void requireKnownConvergenceMetric(std::string_view metric, std::string_view context)
Throws std::invalid_argument naming context when metric is unrecognized.
std::shared_ptr< Potential > makePotential(const Parameters &params)
TangentStrategy buildTangentStrategy(const Parameters &params)
Build the tangent strategy from parameters.
ProjectionStrategy buildProjectionStrategy(const Parameters &params)
Build the projection strategy from parameters.
void evaluateTogether(Potential &pot, std::span< Matter *const > systems)
Evaluates every system that needs a force update.
Definition Matter.cpp:847
std::shared_ptr< LowestEigenmode > buildEigenmodeStrategy(std::shared_ptr< Matter > matter, const Parameters &params, std::shared_ptr< Potential > pot)

◆ ~NudgedElasticBand()

eonc::NudgedElasticBand::~NudgedElasticBand ( )
default

Member Function Documentation

◆ cellForce()

const Matrix3d & eonc::NudgedElasticBand::cellForce ( long image) const
inlinenodiscard

Definition at line 62 of file NudgedElasticBand.h.

62 {
63 return projectedCellForce.at(static_cast<size_t>(image));
64 }
std::vector< Matrix3d > projectedCellForce

◆ compute()

NudgedElasticBand::NEBStatus eonc::NudgedElasticBand::compute ( void )

Definition at line 280 of file NudgedElasticBand.cpp.

280 {
281 long iteration = 0;
283
284 QUILL_LOG_DEBUG(log, "Nudged elastic band calculation started.");
285
286 // Higher endpoint: springs stay at k_min until an image exceeds it.
287 E_ref = std::max(path[0]->getPotentialEnergy(),
288 path[numImages + 1]->getPotentialEnergy());
289
290 updateForces();
291
292 auto objf = std::make_shared<NEBObjectiveFunction>(this, params);
293
294 bool switched{false};
296 objf, params.neb_options().opt_method, params);
297 std::unique_ptr<Optimizer> refine_optim{nullptr};
298 if (params.optimizer_options().refine.method != OptType::None) {
299 refine_optim = eonc::helpers::create::mkOptim(
300 objf, params.optimizer_options().refine.method, params);
301 }
302 // OCINEB controller
304 eonc::neb::OCINEBController ocineb(ocinebCfg);
305 bool zoomDone{false};
306 long zoomStable{0};
307 long zoomPrevCI{-1};
308 long zoomAt{-1};
309
310 while (this->status != NEBStatus::GOOD) {
311 if (params.debug_options().write_movies &&
312 (iteration % params.debug_options().write_movies_interval == 0)) {
313 bool append = (iteration != 0);
316 params.debug_options().estimate_neb_eigenvalues,
317 std::format("neb_path_{:03d}.con", iteration), iteration,
318 reactantEnergy))) {
319 QUILL_LOG_ERROR(log, "Failed to write NEB path movie for iteration {}",
320 iteration);
321 }
322
323 AtomMatrix maxTang;
324 if (maxEnergyImage == 0) {
325 maxTang =
326 path[0]->pbc(path[1]->getPositions() - path[0]->getPositions());
327 } else if (maxEnergyImage == static_cast<size_t>(numImages + 1)) {
328 maxTang = path[numImages]->pbc(path[numImages + 1]->getPositions() -
329 path[numImages]->getPositions());
330 } else {
331 maxTang = *tangent[maxEnergyImage];
332 }
333 eonc::safemath::safe_normalize_inplace(maxTang);
334 auto maxImageMetadata = eonc::io::ConFrameMetadata{};
335 maxImageMetadata.frame_index = static_cast<uint64_t>(maxEnergyImage);
336 maxImageMetadata.energy = path[maxEnergyImage]->getPotentialEnergy();
337 maxImageMetadata.neb_bead = static_cast<uint64_t>(maxEnergyImage);
338 maxImageMetadata.neb_band = static_cast<uint64_t>(iteration);
339 maxImageMetadata.scalars.push_back(
340 {"relative_energy",
341 path[maxEnergyImage]->getPotentialEnergy() - reactantEnergy});
342 maxImageMetadata.scalars.push_back(
343 {"parallel_force",
344 matDot(path[maxEnergyImage]->getForces(), maxTang)});
345 maxImageMetadata.strings.push_back({"movie_kind", "neb_maximage"});
347 "neb_maximage.con", append, &maxImageMetadata))) {
348 EONC_LOG_WARNING("Failed to write neb_maximage.con");
349 }
350 printImageData(true, iteration);
351 }
352
353 VectorXd pos = objf->getPositions();
354 double convForce = convergenceForce();
355
356 ocineb.updateStability(climbingImage);
357
358 if (iteration == 0) {
359 baseline_force = convForce;
360 ocineb.initBaseline(convForce);
361
362 // Log configuration banner
363 auto &ci_opt = params.neb_options().climbing_image;
364 auto &mmf_opt = ci_opt.ocineb;
365 auto fmt_trigger = [](double val) -> std::string {
366 if (val > 1e100)
367 return "INF";
368 return std::format("{:.4f}", val);
369 };
370
371 QUILL_LOG_INFO(
372 log,
373 "===============================================================");
374 QUILL_LOG_INFO(log, " NEB Optimization Configuration");
375 QUILL_LOG_INFO(
376 log,
377 "===============================================================");
378 QUILL_LOG_INFO(log, " {:<25} : {:.4f}", "Baseline Force", baseline_force);
379
380 std::string ci_status = ci_opt.enabled ? "ENABLED" : "DISABLED";
381 QUILL_LOG_INFO(log, " {:<25} : {}", "Climbing Image (CI)", ci_status);
382 if (ci_opt.enabled) {
383 double ci_rel_val = baseline_force * ci_opt.trigger_factor;
384 QUILL_LOG_INFO(log, " - {:<21} : {} (Factor: {:.2f})",
385 "Relative Trigger", fmt_trigger(ci_rel_val),
386 ci_opt.trigger_factor);
387 QUILL_LOG_INFO(log, " - {:<21} : {}", "Absolute Trigger",
388 fmt_trigger(ci_opt.trigger_force));
389 QUILL_LOG_INFO(log, " - {:<21} : {}", "Converged Only",
390 ci_opt.converged_only);
391 }
392
393 std::string mmf_status =
394 (ci_opt.enabled && mmf_opt.use_mmf) ? "ENABLED" : "DISABLED";
395 QUILL_LOG_INFO(log, " {:<25} : {}", "Hybrid MMF (OCINEB)", mmf_status);
396 if (ci_opt.enabled && mmf_opt.use_mmf) {
397 QUILL_LOG_INFO(log, " - {:<21} : {:.4f} (Factor: {:.2f})",
398 "Initial Threshold", ocineb.threshold(),
399 mmf_opt.trigger_factor);
400 QUILL_LOG_INFO(log, " - {:<21} : {:.4f}", "Absolute Floor",
401 mmf_opt.trigger_force);
402 QUILL_LOG_INFO(log, " - {:<21} : {:.4f}", "Angle Tolerance",
403 mmf_opt.angle_tol);
404 }
405 QUILL_LOG_INFO(
406 log,
407 "---------------------------------------------------------------");
408
409 EONC_LOG_DEBUG("{:>10s} {:>12s} {:>14s} {:>11s} {:>12s}", "iteration",
410 "step size",
411 params.optimizer_options().convergence_metric_label,
412 "max image", "max energy");
413 QUILL_LOG_DEBUG(
415 "---------------------------------------------------------------\n");
416 }
417
418 // CI active when force drops below relative threshold
419 bool ci_active =
420 params.neb_options().climbing_image.enabled &&
421 (convForce < baseline_force *
422 params.neb_options().climbing_image.trigger_factor ||
423 convForce < params.neb_options().climbing_image.trigger_force);
424
425 bool zoomedThisStep = false;
426 if (iteration && !zoomDone && params.neb_options().zoom.enabled &&
427 climbingImage > 0 && climbingImage + 1 < path.size()) {
428 if (static_cast<long>(climbingImage) == zoomPrevCI) {
429 ++zoomStable;
430 } else {
431 zoomPrevCI = static_cast<long>(climbingImage);
432 zoomStable = 1;
433 }
434 const auto &zoom = params.neb_options().zoom;
435 const double zoomForce =
436 zoom.activation_threshold > 0.0
437 ? zoom.activation_threshold
438 : 10.0 * params.neb_options().force_tolerance;
439 if (zoomStable >= zoom.stability_count && convForce < zoomForce) {
440 std::vector<double> energy;
441 energy.reserve(path.size());
442 for (const auto &image : path) {
443 energy.push_back(image->getPotentialEnergy());
444 }
445 const auto window =
448 zoom.interpolation)) {
449 movedAfterForceCall = true;
451 objf, params.neb_options().opt_method, params);
452 zoomDone = true;
453 zoomedThisStep = true;
454 zoomAt = iteration;
455 QUILL_LOG_INFO(log, "Zoom-NEB: packed the band onto images [{}, {}]",
456 window.lo, window.hi);
457 }
458 }
459 }
460
461 if (iteration) {
462 // MMF triggering via controller. Skipped on the zoom step so the
463 // dimer sees the redistributed band, not the pre-zoom geometry.
464 if (!zoomedThisStep &&
465 ocineb.shouldTrigger(convForce, ci_active, climbingImage, numImages,
466 ocineb.stabilityCount())) {
467 auto result = ocineb.run(*this, convForce);
468
469 if (result.convergedAfterMMF) {
471 break;
472 }
473 // Post-MMF arc-length reparameterization: pass full path
474 // (endpoints are fixed by resamplePathInPlace, only interior
475 // images are redistributed). Zero force-call cost; next NEB
476 // iteration recomputes all forces anyway.
477 bool didResample = false;
478 if (!result.convergedAfterMMF && result.newForce < convForce &&
479 path[climbingImage]->getPeriodic() &&
480 path[0]->numberOfAtoms() > 6) {
482 std::span{path.data(), path.size()});
483 movedAfterForceCall = true;
484 didResample = true;
485 }
486
487 // Reset optimizer AFTER reparameterization so fresh L-BFGS
488 // starts from the redistributed positions.
489 if (result.shouldResetOptimizer || didResample) {
491 objf, params.neb_options().opt_method, params);
492 }
493 }
494
495 long iterLimit = params.neb_options().max_iterations;
496 if (zoomDone && params.neb_options().zoom.max_iterations > 0 &&
497 zoomAt >= 0) {
498 iterLimit = zoomAt + params.neb_options().zoom.max_iterations;
499 }
500 if (iteration >= iterLimit) {
502 break;
503 }
504
505 if (zoomedThisStep) {
506 iteration++;
507 continue;
508 }
509
510 // Set CI state so updateForces() inside the optimizer step
511 // applies the correct force projection.
512 setCIEnabled(ci_active);
513
514 auto &activeOptim =
515 (refine_optim &&
516 convForce <= params.optimizer_options().refine.threshold)
517 ? refine_optim
518 : optim;
519 if (refine_optim &&
520 convForce <= params.optimizer_options().refine.threshold &&
521 !switched) {
522 switched = true;
523 EONC_LOG_DEBUG("Switched to {}",
524 magic_enum::enum_name<OptType>(
525 params.optimizer_options().refine.method));
526 }
527 activeOptim->step(params.optimizer_options().max_move);
528
529 setCIEnabled(params.neb_options().climbing_image.enabled);
530 }
531
532 iteration++;
533
534 double dE = path[maxEnergyImage]->getPotentialEnergy() - reactantEnergy;
535 double stepSize = 0.0;
536 if (solidState_) {
537 const VectorXd delta = objf->difference(objf->getPositions(), pos);
538 const long seg = 3L * atoms + 9L;
539 for (long image = 0; image < numImages; ++image) {
540 stepSize = std::max(
541 stepSize, delta.segment(image * seg, seg).cwiseAbs().maxCoeff());
542 }
543 } else {
545 path[0]->pbcV(objf->getPositions() - pos));
546 }
547 QUILL_LOG_DEBUG(log, "{:>10} {:>12.4e} {:>14.4e} {:>11} {:>12.4}",
548 iteration, stepSize, convergenceForce(), maxEnergyImage,
549 dE);
550
551 if (pot->getType() == PotType::CatLearn) {
552 if (objf->isUncertain()) {
553 QUILL_LOG_DEBUG(log, "NEB failed due to high uncertainty");
555 break;
556 } else if (objf->isConverged()) {
557 QUILL_LOG_DEBUG(log, "NEB converged\n");
559 break;
560 }
561 } else {
562 if (objf->isConverged()) {
563 QUILL_LOG_DEBUG(log, "NEB converged\n");
565 break;
566 }
567 }
568 }
569 return status;
570}
double matDot(const AtomMatrix &a, const AtomMatrix &b)
SIMD-optimized dot product for contiguous Eigen matrices.
Definition Eigen.h:50
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
#define EONC_LOG_DEBUG(...)
Definition EonLogger.h:243
#define EONC_LOG_WARNING(...)
Definition EonLogger.h:255
void printImageData(bool writeToFile=false, size_t idx=0)
void setCIEnabled(bool enabled)
friend class eonc::neb::OCINEBController
static Config fromParams(const Parameters &params)
double maxAtomMotionV(const VectorXd v1)
void resamplePathInPlace(std::span< std::shared_ptr< Matter > > path)
In-place path reparameterization for NEB shared_ptr paths.
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
Definition ConFileIO.h:38
Window selectWindow(const std::vector< double > &energy, std::size_t climbingImage, const neb_options_t::zoom_options_t &cfg)
Auto: contiguous images around the climbing image whose energy is above E_ref + alpha * barrier.
Definition NEBZoom.cpp:74
bool redistributePath(std::vector< std::shared_ptr< Matter > > &path, Window window, neb_options_t::zoom_options_t::Interpolation how)
Place every band image on the window by equal arc length.
Definition NEBZoom.cpp:118
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, double referenceEnergy)
Write a NEB band as a multi-frame .con via readcon ConFrameBuilder::clone().

◆ convergenceForce()

double eonc::NudgedElasticBand::convergenceForce ( void )

Definition at line 573 of file NudgedElasticBand.cpp.

573 {
575 updateForces();
576
577 auto imageForce = [&](long i) -> double {
578 const double cellNorm =
579 solidState_ ? projectedCellForce[static_cast<size_t>(i)].norm() : 0.0;
580 if (params.optimizer_options().convergence_metric == "norm") {
581 return std::hypot(projectedForce[i]->norm(), cellNorm);
582 }
583 if (params.optimizer_options().convergence_metric == "max_atom") {
584 // Every image shares the reactant constraint mask.
585 return std::max(path[0]->maxFreeAtomForce(*projectedForce[i]), cellNorm);
586 }
587 if (params.optimizer_options().convergence_metric == "max_component") {
588 double component = projectedForce[i]->cwiseAbs().maxCoeff();
589 if (solidState_) {
590 component = std::max(
591 component,
592 projectedCellForce[static_cast<size_t>(i)].cwiseAbs().maxCoeff());
593 }
594 return component;
595 }
597 QUILL_LOG_CRITICAL(
598 log, "[Nudged Elastic Band] unknown opt_convergence_metric: {}",
599 params.optimizer_options().convergence_metric);
600 throw std::invalid_argument(
601 std::format("[Nudged Elastic Band] unknown convergence_metric: {}",
602 params.optimizer_options().convergence_metric));
603 };
604
605 double bandMax = 0;
606 for (long i = 1; i <= numImages; ++i) {
607 bandMax = std::max(bandMax, imageForce(i));
608 }
609
610 const bool ciOnly = params.neb_options().climbing_image.converged_only &&
612 if (!ciOnly) {
613 return bandMax;
614 }
615
616 const double ciForce = imageForce(climbingImage);
617 const double slack = params.neb_options().climbing_image.band_slack;
618 const double tol = params.neb_options().force_tolerance;
619 if (slack > 0.0 && bandMax > slack * tol) {
620 return bandMax;
621 }
622 return ciForce;
623}
quill::Logger * traceback() noexcept
Get or create the "_traceback" logger for traceback logging.
Definition EonLogger.h:88

◆ findExtrema()

void eonc::NudgedElasticBand::findExtrema ( void )

Definition at line 838 of file NudgedElasticBand.cpp.

838 {
840 numExtrema = result.numExtrema;
841 extremumPosition = std::move(result.positions);
842 extremumEnergy = std::move(result.energies);
843 extremumCurvature = std::move(result.curvatures);
844}
ExtremaResult findSplineExtrema(const std::vector< std::shared_ptr< Matter > > &path, const std::vector< std::shared_ptr< AtomMatrix > > &tangent, long numImages)
Find extrema along the MEP using cubic spline interpolation.

◆ getStatus()

NudgedElasticBand::NEBStatus eonc::NudgedElasticBand::getStatus ( )
inline

Definition at line 59 of file NudgedElasticBand.h.

59{ return this->status; };

◆ pathFrames()

std::vector< readcon::ConFrame > eonc::NudgedElasticBand::pathFrames ( std::optional< size_t > bandIndex = std::nullopt)
nodiscard

In-memory ConFrames with the same NEB stamps as writePathCon / neb.con.

Empty if path is incomplete. Does not write to disk.

Definition at line 847 of file NudgedElasticBand.cpp.

847 {
850 params.debug_options().estimate_neb_eigenvalues, bandIndex,
852}
std::vector< readcon::ConFrame > pathToConFrames(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::optional< size_t > bandIndex, double referenceEnergy)
Build stamped ConFrames for a NEB band (same metadata as writePathCon).

◆ prepareSolidState()

void eonc::NudgedElasticBand::prepareSolidState ( )
private

Definition at line 914 of file NudgedElasticBand.cpp.

914 {
915 if (!params.neb_options().solid_state.enabled) {
916 return;
917 }
918 refuseSolidStateCombination(params);
919 for (const auto &image : path) {
920 if (!image->getPeriodic()) {
921 throw std::invalid_argument(
922 "solid_state NEB requires periodic boundaries on every image");
923 }
925 }
926 const double volume0 = std::abs(path.front()->getCell().determinant());
927 const double volume1 = std::abs(path.back()->getCell().determinant());
929 eonc::neb::solidStateJacobian(0.5 * (volume0 + volume1), atoms,
930 params.neb_options().solid_state.weight);
931 projectedCellForce.assign(static_cast<size_t>(numImages + 2),
932 Matrix3d::Zero());
933 solidState_ = true;
934 QUILL_LOG_INFO(log, "Solid-state NEB Jacobian {:.6f} Angstrom",
936}
double solidStateJacobian(double meanVolume, int nAtoms, double weight)
J = (V/N)^{1/3} * N^{1/2} * weight, with V the mean endpoint volume.

◆ printImageData()

void eonc::NudgedElasticBand::printImageData ( bool writeToFile = false,
size_t idx = 0 )

Definition at line 832 of file NudgedElasticBand.cpp.

832 {
834 params.debug_options().estimate_neb_eigenvalues,
835 writeToFile, idx, log, reactantEnergy);
836}
void printImageData(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, bool writeToFile, size_t idx, eonc::log::Scoped log, double referenceEnergy)
Print NEB image data to log and optionally to file.

◆ projectSolidState()

void eonc::NudgedElasticBand::projectSolidState ( bool ci_active)
private

Definition at line 938 of file NudgedElasticBand.cpp.

938 {
939 const double pressure = params.neb_options().solid_state.pressure;
940 const Matrix3d external = Matrix3d::Identity() * pressure;
941 std::vector<double> enthalpy(static_cast<size_t>(numImages + 2));
942 for (long i = 0; i <= numImages + 1; ++i) {
943 enthalpy[static_cast<size_t>(i)] =
944 eonc::neb::solidStateEnthalpy(*path[i], *path[0], pressure);
945 }
946
947 long highest = 1;
948 for (long i = 2; i <= numImages; ++i) {
949 if (enthalpy[static_cast<size_t>(i)] >
950 enthalpy[static_cast<size_t>(highest)]) {
951 highest = i;
952 }
953 }
954 maxEnergyImage = static_cast<size_t>(highest);
955 const double endpointEnthalpy = std::max(enthalpy.front(), enthalpy.back());
956 const bool climb =
957 ci_active && enthalpy[static_cast<size_t>(highest)] > endpointEnthalpy;
958 climbingImage = 0;
959
960 if (params.neb_options().spring.weighting.enabled) {
961 E_ref = std::max(path[0]->getPotentialEnergy(),
962 path[numImages + 1]->getPotentialEnergy());
963 }
964 const double maxEnergy = path[highest]->getPotentialEnergy();
966 maxEnergy, E_ref);
967 if (const auto *uniform = std::get_if<eonc::neb::UniformSpring>(&spring)) {
968 ksp = uniform->ksp;
969 }
970
971 for (long i = 1; i <= numImages; ++i) {
972 const double volume = std::abs(path[i]->getCell().determinant());
973 Matrix3d stress =
974 pot->computesStress()
975 ? path[i]->cauchyStress()
977 Matrix3d cellTrue =
978 eonc::neb::cellNebForce(stress, volume, solidJacobian_, external);
979 AtomMatrix atomicTrue = path[i]->getForces();
980 for (long atom = 0; atom < atoms; ++atom) {
981 if (path[i]->getFixed(atom)) {
982 atomicTrue.row(atom).setZero();
983 }
984 }
985
986 const eonc::neb::JointBlock toNext =
988 const eonc::neb::JointBlock toPrev =
990 const AtomMatrix packedNext = packJoint(toNext.atomic, toNext.cell);
991 const AtomMatrix packedPrev = packJoint(toPrev.atomic, toPrev.cell);
992 const double energy = enthalpy[static_cast<size_t>(i)];
993 const double energyPrev = enthalpy[static_cast<size_t>(i - 1)];
994 const double energyNext = enthalpy[static_cast<size_t>(i + 1)];
995 const AtomMatrix packedTangent = std::visit(
996 [&](auto &tangentStrategy) {
997 return tangentStrategy.compute(packedNext, packedPrev, energy,
998 energyPrev, energyNext);
999 },
1001 *tangent[i] = packedTangent.topRows(atoms);
1002
1003 AtomMatrix packedForce = packJoint(atomicTrue, cellTrue);
1004 AtomMatrix total;
1005 if (climb && i == highest) {
1006 climbingImage = static_cast<size_t>(highest);
1007 const double parallel = matDot(packedForce, packedTangent);
1008 total = packedForce - 2.0 * parallel * packedTangent;
1009 } else {
1010 const double parallel = matDot(packedForce, packedTangent);
1011 const AtomMatrix perpendicular = packedForce - parallel * packedTangent;
1012 const double scale = springScale(spring, i, eonc::neb::jointNorm(toNext),
1013 eonc::neb::jointNorm(toPrev));
1014 total = perpendicular + scale * packedTangent;
1015 }
1016 *projectedForce[i] = total.topRows(atoms);
1017 projectedCellForce[static_cast<size_t>(i)] = total.bottomRows(3);
1018 projectedCellForce[static_cast<size_t>(i)](0, 1) = 0.0;
1019 projectedCellForce[static_cast<size_t>(i)](0, 2) = 0.0;
1020 projectedCellForce[static_cast<size_t>(i)](1, 2) = 0.0;
1021 for (long atom = 0; atom < atoms; ++atom) {
1022 if (path[i]->getFixed(atom)) {
1023 projectedForce[i]->row(atom).setZero();
1024 }
1025 }
1026 eonc::neb::zeroTranslation(*projectedForce[i], path[i]->numberOfFreeAtoms(),
1027 path[i]->numberOfAtoms());
1028 }
1029}
Eigen::Matrix< double, 3, 3, eOnStorageOrder > Matrix3d
Definition Eigen.h:35
void zeroTranslation(AtomMatrix &projectedForce, int nFreeAtoms, int nAtoms)
Zero net translational force for fully free systems.
SpringStrategy buildSpringStrategy(const Parameters &params, const std::vector< std::shared_ptr< Matter > > &path, long numImages, int atoms, double maxEnergy, double E_ref)
Build the appropriate spring strategy from parameters and current path state.
double jointNorm(const JointBlock &block)
JointBlock jointDisplacement(const Matter &from, const Matter &to, double jacobian)
Displacement of to relative to from in the joint metric.
double solidStateEnthalpy(const Matter &image, const Matter &reference, double pressure)
Potential energy plus P : (h0^{-1} (h-h0)) * V0.
Matrix3d finiteDifferenceCauchyStress(const Matter &image, double strainStep)
Central difference of the potential energy on the six lower strain components.
Matrix3d cellNebForce(const Matrix3d &cauchy, double volume, double jacobian, const Matrix3d &externalStress)
NEB force on the Jacobian-scaled strain.

◆ setCIEnabled()

void eonc::NudgedElasticBand::setCIEnabled ( bool enabled)
inline

Definition at line 67 of file NudgedElasticBand.h.

67{ ci_enabled_ = enabled; }

◆ solidJacobian()

double eonc::NudgedElasticBand::solidJacobian ( ) const
inlinenodiscardnoexcept

Definition at line 61 of file NudgedElasticBand.h.

61{ return solidJacobian_; }

◆ solidState()

bool eonc::NudgedElasticBand::solidState ( ) const
inlinenodiscardnoexcept

Definition at line 60 of file NudgedElasticBand.h.

60{ return solidState_; }

◆ updateForces() [1/2]

void eonc::NudgedElasticBand::updateForces ( bool ci_active)

Definition at line 626 of file NudgedElasticBand.cpp.

626 {
627 // Update forces for all intermediate images. Prefer batched evaluation
628 // (single model.forward() over all dirty images, e.g. MetatomicPotential
629 // on GPU). Else fall back to per-image evaluation, which is itself
630 // thread-parallel when (a) the potential is thread-safe on the same
631 // instance, or (b) per-image instances were created (separate models).
632 if (pot->supportsBatchEvaluation() && numImages > 1) {
633 // Collect only images that need recomputation (positions changed).
634 // Materialize atomic numbers and cells first, then build the raw-pointer
635 // arrays after storage is stable. Otherwise vector growth can invalidate
636 // earlier .data() pointers and hand garbage cells/types to forceBatch().
637 std::vector<long> dirty; // indices into path[] (1-based)
638 dirty.reserve(numImages);
639 for (long i = 1; i <= numImages; i++) {
640 if (path[i]->needsForceUpdate()) {
641 dirty.push_back(i);
642 }
643 }
644
645 if (!dirty.empty()) {
646 auto nDirty = static_cast<long>(dirty.size());
647 std::vector<VectorXi> nrsStore;
648 std::vector<Matrix3d> boxStore;
649 std::vector<const double *> posVec, boxVec;
650 std::vector<const int *> nrsVec;
651 std::vector<double *> frcVec;
652 nrsStore.reserve(static_cast<size_t>(nDirty));
653 boxStore.reserve(static_cast<size_t>(nDirty));
654 posVec.reserve(static_cast<size_t>(nDirty));
655 boxVec.reserve(static_cast<size_t>(nDirty));
656 nrsVec.reserve(static_cast<size_t>(nDirty));
657 frcVec.reserve(static_cast<size_t>(nDirty));
658
659 for (long idx : dirty) {
660 nrsStore.push_back(path[idx]->getAtomicNrs());
661 // Isolated molecules still store a box for I/O. Pots that infer
662 // PBC from a non-zero cell must see a zero box, as
663 // Matter::computePotential does on the endpoints.
664 boxStore.push_back(path[idx]->getPeriodic() ? path[idx]->getCell()
665 : Matrix3d::Zero());
666 }
667 for (long j = 0; j < nDirty; j++) {
668 auto idx = dirty[static_cast<size_t>(j)];
669 posVec.push_back(path[idx]->getPositions().data());
670 nrsVec.push_back(nrsStore[static_cast<size_t>(j)].data());
671 frcVec.push_back(path[idx]->forcesData());
672 boxVec.push_back(boxStore[static_cast<size_t>(j)].data());
673 }
674
675 std::vector<double> energies(nDirty), variances(nDirty);
676 // Image i is system i - 1 to the potential's router, whether or not
677 // the images before it are dirty.
678 std::vector<long> owners(static_cast<size_t>(nDirty));
679 for (long j = 0; j < nDirty; j++) {
680 owners[static_cast<size_t>(j)] = dirty[static_cast<size_t>(j)] - 1;
681 }
682 pot->forceBatchOwned(nDirty, atoms, posVec.data(), nrsVec.data(),
683 frcVec.data(), energies.data(), variances.data(),
684 boxVec.data(), owners.data());
685 for (long j = 0; j < nDirty; j++) {
686 path[dirty[j]]->setComputedPotential(energies[j], variances[j]);
687 }
688 }
689 } else {
690 // Per-image evaluation (sequential or parallel threads)
691 bool canParallel =
693 if (numImages > 1 && params.main_options().parallel && canParallel) {
694#ifdef EON_PARALLEL_NEB
695 // nvc++ -stdpar=multicore|gpu (meson -Dstdpar=cpu|gpu).
696 std::vector<long> beads(static_cast<size_t>(numImages));
697 std::iota(beads.begin(), beads.end(), 1);
698 std::for_each(std::execution::par, beads.begin(), beads.end(),
699 [this](long i) { path[i]->getForcesRaw(); });
700#else
702 [this](long i) { path[i]->getForcesRaw(); });
703#endif
704 } else {
705 for (long i = 1; i <= numImages; i++) {
706 path[i]->getForcesRaw();
707 }
708 }
709 }
710
711 if (solidState_) {
712 projectSolidState(ci_active);
713 movedAfterForceCall = false;
714 return;
715 }
716
717 // Find the highest energy non-endpoint image
718 auto first = path.begin() + 1;
719 auto last = path.begin() + numImages + 1;
720 auto it = std::max_element(
721 first, last,
722 [](const std::shared_ptr<Matter> &a, const std::shared_ptr<Matter> &b) {
723 return a->getPotentialEnergy() < b->getPotentialEnergy();
724 });
725 maxEnergyImage = std::distance(path.begin(), it);
726 double maxEnergy = (*it)->getPotentialEnergy();
727
728 // Update E_ref for energy weighting. The higher endpoint keeps the
729 // soft spring on the side below that minimum.
730 if (params.neb_options().spring.weighting.enabled) {
731 E_ref = std::max(path[0]->getPotentialEnergy(),
732 path[numImages + 1]->getPotentialEnergy());
733 }
734
735 // Climbing requires an interior peak above both fixed endpoints.
736 // A monotonic path retains the spring force on every interior image.
737 const double endpointEnergy = std::max(path.front()->getPotentialEnergy(),
738 path.back()->getPotentialEnergy());
739 const bool climb = ci_active && maxEnergy > endpointEnergy;
740 climbingImage = 0;
741
742 // Spring strategy must be rebuilt each iteration (depends on maxEnergy,
743 // E_ref). Tangent and projection strategies are cached as members.
745 maxEnergy, E_ref);
746
747 // Pre-allocate temporaries outside the loop to avoid repeated heap
748 // allocation of Nx3 matrices (each ~8KB for 337 atoms).
749 AtomMatrix posDiffNext(atoms, 3), posDiffPrev(atoms, 3);
750
751 for (long i = 1; i <= numImages; i++) {
752 const AtomMatrix &force = path[i]->getForces();
753 const AtomMatrix &pos = path[i]->getPositions();
754 const AtomMatrix &posPrev = path[i - 1]->getPositions();
755 const AtomMatrix &posNext = path[i + 1]->getPositions();
756 double energy = path[i]->getPotentialEnergy();
757 double energyPrev = path[i - 1]->getPotentialEnergy();
758 double energyNext = path[i + 1]->getPotentialEnergy();
759 posDiffNext.noalias() = posNext - pos;
760 posDiffNext = path[i]->pbc(posDiffNext);
761 posDiffPrev.noalias() = pos - posPrev;
762 posDiffPrev = path[i]->pbc(posDiffPrev);
763 double distNext = posDiffNext.norm();
764 double distPrev = posDiffPrev.norm();
765
766 // Tangent via strategy dispatch
767 *tangent[i] = std::visit(
768 [&](auto &t) {
769 return t.compute(posDiffNext, posDiffPrev, energy, energyPrev,
770 energyNext);
771 },
773
774 // Spring forces via strategy dispatch
775 eonc::neb::SpringResult springResult = std::visit(
776 [&](auto &s) -> eonc::neb::SpringResult {
777 using T = std::decay_t<decltype(s)>;
778 if constexpr (std::is_same_v<T, eonc::neb::UniformSpring>) {
779 this->ksp = s.ksp;
780 return s.compute(i, *tangent[i], distNext, distPrev, posDiffNext,
781 posDiffPrev, path[i]);
782 } else if constexpr (std::is_same_v<T, eonc::neb::WeightedSpring>) {
783 return s.compute(i, *tangent[i], distNext, distPrev);
784 } else {
785 return s.compute(i, *tangent[i], posNext, posPrev, pos, path[i]);
786 }
787 },
788 spring);
789
790 // Climbing image or projected force
791 if (climb && i == static_cast<long>(maxEnergyImage)) {
793 // CI force: F - 2*(F.t)*t, plus DNEB correction if active
794 AtomMatrix forceDNEB = AtomMatrix::Zero(atoms, 3);
795 if (const auto *dnebProj =
796 std::get_if<eonc::neb::DNEB_Projection>(&projectionStrat_)) {
797 AtomMatrix fPerp = eonc::neb::forcePerp(force, *tangent[i]);
798 forceDNEB = eonc::neb::computeDNEBComponent(springResult.forceSpring,
799 *tangent[i], fPerp,
800 dnebProj->use_switching);
801 }
802 *projectedForce[i] =
803 eonc::neb::climbingImageForce(force, *tangent[i], forceDNEB);
804 } else {
805 eonc::neb::ImageForceData data{force, *tangent[i], springResult,
806 path[i]->numberOfFreeAtoms(),
807 path[i]->numberOfAtoms()};
808 *projectedForce[i] = std::visit([&](auto &p) { return p.project(data); },
810 }
811
812 // The spring force acts along the tangent, which has a component on a
813 // fixed atom whenever the endpoints place it differently; a fixed atom
814 // carries no force on the band.
815 if (path[i]->numberOfFreeAtoms() < path[i]->numberOfAtoms()) {
816 for (long j = 0; j < atoms; j++) {
817 if (path[i]->getFixed(j)) {
818 projectedForce[i]->row(j).setZero();
819 }
820 }
821 }
822
823 eonc::neb::zeroTranslation(*projectedForce[i], path[i]->numberOfFreeAtoms(),
824 path[i]->numberOfAtoms());
825 }
826
827 movedAfterForceCall = false;
828}
void projectSolidState(bool ci_active)
AtomMatrix computeDNEBComponent(const AtomMatrix &forceSpring, const AtomMatrix &tangent, const AtomMatrix &fPerp, bool useSwitching)
Compute the DNEB force component for a given image.
AtomMatrix climbingImageForce(const AtomMatrix &force, const AtomMatrix &tangent, const AtomMatrix &forceDNEB)
Compute the climbing image projected force.
AtomMatrix forcePerp(const AtomMatrix &force, const AtomMatrix &tangent)
Compute the perpendicular component of force relative to the tangent.
bool potAllowsSharedInstance(const P &p) noexcept
void forEachImage(long n, Work &&work)

◆ updateForces() [2/2]

void eonc::NudgedElasticBand::updateForces ( void )
inline

Definition at line 66 of file NudgedElasticBand.h.

◆ eonc::neb::OCINEBController

friend class eonc::neb::OCINEBController
friend

Definition at line 39 of file NudgedElasticBand.h.

Member Data Documentation

◆ atoms

int eonc::NudgedElasticBand::atoms {0}

Definition at line 79 of file NudgedElasticBand.h.

79{0};

◆ baseline_force

double eonc::NudgedElasticBand::baseline_force {-1.0}
private

Definition at line 103 of file NudgedElasticBand.h.

103{-1.0};

◆ ci_enabled_

bool eonc::NudgedElasticBand::ci_enabled_ {false}
private

Definition at line 102 of file NudgedElasticBand.h.

102{false}; // runtime CI state, set by compute()

◆ climbingImage

long eonc::NudgedElasticBand::climbingImage {0}

Definition at line 80 of file NudgedElasticBand.h.

80{0}, climbingImage{0}, numExtrema{0};

◆ E_ref

double eonc::NudgedElasticBand::E_ref

Definition at line 98 of file NudgedElasticBand.h.

◆ eigenmode_solvers

std::vector<std::shared_ptr<EigenmodeStrategy> > eonc::NudgedElasticBand::eigenmode_solvers

Definition at line 77 of file NudgedElasticBand.h.

◆ extremumCurvature

std::vector<double> eonc::NudgedElasticBand::extremumCurvature

Definition at line 89 of file NudgedElasticBand.h.

◆ extremumEnergy

std::vector<double> eonc::NudgedElasticBand::extremumEnergy

Definition at line 87 of file NudgedElasticBand.h.

◆ extremumPosition

std::vector<double> eonc::NudgedElasticBand::extremumPosition

Definition at line 88 of file NudgedElasticBand.h.

◆ k_l

double eonc::NudgedElasticBand::k_l {0.0}

Definition at line 97 of file NudgedElasticBand.h.

97{0.0}; // Lower-bound value for the spring constant

◆ k_u

double eonc::NudgedElasticBand::k_u {0.0}

Definition at line 96 of file NudgedElasticBand.h.

96{0.0}; // Upper-bound value for the spring constant

◆ ksp

double eonc::NudgedElasticBand::ksp {0.0}

Definition at line 95 of file NudgedElasticBand.h.

95{0.0};

◆ log

eonc::log::Scoped eonc::NudgedElasticBand::log
private

Definition at line 107 of file NudgedElasticBand.h.

◆ maxEnergyImage

std::size_t eonc::NudgedElasticBand::maxEnergyImage {0}

Definition at line 91 of file NudgedElasticBand.h.

91{0};

◆ movedAfterForceCall

bool eonc::NudgedElasticBand::movedAfterForceCall {false}

Definition at line 92 of file NudgedElasticBand.h.

92{false};

◆ numExtrema

long eonc::NudgedElasticBand::numExtrema {0}

Definition at line 80 of file NudgedElasticBand.h.

80{0}, climbingImage{0}, numExtrema{0};

◆ numImages

long eonc::NudgedElasticBand::numImages {0}

Definition at line 80 of file NudgedElasticBand.h.

80{0}, climbingImage{0}, numExtrema{0};

◆ params

Parameters eonc::NudgedElasticBand::params
private

Definition at line 104 of file NudgedElasticBand.h.

◆ path

std::vector<std::shared_ptr<Matter> > eonc::NudgedElasticBand::path

Definition at line 84 of file NudgedElasticBand.h.

◆ perImagePotentials_

bool eonc::NudgedElasticBand::perImagePotentials_
Initial value:
{
false}

Whether per-image potential instances exist.

Definition at line 93 of file NudgedElasticBand.h.

93 {
94 false};

◆ pot

std::shared_ptr<Potential> eonc::NudgedElasticBand::pot
private

Definition at line 105 of file NudgedElasticBand.h.

◆ projectedCellForce

std::vector<Matrix3d> eonc::NudgedElasticBand::projectedCellForce
private

Definition at line 114 of file NudgedElasticBand.h.

◆ projectedForce

std::vector<std::shared_ptr<AtomMatrix> > eonc::NudgedElasticBand::projectedForce

Definition at line 86 of file NudgedElasticBand.h.

◆ projectionStrat_

neb::ProjectionStrategy eonc::NudgedElasticBand::projectionStrat_
private

Definition at line 111 of file NudgedElasticBand.h.

◆ reactantEnergy

double eonc::NudgedElasticBand::reactantEnergy {0.0}

Definition at line 83 of file NudgedElasticBand.h.

83{0.0};

◆ solidJacobian_

double eonc::NudgedElasticBand::solidJacobian_ {1.0}
private

Definition at line 113 of file NudgedElasticBand.h.

113{1.0};

◆ solidState_

bool eonc::NudgedElasticBand::solidState_ {false}
private

Definition at line 112 of file NudgedElasticBand.h.

112{false};

◆ status

NEBStatus eonc::NudgedElasticBand::status
private

Definition at line 106 of file NudgedElasticBand.h.

◆ tangent

std::vector<std::shared_ptr<AtomMatrix> > eonc::NudgedElasticBand::tangent

Definition at line 85 of file NudgedElasticBand.h.

◆ tangentStrat_

neb::TangentStrategy eonc::NudgedElasticBand::tangentStrat_
private

Definition at line 110 of file NudgedElasticBand.h.


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