358 {
359 long effectiveMaxIter = max_iterations_override;
360 QUILL_LOG_DEBUG(
361 log,
"Saddle point search started from reactant with energy {} eV.",
363
364 int optStatus;
365 bool firstIteration = true;
366 const char *forceLabel =
367 params.optimizer_options().convergence_metric_label.c_str();
368
369 if (
params.saddle_search_options().minmode_method ==
370 LowestEigenmode::MINMODE_GPRDIMER) {
371 QUILL_LOG_DEBUG(
372 log,
"================= Using the GP Dimer Library =================");
375 QUILL_LOG_DEBUG(
log,
"GPR eigenvalue: {}",
379 }
381 QUILL_LOG_DEBUG(
log,
"[MinModeSaddleSearch] eigenvalue not negative");
383 }
385 params.saddle_search_options().zero_mode_abort_curvature) {
386 QUILL_LOG_DEBUG(
log,
"Zero mode eigenvalue: {}",
389 }
392 } else {
393
394 if (
params.saddle_search_options().minmode_method ==
395 LowestEigenmode::MINMODE_DIMER) {
397 "[Dimer] {:9s} {:9s} {:10s} {:18s} {:9s} "
398 "{:7s} {:6s} {:4s} {:5s}\n",
399 "Step", "Step Size", "Delta E", forceLabel, "Curvature",
400 "Torque", "Angle", "Rots", "Align");
401 }
else if (
params.saddle_search_options().minmode_method ==
402 LowestEigenmode::MINMODE_LANCZOS) {
403 QUILL_LOG_INFO(
405 "[Lanczos] {:9s} {:9s} {:10s} {:18s} {:9s} {:10s} {:7s} {:5s}\n",
406 "Step", "Step Size", "Delta E", forceLabel, "Curvature", "Rel Change",
407 "Angle", "Iters");
408 }
else if (
params.saddle_search_options().minmode_method ==
409 LowestEigenmode::MINMODE_GPRDIMER) {
411 "[GPRDimer] {:9s} {:9s} {:10s} {:18s} {:9s} "
412 " {:7s} {:6s} {:4s}\n",
413 "Step", "Step Size", "Delta E", forceLabel, "Curvature",
414 "Torque", "Angle", "Rots");
415 }
416
417 std::string climbLabel = "climb";
418 std::string climbDatFilename = "climb.dat";
419
421
422 auto objf = std::make_shared<MinModeObjectiveFunction>(
424 auto write_climb_frame = [&](uint64_t frameIndex, bool append,
425 double stepSize, double de, double conv,
426 double eigenval, double torque, double angle,
427 long rotations) {
428 eonc::io::ConFrameMetadata metadata;
431 metadata.
scalars.push_back({
"step_size", stepSize});
432 metadata.
scalars.push_back({
"delta_e", de});
433 metadata.
scalars.push_back({
"convergence", conv});
434 metadata.
scalars.push_back({
"eigenvalue", eigenval});
435 metadata.
scalars.push_back({
"torque", torque});
436 metadata.
scalars.push_back({
"angle", angle});
437 metadata.
scalars.push_back({
"rotations",
static_cast<double>(rotations)});
440 }
441 if (
params.debug_options().write_movies) {
443 matter->matter2con(climbLabel, append, &metadata))) {
444 QUILL_LOG_WARNING(
log,
"Failed to write climb movie frame {}",
445 climbLabel);
446 }
450 }
451
452 if (
params.debug_options().write_deprecated_outs) {
453 std::ofstream climbDat(climbDatFilename,
454 append ? (std::ios::binary | std::ios::app)
455 : std::ios::binary);
456 if (climbDat) {
457 if (!append) {
458 climbDat << "iteration\tstep_size\tdelta_e\tconvergence"
459 "\teigenvalue\ttorque\tangle\trotations\n";
460 }
461 climbDat << std::format("{}\t{:.7e}\t{:.6f}\t{:.5e}\t{:.6f}"
462 "\t{:.6f}\t{:.4f}\t{}\n",
463 frameIndex, stepSize, de, conv, eigenval,
464 torque, angle, rotations);
465 }
466 }
467 };
469 write_climb_frame(0, false, 0.0, 0.0, objf->getConvergence(),
471 0);
472 }
473 if (
params.saddle_search_options().nonnegative_displacement_abort) {
474 objf->getGradient();
476 QUILL_LOG_DEBUG(
log,
"Nonnegative eigenvalue: {}",
480 }
481 }
482
485
486
487
488 const double stockMove =
params.optimizer_options().max_move;
489 double translationCap = stockMove;
490 int wallStalls = 0;
491 int wallEvents = 0;
492 int reseeds = 0;
493 int probeForceCalls = 0;
494 auto probedEnergy = [&]() {
495 const long beforeCalls =
matter->getForceCalls();
496 const double energy = objf->getEnergy();
497 probeForceCalls +=
498 static_cast<int>(
matter->getForceCalls() - beforeCalls);
499 return energy;
500 };
501 auto pullUnderEnergy = [&](const VectorXd &before) -> bool {
502 const double limit =
params.saddle_search_options().max_energy;
503 const VectorXd after = objf->getPositions();
504 const VectorXd delta = objf->difference(after, before);
505 double alpha = 0.5;
506 bool found = false;
507 for (int probe = 0; probe < kMaxEnergyHalvings; ++probe) {
509 kMaxEnergyMotionFloor) {
510 break;
511 }
512 objf->setPositions(before + alpha * delta);
514 found = true;
515 break;
516 }
517 alpha *= 0.5;
518 }
519 auto freshMode = [&]() {
521 if (reseeds % 2 == 1) {
522 seed = -seed;
523 }
524 objf->reseedMode(seed);
525 ++reseeds;
526 };
527 if (found) {
528 wallStalls = 0;
529 ++wallEvents;
531 translationCap =
532 std::min(stockMove, std::max(accepted, kMaxEnergyStepFloor));
533 if (alpha < kMaxEnergyReseedAlpha) {
534 freshMode();
535 QUILL_LOG_DEBUG(
537 "[MinMode] energy above max_energy {:.4f}, shortened "
538 "the translation to {:.3e} and re-initialised the mode",
539 limit, accepted);
540 } else {
542 "[MinMode] energy above max_energy {:.4f}, shortened "
543 "the translation to {:.3e}",
544 limit, accepted);
545 }
546 if (wallEvents >= kMaxEnergyWallEvents) {
548 "[MinMode] {} crossings of max_energy {:.4f}, "
549 "ending the search",
550 wallEvents, limit);
551 return false;
552 }
553 return true;
554 }
555 objf->setPositions(before);
557 return false;
558 }
559 ++wallEvents;
560 ++wallStalls;
561 translationCap = std::min(
562 stockMove, std::max(translationCap * 0.5, kMaxEnergyStepFloor));
563 freshMode();
565 "[MinMode] translation stays above max_energy {:.4f}, "
566 "restored the centre and re-initialised the mode",
567 limit);
568 if (wallEvents >= kMaxEnergyWallEvents) {
570 "[MinMode] {} crossings of max_energy {:.4f}, "
571 "ending the search",
572 wallEvents, limit);
573 return false;
574 }
575 return wallStalls < kMaxEnergyStalls;
576 };
577
578 while (!objf->isConverged() ||
iteration == 0) {
579 matter->pollCancel(
"saddle");
580
581 if (!firstIteration) {
582
583 if (
params.saddle_search_options().nonlocal_count_abort != 0) {
585 initialPosition -
matter->getPositions(),
586 params.saddle_search_options().nonlocal_distance_abort);
587 if (nm >=
params.saddle_search_options().nonlocal_count_abort) {
589 break;
590 }
591 }
592
594 params.saddle_search_options().zero_mode_abort_curvature) {
595 QUILL_LOG_DEBUG(
log,
"Zero mode eigenvalue: {}",
598 break;
599 }
600 }
601 firstIteration = false;
602
605 break;
606 }
607
608 const VectorXd posBefore = objf->getPositions();
610
611 try {
612 if (
params.saddle_search_options().confine_positive.bowl_breakout &&
614 params.optimizer_options().method == OptType::CG) {
615 optStatus = optim->step(-translationCap);
616 } else {
617 optStatus = optim->step(translationCap);
618 }
619 } catch (const eonc::DimerModeRestoredException &) {
620 QUILL_LOG_DEBUG(
621 log,
"Dimer restored to best state. Checking convergence...");
623 break;
624 } catch (const eonc::DimerModeLostException &) {
625 QUILL_LOG_WARNING(
log,
"Dimer lost mode completely. Aborting.");
627 break;
628 }
629
630 if (optStatus < 0) {
632 break;
633 }
634
636 const double energyLimit =
params.saddle_search_options().max_energy;
637 if (de > energyLimit) {
638 const bool kept = pullUnderEnergy(posBefore);
639 if (kept) {
640 optim->onTranslationPulledBack();
641 }
644
645
647 break;
648 }
650 } else if (translationCap < stockMove) {
651 translationCap =
652 std::min(stockMove, translationCap * kMaxEnergyCapRegrowth);
653 wallStalls = 0;
654 }
655
656
657 if (
params.saddle_search_options().remove_rotation) {
659 }
660 double stepSize = (
matter->pbc(
matter->getPositions() - pos)).norm();
661
663
664
669 double conv = objf->getConvergence();
670
671 if (
params.saddle_search_options().minmode_method ==
672 LowestEigenmode::MINMODE_LANCZOS) {
673 QUILL_LOG_DEBUG(
675 "[Lanczos] {:9} {:9.6f} {:10.4f} {:18.5e} {:9.4f} {:10.6f} "
676 "{:7.3f} {:5}\n",
677 iteration, stepSize, de, conv, eigenval, torque, angle, rotations);
678 } else {
679 QUILL_LOG_DEBUG(
681 "[Dimer] {:9} {:9.7f} {:10.4f} {:18.5e} {:9.4f} {:7.3f} "
682 " {:6.3f} {:4}\n",
683 iteration, stepSize, de, conv, eigenval, torque, angle, rotations);
684 }
685
687 write_climb_frame(
static_cast<uint64_t
>(
iteration),
true, stepSize, de,
688 conv, eigenval, torque, angle, rotations);
689 }
690
691 if (
params.main_options().checkpoint) {
693 matter->matter2con(
"displacement_cp.con",
false))) {
694 QUILL_LOG_WARNING(
log,
"Failed to write displacement_cp.con");
695 }
698 }
699
701 QUILL_LOG_DEBUG(
log,
"[MinModeSaddleSearch] reached a known saddle");
703 break;
704 }
705
706
708 if (!dimer->rotationDidConverge) {
712 QUILL_LOG_DEBUG(
log,
"Dimer restored to valid state. C_tau={:.4f}",
713 dimer->getEigenvalue());
714 }
715 break;
716 }
717 }
718 }
719
722 }
723
724
725
726 const bool climbConverged = objf->isConverged();
727 const int statusBeforeGuard =
status;
730 QUILL_LOG_WARNING(
log,
"[MinModeSaddleSearch] objective not converged; "
731 "refusing STATUS_GOOD");
732 }
733
735 QUILL_LOG_DEBUG(
log,
"[MinModeSaddleSearch] eigenvalue not negative");
737 }
740 }
741
743}
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
bool atKnownSaddle() const
bool retain_climb_frames_
static constexpr int finalizeClimbStatus(int climbStatus, bool objectiveConverged) noexcept
Issue #20 policy: never leave climb as STATUS_GOOD when the climb objective is still unconverged (unf...
long numAtomsMoved(const AtomMatrix v1, double cutoff)
double maxAtomMotionV(const VectorXd v1)
void rotationRemove(const AtomMatrix r1, std::shared_ptr< Matter > m2)
std::unique_ptr< Optimizer > mkOptim(std::shared_ptr< ObjectiveFunction > a_objf, OptType a_otype, const Parameters &a_params)
void saveMode(FILE *modeFile, std::shared_ptr< Matter > matter, AtomMatrix mode)
Write a mode; constrained axes are emitted as 0.
readcon::ConFrame matterToConFrame(Matter &m, const ConFrameMetadata *metadata)
Build a single stamped ConFrame from Matter (same builder as matter2con).
constexpr bool io_ok(IoStatus s) noexcept
void eigenmodeCompute(LowestEigenmode &s, std::shared_ptr< Matter > matter, AtomMatrix direction)
long eigenmodeTotalForceCalls(LowestEigenmode &s)
long eigenmodeStatsRotations(LowestEigenmode &s)
long eigenmodeTotalIterations(LowestEigenmode &s)
double eigenmodeStatsTorque(LowestEigenmode &s)
double eigenmodeStatsAngle(LowestEigenmode &s)