Cantera  4.0.0a1
Loading...
Searching...
No Matches
RedlichKwongMFTP.cpp
Go to the documentation of this file.
1//! @file RedlichKwongMFTP.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
12#include <boost/algorithm/string.hpp>
13#include <algorithm>
14
15namespace Cantera
16{
17
18const double RedlichKwongMFTP::omega_a = 4.27480233540E-01;
19const double RedlichKwongMFTP::omega_b = 8.66403499650E-02;
20const double RedlichKwongMFTP::omega_vc = 3.33333333333333E-01;
21
22RedlichKwongMFTP::RedlichKwongMFTP(const string& infile, const string& id_)
23{
24 initThermoFile(infile, id_);
25}
26
27void RedlichKwongMFTP::setSpeciesCoeffs(const string& species,
28 double a0, double a1, double b)
29{
30 size_t k = speciesIndex(species, true);
31
32 if (a1 != 0.0) {
33 m_formTempParam = 1; // expression is temperature-dependent
34 }
35
36 m_a0(k, k) = a0;
37 m_a1(k, k) = a1;
38
39 // standard mixing rule for cross-species interaction term
40 for (size_t j = 0; j < m_kk; j++) {
41 if (k == j) {
42 continue;
43 }
44
45 // m_a0 is initialized to NaN to mark uninitialized species
46 if (isnan(m_a0(j, j))) {
47 // The diagonal element of the jth species has not yet been defined.
48 continue;
49 } else if (isnan(m_a0(j, k))) {
50 // Only use the mixing rules if the off-diagonal element has not already been defined by a
51 // user-specified crossFluidParameters entry:
52 m_a0(j, k) = m_a0(k, j) = sqrt(m_a0(j, j) * a0);
53 m_a1(j, k) = m_a1(k, j) = sqrt(m_a1(j, j) * a1);
54 }
55 }
56 m_a = m_a0;
57 m_b[k] = b;
58}
59
60void RedlichKwongMFTP::setBinaryCoeffs(const string& species_i, const string& species_j,
61 double a0, double a1)
62{
63 size_t ki = speciesIndex(species_i, true);
64 size_t kj = speciesIndex(species_j, true);
65
66 if (a1 != 0.0) {
67 m_formTempParam = 1; // expression is temperature-dependent
68 }
69
70 m_binaryParameters[species_i][species_j] = {a0, a1};
71 m_binaryParameters[species_j][species_i] = {a0, a1};
72 m_a0(ki, kj) = m_a0(kj, ki) = a0;
73 m_a1(ki, kj) = m_a1(kj, ki) = a1;
74 m_a(ki, kj) = m_a(kj, ki) = a0;
75}
76
77// ------------Molar Thermodynamic Properties -------------------------
78
80{
82 double cpref = GasConstant * mean_X(m_cp0_R);
83 if (m_bMix == 0.0) {
84 return cpref;
85 }
86 double T = temperature();
87 double sqt = sqrt(T);
88 double mv = molarVolume();
89 double vpb = mv + m_bMix;
91 double dadt = da_dt();
92 double fac = T * dadt - 3.0 * m_aMix / 2.0;
93 double dHdT_V = cpref - 0.5 * dadt * log(vpb/mv) / (m_bMix * sqt)
94 + mv * dpdT_ - GasConstant - log(vpb/mv) * fac / (2.0 * m_bMix * T * sqt);
95 return dHdT_V - (mv + T * dpdT_ / dpdV_) * dpdT_;
96}
97
99{
101 double cvref = GasConstant * (mean_X(m_cp0_R) - 1.0);
102 if (m_bMix == 0.0) {
103 return cvref;
104 }
105 double T = temperature();
106 double sqt = sqrt(T);
107 double mv = molarVolume();
108 double vpb = mv + m_bMix;
109 double dadt = da_dt();
110 double fac = T * dadt - 3.0 * m_aMix / 2.0;
111 return (cvref - 1.0/(2.0 * m_bMix * T * sqt) * log(vpb/mv)*fac
112 +1.0/(m_bMix * sqt) * log(vpb/mv)*(-0.5*dadt));
113}
114
116{
118
119 // Get a copy of the private variables stored in the State object
120 double T = temperature();
121 double molarV = meanMolecularWeight() / density();
122 double pp = GasConstant * T/(molarV - m_bMix)
123 - m_aMix/(sqrt(T) * molarV * (molarV + m_bMix));
124 return pp;
125}
126
128{
130 return 1.0 / m_workS[k];
131}
132
134{
135 checkArraySize("RedlichKwongMFTP::getActivityCoefficients", ac.size(), m_kk);
136 for (size_t k = 0; k < m_kk; k++) {
137 ac[k] = 1.0;
138 }
139 if (m_bMix == 0.0) {
140 return;
141 }
142 double mv = molarVolume();
143 double sqt = sqrt(temperature());
144 double vpb = mv + m_bMix;
145 double vmb = mv - m_bMix;
146 double logv = log(vpb / mv);
147 Eigen::ArrayXd g = RT() * (log(RT() / (vmb * pressure())) + m_b / vmb)
148 - 2.0 * m_Ak / (m_bMix * sqt) * logv
149 + m_aMix * m_b / (m_bMix * m_bMix * sqt) * logv
150 - m_aMix * m_b / (m_bMix * sqt * vpb);
151 MappedVector(ac.data(), m_kk) = (g / RT()).exp();
152}
153
154// ---- Partial Molar Properties of the Solution -----------------
155
156void RedlichKwongMFTP::getChemPotentials(span<double> mu) const
157{
159 Eigen::Map<Eigen::ArrayXd> muVec(mu.data(), m_kk);
160 auto x = asVectorXd(moleFractions_);
161 muVec += RT() * x.array().max(SmallNumber).log();
162 if (m_bMix == 0.0) {
163 return;
164 }
165
166 double mv = molarVolume();
167 double sqt = sqrt(temperature());
168 double vpb = mv + m_bMix;
169 double vmb = mv - m_bMix;
170 double logv = log(vpb / mv);
171 muVec += RT() * log(RT() / (vmb * pressure()))
172 + RT() * m_b / vmb
173 - 2.0 * m_Ak / (m_bMix * sqt) * logv
174 + m_aMix * m_b / (m_bMix * m_bMix * sqt) * logv
175 - m_aMix * m_b / (m_bMix * sqt * vpb);
176}
177
179{
180 // First we get the reference state contributions
181 getEnthalpy_RT_ref(hbar);
182 scale(hbar.begin(), hbar.end(), hbar.begin(), RT());
183 if (m_bMix == 0.0) {
184 return;
185 }
186
187 // We calculate m_dpdni
188 double T = temperature();
189 double mv = molarVolume();
190 double sqt = sqrt(T);
191 double vpb = mv + m_bMix;
192 double vmb = mv - m_bMix;
193 m_dpdni = RT() / vmb * (1.0 + m_b / vmb)
194 - 2.0 * m_Ak / (sqt * mv * vpb)
195 + m_aMix * m_b / (sqt * mv * vpb * vpb);
196 double dadt = da_dt();
197 double fac = T * dadt - 3.0 * m_aMix / 2.0;
198 Eigen::ArrayXd Sk = 2.0 * T * m_dAkdT - 3.0 * m_Ak;
199
201 double fac2 = mv + T * dpdT_ / dpdV_;
202 double logv = log(vpb / mv);
203 Eigen::ArrayXd hE_v = mv * m_dpdni
204 - RT()
205 - m_b / (m_bMix * m_bMix * sqt) * logv * fac
206 + (1.0 / (m_bMix * sqt) * logv) * Sk
207 + m_b / (vpb * m_bMix * sqt) * fac;
208 Eigen::Map<Eigen::ArrayXd>(hbar.data(), m_kk) += hE_v - fac2 * m_dpdni;
209}
210
212{
213 getEntropy_R_ref(sbar);
214 double T = temperature();
215 double sqt = sqrt(T);
216 double mv = molarVolume();
217 double pres = pressure();
218
219 MappedVector sbarVec(sbar.data(), m_kk);
220 sbarVec -= asVectorXd(moleFractions_).array().max(SmallNumber).log().matrix();
221 sbarVec.array() -= log(pres / refPressure());
222 sbarVec *= GasConstant;
223 if (m_bMix == 0.0) {
224 return;
225 }
226 double fac = da_dt() - m_aMix / (2.0 * T);
227 double vmb = mv - m_bMix;
228 double vpb = mv + m_bMix;
229 double logv = log(vpb / mv);
230 Eigen::ArrayXd sdep = GasConstant * (log(RT() / (pres * vmb)) + 1)
231 + GasConstant * m_b / vmb
232 + m_Ak / (m_bMix * T * sqt) * logv
233 - 2.0 * m_dAkdT / (m_bMix * sqt) * logv
234 + m_b / (m_bMix * m_bMix * sqt) * logv * fac
235 - m_b / (m_bMix * sqt * vpb) * fac;
236 sbarVec -= sdep.matrix();
237
239 getPartialMolarVolumes(m_partialMolarVolumes);
240 sbarVec += dpdT_ * asVectorXd(m_partialMolarVolumes);
241}
242
244{
245 // u_k = h_k - P * v_k
246 getPartialMolarVolumes(m_partialMolarVolumes);
248 double p = pressure();
249 for (size_t k = 0; k < nSpecies(); k++) {
250 ubar[k] -= p * m_partialMolarVolumes[k];
251 }
252}
253
255{
256 checkArraySize("RedlichKwongMFTP::getPartialMolarIntEnergies_TV", utilde.size(), m_kk);
258
259 Eigen::Map<Eigen::ArrayXd> utildeVec(utilde.data(), m_kk);
260 utildeVec = RT() * (asVectorXd(m_h0_RT).array() - 1.0);
261
262 if (fabs(m_bMix) < SmallNumber) {
263 return;
264 }
265
266 double T = temperature();
267 double mv = molarVolume();
268 double vpb = mv + m_bMix;
269 double logv = log(vpb / mv);
270 double F = T * da_dt() - 1.5 * m_aMix;
271 double C = -logv / m_bMix + 1.0 / vpb;
272 double pref = 1.0 / (m_bMix * sqrt(T));
273
274 Eigen::ArrayXd Sk = 2.0 * T * m_dAkdT - 3.0 * m_Ak;
275 utildeVec += pref * (logv * Sk + m_b * F * C);
276}
277
278void RedlichKwongMFTP::getPartialMolarCp(span<double> cpbar) const
279{
280 checkArraySize("RedlichKwongMFTP::getPartialMolarCp", cpbar.size(), m_kk);
282 for (size_t k = 0; k < m_kk; k++) {
283 cpbar[k] = GasConstant * m_cp0_R[k];
284 }
285
286 if (fabs(m_bMix) < SmallNumber) {
287 return;
288 }
289
290 double T = temperature();
291 double sqt = sqrt(T);
292 double v = molarVolume();
293 double dadt = da_dt();
294 double d2adt2 = 0.0; // current RK model uses a(T)=a0+a1*T
295
296 // Mixture pressure derivatives
298 double dvdT_P = -dpdT_ / dpdV_;
299 double vpb = v + m_bMix;
300 double vmb = v - m_bMix;
301
302 // P_TT, P_TV and P_VV for v''(T)|P from EOS:
303 // v'' = -(P_TT + 2 P_TV v' + P_VV v'^2) / P_V
304 double pTT = (-d2adt2 + dadt / T - 3.0 * m_aMix / (4.0 * T * T)) / (sqt * v * vpb);
305 double pTV = -GasConstant / (vmb * vmb)
306 + (dadt - m_aMix / (2.0 * T)) * (2.0 * v + m_bMix) / (sqt * v * v * vpb * vpb);
307 double pVV = 2.0 * GasConstant * T / (vmb * vmb * vmb)
308 - 2.0 * m_aMix * (3.0 * v * v + 3.0 * m_bMix * v + m_bMix * m_bMix)
309 / (sqt * v * v * v * vpb * vpb * vpb);
310 double d2vdT2_P = -(pTT + 2.0 * pTV * dvdT_P + pVV * dvdT_P * dvdT_P) / dpdV_;
311
312 double F = T * dadt - 1.5 * m_aMix;
313 double dF_dT = -0.5 * dadt + T * d2adt2;
314 double logvpv = log(vpb / v);
315 double termLPrime = -m_bMix * dvdT_P / (v * vpb);
316
317 for (size_t k = 0; k < m_kk; k++) {
318 double bk = m_b[k];
319 double Ak = m_Ak[k];
320 double dAk = m_dAkdT[k];
321 double Sk = 2.0 * T * dAk - 3.0 * Ak;
322 double dSk_dT = -dAk; // for linear a(T)
323
324 // dp/dn_k at constant T,V,n_j
325 double dpdni = RT() / vmb + RT() * bk / (vmb * vmb)
326 - 2.0 * Ak / (sqt * v * vpb)
327 + m_aMix * bk / (sqt * v * vpb * vpb);
328
329 // d/dT|P [dp/dn_k]
330 double d_dpdni_dT = GasConstant / vmb
331 - GasConstant * T * dvdT_P / (vmb * vmb)
332 + GasConstant * bk / (vmb * vmb)
333 - 2.0 * GasConstant * T * bk * dvdT_P / (vmb * vmb * vmb)
334 - 2.0 * dAk / (sqt * v * vpb)
335 + Ak / (T * sqt * v * vpb)
336 + 2.0 * Ak * (2.0 * v + m_bMix) * dvdT_P / (sqt * v * v * vpb * vpb)
337 + bk * da_dt() / (sqt * v * vpb * vpb)
338 - bk * m_aMix / (2.0 * T * sqt * v * vpb * vpb)
339 - bk * m_aMix * (3.0 * v + m_bMix) * dvdT_P / (sqt * v * v * vpb * vpb * vpb);
340
341 // d/dT|P of departure terms used in getPartialMolarEnthalpies
342 double dterm1 = -bk / (m_bMix * m_bMix * sqt)
343 * (termLPrime * F + logvpv * dF_dT - logvpv * F / (2.0 * T));
344 double dterm2 = 1.0 / (m_bMix * sqt)
345 * (termLPrime * Sk + logvpv * dSk_dT - logvpv * Sk / (2.0 * T));
346 double dterm3 = bk / (m_bMix * sqt)
347 * (dF_dT / vpb - F * dvdT_P / (vpb * vpb) - F / (2.0 * T * vpb));
348
349 // cp_k = d hbar_k / dT at constant P and composition
350 cpbar[k] += -GasConstant
351 + (dvdT_P + T * d2vdT2_P) * dpdni
352 + T * dvdT_P * d_dpdni_dT
353 + dterm1 + dterm2 + dterm3;
354 }
355}
356
357void RedlichKwongMFTP::getPartialMolarCv_TV(span<double> cvtilde) const
358{
359 checkArraySize("RedlichKwongMFTP::getPartialMolarCv_TV", cvtilde.size(), m_kk);
361
362 Eigen::Map<Eigen::ArrayXd> cvtildeVec(cvtilde.data(), m_kk);
363 cvtildeVec = GasConstant * (asVectorXd(m_cp0_R).array() - 1.0);
364
365 if (fabs(m_bMix) < SmallNumber) {
366 return;
367 }
368
369 double T = temperature();
370 double sqt = sqrt(T);
371 double mv = molarVolume();
372 double dadt = da_dt();
373
374 // Residual contribution for
375 // \tilde{u}_k = (\partial U / \partial n_k)_{T,V,n_j}
376 // with linear a(T): a_ij = a_ij,0 + a_ij,1 T
377 double vpb = mv + m_bMix;
378 double logv = log(vpb / mv);
379 double F = T * dadt - 1.5 * m_aMix;
380 double C = -logv / m_bMix + 1.0 / vpb;
381 double pref = 1.0 / (m_bMix * sqt);
382
383 Eigen::ArrayXd Sk = 2.0 * T * m_dAkdT - 3.0 * m_Ak;
384 Eigen::ArrayXd ures = pref * (logv * Sk + m_b * F * C);
385 Eigen::ArrayXd dresdT = - pref * (logv * m_dAkdT + 0.5 * m_b * dadt * C)
386 - 0.5 * ures / T;
387 cvtildeVec += dresdT;
388}
389
390void RedlichKwongMFTP::getPartialMolarVolumes(span<double> vbar) const
391{
392 checkArraySize("RedlichKwongMFTP::getPartialMolarVolumes", vbar.size(), m_kk);
393 double sqt = sqrt(temperature());
394 double mv = molarVolume();
395 double vmb = mv - m_bMix;
396 double vpb = mv + m_bMix;
397 Eigen::ArrayXd num = RT() * (1.0 + (m_bMix + m_b) / vmb + m_bMix * m_b / (vmb * vmb))
398 - 2.0 * m_Ak / (sqt * vpb)
399 + m_aMix * m_b / (sqt * vpb * vpb);
400 double denom = pressure() + RT() * m_bMix / (vmb * vmb) - m_aMix / (sqt * vpb * vpb);
401 MappedVector(vbar.data(), m_kk) = num / denom;
402}
403
404bool RedlichKwongMFTP::addSpecies(shared_ptr<Species> spec)
405{
406 bool added = MixtureFugacityTP::addSpecies(spec);
407 if (added) {
408 size_t k = m_kk - 1;
409
410 m_a.conservativeResize(m_kk, m_kk);
411 m_a.row(k).setZero();
412 m_a.col(k).setZero();
413
414 // Initialize new entries to NaN to screen for species with undefined
415 // pure-fluid parameters in the input file.
416 m_b.conservativeResize(m_kk);
417 m_b[k] = NAN;
418 m_a0.conservativeResize(m_kk, m_kk);
419 m_a0.row(k).setConstant(NAN);
420 m_a0.col(k).setConstant(NAN);
421 m_a1.conservativeResize(m_kk, m_kk);
422 m_a1.row(k).setConstant(NAN);
423 m_a1.col(k).setConstant(NAN);
424
425 m_Ak.conservativeResizeLike(Eigen::VectorXd::Zero(m_kk));
426 m_dAkdT.conservativeResizeLike(Eigen::VectorXd::Zero(m_kk));
427 m_coeffSource.push_back(CoeffSource::EoS);
428 m_partialMolarVolumes.push_back(0.0);
429 m_dpdni.conservativeResizeLike(Eigen::VectorXd::Zero(m_kk));
430 }
431 return added;
432}
433
435{
436 // Contents of 'critical-properties.yaml', loaded later if needed
437 AnyMap critPropsDb;
438 std::unordered_map<string, AnyMap*> dbSpecies;
439
440 for (auto& [name, species] : m_species) {
441 auto& data = species->input;
442 size_t k = speciesIndex(name, true);
443 if (!isnan(m_a0(k, k)) && !isnan(m_b[k])) {
444 continue;
445 }
446 bool foundCoeffs = false;
447
448 if (data.hasKey("equation-of-state") &&
449 data["equation-of-state"].hasMapWhere("model", "Redlich-Kwong"))
450 {
451 // Read a and b coefficients from species 'input' information (that is, as
452 // specified in a YAML input file) specific to the Redlich-Kwong model
453 auto eos = data["equation-of-state"].getMapWhere(
454 "model", "Redlich-Kwong");
455
456 if (eos.hasKey("a") && eos.hasKey("b")) {
457 double a0 = 0, a1 = 0;
458 if (eos["a"].isScalar()) {
459 a0 = eos.convert("a", "Pa*m^6/kmol^2*K^0.5");
460 } else {
461 auto avec = eos["a"].asVector<AnyValue>(2);
462 a0 = eos.units().convert(avec[0], "Pa*m^6/kmol^2*K^0.5");
463 a1 = eos.units().convert(avec[1], "Pa*m^6/kmol^2/K^0.5");
464 }
465 double b = eos.convert("b", "m^3/kmol");
466 foundCoeffs = true;
467 setSpeciesCoeffs(name, a0, a1, b);
468 m_coeffSource[k] = CoeffSource::EoS;
469 }
470
471 if (eos.hasKey("binary-a")) {
472 AnyMap& binary_a = eos["binary-a"].as<AnyMap>();
473 const UnitSystem& units = binary_a.units();
474 for (auto& [name2, coeff] : binary_a) {
475 double a0 = 0, a1 = 0;
476 if (coeff.isScalar()) {
477 a0 = units.convert(coeff, "Pa*m^6/kmol^2*K^0.5");
478 } else {
479 auto avec = coeff.asVector<AnyValue>(2);
480 a0 = units.convert(avec[0], "Pa*m^6/kmol^2*K^0.5");
481 a1 = units.convert(avec[1], "Pa*m^6/kmol^2/K^0.5");
482 }
483 setBinaryCoeffs(name, name2, a0, a1);
484 }
485 }
486
487 if (foundCoeffs) {
488 continue;
489 }
490 }
491
492 // Coefficients have not been populated from model-specific input
493 double Tc = NAN, Pc = NAN;
494 if (data.hasKey("critical-parameters")) {
495 // Use critical state information stored in the species entry to
496 // calculate a and b
497 auto& critProps = data["critical-parameters"].as<AnyMap>();
498 Tc = critProps.convert("critical-temperature", "K");
499 Pc = critProps.convert("critical-pressure", "Pa");
500 m_coeffSource[k] = CoeffSource::CritProps;
501 } else {
502 // Search 'crit-properties.yaml' to find Tc and Pc. Load data if needed
503 if (critPropsDb.empty()) {
504 critPropsDb = AnyMap::fromYamlFile("critical-properties.yaml");
505 dbSpecies = critPropsDb["species"].asMap("name");
506 }
507
508 // All names in critical-properties.yaml are upper case
509 auto ucName = boost::algorithm::to_upper_copy(name);
510 if (dbSpecies.count(ucName)) {
511 auto& spec = *dbSpecies.at(ucName);
512 auto& critProps = spec["critical-parameters"].as<AnyMap>();
513 Tc = critProps.convert("critical-temperature", "K");
514 Pc = critProps.convert("critical-pressure", "Pa");
515 m_coeffSource[k] = CoeffSource::Database;
516 }
517 }
518
519 // Check if critical properties were found in either location
520 if (!isnan(Tc)) {
521 // Assuming no temperature dependence (that is, a1 = 0)
522 double a = omega_a * pow(GasConstant, 2) * pow(Tc, 2.5) / Pc;
523 double b = omega_b * GasConstant * Tc / Pc;
524 setSpeciesCoeffs(name, a, 0.0, b);
525 } else {
526 throw InputFileError("RedlichKwongMFTP::initThermo", data,
527 "No critical property or Redlich-Kwong parameters found "
528 "for species {}.", name);
529 }
530 }
531}
532
534 AnyMap& speciesNode) const
535{
537 size_t k = speciesIndex(name, true);
538 if (m_coeffSource[k] == CoeffSource::EoS) {
539 auto& eosNode = speciesNode["equation-of-state"].getMapWhere(
540 "model", "Redlich-Kwong", true);
541
542 if (m_a1(k, k) != 0.0) {
543 vector<AnyValue> coeffs(2);
544 coeffs[0].setQuantity(m_a0(k, k), "Pa*m^6/kmol^2*K^0.5");
545 coeffs[1].setQuantity(m_a1(k, k), "Pa*m^6/kmol^2/K^0.5");
546 eosNode["a"] = std::move(coeffs);
547 } else {
548 eosNode["a"].setQuantity(m_a0(k, k),
549 "Pa*m^6/kmol^2*K^0.5");
550 }
551 eosNode["b"].setQuantity(m_b[k], "m^3/kmol");
552 } else if (m_coeffSource[k] == CoeffSource::CritProps) {
553 auto& critProps = speciesNode["critical-parameters"];
554 double a = m_a0(k, k);
555 double b = m_b[k];
556 double Tc = pow(a * omega_b / (b * omega_a * GasConstant), 2.0/3.0);
557 double Pc = omega_b * GasConstant * Tc / b;
558 critProps["critical-temperature"].setQuantity(Tc, "K");
559 critProps["critical-pressure"].setQuantity(Pc, "Pa");
560 }
561
562 if (m_binaryParameters.count(name)) {
563 auto& eosNode = speciesNode["equation-of-state"].getMapWhere(
564 "model", "Redlich-Kwong", true);
565 AnyMap bin_a;
566 for (const auto& [name2, coeffs] : m_binaryParameters.at(name)) {
567 if (coeffs.second == 0) {
568 bin_a[name2].setQuantity(coeffs.first, "Pa*m^6/kmol^2*K^0.5");
569 } else {
570 vector<AnyValue> C(2);
571 C[0].setQuantity(coeffs.first, "Pa*m^6/kmol^2*K^0.5");
572 C[1].setQuantity(coeffs.second, "Pa*m^6/kmol^2/K^0.5");
573 bin_a[name2] = std::move(C);
574 }
575 }
576 eosNode["binary-a"] = std::move(bin_a);
577 }
578}
579
581{
582 if (m_bMix == 0.0) {
583 return 0.0;
584 }
585 // note this agrees with tpx
586 double hh = m_bMix / molarVolume();
587 double zz = z();
588 double T = temperature();
589 double fac = da_dt() - m_aMix / (2.0 * T);
590 double sresid_mol_R = log(zz*(1.0 - hh))
591 + log(1.0 + hh) * fac / (sqrt(T) * GasConstant * m_bMix);
592 return GasConstant * sresid_mol_R;
593}
594
596{
597 if (m_bMix == 0.0) {
598 return 0.0;
599 }
600 // note this agrees with tpx
601 double hh = m_bMix / molarVolume();
602 double zz = z();
603 double T = temperature();
604 double fac = T * da_dt() - 3.0 *m_aMix / (2.0);
605 return GasConstant * T * (zz - 1.0) + fac * log(1.0 + hh) / (sqrt(T) * m_bMix);
606}
607
608double RedlichKwongMFTP::liquidVolEst(double T, double& presGuess) const
609{
610 double v = m_bMix * 1.1;
611 double atmp;
612 double btmp;
613 calculateAB(T, atmp, btmp);
614 double pres = std::max(psatEst(T), presGuess);
615 double Vroot[3];
616 bool foundLiq = false;
617 int m = 0;
618 while (m < 100 && !foundLiq) {
619 int nsol = solveCubic(T, pres, atmp, btmp, Vroot);
620 if (nsol == 1 || nsol == 2) {
621 double pc = critPressure();
622 if (pres > pc) {
623 foundLiq = true;
624 }
625 pres *= 1.04;
626 } else {
627 foundLiq = true;
628 }
629 }
630
631 if (foundLiq) {
632 v = Vroot[0];
633 presGuess = pres;
634 } else {
635 v = -1.0;
636 }
637 return v;
638}
639
640double RedlichKwongMFTP::densityCalc(double T, double presPa, int phaseRequested,
641 double rhoguess)
642{
643 // It's necessary to set the temperature so that m_a_current is set correctly.
645 double tcrit = critTemperature();
646 double mmw = meanMolecularWeight();
647 if (rhoguess == -1.0) {
648 if (phaseRequested >= FLUID_LIQUID_0) {
649 double lqvol = liquidVolEst(T, presPa);
650 rhoguess = mmw / lqvol;
651 }
652 } else {
653 // Assume the Gas phase initial guess, if nothing is specified to the routine
654 rhoguess = presPa * mmw / (GasConstant * T);
655 }
656
657
658 double volguess = mmw / rhoguess;
659 NSolns_ = solveCubic(T, presPa, m_aMix, m_bMix, Vroot_);
660
661 // Ensure root ordering used for branch selection only contains physical roots.
662 double vmin = std::max(0.0, m_bMix * (1.0 + 1e-12));
663 vector<double> physicalRoots;
664 for (double root : Vroot_) {
665 if (std::isfinite(root) && root > vmin) {
666 physicalRoots.push_back(root);
667 }
668 }
669 std::sort(physicalRoots.begin(), physicalRoots.end());
670 if (physicalRoots.empty()) {
671 return -1.0;
672 } else if (physicalRoots.size() == 1) {
673 // Preserve branch-request semantics for single-root states:
674 // return -2 when the requested phase is inconsistent with the
675 // single branch identified by solveCubic() sign convention.
676 if ((phaseRequested == FLUID_GAS && NSolns_ < 0)
677 || (phaseRequested >= FLUID_LIQUID_0 && NSolns_ > 0))
678 {
679 return -2.0;
680 }
681 // Otherwise, accept the physically admissible root directly.
682 return mmw / physicalRoots[0];
683 } else if (physicalRoots.size() == 2) {
684 Vroot_[0] = physicalRoots[0];
685 Vroot_[1] = 0.5 * (physicalRoots[0] + physicalRoots[1]);
686 Vroot_[2] = physicalRoots[1];
687 } else {
688 Vroot_[0] = physicalRoots[0];
689 Vroot_[1] = physicalRoots[1];
690 Vroot_[2] = physicalRoots[2];
691 }
692
693 double molarVolLast = Vroot_[0];
694 if (NSolns_ >= 2) {
695 if (phaseRequested >= FLUID_LIQUID_0) {
696 molarVolLast = Vroot_[0];
697 } else if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT) {
698 molarVolLast = Vroot_[2];
699 } else {
700 if (volguess > Vroot_[1]) {
701 molarVolLast = Vroot_[2];
702 } else {
703 molarVolLast = Vroot_[0];
704 }
705 }
706 } else if (NSolns_ == 1) {
707 if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT || phaseRequested == FLUID_UNDEFINED) {
708 molarVolLast = Vroot_[0];
709 } else {
710 return -2.0;
711 }
712 } else if (NSolns_ == -1) {
713 if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) {
714 molarVolLast = Vroot_[0];
715 } else if (T > tcrit) {
716 molarVolLast = Vroot_[0];
717 } else {
718 return -2.0;
719 }
720 } else {
721 molarVolLast = Vroot_[0];
722 return -1.0;
723 }
724 return mmw / molarVolLast;
725}
726
727double RedlichKwongMFTP::dpdVCalc(double T, double molarVol, double& presCalc) const
728{
729 double sqt = sqrt(T);
730 presCalc = GasConstant * T / (molarVol - m_bMix)
731 - m_aMix / (sqt * molarVol * (molarVol + m_bMix));
732
733 double vpb = molarVol + m_bMix;
734 double vmb = molarVol - m_bMix;
735 return - GasConstant * T / (vmb * vmb)
736 + m_aMix * (2 * molarVol + m_bMix) / (sqt * molarVol * molarVol * vpb * vpb);
737}
738
740{
742 return -1 / (molarVolume() * dpdV_);
743}
744
746{
748 return -dpdT_ / (molarVolume() * dpdV_);
749}
750
752{
754 return temperature() * dpdT_ - pressure();
755}
756
758{
760 return molarVolume() * sqrt(-cp_mole() / cv_mole() * dpdV_ / meanMolecularWeight());
761}
762
764{
765 double T = temperature();
766 double mv = molarVolume();
767 double pres;
768
769 dpdV_ = dpdVCalc(T, mv, pres);
770 double fac = da_dt() - m_aMix/(2.0 * T);
771 dpdT_ = (GasConstant / (mv - m_bMix) - fac / (sqrt(T) * mv * (mv + m_bMix)));
772}
773
775{
776 double T = temperature();
777 auto x = asVectorXd(moleFractions_);
778 if (m_formTempParam == 1) {
779 m_a = m_a0 + T * m_a1;
780 m_dAkdT = m_a1 * x;
781 }
782
783 m_bMix = m_b.matrix().dot(x);
784 m_Ak = m_a * x;
785 m_aMix = x.dot(m_Ak.matrix());
786 if (isnan(m_bMix)) {
787 // One or more species do not have specified coefficients.
788 fmt::memory_buffer b;
789 for (size_t k = 0; k < m_kk; k++) {
790 if (isnan(m_b[k])) {
791 if (b.size() > 0) {
792 fmt_append(b, ", {}", speciesName(k));
793 } else {
794 fmt_append(b, "{}", speciesName(k));
795 }
796 }
797 }
798 throw CanteraError("RedlichKwongMFTP::updateMixingExpressions",
799 "Missing Redlich-Kwong coefficients for species: {}", to_string(b));
800 }
801}
802
803void RedlichKwongMFTP::calculateAB(double T, double& aCalc, double& bCalc) const
804{
805 auto x = asVectorXd(moleFractions_);
806 bCalc = m_b.matrix().dot(x);
807 if (m_formTempParam == 1) {
808 aCalc = x.dot((m_a0 + T * m_a1) * x);
809 } else {
810 aCalc = x.dot(m_a0 * x);
811 }
812}
813
814double RedlichKwongMFTP::da_dt() const
815{
816 if (m_formTempParam == 0) {
817 return 0.0;
818 }
819 auto x = asVectorXd(moleFractions_);
820 return x.dot(m_a1 * x);
821}
822
823void RedlichKwongMFTP::calcCriticalConditions(double& pc, double& tc, double& vc) const
824{
825 auto x = asVectorXd(moleFractions_);
826 double a0 = x.dot(m_a0 * x);
827 double aT = x.dot(m_a1 * x);
828 double a = m_aMix;
829 if (m_formTempParam != 0) {
830 a = a0;
831 }
832 if (m_bMix <= 0.0) {
833 tc = 1000000.;
834 pc = 1.0E13;
835 vc = omega_vc * GasConstant * tc / pc;
836 return;
837 }
838 if (a <= 0.0) {
839 tc = 0.0;
840 pc = 0.0;
841 vc = 2.0 * m_bMix;
842 return;
843 }
844 double tmp = a * omega_b / (m_bMix * omega_a * GasConstant);
845 double pp = 2./3.;
846 double sqrttc, f, dfdt, deltatc;
847
848 if (m_formTempParam == 0) {
849 tc = pow(tmp, pp);
850 } else {
851 tc = pow(tmp, pp);
852 for (int j = 0; j < 10; j++) {
853 sqrttc = sqrt(tc);
854 f = omega_a * m_bMix * GasConstant * tc * sqrttc / omega_b - aT * tc - a0;
855 dfdt = 1.5 * omega_a * m_bMix * GasConstant * sqrttc / omega_b - aT;
856 deltatc = - f / dfdt;
857 tc += deltatc;
858 }
859 if (deltatc > 0.1) {
860 throw CanteraError("RedlichKwongMFTP::calcCriticalConditions",
861 "didn't converge");
862 }
863 }
864
865 pc = omega_b * GasConstant * tc / m_bMix;
866 vc = omega_vc * GasConstant * tc / pc;
867}
868
869int RedlichKwongMFTP::solveCubic(double T, double pres, double a, double b,
870 span<double> Vroot) const
871{
872
873 // Derive the coefficients of the cubic polynomial to solve.
874 double an = 1.0;
875 double bn = - GasConstant * T / pres;
876 double sqt = sqrt(T);
877 double cn = - (GasConstant * T * b / pres - a/(pres * sqt) + b * b);
878 double dn = - (a * b / (pres * sqt));
879
880 double tmp = a * omega_b / (b * omega_a * GasConstant);
881 double pp = 2./3.;
882 double tc = pow(tmp, pp);
883 double pc = omega_b * GasConstant * tc / b;
884 double vc = omega_vc * GasConstant * tc / pc;
885
886 int nSolnValues = MixtureFugacityTP::solveCubic(T, pres, a, b, a, Vroot, an, bn, cn, dn, tc, vc);
887
888 return nSolnValues;
889}
890
891}
Declaration for class Cantera::Species.
Headers for the factory class that can create known ThermoPhase objects (see Thermodynamic Properties...
A map of string keys to values whose type can vary at runtime.
Definition AnyMap.h:431
const UnitSystem & units() const
Return the default units that should be used to convert stored values.
Definition AnyMap.h:640
bool empty() const
Return boolean indicating whether AnyMap is empty.
Definition AnyMap.cpp:1468
double convert(const string &key, const string &units) const
Convert the item stored by the given key to the units specified in units.
Definition AnyMap.cpp:1595
static AnyMap fromYamlFile(const string &name, const string &parent_name="")
Create an AnyMap from a YAML file.
Definition AnyMap.cpp:1841
A wrapper for a variable whose type is determined at runtime.
Definition AnyMap.h:88
Base class for exceptions thrown by Cantera classes.
Error thrown for problems processing information contained in an AnyMap or AnyValue.
Definition AnyMap.h:749
void getEntropy_R_ref(span< double > er) const override
Returns the vector of nondimensional entropies of the reference state at the current temperature of t...
double critPressure() const override
Critical pressure (Pa).
vector< double > m_h0_RT
Temporary storage for dimensionless reference state enthalpies.
void getStandardChemPotentials(span< double > mu) const override
Get the array of chemical potentials at unit activity.
double critTemperature() const override
Critical temperature (K).
virtual void _updateReferenceStateThermo() const
Updates the reference state thermodynamic functions at the current T of the solution.
void getEnthalpy_RT_ref(span< double > hrt) const override
Returns the vector of nondimensional enthalpies of the reference state at the current temperature of ...
vector< double > moleFractions_
Storage for the current values of the mole fractions of the species.
int solveCubic(double T, double pres, double a, double b, double aAlpha, span< double > Vroot, double an, double bn, double cn, double dn, double tc, double vc) const
Solve the cubic equation of state.
void setTemperature(const double temp) override
Set the temperature of the phase.
virtual double psatEst(double TKelvin) const
Estimate for the saturation pressure.
void getStandardVolumes(span< double > vol) const override
Get the molar volumes of each species in their standard states at the current T and P of the solution...
vector< double > m_cp0_R
Temporary storage for dimensionless reference state heat capacities.
bool addSpecies(shared_ptr< Species > spec) override
Add a Species to this Phase.
double z() const
Calculate the value of z.
vector< double > m_workS
Vector of size m_kk, used as a temporary holding area.
Definition Phase.h:906
size_t nSpecies() const
Returns the number of species in the phase.
Definition Phase.h:247
size_t m_kk
Number of species in the phase.
Definition Phase.h:882
size_t speciesIndex(const string &name, bool raise=true) const
Returns the index of a species named 'name' within the Phase object.
Definition Phase.cpp:127
double temperature() const
Temperature (K).
Definition Phase.h:586
double meanMolecularWeight() const
The mean molecular weight. Units: (kg/kmol)
Definition Phase.h:677
string speciesName(size_t k) const
Name of the species with index k.
Definition Phase.cpp:143
map< string, shared_ptr< Species > > m_species
Map of Species objects.
Definition Phase.h:895
double mean_X(span< const double > Q) const
Evaluate the mole-fraction-weighted mean of an array Q.
Definition Phase.cpp:637
virtual double density() const
Density (kg/m^3).
Definition Phase.h:611
shared_ptr< Species > species(const string &name) const
Return the Species object for the named species.
Definition Phase.cpp:943
virtual double molarVolume() const
Molar volume (m^3/kmol).
Definition Phase.cpp:602
string name() const
Return the name of the phase.
Definition Phase.cpp:20
double m_aMix
Value of a in the equation of state.
Eigen::ArrayXd m_dpdni
Vector of derivatives of pressure wrt mole number.
double dpdT_
The derivative of the pressure wrt the temperature.
Eigen::MatrixXd m_a
Current temperature-dependent value of for each species pair.
double thermalExpansionCoeff() const override
Return the volumetric thermal expansion coefficient. Units: 1/K.
void setBinaryCoeffs(const string &species_i, const string &species_j, double a0, double a1)
Set values for the interaction parameter between two species.
Eigen::ArrayXd m_Ak
. Length m_kk.
double sresid() const override
Calculate the deviation terms for the total entropy of the mixture from the ideal gas mixture.
void getPartialMolarEnthalpies(span< double > hbar) const override
Return partial molar enthalpies (J/kmol).
double soundSpeed() const override
Return the speed of sound. Units: m/s.
double pressure() const override
Return the thermodynamic pressure (Pa).
int m_formTempParam
Form of the temperature parameterization.
void getSpeciesParameters(const string &name, AnyMap &speciesNode) const override
Get phase-specific parameters of a Species object such that an identical one could be reconstructed a...
RedlichKwongMFTP(const string &infile="", const string &id="")
Construct a RedlichKwongMFTP object from an input file.
void getPartialMolarCp(span< double > cpbar) const override
Get the partial molar heat capacities at constant pressure [J/kmol/K].
static const double omega_b
Omega constant for b.
void getActivityCoefficients(span< double > ac) const override
Get the array of non-dimensional activity coefficients at the current solution temperature,...
void initThermo() override
Initialize the ThermoPhase object after all species have been set up.
void getPartialMolarIntEnergies_TV(span< double > utilde) const override
Get the species molar internal energies associated with the derivatives of total internal energy at c...
double internalPressure() const override
Return the internal pressure [Pa].
Eigen::MatrixXd m_a1
Temperature coefficient in the expression for .
double cv_mole() const override
Molar heat capacity at constant volume and composition [J/kmol/K].
void pressureDerivatives() const
Calculate dpdV and dpdT at the current conditions.
double liquidVolEst(double T, double &pres) const override
Estimate for the molar volume of the liquid.
Eigen::ArrayXd m_dAkdT
. Length m_kk.
double m_bMix
Value of b in the equation of state.
double isothermalCompressibility() const override
Returns the isothermal compressibility. Units: 1/Pa.
double hresid() const override
Calculate the deviation terms for the total enthalpy of the mixture from the ideal gas mixture.
static const double omega_a
Omega constant for a -> value of a in terms of critical properties.
void getPartialMolarCv_TV(span< double > cvtilde) const override
Get the species molar heat capacities associated with the constant volume partial molar internal ener...
static const double omega_vc
Omega constant for the critical molar volume.
vector< CoeffSource > m_coeffSource
For each species, specifies the source of the a and b coefficients.
Eigen::MatrixXd m_a0
Constant term in the expression for for each species pair.
void updateMixingExpressions() override
Update the a and b parameters.
double cp_mole() const override
Molar heat capacity at constant pressure and composition [J/kmol/K].
void getPartialMolarVolumes(span< double > vbar) const override
Return an array of partial molar volumes for the species in the mixture.
void getPartialMolarEntropies(span< double > sbar) const override
Returns an array of partial molar entropies of the species in the solution.
double standardConcentration(size_t k=0) const override
Returns the standard concentration , which is used to normalize the generalized concentration.
double dpdV_
The derivative of the pressure wrt the volume.
bool addSpecies(shared_ptr< Species > spec) override
Add a Species to this Phase.
double dpdVCalc(double T, double molarVol, double &presCalc) const override
Calculate the pressure and the pressure derivative given the temperature and the molar volume.
Eigen::ArrayXd m_b
Coefficients for each species. Size m_kk.
void getChemPotentials(span< double > mu) const override
Get the species chemical potentials. Units: J/kmol.
int solveCubic(double T, double pres, double a, double b, span< double > Vroot) const
Prepare variables and call the function to solve the cubic equation of state.
double densityCalc(double T, double pressure, int phase, double rhoguess) override
Calculates the density given the temperature and the pressure and a guess at the density.
map< string, map< string, pair< double, double > > > m_binaryParameters
Explicitly-specified binary interaction parameters.
void setSpeciesCoeffs(const string &species, double a0, double a1, double b)
Set the pure fluid interaction parameters for a species.
void calculateAB(double T, double &aCalc, double &bCalc) const
Calculate the a and the b parameters given the temperature.
void getPartialMolarIntEnergies(span< double > ubar) const override
Return an array of partial molar internal energies for the species in the mixture.
double RT() const
Return the Gas Constant multiplied by the current temperature.
void initThermoFile(const string &inputFile, const string &id)
Initialize a ThermoPhase object using an input file.
shared_ptr< Solution > root() const
Get the Solution object containing this ThermoPhase object and linked Kinetics and Transport objects.
virtual void getSpeciesParameters(const string &name, AnyMap &speciesNode) const
Get phase-specific parameters of a Species object such that an identical one could be reconstructed a...
virtual double refPressure() const
Returns the reference pressure in Pa.
Unit conversion utility.
Definition Units.h:169
double convert(double value, const string &src, const string &dest) const
Convert value from the units of src to the units of dest.
Definition Units.cpp:539
void scale(InputIter begin, InputIter end, OutputIter out, S scale_factor)
Multiply elements of an array by a scale factor.
Definition utilities.h:118
const double GasConstant
Universal Gas Constant [J/kmol/K].
Definition ct_defs.h:123
Namespace for the Cantera kernel.
Definition AnyMap.cpp:595
MappedVector asVectorXd(vector< double > &v)
Convenience wrapper for accessing std::vector as an Eigen VectorXd.
Definition eigen_dense.h:60
const double SmallNumber
smallest number to compare to zero.
Definition ct_defs.h:161
void checkArraySize(const char *procedure, size_t available, size_t required)
Wrapper for throwing ArraySizeError.
Contains declarations for string manipulation functions within Cantera.
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...