Loading...
Searching...
No Matches
NEBSplineExtrema.cpp
Go to the documentation of this file.
1/*
2** This file is part of eOn.
3**
4** SPDX-License-Identifier: BSD-3-Clause
5**
6** Copyright (c) 2010--present, eOn Development Team
7** All rights reserved.
8**
9** Repo:
10** https://github.com/TheochemUI/eOn
11*/
13#include "eon/ConFileIO.h"
14#include <cmath>
15#include <format>
16#include <fstream>
17#include <limits>
18
19namespace fs = std::filesystem;
20
21namespace eonc::neb {
22
23namespace {
24
25eonc::io::ConFrameMetadata neb_frame_metadata(
26 const std::vector<std::shared_ptr<Matter>> &path,
27 const std::vector<std::shared_ptr<AtomMatrix>> &tangent,
28 const std::vector<std::shared_ptr<EigenmodeStrategy>> &eigenmode_solvers,
29 long numImages, bool estimateEigenvalues, long imageIndex,
30 double reactionCoordinate, std::optional<size_t> bandIndex) {
31 AtomMatrix tang;
32 if (imageIndex == 0) {
33 tang = path[0]->pbc(path[1]->getPositions() - path[0]->getPositions());
34 } else if (imageIndex == numImages + 1) {
35 tang = path[numImages]->pbc(path[numImages + 1]->getPositions() -
36 path[numImages]->getPositions());
37 } else {
38 tang = *tangent[imageIndex];
39 }
40 tang.normalize();
41
42 const double reference_energy = path[0]->getPotentialEnergy();
43 const double absolute_energy = path[imageIndex]->getPotentialEnergy();
44 const double relative_energy = absolute_energy - reference_energy;
45 const double parallel_force = matDot(path[imageIndex]->getForces(), tang);
46
47 eonc::io::ConFrameMetadata metadata;
48 metadata.frame_index = static_cast<uint64_t>(imageIndex);
49 metadata.energy = absolute_energy;
50 metadata.neb_bead = static_cast<uint64_t>(imageIndex);
51 if (bandIndex) {
52 metadata.neb_band = static_cast<uint64_t>(*bandIndex);
53 }
54 metadata.scalars.push_back({"reaction_coordinate", reactionCoordinate});
55 metadata.scalars.push_back({"relative_energy", relative_energy});
56 metadata.scalars.push_back({"parallel_force", parallel_force});
57
58 if (estimateEigenvalues && imageIndex >= 0 &&
59 imageIndex < static_cast<long>(eigenmode_solvers.size()) &&
60 eigenmode_solvers[imageIndex]) {
61 eonc::eigenmodeCompute(*eigenmode_solvers[imageIndex], path[imageIndex],
62 tang);
63 metadata.scalars.push_back(
64 {"lowest_eigenvalue",
65 eonc::eigenmodeGetEigenvalue(*eigenmode_solvers[imageIndex])});
66 }
67
68 return metadata;
69}
70
71} // namespace
72
74findSplineExtrema(const std::vector<std::shared_ptr<Matter>> &path,
75 const std::vector<std::shared_ptr<AtomMatrix>> &tangent,
76 long numImages) {
77
78 auto *log = eonc::log::get();
79
80 // Calculate cubic parameters for each interval
81 AtomMatrix tangentEndpoint;
82 std::vector<double> a(numImages + 1), b(numImages + 1), c(numImages + 1),
83 d(numImages + 1);
84 double F1, F2, U1, U2, dist;
85
86 for (long i = 0; i <= numImages; i++) {
87 dist = path[i]->distanceTo(*path[i + 1]);
88 if (i == 0) {
89 tangentEndpoint =
90 path[i]->pbc(path[1]->getPositions() - path[0]->getPositions());
91 tangentEndpoint.normalize();
92 F1 = matDot(path[i]->getForces(), tangentEndpoint) * dist;
93 } else {
94 F1 = matDot(path[i]->getForces(), *tangent[i]) * dist;
95 }
96 if (i == numImages) {
97 tangentEndpoint = path[i + 1]->pbc(path[numImages + 1]->getPositions() -
98 path[numImages]->getPositions());
99 tangentEndpoint.normalize();
100 F2 = matDot(path[i + 1]->getForces(), tangentEndpoint) * dist;
101 } else {
102 F2 = matDot(path[i + 1]->getForces(), *tangent[i + 1]) * dist;
103 }
104 U1 = path[i]->getPotentialEnergy();
105 U2 = path[i + 1]->getPotentialEnergy();
106 a[i] = U1;
107 b[i] = -F1;
108 c[i] = 3. * (U2 - U1) + 2. * F1 + F2;
109 d[i] = -2. * (U2 - U1) - (F1 + F2);
110 }
111
112 ExtremaResult result;
113 result.positions.resize(2 * (numImages + 1));
114 result.energies.resize(2 * (numImages + 1));
115 result.curvatures.resize(2 * (numImages + 1));
116
117 double discriminant, f;
118
119 for (long i = 0; i <= numImages; i++) {
120 discriminant = c[i] * c[i] - 3.0 * b[i] * d[i];
121 if (discriminant >= 0) {
122 f = -1;
123
124 // Quadratic case
125 if ((d[i] == 0) && (c[i] != 0)) {
126 f = (-b[i] / (2. * c[i]));
127 }
128 // Cubic case 1
129 else if (d[i] != 0) {
130 f = -(c[i] + std::sqrt(discriminant)) / (3. * d[i]);
131 }
132 if ((f >= 0) && (f <= 1)) {
133 result.positions[result.numExtrema] = i + f;
134 result.energies[result.numExtrema] =
135 ((d[i] * f + c[i]) * f + b[i]) * f + a[i]; // Horner's method
136 result.curvatures[result.numExtrema] = 6.0 * d[i] * f + 2 * c[i];
137 result.numExtrema++;
138 }
139 // Cubic case 2
140 if (d[i] != 0) {
141 f = (-(c[i] - std::sqrt(discriminant)) / (3. * d[i]));
142 }
143 if ((f >= 0) && (f <= 1)) {
144 result.positions[result.numExtrema] = i + f;
145 result.energies[result.numExtrema] =
146 ((d[i] * f + c[i]) * f + b[i]) * f + a[i]; // Horner's method
147 result.curvatures[result.numExtrema] = 6 * d[i] * f + 2 * c[i];
148 result.numExtrema++;
149 }
150 }
151 }
152
153 QUILL_LOG_DEBUG(log, "Found {} extrema", result.numExtrema);
154 QUILL_LOG_DEBUG(log, "Energy reference: {}", path[0]->getPotentialEnergy());
155 for (long i = 0; i < result.numExtrema; i++) {
156 QUILL_LOG_DEBUG(
157 log, "extrema #{} at image position {} with energy {} and curvature {}",
158 i + 1, result.positions[i],
159 result.energies[i] - path[0]->getPotentialEnergy(),
160 result.curvatures[i]);
161 }
162
163 return result;
164}
165
167 const std::vector<std::shared_ptr<Matter>> &path,
168 const std::vector<std::shared_ptr<AtomMatrix>> &tangent,
169 const std::vector<std::shared_ptr<EigenmodeStrategy>> &eigenmode_solvers,
170 long numImages, bool estimateEigenvalues, bool writeToFile, size_t idx,
172
173 double dist, distTotal = 0;
174 AtomMatrix tangentStart =
175 path[0]->pbc(path[1]->getPositions() - path[0]->getPositions());
176 AtomMatrix tangentEnd = path[numImages]->pbc(
177 path[numImages + 1]->getPositions() - path[numImages]->getPositions());
178 AtomMatrix tang;
179 std::string header;
180 if (estimateEigenvalues) {
181 header = std::format("{:>3s} {:>12s} {:>12s} {:>12s} {:>12s}", "img",
182 "rxn_coord", "energy", "f_para", "eigval");
183 } else {
184 header = std::format("{:>3s} {:>12s} {:>12s} {:>12s}", "img", "rxn_coord",
185 "energy", "f_para");
186 }
187
188 tangentStart.normalize();
189 tangentEnd.normalize();
190
191 std::ofstream fileLogger;
192 if (writeToFile) {
193 std::string neb_dat_fs;
194 if (idx == std::numeric_limits<size_t>::max()) {
195 neb_dat_fs = "neb.dat";
196 } else {
197 neb_dat_fs = std::format("neb_{:03}.dat", idx);
198 }
199 if (fs::exists(neb_dat_fs)) {
200 fs::remove(neb_dat_fs);
201 }
202 fileLogger.open(neb_dat_fs);
203 if (fileLogger.is_open()) {
204 fileLogger << header << "\n";
205 }
206 }
207 const double energy_reactant = path[0]->getPotentialEnergy();
208
209 for (long i = 0; i <= numImages + 1; i++) {
210 if (i == 0) {
211 tang = tangentStart;
212 } else if (i == numImages + 1) {
213 tang = tangentEnd;
214 } else {
215 tang = *tangent[i];
216 }
217
218 if (i > 0) {
219 dist = path[i]->distanceTo(*path[i - 1]);
220 distTotal += dist;
221 }
222
223 double relative_energy = path[i]->getPotentialEnergy() - energy_reactant;
224 double parallel_force = matDot(path[i]->getForces(), tang);
225
226 if (estimateEigenvalues) {
227 eonc::eigenmodeCompute(*eigenmode_solvers[i], path[i], tang);
228 double lowest_eigenvalue =
229 eonc::eigenmodeGetEigenvalue(*eigenmode_solvers[i]);
230 if (fileLogger.is_open()) {
231 fileLogger << std::format(
232 "{:>3} {:>12.6f} {:>12.6f} {:>12.6f} {:>12.6f}\n", i, distTotal,
233 relative_energy, parallel_force, lowest_eigenvalue);
234 } else {
235 QUILL_LOG_DEBUG(log, "{:>3} {:>12.6f} {:>12.6f} {:>12.6f} {:>12.6f}", i,
236 distTotal, relative_energy, parallel_force,
237 lowest_eigenvalue);
238 }
239 } else {
240 if (fileLogger.is_open()) {
241 fileLogger << std::format("{:>3} {:>12.6f} {:>12.6f} {:>12.6f}\n", i,
242 distTotal, relative_energy, parallel_force);
243 } else {
244 QUILL_LOG_DEBUG(log, "{:>3} {:>12.6f} {:>12.6f} {:>12.6f}", i,
245 distTotal, relative_energy, parallel_force);
246 }
247 }
248 }
249}
250
251std::vector<readcon::ConFrame> pathToConFrames(
252 const std::vector<std::shared_ptr<Matter>> &path,
253 const std::vector<std::shared_ptr<AtomMatrix>> &tangent,
254 const std::vector<std::shared_ptr<EigenmodeStrategy>> &eigenmode_solvers,
255 long numImages, bool estimateEigenvalues, std::optional<size_t> bandIndex) {
256 const size_t nframes = static_cast<size_t>(numImages) + 2;
257 if (path.size() < nframes) {
258 return {};
259 }
260
261 double distTotal = 0.0;
262 std::vector<eonc::io::ConFrameMetadata> metas;
263 metas.reserve(nframes);
264
265 for (long i = 0; i <= numImages + 1; i++) {
266 if (i > 0) {
267 distTotal += path[i]->distanceTo(*path[i - 1]);
268 }
269 metas.push_back(neb_frame_metadata(path, tangent, eigenmode_solvers,
270 numImages, estimateEigenvalues, i,
271 distTotal, bandIndex));
272 }
273
274 std::vector<std::shared_ptr<Matter>> band(
275 path.begin(), path.begin() + static_cast<std::ptrdiff_t>(nframes));
276 return eonc::io::buildNebPathFrames(band, metas);
277}
278
280 const std::vector<std::shared_ptr<Matter>> &path,
281 const std::vector<std::shared_ptr<AtomMatrix>> &tangent,
282 const std::vector<std::shared_ptr<EigenmodeStrategy>> &eigenmode_solvers,
283 long numImages, bool estimateEigenvalues, std::string filename,
284 std::optional<size_t> bandIndex) {
285 auto frames = pathToConFrames(path, tangent, eigenmode_solvers, numImages,
286 estimateEigenvalues, bandIndex);
287 if (frames.empty()) {
289 }
290 return eonc::io::writeConFrames(std::move(filename), frames);
291}
292
293} // namespace eonc::neb
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
IoStatus writeConFrames(std::string filename, const std::vector< readcon::ConFrame > &frames)
Write already-built ConFrames to a multi-frame .con (temp or durable).
std::vector< readcon::ConFrame > buildNebPathFrames(const std::vector< std::shared_ptr< Matter > > &path, const std::vector< ConFrameMetadata > &metadata_per_image)
Build NEB band ConFrames without writing (clone builder path of writeNebPath).
IoStatus
Structured I/O result for the client surface (nanobind-friendly).
Definition ConFileIO.h:29
quill::Logger * get() noexcept
Get or create the default "combi" logger.
Definition EonLogger.h:44
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)
Build stamped ConFrames for a NEB band (same metadata as writePathCon).
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().
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)
Print NEB image data to log and optionally to file.
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.
void eigenmodeCompute(EigenmodeStrategy &s, std::shared_ptr< Matter > matter, AtomMatrix direction)
Dispatch compute() to the active variant.
double eigenmodeGetEigenvalue(EigenmodeStrategy &s)
Dispatch getEigenvalue() to the active variant.
std::optional< uint64_t > frame_index
Definition ConFileIO.h:72
std::optional< uint64_t > neb_band
Definition ConFileIO.h:77
std::vector< ConMetadataValue > scalars
Definition ConFileIO.h:79
std::optional< double > energy
Definition ConFileIO.h:73
std::optional< uint64_t > neb_bead
Definition ConFileIO.h:76
RAII helper for class-scoped logging.
Definition EonLogger.h:171
std::vector< double > energies
std::vector< double > curvatures
std::vector< double > positions