Loading...
Searching...
No Matches
IDPPObjectiveFunction.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
14
15#include <algorithm>
16#include <cmath>
17
18namespace eonc {
19
20namespace {
21VectorXd packFree(const Matter &m, const AtomMatrix &forces) {
22 const long nfree = m.numberOfFreeAtoms();
23 AtomMatrix freeF(nfree, 3);
24 long k = 0;
25 for (long i = 0; i < m.numberOfAtoms(); ++i) {
26 if (!m.getFixed(i)) {
27 freeF.row(k++) = forces.row(i);
28 }
29 }
30 return VectorXd(VectorXd::Map(freeF.data(), 3 * nfree));
31}
32} // namespace
33
35 double energy = 0.0;
36 int natoms = matter->numberOfAtoms();
37 AtomMatrix pos = matter->getPositions();
38
39 // Loop over unique pairs
40 for (int i = 0; i < natoms; ++i) {
41 for (int j = i + 1; j < natoms; ++j) {
42 // Respect PBC
43 double r = matter->pbc(pos.row(i) - pos.row(j)).norm();
44
45 // Weight function w = 1 / r^4
46 // Avoid division by zero if atoms overlap perfectly (unlikely in IDPP but
47 // possible)
48 if (r < 1e-4)
49 r = 1e-4;
50
51 double diff = r - d_target(i, j);
52 double r2 = r * r;
53 double weight = 1.0 / (r2 * r2); // 1/r^4
54
55 energy += 0.5 * weight * diff * diff;
56 }
57 }
58 return energy;
59}
60
61VectorXd IDPPObjectiveFunction::getGradient(bool /*fdstep*/) {
62 int natoms = matter->numberOfAtoms();
63 AtomMatrix pos = matter->getPositions();
64 AtomMatrix forces = AtomMatrix::Zero(natoms, 3);
65
66 for (int i = 0; i < natoms; ++i) {
67 for (int j = i + 1; j < natoms; ++j) {
68 // Vector pointing from j to i
69 Eigen::RowVector3d dr_vec = matter->pbc(pos.row(i) - pos.row(j));
70 double r = dr_vec.norm();
71
72 if (r < 1e-4)
73 r = 1e-4;
74
75 double diff = r - d_target(i, j);
76 double r2 = r * r;
77
78 // Derivative of E_pair = 0.5 * (1/r^4) * (r - d_target)^2
79 // dE/dr = (r - d_target)/r^4 - 2(r - d_target)^2 / r^5
80 // Simplified: (r - d_target) * (1 - 2(r - d_target)/r) / r^4
81
82 double r4 = r2 * r2;
83 double dEdr = (diff * (1.0 - 2.0 * diff / r)) / r4;
84
85 // Force contribution: F = -dE/dr * (dr_vec / r)
86 Eigen::RowVector3d f_contribution = -dEdr * (dr_vec / r);
87
88 forces.row(i) += f_contribution;
89 forces.row(j) -= f_contribution; // Newton's 3rd law
90 }
91 }
92
93 // dV/dx on free atoms only. Frozen rows stay in `forces` for Newton's
94 // third law during the pair loop, then are dropped.
95 return packFree(*matter, forces) * -1.0;
96}
97
99 int natoms = m.numberOfAtoms();
100 MatrixXd d(natoms, natoms);
101 auto pos = m.getPositions();
102 for (int i = 0; i < natoms; ++i) {
103 for (int j = 0; j < natoms; ++j) {
104 d(i, j) = m.pbc(pos.row(i) - pos.row(j)).norm();
105 }
106 }
107 return d;
108}
109
112 const MatrixXd &dTarget) {
113 int natoms = m.numberOfAtoms();
114 AtomMatrix forces = AtomMatrix::Zero(natoms, 3);
115 auto pos = m.getPositions();
116
117 for (int i = 0; i < natoms; ++i) {
118 for (int j = i + 1; j < natoms; ++j) {
119 Eigen::RowVector3d dr_vec = m.pbc(pos.row(i) - pos.row(j));
120 double r = dr_vec.norm();
121 if (r < 1e-4)
122 r = 1e-4;
123
124 double diff = r - dTarget(i, j);
125 // SOTA Weighting: w = 1/r^4. Gradient logic matches Smidstrup/ASE
126 double r2 = r * r;
127 double r5 = r2 * r2 * r;
128 double dEdr = (diff / r5) * (2.0 * dTarget(i, j) - r);
129
130 Eigen::RowVector3d f = -dEdr * (dr_vec / r); // -dE/dr * r_hat
131 forces.row(i) += f;
132 forces.row(j) -= f;
133 }
134 }
135 return forces;
136}
137
139 int nImgs = path.size() - 2; // Exclude fixed endpoints
140 int nfree = static_cast<int>(path[0].numberOfFreeAtoms());
141 VectorXd totalGradient(3 * nfree * nImgs);
142 double maxForce = 0.0;
143
144 // 1. Compute Raw IDPP Forces and Tangents
145 std::vector<AtomMatrix> rawForces(path.size());
146 std::vector<AtomMatrix> tangents(path.size());
147
148 // We compute for 1..N (moving images)
149 for (size_t i = 1; i <= nImgs; ++i) {
150 // Interpolate Target
151 double xi = static_cast<double>(i) / (nImgs + 1);
152 MatrixXd dTarget = (1.0 - xi) * dInit + xi * dFinal;
153
154 rawForces[i] = getIDPPForces(path[i], dTarget);
155
156 // Simple Tangent: Next - Prev
157 AtomMatrix nextPos = path[i + 1].getPositions();
158 AtomMatrix prevPos = path[i - 1].getPositions();
159 tangents[i] = path[i].pbc(nextPos - prevPos);
160 const double tnorm = tangents[i].norm();
161 if (tnorm > 1e-10) {
162 tangents[i] /= tnorm;
163 }
164 }
165
166 // 2. Project Forces and Add Springs (The "NEB" part of IDPP-NEB)
167 double k = params.neb_options().spring.constant;
168
169 for (size_t i = 1; i <= nImgs; ++i) {
170 AtomMatrix f = rawForces[i];
171 AtomMatrix t = tangents[i];
172
173 // Perpendicular Force (IDPP optimization)
174 double f_dot_t = matDot(f, t);
175 AtomMatrix f_perp = f - f_dot_t * t;
176
177 // Spring Force (Spacing optimization)
178 double distNext = path[i].distanceTo(path[i + 1]);
179 double distPrev = path[i].distanceTo(path[i - 1]);
180 AtomMatrix f_spring = k * (distNext - distPrev) * t;
181
182 AtomMatrix f_neb = f_perp + f_spring;
183
184 VectorXd freeForce = packFree(path[i], f_neb);
185 totalGradient.segment(3 * nfree * static_cast<int>(i - 1), 3 * nfree) =
186 freeForce * -1.0;
187
188 // Free-atom residuals only. Frozen pair rows stay in f_neb for
189 // Newton's third law and must not pin lastMaxForce.
190 maxForce = std::max(maxForce, freeForce.lpNorm<Eigen::Infinity>());
191 }
192
193 lastMaxForce = maxForce;
194 return totalGradient;
195}
196
197} // namespace eonc
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, Eigen::Dynamic, eOnStorageOrder > MatrixXd
Definition Eigen.h:33
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
VectorXd getGradient(bool fdstep=false) override
MatrixXd getIDPPForces(const Matter &m, const MatrixXd &dTarget)
std::shared_ptr< Matter > matter
VectorXd getGradient(bool fdstep=false) override
const AtomMatrix & getPositions() const
Definition Matter.cpp:308
long int numberOfAtoms() const
Definition Matter.cpp:273
AtomMatrix pbc(const AtomMatrix &diff) const
Definition Matter.cpp:810
const Parameters & params
RAII resource manager for the ARTn C library with global synchronization.