Loading...
Searching...
No Matches
eonc::neb Namespace Reference

Namespaces

namespace  zoom

Classes

struct  CartesianStep
 One steepest step in Cartesian coordinates and the cell matrix. More...
struct  DNEB_Projection
 Trygubenko & Wales, JCP 120:2082, 2004. More...
struct  ExtremaResult
struct  ImageForceData
 Data for a single image needed by projection strategies. More...
struct  ImprovedTangent
 Henkelman & Jonsson, JCP 113:9978, 2000. More...
struct  JointBlock
struct  NEB_Projection
 Jonsson, Mills, Jacobsen 1998 (World Scientific). More...
class  OCINEBController
 Goswami (in prep). More...
struct  OnsagerMachlupSpring
 Onsager-Machlup action-based springs (Mandelli & Parrinello 2021). More...
struct  PlainEB
 Mills, Jonsson, Schenter, Surf. More...
struct  SimpleTangent
 Mills, Jonsson, Schenter, Surf. More...
struct  SpringResult
 Result of spring force computation for a single image. More...
struct  UniformSpring
 Uniform spring constant for all images. More...
struct  WeightedSpring
 Energy-weighted spring constants (variable per segment). More...

Typedefs

using ProjectionStrategy
using SpringStrategy
using TangentStrategy = std::variant<SimpleTangent, ImprovedTangent>

Functions

AtomMatrix computeTangent (const AtomMatrix &posDiffNext, const AtomMatrix &posDiffPrev, double energy, double energyPrev, double energyNext, bool use_old_tangent)
 Compute the tangent vector at image i using the improved tangent scheme.
AtomMatrix forcePerp (const AtomMatrix &force, const AtomMatrix &tangent)
 Compute the perpendicular component of force relative to the tangent.
AtomMatrix climbingImageForce (const AtomMatrix &force, const AtomMatrix &tangent, const AtomMatrix &forceDNEB)
 Compute the climbing image projected force.
AtomMatrix computeDNEB (const AtomMatrix &forceSpring, const AtomMatrix &tangent, const AtomMatrix &forcePerp, bool useSwitching=true)
 Compute the doubly-nudged elastic band perpendicular spring force.
void zeroTranslation (AtomMatrix &projectedForce, int nFreeAtoms, int nAtoms)
 Zero net translational force for fully free systems.
ProjectionStrategy buildProjectionStrategy (const Parameters &params)
 Build the projection strategy from parameters.
AtomMatrix computeDNEBComponent (const AtomMatrix &forceSpring, const AtomMatrix &tangent, const AtomMatrix &forcePerp, bool useSwitching)
 Compute the DNEB force component for a given image.
ExtremaResult findSplineExtrema (const std::vector< std::shared_ptr< Matter > > &path, const std::vector< std::shared_ptr< AtomMatrix > > &tangent, long numImages)
 Find extrema along the MEP using cubic spline interpolation.
AtomMatrix interpolatedPeakMode (const std::vector< std::shared_ptr< Matter > > &path, const std::vector< std::shared_ptr< AtomMatrix > > &tangent, long numImages, double posFraction)
 Unit tangent at a fractional image index.
void printImageData (const std::vector< std::shared_ptr< Matter > > &path, const std::vector< std::shared_ptr< AtomMatrix > > &tangent, const std::vector< std::shared_ptr< EigenmodeStrategy > > &eigenmode_solvers, long numImages, bool estimateEigenvalues, bool writeToFile, size_t idx, eonc::log::Scoped log, double referenceEnergy=std::numeric_limits< double >::quiet_NaN())
 Print NEB image data to log and optionally to file.
std::vector< readcon::ConFrame > pathToConFrames (const std::vector< std::shared_ptr< Matter > > &path, const std::vector< std::shared_ptr< AtomMatrix > > &tangent, const std::vector< std::shared_ptr< EigenmodeStrategy > > &eigenmode_solvers, long numImages, bool estimateEigenvalues, std::optional< size_t > bandIndex=std::nullopt, double referenceEnergy=std::numeric_limits< double >::quiet_NaN())
 Build stamped ConFrames for a NEB band (same metadata as writePathCon).
eonc::io::IoStatus writePathCon (const std::vector< std::shared_ptr< Matter > > &path, const std::vector< std::shared_ptr< AtomMatrix > > &tangent, const std::vector< std::shared_ptr< EigenmodeStrategy > > &eigenmode_solvers, long numImages, bool estimateEigenvalues, std::string filename, std::optional< size_t > bandIndex=std::nullopt, double referenceEnergy=std::numeric_limits< double >::quiet_NaN())
 Write a NEB band as a multi-frame .con via readcon ConFrameBuilder::clone().
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.
TangentStrategy buildTangentStrategy (const Parameters &params)
 Build the tangent strategy from parameters.
