Loading...
Searching...
No Matches
EAM.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*/
14
15#include <algorithm>
16#include <array>
17#include <cassert>
18#include <cfloat>
19#include <climits>
20#include <cmath>
21#include <cstring>
22#include <vector>
23
25 // RAII: vectors clean themselves. Nothing to do.
26}
27
28void EAM::force(long N, const double *R, const int *atomicNrs, double *F,
29 double *U, double *variance, const double *fullbox) {
30 variance = nullptr;
31 std::array<double, 3> box = {fullbox[0], fullbox[4], fullbox[8]};
32
33 std::array<long, 3> num_axis;
34 std::array<long, 3> cell_length;
35
36 for (long i = 0; i < 3; i++) {
37 num_axis[i] = static_cast<long>(box[i] / rc_[i]) + 1;
38 }
39 for (long i = 0; i < 3; i++) {
40 cell_length[i] = static_cast<long>(box[i] / (num_axis[i] - 1));
41 }
42
43 long num_cells = num_axis[0] * num_axis[1] * num_axis[2];
44
45 if (!initialized_) {
46 celllist_old_.assign(num_cells * (N + 1), 0);
47 celllist_new_.assign(num_cells * (N + 1), 0);
48 neigh_list_.assign(N * (N + 1), 0);
49 }
50
51 *U = 0;
52 for (long k = 0; k < 3 * N; k++) {
53 F[k] = 0.0;
54 }
55
56 // Find minimum coordinates for shifting all positions to positive
57 double xmin = std::numeric_limits<double>::max();
58 double ymin = xmin, zmin = xmin;
59
60 std::vector<double> Rtemp(3 * N);
61 std::vector<double> Rnew(3 * N);
62
63 for (long i = 0; i < 3 * N; i += 3) {
64 Rtemp[i] = R[i];
65 Rtemp[i + 1] = R[i + 1];
66 Rtemp[i + 2] = R[i + 2];
67 xmin = std::min(xmin, R[i]);
68 ymin = std::min(ymin, R[i + 1]);
69 zmin = std::min(zmin, R[i + 2]);
70 }
71 // Only shift if coordinates are negative
72 if (xmin > 0)
73 xmin = 0;
74 if (ymin > 0)
75 ymin = 0;
76 if (zmin > 0)
77 zmin = 0;
78
79 for (long i = 0; i < 3 * N; i += 3) {
80 Rtemp[i] += std::abs(xmin);
81 Rtemp[i + 1] += std::abs(ymin);
82 Rtemp[i + 2] += std::abs(zmin);
83 }
84
85 // Enforce periodic boundary conditions
86 for (long i = 0; i < 3 * N; i++) {
87 while (Rtemp[i] > box[i % 3]) {
88 Rtemp[i] -= box[i % 3];
89 }
90 }
91
92 std::copy(Rtemp.begin(), Rtemp.end(), Rnew.begin());
93
94 if (!initialized_) {
95 new_celllist(N, box.data(), num_axis.data(), cell_length.data(),
96 celllist_new_.data(), num_cells, Rnew.data());
97 cell_to_neighbor(N, num_cells, num_axis.data(), cell_length.data(),
98 celllist_new_.data(), neigh_list_.data());
99 } else {
100 if (update_cell_list(N, num_cells, num_axis.data(), cell_length.data(),
101 celllist_old_.data(), Rnew.data()) > 0) {
102 new_celllist(N, box.data(), num_axis.data(), cell_length.data(),
103 celllist_new_.data(), num_cells, Rnew.data());
104 cell_to_neighbor(N, num_cells, num_axis.data(), cell_length.data(),
105 celllist_new_.data(), neigh_list_.data());
106 }
107 }
108
109 calc_force(N, Rnew.data(), atomicNrs, F, U, box.data());
110
111 std::copy(celllist_new_.begin(), celllist_new_.end(), celllist_old_.begin());
112
113 initialized_ = true;
114}
115
117 for (int i = 0; i < NPARAMS; i++) {
118 if (el_params[i].Z == atomic_number) {
119 return el_params[i];
120 }
121 }
122 throw 14324; // Element not found
123}
124
125void EAM::calc_force(long N, double *R, const int *atomicNrs, double *F,
126 double *U, const double *box) {
127 std::vector<double> drho_dr(3 * N);
128 for (long k = 0; k < 3 * N; k++) {
129 F[k] = 0.0;
130 }
131 *U = 0;
132
133 // Pre-compute half-box for PBC
134 const double halfBox0 = box[0] * 0.5;
135 const double halfBox1 = box[1] * 0.5;
136 const double halfBox2 = box[2] * 0.5;
137
138 for (long i = 0; i < N; i++) {
139 std::fill(drho_dr.begin(), drho_dr.end(), 0.0);
140 element_parameters epar = get_element_parameters(atomicNrs[i]);
141 const double cutoff2 = epar.r_cut * epar.r_cut;
142
143 double dens = 0.0;
144 const double xi = R[3 * i];
145 const double yi = R[3 * i + 1];
146 const double zi = R[3 * i + 2];
147
148 for (long j = 0; j < N; j++) {
149 if (i == j)
150 continue;
151
152 double dx = xi - R[3 * j];
153 double dy = yi - R[3 * j + 1];
154 double dz = zi - R[3 * j + 2];
155
156 // Minimum image convention
157 if (dx > halfBox0)
158 dx -= box[0];
159 else if (dx < -halfBox0)
160 dx += box[0];
161 if (dy > halfBox1)
162 dy -= box[1];
163 else if (dy < -halfBox1)
164 dy += box[1];
165 if (dz > halfBox2)
166 dz -= box[2];
167 else if (dz < -halfBox2)
168 dz += box[2];
169
170 double r2 = dx * dx + dy * dy + dz * dz;
171 // r^2 cutoff avoids sqrt for far pairs
172 if (r2 > cutoff2)
173 continue;
174
175 double r = std::sqrt(r2);
176
177 // r^6 = (r^2)^3 instead of pow(r, 6)
178 double r6 = r2 * r2 * r2;
179 double exp_b1 = std::exp(-epar.beta1 * r);
180 double exp_b2 = std::exp(-2.0 * epar.beta2 * r);
181 double rho_pair = exp_b1 + 512.0 * exp_b2;
182
183 dens += r6 * rho_pair;
184
185 // r^5 = r^4 * r = (r^2)^2 * r instead of pow(r, 5)
186 double r5 = r2 * r2 * r;
187 double mag_force_den =
188 6.0 * r5 * rho_pair +
189 r6 * (-epar.beta1 * exp_b1 - 1024.0 * epar.beta2 * exp_b2);
190
191 double invR = 1.0 / r;
192 double fscale = -mag_force_den * invR;
193 drho_dr[3 * i] += fscale * dx;
194 drho_dr[3 * i + 1] += fscale * dy;
195 drho_dr[3 * i + 2] += fscale * dz;
196 drho_dr[3 * j] -= fscale * dx;
197 drho_dr[3 * j + 1] -= fscale * dy;
198 drho_dr[3 * j + 2] -= fscale * dz;
199
200 if (j > i) {
201 // Morse pair portion: simplify exp chain
202 double expArg = std::exp(-epar.alphaM * (r - epar.Rm));
203 double d = 1.0 - expArg;
204 double phi_r = epar.Dm * d * d - epar.Dm;
205 // Force: 2*Dm*alpha*d*(d-1) =
206 // 2*Dm*alpha*exp(-a*(r-re))*(1-exp(-a*(r-re)))
207 double mag_force = 2.0 * epar.alphaM * epar.Dm * d * (d - 1.0);
208
209 double fcomp_scale = mag_force * invR;
210 F[3 * i] -= fcomp_scale * dx;
211 F[3 * i + 1] -= fcomp_scale * dy;
212 F[3 * i + 2] -= fcomp_scale * dz;
213 F[3 * j] += fcomp_scale * dx;
214 F[3 * j + 1] += fcomp_scale * dy;
215 F[3 * j + 2] += fcomp_scale * dz;
216 *U += phi_r;
217 }
218 }
219
220 *U += embedding_function(epar.func_coeff, dens);
221 double dF_drho = embedding_force(epar.func_coeff, dens);
222 for (long k = 0; k < 3 * N; k++) {
223 F[k] -= dF_drho * drho_dr[k];
224 }
225 }
226}
227
228void EAM::new_celllist(long N, const double * /*box*/, long *num_axis,
229 long *cell_length, long *celllist_new, long num_cells,
230 double *Rnew) {
231 std::vector<long> cell_list(num_cells * (N + 1), -1);
232 // Last slot of each cell holds atom count
233 for (long i = 0; i < num_cells; i++) {
234 cell_list[i * (N + 1) + N] = 0;
235 }
236
237 for (long i = 0; i < N; i++) {
238 long cx = static_cast<long>(Rnew[3 * i] / cell_length[0]);
239 long cy = static_cast<long>(Rnew[3 * i + 1] / cell_length[1]);
240 long cz = static_cast<long>(Rnew[3 * i + 2] / cell_length[2]);
241 long cell = cx * num_axis[1] * num_axis[2] + cy * num_axis[2] + cz;
242
243 cell_list[cell * (N + 1) + cell_list[cell * (N + 1) + N]] = i;
244 cell_list[cell * (N + 1) + N]++;
245 }
246
247 std::copy(cell_list.begin(), cell_list.end(), celllist_new);
248}
249
250void EAM::cell_to_neighbor(long N, long /*num_of_cells*/, long *num_axis,
251 long * /*cell_length*/, long *celllist_new,
252 long *neigh_list) {
253 long num_cells = num_axis[0] * num_axis[1] * num_axis[2];
254
255 for (long i = 0; i < N * (N + 1); i++) {
256 neigh_list[i] = -1;
257 }
258
259 std::vector<long> neighbors(N + 1);
260 std::vector<long> cell_list_copy(num_cells * (N + 1));
261
262 for (long j = 0; j < num_axis[0]; j++) {
263 for (long j1 = 0; j1 < num_axis[1]; j1++) {
264 for (long j2 = 0; j2 < num_axis[2]; j2++) {
265 std::fill(neighbors.begin(), neighbors.end(), -1);
266
267 long cur_index = j * num_axis[1] * num_axis[2] + j1 * num_axis[2] + j2;
268
269 std::copy(celllist_new, celllist_new + num_cells * (N + 1),
270 cell_list_copy.begin());
271
272 if (cell_list_copy[cur_index * (N + 1) + N] == 0) {
273 continue;
274 }
275
276 for (long d1 = -1; d1 < 2; d1++) {
277 for (long d2 = -1; d2 < 2; d2++) {
278 for (long d3 = -1; d3 < 2; d3++) {
279 std::array<long, 3> pos = {j - d1, j1 - d2, j2 - d3};
280 // Periodic wrapping
281 for (long y = 0; y < 3; y++) {
282 if (pos[y] < 0)
283 pos[y] = num_axis[y] - 1;
284 else if (pos[y] >= num_axis[y])
285 pos[y] = 0;
286 }
287 long neigh_index = pos[0] * num_axis[1] * num_axis[2] +
288 pos[1] * num_axis[2] + pos[2];
289
290 long num = cell_list_copy[neigh_index * (N + 1) + N];
291 for (long y = 0; y < num; y++) {
292 long candidate = cell_list_copy[neigh_index * (N + 1) + y];
293 // Check not already in neighbors
294 bool already = false;
295 for (long k = 0; k <= neighbors[N]; k++) {
296 if (neighbors[k] == candidate) {
297 already = true;
298 break;
299 }
300 }
301 if (!already) {
302 neighbors[N]++;
303 neighbors[neighbors[N]] = candidate;
304 }
305 }
306 }
307 }
308 }
309
310 for (long i = 0; i < celllist_new[cur_index * (N + 1) + N]; i++) {
311 long cur = celllist_new[cur_index * (N + 1) + i];
312 neigh_list[cur * (N + 1) + N] = 0;
313 for (long temp = 0; temp <= neighbors[N]; temp++) {
314 if (cur != neighbors[temp]) {
315 neigh_list[cur * (N + 1) + neigh_list[cur * (N + 1) + N]] =
316 neighbors[temp];
317 neigh_list[cur * (N + 1) + N]++;
318 }
319 }
320 }
321 }
322 }
323 }
324}
325
326int EAM::update_cell_list(long N, long num_cells, long *num_axis,
327 long *cell_length, long *celllist_old, double *Rnew) {
328 int changed = 0;
329 std::vector<long> table(N);
330
331 for (long i = 0; i < num_cells; i++) {
332 for (long j = 0; j < celllist_old[i * (N + 1) + N]; j++) {
333 table[celllist_old[i * (N + 1) + j]] = i;
334 }
335 }
336
337 for (long i = 0; i < N; i++) {
338 long cx = static_cast<long>(Rnew[3 * i] / cell_length[0]);
339 long cy = static_cast<long>(Rnew[3 * i + 1] / cell_length[1]);
340 long cz = static_cast<long>(Rnew[3 * i + 2] / cell_length[2]);
341 long cell = cx * num_axis[1] * num_axis[2] + cy * num_axis[2] + cz;
342 if (cell != table[i]) {
343 changed++;
344 }
345 }
346 return changed;
347}
348
349double EAM::embedding_function(const double *func_coeff, double rho) {
350 // Horner's method for 8th order polynomial
351 double result = func_coeff[8];
352 for (int i = 7; i >= 0; i--) {
353 result = result * rho + func_coeff[i];
354 }
355 return result;
356}
357
358double EAM::embedding_force(const double *func_coeff, double rho) {
359 // Derivative of embedding function via Horner's method
360 double result = func_coeff[8] * 8;
361 for (int i = 7; i >= 1; i--) {
362 result = result * rho + i * func_coeff[i];
363 }
364 return -result;
365}
int update_cell_list(long N, long num_cells, long *num_axis, long *cell_length, long *celllist_old, double *Rnew)
Definition EAM.cpp:326
std::vector< long > neigh_list_
Definition EAM.h:54
void cell_to_neighbor(long N, long num_of_cells, long *num_axis, long *cell_length, long *celllist_new, long *neigh_list)
Definition EAM.cpp:250
void calc_force(long N, double *R, const int *atomicNrs, double *F, double *U, const double *box)
Definition EAM.cpp:125
element_parameters get_element_parameters(int atomic_number)
Definition EAM.cpp:116
std::vector< long > celllist_old_
Definition EAM.h:52
void new_celllist(long N, const double *box, long *num_axis, long *cell_length, long *celllist_new, long num_cells, double *Rnew)
Definition EAM.cpp:228
bool initialized_
Definition EAM.h:55
std::array< double, 3 > rc_
Definition EAM.h:56
static double embedding_force(const double *func_coeff, double rho)
Definition EAM.cpp:358
void cleanMemory()
Definition EAM.cpp:24
void force(long N, const double *R, const int *atomicNrs, double *F, double *U, double *variance, const double *fullbox) override
Definition EAM.cpp:28
static const element_parameters el_params[]
Definition EAM.h:15
static double embedding_function(const double *func_coeff, double rho)
Definition EAM.cpp:349
std::vector< long > celllist_new_
Definition EAM.h:53
#define NPARAMS
Definition Parameters.h:13
const double beta1
Definition EAM.h:45
const double r_cut
Definition EAM.h:47
const double Dm
Definition EAM.h:42
const double alphaM
Definition EAM.h:43
const double beta2
Definition EAM.h:46
const double Rm
Definition EAM.h:44
const double func_coeff[9]
Definition EAM.h:48