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)
static bool walkHelped (double walkedForce, double convForce, int mmfStatus)
static bool convergedClimb (double bandForce, double forceTolerance, int mmfStatus)

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 38 of file NEBOcinebController.cpp.

Member Function Documentation

◆ convergedClimb()

bool eonc::neb::OCINEBController::convergedClimb ( double bandForce,
double forceTolerance,
int mmfStatus )
inlinestatic

Definition at line 65 of file NEBOcinebController.h.

66 {
67 return bandForce < forceTolerance && mmfStatus != -2;
68 }

◆ 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,
33 params.neb_options().force_tolerance,
34 r.restore_unhelpful,
35 };
36}

◆ initBaseline()

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

Definition at line 41 of file NEBOcinebController.cpp.

41 {
42 baseline_force_ = baseline_force;
43 current_threshold_ = std::max(baseline_force_ * cfg_.trigger_factor,
44 2.0 * cfg_.force_tolerance);
45}

◆ resetStability()

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

Definition at line 70 of file NEBOcinebController.h.

◆ run()

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

Definition at line 71 of file NEBOcinebController.cpp.

72 {
73 auto *log = eonc::log::get();
74
75 QUILL_LOG_DEBUG(log,
76 "Triggering MMF. Force: {:.4f}, Threshold: {:.4f} "
77 "({:.2f}x baseline)",
78 convForce, current_threshold_,
80
81 // The dimer moves this image. updateForces() may then hop
82 // climbingImage to a neighbor; band CI force is that neighbor.
83 const long walked = neb.climbingImage;
84 AtomMatrix savedPositions = neb.path[walked]->getPositions();
85 // The saved geometry is evaluated already; a restore puts its forces
86 // back instead of asking the potential again.
87 const AtomMatrix savedForces = neb.path[walked]->getForcesRaw();
88 const double savedEnergy = neb.path[walked]->getPotentialEnergy();
89
90 double alignment = 0.0;
91 int mmfResult = runDimer(neb, alignment);
92
93 // Always update forces after MMF
94 neb.movedAfterForceCall = true;
95 neb.updateForces();
96 double newForce = neb.convergenceForce();
97
98 // A positive-curvature walk can put the band force under tolerance
99 // while the climbing image sits in a well. Restore that image
100 // before accepting the band.
101 if (convergedClimb(newForce, cfg_.force_tolerance, mmfResult)) {
102 QUILL_LOG_DEBUG(log, "NEB converged after MMF. Force: {:.4f}", newForce);
103 return {newForce, true, false};
104 }
105
106 const double walkedForce = neb.path[walked]->getForcesFreeV().norm();
107 const bool mmfHelped = walkHelped(walkedForce, convForce, mmfResult);
108
109 if (mmfHelped) {
110 updateThresholdSuccess(convForce, walkedForce);
111 QUILL_LOG_DEBUG(log,
112 "MMF helped (status={}, walked={}, now_ci={}). "
113 "Walked force: {:.4f} -> {:.4f} ({:.2f}x baseline). "
114 "Band CI force: {:.4f}. New threshold: {:.4f}",
115 mmfResult, walked, neb.climbingImage, convForce,
116 walkedForce, walkedForce / baseline_force_, newForce,
118 } else {
119 // Frontiers OCI-NEB (doi:10.3389/fchem.2026.1807063, Algorithm 1)
120 // restores only on positive curvature (status -2). Alignment
121 // failure keeps the walked CI and applies the linear penalty.
122 // restore_unhelpful also restores on alignment reject / force
123 // increase on the walked image. Default false: the paper setting.
124 const bool restore = cfg_.restore_unhelpful || mmfResult == -2;
125 if (restore) {
126 neb.path[walked]->setPositions(savedPositions);
127 neb.path[walked]->setEvaluation(savedForces, savedEnergy);
128 neb.movedAfterForceCall = true;
129 has_cached_mode_ = false;
130 newForce = convForce;
131 }
132 updateThresholdBackoff(alignment);
133 QUILL_LOG_DEBUG(
134 log,
135 "MMF backoff (status={}, walked={}, now_ci={}). "
136 "Walked force: {:.4f} -> {:.4f}, band CI: {:.4f}, "
137 "Alignment: {:.3f}. {}New threshold: {:.4f} ({:.2f}x baseline)",
138 mmfResult, walked, neb.climbingImage, convForce, walkedForce, newForce,
139 alignment, restore ? "Restored CI. " : "", current_threshold_,
141 }
142
143 bool shouldReset =
144 (savedPositions - neb.path[walked]->getPositions()).norm() >
147
148 if (shouldReset) {
149 QUILL_LOG_DEBUG(log, "Resetting optimization history.");
150 }
151
153
154 return {newForce, false, shouldReset};
155}
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
void updateForces(bool ci_active)
std::vector< std::shared_ptr< Matter > > path
const neb_options_t & neb_options() const
const optimizer_options_t & optimizer_options() const
static bool walkHelped(double walkedForce, double convForce, int mmfStatus)
void updateThresholdSuccess(double convForce, double newForce)
int runDimer(eonc::NudgedElasticBand &neb, double &alignment)
void updateThresholdBackoff(double alignment)
static bool convergedClimb(double bandForce, double forceTolerance, int mmfStatus)
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 157 of file NEBOcinebController.cpp.

158 {
159 auto *log = eonc::log::get();
160 alignment = 0.0;
161
162 if (neb.climbingImage <= 0 || neb.climbingImage > neb.numImages) {
163 QUILL_LOG_WARNING(log, "Invalid climbing image for MMF: {}",
164 neb.climbingImage);
165 return -1;
166 }
167
168 AtomMatrix initialMode;
169 if (has_cached_mode_) {
170 initialMode = cached_mode_;
171 } else {
172 initialMode = *neb.tangent[neb.climbingImage];
173 }
174 double tangentNorm = initialMode.norm();
175 if (tangentNorm < 1e-8) {
176 QUILL_LOG_WARNING(log, "Tangent too small for MMF initialization");
177 return -1;
178 }
179 initialMode /= tangentNorm;
180
181 auto tempMinModeSearch = std::make_shared<MinModeSaddleSearch>(
182 neb.path[neb.climbingImage], initialMode,
183 neb.path[neb.climbingImage]->getPotentialEnergy(), neb.params, neb.pot);
184
185 int minModeStatus;
186 try {
187 minModeStatus = tempMinModeSearch->run(cfg_.max_steps);
188 } catch (const eonc::DimerModeRestoredException &) {
190 QUILL_LOG_DEBUG(log, "MMF: Dimer restored to best state");
191 } catch (const eonc::DimerModeLostException &) {
193 QUILL_LOG_WARNING(log, "Dimer lost mode during MMF refinement");
194 }
195
196 mmf_iterations_used_ += tempMinModeSearch->iteration;
197
198 double eigenvalue = tempMinModeSearch->getEigenvalue();
199 if (eigenvalue > 0.0) {
200 QUILL_LOG_WARNING(log,
201 "MMF skipped: Positive curvature detected (eig={:.4f}).",
202 eigenvalue);
203 return -2;
204 }
205
206 AtomMatrix finalModeMatrix = tempMinModeSearch->getEigenvector();
207 VectorXd finalMode =
208 VectorXd::Map(finalModeMatrix.data(), finalModeMatrix.size());
209 VectorXd currentTangent =
210 VectorXd::Map(neb.tangent[neb.climbingImage]->data(),
211 neb.tangent[neb.climbingImage]->size());
212 if (finalMode.size() != currentTangent.size()) {
213 QUILL_LOG_WARNING(log,
214 "MMF mode size {} != tangent size {}; skip alignment",
215 finalMode.size(), currentTangent.size());
216 alignment = 0.0;
217 } else if (finalMode.norm() == 0.0 || currentTangent.norm() == 0.0) {
218 alignment = 0.0;
219 } else {
220 alignment =
221 std::abs(finalMode.normalized().dot(currentTangent.normalized()));
222 }
223
224 if (minModeStatus == MinModeSaddleSearch::STATUS_GOOD ||
226 if (alignment < cfg_.angle_tol) {
227 QUILL_LOG_WARNING(
228 log,
229 "MMF converged/restored but mode drifted (alignment={:.3f} < {:.3f})",
230 alignment, cfg_.angle_tol);
231 return -1;
232 }
233 cached_mode_ = finalModeMatrix;
234 has_cached_mode_ = true;
235 return 0;
236 } else if (minModeStatus == MinModeSaddleSearch::STATUS_BAD_MAX_ITERATIONS) {
237 return 1;
238 } else {
239 QUILL_LOG_WARNING(log, "MMF failed. Mode-tangent alignment: {:.3f}",
240 alignment);
241 return -1;
242 }
243}
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 47 of file NEBOcinebController.cpp.

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

◆ stabilityCount()

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

Definition at line 72 of file NEBOcinebController.h.

72{ return ciStabilityCounter_; }

◆ threshold()

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

Definition at line 73 of file NEBOcinebController.h.

73{ return current_threshold_; }

◆ updateStability()

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

Definition at line 61 of file NEBOcinebController.cpp.

61 {
62 if (climbingImage == previousClimbingImage_) {
64 } else {
66 previousClimbingImage_ = climbingImage;
67 has_cached_mode_ = false;
68 }
69}

◆ updateThresholdBackoff()

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

Definition at line 253 of file NEBOcinebController.cpp.

253 {
254 double alpha = std::clamp(alignment, 0.0, 1.0);
255 double penalty_factor = 0.5 + 0.5 * alpha;
256 current_threshold_ = baseline_force_ * cfg_.trigger_factor * penalty_factor;
257 // Lower bound on the MMF trigger threshold. Scaled by force_tolerance so
258 // the MMF gate never collapses to zero when the NEB is already near
259 // convergence, but also capped by the trigger_factor envelope so a
260 // loose force_tolerance cannot push min_threshold above the cap and
261 // starve MMF activation.
262 double min_threshold = 2.0 * cfg_.force_tolerance;
263 current_threshold_ = std::max(current_threshold_, min_threshold);
264}

◆ updateThresholdSuccess()

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

Definition at line 245 of file NEBOcinebController.cpp.

246 {
247 current_threshold_ = newForce * (0.5 + 0.4 * (newForce / convForce));
248 double max_threshold = std::max(baseline_force_ * cfg_.trigger_factor,
249 2.0 * cfg_.force_tolerance);
250 current_threshold_ = std::min(current_threshold_, max_threshold);
251}

◆ walkHelped()

bool eonc::neb::OCINEBController::walkHelped ( double walkedForce,
double convForce,
int mmfStatus )
inlinestatic

Definition at line 58 of file NEBOcinebController.h.

58 {
59 return walkedForce < convForce && mmfStatus != -2;
60 }

Member Data Documentation

◆ baseline_force_

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

Definition at line 78 of file NEBOcinebController.h.

78{-1.0};

◆ cached_mode_

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

Definition at line 85 of file NEBOcinebController.h.

◆ cfg_

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

Definition at line 76 of file NEBOcinebController.h.

◆ ciStabilityCounter_

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

Definition at line 80 of file NEBOcinebController.h.

80{0};

◆ current_threshold_

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

Definition at line 77 of file NEBOcinebController.h.

77{-1.0};

◆ has_cached_mode_

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

Definition at line 84 of file NEBOcinebController.h.

84{false};

◆ mmf_iterations_used_

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

Definition at line 81 of file NEBOcinebController.h.

81{0};

◆ previousClimbingImage_

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

Definition at line 79 of file NEBOcinebController.h.

79{-1};

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