double solidStateJacobian (double meanVolume, int nAtoms, double weight)
 J = (V/N)^{1/3} * N^{1/2} * weight, with V the mean endpoint volume.
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 second lies in the xy plane.
void orientSolidStateMatter (Matter &image)
void interpolateSolidStateLinear (std::vector< Matter > &images)
 Replace interior images by a fractional linear interpolation of the oriented endpoints.
JointBlock jointDisplacement (const Matter &from, const Matter &to, double jacobian)
 Displacement of to relative to from in the joint metric.
double jointNorm (const JointBlock &block)
Matrix3d cellNebForce (const Matrix3d &cauchy, double volume, double jacobian, const Matrix3d &externalStress)
 NEB force on the Jacobian-scaled strain.
Matrix3d finiteDifferenceCauchyStress (const Matter &image, double strainStep)
 Central difference of the potential energy on the six lower strain components.
double solidStateEnthalpy (const Matter &image, const Matter &reference, double pressure)
 Potential energy plus P : (h0^{-1} (h-h0)) * V0.
CartesianStep solidStateCartesianStep (const Matter &image, const AtomMatrix &atomicForce, const Matrix3d &cellForce, double jacobian)

Typedef Documentation

◆ ProjectionStrategy

Initial value:
std::variant<PlainEB, NEB_Projection, DNEB_Projection>

Definition at line 50 of file NEBProjection.h.

◆ SpringStrategy

Initial value:
std::variant<UniformSpring, WeightedSpring, OnsagerMachlupSpring>

Definition at line 58 of file NEBSpringForce.h.

◆ TangentStrategy

Definition at line 38 of file NEBTangent.h.

Function Documentation

◆ buildProjectionStrategy()

ProjectionStrategy eonc::neb::buildProjectionStrategy ( const Parameters & params)

Build the projection strategy from parameters.

DNEB is incompatible with OM and weighted springs; falls back to NEB_Projection.

Definition at line 33 of file NEBProjection.cpp.

33 {
34 bool omActive = params.neb_options().spring.om.enabled;
35 bool weightedActive = params.neb_options().spring.weighting.enabled;
36
37 if (params.neb_options().spring.use_elastic_band && !omActive &&
38 !weightedActive) {
39 return PlainEB{};
40 }
41 if (params.neb_options().spring.doubly_nudged && !omActive &&
42 !weightedActive) {
43 return DNEB_Projection{params.neb_options().spring.use_switching};
44 }
45 return NEB_Projection{};
46}
const neb_options_t & neb_options() const
Jonsson, Mills, Jacobsen 1998 (World Scientific).
Mills, Jonsson, Schenter, Surf.
struct eonc::neb_options_t::spring_options_t::energy_weighting_t weighting
struct eonc::neb_options_t::spring_options_t::onsager_machlup_t om
struct eonc::neb_options_t::spring_options_t spring

◆ buildSpringStrategy()

SpringStrategy eonc::neb::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.

Definition at line 71 of file NEBSpringForce.cpp.

