Cantera  4.0.0a2
Loading...
Searching...
No Matches
MixtureFugacityTP.cpp
Go to the documentation of this file.
1/**
2 * @file MixtureFugacityTP.cpp
3 * Methods file for a derived class of ThermoPhase that handles
4 * non-ideal mixtures based on the fugacity models (see @ref thermoprops and
5 * class @link Cantera::MixtureFugacityTP MixtureFugacityTP@endlink).
6 */
7
8// This file is part of Cantera. See License.txt in the top-level directory or
9// at https://cantera.org/license.txt for license and copyright information.
10
14#include "cantera/base/global.h"
15
16#include <algorithm>
17#include <limits>
18
19using namespace std;
20
21namespace Cantera
22{
23
25{
27}
28
30{
31 forcedState_ = solnBranch;
32}
33
35{
36 return forcedState_;
37}
38
40{
41 return iState_;
42}
43
44// ---- Molar Thermodynamic Properties ---------------------------
46{
47 double h_ideal = RT() * mean_X(m_h0_RT);
48 double h_nonideal = hresid();
49 return h_ideal + h_nonideal;
50}
51
52
54{
55 double s_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx()
56 - std::log(pressure()/refPressure()));
57 double s_nonideal = sresid();
58 return s_ideal + s_nonideal;
59}
60
61// ----- Thermodynamic Values for the Species Standard States States ----
62
64{
65 checkArraySize("MixtureFugacityTP::getStandardChemPotentials", g.size(), m_kk);
66 copy(m_g0_RT.begin(), m_g0_RT.end(), g.begin());
67 double tmp = log(pressure() / refPressure());
68 for (size_t k = 0; k < m_kk; k++) {
69 g[k] = RT() * (g[k] + tmp);
70 }
71}
72
73void MixtureFugacityTP::getEnthalpy_RT(span<double> hrt) const
74{
76}
77
78void MixtureFugacityTP::getEntropy_R(span<double> sr) const
79{
80 checkArraySize("MixtureFugacityTP::getEntropy_R", sr.size(), m_kk);
81 copy(m_s0_R.begin(), m_s0_R.end(), sr.begin());
82 double tmp = log(pressure() / refPressure());
83 for (size_t k = 0; k < m_kk; k++) {
84 sr[k] -= tmp;
85 }
86}
87
88void MixtureFugacityTP::getGibbs_RT(span<double> grt) const
89{
90 checkArraySize("MixtureFugacityTP::getGibbs_RT", grt.size(), m_kk);
91 copy(m_g0_RT.begin(), m_g0_RT.end(), grt.begin());
92 double tmp = log(pressure() / refPressure());
93 for (size_t k = 0; k < m_kk; k++) {
94 grt[k] += tmp;
95 }
96}
97
98void MixtureFugacityTP::getIntEnergy_RT(span<double> urt) const
99{
100 checkArraySize("MixtureFugacityTP::getIntEnergy_RT", urt.size(), m_kk);
101 copy(m_h0_RT.begin(), m_h0_RT.end(), urt.begin());
102 for (size_t i = 0; i < m_kk; i++) {
103 urt[i] -= 1.0;
104 }
105}
106
107void MixtureFugacityTP::getCp_R(span<double> cpr) const
108{
109 checkArraySize("MixtureFugacityTP::getCp_R", cpr.size(), m_kk);
110 copy(m_cp0_R.begin(), m_cp0_R.end(), cpr.begin());
111}
112
113void MixtureFugacityTP::getStandardVolumes(span<double> vol) const
114{
115 checkArraySize("MixtureFugacityTP::getStandardVolumes", vol.size(), m_kk);
116 for (size_t i = 0; i < m_kk; i++) {
117 vol[i] = RT() / pressure();
118 }
119}
120
121// ----- Thermodynamic Values for the Species Reference States ----
122
123void MixtureFugacityTP::getEnthalpy_RT_ref(span<double> hrt) const
124{
125 checkArraySize("MixtureFugacityTP::getEnthalpy_RT_ref", hrt.size(), m_kk);
126 copy(m_h0_RT.begin(), m_h0_RT.end(), hrt.begin());
127}
128
129void MixtureFugacityTP::getGibbs_RT_ref(span<double> grt) const
130{
131 checkArraySize("MixtureFugacityTP::getGibbs_RT_ref", grt.size(), m_kk);
132 copy(m_g0_RT.begin(), m_g0_RT.end(), grt.begin());
133}
134
135void MixtureFugacityTP::getGibbs_ref(span<double> g) const
136{
137 checkArraySize("MixtureFugacityTP::getGibbs_ref", g.size(), m_kk);
138 scale(m_g0_RT.begin(), m_g0_RT.end(), g.begin(), RT());
139}
140
141void MixtureFugacityTP::getEntropy_R_ref(span<double> er) const
142{
143 checkArraySize("MixtureFugacityTP::getEntropy_R_ref", er.size(), m_kk);
144 copy(m_s0_R.begin(), m_s0_R.end(), er.begin());
145}
146
147void MixtureFugacityTP::getCp_R_ref(span<double> cpr) const
148{
149 checkArraySize("MixtureFugacityTP::getCp_R_ref", cpr.size(), m_kk);
150 copy(m_cp0_R.begin(), m_cp0_R.end(), cpr.begin());
151}
152
154{
155 checkArraySize("MixtureFugacityTP::getStandardVolumes_ref", vol.size(), m_kk);
156 for (size_t i = 0; i < m_kk; i++) {
157 vol[i]= RT() / refPressure();
158 }
159}
160
161bool MixtureFugacityTP::addSpecies(shared_ptr<Species> spec)
162{
163 bool added = ThermoPhase::addSpecies(spec);
164 if (added) {
165 if (m_kk == 1) {
166 moleFractions_.push_back(1.0);
167 } else {
168 moleFractions_.push_back(0.0);
169 }
170 m_h0_RT.push_back(0.0);
171 m_cp0_R.push_back(0.0);
172 m_g0_RT.push_back(0.0);
173 m_s0_R.push_back(0.0);
174 }
175 return added;
176}
177
179{
182 // depends on mole fraction and temperature
183 updateMixingExpressions();
184 iState_ = phaseState(true);
185}
186
188{
189 // A pretty tricky algorithm is needed here, due to problems involving
190 // standard states of real fluids. For those cases you need to combine the T
191 // and P specification for the standard state, or else you may venture into
192 // the forbidden zone, especially when nearing the triple point. Therefore,
193 // we need to do the standard state thermo calc with the (t, pres) combo.
194
195 double t = temperature();
196 double rhoNow = density();
197 if (forcedState_ == FLUID_UNDEFINED) {
198 double rho = densityCalc(t, p, iState_, rhoNow);
199 if (rho > 0.0) {
200 setDensity(rho);
201 iState_ = phaseState(true);
202 } else {
203 if (rho < -1.5) {
204 rho = densityCalc(t, p, FLUID_UNDEFINED , rhoNow);
205 if (rho > 0.0) {
206 setDensity(rho);
207 iState_ = phaseState(true);
208 } else {
209 throw CanteraError("MixtureFugacityTP::setPressure",
210 "neg rho");
211 }
212 } else {
213 throw CanteraError("MixtureFugacityTP::setPressure",
214 "neg rho");
215 }
216 }
217 } else if (forcedState_ == FLUID_GAS) {
218 // Normal density calculation
219 if (iState_ < FLUID_LIQUID_0) {
220 double rho = densityCalc(t, p, iState_, rhoNow);
221 if (rho > 0.0) {
222 setDensity(rho);
223 iState_ = phaseState(true);
224 if (iState_ >= FLUID_LIQUID_0) {
225 throw CanteraError("MixtureFugacityTP::setPressure",
226 "wrong state");
227 }
228 } else {
229 throw CanteraError("MixtureFugacityTP::setPressure",
230 "neg rho");
231 }
232 }
233 } else if (forcedState_ > FLUID_LIQUID_0) {
234 if (iState_ >= FLUID_LIQUID_0) {
235 double rho = densityCalc(t, p, iState_, rhoNow);
236 if (rho > 0.0) {
237 setDensity(rho);
238 iState_ = phaseState(true);
239 if (iState_ == FLUID_GAS) {
240 throw CanteraError("MixtureFugacityTP::setPressure",
241 "wrong state");
242 }
243 } else {
244 throw CanteraError("MixtureFugacityTP::setPressure",
245 "neg rho");
246 }
247 }
248 }
249}
250
252{
255 updateMixingExpressions();
256}
257
259{
261 double p_RT = pressure() / RT();
262 for (size_t k = 0; k < m_kk; k++) {
263 c[k] *= moleFraction(k)*p_RT;
264 }
265}
266
268{
269 return pressure() * meanMolecularWeight() / (density() * RT());
270}
271
273{
274 throw NotImplementedError("MixtureFugacityTP::sresid");
275}
276
278{
279 throw NotImplementedError("MixtureFugacityTP::hresid");
280}
281
282double MixtureFugacityTP::psatEst(double TKelvin) const
283{
284 double pcrit = critPressure();
285 double tt = critTemperature() / TKelvin;
286 if (tt < 1.0) {
287 return pcrit;
288 }
289 double lpr = -0.8734*tt*tt - 3.4522*tt + 4.2918;
290 return pcrit*exp(lpr);
291}
292
293double MixtureFugacityTP::liquidVolEst(double TKelvin, double& pres) const
294{
295 throw NotImplementedError("MixtureFugacityTP::liquidVolEst");
296}
297
298double MixtureFugacityTP::densityCalc(double TKelvin, double presPa,
299 int phase, double rhoguess)
300{
301 double tcrit = critTemperature();
302 double mmw = meanMolecularWeight();
303 if (rhoguess == -1.0) {
304 if (phase != -1) {
305 if (TKelvin > tcrit) {
306 rhoguess = presPa * mmw / (GasConstant * TKelvin);
307 } else {
308 if (phase == FLUID_GAS || phase == FLUID_SUPERCRIT) {
309 rhoguess = presPa * mmw / (GasConstant * TKelvin);
310 } else if (phase >= FLUID_LIQUID_0) {
311 double lqvol = liquidVolEst(TKelvin, presPa);
312 rhoguess = mmw / lqvol;
313 }
314 }
315 } else {
316 // Assume the Gas phase initial guess, if nothing is specified to
317 // the routine
318 rhoguess = presPa * mmw / (GasConstant * TKelvin);
319 }
320 }
321
322 double molarVolBase = mmw / rhoguess;
323 double molarVolLast = molarVolBase;
324 double vc = mmw / critDensity();
325
326 // molar volume of the spinodal at the current temperature and mole
327 // fractions. this will be updated as we go.
328 double molarVolSpinodal = vc;
329 bool conv = false;
330
331 // We start on one side of the vc and stick with that side
332 bool gasSide = molarVolBase > vc;
333 if (gasSide) {
334 molarVolLast = (GasConstant * TKelvin)/presPa;
335 } else {
336 molarVolLast = liquidVolEst(TKelvin, presPa);
337 }
338
339 // OK, now we do a small solve to calculate the molar volume given the T,P
340 // value. The algorithm is taken from dfind()
341 for (int n = 0; n < 200; n++) {
342 // Calculate the predicted reduced pressure, pred0, based on the current
343 // tau and dd. Calculate the derivative of the predicted pressure wrt
344 // the molar volume. This routine also returns the pressure, presBase
345 double presBase;
346 double dpdVBase = dpdVCalc(TKelvin, molarVolBase, presBase);
347
348 // If dpdV is positive, then we are in the middle of the 2 phase region
349 // and beyond the spinodal stability curve. We need to adjust the
350 // initial guess outwards and start a new iteration.
351 if (dpdVBase >= 0.0) {
352 if (TKelvin > tcrit) {
353 throw CanteraError("MixtureFugacityTP::densityCalc",
354 "T > tcrit unexpectedly");
355 }
356
357 // TODO Spawn a calculation for the value of the spinodal point that
358 // is very accurate. Answer the question as to whether a
359 // solution is possible on the current side of the vapor dome.
360 if (gasSide) {
361 if (molarVolBase >= vc) {
362 molarVolSpinodal = molarVolBase;
363 molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal);
364 } else {
365 molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal);
366 }
367 } else {
368 if (molarVolBase <= vc) {
369 molarVolSpinodal = molarVolBase;
370 molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal);
371 } else {
372 molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal);
373 }
374 }
375 continue;
376 }
377
378 // Check for convergence
379 if (fabs(presBase-presPa) < 1.0E-30 + 1.0E-8 * presPa) {
380 conv = true;
381 break;
382 }
383
384 // Dampen and crop the update
385 double dpdV = dpdVBase;
386 if (n < 10) {
387 dpdV = dpdVBase * 1.5;
388 }
389
390 // Formulate the update to the molar volume by Newton's method. Then,
391 // crop it to a max value of 0.1 times the current volume
392 double delMV = - (presBase - presPa) / dpdV;
393 if ((!gasSide || delMV < 0.0) && fabs(delMV) > 0.2 * molarVolBase) {
394 delMV = delMV / fabs(delMV) * 0.2 * molarVolBase;
395 }
396 // Only go 1/10 the way towards the spinodal at any one time.
397 if (TKelvin < tcrit) {
398 if (gasSide) {
399 if (delMV < 0.0 && -delMV > 0.5 * (molarVolBase - molarVolSpinodal)) {
400 delMV = - 0.5 * (molarVolBase - molarVolSpinodal);
401 }
402 } else {
403 if (delMV > 0.0 && delMV > 0.5 * (molarVolSpinodal - molarVolBase)) {
404 delMV = 0.5 * (molarVolSpinodal - molarVolBase);
405 }
406 }
407 }
408 // updated the molar volume value
409 molarVolLast = molarVolBase;
410 molarVolBase += delMV;
411
412 if (fabs(delMV/molarVolBase) < 1.0E-14) {
413 conv = true;
414 break;
415 }
416
417 // Check for negative molar volumes
418 if (molarVolBase <= 0.0) {
419 molarVolBase = std::min(1.0E-30, fabs(delMV*1.0E-4));
420 }
421 }
422
423 // Check for convergence, and return 0.0 if it wasn't achieved.
424 double densBase = 0.0;
425 if (! conv) {
426 molarVolBase = 0.0;
427 throw CanteraError("MixtureFugacityTP::densityCalc",
428 "Process did not converge");
429 } else {
430 densBase = mmw / molarVolBase;
431 }
432 return densBase;
433}
434
435void MixtureFugacityTP::updateMixingExpressions()
436{
437}
438
439int MixtureFugacityTP::corr0(double TKelvin, double pres, double& densLiqGuess,
440 double& densGasGuess, double& liqGRT, double& gasGRT)
441{
442 int retn = 0;
443 double densLiq = densityCalc(TKelvin, pres, FLUID_LIQUID_0, densLiqGuess);
444 if (densLiq <= 0.0) {
445 retn = -1;
446 } else {
447 densLiqGuess = densLiq;
448 setState_TD(TKelvin, densLiq);
449 liqGRT = gibbs_mole() / RT();
450 }
451
452 double densGas = densityCalc(TKelvin, pres, FLUID_GAS, densGasGuess);
453 if (densGas <= 0.0) {
454 if (retn == -1) {
455 throw CanteraError("MixtureFugacityTP::corr0",
456 "Error occurred trying to find gas density at (T,P) = {} {}",
457 TKelvin, pres);
458 }
459 retn = -2;
460 } else {
461 densGasGuess = densGas;
462 setState_TD(TKelvin, densGas);
463 gasGRT = gibbs_mole() / RT();
464 }
465 return retn;
466}
467
468int MixtureFugacityTP::phaseState(bool checkState) const
469{
470 int state = iState_;
471 if (checkState) {
472 double t = temperature();
473 double tcrit = critTemperature();
474 double rhocrit = critDensity();
475 if (t >= tcrit) {
476 return FLUID_SUPERCRIT;
477 }
478 double tmid = tcrit - 100.;
479 if (tmid < 0.0) {
480 tmid = tcrit / 2.0;
481 }
482 double pp = psatEst(tmid);
483 double mmw = meanMolecularWeight();
484 double molVolLiqTmid = liquidVolEst(tmid, pp);
485 double molVolGasTmid = GasConstant * tmid / pp;
486 double densLiqTmid = mmw / molVolLiqTmid;
487 double densGasTmid = mmw / molVolGasTmid;
488 double densMidTmid = 0.5 * (densLiqTmid + densGasTmid);
489 double rhoMid = rhocrit + (t - tcrit) * (rhocrit - densMidTmid) / (tcrit - tmid);
490
491 double rho = density();
492 int iStateGuess = FLUID_LIQUID_0;
493 if (rho < rhoMid) {
494 iStateGuess = FLUID_GAS;
495 }
496 double molarVol = mmw / rho;
497 double presCalc;
498
499 double dpdv = dpdVCalc(t, molarVol, presCalc);
500 if (dpdv < 0.0) {
501 state = iStateGuess;
502 } else {
503 state = FLUID_UNSTABLE;
504 }
505 }
506 return state;
507}
508
509double MixtureFugacityTP::satPressure(double TKelvin)
510{
511 double molarVolGas;
512 double molarVolLiquid;
513 return calculatePsat(TKelvin, molarVolGas, molarVolLiquid);
514}
515
516double MixtureFugacityTP::calculatePsat(double TKelvin, double& molarVolGas,
517 double& molarVolLiquid)
518{
519 // The algorithm for this routine has undergone quite a bit of work. It
520 // probably needs more work. However, it seems now to be fairly robust. The
521 // key requirement is to find an initial pressure where both the liquid and
522 // the gas exist. This is not as easy as it sounds, and it gets exceedingly
523 // hard as the critical temperature is approached from below. Once we have
524 // this initial state, then we seek to equilibrate the Gibbs free energies
525 // of the gas and liquid and use the formula
526 //
527 // dp = VdG
528 //
529 // to create an update condition for deltaP using
530 //
531 // - (Gliq - Ggas) = (Vliq - Vgas) (deltaP)
532 //
533 // @todo Suggestions for the future would be to switch it to an algorithm
534 // that uses the gas molar volume and the liquid molar volumes as the
535 // fundamental unknowns.
536
537 // we need this because this is a non-const routine that is public
538 setTemperature(TKelvin);
539 double densSave = density();
540 double tempSave = temperature();
541 double pres;
542 double mw = meanMolecularWeight();
543 if (TKelvin < critTemperature()) {
544 pres = psatEst(TKelvin);
545 // trial value = Psat from correlation
546 double volLiquid = liquidVolEst(TKelvin, pres);
547 double RhoLiquidGood = mw / volLiquid;
548 double RhoGasGood = pres * mw / (GasConstant * TKelvin);
549 double delGRT = 1.0E6;
550 double liqGRT, gasGRT;
551
552 // First part of the calculation involves finding a pressure at which
553 // the gas and the liquid state coexists.
554 double presLiquid = 0.;
555 double presGas;
556 double presBase = pres;
557 bool foundLiquid = false;
558 bool foundGas = false;
559
560 double densLiquid = densityCalc(TKelvin, presBase, FLUID_LIQUID_0, RhoLiquidGood);
561 if (densLiquid > 0.0) {
562 foundLiquid = true;
563 presLiquid = pres;
564 RhoLiquidGood = densLiquid;
565 }
566 if (!foundLiquid) {
567 for (int i = 0; i < 50; i++) {
568 pres = 1.1 * pres;
569 densLiquid = densityCalc(TKelvin, pres, FLUID_LIQUID_0, RhoLiquidGood);
570 if (densLiquid > 0.0) {
571 foundLiquid = true;
572 presLiquid = pres;
573 RhoLiquidGood = densLiquid;
574 break;
575 }
576 }
577 }
578
579 pres = presBase;
580 double densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood);
581 if (densGas <= 0.0) {
582 foundGas = false;
583 } else {
584 foundGas = true;
585 presGas = pres;
586 RhoGasGood = densGas;
587 }
588 if (!foundGas) {
589 for (int i = 0; i < 50; i++) {
590 pres = 0.9 * pres;
591 densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood);
592 if (densGas > 0.0) {
593 foundGas = true;
594 presGas = pres;
595 RhoGasGood = densGas;
596 break;
597 }
598 }
599 }
600
601 if (foundGas && foundLiquid && presGas != presLiquid) {
602 pres = 0.5 * (presLiquid + presGas);
603 bool goodLiq;
604 bool goodGas;
605 for (int i = 0; i < 50; i++) {
606 densLiquid = densityCalc(TKelvin, pres, FLUID_LIQUID_0, RhoLiquidGood);
607 if (densLiquid <= 0.0) {
608 goodLiq = false;
609 } else {
610 goodLiq = true;
611 RhoLiquidGood = densLiquid;
612 presLiquid = pres;
613 }
614 densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood);
615 if (densGas <= 0.0) {
616 goodGas = false;
617 } else {
618 goodGas = true;
619 RhoGasGood = densGas;
620 presGas = pres;
621 }
622 if (goodGas && goodLiq) {
623 break;
624 }
625 if (!goodLiq && !goodGas) {
626 pres = 0.5 * (pres + presLiquid);
627 }
628 if (goodLiq || goodGas) {
629 pres = 0.5 * (presLiquid + presGas);
630 }
631 }
632 }
633 if (!foundGas || !foundLiquid) {
634 warn_user("MixtureFugacityTP::calculatePsat",
635 "could not find a starting pressure; exiting.");
636 return 0.0;
637 }
638 if (presGas != presLiquid) {
639 warn_user("MixtureFugacityTP::calculatePsat",
640 "could not find a starting pressure; exiting");
641 return 0.0;
642 }
643
644 pres = presGas;
645 double presLast = pres;
646 double RhoGas = RhoGasGood;
647 double RhoLiquid = RhoLiquidGood;
648
649 // Now that we have found a good pressure we can proceed with the algorithm.
650 for (int i = 0; i < 20; i++) {
651 int stab = corr0(TKelvin, pres, RhoLiquid, RhoGas, liqGRT, gasGRT);
652 if (stab == 0) {
653 presLast = pres;
654 delGRT = liqGRT - gasGRT;
655 double delV = mw * (1.0/RhoLiquid - 1.0/RhoGas);
656 double dp = - delGRT * GasConstant * TKelvin / delV;
657
658 if (fabs(dp) > 0.1 * pres) {
659 if (dp > 0.0) {
660 dp = 0.1 * pres;
661 } else {
662 dp = -0.1 * pres;
663 }
664 }
665 pres += dp;
666 } else if (stab == -1) {
667 delGRT = 1.0E6;
668 if (presLast > pres) {
669 pres = 0.5 * (presLast + pres);
670 } else {
671 // we are stuck here - try this
672 pres = 1.1 * pres;
673 }
674 } else if (stab == -2) {
675 if (presLast < pres) {
676 pres = 0.5 * (presLast + pres);
677 } else {
678 // we are stuck here - try this
679 pres = 0.9 * pres;
680 }
681 }
682 molarVolGas = mw / RhoGas;
683 molarVolLiquid = mw / RhoLiquid;
684
685 if (fabs(delGRT) < 1.0E-8) {
686 // converged
687 break;
688 }
689 }
690
691 molarVolGas = mw / RhoGas;
692 molarVolLiquid = mw / RhoLiquid;
693 // Put the fluid in the desired end condition
694 setState_TD(tempSave, densSave);
695 return pres;
696 } else {
697 pres = critPressure();
698 setState_TP(TKelvin, pres);
699 molarVolGas = mw / density();
700 molarVolLiquid = molarVolGas;
701 setState_TD(tempSave, densSave);
702 }
703 return pres;
704}
705
706double MixtureFugacityTP::dpdVCalc(double TKelvin, double molarVol, double& presCalc) const
707{
708 throw NotImplementedError("MixtureFugacityTP::dpdVCalc");
709}
710
712{
713 double Tnow = temperature();
714
715 // If the temperature has changed since the last time these
716 // properties were computed, recompute them.
717 if (m_tlast != Tnow) {
719 m_tlast = Tnow;
720
721 // update the species Gibbs functions
722 for (size_t k = 0; k < m_kk; k++) {
723 m_g0_RT[k] = m_h0_RT[k] - m_s0_R[k];
724 }
725 double pref = refPressure();
726 if (pref <= 0.0) {
727 throw CanteraError("MixtureFugacityTP::_updateReferenceStateThermo",
728 "negative reference pressure");
729 }
730 }
731}
732
734{
735 double pc, tc, vc;
736 calcCriticalConditions(pc, tc, vc);
737 return tc;
738}
739
741{
742 double pc, tc, vc;
743 calcCriticalConditions(pc, tc, vc);
744 return pc;
745}
746
748{
749 double pc, tc, vc;
750 calcCriticalConditions(pc, tc, vc);
751 return vc;
752}
753
755{
756 double pc, tc, vc;
757 calcCriticalConditions(pc, tc, vc);
758 return pc*vc/tc/GasConstant;
759}
760
762{
763 double pc, tc, vc;
764 calcCriticalConditions(pc, tc, vc);
765 double mmw = meanMolecularWeight();
766 return mmw / vc;
767}
768
769void MixtureFugacityTP::calcCriticalConditions(double& pc, double& tc, double& vc) const
770{
771 throw NotImplementedError("MixtureFugacityTP::calcCriticalConditions");
772}
773
774int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b,
775 double aAlpha, span<double> Vroot, double an,
776 double bn, double cn, double dn, double tc, double vc) const
777{
778 checkArraySize("MixtureFugacityTP::solveCubic: Vroot", Vroot.size(), 3);
779 fill(Vroot.begin(), Vroot.end(), 0.0);
780 if (T <= 0.0) {
781 throw CanteraError("MixtureFugacityTP::solveCubic",
782 "negative temperature T = {}", T);
783 }
784
785 // Derive the center of the cubic, x_N
786 double xN = - bn /(3 * an);
787
788 // Derive the value of delta**2. This is a key quantity that determines the number of turning points
789 double deltaNumerator = bn * bn - 3 * an * cn;
790 double delta2 = deltaNumerator / (9 * an * an);
791 double delta = 0.0;
792
793 // Calculate a couple of ratios
794 // Cubic equation in z : z^3 - (1-B) z^2 + (A -2B -3B^2)z - (AB- B^2- B^3) = 0
795 double ratio1 = 3.0 * an * cn / (bn * bn);
796 double ratio2 = pres * b / (GasConstant * T); // B
797 if (fabs(ratio1) < 1.0E-7) {
798 double ratio3 = aAlpha / (GasConstant * T) * pres / (GasConstant * T); // A
799 if (fabs(ratio2) < 1.0E-5 && fabs(ratio3) < 1.0E-5) {
800 // A and B terms in cubic equation for z are almost zero, then z is near to 1
801 double zz = 1.0;
802 for (int i = 0; i < 10; i++) {
803 double znew = zz / (zz - ratio2) - ratio3 / (zz + ratio1);
804 double deltaz = znew - zz;
805 zz = znew;
806 if (fabs(deltaz) < 1.0E-14) {
807 break;
808 }
809 }
810 double v = zz * GasConstant * T / pres;
811 Vroot[0] = v;
812 return 1;
813 }
814 }
815
816 int nSolnValues = -1; // Represents number of solutions to the cubic equation
817 double h2 = 4. * an * an * delta2 * delta2 * delta2; // h^2
818 if (delta2 > 0.0) {
819 delta = sqrt(delta2);
820 }
821
822 double h = 2.0 * an * delta * delta2;
823 double yTerm1 = 2.0 * bn * bn * bn / (27.0 * an * an);
824 double yTerm2 = -bn * cn / (3.0 * an);
825 double yN = yTerm1 + yTerm2 + dn; // y_N term
826 double disc = yN * yN - h2; // discriminant
827
828 // At a triple root, both terms of the depressed cubic are zero. Detect
829 // cancellation relative to the terms used to calculate them so that the
830 // result does not depend on compiler-specific floating-point evaluation.
831 // The factor of 64 allows for roundoff accumulated across the arithmetic
832 // operations; the scales below convert this relative tolerance to an
833 // absolute tolerance for each cancellation residual.
834 double cancellationTol = 64 * std::numeric_limits<double>::epsilon();
835 double deltaScale = max(fabs(bn * bn), fabs(3 * an * cn));
836 double yScale = max({fabs(yTerm1), fabs(yTerm2), fabs(dn)});
837 bool tripleRoot = (fabs(deltaNumerator) <= cancellationTol * deltaScale
838 && fabs(yN) <= cancellationTol * yScale);
839 if (tripleRoot) {
840 delta = 0.0;
841 disc = 0.0;
842 }
843
844 //check if y = h
845 if (!tripleRoot && fabs(fabs(h) - fabs(yN)) < 1.0E-10) {
846 if (disc > 1e-10) {
847 throw CanteraError("MixtureFugacityTP::solveCubic",
848 "value of yN and h are too high, unrealistic roots may be obtained");
849 }
850 disc = 0.0;
851 }
852
853 if (disc < -1e-14) {
854 // disc<0 then we have three distinct roots.
855 nSolnValues = 3;
856 } else if (tripleRoot) {
857 // At the critical point, the cubic has one distinct real root with
858 // multiplicity three. Report one usable root since there is no
859 // meaningful liquid/gas branch distinction.
860 nSolnValues = 1;
861 } else if (fabs(disc) < 1e-14) {
862 // disc=0 then we have two distinct roots (third one is repeated root)
863 nSolnValues = 2;
864 // We are here as p goes to zero.
865 } else if (disc > 1e-14) {
866 // disc> 0 then we have one real root.
867 nSolnValues = 1;
868 }
869
870 double tmp;
871 auto physicalRoot = [b](double v) {
872 // For cubic EoS, physically admissible roots satisfy V > b and V > 0.
873 double vmin = std::max(0.0, b * (1.0 + 1e-12));
874 return std::isfinite(v) && v > vmin;
875 };
876 // One real root -> have to determine whether gas or liquid is the root
877 if (disc > 0.0) {
878 double tmpD = sqrt(disc);
879 double tmp1 = (- yN + tmpD) / (2.0 * an);
880 double sgn1 = 1.0;
881 if (tmp1 < 0.0) {
882 sgn1 = -1.0;
883 tmp1 = -tmp1;
884 }
885 double tmp2 = (- yN - tmpD) / (2.0 * an);
886 double sgn2 = 1.0;
887 if (tmp2 < 0.0) {
888 sgn2 = -1.0;
889 tmp2 = -tmp2;
890 }
891 double p1 = pow(tmp1, 1./3.);
892 double p2 = pow(tmp2, 1./3.);
893 double alpha = xN + sgn1 * p1 + sgn2 * p2;
894 Vroot[0] = alpha;
895 Vroot[1] = 0.0;
896 Vroot[2] = 0.0;
897 } else if (disc < 0.0) {
898 // Three real roots alpha, beta, gamma are obtained.
899 double val = acos(-yN / h);
900 double theta = val / 3.0;
901 double twoThirdPi = 2. * Pi / 3.;
902 double alpha = xN + 2. * delta * cos(theta);
903 double beta = xN + 2. * delta * cos(theta + twoThirdPi);
904 double gamma = xN + 2. * delta * cos(theta + 2.0 * twoThirdPi);
905 Vroot[0] = beta;
906 Vroot[1] = gamma;
907 Vroot[2] = alpha;
908
909 for (int i = 0; i < 3; i++) {
910 tmp = an * Vroot[i] * Vroot[i] * Vroot[i] + bn * Vroot[i] * Vroot[i] + cn * Vroot[i] + dn;
911 if (fabs(tmp) > 1.0E-4) {
912 for (int j = 0; j < 3; j++) {
913 if (j != i && fabs(Vroot[i] - Vroot[j]) < 1.0E-4 * (fabs(Vroot[i]) + fabs(Vroot[j]))) {
914 warn_user("MixtureFugacityTP::solveCubic",
915 "roots have merged for T = {}, p = {}: {}, {}",
916 T, pres, Vroot[i], Vroot[j]);
917 }
918 }
919 }
920 }
921 } else if (disc == 0.0) {
922 //Three equal roots are obtained, that is, alpha = beta = gamma
923 if (tripleRoot) {
924 // yN = 0.0 and h = 0 (that is, disc = 0)
925 Vroot[0] = xN;
926 } else {
927 // h and yN need to figure out whether delta^3 is positive or negative
928 if (yN > 0.0) {
929 tmp = cbrt(yN / (2 * an));
930 // In this case, tmp and delta must be equal.
931 if (fabs(tmp - delta) > 1.0E-9) {
932 throw CanteraError("MixtureFugacityTP::solveCubic",
933 "Inconsistency in solver: solver is ill-conditioned.");
934 }
935 Vroot[1] = xN + delta;
936 Vroot[0] = xN - 2.0*delta; // liquid phase root
937 } else {
938 tmp = cbrt(yN / (2 * an));
939 // In this case, tmp and delta have equal magnitudes and
940 // opposite signs.
941 if (fabs(tmp + delta) > 1.0E-9) {
942 throw CanteraError("MixtureFugacityTP::solveCubic",
943 "Inconsistency in solver: solver is ill-conditioned.");
944 }
945 delta = -delta;
946 Vroot[0] = xN + delta;
947 Vroot[1] = xN - 2.0*delta; // gas phase root
948 }
949 }
950 }
951
952 // Find an accurate root, since there might be a heavy amount of roundoff error due to bad conditioning in this solver.
953 double res, dresdV = 0.0;
954 for (int i = 0; i < nSolnValues; i++) {
955 if (!physicalRoot(Vroot[i])) {
956 // Non-physical roots (for example, negative molar volume) are not
957 // used for state selection and should not trigger convergence errors.
958 continue;
959 }
960 for (int n = 0; n < 20; n++) {
961 res = an * Vroot[i] * Vroot[i] * Vroot[i] + bn * Vroot[i] * Vroot[i] + cn * Vroot[i] + dn;
962 if (fabs(res) < 1.0E-14) { // accurate root is obtained
963 break;
964 }
965 dresdV = 3.0 * an * Vroot[i] * Vroot[i] + 2.0 * bn * Vroot[i] + cn; // derivative of the residual
966 double del = - res / dresdV;
967 Vroot[i] += del;
968 if (fabs(del) / (fabs(Vroot[i]) + fabs(del)) < 1.0E-14) {
969 break;
970 }
971 double res2 = an * Vroot[i] * Vroot[i] * Vroot[i] + bn * Vroot[i] * Vroot[i] + cn * Vroot[i] + dn;
972 if (fabs(res2) < fabs(res)) {
973 continue;
974 } else {
975 Vroot[i] -= del; // Go back to previous value of Vroot.
976 Vroot[i] += 0.1 * del; // under-relax by 0.1
977 }
978 }
979 if ((fabs(res) > 1.0E-14) && (fabs(res) > 1.0E-14 * fabs(dresdV) * fabs(Vroot[i]))) {
980 throw CanteraError("MixtureFugacityTP::solveCubic",
981 "root failed to converge for T = {}, p = {} with "
982 "V = {}", T, pres, Vroot[i]);
983 }
984 }
985 if (nSolnValues == 1 && !physicalRoot(Vroot[0])) {
986 throw CanteraError("MixtureFugacityTP::solveCubic",
987 "single real root is non-physical for T = {}, p = {} "
988 "(V = {}, b = {})", T, pres, Vroot[0], b);
989 }
990
991 if (nSolnValues == 1) {
992 // Determine the phase of the single root.
993 // nSolnValues = 1 represents the gas phase by default.
994 if (T > tc) {
995 if (Vroot[0] < vc) {
996 // Supercritical phase
997 nSolnValues = -1;
998 }
999 } else {
1000 if (Vroot[0] < xN) {
1001 //Liquid phase
1002 nSolnValues = -1;
1003 }
1004 }
1005 } else {
1006 // Determine if we have two distinct roots or three equal roots
1007 // nSolnValues = 2 represents 2 equal roots by default.
1008 if (nSolnValues == 2 && delta > 1e-14) {
1009 //If delta > 0, we have two distinct roots (and one repeated root)
1010 nSolnValues = -2;
1011 }
1012 }
1013 return nSolnValues;
1014}
1015
1016}
Header file for a derived class of ThermoPhase that handles non-ideal mixtures based on the fugacity ...
#define FLUID_UNSTABLE
Various states of the Fugacity object.
Base class for exceptions thrown by Cantera classes.
int iState_
Current state of the fluid.
void getGibbs_ref(span< double > g) const override
Returns the vector of the Gibbs function of the reference state at the current temperature of the sol...
int reportSolnBranchActual() const
Report the solution branch which the solution is actually on.
double enthalpy_mole() const override
Molar enthalpy. Units: J/kmol.
void getCp_R(span< double > cpr) const override
Get the nondimensional Heat Capacities at constant pressure for the standard state of the species at ...
int standardStateConvention() const override
This method returns the convention used in specification of the standard state, of which there are cu...
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...
vector< double > m_g0_RT
Temporary storage for dimensionless reference state Gibbs energies.
void getIntEnergy_RT(span< double > urt) const override
Returns the vector of nondimensional internal Energies of the standard state at the current temperatu...
double critPressure() const override
Critical pressure (Pa).
double critDensity() const override
Critical density (kg/m3).
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).
void getGibbs_RT(span< double > grt) const override
Get the nondimensional Gibbs functions for the species at their standard states of solution at the cu...
void getCp_R_ref(span< double > cprt) const override
Returns the vector of nondimensional constant pressure heat capacities of the reference state at the ...
virtual void _updateReferenceStateThermo() const
Updates the reference state thermodynamic functions at the current T of the solution.
double satPressure(double TKelvin) override
Calculate the saturation pressure at the current mixture content for the given temperature.
double critCompressibility() const override
Critical compressibility (unitless).
void setPressure(double p) override
Set the internally stored pressure (Pa) at constant temperature and composition.
void getEnthalpy_RT_ref(span< double > hrt) const override
Returns the vector of nondimensional enthalpies of the reference state at the current temperature of ...
void getEnthalpy_RT(span< double > hrt) const override
Get the nondimensional Enthalpy functions for the species at their standard states at the current T a...
double calculatePsat(double TKelvin, double &molarVolGas, double &molarVolLiquid)
Calculate the saturation pressure at the current mixture content for the given temperature.
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 getEntropy_R(span< double > sr) const override
Get the array of nondimensional Enthalpy functions for the standard state species at the current T an...
void setTemperature(const double temp) override
Set the temperature of the phase.
vector< double > m_s0_R
Temporary storage for dimensionless reference state entropies.
virtual double dpdVCalc(double TKelvin, double molarVol, double &presCalc) const
Calculate the pressure and the pressure derivative given the temperature and the molar volume.
double entropy_mole() const override
Molar entropy. Units: J/kmol/K.
virtual double densityCalc(double TKelvin, double pressure, int phaseRequested, double rhoguess)
Calculates the density given the temperature and the pressure and a guess at the density.
double critVolume() const override
Critical volume (m3/kmol).
virtual double psatEst(double TKelvin) const
Estimate for the saturation pressure.
void getGibbs_RT_ref(span< double > grt) const override
Returns the vector of nondimensional Gibbs Free Energies of the reference state at the current temper...
virtual double sresid() const
Calculate the deviation terms for the total entropy of the mixture from the ideal gas mixture.
int forcedState_
Force the system to be on a particular side of the spinodal curve.
virtual double liquidVolEst(double TKelvin, double &pres) const
Estimate for the molar volume of the liquid.
int forcedSolutionBranch() const
Report the solution branch which the solution is restricted to.
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...
void compositionChanged() override
Apply changes to the state which are needed after the composition changes.
vector< double > m_cp0_R
Temporary storage for dimensionless reference state heat capacities.
int corr0(double TKelvin, double pres, double &densLiq, double &densGas, double &liqGRT, double &gasGRT)
Utility routine in the calculation of the saturation pressure.
void setForcedSolutionBranch(int solnBranch)
Set the solution branch to force the ThermoPhase to exist on one branch or another.
bool addSpecies(shared_ptr< Species > spec) override
Add a Species to this Phase.
virtual double hresid() const
Calculate the deviation terms for the total enthalpy of the mixture from the ideal gas mixture.
void getActivityConcentrations(span< double > c) const override
This method returns an array of generalized concentrations.
double z() const
Calculate the value of z.
void getStandardVolumes_ref(span< double > vol) const override
Get the molar volumes of the species reference states at the current T and P_ref of the solution.
int phaseState(bool checkState=false) const
Returns the Phase State flag for the current state of the object.
virtual void update(double T, span< double > cp_R, span< double > h_RT, span< double > s_R) const
Compute the reference-state properties for all species.
An error indicating that an unimplemented function has been called.
void getMoleFractions(span< double > x) const
Get the species mole fraction vector.
Definition Phase.cpp:451
size_t m_kk
Number of species in the phase.
Definition Phase.h:882
virtual void setState_TD(double t, double rho)
Set the internally stored temperature (K) and density (kg/m^3)
Definition Phase.cpp:385
double temperature() const
Temperature (K).
Definition Phase.h:586
double meanMolecularWeight() const
The mean molecular weight. Units: (kg/kmol)
Definition Phase.h:677
virtual void setDensity(const double density_)
Set the internally stored density (kg/m^3) of the phase.
Definition Phase.cpp:607
double sum_xlogx() const
Evaluate .
Definition Phase.cpp:643
double mean_X(span< const double > Q) const
Evaluate the mole-fraction-weighted mean of an array Q.
Definition Phase.cpp:637
double moleFraction(size_t k) const
Return the mole fraction of a single species.
Definition Phase.cpp:457
virtual double density() const
Density (kg/m^3).
Definition Phase.h:611
virtual void compositionChanged()
Apply changes to the state which are needed after the composition changes.
Definition Phase.cpp:987
virtual void setTemperature(double temp)
Set the internally stored temperature of the phase (K).
Definition Phase.h:647
virtual double pressure() const
Return the thermodynamic pressure (Pa).
Definition Phase.h:604
virtual void setState_TP(double t, double p)
Set the temperature (K) and pressure (Pa)
double RT() const
Return the Gas Constant multiplied by the current temperature.
double m_tlast
last value of the temperature processed by reference state
MultiSpeciesThermo m_spthermo
Pointer to the calculation manager for species reference-state thermodynamic properties.
virtual double refPressure() const
Returns the reference pressure in Pa.
bool addSpecies(shared_ptr< Species > spec) override
Add a Species to this Phase.
virtual double gibbs_mole() const
Molar Gibbs function. Units: J/kmol.
virtual void getActivityCoefficients(span< double > ac) const
Get the array of non-dimensional molar-based activity coefficients at the current solution temperatur...
This file contains definitions for utility functions and text for modules, inputfiles and logging,...
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
const double Pi
Pi.
Definition ct_defs.h:71
void warn_user(const string &method, const string &msg, const Args &... args)
Print a user warning raised from method as CanteraWarning.
Definition global.h:263
Namespace for the Cantera kernel.
Definition AnyMap.cpp:595
const int cSS_CONVENTION_TEMPERATURE
Standard state uses the molar convention.
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...