Cantera 2.6.0
IonGasTransport.cpp
Go to the documentation of this file.
1//! @file IonGasTransport.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
11#include "cantera/base/global.h"
12#include "MMCollisionInt.h"
13
14namespace Cantera
15{
16IonGasTransport::IonGasTransport() :
17 m_kElectron(npos)
18{
19}
20
21void IonGasTransport::init(ThermoPhase* thermo, int mode, int log_level)
22{
23 m_thermo = thermo;
24 m_nsp = m_thermo->nSpecies();
25 m_mode = mode;
26 if (m_mode == CK_Mode) {
27 throw CanteraError("IonGasTransport::init",
28 "mode = CK_Mode, which is an outdated lower-order fit.");
29 }
30 m_log_level = log_level;
31 // make a local copy of species charge
32 for (size_t k = 0; k < m_nsp; k++) {
33 m_speciesCharge.push_back(m_thermo->charge(k));
34 }
35
36 // Find the index of electron
37 size_t nElectronSpecies = 0;
38 for (size_t k = 0; k < m_nsp; k++) {
39 if (m_thermo->molecularWeight(k) ==
40 m_thermo->atomicWeight(m_thermo->elementIndex("E")) &&
41 m_thermo->charge(k) == -1) {
42 m_kElectron = k;
43 nElectronSpecies++;
44 }
45 }
46 if (nElectronSpecies > 1) {
47 throw CanteraError("IonGasTransport::init",
48 "Found multiple electron species.");
49 }
50
51 // Find indices for charge of species
52 for (size_t k = 0; k < m_nsp; k++) {
53 if (m_speciesCharge[k] != 0){
54 if (k != m_kElectron) {
55 m_kIon.push_back(k);
56 }
57 } else {
58 m_kNeutral.push_back(k);
59 }
60 }
61 // set up O2/O2- collision integral [A^2]
62 // Data taken from Prager (2005)
63 const vector_fp temp{300.0, 400.0, 500.0, 600.0, 800.0, 1000.0,
64 1200.0, 1500.0, 2000.0, 2500.0, 3000.0, 4000.0};
65 const vector_fp om11_O2{120.0, 107.0, 98.1, 92.1, 83.0, 77.0,
66 72.6, 67.9, 62.7, 59.3, 56.7, 53.8};
67 vector_fp w(temp.size(),-1);
68 int degree = 5;
69 m_om11_O2.resize(degree + 1);
70 polyfit(temp.size(), degree, temp.data(), om11_O2.data(),
71 w.data(), m_om11_O2.data());
72 // set up Monchick and Mason parameters
73 setupCollisionParameters();
74 // set up n64 parameters
75 setupN64();
76 // setup collision integrals
77 setupCollisionIntegral();
78 m_molefracs.resize(m_nsp);
79 m_spwork.resize(m_nsp);
80 m_visc.resize(m_nsp);
81 m_sqvisc.resize(m_nsp);
82 m_phi.resize(m_nsp, m_nsp, 0.0);
83 m_bdiff.resize(m_nsp, m_nsp);
84 m_cond.resize(m_nsp);
85
86 // make a local copy of the molecular weights
87 m_mw = m_thermo->molecularWeights();
88
89 m_wratjk.resize(m_nsp, m_nsp, 0.0);
90 m_wratkj1.resize(m_nsp, m_nsp, 0.0);
91 for (size_t j = 0; j < m_nsp; j++) {
92 for (size_t k = j; k < m_nsp; k++) {
93 m_wratjk(j,k) = sqrt(m_mw[j]/m_mw[k]);
94 m_wratjk(k,j) = sqrt(m_wratjk(j,k));
95 m_wratkj1(j,k) = sqrt(1.0 + m_mw[k]/m_mw[j]);
96 }
97 }
98}
99
100double IonGasTransport::viscosity()
101{
102 update_T();
103 update_C();
104
105 if (m_visc_ok) {
106 return m_viscmix;
107 }
108
109 double vismix = 0.0;
110 // update m_visc and m_phi if necessary
111 if (!m_viscwt_ok) {
112 updateViscosity_T();
113 }
114
115 multiply(m_phi, m_molefracs.data(), m_spwork.data());
116
117 for (size_t k : m_kNeutral) {
118 vismix += m_molefracs[k] * m_visc[k]/m_spwork[k]; //denom;
119 }
120 m_viscmix = vismix;
121 return vismix;
122}
123
124double IonGasTransport::thermalConductivity()
125{
126 update_T();
127 update_C();
128 if (!m_spcond_ok) {
129 updateCond_T();
130 }
131 if (!m_condmix_ok) {
132 doublereal sum1 = 0.0, sum2 = 0.0;
133 for (size_t k : m_kNeutral) {
134 sum1 += m_molefracs[k] * m_cond[k];
135 sum2 += m_molefracs[k] / m_cond[k];
136 }
137 m_lambda = 0.5*(sum1 + 1.0/sum2);
138 m_condmix_ok = true;
139 }
140 return m_lambda;
141}
142
143double IonGasTransport::electricalConductivity()
144{
145 vector_fp mobi(m_nsp);
146 getMobilities(&mobi[0]);
147 double p = m_thermo->pressure();
148 double sum = 0.0;
149 for (size_t k : m_kIon) {
150 double ND_k = m_molefracs[k] * p / m_kbt;
151 sum += ND_k * std::abs(m_speciesCharge[k]) * ElectronCharge * mobi[k];
152 }
153 if (m_kElectron != npos) {
154 sum += m_molefracs[m_kElectron] * p / m_kbt *
155 ElectronCharge * mobi[m_kElectron];
156 }
157 return sum;
158}
159
160void IonGasTransport::fitDiffCoeffs(MMCollisionInt& integrals)
161{
162 GasTransport::fitDiffCoeffs(integrals);
163
164 // number of points to use in generating fit data
165 const size_t np = 50;
166 int degree = 4;
167 double dt = (m_thermo->maxTemp() - m_thermo->minTemp())/(np-1);
168 vector_fp tlog(np);
169 vector_fp w(np);
170
171 // generate array of log(t) values
172 for (size_t n = 0; n < np; n++) {
173 double t = m_thermo->minTemp() + dt*n;
174 tlog[n] = log(t);
175 }
176
177 // vector of polynomial coefficients
178 vector_fp c(degree + 1);
179 double err = 0.0, relerr = 0.0,
180 mxerr = 0.0, mxrelerr = 0.0;
181
182 vector_fp diff(np + 1);
183 // The array order still not ideal
184 for (size_t k = 0; k < m_nsp; k++) {
185 for (size_t j = k; j < m_nsp; j++) {
186 if (m_alpha[k] == 0.0 || m_alpha[j] == 0.0 ||
187 k == m_kElectron || j == m_kElectron) {
188 continue;
189 }
190 if (m_speciesCharge[k] == 0) {
191 if (m_speciesCharge[j] == 0) {
192 continue;
193 }
194 } else {
195 if (m_speciesCharge[j] != 0) {
196 continue;
197 }
198 }
199 for (size_t n = 0; n < np; n++) {
200 double t = m_thermo->minTemp() + dt*n;
201 double eps = m_epsilon(j,k);
202 double tstar = Boltzmann * t/eps;
203 double sigma = m_diam(j,k);
204 double om11 = omega11_n64(tstar, m_gamma(j,k))
205 * Pi * sigma * sigma;
206 // Stockmayer-(n,6,4) model is not suitable for collision
207 // between O2/O2- due to resonant charge transfer.
208 // Therefore, the experimental collision data is used instead.
209 if ((k == m_thermo->speciesIndex("O2-") ||
210 j == m_thermo->speciesIndex("O2-")) &&
211 (k == m_thermo->speciesIndex("O2") ||
212 j == m_thermo->speciesIndex("O2"))) {
213 om11 = poly5(t, m_om11_O2.data()) / 1e20;
214 }
215 double diffcoeff = 3.0/16.0 * sqrt(2.0 * Pi/m_reducedMass(k,j))
216 * pow(Boltzmann * t, 1.5) / om11;
217
218 diff[n] = diffcoeff/pow(t, 1.5);
219 w[n] = 1.0/(diff[n]*diff[n]);
220 }
221 polyfit(np, degree, tlog.data(), diff.data(), w.data(), c.data());
222
223 for (size_t n = 0; n < np; n++) {
224 double val, fit;
225 double t = exp(tlog[n]);
226 double pre = pow(t, 1.5);
227 val = pre * diff[n];
228 fit = pre * poly4(tlog[n], c.data());
229 err = fit - val;
230 relerr = err/val;
231 mxerr = std::max(mxerr, fabs(err));
232 mxrelerr = std::max(mxrelerr, fabs(relerr));
233 }
234 size_t sum = k * (k + 1) / 2;
235 m_diffcoeffs[k*m_nsp+j-sum] = c;
236 if (m_log_level >= 2) {
237 writelog(m_thermo->speciesName(k) + "__" +
238 m_thermo->speciesName(j) + ": [" + vec2str(c) + "]\n");
239 }
240 }
241 }
242
243 if (m_log_level) {
244 writelogf("Maximum binary diffusion coefficient absolute error:"
245 " %12.6g\n", mxerr);
246 writelogf("Maximum binary diffusion coefficient relative error:"
247 "%12.6g", mxrelerr);
248 }
249}
250
251void IonGasTransport::setupN64()
252{
253 m_gamma.resize(m_nsp, m_nsp, 0.0);
254 for (size_t i : m_kIon) {
255 for (size_t j : m_kNeutral) {
256 if (m_alpha[j] != 0.0 && m_alpha[i] != 0.0) {
257 double r_alpha = m_alpha[i] / m_alpha[j];
258 // save a copy of polarizability in Angstrom
259 double alphaA_i = m_alpha[i] * 1e30;
260 double alphaA_j = m_alpha[j] * 1e30;
261 // The ratio of dispersion to induction forces
262 double xi = alphaA_i / (m_speciesCharge[i] * m_speciesCharge[i] *
263 (1.0 + pow(2 * r_alpha, 2./3.)) * sqrt(alphaA_j));
264
265 // the collision diameter
266 double K1 = 1.767;
267 double kappa = 0.095;
268 m_diam(i,j) = K1 * (pow(m_alpha[i], 1./3.) + pow(m_alpha[j], 1./3.)) /
269 pow(alphaA_i * alphaA_j * (1.0 + 1.0 / xi), kappa);
270
271 // The original K2 is 0.72, but Han et al. suggested that K2 = 1.44
272 // for better fit.
273 double K2 = 1.44;
274 double epsilon = K2 * ElectronCharge * ElectronCharge *
275 m_speciesCharge[i] * m_speciesCharge[i] *
276 m_alpha[j] * (1.0 + xi) /
277 (8 * Pi * epsilon_0 * pow(m_diam(i,j),4));
278 if (epsilon != 0.0) {
279 m_epsilon(i,j) = epsilon;
280 }
281
282 // Calculate dispersion coefficient and quadrupole polarizability
283 // from curve fitting if not available.
284 // Neutrals
285 if (m_disp[j] == 0.0) {
286 m_disp[j] = exp(1.8846*log(alphaA_j)-0.4737)* 1e-50;
287 }
288 if (m_quad_polar[j] == 0.0) {
289 m_quad_polar[j] = 2.0 * m_disp[j];
290 }
291 // Ions
292 if (m_disp[i] == 0.0) {
293 if (m_speciesCharge[i] > 0) {
294 m_disp[i] = exp(1.8853*log(alphaA_i)+0.2682)* 1e-50;
295 } else {
296 m_disp[i] = exp(3.2246*log(alphaA_i)-3.2397)* 1e-50;
297 }
298 }
299
300 // The binary dispersion coefficient is determined by the combination rule
301 // Reference:
302 // Tang, K. T. "Dynamic polarizabilities and van der Waals coefficients."
303 // Physical Review 177.1 (1969): 108.
304 double C6 = 2.0 * m_disp[i] * m_disp[j] /
305 (1.0/r_alpha * m_disp[i] + r_alpha * m_disp[j]);
306
307 m_gamma(i,j) = (2.0 / pow(m_speciesCharge[i],2) * C6 + m_quad_polar[j]) /
308 (m_alpha[j] * m_diam(i,j) * m_diam(i,j));//Dimensionless
309
310 // properties are symmetric
311 m_diam(j,i) = m_diam(i,j);
312 m_epsilon(j,i) = m_epsilon(i,j);
313 m_gamma(j,i) = m_gamma(i,j);
314 }
315 }
316 }
317}
318
319double IonGasTransport::omega11_n64(const double tstar, const double gamma)
320{
321 double logtstar = log(tstar);
322 double om11 = 0.0;
323 if (tstar < 0.01) {
324 throw CanteraError("IonGasTransport::omega11_n64",
325 "tstar = {} is smaller than 0.01", tstar);
326 } else if (tstar <= 0.04) {
327 // for interval 0.01 to 0.04, SSE = 0.006; R^2 = 1; RMSE = 0.020
328 om11 = 2.97 - 12.0 * gamma
329 - 0.887 * logtstar
330 + 3.86 * gamma * gamma
331 - 6.45 * gamma * logtstar
332 - 0.275 * logtstar * logtstar
333 + 1.20 * gamma * gamma * logtstar
334 - 1.24 * gamma * logtstar * logtstar
335 - 0.164 * pow(logtstar,3);
336 } else if (tstar <= 1000) {
337 // for interval 0.04 to 1000, SSE = 0.282; R^2 = 1; RMSE = 0.033
338 om11 = 1.22 - 0.0343 * gamma
339 + (-0.769 + 0.232 * gamma) * logtstar
340 + (0.306 - 0.165 * gamma) * logtstar * logtstar
341 + (-0.0465 + 0.0388 * gamma) * pow(logtstar,3)
342 + (0.000614 - 0.00285 * gamma) * pow(logtstar,4)
343 + 0.000238 * pow(logtstar,5);
344 } else {
345 throw CanteraError("IonGasTransport::omega11_n64",
346 "tstar = {} is larger than 1000", tstar);
347 }
348 return om11;
349}
350
351void IonGasTransport::getMixDiffCoeffs(double* const d)
352{
353 update_T();
354 update_C();
355
356 // update the binary diffusion coefficients if necessary
357 if (!m_bindiff_ok) {
358 updateDiff_T();
359 }
360
361 double mmw = m_thermo->meanMolecularWeight();
362 double p = m_thermo->pressure();
363 if (m_nsp == 1) {
364 d[0] = m_bdiff(0,0) / p;
365 } else {
366 for (size_t k = 0; k < m_nsp; k++) {
367 if (k == m_kElectron) {
368 d[k] = 0.4 * m_kbt / ElectronCharge;
369 } else {
370 double sum2 = 0.0;
371 for (size_t j : m_kNeutral) {
372 if (j != k) {
373 sum2 += m_molefracs[j] / m_bdiff(j,k);
374 }
375 }
376 if (sum2 <= 0.0) {
377 d[k] = m_bdiff(k,k) / p;
378 } else {
379 d[k] = (mmw - m_molefracs[k] * m_mw[k])/(p * mmw * sum2);
380 }
381 }
382 }
383 }
384}
385
386void IonGasTransport::getMobilities(double* const mobi)
387{
388 update_T();
389 update_C();
390
391 // update the binary diffusion coefficients if necessary
392 if (!m_bindiff_ok) {
393 updateDiff_T();
394 }
395 double p = m_thermo->pressure();
396 for (size_t k = 0; k < m_nsp; k++) {
397 if (k == m_kElectron) {
398 mobi[k] = 0.4;
399 } else {
400 mobi[k] = 0.0;
401 }
402 }
403 for (size_t k : m_kIon) {
404 double sum = 0.0;
405 for (size_t j : m_kNeutral) {
406 double bmobi = m_bdiff(k,j) * ElectronCharge / m_kbt;
407 sum += m_molefracs[j] / bmobi;
408 }
409 mobi[k] = 1.0 / sum / p;
410 }
411}
412
413}
Monk and Monchick collision integrals.
Header file for class ThermoPhase, the base class for phases with thermodynamic properties,...
Base class for exceptions thrown by Cantera classes.
Definition: ctexceptions.h:61
Calculation of Collision integrals.
size_t nSpecies() const
Returns the number of species in the phase.
Definition: Phase.h:273
Base class for a phase with thermodynamic properties.
Definition: ThermoPhase.h:102
This file contains definitions for utility functions and text for modules, inputfiles,...
const double Pi
Pi.
Definition: ct_defs.h:52
void writelog(const std::string &fmt, const Args &... args)
Write a formatted message to the screen.
Definition: global.h:164
void writelogf(const char *fmt, const Args &... args)
Write a formatted message to the screen.
Definition: global.h:185
Namespace for the Cantera kernel.
Definition: AnyMap.h:29
const size_t npos
index returned by functions to indicate "no position"
Definition: ct_defs.h:192
const double Boltzmann
Boltzmann constant [J/K].
Definition: ct_defs.h:69
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.
std::string vec2str(const vector_fp &v, const std::string &fmt="%g", const std::string &sep=", ")
Convert a vector to a string (separated by commas)
Definition: stringUtils.cpp:33
R poly5(D x, R *c)
Templated evaluation of a polynomial of order 5.
Definition: utilities.h:137
std::vector< double > vector_fp
Turn on the use of stl vectors for the basic array type within cantera Vector of doubles.
Definition: ct_defs.h:184
const double epsilon_0
Permittivity of free space [F/m].
Definition: ct_defs.h:134
const double ElectronCharge
Elementary charge [C].
Definition: ct_defs.h:75
R poly4(D x, R *c)
Evaluates a polynomial of order 4.
Definition: utilities.h:149
double polyfit(size_t n, size_t deg, const double *x, const double *y, const double *w, double *p)
Fits a polynomial function to a set of data points.
Definition: polyfit.cpp:14
Contains declarations for string manipulation functions within Cantera.
Various templated functions that carry out common vector operations (see Templated Utility Functions)...