73 {
74
75 if (params.neb_options().spring.om.enabled) {
76 double base_k = params.neb_options().spring.constant;
77
78 if (params.neb_options().spring.om.optimize_k) {
79 double avgPotForce = 0.0;
80 double avgPathCurvature = 0.0;
81 int count = 0;
82 for (long j = 1; j <= numImages; j++) {
83 avgPotForce += path[j]->getForces().norm();
84 AtomMatrix next = path[j + 1]->getPositions();
85 AtomMatrix prev = path[j - 1]->getPositions();
86 AtomMatrix curr = path[j]->getPositions();
87 AtomMatrix curvVec = path[j]->pbc(next + prev - 2.0 * curr);
88 avgPathCurvature += curvVec.norm();
89 count++;
90 }
91 if (count > 0 && avgPathCurvature > 1e-6) {
92 double scale = params.neb_options().spring.om.k_scale;
93 base_k = scale * (avgPotForce / avgPathCurvature);
94 base_k = std::max(base_k, params.neb_options().spring.om.k_min);
95 base_k = std::min(base_k, params.neb_options().spring.om.k_max);
96 }
97 }
98
99 // Pre-calculate L vectors for all images
100 std::vector<AtomMatrix> L_vecs(numImages + 2);
101 for (long j = 0; j <= numImages + 1; j++) {
102 L_vecs[j].resize(atoms, 3);
103 if (j == 0 || j == numImages + 1) {
104 L_vecs[j].setZero();
105 } else {
106 const AtomMatrix &forces = path[j]->getForces();
107 double alpha_k = eonc::safemath::safe_recip(2.0 * base_k, 0.0);
108 for (int k = 0; k < atoms; k++) {
109 L_vecs[j].row(k) = alpha_k * forces.row(k);
110 }
111 }
112 }
113
114 return OnsagerMachlupSpring{base_k, std::move(L_vecs)};
115
116 } else if (params.neb_options().spring.weighting.enabled) {
117 double k_l = params.neb_options().spring.weighting.k_min;
118 double k_u = params.neb_options().spring.weighting.k_max;
119 std::vector<double> springConstants(numImages + 2, k_l);
120
121 double energyRange = maxEnergy - E_ref;
122 if (energyRange < 1e-10) {
123 std::fill(springConstants.begin(), springConstants.end(), k_l);
124 } else {
125 for (int idx = 1; idx <= numImages + 1; idx++) {
126 double Ei = std::max(path[idx]->getPotentialEnergy(),
127 path[idx - 1]->getPotentialEnergy());
128 if (Ei > E_ref) {
129 double alpha_i = (maxEnergy - Ei) / energyRange;
130 alpha_i = std::max(0.0, std::min(1.0, alpha_i));
131 springConstants[idx - 1] = (1.0 - alpha_i) * k_u + alpha_i * k_l;
132 } else {
133 springConstants[idx - 1] = k_l;
134 }
135 }
136 }
137
138 return WeightedSpring{std::move(springConstants)};
139
140 } else {
141 return UniformSpring{params.neb_options().spring.constant};
142 }
143}
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
constexpr double safe_recip(double x, double fallback=0.0)
Definition SafeMath.h:29
Onsager-Machlup action-based springs (Mandelli & Parrinello 2021).
Uniform spring constant for all images.
Energy-weighted spring constants (variable per segment).

◆ buildTangentStrategy()

TangentStrategy eonc::neb::buildTangentStrategy ( const Parameters & params)

Build the tangent strategy from parameters.

Definition at line 72 of file NEBTangent.cpp.

72 {
74 return SimpleTangent{};
75 }
76 return ImprovedTangent{};
77}
Mills, Jonsson, Schenter, Surf.
Definition NEBTangent.h:23
struct eonc::neb_options_t::climbing_image_options_t climbing_image

◆ cellNebForce()

Matrix3d eonc::neb::cellNebForce ( const Matrix3d & cauchy,
double volume,
double jacobian,
const Matrix3d & externalStress )

NEB force on the Jacobian-scaled strain.

Cauchy stress uses sigma = (1/V) dE/dε for h <- h (I+ε) at fixed fractional coordinates. External stress is added before the -V factor (positive hydrostatic pressure pushes the cell inward).

Definition at line 146 of file SolidStateNEB.cpp.

147 {
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}
Eigen::Matrix< double, 3, 3, eOnStorageOrder > Matrix3d
Definition Eigen.h:35

◆ climbingImageForce()

AtomMatrix eonc::neb::climbingImageForce ( const AtomMatrix & force,
const AtomMatrix & tangent,
const AtomMatrix & forceDNEB )

Compute the climbing image projected force.

F_CI = F - 2*(F.t)*t + forceDNEB

Definition at line 73 of file NEBForceProjection.cpp.

75 {
76 return force - 2.0 * matDot(force, tangent) * tangent + forceDNEB;
77}
double matDot(const AtomMatrix &a, const AtomMatrix &b)
SIMD-optimized dot product for contiguous Eigen matrices.
Definition Eigen.h:50

◆ computeDNEB()

AtomMatrix eonc::neb::computeDNEB ( const AtomMatrix & forceSpring,
const AtomMatrix & tangent,
const AtomMatrix & forcePerp,
bool useSwitching = true )

Compute the doubly-nudged elastic band perpendicular spring force.

useSwitching multiplies the remainder by (2/pi)*atan(|F_perp|^2 / |F_spring_perp|^2).

Definition at line 79 of file NEBForceProjection.cpp.

80 {
81 AtomMatrix forceSpringPerp =
82 forceSpring - matDot(forceSpring, tangent) * tangent;
83
84 const double forceSpringPerpNorm = forceSpringPerp.norm();
85 const double forcePerpNorm = fPerp.norm();
86
87 if (forceSpringPerpNorm > 1e-10 && forcePerpNorm > 1e-10) {
88 AtomMatrix forcePerpNormalized = fPerp / forcePerpNorm;
89 AtomMatrix dneb =
90 forceSpringPerp -
91 matDot(forceSpringPerp, forcePerpNormalized) * forcePerpNormalized;
92
93 if (useSwitching) {
94 double switching = 2.0 / eonc::helpers::pi *
95 std::atan(forcePerpNorm * forcePerpNorm /
96 (forceSpringPerpNorm * forceSpringPerpNorm));
97 dneb *= switching;
98 }
99 return dneb;
100 }
101
102 return AtomMatrix::Zero(tangent.rows(), tangent.cols());
103}
constexpr double pi

