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