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

#include <NudgedElasticBandJob.h>

Inheritance diagram for eonc::NudgedElasticBandJob:

Public Member Functions

 NudgedElasticBandJob (std::unique_ptr< Parameters > parameters)
 ~NudgedElasticBandJob (void)=default
std::vector< std::string > run (void)
 Virtual run; used solely for dynamic dispatch.
Public Member Functions inherited from eonc::Job
 Job (std::unique_ptr< Parameters > parameters)
 Job (std::shared_ptr< Potential > potPassed, const Parameters &parameters)
virtual ~Job ()=default
JobType getType ()

Private Member Functions

void printEndState (NudgedElasticBand::NEBStatus status)
void saveData (NudgedElasticBand::NEBStatus status, NudgedElasticBand *neb)

Private Attributes

std::vector< std::string > returnFiles
size_t fCallsNEB
eonc::log::Scoped m_log

Additional Inherited Members

Protected Attributes inherited from eonc::Job
JobType jtype
Parameters params
std::shared_ptr< Potentialpot

Detailed Description

Definition at line 22 of file NudgedElasticBandJob.h.

Constructor & Destructor Documentation

◆ NudgedElasticBandJob()

eonc::NudgedElasticBandJob::NudgedElasticBandJob ( std::unique_ptr< Parameters > parameters)
inline

Definition at line 25 of file NudgedElasticBandJob.h.

26 : Job(std::move(parameters)),
27 fCallsNEB{0} {}
Job(std::unique_ptr< Parameters > parameters)
Definition Job.h:58

◆ ~NudgedElasticBandJob()

eonc::NudgedElasticBandJob::~NudgedElasticBandJob ( void )
default

Member Function Documentation

◆ printEndState()

void NudgedElasticBandJob::printEndState ( NudgedElasticBand::NEBStatus status)
private

Definition at line 307 of file NudgedElasticBandJob.cpp.

307 {
308 QUILL_LOG_DEBUG(m_log, "Final state: ");
310 QUILL_LOG_DEBUG(m_log, "Nudged elastic band, successful.");
312 QUILL_LOG_DEBUG(m_log, "Nudged elastic band, too many iterations.");
313 else
314 QUILL_LOG_WARNING(m_log, "Unknown status: {}!", static_cast<int>(status));
315 return;
316}

◆ run()

std::vector< std::string > NudgedElasticBandJob::run ( void )
virtual

Virtual run; used solely for dynamic dispatch.

Implements eonc::Job.

Definition at line 24 of file NudgedElasticBandJob.cpp.