◆ computeDNEBComponent()

AtomMatrix eonc::neb::computeDNEBComponent ( const AtomMatrix & forceSpring,
const AtomMatrix & tangent,
const AtomMatrix & forcePerp,
bool useSwitching )

Compute the DNEB force component for a given image.

Returned separately so it can be added to the CI force when DNEB is active. useSwitching applies the (2/pi)*atan scale. Otherwise the perpendicular spring remainder is unscaled.

Definition at line 48 of file NEBProjection.cpp.

50 {
51 return eonc::neb::computeDNEB(forceSpring, tangent, fPerp, useSwitching);
52}
AtomMatrix computeDNEB(const AtomMatrix &forceSpring, const AtomMatrix &tangent, const AtomMatrix &fPerp, bool useSwitching)
Compute the doubly-nudged elastic band perpendicular spring force.

◆ computeTangent()

AtomMatrix eonc::neb::computeTangent ( const AtomMatrix & posDiffNext,
const AtomMatrix & posDiffPrev,
double energy,
double energyPrev,
double energyNext,
bool use_old_tangent )

Compute the tangent vector at image i using the improved tangent scheme.

Returns a normalized tangent vector.

Definition at line 22 of file NEBForceProjection.cpp.

25 {
26 AtomMatrix tang;
27
28 if (use_old_tangent) {
29 tang = posDiffNext;
30 } else {
31 // Improved tangent scheme
32 if (energyNext > energy && energy > energyPrev) {
33 tang = posDiffNext;
34 } else if (energy > energyNext && energyPrev > energy) {
35 tang = posDiffPrev;
36 } else {
37 // Extremum: energy-weighted combination
38 double energyDiffPrev = energyPrev - energy;
39 double energyDiffNext = energyNext - energy;
40 double minDiffEnergy =
41 std::min(std::abs(energyDiffPrev), std::abs(energyDiffNext));
42 double maxDiffEnergy =
43 std::max(std::abs(energyDiffPrev), std::abs(energyDiffNext));
44
45 if (energyDiffPrev > energyDiffNext) {
46 tang = posDiffNext * minDiffEnergy + posDiffPrev * maxDiffEnergy;
47 } else {
48 tang = posDiffNext * maxDiffEnergy + posDiffPrev * minDiffEnergy;
49 }
50 }
51 }
52
53 // Normalize with safety check
54 double norm = tang.norm();
55 if (norm > 1e-10) {
56 tang /= norm;
57 } else {
58 // Fallback: use direction to next image
59 tang = posDiffNext;
60 norm = tang.norm();
61 if (norm > 1e-10) {
62 tang /= norm;
63 }
64 }
65
66 return tang;
67}

◆ findSplineExtrema()

ExtremaResult eonc::neb::findSplineExtrema ( const std::vector< std::shared_ptr< Matter > > & path,
const std::vector< std::shared_ptr< AtomMatrix > > & tangent,
long numImages )

Find extrema along the MEP using cubic spline interpolation.

Definition at line 111 of file NEBSplineExtrema.cpp.

