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);
154 }
catch (CanteraError&) {
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);
168 }
catch (CanteraError&) {
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)
499 throw CanteraError(
"Substance::set_v",
500 "Negative specific volume: {}", v0);
504double Substance::Ps()
507 throw CanteraError(
"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());
737 throw CanteraError(
"Substance::set_xy", msg);
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.
const double Undef
Fairly random number to be used to initialize variables against to see if they are subsequently defin...
Contains declarations for string manipulation functions within Cantera.