24 {
26 size_t f1;
27
28 std::string reactantFilename = eonc::helpers::getRelevantFile("reactant.con");
29 std::string productFilename = eonc::helpers::getRelevantFile("product.con");
30
31 // TS interpolation: only when initializer is FILE (explicit path loading).
32 // Previously, any ts.con in CWD would silently override SIDPP/IDPP paths.
33 bool tsInterpolate = false;
34 std::shared_ptr<Matter> transitionState = nullptr;
35 if (params.neb_options.initialization.method == NEBInit::FILE &&
36 std::filesystem::exists("ts.con")) {
37 std::string transitionStateFilename =
39 transitionState = std::make_shared<Matter>(pot, params);
40 tsInterpolate = true;
41 if (!eonc::io::io_ok(
42 transitionState->con2matter(transitionStateFilename))) {
43 EONC_LOG_CRITICAL("Failed to load {}", transitionStateFilename);
44 throw std::runtime_error("failed to load " + transitionStateFilename);
45 }
46 }
47
48 auto initial = std::make_shared<Matter>(pot, params);
49 auto final_state = std::make_shared<Matter>(pot, params);
50
51 // When initializer=file and initial_path_in is set, endpoints are the first
52 // and last frames of that list — do not require reactant.con / product.con
53 // (issue #278; buggy endpoint files were still loaded and could crash).
54 const bool usePathEndpoints =
55 params.neb_options.initialization.method == NEBInit::FILE &&
56 !params.neb_options.initialization.input_path.empty();
57
58 if (usePathEndpoints) {
59 const auto file_paths = eonc::helpers::neb_paths::readFilePaths(
60 params.neb_options.initialization.input_path);
61 if (file_paths.size() < 2) {
62 throw std::runtime_error(
63 "NEB initial_path_in must list at least two frames "
64 "(reactant and product endpoints)");
65 }
66 if (!eonc::io::io_ok(initial->con2matter(file_paths.front().string()))) {
67 EONC_LOG_CRITICAL("Failed to load NEB path reactant frame {}",
68 file_paths.front().string());
69 throw std::runtime_error("failed to load NEB path reactant frame");
70 }
71 if (!eonc::io::io_ok(final_state->con2matter(file_paths.back().string()))) {
72 EONC_LOG_CRITICAL("Failed to load NEB path product frame {}",
73 file_paths.back().string());
74 throw std::runtime_error("failed to load NEB path product frame");
75 }
76 QUILL_LOG_INFO(m_log,
77 "NEB endpoints from initial path list (skipped {} / {})",
78 reactantFilename, productFilename);
79 } else {
80 if (!eonc::io::io_ok(initial->con2matter(reactantFilename))) {
81 EONC_LOG_CRITICAL("Failed to load {}", reactantFilename);
82 throw std::runtime_error("failed to load reactant.con");
83 }
84 if (!eonc::io::io_ok(final_state->con2matter(productFilename))) {
85 EONC_LOG_CRITICAL("Failed to load {}", productFilename);
86 throw std::runtime_error("failed to load product.con");
87 }
88 }
89
90 // Endpoint minimization logic:
91 // - If params.neb_options.endpoints.minimize is false: never minimize
92 // endpoints.
93 // - If params.neb_options.endpoints.minimize is true and
94 // params.neb_options.initialization.input_path is empty: minimize endpoints.
95 // - If params.nebMinimEP is true and params.nebIpath is NOT empty:
96 // -> minimize endpoints only if params.nebMinimEPIpath is true.
97 // Log what decision was made so users can see behavior.
98 bool shouldMinimizeEndpoints = false;
99 if (!params.neb_options.endpoints.minimize) {
100 QUILL_LOG_DEBUG(m_log,
101 "minimize_endpoints == false: not minimizing endpoints.");
102 shouldMinimizeEndpoints = false;
103 } else {
104 if (params.neb_options.initialization.input_path.empty()) {
105 QUILL_LOG_DEBUG(m_log, "minimize_endpoints == true and nebIpath "
106 "empty: minimizing endpoints.");
107 shouldMinimizeEndpoints = true;
108 } else {
109 // nebIpath provided: only minimize if neb_options.endpoints.use_path_file
110 // explicitly allowed.
111 if (params.neb_options.endpoints.use_path_file) {
112 QUILL_LOG_DEBUG(
113 m_log,
114 "minimize_endpoints == true and nebIpath provided, but "
115 "minimize_endpoints_for_ipath == true: minimizing endpoints.");
116 shouldMinimizeEndpoints = true;
117 } else {
118 QUILL_LOG_DEBUG(
119 m_log,
120 "minimize_endpoints == true but nebIpath provided and "
121 "minimize_endpoints_for_ipath == false: not minimizing endpoints.");
122 shouldMinimizeEndpoints = false;
123 }
124 }
125 }
126
127 if (shouldMinimizeEndpoints) {
128 QUILL_LOG_DEBUG(m_log, "Minimizing reactant");
129 // TODO(rg): Maybe when we have even more parameters, false can be set by
130 // the user too..
131 initial->relax(false, params.debug_options.write_movies,
132 params.main_options.checkpoint, "react_neb", "react_neb");
133 // TODO(rg): How do we report the total E/F now? Currently this is just the
134 // total total, people might want "per-stage" totals (but they can also get
135 // them from the log.)
136 QUILL_LOG_DEBUG(m_log, "Minimized reactant in ");
137 QUILL_LOG_DEBUG(m_log, "Minimizing product");
138 final_state->relax(false, params.debug_options.write_movies,
139 params.main_options.checkpoint, "prod_neb", "prod_neb");
140 }
141
142 auto neb =
143 std::make_unique<NudgedElasticBand>(initial, final_state, params, pot);
144
145 if (tsInterpolate) {
146 AtomMatrix reactantToTS = transitionState->pbc(
147 transitionState->getPositions() - initial->getPositions());
148 AtomMatrix TSToProduct = transitionState->pbc(
149 final_state->getPositions() - transitionState->getPositions());
150 for (int image = 1; image <= neb->numImages; image++) {
151 int mid = neb->numImages / 2 + 1;
152 if (image < mid) {
153 double frac = static_cast<double>(image) / static_cast<double>(mid);
154 neb->path[image]->setPositions(initial->getPositions() +
155 frac * reactantToTS);
156 } else if (image > mid) {
157 double frac = static_cast<double>(image - mid) /
158 static_cast<double>(neb->numImages - mid + 1);
159 neb->path[image]->setPositions(transitionState->getPositions() +
160 frac * TSToProduct);
161 } else if (image == mid) {
162 neb->path[image]->setPositions(transitionState->getPositions());
163 }
164 }
165 }
166
168 status = neb->compute();
170
172 neb->printImageData();
173 neb->findExtrema();
174 }
175
176 printEndState(status);
177 saveData(status, neb.get());
178
179 return returnFiles;
180}
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
#define EONC_LOG_CRITICAL(...)
Definition EonLogger.h:268
std::shared_ptr< Potential > pot
Definition Job.h:55
Parameters params
Definition Job.h:54
void saveData(NudgedElasticBand::NEBStatus status, NudgedElasticBand *neb)
void printEndState(NudgedElasticBand::NEBStatus status)
std::vector< std::string > returnFiles
static PotRegistry & get() noexcept
Process-lifetime singleton.
size_t total_force_calls() const noexcept
std::vector< fs::path > readFilePaths(const std::string &listFilePath)
Reads a file where each line contains a path to another file.
std::string getRelevantFile(std::string filename)
constexpr bool io_ok(IoStatus s) noexcept
Definition ConFileIO.h:38

