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