13namespace fs = std::filesystem;
22 zbl_params.zbl_options.cut_inner = 0.5;
24 zbl_params.zbl_options.cut_global = 3.0;
30 std::vector<Matter> all_images_on_path(nimgs + 2, initImg);
31 all_images_on_path.front() =
Matter(initImg);
32 all_images_on_path.back() =
Matter(finalImg);
33 AtomMatrix posInitial = all_images_on_path.front().getPositions();
34 AtomMatrix posFinal = all_images_on_path.back().getPositions();
35 AtomMatrix imageSep = initImg.
pbc(posFinal - posInitial) / (nimgs + 1);
37 for (
auto it{std::next(all_images_on_path.begin())};
38 it != std::prev(all_images_on_path.end()); ++it) {
40 (*it).setPositions(posInitial +
42 int(std::distance(all_images_on_path.begin(), it)));
44 return all_images_on_path;
47std::vector<Matter>
filePathInit(
const std::vector<fs::path> &fsrcs,
48 const Matter &refImg,
const size_t nimgs) {
49 std::vector<Matter> all_images_on_path;
50 if (nimgs + 2 != fsrcs.size()) {
51 throw std::runtime_error(
"Error in filePathInit: Expected " +
52 std::to_string(nimgs + 2) +
" files, but got " +
53 std::to_string(fsrcs.size()) +
".");
55 all_images_on_path.reserve(nimgs + 2);
57 for (
const auto &filePath : fsrcs) {
60 throw std::runtime_error(
"failed to load NEB path frame: " +
63 all_images_on_path.push_back(img);
65 return all_images_on_path;
69 std::vector<fs::path> paths;
70 std::ifstream inputFile(listFilePath);
72 if (!inputFile.is_open()) {
73 throw std::runtime_error(
"Error: Could not open path list file: " +
78 while (std::getline(inputFile, line)) {
81 paths.emplace_back(line);
92 for (
int i = 0; i < natoms; ++i) {
93 for (
int j = 0; j < natoms; ++j) {
97 d(i, j) = m.
pbc(pos.row(i) - pos.row(j)).norm();
109 QUILL_LOG_INFO(
log,
"Generating initial path using IDPP...");
112 log,
"ZBL Repulsion not implemented for iterative IDPP (idppPath). "
113 "Using standard IDPP.");
117 std::vector<Matter> path =
linearPath(initImg, finalImg, nimgs);
125 for (
size_t i = 1; i <= nimgs; ++i) {
128 double xi =
static_cast<double>(i) / (nimgs + 1);
131 MatrixXd dTarget = (1.0 - xi) * dInit + xi * dFinal;
134 auto idpp_objf = std::make_shared<IDPPObjectiveFunction>(
135 std::make_shared<Matter>(path[i]), params, dTarget);
148 double residual = idpp_objf->getConvergence();
150 "IDPP Image {:2d}/{:2d} | xi: {:.2f} | Residual: {:.4e}", i,
151 nimgs, xi, residual);
154 path[i].setPositions(AtomMatrix::Map(idpp_objf->getPositions().data(),
155 path[i].numberOfAtoms(), 3));
158 QUILL_LOG_INFO(
log,
"IDPP path generation complete.");
163 const Matter &finalImg,
size_t nimgs,
166 QUILL_LOG_INFO(
log,
"Generating initial path using Collective IDPP-NEB...");
168 std::vector<Matter> path =
linearPath(initImg, finalImg, nimgs);
171 std::shared_ptr<ObjectiveFunction> idpp_objf =
172 std::make_shared<CollectiveIDPPObjectiveFunction>(path, params);
176 QUILL_LOG_INFO(
log,
"Enabling ZBL repulsive penalty for IDPP...");
179 idpp_objf = std::make_shared<ZBLRepulsiveIDPPObjective>(idpp_objf, zbl_pot,
188 int checkInterval = 40;
190 while (currentStep < maxSteps) {
192 currentStep += checkInterval;
194 if (idpp_objf->isConverged()) {
196 "IDPP-NEB converged after {} steps. Max Residual: {:.4f}",
197 currentStep, idpp_objf->getConvergence());
202 QUILL_LOG_WARNING(
log,
203 "IDPP-NEB reached max_iterations ({}) without full "
204 "convergence. Residual: {:.4f}",
205 maxSteps, idpp_objf->getConvergence());
221 size_t target_nimgs,
const Parameters ¶ms,
227 "Generating initial path using S-IDPP{} ({} images, "
228 "alpha={:.2f}, frontier_tol={:.4f})...",
229 use_zbl ?
"-ZBL" :
"", target_nimgs, init.sidpp_alpha,
230 init.sidpp_frontier_tol);
233 std::vector<Matter> path;
234 path.push_back(initImg);
235 path.push_back(finalImg);
237 std::shared_ptr<Potential> zbl_pot =
nullptr;
245 int nIntermediate = 0;
246 bool addToLeft =
true;
249 auto makeIDPP = [&]() -> std::shared_ptr<ObjectiveFunction> {
250 auto objf = std::make_shared<CollectiveIDPPObjectiveFunction>(path, params);
251 if (use_zbl && zbl_pot) {
252 return std::make_shared<ZBLRepulsiveIDPPObjective>(objf, zbl_pot, path,
259 auto relaxPath = [&](
int maxSteps) ->
double {
260 auto objf = makeIDPP();
263 while (step < maxSteps) {
264 optim->run(5, init.max_move);
266 if (objf->isConverged())
269 return objf->getConvergence();
273 while (nIntermediate <
static_cast<int>(target_nimgs)) {
275 if (addToLeft && nIntermediate <
static_cast<int>(target_nimgs)) {
277 Matter &frontier = path[nLeft];
278 Matter &next = path[nLeft + 1];
280 path.insert(path.begin() + nLeft + 1, newImg);
283 QUILL_LOG_DEBUG(
log,
"S-IDPP: +L frontier (nL={}, nR={}, total={})",
284 nLeft, nRight, nIntermediate);
285 }
else if (nIntermediate <
static_cast<int>(target_nimgs)) {
287 int rightIdx =
static_cast<int>(path.size()) - 1 - nRight;
288 Matter &frontier = path[rightIdx];
289 Matter &prev = path[rightIdx - 1];
291 path.insert(path.begin() + rightIdx, newImg);
294 QUILL_LOG_DEBUG(
log,
"S-IDPP: +R frontier (nL={}, nR={}, total={})",
295 nLeft, nRight, nIntermediate);
297 addToLeft = !addToLeft;
300 double residual = relaxPath(init.nsteps);
304 if (residual > init.sidpp_frontier_tol) {
305 double residual2 = relaxPath(init.max_iterations - init.nsteps);
306 QUILL_LOG_DEBUG(
log,
"S-IDPP: Extended relaxation {:.4f} -> {:.4f}",
307 residual, residual2);
308 residual = residual2;
311 QUILL_LOG_DEBUG(
log,
"S-IDPP: {} images | Residual: {:.4f}", nIntermediate,
316 if (init.sidpp_reparam && path.size() > 3) {
317 QUILL_LOG_INFO(
log,
"S-IDPP: Reparameterizing {} images along arc length",
323 QUILL_LOG_INFO(
log,
"S-IDPP: Final relaxation of full path...");
324 double finalResidual = relaxPath(init.max_iterations);
325 QUILL_LOG_INFO(
log,
"S-IDPP: Final residual: {:.4f}", finalResidual);
337 double h00 = 2 * f3 - 3 * f2 + 1;
338 double h10 = f3 - 2 * f2 + f;
339 double h01 = -2 * f3 + 3 * f2;
340 double h11 = f3 - f2;
342 return h00 * P0 + h10 * T0 + h01 * P1 + h11 * T1;
346 size_t targetCount) {
347 if (densePath.size() == targetCount + 2)
350 size_t n = densePath.size();
353 std::vector<double> arcLength(n, 0.0);
354 for (
size_t i = 1; i < n; ++i) {
355 AtomMatrix diff = densePath[i].pbc(densePath[i].getPositions() -
356 densePath[i - 1].getPositions());
357 arcLength[i] = arcLength[i - 1] + diff.norm();
359 double totalLength = arcLength.back();
362 std::vector<AtomMatrix> tangents(n);
363 for (
size_t i = 0; i < n; ++i) {
366 T = densePath[i].pbc(densePath[i + 1].getPositions() -
367 densePath[i].getPositions());
368 }
else if (i == n - 1) {
369 T = densePath[i].pbc(densePath[i].getPositions() -
370 densePath[i - 1].getPositions());
372 AtomMatrix dNext = densePath[i].pbc(densePath[i + 1].getPositions() -
373 densePath[i].getPositions());
374 AtomMatrix dPrev = densePath[i].pbc(densePath[i].getPositions() -
375 densePath[i - 1].getPositions());
376 T = 0.5 * (dNext + dPrev);
379 if (i > 0 && i < n - 1) {
380 double localScale = (arcLength[i + 1] - arcLength[i - 1]) / 2.0;
381 T = T.normalized() * localScale;
386 std::vector<Matter> resampled;
387 resampled.reserve(targetCount + 2);
388 resampled.push_back(densePath.front());
391 double segmentLength = totalLength / (targetCount + 1);
393 for (
size_t i = 1; i <= targetCount; ++i) {
394 double targetArc = i * segmentLength;
398 for (
size_t j = 1; j < n; ++j) {
399 if (arcLength[j] >= targetArc) {
404 size_t highIdx = lowIdx + 1;
407 double segmentArc = arcLength[highIdx] - arcLength[lowIdx];
408 double f = (segmentArc > 1e-10)
409 ? (targetArc - arcLength[lowIdx]) / segmentArc
412 Matter newImg(densePath[0]);
414 densePath[lowIdx].getPositions(), tangents[lowIdx],
415 densePath[highIdx].getPositions(), tangents[highIdx], f));
416 resampled.push_back(newImg);
419 resampled.push_back(densePath.back());
424 size_t n = path.size();
429 std::vector<double> arcLength(n, 0.0);
430 for (
size_t i = 1; i < n; ++i) {
432 path[i]->pbc(path[i]->getPositions() - path[i - 1]->getPositions());
433 arcLength[i] = arcLength[i - 1] + diff.norm();
435 double totalLength = arcLength.back();
436 if (totalLength < 1e-12)
440 std::vector<AtomMatrix> tangents(n);
441 for (
size_t i = 0; i < n; ++i) {
444 T = path[i]->pbc(path[i + 1]->getPositions() - path[i]->getPositions());
445 }
else if (i == n - 1) {
446 T = path[i]->pbc(path[i]->getPositions() - path[i - 1]->getPositions());
449 path[i]->pbc(path[i + 1]->getPositions() - path[i]->getPositions());
451 path[i]->pbc(path[i]->getPositions() - path[i - 1]->getPositions());
454 if (i > 0 && i < n - 1) {
455 double localScale = (arcLength[i + 1] - arcLength[i - 1]) / 2.0;
456 T = T.normalized() * localScale;
462 std::vector<AtomMatrix> origPos(n);
463 for (
size_t i = 0; i < n; ++i)
464 origPos[i] = path[i]->getPositions();
467 size_t nInterior = n - 2;
468 double segLen = totalLength / (nInterior + 1);
470 for (
size_t i = 1; i <= nInterior; ++i) {
471 double targetArc = i * segLen;
473 for (
size_t j = 1; j < n; ++j) {
474 if (arcLength[j] >= targetArc) {
480 double sArc = arcLength[hi] - arcLength[lo];
481 double f = (sArc > 1e-10) ? (targetArc - arcLength[lo]) / sArc : 0.0;
484 origPos[hi], tangents[hi], f));
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, eOnStorageOrder > MatrixXd
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
The optimizer class is used to serve as an abstract class for all optimizers, as well as to call an o...
AtomMatrix pbc(const AtomMatrix &diff) const
long int numberOfAtoms() const
void setPositions(const AtomMatrix &pos)
io::IoStatus con2matter(std::string filename)
const AtomMatrix & getPositions() const
struct eonc::Parameters::optimizer_options_t optimizer_options
struct eonc::Parameters::neb_options_t neb_options
struct eonc::Parameters::potential_options_t potential_options
std::unique_ptr< Optimizer > mkOptim(std::shared_ptr< ObjectiveFunction > a_objf, OptType a_otype, const Parameters &a_params)
AtomMatrix cubicInterpolate(const AtomMatrix &P0, const AtomMatrix &T0, const AtomMatrix &P1, const AtomMatrix &T1, double f)
Interpolates positions using a cubic Hermite spline.
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 ¶ms, bool use_zbl)
void resamplePathInPlace(std::span< std::shared_ptr< Matter > > path)
In-place path reparameterization for NEB shared_ptr paths.
Matter interpolateImage(const Matter &A, const Matter &B, double fraction)
std::shared_ptr< Potential > createZBLPotential()
std::vector< Matter > linearPath(const Matter &initImg, const Matter &finalImg, const size_t nimgs)
MatrixXd getDistanceMatrix(const Matter &m)
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 ¶ms, bool use_zbl)
std::vector< Matter > idppCollectivePath(const Matter &initImg, const Matter &finalImg, size_t nimgs, const Parameters ¶ms, bool use_zbl)
std::vector< fs::path > readFilePaths(const std::string &listFilePath)
Reads a file where each line contains a path to another file.
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.
struct eonc::Parameters::neb_options_t::path_initialization_t initialization