42 std::shared_ptr<Matter> matterPassed,
43 std::shared_ptr<EigenmodeStrategy> minModeMethodPassed,
46 matter{std::move(matterPassed)},
60 if (!std::isfinite(c) || c >= 0.0) {
73 if (dimer && !dimer->rotationDidConverge) {
74 if (dimer->getEigenvalue() < 0.0) {
77 "[MinMode] Dimer restored to best state with C_tau={:.4f}",
78 dimer->getEigenvalue());
93 if (eigenvalue > 0.0) {
94 if (
params.saddle_search_options().perp_force_ratio > 0.0) {
95 double d =
params.saddle_search_options().perp_force_ratio;
96 force = d * force - (1.0 + d) * proj;
97 }
else if (
params.saddle_search_options().confine_positive.enabled) {
98 if (
params.saddle_search_options().confine_positive.bowl_breakout) {
100 const long nAtoms =
matter->numberOfAtoms();
101 int nBowlActive =
static_cast<int>(std::min<long>(
102 params.saddle_search_options().confine_positive.bowl_active,
104 if (nBowlActive <= 0) {
107 std::vector<int> indices_max(nBowlActive);
110 for (
int j = 0; j < nBowlActive; j++) {
111 double f_max = forceTemp.row(0).norm();
113 for (
long i = 0; i <
matter->numberOfAtoms(); i++) {
114 if (f_max < forceTemp.row(i).norm()) {
115 f_max = forceTemp.row(i).norm();
116 i_max =
static_cast<int>(i);
119 forceTemp.row(i_max).setZero();
120 indices_max[j] = i_max;
123 for (
int j = 0; j < nBowlActive; j++) {
124 forceTemp.row(indices_max[j]) = -proj.row(indices_max[j]);
129 int sufficientForce = 0;
131 params.saddle_search_options().confine_positive.min_force;
132 const long maxBoostTries = std::max(
133 3 *
matter->numberOfAtoms(),
134 params.saddle_search_options().confine_positive.min_active);
138 params.saddle_search_options().confine_positive.min_active &&
139 boostTries < maxBoostTries) {
141 force =
matter->getForces();
142 for (
long i = 0; i <
matter->numberOfAtoms(); i++) {
143 for (
int k = 0; k < 3; k++) {
144 if (std::abs(force(i, k)) < minForce) {
149 -
params.saddle_search_options().confine_positive.boost *
155 params.saddle_search_options().confine_positive.scale_ratio;
163 force += -2.0 * proj;
166 VectorXd forceV = VectorXd::Map(force.data(), 3 *
matter->numberOfAtoms());
179 if (
params.optimizer_options().convergence_metric ==
"norm") {
180 return matter->getForcesFreeV().norm();
181 }
else if (
params.optimizer_options().convergence_metric ==
"max_atom") {
182 return matter->maxForce();
183 }
else if (
params.optimizer_options().convergence_metric ==
185 return matter->getForces().cwiseAbs().maxCoeff();
188 params.optimizer_options().convergence_metric);
189 throw std::invalid_argument(
190 std::format(
"[MinModeSaddleSearch] unknown convergence_metric: {}",
191 params.optimizer_options().convergence_metric));
196 return matter->pbcV(a - b);
202 double reactantEnergyPassed,
204 std::shared_ptr<Potential> potPassed)
208 params.optimizer_options().convergence_metric,
"[MinModeSaddleSearch]");
219 VectorXd refVec = VectorXd::Map(
mode.data(), 3 *
matter->numberOfAtoms());
220 refVec = refVec.array() *
matter->getFreeV().array();
221 dimer->setReferenceMode(refVec);
226 return run(
params.saddle_search_options().max_iterations);
232 const long maxIter = max_iterations_override < 0
233 ?
params.saddle_search_options().max_iterations
234 : max_iterations_override;
235 const int st =
run(maxIter);
241 long effectiveMaxIter = max_iterations_override;
243 log,
"Saddle point search started from reactant with energy {} eV.",
247 bool firstIteration =
true;
248 const char *forceLabel =
249 params.optimizer_options().convergence_metric_label.c_str();
251 if (
params.saddle_search_options().minmode_method ==
254 log,
"================= Using the GP Dimer Library =================");
257 QUILL_LOG_DEBUG(
log,
"GPR eigenvalue: {}",
263 QUILL_LOG_DEBUG(
log,
"[MinModeSaddleSearch] eigenvalue not negative");
267 params.saddle_search_options().zero_mode_abort_curvature) {
268 QUILL_LOG_DEBUG(
log,
"Zero mode eigenvalue: {}",
276 if (
params.saddle_search_options().minmode_method ==
279 "[Dimer] {:9s} {:9s} {:10s} {:18s} {:9s} "
280 "{:7s} {:6s} {:4s} {:5s}\n",
281 "Step",
"Step Size",
"Delta E", forceLabel,
"Curvature",
282 "Torque",
"Angle",
"Rots",
"Align");
283 }
else if (
params.saddle_search_options().minmode_method ==
287 "[Lanczos] {:9s} {:9s} {:10s} {:18s} {:9s} {:10s} {:7s} {:5s}\n",
288 "Step",
"Step Size",
"Delta E", forceLabel,
"Curvature",
"Rel Change",
290 }
else if (
params.saddle_search_options().minmode_method ==
293 "[GPRDimer] {:9s} {:9s} {:10s} {:18s} {:9s} "
294 " {:7s} {:6s} {:4s}\n",
295 "Step",
"Step Size",
"Delta E", forceLabel,
"Curvature",
296 "Torque",
"Angle",
"Rots");
299 std::string climbLabel =
"climb";
300 std::string climbDatFilename =
"climb.dat";
304 auto objf = std::make_shared<MinModeObjectiveFunction>(
306 auto write_climb_frame = [&](uint64_t frameIndex,
bool append,
307 double stepSize,
double de,
double conv,
308 double eigenval,
double torque,
double angle,
313 metadata.
scalars.push_back({
"step_size", stepSize});
314 metadata.
scalars.push_back({
"delta_e", de});
315 metadata.
scalars.push_back({
"convergence", conv});
316 metadata.
scalars.push_back({
"eigenvalue", eigenval});
317 metadata.
scalars.push_back({
"torque", torque});
318 metadata.
scalars.push_back({
"angle", angle});
319 metadata.
scalars.push_back({
"rotations",
static_cast<double>(rotations)});
323 if (
params.debug_options().write_movies) {
325 matter->matter2con(climbLabel, append, &metadata))) {
326 QUILL_LOG_WARNING(
log,
"Failed to write climb movie frame {}",
334 if (
params.debug_options().write_deprecated_outs) {
335 std::ofstream climbDat(climbDatFilename,
336 append ? (std::ios::binary | std::ios::app)
340 climbDat <<
"iteration\tstep_size\tdelta_e\tconvergence"
341 "\teigenvalue\ttorque\tangle\trotations\n";
343 climbDat << std::format(
"{}\t{:.7e}\t{:.6f}\t{:.5e}\t{:.6f}"
344 "\t{:.6f}\t{:.4f}\t{}\n",
345 frameIndex, stepSize, de, conv, eigenval,
346 torque, angle, rotations);
351 write_climb_frame(0,
false, 0.0, 0.0, objf->getConvergence(),
355 if (
params.saddle_search_options().nonnegative_displacement_abort) {
358 QUILL_LOG_DEBUG(
log,
"Nonnegative eigenvalue: {}",
368 while (!objf->isConverged() ||
iteration == 0) {
370 if (!firstIteration) {
372 if (
params.saddle_search_options().nonlocal_count_abort != 0) {
374 initialPosition -
matter->getPositions(),
375 params.saddle_search_options().nonlocal_distance_abort);
376 if (nm >=
params.saddle_search_options().nonlocal_count_abort) {
383 params.saddle_search_options().zero_mode_abort_curvature) {
384 QUILL_LOG_DEBUG(
log,
"Zero mode eigenvalue: {}",
390 firstIteration =
false;
400 if (
params.saddle_search_options().confine_positive.bowl_breakout &&
403 optStatus = optim->step(-
params.optimizer_options().max_move);
405 optStatus = optim->step(
params.optimizer_options().max_move);
409 log,
"Dimer restored to best state. Checking convergence...");
413 QUILL_LOG_WARNING(
log,
"Dimer lost mode completely. Aborting.");
426 if (
params.saddle_search_options().remove_rotation) {
429 double stepSize = (
matter->pbc(
matter->getPositions() - pos)).norm();
438 double conv = objf->getConvergence();
440 if (
params.saddle_search_options().minmode_method ==
444 "[Lanczos] {:9} {:9.6f} {:10.4f} {:18.5e} {:9.4f} {:10.6f} "
446 iteration, stepSize, de, conv, eigenval, torque, angle, rotations);
450 "[Dimer] {:9} {:9.7f} {:10.4f} {:18.5e} {:9.4f} {:7.3f} "
452 iteration, stepSize, de, conv, eigenval, torque, angle, rotations);
456 write_climb_frame(
static_cast<uint64_t
>(
iteration),
true, stepSize, de,
457 conv, eigenval, torque, angle, rotations);
460 if (
params.main_options().checkpoint) {
462 matter->matter2con(
"displacement_cp.con",
false))) {
463 QUILL_LOG_WARNING(
log,
"Failed to write displacement_cp.con");
469 if (de >
params.saddle_search_options().max_energy) {
476 if (!dimer->rotationDidConverge) {
480 QUILL_LOG_DEBUG(
log,
"Dimer restored to valid state. C_tau={:.4f}",
481 dimer->getEigenvalue());
494 const bool climbConverged = objf->isConverged();
495 const int statusBeforeGuard =
status;
498 QUILL_LOG_WARNING(
log,
"[MinModeSaddleSearch] objective not converged; "
499 "refusing STATUS_GOOD");
503 QUILL_LOG_DEBUG(
log,
"[MinModeSaddleSearch] eigenvalue not negative");
Direct optimization for energy minimization.
double matDot(const AtomMatrix &a, const AtomMatrix &b)
SIMD-optimized dot product for contiguous Eigen matrices.
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
#define EONC_LOG_DEBUG(...)
#define EONC_LOG_CRITICAL(...)
Finds transition states by finding saddle points on the potential energy surface.
static const char MINMODE_GPRDIMER[]
static const char MINMODE_DIMER[]
static const char MINMODE_LANCZOS[]
~MinModeObjectiveFunction() override=default
VectorXd getGradient(bool fdstep=false)
VectorXd difference(const VectorXd &a, const VectorXd &b)
std::shared_ptr< EigenmodeStrategy > minModeMethod
std::optional< double > knownCurvature() const override
MinModeObjectiveFunction(std::shared_ptr< Matter > matterPassed, std::shared_ptr< EigenmodeStrategy > minModeMethodPassed, AtomMatrix modePassed, const Parameters ¶msPassed)
void setPositions(const VectorXd &x)
std::shared_ptr< Matter > matter
MinModeSaddleSearch(std::shared_ptr< Matter > matterPassed, AtomMatrix modePassed, double reactantEnergyPassed, const Parameters ¶metersPassed, std::shared_ptr< Potential > potPassed)
int runRetainFrames(long max_iterations_override=-1)
Like run(), but also retain climb ConFrames in memory (same stamps as write_movies climb CON).
AtomMatrix getEigenvector()
@ STATUS_BAD_MAX_ITERATIONS
@ STATUS_NONNEGATIVE_ABORT
@ STATUS_BAD_NO_NEGATIVE_MODE_AT_SADDLE
@ STATUS_DIMER_RESTORED_BEST
std::vector< readcon::ConFrame > climb_frames_
bool retain_climb_frames_
AtomMatrix initialTangent_
std::shared_ptr< Matter > matter
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...
std::shared_ptr< EigenmodeStrategy > minModeMethod
ObjectiveFunction(const Parameters ¶msPassed)
const Parameters & params
std::shared_ptr< Potential > pot
const Parameters & params
SaddleSearchMethod(std::shared_ptr< Potential > potPassed, const Parameters ¶msPassed)
long numAtomsMoved(const AtomMatrix v1, double cutoff)
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.
void requireKnownConvergenceMetric(std::string_view metric, std::string_view context)
Throws std::invalid_argument naming context when metric is unrecognized.
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
RAII resource manager for the ARTn C library with global synchronization.
void eigenmodeCompute(LowestEigenmode &s, std::shared_ptr< Matter > matter, AtomMatrix direction)
long eigenmodeTotalForceCalls(LowestEigenmode &s)
std::shared_ptr< LowestEigenmode > buildEigenmodeStrategy(std::shared_ptr< Matter > matter, const Parameters ¶ms, std::shared_ptr< Potential > pot)
long eigenmodeStatsRotations(LowestEigenmode &s)
ImprovedDimer * asImprovedDimer(LowestEigenmode &s)
long eigenmodeTotalIterations(LowestEigenmode &s)
double eigenmodeGetEigenvalue(LowestEigenmode &s)
double eigenmodeStatsTorque(LowestEigenmode &s)
AtomMatrix eigenmodeGetEigenvector(LowestEigenmode &s)
double eigenmodeStatsAngle(LowestEigenmode &s)