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