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