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