20string propertySymbols[] = {
"H",
"S",
"U",
"V",
"P",
"T"};
26void Substance::setStdState(
double h0,
double s0,
double t0,
double p0)
28 Set(PropertyPair::TP, t0, p0);
31 double hoff = h0 - hh;
32 double soff = s0 - ss;
33 m_entropy_offset += soff;
34 m_energy_offset += hoff;
42const double DeltaT = 0.000001;
50 return std::numeric_limits<double>::quiet_NaN();
53 double Tsave = T, dt = 1.e-4 * T;
55 double T1 = std::max(
Tmin(), Tsave - dt);
56 double T2 = std::min(
Tmax(), Tsave + dt);
60 if ((x0 == 1.0 || x0 == 0.0) && x1 != x0) {
70 if ((x0 == 1.0 || x0 == 0.0) && x2 != x0) {
79 return T*(s2 - s1)/(T2-T1);
86 return std::numeric_limits<double>::infinity();
89 double Tsave = T, dt = 1.e-4 * T;
91 double T1, T2, s1, s2;
96 T1 = std::max(
Tmin(), Tsave - dt);
97 Set(PropertyPair::TP, T1, p0);
100 T2 = std::min(
Tsat(p0), Tsave + dt);
105 if (T2 < Tsave + dt) {
106 Set(PropertyPair::TX, T2, 0.);
108 Set(PropertyPair::TP, T2, p0);
114 T1 = std::max(
Tsat(p0), Tsave - dt);
119 if (T1 > Tsave - dt) {
120 Set(PropertyPair::TX, T1, 1.);
122 Set(PropertyPair::TP, T1, p0);
125 T2 = std::min(
Tmax(), Tsave + dt);
126 Set(PropertyPair::TP, T2, p0);
130 Set(PropertyPair::TV, Tsave, 1.0 / RhoSave);
131 return T * (s2 - s1) / (T2 - T1);
134double Substance::thermalExpansionCoeff()
139 return std::numeric_limits<double>::infinity();
142 double Tsave = T, dt = 1.e-4 * T;
143 double RhoSave = Rho;
144 double T1, T2, v1, v2;
149 T1 = std::max(
Tmin(), Tsave - dt);
150 Set(PropertyPair::TP, T1, p0);
153 T2 = std::min(
Tsat(p0), Tsave + dt);
158 if (T2 < Tsave + dt) {
159 Set(PropertyPair::TX, T2, 0.);
161 Set(PropertyPair::TP, T2, p0);
167 T1 = std::max(
Tsat(p0), Tsave - dt);
172 if (T1 > Tsave - dt) {
173 Set(PropertyPair::TX, T1, 1.);
175 Set(PropertyPair::TP, T1, p0);
178 T2 = std::min(
Tmax(), Tsave + dt);
179 Set(PropertyPair::TP, T2, p0);
183 Set(PropertyPair::TV, Tsave, 1.0 / RhoSave);
184 return 2.0 * (v2 - v1) / ((v2 + v1) * (T2 - T1));
187double Substance::isothermalCompressibility()
191 return std::numeric_limits<double>::infinity();
194 double Psave =
P(), dp = 1.e-4 * Psave;
195 double RhoSave = Rho;
196 double P1, P2, v1, v2;
203 P1 = std::max(Ps(), P1);
205 if (P1 > Psave - dp) {
206 Set(PropertyPair::PX, P1, 0.);
208 Set(PropertyPair::TP, T, P1);
212 Set(PropertyPair::TP, T, P2);
216 P1 = std::max(1.e-7, Psave - dp);
217 Set(PropertyPair::TP, T, P1);
221 P2 = std::min(Ps(), P2);
223 if (P2 < Psave + dp) {
224 Set(PropertyPair::PX, P2, 1.);
226 Set(PropertyPair::TP, T, P2);
231 Set(PropertyPair::TV, T, 1.0 / RhoSave);
232 return -(v2 - v1) / (v0 * (P2 - P1));
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;
245 Set(PropertyPair::TV, Tsave, v1);
247 Set(PropertyPair::TV, Tsave, v2);
250 Set(PropertyPair::TV, Tsave, vsave);
251 return (u2 - u1) / (v2 - v1);
259 if (T + DeltaT <
Tcrit()) {
261 dpdt = (Ps() - ps1)/DeltaT;
265 dpdt = (ps1 - Ps())/DeltaT;
278 return ((Rho > Rhv) && (Rho < Rhf) ? 1 : 0);
280 return ((Rho >= Rhv) && (Rho <= Rhf) ? 1 : 0);
286 return (1.0/Rho <
Vcrit() ? 0.0 : 1.0);
291 }
else if (Rho >= Rhf) {
296 return (1.0/Rho - vl)/(vv - vl);
310 if (p <= 0. || p >
Pcrit() || p < Pmin) {
311 throw CanteraError(
"Substance::Tsat",
"Illegal pressure value: {}", p);
316 double tol = 1.e-6*p;
321 while (fabs(dp) > tol) {
322 T = std::min(T,
Tcrit());
323 T = std::max(T,
Tmin());
325 double dt = dp/
dPsdT();
326 double dta = fabs(dt);
333 if (LoopCount > 100) {
335 throw CanteraError(
"Substance::Tsat",
"No convergence: p = {}", p);
344static const double TolAbsH = 1.e-4;
345static const double TolAbsU = 1.e-4;
346static const double TolAbsS = 1.e-7;
347static const double TolAbsP = 0.0;
348static const double TolAbsV = 1.e-8;
349static const double TolAbsT = 1.e-3;
350static const double TolRel = 1.e-8;
359 XY =
static_cast<PropertyPair::type
>(-XY);
363 case PropertyPair::TV:
367 case PropertyPair::HP:
368 if (
Lever(Pgiven, y0, x0, propertyFlag::H)) {
371 set_xy(propertyFlag::H, propertyFlag::P,
372 x0, y0, TolAbsH, TolAbsP, TolRel, TolRel);
374 case PropertyPair::SP:
375 if (
Lever(Pgiven, y0, x0, propertyFlag::S)) {
378 set_xy(propertyFlag::S, propertyFlag::P,
379 x0, y0, TolAbsS, TolAbsP, TolRel, TolRel);
381 case PropertyPair::PV:
382 if (
Lever(Pgiven, x0, y0, propertyFlag::V)) {
385 set_xy(propertyFlag::P, propertyFlag::V,
386 x0, y0, TolAbsP, TolAbsV, TolRel, TolRel);
388 case PropertyPair::TP:
391 if (fabs(y0 - Ps()) / y0 < TolRel) {
393 "Saturated mixture detected: use vapor "
394 "fraction to specify state instead");
395 }
else if (y0 < Ps()) {
396 Set(PropertyPair::TX, x0, 1.0);
398 Set(PropertyPair::TX, x0, 0.0);
401 set_xy(propertyFlag::T, propertyFlag::P,
402 x0, y0, TolAbsT, TolAbsP, TolRel, TolRel);
404 case PropertyPair::UV:
405 set_xy(propertyFlag::U, propertyFlag::V,
406 x0, y0, TolAbsU, TolAbsV, TolRel, TolRel);
408 case PropertyPair::ST:
409 if (
Lever(Tgiven, y0, x0, propertyFlag::S)) {
412 set_xy(propertyFlag::S, propertyFlag::T,
413 x0, y0, TolAbsS, TolAbsT, TolRel, TolRel);
415 case PropertyPair::SV:
416 set_xy(propertyFlag::S, propertyFlag::V,
417 x0, y0, TolAbsS, TolAbsV, TolRel, TolRel);
419 case PropertyPair::UP:
420 if (
Lever(Pgiven, y0, x0, propertyFlag::U)) {
423 set_xy(propertyFlag::U, propertyFlag::P,
424 x0, y0, TolAbsU, TolAbsP, TolRel, TolRel);
426 case PropertyPair::VH:
427 set_xy(propertyFlag::V, propertyFlag::H,
428 x0, y0, TolAbsV, TolAbsH, TolRel, TolRel);
430 case PropertyPair::TH:
431 set_xy(propertyFlag::T, propertyFlag::H,
432 x0, y0, TolAbsT, TolAbsH, TolRel, TolRel);
434 case PropertyPair::SH:
435 set_xy(propertyFlag::S, propertyFlag::H,
436 x0, y0, TolAbsS, TolAbsH, TolRel, TolRel);
438 case PropertyPair::PX:
441 if (y0 > 1.0 || y0 < 0.0) {
443 "Invalid vapor fraction, {}", y0);
445 if (x0 < Ps() || x0 >
Pcrit()) {
447 "Illegal pressure value: {} (supercritical "
448 "or below triple point)", x0);
453 Rho = 1.0/((1.0 - y0)/Rhf + y0/Rhv);
455 case PropertyPair::TX:
456 if (y0 > 1.0 || y0 < 0.0) {
458 "Invalid vapor fraction, {}", y0);
462 "Illegal temperature value: {} "
463 "(supercritical or below triple point)", x0);
467 Rho = 1.0/((1.0 - y0)/Rhf + y0/Rhv);
476void Substance::set_Rho(
double r0)
481 throw CanteraError(
"Substance::set_Rho",
"Invalid density: {}", r0);
485void Substance::set_T(
double t0)
487 if ((t0 >=
Tmin()) && (t0 <=
Tmax())) {
490 throw CanteraError(
"Substance::set_T",
"Illegal temperature: {}", t0);
494void Substance::set_v(
double v0)
500 "Negative specific volume: {}", v0);
504double Substance::Ps()
508 "Illegal temperature value: {}", T);
517 double Rho_save = Rho;
519 double lps = log(pp);
522 for (i = 0; i<20; i++) {
531 double gf =
hp() - T*
sp();
540 double gv =
hp() - T*
sp();
547 if (fabs(dg) < 0.001) {
550 double dp = dg/(1.0/Rhv - 1.0/Rhf);
553 lps -= dg/(pp*(1.0/Rhv - 1.0/Rhf));
560 pp = psold + 0.5*(
Pcrit() - psold);
562 }
else if (pp < 0.0) {
569 throw CanteraError(
"Substance::update_sat",
"No convergence");
578double Substance::vprop(propertyFlag::type ijob)
581 case propertyFlag::H:
583 case propertyFlag::S:
585 case propertyFlag::U:
587 case propertyFlag::V:
589 case propertyFlag::P:
592 throw CanteraError(
"Substance::vprop",
"Invalid job index");
600 double Rhosave = Rho;
602 if (sat >=
Tcrit()) {
607 }
else if (itp == Pgiven) {
608 if (sat >=
Pcrit()) {
621 throw CanteraError(
"Substance::Lever",
"General error");
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;
639void Substance::set_xy(propertyFlag::type ifx, propertyFlag::type ify,
641 double atx,
double aty,
642 double rtx,
double rty)
644 double v_here, t_here;
645 double dvs1 = 2.0*
Vcrit();
646 double dvs2 = 0.7*
Vcrit();
649 double v_save = 1.0/Rho;
652 if ((T == Undef) && (Rho == Undef)) {
657 }
else if (Rho == Undef) {
659 Set(PropertyPair::TV,T,
Vcrit()*1.1);
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);
675 if ((err_x < atx + rtx*Xa) && (err_y < aty + rty*Ya)) {
680 double dt = 0.001*t_here;
681 if (t_here + dt >
Tmax()) {
686 double dv = 0.001*v_here;
687 if (v_here <=
Vcrit()) {
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;
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;
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;
705 double dvm = 0.2*v_here;
712 double dtm = 0.1*t_here;
713 double dva = fabs(dv);
714 double dta = fabs(dt);
727 Set(PropertyPair::TV, t_here, v_here);
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());
742double Substance::prop(propertyFlag::type ijob)
744 if (ijob == propertyFlag::P) {
747 if (ijob == propertyFlag::T) {
751 if ((xx > 0.0) && (xx < 1.0)) {
752 double Rho_save = Rho;
754 double vp = vprop(ijob);
756 double lp = vprop(ijob);
757 double pp = (1.0 - xx)*lp + xx*vp;
765static const double ErrP = 1.e-7;
766static const double Big = 1.e30;
768void Substance::BracketSlope(
double Pressure)
771 dv = (v_here <
Vcrit() ? -0.05*v_here : 0.2*v_here);
779 double dpdv = (Pmax - Pmin)/(Vmax - Vmin);
782 dv = dvbf*(Pressure - P_here)/dpdv;
795 double dvs1 = 2.0*
Vcrit();
796 double dvs2 = 0.7*
Vcrit();
799 double v_save = 1.0/Rho;
805 double v_prev = v_here;
807 double res_prev = 0.0;
808 bool have_prev =
false;
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) {
822 if (v_here < v_prev) {
841 BracketSlope(Pressure);
844 if (v_here <=
Vcrit()) {
847 Set(PropertyPair::TV,
Temp, v_here+dv);
848 double dpdv = (Pp() - P_here)/dv;
850 BracketSlope(Pressure);
852 if ((P_here > Pressure) && (v_here > Vmin)) {
854 }
else if ((P_here < Pressure) && (v_here < Vmax)) {
857 if (v_here == Vmin) {
860 if (v_here == Vmax) {
864 throw CanteraError(
"Substance::set_TPp",
"Vmin >= Vmax");
865 }
else if ((Vmin > 0.0) && (Vmax < Big)) {
871 BracketSlope(Pressure);
873 dv = (Pressure - P_here)/dpdv;
877 double dvm = 0.2*v_here;
885 double vt = v_here + dv;
886 if ((vt < Vmin) || (vt > Vmax)) {
887 dv = Vmin + (Pressure - Pmin)*(Vmax - Vmin)/(Pmax - Pmin) - v_here;
890 double dva = fabs(dv);
895 if (fabs(dv) <= 4.0*std::numeric_limits<double>::epsilon()*fabs(v_here)) {
906 Set(PropertyPair::TV,
Temp, v_here);
908 if (LoopCount > 200) {
909 Set(PropertyPair::TV,
Temp, v_save);
911 "No convergence for P = {}, T = {}\n"
912 "(P* = {}, V* = {})", Pressure,
Temp,
916 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.