◆ saveData()

void NudgedElasticBandJob::saveData ( NudgedElasticBand::NEBStatus status,
NudgedElasticBand * neb )
private

Definition at line 182 of file NudgedElasticBandJob.cpp.

183 {
184 std::string resultsFilename("results.dat");
185 returnFiles.push_back(resultsFilename);
186
187 {
188 std::ofstream out(resultsFilename, std::ios::binary);
189 if (!out) {
190 QUILL_LOG_ERROR(m_log, "Failed to open {} for writing", resultsFilename);
191 return;
192 }
193
194 out << std::format("{} termination_reason\n", static_cast<int>(status));
195 out << std::format("{} termination_reason_text\n",
196 magic_enum::enum_name(status));
197 out << std::format(
198 "{} potential_type\n",
199 magic_enum::enum_name<PotType>(params.potential_options.potential));
200 out << std::format("{} total_force_calls\n",
201 PotRegistry::get().total_force_calls());
202 out << std::format("{} force_calls_neb\n", fCallsNEB);
203 out << std::format("{:f} energy_reference\n",
204 neb->path[0]->getPotentialEnergy());
205 out << std::format("{} number_of_images\n", neb->numImages);
206
207 for (long i = 0; i <= neb->numImages + 1; i++) {
208 out << std::format("{:f} image{}_energy\n",
209 neb->path[i]->getPotentialEnergy() -
210 neb->path[0]->getPotentialEnergy(),
211 i);
212 out << std::format("{:f} image{}_force\n",
213 neb->path[i]->getForces().norm(), i);
214 double proj_norm = (i >= 1 && i <= neb->numImages)
215 ? neb->projectedForce[i]->norm()
216 : 0.0;
217 out << std::format("{:f} image{}_projected_force\n", proj_norm, i);
218 }
219
220 long safeNumExtrema = std::min(
221 neb->numExtrema, static_cast<long>(neb->extremumPosition.size()));
222 out << std::format("{} number_of_extrema\n", safeNumExtrema);
223 for (long i = 0; i < safeNumExtrema; i++) {
224 out << std::format("{:f} extremum{}_position\n", neb->extremumPosition[i],
225 i);
226 out << std::format("{:f} extremum{}_energy\n", neb->extremumEnergy[i], i);
227 }
228 }
229
230 // Save the Full NEB Path
231 std::string nebFilename("neb.con");
232 returnFiles.push_back(nebFilename);
234 neb->path, neb->tangent, neb->eigenmode_solvers, neb->numImages,
235 params.debug_options.estimate_neb_eigenvalues, nebFilename))) {
236 QUILL_LOG_ERROR(m_log, "Failed to write {}", nebFilename);
237 }
238
239 // Save Discrete Saddle Point (Highest Energy Image)
240 std::string spFilename("sp.con");
241 if (!eonc::io::io_ok(
242 neb->path[neb->maxEnergyImage]->matter2con(spFilename))) {
243 QUILL_LOG_ERROR(m_log, "Failed to write {}", spFilename);
244 }
245 returnFiles.push_back(spFilename);
246
247 // Setup Dimer Configurations for Each Spline Peak
248 if (params.neb_options.mmf_peaks.enabled && neb->numExtrema > 0) {
249 int peakCount = 0;
250 for (long i = 0; i < neb->numExtrema; i++) {
251 // Filter 1: Only look at maxima (negative curvature)
252 // Filter 2: Energy threshold (e.g., peak must be > 0.05 eV above
253 // reactant)
254 double relativeEnergy =
255 neb->extremumEnergy[i] - neb->path[0]->getPotentialEnergy();
256
257 if (neb->extremumCurvature[i] < 0 &&
258 relativeEnergy > params.neb_options.mmf_peaks.tolerance) {
259 double posFraction = neb->extremumPosition[i];
260 int leftIdx = static_cast<int>(std::floor(posFraction));
261 double f = posFraction - leftIdx;
262
263 if (leftIdx < 0 || leftIdx >= neb->numImages + 1)
264 continue;
265
266 // 1. Write Interpolated Position (.con)
268 *neb->path[leftIdx], *neb->path[leftIdx + 1], f);
269 std::string peakPosFile = std::format("peak{:02d}_pos.con", peakCount);
270 if (!eonc::io::io_ok(peakPos.matter2con(peakPosFile))) {
271 QUILL_LOG_ERROR(m_log, "Failed to write {}", peakPosFile);
272 }
273 returnFiles.push_back(peakPosFile);
274
275 // 2. Write Interpolated Tangent as standard mode.dat
276 AtomMatrix peakMode = (1.0 - f) * (*neb->tangent[leftIdx]) +
277 f * (*neb->tangent[leftIdx + 1]);
278 peakMode.normalize();
279
280 std::string peakModeFile =
281 std::format("peak{:02d}_mode.dat", peakCount);
282 {
283 std::ofstream modeOut(peakModeFile);
284 if (modeOut) {
285 for (long row = 0; row < peakMode.rows(); ++row) {
286 modeOut << std::format("{:.17g} {:.17g} {:.17g}\n",
287 peakMode(row, 0), peakMode(row, 1),
288 peakMode(row, 2));
289 }
290 returnFiles.push_back(peakModeFile);
291 }
292 }
293
294 QUILL_LOG_INFO(
295 m_log,
296 "Generated MMF peak {:02d} at position {:.3f} (Energy: {:.3f} eV)",
297 peakCount, posFraction, relativeEnergy);
298 peakCount++;
299 }
300 }
301 }
302
303 returnFiles.push_back("neb.dat");
304 neb->printImageData(true, std::numeric_limits<size_t>::max());
305}
io::IoStatus matter2con(std::string filename, bool append=false, const io::ConFrameMetadata *metadata=nullptr)
Definition Matter.h:281
std::vector< double > extremumEnergy
void printImageData(bool writeToFile=false, size_t idx=0)
std::vector< std::shared_ptr< AtomMatrix > > tangent
std::vector< std::shared_ptr< Matter > > path
std::vector< double > extremumPosition
std::vector< std::shared_ptr< EigenmodeStrategy > > eigenmode_solvers
std::vector< std::shared_ptr< AtomMatrix > > projectedForce
std::vector< double > extremumCurvature
Matter interpolateImage(const Matter &A, const Matter &B, double fraction)
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().

Member Data Documentation

◆ fCallsNEB

size_t eonc::NudgedElasticBandJob::fCallsNEB
private

Definition at line 38 of file NudgedElasticBandJob.h.

◆ m_log

eonc::log::Scoped eonc::NudgedElasticBandJob::m_log
private

Definition at line 39 of file NudgedElasticBandJob.h.

◆ returnFiles

std::vector<std::string> eonc::NudgedElasticBandJob::returnFiles
private

Definition at line 37 of file NudgedElasticBandJob.h.


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