113 {
114
115 auto *log = eonc::log::get();
116
117 // Calculate cubic parameters for each interval
118 AtomMatrix tangentEndpoint;
119 std::vector<double> a(numImages + 1), b(numImages + 1), c(numImages + 1),
120 d(numImages + 1);
121 double F1, F2, U1, U2, dist;
122
123 for (long i = 0; i <= numImages; i++) {
124 dist = path[i]->distanceTo(*path[i + 1]);
125 if (i == 0) {
126 tangentEndpoint =
127 path[i]->pbc(path[1]->getPositions() - path[0]->getPositions());
128 normalizeOrZero(tangentEndpoint);
129 F1 = matDot(path[i]->getForces(), tangentEndpoint) * dist;
130 } else {
131 F1 = matDot(path[i]->getForces(), *tangent[i]) * dist;
132 }
133 if (i == numImages) {
134 tangentEndpoint = path[i + 1]->pbc(path[numImages + 1]->getPositions() -
135 path[numImages]->getPositions());
136 normalizeOrZero(tangentEndpoint);
137 F2 = matDot(path[i + 1]->getForces(), tangentEndpoint) * dist;
138 } else {
139 F2 = matDot(path[i + 1]->getForces(), *tangent[i + 1]) * dist;
140 }
141 U1 = path[i]->getPotentialEnergy();
142 U2 = path[i + 1]->getPotentialEnergy();
143 a[i] = U1;
144 b[i] = -F1;
145 c[i] = 3. * (U2 - U1) + 2. * F1 + F2;
146 d[i] = -2. * (U2 - U1) - (F1 + F2);
147 }
148
149 ExtremaResult result;
150 result.positions.resize(2 * (numImages + 1));
151 result.energies.resize(2 * (numImages + 1));
152 result.curvatures.resize(2 * (numImages + 1));
153
154 double discriminant, f;
155
156 for (long i = 0; i <= numImages; i++) {
157 discriminant = c[i] * c[i] - 3.0 * b[i] * d[i];
158 if (discriminant >= 0) {
159 f = -1;
160
161 // Quadratic case
162 if ((d[i] == 0) && (c[i] != 0)) {
163 f = (-b[i] / (2. * c[i]));
164 }
165 // Cubic case 1
166 else if (d[i] != 0) {
167 f = -(c[i] + std::sqrt(discriminant)) / (3. * d[i]);
168 }
169 if ((f >= 0) && (f <= 1)) {
170 result.positions[result.numExtrema] = i + f;
171 result.energies[result.numExtrema] =
172 ((d[i] * f + c[i]) * f + b[i]) * f + a[i]; // Horner's method
173 result.curvatures[result.numExtrema] = 6.0 * d[i] * f + 2 * c[i];
174 result.numExtrema++;
175 }
176 // Cubic case 2. A zero cubic coefficient leaves f at the quadratic
177 // root, and a zero discriminant is that same cubic root. Storing
178 // either again doubles one stationary point.
179 if (d[i] != 0 && discriminant != 0.0) {
180 f = -(c[i] - std::sqrt(discriminant)) / (3. * d[i]);
181 } else {
182 f = -1;
183 }
184 if ((f >= 0) && (f <= 1)) {
185 result.positions[result.numExtrema] = i + f;
186 result.energies[result.numExtrema] =
187 ((d[i] * f + c[i]) * f + b[i]) * f + a[i]; // Horner's method
188 result.curvatures[result.numExtrema] = 6 * d[i] * f + 2 * c[i];
189 result.numExtrema++;
190 }
191 }
192 }
193
194 QUILL_LOG_DEBUG(log, "Found {} extrema", result.numExtrema);
195 QUILL_LOG_DEBUG(log, "Energy reference: {}", path[0]->getPotentialEnergy());
196 for (long i = 0; i < result.numExtrema; i++) {
197 QUILL_LOG_DEBUG(
198 log, "extrema #{} at image position {} with energy {} and curvature {}",
199 i + 1, result.positions[i],
200 result.energies[i] - path[0]->getPotentialEnergy(),
201 result.curvatures[i]);
202 }
203
204 return result;
205}
quill::Logger * get() noexcept
Get or create the default "combi" logger.
Definition EonLogger.h:44
std::vector< double > positions

◆ finiteDifferenceCauchyStress()

Matrix3d eonc::neb::finiteDifferenceCauchyStress ( const Matter & image,
double strainStep )

Central difference of the potential energy on the six lower strain components.

Upper-triangle components stay zero.

Definition at line 157 of file SolidStateNEB.cpp.

157 {
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}
const AtomMatrix & getPositions() const
Definition Matter.cpp:308
Matrix3d getCell() const
Definition Matter.cpp:275

◆ forcePerp()

AtomMatrix eonc::neb::forcePerp ( const AtomMatrix & force,
const AtomMatrix & tangent )

Compute the perpendicular component of force relative to the tangent.

Definition at line 69 of file NEBForceProjection.cpp.

69 {
70 return force - matDot(force, tangent) * tangent;
71}

◆ interpolatedPeakMode()

AtomMatrix eonc::neb::interpolatedPeakMode ( const std::vector< std::shared_ptr< Matter > > & path,
const std::vector< std::shared_ptr< AtomMatrix > > & tangent,
long numImages,
double posFraction )
nodiscard

Unit tangent at a fractional image index.

End intervals use the geometric endpoint displacement because stored endpoint tangents stay zero.

Definition at line 208 of file NEBSplineExtrema.cpp.

