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