Cantera  4.0.0a2
Loading...
Searching...
No Matches
Sub.cpp
Go to the documentation of this file.
1/**
2 * @file Sub.cpp
3 * The Substance class
4 * D. Goodwin, Caltech Nov. 1996
5 */
6
7// This file is part of Cantera. See License.txt in the top-level directory or
8// at https://cantera.org/license.txt for license and copyright information.
9
10#include "cantera/tpx/Sub.h"
12#include "cantera/base/global.h"
13#include <cmath>
14#include <limits>
15
16using namespace Cantera;
17
18namespace {
19// these correspond to ordering withing propertyFlag::type
20string propertySymbols[] = {"H", "S", "U", "V", "P", "T"};
21}
22
23namespace tpx
24{
25
26void Substance::setStdState(double h0, double s0, double t0, double p0)
27{
28 Set(PropertyPair::TP, t0, p0);
29 double hh = h();
30 double ss = s();
31 double hoff = h0 - hh;
32 double soff = s0 - ss;
33 m_entropy_offset += soff;
34 m_energy_offset += hoff;
35}
36
38{
39 return TwoPhase() ? Ps() : Pp();
40}
41
42const double DeltaT = 0.000001;
43
45{
46 if (TwoPhase(true)) {
47 // While cv can be calculated for the two-phase region (the state can
48 // always be continuously varied along an isochor on a T-v diagram),
49 // this calculation is currently not implemented
50 return std::numeric_limits<double>::quiet_NaN();
51 }
52
53 double Tsave = T, dt = 1.e-4 * T;
54 double x0 = x();
55 double T1 = std::max(Tmin(), Tsave - dt);
56 double T2 = std::min(Tmax(), Tsave + dt);
57
58 set_T(T1);
59 double x1 = x();
60 if ((x0 == 1.0 || x0 == 0.0) && x1 != x0) {
61 // If the initial state was pure liquid or pure vapor, and the state at
62 // T-dT is not, just take a one-sided difference
63 T1 = Tsave;
64 set_T(T1);
65 }
66 double s1 = s();
67
68 set_T(T2);
69 double x2 = x();
70 if ((x0 == 1.0 || x0 == 0.0) && x2 != x0) {
71 // If the initial state was pure liquid or pure vapor, and the state at
72 // T+dT is not, just take a one-sided difference
73 T2 = Tsave;
74 set_T(T2);
75 }
76 double s2 = s();
77
78 set_T(Tsave);
79 return T*(s2 - s1)/(T2-T1);
80}
81
83{
84 if (TwoPhase(true)) {
85 // In the two-phase region, cp is infinite
86 return std::numeric_limits<double>::infinity();
87 }
88
89 double Tsave = T, dt = 1.e-4 * T;
90 double RhoSave = Rho;
91 double T1, T2, s1, s2;
92 double p0 = P();
93
94 if (Rho >= Rhf) {
95 // initial state is pure liquid
96 T1 = std::max(Tmin(), Tsave - dt);
97 Set(PropertyPair::TP, T1, p0);
98 s1 = s();
99 try {
100 T2 = std::min(Tsat(p0), Tsave + dt);
101 } catch (CanteraError&) {
102 // Tsat does not exist beyond two-phase region
103 T2 = Tsave + dt;
104 }
105 if (T2 < Tsave + dt) {
106 Set(PropertyPair::TX, T2, 0.);
107 } else {
108 Set(PropertyPair::TP, T2, p0);
109 }
110 s2 = s();
111 } else {
112 // initial state is pure vapor
113 try {
114 T1 = std::max(Tsat(p0), Tsave - dt);
115 } catch (CanteraError&) {
116 // Tsat does not exist beyond two-phase region
117 T1 = Tsave - dt;
118 }
119 if (T1 > Tsave - dt) {
120 Set(PropertyPair::TX, T1, 1.);
121 } else {
122 Set(PropertyPair::TP, T1, p0);
123 }
124 s1 = s();
125 T2 = std::min(Tmax(), Tsave + dt);
126 Set(PropertyPair::TP, T2, p0);
127 s2 = s();
128 }
129
130 Set(PropertyPair::TV, Tsave, 1.0 / RhoSave);
131 return T * (s2 - s1) / (T2 - T1);
132}
133
134double Substance::thermalExpansionCoeff()
135{
136 if (TwoPhase(true)) {
137 // In the two-phase region, the thermal expansion coefficient is
138 // infinite
139 return std::numeric_limits<double>::infinity();
140 }
141
142 double Tsave = T, dt = 1.e-4 * T;
143 double RhoSave = Rho;
144 double T1, T2, v1, v2;
145 double p0 = P();
146
147 if (Rho >= Rhf) {
148 // initial state is pure liquid
149 T1 = std::max(Tmin(), Tsave - dt);
150 Set(PropertyPair::TP, T1, p0);
151 v1 = v();
152 try {
153 T2 = std::min(Tsat(p0), Tsave + dt);
154 } catch (CanteraError&) {
155 // Tsat does not exist beyond two-phase region
156 T2 = Tsave + dt;
157 }
158 if (T2 < Tsave + dt) {
159 Set(PropertyPair::TX, T2, 0.);
160 } else {
161 Set(PropertyPair::TP, T2, p0);
162 }
163 v2 = v();
164 } else {
165 // initial state is pure vapor
166 try {
167 T1 = std::max(Tsat(p0), Tsave - dt);
168 } catch (CanteraError&) {
169 // Tsat does not exist beyond two-phase region
170 T1 = Tsave - dt;
171 }
172 if (T1 > Tsave - dt) {
173 Set(PropertyPair::TX, T1, 1.);
174 } else {
175 Set(PropertyPair::TP, T1, p0);
176 }
177 v1 = v();
178 T2 = std::min(Tmax(), Tsave + dt);
179 Set(PropertyPair::TP, T2, p0);
180 v2 = v();
181 }
182
183 Set(PropertyPair::TV, Tsave, 1.0 / RhoSave);
184 return 2.0 * (v2 - v1) / ((v2 + v1) * (T2 - T1));
185}
186
187double Substance::isothermalCompressibility()
188{
189 if (TwoPhase(true)) {
190 // In the two-phase region, the isothermal compressibility is infinite
191 return std::numeric_limits<double>::infinity();
192 }
193
194 double Psave = P(), dp = 1.e-4 * Psave;
195 double RhoSave = Rho;
196 double P1, P2, v1, v2;
197 double v0 = v();
198
199 if (Rho >= Rhf) {
200 // initial state is pure liquid
201 P1 = Psave - dp;
202 if (T > Tmin() && T <= Tcrit()) {
203 P1 = std::max(Ps(), P1);
204 }
205 if (P1 > Psave - dp) {
206 Set(PropertyPair::PX, P1, 0.);
207 } else {
208 Set(PropertyPair::TP, T, P1);
209 }
210 v1 = v();
211 P2 = Psave + dp;
212 Set(PropertyPair::TP, T, P2);
213 v2 = v();
214 } else {
215 // initial state is pure vapor
216 P1 = std::max(1.e-7, Psave - dp);
217 Set(PropertyPair::TP, T, P1);
218 v1 = v();
219 P2 = Psave + dp;
220 if (T > Tmin() && T <= Tcrit()) {
221 P2 = std::min(Ps(), P2);
222 }
223 if (P2 < Psave + dp) {
224 Set(PropertyPair::PX, P2, 1.);
225 } else {
226 Set(PropertyPair::TP, T, P2);
227 }
228 v2 = v();
229 }
230
231 Set(PropertyPair::TV, T, 1.0 / RhoSave);
232 return -(v2 - v1) / (v0 * (P2 - P1));
233}
234
236{
237 // Use the defining relation at constant temperature:
238 // pi_T = (dU/dV)|T
239 double Tsave = T;
240 double vsave = v();
241 double dv = std::max(1.e-8, 1.e-6 * std::abs(vsave));
242 double v1 = std::max(1.e-300, vsave - dv);
243 double v2 = vsave + dv;
244
245 Set(PropertyPair::TV, Tsave, v1);
246 double u1 = u();
247 Set(PropertyPair::TV, Tsave, v2);
248 double u2 = u();
249
250 Set(PropertyPair::TV, Tsave, vsave);
251 return (u2 - u1) / (v2 - v1);
252}
253
255{
256 double tsave = T;
257 double ps1 = Ps();
258 double dpdt;
259 if (T + DeltaT < Tcrit()) {
260 T += DeltaT;
261 dpdt = (Ps() - ps1)/DeltaT;
262 } else {
263 T -= DeltaT;
264 // Ps() will fail for illegal temperature
265 dpdt = (ps1 - Ps())/DeltaT;
266 }
267 T = tsave;
268 return dpdt;
269}
270
271int Substance::TwoPhase(bool strict)
272{
273 if (T >= Tcrit()) {
274 return 0;
275 }
276 update_sat();
277 if (strict) {
278 return ((Rho > Rhv) && (Rho < Rhf) ? 1 : 0);
279 }
280 return ((Rho >= Rhv) && (Rho <= Rhf) ? 1 : 0);
281}
282
284{
285 if (T >= Tcrit()) {
286 return (1.0/Rho < Vcrit() ? 0.0 : 1.0);
287 } else {
288 update_sat();
289 if (Rho <= Rhv) {
290 return 1.0;
291 } else if (Rho >= Rhf) {
292 return 0.0;
293 } else {
294 double vv = 1.0/Rhv;
295 double vl = 1.0/Rhf;
296 return (1.0/Rho - vl)/(vv - vl);
297 }
298 }
299}
300
301double Substance::Tsat(double p)
302{
303 double Tsave = T;
304 // The minimum pressure is not stored as a numerical parameter, so it is evaluated
305 // as the saturation pressure at the minimum temperature. Note that minimum
306 // temperature and pressure are often defined at the triple point.
307 T = Tmin();
308 double Pmin = Ps();
309 T = Tsave;
310 if (p <= 0. || p > Pcrit() || p < Pmin) {
311 throw CanteraError("Substance::Tsat", "Illegal pressure value: {}", p);
312 }
313 Tsave = T;
314
315 int LoopCount = 0;
316 double tol = 1.e-6*p;
317 if (T < Tmin() || T > Tcrit()) {
318 T = 0.5*(Tcrit() - Tmin());
319 }
320 double dp = 10*tol;
321 while (fabs(dp) > tol) {
322 T = std::min(T, Tcrit());
323 T = std::max(T, Tmin());
324 dp = p - Ps();
325 double dt = dp/dPsdT();
326 double dta = fabs(dt);
327 double dtm = 0.1*T;
328 if (dta > dtm) {
329 dt = dt*dtm/dta;
330 }
331 T += dt;
332 LoopCount++;
333 if (LoopCount > 100) {
334 T = Tsave;
335 throw CanteraError("Substance::Tsat", "No convergence: p = {}", p);
336 }
337 }
338 double tsat = T;
339 T = Tsave;
340 return tsat;
341}
342
343// property tolerances
344static const double TolAbsH = 1.e-4; // J/kg
345static const double TolAbsU = 1.e-4;
346static const double TolAbsS = 1.e-7;
347static const double TolAbsP = 0.0; // Pa, this is supposed to be zero
348static const double TolAbsV = 1.e-8;
349static const double TolAbsT = 1.e-3;
350static const double TolRel = 1.e-8;
351
352void Substance::Set(PropertyPair::type XY, double x0, double y0)
353{
354 double temp;
355
356 /* if inverted (PT) switch order and change sign of XY (TP = -PT) */
357 if (XY < 0) {
358 std::swap(x0, y0);
359 XY = static_cast<PropertyPair::type>(-XY);
360 }
361
362 switch (XY) {
363 case PropertyPair::TV:
364 set_T(x0);
365 set_v(y0);
366 break;
367 case PropertyPair::HP:
368 if (Lever(Pgiven, y0, x0, propertyFlag::H)) {
369 return;
370 }
371 set_xy(propertyFlag::H, propertyFlag::P,
372 x0, y0, TolAbsH, TolAbsP, TolRel, TolRel);
373 break;
374 case PropertyPair::SP:
375 if (Lever(Pgiven, y0, x0, propertyFlag::S)) {
376 return;
377 }
378 set_xy(propertyFlag::S, propertyFlag::P,
379 x0, y0, TolAbsS, TolAbsP, TolRel, TolRel);
380 break;
381 case PropertyPair::PV:
382 if (Lever(Pgiven, x0, y0, propertyFlag::V)) {
383 return;
384 }
385 set_xy(propertyFlag::P, propertyFlag::V,
386 x0, y0, TolAbsP, TolAbsV, TolRel, TolRel);
387 break;
388 case PropertyPair::TP:
389 set_T(x0);
390 if (x0 < Tcrit()) {
391 if (fabs(y0 - Ps()) / y0 < TolRel) {
392 throw CanteraError("Substance::Set",
393 "Saturated mixture detected: use vapor "
394 "fraction to specify state instead");
395 } else if (y0 < Ps()) {
396 Set(PropertyPair::TX, x0, 1.0);
397 } else {
398 Set(PropertyPair::TX, x0, 0.0);
399 }
400 }
401 set_xy(propertyFlag::T, propertyFlag::P,
402 x0, y0, TolAbsT, TolAbsP, TolRel, TolRel);
403 break;
404 case PropertyPair::UV:
405 set_xy(propertyFlag::U, propertyFlag::V,
406 x0, y0, TolAbsU, TolAbsV, TolRel, TolRel);
407 break;
408 case PropertyPair::ST:
409 if (Lever(Tgiven, y0, x0, propertyFlag::S)) {
410 return;
411 }
412 set_xy(propertyFlag::S, propertyFlag::T,
413 x0, y0, TolAbsS, TolAbsT, TolRel, TolRel);
414 break;
415 case PropertyPair::SV:
416 set_xy(propertyFlag::S, propertyFlag::V,
417 x0, y0, TolAbsS, TolAbsV, TolRel, TolRel);
418 break;
419 case PropertyPair::UP:
420 if (Lever(Pgiven, y0, x0, propertyFlag::U)) {
421 return;
422 }
423 set_xy(propertyFlag::U, propertyFlag::P,
424 x0, y0, TolAbsU, TolAbsP, TolRel, TolRel);
425 break;
426 case PropertyPair::VH:
427 set_xy(propertyFlag::V, propertyFlag::H,
428 x0, y0, TolAbsV, TolAbsH, TolRel, TolRel);
429 break;
430 case PropertyPair::TH:
431 set_xy(propertyFlag::T, propertyFlag::H,
432 x0, y0, TolAbsT, TolAbsH, TolRel, TolRel);
433 break;
434 case PropertyPair::SH:
435 set_xy(propertyFlag::S, propertyFlag::H,
436 x0, y0, TolAbsS, TolAbsH, TolRel, TolRel);
437 break;
438 case PropertyPair::PX:
439 temp = Tmin();
440 set_T(temp);
441 if (y0 > 1.0 || y0 < 0.0) {
442 throw CanteraError("Substance::Set",
443 "Invalid vapor fraction, {}", y0);
444 }
445 if (x0 < Ps() || x0 > Pcrit()) {
446 throw CanteraError("Substance::Set",
447 "Illegal pressure value: {} (supercritical "
448 "or below triple point)", x0);
449 }
450 temp = Tsat(x0);
451 set_T(temp);
452 update_sat();
453 Rho = 1.0/((1.0 - y0)/Rhf + y0/Rhv);
454 break;
455 case PropertyPair::TX:
456 if (y0 > 1.0 || y0 < 0.0) {
457 throw CanteraError("Substance::Set",
458 "Invalid vapor fraction, {}", y0);
459 }
460 if (x0 < Tmin() || x0 > Tcrit()) {
461 throw CanteraError("Substance::Set",
462 "Illegal temperature value: {} "
463 "(supercritical or below triple point)", x0);
464 }
465 set_T(x0);
466 update_sat();
467 Rho = 1.0/((1.0 - y0)/Rhf + y0/Rhv);
468 break;
469 default:
470 throw CanteraError("Substance::Set", "Invalid input.");
471 }
472}
473
474//------------------ Protected and Private Functions -------------------
475
476void Substance::set_Rho(double r0)
477{
478 if (r0 > 0.0) {
479 Rho = r0;
480 } else {
481 throw CanteraError("Substance::set_Rho", "Invalid density: {}", r0);
482 }
483}
484
485void Substance::set_T(double t0)
486{
487 if ((t0 >= Tmin()) && (t0 <= Tmax())) {
488 T = t0;
489 } else {
490 throw CanteraError("Substance::set_T", "Illegal temperature: {}", t0);
491 }
492}
493
494void Substance::set_v(double v0)
495{
496 if (v0 > 0) {
497 Rho = 1.0/v0;
498 } else {
499 throw CanteraError("Substance::set_v",
500 "Negative specific volume: {}", v0);
501 }
502}
503
504double Substance::Ps()
505{
506 if (T < Tmin() || T > Tcrit()) {
507 throw CanteraError("Substance::Ps",
508 "Illegal temperature value: {}", T);
509 }
510 update_sat();
511 return Pst;
512}
513
515{
516 if (T != Tslast) {
517 double Rho_save = Rho;
518 double pp = Psat(); // illegal temperatures are caught by Psat()
519 double lps = log(pp);
520 // trial value = Psat from correlation
521 int i;
522 for (i = 0; i<20; i++) {
523 if (i==0) {
524 Rho = ldens(); // trial value = liquid density
525 } else {
526 Rho = Rhf;
527 }
528 set_TPp(T,pp);
529 Rhf = Rho; // sat liquid density
530
531 double gf = hp() - T*sp();
532 if (i==0) {
533 Rho = pp*MolWt()/(GasConstant*T); // trial value = ideal gas
534 } else {
535 Rho = Rhv;
536 }
537 set_TPp(T,pp);
538
539 Rhv = Rho; // sat vapor density
540 double gv = hp() - T*sp();
541 double dg = gv - gf;
542 if (Rhv > Rhf) {
543 std::swap(Rhv, Rhf);
544 dg = - dg;
545 }
546
547 if (fabs(dg) < 0.001) {
548 break;
549 }
550 double dp = dg/(1.0/Rhv - 1.0/Rhf);
551 double psold = pp;
552 if (fabs(dp) > pp) {
553 lps -= dg/(pp*(1.0/Rhv - 1.0/Rhf));
554 pp = exp(lps);
555 } else {
556 pp -= dp;
557 lps = log(pp);
558 }
559 if (pp > Pcrit()) {
560 pp = psold + 0.5*(Pcrit() - psold);
561 lps = log(pp);
562 } else if (pp < 0.0) {
563 pp = psold/2.0;
564 lps = log(pp);
565 }
566 }
567
568 if (i >= 20) {
569 throw CanteraError("Substance::update_sat", "No convergence");
570 } else {
571 Pst = pp;
572 Tslast = T;
573 }
574 Rho = Rho_save;
575 }
576}
577
578double Substance::vprop(propertyFlag::type ijob)
579{
580 switch (ijob) {
581 case propertyFlag::H:
582 return hp();
583 case propertyFlag::S:
584 return sp();
585 case propertyFlag::U:
586 return up();
587 case propertyFlag::V:
588 return vp();
589 case propertyFlag::P:
590 return Pp();
591 default:
592 throw CanteraError("Substance::vprop", "Invalid job index");
593 }
594}
595
596int Substance::Lever(int itp, double sat, double val, propertyFlag::type ifunc)
597{
598 double psat;
599 double Tsave = T;
600 double Rhosave = Rho;
601 if (itp == Tgiven) {
602 if (sat >= Tcrit()) {
603 return 0;
604 }
605 T = sat;
606 psat = Ps();
607 } else if (itp == Pgiven) {
608 if (sat >= Pcrit()) {
609 return 0;
610 }
611 psat = sat;
612 try {
613 T = Tsat(psat);
614 } catch (CanteraError&) {
615 // Failure to converge here is not an error
616 T = Tsave;
617 Rho = Rhosave;
618 return 0;
619 }
620 } else {
621 throw CanteraError("Substance::Lever", "General error");
622 }
623 Set(PropertyPair::TX, T, 1.0);
624 double Valg = vprop(ifunc);
625 Set(PropertyPair::TX, T, 0.0);
626 double Valf = vprop(ifunc);
627 if (val >= Valf && val <= Valg) {
628 double xx = (val - Valf)/(Valg - Valf);
629 double vv = (1.0 - xx)/Rhf + xx/Rhv;
630 set_v(vv);
631 return 1;
632 } else {
633 T = Tsave;
634 Rho = Rhosave;
635 return 0;
636 }
637}
638
639void Substance::set_xy(propertyFlag::type ifx, propertyFlag::type ify,
640 double X, double Y,
641 double atx, double aty,
642 double rtx, double rty)
643{
644 double v_here, t_here;
645 double dvs1 = 2.0*Vcrit();
646 double dvs2 = 0.7*Vcrit();
647 int LoopCount = 0;
648
649 double v_save = 1.0/Rho;
650 double t_save = T;
651
652 if ((T == Undef) && (Rho == Undef)) {
653 // new object, try to pick a "reasonable" starting point
654 Set(PropertyPair::TV,Tcrit()*1.1,Vcrit()*1.1);
655 t_here = T;
656 v_here = 1.0/Rho;
657 } else if (Rho == Undef) {
658 // new object, try to pick a "reasonable" starting point
659 Set(PropertyPair::TV,T,Vcrit()*1.1);
660 t_here = T;
661 v_here = 1.0/Rho;
662 } else {
663 v_here = v_save;
664 t_here = t_save;
665 }
666
667 double Xa = fabs(X);
668 double Ya = fabs(Y);
669 while (true) {
670 double x_here = prop(ifx);
671 double y_here = prop(ify);
672 double err_x = fabs(X - x_here);
673 double err_y = fabs(Y - y_here);
674
675 if ((err_x < atx + rtx*Xa) && (err_y < aty + rty*Ya)) {
676 break;
677 }
678
679 /* perturb t */
680 double dt = 0.001*t_here;
681 if (t_here + dt > Tmax()) {
682 dt *= -1.0;
683 }
684
685 /* perturb v */
686 double dv = 0.001*v_here;
687 if (v_here <= Vcrit()) {
688 dv *= -1.0;
689 }
690
691 /* derivatives with respect to T */
692 Set(PropertyPair::TV, t_here + dt, v_here);
693 double dxdt = (prop(ifx) - x_here)/dt;
694 double dydt = (prop(ify) - y_here)/dt;
695
696 /* derivatives with respect to v */
697 Set(PropertyPair::TV, t_here, v_here + dv);
698 double dxdv = (prop(ifx) - x_here)/dv;
699 double dydv = (prop(ify) - y_here)/dv;
700
701 double det = dxdt*dydv - dydt*dxdv;
702 dt = ((X - x_here)*dydv - (Y - y_here)*dxdv)/det;
703 dv = ((Y - y_here)*dxdt - (X - x_here)*dydt)/det;
704
705 double dvm = 0.2*v_here;
706 if (v_here < dvs1) {
707 dvm *= 0.5;
708 }
709 if (v_here < dvs2) {
710 dvm *= 0.5;
711 }
712 double dtm = 0.1*t_here;
713 double dva = fabs(dv);
714 double dta = fabs(dt);
715 if (dva > dvm) {
716 dv *= dvm/dva;
717 }
718 if (dta > dtm) {
719 dt *= dtm/dta;
720 }
721 v_here += dv;
722 t_here += dt;
723 t_here = clip(t_here, Tmin(), Tmax());
724 if (v_here <= 0.0) {
725 v_here = 0.0001;
726 }
727 Set(PropertyPair::TV, t_here, v_here);
728 LoopCount++;
729 if (LoopCount > 200) {
730 string msg = fmt::format("No convergence. {} = {}, {} = {}",
731 propertySymbols[ifx], X, propertySymbols[ify], Y);
732 if (t_here == Tmin()) {
733 msg += fmt::format("\nAt temperature limit (Tmin = {})", Tmin());
734 } else if (t_here == Tmax()) {
735 msg += fmt::format("\nAt temperature limit (Tmax = {})", Tmax());
736 }
737 throw CanteraError("Substance::set_xy", msg);
738 }
739 }
740}
741
742double Substance::prop(propertyFlag::type ijob)
743{
744 if (ijob == propertyFlag::P) {
745 return P();
746 }
747 if (ijob == propertyFlag::T) {
748 return T;
749 }
750 double xx = x();
751 if ((xx > 0.0) && (xx < 1.0)) {
752 double Rho_save = Rho;
753 Rho = Rhv;
754 double vp = vprop(ijob);
755 Rho = Rhf;
756 double lp = vprop(ijob);
757 double pp = (1.0 - xx)*lp + xx*vp;
758 Rho = Rho_save;
759 return pp;
760 } else {
761 return vprop(ijob);
762 }
763}
764
765static const double ErrP = 1.e-7;
766static const double Big = 1.e30;
767
768void Substance::BracketSlope(double Pressure)
769{
770 if (kbr == 0) {
771 dv = (v_here < Vcrit() ? -0.05*v_here : 0.2*v_here);
772 if (Vmin > 0.0) {
773 dv = 0.2*v_here;
774 }
775 if (Vmax < Big) {
776 dv = -0.05*v_here;
777 }
778 } else {
779 double dpdv = (Pmax - Pmin)/(Vmax - Vmin);
780 v_here = Vmax;
781 P_here = Pmax;
782 dv = dvbf*(Pressure - P_here)/dpdv;
783 dvbf = 0.5*dvbf;
784 }
785}
786
787void Substance::set_TPp(double Temp, double Pressure)
788{
789 kbr = 0;
790 dvbf = 1.0;
791 Vmin = 0.0;
792 Vmax = Big;
793 Pmin = Big;
794 Pmax = 0.0;
795 double dvs1 = 2.0*Vcrit();
796 double dvs2 = 0.7*Vcrit();
797 int LoopCount = 0;
798
799 double v_save = 1.0/Rho;
800 T = Temp;
801 v_here = vp();
802
803 // Track the previous iterate so that a sign change in the pressure residual
804 // can be detected and turned into a bracket (see below).
805 double v_prev = v_here;
806 double P_prev = 0.0;
807 double res_prev = 0.0;
808 bool have_prev = false;
809
810 // loop
811 while (P_here = Pp(),
812 fabs(Pressure - P_here) >= ErrP* Pressure || LoopCount == 0) {
813 double res = P_here - Pressure;
814 if (have_prev && kbr == 0 && res*res_prev < 0.0) {
815 // The residual changed sign between the previous and current specific
816 // volume, so the root lies between them. On the steep liquid branch at low
817 // temperature the iteration can step across the (very thin) target band
818 // without the logic below ever recording a bracket, leaving the solver
819 // unable to converge. Capture the bracket explicitly so the in-bracket
820 // refinement below can proceed. Pressure decreases with specific volume
821 // here, so the smaller volume is the lower bound.
822 if (v_here < v_prev) {
823 Vmin = v_here;
824 Pmin = P_here;
825 Vmax = v_prev;
826 Pmax = P_prev;
827 } else {
828 Vmin = v_prev;
829 Pmin = P_prev;
830 Vmax = v_here;
831 Pmax = P_here;
832 }
833 kbr = 1;
834 }
835 v_prev = v_here;
836 P_prev = P_here;
837 res_prev = res;
838 have_prev = true;
839
840 if (P_here < 0.0) {
841 BracketSlope(Pressure);
842 } else {
843 dv = 0.001*v_here;
844 if (v_here <= Vcrit()) {
845 dv *= -1.0;
846 }
847 Set(PropertyPair::TV, Temp, v_here+dv);
848 double dpdv = (Pp() - P_here)/dv;
849 if (dpdv > 0.0) {
850 BracketSlope(Pressure);
851 } else {
852 if ((P_here > Pressure) && (v_here > Vmin)) {
853 Vmin = v_here;
854 } else if ((P_here < Pressure) && (v_here < Vmax)) {
855 Vmax = v_here;
856 }
857 if (v_here == Vmin) {
858 Pmin = P_here;
859 }
860 if (v_here == Vmax) {
861 Pmax = P_here;
862 }
863 if (Vmin >= Vmax) {
864 throw CanteraError("Substance::set_TPp", "Vmin >= Vmax");
865 } else if ((Vmin > 0.0) && (Vmax < Big)) {
866 kbr = 1;
867 }
868 dvbf = 1.0;
869 if (dpdv == 0.0) {
870 dvbf = 0.5;
871 BracketSlope(Pressure);
872 } else {
873 dv = (Pressure - P_here)/dpdv;
874 }
875 }
876 }
877 double dvm = 0.2*v_here;
878 if (v_here < dvs1) {
879 dvm *= 0.5;
880 }
881 if (v_here < dvs2) {
882 dvm *= 0.5;
883 }
884 if (kbr != 0) {
885 double vt = v_here + dv;
886 if ((vt < Vmin) || (vt > Vmax)) {
887 dv = Vmin + (Pressure - Pmin)*(Vmax - Vmin)/(Pmax - Pmin) - v_here;
888 }
889 }
890 double dva = fabs(dv);
891 if (dva > dvm) {
892 dv *= dvm/dva;
893 }
894 v_here += dv;
895 if (fabs(dv) <= 4.0*std::numeric_limits<double>::epsilon()*fabs(v_here)) {
896 // The volume step has dropped below the resolution of the specific volume
897 // in double precision, so no nearer representable volume brings the brings
898 // the pressure closer to the target. This is convergence to floating-point
899 // precision rather than a failure: it occurs on the nearly incompressible
900 // liquid branch at low temperature, where the saturation pressure is so
901 // small that the requested relative pressure tolerance is finer than one
902 // floating-point ULP of pressure, and when the initial density guess
903 // already sits at the root.
904 break;
905 }
906 Set(PropertyPair::TV, Temp, v_here);
907 LoopCount++;
908 if (LoopCount > 200) {
909 Set(PropertyPair::TV, Temp, v_save);
910 throw CanteraError("Substance::set_TPp",
911 "No convergence for P = {}, T = {}\n"
912 "(P* = {}, V* = {})", Pressure, Temp,
913 Pressure/Pcrit(), v_save/Vcrit());
914 }
915 }
916 Set(PropertyPair::TV, Temp,v_here);
917}
918}
Base class for exceptions thrown by Cantera classes.
virtual double up()=0
Internal energy of a single-phase state.
virtual double Vcrit()=0
Critical specific volume [m^3/kg].
double hp()
Enthalpy of a single-phase state.
Definition Sub.h:161
int TwoPhase(bool strict=false)
Returns 1 if the current state is a liquid/vapor mixture, 0 otherwise.
Definition Sub.cpp:271
virtual double internalPressure()
Internal pressure [Pa], evaluated as .
Definition Sub.cpp:235
virtual double cp()
Specific heat at constant pressure [J/kg/K].
Definition Sub.cpp:82
double x()
Vapor mass fraction.
Definition Sub.cpp:283
virtual double Tmax()=0
Maximum temperature for which the equation of state is valid.
double u()
Internal energy [J/kg].
Definition Sub.h:98
virtual double MolWt()=0
Molecular weight [kg/kmol].
double h()
Enthalpy [J/kg].
Definition Sub.h:103
virtual double cv()
Specific heat at constant volume [J/kg/K].
Definition Sub.cpp:44
double Temp()
Temperature [K].
Definition Sub.h:88
virtual double Tmin()=0
Minimum temperature for which the equation of state is valid.
int Lever(int itp, double sat, double val, propertyFlag::type ifunc)
Uses the lever rule to set state in the dome.
Definition Sub.cpp:596
double P()
Pressure [Pa].
Definition Sub.cpp:37
virtual double sp()=0
Entropy of a single-phase state.
virtual double Psat()=0
Saturation pressure, Pa.
double v()
Specific volume [m^3/kg].
Definition Sub.h:93
virtual double Tcrit()=0
Critical temperature [K].
void Set(PropertyPair::type XY, double x0, double y0)
Function to set or change the state for a property pair XY where x0 is the value of first property an...
Definition Sub.cpp:352
virtual double Pcrit()=0
Critical pressure [Pa].
virtual double dPsdT()
The derivative of the saturation pressure with respect to temperature.
Definition Sub.cpp:254
double Tsat(double p)
Saturation temperature at pressure p.
Definition Sub.cpp:301
void update_sat()
Update saturated liquid and vapor densities and saturation pressure.
Definition Sub.cpp:514
void set_TPp(double t0, double p0)
set T and P
Definition Sub.cpp:787
double s()
Entropy [J/kg/K].
Definition Sub.h:108
This file contains definitions for utility functions and text for modules, inputfiles and logging,...
T clip(const T &value, const T &lower, const T &upper)
Clip value such that lower <= value <= upper.
Definition global.h:326
const double GasConstant
Universal Gas Constant [J/kmol/K].
Definition ct_defs.h:123
Namespace for the Cantera kernel.
Definition AnyMap.cpp:595
Contains declarations for string manipulation functions within Cantera.