1 /* ----------------------------------------------------------------------
2    SPARTA - Stochastic PArallel Rarefied-gas Time-accurate Analyzer
3    http://sparta.sandia.gov
4    Steve Plimpton, sjplimp@sandia.gov, Michael Gallis, magalli@sandia.gov
5    Sandia National Laboratories
6 
7    Copyright (2014) 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 SPARTA directory.
13 ------------------------------------------------------------------------- */
14 
15 /* ----------------------------------------------------------------------
16    Contributing author: Krishnan Gopalan (NASA Ames)
17 ------------------------------------------------------------------------- */
18 
19 #include "collide.h"
20 #include "mpi.h"
21 #include "stdio.h"
22 #include "sparta.h"
23 #include "math.h"
24 #include "stdlib.h"
25 #include "string.h"
26 #include "surf_collide_cll.h"
27 #include "surf.h"
28 #include "surf_react.h"
29 #include "input.h"
30 #include "variable.h"
31 #include "particle.h"
32 #include "collide.h"
33 #include "domain.h"
34 #include "update.h"
35 #include "modify.h"
36 #include "comm.h"
37 #include "random_mars.h"
38 #include "random_knuth.h"
39 #include "math_const.h"
40 #include "math_extra.h"
41 #include "error.h"
42 
43 using namespace SPARTA_NS;
44 using namespace MathConst;
45 
46 enum{NONE,DISCRETE,SMOOTH};
47 
48 /* ---------------------------------------------------------------------- */
49 
SurfCollideCLL(SPARTA * sparta,int narg,char ** arg)50 SurfCollideCLL::SurfCollideCLL(SPARTA *sparta, int narg, char **arg) :
51   SurfCollide(sparta, narg, arg)
52 {
53   if (narg < 7) error->all(FLERR,"Illegal surf_collide cll command");
54 
55   tstr = NULL;
56 
57   if (strstr(arg[2],"v_") == arg[2]) {
58     int n = strlen(&arg[2][2]) + 1;
59     tstr = new char[n];
60     strcpy(tstr,&arg[2][2]);
61   } else {
62     twall = atof(arg[2]);
63     if (twall < 0.0) error->all(FLERR,"Illegal surf_collide cll command");
64   }
65 
66   acc_n = atof(arg[3]);
67   acc_t = atof(arg[4]);
68   acc_rot = atof(arg[5]);
69   acc_vib = atof(arg[6]);
70 
71   if (acc_n < 0.0 || acc_n > 1.0 || acc_t < 0.0 || acc_t > 1.0 ||
72       acc_rot < 0.0 || acc_rot > 1.0 || acc_vib < 0.0 || acc_vib > 1.0)
73     error->all(FLERR,"Surf_collide cll accommodation coeffs "
74                "must be >= 0 and <= 1");
75 
76   // optional args
77 
78   eccen = 0.0;
79   pflag = 0;
80   tflag = rflag = 0;
81 
82   int iarg = 7;
83   while (iarg < narg) {
84 
85     if (strcmp(arg[iarg],"partial") == 0) {
86         if (iarg+2 > narg) error->all(FLERR,"Illegal surf_collide cll command");
87         if (acc_n != acc_t)
88           error->all(FLERR,"Surf_collide cll partial requires acc_n = acc_t");
89         pflag = 1;
90         eccen = atof(arg[iarg+1]);
91         if (eccen < 0.0 || eccen >= 1.0 )
92           error->all(FLERR,"Surf_collide cll eccentricity must be >= 0 and <= 1");
93         iarg += 2;
94     } else if (strcmp(arg[iarg],"translate") == 0) {
95       if (iarg+4 > narg) error->all(FLERR,"Illegal surf_collide cll command");
96       tflag = 1;
97       vx = atof(arg[iarg+1]);
98       vy = atof(arg[iarg+2]);
99       vz = atof(arg[iarg+3]);
100       iarg += 4;
101     } else if (strcmp(arg[iarg],"rotate") == 0) {
102       if (iarg+7 > narg) error->all(FLERR,"Illegal surf_collide cll command");
103       rflag = 1;
104       px = atof(arg[iarg+1]);
105       py = atof(arg[iarg+2]);
106       pz = atof(arg[iarg+3]);
107       wx = atof(arg[iarg+4]);
108       wy = atof(arg[iarg+5]);
109       wz = atof(arg[iarg+6]);
110       if (domain->dimension == 2 && pz != 0.0)
111         error->all(FLERR,"Surf_collide cll rotation invalid for 2d");
112       if (domain->dimension == 2 && (wx != 0.0 || wy != 0.0))
113         error->all(FLERR,"Surf_collide cll rotation invalid for 2d");
114       iarg += 7;
115     } else error->all(FLERR,"Illegal surf_collide cll command");
116   }
117 
118   if (tflag && rflag) error->all(FLERR,"Illegal surf_collide cll command");
119   if (tflag || rflag) trflag = 1;
120   else trflag = 0;
121 
122   vstream[0] = vstream[1] = vstream[2] = 0.0;
123 
124   // initialize RNG
125 
126   random = new RanKnuth(update->ranmaster->uniform());
127   double seed = update->ranmaster->uniform();
128   random->reset(seed,comm->me,100);
129 }
130 
131 /* ---------------------------------------------------------------------- */
132 
~SurfCollideCLL()133 SurfCollideCLL::~SurfCollideCLL()
134 {
135   delete [] tstr;
136   delete random;
137 }
138 
139 /* ---------------------------------------------------------------------- */
140 
init()141 void SurfCollideCLL::init()
142 {
143   SurfCollide::init();
144 
145   // check variable
146 
147   if (tstr) {
148     tvar = input->variable->find(tstr);
149     if (tvar < 0)
150       error->all(FLERR,"Surf_collide cll variable name does not exist");
151     if (!input->variable->equal_style(tvar))
152       error->all(FLERR,"Surf_collide cll variable is invalid style");
153   }
154 }
155 
156 /* ----------------------------------------------------------------------
157    particle collision with surface with optional chemistry
158    ip = particle with current x = collision pt, current v = incident v
159    isurf = index of surface element
160    norm = surface normal unit vector
161    isr = index of reaction model if >= 0, -1 for no chemistry
162    ip = reset to NULL if destroyed by chemsitry
163    return jp = new particle if created by chemistry
164    return reaction = index of reaction (1 to N) that took place, 0 = no reaction
165    resets particle(s) to post-collision outward velocity
166 ------------------------------------------------------------------------- */
167 
168 Particle::OnePart *SurfCollideCLL::
collide(Particle::OnePart * & ip,double &,int isurf,double * norm,int isr,int & reaction)169 collide(Particle::OnePart *&ip, double &,
170         int isurf, double *norm, int isr, int &reaction)
171 {
172   nsingle++;
173 
174   // if surface chemistry defined, attempt reaction
175   // reaction = 1 to N for which reaction took place, 0 for none
176   // velreset = 1 if reaction reset post-collision velocity, else 0
177 
178   Particle::OnePart iorig;
179   Particle::OnePart *jp = NULL;
180   reaction = 0;
181   int velreset = 0;
182 
183   if (isr >= 0) {
184     if (modify->n_surf_react) memcpy(&iorig,ip,sizeof(Particle::OnePart));
185     reaction = surf->sr[isr]->react(ip,isurf,norm,jp,velreset);
186     if (reaction) surf->nreact_one++;
187   }
188 
189   // CLL reflection for each particle
190   // only if SurfReact did not already reset velocities
191   // also both partiticles need to trigger any fixes
192   //   to update per-particle properties which depend on
193   //   temperature of the particle, e.g. fix vibmode and fix ambipolar
194 
195   if (ip) {
196     if (!velreset) cll(ip,norm);
197     if (modify->n_update_custom) {
198       int i = ip - particle->particles;
199       modify->update_custom(i,twall,twall,twall,vstream);
200     }
201   }
202   if (jp) {
203     if (!velreset) cll(jp,norm);
204     if (modify->n_update_custom) {
205       int j = jp - particle->particles;
206       modify->update_custom(j,twall,twall,twall,vstream);
207     }
208   }
209 
210   // call any fixes with a surf_react() method
211   // they may reset j to -1, e.g. fix ambipolar
212   //   in which case newly created j is deleted
213 
214   if (reaction && modify->n_surf_react) {
215     int i = -1;
216     if (ip) i = ip - particle->particles;
217     int j = -1;
218     if (jp) j = jp - particle->particles;
219     modify->surf_react(&iorig,i,j);
220     if (jp && j < 0) {
221       jp = NULL;
222       particle->nlocal--;
223     }
224   }
225 
226   return jp;
227 }
228 
229 /* ----------------------------------------------------------------------
230   cll reflection
231   vrm = most probable speed of species, eqns (4.1) and (4.7)
232   vperp = velocity component perpendicular to surface along norm, eqn (12.3)
233   vtan12 = 2 velocity components tangential to surface
234   tangent1 = component of particle v tangential to surface,
235   check if tangent1 = 0 (normal collision), set randomly
236   tangent2 = norm x tangent1 = orthogonal tangential direction
237   tangent12 are both unit vectors
238 ------------------------------------------------------------------------- */
239 
cll(Particle::OnePart * p,double * norm)240 void SurfCollideCLL::cll(Particle::OnePart *p, double *norm)
241 {
242   double tangent1[3],tangent2[3];
243   Particle::Species *species = particle->species;
244   int ispecies = p->ispecies;
245   double beta_un,normalized_distbn_fn;
246 
247   double *v = p->v;
248   double dot = MathExtra::dot3(v,norm);
249   double vrm, vperp, vtan1, vtan2;
250 
251   tangent1[0] = v[0] - dot*norm[0];
252   tangent1[1] = v[1] - dot*norm[1];
253   tangent1[2] = v[2] - dot*norm[2];
254 
255   if (MathExtra::lensq3(tangent1) == 0.0) {
256     tangent2[0] = random->uniform();
257     tangent2[1] = random->uniform();
258     tangent2[2] = random->uniform();
259     MathExtra::cross3(norm,tangent2,tangent1);
260   }
261 
262   MathExtra::norm3(tangent1);
263   MathExtra::cross3(norm,tangent1,tangent2);
264 
265   double tan1 = MathExtra::dot3(v,tangent1);
266 
267   vrm = sqrt(2.0*update->boltz * twall / species[ispecies].mass);
268 
269   // CLL model normal velocity
270 
271   double r_1 = sqrt(-acc_n*log(random->uniform()));
272   double theta_1 = MY_2PI * random->uniform();
273   double dot_norm = dot/vrm * sqrt(1-acc_n);
274   vperp = vrm * sqrt(r_1*r_1 + dot_norm*dot_norm + 2*r_1*dot_norm*cos(theta_1));
275 
276   // CLL model tangential velocities
277 
278   double r_2 = sqrt(-acc_t*log(random->uniform()));
279   double theta_2 = MY_2PI * random->uniform();
280   double vtangent = tan1/vrm * sqrt(1-acc_t);
281   vtan1 = vrm * (vtangent + r_2*cos(theta_2));
282   vtan2 = vrm * r_2 * sin(theta_2);
283 
284   // partial keyword
285   // incomplete energy accommodation with partial/fully diffuse scattering
286   // adjust the final angle of the particle while keeping
287   //   the velocity magnitude or speed according to CLL scattering
288 
289   if (pflag) {
290     double tan2 = MathExtra::dot3(v,tangent2);
291     double theta_i, phi_i, psi_i, theta_f, phi_f, psi_f, cos_beta;
292 
293     theta_i = acos(dot/sqrt(MathExtra::lensq3(v)));
294     psi_i = acos(dot*dot/MathExtra::lensq3(v));
295     phi_i = atan2(tan2,tan1);
296 
297     double v_mag = sqrt(vperp*vperp + vtan1*vtan1 + vtan2*vtan2);
298 
299     double P = 0;
300     while (random->uniform() > P) {
301       phi_f = MY_2PI*random->uniform();
302       psi_f = acos(1-random->uniform());
303       cos_beta =  cos(psi_i)*cos(psi_f) +
304         sin(psi_i)*sin(psi_f)*cos(phi_i - phi_f);
305       P = (1-eccen)/(1-eccen*cos_beta);
306     }
307 
308     theta_f = acos(sqrt(cos(psi_f)));
309 
310     vperp = v_mag * cos(theta_f);
311     vtan1 = v_mag * sin(theta_f) * cos(phi_f);
312     vtan2 = v_mag * sin(theta_f) * sin(phi_f);
313   }
314 
315   // add in translation or rotation vector if specified
316   // only keep portion of vector tangential to surface element
317 
318   if (trflag) {
319     double vxdelta,vydelta,vzdelta;
320     if (tflag) {
321       vxdelta = vx; vydelta = vy; vzdelta = vz;
322       double dot = vxdelta*norm[0] + vydelta*norm[1] + vzdelta*norm[2];
323 
324       if (fabs(dot) > 0.001) {
325         dot /= vrm;
326         do {
327           do {
328             beta_un = (6.0*random->gaussian() - 3.0);
329           } while (beta_un + dot < 0.0);
330           normalized_distbn_fn = 2.0 * (beta_un + dot) /
331             (dot + sqrt(dot*dot + 2.0)) *
332             exp(0.5 + (0.5*dot)*(dot-sqrt(dot*dot + 2.0)) - beta_un*beta_un);
333         } while (normalized_distbn_fn < random->uniform());
334         vperp = beta_un*vrm;
335       }
336 
337     } else {
338       double *x = p->x;
339       vxdelta = wy*(x[2]-pz) - wz*(x[1]-py);
340       vydelta = wz*(x[0]-px) - wx*(x[2]-pz);
341       vzdelta = wx*(x[1]-py) - wy*(x[0]-px);
342       double dot = vxdelta*norm[0] + vydelta*norm[1] + vzdelta*norm[2];
343       vxdelta -= dot*norm[0];
344       vydelta -= dot*norm[1];
345       vzdelta -= dot*norm[2];
346     }
347 
348     v[0] = vperp*norm[0] + vtan1*tangent1[0] + vtan2*tangent2[0] + vxdelta;
349     v[1] = vperp*norm[1] + vtan1*tangent1[1] + vtan2*tangent2[1] + vydelta;
350     v[2] = vperp*norm[2] + vtan1*tangent1[2] + vtan2*tangent2[2] + vzdelta;
351 
352   // no translation or rotation
353 
354   } else {
355     v[0] = vperp*norm[0] + vtan1*tangent1[0] + vtan2*tangent2[0];
356     v[1] = vperp*norm[1] + vtan1*tangent1[1] + vtan2*tangent2[1];
357     v[2] = vperp*norm[2] + vtan1*tangent1[2] + vtan2*tangent2[2];
358   }
359 
360   // rotational component
361 
362   if (!sparta->collide || sparta->collide->rotstyle == NONE ||
363       species[ispecies].rotdof < 2) p->erot = 0.0;
364 
365   else {
366     double erot_mag = sqrt(p->erot*(1-acc_rot)/(update->boltz*twall));
367 
368     double r_rot,cos_theta_rot,A_rot,X_rot;
369     if (species[ispecies].rotdof == 2) {
370       r_rot = sqrt(-acc_rot*log(random->uniform()));
371       cos_theta_rot = cos(MY_2PI*random->uniform());
372     }
373     else if (species[ispecies].rotdof > 2) {
374       A_rot = 0;
375       while (A_rot < random->uniform()) {
376         X_rot = 4*random->uniform();
377         A_rot = 2.71828182845904523536028747*X_rot*X_rot*exp(-X_rot*X_rot);
378       }
379       r_rot = sqrt(acc_rot)*X_rot;
380       cos_theta_rot = 2*random->uniform() - 1;
381     }
382 
383     p->erot = update->boltz * twall *
384       (r_rot*r_rot + erot_mag*erot_mag + 2*r_rot*erot_mag*cos_theta_rot);
385     }
386 
387   // vibrational component
388   // NOTE: check all references to species[]->vibtmp
389 
390   int vibdof = species[ispecies].vibdof;
391   double r_vib, cos_theta_vib, A_vib, X_vib, evib_mag, evib_val;
392 
393   if (!sparta->collide || sparta->collide->vibstyle == NONE || vibdof < 2)
394     p->evib = 0.0;
395 
396   else if (sparta->collide->vibstyle == DISCRETE && vibdof == 2) {
397     double evib_star =
398       -log(1 - random->uniform() *
399            (1 - exp(-update->boltz*species[ispecies].vibtemp[0])));
400     evib_val = p->evib + evib_star;
401     evib_mag = sqrt(evib_val*(1-acc_vib)/(update->boltz*twall));
402     r_vib = sqrt(-acc_vib*log(random->uniform()));
403     cos_theta_vib = cos(MY_2PI*random->uniform());
404     evib_val = update->boltz * twall *
405       (r_vib*r_vib + evib_mag*evib_mag + 2*r_vib*evib_mag*cos_theta_vib);
406     int ivib =  evib_val / (update->boltz*species[ispecies].vibtemp[0]);
407     p->evib = ivib * update->boltz * species[ispecies].vibtemp[0];
408   }
409 
410   else if (sparta->collide->vibstyle == SMOOTH || vibdof >= 2) {
411     evib_mag = sqrt(p->evib*(1-acc_vib)/(update->boltz*twall));
412     if (vibdof == 2) {
413       r_vib = sqrt(-acc_vib*log(random->uniform()));
414       cos_theta_vib = cos(MY_2PI*random->uniform());
415     } else if (vibdof > 2) {
416       A_vib = 0;
417       while (A_vib < random->uniform()) {
418         X_vib = 4*random->uniform();
419         A_vib = 2.71828182845904523536028747*X_vib*X_vib*exp(-X_vib*X_vib);
420       }
421       r_vib = sqrt(acc_vib)*X_vib;
422       cos_theta_vib = 2*random->uniform() - 1;
423     }
424 
425     p->evib = update->boltz * twall *
426       (r_vib*r_vib + evib_mag*evib_mag + 2*r_vib*evib_mag*cos_theta_vib);
427   }
428 }
429 
430 /* ----------------------------------------------------------------------
431    wrapper on cll() method to perform collision for a single particle
432    pass in flags/coefficients to match command-line args for style cll
433    flags, coeffs can be NULL
434    called by SurfReactAdsorb
435 ------------------------------------------------------------------------- */
436 
wrapper(Particle::OnePart * p,double * norm,int * flags,double * coeffs)437 void SurfCollideCLL::wrapper(Particle::OnePart *p, double *norm,
438                              int *flags, double *coeffs)
439 {
440   if (flags) {
441     twall = coeffs[0];
442     acc_n = coeffs[1];
443     acc_t = coeffs[2];
444     acc_rot = coeffs[3];
445     acc_vib = coeffs[4];
446 
447     if (flags[0]) eccen = coeffs[5];
448     else eccen = 0.0;
449   }
450 
451   cll(p,norm);
452 }
453 
454 /* ----------------------------------------------------------------------
455    return flags and coeffs for this SurfCollide instance to caller
456 ------------------------------------------------------------------------- */
457 
flags_and_coeffs(int * flags,double * coeffs)458 void SurfCollideCLL::flags_and_coeffs(int *flags, double *coeffs)
459 {
460   coeffs[0] = twall;
461   coeffs[1] = acc_n;
462   coeffs[2] = acc_t;
463   coeffs[3] = acc_rot;
464   coeffs[4] = acc_vib;
465 
466   flags[0] = 0;
467   if (eccen != 0.0) {
468     flags[0] = 1;
469     coeffs[5] = eccen;
470   }
471 }
472 
473 /* ----------------------------------------------------------------------
474    set current surface temperature
475 ------------------------------------------------------------------------- */
476 
dynamic()477 void SurfCollideCLL::dynamic()
478 {
479   twall = input->variable->compute_equal(tvar);
480 }
481