19 std::string propertySymbols[] = {
"H",
"S",
"U",
"V",
"P",
"T"};
24 Substance::Substance() :
32 m_entropy_offset(0.0),
37 void 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();
53 const double DeltaT = 0.000001;
55 double Substance::cv()
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);
93 double Substance::cp()
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);
145 double 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));
198 double 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));
246 double Substance::dPsdT()
251 if (T + DeltaT < Tcrit()) {
253 dpdt = (Ps() - ps1)/DeltaT;
257 dpdt = (ps1 - Ps())/DeltaT;
263 int Substance::TwoPhase(
bool strict)
270 return ((Rho > Rhv) && (Rho < Rhf) ? 1 : 0);
272 return ((Rho >= Rhv) && (Rho <= Rhf) ? 1 : 0);
275 double Substance::x()
278 return (1.0/Rho < Vcrit() ? 0.0 : 1.0);
283 }
else if (Rho >= Rhf) {
288 return (1.0/Rho - vl)/(vv - vl);
293 double 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);
338 static const double TolAbsH = 1.e-4;
339 static const double TolAbsU = 1.e-4;
340 static const double TolAbsS = 1.e-7;
341 static const double TolAbsP = 0.0;
342 static const double TolAbsV = 1.e-8;
343 static const double TolAbsT = 1.e-3;
344 static const double TolRel = 1.e-8;
346 void 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);
470 void Substance::set_Rho(
double r0)
475 throw CanteraError(
"Substance::set_Rho",
"Invalid density: {}", r0);
479 void Substance::set_T(
double t0)
481 if ((t0 >= Tmin()) && (t0 <= Tmax())) {
484 throw CanteraError(
"Substance::set_T",
"Illegal temperature: {}", t0);
488 void Substance::set_v(
double v0)
494 "Negative specific volume: {}", v0);
498 double Substance::Ps()
500 if (T < Tmin() || T > Tcrit()) {
502 "Illegal temperature value: {}", T);
508 void 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");
572 double 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");
590 int 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;
633 void 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());
736 double 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;
759 static const double ErrP = 1.e-7;
760 static const double Big = 1.e30;
762 void 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;
781 void 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);