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