eOn 3.2.0
Long-timescale dynamics: aKMC, NEB, parallel replica
☾
Toggle main menu visibility
Loading...
Searching...
No Matches
NEBOcinebController.cpp
Go to the documentation of this file.
1
/*
2
** This file is part of eOn.
3
**
4
** SPDX-License-Identifier: BSD-3-Clause
5
**
6
** Copyright (c) 2010--present, eOn Development Team
7
** All rights reserved.
8
**
9
** Repo:
10
** https://github.com/TheochemUI/eOn
11
*/
12
#include "
eon/NEBOcinebController.h
"
13
#include "
eon/EonLogger.h
"
14
#include "
eon/MinModeSaddleSearch.h
"
15
#include "
eon/NudgedElasticBand.h
"
16
#include "
eon/eonExceptions.hpp
"
17
#include <algorithm>
18
#include <cmath>
19
20
namespace
eonc::neb
{
21
22
OCINEBController::Config
23
OCINEBController::fromParams
(
const
Parameters
¶ms) {
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
}
37
38
OCINEBController::OCINEBController
(
const
Config
&cfg)
39
:
cfg_
{cfg} {}
40
41
void
OCINEBController::initBaseline
(
double
baseline_force) {
42
baseline_force_
= baseline_force;
43
current_threshold_
= std::max(
baseline_force_
*
cfg_
.trigger_factor,
44
2.0 *
cfg_
.force_tolerance);
45
}
46
47
bool
OCINEBController::shouldTrigger
(
double
convForce,
bool
ci_active,
48
long
climbingImage,
long
numImages,
49
int
ciStabilityCounter)
const
{
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
}
60
61
void
OCINEBController::updateStability
(
long
climbingImage) {
62
if
(climbingImage ==
previousClimbingImage_
) {
63
ciStabilityCounter_
++;
64
}
else
{
65
ciStabilityCounter_
= 0;
66
previousClimbingImage_
= climbingImage;
67
has_cached_mode_
=
false
;
68
}
69
}
70
71
OCINEBController::MMFResult
OCINEBController::run
(
eonc::NudgedElasticBand
&
neb
,
72
double
convForce) {
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_
,
79
current_threshold_
/
baseline_force_
);
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,
117
current_threshold_
);
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_
,
140
current_threshold_
/
baseline_force_
);
141
}
142
143
bool
shouldReset =
144
(savedPositions -
neb
.path[walked]->getPositions()).norm() >
145
neb
.params.optimizer_options().max_move *
146
neb
.params.neb_options().image_count;
147
148
if
(shouldReset) {
149
QUILL_LOG_DEBUG(
log
,
"Resetting optimization history."
);
150
}
151
152
ciStabilityCounter_
= 0;
153
154
return
{newForce,
false
, shouldReset};
155
}
156
157
int
OCINEBController::runDimer
(
eonc::NudgedElasticBand
&
neb
,
158
double
&alignment) {
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
&) {
189
minModeStatus =
MinModeSaddleSearch::STATUS_DIMER_RESTORED_BEST
;
190
QUILL_LOG_DEBUG(
log
,
"MMF: Dimer restored to best state"
);
191
}
catch
(
const
eonc::DimerModeLostException
&) {
192
minModeStatus =
MinModeSaddleSearch::STATUS_DIMER_LOST_MODE
;
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
||
225
minModeStatus ==
MinModeSaddleSearch::STATUS_DIMER_RESTORED_BEST
) {
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
}
244
245
void
OCINEBController::updateThresholdSuccess
(
double
convForce,
246
double
newForce) {
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
}
252
253
void
OCINEBController::updateThresholdBackoff
(
double
alignment) {
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
}
265
266
}
// namespace eonc::neb
AtomMatrix
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition
Eigen.h:37
EonLogger.h
MinModeSaddleSearch.h
NEBOcinebController.h
NudgedElasticBand.h
eonc::DimerModeLostException
Definition
eonExceptions.hpp:7
eonc::DimerModeRestoredException
Definition
eonExceptions.hpp:12
eonc::MinModeSaddleSearch::STATUS_GOOD
@ STATUS_GOOD
Definition
MinModeSaddleSearch.h:31
eonc::MinModeSaddleSearch::STATUS_BAD_MAX_ITERATIONS
@ STATUS_BAD_MAX_ITERATIONS
Definition
MinModeSaddleSearch.h:36
eonc::MinModeSaddleSearch::STATUS_DIMER_LOST_MODE
@ STATUS_DIMER_LOST_MODE
Definition
MinModeSaddleSearch.h:51
eonc::MinModeSaddleSearch::STATUS_DIMER_RESTORED_BEST
@ STATUS_DIMER_RESTORED_BEST
Definition
MinModeSaddleSearch.h:52
eonc::NudgedElasticBand
Definition
NudgedElasticBand.h:38
eonc::Parameters
Definition
Parameters.h:35
eonc::Parameters::neb_options
const neb_options_t & neb_options() const
Definition
ParametersAccess.cpp:180
eonc::neb::OCINEBController::baseline_force_
double baseline_force_
Definition
NEBOcinebController.h:78
eonc::neb::OCINEBController::walkHelped
static bool walkHelped(double walkedForce, double convForce, int mmfStatus)
Definition
NEBOcinebController.h:58
eonc::neb::OCINEBController::shouldTrigger
bool shouldTrigger(double convForce, bool ci_active, long climbingImage, long numImages, int ciStabilityCounter) const
Definition
NEBOcinebController.cpp:47
eonc::neb::OCINEBController::fromParams
static Config fromParams(const Parameters ¶ms)
Definition
NEBOcinebController.cpp:23
eonc::neb::OCINEBController::OCINEBController
OCINEBController(const Config &cfg)
Definition
NEBOcinebController.cpp:38
eonc::neb::OCINEBController::has_cached_mode_
bool has_cached_mode_
Definition
NEBOcinebController.h:84
eonc::neb::OCINEBController::updateThresholdSuccess
void updateThresholdSuccess(double convForce, double newForce)
Definition
NEBOcinebController.cpp:245
eonc::neb::OCINEBController::updateStability
void updateStability(long climbingImage)
Definition
NEBOcinebController.cpp:61
eonc::neb::OCINEBController::mmf_iterations_used_
int mmf_iterations_used_
Definition
NEBOcinebController.h:81
eonc::neb::OCINEBController::previousClimbingImage_
long previousClimbingImage_
Definition
NEBOcinebController.h:79
eonc::neb::OCINEBController::cached_mode_
AtomMatrix cached_mode_
Definition
NEBOcinebController.h:85
eonc::neb::OCINEBController::run
MMFResult run(eonc::NudgedElasticBand &neb, double convForce)
Definition
NEBOcinebController.cpp:71
eonc::neb::OCINEBController::ciStabilityCounter_
int ciStabilityCounter_
Definition
NEBOcinebController.h:80
eonc::neb::OCINEBController::runDimer
int runDimer(eonc::NudgedElasticBand &neb, double &alignment)
Definition
NEBOcinebController.cpp:157
eonc::neb::OCINEBController::initBaseline
void initBaseline(double baseline_force)
Definition
NEBOcinebController.cpp:41
eonc::neb::OCINEBController::current_threshold_
double current_threshold_
Definition
NEBOcinebController.h:77
eonc::neb::OCINEBController::updateThresholdBackoff
void updateThresholdBackoff(double alignment)
Definition
NEBOcinebController.cpp:253
eonc::neb::OCINEBController::convergedClimb
static bool convergedClimb(double bandForce, double forceTolerance, int mmfStatus)
Definition
NEBOcinebController.h:65
eonc::neb::OCINEBController::cfg_
Config cfg_
Definition
NEBOcinebController.h:76
eonExceptions.hpp
eonc::log
Definition
EonLogger.h:28
eonc::log::get
quill::Logger * get() noexcept
Get or create the default "combi" logger.
Definition
EonLogger.h:44
eonc::neb
Definition
NEBForceProjection.cpp:18
eonc::neb::OCINEBController::Config
Definition
NEBOcinebController.h:28
eonc::neb::OCINEBController::MMFResult
Definition
NEBOcinebController.h:47
eonc::neb_options_t::climbing_image_options_t::ocineb
struct eonc::neb_options_t::climbing_image_options_t::hybrid_dimer_t ocineb
eonc::neb_options_t::climbing_image
struct eonc::neb_options_t::climbing_image_options_t climbing_image
eonc::neb_options_t::force_tolerance
double force_tolerance
Definition
ParametersOptions.h:521
client
NEBOcinebController.cpp
Generated by
1.17.0
Generated by
Doxygen 1.17.0
Analytics by
Antics
provided by
TurtleTech ehf