19namespace fs = std::filesystem;
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) {
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());
38 tang = *tangent[imageIndex];
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);
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);
52 metadata.
neb_band =
static_cast<uint64_t
>(*bandIndex);
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});
58 if (estimateEigenvalues && imageIndex >= 0 &&
59 imageIndex <
static_cast<long>(eigenmode_solvers.size()) &&
60 eigenmode_solvers[imageIndex]) {
75 const std::vector<std::shared_ptr<AtomMatrix>> &tangent,
82 std::vector<double> a(numImages + 1), b(numImages + 1), c(numImages + 1),
84 double F1, F2, U1, U2, dist;
86 for (
long i = 0; i <= numImages; i++) {
87 dist = path[i]->distanceTo(*path[i + 1]);
90 path[i]->pbc(path[1]->getPositions() - path[0]->getPositions());
91 tangentEndpoint.normalize();
92 F1 =
matDot(path[i]->getForces(), tangentEndpoint) * dist;
94 F1 =
matDot(path[i]->getForces(), *tangent[i]) * dist;
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;
102 F2 =
matDot(path[i + 1]->getForces(), *tangent[i + 1]) * dist;
104 U1 = path[i]->getPotentialEnergy();
105 U2 = path[i + 1]->getPotentialEnergy();
108 c[i] = 3. * (U2 - U1) + 2. * F1 + F2;
109 d[i] = -2. * (U2 - U1) - (F1 + F2);
113 result.
positions.resize(2 * (numImages + 1));
114 result.
energies.resize(2 * (numImages + 1));
115 result.
curvatures.resize(2 * (numImages + 1));
117 double discriminant, f;
119 for (
long i = 0; i <= numImages; i++) {
120 discriminant = c[i] * c[i] - 3.0 * b[i] * d[i];
121 if (discriminant >= 0) {
125 if ((d[i] == 0) && (c[i] != 0)) {
126 f = (-b[i] / (2. * c[i]));
129 else if (d[i] != 0) {
130 f = -(c[i] + std::sqrt(discriminant)) / (3. * d[i]);
132 if ((f >= 0) && (f <= 1)) {
135 ((d[i] * f + c[i]) * f + b[i]) * f + a[i];
141 f = (-(c[i] - std::sqrt(discriminant)) / (3. * d[i]));
143 if ((f >= 0) && (f <= 1)) {
146 ((d[i] * f + c[i]) * f + b[i]) * f + a[i];
154 QUILL_LOG_DEBUG(
log,
"Energy reference: {}", path[0]->getPotentialEnergy());
155 for (
long i = 0; i < result.
numExtrema; i++) {
157 log,
"extrema #{} at image position {} with energy {} and curvature {}",
159 result.
energies[i] - path[0]->getPotentialEnergy(),
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,
173 double dist, distTotal = 0;
175 path[0]->pbc(path[1]->getPositions() - path[0]->getPositions());
177 path[numImages + 1]->getPositions() - path[numImages]->getPositions());
180 if (estimateEigenvalues) {
181 header = std::format(
"{:>3s} {:>12s} {:>12s} {:>12s} {:>12s}",
"img",
182 "rxn_coord",
"energy",
"f_para",
"eigval");
184 header = std::format(
"{:>3s} {:>12s} {:>12s} {:>12s}",
"img",
"rxn_coord",
188 tangentStart.normalize();
189 tangentEnd.normalize();
191 std::ofstream fileLogger;
193 std::string neb_dat_fs;
194 if (idx == std::numeric_limits<size_t>::max()) {
195 neb_dat_fs =
"neb.dat";
197 neb_dat_fs = std::format(
"neb_{:03}.dat", idx);
199 if (fs::exists(neb_dat_fs)) {
200 fs::remove(neb_dat_fs);
202 fileLogger.open(neb_dat_fs);
203 if (fileLogger.is_open()) {
204 fileLogger << header <<
"\n";
207 const double energy_reactant = path[0]->getPotentialEnergy();
209 for (
long i = 0; i <= numImages + 1; i++) {
212 }
else if (i == numImages + 1) {
219 dist = path[i]->distanceTo(*path[i - 1]);
223 double relative_energy = path[i]->getPotentialEnergy() - energy_reactant;
224 double parallel_force =
matDot(path[i]->getForces(), tang);
226 if (estimateEigenvalues) {
228 double lowest_eigenvalue =
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);
235 QUILL_LOG_DEBUG(
log,
"{:>3} {:>12.6f} {:>12.6f} {:>12.6f} {:>12.6f}", i,
236 distTotal, relative_energy, parallel_force,
240 if (fileLogger.is_open()) {
241 fileLogger << std::format(
"{:>3} {:>12.6f} {:>12.6f} {:>12.6f}\n", i,
242 distTotal, relative_energy, parallel_force);
244 QUILL_LOG_DEBUG(
log,
"{:>3} {:>12.6f} {:>12.6f} {:>12.6f}", i,
245 distTotal, relative_energy, parallel_force);
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) {
261 double distTotal = 0.0;
262 std::vector<eonc::io::ConFrameMetadata> metas;
263 metas.reserve(nframes);
265 for (
long i = 0; i <= numImages + 1; i++) {
267 distTotal += path[i]->distanceTo(*path[i - 1]);
269 metas.push_back(neb_frame_metadata(path, tangent, eigenmode_solvers,
270 numImages, estimateEigenvalues, i,
271 distTotal, bandIndex));
274 std::vector<std::shared_ptr<Matter>> band(
275 path.begin(), path.begin() +
static_cast<std::ptrdiff_t
>(nframes));
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()) {
double matDot(const AtomMatrix &a, const AtomMatrix &b)
SIMD-optimized dot product for contiguous Eigen matrices.
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
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).
quill::Logger * get() noexcept
Get or create the default "combi" logger.
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.
RAII helper for class-scoped logging.
std::vector< double > energies
std::vector< double > curvatures
std::vector< double > positions