1 // clang-format off
2 /* ----------------------------------------------------------------------
3    LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
4    https://www.lammps.org/, Sandia National Laboratories
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 #include "fix_langevin_drude.h"
16 #include "fix_drude.h"
17 
18 #include "atom.h"
19 #include "comm.h"
20 #include "compute.h"
21 #include "domain.h"
22 #include "error.h"
23 #include "force.h"
24 #include "input.h"
25 #include "modify.h"
26 #include "random_mars.h"
27 #include "update.h"
28 #include "variable.h"
29 
30 #include <cstring>
31 #include <cmath>
32 
33 using namespace LAMMPS_NS;
34 using namespace FixConst;
35 
36 enum{NOBIAS,BIAS};
37 enum{CONSTANT,EQUAL};
38 
39 /* ---------------------------------------------------------------------- */
40 
FixLangevinDrude(LAMMPS * lmp,int narg,char ** arg)41 FixLangevinDrude::FixLangevinDrude(LAMMPS *lmp, int narg, char **arg) :
42   Fix(lmp, narg, arg)
43 {
44   if (narg < 9) error->all(FLERR,"Illegal fix langevin/drude command");
45   // TODO add option for tally
46 
47   // Langevin thermostat should be applied every step
48   nevery = 1;
49   global_freq = nevery;
50   comm_reverse = 3;
51 
52   // core temperature
53   tstr_core = nullptr;
54   if (utils::strmatch(arg[3],"^v_")) {
55     tstr_core = utils::strdup(arg[3]+2);
56     tstyle_core = EQUAL;
57   } else {
58     t_start_core = utils::numeric(FLERR,arg[3],false,lmp);
59     t_target_core = t_start_core;
60     tstyle_core = CONSTANT;
61   }
62   t_period_core = utils::numeric(FLERR,arg[4],false,lmp);
63   int seed_core = utils::inumeric(FLERR,arg[5],false,lmp);
64 
65   // drude temperature
66   tstr_drude = nullptr;
67   if (strstr(arg[7],"v_") == arg[6]) {
68     tstr_drude = utils::strdup(arg[6]+2);
69     tstyle_drude = EQUAL;
70   } else {
71     t_start_drude = utils::numeric(FLERR,arg[6],false,lmp);
72     t_target_drude = t_start_drude;
73     tstyle_drude = CONSTANT;
74   }
75   t_period_drude = utils::numeric(FLERR,arg[7],false,lmp);
76   int seed_drude = utils::inumeric(FLERR,arg[8],false,lmp);
77 
78   // error checks
79   if (t_period_core <= 0.0)
80     error->all(FLERR,"Fix langevin/drude period must be > 0.0");
81   if (seed_core  <= 0) error->all(FLERR,"Illegal langevin/drude seed");
82   if (t_period_drude <= 0.0)
83     error->all(FLERR,"Fix langevin/drude period must be > 0.0");
84   if (seed_drude <= 0) error->all(FLERR,"Illegal langevin/drude seed");
85 
86   random_core  = new RanMars(lmp,seed_core);
87   random_drude = new RanMars(lmp,seed_drude);
88 
89   int iarg = 9;
90   zero = 0;
91   while (iarg < narg) {
92     if (strcmp(arg[iarg],"zero") == 0) {
93       if (iarg+2 > narg) error->all(FLERR,"Illegal fix langevin/drude command");
94       if (strcmp(arg[iarg+1],"no") == 0) zero = 0;
95       else if (strcmp(arg[iarg+1],"yes") == 0) zero = 1;
96       else error->all(FLERR,"Illegal fix langevin/drude command");
97       iarg += 2;
98     } else error->all(FLERR,"Illegal fix langevin/drude command");
99   }
100 
101   tflag = 0; // no external compute/temp is specified yet (for bias)
102   energy = 0.;
103   fix_drude = nullptr;
104   temperature = nullptr;
105   id_temp = nullptr;
106 }
107 
108 /* ---------------------------------------------------------------------- */
109 
~FixLangevinDrude()110 FixLangevinDrude::~FixLangevinDrude()
111 {
112   delete random_core;
113   delete [] tstr_core;
114   delete random_drude;
115   delete [] tstr_drude;
116 }
117 
118 /* ---------------------------------------------------------------------- */
119 
setmask()120 int FixLangevinDrude::setmask()
121 {
122   int mask = 0;
123   mask |= POST_FORCE;
124   return mask;
125 }
126 
127 /* ---------------------------------------------------------------------- */
128 
init()129 void FixLangevinDrude::init()
130 {
131   // check variable-style target core temperature
132   if (tstr_core) {
133     tvar_core = input->variable->find(tstr_core);
134     if (tvar_core < 0)
135       error->all(FLERR,"Variable name for fix langevin/drude does not exist");
136     if (input->variable->equalstyle(tvar_core)) tstyle_core = EQUAL;
137     else error->all(FLERR,"Variable for fix langevin/drude is invalid style");
138   }
139 
140   // check variable-style target drude temperature
141   if (tstr_drude) {
142     tvar_drude = input->variable->find(tstr_drude);
143     if (tvar_drude < 0)
144       error->all(FLERR,"Variable name for fix langevin/drude does not exist");
145     if (input->variable->equalstyle(tvar_drude)) tstyle_drude = EQUAL;
146     else error->all(FLERR,"Variable for fix langevin/drude is invalid style");
147   }
148 
149   int ifix;
150   for (ifix = 0; ifix < modify->nfix; ifix++)
151     if (strcmp(modify->fix[ifix]->style,"drude") == 0) break;
152   if (ifix == modify->nfix) error->all(FLERR, "fix langevin/drude requires fix drude");
153   fix_drude = (FixDrude *) modify->fix[ifix];
154 }
155 
156 /* ---------------------------------------------------------------------- */
157 
setup(int)158 void FixLangevinDrude::setup(int /*vflag*/)
159 {
160   if (!utils::strmatch(update->integrate_style,"^verlet"))
161     error->all(FLERR,"RESPA style not compatible with fix langevin/drude");
162   if (!comm->ghost_velocity)
163     error->all(FLERR,"fix langevin/drude requires ghost velocities. Use comm_modify vel yes");
164 
165   if (zero) {
166       int *mask = atom->mask;
167       int nlocal = atom->nlocal;
168       int *drudetype = fix_drude->drudetype;
169       int *type = atom->type;
170       bigint ncore_loc = 0;
171       for (int i=0; i<nlocal; i++)
172           if (mask[i] & groupbit && drudetype[type[i]] != DRUDE_TYPE)
173               ncore_loc++;
174       MPI_Allreduce(&ncore_loc, &ncore, 1, MPI_LMP_BIGINT, MPI_SUM, world);
175   }
176 }
177 
178 /* ---------------------------------------------------------------------- */
179 
modify_param(int narg,char ** arg)180 int FixLangevinDrude::modify_param(int narg, char **arg)
181 {
182   if (strcmp(arg[0],"temp") == 0) {
183     if (narg < 2) error->all(FLERR,"Illegal fix_modify command");
184     delete [] id_temp;
185     id_temp = utils::strdup(arg[1]);
186 
187     int icompute = modify->find_compute(id_temp);
188     if (icompute < 0)
189       error->all(FLERR,"Could not find fix_modify temperature ID");
190     temperature = modify->compute[icompute];
191 
192     if (temperature->tempflag == 0)
193       error->all(FLERR,
194                  "Fix_modify temperature ID does not compute temperature");
195     if (temperature->igroup != igroup && comm->me == 0)
196       error->warning(FLERR,"Group for fix_modify temp != fix group");
197     return 2;
198   }
199   return 0;
200 }
201 
202 /* ---------------------------------------------------------------------- */
203 
post_force(int)204 void FixLangevinDrude::post_force(int /*vflag*/)
205 {
206   // Thermalize by adding the langevin force if thermalize=true.
207   // Each core-Drude pair is thermalized only once: where the core is local.
208 
209   double **v = atom->v, **f = atom->f;
210   int *mask = atom->mask;
211   int nlocal = atom->nlocal, nall = atom->nlocal + atom->nghost;
212   int *type = atom->type;
213   double *mass = atom->mass;
214   double *rmass = atom->rmass;
215   double ftm2v = force->ftm2v, mvv2e = force->mvv2e;
216   double kb = force->boltz, dt = update->dt;
217 
218   int *drudetype = fix_drude->drudetype;
219   tagint *drudeid = fix_drude->drudeid;
220   double vdrude[3], vcore[3]; // velocities in reduced representation
221   double fdrude[3], fcore[3]; // forces in reduced representation
222   double Ccore, Cdrude, Gcore, Gdrude;
223   double fcoresum[3], fcoreloc[3];
224   int dim = domain->dimension;
225 
226   // Compute target core temperature
227   if (tstyle_core == CONSTANT)
228      t_target_core = t_start_core; // + delta * (t_stop-t_start_core);
229   else {
230       modify->clearstep_compute();
231       t_target_core = input->variable->compute_equal(tvar_core);
232       if (t_target_core < 0.0)
233         error->one(FLERR, "Fix langevin/drude variable returned "
234                           "negative core temperature");
235       modify->addstep_compute(update->ntimestep + nevery);
236   }
237 
238   // Compute target drude temperature
239   if (tstyle_drude == CONSTANT)
240       t_target_drude = t_start_drude; // + delta * (t_stop-t_start_core);
241   else {
242       modify->clearstep_compute();
243       t_target_drude = input->variable->compute_equal(tvar_drude);
244       if (t_target_drude < 0.0)
245         error->one(FLERR, "Fix langevin/drude variable returned "
246                           "negative drude temperature");
247       modify->addstep_compute(update->ntimestep + nevery);
248   }
249 
250   // Clear ghost forces
251   // They have already been communicated if needed
252   for (int i = nlocal; i < nall; i++) {
253       for (int k = 0; k < dim; k++)
254         f[i][k] = 0.;
255   }
256   if (zero) for (int k=0; k<dim; k++) fcoreloc[k] = 0.;
257 
258   // NB : the masses are the real masses, not the reduced ones
259   for (int i = 0; i < nlocal; i++) {
260     if (mask[i] & groupbit) { // only the cores need to be in the group
261       if (drudetype[type[i]] == NOPOL_TYPE) { // Non-polarizable atom
262         double mi;
263         if (rmass)
264           mi = rmass[i];
265         else
266           mi = mass[type[i]];
267         Gcore  = mi / t_period_core  / ftm2v;
268         Ccore  = sqrt(2.0 * Gcore  * kb * t_target_core  / dt / ftm2v / mvv2e);
269         if (temperature) temperature->remove_bias(i, v[i]);
270         for (int k = 0; k < dim; k++) {
271             fcore[k] = Ccore  * random_core->gaussian()  - Gcore  * v[i][k];
272             if (zero) fcoreloc[k] += fcore[k];
273             f[i][k] += fcore[k];
274         }
275         if (temperature) temperature->restore_bias(i, v[i]);
276       } else {
277         if (drudetype[type[i]] == DRUDE_TYPE) continue; // do with the core
278 
279         int j = atom->map(drudeid[i]);
280         double mi, mj, mtot, mu; // i is core, j is drude
281         if (rmass) {
282           mi = rmass[i];
283           mj = rmass[j];
284         } else {
285           mi = mass[type[i]];
286           mj = mass[type[j]];
287         }
288         mtot = mi + mj;
289         mu = mi * mj / mtot;
290         mi /= mtot;
291         mj /= mtot;
292 
293         Gcore  = mtot / t_period_core  / ftm2v;
294         Gdrude = mu   / t_period_drude / ftm2v;
295         Ccore  = sqrt(2.0 * Gcore  * kb * t_target_core  / dt / ftm2v / mvv2e);
296         Cdrude = sqrt(2.0 * Gdrude * kb * t_target_drude / dt / ftm2v / mvv2e);
297 
298         if (temperature) {
299             temperature->remove_bias(i, v[i]);
300             temperature->remove_bias(j, v[j]);
301         }
302         for (int k=0; k<dim; k++) {
303           // TODO check whether a fix_modify temp can subtract a bias velocity
304           vcore[k] = mi * v[i][k] + mj * v[j][k];
305           vdrude[k] = v[j][k] - v[i][k];
306 
307           fcore[k]  = Ccore  * random_core->gaussian()  - Gcore  * vcore[k];
308           fdrude[k] = Cdrude * random_drude->gaussian() - Gdrude * vdrude[k];
309 
310           if (zero) fcoreloc[k]  += fcore[k];
311 
312           f[i][k] += mi * fcore[k] - fdrude[k];
313           f[j][k] += mj * fcore[k] + fdrude[k];
314 
315           // TODO tally energy if asked
316         }
317         if (temperature) {
318             temperature->restore_bias(i, v[i]);
319             temperature->restore_bias(j, v[j]);
320         }
321       }
322     }
323   }
324 
325   if (zero) { // Remove the drift
326     MPI_Allreduce(fcoreloc, fcoresum, dim, MPI_DOUBLE, MPI_SUM, world);
327     for (int k=0; k<dim; k++) fcoresum[k] /= ncore;
328     for (int i=0; i<nlocal; i++) {
329       if (mask[i] & groupbit) { // only the cores need to be in the group
330         if (drudetype[type[i]] == NOPOL_TYPE) {
331           for (int k=0; k<dim; k++) f[i][k] -= fcoresum[k];
332         } else {
333           if (drudetype[type[i]] == DRUDE_TYPE) continue; // do with the core
334           int j = atom->map(drudeid[i]);
335           double mi, mj, mtot; // i is core, j is drude
336           if (rmass) {
337             mi = rmass[i];
338             mj = rmass[j];
339           } else {
340             mi = mass[type[i]];
341             mj = mass[type[j]];
342           }
343           mtot = mi + mj;
344           mi /= mtot;
345           mj /= mtot;
346           for (int k=0; k<dim; k++) {
347             f[i][k] -= mi * fcoresum[k];
348             f[j][k] -= mj * fcoresum[k];
349           }
350         }
351       }
352     }
353   }
354 
355   // Reverse communication of the forces on ghost Drude particles
356   comm->reverse_comm();
357 }
358 
359 /* ---------------------------------------------------------------------- */
360 
reset_target(double t_new)361 void FixLangevinDrude::reset_target(double t_new)
362 {
363   t_target_core = t_start_core = t_new;
364 }
365 
366 /* ----------------------------------------------------------------------
367    extract thermostat properties
368 ------------------------------------------------------------------------- */
369 
extract(const char * str,int & dim)370 void *FixLangevinDrude::extract(const char *str, int &dim)
371 {
372   dim = 0;
373   if (strcmp(str,"t_target_core") == 0) {
374     return &t_target_core;
375   } else if (strcmp(str,"t_target_drude") == 0) {
376     return &t_target_drude;
377   } else error->all(FLERR, "Illegal extract string in fix langevin/drude");
378   return nullptr;
379 }
380 
381 /* ---------------------------------------------------------------------- */
pack_reverse_comm(int n,int first,double * buf)382 int FixLangevinDrude::pack_reverse_comm(int n, int first, double *buf)
383 {
384   int i,m,last;
385   double ** f = atom->f;
386 
387   m = 0;
388   last = first + n;
389   for (i = first; i < last; i++) {
390     buf[m++] = f[i][0];
391     buf[m++] = f[i][1];
392     buf[m++] = f[i][2];
393   }
394   return m;
395 }
396 
397 /* ---------------------------------------------------------------------- */
398 
unpack_reverse_comm(int n,int * list,double * buf)399 void FixLangevinDrude::unpack_reverse_comm(int n, int *list, double *buf)
400 {
401   int i,j,m;
402   double ** f = atom->f;
403 
404   m = 0;
405   for (i = 0; i < n; i++) {
406     j = list[i];
407     f[j][0] += buf[m++];
408     f[j][1] += buf[m++];
409     f[j][2] += buf[m++];
410   }
411 }
412 
413