Cantera  2.3.0
GasTransport.cpp
Go to the documentation of this file.
1 //! @file GasTransport.cpp
2 
3 // This file is part of Cantera. See License.txt in the top-level directory or
4 // at http://www.cantera.org/license.txt for license and copyright information.
5 
7 #include "MMCollisionInt.h"
11 
12 namespace Cantera
13 {
14 
15 //! polynomial degree used for fitting collision integrals
16 //! except in CK mode, where the degree is 6.
17 #define COLL_INT_POLY_DEGREE 8
18 
19 GasTransport::GasTransport(ThermoPhase* thermo) :
20  Transport(thermo),
21  m_viscmix(0.0),
22  m_visc_ok(false),
23  m_viscwt_ok(false),
24  m_spvisc_ok(false),
25  m_bindiff_ok(false),
26  m_mode(0),
27  m_polytempvec(5),
28  m_temp(-1.0),
29  m_kbt(0.0),
30  m_sqrt_kbt(0.0),
31  m_sqrt_t(0.0),
32  m_logt(0.0),
33  m_t14(0.0),
34  m_t32(0.0),
35  m_log_level(0)
36 {
37 }
38 
39 GasTransport::GasTransport(const GasTransport& right) :
40  m_viscmix(0.0),
41  m_visc_ok(false),
42  m_viscwt_ok(false),
43  m_spvisc_ok(false),
44  m_bindiff_ok(false),
45  m_mode(0),
46  m_polytempvec(5),
47  m_temp(-1.0),
48  m_kbt(0.0),
49  m_sqrt_kbt(0.0),
50  m_sqrt_t(0.0),
51  m_logt(0.0),
52  m_t14(0.0),
53  m_t32(0.0),
54  m_log_level(0)
55 {
56 }
57 
58 GasTransport& GasTransport::operator=(const GasTransport& right)
59 {
60  m_molefracs = right.m_molefracs;
61  m_viscmix = right.m_viscmix;
62  m_visc_ok = right.m_visc_ok;
63  m_viscwt_ok = right.m_viscwt_ok;
64  m_spvisc_ok = right.m_spvisc_ok;
65  m_bindiff_ok = right.m_bindiff_ok;
66  m_mode = right.m_mode;
67  m_phi = right.m_phi;
68  m_spwork = right.m_spwork;
69  m_visc = right.m_visc;
70  m_mw = right.m_mw;
71  m_wratjk = right.m_wratjk;
72  m_wratkj1 = right.m_wratkj1;
73  m_sqvisc = right.m_sqvisc;
74  m_polytempvec = right.m_polytempvec;
75  m_temp = right.m_temp;
76  m_kbt = right.m_kbt;
77  m_sqrt_kbt = right.m_sqrt_kbt;
78  m_sqrt_t = right.m_sqrt_t;
79  m_logt = right.m_logt;
80  m_t14 = right.m_t14;
81  m_t32 = right.m_t32;
82  m_diffcoeffs = right.m_diffcoeffs;
83  m_bdiff = right.m_bdiff;
84  m_condcoeffs = right.m_condcoeffs;
85  m_poly = right.m_poly;
86  m_omega22_poly = right.m_omega22_poly;
87  m_astar_poly = right.m_astar_poly;
88  m_bstar_poly = right.m_bstar_poly;
89  m_cstar_poly = right.m_cstar_poly;
90  m_zrot = right.m_zrot;
91  m_polar = right.m_polar;
92  m_alpha = right.m_alpha;
93  m_eps = right.m_eps;
94  m_sigma = right.m_sigma;
95  m_reducedMass = right.m_reducedMass;
96  m_diam = right.m_diam;
97  m_epsilon = right.m_epsilon;
98  m_dipole = right.m_dipole;
99  m_delta = right.m_delta;
100  m_w_ac = right.m_w_ac;
101  m_log_level = right.m_log_level;
102 
103  return *this;
104 }
105 
106 void GasTransport::update_T()
107 {
108  if (m_thermo->nSpecies() != m_nsp) {
109  // Rebuild data structures if number of species has changed
110  init(m_thermo, m_mode, m_log_level);
111  }
112 
113  double T = m_thermo->temperature();
114  if (T == m_temp) {
115  return;
116  }
117 
118  m_temp = T;
119  m_kbt = Boltzmann * m_temp;
120  m_sqrt_kbt = sqrt(Boltzmann*m_temp);
121  m_logt = log(m_temp);
122  m_sqrt_t = sqrt(m_temp);
123  m_t14 = sqrt(m_sqrt_t);
124  m_t32 = m_temp * m_sqrt_t;
125 
126  // compute powers of log(T)
127  m_polytempvec[0] = 1.0;
128  m_polytempvec[1] = m_logt;
129  m_polytempvec[2] = m_logt*m_logt;
130  m_polytempvec[3] = m_logt*m_logt*m_logt;
131  m_polytempvec[4] = m_logt*m_logt*m_logt*m_logt;
132 
133  // temperature has changed, so polynomial fits will need to be redone
134  m_visc_ok = false;
135  m_spvisc_ok = false;
136  m_viscwt_ok = false;
137  m_bindiff_ok = false;
138 }
139 
140 doublereal GasTransport::viscosity()
141 {
142  update_T();
143  update_C();
144 
145  if (m_visc_ok) {
146  return m_viscmix;
147  }
148 
149  doublereal vismix = 0.0;
150  // update m_visc and m_phi if necessary
151  if (!m_viscwt_ok) {
152  updateViscosity_T();
153  }
154 
155  multiply(m_phi, m_molefracs.data(), m_spwork.data());
156 
157  for (size_t k = 0; k < m_nsp; k++) {
158  vismix += m_molefracs[k] * m_visc[k]/m_spwork[k]; //denom;
159  }
160  m_viscmix = vismix;
161  return vismix;
162 }
163 
164 void GasTransport::updateViscosity_T()
165 {
166  if (!m_spvisc_ok) {
167  updateSpeciesViscosities();
168  }
169 
170  // see Eq. (9-5.15) of Reid, Prausnitz, and Poling
171  for (size_t j = 0; j < m_nsp; j++) {
172  for (size_t k = j; k < m_nsp; k++) {
173  double vratiokj = m_visc[k]/m_visc[j];
174  double wratiojk = m_mw[j]/m_mw[k];
175 
176  // Note that m_wratjk(k,j) holds the square root of m_wratjk(j,k)!
177  double factor1 = 1.0 + (m_sqvisc[k]/m_sqvisc[j]) * m_wratjk(k,j);
178  m_phi(k,j) = factor1*factor1 / (sqrt(8.0) * m_wratkj1(j,k));
179  m_phi(j,k) = m_phi(k,j)/(vratiokj * wratiojk);
180  }
181  }
182  m_viscwt_ok = true;
183 }
184 
185 void GasTransport::updateSpeciesViscosities()
186 {
187  update_T();
188  if (m_mode == CK_Mode) {
189  for (size_t k = 0; k < m_nsp; k++) {
190  m_visc[k] = exp(dot4(m_polytempvec, m_visccoeffs[k]));
191  m_sqvisc[k] = sqrt(m_visc[k]);
192  }
193  } else {
194  for (size_t k = 0; k < m_nsp; k++) {
195  // the polynomial fit is done for sqrt(visc/sqrt(T))
196  m_sqvisc[k] = m_t14 * dot5(m_polytempvec, m_visccoeffs[k]);
197  m_visc[k] = (m_sqvisc[k] * m_sqvisc[k]);
198  }
199  }
200  m_spvisc_ok = true;
201 }
202 
203 void GasTransport::updateDiff_T()
204 {
205  update_T();
206  // evaluate binary diffusion coefficients at unit pressure
207  size_t ic = 0;
208  if (m_mode == CK_Mode) {
209  for (size_t i = 0; i < m_nsp; i++) {
210  for (size_t j = i; j < m_nsp; j++) {
211  m_bdiff(i,j) = exp(dot4(m_polytempvec, m_diffcoeffs[ic]));
212  m_bdiff(j,i) = m_bdiff(i,j);
213  ic++;
214  }
215  }
216  } else {
217  for (size_t i = 0; i < m_nsp; i++) {
218  for (size_t j = i; j < m_nsp; j++) {
219  m_bdiff(i,j) = m_temp * m_sqrt_t*dot5(m_polytempvec,
220  m_diffcoeffs[ic]);
221  m_bdiff(j,i) = m_bdiff(i,j);
222  ic++;
223  }
224  }
225  }
226  m_bindiff_ok = true;
227 }
228 
229 void GasTransport::getBinaryDiffCoeffs(const size_t ld, doublereal* const d)
230 {
231  update_T();
232  // if necessary, evaluate the binary diffusion coefficients from the polynomial fits
233  if (!m_bindiff_ok) {
234  updateDiff_T();
235  }
236  if (ld < m_nsp) {
237  throw CanteraError(" MixTransport::getBinaryDiffCoeffs()", "ld is too small");
238  }
239  doublereal rp = 1.0/m_thermo->pressure();
240  for (size_t i = 0; i < m_nsp; i++) {
241  for (size_t j = 0; j < m_nsp; j++) {
242  d[ld*j + i] = rp * m_bdiff(i,j);
243  }
244  }
245 }
246 
247 void GasTransport::getMixDiffCoeffs(doublereal* const d)
248 {
249  update_T();
250  update_C();
251 
252  // update the binary diffusion coefficients if necessary
253  if (!m_bindiff_ok) {
254  updateDiff_T();
255  }
256 
257  doublereal mmw = m_thermo->meanMolecularWeight();
258  doublereal p = m_thermo->pressure();
259  if (m_nsp == 1) {
260  d[0] = m_bdiff(0,0) / p;
261  } else {
262  for (size_t k = 0; k < m_nsp; k++) {
263  double sum2 = 0.0;
264  for (size_t j = 0; j < m_nsp; j++) {
265  if (j != k) {
266  sum2 += m_molefracs[j] / m_bdiff(j,k);
267  }
268  }
269  if (sum2 <= 0.0) {
270  d[k] = m_bdiff(k,k) / p;
271  } else {
272  d[k] = (mmw - m_molefracs[k] * m_mw[k])/(p * mmw * sum2);
273  }
274  }
275  }
276 }
277 
278 void GasTransport::getMixDiffCoeffsMole(doublereal* const d)
279 {
280  update_T();
281  update_C();
282 
283  // update the binary diffusion coefficients if necessary
284  if (!m_bindiff_ok) {
285  updateDiff_T();
286  }
287 
288  doublereal p = m_thermo->pressure();
289  if (m_nsp == 1) {
290  d[0] = m_bdiff(0,0) / p;
291  } else {
292  for (size_t k = 0; k < m_nsp; k++) {
293  double sum2 = 0.0;
294  for (size_t j = 0; j < m_nsp; j++) {
295  if (j != k) {
296  sum2 += m_molefracs[j] / m_bdiff(j,k);
297  }
298  }
299  if (sum2 <= 0.0) {
300  d[k] = m_bdiff(k,k) / p;
301  } else {
302  d[k] = (1 - m_molefracs[k]) / (p * sum2);
303  }
304  }
305  }
306 }
307 
308 void GasTransport::getMixDiffCoeffsMass(doublereal* const d)
309 {
310  update_T();
311  update_C();
312 
313  // update the binary diffusion coefficients if necessary
314  if (!m_bindiff_ok) {
315  updateDiff_T();
316  }
317 
318  doublereal mmw = m_thermo->meanMolecularWeight();
319  doublereal p = m_thermo->pressure();
320 
321  if (m_nsp == 1) {
322  d[0] = m_bdiff(0,0) / p;
323  } else {
324  for (size_t k=0; k<m_nsp; k++) {
325  double sum1 = 0.0;
326  double sum2 = 0.0;
327  for (size_t i=0; i<m_nsp; i++) {
328  if (i==k) {
329  continue;
330  }
331  sum1 += m_molefracs[i] / m_bdiff(k,i);
332  sum2 += m_molefracs[i] * m_mw[i] / m_bdiff(k,i);
333  }
334  sum1 *= p;
335  sum2 *= p * m_molefracs[k] / (mmw - m_mw[k]*m_molefracs[k]);
336  d[k] = 1.0 / (sum1 + sum2);
337  }
338  }
339 }
340 
341 void GasTransport::init(thermo_t* thermo, int mode, int log_level)
342 {
343  m_thermo = thermo;
344  m_nsp = m_thermo->nSpecies();
345  m_mode = mode;
346  m_log_level = log_level;
347  // set up Monchick and Mason collision integrals
348  setupMM();
349 
350  m_molefracs.resize(m_nsp);
351  m_spwork.resize(m_nsp);
352  m_visc.resize(m_nsp);
353  m_sqvisc.resize(m_nsp);
354  m_phi.resize(m_nsp, m_nsp, 0.0);
355  m_bdiff.resize(m_nsp, m_nsp);
356 
357  // make a local copy of the molecular weights
358  m_mw = m_thermo->molecularWeights();
359 
360  m_wratjk.resize(m_nsp, m_nsp, 0.0);
361  m_wratkj1.resize(m_nsp, m_nsp, 0.0);
362  for (size_t j = 0; j < m_nsp; j++) {
363  for (size_t k = j; k < m_nsp; k++) {
364  m_wratjk(j,k) = sqrt(m_mw[j]/m_mw[k]);
365  m_wratjk(k,j) = sqrt(m_wratjk(j,k));
366  m_wratkj1(j,k) = sqrt(1.0 + m_mw[k]/m_mw[j]);
367  }
368  }
369 
370  // set flags all false
371  m_visc_ok = false;
372  m_viscwt_ok = false;
373  m_spvisc_ok = false;
374  m_bindiff_ok = false;
375 }
376 
377 void GasTransport::setupMM()
378 {
379  m_epsilon.resize(m_nsp, m_nsp, 0.0);
380  m_delta.resize(m_nsp, m_nsp, 0.0);
381  m_reducedMass.resize(m_nsp, m_nsp, 0.0);
382  m_dipole.resize(m_nsp, m_nsp, 0.0);
383  m_diam.resize(m_nsp, m_nsp, 0.0);
384  m_crot.resize(m_nsp);
385  m_zrot.resize(m_nsp);
386  m_polar.resize(m_nsp, false);
387  m_alpha.resize(m_nsp, 0.0);
388  m_poly.resize(m_nsp);
389  m_sigma.resize(m_nsp);
390  m_eps.resize(m_nsp);
391  m_w_ac.resize(m_nsp);
392 
393  const vector_fp& mw = m_thermo->molecularWeights();
394  getTransportData();
395 
396  for (size_t i = 0; i < m_nsp; i++) {
397  m_poly[i].resize(m_nsp);
398  }
399 
400  double tstar_min = 1.e8, tstar_max = 0.0;
401  double f_eps, f_sigma;
402 
403  for (size_t i = 0; i < m_nsp; i++) {
404  for (size_t j = i; j < m_nsp; j++) {
405  // the reduced mass
406  m_reducedMass(i,j) = mw[i] * mw[j] / (Avogadro * (mw[i] + mw[j]));
407 
408  // hard-sphere diameter for (i,j) collisions
409  m_diam(i,j) = 0.5*(m_sigma[i] + m_sigma[j]);
410 
411  // the effective well depth for (i,j) collisions
412  m_epsilon(i,j) = sqrt(m_eps[i]*m_eps[j]);
413 
414  // The polynomial fits of collision integrals vs. T*
415  // will be done for the T* from tstar_min to tstar_max
416  tstar_min = std::min(tstar_min, Boltzmann * m_thermo->minTemp()/m_epsilon(i,j));
417  tstar_max = std::max(tstar_max, Boltzmann * m_thermo->maxTemp()/m_epsilon(i,j));
418 
419  // the effective dipole moment for (i,j) collisions
420  m_dipole(i,j) = sqrt(m_dipole(i,i)*m_dipole(j,j));
421 
422  // reduced dipole moment delta* (nondimensional)
423  double d = m_diam(i,j);
424  m_delta(i,j) = 0.5 * m_dipole(i,j)*m_dipole(i,j)
425  / (4 * Pi * epsilon_0 * m_epsilon(i,j) * d * d * d);
426  makePolarCorrections(i, j, f_eps, f_sigma);
427  m_diam(i,j) *= f_sigma;
428  m_epsilon(i,j) *= f_eps;
429 
430  // properties are symmetric
431  m_reducedMass(j,i) = m_reducedMass(i,j);
432  m_diam(j,i) = m_diam(i,j);
433  m_epsilon(j,i) = m_epsilon(i,j);
434  m_dipole(j,i) = m_dipole(i,j);
435  m_delta(j,i) = m_delta(i,j);
436  }
437  }
438 
439  // Chemkin fits the entire T* range in the Monchick and Mason tables,
440  // so modify tstar_min and tstar_max if in Chemkin compatibility mode
441  if (m_mode == CK_Mode) {
442  tstar_min = 0.101;
443  tstar_max = 99.9;
444  }
445 
446  // initialize the collision integral calculator for the desired T* range
447  debuglog("*** collision_integrals ***\n", m_log_level);
448  MMCollisionInt integrals;
449  integrals.init(tstar_min, tstar_max, m_log_level);
450  fitCollisionIntegrals(integrals);
451  debuglog("*** end of collision_integrals ***\n", m_log_level);
452  // make polynomial fits
453  debuglog("*** property fits ***\n", m_log_level);
454  fitProperties(integrals);
455  debuglog("*** end of property fits ***\n", m_log_level);
456 }
457 
458 void GasTransport::getTransportData()
459 {
460  for (size_t k = 0; k < m_thermo->nSpecies(); k++) {
461  shared_ptr<Species> s = m_thermo->species(m_thermo->speciesName(k));
462  const GasTransportData* sptran =
463  dynamic_cast<GasTransportData*>(s->transport.get());
464  if (!sptran) {
465  throw CanteraError("GasTransport::getTransportData",
466  "Missing gas-phase transport data for species '{}'.", s->name);
467  }
468 
469  if (sptran->geometry == "atom") {
470  m_crot[k] = 0.0;
471  } else if (sptran->geometry == "linear") {
472  m_crot[k] = 1.0;
473  } else if (sptran->geometry == "nonlinear") {
474  m_crot[k] = 1.5;
475  }
476 
477  m_sigma[k] = sptran->diameter;
478  m_eps[k] = sptran->well_depth;
479  m_dipole(k,k) = sptran->dipole;
480  m_polar[k] = (sptran->dipole > 0);
481  m_alpha[k] = sptran->polarizability;
482  m_zrot[k] = sptran->rotational_relaxation;
483  m_w_ac[k] = sptran->acentric_factor;
484  }
485 }
486 
487 void GasTransport::makePolarCorrections(size_t i, size_t j,
488  doublereal& f_eps, doublereal& f_sigma)
489 {
490  // no correction if both are nonpolar, or both are polar
491  if (m_polar[i] == m_polar[j]) {
492  f_eps = 1.0;
493  f_sigma = 1.0;
494  return;
495  }
496 
497  // corrections to the effective diameter and well depth
498  // if one is polar and one is non-polar
499  size_t kp = (m_polar[i] ? i : j); // the polar one
500  size_t knp = (i == kp ? j : i); // the nonpolar one
501  double d3np, d3p, alpha_star, mu_p_star, xi;
502  d3np = pow(m_sigma[knp],3);
503  d3p = pow(m_sigma[kp],3);
504  alpha_star = m_alpha[knp]/d3np;
505  mu_p_star = m_dipole(kp,kp)/sqrt(4 * Pi * epsilon_0 * d3p * m_eps[kp]);
506  xi = 1.0 + 0.25 * alpha_star * mu_p_star * mu_p_star *
507  sqrt(m_eps[kp]/m_eps[knp]);
508  f_sigma = pow(xi, -1.0/6.0);
509  f_eps = xi*xi;
510 }
511 
512 void GasTransport::fitCollisionIntegrals(MMCollisionInt& integrals)
513 {
514  // Chemkin fits to sixth order polynomials
515  int degree = (m_mode == CK_Mode ? 6 : COLL_INT_POLY_DEGREE);
516  if (m_log_level) {
517  writelog("tstar_fits\n"
518  "fits to A*, B*, and C* vs. log(T*).\n"
519  "These are done only for the required dstar(j,k) values.\n\n");
520  if (m_log_level < 3) {
521  writelog("*** polynomial coefficients not printed (log_level < 3) ***\n");
522  }
523  }
524  vector_fp fitlist;
525  m_omega22_poly.clear();
526  m_astar_poly.clear();
527  m_bstar_poly.clear();
528  m_cstar_poly.clear();
529  for (size_t i = 0; i < m_nsp; i++) {
530  for (size_t j = i; j < m_nsp; j++) {
531  // Chemkin fits only delta* = 0
532  double dstar = (m_mode != CK_Mode) ? m_delta(i,j) : 0.0;
533 
534  // if a fit has already been generated for delta* = m_delta(i,j),
535  // then use it. Otherwise, make a new fit, and add m_delta(i,j) to
536  // the list of delta* values for which fits have been done.
537 
538  // 'find' returns a pointer to end() if not found
539  auto dptr = find(fitlist.begin(), fitlist.end(), dstar);
540  if (dptr == fitlist.end()) {
541  vector_fp ca(degree+1), cb(degree+1), cc(degree+1);
542  vector_fp co22(degree+1);
543  integrals.fit(degree, dstar, ca.data(), cb.data(), cc.data());
544  integrals.fit_omega22(degree, dstar, co22.data());
545  m_omega22_poly.push_back(co22);
546  m_astar_poly.push_back(ca);
547  m_bstar_poly.push_back(cb);
548  m_cstar_poly.push_back(cc);
549  m_poly[i][j] = static_cast<int>(m_astar_poly.size()) - 1;
550  fitlist.push_back(dstar);
551  } else {
552  // delta* found in fitlist, so just point to this polynomial
553  m_poly[i][j] = static_cast<int>((dptr - fitlist.begin()));
554  }
555  m_poly[j][i] = m_poly[i][j];
556  }
557  }
558 }
559 
560 void GasTransport::fitProperties(MMCollisionInt& integrals)
561 {
562  // number of points to use in generating fit data
563  const size_t np = 50;
564  int degree = (m_mode == CK_Mode ? 3 : 4);
565  double dt = (m_thermo->maxTemp() - m_thermo->minTemp())/(np-1);
566  vector_fp tlog(np), spvisc(np), spcond(np);
567  vector_fp w(np), w2(np);
568 
569  m_visccoeffs.clear();
570  m_condcoeffs.clear();
571 
572  // generate array of log(t) values
573  for (size_t n = 0; n < np; n++) {
574  double t = m_thermo->minTemp() + dt*n;
575  tlog[n] = log(t);
576  }
577 
578  // vector of polynomial coefficients
579  vector_fp c(degree + 1), c2(degree + 1);
580 
581  // fit the pure-species viscosity and thermal conductivity for each species
582  if (m_log_level && m_log_level < 2) {
583  writelog("*** polynomial coefficients not printed (log_level < 2) ***\n");
584  }
585  double visc, err, relerr,
586  mxerr = 0.0, mxrelerr = 0.0, mxerr_cond = 0.0, mxrelerr_cond = 0.0;
587 
588  if (m_log_level) {
589  writelog("Polynomial fits for viscosity:\n");
590  if (m_mode == CK_Mode) {
591  writelog("log(viscosity) fit to cubic polynomial in log(T)\n");
592  } else {
593  writelogf("viscosity/sqrt(T) fit to polynomial of degree "
594  "%d in log(T)", degree);
595  }
596  }
597 
598  double T_save = m_thermo->temperature();
599  const vector_fp& mw = m_thermo->molecularWeights();
600  for (size_t k = 0; k < m_nsp; k++) {
601  for (size_t n = 0; n < np; n++) {
602  double t = m_thermo->minTemp() + dt*n;
603  m_thermo->setTemperature(t);
604  vector_fp cp_R_all(m_thermo->nSpecies());
605  m_thermo->getCp_R_ref(&cp_R_all[0]);
606  double cp_R = cp_R_all[k];
607  double tstar = Boltzmann * t/ m_eps[k];
608  double sqrt_T = sqrt(t);
609  double om22 = integrals.omega22(tstar, m_delta(k,k));
610  double om11 = integrals.omega11(tstar, m_delta(k,k));
611 
612  // self-diffusion coefficient, without polar corrections
613  double diffcoeff = 3.0/16.0 * sqrt(2.0 * Pi/m_reducedMass(k,k)) *
614  pow((Boltzmann * t), 1.5)/
615  (Pi * m_sigma[k] * m_sigma[k] * om11);
616 
617  // viscosity
618  visc = 5.0/16.0 * sqrt(Pi * mw[k] * Boltzmann * t / Avogadro) /
619  (om22 * Pi * m_sigma[k]*m_sigma[k]);
620 
621  // thermal conductivity
622  double f_int = mw[k]/(GasConstant * t) * diffcoeff/visc;
623  double cv_rot = m_crot[k];
624  double A_factor = 2.5 - f_int;
625  double B_factor = m_zrot[k] + 2.0/Pi * (5.0/3.0 * cv_rot + f_int);
626  double c1 = 2.0/Pi * A_factor/B_factor;
627  double cv_int = cp_R - 2.5 - cv_rot;
628  double f_rot = f_int * (1.0 + c1);
629  double f_trans = 2.5 * (1.0 - c1 * cv_rot/1.5);
630  double cond = (visc/mw[k])*GasConstant*(f_trans * 1.5
631  + f_rot * cv_rot + f_int * cv_int);
632 
633  if (m_mode == CK_Mode) {
634  spvisc[n] = log(visc);
635  spcond[n] = log(cond);
636  w[n] = -1.0;
637  w2[n] = -1.0;
638  } else {
639  // the viscosity should be proportional approximately to
640  // sqrt(T); therefore, visc/sqrt(T) should have only a weak
641  // temperature dependence. And since the mixture rule requires
642  // the square root of the pure-species viscosity, fit the square
643  // root of (visc/sqrt(T)) to avoid having to compute square
644  // roots in the mixture rule.
645  spvisc[n] = sqrt(visc/sqrt_T);
646 
647  // the pure-species conductivity scales approximately with
648  // sqrt(T). Unlike the viscosity, there is no reason here to fit
649  // the square root, since a different mixture rule is used.
650  spcond[n] = cond/sqrt_T;
651  w[n] = 1.0/(spvisc[n]*spvisc[n]);
652  w2[n] = 1.0/(spcond[n]*spcond[n]);
653  }
654  }
655  polyfit(np, degree, tlog.data(), spvisc.data(), w.data(), c.data());
656  polyfit(np, degree, tlog.data(), spcond.data(), w2.data(), c2.data());
657 
658  // evaluate max fit errors for viscosity
659  for (size_t n = 0; n < np; n++) {
660  double val, fit;
661  if (m_mode == CK_Mode) {
662  val = exp(spvisc[n]);
663  fit = exp(poly3(tlog[n], c.data()));
664  } else {
665  double sqrt_T = exp(0.5*tlog[n]);
666  val = sqrt_T * pow(spvisc[n],2);
667  fit = sqrt_T * pow(poly4(tlog[n], c.data()),2);
668  }
669  err = fit - val;
670  relerr = err/val;
671  mxerr = std::max(mxerr, fabs(err));
672  mxrelerr = std::max(mxrelerr, fabs(relerr));
673  }
674 
675  // evaluate max fit errors for conductivity
676  for (size_t n = 0; n < np; n++) {
677  double val, fit;
678  if (m_mode == CK_Mode) {
679  val = exp(spcond[n]);
680  fit = exp(poly3(tlog[n], c2.data()));
681  } else {
682  double sqrt_T = exp(0.5*tlog[n]);
683  val = sqrt_T * spcond[n];
684  fit = sqrt_T * poly4(tlog[n], c2.data());
685  }
686  err = fit - val;
687  relerr = err/val;
688  mxerr_cond = std::max(mxerr_cond, fabs(err));
689  mxrelerr_cond = std::max(mxrelerr_cond, fabs(relerr));
690  }
691  m_visccoeffs.push_back(c);
692  m_condcoeffs.push_back(c2);
693 
694  if (m_log_level >= 2) {
695  writelog(m_thermo->speciesName(k) + ": [" + vec2str(c) + "]\n");
696  }
697  }
698  m_thermo->setTemperature(T_save);
699 
700  if (m_log_level) {
701  writelogf("Maximum viscosity absolute error: %12.6g\n", mxerr);
702  writelogf("Maximum viscosity relative error: %12.6g\n", mxrelerr);
703  writelog("\nPolynomial fits for conductivity:\n");
704  if (m_mode == CK_Mode) {
705  writelog("log(conductivity) fit to cubic polynomial in log(T)");
706  } else {
707  writelogf("conductivity/sqrt(T) fit to "
708  "polynomial of degree %d in log(T)", degree);
709  }
710  if (m_log_level >= 2) {
711  for (size_t k = 0; k < m_nsp; k++) {
712  writelog(m_thermo->speciesName(k) + ": [" +
713  vec2str(m_condcoeffs[k]) + "]\n");
714  }
715  }
716  writelogf("Maximum conductivity absolute error: %12.6g\n", mxerr_cond);
717  writelogf("Maximum conductivity relative error: %12.6g\n", mxrelerr_cond);
718 
719  // fit the binary diffusion coefficients for each species pair
720  writelogf("\nbinary diffusion coefficients:\n");
721  if (m_mode == CK_Mode) {
722  writelog("log(D) fit to cubic polynomial in log(T)");
723  } else {
724  writelogf("D/T**(3/2) fit to polynomial of degree %d in log(T)",degree);
725  }
726  }
727 
728  mxerr = 0.0, mxrelerr = 0.0;
729  vector_fp diff(np + 1);
730  m_diffcoeffs.clear();
731  for (size_t k = 0; k < m_nsp; k++) {
732  for (size_t j = k; j < m_nsp; j++) {
733  for (size_t n = 0; n < np; n++) {
734  double t = m_thermo->minTemp() + dt*n;
735  double eps = m_epsilon(j,k);
736  double tstar = Boltzmann * t/eps;
737  double sigma = m_diam(j,k);
738  double om11 = integrals.omega11(tstar, m_delta(j,k));
739  double diffcoeff = 3.0/16.0 * sqrt(2.0 * Pi/m_reducedMass(k,j))
740  * pow(Boltzmann * t, 1.5) / (Pi * sigma * sigma * om11);
741 
742  // 2nd order correction
743  // NOTE: THIS CORRECTION IS NOT APPLIED
744  double fkj, fjk;
745  getBinDiffCorrection(t, integrals, k, j, 1.0, 1.0, fkj, fjk);
746 
747  if (m_mode == CK_Mode) {
748  diff[n] = log(diffcoeff);
749  w[n] = -1.0;
750  } else {
751  diff[n] = diffcoeff/pow(t, 1.5);
752  w[n] = 1.0/(diff[n]*diff[n]);
753  }
754  }
755  polyfit(np, degree, tlog.data(), diff.data(), w.data(), c.data());
756 
757  for (size_t n = 0; n < np; n++) {
758  double val, fit;
759  if (m_mode == CK_Mode) {
760  val = exp(diff[n]);
761  fit = exp(poly3(tlog[n], c.data()));
762  } else {
763  double t = exp(tlog[n]);
764  double pre = pow(t, 1.5);
765  val = pre * diff[n];
766  fit = pre * poly4(tlog[n], c.data());
767  }
768  err = fit - val;
769  relerr = err/val;
770  mxerr = std::max(mxerr, fabs(err));
771  mxrelerr = std::max(mxrelerr, fabs(relerr));
772  }
773  m_diffcoeffs.push_back(c);
774  if (m_log_level >= 2) {
775  writelog(m_thermo->speciesName(k) + "__" +
776  m_thermo->speciesName(j) + ": [" + vec2str(c) + "]\n");
777  }
778  }
779  }
780  if (m_log_level) {
781  writelogf("Maximum binary diffusion coefficient absolute error:"
782  " %12.6g\n", mxerr);
783  writelogf("Maximum binary diffusion coefficient relative error:"
784  "%12.6g", mxrelerr);
785  }
786 }
787 
788 void GasTransport::getBinDiffCorrection(double t, MMCollisionInt& integrals,
789  size_t k, size_t j, double xk, double xj, double& fkj, double& fjk)
790 {
791  double w1 = m_thermo->molecularWeight(k);
792  double w2 = m_thermo->molecularWeight(j);
793  double wsum = w1 + w2;
794  double wmwp = (w1 - w2)/wsum;
795  double sqw12 = sqrt(w1*w2);
796  double sig1 = m_sigma[k];
797  double sig2 = m_sigma[j];
798  double sig12 = 0.5*(m_sigma[k] + m_sigma[j]);
799  double sigratio = sig1*sig1/(sig2*sig2);
800  double sigratio2 = sig1*sig1/(sig12*sig12);
801  double sigratio3 = sig2*sig2/(sig12*sig12);
802  double tstar1 = Boltzmann * t / m_eps[k];
803  double tstar2 = Boltzmann * t / m_eps[j];
804  double tstar12 = Boltzmann * t / sqrt(m_eps[k] * m_eps[j]);
805  double om22_1 = integrals.omega22(tstar1, m_delta(k,k));
806  double om22_2 = integrals.omega22(tstar2, m_delta(j,j));
807  double om11_12 = integrals.omega11(tstar12, m_delta(k,j));
808  double astar_12 = integrals.astar(tstar12, m_delta(k,j));
809  double bstar_12 = integrals.bstar(tstar12, m_delta(k,j));
810  double cstar_12 = integrals.cstar(tstar12, m_delta(k,j));
811 
812  double cnst = sigratio * sqrt(2.0*w2/wsum) * 2.0 * w1*w1/(wsum * w2);
813  double p1 = cnst * om22_1 / om11_12;
814 
815  cnst = (1.0/sigratio) * sqrt(2.0*w1/wsum) * 2.0*w2*w2/(wsum*w1);
816  double p2 = cnst * om22_2 / om11_12;
817  double p12 = 15.0 * wmwp*wmwp + 8.0*w1*w2*astar_12/(wsum*wsum);
818 
819  cnst = (2.0/(w2*wsum))*sqrt(2.0*w2/wsum)*sigratio2;
820  double q1 = cnst*((2.5 - 1.2*bstar_12)*w1*w1 + 3.0*w2*w2
821  + 1.6*w1*w2*astar_12);
822 
823  cnst = (2.0/(w1*wsum))*sqrt(2.0*w1/wsum)*sigratio3;
824  double q2 = cnst*((2.5 - 1.2*bstar_12)*w2*w2 + 3.0*w1*w1
825  + 1.6*w1*w2*astar_12);
826  double q12 = wmwp*wmwp*15.0*(2.5 - 1.2*bstar_12)
827  + 4.0*w1*w2*astar_12*(11.0 - 2.4*bstar_12)/(wsum*wsum)
828  + 1.6*wsum*om22_1*om22_2/(om11_12*om11_12*sqw12)
829  * sigratio2 * sigratio3;
830 
831  cnst = 6.0*cstar_12 - 5.0;
832  fkj = 1.0 + 0.1*cnst*cnst *
833  (p1*xk*xk + p2*xj*xj + p12*xk*xj)/
834  (q1*xk*xk + q2*xj*xj + q12*xk*xj);
835  fjk = 1.0 + 0.1*cnst*cnst *
836  (p2*xk*xk + p1*xj*xj + p12*xk*xj)/
837  (q2*xk*xk + q1*xj*xj + q12*xk*xj);
838 }
839 
840 }
Transport data for a single gas-phase species which can be used in mixture-averaged or multicomponent...
Definition: TransportData.h:29
R poly3(D x, R *c)
Templated evaluation of a polynomial of order 3.
Definition: utilities.h:479
void init(doublereal tsmin, doublereal tsmax, int loglevel=0)
Initialize the object for calculation.
std::string vec2str(const vector_fp &v, const std::string &fmt, const std::string &sep)
Convert a vector to a string (separated by commas)
Definition: stringUtils.cpp:69
doublereal dot4(const V &x, const V &y)
Templated Inner product of two vectors of length 4.
Definition: utilities.h:65
void writelog(const std::string &fmt, const Args &... args)
Write a formatted message to the screen.
Definition: global.h:179
size_t nSpecies() const
Returns the number of species in the phase.
Definition: Phase.h:262
#define COLL_INT_POLY_DEGREE
polynomial degree used for fitting collision integrals except in CK mode, where the degree is 6...
double rotational_relaxation
The rotational relaxation number (the number of collisions it takes to equilibrate the rotational deg...
Definition: TransportData.h:73
const doublereal Pi
Pi.
Definition: ct_defs.h:51
double well_depth
The Lennard-Jones well depth [J].
Definition: TransportData.h:62
Monk and Monchick collision integrals.
R poly4(D x, R *c)
Evaluates a polynomial of order 4.
Definition: utilities.h:467
Base class for a phase with thermodynamic properties.
Definition: ThermoPhase.h:93
double diameter
The Lennard-Jones collision diameter [m].
Definition: TransportData.h:59
double polyfit(int n, double *xp, double *yp, double *wp, int deg, int &ndeg, double eps, double *rp)
Fits a polynomial function to a set of data points.
Definition: polyfit.cpp:14
void multiply(const DenseMatrix &A, const double *const b, double *const prod)
Multiply A*b and return the result in prod. Uses BLAS routine DGEMV.
Base class for exceptions thrown by Cantera classes.
Definition: ctexceptions.h:65
double dipole
The permanent dipole moment of the molecule [Coulomb-m]. Default 0.0.
Definition: TransportData.h:65
void debuglog(const std::string &msg, int loglevel)
Write a message to the log only if loglevel > 0.
Definition: global.h:161
void writelogf(const char *fmt, const Args &... args)
Write a formatted message to the screen.
Definition: global.h:200
const doublereal Avogadro
Avogadro&#39;s Number [number/kmol].
Definition: ct_defs.h:61
doublereal dot5(const V &x, const V &y)
Templated Inner product of two vectors of length 5.
Definition: utilities.h:80
std::vector< double > vector_fp
Turn on the use of stl vectors for the basic array type within cantera Vector of doubles.
Definition: ct_defs.h:157
Contains declarations for string manipulation functions within Cantera.
std::string geometry
A string specifying the molecular geometry.
Definition: TransportData.h:56
const doublereal epsilon_0
Permittivity of free space in F/m.
Definition: ct_defs.h:106
double polarizability
The polarizability of the molecule [m^3]. Default 0.0.
Definition: TransportData.h:68
Calculation of Collision integrals.
double acentric_factor
Pitzer&#39;s acentric factor [dimensionless]. Default 0.0.
Definition: TransportData.h:76
Namespace for the Cantera kernel.
Definition: application.cpp:29
const doublereal Boltzmann
Boltzmann&#39;s constant [J/K].
Definition: ct_defs.h:76