eOn client
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
};
35
}
36
37
OCINEBController::OCINEBController
(
const
Config
&cfg)
38
:
cfg_
{cfg} {}
39
40
void
OCINEBController::initBaseline
(
double
baseline_force) {
41
baseline_force_
= baseline_force;
42
current_threshold_
=
baseline_force_
*
cfg_
.trigger_factor;
43
}
44
45
bool
OCINEBController::shouldTrigger
(
double
convForce,
bool
ci_active,
46
long
climbingImage,
long
numImages,
47
int
ciStabilityCounter)
const
{
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
}
58
59
void
OCINEBController::updateStability
(
long
climbingImage) {
60
if
(climbingImage ==
previousClimbingImage_
) {
61
ciStabilityCounter_
++;
62
}
else
{
63
ciStabilityCounter_
= 0;
64
previousClimbingImage_
= climbingImage;
65
has_cached_mode_
=
false
;
66
}
67
}
68
69
OCINEBController::MMFResult
OCINEBController::run
(
eonc::NudgedElasticBand
&
neb
,
70
double
convForce) {
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_
,
77
current_threshold_
/
baseline_force_
);
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_
,
104
current_threshold_
);
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_
,
124
current_threshold_
/
baseline_force_
);
125
}
126
127
bool
shouldReset =
128
(savedPositions -
neb
.path[
neb
.climbingImage]->getPositions()).norm() >
129
neb
.params.optimizer_options.max_move *
130
neb
.params.neb_options.image_count;
131
132
if
(shouldReset) {
133
QUILL_LOG_DEBUG(
log
,
"Resetting optimization history."
);
134
}
135
136
ciStabilityCounter_
= 0;
137
138
return
{newForce,
false
, shouldReset};
139
}
140
141
int
OCINEBController::runDimer
(
eonc::NudgedElasticBand
&
neb
,
142
double
&alignment) {
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
&) {
173
minModeStatus =
MinModeSaddleSearch::STATUS_DIMER_RESTORED_BEST
;
174
QUILL_LOG_DEBUG(
log
,
"MMF: Dimer restored to best state"
);
175
}
catch
(
const
eonc::DimerModeLostException
&) {
176
minModeStatus =
MinModeSaddleSearch::STATUS_DIMER_LOST_MODE
;
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
||
197
minModeStatus ==
MinModeSaddleSearch::STATUS_DIMER_RESTORED_BEST
) {
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
}
216
217
void
OCINEBController::updateThresholdSuccess
(
double
convForce,
218
double
newForce) {
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
}
223
224
void
OCINEBController::updateThresholdBackoff
(
double
alignment) {
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
}
237
238
}
// 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:13
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:28
eonc::Parameters::neb_options
struct eonc::Parameters::neb_options_t neb_options
eonc::neb::OCINEBController::baseline_force_
double baseline_force_
Definition
NEBOcinebController.h:62
eonc::neb::OCINEBController::shouldTrigger
bool shouldTrigger(double convForce, bool ci_active, long climbingImage, long numImages, int ciStabilityCounter) const
Definition
NEBOcinebController.cpp:45
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:37
eonc::neb::OCINEBController::has_cached_mode_
bool has_cached_mode_
Definition
NEBOcinebController.h:68
eonc::neb::OCINEBController::updateThresholdSuccess
void updateThresholdSuccess(double convForce, double newForce)
Definition
NEBOcinebController.cpp:217
eonc::neb::OCINEBController::updateStability
void updateStability(long climbingImage)
Definition
NEBOcinebController.cpp:59
eonc::neb::OCINEBController::mmf_iterations_used_
int mmf_iterations_used_
Definition
NEBOcinebController.h:65
eonc::neb::OCINEBController::previousClimbingImage_
long previousClimbingImage_
Definition
NEBOcinebController.h:63
eonc::neb::OCINEBController::cached_mode_
AtomMatrix cached_mode_
Definition
NEBOcinebController.h:69
eonc::neb::OCINEBController::run
MMFResult run(eonc::NudgedElasticBand &neb, double convForce)
Definition
NEBOcinebController.cpp:69
eonc::neb::OCINEBController::ciStabilityCounter_
int ciStabilityCounter_
Definition
NEBOcinebController.h:64
eonc::neb::OCINEBController::runDimer
int runDimer(eonc::NudgedElasticBand &neb, double &alignment)
Definition
NEBOcinebController.cpp:141
eonc::neb::OCINEBController::initBaseline
void initBaseline(double baseline_force)
Definition
NEBOcinebController.cpp:40
eonc::neb::OCINEBController::current_threshold_
double current_threshold_
Definition
NEBOcinebController.h:61
eonc::neb::OCINEBController::updateThresholdBackoff
void updateThresholdBackoff(double alignment)
Definition
NEBOcinebController.cpp:224
eonc::neb::OCINEBController::cfg_
Config cfg_
Definition
NEBOcinebController.h:60
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::Parameters::neb_options_t::climbing_image_options_t::ocineb
struct eonc::Parameters::neb_options_t::climbing_image_options_t::hybrid_dimer_t ocineb
eonc::Parameters::neb_options_t::climbing_image
struct eonc::Parameters::neb_options_t::climbing_image_options_t climbing_image
eonc::Parameters::neb_options_t::force_tolerance
double force_tolerance
Definition
Parameters.h:471
eonc::neb::OCINEBController::Config
Definition
NEBOcinebController.h:28
eonc::neb::OCINEBController::MMFResult
Definition
NEBOcinebController.h:46
client
NEBOcinebController.cpp
Generated by
1.17.0
Generated by
Doxygen 1.17.0
Analytics by
Antics
provided by
TurtleTech ehf