19std::string propertySymbols[] = {
"H",
"S",
"U",
"V",
"P",
"T"};
24Substance::Substance() :
32 m_entropy_offset(0.0),
37void Substance::setStdState(
double h0,
double s0,
double t0,
double p0)
39 Set(PropertyPair::TP, t0, p0);
42 double hoff = h0 - hh;
43 double soff = s0 - ss;
44 m_entropy_offset += soff;
45 m_energy_offset += hoff;
50 return TwoPhase() ? Ps() : Pp();
53const double DeltaT = 0.000001;
61 return std::numeric_limits<double>::quiet_NaN();
64 double Tsave = T, dt = 1.e-4 * T;
66 double T1 = std::max(Tmin(), Tsave - dt);
67 double T2 = std::min(Tmax(), Tsave + dt);
71 if ((x0 == 1.0 || x0 == 0.0) && x1 != x0) {
81 if ((x0 == 1.0 || x0 == 0.0) && x2 != x0) {
90 return T*(s2 - s1)/(T2-T1);
97 return std::numeric_limits<double>::infinity();
100 double Tsave = T, dt = 1.e-4 * T;
101 double RhoSave = Rho;
102 double T1, T2, s1, s2;
107 T1 = std::max(Tmin(), Tsave - dt);
108 Set(PropertyPair::TP, T1, p0);
111 T2 = std::min(Tsat(p0), Tsave + dt);
116 if (T2 < Tsave + dt) {
117 Set(PropertyPair::TX, T2, 0.);
119 Set(PropertyPair::TP, T2, p0);
125 T1 = std::max(Tsat(p0), Tsave - dt);
130 if (T1 > Tsave - dt) {
131 Set(PropertyPair::TX, T1, 1.);
133 Set(PropertyPair::TP, T1, p0);
136 T2 = std::min(Tmax(), Tsave + dt);
137 Set(PropertyPair::TP, T2, p0);
141 Set(PropertyPair::TV, Tsave, 1.0 / RhoSave);
142 return T * (s2 - s1) / (T2 - T1);
145double Substance::thermalExpansionCoeff()
147 if (TwoPhase(
true)) {
150 return std::numeric_limits<double>::infinity();
153 double Tsave = T, dt = 1.e-4 * T;
154 double RhoSave = Rho;
155 double T1, T2, v1, v2;
160 T1 = std::max(Tmin(), Tsave - dt);
161 Set(PropertyPair::TP, T1, p0);
164 T2 = std::min(Tsat(p0), Tsave + dt);
169 if (T2 < Tsave + dt) {
170 Set(PropertyPair::TX, T2, 0.);
172 Set(PropertyPair::TP, T2, p0);
178 T1 = std::max(Tsat(p0), Tsave - dt);
183 if (T1 > Tsave - dt) {
184 Set(PropertyPair::TX, T1, 1.);
186 Set(PropertyPair::TP, T1, p0);
189 T2 = std::min(Tmax(), Tsave + dt);
190 Set(PropertyPair::TP, T2, p0);
194 Set(PropertyPair::TV, Tsave, 1.0 / RhoSave);
195 return 2.0 * (v2 - v1) / ((v2 + v1) * (T2 - T1));
198double Substance::isothermalCompressibility()
200 if (TwoPhase(
true)) {
202 return std::numeric_limits<double>::infinity();
205 double Psave = P(), dp = 1.e-4 * Psave;
206 double RhoSave = Rho;
207 double P1, P2, v1, v2;
213 if (T > Tmin() && T <= Tcrit()) {
214 P1 = std::max(Ps(), P1);
216 if (P1 > Psave - dp) {
217 Set(PropertyPair::PX, P1, 0.);
219 Set(PropertyPair::TP, T, P1);
223 Set(PropertyPair::TP, T, P2);
227 P1 = std::max(1.e-7, Psave - dp);
228 Set(PropertyPair::TP, T, P1);
231 if (T > Tmin() && T <= Tcrit()) {
232 P2 = std::min(Ps(), P2);
234 if (P2 < Psave + dp) {
235 Set(PropertyPair::PX, P2, 1.);
237 Set(PropertyPair::TP, T, P2);
242 Set(PropertyPair::TV, T, 1.0 / RhoSave);
243 return -(v2 - v1) / (v0 * (P2 - P1));
246double Substance::dPsdT()
251 if (T + DeltaT < Tcrit()) {
253 dpdt = (Ps() - ps1)/DeltaT;
257 dpdt = (ps1 - Ps())/DeltaT;
263int Substance::TwoPhase(
bool strict)
270 return ((Rho > Rhv) && (Rho < Rhf) ? 1 : 0);
272 return ((Rho >= Rhv) && (Rho <= Rhf) ? 1 : 0);
278 return (1.0/Rho < Vcrit() ? 0.0 : 1.0);
283 }
else if (Rho >= Rhf) {
288 return (1.0/Rho - vl)/(vv - vl);
293double Substance::Tsat(
double p)
296 if (p <= 0. || p > Pcrit()) {
304 "Illegal pressure value: {}", p);
310 double tol = 1.e-6*p;
311 if (T < Tmin() || T > Tcrit()) {
312 T = 0.5*(Tcrit() - Tmin());
315 while (fabs(dp) > tol) {
316 T = std::min(T, Tcrit());
317 T = std::max(T, Tmin());
319 double dt = dp/dPsdT();
320 double dta = fabs(dt);
327 if (LoopCount > 100) {
329 throw CanteraError(
"Substance::Tsat",
"No convergence: p = {}", p);
338static const double TolAbsH = 1.e-4;
339static const double TolAbsU = 1.e-4;
340static const double TolAbsS = 1.e-7;
341static const double TolAbsP = 0.0;
342static const double TolAbsV = 1.e-8;
343static const double TolAbsT = 1.e-3;
344static const double TolRel = 1.e-8;
346void Substance::Set(PropertyPair::type XY,
double x0,
double y0)
353 XY =
static_cast<PropertyPair::type
>(-XY);
357 case PropertyPair::TV:
361 case PropertyPair::HP:
362 if (Lever(Pgiven, y0, x0, propertyFlag::H)) {
365 set_xy(propertyFlag::H, propertyFlag::P,
366 x0, y0, TolAbsH, TolAbsP, TolRel, TolRel);
368 case PropertyPair::SP:
369 if (Lever(Pgiven, y0, x0, propertyFlag::S)) {
372 set_xy(propertyFlag::S, propertyFlag::P,
373 x0, y0, TolAbsS, TolAbsP, TolRel, TolRel);
375 case PropertyPair::PV:
376 if (Lever(Pgiven, x0, y0, propertyFlag::V)) {
379 set_xy(propertyFlag::P, propertyFlag::V,
380 x0, y0, TolAbsP, TolAbsV, TolRel, TolRel);
382 case PropertyPair::TP:
385 if (fabs(y0 - Ps()) / y0 < TolRel) {
387 "Saturated mixture detected: use vapor "
388 "fraction to specify state instead");
389 }
else if (y0 < Ps()) {
390 Set(PropertyPair::TX, x0, 1.0);
392 Set(PropertyPair::TX, x0, 0.0);
395 set_xy(propertyFlag::T, propertyFlag::P,
396 x0, y0, TolAbsT, TolAbsP, TolRel, TolRel);
398 case PropertyPair::UV:
399 set_xy(propertyFlag::U, propertyFlag::V,
400 x0, y0, TolAbsU, TolAbsV, TolRel, TolRel);
402 case PropertyPair::ST:
403 if (Lever(Tgiven, y0, x0, propertyFlag::S)) {
406 set_xy(propertyFlag::S, propertyFlag::T,
407 x0, y0, TolAbsS, TolAbsT, TolRel, TolRel);
409 case PropertyPair::SV:
410 set_xy(propertyFlag::S, propertyFlag::V,
411 x0, y0, TolAbsS, TolAbsV, TolRel, TolRel);
413 case PropertyPair::UP:
414 if (Lever(Pgiven, y0, x0, propertyFlag::U)) {
417 set_xy(propertyFlag::U, propertyFlag::P,
418 x0, y0, TolAbsU, TolAbsP, TolRel, TolRel);
420 case PropertyPair::VH:
421 set_xy(propertyFlag::V, propertyFlag::H,
422 x0, y0, TolAbsV, TolAbsH, TolRel, TolRel);
424 case PropertyPair::TH:
425 set_xy(propertyFlag::T, propertyFlag::H,
426 x0, y0, TolAbsT, TolAbsH, TolRel, TolRel);
428 case PropertyPair::SH:
429 set_xy(propertyFlag::S, propertyFlag::H,
430 x0, y0, TolAbsS, TolAbsH, TolRel, TolRel);
432 case PropertyPair::PX:
435 if (y0 > 1.0 || y0 < 0.0) {
437 "Invalid vapor fraction, {}", y0);
439 if (x0 < Ps() || x0 > Pcrit()) {
441 "Illegal pressure value: {} (supercritical "
442 "or below triple point)", x0);
447 Rho = 1.0/((1.0 - y0)/Rhf + y0/Rhv);
449 case PropertyPair::TX:
450 if (y0 > 1.0 || y0 < 0.0) {
452 "Invalid vapor fraction, {}", y0);
454 if (x0 < Tmin() || x0 > Tcrit()) {
456 "Illegal temperature value: {} "
457 "(supercritical or below triple point)", x0);
461 Rho = 1.0/((1.0 - y0)/Rhf + y0/Rhv);
470void Substance::set_Rho(
double r0)
475 throw CanteraError(
"Substance::set_Rho",
"Invalid density: {}", r0);
479void Substance::set_T(
double t0)
481 if ((t0 >= Tmin()) && (t0 <= Tmax())) {
484 throw CanteraError(
"Substance::set_T",
"Illegal temperature: {}", t0);
488void Substance::set_v(
double v0)
494 "Negative specific volume: {}", v0);
498double Substance::Ps()
500 if (T < Tmin() || T > Tcrit()) {
502 "Illegal temperature value: {}", T);
508void Substance::update_sat()
511 double Rho_save = Rho;
513 double lps = log(pp);
516 for (i = 0; i<20; i++) {
525 double gf = hp() - T*sp();
534 double gv = hp() - T*sp();
541 if (fabs(dg) < 0.001) {
544 double dp = dg/(1.0/Rhv - 1.0/Rhf);
547 lps -= dg/(pp*(1.0/Rhv - 1.0/Rhf));
554 pp = psold + 0.5*(Pcrit() - psold);
556 }
else if (pp < 0.0) {
563 throw CanteraError(
"Substance::update_sat",
"No convergence");
572double Substance::vprop(propertyFlag::type ijob)
575 case propertyFlag::H:
577 case propertyFlag::S:
579 case propertyFlag::U:
581 case propertyFlag::V:
583 case propertyFlag::P:
586 throw CanteraError(
"Substance::vprop",
"Invalid job index");
590int Substance::Lever(
int itp,
double sat,
double val, propertyFlag::type ifunc)
594 double Rhosave = Rho;
596 if (sat >= Tcrit()) {
601 }
else if (itp == Pgiven) {
602 if (sat >= Pcrit()) {
615 throw CanteraError(
"Substance::Lever",
"General error");
617 Set(PropertyPair::TX, T, 1.0);
618 double Valg = vprop(ifunc);
619 Set(PropertyPair::TX, T, 0.0);
620 double Valf = vprop(ifunc);
621 if (val >= Valf && val <= Valg) {
622 double xx = (val - Valf)/(Valg - Valf);
623 double vv = (1.0 - xx)/Rhf + xx/Rhv;
633void Substance::set_xy(propertyFlag::type ifx, propertyFlag::type ify,
635 double atx,
double aty,
636 double rtx,
double rty)
638 double v_here, t_here;
639 double dvs1 = 2.0*Vcrit();
640 double dvs2 = 0.7*Vcrit();
643 double v_save = 1.0/Rho;
648 Set(PropertyPair::TV,Tcrit()*1.1,Vcrit()*1.1);
651 }
else if (Rho ==
Undef) {
653 Set(PropertyPair::TV,T,Vcrit()*1.1);
664 double x_here = prop(ifx);
665 double y_here = prop(ify);
666 double err_x = fabs(X - x_here);
667 double err_y = fabs(Y - y_here);
669 if ((err_x < atx + rtx*Xa) && (err_y < aty + rty*Ya)) {
674 double dt = 0.001*t_here;
675 if (t_here + dt > Tmax()) {
680 double dv = 0.001*v_here;
681 if (v_here <= Vcrit()) {
686 Set(PropertyPair::TV, t_here + dt, v_here);
687 double dxdt = (prop(ifx) - x_here)/dt;
688 double dydt = (prop(ify) - y_here)/dt;
691 Set(PropertyPair::TV, t_here, v_here + dv);
692 double dxdv = (prop(ifx) - x_here)/dv;
693 double dydv = (prop(ify) - y_here)/dv;
695 double det = dxdt*dydv - dydt*dxdv;
696 dt = ((X - x_here)*dydv - (Y - y_here)*dxdv)/det;
697 dv = ((Y - y_here)*dxdt - (X - x_here)*dydt)/det;
699 double dvm = 0.2*v_here;
706 double dtm = 0.1*t_here;
707 double dva = fabs(dv);
708 double dta = fabs(dt);
717 t_here =
clip(t_here, Tmin(), Tmax());
721 Set(PropertyPair::TV, t_here, v_here);
723 if (LoopCount > 200) {
724 std::string msg = fmt::format(
"No convergence. {} = {}, {} = {}",
725 propertySymbols[ifx], X, propertySymbols[ify], Y);
726 if (t_here == Tmin()) {
727 msg += fmt::format(
"\nAt temperature limit (Tmin = {})", Tmin());
728 }
else if (t_here == Tmax()) {
729 msg += fmt::format(
"\nAt temperature limit (Tmax = {})", Tmax());
736double Substance::prop(propertyFlag::type ijob)
738 if (ijob == propertyFlag::P) {
741 if (ijob == propertyFlag::T) {
745 if ((xx > 0.0) && (xx < 1.0)) {
746 double Rho_save = Rho;
748 double vp = vprop(ijob);
750 double lp = vprop(ijob);
751 double pp = (1.0 - xx)*lp + xx*vp;
759static const double ErrP = 1.e-7;
760static const double Big = 1.e30;
762void Substance::BracketSlope(
double Pressure)
765 dv = (v_here < Vcrit() ? -0.05*v_here : 0.2*v_here);
773 double dpdv = (Pmax - Pmin)/(Vmax - Vmin);
776 dv = dvbf*(Pressure - P_here)/dpdv;
781void Substance::set_TPp(
double Temp,
double Pressure)
789 double dvs1 = 2.0*Vcrit();
790 double dvs2 = 0.7*Vcrit();
793 double v_save = 1.0/Rho;
798 while (P_here = Pp(),
799 fabs(Pressure - P_here) >= ErrP* Pressure || LoopCount == 0) {
801 BracketSlope(Pressure);
804 if (v_here <= Vcrit()) {
807 Set(PropertyPair::TV, Temp, v_here+dv);
808 double dpdv = (Pp() - P_here)/dv;
810 BracketSlope(Pressure);
812 if ((P_here > Pressure) && (v_here > Vmin)) {
814 }
else if ((P_here < Pressure) && (v_here < Vmax)) {
817 if (v_here == Vmin) {
820 if (v_here == Vmax) {
824 throw CanteraError(
"Substance::set_TPp",
"Vmin >= Vmax");
825 }
else if ((Vmin > 0.0) && (Vmax < Big)) {
831 BracketSlope(Pressure);
833 dv = (Pressure - P_here)/dpdv;
837 double dvm = 0.2*v_here;
845 double vt = v_here + dv;
846 if ((vt < Vmin) || (vt > Vmax)) {
847 dv = Vmin + (Pressure - Pmin)*(Vmax - Vmin)/(Pmax - Pmin) - v_here;
850 double dva = fabs(dv);
856 throw CanteraError(
"Substance::set_TPp",
"dv = 0 and no convergence");
858 Set(PropertyPair::TV, Temp, v_here);
860 if (LoopCount > 200) {
861 Set(PropertyPair::TV, Temp, v_save);
863 "No convergence for P = {}, T = {}\n"
864 "(P* = {}, V* = {})", Pressure, Temp,
865 Pressure/Pcrit(), v_save/Vcrit());
868 Set(PropertyPair::TV, Temp,v_here);
Base class for exceptions thrown by Cantera classes.
This file contains definitions for utility functions and text for modules, inputfiles,...
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...
const double GasConstant
Universal Gas Constant [J/kmol/K].
T clip(const T &value, const T &lower, const T &upper)
Clip value such that lower <= value <= upper.
Contains declarations for string manipulation functions within Cantera.