Loading...
Searching...
No Matches
eonc::neb::OCINEBController Class Reference

Goswami (in prep). More...

#include <NEBOcinebController.h>

Classes

struct  Config
struct  MMFResult

Public Member Functions

 OCINEBController (const Config &cfg)
void initBaseline (double baseline_force)
bool shouldTrigger (double convForce, bool ci_active, long climbingImage, long numImages, int ciStabilityCounter) const
MMFResult run (eonc::NudgedElasticBand &neb, double convForce)
void resetStability ()
void updateStability (long climbingImage)
int stabilityCount () const
double threshold () const

Static Public Member Functions

static Config fromParams (const Parameters &params)

Private Member Functions

int runDimer (eonc::NudgedElasticBand &neb, double &alignment)
void updateThresholdSuccess (double convForce, double newForce)
void updateThresholdBackoff (double alignment)

Private Attributes

Config cfg_
double current_threshold_ {-1.0}
double baseline_force_ {-1.0}
long previousClimbingImage_ {-1}
int ciStabilityCounter_ {0}
int mmf_iterations_used_ {0}
bool has_cached_mode_ {false}
AtomMatrix cached_mode_

Detailed Description

Goswami (in prep).

Controller for OCINEB hybrid dimer refinement of the climbing image. Manages MMF triggering, threshold adaptation, and backoff logic.

Definition at line 26 of file NEBOcinebController.h.

Constructor & Destructor Documentation

◆ OCINEBController()

eonc::neb::OCINEBController::OCINEBController ( const Config & cfg)
explicit

Definition at line 37 of file NEBOcinebController.cpp.

Member Function Documentation

◆ fromParams()

OCINEBController::Config eonc::neb::OCINEBController::fromParams ( const Parameters & params)
static

Definition at line 23 of file NEBOcinebController.cpp.

23 {
24 auto &ci = params.neb_options.climbing_image;
25 auto &r = ci.ocineb;
26 return Config{
27 r.use_mmf,
28 r.trigger_force,
29 r.trigger_factor,
30 r.max_steps,
31 r.ci_stability_count,
32 r.angle_tol,
34 };
35}
struct eonc::Parameters::neb_options_t neb_options
struct eonc::Parameters::neb_options_t::climbing_image_options_t::hybrid_dimer_t ocineb
struct eonc::Parameters::neb_options_t::climbing_image_options_t climbing_image

◆ initBaseline()

void eonc::neb::OCINEBController::initBaseline ( double baseline_force)

Definition at line 40 of file NEBOcinebController.cpp.

40 {
41 baseline_force_ = baseline_force;
42 current_threshold_ = baseline_force_ * cfg_.trigger_factor;
43}

◆ resetStability()

void eonc::neb::OCINEBController::resetStability ( )
inline

Definition at line 54 of file NEBOcinebController.h.

◆ run()

OCINEBController::MMFResult eonc::neb::OCINEBController::run ( eonc::NudgedElasticBand & neb,
double convForce )

Definition at line 69 of file NEBOcinebController.cpp.

