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