29#include "magic_enum/magic_enum.hpp"
37#ifdef EON_PARALLEL_NEB
46namespace fs = std::filesystem;
51 std::shared_ptr<Matter> finalPassed,
53 std::shared_ptr<Potential> potPassed)
56 auto &init_opt = parametersPassed.
neb_options().initialization;
57 const size_t base_count =
62 throw std::invalid_argument(
63 "solid_state accepts initializer linear or file");
66 auto aligned = eonc::IRACompare::alignReactantToProduct(
67 *initialPassed, *finalPassed, 1.0);
68 auto *log = eonc::log::get();
69 if (aligned.error != 0) {
72 "match_endpoints: IRA align failed (error {}), "
73 "interpolating the input order",
77 "match_endpoints: Hausdorff {:.4f} A after IRA "
78 "permute+rotate of the reactant",
79 aligned.hausdorffDistance);
84 const size_t relax_count =
86 ? base_count * init_opt.oversampling_factor
89 std::vector<Matter> path;
90 switch (init_opt.method) {
92 std::vector<fs::path> file_paths =
93 eonc::helpers::neb_paths::readFilePaths(init_opt.input_path);
94 path = eonc::helpers::neb_paths::filePathInit(
95 file_paths, *initialPassed, base_count);
98 path.front() = Matter(*initialPassed);
99 path.back() = Matter(*finalPassed);
104 *initialPassed, *finalPassed, relax_count, parametersPassed);
108 *initialPassed, *finalPassed, relax_count, parametersPassed);
113 *initialPassed, *finalPassed, relax_count, parametersPassed,
119 *initialPassed, *finalPassed, base_count);
124 if (init_opt.oversampling && path.size() > (base_count + 2)) {
125 auto *log = eonc::log::get();
127 "Decimating oversampled path ({} images) to "
128 "{} images via cubic spline.",
129 path.size() - 2, base_count);
132 path = eonc::helpers::neb_paths::resamplePath(path, base_count);
137 log,
"Relaxing decimated path to restore IDPP surface...");
140 std::shared_ptr<ObjectiveFunction> post_decim_objf =
141 std::make_shared<CollectiveIDPPObjectiveFunction>(
142 path, parametersPassed);
145 bool use_zbl = (init_opt.method == NEBInit::SIDPP_ZBL);
147 auto zbl_pot = eonc::helpers::neb_paths::createZBLPotential();
148 post_decim_objf = std::make_shared<ZBLRepulsiveIDPPObjective>(
149 post_decim_objf, zbl_pot, path, parametersPassed, 1.0);
153 post_decim_objf, parametersPassed.neb_options().opt_method,
158 parametersPassed.neb_options().initialization.max_iterations,
159 parametersPassed.neb_options().initialization.max_move);
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");
167 for (Matter &image : path) {
171 }
else if (parametersPassed.neb_options().solid_state.enabled &&
172 init_opt.method == NEBInit::FILE) {
173 for (Matter &image : path) {
179 parametersPassed, potPassed) {}
184 std::shared_ptr<Potential> potPassed)
185 :
ci_enabled_{parametersPassed.neb_options().climbing_image.enabled},
192 params.optimizer_options().convergence_metric,
"[Nudged Elastic Band]");
195 if (initPath.empty()) {
196 throw std::invalid_argument(
"NEB: initPath is empty");
198 if (initPath.size() !=
static_cast<size_t>(
numImages + 2)) {
199 throw std::invalid_argument(
"NEB: initPath.size() must be image_count + 2");
201 atoms = initPath.front().numberOfAtoms();
202 for (
size_t i = 1; i < initPath.size(); ++i) {
204 initPath[i],
"path images");
218 pot->needsPerImageInstance() &&
params.main_options().parallel;
221 "NEB: Creating per-image potential instances for "
222 "parallel force evaluation ({} images)",
226 for (
long i = 0; i <=
numImages + 1; i++) {
227 path[i] = std::make_shared<Matter>(std::move(initPath[i]));
233 auto cloned =
pot->clonePotential();
234 path[i]->setPotential(cloned ? cloned
238 tangent[i] = std::make_shared<AtomMatrix>();
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) {
263 ksp =
params.neb_options().spring.constant;
271 if (
params.debug_options().estimate_neb_eigenvalues) {
273 for (
long i = 0; i <=
numImages + 1; i++) {
284 QUILL_LOG_DEBUG(
log,
"Nudged elastic band calculation started.");
287 E_ref = std::max(
path[0]->getPotentialEnergy(),
292 auto objf = std::make_shared<NEBObjectiveFunction>(
this,
params);
294 bool switched{
false};
297 std::unique_ptr<Optimizer> refine_optim{
nullptr};
300 objf,
params.optimizer_options().refine.method,
params);
305 bool zoomDone{
false};
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,
319 QUILL_LOG_ERROR(
log,
"Failed to write NEB path movie for iteration {}",
326 path[0]->pbc(
path[1]->getPositions() -
path[0]->getPositions());
333 eonc::safemath::safe_normalize_inplace(maxTang);
337 maxImageMetadata.neb_bead =
static_cast<uint64_t
>(
maxEnergyImage);
338 maxImageMetadata.neb_band =
static_cast<uint64_t
>(iteration);
339 maxImageMetadata.scalars.push_back(
342 maxImageMetadata.scalars.push_back(
345 maxImageMetadata.strings.push_back({
"movie_kind",
"neb_maximage"});
347 "neb_maximage.con", append, &maxImageMetadata))) {
353 VectorXd pos = objf->getPositions();
358 if (iteration == 0) {
363 auto &ci_opt =
params.neb_options().climbing_image;
364 auto &mmf_opt = ci_opt.ocineb;
365 auto fmt_trigger = [](
double val) -> std::string {
368 return std::format(
"{:.4f}", val);
373 "===============================================================");
374 QUILL_LOG_INFO(
log,
" NEB Optimization Configuration");
377 "===============================================================");
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) {
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);
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})",
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",
407 "---------------------------------------------------------------");
409 EONC_LOG_DEBUG(
"{:>10s} {:>12s} {:>14s} {:>11s} {:>12s}",
"iteration",
411 params.optimizer_options().convergence_metric_label,
412 "max image",
"max energy");
415 "---------------------------------------------------------------\n");
420 params.neb_options().climbing_image.enabled &&
422 params.neb_options().climbing_image.trigger_factor ||
423 convForce <
params.neb_options().climbing_image.trigger_force);
425 bool zoomedThisStep =
false;
426 if (iteration && !zoomDone &&
params.neb_options().zoom.enabled &&
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());
448 zoom.interpolation)) {
453 zoomedThisStep =
true;
455 QUILL_LOG_INFO(
log,
"Zoom-NEB: packed the band onto images [{}, {}]",
456 window.lo, window.hi);
464 if (!zoomedThisStep &&
467 auto result = ocineb.
run(*
this, convForce);
469 if (result.convergedAfterMMF) {
477 bool didResample =
false;
478 if (!result.convergedAfterMMF && result.newForce < convForce &&
480 path[0]->numberOfAtoms() > 6) {
482 std::span{
path.data(),
path.size()});
489 if (result.shouldResetOptimizer || didResample) {
495 long iterLimit =
params.neb_options().max_iterations;
496 if (zoomDone &&
params.neb_options().zoom.max_iterations > 0 &&
498 iterLimit = zoomAt +
params.neb_options().zoom.max_iterations;
500 if (iteration >= iterLimit) {
505 if (zoomedThisStep) {
516 convForce <=
params.optimizer_options().refine.threshold)
520 convForce <=
params.optimizer_options().refine.threshold &&
524 magic_enum::enum_name<OptType>(
525 params.optimizer_options().refine.method));
527 activeOptim->step(
params.optimizer_options().max_move);
535 double stepSize = 0.0;
537 const VectorXd delta = objf->difference(objf->getPositions(), pos);
538 const long seg = 3L *
atoms + 9L;
539 for (
long image = 0; image <
numImages; ++image) {
541 stepSize, delta.segment(image * seg, seg).cwiseAbs().maxCoeff());
545 path[0]->pbcV(objf->getPositions() - pos));
547 QUILL_LOG_DEBUG(
log,
"{:>10} {:>12.4e} {:>14.4e} {:>11} {:>12.4}",
552 if (objf->isUncertain()) {
553 QUILL_LOG_DEBUG(
log,
"NEB failed due to high uncertainty");
556 }
else if (objf->isConverged()) {
557 QUILL_LOG_DEBUG(
log,
"NEB converged\n");
562 if (objf->isConverged()) {
563 QUILL_LOG_DEBUG(
log,
"NEB converged\n");
577 auto imageForce = [&](
long i) ->
double {
578 const double cellNorm =
580 if (
params.optimizer_options().convergence_metric ==
"norm") {
583 if (
params.optimizer_options().convergence_metric ==
"max_atom") {
587 if (
params.optimizer_options().convergence_metric ==
"max_component") {
590 component = std::max(
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));
607 bandMax = std::max(bandMax, imageForce(i));
610 const bool ciOnly =
params.neb_options().climbing_image.converged_only &&
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) {
637 std::vector<long> dirty;
640 if (
path[i]->needsForceUpdate()) {
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));
659 for (
long idx : dirty) {
660 nrsStore.push_back(
path[idx]->getAtomicNrs());
664 boxStore.push_back(
path[idx]->getPeriodic() ?
path[idx]->getCell()
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());
675 std::vector<double> energies(nDirty), variances(nDirty);
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;
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]);
694#ifdef EON_PARALLEL_NEB
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(); });
702 [
this](
long i) {
path[i]->getForcesRaw(); });
706 path[i]->getForcesRaw();
718 auto first =
path.begin() + 1;
720 auto it = std::max_element(
722 [](
const std::shared_ptr<Matter> &a,
const std::shared_ptr<Matter> &b) {
723 return a->getPotentialEnergy() < b->getPotentialEnergy();
726 double maxEnergy = (*it)->getPotentialEnergy();
730 if (
params.neb_options().spring.weighting.enabled) {
731 E_ref = std::max(
path[0]->getPotentialEnergy(),
737 const double endpointEnergy = std::max(
path.front()->getPotentialEnergy(),
738 path.back()->getPotentialEnergy());
739 const bool climb = ci_active && maxEnergy > endpointEnergy;
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();
769 return t.compute(posDiffNext, posDiffPrev, energy, energyPrev,
777 using T = std::decay_t<
decltype(s)>;
778 if constexpr (std::is_same_v<T, eonc::neb::UniformSpring>) {
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);
785 return s.compute(i, *
tangent[i], posNext, posPrev, pos,
path[i]);
795 if (
const auto *dnebProj =
800 dnebProj->use_switching);
806 path[i]->numberOfFreeAtoms(),
807 path[i]->numberOfAtoms()};
808 *
projectedForce[i] = std::visit([&](
auto &p) {
return p.project(data); },
815 if (
path[i]->numberOfFreeAtoms() <
path[i]->numberOfAtoms()) {
816 for (
long j = 0; j <
atoms; j++) {
817 if (
path[i]->getFixed(j)) {
824 path[i]->numberOfAtoms());
834 params.debug_options().estimate_neb_eigenvalues,
846std::vector<readcon::ConFrame>
850 params.debug_options().estimate_neb_eigenvalues, bandIndex,
856void refuseSolidStateCombination(
const Parameters ¶ms) {
858 if (!
neb.solid_state.enabled) {
861 if (neb.climbing_image.ocineb.use_mmf) {
862 throw std::invalid_argument(
863 "solid_state is set and ci_mmf is true. The min-mode walk does not "
866 if (neb.zoom.enabled) {
867 throw std::invalid_argument(
868 "solid_state is set and zoom_neb is true. Zoom does not move the cell");
870 if (neb.spring.om.enabled) {
871 throw std::invalid_argument(
872 "solid_state is set and onsager_machlup is true");
874 if (neb.spring.doubly_nudged) {
875 throw std::invalid_argument(
876 "solid_state is set and neb_doubly_nudged is true");
878 if (neb.spring.use_elastic_band) {
879 throw std::invalid_argument(
880 "solid_state is set and neb_elastic_band is true");
883 if (method != NEBInit::LINEAR && method != NEBInit::FILE) {
884 throw std::invalid_argument(
885 "solid_state accepts initializer linear or file");
887 if (!(neb.solid_state.weight > 0.0)) {
888 throw std::invalid_argument(
"solid_state_weight must be positive");
894 packed.topRows(atomic.rows()) = atomic;
895 packed.bottomRows(3) = cell;
900 double distNext,
double distPrev) {
901 if (
const auto *uniform = std::get_if<eonc::neb::UniformSpring>(&spring)) {
902 return uniform->ksp * (distNext - distPrev);
904 if (
const auto *weighted = std::get_if<eonc::neb::WeightedSpring>(&spring)) {
905 return weighted->springConstants[
static_cast<size_t>(image)] * distNext -
906 weighted->springConstants[
static_cast<size_t>(image - 1)] * distPrev;
908 throw std::invalid_argument(
909 "solid_state NEB does not use this spring strategy");
915 if (!
params.neb_options().solid_state.enabled) {
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");
926 const double volume0 = std::abs(
path.front()->getCell().determinant());
927 const double volume1 = std::abs(
path.back()->getCell().determinant());
930 params.neb_options().solid_state.weight);
934 QUILL_LOG_INFO(
log,
"Solid-state NEB Jacobian {:.6f} Angstrom",
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)] =
949 if (enthalpy[
static_cast<size_t>(i)] >
950 enthalpy[
static_cast<size_t>(highest)]) {
955 const double endpointEnthalpy = std::max(enthalpy.front(), enthalpy.back());
957 ci_active && enthalpy[
static_cast<size_t>(highest)] > endpointEnthalpy;
960 if (
params.neb_options().spring.weighting.enabled) {
961 E_ref = std::max(
path[0]->getPotentialEnergy(),
964 const double maxEnergy =
path[highest]->getPotentialEnergy();
967 if (
const auto *uniform = std::get_if<eonc::neb::UniformSpring>(&spring)) {
972 const double volume = std::abs(
path[i]->getCell().determinant());
974 pot->computesStress()
975 ?
path[i]->cauchyStress()
980 for (
long atom = 0; atom <
atoms; ++atom) {
981 if (
path[i]->getFixed(atom)) {
982 atomicTrue.row(atom).setZero();
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)];
996 [&](
auto &tangentStrategy) {
997 return tangentStrategy.compute(packedNext, packedPrev, energy,
998 energyPrev, energyNext);
1003 AtomMatrix packedForce = packJoint(atomicTrue, cellTrue);
1005 if (climb && i == highest) {
1007 const double parallel =
matDot(packedForce, packedTangent);
1008 total = packedForce - 2.0 * parallel * packedTangent;
1010 const double parallel =
matDot(packedForce, packedTangent);
1011 const AtomMatrix perpendicular = packedForce - parallel * packedTangent;
1014 total = perpendicular + scale * packedTangent;
1021 for (
long atom = 0; atom <
atoms; ++atom) {
1022 if (
path[i]->getFixed(atom)) {
1027 path[i]->numberOfAtoms());
double matDot(const AtomMatrix &a, const AtomMatrix &b)
SIMD-optimized dot product for contiguous Eigen matrices.
Eigen::Matrix< double, 3, 3, eOnStorageOrder > Matrix3d
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
#define EONC_LOG_DEBUG(...)
#define EONC_LOG_WARNING(...)
The optimizer class is used to serve as an abstract class for all optimizers, as well as to call an o...
NudgedElasticBand::NEBStatus compute(void)
NudgedElasticBand(std::shared_ptr< Matter > initialPassed, std::shared_ptr< Matter > finalPassed, const Parameters ¶metersPassed, std::shared_ptr< Potential > potPassed)
std::vector< double > extremumEnergy
std::size_t maxEnergyImage
std::vector< std::shared_ptr< AtomMatrix > > tangent
void printImageData(bool writeToFile=false, size_t idx=0)
std::vector< std::shared_ptr< Matter > > path
bool perImagePotentials_
Whether per-image potential instances exist.
std::vector< double > extremumPosition
std::vector< std::shared_ptr< EigenmodeStrategy > > eigenmode_solvers
void projectSolidState(bool ci_active)
std::vector< readcon::ConFrame > pathFrames(std::optional< size_t > bandIndex=std::nullopt)
In-memory ConFrames with the same NEB stamps as writePathCon / neb.con.
double convergenceForce(void)
neb::TangentStrategy tangentStrat_
std::vector< std::shared_ptr< AtomMatrix > > projectedForce
void setCIEnabled(bool enabled)
std::shared_ptr< Potential > pot
neb::ProjectionStrategy projectionStrat_
std::vector< double > extremumCurvature
std::vector< Matrix3d > projectedCellForce
const neb_options_t & neb_options() const
bool shouldTrigger(double convForce, bool ci_active, long climbingImage, long numImages, int ciStabilityCounter) const
static Config fromParams(const Parameters ¶ms)
void updateStability(long climbingImage)
MMFResult run(eonc::NudgedElasticBand &neb, double convForce)
void initBaseline(double baseline_force)
int stabilityCount() const
double maxAtomMotionV(const VectorXd v1)
std::unique_ptr< Optimizer > mkOptim(std::shared_ptr< ObjectiveFunction > a_objf, OptType a_otype, const Parameters &a_params)
std::vector< Matter > sidppPath(const Matter &initImg, const Matter &finalImg, size_t target_nimgs, const Parameters ¶ms, bool use_zbl)
void resamplePathInPlace(std::span< std::shared_ptr< Matter > > path)
In-place path reparameterization for NEB shared_ptr paths.
std::vector< Matter > linearPath(const Matter &initImg, const Matter &finalImg, const size_t nimgs)
void requireSameAtomCount(const Matter &a, const Matter &b, std::string_view what)
Abort before Eigen subtracts two position matrices of different size.
std::vector< Matter > idppPath(const Matter &initImg, const Matter &finalImg, const size_t nimgs, const Parameters ¶ms, bool use_zbl)
std::vector< Matter > idppCollectivePath(const Matter &initImg, const Matter &finalImg, size_t nimgs, const Parameters ¶ms, bool use_zbl)
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 ¶ms)
constexpr bool io_ok(IoStatus s) noexcept
quill::Logger * get() noexcept
Get or create the default "combi" logger.
quill::Logger * traceback() noexcept
Get or create the "_traceback" logger for traceback logging.
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.
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.
void interpolateSolidStateLinear(std::vector< Matter > &images)
Replace interior images by a fractional linear interpolation of the oriented endpoints.
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).
AtomMatrix computeDNEBComponent(const AtomMatrix &forceSpring, const AtomMatrix &tangent, const AtomMatrix &fPerp, bool useSwitching)
Compute the DNEB force component for a given image.
void zeroTranslation(AtomMatrix &projectedForce, int nFreeAtoms, int nAtoms)
Zero net translational force for fully free systems.
SpringStrategy buildSpringStrategy(const Parameters ¶ms, 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.
TangentStrategy buildTangentStrategy(const Parameters ¶ms)
Build the tangent strategy from parameters.
double jointNorm(const JointBlock &block)
ProjectionStrategy buildProjectionStrategy(const Parameters ¶ms)
Build the projection strategy from parameters.
std::variant< UniformSpring, WeightedSpring, OnsagerMachlupSpring > SpringStrategy
AtomMatrix climbingImageForce(const AtomMatrix &force, const AtomMatrix &tangent, const AtomMatrix &forceDNEB)
Compute the climbing image projected force.
JointBlock jointDisplacement(const Matter &from, const Matter &to, double jacobian)
Displacement of to relative to from in the joint metric.
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.
double solidStateEnthalpy(const Matter &image, const Matter &reference, double pressure)
Potential energy plus P : (h0^{-1} (h-h0)) * V0.
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().
double solidStateJacobian(double meanVolume, int nAtoms, double weight)
J = (V/N)^{1/3} * N^{1/2} * weight, with V the mean endpoint volume.
AtomMatrix forcePerp(const AtomMatrix &force, const AtomMatrix &tangent)
Compute the perpendicular component of force relative to the tangent.
void orientSolidStateMatter(Matter &image)
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.
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.
RAII resource manager for the ARTn C library with global synchronization.
void evaluateTogether(Potential &pot, std::span< Matter *const > systems)
Evaluates every system that needs a force update.
std::shared_ptr< LowestEigenmode > buildEigenmodeStrategy(std::shared_ptr< Matter > matter, const Parameters ¶ms, std::shared_ptr< Potential > pot)
bool potAllowsSharedInstance(const P &p) noexcept
void forEachImage(long n, Work &&work)
Data for a single image needed by projection strategies.
Result of spring force computation for a single image.
struct eonc::neb_options_t::solid_state_options_t solid_state
bool match_endpoints
Permute+rotate the reactant onto the product before interpolation.
struct eonc::neb_options_t::path_initialization_t initialization