Loading...
Searching...
No Matches
NEBSpringForce.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/NEBSpringForce.h"
13
14namespace eonc::neb {
15
16// --- UniformSpring ---
17
19UniformSpring::compute(long i, const AtomMatrix &tangent, double distNext,
20 double distPrev, const AtomMatrix &posDiffNext,
21 const AtomMatrix &posDiffPrev,
22 const std::shared_ptr<Matter> &image) const {
23 SpringResult result;
24 result.forceSpringPar = ksp * (distNext - distPrev) * tangent;
25 result.forceSpring = ksp * image->pbc((posDiffNext) - (posDiffPrev));
26 return result;
27}
28
29// --- WeightedSpring ---
30
32 double distNext, double distPrev) const {
33 double kspNext = springConstants[i];
34 double kspPrev = springConstants[i - 1];
35
36 SpringResult result;
37 result.forceSpringPar =
38 ((kspNext * distNext) - (kspPrev * distPrev)) * tangent;
39 result.forceSpring = AtomMatrix::Zero(tangent.rows(), tangent.cols());
40 return result;
41}
42
43// --- OnsagerMachlupSpring ---
44
47 const AtomMatrix &posNext,
48 const AtomMatrix &posPrev, const AtomMatrix &pos,
49 const std::shared_ptr<Matter> &image) const {
50 // Mandelli Eq. 13: k * ( R(i+1) + R(i-1) - 2R(i) + L(i+1) - L(i) )
51 AtomMatrix diff =
52 image->pbc(posNext + posPrev - 2.0 * pos + L_vecs[i + 1] - L_vecs[i]);
53 AtomMatrix f_om_vec = base_k * diff;
54
55 // Mandelli Eq. 15: Project onto tangent
56 SpringResult result;
57 result.forceSpringPar = matDot(f_om_vec, tangent) * tangent;
58 result.forceSpring = AtomMatrix::Zero(tangent.rows(), tangent.cols());
59 return result;
60}
61
62// --- Factory ---
63
66 const std::vector<std::shared_ptr<Matter>> &path,
67 long numImages, int atoms, double maxEnergy, double E_ref) {
68
69 if (params.neb_options.spring.om.enabled) {
70 double base_k = params.neb_options.spring.constant;
71
72 if (params.neb_options.spring.om.optimize_k) {
73 double avgPotForce = 0.0;
74 double avgPathCurvature = 0.0;
75 int count = 0;
76 for (long j = 1; j <= numImages; j++) {
77 avgPotForce += path[j]->getForces().norm();
78 AtomMatrix next = path[j + 1]->getPositions();
79 AtomMatrix prev = path[j - 1]->getPositions();
80 AtomMatrix curr = path[j]->getPositions();
81 AtomMatrix curvVec = path[j]->pbc(next + prev - 2.0 * curr);
82 avgPathCurvature += curvVec.norm();
83 count++;
84 }
85 if (count > 0 && avgPathCurvature > 1e-6) {
86 double scale = params.neb_options.spring.om.k_scale;
87 base_k = scale * (avgPotForce / avgPathCurvature);
88 base_k = std::max(base_k, params.neb_options.spring.om.k_min);
89 base_k = std::min(base_k, params.neb_options.spring.om.k_max);
90 }
91 }
92
93 // Pre-calculate L vectors for all images
94 std::vector<AtomMatrix> L_vecs(numImages + 2);
95 for (long j = 0; j <= numImages + 1; j++) {
96 L_vecs[j].resize(atoms, 3);
97 if (j == 0 || j == numImages + 1) {
98 L_vecs[j].setZero();
99 } else {
100 const AtomMatrix &forces = path[j]->getForces();
101 double alpha_k = eonc::safemath::safe_recip(2.0 * base_k, 0.0);
102 for (int k = 0; k < atoms; k++) {
103 L_vecs[j].row(k) = alpha_k * forces.row(k);
104 }
105 }
106 }
107
108 return OnsagerMachlupSpring{base_k, std::move(L_vecs)};
109
110 } else if (params.neb_options.spring.weighting.enabled) {
111 double k_l = params.neb_options.spring.weighting.k_min;
112 double k_u = params.neb_options.spring.weighting.k_max;
113 std::vector<double> springConstants(numImages + 2, k_l);
114
115 double energyRange = maxEnergy - E_ref;
116 if (energyRange < 1e-10) {
117 std::fill(springConstants.begin(), springConstants.end(), k_l);
118 } else {
119 for (int idx = 1; idx <= numImages + 1; idx++) {
120 double Ei = std::max(path[idx]->getPotentialEnergy(),
121 path[idx - 1]->getPotentialEnergy());
122 if (Ei > E_ref) {
123 double alpha_i = (maxEnergy - Ei) / energyRange;
124 alpha_i = std::max(0.0, std::min(1.0, alpha_i));
125 springConstants[idx - 1] = (1.0 - alpha_i) * k_u + alpha_i * k_l;
126 } else {
127 springConstants[idx - 1] = k_l;
128 }
129 }
130 }
131
132 return WeightedSpring{std::move(springConstants)};
133
134 } else {
136 }
137}
138
139} // namespace eonc::neb
double matDot(const AtomMatrix &a, const AtomMatrix &b)
SIMD-optimized dot product for contiguous Eigen matrices.
Definition Eigen.h:50
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
struct eonc::Parameters::neb_options_t neb_options
SpringStrategy buildSpringStrategy(const Parameters &params, const std::vector< std::shared_ptr< Matter > > &path, long numImages, int atoms, double maxEnergy, double E_ref)
Build the appropriate spring strategy from parameters and current path state.
std::variant< UniformSpring, WeightedSpring, OnsagerMachlupSpring > SpringStrategy
constexpr double safe_recip(double x, double fallback=0.0)
Definition SafeMath.h:29
struct eonc::Parameters::neb_options_t::spring_options_t::onsager_machlup_t om
struct eonc::Parameters::neb_options_t::spring_options_t::energy_weighting_t weighting
struct eonc::Parameters::neb_options_t::spring_options_t spring
Onsager-Machlup action-based springs (Mandelli & Parrinello 2021).
SpringResult compute(long i, const AtomMatrix &tangent, const AtomMatrix &posNext, const AtomMatrix &posPrev, const AtomMatrix &pos, const std::shared_ptr< Matter > &image) const
std::vector< AtomMatrix > L_vecs
Result of spring force computation for a single image.
Uniform spring constant for all images.
SpringResult compute(long i, const AtomMatrix &tangent, double distNext, double distPrev, const AtomMatrix &posDiffNext, const AtomMatrix &posDiffPrev, const std::shared_ptr< Matter > &image) const
Energy-weighted spring constants (variable per segment).
std::vector< double > springConstants
SpringResult compute(long i, const AtomMatrix &tangent, double distNext, double distPrev) const