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
19 double energy = 0.0;
20 int natoms = matter->numberOfAtoms();
21 AtomMatrix pos = matter->getPositions();
22
23 // Loop over unique pairs
24 for (int i = 0; i < natoms; ++i) {
25 for (int j = i + 1; j < natoms; ++j) {
26 // Respect PBC
27 double r = matter->pbc(pos.row(i) - pos.row(j)).norm();
28
29 // Weight function w = 1 / r^4
30 // Avoid division by zero if atoms overlap perfectly (unlikely in IDPP but
31 // possible)
32 if (r < 1e-4)
33 r = 1e-4;
34
35 double diff = r - d_target(i, j);
36 double r2 = r * r;
37 double weight = 1.0 / (r2 * r2); // 1/r^4
38
39 energy += 0.5 * weight * diff * diff;
40 }
41 }
42 return energy;
43}
44
46 int natoms = matter->numberOfAtoms();
47 AtomMatrix pos = matter->getPositions();
48 AtomMatrix forces = AtomMatrix::Zero(natoms, 3);
49
50 for (int i = 0; i < natoms; ++i) {
51 for (int j = i + 1; j < natoms; ++j) {
52 // Vector pointing from j to i
53 Eigen::RowVector3d dr_vec = matter->pbc(pos.row(i) - pos.row(j));
54 double r = dr_vec.norm();
55
56 if (r < 1e-4)
57 r = 1e-4;
58
59 double diff = r - d_target(i, j);
60 double r2 = r * r;
61 double r5 = r2 * r2 * r;
62
63 // Derivative of E_pair = 0.5 * (1/r^4) * (r - d_target)^2
64 // dE/dr = (r - d_target)/r^4 - 2(r - d_target)^2 / r^5
65 // Simplified: (r - d_target) * (1 - 2(r - d_target)/r) / r^4
66
67 double r4 = r2 * r2;
68 double dEdr = (diff * (1.0 - 2.0 * diff / r)) / r4;
69
70 // Force contribution: F = -dE/dr * (dr_vec / r)
71 Eigen::RowVector3d f_contribution = -dEdr * (dr_vec / r);
72
73 forces.row(i) += f_contribution;
74 forces.row(j) -= f_contribution; // Newton's 3rd law
75 }
76 }
77
78 // Convert N x 3 matrix to 3N vector and return negative gradient (force)
79 // BUT getGradient expects the Gradient (positive derivative), so we return
80 // -Forces Actually, typical eOn getGradient returns dV/dx.
81 return VectorXd::Map(forces.data(), 3 * natoms) * -1.0;
82}
83
85 int natoms = m.numberOfAtoms();
86 MatrixXd d(natoms, natoms);
87 auto pos = m.getPositions();
88 for (int i = 0; i < natoms; ++i) {
89 for (int j = 0; j < natoms; ++j) {
90 d(i, j) = m.pbc(pos.row(i) - pos.row(j)).norm();
91 }
92 }
93 return d;
94}
95
98 const MatrixXd &dTarget) {
99 int natoms = m.numberOfAtoms();
100 AtomMatrix forces = AtomMatrix::Zero(natoms, 3);
101 auto pos = m.getPositions();
102
103 for (int i = 0; i < natoms; ++i) {
104 for (int j = i + 1; j < natoms; ++j) {
105 Eigen::RowVector3d dr_vec = m.pbc(pos.row(i) - pos.row(j));
106 double r = dr_vec.norm();
107 if (r < 1e-4)
108 r = 1e-4;
109
110 double diff = r - dTarget(i, j);
111 // SOTA Weighting: w = 1/r^4. Gradient logic matches Smidstrup/ASE
112 double r2 = r * r;
113 double r5 = r2 * r2 * r;
114 double dEdr = (diff / r5) * (2.0 * dTarget(i, j) - r);
115
116 Eigen::RowVector3d f = -dEdr * (dr_vec / r); // -dE/dr * r_hat
117 forces.row(i) += f;
118 forces.row(j) -= f;
119 }
120 }
121 return forces;
122}
123
125 int nImgs = path.size() - 2; // Exclude fixed endpoints
126 int natoms = path[0].numberOfAtoms();
127 VectorXd totalGradient(3 * natoms * nImgs);
128 double maxForce = 0.0;
129
130 // 1. Compute Raw IDPP Forces and Tangents
131 std::vector<AtomMatrix> rawForces(path.size());
132 std::vector<AtomMatrix> tangents(path.size());
133
134 // We compute for 1..N (moving images)
135 for (size_t i = 1; i <= nImgs; ++i) {
136 // Interpolate Target
137 double xi = static_cast<double>(i) / (nImgs + 1);
138 MatrixXd dTarget = (1.0 - xi) * dInit + xi * dFinal;
139
140 rawForces[i] = getIDPPForces(path[i], dTarget);
141
142 // Simple Tangent: Next - Prev
143 AtomMatrix nextPos = path[i + 1].getPositions();
144 AtomMatrix prevPos = path[i - 1].getPositions();
145 tangents[i] = path[i].pbc(nextPos - prevPos);
146 tangents[i].normalize(); // Unit tangent
147 }
148
149 // 2. Project Forces and Add Springs (The "NEB" part of IDPP-NEB)
150 double k = params.neb_options.spring.constant;
151
152 for (size_t i = 1; i <= nImgs; ++i) {
153 AtomMatrix f = rawForces[i];
154 AtomMatrix t = tangents[i];
155
156 // Perpendicular Force (IDPP optimization)
157 double f_dot_t = matDot(f, t);
158 AtomMatrix f_perp = f - f_dot_t * t;
159
160 // Spring Force (Spacing optimization)
161 double distNext = path[i].distanceTo(path[i + 1]);
162 double distPrev = path[i].distanceTo(path[i - 1]);
163 AtomMatrix f_spring = k * (distNext - distPrev) * t;
164
165 // Total NEB Force
166 AtomMatrix f_neb = f_perp + f_spring;
167
168 // Store as Gradient (-Force)
169 totalGradient.segment(3 * natoms * (i - 1), 3 * natoms) =
170 VectorXd::Map(f_neb.data(), 3 * natoms) * -1.0;
171
172 // Tracking convergence
173 maxForce = std::max(maxForce, f_neb.template lpNorm<Eigen::Infinity>());
174 }
175
176 lastMaxForce = maxForce;
177 return totalGradient;
178}
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
MatrixXd getIDPPForces(const Matter &m, const MatrixXd &dTarget)
VectorXd getGradient(bool fdstep=false) override
std::shared_ptr< Matter > matter
VectorXd getGradient(bool fdstep=false) override
AtomMatrix pbc(const AtomMatrix &diff) const
Definition Matter.h:163
long int numberOfAtoms() const
Definition Matter.cpp:209
const AtomMatrix & getPositions() const
Definition Matter.cpp:236
const Parameters & params