Loading...
Searching...
No Matches
GeometryAnalysis.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*/
13#include "eon/EonLogger.h"
14#include "eon/Matter.h"
15#include "eon/SafeMath.h"
16
17#include <cmath>
18#include <set>
19#include <vector>
20
22 const AtomMatrix r2) {
25 // Determine optimal rotation
26 // Horn, J. Opt. Soc. Am. A, 1987
27 Matrix3d m = r1.transpose() * r2;
28
29 double sxx = m(0, 0);
30 double sxy = m(0, 1);
31 double sxz = m(0, 2);
32 double syx = m(1, 0);
33 double syy = m(1, 1);
34 double syz = m(1, 2);
35 double szx = m(2, 0);
36 double szy = m(2, 1);
37 double szz = m(2, 2);
38
40 n.setZero();
41 n(0, 1) = syz - szy;
42 n(0, 2) = szx - sxz;
43 n(0, 3) = sxy - syx;
44
45 n(1, 2) = sxy + syx;
46 n(1, 3) = szx + sxz;
47
48 n(2, 3) = syz + szy;
49
50 n += n.transpose().eval();
51
52 n(0, 0) = sxx + syy + szz;
53 n(1, 1) = sxx - syy - szz;
54 n(2, 2) = -sxx + syy - szz;
55 n(3, 3) = -sxx - syy + szz;
56
57 Eigen::SelfAdjointEigenSolver<Matrix4d> es(n);
58 Eigen::Vector4d maxv = es.eigenvectors().col(3);
59
60 double aa = maxv[0] * maxv[0];
61 double bb = maxv[1] * maxv[1];
62 double cc = maxv[2] * maxv[2];
63 double dd = maxv[3] * maxv[3];
64 double ab = maxv[0] * maxv[1];
65 double ac = maxv[0] * maxv[2];
66 double ad = maxv[0] * maxv[3];
67 double bc = maxv[1] * maxv[2];
68 double bd = maxv[1] * maxv[3];
69 double cd = maxv[2] * maxv[3];
70
71 R(0, 0) = aa + bb - cc - dd;
72 R(0, 1) = 2 * (bc - ad);
73 R(0, 2) = 2 * (bd + ac);
74 R(1, 0) = 2 * (bc + ad);
75 R(1, 1) = aa - bb + cc - dd;
76 R(1, 2) = 2 * (cd - ab);
77 R(2, 0) = 2 * (bd - ac);
78 R(2, 1) = 2 * (cd + ab);
79 R(2, 2) = aa - bb - cc + dd;
80
81 return R;
82}
83
85 const double max_diff) {
86 AtomMatrix r1 = m1.getPositions();
87 AtomMatrix r2 = m2.getPositions();
88
89 // Align centroids
90 Eigen::VectorXd c1(3);
91 Eigen::VectorXd c2(3);
92
93 c1[0] = r1.col(0).sum();
94 c1[1] = r1.col(1).sum();
95 c1[2] = r1.col(2).sum();
96 c2[0] = r2.col(0).sum();
97 c2[1] = r2.col(1).sum();
98 c2[2] = r2.col(2).sum();
99 c1 /= r1.rows();
100 c2 /= r2.rows();
101
102 for (int i = 0; i < r1.rows(); i++) {
103 r1(i, 0) -= c1[0];
104 r1(i, 1) -= c1[1];
105 r1(i, 2) -= c1[2];
106
107 r2(i, 0) -= c2[0];
108 r2(i, 1) -= c2[1];
109 r2(i, 2) -= c2[2];
110 }
111
113
114 // Eigen is transposed relative to numpy
115 r2 = r2 * R;
116
117 for (int i = 0; i < r1.rows(); i++) {
118 double diff = (r2.row(i) - r1.row(i)).norm();
119 if (diff > max_diff) {
120 return false;
121 }
122 }
123 return true;
124}
125
126// Project out rigid-body translation and rotation from a displacement vector.
127// Uses infinitesimal-rotation basis vectors and modified Gram-Schmidt
128// orthonormalization, following the approach in:
129// R. Goswami and H. Jonsson, "Adaptive Pruning for Increased Robustness and
130// Reduced Computational Overhead in Gaussian Process Accelerated Saddle Point
131// Searches," ChemPhysChem, Nov. 2025, doi: 10.1002/cphc.202500730.
132void eonc::geometry::projectOutRotTrans(Eigen::VectorXd &step,
133 const AtomMatrix &positions) {
134 long nAtoms = positions.rows();
135 long dof = nAtoms * 3;
136
137 // 1. Compute center of mass (unweighted geometric center)
138 Eigen::Vector3d com = Eigen::Vector3d::Zero();
139 for (long i = 0; i < nAtoms; ++i) {
140 com(0) += positions(i, 0);
141 com(1) += positions(i, 1);
142 com(2) += positions(i, 2);
143 }
144 com /= static_cast<double>(nAtoms);
145
146 // 2. Construct 6 basis vectors for rigid-body translation and rotation
147 std::vector<Eigen::VectorXd> basis;
148 basis.reserve(6);
149
150 // Translational basis vectors
151 for (int d = 0; d < 3; ++d) {
152 Eigen::VectorXd t = Eigen::VectorXd::Zero(dof);
153 for (long j = 0; j < nAtoms; ++j) {
154 t(3 * j + d) = 1.0;
155 }
156 basis.push_back(t);
157 }
158
159 // Rotational basis vectors (infinitesimal rotations around COM)
160 Eigen::VectorXd rx = Eigen::VectorXd::Zero(dof);
161 Eigen::VectorXd ry = Eigen::VectorXd::Zero(dof);
162 Eigen::VectorXd rz = Eigen::VectorXd::Zero(dof);
163
164 for (long i = 0; i < nAtoms; ++i) {
165 double x = positions(i, 0) - com(0);
166 double y = positions(i, 1) - com(1);
167 double z = positions(i, 2) - com(2);
168 // Rotation around x-axis: cross(xhat, r) = (0, -z, y)
169 rx(3 * i + 1) = -z;
170 rx(3 * i + 2) = y;
171 // Rotation around y-axis: cross(yhat, r) = (z, 0, -x)
172 ry(3 * i + 0) = z;
173 ry(3 * i + 2) = -x;
174 // Rotation around z-axis: cross(zhat, r) = (-y, x, 0)
175 rz(3 * i + 0) = -y;
176 rz(3 * i + 1) = x;
177 }
178 basis.push_back(rx);
179 basis.push_back(ry);
180 basis.push_back(rz);
181
182 // 3. Modified Gram-Schmidt orthonormalization
183 std::vector<Eigen::VectorXd> ortho;
184 ortho.reserve(6);
185
186 for (auto &v : basis) {
187 Eigen::VectorXd u = v;
188 for (const auto &e : ortho) {
189 u -= u.dot(e) * e;
190 }
191 // Handles linear molecules where one rotational mode is degenerate
192 if (u.norm() > 1e-9) {
193 u.normalize();
194 ortho.push_back(u);
195 }
196 }
197
198 // 4. Project out rigid-body components from step
199 for (const auto &e : ortho) {
200 step -= step.dot(e) * e;
201 }
202}
203
205 std::shared_ptr<Matter> m2) {
206 // Skip for extended systems (slabs/surfaces with frozen atoms).
207 // Rigid-body rotation and translation are not well-defined when the
208 // system is anchored by frozen atoms.
209 if (m2->numberOfFixedAtoms() > 0) {
210 return;
211 }
212
213 AtomMatrix r2 = m2->getPositions();
214 long n = r1_passed.size();
215
216 // Compute displacement as flat 3N vector
217 Eigen::VectorXd step(n);
218 Eigen::Map<const Eigen::VectorXd> r1_flat(r1_passed.data(), n);
219 Eigen::Map<const Eigen::VectorXd> r2_flat(r2.data(), n);
220 step = r2_flat - r1_flat;
221
222 // Project out rigid-body translation and rotation
223 projectOutRotTrans(step, r1_passed);
224
225 // Reconstruct positions: r1 + projected step
226 Eigen::VectorXd result = r1_flat + step;
227 AtomMatrix resultMat(r1_passed.rows(), 3);
228 Eigen::Map<Eigen::VectorXd>(resultMat.data(), n) = result;
229
230 m2->setPositions(resultMat);
231}
232
233void eonc::geometry::rotationRemove(const std::shared_ptr<Matter> m1,
234 std::shared_ptr<Matter> m2) {
235 AtomMatrix r1 = m1->getPositions();
236 rotationRemove(r1, m2);
237 return;
238}
239
241 AtomMatrix r1 = m1.getPositions();
242 AtomMatrix r2 = r2_passed;
243
244 // net displacement
245 Eigen::VectorXd disp(3);
246 AtomMatrix r12 = m1.pbc(r2 - r1);
247
248 disp[0] = r12.col(0).sum();
249 disp[1] = r12.col(1).sum();
250 disp[2] = r12.col(2).sum();
251 disp /= r1.rows();
252
253 for (int i = 0; i < r1.rows(); i++) {
254 r1(i, 0) += disp[0];
255 r1(i, 1) += disp[1];
256 r1(i, 2) += disp[2];
257 }
258
259 m1.setPositions(r1);
260 return;
261}
262
264 AtomMatrix r2 = m2.getPositions();
265 translationRemove(m1, r2);
266 return;
267}
268
270 return v1.rowwise().norm().maxCoeff();
271}
272
273double eonc::geometry::maxAtomMotionV(const VectorXd v1) {
274 double max = 0.0;
275 long n = v1.rows();
276 if (n < 3) {
277 // Vector too short for 3D atom grouping; treat as single displacement
278 return v1.norm();
279 }
280 for (long i = 0; i + 3 <= n; i += 3) {
281 double norm = v1.segment<3>(i).norm();
282 if (max < norm) {
283 max = norm;
284 }
285 }
286 // Handle trailing elements (vector size not multiple of 3)
287 long rem = n % 3;
288 if (rem > 0) {
289 double norm = v1.tail(rem).norm();
290 if (max < norm) {
291 max = norm;
292 }
293 }
294 return max;
295}
296
297long eonc::geometry::numAtomsMoved(const AtomMatrix v1, double cutoff) {
298 long num = 0;
299 for (int i = 0; i < v1.rows(); i++) {
300 double norm = v1.row(i).norm();
301 if (norm >= cutoff) {
302 num += 1;
303 }
304 }
305 return num;
306}
307
309 double maxMotion) {
310 AtomMatrix v2(v1);
311
312 double max = maxAtomMotion(v1);
313 if (max > maxMotion) {
314 v2 *= maxMotion / max;
315 }
316 return v2;
317}
318
319VectorXd eonc::geometry::maxAtomMotionAppliedV(const VectorXd v1,
320 double maxMotion) {
321 VectorXd v2(v1);
322
323 double max = maxAtomMotionV(v1);
324 if (max > maxMotion) {
325 v2 *= maxMotion / max;
326 }
327 return v2;
328}
329
331 double maxMotion) {
332 AtomMatrix v2(v1);
333
334 double max = v1.norm();
335 if (max > maxMotion) {
336 v2 *= maxMotion / max;
337 }
338 return v2;
339}
340
341VectorXd eonc::geometry::maxMotionAppliedV(const VectorXd v1,
342 double maxMotion) {
343 VectorXd v2(v1);
344
345 double max = v1.norm();
346 if (max > maxMotion) {
347 v2 *= maxMotion / max;
348 }
349 return v2;
350}
351
352namespace eonc::geometry {
353struct atom {
354 double r;
355 int z;
356};
357
358struct by_atom {
359 bool operator()(atom const &a, atom const &b) const {
360 if (a.z != b.z) {
361 return a.z < b.z;
362 } else {
363 return a.r < b.r;
364 }
365 }
366};
367} // namespace eonc::geometry
368
369bool eonc::geometry::identical(const Matter &m1, const Matter &m2,
370 const double distanceDifference) {
371
372 AtomMatrix r1 = m1.getPositions();
373 AtomMatrix r2 = m2.getPositions();
374
375 std::set<int> matched;
376 double tolerance = distanceDifference;
377
378 if (r1.rows() != r2.rows()) {
379 return false;
380 }
381 int N = r1.rows();
382
383 for (int i = 0; i < N; i++) {
384 if (std::fabs((m1.pbc(r1.row(i) - r2.row(i))).norm()) < tolerance &&
385 m1.getAtomicNr(i) == m2.getAtomicNr(i)) {
386 matched.insert(i);
387 }
388 }
389
390 for (int j = 0; j < N; j++) {
391
392 if (matched.count(j) == 1)
393 continue;
394
395 for (int k = 0; k < N; k++) {
396 if (matched.count(j) == 1)
397 break;
398
399 if (std::fabs((m1.pbc(r1.row(j) - r2.row(k))).norm()) < tolerance &&
400 m1.getAtomicNr(j) == m2.getAtomicNr(k)) {
401 matched.insert(j);
402 }
403 }
404 }
405
406 if (matched.size() == static_cast<unsigned>(N)) {
407 return true;
408 } else {
409 return false;
410 }
411}
412
413bool eonc::geometry::sortedR(const Matter &m1, const Matter &m2,
414 const double distanceDifference) {
415 EONC_LOG_INFO("In sortedR");
416 AtomMatrix r1 = m1.getPositions();
417 AtomMatrix r2 = m2.getPositions();
418 double tolerance = distanceDifference;
419 int matches = 0;
420
421 if (r1.rows() != r2.rows()) {
422 return false;
423 }
424
425 // Allocate memory for rdf1 and rdf2
426 std::vector<std::set<atom, by_atom>> rdf1(r1.rows());
427 std::vector<std::set<atom, by_atom>> rdf2(r2.rows());
428
429 for (int i2 = 0; i2 < r2.rows(); i2++) {
430 rdf2[i2].clear();
431 for (int j2 = 0; j2 < r2.rows(); j2++) {
432 if (j2 == i2)
433 continue;
434 atom a2;
435 a2.r = m2.distance(i2, j2);
436 a2.z = m2.getAtomicNr(j2);
437 rdf2[i2].insert(a2);
438 rdf2[j2].insert(a2);
439 }
440 }
441
442 for (int i1 = 0; i1 < r1.rows(); i1++) {
443 if (matches == i1 - 2) {
444 return false;
445 }
446 for (int j1 = 0; j1 < r1.rows(); j1++) {
447 if (j1 == i1)
448 continue;
449 atom a;
450 a.r = m1.distance(i1, j1);
451 a.z = m1.getAtomicNr(j1);
452 rdf1[i1].insert(a);
453 rdf1[j1].insert(a);
454 }
455 for (int x = 0; x < r2.rows(); x++) {
456 auto it2 = rdf2[x].begin();
457 auto it = rdf1[i1].begin();
458 int c = 0;
459 int counter = 0;
460 for (; c < r1.rows(); c++) {
461 if (it == rdf1[i1].end() || it2 == rdf2[x].end())
462 break;
463 atom k1 = *it;
464 atom k2 = *it2;
465 if (std::fabs(k1.r - k2.r) < tolerance && k1.z == k2.z) {
466 counter++;
467 } else {
468 EONC_LOG_INFO("No match");
469 break;
470 }
471 ++it;
472 ++it2;
473 }
474 if (counter == r1.rows()) {
475 matches++;
476 } else {
477 EONC_LOG_INFO("No match");
478 }
479 }
480 }
481
482 return matches >= r1.rows();
483}
484
485void eonc::geometry::pushApart(std::shared_ptr<Matter> m1, double minDistance) {
486 if (minDistance <= 0)
487 return;
488
489 AtomMatrix r1 = m1->getPositions();
490 AtomMatrix Force(r1.rows(), 3);
491 double f = 0.025;
492 double cut = minDistance;
493 double pushAparts = 500;
494 for (int p = 0; p < r1.rows(); p++) {
495 for (int axis = 0; axis <= 2; axis++) {
496 Force(p, axis) = 0;
497 }
498 }
499 for (int count = 0; count < pushAparts; count++) {
500 int moved = 0;
501 for (int i = 0; i < r1.rows(); i++) {
502 for (int j = i + 1; j < r1.rows(); j++) {
503 double d = m1->distance(i, j);
504 if (d < cut) {
505 moved++;
506 for (int axis = 0; axis <= 2; axis++) {
507 double componant = f * (r1(i, axis) - r1(j, axis)) / d;
508 Force(i, axis) += componant;
509 Force(j, axis) -= componant;
510 }
511 }
512 }
513 }
514 if (moved == 0)
515 break;
516 for (int k = 0; k < r1.rows(); k++) {
517 for (int axis = 0; axis <= 2; axis++) {
518 r1(k, axis) += Force(k, axis);
519 Force(k, axis) = 0;
520 }
521 }
522 m1->setPositions(r1);
523 }
524}
Eigen::Matrix< double, 3, 3, eOnStorageOrder > RotationMatrix
Definition Eigen.h:38
Eigen::Matrix< double, 3, 3, eOnStorageOrder > Matrix3d
Definition Eigen.h:35
Eigen::Matrix< double, 4, 4, eOnStorageOrder > Matrix4d
Definition Eigen.h:36
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
#define EONC_LOG_INFO(...)
Definition EonLogger.h:250
double distance(long index1, long index2) const
Definition Matter.cpp:360
AtomMatrix pbc(const AtomMatrix &diff) const
Definition Matter.h:163
long getAtomicNr(long int atom) const
Definition Matter.cpp:392
void setPositions(const AtomMatrix &pos)
Definition Matter.cpp:273
const AtomMatrix & getPositions() const
Definition Matter.cpp:236
RotationMatrix rotationExtract(const AtomMatrix r1, const AtomMatrix r2)
AtomMatrix maxAtomMotionApplied(const AtomMatrix v1, double maxMotion)
long numAtomsMoved(const AtomMatrix v1, double cutoff)
double maxAtomMotion(const AtomMatrix v1)
void pushApart(std::shared_ptr< Matter > m1, double minDistance)
bool rotationMatch(const Matter &m1, const Matter &m2, const double max_diff)
VectorXd maxMotionAppliedV(const VectorXd v1, double maxMotion)
void projectOutRotTrans(Eigen::VectorXd &step, const AtomMatrix &positions)
bool identical(const Matter &m1, const Matter &m2, const double distanceDifference)
VectorXd maxAtomMotionAppliedV(const VectorXd v1, double maxMotion)
double maxAtomMotionV(const VectorXd v1)
bool sortedR(const Matter &m1, const Matter &m2, const double distanceDifference)
void rotationRemove(const AtomMatrix r1, std::shared_ptr< Matter > m2)
AtomMatrix maxMotionApplied(const AtomMatrix v1, double maxMotion)
void translationRemove(Matter &m1, const AtomMatrix r1)
bool operator()(atom const &a, atom const &b) const