1 // clang-format off
2 /* ----------------------------------------------------------------------
3 LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
4 https://www.lammps.org/, Sandia National Laboratdir_veces
5 Steve Plimpton, sjplimp@sandia.gov
6
7 Copyright (2003) Sandia Corporation. Under the terms of Contract
8 DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
9 certain rights in this software. This software is distributed under
10 the GNU General Public License.
11
12 See the README file in the top-level LAMMPS directory.
13 ------------------------------------------------------------------------- */
14
15 /* ----------------------------------------------------------------------
16 Contributing authors: Adrian A. Schratt and Volker Mohles (ICAMS)
17 ------------------------------------------------------------------------- */
18
19 #include "fix_orient_eco.h"
20
21 #include "atom.h"
22 #include "citeme.h"
23 #include "comm.h"
24 #include "error.h"
25 #include "force.h"
26 #include "math_const.h"
27 #include "memory.h"
28 #include "neigh_list.h"
29 #include "neigh_request.h"
30 #include "neighbor.h"
31 #include "pair.h"
32 #include "respa.h"
33 #include "update.h"
34
35 #include <cmath>
36
37 using namespace LAMMPS_NS;
38 using namespace FixConst;
39 using namespace MathConst;
40
41 static const char cite_fix_orient_eco[] =
42 "fix orient/eco command:\n\n"
43 "@Article{Schratt20,\n"
44 " author = {A. A. Schratt, V. Mohles},\n"
45 " title = {Efficient calculation of the ECO driving force for atomistic simulations of grain boundary motion},\n"
46 " journal = {Computational Materials Science},\n"
47 " volume = {182},\n"
48 " year = {2020},\n"
49 " pages = {109774},\n"
50 " doi = {j.commatsci.2020.109774},\n"
51 " url = {https://doi.org/10.1016/j.commatsci.2020.109774}\n"
52 "}\n\n";
53
54 struct FixOrientECO::Nbr {
55 double duchi; // potential derivative
56 double real_phi[2][3]; // real part of wave function
57 double imag_phi[2][3]; // imaginary part of wave function
58 };
59
60 /* ---------------------------------------------------------------------- */
61
FixOrientECO(LAMMPS * lmp,int narg,char ** arg)62 FixOrientECO::FixOrientECO(LAMMPS *lmp, int narg, char **arg) :
63 Fix(lmp, narg, arg),
64 dir_filename(nullptr), order(nullptr), nbr(nullptr), list(nullptr)
65 {
66 if (lmp->citeme) lmp->citeme->add(cite_fix_orient_eco);
67
68 MPI_Comm_rank(world, &me);
69
70 if (narg != 7) error->all(FLERR, "Illegal fix orient/eco command");
71
72 scalar_flag = 1; // computes scalar
73 global_freq = 1; // values can be computed at every timestep
74 extscalar = 1; // scalar scales with # of atoms
75 peratom_flag = 1; // quantities are per atom quantities
76 size_peratom_cols = 2; // # of per atom quantities
77 peratom_freq = 1;
78 energy_global_flag = 1;
79
80 // parse input parameters
81
82 u_0 = utils::numeric(FLERR, arg[3],false,lmp);
83 sign = (u_0 >= 0.0 ? 1 : -1);
84 eta = utils::numeric(FLERR, arg[4],false,lmp);
85 r_cut = utils::numeric(FLERR, arg[5],false,lmp);
86
87 // read reference orientations from file
88 // work on rank 0 only
89
90 dir_filename = utils::strdup(arg[6]);
91 if (me == 0) {
92 char line[IMGMAX];
93 char *result;
94 int count;
95
96 FILE *infile = utils::open_potential(dir_filename,lmp,nullptr);
97 if (infile == nullptr)
98 error->one(FLERR,"Cannot open fix orient/eco file {}: {}",
99 dir_filename, utils::getsyserror());
100 for (int i = 0; i < 6; ++i) {
101 result = fgets(line, IMGMAX, infile);
102 if (!result) error->one(FLERR, "Fix orient/eco file read failed");
103 count = sscanf(line, "%lg %lg %lg", &dir_vec[i][0], &dir_vec[i][1], &dir_vec[i][2]);
104 if (count != 3) error->one(FLERR, "Fix orient/eco file read failed");
105 }
106 fclose(infile);
107
108 // calculate reciprocal lattice vectors
109 get_reciprocal();
110
111 squared_cutoff = r_cut * r_cut;
112 inv_squared_cutoff = 1.0 / squared_cutoff;
113 half_u = 0.5 * u_0;
114 inv_eta = 1.0 / eta;
115 }
116
117 // initializations
118 MPI_Bcast(&dir_vec[0][0], 18, MPI_DOUBLE, 0, world); // communicate direct lattice vectors
119 MPI_Bcast(&reciprocal_vectors[0][0][0], 18, MPI_DOUBLE, 0, world); // communicate reciprocal vectors
120 MPI_Bcast(&squared_cutoff, 1, MPI_DOUBLE, 0, world); // communicate squared cutoff radius
121 MPI_Bcast(&inv_squared_cutoff, 1, MPI_DOUBLE, 0, world); // communicate inverse squared cutoff radius
122 MPI_Bcast(&half_u, 1, MPI_DOUBLE, 0, world); // communicate half potential energy
123 MPI_Bcast(&inv_eta, 1, MPI_DOUBLE, 0, world); // communicate inverse threshold
124
125 // set comm size needed by this Fix
126 if (u_0 != 0) comm_forward = sizeof(Nbr) / sizeof(double);
127
128 added_energy = 0.0; // initial energy
129
130 nmax = atom->nmax;
131 nbr = (Nbr *) memory->smalloc(nmax * sizeof(Nbr), "orient/eco:nbr");
132 memory->create(order, nmax, 2, "orient/eco:order");
133 array_atom = order;
134
135 // zero the array since a variable may access it before first run
136 for (int i = 0; i < atom->nlocal; ++i) order[i][0] = order[i][1] = 0.0;
137 }
138
139 /* ---------------------------------------------------------------------- */
140
~FixOrientECO()141 FixOrientECO::~FixOrientECO() {
142 memory->destroy(order);
143 memory->sfree(nbr);
144 delete[] dir_filename;
145 }
146
147 /* ---------------------------------------------------------------------- */
148
setmask()149 int FixOrientECO::setmask() {
150 int mask = 0;
151 mask |= POST_FORCE;
152 mask |= POST_FORCE_RESPA;
153 return mask;
154 }
155
156 /* ---------------------------------------------------------------------- */
157
init()158 void FixOrientECO::init() {
159 // get this processors rank
160 MPI_Comm_rank(world, &me);
161
162 // compute normalization factor
163 int neigh = get_norm();
164 if (me == 0) {
165 utils::logmesg(lmp," fix orient/eco: cutoff={} norm_fac={} "
166 "neighbors={}\n", r_cut, norm_fac, neigh);
167 }
168
169 inv_norm_fac = 1.0 / norm_fac;
170
171 // check parameters
172 if (r_cut > force->pair->cutforce) {
173 error->all(FLERR, "Cutoff radius used by fix orient/eco must be smaller than force cutoff");
174 }
175
176 // communicate normalization factor
177 MPI_Bcast(&norm_fac, 1, MPI_DOUBLE, 0, world);
178 MPI_Bcast(&inv_norm_fac, 1, MPI_DOUBLE, 0, world);
179
180 if (utils::strmatch(update->integrate_style,"^respa")) {
181 ilevel_respa = ((Respa *) update->integrate)->nlevels - 1;
182 if (respa_level >= 0) ilevel_respa = MIN(respa_level, ilevel_respa);
183 }
184
185 // need a full neighbor list
186 // perpetual list, built whenever re-neighboring occurs
187
188 int irequest = neighbor->request(this, instance_me);
189 neighbor->requests[irequest]->pair = 0;
190 neighbor->requests[irequest]->fix = 1;
191 neighbor->requests[irequest]->half = 0;
192 neighbor->requests[irequest]->full = 1;
193 }
194
195 /* ---------------------------------------------------------------------- */
196
init_list(int,NeighList * ptr)197 void FixOrientECO::init_list(int /* id */, NeighList *ptr) {
198 list = ptr;
199 }
200
201 /* ---------------------------------------------------------------------- */
202
setup(int vflag)203 void FixOrientECO::setup(int vflag) {
204 if (utils::strmatch(update->integrate_style,"^verlet"))
205 post_force(vflag);
206 else {
207 ((Respa *) update->integrate)->copy_flevel_f(ilevel_respa);
208 post_force_respa(vflag,ilevel_respa, 0);
209 ((Respa *) update->integrate)->copy_f_flevel(ilevel_respa);
210 }
211 }
212
213 /* ---------------------------------------------------------------------- */
214
post_force(int)215 void FixOrientECO::post_force(int /* vflag */) {
216 // set local variables
217 int ii, i, jj, j; // loop variables and atom IDs
218 int k; // variable to loop over 3 reciprocal directions
219 int lambda; // variable to loop over 2 crystals
220 int dim; // variable to loop over 3 spatial components
221 double dx, dy, dz; // stores current interatomic vector
222 double squared_distance; // stores current squared distance
223 double chi; // stores current order parameter
224 double weight; // stores current weight function
225 double scalar_product; // stores current scalar product
226 double omega; // phase of sine transition
227 double omega_pre = MY_PI2 * inv_eta; // prefactor for omega
228 double duchi_pre = half_u * MY_PI * inv_eta * inv_norm_fac; // prefactor for duchi
229 double sin_om; // stores the value of the sine transition
230
231 // initialize added energy at this step
232 added_energy = 0.0;
233
234 // set local pointers and neighbor lists
235 double **x = atom->x;
236 double **f = atom->f;
237 int *mask = atom->mask;
238 int nall = atom->nlocal + atom->nghost;
239
240 int inum = list->inum;
241 int jnum;
242 int *ilist = list->ilist;
243 int *jlist;
244 int *numneigh = list->numneigh;
245 int **firstneigh = list->firstneigh;
246
247 // insure nbr and order data structures are adequate size
248 if (nall > nmax) {
249 nmax = nall;
250 memory->destroy(nbr);
251 memory->destroy(order);
252 nbr = (Nbr *) memory->smalloc(nmax * sizeof(Nbr), "orient/eco:nbr");
253 memory->create(order, nmax, 2, "orient/eco:order");
254 array_atom = order;
255 }
256
257 // loop over owned atoms and compute order parameter
258 for (ii = 0; ii < inum; ++ii) {
259 i = ilist[ii];
260 jlist = firstneigh[i];
261 jnum = numneigh[i];
262
263 // initializations
264 chi = 0.0;
265 for (k = 0; k < 3; ++k) {
266 nbr[i].real_phi[0][k] = nbr[i].real_phi[1][k] = 0.0;
267 nbr[i].imag_phi[0][k] = nbr[i].imag_phi[1][k] = 0.0;
268 }
269
270 // loop over all neighbors of atom i
271 for (jj = 0; jj < jnum; ++jj) {
272 j = jlist[jj];
273 j &= NEIGHMASK;
274
275 // vector difference
276 dx = x[i][0] - x[j][0];
277 dy = x[i][1] - x[j][1];
278 dz = x[i][2] - x[j][2];
279 squared_distance = dx * dx + dy * dy + dz * dz;
280
281 if (squared_distance < squared_cutoff) {
282 squared_distance *= inv_squared_cutoff;
283 weight = squared_distance * (squared_distance - 2.0) + 1.0;
284
285 for (lambda = 0; lambda < 2; ++lambda) {
286 for (k = 0; k < 3; ++k) {
287 scalar_product = reciprocal_vectors[lambda][k][0] * dx + reciprocal_vectors[lambda][k][1] * dy + reciprocal_vectors[lambda][k][2] * dz;
288 nbr[i].real_phi[lambda][k] += weight * cos(scalar_product);
289 nbr[i].imag_phi[lambda][k] += weight * sin(scalar_product);
290 }
291 }
292 }
293 }
294
295 // collect contributions
296 for (k = 0; k < 3; ++k) {
297 chi += (nbr[i].real_phi[0][k] * nbr[i].real_phi[0][k] + nbr[i].imag_phi[0][k] * nbr[i].imag_phi[0][k] -
298 nbr[i].real_phi[1][k] * nbr[i].real_phi[1][k] - nbr[i].imag_phi[1][k] * nbr[i].imag_phi[1][k]);
299 }
300 chi *= inv_norm_fac;
301 order[i][0] = chi;
302
303 // compute normalized order parameter
304 // and potential energy
305 if (chi > eta) {
306 added_energy += half_u;
307 nbr[i].duchi = 0.0;
308 order[i][1] = sign;
309 } else if (chi < -eta) {
310 added_energy -= half_u;
311 nbr[i].duchi = 0.0;
312 order[i][1] = -sign;
313 } else {
314 omega = omega_pre * chi;
315 sin_om = sin(omega);
316
317 added_energy += half_u * sin_om;
318 nbr[i].duchi = duchi_pre * cos(omega);
319 order[i][1] = sign * sin_om;
320 }
321
322 // compute product with potential derivative
323 for (k = 0; k < 3; ++k) {
324 for (lambda = 0; lambda < 2; ++lambda) {
325 nbr[i].real_phi[lambda][k] *= nbr[i].duchi;
326 nbr[i].imag_phi[lambda][k] *= nbr[i].duchi;
327 }
328 }
329 }
330
331 // set additional local variables
332 double gradient_ii_cos[2][3][3]; // gradient ii cosine term
333 double gradient_ii_sin[2][3][3]; // gradient ii sine term
334 double gradient_ij_vec[2][3][3]; // gradient ij vector term
335 double gradient_ij_sca[2][3]; // gradient ij scalar term
336 double weight_gradient_prefactor; // gradient prefactor
337 double weight_gradient[3]; // gradient of weight
338 double cos_scalar_product; // cosine of scalar product
339 double sin_scalar_product; // sine of scalar product
340 double gcos_scalar_product; // gradient weight function * cosine of scalar product
341 double gsin_scalar_product; // gradient weight function * sine of scalar product
342
343 // compute force only if synthetic
344 // potential is not zero
345 if (u_0 != 0.0) {
346 // communicate to acquire nbr data for ghost atoms
347 comm->forward_comm_fix(this);
348
349 // loop over all atoms
350 for (ii = 0; ii < inum; ++ii) {
351 i = ilist[ii];
352 jlist = firstneigh[i];
353 jnum = numneigh[i];
354
355 const bool no_boundary_atom = (nbr[i].duchi == 0.0);
356
357 // skip atoms not in group
358 if (!(mask[i] & groupbit)) continue;
359
360 // initializations
361 for (k = 0; k < 3; ++k) {
362 for (lambda = 0; lambda < 2; ++lambda) {
363 for (dim = 0; dim < 3; ++dim) {
364 gradient_ii_cos[lambda][k][dim] = 0.0;
365 gradient_ii_sin[lambda][k][dim] = 0.0;
366 gradient_ij_vec[lambda][k][dim] = 0.0;
367 }
368 gradient_ij_sca[lambda][k] = 0.0;
369 }
370 }
371 // loop over all neighbors of atom i
372 // for those within squared_cutoff, compute force
373 for (jj = 0; jj < jnum; ++jj) {
374 j = jlist[jj];
375 j &= NEIGHMASK;
376
377 // do not compute force on atom i if it is far from boundary
378 if ((nbr[j].duchi == 0.0) && no_boundary_atom) continue;
379
380 // vector difference
381 dx = x[i][0] - x[j][0];
382 dy = x[i][1] - x[j][1];
383 dz = x[i][2] - x[j][2];
384 squared_distance = dx * dx + dy * dy + dz * dz;
385
386 if (squared_distance < squared_cutoff) {
387 // compute force on atom i
388 // need weight and its gradient
389 squared_distance *= inv_squared_cutoff;
390 weight = squared_distance * (squared_distance - 2.0) + 1.0;
391 weight_gradient_prefactor = 4.0 * (squared_distance - 1.0) * inv_squared_cutoff;
392 weight_gradient[0] = weight_gradient_prefactor * dx;
393 weight_gradient[1] = weight_gradient_prefactor * dy;
394 weight_gradient[2] = weight_gradient_prefactor * dz;
395
396 // (1) compute scalar product and sine and cosine of it
397 // (2) compute product of sine and cosine with gradient of weight function
398 // (3) compute gradient_ii_cos and gradient_ii_sin by summing up result of (2)
399 // (4) compute gradient_ij_vec and gradient_ij_sca
400 for (lambda = 0; lambda < 2; ++lambda) {
401 for (k = 0; k < 3; ++k) {
402 scalar_product = reciprocal_vectors[lambda][k][0] * dx + reciprocal_vectors[lambda][k][1] * dy + reciprocal_vectors[lambda][k][2] * dz;
403 cos_scalar_product = cos(scalar_product);
404 sin_scalar_product = sin(scalar_product);
405 for (dim = 0; dim < 3; ++dim) {
406 gradient_ii_cos[lambda][k][dim] += (gcos_scalar_product = weight_gradient[dim] * cos_scalar_product);
407 gradient_ii_sin[lambda][k][dim] += (gsin_scalar_product = weight_gradient[dim] * sin_scalar_product);
408 gradient_ij_vec[lambda][k][dim] += (nbr[j].real_phi[lambda][k] * gcos_scalar_product - nbr[j].imag_phi[lambda][k] * gsin_scalar_product);
409 }
410 gradient_ij_sca[lambda][k] += weight * (nbr[j].real_phi[lambda][k] * sin_scalar_product + nbr[j].imag_phi[lambda][k] * cos_scalar_product);
411 }
412 }
413 }
414 }
415
416 // sum contributions
417 for (k = 0; k < 3; ++k) {
418 for (dim = 0; dim < 3; ++dim) {
419 f[i][dim] -= (nbr[i].real_phi[0][k] * gradient_ii_cos[0][k][dim] + nbr[i].imag_phi[0][k] * gradient_ii_sin[0][k][dim] + gradient_ij_vec[0][k][dim] + reciprocal_vectors[1][k][dim] * gradient_ij_sca[1][k]);
420 f[i][dim] += (nbr[i].real_phi[1][k] * gradient_ii_cos[1][k][dim] + nbr[i].imag_phi[1][k] * gradient_ii_sin[1][k][dim] + gradient_ij_vec[1][k][dim] + reciprocal_vectors[0][k][dim] * gradient_ij_sca[0][k]);
421 }
422 }
423 }
424 }
425 }
426
427 /* ---------------------------------------------------------------------- */
428
post_force_respa(int vflag,int ilevel,int)429 void FixOrientECO::post_force_respa(int vflag, int ilevel, int /* iloop */) {
430 if (ilevel == ilevel_respa) post_force(vflag);
431 }
432
433 /* ---------------------------------------------------------------------- */
434
compute_scalar()435 double FixOrientECO::compute_scalar() {
436 double added_energy_total;
437 MPI_Allreduce(&added_energy, &added_energy_total, 1, MPI_DOUBLE, MPI_SUM, world);
438 return added_energy_total;
439 }
440
441 /* ---------------------------------------------------------------------- */
442
pack_forward_comm(int n,int * list,double * buf,int,int *)443 int FixOrientECO::pack_forward_comm(int n, int *list, double *buf, int /*pbc_flag*/, int */*pbc*/) {
444 int i, j;
445 int m = 0;
446
447 for (i = 0; i < n; ++i) {
448 j = list[i];
449 *((Nbr*)&buf[m]) = nbr[j];
450 m += sizeof(Nbr)/sizeof(double);
451 }
452
453 return m;
454 }
455
456 /* ---------------------------------------------------------------------- */
457
unpack_forward_comm(int n,int first,double * buf)458 void FixOrientECO::unpack_forward_comm(int n, int first, double *buf) {
459 int i;
460 int last = first + n;
461 int m = 0;
462
463 for (i = first; i < last; ++i) {
464 nbr[i] = *((Nbr*) &buf[m]);
465 m += sizeof(Nbr) / sizeof(double);
466 }
467 }
468
469 /* ----------------------------------------------------------------------
470 memory usage of local atom-based arrays
471 ------------------------------------------------------------------------- */
472
memory_usage()473 double FixOrientECO::memory_usage() {
474 double bytes = (double)nmax * sizeof(Nbr);
475 bytes += (double)2 * nmax * sizeof(double);
476 return bytes;
477 }
478
479 /* ----------------------------------------------------------------------
480 reciprocal lattice vectors from file input
481 ------------------------------------------------------------------------- */
482
get_reciprocal()483 void FixOrientECO::get_reciprocal() {
484 // volume of unit cell A
485 double vol = 0.5 * (dir_vec[0][0] * (dir_vec[1][1] * dir_vec[2][2] - dir_vec[2][1] * dir_vec[1][2]) + dir_vec[1][0] * (dir_vec[2][1] * dir_vec[0][2] - dir_vec[0][1] * dir_vec[2][2]) + dir_vec[2][0] * (dir_vec[0][1] * dir_vec[1][2] - dir_vec[1][1] * dir_vec[0][2])) / MY_PI;
486 double i_vol = 1.0 / vol;
487
488 // grain A: reciprocal_vectors 0
489 reciprocal_vectors[0][0][0] = (dir_vec[1][1] * dir_vec[2][2] - dir_vec[2][1] * dir_vec[1][2]) * i_vol;
490 reciprocal_vectors[0][0][1] = (dir_vec[1][2] * dir_vec[2][0] - dir_vec[2][2] * dir_vec[1][0]) * i_vol;
491 reciprocal_vectors[0][0][2] = (dir_vec[1][0] * dir_vec[2][1] - dir_vec[2][0] * dir_vec[1][1]) * i_vol;
492
493 // grain A: reciprocal_vectors 1
494 reciprocal_vectors[0][1][0] = (dir_vec[2][1] * dir_vec[0][2] - dir_vec[0][1] * dir_vec[2][2]) * i_vol;
495 reciprocal_vectors[0][1][1] = (dir_vec[2][2] * dir_vec[0][0] - dir_vec[0][2] * dir_vec[2][0]) * i_vol;
496 reciprocal_vectors[0][1][2] = (dir_vec[2][0] * dir_vec[0][1] - dir_vec[0][0] * dir_vec[2][1]) * i_vol;
497
498 // grain A: reciprocal_vectors 2
499 reciprocal_vectors[0][2][0] = (dir_vec[0][1] * dir_vec[1][2] - dir_vec[1][1] * dir_vec[0][2]) * i_vol;
500 reciprocal_vectors[0][2][1] = (dir_vec[0][2] * dir_vec[1][0] - dir_vec[1][2] * dir_vec[0][0]) * i_vol;
501 reciprocal_vectors[0][2][2] = (dir_vec[0][0] * dir_vec[1][1] - dir_vec[1][0] * dir_vec[0][1]) * i_vol;
502
503 // volume of unit cell B
504 vol = 0.5 * (dir_vec[3][0] * (dir_vec[4][1] * dir_vec[5][2] - dir_vec[5][1] * dir_vec[4][2]) + dir_vec[4][0] * (dir_vec[5][1] * dir_vec[3][2] - dir_vec[3][1] * dir_vec[5][2]) + dir_vec[5][0] * (dir_vec[3][1] * dir_vec[4][2] - dir_vec[4][1] * dir_vec[3][2])) / MY_PI;
505 i_vol = 1.0 / vol;
506
507 // grain B: reciprocal_vectors 0
508 reciprocal_vectors[1][0][0] = (dir_vec[4][1] * dir_vec[5][2] - dir_vec[5][1] * dir_vec[4][2]) * i_vol;
509 reciprocal_vectors[1][0][1] = (dir_vec[4][2] * dir_vec[5][0] - dir_vec[5][2] * dir_vec[4][0]) * i_vol;
510 reciprocal_vectors[1][0][2] = (dir_vec[4][0] * dir_vec[5][1] - dir_vec[5][0] * dir_vec[4][1]) * i_vol;
511
512 // grain B: reciprocal_vectors 1
513 reciprocal_vectors[1][1][0] = (dir_vec[5][1] * dir_vec[3][2] - dir_vec[3][1] * dir_vec[5][2]) * i_vol;
514 reciprocal_vectors[1][1][1] = (dir_vec[5][2] * dir_vec[3][0] - dir_vec[3][2] * dir_vec[5][0]) * i_vol;
515 reciprocal_vectors[1][1][2] = (dir_vec[5][0] * dir_vec[3][1] - dir_vec[3][0] * dir_vec[5][1]) * i_vol;
516
517 // grain B: reciprocal_vectors 2
518 reciprocal_vectors[1][2][0] = (dir_vec[3][1] * dir_vec[4][2] - dir_vec[4][1] * dir_vec[3][2]) * i_vol;
519 reciprocal_vectors[1][2][1] = (dir_vec[3][2] * dir_vec[4][0] - dir_vec[4][2] * dir_vec[3][0]) * i_vol;
520 reciprocal_vectors[1][2][2] = (dir_vec[3][0] * dir_vec[4][1] - dir_vec[4][0] * dir_vec[3][1]) * i_vol;
521 }
522
523 /* ----------------------------------------------------------------------
524 normalization factor
525 ------------------------------------------------------------------------- */
526
get_norm()527 int FixOrientECO::get_norm() {
528 // set up local variables
529 double delta[3]; // relative position
530 double squared_distance; // squared distance of atoms i and j
531 double weight; // weight function for atoms i and j
532 double wsum = 0.0; // sum of all weight functions
533 double scalar_product; // scalar product
534 double reesum[3] = {0.0, 0.0, 0.0}; // sum of real part
535 double imesum[3] = {0.0, 0.0, 0.0}; // sum of imaginary part
536
537 int max_co = 4; // will produce wrong results for rcut > 3 * lattice constant
538
539 int neigh = 0; // count number of neighbors used
540
541 // loop over ideal lattice positions
542 int i, k, idx[3];
543 for (idx[0] = -max_co; idx[0] <= max_co; ++idx[0]) {
544 for (idx[1] = -max_co; idx[1] <= max_co; ++idx[1]) {
545 for (idx[2] = -max_co; idx[2] <= max_co; ++idx[2]) {
546 // distance of atoms
547 for (i = 0; i < 3; ++i) {
548 delta[i] = dir_vec[0][i] * idx[0] + dir_vec[1][i] * idx[1] + dir_vec[2][i] * idx[2];
549 }
550 squared_distance = delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2];
551
552 // check if atom is within cutoff region
553 if ((squared_distance != 0.0) and (squared_distance < squared_cutoff)) {
554 ++neigh;
555 squared_distance *= inv_squared_cutoff;
556
557 // weight
558 weight = squared_distance * (squared_distance - 2.0) + 1.0;
559 wsum += weight;
560
561 // three reciprocal directions
562 for (k = 0; k < 3; ++k) {
563 scalar_product = reciprocal_vectors[1][k][0] * delta[0] + reciprocal_vectors[1][k][1] * delta[1] + reciprocal_vectors[1][k][2] * delta[2];
564 reesum[k] += weight * cos(scalar_product);
565 imesum[k] -= weight * sin(scalar_product);
566 }
567 }
568 }
569 }
570 }
571
572 // compute normalization
573 norm_fac = 3.0 * wsum * wsum;
574 for (k = 0; k < 3; ++k) {
575 norm_fac -= reesum[k] * reesum[k] + imesum[k] * imesum[k];
576 }
577 return neigh;
578 }
579
580
581
582