Loading...
Searching...
No Matches
SolidStateNEB.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/SolidStateNEB.h"
13
14#include <cmath>
15#include <stdexcept>
16
17namespace eonc::neb {
18namespace {
19
20void zeroUpper(Matrix3d &cell) {
21 cell(0, 1) = 0.0;
22 cell(0, 2) = 0.0;
23 cell(1, 2) = 0.0;
24}
25
26double wrapFrac(double value) {
27 double wrapped = value - std::floor(value);
28 if (wrapped >= 0.5) {
29 wrapped -= 1.0;
30 }
31 return wrapped;
32}
33
34AtomMatrix fractionalCoordinates(const Matter &image) {
35 return image.getPositions() * image.getCell().inverse();
36}
37
38} // namespace
39
40double solidStateJacobian(double meanVolume, int nAtoms, double weight) {
41 if (!(meanVolume > 0.0) || nAtoms < 1 || !(weight > 0.0)) {
42 throw std::invalid_argument(
43 "solid-state Jacobian needs a positive volume, atom count, and weight");
44 }
45 const double n = static_cast<double>(nAtoms);
46 return std::cbrt(meanVolume / n) * std::sqrt(n) * weight;
47}
48
50 const Vector3d a = cell.row(0);
51 const Vector3d b = cell.row(1);
52 const double na = a.norm();
53 if (!(na > 1e-12)) {
54 return false;
55 }
56 const Vector3d e1 = a / na;
57 const Vector3d bPerp = b - b.dot(e1) * e1;
58 const double nb = bPerp.norm();
59 if (!(nb > 1e-12)) {
60 return false;
61 }
62 const Vector3d e2 = bPerp / nb;
63 const Vector3d e3 = e1.cross(e2);
64 Matrix3d rotation;
65 rotation.col(0) = e1;
66 rotation.col(1) = e2;
67 rotation.col(2) = e3;
68 Matrix3d oriented = cell * rotation;
69 zeroUpper(oriented);
70 if (positions.rows() > 0) {
71 positions = positions * rotation;
72 }
73 if (!(std::abs(oriented.determinant()) > 1e-18)) {
74 return false;
75 }
76 cell = oriented;
77 return true;
78}
79
81 Matrix3d cell = image.getCell();
82 AtomMatrix positions = image.getPositions();
83 if (!orientCellLowerTriangular(cell, positions)) {
84 throw std::invalid_argument(
85 "solid_state NEB could not put the cell in lower-triangular form");
86 }
87 image.setCell(cell);
88 if (positions.rows() > 0) {
89 image.setPositions(positions);
90 }
91}
92
93void interpolateSolidStateLinear(std::vector<Matter> &images) {
94 const auto n = static_cast<long>(images.size());
95 if (n < 3) {
96 throw std::invalid_argument(
97 "solid_state interpolation needs two endpoints and one image");
98 }
99 const Matrix3d h0 = images.front().getCell();
100 const Matrix3d h1 = images.back().getCell();
101 const AtomMatrix s0 = fractionalCoordinates(images.front());
102 AtomMatrix ds = fractionalCoordinates(images.back()) - s0;
103 for (int atom = 0; atom < ds.rows(); ++atom) {
104 for (int axis = 0; axis < 3; ++axis) {
105 ds(atom, axis) = wrapFrac(ds(atom, axis));
106 }
107 }
108 const double denom = static_cast<double>(n - 1);
109 for (long i = 1; i < n - 1; ++i) {
110 const double t = static_cast<double>(i) / denom;
111 Matrix3d h = (1.0 - t) * h0 + t * h1;
112 zeroUpper(h);
113 const AtomMatrix fractional = s0 + t * ds;
114 images[static_cast<size_t>(i)].setCell(h);
115 images[static_cast<size_t>(i)].setPositions(fractional * h);
116 }
117}
118
120 double jacobian) {
121 if (!(jacobian > 0.0)) {
122 throw std::invalid_argument(
123 "solid-state displacement needs a positive Jacobian");
124 }
125 const Matrix3d hFrom = from.getCell();
126 const Matrix3d hTo = to.getCell();
127 AtomMatrix frac = fractionalCoordinates(to) - fractionalCoordinates(from);
128 for (int atom = 0; atom < frac.rows(); ++atom) {
129 for (int axis = 0; axis < 3; ++axis) {
130 frac(atom, axis) = wrapFrac(frac(atom, axis));
131 }
132 }
133 const Matrix3d average = 0.5 * (hFrom + hTo);
134 JointBlock out;
135 out.atomic = frac * average;
136 const Matrix3d dh = jacobian * (hTo - hFrom);
137 out.cell = 0.5 * (hFrom.inverse() * dh + hTo.inverse() * dh);
138 zeroUpper(out.cell);
139 return out;
140}
141
142double jointNorm(const JointBlock &block) {
143 return std::hypot(block.atomic.norm(), block.cell.norm());
144}
145
146Matrix3d cellNebForce(const Matrix3d &cauchy, double volume, double jacobian,
147 const Matrix3d &externalStress) {
148 if (!(volume > 0.0) || !(jacobian > 0.0)) {
149 throw std::invalid_argument(
150 "solid-state cell force needs a positive volume and Jacobian");
151 }
152 Matrix3d force = -(volume / jacobian) * (cauchy + externalStress);
153 zeroUpper(force);
154 return force;
155}
156
157Matrix3d finiteDifferenceCauchyStress(const Matter &image, double strainStep) {
158 if (!(strainStep > 0.0)) {
159 throw std::invalid_argument(
160 "stress finite difference needs a positive step");
161 }
162 const Matrix3d cell = image.getCell();
163 const AtomMatrix positions = image.getPositions();
164 const double volume = std::abs(cell.determinant());
165 if (!(volume > 0.0)) {
166 throw std::invalid_argument(
167 "stress finite difference needs a nonzero cell");
168 }
169 Matrix3d sigma = Matrix3d::Zero();
170 const int rows[6] = {0, 1, 1, 2, 2, 2};
171 const int cols[6] = {0, 0, 1, 0, 1, 2};
172 for (int comp = 0; comp < 6; ++comp) {
173 Matrix3d strain = Matrix3d::Zero();
174 strain(rows[comp], cols[comp]) = strainStep;
175 const Matrix3d plus = Matrix3d::Identity() + strain;
176 const Matrix3d minus = Matrix3d::Identity() - strain;
177 Matter raised(image);
178 raised.setCell(cell * plus);
179 raised.setPositions(positions * plus);
180 Matter lowered(image);
181 lowered.setCell(cell * minus);
182 lowered.setPositions(positions * minus);
183 const double derivative =
184 (raised.getPotentialEnergy() - lowered.getPotentialEnergy()) /
185 (2.0 * strainStep);
186 sigma(rows[comp], cols[comp]) = derivative / volume;
187 }
188 return sigma;
189}
190
191double solidStateEnthalpy(const Matter &image, const Matter &reference,
192 double pressure) {
193 const double energy = image.getPotentialEnergy();
194 if (pressure == 0.0) {
195 return energy;
196 }
197 const Matrix3d h0 = reference.getCell();
198 const Matrix3d strain = h0.inverse() * (image.getCell() - h0);
199 const double volume = std::abs(h0.determinant());
200 return energy + pressure * strain.trace() * volume;
201}
202
204 const AtomMatrix &atomicForce,
205 const Matrix3d &cellForce,
206 double jacobian) {
207 if (!(jacobian > 0.0)) {
208 throw std::invalid_argument("solid-state step needs a positive Jacobian");
209 }
210 if (atomicForce.rows() != image.numberOfAtoms()) {
211 throw std::invalid_argument("solid-state step force row count mismatch");
212 }
213 const Matrix3d cell = image.getCell();
214 Matrix3d strain = cellForce / jacobian;
215 zeroUpper(strain);
216 CartesianStep step;
217 step.cell = cell * strain;
218 zeroUpper(step.cell);
219 step.positions = atomicForce + image.getPositions() * strain;
220 for (long atom = 0; atom < image.numberOfAtoms(); ++atom) {
221 if (image.getFixed(atom)) {
222 step.positions.row(atom).setZero();
223 }
224 }
225 return step;
226}
227
228} // namespace eonc::neb
Eigen::Matrix< double, 3, 3, eOnStorageOrder > Matrix3d
Definition Eigen.h:35
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
const AtomMatrix & getPositions() const
Definition Matter.cpp:308
void setPositions(const AtomMatrix &pos)
Definition Matter.cpp:350
void setCell(const Matrix3d &newCell)
Definition Matter.cpp:277
Matrix3d getCell() const
Definition Matter.cpp:275
long int numberOfAtoms() const
Definition Matter.cpp:273
double getPotentialEnergy() const
Definition Matter.cpp:554
int getFixed(long int atom) const
1 if every Cartesian axis of the atom is fixed, else 0.
Definition Matter.cpp:505
bool orientCellLowerTriangular(Matrix3d &cell, AtomMatrix &positions)
Rotate the lab frame so the cell is lower triangular: the first lattice vector lies on x and the seco...
void interpolateSolidStateLinear(std::vector< Matter > &images)
Replace interior images by a fractional linear interpolation of the oriented endpoints.
CartesianStep solidStateCartesianStep(const Matter &image, const AtomMatrix &atomicForce, const Matrix3d &cellForce, double jacobian)
double jointNorm(const JointBlock &block)
JointBlock jointDisplacement(const Matter &from, const Matter &to, double jacobian)
Displacement of to relative to from in the joint metric.
double solidStateEnthalpy(const Matter &image, const Matter &reference, double pressure)
Potential energy plus P : (h0^{-1} (h-h0)) * V0.
double solidStateJacobian(double meanVolume, int nAtoms, double weight)
J = (V/N)^{1/3} * N^{1/2} * weight, with V the mean endpoint volume.
void orientSolidStateMatter(Matter &image)
Matrix3d finiteDifferenceCauchyStress(const Matter &image, double strainStep)
Central difference of the potential energy on the six lower strain components.
Matrix3d cellNebForce(const Matrix3d &cauchy, double volume, double jacobian, const Matrix3d &externalStress)
NEB force on the Jacobian-scaled strain.
One steepest step in Cartesian coordinates and the cell matrix.