OpenMD 3.2
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
nep.cpp
1#include "nonbonded/nep.hpp"
2#include "utils/simError.h"
3
4#include <fstream>
5#include <cmath>
6#include <unistd.h>
7
8using namespace std;
9
10namespace OpenMD {
11
12 NEP::NEP(const std::string &model_filename, int N_atoms,
13 std::vector<RealType> cell, std::vector<std::string> atom_symbols,
14 std::vector<RealType> positions, std::vector<RealType> masses)
15 : nep(model_filename), model_filename(model_filename) {
16 /**
17 @brief Wrapper class for NEP3 in nep.cpp.
18 @details This class wraps the setup functionality of the NEP3 class in
19 nep.cpp.
20 @param model_filename Path to the NEP model (/path/nep.txt).
21 @param N_atoms The number of atoms in the structure.
22 @param cell The cell vectors for the structure.
23 @param atom_symbols The atomic symbol for each atom in the structure.
24 @param positions The position for each atom in the structure.
25 @param masses The mass for each atom in the structure.
26 */
27 atoms.N = N_atoms;
28 atoms.position = positions;
29 atoms.mass = masses;
30 atoms.cell = cell;
31 atoms.type.resize(atoms.N);
32
33 std::vector<std::string> model_atom_symbols =
34 _getAtomSymbols(model_filename); // load atom symbols used in model
35 _convertAtomTypeNEPIndex(atoms.N, atom_symbols, model_atom_symbols, atoms.type);
36 }
37
38 std::vector<RealType> NEP::getDescriptors() {
39 /**
40 @brief Get NEP descriptors.
41 @details Calculates NEP descriptors for a given structure and NEP model.
42 */
43 std::vector<RealType> descriptor(atoms.N * nep.annmb.dim);
44 nep.find_descriptor(atoms.type, atoms.cell, atoms.position, descriptor);
45 return descriptor;
46 }
47
48 std::vector<RealType> NEP::getLatentSpace() {
49 /**
50 @brief Get the NEP latent space.
51 @details Calculates the latent space of a NEP model, i.e., the
52 activations after the first layer.
53 */
54 std::vector<RealType> latent(atoms.N * nep.annmb.num_neurons1);
55 nep.find_latent_space(atoms.type, atoms.cell, atoms.position, latent);
56 return latent;
57 }
58
59 std::vector<RealType> NEP::getDipole() {
60 /**
61 @brief Get dipole.
62 @details Calculates the dipole (a vector of length 3) for the whole box.
63 */
64 std::vector<RealType> dipole(3);
65 nep.find_dipole(atoms.type, atoms.cell, atoms.position, dipole);
66 return dipole;
67 }
68
69 void NEP::_getCenterOfMass(std::vector<RealType> center_of_mass) {
70 /**
71 @brief Computes the center of mass for current atoms object.
72 @details Computes the center of mass (COM) for the structure
73 with positions defined by atoms.position. The COM will be
74 written as a three vector, with in the order [x_component,
75 y_component, z_component].
76 @param center_of_mass Vector to hold the center of mass.
77 */
78
79 RealType total_mass = 0.0;
80 for (int i = 0; i < atoms.N; i++) {
81 // positions are in order [x1, ..., xN, y1, ..., yN, z1, ..., zN]
82 center_of_mass[0] += atoms.position[i] * atoms.mass[i];
83 center_of_mass[1] += atoms.position[i + atoms.N] * atoms.mass[i];
84 center_of_mass[2] += atoms.position[i + 2 * atoms.N] * atoms.mass[i];
85 total_mass += atoms.mass[i];
86 }
87 center_of_mass[0] /= total_mass;
88 center_of_mass[1] /= total_mass;
89 center_of_mass[2] /= total_mass;
90 }
91
92 std::vector<RealType> NEP::getDipoleGradient(RealType displacement,
93 int method,
94 RealType charge) {
95 /**
96 @brief Get dipole gradient through finite differences.
97 @details Calculates the dipole gradient, a (N_atoms, 3, 3) tensor
98 for the gradients dµ_k/dr_ij, for atom i, Cartesian direction j (x,
99 y, z) and dipole moment component k. Mode is either forward
100 difference (method=0) or first or second order central difference
101 (method=1 and method=2 respectively). Before computing the
102 gradient the dipoles are corrected using the center of mass and the
103 total system charge, supplied via the parameter `charge`.
104 @param displacement Displacement in Å.
105 @param method 0 or 1, corresponding to forward or central differences.
106 @param charge Total system charge, used to correct dipoles.
107 */
108 const int N_cartesian = 3;
109 const int N_components = 3;
110 const int values_per_atom = N_cartesian * N_components;
111
112 std::vector<RealType> dipole_gradient(atoms.N * 3 * 3);
113
114 // Compute the total mass - need this for the corrections to COM
115 RealType M = 0.0;
116 for (int i = 0; i < atoms.N; i++) {
117 M += atoms.mass[i];
118 }
119
120 if (method == 0) {
121 // For forward differences we save the first dipole with no displacement
122 std::vector<RealType> dipole(3);
123 nep.find_dipole(atoms.type, atoms.cell, atoms.position, dipole);
124
125 // Correct original dipole
126 std::vector<RealType> center_of_mass(3);
127 _getCenterOfMass(center_of_mass);
128 for (int i = 0; i < 3; i++) {
129 dipole[i] += charge * center_of_mass[i];
130 }
131
132 // dipole vectors are zeroed in find_dipole, can be allocated here
133 std::vector<RealType> dipole_forward(3);
134
135 // Positions are in order [x1, ..., xN, y1, ..., yN, ...]
136 int index = 0;
137 RealType old_position = 0.0;
138 RealType old_center_of_mass = 0.0;
139 const RealType displacement_over_M = displacement / M;
140 const RealType one_over_displacement = 1.0 / displacement;
141
142 for (int i = 0; i < N_cartesian; i++) {
143 for (int j = 0; j < atoms.N; j++) {
144 index = atoms.N * i + j; // idx of position to change
145 old_position = atoms.position[index];
146 atoms.position[index] += displacement;
147 // center of mass gest moved by +h/N*m_j in the same direction
148 old_center_of_mass = center_of_mass[i];
149 center_of_mass[i] += displacement_over_M * atoms.mass[j];
150
151 nep.find_dipole(atoms.type, atoms.cell, atoms.position, dipole_forward);
152
153 for (int k = 0; k < N_components; k++) {
154 dipole_gradient[i * N_components + j * values_per_atom + k] =
155 ((dipole_forward[k] + charge * center_of_mass[k]) - dipole[k]) *
156 one_over_displacement;
157 }
158 // Restore positions
159 atoms.position[index] = old_position;
160 center_of_mass[i] = old_center_of_mass;
161 }
162 }
163 } else if (method == 1) {
164 // For central differences we need both forwards and backwards
165 // displacements
166
167 // dipole vectors are zeroed in find_dipole, can be allocated
168 // here
169 std::vector<RealType> dipole_forward(3);
170 std::vector<RealType> dipole_backward(3);
171
172 // use center of mass to correct for permanent dipole
173 std::vector<RealType> center_of_mass_forward(3);
174 _getCenterOfMass(center_of_mass_forward);
175 std::vector<RealType> center_of_mass_backward(center_of_mass_forward);
176
177 // Positions are in order [x1, ..., xN, y1, ..., yN, ...]
178 int index = 0;
179 RealType old_position = 0.0;
180 RealType old_center_of_mass = 0.0;
181 const RealType displacement_over_M = displacement / M;
182 const RealType one_over_two_displacements = 0.5 / displacement;
183
184 for (int i = 0; i < N_cartesian; i++) {
185 for (int j = 0; j < atoms.N; j++) {
186 index = atoms.N * i + j; // idx of position to change
187 old_position = atoms.position[index];
188 old_center_of_mass = center_of_mass_forward[i];
189
190 // Forward displacement
191 atoms.position[index] += displacement;
192 // center of mass gest moved by +h/N in the same direction
193 center_of_mass_forward[i] += displacement_over_M * atoms.mass[j];
194 nep.find_dipole(atoms.type, atoms.cell, atoms.position, dipole_forward);
195
196 // Backwards displacement
197 atoms.position[index] -= 2 * displacement; // +h - 2h = -h
198 center_of_mass_backward[i] -= displacement_over_M * atoms.mass[j];
199 nep.find_dipole(atoms.type, atoms.cell, atoms.position, dipole_backward);
200
201 for (int k = 0; k < N_components; k++) {
202 dipole_gradient[i * N_components + j * values_per_atom + k] =
203 ((dipole_forward[k] + charge * center_of_mass_forward[k]) -
204 (dipole_backward[k] + charge * center_of_mass_backward[k])) *
205 one_over_two_displacements;
206 }
207 // Restore positions
208 atoms.position[index] = old_position;
209 center_of_mass_forward[i] = old_center_of_mass;
210 center_of_mass_backward[i] = old_center_of_mass;
211 }
212 }
213 } else if (method == 2) {
214 // Second order central differences
215 // Need to compute four dipoles for each structure, yielding an error O(h^4)
216 // Coefficients are defined here:
217 // https://en.wikipedia.org/wiki/Finite_difference_coefficient#Central_finite_difference
218
219 // dipole vectors are zeroed in find_dipole, can be allocated here
220 std::vector<RealType> dipole_forward_one_h(3);
221 std::vector<RealType> dipole_forward_two_h(3);
222 std::vector<RealType> dipole_backward_one_h(3);
223 std::vector<RealType> dipole_backward_two_h(3);
224
225 // use center of mass to correct for permanent dipole
226 std::vector<RealType> center_of_mass_forward_one_h(3);
227 _getCenterOfMass(center_of_mass_forward_one_h);
228 std::vector<RealType> center_of_mass_forward_two_h(center_of_mass_forward_one_h);
229 std::vector<RealType> center_of_mass_backward_one_h(center_of_mass_forward_one_h);
230 std::vector<RealType> center_of_mass_backward_two_h(center_of_mass_forward_one_h);
231
232 // Positions are in order [x1, ..., xN, y1, ..., yN, ...]
233 int index = 0;
234 RealType old_position = 0.0;
235 RealType old_center_of_mass = 0.0;
236 const RealType displacement_over_M = displacement / M;
237 const RealType one_over_displacement =
238 1.0 / displacement; // coefficients are scaled properly
239
240 const RealType c0 = -1.0 / 12.0; // coefficient for 2h
241 const RealType c1 = 2.0 / 3.0; // coefficient for h
242 for (int i = 0; i < N_cartesian; i++) {
243 for (int j = 0; j < atoms.N; j++) {
244 index = atoms.N * i + j; // idx of position to change
245 old_position = atoms.position[index];
246 old_center_of_mass = center_of_mass_forward_one_h[i];
247
248 // --- Forward displacement
249 // Step one displacement forward
250 atoms.position[index] += displacement; // + h
251 center_of_mass_forward_one_h[i] +=
252 displacement_over_M * atoms.mass[j]; // center of mass gets moved by
253 // +h/N in the same direction
254 nep.find_dipole(atoms.type, atoms.cell, atoms.position,
255 dipole_forward_one_h);
256 // Step two displacements forward
257 atoms.position[index] += displacement; // + 2h total
258 center_of_mass_forward_two_h[i] +=
259 2 * displacement_over_M *
260 atoms.mass[j]; // center of mass gest moved by
261 // +2h/N in the same direction
262 nep.find_dipole(atoms.type, atoms.cell, atoms.position,
263 dipole_forward_two_h);
264
265 // --- Backwards displacement
266 atoms.position[index] -= 3 * displacement; // 2h - 3h = -h
267 center_of_mass_backward_one_h[i] -= displacement_over_M * atoms.mass[j];
268 nep.find_dipole(atoms.type, atoms.cell, atoms.position,
269 dipole_backward_one_h);
270
271 atoms.position[index] -= displacement; // -h - h = -2h
272 center_of_mass_backward_two_h[i] -=
273 2 * displacement_over_M * atoms.mass[j];
274 nep.find_dipole(atoms.type, atoms.cell, atoms.position,
275 dipole_backward_two_h);
276
277 for (int k = 0; k < N_components; k++) {
278 dipole_gradient[i * N_components + j * values_per_atom + k] =
279 (c0 * (dipole_forward_two_h[k] +
280 charge * center_of_mass_forward_two_h[k]) +
281 c1 * (dipole_forward_one_h[k] +
282 charge * center_of_mass_forward_one_h[k]) -
283 c1 * (dipole_backward_one_h[k] +
284 charge * center_of_mass_backward_one_h[k]) -
285 c0 * (dipole_backward_two_h[k] +
286 charge * center_of_mass_backward_two_h[k])) *
287 one_over_displacement;
288 }
289 // Restore positions
290 atoms.position[index] = old_position;
291 center_of_mass_forward_one_h[i] = old_center_of_mass;
292 center_of_mass_forward_two_h[i] = old_center_of_mass;
293 center_of_mass_backward_one_h[i] = old_center_of_mass;
294 center_of_mass_backward_two_h[i] = old_center_of_mass;
295 }
296 }
297 }
298 // dipole gradient component d_x refers to cartesian direction x
299 // x1 refers to x position of atom 1
300 // order: [dx_x1, dy_x1, dz_x1,
301 // dx_y1, dy_y1, dz_y1,
302 // dx_z1, dy_z1, dz_z1,
303 // ...
304 // dx_zN, dy_zN, dz_zN]
305 return dipole_gradient;
306 }
307
308 std::vector<RealType> NEP::getPolarizability() {
309
310 /**
311 @brief Get polarizability.
312 @details Calculates the polarizability (a 2-tensor, represented as vector of
313 length 9) for the whole box.
314 Output order is pxx pxy pxz pyx pyy pyz pzx pzy pzz.
315 */
316 std::vector<RealType> polarizability(6);
317 nep.find_polarizability(atoms.type, atoms.cell, atoms.position, polarizability);
318 return polarizability;
319}
320
321 std::vector<RealType> NEP::getPolarizabilityGradient(RealType displacement, std::vector<int> components) {
322 /**
323 @brief Get polarizability gradient through finite differences.
324 @details Calculates the polarizability gradient, a (N_atoms, 3, 6) tensor for the
325 gradients dp_k/dr_ij, for atom i, Cartesian direction j (x, y, z) and polarizability
326 component k, using second order finite differences.
327 @param displacement Displacement in Å.
328 */
329 const int N_cartesian = 3;
330 const int N_components = 6;
331 std::vector<RealType> polarizability_gradient(atoms.N * N_cartesian * N_components);
332
333 // Second order central differences
334 // Need to compute four dipoles for each structure, yielding an error O(h^4)
335 // Coefficients are defined here:
336 // https://en.wikipedia.org/wiki/Finite_difference_coefficient#Central_finite_difference
337
338 // Compute polarizability for forward displacement
339 const int values_per_atom = N_cartesian * N_components;
340
341 // polarizability vectors are zeroed in find_polarizability, can be allocated here
342 std::vector<RealType> polarizability_forward_one_h(6);
343 std::vector<RealType> polarizability_forward_two_h(6);
344 std::vector<RealType> polarizability_backward_one_h(6);
345 std::vector<RealType> polarizability_backward_two_h(6);
346
347 // Positions are in order [x1, ..., xN, y1, ..., yN, ...]
348 int index = 0;
349 RealType old_position = 0.0;
350 const RealType one_over_displacement =
351 1.0 / displacement; // coefficients are scaled properly
352
353 const RealType c0 = -1.0 / 12.0; // coefficient for 2h
354 const RealType c1 = 2.0 / 3.0; // coefficient for h
355 for (int i = 0; i < N_cartesian; i++) {
356 if (std::count(components.begin(), components.end(), i) == 0) {
357 // if i is not present in components to calculate, skip this iteration
358 continue;
359 }
360 for (int j = 0; j < atoms.N; j++) {
361 // Positions have order x1, ..., xN, y1,...,yN, z1,...,zN
362 index = atoms.N * i + j; // idx of position to change
363 old_position = atoms.position[index];
364
365 // --- Forward displacement
366 // Step one displacement forward
367 atoms.position[index] += displacement; // + h
368 nep.find_polarizability(atoms.type, atoms.cell, atoms.position,
369 polarizability_forward_one_h);
370 // Step two displacements forward
371 atoms.position[index] += displacement; // + 2h total
372 nep.find_polarizability(atoms.type, atoms.cell, atoms.position,
373 polarizability_forward_two_h);
374
375 // --- Backwards displacement
376 atoms.position[index] -= 3 * displacement; // 2h - 3h = -h
377 nep.find_polarizability(atoms.type, atoms.cell, atoms.position,
378 polarizability_backward_one_h);
379
380 atoms.position[index] -= displacement; // -h - h = -2h
381 nep.find_polarizability(atoms.type, atoms.cell, atoms.position,
382 polarizability_backward_two_h);
383
384 // --- Compute gradient ---
385 for (int k = 0; k < N_components; k++) {
386 polarizability_gradient[i * N_components + j * values_per_atom + k] =
387 (c0 * polarizability_forward_two_h[k] +
388 c1 * polarizability_forward_one_h[k] -
389 c1 * polarizability_backward_one_h[k] -
390 c0 * polarizability_backward_two_h[k]) *
391 one_over_displacement;
392 }
393 // Restore positions
394 atoms.position[index] = old_position;
395 }
396 }
397 // polarizability gradient component p_x refers to cartesian direction x
398 // x1 refers to x position of atom 1
399 // order: [pxx_x1, pyy_x1, pzz_x1, pxy_x1, pyz_x1, pzx_x1
400 // pxx_y2, pyy_y1, pzz_y1, pxy_y1, pyz_y1, pzx_y1
401 // pxx_z2, pyy_z1, pzz_z1, pxy_z1, pyz_z1, pzx_z1
402 // ...
403 // pxx_zN, pyy_zN, pzz_zN, pxy_zN, pyz_zN, pzx_zN]
404 return polarizability_gradient;
405 }
406
407 std::tuple<std::vector<RealType>, std::vector<RealType>, std::vector<RealType>>
409 /**
410 @brief Calculate potential, forces and virials.
411 @details Calculates potential energy, forces and virials for a given
412 structure and NEP model.
413 */
414 std::vector<RealType> potential(atoms.N);
415 std::vector<RealType> force(atoms.N * 3);
416 std::vector<RealType> virial(atoms.N * 9);
417 nep.compute(atoms.type, atoms.cell, atoms.position, potential, force, virial);
418 return std::make_tuple(potential, force, virial);
419 }
420
421 std::vector<std::string> NEP::_getAtomSymbols(std::string model_filename) {
422 /**
423 @brief Fetches atomic symbols
424 @details This function fetches the atomic symbols from the header of a NEP
425 model. These are later used to ensure consistent indices for the atom types.
426 @param model_filename Path to the NEP model (/path/nep.txt).
427 */
428 std::ifstream input_potential(model_filename);
429 if (!input_potential.is_open()) {
430 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
431 "NEP: Cannot open input_potential file: %s\n",
432 model_filename.c_str());
433 painCave.isFatal = 1;
434 simError();
435 }
436 std::string potential_name;
437 input_potential >> potential_name;
438 int number_of_types;
439 input_potential >> number_of_types;
440 std::vector<std::string> atom_symbols(number_of_types);
441 for (int n = 0; n < number_of_types; ++n) {
442 input_potential >> atom_symbols[n];
443 }
444 input_potential.close();
445 return atom_symbols;
446 }
447
449 std::vector<std::string> atom_symbols,
450 std::vector<std::string> model_atom_symbols,
451 std::vector<int> &type) {
452 /**
453 @brief Converts atom species to NEP index.
454 @details Converts atomic species to indicies, which are used in NEP.
455 @param atom_symbols List of atom symbols.
456 @param model_atom_symbols List of atom symbols used in the model.
457 @param type List of indices corresponding to atom type.
458 */
459 for (int n = 0; n < N; n++) {
460 // Convert atom type to index for consistency with nep.txt
461 // (model_filename)
462 std::string atom_symbol = atom_symbols[n];
463 bool is_allowed_element = false;
464 for (int t = 0; (unsigned)t < model_atom_symbols.size(); ++t) {
465 if (atom_symbol == model_atom_symbols[t]) {
466 type[n] = t;
467 is_allowed_element = true;
468 }
469 }
470 if (!is_allowed_element) {
471 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
472 "NEP: Atom type: %s inot used in the given NEP model.\n",
473 atom_symbols[n].c_str());
474 painCave.isFatal = 1;
475 simError();
476 }
477 }
478 }
479
480 void NEP::setPositions(std::vector<RealType> positions) {
481 /**
482 @brief Sets positions.
483 @details Sets the positions of the atoms object.
484 Also updates the center of mass.
485 @param positions List of positions.
486 */
487 for (int i = 0; i < atoms.N * 3; i++) {
488 atoms.position[i] = positions[i];
489 }
490 }
491
492 void NEP::setCell(std::vector<RealType> cell) {
493 /**
494 @brief Sets cell.
495 @details Sets the cell of the atoms object.
496 @param Cell Cell vectors.
497 */
498 for (int i = 0; i < 9; i++) {
499 atoms.cell[i] = cell[i];
500 }
501 }
502
503 void NEP::setMasses(std::vector<RealType> masses) {
504 /**
505 @brief Sets masses.
506 @details Sets the masses of the atoms object.
507 @param Cell Atom masses.
508 */
509 for (int i = 0; i < atoms.N; i++) {
510 atoms.mass[i] = masses[i];
511 }
512 }
513
514 void NEP::setSymbols(std::vector<std::string> atom_symbols) {
515 /**
516 @brief Sets symbols.
517 @details Sets the symbols of the atoms object from the ones used in the
518 model.
519 @param atom_symbols List of symbols.
520 */
521 std::vector<std::string> model_atom_symbols =
522 _getAtomSymbols(model_filename); // load atom symbols used in model
523 _convertAtomTypeNEPIndex(atoms.N, atom_symbols, model_atom_symbols, atoms.type);
524 }
525}
void setCell(std::vector< RealType > cell)
Definition nep.cpp:492
void setMasses(std::vector< RealType > masses)
Definition nep.cpp:503
void setSymbols(std::vector< std::string > atom_symbols)
Definition nep.cpp:514
std::vector< RealType > getPolarizability()
Definition nep.cpp:308
NEP(const std::string &model_filename, int N_atoms, std::vector< RealType > box, std::vector< std::string > atom_symbols, std::vector< RealType > positions, std::vector< RealType > masses)
Definition nep.cpp:12
void setPositions(std::vector< RealType > positions)
Definition nep.cpp:480
void _getCenterOfMass(std::vector< RealType > center_of_mass)
Definition nep.cpp:69
std::vector< RealType > getDipoleGradient(RealType displacement, int method, RealType charge)
Definition nep.cpp:92
std::vector< RealType > getLatentSpace()
Definition nep.cpp:48
void _convertAtomTypeNEPIndex(int N, std::vector< std::string > atom_symbols, std::vector< std::string > model_atom_symbols, std::vector< int > &type)
Definition nep.cpp:448
std::vector< RealType > getPolarizabilityGradient(RealType displacement, std::vector< int > components)
Definition nep.cpp:321
std::vector< RealType > getDipole()
Definition nep.cpp:59
std::vector< RealType > getDescriptors()
Definition nep.cpp:38
std::vector< std::string > _getAtomSymbols(std::string model_filename)
Definition nep.cpp:421
std::tuple< std::vector< RealType >, std::vector< RealType >, std::vector< RealType > > getPotentialForcesAndVirials()
Definition nep.cpp:408
This basic Periodic Table class was originally taken from the data.cpp file in OpenBabel.