210 {
211 const int nat = path.empty() ? 0 : path[0]->numberOfAtoms();
212 const auto leftIdx = static_cast<long>(std::floor(posFraction));
213 if (path.size() < 2 || leftIdx < 0 || leftIdx >= numImages + 1 ||
214 leftIdx + 1 >= static_cast<long>(path.size())) {
215 return AtomMatrix::Zero(nat, 3);
216 }
217 const double f = posFraction - static_cast<double>(leftIdx);
218 AtomMatrix mode =
219 (1.0 - f) * storedOrEndpointTangent(path, tangent, numImages, leftIdx) +
220 f * storedOrEndpointTangent(path, tangent, numImages, leftIdx + 1);
221 normalizeOrZero(mode);
222 return mode;
223}

◆ interpolateSolidStateLinear()

void eonc::neb::interpolateSolidStateLinear ( std::vector< Matter > & images)

Replace interior images by a fractional linear interpolation of the oriented endpoints.

images includes the endpoints.

Definition at line 93 of file SolidStateNEB.cpp.

93 {
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}

◆ jointDisplacement()

JointBlock eonc::neb::jointDisplacement ( const Matter & from,
const Matter & to,
double jacobian )

Displacement of to relative to from in the joint metric.

Atomic rows are fractional minimum-image displacements mapped through the average cell. The cell block is the averaged right strain of the Jacobian-scaled cell difference.

Definition at line 119 of file SolidStateNEB.cpp.

120 {
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}

◆ jointNorm()

double eonc::neb::jointNorm ( const JointBlock & block)

Definition at line 142 of file SolidStateNEB.cpp.

142 {
143 return std::hypot(block.atomic.norm(), block.cell.norm());
144}

◆ orientCellLowerTriangular()

bool eonc::neb::orientCellLowerTriangular ( Matrix3d & cell,
AtomMatrix & positions )

Rotate the lab frame so the cell is lower triangular: the first lattice vector lies on x and the second lies in the xy plane.

Fractional coordinates and the sign of the volume are unchanged. Returns false when the cell is singular or two lattice vectors are parallel.

Definition at line 49 of file SolidStateNEB.cpp.

49 {
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}

◆ orientSolidStateMatter()

void eonc::neb::orientSolidStateMatter ( Matter & image)

Definition at line 80 of file SolidStateNEB.cpp.

80 {
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}
void setPositions(const AtomMatrix &pos)
Definition Matter.cpp:350
void setCell(const Matrix3d &newCell)
Definition Matter.cpp:277
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...

◆ pathToConFrames()

std::vector< readcon::ConFrame > eonc::neb::pathToConFrames ( const std::vector< std::shared_ptr< Matter > > & path,
const std::vector< std::shared_ptr< AtomMatrix > > & tangent,
const std::vector< std::shared_ptr< EigenmodeStrategy > > & eigenmode_solvers,
long numImages,
bool estimateEigenvalues,
std::optional< size_t > bandIndex = std::nullopt,
double referenceEnergy = std::numeric_limits< double >::quiet_NaN() )
nodiscard

Build stamped ConFrames for a NEB band (same metadata as writePathCon).

Empty on invalid path size. Does not write to disk. referenceEnergy is the energy relative energies are measured from; NaN means path[0]. A zoomed band passes its original reactant energy, since zoom moves path[0] to the start of its window.

Definition at line 310 of file NEBSplineExtrema.cpp.

315 {
316 const size_t nframes = static_cast<size_t>(numImages) + 2;
317 if (path.size() < nframes) {
318 return {};
319 }
320
321 double distTotal = 0.0;
322 std::vector<eonc::io::ConFrameMetadata> metas;
323 metas.reserve(nframes);
324
325 // The mass-weighted arc length rides beside the Cartesian one: it is the
326 // coordinate a tunnelling integral along the band runs over. A structure
327 // without masses leaves it out rather than failing the write.
328 double distMw = 0.0;
329 bool haveMw = true;
330 for (long i = 0; i <= numImages + 1; i++) {
331 if (i > 0) {
332 distTotal += path[i]->distanceTo(*path[i - 1]);
333 if (haveMw) {
334 try {
335 distMw +=
336 eonc::tunneling::massWeightedDistance(*path[i - 1], *path[i]);
337 } catch (const std::invalid_argument &) {
338 haveMw = false;
339 }
340 }
341 }
342 metas.push_back(neb_frame_metadata(path, tangent, eigenmode_solvers,
343 numImages, estimateEigenvalues, i,
344 distTotal, bandIndex, referenceEnergy));
345 if (haveMw) {
346 metas.back().scalars.push_back({"reaction_coordinate_mw", distMw});
347 }
348 }
349 // The band's one-dimensional tunnelling estimate goes on its first frame:
350 // the well frequencies, the WKB action and the splitting a two-level-system
351 // screen reads. Left out when an image has no mass or an end well is flat.
352 if (haveMw && !metas.empty()) {
353 try {
354 std::vector<std::shared_ptr<Matter>> ends(
355 path.begin(), path.begin() + static_cast<std::ptrdiff_t>(nframes));
356 const auto split = eonc::tunneling::bandSplitting(
357 ends, referenceOrFirst(path, referenceEnergy));
358 auto &head = metas.front().scalars;
359 head.push_back({"hbar_omega_reactant", split.hwReactant});
360 head.push_back({"hbar_omega_product", split.hwProduct});
361 head.push_back({"tunnel_action", split.action});
362 head.push_back({"tunnel_splitting", split.delta0});
363 head.push_back({"tls_energy", split.tlsEnergy()});
364 head.push_back({"tunnel_deep_wells", split.deepWells ? 1.0 : 0.0});
365 } catch (const std::invalid_argument &) {
366 }
367 }
368
369 std::vector<std::shared_ptr<Matter>> band(
370 path.begin(), path.begin() + static_cast<std::ptrdiff_t>(nframes));
371 return eonc::io::buildNebPathFrames(band, metas);
372}
std::vector< readcon::ConFrame > buildNebPathFrames(const std::vector< std::shared_ptr< Matter > > &path, const std::vector< ConFrameMetadata > &metadata_per_image)
Build NEB band ConFrames without writing (clone builder path of writeNebPath).
Splitting bandSplitting(const std::vector< std::shared_ptr< Matter > > &band, double referenceEnergy)
The splitting of a converged band, with the well frequencies from the band's curvature at each end.
double massWeightedDistance(const Matter &a, const Matter &b)
sqrt(sum_i m_i |b_i - a_i|^2) under the minimum image of a's cell.
Definition Tunneling.cpp:34

◆ printImageData()

void eonc::neb::printImageData ( const std::vector< std::shared_ptr< Matter > > & path,
const std::vector< std::shared_ptr< AtomMatrix > > & tangent,
const std::vector< std::shared_ptr< EigenmodeStrategy > > & eigenmode_solvers,
long numImages,
bool estimateEigenvalues,
bool writeToFile,
size_t idx,
eonc::log::Scoped log,
double referenceEnergy )

Print NEB image data to log and optionally to file.

Definition at line 225 of file NEBSplineExtrema.cpp.

230 {
231
232 double dist, distTotal = 0;
233 AtomMatrix tangentStart =
234 path[0]->pbc(path[1]->getPositions() - path[0]->getPositions());
235 AtomMatrix tangentEnd = path[numImages]->pbc(
236 path[numImages + 1]->getPositions() - path[numImages]->getPositions());
237 AtomMatrix tang;
238 std::string header;
239 if (estimateEigenvalues) {
240 header = std::format("{:>3s} {:>12s} {:>12s} {:>12s} {:>12s}", "img",
241 "rxn_coord", "energy", "f_para", "eigval");
242 } else {
243 header = std::format("{:>3s} {:>12s} {:>12s} {:>12s}", "img", "rxn_coord",
244 "energy", "f_para");
245 }
246
247 normalizeOrZero(tangentStart);
248 normalizeOrZero(tangentEnd);
249
250 std::ofstream fileLogger;
251 if (writeToFile) {
252 std::string neb_dat_fs;
253 if (idx == std::numeric_limits<size_t>::max()) {
254 neb_dat_fs = "neb.dat";
255 } else {
256 neb_dat_fs = std::format("neb_{:03}.dat", idx);
257 }
258 if (fs::exists(neb_dat_fs)) {
259 fs::remove(neb_dat_fs);
260 }
261 fileLogger.open(neb_dat_fs);
262 if (fileLogger.is_open()) {
263 fileLogger << header << "\n";
264 }
265 }
266 const double energy_reactant = referenceOrFirst(path, referenceEnergy);
267
268 for (long i = 0; i <= numImages + 1; i++) {
269 if (i == 0) {
270 tang = tangentStart;
271 } else if (i == numImages + 1) {
272 tang = tangentEnd;
273 } else {
274 tang = *tangent[i];
275 }
276
277 if (i > 0) {
278 dist = path[i]->distanceTo(*path[i - 1]);
279 distTotal += dist;
280 }
281
282 double relative_energy = path[i]->getPotentialEnergy() - energy_reactant;
283 double parallel_force = matDot(path[i]->getForces(), tang);
284
285 if (estimateEigenvalues) {
286 eonc::eigenmodeCompute(*eigenmode_solvers[i], path[i], tang);
287 double lowest_eigenvalue =
288 eonc::eigenmodeGetEigenvalue(*eigenmode_solvers[i]);
289 if (fileLogger.is_open()) {
290 fileLogger << std::format(
291 "{:>3} {:>12.6f} {:>12.6f} {:>12.6f} {:>12.6f}\n", i, distTotal,
292 relative_energy, parallel_force, lowest_eigenvalue);
293 } else {
294 QUILL_LOG_DEBUG(log, "{:>3} {:>12.6f} {:>12.6f} {:>12.6f} {:>12.6f}", i,
295 distTotal, relative_energy, parallel_force,
296 lowest_eigenvalue);
297 }
298 } else {
299 if (fileLogger.is_open()) {
300 fileLogger << std::format("{:>3} {:>12.6f} {:>12.6f} {:>12.6f}\n", i,
301 distTotal, relative_energy, parallel_force);
302 } else {
303 QUILL_LOG_DEBUG(log, "{:>3} {:>12.6f} {:>12.6f} {:>12.6f}", i,
304 distTotal, relative_energy, parallel_force);
305 }
306 }
307 }
308}
void eigenmodeCompute(LowestEigenmode &s, std::shared_ptr< Matter > matter, AtomMatrix direction)
double eigenmodeGetEigenvalue(LowestEigenmode &s)

◆ solidStateCartesianStep()

CartesianStep eonc::neb::solidStateCartesianStep ( const Matter & image,
const AtomMatrix & atomicForce,
const Matrix3d & cellForce,
double jacobian )

Definition at line 203 of file SolidStateNEB.cpp.

206 {
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}
long int numberOfAtoms() const
Definition Matter.cpp:273
int getFixed(long int atom) const
1 if every Cartesian axis of the atom is fixed, else 0.
Definition Matter.cpp:505

◆ solidStateEnthalpy()

double eonc::neb::solidStateEnthalpy ( const Matter & image,
const Matter & reference,
double pressure )

Potential energy plus P : (h0^{-1} (h-h0)) * V0.

Pressure is hydrostatic, in eV/Angstrom^3. A zero pressure returns the potential energy.

Definition at line 191 of file SolidStateNEB.cpp.

192 {
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}
double getPotentialEnergy() const
Definition Matter.cpp:554

◆ solidStateJacobian()

double eonc::neb::solidStateJacobian ( double meanVolume,
int nAtoms,
double weight )

J = (V/N)^{1/3} * N^{1/2} * weight, with V the mean endpoint volume.

doi:10.1063/1.3684549

Definition at line 40 of file SolidStateNEB.cpp.

40 {
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}

◆ writePathCon()

eonc::io::IoStatus eonc::neb::writePathCon ( const std::vector< std::shared_ptr< Matter > > & path,
const std::vector< std::shared_ptr< AtomMatrix > > & tangent,
const std::vector< std::shared_ptr< EigenmodeStrategy > > & eigenmode_solvers,
long numImages,
bool estimateEigenvalues,
std::string filename,
std::optional< size_t > bandIndex,
double referenceEnergy )
nodiscard

Write a NEB band as a multi-frame .con via readcon ConFrameBuilder::clone().

Definition at line 374 of file NEBSplineExtrema.cpp.

379 {
380 auto frames =
381 pathToConFrames(path, tangent, eigenmode_solvers, numImages,
382 estimateEigenvalues, bandIndex, referenceEnergy);
383 if (frames.empty()) {
385 }
386 return eonc::io::writeConFrames(std::move(filename), frames);
387}
IoStatus writeConFrames(std::string filename, const std::vector< readcon::ConFrame > &frames)
Write already-built ConFrames to a multi-frame .con (temp or durable).
std::vector< readcon::ConFrame > pathToConFrames(const std::vector< std::shared_ptr< Matter > > &path, const std::vector< std::shared_ptr< AtomMatrix > > &tangent, const std::vector< std::shared_ptr< EigenmodeStrategy > > &eigenmode_solvers, long numImages, bool estimateEigenvalues, std::optional< size_t > bandIndex, double referenceEnergy)
Build stamped ConFrames for a NEB band (same metadata as writePathCon).

◆ zeroTranslation()

void eonc::neb::zeroTranslation ( AtomMatrix & projectedForce,
int nFreeAtoms,
int nAtoms )

Zero net translational force for fully free systems.

Definition at line 105 of file NEBForceProjection.cpp.

105 {
106 if (nFreeAtoms == nAtoms) {
107 for (int j = 0; j <= 2; j++) {
108 double translationMag = projectedForce.col(j).sum();
109 int natoms = projectedForce.col(j).size();
110 projectedForce.col(j).array() -=
111 translationMag / static_cast<double>(natoms);
112 }
113 }
114}