19string propertySymbols[] = {
"H",
"S",
"U",
"V",
"P",
"T"};
25void Substance::setStdState(
double h0,
double s0,
double t0,
double p0)
27 Set(PropertyPair::TP, t0, p0);
30 double hoff = h0 - hh;
31 double soff = s0 - ss;
32 m_entropy_offset += soff;
33 m_energy_offset += hoff;
41const double DeltaT = 0.000001;
49 return std::numeric_limits<double>::quiet_NaN();
52 double Tsave = T, dt = 1.e-4 * T;
54 double T1 = std::max(
Tmin(), Tsave - dt);
55 double T2 = std::min(
Tmax(), Tsave + dt);
59 if ((x0 == 1.0 || x0 == 0.0) && x1 != x0) {
69 if ((x0 == 1.0 || x0 == 0.0) && x2 != x0) {
78 return T*(s2 - s1)/(T2-T1);
85 return std::numeric_limits<double>::infinity();
88 double Tsave = T, dt = 1.e-4 * T;
90 double T1, T2, s1, s2;
95 T1 = std::max(
Tmin(), Tsave - dt);
96 Set(PropertyPair::TP, T1, p0);
99 T2 = std::min(
Tsat(p0), Tsave + dt);
104 if (T2 < Tsave + dt) {
105 Set(PropertyPair::TX, T2, 0.);
107 Set(PropertyPair::TP, T2, p0);
113 T1 = std::max(
Tsat(p0), Tsave - dt);
118 if (T1 > Tsave - dt) {
119 Set(PropertyPair::TX, T1, 1.);
121 Set(PropertyPair::TP, T1, p0);
124 T2 = std::min(
Tmax(), Tsave + dt);
125 Set(PropertyPair::TP, T2, p0);
129 Set(PropertyPair::TV, Tsave, 1.0 / RhoSave);
130 return T * (s2 - s1) / (T2 - T1);
133double Substance::thermalExpansionCoeff()
138 return std::numeric_limits<double>::infinity();
141 double Tsave = T, dt = 1.e-4 * T;
142 double RhoSave = Rho;
143 double T1, T2, v1, v2;
148 T1 = std::max(
Tmin(), Tsave - dt);
149 Set(PropertyPair::TP, T1, p0);
152 T2 = std::min(
Tsat(p0), Tsave + dt);
157 if (T2 < Tsave + dt) {
158 Set(PropertyPair::TX, T2, 0.);
160 Set(PropertyPair::TP, T2, p0);
166 T1 = std::max(
Tsat(p0), Tsave - dt);
171 if (T1 > Tsave - dt) {
172 Set(PropertyPair::TX, T1, 1.);
174 Set(PropertyPair::TP, T1, p0);
177 T2 = std::min(
Tmax(), Tsave + dt);
178 Set(PropertyPair::TP, T2, p0);
182 Set(PropertyPair::TV, Tsave, 1.0 / RhoSave);
183 return 2.0 * (v2 - v1) / ((v2 + v1) * (T2 - T1));
186double Substance::isothermalCompressibility()
190 return std::numeric_limits<double>::infinity();
193 double Psave =
P(), dp = 1.e-4 * Psave;
194 double RhoSave = Rho;
195 double P1, P2, v1, v2;
202 P1 = std::max(Ps(), P1);
204 if (P1 > Psave - dp) {
205 Set(PropertyPair::PX, P1, 0.);
207 Set(PropertyPair::TP, T, P1);
211 Set(PropertyPair::TP, T, P2);
215 P1 = std::max(1.e-7, Psave - dp);
216 Set(PropertyPair::TP, T, P1);
220 P2 = std::min(Ps(), P2);
222 if (P2 < Psave + dp) {
223 Set(PropertyPair::PX, P2, 1.);
225 Set(PropertyPair::TP, T, P2);
230 Set(PropertyPair::TV, T, 1.0 / RhoSave);
231 return -(v2 - v1) / (v0 * (P2 - P1));
240 double dv = std::max(1.e-8, 1.e-6 * std::abs(vsave));
241 double v1 = std::max(1.e-300, vsave - dv);
242 double v2 = vsave + dv;
244 Set(PropertyPair::TV, Tsave, v1);
246 Set(PropertyPair::TV, Tsave, v2);
249 Set(PropertyPair::TV, Tsave, vsave);
250 return (u2 - u1) / (v2 - v1);
258 if (T + DeltaT <
Tcrit()) {
260 dpdt = (Ps() - ps1)/DeltaT;
264 dpdt = (ps1 - Ps())/DeltaT;
277 return ((Rho > Rhv) && (Rho < Rhf) ? 1 : 0);
279 return ((Rho >= Rhv) && (Rho <= Rhf) ? 1 : 0);
285 return (1.0/Rho <
Vcrit() ? 0.0 : 1.0);
290 }
else if (Rho >= Rhf) {
295 return (1.0/Rho - vl)/(vv - vl);
303 if (p <= 0. || p >
Pcrit()) {
311 "Illegal pressure value: {}", p);
317 double tol = 1.e-6*p;
322 while (fabs(dp) > tol) {
323 T = std::min(T,
Tcrit());
324 T = std::max(T,
Tmin());
326 double dt = dp/
dPsdT();
327 double dta = fabs(dt);
334 if (LoopCount > 100) {
336 throw CanteraError(
"Substance::Tsat",
"No convergence: p = {}", p);
345static const double TolAbsH = 1.e-4;
346static const double TolAbsU = 1.e-4;
347static const double TolAbsS = 1.e-7;
348static const double TolAbsP = 0.0;
349static const double TolAbsV = 1.e-8;
350static const double TolAbsT = 1.e-3;
351static const double TolRel = 1.e-8;
360 XY =
static_cast<PropertyPair::type
>(-XY);
364 case PropertyPair::TV:
368 case PropertyPair::HP:
369 if (
Lever(Pgiven, y0, x0, propertyFlag::H)) {
372 set_xy(propertyFlag::H, propertyFlag::P,
373 x0, y0, TolAbsH, TolAbsP, TolRel, TolRel);
375 case PropertyPair::SP:
376 if (
Lever(Pgiven, y0, x0, propertyFlag::S)) {
379 set_xy(propertyFlag::S, propertyFlag::P,
380 x0, y0, TolAbsS, TolAbsP, TolRel, TolRel);
382 case PropertyPair::PV:
383 if (
Lever(Pgiven, x0, y0, propertyFlag::V)) {
386 set_xy(propertyFlag::P, propertyFlag::V,
387 x0, y0, TolAbsP, TolAbsV, TolRel, TolRel);
389 case PropertyPair::TP:
392 if (fabs(y0 - Ps()) / y0 < TolRel) {
394 "Saturated mixture detected: use vapor "
395 "fraction to specify state instead");
396 }
else if (y0 < Ps()) {
397 Set(PropertyPair::TX, x0, 1.0);
399 Set(PropertyPair::TX, x0, 0.0);
402 set_xy(propertyFlag::T, propertyFlag::P,
403 x0, y0, TolAbsT, TolAbsP, TolRel, TolRel);
405 case PropertyPair::UV:
406 set_xy(propertyFlag::U, propertyFlag::V,
407 x0, y0, TolAbsU, TolAbsV, TolRel, TolRel);
409 case PropertyPair::ST:
410 if (
Lever(Tgiven, y0, x0, propertyFlag::S)) {
413 set_xy(propertyFlag::S, propertyFlag::T,
414 x0, y0, TolAbsS, TolAbsT, TolRel, TolRel);
416 case PropertyPair::SV:
417 set_xy(propertyFlag::S, propertyFlag::V,
418 x0, y0, TolAbsS, TolAbsV, TolRel, TolRel);
420 case PropertyPair::UP:
421 if (
Lever(Pgiven, y0, x0, propertyFlag::U)) {
424 set_xy(propertyFlag::U, propertyFlag::P,
425 x0, y0, TolAbsU, TolAbsP, TolRel, TolRel);
427 case PropertyPair::VH:
428 set_xy(propertyFlag::V, propertyFlag::H,
429 x0, y0, TolAbsV, TolAbsH, TolRel, TolRel);
431 case PropertyPair::TH:
432 set_xy(propertyFlag::T, propertyFlag::H,
433 x0, y0, TolAbsT, TolAbsH, TolRel, TolRel);
435 case PropertyPair::SH:
436 set_xy(propertyFlag::S, propertyFlag::H,
437 x0, y0, TolAbsS, TolAbsH, TolRel, TolRel);
439 case PropertyPair::PX:
442 if (y0 > 1.0 || y0 < 0.0) {
444 "Invalid vapor fraction, {}", y0);
446 if (x0 < Ps() || x0 >
Pcrit()) {
448 "Illegal pressure value: {} (supercritical "
449 "or below triple point)", x0);
454 Rho = 1.0/((1.0 - y0)/Rhf + y0/Rhv);
456 case PropertyPair::TX:
457 if (y0 > 1.0 || y0 < 0.0) {
459 "Invalid vapor fraction, {}", y0);
463 "Illegal temperature value: {} "
464 "(supercritical or below triple point)", x0);
468 Rho = 1.0/((1.0 - y0)/Rhf + y0/Rhv);
477void Substance::set_Rho(
double r0)
482 throw CanteraError(
"Substance::set_Rho",
"Invalid density: {}", r0);
486void Substance::set_T(
double t0)
488 if ((t0 >=
Tmin()) && (t0 <=
Tmax())) {
491 throw CanteraError(
"Substance::set_T",
"Illegal temperature: {}", t0);
495void Substance::set_v(
double v0)
501 "Negative specific volume: {}", v0);
505double Substance::Ps()
509 "Illegal temperature value: {}", T);
518 double Rho_save = Rho;
520 double lps = log(pp);
523 for (i = 0; i<20; i++) {
532 double gf =
hp() - T*
sp();
541 double gv =
hp() - T*
sp();
548 if (fabs(dg) < 0.001) {
551 double dp = dg/(1.0/Rhv - 1.0/Rhf);
554 lps -= dg/(pp*(1.0/Rhv - 1.0/Rhf));
561 pp = psold + 0.5*(
Pcrit() - psold);
563 }
else if (pp < 0.0) {
570 throw CanteraError(
"Substance::update_sat",
"No convergence");
579double Substance::vprop(propertyFlag::type ijob)
582 case propertyFlag::H:
584 case propertyFlag::S:
586 case propertyFlag::U:
588 case propertyFlag::V:
590 case propertyFlag::P:
593 throw CanteraError(
"Substance::vprop",
"Invalid job index");
601 double Rhosave = Rho;
603 if (sat >=
Tcrit()) {
608 }
else if (itp == Pgiven) {
609 if (sat >=
Pcrit()) {
622 throw CanteraError(
"Substance::Lever",
"General error");
624 Set(PropertyPair::TX, T, 1.0);
625 double Valg = vprop(ifunc);
626 Set(PropertyPair::TX, T, 0.0);
627 double Valf = vprop(ifunc);
628 if (val >= Valf && val <= Valg) {
629 double xx = (val - Valf)/(Valg - Valf);
630 double vv = (1.0 - xx)/Rhf + xx/Rhv;
640void Substance::set_xy(propertyFlag::type ifx, propertyFlag::type ify,
642 double atx,
double aty,
643 double rtx,
double rty)
645 double v_here, t_here;
646 double dvs1 = 2.0*
Vcrit();
647 double dvs2 = 0.7*
Vcrit();
650 double v_save = 1.0/Rho;
653 if ((T == Undef) && (Rho == Undef)) {
658 }
else if (Rho == Undef) {
660 Set(PropertyPair::TV,T,
Vcrit()*1.1);
671 double x_here = prop(ifx);
672 double y_here = prop(ify);
673 double err_x = fabs(X - x_here);
674 double err_y = fabs(Y - y_here);
676 if ((err_x < atx + rtx*Xa) && (err_y < aty + rty*Ya)) {
681 double dt = 0.001*t_here;
682 if (t_here + dt >
Tmax()) {
687 double dv = 0.001*v_here;
688 if (v_here <=
Vcrit()) {
693 Set(PropertyPair::TV, t_here + dt, v_here);
694 double dxdt = (prop(ifx) - x_here)/dt;
695 double dydt = (prop(ify) - y_here)/dt;
698 Set(PropertyPair::TV, t_here, v_here + dv);
699 double dxdv = (prop(ifx) - x_here)/dv;
700 double dydv = (prop(ify) - y_here)/dv;
702 double det = dxdt*dydv - dydt*dxdv;
703 dt = ((X - x_here)*dydv - (Y - y_here)*dxdv)/det;
704 dv = ((Y - y_here)*dxdt - (X - x_here)*dydt)/det;
706 double dvm = 0.2*v_here;
713 double dtm = 0.1*t_here;
714 double dva = fabs(dv);
715 double dta = fabs(dt);
728 Set(PropertyPair::TV, t_here, v_here);
730 if (LoopCount > 200) {
731 string msg = fmt::format(
"No convergence. {} = {}, {} = {}",
732 propertySymbols[ifx], X, propertySymbols[ify], Y);
733 if (t_here ==
Tmin()) {
734 msg += fmt::format(
"\nAt temperature limit (Tmin = {})",
Tmin());
735 }
else if (t_here ==
Tmax()) {
736 msg += fmt::format(
"\nAt temperature limit (Tmax = {})",
Tmax());
743double Substance::prop(propertyFlag::type ijob)
745 if (ijob == propertyFlag::P) {
748 if (ijob == propertyFlag::T) {
752 if ((xx > 0.0) && (xx < 1.0)) {
753 double Rho_save = Rho;
755 double vp = vprop(ijob);
757 double lp = vprop(ijob);
758 double pp = (1.0 - xx)*lp + xx*vp;
766static const double ErrP = 1.e-7;
767static const double Big = 1.e30;
769void Substance::BracketSlope(
double Pressure)
772 dv = (v_here <
Vcrit() ? -0.05*v_here : 0.2*v_here);
780 double dpdv = (Pmax - Pmin)/(Vmax - Vmin);
783 dv = dvbf*(Pressure - P_here)/dpdv;
796 double dvs1 = 2.0*
Vcrit();
797 double dvs2 = 0.7*
Vcrit();
800 double v_save = 1.0/Rho;
805 while (P_here = Pp(),
806 fabs(Pressure - P_here) >= ErrP* Pressure || LoopCount == 0) {
808 BracketSlope(Pressure);
811 if (v_here <=
Vcrit()) {
814 Set(PropertyPair::TV,
Temp, v_here+dv);
815 double dpdv = (Pp() - P_here)/dv;
817 BracketSlope(Pressure);
819 if ((P_here > Pressure) && (v_here > Vmin)) {
821 }
else if ((P_here < Pressure) && (v_here < Vmax)) {
824 if (v_here == Vmin) {
827 if (v_here == Vmax) {
831 throw CanteraError(
"Substance::set_TPp",
"Vmin >= Vmax");
832 }
else if ((Vmin > 0.0) && (Vmax < Big)) {
838 BracketSlope(Pressure);
840 dv = (Pressure - P_here)/dpdv;
844 double dvm = 0.2*v_here;
852 double vt = v_here + dv;
853 if ((vt < Vmin) || (vt > Vmax)) {
854 dv = Vmin + (Pressure - Pmin)*(Vmax - Vmin)/(Pmax - Pmin) - v_here;
857 double dva = fabs(dv);
863 throw CanteraError(
"Substance::set_TPp",
"dv = 0 and no convergence");
865 Set(PropertyPair::TV,
Temp, v_here);
867 if (LoopCount > 200) {
868 Set(PropertyPair::TV,
Temp, v_save);
870 "No convergence for P = {}, T = {}\n"
871 "(P* = {}, V* = {})", Pressure,
Temp,
875 Set(PropertyPair::TV,
Temp,v_here);
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.
int TwoPhase(bool strict=false)
Returns 1 if the current state is a liquid/vapor mixture, 0 otherwise.
virtual double internalPressure()
Internal pressure [Pa], evaluated as .
virtual double cp()
Specific heat at constant pressure [J/kg/K].
double x()
Vapor mass fraction.
virtual double Tmax()=0
Maximum temperature for which the equation of state is valid.
double u()
Internal energy [J/kg].
virtual double MolWt()=0
Molecular weight [kg/kmol].
double h()
Enthalpy [J/kg].
virtual double cv()
Specific heat at constant volume [J/kg/K].
double Temp()
Temperature [K].
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.
virtual double sp()=0
Entropy of a single-phase state.
virtual double Psat()=0
Saturation pressure, Pa.
double v()
Specific volume [m^3/kg].
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...
virtual double Pcrit()=0
Critical pressure [Pa].
virtual double dPsdT()
The derivative of the saturation pressure with respect to temperature.
double Tsat(double p)
Saturation temperature at pressure p.
void update_sat()
Update saturated liquid and vapor densities and saturation pressure.
void set_TPp(double t0, double p0)
set T and P
double s()
Entropy [J/kg/K].
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.
const double GasConstant
Universal Gas Constant [J/kmol/K].
Namespace for the Cantera kernel.
Contains declarations for string manipulation functions within Cantera.