70 {
71 auto *log = eonc::log::get();
72
73 QUILL_LOG_DEBUG(log,
74 "Triggering MMF. Force: {:.4f}, Threshold: {:.4f} "
75 "({:.2f}x baseline)",
76 convForce, current_threshold_,
78
79 // Save climbing image state before MMF
80 AtomMatrix savedPositions = neb.path[neb.climbingImage]->getPositions();
81
82 double alignment = 0.0;
83 int mmfResult = runDimer(neb, alignment);
84
85 // Always update forces after MMF
86 neb.movedAfterForceCall = true;
87 neb.updateForces();
88 double newForce = neb.convergenceForce();
89
90 // Check convergence
91 if (newForce < cfg_.force_tolerance) {
92 QUILL_LOG_DEBUG(log, "NEB converged after MMF. Force: {:.4f}", newForce);
93 return {newForce, true, false};
94 }
95
96 bool mmfHelped = (newForce < convForce) && mmfResult != -2;
97
98 if (mmfHelped) {
99 updateThresholdSuccess(convForce, newForce);
100 QUILL_LOG_DEBUG(log,
101 "MMF helped (status={}). Force: {:.4f} -> {:.4f} "
102 "({:.2f}x baseline). New threshold: {:.4f}",
103 mmfResult, convForce, newForce, newForce / baseline_force_,
105 } else {
106 // On positive curvature (status=-2), the CI is at a minimum, not a
107 // saddle. Restore to pre-MMF position to prevent catastrophic force
108 // explosion. For other failures (alignment loss, force increase),
109 // let the NEB recover naturally -- the CI position may still be
110 // closer to the saddle than before.
111 if (mmfResult == -2) {
112 neb.path[neb.climbingImage]->setPositions(savedPositions);
113 neb.movedAfterForceCall = true;
114 has_cached_mode_ = false;
115 newForce = convForce;
116 }
117 updateThresholdBackoff(alignment);
118 QUILL_LOG_DEBUG(
119 log,
120 "MMF backoff (status={}). Force: {:.4f} -> {:.4f}, "
121 "Alignment: {:.3f}. {}New threshold: {:.4f} ({:.2f}x baseline)",
122 mmfResult, convForce, newForce, alignment,
123 mmfResult == -2 ? "Restored CI. " : "", current_threshold_,
125 }
126
127 bool shouldReset =
128 (savedPositions - neb.path[neb.climbingImage]->getPositions()).norm() >
131
132 if (shouldReset) {
133 QUILL_LOG_DEBUG(log, "Resetting optimization history.");
134 }
135
137
138 return {newForce, false, shouldReset};
139}
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
std::vector< std::shared_ptr< Matter > > path
void updateForces(bool ci_active)
struct eonc::Parameters::optimizer_options_t optimizer_options
void updateThresholdSuccess(double convForce, double newForce)
int runDimer(eonc::NudgedElasticBand &neb, double &alignment)
void updateThresholdBackoff(double alignment)
quill::Logger * get() noexcept
Get or create the default "combi" logger.
Definition EonLogger.h:44

◆ runDimer()

int eonc::neb::OCINEBController::runDimer ( eonc::NudgedElasticBand & neb,
double & alignment )
private

Definition at line 141 of file NEBOcinebController.cpp.

142 {
143 auto *log = eonc::log::get();
144 alignment = 0.0;
145
146 if (neb.climbingImage <= 0 || neb.climbingImage > neb.numImages) {
147 QUILL_LOG_WARNING(log, "Invalid climbing image for MMF: {}",
148 neb.climbingImage);
149 return -1;
150 }
151
152 AtomMatrix initialMode;
153 if (has_cached_mode_) {
154 initialMode = cached_mode_;
155 } else {
156 initialMode = *neb.tangent[neb.climbingImage];
157 }
158 double tangentNorm = initialMode.norm();
159 if (tangentNorm < 1e-8) {
160 QUILL_LOG_WARNING(log, "Tangent too small for MMF initialization");
161 return -1;
162 }
163 initialMode /= tangentNorm;
164
165 auto tempMinModeSearch = std::make_shared<MinModeSaddleSearch>(
166 neb.path[neb.climbingImage], initialMode,
167 neb.path[neb.climbingImage]->getPotentialEnergy(), neb.params, neb.pot);
168
169 int minModeStatus;
170 try {
171 minModeStatus = tempMinModeSearch->run(cfg_.max_steps);
172 } catch (const eonc::DimerModeRestoredException &) {
174 QUILL_LOG_DEBUG(log, "MMF: Dimer restored to best state");
175 } catch (const eonc::DimerModeLostException &) {
177 QUILL_LOG_WARNING(log, "Dimer lost mode during MMF refinement");
178 }
179
180 mmf_iterations_used_ += tempMinModeSearch->iteration;
181
182 double eigenvalue = tempMinModeSearch->getEigenvalue();
183 if (eigenvalue > 0.0) {
184 QUILL_LOG_WARNING(log,
185 "MMF skipped: Positive curvature detected (eig={:.4f}).",
186 eigenvalue);
187 return -2;
188 }
189
190 AtomMatrix finalModeMatrix = tempMinModeSearch->getEigenvector();
191 VectorXd finalMode = VectorXd::Map(finalModeMatrix.data(), 3 * neb.atoms);
192 VectorXd currentTangent =
193 VectorXd::Map(neb.tangent[neb.climbingImage]->data(), 3 * neb.atoms);
194 alignment = std::abs(finalMode.normalized().dot(currentTangent.normalized()));
195
196 if (minModeStatus == MinModeSaddleSearch::STATUS_GOOD ||
198 if (alignment < cfg_.angle_tol) {
199 QUILL_LOG_WARNING(
200 log,
201 "MMF converged/restored but mode drifted (alignment={:.3f} < {:.3f})",
202 alignment, cfg_.angle_tol);
203 return -1;
204 }
205 cached_mode_ = finalModeMatrix;
206 has_cached_mode_ = true;
207 return 0;
208 } else if (minModeStatus == MinModeSaddleSearch::STATUS_BAD_MAX_ITERATIONS) {
209 return 1;
210 } else {
211 QUILL_LOG_WARNING(log, "MMF failed. Mode-tangent alignment: {:.3f}",
212 alignment);
213 return -1;
214 }
215}
std::vector< std::shared_ptr< AtomMatrix > > tangent
std::shared_ptr< Potential > pot

◆ shouldTrigger()

bool eonc::neb::OCINEBController::shouldTrigger ( double convForce,
bool ci_active,
long climbingImage,
long numImages,
int ciStabilityCounter ) const

Definition at line 45 of file NEBOcinebController.cpp.

47 {
48 if (!cfg_.use_mmf || !ci_active)
49 return false;
50 if (climbingImage <= 0 || climbingImage > numImages)
51 return false;
52 if (ciStabilityCounter <= static_cast<int>(cfg_.ci_stability_count))
53 return false;
54 if (convForce <= cfg_.force_tolerance)
55 return false;
56 return (convForce < current_threshold_ || convForce < cfg_.trigger_force);
57}

◆ stabilityCount()

int eonc::neb::OCINEBController::stabilityCount ( ) const
inline

Definition at line 56 of file NEBOcinebController.h.

56{ return ciStabilityCounter_; }

◆ threshold()

double eonc::neb::OCINEBController::threshold ( ) const
inline

Definition at line 57 of file NEBOcinebController.h.

57{ return current_threshold_; }

◆ updateStability()

void eonc::neb::OCINEBController::updateStability ( long climbingImage)

Definition at line 59 of file NEBOcinebController.cpp.

59 {
60 if (climbingImage == previousClimbingImage_) {
62 } else {
64 previousClimbingImage_ = climbingImage;
65 has_cached_mode_ = false;
66 }
67}

◆ updateThresholdBackoff()

void eonc::neb::OCINEBController::updateThresholdBackoff ( double alignment)
private

Definition at line 224 of file NEBOcinebController.cpp.

224 {
225 double alpha = std::clamp(alignment, 0.0, 1.0);
226 double penalty_factor = 0.5 + 0.5 * alpha;
227 current_threshold_ = baseline_force_ * cfg_.trigger_factor * penalty_factor;
228 // Lower bound on the MMF trigger threshold. Scaled by force_tolerance so
229 // the MMF gate never collapses to zero when the NEB is already near
230 // convergence, but also capped by the trigger_factor envelope so a
231 // loose force_tolerance cannot push min_threshold above the cap and
232 // starve MMF activation.
233 double min_threshold = std::min(cfg_.force_tolerance * 2.0,
234 baseline_force_ * cfg_.trigger_factor);
235 current_threshold_ = std::max(current_threshold_, min_threshold);
236}

◆ updateThresholdSuccess()

void eonc::neb::OCINEBController::updateThresholdSuccess ( double convForce,
double newForce )
private

Definition at line 217 of file NEBOcinebController.cpp.

218 {
219 current_threshold_ = newForce * (0.5 + 0.4 * (newForce / convForce));
220 double max_threshold = baseline_force_ * cfg_.trigger_factor;
221 current_threshold_ = std::min(current_threshold_, max_threshold);
222}

Member Data Documentation

◆ baseline_force_

double eonc::neb::OCINEBController::baseline_force_ {-1.0}
private

Definition at line 62 of file NEBOcinebController.h.

62{-1.0};

◆ cached_mode_

AtomMatrix eonc::neb::OCINEBController::cached_mode_
private

Definition at line 69 of file NEBOcinebController.h.

◆ cfg_

Config eonc::neb::OCINEBController::cfg_
private

Definition at line 60 of file NEBOcinebController.h.

◆ ciStabilityCounter_

int eonc::neb::OCINEBController::ciStabilityCounter_ {0}
private

Definition at line 64 of file NEBOcinebController.h.

64{0};

◆ current_threshold_

double eonc::neb::OCINEBController::current_threshold_ {-1.0}
private

Definition at line 61 of file NEBOcinebController.h.

61{-1.0};

◆ has_cached_mode_

bool eonc::neb::OCINEBController::has_cached_mode_ {false}
private

Definition at line 68 of file NEBOcinebController.h.

68{false};

◆ mmf_iterations_used_

int eonc::neb::OCINEBController::mmf_iterations_used_ {0}
private

Definition at line 65 of file NEBOcinebController.h.

65{0};

◆ previousClimbingImage_

long eonc::neb::OCINEBController::previousClimbingImage_ {-1}
private

Definition at line 63 of file NEBOcinebController.h.

63{-1};

The documentation for this class was generated from the following files: