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